Cherrypicks 4 (#393)
* Immovable Rod changes (#26757) * Adds non randomized rod velocity (#27123) * adds non randomized rod velocity * Adds despawn suffix to despawn rod * make fire spreading scale with mass (#27202) * make fire spreading scale with mass * realer --------- Co-authored-by: deltanedas <@deltanedas:kde.org> * lower max firestacks to 10, refactor flammable (#27159) * lower max firestacks to 10, refactor flammable * fix * uncap fire stack damage, lower fire stack damage * fix fire spread round removal (#27986) * fix a resolve debug assert * rewrite fire spread --------- Co-authored-by: deltanedas <@deltanedas:kde.org> * fire troll fix (#28034) Co-authored-by: deltanedas <@deltanedas:kde.org> * Hide doafters if you're in a container (#29487) * Hide doafters if you're in a container * Out of the loop --------- Co-authored-by: plykiya <plykiya@protonmail.com> * Add ghost role raffles (#26629) * Add ghost role raffles * GRR: Fix dialogue sizing, fix merge * GRR: Add raffle deciders (winner picker) * GRR: Make settings prototype based with option to override * GRR: Use Raffles folder and namespace * GRR: DataFieldify and TimeSpanify * GRR: Don't actually DataFieldify HashSet<ICommonSession>s * GRR: add GetGhostRoleCount() + docs * update engine on branch * Ghost role raffles: docs, fix window size, cleanup, etc * GRR: Admin UI * GRR: Admin UI: Display initial/max/ext of selected raffle settings proto * GRR: Make a ton of roles raffled * Make ERT use short raffle timer (#27830) Co-authored-by: plykiya <plykiya@protonmail.com> * gives loneops a proper ghost role raffle (#27841) * shorten short raffle (#28685) * - fix: Conflicts. * - fix. --------- Co-authored-by: keronshb <54602815+keronshb@users.noreply.github.com> Co-authored-by: deltanedas <39013340+deltanedas@users.noreply.github.com> Co-authored-by: Whisper <121047731+QuietlyWhisper@users.noreply.github.com> Co-authored-by: Plykiya <58439124+Plykiya@users.noreply.github.com> Co-authored-by: plykiya <plykiya@protonmail.com> Co-authored-by: no <165581243+pissdemon@users.noreply.github.com> Co-authored-by: Boaz1111 <149967078+Boaz1111@users.noreply.github.com> Co-authored-by: HS <81934438+HolySSSS@users.noreply.github.com>
This commit is contained in:
@@ -8,6 +8,7 @@ using Robust.Client.Player;
|
|||||||
using Robust.Shared.Prototypes;
|
using Robust.Shared.Prototypes;
|
||||||
using Robust.Shared.Timing;
|
using Robust.Shared.Timing;
|
||||||
using Robust.Shared.Utility;
|
using Robust.Shared.Utility;
|
||||||
|
using Robust.Shared.Containers;
|
||||||
|
|
||||||
namespace Content.Client.DoAfter;
|
namespace Content.Client.DoAfter;
|
||||||
|
|
||||||
@@ -19,6 +20,7 @@ public sealed class DoAfterOverlay : Overlay
|
|||||||
private readonly SharedTransformSystem _transform;
|
private readonly SharedTransformSystem _transform;
|
||||||
private readonly MetaDataSystem _meta;
|
private readonly MetaDataSystem _meta;
|
||||||
private readonly ProgressColorSystem _progressColor;
|
private readonly ProgressColorSystem _progressColor;
|
||||||
|
private readonly SharedContainerSystem _container;
|
||||||
|
|
||||||
private readonly Texture _barTexture;
|
private readonly Texture _barTexture;
|
||||||
|
|
||||||
@@ -40,6 +42,7 @@ public sealed class DoAfterOverlay : Overlay
|
|||||||
_player = player;
|
_player = player;
|
||||||
_transform = _entManager.EntitySysManager.GetEntitySystem<SharedTransformSystem>();
|
_transform = _entManager.EntitySysManager.GetEntitySystem<SharedTransformSystem>();
|
||||||
_meta = _entManager.EntitySysManager.GetEntitySystem<MetaDataSystem>();
|
_meta = _entManager.EntitySysManager.GetEntitySystem<MetaDataSystem>();
|
||||||
|
_container = _entManager.EntitySysManager.GetEntitySystem<SharedContainerSystem>();
|
||||||
_progressColor = _entManager.System<ProgressColorSystem>();
|
_progressColor = _entManager.System<ProgressColorSystem>();
|
||||||
var sprite = new SpriteSpecifier.Rsi(new ResPath("/Textures/Interface/Misc/progress_bar.rsi"), "icon");
|
var sprite = new SpriteSpecifier.Rsi(new ResPath("/Textures/Interface/Misc/progress_bar.rsi"), "icon");
|
||||||
_barTexture = _entManager.EntitySysManager.GetEntitySystem<SpriteSystem>().Frame0(sprite);
|
_barTexture = _entManager.EntitySysManager.GetEntitySystem<SpriteSystem>().Frame0(sprite);
|
||||||
@@ -90,11 +93,13 @@ public sealed class DoAfterOverlay : Overlay
|
|||||||
|
|
||||||
var offset = 0f;
|
var offset = 0f;
|
||||||
|
|
||||||
|
var isInContainer = _container.IsEntityOrParentInContainer(uid, meta, xform);
|
||||||
|
|
||||||
foreach (var doAfter in comp.DoAfters.Values)
|
foreach (var doAfter in comp.DoAfters.Values)
|
||||||
{
|
{
|
||||||
// Hide some DoAfters from other players for stealthy actions (ie: thieving gloves)
|
// Hide some DoAfters from other players for stealthy actions (ie: thieving gloves)
|
||||||
var alpha = 1f;
|
var alpha = 1f;
|
||||||
if (doAfter.Args.Hidden)
|
if (doAfter.Args.Hidden || isInContainer)
|
||||||
{
|
{
|
||||||
if (uid != localEnt)
|
if (uid != localEnt)
|
||||||
continue;
|
continue;
|
||||||
|
|||||||
@@ -134,6 +134,7 @@ namespace Content.Client.Entry
|
|||||||
_prototypeManager.RegisterIgnore("wireLayout");
|
_prototypeManager.RegisterIgnore("wireLayout");
|
||||||
_prototypeManager.RegisterIgnore("alertLevels");
|
_prototypeManager.RegisterIgnore("alertLevels");
|
||||||
_prototypeManager.RegisterIgnore("nukeopsRole");
|
_prototypeManager.RegisterIgnore("nukeopsRole");
|
||||||
|
_prototypeManager.RegisterIgnore("ghostRoleRaffleDecider");
|
||||||
|
|
||||||
//WD-EDIT
|
//WD-EDIT
|
||||||
_prototypeManager.RegisterIgnore("loadout");
|
_prototypeManager.RegisterIgnore("loadout");
|
||||||
|
|||||||
@@ -5,7 +5,7 @@
|
|||||||
Text="{Loc 'ghost-roles-window-request-role-button'}"
|
Text="{Loc 'ghost-roles-window-request-role-button'}"
|
||||||
StyleClasses="OpenRight"
|
StyleClasses="OpenRight"
|
||||||
HorizontalAlignment="Left"
|
HorizontalAlignment="Left"
|
||||||
SetWidth="150"/>
|
SetWidth="300"/>
|
||||||
<Button Name="FollowButton"
|
<Button Name="FollowButton"
|
||||||
Access="Public"
|
Access="Public"
|
||||||
Text="{Loc 'ghost-roles-window-follow-role-button'}"
|
Text="{Loc 'ghost-roles-window-follow-role-button'}"
|
||||||
|
|||||||
@@ -1,9 +1,72 @@
|
|||||||
using Robust.Client.AutoGenerated;
|
using Content.Shared.Ghost.Roles;
|
||||||
|
using Robust.Client.AutoGenerated;
|
||||||
using Robust.Client.UserInterface.Controls;
|
using Robust.Client.UserInterface.Controls;
|
||||||
|
using Robust.Client.UserInterface.XAML;
|
||||||
|
using Robust.Shared.Timing;
|
||||||
|
|
||||||
namespace Content.Client.UserInterface.Systems.Ghost.Controls.Roles;
|
namespace Content.Client.UserInterface.Systems.Ghost.Controls.Roles;
|
||||||
|
|
||||||
[GenerateTypedNameReferences]
|
[GenerateTypedNameReferences]
|
||||||
public sealed partial class GhostRoleEntryButtons : BoxContainer
|
public sealed partial class GhostRoleEntryButtons : BoxContainer
|
||||||
{
|
{
|
||||||
|
[Dependency] private readonly IGameTiming _timing = default!;
|
||||||
|
private readonly GhostRoleKind _ghostRoleKind;
|
||||||
|
private readonly uint _playerCount;
|
||||||
|
private readonly TimeSpan _raffleEndTime = TimeSpan.MinValue;
|
||||||
|
|
||||||
|
public GhostRoleEntryButtons(GhostRoleInfo ghostRoleInfo)
|
||||||
|
{
|
||||||
|
RobustXamlLoader.Load(this);
|
||||||
|
IoCManager.InjectDependencies(this);
|
||||||
|
|
||||||
|
_ghostRoleKind = ghostRoleInfo.Kind;
|
||||||
|
if (IsActiveRaffle(_ghostRoleKind))
|
||||||
|
{
|
||||||
|
_playerCount = ghostRoleInfo.RafflePlayerCount;
|
||||||
|
_raffleEndTime = ghostRoleInfo.RaffleEndTime;
|
||||||
|
}
|
||||||
|
|
||||||
|
UpdateRequestButton();
|
||||||
|
}
|
||||||
|
|
||||||
|
private void UpdateRequestButton()
|
||||||
|
{
|
||||||
|
var messageId = _ghostRoleKind switch
|
||||||
|
{
|
||||||
|
GhostRoleKind.FirstComeFirstServe => "ghost-roles-window-request-role-button",
|
||||||
|
GhostRoleKind.RaffleReady => "ghost-roles-window-join-raffle-button",
|
||||||
|
GhostRoleKind.RaffleInProgress => "ghost-roles-window-raffle-in-progress-button",
|
||||||
|
GhostRoleKind.RaffleJoined => "ghost-roles-window-leave-raffle-button",
|
||||||
|
_ => throw new ArgumentOutOfRangeException(nameof(_ghostRoleKind),
|
||||||
|
$"Unknown {nameof(GhostRoleKind)} '{_ghostRoleKind}'")
|
||||||
|
};
|
||||||
|
|
||||||
|
if (IsActiveRaffle(_ghostRoleKind))
|
||||||
|
{
|
||||||
|
var timeLeft = _timing.CurTime <= _raffleEndTime
|
||||||
|
? _raffleEndTime - _timing.CurTime
|
||||||
|
: TimeSpan.Zero;
|
||||||
|
|
||||||
|
var timeString = $"{timeLeft.Minutes:0}:{timeLeft.Seconds:00}";
|
||||||
|
RequestButton.Text = Loc.GetString(messageId, ("time", timeString), ("players", _playerCount));
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
RequestButton.Text = Loc.GetString(messageId);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private static bool IsActiveRaffle(GhostRoleKind kind)
|
||||||
|
{
|
||||||
|
return kind is GhostRoleKind.RaffleInProgress or GhostRoleKind.RaffleJoined;
|
||||||
|
}
|
||||||
|
|
||||||
|
protected override void FrameUpdate(FrameEventArgs args)
|
||||||
|
{
|
||||||
|
base.FrameUpdate(args);
|
||||||
|
if (IsActiveRaffle(_ghostRoleKind))
|
||||||
|
{
|
||||||
|
UpdateRequestButton();
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -26,7 +26,7 @@ namespace Content.Client.UserInterface.Systems.Ghost.Controls.Roles
|
|||||||
|
|
||||||
foreach (var role in roles)
|
foreach (var role in roles)
|
||||||
{
|
{
|
||||||
var button = new GhostRoleEntryButtons();
|
var button = new GhostRoleEntryButtons(role);
|
||||||
button.RequestButton.OnPressed += _ => OnRoleSelected?.Invoke(role);
|
button.RequestButton.OnPressed += _ => OnRoleSelected?.Invoke(role);
|
||||||
button.FollowButton.OnPressed += _ => OnRoleFollow?.Invoke(role);
|
button.FollowButton.OnPressed += _ => OnRoleFollow?.Invoke(role);
|
||||||
|
|
||||||
|
|||||||
@@ -20,13 +20,24 @@ namespace Content.Client.UserInterface.Systems.Ghost.Controls.Roles
|
|||||||
{
|
{
|
||||||
_window = new GhostRolesWindow();
|
_window = new GhostRolesWindow();
|
||||||
|
|
||||||
_window.OnRoleRequested += info =>
|
_window.OnRoleRequestButtonClicked += info =>
|
||||||
{
|
{
|
||||||
if (_windowRules != null)
|
_windowRules?.Close();
|
||||||
_windowRules.Close();
|
|
||||||
|
if (info.Kind == GhostRoleKind.RaffleJoined)
|
||||||
|
{
|
||||||
|
SendMessage(new LeaveGhostRoleRaffleMessage(info.Identifier));
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
_windowRules = new GhostRoleRulesWindow(info.Rules, _ =>
|
_windowRules = new GhostRoleRulesWindow(info.Rules, _ =>
|
||||||
{
|
{
|
||||||
SendMessage(new GhostRoleTakeoverRequestMessage(info.Identifier));
|
SendMessage(new RequestGhostRoleMessage(info.Identifier));
|
||||||
|
|
||||||
|
// if raffle role, close rules window on request, otherwise do
|
||||||
|
// old behavior of waiting for the server to close it
|
||||||
|
if (info.Kind != GhostRoleKind.FirstComeFirstServe)
|
||||||
|
_windowRules?.Close();
|
||||||
});
|
});
|
||||||
_windowRulesId = info.Identifier;
|
_windowRulesId = info.Identifier;
|
||||||
_windowRules.OnClose += () =>
|
_windowRules.OnClose += () =>
|
||||||
@@ -38,7 +49,7 @@ namespace Content.Client.UserInterface.Systems.Ghost.Controls.Roles
|
|||||||
|
|
||||||
_window.OnRoleFollow += info =>
|
_window.OnRoleFollow += info =>
|
||||||
{
|
{
|
||||||
SendMessage(new GhostRoleFollowRequestMessage(info.Identifier));
|
SendMessage(new FollowGhostRoleMessage(info.Identifier));
|
||||||
};
|
};
|
||||||
|
|
||||||
_window.OnClose += () =>
|
_window.OnClose += () =>
|
||||||
@@ -64,7 +75,8 @@ namespace Content.Client.UserInterface.Systems.Ghost.Controls.Roles
|
|||||||
{
|
{
|
||||||
base.HandleState(state);
|
base.HandleState(state);
|
||||||
|
|
||||||
if (state is not GhostRolesEuiState ghostState) return;
|
if (state is not GhostRolesEuiState ghostState)
|
||||||
|
return;
|
||||||
_window.ClearEntries();
|
_window.ClearEntries();
|
||||||
|
|
||||||
var entityManager = IoCManager.Resolve<IEntityManager>();
|
var entityManager = IoCManager.Resolve<IEntityManager>();
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
<DefaultWindow xmlns="https://spacestation14.io"
|
<DefaultWindow xmlns="https://spacestation14.io"
|
||||||
Title="{Loc 'ghost-roles-window-title'}"
|
Title="{Loc 'ghost-roles-window-title'}"
|
||||||
MinSize="450 400"
|
MinSize="490 400"
|
||||||
SetSize="400 500">
|
SetSize="490 500">
|
||||||
<Label Name="NoRolesMessage"
|
<Label Name="NoRolesMessage"
|
||||||
Text="{Loc 'ghost-roles-window-no-roles-available-label'}"
|
Text="{Loc 'ghost-roles-window-no-roles-available-label'}"
|
||||||
VerticalAlignment="Top" />
|
VerticalAlignment="Top" />
|
||||||
|
|||||||
@@ -9,7 +9,7 @@ namespace Content.Client.UserInterface.Systems.Ghost.Controls.Roles
|
|||||||
[GenerateTypedNameReferences]
|
[GenerateTypedNameReferences]
|
||||||
public sealed partial class GhostRolesWindow : DefaultWindow
|
public sealed partial class GhostRolesWindow : DefaultWindow
|
||||||
{
|
{
|
||||||
public event Action<GhostRoleInfo>? OnRoleRequested;
|
public event Action<GhostRoleInfo>? OnRoleRequestButtonClicked;
|
||||||
public event Action<GhostRoleInfo>? OnRoleFollow;
|
public event Action<GhostRoleInfo>? OnRoleFollow;
|
||||||
|
|
||||||
public void ClearEntries()
|
public void ClearEntries()
|
||||||
@@ -23,7 +23,7 @@ namespace Content.Client.UserInterface.Systems.Ghost.Controls.Roles
|
|||||||
NoRolesMessage.Visible = false;
|
NoRolesMessage.Visible = false;
|
||||||
|
|
||||||
var entry = new GhostRolesEntry(name, description, hasAccess, reason, roles, spriteSystem);
|
var entry = new GhostRolesEntry(name, description, hasAccess, reason, roles, spriteSystem);
|
||||||
entry.OnRoleSelected += OnRoleRequested;
|
entry.OnRoleSelected += OnRoleRequestButtonClicked;
|
||||||
entry.OnRoleFollow += OnRoleFollow;
|
entry.OnRoleFollow += OnRoleFollow;
|
||||||
EntryContainer.AddChild(entry);
|
EntryContainer.AddChild(entry);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,4 +1,5 @@
|
|||||||
using Content.Client.Eui;
|
using Content.Client.Eui;
|
||||||
|
using Content.Server.Ghost.Roles.Raffles;
|
||||||
using Content.Shared.Eui;
|
using Content.Shared.Eui;
|
||||||
using Content.Shared.Ghost.Roles;
|
using Content.Shared.Ghost.Roles;
|
||||||
using JetBrains.Annotations;
|
using JetBrains.Annotations;
|
||||||
@@ -41,7 +42,7 @@ public sealed class MakeGhostRoleEui : BaseEui
|
|||||||
_window.OpenCentered();
|
_window.OpenCentered();
|
||||||
}
|
}
|
||||||
|
|
||||||
private void OnMake(NetEntity entity, string name, string description, string rules, bool makeSentient)
|
private void OnMake(NetEntity entity, string name, string description, string rules, bool makeSentient, GhostRoleRaffleSettings? raffleSettings)
|
||||||
{
|
{
|
||||||
var session = _playerManager.LocalSession;
|
var session = _playerManager.LocalSession;
|
||||||
if (session == null)
|
if (session == null)
|
||||||
@@ -49,12 +50,22 @@ public sealed class MakeGhostRoleEui : BaseEui
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
var command = raffleSettings is not null ? "makeghostroleraffled" : "makeghostrole";
|
||||||
|
|
||||||
var makeGhostRoleCommand =
|
var makeGhostRoleCommand =
|
||||||
$"makeghostrole " +
|
$"{command} " +
|
||||||
$"\"{CommandParsing.Escape(entity.ToString())}\" " +
|
$"\"{CommandParsing.Escape(entity.ToString())}\" " +
|
||||||
$"\"{CommandParsing.Escape(name)}\" " +
|
$"\"{CommandParsing.Escape(name)}\" " +
|
||||||
$"\"{CommandParsing.Escape(description)}\" " +
|
$"\"{CommandParsing.Escape(description)}\" ";
|
||||||
$"\"{CommandParsing.Escape(rules)}\"";
|
|
||||||
|
if (raffleSettings is not null)
|
||||||
|
{
|
||||||
|
makeGhostRoleCommand += $"{raffleSettings.InitialDuration} " +
|
||||||
|
$"{raffleSettings.JoinExtendsDurationBy} " +
|
||||||
|
$"{raffleSettings.MaxDuration} ";
|
||||||
|
}
|
||||||
|
|
||||||
|
makeGhostRoleCommand += $"\"{CommandParsing.Escape(rules)}\"";
|
||||||
|
|
||||||
_consoleHost.ExecuteCommand(session, makeGhostRoleCommand);
|
_consoleHost.ExecuteCommand(session, makeGhostRoleCommand);
|
||||||
|
|
||||||
|
|||||||
@@ -22,6 +22,24 @@
|
|||||||
<Label Name="MakeSentientLabel" Text="Make Sentient" />
|
<Label Name="MakeSentientLabel" Text="Make Sentient" />
|
||||||
<CheckBox Name="MakeSentientCheckbox" />
|
<CheckBox Name="MakeSentientCheckbox" />
|
||||||
</BoxContainer>
|
</BoxContainer>
|
||||||
|
<BoxContainer Orientation="Horizontal">
|
||||||
|
<Label Name="RaffleLabel" Text="Raffle Role?" />
|
||||||
|
<OptionButton Name="RaffleButton" />
|
||||||
|
</BoxContainer>
|
||||||
|
<BoxContainer Name="RaffleCustomSettingsContainer" Orientation="Vertical" Visible="False">
|
||||||
|
<BoxContainer Orientation="Horizontal">
|
||||||
|
<Label Name="RaffleInitialDurationLabel" Text="Initial Duration (s)" />
|
||||||
|
<SpinBox Name="RaffleInitialDuration" HorizontalExpand="True" />
|
||||||
|
</BoxContainer>
|
||||||
|
<BoxContainer Orientation="Horizontal">
|
||||||
|
<Label Name="RaffleJoinExtendsDurationByLabel" Text="Joins Extend By (s)" />
|
||||||
|
<SpinBox Name="RaffleJoinExtendsDurationBy" HorizontalExpand="True" />
|
||||||
|
</BoxContainer>
|
||||||
|
<BoxContainer Orientation="Horizontal">
|
||||||
|
<Label Name="RaffleMaxDurationLabel" Text="Max Duration (s)" />
|
||||||
|
<SpinBox Name="RaffleMaxDuration" HorizontalExpand="True" />
|
||||||
|
</BoxContainer>
|
||||||
|
</BoxContainer>
|
||||||
<BoxContainer Orientation="Horizontal">
|
<BoxContainer Orientation="Horizontal">
|
||||||
<Button Name="MakeButton" Text="Make" />
|
<Button Name="MakeButton" Text="Make" />
|
||||||
</BoxContainer>
|
</BoxContainer>
|
||||||
|
|||||||
@@ -1,7 +1,12 @@
|
|||||||
using System.Numerics;
|
using System.Linq;
|
||||||
|
using System.Numerics;
|
||||||
|
using Content.Server.Ghost.Roles.Raffles;
|
||||||
|
using Content.Shared.Ghost.Roles.Raffles;
|
||||||
using Robust.Client.AutoGenerated;
|
using Robust.Client.AutoGenerated;
|
||||||
|
using Robust.Client.UserInterface.Controls;
|
||||||
using Robust.Client.UserInterface.CustomControls;
|
using Robust.Client.UserInterface.CustomControls;
|
||||||
using Robust.Client.UserInterface.XAML;
|
using Robust.Client.UserInterface.XAML;
|
||||||
|
using Robust.Shared.Prototypes;
|
||||||
using static Robust.Client.UserInterface.Controls.BaseButton;
|
using static Robust.Client.UserInterface.Controls.BaseButton;
|
||||||
|
|
||||||
namespace Content.Client.UserInterface.Systems.Ghost.Controls.Roles
|
namespace Content.Client.UserInterface.Systems.Ghost.Controls.Roles
|
||||||
@@ -9,10 +14,20 @@ namespace Content.Client.UserInterface.Systems.Ghost.Controls.Roles
|
|||||||
[GenerateTypedNameReferences]
|
[GenerateTypedNameReferences]
|
||||||
public sealed partial class MakeGhostRoleWindow : DefaultWindow
|
public sealed partial class MakeGhostRoleWindow : DefaultWindow
|
||||||
{
|
{
|
||||||
public delegate void MakeRole(NetEntity uid, string name, string description, string rules, bool makeSentient);
|
[Dependency] private readonly IPrototypeManager _prototypeManager = default!;
|
||||||
|
private readonly List<GhostRoleRaffleSettingsPrototype> _rafflePrototypes = [];
|
||||||
|
|
||||||
|
private const int RaffleDontRaffleId = -1;
|
||||||
|
private const int RaffleCustomRaffleId = -2;
|
||||||
|
private int _raffleSettingId = RaffleDontRaffleId;
|
||||||
|
|
||||||
|
private NetEntity? Entity { get; set; }
|
||||||
|
|
||||||
|
public event MakeRole? OnMake;
|
||||||
|
|
||||||
public MakeGhostRoleWindow()
|
public MakeGhostRoleWindow()
|
||||||
{
|
{
|
||||||
|
IoCManager.InjectDependencies(this);
|
||||||
RobustXamlLoader.Load(this);
|
RobustXamlLoader.Load(this);
|
||||||
|
|
||||||
MakeSentientLabel.MinSize = new Vector2(150, 0);
|
MakeSentientLabel.MinSize = new Vector2(150, 0);
|
||||||
@@ -23,13 +38,87 @@ namespace Content.Client.UserInterface.Systems.Ghost.Controls.Roles
|
|||||||
RoleDescription.MinSize = new Vector2(300, 0);
|
RoleDescription.MinSize = new Vector2(300, 0);
|
||||||
RoleRulesLabel.MinSize = new Vector2(150, 0);
|
RoleRulesLabel.MinSize = new Vector2(150, 0);
|
||||||
RoleRules.MinSize = new Vector2(300, 0);
|
RoleRules.MinSize = new Vector2(300, 0);
|
||||||
|
RaffleLabel.MinSize = new Vector2(150, 0);
|
||||||
|
RaffleButton.MinSize = new Vector2(300, 0);
|
||||||
|
RaffleInitialDurationLabel.MinSize = new Vector2(150, 0);
|
||||||
|
RaffleInitialDuration.MinSize = new Vector2(300, 0);
|
||||||
|
RaffleJoinExtendsDurationByLabel.MinSize = new Vector2(150, 0);
|
||||||
|
RaffleJoinExtendsDurationBy.MinSize = new Vector2(270, 0);
|
||||||
|
RaffleMaxDurationLabel.MinSize = new Vector2(150, 0);
|
||||||
|
RaffleMaxDuration.MinSize = new Vector2(270, 0);
|
||||||
|
|
||||||
MakeButton.OnPressed += OnPressed;
|
RaffleInitialDuration.OverrideValue(30);
|
||||||
|
RaffleJoinExtendsDurationBy.OverrideValue(5);
|
||||||
|
RaffleMaxDuration.OverrideValue(60);
|
||||||
|
|
||||||
|
RaffleInitialDuration.SetButtons(new List<int> { -30, -10 }, new List<int> { 10, 30 });
|
||||||
|
RaffleJoinExtendsDurationBy.SetButtons(new List<int> { -10, -5 }, new List<int> { 5, 10 });
|
||||||
|
RaffleMaxDuration.SetButtons(new List<int> { -30, -10 }, new List<int> { 10, 30 });
|
||||||
|
|
||||||
|
RaffleInitialDuration.IsValid = duration => duration > 0;
|
||||||
|
RaffleJoinExtendsDurationBy.IsValid = duration => duration >= 0;
|
||||||
|
RaffleMaxDuration.IsValid = duration => duration > 0;
|
||||||
|
|
||||||
|
RaffleInitialDuration.ValueChanged += OnRaffleDurationChanged;
|
||||||
|
RaffleJoinExtendsDurationBy.ValueChanged += OnRaffleDurationChanged;
|
||||||
|
RaffleMaxDuration.ValueChanged += OnRaffleDurationChanged;
|
||||||
|
|
||||||
|
|
||||||
|
RaffleButton.AddItem("Don't raffle", RaffleDontRaffleId);
|
||||||
|
RaffleButton.AddItem("Custom settings", RaffleCustomRaffleId);
|
||||||
|
|
||||||
|
var raffleProtos =
|
||||||
|
_prototypeManager.EnumeratePrototypes<GhostRoleRaffleSettingsPrototype>();
|
||||||
|
|
||||||
|
var idx = 0;
|
||||||
|
foreach (var raffleProto in raffleProtos)
|
||||||
|
{
|
||||||
|
_rafflePrototypes.Add(raffleProto);
|
||||||
|
var s = raffleProto.Settings;
|
||||||
|
var label =
|
||||||
|
$"{raffleProto.ID} (initial {s.InitialDuration}s, max {s.MaxDuration}s, join adds {s.JoinExtendsDurationBy}s)";
|
||||||
|
RaffleButton.AddItem(label, idx++);
|
||||||
|
}
|
||||||
|
|
||||||
|
MakeButton.OnPressed += OnMakeButtonPressed;
|
||||||
|
RaffleButton.OnItemSelected += OnRaffleButtonItemSelected;
|
||||||
}
|
}
|
||||||
|
|
||||||
private NetEntity? Entity { get; set; }
|
private void OnRaffleDurationChanged(ValueChangedEventArgs args)
|
||||||
|
{
|
||||||
|
ValidateRaffleDurations();
|
||||||
|
}
|
||||||
|
|
||||||
public event MakeRole? OnMake;
|
private void ValidateRaffleDurations()
|
||||||
|
{
|
||||||
|
if (RaffleInitialDuration.Value > RaffleMaxDuration.Value)
|
||||||
|
{
|
||||||
|
MakeButton.Disabled = true;
|
||||||
|
MakeButton.ToolTip = "The initial duration must not exceed the maximum duration.";
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
MakeButton.Disabled = false;
|
||||||
|
MakeButton.ToolTip = null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void OnRaffleButtonItemSelected(OptionButton.ItemSelectedEventArgs args)
|
||||||
|
{
|
||||||
|
_raffleSettingId = args.Id;
|
||||||
|
args.Button.SelectId(args.Id);
|
||||||
|
if (args.Id != RaffleCustomRaffleId)
|
||||||
|
{
|
||||||
|
RaffleCustomSettingsContainer.Visible = false;
|
||||||
|
MakeButton.ToolTip = null;
|
||||||
|
MakeButton.Disabled = false;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
RaffleCustomSettingsContainer.Visible = true;
|
||||||
|
ValidateRaffleDurations();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
public void SetEntity(IEntityManager entManager, NetEntity entity)
|
public void SetEntity(IEntityManager entManager, NetEntity entity)
|
||||||
{
|
{
|
||||||
@@ -38,14 +127,32 @@ namespace Content.Client.UserInterface.Systems.Ghost.Controls.Roles
|
|||||||
RoleEntity.Text = $"{entity}";
|
RoleEntity.Text = $"{entity}";
|
||||||
}
|
}
|
||||||
|
|
||||||
private void OnPressed(ButtonEventArgs args)
|
private void OnMakeButtonPressed(ButtonEventArgs args)
|
||||||
{
|
{
|
||||||
if (Entity == null)
|
if (Entity == null)
|
||||||
{
|
{
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
OnMake?.Invoke(Entity.Value, RoleName.Text, RoleDescription.Text, RoleRules.Text, MakeSentientCheckbox.Pressed);
|
GhostRoleRaffleSettings? raffleSettings = null;
|
||||||
|
|
||||||
|
if (_raffleSettingId == RaffleCustomRaffleId)
|
||||||
|
{
|
||||||
|
raffleSettings = new GhostRoleRaffleSettings()
|
||||||
|
{
|
||||||
|
InitialDuration = (uint) RaffleInitialDuration.Value,
|
||||||
|
JoinExtendsDurationBy = (uint) RaffleJoinExtendsDurationBy.Value,
|
||||||
|
MaxDuration = (uint) RaffleMaxDuration.Value
|
||||||
|
};
|
||||||
|
}
|
||||||
|
else if (_raffleSettingId != RaffleDontRaffleId)
|
||||||
|
{
|
||||||
|
raffleSettings = _rafflePrototypes[_raffleSettingId].Settings;
|
||||||
|
}
|
||||||
|
|
||||||
|
OnMake?.Invoke(Entity.Value, RoleName.Text, RoleDescription.Text, RoleRules.Text, MakeSentientCheckbox.Pressed, raffleSettings);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public delegate void MakeRole(NetEntity uid, string name, string description, string rules, bool makeSentient, GhostRoleRaffleSettings? settings);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -152,7 +152,7 @@ public sealed partial class AdminVerbSystem
|
|||||||
Act = () =>
|
Act = () =>
|
||||||
{
|
{
|
||||||
// Fuck you. Burn Forever.
|
// Fuck you. Burn Forever.
|
||||||
flammable.FireStacks = FlammableSystem.MaximumFireStacks;
|
flammable.FireStacks = flammable.MaximumFireStacks;
|
||||||
_flammableSystem.Ignite(args.Target, args.User);
|
_flammableSystem.Ignite(args.Target, args.User);
|
||||||
var xform = Transform(args.Target);
|
var xform = Transform(args.Target);
|
||||||
_popupSystem.PopupEntity(Loc.GetString("admin-smite-set-alight-self"), args.Target,
|
_popupSystem.PopupEntity(Loc.GetString("admin-smite-set-alight-self"), args.Target,
|
||||||
|
|||||||
@@ -11,49 +11,65 @@ namespace Content.Server.Atmos.Components
|
|||||||
|
|
||||||
[ViewVariables(VVAccess.ReadWrite)]
|
[ViewVariables(VVAccess.ReadWrite)]
|
||||||
[DataField]
|
[DataField]
|
||||||
public bool OnFire { get; set; }
|
public bool OnFire;
|
||||||
|
|
||||||
[ViewVariables(VVAccess.ReadWrite)]
|
[ViewVariables(VVAccess.ReadWrite)]
|
||||||
[DataField]
|
[DataField]
|
||||||
public float FireStacks { get; set; }
|
public float FireStacks;
|
||||||
|
|
||||||
[ViewVariables(VVAccess.ReadWrite)]
|
[ViewVariables(VVAccess.ReadWrite)]
|
||||||
[DataField("fireSpread")]
|
[DataField]
|
||||||
|
public float MaximumFireStacks = 10f;
|
||||||
|
|
||||||
|
[ViewVariables(VVAccess.ReadWrite)]
|
||||||
|
[DataField]
|
||||||
|
public float MinimumFireStacks = -10f;
|
||||||
|
|
||||||
|
[ViewVariables(VVAccess.ReadWrite)]
|
||||||
|
[DataField]
|
||||||
|
public string FlammableFixtureID = "flammable";
|
||||||
|
|
||||||
|
[ViewVariables(VVAccess.ReadWrite)]
|
||||||
|
[DataField]
|
||||||
|
public float MinIgnitionTemperature = 373.15f;
|
||||||
|
|
||||||
|
[ViewVariables(VVAccess.ReadWrite)]
|
||||||
|
[DataField]
|
||||||
public bool FireSpread { get; private set; } = false;
|
public bool FireSpread { get; private set; } = false;
|
||||||
|
|
||||||
[ViewVariables(VVAccess.ReadWrite)]
|
[ViewVariables(VVAccess.ReadWrite)]
|
||||||
[DataField("canResistFire")]
|
[DataField]
|
||||||
public bool CanResistFire { get; private set; } = false;
|
public bool CanResistFire { get; private set; } = false;
|
||||||
|
|
||||||
[DataField("damage", required: true)]
|
[DataField(required: true)]
|
||||||
[ViewVariables(VVAccess.ReadWrite)]
|
[ViewVariables(VVAccess.ReadWrite)]
|
||||||
public DamageSpecifier Damage = new(); // Empty by default, we don't want any funny NREs.
|
public DamageSpecifier Damage = new(); // Empty by default, we don't want any funny NREs.
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Used for the fixture created to handle passing firestacks when two flammable objects collide.
|
/// Used for the fixture created to handle passing firestacks when two flammable objects collide.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
[DataField("flammableCollisionShape")]
|
[DataField]
|
||||||
public IPhysShape FlammableCollisionShape = new PhysShapeCircle(0.35f);
|
public IPhysShape FlammableCollisionShape = new PhysShapeCircle(0.35f);
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Should the component be set on fire by interactions with isHot entities
|
/// Should the component be set on fire by interactions with isHot entities
|
||||||
/// </summary>
|
/// </summary>
|
||||||
[ViewVariables(VVAccess.ReadWrite)]
|
[ViewVariables(VVAccess.ReadWrite)]
|
||||||
[DataField("alwaysCombustible")]
|
[DataField]
|
||||||
public bool AlwaysCombustible = false;
|
public bool AlwaysCombustible = false;
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Can the component anyhow lose its FireStacks?
|
/// Can the component anyhow lose its FireStacks?
|
||||||
/// </summary>
|
/// </summary>
|
||||||
[ViewVariables(VVAccess.ReadWrite)]
|
[ViewVariables(VVAccess.ReadWrite)]
|
||||||
[DataField("canExtinguish")]
|
[DataField]
|
||||||
public bool CanExtinguish = true;
|
public bool CanExtinguish = true;
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// How many firestacks should be applied to component when being set on fire?
|
/// How many firestacks should be applied to component when being set on fire?
|
||||||
/// </summary>
|
/// </summary>
|
||||||
[ViewVariables(VVAccess.ReadWrite)]
|
[ViewVariables(VVAccess.ReadWrite)]
|
||||||
[DataField("firestacksOnIgnite")]
|
[DataField]
|
||||||
public float FirestacksOnIgnite = 2.0f;
|
public float FirestacksOnIgnite = 2.0f;
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
|
|||||||
@@ -52,12 +52,10 @@ namespace Content.Server.Atmos.EntitySystems
|
|||||||
[Dependency] private readonly IRobustRandom _random = default!;
|
[Dependency] private readonly IRobustRandom _random = default!;
|
||||||
[Dependency] private readonly SpellBladeSystem _spellBlade = default!; // WD
|
[Dependency] private readonly SpellBladeSystem _spellBlade = default!; // WD
|
||||||
|
|
||||||
public const float MinimumFireStacks = -10f;
|
private EntityQuery<PhysicsComponent> _physicsQuery;
|
||||||
public const float MaximumFireStacks = 20f;
|
|
||||||
private const float UpdateTime = 1f;
|
|
||||||
|
|
||||||
public const float MinIgnitionTemperature = 373.15f;
|
// This should probably be moved to the component, requires a rewrite, all fires tick at the same time
|
||||||
public const string FlammableFixtureID = "flammable";
|
private const float UpdateTime = 1f;
|
||||||
|
|
||||||
private float _timer;
|
private float _timer;
|
||||||
|
|
||||||
@@ -67,6 +65,8 @@ namespace Content.Server.Atmos.EntitySystems
|
|||||||
{
|
{
|
||||||
UpdatesAfter.Add(typeof(AtmosphereSystem));
|
UpdatesAfter.Add(typeof(AtmosphereSystem));
|
||||||
|
|
||||||
|
_physicsQuery = GetEntityQuery<PhysicsComponent>();
|
||||||
|
|
||||||
SubscribeLocalEvent<FlammableComponent, MapInitEvent>(OnMapInit);
|
SubscribeLocalEvent<FlammableComponent, MapInitEvent>(OnMapInit);
|
||||||
SubscribeLocalEvent<FlammableComponent, InteractUsingEvent>(OnInteractUsing);
|
SubscribeLocalEvent<FlammableComponent, InteractUsingEvent>(OnInteractUsing);
|
||||||
SubscribeLocalEvent<FlammableComponent, StartCollideEvent>(OnCollide);
|
SubscribeLocalEvent<FlammableComponent, StartCollideEvent>(OnCollide);
|
||||||
@@ -141,7 +141,7 @@ namespace Content.Server.Atmos.EntitySystems
|
|||||||
if (!TryComp<PhysicsComponent>(uid, out var body))
|
if (!TryComp<PhysicsComponent>(uid, out var body))
|
||||||
return;
|
return;
|
||||||
|
|
||||||
_fixture.TryCreateFixture(uid, component.FlammableCollisionShape, FlammableFixtureID, hard: false,
|
_fixture.TryCreateFixture(uid, component.FlammableCollisionShape, component.FlammableFixtureID, hard: false,
|
||||||
collisionMask: (int) CollisionGroup.FullTileLayer, body: body);
|
collisionMask: (int) CollisionGroup.FullTileLayer, body: body);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -199,7 +199,7 @@ namespace Content.Server.Atmos.EntitySystems
|
|||||||
|
|
||||||
// Normal hard collisions, though this isn't generally possible since most flammable things are mobs
|
// Normal hard collisions, though this isn't generally possible since most flammable things are mobs
|
||||||
// which don't collide with one another, shouldn't work here.
|
// which don't collide with one another, shouldn't work here.
|
||||||
if (args.OtherFixtureId != FlammableFixtureID && args.OurFixtureId != FlammableFixtureID)
|
if (args.OtherFixtureId != flammable.FlammableFixtureID && args.OurFixtureId != flammable.FlammableFixtureID)
|
||||||
return;
|
return;
|
||||||
|
|
||||||
if (!flammable.FireSpread)
|
if (!flammable.FireSpread)
|
||||||
@@ -211,49 +211,30 @@ namespace Content.Server.Atmos.EntitySystems
|
|||||||
if (!flammable.OnFire && !otherFlammable.OnFire)
|
if (!flammable.OnFire && !otherFlammable.OnFire)
|
||||||
return; // Neither are on fire
|
return; // Neither are on fire
|
||||||
|
|
||||||
// WD START
|
// Both are on fire -> equalize fire stacks.
|
||||||
var weHold = _spellBlade.IsHoldingItemWithComponent<FireAspectComponent>(uid);
|
// Weight each thing's firestacks by its mass
|
||||||
var theyHold = _spellBlade.IsHoldingItemWithComponent<FireAspectComponent>(otherUid);
|
var mass1 = 1f;
|
||||||
// WD END
|
var mass2 = 1f;
|
||||||
|
if (_physicsQuery.TryComp(uid, out var physics) && _physicsQuery.TryComp(otherUid, out var otherPhys))
|
||||||
if (flammable.OnFire && otherFlammable.OnFire)
|
|
||||||
{
|
{
|
||||||
if (weHold && !theyHold || theyHold && !weHold) // WD
|
mass1 = physics.Mass;
|
||||||
return;
|
mass2 = otherPhys.Mass;
|
||||||
// Both are on fire -> equalize fire stacks.
|
|
||||||
var avg = (flammable.FireStacks + otherFlammable.FireStacks) / 2;
|
|
||||||
flammable.FireStacks = flammable.CanExtinguish ? avg : Math.Max(flammable.FireStacks, avg);
|
|
||||||
otherFlammable.FireStacks = otherFlammable.CanExtinguish ? avg : Math.Max(otherFlammable.FireStacks, avg);
|
|
||||||
UpdateAppearance(uid, flammable);
|
|
||||||
UpdateAppearance(otherUid, otherFlammable);
|
|
||||||
return;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Only one is on fire -> attempt to spread the fire.
|
// when the thing on fire is more massive than the other, the following happens:
|
||||||
if (flammable.OnFire)
|
// - the thing on fire loses a small number of firestacks
|
||||||
{
|
// - the other thing gains a large number of firestacks
|
||||||
if (theyHold) // WD
|
// so a person on fire engulfs a mouse, but an engulfed mouse barely does anything to a person
|
||||||
return;
|
var total = mass1 + mass2;
|
||||||
otherFlammable.FireStacks += flammable.FireStacks / 2;
|
var avg = (flammable.FireStacks + otherFlammable.FireStacks) / total;
|
||||||
Ignite(otherUid, uid, otherFlammable);
|
|
||||||
if (flammable.CanExtinguish)
|
// swap the entity losing stacks depending on whichever has the most firestack kilos
|
||||||
{
|
var (src, dest) = flammable.FireStacks * mass1 > otherFlammable.FireStacks * mass2
|
||||||
flammable.FireStacks /= 2;
|
? (-1f, 1f)
|
||||||
UpdateAppearance(uid, flammable);
|
: (1f, -1f);
|
||||||
}
|
// bring each entity to the same firestack mass, firestacks being scaled by the other's mass
|
||||||
}
|
AdjustFireStacks(uid, src * avg * mass2, flammable, ignite: true);
|
||||||
else
|
AdjustFireStacks(otherUid, dest * avg * mass1, otherFlammable, ignite: true);
|
||||||
{
|
|
||||||
if (weHold) // WD
|
|
||||||
return;
|
|
||||||
flammable.FireStacks += otherFlammable.FireStacks / 2;
|
|
||||||
Ignite(uid, otherUid, flammable);
|
|
||||||
if (otherFlammable.CanExtinguish)
|
|
||||||
{
|
|
||||||
otherFlammable.FireStacks /= 2;
|
|
||||||
UpdateAppearance(otherUid, otherFlammable);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private void OnIsHot(EntityUid uid, FlammableComponent flammable, IsHotEvent args)
|
private void OnIsHot(EntityUid uid, FlammableComponent flammable, IsHotEvent args)
|
||||||
@@ -263,7 +244,7 @@ namespace Content.Server.Atmos.EntitySystems
|
|||||||
|
|
||||||
private void OnTileFire(Entity<FlammableComponent> ent, ref TileFireEvent args)
|
private void OnTileFire(Entity<FlammableComponent> ent, ref TileFireEvent args)
|
||||||
{
|
{
|
||||||
var tempDelta = args.Temperature - MinIgnitionTemperature;
|
var tempDelta = args.Temperature - ent.Comp.MinIgnitionTemperature;
|
||||||
|
|
||||||
_fireEvents.TryGetValue(ent, out var maxTemp);
|
_fireEvents.TryGetValue(ent, out var maxTemp);
|
||||||
|
|
||||||
@@ -291,17 +272,30 @@ namespace Content.Server.Atmos.EntitySystems
|
|||||||
_appearance.SetData(uid, ToggleableLightVisuals.Enabled, flammable.OnFire, appearance);
|
_appearance.SetData(uid, ToggleableLightVisuals.Enabled, flammable.OnFire, appearance);
|
||||||
}
|
}
|
||||||
|
|
||||||
public void AdjustFireStacks(EntityUid uid, float relativeFireStacks, FlammableComponent? flammable = null)
|
public void AdjustFireStacks(EntityUid uid, float relativeFireStacks, FlammableComponent? flammable = null, bool ignite = false)
|
||||||
{
|
{
|
||||||
if (!Resolve(uid, ref flammable))
|
if (!Resolve(uid, ref flammable))
|
||||||
return;
|
return;
|
||||||
|
|
||||||
flammable.FireStacks = MathF.Min(MathF.Max(MinimumFireStacks, flammable.FireStacks + relativeFireStacks), MaximumFireStacks);
|
SetFireStacks(uid, flammable.FireStacks + relativeFireStacks, flammable, ignite);
|
||||||
|
}
|
||||||
|
|
||||||
if (flammable.OnFire && flammable.FireStacks <= 0)
|
public void SetFireStacks(EntityUid uid, float stacks, FlammableComponent? flammable = null, bool ignite = false)
|
||||||
|
{
|
||||||
|
if (!Resolve(uid, ref flammable))
|
||||||
|
return;
|
||||||
|
|
||||||
|
flammable.FireStacks = MathF.Min(MathF.Max(flammable.MinimumFireStacks, stacks), flammable.MaximumFireStacks);
|
||||||
|
|
||||||
|
if (flammable.FireStacks <= 0)
|
||||||
|
{
|
||||||
Extinguish(uid, flammable);
|
Extinguish(uid, flammable);
|
||||||
|
}
|
||||||
else
|
else
|
||||||
|
{
|
||||||
|
flammable.OnFire = ignite;
|
||||||
UpdateAppearance(uid, flammable);
|
UpdateAppearance(uid, flammable);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public void Extinguish(EntityUid uid, FlammableComponent? flammable = null)
|
public void Extinguish(EntityUid uid, FlammableComponent? flammable = null)
|
||||||
@@ -450,13 +444,11 @@ namespace Content.Server.Atmos.EntitySystems
|
|||||||
EnsureComp<IgnitionSourceComponent>(uid);
|
EnsureComp<IgnitionSourceComponent>(uid);
|
||||||
_ignitionSourceSystem.SetIgnited(uid);
|
_ignitionSourceSystem.SetIgnited(uid);
|
||||||
|
|
||||||
var damageScale = MathF.Min( flammable.FireStacks, 5);
|
|
||||||
|
|
||||||
if (TryComp(uid, out TemperatureComponent? temp))
|
if (TryComp(uid, out TemperatureComponent? temp))
|
||||||
_temperatureSystem.ChangeHeat(uid, 12500 * damageScale, false, temp);
|
_temperatureSystem.ChangeHeat(uid, 12500 * flammable.FireStacks, false, temp);
|
||||||
|
|
||||||
if (!_spellBlade.IsHoldingItemWithComponent<FireAspectComponent>(uid)) // WD EDIT
|
if (!_spellBlade.IsHoldingItemWithComponent<FireAspectComponent>(uid)) // WD EDIT
|
||||||
_damageableSystem.TryChangeDamage(uid, flammable.Damage * damageScale, interruptsDoAfters: false);
|
_damageableSystem.TryChangeDamage(uid, flammable.Damage * flammable.FireStacks, interruptsDoAfters: false);
|
||||||
|
|
||||||
AdjustFireStacks(uid, flammable.FirestackFade * (flammable.Resisting ? 10f : 1f), flammable);
|
AdjustFireStacks(uid, flammable.FirestackFade * (flammable.Resisting ? 10f : 1f), flammable);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
using System.Linq;
|
using System.Linq;
|
||||||
using Content.Server.Administration;
|
using Content.Server.Administration;
|
||||||
using Content.Server.Administration.Logs;
|
using Content.Server.Administration.Logs;
|
||||||
using Content.Server.Chat.Managers;
|
using Content.Server.Chat.Managers;
|
||||||
@@ -42,7 +42,7 @@ namespace Content.Server.GameTicking.Commands
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
ticker.SetGamePreset(type);
|
ticker.SetGamePreset(type, true);
|
||||||
|
|
||||||
_adminLogger.Add(LogType.EventStarted, $"Forced {type.ID} for secret.");
|
_adminLogger.Add(LogType.EventStarted, $"Forced {type.ID} for secret.");
|
||||||
|
|
||||||
|
|||||||
@@ -318,10 +318,9 @@ public sealed class NukeopsRuleSystem : GameRuleSystem<NukeopsRuleComponent>
|
|||||||
// TODO: this is kinda awful for multi-nukies
|
// TODO: this is kinda awful for multi-nukies
|
||||||
foreach (var nukeops in EntityQuery<NukeopsRuleComponent>())
|
foreach (var nukeops in EntityQuery<NukeopsRuleComponent>())
|
||||||
{
|
{
|
||||||
SetupOperativeEntity(uid, nukeOpSpawner.OperativeName, nukeOpSpawner.SpawnDetails, profile);
|
|
||||||
|
|
||||||
nukeops.OperativeMindPendingData.Add(uid, nukeOpSpawner.SpawnDetails.AntagRoleProto);
|
nukeops.OperativeMindPendingData.Add(uid, nukeOpSpawner.SpawnDetails.AntagRoleProto);
|
||||||
}
|
}
|
||||||
|
SetupOperativeEntity(uid, nukeOpSpawner.OperativeName, nukeOpSpawner.SpawnDetails, profile);
|
||||||
}
|
}
|
||||||
|
|
||||||
private void OnMindAdded(EntityUid uid, NukeOperativeComponent component, MindAddedMessage args)
|
private void OnMindAdded(EntityUid uid, NukeOperativeComponent component, MindAddedMessage args)
|
||||||
|
|||||||
@@ -1,4 +1,5 @@
|
|||||||
using Content.Server.Mind.Commands;
|
using Content.Server.Ghost.Roles.Raffles;
|
||||||
|
using Content.Server.Mind.Commands;
|
||||||
using Content.Shared.Roles;
|
using Content.Shared.Roles;
|
||||||
|
|
||||||
namespace Content.Server.Ghost.Roles.Components
|
namespace Content.Server.Ghost.Roles.Components
|
||||||
@@ -87,5 +88,12 @@ namespace Content.Server.Ghost.Roles.Components
|
|||||||
[ViewVariables(VVAccess.ReadWrite)]
|
[ViewVariables(VVAccess.ReadWrite)]
|
||||||
[DataField("reregister")]
|
[DataField("reregister")]
|
||||||
public bool ReregisterOnGhost { get; set; } = true;
|
public bool ReregisterOnGhost { get; set; } = true;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// If set, ghost role is raffled, otherwise it is first-come-first-serve.
|
||||||
|
/// </summary>
|
||||||
|
[DataField("raffle")]
|
||||||
|
[Access(typeof(GhostRoleSystem), Other = AccessPermissions.ReadWriteExecute)] // FIXME Friends
|
||||||
|
public GhostRoleRaffleConfig? RaffleConfig { get; set; }
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,58 @@
|
|||||||
|
using Content.Server.Ghost.Roles.Raffles;
|
||||||
|
using Robust.Shared.Player;
|
||||||
|
|
||||||
|
namespace Content.Server.Ghost.Roles.Components;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Indicates that a ghost role is currently being raffled, and stores data about the raffle in progress.
|
||||||
|
/// Raffles start when the first player joins a raffle.
|
||||||
|
/// </summary>
|
||||||
|
[RegisterComponent]
|
||||||
|
[Access(typeof(GhostRoleSystem))]
|
||||||
|
public sealed partial class GhostRoleRaffleComponent : Component
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Identifier of the <see cref="GhostRoleComponent">Ghost Role</see> this raffle is for.
|
||||||
|
/// </summary>
|
||||||
|
[ViewVariables(VVAccess.ReadOnly)]
|
||||||
|
[DataField]
|
||||||
|
public uint Identifier { get; set; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// List of sessions that are currently in the raffle.
|
||||||
|
/// </summary>
|
||||||
|
[ViewVariables(VVAccess.ReadOnly)]
|
||||||
|
public HashSet<ICommonSession> CurrentMembers = [];
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// List of sessions that are currently or were previously in the raffle.
|
||||||
|
/// </summary>
|
||||||
|
[ViewVariables(VVAccess.ReadOnly)]
|
||||||
|
public HashSet<ICommonSession> AllMembers = [];
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Time left in the raffle in seconds. This must be initialized to a positive value.
|
||||||
|
/// </summary>
|
||||||
|
[ViewVariables(VVAccess.ReadOnly)]
|
||||||
|
[DataField]
|
||||||
|
public TimeSpan Countdown = TimeSpan.MaxValue;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// The cumulative time, i.e. how much time the raffle will take in total. Added to when the time is extended
|
||||||
|
/// by someone joining the raffle.
|
||||||
|
/// Must be set to the same value as <see cref="Countdown"/> on initialization.
|
||||||
|
/// </summary>
|
||||||
|
[ViewVariables(VVAccess.ReadOnly)]
|
||||||
|
[DataField("cumulativeTime")]
|
||||||
|
public TimeSpan CumulativeTime = TimeSpan.MaxValue;
|
||||||
|
|
||||||
|
/// <inheritdoc cref="GhostRoleRaffleSettings.JoinExtendsDurationBy"/>
|
||||||
|
[ViewVariables(VVAccess.ReadOnly)]
|
||||||
|
[DataField("joinExtendsDurationBy")]
|
||||||
|
public TimeSpan JoinExtendsDurationBy { get; set; }
|
||||||
|
|
||||||
|
/// <inheritdoc cref="GhostRoleRaffleSettings.MaxDuration"/>
|
||||||
|
[ViewVariables(VVAccess.ReadOnly)]
|
||||||
|
[DataField("maxDuration")]
|
||||||
|
public TimeSpan MaxDuration { get; set; }
|
||||||
|
}
|
||||||
@@ -1,10 +1,14 @@
|
|||||||
using Content.Server._Miracle.GulagSystem;
|
using Content.Server._Miracle.GulagSystem;
|
||||||
|
using System.Linq;
|
||||||
using Content.Server.Administration.Logs;
|
using Content.Server.Administration.Logs;
|
||||||
using Content.Server.EUI;
|
using Content.Server.EUI;
|
||||||
using Content.Server.Ghost.Roles.Components;
|
using Content.Server.Ghost.Roles.Components;
|
||||||
using Content.Server.Ghost.Roles.Events;
|
using Content.Server.Ghost.Roles.Events;
|
||||||
|
using Content.Server.Ghost.Roles.Raffles;
|
||||||
|
using Content.Shared.Ghost.Roles.Raffles;
|
||||||
using Content.Server.Ghost.Roles.UI;
|
using Content.Server.Ghost.Roles.UI;
|
||||||
using Content.Server.Mind.Commands;
|
using Content.Server.Mind.Commands;
|
||||||
|
using Content.Server.Popups;
|
||||||
using Content.Shared.Administration;
|
using Content.Shared.Administration;
|
||||||
using Content.Shared.Database;
|
using Content.Shared.Database;
|
||||||
using Content.Shared.Follower;
|
using Content.Shared.Follower;
|
||||||
@@ -22,7 +26,9 @@ using Robust.Server.Player;
|
|||||||
using Robust.Shared.Console;
|
using Robust.Shared.Console;
|
||||||
using Robust.Shared.Enums;
|
using Robust.Shared.Enums;
|
||||||
using Robust.Shared.Player;
|
using Robust.Shared.Player;
|
||||||
|
using Robust.Shared.Prototypes;
|
||||||
using Robust.Shared.Random;
|
using Robust.Shared.Random;
|
||||||
|
using Robust.Shared.Timing;
|
||||||
using Robust.Shared.Utility;
|
using Robust.Shared.Utility;
|
||||||
|
|
||||||
namespace Content.Server.Ghost.Roles
|
namespace Content.Server.Ghost.Roles
|
||||||
@@ -39,11 +45,16 @@ namespace Content.Server.Ghost.Roles
|
|||||||
[Dependency] private readonly SharedMindSystem _mindSystem = default!;
|
[Dependency] private readonly SharedMindSystem _mindSystem = default!;
|
||||||
[Dependency] private readonly SharedRoleSystem _roleSystem = default!;
|
[Dependency] private readonly SharedRoleSystem _roleSystem = default!;
|
||||||
[Dependency] private readonly GulagSystem _gulagSystem = default!;
|
[Dependency] private readonly GulagSystem _gulagSystem = default!;
|
||||||
|
[Dependency] private readonly IGameTiming _timing = default!;
|
||||||
|
[Dependency] private readonly PopupSystem _popupSystem = default!;
|
||||||
|
[Dependency] private readonly IPrototypeManager _prototype = default!;
|
||||||
|
|
||||||
private uint _nextRoleIdentifier;
|
private uint _nextRoleIdentifier;
|
||||||
private bool _needsUpdateGhostRoleCount = true;
|
private bool _needsUpdateGhostRoleCount = true;
|
||||||
|
|
||||||
private readonly Dictionary<uint, Entity<GhostRoleComponent>> _ghostRoles = new();
|
private readonly Dictionary<uint, Entity<GhostRoleComponent>> _ghostRoles = new();
|
||||||
|
private readonly Dictionary<uint, Entity<GhostRoleRaffleComponent>> _ghostRoleRaffles = new();
|
||||||
|
|
||||||
private readonly Dictionary<ICommonSession, GhostRolesEui> _openUis = new();
|
private readonly Dictionary<ICommonSession, GhostRolesEui> _openUis = new();
|
||||||
private readonly Dictionary<ICommonSession, MakeGhostRoleEui> _openMakeGhostRoleUis = new();
|
private readonly Dictionary<ICommonSession, MakeGhostRoleEui> _openMakeGhostRoleUis = new();
|
||||||
|
|
||||||
@@ -60,10 +71,12 @@ namespace Content.Server.Ghost.Roles
|
|||||||
SubscribeLocalEvent<GhostTakeoverAvailableComponent, MindRemovedMessage>(OnMindRemoved);
|
SubscribeLocalEvent<GhostTakeoverAvailableComponent, MindRemovedMessage>(OnMindRemoved);
|
||||||
SubscribeLocalEvent<GhostTakeoverAvailableComponent, MobStateChangedEvent>(OnMobStateChanged);
|
SubscribeLocalEvent<GhostTakeoverAvailableComponent, MobStateChangedEvent>(OnMobStateChanged);
|
||||||
SubscribeLocalEvent<GhostRoleComponent, MapInitEvent>(OnMapInit);
|
SubscribeLocalEvent<GhostRoleComponent, MapInitEvent>(OnMapInit);
|
||||||
SubscribeLocalEvent<GhostRoleComponent, ComponentStartup>(OnStartup);
|
SubscribeLocalEvent<GhostRoleComponent, ComponentStartup>(OnRoleStartup);
|
||||||
SubscribeLocalEvent<GhostRoleComponent, ComponentShutdown>(OnShutdown);
|
SubscribeLocalEvent<GhostRoleComponent, ComponentShutdown>(OnRoleShutdown);
|
||||||
SubscribeLocalEvent<GhostRoleComponent, EntityPausedEvent>(OnPaused);
|
SubscribeLocalEvent<GhostRoleComponent, EntityPausedEvent>(OnPaused);
|
||||||
SubscribeLocalEvent<GhostRoleComponent, EntityUnpausedEvent>(OnUnpaused);
|
SubscribeLocalEvent<GhostRoleComponent, EntityUnpausedEvent>(OnUnpaused);
|
||||||
|
SubscribeLocalEvent<GhostRoleRaffleComponent, ComponentInit>(OnRaffleInit);
|
||||||
|
SubscribeLocalEvent<GhostRoleRaffleComponent, ComponentShutdown>(OnRaffleShutdown);
|
||||||
SubscribeLocalEvent<GhostRoleMobSpawnerComponent, TakeGhostRoleEvent>(OnSpawnerTakeRole);
|
SubscribeLocalEvent<GhostRoleMobSpawnerComponent, TakeGhostRoleEvent>(OnSpawnerTakeRole);
|
||||||
SubscribeLocalEvent<GhostTakeoverAvailableComponent, TakeGhostRoleEvent>(OnTakeoverTakeRole);
|
SubscribeLocalEvent<GhostTakeoverAvailableComponent, TakeGhostRoleEvent>(OnTakeoverTakeRole);
|
||||||
_playerManager.PlayerStatusChanged += PlayerStatusChanged;
|
_playerManager.PlayerStatusChanged += PlayerStatusChanged;
|
||||||
@@ -161,17 +174,118 @@ namespace Content.Server.Ghost.Roles
|
|||||||
public override void Update(float frameTime)
|
public override void Update(float frameTime)
|
||||||
{
|
{
|
||||||
base.Update(frameTime);
|
base.Update(frameTime);
|
||||||
if (_needsUpdateGhostRoleCount)
|
|
||||||
|
UpdateGhostRoleCount();
|
||||||
|
UpdateRaffles(frameTime);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Handles sending count update for the ghost role button in ghost UI, if ghost role count changed.
|
||||||
|
/// </summary>
|
||||||
|
private void UpdateGhostRoleCount()
|
||||||
|
{
|
||||||
|
if (!_needsUpdateGhostRoleCount)
|
||||||
|
return;
|
||||||
|
|
||||||
|
_needsUpdateGhostRoleCount = false;
|
||||||
|
var response = new GhostUpdateGhostRoleCountEvent(GetGhostRoleCount());
|
||||||
|
foreach (var player in _playerManager.Sessions)
|
||||||
{
|
{
|
||||||
_needsUpdateGhostRoleCount = false;
|
RaiseNetworkEvent(response, player.Channel);
|
||||||
var response = new GhostUpdateGhostRoleCountEvent(GetGhostRolesInfo().Length);
|
|
||||||
foreach (var player in _playerManager.Sessions)
|
|
||||||
{
|
|
||||||
RaiseNetworkEvent(response, player.Channel);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Handles ghost role raffle logic.
|
||||||
|
/// </summary>
|
||||||
|
private void UpdateRaffles(float frameTime)
|
||||||
|
{
|
||||||
|
var query = EntityQueryEnumerator<GhostRoleRaffleComponent, MetaDataComponent>();
|
||||||
|
while (query.MoveNext(out var entityUid, out var raffle, out var meta))
|
||||||
|
{
|
||||||
|
if (meta.EntityPaused)
|
||||||
|
continue;
|
||||||
|
|
||||||
|
// if all participants leave/were removed from the raffle, the raffle is canceled.
|
||||||
|
if (raffle.CurrentMembers.Count == 0)
|
||||||
|
{
|
||||||
|
RemoveRaffleAndUpdateEui(entityUid, raffle);
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
raffle.Countdown = raffle.Countdown.Subtract(TimeSpan.FromSeconds(frameTime));
|
||||||
|
if (raffle.Countdown.Ticks > 0)
|
||||||
|
continue;
|
||||||
|
|
||||||
|
// the raffle is over! find someone to take over the ghost role
|
||||||
|
if (!TryComp(entityUid, out GhostRoleComponent? ghostRole))
|
||||||
|
{
|
||||||
|
Log.Warning($"Ghost role raffle finished on {entityUid} but {nameof(GhostRoleComponent)} is missing");
|
||||||
|
RemoveRaffleAndUpdateEui(entityUid, raffle);
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (ghostRole.RaffleConfig is null)
|
||||||
|
{
|
||||||
|
Log.Warning($"Ghost role raffle finished on {entityUid} but RaffleConfig became null");
|
||||||
|
RemoveRaffleAndUpdateEui(entityUid, raffle);
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
var foundWinner = false;
|
||||||
|
var deciderPrototype = _prototype.Index(ghostRole.RaffleConfig.Decider);
|
||||||
|
|
||||||
|
// use the ghost role's chosen winner picker to find a winner
|
||||||
|
deciderPrototype.Decider.PickWinner(
|
||||||
|
raffle.CurrentMembers.AsEnumerable(),
|
||||||
|
session =>
|
||||||
|
{
|
||||||
|
var success = TryTakeover(session, raffle.Identifier);
|
||||||
|
foundWinner |= success;
|
||||||
|
return success;
|
||||||
|
}
|
||||||
|
);
|
||||||
|
|
||||||
|
if (!foundWinner)
|
||||||
|
{
|
||||||
|
Log.Warning($"Ghost role raffle for {entityUid} ({ghostRole.RoleName}) finished without " +
|
||||||
|
$"{ghostRole.RaffleConfig?.Decider} finding a winner");
|
||||||
|
}
|
||||||
|
|
||||||
|
// raffle over
|
||||||
|
RemoveRaffleAndUpdateEui(entityUid, raffle);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private bool TryTakeover(ICommonSession player, uint identifier)
|
||||||
|
{
|
||||||
|
// TODO: the following two checks are kind of redundant since they should already be removed
|
||||||
|
// from the raffle
|
||||||
|
// can't win if you are disconnected (although you shouldn't be a candidate anyway)
|
||||||
|
if (player.Status != SessionStatus.InGame)
|
||||||
|
return false;
|
||||||
|
|
||||||
|
// can't win if you are no longer a ghost (e.g. if you returned to your body)
|
||||||
|
if (player.AttachedEntity == null || !HasComp<GhostComponent>(player.AttachedEntity))
|
||||||
|
return false;
|
||||||
|
|
||||||
|
if (Takeover(player, identifier))
|
||||||
|
{
|
||||||
|
// takeover successful, we have a winner! remove the winner from other raffles they might be in
|
||||||
|
LeaveAllRaffles(player);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
private void RemoveRaffleAndUpdateEui(EntityUid entityUid, GhostRoleRaffleComponent raffle)
|
||||||
|
{
|
||||||
|
_ghostRoleRaffles.Remove(raffle.Identifier);
|
||||||
|
RemComp(entityUid, raffle);
|
||||||
|
UpdateAllEui();
|
||||||
|
}
|
||||||
|
|
||||||
private void PlayerStatusChanged(object? blah, SessionStatusEventArgs args)
|
private void PlayerStatusChanged(object? blah, SessionStatusEventArgs args)
|
||||||
{
|
{
|
||||||
if (args.NewStatus == SessionStatus.InGame)
|
if (args.NewStatus == SessionStatus.InGame)
|
||||||
@@ -179,6 +293,11 @@ namespace Content.Server.Ghost.Roles
|
|||||||
var response = new GhostUpdateGhostRoleCountEvent(_ghostRoles.Count);
|
var response = new GhostUpdateGhostRoleCountEvent(_ghostRoles.Count);
|
||||||
RaiseNetworkEvent(response, args.Session.Channel);
|
RaiseNetworkEvent(response, args.Session.Channel);
|
||||||
}
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
// people who disconnect are removed from ghost role raffles
|
||||||
|
LeaveAllRaffles(args.Session);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public void RegisterGhostRole(Entity<GhostRoleComponent> role)
|
public void RegisterGhostRole(Entity<GhostRoleComponent> role)
|
||||||
@@ -197,12 +316,65 @@ namespace Content.Server.Ghost.Roles
|
|||||||
return;
|
return;
|
||||||
|
|
||||||
_ghostRoles.Remove(comp.Identifier);
|
_ghostRoles.Remove(comp.Identifier);
|
||||||
UpdateAllEui();
|
if (TryComp(role.Owner, out GhostRoleRaffleComponent? raffle))
|
||||||
|
{
|
||||||
|
// if a raffle is still running, get rid of it
|
||||||
|
RemoveRaffleAndUpdateEui(role.Owner, raffle);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
UpdateAllEui();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public void Takeover(ICommonSession player, uint identifier)
|
// probably fine to be init because it's never added during entity initialization, but much later
|
||||||
|
private void OnRaffleInit(Entity<GhostRoleRaffleComponent> ent, ref ComponentInit args)
|
||||||
{
|
{
|
||||||
if (!_ghostRoles.TryGetValue(identifier, out var role))
|
if (!TryComp(ent, out GhostRoleComponent? ghostRole))
|
||||||
|
{
|
||||||
|
// can't have a raffle for a ghost role that doesn't exist
|
||||||
|
RemComp<GhostRoleRaffleComponent>(ent);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
var config = ghostRole.RaffleConfig;
|
||||||
|
if (config is null)
|
||||||
|
return; // should, realistically, never be reached but you never know
|
||||||
|
|
||||||
|
var settings = config.SettingsOverride
|
||||||
|
?? _prototype.Index<GhostRoleRaffleSettingsPrototype>(config.Settings).Settings;
|
||||||
|
|
||||||
|
if (settings.MaxDuration < settings.InitialDuration)
|
||||||
|
{
|
||||||
|
Log.Error($"Ghost role on {ent} has invalid raffle settings (max duration shorter than initial)");
|
||||||
|
ghostRole.RaffleConfig = null; // make it a non-raffle role so stuff isn't entirely broken
|
||||||
|
RemComp<GhostRoleRaffleComponent>(ent);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
var raffle = ent.Comp;
|
||||||
|
raffle.Identifier = ghostRole.Identifier;
|
||||||
|
raffle.Countdown = TimeSpan.FromSeconds(settings.InitialDuration);
|
||||||
|
raffle.CumulativeTime = TimeSpan.FromSeconds(settings.InitialDuration);
|
||||||
|
// we copy these settings into the component because they would be cumbersome to access otherwise
|
||||||
|
raffle.JoinExtendsDurationBy = TimeSpan.FromSeconds(settings.JoinExtendsDurationBy);
|
||||||
|
raffle.MaxDuration = TimeSpan.FromSeconds(settings.MaxDuration);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void OnRaffleShutdown(Entity<GhostRoleRaffleComponent> ent, ref ComponentShutdown args)
|
||||||
|
{
|
||||||
|
_ghostRoleRaffles.Remove(ent.Comp.Identifier);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Joins the given player onto a ghost role raffle, or creates it if it doesn't exist.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="player">The player.</param>
|
||||||
|
/// <param name="identifier">The ID that represents the ghost role or ghost role raffle.
|
||||||
|
/// (A raffle will have the same ID as the ghost role it's for.)</param>
|
||||||
|
private void JoinRaffle(ICommonSession player, uint identifier)
|
||||||
|
{
|
||||||
|
if (!_ghostRoles.TryGetValue(identifier, out var roleEnt))
|
||||||
return;
|
return;
|
||||||
|
|
||||||
if (_gulagSystem.IsUserGulagged(player.UserId, out _))
|
if (_gulagSystem.IsUserGulagged(player.UserId, out _))
|
||||||
@@ -210,16 +382,114 @@ namespace Content.Server.Ghost.Roles
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// get raffle or create a new one if it doesn't exist
|
||||||
|
var raffle = _ghostRoleRaffles.TryGetValue(identifier, out var raffleEnt)
|
||||||
|
? raffleEnt.Comp
|
||||||
|
: EnsureComp<GhostRoleRaffleComponent>(roleEnt.Owner);
|
||||||
|
|
||||||
|
_ghostRoleRaffles.TryAdd(identifier, (roleEnt.Owner, raffle));
|
||||||
|
|
||||||
|
if (!raffle.CurrentMembers.Add(player))
|
||||||
|
{
|
||||||
|
Log.Warning($"{player.Name} tried to join raffle for ghost role {identifier} but they are already in the raffle");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// if this is the first time the player joins this raffle, and the player wasn't the starter of the raffle:
|
||||||
|
// extend the countdown, but only if doing so will not make the raffle take longer than the maximum
|
||||||
|
// duration
|
||||||
|
if (raffle.AllMembers.Add(player) && raffle.AllMembers.Count > 1
|
||||||
|
&& raffle.CumulativeTime.Add(raffle.JoinExtendsDurationBy) <= raffle.MaxDuration)
|
||||||
|
{
|
||||||
|
raffle.Countdown += raffle.JoinExtendsDurationBy;
|
||||||
|
raffle.CumulativeTime += raffle.JoinExtendsDurationBy;
|
||||||
|
}
|
||||||
|
|
||||||
|
UpdateAllEui();
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Makes the given player leave the raffle corresponding to the given ID.
|
||||||
|
/// </summary>
|
||||||
|
public void LeaveRaffle(ICommonSession player, uint identifier)
|
||||||
|
{
|
||||||
|
if (!_ghostRoleRaffles.TryGetValue(identifier, out var raffleEnt))
|
||||||
|
return;
|
||||||
|
|
||||||
|
if (raffleEnt.Comp.CurrentMembers.Remove(player))
|
||||||
|
{
|
||||||
|
UpdateAllEui();
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
Log.Warning($"{player.Name} tried to leave raffle for ghost role {identifier} but they are not in the raffle");
|
||||||
|
}
|
||||||
|
|
||||||
|
// (raffle ending because all players left is handled in update())
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Makes the given player leave all ghost role raffles.
|
||||||
|
/// </summary>
|
||||||
|
public void LeaveAllRaffles(ICommonSession player)
|
||||||
|
{
|
||||||
|
var shouldUpdateEui = false;
|
||||||
|
|
||||||
|
foreach (var raffleEnt in _ghostRoleRaffles.Values)
|
||||||
|
{
|
||||||
|
shouldUpdateEui |= raffleEnt.Comp.CurrentMembers.Remove(player);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (shouldUpdateEui)
|
||||||
|
UpdateAllEui();
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Request a ghost role. If it's a raffled role starts or joins a raffle, otherwise the player immediately
|
||||||
|
/// takes over the ghost role if possible.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="player">The player.</param>
|
||||||
|
/// <param name="identifier">ID of the ghost role.</param>
|
||||||
|
public void Request(ICommonSession player, uint identifier)
|
||||||
|
{
|
||||||
|
if (!_ghostRoles.TryGetValue(identifier, out var roleEnt))
|
||||||
|
return;
|
||||||
|
|
||||||
|
if (roleEnt.Comp.RaffleConfig is not null)
|
||||||
|
{
|
||||||
|
JoinRaffle(player, identifier);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
Takeover(player, identifier);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Attempts having the player take over the ghost role with the corresponding ID. Does not start a raffle.
|
||||||
|
/// </summary>
|
||||||
|
/// <returns>True if takeover was successful, otherwise false.</returns>
|
||||||
|
public bool Takeover(ICommonSession player, uint identifier)
|
||||||
|
{
|
||||||
|
if (!_ghostRoles.TryGetValue(identifier, out var role))
|
||||||
|
return false;
|
||||||
|
|
||||||
|
if (_gulagSystem.IsUserGulagged(player.UserId, out _))
|
||||||
|
{
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
var ev = new TakeGhostRoleEvent(player);
|
var ev = new TakeGhostRoleEvent(player);
|
||||||
RaiseLocalEvent(role, ref ev);
|
RaiseLocalEvent(role, ref ev);
|
||||||
|
|
||||||
if (!ev.TookRole)
|
if (!ev.TookRole)
|
||||||
return;
|
return false;
|
||||||
|
|
||||||
if (player.AttachedEntity != null)
|
if (player.AttachedEntity != null)
|
||||||
_adminLogger.Add(LogType.GhostRoleTaken, LogImpact.Low, $"{player:player} took the {role.Comp.RoleName:roleName} ghost role {ToPrettyString(player.AttachedEntity.Value):entity}");
|
_adminLogger.Add(LogType.GhostRoleTaken, LogImpact.Low, $"{player:player} took the {role.Comp.RoleName:roleName} ghost role {ToPrettyString(player.AttachedEntity.Value):entity}");
|
||||||
|
|
||||||
CloseEui(player);
|
CloseEui(player);
|
||||||
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
public void Follow(ICommonSession player, uint identifier)
|
public void Follow(ICommonSession player, uint identifier)
|
||||||
@@ -248,7 +518,22 @@ namespace Content.Server.Ghost.Roles
|
|||||||
_mindSystem.TransferTo(newMind, mob);
|
_mindSystem.TransferTo(newMind, mob);
|
||||||
}
|
}
|
||||||
|
|
||||||
public GhostRoleInfo[] GetGhostRolesInfo()
|
/// <summary>
|
||||||
|
/// Returns the number of available ghost roles.
|
||||||
|
/// </summary>
|
||||||
|
public int GetGhostRoleCount()
|
||||||
|
{
|
||||||
|
var metaQuery = GetEntityQuery<MetaDataComponent>();
|
||||||
|
return _ghostRoles.Count(pair => metaQuery.GetComponent(pair.Value.Owner).EntityPaused == false);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Returns information about all available ghost roles.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="player">
|
||||||
|
/// If not null, the <see cref="GhostRoleInfo"/>s will show if the given player is in a raffle.
|
||||||
|
/// </param>
|
||||||
|
public GhostRoleInfo[] GetGhostRolesInfo(ICommonSession? player)
|
||||||
{
|
{
|
||||||
var roles = new List<GhostRoleInfo>();
|
var roles = new List<GhostRoleInfo>();
|
||||||
var metaQuery = GetEntityQuery<MetaDataComponent>();
|
var metaQuery = GetEntityQuery<MetaDataComponent>();
|
||||||
@@ -258,7 +543,39 @@ namespace Content.Server.Ghost.Roles
|
|||||||
if (metaQuery.GetComponent(uid).EntityPaused)
|
if (metaQuery.GetComponent(uid).EntityPaused)
|
||||||
continue;
|
continue;
|
||||||
|
|
||||||
roles.Add(new GhostRoleInfo {Identifier = id, Name = role.RoleName, Description = role.RoleDescription, Rules = role.RoleRules, Requirements = role.Requirements});
|
var kind = GhostRoleKind.FirstComeFirstServe;
|
||||||
|
GhostRoleRaffleComponent? raffle = null;
|
||||||
|
|
||||||
|
if (role.RaffleConfig is not null)
|
||||||
|
{
|
||||||
|
kind = GhostRoleKind.RaffleReady;
|
||||||
|
|
||||||
|
if (_ghostRoleRaffles.TryGetValue(id, out var raffleEnt))
|
||||||
|
{
|
||||||
|
kind = GhostRoleKind.RaffleInProgress;
|
||||||
|
raffle = raffleEnt.Comp;
|
||||||
|
|
||||||
|
if (player is not null && raffle.CurrentMembers.Contains(player))
|
||||||
|
kind = GhostRoleKind.RaffleJoined;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
var rafflePlayerCount = (uint?) raffle?.CurrentMembers.Count ?? 0;
|
||||||
|
var raffleEndTime = raffle is not null
|
||||||
|
? _timing.CurTime.Add(raffle.Countdown)
|
||||||
|
: TimeSpan.MinValue;
|
||||||
|
|
||||||
|
roles.Add(new GhostRoleInfo
|
||||||
|
{
|
||||||
|
Identifier = id,
|
||||||
|
Name = role.RoleName,
|
||||||
|
Description = role.RoleDescription,
|
||||||
|
Rules = role.RoleRules,
|
||||||
|
Requirements = role.Requirements,
|
||||||
|
Kind = kind,
|
||||||
|
RafflePlayerCount = rafflePlayerCount,
|
||||||
|
RaffleEndTime = raffleEndTime
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
return roles.ToArray();
|
return roles.ToArray();
|
||||||
@@ -273,6 +590,10 @@ namespace Content.Server.Ghost.Roles
|
|||||||
if (HasComp<GhostComponent>(message.Entity))
|
if (HasComp<GhostComponent>(message.Entity))
|
||||||
return;
|
return;
|
||||||
|
|
||||||
|
// The player is not a ghost (anymore), so they should not be in any raffles. Remove them.
|
||||||
|
// This ensures player doesn't win a raffle after returning to their (revived) body and ends up being
|
||||||
|
// forced into a ghost role.
|
||||||
|
LeaveAllRaffles(message.Player);
|
||||||
CloseEui(message.Player);
|
CloseEui(message.Player);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -307,6 +628,7 @@ namespace Content.Server.Ghost.Roles
|
|||||||
|
|
||||||
_openUis.Clear();
|
_openUis.Clear();
|
||||||
_ghostRoles.Clear();
|
_ghostRoles.Clear();
|
||||||
|
_ghostRoleRaffles.Clear();
|
||||||
_nextRoleIdentifier = 0;
|
_nextRoleIdentifier = 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -332,12 +654,12 @@ namespace Content.Server.Ghost.Roles
|
|||||||
RemCompDeferred<GhostRoleComponent>(ent);
|
RemCompDeferred<GhostRoleComponent>(ent);
|
||||||
}
|
}
|
||||||
|
|
||||||
private void OnStartup(Entity<GhostRoleComponent> ent, ref ComponentStartup args)
|
private void OnRoleStartup(Entity<GhostRoleComponent> ent, ref ComponentStartup args)
|
||||||
{
|
{
|
||||||
RegisterGhostRole(ent);
|
RegisterGhostRole(ent);
|
||||||
}
|
}
|
||||||
|
|
||||||
private void OnShutdown(Entity<GhostRoleComponent> role, ref ComponentShutdown args)
|
private void OnRoleShutdown(Entity<GhostRoleComponent> role, ref ComponentShutdown args)
|
||||||
{
|
{
|
||||||
UnregisterGhostRole(role);
|
UnregisterGhostRole(role);
|
||||||
}
|
}
|
||||||
|
|||||||
127
Content.Server/Ghost/Roles/MakeRaffledGhostRoleCommand.cs
Normal file
127
Content.Server/Ghost/Roles/MakeRaffledGhostRoleCommand.cs
Normal file
@@ -0,0 +1,127 @@
|
|||||||
|
using System.Linq;
|
||||||
|
using Content.Server.Administration;
|
||||||
|
using Content.Server.Ghost.Roles.Components;
|
||||||
|
using Content.Server.Ghost.Roles.Raffles;
|
||||||
|
using Content.Shared.Administration;
|
||||||
|
using Content.Shared.Ghost.Roles.Raffles;
|
||||||
|
using Content.Shared.Mind.Components;
|
||||||
|
using Robust.Shared.Console;
|
||||||
|
using Robust.Shared.Prototypes;
|
||||||
|
|
||||||
|
namespace Content.Server.Ghost.Roles
|
||||||
|
{
|
||||||
|
[AdminCommand(AdminFlags.Admin)]
|
||||||
|
public sealed class MakeRaffledGhostRoleCommand : IConsoleCommand
|
||||||
|
{
|
||||||
|
[Dependency] private readonly IPrototypeManager _protoManager = default!;
|
||||||
|
[Dependency] private readonly IEntityManager _entManager = default!;
|
||||||
|
|
||||||
|
public string Command => "makeghostroleraffled";
|
||||||
|
public string Description => "Turns an entity into a raffled ghost role.";
|
||||||
|
public string Help => $"Usage: {Command} <entity uid> <name> <description> (<settings prototype> | <initial duration> <extend by> <max duration>) [<rules>]\n" +
|
||||||
|
$"Durations are in seconds.";
|
||||||
|
|
||||||
|
public void Execute(IConsoleShell shell, string argStr, string[] args)
|
||||||
|
{
|
||||||
|
if (args.Length is < 4 or > 7)
|
||||||
|
{
|
||||||
|
shell.WriteLine($"Invalid amount of arguments.\n{Help}");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!NetEntity.TryParse(args[0], out var uidNet) || !_entManager.TryGetEntity(uidNet, out var uid))
|
||||||
|
{
|
||||||
|
shell.WriteLine($"{args[0]} is not a valid entity uid.");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!_entManager.TryGetComponent(uid, out MetaDataComponent? metaData))
|
||||||
|
{
|
||||||
|
shell.WriteLine($"No entity found with uid {uid}");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (_entManager.TryGetComponent(uid, out MindContainerComponent? mind) &&
|
||||||
|
mind.HasMind)
|
||||||
|
{
|
||||||
|
shell.WriteLine($"Entity {metaData.EntityName} with id {uid} already has a mind.");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (_entManager.TryGetComponent(uid, out GhostRoleComponent? ghostRole))
|
||||||
|
{
|
||||||
|
shell.WriteLine($"Entity {metaData.EntityName} with id {uid} already has a {nameof(GhostRoleComponent)}");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (_entManager.HasComponent<GhostTakeoverAvailableComponent>(uid))
|
||||||
|
{
|
||||||
|
shell.WriteLine($"Entity {metaData.EntityName} with id {uid} already has a {nameof(GhostTakeoverAvailableComponent)}");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
var name = args[1];
|
||||||
|
var description = args[2];
|
||||||
|
|
||||||
|
// if the rules are specified then use those, otherwise use the default
|
||||||
|
var rules = args.Length switch
|
||||||
|
{
|
||||||
|
5 => args[4],
|
||||||
|
7 => args[6],
|
||||||
|
_ => Loc.GetString("ghost-role-component-default-rules"),
|
||||||
|
};
|
||||||
|
|
||||||
|
// is it an invocation with a prototype ID and optional rules?
|
||||||
|
var isProto = args.Length is 4 or 5;
|
||||||
|
GhostRoleRaffleSettings settings;
|
||||||
|
|
||||||
|
if (isProto)
|
||||||
|
{
|
||||||
|
if (!_protoManager.TryIndex<GhostRoleRaffleSettingsPrototype>(args[4], out var proto))
|
||||||
|
{
|
||||||
|
var validProtos = string.Join(", ",
|
||||||
|
_protoManager.EnumeratePrototypes<GhostRoleRaffleSettingsPrototype>().Select(p => p.ID)
|
||||||
|
);
|
||||||
|
|
||||||
|
shell.WriteLine($"{args[4]} is not a valid raffle settings prototype. Valid options: {validProtos}");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
settings = proto.Settings;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
if (!uint.TryParse(args[3], out var initial)
|
||||||
|
|| !uint.TryParse(args[4], out var extends)
|
||||||
|
|| !uint.TryParse(args[5], out var max)
|
||||||
|
|| initial == 0 || max == 0)
|
||||||
|
{
|
||||||
|
shell.WriteLine($"The raffle initial/extends/max settings must be positive numbers.");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (initial > max)
|
||||||
|
{
|
||||||
|
shell.WriteLine("The initial duration must be smaller than or equal to the maximum duration.");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
settings = new GhostRoleRaffleSettings()
|
||||||
|
{
|
||||||
|
InitialDuration = initial,
|
||||||
|
JoinExtendsDurationBy = extends,
|
||||||
|
MaxDuration = max
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
ghostRole = _entManager.AddComponent<GhostRoleComponent>(uid.Value);
|
||||||
|
_entManager.AddComponent<GhostTakeoverAvailableComponent>(uid.Value);
|
||||||
|
ghostRole.RoleName = name;
|
||||||
|
ghostRole.RoleDescription = description;
|
||||||
|
ghostRole.RoleRules = rules;
|
||||||
|
ghostRole.RaffleConfig = new GhostRoleRaffleConfig(settings);
|
||||||
|
|
||||||
|
shell.WriteLine($"Made entity {metaData.EntityName} a raffled ghost role.");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
35
Content.Server/Ghost/Roles/Raffles/GhostRoleRaffleConfig.cs
Normal file
35
Content.Server/Ghost/Roles/Raffles/GhostRoleRaffleConfig.cs
Normal file
@@ -0,0 +1,35 @@
|
|||||||
|
using Content.Shared.Ghost.Roles.Raffles;
|
||||||
|
using Robust.Shared.Prototypes;
|
||||||
|
|
||||||
|
namespace Content.Server.Ghost.Roles.Raffles;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Raffle configuration.
|
||||||
|
/// </summary>
|
||||||
|
[DataDefinition]
|
||||||
|
public sealed partial class GhostRoleRaffleConfig
|
||||||
|
{
|
||||||
|
public GhostRoleRaffleConfig(GhostRoleRaffleSettings settings)
|
||||||
|
{
|
||||||
|
SettingsOverride = settings;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Specifies the raffle settings to use.
|
||||||
|
/// </summary>
|
||||||
|
[DataField("settings", required: true)]
|
||||||
|
public ProtoId<GhostRoleRaffleSettingsPrototype> Settings { get; set; } = "default";
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// If not null, the settings from <see cref="Settings"/> are ignored and these settings are used instead.
|
||||||
|
/// Intended for allowing admins to set custom raffle settings for admeme ghost roles.
|
||||||
|
/// </summary>
|
||||||
|
[ViewVariables(VVAccess.ReadOnly)]
|
||||||
|
public GhostRoleRaffleSettings? SettingsOverride { get; set; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Sets which <see cref="IGhostRoleRaffleDecider"/> is used.
|
||||||
|
/// </summary>
|
||||||
|
[DataField("decider")]
|
||||||
|
public ProtoId<GhostRoleRaffleDeciderPrototype> Decider { get; set; } = "default";
|
||||||
|
}
|
||||||
@@ -0,0 +1,20 @@
|
|||||||
|
using Robust.Shared.Prototypes;
|
||||||
|
|
||||||
|
namespace Content.Server.Ghost.Roles.Raffles;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Allows getting a <see cref="IGhostRoleRaffleDecider"/> as prototype.
|
||||||
|
/// </summary>
|
||||||
|
[Prototype("ghostRoleRaffleDecider")]
|
||||||
|
public sealed class GhostRoleRaffleDeciderPrototype : IPrototype
|
||||||
|
{
|
||||||
|
/// <inheritdoc />
|
||||||
|
[IdDataField]
|
||||||
|
public string ID { get; private set; } = default!;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// The <see cref="IGhostRoleRaffleDecider"/> instance that chooses the winner of a raffle.
|
||||||
|
/// </summary>
|
||||||
|
[DataField("decider", required: true)]
|
||||||
|
public IGhostRoleRaffleDecider Decider { get; private set; } = new RngGhostRoleRaffleDecider();
|
||||||
|
}
|
||||||
@@ -0,0 +1,28 @@
|
|||||||
|
using Robust.Shared.Player;
|
||||||
|
|
||||||
|
namespace Content.Server.Ghost.Roles.Raffles;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Chooses a winner of a ghost role raffle.
|
||||||
|
/// </summary>
|
||||||
|
[ImplicitDataDefinitionForInheritors]
|
||||||
|
public partial interface IGhostRoleRaffleDecider
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Chooses a winner of a ghost role raffle draw from the given pool of candidates.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="candidates">The players in the session at the time of drawing.</param>
|
||||||
|
/// <param name="tryTakeover">
|
||||||
|
/// Call this with the chosen winner as argument.
|
||||||
|
/// <ul><li>If <c>true</c> is returned, your winner was able to take over the ghost role, and the drawing is complete.
|
||||||
|
/// <b>Do not call <see cref="tryTakeover"/> again after true is returned.</b></li>
|
||||||
|
/// <li>If <c>false</c> is returned, your winner was not able to take over the ghost role,
|
||||||
|
/// and you must choose another winner, and call <see cref="tryTakeover"/> with the new winner as argument.</li>
|
||||||
|
/// </ul>
|
||||||
|
///
|
||||||
|
/// If <see cref="tryTakeover"/> is not called, or only returns false, the raffle will end without a winner.
|
||||||
|
/// Do not call <see cref="tryTakeover"/> with the same player several times.
|
||||||
|
/// </param>
|
||||||
|
void PickWinner(IEnumerable<ICommonSession> candidates, Func<ICommonSession, bool> tryTakeover);
|
||||||
|
}
|
||||||
|
|
||||||
@@ -0,0 +1,27 @@
|
|||||||
|
using System.Linq;
|
||||||
|
using JetBrains.Annotations;
|
||||||
|
using Robust.Shared.Player;
|
||||||
|
using Robust.Shared.Random;
|
||||||
|
|
||||||
|
namespace Content.Server.Ghost.Roles.Raffles;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Chooses the winner of a ghost role raffle entirely randomly, without any weighting.
|
||||||
|
/// </summary>
|
||||||
|
[UsedImplicitly(ImplicitUseTargetFlags.WithMembers)]
|
||||||
|
public sealed partial class RngGhostRoleRaffleDecider : IGhostRoleRaffleDecider
|
||||||
|
{
|
||||||
|
public void PickWinner(IEnumerable<ICommonSession> candidates, Func<ICommonSession, bool> tryTakeover)
|
||||||
|
{
|
||||||
|
var random = IoCManager.Resolve<IRobustRandom>();
|
||||||
|
|
||||||
|
var choices = candidates.ToList();
|
||||||
|
random.Shuffle(choices); // shuffle the list so we can pick a lucky winner!
|
||||||
|
|
||||||
|
foreach (var candidate in choices)
|
||||||
|
{
|
||||||
|
if (tryTakeover(candidate))
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -6,9 +6,16 @@ namespace Content.Server.Ghost.Roles.UI
|
|||||||
{
|
{
|
||||||
public sealed class GhostRolesEui : BaseEui
|
public sealed class GhostRolesEui : BaseEui
|
||||||
{
|
{
|
||||||
|
[Dependency] private readonly GhostRoleSystem _ghostRoleSystem;
|
||||||
|
|
||||||
|
public GhostRolesEui()
|
||||||
|
{
|
||||||
|
_ghostRoleSystem = IoCManager.Resolve<IEntitySystemManager>().GetEntitySystem<GhostRoleSystem>();
|
||||||
|
}
|
||||||
|
|
||||||
public override GhostRolesEuiState GetNewState()
|
public override GhostRolesEuiState GetNewState()
|
||||||
{
|
{
|
||||||
return new(EntitySystem.Get<GhostRoleSystem>().GetGhostRolesInfo());
|
return new(_ghostRoleSystem.GetGhostRolesInfo(Player));
|
||||||
}
|
}
|
||||||
|
|
||||||
public override void HandleMessage(EuiMessageBase msg)
|
public override void HandleMessage(EuiMessageBase msg)
|
||||||
@@ -17,11 +24,14 @@ namespace Content.Server.Ghost.Roles.UI
|
|||||||
|
|
||||||
switch (msg)
|
switch (msg)
|
||||||
{
|
{
|
||||||
case GhostRoleTakeoverRequestMessage req:
|
case RequestGhostRoleMessage req:
|
||||||
EntitySystem.Get<GhostRoleSystem>().Takeover(Player, req.Identifier);
|
_ghostRoleSystem.Request(Player, req.Identifier);
|
||||||
break;
|
break;
|
||||||
case GhostRoleFollowRequestMessage req:
|
case FollowGhostRoleMessage req:
|
||||||
EntitySystem.Get<GhostRoleSystem>().Follow(Player, req.Identifier);
|
_ghostRoleSystem.Follow(Player, req.Identifier);
|
||||||
|
break;
|
||||||
|
case LeaveGhostRoleRaffleMessage req:
|
||||||
|
_ghostRoleSystem.LeaveRaffle(Player, req.Identifier);
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -36,7 +36,7 @@ public sealed class IgnitionSourceSystem : EntitySystem
|
|||||||
/// </summary>
|
/// </summary>
|
||||||
public void SetIgnited(Entity<IgnitionSourceComponent?> ent, bool ignited = true)
|
public void SetIgnited(Entity<IgnitionSourceComponent?> ent, bool ignited = true)
|
||||||
{
|
{
|
||||||
if (!Resolve(ent, ref ent.Comp))
|
if (!Resolve(ent, ref ent.Comp, false))
|
||||||
return;
|
return;
|
||||||
|
|
||||||
ent.Comp.Ignited = ignited;
|
ent.Comp.Ignited = ignited;
|
||||||
|
|||||||
@@ -1,3 +1,4 @@
|
|||||||
|
using Content.Shared.Damage;
|
||||||
using Robust.Shared.Audio;
|
using Robust.Shared.Audio;
|
||||||
|
|
||||||
namespace Content.Server.ImmovableRod;
|
namespace Content.Server.ImmovableRod;
|
||||||
@@ -36,4 +37,16 @@ public sealed partial class ImmovableRodComponent : Component
|
|||||||
/// </summary>
|
/// </summary>
|
||||||
[DataField("destroyTiles")]
|
[DataField("destroyTiles")]
|
||||||
public bool DestroyTiles = true;
|
public bool DestroyTiles = true;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// If true, this will gib & delete bodies
|
||||||
|
/// </summary>
|
||||||
|
[DataField]
|
||||||
|
public bool ShouldGib = true;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Damage done, if not gibbing
|
||||||
|
/// </summary>
|
||||||
|
[DataField]
|
||||||
|
public DamageSpecifier? Damage;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,9 +1,10 @@
|
|||||||
using Content.Server.Body.Systems;
|
using Content.Server.Body.Systems;
|
||||||
|
using Content.Server.Polymorph.Components;
|
||||||
using Content.Server.Popups;
|
using Content.Server.Popups;
|
||||||
using Content.Shared.Body.Components;
|
using Content.Shared.Body.Components;
|
||||||
|
using Content.Shared.Damage;
|
||||||
using Content.Shared.Examine;
|
using Content.Shared.Examine;
|
||||||
using Content.Shared.Popups;
|
using Content.Shared.Popups;
|
||||||
using Robust.Shared.Audio;
|
|
||||||
using Robust.Shared.Audio.Systems;
|
using Robust.Shared.Audio.Systems;
|
||||||
using Robust.Shared.Map;
|
using Robust.Shared.Map;
|
||||||
using Robust.Shared.Map.Components;
|
using Robust.Shared.Map.Components;
|
||||||
@@ -22,6 +23,8 @@ public sealed class ImmovableRodSystem : EntitySystem
|
|||||||
[Dependency] private readonly PopupSystem _popup = default!;
|
[Dependency] private readonly PopupSystem _popup = default!;
|
||||||
[Dependency] private readonly SharedPhysicsSystem _physics = default!;
|
[Dependency] private readonly SharedPhysicsSystem _physics = default!;
|
||||||
[Dependency] private readonly SharedAudioSystem _audio = default!;
|
[Dependency] private readonly SharedAudioSystem _audio = default!;
|
||||||
|
[Dependency] private readonly DamageableSystem _damageable = default!;
|
||||||
|
[Dependency] private readonly SharedTransformSystem _transform = default!;
|
||||||
|
|
||||||
public override void Update(float frameTime)
|
public override void Update(float frameTime)
|
||||||
{
|
{
|
||||||
@@ -57,18 +60,21 @@ public sealed class ImmovableRodSystem : EntitySystem
|
|||||||
_physics.SetFriction(uid, phys, 0f);
|
_physics.SetFriction(uid, phys, 0f);
|
||||||
_physics.SetBodyStatus(uid, phys, BodyStatus.InAir);
|
_physics.SetBodyStatus(uid, phys, BodyStatus.InAir);
|
||||||
|
|
||||||
if (!component.RandomizeVelocity)
|
|
||||||
return;
|
|
||||||
|
|
||||||
var xform = Transform(uid);
|
var xform = Transform(uid);
|
||||||
var vel = component.DirectionOverride.Degrees switch
|
var worldRot = _transform.GetWorldRotation(uid);
|
||||||
|
var vel = worldRot.ToWorldVec() * component.MaxSpeed;
|
||||||
|
|
||||||
|
if (component.RandomizeVelocity)
|
||||||
{
|
{
|
||||||
0f => _random.NextVector2(component.MinSpeed, component.MaxSpeed),
|
vel = component.DirectionOverride.Degrees switch
|
||||||
_ => xform.WorldRotation.RotateVec(component.DirectionOverride.ToVec()) * _random.NextFloat(component.MinSpeed, component.MaxSpeed)
|
{
|
||||||
};
|
0f => _random.NextVector2(component.MinSpeed, component.MaxSpeed),
|
||||||
|
_ => worldRot.RotateVec(component.DirectionOverride.ToVec()) * _random.NextFloat(component.MinSpeed, component.MaxSpeed)
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
_physics.ApplyLinearImpulse(uid, vel, body: phys);
|
_physics.ApplyLinearImpulse(uid, vel, body: phys);
|
||||||
xform.LocalRotation = (vel - xform.WorldPosition).ToWorldAngle() + MathHelper.PiOver2;
|
xform.LocalRotation = (vel - _transform.GetWorldPosition(uid)).ToWorldAngle() + MathHelper.PiOver2;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -94,12 +100,28 @@ public sealed class ImmovableRodSystem : EntitySystem
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
// gib em
|
// dont delete/hurt self if polymoprhed into a rod
|
||||||
|
if (TryComp<PolymorphedEntityComponent>(uid, out var polymorphed))
|
||||||
|
{
|
||||||
|
if (polymorphed.Parent == ent)
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// gib or damage em
|
||||||
if (TryComp<BodyComponent>(ent, out var body))
|
if (TryComp<BodyComponent>(ent, out var body))
|
||||||
{
|
{
|
||||||
component.MobCount++;
|
component.MobCount++;
|
||||||
|
|
||||||
_popup.PopupEntity(Loc.GetString("immovable-rod-penetrated-mob", ("rod", uid), ("mob", ent)), uid, PopupType.LargeCaution);
|
_popup.PopupEntity(Loc.GetString("immovable-rod-penetrated-mob", ("rod", uid), ("mob", ent)), uid, PopupType.LargeCaution);
|
||||||
|
|
||||||
|
if (!component.ShouldGib)
|
||||||
|
{
|
||||||
|
if (component.Damage == null || !TryComp<DamageableComponent>(ent, out var damageable))
|
||||||
|
return;
|
||||||
|
|
||||||
|
_damageable.SetDamage(ent, damageable, component.Damage);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
_bodySystem.GibBody(ent, body: body);
|
_bodySystem.GibBody(ent, body: body);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -12,6 +12,21 @@ namespace Content.Shared.Ghost.Roles
|
|||||||
public string Description { get; set; }
|
public string Description { get; set; }
|
||||||
public string Rules { get; set; }
|
public string Rules { get; set; }
|
||||||
public HashSet<JobRequirement>? Requirements { get; set; }
|
public HashSet<JobRequirement>? Requirements { get; set; }
|
||||||
|
|
||||||
|
/// <inheritdoc cref="GhostRoleKind"/>
|
||||||
|
public GhostRoleKind Kind { get; set; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// if <see cref="Kind"/> is <see cref="GhostRoleKind.RaffleInProgress"/>, specifies how many players are currently
|
||||||
|
/// in the raffle for this role.
|
||||||
|
/// </summary>
|
||||||
|
public uint RafflePlayerCount { get; set; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// if <see cref="Kind"/> is <see cref="GhostRoleKind.RaffleInProgress"/>, specifies when raffle finishes.
|
||||||
|
/// </summary>
|
||||||
|
public TimeSpan RaffleEndTime { get; set; }
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
[NetSerializable, Serializable]
|
[NetSerializable, Serializable]
|
||||||
@@ -26,24 +41,62 @@ namespace Content.Shared.Ghost.Roles
|
|||||||
}
|
}
|
||||||
|
|
||||||
[NetSerializable, Serializable]
|
[NetSerializable, Serializable]
|
||||||
public sealed class GhostRoleTakeoverRequestMessage : EuiMessageBase
|
public sealed class RequestGhostRoleMessage : EuiMessageBase
|
||||||
{
|
{
|
||||||
public uint Identifier { get; }
|
public uint Identifier { get; }
|
||||||
|
|
||||||
public GhostRoleTakeoverRequestMessage(uint identifier)
|
public RequestGhostRoleMessage(uint identifier)
|
||||||
{
|
{
|
||||||
Identifier = identifier;
|
Identifier = identifier;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
[NetSerializable, Serializable]
|
[NetSerializable, Serializable]
|
||||||
public sealed class GhostRoleFollowRequestMessage : EuiMessageBase
|
public sealed class FollowGhostRoleMessage : EuiMessageBase
|
||||||
{
|
{
|
||||||
public uint Identifier { get; }
|
public uint Identifier { get; }
|
||||||
|
|
||||||
public GhostRoleFollowRequestMessage(uint identifier)
|
public FollowGhostRoleMessage(uint identifier)
|
||||||
{
|
{
|
||||||
Identifier = identifier;
|
Identifier = identifier;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
[NetSerializable, Serializable]
|
||||||
|
public sealed class LeaveGhostRoleRaffleMessage : EuiMessageBase
|
||||||
|
{
|
||||||
|
public uint Identifier { get; }
|
||||||
|
|
||||||
|
public LeaveGhostRoleRaffleMessage(uint identifier)
|
||||||
|
{
|
||||||
|
Identifier = identifier;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Determines whether a ghost role is a raffle role, and if it is, whether it's running.
|
||||||
|
/// </summary>
|
||||||
|
[NetSerializable, Serializable]
|
||||||
|
public enum GhostRoleKind
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Role is not a raffle role and can be taken immediately.
|
||||||
|
/// </summary>
|
||||||
|
FirstComeFirstServe,
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Role is a raffle role, but raffle hasn't started yet.
|
||||||
|
/// </summary>
|
||||||
|
RaffleReady,
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Role is raffle role and currently being raffled, but player hasn't joined raffle.
|
||||||
|
/// </summary>
|
||||||
|
RaffleInProgress,
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Role is raffle role and currently being raffled, and player joined raffle.
|
||||||
|
/// </summary>
|
||||||
|
RaffleJoined
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,30 @@
|
|||||||
|
namespace Content.Server.Ghost.Roles.Raffles;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Defines settings for a ghost role raffle.
|
||||||
|
/// </summary>
|
||||||
|
[DataDefinition]
|
||||||
|
public sealed partial class GhostRoleRaffleSettings
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// The initial duration of a raffle in seconds. This is the countdown timer's value when the raffle starts.
|
||||||
|
/// </summary>
|
||||||
|
[ViewVariables(VVAccess.ReadWrite)]
|
||||||
|
[DataField(required: true)]
|
||||||
|
public uint InitialDuration { get; set; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// When the raffle is joined by a player, the countdown timer is extended by this value in seconds.
|
||||||
|
/// </summary>
|
||||||
|
[ViewVariables(VVAccess.ReadWrite)]
|
||||||
|
[DataField(required: true)]
|
||||||
|
public uint JoinExtendsDurationBy { get; set; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// The maximum duration in seconds for the ghost role raffle. A raffle cannot run for longer than this
|
||||||
|
/// duration, even if extended by joiners. Must be greater than or equal to <see cref="InitialDuration"/>.
|
||||||
|
/// </summary>
|
||||||
|
[ViewVariables(VVAccess.ReadWrite)]
|
||||||
|
[DataField(required: true)]
|
||||||
|
public uint MaxDuration { get; set; }
|
||||||
|
}
|
||||||
@@ -0,0 +1,22 @@
|
|||||||
|
using Content.Server.Ghost.Roles.Raffles;
|
||||||
|
using Robust.Shared.Prototypes;
|
||||||
|
|
||||||
|
namespace Content.Shared.Ghost.Roles.Raffles;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Allows specifying the settings for a ghost role raffle as a prototype.
|
||||||
|
/// </summary>
|
||||||
|
[Prototype("ghostRoleRaffleSettings")]
|
||||||
|
public sealed class GhostRoleRaffleSettingsPrototype : IPrototype
|
||||||
|
{
|
||||||
|
/// <inheritdoc />
|
||||||
|
[IdDataField]
|
||||||
|
public string ID { get; private set; } = default!;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// The settings for a ghost role raffle.
|
||||||
|
/// </summary>
|
||||||
|
/// <seealso cref="GhostRoleRaffleSettings"/>
|
||||||
|
[DataField(required: true)]
|
||||||
|
public GhostRoleRaffleSettings Settings { get; private set; } = new();
|
||||||
|
}
|
||||||
@@ -22,6 +22,17 @@ ghost-target-window-current-button = Warp: {$name}
|
|||||||
ghost-target-window-warp-to-most-followed = Warp to Most Followed
|
ghost-target-window-warp-to-most-followed = Warp to Most Followed
|
||||||
|
|
||||||
ghost-roles-window-title = Ghost Roles
|
ghost-roles-window-title = Ghost Roles
|
||||||
|
ghost-roles-window-join-raffle-button = Join raffle
|
||||||
|
ghost-roles-window-raffle-in-progress-button =
|
||||||
|
Join raffle ({$time} left, { $players ->
|
||||||
|
[one] {$players} player
|
||||||
|
*[other] {$players} players
|
||||||
|
})
|
||||||
|
ghost-roles-window-leave-raffle-button =
|
||||||
|
Leave raffle ({$time} left, { $players ->
|
||||||
|
[one] {$players} player
|
||||||
|
*[other] {$players} players
|
||||||
|
})
|
||||||
ghost-roles-window-request-role-button = Request
|
ghost-roles-window-request-role-button = Request
|
||||||
ghost-roles-window-request-role-button-timer = Request ({$time}s)
|
ghost-roles-window-request-role-button-timer = Request ({$time}s)
|
||||||
ghost-roles-window-follow-role-button = Follow
|
ghost-roles-window-follow-role-button = Follow
|
||||||
|
|||||||
@@ -8,6 +8,8 @@
|
|||||||
name: ghost-role-information-rat-king-name
|
name: ghost-role-information-rat-king-name
|
||||||
description: ghost-role-information-rat-king-description
|
description: ghost-role-information-rat-king-description
|
||||||
rules: ghost-role-information-rat-king-rules
|
rules: ghost-role-information-rat-king-rules
|
||||||
|
raffle:
|
||||||
|
settings: default
|
||||||
- type: GhostRoleMobSpawner
|
- type: GhostRoleMobSpawner
|
||||||
prototype: MobRatKing
|
prototype: MobRatKing
|
||||||
- type: Sprite
|
- type: Sprite
|
||||||
@@ -27,6 +29,8 @@
|
|||||||
name: ghost-role-information-remilia-name
|
name: ghost-role-information-remilia-name
|
||||||
description: ghost-role-information-remilia-description
|
description: ghost-role-information-remilia-description
|
||||||
rules: ghost-role-information-remilia-rules
|
rules: ghost-role-information-remilia-rules
|
||||||
|
raffle:
|
||||||
|
settings: short
|
||||||
- type: GhostRoleMobSpawner
|
- type: GhostRoleMobSpawner
|
||||||
prototype: MobBatRemilia
|
prototype: MobBatRemilia
|
||||||
- type: Sprite
|
- type: Sprite
|
||||||
@@ -46,6 +50,8 @@
|
|||||||
name: ghost-role-information-cerberus-name
|
name: ghost-role-information-cerberus-name
|
||||||
description: ghost-role-information-cerberus-description
|
description: ghost-role-information-cerberus-description
|
||||||
rules: ghost-role-information-cerberus-rules
|
rules: ghost-role-information-cerberus-rules
|
||||||
|
raffle:
|
||||||
|
settings: short
|
||||||
- type: GhostRoleMobSpawner
|
- type: GhostRoleMobSpawner
|
||||||
prototype: MobCorgiCerberus
|
prototype: MobCorgiCerberus
|
||||||
- type: Sprite
|
- type: Sprite
|
||||||
@@ -64,6 +70,8 @@
|
|||||||
components:
|
components:
|
||||||
- type: GhostRole
|
- type: GhostRole
|
||||||
rules: ghost-role-information-nukeop-rules
|
rules: ghost-role-information-nukeop-rules
|
||||||
|
raffle:
|
||||||
|
settings: default
|
||||||
- type: GhostRoleMobSpawner
|
- type: GhostRoleMobSpawner
|
||||||
prototype: MobHumanNukeOp
|
prototype: MobHumanNukeOp
|
||||||
- type: Sprite
|
- type: Sprite
|
||||||
@@ -84,6 +92,8 @@
|
|||||||
name: ghost-role-information-loneop-name
|
name: ghost-role-information-loneop-name
|
||||||
description: ghost-role-information-loneop-description
|
description: ghost-role-information-loneop-description
|
||||||
rules: ghost-role-information-loneop-rules
|
rules: ghost-role-information-loneop-rules
|
||||||
|
raffle:
|
||||||
|
settings: default
|
||||||
- type: GhostRoleMobSpawner
|
- type: GhostRoleMobSpawner
|
||||||
prototype: MobHumanLoneNuclearOperative
|
prototype: MobHumanLoneNuclearOperative
|
||||||
- type: Sprite
|
- type: Sprite
|
||||||
@@ -104,6 +114,8 @@
|
|||||||
name: ghost-role-information-space-dragon-name
|
name: ghost-role-information-space-dragon-name
|
||||||
description: ghost-role-information-space-dragon-description
|
description: ghost-role-information-space-dragon-description
|
||||||
rules: ghost-role-component-default-rules
|
rules: ghost-role-component-default-rules
|
||||||
|
raffle:
|
||||||
|
settings: default
|
||||||
- type: GhostRoleMobSpawner
|
- type: GhostRoleMobSpawner
|
||||||
prototype: MobDragon
|
prototype: MobDragon
|
||||||
- type: Sprite
|
- type: Sprite
|
||||||
@@ -122,6 +134,8 @@
|
|||||||
name: ghost-role-information-space-ninja-name
|
name: ghost-role-information-space-ninja-name
|
||||||
description: ghost-role-information-space-ninja-description
|
description: ghost-role-information-space-ninja-description
|
||||||
rules: ghost-role-information-space-ninja-rules
|
rules: ghost-role-information-space-ninja-rules
|
||||||
|
raffle:
|
||||||
|
settings: default
|
||||||
- type: GhostRoleMobSpawner
|
- type: GhostRoleMobSpawner
|
||||||
prototype: MobHumanSpaceNinja
|
prototype: MobHumanSpaceNinja
|
||||||
- type: Sprite
|
- type: Sprite
|
||||||
|
|||||||
@@ -1318,6 +1318,8 @@
|
|||||||
makeSentient: true
|
makeSentient: true
|
||||||
name: ghost-role-information-monkey-name
|
name: ghost-role-information-monkey-name
|
||||||
description: ghost-role-information-monkey-description
|
description: ghost-role-information-monkey-description
|
||||||
|
raffle:
|
||||||
|
settings: default
|
||||||
- type: GhostTakeoverAvailable
|
- type: GhostTakeoverAvailable
|
||||||
- type: Loadout
|
- type: Loadout
|
||||||
prototypes: [SyndicateOperativeGearMonkey]
|
prototypes: [SyndicateOperativeGearMonkey]
|
||||||
@@ -2224,6 +2226,8 @@
|
|||||||
makeSentient: true
|
makeSentient: true
|
||||||
name: ghost-role-information-giant-spider-name
|
name: ghost-role-information-giant-spider-name
|
||||||
description: ghost-role-information-giant-spider-description
|
description: ghost-role-information-giant-spider-description
|
||||||
|
raffle:
|
||||||
|
settings: short
|
||||||
- type: GhostTakeoverAvailable
|
- type: GhostTakeoverAvailable
|
||||||
- type: GlobalAntagonist
|
- type: GlobalAntagonist
|
||||||
antagonistPrototype: globalAntagonistSpider
|
antagonistPrototype: globalAntagonistSpider
|
||||||
@@ -2762,6 +2766,8 @@
|
|||||||
allowMovement: true
|
allowMovement: true
|
||||||
description: ghost-role-information-SyndiCat-description
|
description: ghost-role-information-SyndiCat-description
|
||||||
rules: ghost-role-information-SyndiCat-rules
|
rules: ghost-role-information-SyndiCat-rules
|
||||||
|
raffle:
|
||||||
|
settings: default
|
||||||
- type: GhostTakeoverAvailable
|
- type: GhostTakeoverAvailable
|
||||||
- type: AutoImplant
|
- type: AutoImplant
|
||||||
implants:
|
implants:
|
||||||
|
|||||||
@@ -10,6 +10,8 @@
|
|||||||
makeSentient: true
|
makeSentient: true
|
||||||
name: ghost-role-information-behonker-name
|
name: ghost-role-information-behonker-name
|
||||||
description: ghost-role-information-behonker-description
|
description: ghost-role-information-behonker-description
|
||||||
|
raffle:
|
||||||
|
settings: default
|
||||||
- type: GhostTakeoverAvailable
|
- type: GhostTakeoverAvailable
|
||||||
- type: HTN
|
- type: HTN
|
||||||
rootTask:
|
rootTask:
|
||||||
|
|||||||
@@ -183,6 +183,8 @@
|
|||||||
makeSentient: true
|
makeSentient: true
|
||||||
name: ghost-role-information-sentient-carp-name
|
name: ghost-role-information-sentient-carp-name
|
||||||
description: ghost-role-information-sentient-carp-description
|
description: ghost-role-information-sentient-carp-description
|
||||||
|
raffle:
|
||||||
|
settings: short
|
||||||
- type: GhostTakeoverAvailable
|
- type: GhostTakeoverAvailable
|
||||||
- type: HTN
|
- type: HTN
|
||||||
rootTask:
|
rootTask:
|
||||||
|
|||||||
@@ -232,6 +232,8 @@
|
|||||||
components:
|
components:
|
||||||
- type: GhostRole
|
- type: GhostRole
|
||||||
description: ghost-role-information-angry-slimes-description
|
description: ghost-role-information-angry-slimes-description
|
||||||
|
raffle:
|
||||||
|
settings: short
|
||||||
- type: NpcFactionMember
|
- type: NpcFactionMember
|
||||||
factions:
|
factions:
|
||||||
- SimpleHostile
|
- SimpleHostile
|
||||||
|
|||||||
@@ -12,6 +12,8 @@
|
|||||||
makeSentient: true
|
makeSentient: true
|
||||||
name: ghost-role-information-hellspawn-name
|
name: ghost-role-information-hellspawn-name
|
||||||
description: ghost-role-information-hellspawn-description
|
description: ghost-role-information-hellspawn-description
|
||||||
|
raffle:
|
||||||
|
settings: default
|
||||||
- type: RotationVisuals
|
- type: RotationVisuals
|
||||||
defaultRotation: 90
|
defaultRotation: 90
|
||||||
horizontalRotation: 90
|
horizontalRotation: 90
|
||||||
|
|||||||
@@ -90,6 +90,8 @@
|
|||||||
name: ghost-role-information-rat-king-name
|
name: ghost-role-information-rat-king-name
|
||||||
description: ghost-role-information-rat-king-description
|
description: ghost-role-information-rat-king-description
|
||||||
rules: ghost-role-information-rat-king-rules
|
rules: ghost-role-information-rat-king-rules
|
||||||
|
raffle:
|
||||||
|
settings: default
|
||||||
- type: GhostTakeoverAvailable
|
- type: GhostTakeoverAvailable
|
||||||
- type: Tag
|
- type: Tag
|
||||||
tags:
|
tags:
|
||||||
|
|||||||
@@ -56,6 +56,8 @@
|
|||||||
name: ghost-role-information-revenant-name
|
name: ghost-role-information-revenant-name
|
||||||
description: ghost-role-information-revenant-description
|
description: ghost-role-information-revenant-description
|
||||||
rules: ghost-role-information-revenant-rules
|
rules: ghost-role-information-revenant-rules
|
||||||
|
raffle:
|
||||||
|
settings: default
|
||||||
- type: GhostTakeoverAvailable
|
- type: GhostTakeoverAvailable
|
||||||
- type: Revenant
|
- type: Revenant
|
||||||
malfunctionWhitelist:
|
malfunctionWhitelist:
|
||||||
|
|||||||
@@ -151,6 +151,8 @@
|
|||||||
makeSentient: true
|
makeSentient: true
|
||||||
name: ghost-role-information-honkbot-name
|
name: ghost-role-information-honkbot-name
|
||||||
description: ghost-role-information-honkbot-description
|
description: ghost-role-information-honkbot-description
|
||||||
|
raffle:
|
||||||
|
settings: default
|
||||||
- type: GhostTakeoverAvailable
|
- type: GhostTakeoverAvailable
|
||||||
- type: InteractionPopup
|
- type: InteractionPopup
|
||||||
interactSuccessString: petting-success-honkbot
|
interactSuccessString: petting-success-honkbot
|
||||||
@@ -176,6 +178,8 @@
|
|||||||
makeSentient: true
|
makeSentient: true
|
||||||
name: ghost-role-information-jonkbot-name
|
name: ghost-role-information-jonkbot-name
|
||||||
description: ghost-role-information-jonkbot-description
|
description: ghost-role-information-jonkbot-description
|
||||||
|
raffle:
|
||||||
|
settings: default
|
||||||
- type: InteractionPopup
|
- type: InteractionPopup
|
||||||
interactSuccessSound:
|
interactSuccessSound:
|
||||||
path: /Audio/Items/brokenbikehorn.ogg
|
path: /Audio/Items/brokenbikehorn.ogg
|
||||||
@@ -312,6 +316,8 @@
|
|||||||
makeSentient: true
|
makeSentient: true
|
||||||
name: ghost-role-information-mimebot-name
|
name: ghost-role-information-mimebot-name
|
||||||
description: ghost-role-information-mimebot-description
|
description: ghost-role-information-mimebot-description
|
||||||
|
raffle:
|
||||||
|
settings: default
|
||||||
- type: GhostTakeoverAvailable
|
- type: GhostTakeoverAvailable
|
||||||
- type: InteractionPopup
|
- type: InteractionPopup
|
||||||
interactSuccessString: petting-success-mimebot
|
interactSuccessString: petting-success-mimebot
|
||||||
|
|||||||
@@ -119,6 +119,8 @@
|
|||||||
makeSentient: true
|
makeSentient: true
|
||||||
name: ghost-role-information-slimes-name
|
name: ghost-role-information-slimes-name
|
||||||
description: ghost-role-information-slimes-description
|
description: ghost-role-information-slimes-description
|
||||||
|
raffle:
|
||||||
|
settings: short
|
||||||
- type: Speech
|
- type: Speech
|
||||||
speechVerb: Slime
|
speechVerb: Slime
|
||||||
speechSounds: Slime
|
speechSounds: Slime
|
||||||
@@ -148,6 +150,8 @@
|
|||||||
- SimpleHostile
|
- SimpleHostile
|
||||||
- type: GhostRole
|
- type: GhostRole
|
||||||
description: ghost-role-information-angry-slimes-description
|
description: ghost-role-information-angry-slimes-description
|
||||||
|
raffle:
|
||||||
|
settings: short
|
||||||
- type: GlobalAntagonist
|
- type: GlobalAntagonist
|
||||||
antagonistPrototype: globalAntagonistSlime
|
antagonistPrototype: globalAntagonistSlime
|
||||||
|
|
||||||
@@ -185,6 +189,8 @@
|
|||||||
- SimpleHostile
|
- SimpleHostile
|
||||||
- type: GhostRole
|
- type: GhostRole
|
||||||
description: ghost-role-information-angry-slimes-description
|
description: ghost-role-information-angry-slimes-description
|
||||||
|
raffle:
|
||||||
|
settings: short
|
||||||
- type: GlobalAntagonist
|
- type: GlobalAntagonist
|
||||||
antagonistPrototype: globalAntagonistSlime
|
antagonistPrototype: globalAntagonistSlime
|
||||||
|
|
||||||
@@ -222,5 +228,7 @@
|
|||||||
- SimpleHostile
|
- SimpleHostile
|
||||||
- type: GhostRole
|
- type: GhostRole
|
||||||
description: ghost-role-information-angry-slimes-description
|
description: ghost-role-information-angry-slimes-description
|
||||||
|
raffle:
|
||||||
|
settings: short
|
||||||
- type: GlobalAntagonist
|
- type: GlobalAntagonist
|
||||||
antagonistPrototype: globalAntagonistSlime
|
antagonistPrototype: globalAntagonistSlime
|
||||||
|
|||||||
@@ -104,6 +104,8 @@
|
|||||||
name: ghost-role-information-xeno-name
|
name: ghost-role-information-xeno-name
|
||||||
description: ghost-role-information-xeno-description
|
description: ghost-role-information-xeno-description
|
||||||
rules: ghost-role-information-xeno-rules
|
rules: ghost-role-information-xeno-rules
|
||||||
|
raffle:
|
||||||
|
settings: default
|
||||||
- type: GhostTakeoverAvailable
|
- type: GhostTakeoverAvailable
|
||||||
- type: TypingIndicator
|
- type: TypingIndicator
|
||||||
proto: alien
|
proto: alien
|
||||||
|
|||||||
@@ -14,9 +14,11 @@
|
|||||||
makeSentient: true
|
makeSentient: true
|
||||||
name: ghost-role-information-space-dragon-name
|
name: ghost-role-information-space-dragon-name
|
||||||
description: ghost-role-information-space-dragon-description
|
description: ghost-role-information-space-dragon-description
|
||||||
|
raffle:
|
||||||
|
settings: default
|
||||||
- type: SlowOnDamage
|
- type: SlowOnDamage
|
||||||
speedModifierThresholds:
|
speedModifierThresholds:
|
||||||
270: 0.7
|
400: 0.7
|
||||||
- type: GhostTakeoverAvailable
|
- type: GhostTakeoverAvailable
|
||||||
- type: HTN
|
- type: HTN
|
||||||
rootTask:
|
rootTask:
|
||||||
@@ -164,6 +166,8 @@
|
|||||||
components:
|
components:
|
||||||
- type: GhostRole
|
- type: GhostRole
|
||||||
description: ghost-role-information-space-dragon-dungeon-description
|
description: ghost-role-information-space-dragon-dungeon-description
|
||||||
|
raffle:
|
||||||
|
settings: short
|
||||||
- type: MobThresholds
|
- type: MobThresholds
|
||||||
thresholds:
|
thresholds:
|
||||||
0: Alive
|
0: Alive
|
||||||
|
|||||||
@@ -44,6 +44,8 @@
|
|||||||
name: ghost-role-information-cerberus-name
|
name: ghost-role-information-cerberus-name
|
||||||
description: ghost-role-information-cerberus-description
|
description: ghost-role-information-cerberus-description
|
||||||
rules: ghost-role-information-cerberus-rules
|
rules: ghost-role-information-cerberus-rules
|
||||||
|
raffle:
|
||||||
|
settings: default
|
||||||
- type: GhostTakeoverAvailable
|
- type: GhostTakeoverAvailable
|
||||||
- type: MeleeWeapon
|
- type: MeleeWeapon
|
||||||
altDisarm: false
|
altDisarm: false
|
||||||
|
|||||||
@@ -13,6 +13,8 @@
|
|||||||
makeSentient: true
|
makeSentient: true
|
||||||
name: ghost-role-information-guardian-name
|
name: ghost-role-information-guardian-name
|
||||||
description: ghost-role-information-guardian-description
|
description: ghost-role-information-guardian-description
|
||||||
|
raffle:
|
||||||
|
settings: default
|
||||||
- type: GhostTakeoverAvailable
|
- type: GhostTakeoverAvailable
|
||||||
- type: Input
|
- type: Input
|
||||||
context: "human"
|
context: "human"
|
||||||
@@ -119,6 +121,8 @@
|
|||||||
makeSentient: true
|
makeSentient: true
|
||||||
name: ghost-role-information-holoparasite-name
|
name: ghost-role-information-holoparasite-name
|
||||||
description: ghost-role-information-holoparasite-description
|
description: ghost-role-information-holoparasite-description
|
||||||
|
raffle:
|
||||||
|
settings: default
|
||||||
- type: GhostTakeoverAvailable
|
- type: GhostTakeoverAvailable
|
||||||
- type: NameIdentifier
|
- type: NameIdentifier
|
||||||
group: Holoparasite
|
group: Holoparasite
|
||||||
@@ -149,6 +153,8 @@
|
|||||||
makeSentient: true
|
makeSentient: true
|
||||||
name: ghost-role-information-ifrit-name
|
name: ghost-role-information-ifrit-name
|
||||||
description: ghost-role-information-ifrit-description
|
description: ghost-role-information-ifrit-description
|
||||||
|
raffle:
|
||||||
|
settings: default
|
||||||
- type: GhostTakeoverAvailable
|
- type: GhostTakeoverAvailable
|
||||||
- type: RandomSprite
|
- type: RandomSprite
|
||||||
available:
|
available:
|
||||||
@@ -175,6 +181,8 @@
|
|||||||
makeSentient: true
|
makeSentient: true
|
||||||
name: ghost-role-information-holoclown-name
|
name: ghost-role-information-holoclown-name
|
||||||
description: ghost-role-information-holoclown-description
|
description: ghost-role-information-holoclown-description
|
||||||
|
raffle:
|
||||||
|
settings: default
|
||||||
- type: GhostTakeoverAvailable
|
- type: GhostTakeoverAvailable
|
||||||
- type: NameIdentifier
|
- type: NameIdentifier
|
||||||
group: Holoparasite
|
group: Holoparasite
|
||||||
|
|||||||
@@ -41,6 +41,8 @@
|
|||||||
- type: GhostRole
|
- type: GhostRole
|
||||||
name: ghost-role-information-Death-Squad-name
|
name: ghost-role-information-Death-Squad-name
|
||||||
description: ghost-role-information-Death-Squad-description
|
description: ghost-role-information-Death-Squad-description
|
||||||
|
raffle:
|
||||||
|
settings: short
|
||||||
- type: GhostTakeoverAvailable
|
- type: GhostTakeoverAvailable
|
||||||
- type: Loadout
|
- type: Loadout
|
||||||
prototypes: [ DeathSquadGear ]
|
prototypes: [ DeathSquadGear ]
|
||||||
@@ -78,6 +80,8 @@
|
|||||||
- type: GhostRole
|
- type: GhostRole
|
||||||
name: ghost-role-information-ert-leader-name
|
name: ghost-role-information-ert-leader-name
|
||||||
description: ghost-role-information-ert-leader-description
|
description: ghost-role-information-ert-leader-description
|
||||||
|
raffle:
|
||||||
|
settings: short
|
||||||
- type: GhostTakeoverAvailable
|
- type: GhostTakeoverAvailable
|
||||||
- type: Loadout
|
- type: Loadout
|
||||||
prototypes: [ ERTLeaderGear ]
|
prototypes: [ ERTLeaderGear ]
|
||||||
@@ -108,6 +112,8 @@
|
|||||||
- type: GhostRole
|
- type: GhostRole
|
||||||
name: ghost-role-information-ert-leader-name
|
name: ghost-role-information-ert-leader-name
|
||||||
description: ghost-role-information-ert-leader-description
|
description: ghost-role-information-ert-leader-description
|
||||||
|
raffle:
|
||||||
|
settings: short
|
||||||
- type: GhostTakeoverAvailable
|
- type: GhostTakeoverAvailable
|
||||||
- type: Loadout
|
- type: Loadout
|
||||||
prototypes: [ ERTLeaderGearEVA ]
|
prototypes: [ ERTLeaderGearEVA ]
|
||||||
@@ -130,6 +136,8 @@
|
|||||||
- type: GhostRole
|
- type: GhostRole
|
||||||
name: ghost-role-information-ert-leader-name
|
name: ghost-role-information-ert-leader-name
|
||||||
description: ghost-role-information-ert-leader-description
|
description: ghost-role-information-ert-leader-description
|
||||||
|
raffle:
|
||||||
|
settings: short
|
||||||
- type: GhostTakeoverAvailable
|
- type: GhostTakeoverAvailable
|
||||||
- type: Loadout
|
- type: Loadout
|
||||||
prototypes: [ ERTLeaderGearEVALecter ]
|
prototypes: [ ERTLeaderGearEVALecter ]
|
||||||
@@ -161,6 +169,8 @@
|
|||||||
- type: GhostRole
|
- type: GhostRole
|
||||||
name: ghost-role-information-ert-chaplain-name
|
name: ghost-role-information-ert-chaplain-name
|
||||||
description: ghost-role-information-ert-chaplain-description
|
description: ghost-role-information-ert-chaplain-description
|
||||||
|
raffle:
|
||||||
|
settings: short
|
||||||
- type: GhostTakeoverAvailable
|
- type: GhostTakeoverAvailable
|
||||||
- type: RandomMetadata
|
- type: RandomMetadata
|
||||||
nameSegments:
|
nameSegments:
|
||||||
@@ -190,6 +200,8 @@
|
|||||||
- type: GhostRole
|
- type: GhostRole
|
||||||
name: ghost-role-information-ert-chaplain-name
|
name: ghost-role-information-ert-chaplain-name
|
||||||
description: ghost-role-information-ert-chaplain-description
|
description: ghost-role-information-ert-chaplain-description
|
||||||
|
raffle:
|
||||||
|
settings: short
|
||||||
- type: GhostTakeoverAvailable
|
- type: GhostTakeoverAvailable
|
||||||
- type: Loadout
|
- type: Loadout
|
||||||
prototypes: [ ERTChaplainGearEVA ]
|
prototypes: [ ERTChaplainGearEVA ]
|
||||||
@@ -221,6 +233,8 @@
|
|||||||
- type: GhostRole
|
- type: GhostRole
|
||||||
name: ghost-role-information-ert-janitor-name
|
name: ghost-role-information-ert-janitor-name
|
||||||
description: ghost-role-information-ert-janitor-description
|
description: ghost-role-information-ert-janitor-description
|
||||||
|
raffle:
|
||||||
|
settings: short
|
||||||
- type: GhostTakeoverAvailable
|
- type: GhostTakeoverAvailable
|
||||||
- type: RandomMetadata
|
- type: RandomMetadata
|
||||||
nameSegments:
|
nameSegments:
|
||||||
@@ -250,6 +264,8 @@
|
|||||||
- type: GhostRole
|
- type: GhostRole
|
||||||
name: ghost-role-information-ert-janitor-name
|
name: ghost-role-information-ert-janitor-name
|
||||||
description: ghost-role-information-ert-janitor-description
|
description: ghost-role-information-ert-janitor-description
|
||||||
|
raffle:
|
||||||
|
settings: short
|
||||||
- type: GhostTakeoverAvailable
|
- type: GhostTakeoverAvailable
|
||||||
- type: Loadout
|
- type: Loadout
|
||||||
prototypes: [ ERTJanitorGearEVA ]
|
prototypes: [ ERTJanitorGearEVA ]
|
||||||
@@ -281,6 +297,8 @@
|
|||||||
- type: GhostRole
|
- type: GhostRole
|
||||||
name: ghost-role-information-ert-engineer-name
|
name: ghost-role-information-ert-engineer-name
|
||||||
description: ghost-role-information-ert-engineer-description
|
description: ghost-role-information-ert-engineer-description
|
||||||
|
raffle:
|
||||||
|
settings: short
|
||||||
- type: GhostTakeoverAvailable
|
- type: GhostTakeoverAvailable
|
||||||
- type: RandomMetadata
|
- type: RandomMetadata
|
||||||
nameSegments:
|
nameSegments:
|
||||||
@@ -310,6 +328,8 @@
|
|||||||
- type: GhostRole
|
- type: GhostRole
|
||||||
name: ghost-role-information-ert-engineer-name
|
name: ghost-role-information-ert-engineer-name
|
||||||
description: ghost-role-information-ert-engineer-description
|
description: ghost-role-information-ert-engineer-description
|
||||||
|
raffle:
|
||||||
|
settings: short
|
||||||
- type: GhostTakeoverAvailable
|
- type: GhostTakeoverAvailable
|
||||||
- type: Loadout
|
- type: Loadout
|
||||||
prototypes: [ ERTEngineerGearEVA ]
|
prototypes: [ ERTEngineerGearEVA ]
|
||||||
@@ -341,6 +361,8 @@
|
|||||||
- type: GhostRole
|
- type: GhostRole
|
||||||
name: ghost-role-information-ert-security-name
|
name: ghost-role-information-ert-security-name
|
||||||
description: ghost-role-information-ert-security-description
|
description: ghost-role-information-ert-security-description
|
||||||
|
raffle:
|
||||||
|
settings: short
|
||||||
- type: GhostTakeoverAvailable
|
- type: GhostTakeoverAvailable
|
||||||
- type: RandomMetadata
|
- type: RandomMetadata
|
||||||
nameSegments:
|
nameSegments:
|
||||||
@@ -370,6 +392,8 @@
|
|||||||
- type: GhostRole
|
- type: GhostRole
|
||||||
name: ghost-role-information-ert-security-name
|
name: ghost-role-information-ert-security-name
|
||||||
description: ghost-role-information-ert-security-description
|
description: ghost-role-information-ert-security-description
|
||||||
|
raffle:
|
||||||
|
settings: short
|
||||||
- type: GhostTakeoverAvailable
|
- type: GhostTakeoverAvailable
|
||||||
- type: Loadout
|
- type: Loadout
|
||||||
prototypes: [ ERTSecurityGearEVA ]
|
prototypes: [ ERTSecurityGearEVA ]
|
||||||
@@ -391,6 +415,8 @@
|
|||||||
- type: GhostRole
|
- type: GhostRole
|
||||||
name: ghost-role-information-ert-security-name
|
name: ghost-role-information-ert-security-name
|
||||||
description: ghost-role-information-ert-security-description
|
description: ghost-role-information-ert-security-description
|
||||||
|
raffle:
|
||||||
|
settings: short
|
||||||
- type: GhostTakeoverAvailable
|
- type: GhostTakeoverAvailable
|
||||||
- type: Loadout
|
- type: Loadout
|
||||||
prototypes: [ ERTSecurityGearEVALecter ]
|
prototypes: [ ERTSecurityGearEVALecter ]
|
||||||
@@ -422,6 +448,8 @@
|
|||||||
- type: GhostRole
|
- type: GhostRole
|
||||||
name: ghost-role-information-ert-medical-name
|
name: ghost-role-information-ert-medical-name
|
||||||
description: ghost-role-information-ert-medical-description
|
description: ghost-role-information-ert-medical-description
|
||||||
|
raffle:
|
||||||
|
settings: short
|
||||||
- type: GhostTakeoverAvailable
|
- type: GhostTakeoverAvailable
|
||||||
- type: RandomMetadata
|
- type: RandomMetadata
|
||||||
nameSegments:
|
nameSegments:
|
||||||
@@ -451,6 +479,8 @@
|
|||||||
- type: GhostRole
|
- type: GhostRole
|
||||||
name: ghost-role-information-ert-medical-name
|
name: ghost-role-information-ert-medical-name
|
||||||
description: ghost-role-information-ert-medical-description
|
description: ghost-role-information-ert-medical-description
|
||||||
|
raffle:
|
||||||
|
settings: short
|
||||||
- type: GhostTakeoverAvailable
|
- type: GhostTakeoverAvailable
|
||||||
- type: Loadout
|
- type: Loadout
|
||||||
prototypes: [ ERTMedicalGearEVA ]
|
prototypes: [ ERTMedicalGearEVA ]
|
||||||
@@ -480,6 +510,8 @@
|
|||||||
- type: GhostRole
|
- type: GhostRole
|
||||||
name: ghost-role-information-cburn-agent-name
|
name: ghost-role-information-cburn-agent-name
|
||||||
description: ghost-role-information-cburn-agent-description
|
description: ghost-role-information-cburn-agent-description
|
||||||
|
raffle:
|
||||||
|
settings: short
|
||||||
- type: GhostTakeoverAvailable
|
- type: GhostTakeoverAvailable
|
||||||
- type: RandomMetadata
|
- type: RandomMetadata
|
||||||
nameSegments:
|
nameSegments:
|
||||||
@@ -507,6 +539,8 @@
|
|||||||
- type: GhostRole
|
- type: GhostRole
|
||||||
name: ghost-role-information-centcom-official-name
|
name: ghost-role-information-centcom-official-name
|
||||||
description: ghost-role-information-centcom-official-description
|
description: ghost-role-information-centcom-official-description
|
||||||
|
raffle:
|
||||||
|
settings: default
|
||||||
- type: GhostTakeoverAvailable
|
- type: GhostTakeoverAvailable
|
||||||
- type: Loadout
|
- type: Loadout
|
||||||
prototypes: [ CentcomGear ]
|
prototypes: [ CentcomGear ]
|
||||||
@@ -569,5 +603,7 @@
|
|||||||
- type: GhostRole
|
- type: GhostRole
|
||||||
name: ghost-role-information-cluwne-name
|
name: ghost-role-information-cluwne-name
|
||||||
description: ghost-role-information-cluwne-description
|
description: ghost-role-information-cluwne-description
|
||||||
|
raffle:
|
||||||
|
settings: default
|
||||||
- type: GhostTakeoverAvailable
|
- type: GhostTakeoverAvailable
|
||||||
- type: Cluwne
|
- type: Cluwne
|
||||||
|
|||||||
@@ -17,6 +17,8 @@
|
|||||||
- type: GhostRole
|
- type: GhostRole
|
||||||
name: ghost-role-information-skeleton-pirate-name
|
name: ghost-role-information-skeleton-pirate-name
|
||||||
description: ghost-role-information-skeleton-pirate-description
|
description: ghost-role-information-skeleton-pirate-description
|
||||||
|
raffle:
|
||||||
|
settings: default
|
||||||
- type: GhostTakeoverAvailable
|
- type: GhostTakeoverAvailable
|
||||||
- type: Loadout
|
- type: Loadout
|
||||||
prototypes: [PirateGear]
|
prototypes: [PirateGear]
|
||||||
@@ -31,6 +33,8 @@
|
|||||||
- type: GhostRole
|
- type: GhostRole
|
||||||
name: ghost-role-information-skeleton-biker-name
|
name: ghost-role-information-skeleton-biker-name
|
||||||
description: ghost-role-information-skeleton-biker-description
|
description: ghost-role-information-skeleton-biker-description
|
||||||
|
raffle:
|
||||||
|
settings: default
|
||||||
- type: GhostTakeoverAvailable
|
- type: GhostTakeoverAvailable
|
||||||
- type: Loadout
|
- type: Loadout
|
||||||
prototypes: [SkeletonBiker]
|
prototypes: [SkeletonBiker]
|
||||||
@@ -44,6 +48,8 @@
|
|||||||
- type: GhostRole
|
- type: GhostRole
|
||||||
name: ghost-role-information-closet-skeleton-name
|
name: ghost-role-information-closet-skeleton-name
|
||||||
description: ghost-role-information-closet-skeleton-description
|
description: ghost-role-information-closet-skeleton-description
|
||||||
|
raffle:
|
||||||
|
settings: default
|
||||||
- type: GhostTakeoverAvailable
|
- type: GhostTakeoverAvailable
|
||||||
- type: Loadout
|
- type: Loadout
|
||||||
prototypes: [LimitedPassengerGear]
|
prototypes: [LimitedPassengerGear]
|
||||||
|
|||||||
@@ -192,7 +192,7 @@
|
|||||||
canResistFire: true
|
canResistFire: true
|
||||||
damage: #per second, scales with number of fire 'stacks'
|
damage: #per second, scales with number of fire 'stacks'
|
||||||
types:
|
types:
|
||||||
Heat: 3
|
Heat: 1.5
|
||||||
- type: FireVisuals
|
- type: FireVisuals
|
||||||
sprite: Mobs/Effects/onfire.rsi
|
sprite: Mobs/Effects/onfire.rsi
|
||||||
normalState: Generic_mob_burning
|
normalState: Generic_mob_burning
|
||||||
|
|||||||
@@ -810,6 +810,8 @@
|
|||||||
allowMovement: true
|
allowMovement: true
|
||||||
description: ghost-role-information-BreadDog-description
|
description: ghost-role-information-BreadDog-description
|
||||||
rules: ghost-role-information-BreadDog-rules
|
rules: ghost-role-information-BreadDog-rules
|
||||||
|
raffle:
|
||||||
|
settings: short
|
||||||
- type: GhostTakeoverAvailable
|
- type: GhostTakeoverAvailable
|
||||||
- type: BarkAccent
|
- type: BarkAccent
|
||||||
- type: Speech
|
- type: Speech
|
||||||
@@ -834,4 +836,4 @@
|
|||||||
hidden: true
|
hidden: true
|
||||||
damage:
|
damage:
|
||||||
groups:
|
groups:
|
||||||
Brute: 1
|
Brute: 1
|
||||||
|
|||||||
@@ -672,6 +672,8 @@
|
|||||||
allowMovement: true
|
allowMovement: true
|
||||||
description: ghost-role-information-Cak-description
|
description: ghost-role-information-Cak-description
|
||||||
rules: ghost-role-information-Cak-rules
|
rules: ghost-role-information-Cak-rules
|
||||||
|
raffle:
|
||||||
|
settings: short
|
||||||
- type: GhostTakeoverAvailable
|
- type: GhostTakeoverAvailable
|
||||||
- type: OwOAccent
|
- type: OwOAccent
|
||||||
- type: Speech
|
- type: Speech
|
||||||
|
|||||||
@@ -12,6 +12,8 @@
|
|||||||
name: ghost-role-information-syndicate-reinforcement-name
|
name: ghost-role-information-syndicate-reinforcement-name
|
||||||
description: ghost-role-information-syndicate-reinforcement-description
|
description: ghost-role-information-syndicate-reinforcement-description
|
||||||
rules: ghost-role-information-syndicate-reinforcement-rules
|
rules: ghost-role-information-syndicate-reinforcement-rules
|
||||||
|
raffle:
|
||||||
|
settings: default
|
||||||
- type: GhostRoleMobSpawner
|
- type: GhostRoleMobSpawner
|
||||||
prototype: MobHumanSyndicateAgent
|
prototype: MobHumanSyndicateAgent
|
||||||
- type: EmitSoundOnUse
|
- type: EmitSoundOnUse
|
||||||
@@ -37,6 +39,8 @@
|
|||||||
name: ghost-role-information-syndicate-monkey-reinforcement-name
|
name: ghost-role-information-syndicate-monkey-reinforcement-name
|
||||||
description: ghost-role-information-syndicate-monkey-reinforcement-description
|
description: ghost-role-information-syndicate-monkey-reinforcement-description
|
||||||
rules: ghost-role-information-syndicate-monkey-reinforcement-rules
|
rules: ghost-role-information-syndicate-monkey-reinforcement-rules
|
||||||
|
raffle:
|
||||||
|
settings: default
|
||||||
- type: GhostRoleMobSpawner
|
- type: GhostRoleMobSpawner
|
||||||
prototype: MobMonkeySyndicateAgent
|
prototype: MobMonkeySyndicateAgent
|
||||||
|
|
||||||
@@ -59,5 +63,7 @@
|
|||||||
name: Syndicate Assault Cyborg
|
name: Syndicate Assault Cyborg
|
||||||
description: Nuclear operatives needs reinforcements. You, a cold silicon killing machine, will help them.
|
description: Nuclear operatives needs reinforcements. You, a cold silicon killing machine, will help them.
|
||||||
rules: Normal syndicate antagonist rules apply. Work with whoever called you in, and don't harm them.
|
rules: Normal syndicate antagonist rules apply. Work with whoever called you in, and don't harm them.
|
||||||
|
raffle:
|
||||||
|
settings: default
|
||||||
- type: GhostRoleMobSpawner
|
- type: GhostRoleMobSpawner
|
||||||
prototype: PlayerBorgSyndicateAssaultBattery
|
prototype: PlayerBorgSyndicateAssaultBattery
|
||||||
|
|||||||
@@ -11,8 +11,6 @@
|
|||||||
state: icon
|
state: icon
|
||||||
noRot: false
|
noRot: false
|
||||||
- type: ImmovableRod
|
- type: ImmovableRod
|
||||||
- type: TimedDespawn
|
|
||||||
lifetime: 30.0
|
|
||||||
- type: Physics
|
- type: Physics
|
||||||
bodyType: Dynamic
|
bodyType: Dynamic
|
||||||
linearDamping: 0
|
linearDamping: 0
|
||||||
@@ -36,8 +34,16 @@
|
|||||||
location: immovable rod
|
location: immovable rod
|
||||||
|
|
||||||
- type: entity
|
- type: entity
|
||||||
|
id: ImmovableRodDespawn
|
||||||
|
suffix: Despawn
|
||||||
parent: ImmovableRod
|
parent: ImmovableRod
|
||||||
|
components:
|
||||||
|
- type: TimedDespawn
|
||||||
|
lifetime: 30.0
|
||||||
|
|
||||||
|
- type: entity
|
||||||
id: ImmovableRodSlow
|
id: ImmovableRodSlow
|
||||||
|
parent: ImmovableRodDespawn
|
||||||
suffix: Slow
|
suffix: Slow
|
||||||
components:
|
components:
|
||||||
- type: ImmovableRod
|
- type: ImmovableRod
|
||||||
@@ -45,7 +51,7 @@
|
|||||||
maxSpeed: 5
|
maxSpeed: 5
|
||||||
|
|
||||||
- type: entity
|
- type: entity
|
||||||
parent: ImmovableRod
|
parent: ImmovableRodDespawn
|
||||||
id: ImmovableRodKeepTiles
|
id: ImmovableRodKeepTiles
|
||||||
suffix: Keep Tiles
|
suffix: Keep Tiles
|
||||||
components:
|
components:
|
||||||
@@ -53,6 +59,33 @@
|
|||||||
destroyTiles: false
|
destroyTiles: false
|
||||||
hitSoundProbability: 1.0
|
hitSoundProbability: 1.0
|
||||||
|
|
||||||
|
# For Wizard Polymorph
|
||||||
|
- type: entity
|
||||||
|
parent: ImmovableRod
|
||||||
|
id: ImmovableRodWizard
|
||||||
|
suffix: Wizard
|
||||||
|
components:
|
||||||
|
- type: ImmovableRod
|
||||||
|
minSpeed: 35
|
||||||
|
destroyTiles: false
|
||||||
|
randomizeVelocity: false
|
||||||
|
shouldGib: false
|
||||||
|
damage:
|
||||||
|
types:
|
||||||
|
Blunt: 200
|
||||||
|
- type: MovementIgnoreGravity
|
||||||
|
gravityState: true
|
||||||
|
- type: InputMover
|
||||||
|
- type: MovementSpeedModifier
|
||||||
|
weightlessAcceleration: 5
|
||||||
|
weightlessModifier: 2
|
||||||
|
weightlessFriction: 0
|
||||||
|
friction: 0
|
||||||
|
frictionNoInput: 0
|
||||||
|
- type: CanMoveInAir
|
||||||
|
- type: MovementAlwaysTouching
|
||||||
|
- type: NoSlip
|
||||||
|
|
||||||
- type: entity
|
- type: entity
|
||||||
parent: ImmovableRodKeepTiles
|
parent: ImmovableRodKeepTiles
|
||||||
id: ImmovableRodKeepTilesStill
|
id: ImmovableRodKeepTilesStill
|
||||||
|
|||||||
3
Resources/Prototypes/GhostRoleRaffles/deciders.yml
Normal file
3
Resources/Prototypes/GhostRoleRaffles/deciders.yml
Normal file
@@ -0,0 +1,3 @@
|
|||||||
|
- type: ghostRoleRaffleDecider
|
||||||
|
id: default
|
||||||
|
decider: !type:RngGhostRoleRaffleDecider {}
|
||||||
15
Resources/Prototypes/GhostRoleRaffles/settings.yml
Normal file
15
Resources/Prototypes/GhostRoleRaffles/settings.yml
Normal file
@@ -0,0 +1,15 @@
|
|||||||
|
# for important antag roles (nukie reinforcements, ninja, etc.)
|
||||||
|
- type: ghostRoleRaffleSettings
|
||||||
|
id: default
|
||||||
|
settings:
|
||||||
|
initialDuration: 30
|
||||||
|
joinExtendsDurationBy: 10
|
||||||
|
maxDuration: 90
|
||||||
|
|
||||||
|
# for roles that don't matter too much or are available plentifully (e.g. space carp)
|
||||||
|
- type: ghostRoleRaffleSettings
|
||||||
|
id: short
|
||||||
|
settings:
|
||||||
|
initialDuration: 10
|
||||||
|
joinExtendsDurationBy: 2
|
||||||
|
maxDuration: 15
|
||||||
@@ -26,6 +26,8 @@
|
|||||||
name: wizard spawn point (ghost role)
|
name: wizard spawn point (ghost role)
|
||||||
components:
|
components:
|
||||||
- type: GhostRole
|
- type: GhostRole
|
||||||
|
raffle:
|
||||||
|
settings: default
|
||||||
- type: GhostRoleMobSpawner
|
- type: GhostRoleMobSpawner
|
||||||
prototype: MobHumanSpaceWiz
|
prototype: MobHumanSpaceWiz
|
||||||
- type: Sprite
|
- type: Sprite
|
||||||
|
|||||||
Reference in New Issue
Block a user