Skip to content

Commit

Permalink
Merge branch 'DeltaV-Station:master' into Byoin
Browse files Browse the repository at this point in the history
  • Loading branch information
FieldCommand authored Dec 26, 2024
2 parents b04f52f + 85e7c83 commit d391926
Show file tree
Hide file tree
Showing 43 changed files with 687 additions and 505 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -86,6 +86,7 @@ protected override void UpdateState(BoundUserInterfaceState state)
{
case AsteroidOffering asteroid:
option.Title = Loc.GetString($"dungeon-config-proto-{asteroid.Id}");
break; // DeltaV: Skip ores since they aren't used with custom generation
var layerKeys = asteroid.MarkerLayers.Keys.ToList();
layerKeys.Sort();

Expand Down
106 changes: 60 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,39 @@ 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();
CachePrecognitionResults();

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 +74,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 +84,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 +112,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 +129,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", msg)),
uid,
false,
mindComponent.Session.Channel,
Color.PaleVioletRed);

component.DoAfter = null;
}
Expand All @@ -156,37 +162,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 null;
}
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 +214,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
Original file line number Diff line number Diff line change
@@ -1,17 +1,50 @@
using Content.Server.StationEvents.Events;
using Content.Shared.EntityTable.EntitySelectors;
using Content.Shared.Storage;
using Robust.Shared.Map; // DeltaV

namespace Content.Server.StationEvents.Components;

[RegisterComponent, Access(typeof(VentCrittersRule))]
public sealed partial class VentCrittersRuleComponent : Component
{
[DataField("entries")]
public List<EntitySpawnEntry> Entries = new();
// DeltaV: Replaced by Table
//[DataField("entries")]
//public List<EntitySpawnEntry> Entries = new();

/// <summary>
/// DeltaV: Table of possible entities to spawn.
/// </summary>
[DataField(required: true)]
public EntityTableSelector Table = default!;

/// <summary>
/// At least one special entry is guaranteed to spawn
/// </summary>
[DataField("specialEntries")]
public List<EntitySpawnEntry> SpecialEntries = new();

/// <summary>
/// DeltaV: The location of the vent that got picked.
/// </summary>
[ViewVariables]
public EntityCoordinates? Location;

/// <summary>
/// DeltaV: Base minimum number of critters to spawn.
/// </summary>
[DataField]
public int Min = 2;

/// <summary>
/// DeltaV: Base maximum number of critters to spawn.
/// </summary>
[DataField]
public int Max = 3;

/// <summary>
/// DeltaV: Min and max get multiplied by the player count then divided by this.
/// </summary>
[DataField]
public int PlayerRatio = 25;
}
Loading

0 comments on commit d391926

Please sign in to comment.