2024-08-26 15:15:33 +03:00
using Content.Server.Administration.Logs ;
using Content.Server.Chat.Managers ;
2021-09-20 19:06:48 +10:00
using Content.Server.GameTicking ;
2021-06-09 22:19:39 +02:00
using Content.Server.Ghost.Components ;
2023-09-01 12:30:29 +10:00
using Content.Server.Mind ;
using Content.Server.Roles.Jobs ;
2021-06-18 09:56:23 +02:00
using Content.Server.Warps ;
2022-02-26 18:24:08 +13:00
using Content.Shared.Actions ;
2024-08-26 15:15:33 +03:00
using Content.Shared.CCVar ;
using Content.Shared.Damage ;
using Content.Shared.Damage.Prototypes ;
using Content.Shared.Database ;
2021-06-18 09:56:23 +02:00
using Content.Shared.Examine ;
2023-09-11 16:15:23 +10:00
using Content.Shared.Eye ;
2024-08-26 15:15:33 +03:00
using Content.Shared.FixedPoint ;
2022-02-19 12:16:27 -07:00
using Content.Shared.Follower ;
2021-06-18 09:56:23 +02:00
using Content.Shared.Ghost ;
2023-08-30 21:46:11 -07:00
using Content.Shared.Mind ;
using Content.Shared.Mind.Components ;
2024-08-26 15:15:33 +03:00
using Content.Shared.Mobs ;
2023-01-13 16:57:10 -08:00
using Content.Shared.Mobs.Components ;
using Content.Shared.Mobs.Systems ;
2022-06-24 17:44:30 +10:00
using Content.Shared.Movement.Events ;
2023-12-26 00:19:12 +11:00
using Content.Shared.Movement.Systems ;
2023-02-11 20:12:29 -05:00
using Content.Shared.Storage.Components ;
2021-06-18 09:56:23 +02:00
using Robust.Server.GameObjects ;
using Robust.Server.Player ;
2024-08-26 15:15:33 +03:00
using Robust.Shared.Configuration ;
2024-05-11 08:03:40 -07:00
using Robust.Shared.Map ;
2022-09-14 17:26:26 +10:00
using Robust.Shared.Physics.Components ;
2023-01-15 15:38:59 +11:00
using Robust.Shared.Physics.Systems ;
2023-10-29 04:21:02 +11:00
using Robust.Shared.Player ;
2024-08-26 15:15:33 +03:00
using Robust.Shared.Prototypes ;
2021-06-18 09:56:23 +02:00
using Robust.Shared.Timing ;
2024-08-26 15:15:33 +03:00
using System.Linq ;
using System.Numerics ;
2021-03-31 14:17:22 -07:00
2021-06-09 22:19:39 +02:00
namespace Content.Server.Ghost
2021-03-31 14:17:22 -07:00
{
2023-09-15 22:21:33 -07:00
public sealed class GhostSystem : SharedGhostSystem
2021-03-31 14:17:22 -07:00
{
2022-02-26 18:24:08 +13:00
[Dependency] private readonly SharedActionsSystem _actions = default ! ;
2023-09-15 22:21:33 -07:00
[Dependency] private readonly SharedEyeSystem _eye = default ! ;
2022-02-19 12:16:27 -07:00
[Dependency] private readonly FollowerSystem _followerSystem = default ! ;
2023-09-15 22:21:33 -07:00
[Dependency] private readonly IGameTiming _gameTiming = default ! ;
[Dependency] private readonly JobSystem _jobs = default ! ;
[Dependency] private readonly EntityLookupSystem _lookup = default ! ;
[Dependency] private readonly MindSystem _minds = default ! ;
2022-10-05 22:55:11 -04:00
[Dependency] private readonly MobStateSystem _mobState = default ! ;
2023-01-15 15:38:59 +11:00
[Dependency] private readonly SharedPhysicsSystem _physics = default ! ;
2023-09-15 22:21:33 -07:00
[Dependency] private readonly IPlayerManager _playerManager = default ! ;
[Dependency] private readonly TransformSystem _transformSystem = default ! ;
[Dependency] private readonly VisibilitySystem _visibilitySystem = default ! ;
2024-05-11 08:03:40 -07:00
[Dependency] private readonly MetaDataSystem _metaData = default ! ;
2024-08-26 15:15:33 +03:00
[Dependency] private readonly MobThresholdSystem _mobThresholdSystem = default ! ;
[Dependency] private readonly IPrototypeManager _prototypeManager = default ! ;
[Dependency] private readonly IAdminLogManager _adminLogger = default ! ;
[Dependency] private readonly IConfigurationManager _configurationManager = default ! ;
[Dependency] private readonly IChatManager _chatManager = default ! ;
[Dependency] private readonly SharedMindSystem _mind = default ! ;
[Dependency] private readonly GameTicker _gameTicker = default ! ;
[Dependency] private readonly DamageableSystem _damageable = default ! ;
2021-06-18 09:56:23 +02:00
2024-05-01 13:59:35 +00:00
private EntityQuery < GhostComponent > _ghostQuery ;
private EntityQuery < PhysicsComponent > _physicsQuery ;
2021-03-31 14:17:22 -07:00
public override void Initialize ( )
{
base . Initialize ( ) ;
2024-05-01 13:59:35 +00:00
_ghostQuery = GetEntityQuery < GhostComponent > ( ) ;
_physicsQuery = GetEntityQuery < PhysicsComponent > ( ) ;
2021-06-18 09:56:23 +02:00
SubscribeLocalEvent < GhostComponent , ComponentStartup > ( OnGhostStartup ) ;
2023-09-23 04:49:39 -04:00
SubscribeLocalEvent < GhostComponent , MapInitEvent > ( OnMapInit ) ;
2021-06-18 09:56:23 +02:00
SubscribeLocalEvent < GhostComponent , ComponentShutdown > ( OnGhostShutdown ) ;
SubscribeLocalEvent < GhostComponent , ExaminedEvent > ( OnGhostExamine ) ;
2021-03-31 14:17:22 -07:00
SubscribeLocalEvent < GhostComponent , MindRemovedMessage > ( OnMindRemovedMessage ) ;
SubscribeLocalEvent < GhostComponent , MindUnvisitedMessage > ( OnMindUnvisitedMessage ) ;
2022-10-27 07:09:35 -07:00
SubscribeLocalEvent < GhostComponent , PlayerDetachedEvent > ( OnPlayerDetached ) ;
2021-06-18 09:56:23 +02:00
2022-07-16 13:51:52 +10:00
SubscribeLocalEvent < GhostOnMoveComponent , MoveInputEvent > ( OnRelayMoveInput ) ;
2021-09-20 19:06:48 +10:00
2021-06-18 09:56:23 +02:00
SubscribeNetworkEvent < GhostWarpsRequestEvent > ( OnGhostWarpsRequest ) ;
SubscribeNetworkEvent < GhostReturnToBodyRequest > ( OnGhostReturnToBodyRequest ) ;
SubscribeNetworkEvent < GhostWarpToTargetRequestEvent > ( OnGhostWarpToTargetRequest ) ;
2024-05-01 13:59:35 +00:00
SubscribeNetworkEvent < GhostnadoRequestEvent > ( OnGhostnadoRequest ) ;
2022-02-26 18:24:08 +13:00
SubscribeLocalEvent < GhostComponent , BooActionEvent > ( OnActionPerform ) ;
2023-09-24 13:34:08 -07:00
SubscribeLocalEvent < GhostComponent , ToggleGhostHearingActionEvent > ( OnGhostHearingAction ) ;
2022-06-24 16:26:56 -03:00
SubscribeLocalEvent < GhostComponent , InsertIntoEntityStorageAttemptEvent > ( OnEntityStorageInsertAttempt ) ;
2022-10-05 22:55:11 -04:00
SubscribeLocalEvent < RoundEndTextAppendEvent > ( _ = > MakeVisible ( true ) ) ;
Magic Refactor + Wizard Grimoire (#22568)
* Brings over changes from the original magic refactor PR
* Adds Master Spellbook, spellbook categories, WizCoin currency, and locale
* Wiz€oin™
* Adds currency whitelist to Spellbook preset, grants contained actions on action added.
* Adds grant contained action and remove provided action.
* adds a way for actions to be upgraded to the store
* Adds Fireball 3 and fixes action upgrade logic so that it checks if the action can level or if the action can upgrade separately
* Fixes upgrade logic in ActionUpgradeSystem to allow for level ups without an actual upgrade. Fixed action upgrade logic in store system as well
* Removes current action entity from the bought entities list and adds new or old action entity
* Removes Current Entity
* Removes old comments, fixes TransferAllActionsWithNewAttached
* Removes TODO
* Removes Product Action Upgrade Event
* reverts changes to immovablerodrule
* Removes stale event reference
* fixes mind action grant logic
* reverts shared gun system change to projectile anomaly system
* forgor to remove the using
* Reverts unintended changes to action container
* Adds refund button to the store
* Refreshes store back to origin.
* Refund with correct currency
* Init refund
* Check for terminating and update interface
* Disables refund button
* Removes preset allow refund
* dont refund if map changed
* adds refunds to stores
* Adds method to check for starting map
* comments, datafields, some requested changes
* turns event into ref event
* Adds datafields
* Switches to entity terminating event
* Changes store entity to be nullable and checks if store is terminating to remove reference.
* Tryadd instead of containskey
* Adds a refund disable method, disables refund on bought ent container changes if not an action
* Removes duplicate refundcomp
* Removes unintended merges
* Removed another unintended change from merge
* removes extra using statement
* readds using statement
* might as well just remove both usings since it won't leave the PR
* Fixes Action upgrades from stores
* Changes to non obsolete method uses
* Shares spawn code between instant and world
* Adds action entity to action event, adds beforecastspellevent, adds spell requirements to magic component
* puts prereq check in spell methods, sets up template code for before cast event
* checks for required wizard clothes
* Networks Magic Comp and Wizard Clothes Comp. Renames MagicSpawnData to MagicInstantSpawnData.
* Removes posdata from projectiles
* Speech > RequiresSpeech
* Fixes ActionOnInteract
* checks for muted
* popup for missing reqs
* Validate click loc for blink spell
* Checks if doors are in range and not obstructed before opening
* Check ents by map coords
* Adds speak event
* Comments spellbooks
* Removes comments
* Unobsoletes smite spell
* Invert if
* Requirements loc
* Fixes spell reqs
* Inverts an if
* Comment updates
* Starts doafter work
* Removes doafter references
* Balances fireball upgrades to be more reasonable
* Enables refund on master spellbooks
* Spells to do
* update spellbook doafter
* knock toggles bolts
* Touch Spell comments
* Comments for pending spells
* more comments
* adds spider polymorph to spellbook
* TODOs for spells
* reorganizes spellbook categories and adds wands
* fixes spacing and adds limited conditions
* commented owner only for future store PR
* reenables owner only for the grimoire
* fixes grimoire sprite
* Adds wizard rod polymorph
* summon ghosts event
* Moves rod form to offensive category
* Adds charge spell and loc for rod polymorph
* Oops forgor the actual chages
* Item Recall comment
* Fixes UI
* removes extra field for wizard rod
* Cleanup
* New Condition (INCOMPLETE)
* Fix linter
* Fix linter (for real)
* fixed some descriptions
* adds regions to magic
* Adds a non-refund wizard grimoire, fixes blink to deselect after teleporting, reduces force wall despawn time to 12 seconds
* removes limited upgrade condition
---------
Co-authored-by: AJCM <AJCM@tutanota.com>
2024-05-11 19:06:49 -04:00
SubscribeLocalEvent < ToggleGhostVisibilityToAllEvent > ( OnToggleGhostVisibilityToAll ) ;
2022-02-26 18:24:08 +13:00
}
2022-10-27 07:09:35 -07:00
2023-09-24 13:34:08 -07:00
private void OnGhostHearingAction ( EntityUid uid , GhostComponent component , ToggleGhostHearingActionEvent args )
{
args . Handled = true ;
if ( HasComp < GhostHearingComponent > ( uid ) )
{
RemComp < GhostHearingComponent > ( uid ) ;
_actions . SetToggled ( component . ToggleGhostHearingActionEntity , true ) ;
}
else
{
AddComp < GhostHearingComponent > ( uid ) ;
_actions . SetToggled ( component . ToggleGhostHearingActionEntity , false ) ;
}
var str = HasComp < GhostHearingComponent > ( uid )
? Loc . GetString ( "ghost-gui-toggle-hearing-popup-on" )
: Loc . GetString ( "ghost-gui-toggle-hearing-popup-off" ) ;
Popup . PopupEntity ( str , uid , uid ) ;
Dirty ( uid , component ) ;
}
2022-02-26 18:24:08 +13:00
private void OnActionPerform ( EntityUid uid , GhostComponent component , BooActionEvent args )
{
if ( args . Handled )
return ;
2023-09-15 22:21:33 -07:00
var entities = _lookup . GetEntitiesInRange ( args . Performer , component . BooRadius ) ;
2022-02-26 18:24:08 +13:00
var booCounter = 0 ;
2023-09-15 22:21:33 -07:00
foreach ( var ent in entities )
2022-02-26 18:24:08 +13:00
{
2022-08-13 09:49:41 -04:00
var handled = DoGhostBooEvent ( ent ) ;
2022-02-26 18:24:08 +13:00
2022-08-13 09:49:41 -04:00
if ( handled )
2022-02-26 18:24:08 +13:00
booCounter + + ;
if ( booCounter > = component . BooMaxTargets )
break ;
}
args . Handled = true ;
2021-06-18 09:56:23 +02:00
}
2022-07-16 13:51:52 +10:00
private void OnRelayMoveInput ( EntityUid uid , GhostOnMoveComponent component , ref MoveInputEvent args )
2021-09-20 19:06:48 +10:00
{
2023-12-26 00:19:12 +11:00
// If they haven't actually moved then ignore it.
2024-08-01 18:32:32 -07:00
if ( ( args . Entity . Comp . HeldMoveButtons &
2023-12-26 00:19:12 +11:00
( MoveButtons . Down | MoveButtons . Left | MoveButtons . Up | MoveButtons . Right ) ) = = 0x0 )
{
return ;
}
2021-09-20 19:06:48 +10:00
// Let's not ghost if our mind is visiting...
2023-09-15 22:21:33 -07:00
if ( HasComp < VisitingMindComponent > ( uid ) )
2022-10-05 22:55:11 -04:00
return ;
2023-08-28 16:53:24 -07:00
if ( ! _minds . TryGetMind ( uid , out var mindId , out var mind ) | | mind . IsVisitingEntity )
2022-10-05 22:55:11 -04:00
return ;
if ( component . MustBeDead & & ( _mobState . IsAlive ( uid ) | | _mobState . IsCritical ( uid ) ) )
return ;
2021-09-20 19:06:48 +10:00
2024-08-26 15:15:33 +03:00
OnGhostAttempt ( mindId , component . CanReturn , mind : mind ) ;
2021-09-20 19:06:48 +10:00
}
2021-06-18 09:56:23 +02:00
private void OnGhostStartup ( EntityUid uid , GhostComponent component , ComponentStartup args )
{
// Allow this entity to be seen by other ghosts.
2023-09-15 22:21:33 -07:00
var visibility = EnsureComp < VisibilityComponent > ( uid ) ;
2021-09-30 13:57:01 +02:00
2024-09-29 03:25:21 +03:00
if ( _gameTicker . RunLevel ! = GameRunLevel . PostRound )
2022-10-05 22:55:11 -04:00
{
2024-06-05 00:04:31 -07:00
_visibilitySystem . AddLayer ( ( uid , visibility ) , ( int ) VisibilityFlags . Ghost , false ) ;
_visibilitySystem . RemoveLayer ( ( uid , visibility ) , ( int ) VisibilityFlags . Normal , false ) ;
2023-09-15 22:21:33 -07:00
_visibilitySystem . RefreshVisibility ( uid , visibilityComponent : visibility ) ;
2022-10-05 22:55:11 -04:00
}
2021-06-18 09:56:23 +02:00
2023-09-15 22:21:33 -07:00
SetCanSeeGhosts ( uid , true ) ;
2021-06-18 09:56:23 +02:00
2023-09-09 16:14:17 -07:00
var time = _gameTiming . CurTime ;
component . TimeOfDeath = time ;
2021-06-18 09:56:23 +02:00
}
private void OnGhostShutdown ( EntityUid uid , GhostComponent component , ComponentShutdown args )
{
// Perf: If the entity is deleting itself, no reason to change these back.
2023-09-15 22:21:33 -07:00
if ( Terminating ( uid ) )
return ;
// Entity can't be seen by ghosts anymore.
if ( TryComp ( uid , out VisibilityComponent ? visibility ) )
2021-06-18 09:56:23 +02:00
{
2024-06-05 00:04:31 -07:00
_visibilitySystem . RemoveLayer ( ( uid , visibility ) , ( int ) VisibilityFlags . Ghost , false ) ;
_visibilitySystem . AddLayer ( ( uid , visibility ) , ( int ) VisibilityFlags . Normal , false ) ;
2023-09-15 22:21:33 -07:00
_visibilitySystem . RefreshVisibility ( uid , visibilityComponent : visibility ) ;
}
2021-06-18 09:56:23 +02:00
2023-09-15 22:21:33 -07:00
// Entity can't see ghosts anymore.
SetCanSeeGhosts ( uid , false ) ;
2023-09-23 04:49:39 -04:00
_actions . RemoveAction ( uid , component . BooActionEntity ) ;
2023-09-15 22:21:33 -07:00
}
private void SetCanSeeGhosts ( EntityUid uid , bool canSee , EyeComponent ? eyeComponent = null )
{
if ( ! Resolve ( uid , ref eyeComponent , false ) )
return ;
if ( canSee )
_eye . SetVisibilityMask ( uid , eyeComponent . VisibilityMask | ( int ) VisibilityFlags . Ghost , eyeComponent ) ;
else
_eye . SetVisibilityMask ( uid , eyeComponent . VisibilityMask & ~ ( int ) VisibilityFlags . Ghost , eyeComponent ) ;
2021-06-18 09:56:23 +02:00
}
2023-09-23 04:49:39 -04:00
private void OnMapInit ( EntityUid uid , GhostComponent component , MapInitEvent args )
{
if ( _actions . AddAction ( uid , ref component . BooActionEntity , out var act , component . BooAction )
& & act . UseDelay ! = null )
{
var start = _gameTiming . CurTime ;
var end = start + act . UseDelay . Value ;
_actions . SetCooldown ( component . BooActionEntity . Value , start , end ) ;
}
2023-09-24 13:34:08 -07:00
_actions . AddAction ( uid , ref component . ToggleGhostHearingActionEntity , component . ToggleGhostHearingAction ) ;
2023-09-23 04:49:39 -04:00
_actions . AddAction ( uid , ref component . ToggleLightingActionEntity , component . ToggleLightingAction ) ;
_actions . AddAction ( uid , ref component . ToggleFoVActionEntity , component . ToggleFoVAction ) ;
_actions . AddAction ( uid , ref component . ToggleGhostsActionEntity , component . ToggleGhostsAction ) ;
}
2021-06-18 09:56:23 +02:00
private void OnGhostExamine ( EntityUid uid , GhostComponent component , ExaminedEvent args )
{
var timeSinceDeath = _gameTiming . RealTime . Subtract ( component . TimeOfDeath ) ;
var deathTimeInfo = timeSinceDeath . Minutes > 0
? Loc . GetString ( "comp-ghost-examine-time-minutes" , ( "minutes" , timeSinceDeath . Minutes ) )
: Loc . GetString ( "comp-ghost-examine-time-seconds" , ( "seconds" , timeSinceDeath . Seconds ) ) ;
2021-09-15 16:58:15 +02:00
args . PushMarkup ( deathTimeInfo ) ;
2021-03-31 14:17:22 -07:00
}
2023-09-15 22:21:33 -07:00
#region Ghost Deletion
2021-03-31 14:17:22 -07:00
private void OnMindRemovedMessage ( EntityUid uid , GhostComponent component , MindRemovedMessage args )
{
2021-04-18 01:54:36 -07:00
DeleteEntity ( uid ) ;
2021-03-31 14:17:22 -07:00
}
private void OnMindUnvisitedMessage ( EntityUid uid , GhostComponent component , MindUnvisitedMessage args )
{
2021-04-18 01:54:36 -07:00
DeleteEntity ( uid ) ;
2021-03-31 14:17:22 -07:00
}
2022-10-27 07:09:35 -07:00
private void OnPlayerDetached ( EntityUid uid , GhostComponent component , PlayerDetachedEvent args )
{
2023-06-18 11:33:19 -07:00
DeleteEntity ( uid ) ;
2022-10-27 07:09:35 -07:00
}
2023-09-15 22:21:33 -07:00
private void DeleteEntity ( EntityUid uid )
2021-06-18 09:56:23 +02:00
{
2023-09-15 22:21:33 -07:00
if ( Deleted ( uid ) | | Terminating ( uid ) )
2021-06-18 09:56:23 +02:00
return ;
2023-09-15 22:21:33 -07:00
QueueDel ( uid ) ;
2021-06-18 09:56:23 +02:00
}
2023-09-15 22:21:33 -07:00
#endregion
2021-06-18 09:56:23 +02:00
private void OnGhostReturnToBodyRequest ( GhostReturnToBodyRequest msg , EntitySessionEventArgs args )
{
2023-09-15 22:21:33 -07:00
if ( args . SenderSession . AttachedEntity is not { Valid : true } attached
2024-05-01 13:59:35 +00:00
| | ! _ghostQuery . TryComp ( attached , out var ghost )
2023-09-15 22:21:33 -07:00
| | ! ghost . CanReturnToBody
| | ! TryComp ( attached , out ActorComponent ? actor ) )
2021-06-18 09:56:23 +02:00
{
2023-09-11 09:42:41 +10:00
Log . Warning ( $"User {args.SenderSession.Name} sent an invalid {nameof(GhostReturnToBodyRequest)}" ) ;
2021-06-18 09:56:23 +02:00
return ;
}
2024-09-29 03:25:21 +03:00
_mind . UnVisit ( actor . PlayerSession ) ;
2021-06-18 09:56:23 +02:00
}
2023-09-15 22:21:33 -07:00
#region Warp
private void OnGhostWarpsRequest ( GhostWarpsRequestEvent msg , EntitySessionEventArgs args )
{
if ( args . SenderSession . AttachedEntity is not { Valid : true } entity
2024-05-01 13:59:35 +00:00
| | ! _ghostQuery . HasComp ( entity ) )
2023-09-15 22:21:33 -07:00
{
Log . Warning ( $"User {args.SenderSession.Name} sent a {nameof(GhostWarpsRequestEvent)} without being a ghost." ) ;
return ;
}
var response = new GhostWarpsResponseEvent ( GetPlayerWarps ( entity ) . Concat ( GetLocationWarps ( ) ) . ToList ( ) ) ;
2024-01-22 23:14:13 +01:00
RaiseNetworkEvent ( response , args . SenderSession . Channel ) ;
2023-09-15 22:21:33 -07:00
}
2021-06-18 09:56:23 +02:00
private void OnGhostWarpToTargetRequest ( GhostWarpToTargetRequestEvent msg , EntitySessionEventArgs args )
{
2023-09-15 22:21:33 -07:00
if ( args . SenderSession . AttachedEntity is not { Valid : true } attached
2024-05-01 13:59:35 +00:00
| | ! _ghostQuery . HasComp ( attached ) )
2021-06-18 09:56:23 +02:00
{
2023-09-11 09:42:41 +10:00
Log . Warning ( $"User {args.SenderSession.Name} tried to warp to {msg.Target} without being a ghost." ) ;
2021-06-18 09:56:23 +02:00
return ;
}
2023-09-11 09:42:41 +10:00
var target = GetEntity ( msg . Target ) ;
2023-09-15 22:21:33 -07:00
if ( ! Exists ( target ) )
2021-06-18 09:56:23 +02:00
{
2023-09-11 09:42:41 +10:00
Log . Warning ( $"User {args.SenderSession.Name} tried to warp to an invalid entity id: {msg.Target}" ) ;
2021-06-18 09:56:23 +02:00
return ;
}
2024-05-01 13:59:35 +00:00
WarpTo ( attached , target ) ;
}
private void OnGhostnadoRequest ( GhostnadoRequestEvent msg , EntitySessionEventArgs args )
{
if ( args . SenderSession . AttachedEntity is not { } uid
| | ! _ghostQuery . HasComp ( uid ) )
{
Log . Warning ( $"User {args.SenderSession.Name} tried to ghostnado without being a ghost." ) ;
return ;
}
2024-05-20 10:12:05 -04:00
if ( _followerSystem . GetMostGhostFollowed ( ) is not { } target )
2024-05-01 13:59:35 +00:00
return ;
WarpTo ( uid , target ) ;
}
private void WarpTo ( EntityUid uid , EntityUid target )
{
2023-09-15 22:21:33 -07:00
if ( ( TryComp ( target , out WarpPointComponent ? warp ) & & warp . Follow ) | | HasComp < MobStateComponent > ( target ) )
2022-09-10 14:47:17 -07:00
{
2024-05-01 13:59:35 +00:00
_followerSystem . StartFollowingEntity ( uid , target ) ;
2023-09-15 22:21:33 -07:00
return ;
2022-09-10 14:47:17 -07:00
}
2022-09-14 17:26:26 +10:00
2024-05-01 13:59:35 +00:00
var xform = Transform ( uid ) ;
_transformSystem . SetCoordinates ( uid , xform , Transform ( target ) . Coordinates ) ;
_transformSystem . AttachToGridOrMap ( uid , xform ) ;
if ( _physicsQuery . TryComp ( uid , out var physics ) )
_physics . SetLinearVelocity ( uid , Vector2 . Zero , body : physics ) ;
2021-06-18 09:56:23 +02:00
}
2022-09-10 14:47:17 -07:00
private IEnumerable < GhostWarp > GetLocationWarps ( )
2021-06-18 09:56:23 +02:00
{
2023-09-11 09:42:41 +10:00
var allQuery = AllEntityQuery < WarpPointComponent > ( ) ;
while ( allQuery . MoveNext ( out var uid , out var warp ) )
2021-06-18 09:56:23 +02:00
{
2023-10-16 16:39:39 +11:00
yield return new GhostWarp ( GetNetEntity ( uid ) , warp . Location ? ? Name ( uid ) , true ) ;
2021-06-18 09:56:23 +02:00
}
}
2022-09-10 14:47:17 -07:00
private IEnumerable < GhostWarp > GetPlayerWarps ( EntityUid except )
2021-06-18 09:56:23 +02:00
{
2021-11-22 23:11:48 -08:00
foreach ( var player in _playerManager . Sessions )
2021-06-18 09:56:23 +02:00
{
2023-09-15 22:21:33 -07:00
if ( player . AttachedEntity is not { Valid : true } attached )
continue ;
2022-09-14 17:26:26 +10:00
2023-09-15 22:21:33 -07:00
if ( attached = = except ) continue ;
2022-04-15 18:41:27 -03:00
2023-09-15 22:21:33 -07:00
TryComp < MindContainerComponent > ( attached , out var mind ) ;
2022-04-15 18:41:27 -03:00
2023-09-15 22:21:33 -07:00
var jobName = _jobs . MindTryGetJobName ( mind ? . Mind ) ;
var playerInfo = $"{Comp<MetaDataComponent>(attached).EntityName} ({jobName})" ;
if ( _mobState . IsAlive ( attached ) | | _mobState . IsCritical ( attached ) )
yield return new GhostWarp ( GetNetEntity ( attached ) , playerInfo , false ) ;
2021-06-18 09:56:23 +02:00
}
}
2022-06-24 16:26:56 -03:00
2023-09-15 22:21:33 -07:00
#endregion
2023-02-11 20:12:29 -05:00
private void OnEntityStorageInsertAttempt ( EntityUid uid , GhostComponent comp , ref InsertIntoEntityStorageAttemptEvent args )
2022-06-24 16:26:56 -03:00
{
2023-02-11 20:12:29 -05:00
args . Cancelled = true ;
2022-06-24 16:26:56 -03:00
}
2022-08-13 09:49:41 -04:00
Magic Refactor + Wizard Grimoire (#22568)
* Brings over changes from the original magic refactor PR
* Adds Master Spellbook, spellbook categories, WizCoin currency, and locale
* Wiz€oin™
* Adds currency whitelist to Spellbook preset, grants contained actions on action added.
* Adds grant contained action and remove provided action.
* adds a way for actions to be upgraded to the store
* Adds Fireball 3 and fixes action upgrade logic so that it checks if the action can level or if the action can upgrade separately
* Fixes upgrade logic in ActionUpgradeSystem to allow for level ups without an actual upgrade. Fixed action upgrade logic in store system as well
* Removes current action entity from the bought entities list and adds new or old action entity
* Removes Current Entity
* Removes old comments, fixes TransferAllActionsWithNewAttached
* Removes TODO
* Removes Product Action Upgrade Event
* reverts changes to immovablerodrule
* Removes stale event reference
* fixes mind action grant logic
* reverts shared gun system change to projectile anomaly system
* forgor to remove the using
* Reverts unintended changes to action container
* Adds refund button to the store
* Refreshes store back to origin.
* Refund with correct currency
* Init refund
* Check for terminating and update interface
* Disables refund button
* Removes preset allow refund
* dont refund if map changed
* adds refunds to stores
* Adds method to check for starting map
* comments, datafields, some requested changes
* turns event into ref event
* Adds datafields
* Switches to entity terminating event
* Changes store entity to be nullable and checks if store is terminating to remove reference.
* Tryadd instead of containskey
* Adds a refund disable method, disables refund on bought ent container changes if not an action
* Removes duplicate refundcomp
* Removes unintended merges
* Removed another unintended change from merge
* removes extra using statement
* readds using statement
* might as well just remove both usings since it won't leave the PR
* Fixes Action upgrades from stores
* Changes to non obsolete method uses
* Shares spawn code between instant and world
* Adds action entity to action event, adds beforecastspellevent, adds spell requirements to magic component
* puts prereq check in spell methods, sets up template code for before cast event
* checks for required wizard clothes
* Networks Magic Comp and Wizard Clothes Comp. Renames MagicSpawnData to MagicInstantSpawnData.
* Removes posdata from projectiles
* Speech > RequiresSpeech
* Fixes ActionOnInteract
* checks for muted
* popup for missing reqs
* Validate click loc for blink spell
* Checks if doors are in range and not obstructed before opening
* Check ents by map coords
* Adds speak event
* Comments spellbooks
* Removes comments
* Unobsoletes smite spell
* Invert if
* Requirements loc
* Fixes spell reqs
* Inverts an if
* Comment updates
* Starts doafter work
* Removes doafter references
* Balances fireball upgrades to be more reasonable
* Enables refund on master spellbooks
* Spells to do
* update spellbook doafter
* knock toggles bolts
* Touch Spell comments
* Comments for pending spells
* more comments
* adds spider polymorph to spellbook
* TODOs for spells
* reorganizes spellbook categories and adds wands
* fixes spacing and adds limited conditions
* commented owner only for future store PR
* reenables owner only for the grimoire
* fixes grimoire sprite
* Adds wizard rod polymorph
* summon ghosts event
* Moves rod form to offensive category
* Adds charge spell and loc for rod polymorph
* Oops forgor the actual chages
* Item Recall comment
* Fixes UI
* removes extra field for wizard rod
* Cleanup
* New Condition (INCOMPLETE)
* Fix linter
* Fix linter (for real)
* fixed some descriptions
* adds regions to magic
* Adds a non-refund wizard grimoire, fixes blink to deselect after teleporting, reduces force wall despawn time to 12 seconds
* removes limited upgrade condition
---------
Co-authored-by: AJCM <AJCM@tutanota.com>
2024-05-11 19:06:49 -04:00
private void OnToggleGhostVisibilityToAll ( ToggleGhostVisibilityToAllEvent ev )
{
if ( ev . Handled )
return ;
ev . Handled = true ;
MakeVisible ( true ) ;
}
2022-10-05 22:55:11 -04:00
/// <summary>
/// When the round ends, make all players able to see ghosts.
/// </summary>
public void MakeVisible ( bool visible )
{
2023-09-15 22:21:33 -07:00
var entityQuery = EntityQueryEnumerator < GhostComponent , VisibilityComponent > ( ) ;
while ( entityQuery . MoveNext ( out var uid , out _ , out var vis ) )
2022-10-05 22:55:11 -04:00
{
if ( visible )
{
2024-06-05 00:04:31 -07:00
_visibilitySystem . AddLayer ( ( uid , vis ) , ( int ) VisibilityFlags . Normal , false ) ;
_visibilitySystem . RemoveLayer ( ( uid , vis ) , ( int ) VisibilityFlags . Ghost , false ) ;
2022-10-05 22:55:11 -04:00
}
else
{
2024-06-05 00:04:31 -07:00
_visibilitySystem . AddLayer ( ( uid , vis ) , ( int ) VisibilityFlags . Ghost , false ) ;
_visibilitySystem . RemoveLayer ( ( uid , vis ) , ( int ) VisibilityFlags . Normal , false ) ;
2022-10-05 22:55:11 -04:00
}
2023-09-15 22:21:33 -07:00
_visibilitySystem . RefreshVisibility ( uid , visibilityComponent : vis ) ;
2022-10-05 22:55:11 -04:00
}
}
2022-08-13 09:49:41 -04:00
public bool DoGhostBooEvent ( EntityUid target )
{
var ghostBoo = new GhostBooEvent ( ) ;
RaiseLocalEvent ( target , ghostBoo , true ) ;
return ghostBoo . Handled ;
}
2024-05-11 08:03:40 -07:00
public EntityUid ? SpawnGhost ( Entity < MindComponent ? > mind , EntityUid targetEntity ,
bool canReturn = false )
{
_transformSystem . TryGetMapOrGridCoordinates ( targetEntity , out var spawnPosition ) ;
return SpawnGhost ( mind , spawnPosition , canReturn ) ;
}
2024-05-20 09:52:49 -04:00
private bool IsValidSpawnPosition ( EntityCoordinates ? spawnPosition )
{
if ( spawnPosition ? . IsValid ( EntityManager ) ! = true )
return false ;
var mapUid = spawnPosition ? . GetMapUid ( EntityManager ) ;
var gridUid = spawnPosition ? . EntityId ;
// Test if the map is being deleted
if ( mapUid = = null | | TerminatingOrDeleted ( mapUid . Value ) )
return false ;
// Test if the grid is being deleted
if ( gridUid ! = null & & TerminatingOrDeleted ( gridUid . Value ) )
return false ;
return true ;
}
2024-05-11 08:03:40 -07:00
public EntityUid ? SpawnGhost ( Entity < MindComponent ? > mind , EntityCoordinates ? spawnPosition = null ,
bool canReturn = false )
{
if ( ! Resolve ( mind , ref mind . Comp ) )
return null ;
2024-05-20 09:52:49 -04:00
// Test if the map or grid is being deleted
if ( ! IsValidSpawnPosition ( spawnPosition ) )
2024-05-11 08:03:40 -07:00
spawnPosition = null ;
2024-05-20 09:52:49 -04:00
// If it's bad, look for a valid point to spawn
2024-09-29 03:25:21 +03:00
spawnPosition ? ? = _gameTicker . GetObserverSpawnPoint ( ) ;
2024-05-11 08:03:40 -07:00
2024-05-20 09:52:49 -04:00
// Make sure the new point is valid too
if ( ! IsValidSpawnPosition ( spawnPosition ) )
2024-05-11 08:03:40 -07:00
{
Log . Warning ( $"No spawn valid ghost spawn position found for {mind.Comp.CharacterName}"
2024-05-20 09:52:49 -04:00
+ $" \" { ToPrettyString ( mind ) } \ "" ) ;
2024-05-11 08:03:40 -07:00
_minds . TransferTo ( mind . Owner , null , createGhost : false , mind : mind . Comp ) ;
return null ;
}
var ghost = SpawnAtPosition ( GameTicker . ObserverPrototypeName , spawnPosition . Value ) ;
var ghostComponent = Comp < GhostComponent > ( ghost ) ;
// Try setting the ghost entity name to either the character name or the player name.
// If all else fails, it'll default to the default entity prototype name, "observer".
// However, that should rarely happen.
if ( ! string . IsNullOrWhiteSpace ( mind . Comp . CharacterName ) )
_metaData . SetEntityName ( ghost , mind . Comp . CharacterName ) ;
else if ( ! string . IsNullOrWhiteSpace ( mind . Comp . Session ? . Name ) )
_metaData . SetEntityName ( ghost , mind . Comp . Session . Name ) ;
if ( mind . Comp . TimeOfDeath . HasValue )
{
SetTimeOfDeath ( ghost , mind . Comp . TimeOfDeath ! . Value , ghostComponent ) ;
}
SetCanReturnToBody ( ghostComponent , canReturn ) ;
if ( canReturn )
_minds . Visit ( mind . Owner , ghost , mind . Comp ) ;
else
_minds . TransferTo ( mind . Owner , ghost , mind : mind . Comp ) ;
Log . Debug ( $"Spawned ghost \" { ToPrettyString ( ghost ) } \ " for {mind.Comp.CharacterName}." ) ;
return ghost ;
}
2024-08-26 15:15:33 +03:00
public bool OnGhostAttempt ( EntityUid mindId , bool canReturnGlobal , bool viaCommand = false , MindComponent ? mind = null )
{
if ( ! Resolve ( mindId , ref mind ) )
return false ;
var playerEntity = mind . CurrentEntity ;
if ( playerEntity ! = null & & viaCommand )
_adminLogger . Add ( LogType . Mind , $"{EntityManager.ToPrettyString(playerEntity.Value):player} is attempting to ghost via command" ) ;
var handleEv = new GhostAttemptHandleEvent ( mind , canReturnGlobal ) ;
RaiseLocalEvent ( handleEv ) ;
// Something else has handled the ghost attempt for us! We return its result.
if ( handleEv . Handled )
return handleEv . Result ;
if ( mind . PreventGhosting )
{
if ( mind . Session ! = null ) // Logging is suppressed to prevent spam from ghost attempts caused by movement attempts
{
_chatManager . DispatchServerMessage ( mind . Session , Loc . GetString ( "comp-mind-ghosting-prevented" ) ,
true ) ;
}
return false ;
}
if ( TryComp < GhostComponent > ( playerEntity , out var comp ) & & ! comp . CanGhostInteract )
return false ;
if ( mind . VisitingEntity ! = default )
{
_mind . UnVisit ( mindId , mind : mind ) ;
}
var position = Exists ( playerEntity )
? Transform ( playerEntity . Value ) . Coordinates
: _gameTicker . GetObserverSpawnPoint ( ) ;
if ( position = = default )
return false ;
// Ok, so, this is the master place for the logic for if ghosting is "too cheaty" to allow returning.
// There's no reason at this time to move it to any other place, especially given that the 'side effects required' situations would also have to be moved.
// + If CharacterDeadPhysically applies, we're physically dead. Therefore, ghosting OK, and we can return (this is critical for gibbing)
// Note that we could theoretically be ICly dead and still physically alive and vice versa.
// (For example, a zombie could be dead ICly, but may retain memories and is definitely physically active)
// + If we're in a mob that is critical, and we're supposed to be able to return if possible,
// we're succumbing - the mob is killed. Therefore, character is dead. Ghosting OK.
// (If the mob survives, that's a bug. Ghosting is kept regardless.)
var canReturn = canReturnGlobal & & _mind . IsCharacterDeadPhysically ( mind ) ;
if ( _configurationManager . GetCVar ( CCVars . GhostKillCrit ) & &
canReturnGlobal & &
TryComp ( playerEntity , out MobStateComponent ? mobState ) )
{
if ( _mobState . IsCritical ( playerEntity . Value , mobState ) )
{
canReturn = true ;
//todo: what if they dont breathe lol
//cry deeply
FixedPoint2 dealtDamage = 200 ;
if ( TryComp < DamageableComponent > ( playerEntity , out var damageable )
& & TryComp < MobThresholdsComponent > ( playerEntity , out var thresholds ) )
{
var playerDeadThreshold = _mobThresholdSystem . GetThresholdForState ( playerEntity . Value , MobState . Dead , thresholds ) ;
dealtDamage = playerDeadThreshold - damageable . TotalDamage ;
}
DamageSpecifier damage = new ( _prototypeManager . Index < DamageTypePrototype > ( "Asphyxiation" ) , dealtDamage ) ;
_damageable . TryChangeDamage ( playerEntity , damage , true ) ;
}
}
2024-08-31 08:26:52 +00:00
if ( playerEntity ! = null )
_adminLogger . Add ( LogType . Mind , $"{EntityManager.ToPrettyString(playerEntity.Value):player} ghosted{(!canReturn ? " ( non - returnable ) " : " ")}" ) ;
2024-08-26 15:15:33 +03:00
var ghost = SpawnGhost ( ( mindId , mind ) , position , canReturn ) ;
if ( ghost = = null )
return false ;
return true ;
}
}
public sealed class GhostAttemptHandleEvent ( MindComponent mind , bool canReturnGlobal ) : HandledEntityEventArgs
{
public MindComponent Mind { get ; } = mind ;
public bool CanReturnGlobal { get ; } = canReturnGlobal ;
public bool Result { get ; set ; }
2021-03-31 14:17:22 -07:00
}
}