* base

* remove redudant shit

* implement needed

* doc

* manager

* Commands

* Update AspectManager.cs

* oops, hardcoded!

* fix

* похуй

* да похуй

* увфывфывфывфыв

* Update AspectManager.cs

* RandomAccentAspect

* avoid repeating

* FastAndFuriousAccent

* Update Aspects.yml

* Update FastandFuriousAspect.cs

* RandomAppearanceAspect

* helpers and some shit

* Bombassssssss

* DrunkAspect

* CargoRich

* TraitoredAspect

* require info and feature to force aspect

* add exec only in lobby

* deforce command

* Update TraitoredAspect.cs

* Prepare for Bloody and Weak Aspects.

* comments

* WeakWallsAspect

* tweak

* NoEngineAspect

* airunlock aspect

* BloodyAspect

* WeakAspect

* BattledAspect

* I Have Two Butts But I must Seat

* веса

* Update WhiteCVars.cs
This commit is contained in:
rhailrake
2023-09-17 18:51:42 +06:00
committed by Aviu00
parent 015bb02e86
commit 6db5d91e1c
52 changed files with 1902 additions and 9 deletions

View File

@@ -81,7 +81,7 @@ public sealed partial class CargoSystem : SharedCargoSystem
}
[PublicAPI]
public void UpdateBankAccount(EntityUid uid, StationBankAccountComponent component, int balanceAdded)
public void UpdateBankAccount(EntityUid? uid, StationBankAccountComponent component, int balanceAdded)
{
component.Balance += balanceAdded;
var query = EntityQueryEnumerator<CargoOrderConsoleComponent>();

View File

@@ -340,6 +340,39 @@ public sealed class TraitorRuleSystem : GameRuleSystem<TraitorRuleComponent>
args.AgentName = Loc.GetString("traitor-round-end-agent-name");
}
public List<(EntityUid Id, MindComponent Mind)> GetAllLivingConnectedTraitors()
{
var traitors = new List<(EntityUid Id, MindComponent Mind)>();
var traitorRules = EntityQuery<TraitorRuleComponent>();
foreach (var traitorRule in traitorRules)
{
traitors.AddRange(GetLivingConnectedTraitors(traitorRule));
}
return traitors;
}
private List<(EntityUid Id, MindComponent Mind)> GetLivingConnectedTraitors(TraitorRuleComponent traitorRule)
{
var traitors = new List<(EntityUid Id, MindComponent Mind)>();
foreach (var traitor in traitorRule.TraitorMinds)
{
if (TryComp(traitor, out MindComponent? mind) &&
mind.OwnedEntity != null &&
mind.Session != null &&
_mobStateSystem.IsAlive(mind.OwnedEntity.Value) &&
mind.CurrentEntity == mind.OwnedEntity)
{
traitors.Add((traitor, mind));
}
}
return traitors;
}
private void OnObjectivesTextPrepend(EntityUid uid, TraitorRuleComponent comp, ref ObjectivesTextPrependEvent args)
{
args.Text += "\n" + Loc.GetString("traitor-round-end-codewords", ("codewords", string.Join(", ", comp.Codewords)));

View File

@@ -5,10 +5,11 @@ using Content.Shared.Camera;
using Content.Shared.Damage;
using Content.Shared.Database;
using Content.Shared.Projectiles;
using Robust.Server.GameObjects;
using Content.Shared.White;
using Robust.Shared.Configuration;
using Robust.Shared.Player;
using Robust.Shared.Physics.Events;
using Content.Shared.Mobs.Components;
using Robust.Shared.Player;
namespace Content.Server.Projectiles;
@@ -19,10 +20,18 @@ public sealed class ProjectileSystem : SharedProjectileSystem
[Dependency] private readonly DamageableSystem _damageableSystem = default!;
[Dependency] private readonly GunSystem _guns = default!;
[Dependency] private readonly SharedCameraRecoilSystem _sharedCameraRecoil = default!;
[Dependency] private readonly IConfigurationManager _cfg = default!;
private float DamageModifier { get; set; }
private void SetDamage(float value) => DamageModifier = value;
public override void Initialize()
{
base.Initialize();
_cfg.OnValueChanged(WhiteCVars.DamageModifier, SetDamage, true);
SubscribeLocalEvent<ProjectileComponent, StartCollideEvent>(OnStartCollide);
}
@@ -48,7 +57,7 @@ public sealed class ProjectileSystem : SharedProjectileSystem
var otherName = ToPrettyString(target);
var direction = args.OurBody.LinearVelocity.Normalized();
var modifiedDamage = _damageableSystem.TryChangeDamage(target, ev.Damage, component.IgnoreResistances, origin: component.Shooter);
var modifiedDamage = _damageableSystem.TryChangeDamage(target, ev.Damage * DamageModifier, component.IgnoreResistances, origin: component.Shooter);
var deleted = Deleted(target);
if (modifiedDamage is not null && EntityManager.EntityExists(component.Shooter))

View File

@@ -0,0 +1,20 @@
using Content.Server.GameTicking.Rules.Components;
using Content.Server.White.AspectsSystem.Aspects.Components;
using Content.Server.White.AspectsSystem.Base;
using Content.Shared.Access.Components;
namespace Content.Server.White.AspectsSystem.Aspects;
public sealed class AiRunLockAspect : AspectSystem<AiRunLockAspectComponent>
{
protected override void Started(EntityUid uid, AiRunLockAspectComponent component, GameRuleComponent gameRule, GameRuleStartedEvent args)
{
base.Started(uid, component, gameRule, args);
var query = EntityQueryEnumerator<AccessReaderComponent>();
while (query.MoveNext(out _, out var accessReaderComponent))
{
accessReaderComponent.Enabled = false;
}
}
}

View File

@@ -0,0 +1,87 @@
using System.Linq;
using Content.Server.GameTicking;
using Content.Server.GameTicking.Rules.Components;
using Content.Server.White.AspectsSystem.Aspects.Components;
using Content.Server.White.AspectsSystem.Base;
using Content.Shared.Hands.Components;
using Content.Shared.Hands.EntitySystems;
using Content.Shared.Humanoid;
using Content.Shared.White.NonPeacefulRoundEnd;
using Robust.Shared.Prototypes;
using Robust.Shared.Random;
namespace Content.Server.White.AspectsSystem.Aspects;
public sealed class BattledAspect : AspectSystem<BattledAspectComponent>
{
[Dependency] private readonly IPrototypeManager _prototypeManager = default!;
[Dependency] private readonly SharedHandsSystem _handsSystem = default!;
[Dependency] private readonly IRobustRandom _robustRandom = default!;
private NonPeacefulRoundItemsPrototype _nonPeacefulRoundItemsPrototype = default!;
public override void Initialize()
{
base.Initialize();
SubscribeLocalEvent<PlayerSpawnCompleteEvent>(HandleLateJoin);
}
protected override void Started(EntityUid uid, BattledAspectComponent component, GameRuleComponent gameRule, GameRuleStartedEvent args)
{
base.Started(uid, component, gameRule, args);
var prototypes = _prototypeManager.EnumeratePrototypes<NonPeacefulRoundItemsPrototype>().ToList();
if (prototypes.Count == 0)
ForceEndSelf(uid, gameRule);
_nonPeacefulRoundItemsPrototype = _robustRandom.Pick(prototypes);
var query = EntityQueryEnumerator<HumanoidAppearanceComponent>();
while (query.MoveNext(out var ent, out _))
{
GiveItem(ent);
}
}
private void HandleLateJoin(PlayerSpawnCompleteEvent ev)
{
var query = EntityQueryEnumerator<BattledAspectComponent, GameRuleComponent>();
while (query.MoveNext(out var ruleEntity, out _, out var gameRule))
{
if (!GameTicker.IsGameRuleAdded(ruleEntity, gameRule))
continue;
if (!ev.LateJoin)
return;
var mob = ev.Mob;
GiveItem(mob);
}
}
#region Helpers
private void GiveItem(EntityUid player)
{
var item = _robustRandom.Pick(_nonPeacefulRoundItemsPrototype.Items);
var transform = CompOrNull<TransformComponent>(player);
if(transform == null)
return;
if(!HasComp<HandsComponent>(player))
return;
var weaponEntity = EntityManager.SpawnEntity(item, transform.Coordinates);
_handsSystem.TryDrop(player);
_handsSystem.PickupOrDrop(player, weaponEntity);
}
#endregion
}

View File

@@ -0,0 +1,26 @@
using Content.Server.GameTicking.Rules.Components;
using Content.Server.White.AspectsSystem.Aspects.Components;
using Content.Server.White.AspectsSystem.Base;
using Content.Shared.White;
using Robust.Shared.Configuration;
namespace Content.Server.White.AspectsSystem.Aspects;
public sealed class BloodyAspect : AspectSystem<BloodyAspectComponent>
{
[Dependency] private readonly IConfigurationManager _cfg = default!;
protected override void Started(EntityUid uid, BloodyAspectComponent component, GameRuleComponent gameRule, GameRuleStartedEvent args)
{
base.Started(uid, component, gameRule, args);
_cfg.SetCVar(WhiteCVars.DamageGetModifier, 2.5f);
}
protected override void Ended(EntityUid uid, BloodyAspectComponent component, GameRuleComponent gameRule, GameRuleEndedEvent args)
{
base.Ended(uid, component, gameRule, args);
_cfg.SetCVar(WhiteCVars.DamageGetModifier, 1.0f);
}
}

View File

@@ -0,0 +1,31 @@
using Content.Server.GameTicking.Rules.Components;
using Content.Server.White.AspectsSystem.Aspects.Components;
using Content.Server.White.AspectsSystem.Base;
using Robust.Shared.Random;
namespace Content.Server.White.AspectsSystem.Aspects;
public sealed class BombassAspect : AspectSystem<BombassAspectComponent>
{
[Dependency] private readonly IRobustRandom _random = default!;
protected override void Added(EntityUid uid, BombassAspectComponent component, GameRuleComponent gameRule, GameRuleAddedEvent args)
{
base.Added(uid, component, gameRule, args);
SpawnMines();
}
private void SpawnMines()
{
var minMines = _random.Next(35, 100);
for (var i = 0; i < minMines; i++)
{
if (!TryFindRandomTile(out _, out _, out _, out var targetCoords))
break;
EntityManager.SpawnEntity("LandMineExplosive", targetCoords);
}
}
}

View File

@@ -0,0 +1,25 @@
using Content.Server.Cargo.Components;
using Content.Server.Cargo.Systems;
using Content.Server.GameTicking.Rules.Components;
using Content.Server.White.AspectsSystem.Aspects.Components;
using Content.Server.White.AspectsSystem.Base;
namespace Content.Server.White.AspectsSystem.Aspects;
public sealed class CargoRichAspect : AspectSystem<CargoRichAspectComponent>
{
[Dependency] private readonly CargoSystem _cargoSystem = default!;
protected override void Added(EntityUid uid, CargoRichAspectComponent component, GameRuleComponent gameRule, GameRuleAddedEvent args)
{
base.Added(uid, component, gameRule, args);
if (!TryGetRandomStation(out var station))
return;
if (!TryComp<StationBankAccountComponent>(station, out var stationBankAccountComponent))
return;
_cargoSystem.UpdateBankAccount(station, stationBankAccountComponent, 100000);
}
}

View File

@@ -0,0 +1,21 @@
using Content.Server.GameTicking.Rules.Components;
using Content.Server.White.AspectsSystem.Aspects.Components;
using Content.Server.White.AspectsSystem.Base;
using Content.Server.White.Other;
namespace Content.Server.White.AspectsSystem.Aspects;
public sealed class ChairLeakAspect : AspectSystem<ChairLeakAspectComponent>
{
protected override void Started(EntityUid uid, ChairLeakAspectComponent component, GameRuleComponent gameRule, GameRuleStartedEvent args)
{
base.Started(uid, component, gameRule, args);
var query = EntityQueryEnumerator<ChairMarkComponent>();
while (query.MoveNext(out var ent, out _))
{
EntityManager.DeleteEntity(ent);
}
}
}

View File

@@ -0,0 +1,6 @@
namespace Content.Server.White.AspectsSystem.Aspects.Components;
[RegisterComponent]
public sealed partial class AiRunLockAspectComponent : Component
{
}

View File

@@ -0,0 +1,6 @@
namespace Content.Server.White.AspectsSystem.Aspects.Components;
[RegisterComponent]
public sealed partial class BattledAspectComponent : Component
{
}

View File

@@ -0,0 +1,6 @@
namespace Content.Server.White.AspectsSystem.Aspects.Components;
[RegisterComponent]
public sealed partial class BloodyAspectComponent : Component
{
}

View File

@@ -0,0 +1,6 @@
namespace Content.Server.White.AspectsSystem.Aspects.Components;
[RegisterComponent]
public sealed partial class BombassAspectComponent : Component
{
}

View File

@@ -0,0 +1,6 @@
namespace Content.Server.White.AspectsSystem.Aspects.Components;
[RegisterComponent]
public sealed partial class CargoRichAspectComponent : Component
{
}

View File

@@ -0,0 +1,6 @@
namespace Content.Server.White.AspectsSystem.Aspects.Components;
[RegisterComponent]
public sealed partial class ChairLeakAspectComponent : Component
{
}

View File

@@ -0,0 +1,6 @@
namespace Content.Server.White.AspectsSystem.Aspects.Components;
[RegisterComponent]
public sealed partial class DrunkAspectComponent : Component
{
}

View File

@@ -0,0 +1,6 @@
namespace Content.Server.White.AspectsSystem.Aspects.Components;
[RegisterComponent]
public sealed partial class FastAndFuriousAspectComponent : Component
{
}

View File

@@ -0,0 +1,6 @@
namespace Content.Server.White.AspectsSystem.Aspects.Components;
[RegisterComponent]
public sealed partial class NoEngineAspectComponent : Component
{
}

View File

@@ -0,0 +1,6 @@
namespace Content.Server.White.AspectsSystem.Aspects.Components;
[RegisterComponent]
public sealed partial class RandomAccentAspectComponent : Component
{
}

View File

@@ -0,0 +1,6 @@
namespace Content.Server.White.AspectsSystem.Aspects.Components;
[RegisterComponent]
public sealed partial class RandomAppearanceAspectComponent : Component
{
}

View File

@@ -0,0 +1,6 @@
namespace Content.Server.White.AspectsSystem.Aspects.Components;
[RegisterComponent]
public sealed partial class TraitoredAspectComponent : Component
{
}

View File

@@ -0,0 +1,6 @@
namespace Content.Server.White.AspectsSystem.Aspects.Components;
[RegisterComponent]
public sealed partial class WeakAspectComponent : Component
{
}

View File

@@ -0,0 +1,6 @@
namespace Content.Server.White.AspectsSystem.Aspects.Components;
[RegisterComponent]
public sealed partial class WeakWallsAspectComponent : Component
{
}

View File

@@ -0,0 +1,48 @@
using Content.Server.GameTicking;
using Content.Server.GameTicking.Rules.Components;
using Content.Server.White.AspectsSystem.Aspects.Components;
using Content.Server.White.AspectsSystem.Base;
using Content.Shared.Drunk;
using Content.Shared.Humanoid;
namespace Content.Server.White.AspectsSystem.Aspects;
public sealed class DrunkAspect : AspectSystem<DrunkAspectComponent>
{
[Dependency] private readonly SharedDrunkSystem _drunkSystem = default!;
public override void Initialize()
{
base.Initialize();
SubscribeLocalEvent<PlayerSpawnCompleteEvent>(HandleLateJoin);
}
protected override void Started(EntityUid uid, DrunkAspectComponent component, GameRuleComponent gameRule, GameRuleStartedEvent args)
{
base.Started(uid, component, gameRule, args);
var query = EntityQueryEnumerator<HumanoidAppearanceComponent>();
while (query.MoveNext(out var ent, out _))
{
_drunkSystem.TryApplyDrunkenness(ent, 50);
}
}
private void HandleLateJoin(PlayerSpawnCompleteEvent ev)
{
var query = EntityQueryEnumerator<DrunkAspectComponent, GameRuleComponent>();
while (query.MoveNext(out var ruleEntity, out _, out var gameRule))
{
if (!GameTicker.IsGameRuleAdded(ruleEntity, gameRule))
continue;
if (!ev.LateJoin)
return;
var mob = ev.Mob;
_drunkSystem.TryApplyDrunkenness(mob, 50);
}
}
}

View File

@@ -0,0 +1,59 @@
using Content.Server.GameTicking;
using Content.Server.GameTicking.Rules.Components;
using Content.Server.White.AspectsSystem.Aspects.Components;
using Content.Server.White.AspectsSystem.Base;
using Content.Shared.Movement.Components;
using Content.Shared.Movement.Systems;
namespace Content.Server.White.AspectsSystem.Aspects;
public sealed class FastAndFuriousAspect : AspectSystem<FastAndFuriousAspectComponent>
{
[Dependency] private readonly MovementSpeedModifierSystem _movementSystem = default!;
public override void Initialize()
{
base.Initialize();
SubscribeLocalEvent<PlayerSpawnCompleteEvent>(HandleLateJoin);
}
protected override void Started(EntityUid uid, FastAndFuriousAspectComponent component, GameRuleComponent gameRule, GameRuleStartedEvent args)
{
base.Started(uid, component, gameRule, args);
var query = EntityQueryEnumerator<MovementSpeedModifierComponent>();
while (query.MoveNext(out var ent, out var speedModifierComponent))
{
_movementSystem.ChangeBaseSpeed(ent, speedModifierComponent.BaseWalkSpeed + 2.5f, speedModifierComponent.BaseSprintSpeed + 4, speedModifierComponent.Acceleration);
}
}
protected override void Ended(EntityUid uid, FastAndFuriousAspectComponent component, GameRuleComponent gameRule, GameRuleEndedEvent args)
{
base.Ended(uid, component, gameRule, args);
var query = EntityQueryEnumerator<MovementSpeedModifierComponent>();
while (query.MoveNext(out var ent, out var speedModifierComponent))
{
_movementSystem.ChangeBaseSpeed(ent, 2.5f, 4.5f, speedModifierComponent.Acceleration);
}
}
private void HandleLateJoin(PlayerSpawnCompleteEvent ev)
{
var query = EntityQueryEnumerator<FastAndFuriousAspectComponent, GameRuleComponent>();
while (query.MoveNext(out var ruleEntity, out _, out var gameRule))
{
if (!GameTicker.IsGameRuleAdded(ruleEntity, gameRule))
continue;
if (!ev.LateJoin)
return;
var mob = ev.Mob;
if (!TryComp<MovementSpeedModifierComponent>(mob, out var speedModifierComponent))
return;
_movementSystem.ChangeBaseSpeed(mob, speedModifierComponent.BaseWalkSpeed + 2.5f, speedModifierComponent.BaseSprintSpeed + 4, speedModifierComponent.Acceleration);
}
}
}

View File

@@ -0,0 +1,21 @@
using Content.Server.GameTicking.Rules.Components;
using Content.Server.White.AspectsSystem.Aspects.Components;
using Content.Server.White.AspectsSystem.Base;
using Content.Server.White.Other;
namespace Content.Server.White.AspectsSystem.Aspects;
public sealed class NoEngineAspect : AspectSystem<NoEngineAspectComponent>
{
protected override void Started(EntityUid uid, NoEngineAspectComponent component, GameRuleComponent gameRule, GameRuleStartedEvent args)
{
base.Started(uid, component, gameRule, args);
var query = EntityQueryEnumerator<EngineMarkComponent>();
while (query.MoveNext(out var ent, out _))
{
EntityManager.DeleteEntity(ent);
}
}
}

View File

@@ -0,0 +1,107 @@
using System.Linq;
using Content.Server.GameTicking;
using Content.Server.GameTicking.Rules.Components;
using Content.Server.Speech.Components;
using Content.Server.White.AspectsSystem.Aspects.Components;
using Content.Server.White.AspectsSystem.Base;
using Content.Shared.Mind.Components;
using Robust.Shared.Random;
namespace Content.Server.White.AspectsSystem.Aspects;
public sealed class RandomAccentAspect : AspectSystem<RandomAccentAspectComponent>
{
[Dependency] private readonly IRobustRandom _random = default!;
public override void Initialize()
{
base.Initialize();
SubscribeLocalEvent<PlayerSpawnCompleteEvent>(HandleLateJoin);
}
protected override void Started(EntityUid uid, RandomAccentAspectComponent component, GameRuleComponent gameRule, GameRuleStartedEvent args)
{
base.Started(uid, component, gameRule, args);
var query = EntityQueryEnumerator<MindContainerComponent>();
while (query.MoveNext(out var ent, out _))
{
ApplyRandomAccent(ent);
}
}
private void HandleLateJoin(PlayerSpawnCompleteEvent ev)
{
var query = EntityQueryEnumerator<RandomAccentAspectComponent, GameRuleComponent>();
while (query.MoveNext(out var ruleEntity, out _, out var gameRule))
{
if (!GameTicker.IsGameRuleAdded(ruleEntity, gameRule))
continue;
if (!ev.LateJoin)
return;
var mob = ev.Mob;
ApplyRandomAccent(mob);
}
}
#region Helpers
private enum AccentType
{
Stuttering,
Spanish,
Slurred,
Scrambled,
Pirate,
Russian,
Anime,
Lizard,
Backwards
}
private void ApplyRandomAccent(EntityUid uid)
{
var allAccents = Enum.GetValues(typeof(AccentType)).Cast<AccentType>().ToList();
var randomIndex = _random.Next(allAccents.Count);
var selectedAccent = allAccents[randomIndex];
ApplyAccent(uid, selectedAccent);
}
private void ApplyAccent(EntityUid uid, AccentType accentType)
{
switch (accentType)
{
case AccentType.Stuttering:
EntityManager.EnsureComponent<StutteringAccentComponent>(uid);
break;
case AccentType.Spanish:
EntityManager.EnsureComponent<SpanishAccentComponent>(uid);
break;
case AccentType.Slurred:
EntityManager.EnsureComponent<SlurredAccentComponent>(uid);
break;
case AccentType.Scrambled:
EntityManager.EnsureComponent<ScrambledAccentComponent>(uid);
break;
case AccentType.Pirate:
EntityManager.EnsureComponent<PirateAccentComponent>(uid);
break;
case AccentType.Russian:
EntityManager.EnsureComponent<RussianAccentComponent>(uid);
break;
case AccentType.Anime:
EntityManager.EnsureComponent<OwOAccentComponent>(uid);
break;
case AccentType.Lizard:
EntityManager.EnsureComponent<LizardAccentComponent>(uid);
break;
case AccentType.Backwards:
EntityManager.EnsureComponent<BackwardsAccentComponent>(uid);
break;
}
}
#endregion
}

View File

@@ -0,0 +1,44 @@
using Content.Server.GameTicking;
using Content.Server.GameTicking.Rules.Components;
using Content.Server.White.AspectsSystem.Aspects.Components;
using Content.Server.White.AspectsSystem.Base;
using Content.Server.White.Other.RandomHumanSystem;
using Content.Shared.Humanoid;
namespace Content.Server.White.AspectsSystem.Aspects;
public sealed class RandomAppearanceAspect : AspectSystem<RandomAppearanceAspectComponent>
{
public override void Initialize()
{
base.Initialize();
SubscribeLocalEvent<PlayerSpawnCompleteEvent>(HandleLateJoin);
}
protected override void Started(EntityUid uid, RandomAppearanceAspectComponent component, GameRuleComponent gameRule, GameRuleStartedEvent args)
{
base.Started(uid, component, gameRule, args);
var query = EntityQueryEnumerator<HumanoidAppearanceComponent>();
while (query.MoveNext(out var ent, out _))
{
EnsureComp<RandomHumanComponent>(ent);
}
}
private void HandleLateJoin(PlayerSpawnCompleteEvent ev)
{
var query = EntityQueryEnumerator<RandomAppearanceAspectComponent, GameRuleComponent>();
while (query.MoveNext(out var ruleEntity, out _, out var gameRule))
{
if (!GameTicker.IsGameRuleAdded(ruleEntity, gameRule))
continue;
if (!ev.LateJoin)
return;
var mob = ev.Mob;
EnsureComp<RandomHumanComponent>(mob);
}
}
}

View File

@@ -0,0 +1,130 @@
using System.Linq;
using Content.Server.Chat.Managers;
using Content.Server.Chat.Systems;
using Content.Server.GameTicking.Rules;
using Content.Server.GameTicking.Rules.Components;
using Content.Server.Mind;
using Content.Server.White.AspectsSystem.Aspects.Components;
using Content.Server.White.AspectsSystem.Base;
using Robust.Shared.Audio.Systems;
using Robust.Shared.Random;
namespace Content.Server.White.AspectsSystem.Aspects
{
public sealed class TraitoredAspect : AspectSystem<TraitoredAspectComponent>
{
[Dependency] private readonly TraitorRuleSystem _traitorRuleSystem = default!;
[Dependency] private readonly IChatManager _chatManager = default!;
[Dependency] private readonly ChatSystem _chatSystem = default!;
[Dependency] private readonly MindSystem _mindSystem = default!;
[Dependency] private readonly SharedAudioSystem _audio = default!;
[Dependency] private readonly IRobustRandom _random = default!;
private bool _announcedForTators;
private float _timeElapsed;
private float _timeElapsedForTators;
private float _wacky;
private const float WackyAaa = 60;
protected override void Started(EntityUid uid, TraitoredAspectComponent component, GameRuleComponent gameRule, GameRuleStartedEvent args)
{
base.Started(uid, component, gameRule, args);
// Just to make sure
ResetValues();
if (!HasTraitorGameRule())
ForceEndSelf(uid, gameRule);
_wacky = _random.Next(300, 360);
}
protected override void ActiveTick(EntityUid uid, TraitoredAspectComponent component, GameRuleComponent gameRule, float frameTime)
{
base.ActiveTick(uid, component, gameRule, frameTime);
_timeElapsedForTators += frameTime;
_timeElapsed += frameTime;
if (_timeElapsedForTators >= WackyAaa && !_announcedForTators)
{
AnnounceToTators(uid, gameRule);
_announcedForTators = true;
}
if (_timeElapsed >= _wacky)
{
AnnounceToAll(uid, gameRule);
}
}
protected override void Ended(EntityUid uid, TraitoredAspectComponent component, GameRuleComponent gameRule, GameRuleEndedEvent args)
{
base.Ended(uid, component, gameRule, args);
ResetValues();
}
#region Helpers
private void AnnounceToTators(EntityUid uid, GameRuleComponent rule)
{
var tators = _traitorRuleSystem.GetAllLivingConnectedTraitors();
if (tators.Count == 0)
{
ForceEndSelf(uid, rule);
}
foreach (var tator in tators)
{
if (!_mindSystem.TryGetSession(tator.Mind, out var session))
continue;
var nig = tator.Mind.OwnedEntity;
if (nig == null)
return;
_chatManager.DispatchServerMessage(session, "Внимание, коммуникации синдиката перехвачены, вас раскрыли!");
_audio.PlayEntity("/Audio/White/Aspects/palevo.ogg", nig.Value, nig.Value);
}
}
private void AnnounceToAll(EntityUid uid, GameRuleComponent rule)
{
var tators = _traitorRuleSystem.GetAllLivingConnectedTraitors();
var msg = "Станция, служба контрразведки нанотрейзен рассекретила секретную передачу Синдиката и выяснила имена проникниших на вашу станцию агентов. Агенты имеют следующие имена: \n";
foreach (var tator in tators)
{
var name = tator.Mind.CharacterName;
if (!string.IsNullOrEmpty(name))
{
msg += $" {name} - УБЕЙТЕ ЕГО НАХУЙ\n";
}
}
_chatSystem.DispatchGlobalAnnouncement(msg, "Мяукиман Крысус");
ForceEndSelf(uid, rule);
}
private void ResetValues()
{
_announcedForTators = false;
_timeElapsed = 0;
_timeElapsedForTators = 0;
}
private bool HasTraitorGameRule()
{
return EntityQuery<TraitorRuleComponent>().Any();
}
#endregion
}
}

View File

@@ -0,0 +1,26 @@
using Content.Server.GameTicking.Rules.Components;
using Content.Server.White.AspectsSystem.Aspects.Components;
using Content.Server.White.AspectsSystem.Base;
using Content.Shared.White;
using Robust.Shared.Configuration;
namespace Content.Server.White.AspectsSystem.Aspects;
public sealed class WeakAspect : AspectSystem<WeakAspectComponent>
{
[Dependency] private readonly IConfigurationManager _cfg = default!;
protected override void Started(EntityUid uid, WeakAspectComponent component, GameRuleComponent gameRule, GameRuleStartedEvent args)
{
base.Started(uid, component, gameRule, args);
_cfg.SetCVar(WhiteCVars.DamageGetModifier, 0.5f);
}
protected override void Ended(EntityUid uid, WeakAspectComponent component, GameRuleComponent gameRule, GameRuleEndedEvent args)
{
base.Ended(uid, component, gameRule, args);
_cfg.SetCVar(WhiteCVars.DamageGetModifier, 1.0f);
}
}

View File

@@ -0,0 +1,35 @@
using System.Linq;
using Content.Server.Destructible;
using Content.Server.Destructible.Thresholds.Triggers;
using Content.Server.GameTicking.Rules.Components;
using Content.Server.White.AspectsSystem.Aspects.Components;
using Content.Server.White.AspectsSystem.Base;
using Content.Server.White.Other;
namespace Content.Server.White.AspectsSystem.Aspects;
public sealed class WeakWallsAspect : AspectSystem<WeakWallsAspectComponent>
{
private const int DamageToSet = 100;
protected override void Started(EntityUid uid, WeakWallsAspectComponent component, GameRuleComponent gameRule, GameRuleStartedEvent args)
{
base.Started(uid, component, gameRule, args);
var query = EntityQueryEnumerator<WallMarkComponent>();
while (query.MoveNext(out var ent, out _))
{
if (!TryComp<DestructibleComponent>(ent, out var destructible))
continue;
var trigger = (DamageTrigger?) destructible.Thresholds
.LastOrDefault(threshold => threshold.Trigger is DamageTrigger)?.Trigger;
if (trigger == null)
continue;
trigger.Damage = (trigger.Damage == DamageToSet) ? trigger.Damage : DamageToSet;
}
}
}

View File

@@ -0,0 +1,39 @@
using Robust.Shared.Audio;
using Robust.Shared.Serialization.TypeSerializers.Implementations.Custom;
namespace Content.Server.White.AspectsSystem.Base
{
[RegisterComponent]
public sealed partial class AspectComponent : Component
{
[DataField("name")]
public string? Name;
[DataField("description")]
public string? Description;
[DataField("requires")]
public string? Requires;
[DataField("weight")]
public float Weight = 1.0f;
[DataField("forbidden")]
public bool IsForbidden;
[DataField("hidden")]
public bool IsHidden;
[DataField("startAudio")]
public SoundSpecifier? StartAudio;
[DataField("endAudio")]
public SoundSpecifier? EndAudio;
[DataField("startDelay")]
public TimeSpan StartDelay = TimeSpan.Zero;
[DataField("startTime", customTypeSerializer: typeof(TimeOffsetSerializer))]
public TimeSpan StartTime;
}
}

View File

@@ -0,0 +1,211 @@
using System.Diagnostics.CodeAnalysis;
using Content.Server.Administration.Logs;
using Content.Server.Atmos.EntitySystems;
using Content.Server.Chat.Systems;
using Content.Server.GameTicking.Rules;
using Content.Server.GameTicking.Rules.Components;
using Content.Server.Station.Components;
using Content.Shared.Database;
using Robust.Server.GameObjects;
using Robust.Shared.Audio.Systems;
using Robust.Shared.Collections;
using Robust.Shared.Map;
using Robust.Shared.Map.Components;
using Robust.Shared.Player;
using Robust.Shared.Random;
using Robust.Shared.Timing;
namespace Content.Server.White.AspectsSystem.Base
{
/// <summary>
/// Base class for aspect systems.
/// </summary>
/// <typeparam name="T">The type of component to which the system is applied.</typeparam>
public abstract class AspectSystem<T> : GameRuleSystem<T> where T : Component
{
[Dependency] private readonly IAdminLogManager _adminLogManager = default!;
[Dependency] private readonly IGameTiming _timing = default!;
[Dependency] private readonly ChatSystem _chatSystem = default!;
[Dependency] private readonly SharedAudioSystem _audio = default!;
[Dependency] private readonly IRobustRandom _robustRandom = default!;
[Dependency] private readonly AtmosphereSystem _atmosphere = default!;
[Dependency] private readonly TransformSystem _transform = default!;
protected ISawmill Sawmill = default!;
public override void Initialize()
{
base.Initialize();
Sawmill = Logger.GetSawmill("aspects");
}
/// <summary>
/// Called every tick when this aspect is running.
/// </summary>
public override void Update(float frameTime)
{
base.Update(frameTime);
var query = EntityQueryEnumerator<AspectComponent, GameRuleComponent>();
while (query.MoveNext(out var uid, out var aspect, out var ruleData))
{
if (!GameTicker.IsGameRuleAdded(uid, ruleData))
continue;
if (!GameTicker.IsGameRuleActive(uid, ruleData) && _timing.CurTime >= aspect.StartTime)
{
GameTicker.StartGameRule(uid, ruleData);
}
}
}
/// <summary>
/// Called when an aspect is added to an entity.
/// </summary>
protected override void Added(EntityUid uid, T component, GameRuleComponent gameRule, GameRuleAddedEvent args)
{
base.Added(uid, component, gameRule, args);
if (!TryComp<AspectComponent>(uid, out var aspect))
return;
_adminLogManager.Add(LogType.AspectAnnounced, $"Aspect added {ToPrettyString(uid)}");
if (aspect is { Description: not null, IsHidden: false })
{
_chatSystem.DispatchGlobalAnnouncement(aspect.Description, playSound: false, colorOverride: Color.LimeGreen);
}
_audio.PlayGlobal(aspect.StartAudio, Filter.Broadcast(), true);
aspect.StartTime = _timing.CurTime + aspect.StartDelay;
}
/// <summary>
/// Called when an aspect is started.
/// </summary>
protected override void Started(EntityUid uid, T component, GameRuleComponent gameRule, GameRuleStartedEvent args)
{
base.Started(uid, component, gameRule, args);
if (!TryComp<AspectComponent>(uid, out _))
return;
_adminLogManager.Add(LogType.AspectStarted, LogImpact.High, $"Aspect started: {ToPrettyString(uid)}");
}
/// <summary>
/// Called when an aspect is ended.
/// </summary>
protected override void Ended(EntityUid uid, T component, GameRuleComponent gameRule, GameRuleEndedEvent args)
{
base.Ended(uid, component, gameRule, args);
if (!TryComp<AspectComponent>(uid, out var aspect))
return;
_adminLogManager.Add(LogType.AspectStopped, $"Aspect ended: {ToPrettyString(uid)}");
if (aspect is { Name: not null, IsHidden: false })
{
_chatSystem.DispatchGlobalAnnouncement($"Именем аспекта являлось: {aspect.Name}", playSound: false, colorOverride: Color.LimeGreen);
}
_audio.PlayGlobal(aspect.EndAudio, Filter.Broadcast(), true);
}
#region Helpers
/// <summary>
/// Forces this aspect to end prematurely.
/// </summary>
/// <param name="uid">The entity UID on which the aspect is being performed.</param>
/// <param name="component">The game rule component associated with this aspect (optional).</param>
protected void ForceEndSelf(EntityUid uid, GameRuleComponent? component = null)
{
GameTicker.EndGameRule(uid, component);
}
protected bool TryGetRandomStation([NotNullWhen(true)] out EntityUid? station, Func<EntityUid, bool>? filter = null)
{
var stations = new ValueList<EntityUid>();
if (filter == null)
{
stations.EnsureCapacity(Count<StationEventEligibleComponent>());
}
filter ??= _ => true;
var query = AllEntityQuery<StationEventEligibleComponent>();
while (query.MoveNext(out var uid, out _))
{
if (!filter(uid))
continue;
stations.Add(uid);
}
if (stations.Count == 0)
{
station = null;
return false;
}
station = stations[_robustRandom.Next(stations.Count)];
return true;
}
protected bool TryFindRandomTile(out Vector2i tile, [NotNullWhen(true)] out EntityUid? targetStation, out EntityUid targetGrid, out EntityCoordinates targetCoords)
{
tile = default;
targetCoords = EntityCoordinates.Invalid;
if (!TryGetRandomStation(out targetStation))
{
targetStation = EntityUid.Invalid;
targetGrid = EntityUid.Invalid;
return false;
}
var possibleTargets = Comp<StationDataComponent>(targetStation.Value).Grids;
if (possibleTargets.Count == 0)
{
targetGrid = EntityUid.Invalid;
return false;
}
targetGrid = _robustRandom.Pick(possibleTargets);
if (!TryComp<MapGridComponent>(targetGrid, out var gridComp))
return false;
var found = false;
var (gridPos, _, gridMatrix) = _transform.GetWorldPositionRotationMatrix(targetGrid);
var gridBounds = gridMatrix.TransformBox(gridComp.LocalAABB);
for (var i = 0; i < 10; i++)
{
var randomX = _robustRandom.Next((int) gridBounds.Left, (int) gridBounds.Right);
var randomY = _robustRandom.Next((int) gridBounds.Bottom, (int) gridBounds.Top);
tile = new Vector2i(randomX - (int) gridPos.X, randomY - (int) gridPos.Y);
if (_atmosphere.IsTileSpace(targetGrid, Transform(targetGrid).MapUid, tile,
mapGridComp: gridComp)
|| _atmosphere.IsTileAirBlocked(targetGrid, tile, mapGridComp: gridComp))
{
continue;
}
found = true;
targetCoords = gridComp.GridTileToLocal(tile);
break;
}
return found;
}
#endregion
}
}

View File

@@ -0,0 +1,145 @@
using Content.Server.Administration;
using Content.Server.GameTicking;
using Content.Server.White.AspectsSystem.Managers;
using Content.Shared.Administration;
using Robust.Shared.Console;
namespace Content.Server.White.AspectsSystem.Commands
{
[AdminCommand(AdminFlags.Fun)]
public sealed class ForceAspectCommand : IConsoleCommand
{
public string Command => "forceaspect";
public string Description => "Принудительно форсит аспект по его ID.";
public string Help => "forceaspect <aspectId>";
public void Execute(IConsoleShell shell, string argStr, string[] args)
{
var ticker = EntitySystem.Get<GameTicker>();
if (ticker.RunLevel != GameRunLevel.PreRoundLobby)
{
shell.WriteLine("This can only be executed while the game is in the pre-round lobby.");
return;
}
if (args.Length != 1)
{
shell.WriteError("Использование: forceaspect <aspectId>");
return;
}
var aspectId = args[0];
var aspectManager = EntitySystem.Get<AspectManager>();
var result = aspectManager.ForceAspect(aspectId);
shell.WriteLine(result);
}
}
[AdminCommand(AdminFlags.Fun)]
public sealed class DeForceAspectCommand : IConsoleCommand
{
public string Command => "deforceaspect";
public string Description => "Дефорсит принудительно установленный аспект.";
public string Help => "deforceaspect";
public void Execute(IConsoleShell shell, string argStr, string[] args)
{
var ticker = EntitySystem.Get<GameTicker>();
if (ticker.RunLevel != GameRunLevel.PreRoundLobby)
{
shell.WriteLine("This can only be executed while the game is in the pre-round lobby.");
return;
}
var aspectManager = EntitySystem.Get<AspectManager>();
var result = aspectManager.DeForceAspect();
shell.WriteLine(result);
}
}
[AdminCommand(AdminFlags.Fun)]
public sealed class GetForcedAspectCommand : IConsoleCommand
{
public string Command => "getforcedaspect";
public string Description => "Получает информацию о принудительно установленном аспекте.";
public string Help => "getforcedaspect";
public void Execute(IConsoleShell shell, string argStr, string[] args)
{
var ticker = EntitySystem.Get<GameTicker>();
if (ticker.RunLevel != GameRunLevel.PreRoundLobby)
{
shell.WriteLine("This can only be executed while the game is in the pre-round lobby.");
return;
}
var aspectManager = EntitySystem.Get<AspectManager>();
var result = aspectManager.GetForcedAspect();
shell.WriteLine(result);
}
}
[AdminCommand(AdminFlags.Fun)]
public sealed class ListAspectsCommand : IConsoleCommand
{
public string Command => "listaspects";
public string Description => "Список всех доступных аспектов.";
public string Help => "listaspects";
public void Execute(IConsoleShell shell, string argStr, string[] args)
{
var aspectManager = EntitySystem.Get<AspectManager>();
var aspectIds = aspectManager.GetAllAspectIds();
if (aspectIds.Count == 0)
{
shell.WriteLine("Нет доступных аспектов.");
}
else
{
shell.WriteLine("Список доступных аспектов:");
foreach (var aspectId in aspectIds)
{
shell.WriteLine(aspectId);
}
}
}
}
[AdminCommand(AdminFlags.Fun)]
public sealed class RunAspectCommand : IConsoleCommand
{
public string Command => "runaspect";
public string Description => "Запускает аспект по его ID.";
public string Help => "runaspect <aspectId>";
public void Execute(IConsoleShell shell, string argStr, string[] args)
{
if (args.Length != 1)
{
shell.WriteError("Использование: runaspect <aspectId>");
return;
}
var aspectId = args[0];
var aspectManager = EntitySystem.Get<AspectManager>();
var result = aspectManager.RunAspect(aspectId);
shell.WriteLine(result);
}
}
[AdminCommand(AdminFlags.Fun)]
public sealed class RunRandomAspectCommand : IConsoleCommand
{
public string Command => "runrandomaspect";
public string Description => "Запускает случайный аспект.";
public string Help => "runrandomaspect";
public void Execute(IConsoleShell shell, string argStr, string[] args)
{
var aspectManager = EntitySystem.Get<AspectManager>();
var result = aspectManager.RunRandomAspect();
shell.WriteLine(result);
}
}
}

View File

@@ -0,0 +1,378 @@
using Content.Server.GameTicking;
using Content.Server.White.AspectsSystem.Base;
using Content.Shared.GameTicking;
using Content.Shared.White;
using Robust.Shared.Configuration;
using Robust.Shared.Prototypes;
using Robust.Shared.Random;
namespace Content.Server.White.AspectsSystem.Managers
{
/// <summary>
/// Manager for aspects.
/// </summary>
public sealed class AspectManager : EntitySystem
{
[Dependency] private readonly IPrototypeManager _prototype = default!;
[Dependency] private readonly IRobustRandom _random = default!;
[Dependency] private readonly GameTicker _gameTicker = default!;
[Dependency] private readonly IConfigurationManager _cfg = default!;
private ISawmill _sawmill = default!;
private bool AspectsEnabled { get; set; }
private double Chance { get; set; }
private void SetEnabled(bool value) => AspectsEnabled = value;
private void SetChance(double value) => Chance = value;
private string? ForcedAspect { get; set; }
public override void Initialize()
{
base.Initialize();
_sawmill = Logger.GetSawmill("aspects");
_cfg.OnValueChanged(WhiteCVars.IsAspectsEnabled, SetEnabled, true);
_cfg.OnValueChanged(WhiteCVars.AspectChance, SetChance, true);
SubscribeLocalEvent<RoundStartedEvent>(OnRoundStarted);
SubscribeLocalEvent<RoundRestartCleanupEvent>(OnRestart);
}
#region Handlers
private void OnRoundStarted(RoundStartedEvent ev)
{
if (!AspectsEnabled)
return;
if (ForcedAspect != null)
{
RunAspect(ForcedAspect);
ForcedAspect = null;
return;
}
if (_random.NextDouble() <= Chance)
{
RunRandomAspect();
}
}
// Put here needed cleanup.
private void OnRestart(RoundRestartCleanupEvent ev)
{
_cfg.SetCVar(WhiteCVars.DamageModifier, 1.0f);
_cfg.SetCVar(WhiteCVars.DamageGetModifier, 1.0f);
}
#endregion
#region PublicApi
/// <summary>
/// Forces a specific aspect by its prototype ID.
/// </summary>
/// <param name="aspectProtoId">The prototype ID of the aspect to be forced.</param>
public string ForceAspect(string aspectProtoId)
{
if (!AspectsEnabled)
{
var disabledStr = "Aspects disabled.";
_sawmill.Warning("Someone tried to force aspect when they disabled!");
return disabledStr;
}
if (!_prototype.TryIndex<EntityPrototype>(aspectProtoId, out var entityPrototype))
{
var response = "Aspect not found. Can`t find proto";
_sawmill.Warning("Someone tried to force invalid Aspect!");
return response;
}
if (!entityPrototype.TryGetComponent<AspectComponent>(out _))
{
var errStr = $"Aspect with ID '{aspectProtoId}' not found or does not have an AspectComponent!";
_sawmill.Error(errStr);
return errStr;
}
if (ForcedAspect == aspectProtoId)
{
var errStr = $"Aspect with ID '{aspectProtoId}' already forced!";
_sawmill.Error(errStr);
return errStr;
}
ForcedAspect = aspectProtoId;
var str = $"Successfully forced Aspect with ID '{aspectProtoId}'";
_sawmill.Info(str);
return str;
}
/// <summary>
/// DeForces a ForcedAspect, if any.
/// </summary>
public string DeForceAspect()
{
string response;
if (ForcedAspect != null)
{
response = $"DeForced Aspect : {ForcedAspect}";
ForcedAspect = null;
}
else
{
response = "How to DeForce if no aspect forced, retard..";
}
return response;
}
/// <summary>
/// Retrieves information about the currently forced aspect, if any.
/// </summary>
public string GetForcedAspect()
{
var response = ForcedAspect != null
? $"Current forced Aspect : {ForcedAspect}"
: "No forced Aspects";
return response;
}
/// <summary>
/// Retrieves a list of IDs for all available aspects.
/// </summary>
/// <returns>A list of IDs for available aspects.</returns>
public List<string> GetAllAspectIds()
{
var availableAspects = AllAspects();
var aspectIds = new List<string>();
foreach (var (proto, aspect) in availableAspects)
{
var aspectId = proto.ID;
if (aspect.Requires != null)
{
aspectId += $" (Requires: {aspect.Requires})";
}
if (aspect.IsForbidden)
{
aspectId += " (ShitSpawn)";
}
if (ForcedAspect == aspectId)
{
aspectId += " (Forced)";
}
if (CheckIfAspectAlreadyRunning(aspectId))
{
aspectId += " (Already Running)";
}
aspectIds.Add(aspectId);
}
return aspectIds;
}
/// <summary>
/// Runs the specified aspect and adds it as a game rule.
/// </summary>
/// <param name="aspectId">The ID of the aspect to run.</param>
public string RunAspect(string aspectId)
{
if (!AspectsEnabled)
{
var disabledStr = "Aspects disabled.";
_sawmill.Warning("Someone tried to run aspects when they disabled!");
return disabledStr;
}
if (!_prototype.TryIndex<EntityPrototype>(aspectId, out var entityPrototype))
{
var response = "Aspect not found. Can`t find proto";
_sawmill.Warning("Someone tried to run invalid Aspect!");
return response;
}
if (!entityPrototype.TryGetComponent<AspectComponent>(out var aspect))
{
var errStr = $"Aspect with ID '{aspectId}' not found or does not have an AspectComponent!";
_sawmill.Error(errStr);
return errStr;
}
if (CheckIfAspectAlreadyRunning(aspectId))
{
var alreadyRunningStr = $"Aspect '{aspectId}' is already running!";
_sawmill.Warning(alreadyRunningStr);
return alreadyRunningStr;
}
var ent = _gameTicker.AddGameRule(aspectId);
var str = $"Ran {aspect.Name ?? "Unnamed Aspect"} ({ToPrettyString(ent)})!!";
_sawmill.Info(str);
return str;
}
/// <summary>
/// Runs a random aspect and adds it as a game rule.
/// </summary>
public string RunRandomAspect()
{
if (!AspectsEnabled)
{
var disabledStr = "Aspects disabled.";
_sawmill.Warning("Someone tried to run aspects when they disabled!");
return disabledStr;
}
var randomAspect = PickRandomAspect();
if (randomAspect == null)
{
var errStr = "Oopsie, no valid aspects found! Sorry.";
_sawmill.Error(errStr);
return errStr;
}
var ent = _gameTicker.AddGameRule(randomAspect);
var str = $"Ran {ToPrettyString(ent)}!!";
_sawmill.Info(str);
return str;
}
#endregion
#region Helpers
/// <summary>
/// Picks a random aspect based on their weight.
/// </summary>
/// <param name="allowForbidden">Allow selecting forbidden aspects.</param>
/// <returns>The ID of the selected aspect or null if no aspect was selected.</returns>
private string? PickRandomAspect(bool allowForbidden = false)
{
var availableAspects = AllAspects();
_sawmill.Info($"Picking from {availableAspects.Count} total available aspects");
return FindAspect(availableAspects, allowForbidden);
}
/// <summary>
/// Finds a suitable aspect from the available aspects.
/// </summary>
/// <param name="availableAspects">A dictionary of available aspects.</param>
/// <param name="allowForbidden">Allow selecting forbidden aspects.</param>
/// <returns>The ID of the selected aspect or null if no aspect was found.</returns>
private string? FindAspect(Dictionary<EntityPrototype, AspectComponent> availableAspects, bool allowForbidden = false)
{
if (availableAspects.Count == 0)
{
_sawmill.Warning("No aspects were available to run!");
return null;
}
var sumOfWeights = 0;
foreach (var (_, aspect) in availableAspects)
{
if (!allowForbidden && aspect.IsForbidden)
{
continue;
}
sumOfWeights += (int)aspect.Weight;
}
sumOfWeights = _random.Next(sumOfWeights);
foreach (var (proto, aspect) in availableAspects)
{
if (!allowForbidden && aspect.IsForbidden)
{
continue;
}
if (CheckIfAspectAlreadyRunning(proto.ID))
{
continue;
}
sumOfWeights -= (int)aspect.Weight;
if (sumOfWeights <= 0)
{
return proto.ID;
}
}
_sawmill.Error("Aspect was not found after weighted pick process!");
return null;
}
/// <summary>
/// Checking if aspect is already running, needed to avoid repeating.
/// </summary>
private bool CheckIfAspectAlreadyRunning(string aspectId)
{
var activeRules = _gameTicker.GetActiveGameRules();
foreach (var gameRule in activeRules)
{
if (!HasComp<AspectComponent>(gameRule))
continue;
if (!TryComp<MetaDataComponent>(gameRule, out var metaDataComponent))
continue;
var runningAspectId = metaDataComponent.EntityPrototype?.ID;
if (runningAspectId == aspectId)
{
return true;
}
}
return false;
}
/// <summary>
/// Retrieves a dictionary of all available aspects from prototypes.
/// </summary>
/// <returns>A dictionary of available aspects.</returns>
private Dictionary<EntityPrototype, AspectComponent> AllAspects()
{
var allAspects = new Dictionary<EntityPrototype, AspectComponent>();
foreach (var prototype in _prototype.EnumeratePrototypes<EntityPrototype>())
{
if (prototype.Abstract)
continue;
if (!prototype.TryGetComponent<AspectComponent>(out var aspect))
continue;
allAspects.Add(prototype, aspect);
}
return allAspects;
}
#endregion
}
}

View File

@@ -0,0 +1,9 @@
namespace Content.Server.White.Other;
/*
* Used for mark chairs, used by I Have Two Butts But I must Seat Aspect.
*/
[RegisterComponent]
public sealed partial class ChairMarkComponent : Component
{
}

View File

@@ -0,0 +1,9 @@
namespace Content.Server.White.Other;
/*
* Used for mark engines, used by NoEngine Aspect.
*/
[RegisterComponent]
public sealed partial class EngineMarkComponent : Component
{
}

View File

@@ -0,0 +1,6 @@
namespace Content.Server.White.Other.RandomHumanSystem;
[RegisterComponent]
public sealed partial class RandomHumanComponent : Component
{
}

View File

@@ -0,0 +1,49 @@
using Content.Server.Access.Systems;
using Content.Server.Humanoid;
using Content.Server.IdentityManagement;
using Content.Server.PDA;
using Content.Shared.Access.Components;
using Content.Shared.Inventory;
using Content.Shared.PDA;
using Content.Shared.Preferences;
namespace Content.Server.White.Other.RandomHumanSystem;
public sealed class RandomHumanSystem : EntitySystem
{
[Dependency] private readonly HumanoidAppearanceSystem _humanoid = default!;
[Dependency] private readonly MetaDataSystem _metaData = default!;
[Dependency] private readonly InventorySystem _inventorySystem = default!;
[Dependency] private readonly IdCardSystem _card = default!;
[Dependency] private readonly PdaSystem _pda = default!;
[Dependency] private readonly IdentitySystem _identity = default!;
public override void Initialize()
{
base.Initialize();
SubscribeLocalEvent<RandomHumanComponent, ComponentInit>(OnInit);
}
private void OnInit(EntityUid uid, RandomHumanComponent component, ComponentInit args)
{
var newProfile = HumanoidCharacterProfile.RandomWithSpecies();
_humanoid.LoadProfile(uid, newProfile);
_metaData.SetEntityName(uid, newProfile.Name);
if (!_inventorySystem.TryGetSlotEntity(uid, "id", out var idUid))
return;
if (!EntityManager.TryGetComponent(idUid, out PdaComponent? pdaComponent) || !TryComp<IdCardComponent>(pdaComponent.ContainedId, out var card))
return;
var cardId = pdaComponent.ContainedId.Value;
_card.TryChangeFullName(cardId, newProfile.Name, card);
_pda.SetOwner(idUid.Value, pdaComponent, newProfile.Name);
_identity.QueueIdentityUpdate(uid);
}
}

View File

@@ -0,0 +1,9 @@
namespace Content.Server.White.Other;
/*
* Used for mark walls, used by WeakWalls Aspect.
*/
[RegisterComponent]
public sealed partial class WallMarkComponent : Component
{
}

View File

@@ -9,6 +9,9 @@ public enum LogType
Healed = 3,
Slip = 4,
EventAnnounced = 5,
AspectAnnounced = 86,
AspectStarted = 87,
AspectStopped = 88,
EventStarted = 6,
EventRan = 16,
EventStopped = 7,

View File

@@ -8,6 +8,8 @@ using Content.Shared.Mobs.Components;
using Content.Shared.Mobs.Systems;
using Content.Shared.Radiation.Events;
using Content.Shared.Rejuvenate;
using Content.Shared.White;
using Robust.Shared.Configuration;
using Robust.Shared.GameStates;
using Robust.Shared.Network;
using Robust.Shared.Prototypes;
@@ -22,6 +24,12 @@ namespace Content.Shared.Damage
[Dependency] private readonly SharedAppearanceSystem _appearance = default!;
[Dependency] private readonly INetManager _netMan = default!;
[Dependency] private readonly MobThresholdSystem _mobThreshold = default!;
[Dependency] private readonly IConfigurationManager _cfg = default!;
private float DamageGetModifier { get; set; }
private void SetDamage(float value) => DamageGetModifier = value;
private EntityQuery<AppearanceComponent> _appearanceQuery;
private EntityQuery<DamageableComponent> _damageableQuery;
@@ -29,6 +37,8 @@ namespace Content.Shared.Damage
public override void Initialize()
{
_cfg.OnValueChanged(WhiteCVars.DamageGetModifier, SetDamage, true);
SubscribeLocalEvent<DamageableComponent, ComponentInit>(DamageableInit);
SubscribeLocalEvent<DamageableComponent, ComponentHandleState>(DamageableHandleState);
SubscribeLocalEvent<DamageableComponent, ComponentGetState>(DamageableGetState);
@@ -139,6 +149,7 @@ namespace Content.Shared.Damage
return damage;
}
damage *= DamageGetModifier;
var before = new BeforeDamageChangedEvent(damage, origin);
RaiseLocalEvent(uid.Value, ref before);

View File

@@ -21,15 +21,16 @@ using Content.Shared.Weapons.Melee.Events;
using Content.Shared.Weapons.Ranged.Components;
using Content.Shared.Weapons.Ranged.Events;
using Content.Shared.Weapons.Ranged.Systems;
using Content.Shared.White;
using Robust.Shared.Audio;
using Robust.Shared.Audio.Systems;
using Robust.Shared.Configuration;
using Robust.Shared.Map;
using Robust.Shared.Physics;
using Robust.Shared.Physics.Systems;
using Robust.Shared.Player;
using Robust.Shared.Prototypes;
using Robust.Shared.Timing;
using Robust.Shared.Toolshed.Syntax;
using ItemToggleMeleeWeaponComponent = Content.Shared.Item.ItemToggle.Components.ItemToggleMeleeWeaponComponent;
namespace Content.Shared.Weapons.Melee;
@@ -50,6 +51,7 @@ public abstract class SharedMeleeWeaponSystem : EntitySystem
[Dependency] protected readonly SharedPopupSystem PopupSystem = default!;
[Dependency] protected readonly SharedTransformSystem TransformSystem = default!;
[Dependency] private readonly StaminaSystem _stamina = default!;
[Dependency] private readonly IConfigurationManager _cfg = default!;
public const float DamagePitchVariation = 0.05f;
private const int AttackMask = (int) (CollisionGroup.MobMask | CollisionGroup.Opaque);
@@ -59,6 +61,10 @@ public abstract class SharedMeleeWeaponSystem : EntitySystem
/// </summary>
public const int MaxTargets = 5;
private float DamageModifier { get; set; }
private void SetDamage(float value) => DamageModifier = value;
/// <summary>
/// If an attack is released within this buffer it's assumed to be full damage.
/// </summary>
@@ -68,6 +74,8 @@ public abstract class SharedMeleeWeaponSystem : EntitySystem
{
base.Initialize();
_cfg.OnValueChanged(WhiteCVars.DamageModifier, SetDamage, true);
SubscribeLocalEvent<MeleeWeaponComponent, EntityUnpausedEvent>(OnMeleeUnpaused);
SubscribeLocalEvent<MeleeWeaponComponent, HandSelectedEvent>(OnMeleeSelected);
SubscribeLocalEvent<MeleeWeaponComponent, ShotAttemptedEvent>(OnMeleeShotAttempted);
@@ -238,7 +246,9 @@ public abstract class SharedMeleeWeaponSystem : EntitySystem
var ev = new GetMeleeDamageEvent(uid, new (component.Damage), new(), user);
RaiseLocalEvent(uid, ref ev);
return DamageSpecifier.ApplyModifierSets(ev.Damage, ev.Modifiers);
var modifiedDamage = ev.Damage * DamageModifier;
return DamageSpecifier.ApplyModifierSets(modifiedDamage, ev.Modifiers);
}
public float GetAttackRate(EntityUid uid, EntityUid user, MeleeWeaponComponent? component = null)

View File

@@ -302,4 +302,27 @@ public sealed class WhiteCVars
public static readonly CVarDef<bool> LogChatActions =
CVarDef.Create("white.log_to_chat", true, CVar.CLIENT | CVar.ARCHIVE | CVar.REPLICATED);
/*
* Aspects
*/
public static readonly CVarDef<bool> IsAspectsEnabled =
CVarDef.Create("aspects.enabled", false, CVar.SERVERONLY);
public static readonly CVarDef<double> AspectChance =
CVarDef.Create("aspects.chance", 0.66, CVar.SERVERONLY);
/*
* Damage
*/
// Applies Projectile and Melee damage.
public static readonly CVarDef<float> DamageModifier =
CVarDef.Create("damage.modifier", 1.0f, CVar.REPLICATED);
// Applies ALL damage, EVEN walls and etc.
public static readonly CVarDef<float> DamageGetModifier =
CVarDef.Create("damage.get_modifier", 1.0f, CVar.REPLICATED);
}

Binary file not shown.

Binary file not shown.

View File

@@ -67,8 +67,8 @@
- type: ExplodeOnTrigger
- type: Explosive
explosionType: Default
maxIntensity: 10
intensitySlope: 3
totalIntensity: 120 # about a ~4 tile radius
maxIntensity: 3
intensitySlope: 1
totalIntensity: 120
canCreateVacuum: false
- type: DeleteOnTrigger

View File

@@ -48,6 +48,7 @@
path: /Audio/Effects/metalbreak.ogg
- type: StaticPrice
price: 10
- type: ChairMark
- type: entity
name: chair

View File

@@ -33,4 +33,5 @@
- type: Pullable
- type: GuideHelp
guides: [ Singularity, Power ]
- type: EngineMark

View File

@@ -49,6 +49,7 @@
price: 75
- type: RadiationBlocker
resistance: 2
- type: WallMark
- type: entity
parent: BaseWall

View File

@@ -0,0 +1,181 @@
- type: entity
id: RandomAccentAspect
parent: BaseGameRule
noSpawn: true
components:
- type: Aspect
name: "Акценты вне контроля"
description: "Всегда интересно, какой акцент вы услышите следующим."
weight: 3
startAudio:
path: /Audio/White/Aspects/accent.ogg
- type: RandomAccentAspect
- type: entity
id: FastAndFuriousAspect
parent: BaseGameRule
noSpawn: true
components:
- type: Aspect
name: "Fast and Furious"
description: "Люди спешат и не важно куда."
weight: 3
startAudio:
path: /Audio/White/Aspects/accent.ogg
- type: FastAndFuriousAspect
- type: entity
id: RandomAppearanceAspect
parent: BaseGameRule
noSpawn: true
components:
- type: Aspect
name: "Random appearance"
description: "Экипаж перестал узнавать друг-друга в лицо."
weight: 3
startAudio:
path: /Audio/White/Aspects/accent.ogg
- type: RandomAppearanceAspect
- type: entity
id: BombassAspect
parent: BaseGameRule
noSpawn: true
components:
- type: Aspect
name: "Bombass"
description: "Кто-то заложил мины на станции!"
weight: 1
startAudio:
path: /Audio/White/Aspects/accent.ogg
- type: BombassAspect
- type: entity
id: DrunkAspect
parent: BaseGameRule
noSpawn: true
components:
- type: Aspect
name: "Drunk"
description: "На станции стоит явный запах вчерашнего веселья... и кажется оно только начинается."
weight: 3
startAudio:
path: /Audio/White/Aspects/accent.ogg
- type: DrunkAspect
- type: entity
id: CargoRichAspect
parent: BaseGameRule
noSpawn: true
components:
- type: Aspect
name: "Cargo Rich"
description: "Карго работало усердно в прошлую смену, за что они и были награждены премией в размере 100000 кредитов."
weight: 2
startAudio:
path: /Audio/White/Aspects/accent.ogg
- type: CargoRichAspect
- type: entity
id: TraitoredAspect
parent: BaseGameRule
noSpawn: true
components:
- type: Aspect
name: "Traitored"
description: "Кто-то сдал всех предателей!"
requires: "Traitors"
weight: 1
hidden: true
- type: TraitoredAspect
- type: entity
id: WeakWallsAspect
parent: BaseGameRule
noSpawn: true
components:
- type: Aspect
name: "Weak Walls"
description: "На стенах явно экономили."
weight: 2
startAudio:
path: /Audio/White/Aspects/accent.ogg
- type: WeakWallsAspect
- type: entity
id: NoEngineAspect
parent: BaseGameRule
noSpawn: true
components:
- type: Aspect
name: "No engine"
description: "Какой-то смышлённый агент синдиката решил украсть все ваши генераторы энергии целиком."
weight: 2
startAudio:
path: /Audio/White/Aspects/accent.ogg
- type: NoEngineAspect
- type: entity
id: AiRunLockAspect
parent: BaseGameRule
noSpawn: true
components:
- type: Aspect
name: "Airunlock"
description: "Кого волнует безопасность? Экипаж свободно может ходить по всем отсекам, ведь все шлюзы теперь для них доступны."
weight: 2
startAudio:
path: /Audio/White/Aspects/accent.ogg
- type: AiRunLockAspect
- type: entity
id: BloodyAspect
parent: BaseGameRule
noSpawn: true
components:
- type: Aspect
name: "Bloody"
description: "В эту смену любая незначительная травма может оказаться летальной."
weight: 1
startAudio:
path: /Audio/White/Aspects/accent.ogg
- type: BloodyAspect
- type: entity
id: WeakAspect
parent: BaseGameRule
noSpawn: true
components:
- type: Aspect
name: "Weak"
description: "Удары стали слабее. Пули мягче. К чему это приведёт?"
weight: 1
startAudio:
path: /Audio/White/Aspects/accent.ogg
- type: WeakAspect
- type: entity
id: BattledAspect
parent: BaseGameRule
noSpawn: true
components:
- type: Aspect
name: "Battled"
description: "Люди очень насторожены и готовы дать отпор в любую секунду."
weight: 1
startAudio:
path: /Audio/White/Aspects/accent.ogg
- type: BattledAspect
- type: entity
id: ChairLeakAspect
parent: BaseGameRule
noSpawn: true
components:
- type: Aspect
name: "I Have Two Butts But I must Seat"
description: "Стулья украли!"
weight: 3
startAudio:
path: /Audio/White/Aspects/accent.ogg
- type: ChairLeakAspect

View File

@@ -64,3 +64,4 @@
maxIntensity: 10000
intensitySlope: 10
totalIntensity: 10000
- type: EngineMark