Skip to content

Commit

Permalink
full cleanup of precognition
Browse files Browse the repository at this point in the history
  • Loading branch information
deltanedas committed Dec 19, 2024
1 parent a8099f2 commit 11b219f
Show file tree
Hide file tree
Showing 11 changed files with 134 additions and 163 deletions.
105 changes: 59 additions & 46 deletions Content.Server/DeltaV/Abilities/Psionics/PrecognitionPowerSystem.cs
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@
using Content.Shared.Abilities.Psionics;
using Content.Shared.Actions.Events;
using Content.Shared.Actions;
using Content.Shared.Chat;
using Content.Shared.DoAfter;
using Content.Shared.Eye.Blinding.Components;
using Content.Shared.Popups;
Expand All @@ -22,32 +23,38 @@ namespace Content.Server.Abilities.Psionics;

public sealed class PrecognitionPowerSystem : EntitySystem
{
[Dependency] private readonly DoAfterSystem _doAfterSystem = default!;
[Dependency] private readonly DoAfterSystem _doAfter = default!;
[Dependency] private readonly GameTicker _gameTicker = default!;
[Dependency] private readonly MindSystem _mind = default!;
[Dependency] private readonly SharedActionsSystem _actions = default!;
[Dependency] private readonly SharedAudioSystem _audio = default!;
[Dependency] private readonly SharedPopupSystem _popups = default!;
[Dependency] private readonly SharedPopupSystem _popup = default!;
[Dependency] private readonly SharedPsionicAbilitiesSystem _psionics = default!;
[Dependency] private readonly StatusEffectsSystem _statusEffects = default!;
[Dependency] private readonly IChatManager _chat = default!;
[Dependency] private readonly IComponentFactory _factory = default!;
[Dependency] private readonly IGameTiming _gameTiming = default!;
[Dependency] private readonly IPrototypeManager _prototype = default!;
[Dependency] private readonly IPrototypeManager _proto = default!;
[Dependency] private readonly IRobustRandom _random = default!;

/// <summary>
/// A map between game rule prototypes and their results to give.
/// </summary>
public Dictionary<EntProtoId, PrecognitionResultComponent> Results = new();

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

SubscribeLocalEvent<PrecognitionPowerComponent, MapInitEvent>(OnMapInit);
SubscribeLocalEvent<PrecognitionPowerComponent, ComponentShutdown>(OnShutdown);
SubscribeLocalEvent<PrecognitionPowerComponent, PrecognitionPowerActionEvent>(OnPowerUsed);
SubscribeLocalEvent<PrecognitionPowerComponent, PrecognitionDoAfterEvent>(OnDoAfter);
SubscribeLocalEvent<PrototypesReloadedEventArgs>(OnPrototypesReloaded);
}

private void OnMapInit(Entity<PrecognitionPowerComponent> ent, ref MapInitEvent args)
{
ent.Comp.AllResults = GetAllPrecognitionResults();
_actions.AddAction(ent, ref ent.Comp.PrecognitionActionEntity, ent.Comp.PrecognitionActionId);
_actions.StartUseDelay(ent.Comp.PrecognitionActionEntity);
if (TryComp<PsionicComponent>(ent, out var psionic) && psionic.PsionicAbility == null)
Expand All @@ -66,7 +73,7 @@ private void OnShutdown(EntityUid uid, PrecognitionPowerComponent component, Com

private void OnPowerUsed(EntityUid uid, PrecognitionPowerComponent component, PrecognitionPowerActionEvent args)
{
var ev = new PrecognitionDoAfterEvent(_gameTiming.CurTime);
var ev = new PrecognitionDoAfterEvent();
var doAfterArgs = new DoAfterArgs(EntityManager, uid, component.UseDelay, ev, uid)
{
BreakOnDamage = true
Expand All @@ -76,7 +83,7 @@ private void OnPowerUsed(EntityUid uid, PrecognitionPowerComponent component, Pr
_statusEffects.TryAddStatusEffect<TemporaryBlindnessComponent>(uid, "TemporaryBlindness", component.UseDelay, true);
_statusEffects.TryAddStatusEffect<SlowedDownComponent>(uid, "SlowedDown", component.UseDelay, true);

_doAfterSystem.TryStartDoAfter(doAfterArgs, out var doAfterId);
_doAfter.TryStartDoAfter(doAfterArgs, out var doAfterId);
component.DoAfter = doAfterId;

var player = _audio.PlayGlobal(component.VisionSound, Filter.Entities(uid), true);
Expand Down Expand Up @@ -104,7 +111,7 @@ private void OnDoAfter(EntityUid uid, PrecognitionPowerComponent component, Prec
_statusEffects.TryRemoveStatusEffect(uid, "TemporaryBlindness");
_statusEffects.TryRemoveStatusEffect(uid, "SlowedDown");

_popups.PopupEntity(
_popup.PopupEntity(
Loc.GetString("psionic-power-precognition-failure-by-damage"),
uid,
uid,
Expand All @@ -121,33 +128,31 @@ private void OnDoAfter(EntityUid uid, PrecognitionPowerComponent component, Prec
// Determines the window that will be looked at for events, avoiding events that are too close or too far to be useful.
var minDetectWindow = TimeSpan.FromSeconds(30);
var maxDetectWindow = TimeSpan.FromMinutes(10);
string? message = null;

if (!_mind.TryGetMind(uid, out _, out var mindComponent) || mindComponent.Session == null)
return;

var nextEvent = (FindEarliestNextEvent(minDetectWindow, maxDetectWindow));
if (nextEvent == null) // A special message given if there is no event within the time window.
message = "psionic-power-precognition-no-event-result-message";

if (nextEvent != null && nextEvent.NextEventId != null)
message = GetResultMessage(nextEvent.NextEventId, component);
var nextEvent = FindEarliestNextEvent(minDetectWindow, maxDetectWindow);
LocId? message = nextEvent?.NextEventId is {} nextEventId
? GetResultMessage(nextEventId)
// A special message given if there is no event within the time window.
: "psionic-power-precognition-no-event-result-message";

if (_random.Prob(component.RandomResultChance)) // This will replace the proper result message with a random one occasionaly to simulate some unreliablity.
message = GetRandomResult();

if (string.IsNullOrEmpty(message)) // If there is no message to send don't bother trying to send it.
if (message is not {} locId) // If there is no message to send don't bother trying to send it.
return;

// Send a message describing the vision they see
message = Loc.GetString(message);
_chat.ChatMessageToOne(Shared.Chat.ChatChannel.Server,
message,
Loc.GetString("chat-manager-server-wrap-message", ("message", message)),
uid,
false,
mindComponent.Session.Channel,
Color.PaleVioletRed);
var msg = Loc.GetString(locId);
_chat.ChatMessageToOne(ChatChannel.Server,
msg,
Loc.GetString("chat-manager-server-wrap-message", ("message", message)),
uid,
false,
mindComponent.Session.Channel,
Color.PaleVioletRed);

component.DoAfter = null;
}
Expand All @@ -156,37 +161,37 @@ private void OnDoAfter(EntityUid uid, PrecognitionPowerComponent component, Prec
/// Gets the precognition result message corosponding to the passed event id.
/// </summary>
/// <returns>message string corosponding to the event id passed</returns>
private string GetResultMessage(EntProtoId? eventId, PrecognitionPowerComponent component)
private LocId GetResultMessage(EntProtoId eventId)
{
foreach (var (eventProto, precognitionResult) in component.AllResults)
if (!Results.TryGetValue(eventId, out var result))
{
if (eventProto.ID == eventId && precognitionResult != null)
return precognitionResult.Message;
Log.Error($"Prototype {eventId} does not have an associated precognitionResult!");
return string.Empty;
}
Log.Error($"Prototype {eventId} does not have an associated precognitionResult!");
return string.Empty;

return result.Message;
}

/// <summary>
/// </summary>
/// <returns>The localized string of a weighted randomly chosen precognition result</returns>
public string? GetRandomResult()
/// <returns>The locale message id of a weighted randomly chosen precognition result</returns>
public LocId? GetRandomResult()
{
var precognitionResults = GetAllPrecognitionResults();
var sumOfWeights = 0;
foreach (var precognitionResult in precognitionResults.Values)
sumOfWeights += (int)precognitionResult.Weight;
// funny weighted random
var sumOfWeights = 0f;
foreach (var precognitionResult in Results.Values)
sumOfWeights += precognitionResult.Weight;

sumOfWeights = _random.Next(sumOfWeights);
foreach (var precognitionResult in precognitionResults.Values)
sumOfWeights = (float) _random.Next((double) sumOfWeights);
foreach (var precognitionResult in Results.Values)
{
sumOfWeights -= (int)precognitionResult.Weight;
sumOfWeights -= precognitionResult.Weight;

if (sumOfWeights <= 0)
if (sumOfWeights <= 0f)
return precognitionResult.Message;
}

Log.Error("Result was not found after weighted pick process!");
Log.Error("Precognition result was not found after weighted pick process!");
return null;
}

Expand All @@ -208,25 +213,33 @@ private string GetResultMessage(EntProtoId? eventId, PrecognitionPowerComponent
&& nextEventComponent.NextEventTime < _gameTicker.RoundDuration() + maxDetectWindow
&& earliestNextEvent == null
|| nextEventComponent.NextEventTime < earliestNextEventTime)
{
earliestNextEvent ??= nextEventComponent;
}
}
return earliestNextEvent;
}

public Dictionary<EntityPrototype, PrecognitionResultComponent> GetAllPrecognitionResults()
private void OnPrototypesReloaded(PrototypesReloadedEventArgs args)
{
var allEvents = new Dictionary<EntityPrototype, PrecognitionResultComponent>();
foreach (var prototype in _prototype.EnumeratePrototypes<EntityPrototype>())
if (!args.WasModified<EntityPrototype>())
return;

CachePrecognitionResults();
}

private void CachePrecognitionResults()
{
Results.Clear();
foreach (var prototype in _proto.EnumeratePrototypes<EntityPrototype>())
{
if (prototype.Abstract)
continue;

if (!prototype.TryGetComponent<PrecognitionResultComponent>(out var precognitionResult, _factory))
continue;

allEvents.Add(prototype, precognitionResult);
Results.Add(prototype.ID, precognitionResult);
}

return allEvents;
}
}
Original file line number Diff line number Diff line change
@@ -1,8 +1,10 @@
using Robust.Shared.Prototypes;
using Robust.Shared.Serialization.TypeSerializers.Implementations.Custom;

namespace Content.Server.DeltaV.StationEvents.NextEvent;

[RegisterComponent, Access(typeof(NextEventSystem))]
[AutoGenerateComponentPause]
public sealed partial class NextEventComponent : Component
{
/// <summary>
Expand All @@ -14,6 +16,6 @@ public sealed partial class NextEventComponent : Component
/// <summary>
/// Round time of the scheduler's next station event.
/// </summary>
[DataField]
[DataField(customTypeSerializer: typeof(TimeOffsetSerializer)), AutoPausedField]
public TimeSpan NextEventTime;
}
17 changes: 9 additions & 8 deletions Content.Server/StationEvents/BasicStationEventSchedulerSystem.cs
Original file line number Diff line number Diff line change
Expand Up @@ -36,15 +36,14 @@ protected override void Started(EntityUid uid, BasicStationEventSchedulerCompone
// A little starting variance so schedulers dont all proc at once.
component.TimeUntilNextEvent = RobustRandom.NextFloat(component.MinimumTimeUntilFirstEvent, component.MinimumTimeUntilFirstEvent + 120);

// DeltaV - end init NextEventComp
// Begin DeltaV Additions: init NextEventComp
if (TryComp<NextEventComponent>(uid, out var nextEventComponent)
&& _event.TryGenerateRandomEvent(component.ScheduledGameRules, out string? firstEvent, TimeSpan.FromSeconds(component.TimeUntilNextEvent))
&& firstEvent != null)
&& _event.TryGenerateRandomEvent(component.ScheduledGameRules, TimeSpan.FromSeconds(component.TimeUntilNextEvent)) is {} firstEvent)
{
_chatManager.SendAdminAlert(Loc.GetString("station-event-system-run-event-delayed", ("eventName", firstEvent), ("seconds", (int)component.TimeUntilNextEvent)));
_next.UpdateNextEvent(nextEventComponent, firstEvent, TimeSpan.FromSeconds(component.TimeUntilNextEvent));
}
// DeltaV - end init NextEventComp
// End DeltaV Additions
}

protected override void Ended(EntityUid uid, BasicStationEventSchedulerComponent component, GameRuleComponent gameRule,
Expand Down Expand Up @@ -73,22 +72,24 @@ public override void Update(float frameTime)
continue;
}

// DeltaV events using NextEventComponent
// Begin DeltaV Additions: events using NextEventComponent
if (TryComp<NextEventComponent>(uid, out var nextEventComponent)) // If there is a nextEventComponent use the stashed event instead of running it directly.
{
ResetTimer(eventScheduler); // Time needs to be reset ahead of time since we need to chose events based on the next time it will run.
var nextEventTime = _timing.CurTime + TimeSpan.FromSeconds(eventScheduler.TimeUntilNextEvent);
if (!_event.TryGenerateRandomEvent(eventScheduler.ScheduledGameRules, out string? generatedEvent, nextEventTime))
if (_event.TryGenerateRandomEvent(eventScheduler.ScheduledGameRules, nextEventTime) is not {} generatedEvent)
continue;

_chatManager.SendAdminAlert(Loc.GetString("station-event-system-run-event-delayed", ("eventName", generatedEvent), ("seconds", (int)eventScheduler.TimeUntilNextEvent)));
// Cycle the stashed event with the new generated event and time.
string? storedEvent = _next.UpdateNextEvent(nextEventComponent, generatedEvent, nextEventTime);
var storedEvent = _next.UpdateNextEvent(nextEventComponent, generatedEvent, nextEventTime);
if (string.IsNullOrEmpty(storedEvent)) //If there was no stored event don't try to run it.
continue;

GameTicker.AddGameRule(storedEvent);
continue;
}
// DeltaV end events using NextEventComponent
// End DeltaV Additions: events using NextEventComponent

_event.RunRandomEvent(eventScheduler.ScheduledGameRules);
ResetTimer(eventScheduler);
Expand Down
Loading

0 comments on commit 11b219f

Please sign in to comment.