From 2e22f2702c99a3afdb8e5a4a5460790aa3259f4b Mon Sep 17 00:00:00 2001 From: Gerkiz Date: Sun, 16 Nov 2025 21:12:05 +0100 Subject: [PATCH] Fix modules --- control.lua | 192 +++++------ maps/fish_defender_v2/main.lua | 6 + utils/commands/hotpatch.lua | 47 ++- utils/datastore/init.lua | 2 +- utils/datastore/warning_data.lua | 411 +++++++++++++++++++++++ utils/datastore/warning_on_join_data.lua | 144 -------- 6 files changed, 551 insertions(+), 251 deletions(-) create mode 100644 utils/datastore/warning_data.lua delete mode 100644 utils/datastore/warning_on_join_data.lua diff --git a/control.lua b/control.lua index f9753fe4..9533226b 100644 --- a/control.lua +++ b/control.lua @@ -27,6 +27,102 @@ require 'modules.autostash' require 'utils.remote_chunks' +---------------- ENABLE MAPS HERE ---------------- +--!Make sure only one map is enabled at a time. +--!Remove the "--" in front of the line to enable. +--!All lines with the "require" keyword are different maps. + +--![[Comfylatron has seized the Fish Train and turned it into a time machine]]-- +--require 'maps.chronosphere.main' + +--![[Guide a Train through rough terrain, while defending it from the biters]]-- +--require 'maps.mountain_fortress_v3.main' +--require 'maps.mountain_fortress_v2.main' +--require 'maps.mountain_fortress' + +--![[Launch rockets in increasingly harder getting worlds.]]-- +--require 'maps.journey.main' + +--![[Infestation Islands]]-- +--require 'maps.infestation_islands.main' + +--![[Infinite random dungeon with RPG]]-- +--require 'maps.dungeons.main' + +--![[Defend the market against waves of biters]]-- +--require 'maps.fish_defender_v2.main' +--require 'maps.crab_defender.main' +--require 'maps.fish_defender_v1.fish_defender' +--require 'maps.fish_defender.main' + +--![[Adventure as a crew of pirates]]-- +--require 'maps.pirates.main' + +--![[East VS West Survival PVP, where you breed biters with science flasks]]-- +--require 'maps.biter_hatchery.main' + +--![[Fight in a world where everyone are prisoners]] +--require 'maps.planet_prison' + +--![[Chop trees to gain resources]]-- +--require 'maps.choppy' +--require 'maps.choppy_dx' + +--![[Minesweeper?]]-- +--require 'maps.minesweeper.main' + +--![[Tower defense system]]-- +--require 'maps.tower_defense.main' + +--![[North VS South Survival PVP, feed the opposing team's biters with science flasks. Disable Autostash, Group and Poll modules.]]-- +--require 'maps.biter_battles_v2.main' +--require 'maps.biter_battles.biter_battles' + +--![[Randomly generating Islands that have to be beaten in levels to gain credits]]-- +--require 'maps.island_troopers.main' + +--![[Infinitely expanding mazes]]-- +--require 'maps.stone_maze.main' +--require 'maps.labyrinth' + +--![[Extreme survival mode with thirst and limited building room]]-- +--require 'maps.desert_oasis' + +--![[The trees are your enemy here]]-- +--require 'maps.overgrowth' + +--![[Wave Defense Map split in 4 Quarters]]-- +--require 'maps.quarters' + +--![[Flee from the collapsing map with portable base inside train]]-- +--require 'maps.railway_troopers_v2.main' + +--![[Another simliar version without collapsing terrain]]-- +--require 'maps.railway_troopers.main' + +--![[Territorial Control - reveal the map as you walk through the mist]]-- +--require 'maps.territorial_control' + +--![[Deep Jungle - dangerous map]]-- +--require 'maps.deep_jungle.main' + +--![[You fell in a dark cave, will you survive?]]-- +--require 'maps.cave_choppy.main' +--require 'maps.cave_miner' +--require 'maps.cave_miner_v2.main' + +--![[Hungry boxes eat your items, but reward you with new territory to build.]]-- +--require 'maps.expanse.main' + +--![[Dangerous forest with unique map revealing]]-- +--require 'maps.spooky_forest' + +--![[Defeat the biters and unlock new areas]]-- +--require 'maps.spiral_troopers' + +--![[Test map spawns all entities for testing]]-- +--require 'maps.test_map.main' + ---------------- !ENABLE MODULES HERE ---------------- --require 'modules.rpg.main' --require 'modules.admins_operate_biters' @@ -74,102 +170,6 @@ require 'utils.remote_chunks' --require 'modules.turret_filler' --------------------------------------------------------------- ----------------- ENABLE MAPS HERE ---------------- ---!Make sure only one map is enabled at a time. ---!Remove the "--" in front of the line to enable. ---!All lines with the "require" keyword are different maps. - ---![[North VS South Survival PVP, feed the opposing team's biters with science flasks. Disable Autostash, Group and Poll modules.]]-- ---require 'maps.biter_battles_v2.main' ---require 'maps.biter_battles.biter_battles' - ---![[Guide a Train through rough terrain, while defending it from the biters]]-- ---require 'maps.mountain_fortress_v3.main' ---require 'maps.mountain_fortress_v2.main' ---require 'maps.mountain_fortress' - ---![[Comfylatron has seized the Fish Train and turned it into a time machine]]-- ---require 'maps.chronosphere.main' - ---![[Launch rockets in increasingly harder getting worlds.]]-- ---require 'maps.journey.main' - ---![[Infestation Islands]]-- ---require 'maps.infestation_islands.main' - ---![[Infinite random dungeon with RPG]]-- ---require 'maps.dungeons.main' - ---![[Defend the market against waves of biters]]-- ---require 'maps.fish_defender_v2.main' ---require 'maps.crab_defender.main' ---require 'maps.fish_defender_v1.fish_defender' ---require 'maps.fish_defender.main' - ---![[Adventure as a crew of pirates]]-- ---require 'maps.pirates.main' - ---![[East VS West Survival PVP, where you breed biters with science flasks]]-- ---require 'maps.biter_hatchery.main' - ---![[Fight in a world where everyone are prisoners]] ---require 'maps.planet_prison' - ---![[Chop trees to gain resources]]-- ---require 'maps.choppy' ---require 'maps.choppy_dx' - ---![[Minesweeper?]]-- ---require 'maps.minesweeper.main' - ---![[Tower defense system]]-- ---require 'maps.tower_defense.main' - ---![[Randomly generating Islands that have to be beaten in levels to gain credits]]-- ---require 'maps.island_troopers.main' - ---![[Infinitely expanding mazes]]-- ---require 'maps.stone_maze.main' ---require 'maps.labyrinth' - ---![[Extreme survival mode with thirst and limited building room]]-- ---require 'maps.desert_oasis' - ---![[The trees are your enemy here]]-- ---require 'maps.overgrowth' - ---![[Wave Defense Map split in 4 Quarters]]-- ---require 'maps.quarters' - ---![[Flee from the collapsing map with portable base inside train]]-- ---require 'maps.railway_troopers_v2.main' - ---![[Another simliar version without collapsing terrain]]-- ---require 'maps.railway_troopers.main' - ---![[Territorial Control - reveal the map as you walk through the mist]]-- ---require 'maps.territorial_control' - ---![[Deep Jungle - dangerous map]]-- ---require 'maps.deep_jungle.main' - ---![[You fell in a dark cave, will you survive?]]-- ---require 'maps.cave_choppy.main' ---require 'maps.cave_miner' ---require 'maps.cave_miner_v2.main' - ---![[Hungry boxes eat your items, but reward you with new territory to build.]]-- ---require 'maps.expanse.main' - ---![[Dangerous forest with unique map revealing]]-- ---require 'maps.spooky_forest' - ---![[Defeat the biters and unlock new areas]]-- ---require 'maps.spiral_troopers' - ---![[Test map spawns all entities for testing]]-- ---require 'maps.test_map.main' - --- this file exists only for the panel to sync and start from within the panel -- it does nothing if it's not synced from within the panel diff --git a/maps/fish_defender_v2/main.lua b/maps/fish_defender_v2/main.lua index d92eb266..db76326d 100644 --- a/maps/fish_defender_v2/main.lua +++ b/maps/fish_defender_v2/main.lua @@ -19,6 +19,7 @@ local Score = require 'utils.gui.score' local AntiGrief = require 'utils.antigrief' local Core = require 'utils.core' local RobotLimits = require 'modules.robot_limits' +local JailData = require 'utils.datastore.jail_data' local format_number = require 'util'.format_number local random = math.random local insert = table.insert @@ -1487,6 +1488,7 @@ function Public.reset_game() end Difficulty.reset_difficulty_poll({ difficulty_poll_closing_timeout = wave_grace_period }) + local difficulties = { [1] = @@ -1602,6 +1604,8 @@ function Public.reset_game() return end + JailData.set_valid_surface(surface.name) + for _, tile in pairs(surface.find_tiles_filtered({ name = { 'water', 'deepwater' }, area = { { -300, -256 }, { 300, 300 } } })) do surface.set_tiles({ { name = Public.get_replacement_tile(surface, tile.position), position = { tile.position.x, tile.position.y } } }, true) end @@ -1691,6 +1695,8 @@ local function on_tick() market.die() game.print('Game won!', { r = 0.22, g = 0.88, b = 0.22 }) game.print('Game wave limit reached! Game will soft-reset shortly.', { r = 0.22, g = 0.88, b = 0.22 }) + local message = 'Game won! Game wave limit reached! Game will soft-reset shortly.' + Server.to_discord_bold(table.concat { '*** ', message, ' ***' }) end end diff --git a/utils/commands/hotpatch.lua b/utils/commands/hotpatch.lua index e635855f..99ae7588 100644 --- a/utils/commands/hotpatch.lua +++ b/utils/commands/hotpatch.lua @@ -4,9 +4,11 @@ local Task = require 'utils.task_token' local Global = require 'utils.global' local hotpatch = '[color=yellow]Hotpatch:[/color] ' -local halt_after_timer = 60 * 60 * 1 -- 1 minute -local this = {} +local this = +{ + halt_after_timer = 60 * 60 * 1 -- 1 minute +} Global.register( this, @@ -21,6 +23,8 @@ local save_hot_patch_token = Task.register( this.halted = nil return end + this.halt_after = nil + this.halted = nil Server.save_hot_patch() end @@ -43,23 +47,42 @@ local save_hot_patch_notify_token = end game.print(hotpatch .. 'The server will be stopped in ' .. time_left .. ' seconds.') - game.print(hotpatch .. 'To abort the hotpatch, use the command /save-hot-patch-abort.') + game.print(hotpatch .. 'To abort the hotpatch, use the command /abort-patch-save.') end end ) -Commands.new('save-hot-patch', 'Tries to hotpatch the current save from the panel if possible.') +Commands.new('patch-save', 'Tries to hotpatch the current save from the panel if possible.') :require_admin() + :add_alias('ps') + :add_alias('hot-patch') :require_backend() + :add_parameter('halt_after', true, 'number') :require_validation('Running this will stop the server and hotpatch, only run this if you really want to!') :callback( - function (player) + function (player, halt_after) + if halt_after then + if halt_after == 0 then + this.halt_after_timer = 60 + elseif halt_after < 1 then + player.print(hotpatch .. 'Halt after time cannot be less than 1 minute.') + return false + elseif halt_after > 60 then + player.print(hotpatch .. 'Halt after time cannot be greater than 60 minutes.') + return false + end + + if not this.halt_after_timer == 60 then + this.halt_after_timer = 60 * 60 * halt_after + end + end + if not this.halt_after then - this.halt_after = game.tick + halt_after_timer + this.halt_after = game.tick + this.halt_after_timer game.print(hotpatch .. 'Save hot-patching has been initiated by ' .. player.name .. '.') - game.print(hotpatch .. 'The server will be stopped, hotpatched and resumed in ' .. math.round(halt_after_timer / 60, 0) .. ' seconds.') - Task.set_duration_task(500, halt_after_timer, save_hot_patch_notify_token, {}) - Task.set_timeout_in_ticks(halt_after_timer, save_hot_patch_token) + game.print(hotpatch .. 'The server will be stopped, hotpatched and resumed in ' .. math.round(this.halt_after_timer / 60, 0) .. ' seconds.') + Task.set_duration_task(500, this.halt_after_timer, save_hot_patch_notify_token, {}) + Task.set_timeout_in_ticks(this.halt_after_timer, save_hot_patch_token) else player.print(hotpatch .. 'A hotpatch is already in progress.') return false @@ -67,11 +90,15 @@ Commands.new('save-hot-patch', 'Tries to hotpatch the current save from the pane end ) -Commands.new('save-hot-patch-abort', 'Aborts the hotpatch if it is in progress.') +Commands.new('abort-patch-save', 'Aborts the hotpatch if it is in progress.') :require_admin() :require_backend() + :add_alias('aps') + :add_alias('hot-patch-abort') :callback( function (player) + this.halt_after_timer = 60 * 60 * 1 + if not this.halted and this.halt_after then game.print(hotpatch .. 'Hotpatch has been aborted by ' .. player.name .. '.') this.halted = true diff --git a/utils/datastore/init.lua b/utils/datastore/init.lua index c0180727..572d0ac5 100644 --- a/utils/datastore/init.lua +++ b/utils/datastore/init.lua @@ -4,7 +4,7 @@ require 'utils.datastore.session_data' require 'utils.datastore.statistics' require 'utils.datastore.jail_data' require 'utils.datastore.quickbar_data' -require 'utils.datastore.warning_on_join_data' +require 'utils.datastore.warning_data' require 'utils.datastore.message_on_join_data' require 'utils.datastore.player_tag_data' require 'utils.datastore.supporters' diff --git a/utils/datastore/warning_data.lua b/utils/datastore/warning_data.lua new file mode 100644 index 00000000..698b19a2 --- /dev/null +++ b/utils/datastore/warning_data.lua @@ -0,0 +1,411 @@ +-- created by Gerkiz for ComfyFactorio +local Global = require 'utils.global' +local Server = require 'utils.server' +local Event = require 'utils.event' +local Utils = require 'utils.core' +local Gui = require 'utils.gui' +local Commands = require 'utils.commands' +local Token = require 'utils.token' +local Discord = require 'utils.discord' +local DiscordHandler = require 'utils.discord_handler' + +local module_name = '[Warning handler] ' + +local warning_data_set = 'warnings' +local warnings = {} + +local set_data = Server.set_data +local try_get_data = Server.try_get_data + +local warning_frame_name = Gui.uid_name() +local ok_button_name = Gui.uid_name() + +Global.register( + { + warnings = warnings + }, + function (t) + warnings = t.warnings + end +) + +local Public = {} + +local function generate_warning_id() + return game.tick .. '_' .. math.random(1000, 9999) +end + +local function set_character_state(player, state) + if not player or not player.valid then + return false + end + + if player.character ~= nil then + player.character.active = state + end + + return true +end + +local function draw_warning_frame(player, warning_data) + local main_frame, inside_table = Gui.add_main_frame_with_toolbar(player, 'screen', warning_frame_name, nil, nil, 'Warning', true, 2) + + if not main_frame or not inside_table then + return + end + + main_frame.style.width = 500 + main_frame.auto_center = true + + local content_flow = inside_table.add + { + type = 'flow', + direction = 'vertical' + } + content_flow.style.top_padding = 16 + content_flow.style.bottom_padding = 16 + content_flow.style.left_padding = 24 + content_flow.style.right_padding = 24 + content_flow.style.vertical_spacing = 12 + + local top_row = content_flow.add + { + type = 'flow', + direction = 'horizontal' + } + + local sprite_flow = top_row.add { type = 'flow' } + sprite_flow.style.vertical_align = 'center' + sprite_flow.add { type = 'sprite', sprite = 'utility/warning_icon' } + + local label_flow = top_row.add { type = 'flow' } + label_flow.style.left_padding = 24 + label_flow.style.top_padding = 6 + + local warning_message = '[font=heading-2]You have received a warning[/font]\n' .. (warning_data.reason or 'No reason provided.') + + local label = label_flow.add + { + type = 'label', + caption = warning_message + } + label.style.single_line = false + label.style.width = 400 + + local notice_message = '[font=heading-2]Breaking our rules multiple times will result in a ban.[/font]' + + local notice_label = content_flow.add + { + type = 'label', + caption = notice_message + } + notice_label.style.single_line = false + notice_label.style.width = 400 + + local bottom_flow = main_frame.add + { + type = 'flow', + direction = 'horizontal' + } + + local right_flow = bottom_flow.add { type = 'flow' } + right_flow.style.horizontally_stretchable = true + right_flow.style.horizontal_align = 'right' + + set_character_state(player, false) + + local ok_button = right_flow.add + { + type = 'button', + name = ok_button_name, + caption = 'OK', + style = 'confirm_button' + } + + Gui.set_data(ok_button, { warning_id = warning_data.id }) + + player.opened = main_frame +end + +local function show_next_warning(player) + if not warnings[player.name] then + return + end + + local player_warnings = warnings[player.name] + for _, warning in ipairs(player_warnings) do + if not warning.accepted then + draw_warning_frame(player, warning) + return + end + end +end + + +local function send_warning_discord_message(offender_name, admin_name, reason, accepted) + local data = Server.build_embed_data() + data.username = offender_name + data.admin = admin_name + data.reason = reason + data.accepted = accepted or false + + local message = offender_name .. ' has ' .. (accepted and 'accepted' or 'received') .. ' a warning from ' .. admin_name .. '. Reason: ' .. reason + DiscordHandler.send_notification( + { + title = 'Warning', + description = message, + color = 'warning' + }) +end + +local function assign_warning(offender_name, admin_name, reason) + if not offender_name or not reason then + return false + end + + local offender = game.get_player(offender_name) + local date = Server.get_current_date_with_time() + local warning_id = generate_warning_id() + + local warning_data = + { + id = warning_id, + reason = reason, + admin = admin_name, + date = date, + accepted = false + } + + if not warnings[offender_name] then + warnings[offender_name] = {} + end + + table.insert(warnings[offender_name], warning_data) + + set_data(warning_data_set, offender_name, warnings[offender_name]) + + send_warning_discord_message(offender_name, admin_name, reason, false) + + if offender and offender.valid then + local frame = offender.gui.screen[warning_frame_name] + if not frame or not frame.valid then + draw_warning_frame(offender, warning_data) + end + end + + return true +end + +local function accept_warning(player_name, warning_id) + if not warnings[player_name] then + return false + end + + local player_warnings = warnings[player_name] + local warning_data = nil + + for _, warning in ipairs(player_warnings) do + if warning.id == warning_id then + warning_data = warning + break + end + end + + if not warning_data then + return false + end + + warning_data.accepted = true + warning_data.accepted_date = Server.get_current_date_with_time() + + set_data(warning_data_set, player_name, player_warnings) + + warnings[player_name] = player_warnings + + send_warning_discord_message(player_name, warning_data.admin, warning_data.reason, true) + + local p = game.get_player(player_name) + if p and p.valid then + set_character_state(p, true) + end + + return true +end + +local load_warning_token = + Token.register( + function (data) + local key = data.key + local value = data.value + + if not key or not value then + return + end + + local player = game.get_player(key) + if not player or not player.valid then + return + end + + if type(value) ~= 'table' then + return + end + + if not value[1] and value.id then + value = { value } + end + + if not value[1] then + return + end + + local needs_save = false + for _, warning in ipairs(value) do + if not warning.id then + warning.id = generate_warning_id() + needs_save = true + end + end + + warnings[key] = value + + if needs_save then + set_data(warning_data_set, key, value) + end + + show_next_warning(player) + end + ) + +function Public.try_dl_data(key) + key = tostring(key) + + local secs = Server.get_current_time() + if not secs then + return + else + try_get_data(warning_data_set, key, load_warning_token) + end +end + +Commands.new('warn', 'Assigns a warning to a player.') + :add_parameter('offender', false, 'player') + :add_parameter('reason', false, 'string') + :require_backend() + :callback(function (player, offender, reason) + if not offender then + Utils.print_to(player, module_name .. 'No valid player given.') + return false + end + + if not reason or string.len(reason) <= 0 then + Utils.print_to(player, module_name .. 'No valid reason was given.') + return false + end + + if string.len(reason) < 10 then + Utils.print_to(player, module_name .. 'Reason is too short.') + return false + end + + if assign_warning(offender.name, player.name, reason) then + Utils.print_to(player, module_name .. 'Warning assigned to ' .. offender.name .. '.') + return true + else + Utils.print_to(player, module_name .. 'Failed to assign warning.') + return false + end + end) + +Event.add( + defines.events.on_player_joined_game, + function (event) + local player = game.get_player(event.player_index) + if not player or not player.valid then + return + end + + Public.try_dl_data(player.name) + end +) + +Gui.on_click( + ok_button_name, + function (event) + local player = event.player + if not player or not player.valid then + return + end + + local screen = player.gui.screen + local frame = screen[warning_frame_name] + if not frame or not frame.valid then + return + end + + local data = Gui.get_data(event.element) + if not data or not data.warning_id then + return + end + + accept_warning(player.name, data.warning_id) + + frame.destroy() + + show_next_warning(player) + end +) + +Server.on_data_set_changed( + warning_data_set, + function (data) + if not data then + return + end + + local key = data.key + local value = data.value + + if not key or not value then + return + end + + if type(value) ~= 'table' then + warnings[key] = nil + return + end + + if not value[1] and value.id then + value = { value } + end + + if not value[1] then + warnings[key] = nil + return + end + + for _, warning in ipairs(value) do + if not warning.id then + warning.id = generate_warning_id() + end + end + + warnings[key] = value + + local player = game.get_player(key) + if player and player.valid then + local frame = player.gui.screen[warning_frame_name] + if not frame or not frame.valid then + show_next_warning(player) + end + end + end +) + +Public.assign_warning = assign_warning +Public.accept_warning = accept_warning +Public.get_warnings = function () + return warnings +end + +return Public diff --git a/utils/datastore/warning_on_join_data.lua b/utils/datastore/warning_on_join_data.lua deleted file mode 100644 index c9aa03c6..00000000 --- a/utils/datastore/warning_on_join_data.lua +++ /dev/null @@ -1,144 +0,0 @@ --- created by Gerkiz for ComfyFactorio -local Token = require 'utils.token' -local Server = require 'utils.server' -local Event = require 'utils.event' -local Gui = require 'utils.gui' - -local dataset = 'warnings' -local set_data = Server.set_data -local try_get_data = Server.try_get_data -local warning_frame_name = Gui.uid_name() -local discard_button_name = Gui.uid_name() - -local Public = {} - -local function draw_warning_frame(player, message) - local main_frame, inside_table = Gui.add_main_frame_with_toolbar(player, 'screen', warning_frame_name, nil, nil, 'Warning', true, 2) - - if not main_frame or not inside_table then - return - end - - local main_frame_style = main_frame.style - main_frame_style.width = 400 - main_frame.auto_center = true - - local content_flow = inside_table.add {type = 'flow', direction = 'horizontal'} - content_flow.style.top_padding = 16 - content_flow.style.bottom_padding = 16 - content_flow.style.left_padding = 24 - content_flow.style.right_padding = 24 - content_flow.style.horizontally_stretchable = false - - local sprite_flow = content_flow.add {type = 'flow'} - sprite_flow.style.vertical_align = 'center' - sprite_flow.style.vertically_stretchable = false - - sprite_flow.add {type = 'sprite', sprite = 'utility/warning_icon'} - - local label_flow = content_flow.add {type = 'flow'} - label_flow.style.horizontal_align = 'left' - label_flow.style.top_padding = 10 - label_flow.style.left_padding = 24 - - local warning_message = '[font=heading-2]Message from Comfy to: ' .. player.name .. '[/font]\n' .. message - - label_flow.style.horizontally_stretchable = false - local label = label_flow.add {type = 'label', caption = warning_message} - label.style.single_line = false - - local bottom_flow = main_frame.add({type = 'flow', direction = 'horizontal'}) - - local left_flow = bottom_flow.add({type = 'flow'}) - left_flow.style.horizontal_align = 'left' - left_flow.style.horizontally_stretchable = true - - local close_button = left_flow.add({type = 'button', name = discard_button_name, caption = 'Understood'}) - close_button.style = 'back_button' - - player.opened = main_frame -end - -local fetch = - Token.register( - function(data) - local key = data.key - if not key then - return - end - - local value = data.value - if not value then - return - end - - local player = game.get_player(key) - if not player or not player.valid then - return - end - draw_warning_frame(player, value) - end -) - ---- Tries to get data from the webpanel and applies the value to the player. --- @param data_set player token -function Public.fetch(key) - local secs = Server.get_current_time() - if not secs then - local player = game.players[key] - if not player or not player.valid then - return - end - return - else - try_get_data(dataset, key, fetch) - end -end - -Event.add( - defines.events.on_player_joined_game, - function(event) - local player = game.get_player(event.player_index) - if not player or not player.valid then - return - end - - Public.fetch(player.name) - end -) - -Gui.on_click( - discard_button_name, - function(event) - local player = event.player - local screen = player.gui.screen - local frame = screen[warning_frame_name] - if not player or not player.valid then - return - end - - if frame and frame.valid then - frame.destroy() - set_data(dataset, player.name) - end - end -) - -Server.on_data_set_changed( - dataset, - function(data) - if not data then - return - end - - local key = data.key - local value = data.value - local player = game.get_player(key) - if not player or not player.valid then - return - end - draw_warning_frame(player, value) - end -) - -return Public