mirror of
https://github.com/WWhiteDreamProject/wwdpublic.git
synced 2026-04-17 13:37:47 +03:00
# Description This PR brings back a feature that was present in the Psionic Refactor Version 1, which ultimately never made it into the game. I have substantially reworked the underlying math behind Glimmer, such that it operates on a Logistic Curve described by this equation:  Instead of 0 being the "Normal" amount of glimmer, the "Normal" amount is the "seemingly arbitrary" number 502.941. This number is measured first by taking the derivative of the Glimmer Equation, and then solving for the derivative equal to 1. Above this constant, glimmer grows exponentially more difficult to increase. Below this constant, glimmer grows exponentially easier to increase. It will thus constantly attempt to trend towards the "Glimmer Equilibrium". Probers, Drainers, Anomalies, and Glimmer Mites all cause glimmer to "Fluctuate", either up or down the graph. This gives a glimmer that swings constantly to both high and low values, with more violent swings being caused by having more probers/anomalies etc. A great deal of math functions have been implemented that allow for various uses for glimmer measurements, and psionic powers can even have their effects modified by said measurements. The most significant part of this rework is what's facing Epistemics. It's essentially no longer possible for Probers to cause a round-ending chain of events known as "Glimmerloose". You can ALWAYS recover from high glimmer, no matter how high it gets. As a counterpart to this, Probers have had the math behind their research point generation reworked. Research output follows an inverse log base 4 curve. Which can be found here: https://www.desmos.com/calculator/q183tseun8 I wouldn't drop this massive update on people without a way for them to understand what's going on. So this PR also features the return(and expansion of), the much-demanded Psionics Guidebook. <details><summary><h1>Media</h1></summary> <p> Psionics Guidebook   </p> </details> # Changelog 🆑 VMSolidus, Gollee - add: Glimmer has been substantially reworked. Please read the new Psionics Guidebook for more information. In short: "500 is the new normal glimmer. Stop panicking if Sophia says the glimmer is 500. Call code white if it gets to 750 and stays above that level." - add: The much-requested Psionics Guidebook has returned, now with all new up-to-date information. - remove: Removed "GLIMMERLOOSE". It's no longer possible for glimmer to start a chain of events that ends the round if epistemics isn't destroyed. You can ALWAYS recover from high glimmer now. - tweak: All glimmer events have had their thresholds tweaked to reflect the fact that 500 is the new "Normal" amount of glimmer. --------- Signed-off-by: VMSolidus <evilexecutive@gmail.com> (cherry picked from commit 638071c48fe3ac7c727a1294de3b6d5d8136e79f)
210 lines
6.8 KiB
C#
210 lines
6.8 KiB
C#
using System.Linq;
|
|
using Content.Server.Chat.Managers;
|
|
using Content.Server.GameTicking;
|
|
using Content.Server.StationEvents.Components;
|
|
using Content.Shared.CCVar;
|
|
using Robust.Server.Player;
|
|
using Robust.Shared.Configuration;
|
|
using Robust.Shared.Prototypes;
|
|
using Robust.Shared.Random;
|
|
using Content.Server.Psionics.Glimmer;
|
|
using Content.Shared.Psionics.Glimmer;
|
|
namespace Content.Server.StationEvents;
|
|
|
|
public sealed class EventManagerSystem : EntitySystem
|
|
{
|
|
[Dependency] private readonly IConfigurationManager _configurationManager = default!;
|
|
[Dependency] private readonly IPlayerManager _playerManager = default!;
|
|
[Dependency] private readonly IRobustRandom _random = default!;
|
|
[Dependency] private readonly IPrototypeManager _prototype = default!;
|
|
[Dependency] private readonly IChatManager _chat = default!;
|
|
[Dependency] public readonly GameTicker GameTicker = default!;
|
|
[Dependency] private readonly GlimmerSystem _glimmerSystem = default!; //Nyano - Summary: pulls in the glimmer system.
|
|
|
|
public bool EventsEnabled { get; private set; }
|
|
private void SetEnabled(bool value) => EventsEnabled = value;
|
|
|
|
public override void Initialize()
|
|
{
|
|
base.Initialize();
|
|
|
|
Subs.CVar(_configurationManager, CCVars.EventsEnabled, SetEnabled, true);
|
|
}
|
|
|
|
/// <summary>
|
|
/// Randomly runs a valid event.
|
|
/// </summary>
|
|
public string RunRandomEvent()
|
|
{
|
|
var randomEvent = PickRandomEvent();
|
|
|
|
if (randomEvent == null)
|
|
{
|
|
var errStr = Loc.GetString("station-event-system-run-random-event-no-valid-events");
|
|
Log.Error(errStr);
|
|
return errStr;
|
|
}
|
|
|
|
var ent = GameTicker.AddGameRule(randomEvent);
|
|
var str = Loc.GetString("station-event-system-run-event",("eventName", ToPrettyString(ent)));
|
|
_chat.SendAdminAlert(str);
|
|
Log.Info(str);
|
|
return str;
|
|
}
|
|
|
|
/// <summary>
|
|
/// Randomly picks a valid event.
|
|
/// </summary>
|
|
public string? PickRandomEvent()
|
|
{
|
|
var availableEvents = AvailableEvents();
|
|
Log.Info($"Picking from {availableEvents.Count} total available events");
|
|
return FindEvent(availableEvents);
|
|
}
|
|
|
|
/// <summary>
|
|
/// Pick a random event from the available events at this time, also considering their weightings.
|
|
/// </summary>
|
|
/// <returns></returns>
|
|
public string? FindEvent(Dictionary<EntityPrototype, StationEventComponent> availableEvents)
|
|
{
|
|
if (availableEvents.Count == 0)
|
|
{
|
|
Log.Warning("No events were available to run!");
|
|
return null;
|
|
}
|
|
|
|
var sumOfWeights = 0;
|
|
|
|
foreach (var stationEvent in availableEvents.Values)
|
|
{
|
|
sumOfWeights += (int) stationEvent.Weight;
|
|
}
|
|
|
|
sumOfWeights = _random.Next(sumOfWeights);
|
|
|
|
foreach (var (proto, stationEvent) in availableEvents)
|
|
{
|
|
sumOfWeights -= (int) stationEvent.Weight;
|
|
|
|
if (sumOfWeights <= 0)
|
|
{
|
|
return proto.ID;
|
|
}
|
|
}
|
|
|
|
Log.Error("Event was not found after weighted pick process!");
|
|
return null;
|
|
}
|
|
|
|
/// <summary>
|
|
/// Gets the events that have met their player count, time-until start, etc.
|
|
/// </summary>
|
|
/// <param name="playerCountOverride">Override for player count, if using this to simulate events rather than in an actual round.</param>
|
|
/// <param name="currentTimeOverride">Override for round time, if using this to simulate events rather than in an actual round.</param>
|
|
/// <returns></returns>
|
|
public Dictionary<EntityPrototype, StationEventComponent> AvailableEvents(
|
|
bool ignoreEarliestStart = false,
|
|
int? playerCountOverride = null,
|
|
TimeSpan? currentTimeOverride = null)
|
|
{
|
|
var playerCount = playerCountOverride ?? _playerManager.PlayerCount;
|
|
|
|
// playerCount does a lock so we'll just keep the variable here
|
|
var currentTime = currentTimeOverride ?? (!ignoreEarliestStart
|
|
? GameTicker.RoundDuration()
|
|
: TimeSpan.Zero);
|
|
|
|
var result = new Dictionary<EntityPrototype, StationEventComponent>();
|
|
|
|
foreach (var (proto, stationEvent) in AllEvents())
|
|
{
|
|
if (CanRun(proto, stationEvent, playerCount, currentTime))
|
|
{
|
|
result.Add(proto, stationEvent);
|
|
}
|
|
}
|
|
|
|
return result;
|
|
}
|
|
|
|
public Dictionary<EntityPrototype, StationEventComponent> AllEvents()
|
|
{
|
|
var allEvents = new Dictionary<EntityPrototype, StationEventComponent>();
|
|
foreach (var prototype in _prototype.EnumeratePrototypes<EntityPrototype>())
|
|
{
|
|
if (prototype.Abstract)
|
|
continue;
|
|
|
|
if (!prototype.TryGetComponent<StationEventComponent>(out var stationEvent))
|
|
continue;
|
|
|
|
allEvents.Add(prototype, stationEvent);
|
|
}
|
|
|
|
return allEvents;
|
|
}
|
|
|
|
private int GetOccurrences(EntityPrototype stationEvent)
|
|
{
|
|
return GetOccurrences(stationEvent.ID);
|
|
}
|
|
|
|
private int GetOccurrences(string stationEvent)
|
|
{
|
|
return GameTicker.AllPreviousGameRules.Count(p => p.Item2 == stationEvent);
|
|
}
|
|
|
|
public TimeSpan TimeSinceLastEvent(EntityPrototype stationEvent)
|
|
{
|
|
foreach (var (time, rule) in GameTicker.AllPreviousGameRules.Reverse())
|
|
{
|
|
if (rule == stationEvent.ID)
|
|
return time;
|
|
}
|
|
|
|
return TimeSpan.Zero;
|
|
}
|
|
|
|
private bool CanRun(EntityPrototype prototype, StationEventComponent stationEvent, int playerCount, TimeSpan currentTime)
|
|
{
|
|
if (GameTicker.IsGameRuleAdded(prototype.ID))
|
|
return false;
|
|
|
|
if (stationEvent.MaxOccurrences.HasValue && GetOccurrences(prototype) >= stationEvent.MaxOccurrences.Value)
|
|
{
|
|
return false;
|
|
}
|
|
|
|
if (playerCount < stationEvent.MinimumPlayers)
|
|
{
|
|
return false;
|
|
}
|
|
|
|
if (currentTime != TimeSpan.Zero && currentTime.TotalMinutes < stationEvent.EarliestStart)
|
|
{
|
|
return false;
|
|
}
|
|
|
|
var lastRun = TimeSinceLastEvent(prototype);
|
|
if (lastRun != TimeSpan.Zero && currentTime.TotalMinutes <
|
|
stationEvent.ReoccurrenceDelay + lastRun.TotalMinutes)
|
|
{
|
|
return false;
|
|
}
|
|
|
|
// Nyano - Summary: - Begin modified code block: check for glimmer events.
|
|
// This could not be cleanly done anywhere else.
|
|
if (_configurationManager.GetCVar(CCVars.GlimmerEnabled) &&
|
|
prototype.TryGetComponent<GlimmerEventComponent>(out var glimmerEvent) &&
|
|
(_glimmerSystem.GlimmerOutput < glimmerEvent.MinimumGlimmer ||
|
|
_glimmerSystem.GlimmerOutput > glimmerEvent.MaximumGlimmer))
|
|
{
|
|
return false;
|
|
}
|
|
// Nyano - End modified code block.
|
|
|
|
return true;
|
|
}
|
|
}
|