Skip to content

Commit

Permalink
Port Station Goals (Simple-Station#465)
Browse files Browse the repository at this point in the history
# Description

Simple-Station/Parkstation-Friendly-Chainsaw#10

This adds a feature whereby a random goal for the shift is faxed to the
station's Captain at the start of every shift.
It is up to the Captain to decide if and how this goal is to be
completed.
Goals are randomly generated every shift, and are meant to help
encourage station activity and RP.
Admins are also able to send station goals via `sendstationgoal`.

---

<details><summary><h1>Media</h1></summary>
<p>


![stationgoals](https://github.com/Simple-Station/Einstein-Engines/assets/16548818/4a2b5533-dfee-4388-bfbc-043ee71b2647)

---

</p>
</details> 

# Changelog

:cl: VMSolidus
- add: Added station goals that get sent to the Command fax machine at
the start of every shift

---------

Signed-off-by: VMSolidus <evilexecutive@gmail.com>
Co-authored-by: Danger Revolution! <142105406+DangerRevolution@users.noreply.github.com>
Co-authored-by: DEATHB4DEFEAT <77995199+DEATHB4DEFEAT@users.noreply.github.com>
  • Loading branch information
3 people committed Jun 21, 2024
1 parent 8d72195 commit bc93199
Show file tree
Hide file tree
Showing 16 changed files with 572 additions and 1 deletion.
1 change: 1 addition & 0 deletions Content.Client/Entry/EntryPoint.cs
Original file line number Diff line number Diff line change
Expand Up @@ -124,6 +124,7 @@ public override void Init()
_prototypeManager.RegisterIgnore("wireLayout");
_prototypeManager.RegisterIgnore("alertLevels");
_prototypeManager.RegisterIgnore("nukeopsRole");
_prototypeManager.RegisterIgnore("stationGoal");

_componentFactory.GenerateNetIds();
_adminManager.Initialize();
Expand Down
6 changes: 6 additions & 0 deletions Content.Server/Fax/FaxMachineComponent.cs
Original file line number Diff line number Diff line change
Expand Up @@ -51,6 +51,12 @@ public sealed partial class FaxMachineComponent : Component
[DataField("receiveNukeCodes")]
public bool ReceiveNukeCodes { get; set; } = false;

/// <summary>
/// Should this fax receive station goals
/// </summary>
[DataField]
public bool ReceiveStationGoal { get; set; } = false;

/// <summary>
/// Sound to play when fax has been emagged
/// </summary>
Expand Down
13 changes: 13 additions & 0 deletions Content.Server/GameTicking/Events/RoundEndedEvent.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
namespace Content.Server.GameTicking;

public sealed class RoundEndedEvent : EntityEventArgs
{
public int RoundId { get; }
public TimeSpan RoundDuration { get; }

public RoundEndedEvent(int roundId, TimeSpan roundDuration)
{
RoundId = roundId;
RoundDuration = roundDuration;
}
}
11 changes: 11 additions & 0 deletions Content.Server/GameTicking/Events/RoundStartedEvent.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
namespace Content.Server.GameTicking;

public sealed class RoundStartedEvent : EntityEventArgs
{
public int RoundId { get; }

public RoundStartedEvent(int roundId)
{
RoundId = roundId;
}
}
2 changes: 2 additions & 0 deletions Content.Server/GameTicking/GameTicker.RoundFlow.cs
Original file line number Diff line number Diff line change
Expand Up @@ -255,6 +255,7 @@ public void StartRound(bool force = false)
AnnounceRound();
UpdateInfoText();
SendRoundStartedDiscordMessage();
RaiseLocalEvent(new RoundStartedEvent(RoundId));

#if EXCEPTION_TOLERANCE
}
Expand Down Expand Up @@ -402,6 +403,7 @@ public void ShowRoundEndScoreboard(string text = "")

_replayRoundPlayerInfo = listOfPlayerInfoFinal;
_replayRoundText = roundEndText;
RaiseLocalEvent(new RoundEndedEvent(RoundId, roundDuration));
}

private async void SendRoundEndDiscordMessage()
Expand Down
55 changes: 55 additions & 0 deletions Content.Server/StationGoal/StationGoalCommand.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,55 @@
using System.Linq;
using Content.Server.Administration;
using Content.Shared.Administration;
using Robust.Shared.Console;
using Robust.Shared.Prototypes;

namespace Content.Server.StationGoal
{
[AdminCommand(AdminFlags.Fun)]
public sealed class StationGoalCommand : IConsoleCommand
{
public string Command => "sendstationgoal";
public string Description => Loc.GetString("send-station-goal-command-description");
public string Help => Loc.GetString("send-station-goal-command-help-text", ("command", Command));

public void Execute(IConsoleShell shell, string argStr, string[] args)
{
if (args.Length != 1)
{
shell.WriteError(Loc.GetString("shell-need-exactly-one-argument"));
return;
}

var protoId = args[0];
var prototypeManager = IoCManager.Resolve<IPrototypeManager>();
if (!prototypeManager.TryIndex<StationGoalPrototype>(protoId, out var proto))
{
shell.WriteError(Loc.GetString("send-station-goal-command-error-no-goal-proto", ("id", protoId)));
return;
}

var stationGoalPaper = IoCManager.Resolve<IEntityManager>().System<StationGoalPaperSystem>();
if (!stationGoalPaper.SendStationGoal(proto))
{
shell.WriteError(Loc.GetString("send-station-goal-command-error-couldnt-fax"));
return;
}
}

public CompletionResult GetCompletion(IConsoleShell shell, string[] args)
{
if (args.Length == 1)
{
var options = IoCManager.Resolve<IPrototypeManager>()
.EnumeratePrototypes<StationGoalPrototype>()
.OrderBy(p => p.ID)
.Select(p => new CompletionOption(p.ID));

return CompletionResult.FromHintOptions(options, Loc.GetString("send-station-goal-command-arg-id"));
}

return CompletionResult.Empty;
}
}
}
9 changes: 9 additions & 0 deletions Content.Server/StationGoal/StationGoalPaperComponent.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
namespace Content.Server.StationGoal
{
/// <summary>
/// Paper with a written station goal in it.
/// </summary>
[RegisterComponent]
public sealed partial class StationGoalPaperComponent : Component { }
}

118 changes: 118 additions & 0 deletions Content.Server/StationGoal/StationGoalPaperSystem.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,118 @@
using System.Text.RegularExpressions;
using Content.Server.GameTicking;
using Content.Server.Fax;
using Content.Server.Station.Systems;
using Content.Shared.CCVar;
using Content.Shared.Random;
using Content.Shared.Random.Helpers;
using Robust.Shared.Configuration;
using Robust.Shared.Prototypes;
using Robust.Shared.Random;
using Content.Shared.Dataset;

namespace Content.Server.StationGoal;

/// <summary>
/// System for station goals
/// </summary>
public sealed class StationGoalPaperSystem : EntitySystem
{
[Dependency] private readonly IPrototypeManager _prototype = default!;
[Dependency] private readonly IRobustRandom _random = default!;
[Dependency] private readonly FaxSystem _fax = default!;
[Dependency] private readonly IConfigurationManager _config = default!;
[Dependency] private readonly StationSystem _station = default!;

private static readonly Regex StationIdRegex = new(@".*-(\d+)$");

[ValidatePrototypeId<WeightedRandomPrototype>]
private const string RandomPrototype = "StationGoals";
[ValidatePrototypeId<DatasetPrototype>]
private const string RandomSignature = "names_last";

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

SubscribeLocalEvent<RoundStartedEvent>(OnRoundStarted);
}


private void OnRoundStarted(RoundStartedEvent ev)
{
if (_config.GetCVar(CCVars.StationGoalsEnabled)
&& _random.Prob(_config.GetCVar(CCVars.StationGoalsChance)))
SendRandomGoal();
}

/// <summary>
/// Send a random station goal to all faxes which are authorized to receive it
/// </summary>
/// <returns>If the fax was successful</returns>
/// <exception cref="Exception">Raised when station goal types in the prototype is invalid</exception>
public bool SendRandomGoal()
{
// Get the random station goal list
if (!_prototype.TryIndex<WeightedRandomPrototype>(RandomPrototype, out var goals))
{
Log.Error($"StationGoalPaperSystem: Random station goal prototype '{RandomPrototype}' not found");
return false;
}

// Get a random goal
var goal = RecursiveRandom(goals);

// Send the goal
return SendStationGoal(goal);
}

private StationGoalPrototype RecursiveRandom(WeightedRandomPrototype random)
{
var goal = random.Pick(_random);

if (_prototype.TryIndex<StationGoalPrototype>(goal, out var goalPrototype))
return goalPrototype;

if (_prototype.TryIndex<WeightedRandomPrototype>(goal, out var goalRandom))
return RecursiveRandom(goalRandom);

throw new Exception($"StationGoalPaperSystem: Random station goal could not be found from prototypes {RandomPrototype} and {random.ID}");
}

/// <summary>
/// Send a station goal to all faxes which are authorized to receive it
/// </summary>
/// <returns>True if at least one fax received paper</returns>
public bool SendStationGoal(StationGoalPrototype goal)
{
var enumerator = EntityManager.EntityQueryEnumerator<FaxMachineComponent>();
var wasSent = false;
var signerName = _prototype.Index<DatasetPrototype>(RandomSignature);

while (enumerator.MoveNext(out var uid, out var fax))
{
if (!fax.ReceiveStationGoal
|| !TryComp<MetaDataComponent>(_station.GetOwningStation(uid), out var meta))
continue;

var stationId = StationIdRegex.Match(meta.EntityName).Groups[1].Value;

var printout = new FaxPrintout(
Loc.GetString("station-goal-fax-paper-header",
("date", DateTime.Now.AddYears(1000).ToString("yyyy MMMM dd")),
("station", string.IsNullOrEmpty(stationId) ? "???" : stationId),
("content", goal.Text),
("name", _random.Pick(signerName.Values))
),
Loc.GetString("station-goal-fax-paper-name"),
"StationGoalPaper"
);

_fax.Receive(uid, printout, null, fax);

wasSent = true;
}

return wasSent;
}
}
12 changes: 12 additions & 0 deletions Content.Server/StationGoal/StationGoalPrototype.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
using Robust.Shared.Prototypes;

namespace Content.Server.StationGoal
{
[Serializable, Prototype("stationGoal")]
public sealed class StationGoalPrototype : IPrototype
{
[IdDataFieldAttribute] public string ID { get; } = default!;

public string Text => Loc.GetString($"station-goal-{ID.ToLower()}");
}
}
12 changes: 12 additions & 0 deletions Content.Shared/CCVar/CCVars.cs
Original file line number Diff line number Diff line change
Expand Up @@ -2106,5 +2106,17 @@ public static readonly CVarDef<float>
/// </summary>
public static readonly CVarDef<bool> PsionicRollsEnabled =
CVarDef.Create("psionics.rolls_enabled", true, CVar.SERVERONLY);

/// <summary>
/// Enables station goals
/// </summary>
public static readonly CVarDef<bool> StationGoalsEnabled =
CVarDef.Create("game.station_goals", true, CVar.SERVERONLY);

/// <summary>
/// Chance for a station goal to be sent
/// </summary>
public static readonly CVarDef<float> StationGoalsChance =
CVarDef.Create("game.station_goals_chance", 0.1f, CVar.SERVERONLY);
}
}
6 changes: 6 additions & 0 deletions Resources/Locale/en-US/station-goal/station-goal-command.ftl
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
send-station-goal-command-description = Sends the selected station target to all faxes that can receive it
send-station-goal-command-help-text = Usage: { $command } <Goal Prototype ID>
send-station-goal-command-arg-id = Goal Prototype ID
send-station-goal-command-error-no-goal-proto = No station goal found with ID {$id}
send-station-goal-command-error-couldnt-fax = Couldn't send station goal, probably due to a lack of fax machines that are able to recieve it
Loading

0 comments on commit bc93199

Please sign in to comment.