Skip to content
Merged
Show file tree
Hide file tree
Changes from 5 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
28 changes: 26 additions & 2 deletions Content.Client/Changeling/Systems/ChangelingIdentitySystem.cs
Original file line number Diff line number Diff line change
@@ -1,6 +1,8 @@
using System.Linq;
using Content.Shared.Changeling.Components;
using Content.Shared.Changeling.Systems;
using Robust.Client.GameObjects;
using Robust.Shared.GameStates;

namespace Content.Client.Changeling.Systems;

Expand All @@ -12,11 +14,33 @@ public override void Initialize()
{
base.Initialize();

SubscribeLocalEvent<ChangelingIdentityComponent, AfterAutoHandleStateEvent>(OnAfterAutoHandleState);
SubscribeLocalEvent<ChangelingIdentityComponent, ComponentHandleState>(OnHandleState);
}

private void OnAfterAutoHandleState(Entity<ChangelingIdentityComponent> ent, ref AfterAutoHandleStateEvent args)
private void OnHandleState(Entity<ChangelingIdentityComponent> ent, ref ComponentHandleState args)
{
if (args.Current is not ChangelingIdentityComponentState state)
return;

ent.Comp.ConsumedIdentities = new List<ChangelingIdentityData>();

foreach (var identities in state.ConsumedIdentities)
Comment thread
ScarKy0 marked this conversation as resolved.
Outdated
{
ChangelingIdentityData data = new();

data.Identity = GetEntity(identities.Identity);
Comment thread
ScarKy0 marked this conversation as resolved.
Outdated
data.Original = GetEntity(identities.Original);
data.OriginalMind = null; // Don't network the mind!
data.OriginalJob = identities.OriginalJob;
data.Starting = identities.Starting;
Comment thread
ScarKy0 marked this conversation as resolved.
Outdated

ent.Comp.ConsumedIdentities.Add(data);
}

ent.Comp.CurrentIdentity = GetEntity(state.CurrentIdentity);

ent.Comp.IdentityCloningSettings = state.IdentityCloningSettings;

UpdateUi(ent);
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -34,13 +34,13 @@ public override void Update()
if (!EntMan.TryGetComponent<ChangelingIdentityComponent>(Owner, out var lingIdentity))
return;

var models = ConvertToButtons(lingIdentity.ConsumedIdentities.Keys, lingIdentity?.CurrentIdentity);
var models = ConvertToButtons(lingIdentity.ConsumedIdentities, lingIdentity.CurrentIdentity);

_menu.SetButtons(models);
}

private IEnumerable<RadialMenuOptionBase> ConvertToButtons(
IEnumerable<EntityUid> identities,
IEnumerable<ChangelingIdentityData> identities,
EntityUid? currentIdentity
)
{
Expand All @@ -49,25 +49,28 @@ private IEnumerable<RadialMenuOptionBase> ConvertToButtons(

foreach (var identity in identities)
{
if (identity.Identity == null)
continue;

// Options for selecting identities.
var option = new RadialMenuActionOption<NetEntity>(SendIdentitySelect, EntMan.GetNetEntity(identity))
var option = new RadialMenuActionOption<NetEntity>(SendIdentitySelect, EntMan.GetNetEntity(identity.Identity.Value))
{
IconSpecifier = RadialMenuIconSpecifier.With(identity),
ToolTip = Loc.GetString("changeling-transform-bui-select-entity", ("entity", identity)),
BackgroundColor = (currentIdentity == identity) ? SelectedOptionBackground : null, // mark as selected
HoverBackgroundColor = (currentIdentity == identity) ? SelectedOptionHoverBackground : null
IconSpecifier = RadialMenuIconSpecifier.With(identity.Identity.Value),
ToolTip = Loc.GetString("changeling-transform-bui-select-entity", ("entity", identity.Identity)),
BackgroundColor = (currentIdentity == identity.Identity) ? SelectedOptionBackground : null, // mark as selected
HoverBackgroundColor = (currentIdentity == identity.Identity) ? SelectedOptionHoverBackground : null
};
buttons.Add(option);

// Options for dropping identities.
var dropOption = new RadialMenuActionOption<NetEntity>(SendIdentityDrop, EntMan.GetNetEntity(identity))
var dropOption = new RadialMenuActionOption<NetEntity>(SendIdentityDrop, EntMan.GetNetEntity(identity.Identity.Value))
{
IconSpecifier = RadialMenuIconSpecifier.With(identity),
ToolTip = (currentIdentity == identity)
IconSpecifier = RadialMenuIconSpecifier.With(identity.Identity.Value),
ToolTip = (currentIdentity == identity.Identity)
? Loc.GetString("changeling-transform-bui-drop-identity-cannot-drop")
: Loc.GetString("changeling-transform-bui-drop-identity-entity", ("entity", identity)),
BackgroundColor = (currentIdentity == identity) ? DisabledOptionBackground : null, // cannot drop your current identity
HoverBackgroundColor = (currentIdentity == identity) ? DisabledOptionHoverBackground : null
: Loc.GetString("changeling-transform-bui-drop-identity-entity", ("entity", identity.Identity)),
BackgroundColor = (currentIdentity == identity.Identity) ? DisabledOptionBackground : null, // cannot drop your current identity
HoverBackgroundColor = (currentIdentity == identity.Identity) ? DisabledOptionHoverBackground : null
};
dropButtons.Add(dropOption);
}
Expand Down
35 changes: 34 additions & 1 deletion Content.Server/Changeling/Systems/ChangelingIdentitySystem.cs
Original file line number Diff line number Diff line change
@@ -1,5 +1,38 @@
using Content.Shared.Changeling.Components;
using Content.Shared.Changeling.Systems;
using Robust.Shared.GameStates;

namespace Content.Server.Changeling.Systems;

public sealed class ChangelingIdentitySystem : SharedChangelingIdentitySystem;
public sealed class ChangelingIdentitySystem : SharedChangelingIdentitySystem
{
public override void Initialize()
{
base.Initialize();

SubscribeLocalEvent<ChangelingIdentityComponent, ComponentGetState>(OnGetState);
}

private void OnGetState(Entity<ChangelingIdentityComponent> entity, ref ComponentGetState args)
{
List<ChangelingNetworkedIdentityData> sentIdentities = new();

foreach (var identity in entity.Comp.ConsumedIdentities)
{
ChangelingNetworkedIdentityData netData = new();

netData.Identity = GetNetEntity(identity.Identity);
netData.Original = GetNetEntity(identity.Original);
netData.Starting = identity.Starting;
netData.OriginalJob = identity.OriginalJob;
Comment thread
ScarKy0 marked this conversation as resolved.
Outdated

sentIdentities.Add(netData);
}

var current = entity.Comp.CurrentIdentity;

var netCurrent = GetNetEntity(current);

args.State = new ChangelingIdentityComponentState(sentIdentities, netCurrent, entity.Comp.IdentityCloningSettings);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
using Content.Server.Objectives.Systems;

namespace Content.Server.Objectives.Components;

/// <summary>
/// Requires that a changeling has obtained X unique identities.
/// Depends on <see cref="NumberObjectiveComponent"/> to function.
/// </summary>
[RegisterComponent, Access(typeof(ChangelingObjectiveSystem))]
public sealed partial class ChangelingUniqueIdentityConditionComponent : Component
{
/// <summary>
/// Whether the target must be dead
Comment thread
ScarKy0 marked this conversation as resolved.
Outdated
/// </summary>
[DataField, ViewVariables(VVAccess.ReadWrite)]
Comment thread
ScarKy0 marked this conversation as resolved.
Outdated
public int UniqueIdentities;
}
48 changes: 48 additions & 0 deletions Content.Server/Objectives/Systems/ChangelingObjectiveSystem.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,48 @@
using Content.Server.Objectives.Components;
using Content.Shared.Changeling.Components;
using Content.Shared.Mind;
using Content.Shared.Objectives.Components;

namespace Content.Server.Objectives.Systems;

public sealed class ChangelingObjectiveSystem : EntitySystem
{
[Dependency] private readonly NumberObjectiveSystem _number = default!;
[Dependency] private readonly SharedMindSystem _mind = default!;

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

SubscribeLocalEvent<ChangelingUniqueIdentityConditionComponent, ObjectiveGetProgressEvent>(OnGetProgress);
SubscribeLocalEvent<ChangelingDevouredEntityEvent>(OnChangelingDevoured);
}

private void OnChangelingDevoured(ref ChangelingDevouredEntityEvent args)
{
if (!args.Unique)
return;

if (!_mind.TryGetObjectiveComp<ChangelingUniqueIdentityConditionComponent>(args.Changeling, out var obj))
return;

obj.UniqueIdentities++;
}

private void OnGetProgress(Entity<ChangelingUniqueIdentityConditionComponent> ent, ref ObjectiveGetProgressEvent args)
{
args.Progress = GetProgress(ent.Comp, _number.GetTarget(ent));
}

private float GetProgress(ChangelingUniqueIdentityConditionComponent comp, int target)
{
// prevent divide-by-zero
if (target == 0)
return 1f;

if (comp.UniqueIdentities >= target)
return 1f;

return (float) comp.UniqueIdentities / (float) target;
Comment thread
ScarKy0 marked this conversation as resolved.
Outdated
}
}
10 changes: 10 additions & 0 deletions Content.Shared/Changeling/Components/ChangelingDevourComponent.cs
Original file line number Diff line number Diff line change
Expand Up @@ -127,3 +127,13 @@ public sealed partial class ChangelingDevourComponent : Component

public override bool SendOnlyToOwner => true;
}

/// <summary>
/// Event raised on the changeling and broadcast when a given changeling devours an entity.
/// </summary>
/// <param name="Changeling">The changeling devouring this entity.</param>
/// <param name="Devoured">The entity that was devoured.</param>
/// <param name="ObtainedIdentity">Whether the changeling is going to be given the target's identity after devouring.</param>
/// <param name="Unique">Whether this entity was eaten by the changeling before.</param>
[ByRefEvent]
public record struct ChangelingDevouredEntityEvent(EntityUid Changeling, EntityUid Devoured, bool ObtainedIdentity, bool Unique);
Comment thread
ScarKy0 marked this conversation as resolved.
Outdated
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,13 @@ namespace Content.Shared.Changeling.Components;
[RegisterComponent, NetworkedComponent, AutoGenerateComponentState]
public sealed partial class ChangelingDevouredComponent : Component
{
/// <summary>
/// Whether this entity has been devoured recently.
/// Gets set back to False when the entity with this component becomes <see cref="MobState.Alive"/> again.
/// </summary>
[DataField, AutoNetworkedField]
public bool Recent;

/// <summary>
/// HashSet of all changelings that have devoured this entity.
/// </summary>
Expand Down
Original file line number Diff line number Diff line change
@@ -1,29 +1,33 @@
using Content.Shared.Cloning;
using Content.Shared.Roles;
using Robust.Shared.GameStates;
using Robust.Shared.Prototypes;
using Robust.Shared.Serialization;

namespace Content.Shared.Changeling.Components;

/// <summary>
/// The storage component for Changelings, it handles the link between a changeling and its consumed identities
/// that exist on a paused map.
/// </summary>
[RegisterComponent, NetworkedComponent, AutoGenerateComponentState(raiseAfterAutoHandleState: true)]
[RegisterComponent, NetworkedComponent]
public sealed partial class ChangelingIdentityComponent : Component
{
/// <summary>
/// The list of entities that exist on a paused map. They are paused clones of the victims that the ling has consumed, with all relevant components copied from the original.
/// The key is the EntityUid of the stored identity, the value is the original entity the identity came from.
/// The value will be set to null if that entity is deleted.
/// List containing data regarding all devoured identities.
/// The identities are paused clones of the victims that the ling has consumed, with all relevant components copied from the original.
/// </summary>
// TODO: This should be handled via a relation system in the future.
[DataField, AutoNetworkedField]
public Dictionary<EntityUid, EntityUid?> ConsumedIdentities = new();
/// <remarks>
/// Entries in this list do not get deleted for keeping track of total and unique identities.
/// To check if an identity is valid compare <see cref="ChangelingIdentityData.Identity"/> to null.
/// </remarks>
[DataField]
public List<ChangelingIdentityData> ConsumedIdentities = new();

/// <summary>
/// The currently assumed identity.
/// </summary>
[DataField, AutoNetworkedField]
[DataField]
public EntityUid? CurrentIdentity;

/// <summary>
Expand All @@ -35,3 +39,73 @@ public sealed partial class ChangelingIdentityComponent : Component

public override bool SendOnlyToOwner => true;
}

[Serializable, NetSerializable]
public sealed class ChangelingIdentityComponentState : ComponentState
{
public List<ChangelingNetworkedIdentityData> ConsumedIdentities;
public NetEntity? CurrentIdentity;

public ProtoId<CloningSettingsPrototype> IdentityCloningSettings;

public ChangelingIdentityComponentState(List<ChangelingNetworkedIdentityData> consumedIdentities,
NetEntity? currentIdentity,
ProtoId<CloningSettingsPrototype> identityCloningSettings)
{
ConsumedIdentities = consumedIdentities;
CurrentIdentity = currentIdentity;
IdentityCloningSettings = identityCloningSettings;
}
}

[DataDefinition]
public sealed partial class ChangelingIdentityData
Comment thread
ScarKy0 marked this conversation as resolved.
{
/// <summary>
/// The stored identity used for cloning appearance and components.
/// Set to null if the identity is ever deleted.
/// </summary>
[DataField]
public EntityUid? Identity;

/// <summary>
/// The original entity that was devoured to obtain this identity.
Comment thread
ScarKy0 marked this conversation as resolved.
/// </summary>
[DataField]
public EntityUid? Original;

/// <summary>
/// The mind of the original entity that was devoured to obtain this identity.
/// Always null on Client.
/// </summary>
[DataField]
public EntityUid? OriginalMind;

/// <summary>
/// Job prototype of the original entity at the time of devouring.
/// </summary>
[DataField]
public ProtoId<JobPrototype>? OriginalJob;

/// <summary>
/// Whether this is the identity the entity started with.
/// </summary>
[DataField]
public bool Starting = false;
}

[Serializable, NetSerializable]
public sealed partial class ChangelingNetworkedIdentityData
Comment thread
ScarKy0 marked this conversation as resolved.
{
[DataField]
public NetEntity? Identity;

[DataField]
public NetEntity? Original;

[DataField]
public ProtoId<JobPrototype>? OriginalJob;

[DataField]
public bool Starting;
}
Loading
Loading