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

Roundstart Fugitives #2291

Draft
wants to merge 7 commits into
base: master
Choose a base branch
from
Draft
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
17 changes: 17 additions & 0 deletions Content.Server/Administration/Systems/AdminVerbSystem.Antags.cs
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,9 @@ public sealed partial class AdminVerbSystem
[ValidatePrototypeId<EntityPrototype>]
private const string DefaultThiefRule = "Thief";

[ValidatePrototypeId<EntityPrototype>]
private const string DefaultRoundstartFugitiveRule = "RoundstartFugitive";

[ValidatePrototypeId<StartingGearPrototype>]
private const string PirateGearId = "PirateGear";

Expand Down Expand Up @@ -151,5 +154,19 @@ private void AddAntagVerbs(GetVerbsEvent<Verb> args)
Message = Loc.GetString("admin-verb-make-thief"),
};
args.Verbs.Add(thief);

Verb roundstartfugitive = new()
{
Text = Loc.GetString("admin-verb-text-make-roundstartfugitive"),
Category = VerbCategory.Antag,
Icon = new SpriteSpecifier.Rsi(new ResPath("/Textures/Objects/Misc/handcuffs.rsi"), "handcuff"),
Act = () =>
{
_antag.ForceMakeAntag<RoundstartFugitiveRuleComponent>(targetPlayer, DefaultRoundstartFugitiveRule);
},
Impact = LogImpact.High,
Message = Loc.GetString("admin-verb-make-roundstartfugitive"),
};
args.Verbs.Add(roundstartfugitive);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,96 @@
using Content.Shared.Random;
using Robust.Shared.Audio;
using Robust.Shared.Prototypes;
using Content.Shared.Dataset;
using Content.Server.StationEvents.Events;
using Robust.Shared.Prototypes;
using Robust.Shared.Serialization.TypeSerializers.Implementations.Custom;
using Robust.Shared.Utility;

namespace Content.Server.GameTicking.Rules.Components;

/// <summary>
/// Taken from ThiefRuleComponent
/// Stores data for <see cref="RoundstartFugitiveRuleSystem"/>.
/// </summary>
[RegisterComponent, Access(typeof(RoundstartFugitiveRuleSystem))]
[AutoGenerateComponentPause] //This is from FugitiveRuleComponenet
//Everything below here is taken from FugitiveRuleComponent to hopefully make a miracle happen
public sealed partial class RoundstartFugitiveRuleComponent : Component
{
[DataField]
public LocId Announcement = "station-event-fugitive-hunt-announcement";

[DataField]
public LocId Sender = "fugitive-announcement-GALPOL";

[DataField]
public Color Color = Color.Yellow;

/// <summary>
/// Report paper to spawn. Its content is generated from the fugitive.
/// </summary>
[DataField]
public EntProtoId ReportPaper = "PaperFugitiveReport";

/// <summary>
/// How long to wait after the antag spawns before announcing it.
/// </summary>
[DataField]
public TimeSpan AnnounceDelay = TimeSpan.FromSeconds(5); //Should be FromMinutes(5) - changed for testing only!

/// <summary>
/// Station to give the report to.
/// </summary>
[DataField]
public EntityUid? Station;

/// <summary>
/// The report generated for the spawned fugitive.
/// </summary>
[DataField]
public string Report = string.Empty;

/// <summary>
/// When the announcement will be made, if an antag has spawned yet.
/// </summary>
[DataField(customTypeSerializer: typeof(TimeOffsetSerializer)), AutoPausedField]
public TimeSpan? NextAnnounce;

/// <summary>
/// Dataset to pick crimes on the report from.
/// </summary>
[DataField]
public ProtoId<LocalizedDatasetPrototype> CrimesDataset = "FugitiveCrimes";

/// <summary>
/// Max number of unique crimes they can be charged with.
/// Does not affect the counts of each crime.
/// </summary>
[DataField]
public int MinCrimes = 2;

/// <summary>
/// Min number of unique crimes they can be charged with.
/// Does not affect the counts of each crime.
/// </summary>
[DataField]
public int MaxCrimes = 7;

/// <summary>
/// Min counts of each crime that can be rolled.
/// </summary>
[DataField]
public int MinCounts = 1;

/// <summary>
/// Max counts of each crime that can be rolled.
/// </summary>
[DataField]
public int MaxCounts = 4;

public RoundstartFugitiveRuleComponent(TimeSpan? nextAnnounce)
{
NextAnnounce = nextAnnounce;
}
}
234 changes: 234 additions & 0 deletions Content.Server/GameTicking/Rules/RoundstartFugitiveRuleSystem.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,234 @@
using Content.Server.Antag;
using Content.Server.Chat.Systems;
using Content.Server.GameTicking.Rules.Components;
using Content.Server.Roles;
using Content.Shared.Humanoid;
using Content.Server.Communications;
using Content.Server.Forensics;
using Content.Server.Station.Systems;
using Content.Server.StationEvents.Components;
using Content.Server.StationEvents.Events;
using Content.Shared.GameTicking.Components;
using Content.Shared.Ghost;
using Content.Shared.Hands.EntitySystems;
using Content.Shared.Humanoid;
using Content.Shared.Humanoid.Prototypes;
using Content.Shared.Inventory;
using Content.Shared.Paper;
using Content.Shared.Popups;
using Content.Shared.Random.Helpers;
using Content.Shared.Storage.EntitySystems;
using Robust.Shared.Physics.Components;
using Robust.Shared.Prototypes;
using Robust.Shared.Utility;

namespace Content.Server.GameTicking.Rules;

/// <summary>
/// Copy of ThiefRuleSystem
/// </summary>
public sealed class RoundstartFugitiveRuleSystem : GameRuleSystem<RoundstartFugitiveRuleComponent>
{
[Dependency] private readonly AntagSelectionSystem _antag = default!;
[Dependency] private readonly InventorySystem _inventory = default!;
[Dependency] private readonly PaperSystem _paper = default!;
[Dependency] private readonly SharedHandsSystem _hands = default!;
[Dependency] private readonly SharedPopupSystem _popup = default!;
[Dependency] private readonly SharedStorageSystem _storage = default!;
[Dependency] private readonly ChatSystem _chat = default!;
[Dependency] private readonly StationSystem _station = default!;
[Dependency] private readonly IPrototypeManager _proto = default!;

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

SubscribeLocalEvent<RoundstartFugitiveRuleComponent, AfterAntagEntitySelectedEvent>(AfterAntagSelected);

SubscribeLocalEvent<RoundstartFugitiveRoleComponent, GetBriefingEvent>(OnGetBriefing);

}

//Moved this bit of code down below so AfterAntagSelcted isn't duplicate

// Greeting upon thief activation
//private void AfterAntagSelected(Entity<RoundstartFugitiveRuleComponent> mindId,
// ref AfterAntagEntitySelectedEvent args)
//{
// var ent = args.EntityUid;
// _antag.SendBriefing(ent, MakeBriefing(ent), null, null);
//}

// Character screen briefing
private void OnGetBriefing(Entity<RoundstartFugitiveRoleComponent> role, ref GetBriefingEvent args)
{
var ent = args.Mind.Comp.OwnedEntity;

if (ent is null)
return;
args.Append(MakeBriefing(ent.Value));
}

private string MakeBriefing(EntityUid ent)
{
var isHuman = HasComp<HumanoidAppearanceComponent>(ent);
var briefing = isHuman
? Loc.GetString("roundstartfugitive-role-greeting-human")
: Loc.GetString("roundstartfugitive-role-greeting-animal"); //Can thieves be animals???

if (isHuman)
briefing += "\n \n" + Loc.GetString("roundstartfugitive-role-greeting-equipment") + "\n";

return briefing;
}
// Copy of parts from FugitiveRules below here, hopefully this will cause the Fugitive Fax event to trigger?

protected override void ActiveTick(EntityUid uid, RoundstartFugitiveRuleComponent comp, GameRuleComponent rule, float frameTime)
{
if (comp.NextAnnounce is not {} next || next > Timing.CurTime)
return;

var announcement = Loc.GetString(comp.Announcement);
var sender = Loc.GetString(comp.Sender);
_chat.DispatchGlobalAnnouncement(announcement, sender: sender, colorOverride: comp.Color);

// send the report to every comms console on the station
var query = EntityQueryEnumerator<TransformComponent, CommunicationsConsoleComponent>();
var consoles = new List<TransformComponent>();
while (query.MoveNext(out var console, out var xform, out _))
{
if (_station.GetOwningStation(console, xform) != comp.Station || HasComp<GhostComponent>(console))
continue;

consoles.Add(xform);
}

foreach (var xform in consoles)
{
SpawnReport(comp, xform);
}

// prevent any possible funnies
comp.NextAnnounce = null;

RemCompDeferred(uid, comp);
}

private void AfterAntagSelected(Entity<RoundstartFugitiveRuleComponent> mindId, ref AfterAntagEntitySelectedEvent args)
{
{
var ent = args.EntityUid;
_antag.SendBriefing(ent, MakeBriefing(ent), null, null);
}

var (uid, comp) = mindId;
if (comp.NextAnnounce != null)
{
Log.Error("Fugitive rule spawning multiple fugitives isn't supported, sorry.");
return;
}

var fugi = args.EntityUid;
comp.Report = GenerateReport(fugi, comp).ToMarkup();
comp.Station = _station.GetOwningStation(fugi);
comp.NextAnnounce = Timing.CurTime + comp.AnnounceDelay;

// _popup.PopupEntity(Loc.GetString("fugitive-spawn"), fugi, fugi); //I think this does the 'You fall from the ceiling popup? If so, can be removed later

// give the fugi a report so they know what their charges are
var report = SpawnReport(comp, Transform(fugi));

// try to insert it into their bag
if (_inventory.TryGetSlotEntity(fugi, "back", out var backpack))
{
_storage.Insert(backpack.Value, report, out _, playSound: false);
}
else
{
// no bag somehow, at least pick it up
_hands.TryPickup(fugi, report);
}
}

private Entity<PaperComponent> SpawnReport(RoundstartFugitiveRuleComponent rule, TransformComponent xform)
{
var report = Spawn(rule.ReportPaper, xform.Coordinates);
var paper = Comp<PaperComponent>(report);
var ent = (report, paper);
_paper.SetContent(ent, rule.Report);
return ent;
}

private FormattedMessage GenerateReport(EntityUid uid, RoundstartFugitiveRuleComponent rule)
{
var report = new FormattedMessage();
report.PushMarkup(Loc.GetString("fugitive-report-title"));
report.PushNewline();
report.PushMarkup(Loc.GetString("fugitive-report-first-line"));
report.PushNewline();

if (!TryComp<HumanoidAppearanceComponent>(uid, out var humanoid))
{
report.AddMarkup(Loc.GetString("fugitive-report-inhuman", ("name", uid)));
return report;
}

var species = _proto.Index(humanoid.Species); //IPrototype or protoype

report.PushMarkup(Loc.GetString("fugitive-report-morphotype", ("species", Loc.GetString(species.Name))));
report.PushMarkup(Loc.GetString("fugitive-report-age", ("age", humanoid.Age)));
report.PushMarkup(Loc.GetString("fugitive-report-sex", ("sex", humanoid.Sex.ToString())));

if (TryComp<PhysicsComponent>(uid, out var physics))
report.PushMarkup(Loc.GetString("fugitive-report-weight", ("weight", Math.Round(physics.FixturesMass))));

// add a random identifying quality that officers can use to track them down - DISABLED
//report.PushMarkup(RobustRandom.Next(0, 2) switch
//{
// 0 => Loc.GetString("fugitive-report-detail-dna", ("dna", GetDNA(uid))),
// _ => Loc.GetString("fugitive-report-detail-prints", ("prints", GetPrints(uid)))
//});

report.PushNewline();
report.PushMarkup(Loc.GetString("fugitive-report-crimes-header"));

// generate some random crimes to avoid this situation
// "officer what are my charges?"
// "uh i dunno a piece of paper said to arrest you thats it"
AddCharges(report, rule);

report.PushNewline();
report.AddMarkup(Loc.GetString("fugitive-report-last-line"));

return report;
}

private string GetDNA(EntityUid uid)
{
return CompOrNull<DnaComponent>(uid)?.DNA ?? "?";
}

private string GetPrints(EntityUid uid)
{
return CompOrNull<FingerprintComponent>(uid)?.Fingerprint ?? "?";
}

private void AddCharges(FormattedMessage report, RoundstartFugitiveRuleComponent rule)
{
var crimeTypes = _proto.Index(rule.CrimesDataset);
var crimes = new HashSet<LocId>();
var total = RobustRandom.Next(rule.MinCrimes, rule.MaxCrimes + 1);
while (crimes.Count < total)
{
crimes.Add(RobustRandom.Pick(crimeTypes));
}

foreach (var crime in crimes)
{
var count = RobustRandom.Next(rule.MinCounts, rule.MaxCounts + 1);
report.PushMarkup(Loc.GetString("fugitive-report-crime", ("crime", Loc.GetString(crime)), ("count", count)));
}
}
}


12 changes: 12 additions & 0 deletions Content.Server/Roles/RoundstartFugitiveRoleComponent.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
using Content.Shared.Roles;

namespace Content.Server.Roles;

/// <summary>
/// Taken from ThiefRoleComponent
/// Added to mind role entities to tag that they are a roundstart fugitive.
/// </summary>
[RegisterComponent]
public sealed partial class RoundstartFugitiveRoleComponent : BaseMindRoleComponent
{
}
3 changes: 3 additions & 0 deletions Resources/Locale/en-US/administration/antag.ftl
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,8 @@ admin-verb-make-nuclear-operative = Make target into a lone Nuclear Operative.
admin-verb-make-pirate = Make the target into a pirate. Note this doesn't configure the game rule.
admin-verb-make-head-rev = Make the target into a Head Revolutionary.
admin-verb-make-thief = Make the target into a thief.
admin-verb-make-roundstartfugitive = Make the target into a roundstart fugitive.


admin-verb-text-make-traitor = Make Traitor
admin-verb-text-make-initial-infected = Make Initial Infected
Expand All @@ -14,3 +16,4 @@ admin-verb-text-make-nuclear-operative = Make Nuclear Operative
admin-verb-text-make-pirate = Make Pirate
admin-verb-text-make-head-rev = Make Head Rev
admin-verb-text-make-thief = Make Thief
admin-verb-text-make-roundstartfugitive = Make Roundstart Fugitive
Loading