mirror of
https://github.com/PHIDIAS0303/ExpCluster.git
synced 2025-12-27 03:25:23 +09:00
Refactor Selection Util (#409)
* Add Selection to ExpUtil * Convert modules to use new lib * Bug Fixes
This commit is contained in:
@@ -4,6 +4,7 @@
|
||||
"include/package.lua",
|
||||
"include/require.lua",
|
||||
"storage.lua",
|
||||
"selection.lua",
|
||||
"async.lua"
|
||||
],
|
||||
"require": [
|
||||
|
||||
253
exp_util/module/selection.lua
Normal file
253
exp_util/module/selection.lua
Normal file
@@ -0,0 +1,253 @@
|
||||
--[[-- 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
|
||||
Reference in New Issue
Block a user