Refactor antag rule code (#23445)
* Initial Pass, Rev, Thief * Zombie initial pass * Rebase, Traitor * Nukeops, More overloads * Revert RevolutionaryRuleComponent * Use TryRoundStartAttempt, Rewrite nukie spawning * Comments, Add task scheduler to GameRuleSystem * Zombie initial testing done * Sort methods, rework GameRuleTask * Add CCVar, Initial testing continues * Might as well get rid of the obsolete logging * Oops, i dont know how to log apparently * Suggested formatting fixes * Suggested changes * Fix merge issues * Minor optimisation * Allowed thief to choose other antags * Review changes * Spawn items on floor first, then inserting * minor tweaks * Shift as much as possible to ProtoId<> * Remove unneeded * Add exclusive antag attribute * Fix merge issues * Minor formatting fix * Convert to struct * Cleanup * Review cleanup (need to test a lot) * Some fixes, (mostly) tested * oop * Pass tests (for real) --------- Co-authored-by: Rainfall <rainfey0+git@gmail.com> Co-authored-by: AJCM <AJCM@tutanota.com>
This commit is contained in:
@@ -3,6 +3,7 @@ using Content.Shared.Chat;
|
|||||||
using Content.Shared.NukeOps;
|
using Content.Shared.NukeOps;
|
||||||
using JetBrains.Annotations;
|
using JetBrains.Annotations;
|
||||||
using Robust.Shared.Configuration;
|
using Robust.Shared.Configuration;
|
||||||
|
using Robust.Shared.Timing;
|
||||||
|
|
||||||
namespace Content.Client.NukeOps;
|
namespace Content.Client.NukeOps;
|
||||||
|
|
||||||
@@ -10,6 +11,8 @@ namespace Content.Client.NukeOps;
|
|||||||
public sealed class WarDeclaratorBoundUserInterface : BoundUserInterface
|
public sealed class WarDeclaratorBoundUserInterface : BoundUserInterface
|
||||||
{
|
{
|
||||||
[Dependency] private readonly IConfigurationManager _cfg = default!;
|
[Dependency] private readonly IConfigurationManager _cfg = default!;
|
||||||
|
[Dependency] private readonly IGameTiming _gameTiming = default!;
|
||||||
|
[Dependency] private readonly ILocalizationManager _localizationManager = default!;
|
||||||
|
|
||||||
[ViewVariables]
|
[ViewVariables]
|
||||||
private WarDeclaratorWindow? _window;
|
private WarDeclaratorWindow? _window;
|
||||||
@@ -20,7 +23,7 @@ public sealed class WarDeclaratorBoundUserInterface : BoundUserInterface
|
|||||||
{
|
{
|
||||||
base.Open();
|
base.Open();
|
||||||
|
|
||||||
_window = new WarDeclaratorWindow();
|
_window = new WarDeclaratorWindow(_gameTiming, _localizationManager);
|
||||||
if (State != null)
|
if (State != null)
|
||||||
UpdateState(State);
|
UpdateState(State);
|
||||||
|
|
||||||
@@ -42,7 +45,8 @@ public sealed class WarDeclaratorBoundUserInterface : BoundUserInterface
|
|||||||
protected override void Dispose(bool disposing)
|
protected override void Dispose(bool disposing)
|
||||||
{
|
{
|
||||||
base.Dispose(disposing);
|
base.Dispose(disposing);
|
||||||
if (disposing) _window?.Dispose();
|
if (disposing)
|
||||||
|
_window?.Dispose();
|
||||||
}
|
}
|
||||||
|
|
||||||
private void OnWarDeclaratorActivated(string message)
|
private void OnWarDeclaratorActivated(string message)
|
||||||
|
|||||||
@@ -1,4 +1,5 @@
|
|||||||
<DefaultWindow xmlns="https://spacestation14.io"
|
<controls:FancyWindow xmlns="https://spacestation14.io"
|
||||||
|
xmlns:controls="clr-namespace:Content.Client.UserInterface.Controls"
|
||||||
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
|
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
|
||||||
Title="{Loc 'war-declarator-ui-header'}">
|
Title="{Loc 'war-declarator-ui-header'}">
|
||||||
<BoxContainer Orientation="Vertical" SeparationOverride="4" MinWidth="440">
|
<BoxContainer Orientation="Vertical" SeparationOverride="4" MinWidth="440">
|
||||||
@@ -7,12 +8,16 @@
|
|||||||
MinHeight="200"
|
MinHeight="200"
|
||||||
Access="Public" />
|
Access="Public" />
|
||||||
<Button Name="WarButton"
|
<Button Name="WarButton"
|
||||||
Text="{Loc 'war-declarator-ui-war-button'}"
|
Text="{Loc 'war-declarator-ui-try-war-button'}"
|
||||||
StyleClasses="Caution"
|
StyleClasses="Caution"
|
||||||
Access="Public"/>
|
Access="Public"/>
|
||||||
|
<BoxContainer Orientation="Vertical" HorizontalExpand="True">
|
||||||
<Label Name="StatusLabel"
|
<Label Name="StatusLabel"
|
||||||
|
Align="Center"
|
||||||
Access="Public"/>
|
Access="Public"/>
|
||||||
<Label Name="InfoLabel"
|
<Label Name="InfoLabel"
|
||||||
|
Align="Center"
|
||||||
Access="Public"/>
|
Access="Public"/>
|
||||||
</BoxContainer>
|
</BoxContainer>
|
||||||
</DefaultWindow>
|
</BoxContainer>
|
||||||
|
</controls:FancyWindow>
|
||||||
|
|||||||
@@ -1,8 +1,7 @@
|
|||||||
using Content.Client.Stylesheets;
|
using Content.Client.Stylesheets;
|
||||||
|
using Content.Client.UserInterface.Controls;
|
||||||
using Content.Shared.NukeOps;
|
using Content.Shared.NukeOps;
|
||||||
using Robust.Client.AutoGenerated;
|
using Robust.Client.AutoGenerated;
|
||||||
using Robust.Client.Graphics;
|
|
||||||
using Robust.Client.UserInterface.CustomControls;
|
|
||||||
using Robust.Client.UserInterface.XAML;
|
using Robust.Client.UserInterface.XAML;
|
||||||
using Robust.Shared.Timing;
|
using Robust.Shared.Timing;
|
||||||
using Robust.Shared.Utility;
|
using Robust.Shared.Utility;
|
||||||
@@ -10,74 +9,83 @@ using Robust.Shared.Utility;
|
|||||||
namespace Content.Client.NukeOps;
|
namespace Content.Client.NukeOps;
|
||||||
|
|
||||||
[GenerateTypedNameReferences]
|
[GenerateTypedNameReferences]
|
||||||
public sealed partial class WarDeclaratorWindow : DefaultWindow
|
public sealed partial class WarDeclaratorWindow : FancyWindow
|
||||||
{
|
{
|
||||||
private readonly IGameTiming _gameTiming;
|
private readonly IGameTiming _gameTiming;
|
||||||
|
|
||||||
public event Action<string>? OnActivated;
|
public event Action<string>? OnActivated;
|
||||||
|
|
||||||
private TimeSpan _endTime;
|
private TimeSpan _endTime;
|
||||||
private TimeSpan _timeStamp;
|
private TimeSpan _shuttleDisabledTime;
|
||||||
private WarConditionStatus _status;
|
private WarConditionStatus _status;
|
||||||
|
|
||||||
public WarDeclaratorWindow()
|
public WarDeclaratorWindow(IGameTiming gameTiming, ILocalizationManager localizationManager)
|
||||||
{
|
{
|
||||||
RobustXamlLoader.Load(this);
|
RobustXamlLoader.Load(this);
|
||||||
|
|
||||||
_gameTiming = IoCManager.Resolve<IGameTiming>();
|
_gameTiming = gameTiming;
|
||||||
|
|
||||||
WarButton.OnPressed += (_) => OnActivated?.Invoke(Rope.Collapse(MessageEdit.TextRope));
|
WarButton.OnPressed += (_) => OnActivated?.Invoke(Rope.Collapse(MessageEdit.TextRope));
|
||||||
|
|
||||||
var loc = IoCManager.Resolve<ILocalizationManager>();
|
MessageEdit.Placeholder = new Rope.Leaf(localizationManager.GetString("war-declarator-message-placeholder"));
|
||||||
MessageEdit.Placeholder = new Rope.Leaf(loc.GetString("war-declarator-message-placeholder"));
|
|
||||||
}
|
}
|
||||||
|
|
||||||
protected override void Draw(DrawingHandleScreen handle)
|
protected override void FrameUpdate(FrameEventArgs args)
|
||||||
{
|
{
|
||||||
base.Draw(handle);
|
|
||||||
UpdateTimer();
|
UpdateTimer();
|
||||||
}
|
}
|
||||||
|
|
||||||
public void UpdateState(WarDeclaratorBoundUserInterfaceState state)
|
public void UpdateState(WarDeclaratorBoundUserInterfaceState state)
|
||||||
{
|
{
|
||||||
WarButton.Disabled = state.Status != WarConditionStatus.YES_WAR;
|
if (state.Status == null)
|
||||||
|
return;
|
||||||
|
|
||||||
|
WarButton.Disabled = state.Status == WarConditionStatus.WarReady;
|
||||||
|
|
||||||
_timeStamp = state.Delay;
|
|
||||||
_endTime = state.EndTime;
|
_endTime = state.EndTime;
|
||||||
_status = state.Status;
|
_shuttleDisabledTime = state.ShuttleDisabledTime;
|
||||||
|
_status = state.Status.Value;
|
||||||
|
|
||||||
switch(state.Status)
|
UpdateStatus(state.Status.Value);
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
private void UpdateStatus(WarConditionStatus status)
|
||||||
{
|
{
|
||||||
case WarConditionStatus.WAR_READY:
|
switch (status)
|
||||||
|
{
|
||||||
|
case WarConditionStatus.WarReady:
|
||||||
|
WarButton.Disabled = true;
|
||||||
StatusLabel.Text = Loc.GetString("war-declarator-boost-declared");
|
StatusLabel.Text = Loc.GetString("war-declarator-boost-declared");
|
||||||
InfoLabel.Text = Loc.GetString("war-declarator-conditions-ready");
|
|
||||||
StatusLabel.SetOnlyStyleClass(StyleNano.StyleClassPowerStateLow);
|
|
||||||
break;
|
|
||||||
case WarConditionStatus.WAR_DELAY:
|
|
||||||
StatusLabel.Text = Loc.GetString("war-declarator-boost-declared-delay");
|
|
||||||
UpdateTimer();
|
UpdateTimer();
|
||||||
StatusLabel.SetOnlyStyleClass(StyleNano.StyleClassPowerStateLow);
|
StatusLabel.SetOnlyStyleClass(StyleNano.StyleClassPowerStateLow);
|
||||||
break;
|
break;
|
||||||
case WarConditionStatus.YES_WAR:
|
case WarConditionStatus.YesWar:
|
||||||
|
WarButton.Text = Loc.GetString("war-declarator-ui-war-button");
|
||||||
StatusLabel.Text = Loc.GetString("war-declarator-boost-possible");
|
StatusLabel.Text = Loc.GetString("war-declarator-boost-possible");
|
||||||
UpdateTimer();
|
UpdateTimer();
|
||||||
StatusLabel.SetOnlyStyleClass(StyleNano.StyleClassPowerStateGood);
|
StatusLabel.SetOnlyStyleClass(StyleNano.StyleClassPowerStateGood);
|
||||||
break;
|
break;
|
||||||
case WarConditionStatus.NO_WAR_SMALL_CREW:
|
case WarConditionStatus.NoWarSmallCrew:
|
||||||
StatusLabel.Text = Loc.GetString("war-declarator-boost-impossible");
|
StatusLabel.Text = Loc.GetString("war-declarator-boost-impossible");
|
||||||
InfoLabel.Text = Loc.GetString("war-declarator-conditions-small-crew", ("min", state.MinCrew));
|
InfoLabel.Text = Loc.GetString("war-declarator-conditions-small-crew");
|
||||||
StatusLabel.SetOnlyStyleClass(StyleNano.StyleClassPowerStateNone);
|
StatusLabel.SetOnlyStyleClass(StyleNano.StyleClassPowerStateNone);
|
||||||
break;
|
break;
|
||||||
case WarConditionStatus.NO_WAR_SHUTTLE_DEPARTED:
|
case WarConditionStatus.NoWarShuttleDeparted:
|
||||||
StatusLabel.Text = Loc.GetString("war-declarator-boost-impossible");
|
StatusLabel.Text = Loc.GetString("war-declarator-boost-impossible");
|
||||||
InfoLabel.Text = Loc.GetString("war-declarator-conditions-left-outpost");
|
InfoLabel.Text = Loc.GetString("war-declarator-conditions-left-outpost");
|
||||||
StatusLabel.SetOnlyStyleClass(StyleNano.StyleClassPowerStateNone);
|
StatusLabel.SetOnlyStyleClass(StyleNano.StyleClassPowerStateNone);
|
||||||
break;
|
break;
|
||||||
case WarConditionStatus.NO_WAR_TIMEOUT:
|
case WarConditionStatus.NoWarTimeout:
|
||||||
StatusLabel.Text = Loc.GetString("war-declarator-boost-impossible");
|
StatusLabel.Text = Loc.GetString("war-declarator-boost-impossible");
|
||||||
InfoLabel.Text = Loc.GetString("war-declarator-conditions-time-out");
|
InfoLabel.Text = Loc.GetString("war-declarator-conditions-time-out");
|
||||||
StatusLabel.SetOnlyStyleClass(StyleNano.StyleClassPowerStateNone);
|
StatusLabel.SetOnlyStyleClass(StyleNano.StyleClassPowerStateNone);
|
||||||
break;
|
break;
|
||||||
|
case WarConditionStatus.NoWarUnknown:
|
||||||
|
StatusLabel.Text = Loc.GetString("war-declarator-boost-impossible");
|
||||||
|
InfoLabel.Text = Loc.GetString("war-declarator-conditions-unknown");
|
||||||
|
StatusLabel.SetOnlyStyleClass(StyleNano.StyleClassPowerStateNone);
|
||||||
|
break;
|
||||||
default:
|
default:
|
||||||
StatusLabel.Text = Loc.GetString("war-declarator-boost-impossible");
|
StatusLabel.Text = Loc.GetString("war-declarator-boost-impossible");
|
||||||
InfoLabel.Text = Loc.GetString("war-declarator-conditions-unknown");
|
InfoLabel.Text = Loc.GetString("war-declarator-conditions-unknown");
|
||||||
@@ -86,43 +94,24 @@ public sealed partial class WarDeclaratorWindow : DefaultWindow
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public void UpdateTimer()
|
private void UpdateTimer()
|
||||||
{
|
{
|
||||||
switch(_status)
|
switch(_status)
|
||||||
{
|
{
|
||||||
case WarConditionStatus.YES_WAR:
|
case WarConditionStatus.YesWar:
|
||||||
var gameruleTime = _gameTiming.CurTime.Subtract(_timeStamp);
|
var timeLeft = _endTime.Subtract(_gameTiming.CurTime);
|
||||||
var timeLeft = _endTime.Subtract(gameruleTime);
|
|
||||||
|
|
||||||
if (timeLeft > TimeSpan.Zero)
|
if (timeLeft > TimeSpan.Zero)
|
||||||
{
|
InfoLabel.Text = Loc.GetString("war-declarator-boost-timer", ("time", timeLeft.ToString("mm\\:ss")));
|
||||||
InfoLabel.Text = Loc.GetString("war-declarator-boost-timer", ("minutes", timeLeft.Minutes), ("seconds", timeLeft.Seconds));
|
|
||||||
}
|
|
||||||
else
|
else
|
||||||
{
|
UpdateStatus(WarConditionStatus.NoWarTimeout);
|
||||||
_status = WarConditionStatus.NO_WAR_TIMEOUT;
|
|
||||||
StatusLabel.Text = Loc.GetString("war-declarator-boost-impossible");
|
|
||||||
InfoLabel.Text = Loc.GetString("war-declarator-conditions-time-out");
|
|
||||||
StatusLabel.SetOnlyStyleClass(StyleNano.StyleClassPowerStateNone);
|
|
||||||
WarButton.Disabled = true;
|
|
||||||
}
|
|
||||||
break;
|
break;
|
||||||
case WarConditionStatus.WAR_DELAY:
|
|
||||||
var timeAfterDeclaration = _gameTiming.CurTime.Subtract(_timeStamp);
|
|
||||||
var timeRemain = _endTime.Subtract(timeAfterDeclaration);
|
|
||||||
|
|
||||||
if (timeRemain > TimeSpan.Zero)
|
case WarConditionStatus.WarReady:
|
||||||
{
|
var time = _shuttleDisabledTime.Subtract(_gameTiming.CurTime);
|
||||||
InfoLabel.Text = Loc.GetString("war-declarator-boost-timer", ("minutes", timeRemain.Minutes), ("seconds", timeRemain.Seconds));
|
if (time > TimeSpan.Zero)
|
||||||
}
|
InfoLabel.Text = Loc.GetString("war-declarator-boost-timer", ("time", time.ToString("mm\\:ss")));
|
||||||
else
|
else
|
||||||
{
|
|
||||||
_status = WarConditionStatus.WAR_READY;
|
|
||||||
StatusLabel.Text = Loc.GetString("war-declarator-boost-declared");
|
|
||||||
InfoLabel.Text = Loc.GetString("war-declarator-conditions-ready");
|
InfoLabel.Text = Loc.GetString("war-declarator-conditions-ready");
|
||||||
StatusLabel.SetOnlyStyleClass(StyleNano.StyleClassPowerStateLow);
|
|
||||||
WarButton.Disabled = true;
|
|
||||||
}
|
|
||||||
break;
|
break;
|
||||||
default:
|
default:
|
||||||
return;
|
return;
|
||||||
|
|||||||
@@ -31,6 +31,7 @@ namespace Content.IntegrationTests.Tests
|
|||||||
{
|
{
|
||||||
"CentComm",
|
"CentComm",
|
||||||
"Dart",
|
"Dart",
|
||||||
|
"NukieOutpost"
|
||||||
};
|
};
|
||||||
|
|
||||||
private static readonly string[] Grids =
|
private static readonly string[] Grids =
|
||||||
@@ -38,7 +39,7 @@ namespace Content.IntegrationTests.Tests
|
|||||||
"/Maps/centcomm.yml",
|
"/Maps/centcomm.yml",
|
||||||
"/Maps/Shuttles/cargo.yml",
|
"/Maps/Shuttles/cargo.yml",
|
||||||
"/Maps/Shuttles/emergency.yml",
|
"/Maps/Shuttles/emergency.yml",
|
||||||
"/Maps/infiltrator.yml",
|
"/Maps/Shuttles/infiltrator.yml",
|
||||||
};
|
};
|
||||||
|
|
||||||
private static readonly string[] GameMaps =
|
private static readonly string[] GameMaps =
|
||||||
@@ -53,6 +54,7 @@ namespace Content.IntegrationTests.Tests
|
|||||||
"Bagel",
|
"Bagel",
|
||||||
"Origin",
|
"Origin",
|
||||||
"CentComm",
|
"CentComm",
|
||||||
|
"NukieOutpost",
|
||||||
"Box",
|
"Box",
|
||||||
"Europa",
|
"Europa",
|
||||||
"Saltern",
|
"Saltern",
|
||||||
|
|||||||
@@ -1,15 +1,12 @@
|
|||||||
using Content.Server.GameTicking;
|
|
||||||
using Content.Server.GameTicking.Rules;
|
using Content.Server.GameTicking.Rules;
|
||||||
using Content.Server.StationEvents.Events;
|
|
||||||
using Content.Server.Zombies;
|
using Content.Server.Zombies;
|
||||||
using Content.Shared.Administration;
|
using Content.Shared.Administration;
|
||||||
using Content.Shared.Database;
|
using Content.Shared.Database;
|
||||||
using Content.Shared.Humanoid;
|
using Content.Shared.Humanoid;
|
||||||
using Content.Shared.Mind;
|
|
||||||
using Content.Shared.Mind.Components;
|
using Content.Shared.Mind.Components;
|
||||||
using Content.Shared.Verbs;
|
using Content.Shared.Verbs;
|
||||||
using Robust.Shared.Utility;
|
|
||||||
using Robust.Shared.Player;
|
using Robust.Shared.Player;
|
||||||
|
using Robust.Shared.Utility;
|
||||||
|
|
||||||
namespace Content.Server.Administration.Systems;
|
namespace Content.Server.Administration.Systems;
|
||||||
|
|
||||||
@@ -21,7 +18,6 @@ public sealed partial class AdminVerbSystem
|
|||||||
[Dependency] private readonly NukeopsRuleSystem _nukeopsRule = default!;
|
[Dependency] private readonly NukeopsRuleSystem _nukeopsRule = default!;
|
||||||
[Dependency] private readonly PiratesRuleSystem _piratesRule = default!;
|
[Dependency] private readonly PiratesRuleSystem _piratesRule = default!;
|
||||||
[Dependency] private readonly RevolutionaryRuleSystem _revolutionaryRule = default!;
|
[Dependency] private readonly RevolutionaryRuleSystem _revolutionaryRule = default!;
|
||||||
[Dependency] private readonly SharedMindSystem _minds = default!;
|
|
||||||
|
|
||||||
// All antag verbs have names so invokeverb works.
|
// All antag verbs have names so invokeverb works.
|
||||||
private void AddAntagVerbs(GetVerbsEvent<Verb> args)
|
private void AddAntagVerbs(GetVerbsEvent<Verb> args)
|
||||||
@@ -34,7 +30,7 @@ public sealed partial class AdminVerbSystem
|
|||||||
if (!_adminManager.HasAdminFlag(player, AdminFlags.Fun))
|
if (!_adminManager.HasAdminFlag(player, AdminFlags.Fun))
|
||||||
return;
|
return;
|
||||||
|
|
||||||
if (!TryComp<MindContainerComponent>(args.Target, out var targetMindComp))
|
if (!HasComp<MindContainerComponent>(args.Target))
|
||||||
return;
|
return;
|
||||||
|
|
||||||
Verb traitor = new()
|
Verb traitor = new()
|
||||||
@@ -44,12 +40,9 @@ public sealed partial class AdminVerbSystem
|
|||||||
Icon = new SpriteSpecifier.Rsi(new ResPath("/Textures/Structures/Wallmounts/posters.rsi"), "poster5_contraband"),
|
Icon = new SpriteSpecifier.Rsi(new ResPath("/Textures/Structures/Wallmounts/posters.rsi"), "poster5_contraband"),
|
||||||
Act = () =>
|
Act = () =>
|
||||||
{
|
{
|
||||||
if (!_minds.TryGetSession(targetMindComp.Mind, out var session))
|
|
||||||
return;
|
|
||||||
|
|
||||||
// if its a monkey or mouse or something dont give uplink or objectives
|
// if its a monkey or mouse or something dont give uplink or objectives
|
||||||
var isHuman = HasComp<HumanoidAppearanceComponent>(args.Target);
|
var isHuman = HasComp<HumanoidAppearanceComponent>(args.Target);
|
||||||
_traitorRule.MakeTraitor(session, giveUplink: isHuman, giveObjectives: isHuman);
|
_traitorRule.MakeTraitorAdmin(args.Target, giveUplink: isHuman, giveObjectives: isHuman);
|
||||||
},
|
},
|
||||||
Impact = LogImpact.High,
|
Impact = LogImpact.High,
|
||||||
Message = Loc.GetString("admin-verb-make-traitor"),
|
Message = Loc.GetString("admin-verb-make-traitor"),
|
||||||
@@ -78,10 +71,7 @@ public sealed partial class AdminVerbSystem
|
|||||||
Icon = new SpriteSpecifier.Rsi(new("/Textures/Structures/Wallmounts/signs.rsi"), "radiation"),
|
Icon = new SpriteSpecifier.Rsi(new("/Textures/Structures/Wallmounts/signs.rsi"), "radiation"),
|
||||||
Act = () =>
|
Act = () =>
|
||||||
{
|
{
|
||||||
if (!_minds.TryGetMind(args.Target, out var mindId, out var mind))
|
_nukeopsRule.MakeLoneNukie(args.Target);
|
||||||
return;
|
|
||||||
|
|
||||||
_nukeopsRule.MakeLoneNukie(mindId, mind);
|
|
||||||
},
|
},
|
||||||
Impact = LogImpact.High,
|
Impact = LogImpact.High,
|
||||||
Message = Loc.GetString("admin-verb-make-nuclear-operative"),
|
Message = Loc.GetString("admin-verb-make-nuclear-operative"),
|
||||||
@@ -95,10 +85,7 @@ public sealed partial class AdminVerbSystem
|
|||||||
Icon = new SpriteSpecifier.Rsi(new("/Textures/Clothing/Head/Hats/pirate.rsi"), "icon"),
|
Icon = new SpriteSpecifier.Rsi(new("/Textures/Clothing/Head/Hats/pirate.rsi"), "icon"),
|
||||||
Act = () =>
|
Act = () =>
|
||||||
{
|
{
|
||||||
if (!_minds.TryGetMind(args.Target, out var mindId, out var mind))
|
_piratesRule.MakePirate(args.Target);
|
||||||
return;
|
|
||||||
|
|
||||||
_piratesRule.MakePirate(mindId, mind);
|
|
||||||
},
|
},
|
||||||
Impact = LogImpact.High,
|
Impact = LogImpact.High,
|
||||||
Message = Loc.GetString("admin-verb-make-pirate"),
|
Message = Loc.GetString("admin-verb-make-pirate"),
|
||||||
@@ -113,9 +100,7 @@ public sealed partial class AdminVerbSystem
|
|||||||
Icon = new SpriteSpecifier.Rsi(new("/Textures/Interface/Misc/job_icons.rsi"), "HeadRevolutionary"),
|
Icon = new SpriteSpecifier.Rsi(new("/Textures/Interface/Misc/job_icons.rsi"), "HeadRevolutionary"),
|
||||||
Act = () =>
|
Act = () =>
|
||||||
{
|
{
|
||||||
if (!_minds.TryGetMind(args.Target, out var mindId, out var mind))
|
_revolutionaryRule.OnHeadRevAdmin(args.Target);
|
||||||
return;
|
|
||||||
_revolutionaryRule.OnHeadRevAdmin(mindId, mind);
|
|
||||||
},
|
},
|
||||||
Impact = LogImpact.High,
|
Impact = LogImpact.High,
|
||||||
Message = Loc.GetString("admin-verb-make-head-rev"),
|
Message = Loc.GetString("admin-verb-make-head-rev"),
|
||||||
@@ -129,10 +114,7 @@ public sealed partial class AdminVerbSystem
|
|||||||
Icon = new SpriteSpecifier.Rsi(new ResPath("/Textures/Clothing/Hands/Gloves/ihscombat.rsi"), "icon"),
|
Icon = new SpriteSpecifier.Rsi(new ResPath("/Textures/Clothing/Hands/Gloves/ihscombat.rsi"), "icon"),
|
||||||
Act = () =>
|
Act = () =>
|
||||||
{
|
{
|
||||||
if (!_minds.TryGetSession(targetMindComp.Mind, out var session))
|
_thief.AdminMakeThief(args.Target, false); //Midround add pacified is bad
|
||||||
return;
|
|
||||||
|
|
||||||
_thief.AdminMakeThief(session, false); //Midround add pacific is bad
|
|
||||||
},
|
},
|
||||||
Impact = LogImpact.High,
|
Impact = LogImpact.High,
|
||||||
Message = Loc.GetString("admin-verb-make-thief"),
|
Message = Loc.GetString("admin-verb-make-thief"),
|
||||||
|
|||||||
@@ -1,305 +1,347 @@
|
|||||||
using Content.Server.GameTicking.Rules;
|
using Content.Server.GameTicking.Rules;
|
||||||
using Content.Server.GameTicking.Rules.Components;
|
using Content.Server.GameTicking.Rules.Components;
|
||||||
using Content.Server.Roles.Jobs;
|
|
||||||
using Content.Server.Preferences.Managers;
|
|
||||||
using Content.Shared.Humanoid;
|
|
||||||
using Content.Shared.Preferences;
|
|
||||||
using Robust.Server.Player;
|
|
||||||
using System.Linq;
|
|
||||||
using Content.Server.Mind;
|
using Content.Server.Mind;
|
||||||
using Robust.Shared.Random;
|
using Content.Server.Preferences.Managers;
|
||||||
using Robust.Shared.Map;
|
using Content.Server.Roles.Jobs;
|
||||||
using System.Numerics;
|
using Content.Server.Shuttles.Components;
|
||||||
using Content.Shared.Inventory;
|
using Content.Shared.Antag;
|
||||||
using Content.Server.Storage.EntitySystems;
|
using Content.Shared.Humanoid;
|
||||||
using Robust.Shared.Audio;
|
using Content.Shared.Players;
|
||||||
using Robust.Server.GameObjects;
|
using Content.Shared.Preferences;
|
||||||
using Content.Server.Chat.Managers;
|
using Content.Shared.Roles;
|
||||||
using Content.Server.GameTicking;
|
|
||||||
using Robust.Shared.Containers;
|
|
||||||
using Content.Shared.Mobs.Components;
|
|
||||||
using Content.Server.Station.Systems;
|
|
||||||
using Content.Server.Shuttles.Systems;
|
|
||||||
using Content.Shared.Mobs;
|
|
||||||
using Robust.Server.Audio;
|
using Robust.Server.Audio;
|
||||||
using Robust.Server.Containers;
|
using Robust.Shared.Audio;
|
||||||
using Robust.Shared.Player;
|
using Robust.Shared.Player;
|
||||||
using Robust.Shared.Prototypes;
|
using Robust.Shared.Prototypes;
|
||||||
using Content.Server.Shuttles.Components;
|
using Robust.Shared.Random;
|
||||||
|
using System.Linq;
|
||||||
|
using Content.Shared.Chat;
|
||||||
|
using Robust.Shared.Enums;
|
||||||
|
|
||||||
namespace Content.Server.Antag;
|
namespace Content.Server.Antag;
|
||||||
|
|
||||||
public sealed class AntagSelectionSystem : GameRuleSystem<GameRuleComponent>
|
public sealed class AntagSelectionSystem : GameRuleSystem<GameRuleComponent>
|
||||||
{
|
{
|
||||||
[Dependency] private readonly IChatManager _chatManager = default!;
|
|
||||||
[Dependency] private readonly IServerPreferencesManager _prefs = default!;
|
[Dependency] private readonly IServerPreferencesManager _prefs = default!;
|
||||||
[Dependency] private readonly IPlayerManager _playerSystem = default!;
|
|
||||||
[Dependency] private readonly IRobustRandom _random = default!;
|
|
||||||
[Dependency] private readonly AudioSystem _audioSystem = default!;
|
[Dependency] private readonly AudioSystem _audioSystem = default!;
|
||||||
[Dependency] private readonly ContainerSystem _containerSystem = default!;
|
|
||||||
[Dependency] private readonly JobSystem _jobs = default!;
|
[Dependency] private readonly JobSystem _jobs = default!;
|
||||||
[Dependency] private readonly MindSystem _mindSystem = default!;
|
[Dependency] private readonly MindSystem _mindSystem = default!;
|
||||||
[Dependency] private readonly InventorySystem _inventory = default!;
|
[Dependency] private readonly SharedRoleSystem _roleSystem = default!;
|
||||||
[Dependency] private readonly StorageSystem _storageSystem = default!;
|
|
||||||
[Dependency] private readonly StationSystem _stationSystem = default!;
|
|
||||||
[Dependency] private readonly EmergencyShuttleSystem _emergencyShuttle = default!;
|
|
||||||
|
|
||||||
|
#region Eligible Player Selection
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Attempts to start the game rule by checking if there are enough players in lobby and readied.
|
/// Get all players that are eligible for an antag role
|
||||||
/// </summary>
|
/// </summary>
|
||||||
/// <param name="ev">The roundstart attempt event</param>
|
/// <param name="playerSessions">All sessions from which to select eligible players</param>
|
||||||
/// <param name="uid">The entity the gamerule you are using is on</param>
|
/// <param name="antagPrototype">The prototype to get eligible players for</param>
|
||||||
/// <param name="minPlayers">The minimum amount of players needed for you gamerule to start.</param>
|
/// <param name="includeAllJobs">Should jobs that prohibit antag roles (ie Heads, Sec, Interns) be included</param>
|
||||||
/// <param name="gameRule">The gamerule component.</param>
|
/// <param name="acceptableAntags">Should players already selected as antags be eligible</param>
|
||||||
|
/// <param name="ignorePreferences">Should we ignore if the player has enabled this specific role</param>
|
||||||
|
/// <param name="customExcludeCondition">A custom condition that each player is tested against, if it returns true the player is excluded from eligibility</param>
|
||||||
|
/// <returns>List of all player entities that match the requirements</returns>
|
||||||
|
public List<EntityUid> GetEligiblePlayers(IEnumerable<ICommonSession> playerSessions,
|
||||||
|
ProtoId<AntagPrototype> antagPrototype,
|
||||||
|
bool includeAllJobs = false,
|
||||||
|
AntagAcceptability acceptableAntags = AntagAcceptability.NotExclusive,
|
||||||
|
bool ignorePreferences = false,
|
||||||
|
bool allowNonHumanoids = false,
|
||||||
|
Func<EntityUid?, bool>? customExcludeCondition = null)
|
||||||
|
{
|
||||||
|
var eligiblePlayers = new List<EntityUid>();
|
||||||
|
|
||||||
public void AttemptStartGameRule(RoundStartAttemptEvent ev, EntityUid uid, int minPlayers, GameRuleComponent gameRule)
|
foreach (var player in playerSessions)
|
||||||
{
|
{
|
||||||
if (GameTicker.IsGameRuleAdded(uid, gameRule))
|
if (IsPlayerEligible(player, antagPrototype, includeAllJobs, acceptableAntags, ignorePreferences, allowNonHumanoids, customExcludeCondition))
|
||||||
{
|
eligiblePlayers.Add(player.AttachedEntity!.Value);
|
||||||
if (!ev.Forced && ev.Players.Length < minPlayers)
|
|
||||||
{
|
|
||||||
_chatManager.SendAdminAnnouncement(Loc.GetString("rev-not-enough-ready-players",
|
|
||||||
("readyPlayersCount", ev.Players.Length),
|
|
||||||
("minimumPlayers", minPlayers)));
|
|
||||||
ev.Cancel();
|
|
||||||
}
|
|
||||||
else if (ev.Players.Length == 0)
|
|
||||||
{
|
|
||||||
_chatManager.DispatchServerAnnouncement(Loc.GetString("rev-no-one-ready"));
|
|
||||||
ev.Cancel();
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
return eligiblePlayers;
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Will check which players are eligible to be chosen for antagonist and give them the given antag.
|
/// Get all sessions that are eligible for an antag role, can be run prior to sessions being attached to an entity
|
||||||
|
/// This does not exclude sessions that have already been chosen as antags - that must be handled manually
|
||||||
/// </summary>
|
/// </summary>
|
||||||
/// <param name="antagPrototype">The antag prototype from your rule component.</param>
|
/// <param name="playerSessions">All sessions from which to select eligible players</param>
|
||||||
/// <param name="maxAntags">How many antags can be present in any given round.</param>
|
/// <param name="antagPrototype">The prototype to get eligible players for</param>
|
||||||
/// <param name="antagsPerPlayer">How many players you need to spawn an additional antag.</param>
|
/// <param name="ignorePreferences">Should we ignore if the player has enabled this specific role</param>
|
||||||
/// <param name="antagSound">The intro sound that plays when the antag is chosen.</param>
|
/// <returns>List of all player sessions that match the requirements</returns>
|
||||||
/// <param name="antagGreeting">The antag message you want shown when the antag is chosen.</param>
|
public List<ICommonSession> GetEligibleSessions(IEnumerable<ICommonSession> playerSessions, ProtoId<AntagPrototype> antagPrototype, bool ignorePreferences = false)
|
||||||
/// <param name="greetingColor">The color of the message for the antag greeting in hex.</param>
|
|
||||||
/// <param name="chosen">A list of all the antags chosen in case you need to add stuff after.</param>
|
|
||||||
/// <param name="includeHeads">Whether or not heads can be chosen as antags for this gamemode.</param>
|
|
||||||
public void EligiblePlayers(string antagPrototype,
|
|
||||||
int maxAntags,
|
|
||||||
int antagsPerPlayer,
|
|
||||||
SoundSpecifier? antagSound,
|
|
||||||
string antagGreeting,
|
|
||||||
string greetingColor,
|
|
||||||
out List<EntityUid> chosen,
|
|
||||||
bool includeHeads = false)
|
|
||||||
{
|
{
|
||||||
var allPlayers = _playerSystem.Sessions.ToList();
|
var eligibleSessions = new List<ICommonSession>();
|
||||||
var playerList = new List<ICommonSession>();
|
|
||||||
var prefList = new List<ICommonSession>();
|
foreach (var session in playerSessions)
|
||||||
chosen = new List<EntityUid>();
|
|
||||||
foreach (var player in allPlayers)
|
|
||||||
{
|
{
|
||||||
if (includeHeads == false)
|
if (IsSessionEligible(session, antagPrototype, ignorePreferences))
|
||||||
{
|
eligibleSessions.Add(session);
|
||||||
if (!_jobs.CanBeAntag(player))
|
|
||||||
continue;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if (player.AttachedEntity == null || HasComp<HumanoidAppearanceComponent>(player.AttachedEntity))
|
return eligibleSessions;
|
||||||
playerList.Add(player);
|
|
||||||
else
|
|
||||||
continue;
|
|
||||||
|
|
||||||
var pref = (HumanoidCharacterProfile) _prefs.GetPreferences(player.UserId).SelectedCharacter;
|
|
||||||
if (pref.AntagPreferences.Contains(antagPrototype))
|
|
||||||
prefList.Add(player);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if (playerList.Count == 0)
|
/// <summary>
|
||||||
return;
|
/// Test eligibility of the player for a specific antag role
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="session">The player session to test</param>
|
||||||
|
/// <param name="antagPrototype">The prototype to get eligible players for</param>
|
||||||
|
/// <param name="includeAllJobs">Should jobs that prohibit antag roles (ie Heads, Sec, Interns) be included</param>
|
||||||
|
/// <param name="acceptableAntags">Should players already selected as antags be eligible</param>
|
||||||
|
/// <param name="ignorePreferences">Should we ignore if the player has enabled this specific role</param>
|
||||||
|
/// <param name="customExcludeCondition">A function, accepting an EntityUid and returning bool. Each player is tested against this, returning truw will exclude the player from eligibility</param>
|
||||||
|
/// <returns>True if the player session matches the requirements, false otherwise</returns>
|
||||||
|
public bool IsPlayerEligible(ICommonSession session,
|
||||||
|
ProtoId<AntagPrototype> antagPrototype,
|
||||||
|
bool includeAllJobs = false,
|
||||||
|
AntagAcceptability acceptableAntags = AntagAcceptability.NotExclusive,
|
||||||
|
bool ignorePreferences = false,
|
||||||
|
bool allowNonHumanoids = false,
|
||||||
|
Func<EntityUid?, bool>? customExcludeCondition = null)
|
||||||
|
{
|
||||||
|
if (!IsSessionEligible(session, antagPrototype, ignorePreferences))
|
||||||
|
return false;
|
||||||
|
|
||||||
var antags = Math.Clamp(allPlayers.Count / antagsPerPlayer, 1, maxAntags);
|
//Ensure the player has a mind
|
||||||
for (var antag = 0; antag < antags; antag++)
|
if (session.GetMind() is not { } playerMind)
|
||||||
|
return false;
|
||||||
|
|
||||||
|
//Ensure the player has an attached entity
|
||||||
|
if (session.AttachedEntity is not { } playerEntity)
|
||||||
|
return false;
|
||||||
|
|
||||||
|
//Ignore latejoined players, ie those on the arrivals station
|
||||||
|
if (HasComp<PendingClockInComponent>(playerEntity))
|
||||||
|
return false;
|
||||||
|
|
||||||
|
//Exclude jobs that cannot be antag, unless explicitly allowed
|
||||||
|
if (!includeAllJobs && !_jobs.CanBeAntag(session))
|
||||||
|
return false;
|
||||||
|
|
||||||
|
//Check if the entity is already an antag
|
||||||
|
switch (acceptableAntags)
|
||||||
{
|
{
|
||||||
ICommonSession? chosenPlayer = null;
|
//If we dont want to select any antag roles
|
||||||
if (prefList.Count == 0)
|
case AntagAcceptability.None:
|
||||||
{
|
{
|
||||||
if (playerList.Count == 0)
|
if (_roleSystem.MindIsAntagonist(playerMind))
|
||||||
|
return false;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
//If we dont want to select exclusive antag roles
|
||||||
|
case AntagAcceptability.NotExclusive:
|
||||||
{
|
{
|
||||||
|
if (_roleSystem.MindIsExclusiveAntagonist(playerMind))
|
||||||
|
return false;
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
else
|
|
||||||
|
//Unless explictly allowed, ignore non humanoids (eg pets)
|
||||||
|
if (!allowNonHumanoids && !HasComp<HumanoidAppearanceComponent>(playerEntity))
|
||||||
|
return false;
|
||||||
|
|
||||||
|
//If a custom condition was provided, test it and exclude the player if it returns true
|
||||||
|
if (customExcludeCondition != null && customExcludeCondition(playerEntity))
|
||||||
|
return false;
|
||||||
|
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Check if the session is eligible for a role, can be run prior to the session being attached to an entity
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="session">Player session to check</param>
|
||||||
|
/// <param name="antagPrototype">Which antag prototype to check for</param>
|
||||||
|
/// <param name="ignorePreferences">Ignore if the player has enabled this antag</param>
|
||||||
|
/// <returns>True if the session matches the requirements, false otherwise</returns>
|
||||||
|
public bool IsSessionEligible(ICommonSession session, ProtoId<AntagPrototype> antagPrototype, bool ignorePreferences = false)
|
||||||
|
{
|
||||||
|
//Exclude disconnected or zombie sessions
|
||||||
|
//No point giving antag roles to them
|
||||||
|
if (session.Status == SessionStatus.Disconnected ||
|
||||||
|
session.Status == SessionStatus.Zombie)
|
||||||
|
return false;
|
||||||
|
|
||||||
|
//Check the player has this antag preference selected
|
||||||
|
//Unless we are ignoring preferences, in which case add them anyway
|
||||||
|
var pref = (HumanoidCharacterProfile) _prefs.GetPreferences(session.UserId).SelectedCharacter;
|
||||||
|
if (!pref.AntagPreferences.Contains(antagPrototype.Id) && !ignorePreferences)
|
||||||
|
return false;
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
#endregion
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Helper method to calculate the number of antags to select based upon the number of players
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="playerCount">How many players there are on the server</param>
|
||||||
|
/// <param name="playersPerAntag">How many players should there be for an additional antag</param>
|
||||||
|
/// <param name="maxAntags">Maximum number of antags allowed</param>
|
||||||
|
/// <returns>The number of antags that should be chosen</returns>
|
||||||
|
public int CalculateAntagCount(int playerCount, int playersPerAntag, int maxAntags)
|
||||||
|
{
|
||||||
|
return Math.Clamp(playerCount / playersPerAntag, 1, maxAntags);
|
||||||
|
}
|
||||||
|
|
||||||
|
#region Antag Selection
|
||||||
|
/// <summary>
|
||||||
|
/// Selects a set number of entities from several lists, prioritising the first list till its empty, then second list etc
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="eligiblePlayerLists">Array of lists, which are chosen from in order until the correct number of items are selected</param>
|
||||||
|
/// <param name="count">How many items to select</param>
|
||||||
|
/// <returns>Up to the specified count of elements from all provided lists</returns>
|
||||||
|
public List<EntityUid> ChooseAntags(int count, params List<EntityUid>[] eligiblePlayerLists)
|
||||||
|
{
|
||||||
|
var chosenPlayers = new List<EntityUid>();
|
||||||
|
foreach (var playerList in eligiblePlayerLists)
|
||||||
|
{
|
||||||
|
//Remove all chosen players from this list, to prevent duplicates
|
||||||
|
foreach (var chosenPlayer in chosenPlayers)
|
||||||
{
|
{
|
||||||
chosenPlayer = _random.PickAndTake(prefList);
|
|
||||||
playerList.Remove(chosenPlayer);
|
playerList.Remove(chosenPlayer);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!_mindSystem.TryGetMind(chosenPlayer, out _, out var mind) ||
|
//If we have reached the desired number of players, skip
|
||||||
mind.OwnedEntity is not { } ownedEntity)
|
if (chosenPlayers.Count >= count)
|
||||||
{
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
chosen.Add(ownedEntity);
|
|
||||||
_audioSystem.PlayGlobal(antagSound, ownedEntity);
|
|
||||||
if (mind.Session != null)
|
|
||||||
{
|
|
||||||
var message = Loc.GetString(antagGreeting);
|
|
||||||
var wrappedMessage = Loc.GetString("chat-manager-server-wrap-message", ("message", message));
|
|
||||||
_chatManager.ChatMessageToOne(Shared.Chat.ChatChannel.Server, message, wrappedMessage, default, false, mind.Session.Channel, Color.FromHex(greetingColor));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// The function walks through all players, checking their role and preferences to generate a list of players who can become antagonists.
|
|
||||||
/// </summary>
|
|
||||||
/// <param name="candidates">a list of players to check out</param>
|
|
||||||
/// <param name="antagPreferenceId">antagonist's code id</param>
|
|
||||||
/// <returns></returns>
|
|
||||||
public List<ICommonSession> FindPotentialAntags(in Dictionary<ICommonSession, HumanoidCharacterProfile> candidates, string antagPreferenceId)
|
|
||||||
{
|
|
||||||
var list = new List<ICommonSession>();
|
|
||||||
var pendingQuery = GetEntityQuery<PendingClockInComponent>();
|
|
||||||
|
|
||||||
foreach (var player in candidates.Keys)
|
|
||||||
{
|
|
||||||
// Role prevents antag.
|
|
||||||
if (!_jobs.CanBeAntag(player))
|
|
||||||
continue;
|
continue;
|
||||||
|
|
||||||
// Latejoin
|
//Pick and choose a random number of players from this list
|
||||||
if (player.AttachedEntity != null && pendingQuery.HasComponent(player.AttachedEntity.Value))
|
chosenPlayers.AddRange(ChooseAntags(count - chosenPlayers.Count, playerList));
|
||||||
|
}
|
||||||
|
return chosenPlayers;
|
||||||
|
}
|
||||||
|
/// <summary>
|
||||||
|
/// Helper method to choose antags from a list
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="eligiblePlayers">List of eligible players</param>
|
||||||
|
/// <param name="count">How many to choose</param>
|
||||||
|
/// <returns>Up to the specified count of elements from the provided list</returns>
|
||||||
|
public List<EntityUid> ChooseAntags(int count, List<EntityUid> eligiblePlayers)
|
||||||
|
{
|
||||||
|
var chosenPlayers = new List<EntityUid>();
|
||||||
|
|
||||||
|
for (var i = 0; i < count; i++)
|
||||||
|
{
|
||||||
|
if (eligiblePlayers.Count == 0)
|
||||||
|
break;
|
||||||
|
|
||||||
|
chosenPlayers.Add(RobustRandom.PickAndTake(eligiblePlayers));
|
||||||
|
}
|
||||||
|
|
||||||
|
return chosenPlayers;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Selects a set number of sessions from several lists, prioritising the first list till its empty, then second list etc
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="eligiblePlayerLists">Array of lists, which are chosen from in order until the correct number of items are selected</param>
|
||||||
|
/// <param name="count">How many items to select</param>
|
||||||
|
/// <returns>Up to the specified count of elements from all provided lists</returns>
|
||||||
|
public List<ICommonSession> ChooseAntags(int count, params List<ICommonSession>[] eligiblePlayerLists)
|
||||||
|
{
|
||||||
|
var chosenPlayers = new List<ICommonSession>();
|
||||||
|
foreach (var playerList in eligiblePlayerLists)
|
||||||
|
{
|
||||||
|
//Remove all chosen players from this list, to prevent duplicates
|
||||||
|
foreach (var chosenPlayer in chosenPlayers)
|
||||||
|
{
|
||||||
|
playerList.Remove(chosenPlayer);
|
||||||
|
}
|
||||||
|
|
||||||
|
//If we have reached the desired number of players, skip
|
||||||
|
if (chosenPlayers.Count >= count)
|
||||||
continue;
|
continue;
|
||||||
|
|
||||||
list.Add(player);
|
//Pick and choose a random number of players from this list
|
||||||
|
chosenPlayers.AddRange(ChooseAntags(count - chosenPlayers.Count, playerList));
|
||||||
}
|
}
|
||||||
|
return chosenPlayers;
|
||||||
var prefList = new List<ICommonSession>();
|
|
||||||
|
|
||||||
foreach (var player in list)
|
|
||||||
{
|
|
||||||
//player preferences to play as this antag
|
|
||||||
var profile = candidates[player];
|
|
||||||
if (profile.AntagPreferences.Contains(antagPreferenceId))
|
|
||||||
{
|
|
||||||
prefList.Add(player);
|
|
||||||
}
|
}
|
||||||
}
|
|
||||||
if (prefList.Count == 0)
|
|
||||||
{
|
|
||||||
Log.Info($"Insufficient preferred antag:{antagPreferenceId}, picking at random.");
|
|
||||||
prefList = list;
|
|
||||||
}
|
|
||||||
return prefList;
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// selects the specified number of players from the list
|
/// Helper method to choose sessions from a list
|
||||||
/// </summary>
|
/// </summary>
|
||||||
/// <param name="antagCount">how many players to take</param>
|
/// <param name="eligiblePlayers">List of eligible sessions</param>
|
||||||
/// <param name="prefList">a list of players from which to draw</param>
|
/// <param name="count">How many to choose</param>
|
||||||
/// <returns></returns>
|
/// <returns>Up to the specified count of elements from the provided list</returns>
|
||||||
public List<ICommonSession> PickAntag(int antagCount, List<ICommonSession> prefList)
|
public List<ICommonSession> ChooseAntags(int count, List<ICommonSession> eligiblePlayers)
|
||||||
{
|
{
|
||||||
var results = new List<ICommonSession>(antagCount);
|
var chosenPlayers = new List<ICommonSession>();
|
||||||
if (prefList.Count == 0)
|
|
||||||
|
for (int i = 0; i < count; i++)
|
||||||
{
|
{
|
||||||
Log.Info("Insufficient ready players to fill up with antags, stopping the selection.");
|
if (eligiblePlayers.Count == 0)
|
||||||
return results;
|
break;
|
||||||
|
|
||||||
|
chosenPlayers.Add(RobustRandom.PickAndTake(eligiblePlayers));
|
||||||
}
|
}
|
||||||
|
|
||||||
for (var i = 0; i < antagCount; i++)
|
return chosenPlayers;
|
||||||
{
|
|
||||||
results.Add(_random.PickAndTake(prefList));
|
|
||||||
Log.Info("Selected a preferred antag.");
|
|
||||||
}
|
|
||||||
return results;
|
|
||||||
}
|
}
|
||||||
|
#endregion
|
||||||
|
|
||||||
|
#region Briefings
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Will take a group of entities and check if they are all alive or dead
|
/// Helper method to send the briefing text and sound to a list of entities
|
||||||
/// </summary>
|
/// </summary>
|
||||||
/// <param name="list">The list of the entities</param>
|
/// <param name="entities">The players chosen to be antags</param>
|
||||||
/// <param name="checkOffStation">Bool for if you want to check if someone is in space and consider them dead. (Won't check when emergency shuttle arrives just in case)</param>
|
/// <param name="briefing">The briefing text to send</param>
|
||||||
/// <returns></returns>
|
/// <param name="briefingColor">The color the briefing should be, null for default</param>
|
||||||
public bool IsGroupDead(List<EntityUid> list, bool checkOffStation)
|
/// <param name="briefingSound">The sound to briefing/greeting sound to play</param>
|
||||||
|
public void SendBriefing(List<EntityUid> entities, string briefing, Color? briefingColor, SoundSpecifier? briefingSound)
|
||||||
{
|
{
|
||||||
var dead = 0;
|
foreach (var entity in entities)
|
||||||
foreach (var entity in list)
|
|
||||||
{
|
{
|
||||||
if (TryComp<MobStateComponent>(entity, out var state))
|
SendBriefing(entity, briefing, briefingColor, briefingSound);
|
||||||
{
|
|
||||||
if (state.CurrentState == MobState.Dead || state.CurrentState == MobState.Invalid)
|
|
||||||
{
|
|
||||||
dead++;
|
|
||||||
}
|
|
||||||
else if (checkOffStation && _stationSystem.GetOwningStation(entity) == null && !_emergencyShuttle.EmergencyShuttleArrived)
|
|
||||||
{
|
|
||||||
dead++;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
//If they don't have the MobStateComponent they might as well be dead.
|
|
||||||
else
|
|
||||||
{
|
|
||||||
dead++;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return dead == list.Count || list.Count == 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Will attempt to spawn an item inside of a persons bag and then pockets.
|
|
||||||
/// </summary>
|
|
||||||
/// <param name="antag">The entity that you want to spawn an item on</param>
|
|
||||||
/// <param name="items">A list of prototype IDs that you want to spawn in the bag.</param>
|
|
||||||
public void GiveAntagBagGear(EntityUid antag, List<EntProtoId> items)
|
|
||||||
{
|
|
||||||
foreach (var item in items)
|
|
||||||
{
|
|
||||||
GiveAntagBagGear(antag, item);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Will attempt to spawn an item inside of a persons bag and then pockets.
|
/// Helper method to send the briefing text and sound to a player entity
|
||||||
/// </summary>
|
/// </summary>
|
||||||
/// <param name="antag">The entity that you want to spawn an item on</param>
|
/// <param name="entity">The entity chosen to be antag</param>
|
||||||
/// <param name="item">The prototype ID that you want to spawn in the bag.</param>
|
/// <param name="briefing">The briefing text to send</param>
|
||||||
public void GiveAntagBagGear(EntityUid antag, string item)
|
/// <param name="briefingColor">The color the briefing should be, null for default</param>
|
||||||
|
/// <param name="briefingSound">The sound to briefing/greeting sound to play</param>
|
||||||
|
public void SendBriefing(EntityUid entity, string briefing, Color? briefingColor, SoundSpecifier? briefingSound)
|
||||||
{
|
{
|
||||||
var itemToSpawn = Spawn(item, new EntityCoordinates(antag, Vector2.Zero));
|
if (!_mindSystem.TryGetMind(entity, out _, out var mindComponent))
|
||||||
if (!_inventory.TryGetSlotContainer(antag, "back", out var backSlot, out _))
|
|
||||||
return;
|
return;
|
||||||
|
|
||||||
var bag = backSlot.ContainedEntity;
|
if (mindComponent.Session == null)
|
||||||
if (bag != null && HasComp<ContainerManagerComponent>(bag) && _storageSystem.CanInsert(bag.Value, itemToSpawn, out _))
|
return;
|
||||||
{
|
|
||||||
_storageSystem.Insert(bag.Value, itemToSpawn, out _);
|
SendBriefing(mindComponent.Session, briefing, briefingColor, briefingSound);
|
||||||
}
|
|
||||||
else if (_inventory.TryGetSlotContainer(antag, "jumpsuit", out var jumpsuit, out _) && jumpsuit.ContainedEntity != null)
|
|
||||||
{
|
|
||||||
if (_inventory.TryGetSlotContainer(antag, "pocket1", out var pocket1Slot, out _))
|
|
||||||
{
|
|
||||||
if (pocket1Slot.ContainedEntity == null)
|
|
||||||
{
|
|
||||||
if (_containerSystem.CanInsert(itemToSpawn, pocket1Slot))
|
|
||||||
{
|
|
||||||
_containerSystem.Insert(itemToSpawn, pocket1Slot);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
else if (_inventory.TryGetSlotContainer(antag, "pocket2", out var pocket2Slot, out _))
|
|
||||||
{
|
|
||||||
if (pocket2Slot.ContainedEntity == null)
|
|
||||||
{
|
|
||||||
if (_containerSystem.CanInsert(itemToSpawn, pocket2Slot))
|
|
||||||
{
|
|
||||||
_containerSystem.Insert(itemToSpawn, pocket2Slot);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Helper method to send the briefing text and sound to a list of sessions
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="sessions"></param>
|
||||||
|
/// <param name="briefing"></param>
|
||||||
|
/// <param name="briefingColor"></param>
|
||||||
|
/// <param name="briefingSound"></param>
|
||||||
|
|
||||||
|
public void SendBriefing(List<ICommonSession> sessions, string briefing, Color? briefingColor, SoundSpecifier? briefingSound)
|
||||||
|
{
|
||||||
|
foreach (var session in sessions)
|
||||||
|
{
|
||||||
|
SendBriefing(session, briefing, briefingColor, briefingSound);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
/// <summary>
|
||||||
|
/// Helper method to send the briefing text and sound to a session
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="session">The player chosen to be an antag</param>
|
||||||
|
/// <param name="briefing">The briefing text to send</param>
|
||||||
|
/// <param name="briefingColor">The color the briefing should be, null for default</param>
|
||||||
|
/// <param name="briefingSound">The sound to briefing/greeting sound to play</param>
|
||||||
|
|
||||||
|
public void SendBriefing(ICommonSession session, string briefing, Color? briefingColor, SoundSpecifier? briefingSound)
|
||||||
|
{
|
||||||
|
_audioSystem.PlayGlobal(briefingSound, session);
|
||||||
|
var wrappedMessage = Loc.GetString("chat-manager-server-wrap-message", ("message", briefing));
|
||||||
|
ChatManager.ChatMessageToOne(ChatChannel.Server, briefing, wrappedMessage, default, false, session.Channel, briefingColor);
|
||||||
|
}
|
||||||
|
#endregion
|
||||||
|
}
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
using Robust.Shared.Serialization.TypeSerializers.Implementations.Custom;
|
using Robust.Shared.Serialization.TypeSerializers.Implementations.Custom;
|
||||||
|
|
||||||
namespace Content.Server.GameTicking.Rules.Components;
|
namespace Content.Server.GameTicking.Rules.Components;
|
||||||
|
|
||||||
|
|||||||
@@ -14,9 +14,6 @@ public sealed partial class NukeOperativeSpawnerComponent : Component
|
|||||||
[DataField("name", required:true)]
|
[DataField("name", required:true)]
|
||||||
public string OperativeName = default!;
|
public string OperativeName = default!;
|
||||||
|
|
||||||
[DataField("rolePrototype", customTypeSerializer:typeof(PrototypeIdSerializer<AntagPrototype>), required:true)]
|
[DataField]
|
||||||
public string OperativeRolePrototype = default!;
|
public NukeopSpawnPreset SpawnDetails = default!;
|
||||||
|
|
||||||
[DataField("startingGearPrototype", customTypeSerializer:typeof(PrototypeIdSerializer<StartingGearPrototype>), required:true)]
|
|
||||||
public string OperativeStartingGear = default!;
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,3 +1,4 @@
|
|||||||
|
using Content.Server.Maps;
|
||||||
using Content.Server.NPC.Components;
|
using Content.Server.NPC.Components;
|
||||||
using Content.Server.RoundEnd;
|
using Content.Server.RoundEnd;
|
||||||
using Content.Server.StationEvents.Events;
|
using Content.Server.StationEvents.Events;
|
||||||
@@ -16,13 +17,6 @@ namespace Content.Server.GameTicking.Rules.Components;
|
|||||||
[RegisterComponent, Access(typeof(NukeopsRuleSystem), typeof(LoneOpsSpawnRule))]
|
[RegisterComponent, Access(typeof(NukeopsRuleSystem), typeof(LoneOpsSpawnRule))]
|
||||||
public sealed partial class NukeopsRuleComponent : Component
|
public sealed partial class NukeopsRuleComponent : Component
|
||||||
{
|
{
|
||||||
// TODO Replace with GameRuleComponent.minPlayers
|
|
||||||
/// <summary>
|
|
||||||
/// The minimum needed amount of players
|
|
||||||
/// </summary>
|
|
||||||
[DataField]
|
|
||||||
public int MinPlayers = 20;
|
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// This INCLUDES the operatives. So a value of 3 is satisfied by 2 players & 1 operative
|
/// This INCLUDES the operatives. So a value of 3 is satisfied by 2 players & 1 operative
|
||||||
/// </summary>
|
/// </summary>
|
||||||
@@ -92,17 +86,11 @@ public sealed partial class NukeopsRuleComponent : Component
|
|||||||
[DataField]
|
[DataField]
|
||||||
public int WarTCAmountPerNukie = 40;
|
public int WarTCAmountPerNukie = 40;
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Time allowed for declaration of war
|
|
||||||
/// </summary>
|
|
||||||
[DataField("warDeclarationDelay")]
|
|
||||||
public TimeSpan WarDeclarationDelay = TimeSpan.FromMinutes(6);
|
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Delay between war declaration and nuke ops arrival on station map. Gives crew time to prepare
|
/// Delay between war declaration and nuke ops arrival on station map. Gives crew time to prepare
|
||||||
/// </summary>
|
/// </summary>
|
||||||
[DataField]
|
[DataField]
|
||||||
public TimeSpan? WarNukieArriveDelay = TimeSpan.FromMinutes(15);
|
public TimeSpan WarNukieArriveDelay = TimeSpan.FromMinutes(15);
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Minimal operatives count for war declaration
|
/// Minimal operatives count for war declaration
|
||||||
@@ -116,38 +104,11 @@ public sealed partial class NukeopsRuleComponent : Component
|
|||||||
[DataField]
|
[DataField]
|
||||||
public EntProtoId GhostSpawnPointProto = "SpawnPointGhostNukeOperative";
|
public EntProtoId GhostSpawnPointProto = "SpawnPointGhostNukeOperative";
|
||||||
|
|
||||||
[DataField]
|
|
||||||
public ProtoId<AntagPrototype> CommanderRoleProto = "NukeopsCommander";
|
|
||||||
|
|
||||||
[DataField]
|
|
||||||
public ProtoId<AntagPrototype> OperativeRoleProto = "Nukeops";
|
|
||||||
|
|
||||||
[DataField]
|
|
||||||
public ProtoId<AntagPrototype> MedicRoleProto = "NukeopsMedic";
|
|
||||||
|
|
||||||
[DataField]
|
|
||||||
public ProtoId<StartingGearPrototype> CommanderStartGearProto = "SyndicateCommanderGearFull";
|
|
||||||
|
|
||||||
[DataField]
|
|
||||||
public ProtoId<StartingGearPrototype> MedicStartGearProto = "SyndicateOperativeMedicFull";
|
|
||||||
|
|
||||||
[DataField]
|
|
||||||
public ProtoId<StartingGearPrototype> OperativeStartGearProto = "SyndicateOperativeGearFull";
|
|
||||||
|
|
||||||
[DataField(customTypeSerializer: typeof(PrototypeIdSerializer<DatasetPrototype>))]
|
|
||||||
public string EliteNames = "SyndicateNamesElite";
|
|
||||||
|
|
||||||
[DataField]
|
[DataField]
|
||||||
public string OperationName = "Test Operation";
|
public string OperationName = "Test Operation";
|
||||||
|
|
||||||
[DataField(customTypeSerializer: typeof(PrototypeIdSerializer<DatasetPrototype>))]
|
[DataField]
|
||||||
public string NormalNames = "SyndicateNamesNormal";
|
public ProtoId<GameMapPrototype> OutpostMapPrototype = "NukieOutpost";
|
||||||
|
|
||||||
[DataField(customTypeSerializer: typeof(ResPathSerializer))]
|
|
||||||
public ResPath OutpostMap = new("/Maps/nukieplanet.yml");
|
|
||||||
|
|
||||||
[DataField(customTypeSerializer: typeof(ResPathSerializer))]
|
|
||||||
public ResPath ShuttleMap = new("/Maps/infiltrator.yml");
|
|
||||||
|
|
||||||
[DataField]
|
[DataField]
|
||||||
public WinType WinType = WinType.Neutral;
|
public WinType WinType = WinType.Neutral;
|
||||||
@@ -163,33 +124,53 @@ public sealed partial class NukeopsRuleComponent : Component
|
|||||||
public EntityUid? NukieShuttle;
|
public EntityUid? NukieShuttle;
|
||||||
public EntityUid? TargetStation;
|
public EntityUid? TargetStation;
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Cached starting gear prototypes.
|
|
||||||
/// </summary>
|
|
||||||
[DataField]
|
|
||||||
public Dictionary<string, StartingGearPrototype> StartingGearPrototypes = new ();
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Cached operator name prototypes.
|
|
||||||
/// </summary>
|
|
||||||
[DataField]
|
|
||||||
public Dictionary<string, List<string>> OperativeNames = new();
|
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Data to be used in <see cref="OnMindAdded"/> for an operative once the Mind has been added.
|
/// Data to be used in <see cref="OnMindAdded"/> for an operative once the Mind has been added.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
[DataField]
|
[DataField]
|
||||||
public Dictionary<EntityUid, string> OperativeMindPendingData = new();
|
public Dictionary<EntityUid, string> OperativeMindPendingData = new();
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Players who played as an operative at some point in the round.
|
|
||||||
/// Stores the mind as well as the entity name
|
|
||||||
/// </summary>
|
|
||||||
[DataField]
|
|
||||||
public Dictionary<string, EntityUid> OperativePlayers = new();
|
|
||||||
|
|
||||||
[DataField(required: true)]
|
[DataField(required: true)]
|
||||||
public ProtoId<NpcFactionPrototype> Faction = default!;
|
public ProtoId<NpcFactionPrototype> Faction = default!;
|
||||||
|
|
||||||
|
[DataField]
|
||||||
|
public NukeopSpawnPreset CommanderSpawnDetails = new() { AntagRoleProto = "NukeopsCommander", GearProto = "SyndicateCommanderGearFull", NamePrefix = "nukeops-role-commander", NameList = "SyndicateNamesElite" };
|
||||||
|
|
||||||
|
[DataField]
|
||||||
|
public NukeopSpawnPreset AgentSpawnDetails = new() { AntagRoleProto = "NukeopsMedic", GearProto = "SyndicateOperativeMedicFull", NamePrefix = "nukeops-role-agent", NameList = "SyndicateNamesNormal" };
|
||||||
|
|
||||||
|
[DataField]
|
||||||
|
public NukeopSpawnPreset OperativeSpawnDetails = new();
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Stores the presets for each operative type
|
||||||
|
/// Ie Commander, Agent and Operative
|
||||||
|
/// </summary>
|
||||||
|
[DataDefinition, Serializable]
|
||||||
|
public sealed partial class NukeopSpawnPreset
|
||||||
|
{
|
||||||
|
|
||||||
|
[DataField]
|
||||||
|
public ProtoId<AntagPrototype> AntagRoleProto = "Nukeops";
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// The equipment set this operative will be given when spawned
|
||||||
|
/// </summary>
|
||||||
|
[DataField]
|
||||||
|
public ProtoId<StartingGearPrototype> GearProto = "SyndicateOperativeGearFull";
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// The name prefix, ie "Agent"
|
||||||
|
/// </summary>
|
||||||
|
[DataField]
|
||||||
|
public LocId NamePrefix = "nukeops-role-operator";
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// The entity name suffix will be chosen from this list randomly
|
||||||
|
/// </summary>
|
||||||
|
[DataField]
|
||||||
|
public ProtoId<DatasetPrototype> NameList = "SyndicateNamesNormal";
|
||||||
}
|
}
|
||||||
|
|
||||||
public enum WinType : byte
|
public enum WinType : byte
|
||||||
|
|||||||
@@ -1,5 +1,4 @@
|
|||||||
using Content.Shared.Roles;
|
using Content.Shared.Roles;
|
||||||
using Robust.Shared.Audio;
|
|
||||||
using Robust.Shared.Prototypes;
|
using Robust.Shared.Prototypes;
|
||||||
using Robust.Shared.Serialization.TypeSerializers.Implementations.Custom;
|
using Robust.Shared.Serialization.TypeSerializers.Implementations.Custom;
|
||||||
|
|
||||||
@@ -32,15 +31,6 @@ public sealed partial class RevolutionaryRuleComponent : Component
|
|||||||
[DataField]
|
[DataField]
|
||||||
public ProtoId<AntagPrototype> HeadRevPrototypeId = "HeadRev";
|
public ProtoId<AntagPrototype> HeadRevPrototypeId = "HeadRev";
|
||||||
|
|
||||||
[DataField]
|
|
||||||
public ProtoId<AntagPrototype> RevPrototypeId = "Rev";
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Sound that plays when you are chosen as Rev. (Placeholder until I find something cool I guess)
|
|
||||||
/// </summary>
|
|
||||||
[DataField]
|
|
||||||
public SoundSpecifier HeadRevStartSound = new SoundPathSpecifier("/Audio/Ambience/Antag/headrev_start.ogg");
|
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Min players needed for Revolutionary gamemode to start.
|
/// Min players needed for Revolutionary gamemode to start.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
|
|||||||
@@ -1,8 +1,7 @@
|
|||||||
|
using Content.Shared.Random;
|
||||||
|
using Content.Shared.Roles;
|
||||||
using Robust.Shared.Audio;
|
using Robust.Shared.Audio;
|
||||||
using Robust.Shared.Prototypes;
|
using Robust.Shared.Prototypes;
|
||||||
using Content.Shared.Roles;
|
|
||||||
using Robust.Shared.Player;
|
|
||||||
using Content.Shared.Preferences;
|
|
||||||
|
|
||||||
namespace Content.Server.GameTicking.Rules.Components;
|
namespace Content.Server.GameTicking.Rules.Components;
|
||||||
|
|
||||||
@@ -12,6 +11,18 @@ namespace Content.Server.GameTicking.Rules.Components;
|
|||||||
[RegisterComponent, Access(typeof(ThiefRuleSystem))]
|
[RegisterComponent, Access(typeof(ThiefRuleSystem))]
|
||||||
public sealed partial class ThiefRuleComponent : Component
|
public sealed partial class ThiefRuleComponent : Component
|
||||||
{
|
{
|
||||||
|
[DataField]
|
||||||
|
public ProtoId<WeightedRandomPrototype> BigObjectiveGroup = "ThiefBigObjectiveGroups";
|
||||||
|
|
||||||
|
[DataField]
|
||||||
|
public ProtoId<WeightedRandomPrototype> SmallObjectiveGroup = "ThiefObjectiveGroups";
|
||||||
|
|
||||||
|
[DataField]
|
||||||
|
public ProtoId<WeightedRandomPrototype> EscapeObjectiveGroup = "ThiefEscapeObjectiveGroups";
|
||||||
|
|
||||||
|
[DataField]
|
||||||
|
public float BigObjectiveChance = 0.7f;
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Add a Pacified comp to thieves
|
/// Add a Pacified comp to thieves
|
||||||
/// </summary>
|
/// </summary>
|
||||||
@@ -27,8 +38,6 @@ public sealed partial class ThiefRuleComponent : Component
|
|||||||
[DataField]
|
[DataField]
|
||||||
public ProtoId<AntagPrototype> ThiefPrototypeId = "Thief";
|
public ProtoId<AntagPrototype> ThiefPrototypeId = "Thief";
|
||||||
|
|
||||||
public Dictionary<ICommonSession, HumanoidCharacterProfile> StartCandidates = new();
|
|
||||||
|
|
||||||
[DataField]
|
[DataField]
|
||||||
public float MaxObjectiveDifficulty = 2.5f;
|
public float MaxObjectiveDifficulty = 2.5f;
|
||||||
|
|
||||||
@@ -39,7 +48,7 @@ public sealed partial class ThiefRuleComponent : Component
|
|||||||
/// Things that will be given to thieves
|
/// Things that will be given to thieves
|
||||||
/// </summary>
|
/// </summary>
|
||||||
[DataField]
|
[DataField]
|
||||||
public List<EntProtoId> StarterItems = new List<EntProtoId> { "ToolboxThief", "ClothingHandsChameleonThief" }; //TO DO - replace to chameleon thieving gloves whem merg
|
public List<EntProtoId> StarterItems = new() { "ToolboxThief", "ClothingHandsChameleonThief" };
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// All Thieves created by this rule
|
/// All Thieves created by this rule
|
||||||
|
|||||||
@@ -1,8 +1,10 @@
|
|||||||
using Content.Shared.Preferences;
|
using Content.Server.NPC.Components;
|
||||||
|
using Content.Shared.Dataset;
|
||||||
|
using Content.Shared.Random;
|
||||||
using Content.Shared.Roles;
|
using Content.Shared.Roles;
|
||||||
using Robust.Shared.Audio;
|
using Robust.Shared.Audio;
|
||||||
using Robust.Shared.Player;
|
using Robust.Shared.Prototypes;
|
||||||
using Robust.Shared.Serialization.TypeSerializers.Implementations.Custom.Prototype;
|
using Robust.Shared.Serialization.TypeSerializers.Implementations.Custom;
|
||||||
|
|
||||||
namespace Content.Server.GameTicking.Rules.Components;
|
namespace Content.Server.GameTicking.Rules.Components;
|
||||||
|
|
||||||
@@ -11,8 +13,23 @@ public sealed partial class TraitorRuleComponent : Component
|
|||||||
{
|
{
|
||||||
public readonly List<EntityUid> TraitorMinds = new();
|
public readonly List<EntityUid> TraitorMinds = new();
|
||||||
|
|
||||||
[DataField("traitorPrototypeId", customTypeSerializer: typeof(PrototypeIdSerializer<AntagPrototype>))]
|
[DataField]
|
||||||
public string TraitorPrototypeId = "Traitor";
|
public ProtoId<AntagPrototype> TraitorPrototypeId = "Traitor";
|
||||||
|
|
||||||
|
[DataField]
|
||||||
|
public ProtoId<NpcFactionPrototype> NanoTrasenFaction = "NanoTrasen";
|
||||||
|
|
||||||
|
[DataField]
|
||||||
|
public ProtoId<NpcFactionPrototype> SyndicateFaction = "Syndicate";
|
||||||
|
|
||||||
|
[DataField]
|
||||||
|
public ProtoId<WeightedRandomPrototype> ObjectiveGroup = "TraitorObjectiveGroups";
|
||||||
|
|
||||||
|
[DataField]
|
||||||
|
public ProtoId<DatasetPrototype> CodewordAdjectives = "adjectives";
|
||||||
|
|
||||||
|
[DataField]
|
||||||
|
public ProtoId<DatasetPrototype> CodewordVerbs = "verbs";
|
||||||
|
|
||||||
public int TotalTraitors => TraitorMinds.Count;
|
public int TotalTraitors => TraitorMinds.Count;
|
||||||
public string[] Codewords = new string[3];
|
public string[] Codewords = new string[3];
|
||||||
@@ -20,17 +37,24 @@ public sealed partial class TraitorRuleComponent : Component
|
|||||||
public enum SelectionState
|
public enum SelectionState
|
||||||
{
|
{
|
||||||
WaitingForSpawn = 0,
|
WaitingForSpawn = 0,
|
||||||
ReadyToSelect = 1,
|
ReadyToStart = 1,
|
||||||
SelectionMade = 2,
|
Started = 2,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Current state of the rule
|
||||||
|
/// </summary>
|
||||||
public SelectionState SelectionStatus = SelectionState.WaitingForSpawn;
|
public SelectionState SelectionStatus = SelectionState.WaitingForSpawn;
|
||||||
public TimeSpan AnnounceAt = TimeSpan.Zero;
|
|
||||||
public Dictionary<ICommonSession, HumanoidCharacterProfile> StartCandidates = new();
|
/// <summary>
|
||||||
|
/// When should traitors be selected and the announcement made
|
||||||
|
/// </summary>
|
||||||
|
[DataField(customTypeSerializer: typeof(TimeOffsetSerializer)), ViewVariables(VVAccess.ReadWrite)]
|
||||||
|
public TimeSpan? AnnounceAt;
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Path to antagonist alert sound.
|
/// Path to antagonist alert sound.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
[DataField("greetSoundNotification")]
|
[DataField]
|
||||||
public SoundSpecifier GreetSoundNotification = new SoundPathSpecifier("/Audio/Ambience/Antag/traitor_start.ogg");
|
public SoundSpecifier GreetSoundNotification = new SoundPathSpecifier("/Audio/Ambience/Antag/traitor_start.ogg");
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -2,98 +2,85 @@ using Content.Shared.Roles;
|
|||||||
using Robust.Shared.Audio;
|
using Robust.Shared.Audio;
|
||||||
using Robust.Shared.Prototypes;
|
using Robust.Shared.Prototypes;
|
||||||
using Robust.Shared.Serialization.TypeSerializers.Implementations.Custom;
|
using Robust.Shared.Serialization.TypeSerializers.Implementations.Custom;
|
||||||
using Robust.Shared.Serialization.TypeSerializers.Implementations.Custom.Prototype;
|
|
||||||
|
|
||||||
namespace Content.Server.GameTicking.Rules.Components;
|
namespace Content.Server.GameTicking.Rules.Components;
|
||||||
|
|
||||||
[RegisterComponent, Access(typeof(ZombieRuleSystem))]
|
[RegisterComponent, Access(typeof(ZombieRuleSystem))]
|
||||||
public sealed partial class ZombieRuleComponent : Component
|
public sealed partial class ZombieRuleComponent : Component
|
||||||
{
|
{
|
||||||
[DataField("initialInfectedNames")]
|
[DataField]
|
||||||
public Dictionary<string, string> InitialInfectedNames = new();
|
public Dictionary<string, string> InitialInfectedNames = new();
|
||||||
|
|
||||||
[DataField("patientZeroPrototypeId", customTypeSerializer: typeof(PrototypeIdSerializer<AntagPrototype>))]
|
[DataField]
|
||||||
public string PatientZeroPrototypeId = "InitialInfected";
|
public ProtoId<AntagPrototype> PatientZeroPrototypeId = "InitialInfected";
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Whether or not the initial infected have been chosen.
|
|
||||||
/// </summary>
|
|
||||||
[DataField("infectedChosen")]
|
|
||||||
public bool InfectedChosen;
|
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// When the round will next check for round end.
|
/// When the round will next check for round end.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
[DataField("nextRoundEndCheck", customTypeSerializer: typeof(TimeOffsetSerializer))]
|
[DataField(customTypeSerializer: typeof(TimeOffsetSerializer))]
|
||||||
public TimeSpan NextRoundEndCheck;
|
public TimeSpan? NextRoundEndCheck;
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// The amount of time between each check for the end of the round.
|
/// The amount of time between each check for the end of the round.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
[DataField("endCheckDelay")]
|
[DataField]
|
||||||
public TimeSpan EndCheckDelay = TimeSpan.FromSeconds(30);
|
public TimeSpan EndCheckDelay = TimeSpan.FromSeconds(30);
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// The time at which the initial infected will be chosen.
|
/// The time at which the initial infected will be chosen.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
[DataField("startTime", customTypeSerializer: typeof(TimeOffsetSerializer)), ViewVariables(VVAccess.ReadWrite)]
|
[DataField(customTypeSerializer: typeof(TimeOffsetSerializer)), ViewVariables(VVAccess.ReadWrite)]
|
||||||
public TimeSpan? StartTime;
|
public TimeSpan? StartTime;
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// The minimum amount of time after the round starts that the initial infected will be chosen.
|
/// The minimum amount of time after the round starts that the initial infected will be chosen.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
[DataField("minStartDelay")]
|
[DataField]
|
||||||
public TimeSpan MinStartDelay = TimeSpan.FromMinutes(10);
|
public TimeSpan MinStartDelay = TimeSpan.FromMinutes(10);
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// The maximum amount of time after the round starts that the initial infected will be chosen.
|
/// The maximum amount of time after the round starts that the initial infected will be chosen.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
[DataField("maxStartDelay")]
|
[DataField]
|
||||||
public TimeSpan MaxStartDelay = TimeSpan.FromMinutes(15);
|
public TimeSpan MaxStartDelay = TimeSpan.FromMinutes(15);
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// The sound that plays when someone becomes an initial infected.
|
/// The sound that plays when someone becomes an initial infected.
|
||||||
/// todo: this should have a unique sound instead of reusing the zombie one.
|
/// todo: this should have a unique sound instead of reusing the zombie one.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
[DataField("initialInfectedSound")]
|
[DataField]
|
||||||
public SoundSpecifier InitialInfectedSound = new SoundPathSpecifier("/Audio/Ambience/Antag/zombie_start.ogg");
|
public SoundSpecifier InitialInfectedSound = new SoundPathSpecifier("/Audio/Ambience/Antag/zombie_start.ogg");
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// The minimum amount of time initial infected have before they start taking infection damage.
|
/// The minimum amount of time initial infected have before they start taking infection damage.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
[DataField("minInitialInfectedGrace")]
|
[DataField]
|
||||||
public TimeSpan MinInitialInfectedGrace = TimeSpan.FromMinutes(12.5f);
|
public TimeSpan MinInitialInfectedGrace = TimeSpan.FromMinutes(12.5f);
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// The maximum amount of time initial infected have before they start taking damage.
|
/// The maximum amount of time initial infected have before they start taking damage.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
[DataField("maxInitialInfectedGrace")]
|
[DataField]
|
||||||
public TimeSpan MaxInitialInfectedGrace = TimeSpan.FromMinutes(15f);
|
public TimeSpan MaxInitialInfectedGrace = TimeSpan.FromMinutes(15f);
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// How many players for each initial infected.
|
/// How many players for each initial infected.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
[DataField("playersPerInfected")]
|
[DataField]
|
||||||
public int PlayersPerInfected = 10;
|
public int PlayersPerInfected = 10;
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// The maximum number of initial infected.
|
/// The maximum number of initial infected.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
[DataField("maxInitialInfected")]
|
[DataField]
|
||||||
public int MaxInitialInfected = 6;
|
public int MaxInitialInfected = 6;
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// After this amount of the crew become zombies, the shuttle will be automatically called.
|
/// After this amount of the crew become zombies, the shuttle will be automatically called.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
[DataField("zombieShuttleCallPercentage")]
|
[DataField]
|
||||||
public float ZombieShuttleCallPercentage = 0.7f;
|
public float ZombieShuttleCallPercentage = 0.7f;
|
||||||
|
|
||||||
/// <summary>
|
[DataField]
|
||||||
/// Have we called the evac shuttle yet?
|
public EntProtoId ZombifySelfActionPrototype = "ActionTurnUndead";
|
||||||
/// </summary>
|
|
||||||
[DataField("shuttleCalled")]
|
|
||||||
public bool ShuttleCalled;
|
|
||||||
|
|
||||||
[ValidatePrototypeId<EntityPrototype>]
|
|
||||||
public const string ZombifySelfActionPrototype = "ActionTurnUndead";
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
using System.Diagnostics.CodeAnalysis;
|
using System.Diagnostics.CodeAnalysis;
|
||||||
using Content.Server.GameTicking.Rules.Components;
|
using Content.Server.GameTicking.Rules.Components;
|
||||||
using Content.Server.Station.Components;
|
using Content.Server.Station.Components;
|
||||||
using Robust.Shared.Collections;
|
using Robust.Shared.Collections;
|
||||||
|
|||||||
@@ -1,13 +1,9 @@
|
|||||||
using System.Diagnostics.CodeAnalysis;
|
|
||||||
using Content.Server.Atmos.EntitySystems;
|
using Content.Server.Atmos.EntitySystems;
|
||||||
using Content.Server.Chat.Managers;
|
using Content.Server.Chat.Managers;
|
||||||
using Content.Server.GameTicking.Rules.Components;
|
using Content.Server.GameTicking.Rules.Components;
|
||||||
using Content.Server.Station.Components;
|
|
||||||
using Robust.Server.GameObjects;
|
using Robust.Server.GameObjects;
|
||||||
using Robust.Shared.Collections;
|
|
||||||
using Robust.Shared.Map;
|
|
||||||
using Robust.Shared.Map.Components;
|
|
||||||
using Robust.Shared.Random;
|
using Robust.Shared.Random;
|
||||||
|
using Robust.Shared.Timing;
|
||||||
|
|
||||||
namespace Content.Server.GameTicking.Rules;
|
namespace Content.Server.GameTicking.Rules;
|
||||||
|
|
||||||
@@ -16,9 +12,9 @@ public abstract partial class GameRuleSystem<T> : EntitySystem where T : ICompon
|
|||||||
[Dependency] protected readonly IRobustRandom RobustRandom = default!;
|
[Dependency] protected readonly IRobustRandom RobustRandom = default!;
|
||||||
[Dependency] protected readonly IChatManager ChatManager = default!;
|
[Dependency] protected readonly IChatManager ChatManager = default!;
|
||||||
[Dependency] protected readonly GameTicker GameTicker = default!;
|
[Dependency] protected readonly GameTicker GameTicker = default!;
|
||||||
|
[Dependency] protected readonly IGameTiming Timing = default!;
|
||||||
|
|
||||||
// Not protected, just to be used in utility methods
|
// Not protected, just to be used in utility methods
|
||||||
[Dependency] private readonly SharedTransformSystem _transform = default!;
|
|
||||||
[Dependency] private readonly AtmosphereSystem _atmosphere = default!;
|
[Dependency] private readonly AtmosphereSystem _atmosphere = default!;
|
||||||
[Dependency] private readonly MapSystem _map = default!;
|
[Dependency] private readonly MapSystem _map = default!;
|
||||||
|
|
||||||
|
|||||||
File diff suppressed because it is too large
Load Diff
@@ -271,11 +271,12 @@ public sealed class PiratesRuleSystem : GameRuleSystem<PiratesRuleComponent>
|
|||||||
}
|
}
|
||||||
|
|
||||||
//Forcing one player to be a pirate.
|
//Forcing one player to be a pirate.
|
||||||
public void MakePirate(EntityUid mindId, MindComponent mind)
|
public void MakePirate(EntityUid entity)
|
||||||
{
|
{
|
||||||
if (!mind.OwnedEntity.HasValue)
|
if (!_mindSystem.TryGetMind(entity, out var mindId, out var mind))
|
||||||
return;
|
return;
|
||||||
SetOutfitCommand.SetOutfit(mind.OwnedEntity.Value, GearId, EntityManager);
|
|
||||||
|
SetOutfitCommand.SetOutfit(entity, GearId, EntityManager);
|
||||||
|
|
||||||
var pirateRule = EntityQuery<PiratesRuleComponent>().FirstOrDefault();
|
var pirateRule = EntityQuery<PiratesRuleComponent>().FirstOrDefault();
|
||||||
if (pirateRule == null)
|
if (pirateRule == null)
|
||||||
|
|||||||
@@ -1,7 +1,5 @@
|
|||||||
using System.Linq;
|
|
||||||
using Content.Server.Administration.Logs;
|
using Content.Server.Administration.Logs;
|
||||||
using Content.Server.Antag;
|
using Content.Server.Antag;
|
||||||
using Content.Server.Chat.Managers;
|
|
||||||
using Content.Server.EUI;
|
using Content.Server.EUI;
|
||||||
using Content.Server.Flash;
|
using Content.Server.Flash;
|
||||||
using Content.Server.GameTicking.Rules.Components;
|
using Content.Server.GameTicking.Rules.Components;
|
||||||
@@ -13,10 +11,12 @@ using Content.Server.Revolutionary;
|
|||||||
using Content.Server.Revolutionary.Components;
|
using Content.Server.Revolutionary.Components;
|
||||||
using Content.Server.Roles;
|
using Content.Server.Roles;
|
||||||
using Content.Server.RoundEnd;
|
using Content.Server.RoundEnd;
|
||||||
using Content.Shared.Chat;
|
using Content.Server.Shuttles.Systems;
|
||||||
|
using Content.Server.Station.Systems;
|
||||||
using Content.Shared.Database;
|
using Content.Shared.Database;
|
||||||
using Content.Shared.Humanoid;
|
using Content.Shared.Humanoid;
|
||||||
using Content.Shared.IdentityManagement;
|
using Content.Shared.IdentityManagement;
|
||||||
|
using Content.Shared.Inventory;
|
||||||
using Content.Shared.Mind;
|
using Content.Shared.Mind;
|
||||||
using Content.Shared.Mind.Components;
|
using Content.Shared.Mind.Components;
|
||||||
using Content.Shared.Mindshield.Components;
|
using Content.Shared.Mindshield.Components;
|
||||||
@@ -27,9 +27,9 @@ using Content.Shared.Revolutionary.Components;
|
|||||||
using Content.Shared.Roles;
|
using Content.Shared.Roles;
|
||||||
using Content.Shared.Stunnable;
|
using Content.Shared.Stunnable;
|
||||||
using Content.Shared.Zombies;
|
using Content.Shared.Zombies;
|
||||||
using Robust.Server.Audio;
|
using Robust.Shared.Prototypes;
|
||||||
using Robust.Server.GameObjects;
|
|
||||||
using Robust.Shared.Timing;
|
using Robust.Shared.Timing;
|
||||||
|
using System.Linq;
|
||||||
|
|
||||||
namespace Content.Server.GameTicking.Rules;
|
namespace Content.Server.GameTicking.Rules;
|
||||||
|
|
||||||
@@ -39,7 +39,6 @@ namespace Content.Server.GameTicking.Rules;
|
|||||||
public sealed class RevolutionaryRuleSystem : GameRuleSystem<RevolutionaryRuleComponent>
|
public sealed class RevolutionaryRuleSystem : GameRuleSystem<RevolutionaryRuleComponent>
|
||||||
{
|
{
|
||||||
[Dependency] private readonly IAdminLogManager _adminLogManager = default!;
|
[Dependency] private readonly IAdminLogManager _adminLogManager = default!;
|
||||||
[Dependency] private readonly IChatManager _chatManager = default!;
|
|
||||||
[Dependency] private readonly IGameTiming _timing = default!;
|
[Dependency] private readonly IGameTiming _timing = default!;
|
||||||
[Dependency] private readonly AntagSelectionSystem _antagSelection = default!;
|
[Dependency] private readonly AntagSelectionSystem _antagSelection = default!;
|
||||||
[Dependency] private readonly EuiManager _euiMan = default!;
|
[Dependency] private readonly EuiManager _euiMan = default!;
|
||||||
@@ -50,12 +49,13 @@ public sealed class RevolutionaryRuleSystem : GameRuleSystem<RevolutionaryRuleCo
|
|||||||
[Dependency] private readonly RoleSystem _role = default!;
|
[Dependency] private readonly RoleSystem _role = default!;
|
||||||
[Dependency] private readonly SharedStunSystem _stun = default!;
|
[Dependency] private readonly SharedStunSystem _stun = default!;
|
||||||
[Dependency] private readonly RoundEndSystem _roundEnd = default!;
|
[Dependency] private readonly RoundEndSystem _roundEnd = default!;
|
||||||
[Dependency] private readonly AudioSystem _audioSystem = default!;
|
[Dependency] private readonly StationSystem _stationSystem = default!;
|
||||||
|
[Dependency] private readonly EmergencyShuttleSystem _emergencyShuttle = default!;
|
||||||
|
[Dependency] private readonly InventorySystem _inventory = default!;
|
||||||
|
|
||||||
[ValidatePrototypeId<NpcFactionPrototype>]
|
//Used in OnPostFlash, no reference to the rule component is available
|
||||||
public const string RevolutionaryNpcFaction = "Revolutionary";
|
public readonly ProtoId<NpcFactionPrototype> RevolutionaryNpcFaction = "Revolutionary";
|
||||||
[ValidatePrototypeId<AntagPrototype>]
|
public readonly ProtoId<NpcFactionPrototype> RevPrototypeId = "Rev";
|
||||||
public const string RevolutionaryAntagRole = "Rev";
|
|
||||||
|
|
||||||
public override void Initialize()
|
public override void Initialize()
|
||||||
{
|
{
|
||||||
@@ -69,15 +69,20 @@ public sealed class RevolutionaryRuleSystem : GameRuleSystem<RevolutionaryRuleCo
|
|||||||
SubscribeLocalEvent<HeadRevolutionaryComponent, AfterFlashedEvent>(OnPostFlash);
|
SubscribeLocalEvent<HeadRevolutionaryComponent, AfterFlashedEvent>(OnPostFlash);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
//Set miniumum players
|
||||||
|
protected override void Added(EntityUid uid, RevolutionaryRuleComponent component, GameRuleComponent gameRule, GameRuleAddedEvent args)
|
||||||
|
{
|
||||||
|
base.Added(uid, component, gameRule, args);
|
||||||
|
|
||||||
|
gameRule.MinPlayers = component.MinPlayers;
|
||||||
|
}
|
||||||
|
|
||||||
protected override void Started(EntityUid uid, RevolutionaryRuleComponent component, GameRuleComponent gameRule, GameRuleStartedEvent args)
|
protected override void Started(EntityUid uid, RevolutionaryRuleComponent component, GameRuleComponent gameRule, GameRuleStartedEvent args)
|
||||||
{
|
{
|
||||||
base.Started(uid, component, gameRule, args);
|
base.Started(uid, component, gameRule, args);
|
||||||
component.CommandCheck = _timing.CurTime + component.TimerWait;
|
component.CommandCheck = _timing.CurTime + component.TimerWait;
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Checks if the round should end and also checks who has a mindshield.
|
|
||||||
/// </summary>
|
|
||||||
protected override void ActiveTick(EntityUid uid, RevolutionaryRuleComponent component, GameRuleComponent gameRule, float frameTime)
|
protected override void ActiveTick(EntityUid uid, RevolutionaryRuleComponent component, GameRuleComponent gameRule, float frameTime)
|
||||||
{
|
{
|
||||||
base.ActiveTick(uid, component, gameRule, frameTime);
|
base.ActiveTick(uid, component, gameRule, frameTime);
|
||||||
@@ -139,63 +144,63 @@ public sealed class RevolutionaryRuleSystem : GameRuleSystem<RevolutionaryRuleCo
|
|||||||
args.Append(Loc.GetString(head ? "head-rev-briefing" : "rev-briefing"));
|
args.Append(Loc.GetString(head ? "head-rev-briefing" : "rev-briefing"));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
//Check for enough players to start rule
|
||||||
private void OnStartAttempt(RoundStartAttemptEvent ev)
|
private void OnStartAttempt(RoundStartAttemptEvent ev)
|
||||||
{
|
{
|
||||||
var query = AllEntityQuery<RevolutionaryRuleComponent, GameRuleComponent>();
|
TryRoundStartAttempt(ev, Loc.GetString("roles-antag-rev-name"));
|
||||||
while (query.MoveNext(out var uid, out var comp, out var gameRule))
|
|
||||||
{
|
|
||||||
_antagSelection.AttemptStartGameRule(ev, uid, comp.MinPlayers, gameRule);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private void OnPlayerJobAssigned(RulePlayerJobsAssignedEvent ev)
|
private void OnPlayerJobAssigned(RulePlayerJobsAssignedEvent ev)
|
||||||
{
|
{
|
||||||
var query = QueryActiveRules();
|
var query = QueryActiveRules();
|
||||||
while (query.MoveNext(out _, out var comp, out _))
|
while (query.MoveNext(out var uid, out var activeGameRule, out var comp, out var gameRule))
|
||||||
{
|
{
|
||||||
_antagSelection.EligiblePlayers(comp.HeadRevPrototypeId, comp.MaxHeadRevs, comp.PlayersPerHeadRev, comp.HeadRevStartSound,
|
var eligiblePlayers = _antagSelection.GetEligiblePlayers(ev.Players, comp.HeadRevPrototypeId);
|
||||||
"head-rev-role-greeting", "#5e9cff", out var chosen);
|
|
||||||
if (chosen.Any())
|
if (eligiblePlayers.Count == 0)
|
||||||
GiveHeadRev(chosen, comp.HeadRevPrototypeId, comp);
|
continue;
|
||||||
else
|
|
||||||
{
|
var headRevCount = _antagSelection.CalculateAntagCount(ev.Players.Length, comp.PlayersPerHeadRev, comp.MaxHeadRevs);
|
||||||
_chatManager.SendAdminAnnouncement(Loc.GetString("rev-no-heads"));
|
|
||||||
}
|
var headRevs = _antagSelection.ChooseAntags(headRevCount, eligiblePlayers);
|
||||||
|
|
||||||
|
GiveHeadRev(headRevs, comp.HeadRevPrototypeId, comp);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private void GiveHeadRev(List<EntityUid> chosen, string antagProto, RevolutionaryRuleComponent comp)
|
private void GiveHeadRev(IEnumerable<EntityUid> chosen, ProtoId<AntagPrototype> antagProto, RevolutionaryRuleComponent comp)
|
||||||
{
|
{
|
||||||
foreach (var headRev in chosen)
|
foreach (var headRev in chosen)
|
||||||
|
GiveHeadRev(headRev, antagProto, comp);
|
||||||
|
}
|
||||||
|
private void GiveHeadRev(EntityUid chosen, ProtoId<AntagPrototype> antagProto, RevolutionaryRuleComponent comp)
|
||||||
{
|
{
|
||||||
RemComp<CommandStaffComponent>(headRev);
|
RemComp<CommandStaffComponent>(chosen);
|
||||||
|
|
||||||
var inCharacterName = MetaData(headRev).EntityName;
|
var inCharacterName = MetaData(chosen).EntityName;
|
||||||
if (_mind.TryGetMind(headRev, out var mindId, out var mind))
|
|
||||||
|
if (!_mind.TryGetMind(chosen, out var mind, out _))
|
||||||
|
return;
|
||||||
|
|
||||||
|
if (!_role.MindHasRole<RevolutionaryRoleComponent>(mind))
|
||||||
{
|
{
|
||||||
if (!_role.MindHasRole<RevolutionaryRoleComponent>(mindId))
|
_role.MindAddRole(mind, new RevolutionaryRoleComponent { PrototypeId = antagProto }, silent: true);
|
||||||
{
|
|
||||||
_role.MindAddRole(mindId, new RevolutionaryRoleComponent { PrototypeId = antagProto });
|
|
||||||
}
|
|
||||||
if (mind.Session != null)
|
|
||||||
{
|
|
||||||
comp.HeadRevs.Add(inCharacterName, mindId);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
_antagSelection.GiveAntagBagGear(headRev, comp.StartingGear);
|
comp.HeadRevs.Add(inCharacterName, mind);
|
||||||
EnsureComp<RevolutionaryComponent>(headRev);
|
_inventory.SpawnItemsOnEntity(chosen, comp.StartingGear);
|
||||||
EnsureComp<HeadRevolutionaryComponent>(headRev);
|
var revComp = EnsureComp<RevolutionaryComponent>(chosen);
|
||||||
}
|
EnsureComp<HeadRevolutionaryComponent>(chosen);
|
||||||
|
|
||||||
|
_antagSelection.SendBriefing(chosen, Loc.GetString("head-rev-role-greeting"), Color.CornflowerBlue, revComp.RevStartSound);
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Called when a Head Rev uses a flash in melee to convert somebody else.
|
/// Called when a Head Rev uses a flash in melee to convert somebody else.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public void OnPostFlash(EntityUid uid, HeadRevolutionaryComponent comp, ref AfterFlashedEvent ev)
|
private void OnPostFlash(EntityUid uid, HeadRevolutionaryComponent comp, ref AfterFlashedEvent ev)
|
||||||
{
|
{
|
||||||
TryComp<AlwaysRevolutionaryConvertibleComponent>(ev.Target, out var alwaysConvertibleComp);
|
var alwaysConvertible = HasComp<AlwaysRevolutionaryConvertibleComponent>(ev.Target);
|
||||||
var alwaysConvertible = alwaysConvertibleComp != null;
|
|
||||||
|
|
||||||
if (!_mind.TryGetMind(ev.Target, out var mindId, out var mind) && !alwaysConvertible)
|
if (!_mind.TryGetMind(ev.Target, out var mindId, out var mind) && !alwaysConvertible)
|
||||||
return;
|
return;
|
||||||
@@ -211,8 +216,9 @@ public sealed class RevolutionaryRuleSystem : GameRuleSystem<RevolutionaryRuleCo
|
|||||||
}
|
}
|
||||||
|
|
||||||
_npcFaction.AddFaction(ev.Target, RevolutionaryNpcFaction);
|
_npcFaction.AddFaction(ev.Target, RevolutionaryNpcFaction);
|
||||||
EnsureComp<RevolutionaryComponent>(ev.Target);
|
var revComp = EnsureComp<RevolutionaryComponent>(ev.Target);
|
||||||
_stun.TryParalyze(ev.Target, comp.StunTime, true);
|
_stun.TryParalyze(ev.Target, comp.StunTime, true);
|
||||||
|
|
||||||
if (ev.User != null)
|
if (ev.User != null)
|
||||||
{
|
{
|
||||||
_adminLogManager.Add(LogType.Mind, LogImpact.Medium, $"{ToPrettyString(ev.User.Value)} converted {ToPrettyString(ev.Target)} into a Revolutionary");
|
_adminLogManager.Add(LogType.Mind, LogImpact.Medium, $"{ToPrettyString(ev.User.Value)} converted {ToPrettyString(ev.Target)} into a Revolutionary");
|
||||||
@@ -223,20 +229,16 @@ public sealed class RevolutionaryRuleSystem : GameRuleSystem<RevolutionaryRuleCo
|
|||||||
|
|
||||||
if (mindId == default || !_role.MindHasRole<RevolutionaryRoleComponent>(mindId))
|
if (mindId == default || !_role.MindHasRole<RevolutionaryRoleComponent>(mindId))
|
||||||
{
|
{
|
||||||
_role.MindAddRole(mindId, new RevolutionaryRoleComponent { PrototypeId = RevolutionaryAntagRole });
|
_role.MindAddRole(mindId, new RevolutionaryRoleComponent { PrototypeId = RevPrototypeId });
|
||||||
}
|
|
||||||
if (mind?.Session != null)
|
|
||||||
{
|
|
||||||
var message = Loc.GetString("rev-role-greeting");
|
|
||||||
var wrappedMessage = Loc.GetString("chat-manager-server-wrap-message", ("message", message));
|
|
||||||
_chatManager.ChatMessageToOne(ChatChannel.Server, message, wrappedMessage, default, false, mind.Session.Channel, Color.Red);
|
|
||||||
_audioSystem.PlayGlobal("/Audio/Ambience/Antag/headrev_start.ogg", ev.Target);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public void OnHeadRevAdmin(EntityUid mindId, MindComponent? mind = null)
|
if (mind?.Session != null)
|
||||||
|
_antagSelection.SendBriefing(mind.Session, Loc.GetString("rev-role-greeting"), Color.Red, revComp.RevStartSound);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void OnHeadRevAdmin(EntityUid entity)
|
||||||
{
|
{
|
||||||
if (!Resolve(mindId, ref mind))
|
if (HasComp<HeadRevolutionaryComponent>(entity))
|
||||||
return;
|
return;
|
||||||
|
|
||||||
var revRule = EntityQuery<RevolutionaryRuleComponent>().FirstOrDefault();
|
var revRule = EntityQuery<RevolutionaryRuleComponent>().FirstOrDefault();
|
||||||
@@ -246,24 +248,10 @@ public sealed class RevolutionaryRuleSystem : GameRuleSystem<RevolutionaryRuleCo
|
|||||||
revRule = Comp<RevolutionaryRuleComponent>(ruleEnt);
|
revRule = Comp<RevolutionaryRuleComponent>(ruleEnt);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!HasComp<HeadRevolutionaryComponent>(mind.OwnedEntity))
|
GiveHeadRev(entity, revRule.HeadRevPrototypeId, revRule);
|
||||||
{
|
|
||||||
if (mind.OwnedEntity != null)
|
|
||||||
{
|
|
||||||
var player = new List<EntityUid>
|
|
||||||
{
|
|
||||||
mind.OwnedEntity.Value
|
|
||||||
};
|
|
||||||
GiveHeadRev(player, RevolutionaryAntagRole, revRule);
|
|
||||||
}
|
|
||||||
if (mind.Session != null)
|
|
||||||
{
|
|
||||||
var message = Loc.GetString("head-rev-role-greeting");
|
|
||||||
var wrappedMessage = Loc.GetString("chat-manager-server-wrap-message", ("message", message));
|
|
||||||
_chatManager.ChatMessageToOne(ChatChannel.Server, message, wrappedMessage, default, false, mind.Session.Channel, Color.FromHex("#5e9cff"));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
//TODO: Enemies of the revolution
|
||||||
private void OnCommandMobStateChanged(EntityUid uid, CommandStaffComponent comp, MobStateChangedEvent ev)
|
private void OnCommandMobStateChanged(EntityUid uid, CommandStaffComponent comp, MobStateChangedEvent ev)
|
||||||
{
|
{
|
||||||
if (ev.NewMobState == MobState.Dead || ev.NewMobState == MobState.Invalid)
|
if (ev.NewMobState == MobState.Dead || ev.NewMobState == MobState.Invalid)
|
||||||
@@ -283,7 +271,7 @@ public sealed class RevolutionaryRuleSystem : GameRuleSystem<RevolutionaryRuleCo
|
|||||||
commandList.Add(id);
|
commandList.Add(id);
|
||||||
}
|
}
|
||||||
|
|
||||||
return _antagSelection.IsGroupDead(commandList, true);
|
return IsGroupDead(commandList, true);
|
||||||
}
|
}
|
||||||
|
|
||||||
private void OnHeadRevMobStateChanged(EntityUid uid, HeadRevolutionaryComponent comp, MobStateChangedEvent ev)
|
private void OnHeadRevMobStateChanged(EntityUid uid, HeadRevolutionaryComponent comp, MobStateChangedEvent ev)
|
||||||
@@ -307,7 +295,7 @@ public sealed class RevolutionaryRuleSystem : GameRuleSystem<RevolutionaryRuleCo
|
|||||||
}
|
}
|
||||||
|
|
||||||
// If no Head Revs are alive all normal Revs will lose their Rev status and rejoin Nanotrasen
|
// If no Head Revs are alive all normal Revs will lose their Rev status and rejoin Nanotrasen
|
||||||
if (_antagSelection.IsGroupDead(headRevList, false))
|
if (IsGroupDead(headRevList, false))
|
||||||
{
|
{
|
||||||
var rev = AllEntityQuery<RevolutionaryComponent, MindContainerComponent>();
|
var rev = AllEntityQuery<RevolutionaryComponent, MindContainerComponent>();
|
||||||
while (rev.MoveNext(out var uid, out _, out var mc))
|
while (rev.MoveNext(out var uid, out _, out var mc))
|
||||||
@@ -338,6 +326,38 @@ public sealed class RevolutionaryRuleSystem : GameRuleSystem<RevolutionaryRuleCo
|
|||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Will take a group of entities and check if they are all alive or dead
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="list">The list of the entities</param>
|
||||||
|
/// <param name="checkOffStation">Bool for if you want to check if someone is in space and consider them dead. (Won't check when emergency shuttle arrives just in case)</param>
|
||||||
|
/// <returns></returns>
|
||||||
|
private bool IsGroupDead(List<EntityUid> list, bool checkOffStation)
|
||||||
|
{
|
||||||
|
var dead = 0;
|
||||||
|
foreach (var entity in list)
|
||||||
|
{
|
||||||
|
if (TryComp<MobStateComponent>(entity, out var state))
|
||||||
|
{
|
||||||
|
if (state.CurrentState == MobState.Dead || state.CurrentState == MobState.Invalid)
|
||||||
|
{
|
||||||
|
dead++;
|
||||||
|
}
|
||||||
|
else if (checkOffStation && _stationSystem.GetOwningStation(entity) == null && !_emergencyShuttle.EmergencyShuttleArrived)
|
||||||
|
{
|
||||||
|
dead++;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
//If they don't have the MobStateComponent they might as well be dead.
|
||||||
|
else
|
||||||
|
{
|
||||||
|
dead++;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return dead == list.Count || list.Count == 0;
|
||||||
|
}
|
||||||
|
|
||||||
private static readonly string[] Outcomes =
|
private static readonly string[] Outcomes =
|
||||||
{
|
{
|
||||||
// revs survived and heads survived... how
|
// revs survived and heads survived... how
|
||||||
|
|||||||
@@ -1,42 +1,29 @@
|
|||||||
using Content.Server.Chat.Managers;
|
using Content.Server.Antag;
|
||||||
using Content.Server.GameTicking.Rules.Components;
|
using Content.Server.GameTicking.Rules.Components;
|
||||||
using Content.Server.Mind;
|
using Content.Server.Mind;
|
||||||
using Content.Server.Objectives;
|
using Content.Server.Objectives;
|
||||||
using Content.Server.Roles;
|
using Content.Server.Roles;
|
||||||
|
using Content.Shared.Antag;
|
||||||
|
using Content.Shared.CombatMode.Pacification;
|
||||||
|
using Content.Shared.Humanoid;
|
||||||
|
using Content.Shared.Inventory;
|
||||||
using Content.Shared.Mind;
|
using Content.Shared.Mind;
|
||||||
using Content.Shared.Objectives.Components;
|
using Content.Shared.Objectives.Components;
|
||||||
using Content.Shared.Roles;
|
using Content.Shared.Roles;
|
||||||
using Content.Shared.Roles.Jobs;
|
|
||||||
using Robust.Shared.Player;
|
|
||||||
using Robust.Shared.Random;
|
using Robust.Shared.Random;
|
||||||
using Robust.Shared.Prototypes;
|
|
||||||
using System.Linq;
|
using System.Linq;
|
||||||
using Content.Shared.Humanoid;
|
|
||||||
using Content.Server.Antag;
|
|
||||||
using Robust.Server.Audio;
|
|
||||||
using Content.Shared.CombatMode.Pacification;
|
|
||||||
using Content.Shared.Random;
|
|
||||||
|
|
||||||
namespace Content.Server.GameTicking.Rules;
|
namespace Content.Server.GameTicking.Rules;
|
||||||
|
|
||||||
public sealed class ThiefRuleSystem : GameRuleSystem<ThiefRuleComponent>
|
public sealed class ThiefRuleSystem : GameRuleSystem<ThiefRuleComponent>
|
||||||
{
|
{
|
||||||
[Dependency] private readonly IChatManager _chatManager = default!;
|
|
||||||
[Dependency] private readonly IRobustRandom _random = default!;
|
[Dependency] private readonly IRobustRandom _random = default!;
|
||||||
[Dependency] private readonly AntagSelectionSystem _antagSelection = default!;
|
[Dependency] private readonly AntagSelectionSystem _antagSelection = default!;
|
||||||
[Dependency] private readonly AudioSystem _audio = default!;
|
|
||||||
[Dependency] private readonly MindSystem _mindSystem = default!;
|
[Dependency] private readonly MindSystem _mindSystem = default!;
|
||||||
[Dependency] private readonly SharedRoleSystem _roleSystem = default!;
|
[Dependency] private readonly SharedRoleSystem _roleSystem = default!;
|
||||||
[Dependency] private readonly ObjectivesSystem _objectives = default!;
|
[Dependency] private readonly ObjectivesSystem _objectives = default!;
|
||||||
|
[Dependency] private readonly InventorySystem _inventory = default!;
|
||||||
|
|
||||||
[ValidatePrototypeId<WeightedRandomPrototype>]
|
|
||||||
const string BigObjectiveGroup = "ThiefBigObjectiveGroups";
|
|
||||||
[ValidatePrototypeId<WeightedRandomPrototype>]
|
|
||||||
const string SmallObjectiveGroup = "ThiefObjectiveGroups";
|
|
||||||
[ValidatePrototypeId<WeightedRandomPrototype>]
|
|
||||||
const string EscapeObjectiveGroup = "ThiefEscapeObjectiveGroups";
|
|
||||||
|
|
||||||
private const float BigObjectiveChance = 0.7f;
|
|
||||||
public override void Initialize()
|
public override void Initialize()
|
||||||
{
|
{
|
||||||
base.Initialize();
|
base.Initialize();
|
||||||
@@ -49,99 +36,95 @@ public sealed class ThiefRuleSystem : GameRuleSystem<ThiefRuleComponent>
|
|||||||
|
|
||||||
private void OnPlayersSpawned(RulePlayerJobsAssignedEvent ev)
|
private void OnPlayersSpawned(RulePlayerJobsAssignedEvent ev)
|
||||||
{
|
{
|
||||||
var query = EntityQueryEnumerator<ThiefRuleComponent, GameRuleComponent>();
|
var query = QueryActiveRules();
|
||||||
while (query.MoveNext(out var uid, out var thief, out var gameRule))
|
while (query.MoveNext(out _, out var comp, out _))
|
||||||
{
|
{
|
||||||
//Chance to not lauch gamerule
|
//Chance to not launch the game rule
|
||||||
if (_random.Prob(thief.RuleChance))
|
if (!_random.Prob(comp.RuleChance))
|
||||||
{
|
|
||||||
if (!GameTicker.IsGameRuleAdded(uid, gameRule))
|
|
||||||
continue;
|
continue;
|
||||||
|
|
||||||
foreach (var player in ev.Players)
|
//Get all players eligible for this role, allow selecting existing antags
|
||||||
{
|
|
||||||
if (!ev.Profiles.TryGetValue(player.UserId, out var profile))
|
|
||||||
continue;
|
|
||||||
|
|
||||||
thief.StartCandidates[player] = profile;
|
|
||||||
}
|
|
||||||
DoThiefStart(thief);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private void DoThiefStart(ThiefRuleComponent component)
|
|
||||||
{
|
|
||||||
if (!component.StartCandidates.Any())
|
|
||||||
{
|
|
||||||
Log.Error("There are no players who can become thieves.");
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
var startThiefCount = Math.Min(component.MaxAllowThief, component.StartCandidates.Count);
|
|
||||||
var thiefPool = _antagSelection.FindPotentialAntags(component.StartCandidates, component.ThiefPrototypeId);
|
|
||||||
//TO DO: When voxes specifies are added, increase their chance of becoming a thief by 4 times >:)
|
//TO DO: When voxes specifies are added, increase their chance of becoming a thief by 4 times >:)
|
||||||
|
var eligiblePlayers = _antagSelection.GetEligiblePlayers(ev.Players, comp.ThiefPrototypeId, acceptableAntags: AntagAcceptability.All, allowNonHumanoids: true);
|
||||||
|
|
||||||
//Add 1, as Next() is exclusive of maxValue
|
//Abort if there are none
|
||||||
var numberOfThievesToSelect = _random.Next(1, startThiefCount + 1);
|
if (eligiblePlayers.Count == 0)
|
||||||
|
continue;
|
||||||
|
|
||||||
//While we dont have the correct number of thieves, and there are potential thieves remaining
|
//Calculate number of thieves to choose
|
||||||
while (component.ThievesMinds.Count < numberOfThievesToSelect && thiefPool.Count > 0)
|
var thiefCount = _random.Next(1, comp.MaxAllowThief + 1);
|
||||||
{
|
|
||||||
Log.Info($"{numberOfThievesToSelect} thieves required, {component.ThievesMinds.Count} currently chosen, {thiefPool.Count} potentials");
|
//Select our theives
|
||||||
var selectedThieves = _antagSelection.PickAntag(numberOfThievesToSelect - component.ThievesMinds.Count, thiefPool);
|
var thieves = _antagSelection.ChooseAntags(thiefCount, eligiblePlayers);
|
||||||
foreach (var thief in selectedThieves)
|
|
||||||
{
|
MakeThief(thieves, comp, comp.PacifistThieves);
|
||||||
MakeThief(component, thief, component.PacifistThieves);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public bool MakeThief(ThiefRuleComponent thiefRule, ICommonSession thief, bool addPacified)
|
public void MakeThief(List<EntityUid> players, ThiefRuleComponent thiefRule, bool addPacified)
|
||||||
|
{
|
||||||
|
foreach (var thief in players)
|
||||||
|
{
|
||||||
|
MakeThief(thief, thiefRule, addPacified);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public void MakeThief(EntityUid thief, ThiefRuleComponent thiefRule, bool addPacified)
|
||||||
{
|
{
|
||||||
//checks
|
|
||||||
if (!_mindSystem.TryGetMind(thief, out var mindId, out var mind))
|
if (!_mindSystem.TryGetMind(thief, out var mindId, out var mind))
|
||||||
{
|
return;
|
||||||
Log.Info("Failed getting mind for picked thief.");
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
if (HasComp<ThiefRoleComponent>(mindId))
|
if (HasComp<ThiefRoleComponent>(mindId))
|
||||||
{
|
return;
|
||||||
Log.Error($"Player {thief.Name} is already a thief.");
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
if (mind.OwnedEntity is not { } entity)
|
|
||||||
{
|
|
||||||
Log.Error("Mind picked for thief did not have an attached entity.");
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Assign thief roles
|
// Assign thief roles
|
||||||
_roleSystem.MindAddRole(mindId, new ThiefRoleComponent
|
_roleSystem.MindAddRole(mindId, new ThiefRoleComponent
|
||||||
{
|
{
|
||||||
PrototypeId = thiefRule.ThiefPrototypeId
|
PrototypeId = thiefRule.ThiefPrototypeId,
|
||||||
});
|
}, silent: true);
|
||||||
|
|
||||||
//Add Pacified
|
//Add Pacified
|
||||||
//To Do: Long-term this should just be using the antag code to add components.
|
//To Do: Long-term this should just be using the antag code to add components.
|
||||||
if (addPacified) //This check is important because some servers may want to disable the thief's pacifism. Do not remove.
|
if (addPacified) //This check is important because some servers may want to disable the thief's pacifism. Do not remove.
|
||||||
{
|
{
|
||||||
EnsureComp<PacifiedComponent>(mind.OwnedEntity.Value);
|
EnsureComp<PacifiedComponent>(thief);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Notificate player about new role assignment
|
//Generate objectives
|
||||||
if (_mindSystem.TryGetSession(mindId, out var session))
|
GenerateObjectives(mindId, mind, thiefRule);
|
||||||
|
|
||||||
|
//Send briefing here to account for humanoid/animal
|
||||||
|
_antagSelection.SendBriefing(thief, MakeBriefing(thief), null, thiefRule.GreetingSound);
|
||||||
|
|
||||||
|
// Give starting items
|
||||||
|
_inventory.SpawnItemsOnEntity(thief, thiefRule.StarterItems);
|
||||||
|
|
||||||
|
thiefRule.ThievesMinds.Add(mindId);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void AdminMakeThief(EntityUid entity, bool addPacified)
|
||||||
{
|
{
|
||||||
_audio.PlayGlobal(thiefRule.GreetingSound, session);
|
var thiefRule = EntityQuery<ThiefRuleComponent>().FirstOrDefault();
|
||||||
_chatManager.DispatchServerMessage(session, MakeBriefing(mind.OwnedEntity.Value));
|
if (thiefRule == null)
|
||||||
|
{
|
||||||
|
GameTicker.StartGameRule("Thief", out var ruleEntity);
|
||||||
|
thiefRule = Comp<ThiefRuleComponent>(ruleEntity);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (HasComp<ThiefRoleComponent>(entity))
|
||||||
|
return;
|
||||||
|
|
||||||
|
MakeThief(entity, thiefRule, addPacified);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void GenerateObjectives(EntityUid mindId, MindComponent mind, ThiefRuleComponent thiefRule)
|
||||||
|
{
|
||||||
// Give thieves their objectives
|
// Give thieves their objectives
|
||||||
var difficulty = 0f;
|
var difficulty = 0f;
|
||||||
|
|
||||||
if (_random.Prob(BigObjectiveChance)) // 70% chance to 1 big objective (structure or animal)
|
if (_random.Prob(thiefRule.BigObjectiveChance)) // 70% chance to 1 big objective (structure or animal)
|
||||||
{
|
{
|
||||||
var objective = _objectives.GetRandomObjective(mindId, mind, BigObjectiveGroup);
|
var objective = _objectives.GetRandomObjective(mindId, mind, thiefRule.BigObjectiveGroup);
|
||||||
if (objective != null)
|
if (objective != null)
|
||||||
{
|
{
|
||||||
_mindSystem.AddObjective(mindId, mind, objective.Value);
|
_mindSystem.AddObjective(mindId, mind, objective.Value);
|
||||||
@@ -151,7 +134,7 @@ public sealed class ThiefRuleSystem : GameRuleSystem<ThiefRuleComponent>
|
|||||||
|
|
||||||
for (var i = 0; i < thiefRule.MaxStealObjectives && thiefRule.MaxObjectiveDifficulty > difficulty; i++) // Many small objectives
|
for (var i = 0; i < thiefRule.MaxStealObjectives && thiefRule.MaxObjectiveDifficulty > difficulty; i++) // Many small objectives
|
||||||
{
|
{
|
||||||
var objective = _objectives.GetRandomObjective(mindId, mind, SmallObjectiveGroup);
|
var objective = _objectives.GetRandomObjective(mindId, mind, thiefRule.SmallObjectiveGroup);
|
||||||
if (objective == null)
|
if (objective == null)
|
||||||
continue;
|
continue;
|
||||||
|
|
||||||
@@ -160,27 +143,9 @@ public sealed class ThiefRuleSystem : GameRuleSystem<ThiefRuleComponent>
|
|||||||
}
|
}
|
||||||
|
|
||||||
//Escape target
|
//Escape target
|
||||||
var escapeObjective = _objectives.GetRandomObjective(mindId, mind, EscapeObjectiveGroup);
|
var escapeObjective = _objectives.GetRandomObjective(mindId, mind, thiefRule.EscapeObjectiveGroup);
|
||||||
if (escapeObjective != null)
|
if (escapeObjective != null)
|
||||||
_mindSystem.AddObjective(mindId, mind, escapeObjective.Value);
|
_mindSystem.AddObjective(mindId, mind, escapeObjective.Value);
|
||||||
|
|
||||||
// Give starting items
|
|
||||||
_antagSelection.GiveAntagBagGear(mind.OwnedEntity.Value, thiefRule.StarterItems);
|
|
||||||
|
|
||||||
thiefRule.ThievesMinds.Add(mindId);
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
public void AdminMakeThief(ICommonSession thief, bool addPacified)
|
|
||||||
{
|
|
||||||
var thiefRule = EntityQuery<ThiefRuleComponent>().FirstOrDefault();
|
|
||||||
if (thiefRule == null)
|
|
||||||
{
|
|
||||||
GameTicker.StartGameRule("Thief", out var ruleEntity);
|
|
||||||
thiefRule = Comp<ThiefRuleComponent>(ruleEntity);
|
|
||||||
}
|
|
||||||
|
|
||||||
MakeThief(thiefRule, thief, addPacified);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
//Add mind briefing
|
//Add mind briefing
|
||||||
|
|||||||
@@ -1,13 +1,10 @@
|
|||||||
using System.Linq;
|
|
||||||
using Content.Server.Antag;
|
using Content.Server.Antag;
|
||||||
using Content.Server.Chat.Managers;
|
|
||||||
using Content.Server.GameTicking.Rules.Components;
|
using Content.Server.GameTicking.Rules.Components;
|
||||||
using Content.Server.Mind;
|
using Content.Server.Mind;
|
||||||
using Content.Server.NPC.Systems;
|
using Content.Server.NPC.Systems;
|
||||||
using Content.Server.Objectives;
|
using Content.Server.Objectives;
|
||||||
using Content.Server.PDA.Ringer;
|
using Content.Server.PDA.Ringer;
|
||||||
using Content.Server.Roles;
|
using Content.Server.Roles;
|
||||||
using Content.Server.Shuttles.Components;
|
|
||||||
using Content.Server.Traitor.Uplink;
|
using Content.Server.Traitor.Uplink;
|
||||||
using Content.Shared.CCVar;
|
using Content.Shared.CCVar;
|
||||||
using Content.Shared.Dataset;
|
using Content.Shared.Dataset;
|
||||||
@@ -15,17 +12,15 @@ using Content.Shared.Mind;
|
|||||||
using Content.Shared.Mobs.Systems;
|
using Content.Shared.Mobs.Systems;
|
||||||
using Content.Shared.Objectives.Components;
|
using Content.Shared.Objectives.Components;
|
||||||
using Content.Shared.PDA;
|
using Content.Shared.PDA;
|
||||||
using Content.Shared.Preferences;
|
|
||||||
using Content.Shared.Roles;
|
using Content.Shared.Roles;
|
||||||
using Content.Shared.Roles.Jobs;
|
using Content.Shared.Roles.Jobs;
|
||||||
using Robust.Server.Player;
|
using Robust.Server.Player;
|
||||||
using Robust.Shared.Audio;
|
|
||||||
using Robust.Shared.Audio.Systems;
|
|
||||||
using Robust.Shared.Configuration;
|
using Robust.Shared.Configuration;
|
||||||
using Robust.Shared.Player;
|
|
||||||
using Robust.Shared.Prototypes;
|
using Robust.Shared.Prototypes;
|
||||||
using Robust.Shared.Random;
|
using Robust.Shared.Random;
|
||||||
using Robust.Shared.Timing;
|
using Robust.Shared.Timing;
|
||||||
|
using System.Linq;
|
||||||
|
using System.Text;
|
||||||
|
|
||||||
namespace Content.Server.GameTicking.Rules;
|
namespace Content.Server.GameTicking.Rules;
|
||||||
|
|
||||||
@@ -35,16 +30,15 @@ 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!;
|
||||||
[Dependency] private readonly IConfigurationManager _cfg = default!;
|
[Dependency] private readonly IConfigurationManager _cfg = default!;
|
||||||
[Dependency] private readonly IChatManager _chatManager = default!;
|
[Dependency] private readonly IPlayerManager _playerManager = default!;
|
||||||
[Dependency] private readonly IGameTiming _gameTiming = default!;
|
|
||||||
[Dependency] private readonly NpcFactionSystem _npcFaction = default!;
|
[Dependency] private readonly NpcFactionSystem _npcFaction = 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!;
|
||||||
[Dependency] private readonly SharedAudioSystem _audioSystem = default!;
|
|
||||||
[Dependency] private readonly MindSystem _mindSystem = default!;
|
[Dependency] private readonly MindSystem _mindSystem = default!;
|
||||||
[Dependency] private readonly SharedRoleSystem _roleSystem = default!;
|
[Dependency] private readonly SharedRoleSystem _roleSystem = default!;
|
||||||
[Dependency] private readonly SharedJobSystem _jobs = default!;
|
[Dependency] private readonly SharedJobSystem _jobs = default!;
|
||||||
[Dependency] private readonly ObjectivesSystem _objectives = default!;
|
[Dependency] private readonly ObjectivesSystem _objectives = default!;
|
||||||
|
[Dependency] private readonly IGameTiming _timing = default!;
|
||||||
|
|
||||||
private int PlayersPerTraitor => _cfg.GetCVar(CCVars.TraitorPlayersPerTraitor);
|
private int PlayersPerTraitor => _cfg.GetCVar(CCVars.TraitorPlayersPerTraitor);
|
||||||
private int MaxTraitors => _cfg.GetCVar(CCVars.TraitorMaxTraitors);
|
private int MaxTraitors => _cfg.GetCVar(CCVars.TraitorMaxTraitors);
|
||||||
@@ -61,46 +55,45 @@ public sealed class TraitorRuleSystem : GameRuleSystem<TraitorRuleComponent>
|
|||||||
SubscribeLocalEvent<TraitorRuleComponent, ObjectivesTextPrependEvent>(OnObjectivesTextPrepend);
|
SubscribeLocalEvent<TraitorRuleComponent, ObjectivesTextPrependEvent>(OnObjectivesTextPrepend);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
//Set min players on game rule
|
||||||
|
protected override void Added(EntityUid uid, TraitorRuleComponent component, GameRuleComponent gameRule, GameRuleAddedEvent args)
|
||||||
|
{
|
||||||
|
base.Added(uid, component, gameRule, args);
|
||||||
|
|
||||||
|
gameRule.MinPlayers = _cfg.GetCVar(CCVars.TraitorMinPlayers);
|
||||||
|
}
|
||||||
|
|
||||||
|
protected override void Started(EntityUid uid, TraitorRuleComponent component, GameRuleComponent gameRule, GameRuleStartedEvent args)
|
||||||
|
{
|
||||||
|
base.Started(uid, component, gameRule, args);
|
||||||
|
MakeCodewords(component);
|
||||||
|
}
|
||||||
|
|
||||||
protected override void ActiveTick(EntityUid uid, TraitorRuleComponent component, GameRuleComponent gameRule, float frameTime)
|
protected override void ActiveTick(EntityUid uid, TraitorRuleComponent component, GameRuleComponent gameRule, float frameTime)
|
||||||
{
|
{
|
||||||
base.ActiveTick(uid, component, gameRule, frameTime);
|
base.ActiveTick(uid, component, gameRule, frameTime);
|
||||||
|
|
||||||
if (component.SelectionStatus == TraitorRuleComponent.SelectionState.ReadyToSelect && _gameTiming.CurTime > component.AnnounceAt)
|
if (component.SelectionStatus < TraitorRuleComponent.SelectionState.Started && component.AnnounceAt < _timing.CurTime)
|
||||||
|
{
|
||||||
DoTraitorStart(component);
|
DoTraitorStart(component);
|
||||||
|
component.SelectionStatus = TraitorRuleComponent.SelectionState.Started;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Check for enough players
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="ev"></param>
|
||||||
private void OnStartAttempt(RoundStartAttemptEvent ev)
|
private void OnStartAttempt(RoundStartAttemptEvent ev)
|
||||||
{
|
{
|
||||||
var query = EntityQueryEnumerator<TraitorRuleComponent, GameRuleComponent>();
|
TryRoundStartAttempt(ev, Loc.GetString("traitor-title"));
|
||||||
while (query.MoveNext(out var uid, out var traitor, out var gameRule))
|
|
||||||
{
|
|
||||||
if (!GameTicker.IsGameRuleAdded(uid, gameRule))
|
|
||||||
continue;
|
|
||||||
|
|
||||||
MakeCodewords(traitor);
|
|
||||||
|
|
||||||
var minPlayers = _cfg.GetCVar(CCVars.TraitorMinPlayers);
|
|
||||||
if (!ev.Forced && ev.Players.Length < minPlayers)
|
|
||||||
{
|
|
||||||
_chatManager.SendAdminAnnouncement(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(TraitorRuleComponent component)
|
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>(component.CodewordAdjectives).Values;
|
||||||
var verbs = _prototypeManager.Index<DatasetPrototype>("verbs").Values;
|
var verbs = _prototypeManager.Index<DatasetPrototype>(component.CodewordVerbs).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);
|
||||||
component.Codewords = new string[finalCodewordCount];
|
component.Codewords = new string[finalCodewordCount];
|
||||||
@@ -112,125 +105,99 @@ public sealed class TraitorRuleSystem : GameRuleSystem<TraitorRuleComponent>
|
|||||||
|
|
||||||
private void DoTraitorStart(TraitorRuleComponent component)
|
private void DoTraitorStart(TraitorRuleComponent component)
|
||||||
{
|
{
|
||||||
if (!component.StartCandidates.Any())
|
var eligiblePlayers = _antagSelection.GetEligiblePlayers(_playerManager.Sessions, component.TraitorPrototypeId);
|
||||||
{
|
|
||||||
Log.Error("Tried to start Traitor mode without any candidates.");
|
if (eligiblePlayers.Count == 0)
|
||||||
return;
|
return;
|
||||||
}
|
|
||||||
|
|
||||||
var numTraitors = MathHelper.Clamp(component.StartCandidates.Count / PlayersPerTraitor, 1, MaxTraitors);
|
var traitorsToSelect = _antagSelection.CalculateAntagCount(_playerManager.PlayerCount, PlayersPerTraitor, MaxTraitors);
|
||||||
var traitorPool = _antagSelection.FindPotentialAntags(component.StartCandidates, component.TraitorPrototypeId);
|
|
||||||
var selectedTraitors = _antagSelection.PickAntag(numTraitors, traitorPool);
|
|
||||||
|
|
||||||
foreach (var traitor in selectedTraitors)
|
var selectedTraitors = _antagSelection.ChooseAntags(traitorsToSelect, eligiblePlayers);
|
||||||
{
|
|
||||||
MakeTraitor(traitor);
|
|
||||||
}
|
|
||||||
|
|
||||||
component.SelectionStatus = TraitorRuleComponent.SelectionState.SelectionMade;
|
MakeTraitor(selectedTraitors, component);
|
||||||
}
|
}
|
||||||
|
|
||||||
private void OnPlayersSpawned(RulePlayerJobsAssignedEvent ev)
|
private void OnPlayersSpawned(RulePlayerJobsAssignedEvent ev)
|
||||||
{
|
{
|
||||||
var query = EntityQueryEnumerator<TraitorRuleComponent, GameRuleComponent>();
|
//Start the timer
|
||||||
while (query.MoveNext(out var uid, out var traitor, out var gameRule))
|
var query = QueryActiveRules();
|
||||||
|
while (query.MoveNext(out _, out var comp, out var gameRuleComponent))
|
||||||
{
|
{
|
||||||
if (!GameTicker.IsGameRuleAdded(uid, gameRule))
|
|
||||||
continue;
|
|
||||||
foreach (var player in ev.Players)
|
|
||||||
{
|
|
||||||
if (!ev.Profiles.ContainsKey(player.UserId))
|
|
||||||
continue;
|
|
||||||
|
|
||||||
traitor.StartCandidates[player] = ev.Profiles[player.UserId];
|
|
||||||
}
|
|
||||||
|
|
||||||
var delay = TimeSpan.FromSeconds(
|
var delay = TimeSpan.FromSeconds(
|
||||||
_cfg.GetCVar(CCVars.TraitorStartDelay) +
|
_cfg.GetCVar(CCVars.TraitorStartDelay) +
|
||||||
_random.NextFloat(0f, _cfg.GetCVar(CCVars.TraitorStartDelayVariance)));
|
_random.NextFloat(0f, _cfg.GetCVar(CCVars.TraitorStartDelayVariance)));
|
||||||
|
|
||||||
traitor.AnnounceAt = _gameTiming.CurTime + delay;
|
//Set the delay for choosing traitors
|
||||||
|
comp.AnnounceAt = _timing.CurTime + delay;
|
||||||
|
|
||||||
traitor.SelectionStatus = TraitorRuleComponent.SelectionState.ReadyToSelect;
|
comp.SelectionStatus = TraitorRuleComponent.SelectionState.ReadyToStart;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public bool MakeTraitor(ICommonSession traitor, bool giveUplink = true, bool giveObjectives = true)
|
public bool MakeTraitor(List<EntityUid> traitors, TraitorRuleComponent component, bool giveUplink = true, bool giveObjectives = true)
|
||||||
{
|
{
|
||||||
var traitorRule = EntityQuery<TraitorRuleComponent>().FirstOrDefault();
|
foreach (var traitor in traitors)
|
||||||
if (traitorRule == null)
|
|
||||||
{
|
{
|
||||||
//todo fuck me this shit is awful
|
MakeTraitor(traitor, component, giveUplink, giveObjectives);
|
||||||
//no i wont fuck you, erp is against rules
|
|
||||||
GameTicker.StartGameRule("Traitor", out var ruleEntity);
|
|
||||||
traitorRule = Comp<TraitorRuleComponent>(ruleEntity);
|
|
||||||
MakeCodewords(traitorRule);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
public bool MakeTraitor(EntityUid traitor, TraitorRuleComponent component, bool giveUplink = true, bool giveObjectives = true)
|
||||||
|
{
|
||||||
|
//Grab the mind if it wasnt provided
|
||||||
if (!_mindSystem.TryGetMind(traitor, out var mindId, out var mind))
|
if (!_mindSystem.TryGetMind(traitor, out var mindId, out var mind))
|
||||||
{
|
|
||||||
Log.Info("Failed getting mind for picked traitor.");
|
|
||||||
return false;
|
return false;
|
||||||
}
|
|
||||||
|
|
||||||
if (HasComp<TraitorRoleComponent>(mindId))
|
if (HasComp<TraitorRoleComponent>(mindId))
|
||||||
{
|
{
|
||||||
Log.Error($"Player {traitor.Name} is already a traitor.");
|
Log.Error($"Player {mind.CharacterName} is already a traitor.");
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (mind.OwnedEntity is not { } entity)
|
var briefing = Loc.GetString("traitor-role-codewords-short", ("codewords", string.Join(", ", component.Codewords)));
|
||||||
|
|
||||||
|
Note[]? code = null;
|
||||||
|
if (giveUplink)
|
||||||
{
|
{
|
||||||
Log.Error("Mind picked for traitor did not have an attached entity.");
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Calculate the amount of currency on the uplink.
|
// Calculate the amount of currency on the uplink.
|
||||||
var startingBalance = _cfg.GetCVar(CCVars.TraitorStartingBalance);
|
var startingBalance = _cfg.GetCVar(CCVars.TraitorStartingBalance);
|
||||||
if (_jobs.MindTryGetJob(mindId, out _, out var prototype))
|
if (_jobs.MindTryGetJob(mindId, out _, out var prototype))
|
||||||
startingBalance = Math.Max(startingBalance - prototype.AntagAdvantage, 0);
|
startingBalance = Math.Max(startingBalance - prototype.AntagAdvantage, 0);
|
||||||
|
|
||||||
// Give traitors their codewords and uplink code to keep in their character info menu
|
|
||||||
var briefing = Loc.GetString("traitor-role-codewords-short", ("codewords", string.Join(", ", traitorRule.Codewords)));
|
|
||||||
Note[]? code = null;
|
|
||||||
if (giveUplink)
|
|
||||||
{
|
|
||||||
// creadth: we need to create uplink for the antag.
|
// creadth: we need to create uplink for the antag.
|
||||||
// PDA should be in place already
|
// PDA should be in place already
|
||||||
var pda = _uplink.FindUplinkTarget(mind.OwnedEntity!.Value);
|
var pda = _uplink.FindUplinkTarget(traitor);
|
||||||
if (pda == null || !_uplink.AddUplink(mind.OwnedEntity.Value, startingBalance))
|
if (pda == null || !_uplink.AddUplink(traitor, startingBalance))
|
||||||
return false;
|
return false;
|
||||||
|
|
||||||
// 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
|
||||||
code = EnsureComp<RingerUplinkComponent>(pda.Value).Code;
|
code = EnsureComp<RingerUplinkComponent>(pda.Value).Code;
|
||||||
|
|
||||||
// If giveUplink is false the uplink code part is omitted
|
// If giveUplink is false the uplink code part is omitted
|
||||||
briefing = string.Format("{0}\n{1}", briefing,
|
briefing = string.Format("{0}\n{1}", briefing,
|
||||||
Loc.GetString("traitor-role-uplink-code-short", ("code", string.Join("-", code).Replace("sharp", "#"))));
|
Loc.GetString("traitor-role-uplink-code-short", ("code", string.Join("-", code).Replace("sharp", "#"))));
|
||||||
}
|
}
|
||||||
|
|
||||||
// Prepare traitor role
|
_antagSelection.SendBriefing(traitor, GenerateBriefing(component.Codewords, code), null, component.GreetSoundNotification);
|
||||||
var traitorRole = new TraitorRoleComponent
|
|
||||||
{
|
component.TraitorMinds.Add(mindId);
|
||||||
PrototypeId = traitorRule.TraitorPrototypeId,
|
|
||||||
};
|
|
||||||
|
|
||||||
// Assign traitor roles
|
// Assign traitor roles
|
||||||
_roleSystem.MindAddRole(mindId, new TraitorRoleComponent
|
_roleSystem.MindAddRole(mindId, new TraitorRoleComponent
|
||||||
{
|
{
|
||||||
PrototypeId = traitorRule.TraitorPrototypeId
|
PrototypeId = component.TraitorPrototypeId
|
||||||
}, mind);
|
}, mind, true);
|
||||||
// Assign briefing and greeting sound
|
// Assign briefing
|
||||||
_roleSystem.MindAddRole(mindId, new RoleBriefingComponent
|
_roleSystem.MindAddRole(mindId, new RoleBriefingComponent
|
||||||
{
|
{
|
||||||
Briefing = briefing
|
Briefing = briefing.ToString()
|
||||||
}, mind);
|
}, mind, true);
|
||||||
_roleSystem.MindPlaySound(mindId, traitorRule.GreetSoundNotification, mind);
|
|
||||||
SendTraitorBriefing(mindId, traitorRule.Codewords, code);
|
|
||||||
traitorRule.TraitorMinds.Add(mindId);
|
|
||||||
|
|
||||||
// Change the faction
|
// Change the faction
|
||||||
_npcFaction.RemoveFaction(entity, "NanoTrasen", false);
|
_npcFaction.RemoveFaction(traitor, component.NanoTrasenFaction, false);
|
||||||
_npcFaction.AddFaction(entity, "Syndicate");
|
_npcFaction.AddFaction(traitor, component.SyndicateFaction);
|
||||||
|
|
||||||
// Give traitors their objectives
|
// Give traitors their objectives
|
||||||
if (giveObjectives)
|
if (giveObjectives)
|
||||||
@@ -241,7 +208,7 @@ public sealed class TraitorRuleSystem : GameRuleSystem<TraitorRuleComponent>
|
|||||||
Log.Debug($"Attempting {maxPicks} objective picks with {maxDifficulty} difficulty");
|
Log.Debug($"Attempting {maxPicks} objective picks with {maxDifficulty} difficulty");
|
||||||
for (var pick = 0; pick < maxPicks && maxDifficulty > difficulty; pick++)
|
for (var pick = 0; pick < maxPicks && maxDifficulty > difficulty; pick++)
|
||||||
{
|
{
|
||||||
var objective = _objectives.GetRandomObjective(mindId, mind, "TraitorObjectiveGroups");
|
var objective = _objectives.GetRandomObjective(mindId, mind, component.ObjectiveGroup);
|
||||||
if (objective == null)
|
if (objective == null)
|
||||||
continue;
|
continue;
|
||||||
|
|
||||||
@@ -255,54 +222,26 @@ public sealed class TraitorRuleSystem : GameRuleSystem<TraitorRuleComponent>
|
|||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Send a codewords and uplink codes to traitor chat.
|
|
||||||
/// </summary>
|
|
||||||
/// <param name="mind">A mind (player)</param>
|
|
||||||
/// <param name="codewords">Codewords</param>
|
|
||||||
/// <param name="code">Uplink codes</param>
|
|
||||||
private void SendTraitorBriefing(EntityUid mind, string[] codewords, Note[]? code)
|
|
||||||
{
|
|
||||||
if (!_mindSystem.TryGetSession(mind, out var session))
|
|
||||||
return;
|
|
||||||
|
|
||||||
_chatManager.DispatchServerMessage(session, Loc.GetString("traitor-role-greeting"));
|
|
||||||
_chatManager.DispatchServerMessage(session, Loc.GetString("traitor-role-codewords", ("codewords", string.Join(", ", codewords))));
|
|
||||||
if (code != null)
|
|
||||||
_chatManager.DispatchServerMessage(session, Loc.GetString("traitor-role-uplink-code", ("code", string.Join("-", code).Replace("sharp","#"))));
|
|
||||||
}
|
|
||||||
|
|
||||||
private void HandleLatejoin(PlayerSpawnCompleteEvent ev)
|
private void HandleLatejoin(PlayerSpawnCompleteEvent ev)
|
||||||
{
|
{
|
||||||
var query = EntityQueryEnumerator<TraitorRuleComponent, GameRuleComponent>();
|
var query = QueryActiveRules();
|
||||||
while (query.MoveNext(out var uid, out var traitor, out var gameRule))
|
while (query.MoveNext(out _, out var comp, out _))
|
||||||
{
|
{
|
||||||
if (!GameTicker.IsGameRuleAdded(uid, gameRule))
|
if (comp.TotalTraitors >= MaxTraitors)
|
||||||
continue;
|
continue;
|
||||||
|
|
||||||
if (traitor.TotalTraitors >= MaxTraitors)
|
|
||||||
continue;
|
|
||||||
if (!ev.LateJoin)
|
if (!ev.LateJoin)
|
||||||
continue;
|
continue;
|
||||||
if (!ev.Profile.AntagPreferences.Contains(traitor.TraitorPrototypeId))
|
|
||||||
|
if (!_antagSelection.IsPlayerEligible(ev.Player, comp.TraitorPrototypeId))
|
||||||
continue;
|
continue;
|
||||||
|
|
||||||
if (ev.JobId == null || !_prototypeManager.TryIndex<JobPrototype>(ev.JobId, out var job))
|
//If its before we have selected traitors, continue
|
||||||
|
if (comp.SelectionStatus < TraitorRuleComponent.SelectionState.Started)
|
||||||
continue;
|
continue;
|
||||||
|
|
||||||
if (!job.CanBeAntag)
|
|
||||||
continue;
|
|
||||||
|
|
||||||
// Before the announcement is made, late-joiners are considered the same as players who readied.
|
|
||||||
if (traitor.SelectionStatus < TraitorRuleComponent.SelectionState.SelectionMade)
|
|
||||||
{
|
|
||||||
traitor.StartCandidates[ev.Player] = ev.Profile;
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
// the nth player we adjust our probabilities around
|
// the nth player we adjust our probabilities around
|
||||||
var target = PlayersPerTraitor * traitor.TotalTraitors + 1;
|
var target = PlayersPerTraitor * comp.TotalTraitors + 1;
|
||||||
|
|
||||||
var chance = 1f / PlayersPerTraitor;
|
var chance = 1f / PlayersPerTraitor;
|
||||||
|
|
||||||
// If we have too many traitors, divide by how many players below target for next traitor we are.
|
// If we have too many traitors, divide by how many players below target for next traitor we are.
|
||||||
@@ -322,7 +261,7 @@ public sealed class TraitorRuleSystem : GameRuleSystem<TraitorRuleComponent>
|
|||||||
// You get one shot.
|
// You get one shot.
|
||||||
if (_random.Prob(chance))
|
if (_random.Prob(chance))
|
||||||
{
|
{
|
||||||
MakeTraitor(ev.Player);
|
MakeTraitor(ev.Mob, comp);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -338,6 +277,38 @@ public sealed class TraitorRuleSystem : GameRuleSystem<TraitorRuleComponent>
|
|||||||
args.Text += "\n" + Loc.GetString("traitor-round-end-codewords", ("codewords", string.Join(", ", comp.Codewords)));
|
args.Text += "\n" + Loc.GetString("traitor-round-end-codewords", ("codewords", string.Join(", ", comp.Codewords)));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Start this game rule manually
|
||||||
|
/// </summary>
|
||||||
|
public TraitorRuleComponent StartGameRule()
|
||||||
|
{
|
||||||
|
var comp = EntityQuery<TraitorRuleComponent>().FirstOrDefault();
|
||||||
|
if (comp == null)
|
||||||
|
{
|
||||||
|
GameTicker.StartGameRule("Traitor", out var ruleEntity);
|
||||||
|
comp = Comp<TraitorRuleComponent>(ruleEntity);
|
||||||
|
}
|
||||||
|
|
||||||
|
return comp;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void MakeTraitorAdmin(EntityUid entity, bool giveUplink, bool giveObjectives)
|
||||||
|
{
|
||||||
|
var traitorRule = StartGameRule();
|
||||||
|
MakeTraitor(entity, traitorRule, giveUplink, giveObjectives);
|
||||||
|
}
|
||||||
|
|
||||||
|
private string GenerateBriefing(string[] codewords, Note[]? uplinkCode)
|
||||||
|
{
|
||||||
|
var sb = new StringBuilder();
|
||||||
|
sb.AppendLine(Loc.GetString("traitor-role-greeting"));
|
||||||
|
sb.AppendLine(Loc.GetString("traitor-role-codewords-short", ("codewords", string.Join(", ", codewords))));
|
||||||
|
if (uplinkCode != null)
|
||||||
|
sb.AppendLine(Loc.GetString("traitor-role-uplink-code-short", ("code", string.Join("-", uplinkCode).Replace("sharp", "#"))));
|
||||||
|
|
||||||
|
return sb.ToString();
|
||||||
|
}
|
||||||
|
|
||||||
public List<(EntityUid Id, MindComponent Mind)> GetOtherTraitorMindsAliveAndConnected(MindComponent ourMind)
|
public List<(EntityUid Id, MindComponent Mind)> GetOtherTraitorMindsAliveAndConnected(MindComponent ourMind)
|
||||||
{
|
{
|
||||||
List<(EntityUid Id, MindComponent Mind)> allTraitors = new();
|
List<(EntityUid Id, MindComponent Mind)> allTraitors = new();
|
||||||
|
|||||||
@@ -1,13 +1,9 @@
|
|||||||
using System.Globalization;
|
|
||||||
using System.Linq;
|
|
||||||
using Content.Server.Actions;
|
using Content.Server.Actions;
|
||||||
using Content.Server.Chat.Managers;
|
using Content.Server.Antag;
|
||||||
using Content.Server.Chat.Systems;
|
using Content.Server.Chat.Systems;
|
||||||
using Content.Server.GameTicking.Rules.Components;
|
using Content.Server.GameTicking.Rules.Components;
|
||||||
using Content.Server.Popups;
|
using Content.Server.Popups;
|
||||||
using Content.Server.Preferences.Managers;
|
|
||||||
using Content.Server.Roles;
|
using Content.Server.Roles;
|
||||||
using Content.Server.Roles.Jobs;
|
|
||||||
using Content.Server.RoundEnd;
|
using Content.Server.RoundEnd;
|
||||||
using Content.Server.Station.Components;
|
using Content.Server.Station.Components;
|
||||||
using Content.Server.Station.Systems;
|
using Content.Server.Station.Systems;
|
||||||
@@ -18,17 +14,14 @@ using Content.Shared.Mind;
|
|||||||
using Content.Shared.Mobs;
|
using Content.Shared.Mobs;
|
||||||
using Content.Shared.Mobs.Components;
|
using Content.Shared.Mobs.Components;
|
||||||
using Content.Shared.Mobs.Systems;
|
using Content.Shared.Mobs.Systems;
|
||||||
using Content.Shared.Preferences;
|
|
||||||
using Content.Shared.Roles;
|
using Content.Shared.Roles;
|
||||||
using Content.Shared.Zombies;
|
using Content.Shared.Zombies;
|
||||||
using Robust.Server.GameObjects;
|
|
||||||
using Robust.Server.Player;
|
using Robust.Server.Player;
|
||||||
using Robust.Shared.Audio;
|
|
||||||
using Robust.Shared.Audio.Systems;
|
|
||||||
using Robust.Shared.Configuration;
|
using Robust.Shared.Configuration;
|
||||||
using Robust.Shared.Player;
|
using Robust.Shared.Player;
|
||||||
using Robust.Shared.Random;
|
using Robust.Shared.Random;
|
||||||
using Robust.Shared.Timing;
|
using Robust.Shared.Timing;
|
||||||
|
using System.Globalization;
|
||||||
|
|
||||||
namespace Content.Server.GameTicking.Rules;
|
namespace Content.Server.GameTicking.Rules;
|
||||||
|
|
||||||
@@ -36,10 +29,7 @@ public sealed class ZombieRuleSystem : GameRuleSystem<ZombieRuleComponent>
|
|||||||
{
|
{
|
||||||
[Dependency] private readonly IRobustRandom _random = default!;
|
[Dependency] private readonly IRobustRandom _random = default!;
|
||||||
[Dependency] private readonly IConfigurationManager _cfg = default!;
|
[Dependency] private readonly IConfigurationManager _cfg = default!;
|
||||||
[Dependency] private readonly IChatManager _chatManager = default!;
|
|
||||||
[Dependency] private readonly IGameTiming _timing = default!;
|
|
||||||
[Dependency] private readonly IPlayerManager _playerManager = default!;
|
[Dependency] private readonly IPlayerManager _playerManager = default!;
|
||||||
[Dependency] private readonly IServerPreferencesManager _prefs = default!;
|
|
||||||
[Dependency] private readonly ChatSystem _chat = default!;
|
[Dependency] private readonly ChatSystem _chat = default!;
|
||||||
[Dependency] private readonly RoundEndSystem _roundEnd = default!;
|
[Dependency] private readonly RoundEndSystem _roundEnd = default!;
|
||||||
[Dependency] private readonly PopupSystem _popup = default!;
|
[Dependency] private readonly PopupSystem _popup = default!;
|
||||||
@@ -49,8 +39,8 @@ public sealed class ZombieRuleSystem : GameRuleSystem<ZombieRuleComponent>
|
|||||||
[Dependency] private readonly SharedMindSystem _mindSystem = default!;
|
[Dependency] private readonly SharedMindSystem _mindSystem = default!;
|
||||||
[Dependency] private readonly SharedRoleSystem _roles = default!;
|
[Dependency] private readonly SharedRoleSystem _roles = default!;
|
||||||
[Dependency] private readonly StationSystem _station = default!;
|
[Dependency] private readonly StationSystem _station = default!;
|
||||||
[Dependency] private readonly SharedAudioSystem _audio = default!;
|
[Dependency] private readonly AntagSelectionSystem _antagSelection = default!;
|
||||||
[Dependency] private readonly JobSystem _jobs = default!;
|
[Dependency] private readonly IGameTiming _timing = default!;
|
||||||
|
|
||||||
public override void Initialize()
|
public override void Initialize()
|
||||||
{
|
{
|
||||||
@@ -61,6 +51,16 @@ public sealed class ZombieRuleSystem : GameRuleSystem<ZombieRuleComponent>
|
|||||||
SubscribeLocalEvent<PendingZombieComponent, ZombifySelfActionEvent>(OnZombifySelf);
|
SubscribeLocalEvent<PendingZombieComponent, ZombifySelfActionEvent>(OnZombifySelf);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Set the required minimum players for this gamemode to start
|
||||||
|
/// </summary>
|
||||||
|
protected override void Added(EntityUid uid, ZombieRuleComponent component, GameRuleComponent gameRule, GameRuleAddedEvent args)
|
||||||
|
{
|
||||||
|
base.Added(uid, component, gameRule, args);
|
||||||
|
|
||||||
|
gameRule.MinPlayers = _cfg.GetCVar(CCVars.ZombieMinPlayers);
|
||||||
|
}
|
||||||
|
|
||||||
private void OnRoundEndText(RoundEndTextAppendEvent ev)
|
private void OnRoundEndText(RoundEndTextAppendEvent ev)
|
||||||
{
|
{
|
||||||
foreach (var zombie in EntityQuery<ZombieRuleComponent>())
|
foreach (var zombie in EntityQuery<ZombieRuleComponent>())
|
||||||
@@ -113,21 +113,14 @@ public sealed class ZombieRuleSystem : GameRuleSystem<ZombieRuleComponent>
|
|||||||
/// <summary>
|
/// <summary>
|
||||||
/// The big kahoona function for checking if the round is gonna end
|
/// The big kahoona function for checking if the round is gonna end
|
||||||
/// </summary>
|
/// </summary>
|
||||||
private void CheckRoundEnd()
|
private void CheckRoundEnd(ZombieRuleComponent zombieRuleComponent)
|
||||||
{
|
{
|
||||||
var query = EntityQueryEnumerator<ZombieRuleComponent, GameRuleComponent>();
|
|
||||||
while (query.MoveNext(out var uid, out var comp, out var gameRule))
|
|
||||||
{
|
|
||||||
if (!GameTicker.IsGameRuleActive(uid, gameRule))
|
|
||||||
continue;
|
|
||||||
|
|
||||||
var healthy = GetHealthyHumans();
|
var healthy = GetHealthyHumans();
|
||||||
if (healthy.Count == 1) // Only one human left. spooky
|
if (healthy.Count == 1) // Only one human left. spooky
|
||||||
_popup.PopupEntity(Loc.GetString("zombie-alone"), healthy[0], healthy[0]);
|
_popup.PopupEntity(Loc.GetString("zombie-alone"), healthy[0], healthy[0]);
|
||||||
|
|
||||||
if (!comp.ShuttleCalled && GetInfectedFraction(false) >= comp.ZombieShuttleCallPercentage)
|
if (GetInfectedFraction(false) > zombieRuleComponent.ZombieShuttleCallPercentage && !_roundEnd.IsRoundEndRequested())
|
||||||
{
|
{
|
||||||
comp.ShuttleCalled = true;
|
|
||||||
foreach (var station in _station.GetStations())
|
foreach (var station in _station.GetStations())
|
||||||
{
|
{
|
||||||
_chat.DispatchStationAnnouncement(station, Loc.GetString("zombie-shuttle-call"), colorOverride: Color.Crimson);
|
_chat.DispatchStationAnnouncement(station, Loc.GetString("zombie-shuttle-call"), colorOverride: Color.Crimson);
|
||||||
@@ -140,58 +133,39 @@ public sealed class ZombieRuleSystem : GameRuleSystem<ZombieRuleComponent>
|
|||||||
if (GetInfectedFraction() >= 1) // Oops, all zombies
|
if (GetInfectedFraction() >= 1) // Oops, all zombies
|
||||||
_roundEnd.EndRound();
|
_roundEnd.EndRound();
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Check we have enough players to start this game mode, if not - cancel and announce
|
||||||
|
/// </summary>
|
||||||
private void OnStartAttempt(RoundStartAttemptEvent ev)
|
private void OnStartAttempt(RoundStartAttemptEvent ev)
|
||||||
{
|
{
|
||||||
var query = EntityQueryEnumerator<ZombieRuleComponent, GameRuleComponent>();
|
TryRoundStartAttempt(ev, Loc.GetString("zombie-title"));
|
||||||
while (query.MoveNext(out var uid, out _, out var gameRule))
|
|
||||||
{
|
|
||||||
if (!GameTicker.IsGameRuleAdded(uid, gameRule))
|
|
||||||
continue;
|
|
||||||
|
|
||||||
var minPlayers = _cfg.GetCVar(CCVars.ZombieMinPlayers);
|
|
||||||
if (!ev.Forced && ev.Players.Length < minPlayers)
|
|
||||||
{
|
|
||||||
_chatManager.SendAdminAnnouncement(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();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
protected override void Started(EntityUid uid, ZombieRuleComponent component, GameRuleComponent gameRule, GameRuleStartedEvent args)
|
protected override void Started(EntityUid uid, ZombieRuleComponent component, GameRuleComponent gameRule, GameRuleStartedEvent args)
|
||||||
{
|
{
|
||||||
base.Started(uid, component, gameRule, args);
|
base.Started(uid, component, gameRule, args);
|
||||||
component.StartTime = _timing.CurTime + _random.Next(component.MinStartDelay, component.MaxStartDelay);
|
|
||||||
|
var delay = _random.Next(component.MinStartDelay, component.MaxStartDelay);
|
||||||
|
component.StartTime = _timing.CurTime + delay;
|
||||||
}
|
}
|
||||||
|
|
||||||
protected override void ActiveTick(EntityUid uid, ZombieRuleComponent component, GameRuleComponent gameRule, float frameTime)
|
protected override void ActiveTick(EntityUid uid, ZombieRuleComponent component, GameRuleComponent gameRule, float frameTime)
|
||||||
{
|
{
|
||||||
base.ActiveTick(uid, component, gameRule, frameTime);
|
base.ActiveTick(uid, component, gameRule, frameTime);
|
||||||
|
|
||||||
if (component.InfectedChosen)
|
if (component.StartTime.HasValue && component.StartTime < _timing.CurTime)
|
||||||
{
|
{
|
||||||
if (_timing.CurTime >= component.NextRoundEndCheck)
|
|
||||||
{
|
|
||||||
component.NextRoundEndCheck += component.EndCheckDelay;
|
|
||||||
CheckRoundEnd();
|
|
||||||
}
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (component.StartTime == null || _timing.CurTime < component.StartTime)
|
|
||||||
return;
|
|
||||||
|
|
||||||
InfectInitialPlayers(component);
|
InfectInitialPlayers(component);
|
||||||
|
component.StartTime = null;
|
||||||
|
component.NextRoundEndCheck = _timing.CurTime + component.EndCheckDelay;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (component.NextRoundEndCheck.HasValue && component.NextRoundEndCheck < _timing.CurTime)
|
||||||
|
{
|
||||||
|
CheckRoundEnd(component);
|
||||||
|
component.NextRoundEndCheck = _timing.CurTime + component.EndCheckDelay;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private void OnZombifySelf(EntityUid uid, PendingZombieComponent component, ZombifySelfActionEvent args)
|
private void OnZombifySelf(EntityUid uid, PendingZombieComponent component, ZombifySelfActionEvent args)
|
||||||
@@ -201,6 +175,12 @@ public sealed class ZombieRuleSystem : GameRuleSystem<ZombieRuleComponent>
|
|||||||
Del(component.Action.Value);
|
Del(component.Action.Value);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Get the fraction of players that are infected, between 0 and 1
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="includeOffStation">Include healthy players that are not on the station grid</param>
|
||||||
|
/// <param name="includeDead">Should dead zombies be included in the count</param>
|
||||||
|
/// <returns></returns>
|
||||||
private float GetInfectedFraction(bool includeOffStation = true, bool includeDead = false)
|
private float GetInfectedFraction(bool includeOffStation = true, bool includeDead = false)
|
||||||
{
|
{
|
||||||
var players = GetHealthyHumans(includeOffStation);
|
var players = GetHealthyHumans(includeOffStation);
|
||||||
@@ -264,87 +244,55 @@ public sealed class ZombieRuleSystem : GameRuleSystem<ZombieRuleComponent>
|
|||||||
/// </remarks>
|
/// </remarks>
|
||||||
private void InfectInitialPlayers(ZombieRuleComponent component)
|
private void InfectInitialPlayers(ZombieRuleComponent component)
|
||||||
{
|
{
|
||||||
if (component.InfectedChosen)
|
//Get all players with initial infected enabled, and exclude those with the ZombieImmuneComponent
|
||||||
return;
|
var eligiblePlayers = _antagSelection.GetEligiblePlayers(_playerManager.Sessions, component.PatientZeroPrototypeId, includeAllJobs: true, customExcludeCondition: x => HasComp<ZombieImmuneComponent>(x) || HasComp<InitialInfectedExemptComponent>(x));
|
||||||
component.InfectedChosen = true;
|
//And get all players, excluding ZombieImmune - to fill any leftover initial infected slots
|
||||||
|
var allPlayers = _antagSelection.GetEligiblePlayers(_playerManager.Sessions, component.PatientZeroPrototypeId, acceptableAntags: Shared.Antag.AntagAcceptability.All, includeAllJobs: true, ignorePreferences: true, customExcludeCondition: HasComp<ZombieImmuneComponent>);
|
||||||
|
|
||||||
var allPlayers = _playerManager.Sessions.ToList();
|
//If there are no players to choose, abort
|
||||||
var playerList = new List<ICommonSession>();
|
if (allPlayers.Count == 0)
|
||||||
var prefList = new List<ICommonSession>();
|
|
||||||
foreach (var player in allPlayers)
|
|
||||||
{
|
|
||||||
if (player.AttachedEntity == null || !HasComp<HumanoidAppearanceComponent>(player.AttachedEntity) ||
|
|
||||||
HasComp<ZombieImmuneComponent>(player.AttachedEntity) || !_jobs.CanBeAntag(player))
|
|
||||||
continue;
|
|
||||||
|
|
||||||
if (HasComp<InitialInfectedExemptComponent>(player.AttachedEntity))
|
|
||||||
continue; // used (for example) on ERT
|
|
||||||
|
|
||||||
playerList.Add(player);
|
|
||||||
|
|
||||||
var pref = (HumanoidCharacterProfile) _prefs.GetPreferences(player.UserId).SelectedCharacter;
|
|
||||||
if (pref.AntagPreferences.Contains(component.PatientZeroPrototypeId))
|
|
||||||
prefList.Add(player);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (playerList.Count == 0)
|
|
||||||
return;
|
return;
|
||||||
|
|
||||||
var numInfected = Math.Max(1,
|
//How many initial infected should we select
|
||||||
(int) Math.Min(
|
var initialInfectedCount = _antagSelection.CalculateAntagCount(_playerManager.PlayerCount, component.PlayersPerInfected, component.MaxInitialInfected);
|
||||||
Math.Floor((double) playerList.Count / component.PlayersPerInfected), component.MaxInitialInfected));
|
|
||||||
|
|
||||||
var totalInfected = 0;
|
//Choose the required number of initial infected from the eligible players, making up any shortfall by choosing from all players
|
||||||
while (totalInfected < numInfected)
|
var initialInfected = _antagSelection.ChooseAntags(initialInfectedCount, eligiblePlayers, allPlayers);
|
||||||
{
|
|
||||||
ICommonSession zombie;
|
//Make brain craving
|
||||||
if (prefList.Count == 0)
|
MakeZombie(initialInfected, component);
|
||||||
{
|
|
||||||
if (playerList.Count == 0)
|
//Send the briefing, play greeting sound
|
||||||
{
|
_antagSelection.SendBriefing(initialInfected, Loc.GetString("zombie-patientzero-role-greeting"), Color.Plum, component.InitialInfectedSound);
|
||||||
Log.Info("Insufficient number of players. stopping selection.");
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
zombie = _random.Pick(playerList);
|
|
||||||
Log.Info("Insufficient preferred patient 0, picking at random.");
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
zombie = _random.Pick(prefList);
|
|
||||||
Log.Info("Selected a patient 0.");
|
|
||||||
}
|
}
|
||||||
|
|
||||||
prefList.Remove(zombie);
|
private void MakeZombie(List<EntityUid> entities, ZombieRuleComponent component)
|
||||||
playerList.Remove(zombie);
|
|
||||||
if (!_mindSystem.TryGetMind(zombie, out var mindId, out var mind) ||
|
|
||||||
mind.OwnedEntity is not { } ownedEntity)
|
|
||||||
{
|
{
|
||||||
continue;
|
foreach (var entity in entities)
|
||||||
|
{
|
||||||
|
MakeZombie(entity, component);
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
private void MakeZombie(EntityUid entity, ZombieRuleComponent component)
|
||||||
|
{
|
||||||
|
if (!_mindSystem.TryGetMind(entity, out var mind, out var mindComponent))
|
||||||
|
return;
|
||||||
|
|
||||||
totalInfected++;
|
//Add the role to the mind silently (to avoid repeating job assignment)
|
||||||
|
_roles.MindAddRole(mind, new InitialInfectedRoleComponent { PrototypeId = component.PatientZeroPrototypeId }, silent: true);
|
||||||
|
|
||||||
_roles.MindAddRole(mindId, new InitialInfectedRoleComponent { PrototypeId = component.PatientZeroPrototypeId });
|
//Add the zombie components and grace period
|
||||||
|
var pending = EnsureComp<PendingZombieComponent>(entity);
|
||||||
var pending = EnsureComp<PendingZombieComponent>(ownedEntity);
|
|
||||||
pending.GracePeriod = _random.Next(component.MinInitialInfectedGrace, component.MaxInitialInfectedGrace);
|
pending.GracePeriod = _random.Next(component.MinInitialInfectedGrace, component.MaxInitialInfectedGrace);
|
||||||
EnsureComp<ZombifyOnDeathComponent>(ownedEntity);
|
EnsureComp<ZombifyOnDeathComponent>(entity);
|
||||||
EnsureComp<IncurableZombieComponent>(ownedEntity);
|
EnsureComp<IncurableZombieComponent>(entity);
|
||||||
var inCharacterName = MetaData(ownedEntity).EntityName;
|
|
||||||
_action.AddAction(ownedEntity, ref pending.Action, ZombieRuleComponent.ZombifySelfActionPrototype, ownedEntity);
|
|
||||||
|
|
||||||
var message = Loc.GetString("zombie-patientzero-role-greeting");
|
//Add the zombify action
|
||||||
var wrappedMessage = Loc.GetString("chat-manager-server-wrap-message", ("message", message));
|
_action.AddAction(entity, ref pending.Action, component.ZombifySelfActionPrototype, entity);
|
||||||
|
|
||||||
//gets the names now in case the players leave.
|
//Get names for the round end screen, incase they leave mid-round
|
||||||
//this gets unhappy if people with the same name get chosen. Probably shouldn't happen.
|
var inCharacterName = MetaData(entity).EntityName;
|
||||||
component.InitialInfectedNames.Add(inCharacterName, zombie.Name);
|
var accountName = mindComponent.Session == null ? string.Empty : mindComponent.Session.Name;
|
||||||
|
component.InitialInfectedNames.Add(inCharacterName, accountName);
|
||||||
// I went all the way to ChatManager.cs and all i got was this lousy T-shirt
|
|
||||||
// You got a free T-shirt!?!?
|
|
||||||
_chatManager.ChatMessageToOne(Shared.Chat.ChatChannel.Server, message,
|
|
||||||
wrappedMessage, default, false, zombie.Channel, Color.Plum);
|
|
||||||
_audio.PlayGlobal(component.InitialInfectedSound, ownedEntity);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,11 +1,15 @@
|
|||||||
using Robust.Shared.Audio;
|
using Content.Server.GameTicking.Rules;
|
||||||
|
using Content.Shared.NukeOps;
|
||||||
|
using Robust.Shared.Audio;
|
||||||
|
using Robust.Shared.Serialization.TypeSerializers.Implementations.Custom;
|
||||||
|
|
||||||
namespace Content.Server.NukeOps;
|
namespace Content.Server.NukeOps;
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Used with NukeOps game rule to send war declaration announcement
|
/// Used with NukeOps game rule to send war declaration announcement
|
||||||
/// </summary>
|
/// </summary>
|
||||||
[RegisterComponent]
|
[RegisterComponent, AutoGenerateComponentPause]
|
||||||
|
[Access(typeof(WarDeclaratorSystem), typeof(NukeopsRuleSystem))]
|
||||||
public sealed partial class WarDeclaratorComponent : Component
|
public sealed partial class WarDeclaratorComponent : Component
|
||||||
{
|
{
|
||||||
/// <summary>
|
/// <summary>
|
||||||
@@ -23,22 +27,43 @@ public sealed partial class WarDeclaratorComponent : Component
|
|||||||
public bool AllowEditingMessage = true;
|
public bool AllowEditingMessage = true;
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// War declarement text color
|
/// War declaration text color
|
||||||
/// </summary>
|
/// </summary>
|
||||||
[ViewVariables(VVAccess.ReadWrite)]
|
[ViewVariables(VVAccess.ReadWrite)]
|
||||||
[DataField]
|
[DataField]
|
||||||
public Color Color = Color.Red;
|
public Color Color = Color.Red;
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// War declarement sound file path
|
/// War declaration sound file path
|
||||||
/// </summary>
|
/// </summary>
|
||||||
[DataField]
|
[DataField]
|
||||||
public SoundSpecifier Sound = new SoundPathSpecifier("/Audio/Announcements/war.ogg");
|
public SoundSpecifier Sound = new SoundPathSpecifier("/Audio/Announcements/war.ogg");
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Fluent ID for the declarement title
|
/// Fluent ID for the declaration sender title
|
||||||
/// </summary>
|
/// </summary>
|
||||||
[ViewVariables(VVAccess.ReadWrite)]
|
[ViewVariables(VVAccess.ReadWrite)]
|
||||||
[DataField]
|
[DataField]
|
||||||
public LocId Title = "comms-console-announcement-title-nukie";
|
public LocId SenderTitle = "comms-console-announcement-title-nukie";
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Time allowed for declaration of war
|
||||||
|
/// </summary>
|
||||||
|
[DataField]
|
||||||
|
public float WarDeclarationDelay = 6.0f;
|
||||||
|
|
||||||
|
[DataField(customTypeSerializer: typeof(TimeOffsetSerializer)), AutoPausedField]
|
||||||
|
public TimeSpan DisableAt;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// How long the shuttle will be disabled for
|
||||||
|
/// </summary>
|
||||||
|
[DataField(customTypeSerializer: typeof(TimeOffsetSerializer)), AutoPausedField]
|
||||||
|
public TimeSpan ShuttleDisabledTime;
|
||||||
|
|
||||||
|
[DataField]
|
||||||
|
public WarConditionStatus? CurrentStatus;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
[ByRefEvent]
|
||||||
|
public record struct WarDeclaredEvent(WarConditionStatus? Status, Entity<WarDeclaratorComponent> DeclaratorEntity);
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
using Content.Server.Administration.Logs;
|
using Content.Server.Administration.Logs;
|
||||||
using Content.Server.GameTicking.Rules;
|
using Content.Server.Chat.Systems;
|
||||||
using Content.Server.GameTicking.Rules.Components;
|
|
||||||
using Content.Server.Popups;
|
using Content.Server.Popups;
|
||||||
|
using Content.Shared.Access.Systems;
|
||||||
using Content.Shared.CCVar;
|
using Content.Shared.CCVar;
|
||||||
using Content.Shared.Chat;
|
using Content.Shared.Chat;
|
||||||
using Content.Shared.Database;
|
using Content.Shared.Database;
|
||||||
@@ -9,6 +9,7 @@ using Content.Shared.NukeOps;
|
|||||||
using Content.Shared.UserInterface;
|
using Content.Shared.UserInterface;
|
||||||
using Robust.Server.GameObjects;
|
using Robust.Server.GameObjects;
|
||||||
using Robust.Shared.Configuration;
|
using Robust.Shared.Configuration;
|
||||||
|
using Robust.Shared.Timing;
|
||||||
|
|
||||||
namespace Content.Server.NukeOps;
|
namespace Content.Server.NukeOps;
|
||||||
|
|
||||||
@@ -17,101 +18,74 @@ namespace Content.Server.NukeOps;
|
|||||||
/// </summary>
|
/// </summary>
|
||||||
public sealed class WarDeclaratorSystem : EntitySystem
|
public sealed class WarDeclaratorSystem : EntitySystem
|
||||||
{
|
{
|
||||||
[Dependency] private readonly UserInterfaceSystem _userInterfaceSystem = default!;
|
|
||||||
[Dependency] private readonly IAdminLogManager _adminLogger = default!;
|
[Dependency] private readonly IAdminLogManager _adminLogger = default!;
|
||||||
[Dependency] private readonly NukeopsRuleSystem _nukeopsRuleSystem = default!;
|
|
||||||
[Dependency] private readonly PopupSystem _popupSystem = default!;
|
|
||||||
[Dependency] private readonly IConfigurationManager _cfg = default!;
|
[Dependency] private readonly IConfigurationManager _cfg = default!;
|
||||||
|
[Dependency] private readonly IGameTiming _gameTiming = default!;
|
||||||
|
[Dependency] private readonly UserInterfaceSystem _userInterfaceSystem = default!;
|
||||||
|
[Dependency] private readonly ChatSystem _chat = default!;
|
||||||
|
[Dependency] private readonly PopupSystem _popupSystem = default!;
|
||||||
|
[Dependency] private readonly AccessReaderSystem _accessReaderSystem = default!;
|
||||||
|
|
||||||
public override void Initialize()
|
public override void Initialize()
|
||||||
{
|
{
|
||||||
base.Initialize();
|
SubscribeLocalEvent<WarDeclaratorComponent, MapInitEvent>(OnMapInit);
|
||||||
SubscribeLocalEvent<WarDeclaratorComponent, WarDeclaratorActivateMessage>(OnActivated);
|
|
||||||
SubscribeLocalEvent<WarDeclaratorComponent, ActivatableUIOpenAttemptEvent>(OnAttemptOpenUI);
|
SubscribeLocalEvent<WarDeclaratorComponent, ActivatableUIOpenAttemptEvent>(OnAttemptOpenUI);
|
||||||
|
SubscribeLocalEvent<WarDeclaratorComponent, WarDeclaratorActivateMessage>(OnActivated);
|
||||||
}
|
}
|
||||||
|
|
||||||
private void OnAttemptOpenUI(EntityUid uid, WarDeclaratorComponent component, ActivatableUIOpenAttemptEvent args)
|
private void OnMapInit(Entity<WarDeclaratorComponent> ent, ref MapInitEvent args)
|
||||||
{
|
{
|
||||||
if (!_nukeopsRuleSystem.TryGetRuleFromOperative(args.User, out var comps))
|
ent.Comp.Message = Loc.GetString("war-declarator-default-message");
|
||||||
|
ent.Comp.DisableAt = _gameTiming.CurTime + TimeSpan.FromMinutes(ent.Comp.WarDeclarationDelay);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void OnAttemptOpenUI(Entity<WarDeclaratorComponent> ent, ref ActivatableUIOpenAttemptEvent args)
|
||||||
{
|
{
|
||||||
var msg = Loc.GetString("war-declarator-not-nukeops");
|
if (!_accessReaderSystem.IsAllowed(args.User, ent))
|
||||||
_popupSystem.PopupEntity(msg, uid);
|
{
|
||||||
|
var msg = Loc.GetString("war-declarator-not-working");
|
||||||
|
_popupSystem.PopupEntity(msg, ent);
|
||||||
args.Cancel();
|
args.Cancel();
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
UpdateUI(uid, comps.Value.Item1, comps.Value.Item2);
|
UpdateUI(ent, ent.Comp.CurrentStatus);
|
||||||
}
|
}
|
||||||
|
|
||||||
private void OnActivated(EntityUid uid, WarDeclaratorComponent component, WarDeclaratorActivateMessage args)
|
private void OnActivated(Entity<WarDeclaratorComponent> ent, ref WarDeclaratorActivateMessage args)
|
||||||
{
|
{
|
||||||
if (!args.Session.AttachedEntity.HasValue ||
|
if (args.Session.AttachedEntity is not {} playerEntity)
|
||||||
!_nukeopsRuleSystem.TryGetRuleFromOperative(args.Session.AttachedEntity.Value, out var comps))
|
|
||||||
return;
|
return;
|
||||||
|
|
||||||
var condition = _nukeopsRuleSystem.GetWarCondition(comps.Value.Item1, comps.Value.Item2);
|
var ev = new WarDeclaredEvent(ent.Comp.CurrentStatus, ent);
|
||||||
if (condition != WarConditionStatus.YES_WAR)
|
RaiseLocalEvent(ref ev);
|
||||||
{
|
|
||||||
UpdateUI(uid, comps.Value.Item1, comps.Value.Item2);
|
if (ent.Comp.DisableAt < _gameTiming.CurTime)
|
||||||
return;
|
ev.Status = WarConditionStatus.NoWarTimeout;
|
||||||
}
|
|
||||||
|
ent.Comp.CurrentStatus = ev.Status;
|
||||||
|
|
||||||
var maxLength = _cfg.GetCVar(CCVars.ChatMaxAnnouncementLength);
|
var maxLength = _cfg.GetCVar(CCVars.ChatMaxAnnouncementLength);
|
||||||
var message = SharedChatSystem.SanitizeAnnouncement(args.Message, maxLength);
|
var message = SharedChatSystem.SanitizeAnnouncement(args.Message, maxLength);
|
||||||
if (component.AllowEditingMessage && message != string.Empty)
|
if (ent.Comp.AllowEditingMessage && message != string.Empty)
|
||||||
{
|
ent.Comp.Message = message;
|
||||||
component.Message = message;
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
message = Loc.GetString("war-declarator-default-message");
|
|
||||||
}
|
|
||||||
var title = Loc.GetString(component.Title);
|
|
||||||
|
|
||||||
_nukeopsRuleSystem.DeclareWar(args.Session.AttachedEntity.Value, message, title, component.Sound, component.Color);
|
if (ev.Status == WarConditionStatus.WarReady)
|
||||||
|
{
|
||||||
if (args.Session.AttachedEntity != null)
|
var title = Loc.GetString(ent.Comp.SenderTitle);
|
||||||
_adminLogger.Add(LogType.Chat, LogImpact.Low, $"{ToPrettyString(args.Session.AttachedEntity.Value):player} has declared war with this text: {message}");
|
_chat.DispatchGlobalAnnouncement(ent.Comp.Message, title, true, ent.Comp.Sound, ent.Comp.Color);
|
||||||
|
_adminLogger.Add(LogType.Chat, LogImpact.Low, $"{ToPrettyString(playerEntity):player} has declared war with this text: {ent.Comp.Message}");
|
||||||
}
|
}
|
||||||
|
|
||||||
public void RefreshAllUI(NukeopsRuleComponent nukeops, GameRuleComponent gameRule)
|
UpdateUI(ent, ev.Status);
|
||||||
{
|
|
||||||
var enumerator = EntityQueryEnumerator<WarDeclaratorComponent>();
|
|
||||||
while (enumerator.MoveNext(out var uid, out _))
|
|
||||||
{
|
|
||||||
UpdateUI(uid, nukeops, gameRule);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private void UpdateUI(EntityUid declaratorUid, NukeopsRuleComponent nukeops, GameRuleComponent gameRule)
|
private void UpdateUI(Entity<WarDeclaratorComponent> ent, WarConditionStatus? status = null)
|
||||||
{
|
{
|
||||||
var condition = _nukeopsRuleSystem.GetWarCondition(nukeops, gameRule);
|
|
||||||
|
|
||||||
TimeSpan startTime;
|
|
||||||
TimeSpan delayTime;
|
|
||||||
switch(condition)
|
|
||||||
{
|
|
||||||
case WarConditionStatus.YES_WAR:
|
|
||||||
startTime = gameRule.ActivatedAt;
|
|
||||||
delayTime = nukeops.WarDeclarationDelay;
|
|
||||||
break;
|
|
||||||
case WarConditionStatus.WAR_DELAY:
|
|
||||||
startTime = nukeops.WarDeclaredTime!.Value;
|
|
||||||
delayTime = nukeops.WarNukieArriveDelay!.Value;
|
|
||||||
break;
|
|
||||||
default:
|
|
||||||
startTime = TimeSpan.Zero;
|
|
||||||
delayTime = TimeSpan.Zero;
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
|
|
||||||
_userInterfaceSystem.TrySetUiState(
|
_userInterfaceSystem.TrySetUiState(
|
||||||
declaratorUid,
|
ent,
|
||||||
WarDeclaratorUiKey.Key,
|
WarDeclaratorUiKey.Key,
|
||||||
new WarDeclaratorBoundUserInterfaceState(
|
new WarDeclaratorBoundUserInterfaceState(status, ent.Comp.DisableAt, ent.Comp.ShuttleDisabledTime));
|
||||||
condition,
|
|
||||||
nukeops.WarDeclarationMinOps,
|
|
||||||
delayTime,
|
|
||||||
startTime));
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -6,7 +6,7 @@ namespace Content.Server.Roles;
|
|||||||
/// <summary>
|
/// <summary>
|
||||||
/// Role used to keep track of space dragons for antag purposes.
|
/// Role used to keep track of space dragons for antag purposes.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
[RegisterComponent, Access(typeof(DragonSystem))]
|
[RegisterComponent, Access(typeof(DragonSystem)), ExclusiveAntagonist]
|
||||||
public sealed partial class DragonRoleComponent : AntagonistRoleComponent
|
public sealed partial class DragonRoleComponent : AntagonistRoleComponent
|
||||||
{
|
{
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,8 +1,8 @@
|
|||||||
using Content.Shared.Roles;
|
using Content.Shared.Roles;
|
||||||
|
|
||||||
namespace Content.Server.Roles;
|
namespace Content.Server.Roles;
|
||||||
|
|
||||||
[RegisterComponent]
|
[RegisterComponent, ExclusiveAntagonist]
|
||||||
public sealed partial class InitialInfectedRoleComponent : AntagonistRoleComponent
|
public sealed partial class InitialInfectedRoleComponent : AntagonistRoleComponent
|
||||||
{
|
{
|
||||||
|
|
||||||
|
|||||||
@@ -2,7 +2,7 @@ using Content.Shared.Roles;
|
|||||||
|
|
||||||
namespace Content.Server.Roles;
|
namespace Content.Server.Roles;
|
||||||
|
|
||||||
[RegisterComponent]
|
[RegisterComponent, ExclusiveAntagonist]
|
||||||
public sealed partial class NinjaRoleComponent : AntagonistRoleComponent
|
public sealed partial class NinjaRoleComponent : AntagonistRoleComponent
|
||||||
{
|
{
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,11 +1,11 @@
|
|||||||
using Content.Shared.Roles;
|
using Content.Shared.Roles;
|
||||||
|
|
||||||
namespace Content.Server.Roles;
|
namespace Content.Server.Roles;
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Added to mind entities to tag that they are a nuke operative.
|
/// Added to mind entities to tag that they are a nuke operative.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
[RegisterComponent]
|
[RegisterComponent, ExclusiveAntagonist]
|
||||||
public sealed partial class NukeopsRoleComponent : AntagonistRoleComponent
|
public sealed partial class NukeopsRoleComponent : AntagonistRoleComponent
|
||||||
{
|
{
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -5,7 +5,7 @@ namespace Content.Server.Roles;
|
|||||||
/// <summary>
|
/// <summary>
|
||||||
/// Added to mind entities to tag that they are a Revolutionary.
|
/// Added to mind entities to tag that they are a Revolutionary.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
[RegisterComponent]
|
[RegisterComponent, ExclusiveAntagonist]
|
||||||
public sealed partial class RevolutionaryRoleComponent : AntagonistRoleComponent
|
public sealed partial class RevolutionaryRoleComponent : AntagonistRoleComponent
|
||||||
{
|
{
|
||||||
/// <summary>
|
/// <summary>
|
||||||
|
|||||||
@@ -1,8 +1,8 @@
|
|||||||
using Content.Shared.Roles;
|
using Content.Shared.Roles;
|
||||||
|
|
||||||
namespace Content.Server.Roles;
|
namespace Content.Server.Roles;
|
||||||
|
|
||||||
[RegisterComponent]
|
[RegisterComponent, ExclusiveAntagonist]
|
||||||
public sealed partial class TerminatorRoleComponent : AntagonistRoleComponent
|
public sealed partial class TerminatorRoleComponent : AntagonistRoleComponent
|
||||||
{
|
{
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,8 +1,8 @@
|
|||||||
using Content.Shared.Roles;
|
using Content.Shared.Roles;
|
||||||
|
|
||||||
namespace Content.Server.Roles;
|
namespace Content.Server.Roles;
|
||||||
|
|
||||||
[RegisterComponent]
|
[RegisterComponent, ExclusiveAntagonist]
|
||||||
public sealed partial class TraitorRoleComponent : AntagonistRoleComponent
|
public sealed partial class TraitorRoleComponent : AntagonistRoleComponent
|
||||||
{
|
{
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,8 +1,8 @@
|
|||||||
using Content.Shared.Roles;
|
using Content.Shared.Roles;
|
||||||
|
|
||||||
namespace Content.Server.Roles;
|
namespace Content.Server.Roles;
|
||||||
|
|
||||||
[RegisterComponent]
|
[RegisterComponent, ExclusiveAntagonist]
|
||||||
public sealed partial class ZombieRoleComponent : AntagonistRoleComponent
|
public sealed partial class ZombieRoleComponent : AntagonistRoleComponent
|
||||||
{
|
{
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,4 +1,5 @@
|
|||||||
using Content.Server.Shuttles.Systems;
|
using Content.Server.Shuttles.Systems;
|
||||||
|
using Robust.Shared.Prototypes;
|
||||||
using Robust.Shared.Utility;
|
using Robust.Shared.Utility;
|
||||||
|
|
||||||
namespace Content.Server.Shuttles.Components;
|
namespace Content.Server.Shuttles.Components;
|
||||||
@@ -9,5 +10,12 @@ namespace Content.Server.Shuttles.Components;
|
|||||||
[RegisterComponent, Access(typeof(ShuttleSystem))]
|
[RegisterComponent, Access(typeof(ShuttleSystem))]
|
||||||
public sealed partial class GridFillComponent : Component
|
public sealed partial class GridFillComponent : Component
|
||||||
{
|
{
|
||||||
[DataField("path")] public ResPath Path = new("/Maps/Shuttles/escape_pod_small.yml");
|
[DataField]
|
||||||
|
public ResPath Path = new("/Maps/Shuttles/escape_pod_small.yml");
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Components to be added to any spawned grids.
|
||||||
|
/// </summary>
|
||||||
|
[DataField]
|
||||||
|
public ComponentRegistry AddComponents = new();
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -216,6 +216,17 @@ public sealed partial class ShuttleSystem
|
|||||||
valid = true;
|
valid = true;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
foreach (var compReg in component.AddComponents.Values)
|
||||||
|
{
|
||||||
|
var compType = compReg.Component.GetType();
|
||||||
|
|
||||||
|
if (HasComp(ent[0], compType))
|
||||||
|
continue;
|
||||||
|
|
||||||
|
var comp = _factory.GetComponent(compType);
|
||||||
|
AddComp(ent[0], comp, true);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!valid)
|
if (!valid)
|
||||||
|
|||||||
@@ -1,8 +1,5 @@
|
|||||||
using Robust.Server.GameObjects;
|
using Robust.Server.GameObjects;
|
||||||
using Robust.Server.Maps;
|
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.GameTicking.Rules.Components;
|
||||||
using Content.Server.StationEvents.Components;
|
using Content.Server.StationEvents.Components;
|
||||||
using Content.Server.RoundEnd;
|
using Content.Server.RoundEnd;
|
||||||
@@ -11,22 +8,20 @@ namespace Content.Server.StationEvents.Events;
|
|||||||
|
|
||||||
public sealed class LoneOpsSpawnRule : StationEventSystem<LoneOpsSpawnRuleComponent>
|
public sealed class LoneOpsSpawnRule : StationEventSystem<LoneOpsSpawnRuleComponent>
|
||||||
{
|
{
|
||||||
[Dependency] private readonly IMapManager _mapManager = default!;
|
|
||||||
[Dependency] private readonly MapLoaderSystem _map = 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)
|
protected override void Started(EntityUid uid, LoneOpsSpawnRuleComponent component, GameRuleComponent gameRule, GameRuleStartedEvent args)
|
||||||
{
|
{
|
||||||
base.Started(uid, component, gameRule, args);
|
base.Started(uid, component, gameRule, args);
|
||||||
|
|
||||||
if (!_nukeopsRuleSystem.CheckLoneOpsSpawn())
|
// Loneops can only spawn if there is no nukeops active
|
||||||
|
if (GameTicker.IsGameRuleAdded<NukeopsRuleComponent>())
|
||||||
{
|
{
|
||||||
ForceEndSelf(uid, gameRule);
|
ForceEndSelf(uid, gameRule);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
var shuttleMap = _mapManager.CreateMap();
|
var shuttleMap = MapManager.CreateMap();
|
||||||
var options = new MapLoadOptions
|
var options = new MapLoadOptions
|
||||||
{
|
{
|
||||||
LoadMap = true,
|
LoadMap = true,
|
||||||
@@ -34,12 +29,12 @@ public sealed class LoneOpsSpawnRule : StationEventSystem<LoneOpsSpawnRuleCompon
|
|||||||
|
|
||||||
_map.TryLoad(shuttleMap, component.LoneOpsShuttlePath, out _, options);
|
_map.TryLoad(shuttleMap, component.LoneOpsShuttlePath, out _, options);
|
||||||
|
|
||||||
var nukeopsEntity = _gameTicker.AddGameRule(component.GameRuleProto);
|
var nukeopsEntity = GameTicker.AddGameRule(component.GameRuleProto);
|
||||||
component.AdditionalRule = nukeopsEntity;
|
component.AdditionalRule = nukeopsEntity;
|
||||||
var nukeopsComp = EntityManager.GetComponent<NukeopsRuleComponent>(nukeopsEntity);
|
var nukeopsComp = Comp<NukeopsRuleComponent>(nukeopsEntity);
|
||||||
nukeopsComp.SpawnOutpost = false;
|
nukeopsComp.SpawnOutpost = false;
|
||||||
nukeopsComp.RoundEndBehavior = RoundEndBehavior.Nothing;
|
nukeopsComp.RoundEndBehavior = RoundEndBehavior.Nothing;
|
||||||
_gameTicker.StartGameRule(nukeopsEntity);
|
GameTicker.StartGameRule(nukeopsEntity);
|
||||||
}
|
}
|
||||||
|
|
||||||
protected override void Ended(EntityUid uid, LoneOpsSpawnRuleComponent component, GameRuleComponent gameRule, GameRuleEndedEvent args)
|
protected override void Ended(EntityUid uid, LoneOpsSpawnRuleComponent component, GameRuleComponent gameRule, GameRuleEndedEvent args)
|
||||||
@@ -50,4 +45,3 @@ public sealed class LoneOpsSpawnRule : StationEventSystem<LoneOpsSpawnRuleCompon
|
|||||||
GameTicker.EndGameRule(component.AdditionalRule.Value);
|
GameTicker.EndGameRule(component.AdditionalRule.Value);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -1,38 +1,27 @@
|
|||||||
using System.Diagnostics.CodeAnalysis;
|
|
||||||
using Content.Server.Administration.Logs;
|
using Content.Server.Administration.Logs;
|
||||||
using Content.Server.Atmos.EntitySystems;
|
|
||||||
using Content.Server.Chat.Systems;
|
using Content.Server.Chat.Systems;
|
||||||
using Content.Server.GameTicking.Rules;
|
using Content.Server.GameTicking.Rules;
|
||||||
using Content.Server.GameTicking.Rules.Components;
|
using Content.Server.GameTicking.Rules.Components;
|
||||||
using Content.Server.Station.Components;
|
|
||||||
using Content.Server.Station.Systems;
|
using Content.Server.Station.Systems;
|
||||||
using Content.Server.StationEvents.Components;
|
using Content.Server.StationEvents.Components;
|
||||||
using Content.Shared.Database;
|
using Content.Shared.Database;
|
||||||
using Robust.Shared.Audio;
|
|
||||||
using Robust.Shared.Audio.Systems;
|
using Robust.Shared.Audio.Systems;
|
||||||
using Robust.Shared.Collections;
|
|
||||||
using Robust.Shared.Map;
|
using Robust.Shared.Map;
|
||||||
using Robust.Shared.Map.Components;
|
|
||||||
using Robust.Shared.Player;
|
using Robust.Shared.Player;
|
||||||
using Robust.Shared.Prototypes;
|
using Robust.Shared.Prototypes;
|
||||||
using Robust.Shared.Random;
|
|
||||||
using Robust.Shared.Timing;
|
|
||||||
|
|
||||||
namespace Content.Server.StationEvents.Events;
|
namespace Content.Server.StationEvents.Events;
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// An abstract entity system inherited by all station events for their behavior.
|
/// An abstract entity system inherited by all station events for their behavior.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public abstract partial class StationEventSystem<T> : GameRuleSystem<T> where T : IComponent
|
public abstract class StationEventSystem<T> : GameRuleSystem<T> where T : IComponent
|
||||||
{
|
{
|
||||||
[Dependency] protected readonly IAdminLogManager AdminLogManager = default!;
|
[Dependency] protected readonly IAdminLogManager AdminLogManager = default!;
|
||||||
[Dependency] private readonly IGameTiming _timing = default!;
|
|
||||||
[Dependency] protected readonly IMapManager MapManager = default!;
|
[Dependency] protected readonly IMapManager MapManager = default!;
|
||||||
[Dependency] protected readonly IPrototypeManager PrototypeManager = default!;
|
[Dependency] protected readonly IPrototypeManager PrototypeManager = default!;
|
||||||
[Dependency] private readonly AtmosphereSystem _atmosphere = default!;
|
|
||||||
[Dependency] protected readonly ChatSystem ChatSystem = default!;
|
[Dependency] protected readonly ChatSystem ChatSystem = default!;
|
||||||
[Dependency] protected readonly SharedAudioSystem Audio = default!;
|
[Dependency] protected readonly SharedAudioSystem Audio = default!;
|
||||||
[Dependency] private readonly SharedTransformSystem _transform = default!;
|
|
||||||
[Dependency] protected readonly StationSystem StationSystem = default!;
|
[Dependency] protected readonly StationSystem StationSystem = default!;
|
||||||
|
|
||||||
protected ISawmill Sawmill = default!;
|
protected ISawmill Sawmill = default!;
|
||||||
@@ -60,7 +49,7 @@ public abstract partial class StationEventSystem<T> : GameRuleSystem<T> where T
|
|||||||
}
|
}
|
||||||
|
|
||||||
Audio.PlayGlobal(stationEvent.StartAudio, Filter.Broadcast(), true);
|
Audio.PlayGlobal(stationEvent.StartAudio, Filter.Broadcast(), true);
|
||||||
stationEvent.StartTime = _timing.CurTime + stationEvent.StartDelay;
|
stationEvent.StartTime = Timing.CurTime + stationEvent.StartDelay;
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <inheritdoc/>
|
/// <inheritdoc/>
|
||||||
@@ -79,7 +68,7 @@ public abstract partial class StationEventSystem<T> : GameRuleSystem<T> where T
|
|||||||
? stationEvent.Duration
|
? stationEvent.Duration
|
||||||
: TimeSpan.FromSeconds(RobustRandom.NextDouble(stationEvent.Duration.Value.TotalSeconds,
|
: TimeSpan.FromSeconds(RobustRandom.NextDouble(stationEvent.Duration.Value.TotalSeconds,
|
||||||
stationEvent.MaxDuration.Value.TotalSeconds));
|
stationEvent.MaxDuration.Value.TotalSeconds));
|
||||||
stationEvent.EndTime = _timing.CurTime + duration;
|
stationEvent.EndTime = Timing.CurTime + duration;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -116,11 +105,11 @@ public abstract partial class StationEventSystem<T> : GameRuleSystem<T> where T
|
|||||||
if (!GameTicker.IsGameRuleAdded(uid, ruleData))
|
if (!GameTicker.IsGameRuleAdded(uid, ruleData))
|
||||||
continue;
|
continue;
|
||||||
|
|
||||||
if (!GameTicker.IsGameRuleActive(uid, ruleData) && _timing.CurTime >= stationEvent.StartTime)
|
if (!GameTicker.IsGameRuleActive(uid, ruleData) && Timing.CurTime >= stationEvent.StartTime)
|
||||||
{
|
{
|
||||||
GameTicker.StartGameRule(uid, ruleData);
|
GameTicker.StartGameRule(uid, ruleData);
|
||||||
}
|
}
|
||||||
else if (stationEvent.EndTime != null && _timing.CurTime >= stationEvent.EndTime && GameTicker.IsGameRuleActive(uid, ruleData))
|
else if (stationEvent.EndTime != null && Timing.CurTime >= stationEvent.EndTime && GameTicker.IsGameRuleActive(uid, ruleData))
|
||||||
{
|
{
|
||||||
GameTicker.EndGameRule(uid, ruleData);
|
GameTicker.EndGameRule(uid, ruleData);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,6 +1,5 @@
|
|||||||
using Content.Server.GameTicking.Rules;
|
using Content.Server.GameTicking.Rules;
|
||||||
using Content.Server.Traitor.Components;
|
using Content.Server.Traitor.Components;
|
||||||
using Content.Shared.Mind;
|
|
||||||
using Content.Shared.Mind.Components;
|
using Content.Shared.Mind.Components;
|
||||||
|
|
||||||
namespace Content.Server.Traitor.Systems;
|
namespace Content.Server.Traitor.Systems;
|
||||||
@@ -16,15 +15,9 @@ public sealed class AutoTraitorSystem : EntitySystem
|
|||||||
{
|
{
|
||||||
base.Initialize();
|
base.Initialize();
|
||||||
|
|
||||||
SubscribeLocalEvent<AutoTraitorComponent, MapInitEvent>(OnMapInit);
|
|
||||||
SubscribeLocalEvent<AutoTraitorComponent, MindAddedMessage>(OnMindAdded);
|
SubscribeLocalEvent<AutoTraitorComponent, MindAddedMessage>(OnMindAdded);
|
||||||
}
|
}
|
||||||
|
|
||||||
private void OnMapInit(EntityUid uid, AutoTraitorComponent comp, MapInitEvent args)
|
|
||||||
{
|
|
||||||
TryMakeTraitor(uid, comp);
|
|
||||||
}
|
|
||||||
|
|
||||||
private void OnMindAdded(EntityUid uid, AutoTraitorComponent comp, MindAddedMessage args)
|
private void OnMindAdded(EntityUid uid, AutoTraitorComponent comp, MindAddedMessage args)
|
||||||
{
|
{
|
||||||
TryMakeTraitor(uid, comp);
|
TryMakeTraitor(uid, comp);
|
||||||
@@ -60,15 +53,9 @@ public sealed class AutoTraitorSystem : EntitySystem
|
|||||||
if (!Resolve(uid, ref comp))
|
if (!Resolve(uid, ref comp))
|
||||||
return false;
|
return false;
|
||||||
|
|
||||||
if (!TryComp<MindContainerComponent>(uid, out var mindContainer) || mindContainer.Mind == null)
|
//Start the rule if it has not already been started
|
||||||
return false;
|
var traitorRuleComponent = _traitorRule.StartGameRule();
|
||||||
|
_traitorRule.MakeTraitor(uid, traitorRuleComponent, giveUplink: comp.GiveUplink, giveObjectives: comp.GiveObjectives);
|
||||||
var mindId = mindContainer.Mind.Value;
|
|
||||||
if (!TryComp<MindComponent>(mindId, out var mind) || mind.Session == null)
|
|
||||||
return false;
|
|
||||||
|
|
||||||
var session = mind.Session;
|
|
||||||
_traitorRule.MakeTraitor(session, giveUplink: comp.GiveUplink, giveObjectives: comp.GiveObjectives);
|
|
||||||
// prevent spamming anything if it fails
|
// prevent spamming anything if it fails
|
||||||
RemComp<AutoTraitorComponent>(uid);
|
RemComp<AutoTraitorComponent>(uid);
|
||||||
return true;
|
return true;
|
||||||
|
|||||||
22
Content.Shared/Antag/AntagAcceptability.cs
Normal file
22
Content.Shared/Antag/AntagAcceptability.cs
Normal file
@@ -0,0 +1,22 @@
|
|||||||
|
namespace Content.Shared.Antag;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Used by AntagSelectionSystem to indicate which types of antag roles are allowed to choose the same entity
|
||||||
|
/// For example, Thief HeadRev
|
||||||
|
/// </summary>
|
||||||
|
public enum AntagAcceptability
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Dont choose anyone who already has an antag role
|
||||||
|
/// </summary>
|
||||||
|
None,
|
||||||
|
/// <summary>
|
||||||
|
/// Dont choose anyone who has an exclusive antag role
|
||||||
|
/// </summary>
|
||||||
|
NotExclusive,
|
||||||
|
/// <summary>
|
||||||
|
/// Choose anyone
|
||||||
|
/// </summary>
|
||||||
|
All
|
||||||
|
}
|
||||||
|
|
||||||
@@ -487,6 +487,13 @@ namespace Content.Shared.CCVar
|
|||||||
public static readonly CVarDef<int> PiratesPlayersPerOp =
|
public static readonly CVarDef<int> PiratesPlayersPerOp =
|
||||||
CVarDef.Create("pirates.players_per_pirate", 5);
|
CVarDef.Create("pirates.players_per_pirate", 5);
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Nukeops
|
||||||
|
*/
|
||||||
|
|
||||||
|
public static readonly CVarDef<bool> NukeopsSpawnGhostRoles =
|
||||||
|
CVarDef.Create("nukeops.spawn_ghost_roles", false);
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* Tips
|
* Tips
|
||||||
*/
|
*/
|
||||||
|
|||||||
@@ -1,12 +1,16 @@
|
|||||||
using System.Diagnostics.CodeAnalysis;
|
using System.Diagnostics.CodeAnalysis;
|
||||||
using System.Linq;
|
using System.Linq;
|
||||||
using Content.Shared.Hands.Components;
|
using Content.Shared.Hands.Components;
|
||||||
|
using Content.Shared.Storage.EntitySystems;
|
||||||
|
using Robust.Shared.Containers;
|
||||||
using Robust.Shared.Prototypes;
|
using Robust.Shared.Prototypes;
|
||||||
|
|
||||||
namespace Content.Shared.Inventory;
|
namespace Content.Shared.Inventory;
|
||||||
|
|
||||||
public partial class InventorySystem
|
public partial class InventorySystem
|
||||||
{
|
{
|
||||||
|
[Dependency] private readonly SharedStorageSystem _storageSystem = default!;
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Yields all entities in hands or inventory slots with the specific flags.
|
/// Yields all entities in hands or inventory slots with the specific flags.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
@@ -86,4 +90,55 @@ public partial class InventorySystem
|
|||||||
// We finally try to equip the item, otherwise we delete it.
|
// We finally try to equip the item, otherwise we delete it.
|
||||||
return TryEquip(uid, item, slot, silent, force) || DeleteItem();
|
return TryEquip(uid, item, slot, silent, force) || DeleteItem();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Will attempt to spawn a list of items inside of an entities bag, pockets, hands or nearby
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="entity">The entity that you want to spawn an item on</param>
|
||||||
|
/// <param name="items">A list of prototype IDs that you want to spawn in the bag.</param>
|
||||||
|
public void SpawnItemsOnEntity(EntityUid entity, List<EntProtoId> items)
|
||||||
|
{
|
||||||
|
foreach (var item in items)
|
||||||
|
{
|
||||||
|
SpawnItemOnEntity(entity, item);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Will attempt to spawn an item inside of an entities bag, pockets, hands or nearby
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="entity">The entity that you want to spawn an item on</param>
|
||||||
|
/// <param name="item">The prototype ID that you want to spawn in the bag.</param>
|
||||||
|
public void SpawnItemOnEntity(EntityUid entity, EntProtoId item)
|
||||||
|
{
|
||||||
|
//Transform() throws error if TransformComponent doesnt exist
|
||||||
|
if (!HasComp<TransformComponent>(entity))
|
||||||
|
return;
|
||||||
|
|
||||||
|
var xform = Transform(entity);
|
||||||
|
var mapCoords = _transform.GetMapCoordinates(xform);
|
||||||
|
|
||||||
|
var itemToSpawn = Spawn(item, mapCoords);
|
||||||
|
|
||||||
|
//Try insert into the backpack
|
||||||
|
if (TryGetSlotContainer(entity, "back", out var backSlot, out _)
|
||||||
|
&& backSlot.ContainedEntity.HasValue
|
||||||
|
&& _storageSystem.Insert(backSlot.ContainedEntity.Value, itemToSpawn, out _)
|
||||||
|
)
|
||||||
|
return;
|
||||||
|
|
||||||
|
//Try insert into pockets
|
||||||
|
if (TryGetSlotContainer(entity, "pocket1", out var pocket1, out _)
|
||||||
|
&& _containerSystem.Insert(itemToSpawn, pocket1)
|
||||||
|
)
|
||||||
|
return;
|
||||||
|
|
||||||
|
if (TryGetSlotContainer(entity, "pocket2", out var pocket2, out _)
|
||||||
|
&& _containerSystem.Insert(itemToSpawn, pocket2)
|
||||||
|
)
|
||||||
|
return;
|
||||||
|
|
||||||
|
//Try insert into hands, or drop on the floor
|
||||||
|
_handsSystem.PickupOrDrop(entity, itemToSpawn, false);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -10,30 +10,28 @@ public enum WarDeclaratorUiKey
|
|||||||
|
|
||||||
public enum WarConditionStatus : byte
|
public enum WarConditionStatus : byte
|
||||||
{
|
{
|
||||||
WAR_READY,
|
WarReady,
|
||||||
WAR_DELAY,
|
YesWar,
|
||||||
YES_WAR,
|
NoWarUnknown,
|
||||||
NO_WAR_UNKNOWN,
|
NoWarTimeout,
|
||||||
NO_WAR_TIMEOUT,
|
NoWarSmallCrew,
|
||||||
NO_WAR_SMALL_CREW,
|
NoWarShuttleDeparted
|
||||||
NO_WAR_SHUTTLE_DEPARTED
|
|
||||||
}
|
}
|
||||||
|
|
||||||
[Serializable, NetSerializable]
|
[Serializable, NetSerializable]
|
||||||
public sealed class WarDeclaratorBoundUserInterfaceState : BoundUserInterfaceState
|
public sealed class WarDeclaratorBoundUserInterfaceState : BoundUserInterfaceState
|
||||||
{
|
{
|
||||||
public WarConditionStatus Status;
|
public WarConditionStatus? Status;
|
||||||
public int MinCrew;
|
public TimeSpan ShuttleDisabledTime;
|
||||||
public TimeSpan Delay;
|
|
||||||
public TimeSpan EndTime;
|
public TimeSpan EndTime;
|
||||||
|
|
||||||
public WarDeclaratorBoundUserInterfaceState(WarConditionStatus status, int minCrew, TimeSpan delay, TimeSpan endTime)
|
public WarDeclaratorBoundUserInterfaceState(WarConditionStatus? status, TimeSpan endTime, TimeSpan shuttleDisabledTime)
|
||||||
{
|
{
|
||||||
Status = status;
|
Status = status;
|
||||||
MinCrew = minCrew;
|
|
||||||
Delay = delay;
|
|
||||||
EndTime = endTime;
|
EndTime = endTime;
|
||||||
|
ShuttleDisabledTime = shuttleDisabledTime;
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
[Serializable, NetSerializable]
|
[Serializable, NetSerializable]
|
||||||
|
|||||||
@@ -2,6 +2,7 @@ using Content.Shared.Antag;
|
|||||||
using Robust.Shared.GameStates;
|
using Robust.Shared.GameStates;
|
||||||
using Content.Shared.StatusIcon;
|
using Content.Shared.StatusIcon;
|
||||||
using Robust.Shared.Prototypes;
|
using Robust.Shared.Prototypes;
|
||||||
|
using Robust.Shared.Audio;
|
||||||
|
|
||||||
namespace Content.Shared.Revolutionary.Components;
|
namespace Content.Shared.Revolutionary.Components;
|
||||||
|
|
||||||
@@ -17,6 +18,12 @@ public sealed partial class RevolutionaryComponent : Component, IAntagStatusIcon
|
|||||||
[DataField, ViewVariables(VVAccess.ReadWrite)]
|
[DataField, ViewVariables(VVAccess.ReadWrite)]
|
||||||
public ProtoId<StatusIconPrototype> StatusIcon { get; set; } = "RevolutionaryFaction";
|
public ProtoId<StatusIconPrototype> StatusIcon { get; set; } = "RevolutionaryFaction";
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Sound that plays when you are chosen as Rev. (Placeholder until I find something cool I guess)
|
||||||
|
/// </summary>
|
||||||
|
[DataField]
|
||||||
|
public SoundSpecifier RevStartSound = new SoundPathSpecifier("/Audio/Ambience/Antag/headrev_start.ogg");
|
||||||
|
|
||||||
public override bool SessionSpecific => true;
|
public override bool SessionSpecific => true;
|
||||||
|
|
||||||
[DataField]
|
[DataField]
|
||||||
|
|||||||
@@ -1,4 +1,5 @@
|
|||||||
using Robust.Shared.Serialization.TypeSerializers.Implementations.Custom.Prototype;
|
using JetBrains.Annotations;
|
||||||
|
using Robust.Shared.Serialization.TypeSerializers.Implementations.Custom.Prototype;
|
||||||
|
|
||||||
namespace Content.Shared.Roles;
|
namespace Content.Shared.Roles;
|
||||||
|
|
||||||
@@ -7,3 +8,13 @@ public abstract partial class AntagonistRoleComponent : Component
|
|||||||
[DataField("prototype", required: true, customTypeSerializer: typeof(PrototypeIdSerializer<AntagPrototype>))]
|
[DataField("prototype", required: true, customTypeSerializer: typeof(PrototypeIdSerializer<AntagPrototype>))]
|
||||||
public string? PrototypeId;
|
public string? PrototypeId;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Mark the antagonist role component as being exclusive
|
||||||
|
/// IE by default other antagonists should refuse to select the same entity for a different antag role
|
||||||
|
/// </summary>
|
||||||
|
[AttributeUsage(AttributeTargets.Class, Inherited = false)]
|
||||||
|
[BaseTypeRequired(typeof(AntagonistRoleComponent))]
|
||||||
|
public sealed partial class ExclusiveAntagonistAttribute : Attribute
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|||||||
@@ -1,9 +1,10 @@
|
|||||||
namespace Content.Shared.Roles;
|
namespace Content.Shared.Roles;
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Event raised on a mind entity id to get whether or not the player is considered an antagonist,
|
/// Event raised on a mind entity id to get whether or not the player is considered an antagonist,
|
||||||
/// depending on their roles.
|
/// depending on their roles.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
/// <param name="IsAntagonist">Whether or not the player is an antagonist.</param>
|
/// <param name="IsAntagonist">Whether or not the player is an antagonist.</param>
|
||||||
|
/// <param name="IsExclusiveAntagonist">Whether or not AntagSelectionSystem should exclude this player from other antag roles</param
|
||||||
[ByRefEvent]
|
[ByRefEvent]
|
||||||
public record struct MindIsAntagonistEvent(bool IsAntagonist);
|
public record struct MindIsAntagonistEvent(bool IsAntagonist, bool IsExclusiveAntagonist);
|
||||||
|
|||||||
@@ -1,10 +1,11 @@
|
|||||||
using Content.Shared.Administration.Logs;
|
using Content.Shared.Administration.Logs;
|
||||||
using Content.Shared.Database;
|
using Content.Shared.Database;
|
||||||
using Content.Shared.Mind;
|
using Content.Shared.Mind;
|
||||||
using Content.Shared.Roles.Jobs;
|
using Content.Shared.Roles.Jobs;
|
||||||
using Robust.Shared.Audio;
|
using Robust.Shared.Audio;
|
||||||
using Robust.Shared.Audio.Systems;
|
using Robust.Shared.Audio.Systems;
|
||||||
using Robust.Shared.Prototypes;
|
using Robust.Shared.Prototypes;
|
||||||
|
using Robust.Shared.Utility;
|
||||||
|
|
||||||
namespace Content.Shared.Roles;
|
namespace Content.Shared.Roles;
|
||||||
|
|
||||||
@@ -57,7 +58,7 @@ public abstract class SharedRoleSystem : EntitySystem
|
|||||||
args.Roles.Add(new RoleInfo(component, name, true, null, prototype));
|
args.Roles.Add(new RoleInfo(component, name, true, null, prototype));
|
||||||
});
|
});
|
||||||
|
|
||||||
SubscribeLocalEvent((EntityUid _, T _, ref MindIsAntagonistEvent args) => args.IsAntagonist = true);
|
SubscribeLocalEvent((EntityUid _, T _, ref MindIsAntagonistEvent args) => { args.IsAntagonist = true; args.IsExclusiveAntagonist |= typeof(T).TryGetCustomAttribute<ExclusiveAntagonistAttribute>(out _); });
|
||||||
_antagTypes.Add(typeof(T));
|
_antagTypes.Add(typeof(T));
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -85,7 +86,7 @@ public abstract class SharedRoleSystem : EntitySystem
|
|||||||
AddComp(mindId, component);
|
AddComp(mindId, component);
|
||||||
var antagonist = IsAntagonistRole<T>();
|
var antagonist = IsAntagonistRole<T>();
|
||||||
|
|
||||||
var mindEv = new MindRoleAddedEvent();
|
var mindEv = new MindRoleAddedEvent(silent);
|
||||||
RaiseLocalEvent(mindId, ref mindEv);
|
RaiseLocalEvent(mindId, ref mindEv);
|
||||||
|
|
||||||
var message = new RoleAddedEvent(mindId, mind, antagonist, silent);
|
var message = new RoleAddedEvent(mindId, mind, antagonist, silent);
|
||||||
@@ -156,6 +157,21 @@ public abstract class SharedRoleSystem : EntitySystem
|
|||||||
return ev.IsAntagonist;
|
return ev.IsAntagonist;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Does this mind possess an exclusive antagonist role
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="mindId">The mind entity</param>
|
||||||
|
/// <returns>True if the mind possesses an exclusive antag role</returns>
|
||||||
|
public bool MindIsExclusiveAntagonist(EntityUid? mindId)
|
||||||
|
{
|
||||||
|
if (mindId == null)
|
||||||
|
return false;
|
||||||
|
|
||||||
|
var ev = new MindIsAntagonistEvent();
|
||||||
|
RaiseLocalEvent(mindId.Value, ref ev);
|
||||||
|
return ev.IsExclusiveAntagonist;
|
||||||
|
}
|
||||||
|
|
||||||
public bool IsAntagonistRole<T>()
|
public bool IsAntagonistRole<T>()
|
||||||
{
|
{
|
||||||
return _antagTypes.Contains(typeof(T));
|
return _antagTypes.Contains(typeof(T));
|
||||||
|
|||||||
@@ -37,3 +37,6 @@ latejoin-arrival-announcement = {$character} ({$job}) has arrived at the station
|
|||||||
latejoin-arrival-sender = Station
|
latejoin-arrival-sender = Station
|
||||||
latejoin-arrivals-direction = A shuttle transferring you to your station will arrive shortly.
|
latejoin-arrivals-direction = A shuttle transferring you to your station will arrive shortly.
|
||||||
latejoin-arrivals-direction-time = A shuttle transferring you to your station will arrive in {$time}.
|
latejoin-arrivals-direction-time = A shuttle transferring you to your station will arrive in {$time}.
|
||||||
|
|
||||||
|
preset-not-enough-ready-players = Can't start {$presetName}. Requires {$minimumPlayers} players but we have {$readyPlayersCount}.
|
||||||
|
preset-no-one-ready = Can't start {$presetName}. No players are ready.
|
||||||
@@ -1,16 +1,15 @@
|
|||||||
war-declarator-not-nukeops = The device makes beeping noises, but nothing happens...
|
war-declarator-not-working = The device makes beeping noises, but nothing happens...
|
||||||
war-declarator-ui-header = Declaration of War
|
war-declarator-ui-header = Declaration of War
|
||||||
war-declarator-ui-war-button = DECLARE WAR!
|
war-declarator-ui-war-button = DECLARE WAR!
|
||||||
war-declarator-conditions-small-crew = Less than { $min } operatives
|
war-declarator-ui-try-war-button = Try to declare war
|
||||||
|
war-declarator-conditions-small-crew = There are not enough nuclear operatives to declare war!
|
||||||
war-declarator-conditions-left-outpost = Shuttle left the syndicate outpost
|
war-declarator-conditions-left-outpost = Shuttle left the syndicate outpost
|
||||||
war-declarator-conditions-time-out = War declaration time passed
|
war-declarator-conditions-time-out = War declaration time passed
|
||||||
war-declarator-conditions-delay = Shuttle departure temporarily unavailable
|
|
||||||
war-declarator-conditions-ready = Shuttle can leave the outpost!
|
war-declarator-conditions-ready = Shuttle can leave the outpost!
|
||||||
war-declarator-conditions-unknown = Unknown
|
war-declarator-conditions-unknown = Unknown
|
||||||
war-declarator-boost-possible = Able to declare war
|
war-declarator-boost-possible = Able to declare war
|
||||||
war-declarator-boost-impossible = Unable to declare war
|
war-declarator-boost-impossible = Unable to declare war
|
||||||
war-declarator-boost-declared = War declared!
|
war-declarator-boost-declared = War declared! Shuttle may be disabled for...
|
||||||
war-declarator-boost-declared-delay = War declared! Shuttle departure temporarily disabled
|
war-declarator-boost-timer = Time left: {$time} minutes.
|
||||||
war-declarator-boost-timer = Time left: {$minutes} minutes and {$seconds} seconds
|
|
||||||
war-declarator-default-message = A syndicate fringe group has declared their intent to utterly destroy station with a nuclear device, and dares the crew to try and stop them.
|
war-declarator-default-message = A syndicate fringe group has declared their intent to utterly destroy station with a nuclear device, and dares the crew to try and stop them.
|
||||||
war-declarator-message-placeholder = Write a custom declaration of war here...
|
war-declarator-message-placeholder = Write a custom declaration of war here...
|
||||||
|
|||||||
@@ -1,2 +1,2 @@
|
|||||||
war-ops-infiltrator-unavailable = ERROR: FTL Travel recalculation in progress. Estimated time: {$minutes} minutes and {$seconds} seconds
|
war-ops-infiltrator-unavailable = ERROR: FTL Travel recalculation in progress. Estimated time: {$time} minutes.
|
||||||
war-ops-shuttle-call-unavailable = Evacuation shuttle is currently unavailable. Please wait
|
war-ops-shuttle-call-unavailable = Evacuation shuttle is currently unavailable. Please wait
|
||||||
|
|||||||
File diff suppressed because it is too large
Load Diff
@@ -1,7 +1,7 @@
|
|||||||
- type: entity
|
- type: entity
|
||||||
parent: BaseItem
|
parent: BaseItem
|
||||||
id: NukeOpsDeclarationOfWar
|
id: NukeOpsDeclarationOfWar
|
||||||
name: declaration of war
|
name: war declarator
|
||||||
description: Use to send a declaration of hostilities to the target, delaying your shuttle departure while they prepare for your assault. Such a brazen move will attract the attention of powerful benefactors within the Syndicate, who will supply your team with a massive amount of bonus telecrystals. Must be used at start of mission, or your benefactors will lose interest.
|
description: Use to send a declaration of hostilities to the target, delaying your shuttle departure while they prepare for your assault. Such a brazen move will attract the attention of powerful benefactors within the Syndicate, who will supply your team with a massive amount of bonus telecrystals. Must be used at start of mission, or your benefactors will lose interest.
|
||||||
components:
|
components:
|
||||||
- type: Sprite
|
- type: Sprite
|
||||||
@@ -22,4 +22,5 @@
|
|||||||
type: WarDeclaratorBoundUserInterface
|
type: WarDeclaratorBoundUserInterface
|
||||||
- type: WarDeclarator
|
- type: WarDeclarator
|
||||||
message: war-declarator-default-message
|
message: war-declarator-default-message
|
||||||
# - type: WarConditionOnExamine
|
- type: AccessReader
|
||||||
|
access: [["NuclearOperative"]]
|
||||||
|
|||||||
16
Resources/Prototypes/Entities/Stations/syndicate.yml
Normal file
16
Resources/Prototypes/Entities/Stations/syndicate.yml
Normal file
@@ -0,0 +1,16 @@
|
|||||||
|
- type: entity
|
||||||
|
abstract: true
|
||||||
|
id: BaseStationSyndicate
|
||||||
|
components:
|
||||||
|
- type: NpcFactionMember
|
||||||
|
factions:
|
||||||
|
- Syndicate
|
||||||
|
|
||||||
|
- type: entity
|
||||||
|
id: StandardNukieOutpost
|
||||||
|
parent:
|
||||||
|
- BaseStation
|
||||||
|
- BaseStationSyndicate
|
||||||
|
noSpawn: true
|
||||||
|
components:
|
||||||
|
- type: Transform
|
||||||
@@ -58,6 +58,8 @@
|
|||||||
parent: BaseGameRule
|
parent: BaseGameRule
|
||||||
noSpawn: true
|
noSpawn: true
|
||||||
components:
|
components:
|
||||||
|
- type: GameRule
|
||||||
|
minPlayers: 20
|
||||||
- type: NukeopsRule
|
- type: NukeopsRule
|
||||||
faction: Syndicate
|
faction: Syndicate
|
||||||
- type: ThiefRule #the thieves come as an extension of another gamemode
|
- type: ThiefRule #the thieves come as an extension of another gamemode
|
||||||
|
|||||||
11
Resources/Prototypes/Maps/syndicate.yml
Normal file
11
Resources/Prototypes/Maps/syndicate.yml
Normal file
@@ -0,0 +1,11 @@
|
|||||||
|
- type: gameMap
|
||||||
|
id: NukieOutpost
|
||||||
|
mapName: Nukie Outpost
|
||||||
|
mapPath: /Maps/Nonstations/nukieplanet.yml
|
||||||
|
minPlayers: 0
|
||||||
|
stations:
|
||||||
|
SyndicateOutpost:
|
||||||
|
stationProto: StandardNukieOutpost
|
||||||
|
components:
|
||||||
|
- type: StationNameSetup
|
||||||
|
mapNameTemplate: "Nukie Outpost"
|
||||||
Reference in New Issue
Block a user