2021-06-20 10:09:24 +02:00
using System.Globalization ;
2021-11-26 03:02:46 -06:00
using System.Linq ;
2023-07-08 14:08:32 +10:00
using System.Numerics ;
2023-07-31 12:13:38 -07:00
using Content.Server.Administration.Managers ;
2024-06-01 05:08:31 -07:00
using Content.Server.GameTicking.Events ;
2021-06-20 10:09:24 +02:00
using Content.Server.Spawners.Components ;
using Content.Server.Speech.Components ;
2022-05-27 06:01:07 +02:00
using Content.Server.Station.Components ;
2021-11-28 14:56:53 +01:00
using Content.Shared.Database ;
2024-05-11 08:03:40 -07:00
using Content.Shared.Mind ;
2023-10-28 09:59:53 +11:00
using Content.Shared.Players ;
2021-06-20 10:09:24 +02:00
using Content.Shared.Preferences ;
using Content.Shared.Roles ;
2023-08-30 21:46:11 -07:00
using Content.Shared.Roles.Jobs ;
2022-05-10 13:43:30 -05:00
using JetBrains.Annotations ;
2021-06-20 10:09:24 +02:00
using Robust.Shared.Map ;
2023-10-19 12:34:31 -07:00
using Robust.Shared.Map.Components ;
2022-02-21 14:11:39 -08:00
using Robust.Shared.Network ;
2023-10-28 09:59:53 +11:00
using Robust.Shared.Player ;
2023-08-13 20:26:59 -04:00
using Robust.Shared.Prototypes ;
2021-06-20 10:09:24 +02:00
using Robust.Shared.Random ;
using Robust.Shared.Utility ;
namespace Content.Server.GameTicking
{
2022-02-16 00:23:23 -07:00
public sealed partial class GameTicker
2021-06-20 10:09:24 +02:00
{
2023-07-31 12:13:38 -07:00
[Dependency] private readonly IAdminManager _adminManager = default ! ;
2023-08-30 21:46:11 -07:00
[Dependency] private readonly SharedJobSystem _jobs = default ! ;
2023-07-31 12:13:38 -07:00
2023-08-13 20:26:59 -04:00
[ValidatePrototypeId<EntityPrototype>]
2023-10-28 09:59:53 +11:00
public const string ObserverPrototypeName = "MobObserver" ;
2021-06-20 10:09:24 +02:00
2023-11-20 20:27:37 -08:00
[ValidatePrototypeId<EntityPrototype>]
public const string AdminObserverPrototypeName = "AdminObserver" ;
2022-07-28 20:59:45 -04:00
/// <summary>
/// How many players have joined the round through normal methods.
/// Useful for game rules to look at. Doesn't count observers, people in lobby, etc.
/// </summary>
2023-10-19 12:34:31 -07:00
public int PlayersJoinedRoundNormally ;
2022-07-28 20:59:45 -04:00
2021-06-20 10:09:24 +02:00
// Mainly to avoid allocations.
private readonly List < EntityCoordinates > _possiblePositions = new ( ) ;
2023-10-19 12:34:31 -07:00
private List < EntityUid > GetSpawnableStations ( )
{
var spawnableStations = new List < EntityUid > ( ) ;
var query = EntityQueryEnumerator < StationJobsComponent , StationSpawningComponent > ( ) ;
while ( query . MoveNext ( out var uid , out _ , out _ ) )
{
spawnableStations . Add ( uid ) ;
}
return spawnableStations ;
}
2024-05-07 03:50:22 -07:00
private void SpawnPlayers ( List < ICommonSession > readyPlayers ,
Dictionary < NetUserId , HumanoidCharacterProfile > profiles ,
bool force )
2022-02-21 14:11:39 -08:00
{
// Allow game rules to spawn players by themselves if needed. (For example, nuke ops or wizard)
RaiseLocalEvent ( new RulePlayerSpawningEvent ( readyPlayers , profiles , force ) ) ;
2022-05-19 07:48:00 +10:00
var playerNetIds = readyPlayers . Select ( o = > o . UserId ) . ToHashSet ( ) ;
// RulePlayerSpawning feeds a readonlydictionary of profiles.
// We need to take these players out of the pool of players available as they've been used.
if ( readyPlayers . Count ! = profiles . Count )
{
var toRemove = new RemQueue < NetUserId > ( ) ;
foreach ( var ( player , _ ) in profiles )
{
2023-05-19 15:45:09 -05:00
if ( playerNetIds . Contains ( player ) )
continue ;
2022-05-19 07:48:00 +10:00
toRemove . Add ( player ) ;
}
foreach ( var player in toRemove )
{
profiles . Remove ( player ) ;
}
}
2023-10-19 12:34:31 -07:00
var spawnableStations = GetSpawnableStations ( ) ;
2023-05-19 15:45:09 -05:00
var assignedJobs = _stationJobs . AssignJobs ( profiles , spawnableStations ) ;
2022-02-21 14:11:39 -08:00
2023-05-19 15:45:09 -05:00
_stationJobs . AssignOverflowJobs ( ref assignedJobs , playerNetIds , profiles , spawnableStations ) ;
2022-02-21 14:11:39 -08:00
2022-05-27 06:01:07 +02:00
// Calculate extended access for stations.
2023-05-19 15:45:09 -05:00
var stationJobCounts = spawnableStations . ToDictionary ( e = > e , _ = > 0 ) ;
2022-10-29 23:49:43 -07:00
foreach ( var ( netUser , ( job , station ) ) in assignedJobs )
2022-05-27 06:01:07 +02:00
{
2022-10-29 23:49:43 -07:00
if ( job = = null )
{
2024-02-13 22:48:39 +01:00
var playerSession = _playerManager . GetSessionById ( netUser ) ;
2024-05-11 08:03:40 -07:00
_chatManager . DispatchServerMessage ( playerSession , Loc . GetString ( "job-not-available-wait-in-lobby" ) ) ;
2022-10-29 23:49:43 -07:00
}
else
{
stationJobCounts [ station ] + = 1 ;
}
2022-05-27 06:01:07 +02:00
}
_stationJobs . CalcExtendedAccess ( stationJobCounts ) ;
2022-02-21 14:11:39 -08:00
// Spawn everybody in!
foreach ( var ( player , ( job , station ) ) in assignedJobs )
{
2022-10-29 23:49:43 -07:00
if ( job = = null )
continue ;
2024-02-13 22:48:39 +01:00
SpawnPlayer ( _playerManager . GetSessionById ( player ) , profiles [ player ] , station , job , false ) ;
2022-02-21 14:11:39 -08:00
}
RefreshLateJoinAllowed ( ) ;
// Allow rules to add roles to players who have been spawned in. (For example, on-station traitors)
2024-05-07 03:50:22 -07:00
RaiseLocalEvent ( new RulePlayerJobsAssignedEvent (
assignedJobs . Keys . Select ( x = > _playerManager . GetSessionById ( x ) ) . ToArray ( ) ,
profiles ,
force ) ) ;
2022-02-21 14:11:39 -08:00
}
2024-05-07 03:50:22 -07:00
private void SpawnPlayer ( ICommonSession player ,
EntityUid station ,
string? jobId = null ,
bool lateJoin = true ,
bool silent = false )
2021-06-20 10:09:24 +02:00
{
var character = GetPlayerProfile ( player ) ;
2023-07-21 13:38:52 +02:00
var jobBans = _banManager . GetJobBans ( player . UserId ) ;
2022-08-07 08:00:42 +02:00
if ( jobBans = = null | | jobId ! = null & & jobBans . Contains ( jobId ) )
return ;
2024-06-01 05:08:31 -07:00
if ( jobId ! = null )
{
var ev = new IsJobAllowedEvent ( player , new ProtoId < JobPrototype > ( jobId ) ) ;
RaiseLocalEvent ( ref ev ) ;
if ( ev . Cancelled )
return ;
}
2023-08-30 21:06:15 -04:00
SpawnPlayer ( player , character , station , jobId , lateJoin , silent ) ;
2021-06-20 10:09:24 +02:00
}
2024-05-07 03:50:22 -07:00
private void SpawnPlayer ( ICommonSession player ,
HumanoidCharacterProfile character ,
EntityUid station ,
string? jobId = null ,
bool lateJoin = true ,
bool silent = false )
2021-06-20 10:09:24 +02:00
{
2021-12-21 21:23:29 +01:00
// Can't spawn players with a dummy ticker!
if ( DummyTicker )
return ;
2022-05-10 13:43:30 -05:00
if ( station = = EntityUid . Invalid )
2021-11-26 03:02:46 -06:00
{
2023-10-19 12:34:31 -07:00
var stations = GetSpawnableStations ( ) ;
2021-11-26 03:02:46 -06:00
_robustRandom . Shuffle ( stations ) ;
if ( stations . Count = = 0 )
2022-05-10 13:43:30 -05:00
station = EntityUid . Invalid ;
2021-11-26 03:02:46 -06:00
else
station = stations [ 0 ] ;
}
2021-12-21 19:25:52 +01:00
if ( lateJoin & & DisallowLateJoin )
2021-12-21 18:56:47 +01:00
{
2023-06-21 13:04:07 +12:00
JoinAsObserver ( player ) ;
2021-12-21 18:56:47 +01:00
return ;
}
2021-12-21 21:23:29 +01:00
// We raise this event to allow other systems to handle spawning this player themselves. (e.g. late-join wizard, etc)
var bev = new PlayerBeforeSpawnEvent ( player , character , jobId , lateJoin , station ) ;
RaiseLocalEvent ( bev ) ;
// Do nothing, something else has handled spawning this player for us!
if ( bev . Handled )
{
2023-08-30 21:06:15 -04:00
PlayerJoinGame ( player , silent ) ;
2021-12-21 21:23:29 +01:00
return ;
}
2022-08-07 08:00:42 +02:00
// Figure out job restrictions
2024-06-01 05:08:31 -07:00
var restrictedRoles = new HashSet < ProtoId < JobPrototype > > ( ) ;
var ev = new GetDisallowedJobsEvent ( player , restrictedRoles ) ;
RaiseLocalEvent ( ref ev ) ;
2022-08-07 08:00:42 +02:00
2023-07-21 13:38:52 +02:00
var jobBans = _banManager . GetJobBans ( player . UserId ) ;
2023-10-19 12:34:31 -07:00
if ( jobBans ! = null )
restrictedRoles . UnionWith ( jobBans ) ;
2022-08-07 08:00:42 +02:00
2021-11-26 16:51:00 -06:00
// Pick best job best on prefs.
2024-05-07 03:50:22 -07:00
jobId ? ? = _stationJobs . PickBestAvailableJobWithPriority ( station ,
character . JobPriorities ,
true ,
2022-08-07 08:00:42 +02:00
restrictedRoles ) ;
2022-01-23 09:44:27 -08:00
// If no job available, stay in lobby, or if no lobby spawn as observer
2021-11-26 16:51:00 -06:00
if ( jobId is null )
{
2022-01-23 09:44:27 -08:00
if ( ! LobbyEnabled )
{
2023-06-21 13:04:07 +12:00
JoinAsObserver ( player ) ;
2022-01-23 09:44:27 -08:00
}
2024-05-07 03:50:22 -07:00
_chatManager . DispatchServerMessage ( player ,
Loc . GetString ( "game-ticker-player-no-jobs-available-when-joining" ) ) ;
2021-11-26 16:51:00 -06:00
return ;
}
2023-08-30 21:06:15 -04:00
PlayerJoinGame ( player , silent ) ;
2021-06-20 10:09:24 +02:00
var data = player . ContentData ( ) ;
DebugTools . AssertNotNull ( data ) ;
2023-06-21 13:04:07 +12:00
var newMind = _mind . CreateMind ( data ! . UserId , character . Name ) ;
_mind . SetUserId ( newMind , data . UserId ) ;
2021-06-20 10:09:24 +02:00
var jobPrototype = _prototypeManager . Index < JobPrototype > ( jobId ) ;
2024-10-10 10:48:56 +02:00
_roles . MindAddJobRole ( newMind , silent : silent , jobPrototype : jobId ) ;
2023-08-28 16:53:24 -07:00
var jobName = _jobs . MindTryGetJobName ( newMind ) ;
2021-06-20 10:09:24 +02:00
2022-08-07 08:00:42 +02:00
_playTimeTrackings . PlayerRolesChanged ( player ) ;
2024-10-10 10:48:56 +02:00
var mobMaybe = _stationSpawning . SpawnPlayerCharacterOnStation ( station , jobId , character ) ;
2022-11-08 16:13:20 -05:00
DebugTools . AssertNotNull ( mobMaybe ) ;
var mob = mobMaybe ! . Value ;
2023-06-21 13:04:07 +12:00
_mind . TransferTo ( newMind , mob ) ;
2022-11-08 16:13:20 -05:00
2023-08-30 21:06:15 -04:00
if ( lateJoin & & ! silent )
2021-06-20 10:09:24 +02:00
{
2024-09-09 21:57:37 +02:00
if ( jobPrototype . JoinNotifyCrew )
{
_chatSystem . DispatchStationAnnouncement ( station ,
Loc . GetString ( "latejoin-arrival-announcement-special" ,
( "character" , MetaData ( mob ) . EntityName ) ,
2024-11-18 14:40:52 +03:00
( "gender" , character . Gender ) , // CrystallEdge-LastnameGender
2024-09-09 21:57:37 +02:00
( "entity" , mob ) ,
( "job" , CultureInfo . CurrentCulture . TextInfo . ToTitleCase ( jobName ) ) ) ,
Loc . GetString ( "latejoin-arrival-sender" ) ,
playDefaultSound : false ,
colorOverride : Color . Gold ) ;
}
else
{
_chatSystem . DispatchStationAnnouncement ( station ,
Loc . GetString ( "latejoin-arrival-announcement" ,
( "character" , MetaData ( mob ) . EntityName ) ,
2024-11-18 14:40:52 +03:00
( "gender" , character . Gender ) , // CrystallEdge-LastnameGender
2024-09-09 21:57:37 +02:00
( "entity" , mob ) ,
( "job" , CultureInfo . CurrentCulture . TextInfo . ToTitleCase ( jobName ) ) ) ,
Loc . GetString ( "latejoin-arrival-sender" ) ,
playDefaultSound : false ) ;
}
2021-06-20 10:09:24 +02:00
}
if ( player . UserId = = new Guid ( "{e887eb93-f503-4b65-95b6-2f282c014192}" ) )
{
2021-12-07 14:19:41 -08:00
EntityManager . AddComponent < OwOAccentComponent > ( mob ) ;
2021-06-20 10:09:24 +02:00
}
2024-01-15 01:35:28 -05:00
_stationJobs . TryAssignJob ( station , jobPrototype , player . UserId ) ;
2021-11-26 03:02:46 -06:00
if ( lateJoin )
2024-10-10 10:48:56 +02:00
{
2024-05-07 03:50:22 -07:00
_adminLogger . Add ( LogType . LateJoin ,
LogImpact . Medium ,
$"Player {player.Name} late joined as {character.Name:characterName} on station {Name(station):stationName} with {ToPrettyString(mob):entity} as a {jobName:jobName}." ) ;
2024-10-10 10:48:56 +02:00
}
2021-11-26 03:02:46 -06:00
else
2024-10-10 10:48:56 +02:00
{
2024-05-07 03:50:22 -07:00
_adminLogger . Add ( LogType . RoundStartJoin ,
LogImpact . Medium ,
$"Player {player.Name} joined as {character.Name:characterName} on station {Name(station):stationName} with {ToPrettyString(mob):entity} as a {jobName:jobName}." ) ;
2024-10-10 10:48:56 +02:00
}
2021-11-26 03:02:46 -06:00
2022-05-27 06:01:07 +02:00
// Make sure they're aware of extended access.
if ( Comp < StationJobsComponent > ( station ) . ExtendedAccess
2024-05-07 03:50:22 -07:00
& & ( jobPrototype . ExtendedAccess . Count > 0 | | jobPrototype . ExtendedAccessGroups . Count > 0 ) )
2022-05-27 06:01:07 +02:00
{
_chatManager . DispatchServerMessage ( player , Loc . GetString ( "job-greet-crew-shortages" ) ) ;
}
2023-08-30 21:06:15 -04:00
if ( ! silent & & TryComp ( station , out MetaDataComponent ? metaData ) )
2022-10-22 14:51:51 -07:00
{
_chatManager . DispatchServerMessage ( player ,
Loc . GetString ( "job-greet-station-name" , ( "stationName" , metaData . EntityName ) ) ) ;
}
2021-12-21 21:23:29 +01:00
// We raise this event directed to the mob, but also broadcast it so game rules can do something now.
2022-07-28 20:59:45 -04:00
PlayersJoinedRoundNormally + + ;
2024-05-07 03:50:22 -07:00
var aev = new PlayerSpawnCompleteEvent ( mob ,
player ,
jobId ,
lateJoin ,
2024-08-27 10:02:21 -05:00
silent ,
2024-05-07 03:50:22 -07:00
PlayersJoinedRoundNormally ,
station ,
character ) ;
2022-06-22 09:53:41 +10:00
RaiseLocalEvent ( mob , aev , true ) ;
2021-06-20 10:09:24 +02:00
}
2023-10-28 09:59:53 +11:00
public void Respawn ( ICommonSession player )
2021-06-20 10:09:24 +02:00
{
2023-06-21 13:04:07 +12:00
_mind . WipeMind ( player ) ;
2022-05-28 23:41:17 -07:00
_adminLogger . Add ( LogType . Respawn , LogImpact . Medium , $"Player {player} was respawned." ) ;
2021-06-20 10:09:24 +02:00
if ( LobbyEnabled )
PlayerJoinLobby ( player ) ;
else
2022-05-10 13:43:30 -05:00
SpawnPlayer ( player , EntityUid . Invalid ) ;
2021-06-20 10:09:24 +02:00
}
2023-08-30 21:06:15 -04:00
/// <summary>
2024-08-27 10:02:21 -05:00
/// Makes a player join into the game and spawn on a station.
2023-08-30 21:06:15 -04:00
/// </summary>
/// <param name="player">The player joining</param>
/// <param name="station">The station they're spawning on</param>
/// <param name="jobId">An optional job for them to spawn as</param>
/// <param name="silent">Whether or not the player should be greeted upon joining</param>
2024-05-11 08:03:40 -07:00
public void MakeJoinGame ( ICommonSession player , EntityUid station , string? jobId = null , bool silent = false )
2021-06-20 10:09:24 +02:00
{
2022-08-14 12:54:49 -07:00
if ( ! _playerGameStatuses . ContainsKey ( player . UserId ) )
2022-08-07 08:00:42 +02:00
return ;
2021-06-20 10:09:24 +02:00
2022-08-07 08:00:42 +02:00
if ( ! _userDb . IsLoadComplete ( player ) )
2021-06-20 10:09:24 +02:00
return ;
2023-08-30 21:06:15 -04:00
SpawnPlayer ( player , station , jobId , silent : silent ) ;
2021-06-20 10:09:24 +02:00
}
2023-06-21 13:04:07 +12:00
/// <summary>
/// Causes the given player to join the current game as observer ghost. See also <see cref="SpawnObserver"/>
/// </summary>
2023-10-28 09:59:53 +11:00
public void JoinAsObserver ( ICommonSession player )
2021-06-20 10:09:24 +02:00
{
// Can't spawn players with a dummy ticker!
if ( DummyTicker )
return ;
PlayerJoinGame ( player ) ;
2023-06-21 13:04:07 +12:00
SpawnObserver ( player ) ;
}
2021-06-20 10:09:24 +02:00
2023-06-21 13:04:07 +12:00
/// <summary>
/// Spawns an observer ghost and attaches the given player to it. If the player does not yet have a mind, the
/// player is given a new mind with the observer role. Otherwise, the current mind is transferred to the ghost.
/// </summary>
2023-10-28 09:59:53 +11:00
public void SpawnObserver ( ICommonSession player )
2023-06-21 13:04:07 +12:00
{
if ( DummyTicker )
return ;
2021-06-20 10:09:24 +02:00
2024-05-11 08:03:40 -07:00
Entity < MindComponent ? > ? mind = player . GetMind ( ) ;
2023-06-21 13:04:07 +12:00
if ( mind = = null )
{
2024-05-11 08:03:40 -07:00
var name = GetPlayerProfile ( player ) . Name ;
var ( mindId , mindComp ) = _mind . CreateMind ( player . UserId , name ) ;
mind = ( mindId , mindComp ) ;
2023-08-28 16:53:24 -07:00
_mind . SetUserId ( mind . Value , player . UserId ) ;
2024-10-10 10:48:56 +02:00
_roles . MindAddRole ( mind . Value , "MindRoleObserver" ) ;
2023-06-21 13:04:07 +12:00
}
2021-06-20 10:09:24 +02:00
2024-05-11 08:03:40 -07:00
var ghost = _ghost . SpawnGhost ( mind . Value ) ;
2024-05-07 03:50:22 -07:00
_adminLogger . Add ( LogType . LateJoin ,
LogImpact . Low ,
$"{player.Name} late joined the round as an Observer with {ToPrettyString(ghost):entity}." ) ;
2021-06-20 10:09:24 +02:00
}
#region Spawn Points
2024-05-07 03:50:22 -07:00
2021-06-20 10:09:24 +02:00
public EntityCoordinates GetObserverSpawnPoint ( )
{
_possiblePositions . Clear ( ) ;
2024-05-20 09:52:49 -04:00
var spawnPointQuery = EntityManager . EntityQueryEnumerator < SpawnPointComponent , TransformComponent > ( ) ;
while ( spawnPointQuery . MoveNext ( out var uid , out var point , out var transform ) )
2021-06-20 10:09:24 +02:00
{
2024-05-20 09:52:49 -04:00
if ( point . SpawnType ! = SpawnPointType . Observer
| | TerminatingOrDeleted ( uid )
| | transform . MapUid = = null
| | TerminatingOrDeleted ( transform . MapUid . Value ) )
{
2022-08-29 15:05:53 +10:00
continue ;
2024-05-20 09:52:49 -04:00
}
2022-08-29 15:05:53 +10:00
_possiblePositions . Add ( transform . Coordinates ) ;
}
var metaQuery = GetEntityQuery < MetaDataComponent > ( ) ;
// Fallback to a random grid.
if ( _possiblePositions . Count = = 0 )
{
2023-10-19 12:34:31 -07:00
var query = AllEntityQuery < MapGridComponent > ( ) ;
while ( query . MoveNext ( out var uid , out var grid ) )
2022-08-29 15:05:53 +10:00
{
2024-05-11 08:03:40 -07:00
if ( ! metaQuery . TryGetComponent ( uid , out var meta ) | | meta . EntityPaused | | TerminatingOrDeleted ( uid ) )
2022-08-29 15:05:53 +10:00
{
continue ;
}
2023-10-19 12:34:31 -07:00
_possiblePositions . Add ( new EntityCoordinates ( uid , Vector2 . Zero ) ) ;
2022-08-29 15:05:53 +10:00
}
2021-06-20 10:09:24 +02:00
}
if ( _possiblePositions . Count ! = 0 )
2022-08-29 15:05:53 +10:00
{
// TODO: This is just here for the eye lerping.
// Ideally engine would just spawn them on grid directly I guess? Right now grid traversal is handling it during
// update which means we need to add a hack somewhere around it.
var spawn = _robustRandom . Pick ( _possiblePositions ) ;
2024-03-20 21:59:56 -04:00
var toMap = spawn . ToMap ( EntityManager , _transform ) ;
2022-08-29 15:05:53 +10:00
2023-05-28 23:22:44 +10:00
if ( _mapManager . TryFindGridAt ( toMap , out var gridUid , out _ ) )
2022-08-29 15:05:53 +10:00
{
2023-05-28 23:22:44 +10:00
var gridXform = Transform ( gridUid ) ;
2022-11-01 11:27:18 +11:00
2024-06-02 05:07:41 +01:00
return new EntityCoordinates ( gridUid , Vector2 . Transform ( toMap . Position , gridXform . InvWorldMatrix ) ) ;
2022-08-29 15:05:53 +10:00
}
return spawn ;
}
if ( _mapManager . MapExists ( DefaultMap ) )
{
2024-05-20 09:52:49 -04:00
var mapUid = _mapManager . GetMapEntityId ( DefaultMap ) ;
if ( ! TerminatingOrDeleted ( mapUid ) )
return new EntityCoordinates ( mapUid , Vector2 . Zero ) ;
2022-08-29 15:05:53 +10:00
}
// Just pick a point at this point I guess.
foreach ( var map in _mapManager . GetAllMapIds ( ) )
{
var mapUid = _mapManager . GetMapEntityId ( map ) ;
2024-05-11 08:03:40 -07:00
if ( ! metaQuery . TryGetComponent ( mapUid , out var meta )
| | meta . EntityPaused
| | TerminatingOrDeleted ( mapUid ) )
2022-08-29 15:05:53 +10:00
{
continue ;
}
return new EntityCoordinates ( mapUid , Vector2 . Zero ) ;
}
2021-06-20 10:09:24 +02:00
2022-08-29 15:05:53 +10:00
// AAAAAAAAAAAAA
2023-01-24 13:33:49 +13:00
// This should be an error, if it didn't cause tests to start erroring when they delete a player.
_sawmill . Warning ( "Found no observer spawn points!" ) ;
2022-08-29 15:05:53 +10:00
return EntityCoordinates . Invalid ;
2021-06-20 10:09:24 +02:00
}
2024-05-07 03:50:22 -07:00
2021-06-20 10:09:24 +02:00
#endregion
}
2021-12-21 21:23:29 +01:00
/// <summary>
/// Event raised broadcast before a player is spawned by the GameTicker.
/// You can use this event to spawn a player off-station on late-join but also at round start.
/// When this event is handled, the GameTicker will not perform its own player-spawning logic.
/// </summary>
2022-05-10 13:43:30 -05:00
[PublicAPI]
2022-02-16 00:23:23 -07:00
public sealed class PlayerBeforeSpawnEvent : HandledEntityEventArgs
2021-12-21 21:23:29 +01:00
{
2023-10-28 09:59:53 +11:00
public ICommonSession Player { get ; }
2021-12-21 21:23:29 +01:00
public HumanoidCharacterProfile Profile { get ; }
public string? JobId { get ; }
public bool LateJoin { get ; }
2022-05-10 13:43:30 -05:00
public EntityUid Station { get ; }
2021-12-21 21:23:29 +01:00
2024-05-07 03:50:22 -07:00
public PlayerBeforeSpawnEvent ( ICommonSession player ,
HumanoidCharacterProfile profile ,
string? jobId ,
bool lateJoin ,
EntityUid station )
2021-12-21 21:23:29 +01:00
{
Player = player ;
Profile = profile ;
JobId = jobId ;
LateJoin = lateJoin ;
Station = station ;
}
}
/// <summary>
/// Event raised both directed and broadcast when a player has been spawned by the GameTicker.
/// You can use this to handle people late-joining, or to handle people being spawned at round start.
/// Can be used to give random players a role, modify their equipment, etc.
/// </summary>
2022-05-10 13:43:30 -05:00
[PublicAPI]
2022-02-16 00:23:23 -07:00
public sealed class PlayerSpawnCompleteEvent : EntityEventArgs
2021-12-21 21:23:29 +01:00
{
public EntityUid Mob { get ; }
2023-10-28 09:59:53 +11:00
public ICommonSession Player { get ; }
2021-12-21 21:23:29 +01:00
public string? JobId { get ; }
public bool LateJoin { get ; }
2024-08-27 10:02:21 -05:00
public bool Silent { get ; }
2022-05-10 13:43:30 -05:00
public EntityUid Station { get ; }
2021-12-21 21:23:29 +01:00
public HumanoidCharacterProfile Profile { get ; }
2022-07-28 20:59:45 -04:00
// Ex. If this is the 27th person to join, this will be 27.
public int JoinOrder { get ; }
2024-05-07 03:50:22 -07:00
public PlayerSpawnCompleteEvent ( EntityUid mob ,
ICommonSession player ,
string? jobId ,
bool lateJoin ,
2024-08-27 10:02:21 -05:00
bool silent ,
2024-05-07 03:50:22 -07:00
int joinOrder ,
EntityUid station ,
HumanoidCharacterProfile profile )
2021-12-21 21:23:29 +01:00
{
Mob = mob ;
Player = player ;
JobId = jobId ;
LateJoin = lateJoin ;
2024-08-27 10:02:21 -05:00
Silent = silent ;
2021-12-21 21:23:29 +01:00
Station = station ;
Profile = profile ;
2022-07-28 20:59:45 -04:00
JoinOrder = joinOrder ;
2021-12-21 21:23:29 +01:00
}
}
2021-06-20 10:09:24 +02:00
}