diff --git a/modules/commands/protection.lua b/modules/commands/protection.lua index e43a0989..bc79165f 100644 --- a/modules/commands/protection.lua +++ b/modules/commands/protection.lua @@ -3,13 +3,24 @@ @commands Protection ]] +local Event = require 'utils.event' --- @dep utils.event +local Global = require 'utils.global' --- @dep utils.global +local Roles = require 'expcore.roles' --- @dep expcore.roles local Commands = require 'expcore.commands' --- @dep expcore.commands +local format_chat_player_name = _C.format_chat_player_name --- @dep expcore.common local EntityProtection = require 'modules.control.protection' --- @dep modules.control.protection local Selection = require 'modules.control.selection' --- @dep modules.control.selection local SelectionProtectEntity = 'ProtectEntity' local SelectionProtectArea = 'ProtectArea' +local renders = {} -- Stores all renders for a player +Global.register({ + renders = renders +}, function(tbl) + renders = tbl.renders +end) + --- Test if a point is inside an aabb local function aabb_point_enclosed(point, aabb) return point.x >= aabb.left_top.x and point.y >= aabb.left_top.y @@ -30,6 +41,58 @@ local function aabb_align_expand(aabb) } end +--- Get the key used in protected_entities +local function get_entity_key(entity) + return string.format('%i,%i', math.floor(entity.position.x), math.floor(entity.position.y)) +end + +--- Get the key used in protected_areas +local function get_area_key(area) + return string.format('%i,%i', math.floor(area.left_top.x), math.floor(area.left_top.y)) +end + + +--- Show a protected entity to a player +local function show_protected_entity(player, entity) + local key = get_entity_key(entity) + if renders[player.index][key] then return end + local rb = entity.selection_box.right_bottom + local render_id = rendering.draw_sprite{ + sprite = 'utility/notification', + target = entity, + target_offset = { + (rb.x-entity.position.x)*0.8, + (rb.y-entity.position.y)*0.8 + }, + surface = entity.surface, + players = { player } + } + renders[player.index][key] = render_id +end + +--- Show a protected are to a player +local function show_protected_area(player, surface, area) + local key = get_area_key(area) + if renders[player.index][key] then return end + local render_id = rendering.draw_rectangle{ + color = {1, 1, 0, 0.5}, + filled = false, + width = 3, + left_top = area.left_top, + right_bottom = area.right_bottom, + surface = surface, + players = { player } + } + renders[player.index][key] = render_id +end + +--- Remove a render object for a player +local function remove_render(player, key) + local render = renders[player.index][key] + if render and rendering.is_valid(render) then rendering.destroy(render) end + renders[player.index][key] = nil +end + --- Toggles entity protection selection -- @command protect-entity Commands.new_command('protect-entity', 'Toggles entity protection selection, hold shift to remove protection') @@ -58,16 +121,20 @@ end) --- When an area is selected to add protection to entities Selection.on_selection(SelectionProtectEntity, function(event) + local player = game.get_player(event.player_index) for _, entity in ipairs(event.entities) do EntityProtection.add_entity(entity) + show_protected_entity(player, entity) end return Commands.success{'expcom-protection.protected-entities', #event.entities} end) --- When an area is selected to remove protection from entities Selection.on_alt_selection(SelectionProtectEntity, function(event) + local player = game.get_player(event.player_index) for _, entity in ipairs(event.entities) do EntityProtection.remove_entity(entity) + remove_render(player, get_entity_key(entity)) end return Commands.success{'expcom-protection.unprotected-entities', #event.entities} end) @@ -81,7 +148,9 @@ Selection.on_selection(SelectionProtectArea, function(event) return Commands.error{'expcom-protection.already-protected'} end end + local player = game.get_player(event.player_index) EntityProtection.add_area(event.surface, area) + show_protected_area(player, event.surface, area) return Commands.success{'expcom-protection.protected-area'} end) @@ -89,14 +158,55 @@ end) Selection.on_alt_selection(SelectionProtectArea, function(event) local area = aabb_align_expand(event.area) local areas = EntityProtection.get_areas(event.surface) + local player = game.get_player(event.player_index) for _, next_area in pairs(areas) do if aabb_area_enclosed(next_area, area) then EntityProtection.remove_area(event.surface, next_area) Commands.print{'expcom-protection.unprotected-area'} + remove_render(player, get_area_key(next_area)) end end end) +--- When selection starts show all protected entities and protected areas +Event.add(Selection.events.on_player_selection_start, function(event) + if event.selection ~= SelectionProtectEntity and event.selection ~= SelectionProtectArea then return end + local player = game.get_player(event.player_index) + local surface = player.surface + renders[player.index] = {} + -- Show protected entities + local entities = EntityProtection.get_entities(surface) + for _, entity in pairs(entities) do + show_protected_entity(player, entity) + end + -- Show always protected entities by name + if #EntityProtection.protected_entity_names > 0 then + for _, entity in pairs(surface.find_entities_filtered{ name = EntityProtection.protected_entity_names, force = player.force }) do + show_protected_entity(player, entity) + end + end + -- Show always protected entities by type + if #EntityProtection.protected_entity_types > 0 then + for _, entity in pairs(surface.find_entities_filtered{ type = EntityProtection.protected_entity_types, force = player.force }) do + show_protected_entity(player, entity) + end + end + -- Show protected areas + local areas = EntityProtection.get_areas(surface) + for _, area in pairs(areas) do + show_protected_area(player, surface, area) + end +end) + +--- When selection ends show hide protected entities on screen and protected areas +Event.add(Selection.events.on_player_selection_end, function(event) + if event.selection ~= SelectionProtectEntity and event.selection ~= SelectionProtectArea then return end + for _, id in pairs(renders[event.player_index]) do + if rendering.is_valid(id) then rendering.destroy(id) end + end + renders[event.player_index] = nil +end) + --- When there is a repeat offence print it in chat Event.add(EntityProtection.events.on_repeat_violation, function(event) local player_name = format_chat_player_name(event.player_index) diff --git a/modules/control/protection.lua b/modules/control/protection.lua index 490e0c7d..e0d1c7cc 100644 --- a/modules/control/protection.lua +++ b/modules/control/protection.lua @@ -8,6 +8,8 @@ local Global = require 'utils.global' --- @dep utils.global local Event = require 'utils.event' --- @dep utils.event local config = require 'config.protection' --- @dep config.protection local EntityProtection = { + protected_entity_names = table.deep_copy(config.always_protected_names), + protected_entity_types = table.deep_copy(config.always_protected_types), events = { --- When a player mines a protected entity -- @event on_player_mined_protected @@ -59,12 +61,12 @@ end) --- Get the key used in protected_entities local function get_entity_key(entity) - return string.format('%i,%i', entity.position.x, entity.position.y) + return string.format('%i,%i', math.floor(entity.position.x), math.floor(entity.position.y)) end --- Get the key used in protected_areas local function get_area_key(area) - return string.format('%i,%i', area.left_top.x, area.left_top.y) + return string.format('%i,%i', math.floor(area.left_top.x), math.floor(area.left_top.y)) end --- Check if an entity is always protected @@ -99,7 +101,7 @@ end --- Get all protected entities on a surface function EntityProtection.get_entities(surface) - return protected_entities[surface.index] + return protected_entities[surface.index] or {} end --- Check if an entity is protected @@ -129,7 +131,7 @@ end --- Get all protected areas on a surface function EntityProtection.get_areas(surface) - return protected_areas[surface.index] + return protected_areas[surface.index] or {} end --- Check if an entity is protected diff --git a/modules/control/selection.lua b/modules/control/selection.lua index 00f76c6c..0b009b40 100644 --- a/modules/control/selection.lua +++ b/modules/control/selection.lua @@ -6,7 +6,20 @@ local Event = require 'utils.event' --- @dep utils.event local Global = require 'utils.global' --- @dep utils.global -local Selection = {} +local Selection = { + events = { + --- When a player enterers selection mode + -- @event on_player_selection_start + -- @tparam number player_index the player index of the player who entered selection mode + -- @tparam string selection the name of the selection being made + on_player_selection_start = script.generate_event_name(), + --- When a player leaves selection mode + -- @event on_player_selection_end + -- @tparam number player_index the player index of the player who left selection mode + -- @tparam string selection the name of the selection which ended + on_player_selection_end = script.generate_event_name(), + } +} local selection_tool = { name='selection-tool' } @@ -22,8 +35,18 @@ end) -- @tparam string selection_name The name of the selection to start, used with on_selection -- @tparam[opt=false] boolean single_use When true the selection will stop after first use function Selection.start(player, selection_name, single_use, ...) - -- Assign the arguments if the player is valid if not player or not player.valid then return end + if selections[player.index] then + -- Raise the end event if a selection was already in progress + script.raise_event(Selection.events.on_player_selection_end, { + name = Selection.events.on_player_selection_end, + tick = game.tick, + player_index = player.index, + selection = selections[player.index].name + }) + end + + -- Set the selection data selections[player.index] = { name = selection_name, arguments = { ... }, @@ -31,6 +54,14 @@ function Selection.start(player, selection_name, single_use, ...) character = player.character } + -- Raise the event + script.raise_event(Selection.events.on_player_selection_start, { + name = Selection.events.on_player_selection_start, + tick = game.tick, + player_index = player.index, + selection = selection_name + }) + -- Give a selection tool if one is not in use if player.cursor_stack.is_selection_tool then return end player.clear_cursor() -- Clear the current item @@ -47,8 +78,17 @@ end function Selection.stop(player) if not selections[player.index] then return end local character = selections[player.index].character + local selection = selections[player.index].name selections[player.index] = nil + -- Raise the event + script.raise_event(Selection.events.on_player_selection_end, { + name = Selection.events.on_player_selection_end, + tick = game.tick, + player_index = player.index, + selection = selection + }) + -- Remove the selection tool if player.cursor_stack.is_selection_tool then player.cursor_stack.clear() @@ -78,8 +118,8 @@ function Selection.is_selecting(player, selection_name) if not selections[player.index] then return false end return selections[player.index].name == selection_name else - return player.cursor_stack.is_selection_tool -end + return player.cursor_stack.is_selection_tool + end end --- Filter on_player_selected_area to this custom selection, appends the selection arguments