Gamerule Entities, Take 2 (#15765)
This commit is contained in:
@@ -1,23 +0,0 @@
|
|||||||
using System;
|
|
||||||
using Content.Shared.Suspicion;
|
|
||||||
using Robust.Shared.GameObjects;
|
|
||||||
|
|
||||||
namespace Content.Client.Suspicion
|
|
||||||
{
|
|
||||||
public sealed class SuspicionEndTimerSystem : EntitySystem
|
|
||||||
{
|
|
||||||
public TimeSpan? EndTime { get; private set; }
|
|
||||||
|
|
||||||
public override void Initialize()
|
|
||||||
{
|
|
||||||
base.Initialize();
|
|
||||||
|
|
||||||
SubscribeNetworkEvent<SuspicionMessages.SetSuspicionEndTimerMessage>(RxTimerMessage);
|
|
||||||
}
|
|
||||||
|
|
||||||
private void RxTimerMessage(SuspicionMessages.SetSuspicionEndTimerMessage ev)
|
|
||||||
{
|
|
||||||
EndTime = ev.EndTime;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,8 +0,0 @@
|
|||||||
<sus:SuspicionGui xmlns="https://spacestation14.io"
|
|
||||||
xmlns:sus="clr-namespace:Content.Client.Suspicion">
|
|
||||||
<BoxContainer Orientation="Vertical" SeparationOverride="0">
|
|
||||||
<Button Name="RoleButton">
|
|
||||||
<Label Name="TimerLabel" HorizontalAlignment="Right" VerticalAlignment="Bottom" />
|
|
||||||
</Button>
|
|
||||||
</BoxContainer>
|
|
||||||
</sus:SuspicionGui>
|
|
||||||
@@ -1,126 +0,0 @@
|
|||||||
using System;
|
|
||||||
using System.Diagnostics.CodeAnalysis;
|
|
||||||
using System.Globalization;
|
|
||||||
using System.Linq;
|
|
||||||
using Content.Shared.Popups;
|
|
||||||
using Robust.Client.AutoGenerated;
|
|
||||||
using Robust.Client.Player;
|
|
||||||
using Robust.Client.UserInterface;
|
|
||||||
using Robust.Client.UserInterface.Controls;
|
|
||||||
using Robust.Client.UserInterface.XAML;
|
|
||||||
using Robust.Shared.GameObjects;
|
|
||||||
using Robust.Shared.IoC;
|
|
||||||
using Robust.Shared.Localization;
|
|
||||||
using Robust.Shared.Maths;
|
|
||||||
using Robust.Shared.Timing;
|
|
||||||
using static Robust.Client.UserInterface.Controls.BaseButton;
|
|
||||||
|
|
||||||
namespace Content.Client.Suspicion
|
|
||||||
{
|
|
||||||
[GenerateTypedNameReferences]
|
|
||||||
public sealed partial class SuspicionGui : UIWidget
|
|
||||||
{
|
|
||||||
[Dependency] private readonly IEntityManager _entManager = default!;
|
|
||||||
[Dependency] private readonly IPlayerManager _playerManager = default!;
|
|
||||||
[Dependency] private readonly IGameTiming _timing = default!;
|
|
||||||
|
|
||||||
private string? _previousRoleName;
|
|
||||||
private bool _previousAntagonist;
|
|
||||||
|
|
||||||
public SuspicionGui()
|
|
||||||
{
|
|
||||||
RobustXamlLoader.Load(this);
|
|
||||||
IoCManager.InjectDependencies(this);
|
|
||||||
|
|
||||||
RoleButton.OnPressed += RoleButtonPressed;
|
|
||||||
RoleButton.MinSize = (200, 60);
|
|
||||||
}
|
|
||||||
|
|
||||||
private void RoleButtonPressed(ButtonEventArgs obj)
|
|
||||||
{
|
|
||||||
if (!TryGetComponent(out var role))
|
|
||||||
{
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!role.Antagonist ?? false)
|
|
||||||
{
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
var allies = string.Join(", ", role.Allies.Select(tuple => tuple.name));
|
|
||||||
|
|
||||||
role.Owner.PopupMessage(
|
|
||||||
Loc.GetString(
|
|
||||||
"suspicion-ally-count-display",
|
|
||||||
("allyCount", role.Allies.Count),
|
|
||||||
("allyNames", allies)
|
|
||||||
)
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
private bool TryGetComponent([NotNullWhen(true)] out SuspicionRoleComponent? suspicion)
|
|
||||||
{
|
|
||||||
suspicion = default;
|
|
||||||
if (_playerManager.LocalPlayer?.ControlledEntity == null)
|
|
||||||
{
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
return _entManager.TryGetComponent(_playerManager.LocalPlayer.ControlledEntity, out suspicion);
|
|
||||||
}
|
|
||||||
|
|
||||||
public void UpdateLabel()
|
|
||||||
{
|
|
||||||
if (!TryGetComponent(out var suspicion))
|
|
||||||
{
|
|
||||||
Visible = false;
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (suspicion.Role == null || suspicion.Antagonist == null)
|
|
||||||
{
|
|
||||||
Visible = false;
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
var endTime = _entManager.System<SuspicionEndTimerSystem>().EndTime;
|
|
||||||
if (endTime == null)
|
|
||||||
{
|
|
||||||
TimerLabel.Visible = false;
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
var diff = endTime.Value - _timing.CurTime;
|
|
||||||
if (diff < TimeSpan.Zero)
|
|
||||||
{
|
|
||||||
diff = TimeSpan.Zero;
|
|
||||||
}
|
|
||||||
TimerLabel.Visible = true;
|
|
||||||
TimerLabel.Text = $"{diff:mm\\:ss}";
|
|
||||||
}
|
|
||||||
|
|
||||||
if (_previousRoleName == suspicion.Role && _previousAntagonist == suspicion.Antagonist)
|
|
||||||
{
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
_previousRoleName = suspicion.Role;
|
|
||||||
_previousAntagonist = suspicion.Antagonist.Value;
|
|
||||||
|
|
||||||
var buttonText = CultureInfo.CurrentCulture.TextInfo.ToTitleCase(_previousRoleName);
|
|
||||||
buttonText = Loc.GetString(buttonText);
|
|
||||||
|
|
||||||
RoleButton.Text = buttonText;
|
|
||||||
RoleButton.ModulateSelfOverride = _previousAntagonist ? Color.Red : Color.LimeGreen;
|
|
||||||
|
|
||||||
Visible = true;
|
|
||||||
}
|
|
||||||
|
|
||||||
protected override void FrameUpdate(FrameEventArgs args)
|
|
||||||
{
|
|
||||||
base.FrameUpdate(args);
|
|
||||||
UpdateLabel();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,128 +0,0 @@
|
|||||||
using Content.Shared.Suspicion;
|
|
||||||
using Robust.Client.Graphics;
|
|
||||||
using Robust.Client.Player;
|
|
||||||
using Robust.Client.ResourceManagement;
|
|
||||||
using Robust.Client.UserInterface;
|
|
||||||
using static Robust.Client.UserInterface.Controls.LayoutContainer;
|
|
||||||
|
|
||||||
namespace Content.Client.Suspicion
|
|
||||||
{
|
|
||||||
[RegisterComponent]
|
|
||||||
public sealed class SuspicionRoleComponent : SharedSuspicionRoleComponent
|
|
||||||
{
|
|
||||||
[Dependency] private readonly IOverlayManager _overlayManager = default!;
|
|
||||||
[Dependency] private readonly IResourceCache _resourceCache = default!;
|
|
||||||
[Dependency] private readonly IUserInterfaceManager _ui = default!;
|
|
||||||
|
|
||||||
private SuspicionGui? _gui;
|
|
||||||
private string? _role;
|
|
||||||
private bool? _antagonist;
|
|
||||||
private bool _overlayActive;
|
|
||||||
|
|
||||||
public string? Role
|
|
||||||
{
|
|
||||||
get => _role;
|
|
||||||
set
|
|
||||||
{
|
|
||||||
if (_role == value)
|
|
||||||
{
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
_role = value;
|
|
||||||
_gui?.UpdateLabel();
|
|
||||||
Dirty();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public bool? Antagonist
|
|
||||||
{
|
|
||||||
get => _antagonist;
|
|
||||||
set
|
|
||||||
{
|
|
||||||
if (_antagonist == value)
|
|
||||||
{
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
_antagonist = value;
|
|
||||||
_gui?.UpdateLabel();
|
|
||||||
|
|
||||||
if (value ?? false)
|
|
||||||
{
|
|
||||||
AddTraitorOverlay();
|
|
||||||
}
|
|
||||||
|
|
||||||
Dirty();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
[ViewVariables]
|
|
||||||
public List<(string name, EntityUid uid)> Allies { get; } = new();
|
|
||||||
|
|
||||||
private void AddTraitorOverlay()
|
|
||||||
{
|
|
||||||
if (_overlayManager.HasOverlay<TraitorOverlay>())
|
|
||||||
{
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
_overlayActive = true;
|
|
||||||
var entManager = IoCManager.Resolve<IEntityManager>();
|
|
||||||
var overlay = new TraitorOverlay(entManager, IoCManager.Resolve<IPlayerManager>(), _resourceCache, entManager.System<EntityLookupSystem>());
|
|
||||||
_overlayManager.AddOverlay(overlay);
|
|
||||||
}
|
|
||||||
|
|
||||||
private void RemoveTraitorOverlay()
|
|
||||||
{
|
|
||||||
if (!_overlayActive)
|
|
||||||
{
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
_overlayManager.RemoveOverlay<TraitorOverlay>();
|
|
||||||
}
|
|
||||||
|
|
||||||
public override void HandleComponentState(ComponentState? curState, ComponentState? nextState)
|
|
||||||
{
|
|
||||||
base.HandleComponentState(curState, nextState);
|
|
||||||
|
|
||||||
if (curState is not SuspicionRoleComponentState state)
|
|
||||||
{
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
Role = state.Role;
|
|
||||||
Antagonist = state.Antagonist;
|
|
||||||
Allies.Clear();
|
|
||||||
Allies.AddRange(state.Allies);
|
|
||||||
}
|
|
||||||
|
|
||||||
public void RemoveUI()
|
|
||||||
{
|
|
||||||
_gui?.Parent?.RemoveChild(_gui);
|
|
||||||
RemoveTraitorOverlay();
|
|
||||||
}
|
|
||||||
|
|
||||||
public void AddUI()
|
|
||||||
{
|
|
||||||
// TODO move this out of the component
|
|
||||||
_gui = _ui.ActiveScreen?.GetOrAddWidget<SuspicionGui>();
|
|
||||||
_gui!.UpdateLabel();
|
|
||||||
SetAnchorAndMarginPreset(_gui, LayoutPreset.BottomLeft);
|
|
||||||
|
|
||||||
if (_antagonist ?? false)
|
|
||||||
{
|
|
||||||
AddTraitorOverlay();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
protected override void OnRemove()
|
|
||||||
{
|
|
||||||
base.OnRemove();
|
|
||||||
|
|
||||||
_gui?.Dispose();
|
|
||||||
RemoveTraitorOverlay();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,18 +0,0 @@
|
|||||||
using Robust.Client.GameObjects;
|
|
||||||
|
|
||||||
namespace Content.Client.Suspicion
|
|
||||||
{
|
|
||||||
sealed class SuspicionRoleSystem : EntitySystem
|
|
||||||
{
|
|
||||||
public override void Initialize()
|
|
||||||
{
|
|
||||||
base.Initialize();
|
|
||||||
|
|
||||||
SubscribeLocalEvent<SuspicionRoleComponent, ComponentAdd>((_, component, _) => component.AddUI());
|
|
||||||
SubscribeLocalEvent<SuspicionRoleComponent, ComponentRemove>((_, component, _) => component.RemoveUI());
|
|
||||||
|
|
||||||
SubscribeLocalEvent<SuspicionRoleComponent, PlayerAttachedEvent>((_, component, _) => component.AddUI());
|
|
||||||
SubscribeLocalEvent<SuspicionRoleComponent, PlayerDetachedEvent>((_, component, _) => component.RemoveUI());
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,95 +0,0 @@
|
|||||||
using Content.Shared.Examine;
|
|
||||||
using Robust.Client.Graphics;
|
|
||||||
using Robust.Client.Player;
|
|
||||||
using Robust.Client.ResourceManagement;
|
|
||||||
using Robust.Shared.Containers;
|
|
||||||
using Robust.Shared.Enums;
|
|
||||||
using Robust.Shared.GameObjects;
|
|
||||||
using Robust.Shared.IoC;
|
|
||||||
using Robust.Shared.Localization;
|
|
||||||
using Robust.Shared.Maths;
|
|
||||||
using Robust.Shared.Physics;
|
|
||||||
using Robust.Shared.Physics.Components;
|
|
||||||
|
|
||||||
namespace Content.Client.Suspicion
|
|
||||||
{
|
|
||||||
public sealed class TraitorOverlay : Overlay
|
|
||||||
{
|
|
||||||
private readonly IEntityManager _entityManager;
|
|
||||||
private readonly IPlayerManager _playerManager;
|
|
||||||
private readonly EntityLookupSystem _lookup;
|
|
||||||
|
|
||||||
public override OverlaySpace Space => OverlaySpace.ScreenSpace;
|
|
||||||
private readonly Font _font;
|
|
||||||
|
|
||||||
private readonly string _traitorText = Loc.GetString("traitor-overlay-traitor-text");
|
|
||||||
|
|
||||||
public TraitorOverlay(
|
|
||||||
IEntityManager entityManager,
|
|
||||||
IPlayerManager playerManager,
|
|
||||||
IResourceCache resourceCache,
|
|
||||||
EntityLookupSystem lookup)
|
|
||||||
{
|
|
||||||
_playerManager = playerManager;
|
|
||||||
_entityManager = entityManager;
|
|
||||||
_lookup = lookup;
|
|
||||||
|
|
||||||
_font = new VectorFont(resourceCache.GetResource<FontResource>("/Fonts/NotoSans/NotoSans-Regular.ttf"), 10);
|
|
||||||
}
|
|
||||||
|
|
||||||
protected override void Draw(in OverlayDrawArgs args)
|
|
||||||
{
|
|
||||||
var viewport = args.WorldAABB;
|
|
||||||
|
|
||||||
var ent = _playerManager.LocalPlayer?.ControlledEntity;
|
|
||||||
if (_entityManager.TryGetComponent(ent, out SuspicionRoleComponent? sus) != true)
|
|
||||||
{
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
foreach (var (_, ally) in sus.Allies)
|
|
||||||
{
|
|
||||||
// Otherwise the entity can not exist yet
|
|
||||||
if (!_entityManager.EntityExists(ally))
|
|
||||||
{
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!_entityManager.TryGetComponent(ally, out PhysicsComponent? physics))
|
|
||||||
{
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
var allyXform = _entityManager.GetComponent<TransformComponent>(ally);
|
|
||||||
|
|
||||||
var entPosition = _entityManager.GetComponent<TransformComponent>(ent.Value).MapPosition;
|
|
||||||
var allyPosition = allyXform.MapPosition;
|
|
||||||
if (!ExamineSystemShared.InRangeUnOccluded(entPosition, allyPosition, 15,
|
|
||||||
entity => entity == ent || entity == ally))
|
|
||||||
{
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
// if not on the same map, continue
|
|
||||||
if (allyXform.MapID != args.Viewport.Eye!.Position.MapId
|
|
||||||
|| physics.Owner.IsInContainer())
|
|
||||||
{
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
var (allyWorldPos, allyWorldRot) = allyXform.GetWorldPositionRotation();
|
|
||||||
|
|
||||||
var worldBox = _lookup.GetWorldAABB(ally, allyXform);
|
|
||||||
|
|
||||||
// if not on screen, or too small, continue
|
|
||||||
if (!worldBox.Intersects(in viewport) || worldBox.IsEmpty())
|
|
||||||
{
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
var screenCoordinates = args.ViewportControl!.WorldToScreen(worldBox.TopLeft + (0, 0.5f));
|
|
||||||
args.ScreenHandle.DrawString(_font, screenCoordinates, _traitorText, Color.OrangeRed);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -3,12 +3,11 @@ using System.Threading.Tasks;
|
|||||||
using Content.Server.GameTicking;
|
using Content.Server.GameTicking;
|
||||||
using Content.Server.GameTicking.Commands;
|
using Content.Server.GameTicking.Commands;
|
||||||
using Content.Server.GameTicking.Rules;
|
using Content.Server.GameTicking.Rules;
|
||||||
|
using Content.Server.GameTicking.Rules.Components;
|
||||||
using Content.Shared.CCVar;
|
using Content.Shared.CCVar;
|
||||||
using NUnit.Framework;
|
using NUnit.Framework;
|
||||||
using Robust.Shared.Configuration;
|
using Robust.Shared.Configuration;
|
||||||
using Robust.Shared.GameObjects;
|
using Robust.Shared.GameObjects;
|
||||||
using Robust.Shared.IoC;
|
|
||||||
using Robust.Shared.Prototypes;
|
|
||||||
using Robust.Shared.Timing;
|
using Robust.Shared.Timing;
|
||||||
|
|
||||||
namespace Content.IntegrationTests.Tests.GameRules
|
namespace Content.IntegrationTests.Tests.GameRules
|
||||||
@@ -23,6 +22,7 @@ namespace Content.IntegrationTests.Tests.GameRules
|
|||||||
await using var pairTracker = await PoolManager.GetServerClient();
|
await using var pairTracker = await PoolManager.GetServerClient();
|
||||||
var server = pairTracker.Pair.Server;
|
var server = pairTracker.Pair.Server;
|
||||||
|
|
||||||
|
var entityManager = server.ResolveDependency<IEntityManager>();
|
||||||
var configManager = server.ResolveDependency<IConfigurationManager>();
|
var configManager = server.ResolveDependency<IConfigurationManager>();
|
||||||
await server.WaitPost(() =>
|
await server.WaitPost(() =>
|
||||||
{
|
{
|
||||||
@@ -31,18 +31,17 @@ namespace Content.IntegrationTests.Tests.GameRules
|
|||||||
command.Execute(null, string.Empty, Array.Empty<string>());
|
command.Execute(null, string.Empty, Array.Empty<string>());
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
||||||
var sGameTicker = server.ResolveDependency<IEntitySystemManager>().GetEntitySystem<GameTicker>();
|
var sGameTicker = server.ResolveDependency<IEntitySystemManager>().GetEntitySystem<GameTicker>();
|
||||||
var maxTimeMaxTimeRestartRuleSystem = server.ResolveDependency<IEntitySystemManager>().GetEntitySystem<MaxTimeRestartRuleSystem>();
|
|
||||||
var sGameTiming = server.ResolveDependency<IGameTiming>();
|
var sGameTiming = server.ResolveDependency<IGameTiming>();
|
||||||
|
|
||||||
|
|
||||||
|
sGameTicker.StartGameRule("MaxTimeRestart", out var ruleEntity);
|
||||||
|
Assert.That(entityManager.TryGetComponent<MaxTimeRestartRuleComponent>(ruleEntity, out var maxTime));
|
||||||
|
|
||||||
await server.WaitAssertion(() =>
|
await server.WaitAssertion(() =>
|
||||||
{
|
{
|
||||||
Assert.That(sGameTicker.RunLevel, Is.EqualTo(GameRunLevel.PreRoundLobby));
|
Assert.That(sGameTicker.RunLevel, Is.EqualTo(GameRunLevel.PreRoundLobby));
|
||||||
|
maxTime.RoundMaxTime = TimeSpan.FromSeconds(3);
|
||||||
sGameTicker.StartGameRule(IoCManager.Resolve<IPrototypeManager>().Index<GameRulePrototype>(maxTimeMaxTimeRestartRuleSystem.Prototype));
|
|
||||||
maxTimeMaxTimeRestartRuleSystem.RoundMaxTime = TimeSpan.FromSeconds(3);
|
|
||||||
|
|
||||||
sGameTicker.StartRound();
|
sGameTicker.StartRound();
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -51,7 +50,7 @@ namespace Content.IntegrationTests.Tests.GameRules
|
|||||||
Assert.That(sGameTicker.RunLevel, Is.EqualTo(GameRunLevel.InRound));
|
Assert.That(sGameTicker.RunLevel, Is.EqualTo(GameRunLevel.InRound));
|
||||||
});
|
});
|
||||||
|
|
||||||
var ticks = sGameTiming.TickRate * (int) Math.Ceiling(maxTimeMaxTimeRestartRuleSystem.RoundMaxTime.TotalSeconds * 1.1f);
|
var ticks = sGameTiming.TickRate * (int) Math.Ceiling(maxTime.RoundMaxTime.TotalSeconds * 1.1f);
|
||||||
await PoolManager.RunTicksSync(pairTracker.Pair, ticks);
|
await PoolManager.RunTicksSync(pairTracker.Pair, ticks);
|
||||||
|
|
||||||
await server.WaitAssertion(() =>
|
await server.WaitAssertion(() =>
|
||||||
@@ -59,7 +58,7 @@ namespace Content.IntegrationTests.Tests.GameRules
|
|||||||
Assert.That(sGameTicker.RunLevel, Is.EqualTo(GameRunLevel.PostRound));
|
Assert.That(sGameTicker.RunLevel, Is.EqualTo(GameRunLevel.PostRound));
|
||||||
});
|
});
|
||||||
|
|
||||||
ticks = sGameTiming.TickRate * (int) Math.Ceiling(maxTimeMaxTimeRestartRuleSystem.RoundEndDelay.TotalSeconds * 1.1f);
|
ticks = sGameTiming.TickRate * (int) Math.Ceiling(maxTime.RoundEndDelay.TotalSeconds * 1.1f);
|
||||||
await PoolManager.RunTicksSync(pairTracker.Pair, ticks);
|
await PoolManager.RunTicksSync(pairTracker.Pair, ticks);
|
||||||
|
|
||||||
await server.WaitAssertion(() =>
|
await server.WaitAssertion(() =>
|
||||||
|
|||||||
@@ -1,11 +1,8 @@
|
|||||||
using System;
|
using System.Linq;
|
||||||
using System.Linq;
|
|
||||||
using System.Threading.Tasks;
|
using System.Threading.Tasks;
|
||||||
using Content.Server.GameTicking;
|
using Content.Server.GameTicking;
|
||||||
using Content.Server.GameTicking.Rules;
|
|
||||||
using NUnit.Framework;
|
using NUnit.Framework;
|
||||||
using Robust.Shared.GameObjects;
|
using Robust.Shared.GameObjects;
|
||||||
using Robust.Shared.Prototypes;
|
|
||||||
|
|
||||||
namespace Content.IntegrationTests.Tests.GameRules;
|
namespace Content.IntegrationTests.Tests.GameRules;
|
||||||
|
|
||||||
@@ -26,12 +23,11 @@ public sealed class SecretStartsTest
|
|||||||
|
|
||||||
var server = pairTracker.Pair.Server;
|
var server = pairTracker.Pair.Server;
|
||||||
await server.WaitIdleAsync();
|
await server.WaitIdleAsync();
|
||||||
var protoMan = server.ResolveDependency<IPrototypeManager>();
|
|
||||||
var gameTicker = server.ResolveDependency<IEntitySystemManager>().GetEntitySystem<GameTicker>();
|
var gameTicker = server.ResolveDependency<IEntitySystemManager>().GetEntitySystem<GameTicker>();
|
||||||
|
|
||||||
await server.WaitAssertion(() =>
|
await server.WaitAssertion(() =>
|
||||||
{
|
{
|
||||||
gameTicker.StartGameRule(protoMan.Index<GameRulePrototype>("Secret"));
|
gameTicker.StartGameRule("Secret");
|
||||||
});
|
});
|
||||||
|
|
||||||
// Wait three ticks for any random update loops that might happen
|
// Wait three ticks for any random update loops that might happen
|
||||||
@@ -39,9 +35,9 @@ public sealed class SecretStartsTest
|
|||||||
|
|
||||||
await server.WaitAssertion(() =>
|
await server.WaitAssertion(() =>
|
||||||
{
|
{
|
||||||
foreach (var rule in gameTicker.AddedGameRules)
|
foreach (var rule in gameTicker.GetAddedGameRules())
|
||||||
{
|
{
|
||||||
Assert.That(gameTicker.StartedGameRules.Contains(rule));
|
Assert.That(gameTicker.GetActiveGameRules().Contains(rule));
|
||||||
}
|
}
|
||||||
|
|
||||||
// End all rules
|
// End all rules
|
||||||
|
|||||||
@@ -1,17 +1,14 @@
|
|||||||
using System;
|
using System;
|
||||||
using System.Linq;
|
using System.Linq;
|
||||||
using System.Threading.Tasks;
|
using System.Threading.Tasks;
|
||||||
using Content.Server.GameTicking;
|
using Content.Server.GameTicking;
|
||||||
using Content.Server.GameTicking.Rules;
|
|
||||||
using Content.Shared.CCVar;
|
using Content.Shared.CCVar;
|
||||||
using NUnit.Framework;
|
using NUnit.Framework;
|
||||||
using Robust.Shared.Configuration;
|
using Robust.Shared.Configuration;
|
||||||
using Robust.Shared.GameObjects;
|
using Robust.Shared.GameObjects;
|
||||||
using Robust.Shared.Prototypes;
|
|
||||||
|
|
||||||
namespace Content.IntegrationTests.Tests.GameRules;
|
namespace Content.IntegrationTests.Tests.GameRules;
|
||||||
|
|
||||||
|
|
||||||
[TestFixture]
|
[TestFixture]
|
||||||
public sealed class StartEndGameRulesTest
|
public sealed class StartEndGameRulesTest
|
||||||
{
|
{
|
||||||
@@ -28,24 +25,20 @@ public sealed class StartEndGameRulesTest
|
|||||||
});
|
});
|
||||||
var server = pairTracker.Pair.Server;
|
var server = pairTracker.Pair.Server;
|
||||||
await server.WaitIdleAsync();
|
await server.WaitIdleAsync();
|
||||||
var protoMan = server.ResolveDependency<IPrototypeManager>();
|
|
||||||
var gameTicker = server.ResolveDependency<IEntitySystemManager>().GetEntitySystem<GameTicker>();
|
var gameTicker = server.ResolveDependency<IEntitySystemManager>().GetEntitySystem<GameTicker>();
|
||||||
var cfg = server.ResolveDependency<IConfigurationManager>();
|
var cfg = server.ResolveDependency<IConfigurationManager>();
|
||||||
Assert.That(cfg.GetCVar(CCVars.DisableGridFill), Is.False);
|
Assert.That(cfg.GetCVar(CCVars.DisableGridFill), Is.False);
|
||||||
|
|
||||||
await server.WaitAssertion(() =>
|
await server.WaitAssertion(() =>
|
||||||
{
|
{
|
||||||
var rules = protoMan.EnumeratePrototypes<GameRulePrototype>().ToList();
|
var rules = gameTicker.GetAllGameRulePrototypes().ToList();
|
||||||
rules.Sort((x, y) => string.Compare(x.ID, y.ID, StringComparison.Ordinal));
|
rules.Sort((x, y) => string.Compare(x.ID, y.ID, StringComparison.Ordinal));
|
||||||
|
|
||||||
// Start all rules
|
// Start all rules
|
||||||
foreach (var rule in rules)
|
foreach (var rule in rules)
|
||||||
{
|
{
|
||||||
gameTicker.StartGameRule(rule);
|
gameTicker.StartGameRule(rule.ID);
|
||||||
}
|
}
|
||||||
|
|
||||||
Assert.That(gameTicker.AddedGameRules, Has.Count.EqualTo(rules.Count));
|
|
||||||
Assert.That(gameTicker.AddedGameRules, Has.Count.EqualTo(gameTicker.StartedGameRules.Count));
|
|
||||||
});
|
});
|
||||||
|
|
||||||
// Wait three ticks for any random update loops that might happen
|
// Wait three ticks for any random update loops that might happen
|
||||||
@@ -55,7 +48,7 @@ public sealed class StartEndGameRulesTest
|
|||||||
{
|
{
|
||||||
// End all rules
|
// End all rules
|
||||||
gameTicker.ClearGameRules();
|
gameTicker.ClearGameRules();
|
||||||
Assert.That(!gameTicker.AddedGameRules.Any());
|
Assert.That(!gameTicker.GetAddedGameRules().Any());
|
||||||
});
|
});
|
||||||
|
|
||||||
await pairTracker.CleanReturnAsync();
|
await pairTracker.CleanReturnAsync();
|
||||||
|
|||||||
7
Content.Server/Dragon/Components/DragonRuleComponent.cs
Normal file
7
Content.Server/Dragon/Components/DragonRuleComponent.cs
Normal file
@@ -0,0 +1,7 @@
|
|||||||
|
namespace Content.Server.Dragon;
|
||||||
|
|
||||||
|
[RegisterComponent]
|
||||||
|
public sealed class DragonRuleComponent : Component
|
||||||
|
{
|
||||||
|
|
||||||
|
}
|
||||||
@@ -1,6 +1,6 @@
|
|||||||
using System.Linq;
|
using System.Linq;
|
||||||
using Content.Server.GameTicking;
|
using Content.Server.GameTicking;
|
||||||
using Content.Server.StationEvents.Components;
|
using Content.Server.GameTicking.Rules.Components;
|
||||||
using Content.Shared.Dragon;
|
using Content.Shared.Dragon;
|
||||||
using Robust.Server.GameObjects;
|
using Robust.Server.GameObjects;
|
||||||
using Robust.Shared.Map.Components;
|
using Robust.Shared.Map.Components;
|
||||||
@@ -10,8 +10,6 @@ namespace Content.Server.Dragon;
|
|||||||
|
|
||||||
public sealed partial class DragonSystem
|
public sealed partial class DragonSystem
|
||||||
{
|
{
|
||||||
public override string Prototype => "Dragon";
|
|
||||||
|
|
||||||
private int RiftsMet(DragonComponent component)
|
private int RiftsMet(DragonComponent component)
|
||||||
{
|
{
|
||||||
var finished = 0;
|
var finished = 0;
|
||||||
@@ -28,9 +26,11 @@ public sealed partial class DragonSystem
|
|||||||
return finished;
|
return finished;
|
||||||
}
|
}
|
||||||
|
|
||||||
public override void Started()
|
protected override void Started(EntityUid uid, DragonRuleComponent component, GameRuleComponent gameRule, GameRuleStartedEvent args)
|
||||||
{
|
{
|
||||||
var spawnLocations = EntityManager.EntityQuery<MapGridComponent, TransformComponent>().ToList();
|
base.Started(uid, component, gameRule, args);
|
||||||
|
|
||||||
|
var spawnLocations = EntityQuery<MapGridComponent, TransformComponent>().ToList();
|
||||||
|
|
||||||
if (spawnLocations.Count == 0)
|
if (spawnLocations.Count == 0)
|
||||||
return;
|
return;
|
||||||
@@ -39,16 +39,8 @@ public sealed partial class DragonSystem
|
|||||||
Spawn("MobDragon", location.Item2.MapPosition);
|
Spawn("MobDragon", location.Item2.MapPosition);
|
||||||
}
|
}
|
||||||
|
|
||||||
public override void Ended()
|
|
||||||
{
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
private void OnRiftRoundEnd(RoundEndTextAppendEvent args)
|
private void OnRiftRoundEnd(RoundEndTextAppendEvent args)
|
||||||
{
|
{
|
||||||
if (!RuleAdded)
|
|
||||||
return;
|
|
||||||
|
|
||||||
var dragons = EntityQuery<DragonComponent>(true).ToList();
|
var dragons = EntityQuery<DragonComponent>(true).ToList();
|
||||||
|
|
||||||
if (dragons.Count == 0)
|
if (dragons.Count == 0)
|
||||||
|
|||||||
@@ -24,7 +24,7 @@ using Content.Shared.Mobs.Components;
|
|||||||
|
|
||||||
namespace Content.Server.Dragon
|
namespace Content.Server.Dragon
|
||||||
{
|
{
|
||||||
public sealed partial class DragonSystem : GameRuleSystem
|
public sealed partial class DragonSystem : GameRuleSystem<DragonRuleComponent>
|
||||||
{
|
{
|
||||||
[Dependency] private readonly IMapManager _mapManager = default!;
|
[Dependency] private readonly IMapManager _mapManager = default!;
|
||||||
[Dependency] private readonly IRobustRandom _random = default!;
|
[Dependency] private readonly IRobustRandom _random = default!;
|
||||||
|
|||||||
@@ -1,9 +1,7 @@
|
|||||||
using System.Diagnostics.CodeAnalysis;
|
using System.Diagnostics.CodeAnalysis;
|
||||||
using System.Linq;
|
using System.Linq;
|
||||||
using System.Threading.Tasks;
|
using System.Threading.Tasks;
|
||||||
using Content.Server.GameTicking.Events;
|
|
||||||
using Content.Server.GameTicking.Presets;
|
using Content.Server.GameTicking.Presets;
|
||||||
using Content.Server.GameTicking.Rules;
|
|
||||||
using Content.Server.Ghost.Components;
|
using Content.Server.Ghost.Components;
|
||||||
using Content.Shared.CCVar;
|
using Content.Shared.CCVar;
|
||||||
using Content.Shared.Damage;
|
using Content.Shared.Damage;
|
||||||
@@ -11,6 +9,7 @@ using Content.Shared.Damage.Prototypes;
|
|||||||
using Content.Shared.Database;
|
using Content.Shared.Database;
|
||||||
using Content.Shared.Mobs.Components;
|
using Content.Shared.Mobs.Components;
|
||||||
using Content.Shared.Mobs.Systems;
|
using Content.Shared.Mobs.Systems;
|
||||||
|
using JetBrains.Annotations;
|
||||||
using Robust.Server.Player;
|
using Robust.Server.Player;
|
||||||
|
|
||||||
namespace Content.Server.GameTicking
|
namespace Content.Server.GameTicking
|
||||||
@@ -43,7 +42,6 @@ namespace Content.Server.GameTicking
|
|||||||
|
|
||||||
if (_configurationManager.GetCVar(CCVars.GameLobbyFallbackEnabled))
|
if (_configurationManager.GetCVar(CCVars.GameLobbyFallbackEnabled))
|
||||||
{
|
{
|
||||||
var oldPreset = Preset;
|
|
||||||
ClearGameRules();
|
ClearGameRules();
|
||||||
SetGamePreset(_configurationManager.GetCVar(CCVars.GameLobbyFallbackPreset));
|
SetGamePreset(_configurationManager.GetCVar(CCVars.GameLobbyFallbackPreset));
|
||||||
AddGamePresetRules();
|
AddGamePresetRules();
|
||||||
@@ -125,6 +123,7 @@ namespace Content.Server.GameTicking
|
|||||||
return prototype != null;
|
return prototype != null;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
[PublicAPI]
|
||||||
private bool AddGamePresetRules()
|
private bool AddGamePresetRules()
|
||||||
{
|
{
|
||||||
if (DummyTicker || Preset == null)
|
if (DummyTicker || Preset == null)
|
||||||
@@ -132,10 +131,7 @@ namespace Content.Server.GameTicking
|
|||||||
|
|
||||||
foreach (var rule in Preset.Rules)
|
foreach (var rule in Preset.Rules)
|
||||||
{
|
{
|
||||||
if (!_prototypeManager.TryIndex(rule, out GameRulePrototype? ruleProto))
|
AddGameRule(rule);
|
||||||
continue;
|
|
||||||
|
|
||||||
AddGameRule(ruleProto);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return true;
|
return true;
|
||||||
@@ -144,7 +140,8 @@ namespace Content.Server.GameTicking
|
|||||||
private void StartGamePresetRules()
|
private void StartGamePresetRules()
|
||||||
{
|
{
|
||||||
// May be touched by the preset during init.
|
// May be touched by the preset during init.
|
||||||
foreach (var rule in _addedGameRules.ToArray())
|
var rules = new List<EntityUid>(GetAddedGameRules());
|
||||||
|
foreach (var rule in rules)
|
||||||
{
|
{
|
||||||
StartGameRule(rule);
|
StartGameRule(rule);
|
||||||
}
|
}
|
||||||
@@ -166,10 +163,12 @@ namespace Content.Server.GameTicking
|
|||||||
|
|
||||||
if (mind.PreventGhosting)
|
if (mind.PreventGhosting)
|
||||||
{
|
{
|
||||||
if (mind.Session != null)
|
if (mind.Session != null) // Logging is suppressed to prevent spam from ghost attempts caused by movement attempts
|
||||||
// Logging is suppressed to prevent spam from ghost attempts caused by movement attempts
|
{
|
||||||
_chatManager.DispatchServerMessage(mind.Session, Loc.GetString("comp-mind-ghosting-prevented"),
|
_chatManager.DispatchServerMessage(mind.Session, Loc.GetString("comp-mind-ghosting-prevented"),
|
||||||
true);
|
true);
|
||||||
|
}
|
||||||
|
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -1,244 +1,303 @@
|
|||||||
using System.Linq;
|
using System.Linq;
|
||||||
using Content.Server.Administration;
|
using Content.Server.Administration;
|
||||||
using Content.Server.GameTicking.Rules;
|
using Content.Server.GameTicking.Rules.Components;
|
||||||
using Content.Server.GameTicking.Rules.Configurations;
|
|
||||||
using Content.Shared.Administration;
|
using Content.Shared.Administration;
|
||||||
|
using Content.Shared.Prototypes;
|
||||||
|
using JetBrains.Annotations;
|
||||||
using Robust.Shared.Console;
|
using Robust.Shared.Console;
|
||||||
|
using Robust.Shared.Map;
|
||||||
|
using Robust.Shared.Prototypes;
|
||||||
|
|
||||||
namespace Content.Server.GameTicking
|
namespace Content.Server.GameTicking;
|
||||||
|
|
||||||
|
public sealed partial class GameTicker
|
||||||
{
|
{
|
||||||
public sealed partial class GameTicker
|
[ViewVariables] private readonly List<(TimeSpan, string)> _allPreviousGameRules = new();
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// A list storing the start times of all game rules that have been started this round.
|
||||||
|
/// Game rules can be started and stopped at any time, including midround.
|
||||||
|
/// </summary>
|
||||||
|
public IReadOnlyList<(TimeSpan, string)> AllPreviousGameRules => _allPreviousGameRules;
|
||||||
|
|
||||||
|
private void InitializeGameRules()
|
||||||
{
|
{
|
||||||
// No duplicates.
|
// Add game rule command.
|
||||||
[ViewVariables] private readonly HashSet<GameRulePrototype> _addedGameRules = new();
|
_consoleHost.RegisterCommand("addgamerule",
|
||||||
|
string.Empty,
|
||||||
|
"addgamerule <rules>",
|
||||||
|
AddGameRuleCommand,
|
||||||
|
AddGameRuleCompletions);
|
||||||
|
|
||||||
/// <summary>
|
// End game rule command.
|
||||||
/// Holds all currently added game rules.
|
_consoleHost.RegisterCommand("endgamerule",
|
||||||
/// </summary>
|
string.Empty,
|
||||||
public IReadOnlySet<GameRulePrototype> AddedGameRules => _addedGameRules;
|
"endgamerule <rules>",
|
||||||
|
EndGameRuleCommand,
|
||||||
|
EndGameRuleCompletions);
|
||||||
|
|
||||||
[ViewVariables] private readonly HashSet<GameRulePrototype> _startedGameRules = new();
|
// Clear game rules command.
|
||||||
|
_consoleHost.RegisterCommand("cleargamerules",
|
||||||
|
string.Empty,
|
||||||
|
"cleargamerules",
|
||||||
|
ClearGameRulesCommand);
|
||||||
|
}
|
||||||
|
|
||||||
/// <summary>
|
private void ShutdownGameRules()
|
||||||
/// Holds all currently started game rules.
|
{
|
||||||
/// </summary>
|
_consoleHost.UnregisterCommand("addgamerule");
|
||||||
public IReadOnlySet<GameRulePrototype> StartedGameRules => _startedGameRules;
|
_consoleHost.UnregisterCommand("endgamerule");
|
||||||
|
_consoleHost.UnregisterCommand("cleargamerules");
|
||||||
[ViewVariables] private readonly List<(TimeSpan, GameRulePrototype)> _allPreviousGameRules = new();
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// A list storing the start times of all game rules that have been started this round.
|
|
||||||
/// Game rules can be started and stopped at any time, including midround.
|
|
||||||
/// </summary>
|
|
||||||
public IReadOnlyList<(TimeSpan, GameRulePrototype)> AllPreviousGameRules => _allPreviousGameRules;
|
|
||||||
|
|
||||||
private void InitializeGameRules()
|
|
||||||
{
|
|
||||||
// Add game rule command.
|
|
||||||
_consoleHost.RegisterCommand("addgamerule",
|
|
||||||
string.Empty,
|
|
||||||
"addgamerule <rules>",
|
|
||||||
AddGameRuleCommand,
|
|
||||||
AddGameRuleCompletions);
|
|
||||||
|
|
||||||
// End game rule command.
|
|
||||||
_consoleHost.RegisterCommand("endgamerule",
|
|
||||||
string.Empty,
|
|
||||||
"endgamerule <rules>",
|
|
||||||
EndGameRuleCommand,
|
|
||||||
EndGameRuleCompletions);
|
|
||||||
|
|
||||||
// Clear game rules command.
|
|
||||||
_consoleHost.RegisterCommand("cleargamerules",
|
|
||||||
string.Empty,
|
|
||||||
"cleargamerules",
|
|
||||||
ClearGameRulesCommand);
|
|
||||||
}
|
|
||||||
|
|
||||||
private void ShutdownGameRules()
|
|
||||||
{
|
|
||||||
_consoleHost.UnregisterCommand("addgamerule");
|
|
||||||
_consoleHost.UnregisterCommand("endgamerule");
|
|
||||||
_consoleHost.UnregisterCommand("cleargamerules");
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Game rules can be 'started' separately from being added. 'Starting' them usually
|
|
||||||
/// happens at round start while they can be added and removed before then.
|
|
||||||
/// </summary>
|
|
||||||
public void StartGameRule(GameRulePrototype rule)
|
|
||||||
{
|
|
||||||
if (!IsGameRuleAdded(rule))
|
|
||||||
AddGameRule(rule);
|
|
||||||
|
|
||||||
_allPreviousGameRules.Add((RoundDuration(), rule));
|
|
||||||
_sawmill.Info($"Started game rule {rule.ID}");
|
|
||||||
|
|
||||||
if (_startedGameRules.Add(rule))
|
|
||||||
RaiseLocalEvent(new GameRuleStartedEvent(rule));
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Ends a game rule.
|
|
||||||
/// This always includes removing it (from added game rules) so that behavior
|
|
||||||
/// is not separate from this.
|
|
||||||
/// </summary>
|
|
||||||
/// <param name="rule"></param>
|
|
||||||
public void EndGameRule(GameRulePrototype rule)
|
|
||||||
{
|
|
||||||
if (!IsGameRuleAdded(rule))
|
|
||||||
return;
|
|
||||||
|
|
||||||
_addedGameRules.Remove(rule);
|
|
||||||
_sawmill.Info($"Ended game rule {rule.ID}");
|
|
||||||
|
|
||||||
if (IsGameRuleStarted(rule))
|
|
||||||
_startedGameRules.Remove(rule);
|
|
||||||
RaiseLocalEvent(new GameRuleEndedEvent(rule));
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Adds a game rule to the list, but does not
|
|
||||||
/// start it yet, instead waiting until the rule is actually started by other code (usually roundstart)
|
|
||||||
/// </summary>
|
|
||||||
public bool AddGameRule(GameRulePrototype rule)
|
|
||||||
{
|
|
||||||
if (!_addedGameRules.Add(rule))
|
|
||||||
return false;
|
|
||||||
|
|
||||||
_sawmill.Info($"Added game rule {rule.ID}");
|
|
||||||
RaiseLocalEvent(new GameRuleAddedEvent(rule));
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
public bool IsGameRuleAdded(GameRulePrototype rule)
|
|
||||||
{
|
|
||||||
return _addedGameRules.Contains(rule);
|
|
||||||
}
|
|
||||||
|
|
||||||
public bool IsGameRuleAdded(string rule)
|
|
||||||
{
|
|
||||||
foreach (var ruleProto in _addedGameRules)
|
|
||||||
{
|
|
||||||
if (ruleProto.ID.Equals(rule))
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
public bool IsGameRuleStarted(GameRulePrototype rule)
|
|
||||||
{
|
|
||||||
return _startedGameRules.Contains(rule);
|
|
||||||
}
|
|
||||||
|
|
||||||
public bool IsGameRuleStarted(string rule)
|
|
||||||
{
|
|
||||||
foreach (var ruleProto in _startedGameRules)
|
|
||||||
{
|
|
||||||
if (ruleProto.ID.Equals(rule))
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
public void ClearGameRules()
|
|
||||||
{
|
|
||||||
foreach (var rule in _addedGameRules.ToArray())
|
|
||||||
{
|
|
||||||
EndGameRule(rule);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#region Command Implementations
|
|
||||||
|
|
||||||
[AdminCommand(AdminFlags.Fun)]
|
|
||||||
private void AddGameRuleCommand(IConsoleShell shell, string argstr, string[] args)
|
|
||||||
{
|
|
||||||
if (args.Length == 0)
|
|
||||||
return;
|
|
||||||
|
|
||||||
foreach (var ruleId in args)
|
|
||||||
{
|
|
||||||
if (!_prototypeManager.TryIndex<GameRulePrototype>(ruleId, out var rule))
|
|
||||||
continue;
|
|
||||||
|
|
||||||
AddGameRule(rule);
|
|
||||||
|
|
||||||
// Start rule if we're already in the middle of a round
|
|
||||||
if(RunLevel == GameRunLevel.InRound)
|
|
||||||
StartGameRule(rule);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private CompletionResult AddGameRuleCompletions(IConsoleShell shell, string[] args)
|
|
||||||
{
|
|
||||||
var activeIds = _addedGameRules.Select(c => c.ID);
|
|
||||||
return CompletionResult.FromHintOptions(CompletionHelper.PrototypeIDs<GameRulePrototype>().Where(p => !activeIds.Contains(p.Value)),
|
|
||||||
"<rule>");
|
|
||||||
}
|
|
||||||
|
|
||||||
[AdminCommand(AdminFlags.Fun)]
|
|
||||||
private void EndGameRuleCommand(IConsoleShell shell, string argstr, string[] args)
|
|
||||||
{
|
|
||||||
if (args.Length == 0)
|
|
||||||
return;
|
|
||||||
|
|
||||||
foreach (var ruleId in args)
|
|
||||||
{
|
|
||||||
if (!_prototypeManager.TryIndex<GameRulePrototype>(ruleId, out var rule))
|
|
||||||
continue;
|
|
||||||
|
|
||||||
EndGameRule(rule);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private CompletionResult EndGameRuleCompletions(IConsoleShell shell, string[] args)
|
|
||||||
{
|
|
||||||
return CompletionResult.FromHintOptions(_addedGameRules.Select(c => new CompletionOption(c.ID)),
|
|
||||||
"<added rule>");
|
|
||||||
}
|
|
||||||
|
|
||||||
[AdminCommand(AdminFlags.Fun)]
|
|
||||||
private void ClearGameRulesCommand(IConsoleShell shell, string argstr, string[] args)
|
|
||||||
{
|
|
||||||
ClearGameRules();
|
|
||||||
}
|
|
||||||
|
|
||||||
#endregion
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Raised broadcast when a game rule is selected, but not started yet.
|
/// Adds a game rule to the list, but does not
|
||||||
|
/// start it yet, instead waiting until the rule is actually started by other code (usually roundstart)
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public sealed class GameRuleAddedEvent
|
/// <returns>The entity for the added gamerule</returns>
|
||||||
|
public EntityUid AddGameRule(string ruleId)
|
||||||
{
|
{
|
||||||
public GameRulePrototype Rule { get; }
|
var ruleEntity = Spawn(ruleId, MapCoordinates.Nullspace);
|
||||||
|
_sawmill.Info($"Added game rule {ToPrettyString(ruleEntity)}");
|
||||||
|
|
||||||
public GameRuleAddedEvent(GameRulePrototype rule)
|
var ev = new GameRuleAddedEvent(ruleEntity, ruleId);
|
||||||
|
RaiseLocalEvent(ruleEntity, ref ev, true);
|
||||||
|
return ruleEntity;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Game rules can be 'started' separately from being added. 'Starting' them usually
|
||||||
|
/// happens at round start while they can be added and removed before then.
|
||||||
|
/// </summary>
|
||||||
|
public bool StartGameRule(string ruleId)
|
||||||
|
{
|
||||||
|
return StartGameRule(ruleId, out _);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Game rules can be 'started' separately from being added. 'Starting' them usually
|
||||||
|
/// happens at round start while they can be added and removed before then.
|
||||||
|
/// </summary>
|
||||||
|
public bool StartGameRule(string ruleId, out EntityUid ruleEntity)
|
||||||
|
{
|
||||||
|
ruleEntity = AddGameRule(ruleId);
|
||||||
|
return StartGameRule(ruleEntity);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Game rules can be 'started' separately from being added. 'Starting' them usually
|
||||||
|
/// happens at round start while they can be added and removed before then.
|
||||||
|
/// </summary>
|
||||||
|
public bool StartGameRule(EntityUid ruleEntity, GameRuleComponent? ruleData = null)
|
||||||
|
{
|
||||||
|
if (!Resolve(ruleEntity, ref ruleData))
|
||||||
|
ruleData ??= EnsureComp<GameRuleComponent>(ruleEntity);
|
||||||
|
|
||||||
|
// can't start an already active rule
|
||||||
|
if (ruleData.Active || ruleData.Ended)
|
||||||
|
return false;
|
||||||
|
|
||||||
|
if (MetaData(ruleEntity).EntityPrototype?.ID is not { } id) // you really fucked up
|
||||||
|
return false;
|
||||||
|
|
||||||
|
_allPreviousGameRules.Add((RoundDuration(), id));
|
||||||
|
_sawmill.Info($"Started game rule {ToPrettyString(ruleEntity)}");
|
||||||
|
|
||||||
|
ruleData.Active = true;
|
||||||
|
var ev = new GameRuleStartedEvent(ruleEntity, id);
|
||||||
|
RaiseLocalEvent(ruleEntity, ref ev, true);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Ends a game rule.
|
||||||
|
/// </summary>
|
||||||
|
[PublicAPI]
|
||||||
|
public bool EndGameRule(EntityUid ruleEntity, GameRuleComponent? ruleData = null)
|
||||||
|
{
|
||||||
|
if (!Resolve(ruleEntity, ref ruleData))
|
||||||
|
return false;
|
||||||
|
|
||||||
|
// don't end it multiple times
|
||||||
|
if (ruleData.Ended)
|
||||||
|
return false;
|
||||||
|
|
||||||
|
if (MetaData(ruleEntity).EntityPrototype?.ID is not { } id) // you really fucked up
|
||||||
|
return false;
|
||||||
|
|
||||||
|
ruleData.Active = false;
|
||||||
|
ruleData.Ended = true;
|
||||||
|
_sawmill.Info($"Ended game rule {ToPrettyString(ruleEntity)}");
|
||||||
|
|
||||||
|
var ev = new GameRuleEndedEvent(ruleEntity, id);
|
||||||
|
RaiseLocalEvent(ruleEntity, ref ev, true);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
public bool IsGameRuleAdded(EntityUid ruleEntity, GameRuleComponent? component = null)
|
||||||
|
{
|
||||||
|
return Resolve(ruleEntity, ref component) && !component.Ended;
|
||||||
|
}
|
||||||
|
|
||||||
|
public bool IsGameRuleAdded(string rule)
|
||||||
|
{
|
||||||
|
foreach (var ruleEntity in GetAddedGameRules())
|
||||||
{
|
{
|
||||||
Rule = rule;
|
if (MetaData(ruleEntity).EntityPrototype?.ID == rule)
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
public bool IsGameRuleActive(EntityUid ruleEntity, GameRuleComponent? component = null)
|
||||||
|
{
|
||||||
|
return Resolve(ruleEntity, ref component) && component.Active;
|
||||||
|
}
|
||||||
|
|
||||||
|
public bool IsGameRuleActive(string rule)
|
||||||
|
{
|
||||||
|
foreach (var ruleEntity in GetActiveGameRules())
|
||||||
|
{
|
||||||
|
if (MetaData(ruleEntity).EntityPrototype?.ID == rule)
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void ClearGameRules()
|
||||||
|
{
|
||||||
|
foreach (var rule in GetAddedGameRules())
|
||||||
|
{
|
||||||
|
EndGameRule(rule);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public sealed class GameRuleStartedEvent
|
/// <summary>
|
||||||
|
/// Gets all the gamerule entities which are currently active.
|
||||||
|
/// </summary>
|
||||||
|
public IEnumerable<EntityUid> GetAddedGameRules()
|
||||||
{
|
{
|
||||||
public GameRulePrototype Rule { get; }
|
var query = EntityQueryEnumerator<GameRuleComponent>();
|
||||||
|
while (query.MoveNext(out var uid, out var ruleData))
|
||||||
public GameRuleStartedEvent(GameRulePrototype rule)
|
|
||||||
{
|
{
|
||||||
Rule = rule;
|
if (IsGameRuleAdded(uid, ruleData))
|
||||||
|
yield return uid;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public sealed class GameRuleEndedEvent
|
/// <summary>
|
||||||
|
/// Gets all the gamerule entities which are currently active.
|
||||||
|
/// </summary>
|
||||||
|
public IEnumerable<EntityUid> GetActiveGameRules()
|
||||||
{
|
{
|
||||||
public GameRulePrototype Rule { get; }
|
var query = EntityQueryEnumerator<GameRuleComponent>();
|
||||||
|
while (query.MoveNext(out var uid, out var ruleData))
|
||||||
public GameRuleEndedEvent(GameRulePrototype rule)
|
|
||||||
{
|
{
|
||||||
Rule = rule;
|
if (ruleData.Active)
|
||||||
|
yield return uid;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets all gamerule prototypes
|
||||||
|
/// </summary>
|
||||||
|
public IEnumerable<EntityPrototype> GetAllGameRulePrototypes()
|
||||||
|
{
|
||||||
|
foreach (var proto in _prototypeManager.EnumeratePrototypes<EntityPrototype>())
|
||||||
|
{
|
||||||
|
if (proto.Abstract)
|
||||||
|
continue;
|
||||||
|
|
||||||
|
if (proto.HasComponent<GameRuleComponent>())
|
||||||
|
yield return proto;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#region Command Implementations
|
||||||
|
|
||||||
|
[AdminCommand(AdminFlags.Fun)]
|
||||||
|
private void AddGameRuleCommand(IConsoleShell shell, string argstr, string[] args)
|
||||||
|
{
|
||||||
|
if (args.Length == 0)
|
||||||
|
return;
|
||||||
|
|
||||||
|
foreach (var rule in args)
|
||||||
|
{
|
||||||
|
var ent = AddGameRule(rule);
|
||||||
|
|
||||||
|
// Start rule if we're already in the middle of a round
|
||||||
|
if(RunLevel == GameRunLevel.InRound)
|
||||||
|
StartGameRule(ent);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private CompletionResult AddGameRuleCompletions(IConsoleShell shell, string[] args)
|
||||||
|
{
|
||||||
|
return CompletionResult.FromHintOptions(GetAllGameRulePrototypes().Select(p => p.ID), "<rule>");
|
||||||
|
}
|
||||||
|
|
||||||
|
[AdminCommand(AdminFlags.Fun)]
|
||||||
|
private void EndGameRuleCommand(IConsoleShell shell, string argstr, string[] args)
|
||||||
|
{
|
||||||
|
if (args.Length == 0)
|
||||||
|
return;
|
||||||
|
|
||||||
|
foreach (var rule in args)
|
||||||
|
{
|
||||||
|
if (!EntityUid.TryParse(rule, out var ruleEnt))
|
||||||
|
continue;
|
||||||
|
|
||||||
|
EndGameRule(ruleEnt);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private CompletionResult EndGameRuleCompletions(IConsoleShell shell, string[] args)
|
||||||
|
{
|
||||||
|
return CompletionResult.FromHintOptions(GetAddedGameRules().Select(u => u.ToString()), "<added rule>");
|
||||||
|
}
|
||||||
|
|
||||||
|
[AdminCommand(AdminFlags.Fun)]
|
||||||
|
private void ClearGameRulesCommand(IConsoleShell shell, string argstr, string[] args)
|
||||||
|
{
|
||||||
|
ClearGameRules();
|
||||||
|
}
|
||||||
|
|
||||||
|
#endregion
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
/// <summary>
|
||||||
|
/// Raised broadcast when a game rule is selected, but not started yet.
|
||||||
|
/// </summary>
|
||||||
|
public sealed class GameRuleAddedEvent
|
||||||
|
{
|
||||||
|
public GameRulePrototype Rule { get; }
|
||||||
|
|
||||||
|
public GameRuleAddedEvent(GameRulePrototype rule)
|
||||||
|
{
|
||||||
|
Rule = rule;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public sealed class GameRuleStartedEvent
|
||||||
|
{
|
||||||
|
public GameRulePrototype Rule { get; }
|
||||||
|
|
||||||
|
public GameRuleStartedEvent(GameRulePrototype rule)
|
||||||
|
{
|
||||||
|
Rule = rule;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public sealed class GameRuleEndedEvent
|
||||||
|
{
|
||||||
|
public GameRulePrototype Rule { get; }
|
||||||
|
|
||||||
|
public GameRuleEndedEvent(GameRulePrototype rule)
|
||||||
|
{
|
||||||
|
Rule = rule;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
*/
|
||||||
|
|||||||
@@ -456,7 +456,6 @@ namespace Content.Server.GameTicking
|
|||||||
// Clear up any game rules.
|
// Clear up any game rules.
|
||||||
ClearGameRules();
|
ClearGameRules();
|
||||||
|
|
||||||
_addedGameRules.Clear();
|
|
||||||
_allPreviousGameRules.Clear();
|
_allPreviousGameRules.Clear();
|
||||||
|
|
||||||
// Round restart cleanup event, so entity systems can reset.
|
// Round restart cleanup event, so entity systems can reset.
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
using Content.Server.GameTicking.Rules;
|
|
||||||
using Robust.Shared.Prototypes;
|
using Robust.Shared.Prototypes;
|
||||||
using Robust.Shared.Serialization.TypeSerializers.Implementations.Custom.Prototype.List;
|
using Robust.Shared.Serialization.TypeSerializers.Implementations.Custom.Prototype.List;
|
||||||
|
|
||||||
@@ -14,24 +14,24 @@ namespace Content.Server.GameTicking.Presets
|
|||||||
public string ID { get; } = default!;
|
public string ID { get; } = default!;
|
||||||
|
|
||||||
[DataField("alias")]
|
[DataField("alias")]
|
||||||
public string[] Alias { get; } = Array.Empty<string>();
|
public readonly string[] Alias = Array.Empty<string>();
|
||||||
|
|
||||||
[DataField("name")]
|
[DataField("name")]
|
||||||
public string ModeTitle { get; } = "????";
|
public readonly string ModeTitle = "????";
|
||||||
|
|
||||||
[DataField("description")]
|
[DataField("description")]
|
||||||
public string Description { get; } = string.Empty;
|
public readonly string Description = string.Empty;
|
||||||
|
|
||||||
[DataField("showInVote")]
|
[DataField("showInVote")]
|
||||||
public bool ShowInVote { get; } = false;
|
public readonly bool ShowInVote;
|
||||||
|
|
||||||
[DataField("minPlayers")]
|
[DataField("minPlayers")]
|
||||||
public int? MinPlayers { get; } = null;
|
public readonly int? MinPlayers;
|
||||||
|
|
||||||
[DataField("maxPlayers")]
|
[DataField("maxPlayers")]
|
||||||
public int? MaxPlayers { get; } = null;
|
public readonly int? MaxPlayers;
|
||||||
|
|
||||||
[DataField("rules", customTypeSerializer:typeof(PrototypeIdListSerializer<GameRulePrototype>))]
|
[DataField("rules", customTypeSerializer: typeof(PrototypeIdListSerializer<EntityPrototype>))]
|
||||||
public IReadOnlyList<string> Rules { get; } = Array.Empty<string>();
|
public IReadOnlyList<string> Rules { get; } = Array.Empty<string>();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,33 @@
|
|||||||
|
namespace Content.Server.GameTicking.Rules.Components;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Simple GameRule that will do a free-for-all death match.
|
||||||
|
/// Kill everybody else to win.
|
||||||
|
/// </summary>
|
||||||
|
[RegisterComponent, Access(typeof(DeathMatchRuleSystem))]
|
||||||
|
public sealed class DeathMatchRuleComponent : Component
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// How long until the round restarts
|
||||||
|
/// </summary>
|
||||||
|
[DataField("restartDelay"), ViewVariables(VVAccess.ReadWrite)]
|
||||||
|
public float RestartDelay = 10f;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// How long after a person dies will the restart be checked
|
||||||
|
/// </summary>
|
||||||
|
[DataField("deadCheckDelay"), ViewVariables(VVAccess.ReadWrite)]
|
||||||
|
public float DeadCheckDelay = 5f;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// A timer for checking after a death
|
||||||
|
/// </summary>
|
||||||
|
[DataField("deadCheckTimer"), ViewVariables(VVAccess.ReadWrite)]
|
||||||
|
public float? DeadCheckTimer;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// A timer for the restart.
|
||||||
|
/// </summary>
|
||||||
|
[DataField("restartTimer"), ViewVariables(VVAccess.ReadWrite)]
|
||||||
|
public float? RestartTimer;
|
||||||
|
}
|
||||||
@@ -0,0 +1,44 @@
|
|||||||
|
namespace Content.Server.GameTicking.Rules.Components;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Component attached to all gamerule entities.
|
||||||
|
/// Used to both track the entity as well as store basic data
|
||||||
|
/// </summary>
|
||||||
|
[RegisterComponent]
|
||||||
|
public sealed class GameRuleComponent : Component
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Whether or not the rule is active.
|
||||||
|
/// Is enabled after <see cref="GameRuleStartedEvent"/> and disabled after <see cref="GameRuleEndedEvent"/>
|
||||||
|
/// </summary>
|
||||||
|
[DataField("active")]
|
||||||
|
public bool Active;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Whether or not the gamerule finished.
|
||||||
|
/// Used for tracking whether a non-active gamerule has been started before.
|
||||||
|
/// </summary>
|
||||||
|
[DataField("ended")]
|
||||||
|
public bool Ended;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Raised when a rule is added but hasn't formally begun yet.
|
||||||
|
/// Good for announcing station events and other such things.
|
||||||
|
/// </summary>
|
||||||
|
[ByRefEvent]
|
||||||
|
public readonly record struct GameRuleAddedEvent(EntityUid RuleEntity, string RuleId);
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Raised when the rule actually begins.
|
||||||
|
/// Player-facing logic should begin here.
|
||||||
|
/// </summary>
|
||||||
|
[ByRefEvent]
|
||||||
|
public readonly record struct GameRuleStartedEvent(EntityUid RuleEntity, string RuleId);
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Raised when the rule ends.
|
||||||
|
/// Do cleanup and other such things here.
|
||||||
|
/// </summary>
|
||||||
|
[ByRefEvent]
|
||||||
|
public readonly record struct GameRuleEndedEvent(EntityUid RuleEntity, string RuleId);
|
||||||
@@ -0,0 +1,24 @@
|
|||||||
|
using System.Threading;
|
||||||
|
|
||||||
|
namespace Content.Server.GameTicking.Rules.Components;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gamerule that ends the round after a period of inactivity.
|
||||||
|
/// </summary>
|
||||||
|
[RegisterComponent, Access(typeof(InactivityTimeRestartRuleSystem))]
|
||||||
|
public sealed class InactivityRuleComponent : Component
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// How long the round must be inactive to restart
|
||||||
|
/// </summary>
|
||||||
|
[DataField("inactivityMaxTime", required: true)]
|
||||||
|
public TimeSpan InactivityMaxTime = TimeSpan.FromMinutes(10);
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// The delay between announcing round end and the lobby.
|
||||||
|
/// </summary>
|
||||||
|
[DataField("roundEndDelay", required: true)]
|
||||||
|
public TimeSpan RoundEndDelay = TimeSpan.FromSeconds(10);
|
||||||
|
|
||||||
|
public CancellationTokenSource TimerCancel = new();
|
||||||
|
}
|
||||||
@@ -0,0 +1,24 @@
|
|||||||
|
using System.Threading;
|
||||||
|
|
||||||
|
namespace Content.Server.GameTicking.Rules.Components;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Configures the <see cref="InactivityTimeRestartRuleSystem"/> game rule.
|
||||||
|
/// </summary>
|
||||||
|
[RegisterComponent]
|
||||||
|
public sealed class MaxTimeRestartRuleComponent : Component
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// The max amount of time the round can last
|
||||||
|
/// </summary>
|
||||||
|
[DataField("roundMaxTime", required: true)]
|
||||||
|
public TimeSpan RoundMaxTime = TimeSpan.FromMinutes(5);
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// The amount of time between the round completing and the lobby appearing.
|
||||||
|
/// </summary>
|
||||||
|
[DataField("roundEndDelay", required: true)]
|
||||||
|
public TimeSpan RoundEndDelay = TimeSpan.FromSeconds(10);
|
||||||
|
|
||||||
|
public CancellationTokenSource TimerCancel = new();
|
||||||
|
}
|
||||||
@@ -6,7 +6,6 @@ namespace Content.Server.GameTicking.Rules.Components;
|
|||||||
/// TODO: Remove once systems can request spawns from the ghost role system directly.
|
/// TODO: Remove once systems can request spawns from the ghost role system directly.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
[RegisterComponent]
|
[RegisterComponent]
|
||||||
[Access(typeof(NukeopsRuleSystem))]
|
|
||||||
public sealed class NukeOperativeSpawnerComponent : Component
|
public sealed class NukeOperativeSpawnerComponent : Component
|
||||||
{
|
{
|
||||||
[DataField("name")]
|
[DataField("name")]
|
||||||
|
|||||||
@@ -1,19 +1,22 @@
|
|||||||
using Content.Server.GameTicking.Rules.Configurations;
|
using Content.Server.StationEvents.Events;
|
||||||
using Content.Shared.Dataset;
|
using Content.Shared.Dataset;
|
||||||
using Content.Shared.Humanoid.Prototypes;
|
using Content.Shared.Humanoid.Prototypes;
|
||||||
using Content.Shared.Roles;
|
using Content.Shared.Roles;
|
||||||
|
using Robust.Server.Player;
|
||||||
using Robust.Shared.Audio;
|
using Robust.Shared.Audio;
|
||||||
using Robust.Shared.Prototypes;
|
using Robust.Shared.Map;
|
||||||
using Robust.Shared.Serialization.TypeSerializers.Implementations;
|
using Robust.Shared.Serialization.TypeSerializers.Implementations;
|
||||||
using Robust.Shared.Serialization.TypeSerializers.Implementations.Custom.Prototype;
|
using Robust.Shared.Serialization.TypeSerializers.Implementations.Custom.Prototype;
|
||||||
using Robust.Shared.Utility;
|
using Robust.Shared.Utility;
|
||||||
|
|
||||||
namespace Content.Server.GameTicking.Rules.Configurations;
|
namespace Content.Server.GameTicking.Rules.Components;
|
||||||
|
|
||||||
public sealed class NukeopsRuleConfiguration : GameRuleConfiguration
|
[RegisterComponent, Access(typeof(NukeopsRuleSystem), typeof(LoneOpsSpawnRule))]
|
||||||
|
public sealed class NukeopsRuleComponent : Component
|
||||||
{
|
{
|
||||||
public override string Id => "Nukeops";
|
/// <summary>
|
||||||
|
/// The minimum needed amount of players
|
||||||
|
/// </summary>
|
||||||
[DataField("minPlayers")]
|
[DataField("minPlayers")]
|
||||||
public int MinPlayers = 15;
|
public int MinPlayers = 15;
|
||||||
|
|
||||||
@@ -38,15 +41,6 @@ public sealed class NukeopsRuleConfiguration : GameRuleConfiguration
|
|||||||
[DataField("spawnOutpost")]
|
[DataField("spawnOutpost")]
|
||||||
public bool SpawnOutpost = true;
|
public bool SpawnOutpost = true;
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Whether or not loneops can spawn. Set to false if a normal nukeops round is occurring.
|
|
||||||
/// </summary>
|
|
||||||
[DataField("canLoneOpsSpawn")]
|
|
||||||
public bool CanLoneOpsSpawn = true;
|
|
||||||
|
|
||||||
[DataField("randomHumanoidSettings", customTypeSerializer: typeof(PrototypeIdSerializer<RandomHumanoidSettingsPrototype>))]
|
|
||||||
public string RandomHumanoidSettingsPrototype = "NukeOp";
|
|
||||||
|
|
||||||
[DataField("spawnPointProto", customTypeSerializer: typeof(PrototypeIdSerializer<StartingGearPrototype>))]
|
[DataField("spawnPointProto", customTypeSerializer: typeof(PrototypeIdSerializer<StartingGearPrototype>))]
|
||||||
public string SpawnPointPrototype = "SpawnPointNukies";
|
public string SpawnPointPrototype = "SpawnPointNukies";
|
||||||
|
|
||||||
@@ -82,4 +76,86 @@ public sealed class NukeopsRuleConfiguration : GameRuleConfiguration
|
|||||||
|
|
||||||
[DataField("greetingSound", customTypeSerializer: typeof(SoundSpecifierTypeSerializer))]
|
[DataField("greetingSound", customTypeSerializer: typeof(SoundSpecifierTypeSerializer))]
|
||||||
public SoundSpecifier? GreetSound = new SoundPathSpecifier("/Audio/Misc/nukeops.ogg");
|
public SoundSpecifier? GreetSound = new SoundPathSpecifier("/Audio/Misc/nukeops.ogg");
|
||||||
|
|
||||||
|
[DataField("winType")]
|
||||||
|
public WinType WinType = WinType.Neutral;
|
||||||
|
|
||||||
|
[DataField("winConditions")]
|
||||||
|
public List<WinCondition> WinConditions = new ();
|
||||||
|
|
||||||
|
public MapId? NukiePlanet;
|
||||||
|
|
||||||
|
// TODO: use components, don't just cache entity UIDs
|
||||||
|
// There have been (and probably still are) bugs where these refer to deleted entities from old rounds.
|
||||||
|
public EntityUid? NukieOutpost;
|
||||||
|
public EntityUid? NukieShuttle;
|
||||||
|
public EntityUid? TargetStation;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Cached starting gear prototypes.
|
||||||
|
/// </summary>
|
||||||
|
[DataField("startingGearPrototypes")]
|
||||||
|
public readonly Dictionary<string, StartingGearPrototype> StartingGearPrototypes = new ();
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Cached operator name prototypes.
|
||||||
|
/// </summary>
|
||||||
|
[DataField("operativeNames")]
|
||||||
|
public readonly Dictionary<string, List<string>> OperativeNames = new();
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Data to be used in <see cref="OnMindAdded"/> for an operative once the Mind has been added.
|
||||||
|
/// </summary>
|
||||||
|
[DataField("operativeMindPendingData")]
|
||||||
|
public readonly Dictionary<EntityUid, string> OperativeMindPendingData = new();
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Players who played as an operative at some point in the round.
|
||||||
|
/// Stores the session as well as the entity name
|
||||||
|
/// </summary>
|
||||||
|
/// todo: don't store sessions, dingus
|
||||||
|
[DataField("operativePlayers")]
|
||||||
|
public readonly Dictionary<string, IPlayerSession> OperativePlayers = new();
|
||||||
|
}
|
||||||
|
|
||||||
|
public enum WinType : byte
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Operative major win. This means they nuked the station.
|
||||||
|
/// </summary>
|
||||||
|
OpsMajor,
|
||||||
|
/// <summary>
|
||||||
|
/// Minor win. All nukies were alive at the end of the round.
|
||||||
|
/// Alternatively, some nukies were alive, but the disk was left behind.
|
||||||
|
/// </summary>
|
||||||
|
OpsMinor,
|
||||||
|
/// <summary>
|
||||||
|
/// Neutral win. The nuke exploded, but on the wrong station.
|
||||||
|
/// </summary>
|
||||||
|
Neutral,
|
||||||
|
/// <summary>
|
||||||
|
/// Crew minor win. The nuclear authentication disk escaped on the shuttle,
|
||||||
|
/// but some nukies were alive.
|
||||||
|
/// </summary>
|
||||||
|
CrewMinor,
|
||||||
|
/// <summary>
|
||||||
|
/// Crew major win. This means they either killed all nukies,
|
||||||
|
/// or the bomb exploded too far away from the station, or on the nukie moon.
|
||||||
|
/// </summary>
|
||||||
|
CrewMajor
|
||||||
|
}
|
||||||
|
|
||||||
|
public enum WinCondition : byte
|
||||||
|
{
|
||||||
|
NukeExplodedOnCorrectStation,
|
||||||
|
NukeExplodedOnNukieOutpost,
|
||||||
|
NukeExplodedOnIncorrectLocation,
|
||||||
|
NukeActiveInStation,
|
||||||
|
NukeActiveAtCentCom,
|
||||||
|
NukeDiskOnCentCom,
|
||||||
|
NukeDiskNotOnCentCom,
|
||||||
|
NukiesAbandoned,
|
||||||
|
AllNukiesDead,
|
||||||
|
SomeNukiesAlive,
|
||||||
|
AllNukiesAlive
|
||||||
}
|
}
|
||||||
@@ -0,0 +1,15 @@
|
|||||||
|
namespace Content.Server.GameTicking.Rules.Components;
|
||||||
|
|
||||||
|
[RegisterComponent, Access(typeof(PiratesRuleSystem))]
|
||||||
|
public sealed class PiratesRuleComponent : Component
|
||||||
|
{
|
||||||
|
[ViewVariables]
|
||||||
|
public List<Mind.Mind> Pirates = new();
|
||||||
|
[ViewVariables]
|
||||||
|
public EntityUid PirateShip = EntityUid.Invalid;
|
||||||
|
[ViewVariables]
|
||||||
|
public HashSet<EntityUid> InitialItems = new();
|
||||||
|
[ViewVariables]
|
||||||
|
public double InitialShipValue;
|
||||||
|
|
||||||
|
}
|
||||||
@@ -0,0 +1,7 @@
|
|||||||
|
namespace Content.Server.GameTicking.Rules.Components;
|
||||||
|
|
||||||
|
[RegisterComponent, Access(typeof(SandboxRuleSystem))]
|
||||||
|
public sealed class SandboxRuleComponent : Component
|
||||||
|
{
|
||||||
|
|
||||||
|
}
|
||||||
@@ -0,0 +1,11 @@
|
|||||||
|
namespace Content.Server.GameTicking.Rules.Components;
|
||||||
|
|
||||||
|
[RegisterComponent, Access(typeof(SecretRuleSystem))]
|
||||||
|
public sealed class SecretRuleComponent : Component
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// The gamerules that get added by secret.
|
||||||
|
/// </summary>
|
||||||
|
[DataField("additionalGameRules")]
|
||||||
|
public HashSet<EntityUid> AdditionalGameRules = new();
|
||||||
|
}
|
||||||
@@ -0,0 +1,32 @@
|
|||||||
|
using Content.Server.Traitor;
|
||||||
|
using Content.Shared.Preferences;
|
||||||
|
using Content.Shared.Roles;
|
||||||
|
using Robust.Server.Player;
|
||||||
|
using Robust.Shared.Audio;
|
||||||
|
using Robust.Shared.Serialization.TypeSerializers.Implementations.Custom.Prototype;
|
||||||
|
|
||||||
|
namespace Content.Server.GameTicking.Rules.Components;
|
||||||
|
|
||||||
|
[RegisterComponent, Access(typeof(TraitorRuleSystem))]
|
||||||
|
public sealed class TraitorRuleComponent : Component
|
||||||
|
{
|
||||||
|
public readonly SoundSpecifier AddedSound = new SoundPathSpecifier("/Audio/Misc/tatoralert.ogg");
|
||||||
|
public List<TraitorRole> Traitors = new();
|
||||||
|
|
||||||
|
[DataField("traitorPrototypeId", customTypeSerializer: typeof(PrototypeIdSerializer<AntagPrototype>))]
|
||||||
|
public string TraitorPrototypeId = "Traitor";
|
||||||
|
|
||||||
|
public int TotalTraitors => Traitors.Count;
|
||||||
|
public string[] Codewords = new string[3];
|
||||||
|
|
||||||
|
public enum SelectionState
|
||||||
|
{
|
||||||
|
WaitingForSpawn = 0,
|
||||||
|
ReadyToSelect = 1,
|
||||||
|
SelectionMade = 2,
|
||||||
|
}
|
||||||
|
|
||||||
|
public SelectionState SelectionStatus = SelectionState.WaitingForSpawn;
|
||||||
|
public TimeSpan AnnounceAt = TimeSpan.Zero;
|
||||||
|
public Dictionary<IPlayerSession, HumanoidCharacterProfile> StartCandidates = new();
|
||||||
|
}
|
||||||
@@ -0,0 +1,12 @@
|
|||||||
|
namespace Content.Server.GameTicking.Rules.Components;
|
||||||
|
|
||||||
|
|
||||||
|
[RegisterComponent, Access(typeof(ZombieRuleSystem))]
|
||||||
|
public sealed class ZombieRuleComponent : Component
|
||||||
|
{
|
||||||
|
public Dictionary<string, string> InitialInfectedNames = new();
|
||||||
|
|
||||||
|
public string PatientZeroPrototypeID = "InitialInfected";
|
||||||
|
public string InitialZombieVirusPrototype = "PassiveZombieVirus";
|
||||||
|
public const string ZombifySelfActionPrototype = "TurnUndead";
|
||||||
|
}
|
||||||
@@ -1,13 +0,0 @@
|
|||||||
namespace Content.Server.GameTicking.Rules.Configurations;
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Configures a game rule, providing information like what maps to use or how long to run.
|
|
||||||
/// </summary>
|
|
||||||
[ImplicitDataDefinitionForInheritors]
|
|
||||||
public abstract class GameRuleConfiguration
|
|
||||||
{
|
|
||||||
/// <summary>
|
|
||||||
/// The game rule this configuration is intended for.
|
|
||||||
/// </summary>
|
|
||||||
public abstract string Id { get; }
|
|
||||||
}
|
|
||||||
@@ -1,14 +0,0 @@
|
|||||||
using JetBrains.Annotations;
|
|
||||||
|
|
||||||
namespace Content.Server.GameTicking.Rules.Configurations;
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// A generic configuration, for game rules that don't have special config data.
|
|
||||||
/// </summary>
|
|
||||||
[UsedImplicitly]
|
|
||||||
public sealed class GenericGameRuleConfiguration : GameRuleConfiguration
|
|
||||||
{
|
|
||||||
[DataField("id", required: true)]
|
|
||||||
private string _id = default!;
|
|
||||||
public override string Id => _id;
|
|
||||||
}
|
|
||||||
@@ -1,17 +0,0 @@
|
|||||||
using JetBrains.Annotations;
|
|
||||||
|
|
||||||
namespace Content.Server.GameTicking.Rules.Configurations;
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Configures the <see cref="InactivityTimeRestartRuleSystem"/> game rule.
|
|
||||||
/// </summary>
|
|
||||||
[UsedImplicitly]
|
|
||||||
public sealed class InactivityGameRuleConfiguration : GameRuleConfiguration
|
|
||||||
{
|
|
||||||
public override string Id => "InactivityTimeRestart"; // The value for this in the system isn't static and can't be made static. RIP.
|
|
||||||
|
|
||||||
[DataField("inactivityMaxTime", required: true)]
|
|
||||||
public TimeSpan InactivityMaxTime { get; }
|
|
||||||
[DataField("roundEndDelay", required: true)]
|
|
||||||
public TimeSpan RoundEndDelay { get; }
|
|
||||||
}
|
|
||||||
@@ -1,17 +0,0 @@
|
|||||||
using JetBrains.Annotations;
|
|
||||||
|
|
||||||
namespace Content.Server.GameTicking.Rules.Configurations;
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Configures the <see cref="InactivityTimeRestartRuleSystem"/> game rule.
|
|
||||||
/// </summary>
|
|
||||||
[UsedImplicitly]
|
|
||||||
public sealed class MaxTimeRestartRuleConfiguration : GameRuleConfiguration
|
|
||||||
{
|
|
||||||
public override string Id => "MaxTimeRestart"; // The value for this in the system isn't static and can't be made static. RIP.
|
|
||||||
|
|
||||||
[DataField("roundMaxTime", required: true)]
|
|
||||||
public TimeSpan RoundMaxTime { get; }
|
|
||||||
[DataField("roundEndDelay", required: true)]
|
|
||||||
public TimeSpan RoundEndDelay { get; }
|
|
||||||
}
|
|
||||||
@@ -1,5 +1,5 @@
|
|||||||
using Content.Server.Chat.Managers;
|
using Content.Server.Chat.Managers;
|
||||||
using Content.Server.GameTicking.Rules.Configurations;
|
using Content.Server.GameTicking.Rules.Components;
|
||||||
using Content.Shared.CCVar;
|
using Content.Shared.CCVar;
|
||||||
using Content.Shared.Damage;
|
using Content.Shared.Damage;
|
||||||
using Content.Shared.Mobs.Components;
|
using Content.Shared.Mobs.Components;
|
||||||
@@ -11,52 +11,50 @@ using Robust.Shared.Enums;
|
|||||||
namespace Content.Server.GameTicking.Rules;
|
namespace Content.Server.GameTicking.Rules;
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Simple GameRule that will do a free-for-all death match.
|
/// Manages <see cref="DeathMatchRuleComponent"/>
|
||||||
/// Kill everybody else to win.
|
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public sealed class DeathMatchRuleSystem : GameRuleSystem
|
public sealed class DeathMatchRuleSystem : GameRuleSystem<DeathMatchRuleComponent>
|
||||||
{
|
{
|
||||||
public override string Prototype => "DeathMatch";
|
|
||||||
|
|
||||||
[Dependency] private readonly IPlayerManager _playerManager = default!;
|
[Dependency] private readonly IPlayerManager _playerManager = default!;
|
||||||
[Dependency] private readonly IChatManager _chatManager = default!;
|
[Dependency] private readonly IChatManager _chatManager = default!;
|
||||||
[Dependency] private readonly MobStateSystem _mobStateSystem = default!;
|
[Dependency] private readonly MobStateSystem _mobStateSystem = default!;
|
||||||
[Dependency] private readonly IConfigurationManager _cfg = default!;
|
[Dependency] private readonly IConfigurationManager _cfg = default!;
|
||||||
|
|
||||||
private const float RestartDelay = 10f;
|
|
||||||
private const float DeadCheckDelay = 5f;
|
|
||||||
|
|
||||||
private float? _deadCheckTimer = null;
|
|
||||||
private float? _restartTimer = null;
|
|
||||||
|
|
||||||
public override void Initialize()
|
public override void Initialize()
|
||||||
{
|
{
|
||||||
base.Initialize();
|
base.Initialize();
|
||||||
|
|
||||||
SubscribeLocalEvent<DamageChangedEvent>(OnHealthChanged);
|
SubscribeLocalEvent<DamageChangedEvent>(OnHealthChanged);
|
||||||
}
|
|
||||||
|
|
||||||
public override void Started()
|
|
||||||
{
|
|
||||||
_chatManager.DispatchServerAnnouncement(Loc.GetString("rule-death-match-added-announcement"));
|
|
||||||
|
|
||||||
_playerManager.PlayerStatusChanged += OnPlayerStatusChanged;
|
_playerManager.PlayerStatusChanged += OnPlayerStatusChanged;
|
||||||
}
|
}
|
||||||
|
|
||||||
public override void Ended()
|
public override void Shutdown()
|
||||||
{
|
{
|
||||||
_deadCheckTimer = null;
|
base.Shutdown();
|
||||||
_restartTimer = null;
|
|
||||||
|
|
||||||
_playerManager.PlayerStatusChanged -= OnPlayerStatusChanged;
|
_playerManager.PlayerStatusChanged -= OnPlayerStatusChanged;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
protected override void Started(EntityUid uid, DeathMatchRuleComponent component, GameRuleComponent gameRule, GameRuleStartedEvent args)
|
||||||
|
{
|
||||||
|
_chatManager.DispatchServerAnnouncement(Loc.GetString("rule-death-match-added-announcement"));
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
protected override void Ended(EntityUid uid, DeathMatchRuleComponent component, GameRuleComponent gameRule, GameRuleEndedEvent args)
|
||||||
|
{
|
||||||
|
base.Ended(uid, component, gameRule, args);
|
||||||
|
|
||||||
|
component.DeadCheckTimer = null;
|
||||||
|
component.RestartTimer = null;
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
private void OnHealthChanged(DamageChangedEvent _)
|
private void OnHealthChanged(DamageChangedEvent _)
|
||||||
{
|
{
|
||||||
RunDelayedCheck();
|
RunDelayedCheck();
|
||||||
}
|
}
|
||||||
|
|
||||||
private void OnPlayerStatusChanged(object? _, SessionStatusEventArgs e)
|
private void OnPlayerStatusChanged(object? ojb, SessionStatusEventArgs e)
|
||||||
{
|
{
|
||||||
if (e.NewStatus == SessionStatus.Disconnected)
|
if (e.NewStatus == SessionStatus.Disconnected)
|
||||||
{
|
{
|
||||||
@@ -66,24 +64,27 @@ public sealed class DeathMatchRuleSystem : GameRuleSystem
|
|||||||
|
|
||||||
private void RunDelayedCheck()
|
private void RunDelayedCheck()
|
||||||
{
|
{
|
||||||
if (!RuleAdded || _deadCheckTimer != null)
|
var query = EntityQueryEnumerator<DeathMatchRuleComponent, GameRuleComponent>();
|
||||||
return;
|
while (query.MoveNext(out var uid, out var deathMatch, out var gameRule))
|
||||||
|
{
|
||||||
|
if (!GameTicker.IsGameRuleActive(uid, gameRule) || deathMatch.DeadCheckTimer != null)
|
||||||
|
continue;
|
||||||
|
|
||||||
_deadCheckTimer = DeadCheckDelay;
|
deathMatch.DeadCheckTimer = deathMatch.DeadCheckDelay;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public override void Update(float frameTime)
|
protected override void ActiveTick(EntityUid uid, DeathMatchRuleComponent component, GameRuleComponent gameRule, float frameTime)
|
||||||
{
|
{
|
||||||
if (!RuleAdded)
|
base.ActiveTick(uid, component, gameRule, frameTime);
|
||||||
return;
|
|
||||||
|
|
||||||
// If the restart timer is active, that means the round is ending soon, no need to check for winners.
|
// If the restart timer is active, that means the round is ending soon, no need to check for winners.
|
||||||
// TODO: We probably want a sane, centralized round end thingie in GameTicker, RoundEndSystem is no good...
|
// TODO: We probably want a sane, centralized round end thingie in GameTicker, RoundEndSystem is no good...
|
||||||
if (_restartTimer != null)
|
if (component.RestartTimer != null)
|
||||||
{
|
{
|
||||||
_restartTimer -= frameTime;
|
component.RestartTimer -= frameTime;
|
||||||
|
|
||||||
if (_restartTimer > 0f)
|
if (component.RestartTimer > 0f)
|
||||||
return;
|
return;
|
||||||
|
|
||||||
GameTicker.EndRound();
|
GameTicker.EndRound();
|
||||||
@@ -91,20 +92,20 @@ public sealed class DeathMatchRuleSystem : GameRuleSystem
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!_cfg.GetCVar(CCVars.GameLobbyEnableWin) || _deadCheckTimer == null)
|
if (!_cfg.GetCVar(CCVars.GameLobbyEnableWin) || component.DeadCheckTimer == null)
|
||||||
return;
|
return;
|
||||||
|
|
||||||
_deadCheckTimer -= frameTime;
|
component.DeadCheckTimer -= frameTime;
|
||||||
|
|
||||||
if (_deadCheckTimer > 0)
|
if (component.DeadCheckTimer > 0)
|
||||||
return;
|
return;
|
||||||
|
|
||||||
_deadCheckTimer = null;
|
component.DeadCheckTimer = null;
|
||||||
|
|
||||||
IPlayerSession? winner = null;
|
IPlayerSession? winner = null;
|
||||||
foreach (var playerSession in _playerManager.ServerSessions)
|
foreach (var playerSession in _playerManager.ServerSessions)
|
||||||
{
|
{
|
||||||
if (playerSession.AttachedEntity is not {Valid: true} playerEntity
|
if (playerSession.AttachedEntity is not { Valid: true } playerEntity
|
||||||
|| !TryComp(playerEntity, out MobStateComponent? state))
|
|| !TryComp(playerEntity, out MobStateComponent? state))
|
||||||
continue;
|
continue;
|
||||||
|
|
||||||
@@ -120,9 +121,10 @@ public sealed class DeathMatchRuleSystem : GameRuleSystem
|
|||||||
|
|
||||||
_chatManager.DispatchServerAnnouncement(winner == null
|
_chatManager.DispatchServerAnnouncement(winner == null
|
||||||
? Loc.GetString("rule-death-match-check-winner-stalemate")
|
? Loc.GetString("rule-death-match-check-winner-stalemate")
|
||||||
: Loc.GetString("rule-death-match-check-winner",("winner", winner)));
|
: Loc.GetString("rule-death-match-check-winner", ("winner", winner)));
|
||||||
|
|
||||||
_chatManager.DispatchServerAnnouncement(Loc.GetString("rule-restarting-in-seconds", ("seconds", RestartDelay)));
|
_chatManager.DispatchServerAnnouncement(Loc.GetString("rule-restarting-in-seconds",
|
||||||
_restartTimer = RestartDelay;
|
("seconds", component.RestartDelay)));
|
||||||
|
component.RestartTimer = component.RestartDelay;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,8 +1,8 @@
|
|||||||
using Content.Server.GameTicking.Rules.Configurations;
|
|
||||||
using Robust.Shared.Prototypes;
|
|
||||||
|
|
||||||
namespace Content.Server.GameTicking.Rules;
|
namespace Content.Server.GameTicking.Rules;
|
||||||
|
|
||||||
|
/*
|
||||||
[Prototype("gameRule")]
|
[Prototype("gameRule")]
|
||||||
public sealed class GameRulePrototype : IPrototype
|
public sealed class GameRulePrototype : IPrototype
|
||||||
{
|
{
|
||||||
@@ -12,3 +12,4 @@ public sealed class GameRulePrototype : IPrototype
|
|||||||
[DataField("config", required: true)]
|
[DataField("config", required: true)]
|
||||||
public GameRuleConfiguration Configuration { get; } = default!;
|
public GameRuleConfiguration Configuration { get; } = default!;
|
||||||
}
|
}
|
||||||
|
*/
|
||||||
|
|||||||
@@ -1,94 +1,84 @@
|
|||||||
using Content.Server.GameTicking.Rules.Configurations;
|
using Content.Server.GameTicking.Rules.Components;
|
||||||
using JetBrains.Annotations;
|
|
||||||
|
|
||||||
namespace Content.Server.GameTicking.Rules;
|
namespace Content.Server.GameTicking.Rules;
|
||||||
|
|
||||||
[PublicAPI]
|
public abstract class GameRuleSystem<T> : EntitySystem where T : Component
|
||||||
public abstract class GameRuleSystem : EntitySystem
|
|
||||||
{
|
{
|
||||||
[Dependency] protected GameTicker GameTicker = default!;
|
[Dependency] protected GameTicker GameTicker = default!;
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Whether this GameRule is currently added or not.
|
|
||||||
/// Be sure to check this before doing anything rule-specific.
|
|
||||||
/// </summary>
|
|
||||||
public bool RuleAdded { get; protected set; }
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Whether this game rule has been started after being added.
|
|
||||||
/// You probably want to check this before doing any update loop stuff.
|
|
||||||
/// </summary>
|
|
||||||
public bool RuleStarted { get; protected set; }
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// When the GameRule prototype with this ID is added, this system will be enabled.
|
|
||||||
/// When it gets removed, this system will be disabled.
|
|
||||||
/// </summary>
|
|
||||||
public new abstract string Prototype { get; }
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Holds the current configuration after the event has been added.
|
|
||||||
/// This should not be getting accessed before the event is enabled, as usual.
|
|
||||||
/// </summary>
|
|
||||||
public GameRuleConfiguration Configuration = default!;
|
|
||||||
|
|
||||||
public override void Initialize()
|
public override void Initialize()
|
||||||
{
|
{
|
||||||
base.Initialize();
|
base.Initialize();
|
||||||
|
|
||||||
SubscribeLocalEvent<GameRuleAddedEvent>(OnGameRuleAdded);
|
SubscribeLocalEvent<T, GameRuleAddedEvent>(OnGameRuleAdded);
|
||||||
|
SubscribeLocalEvent<T, GameRuleStartedEvent>(OnGameRuleStarted);
|
||||||
SubscribeLocalEvent<GameRuleStartedEvent>(OnGameRuleStarted);
|
SubscribeLocalEvent<T, GameRuleEndedEvent>(OnGameRuleEnded);
|
||||||
SubscribeLocalEvent<GameRuleEndedEvent>(OnGameRuleEnded);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private void OnGameRuleAdded(GameRuleAddedEvent ev)
|
private void OnGameRuleAdded(EntityUid uid, T component, ref GameRuleAddedEvent args)
|
||||||
{
|
{
|
||||||
if (ev.Rule.Configuration.Id != Prototype)
|
if (!TryComp<GameRuleComponent>(uid, out var ruleData))
|
||||||
return;
|
return;
|
||||||
|
Added(uid, component, ruleData, args);
|
||||||
Configuration = ev.Rule.Configuration;
|
|
||||||
RuleAdded = true;
|
|
||||||
|
|
||||||
Added();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private void OnGameRuleStarted(GameRuleStartedEvent ev)
|
private void OnGameRuleStarted(EntityUid uid, T component, ref GameRuleStartedEvent args)
|
||||||
{
|
{
|
||||||
if (ev.Rule.Configuration.Id != Prototype)
|
if (!TryComp<GameRuleComponent>(uid, out var ruleData))
|
||||||
return;
|
return;
|
||||||
|
Started(uid, component, ruleData, args);
|
||||||
RuleStarted = true;
|
|
||||||
|
|
||||||
Started();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private void OnGameRuleEnded(GameRuleEndedEvent ev)
|
private void OnGameRuleEnded(EntityUid uid, T component, ref GameRuleEndedEvent args)
|
||||||
{
|
{
|
||||||
if (ev.Rule.Configuration.Id != Prototype)
|
if (!TryComp<GameRuleComponent>(uid, out var ruleData))
|
||||||
return;
|
return;
|
||||||
|
Ended(uid, component, ruleData, args);
|
||||||
RuleAdded = false;
|
|
||||||
RuleStarted = false;
|
|
||||||
Ended();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Called when the game rule has been added.
|
/// Called when the gamerule is added
|
||||||
/// You should avoid using this in favor of started--they are not the same thing.
|
|
||||||
/// </summary>
|
/// </summary>
|
||||||
/// <remarks>
|
protected virtual void Added(EntityUid uid, T component, GameRuleComponent gameRule, GameRuleAddedEvent args)
|
||||||
/// This is virtual because it doesn't actually have to be used, and most of the time shouldn't be.
|
{
|
||||||
/// </remarks>
|
|
||||||
public virtual void Added() { }
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Called when the game rule has been started.
|
/// Called when the gamerule begins
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public abstract void Started();
|
protected virtual void Started(EntityUid uid, T component, GameRuleComponent gameRule, GameRuleStartedEvent args)
|
||||||
|
{
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Called when the game rule has ended.
|
/// Called when the gamerule ends
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public abstract void Ended();
|
protected virtual void Ended(EntityUid uid, T component, GameRuleComponent gameRule, GameRuleEndedEvent args)
|
||||||
|
{
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Called on an active gamerule entity in the Update function
|
||||||
|
/// </summary>
|
||||||
|
protected virtual void ActiveTick(EntityUid uid, T component, GameRuleComponent gameRule, float frameTime)
|
||||||
|
{
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
public override void Update(float frameTime)
|
||||||
|
{
|
||||||
|
base.Update(frameTime);
|
||||||
|
|
||||||
|
var query = EntityQueryEnumerator<T, GameRuleComponent>();
|
||||||
|
while (query.MoveNext(out var uid, out var comp1, out var comp2))
|
||||||
|
{
|
||||||
|
if (!GameTicker.IsGameRuleActive(uid, comp2))
|
||||||
|
continue;
|
||||||
|
|
||||||
|
ActiveTick(uid, comp1, comp2, frameTime);
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,98 +1,109 @@
|
|||||||
using System.Threading;
|
using System.Threading;
|
||||||
using Content.Server.Chat.Managers;
|
using Content.Server.Chat.Managers;
|
||||||
using Content.Server.GameTicking.Rules.Configurations;
|
using Content.Server.GameTicking.Rules.Components;
|
||||||
using Robust.Server.Player;
|
using Robust.Server.Player;
|
||||||
using Timer = Robust.Shared.Timing.Timer;
|
using Timer = Robust.Shared.Timing.Timer;
|
||||||
|
|
||||||
namespace Content.Server.GameTicking.Rules;
|
namespace Content.Server.GameTicking.Rules;
|
||||||
|
|
||||||
public sealed class InactivityTimeRestartRuleSystem : GameRuleSystem
|
public sealed class InactivityTimeRestartRuleSystem : GameRuleSystem<InactivityRuleComponent>
|
||||||
{
|
{
|
||||||
[Dependency] private readonly IChatManager _chatManager = default!;
|
[Dependency] private readonly IChatManager _chatManager = default!;
|
||||||
[Dependency] private readonly IPlayerManager _playerManager = default!;
|
[Dependency] private readonly IPlayerManager _playerManager = default!;
|
||||||
|
|
||||||
public override string Prototype => "InactivityTimeRestart";
|
|
||||||
|
|
||||||
private CancellationTokenSource _timerCancel = new();
|
|
||||||
|
|
||||||
public TimeSpan InactivityMaxTime { get; set; } = TimeSpan.FromMinutes(10);
|
|
||||||
public TimeSpan RoundEndDelay { get; set; } = TimeSpan.FromSeconds(10);
|
|
||||||
|
|
||||||
public override void Initialize()
|
public override void Initialize()
|
||||||
{
|
{
|
||||||
base.Initialize();
|
base.Initialize();
|
||||||
|
|
||||||
SubscribeLocalEvent<GameRunLevelChangedEvent>(RunLevelChanged);
|
SubscribeLocalEvent<GameRunLevelChangedEvent>(RunLevelChanged);
|
||||||
}
|
|
||||||
|
|
||||||
public override void Started()
|
|
||||||
{
|
|
||||||
if (Configuration is not InactivityGameRuleConfiguration inactivityConfig)
|
|
||||||
return;
|
|
||||||
InactivityMaxTime = inactivityConfig.InactivityMaxTime;
|
|
||||||
RoundEndDelay = inactivityConfig.RoundEndDelay;
|
|
||||||
_playerManager.PlayerStatusChanged += PlayerStatusChanged;
|
_playerManager.PlayerStatusChanged += PlayerStatusChanged;
|
||||||
}
|
}
|
||||||
|
|
||||||
public override void Ended()
|
public override void Shutdown()
|
||||||
{
|
{
|
||||||
|
base.Shutdown();
|
||||||
_playerManager.PlayerStatusChanged -= PlayerStatusChanged;
|
_playerManager.PlayerStatusChanged -= PlayerStatusChanged;
|
||||||
|
|
||||||
StopTimer();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public void RestartTimer()
|
protected override void Ended(EntityUid uid, InactivityRuleComponent component, GameRuleComponent gameRule, GameRuleEndedEvent args)
|
||||||
{
|
{
|
||||||
_timerCancel.Cancel();
|
base.Ended(uid, component, gameRule, args);
|
||||||
_timerCancel = new CancellationTokenSource();
|
|
||||||
Timer.Spawn(InactivityMaxTime, TimerFired, _timerCancel.Token);
|
StopTimer(uid, component);
|
||||||
}
|
}
|
||||||
|
|
||||||
public void StopTimer()
|
public void RestartTimer(EntityUid uid, InactivityRuleComponent? component = null)
|
||||||
{
|
{
|
||||||
_timerCancel.Cancel();
|
if (!Resolve(uid, ref component))
|
||||||
|
return;
|
||||||
|
|
||||||
|
component.TimerCancel.Cancel();
|
||||||
|
component.TimerCancel = new CancellationTokenSource();
|
||||||
|
Timer.Spawn(component.InactivityMaxTime, () => TimerFired(uid, component), component.TimerCancel.Token);
|
||||||
}
|
}
|
||||||
|
|
||||||
private void TimerFired()
|
public void StopTimer(EntityUid uid, InactivityRuleComponent? component = null)
|
||||||
{
|
{
|
||||||
|
if (!Resolve(uid, ref component))
|
||||||
|
return;
|
||||||
|
|
||||||
|
component.TimerCancel.Cancel();
|
||||||
|
}
|
||||||
|
|
||||||
|
private void TimerFired(EntityUid uid, InactivityRuleComponent? component = null)
|
||||||
|
{
|
||||||
|
if (!Resolve(uid, ref component))
|
||||||
|
return;
|
||||||
|
|
||||||
GameTicker.EndRound(Loc.GetString("rule-time-has-run-out"));
|
GameTicker.EndRound(Loc.GetString("rule-time-has-run-out"));
|
||||||
|
|
||||||
_chatManager.DispatchServerAnnouncement(Loc.GetString("rule-restarting-in-seconds", ("seconds",(int) RoundEndDelay.TotalSeconds)));
|
_chatManager.DispatchServerAnnouncement(Loc.GetString("rule-restarting-in-seconds", ("seconds",(int) component.RoundEndDelay.TotalSeconds)));
|
||||||
|
|
||||||
Timer.Spawn(RoundEndDelay, () => GameTicker.RestartRound());
|
Timer.Spawn(component.RoundEndDelay, () => GameTicker.RestartRound());
|
||||||
}
|
}
|
||||||
|
|
||||||
private void RunLevelChanged(GameRunLevelChangedEvent args)
|
private void RunLevelChanged(GameRunLevelChangedEvent args)
|
||||||
{
|
{
|
||||||
if (!RuleAdded)
|
var query = EntityQueryEnumerator<InactivityRuleComponent, GameRuleComponent>();
|
||||||
return;
|
while (query.MoveNext(out var uid, out var inactivity, out var gameRule))
|
||||||
|
|
||||||
switch (args.New)
|
|
||||||
{
|
{
|
||||||
case GameRunLevel.InRound:
|
if (!GameTicker.IsGameRuleActive(uid, gameRule))
|
||||||
RestartTimer();
|
return;
|
||||||
break;
|
|
||||||
case GameRunLevel.PreRoundLobby:
|
switch (args.New)
|
||||||
case GameRunLevel.PostRound:
|
{
|
||||||
StopTimer();
|
case GameRunLevel.InRound:
|
||||||
break;
|
RestartTimer(uid, inactivity);
|
||||||
|
break;
|
||||||
|
case GameRunLevel.PreRoundLobby:
|
||||||
|
case GameRunLevel.PostRound:
|
||||||
|
StopTimer(uid, inactivity);
|
||||||
|
break;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private void PlayerStatusChanged(object? sender, SessionStatusEventArgs e)
|
private void PlayerStatusChanged(object? sender, SessionStatusEventArgs e)
|
||||||
{
|
{
|
||||||
if (GameTicker.RunLevel != GameRunLevel.InRound)
|
var query = EntityQueryEnumerator<InactivityRuleComponent, GameRuleComponent>();
|
||||||
|
while (query.MoveNext(out var uid, out var inactivity, out var gameRule))
|
||||||
{
|
{
|
||||||
return;
|
if (!GameTicker.IsGameRuleActive(uid, gameRule))
|
||||||
}
|
return;
|
||||||
|
|
||||||
if (_playerManager.PlayerCount == 0)
|
if (GameTicker.RunLevel != GameRunLevel.InRound)
|
||||||
{
|
{
|
||||||
RestartTimer();
|
return;
|
||||||
}
|
}
|
||||||
else
|
|
||||||
{
|
if (_playerManager.PlayerCount == 0)
|
||||||
StopTimer();
|
{
|
||||||
|
RestartTimer(uid, inactivity);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
StopTimer(uid, inactivity);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,21 +1,14 @@
|
|||||||
using System.Threading;
|
using System.Threading;
|
||||||
using Content.Server.Chat.Managers;
|
using Content.Server.Chat.Managers;
|
||||||
using Content.Server.GameTicking.Rules.Configurations;
|
using Content.Server.GameTicking.Rules.Components;
|
||||||
using Timer = Robust.Shared.Timing.Timer;
|
using Timer = Robust.Shared.Timing.Timer;
|
||||||
|
|
||||||
namespace Content.Server.GameTicking.Rules;
|
namespace Content.Server.GameTicking.Rules;
|
||||||
|
|
||||||
public sealed class MaxTimeRestartRuleSystem : GameRuleSystem
|
public sealed class MaxTimeRestartRuleSystem : GameRuleSystem<MaxTimeRestartRuleComponent>
|
||||||
{
|
{
|
||||||
[Dependency] private readonly IChatManager _chatManager = default!;
|
[Dependency] private readonly IChatManager _chatManager = default!;
|
||||||
|
|
||||||
public override string Prototype => "MaxTimeRestart";
|
|
||||||
|
|
||||||
private CancellationTokenSource _timerCancel = new();
|
|
||||||
|
|
||||||
public TimeSpan RoundMaxTime { get; set; } = TimeSpan.FromMinutes(5);
|
|
||||||
public TimeSpan RoundEndDelay { get; set; } = TimeSpan.FromSeconds(10);
|
|
||||||
|
|
||||||
public override void Initialize()
|
public override void Initialize()
|
||||||
{
|
{
|
||||||
base.Initialize();
|
base.Initialize();
|
||||||
@@ -23,58 +16,60 @@ public sealed class MaxTimeRestartRuleSystem : GameRuleSystem
|
|||||||
SubscribeLocalEvent<GameRunLevelChangedEvent>(RunLevelChanged);
|
SubscribeLocalEvent<GameRunLevelChangedEvent>(RunLevelChanged);
|
||||||
}
|
}
|
||||||
|
|
||||||
public override void Started()
|
protected override void Started(EntityUid uid, MaxTimeRestartRuleComponent component, GameRuleComponent gameRule, GameRuleStartedEvent args)
|
||||||
{
|
{
|
||||||
if (Configuration is not MaxTimeRestartRuleConfiguration maxTimeRestartConfig)
|
base.Started(uid, component, gameRule, args);
|
||||||
return;
|
|
||||||
|
|
||||||
RoundMaxTime = maxTimeRestartConfig.RoundMaxTime;
|
|
||||||
RoundEndDelay = maxTimeRestartConfig.RoundEndDelay;
|
|
||||||
|
|
||||||
if(GameTicker.RunLevel == GameRunLevel.InRound)
|
if(GameTicker.RunLevel == GameRunLevel.InRound)
|
||||||
RestartTimer();
|
RestartTimer(component);
|
||||||
}
|
}
|
||||||
|
|
||||||
public override void Ended()
|
protected override void Ended(EntityUid uid, MaxTimeRestartRuleComponent component, GameRuleComponent gameRule, GameRuleEndedEvent args)
|
||||||
{
|
{
|
||||||
StopTimer();
|
base.Ended(uid, component, gameRule, args);
|
||||||
|
|
||||||
|
StopTimer(component);
|
||||||
}
|
}
|
||||||
|
|
||||||
public void RestartTimer()
|
public void RestartTimer(MaxTimeRestartRuleComponent component)
|
||||||
{
|
{
|
||||||
_timerCancel.Cancel();
|
component.TimerCancel.Cancel();
|
||||||
_timerCancel = new CancellationTokenSource();
|
component.TimerCancel = new CancellationTokenSource();
|
||||||
Timer.Spawn(RoundMaxTime, TimerFired, _timerCancel.Token);
|
Timer.Spawn(component.RoundMaxTime, () => TimerFired(component), component.TimerCancel.Token);
|
||||||
}
|
}
|
||||||
|
|
||||||
public void StopTimer()
|
public void StopTimer(MaxTimeRestartRuleComponent component)
|
||||||
{
|
{
|
||||||
_timerCancel.Cancel();
|
component.TimerCancel.Cancel();
|
||||||
}
|
}
|
||||||
|
|
||||||
private void TimerFired()
|
private void TimerFired(MaxTimeRestartRuleComponent component)
|
||||||
{
|
{
|
||||||
GameTicker.EndRound(Loc.GetString("rule-time-has-run-out"));
|
GameTicker.EndRound(Loc.GetString("rule-time-has-run-out"));
|
||||||
|
|
||||||
_chatManager.DispatchServerAnnouncement(Loc.GetString("rule-restarting-in-seconds",("seconds", (int) RoundEndDelay.TotalSeconds)));
|
_chatManager.DispatchServerAnnouncement(Loc.GetString("rule-restarting-in-seconds",("seconds", (int) component.RoundEndDelay.TotalSeconds)));
|
||||||
|
|
||||||
Timer.Spawn(RoundEndDelay, () => GameTicker.RestartRound());
|
Timer.Spawn(component.RoundEndDelay, () => GameTicker.RestartRound());
|
||||||
}
|
}
|
||||||
|
|
||||||
private void RunLevelChanged(GameRunLevelChangedEvent args)
|
private void RunLevelChanged(GameRunLevelChangedEvent args)
|
||||||
{
|
{
|
||||||
if (!RuleAdded)
|
var query = EntityQueryEnumerator<MaxTimeRestartRuleComponent, GameRuleComponent>();
|
||||||
return;
|
while (query.MoveNext(out var uid, out var timer, out var gameRule))
|
||||||
|
|
||||||
switch (args.New)
|
|
||||||
{
|
{
|
||||||
case GameRunLevel.InRound:
|
if (!GameTicker.IsGameRuleActive(uid, gameRule))
|
||||||
RestartTimer();
|
return;
|
||||||
break;
|
|
||||||
case GameRunLevel.PreRoundLobby:
|
switch (args.New)
|
||||||
case GameRunLevel.PostRound:
|
{
|
||||||
StopTimer();
|
case GameRunLevel.InRound:
|
||||||
break;
|
RestartTimer(timer);
|
||||||
|
break;
|
||||||
|
case GameRunLevel.PreRoundLobby:
|
||||||
|
case GameRunLevel.PostRound:
|
||||||
|
StopTimer(timer);
|
||||||
|
break;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
File diff suppressed because it is too large
Load Diff
@@ -2,6 +2,7 @@ using System.Linq;
|
|||||||
using Content.Server.Administration.Commands;
|
using Content.Server.Administration.Commands;
|
||||||
using Content.Server.Cargo.Systems;
|
using Content.Server.Cargo.Systems;
|
||||||
using Content.Server.Chat.Managers;
|
using Content.Server.Chat.Managers;
|
||||||
|
using Content.Server.GameTicking.Rules.Components;
|
||||||
using Content.Server.Preferences.Managers;
|
using Content.Server.Preferences.Managers;
|
||||||
using Content.Server.Spawners.Components;
|
using Content.Server.Spawners.Components;
|
||||||
using Content.Server.Station.Components;
|
using Content.Server.Station.Components;
|
||||||
@@ -25,7 +26,7 @@ namespace Content.Server.GameTicking.Rules;
|
|||||||
/// <summary>
|
/// <summary>
|
||||||
/// This handles the Pirates minor antag, which is designed to coincide with other modes on occasion.
|
/// This handles the Pirates minor antag, which is designed to coincide with other modes on occasion.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public sealed class PiratesRuleSystem : GameRuleSystem
|
public sealed class PiratesRuleSystem : GameRuleSystem<PiratesRuleComponent>
|
||||||
{
|
{
|
||||||
[Dependency] private readonly IPrototypeManager _prototypeManager = default!;
|
[Dependency] private readonly IPrototypeManager _prototypeManager = default!;
|
||||||
[Dependency] private readonly IRobustRandom _random = default!;
|
[Dependency] private readonly IRobustRandom _random = default!;
|
||||||
@@ -39,17 +40,6 @@ public sealed class PiratesRuleSystem : GameRuleSystem
|
|||||||
[Dependency] private readonly MapLoaderSystem _map = default!;
|
[Dependency] private readonly MapLoaderSystem _map = default!;
|
||||||
[Dependency] private readonly NamingSystem _namingSystem = default!;
|
[Dependency] private readonly NamingSystem _namingSystem = default!;
|
||||||
|
|
||||||
[ViewVariables]
|
|
||||||
private List<Mind.Mind> _pirates = new();
|
|
||||||
[ViewVariables]
|
|
||||||
private EntityUid _pirateShip = EntityUid.Invalid;
|
|
||||||
[ViewVariables]
|
|
||||||
private HashSet<EntityUid> _initialItems = new();
|
|
||||||
[ViewVariables]
|
|
||||||
private double _initialShipValue;
|
|
||||||
|
|
||||||
public override string Prototype => "Pirates";
|
|
||||||
|
|
||||||
/// <inheritdoc/>
|
/// <inheritdoc/>
|
||||||
public override void Initialize()
|
public override void Initialize()
|
||||||
{
|
{
|
||||||
@@ -57,178 +47,186 @@ public sealed class PiratesRuleSystem : GameRuleSystem
|
|||||||
|
|
||||||
SubscribeLocalEvent<RulePlayerSpawningEvent>(OnPlayerSpawningEvent);
|
SubscribeLocalEvent<RulePlayerSpawningEvent>(OnPlayerSpawningEvent);
|
||||||
SubscribeLocalEvent<RoundEndTextAppendEvent>(OnRoundEndTextEvent);
|
SubscribeLocalEvent<RoundEndTextAppendEvent>(OnRoundEndTextEvent);
|
||||||
|
SubscribeLocalEvent<RoundStartAttemptEvent>(OnStartAttempt);
|
||||||
}
|
}
|
||||||
|
|
||||||
private void OnRoundEndTextEvent(RoundEndTextAppendEvent ev)
|
private void OnRoundEndTextEvent(RoundEndTextAppendEvent ev)
|
||||||
{
|
{
|
||||||
if (!RuleAdded)
|
var query = EntityQueryEnumerator<PiratesRuleComponent, GameRuleComponent>();
|
||||||
return;
|
while (query.MoveNext(out var uid, out var pirates, out var gameRule))
|
||||||
|
|
||||||
if (Deleted(_pirateShip))
|
|
||||||
{
|
{
|
||||||
// Major loss, the ship somehow got annihilated.
|
if (Deleted(pirates.PirateShip))
|
||||||
ev.AddLine(Loc.GetString("pirates-no-ship"));
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
|
|
||||||
List<(double, EntityUid)> mostValuableThefts = new();
|
|
||||||
|
|
||||||
var finalValue = _pricingSystem.AppraiseGrid(_pirateShip, uid =>
|
|
||||||
{
|
{
|
||||||
foreach (var mind in _pirates)
|
// Major loss, the ship somehow got annihilated.
|
||||||
{
|
ev.AddLine(Loc.GetString("pirates-no-ship"));
|
||||||
if (mind.CurrentEntity == uid)
|
|
||||||
return false; // Don't appraise the pirates twice, we count them in separately.
|
|
||||||
}
|
|
||||||
return true;
|
|
||||||
}, (uid, price) =>
|
|
||||||
{
|
|
||||||
if (_initialItems.Contains(uid))
|
|
||||||
return;
|
|
||||||
|
|
||||||
mostValuableThefts.Add((price, uid));
|
|
||||||
mostValuableThefts.Sort((i1, i2) => i2.Item1.CompareTo(i1.Item1));
|
|
||||||
if (mostValuableThefts.Count > 5)
|
|
||||||
mostValuableThefts.Pop();
|
|
||||||
});
|
|
||||||
|
|
||||||
foreach (var mind in _pirates)
|
|
||||||
{
|
|
||||||
if (mind.CurrentEntity is not null)
|
|
||||||
finalValue += _pricingSystem.GetPrice(mind.CurrentEntity.Value);
|
|
||||||
}
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
|
||||||
var score = finalValue - _initialShipValue;
|
List<(double, EntityUid)> mostValuableThefts = new();
|
||||||
|
|
||||||
ev.AddLine(Loc.GetString("pirates-final-score", ("score", $"{score:F2}")));
|
var comp1 = pirates;
|
||||||
ev.AddLine(Loc.GetString("pirates-final-score-2", ("finalPrice", $"{finalValue:F2}")));
|
var finalValue = _pricingSystem.AppraiseGrid(pirates.PirateShip, uid =>
|
||||||
|
{
|
||||||
|
foreach (var mind in comp1.Pirates)
|
||||||
|
{
|
||||||
|
if (mind.CurrentEntity == uid)
|
||||||
|
return false; // Don't appraise the pirates twice, we count them in separately.
|
||||||
|
}
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}, (uid, price) =>
|
||||||
|
{
|
||||||
|
if (comp1.InitialItems.Contains(uid))
|
||||||
|
return;
|
||||||
|
|
||||||
|
mostValuableThefts.Add((price, uid));
|
||||||
|
mostValuableThefts.Sort((i1, i2) => i2.Item1.CompareTo(i1.Item1));
|
||||||
|
if (mostValuableThefts.Count > 5)
|
||||||
|
mostValuableThefts.Pop();
|
||||||
|
});
|
||||||
|
|
||||||
|
foreach (var mind in pirates.Pirates)
|
||||||
|
{
|
||||||
|
if (mind.CurrentEntity is not null)
|
||||||
|
finalValue += _pricingSystem.GetPrice(mind.CurrentEntity.Value);
|
||||||
|
}
|
||||||
|
|
||||||
|
var score = finalValue - pirates.InitialShipValue;
|
||||||
|
|
||||||
|
ev.AddLine(Loc.GetString("pirates-final-score", ("score", $"{score:F2}")));
|
||||||
|
ev.AddLine(Loc.GetString("pirates-final-score-2", ("finalPrice", $"{finalValue:F2}")));
|
||||||
|
|
||||||
|
ev.AddLine("");
|
||||||
|
ev.AddLine(Loc.GetString("pirates-most-valuable"));
|
||||||
|
|
||||||
|
foreach (var (price, obj) in mostValuableThefts)
|
||||||
|
{
|
||||||
|
ev.AddLine(Loc.GetString("pirates-stolen-item-entry", ("entity", obj), ("credits", $"{price:F2}")));
|
||||||
|
}
|
||||||
|
|
||||||
|
if (mostValuableThefts.Count == 0)
|
||||||
|
ev.AddLine(Loc.GetString("pirates-stole-nothing"));
|
||||||
|
}
|
||||||
|
|
||||||
ev.AddLine("");
|
ev.AddLine("");
|
||||||
ev.AddLine(Loc.GetString("pirates-most-valuable"));
|
ev.AddLine(Loc.GetString("pirates-list-start"));
|
||||||
|
foreach (var pirate in pirates.Pirates)
|
||||||
foreach (var (price, obj) in mostValuableThefts)
|
|
||||||
{
|
{
|
||||||
ev.AddLine(Loc.GetString("pirates-stolen-item-entry", ("entity", obj), ("credits", $"{price:F2}")));
|
ev.AddLine($"- {pirate.CharacterName} ({pirate.Session?.Name})");
|
||||||
}
|
}
|
||||||
|
|
||||||
if (mostValuableThefts.Count == 0)
|
|
||||||
ev.AddLine(Loc.GetString("pirates-stole-nothing"));
|
|
||||||
}
|
|
||||||
|
|
||||||
ev.AddLine("");
|
|
||||||
ev.AddLine(Loc.GetString("pirates-list-start"));
|
|
||||||
foreach (var pirates in _pirates)
|
|
||||||
{
|
|
||||||
ev.AddLine($"- {pirates.CharacterName} ({pirates.Session?.Name})");
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public override void Started() { }
|
|
||||||
|
|
||||||
public override void Ended() { }
|
|
||||||
|
|
||||||
private void OnPlayerSpawningEvent(RulePlayerSpawningEvent ev)
|
private void OnPlayerSpawningEvent(RulePlayerSpawningEvent ev)
|
||||||
{
|
{
|
||||||
// Forgive me for copy-pasting nukies.
|
var query = EntityQueryEnumerator<PiratesRuleComponent, GameRuleComponent>();
|
||||||
if (!RuleAdded)
|
while (query.MoveNext(out var uid, out var pirates, out var gameRule))
|
||||||
{
|
{
|
||||||
return;
|
// Forgive me for copy-pasting nukies.
|
||||||
}
|
if (!GameTicker.IsGameRuleAdded(uid, gameRule))
|
||||||
|
return;
|
||||||
|
|
||||||
_pirates.Clear();
|
pirates.Pirates.Clear();
|
||||||
_initialItems.Clear();
|
pirates.InitialItems.Clear();
|
||||||
|
|
||||||
// Between 1 and <max pirate count>: needs at least n players per op.
|
// Between 1 and <max pirate count>: needs at least n players per op.
|
||||||
var numOps = Math.Max(1,
|
var numOps = Math.Max(1,
|
||||||
(int)Math.Min(
|
(int) Math.Min(
|
||||||
Math.Floor((double)ev.PlayerPool.Count / _cfg.GetCVar(CCVars.PiratesPlayersPerOp)), _cfg.GetCVar(CCVars.PiratesMaxOps)));
|
Math.Floor((double) ev.PlayerPool.Count / _cfg.GetCVar(CCVars.PiratesPlayersPerOp)),
|
||||||
var ops = new IPlayerSession[numOps];
|
_cfg.GetCVar(CCVars.PiratesMaxOps)));
|
||||||
for (var i = 0; i < numOps; i++)
|
var ops = new IPlayerSession[numOps];
|
||||||
{
|
for (var i = 0; i < numOps; i++)
|
||||||
ops[i] = _random.PickAndTake(ev.PlayerPool);
|
|
||||||
}
|
|
||||||
|
|
||||||
var map = "/Maps/Shuttles/pirate.yml";
|
|
||||||
var xformQuery = GetEntityQuery<TransformComponent>();
|
|
||||||
|
|
||||||
var aabbs = _stationSystem.Stations.SelectMany(x =>
|
|
||||||
Comp<StationDataComponent>(x).Grids.Select(x => xformQuery.GetComponent(x).WorldMatrix.TransformBox(_mapManager.GetGridComp(x).LocalAABB))).ToArray();
|
|
||||||
|
|
||||||
var aabb = aabbs[0];
|
|
||||||
|
|
||||||
for (var i = 1; i < aabbs.Length; i++)
|
|
||||||
{
|
|
||||||
aabb.Union(aabbs[i]);
|
|
||||||
}
|
|
||||||
|
|
||||||
var gridId = _map.LoadGrid(GameTicker.DefaultMap, map, new MapLoadOptions
|
|
||||||
{
|
|
||||||
Offset = aabb.Center + MathF.Max(aabb.Height / 2f, aabb.Width / 2f) * 2.5f
|
|
||||||
});
|
|
||||||
|
|
||||||
if (!gridId.HasValue)
|
|
||||||
{
|
|
||||||
Logger.ErrorS("pirates", $"Gridid was null when loading \"{map}\", aborting.");
|
|
||||||
foreach (var session in ops)
|
|
||||||
{
|
{
|
||||||
ev.PlayerPool.Add(session);
|
ops[i] = _random.PickAndTake(ev.PlayerPool);
|
||||||
}
|
}
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
_pirateShip = gridId.Value;
|
var map = "/Maps/Shuttles/pirate.yml";
|
||||||
|
var xformQuery = GetEntityQuery<TransformComponent>();
|
||||||
|
|
||||||
// TODO: Loot table or something
|
var aabbs = _stationSystem.Stations.SelectMany(x =>
|
||||||
var pirateGear = _prototypeManager.Index<StartingGearPrototype>("PirateGear"); // YARRR
|
Comp<StationDataComponent>(x).Grids.Select(x =>
|
||||||
|
xformQuery.GetComponent(x).WorldMatrix.TransformBox(_mapManager.GetGridComp(x).LocalAABB)))
|
||||||
|
.ToArray();
|
||||||
|
|
||||||
var spawns = new List<EntityCoordinates>();
|
var aabb = aabbs[0];
|
||||||
|
|
||||||
// Forgive me for hardcoding prototypes
|
for (var i = 1; i < aabbs.Length; i++)
|
||||||
foreach (var (_, meta, xform) in EntityQuery<SpawnPointComponent, MetaDataComponent, TransformComponent>(true))
|
|
||||||
{
|
|
||||||
if (meta.EntityPrototype?.ID != "SpawnPointPirates" || xform.ParentUid != _pirateShip) continue;
|
|
||||||
|
|
||||||
spawns.Add(xform.Coordinates);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (spawns.Count == 0)
|
|
||||||
{
|
|
||||||
spawns.Add(Transform(_pirateShip).Coordinates);
|
|
||||||
Logger.WarningS("pirates", $"Fell back to default spawn for pirates!");
|
|
||||||
}
|
|
||||||
|
|
||||||
for (var i = 0; i < ops.Length; i++)
|
|
||||||
{
|
|
||||||
var sex = _random.Prob(0.5f) ? Sex.Male : Sex.Female;
|
|
||||||
var gender = sex == Sex.Male ? Gender.Male : Gender.Female;
|
|
||||||
|
|
||||||
var name = _namingSystem.GetName("Human", gender);
|
|
||||||
|
|
||||||
var session = ops[i];
|
|
||||||
var newMind = new Mind.Mind(session.UserId)
|
|
||||||
{
|
{
|
||||||
CharacterName = name
|
aabb.Union(aabbs[i]);
|
||||||
};
|
}
|
||||||
newMind.ChangeOwningPlayer(session.UserId);
|
|
||||||
|
|
||||||
var mob = Spawn("MobHuman", _random.Pick(spawns));
|
var gridId = _map.LoadGrid(GameTicker.DefaultMap, map, new MapLoadOptions
|
||||||
MetaData(mob).EntityName = name;
|
{
|
||||||
|
Offset = aabb.Center + MathF.Max(aabb.Height / 2f, aabb.Width / 2f) * 2.5f
|
||||||
|
});
|
||||||
|
|
||||||
newMind.TransferTo(mob);
|
if (!gridId.HasValue)
|
||||||
var profile = _prefs.GetPreferences(session.UserId).SelectedCharacter as HumanoidCharacterProfile;
|
{
|
||||||
_stationSpawningSystem.EquipStartingGear(mob, pirateGear, profile);
|
Logger.ErrorS("pirates", $"Gridid was null when loading \"{map}\", aborting.");
|
||||||
|
foreach (var session in ops)
|
||||||
|
{
|
||||||
|
ev.PlayerPool.Add(session);
|
||||||
|
}
|
||||||
|
|
||||||
_pirates.Add(newMind);
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
GameTicker.PlayerJoinGame(session);
|
pirates.PirateShip = gridId.Value;
|
||||||
|
|
||||||
|
// TODO: Loot table or something
|
||||||
|
var pirateGear = _prototypeManager.Index<StartingGearPrototype>("PirateGear"); // YARRR
|
||||||
|
|
||||||
|
var spawns = new List<EntityCoordinates>();
|
||||||
|
|
||||||
|
// Forgive me for hardcoding prototypes
|
||||||
|
foreach (var (_, meta, xform) in
|
||||||
|
EntityQuery<SpawnPointComponent, MetaDataComponent, TransformComponent>(true))
|
||||||
|
{
|
||||||
|
if (meta.EntityPrototype?.ID != "SpawnPointPirates" || xform.ParentUid != pirates.PirateShip)
|
||||||
|
continue;
|
||||||
|
|
||||||
|
spawns.Add(xform.Coordinates);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (spawns.Count == 0)
|
||||||
|
{
|
||||||
|
spawns.Add(Transform(pirates.PirateShip).Coordinates);
|
||||||
|
Logger.WarningS("pirates", $"Fell back to default spawn for pirates!");
|
||||||
|
}
|
||||||
|
|
||||||
|
for (var i = 0; i < ops.Length; i++)
|
||||||
|
{
|
||||||
|
var sex = _random.Prob(0.5f) ? Sex.Male : Sex.Female;
|
||||||
|
var gender = sex == Sex.Male ? Gender.Male : Gender.Female;
|
||||||
|
|
||||||
|
var name = _namingSystem.GetName("Human", gender);
|
||||||
|
|
||||||
|
var session = ops[i];
|
||||||
|
var newMind = new Mind.Mind(session.UserId)
|
||||||
|
{
|
||||||
|
CharacterName = name
|
||||||
|
};
|
||||||
|
newMind.ChangeOwningPlayer(session.UserId);
|
||||||
|
|
||||||
|
var mob = Spawn("MobHuman", _random.Pick(spawns));
|
||||||
|
MetaData(mob).EntityName = name;
|
||||||
|
|
||||||
|
newMind.TransferTo(mob);
|
||||||
|
var profile = _prefs.GetPreferences(session.UserId).SelectedCharacter as HumanoidCharacterProfile;
|
||||||
|
_stationSpawningSystem.EquipStartingGear(mob, pirateGear, profile);
|
||||||
|
|
||||||
|
pirates.Pirates.Add(newMind);
|
||||||
|
|
||||||
|
GameTicker.PlayerJoinGame(session);
|
||||||
|
}
|
||||||
|
|
||||||
|
pirates.InitialShipValue = _pricingSystem.AppraiseGrid(pirates.PirateShip, uid =>
|
||||||
|
{
|
||||||
|
pirates.InitialItems.Add(uid);
|
||||||
|
return true;
|
||||||
|
}); // Include the players in the appraisal.
|
||||||
}
|
}
|
||||||
|
|
||||||
_initialShipValue = _pricingSystem.AppraiseGrid(_pirateShip, uid =>
|
|
||||||
{
|
|
||||||
_initialItems.Add(uid);
|
|
||||||
return true;
|
|
||||||
}); // Include the players in the appraisal.
|
|
||||||
}
|
}
|
||||||
|
|
||||||
//Forcing one player to be a pirate.
|
//Forcing one player to be a pirate.
|
||||||
@@ -241,21 +239,26 @@ public sealed class PiratesRuleSystem : GameRuleSystem
|
|||||||
|
|
||||||
private void OnStartAttempt(RoundStartAttemptEvent ev)
|
private void OnStartAttempt(RoundStartAttemptEvent ev)
|
||||||
{
|
{
|
||||||
if (!RuleAdded)
|
var query = EntityQueryEnumerator<PiratesRuleComponent, GameRuleComponent>();
|
||||||
return;
|
while (query.MoveNext(out var uid, out var pirates, out var gameRule))
|
||||||
|
|
||||||
var minPlayers = _cfg.GetCVar(CCVars.PiratesMinPlayers);
|
|
||||||
if (!ev.Forced && ev.Players.Length < minPlayers)
|
|
||||||
{
|
{
|
||||||
_chatManager.DispatchServerAnnouncement(Loc.GetString("nukeops-not-enough-ready-players", ("readyPlayersCount", ev.Players.Length), ("minimumPlayers", minPlayers)));
|
if (!GameTicker.IsGameRuleActive(uid, gameRule))
|
||||||
ev.Cancel();
|
return;
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (ev.Players.Length == 0)
|
var minPlayers = _cfg.GetCVar(CCVars.PiratesMinPlayers);
|
||||||
{
|
if (!ev.Forced && ev.Players.Length < minPlayers)
|
||||||
_chatManager.DispatchServerAnnouncement(Loc.GetString("nukeops-no-one-ready"));
|
{
|
||||||
ev.Cancel();
|
_chatManager.DispatchServerAnnouncement(Loc.GetString("nukeops-not-enough-ready-players",
|
||||||
|
("readyPlayersCount", ev.Players.Length), ("minimumPlayers", minPlayers)));
|
||||||
|
ev.Cancel();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (ev.Players.Length == 0)
|
||||||
|
{
|
||||||
|
_chatManager.DispatchServerAnnouncement(Loc.GetString("nukeops-no-one-ready"));
|
||||||
|
ev.Cancel();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,21 +1,21 @@
|
|||||||
using Content.Server.GameTicking.Rules.Configurations;
|
using Content.Server.GameTicking.Rules.Components;
|
||||||
using Content.Server.Sandbox;
|
using Content.Server.Sandbox;
|
||||||
|
|
||||||
namespace Content.Server.GameTicking.Rules;
|
namespace Content.Server.GameTicking.Rules;
|
||||||
|
|
||||||
public sealed class SandboxRuleSystem : GameRuleSystem
|
public sealed class SandboxRuleSystem : GameRuleSystem<SandboxRuleComponent>
|
||||||
{
|
{
|
||||||
[Dependency] private readonly SandboxSystem _sandbox = default!;
|
[Dependency] private readonly SandboxSystem _sandbox = default!;
|
||||||
|
|
||||||
public override string Prototype => "Sandbox";
|
protected override void Started(EntityUid uid, SandboxRuleComponent component, GameRuleComponent gameRule, GameRuleStartedEvent args)
|
||||||
|
|
||||||
public override void Started()
|
|
||||||
{
|
{
|
||||||
|
base.Started(uid, component, gameRule, args);
|
||||||
_sandbox.IsSandboxEnabled = true;
|
_sandbox.IsSandboxEnabled = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
public override void Ended()
|
protected override void Ended(EntityUid uid, SandboxRuleComponent component, GameRuleComponent gameRule, GameRuleEndedEvent args)
|
||||||
{
|
{
|
||||||
|
base.Ended(uid, component, gameRule, args);
|
||||||
_sandbox.IsSandboxEnabled = false;
|
_sandbox.IsSandboxEnabled = false;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,6 +1,5 @@
|
|||||||
using System.Linq;
|
|
||||||
using Content.Server.GameTicking.Presets;
|
using Content.Server.GameTicking.Presets;
|
||||||
using Content.Server.GameTicking.Rules.Configurations;
|
using Content.Server.GameTicking.Rules.Components;
|
||||||
using Content.Shared.Random;
|
using Content.Shared.Random;
|
||||||
using Content.Shared.Random.Helpers;
|
using Content.Shared.Random.Helpers;
|
||||||
using Robust.Shared.Prototypes;
|
using Robust.Shared.Prototypes;
|
||||||
@@ -8,34 +7,40 @@ using Robust.Shared.Random;
|
|||||||
|
|
||||||
namespace Content.Server.GameTicking.Rules;
|
namespace Content.Server.GameTicking.Rules;
|
||||||
|
|
||||||
public sealed class SecretRuleSystem : GameRuleSystem
|
public sealed class SecretRuleSystem : GameRuleSystem<SecretRuleComponent>
|
||||||
{
|
{
|
||||||
[Dependency] private readonly IPrototypeManager _prototypeManager = default!;
|
[Dependency] private readonly IPrototypeManager _prototypeManager = default!;
|
||||||
[Dependency] private readonly IRobustRandom _random = default!;
|
[Dependency] private readonly IRobustRandom _random = default!;
|
||||||
[Dependency] private readonly GameTicker _ticker = default!;
|
|
||||||
|
|
||||||
public override string Prototype => "Secret";
|
protected override void Started(EntityUid uid, SecretRuleComponent component, GameRuleComponent gameRule, GameRuleStartedEvent args)
|
||||||
|
|
||||||
public override void Started()
|
|
||||||
{
|
{
|
||||||
PickRule();
|
base.Started(uid, component, gameRule, args);
|
||||||
|
PickRule(component);
|
||||||
}
|
}
|
||||||
|
|
||||||
public override void Ended()
|
protected override void Ended(EntityUid uid, SecretRuleComponent component, GameRuleComponent gameRule, GameRuleEndedEvent args)
|
||||||
{
|
{
|
||||||
// Preset should already handle it.
|
base.Ended(uid, component, gameRule, args);
|
||||||
|
|
||||||
|
foreach (var rule in component.AdditionalGameRules)
|
||||||
|
{
|
||||||
|
GameTicker.EndGameRule(rule);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private void PickRule()
|
private void PickRule(SecretRuleComponent component)
|
||||||
{
|
{
|
||||||
// TODO: This doesn't consider what can't start due to minimum player count, but currently there's no way to know anyway.
|
// TODO: This doesn't consider what can't start due to minimum player count, but currently there's no way to know anyway.
|
||||||
// as they use cvars.
|
// as they use cvars.
|
||||||
var preset = _prototypeManager.Index<WeightedRandomPrototype>("Secret").Pick(_random);
|
var preset = _prototypeManager.Index<WeightedRandomPrototype>("Secret").Pick(_random);
|
||||||
Logger.InfoS("gamepreset", $"Selected {preset} for secret.");
|
Logger.InfoS("gamepreset", $"Selected {preset} for secret.");
|
||||||
|
|
||||||
foreach (var rule in _prototypeManager.Index<GamePresetPrototype>(preset).Rules)
|
var rules = _prototypeManager.Index<GamePresetPrototype>(preset).Rules;
|
||||||
|
foreach (var rule in rules)
|
||||||
{
|
{
|
||||||
_ticker.StartGameRule(_prototypeManager.Index<GameRulePrototype>(rule));
|
Logger.Debug($"what the fuck, {rule}");
|
||||||
|
GameTicker.StartGameRule(rule, out var ruleEnt);
|
||||||
|
component.AdditionalGameRules.Add(ruleEnt);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,456 +0,0 @@
|
|||||||
using System.Linq;
|
|
||||||
using System.Threading;
|
|
||||||
using Content.Server.Chat.Managers;
|
|
||||||
using Content.Server.GameTicking.Rules.Configurations;
|
|
||||||
using Content.Server.Players;
|
|
||||||
using Content.Server.Roles;
|
|
||||||
using Content.Server.Station.Components;
|
|
||||||
using Content.Server.Suspicion;
|
|
||||||
using Content.Server.Suspicion.Roles;
|
|
||||||
using Content.Server.Traitor.Uplink;
|
|
||||||
using Content.Shared.CCVar;
|
|
||||||
using Content.Shared.Doors.Systems;
|
|
||||||
using Content.Shared.EntityList;
|
|
||||||
using Content.Shared.GameTicking;
|
|
||||||
using Content.Shared.Maps;
|
|
||||||
using Content.Shared.Mobs.Components;
|
|
||||||
using Content.Shared.Mobs.Systems;
|
|
||||||
using Content.Shared.Roles;
|
|
||||||
using Content.Shared.Suspicion;
|
|
||||||
using Robust.Server.GameObjects;
|
|
||||||
using Robust.Server.Player;
|
|
||||||
using Robust.Shared.Audio;
|
|
||||||
using Robust.Shared.Configuration;
|
|
||||||
using Robust.Shared.Enums;
|
|
||||||
using Robust.Shared.Map;
|
|
||||||
using Robust.Shared.Map.Components;
|
|
||||||
using Robust.Shared.Player;
|
|
||||||
using Robust.Shared.Prototypes;
|
|
||||||
using Robust.Shared.Random;
|
|
||||||
using Robust.Shared.Timing;
|
|
||||||
using Robust.Shared.Utility;
|
|
||||||
using Timer = Robust.Shared.Timing.Timer;
|
|
||||||
|
|
||||||
namespace Content.Server.GameTicking.Rules;
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Simple GameRule that will do a TTT-like gamemode with traitors.
|
|
||||||
/// </summary>
|
|
||||||
public sealed class SuspicionRuleSystem : GameRuleSystem
|
|
||||||
{
|
|
||||||
[Dependency] private readonly IMapManager _mapManager = default!;
|
|
||||||
[Dependency] private readonly IPlayerManager _playerManager = default!;
|
|
||||||
[Dependency] private readonly IChatManager _chatManager = default!;
|
|
||||||
[Dependency] private readonly IConfigurationManager _cfg = default!;
|
|
||||||
[Dependency] private readonly IGameTiming _timing = default!;
|
|
||||||
[Dependency] private readonly IRobustRandom _random = default!;
|
|
||||||
[Dependency] private readonly IPrototypeManager _prototypeManager = default!;
|
|
||||||
[Dependency] private readonly MobStateSystem _mobStateSystem = default!;
|
|
||||||
[Dependency] private readonly ITileDefinitionManager _tileDefMan = default!;
|
|
||||||
[Dependency] private readonly SharedDoorSystem _doorSystem = default!;
|
|
||||||
[Dependency] private readonly EntityLookupSystem _lookupSystem = default!;
|
|
||||||
[Dependency] private readonly UplinkSystem _uplink = default!;
|
|
||||||
|
|
||||||
public override string Prototype => "Suspicion";
|
|
||||||
|
|
||||||
private static readonly TimeSpan DeadCheckDelay = TimeSpan.FromSeconds(1);
|
|
||||||
|
|
||||||
private readonly HashSet<SuspicionRoleComponent> _traitors = new();
|
|
||||||
|
|
||||||
public IReadOnlyCollection<SuspicionRoleComponent> Traitors => _traitors;
|
|
||||||
|
|
||||||
[DataField("addedSound")] private SoundSpecifier _addedSound = new SoundPathSpecifier("/Audio/Misc/tatoralert.ogg");
|
|
||||||
|
|
||||||
private CancellationTokenSource _checkTimerCancel = new();
|
|
||||||
private TimeSpan? _endTime;
|
|
||||||
|
|
||||||
public TimeSpan? EndTime
|
|
||||||
{
|
|
||||||
get => _endTime;
|
|
||||||
set
|
|
||||||
{
|
|
||||||
_endTime = value;
|
|
||||||
SendUpdateToAll();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public TimeSpan RoundMaxTime { get; set; } = TimeSpan.FromSeconds(CCVars.SuspicionMaxTimeSeconds.DefaultValue);
|
|
||||||
public TimeSpan RoundEndDelay { get; set; } = TimeSpan.FromSeconds(10);
|
|
||||||
|
|
||||||
private const string TraitorID = "SuspicionTraitor";
|
|
||||||
private const string InnocentID = "SuspicionInnocent";
|
|
||||||
private const string SuspicionLootTable = "SuspicionRule";
|
|
||||||
|
|
||||||
public override void Initialize()
|
|
||||||
{
|
|
||||||
base.Initialize();
|
|
||||||
|
|
||||||
SubscribeLocalEvent<RulePlayerJobsAssignedEvent>(OnPlayersAssigned);
|
|
||||||
SubscribeLocalEvent<RoundStartAttemptEvent>(OnRoundStartAttempt);
|
|
||||||
SubscribeLocalEvent<RefreshLateJoinAllowedEvent>(OnLateJoinRefresh);
|
|
||||||
SubscribeLocalEvent<RoundRestartCleanupEvent>(Reset);
|
|
||||||
|
|
||||||
SubscribeLocalEvent<SuspicionRoleComponent, PlayerAttachedEvent>(OnPlayerAttached);
|
|
||||||
SubscribeLocalEvent<SuspicionRoleComponent, PlayerDetachedEvent>(OnPlayerDetached);
|
|
||||||
SubscribeLocalEvent<SuspicionRoleComponent, RoleAddedEvent>(OnRoleAdded);
|
|
||||||
SubscribeLocalEvent<SuspicionRoleComponent, RoleRemovedEvent>(OnRoleRemoved);
|
|
||||||
}
|
|
||||||
|
|
||||||
private void OnRoundStartAttempt(RoundStartAttemptEvent ev)
|
|
||||||
{
|
|
||||||
if (!RuleAdded)
|
|
||||||
return;
|
|
||||||
|
|
||||||
var minPlayers = _cfg.GetCVar(CCVars.SuspicionMinPlayers);
|
|
||||||
|
|
||||||
if (!ev.Forced && ev.Players.Length < minPlayers)
|
|
||||||
{
|
|
||||||
_chatManager.DispatchServerAnnouncement($"Not enough players readied up for the game! There were {ev.Players.Length} players readied up out of {minPlayers} needed.");
|
|
||||||
ev.Cancel();
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (ev.Players.Length == 0)
|
|
||||||
{
|
|
||||||
_chatManager.DispatchServerAnnouncement("No players readied up! Can't start Suspicion.");
|
|
||||||
ev.Cancel();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private void OnPlayersAssigned(RulePlayerJobsAssignedEvent ev)
|
|
||||||
{
|
|
||||||
if (!RuleAdded)
|
|
||||||
return;
|
|
||||||
|
|
||||||
var minTraitors = _cfg.GetCVar(CCVars.SuspicionMinTraitors);
|
|
||||||
var playersPerTraitor = _cfg.GetCVar(CCVars.SuspicionPlayersPerTraitor);
|
|
||||||
var traitorStartingBalance = _cfg.GetCVar(CCVars.SuspicionStartingBalance);
|
|
||||||
|
|
||||||
var list = new List<IPlayerSession>(ev.Players);
|
|
||||||
var prefList = new List<IPlayerSession>();
|
|
||||||
|
|
||||||
foreach (var player in list)
|
|
||||||
{
|
|
||||||
if (!ev.Profiles.ContainsKey(player.UserId) || player.AttachedEntity is not {} attached)
|
|
||||||
{
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
prefList.Add(player);
|
|
||||||
|
|
||||||
attached.EnsureComponent<SuspicionRoleComponent>();
|
|
||||||
}
|
|
||||||
|
|
||||||
// Max is players-1 so there's always at least one innocent.
|
|
||||||
var numTraitors = MathHelper.Clamp(ev.Players.Length / playersPerTraitor,
|
|
||||||
minTraitors, ev.Players.Length-1);
|
|
||||||
|
|
||||||
var traitors = new List<SuspicionTraitorRole>();
|
|
||||||
|
|
||||||
for (var i = 0; i < numTraitors; i++)
|
|
||||||
{
|
|
||||||
IPlayerSession traitor;
|
|
||||||
if(prefList.Count == 0)
|
|
||||||
{
|
|
||||||
if (list.Count == 0)
|
|
||||||
{
|
|
||||||
Logger.InfoS("preset", "Insufficient ready players to fill up with traitors, stopping the selection.");
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
traitor = _random.PickAndTake(list);
|
|
||||||
Logger.InfoS("preset", "Insufficient preferred traitors, picking at random.");
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
traitor = _random.PickAndTake(prefList);
|
|
||||||
list.Remove(traitor);
|
|
||||||
Logger.InfoS("preset", "Selected a preferred traitor.");
|
|
||||||
}
|
|
||||||
var mind = traitor.Data.ContentData()?.Mind;
|
|
||||||
var antagPrototype = _prototypeManager.Index<AntagPrototype>(TraitorID);
|
|
||||||
|
|
||||||
DebugTools.AssertNotNull(mind?.OwnedEntity);
|
|
||||||
|
|
||||||
var traitorRole = new SuspicionTraitorRole(mind!, antagPrototype);
|
|
||||||
mind!.AddRole(traitorRole);
|
|
||||||
traitors.Add(traitorRole);
|
|
||||||
|
|
||||||
// try to place uplink
|
|
||||||
_uplink.AddUplink(mind.OwnedEntity!.Value, traitorStartingBalance);
|
|
||||||
}
|
|
||||||
|
|
||||||
foreach (var player in list)
|
|
||||||
{
|
|
||||||
var mind = player.Data.ContentData()?.Mind;
|
|
||||||
var antagPrototype = _prototypeManager.Index<AntagPrototype>(InnocentID);
|
|
||||||
|
|
||||||
DebugTools.AssertNotNull(mind);
|
|
||||||
|
|
||||||
mind!.AddRole(new SuspicionInnocentRole(mind, antagPrototype));
|
|
||||||
}
|
|
||||||
|
|
||||||
foreach (var traitor in traitors)
|
|
||||||
{
|
|
||||||
traitor.GreetSuspicion(traitors, _chatManager);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public override void Started()
|
|
||||||
{
|
|
||||||
_playerManager.PlayerStatusChanged += PlayerManagerOnPlayerStatusChanged;
|
|
||||||
|
|
||||||
RoundMaxTime = TimeSpan.FromSeconds(_cfg.GetCVar(CCVars.SuspicionMaxTimeSeconds));
|
|
||||||
|
|
||||||
EndTime = _timing.CurTime + RoundMaxTime;
|
|
||||||
|
|
||||||
_chatManager.DispatchServerAnnouncement(Loc.GetString("rule-suspicion-added-announcement"));
|
|
||||||
|
|
||||||
var filter = Filter.Empty()
|
|
||||||
.AddWhere(session => ((IPlayerSession) session).ContentData()?.Mind?.HasRole<SuspicionTraitorRole>() ?? false);
|
|
||||||
|
|
||||||
SoundSystem.Play(_addedSound.GetSound(), filter, AudioParams.Default);
|
|
||||||
|
|
||||||
_doorSystem.AccessType = SharedDoorSystem.AccessTypes.AllowAllNoExternal;
|
|
||||||
|
|
||||||
var susLoot = _prototypeManager.Index<EntityLootTablePrototype>(SuspicionLootTable);
|
|
||||||
|
|
||||||
foreach (var (_, mapGrid) in EntityManager.EntityQuery<StationMemberComponent, MapGridComponent>(true))
|
|
||||||
{
|
|
||||||
// I'm so sorry.
|
|
||||||
var tiles = mapGrid.GetAllTiles().ToArray();
|
|
||||||
Logger.Info($"TILES: {tiles.Length}");
|
|
||||||
|
|
||||||
var spawn = susLoot.GetSpawns();
|
|
||||||
var count = spawn.Count;
|
|
||||||
|
|
||||||
// Try to scale spawned amount by station size...
|
|
||||||
if (tiles.Length < 1000)
|
|
||||||
{
|
|
||||||
count = Math.Min(count, tiles.Length / 10);
|
|
||||||
|
|
||||||
// Shuffle so we pick items at random.
|
|
||||||
_random.Shuffle(spawn);
|
|
||||||
}
|
|
||||||
|
|
||||||
for (var i = 0; i < count; i++)
|
|
||||||
{
|
|
||||||
var item = spawn[i];
|
|
||||||
|
|
||||||
// Maximum number of attempts for trying to find a suitable empty tile.
|
|
||||||
// We do this because we don't want to hang the server when a devious map has literally no free tiles.
|
|
||||||
const int maxTries = 100;
|
|
||||||
|
|
||||||
for (var j = 0; j < maxTries; j++)
|
|
||||||
{
|
|
||||||
var tile = _random.Pick(tiles);
|
|
||||||
|
|
||||||
// Let's not spawn things on top of walls.
|
|
||||||
if (tile.IsBlockedTurf(false, _lookupSystem) || tile.IsSpace(_tileDefMan))
|
|
||||||
continue;
|
|
||||||
|
|
||||||
var uid = Spawn(item, tile.GridPosition(_mapManager));
|
|
||||||
|
|
||||||
// Keep track of all suspicion-spawned weapons so we can clean them up once the rule ends.
|
|
||||||
EnsureComp<SuspicionItemComponent>(uid);
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
_checkTimerCancel = new CancellationTokenSource();
|
|
||||||
Timer.SpawnRepeating(DeadCheckDelay, CheckWinConditions, _checkTimerCancel.Token);
|
|
||||||
}
|
|
||||||
|
|
||||||
public override void Ended()
|
|
||||||
{
|
|
||||||
_doorSystem.AccessType = SharedDoorSystem.AccessTypes.Id;
|
|
||||||
EndTime = null;
|
|
||||||
_traitors.Clear();
|
|
||||||
|
|
||||||
_playerManager.PlayerStatusChanged -= PlayerManagerOnPlayerStatusChanged;
|
|
||||||
|
|
||||||
// Clean up all items we spawned before...
|
|
||||||
foreach (var item in EntityManager.EntityQuery<SuspicionItemComponent>(true))
|
|
||||||
{
|
|
||||||
Del(item.Owner);
|
|
||||||
}
|
|
||||||
|
|
||||||
_checkTimerCancel.Cancel();
|
|
||||||
}
|
|
||||||
|
|
||||||
private void CheckWinConditions()
|
|
||||||
{
|
|
||||||
if (!RuleAdded || !_cfg.GetCVar(CCVars.GameLobbyEnableWin))
|
|
||||||
return;
|
|
||||||
|
|
||||||
var traitorsAlive = 0;
|
|
||||||
var innocentsAlive = 0;
|
|
||||||
|
|
||||||
foreach (var playerSession in _playerManager.ServerSessions)
|
|
||||||
{
|
|
||||||
if (playerSession.AttachedEntity is not {Valid: true} playerEntity
|
|
||||||
|| !TryComp(playerEntity, out MobStateComponent? mobState)
|
|
||||||
|| !HasComp<SuspicionRoleComponent>(playerEntity))
|
|
||||||
{
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!_mobStateSystem.IsAlive(playerEntity, mobState))
|
|
||||||
{
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
var mind = playerSession.ContentData()?.Mind;
|
|
||||||
|
|
||||||
if (mind != null && mind.HasRole<SuspicionTraitorRole>())
|
|
||||||
traitorsAlive++;
|
|
||||||
else
|
|
||||||
innocentsAlive++;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (innocentsAlive + traitorsAlive == 0)
|
|
||||||
{
|
|
||||||
_chatManager.DispatchServerAnnouncement(Loc.GetString("rule-suspicion-check-winner-stalemate"));
|
|
||||||
EndRound(Victory.Stalemate);
|
|
||||||
}
|
|
||||||
|
|
||||||
else if (traitorsAlive == 0)
|
|
||||||
{
|
|
||||||
_chatManager.DispatchServerAnnouncement(Loc.GetString("rule-suspicion-check-winner-station-win"));
|
|
||||||
EndRound(Victory.Innocents);
|
|
||||||
}
|
|
||||||
else if (innocentsAlive == 0)
|
|
||||||
{
|
|
||||||
_chatManager.DispatchServerAnnouncement(Loc.GetString("rule-suspicion-check-winner-traitor-win"));
|
|
||||||
EndRound(Victory.Traitors);
|
|
||||||
}
|
|
||||||
else if (_timing.CurTime > _endTime)
|
|
||||||
{
|
|
||||||
_chatManager.DispatchServerAnnouncement(Loc.GetString("rule-suspicion-traitor-time-has-run-out"));
|
|
||||||
EndRound(Victory.Innocents);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private enum Victory
|
|
||||||
{
|
|
||||||
Stalemate,
|
|
||||||
Innocents,
|
|
||||||
Traitors
|
|
||||||
}
|
|
||||||
|
|
||||||
private void EndRound(Victory victory)
|
|
||||||
{
|
|
||||||
string text;
|
|
||||||
|
|
||||||
switch (victory)
|
|
||||||
{
|
|
||||||
case Victory.Innocents:
|
|
||||||
text = Loc.GetString("rule-suspicion-end-round-innocents-victory");
|
|
||||||
break;
|
|
||||||
case Victory.Traitors:
|
|
||||||
text = Loc.GetString("rule-suspicion-end-round-traitors-victory");
|
|
||||||
break;
|
|
||||||
default:
|
|
||||||
text = Loc.GetString("rule-suspicion-end-round-nobody-victory");
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
|
|
||||||
GameTicker.EndRound(text);
|
|
||||||
|
|
||||||
_chatManager.DispatchServerAnnouncement(Loc.GetString("rule-restarting-in-seconds", ("seconds", (int) RoundEndDelay.TotalSeconds)));
|
|
||||||
_checkTimerCancel.Cancel();
|
|
||||||
|
|
||||||
Timer.Spawn(RoundEndDelay, () => GameTicker.RestartRound());
|
|
||||||
}
|
|
||||||
|
|
||||||
private void PlayerManagerOnPlayerStatusChanged(object? sender, SessionStatusEventArgs e)
|
|
||||||
{
|
|
||||||
if (e.NewStatus == SessionStatus.InGame)
|
|
||||||
{
|
|
||||||
SendUpdateTimerMessage(e.Session);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private void SendUpdateToAll()
|
|
||||||
{
|
|
||||||
foreach (var player in _playerManager.ServerSessions.Where(p => p.Status == SessionStatus.InGame))
|
|
||||||
{
|
|
||||||
SendUpdateTimerMessage(player);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private void SendUpdateTimerMessage(IPlayerSession player)
|
|
||||||
{
|
|
||||||
var msg = new SuspicionMessages.SetSuspicionEndTimerMessage
|
|
||||||
{
|
|
||||||
EndTime = EndTime
|
|
||||||
};
|
|
||||||
|
|
||||||
EntityManager.EntityNetManager?.SendSystemNetworkMessage(msg, player.ConnectedClient);
|
|
||||||
}
|
|
||||||
|
|
||||||
public void AddTraitor(SuspicionRoleComponent role)
|
|
||||||
{
|
|
||||||
if (!_traitors.Add(role))
|
|
||||||
{
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
foreach (var traitor in _traitors)
|
|
||||||
{
|
|
||||||
traitor.AddAlly(role);
|
|
||||||
}
|
|
||||||
|
|
||||||
role.SetAllies(_traitors);
|
|
||||||
}
|
|
||||||
|
|
||||||
public void RemoveTraitor(SuspicionRoleComponent role)
|
|
||||||
{
|
|
||||||
if (!_traitors.Remove(role))
|
|
||||||
{
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
foreach (var traitor in _traitors)
|
|
||||||
{
|
|
||||||
traitor.RemoveAlly(role);
|
|
||||||
}
|
|
||||||
|
|
||||||
role.ClearAllies();
|
|
||||||
}
|
|
||||||
|
|
||||||
private void Reset(RoundRestartCleanupEvent ev)
|
|
||||||
{
|
|
||||||
EndTime = null;
|
|
||||||
_traitors.Clear();
|
|
||||||
}
|
|
||||||
|
|
||||||
private void OnPlayerDetached(EntityUid uid, SuspicionRoleComponent component, PlayerDetachedEvent args)
|
|
||||||
{
|
|
||||||
component.SyncRoles();
|
|
||||||
}
|
|
||||||
|
|
||||||
private void OnPlayerAttached(EntityUid uid, SuspicionRoleComponent component, PlayerAttachedEvent args)
|
|
||||||
{
|
|
||||||
component.SyncRoles();
|
|
||||||
}
|
|
||||||
|
|
||||||
private void OnRoleAdded(EntityUid uid, SuspicionRoleComponent component, RoleAddedEvent args)
|
|
||||||
{
|
|
||||||
if (args.Role is not SuspicionRole role) return;
|
|
||||||
component.Role = role;
|
|
||||||
}
|
|
||||||
|
|
||||||
private void OnRoleRemoved(EntityUid uid, SuspicionRoleComponent component, RoleRemovedEvent args)
|
|
||||||
{
|
|
||||||
if (args.Role is not SuspicionRole) return;
|
|
||||||
component.Role = null;
|
|
||||||
}
|
|
||||||
|
|
||||||
private void OnLateJoinRefresh(RefreshLateJoinAllowedEvent ev)
|
|
||||||
{
|
|
||||||
if (!RuleAdded)
|
|
||||||
return;
|
|
||||||
|
|
||||||
ev.Disallow();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,276 +0,0 @@
|
|||||||
using System.Linq;
|
|
||||||
using Content.Server.Atmos.EntitySystems;
|
|
||||||
using Content.Server.Chat.Managers;
|
|
||||||
using Content.Server.PDA;
|
|
||||||
using Content.Server.Players;
|
|
||||||
using Content.Server.Spawners.Components;
|
|
||||||
using Content.Server.Store.Components;
|
|
||||||
using Content.Server.Traitor;
|
|
||||||
using Content.Server.Traitor.Uplink;
|
|
||||||
using Content.Server.TraitorDeathMatch.Components;
|
|
||||||
using Content.Shared.CCVar;
|
|
||||||
using Content.Shared.Damage;
|
|
||||||
using Content.Shared.Damage.Prototypes;
|
|
||||||
using Content.Shared.Hands.Components;
|
|
||||||
using Content.Shared.Inventory;
|
|
||||||
using Content.Shared.Mobs.Components;
|
|
||||||
using Content.Shared.Mobs.Systems;
|
|
||||||
using Content.Shared.PDA;
|
|
||||||
using Content.Shared.Roles;
|
|
||||||
using Robust.Server.GameObjects;
|
|
||||||
using Robust.Server.Player;
|
|
||||||
using Robust.Shared.Configuration;
|
|
||||||
using Robust.Shared.Map;
|
|
||||||
using Robust.Shared.Prototypes;
|
|
||||||
using Robust.Shared.Random;
|
|
||||||
|
|
||||||
namespace Content.Server.GameTicking.Rules;
|
|
||||||
|
|
||||||
public sealed class TraitorDeathMatchRuleSystem : GameRuleSystem
|
|
||||||
{
|
|
||||||
[Dependency] private readonly IConfigurationManager _cfg = default!;
|
|
||||||
[Dependency] private readonly IPlayerManager _playerManager = default!;
|
|
||||||
[Dependency] private readonly IChatManager _chatManager = default!;
|
|
||||||
[Dependency] private readonly IRobustRandom _robustRandom = default!;
|
|
||||||
[Dependency] private readonly IPrototypeManager _prototypeManager = default!;
|
|
||||||
[Dependency] private readonly MaxTimeRestartRuleSystem _restarter = default!;
|
|
||||||
[Dependency] private readonly InventorySystem _inventory = default!;
|
|
||||||
[Dependency] private readonly MobStateSystem _mobStateSystem = default!;
|
|
||||||
[Dependency] private readonly AtmosphereSystem _atmosphereSystem = default!;
|
|
||||||
[Dependency] private readonly TransformSystem _transformSystem = default!;
|
|
||||||
[Dependency] private readonly UplinkSystem _uplink = default!;
|
|
||||||
|
|
||||||
public override string Prototype => "TraitorDeathMatch";
|
|
||||||
|
|
||||||
public string PDAPrototypeName => "CaptainPDA";
|
|
||||||
public string BeltPrototypeName => "ClothingBeltJanitorFilled";
|
|
||||||
public string BackpackPrototypeName => "ClothingBackpackFilled";
|
|
||||||
|
|
||||||
private bool _safeToEndRound = false;
|
|
||||||
|
|
||||||
private readonly Dictionary<EntityUid, string> _allOriginalNames = new();
|
|
||||||
|
|
||||||
private const string TraitorPrototypeID = "Traitor";
|
|
||||||
|
|
||||||
public override void Initialize()
|
|
||||||
{
|
|
||||||
base.Initialize();
|
|
||||||
|
|
||||||
SubscribeLocalEvent<RoundEndTextAppendEvent>(OnRoundEndText);
|
|
||||||
SubscribeLocalEvent<PlayerSpawnCompleteEvent>(OnPlayerSpawned);
|
|
||||||
SubscribeLocalEvent<GhostAttemptHandleEvent>(OnGhostAttempt);
|
|
||||||
}
|
|
||||||
|
|
||||||
private void OnPlayerSpawned(PlayerSpawnCompleteEvent ev)
|
|
||||||
{
|
|
||||||
if (!RuleAdded)
|
|
||||||
return;
|
|
||||||
|
|
||||||
var session = ev.Player;
|
|
||||||
var startingBalance = _cfg.GetCVar(CCVars.TraitorDeathMatchStartingBalance);
|
|
||||||
|
|
||||||
// Yup, they're a traitor
|
|
||||||
var mind = session.Data.ContentData()?.Mind;
|
|
||||||
if (mind == null)
|
|
||||||
{
|
|
||||||
Logger.ErrorS("preset", "Failed getting mind for TDM player.");
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
var antagPrototype = _prototypeManager.Index<AntagPrototype>(TraitorPrototypeID);
|
|
||||||
var traitorRole = new TraitorRole(mind, antagPrototype);
|
|
||||||
mind.AddRole(traitorRole);
|
|
||||||
|
|
||||||
// Delete anything that may contain "dangerous" role-specific items.
|
|
||||||
// (This includes the PDA, as everybody gets the captain PDA in this mode for true-all-access reasons.)
|
|
||||||
if (mind.OwnedEntity is {Valid: true} owned)
|
|
||||||
{
|
|
||||||
var victimSlots = new[] {"id", "belt", "back"};
|
|
||||||
foreach (var slot in victimSlots)
|
|
||||||
{
|
|
||||||
if(_inventory.TryUnequip(owned, slot, out var entityUid, true, true))
|
|
||||||
Del(entityUid.Value);
|
|
||||||
}
|
|
||||||
|
|
||||||
// Replace their items:
|
|
||||||
|
|
||||||
var ownedCoords = Transform(owned).Coordinates;
|
|
||||||
|
|
||||||
// pda
|
|
||||||
var newPDA = Spawn(PDAPrototypeName, ownedCoords);
|
|
||||||
_inventory.TryEquip(owned, newPDA, "id", true);
|
|
||||||
|
|
||||||
// belt
|
|
||||||
var newTmp = Spawn(BeltPrototypeName, ownedCoords);
|
|
||||||
_inventory.TryEquip(owned, newTmp, "belt", true);
|
|
||||||
|
|
||||||
// backpack
|
|
||||||
newTmp = Spawn(BackpackPrototypeName, ownedCoords);
|
|
||||||
_inventory.TryEquip(owned, newTmp, "back", true);
|
|
||||||
|
|
||||||
if (!_uplink.AddUplink(owned, startingBalance))
|
|
||||||
return;
|
|
||||||
|
|
||||||
_allOriginalNames[owned] = Name(owned);
|
|
||||||
|
|
||||||
// The PDA needs to be marked with the correct owner.
|
|
||||||
var pda = Comp<PDAComponent>(newPDA);
|
|
||||||
EntityManager.EntitySysManager.GetEntitySystem<PDASystem>().SetOwner(pda, Name(owned));
|
|
||||||
EntityManager.AddComponent<TraitorDeathMatchReliableOwnerTagComponent>(newPDA).UserId = mind.UserId;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Finally, it would be preferable if they spawned as far away from other players as reasonably possible.
|
|
||||||
if (mind.OwnedEntity != null && FindAnyIsolatedSpawnLocation(mind, out var bestTarget))
|
|
||||||
{
|
|
||||||
Transform(mind.OwnedEntity.Value).Coordinates = bestTarget;
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
// The station is too drained of air to safely continue.
|
|
||||||
if (_safeToEndRound)
|
|
||||||
{
|
|
||||||
_chatManager.DispatchServerAnnouncement(Loc.GetString("traitor-death-match-station-is-too-unsafe-announcement"));
|
|
||||||
_restarter.RoundMaxTime = TimeSpan.FromMinutes(1);
|
|
||||||
_restarter.RestartTimer();
|
|
||||||
_safeToEndRound = false;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private void OnGhostAttempt(GhostAttemptHandleEvent ev)
|
|
||||||
{
|
|
||||||
if (!RuleAdded || ev.Handled)
|
|
||||||
return;
|
|
||||||
|
|
||||||
ev.Handled = true;
|
|
||||||
|
|
||||||
var mind = ev.Mind;
|
|
||||||
|
|
||||||
if (mind.OwnedEntity is {Valid: true} entity && TryComp(entity, out MobStateComponent? mobState))
|
|
||||||
{
|
|
||||||
if (_mobStateSystem.IsCritical(entity, mobState))
|
|
||||||
{
|
|
||||||
// TODO BODY SYSTEM KILL
|
|
||||||
var damage = new DamageSpecifier(_prototypeManager.Index<DamageTypePrototype>("Asphyxiation"), 100);
|
|
||||||
Get<DamageableSystem>().TryChangeDamage(entity, damage, true);
|
|
||||||
}
|
|
||||||
else if (!_mobStateSystem.IsDead(entity,mobState))
|
|
||||||
{
|
|
||||||
if (HasComp<HandsComponent>(entity))
|
|
||||||
{
|
|
||||||
ev.Result = false;
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
var session = mind.Session;
|
|
||||||
if (session == null)
|
|
||||||
{
|
|
||||||
ev.Result = false;
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
GameTicker.Respawn(session);
|
|
||||||
ev.Result = true;
|
|
||||||
}
|
|
||||||
|
|
||||||
private void OnRoundEndText(RoundEndTextAppendEvent ev)
|
|
||||||
{
|
|
||||||
if (!RuleAdded)
|
|
||||||
return;
|
|
||||||
|
|
||||||
var lines = new List<string>();
|
|
||||||
lines.Add(Loc.GetString("traitor-death-match-end-round-description-first-line"));
|
|
||||||
|
|
||||||
foreach (var uplink in EntityManager.EntityQuery<StoreComponent>(true))
|
|
||||||
{
|
|
||||||
var owner = uplink.AccountOwner;
|
|
||||||
if (owner != null && _allOriginalNames.ContainsKey(owner.Value))
|
|
||||||
{
|
|
||||||
var tcbalance = _uplink.GetTCBalance(uplink);
|
|
||||||
|
|
||||||
lines.Add(Loc.GetString("traitor-death-match-end-round-description-entry",
|
|
||||||
("originalName", _allOriginalNames[owner.Value]),
|
|
||||||
("tcBalance", tcbalance)));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
ev.AddLine(string.Join('\n', lines));
|
|
||||||
}
|
|
||||||
|
|
||||||
public override void Started()
|
|
||||||
{
|
|
||||||
_restarter.RoundMaxTime = TimeSpan.FromMinutes(30);
|
|
||||||
_restarter.RestartTimer();
|
|
||||||
_safeToEndRound = true;
|
|
||||||
}
|
|
||||||
|
|
||||||
public override void Ended()
|
|
||||||
{
|
|
||||||
}
|
|
||||||
|
|
||||||
// It would be nice if this function were moved to some generic helpers class.
|
|
||||||
private bool FindAnyIsolatedSpawnLocation(Mind.Mind ignoreMe, out EntityCoordinates bestTarget)
|
|
||||||
{
|
|
||||||
// Collate people to avoid...
|
|
||||||
var existingPlayerPoints = new List<EntityCoordinates>();
|
|
||||||
foreach (var player in _playerManager.ServerSessions)
|
|
||||||
{
|
|
||||||
var avoidMeMind = player.Data.ContentData()?.Mind;
|
|
||||||
if ((avoidMeMind == null) || (avoidMeMind == ignoreMe))
|
|
||||||
continue;
|
|
||||||
var avoidMeEntity = avoidMeMind.OwnedEntity;
|
|
||||||
if (avoidMeEntity == null)
|
|
||||||
continue;
|
|
||||||
if (TryComp(avoidMeEntity.Value, out MobStateComponent? mobState))
|
|
||||||
{
|
|
||||||
// Does have mob state component; if critical or dead, they don't really matter for spawn checks
|
|
||||||
if (_mobStateSystem.IsCritical(avoidMeEntity.Value, mobState) || _mobStateSystem.IsDead(avoidMeEntity.Value, mobState))
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
// Doesn't have mob state component. Assume something interesting is going on and don't count this as someone to avoid.
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
existingPlayerPoints.Add(Transform(avoidMeEntity.Value).Coordinates);
|
|
||||||
}
|
|
||||||
|
|
||||||
// Iterate over each possible spawn point, comparing to the existing player points.
|
|
||||||
// On failure, the returned target is the location that we're already at.
|
|
||||||
var bestTargetDistanceFromNearest = -1.0f;
|
|
||||||
// Need the random shuffle or it stuffs the first person into Atmospherics pretty reliably
|
|
||||||
var ents = EntityManager.EntityQuery<SpawnPointComponent>().Select(x => x.Owner).ToList();
|
|
||||||
_robustRandom.Shuffle(ents);
|
|
||||||
var foundATarget = false;
|
|
||||||
bestTarget = EntityCoordinates.Invalid;
|
|
||||||
|
|
||||||
foreach (var entity in ents)
|
|
||||||
{
|
|
||||||
var transform = Transform(entity);
|
|
||||||
|
|
||||||
if (transform.GridUid == null || transform.MapUid == null)
|
|
||||||
continue;
|
|
||||||
|
|
||||||
var position = _transformSystem.GetGridOrMapTilePosition(entity, transform);
|
|
||||||
|
|
||||||
if (!_atmosphereSystem.IsTileMixtureProbablySafe(transform.GridUid.Value, transform.MapUid.Value, position))
|
|
||||||
continue;
|
|
||||||
|
|
||||||
var distanceFromNearest = float.PositiveInfinity;
|
|
||||||
foreach (var existing in existingPlayerPoints)
|
|
||||||
{
|
|
||||||
if (Transform(entity).Coordinates.TryDistance(EntityManager, existing, out var dist))
|
|
||||||
distanceFromNearest = Math.Min(distanceFromNearest, dist);
|
|
||||||
}
|
|
||||||
if (bestTargetDistanceFromNearest < distanceFromNearest)
|
|
||||||
{
|
|
||||||
bestTarget = Transform(entity).Coordinates;
|
|
||||||
bestTargetDistanceFromNearest = distanceFromNearest;
|
|
||||||
foundATarget = true;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return foundATarget;
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
@@ -1,5 +1,6 @@
|
|||||||
using System.Linq;
|
using System.Linq;
|
||||||
using Content.Server.Chat.Managers;
|
using Content.Server.Chat.Managers;
|
||||||
|
using Content.Server.GameTicking.Rules.Components;
|
||||||
using Content.Server.NPC.Systems;
|
using Content.Server.NPC.Systems;
|
||||||
using Content.Server.Objectives.Interfaces;
|
using Content.Server.Objectives.Interfaces;
|
||||||
using Content.Server.PDA.Ringer;
|
using Content.Server.PDA.Ringer;
|
||||||
@@ -24,7 +25,7 @@ using Robust.Shared.Utility;
|
|||||||
|
|
||||||
namespace Content.Server.GameTicking.Rules;
|
namespace Content.Server.GameTicking.Rules;
|
||||||
|
|
||||||
public sealed class TraitorRuleSystem : GameRuleSystem
|
public sealed class TraitorRuleSystem : GameRuleSystem<TraitorRuleComponent>
|
||||||
{
|
{
|
||||||
[Dependency] private readonly IPrototypeManager _prototypeManager = default!;
|
[Dependency] private readonly IPrototypeManager _prototypeManager = default!;
|
||||||
[Dependency] private readonly IRobustRandom _random = default!;
|
[Dependency] private readonly IRobustRandom _random = default!;
|
||||||
@@ -32,7 +33,6 @@ public sealed class TraitorRuleSystem : GameRuleSystem
|
|||||||
[Dependency] private readonly IObjectivesManager _objectivesManager = default!;
|
[Dependency] private readonly IObjectivesManager _objectivesManager = default!;
|
||||||
[Dependency] private readonly IChatManager _chatManager = default!;
|
[Dependency] private readonly IChatManager _chatManager = default!;
|
||||||
[Dependency] private readonly IGameTiming _gameTiming = default!;
|
[Dependency] private readonly IGameTiming _gameTiming = default!;
|
||||||
[Dependency] private readonly GameTicker _gameTicker = default!;
|
|
||||||
[Dependency] private readonly FactionSystem _faction = default!;
|
[Dependency] private readonly FactionSystem _faction = default!;
|
||||||
[Dependency] private readonly MobStateSystem _mobStateSystem = default!;
|
[Dependency] private readonly MobStateSystem _mobStateSystem = default!;
|
||||||
[Dependency] private readonly UplinkSystem _uplink = default!;
|
[Dependency] private readonly UplinkSystem _uplink = default!;
|
||||||
@@ -40,30 +40,8 @@ public sealed class TraitorRuleSystem : GameRuleSystem
|
|||||||
|
|
||||||
private ISawmill _sawmill = default!;
|
private ISawmill _sawmill = default!;
|
||||||
|
|
||||||
public override string Prototype => "Traitor";
|
private int PlayersPerTraitor => _cfg.GetCVar(CCVars.TraitorPlayersPerTraitor);
|
||||||
|
private int MaxTraitors => _cfg.GetCVar(CCVars.TraitorMaxTraitors);
|
||||||
private readonly SoundSpecifier _addedSound = new SoundPathSpecifier("/Audio/Misc/tatoralert.ogg");
|
|
||||||
public List<TraitorRole> Traitors = new();
|
|
||||||
|
|
||||||
private const string TraitorPrototypeID = "Traitor";
|
|
||||||
private const string TraitorUplinkPresetId = "StorePresetUplink";
|
|
||||||
|
|
||||||
public int TotalTraitors => Traitors.Count;
|
|
||||||
public string[] Codewords = new string[3];
|
|
||||||
|
|
||||||
private int _playersPerTraitor => _cfg.GetCVar(CCVars.TraitorPlayersPerTraitor);
|
|
||||||
private int _maxTraitors => _cfg.GetCVar(CCVars.TraitorMaxTraitors);
|
|
||||||
|
|
||||||
public enum SelectionState
|
|
||||||
{
|
|
||||||
WaitingForSpawn = 0,
|
|
||||||
ReadyToSelect = 1,
|
|
||||||
SelectionMade = 2,
|
|
||||||
}
|
|
||||||
|
|
||||||
public SelectionState SelectionStatus = SelectionState.WaitingForSpawn;
|
|
||||||
private TimeSpan _announceAt = TimeSpan.Zero;
|
|
||||||
private Dictionary<IPlayerSession, HumanoidCharacterProfile> _startCandidates = new();
|
|
||||||
|
|
||||||
public override void Initialize()
|
public override void Initialize()
|
||||||
{
|
{
|
||||||
@@ -77,101 +55,101 @@ public sealed class TraitorRuleSystem : GameRuleSystem
|
|||||||
SubscribeLocalEvent<RoundEndTextAppendEvent>(OnRoundEndText);
|
SubscribeLocalEvent<RoundEndTextAppendEvent>(OnRoundEndText);
|
||||||
}
|
}
|
||||||
|
|
||||||
public override void Update(float frameTime)
|
protected override void ActiveTick(EntityUid uid, TraitorRuleComponent component, GameRuleComponent gameRule, float frameTime)
|
||||||
{
|
{
|
||||||
base.Update(frameTime);
|
base.ActiveTick(uid, component, gameRule, frameTime);
|
||||||
|
|
||||||
if (SelectionStatus == SelectionState.ReadyToSelect && _gameTiming.CurTime >= _announceAt)
|
if (component.SelectionStatus == TraitorRuleComponent.SelectionState.ReadyToSelect && _gameTiming.CurTime > component.AnnounceAt)
|
||||||
DoTraitorStart();
|
DoTraitorStart(component);
|
||||||
}
|
|
||||||
|
|
||||||
public override void Started(){}
|
|
||||||
|
|
||||||
public override void Ended()
|
|
||||||
{
|
|
||||||
Traitors.Clear();
|
|
||||||
_startCandidates.Clear();
|
|
||||||
SelectionStatus = SelectionState.WaitingForSpawn;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private void OnStartAttempt(RoundStartAttemptEvent ev)
|
private void OnStartAttempt(RoundStartAttemptEvent ev)
|
||||||
{
|
{
|
||||||
MakeCodewords();
|
var query = EntityQueryEnumerator<TraitorRuleComponent, GameRuleComponent>();
|
||||||
if (!RuleAdded)
|
while (query.MoveNext(out var uid, out var traitor, out var gameRule))
|
||||||
return;
|
|
||||||
|
|
||||||
var minPlayers = _cfg.GetCVar(CCVars.TraitorMinPlayers);
|
|
||||||
if (!ev.Forced && ev.Players.Length < minPlayers)
|
|
||||||
{
|
{
|
||||||
_chatManager.DispatchServerAnnouncement(Loc.GetString("traitor-not-enough-ready-players", ("readyPlayersCount", ev.Players.Length), ("minimumPlayers", minPlayers)));
|
if (!GameTicker.IsGameRuleAdded(uid, gameRule))
|
||||||
ev.Cancel();
|
continue;
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (ev.Players.Length == 0)
|
MakeCodewords(traitor);
|
||||||
{
|
|
||||||
_chatManager.DispatchServerAnnouncement(Loc.GetString("traitor-no-one-ready"));
|
var minPlayers = _cfg.GetCVar(CCVars.TraitorMinPlayers);
|
||||||
ev.Cancel();
|
if (!ev.Forced && ev.Players.Length < minPlayers)
|
||||||
|
{
|
||||||
|
_chatManager.DispatchServerAnnouncement(Loc.GetString("traitor-not-enough-ready-players",
|
||||||
|
("readyPlayersCount", ev.Players.Length), ("minimumPlayers", minPlayers)));
|
||||||
|
ev.Cancel();
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (ev.Players.Length == 0)
|
||||||
|
{
|
||||||
|
_chatManager.DispatchServerAnnouncement(Loc.GetString("traitor-no-one-ready"));
|
||||||
|
ev.Cancel();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private void MakeCodewords()
|
private void MakeCodewords(TraitorRuleComponent component)
|
||||||
{
|
{
|
||||||
var codewordCount = _cfg.GetCVar(CCVars.TraitorCodewordCount);
|
var codewordCount = _cfg.GetCVar(CCVars.TraitorCodewordCount);
|
||||||
var adjectives = _prototypeManager.Index<DatasetPrototype>("adjectives").Values;
|
var adjectives = _prototypeManager.Index<DatasetPrototype>("adjectives").Values;
|
||||||
var verbs = _prototypeManager.Index<DatasetPrototype>("verbs").Values;
|
var verbs = _prototypeManager.Index<DatasetPrototype>("verbs").Values;
|
||||||
var codewordPool = adjectives.Concat(verbs).ToList();
|
var codewordPool = adjectives.Concat(verbs).ToList();
|
||||||
var finalCodewordCount = Math.Min(codewordCount, codewordPool.Count);
|
var finalCodewordCount = Math.Min(codewordCount, codewordPool.Count);
|
||||||
Codewords = new string[finalCodewordCount];
|
component.Codewords = new string[finalCodewordCount];
|
||||||
for (var i = 0; i < finalCodewordCount; i++)
|
for (var i = 0; i < finalCodewordCount; i++)
|
||||||
{
|
{
|
||||||
Codewords[i] = _random.PickAndTake(codewordPool);
|
component.Codewords[i] = _random.PickAndTake(codewordPool);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private void DoTraitorStart()
|
private void DoTraitorStart(TraitorRuleComponent component)
|
||||||
{
|
{
|
||||||
if (!_startCandidates.Any())
|
if (!component.StartCandidates.Any())
|
||||||
{
|
{
|
||||||
_sawmill.Error("Tried to start Traitor mode without any candidates.");
|
_sawmill.Error("Tried to start Traitor mode without any candidates.");
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
var numTraitors = MathHelper.Clamp(_startCandidates.Count / _playersPerTraitor, 1, _maxTraitors);
|
var numTraitors = MathHelper.Clamp(component.StartCandidates.Count / PlayersPerTraitor, 1, MaxTraitors);
|
||||||
var codewordCount = _cfg.GetCVar(CCVars.TraitorCodewordCount);
|
var traitorPool = FindPotentialTraitors(component.StartCandidates, component);
|
||||||
|
|
||||||
var traitorPool = FindPotentialTraitors(_startCandidates);
|
|
||||||
var selectedTraitors = PickTraitors(numTraitors, traitorPool);
|
var selectedTraitors = PickTraitors(numTraitors, traitorPool);
|
||||||
|
|
||||||
foreach (var traitor in selectedTraitors)
|
foreach (var traitor in selectedTraitors)
|
||||||
|
{
|
||||||
MakeTraitor(traitor);
|
MakeTraitor(traitor);
|
||||||
|
}
|
||||||
|
|
||||||
SelectionStatus = SelectionState.SelectionMade;
|
component.SelectionStatus = TraitorRuleComponent.SelectionState.SelectionMade;
|
||||||
}
|
}
|
||||||
|
|
||||||
private void OnPlayersSpawned(RulePlayerJobsAssignedEvent ev)
|
private void OnPlayersSpawned(RulePlayerJobsAssignedEvent ev)
|
||||||
{
|
{
|
||||||
if (!RuleAdded)
|
var query = EntityQueryEnumerator<TraitorRuleComponent, GameRuleComponent>();
|
||||||
return;
|
while (query.MoveNext(out var uid, out var traitor, out var gameRule))
|
||||||
|
|
||||||
foreach (var player in ev.Players)
|
|
||||||
{
|
{
|
||||||
if (!ev.Profiles.ContainsKey(player.UserId))
|
if (!GameTicker.IsGameRuleAdded(uid, gameRule))
|
||||||
continue;
|
continue;
|
||||||
|
foreach (var player in ev.Players)
|
||||||
|
{
|
||||||
|
if (!ev.Profiles.ContainsKey(player.UserId))
|
||||||
|
continue;
|
||||||
|
|
||||||
_startCandidates[player] = ev.Profiles[player.UserId];
|
traitor.StartCandidates[player] = ev.Profiles[player.UserId];
|
||||||
|
}
|
||||||
|
|
||||||
|
var delay = TimeSpan.FromSeconds(
|
||||||
|
_cfg.GetCVar(CCVars.TraitorStartDelay) +
|
||||||
|
_random.NextFloat(0f, _cfg.GetCVar(CCVars.TraitorStartDelayVariance)));
|
||||||
|
|
||||||
|
traitor.AnnounceAt = _gameTiming.CurTime + delay;
|
||||||
|
|
||||||
|
traitor.SelectionStatus = TraitorRuleComponent.SelectionState.ReadyToSelect;
|
||||||
}
|
}
|
||||||
|
|
||||||
var delay = TimeSpan.FromSeconds(
|
|
||||||
_cfg.GetCVar(CCVars.TraitorStartDelay) +
|
|
||||||
_random.NextFloat(0f, _cfg.GetCVar(CCVars.TraitorStartDelayVariance)));
|
|
||||||
|
|
||||||
_announceAt = _gameTiming.CurTime + delay;
|
|
||||||
|
|
||||||
SelectionStatus = SelectionState.ReadyToSelect;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public List<IPlayerSession> FindPotentialTraitors(in Dictionary<IPlayerSession, HumanoidCharacterProfile> candidates)
|
public List<IPlayerSession> FindPotentialTraitors(in Dictionary<IPlayerSession, HumanoidCharacterProfile> candidates, TraitorRuleComponent component)
|
||||||
{
|
{
|
||||||
var list = new List<IPlayerSession>();
|
var list = new List<IPlayerSession>();
|
||||||
var pendingQuery = GetEntityQuery<PendingClockInComponent>();
|
var pendingQuery = GetEntityQuery<PendingClockInComponent>();
|
||||||
@@ -196,7 +174,7 @@ public sealed class TraitorRuleSystem : GameRuleSystem
|
|||||||
foreach (var player in list)
|
foreach (var player in list)
|
||||||
{
|
{
|
||||||
var profile = candidates[player];
|
var profile = candidates[player];
|
||||||
if (profile.AntagPreferences.Contains(TraitorPrototypeID))
|
if (profile.AntagPreferences.Contains(component.TraitorPrototypeId))
|
||||||
{
|
{
|
||||||
prefList.Add(player);
|
prefList.Add(player);
|
||||||
}
|
}
|
||||||
@@ -228,6 +206,14 @@ public sealed class TraitorRuleSystem : GameRuleSystem
|
|||||||
|
|
||||||
public bool MakeTraitor(IPlayerSession traitor)
|
public bool MakeTraitor(IPlayerSession traitor)
|
||||||
{
|
{
|
||||||
|
var traitorRule = EntityQuery<TraitorRuleComponent>().FirstOrDefault();
|
||||||
|
if (traitorRule == null)
|
||||||
|
{
|
||||||
|
//todo fuck me this shit is awful
|
||||||
|
GameTicker.StartGameRule("traitor", out var ruleEntity);
|
||||||
|
traitorRule = EntityManager.GetComponent<TraitorRuleComponent>(ruleEntity);
|
||||||
|
}
|
||||||
|
|
||||||
var mind = traitor.Data.ContentData()?.Mind;
|
var mind = traitor.Data.ContentData()?.Mind;
|
||||||
if (mind == null)
|
if (mind == null)
|
||||||
{
|
{
|
||||||
@@ -254,14 +240,15 @@ public sealed class TraitorRuleSystem : GameRuleSystem
|
|||||||
if (pda == null || !_uplink.AddUplink(mind.OwnedEntity.Value, startingBalance))
|
if (pda == null || !_uplink.AddUplink(mind.OwnedEntity.Value, startingBalance))
|
||||||
return false;
|
return false;
|
||||||
|
|
||||||
|
|
||||||
// add the ringtone uplink and get its code for greeting
|
// add the ringtone uplink and get its code for greeting
|
||||||
var code = AddComp<RingerUplinkComponent>(pda.Value).Code;
|
var code = AddComp<RingerUplinkComponent>(pda.Value).Code;
|
||||||
|
|
||||||
var antagPrototype = _prototypeManager.Index<AntagPrototype>(TraitorPrototypeID);
|
var antagPrototype = _prototypeManager.Index<AntagPrototype>(traitorRule.TraitorPrototypeId);
|
||||||
var traitorRole = new TraitorRole(mind, antagPrototype);
|
var traitorRole = new TraitorRole(mind, antagPrototype);
|
||||||
mind.AddRole(traitorRole);
|
mind.AddRole(traitorRole);
|
||||||
Traitors.Add(traitorRole);
|
traitorRule.Traitors.Add(traitorRole);
|
||||||
traitorRole.GreetTraitor(Codewords, code);
|
traitorRole.GreetTraitor(traitorRule.Codewords, code);
|
||||||
|
|
||||||
_faction.RemoveFaction(entity, "NanoTrasen", false);
|
_faction.RemoveFaction(entity, "NanoTrasen", false);
|
||||||
_faction.AddFaction(entity, "Syndicate");
|
_faction.AddFaction(entity, "Syndicate");
|
||||||
@@ -280,147 +267,173 @@ public sealed class TraitorRuleSystem : GameRuleSystem
|
|||||||
}
|
}
|
||||||
|
|
||||||
//give traitors their codewords and uplink code to keep in their character info menu
|
//give traitors their codewords and uplink code to keep in their character info menu
|
||||||
traitorRole.Mind.Briefing = Loc.GetString("traitor-role-codewords-short", ("codewords", string.Join(", ", Codewords)))
|
traitorRole.Mind.Briefing = Loc.GetString("traitor-role-codewords-short", ("codewords", string.Join(", ", traitorRule.Codewords)))
|
||||||
+ "\n" + Loc.GetString("traitor-role-uplink-code-short", ("code", string.Join("", code)));
|
+ "\n" + Loc.GetString("traitor-role-uplink-code-short", ("code", string.Join("", code)));
|
||||||
|
|
||||||
_audioSystem.PlayGlobal(_addedSound, Filter.Empty().AddPlayer(traitor), false, AudioParams.Default);
|
_audioSystem.PlayGlobal(traitorRule.AddedSound, Filter.Empty().AddPlayer(traitor), false, AudioParams.Default);
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
private void HandleLatejoin(PlayerSpawnCompleteEvent ev)
|
private void HandleLatejoin(PlayerSpawnCompleteEvent ev)
|
||||||
{
|
{
|
||||||
if (!RuleAdded)
|
var query = EntityQueryEnumerator<TraitorRuleComponent, GameRuleComponent>();
|
||||||
return;
|
while (query.MoveNext(out var uid, out var traitor, out var gameRule))
|
||||||
if (TotalTraitors >= _maxTraitors)
|
|
||||||
return;
|
|
||||||
if (!ev.LateJoin)
|
|
||||||
return;
|
|
||||||
if (!ev.Profile.AntagPreferences.Contains(TraitorPrototypeID))
|
|
||||||
return;
|
|
||||||
|
|
||||||
|
|
||||||
if (ev.JobId == null || !_prototypeManager.TryIndex<JobPrototype>(ev.JobId, out var job))
|
|
||||||
return;
|
|
||||||
|
|
||||||
if (!job.CanBeAntag)
|
|
||||||
return;
|
|
||||||
|
|
||||||
// Before the announcement is made, late-joiners are considered the same as players who readied.
|
|
||||||
if (SelectionStatus < SelectionState.SelectionMade)
|
|
||||||
{
|
{
|
||||||
_startCandidates[ev.Player] = ev.Profile;
|
if (!GameTicker.IsGameRuleAdded(uid, gameRule))
|
||||||
return;
|
continue;
|
||||||
}
|
|
||||||
|
|
||||||
// the nth player we adjust our probabilities around
|
if (traitor.TotalTraitors >= MaxTraitors)
|
||||||
int target = ((_playersPerTraitor * TotalTraitors) + 1);
|
continue;
|
||||||
|
if (!ev.LateJoin)
|
||||||
|
continue;
|
||||||
|
if (!ev.Profile.AntagPreferences.Contains(traitor.TraitorPrototypeId))
|
||||||
|
continue;
|
||||||
|
|
||||||
float chance = (1f / _playersPerTraitor);
|
if (ev.JobId == null || !_prototypeManager.TryIndex<JobPrototype>(ev.JobId, out var job))
|
||||||
|
continue;
|
||||||
|
|
||||||
// If we have too many traitors, divide by how many players below target for next traitor we are.
|
if (!job.CanBeAntag)
|
||||||
if (ev.JoinOrder < target)
|
continue;
|
||||||
{
|
|
||||||
chance /= (target - ev.JoinOrder);
|
|
||||||
} else // Tick up towards 100% chance.
|
|
||||||
{
|
|
||||||
chance *= ((ev.JoinOrder + 1) - target);
|
|
||||||
}
|
|
||||||
if (chance > 1)
|
|
||||||
chance = 1;
|
|
||||||
|
|
||||||
// Now that we've calculated our chance, roll and make them a traitor if we roll under.
|
// Before the announcement is made, late-joiners are considered the same as players who readied.
|
||||||
// You get one shot.
|
if (traitor.SelectionStatus < TraitorRuleComponent.SelectionState.SelectionMade)
|
||||||
if (_random.Prob(chance))
|
{
|
||||||
{
|
traitor.StartCandidates[ev.Player] = ev.Profile;
|
||||||
MakeTraitor(ev.Player);
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
// the nth player we adjust our probabilities around
|
||||||
|
var target = PlayersPerTraitor * traitor.TotalTraitors + 1;
|
||||||
|
|
||||||
|
var chance = 1f / PlayersPerTraitor;
|
||||||
|
|
||||||
|
// If we have too many traitors, divide by how many players below target for next traitor we are.
|
||||||
|
if (ev.JoinOrder < target)
|
||||||
|
{
|
||||||
|
chance /= (target - ev.JoinOrder);
|
||||||
|
}
|
||||||
|
else // Tick up towards 100% chance.
|
||||||
|
{
|
||||||
|
chance *= ((ev.JoinOrder + 1) - target);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (chance > 1)
|
||||||
|
chance = 1;
|
||||||
|
|
||||||
|
// Now that we've calculated our chance, roll and make them a traitor if we roll under.
|
||||||
|
// You get one shot.
|
||||||
|
if (_random.Prob(chance))
|
||||||
|
{
|
||||||
|
MakeTraitor(ev.Player);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private void OnRoundEndText(RoundEndTextAppendEvent ev)
|
private void OnRoundEndText(RoundEndTextAppendEvent ev)
|
||||||
{
|
{
|
||||||
if (!RuleAdded)
|
var query = EntityQueryEnumerator<TraitorRuleComponent, GameRuleComponent>();
|
||||||
return;
|
while (query.MoveNext(out var uid, out var traitor, out var gameRule))
|
||||||
|
|
||||||
var result = Loc.GetString("traitor-round-end-result", ("traitorCount", Traitors.Count));
|
|
||||||
|
|
||||||
result += "\n" + Loc.GetString("traitor-round-end-codewords", ("codewords", string.Join(", ", Codewords))) + "\n";
|
|
||||||
|
|
||||||
foreach (var traitor in Traitors)
|
|
||||||
{
|
{
|
||||||
var name = traitor.Mind.CharacterName;
|
if (!GameTicker.IsGameRuleAdded(uid, gameRule))
|
||||||
traitor.Mind.TryGetSession(out var session);
|
continue;
|
||||||
var username = session?.Name;
|
|
||||||
|
|
||||||
var objectives = traitor.Mind.AllObjectives.ToArray();
|
var result = Loc.GetString("traitor-round-end-result", ("traitorCount", traitor.Traitors.Count));
|
||||||
if (objectives.Length == 0)
|
|
||||||
|
result += "\n" + Loc.GetString("traitor-round-end-codewords", ("codewords", string.Join(", ", traitor.Codewords))) +
|
||||||
|
"\n";
|
||||||
|
|
||||||
|
foreach (var t in traitor.Traitors)
|
||||||
{
|
{
|
||||||
|
var name = t.Mind.CharacterName;
|
||||||
|
t.Mind.TryGetSession(out var session);
|
||||||
|
var username = session?.Name;
|
||||||
|
|
||||||
|
var objectives = t.Mind.AllObjectives.ToArray();
|
||||||
|
if (objectives.Length == 0)
|
||||||
|
{
|
||||||
|
if (username != null)
|
||||||
|
{
|
||||||
|
if (name == null)
|
||||||
|
result += "\n" + Loc.GetString("traitor-user-was-a-traitor", ("user", username));
|
||||||
|
else
|
||||||
|
result += "\n" + Loc.GetString("traitor-user-was-a-traitor-named", ("user", username),
|
||||||
|
("name", name));
|
||||||
|
}
|
||||||
|
else if (name != null)
|
||||||
|
result += "\n" + Loc.GetString("traitor-was-a-traitor-named", ("name", name));
|
||||||
|
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
if (username != null)
|
if (username != null)
|
||||||
{
|
{
|
||||||
if (name == null)
|
if (name == null)
|
||||||
result += "\n" + Loc.GetString("traitor-user-was-a-traitor", ("user", username));
|
result += "\n" + Loc.GetString("traitor-user-was-a-traitor-with-objectives",
|
||||||
|
("user", username));
|
||||||
else
|
else
|
||||||
result += "\n" + Loc.GetString("traitor-user-was-a-traitor-named", ("user", username), ("name", name));
|
result += "\n" + Loc.GetString("traitor-user-was-a-traitor-with-objectives-named",
|
||||||
|
("user", username), ("name", name));
|
||||||
}
|
}
|
||||||
else if (name != null)
|
else if (name != null)
|
||||||
result += "\n" + Loc.GetString("traitor-was-a-traitor-named", ("name", name));
|
result += "\n" + Loc.GetString("traitor-was-a-traitor-with-objectives-named", ("name", name));
|
||||||
|
|
||||||
continue;
|
foreach (var objectiveGroup in objectives.GroupBy(o => o.Prototype.Issuer))
|
||||||
}
|
|
||||||
|
|
||||||
if (username != null)
|
|
||||||
{
|
|
||||||
if (name == null)
|
|
||||||
result += "\n" + Loc.GetString("traitor-user-was-a-traitor-with-objectives", ("user", username));
|
|
||||||
else
|
|
||||||
result += "\n" + Loc.GetString("traitor-user-was-a-traitor-with-objectives-named", ("user", username), ("name", name));
|
|
||||||
}
|
|
||||||
else if (name != null)
|
|
||||||
result += "\n" + Loc.GetString("traitor-was-a-traitor-with-objectives-named", ("name", name));
|
|
||||||
|
|
||||||
foreach (var objectiveGroup in objectives.GroupBy(o => o.Prototype.Issuer))
|
|
||||||
{
|
|
||||||
result += "\n" + Loc.GetString($"preset-traitor-objective-issuer-{objectiveGroup.Key}");
|
|
||||||
|
|
||||||
foreach (var objective in objectiveGroup)
|
|
||||||
{
|
{
|
||||||
foreach (var condition in objective.Conditions)
|
result += "\n" + Loc.GetString($"preset-traitor-objective-issuer-{objectiveGroup.Key}");
|
||||||
|
|
||||||
|
foreach (var objective in objectiveGroup)
|
||||||
{
|
{
|
||||||
var progress = condition.Progress;
|
foreach (var condition in objective.Conditions)
|
||||||
if (progress > 0.99f)
|
|
||||||
{
|
{
|
||||||
result += "\n- " + Loc.GetString(
|
var progress = condition.Progress;
|
||||||
"traitor-objective-condition-success",
|
if (progress > 0.99f)
|
||||||
("condition", condition.Title),
|
{
|
||||||
("markupColor", "green")
|
result += "\n- " + Loc.GetString(
|
||||||
);
|
"traitor-objective-condition-success",
|
||||||
}
|
("condition", condition.Title),
|
||||||
else
|
("markupColor", "green")
|
||||||
{
|
);
|
||||||
result += "\n- " + Loc.GetString(
|
}
|
||||||
"traitor-objective-condition-fail",
|
else
|
||||||
("condition", condition.Title),
|
{
|
||||||
("progress", (int) (progress * 100)),
|
result += "\n- " + Loc.GetString(
|
||||||
("markupColor", "red")
|
"traitor-objective-condition-fail",
|
||||||
);
|
("condition", condition.Title),
|
||||||
|
("progress", (int) (progress * 100)),
|
||||||
|
("markupColor", "red")
|
||||||
|
);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
ev.AddLine(result);
|
||||||
}
|
}
|
||||||
ev.AddLine(result);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public IEnumerable<TraitorRole> GetOtherTraitorsAliveAndConnected(Mind.Mind ourMind)
|
public List<TraitorRole> GetOtherTraitorsAliveAndConnected(Mind.Mind ourMind)
|
||||||
{
|
{
|
||||||
var traitors = Traitors;
|
List<TraitorRole> allTraitors = new();
|
||||||
List<TraitorRole> removeList = new();
|
foreach (var traitor in EntityQuery<TraitorRuleComponent>())
|
||||||
|
{
|
||||||
|
foreach (var role in GetOtherTraitorsAliveAndConnected(ourMind, traitor))
|
||||||
|
{
|
||||||
|
if (!allTraitors.Contains(role))
|
||||||
|
allTraitors.Add(role);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
return Traitors // don't want
|
return allTraitors;
|
||||||
.Where(t => t.Mind is not null) // no mind
|
}
|
||||||
|
|
||||||
|
public List<TraitorRole> GetOtherTraitorsAliveAndConnected(Mind.Mind ourMind, TraitorRuleComponent component)
|
||||||
|
{
|
||||||
|
return component.Traitors // don't want
|
||||||
.Where(t => t.Mind.OwnedEntity is not null) // no entity
|
.Where(t => t.Mind.OwnedEntity is not null) // no entity
|
||||||
.Where(t => t.Mind.Session is not null) // player disconnected
|
.Where(t => t.Mind.Session is not null) // player disconnected
|
||||||
.Where(t => t.Mind != ourMind) // ourselves
|
.Where(t => t.Mind != ourMind) // ourselves
|
||||||
.Where(t => _mobStateSystem.IsAlive((EntityUid) t.Mind.OwnedEntity!)) // dead
|
.Where(t => _mobStateSystem.IsAlive((EntityUid) t.Mind.OwnedEntity!)) // dead
|
||||||
.Where(t => t.Mind.CurrentEntity == t.Mind.OwnedEntity); // not in original body
|
.Where(t => t.Mind.CurrentEntity == t.Mind.OwnedEntity).ToList(); // not in original body
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -4,7 +4,7 @@ using Content.Server.Actions;
|
|||||||
using Content.Server.Chat.Managers;
|
using Content.Server.Chat.Managers;
|
||||||
using Content.Server.Disease;
|
using Content.Server.Disease;
|
||||||
using Content.Server.Disease.Components;
|
using Content.Server.Disease.Components;
|
||||||
using Content.Server.Humanoid;
|
using Content.Server.GameTicking.Rules.Components;
|
||||||
using Content.Server.Mind.Components;
|
using Content.Server.Mind.Components;
|
||||||
using Content.Server.Players;
|
using Content.Server.Players;
|
||||||
using Content.Server.Popups;
|
using Content.Server.Popups;
|
||||||
@@ -29,7 +29,7 @@ using Robust.Shared.Utility;
|
|||||||
|
|
||||||
namespace Content.Server.GameTicking.Rules;
|
namespace Content.Server.GameTicking.Rules;
|
||||||
|
|
||||||
public sealed class ZombieRuleSystem : GameRuleSystem
|
public sealed class ZombieRuleSystem : GameRuleSystem<ZombieRuleComponent>
|
||||||
{
|
{
|
||||||
[Dependency] private readonly IPrototypeManager _prototypeManager = default!;
|
[Dependency] private readonly IPrototypeManager _prototypeManager = default!;
|
||||||
[Dependency] private readonly IRobustRandom _random = default!;
|
[Dependency] private readonly IRobustRandom _random = default!;
|
||||||
@@ -44,14 +44,6 @@ public sealed class ZombieRuleSystem : GameRuleSystem
|
|||||||
[Dependency] private readonly MobStateSystem _mobState = default!;
|
[Dependency] private readonly MobStateSystem _mobState = default!;
|
||||||
[Dependency] private readonly ZombifyOnDeathSystem _zombify = default!;
|
[Dependency] private readonly ZombifyOnDeathSystem _zombify = default!;
|
||||||
|
|
||||||
private Dictionary<string, string> _initialInfectedNames = new();
|
|
||||||
|
|
||||||
public override string Prototype => "Zombie";
|
|
||||||
|
|
||||||
private const string PatientZeroPrototypeID = "InitialInfected";
|
|
||||||
private const string InitialZombieVirusPrototype = "PassiveZombieVirus";
|
|
||||||
private const string ZombifySelfActionPrototype = "TurnUndead";
|
|
||||||
|
|
||||||
public override void Initialize()
|
public override void Initialize()
|
||||||
{
|
{
|
||||||
base.Initialize();
|
base.Initialize();
|
||||||
@@ -67,60 +59,61 @@ public sealed class ZombieRuleSystem : GameRuleSystem
|
|||||||
|
|
||||||
private void OnRoundEndText(RoundEndTextAppendEvent ev)
|
private void OnRoundEndText(RoundEndTextAppendEvent ev)
|
||||||
{
|
{
|
||||||
if (!RuleAdded)
|
foreach (var zombie in EntityQuery<ZombieRuleComponent>())
|
||||||
return;
|
|
||||||
|
|
||||||
//this is just the general condition thing used for determining the win/lose text
|
|
||||||
var percent = GetInfectedPercentage(out var livingHumans);
|
|
||||||
|
|
||||||
if (percent <= 0)
|
|
||||||
ev.AddLine(Loc.GetString("zombie-round-end-amount-none"));
|
|
||||||
else if (percent <= 0.25)
|
|
||||||
ev.AddLine(Loc.GetString("zombie-round-end-amount-low"));
|
|
||||||
else if (percent <= 0.5)
|
|
||||||
ev.AddLine(Loc.GetString("zombie-round-end-amount-medium", ("percent", Math.Round((percent * 100), 2).ToString(CultureInfo.InvariantCulture))));
|
|
||||||
else if (percent < 1)
|
|
||||||
ev.AddLine(Loc.GetString("zombie-round-end-amount-high", ("percent", Math.Round((percent * 100), 2).ToString(CultureInfo.InvariantCulture))));
|
|
||||||
else
|
|
||||||
ev.AddLine(Loc.GetString("zombie-round-end-amount-all"));
|
|
||||||
|
|
||||||
ev.AddLine(Loc.GetString("zombie-round-end-initial-count", ("initialCount", _initialInfectedNames.Count)));
|
|
||||||
foreach (var player in _initialInfectedNames)
|
|
||||||
{
|
{
|
||||||
ev.AddLine(Loc.GetString("zombie-round-end-user-was-initial",
|
//this is just the general condition thing used for determining the win/lose text
|
||||||
("name", player.Key),
|
var percent = GetInfectedPercentage(out var livingHumans);
|
||||||
("username", player.Value)));
|
|
||||||
}
|
|
||||||
|
|
||||||
//Gets a bunch of the living players and displays them if they're under a threshold.
|
if (percent <= 0)
|
||||||
//InitialInfected is used for the threshold because it scales with the player count well.
|
ev.AddLine(Loc.GetString("zombie-round-end-amount-none"));
|
||||||
if (livingHumans.Count > 0 && livingHumans.Count <= _initialInfectedNames.Count)
|
else if (percent <= 0.25)
|
||||||
{
|
ev.AddLine(Loc.GetString("zombie-round-end-amount-low"));
|
||||||
ev.AddLine("");
|
else if (percent <= 0.5)
|
||||||
ev.AddLine(Loc.GetString("zombie-round-end-survivor-count", ("count", livingHumans.Count)));
|
ev.AddLine(Loc.GetString("zombie-round-end-amount-medium", ("percent", Math.Round((percent * 100), 2).ToString(CultureInfo.InvariantCulture))));
|
||||||
foreach (var survivor in livingHumans)
|
else if (percent < 1)
|
||||||
|
ev.AddLine(Loc.GetString("zombie-round-end-amount-high", ("percent", Math.Round((percent * 100), 2).ToString(CultureInfo.InvariantCulture))));
|
||||||
|
else
|
||||||
|
ev.AddLine(Loc.GetString("zombie-round-end-amount-all"));
|
||||||
|
|
||||||
|
ev.AddLine(Loc.GetString("zombie-round-end-initial-count", ("initialCount", zombie.InitialInfectedNames.Count)));
|
||||||
|
foreach (var player in zombie.InitialInfectedNames)
|
||||||
{
|
{
|
||||||
var meta = MetaData(survivor);
|
ev.AddLine(Loc.GetString("zombie-round-end-user-was-initial",
|
||||||
var username = string.Empty;
|
("name", player.Key),
|
||||||
if (TryComp<MindComponent>(survivor, out var mindcomp))
|
("username", player.Value)));
|
||||||
if (mindcomp.Mind != null && mindcomp.Mind.Session != null)
|
}
|
||||||
username = mindcomp.Mind.Session.Name;
|
|
||||||
|
|
||||||
ev.AddLine(Loc.GetString("zombie-round-end-user-was-survivor",
|
//Gets a bunch of the living players and displays them if they're under a threshold.
|
||||||
("name", meta.EntityName),
|
//InitialInfected is used for the threshold because it scales with the player count well.
|
||||||
("username", username)));
|
if (livingHumans.Count > 0 && livingHumans.Count <= zombie.InitialInfectedNames.Count)
|
||||||
|
{
|
||||||
|
ev.AddLine("");
|
||||||
|
ev.AddLine(Loc.GetString("zombie-round-end-survivor-count", ("count", livingHumans.Count)));
|
||||||
|
foreach (var survivor in livingHumans)
|
||||||
|
{
|
||||||
|
var meta = MetaData(survivor);
|
||||||
|
var username = string.Empty;
|
||||||
|
if (TryComp<MindComponent>(survivor, out var mindcomp))
|
||||||
|
if (mindcomp.Mind != null && mindcomp.Mind.Session != null)
|
||||||
|
username = mindcomp.Mind.Session.Name;
|
||||||
|
|
||||||
|
ev.AddLine(Loc.GetString("zombie-round-end-user-was-survivor",
|
||||||
|
("name", meta.EntityName),
|
||||||
|
("username", username)));
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private void OnJobAssigned(RulePlayerJobsAssignedEvent ev)
|
private void OnJobAssigned(RulePlayerJobsAssignedEvent ev)
|
||||||
{
|
{
|
||||||
if (!RuleAdded)
|
var query = EntityQueryEnumerator<ZombieRuleComponent, GameRuleComponent>();
|
||||||
return;
|
while (query.MoveNext(out var uid, out var zombies, out var gameRule))
|
||||||
|
{
|
||||||
_initialInfectedNames = new();
|
if (!GameTicker.IsGameRuleAdded(uid, gameRule))
|
||||||
|
continue;
|
||||||
InfectInitialPlayers();
|
InfectInitialPlayers(zombies);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <remarks>
|
/// <remarks>
|
||||||
@@ -129,15 +122,11 @@ public sealed class ZombieRuleSystem : GameRuleSystem
|
|||||||
/// </remarks>
|
/// </remarks>
|
||||||
private void OnMobStateChanged(MobStateChangedEvent ev)
|
private void OnMobStateChanged(MobStateChangedEvent ev)
|
||||||
{
|
{
|
||||||
if (!RuleAdded)
|
|
||||||
return;
|
|
||||||
CheckRoundEnd(ev.Target);
|
CheckRoundEnd(ev.Target);
|
||||||
}
|
}
|
||||||
|
|
||||||
private void OnEntityZombified(EntityZombifiedEvent ev)
|
private void OnEntityZombified(EntityZombifiedEvent ev)
|
||||||
{
|
{
|
||||||
if (!RuleAdded)
|
|
||||||
return;
|
|
||||||
CheckRoundEnd(ev.Target);
|
CheckRoundEnd(ev.Target);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -147,50 +136,59 @@ public sealed class ZombieRuleSystem : GameRuleSystem
|
|||||||
/// <param name="target">depending on this uid, we should care about the round ending</param>
|
/// <param name="target">depending on this uid, we should care about the round ending</param>
|
||||||
private void CheckRoundEnd(EntityUid target)
|
private void CheckRoundEnd(EntityUid target)
|
||||||
{
|
{
|
||||||
//we only care about players, not monkeys and such.
|
var query = EntityQueryEnumerator<ZombieRuleComponent, GameRuleComponent>();
|
||||||
if (!HasComp<HumanoidAppearanceComponent>(target))
|
while (query.MoveNext(out var uid, out var zombies, out var gameRule))
|
||||||
return;
|
{
|
||||||
|
if (GameTicker.IsGameRuleActive(uid, gameRule))
|
||||||
|
continue;
|
||||||
|
|
||||||
var percent = GetInfectedPercentage(out var num);
|
//we only care about players, not monkeys and such.
|
||||||
if (num.Count == 1) //only one human left. spooky
|
if (!HasComp<HumanoidAppearanceComponent>(target))
|
||||||
_popup.PopupEntity(Loc.GetString("zombie-alone"), num[0], num[0]);
|
continue;
|
||||||
if (percent >= 1) //oops, all zombies
|
|
||||||
_roundEndSystem.EndRound();
|
var percent = GetInfectedPercentage(out var num);
|
||||||
|
if (num.Count == 1) //only one human left. spooky
|
||||||
|
_popup.PopupEntity(Loc.GetString("zombie-alone"), num[0], num[0]);
|
||||||
|
if (percent >= 1) //oops, all zombies
|
||||||
|
_roundEndSystem.EndRound();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private void OnStartAttempt(RoundStartAttemptEvent ev)
|
private void OnStartAttempt(RoundStartAttemptEvent ev)
|
||||||
{
|
{
|
||||||
if (!RuleAdded)
|
var query = EntityQueryEnumerator<ZombieRuleComponent, GameRuleComponent>();
|
||||||
return;
|
while (query.MoveNext(out var uid, out var zombies, out var gameRule))
|
||||||
|
|
||||||
var minPlayers = _cfg.GetCVar(CCVars.ZombieMinPlayers);
|
|
||||||
if (!ev.Forced && ev.Players.Length < minPlayers)
|
|
||||||
{
|
{
|
||||||
_chatManager.DispatchServerAnnouncement(Loc.GetString("zombie-not-enough-ready-players", ("readyPlayersCount", ev.Players.Length), ("minimumPlayers", minPlayers)));
|
if (!GameTicker.IsGameRuleAdded(uid, gameRule))
|
||||||
ev.Cancel();
|
continue;
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (ev.Players.Length == 0)
|
var minPlayers = _cfg.GetCVar(CCVars.ZombieMinPlayers);
|
||||||
{
|
if (!ev.Forced && ev.Players.Length < minPlayers)
|
||||||
_chatManager.DispatchServerAnnouncement(Loc.GetString("zombie-no-one-ready"));
|
{
|
||||||
ev.Cancel();
|
_chatManager.DispatchServerAnnouncement(Loc.GetString("zombie-not-enough-ready-players", ("readyPlayersCount", ev.Players.Length), ("minimumPlayers", minPlayers)));
|
||||||
|
ev.Cancel();
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (ev.Players.Length == 0)
|
||||||
|
{
|
||||||
|
_chatManager.DispatchServerAnnouncement(Loc.GetString("zombie-no-one-ready"));
|
||||||
|
ev.Cancel();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public override void Started()
|
protected override void Started(EntityUid uid, ZombieRuleComponent component, GameRuleComponent gameRule, GameRuleStartedEvent args)
|
||||||
{
|
{
|
||||||
//this technically will run twice with zombies on roundstart, but it doesn't matter because it fails instantly
|
base.Started(uid, component, gameRule, args);
|
||||||
InfectInitialPlayers();
|
InfectInitialPlayers(component);
|
||||||
}
|
}
|
||||||
|
|
||||||
public override void Ended() { }
|
|
||||||
|
|
||||||
private void OnZombifySelf(EntityUid uid, ZombifyOnDeathComponent component, ZombifySelfActionEvent args)
|
private void OnZombifySelf(EntityUid uid, ZombifyOnDeathComponent component, ZombifySelfActionEvent args)
|
||||||
{
|
{
|
||||||
_zombify.ZombifyEntity(uid);
|
_zombify.ZombifyEntity(uid);
|
||||||
|
|
||||||
var action = new InstantAction(_prototypeManager.Index<InstantActionPrototype>(ZombifySelfActionPrototype));
|
var action = new InstantAction(_prototypeManager.Index<InstantActionPrototype>(ZombieRuleComponent.ZombifySelfActionPrototype));
|
||||||
_action.RemoveAction(uid, action);
|
_action.RemoveAction(uid, action);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -228,7 +226,7 @@ public sealed class ZombieRuleSystem : GameRuleSystem
|
|||||||
/// allowing this gamemode to be started midround. As such, it doesn't need
|
/// allowing this gamemode to be started midround. As such, it doesn't need
|
||||||
/// any information besides just running.
|
/// any information besides just running.
|
||||||
/// </remarks>
|
/// </remarks>
|
||||||
private void InfectInitialPlayers()
|
private void InfectInitialPlayers(ZombieRuleComponent component)
|
||||||
{
|
{
|
||||||
var allPlayers = _playerManager.ServerSessions.ToList();
|
var allPlayers = _playerManager.ServerSessions.ToList();
|
||||||
var playerList = new List<IPlayerSession>();
|
var playerList = new List<IPlayerSession>();
|
||||||
@@ -240,7 +238,7 @@ public sealed class ZombieRuleSystem : GameRuleSystem
|
|||||||
playerList.Add(player);
|
playerList.Add(player);
|
||||||
|
|
||||||
var pref = (HumanoidCharacterProfile) _prefs.GetPreferences(player.UserId).SelectedCharacter;
|
var pref = (HumanoidCharacterProfile) _prefs.GetPreferences(player.UserId).SelectedCharacter;
|
||||||
if (pref.AntagPreferences.Contains(PatientZeroPrototypeID))
|
if (pref.AntagPreferences.Contains(component.PatientZeroPrototypeID))
|
||||||
prefList.Add(player);
|
prefList.Add(player);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -284,15 +282,15 @@ public sealed class ZombieRuleSystem : GameRuleSystem
|
|||||||
|
|
||||||
DebugTools.AssertNotNull(mind.OwnedEntity);
|
DebugTools.AssertNotNull(mind.OwnedEntity);
|
||||||
|
|
||||||
mind.AddRole(new TraitorRole(mind, _prototypeManager.Index<AntagPrototype>(PatientZeroPrototypeID)));
|
mind.AddRole(new TraitorRole(mind, _prototypeManager.Index<AntagPrototype>(component.PatientZeroPrototypeID)));
|
||||||
|
|
||||||
var inCharacterName = string.Empty;
|
var inCharacterName = string.Empty;
|
||||||
if (mind.OwnedEntity != null)
|
if (mind.OwnedEntity != null)
|
||||||
{
|
{
|
||||||
_diseaseSystem.TryAddDisease(mind.OwnedEntity.Value, InitialZombieVirusPrototype);
|
_diseaseSystem.TryAddDisease(mind.OwnedEntity.Value, component.InitialZombieVirusPrototype);
|
||||||
inCharacterName = MetaData(mind.OwnedEntity.Value).EntityName;
|
inCharacterName = MetaData(mind.OwnedEntity.Value).EntityName;
|
||||||
|
|
||||||
var action = new InstantAction(_prototypeManager.Index<InstantActionPrototype>(ZombifySelfActionPrototype));
|
var action = new InstantAction(_prototypeManager.Index<InstantActionPrototype>(ZombieRuleComponent.ZombifySelfActionPrototype));
|
||||||
_action.AddAction(mind.OwnedEntity.Value, action, null);
|
_action.AddAction(mind.OwnedEntity.Value, action, null);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -303,7 +301,7 @@ public sealed class ZombieRuleSystem : GameRuleSystem
|
|||||||
|
|
||||||
//gets the names now in case the players leave.
|
//gets the names now in case the players leave.
|
||||||
//this gets unhappy if people with the same name get chose. Probably shouldn't happen.
|
//this gets unhappy if people with the same name get chose. Probably shouldn't happen.
|
||||||
_initialInfectedNames.Add(inCharacterName, mind.Session.Name);
|
component.InitialInfectedNames.Add(inCharacterName, mind.Session.Name);
|
||||||
|
|
||||||
// I went all the way to ChatManager.cs and all i got was this lousy T-shirt
|
// I went all the way to ChatManager.cs and all i got was this lousy T-shirt
|
||||||
// You got a free T-shirt!?!?
|
// You got a free T-shirt!?!?
|
||||||
|
|||||||
@@ -14,9 +14,10 @@ namespace Content.Server.Objectives.Conditions
|
|||||||
public IObjectiveCondition GetAssigned(Mind.Mind mind)
|
public IObjectiveCondition GetAssigned(Mind.Mind mind)
|
||||||
{
|
{
|
||||||
var entityMgr = IoCManager.Resolve<IEntityManager>();
|
var entityMgr = IoCManager.Resolve<IEntityManager>();
|
||||||
var traitors = entityMgr.EntitySysManager.GetEntitySystem<TraitorRuleSystem>().GetOtherTraitorsAliveAndConnected(mind).ToList();
|
|
||||||
|
|
||||||
if (traitors.Count == 0) return new EscapeShuttleCondition{}; //You were made a traitor by admins, and are the first/only.
|
var traitors = entityMgr.EntitySysManager.GetEntitySystem<TraitorRuleSystem>().GetOtherTraitorsAliveAndConnected(mind).ToList();
|
||||||
|
if (traitors.Count == 0)
|
||||||
|
return new EscapeShuttleCondition(); //You were made a traitor by admins, and are the first/only.
|
||||||
return new RandomTraitorAliveCondition { _target = IoCManager.Resolve<IRobustRandom>().Pick(traitors).Mind };
|
return new RandomTraitorAliveCondition { _target = IoCManager.Resolve<IRobustRandom>().Pick(traitors).Mind };
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -13,7 +13,9 @@ namespace Content.Server.Objectives.Conditions
|
|||||||
|
|
||||||
public IObjectiveCondition GetAssigned(Mind.Mind mind)
|
public IObjectiveCondition GetAssigned(Mind.Mind mind)
|
||||||
{
|
{
|
||||||
|
//todo shit of a fuck
|
||||||
var entityMgr = IoCManager.Resolve<IEntityManager>();
|
var entityMgr = IoCManager.Resolve<IEntityManager>();
|
||||||
|
|
||||||
var traitors = entityMgr.EntitySysManager.GetEntitySystem<TraitorRuleSystem>().GetOtherTraitorsAliveAndConnected(mind).ToList();
|
var traitors = entityMgr.EntitySysManager.GetEntitySystem<TraitorRuleSystem>().GetOtherTraitorsAliveAndConnected(mind).ToList();
|
||||||
List<Traitor.TraitorRole> removeList = new();
|
List<Traitor.TraitorRole> removeList = new();
|
||||||
|
|
||||||
@@ -23,7 +25,7 @@ namespace Content.Server.Objectives.Conditions
|
|||||||
{
|
{
|
||||||
foreach (var condition in objective.Conditions)
|
foreach (var condition in objective.Conditions)
|
||||||
{
|
{
|
||||||
if (condition.GetType() == typeof(RandomTraitorProgressCondition))
|
if (condition is RandomTraitorProgressCondition)
|
||||||
{
|
{
|
||||||
removeList.Add(traitor);
|
removeList.Add(traitor);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -11,7 +11,7 @@ namespace Content.Server.Objectives.Requirements
|
|||||||
|
|
||||||
public bool CanBeAssigned(Mind.Mind mind)
|
public bool CanBeAssigned(Mind.Mind mind)
|
||||||
{
|
{
|
||||||
return EntitySystem.Get<TraitorRuleSystem>().TotalTraitors >= _requiredTraitors;
|
return EntitySystem.Get<TraitorRuleSystem>().GetOtherTraitorsAliveAndConnected(mind).Count >= _requiredTraitors;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,4 +1,3 @@
|
|||||||
using Content.Server.GameTicking.Rules;
|
|
||||||
using Robust.Shared.Prototypes;
|
using Robust.Shared.Prototypes;
|
||||||
using Robust.Shared.Serialization.TypeSerializers.Implementations.Custom.Prototype.List;
|
using Robust.Shared.Serialization.TypeSerializers.Implementations.Custom.Prototype.List;
|
||||||
|
|
||||||
@@ -9,11 +8,11 @@ namespace Content.Server.Spawners.Components
|
|||||||
public class ConditionalSpawnerComponent : Component
|
public class ConditionalSpawnerComponent : Component
|
||||||
{
|
{
|
||||||
[ViewVariables(VVAccess.ReadWrite)]
|
[ViewVariables(VVAccess.ReadWrite)]
|
||||||
[DataField("prototypes", customTypeSerializer:typeof(PrototypeIdListSerializer<EntityPrototype>))]
|
[DataField("prototypes", customTypeSerializer: typeof(PrototypeIdListSerializer<EntityPrototype>))]
|
||||||
public List<string> Prototypes { get; set; } = new();
|
public List<string> Prototypes { get; set; } = new();
|
||||||
|
|
||||||
[ViewVariables(VVAccess.ReadWrite)]
|
[ViewVariables(VVAccess.ReadWrite)]
|
||||||
[DataField("gameRules", customTypeSerializer:typeof(PrototypeIdListSerializer<GameRulePrototype>))]
|
[DataField("gameRules", customTypeSerializer: typeof(PrototypeIdListSerializer<EntityPrototype>))]
|
||||||
public readonly List<string> GameRules = new();
|
public readonly List<string> GameRules = new();
|
||||||
|
|
||||||
[ViewVariables(VVAccess.ReadWrite)]
|
[ViewVariables(VVAccess.ReadWrite)]
|
||||||
|
|||||||
@@ -1,4 +1,5 @@
|
|||||||
using Content.Server.GameTicking;
|
using Content.Server.GameTicking;
|
||||||
|
using Content.Server.GameTicking.Rules.Components;
|
||||||
using Content.Server.Spawners.Components;
|
using Content.Server.Spawners.Components;
|
||||||
using JetBrains.Annotations;
|
using JetBrains.Annotations;
|
||||||
using Robust.Shared.Random;
|
using Robust.Shared.Random;
|
||||||
@@ -22,65 +23,67 @@ namespace Content.Server.Spawners.EntitySystems
|
|||||||
|
|
||||||
private void OnCondSpawnMapInit(EntityUid uid, ConditionalSpawnerComponent component, MapInitEvent args)
|
private void OnCondSpawnMapInit(EntityUid uid, ConditionalSpawnerComponent component, MapInitEvent args)
|
||||||
{
|
{
|
||||||
TrySpawn(component);
|
TrySpawn(uid, component);
|
||||||
}
|
}
|
||||||
|
|
||||||
private void OnRandSpawnMapInit(EntityUid uid, RandomSpawnerComponent component, MapInitEvent args)
|
private void OnRandSpawnMapInit(EntityUid uid, RandomSpawnerComponent component, MapInitEvent args)
|
||||||
{
|
{
|
||||||
Spawn(component);
|
Spawn(uid, component);
|
||||||
EntityManager.QueueDeleteEntity(uid);
|
QueueDel(uid);
|
||||||
}
|
}
|
||||||
|
|
||||||
private void OnRuleStarted(GameRuleStartedEvent args)
|
private void OnRuleStarted(ref GameRuleStartedEvent args)
|
||||||
{
|
{
|
||||||
foreach (var spawner in EntityManager.EntityQuery<ConditionalSpawnerComponent>())
|
var query = EntityQueryEnumerator<ConditionalSpawnerComponent>();
|
||||||
|
while (query.MoveNext(out var uid, out var spawner))
|
||||||
{
|
{
|
||||||
RuleStarted(spawner, args);
|
RuleStarted(uid, spawner, args);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public void RuleStarted(ConditionalSpawnerComponent component, GameRuleStartedEvent obj)
|
public void RuleStarted(EntityUid uid, ConditionalSpawnerComponent component, GameRuleStartedEvent obj)
|
||||||
{
|
{
|
||||||
if(component.GameRules.Contains(obj.Rule.ID))
|
if (component.GameRules.Contains(obj.RuleId))
|
||||||
Spawn(component);
|
Spawn(uid, component);
|
||||||
}
|
}
|
||||||
|
|
||||||
private void TrySpawn(ConditionalSpawnerComponent component)
|
private void TrySpawn(EntityUid uid, ConditionalSpawnerComponent component)
|
||||||
{
|
{
|
||||||
if (component.GameRules.Count == 0)
|
if (component.GameRules.Count == 0)
|
||||||
{
|
{
|
||||||
Spawn(component);
|
Spawn(uid, component);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
foreach (var rule in component.GameRules)
|
foreach (var rule in component.GameRules)
|
||||||
{
|
{
|
||||||
if (!_ticker.IsGameRuleStarted(rule)) continue;
|
if (!_ticker.IsGameRuleActive(rule))
|
||||||
Spawn(component);
|
continue;
|
||||||
|
Spawn(uid, component);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private void Spawn(ConditionalSpawnerComponent component)
|
private void Spawn(EntityUid uid, ConditionalSpawnerComponent component)
|
||||||
{
|
{
|
||||||
if (component.Chance != 1.0f && !_robustRandom.Prob(component.Chance))
|
if (component.Chance != 1.0f && !_robustRandom.Prob(component.Chance))
|
||||||
return;
|
return;
|
||||||
|
|
||||||
if (component.Prototypes.Count == 0)
|
if (component.Prototypes.Count == 0)
|
||||||
{
|
{
|
||||||
Logger.Warning($"Prototype list in ConditionalSpawnComponent is empty! Entity: {component.Owner}");
|
Logger.Warning($"Prototype list in ConditionalSpawnComponent is empty! Entity: {ToPrettyString(uid)}");
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!Deleted(component.Owner))
|
if (!Deleted(uid))
|
||||||
EntityManager.SpawnEntity(_robustRandom.Pick(component.Prototypes), Transform(component.Owner).Coordinates);
|
EntityManager.SpawnEntity(_robustRandom.Pick(component.Prototypes), Transform(uid).Coordinates);
|
||||||
}
|
}
|
||||||
|
|
||||||
private void Spawn(RandomSpawnerComponent component)
|
private void Spawn(EntityUid uid, RandomSpawnerComponent component)
|
||||||
{
|
{
|
||||||
if (component.RarePrototypes.Count > 0 && (component.RareChance == 1.0f || _robustRandom.Prob(component.RareChance)))
|
if (component.RarePrototypes.Count > 0 && (component.RareChance == 1.0f || _robustRandom.Prob(component.RareChance)))
|
||||||
{
|
{
|
||||||
EntityManager.SpawnEntity(_robustRandom.Pick(component.RarePrototypes), Transform(component.Owner).Coordinates);
|
EntityManager.SpawnEntity(_robustRandom.Pick(component.RarePrototypes), Transform(uid).Coordinates);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -89,17 +92,18 @@ namespace Content.Server.Spawners.EntitySystems
|
|||||||
|
|
||||||
if (component.Prototypes.Count == 0)
|
if (component.Prototypes.Count == 0)
|
||||||
{
|
{
|
||||||
Logger.Warning($"Prototype list in RandomSpawnerComponent is empty! Entity: {component.Owner}");
|
Logger.Warning($"Prototype list in RandomSpawnerComponent is empty! Entity: {ToPrettyString(uid)}");
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (Deleted(component.Owner)) return;
|
if (Deleted(uid))
|
||||||
|
return;
|
||||||
|
|
||||||
var offset = component.Offset;
|
var offset = component.Offset;
|
||||||
var xOffset = _robustRandom.NextFloat(-offset, offset);
|
var xOffset = _robustRandom.NextFloat(-offset, offset);
|
||||||
var yOffset = _robustRandom.NextFloat(-offset, offset);
|
var yOffset = _robustRandom.NextFloat(-offset, offset);
|
||||||
|
|
||||||
var coordinates = Transform(component.Owner).Coordinates.Offset(new Vector2(xOffset, yOffset));
|
var coordinates = Transform(uid).Coordinates.Offset(new Vector2(xOffset, yOffset));
|
||||||
|
|
||||||
EntityManager.SpawnEntity(_robustRandom.Pick(component.Prototypes), coordinates);
|
EntityManager.SpawnEntity(_robustRandom.Pick(component.Prototypes), coordinates);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,12 +1,7 @@
|
|||||||
using System.Linq;
|
|
||||||
using Content.Server.GameTicking.Rules;
|
using Content.Server.GameTicking.Rules;
|
||||||
using Content.Server.GameTicking.Rules.Configurations;
|
using Content.Server.GameTicking.Rules.Components;
|
||||||
using Content.Shared.CCVar;
|
using Content.Server.StationEvents.Components;
|
||||||
using Content.Shared.GameTicking;
|
|
||||||
using JetBrains.Annotations;
|
using JetBrains.Annotations;
|
||||||
using Robust.Server.Player;
|
|
||||||
using Robust.Shared.Configuration;
|
|
||||||
using Robust.Shared.Prototypes;
|
|
||||||
using Robust.Shared.Random;
|
using Robust.Shared.Random;
|
||||||
|
|
||||||
namespace Content.Server.StationEvents
|
namespace Content.Server.StationEvents
|
||||||
@@ -16,53 +11,49 @@ namespace Content.Server.StationEvents
|
|||||||
/// game presets use.
|
/// game presets use.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
[UsedImplicitly]
|
[UsedImplicitly]
|
||||||
public sealed class BasicStationEventSchedulerSystem : GameRuleSystem
|
public sealed class BasicStationEventSchedulerSystem : GameRuleSystem<BasicStationEventSchedulerComponent>
|
||||||
{
|
{
|
||||||
public override string Prototype => "BasicStationEventScheduler";
|
|
||||||
|
|
||||||
[Dependency] private readonly IRobustRandom _random = default!;
|
[Dependency] private readonly IRobustRandom _random = default!;
|
||||||
[Dependency] private readonly EventManagerSystem _event = default!;
|
[Dependency] private readonly EventManagerSystem _event = default!;
|
||||||
|
|
||||||
private const float MinimumTimeUntilFirstEvent = 300;
|
protected override void Ended(EntityUid uid, BasicStationEventSchedulerComponent component, GameRuleComponent gameRule,
|
||||||
|
GameRuleEndedEvent args)
|
||||||
/// <summary>
|
|
||||||
/// How long until the next check for an event runs
|
|
||||||
/// </summary>
|
|
||||||
/// Default value is how long until first event is allowed
|
|
||||||
[ViewVariables(VVAccess.ReadWrite)]
|
|
||||||
private float _timeUntilNextEvent = MinimumTimeUntilFirstEvent;
|
|
||||||
|
|
||||||
public override void Started() { }
|
|
||||||
|
|
||||||
public override void Ended()
|
|
||||||
{
|
{
|
||||||
_timeUntilNextEvent = MinimumTimeUntilFirstEvent;
|
component.TimeUntilNextEvent = BasicStationEventSchedulerComponent.MinimumTimeUntilFirstEvent;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
public override void Update(float frameTime)
|
public override void Update(float frameTime)
|
||||||
{
|
{
|
||||||
base.Update(frameTime);
|
base.Update(frameTime);
|
||||||
|
|
||||||
if (!RuleStarted || !_event.EventsEnabled)
|
if (!_event.EventsEnabled)
|
||||||
return;
|
return;
|
||||||
|
|
||||||
if (_timeUntilNextEvent > 0)
|
var query = EntityQueryEnumerator<BasicStationEventSchedulerComponent, GameRuleComponent>();
|
||||||
|
while (query.MoveNext(out var uid, out var eventScheduler, out var gameRule))
|
||||||
{
|
{
|
||||||
_timeUntilNextEvent -= frameTime;
|
if (!GameTicker.IsGameRuleActive(uid, gameRule))
|
||||||
return;
|
continue;
|
||||||
}
|
|
||||||
|
|
||||||
_event.RunRandomEvent();
|
if (eventScheduler.TimeUntilNextEvent > 0)
|
||||||
ResetTimer();
|
{
|
||||||
|
eventScheduler.TimeUntilNextEvent -= frameTime;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
_event.RunRandomEvent();
|
||||||
|
ResetTimer(eventScheduler);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Reset the event timer once the event is done.
|
/// Reset the event timer once the event is done.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
private void ResetTimer()
|
private void ResetTimer(BasicStationEventSchedulerComponent component)
|
||||||
{
|
{
|
||||||
// 5 - 25 minutes. TG does 3-10 but that's pretty frequent
|
// 5 - 25 minutes. TG does 3-10 but that's pretty frequent
|
||||||
_timeUntilNextEvent = _random.Next(300, 1500);
|
component.TimeUntilNextEvent = _random.Next(300, 1500);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,15 @@
|
|||||||
|
using Content.Server.StationEvents.Events;
|
||||||
|
using Robust.Shared.Prototypes;
|
||||||
|
using Robust.Shared.Serialization.TypeSerializers.Implementations.Custom.Prototype;
|
||||||
|
|
||||||
|
namespace Content.Server.StationEvents.Components;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Used an event that spawns an anomaly somewhere random on the map.
|
||||||
|
/// </summary>
|
||||||
|
[RegisterComponent, Access(typeof(AnomalySpawnRule))]
|
||||||
|
public sealed class AnomalySpawnRuleComponent : Component
|
||||||
|
{
|
||||||
|
[DataField("anomalySpawnerPrototype", customTypeSerializer: typeof(PrototypeIdSerializer<EntityPrototype>))]
|
||||||
|
public string AnomalySpawnerPrototype = "RandomAnomalySpawner";
|
||||||
|
}
|
||||||
@@ -0,0 +1,14 @@
|
|||||||
|
namespace Content.Server.StationEvents.Components;
|
||||||
|
|
||||||
|
[RegisterComponent, Access(typeof(BasicStationEventSchedulerSystem))]
|
||||||
|
public sealed class BasicStationEventSchedulerComponent : Component
|
||||||
|
{
|
||||||
|
public const float MinimumTimeUntilFirstEvent = 300;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// How long until the next check for an event runs
|
||||||
|
/// </summary>
|
||||||
|
/// Default value is how long until first event is allowed
|
||||||
|
[ViewVariables(VVAccess.ReadWrite)]
|
||||||
|
public float TimeUntilNextEvent = MinimumTimeUntilFirstEvent;
|
||||||
|
}
|
||||||
@@ -0,0 +1,31 @@
|
|||||||
|
using Content.Server.StationEvents.Events;
|
||||||
|
using Robust.Shared.Prototypes;
|
||||||
|
using Robust.Shared.Serialization.TypeSerializers.Implementations.Custom.Prototype;
|
||||||
|
|
||||||
|
namespace Content.Server.StationEvents.Components;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// This is used for an event that spawns an artifact
|
||||||
|
/// somewhere random on the station.
|
||||||
|
/// </summary>
|
||||||
|
[RegisterComponent, Access(typeof(BluespaceArtifactRule))]
|
||||||
|
public sealed class BluespaceArtifactRuleComponent : Component
|
||||||
|
{
|
||||||
|
[DataField("artifactSpawnerPrototype", customTypeSerializer: typeof(PrototypeIdSerializer<EntityPrototype>))]
|
||||||
|
public string ArtifactSpawnerPrototype = "RandomArtifactSpawner";
|
||||||
|
|
||||||
|
[DataField("artifactFlashPrototype", customTypeSerializer: typeof(PrototypeIdSerializer<EntityPrototype>))]
|
||||||
|
public string ArtifactFlashPrototype = "EffectFlashBluespace";
|
||||||
|
|
||||||
|
[DataField("possibleSightings")]
|
||||||
|
public List<string> PossibleSighting = new()
|
||||||
|
{
|
||||||
|
"bluespace-artifact-sighting-1",
|
||||||
|
"bluespace-artifact-sighting-2",
|
||||||
|
"bluespace-artifact-sighting-3",
|
||||||
|
"bluespace-artifact-sighting-4",
|
||||||
|
"bluespace-artifact-sighting-5",
|
||||||
|
"bluespace-artifact-sighting-6",
|
||||||
|
"bluespace-artifact-sighting-7"
|
||||||
|
};
|
||||||
|
}
|
||||||
@@ -0,0 +1,9 @@
|
|||||||
|
using Content.Server.StationEvents.Events;
|
||||||
|
|
||||||
|
namespace Content.Server.StationEvents.Components;
|
||||||
|
|
||||||
|
[RegisterComponent, Access(typeof(BluespaceLockerRule))]
|
||||||
|
public sealed class BluespaceLockerRuleComponent : Component
|
||||||
|
{
|
||||||
|
|
||||||
|
}
|
||||||
@@ -0,0 +1,9 @@
|
|||||||
|
using Content.Server.StationEvents.Events;
|
||||||
|
|
||||||
|
namespace Content.Server.StationEvents.Components;
|
||||||
|
|
||||||
|
[RegisterComponent, Access(typeof(BreakerFlipRule))]
|
||||||
|
public sealed class BreakerFlipRuleComponent : Component
|
||||||
|
{
|
||||||
|
|
||||||
|
}
|
||||||
@@ -0,0 +1,9 @@
|
|||||||
|
using Content.Server.StationEvents.Events;
|
||||||
|
|
||||||
|
namespace Content.Server.StationEvents.Components;
|
||||||
|
|
||||||
|
[RegisterComponent, Access(typeof(BureaucraticErrorRule))]
|
||||||
|
public sealed class BureaucraticErrorRuleComponent : Component
|
||||||
|
{
|
||||||
|
|
||||||
|
}
|
||||||
@@ -0,0 +1,25 @@
|
|||||||
|
using Content.Server.StationEvents.Events;
|
||||||
|
|
||||||
|
namespace Content.Server.StationEvents.Components;
|
||||||
|
|
||||||
|
[RegisterComponent, Access(typeof(DiseaseOutbreakRule))]
|
||||||
|
public sealed class DiseaseOutbreakRuleComponent : Component
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Disease prototypes I decided were not too deadly for a random event
|
||||||
|
/// </summary>
|
||||||
|
/// <remarks>
|
||||||
|
/// Fire name
|
||||||
|
/// </remarks>
|
||||||
|
[DataField("notTooSeriousDiseases")]
|
||||||
|
public readonly IReadOnlyList<string> NotTooSeriousDiseases = new[]
|
||||||
|
{
|
||||||
|
"SpaceCold",
|
||||||
|
"VanAusdallsRobovirus",
|
||||||
|
"VentCough",
|
||||||
|
"AMIV",
|
||||||
|
"SpaceFlu",
|
||||||
|
"BirdFlew",
|
||||||
|
"TongueTwister"
|
||||||
|
};
|
||||||
|
}
|
||||||
@@ -0,0 +1,9 @@
|
|||||||
|
using Content.Server.StationEvents.Events;
|
||||||
|
|
||||||
|
namespace Content.Server.StationEvents.Components;
|
||||||
|
|
||||||
|
[RegisterComponent, Access(typeof(FalseAlarmRule))]
|
||||||
|
public sealed class FalseAlarmRuleComponent : Component
|
||||||
|
{
|
||||||
|
|
||||||
|
}
|
||||||
@@ -0,0 +1,46 @@
|
|||||||
|
using Content.Server.StationEvents.Events;
|
||||||
|
using Content.Shared.Atmos;
|
||||||
|
using Robust.Shared.Map;
|
||||||
|
|
||||||
|
namespace Content.Server.StationEvents.Components;
|
||||||
|
|
||||||
|
[RegisterComponent, Access(typeof(GasLeakRule))]
|
||||||
|
public sealed class GasLeakRuleComponent : Component
|
||||||
|
{
|
||||||
|
public readonly Gas[] LeakableGases =
|
||||||
|
{
|
||||||
|
Gas.Miasma,
|
||||||
|
Gas.Plasma,
|
||||||
|
Gas.Tritium,
|
||||||
|
Gas.Frezon,
|
||||||
|
};
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Running cooldown of how much time until another leak.
|
||||||
|
/// </summary>
|
||||||
|
public float TimeUntilLeak;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// How long between more gas being added to the tile.
|
||||||
|
/// </summary>
|
||||||
|
public float LeakCooldown = 1.0f;
|
||||||
|
|
||||||
|
// Event variables
|
||||||
|
public EntityUid TargetStation;
|
||||||
|
public EntityUid TargetGrid;
|
||||||
|
public Vector2i TargetTile;
|
||||||
|
public EntityCoordinates TargetCoords;
|
||||||
|
public bool FoundTile;
|
||||||
|
public Gas LeakGas;
|
||||||
|
public float MolesPerSecond;
|
||||||
|
public readonly int MinimumMolesPerSecond = 20;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Don't want to make it too fast to give people time to flee.
|
||||||
|
/// </summary>
|
||||||
|
public int MaximumMolesPerSecond = 50;
|
||||||
|
|
||||||
|
public int MinimumGas = 250;
|
||||||
|
public int MaximumGas = 1000;
|
||||||
|
public float SparkChance = 0.05f;
|
||||||
|
}
|
||||||
@@ -0,0 +1,9 @@
|
|||||||
|
using Content.Server.StationEvents.Events;
|
||||||
|
|
||||||
|
namespace Content.Server.StationEvents.Components;
|
||||||
|
|
||||||
|
[RegisterComponent, Access(typeof(KudzuGrowthRule))]
|
||||||
|
public sealed class KudzuGrowthRuleComponent : Component
|
||||||
|
{
|
||||||
|
|
||||||
|
}
|
||||||
@@ -0,0 +1,18 @@
|
|||||||
|
using Content.Server.StationEvents.Events;
|
||||||
|
using Robust.Shared.Prototypes;
|
||||||
|
using Robust.Shared.Serialization.TypeSerializers.Implementations.Custom.Prototype;
|
||||||
|
|
||||||
|
namespace Content.Server.StationEvents.Components;
|
||||||
|
|
||||||
|
[RegisterComponent, Access(typeof(LoneOpsSpawnRule))]
|
||||||
|
public sealed class LoneOpsSpawnRuleComponent : Component
|
||||||
|
{
|
||||||
|
[DataField("loneOpsShuttlePath")]
|
||||||
|
public string LoneOpsShuttlePath = "Maps/Shuttles/striker.yml";
|
||||||
|
|
||||||
|
[DataField("gameRuleProto", customTypeSerializer: typeof(PrototypeIdSerializer<EntityPrototype>))]
|
||||||
|
public string GameRuleProto = "Nukeops";
|
||||||
|
|
||||||
|
[DataField("additionalRule")]
|
||||||
|
public EntityUid? AdditionalRule;
|
||||||
|
}
|
||||||
@@ -0,0 +1,25 @@
|
|||||||
|
using Content.Server.StationEvents.Events;
|
||||||
|
|
||||||
|
namespace Content.Server.StationEvents.Components;
|
||||||
|
|
||||||
|
[RegisterComponent, Access(typeof(MeteorSwarmRule))]
|
||||||
|
public sealed class MeteorSwarmRuleComponent : Component
|
||||||
|
{
|
||||||
|
public float _cooldown;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// We'll send a specific amount of waves of meteors towards the station per ending rather than using a timer.
|
||||||
|
/// </summary>
|
||||||
|
public int _waveCounter;
|
||||||
|
|
||||||
|
public int MinimumWaves = 3;
|
||||||
|
public int MaximumWaves = 8;
|
||||||
|
|
||||||
|
public float MinimumCooldown = 10f;
|
||||||
|
public float MaximumCooldown = 60f;
|
||||||
|
|
||||||
|
public int MeteorsPerWave = 5;
|
||||||
|
public float MeteorVelocity = 10f;
|
||||||
|
public float MaxAngularVelocity = 0.25f;
|
||||||
|
public float MinAngularVelocity = -0.25f;
|
||||||
|
}
|
||||||
@@ -0,0 +1,16 @@
|
|||||||
|
using Content.Server.StationEvents.Events;
|
||||||
|
|
||||||
|
namespace Content.Server.StationEvents.Components;
|
||||||
|
|
||||||
|
[RegisterComponent, Access(typeof(MouseMigrationRule))]
|
||||||
|
public sealed class MouseMigrationRuleComponent : Component
|
||||||
|
{
|
||||||
|
[DataField("spawnedPrototypeChoices")]
|
||||||
|
public List<string> SpawnedPrototypeChoices = new() //we double up for that ez fake probability
|
||||||
|
{
|
||||||
|
"MobMouse",
|
||||||
|
"MobMouse1",
|
||||||
|
"MobMouse2",
|
||||||
|
"MobRatServant"
|
||||||
|
};
|
||||||
|
}
|
||||||
@@ -0,0 +1,19 @@
|
|||||||
|
using System.Threading;
|
||||||
|
using Content.Server.StationEvents.Events;
|
||||||
|
|
||||||
|
namespace Content.Server.StationEvents.Components;
|
||||||
|
|
||||||
|
[RegisterComponent, Access(typeof(PowerGridCheckRule))]
|
||||||
|
public sealed class PowerGridCheckRuleComponent : Component
|
||||||
|
{
|
||||||
|
public CancellationTokenSource? AnnounceCancelToken;
|
||||||
|
|
||||||
|
public readonly List<EntityUid> Powered = new();
|
||||||
|
public readonly List<EntityUid> Unpowered = new();
|
||||||
|
|
||||||
|
public float SecondsUntilOff = 30.0f;
|
||||||
|
|
||||||
|
public int NumberPerSecond = 0;
|
||||||
|
public float UpdateRate => 1.0f / NumberPerSecond;
|
||||||
|
public float FrameTimeAccumulator = 0.0f;
|
||||||
|
}
|
||||||
@@ -0,0 +1,17 @@
|
|||||||
|
namespace Content.Server.StationEvents.Components;
|
||||||
|
|
||||||
|
[RegisterComponent, Access(typeof(RampingStationEventSchedulerSystem))]
|
||||||
|
public sealed class RampingStationEventSchedulerComponent : Component
|
||||||
|
{
|
||||||
|
[DataField("endTime"), ViewVariables(VVAccess.ReadWrite)]
|
||||||
|
public float EndTime;
|
||||||
|
|
||||||
|
[DataField("maxChaos"), ViewVariables(VVAccess.ReadWrite)]
|
||||||
|
public float MaxChaos;
|
||||||
|
|
||||||
|
[DataField("startingChaos"), ViewVariables(VVAccess.ReadWrite)]
|
||||||
|
public float StartingChaos;
|
||||||
|
|
||||||
|
[DataField("timeUntilNextEvent"), ViewVariables(VVAccess.ReadWrite)]
|
||||||
|
public float TimeUntilNextEvent;
|
||||||
|
}
|
||||||
@@ -0,0 +1,9 @@
|
|||||||
|
using Content.Server.StationEvents.Events;
|
||||||
|
|
||||||
|
namespace Content.Server.StationEvents.Components;
|
||||||
|
|
||||||
|
[RegisterComponent, Access(typeof(RandomSentienceRule))]
|
||||||
|
public sealed class RandomSentienceRuleComponent : Component
|
||||||
|
{
|
||||||
|
|
||||||
|
}
|
||||||
@@ -0,0 +1,10 @@
|
|||||||
|
using Content.Server.StationEvents.Events;
|
||||||
|
|
||||||
|
namespace Content.Server.StationEvents.Components;
|
||||||
|
|
||||||
|
[RegisterComponent, Access(typeof(RevenantSpawnRule))]
|
||||||
|
public sealed class RevenantSpawnRuleComponent : Component
|
||||||
|
{
|
||||||
|
[DataField("revenantPrototype")]
|
||||||
|
public string RevenantPrototype = "MobRevenant";
|
||||||
|
}
|
||||||
@@ -1,6 +1,8 @@
|
|||||||
namespace Content.Server.StationEvents.Components;
|
using Content.Server.StationEvents.Events;
|
||||||
|
|
||||||
[RegisterComponent]
|
namespace Content.Server.StationEvents.Components;
|
||||||
|
|
||||||
|
[RegisterComponent, Access(typeof(RandomSentienceRule))]
|
||||||
public sealed class SentienceTargetComponent : Component
|
public sealed class SentienceTargetComponent : Component
|
||||||
{
|
{
|
||||||
[DataField("flavorKind", required: true)]
|
[DataField("flavorKind", required: true)]
|
||||||
|
|||||||
@@ -1,25 +1,15 @@
|
|||||||
|
using Content.Server.StationEvents.Events;
|
||||||
using Content.Shared.Radio;
|
using Content.Shared.Radio;
|
||||||
using Robust.Shared.Serialization.TypeSerializers.Implementations.Custom.Prototype.Set;
|
using Robust.Shared.Serialization.TypeSerializers.Implementations.Custom.Prototype.Set;
|
||||||
|
|
||||||
namespace Content.Server.GameTicking.Rules.Configurations;
|
namespace Content.Server.StationEvents.Components;
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Solar Flare event specific configuration
|
/// Solar Flare event specific configuration
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public sealed class SolarFlareEventRuleConfiguration : StationEventRuleConfiguration
|
[RegisterComponent, Access(typeof(SolarFlareRule))]
|
||||||
|
public sealed class SolarFlareRuleComponent : Component
|
||||||
{
|
{
|
||||||
/// <summary>
|
|
||||||
/// In seconds, most early moment event can end
|
|
||||||
/// </summary>
|
|
||||||
[DataField("minEndAfter")]
|
|
||||||
public int MinEndAfter;
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// In seconds, most late moment event can end
|
|
||||||
/// </summary>
|
|
||||||
[DataField("maxEndAfter")]
|
|
||||||
public int MaxEndAfter;
|
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// If true, only headsets affected, but e.g. handheld radio will still work
|
/// If true, only headsets affected, but e.g. handheld radio will still work
|
||||||
/// </summary>
|
/// </summary>
|
||||||
@@ -43,4 +33,4 @@ public sealed class SolarFlareEventRuleConfiguration : StationEventRuleConfigura
|
|||||||
/// </summary>
|
/// </summary>
|
||||||
[DataField("doorToggleChancePerSecond")]
|
[DataField("doorToggleChancePerSecond")]
|
||||||
public float DoorToggleChancePerSecond;
|
public float DoorToggleChancePerSecond;
|
||||||
}
|
}
|
||||||
@@ -0,0 +1,9 @@
|
|||||||
|
using Content.Server.StationEvents.Events;
|
||||||
|
|
||||||
|
namespace Content.Server.StationEvents.Components;
|
||||||
|
|
||||||
|
[RegisterComponent, Access(typeof(SpiderSpawnRule))]
|
||||||
|
public sealed class SpiderSpawnRuleComponent : Component
|
||||||
|
{
|
||||||
|
|
||||||
|
}
|
||||||
@@ -1,19 +1,14 @@
|
|||||||
using JetBrains.Annotations;
|
using Robust.Shared.Audio;
|
||||||
using Robust.Shared.Audio;
|
using Robust.Shared.Serialization.TypeSerializers.Implementations.Custom;
|
||||||
|
|
||||||
namespace Content.Server.GameTicking.Rules.Configurations;
|
namespace Content.Server.StationEvents.Components;
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Defines a configuration for a given station event game rule, since all station events are just
|
/// Defines basic data for a station event
|
||||||
/// game rules.
|
|
||||||
/// </summary>
|
/// </summary>
|
||||||
[UsedImplicitly]
|
[RegisterComponent]
|
||||||
public class StationEventRuleConfiguration : GameRuleConfiguration
|
public sealed class StationEventComponent : Component
|
||||||
{
|
{
|
||||||
[DataField("id", required: true)]
|
|
||||||
private string _id = default!;
|
|
||||||
public override string Id => _id;
|
|
||||||
|
|
||||||
public const float WeightVeryLow = 0.0f;
|
public const float WeightVeryLow = 0.0f;
|
||||||
public const float WeightLow = 5.0f;
|
public const float WeightLow = 5.0f;
|
||||||
public const float WeightNormal = 10.0f;
|
public const float WeightNormal = 10.0f;
|
||||||
@@ -48,16 +43,22 @@ public class StationEventRuleConfiguration : GameRuleConfiguration
|
|||||||
public int ReoccurrenceDelay = 30;
|
public int ReoccurrenceDelay = 30;
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// When in the lifetime to start the event.
|
/// How long after being added does the event start
|
||||||
/// </summary>
|
/// </summary>
|
||||||
[DataField("startAfter")]
|
[DataField("startDelay")]
|
||||||
public float StartAfter;
|
public TimeSpan StartDelay = TimeSpan.Zero;
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// When in the lifetime to end the event..
|
/// How long the event lasts.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
[DataField("endAfter")]
|
[DataField("duration")]
|
||||||
public float EndAfter = float.MaxValue;
|
public TimeSpan Duration = TimeSpan.FromSeconds(1);
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// The max amount of time the event lasts.
|
||||||
|
/// </summary>
|
||||||
|
[DataField("maxDuration")]
|
||||||
|
public TimeSpan? MaxDuration;
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// How many players need to be present on station for the event to run
|
/// How many players need to be present on station for the event to run
|
||||||
@@ -73,4 +74,16 @@ public class StationEventRuleConfiguration : GameRuleConfiguration
|
|||||||
/// </summary>
|
/// </summary>
|
||||||
[DataField("maxOccurrences")]
|
[DataField("maxOccurrences")]
|
||||||
public int? MaxOccurrences;
|
public int? MaxOccurrences;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// When the station event starts.
|
||||||
|
/// </summary>
|
||||||
|
[DataField("startTime", customTypeSerializer: typeof(TimeOffsetSerializer))]
|
||||||
|
public TimeSpan StartTime;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// When the station event starts.
|
||||||
|
/// </summary>
|
||||||
|
[DataField("endTime", customTypeSerializer: typeof(TimeOffsetSerializer))]
|
||||||
|
public TimeSpan EndTime;
|
||||||
}
|
}
|
||||||
@@ -0,0 +1,14 @@
|
|||||||
|
using Content.Server.StationEvents.Events;
|
||||||
|
|
||||||
|
namespace Content.Server.StationEvents.Components;
|
||||||
|
|
||||||
|
[RegisterComponent, Access(typeof(VentClogRule))]
|
||||||
|
public sealed class VentClogRuleComponent : Component
|
||||||
|
{
|
||||||
|
[DataField("safeishVentChemicals")]
|
||||||
|
public readonly IReadOnlyList<string> SafeishVentChemicals = new[]
|
||||||
|
{
|
||||||
|
"Water", "Blood", "Slime", "SpaceDrugs", "SpaceCleaner", "Nutriment", "Sugar", "SpaceLube", "Ephedrine", "Ale", "Beer"
|
||||||
|
};
|
||||||
|
|
||||||
|
}
|
||||||
@@ -1,6 +1,8 @@
|
|||||||
namespace Content.Server.StationEvents.Components;
|
using Content.Server.StationEvents.Events;
|
||||||
|
|
||||||
[RegisterComponent]
|
namespace Content.Server.StationEvents.Components;
|
||||||
|
|
||||||
|
[RegisterComponent, Access(typeof(VentClogRule))]
|
||||||
public sealed class VentCritterSpawnLocationComponent : Component
|
public sealed class VentCritterSpawnLocationComponent : Component
|
||||||
{
|
{
|
||||||
|
|
||||||
|
|||||||
@@ -0,0 +1,15 @@
|
|||||||
|
using Content.Server.StationEvents.Events;
|
||||||
|
|
||||||
|
namespace Content.Server.StationEvents.Components;
|
||||||
|
|
||||||
|
[RegisterComponent, Access(typeof(VentCrittersRule))]
|
||||||
|
public sealed class VentCrittersRuleComponent : Component
|
||||||
|
{
|
||||||
|
[DataField("spawnedPrototypeChoices")]
|
||||||
|
public List<string> SpawnedPrototypeChoices = new()
|
||||||
|
{
|
||||||
|
"MobMouse",
|
||||||
|
"MobMouse1",
|
||||||
|
"MobMouse2"
|
||||||
|
};
|
||||||
|
}
|
||||||
@@ -1,9 +1,7 @@
|
|||||||
using System.Linq;
|
using System.Linq;
|
||||||
using Content.Server.GameTicking;
|
using Content.Server.GameTicking;
|
||||||
using Content.Server.GameTicking.Rules;
|
using Content.Server.StationEvents.Components;
|
||||||
using Content.Server.GameTicking.Rules.Configurations;
|
|
||||||
using Content.Shared.CCVar;
|
using Content.Shared.CCVar;
|
||||||
using Content.Shared.GameTicking;
|
|
||||||
using Robust.Server.Player;
|
using Robust.Server.Player;
|
||||||
using Robust.Shared.Configuration;
|
using Robust.Shared.Configuration;
|
||||||
using Robust.Shared.Prototypes;
|
using Robust.Shared.Prototypes;
|
||||||
@@ -31,6 +29,14 @@ public sealed class EventManagerSystem : EntitySystem
|
|||||||
_sawmill = Logger.GetSawmill("events");
|
_sawmill = Logger.GetSawmill("events");
|
||||||
|
|
||||||
_configurationManager.OnValueChanged(CCVars.EventsEnabled, SetEnabled, true);
|
_configurationManager.OnValueChanged(CCVars.EventsEnabled, SetEnabled, true);
|
||||||
|
|
||||||
|
SubscribeLocalEvent<StationEventComponent, EntityUnpausedEvent>(OnUnpaused);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void OnUnpaused(EntityUid uid, StationEventComponent component, ref EntityUnpausedEvent args)
|
||||||
|
{
|
||||||
|
component.StartTime += args.PausedTime;
|
||||||
|
component.EndTime += args.PausedTime;
|
||||||
}
|
}
|
||||||
|
|
||||||
public override void Shutdown()
|
public override void Shutdown()
|
||||||
@@ -46,16 +52,15 @@ public sealed class EventManagerSystem : EntitySystem
|
|||||||
{
|
{
|
||||||
var randomEvent = PickRandomEvent();
|
var randomEvent = PickRandomEvent();
|
||||||
|
|
||||||
if (randomEvent == null
|
if (randomEvent == null)
|
||||||
|| !_prototype.TryIndex<GameRulePrototype>(randomEvent.Id, out var proto))
|
|
||||||
{
|
{
|
||||||
var errStr = Loc.GetString("station-event-system-run-random-event-no-valid-events");
|
var errStr = Loc.GetString("station-event-system-run-random-event-no-valid-events");
|
||||||
_sawmill.Error(errStr);
|
_sawmill.Error(errStr);
|
||||||
return errStr;
|
return errStr;
|
||||||
}
|
}
|
||||||
|
|
||||||
GameTicker.AddGameRule(proto);
|
var ent = GameTicker.AddGameRule(randomEvent);
|
||||||
var str = Loc.GetString("station-event-system-run-event",("eventName", randomEvent.Id));
|
var str = Loc.GetString("station-event-system-run-event",("eventName", ToPrettyString(ent)));
|
||||||
_sawmill.Info(str);
|
_sawmill.Info(str);
|
||||||
return str;
|
return str;
|
||||||
}
|
}
|
||||||
@@ -63,7 +68,7 @@ public sealed class EventManagerSystem : EntitySystem
|
|||||||
/// <summary>
|
/// <summary>
|
||||||
/// Randomly picks a valid event.
|
/// Randomly picks a valid event.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public StationEventRuleConfiguration? PickRandomEvent()
|
public string? PickRandomEvent()
|
||||||
{
|
{
|
||||||
var availableEvents = AvailableEvents();
|
var availableEvents = AvailableEvents();
|
||||||
_sawmill.Info($"Picking from {availableEvents.Count} total available events");
|
_sawmill.Info($"Picking from {availableEvents.Count} total available events");
|
||||||
@@ -74,7 +79,7 @@ public sealed class EventManagerSystem : EntitySystem
|
|||||||
/// Pick a random event from the available events at this time, also considering their weightings.
|
/// Pick a random event from the available events at this time, also considering their weightings.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
/// <returns></returns>
|
/// <returns></returns>
|
||||||
private StationEventRuleConfiguration? FindEvent(List<StationEventRuleConfiguration> availableEvents)
|
private string? FindEvent(Dictionary<EntityPrototype, StationEventComponent> availableEvents)
|
||||||
{
|
{
|
||||||
if (availableEvents.Count == 0)
|
if (availableEvents.Count == 0)
|
||||||
{
|
{
|
||||||
@@ -84,20 +89,20 @@ public sealed class EventManagerSystem : EntitySystem
|
|||||||
|
|
||||||
var sumOfWeights = 0;
|
var sumOfWeights = 0;
|
||||||
|
|
||||||
foreach (var stationEvent in availableEvents)
|
foreach (var stationEvent in availableEvents.Values)
|
||||||
{
|
{
|
||||||
sumOfWeights += (int) stationEvent.Weight;
|
sumOfWeights += (int) stationEvent.Weight;
|
||||||
}
|
}
|
||||||
|
|
||||||
sumOfWeights = _random.Next(sumOfWeights);
|
sumOfWeights = _random.Next(sumOfWeights);
|
||||||
|
|
||||||
foreach (var stationEvent in availableEvents)
|
foreach (var (proto, stationEvent) in availableEvents)
|
||||||
{
|
{
|
||||||
sumOfWeights -= (int) stationEvent.Weight;
|
sumOfWeights -= (int) stationEvent.Weight;
|
||||||
|
|
||||||
if (sumOfWeights <= 0)
|
if (sumOfWeights <= 0)
|
||||||
{
|
{
|
||||||
return stationEvent;
|
return proto.ID;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -110,67 +115,73 @@ public sealed class EventManagerSystem : EntitySystem
|
|||||||
/// </summary>
|
/// </summary>
|
||||||
/// <param name="ignoreEarliestStart"></param>
|
/// <param name="ignoreEarliestStart"></param>
|
||||||
/// <returns></returns>
|
/// <returns></returns>
|
||||||
private List<StationEventRuleConfiguration> AvailableEvents(bool ignoreEarliestStart = false)
|
private Dictionary<EntityPrototype, StationEventComponent> AvailableEvents(bool ignoreEarliestStart = false)
|
||||||
{
|
{
|
||||||
TimeSpan currentTime;
|
|
||||||
var playerCount = _playerManager.PlayerCount;
|
var playerCount = _playerManager.PlayerCount;
|
||||||
|
|
||||||
// playerCount does a lock so we'll just keep the variable here
|
// playerCount does a lock so we'll just keep the variable here
|
||||||
if (!ignoreEarliestStart)
|
var currentTime = !ignoreEarliestStart
|
||||||
{
|
? GameTicker.RoundDuration()
|
||||||
currentTime = GameTicker.RoundDuration();
|
: TimeSpan.Zero;
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
currentTime = TimeSpan.Zero;
|
|
||||||
}
|
|
||||||
|
|
||||||
var result = new List<StationEventRuleConfiguration>();
|
var result = new Dictionary<EntityPrototype, StationEventComponent>();
|
||||||
|
|
||||||
foreach (var stationEvent in AllEvents())
|
foreach (var (proto, stationEvent) in AllEvents())
|
||||||
{
|
{
|
||||||
if (CanRun(stationEvent, playerCount, currentTime))
|
if (CanRun(proto, stationEvent, playerCount, currentTime))
|
||||||
{
|
{
|
||||||
_sawmill.Debug($"Adding event {stationEvent.Id} to possibilities");
|
_sawmill.Debug($"Adding event {proto.ID} to possibilities");
|
||||||
result.Add(stationEvent);
|
result.Add(proto, stationEvent);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return result;
|
return result;
|
||||||
}
|
}
|
||||||
|
|
||||||
private IEnumerable<StationEventRuleConfiguration> AllEvents()
|
public Dictionary<EntityPrototype, StationEventComponent> AllEvents()
|
||||||
{
|
{
|
||||||
return _prototype.EnumeratePrototypes<GameRulePrototype>()
|
var allEvents = new Dictionary<EntityPrototype, StationEventComponent>();
|
||||||
.Where(p => p.Configuration is StationEventRuleConfiguration)
|
foreach (var prototype in _prototype.EnumeratePrototypes<EntityPrototype>())
|
||||||
.Select(p => (StationEventRuleConfiguration) p.Configuration);
|
{
|
||||||
|
if (prototype.Abstract)
|
||||||
|
continue;
|
||||||
|
|
||||||
|
if (!prototype.TryGetComponent<StationEventComponent>(out var stationEvent))
|
||||||
|
continue;
|
||||||
|
|
||||||
|
allEvents.Add(prototype, stationEvent);
|
||||||
|
}
|
||||||
|
|
||||||
|
return allEvents;
|
||||||
}
|
}
|
||||||
|
|
||||||
private int GetOccurrences(StationEventRuleConfiguration stationEvent)
|
private int GetOccurrences(EntityPrototype stationEvent)
|
||||||
{
|
{
|
||||||
return GameTicker.AllPreviousGameRules.Count(p => p.Item2.ID == stationEvent.Id);
|
return GetOccurrences(stationEvent.ID);
|
||||||
}
|
}
|
||||||
|
|
||||||
public TimeSpan TimeSinceLastEvent(StationEventRuleConfiguration? stationEvent)
|
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())
|
foreach (var (time, rule) in GameTicker.AllPreviousGameRules.Reverse())
|
||||||
{
|
{
|
||||||
if (rule.Configuration is not StationEventRuleConfiguration)
|
if (rule == stationEvent.ID)
|
||||||
continue;
|
|
||||||
|
|
||||||
if (stationEvent == null || rule.ID == stationEvent.Id)
|
|
||||||
return time;
|
return time;
|
||||||
}
|
}
|
||||||
|
|
||||||
return TimeSpan.Zero;
|
return TimeSpan.Zero;
|
||||||
}
|
}
|
||||||
|
|
||||||
private bool CanRun(StationEventRuleConfiguration stationEvent, int playerCount, TimeSpan currentTime)
|
private bool CanRun(EntityPrototype prototype, StationEventComponent stationEvent, int playerCount, TimeSpan currentTime)
|
||||||
{
|
{
|
||||||
if (GameTicker.IsGameRuleStarted(stationEvent.Id))
|
if (GameTicker.IsGameRuleActive(prototype.ID))
|
||||||
return false;
|
return false;
|
||||||
|
|
||||||
if (stationEvent.MaxOccurrences.HasValue && GetOccurrences(stationEvent) >= stationEvent.MaxOccurrences.Value)
|
if (stationEvent.MaxOccurrences.HasValue && GetOccurrences(prototype) >= stationEvent.MaxOccurrences.Value)
|
||||||
{
|
{
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
@@ -185,7 +196,7 @@ public sealed class EventManagerSystem : EntitySystem
|
|||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
var lastRun = TimeSinceLastEvent(stationEvent);
|
var lastRun = TimeSinceLastEvent(prototype);
|
||||||
if (lastRun != TimeSpan.Zero && currentTime.TotalMinutes <
|
if (lastRun != TimeSpan.Zero && currentTime.TotalMinutes <
|
||||||
stationEvent.ReoccurrenceDelay + lastRun.TotalMinutes)
|
stationEvent.ReoccurrenceDelay + lastRun.TotalMinutes)
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -1,31 +1,28 @@
|
|||||||
using System.Linq;
|
using System.Linq;
|
||||||
using Content.Server.Anomaly;
|
using Content.Server.Anomaly;
|
||||||
|
using Content.Server.GameTicking.Rules.Components;
|
||||||
using Content.Server.Station.Components;
|
using Content.Server.Station.Components;
|
||||||
|
using Content.Server.StationEvents.Components;
|
||||||
using Robust.Shared.Random;
|
using Robust.Shared.Random;
|
||||||
|
|
||||||
namespace Content.Server.StationEvents.Events;
|
namespace Content.Server.StationEvents.Events;
|
||||||
|
|
||||||
public sealed class AnomalySpawn : StationEventSystem
|
public sealed class AnomalySpawnRule : StationEventSystem<AnomalySpawnRuleComponent>
|
||||||
{
|
{
|
||||||
[Dependency] private readonly IRobustRandom _random = default!;
|
|
||||||
[Dependency] private readonly AnomalySystem _anomaly = default!;
|
[Dependency] private readonly AnomalySystem _anomaly = default!;
|
||||||
|
|
||||||
public override string Prototype => "AnomalySpawn";
|
protected override void Added(EntityUid uid, AnomalySpawnRuleComponent component, GameRuleComponent gameRule, GameRuleAddedEvent args)
|
||||||
|
|
||||||
public readonly string AnomalySpawnerPrototype = "RandomAnomalySpawner";
|
|
||||||
|
|
||||||
public override void Added()
|
|
||||||
{
|
{
|
||||||
base.Added();
|
base.Added(uid, component, gameRule, args);
|
||||||
|
|
||||||
var str = Loc.GetString("anomaly-spawn-event-announcement",
|
var str = Loc.GetString("anomaly-spawn-event-announcement",
|
||||||
("sighting", Loc.GetString($"anomaly-spawn-sighting-{_random.Next(1, 6)}")));
|
("sighting", Loc.GetString($"anomaly-spawn-sighting-{RobustRandom.Next(1, 6)}")));
|
||||||
ChatSystem.DispatchGlobalAnnouncement(str, colorOverride: Color.FromHex("#18abf5"));
|
ChatSystem.DispatchGlobalAnnouncement(str, colorOverride: Color.FromHex("#18abf5"));
|
||||||
}
|
}
|
||||||
|
|
||||||
public override void Started()
|
protected override void Started(EntityUid uid, AnomalySpawnRuleComponent component, GameRuleComponent gameRule, GameRuleStartedEvent args)
|
||||||
{
|
{
|
||||||
base.Started();
|
base.Started(uid, component, gameRule, args);
|
||||||
|
|
||||||
if (StationSystem.Stations.Count == 0)
|
if (StationSystem.Stations.Count == 0)
|
||||||
return; // No stations
|
return; // No stations
|
||||||
@@ -45,7 +42,7 @@ public sealed class AnomalySpawn : StationEventSystem
|
|||||||
var amountToSpawn = Math.Max(1, (int) MathF.Round(GetSeverityModifier() / 2));
|
var amountToSpawn = Math.Max(1, (int) MathF.Round(GetSeverityModifier() / 2));
|
||||||
for (var i = 0; i < amountToSpawn; i++)
|
for (var i = 0; i < amountToSpawn; i++)
|
||||||
{
|
{
|
||||||
_anomaly.SpawnOnRandomGridLocation(grid.Value, AnomalySpawnerPrototype);
|
_anomaly.SpawnOnRandomGridLocation(grid.Value, component.AnomalySpawnerPrototype);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -1,49 +0,0 @@
|
|||||||
using Robust.Shared.Random;
|
|
||||||
|
|
||||||
namespace Content.Server.StationEvents.Events;
|
|
||||||
|
|
||||||
public sealed class BluespaceArtifact : StationEventSystem
|
|
||||||
{
|
|
||||||
[Dependency] private readonly IRobustRandom _random = default!;
|
|
||||||
|
|
||||||
public override string Prototype => "BluespaceArtifact";
|
|
||||||
|
|
||||||
public readonly string ArtifactSpawnerPrototype = "RandomArtifactSpawner";
|
|
||||||
public readonly string ArtifactFlashPrototype = "EffectFlashBluespace";
|
|
||||||
|
|
||||||
public readonly List<string> PossibleSighting = new()
|
|
||||||
{
|
|
||||||
"bluespace-artifact-sighting-1",
|
|
||||||
"bluespace-artifact-sighting-2",
|
|
||||||
"bluespace-artifact-sighting-3",
|
|
||||||
"bluespace-artifact-sighting-4",
|
|
||||||
"bluespace-artifact-sighting-5",
|
|
||||||
"bluespace-artifact-sighting-6",
|
|
||||||
"bluespace-artifact-sighting-7"
|
|
||||||
};
|
|
||||||
|
|
||||||
public override void Added()
|
|
||||||
{
|
|
||||||
base.Added();
|
|
||||||
|
|
||||||
var str = Loc.GetString("bluespace-artifact-event-announcement",
|
|
||||||
("sighting", Loc.GetString(_random.Pick(PossibleSighting))));
|
|
||||||
ChatSystem.DispatchGlobalAnnouncement(str, colorOverride: Color.FromHex("#18abf5"));
|
|
||||||
}
|
|
||||||
|
|
||||||
public override void Started()
|
|
||||||
{
|
|
||||||
base.Started();
|
|
||||||
var amountToSpawn = Math.Max(1, (int) MathF.Round(GetSeverityModifier() / 1.5f));
|
|
||||||
for (var i = 0; i < amountToSpawn; i++)
|
|
||||||
{
|
|
||||||
if (!TryFindRandomTile(out _, out _, out _, out var coords))
|
|
||||||
return;
|
|
||||||
|
|
||||||
EntityManager.SpawnEntity(ArtifactSpawnerPrototype, coords);
|
|
||||||
EntityManager.SpawnEntity(ArtifactFlashPrototype, coords);
|
|
||||||
|
|
||||||
Sawmill.Info($"Spawning random artifact at {coords}");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
34
Content.Server/StationEvents/Events/BluespaceArtifactRule.cs
Normal file
34
Content.Server/StationEvents/Events/BluespaceArtifactRule.cs
Normal file
@@ -0,0 +1,34 @@
|
|||||||
|
using Content.Server.GameTicking.Rules.Components;
|
||||||
|
using Content.Server.StationEvents.Components;
|
||||||
|
using Robust.Shared.Random;
|
||||||
|
|
||||||
|
namespace Content.Server.StationEvents.Events;
|
||||||
|
|
||||||
|
public sealed class BluespaceArtifactRule : StationEventSystem<BluespaceArtifactRuleComponent>
|
||||||
|
{
|
||||||
|
protected override void Added(EntityUid uid, BluespaceArtifactRuleComponent component, GameRuleComponent gameRule, GameRuleAddedEvent args)
|
||||||
|
{
|
||||||
|
base.Added(uid, component, gameRule, args);
|
||||||
|
|
||||||
|
var str = Loc.GetString("bluespace-artifact-event-announcement",
|
||||||
|
("sighting", Loc.GetString(RobustRandom.Pick(component.PossibleSighting))));
|
||||||
|
ChatSystem.DispatchGlobalAnnouncement(str, colorOverride: Color.FromHex("#18abf5"));
|
||||||
|
}
|
||||||
|
|
||||||
|
protected override void Started(EntityUid uid, BluespaceArtifactRuleComponent component, GameRuleComponent gameRule, GameRuleStartedEvent args)
|
||||||
|
{
|
||||||
|
base.Started(uid, component, gameRule, args);
|
||||||
|
|
||||||
|
var amountToSpawn = Math.Max(1, (int) MathF.Round(GetSeverityModifier() / 1.5f));
|
||||||
|
for (var i = 0; i < amountToSpawn; i++)
|
||||||
|
{
|
||||||
|
if (!TryFindRandomTile(out _, out _, out _, out var coords))
|
||||||
|
return;
|
||||||
|
|
||||||
|
Spawn(component.ArtifactSpawnerPrototype, coords);
|
||||||
|
Spawn(component.ArtifactFlashPrototype, coords);
|
||||||
|
|
||||||
|
Sawmill.Info($"Spawning random artifact at {coords}");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,27 +1,25 @@
|
|||||||
using System.Linq;
|
using System.Linq;
|
||||||
|
using Content.Server.GameTicking.Rules.Components;
|
||||||
using Content.Server.Resist;
|
using Content.Server.Resist;
|
||||||
using Content.Server.Station.Components;
|
using Content.Server.Station.Components;
|
||||||
|
using Content.Server.StationEvents.Components;
|
||||||
using Content.Server.Storage.Components;
|
using Content.Server.Storage.Components;
|
||||||
using Content.Server.Storage.EntitySystems;
|
using Content.Server.Storage.EntitySystems;
|
||||||
using Content.Shared.Access.Components;
|
using Content.Shared.Access.Components;
|
||||||
using Content.Shared.Coordinates;
|
using Content.Shared.Coordinates;
|
||||||
using Robust.Shared.Random;
|
|
||||||
|
|
||||||
namespace Content.Server.StationEvents.Events;
|
namespace Content.Server.StationEvents.Events;
|
||||||
|
|
||||||
public sealed class BluespaceLockerLink : StationEventSystem
|
public sealed class BluespaceLockerRule : StationEventSystem<BluespaceLockerRuleComponent>
|
||||||
{
|
{
|
||||||
[Dependency] private readonly IRobustRandom _robustRandom = default!;
|
|
||||||
[Dependency] private readonly BluespaceLockerSystem _bluespaceLocker = default!;
|
[Dependency] private readonly BluespaceLockerSystem _bluespaceLocker = default!;
|
||||||
|
|
||||||
public override string Prototype => "BluespaceLockerLink";
|
protected override void Started(EntityUid uid, BluespaceLockerRuleComponent component, GameRuleComponent gameRule, GameRuleStartedEvent args)
|
||||||
|
|
||||||
public override void Started()
|
|
||||||
{
|
{
|
||||||
base.Started();
|
base.Started(uid, component, gameRule, args);
|
||||||
|
|
||||||
var targets = EntityQuery<EntityStorageComponent, ResistLockerComponent>().ToList();
|
var targets = EntityQuery<EntityStorageComponent, ResistLockerComponent>().ToList();
|
||||||
_robustRandom.Shuffle(targets);
|
RobustRandom.Shuffle(targets);
|
||||||
|
|
||||||
foreach (var target in targets)
|
foreach (var target in targets)
|
||||||
{
|
{
|
||||||
@@ -1,44 +1,44 @@
|
|||||||
using System.Linq;
|
using System.Linq;
|
||||||
|
using Content.Server.GameTicking.Rules.Components;
|
||||||
using Content.Server.Power.Components;
|
using Content.Server.Power.Components;
|
||||||
using Content.Server.Power.EntitySystems;
|
using Content.Server.Power.EntitySystems;
|
||||||
using Content.Server.Station.Components;
|
using Content.Server.Station.Components;
|
||||||
|
using Content.Server.StationEvents.Components;
|
||||||
using JetBrains.Annotations;
|
using JetBrains.Annotations;
|
||||||
using Robust.Shared.Random;
|
using Robust.Shared.Random;
|
||||||
|
|
||||||
namespace Content.Server.StationEvents.Events;
|
namespace Content.Server.StationEvents.Events;
|
||||||
|
|
||||||
[UsedImplicitly]
|
[UsedImplicitly]
|
||||||
public sealed class BreakerFlip : StationEventSystem
|
public sealed class BreakerFlipRule : StationEventSystem<BreakerFlipRuleComponent>
|
||||||
{
|
{
|
||||||
[Dependency] private readonly ApcSystem _apcSystem = default!;
|
[Dependency] private readonly ApcSystem _apcSystem = default!;
|
||||||
|
|
||||||
public override string Prototype => "BreakerFlip";
|
protected override void Added(EntityUid uid, BreakerFlipRuleComponent component, GameRuleComponent gameRule, GameRuleAddedEvent args)
|
||||||
|
|
||||||
public override void Added()
|
|
||||||
{
|
{
|
||||||
base.Added();
|
base.Added(uid, component, gameRule, args);
|
||||||
|
|
||||||
var str = Loc.GetString("station-event-breaker-flip-announcement", ("data", Loc.GetString(Loc.GetString($"random-sentience-event-data-{RobustRandom.Next(1, 6)}"))));
|
var str = Loc.GetString("station-event-breaker-flip-announcement", ("data", Loc.GetString(Loc.GetString($"random-sentience-event-data-{RobustRandom.Next(1, 6)}"))));
|
||||||
ChatSystem.DispatchGlobalAnnouncement(str, playSound: false, colorOverride: Color.Gold);
|
ChatSystem.DispatchGlobalAnnouncement(str, playSound: false, colorOverride: Color.Gold);
|
||||||
}
|
}
|
||||||
|
|
||||||
public override void Started()
|
protected override void Started(EntityUid uid, BreakerFlipRuleComponent component, GameRuleComponent gameRule, GameRuleStartedEvent args)
|
||||||
{
|
{
|
||||||
base.Started();
|
base.Started(uid, component, gameRule, args);
|
||||||
|
|
||||||
if (StationSystem.Stations.Count == 0)
|
if (StationSystem.Stations.Count == 0)
|
||||||
return;
|
return;
|
||||||
var chosenStation = RobustRandom.Pick(StationSystem.Stations.ToList());
|
var chosenStation = RobustRandom.Pick(StationSystem.Stations.ToList());
|
||||||
|
|
||||||
var stationApcs = new List<ApcComponent>();
|
var stationApcs = new List<ApcComponent>();
|
||||||
foreach (var (apc, transform) in EntityQuery<ApcComponent, TransformComponent>())
|
foreach (var (apc, transform) in EntityQuery<ApcComponent, TransformComponent>())
|
||||||
{
|
{
|
||||||
if (apc.MainBreakerEnabled && CompOrNull<StationMemberComponent>(transform.GridUid)?.Station == chosenStation)
|
if (apc.MainBreakerEnabled && CompOrNull<StationMemberComponent>(transform.GridUid)?.Station == chosenStation)
|
||||||
{
|
{
|
||||||
stationApcs.Add(apc);
|
stationApcs.Add(apc);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
var toDisable = Math.Min(RobustRandom.Next(3, 7), stationApcs.Count);
|
var toDisable = Math.Min(RobustRandom.Next(3, 7), stationApcs.Count);
|
||||||
if (toDisable == 0)
|
if (toDisable == 0)
|
||||||
return;
|
return;
|
||||||
@@ -1,20 +1,20 @@
|
|||||||
using System.Linq;
|
using System.Linq;
|
||||||
|
using Content.Server.GameTicking.Rules.Components;
|
||||||
using Content.Server.Station.Systems;
|
using Content.Server.Station.Systems;
|
||||||
|
using Content.Server.StationEvents.Components;
|
||||||
using JetBrains.Annotations;
|
using JetBrains.Annotations;
|
||||||
using Robust.Shared.Random;
|
using Robust.Shared.Random;
|
||||||
|
|
||||||
namespace Content.Server.StationEvents.Events;
|
namespace Content.Server.StationEvents.Events;
|
||||||
|
|
||||||
[UsedImplicitly]
|
[UsedImplicitly]
|
||||||
public sealed class BureaucraticError : StationEventSystem
|
public sealed class BureaucraticErrorRule : StationEventSystem<BureaucraticErrorRuleComponent>
|
||||||
{
|
{
|
||||||
[Dependency] private readonly StationJobsSystem _stationJobs = default!;
|
[Dependency] private readonly StationJobsSystem _stationJobs = default!;
|
||||||
|
|
||||||
public override string Prototype => "BureaucraticError";
|
protected override void Started(EntityUid uid, BureaucraticErrorRuleComponent component, GameRuleComponent gameRule, GameRuleStartedEvent args)
|
||||||
|
|
||||||
public override void Started()
|
|
||||||
{
|
{
|
||||||
base.Started();
|
base.Started(uid, component, gameRule, args);
|
||||||
|
|
||||||
if (StationSystem.Stations.Count == 0)
|
if (StationSystem.Stations.Count == 0)
|
||||||
return; // No stations
|
return; // No stations
|
||||||
@@ -1,5 +1,7 @@
|
|||||||
using Content.Server.Disease;
|
using Content.Server.Disease;
|
||||||
using Content.Server.Disease.Components;
|
using Content.Server.Disease.Components;
|
||||||
|
using Content.Server.GameTicking.Rules.Components;
|
||||||
|
using Content.Server.StationEvents.Components;
|
||||||
using Content.Shared.Disease;
|
using Content.Shared.Disease;
|
||||||
using Content.Shared.Mobs.Components;
|
using Content.Shared.Mobs.Components;
|
||||||
using Content.Shared.Mobs.Systems;
|
using Content.Shared.Mobs.Systems;
|
||||||
@@ -10,38 +12,23 @@ namespace Content.Server.StationEvents.Events;
|
|||||||
/// Infects a couple people
|
/// Infects a couple people
|
||||||
/// with a random disease that isn't super deadly
|
/// with a random disease that isn't super deadly
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public sealed class DiseaseOutbreak : StationEventSystem
|
public sealed class DiseaseOutbreakRule : StationEventSystem<DiseaseOutbreakRuleComponent>
|
||||||
{
|
{
|
||||||
[Dependency] private readonly DiseaseSystem _diseaseSystem = default!;
|
[Dependency] private readonly DiseaseSystem _diseaseSystem = default!;
|
||||||
[Dependency] private readonly MobStateSystem _mobStateSystem = default!;
|
[Dependency] private readonly MobStateSystem _mobStateSystem = default!;
|
||||||
|
|
||||||
public override string Prototype => "DiseaseOutbreak";
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Disease prototypes I decided were not too deadly for a random event
|
|
||||||
/// </summary>
|
|
||||||
public readonly IReadOnlyList<string> NotTooSeriousDiseases = new[]
|
|
||||||
{
|
|
||||||
"SpaceCold",
|
|
||||||
"VanAusdallsRobovirus",
|
|
||||||
"VentCough",
|
|
||||||
"AMIV",
|
|
||||||
"SpaceFlu",
|
|
||||||
"BirdFlew",
|
|
||||||
"TongueTwister"
|
|
||||||
};
|
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Finds 2-5 random, alive entities that can host diseases
|
/// Finds 2-5 random, alive entities that can host diseases
|
||||||
/// and gives them a randomly selected disease.
|
/// and gives them a randomly selected disease.
|
||||||
/// They all get the same disease.
|
/// They all get the same disease.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public override void Started()
|
protected override void Started(EntityUid uid, DiseaseOutbreakRuleComponent component, GameRuleComponent gameRule, GameRuleStartedEvent args)
|
||||||
{
|
{
|
||||||
base.Started();
|
base.Started(uid, component, gameRule, args);
|
||||||
|
|
||||||
HashSet<EntityUid> stationsToNotify = new();
|
HashSet<EntityUid> stationsToNotify = new();
|
||||||
List<DiseaseCarrierComponent> aliveList = new();
|
List<DiseaseCarrierComponent> aliveList = new();
|
||||||
foreach (var (carrier, mobState) in EntityManager.EntityQuery<DiseaseCarrierComponent, MobStateComponent>())
|
foreach (var (carrier, mobState) in EntityQuery<DiseaseCarrierComponent, MobStateComponent>())
|
||||||
{
|
{
|
||||||
if (!_mobStateSystem.IsDead(mobState.Owner, mobState))
|
if (!_mobStateSystem.IsDead(mobState.Owner, mobState))
|
||||||
aliveList.Add(carrier);
|
aliveList.Add(carrier);
|
||||||
@@ -51,7 +38,7 @@ public sealed class DiseaseOutbreak : StationEventSystem
|
|||||||
// We're going to filter the above out to only alive mobs. Might change after future mobstate rework
|
// We're going to filter the above out to only alive mobs. Might change after future mobstate rework
|
||||||
var toInfect = RobustRandom.Next(2, 5);
|
var toInfect = RobustRandom.Next(2, 5);
|
||||||
|
|
||||||
var diseaseName = RobustRandom.Pick(NotTooSeriousDiseases);
|
var diseaseName = RobustRandom.Pick(component.NotTooSeriousDiseases);
|
||||||
|
|
||||||
if (!PrototypeManager.TryIndex(diseaseName, out DiseasePrototype? disease))
|
if (!PrototypeManager.TryIndex(diseaseName, out DiseasePrototype? disease))
|
||||||
return;
|
return;
|
||||||
@@ -1,33 +0,0 @@
|
|||||||
using Content.Server.GameTicking.Rules.Configurations;
|
|
||||||
using JetBrains.Annotations;
|
|
||||||
using Robust.Shared.Audio;
|
|
||||||
using Robust.Shared.Player;
|
|
||||||
|
|
||||||
namespace Content.Server.StationEvents.Events
|
|
||||||
{
|
|
||||||
[UsedImplicitly]
|
|
||||||
public sealed class FalseAlarm : StationEventSystem
|
|
||||||
{
|
|
||||||
public override string Prototype => "FalseAlarm";
|
|
||||||
|
|
||||||
public override void Started()
|
|
||||||
{
|
|
||||||
base.Started();
|
|
||||||
|
|
||||||
var ev = GetRandomEventUnweighted(PrototypeManager, RobustRandom);
|
|
||||||
|
|
||||||
if (ev.Configuration is not StationEventRuleConfiguration cfg)
|
|
||||||
return;
|
|
||||||
|
|
||||||
if (cfg.StartAnnouncement != null)
|
|
||||||
{
|
|
||||||
ChatSystem.DispatchGlobalAnnouncement(Loc.GetString(cfg.StartAnnouncement), playSound: false, colorOverride: Color.Gold);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (cfg.StartAudio != null)
|
|
||||||
{
|
|
||||||
SoundSystem.Play(cfg.StartAudio.GetSound(), Filter.Broadcast(), cfg.StartAudio.Params);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
28
Content.Server/StationEvents/Events/FalseAlarmRule.cs
Normal file
28
Content.Server/StationEvents/Events/FalseAlarmRule.cs
Normal file
@@ -0,0 +1,28 @@
|
|||||||
|
using System.Linq;
|
||||||
|
using Content.Server.GameTicking.Rules.Components;
|
||||||
|
using Content.Server.StationEvents.Components;
|
||||||
|
using JetBrains.Annotations;
|
||||||
|
using Robust.Shared.Player;
|
||||||
|
using Robust.Shared.Random;
|
||||||
|
|
||||||
|
namespace Content.Server.StationEvents.Events;
|
||||||
|
|
||||||
|
[UsedImplicitly]
|
||||||
|
public sealed class FalseAlarmRule : StationEventSystem<FalseAlarmRuleComponent>
|
||||||
|
{
|
||||||
|
[Dependency] private readonly EventManagerSystem _event = default!;
|
||||||
|
|
||||||
|
protected override void Started(EntityUid uid, FalseAlarmRuleComponent component, GameRuleComponent gameRule, GameRuleStartedEvent args)
|
||||||
|
{
|
||||||
|
base.Started(uid, component, gameRule, args);
|
||||||
|
|
||||||
|
var allEv = _event.AllEvents().Select(p => p.Value).ToList();
|
||||||
|
var picked = RobustRandom.Pick(allEv);
|
||||||
|
|
||||||
|
if (picked.StartAnnouncement != null)
|
||||||
|
{
|
||||||
|
ChatSystem.DispatchGlobalAnnouncement(Loc.GetString(picked.StartAnnouncement), playSound: false, colorOverride: Color.Gold);
|
||||||
|
}
|
||||||
|
Audio.PlayGlobal(picked.StartAudio, Filter.Broadcast(), true);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,147 +0,0 @@
|
|||||||
using Content.Server.Atmos.EntitySystems;
|
|
||||||
using Content.Server.GameTicking.Rules;
|
|
||||||
using Content.Server.GameTicking.Rules.Configurations;
|
|
||||||
using Content.Shared.Atmos;
|
|
||||||
using Robust.Shared.Audio;
|
|
||||||
using Robust.Shared.Map;
|
|
||||||
using Robust.Shared.Player;
|
|
||||||
using Robust.Shared.Random;
|
|
||||||
|
|
||||||
namespace Content.Server.StationEvents.Events
|
|
||||||
{
|
|
||||||
internal sealed class GasLeak : StationEventSystem
|
|
||||||
{
|
|
||||||
[Dependency] private readonly AtmosphereSystem _atmosphere = default!;
|
|
||||||
|
|
||||||
public override string Prototype => "GasLeak";
|
|
||||||
|
|
||||||
private static readonly Gas[] LeakableGases =
|
|
||||||
{
|
|
||||||
Gas.Miasma,
|
|
||||||
Gas.Plasma,
|
|
||||||
Gas.Tritium,
|
|
||||||
Gas.Frezon,
|
|
||||||
};
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Running cooldown of how much time until another leak.
|
|
||||||
/// </summary>
|
|
||||||
private float _timeUntilLeak;
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// How long between more gas being added to the tile.
|
|
||||||
/// </summary>
|
|
||||||
private const float LeakCooldown = 1.0f;
|
|
||||||
|
|
||||||
|
|
||||||
// Event variables
|
|
||||||
|
|
||||||
private EntityUid _targetStation;
|
|
||||||
private EntityUid _targetGrid;
|
|
||||||
private Vector2i _targetTile;
|
|
||||||
private EntityCoordinates _targetCoords;
|
|
||||||
private bool _foundTile;
|
|
||||||
private Gas _leakGas;
|
|
||||||
private float _molesPerSecond;
|
|
||||||
private const int MinimumMolesPerSecond = 20;
|
|
||||||
private float _endAfter = float.MaxValue;
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Don't want to make it too fast to give people time to flee.
|
|
||||||
/// </summary>
|
|
||||||
private const int MaximumMolesPerSecond = 50;
|
|
||||||
|
|
||||||
private const int MinimumGas = 250;
|
|
||||||
private const int MaximumGas = 1000;
|
|
||||||
private const float SparkChance = 0.05f;
|
|
||||||
|
|
||||||
public override void Started()
|
|
||||||
{
|
|
||||||
base.Started();
|
|
||||||
|
|
||||||
var mod = MathF.Sqrt(GetSeverityModifier());
|
|
||||||
|
|
||||||
// Essentially we'll pick out a target amount of gas to leak, then a rate to leak it at, then work out the duration from there.
|
|
||||||
if (TryFindRandomTile(out _targetTile, out _targetStation, out _targetGrid, out _targetCoords))
|
|
||||||
{
|
|
||||||
_foundTile = true;
|
|
||||||
|
|
||||||
_leakGas = RobustRandom.Pick(LeakableGases);
|
|
||||||
// Was 50-50 on using normal distribution.
|
|
||||||
var totalGas = RobustRandom.Next(MinimumGas, MaximumGas) * mod;
|
|
||||||
var startAfter = ((StationEventRuleConfiguration) Configuration).StartAfter;
|
|
||||||
_molesPerSecond = RobustRandom.Next(MinimumMolesPerSecond, MaximumMolesPerSecond);
|
|
||||||
_endAfter = totalGas / _molesPerSecond + startAfter;
|
|
||||||
Sawmill.Info($"Leaking {totalGas} of {_leakGas} over {_endAfter - startAfter} seconds at {_targetTile}");
|
|
||||||
}
|
|
||||||
|
|
||||||
// Look technically if you wanted to guarantee a leak you'd do this in announcement but having the announcement
|
|
||||||
// there just to fuck with people even if there is no valid tile is funny.
|
|
||||||
}
|
|
||||||
|
|
||||||
public override void Update(float frameTime)
|
|
||||||
{
|
|
||||||
base.Update(frameTime);
|
|
||||||
|
|
||||||
if (!RuleStarted)
|
|
||||||
return;
|
|
||||||
|
|
||||||
if (Elapsed > _endAfter)
|
|
||||||
{
|
|
||||||
ForceEndSelf();
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
_timeUntilLeak -= frameTime;
|
|
||||||
|
|
||||||
if (_timeUntilLeak > 0f) return;
|
|
||||||
_timeUntilLeak += LeakCooldown;
|
|
||||||
|
|
||||||
if (!_foundTile ||
|
|
||||||
_targetGrid == default ||
|
|
||||||
EntityManager.Deleted(_targetGrid) ||
|
|
||||||
!_atmosphere.IsSimulatedGrid(_targetGrid))
|
|
||||||
{
|
|
||||||
ForceEndSelf();
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
var environment = _atmosphere.GetTileMixture(_targetGrid, null, _targetTile, true);
|
|
||||||
|
|
||||||
environment?.AdjustMoles(_leakGas, LeakCooldown * _molesPerSecond);
|
|
||||||
}
|
|
||||||
|
|
||||||
public override void Ended()
|
|
||||||
{
|
|
||||||
base.Ended();
|
|
||||||
|
|
||||||
Spark();
|
|
||||||
|
|
||||||
_foundTile = false;
|
|
||||||
_targetGrid = default;
|
|
||||||
_targetTile = default;
|
|
||||||
_targetCoords = default;
|
|
||||||
_leakGas = Gas.Oxygen;
|
|
||||||
_endAfter = float.MaxValue;
|
|
||||||
}
|
|
||||||
|
|
||||||
private void Spark()
|
|
||||||
{
|
|
||||||
if (RobustRandom.NextFloat() <= SparkChance)
|
|
||||||
{
|
|
||||||
if (!_foundTile ||
|
|
||||||
_targetGrid == default ||
|
|
||||||
(!EntityManager.EntityExists(_targetGrid) ? EntityLifeStage.Deleted : EntityManager.GetComponent<MetaDataComponent>(_targetGrid).EntityLifeStage) >= EntityLifeStage.Deleted ||
|
|
||||||
!_atmosphere.IsSimulatedGrid(_targetGrid))
|
|
||||||
{
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Don't want it to be so obnoxious as to instantly murder anyone in the area but enough that
|
|
||||||
// it COULD start potentially start a bigger fire.
|
|
||||||
_atmosphere.HotspotExpose(_targetGrid, _targetTile, 700f, 50f, null, true);
|
|
||||||
SoundSystem.Play("/Audio/Effects/sparks4.ogg", Filter.Pvs(_targetCoords), _targetCoords);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
90
Content.Server/StationEvents/Events/GasLeakRule.cs
Normal file
90
Content.Server/StationEvents/Events/GasLeakRule.cs
Normal file
@@ -0,0 +1,90 @@
|
|||||||
|
using Content.Server.Atmos.EntitySystems;
|
||||||
|
using Content.Server.GameTicking.Rules.Components;
|
||||||
|
using Content.Server.StationEvents.Components;
|
||||||
|
using Robust.Shared.Audio;
|
||||||
|
using Robust.Shared.Random;
|
||||||
|
using Robust.Shared.Timing;
|
||||||
|
|
||||||
|
namespace Content.Server.StationEvents.Events
|
||||||
|
{
|
||||||
|
internal sealed class GasLeakRule : StationEventSystem<GasLeakRuleComponent>
|
||||||
|
{
|
||||||
|
[Dependency] private readonly IGameTiming _timing = default!;
|
||||||
|
[Dependency] private readonly AtmosphereSystem _atmosphere = default!;
|
||||||
|
|
||||||
|
protected override void Started(EntityUid uid, GasLeakRuleComponent component, GameRuleComponent gameRule, GameRuleStartedEvent args)
|
||||||
|
{
|
||||||
|
base.Started(uid, component, gameRule, args);
|
||||||
|
|
||||||
|
if (!TryComp<StationEventComponent>(uid, out var stationEvent))
|
||||||
|
return;
|
||||||
|
|
||||||
|
var mod = MathF.Sqrt(GetSeverityModifier());
|
||||||
|
|
||||||
|
// Essentially we'll pick out a target amount of gas to leak, then a rate to leak it at, then work out the duration from there.
|
||||||
|
if (TryFindRandomTile(out component.TargetTile, out component.TargetStation, out component.TargetGrid, out component.TargetCoords))
|
||||||
|
{
|
||||||
|
component.FoundTile = true;
|
||||||
|
|
||||||
|
component.LeakGas = RobustRandom.Pick(component.LeakableGases);
|
||||||
|
// Was 50-50 on using normal distribution.
|
||||||
|
var totalGas = RobustRandom.Next(component.MinimumGas, component.MaximumGas) * mod;
|
||||||
|
var startAfter = stationEvent.StartDelay;
|
||||||
|
component.MolesPerSecond = RobustRandom.Next(component.MinimumMolesPerSecond, component.MaximumMolesPerSecond);
|
||||||
|
|
||||||
|
stationEvent.EndTime = _timing.CurTime + TimeSpan.FromSeconds(totalGas / component.MolesPerSecond + startAfter.TotalSeconds);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Look technically if you wanted to guarantee a leak you'd do this in announcement but having the announcement
|
||||||
|
// there just to fuck with people even if there is no valid tile is funny.
|
||||||
|
}
|
||||||
|
|
||||||
|
protected override void ActiveTick(EntityUid uid, GasLeakRuleComponent component, GameRuleComponent gameRule, float frameTime)
|
||||||
|
{
|
||||||
|
base.ActiveTick(uid, component, gameRule, frameTime);
|
||||||
|
component.TimeUntilLeak -= frameTime;
|
||||||
|
|
||||||
|
if (component.TimeUntilLeak > 0f)
|
||||||
|
return;
|
||||||
|
component.TimeUntilLeak += component.LeakCooldown;
|
||||||
|
|
||||||
|
if (!component.FoundTile ||
|
||||||
|
component.TargetGrid == default ||
|
||||||
|
Deleted(component.TargetGrid) ||
|
||||||
|
!_atmosphere.IsSimulatedGrid(component.TargetGrid))
|
||||||
|
{
|
||||||
|
ForceEndSelf(uid, gameRule);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
var environment = _atmosphere.GetTileMixture(component.TargetGrid, null, component.TargetTile, true);
|
||||||
|
|
||||||
|
environment?.AdjustMoles(component.LeakGas, component.LeakCooldown * component.MolesPerSecond);
|
||||||
|
}
|
||||||
|
|
||||||
|
protected override void Ended(EntityUid uid, GasLeakRuleComponent component, GameRuleComponent gameRule, GameRuleEndedEvent args)
|
||||||
|
{
|
||||||
|
base.Ended(uid, component, gameRule, args);
|
||||||
|
Spark(uid, component);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void Spark(EntityUid uid, GasLeakRuleComponent component)
|
||||||
|
{
|
||||||
|
if (RobustRandom.NextFloat() <= component.SparkChance)
|
||||||
|
{
|
||||||
|
if (!component.FoundTile ||
|
||||||
|
component.TargetGrid == default ||
|
||||||
|
(!Exists(component.TargetGrid) ? EntityLifeStage.Deleted : MetaData(component.TargetGrid).EntityLifeStage) >= EntityLifeStage.Deleted ||
|
||||||
|
!_atmosphere.IsSimulatedGrid(component.TargetGrid))
|
||||||
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Don't want it to be so obnoxious as to instantly murder anyone in the area but enough that
|
||||||
|
// it COULD start potentially start a bigger fire.
|
||||||
|
_atmosphere.HotspotExpose(component.TargetGrid, component.TargetTile, 700f, 50f, null, true);
|
||||||
|
Audio.PlayPvs(new SoundPathSpecifier("/Audio/Effects/sparks4.ogg"), component.TargetCoords);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,28 +0,0 @@
|
|||||||
using Robust.Shared.Map;
|
|
||||||
using Robust.Shared.Random;
|
|
||||||
|
|
||||||
namespace Content.Server.StationEvents.Events;
|
|
||||||
|
|
||||||
public sealed class KudzuGrowth : StationEventSystem
|
|
||||||
{
|
|
||||||
public override string Prototype => "KudzuGrowth";
|
|
||||||
|
|
||||||
private EntityUid _targetGrid;
|
|
||||||
private Vector2i _targetTile;
|
|
||||||
private EntityCoordinates _targetCoords;
|
|
||||||
|
|
||||||
public override void Started()
|
|
||||||
{
|
|
||||||
base.Started();
|
|
||||||
|
|
||||||
// Pick a place to plant the kudzu.
|
|
||||||
if (TryFindRandomTile(out _targetTile, out _, out _targetGrid, out _targetCoords))
|
|
||||||
{
|
|
||||||
EntityManager.SpawnEntity("Kudzu", _targetCoords);
|
|
||||||
Sawmill.Info($"Spawning a Kudzu at {_targetTile} on {_targetGrid}");
|
|
||||||
}
|
|
||||||
|
|
||||||
// If the kudzu tile selection fails we just let the announcement happen anyways because it's funny and people
|
|
||||||
// will be hunting the non-existent, dangerous plant.
|
|
||||||
}
|
|
||||||
}
|
|
||||||
19
Content.Server/StationEvents/Events/KudzuGrowthRule.cs
Normal file
19
Content.Server/StationEvents/Events/KudzuGrowthRule.cs
Normal file
@@ -0,0 +1,19 @@
|
|||||||
|
using Content.Server.GameTicking.Rules.Components;
|
||||||
|
using Content.Server.StationEvents.Components;
|
||||||
|
|
||||||
|
namespace Content.Server.StationEvents.Events;
|
||||||
|
|
||||||
|
public sealed class KudzuGrowthRule : StationEventSystem<KudzuGrowthRuleComponent>
|
||||||
|
{
|
||||||
|
protected override void Started(EntityUid uid, KudzuGrowthRuleComponent component, GameRuleComponent gameRule, GameRuleStartedEvent args)
|
||||||
|
{
|
||||||
|
base.Started(uid, component, gameRule, args);
|
||||||
|
|
||||||
|
// Pick a place to plant the kudzu.
|
||||||
|
if (!TryFindRandomTile(out var targetTile, out _, out var targetGrid, out var targetCoords))
|
||||||
|
return;
|
||||||
|
Spawn("Kudzu", targetCoords);
|
||||||
|
Sawmill.Info($"Spawning a Kudzu at {targetTile} on {targetGrid}");
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,44 +0,0 @@
|
|||||||
using Robust.Server.GameObjects;
|
|
||||||
using Robust.Server.Maps;
|
|
||||||
using Robust.Shared.Map;
|
|
||||||
using Content.Server.GameTicking;
|
|
||||||
using Robust.Shared.Prototypes;
|
|
||||||
using Content.Server.GameTicking.Rules;
|
|
||||||
|
|
||||||
namespace Content.Server.StationEvents.Events;
|
|
||||||
|
|
||||||
public sealed class LoneOpsSpawn : StationEventSystem
|
|
||||||
{
|
|
||||||
[Dependency] private readonly IMapManager _mapManager = default!;
|
|
||||||
[Dependency] private readonly IPrototypeManager _prototypeManager = default!;
|
|
||||||
[Dependency] private readonly MapLoaderSystem _map = default!;
|
|
||||||
[Dependency] private readonly GameTicker _gameTicker = default!;
|
|
||||||
[Dependency] private readonly NukeopsRuleSystem _nukeopsRuleSystem = default!;
|
|
||||||
|
|
||||||
public override string Prototype => "LoneOpsSpawn";
|
|
||||||
public const string LoneOpsShuttlePath = "Maps/Shuttles/striker.yml";
|
|
||||||
public const string GameRuleProto = "Nukeops";
|
|
||||||
|
|
||||||
public override void Started()
|
|
||||||
{
|
|
||||||
base.Started();
|
|
||||||
|
|
||||||
if (!_nukeopsRuleSystem.CheckLoneOpsSpawn())
|
|
||||||
return;
|
|
||||||
|
|
||||||
var shuttleMap = _mapManager.CreateMap();
|
|
||||||
var options = new MapLoadOptions()
|
|
||||||
{
|
|
||||||
LoadMap = true,
|
|
||||||
};
|
|
||||||
|
|
||||||
_map.TryLoad(shuttleMap, LoneOpsShuttlePath, out var grids, options);
|
|
||||||
|
|
||||||
if (!_prototypeManager.TryIndex<GameRulePrototype>(GameRuleProto, out var ruleProto))
|
|
||||||
return;
|
|
||||||
|
|
||||||
_nukeopsRuleSystem.LoadLoneOpsConfig();
|
|
||||||
_gameTicker.StartGameRule(ruleProto);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
49
Content.Server/StationEvents/Events/LoneOpsSpawnRule.cs
Normal file
49
Content.Server/StationEvents/Events/LoneOpsSpawnRule.cs
Normal file
@@ -0,0 +1,49 @@
|
|||||||
|
using Robust.Server.GameObjects;
|
||||||
|
using Robust.Server.Maps;
|
||||||
|
using Robust.Shared.Map;
|
||||||
|
using Content.Server.GameTicking;
|
||||||
|
using Content.Server.GameTicking.Rules;
|
||||||
|
using Content.Server.GameTicking.Rules.Components;
|
||||||
|
using Content.Server.StationEvents.Components;
|
||||||
|
|
||||||
|
namespace Content.Server.StationEvents.Events;
|
||||||
|
|
||||||
|
public sealed class LoneOpsSpawnRule : StationEventSystem<LoneOpsSpawnRuleComponent>
|
||||||
|
{
|
||||||
|
[Dependency] private readonly IMapManager _mapManager = default!;
|
||||||
|
[Dependency] private readonly MapLoaderSystem _map = default!;
|
||||||
|
[Dependency] private readonly GameTicker _gameTicker = default!;
|
||||||
|
[Dependency] private readonly NukeopsRuleSystem _nukeopsRuleSystem = default!;
|
||||||
|
|
||||||
|
protected override void Started(EntityUid uid, LoneOpsSpawnRuleComponent component, GameRuleComponent gameRule, GameRuleStartedEvent args)
|
||||||
|
{
|
||||||
|
base.Started(uid, component, gameRule, args);
|
||||||
|
|
||||||
|
if (!_nukeopsRuleSystem.CheckLoneOpsSpawn())
|
||||||
|
return;
|
||||||
|
|
||||||
|
var shuttleMap = _mapManager.CreateMap();
|
||||||
|
var options = new MapLoadOptions
|
||||||
|
{
|
||||||
|
LoadMap = true,
|
||||||
|
};
|
||||||
|
|
||||||
|
_map.TryLoad(shuttleMap, component.LoneOpsShuttlePath, out _, options);
|
||||||
|
|
||||||
|
var nukeopsEntity = _gameTicker.AddGameRule(component.GameRuleProto);
|
||||||
|
component.AdditionalRule = nukeopsEntity;
|
||||||
|
var nukeopsComp = EntityManager.GetComponent<NukeopsRuleComponent>(nukeopsEntity);
|
||||||
|
nukeopsComp.SpawnOutpost = false;
|
||||||
|
nukeopsComp.EndsRound = false;
|
||||||
|
_gameTicker.StartGameRule(nukeopsEntity);
|
||||||
|
}
|
||||||
|
|
||||||
|
protected override void Ended(EntityUid uid, LoneOpsSpawnRuleComponent component, GameRuleComponent gameRule, GameRuleEndedEvent args)
|
||||||
|
{
|
||||||
|
base.Ended(uid, component, gameRule, args);
|
||||||
|
|
||||||
|
if (component.AdditionalRule != null)
|
||||||
|
GameTicker.EndGameRule(component.AdditionalRule.Value);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
@@ -1,4 +1,5 @@
|
|||||||
using Content.Server.GameTicking;
|
using Content.Server.GameTicking.Rules.Components;
|
||||||
|
using Content.Server.StationEvents.Components;
|
||||||
using Content.Shared.Spawners.Components;
|
using Content.Shared.Spawners.Components;
|
||||||
using Robust.Shared.Map;
|
using Robust.Shared.Map;
|
||||||
using Robust.Shared.Physics.Components;
|
using Robust.Shared.Physics.Components;
|
||||||
@@ -6,67 +7,37 @@ using Robust.Shared.Physics.Systems;
|
|||||||
|
|
||||||
namespace Content.Server.StationEvents.Events
|
namespace Content.Server.StationEvents.Events
|
||||||
{
|
{
|
||||||
public sealed class MeteorSwarm : StationEventSystem
|
public sealed class MeteorSwarmRule : StationEventSystem<MeteorSwarmRuleComponent>
|
||||||
{
|
{
|
||||||
[Dependency] private readonly SharedPhysicsSystem _physics = default!;
|
[Dependency] private readonly SharedPhysicsSystem _physics = default!;
|
||||||
|
|
||||||
public override string Prototype => "MeteorSwarm";
|
protected override void Started(EntityUid uid, MeteorSwarmRuleComponent component, GameRuleComponent gameRule, GameRuleStartedEvent args)
|
||||||
|
|
||||||
private float _cooldown;
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// We'll send a specific amount of waves of meteors towards the station per ending rather than using a timer.
|
|
||||||
/// </summary>
|
|
||||||
private int _waveCounter;
|
|
||||||
|
|
||||||
private const int MinimumWaves = 3;
|
|
||||||
private const int MaximumWaves = 8;
|
|
||||||
|
|
||||||
private const float MinimumCooldown = 10f;
|
|
||||||
private const float MaximumCooldown = 60f;
|
|
||||||
|
|
||||||
private const int MeteorsPerWave = 5;
|
|
||||||
private const float MeteorVelocity = 10f;
|
|
||||||
private const float MaxAngularVelocity = 0.25f;
|
|
||||||
private const float MinAngularVelocity = -0.25f;
|
|
||||||
|
|
||||||
public override void Started()
|
|
||||||
{
|
{
|
||||||
base.Started();
|
base.Started(uid, component, gameRule, args);
|
||||||
|
|
||||||
var mod = Math.Sqrt(GetSeverityModifier());
|
var mod = Math.Sqrt(GetSeverityModifier());
|
||||||
_waveCounter = (int) (RobustRandom.Next(MinimumWaves, MaximumWaves) * mod);
|
component._waveCounter = (int) (RobustRandom.Next(component.MinimumWaves, component.MaximumWaves) * mod);
|
||||||
}
|
}
|
||||||
|
|
||||||
public override void Ended()
|
protected override void ActiveTick(EntityUid uid, MeteorSwarmRuleComponent component, GameRuleComponent gameRule, float frameTime)
|
||||||
{
|
{
|
||||||
base.Ended();
|
base.ActiveTick(uid, component, gameRule, frameTime);
|
||||||
_waveCounter = 0;
|
if (component._waveCounter <= 0)
|
||||||
_cooldown = 0f;
|
|
||||||
}
|
|
||||||
|
|
||||||
public override void Update(float frameTime)
|
|
||||||
{
|
|
||||||
base.Update(frameTime);
|
|
||||||
|
|
||||||
if (!RuleStarted)
|
|
||||||
return;
|
|
||||||
|
|
||||||
if (_waveCounter <= 0)
|
|
||||||
{
|
{
|
||||||
ForceEndSelf();
|
ForceEndSelf(uid, gameRule);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
var mod = GetSeverityModifier();
|
var mod = GetSeverityModifier();
|
||||||
|
|
||||||
_cooldown -= frameTime;
|
component._cooldown -= frameTime;
|
||||||
|
|
||||||
if (_cooldown > 0f)
|
if (component._cooldown > 0f)
|
||||||
return;
|
return;
|
||||||
|
|
||||||
_waveCounter--;
|
component._waveCounter--;
|
||||||
|
|
||||||
_cooldown += (MaximumCooldown - MinimumCooldown) * RobustRandom.NextFloat() / mod + MinimumCooldown;
|
component._cooldown += (component.MaximumCooldown - component.MinimumCooldown) * RobustRandom.NextFloat() / mod + component.MinimumCooldown;
|
||||||
|
|
||||||
Box2? playableArea = null;
|
Box2? playableArea = null;
|
||||||
var mapId = GameTicker.DefaultMap;
|
var mapId = GameTicker.DefaultMap;
|
||||||
@@ -79,7 +50,7 @@ namespace Content.Server.StationEvents.Events
|
|||||||
|
|
||||||
if (playableArea == null)
|
if (playableArea == null)
|
||||||
{
|
{
|
||||||
ForceEndSelf();
|
ForceEndSelf(uid, gameRule);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -88,7 +59,7 @@ namespace Content.Server.StationEvents.Events
|
|||||||
|
|
||||||
var center = playableArea.Value.Center;
|
var center = playableArea.Value.Center;
|
||||||
|
|
||||||
for (var i = 0; i < MeteorsPerWave; i++)
|
for (var i = 0; i < component.MeteorsPerWave; i++)
|
||||||
{
|
{
|
||||||
var angle = new Angle(RobustRandom.NextFloat() * MathF.Tau);
|
var angle = new Angle(RobustRandom.NextFloat() * MathF.Tau);
|
||||||
var offset = angle.RotateVec(new Vector2((maximumDistance - minimumDistance) * RobustRandom.NextFloat() + minimumDistance, 0));
|
var offset = angle.RotateVec(new Vector2((maximumDistance - minimumDistance) * RobustRandom.NextFloat() + minimumDistance, 0));
|
||||||
@@ -98,10 +69,10 @@ namespace Content.Server.StationEvents.Events
|
|||||||
_physics.SetBodyStatus(physics, BodyStatus.InAir);
|
_physics.SetBodyStatus(physics, BodyStatus.InAir);
|
||||||
_physics.SetLinearDamping(physics, 0f);
|
_physics.SetLinearDamping(physics, 0f);
|
||||||
_physics.SetAngularDamping(physics, 0f);
|
_physics.SetAngularDamping(physics, 0f);
|
||||||
_physics.ApplyLinearImpulse(meteor, -offset.Normalized * MeteorVelocity * physics.Mass, body: physics);
|
_physics.ApplyLinearImpulse(meteor, -offset.Normalized * component.MeteorVelocity * physics.Mass, body: physics);
|
||||||
_physics.ApplyAngularImpulse(
|
_physics.ApplyAngularImpulse(
|
||||||
meteor,
|
meteor,
|
||||||
physics.Mass * ((MaxAngularVelocity - MinAngularVelocity) * RobustRandom.NextFloat() + MinAngularVelocity),
|
physics.Mass * ((component.MaxAngularVelocity - component.MinAngularVelocity) * RobustRandom.NextFloat() + component.MinAngularVelocity),
|
||||||
body: physics);
|
body: physics);
|
||||||
|
|
||||||
EnsureComp<TimedDespawnComponent>(meteor).Lifetime = 120f;
|
EnsureComp<TimedDespawnComponent>(meteor).Lifetime = 120f;
|
||||||
@@ -1,19 +1,15 @@
|
|||||||
using System.Linq;
|
using System.Linq;
|
||||||
|
using Content.Server.GameTicking.Rules.Components;
|
||||||
using Content.Server.StationEvents.Components;
|
using Content.Server.StationEvents.Components;
|
||||||
using Robust.Shared.Random;
|
using Robust.Shared.Random;
|
||||||
|
|
||||||
namespace Content.Server.StationEvents.Events;
|
namespace Content.Server.StationEvents.Events;
|
||||||
|
|
||||||
public sealed class MouseMigration : StationEventSystem
|
public sealed class MouseMigrationRule : StationEventSystem<MouseMigrationRuleComponent>
|
||||||
{
|
{
|
||||||
public static List<string> SpawnedPrototypeChoices = new List<string>() //we double up for that ez fake probability
|
protected override void Started(EntityUid uid, MouseMigrationRuleComponent component, GameRuleComponent gameRule, GameRuleStartedEvent args)
|
||||||
{"MobMouse", "MobMouse1", "MobMouse2", "MobRatServant"};
|
|
||||||
|
|
||||||
public override string Prototype => "MouseMigration";
|
|
||||||
|
|
||||||
public override void Started()
|
|
||||||
{
|
{
|
||||||
base.Started();
|
base.Started(uid, component, gameRule, args);
|
||||||
|
|
||||||
var modifier = GetSeverityModifier();
|
var modifier = GetSeverityModifier();
|
||||||
|
|
||||||
@@ -23,9 +19,9 @@ public sealed class MouseMigration : StationEventSystem
|
|||||||
// sqrt so we dont get insane values for ramping events
|
// sqrt so we dont get insane values for ramping events
|
||||||
var spawnAmount = (int) (RobustRandom.Next(7, 15) * Math.Sqrt(modifier)); // A small colony of critters.
|
var spawnAmount = (int) (RobustRandom.Next(7, 15) * Math.Sqrt(modifier)); // A small colony of critters.
|
||||||
|
|
||||||
for (int i = 0; i < spawnAmount && i < spawnLocations.Count - 1; i++)
|
for (var i = 0; i < spawnAmount && i < spawnLocations.Count - 1; i++)
|
||||||
{
|
{
|
||||||
var spawnChoice = RobustRandom.Pick(SpawnedPrototypeChoices);
|
var spawnChoice = RobustRandom.Pick(component.SpawnedPrototypeChoices);
|
||||||
if (RobustRandom.Prob(Math.Min(0.01f * modifier, 1.0f)) || i == 0) //small chance for multiple, but always at least 1
|
if (RobustRandom.Prob(Math.Min(0.01f * modifier, 1.0f)) || i == 0) //small chance for multiple, but always at least 1
|
||||||
spawnChoice = "SpawnPointGhostRatKing";
|
spawnChoice = "SpawnPointGhostRatKing";
|
||||||
|
|
||||||
@@ -1,122 +0,0 @@
|
|||||||
using Content.Server.Power.Components;
|
|
||||||
using JetBrains.Annotations;
|
|
||||||
using Robust.Shared.Audio;
|
|
||||||
using Robust.Shared.Player;
|
|
||||||
using Robust.Shared.Utility;
|
|
||||||
using System.Threading;
|
|
||||||
using Content.Server.Power.EntitySystems;
|
|
||||||
using Timer = Robust.Shared.Timing.Timer;
|
|
||||||
using System.Linq;
|
|
||||||
using Robust.Shared.Random;
|
|
||||||
using Content.Server.Station.Components;
|
|
||||||
|
|
||||||
namespace Content.Server.StationEvents.Events
|
|
||||||
{
|
|
||||||
[UsedImplicitly]
|
|
||||||
public sealed class PowerGridCheck : StationEventSystem
|
|
||||||
{
|
|
||||||
[Dependency] private readonly ApcSystem _apcSystem = default!;
|
|
||||||
[Dependency] private readonly SharedAudioSystem _audioSystem = default!;
|
|
||||||
|
|
||||||
public override string Prototype => "PowerGridCheck";
|
|
||||||
|
|
||||||
private CancellationTokenSource? _announceCancelToken;
|
|
||||||
|
|
||||||
private readonly List<EntityUid> _powered = new();
|
|
||||||
private readonly List<EntityUid> _unpowered = new();
|
|
||||||
|
|
||||||
private const float SecondsUntilOff = 30.0f;
|
|
||||||
|
|
||||||
private int _numberPerSecond = 0;
|
|
||||||
private float UpdateRate => 1.0f / _numberPerSecond;
|
|
||||||
private float _frameTimeAccumulator = 0.0f;
|
|
||||||
private float _endAfter = 0.0f;
|
|
||||||
|
|
||||||
public override void Added()
|
|
||||||
{
|
|
||||||
base.Added();
|
|
||||||
_endAfter = RobustRandom.Next(60, 120);
|
|
||||||
}
|
|
||||||
|
|
||||||
public override void Started()
|
|
||||||
{
|
|
||||||
if (StationSystem.Stations.Count == 0)
|
|
||||||
return;
|
|
||||||
var chosenStation = RobustRandom.Pick(StationSystem.Stations.ToList());
|
|
||||||
|
|
||||||
foreach (var (apc, transform) in EntityQuery<ApcComponent, TransformComponent>(true))
|
|
||||||
{
|
|
||||||
if (apc.MainBreakerEnabled && CompOrNull<StationMemberComponent>(transform.GridUid)?.Station == chosenStation)
|
|
||||||
_powered.Add(apc.Owner);
|
|
||||||
}
|
|
||||||
|
|
||||||
RobustRandom.Shuffle(_powered);
|
|
||||||
|
|
||||||
_numberPerSecond = Math.Max(1, (int)(_powered.Count / SecondsUntilOff)); // Number of APCs to turn off every second. At least one.
|
|
||||||
|
|
||||||
base.Started();
|
|
||||||
}
|
|
||||||
|
|
||||||
public override void Update(float frameTime)
|
|
||||||
{
|
|
||||||
base.Update(frameTime);
|
|
||||||
|
|
||||||
if (!RuleStarted)
|
|
||||||
return;
|
|
||||||
|
|
||||||
if (Elapsed > _endAfter)
|
|
||||||
{
|
|
||||||
ForceEndSelf();
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
var updates = 0;
|
|
||||||
_frameTimeAccumulator += frameTime;
|
|
||||||
if (_frameTimeAccumulator > UpdateRate)
|
|
||||||
{
|
|
||||||
updates = (int) (_frameTimeAccumulator / UpdateRate);
|
|
||||||
_frameTimeAccumulator -= UpdateRate * updates;
|
|
||||||
}
|
|
||||||
|
|
||||||
for (var i = 0; i < updates; i++)
|
|
||||||
{
|
|
||||||
if (_powered.Count == 0)
|
|
||||||
break;
|
|
||||||
|
|
||||||
var selected = _powered.Pop();
|
|
||||||
if (EntityManager.Deleted(selected)) continue;
|
|
||||||
if (EntityManager.TryGetComponent<ApcComponent>(selected, out var apcComponent))
|
|
||||||
{
|
|
||||||
if (apcComponent.MainBreakerEnabled)
|
|
||||||
_apcSystem.ApcToggleBreaker(selected, apcComponent);
|
|
||||||
}
|
|
||||||
_unpowered.Add(selected);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public override void Ended()
|
|
||||||
{
|
|
||||||
foreach (var entity in _unpowered)
|
|
||||||
{
|
|
||||||
if (EntityManager.Deleted(entity)) continue;
|
|
||||||
|
|
||||||
if (EntityManager.TryGetComponent(entity, out ApcComponent? apcComponent))
|
|
||||||
{
|
|
||||||
if(!apcComponent.MainBreakerEnabled)
|
|
||||||
_apcSystem.ApcToggleBreaker(entity, apcComponent);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Can't use the default EndAudio
|
|
||||||
_announceCancelToken?.Cancel();
|
|
||||||
_announceCancelToken = new CancellationTokenSource();
|
|
||||||
Timer.Spawn(3000, () =>
|
|
||||||
{
|
|
||||||
_audioSystem.PlayGlobal("/Audio/Announcements/power_on.ogg", Filter.Broadcast(), true, AudioParams.Default.WithVolume(-4f));
|
|
||||||
}, _announceCancelToken.Token);
|
|
||||||
_unpowered.Clear();
|
|
||||||
|
|
||||||
base.Ended();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
96
Content.Server/StationEvents/Events/PowerGridCheckRule.cs
Normal file
96
Content.Server/StationEvents/Events/PowerGridCheckRule.cs
Normal file
@@ -0,0 +1,96 @@
|
|||||||
|
using Content.Server.Power.Components;
|
||||||
|
using JetBrains.Annotations;
|
||||||
|
using Robust.Shared.Audio;
|
||||||
|
using Robust.Shared.Player;
|
||||||
|
using Robust.Shared.Utility;
|
||||||
|
using System.Threading;
|
||||||
|
using Content.Server.Power.EntitySystems;
|
||||||
|
using Timer = Robust.Shared.Timing.Timer;
|
||||||
|
using System.Linq;
|
||||||
|
using Content.Server.GameTicking.Rules.Components;
|
||||||
|
using Robust.Shared.Random;
|
||||||
|
using Content.Server.Station.Components;
|
||||||
|
using Content.Server.StationEvents.Components;
|
||||||
|
|
||||||
|
namespace Content.Server.StationEvents.Events
|
||||||
|
{
|
||||||
|
[UsedImplicitly]
|
||||||
|
public sealed class PowerGridCheckRule : StationEventSystem<PowerGridCheckRuleComponent>
|
||||||
|
{
|
||||||
|
[Dependency] private readonly ApcSystem _apcSystem = default!;
|
||||||
|
|
||||||
|
protected override void Started(EntityUid uid, PowerGridCheckRuleComponent component, GameRuleComponent gameRule, GameRuleStartedEvent args)
|
||||||
|
{
|
||||||
|
base.Started(uid, component, gameRule, args);
|
||||||
|
|
||||||
|
if (StationSystem.Stations.Count == 0)
|
||||||
|
return;
|
||||||
|
var chosenStation = RobustRandom.Pick(StationSystem.Stations.ToList());
|
||||||
|
|
||||||
|
foreach (var (apc, transform) in EntityQuery<ApcComponent, TransformComponent>(true))
|
||||||
|
{
|
||||||
|
if (apc.MainBreakerEnabled && CompOrNull<StationMemberComponent>(transform.GridUid)?.Station == chosenStation)
|
||||||
|
component.Powered.Add(apc.Owner);
|
||||||
|
}
|
||||||
|
|
||||||
|
RobustRandom.Shuffle(component.Powered);
|
||||||
|
|
||||||
|
component.NumberPerSecond = Math.Max(1, (int)(component.Powered.Count / component.SecondsUntilOff)); // Number of APCs to turn off every second. At least one.
|
||||||
|
}
|
||||||
|
|
||||||
|
protected override void Ended(EntityUid uid, PowerGridCheckRuleComponent component, GameRuleComponent gameRule, GameRuleEndedEvent args)
|
||||||
|
{
|
||||||
|
base.Ended(uid, component, gameRule, args);
|
||||||
|
|
||||||
|
foreach (var entity in component.Unpowered)
|
||||||
|
{
|
||||||
|
if (Deleted(entity))
|
||||||
|
continue;
|
||||||
|
|
||||||
|
if (TryComp(entity, out ApcComponent? apcComponent))
|
||||||
|
{
|
||||||
|
if(!apcComponent.MainBreakerEnabled)
|
||||||
|
_apcSystem.ApcToggleBreaker(entity, apcComponent);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Can't use the default EndAudio
|
||||||
|
component.AnnounceCancelToken?.Cancel();
|
||||||
|
component.AnnounceCancelToken = new CancellationTokenSource();
|
||||||
|
Timer.Spawn(3000, () =>
|
||||||
|
{
|
||||||
|
Audio.PlayGlobal("/Audio/Announcements/power_on.ogg", Filter.Broadcast(), true, AudioParams.Default.WithVolume(-4f));
|
||||||
|
}, component.AnnounceCancelToken.Token);
|
||||||
|
component.Unpowered.Clear();
|
||||||
|
}
|
||||||
|
|
||||||
|
protected override void ActiveTick(EntityUid uid, PowerGridCheckRuleComponent component, GameRuleComponent gameRule, float frameTime)
|
||||||
|
{
|
||||||
|
base.ActiveTick(uid, component, gameRule, frameTime);
|
||||||
|
|
||||||
|
var updates = 0;
|
||||||
|
component.FrameTimeAccumulator += frameTime;
|
||||||
|
if (component.FrameTimeAccumulator > component.UpdateRate)
|
||||||
|
{
|
||||||
|
updates = (int) (component.FrameTimeAccumulator / component.UpdateRate);
|
||||||
|
component.FrameTimeAccumulator -= component.UpdateRate * updates;
|
||||||
|
}
|
||||||
|
|
||||||
|
for (var i = 0; i < updates; i++)
|
||||||
|
{
|
||||||
|
if (component.Powered.Count == 0)
|
||||||
|
break;
|
||||||
|
|
||||||
|
var selected = component.Powered.Pop();
|
||||||
|
if (Deleted(selected))
|
||||||
|
continue;
|
||||||
|
if (TryComp<ApcComponent>(selected, out var apcComponent))
|
||||||
|
{
|
||||||
|
if (apcComponent.MainBreakerEnabled)
|
||||||
|
_apcSystem.ApcToggleBreaker(selected, apcComponent);
|
||||||
|
}
|
||||||
|
component.Unpowered.Add(selected);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,22 +1,20 @@
|
|||||||
using System.Linq;
|
using System.Linq;
|
||||||
using Content.Server.Chat.Systems;
|
using Content.Server.Chat.Systems;
|
||||||
|
using Content.Server.GameTicking.Rules.Components;
|
||||||
using Content.Server.Ghost.Roles.Components;
|
using Content.Server.Ghost.Roles.Components;
|
||||||
using Content.Server.Station.Systems;
|
using Content.Server.Station.Systems;
|
||||||
using Content.Server.StationEvents.Components;
|
using Content.Server.StationEvents.Components;
|
||||||
|
|
||||||
namespace Content.Server.StationEvents.Events;
|
namespace Content.Server.StationEvents.Events;
|
||||||
|
|
||||||
public sealed class RandomSentience : StationEventSystem
|
public sealed class RandomSentienceRule : StationEventSystem<RandomSentienceRuleComponent>
|
||||||
{
|
{
|
||||||
public override string Prototype => "RandomSentience";
|
protected override void Started(EntityUid uid, RandomSentienceRuleComponent component, GameRuleComponent gameRule, GameRuleStartedEvent args)
|
||||||
|
|
||||||
public override void Started()
|
|
||||||
{
|
{
|
||||||
base.Started();
|
|
||||||
HashSet<EntityUid> stationsToNotify = new();
|
HashSet<EntityUid> stationsToNotify = new();
|
||||||
|
|
||||||
var mod = GetSeverityModifier();
|
var mod = GetSeverityModifier();
|
||||||
var targetList = EntityManager.EntityQuery<SentienceTargetComponent>().ToList();
|
var targetList = EntityQuery<SentienceTargetComponent>().ToList();
|
||||||
RobustRandom.Shuffle(targetList);
|
RobustRandom.Shuffle(targetList);
|
||||||
|
|
||||||
var toMakeSentient = (int) (RobustRandom.Next(2, 5) * Math.Sqrt(mod));
|
var toMakeSentient = (int) (RobustRandom.Next(2, 5) * Math.Sqrt(mod));
|
||||||
@@ -27,10 +25,10 @@ public sealed class RandomSentience : StationEventSystem
|
|||||||
if (toMakeSentient-- == 0)
|
if (toMakeSentient-- == 0)
|
||||||
break;
|
break;
|
||||||
|
|
||||||
EntityManager.RemoveComponent<SentienceTargetComponent>(target.Owner);
|
RemComp<SentienceTargetComponent>(target.Owner);
|
||||||
var ghostRole = AddComp<GhostRoleComponent>(target.Owner);
|
var ghostRole = EnsureComp<GhostRoleComponent>(target.Owner);
|
||||||
AddComp<GhostTakeoverAvailableComponent>(target.Owner);
|
EnsureComp<GhostTakeoverAvailableComponent>(target.Owner);
|
||||||
ghostRole.RoleName = EntityManager.GetComponent<MetaDataComponent>(target.Owner).EntityName;
|
ghostRole.RoleName = MetaData(target.Owner).EntityName;
|
||||||
ghostRole.RoleDescription = Loc.GetString("station-event-random-sentience-role-description", ("name", ghostRole.RoleName));
|
ghostRole.RoleDescription = Loc.GetString("station-event-random-sentience-role-description", ("name", ghostRole.RoleName));
|
||||||
groups.Add(Loc.GetString(target.FlavorKind));
|
groups.Add(Loc.GetString(target.FlavorKind));
|
||||||
}
|
}
|
||||||
@@ -43,18 +41,15 @@ public sealed class RandomSentience : StationEventSystem
|
|||||||
var kind2 = groupList.Count > 1 ? groupList[1] : "???";
|
var kind2 = groupList.Count > 1 ? groupList[1] : "???";
|
||||||
var kind3 = groupList.Count > 2 ? groupList[2] : "???";
|
var kind3 = groupList.Count > 2 ? groupList[2] : "???";
|
||||||
|
|
||||||
var entSysMgr = IoCManager.Resolve<IEntitySystemManager>();
|
|
||||||
var stationSystem = entSysMgr.GetEntitySystem<StationSystem>();
|
|
||||||
var chatSystem = entSysMgr.GetEntitySystem<ChatSystem>();
|
|
||||||
foreach (var target in targetList)
|
foreach (var target in targetList)
|
||||||
{
|
{
|
||||||
var station = stationSystem.GetOwningStation(target.Owner);
|
var station = StationSystem.GetOwningStation(target.Owner);
|
||||||
if(station == null) continue;
|
if(station == null) continue;
|
||||||
stationsToNotify.Add((EntityUid) station);
|
stationsToNotify.Add((EntityUid) station);
|
||||||
}
|
}
|
||||||
foreach (var station in stationsToNotify)
|
foreach (var station in stationsToNotify)
|
||||||
{
|
{
|
||||||
chatSystem.DispatchStationAnnouncement(
|
ChatSystem.DispatchStationAnnouncement(
|
||||||
station,
|
station,
|
||||||
Loc.GetString("station-event-random-sentience-announcement",
|
Loc.GetString("station-event-random-sentience-announcement",
|
||||||
("kind1", kind1), ("kind2", kind2), ("kind3", kind3), ("amount", groupList.Count),
|
("kind1", kind1), ("kind2", kind2), ("kind3", kind3), ("amount", groupList.Count),
|
||||||
@@ -1,18 +0,0 @@
|
|||||||
namespace Content.Server.StationEvents.Events;
|
|
||||||
|
|
||||||
public sealed class RevenantSpawn : StationEventSystem
|
|
||||||
{
|
|
||||||
public override string Prototype => "RevenantSpawn";
|
|
||||||
private static readonly string RevenantPrototype = "MobRevenant";
|
|
||||||
|
|
||||||
public override void Started()
|
|
||||||
{
|
|
||||||
base.Started();
|
|
||||||
|
|
||||||
if (TryFindRandomTile(out _, out _, out _, out var coords))
|
|
||||||
{
|
|
||||||
Sawmill.Info($"Spawning revenant at {coords}");
|
|
||||||
EntityManager.SpawnEntity(RevenantPrototype, coords);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
19
Content.Server/StationEvents/Events/RevenantSpawnRule.cs
Normal file
19
Content.Server/StationEvents/Events/RevenantSpawnRule.cs
Normal file
@@ -0,0 +1,19 @@
|
|||||||
|
using Content.Server.GameTicking.Rules.Components;
|
||||||
|
using Content.Server.StationEvents.Components;
|
||||||
|
|
||||||
|
namespace Content.Server.StationEvents.Events;
|
||||||
|
|
||||||
|
public sealed class RevenantSpawnRule : StationEventSystem<RevenantSpawnRuleComponent>
|
||||||
|
{
|
||||||
|
protected override void Started(EntityUid uid, RevenantSpawnRuleComponent component, GameRuleComponent gameRule,
|
||||||
|
GameRuleStartedEvent args)
|
||||||
|
{
|
||||||
|
base.Started(uid, component, gameRule, args);
|
||||||
|
|
||||||
|
if (TryFindRandomTile(out _, out _, out _, out var coords))
|
||||||
|
{
|
||||||
|
Sawmill.Info($"Spawning revenant at {coords}");
|
||||||
|
Spawn(component.RevenantPrototype, coords);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,78 +0,0 @@
|
|||||||
using Content.Server.GameTicking.Rules.Configurations;
|
|
||||||
using Content.Server.Radio.Components;
|
|
||||||
using Content.Server.Radio;
|
|
||||||
using Robust.Shared.Random;
|
|
||||||
using Content.Server.Light.EntitySystems;
|
|
||||||
using Content.Server.Light.Components;
|
|
||||||
using Content.Shared.Radio.Components;
|
|
||||||
using Content.Shared.Doors.Components;
|
|
||||||
using Content.Shared.Doors.Systems;
|
|
||||||
|
|
||||||
namespace Content.Server.StationEvents.Events;
|
|
||||||
|
|
||||||
public sealed class SolarFlare : StationEventSystem
|
|
||||||
{
|
|
||||||
[Dependency] private readonly PoweredLightSystem _poweredLight = default!;
|
|
||||||
[Dependency] private readonly SharedDoorSystem _door = default!;
|
|
||||||
|
|
||||||
public override string Prototype => "SolarFlare";
|
|
||||||
|
|
||||||
private SolarFlareEventRuleConfiguration _event = default!;
|
|
||||||
private float _effectTimer = 0;
|
|
||||||
|
|
||||||
public override void Initialize()
|
|
||||||
{
|
|
||||||
base.Initialize();
|
|
||||||
SubscribeLocalEvent<RadioReceiveAttemptEvent>(OnRadioSendAttempt);
|
|
||||||
}
|
|
||||||
|
|
||||||
public override void Added()
|
|
||||||
{
|
|
||||||
base.Added();
|
|
||||||
|
|
||||||
if (Configuration is not SolarFlareEventRuleConfiguration ev)
|
|
||||||
return;
|
|
||||||
|
|
||||||
_event = ev;
|
|
||||||
_event.EndAfter = RobustRandom.Next(ev.MinEndAfter, ev.MaxEndAfter);
|
|
||||||
}
|
|
||||||
|
|
||||||
public override void Update(float frameTime)
|
|
||||||
{
|
|
||||||
base.Update(frameTime);
|
|
||||||
|
|
||||||
if (!RuleStarted)
|
|
||||||
return;
|
|
||||||
|
|
||||||
_effectTimer -= frameTime;
|
|
||||||
if (_effectTimer < 0)
|
|
||||||
{
|
|
||||||
_effectTimer += 1;
|
|
||||||
var lightQuery = EntityQueryEnumerator<PoweredLightComponent>();
|
|
||||||
while (lightQuery.MoveNext(out var uid, out var light))
|
|
||||||
{
|
|
||||||
if (RobustRandom.Prob(_event.LightBreakChancePerSecond))
|
|
||||||
_poweredLight.TryDestroyBulb(uid, light);
|
|
||||||
}
|
|
||||||
var airlockQuery = EntityQueryEnumerator<AirlockComponent, DoorComponent>();
|
|
||||||
while (airlockQuery.MoveNext(out var uid, out var airlock, out var door))
|
|
||||||
{
|
|
||||||
if (airlock.AutoClose && RobustRandom.Prob(_event.DoorToggleChancePerSecond))
|
|
||||||
_door.TryToggleDoor(uid, door);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (Elapsed > _event.EndAfter)
|
|
||||||
{
|
|
||||||
ForceEndSelf();
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private void OnRadioSendAttempt(ref RadioReceiveAttemptEvent args)
|
|
||||||
{
|
|
||||||
if (RuleStarted && _event.AffectedChannels.Contains(args.Channel.ID))
|
|
||||||
if (!_event.OnlyJamHeadsets || (HasComp<HeadsetComponent>(args.RadioReceiver) || HasComp<HeadsetComponent>(args.RadioSource)))
|
|
||||||
args.Cancelled = true;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
64
Content.Server/StationEvents/Events/SolarFlareRule.cs
Normal file
64
Content.Server/StationEvents/Events/SolarFlareRule.cs
Normal file
@@ -0,0 +1,64 @@
|
|||||||
|
using Content.Server.GameTicking.Rules.Components;
|
||||||
|
using Content.Server.Radio;
|
||||||
|
using Robust.Shared.Random;
|
||||||
|
using Content.Server.Light.EntitySystems;
|
||||||
|
using Content.Server.Light.Components;
|
||||||
|
using Content.Server.StationEvents.Components;
|
||||||
|
using Content.Shared.Radio.Components;
|
||||||
|
using Content.Shared.Doors.Components;
|
||||||
|
using Content.Shared.Doors.Systems;
|
||||||
|
|
||||||
|
namespace Content.Server.StationEvents.Events;
|
||||||
|
|
||||||
|
public sealed class SolarFlareRule : StationEventSystem<SolarFlareRuleComponent>
|
||||||
|
{
|
||||||
|
[Dependency] private readonly PoweredLightSystem _poweredLight = default!;
|
||||||
|
[Dependency] private readonly SharedDoorSystem _door = default!;
|
||||||
|
|
||||||
|
private float _effectTimer = 0;
|
||||||
|
|
||||||
|
public override void Initialize()
|
||||||
|
{
|
||||||
|
base.Initialize();
|
||||||
|
SubscribeLocalEvent<RadioReceiveAttemptEvent>(OnRadioSendAttempt);
|
||||||
|
}
|
||||||
|
|
||||||
|
protected override void ActiveTick(EntityUid uid, SolarFlareRuleComponent component, GameRuleComponent gameRule, float frameTime)
|
||||||
|
{
|
||||||
|
base.ActiveTick(uid, component, gameRule, frameTime);
|
||||||
|
|
||||||
|
_effectTimer -= frameTime;
|
||||||
|
if (_effectTimer < 0)
|
||||||
|
{
|
||||||
|
_effectTimer += 1;
|
||||||
|
var lightQuery = EntityQueryEnumerator<PoweredLightComponent>();
|
||||||
|
while (lightQuery.MoveNext(out var lightEnt, out var light))
|
||||||
|
{
|
||||||
|
if (RobustRandom.Prob(component.LightBreakChancePerSecond))
|
||||||
|
_poweredLight.TryDestroyBulb(lightEnt, light);
|
||||||
|
}
|
||||||
|
var airlockQuery = EntityQueryEnumerator<AirlockComponent, DoorComponent>();
|
||||||
|
while (airlockQuery.MoveNext(out var airlockEnt, out var airlock, out var door))
|
||||||
|
{
|
||||||
|
if (airlock.AutoClose && RobustRandom.Prob(component.DoorToggleChancePerSecond))
|
||||||
|
_door.TryToggleDoor(airlockEnt, door);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void OnRadioSendAttempt(ref RadioReceiveAttemptEvent args)
|
||||||
|
{
|
||||||
|
var query = EntityQueryEnumerator<SolarFlareRuleComponent, GameRuleComponent>();
|
||||||
|
while (query.MoveNext(out var uid, out var flare, out var gameRule))
|
||||||
|
{
|
||||||
|
if (!GameTicker.IsGameRuleActive(uid, gameRule))
|
||||||
|
continue;
|
||||||
|
|
||||||
|
if (!flare.AffectedChannels.Contains(args.Channel.ID))
|
||||||
|
continue;
|
||||||
|
|
||||||
|
if (!flare.OnlyJamHeadsets || (HasComp<HeadsetComponent>(args.RadioReceiver) || HasComp<HeadsetComponent>(args.RadioSource)))
|
||||||
|
args.Cancelled = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,32 +0,0 @@
|
|||||||
using Content.Server.StationEvents.Components;
|
|
||||||
using Content.Shared.Actions;
|
|
||||||
using Robust.Shared.Random;
|
|
||||||
using System.Linq;
|
|
||||||
|
|
||||||
namespace Content.Server.StationEvents.Events;
|
|
||||||
|
|
||||||
public sealed class SpiderSpawn : StationEventSystem
|
|
||||||
{
|
|
||||||
public override string Prototype => "SpiderSpawn";
|
|
||||||
|
|
||||||
public override void Started()
|
|
||||||
{
|
|
||||||
base.Started();
|
|
||||||
var spawnLocations = EntityManager.EntityQuery<VentCritterSpawnLocationComponent>().ToList();
|
|
||||||
RobustRandom.Shuffle(spawnLocations);
|
|
||||||
|
|
||||||
var mod = Math.Sqrt(GetSeverityModifier());
|
|
||||||
|
|
||||||
var spawnAmount = (int) (RobustRandom.Next(4, 8) * mod);
|
|
||||||
Sawmill.Info($"Spawning {spawnAmount} of spiders");
|
|
||||||
foreach (var location in spawnLocations)
|
|
||||||
{
|
|
||||||
if (spawnAmount-- == 0)
|
|
||||||
break;
|
|
||||||
|
|
||||||
var coords = EntityManager.GetComponent<TransformComponent>(location.Owner);
|
|
||||||
|
|
||||||
EntityManager.SpawnEntity("MobGiantSpiderAngry", coords.Coordinates);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user