mirror of
https://github.com/PHIDIAS0303/ExpCluster.git
synced 2025-12-27 03:25:23 +09:00
254 lines
8.4 KiB
Lua
254 lines
8.4 KiB
Lua
--[[-- ExpUtil - Selection
|
|
Provides an easy way for working with selection planners
|
|
]]
|
|
|
|
local ExpUtil = require("modules/exp_util")
|
|
|
|
--- @class Selection.Active
|
|
--- @field name string
|
|
--- @field character LuaEntity?
|
|
--- @field data table
|
|
|
|
--- @alias Selection.event_handler<E> fun(event: E, ...: any)
|
|
|
|
--- @class ExpUtil_Selection
|
|
local Selection = {
|
|
on_selection_start = script.generate_event_name(),
|
|
--- @class EventData.on_selection_start: EventData
|
|
--- @field player_index number
|
|
--- @field selection Selection.Active
|
|
|
|
on_selection_stop = script.generate_event_name(),
|
|
--- @class EventData.on_selection_stop: EventData
|
|
--- @field player_index number
|
|
--- @field selection Selection.Active
|
|
|
|
--- @type table<string, { [defines.events]: Selection.event_handler[] }>
|
|
_registered = {},
|
|
|
|
--- @package
|
|
events = {},
|
|
}
|
|
|
|
--- @class Selection
|
|
--- @field name string
|
|
--- @field _handlers table
|
|
Selection._prototype = {}
|
|
|
|
Selection._metatable = {
|
|
__index = Selection._prototype,
|
|
__class = "Selection",
|
|
}
|
|
|
|
--- @type table<number, Selection.Active>
|
|
local script_data = {}
|
|
local Storage = require("modules/exp_util/storage")
|
|
Storage.register(script_data, function(tbl)
|
|
script_data = tbl
|
|
end)
|
|
|
|
local empty_table = {}
|
|
local selection_tool = { name = "selection-tool" }
|
|
|
|
--- Test if a player is holding a selection tool
|
|
--- @param player LuaPlayer
|
|
--- @return boolean?
|
|
local function has_selection_tool_in_hand(player)
|
|
return player.cursor_stack and player.cursor_stack.valid_for_read and player.cursor_stack.name == "selection-tool"
|
|
end
|
|
|
|
--- Give a selection tool to a player if they don't have one
|
|
--- @param player LuaPlayer
|
|
local function give_selection_tool(player)
|
|
if has_selection_tool_in_hand(player) then return end
|
|
player.clear_cursor()
|
|
player.cursor_stack.set_stack(selection_tool)
|
|
|
|
-- This does not work for selection planners, will make a feature request for it
|
|
--player.cursor_stack_temporary = true
|
|
|
|
-- Make a slot to place the selection tool even if inventory is full
|
|
if player.character then
|
|
player.character_inventory_slots_bonus = player.character_inventory_slots_bonus + 1
|
|
end
|
|
|
|
local inventory = player.get_main_inventory()
|
|
if inventory then
|
|
player.hand_location = { inventory = inventory.index, slot = #inventory }
|
|
end
|
|
end
|
|
|
|
--- Remove a selection tool to a player if they have one
|
|
--- @param player LuaPlayer
|
|
--- @param old_character LuaEntity?
|
|
local function remove_selection_tool(player, old_character)
|
|
-- Remove the selection tool
|
|
if has_selection_tool_in_hand(player) then
|
|
player.cursor_stack.clear()
|
|
else
|
|
player.remove_item(selection_tool)
|
|
end
|
|
|
|
-- Remove the extra slot
|
|
if old_character and old_character == player.character then
|
|
player.character_inventory_slots_bonus = player.character_inventory_slots_bonus - 1
|
|
player.hand_location = nil
|
|
end
|
|
end
|
|
|
|
--- Create a connection from which events can be registered
|
|
--- @param name string
|
|
--- @return Selection
|
|
function Selection.connect(name)
|
|
ExpUtil.assert_not_runtime()
|
|
ExpUtil.assert_argument_type(name, "string", 1, "name")
|
|
|
|
local handlers = Selection._registered[name] or {}
|
|
Selection._registered[name] = handlers
|
|
|
|
return setmetatable({
|
|
name = name,
|
|
_handlers = handlers,
|
|
}, Selection._metatable)
|
|
end
|
|
|
|
--- Stop the currently active selection for a player
|
|
--- @param player LuaPlayer
|
|
function Selection.stop(player)
|
|
local player_index = player.index
|
|
local active_selection = script_data[player_index]
|
|
if not active_selection then
|
|
return
|
|
end
|
|
|
|
remove_selection_tool(player, active_selection.character)
|
|
|
|
script_data[player_index] = nil
|
|
script.raise_event(Selection.on_selection_stop, {
|
|
player_index = player_index,
|
|
selection = active_selection,
|
|
})
|
|
end
|
|
|
|
--- Start a new selection for a player
|
|
--- @param player LuaPlayer
|
|
--- @param ... unknown
|
|
function Selection._prototype:start(player, ...)
|
|
local player_index = player.index
|
|
local active_selection = script_data[player_index]
|
|
if active_selection then
|
|
script.raise_event(Selection.on_selection_stop, {
|
|
player_index = player_index,
|
|
selection = active_selection,
|
|
})
|
|
end
|
|
|
|
local selection = {
|
|
name = self.name,
|
|
character = player.character,
|
|
data = select("#", ...) > 0 and { ... } or empty_table,
|
|
}
|
|
|
|
give_selection_tool(player)
|
|
|
|
script_data[player_index] = selection
|
|
script.raise_event(Selection.on_selection_start, {
|
|
player_index = player_index,
|
|
selection = selection,
|
|
})
|
|
end
|
|
|
|
--- Stop this selection if it is active, returns if this selection was active
|
|
--- @param player LuaPlayer
|
|
--- @return boolean
|
|
function Selection._prototype:stop(player)
|
|
local player_index = player.index
|
|
local active_selection = script_data[player_index]
|
|
if not active_selection or active_selection.name ~= self.name then
|
|
return false
|
|
end
|
|
|
|
remove_selection_tool(player, active_selection.character)
|
|
|
|
script_data[player_index] = nil
|
|
script.raise_event(Selection.on_selection_stop, {
|
|
player_index = player_index,
|
|
selection = active_selection,
|
|
})
|
|
|
|
return true
|
|
end
|
|
|
|
--- Dispatch events to the correct handlers
|
|
--- @param event EventData.on_player_selected_area | EventData.on_player_alt_selected_area | EventData.on_selection_start | EventData.on_selection_stop
|
|
local function event_dispatch(event)
|
|
local active_selection = event.selection or script_data[event.player_index]
|
|
local selection_handlers = active_selection and Selection._registered[active_selection.name]
|
|
local handlers = selection_handlers and selection_handlers[event.name] or empty_table
|
|
for _, handler in ipairs(handlers) do
|
|
handler(event, table.unpack(active_selection.data))
|
|
end
|
|
end
|
|
|
|
--- Create an on event adder
|
|
--- @param event defines.events
|
|
--- @return function
|
|
local function on_event_factory(event)
|
|
return function(self, callback)
|
|
ExpUtil.assert_not_runtime()
|
|
ExpUtil.assert_argument_type(callback, "function", 1, "callback")
|
|
Selection.events[event] = event_dispatch
|
|
|
|
local handlers = self._handlers[event] or {}
|
|
handlers[#handlers + 1] = callback
|
|
self._handlers[event] = handlers
|
|
return self
|
|
end
|
|
end
|
|
|
|
local e = defines.events
|
|
|
|
--- @alias Selection.on_event<E> fun(self: Selection, callback: fun(event: E, ...: any)): Selection
|
|
|
|
--- @type Selection.on_event<EventData.on_selection_start>
|
|
Selection._prototype.on_start = on_event_factory(Selection.on_selection_start)
|
|
|
|
--- @type Selection.on_event<EventData.on_selection_stop>
|
|
Selection._prototype.on_stop = on_event_factory(Selection.on_selection_stop)
|
|
|
|
--- @type Selection.on_event<EventData.on_player_selected_area>
|
|
Selection._prototype.on_selection = on_event_factory(e.on_player_selected_area)
|
|
|
|
--- @type Selection.on_event<EventData.on_player_alt_selected_area>
|
|
Selection._prototype.on_alt_selection = on_event_factory(e.on_player_alt_selected_area)
|
|
|
|
--- Stop selection if the selection tool is removed from the cursor
|
|
--- @param event EventData.on_player_cursor_stack_changed
|
|
Selection.events[e.on_player_cursor_stack_changed] = function(event)
|
|
local player = assert(game.get_player(event.player_index))
|
|
if has_selection_tool_in_hand(player) then return end
|
|
Selection.stop(player)
|
|
end
|
|
|
|
--- Make sure the hand location exists when the player returns from remote view
|
|
--- @param event EventData.on_player_controller_changed
|
|
Selection.events[e.on_player_controller_changed] = function(event)
|
|
local player = assert(game.get_player(event.player_index))
|
|
local inventory = player.get_main_inventory()
|
|
if inventory and has_selection_tool_in_hand(player) then
|
|
player.hand_location = { inventory = inventory.index, slot = #inventory }
|
|
end
|
|
end
|
|
|
|
--- Stop selection after an event
|
|
--- @param event EventData.on_pre_player_left_game | EventData.on_pre_player_died
|
|
local function stop_after_event(event)
|
|
local player = assert(game.get_player(event.player_index))
|
|
Selection.stop(player)
|
|
end
|
|
|
|
Selection.events[e.on_pre_player_left_game] = stop_after_event
|
|
Selection.events[e.on_pre_player_died] = stop_after_event
|
|
|
|
return Selection
|