mirror of
https://github.com/PHIDIAS0303/ExpCluster.git
synced 2026-09-21 17:04:00 +00:00
Move spectate into exp_scenario
The spectate and follow module moves to exp_scenario/module/control/spectate.lua in the event_handler shape the other control modules use, and the copy in exp_legacy is removed. Follow state is a typed record rather than a positional table, the follow label caption is localised, and the escape handler marks the record to stop on the next tick instead of poking an invalid position into it. The LuaPlayer.close_map call is gone: it does not exist in Factorio 2.0, where the map is the remote controller and set_controller replaces it.
This commit is contained in:
@@ -1,173 +0,0 @@
|
||||
local Storage = require("modules/exp_util/storage")
|
||||
local Gui = require("modules/exp_gui")
|
||||
|
||||
local Event = require("modules/exp_legacy/utils/event") --- @dep utils.event
|
||||
|
||||
----- Locals -----
|
||||
local follow_label --- @type ExpElement Gui constructor
|
||||
local following = {}
|
||||
local spectating = {}
|
||||
local Public = {}
|
||||
|
||||
----- Storage data -----
|
||||
Storage.register({
|
||||
following = following,
|
||||
spectating = spectating,
|
||||
}, function(tbl)
|
||||
following = tbl.following
|
||||
spectating = tbl.spectating
|
||||
end)
|
||||
|
||||
----- Public Functions -----
|
||||
|
||||
--- Test if a player is in spectator mode
|
||||
-- @tparam LuaPlayer player The player to test the controller type of
|
||||
-- @treturn boolean Returns true if the player is in spectator mode
|
||||
function Public.is_spectating(player)
|
||||
assert(player and player.valid, "Invalid player given to follower")
|
||||
return player.controller_type == defines.controllers.spectator
|
||||
end
|
||||
|
||||
--- Puts a player into spectator mode while maintaining an association to their character
|
||||
-- @tparam LuaPlayer player The player that will be placed into spectator mode
|
||||
-- @treturn boolean Returns false if the player was already in spectator mode
|
||||
function Public.start_spectate(player)
|
||||
assert(player and player.valid, "Invalid player given to follower")
|
||||
if spectating[player.index] or not player.character then return false end
|
||||
local character = player.character
|
||||
local opened = player.opened
|
||||
player.set_controller{ type = defines.controllers.spectator }
|
||||
player.associate_character(character)
|
||||
spectating[player.index] = character
|
||||
if opened then player.opened = opened end -- Maintain opened after controller change
|
||||
return true
|
||||
end
|
||||
|
||||
--- Return a player from spectator mode back to their character, if their character was killed then respawn them
|
||||
-- @tparam LuaPlayer player The player that will leave spectator mode
|
||||
function Public.stop_spectate(player)
|
||||
assert(player and player.valid, "Invalid player given to follower")
|
||||
local character = spectating[player.index]
|
||||
spectating[player.index] = nil
|
||||
if character and character.valid then
|
||||
local opened = player.opened
|
||||
player.teleport(character.position, character.surface)
|
||||
player.set_controller{ type = defines.controllers.character, character = character }
|
||||
if opened then player.opened = opened end -- Maintain opened after controller change
|
||||
else
|
||||
player.ticks_to_respawn = 300
|
||||
end
|
||||
end
|
||||
|
||||
--- Test if a player is in follow mode
|
||||
-- @tparam LuaPlayer player The player to test the follow mode of
|
||||
-- @treturn boolean Returns true if the player is in follow mode
|
||||
function Public.is_following(player)
|
||||
assert(player and player.valid, "Invalid player given to follower")
|
||||
return following[player.index] ~= nil
|
||||
end
|
||||
|
||||
--- Puts a player into spectator mode and follows an entity as it moves
|
||||
-- @tparam LuaPlayer player The player that will follow the entity
|
||||
-- @tparam ?LuaPlayer|LuaEntity entity The player or entity that will be followed
|
||||
function Public.start_follow(player, entity)
|
||||
assert(player and player.valid, "Invalid player given to follower")
|
||||
assert(entity and entity.valid, "Invalid entity given to follower")
|
||||
local spectate = Public.start_spectate(player)
|
||||
|
||||
player.close_map()
|
||||
follow_label(player.gui.screen, entity)
|
||||
player.teleport(entity.position, entity.surface)
|
||||
following[player.index] = { player, entity, entity.position, spectate }
|
||||
end
|
||||
|
||||
--- Returns camera control to the player, will return a player to their character if start_follow placed them into spectator mode
|
||||
-- @tparam LuaPlayer player The player that will regain control of their camera
|
||||
function Public.stop_follow(player)
|
||||
assert(player and player.valid, "Invalid player given to follower")
|
||||
if following[player.index] and following[player.index][4] and Public.is_spectating(player) then
|
||||
Public.stop_spectate(player)
|
||||
end
|
||||
|
||||
Gui.destroy_if_valid(player.gui.screen.follow_label)
|
||||
following[player.index] = nil
|
||||
end
|
||||
|
||||
--- Returns camera control to all players, will return a player to their character if start_follow placed them into spectator mode
|
||||
function Public.stop_all()
|
||||
for key, data in pairs(following) do
|
||||
Public.stop_follow(data[1])
|
||||
end
|
||||
end
|
||||
|
||||
----- Gui -----
|
||||
|
||||
--- Label used to show that the player is following, also used to allow esc to stop following
|
||||
follow_label = Gui.define("follow-label")
|
||||
:draw(function(def, parent, target)
|
||||
Gui.destroy_if_valid(parent.follow_label)
|
||||
|
||||
local label = parent.add{
|
||||
type = "label",
|
||||
name = "follow_label",
|
||||
style = "frame_title",
|
||||
caption = "Following " .. target.name .. ".\nClick here or press esc to stop following.",
|
||||
}
|
||||
|
||||
local player = Gui.get_player(parent)
|
||||
local res = player.display_resolution
|
||||
label.location = { 0, res.height - 150 }
|
||||
label.style.width = res.width
|
||||
label.style.horizontal_align = "center"
|
||||
player.opened = label
|
||||
|
||||
return label
|
||||
end)
|
||||
:on_click(function(def, player, element)
|
||||
Public.stop_follow(player)
|
||||
end)
|
||||
:on_closed(function(def, player, element)
|
||||
-- Don't call set_controller during on_close as it invalidates the controller
|
||||
-- Setting an invalid position (as to not equal their current) will call stop_follow on the next tick
|
||||
following[player.index][3] = {}
|
||||
end)
|
||||
|
||||
----- Events -----
|
||||
|
||||
--- Updates the location of the player as well as doing some sanity checks
|
||||
-- @tparam LuaPlayer player The player to update the position of
|
||||
-- @tparam ?LuaPlayer|LuaEntity entity The player or entity being followed
|
||||
local function update_player_location(player, entity, old_position)
|
||||
if player.character or not entity.valid then
|
||||
Public.stop_follow(player)
|
||||
elseif player.position.x ~= old_position.x or player.position.y ~= old_position.y then
|
||||
Public.stop_follow(player)
|
||||
else
|
||||
player.teleport(entity.position, entity.surface)
|
||||
end
|
||||
end
|
||||
|
||||
--- Updates the locations of all players currently following something
|
||||
local function update_all()
|
||||
for _, data in pairs(following) do
|
||||
update_player_location(data[1], data[2], data[3])
|
||||
data[3] = data[1].position
|
||||
end
|
||||
end
|
||||
|
||||
-- Update the location of all players each tick
|
||||
Event.add(defines.events.on_tick, update_all)
|
||||
|
||||
-- Check for player leaving
|
||||
Event.add(defines.events.on_pre_player_left_game, function(event)
|
||||
local player = game.players[event.player_index]
|
||||
Public.stop_follow(player)
|
||||
for _, data in pairs(following) do
|
||||
if data[2] == player then
|
||||
Public.stop_follow(data[1])
|
||||
end
|
||||
end
|
||||
end)
|
||||
|
||||
----- Module Return -----
|
||||
return Public
|
||||
@@ -3,7 +3,7 @@ Adds commands relating to spectate and follow
|
||||
]]
|
||||
|
||||
local Commands = require("modules/exp_commands")
|
||||
local Spectate = require("modules.exp_legacy.modules.control.spectate") --- @dep modules.control.spectate
|
||||
local Spectate = require("modules/exp_scenario/control/spectate")
|
||||
|
||||
--- Toggles spectator mode for the caller
|
||||
Commands.new("spectate", { "exp-commands_spectate.description-spectate" })
|
||||
|
||||
@@ -65,6 +65,7 @@ add(require("modules/exp_scenario/control/report_jail"))
|
||||
add(require("modules/exp_scenario/control/research"))
|
||||
add(require("modules/exp_scenario/control/roles"))
|
||||
add(require("modules/exp_scenario/control/spawn_area"))
|
||||
add(require("modules/exp_scenario/control/spectate"))
|
||||
add(require("modules/exp_scenario/control/station_auto_name"))
|
||||
|
||||
--- Guis
|
||||
|
||||
@@ -0,0 +1,173 @@
|
||||
--[[-- Control - Spectate
|
||||
Lets players spectate without losing their character, and follow other players or entities
|
||||
]]
|
||||
|
||||
local Storage = require("modules/exp_util/storage")
|
||||
local Gui = require("modules/exp_gui")
|
||||
|
||||
--- @class ExpScenario_Spectate
|
||||
local Spectate = {
|
||||
--- @package
|
||||
events = {},
|
||||
}
|
||||
|
||||
--- @class ExpScenario_Spectate.Following
|
||||
--- @field player LuaPlayer
|
||||
--- @field target LuaPlayer | LuaEntity
|
||||
--- @field position MapPosition Where the player was after the last update, moving away stops following
|
||||
--- @field started_spectate boolean True when start_follow put the player into spectator mode
|
||||
--- @field stop boolean? Set when following must end on the next tick
|
||||
|
||||
local following = {} --- @type table<uint, ExpScenario_Spectate.Following> Keyed by player index
|
||||
local spectating = {} --- @type table<uint, LuaEntity> The character a player returns to, keyed by player index
|
||||
|
||||
Storage.register({
|
||||
following = following,
|
||||
spectating = spectating,
|
||||
}, function(tbl)
|
||||
following = tbl.following
|
||||
spectating = tbl.spectating
|
||||
end)
|
||||
|
||||
--- Label shown while following, clicking it or pressing escape stops following
|
||||
--- @class ExpScenario_Spectate.follow_label: ExpElement
|
||||
--- @overload fun(parent: LuaGuiElement, target: LuaPlayer | LuaEntity): LuaGuiElement
|
||||
local follow_label = Gui.define("spectate/follow_label")
|
||||
:draw(function(_, parent, target)
|
||||
Gui.destroy_if_valid(parent.follow_label)
|
||||
|
||||
local label = parent.add{
|
||||
type = "label",
|
||||
name = "follow_label",
|
||||
style = "frame_title",
|
||||
caption = { "exp_spectate.follow-label", target.name },
|
||||
}
|
||||
|
||||
local player = Gui.get_player(parent)
|
||||
local res = player.display_resolution
|
||||
label.location = { 0, res.height - 150 }
|
||||
label.style.width = res.width
|
||||
label.style.horizontal_align = "center"
|
||||
player.opened = label
|
||||
|
||||
return label
|
||||
end)
|
||||
:on_click(function(_, player)
|
||||
Spectate.stop_follow(player)
|
||||
end)
|
||||
:on_closed(function(_, player)
|
||||
-- set_controller can not be called during on_gui_closed, so the next update stops following
|
||||
local data = following[player.index]
|
||||
if data then data.stop = true end
|
||||
end) --[[@as any]]
|
||||
|
||||
--- Check if a player is in spectator mode
|
||||
--- @param player LuaPlayer
|
||||
--- @return boolean
|
||||
function Spectate.is_spectating(player)
|
||||
return player.controller_type == defines.controllers.spectator
|
||||
end
|
||||
|
||||
--- Put a player into spectator mode while keeping their character
|
||||
--- @param player LuaPlayer
|
||||
--- @return boolean # False when the player was already spectating or has no character
|
||||
function Spectate.start_spectate(player)
|
||||
if spectating[player.index] or not player.character then return false end
|
||||
local character = player.character
|
||||
local opened = player.opened
|
||||
player.set_controller{ type = defines.controllers.spectator }
|
||||
player.associate_character(character)
|
||||
spectating[player.index] = character
|
||||
if opened then player.opened = opened end -- Changing controller closes the opened gui
|
||||
return true
|
||||
end
|
||||
|
||||
--- Return a player to their character, or respawn them if it was killed
|
||||
--- @param player LuaPlayer
|
||||
function Spectate.stop_spectate(player)
|
||||
local character = spectating[player.index]
|
||||
spectating[player.index] = nil
|
||||
if character and character.valid then
|
||||
local opened = player.opened
|
||||
player.teleport(character.position, character.surface)
|
||||
player.set_controller{ type = defines.controllers.character, character = character }
|
||||
if opened then player.opened = opened end -- Changing controller closes the opened gui
|
||||
else
|
||||
player.ticks_to_respawn = 300
|
||||
end
|
||||
end
|
||||
|
||||
--- Check if a player is following something
|
||||
--- @param player LuaPlayer
|
||||
--- @return boolean
|
||||
function Spectate.is_following(player)
|
||||
return following[player.index] ~= nil
|
||||
end
|
||||
|
||||
--- Put a player into spectator mode and keep their camera on a target as it moves
|
||||
--- @param player LuaPlayer
|
||||
--- @param target LuaPlayer | LuaEntity
|
||||
function Spectate.start_follow(player, target)
|
||||
local started_spectate = Spectate.start_spectate(player)
|
||||
|
||||
follow_label(player.gui.screen, target)
|
||||
player.teleport(target.position, target.surface)
|
||||
following[player.index] = {
|
||||
player = player,
|
||||
target = target,
|
||||
position = player.position,
|
||||
started_spectate = started_spectate,
|
||||
}
|
||||
end
|
||||
|
||||
--- Give a player their camera back, returning them to their character if start_follow took it
|
||||
--- @param player LuaPlayer
|
||||
function Spectate.stop_follow(player)
|
||||
local data = following[player.index]
|
||||
if data and data.started_spectate and Spectate.is_spectating(player) then
|
||||
Spectate.stop_spectate(player)
|
||||
end
|
||||
|
||||
Gui.destroy_if_valid(player.gui.screen.follow_label)
|
||||
following[player.index] = nil
|
||||
end
|
||||
|
||||
--- Move a following player onto their target, or stop following when they have moved away or regained a character
|
||||
--- @param data ExpScenario_Spectate.Following
|
||||
local function update_following(data)
|
||||
local player, target = data.player, data.target
|
||||
local position = player.position
|
||||
if data.stop or player.character or not target.valid
|
||||
or position.x ~= data.position.x or position.y ~= data.position.y
|
||||
then
|
||||
Spectate.stop_follow(player)
|
||||
else
|
||||
player.teleport(target.position, target.surface)
|
||||
data.position = player.position
|
||||
end
|
||||
end
|
||||
|
||||
local function on_tick()
|
||||
for _, data in pairs(following) do
|
||||
update_following(data)
|
||||
end
|
||||
end
|
||||
|
||||
--- Stop following when a player leaves, and stop anyone who was following them
|
||||
--- @param event EventData.on_pre_player_left_game
|
||||
local function on_pre_player_left_game(event)
|
||||
local player = game.players[event.player_index]
|
||||
Spectate.stop_follow(player)
|
||||
for _, data in pairs(following) do
|
||||
if data.target == player then
|
||||
Spectate.stop_follow(data.player)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
local e = defines.events
|
||||
|
||||
Spectate.events[e.on_tick] = on_tick
|
||||
Spectate.events[e.on_pre_player_left_game] = on_pre_player_left_game
|
||||
|
||||
return Spectate
|
||||
@@ -577,3 +577,6 @@ chat-jailed=__1__ was jailed because they removed too many protected entities. P
|
||||
|
||||
[exp_report-jail]
|
||||
chat-jailed=__1__ was jailed because they were reported too many times. Please wait for a moderator.
|
||||
|
||||
[exp_spectate]
|
||||
follow-label=Following __1__.\nClick here or press escape to stop following.
|
||||
|
||||
Reference in New Issue
Block a user