Skip to content

Commit

Permalink
Merge remote-tracking branch 'EE-Fox/master' into upsteam-merge
Browse files Browse the repository at this point in the history
  • Loading branch information
FoxxoTrystan committed Jul 27, 2024
2 parents face03c + 143b1d5 commit 556605f
Show file tree
Hide file tree
Showing 27 changed files with 346 additions and 266 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -4,38 +4,50 @@
public sealed partial class RampingStationEventSchedulerComponent : Component
{
/// <summary>
/// The maximum number by which the event rate will be multiplied when shift time reaches the end time.
/// Multiplies the End Time of the Ramping Event curve. Lower this number for shorter, hectic shifts, increase this number for longer shifts.
/// </summary>
[DataField]
public float ChaosModifier = 3f;
public float ShiftChaosModifier = 1f;

/// <summary>
/// The minimum number by which the event rate will be multiplied when the shift has just begun.
/// The number by which all event delays will be multiplied. Unlike chaos, remains constant throughout the shift.
/// </summary>
[DataField]
public float StartingChaosRatio = 0.1f;
public float EventDelayModifier = 1f;


/// <summary>
/// The number by which all event delays will be multiplied. Unlike chaos, remains constant throughout the shift.
/// Shift Length(in Minutes) is directly reduced by this value.
/// </summary>
[DataField]
public float EventDelayModifier = 1f;
public float ShiftLengthOffset = 0f;

/// <summary>
/// Minimum time between events is decreased by this value.
/// </summary>
[DataField]
public float MinimumEventTimeOffset = 0f;

/// <summary>
/// The number by which average expected shift length is multiplied. Higher values lead to slower chaos growth.
/// Maximum time between events is decreased by this value.
/// </summary>
public float ShiftLengthModifier = 1f;

[DataField]
public float MaximumEventTimeOffset = 0f;

[DataField]
public bool IgnoreMinimumTimes = false;

// Everything below is overridden in the RampingStationEventSchedulerSystem based on CVars
[DataField("endTime"), ViewVariables(VVAccess.ReadWrite)]
[DataField]
public float EndTime;

[DataField("maxChaos"), ViewVariables(VVAccess.ReadWrite)]
[DataField]
public float MaxChaos;

[DataField("startingChaos"), ViewVariables(VVAccess.ReadWrite)]
[DataField]
public float StartingChaos;

[DataField("timeUntilNextEvent"), ViewVariables(VVAccess.ReadWrite)]
[DataField]
public float TimeUntilNextEvent;
}
65 changes: 42 additions & 23 deletions Content.Server/StationEvents/RampingStationEventSchedulerSystem.cs
Original file line number Diff line number Diff line change
Expand Up @@ -2,10 +2,10 @@
using Content.Server.GameTicking.Rules;
using Content.Server.GameTicking.Rules.Components;
using Content.Server.StationEvents.Components;
using Content.Server.StationEvents.Events;
using Content.Shared.CCVar;
using Robust.Shared.Configuration;
using Robust.Shared.Random;
using Robust.Shared.Utility;

namespace Content.Server.StationEvents;

Expand All @@ -16,30 +16,35 @@ public sealed class RampingStationEventSchedulerSystem : GameRuleSystem<RampingS
[Dependency] private readonly EventManagerSystem _event = default!;
[Dependency] private readonly GameTicker _gameTicker = default!;

public float GetChaosModifier(EntityUid uid, RampingStationEventSchedulerComponent component)
/// <summary>
/// A <see href="https://www.desmos.com/calculator/87huunvoxq">logistic curve equation</see> used to smooth out the transition between event times at shift start, vs. shift end.
/// Depending on the settings used, the end time might not necessarily be the point at which timers hit the floor.
/// It is after all, an asymptote.
/// </summary>
/// <param name="component"></param>
/// <param name="startTime"></param>
/// <param name="endTimeOffset"></param>
/// <returns></returns>
public float RampingEventTimeEquation(RampingStationEventSchedulerComponent component, float startTime, float endTimeOffset = 0)
{
var roundTime = (float) _gameTicker.RoundDuration().TotalSeconds;
if (roundTime > component.EndTime)
return component.MaxChaos;

return component.MaxChaos / component.EndTime * roundTime + component.StartingChaos;
var endTime = Math.Clamp(endTimeOffset, 0.1f, startTime - 1);
var shiftLength = Math.Max(1, _cfg.GetCVar(CCVars.EventsRampingAverageEndTime) - component.ShiftLengthOffset);
return 2 * endTime
/ (1
+ MathF.Exp(_cfg.GetCVar(CCVars.EventsRampingAverageChaos)
* component.ShiftChaosModifier
/ shiftLength
* endTime
* (float) _gameTicker.RoundDuration().TotalSeconds
/ 60))
+ (startTime - endTime);
}

protected override void Started(EntityUid uid, RampingStationEventSchedulerComponent component, GameRuleComponent gameRule, GameRuleStartedEvent args)
{
base.Started(uid, component, gameRule, args);

var avgChaos = _cfg.GetCVar(CCVars.EventsRampingAverageChaos) * component.ChaosModifier;
var avgTime = _cfg.GetCVar(CCVars.EventsRampingAverageEndTime) * component.ShiftLengthModifier;

// Worlds shittiest probability distribution
// Got a complaint? Send them to
component.MaxChaos = avgChaos * _random.NextFloat(0.75f, 1.25f);
// This is in minutes, so *60 for seconds (for the chaos calc)
component.EndTime = avgTime * _random.NextFloat(0.75f, 1.25f) * 60f;
component.StartingChaos = component.MaxChaos * component.StartingChaosRatio;

PickNextEventTime(uid, component);
PickNextEventTime(component);
}

public override void Update(float frameTime)
Expand All @@ -61,17 +66,31 @@ public override void Update(float frameTime)
return;
}

PickNextEventTime(uid, scheduler);
PickNextEventTime(scheduler);
_event.RunRandomEvent();
}
}

private void PickNextEventTime(EntityUid uid, RampingStationEventSchedulerComponent component)
private void PickNextEventTime(RampingStationEventSchedulerComponent component)
{
// In case of server hosts being silly and setting maximum time to be lower than minimum time, sanity check the scheduler inputs and sort them by Min/Max
var minimumTime = MathF.Min(_cfg.GetCVar(CCVars.GameEventsRampingMinimumTime)
- _cfg.GetCVar(CCVars.GameEventsRampingMinimumTimeOffset)
- component.MinimumEventTimeOffset, _cfg.GetCVar(CCVars.GameEventsRampingMaximumTime)
- _cfg.GetCVar(CCVars.GameEventsRampingMaximumTimeOffset)
- component.MaximumEventTimeOffset);

var maximumTime = MathF.Max(_cfg.GetCVar(CCVars.GameEventsRampingMinimumTime)
- _cfg.GetCVar(CCVars.GameEventsRampingMinimumTimeOffset)
- component.MinimumEventTimeOffset, _cfg.GetCVar(CCVars.GameEventsRampingMaximumTime)
- _cfg.GetCVar(CCVars.GameEventsRampingMaximumTimeOffset)
- component.MaximumEventTimeOffset);

// Just in case someone messed up their math, set it to between 6 and 12 seconds. This absolutely isn't ideal
component.TimeUntilNextEvent = _random.NextFloat(
_cfg.GetCVar(CCVars.GameEventsRampingMinimumTime),
_cfg.GetCVar(CCVars.GameEventsRampingMaximumTime));
RampingEventTimeEquation(component, MathF.Max(0.1f, minimumTime)),
RampingEventTimeEquation(component, MathF.Max(0.2f, maximumTime)));

component.TimeUntilNextEvent *= component.EventDelayModifier / GetChaosModifier(uid, component);
component.TimeUntilNextEvent *= component.EventDelayModifier;
}
}
27 changes: 20 additions & 7 deletions Content.Shared/CCVar/CCVars.cs
Original file line number Diff line number Diff line change
Expand Up @@ -119,7 +119,7 @@ public static readonly CVarDef<float>
/// Max chaos chosen for a round will deviate from this
/// </summary>
public static readonly CVarDef<float>
EventsRampingAverageChaos = CVarDef.Create("events.ramping_average_chaos", 6f, CVar.ARCHIVE | CVar.SERVERONLY);
EventsRampingAverageChaos = CVarDef.Create("events.ramping_average_chaos", 0.8f, CVar.ARCHIVE | CVar.SERVERONLY);

/*
* Game
Expand Down Expand Up @@ -186,16 +186,29 @@ public static readonly CVarDef<int> // 25 Minutes
GameEventsBasicMaximumTime = CVarDef.Create("game.events_basic_maximum_time", 1500, CVar.SERVERONLY);

/// <summary>
/// Minimum time between Ramping station events in seconds
/// Minimum time between Ramping station events in minutes
/// </summary>
public static readonly CVarDef<int> // 4 Minutes
GameEventsRampingMinimumTime = CVarDef.Create("game.events_ramping_minimum_time", 240, CVar.SERVERONLY);
public static readonly CVarDef<float> // 8 Minutes
GameEventsRampingMinimumTime = CVarDef.Create("game.events_ramping_minimum_time", 8f, CVar.SERVERONLY);

/// <summary>
/// Maximum time between Ramping station events in seconds
/// After the shift's desired "Endpoint" is reached, the minimum time between events is RampingMinimumTime - Offset.
/// </summary>
public static readonly CVarDef<int> // 12 Minutes
GameEventsRampingMaximumTime = CVarDef.Create("game.events_ramping_maximum_time", 720, CVar.SERVERONLY);

public static readonly CVarDef<float>
GameEventsRampingMinimumTimeOffset = CVarDef.Create("game.events_ramping_minimum_time_offset", 6f, CVar.SERVERONLY);

/// <summary>
/// Maximum time between Ramping station events in minutes
/// </summary>
public static readonly CVarDef<float> // 16 Minutes
GameEventsRampingMaximumTime = CVarDef.Create("game.events_ramping_maximum_time", 16f, CVar.SERVERONLY);

/// <summary>
/// After the shift's desired "Endpoint" is reached, the maximum time between events is RampingMaximumTime - Offset.
/// </summary>
public static readonly CVarDef<float>
GameEventsRampingMaximumTimeOffset = CVarDef.Create("game.events_ramping_maximum_time_offset", 10f, CVar.SERVERONLY);

/// <summary>
///
Expand Down
88 changes: 19 additions & 69 deletions Content.Shared/Customization/Systems/CharacterRequirements.cs
Original file line number Diff line number Diff line change
Expand Up @@ -121,17 +121,20 @@ public override bool IsValid(IPrototype prototype, JobPrototype job, HumanoidCha
public sealed partial class CharacterSpeciesRequirement : CharacterRequirement
{
[DataField(required: true)]
public ProtoId<SpeciesPrototype> Species;
public List<ProtoId<SpeciesPrototype>> Species;

public override bool IsValid(IPrototype prototype, JobPrototype job, HumanoidCharacterProfile profile,
Dictionary<string, TimeSpan> playTimes,
IEntityManager entityManager, IPrototypeManager prototypeManager, IConfigurationManager configManager,
out FormattedMessage? reason)
{
const string color = "green";
reason = FormattedMessage.FromMarkup(Loc.GetString("character-species-requirement",
("inverted", Inverted),
("species", Loc.GetString($"species-name-{Species.ToString().ToLower()}"))));
return profile.Species == Species;
("species", $"[color={color}]{string.Join($"[/color], [color={color}]",
Species.Select(s => Loc.GetString(prototypeManager.Index(s).Name)))}[/color]")));

return Species.Contains(profile.Species);
}
}

Expand All @@ -150,8 +153,11 @@ public override bool IsValid(IPrototype prototype, JobPrototype job, HumanoidCha
IEntityManager entityManager, IPrototypeManager prototypeManager, IConfigurationManager configManager,
out FormattedMessage? reason)
{
reason = FormattedMessage.FromMarkup(Loc.GetString("character-trait-requirement", ("inverted", Inverted),
("traits", string.Join(", ", Traits.Select(t => Loc.GetString($"trait-name-{t}"))))));
const string color = "lightblue";
reason = FormattedMessage.FromMarkup(Loc.GetString("character-trait-requirement",
("inverted", Inverted),
("traits", $"[color={color}]{string.Join($"[/color], [color={color}]",
Traits.Select(t => Loc.GetString($"trait-name-{t}")))}[/color]")));

return Traits.Any(t => profile.TraitPreferences.Contains(t.ToString()));
}
Expand All @@ -168,11 +174,15 @@ public sealed partial class CharacterLoadoutRequirement : CharacterRequirement
public List<ProtoId<LoadoutPrototype>> Loadouts;

public override bool IsValid(IPrototype prototype, JobPrototype job, HumanoidCharacterProfile profile,
Dictionary<string, TimeSpan> playTimes, IEntityManager entityManager, IPrototypeManager prototypeManager,
IConfigurationManager configManager, out FormattedMessage? reason)
Dictionary<string, TimeSpan> playTimes,
IEntityManager entityManager, IPrototypeManager prototypeManager, IConfigurationManager configManager,
out FormattedMessage? reason)
{
reason = FormattedMessage.FromMarkup(Loc.GetString("character-loadout-requirement", ("inverted", Inverted),
("loadouts", string.Join(", ", Loadouts.Select(l => Loc.GetString($"loadout-{l}"))))));
const string color = "lightblue";
reason = FormattedMessage.FromMarkup(Loc.GetString("character-loadout-requirement",
("inverted", Inverted),
("loadouts", $"[color={color}]{string.Join($"[/color], [color={color}]",
Loadouts.Select(l => Loc.GetString($"loadout-name-{l}")))}[/color]")));

return Loadouts.Any(l => profile.LoadoutPreferences.Contains(l.ToString()));
}
Expand Down Expand Up @@ -468,63 +478,3 @@ public override bool IsValid(IPrototype prototype, JobPrototype job, HumanoidCha
}

#endregion

#region Prototype Groups

/// <summary>
/// Requires the profile to not have any of the specified traits
/// </summary>
/// <remarks>
/// Only works if you put this prototype in the denied prototypes' requirements too.
/// Can't be inverted, use <see cref="CharacterTraitRequirement"/>
/// </remarks>
[UsedImplicitly]
[Serializable, NetSerializable]
public sealed partial class TraitGroupExclusionRequirement : CharacterRequirement
{
[DataField(required: true)]
public List<ProtoId<TraitPrototype>> Prototypes;

public override bool IsValid(IPrototype prototype, JobPrototype job, HumanoidCharacterProfile profile,
Dictionary<string, TimeSpan> playTimes,
IEntityManager entityManager, IPrototypeManager prototypeManager, IConfigurationManager configManager,
out FormattedMessage? reason)
{
var invalid = profile.TraitPreferences.Any(t => Prototypes.Contains(t));

reason = FormattedMessage.FromMarkup(Loc.GetString("character-trait-group-exclusion-requirement",
("traits", string.Join(", ", Prototypes.Select(t => Loc.GetString($"trait-name-{t}"))))));

return Inverted ? invalid : !invalid;
}
}

/// <summary>
/// Requires the profile to not have any of the specified loadouts
/// </summary>
/// <remarks>
/// Only works if you put this prototype in the denied prototypes' requirements too.
/// Can't be inverted, use <see cref="CharacterLoadoutRequirement"/>
/// </remarks>
[UsedImplicitly]
[Serializable, NetSerializable]
public sealed partial class LoadoutGroupExclusionRequirement : CharacterRequirement
{
[DataField(required: true)]
public List<ProtoId<LoadoutPrototype>> Prototypes;

public override bool IsValid(IPrototype prototype, JobPrototype job, HumanoidCharacterProfile profile,
Dictionary<string, TimeSpan> playTimes,
IEntityManager entityManager, IPrototypeManager prototypeManager, IConfigurationManager configManager,
out FormattedMessage? reason)
{
var invalid = profile.LoadoutPreferences.Any(l => Prototypes.Contains(l));

reason = FormattedMessage.FromMarkup(Loc.GetString("character-loadout-group-exclusion-requirement",
("loadouts", string.Join(", ", Prototypes.Select(l => Loc.GetString($"loadout-{l}"))))));

return Inverted ? invalid : !invalid;
}
}

#endregion
Original file line number Diff line number Diff line change
Expand Up @@ -34,7 +34,7 @@ public bool CheckRequirementsValid(IPrototype prototype, List<CharacterRequireme
valid = !requirement.Inverted;
}

if (reason != null) // To appease the compiler
if (reason != null)
reasons.Add(reason);
}

Expand Down
4 changes: 2 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -56,8 +56,8 @@ We provide some scripts shown below to make the job easier.

> 1. Clone this repository
> 2. Run `git submodule update --init --recursive` in a terminal to download the engine
> 3. Run `Scripts/bat/buildAllDebug.sh` after making any changes to the source
> 4. Run `Scripts/bat/runQuickAll.sh` to launch the client and the server
> 3. Run `Scripts/sh/buildAllDebug.sh` after making any changes to the source
> 4. Run `Scripts/sh/runQuickAll.sh` to launch the client and the server
> 5. Connect to localhost in the client and play
### MacOS
Expand Down
6 changes: 6 additions & 0 deletions Resources/Changelog/Changelog.yml
Original file line number Diff line number Diff line change
Expand Up @@ -4652,3 +4652,9 @@ Entries:
connection. Attached grids now permit sound to travel through.
id: 6184
time: '2024-07-26T20:09:52.0000000+00:00'
- author: VMSolidus
changes:
- type: Add
message: Long Survival has been added as a new Game mode.
id: 6185
time: '2024-07-27T06:00:24.0000000+00:00'
15 changes: 8 additions & 7 deletions Resources/Locale/en-US/customization/character-requirements.ftl
Original file line number Diff line number Diff line change
Expand Up @@ -5,19 +5,23 @@ character-age-requirement = You must {$inverted ->
character-species-requirement = You must {$inverted ->
[true] not be
*[other] be
} a [color=green]{$species}[/color]
} a {$species}
character-trait-requirement = You must {$inverted ->
[true] not have
*[other] have
} one of these traits: [color=lightblue]{$traits}[/color]
} one of these traits: {$traits}
character-loadout-requirement = You must {$inverted ->
[true] not have
*[other] have
} one of these loadouts: {$loadouts}
character-backpack-type-requirement = You must {$inverted ->
[true] not use
*[other] use
} a [color=lightblue]{$type}[/color] as your bag
} a [color=brown]{$type}[/color] as your bag
character-clothing-preference-requirement = You must {$inverted ->
[true] not wear
*[other] wear
} a [color=lightblue]{$type}[/color]
} a [color=white]{$type}[/color]
character-job-requirement = You must {$inverted ->
[true] not be
Expand All @@ -34,6 +38,3 @@ character-timer-overall-insufficient = You require [color=yellow]{TOSTRING($time
character-timer-overall-too-high = You require [color=yellow]{TOSTRING($time, "0")}[/color] fewer minutes of playtime
character-timer-role-insufficient = You require [color=yellow]{TOSTRING($time, "0")}[/color] more minutes with [color={$departmentColor}]{$job}[/color]
character-timer-role-too-high = You require[color=yellow] {TOSTRING($time, "0")}[/color] fewer minutes with [color={$departmentColor}]{$job}[/color]
character-trait-group-exclusion-requirement = You cannot have one of the following traits if you select this: {$traits}
character-loadout-group-exclusion-requirement = You cannot have one of the following loadouts if you select this: {$loadouts}
Loading

0 comments on commit 556605f

Please sign in to comment.