Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

cham projector fixes/rewrite #27111

Merged
merged 31 commits into from
Nov 8, 2024
Merged
Show file tree
Hide file tree
Changes from 10 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
22 changes: 22 additions & 0 deletions Content.Client/Polymorph/Systems/ChameleonProjectorSystem.cs
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
using Content.Client.Smoking;
using Content.Shared.Chemistry.Components;
using Content.Shared.Polymorph.Components;
using Content.Shared.Polymorph.Systems;
Expand All @@ -10,24 +11,45 @@ public sealed class ChameleonProjectorSystem : SharedChameleonProjectorSystem
[Dependency] private readonly SharedAppearanceSystem _appearance = default!;

private EntityQuery<AppearanceComponent> _appearanceQuery;
private EntityQuery<SpriteComponent> _spriteQuery;

public override void Initialize()
{
base.Initialize();

_appearanceQuery = GetEntityQuery<AppearanceComponent>();
_spriteQuery = GetEntityQuery<SpriteComponent>();

SubscribeLocalEvent<ChameleonDisguiseComponent, AfterAutoHandleStateEvent>(OnHandleState);

SubscribeLocalEvent<ChameleonDisguisedComponent, ComponentStartup>(OnStartup);
SubscribeLocalEvent<ChameleonDisguisedComponent, ComponentShutdown>(OnShutdown);
}

private void OnHandleState(Entity<ChameleonDisguiseComponent> ent, ref AfterAutoHandleStateEvent args)
{
CopyComp<SpriteComponent>(ent);
CopyComp<GenericVisualizerComponent>(ent);
CopyComp<SolutionContainerVisualsComponent>(ent);
CopyComp<BurnStateVisualsComponent>(ent);

// reload appearance to hopefully prevent any invisible layers
if (_appearanceQuery.TryComp(ent, out var appearance))
_appearance.QueueUpdate(ent, appearance);
}

private void OnStartup(Entity<ChameleonDisguisedComponent> ent, ref ComponentStartup args)
{
if (!_spriteQuery.TryComp(ent, out var sprite))
return;

ent.Comp.WasVisible = sprite.Visible;
sprite.Visible = false;
}

private void OnShutdown(Entity<ChameleonDisguisedComponent> ent, ref ComponentShutdown args)
{
if (_spriteQuery.TryComp(ent, out var sprite))
sprite.Visible = ent.Comp.WasVisible;
}
}
96 changes: 1 addition & 95 deletions Content.Server/Polymorph/Systems/ChameleonProjectorSystem.cs
Original file line number Diff line number Diff line change
@@ -1,99 +1,5 @@
using Content.Server.Polymorph.Components;
using Content.Shared.Actions;
using Content.Shared.Construction.Components;
using Content.Shared.Hands;
using Content.Shared.Mobs;
using Content.Shared.Mobs.Components;
using Content.Shared.Mobs.Systems;
using Content.Shared.Polymorph;
using Content.Shared.Polymorph.Components;
using Content.Shared.Polymorph.Systems;
using Content.Shared.StatusIcon.Components;
using Robust.Shared.Physics.Components;

namespace Content.Server.Polymorph.Systems;

public sealed class ChameleonProjectorSystem : SharedChameleonProjectorSystem
{
[Dependency] private readonly MetaDataSystem _meta = default!;
[Dependency] private readonly MobThresholdSystem _mobThreshold = default!;
[Dependency] private readonly PolymorphSystem _polymorph = default!;
[Dependency] private readonly SharedActionsSystem _actions = default!;
[Dependency] private readonly SharedAppearanceSystem _appearance = default!;
[Dependency] private readonly SharedTransformSystem _xform = default!;

public override void Initialize()
{
base.Initialize();

SubscribeLocalEvent<ChameleonDisguiseComponent, GotEquippedHandEvent>(OnEquippedHand);
SubscribeLocalEvent<ChameleonDisguiseComponent, DisguiseToggleNoRotEvent>(OnToggleNoRot);
SubscribeLocalEvent<ChameleonDisguiseComponent, DisguiseToggleAnchoredEvent>(OnToggleAnchored);
}

private void OnEquippedHand(Entity<ChameleonDisguiseComponent> ent, ref GotEquippedHandEvent args)
{
if (!TryComp<PolymorphedEntityComponent>(ent, out var poly))
return;

_polymorph.Revert((ent, poly));
args.Handled = true;
}

public override void Disguise(ChameleonProjectorComponent proj, EntityUid user, EntityUid entity)
{
if (_polymorph.PolymorphEntity(user, proj.Polymorph) is not {} disguise)
return;

// make disguise look real (for simple things at least)
var meta = MetaData(entity);
_meta.SetEntityName(disguise, meta.EntityName);
_meta.SetEntityDescription(disguise, meta.EntityDescription);

var comp = EnsureComp<ChameleonDisguiseComponent>(disguise);
comp.SourceEntity = entity;
comp.SourceProto = Prototype(entity)?.ID;
Dirty(disguise, comp);

// no sechud trolling
RemComp<StatusIconComponent>(disguise);

_appearance.CopyData(entity, disguise);

var mass = CompOrNull<PhysicsComponent>(entity)?.Mass ?? 0f;

// let the disguise die when its taken enough damage, which then transfers to the player
// health is proportional to mass, and capped to not be insane
if (TryComp<MobThresholdsComponent>(disguise, out var thresholds))
{
// if the player is of flesh and blood, cap max health to theirs
// so that when reverting damage scales 1:1 and not round removing
var playerMax = _mobThreshold.GetThresholdForState(user, MobState.Dead).Float();
var max = playerMax == 0f ? proj.MaxHealth : Math.Max(proj.MaxHealth, playerMax);

var health = Math.Clamp(mass, proj.MinHealth, proj.MaxHealth);
_mobThreshold.SetMobStateThreshold(disguise, health, MobState.Critical, thresholds);
_mobThreshold.SetMobStateThreshold(disguise, max, MobState.Dead, thresholds);
}

// add actions for controlling transform aspects
_actions.AddAction(disguise, proj.NoRotAction);
_actions.AddAction(disguise, proj.AnchorAction);
}

private void OnToggleNoRot(Entity<ChameleonDisguiseComponent> ent, ref DisguiseToggleNoRotEvent args)
{
var xform = Transform(ent);
xform.NoLocalRotation = !xform.NoLocalRotation;
}

private void OnToggleAnchored(Entity<ChameleonDisguiseComponent> ent, ref DisguiseToggleAnchoredEvent args)
{
var uid = ent.Owner;
var xform = Transform(uid);
if (xform.Anchored)
_xform.Unanchor(uid, xform);
else
_xform.AnchorEntity((uid, xform));
}
}
public sealed class ChameleonProjectorSystem : SharedChameleonProjectorSystem;
Original file line number Diff line number Diff line change
Expand Up @@ -45,10 +45,14 @@ public bool SetEnabled(EntityUid uid, EntityUid user, bool enabled, StealthCloth
if (MetaData(user).EntityLifeStage >= EntityLifeStage.Terminating)
return false;

// can't enable stealth if something else already enabled it, and vice versa
var stealth = EnsureComp<StealthComponent>(user);
if (stealth.Enabled == enabled)
return false;

comp.Enabled = enabled;
Dirty(uid, comp);

var stealth = EnsureComp<StealthComponent>(user);
// slightly visible, but doesn't change when moving so it's ok
var visibility = enabled ? stealth.MinVisibility + comp.Visibility : stealth.MaxVisibility;
_stealth.SetVisibility(user, visibility, stealth);
Expand Down
12 changes: 12 additions & 0 deletions Content.Shared/Polymorph/Components/ChameleonDisguiseComponent.cs
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,18 @@ namespace Content.Shared.Polymorph.Components;
[RegisterComponent, NetworkedComponent, AutoGenerateComponentState(true)]
public sealed partial class ChameleonDisguiseComponent : Component
{
/// <summary>
/// The user of this disguise.
/// </summary>
[DataField]
public EntityUid User;

/// <summary>
/// The projector that created this disguise.
/// </summary>
[DataField]
public EntityUid Projector;

/// <summary>
/// The disguise source entity for copying the sprite.
/// </summary>
Expand Down
30 changes: 30 additions & 0 deletions Content.Shared/Polymorph/Components/ChameleonDisguisedComponent.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
using Content.Shared.FixedPoint;
using Robust.Shared.GameStates;

namespace Content.Shared.Polymorph.Components;

/// <summary>
/// Added to a player when they use a chameleon projector.
/// Handles making them invisible and revealing when damaged enough or switching hands.
/// </summary>
[RegisterComponent, NetworkedComponent]
public sealed partial class ChameleonDisguisedComponent : Component
{
/// <summary>
/// How much damage can be taken before revealing automatically.
/// </summary>
[DataField]
public FixedPoint2 Integrity = FixedPoint2.Zero;
deltanedas marked this conversation as resolved.
Show resolved Hide resolved

/// <summary>
/// The disguise entity parented to the player.
/// </summary>
[DataField]
public EntityUid Disguise;

/// <summary>
/// For client, whether the user's sprite was previously visible or not.
/// </summary>
[DataField]
public bool WasVisible;
}
20 changes: 18 additions & 2 deletions Content.Shared/Polymorph/Components/ChameleonProjectorComponent.cs
Original file line number Diff line number Diff line change
Expand Up @@ -25,22 +25,26 @@ public sealed partial class ChameleonProjectorComponent : Component
public EntityWhitelist? Blacklist;

/// <summary>
/// Polymorph configuration for the disguise entity.
/// Disguise entity to spawn and use.
/// </summary>
[DataField(required: true)]
public PolymorphConfiguration Polymorph = new();
public EntProtoId DisguiseProto = string.Empty;

/// <summary>
/// Action for disabling your disguise's rotation.
/// </summary>
[DataField]
public EntProtoId NoRotAction = "ActionDisguiseNoRot";
[DataField]
public EntityUid? NoRotActionEntity;

/// <summary>
/// Action for anchoring your disguise in place.
/// </summary>
[DataField]
public EntProtoId AnchorAction = "ActionDisguiseAnchor";
[DataField]
public EntityUid? AnchorActionEntity;

/// <summary>
/// Minimum health to give the disguise.
Expand All @@ -54,6 +58,12 @@ public sealed partial class ChameleonProjectorComponent : Component
[DataField]
public float MaxHealth = 100f;

/// <summary>
/// Popup shown to the user when they try to disguise as an entity inside a container.
/// </summary>
[DataField]
public LocId ContainerPopup = "chameleon-projector-inside-container";
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

i think its just good practice to have the locids on the component instead of hardcoded in the system

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

As these LocIds are now in the system, they should be removed, since they are no longer referenced

deltanedas marked this conversation as resolved.
Show resolved Hide resolved

/// <summary>
/// Popup shown to the user when they try to disguise as an invalid entity.
/// </summary>
Expand All @@ -65,4 +75,10 @@ public sealed partial class ChameleonProjectorComponent : Component
/// </summary>
[DataField]
public LocId SuccessPopup = "chameleon-projector-success";

/// <summary>
/// User currently disguised by this projector, if any
/// </summary>
[DataField]
public EntityUid? Disguised;
}
Loading
Loading