Skip to content

Commit

Permalink
Add hostage ops (Nuke ops alternative objective) (#2545)
Browse files Browse the repository at this point in the history
* first commit

* Some fixes

* final fixes

* Partly working

* More fixes + you can now win

* More fixes

* Forget

* Final fixes

* Update the number of hostages to 4

* Add feedback popups

* Use mind rolls like a cool person

* bruh

* NOW I'm mad

* Bruh

* :trollface:

Signed-off-by: deltanedas <[email protected]>

* :trollface:

Signed-off-by: deltanedas <[email protected]>

* Bruh I forget

---------

Signed-off-by: deltanedas <[email protected]>
Co-authored-by: deltanedas <@deltanedas:kde.org>
Co-authored-by: deltanedas <[email protected]>
  • Loading branch information
beck-thompson and deltanedas authored Jan 25, 2025
1 parent 2324562 commit 13ccf59
Show file tree
Hide file tree
Showing 29 changed files with 763 additions and 6 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -141,5 +141,6 @@ public enum WinCondition : byte
NukiesAbandoned,
AllNukiesDead,
SomeNukiesAlive,
AllNukiesAlive
AllNukiesAlive,
NukiesKidnappedHeads, // DeltaV - Hostage ops
}
35 changes: 34 additions & 1 deletion Content.Server/GameTicking/Rules/NukeopsRuleSystem.cs
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
using Content.Server._DV.Objectives.Components; // DeltaV
using Content.Server._DV.Objectives.Systems; // DeltaV
using Content.Server.Antag;
using Content.Server.Communications;
using Content.Server.GameTicking.Rules.Components;
Expand Down Expand Up @@ -37,6 +39,8 @@ public sealed class NukeopsRuleSystem : GameRuleSystem<NukeopsRuleComponent>
[Dependency] private readonly RoundEndSystem _roundEndSystem = default!;
[Dependency] private readonly StoreSystem _store = default!;
[Dependency] private readonly TagSystem _tag = default!;
[Dependency] private readonly KidnapHeadsConditionSystem _kidnap = default!; // DeltaV
[Dependency] private readonly SharedMapSystem _map = default!; // DeltaV

[ValidatePrototypeId<CurrencyPrototype>]
private const string TelecrystalCurrencyPrototype = "Telecrystal";
Expand All @@ -49,6 +53,7 @@ public override void Initialize()
base.Initialize();

SubscribeLocalEvent<NukeExplodedEvent>(OnNukeExploded);
SubscribeLocalEvent<NukeOpsShuttleComponent, FTLCompletedEvent>(OnFTLCompleted); // DeltaV - Kidnap heads objective
SubscribeLocalEvent<GameRunLevelChangedEvent>(OnRunLevelChanged);
SubscribeLocalEvent<NukeDisarmSuccessEvent>(OnNukeDisarm);

Expand Down Expand Up @@ -156,6 +161,34 @@ private void OnNukeExploded(NukeExplodedEvent ev)
}
}

// DeltaV - Kidnap heads nukie objective
private void OnFTLCompleted(Entity<NukeOpsShuttleComponent> ent, ref FTLCompletedEvent args)
{
var query = QueryActiveRules();
while (query.MoveNext(out var uid, out _, out var nukeops, out _))
{
// Get the nukie outpost map.
if (!TryComp<RuleGridsComponent>(uid, out var ruleGridsComp) || ruleGridsComp.Map == null)
return;

// Make sure your on the same map as the nukie outposts map.
if (args.MapUid == _map.GetMap(ruleGridsComp.Map.Value))
{
// Now check of the kidnap heads objective is complete... (Yes this is suspect)
var objectives = EntityQueryEnumerator<KidnapHeadsConditionComponent>();
if (!objectives.MoveNext(out var objUid, out var kidnapHeads)) // No kidnap head objectives
return;

if (!_kidnap.IsCompleted((objUid, kidnapHeads)))
return;

nukeops.WinConditions.Add(WinCondition.NukiesKidnappedHeads);
SetWinType((uid, nukeops), WinType.OpsMajor);
_roundEndSystem.EndRound();
}
}
}

private void OnRunLevelChanged(GameRunLevelChangedEvent ev)
{
if (ev.New is not GameRunLevel.PostRound)
Expand Down Expand Up @@ -487,7 +520,7 @@ private void OnAfterAntagEntSelected(Entity<NukeopsRuleComponent> ent, ref After
private void OnGetBriefing(Entity<NukeopsRoleComponent> role, ref GetBriefingEvent args)
{
// TODO Different character screen briefing for the 3 nukie types
args.Append(Loc.GetString("nukeops-briefing"));
// args.Append(Loc.GetString("nukeops-briefing")); Delta-V - Nukie operations take care of this.
}

/// <remarks>
Expand Down
11 changes: 11 additions & 0 deletions Content.Server/Nuke/NukeCodePaperSystem.cs
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
using System.Diagnostics.CodeAnalysis;
using Content.Server._DV.Antag; // DeltaV
using Content.Server.Chat.Systems;
using Content.Server.Fax;
using Content.Shared.Fax.Components;
Expand Down Expand Up @@ -35,6 +36,16 @@ private void SetupPaper(EntityUid uid, NukeCodePaperComponent? component = null,
if (!Resolve(uid, ref component))
return;

// DeltaV - Not the best way of doing this
var evnt = new GetNukeCodePaperWriting();
RaiseLocalEvent(ref evnt);
if (evnt.ToWrite != null)
{
if (TryComp<PaperComponent>(uid, out var deltavpaperComp))
_paper.SetContent((uid, deltavpaperComp), evnt.ToWrite);
return;
}
// DeltaV - End
if (TryGetRelativeNukeCode(uid, out var paperContent, station, onlyCurrentStation: component.AllNukesAvailable))
{
if (TryComp<PaperComponent>(uid, out var paperComp))
Expand Down
30 changes: 30 additions & 0 deletions Content.Server/_DV/Antag/NukieOperationComponent.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
using Content.Shared._DV.Antag;
using Content.Shared.Random;
using Robust.Shared.Prototypes;

namespace Content.Server._DV.Antag;

/// <summary>
/// Component holds what operations are possible and their weights.
/// </summary>
[RegisterComponent, Access(typeof(NukieOperationSystem))]
public sealed partial class NukieOperationComponent : Component
{
/// <summary>
/// The different nukie operations.
/// </summary>
[DataField(required: true)]
public ProtoId<WeightedRandomPrototype> Operations;

/// <summary>
/// The chosen operation. Is set after the first nukie spawns.
/// </summary>
[DataField]
public ProtoId<NukieOperationPrototype>? ChosenOperation;
}

/// <summary>
/// Event to get update the nuke code paper to not actually have the code anymore.
/// </summary>
[ByRefEvent]
public record struct GetNukeCodePaperWriting(string? ToWrite);
70 changes: 70 additions & 0 deletions Content.Server/_DV/Antag/NukieOperationSystem.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,70 @@
using Content.Server.Antag;
using Content.Server.Objectives;
using Content.Shared._DV.FeedbackOverwatch;
using Content.Shared.Mind;
using Content.Shared.Random.Helpers;
using Robust.Shared.Prototypes;
using Robust.Shared.Random;

namespace Content.Server._DV.Antag;

public sealed class NukieOperationSystem : EntitySystem
{
[Dependency] private readonly IRobustRandom _random = default!;
[Dependency] private readonly SharedMindSystem _mind = default!;
[Dependency] private readonly ObjectivesSystem _objectives = default!;
[Dependency] private readonly IPrototypeManager _proto = default!;
[Dependency] private readonly SharedFeedbackOverwatchSystem _feedback = default!;
public override void Initialize()
{
base.Initialize();

SubscribeLocalEvent<NukieOperationComponent, AfterAntagEntitySelectedEvent>(OnAntagSelected);
SubscribeLocalEvent<GetNukeCodePaperWriting>(OnNukeCodePaperWritingEvent);
}

private void OnAntagSelected(Entity<NukieOperationComponent> ent, ref AfterAntagEntitySelectedEvent args)
{
// Yes this is bad, but I couldn't easily find an event that would work.
if (ent.Comp.ChosenOperation == null)
{
if (!_proto.TryIndex(ent.Comp.Operations, out var opProto))
return;

ent.Comp.ChosenOperation = _random.Pick(opProto.Weights);
}

if (!_mind.TryGetMind(args.Session, out var mindId, out var mind))
return;

if (!_proto.TryIndex(ent.Comp.ChosenOperation, out var chosenOp))
return;

foreach (var objectiveProto in chosenOp.OperationObjectives)
{
if (!_objectives.TryCreateObjective((mindId, mind), objectiveProto, out var objective))
{
Log.Error("Couldn't create objective for nukie: " + mindId); // This should never happen.
continue;
}

_mind.AddObjective(mindId, mind, objective.Value);

// TODO: Remove once enough feedback has been received!
if (objectiveProto.Id == "KidnapHeadsObjective")
_feedback.SendPopupMind(mindId, "NukieHostageRoundStartPopup");
}
}

private void OnNukeCodePaperWritingEvent(ref GetNukeCodePaperWriting ev)
{
// This is suspect AT BEST
var query = EntityQueryEnumerator<NukieOperationComponent>();
while (query.MoveNext(out _, out var nukieOperation)) // this should only loop once.
{
if (!_proto.TryIndex(nukieOperation.ChosenOperation, out var opProto) || opProto.NukeCodePaperOverride == null)
continue;
ev.ToWrite = Loc.GetString(opProto.NukeCodePaperOverride);
}
}
}
60 changes: 60 additions & 0 deletions Content.Server/_DV/FeedbackPopup/NukeHostageFeedbackPopupSystem.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,60 @@
using Content.Server._DV.Objectives.Components;
using Content.Shared._DV.FeedbackOverwatch;
using Content.Shared.GameTicking;
using Content.Shared.Mind;
using Content.Shared.Mobs;
using Content.Shared.Roles;
using Content.Server.Roles;

namespace Content.Server._DV.FeedbackPopup;

/// <summary>
/// System to get feedback on the new objective!
/// </summary>
public sealed class NukeHostageFeedbackPopupSystem : EntitySystem
{
[Dependency] private readonly SharedMindSystem _mind = default!;
[Dependency] private readonly SharedFeedbackOverwatchSystem _feedback = default!;
[Dependency] private readonly SharedRoleSystem _role = default!;

public override void Initialize()
{
base.Initialize();
SubscribeLocalEvent<RoundEndMessageEvent>(OnRoundEnd);
SubscribeLocalEvent<MobStateChangedEvent>(OnMobStateChanged);
}

private void OnRoundEnd(RoundEndMessageEvent ev)
{
if (!IsHostageOps())
return;

var allMinds = _mind.GetAliveHumans();

foreach (var mind in allMinds)
{
if (mind.Comp.OwnedEntity != null && _role.MindHasRole<NukeopsRoleComponent>(mind))
_feedback.SendPopupMind(mind, "NukieHostageRoundEndPopup");
else
_feedback.SendPopupMind(mind, "NukieHostageRoundEndCrewPopup");
}
}

private void OnMobStateChanged(MobStateChangedEvent args)
{
if (args.NewMobState != MobState.Dead || !_mind.TryGetMind(args.Target, out var mindUid, out _) || !IsHostageOps())
return;

if (_role.MindHasRole<NukeopsRoleComponent>(mindUid))
_feedback.SendPopup(args.Target, "NukieHostageRoundEndPopup");
}


/// <remarks>
/// If even one person has the kidnap heads objective this will return true.
/// </remarks>
private bool IsHostageOps()
{
return EntityQueryEnumerator<KidnapHeadsConditionComponent>().MoveNext(out _);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
using Content.Server._DV.Objectives.Systems;

namespace Content.Server._DV.Objectives.Components;

/// <summary>
/// Kidnap some number of heads. Use the NumberObjective to set the exact number
/// </summary>
[RegisterComponent, Access(typeof(KidnapHeadsConditionSystem))]
public sealed partial class KidnapHeadsConditionComponent: Component;
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
using Content.Server._DV.Objectives.Systems;

namespace Content.Server._DV.Objectives.Components;

/// <summary>
/// For nuclear operatives trying to nuke the station. Should only be completed if the correct station is exploded.
/// </summary>
[RegisterComponent, Access(typeof(NukeStationConditionSystem))]
public sealed partial class NukeStationConditionComponent : Component;
Original file line number Diff line number Diff line change
@@ -0,0 +1,68 @@
using Content.Server._DV.Objectives.Components;
using Content.Server.Objectives.Systems;
using Content.Server.Revolutionary.Components;
using Content.Shared.Cuffs;
using Content.Shared.Cuffs.Components;
using Content.Shared.Mind;
using Content.Shared.Objectives.Components;

namespace Content.Server._DV.Objectives.Systems;

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

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

SubscribeLocalEvent<KidnapHeadsConditionComponent, ObjectiveGetProgressEvent>(OnGetProgress);
}

private void OnGetProgress(Entity<KidnapHeadsConditionComponent> condition, ref ObjectiveGetProgressEvent args)
{
args.Progress = GetProgress(condition);
}

public float GetProgress(Entity<KidnapHeadsConditionComponent> condition)
{
GetTotalAndCuffedHeads(out var totalHeads, out var cuffedHeads);

if (totalHeads == 0)
return 1.0f;

return (float) cuffedHeads / Math.Min(totalHeads, _number.GetTarget(condition));
}

public bool IsCompleted(Entity<KidnapHeadsConditionComponent> condition)
{
GetTotalAndCuffedHeads(out var totalHeads, out var cuffedHeads);
if (totalHeads == 0)
return false;

return cuffedHeads == Math.Min(totalHeads, _number.GetTarget(condition));
}

private void GetTotalAndCuffedHeads(out int totalHeads, out int cuffedHeads)
{
var allHumanMinds = _mind.GetAliveHumans();
totalHeads = 0;
cuffedHeads = 0;
foreach (var mind in allHumanMinds)
{
if (mind.Comp.OwnedEntity is not { } mob)
continue;

if (!HasComp<CommandStaffComponent>(mob))
continue;
totalHeads++;

if (!TryComp<CuffableComponent>(mob, out var cuffable) || !_cuffable.IsCuffed((mob, cuffable)))
continue;
cuffedHeads++;
}
}
}

Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
using Content.Server._DV.Objectives.Components;
using Content.Server.GameTicking.Rules.Components;
using Content.Server.Nuke;
using Content.Server.Objectives.Systems;
using Content.Server.Station.Components;
using Content.Shared.Objectives.Components;

namespace Content.Server._DV.Objectives.Systems;

public sealed class NukeStationConditionSystem : EntitySystem
{
[Dependency] private readonly CodeConditionSystem _codeCondition = default!;

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

SubscribeLocalEvent<NukeExplodedEvent>(OnNukeExploded);
}

private void OnNukeExploded(NukeExplodedEvent ev)
{
var nukeOpsQuery = EntityQueryEnumerator<NukeopsRuleComponent>();
while (nukeOpsQuery.MoveNext(out _, out var nukeopsRule)) // this should only loop once.
{
if (!TryComp<StationDataComponent>(nukeopsRule.TargetStation, out var data))
return;

foreach (var grid in data.Grids)
{
if (grid != ev.OwningStation) // They nuked the target station!
continue;

// Set all the objectives to true.
var nukeStationQuery = EntityQueryEnumerator<NukeStationConditionComponent>();
while (nukeStationQuery.MoveNext(out var uid, out _))
{
_codeCondition.SetCompleted(uid);
}
}
}
}
}
Loading

0 comments on commit 13ccf59

Please sign in to comment.