-- created by Gerkiz for ComfyFactorio local Global = require 'utils.global' local Session = require 'utils.datastore.session_data' local Game = require 'utils.game' local Token = require 'utils.token' local Task = require 'utils.task' local Server = require 'utils.server' local Event = require 'utils.event' local Utils = require 'utils.core' local table = require 'utils.table' local Gui = require 'utils.gui' local module_name = '[Jail handler] ' local jailed_data_set = 'jailed' local revoked_permissions_set = 'revoked_permissions_jailed' local jailed = {} local player_data = {} local terms_tbl = {} local votejail = {} local votefree = {} local revoked_permissions = {} local settings = { playtime_for_vote = 25920000, -- 5 days playtime_for_instant_jail = 207360000, -- 40 days -- playtime_for_instant_jail = 103680000, -- 20 days clear_voted_player = 36000, -- remove player from vote-tbl after 10 minutes clear_terms_tbl = 2000, votejail_count = 5, valid_surface = 'nauvis', normies_can_jail = true -- states that normal players with enough playtime can jail } local set_data = Server.set_data local try_get_data = Server.try_get_data local concat = table.concat local release_player_from_temporary_prison_token local remove_notice local remove_action_needed local draw_notice_frame local jail_frame_name = Gui.uid_name() local notice_frame_name = Gui.uid_name() local placeholder_jail_text_box = Gui.uid_name() local save_button_name = Gui.uid_name() local discard_button_name = Gui.uid_name() local valid_commands = { ['free'] = true, ['jail'] = true } Global.register( { jailed = jailed, votejail = votejail, votefree = votefree, settings = settings, player_data = player_data, terms_tbl = terms_tbl, revoked_permissions = revoked_permissions }, function(t) jailed = t.jailed votejail = t.votejail votefree = t.votefree settings = t.settings player_data = t.player_data terms_tbl = t.terms_tbl revoked_permissions = t.revoked_permissions end ) local Public = {} local function validate_entity(entity) if not (entity and entity.valid) then return false end return true end local function is_revoked(name) if name then if revoked_permissions[name] then return true else return false end end return false end local function add_revoked(name, admin, reason) if name then local date = Server.get_current_date_with_time() if not revoked_permissions[name] then revoked_permissions[name] = true set_data(revoked_permissions_set, name, {revoked = true, actor = admin, reason = reason, date = date}) return true else return false end end return false end local function remove_revoked(name) if name then if revoked_permissions[name] then revoked_permissions[name] = nil set_data(revoked_permissions_set, name, nil) return true else return false end end return false end local clear_terms_tbl = Token.register( function(data) local player = data.player if not player then return end if terms_tbl[player] then terms_tbl[player] = nil return end end ) local play_alert_sound = Token.register( function(data) local name = data.name if not name then return end local player = game.get_player(name) if not player or not player.valid then return end player.play_sound {path = 'utility/scenario_message', volume_modifier = 1} end ) local clear_jail_data_token = Token.register( function(data) local offender = data.offender if not offender then return end if votejail[offender] and votejail[offender].jailed then return end local msg_two = 'You have been cleared of all accusations because not enough players voted against you.' Utils.print_to(offender, msg_two) votejail[offender] = nil votefree[offender] = nil end ) local clear_gui = Token.register( function(data) local player = data.player if player and player.valid then for _, child in pairs(player.gui.center.children) do child.destroy() end for _, child in pairs(player.gui.left.children) do child.destroy() end end end ) local function validate_playtime(player) local tracker = Session.get_session_table() local playtime = player.online_time if tracker[player.name] then playtime = player.online_time + tracker[player.name] end return playtime end local function validate_trusted(player) local trusted = Session.get_trusted_table() local is_trusted = false if trusted[player.name] then is_trusted = true end return is_trusted end local function get_player_data(player, remove) if remove and player_data[player.name] then player_data[player.name] = nil return end if not player_data[player.name] then player_data[player.name] = {} end return player_data[player.name] end local function get_gulag_permission_group() local gulag = game.permissions.get_group('gulag') if not gulag then gulag = game.permissions.create_group('gulag') for action_name, _ in pairs(defines.input_action) do ---@diagnostic disable-next-line: need-check-nil gulag.set_allows_action(defines.input_action[action_name], false) end ---@diagnostic disable-next-line: need-check-nil gulag.set_allows_action(defines.input_action.write_to_console, true) end return gulag end local function get_super_gulag_permission_group() local gulag = game.permissions.get_group('super_gulag') if not gulag then gulag = game.permissions.create_group('super_gulag') for action_name, _ in pairs(defines.input_action) do ---@diagnostic disable-next-line: need-check-nil gulag.set_allows_action(defines.input_action[action_name], false) end end return gulag end local function create_gulag_surface() local surface = game.surfaces['gulag'] if not surface then local walls = {} local tiles = {} pcall( function() surface = game.create_surface( 'gulag', { autoplace_controls = { ['coal'] = {frequency = 23, size = 3, richness = 3}, ['stone'] = {frequency = 20, size = 3, richness = 3}, ['copper-ore'] = {frequency = 25, size = 3, richness = 3}, ['iron-ore'] = {frequency = 35, size = 3, richness = 3}, ['uranium-ore'] = {frequency = 20, size = 3, richness = 3}, ['crude-oil'] = {frequency = 80, size = 3, richness = 1}, ['trees'] = {frequency = 0.75, size = 2, richness = 0.1}, ['enemy-base'] = {frequency = 15, size = 0, richness = 1} }, cliff_settings = {cliff_elevation_0 = 1024, cliff_elevation_interval = 10, name = 'cliff'}, height = 64, width = 256, peaceful_mode = false, seed = 1337, starting_area = 'very-low', starting_points = {{x = 0, y = 0}}, terrain_segmentation = 'normal', water = 'normal' } ) end ) if not surface then surface = game.create_surface('gulag', {width = 40, height = 40}) end surface.always_day = true surface.request_to_generate_chunks({0, 0}, 9) surface.force_generate_chunk_requests() local area = {left_top = {x = -128, y = -32}, right_bottom = {x = 128, y = 32}} for x = area.left_top.x, area.right_bottom.x, 1 do for y = area.left_top.y, area.right_bottom.y, 1 do tiles[#tiles + 1] = {name = 'black-refined-concrete', position = {x = x, y = y}} if x == area.left_top.x or x == area.right_bottom.x or y == area.left_top.y or y == area.right_bottom.y then walls[#walls + 1] = {name = 'stone-wall', force = 'neutral', position = {x = x, y = y}} end end end surface.set_tiles(tiles) for _, entity in pairs(walls) do local e = surface.create_entity(entity) e.destructible = false e.minable = false end rendering.draw_text { text = 'The pit of despair ☹', surface = surface, target = {0, -50}, color = {r = 0.98, g = 0.66, b = 0.22}, scale = 10, font = 'heading-1', alignment = 'center', scale_with_zoom = false } end surface = game.surfaces['gulag'] return surface end local function teleport_player_to_gulag(player, action) local p_data = get_player_data(player) if not p_data then return end if action == 'jail' then local gulag = game.surfaces['gulag'] if p_data and not p_data.locked then p_data.fallback_surface_index = player.surface.index p_data.position = player.position p_data.p_group_id = player.permission_group.group_id p_data.locked = true end player.teleport(gulag.find_non_colliding_position('character', {0, 0}, 128, 1), gulag.name) local data = { player = player } Task.set_timeout_in_ticks(5, clear_gui, data) elseif action == 'free' then jailed[player.name] = nil if votejail[player.name] then votejail[player.name] = nil end if votefree[player.name] then votefree[player.name] = nil end local surface = game.surfaces[p_data.fallback_surface_index] if not surface or not surface.valid then if settings.valid_surface then surface = game.surfaces[settings.valid_surface] end end local p = p_data.position local p_group = game.permissions.get_group(p_data.p_group_id) if not p_group then return end p_group.add_player(player) local pos = {x = p.x, y = p.y} ---@diagnostic disable-next-line: missing-parameter local get_tile = surface.get_tile(pos) if get_tile.valid and get_tile.name == 'out-of-map' then player.teleport(surface.find_non_colliding_position('character', game.forces.player.get_spawn_position(surface), 128, 1), surface.name) else local valid_to_teleport = surface.find_non_colliding_position('character', p, 128, 1) if valid_to_teleport then player.teleport(valid_to_teleport, surface.name) else player.teleport(game.forces.player.get_spawn_position(surface), surface.name) end end get_player_data(player, true) end end local function validate_args(data) local player = data.player local offender = data.offender local trusted = data.trusted local playtime = data.playtime local message = data.message local cmd = data.cmd if not offender then return end if not type(offender) == 'string' then Utils.print_to(player, 'Invalid name.') return false end local get_offender_player = game.get_player(offender) if not validate_entity(get_offender_player) then Utils.print_to(player, module_name .. 'No valid player given. Reason is no longer required.') Utils.print_to(player, module_name .. 'Valid input: /jail ' .. player.name) return false end if not offender or not get_offender_player then Utils.print_to(player, module_name .. 'No valid player given. Reason is no longer required.') Utils.print_to(player, module_name .. 'Valid input: /jail ' .. player.name) return false end if cmd == 'jail' and jailed[get_offender_player.name] then Utils.print_to(player, module_name .. 'Player is already jailed.') return false end if cmd == 'free' and not jailed[get_offender_player.name] then Utils.print_to(player, module_name .. 'Player is not jailed.') return false end if votejail[player.name] and not player.admin then Utils.print_to(player, module_name .. 'You are currently being investigated since you have griefed.') return false end if votefree[player.name] and not player.admin then Utils.print_to(player, module_name .. 'You are currently being investigated since you have griefed.') return false end if jailed[player.name] and not player.admin then Utils.print_to(player, module_name .. 'You are jailed, you can´t run this command.') return false end if player.name == offender then Utils.print_to(player, module_name .. 'You can´t jail yourself.') return false end if get_offender_player.admin and not player.admin then Utils.print_to(player, module_name .. 'You can´t jail an admin.') return false end if not trusted and not player.admin or playtime <= settings.playtime_for_vote and not player.admin then Utils.print_to(player, module_name .. 'You are not trusted enough to run this command.') return false end if not message then Utils.print_to(player, module_name .. 'No valid reason was given.') return false end if cmd == 'jail' and message and string.len(message) <= 0 then Utils.print_to(player, module_name .. 'No valid reason was given.') return false end if cmd == 'jail' and message and string.len(message) <= 10 then Utils.print_to(player, module_name .. 'Reason is too short.') return false end return true end local function validate_server_args(data) local offender = data.offender local message = data.message local cmd = data.cmd if not offender then return end if not type(offender) == 'string' then print(module_name .. 'Invalid name.') return false end local get_offender_player = game.get_player(offender) if not validate_entity(get_offender_player) then print(module_name .. 'Invalid name.') return false end if not offender or not get_offender_player then print(module_name .. 'Invalid name.') return false end if cmd == 'jail' and jailed[get_offender_player.name] then print(module_name .. 'Player is already jailed.') return false end if cmd == 'free' and not jailed[get_offender_player.name] then print(module_name .. 'Player is not jailed.') return false end if cmd == 'jail' and get_offender_player.admin then print(module_name .. 'You can´t jail an admin.') return false end if not message then print(module_name .. 'No valid reason was given.') return false end if cmd == 'jail' and message and string.len(message) <= 0 then print(module_name .. 'No valid reason was given.') return false end if cmd == 'jail' and message and string.len(message) <= 10 then print(module_name .. 'Reason is too short.') return false end return true end local function vote_to_jail(player, offender, msg) if not offender then return end if type(offender) == 'table' then offender = offender.name end if not votejail[offender] then votejail[offender] = {index = 0, actor = player.name} local message = player.name .. ' has started a vote to jail player ' .. offender Utils.print_to(nil, message) Task.set_timeout_in_ticks(settings.clear_voted_player, clear_jail_data_token, {offender = offender}) end if not votejail[offender][player.name] then votejail[offender][player.name] = true votejail[offender].index = votejail[offender].index + 1 Utils.print_to(player, 'You have voted to jail player ' .. offender .. '.') if votejail[offender].index >= settings.votejail_count or (votejail[offender].index == #game.connected_players - 1 and #game.connected_players > votejail[offender].index) then Public.try_ul_data(offender, true, votejail[offender].actor, msg) end else Utils.print_to(player, 'You have already voted to kick ' .. offender .. '.') end end local function vote_to_free(player, offender) if not offender then return end if type(offender) == 'table' then offender = offender.name end if not votefree[offender] then votefree[offender] = {index = 0, actor = player.name} local message = player.name .. ' has started a vote to free player ' .. offender Utils.print_to(nil, message) end if not votefree[offender][player.name] then votefree[offender][player.name] = true votefree[offender].index = votefree[offender].index + 1 Utils.print_to(player, 'You have voted to free player ' .. offender .. '.') if votefree[offender].index >= settings.votejail_count or (votefree[offender].index == #game.connected_players - 1 and #game.connected_players > votefree[offender].index) then Public.try_ul_data(offender, false, votefree[offender].actor) votejail[offender] = nil votefree[offender] = nil end else Utils.print_to(player, 'You have already voted to free ' .. offender .. '.') end return end local function jail(player, offender, msg, raised, mute) player = player or 'script' if jailed[offender] then return false end if not msg then msg = 'Jailed by script' end if not game.get_player(offender) then return end local to_jail_player = game.get_player(offender) if not to_jail_player then return end draw_notice_frame(to_jail_player) if to_jail_player.character and to_jail_player.character.valid and to_jail_player.character.driving then to_jail_player.character.driving = false end teleport_player_to_gulag(to_jail_player, 'jail') if mute then local gulag = get_super_gulag_permission_group() gulag.add_player(offender) else local gulag = get_gulag_permission_group() gulag.add_player(offender) end local date = Server.get_current_date_with_time() local message = offender .. ' has been jailed by ' .. player .. '. Cause: ' .. msg jailed[offender] = {jailed = true, actor = player, reason = msg} if not raised then set_data(jailed_data_set, offender, {jailed = true, actor = player, reason = msg, date = date}) end Utils.print_to(nil, message) local data = Server.build_embed_data() data.username = offender data.admin = player data.reason = msg Server.to_jailed_embed(data) if votejail[offender] then votejail[offender].jailed = true end to_jail_player.clear_console() Utils.print_to(offender, message) return true end --- Jails a player temporary ---@param player LuaPlayer ---@param offender LuaPlayer ---@param msg string ---@param mute boolean ---@return boolean local function jail_temporary(player, offender, msg, mute) if jailed[offender.name] then return false end if not msg then msg = 'Jailed by script' end if offender.character and offender.character.valid and offender.character.driving then offender.character.driving = false end teleport_player_to_gulag(offender, 'jail') if mute then local gulag = get_super_gulag_permission_group() gulag.add_player(offender) else local gulag = get_gulag_permission_group() gulag.add_player(offender) end local message = offender.name .. ' has been temporary jailed by ' .. player.name .. '.' jailed[offender.name] = {jailed = true, actor = player.name, reason = msg, temporary = true} Utils.print_to(nil, message) local data = Server.build_embed_data() data.username = offender.name data.admin = player.name data.reason = msg Server.to_jailed_embed(data) if votejail[offender.name] then votejail[offender.name].jailed = true end offender.clear_console() draw_notice_frame(offender) Task.set_timeout_in_ticks(10800, release_player_from_temporary_prison_token, {offender_name = offender.name, actor_name = player.name}) return true end local function free(player, offender) player = player or 'script' if not jailed[offender] then return false end if not game.get_player(offender) then return end local to_jail_player = game.get_player(offender) teleport_player_to_gulag(to_jail_player, 'free') local message = offender .. ' was set free from jail by ' .. player .. '.' set_data(jailed_data_set, offender, nil) Utils.print_to(nil, message) local data = Server.build_embed_data() data.username = offender data.admin = player Server.to_unjailed_embed(data) Server.to_unjailed_named_embed(data) offender = game.get_player(offender) remove_notice(offender) return true end local is_jailed = Token.register( function(data) local key = data.key local value = data.value if value and value.jailed and value.reason then jail('script', key, value.reason, true) else free('script', key) end end ) --! start gui handler release_player_from_temporary_prison_token = Token.register( function(event) local actor_name = event.actor_name local offender_name = event.offender_name if jailed[offender_name] and jailed[offender_name].temporary then free('script', offender_name) Utils.print_to(nil, module_name .. 'If you find someone abusing their jail permissions - report them to the admins of Comfy!') local actor = game.get_player(actor_name) remove_action_needed(actor) end end ) local function remove_target_frame(target_frame) Gui.remove_data_recursively(target_frame) target_frame.destroy() end local function draw_main_frame(player, offender) local main_frame, inside_table = Gui.add_main_frame_with_toolbar(player, 'screen', jail_frame_name, nil, nil, 'Jail in progress', true) if not main_frame or not inside_table then return end local main_frame_style = main_frame.style main_frame_style.width = 500 main_frame.auto_center = true local warning_message = concat { '[font=heading-2]You have jailed player: [color=yellow]', offender.name, '\n[/color][/font]' } local info_warning_text = inside_table.add({type = 'label', caption = warning_message}) local info_warning_text_style = info_warning_text.style info_warning_text_style.single_line = false info_warning_text_style.width = 470 info_warning_text_style.horizontal_align = 'center' info_warning_text_style.vertical_align = 'center' info_warning_text_style.top_padding = 4 info_warning_text_style.left_padding = 4 info_warning_text_style.right_padding = 4 info_warning_text_style.bottom_padding = 4 local abuse_message = concat { 'Jailing is [color=red]NOT[/color] allowed to solve personal disputes, talk to each other instead of jailing!\n', 'Jail is only a temporary solution, the jailed offender will be released in less than one week automatically.\n', 'If the actions done by the offender was serious, report the offender to the admins on [color=yellow]https://getcomfy.eu/discord[/color]\n', 'Providing NO reason will free the offender after 3 minutes or if you close this window - note - this will log your actions to our admins.\n\n', '[color=yellow]Explain why you jailed ' .. offender.name .. '[/color]' } local info_warning_text_extended = inside_table.add({type = 'label', caption = abuse_message}) local info_warning_text_extended_style = info_warning_text_extended.style info_warning_text_extended_style.single_line = false info_warning_text_extended_style.font = 'heading-2' info_warning_text_extended_style.horizontal_align = 'center' info_warning_text_extended_style.vertical_align = 'center' info_warning_text_extended_style.top_padding = 4 info_warning_text_extended_style.left_padding = 4 info_warning_text_extended_style.right_padding = 4 info_warning_text_extended_style.bottom_padding = 4 local placeholder_text = inside_table.add({type = 'text-box', text = '', name = placeholder_jail_text_box}) local placeholder_text_style = placeholder_text.style placeholder_text_style.width = 470 placeholder_text_style.height = 200 placeholder_text_style.vertically_stretchable = false placeholder_text_style.horizontally_stretchable = false placeholder_text_style.vertically_stretchable = false placeholder_text_style.horizontally_squashable = false placeholder_text_style.vertically_squashable = 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 = 'Discard report'}) close_button.style = 'back_button' local right_flow = bottom_flow.add({type = 'flow'}) right_flow.style.horizontal_align = 'right' local save_button = right_flow.add({type = 'button', name = save_button_name, caption = 'Save report'}) save_button.style = 'confirm_button' local data = { offender = offender.name } Gui.set_data(placeholder_text, data) Gui.set_data(save_button, data) Gui.set_data(close_button, data) player.opened = main_frame end remove_notice = function(player) local screen = player.gui.screen local notice = screen[notice_frame_name] if notice and notice.valid then notice.destroy() end end remove_action_needed = function(player) local screen = player.gui.screen local action_needed = screen[jail_frame_name] if action_needed and action_needed.valid then action_needed.destroy() end end draw_notice_frame = function(player) local main_frame, inside_table = Gui.add_main_frame_with_toolbar(player, 'screen', notice_frame_name, nil, nil, 'Notice', 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]You have been jailed.[/font]\nPlease respond to questions if you are asked something.' label_flow.style.horizontally_stretchable = false local label = label_flow.add {type = 'label', caption = warning_message} label.style.single_line = false player.opened = main_frame end --! end gui handler local update_jailed = Token.register( function(data) local key = data.key local value = data.value or false local player = data.player or 'script' local message = data.message local mute = data.mute or false if value then jail(player, key, message, nil, mute) else free(player, key) end end ) --- Tries to get data from the webpanel and updates the local table with values. -- @param data_set player token function Public.try_dl_data(key) key = tostring(key) local secs = Server.get_current_time() if not secs then return else try_get_data(jailed_data_set, key, is_jailed) end end --- Tries to get data from the webpanel and updates the local table with values. -- @param key LuaPlayer -- @param value boolean -- @param player LuaPlayer or