local Server = require 'utils.server' local Event = require 'utils.event' local CustomEvents = require 'utils.created_events' local Global = require 'utils.global' local Commands = require 'utils.commands' local Task = require 'utils.task_token' local Discord = require 'utils.discord_handler' local module_name = '[Undo actions] ' local undo_polls = {} local Public = {} Global.register( { undo_polls = undo_polls }, function (tbl) undo_polls = tbl.undo_polls end ) local make_entity_destructible_token = Task.register( function (event) local entity = event.entity if not entity or not entity.valid then return end entity.destructible = true end ) local function check_undo_queue(player) if not type(player) == 'userdata' then error('Player is not userdata.') end local undo_redo_stack = player.undo_redo_stack if undo_redo_stack and undo_redo_stack.get_undo_item_count() > 0 then return undo_redo_stack.get_undo_item_count() end end local function do_action_poll(player) if player and type(player) ~= 'userdata' then Server.output_script_data(module_name .. 'Player is not userdata. Getting player from name ' .. player) player = game.get_player(player) end if not player or not player.valid then Server.output_script_data(module_name .. 'Player is not valid. Not doing action poll.') return end local undo_count = check_undo_queue(player) if not undo_count or undo_count <= 0 then Server.output_script_data(module_name .. 'No undo count found for ' .. player.name .. '. Not doing action poll.') return end Server.output_script_data(module_name .. 'Doing action poll for ' .. player.name .. ' with undo count ' .. undo_count) if undo_count > 0 then game.print(module_name .. player.name .. ' has ' .. undo_count .. ' entities in the undo queue. Creating poll before restoring them.') local unique_id = player.name .. '_' .. 'undo_poll' Event.raise(CustomEvents.events.on_poll_created, { question = player.name .. ' removed ' .. undo_count .. ' entities before getting dealt with. Proceed with restoration?', answers = { 'Yes, restore the entities!', 'No, do not restore the entities!' }, duration = 30, custom_data = { unique_id = unique_id, player_name = player.name, surface_index = player.surface.index } }) Server.output_script_data(module_name .. 'Poll created for ' .. player.name .. ' with id ' .. unique_id) undo_polls[#undo_polls + 1] = { unique_id = unique_id, player_index = player.index, player_name = player.name, surface_index = player.surface.index } end end local converted_entities = { ['straight-rail'] = 'rail', ['curved-rail'] = 'rail', } local function check_undo_redo_stack(player) if not type(player) == 'userdata' then error('Player is not userdata.') end local valid_undos = {} local restored_entities = 0 local to_remove_items = 0 local undo_redo_stack = player.undo_redo_stack if undo_redo_stack and undo_redo_stack.get_undo_item_count() > 0 then for i = 1, undo_redo_stack.get_undo_item_count() do local actions = undo_redo_stack.get_undo_item(i) if actions and #actions > 0 then valid_undos[#valid_undos + 1] = actions for _, action in pairs(actions) do if not action.surface_index then Server.output_script_data(module_name .. 'Action has no surface index. Not restoring entity.') goto continue_action end local surface = game.get_surface(action.surface_index) if not (surface and surface.valid) then Server.output_script_data(module_name .. 'Invalid surface for action.') goto continue_action end local target = action.target if not (target and target.name and target.position) then Server.output_script_data(module_name .. 'Invalid target data.') goto continue_action end target.force = player.force local entity = surface.create_entity(target) if entity and entity.valid then restored_entities = restored_entities + 1 local name = converted_entities[target.name] or (string.find(target.name, 'curved') and 'rail') or target.name player.remove_item { name = name, quality = target.quality, count = 999 } if action.insert_plan and next(action.insert_plan) then for _, plan in pairs(action.insert_plan) do for _, items in pairs(plan.items.in_inventory) do if entity.get_module_inventory().index == items.inventory then to_remove_items = to_remove_items + 1 entity.get_module_inventory().insert { name = plan.id.name, quality = plan.id.quality, count = 1 } end end if to_remove_items > 0 then player.remove_item { name = plan.id.name, quality = plan.id.quality, count = to_remove_items } end end end end ::continue_action:: end end end end Server.output_script_data(module_name .. 'Restored ' .. restored_entities .. ' entities for ' .. player.name) if #valid_undos > 0 then while player.undo_redo_stack.get_undo_item_count() > 0 do player.undo_redo_stack.remove_undo_item(player.undo_redo_stack.get_undo_item_count()) end end end Event.add(CustomEvents.events.on_poll_complete, function (event) if not event.winning_answer or not event.winning_answer.text then return end local custom_data = event.custom_data if not custom_data then Server.output_script_data(module_name .. 'Custom data is not set. Not checking undo redo stack.') return end local player_name = custom_data.player_name if not player_name then return end Server.output_script_data(module_name .. 'Poll complete for ' .. player_name .. ' with winning answer ' .. event.winning_answer.text) if not undo_polls or not next(undo_polls) then Server.output_script_data(module_name .. 'No undo polls found. Not checking undo redo stack.') return end for i = 1, #undo_polls do local poll_action = undo_polls[i] if poll_action and poll_action.unique_id == custom_data.unique_id then local surface = game.get_surface(poll_action.surface_index) if not surface or not surface.valid then Server.output_script_data(module_name .. 'Surface is not valid. Not checking undo redo stack.') return end local player = game.get_player(player_name) if not player or not player.valid then Server.output_script_data(module_name .. 'Player is not valid. Not checking undo redo stack.') return end if string.find(event.winning_answer.text, 'Yes') then check_undo_redo_stack(player) Server.output_script_data(module_name .. 'Undo redo stack checked for ' .. player_name) else Server.output_script_data(module_name .. 'Not restore entities. Adding all items to a chest near spawn.') local spawn_position = game.forces.player.get_spawn_position(surface) local non_collidin_position = surface.find_non_colliding_position('blue-chest', spawn_position, 10, 5) local e = surface.create_entity({ name = 'blue-chest', position = non_collidin_position or spawn_position, force = 'player' }) if e and e.valid then Task.set_timeout_in_ticks(1000, make_entity_destructible_token, { entity = e }) e.set_inventory_size_override(defines.inventory.chest, 1000) game.print(module_name .. 'Adding all items have been transferred to a chest near spawn.') game.print('Located here: [gps=' .. e.position.x .. ',' .. e.position.y .. ',' .. e.surface.name .. ']') local main_inventory = player.get_main_inventory() if main_inventory and main_inventory.valid then for _, item in pairs(main_inventory.get_contents()) do e.insert({ name = item.name, count = item.count }) end end player.clear_items_inside() end end Server.output_script_data(module_name .. 'Poll removed from undo polls for ' .. player_name) undo_polls[i] = nil break end end end) Event.add(CustomEvents.events.on_player_banned, function (event) if not event.player_name then return end local player = game.get_player(event.player_name) if not player or not player.valid then Server.output_script_data(module_name .. 'Player is not valid. Not checking undo redo stack.') return end Server.output_script_data(module_name .. 'Player event received for ' .. player.name) local undo_count = check_undo_queue(player) if not undo_count or undo_count <= 0 then Server.output_script_data(module_name .. 'No undo count found for ' .. player.name .. '. Not checking undo redo stack.') return end check_undo_redo_stack(player) Server.output_script_data(module_name .. 'Undo redo stack checked for ' .. player.name) end) Commands.new('undo_player_actions', 'Undoes the actions of a player as a player by creating a poll.') :add_parameter('player', false, 'player') :require_validation('Only utilize this command if the player is jailed and has entities in the undo queue.') :require_playtime(60 * 60 * 60 * 24 * 40) -- 40 days :callback(function (player, target_player) if not target_player or not target_player.valid then return player.print('Player is not valid.') end local undo_count = check_undo_queue(target_player) if not undo_count or undo_count <= 0 then return player.print('No undo count found for ' .. target_player.name .. '.') end do_action_poll(target_player) player.print('Logging your actions to discord.') Discord.send_notification( { title = 'Undo actions', description = 'Undone ' .. undo_count .. ' actions for ' .. target_player.name .. ' by ' .. player.name .. '.', color = 'success', fields = { { title = "Server", description = Server.get_server_name() or 'CommandHandler', inline = "false" } } }) end) Commands.new('undo_player_actions_admin', 'Undoes the actions of a player as an admin.') :add_parameter('player', false, 'player') :require_validation('Only utilize this command if the player is jailed and has entities in the undo queue.') :require_admin() :callback(function (player, target_player) if not target_player or not target_player.valid then return player.print('Player is not valid.') end local undo_count = check_undo_queue(target_player) if not undo_count or undo_count <= 0 then return player.print('No undo count found for ' .. target_player.name .. '.') end check_undo_redo_stack(target_player) player.print('Logging your actions to discord.') player.print('Undone ' .. undo_count .. ' actions for ' .. target_player.name .. '.') Discord.send_notification( { title = 'Undo actions', description = 'Undone ' .. undo_count .. ' actions for ' .. target_player.name .. ' by ' .. player.name .. '.', color = 'success', fields = { { title = "Server", description = Server.get_server_name() or 'CommandHandler', inline = "false" } } }) end) Public.check_undo_redo_stack = check_undo_redo_stack Public.do_action_poll = do_action_poll return Public