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

Add hostage ops (Nuke ops alternative objective) #2545

Open
wants to merge 22 commits into
base: master
Choose a base branch
from
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
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;
}
beck-thompson marked this conversation as resolved.
Show resolved Hide resolved
// 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);
}
}
}
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
Loading