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

New psionic ablility: Precognition #2131

Merged
merged 59 commits into from
Dec 19, 2024
Merged
Show file tree
Hide file tree
Changes from 54 commits
Commits
Show all changes
59 commits
Select commit Hold shift + click to select a range
0345313
TESTING enable events for dev enviroment
ewokswagger Nov 6, 2024
795db58
Merge branch 'DeltaV-Station:master' into seer
ewokswagger Nov 8, 2024
ac2c80a
Add NextEventComponent
ewokswagger Nov 8, 2024
2ca3935
Check for schedulers NextEventComponent
ewokswagger Nov 9, 2024
3247967
Seperate gernateing event to its own method
ewokswagger Nov 9, 2024
ddbbda5
Add NextEventSystem and use in BasicStationEventSchedulerSystem
ewokswagger Nov 11, 2024
9f812b7
TESTING: Override time and player restrictions
ewokswagger Nov 11, 2024
195ab96
Stash events in NextEventComponent (#1)
ewokswagger Nov 11, 2024
e53253f
Merge branch 'DeltaV-Station:master' into seer
ewokswagger Nov 11, 2024
aa6cf42
Format code
ewokswagger Nov 12, 2024
4c857ff
Add nextEvent time perdiction
ewokswagger Nov 12, 2024
5d00ef6
Use RunTime instead of float minutes
ewokswagger Nov 12, 2024
1495cb1
Bug fixes
ewokswagger Nov 12, 2024
84903f6
Add NextEvent to Ramping and Meteors
ewokswagger Nov 12, 2024
83267fd
Fix timing on BasticStationEvents
ewokswagger Nov 12, 2024
36f45be
initialize NextEventComponent when created
ewokswagger Nov 12, 2024
656ca26
Merge branch 'seer' into stash-next-event
ewokswagger Nov 12, 2024
bf9cd26
Event scheduler caching (#2)
ewokswagger Nov 12, 2024
a3c2ddf
Merge branch 'DeltaV-Station:master' into seer
ewokswagger Nov 12, 2024
82678d9
Revert "Event scheduler caching (#2)"
ewokswagger Nov 12, 2024
1d2379b
Revert "Merge branch 'seer' into stash-next-event"
ewokswagger Nov 12, 2024
9f1bee4
Caching next exent
ewokswagger Nov 12, 2024
629867d
Revert "Caching next exent"
ewokswagger Nov 12, 2024
145b801
Reapply "Event scheduler caching (#2)"
ewokswagger Nov 13, 2024
14944b6
More merge conflict nonsence
ewokswagger Nov 13, 2024
43c633e
oops
ewokswagger Nov 13, 2024
742c924
oops 2
ewokswagger Nov 13, 2024
828145e
Oops 3
ewokswagger Nov 13, 2024
d6c3e29
Precognition Psionic ability (#3)
ewokswagger Nov 15, 2024
e400376
Merge branch 'DeltaV-Station:master' into seer
ewokswagger Nov 15, 2024
3e7e9e4
Merge branch 'seer' of https://github.com/ewokswagger/Delta-v into seer
ewokswagger Nov 15, 2024
08dd3f3
Use Timespan for UseDelay
ewokswagger Nov 15, 2024
dd5dc29
Damage breaks doafter
ewokswagger Nov 15, 2024
4219ad7
typo
ewokswagger Nov 15, 2024
a6db1c6
fix localization
ewokswagger Nov 15, 2024
6b6c94c
fix do after
ewokswagger Nov 15, 2024
5365316
Add effects durring do after
ewokswagger Nov 16, 2024
aadb8dd
Revert "TESTING enable events for dev enviroment"
ewokswagger Nov 16, 2024
62377c6
Revert testing changes
ewokswagger Nov 16, 2024
78098c3
add deltav comments
ewokswagger Nov 16, 2024
64d873e
Cleaning up!
ewokswagger Nov 17, 2024
8afac5e
Move NextEvent to server space
ewokswagger Nov 17, 2024
67a7bce
Fix NextEventId init value
ewokswagger Nov 18, 2024
53ef062
Merge branch 'DeltaV-Station:master' into seer
ewokswagger Nov 18, 2024
f8faf4a
Reverted upstream file to block scoped namespace
ewokswagger Nov 18, 2024
e560b61
Add precognitnon result messages
ewokswagger Nov 19, 2024
04a5806
reverting testing changes for real
ewokswagger Nov 19, 2024
1cc4305
Add admin alert for upcoming events
ewokswagger Nov 19, 2024
e7aa83c
Add sound effect
ewokswagger Nov 20, 2024
a2bff89
make alert more subtule
ewokswagger Nov 20, 2024
4519e7c
extended max window size
ewokswagger Nov 20, 2024
9bc248c
fix message mixup
ewokswagger Nov 20, 2024
dda0051
yaml fixes
ewokswagger Nov 20, 2024
6f12d96
more yaml fixes
ewokswagger Nov 20, 2024
a4d0b44
Delta Changes
ewokswagger Dec 11, 2024
343fa47
Merge branch 'master' into seer
ewokswagger Dec 11, 2024
56b78d3
totaly a yaml error trust
ewokswagger Dec 11, 2024
72f5ab7
Merge branch 'seer' of https://github.com/ewokswagger/Delta-v into seer
ewokswagger Dec 11, 2024
11b7c67
remove unsessesary weights
ewokswagger Dec 11, 2024
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
222 changes: 222 additions & 0 deletions Content.Server/DeltaV/Abilities/Psionics/PrecognitionPowerSystem.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,222 @@
using Content.Server.Chat.Managers;
using Content.Server.DoAfter;
using Content.Server.GameTicking;
using Content.Server.Mind;
using Content.Server.StationEvents.NextEvent;
using Content.Shared.Abilities.Psionics;
using Content.Shared.Actions.Events;
using Content.Shared.Actions;
using Content.Shared.DoAfter;
using Content.Shared.Eye.Blinding.Components;
using Content.Shared.Popups;
using Content.Shared.Psionics.Events;
using Content.Shared.StatusEffect;
using Content.Shared.Stunnable;
using Robust.Shared.Audio.Systems;
using Robust.Shared.Prototypes;
using Robust.Shared.Random;
using Robust.Shared.Timing;
using Robust.Shared.Player;

namespace Content.Server.Abilities.Psionics;

public sealed class PrecognitionPowerSystem : EntitySystem
{
[Dependency] private readonly DoAfterSystem _doAfterSystem = 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 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 IRobustRandom _random = default!;

public override void Initialize()
{
base.Initialize();
SubscribeLocalEvent<PrecognitionPowerComponent, ComponentInit>(OnInit);
SubscribeLocalEvent<PrecognitionPowerComponent, ComponentShutdown>(OnShutdown);
SubscribeLocalEvent<PrecognitionPowerComponent, PrecognitionPowerActionEvent>(OnPowerUsed);
SubscribeLocalEvent<PrecognitionPowerComponent, PrecognitionDoAfterEvent>(OnDoAfter);
}

private void OnInit(EntityUid uid, PrecognitionPowerComponent component, ComponentInit args)
ewokswagger marked this conversation as resolved.
Show resolved Hide resolved
{
_actions.AddAction(uid, ref component.PrecognitionActionEntity, component.PrecognitionActionId);
_actions.TryGetActionData(component.PrecognitionActionEntity, out var actionData);
if (actionData is { UseDelay: not null })
ewokswagger marked this conversation as resolved.
Show resolved Hide resolved
_actions.StartUseDelay(component.PrecognitionActionEntity);
if (TryComp<PsionicComponent>(uid, out var psionic) && psionic.PsionicAbility == null)
{
psionic.PsionicAbility = component.PrecognitionActionEntity;
psionic.ActivePowers.Add(component);
}
}

private void OnShutdown(EntityUid uid, PrecognitionPowerComponent component, ComponentShutdown args)
{
_actions.RemoveAction(uid, component.PrecognitionActionEntity);
if (TryComp<PsionicComponent>(uid, out var psionic))
psionic.ActivePowers.Remove(component);
}

private void OnPowerUsed(EntityUid uid, PrecognitionPowerComponent component, PrecognitionPowerActionEvent args)
{
var ev = new PrecognitionDoAfterEvent(_gameTiming.CurTime);
var doAfterArgs = new DoAfterArgs(EntityManager, uid, component.UseDelay, ev, uid)
{
BreakOnDamage = true
};

// A custom shader for seeing visions would be nice but this will do for now.
_statusEffects.TryAddStatusEffect<TemporaryBlindnessComponent>(uid, "TemporaryBlindness", component.UseDelay, true);
_statusEffects.TryAddStatusEffect<SlowedDownComponent>(uid, "SlowedDown", component.UseDelay, true);

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

var player = _audio.PlayGlobal(component.VisionSound, Filter.Entities(uid), true);
if (player != null)
component.SoundStream = player.Value.Entity;
_psionics.LogPowerUsed(uid, "Precognition");
args.Handled = true;
}

/// <summary>
/// Upon completion will send a message to the user corrosponding to the next station event to occour.
/// </summary>
/// <param name="uid"></param>
/// <param name="component"></param>
/// <param name="args"></param>
private void OnDoAfter(EntityUid uid, PrecognitionPowerComponent component, PrecognitionDoAfterEvent args)
{
if (args.Handled)
return;

if (args.Cancelled)
{
// Need to clean up the applied effects in case of cancel and alert the player.
component.SoundStream = _audio.Stop(component.SoundStream);
_statusEffects.TryRemoveStatusEffect(uid, "TemporaryBlindness");
_statusEffects.TryRemoveStatusEffect(uid, "SlowedDown");
_popups.PopupEntity(Loc.GetString("psionic-power-precognition-failure-by-damage"), uid, uid, PopupType.SmallCaution);
return;
}

// 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;

if (!TryFindEarliestNextEvent(minDetectWindow, maxDetectWindow, out var nextEvent)) // 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);

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.
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);

component.DoAfter = null;
}

/// <summary>
/// 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)
{
foreach(var (eventProto, precognitionResult) in AllPrecognitionResults())
ewokswagger marked this conversation as resolved.
Show resolved Hide resolved
if (eventProto.ID == eventId && precognitionResult != null)
return precognitionResult.Message;
Log.Warning("Prototype " + eventId + "does not have an associated precognitionResult!");
ewokswagger marked this conversation as resolved.
Show resolved Hide resolved
return string.Empty;
}

/// <summary>
/// </summary>
/// <returns>The localized string of a weighted randomly chosen precognition result</returns>
public string? GetRandomResult()
{
var precognitionResults = AllPrecognitionResults();
deltanedas marked this conversation as resolved.
Show resolved Hide resolved
var sumOfWeights = 0;
foreach (var precognitionResult in precognitionResults.Values)
sumOfWeights += (int)precognitionResult.Weight;

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

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

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

/// <summary>
/// Gets the soonest nextEvent to occur within the window.
/// </summary>
/// <param name="minDetectWindow"></param> The earliest reletive time that will be return a nextEvent
/// <param name="maxDetectWindow"></param> The latest reletive latest time that will be return a nextEvent
/// <param name="earliestNextEvent"></param> The next nextEvent to occur within the window
/// <returns>True if event was found in timeframe false otherwise.</returns>
private bool TryFindEarliestNextEvent(TimeSpan minDetectWindow, TimeSpan maxDetectWindow, out NextEventComponent? earliestNextEvent)
ewokswagger marked this conversation as resolved.
Show resolved Hide resolved
{
TimeSpan? earliestNextEventTime = null;
earliestNextEvent = null;
var query = EntityQueryEnumerator<NextEventComponent>();
while (query.MoveNext(out _, out var nextEventComponent))
ewokswagger marked this conversation as resolved.
Show resolved Hide resolved
{
// Update if the event is the most recent event that isnt too close or too far from happening to be of use
if (nextEventComponent.NextEventTime > _gameTicker.RoundDuration() + minDetectWindow
&& nextEventComponent.NextEventTime < _gameTicker.RoundDuration() + maxDetectWindow
&& earliestNextEvent == null
|| nextEventComponent.NextEventTime < earliestNextEventTime)
earliestNextEvent ??= nextEventComponent;
}
if (earliestNextEvent == null)
return false;
return true;
}

public Dictionary<EntityPrototype, PrecognitionResultComponent> AllPrecognitionResults()
deltanedas marked this conversation as resolved.
Show resolved Hide resolved
{
var allEvents = new Dictionary<EntityPrototype, PrecognitionResultComponent>();
foreach (var prototype in _prototype.EnumeratePrototypes<EntityPrototype>())
{
if (prototype.Abstract)
continue;

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

allEvents.Add(prototype, precognitionResult);
}

return allEvents;
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
using Robust.Shared.Prototypes;

namespace Content.Server.StationEvents.NextEvent;
ewokswagger marked this conversation as resolved.
Show resolved Hide resolved

[RegisterComponent, Access(typeof(NextEventSystem))]
public sealed partial class NextEventComponent : Component
{
/// <summary>
/// Id of the next event that will be run by EventManagerSystem.
ewokswagger marked this conversation as resolved.
Show resolved Hide resolved
/// </summary>
[DataField]
public EntProtoId? NextEventId { get; set; } = default!;
ewokswagger marked this conversation as resolved.
Show resolved Hide resolved

/// <summary>
/// Round time of the scheduler's next station event.
ewokswagger marked this conversation as resolved.
Show resolved Hide resolved
/// </summary>
[DataField]
public TimeSpan NextEventTime = default!;
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
using Robust.Shared.Prototypes;

namespace Content.Server.StationEvents.NextEvent;
ewokswagger marked this conversation as resolved.
Show resolved Hide resolved

public sealed partial class NextEventSystem : EntitySystem
ewokswagger marked this conversation as resolved.
Show resolved Hide resolved
{
/// <summary>
/// Updates the NextEventComponent with the provided id and time and returns the previously stored id.
/// </summary>
public EntProtoId? UpdateNextEvent(NextEventComponent component, EntProtoId newEventId, TimeSpan newEventTime)
{
EntProtoId? oldEventId = component.NextEventId; // Store components current NextEventId for return
component.NextEventId = newEventId;
component.NextEventTime = newEventTime;
return oldEventId;
}
}
Original file line number Diff line number Diff line change
@@ -1,14 +1,17 @@
using System.Linq;
using Content.Server.Administration;
using Content.Server.Chat.Managers; // DeltaV
using Content.Server.GameTicking;
using Content.Server.GameTicking.Rules;
using Content.Server.StationEvents.Components;
using Content.Server.StationEvents.NextEvent; // DeltaV
using Content.Shared.Administration;
using Content.Shared.EntityTable;
using Content.Shared.GameTicking.Components;
using JetBrains.Annotations;
using Robust.Shared.Prototypes;
using Robust.Shared.Random;
using Robust.Shared.Timing; // DeltaV
using Robust.Shared.Toolshed;
using Robust.Shared.Utility;

Expand All @@ -21,14 +24,28 @@ namespace Content.Server.StationEvents
[UsedImplicitly]
public sealed class BasicStationEventSchedulerSystem : GameRuleSystem<BasicStationEventSchedulerComponent>
{
[Dependency] private readonly IChatManager _chatManager = default!; // DeltaV
[Dependency] private readonly IGameTiming _timing = default!; // DeltaV
deltanedas marked this conversation as resolved.
Show resolved Hide resolved
[Dependency] private readonly IRobustRandom _random = default!;
[Dependency] private readonly EventManagerSystem _event = default!;
[Dependency] private readonly NextEventSystem _next = default!; // DeltaV

protected override void Started(EntityUid uid, BasicStationEventSchedulerComponent component, GameRuleComponent gameRule,
GameRuleStartedEvent args)
{
// A little starting variance so schedulers dont all proc at once.
component.TimeUntilNextEvent = RobustRandom.NextFloat(component.MinimumTimeUntilFirstEvent, component.MinimumTimeUntilFirstEvent + 120);

// DeltaV - end init NextEventComp
NextEventComponent? nextEventComponent = null;
if (Resolve(uid, ref nextEventComponent, false)
ewokswagger marked this conversation as resolved.
Show resolved Hide resolved
&& _event.TryGenerateRandomEvent(component.ScheduledGameRules, out string? firstEvent, TimeSpan.FromSeconds(component.TimeUntilNextEvent))
&& firstEvent != null)
{
_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
}

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

// DeltaV events using NextEventComponent
NextEventComponent? nextEventComponent = null;
if (Resolve(uid, ref nextEventComponent, false)) // If there is a nextEventComponent use the stashed event instead of running it directly.
ewokswagger marked this conversation as resolved.
Show resolved Hide resolved
{
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) || generatedEvent == null)
ewokswagger marked this conversation as resolved.
Show resolved Hide resolved
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);
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

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