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

Port Changelings From Goobstation (Funky PR 387 Included) #1855

Open
wants to merge 9 commits into
base: master
Choose a base branch
from
47 changes: 47 additions & 0 deletions Content.Client/_Goobstation/Changeling/ChangelingSystem.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,47 @@
using Content.Client.Alerts;
using Content.Client.UserInterface.Systems.Alerts.Controls;
using Content.Shared.Changeling;
using Content.Shared.StatusIcon.Components;
using Robust.Shared.Prototypes;

namespace Content.Client.Changeling;

public sealed class ChangelingSystem : SharedChangelingSystem
{

[Dependency] private readonly IPrototypeManager _prototype = default!;
public override void Initialize()
{
base.Initialize();

SubscribeLocalEvent<ChangelingComponent, UpdateAlertSpriteEvent>(OnUpdateAlert);
SubscribeLocalEvent<ChangelingComponent, GetStatusIconsEvent>(GetChanglingIcon);
}

private void OnUpdateAlert(EntityUid uid, ChangelingComponent comp, ref UpdateAlertSpriteEvent args)
{
var stateNormalized = 0f;

// hardcoded because uhh umm i don't know. send help.
switch (args.Alert.AlertKey.AlertType)
{
case "ChangelingChemicals":
stateNormalized = (int) (comp.Chemicals / comp.MaxChemicals * 18);
break;

case "ChangelingBiomass":
stateNormalized = (int) (comp.Biomass / comp.MaxBiomass * 16);
break;
default:
return;
}
Comment on lines +25 to +37
Copy link
Contributor

Choose a reason for hiding this comment

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

🧹 Nitpick (assertive)

Alert calculations could use strategic refinement, cadet.

That comment on line 25 signals potential Sol Alliance infiltration into our codebase! Hardcoding alert types without explanation compromises our tactical flexibility.

Consider implementing a mapping system between alert types and calculation methods rather than relying on switch statements. This would improve maintainability and allow for easier addition of new alert types without modifying core logic.

- // hardcoded because uhh umm i don't know. send help.
- switch (args.Alert.AlertKey.AlertType)
- {
-     case "ChangelingChemicals":
-         stateNormalized = (int) (comp.Chemicals / comp.MaxChemicals * 18);
-         break;
- 
-     case "ChangelingBiomass":
-         stateNormalized = (int) (comp.Biomass / comp.MaxBiomass * 16);
-         break;
-     default:
-         return;
- }
+ // Alert type calculation mapping
+ private readonly Dictionary<string, Func<ChangelingComponent, float>> _alertCalculations = new()
+ {
+     ["ChangelingChemicals"] = comp => comp.Chemicals / comp.MaxChemicals * 18,
+     ["ChangelingBiomass"] = comp => comp.Biomass / comp.MaxBiomass * 16
+ };
+ 
+ if (!_alertCalculations.TryGetValue(args.Alert.AlertKey.AlertType, out var calculation))
+     return;
+     
+ stateNormalized = (int)calculation(comp);

Committable suggestion skipped: line range outside the PR's diff.

var sprite = args.SpriteViewEnt.Comp;
sprite.LayerSetState(AlertVisualLayers.Base, $"{stateNormalized}");
}

private void GetChanglingIcon(Entity<ChangelingComponent> ent, ref GetStatusIconsEvent args)
{
if (HasComp<HivemindComponent>(ent) && _prototype.TryIndex(ent.Comp.StatusIcon, out var iconPrototype))
args.StatusIcons.Add(iconPrototype);
}
Comment on lines +42 to +46
Copy link
Contributor

Choose a reason for hiding this comment

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

🛠️ Refactor suggestion

Method naming could be more precise for naval communications clarity.

The name "GetChanglingIcon" contains a typo - "Changeling" is misspelled. As a naval officer of the Biesel Republic, I must insist on proper spelling in our communications protocols to prevent misidentification of hostile entities.

- private void GetChanglingIcon(Entity<ChangelingComponent> ent, ref GetStatusIconsEvent args)
+ private void GetChangelingIcon(Entity<ChangelingComponent> ent, ref GetStatusIconsEvent args)

Committable suggestion skipped: line range outside the PR's diff.

}
60 changes: 60 additions & 0 deletions Content.Server/_Goobstation/Changeling/ChangelingEggSystem.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,60 @@
using Robust.Shared.Timing;
using Content.Shared.Changeling;
using Content.Shared.Mind;
using Content.Server.Body.Systems;
using Content.Shared.Mind.Components;

namespace Content.Server.Changeling;

public sealed class ChangelingEggSystem : EntitySystem
{
[Dependency] private readonly IGameTiming _timing = default!;
[Dependency] private readonly BodySystem _bodySystem = default!;
[Dependency] private readonly SharedMindSystem _mind = default!;
[Dependency] private readonly ChangelingSystem _changeling = default!;

public override void Update(float frameTime)
{
base.Update(frameTime);

var query = EntityQueryEnumerator<ChangelingEggComponent>();
while (query.MoveNext(out var uid, out var comp))
{
if (_timing.CurTime < comp.UpdateTimer)
continue;

comp.UpdateTimer = _timing.CurTime + TimeSpan.FromSeconds(comp.UpdateCooldown);

Cycle(uid, comp);
}
}
Comment on lines +16 to +30
Copy link
Contributor

Choose a reason for hiding this comment

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

🧹 Nitpick (assertive)

Solid timed update, but watch performance.

Our Biesel Republic analysts confirm this approach is functional for a small number of changeling eggs. However, if hundreds of eggs appear, enumerating them every frame could degrade performance. Consider batch updates or distributing cycles over multiple frames if scalability becomes a concern.


public void Cycle(EntityUid uid, ChangelingEggComponent comp)
{
if (comp.active == false)
{
comp.active = true;
return;
}

if (TerminatingOrDeleted(comp.lingMind))
{
_bodySystem.GibBody(uid);
return;
}

var newUid = Spawn("MobMonkey", Transform(uid).Coordinates);

EnsureComp<MindContainerComponent>(newUid);
_mind.TransferTo(comp.lingMind, newUid);

EnsureComp<ChangelingComponent>(newUid);

EntityManager.AddComponent(newUid, comp.lingStore);

if (comp.AugmentedEyesightPurchased)
_changeling.InitializeAugmentedEyesight(newUid);

_bodySystem.GibBody(uid);
}
Comment on lines +40 to +59
Copy link
Contributor

Choose a reason for hiding this comment

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

🧹 Nitpick (assertive)

Validate mind and lingStore.

Spawning a new mob and transferring the mind is sensible, but it might fail if comp.lingMind or comp.lingStore is null. Add guard clauses or null checks for extra safety, preventing cryptic errors if data is missing.

 if (comp.lingMind == null || comp.lingStore == null)
 {
+    Logger.Warning("ChangelingEggSystem: Missing mind or lingStore, canceling cycle.");
     return;
 }
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
if (TerminatingOrDeleted(comp.lingMind))
{
_bodySystem.GibBody(uid);
return;
}
var newUid = Spawn("MobMonkey", Transform(uid).Coordinates);
EnsureComp<MindContainerComponent>(newUid);
_mind.TransferTo(comp.lingMind, newUid);
EnsureComp<ChangelingComponent>(newUid);
EntityManager.AddComponent(newUid, comp.lingStore);
if (comp.AugmentedEyesightPurchased)
_changeling.InitializeAugmentedEyesight(newUid);
_bodySystem.GibBody(uid);
}
if (comp.lingMind == null || comp.lingStore == null)
{
Logger.Warning("ChangelingEggSystem: Missing mind or lingStore, canceling cycle.");
return;
}
if (TerminatingOrDeleted(comp.lingMind))
{
_bodySystem.GibBody(uid);
return;
}
var newUid = Spawn("MobMonkey", Transform(uid).Coordinates);
EnsureComp<MindContainerComponent>(newUid);
_mind.TransferTo(comp.lingMind, newUid);
EnsureComp<ChangelingComponent>(newUid);
EntityManager.AddComponent(newUid, comp.lingStore);
if (comp.AugmentedEyesightPurchased)
_changeling.InitializeAugmentedEyesight(newUid);
_bodySystem.GibBody(uid);
}

}
Loading
Loading