mirror of
https://github.com/PHIDIAS0303/ExpCluster.git
synced 2026-09-21 17:04:00 +00:00
Merge pull request #460 from bbassie/feature/scenario-spectate
Move spectate into exp_scenario
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" })
|
||||
|
||||
@@ -66,6 +66,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,181 @@
|
||||
--[[-- 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