Skip to content
Open
Show file tree
Hide file tree
Changes from all 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
21 changes: 21 additions & 0 deletions Content.Client/ADT/BloodWorm/BloodWormResourceAlertSystem.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
using Content.Shared._Ganimed.BloodWorm.Components;
using Content.Shared.Alert.Components;

namespace Content.Client._Ganimed.BloodWorm;

public sealed class BloodWormResourceAlertSystem : EntitySystem
{
public override void Initialize()
{
base.Initialize();
SubscribeLocalEvent<BloodWormResourceComponent, GetGenericAlertCounterAmountEvent>(OnGetCounterAmount);
}

private void OnGetCounterAmount(Entity<BloodWormResourceComponent> ent, ref GetGenericAlertCounterAmountEvent args)
{
if (args.Alert.ID != ent.Comp.BloodAlert)
return;

args.Amount = ent.Comp.BloodAmount;
}
}
32 changes: 32 additions & 0 deletions Content.Client/ADT/BloodWorm/BloodWormStatusIconSystem.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
using Content.Shared._Ganimed.BloodWorm.Components;
using Content.Shared.Antag;
using Content.Shared.StatusIcon;
using Content.Shared.StatusIcon.Components;
using Robust.Client.Player;
using Robust.Shared.Prototypes;

namespace Content.Client._Ganimed.BloodWorm;

public sealed class BloodWormStatusIconSystem : EntitySystem
{
[Dependency] private readonly IPrototypeManager _prototype = default!;
[Dependency] private readonly IPlayerManager _player = default!;

public override void Initialize()
{
base.Initialize();
SubscribeLocalEvent<BloodWormInfectedComponent, GetStatusIconsEvent>(OnGetStatusIcons);
}

private void OnGetStatusIcons(Entity<BloodWormInfectedComponent> ent, ref GetStatusIconsEvent args)
{
var viewer = _player.LocalEntity;
if (viewer == null)
return;

if (!HasComp<ShowAntagIconsComponent>(viewer.Value) && !HasComp<BloodWormResourceComponent>(viewer.Value))
return;

args.StatusIcons.Add(_prototype.Index(ent.Comp.StatusIcon));
}
}
1 change: 1 addition & 0 deletions Content.Server/Antag/AntagSelectionSystem.cs
Original file line number Diff line number Diff line change
Expand Up @@ -614,6 +614,7 @@ private void OnObjectivesTextGetInfo(Entity<AntagSelectionComponent> ent, ref Ob
return;

args.Minds = ent.Comp.AssignedMinds;

args.AgentName = Loc.GetString(name);
}
}
Expand Down
90 changes: 90 additions & 0 deletions Content.Server/Objectives/ObjectivesSystem.cs
Original file line number Diff line number Diff line change
Expand Up @@ -131,6 +131,96 @@ private void OnRoundEndText(RoundEndTextAppendEvent ev)

private void AddSummary(StringBuilder result, string agent, List<(EntityUid, string)> minds)
{
if (agent == Loc.GetString("blood-worm-round-end-agent-name"))
{
EntityUid? sharedMindId = null;
MindComponent? sharedMind = null;

foreach (var (mindId, name) in minds)
{
if (!TryComp<MindComponent>(mindId, out var mind))
continue;

var title = GetTitle((mindId, mind), name);
var custody = IsInCustody(mindId, mind) ? Loc.GetString("objectives-in-custody") : string.Empty;

if (mind.Objectives.Count == 0)
result.AppendLine(Loc.GetString("objectives-no-objectives", ("custody", custody), ("title", title), ("agent", agent)));
else
result.AppendLine(Loc.GetString("objectives-with-objectives", ("custody", custody), ("title", title), ("agent", agent)));

if (sharedMindId == null && mind.Objectives.Count > 0)
{
sharedMindId = mindId;
sharedMind = mind;
}
}

if (sharedMindId == null || sharedMind == null)
return;

// Shared hive objectives should be shown only once.
var ev = new PrependObjectivesSummaryTextEvent();
RaiseLocalEvent(sharedMindId.Value, ref ev);
if (ev.Text != string.Empty)
result.AppendLine(ev.Text);

foreach (var objectiveGroup in sharedMind.Objectives.GroupBy(o => Comp<ObjectiveComponent>(o).LocIssuer))
{
result.AppendLine(objectiveGroup.Key);

foreach (var objective in objectiveGroup)
{
var info = GetInfo(objective, sharedMindId.Value, sharedMind);
if (info == null)
continue;

var objectiveTitle = info.Value.Title;
var progress = info.Value.Progress;

result.Append("- ");
if (!_showGreentext)
{
result.AppendLine(objectiveTitle);
}
else if (progress > 0.99f)
{
result.AppendLine(Loc.GetString(
"objectives-objective-success",
("objective", objectiveTitle),
("progress", progress)
));
}
else if (progress <= 0.99f && progress >= 0.5f)
{
result.AppendLine(Loc.GetString(
"objectives-objective-partial-success",
("objective", objectiveTitle),
("progress", progress)
));
}
else if (progress < 0.5f && progress > 0f)
{
result.AppendLine(Loc.GetString(
"objectives-objective-partial-failure",
("objective", objectiveTitle),
("progress", progress)
));
}
else
{
result.AppendLine(Loc.GetString(
"objectives-objective-fail",
("objective", objectiveTitle),
("progress", progress)
));
}
}
}

return;
}

var agentSummaries = new List<(string summary, float successRate, int completedObjectives)>();

foreach (var (mindId, name) in minds)
Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
using Content.Server.StationEvents.Events;
using Content.Shared.Storage;
using Robust.Shared.Map; // Ganimed-tweak

namespace Content.Server.StationEvents.Components;

Expand All @@ -14,4 +15,13 @@ public sealed partial class VentCrittersRuleComponent : Component
/// </summary>
[DataField("specialEntries")]
public List<EntitySpawnEntry> SpecialEntries = new();

// Ganimed-edit start
/// <summary>
/// Cached spawn location for antag selection, so the ghost role spawner and the
/// actual antag mob spawn at the same vent.
/// </summary>
[DataField]
public MapCoordinates? SpawnLocation;
// Ganimed-edit end
}
45 changes: 45 additions & 0 deletions Content.Server/StationEvents/Events/VentCrittersRule.cs
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
using Content.Server.Antag; // Ganimed-tweak
using Content.Server.StationEvents.Components;
using Content.Shared.GameTicking.Components;
using Content.Shared.Station.Components;
Expand All @@ -14,6 +15,50 @@ public sealed class VentCrittersRule : StationEventSystem<VentCrittersRuleCompon
* USE THE PROTOTYPE.
*/

// Ganimed-edit start
[Dependency] private readonly SharedTransformSystem _transform = default!;

public override void Initialize()
{
base.Initialize();
SubscribeLocalEvent<VentCrittersRuleComponent, AntagSelectLocationEvent>(OnSelectLocation);
}

private void OnSelectLocation(Entity<VentCrittersRuleComponent> ent, ref AntagSelectLocationEvent args)
{
if (!TryGetRandomStation(out var station))
return;

var mainGrid = GetStationMainGrid(Comp<StationDataComponent>(station.Value));
if (mainGrid == null)
return;

// Reuse cached location so the actual mob spawns at the same vent as the ghost role spawner.
if (ent.Comp.SpawnLocation != null)
{
args.Coordinates.Add(ent.Comp.SpawnLocation.Value);
return;
Comment thread
Gorox221 marked this conversation as resolved.
}

var locations = EntityQueryEnumerator<VentCritterSpawnLocationComponent, TransformComponent>();
var validVents = new List<MapCoordinates>();
while (locations.MoveNext(out _, out _, out var transform))
{
if (transform.GridUid != mainGrid.Value.Owner)
continue;

validVents.Add(_transform.ToMapCoordinates(transform.Coordinates));
}

if (validVents.Count == 0)
return;

var chosen = RobustRandom.Pick(validVents);
ent.Comp.SpawnLocation = chosen;
args.Coordinates.Add(chosen);
}
// Ganimed-edit end

protected override void Started(EntityUid uid, VentCrittersRuleComponent component, GameRuleComponent gameRule, GameRuleStartedEvent args)
{
base.Started(uid, component, gameRule, args);
Expand Down
136 changes: 136 additions & 0 deletions Content.Server/_Ganimed/BloodWorm/Components/BloodWormComponent.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,136 @@
using Content.Shared.Chemistry.Reagent;
using Robust.Shared.Prototypes;
using Content.Shared.DoAfter;
using Robust.Shared.Audio;

namespace Content.Server._Ganimed.BloodWorm.Components;

public enum BloodWormStage : byte
{
Cocoon,
Hatchling,
Juvenile,
Adult
}

[RegisterComponent]
public sealed partial class BloodWormComponent : Component
{
[DataField]
public BloodWormStage Stage = BloodWormStage.Hatchling;

[DataField]
public float BloodResource = 80f;

[DataField]
public float MaxBloodResource = 80f;

[DataField]
public float ConsumedBlood = 0f;

[DataField]
public float SyntheticBloodConsumed = 0f;

[DataField]
public float MaxSyntheticBloodGain = 1000f;

[DataField]
public float SyntheticEfficiency = 0.7f;

[DataField]
public float HatchlingMatureThreshold = 500f;

[DataField]
public float JuvenileMatureThreshold = 1500f;

[DataField]
public float EjectThresholdRatio = 0.1f;

/// <summary>
/// If the host has ALL of these reagents in their bloodstream,
/// the worm will automatically leave the host.
/// </summary>
[DataField]
public List<ProtoId<ReagentPrototype>> LeaveHostReagents = new();

[DataField]
public float HostDrainPerSecond = 14f;

[DataField]
public float HostBleedDamageMultiplier = 4f;

[DataField]
public float RegenPerSecond = 0.3f;

[DataField]
public EntityUid? Host;

[DataField]
public string HostContainerId = "blood-worm";

[DataField]
public EntProtoId? LeechAction = "ActionBloodWormLeechHatchling";

[DataField]
public EntProtoId? InvadeAction = "ActionBloodWormInvade";

[DataField]
public EntProtoId? SpitAction;

[DataField]
public EntProtoId? MatureAction = "ActionBloodWormMature";

[DataField]
public EntProtoId? InjectAction;

[DataField]
public EntProtoId? LeaveHostAction = "ActionBloodWormLeaveHost";

[DataField]
public EntProtoId? ReviveHostAction;

[DataField]
public EntProtoId SpitProjectile = "BulletAcid";

[DataField]
public float SpitProjectileSpeed = 18f;

[DataField]
public EntProtoId? CocoonHatchPrototype;

[DataField]
public float CocoonHatchDelay = 30f;

[DataField]
public int CocoonSpawnHatchlings = 0;

[DataField]
public bool CocoonResetProgress = false;

public float CocoonAccumulator = 0f;

public EntityUid? LeechActionEntity;
public EntityUid? InvadeActionEntity;
public EntityUid? SpitActionEntity;
public EntityUid? MatureActionEntity;

public DoAfterId? LeechDoAfter;

[DataField]
public SoundSpecifier EnterHostSound = new SoundPathSpecifier("/Audio/Weapons/Xeno/alien_claw_flesh2.ogg");

[DataField]
public SoundSpecifier LeaveHostSound = new SoundPathSpecifier("/Audio/Effects/gib2.ogg");

[DataField]
public SoundSpecifier SpitSound = new SoundPathSpecifier("/Audio/Weapons/Xeno/alien_spitacid.ogg");

[DataField]
public SoundSpecifier CocoonFormSound = new SoundPathSpecifier("/Audio/Effects/Chemistry/bubbles.ogg");

[DataField]
public SoundSpecifier CocoonHatchSound = new SoundPathSpecifier("/Audio/Effects/gib1.ogg");

[DataField]
public SoundSpecifier LeechTickSound = new SoundPathSpecifier("/Audio/Effects/Fluids/slosh.ogg");
}
Loading
Loading