- add: Changeling antagonist (#2)
* Changeling WIP * UI * Pointers fix * Moved out abilities * Regenerate ability * Fixed Regenerate ability Prevent ghosting while regenerating * Cleanup * Base lesser form * Finished Lesser Form && Transform * Transform Sting * Blind Sting * Mute Sting Added OnExamine on absorbed human * Hallucination Sting Changeling Absorb and transfer absorbed entities to absorber * Cryogenic Sting * Adrenaline Sacs * Transform now uses Polymorph * Armblade, Shield, Armor * Tentacle Arm ability Tentacle Gun system * WIP with bugs * WiP bugs * fix implant transfer * Fixed bugs with shop transfer and actions transfer * Just in case * Vi sitter i ventrilo och spelar DotA * Fixes and proper LesserForm tracking * !!!!! * Fixed empty buttons * WIP Gamerule Ready - shop * nerf stun time cause its sucks * cleaning * just in case * Absorb DNA Objective. * Partial objectives with bugs * fix * fix pointer * Changeling objectives * Changeling objectives №2 * Admin verb, game rule * Fixed empty list check Icons for objectives * Changeling chat, changeling names etc. * fix some merge errors * - fix: Fixed all bugs with changeling --------- Co-authored-by: Y-Parvus <yevhen.parvus@gmail.com> Co-authored-by: Y-Parvus <61109031+Y-Parvus@users.noreply.github.com> Co-authored-by: HitPanda <104197232+EnefFlow@users.noreply.github.com> Co-authored-by: EnefFlow <regeto90@mail.ru>
This commit is contained in:
@@ -1,8 +1,8 @@
|
||||
using Content.Client.Administration.Managers;
|
||||
using Content.Client.Ghost;
|
||||
using Content.Shared.Administration;
|
||||
using Content.Shared.Changeling;
|
||||
using Content.Shared.Chat;
|
||||
using Content.Shared._White.Cult;
|
||||
using Robust.Client.Console;
|
||||
using Robust.Client.Player;
|
||||
using Robust.Shared.Utility;
|
||||
@@ -15,7 +15,7 @@ namespace Content.Client.Chat.Managers
|
||||
[Dependency] private readonly IClientConsoleHost _consoleHost = default!;
|
||||
[Dependency] private readonly IClientAdminManager _adminMgr = default!;
|
||||
[Dependency] private readonly IEntitySystemManager _systems = default!;
|
||||
[Dependency] private readonly IEntityManager _entities = default!;
|
||||
[Dependency] private readonly IEntityManager _entityManager = default!;
|
||||
[Dependency] private readonly IPlayerManager _player = default!;
|
||||
|
||||
|
||||
@@ -29,7 +29,6 @@ namespace Content.Client.Chat.Managers
|
||||
|
||||
public void SendMessage(string text, ChatSelectChannel channel)
|
||||
{
|
||||
var str = text.ToString();
|
||||
switch (channel)
|
||||
{
|
||||
case ChatSelectChannel.Console:
|
||||
@@ -38,25 +37,25 @@ namespace Content.Client.Chat.Managers
|
||||
break;
|
||||
|
||||
case ChatSelectChannel.LOOC:
|
||||
_consoleHost.ExecuteCommand($"looc \"{CommandParsing.Escape(str)}\"");
|
||||
_consoleHost.ExecuteCommand($"looc \"{CommandParsing.Escape(text)}\"");
|
||||
break;
|
||||
|
||||
case ChatSelectChannel.OOC:
|
||||
_consoleHost.ExecuteCommand($"ooc \"{CommandParsing.Escape(str)}\"");
|
||||
_consoleHost.ExecuteCommand($"ooc \"{CommandParsing.Escape(text)}\"");
|
||||
break;
|
||||
|
||||
case ChatSelectChannel.Admin:
|
||||
_consoleHost.ExecuteCommand($"asay \"{CommandParsing.Escape(str)}\"");
|
||||
_consoleHost.ExecuteCommand($"asay \"{CommandParsing.Escape(text)}\"");
|
||||
break;
|
||||
|
||||
case ChatSelectChannel.Emotes:
|
||||
_consoleHost.ExecuteCommand($"me \"{CommandParsing.Escape(str)}\"");
|
||||
_consoleHost.ExecuteCommand($"me \"{CommandParsing.Escape(text)}\"");
|
||||
break;
|
||||
|
||||
case ChatSelectChannel.Cult:
|
||||
var localEnt = _player.LocalPlayer != null ? _player.LocalPlayer.ControlledEntity : null;
|
||||
if (_entities.TryGetComponent(localEnt, out CultistComponent? comp))
|
||||
_consoleHost.ExecuteCommand($"csay \"{CommandParsing.Escape(str)}\"");
|
||||
if (_entityManager.TryGetComponent(localEnt, out CultistComponent? comp))
|
||||
_consoleHost.ExecuteCommand($"csay \"{CommandParsing.Escape(text)}\"");
|
||||
break;
|
||||
|
||||
case ChatSelectChannel.Dead:
|
||||
@@ -64,7 +63,7 @@ namespace Content.Client.Chat.Managers
|
||||
goto case ChatSelectChannel.Local;
|
||||
|
||||
if (_adminMgr.HasFlag(AdminFlags.Admin))
|
||||
_consoleHost.ExecuteCommand($"dsay \"{CommandParsing.Escape(str)}\"");
|
||||
_consoleHost.ExecuteCommand($"dsay \"{CommandParsing.Escape(text)}\"");
|
||||
else
|
||||
_sawmill.Warning("Tried to speak on deadchat without being ghost or admin.");
|
||||
break;
|
||||
@@ -72,13 +71,20 @@ namespace Content.Client.Chat.Managers
|
||||
// TODO sepearate radio and say into separate commands.
|
||||
case ChatSelectChannel.Radio:
|
||||
case ChatSelectChannel.Local:
|
||||
_consoleHost.ExecuteCommand($"say \"{CommandParsing.Escape(str)}\"");
|
||||
_consoleHost.ExecuteCommand($"say \"{CommandParsing.Escape(text)}\"");
|
||||
break;
|
||||
|
||||
case ChatSelectChannel.Whisper:
|
||||
_consoleHost.ExecuteCommand($"whisper \"{CommandParsing.Escape(str)}\"");
|
||||
_consoleHost.ExecuteCommand($"whisper \"{CommandParsing.Escape(text)}\"");
|
||||
break;
|
||||
|
||||
case ChatSelectChannel.Changeling:
|
||||
var localEntity = _player.LocalPlayer != null ? _player.LocalPlayer.ControlledEntity : null;
|
||||
if (_entityManager.HasComponent<ChangelingComponent>(localEntity))
|
||||
_consoleHost.ExecuteCommand($"gsay \"{CommandParsing.Escape(text)}\"");
|
||||
break;
|
||||
|
||||
|
||||
default:
|
||||
throw new ArgumentOutOfRangeException(nameof(channel), channel, null);
|
||||
}
|
||||
|
||||
8
Content.Client/Miracle/Changeling/TentacleGun.cs
Normal file
8
Content.Client/Miracle/Changeling/TentacleGun.cs
Normal file
@@ -0,0 +1,8 @@
|
||||
using Content.Shared.Changeling;
|
||||
|
||||
namespace Content.Client.Miracle.Changeling;
|
||||
|
||||
public sealed class TentacleGun : SharedTentacleGun
|
||||
{
|
||||
|
||||
}
|
||||
@@ -0,0 +1,9 @@
|
||||
<DefaultWindow xmlns="https://spacestation14.io"
|
||||
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
|
||||
Title="Changeling UI"
|
||||
MinWidth="350"
|
||||
MinHeight="400">
|
||||
<ScrollContainer HorizontalExpand="True" VerticalExpand="True">
|
||||
<BoxContainer Name="ItemsContainer" Orientation="Vertical"></BoxContainer>
|
||||
</ScrollContainer>
|
||||
</DefaultWindow>
|
||||
@@ -0,0 +1,33 @@
|
||||
using Robust.Client.AutoGenerated;
|
||||
using Robust.Client.UserInterface.Controls;
|
||||
using Robust.Client.UserInterface.CustomControls;
|
||||
using Robust.Client.UserInterface.XAML;
|
||||
|
||||
namespace Content.Client.Miracle.Changeling.UI.ListViewSelector;
|
||||
|
||||
[GenerateTypedNameReferences]
|
||||
public sealed partial class ListViewChangelingSelectorWindow : DefaultWindow
|
||||
{
|
||||
public Action<string>? ItemSelected;
|
||||
|
||||
public ListViewChangelingSelectorWindow()
|
||||
{
|
||||
RobustXamlLoader.Load(this);
|
||||
}
|
||||
|
||||
public void PopulateList(Dictionary<string, string> items)
|
||||
{
|
||||
ItemsContainer.RemoveAllChildren();
|
||||
|
||||
foreach (var item in items)
|
||||
{
|
||||
var button = new Button();
|
||||
|
||||
button.Text = item.Value;
|
||||
|
||||
button.OnPressed += _ => ItemSelected?.Invoke(item.Key);
|
||||
|
||||
ItemsContainer.AddChild(button);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,51 @@
|
||||
using Content.Shared.Miracle.UI;
|
||||
|
||||
namespace Content.Client.Miracle.Changeling.UI.ListViewSelector;
|
||||
|
||||
public sealed class ListViewSelectorBui : BoundUserInterface
|
||||
{
|
||||
private ListViewChangelingSelectorWindow? _window;
|
||||
|
||||
public ListViewSelectorBui(EntityUid owner, Enum uiKey) : base(owner, uiKey)
|
||||
{
|
||||
IoCManager.InjectDependencies(this);
|
||||
}
|
||||
|
||||
protected override void Open()
|
||||
{
|
||||
base.Open();
|
||||
|
||||
_window = new ListViewChangelingSelectorWindow();
|
||||
_window.OpenCentered();
|
||||
_window.OnClose += Close;
|
||||
|
||||
_window.ItemSelected += item =>
|
||||
{
|
||||
var msg = new ListViewItemSelectedMessage(item);
|
||||
SendMessage(msg);
|
||||
};
|
||||
|
||||
if(State != null)
|
||||
UpdateState(State);
|
||||
}
|
||||
|
||||
protected override void UpdateState(BoundUserInterfaceState state)
|
||||
{
|
||||
base.UpdateState(state);
|
||||
|
||||
if (state is ListViewBuiState newState)
|
||||
{
|
||||
_window?.PopulateList(newState.Items);
|
||||
}
|
||||
}
|
||||
|
||||
protected override void Dispose(bool disposing)
|
||||
{
|
||||
base.Dispose(disposing);
|
||||
|
||||
if (!disposing)
|
||||
return;
|
||||
|
||||
_window?.Close();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,9 @@
|
||||
<DefaultWindow xmlns="https://spacestation14.io"
|
||||
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
|
||||
Title="Changeling UI"
|
||||
MinWidth="350"
|
||||
MinHeight="400">
|
||||
<ScrollContainer HorizontalExpand="True" VerticalExpand="True">
|
||||
<BoxContainer Name="ItemsContainer" Orientation="Vertical"></BoxContainer>
|
||||
</ScrollContainer>
|
||||
</DefaultWindow>
|
||||
@@ -0,0 +1,33 @@
|
||||
using Robust.Client.AutoGenerated;
|
||||
using Robust.Client.UserInterface.Controls;
|
||||
using Robust.Client.UserInterface.CustomControls;
|
||||
using Robust.Client.UserInterface.XAML;
|
||||
|
||||
namespace Content.Client.Miracle.Changeling.UI.TransformStingUI;
|
||||
|
||||
[GenerateTypedNameReferences]
|
||||
public sealed partial class TransformStingSelectorWindow : DefaultWindow
|
||||
{
|
||||
public Action<string, NetEntity>? ItemSelected;
|
||||
|
||||
public TransformStingSelectorWindow()
|
||||
{
|
||||
RobustXamlLoader.Load(this);
|
||||
}
|
||||
|
||||
public void PopulateList(Dictionary<string, string> items, NetEntity target)
|
||||
{
|
||||
ItemsContainer.RemoveAllChildren();
|
||||
|
||||
foreach (var item in items)
|
||||
{
|
||||
var button = new Button();
|
||||
|
||||
button.Text = item.Value;
|
||||
|
||||
button.OnPressed += _ => ItemSelected?.Invoke(item.Key, target);
|
||||
|
||||
ItemsContainer.AddChild(button);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,51 @@
|
||||
using Content.Shared.Miracle.UI;
|
||||
|
||||
namespace Content.Client.Miracle.Changeling.UI.TransformStingUI;
|
||||
|
||||
public sealed class TransformStingSelectorBui : BoundUserInterface
|
||||
{
|
||||
private TransformStingSelectorWindow? _window;
|
||||
|
||||
public TransformStingSelectorBui(EntityUid owner, Enum uiKey) : base(owner, uiKey)
|
||||
{
|
||||
IoCManager.InjectDependencies(this);
|
||||
}
|
||||
|
||||
protected override void Open()
|
||||
{
|
||||
base.Open();
|
||||
|
||||
_window = new TransformStingSelectorWindow();
|
||||
_window.OpenCentered();
|
||||
_window.OnClose += Close;
|
||||
|
||||
_window.ItemSelected += (item, target) =>
|
||||
{
|
||||
var msg = new TransformStingItemSelectedMessage(item, target);
|
||||
SendMessage(msg);
|
||||
};
|
||||
|
||||
if(State != null)
|
||||
UpdateState(State);
|
||||
}
|
||||
|
||||
protected override void UpdateState(BoundUserInterfaceState state)
|
||||
{
|
||||
base.UpdateState(state);
|
||||
|
||||
if (state is TransformStingBuiState newState)
|
||||
{
|
||||
_window?.PopulateList(newState.Items, newState.Target);
|
||||
}
|
||||
}
|
||||
|
||||
protected override void Dispose(bool disposing)
|
||||
{
|
||||
base.Dispose(disposing);
|
||||
|
||||
if (!disposing)
|
||||
return;
|
||||
|
||||
_window?.Close();
|
||||
}
|
||||
}
|
||||
@@ -13,6 +13,7 @@ using Content.Client.UserInterface.Systems.Chat.Widgets;
|
||||
using Content.Client.UserInterface.Systems.Gameplay;
|
||||
using Content.Shared.Administration;
|
||||
using Content.Shared.CCVar;
|
||||
using Content.Shared.Changeling;
|
||||
using Content.Shared.Chat;
|
||||
using Content.Shared.Damage.ForceSay;
|
||||
using Content.Shared.Examine;
|
||||
@@ -75,7 +76,8 @@ public sealed class ChatUIController : UIController
|
||||
{SharedChatSystem.AdminPrefix, ChatSelectChannel.Admin},
|
||||
{SharedChatSystem.RadioCommonPrefix, ChatSelectChannel.Radio},
|
||||
{SharedChatSystem.DeadPrefix, ChatSelectChannel.Dead},
|
||||
{SharedChatSystem.CultPrefix, ChatSelectChannel.Cult}, //WD EDIT
|
||||
{SharedChatSystem.CultPrefix, ChatSelectChannel.Cult},
|
||||
{SharedChatSystem.ChangelingPrefix, ChatSelectChannel.Changeling}
|
||||
};
|
||||
|
||||
public static readonly Dictionary<ChatSelectChannel, char> ChannelPrefixes = new()
|
||||
@@ -89,7 +91,8 @@ public sealed class ChatUIController : UIController
|
||||
{ChatSelectChannel.Admin, SharedChatSystem.AdminPrefix},
|
||||
{ChatSelectChannel.Radio, SharedChatSystem.RadioCommonPrefix},
|
||||
{ChatSelectChannel.Dead, SharedChatSystem.DeadPrefix},
|
||||
{ChatSelectChannel.Cult, SharedChatSystem.CultPrefix} // WD EDIT
|
||||
{ChatSelectChannel.Cult, SharedChatSystem.CultPrefix},
|
||||
{ChatSelectChannel.Changeling, SharedChatSystem.ChangelingPrefix}
|
||||
|
||||
};
|
||||
|
||||
@@ -203,6 +206,9 @@ public sealed class ChatUIController : UIController
|
||||
_input.SetInputCommand(ContentKeyFunctions.FocusAdminChat,
|
||||
InputCmdHandler.FromDelegate(_ => FocusChannel(ChatSelectChannel.Admin)));
|
||||
|
||||
_input.SetInputCommand(ContentKeyFunctions.FocusChangelingChat,
|
||||
InputCmdHandler.FromDelegate(_ => FocusChannel(ChatSelectChannel.Changeling)));
|
||||
|
||||
_input.SetInputCommand(ContentKeyFunctions.FocusCultChat,
|
||||
InputCmdHandler.FromDelegate(_ => FocusChannel(ChatSelectChannel.Cult)));
|
||||
|
||||
@@ -221,6 +227,7 @@ public sealed class ChatUIController : UIController
|
||||
_input.SetInputCommand(ContentKeyFunctions.CycleChatChannelBackward,
|
||||
InputCmdHandler.FromDelegate(_ => CycleChatChannel(false)));
|
||||
|
||||
SubscribeLocalEvent<ChangelingUserStart>(OnUpdateChangelingChat);
|
||||
// WD EDIT
|
||||
SubscribeLocalEvent<EventCultistComponentState>(OnUpdateCultState);
|
||||
// WD EDIT END
|
||||
@@ -230,6 +237,11 @@ public sealed class ChatUIController : UIController
|
||||
gameplayStateLoad.OnScreenUnload += OnScreenUnload;
|
||||
}
|
||||
|
||||
private void OnUpdateChangelingChat(ChangelingUserStart ev)
|
||||
{
|
||||
UpdateChannelPermissions();
|
||||
}
|
||||
|
||||
// WD EDIT
|
||||
private void OnUpdateCultState(EventCultistComponentState ev)
|
||||
{
|
||||
|
||||
@@ -23,6 +23,7 @@ public sealed partial class ChannelFilterPopup : Popup
|
||||
ChatChannel.AdminAlert,
|
||||
ChatChannel.AdminChat,
|
||||
ChatChannel.Server,
|
||||
ChatChannel.Changeling,
|
||||
ChatChannel.Cult // WD EDIT
|
||||
};
|
||||
|
||||
|
||||
@@ -64,6 +64,7 @@ public sealed class ChannelSelectorButton : ChatPopupButton<ChannelSelectorPopup
|
||||
ChatSelectChannel.OOC => Color.LightSkyBlue,
|
||||
ChatSelectChannel.Dead => Color.MediumPurple,
|
||||
ChatSelectChannel.Admin => Color.HotPink,
|
||||
ChatSelectChannel.Changeling => Color.Purple,
|
||||
ChatSelectChannel.Cult => Color.DarkRed,
|
||||
_ => Color.DarkGray
|
||||
};
|
||||
|
||||
@@ -17,6 +17,7 @@ public sealed class ChannelSelectorPopup : Popup
|
||||
ChatSelectChannel.OOC,
|
||||
ChatSelectChannel.Dead,
|
||||
ChatSelectChannel.Admin,
|
||||
ChatSelectChannel.Changeling,
|
||||
ChatSelectChannel.Cult // WD EDIT
|
||||
// NOTE: Console is not in there and it can never be permanently selected.
|
||||
// You can, however, still submit commands as console by prefixing with /.
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
using Content.Server.Changeling;
|
||||
using Content.Server.GameTicking;
|
||||
using Content.Server.GameTicking.Rules;
|
||||
using Content.Server.StationEvents.Events;
|
||||
@@ -23,6 +24,7 @@ public sealed partial class AdminVerbSystem
|
||||
[Dependency] private readonly ZombieSystem _zombie = default!;
|
||||
[Dependency] private readonly ThiefRuleSystem _thief = default!;
|
||||
[Dependency] private readonly TraitorRuleSystem _traitorRule = default!;
|
||||
[Dependency] private readonly ChangelingRuleSystem _changelingRule = default!;
|
||||
[Dependency] private readonly NukeopsRuleSystem _nukeopsRule = default!;
|
||||
[Dependency] private readonly PiratesRuleSystem _piratesRule = default!;
|
||||
[Dependency] private readonly RevolutionaryRuleSystem _revolutionaryRule = default!;
|
||||
@@ -63,6 +65,25 @@ public sealed partial class AdminVerbSystem
|
||||
};
|
||||
args.Verbs.Add(traitor);
|
||||
|
||||
Verb changeling = new()
|
||||
{
|
||||
Text = Loc.GetString("admin-verb-text-make-changeling"),
|
||||
Category = VerbCategory.Antag,
|
||||
Icon = new SpriteSpecifier.Texture(new ("/Textures/Interface/Actions/ling_absorb.png")),
|
||||
Act = () =>
|
||||
{
|
||||
if (!_minds.TryGetSession(targetMindComp.Mind, out var session))
|
||||
return;
|
||||
|
||||
var isHuman = HasComp<HumanoidAppearanceComponent>(args.Target);
|
||||
_changelingRule.MakeChangeling(session);
|
||||
},
|
||||
Impact = LogImpact.High,
|
||||
Message = Loc.GetString("admin-verb-make-changeling"),
|
||||
};
|
||||
|
||||
args.Verbs.Add(changeling);
|
||||
|
||||
Verb zombie = new()
|
||||
{
|
||||
Text = Loc.GetString("admin-verb-text-make-zombie"),
|
||||
|
||||
8
Content.Server/Changeling/ChangelingRoleComponent.cs
Normal file
8
Content.Server/Changeling/ChangelingRoleComponent.cs
Normal file
@@ -0,0 +1,8 @@
|
||||
using Content.Shared.Roles;
|
||||
|
||||
namespace Content.Server.Changeling;
|
||||
|
||||
[RegisterComponent]
|
||||
public sealed partial class ChangelingRoleComponent : AntagonistRoleComponent
|
||||
{
|
||||
}
|
||||
35
Content.Server/Changeling/ChangelingRuleComponent.cs
Normal file
35
Content.Server/Changeling/ChangelingRuleComponent.cs
Normal file
@@ -0,0 +1,35 @@
|
||||
using Content.Shared.Preferences;
|
||||
using Content.Shared.Roles;
|
||||
using Robust.Shared.Audio;
|
||||
using Robust.Shared.Player;
|
||||
using Robust.Shared.Serialization.TypeSerializers.Implementations.Custom.Prototype;
|
||||
|
||||
namespace Content.Server.Changeling;
|
||||
|
||||
[RegisterComponent, Access(typeof(ChangelingRuleSystem))]
|
||||
public sealed partial class ChangelingRuleComponent : Component
|
||||
{
|
||||
public readonly List<EntityUid> ChangelingMinds = new();
|
||||
|
||||
[DataField("changelingPrototypeId", customTypeSerializer: typeof(PrototypeIdSerializer<AntagPrototype>))]
|
||||
public string ChangelingPrototypeId = "Changeling";
|
||||
|
||||
public int TotalChangelings => ChangelingMinds.Count;
|
||||
|
||||
public enum SelectionState
|
||||
{
|
||||
WaitingForSpawn = 0,
|
||||
ReadyToSelect = 1,
|
||||
SelectionMade = 2,
|
||||
}
|
||||
|
||||
public SelectionState SelectionStatus = SelectionState.WaitingForSpawn;
|
||||
public TimeSpan AnnounceAt = TimeSpan.Zero;
|
||||
public Dictionary<ICommonSession, HumanoidCharacterProfile> StartCandidates = new();
|
||||
|
||||
/// <summary>
|
||||
/// Path to antagonist alert sound.
|
||||
/// </summary>
|
||||
[DataField("greetSoundNotification")]
|
||||
public SoundSpecifier GreetSoundNotification = new SoundPathSpecifier("/Audio/Ambience/Antag/changeling_start.ogg");
|
||||
}
|
||||
275
Content.Server/Changeling/ChangelingRuleSystem.cs
Normal file
275
Content.Server/Changeling/ChangelingRuleSystem.cs
Normal file
@@ -0,0 +1,275 @@
|
||||
using System.Linq;
|
||||
using Content.Server.Antag;
|
||||
using Content.Server.Chat.Managers;
|
||||
using Content.Server.GameTicking;
|
||||
using Content.Server.GameTicking.Rules;
|
||||
using Content.Server.GameTicking.Rules.Components;
|
||||
using Content.Server.Mind;
|
||||
using Content.Server.NPC.Systems;
|
||||
using Content.Server.Objectives;
|
||||
using Content.Server.Roles;
|
||||
using Content.Shared.Changeling;
|
||||
using Content.Shared.GameTicking;
|
||||
using Content.Shared.Mobs.Systems;
|
||||
using Content.Shared.Objectives.Components;
|
||||
using Content.Shared.Roles;
|
||||
using Robust.Shared.Player;
|
||||
using Robust.Shared.Prototypes;
|
||||
using Robust.Shared.Random;
|
||||
using Robust.Shared.Timing;
|
||||
|
||||
namespace Content.Server.Changeling;
|
||||
|
||||
public sealed class ChangelingRuleSystem : GameRuleSystem<ChangelingRuleComponent>
|
||||
{
|
||||
[Dependency] private readonly AntagSelectionSystem _antagSelection = default!;
|
||||
[Dependency] private readonly IPrototypeManager _prototypeManager = default!;
|
||||
[Dependency] private readonly IRobustRandom _random = default!;
|
||||
[Dependency] private readonly IChatManager _chatManager = default!;
|
||||
[Dependency] private readonly IGameTiming _gameTiming = default!;
|
||||
[Dependency] private readonly NpcFactionSystem _npcFaction = default!;
|
||||
[Dependency] private readonly MindSystem _mindSystem = default!;
|
||||
[Dependency] private readonly SharedRoleSystem _roleSystem = default!;
|
||||
[Dependency] private readonly ObjectivesSystem _objectives = default!;
|
||||
[Dependency] private readonly ChangelingNameGenerator _nameGenerator = default!;
|
||||
|
||||
private const int PlayersPerChangeling = 10;
|
||||
private const int MaxChangelings = 5;
|
||||
|
||||
private const float ChangelingStartDelay = 3f * 60;
|
||||
private const float ChangelingStartDelayVariance = 3f * 60;
|
||||
|
||||
private const int ChangelingMinPlayers = 10;
|
||||
|
||||
private const int ChangelingMaxDifficulty = 5;
|
||||
private const int ChangelingMaxPicks = 20;
|
||||
|
||||
public override void Initialize()
|
||||
{
|
||||
base.Initialize();
|
||||
|
||||
SubscribeLocalEvent<RoundStartAttemptEvent>(OnStartAttempt);
|
||||
SubscribeLocalEvent<RulePlayerJobsAssignedEvent>(OnPlayersSpawned);
|
||||
SubscribeLocalEvent<PlayerSpawnCompleteEvent>(HandleLatejoin);
|
||||
SubscribeLocalEvent<RoundRestartCleanupEvent>(ClearUsedNames);
|
||||
|
||||
SubscribeLocalEvent<ChangelingRuleComponent, ObjectivesTextGetInfoEvent>(OnObjectivesTextGetInfo);
|
||||
}
|
||||
|
||||
protected override void ActiveTick(EntityUid uid, ChangelingRuleComponent component, GameRuleComponent gameRule, float frameTime)
|
||||
{
|
||||
base.ActiveTick(uid, component, gameRule, frameTime);
|
||||
|
||||
if (component.SelectionStatus == ChangelingRuleComponent.SelectionState.ReadyToSelect && _gameTiming.CurTime > component.AnnounceAt)
|
||||
DoChangelingStart(component);
|
||||
}
|
||||
|
||||
private void OnStartAttempt(RoundStartAttemptEvent ev)
|
||||
{
|
||||
var query = EntityQueryEnumerator<ChangelingRuleComponent, GameRuleComponent>();
|
||||
while (query.MoveNext(out var uid, out _, out var gameRule))
|
||||
{
|
||||
if (!GameTicker.IsGameRuleAdded(uid, gameRule))
|
||||
continue;
|
||||
|
||||
var minPlayers = ChangelingMinPlayers;
|
||||
if (!ev.Forced && ev.Players.Length < minPlayers)
|
||||
{
|
||||
_chatManager.SendAdminAnnouncement(Loc.GetString("changeling-not-enough-ready-players",
|
||||
("readyPlayersCount", ev.Players.Length), ("minimumPlayers", minPlayers)));
|
||||
ev.Cancel();
|
||||
continue;
|
||||
}
|
||||
|
||||
if (ev.Players.Length == 0)
|
||||
{
|
||||
_chatManager.DispatchServerAnnouncement(Loc.GetString("changeling-no-one-ready"));
|
||||
ev.Cancel();
|
||||
}
|
||||
}
|
||||
}
|
||||
private void DoChangelingStart(ChangelingRuleComponent component)
|
||||
{
|
||||
if (!component.StartCandidates.Any())
|
||||
{
|
||||
Log.Error("Tried to start Changeling mode without any candidates.");
|
||||
return;
|
||||
}
|
||||
|
||||
var numChangelings = MathHelper.Clamp(component.StartCandidates.Count / PlayersPerChangeling, 1, MaxChangelings);
|
||||
var changelingPool = _antagSelection.FindPotentialAntags(component.StartCandidates, component.ChangelingPrototypeId);
|
||||
var selectedChangelings = _antagSelection.PickAntag(numChangelings, changelingPool);
|
||||
|
||||
foreach (var changeling in selectedChangelings)
|
||||
{
|
||||
MakeChangeling(changeling);
|
||||
}
|
||||
|
||||
component.SelectionStatus = ChangelingRuleComponent.SelectionState.SelectionMade;
|
||||
}
|
||||
|
||||
private void OnPlayersSpawned(RulePlayerJobsAssignedEvent ev)
|
||||
{
|
||||
var query = EntityQueryEnumerator<ChangelingRuleComponent, GameRuleComponent>();
|
||||
while (query.MoveNext(out var uid, out var changeling, out var gameRule))
|
||||
{
|
||||
if (!GameTicker.IsGameRuleAdded(uid, gameRule))
|
||||
continue;
|
||||
foreach (var player in ev.Players)
|
||||
{
|
||||
if (!ev.Profiles.ContainsKey(player.UserId))
|
||||
continue;
|
||||
|
||||
changeling.StartCandidates[player] = ev.Profiles[player.UserId];
|
||||
}
|
||||
|
||||
var delay = TimeSpan.FromSeconds(ChangelingStartDelay + _random.NextFloat(0f, ChangelingStartDelayVariance));
|
||||
|
||||
changeling.AnnounceAt = _gameTiming.CurTime + delay;
|
||||
|
||||
changeling.SelectionStatus = ChangelingRuleComponent.SelectionState.ReadyToSelect;
|
||||
}
|
||||
}
|
||||
|
||||
public bool MakeChangeling(ICommonSession changeling, bool giveObjectives = true)
|
||||
{
|
||||
var changelingRule = EntityQuery<ChangelingRuleComponent>().FirstOrDefault();
|
||||
if (changelingRule == null)
|
||||
{
|
||||
GameTicker.StartGameRule("Changeling", out var ruleEntity);
|
||||
changelingRule = Comp<ChangelingRuleComponent>(ruleEntity);
|
||||
}
|
||||
|
||||
if (!_mindSystem.TryGetMind(changeling, out var mindId, out var mind))
|
||||
{
|
||||
Log.Info("Failed getting mind for picked changeling.");
|
||||
return false;
|
||||
}
|
||||
|
||||
if (HasComp<ChangelingRoleComponent>(mindId))
|
||||
{
|
||||
Log.Error($"Player {changeling.Name} is already a changeling.");
|
||||
return false;
|
||||
}
|
||||
|
||||
if (mind.OwnedEntity is not { } entity)
|
||||
{
|
||||
Log.Error("Mind picked for changeling did not have an attached entity.");
|
||||
return false;
|
||||
}
|
||||
|
||||
_roleSystem.MindAddRole(mindId, new ChangelingRoleComponent
|
||||
{
|
||||
PrototypeId = changelingRule.ChangelingPrototypeId
|
||||
}, mind);
|
||||
|
||||
var briefing = Loc.GetString("changeling-role-briefing-short");
|
||||
|
||||
_roleSystem.MindAddRole(mindId, new RoleBriefingComponent
|
||||
{
|
||||
Briefing = briefing
|
||||
}, mind, true);
|
||||
|
||||
_roleSystem.MindPlaySound(mindId, changelingRule.GreetSoundNotification, mind);
|
||||
SendChangelingBriefing(mindId);
|
||||
changelingRule.ChangelingMinds.Add(mindId);
|
||||
|
||||
// Change the faction
|
||||
_npcFaction.RemoveFaction(entity, "NanoTrasen", false);
|
||||
_npcFaction.AddFaction(entity, "Syndicate");
|
||||
|
||||
EnsureComp<ChangelingComponent>(entity, out var readyChangeling);
|
||||
|
||||
readyChangeling.HiveName = _nameGenerator.GetName();
|
||||
Dirty(entity, readyChangeling);
|
||||
|
||||
|
||||
if (giveObjectives)
|
||||
{
|
||||
var maxDifficulty = ChangelingMaxDifficulty;
|
||||
var maxPicks = ChangelingMaxPicks;
|
||||
var difficulty = 0f;
|
||||
for (var pick = 0; pick < maxPicks && maxDifficulty > difficulty; pick++)
|
||||
{
|
||||
var objective = _objectives.GetRandomObjective(mindId, mind, "ChangelingObjectiveGroups");
|
||||
if (objective == null)
|
||||
continue;
|
||||
|
||||
_mindSystem.AddObjective(mindId, mind, objective.Value);
|
||||
difficulty += Comp<ObjectiveComponent>(objective.Value).Difficulty;
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
private void SendChangelingBriefing(EntityUid mind)
|
||||
{
|
||||
if (!_mindSystem.TryGetSession(mind, out var session))
|
||||
return;
|
||||
|
||||
_chatManager.DispatchServerMessage(session, Loc.GetString("changeling-role-greeting"));
|
||||
}
|
||||
|
||||
private void HandleLatejoin(PlayerSpawnCompleteEvent ev)
|
||||
{
|
||||
var query = EntityQueryEnumerator<ChangelingRuleComponent, GameRuleComponent>();
|
||||
while (query.MoveNext(out var uid, out var changeling, out var gameRule))
|
||||
{
|
||||
if (!GameTicker.IsGameRuleAdded(uid, gameRule))
|
||||
continue;
|
||||
|
||||
if (changeling.TotalChangelings >= MaxChangelings)
|
||||
continue;
|
||||
if (!ev.LateJoin)
|
||||
continue;
|
||||
if (!ev.Profile.AntagPreferences.Contains(changeling.ChangelingPrototypeId))
|
||||
continue;
|
||||
|
||||
if (ev.JobId == null || !_prototypeManager.TryIndex<JobPrototype>(ev.JobId, out var job))
|
||||
continue;
|
||||
|
||||
if (!job.CanBeAntag)
|
||||
continue;
|
||||
|
||||
// Before the announcement is made, late-joiners are considered the same as players who readied.
|
||||
if (changeling.SelectionStatus < ChangelingRuleComponent.SelectionState.SelectionMade)
|
||||
{
|
||||
changeling.StartCandidates[ev.Player] = ev.Profile;
|
||||
continue;
|
||||
}
|
||||
|
||||
var target = PlayersPerChangeling * changeling.TotalChangelings + 1;
|
||||
|
||||
var chance = 1f / PlayersPerChangeling;
|
||||
|
||||
if (ev.JoinOrder < target)
|
||||
{
|
||||
chance /= (target - ev.JoinOrder);
|
||||
}
|
||||
else
|
||||
{
|
||||
chance *= ((ev.JoinOrder + 1) - target);
|
||||
}
|
||||
|
||||
if (chance > 1)
|
||||
chance = 1;
|
||||
|
||||
if (_random.Prob(chance))
|
||||
{
|
||||
MakeChangeling(ev.Player);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void OnObjectivesTextGetInfo(EntityUid uid, ChangelingRuleComponent comp, ref ObjectivesTextGetInfoEvent args)
|
||||
{
|
||||
args.Minds = comp.ChangelingMinds;
|
||||
args.AgentName = Loc.GetString("changeling-round-end-agent-name");
|
||||
}
|
||||
|
||||
private void ClearUsedNames(RoundRestartCleanupEvent ev)
|
||||
{
|
||||
_nameGenerator.ClearUsed();
|
||||
}
|
||||
}
|
||||
879
Content.Server/Changeling/ChangelingSystem.Abilities.cs
Normal file
879
Content.Server/Changeling/ChangelingSystem.Abilities.cs
Normal file
@@ -0,0 +1,879 @@
|
||||
using System.Linq;
|
||||
using Content.Server.Administration.Systems;
|
||||
using Content.Server.DoAfter;
|
||||
using Content.Server.Forensics;
|
||||
using Content.Server.Humanoid;
|
||||
using Content.Server.IdentityManagement;
|
||||
using Content.Server.Mind;
|
||||
using Content.Server.Polymorph.Systems;
|
||||
using Content.Server.Popups;
|
||||
using Content.Server.Store.Components;
|
||||
using Content.Server.Temperature.Systems;
|
||||
using Content.Shared.Actions;
|
||||
using Content.Shared.Changeling;
|
||||
using Content.Shared.Chemistry.EntitySystems;
|
||||
using Content.Shared.Damage;
|
||||
using Content.Shared.DoAfter;
|
||||
using Content.Shared.Eye.Blinding.Components;
|
||||
using Content.Shared.Eye.Blinding.Systems;
|
||||
using Content.Shared.FixedPoint;
|
||||
using Content.Shared.Hands.EntitySystems;
|
||||
using Content.Shared.Humanoid;
|
||||
using Content.Shared.Humanoid.Markings;
|
||||
using Content.Shared.Implants.Components;
|
||||
using Content.Shared.Inventory;
|
||||
using Content.Shared.Miracle.UI;
|
||||
using Content.Shared.Mobs;
|
||||
using Content.Shared.Mobs.Systems;
|
||||
using Content.Shared.Pulling;
|
||||
using Content.Shared.Pulling.Components;
|
||||
using Content.Shared.Standing;
|
||||
using Content.Shared.StatusEffect;
|
||||
using Robust.Server.GameObjects;
|
||||
using Robust.Shared.GameObjects.Components.Localization;
|
||||
using Robust.Shared.Player;
|
||||
using Robust.Shared.Random;
|
||||
using Robust.Shared.Serialization.Manager;
|
||||
|
||||
namespace Content.Server.Changeling;
|
||||
|
||||
public sealed partial class ChangelingSystem
|
||||
{
|
||||
[Dependency] private readonly PopupSystem _popup = default!;
|
||||
[Dependency] private readonly DoAfterSystem _doAfterSystem = default!;
|
||||
[Dependency] private readonly MobThresholdSystem _mobThresholdSystem = default!;
|
||||
[Dependency] private readonly MobStateSystem _mobStateSystem = default!;
|
||||
[Dependency] private readonly DamageableSystem _damage = default!;
|
||||
[Dependency] private readonly StandingStateSystem _stateSystem = default!;
|
||||
[Dependency] private readonly HumanoidAppearanceSystem _humanoidAppearance = default!;
|
||||
[Dependency] private readonly MetaDataSystem _metaData = default!;
|
||||
[Dependency] private readonly IdentitySystem _identity = default!;
|
||||
[Dependency] private readonly UserInterfaceSystem _ui = default!;
|
||||
[Dependency] private readonly ISerializationManager _serializationManager = default!;
|
||||
[Dependency] private readonly RejuvenateSystem _rejuvenate = default!;
|
||||
[Dependency] private readonly PolymorphSystem _polymorph = default!;
|
||||
[Dependency] private readonly StatusEffectsSystem _statusEffectsSystem = default!;
|
||||
[Dependency] private readonly TemperatureSystem _temperatureSystem = default!;
|
||||
[Dependency] private readonly SharedSolutionContainerSystem _solutionContainer = default!;
|
||||
[Dependency] private readonly SharedHandsSystem _handsSystem = default!;
|
||||
[Dependency] private readonly InventorySystem _inventorySystem = default!;
|
||||
[Dependency] private readonly IRobustRandom _random = default!;
|
||||
[Dependency] private readonly ActionContainerSystem _actionContainerSystem = default!;
|
||||
[Dependency] private readonly SharedPullingSystem _pullingSystem = default!;
|
||||
[Dependency] private readonly MindSystem _mindSystem = default!;
|
||||
|
||||
|
||||
private void InitializeAbilities()
|
||||
{
|
||||
SubscribeLocalEvent<ChangelingComponent, AbsorbDnaActionEvent>(OnAbsorb);
|
||||
SubscribeLocalEvent<ChangelingComponent, TransformActionEvent>(OnTransform);
|
||||
SubscribeLocalEvent<ChangelingComponent, RegenerateActionEvent>(OnRegenerate);
|
||||
SubscribeLocalEvent<ChangelingComponent, LesserFormActionEvent>(OnLesserForm);
|
||||
|
||||
SubscribeLocalEvent<ChangelingComponent, TransformStingActionEvent>(OnTransformSting);
|
||||
SubscribeLocalEvent<ChangelingComponent, TransformStingItemSelectedMessage>(OnTransformStingMessage);
|
||||
SubscribeLocalEvent<ChangelingComponent, BlindStingActionEvent>(OnBlindSting);
|
||||
SubscribeLocalEvent<ChangelingComponent, MuteStingActionEvent>(OnMuteSting);
|
||||
SubscribeLocalEvent<ChangelingComponent, HallucinationStingActionEvent>(OnHallucinationSting);
|
||||
SubscribeLocalEvent<ChangelingComponent, CryoStingActionEvent>(OnCryoSting);
|
||||
|
||||
SubscribeLocalEvent<ChangelingComponent, AdrenalineSacsActionEvent>(OnAdrenalineSacs);
|
||||
SubscribeLocalEvent<ChangelingComponent, FleshmendActionEvent>(OnFleshMend);
|
||||
SubscribeLocalEvent<ChangelingComponent, ArmbladeActionEvent>(OnArmBlade);
|
||||
SubscribeLocalEvent<ChangelingComponent, OrganicShieldActionEvent>(OnShield);
|
||||
SubscribeLocalEvent<ChangelingComponent, ChitinousArmorActionEvent>(OnArmor);
|
||||
SubscribeLocalEvent<ChangelingComponent, TentacleArmActionEvent>(OnTentacleArm);
|
||||
|
||||
SubscribeLocalEvent<ChangelingComponent, TransformDoAfterEvent>(OnTransformDoAfter);
|
||||
SubscribeLocalEvent<ChangelingComponent, AbsorbDnaDoAfterEvent>(OnAbsorbDoAfter);
|
||||
SubscribeLocalEvent<ChangelingComponent, RegenerateDoAfterEvent>(OnRegenerateDoAfter);
|
||||
SubscribeLocalEvent<ChangelingComponent, LesserFormDoAfterEvent>(OnLesserFormDoAfter);
|
||||
|
||||
SubscribeLocalEvent<ChangelingComponent, ListViewItemSelectedMessage>(OnTransformUiMessage);
|
||||
}
|
||||
|
||||
#region Data
|
||||
|
||||
private const string ChangelingAbsorb = "ActionChangelingAbsorb";
|
||||
private const string ChangelingTransform = "ActionChangelingTransform";
|
||||
private const string ChangelingRegenerate = "ActionChangelingRegenerate";
|
||||
private const string ChangelingLesserForm = "ActionChangelingLesserForm";
|
||||
private const string ChangelingTransformSting = "ActionTransformSting";
|
||||
private const string ChangelingBlindSting = "ActionBlindSting";
|
||||
private const string ChangelingMuteSting = "ActionMuteSting";
|
||||
private const string ChangelingHallucinationSting = "ActionHallucinationSting";
|
||||
private const string ChangelingCryoSting = "ActionCryoSting";
|
||||
private const string ChangelingAdrenalineSacs = "ActionAdrenalineSacs";
|
||||
private const string ChangelingFleshMend = "ActionFleshmend";
|
||||
private const string ChangelingArmBlade = "ActionArmblade";
|
||||
private const string ChangelingShield = "ActionShield";
|
||||
private const string ChangelingArmor = "ActionArmor";
|
||||
private const string ChangelingTentacleArm = "ActionTentacleArm";
|
||||
|
||||
#endregion
|
||||
|
||||
|
||||
#region Handlers
|
||||
|
||||
private void OnAbsorb(EntityUid uid, ChangelingComponent component, AbsorbDnaActionEvent args)
|
||||
{
|
||||
if (!HasComp<HumanoidAppearanceComponent>(args.Target))
|
||||
{
|
||||
_popup.PopupEntity("You can't absorb not humans!", args.Performer);
|
||||
return;
|
||||
}
|
||||
|
||||
if (HasComp<AbsorbedComponent>(args.Target))
|
||||
{
|
||||
_popup.PopupEntity("This person already absorbed!", args.Performer);
|
||||
return;
|
||||
}
|
||||
|
||||
if (!TryComp<DnaComponent>(args.Target, out var dnaComponent))
|
||||
{
|
||||
_popup.PopupEntity("Unknown creature!", uid);
|
||||
return;
|
||||
}
|
||||
|
||||
if (component.AbsorbedEntities.ContainsKey(dnaComponent.DNA))
|
||||
{
|
||||
_popup.PopupEntity("This DNA already absorbed!", uid);
|
||||
return;
|
||||
}
|
||||
|
||||
if (!_stateSystem.IsDown(args.Target))
|
||||
{
|
||||
_popup.PopupEntity("Target must be down!", args.Performer);
|
||||
return;
|
||||
}
|
||||
|
||||
if (!TryComp<SharedPullableComponent>(args.Target, out var pulled))
|
||||
{
|
||||
_popup.PopupEntity("You must pull target!", args.Performer);
|
||||
return;
|
||||
}
|
||||
|
||||
if (!pulled.BeingPulled)
|
||||
{
|
||||
_popup.PopupEntity("You must pull target!", args.Performer);
|
||||
return;
|
||||
}
|
||||
|
||||
_doAfterSystem.TryStartDoAfter(
|
||||
new DoAfterArgs(EntityManager, args.Performer, component.AbsorbDnaDelay, new AbsorbDnaDoAfterEvent(), uid,
|
||||
args.Target, uid)
|
||||
{
|
||||
BreakOnTargetMove = true,
|
||||
BreakOnUserMove = true
|
||||
});
|
||||
}
|
||||
|
||||
private void OnTransform(EntityUid uid, ChangelingComponent component, TransformActionEvent args)
|
||||
{
|
||||
if (!TryComp<ActorComponent>(uid, out var actorComponent))
|
||||
return;
|
||||
|
||||
if (component.AbsorbedEntities.Count <= 1 && !component.IsLesserForm)
|
||||
{
|
||||
_popup.PopupEntity("You don't have any persons to transform!", uid);
|
||||
return;
|
||||
}
|
||||
|
||||
if (!_ui.TryGetUi(uid, ListViewSelectorUiKeyChangeling.Key, out var bui))
|
||||
return;
|
||||
|
||||
Dictionary<string, string> state;
|
||||
|
||||
if (TryComp<DnaComponent>(uid, out var dnaComponent))
|
||||
{
|
||||
state = component.AbsorbedEntities.Where(key => key.Key != dnaComponent.DNA).ToDictionary(humanoidData
|
||||
=> humanoidData.Key, humanoidData
|
||||
=> humanoidData.Value.Name);
|
||||
}
|
||||
else
|
||||
{
|
||||
state = component.AbsorbedEntities.ToDictionary(humanoidData
|
||||
=> humanoidData.Key, humanoidData
|
||||
=> humanoidData.Value.Name);
|
||||
}
|
||||
|
||||
_ui.SetUiState(bui, new ListViewBuiState(state));
|
||||
_ui.OpenUi(bui, actorComponent.PlayerSession);
|
||||
}
|
||||
|
||||
private void OnTransformUiMessage(EntityUid uid, ChangelingComponent component, ListViewItemSelectedMessage args)
|
||||
{
|
||||
var selectedDna = args.SelectedItem;
|
||||
var user = GetEntity(args.Entity);
|
||||
|
||||
_doAfterSystem.TryStartDoAfter(
|
||||
new DoAfterArgs(EntityManager, user, component.TransformDelay,
|
||||
new TransformDoAfterEvent { SelectedDna = selectedDna }, user,
|
||||
user, user)
|
||||
{
|
||||
BreakOnUserMove = true
|
||||
});
|
||||
|
||||
if (!TryComp<ActorComponent>(uid, out var actorComponent))
|
||||
return;
|
||||
|
||||
if (!_ui.TryGetUi(user, ListViewSelectorUiKeyChangeling.Key, out var bui))
|
||||
return;
|
||||
|
||||
_ui.CloseUi(bui, actorComponent.PlayerSession);
|
||||
}
|
||||
|
||||
private void OnRegenerate(EntityUid uid, ChangelingComponent component, RegenerateActionEvent args)
|
||||
{
|
||||
if (!TryComp<DamageableComponent>(uid, out var damageableComponent))
|
||||
return;
|
||||
|
||||
if (damageableComponent.TotalDamage >= 0 && !_mobStateSystem.IsDead(uid))
|
||||
{
|
||||
KillUser(uid, "Cellular");
|
||||
}
|
||||
|
||||
_popup.PopupEntity("We beginning our regeneration.", uid);
|
||||
|
||||
_doAfterSystem.TryStartDoAfter(
|
||||
new DoAfterArgs(EntityManager, args.Performer, component.RegenerateDelay,
|
||||
new RegenerateDoAfterEvent(), args.Performer,
|
||||
args.Performer, args.Performer)
|
||||
{
|
||||
RequireCanInteract = false
|
||||
});
|
||||
|
||||
component.IsRegenerating = true;
|
||||
}
|
||||
|
||||
private void OnLesserForm(EntityUid uid, ChangelingComponent component, LesserFormActionEvent args)
|
||||
{
|
||||
if (_mobStateSystem.IsDead(uid) || component.IsRegenerating)
|
||||
{
|
||||
_popup.PopupEntity("We can do this right now!", uid);
|
||||
return;
|
||||
}
|
||||
|
||||
if (component.IsLesserForm)
|
||||
{
|
||||
_popup.PopupEntity("We're already in the lesser form!", uid);
|
||||
return;
|
||||
}
|
||||
|
||||
_doAfterSystem.TryStartDoAfter(new DoAfterArgs(EntityManager, args.Performer, component.LesserFormDelay,
|
||||
new LesserFormDoAfterEvent(), args.Performer, args.Performer)
|
||||
{
|
||||
BreakOnUserMove = true
|
||||
});
|
||||
}
|
||||
|
||||
private void OnTransformSting(EntityUid uid, ChangelingComponent component, TransformStingActionEvent args)
|
||||
{
|
||||
if (!HasComp<HumanoidAppearanceComponent>(args.Target))
|
||||
{
|
||||
_popup.PopupEntity("We can't transform that!", args.Performer);
|
||||
return;
|
||||
}
|
||||
|
||||
if (!TryComp<ActorComponent>(uid, out var actorComponent))
|
||||
return;
|
||||
|
||||
if (component.AbsorbedEntities.Count < 1)
|
||||
{
|
||||
_popup.PopupEntity("You don't have any persons to transform!", uid);
|
||||
return;
|
||||
}
|
||||
|
||||
if (!_ui.TryGetUi(uid, TransformStingSelectorUiKey.Key, out var bui))
|
||||
return;
|
||||
|
||||
var target = GetNetEntity(args.Target);
|
||||
|
||||
var state = component.AbsorbedEntities.ToDictionary(humanoidData
|
||||
=> humanoidData.Key, humanoidData
|
||||
=> humanoidData.Value.Name);
|
||||
|
||||
_ui.SetUiState(bui, new TransformStingBuiState(state, target));
|
||||
_ui.OpenUi(bui, actorComponent.PlayerSession);
|
||||
}
|
||||
|
||||
private void OnTransformStingMessage(EntityUid uid, ChangelingComponent component,
|
||||
TransformStingItemSelectedMessage args)
|
||||
{
|
||||
var selectedDna = args.SelectedItem;
|
||||
var humanData = component.AbsorbedEntities[selectedDna];
|
||||
var target = GetEntity(args.Target);
|
||||
var user = GetEntity(args.Entity);
|
||||
|
||||
if (!TryComp<ActorComponent>(uid, out var actorComponent))
|
||||
return;
|
||||
|
||||
if (!_ui.TryGetUi(user, TransformStingSelectorUiKey.Key, out var bui))
|
||||
return;
|
||||
|
||||
if (HasComp<ChangelingComponent>(target))
|
||||
{
|
||||
_popup.PopupEntity("Transform virus was ineffective!", user);
|
||||
return;
|
||||
}
|
||||
|
||||
if (!TakeChemicals(uid, component, 50))
|
||||
return;
|
||||
|
||||
if (TryComp(target, out SharedPullerComponent? puller) && puller.Pulling is { } pulled &&
|
||||
TryComp(pulled, out SharedPullableComponent? pullable))
|
||||
_pullingSystem.TryStopPull(pullable);
|
||||
|
||||
TransformPerson(target, humanData);
|
||||
|
||||
_ui.CloseUi(bui, actorComponent.PlayerSession);
|
||||
|
||||
StartUseDelayById(uid, ChangelingTransformSting);
|
||||
}
|
||||
|
||||
private void OnBlindSting(EntityUid uid, ChangelingComponent component, BlindStingActionEvent args)
|
||||
{
|
||||
if (!HasComp<HumanoidAppearanceComponent>(args.Target) ||
|
||||
!HasComp<BlindableComponent>(args.Target))
|
||||
{
|
||||
_popup.PopupEntity("We cannot sting that!", uid);
|
||||
return;
|
||||
}
|
||||
|
||||
if (!TakeChemicals(uid, component, 25))
|
||||
return;
|
||||
|
||||
var statusTimeSpan = TimeSpan.FromSeconds(25);
|
||||
_statusEffectsSystem.TryAddStatusEffect(args.Target, TemporaryBlindnessSystem.BlindingStatusEffect,
|
||||
statusTimeSpan, false, TemporaryBlindnessSystem.BlindingStatusEffect);
|
||||
|
||||
args.Handled = true;
|
||||
}
|
||||
|
||||
private void OnMuteSting(EntityUid uid, ChangelingComponent component, MuteStingActionEvent args)
|
||||
{
|
||||
if (!HasComp<HumanoidAppearanceComponent>(args.Target))
|
||||
{
|
||||
_popup.PopupEntity("We cannot sting that!", uid);
|
||||
return;
|
||||
}
|
||||
|
||||
if (!TakeChemicals(uid, component, 20))
|
||||
return;
|
||||
|
||||
var statusTimeSpan = TimeSpan.FromSeconds(30);
|
||||
_statusEffectsSystem.TryAddStatusEffect(args.Target, "Muted",
|
||||
statusTimeSpan, false, "Muted");
|
||||
|
||||
args.Handled = true;
|
||||
}
|
||||
|
||||
private void OnHallucinationSting(EntityUid uid, ChangelingComponent component, HallucinationStingActionEvent args)
|
||||
{
|
||||
if (!HasComp<HumanoidAppearanceComponent>(args.Target))
|
||||
{
|
||||
_popup.PopupEntity("We cannot sting that!", uid);
|
||||
return;
|
||||
}
|
||||
|
||||
if (!TakeChemicals(uid, component, 5))
|
||||
return;
|
||||
|
||||
var statusTimeSpan = TimeSpan.FromSeconds(30);
|
||||
_statusEffectsSystem.TryAddStatusEffect(args.Target, "BlurryVision",
|
||||
statusTimeSpan, false, "BlurryVision");
|
||||
|
||||
args.Handled = true;
|
||||
}
|
||||
|
||||
private void OnCryoSting(EntityUid uid, ChangelingComponent component, CryoStingActionEvent args)
|
||||
{
|
||||
if (!HasComp<HumanoidAppearanceComponent>(args.Target))
|
||||
{
|
||||
_popup.PopupEntity("We cannot sting that!", uid);
|
||||
return;
|
||||
}
|
||||
|
||||
if (!TakeChemicals(uid, component, 15))
|
||||
return;
|
||||
|
||||
var statusTimeSpan = TimeSpan.FromSeconds(30);
|
||||
_statusEffectsSystem.TryAddStatusEffect(args.Target, "SlowedDown",
|
||||
statusTimeSpan, false, "SlowedDown");
|
||||
|
||||
_temperatureSystem.ForceChangeTemperature(args.Target, 100);
|
||||
|
||||
args.Handled = true;
|
||||
}
|
||||
|
||||
private void OnAdrenalineSacs(EntityUid uid, ChangelingComponent component, AdrenalineSacsActionEvent args)
|
||||
{
|
||||
if (_mobStateSystem.IsDead(uid))
|
||||
return;
|
||||
|
||||
if (!_solutionContainer.TryGetInjectableSolution(uid, out var injectable, out _))
|
||||
return;
|
||||
|
||||
if (!TakeChemicals(uid, component, 30))
|
||||
return;
|
||||
|
||||
_solutionContainer.TryAddReagent(injectable.Value, "Stimulants", 50);
|
||||
|
||||
args.Handled = true;
|
||||
}
|
||||
|
||||
private void OnFleshMend(EntityUid uid, ChangelingComponent component, FleshmendActionEvent args)
|
||||
{
|
||||
if (_mobStateSystem.IsDead(uid))
|
||||
return;
|
||||
|
||||
if (!_solutionContainer.TryGetInjectableSolution(uid, out var injectable, out _))
|
||||
return;
|
||||
|
||||
if (!TakeChemicals(uid, component, 20))
|
||||
return;
|
||||
|
||||
_solutionContainer.TryAddReagent(injectable.Value, "Omnizine", 50);
|
||||
_solutionContainer.TryAddReagent(injectable.Value, "TranexamicAcid", 10);
|
||||
|
||||
args.Handled = true;
|
||||
}
|
||||
|
||||
private void OnArmBlade(EntityUid uid, ChangelingComponent component, ArmbladeActionEvent args)
|
||||
{
|
||||
SpawnOrDeleteItem(uid, "ArmBlade");
|
||||
|
||||
args.Handled = true;
|
||||
}
|
||||
|
||||
private void OnShield(EntityUid uid, ChangelingComponent component, OrganicShieldActionEvent args)
|
||||
{
|
||||
SpawnOrDeleteItem(uid, "OrganicShield");
|
||||
|
||||
args.Handled = true;
|
||||
}
|
||||
|
||||
private void OnArmor(EntityUid uid, ChangelingComponent component, ChitinousArmorActionEvent args)
|
||||
{
|
||||
const string outerName = "outerClothing";
|
||||
const string protoName = "ClothingOuterChangeling";
|
||||
|
||||
if (!_inventorySystem.TryGetSlotEntity(uid, outerName, out var outerEnt))
|
||||
{
|
||||
_inventorySystem.SpawnItemInSlot(uid, outerName, protoName, silent: true);
|
||||
return;
|
||||
}
|
||||
|
||||
if (!TryComp<MetaDataComponent>(outerEnt, out var meta))
|
||||
{
|
||||
_inventorySystem.SpawnItemInSlot(uid, outerName, protoName, silent: true);
|
||||
return;
|
||||
}
|
||||
|
||||
if (meta.EntityPrototype == null)
|
||||
{
|
||||
_inventorySystem.SpawnItemInSlot(uid, outerName, protoName, silent: true);
|
||||
return;
|
||||
}
|
||||
|
||||
if (meta.EntityPrototype.ID == protoName)
|
||||
{
|
||||
_inventorySystem.TryUnequip(uid, outerName, out var removedItem);
|
||||
QueueDel(removedItem);
|
||||
return;
|
||||
}
|
||||
|
||||
_inventorySystem.TryUnequip(uid, outerName, out _);
|
||||
|
||||
_inventorySystem.SpawnItemInSlot(uid, outerName, protoName, silent: true);
|
||||
|
||||
args.Handled = true;
|
||||
}
|
||||
|
||||
private void OnTentacleArm(EntityUid uid, ChangelingComponent component, TentacleArmActionEvent args)
|
||||
{
|
||||
SpawnOrDeleteItem(uid, "TentacleArmGun");
|
||||
|
||||
args.Handled = true;
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region DoAfters
|
||||
|
||||
private void OnTransformDoAfter(EntityUid uid, ChangelingComponent component, TransformDoAfterEvent args)
|
||||
{
|
||||
if (args.Handled || args.Cancelled)
|
||||
return;
|
||||
|
||||
if (!TakeChemicals(uid, component, 5))
|
||||
return;
|
||||
|
||||
if (TryComp(uid, out SharedPullerComponent? puller) && puller.Pulling is { } pulled &&
|
||||
TryComp(pulled, out SharedPullableComponent? pullable))
|
||||
_pullingSystem.TryStopPull(pullable);
|
||||
|
||||
TryTransformChangeling(args.User, args.SelectedDna, component);
|
||||
|
||||
args.Handled = true;
|
||||
}
|
||||
|
||||
private void OnAbsorbDoAfter(EntityUid uid, ChangelingComponent component, AbsorbDnaDoAfterEvent args)
|
||||
{
|
||||
if (args.Handled || args.Cancelled || args.Target == null)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
if(!_mindSystem.TryGetMind(uid, out var mindId, out _))
|
||||
return;
|
||||
|
||||
if (TryComp(uid, out SharedPullerComponent? puller) && puller.Pulling is { } pulled &&
|
||||
TryComp(pulled, out SharedPullableComponent? pullable))
|
||||
_pullingSystem.TryStopPull(pullable);
|
||||
|
||||
if (TryComp<ChangelingComponent>(args.Target.Value, out var changelingComponent))
|
||||
{
|
||||
var total = component.AbsorbedEntities
|
||||
.Concat(changelingComponent.AbsorbedEntities)
|
||||
.ToDictionary(pair => pair.Key, pair => pair.Value);
|
||||
component.AbsorbedEntities = total;
|
||||
}
|
||||
else
|
||||
{
|
||||
CopyHumanoidData(uid, args.Target.Value, component);
|
||||
}
|
||||
|
||||
AddCurrency(uid, args.Target.Value);
|
||||
|
||||
KillUser(args.Target.Value, "Cellular");
|
||||
|
||||
EnsureComp<AbsorbedComponent>(args.Target.Value, out var absorbedComponent);
|
||||
|
||||
absorbedComponent.AbsorberMind = mindId;
|
||||
|
||||
EnsureComp<UncloneableComponent>(args.Target.Value);
|
||||
|
||||
StartUseDelayById(uid, ChangelingAbsorb);
|
||||
|
||||
args.Handled = true;
|
||||
}
|
||||
|
||||
private void OnRegenerateDoAfter(EntityUid uid, ChangelingComponent component, RegenerateDoAfterEvent args)
|
||||
{
|
||||
if (args.Handled || args.Cancelled || args.Target == null)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
if (HasComp<AbsorbedComponent>(args.Target))
|
||||
{
|
||||
_popup.PopupEntity("You're lost.", args.Target.Value);
|
||||
component.IsRegenerating = false;
|
||||
return;
|
||||
}
|
||||
|
||||
if (!TakeChemicals(uid, component, 15))
|
||||
return;
|
||||
|
||||
_rejuvenate.PerformRejuvenate(args.Target.Value);
|
||||
|
||||
_popup.PopupEntity("We're fully regenerated!", args.Target.Value);
|
||||
|
||||
component.IsRegenerating = false;
|
||||
|
||||
StartUseDelayById(uid, ChangelingRegenerate);
|
||||
|
||||
args.Handled = true;
|
||||
}
|
||||
|
||||
private void OnLesserFormDoAfter(EntityUid uid, ChangelingComponent component, LesserFormDoAfterEvent args)
|
||||
{
|
||||
if (args.Handled || args.Cancelled)
|
||||
return;
|
||||
|
||||
var polymorphEntity = _polymorph.PolymorphEntity(args.User, "MonkeyChangeling");
|
||||
|
||||
if (polymorphEntity == null)
|
||||
return;
|
||||
|
||||
if (!TakeChemicals(uid, component, 5))
|
||||
return;
|
||||
|
||||
var toAdd = new ChangelingComponent
|
||||
{
|
||||
HiveName = component.HiveName,
|
||||
ChemicalsBalance = component.ChemicalsBalance,
|
||||
AbsorbedEntities = component.AbsorbedEntities,
|
||||
IsInited = component.IsInited,
|
||||
IsLesserForm = true
|
||||
};
|
||||
|
||||
EntityManager.AddComponent(polymorphEntity.Value, toAdd);
|
||||
|
||||
_implantSystem.TransferImplants(uid, polymorphEntity.Value);
|
||||
_actionContainerSystem.TransferAllActionsFiltered(uid, polymorphEntity.Value);
|
||||
_action.GrantContainedActions(polymorphEntity.Value, polymorphEntity.Value);
|
||||
|
||||
RemoveLesserFormActions(polymorphEntity.Value);
|
||||
|
||||
_chemicalsSystem.UpdateAlert(polymorphEntity.Value, component);
|
||||
|
||||
args.Handled = true;
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region Helpers
|
||||
|
||||
private void RemoveLesserFormActions(EntityUid uid)
|
||||
{
|
||||
if (!TryComp<ActionsComponent>(uid, out var actionsComponent))
|
||||
return;
|
||||
|
||||
foreach (var action in actionsComponent.Actions.ToArray())
|
||||
{
|
||||
if (!HasComp<LesserFormRestrictedComponent>(action))
|
||||
continue;
|
||||
|
||||
_action.RemoveAction(uid, action);
|
||||
}
|
||||
}
|
||||
|
||||
private void StartUseDelayById(EntityUid performer, string actionProto)
|
||||
{
|
||||
if (!TryComp<ActionsComponent>(performer, out var actionsComponent))
|
||||
return;
|
||||
|
||||
foreach (var action in actionsComponent.Actions.ToArray())
|
||||
{
|
||||
var id = MetaData(action).EntityPrototype?.ID;
|
||||
|
||||
if (id != actionProto)
|
||||
continue;
|
||||
|
||||
_action.StartUseDelay(action);
|
||||
}
|
||||
}
|
||||
|
||||
private void KillUser(EntityUid target, string damageType)
|
||||
{
|
||||
if (!_mobThresholdSystem.TryGetThresholdForState(target, MobState.Dead, out var damage))
|
||||
return;
|
||||
|
||||
DamageSpecifier dmg = new();
|
||||
dmg.DamageDict.Add(damageType, damage.Value);
|
||||
_damage.TryChangeDamage(target, dmg, true);
|
||||
}
|
||||
|
||||
private void CopyHumanoidData(EntityUid uid, EntityUid target, ChangelingComponent component)
|
||||
{
|
||||
if (!TryComp<MetaDataComponent>(target, out var targetMeta))
|
||||
return;
|
||||
if (!TryComp<HumanoidAppearanceComponent>(target, out var targetAppearance))
|
||||
return;
|
||||
if (!TryComp<DnaComponent>(target, out var targetDna))
|
||||
return;
|
||||
if (!TryPrototype(target, out var prototype, targetMeta))
|
||||
return;
|
||||
if (component.AbsorbedEntities.ContainsKey(targetDna.DNA))
|
||||
return;
|
||||
|
||||
var appearance = _serializationManager.CreateCopy(targetAppearance, notNullableOverride: true);
|
||||
var meta = _serializationManager.CreateCopy(targetMeta, notNullableOverride: true);
|
||||
|
||||
var name = string.IsNullOrEmpty(meta.EntityName)
|
||||
? "Unknown Creature"
|
||||
: meta.EntityName;
|
||||
|
||||
component.AbsorbedEntities.Add(targetDna.DNA, new HumanoidData
|
||||
{
|
||||
EntityPrototype = prototype,
|
||||
MetaDataComponent = meta,
|
||||
AppearanceComponent = appearance,
|
||||
Name = name,
|
||||
Dna = targetDna.DNA
|
||||
});
|
||||
|
||||
Dirty(uid, component);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Transforms chosen person to another, transferring it's appearance
|
||||
/// </summary>
|
||||
/// <param name="target">Transform target</param>
|
||||
/// <param name="transformData">Transform data</param>
|
||||
/// <param name="humanoidOverride">Override first check on HumanoidAppearanceComponent</param>
|
||||
/// <returns>Id of the transformed entity</returns>
|
||||
private EntityUid? TransformPerson(EntityUid target, HumanoidData transformData, bool humanoidOverride = false)
|
||||
{
|
||||
if (!HasComp<HumanoidAppearanceComponent>(target) && !humanoidOverride)
|
||||
return null;
|
||||
|
||||
var polymorphEntity = _polymorph.PolymorphEntity(target, transformData.EntityPrototype.ID);
|
||||
|
||||
if (polymorphEntity == null)
|
||||
return null;
|
||||
|
||||
if (!TryComp<HumanoidAppearanceComponent>(polymorphEntity.Value, out var polyAppearance))
|
||||
return null;
|
||||
|
||||
ClonePerson(polymorphEntity.Value, transformData.AppearanceComponent, polyAppearance);
|
||||
TransferDna(polymorphEntity.Value, transformData.Dna);
|
||||
|
||||
if (!TryComp<MetaDataComponent>(polymorphEntity.Value, out var meta))
|
||||
return null;
|
||||
|
||||
_metaData.SetEntityName(polymorphEntity.Value, transformData.MetaDataComponent!.EntityName, meta);
|
||||
_metaData.SetEntityDescription(polymorphEntity.Value, transformData.MetaDataComponent!.EntityDescription, meta);
|
||||
|
||||
_identity.QueueIdentityUpdate(polymorphEntity.Value);
|
||||
|
||||
return polymorphEntity;
|
||||
}
|
||||
|
||||
private void TransferDna(EntityUid target, string dna)
|
||||
{
|
||||
if (!TryComp<DnaComponent>(target, out var dnaComponent))
|
||||
return;
|
||||
|
||||
dnaComponent.DNA = dna;
|
||||
}
|
||||
|
||||
private void TryTransformChangeling(EntityUid uid, string dna, ChangelingComponent component)
|
||||
{
|
||||
if (!component.AbsorbedEntities.TryGetValue(dna, out var person))
|
||||
return;
|
||||
|
||||
EntityUid? reverted = uid;
|
||||
|
||||
reverted = component.IsLesserForm
|
||||
? TransformPerson(reverted.Value, person, humanoidOverride: true)
|
||||
: TransformPerson(reverted.Value, person);
|
||||
|
||||
if (reverted == null)
|
||||
return;
|
||||
|
||||
var toAdd = new ChangelingComponent
|
||||
{
|
||||
HiveName = component.HiveName,
|
||||
ChemicalsBalance = component.ChemicalsBalance,
|
||||
AbsorbedEntities = component.AbsorbedEntities,
|
||||
IsInited = component.IsInited
|
||||
};
|
||||
|
||||
EntityManager.AddComponent(reverted.Value, toAdd);
|
||||
|
||||
_implantSystem.TransferImplants(uid, reverted.Value);
|
||||
_actionContainerSystem.TransferAllActionsFiltered(uid, reverted.Value);
|
||||
_action.GrantContainedActions(reverted.Value,reverted.Value);
|
||||
|
||||
if (component.IsLesserForm)
|
||||
{
|
||||
//Don't copy IsLesserForm bool, because transferred component, in fact, new. Bool default value if false.
|
||||
StartUseDelayById(reverted.Value, ChangelingLesserForm);
|
||||
}
|
||||
|
||||
_chemicalsSystem.UpdateAlert(reverted.Value, component);
|
||||
|
||||
StartUseDelayById(reverted.Value, ChangelingTransform);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Used for cloning appearance
|
||||
/// </summary>
|
||||
/// <param name="target">Acceptor</param>
|
||||
/// <param name="sourceHumanoid">Source appearance</param>
|
||||
/// <param name="targetHumanoid">Acceptor appearance component</param>
|
||||
private void ClonePerson(EntityUid target, HumanoidAppearanceComponent sourceHumanoid,
|
||||
HumanoidAppearanceComponent targetHumanoid)
|
||||
{
|
||||
targetHumanoid.Species = sourceHumanoid.Species;
|
||||
targetHumanoid.SkinColor = sourceHumanoid.SkinColor;
|
||||
targetHumanoid.EyeColor = sourceHumanoid.EyeColor;
|
||||
targetHumanoid.Age = sourceHumanoid.Age;
|
||||
_humanoidAppearance.SetSex(target, sourceHumanoid.Sex, false, targetHumanoid);
|
||||
_humanoidAppearance.SetSpecies(target, sourceHumanoid.Species);
|
||||
targetHumanoid.CustomBaseLayers = new Dictionary<HumanoidVisualLayers,
|
||||
CustomBaseLayerInfo>(sourceHumanoid.CustomBaseLayers);
|
||||
targetHumanoid.MarkingSet = new MarkingSet(sourceHumanoid.MarkingSet);
|
||||
|
||||
targetHumanoid.Gender = sourceHumanoid.Gender;
|
||||
if (TryComp<GrammarComponent>(target, out var grammar))
|
||||
{
|
||||
grammar.Gender = sourceHumanoid.Gender;
|
||||
}
|
||||
|
||||
Dirty(target, targetHumanoid);
|
||||
}
|
||||
|
||||
private void SpawnOrDeleteItem(EntityUid target, string prototypeName)
|
||||
{
|
||||
foreach (var eHand in _handsSystem.EnumerateHands(target))
|
||||
{
|
||||
if (eHand.HeldEntity == null || !TryComp<MetaDataComponent>(eHand.HeldEntity.Value, out var meta))
|
||||
continue;
|
||||
|
||||
if (meta.EntityPrototype != null && meta.EntityPrototype.ID != prototypeName)
|
||||
continue;
|
||||
|
||||
Del(eHand.HeldEntity);
|
||||
return;
|
||||
}
|
||||
|
||||
if (!_handsSystem.TryGetEmptyHand(target, out var hand))
|
||||
{
|
||||
_popup.PopupEntity("We need to have at least one empty hand!", target);
|
||||
return;
|
||||
}
|
||||
|
||||
var item = Spawn(prototypeName, Transform(target).Coordinates);
|
||||
|
||||
if (!_handsSystem.TryPickup(target, item, hand, animate: false))
|
||||
{
|
||||
Del(item);
|
||||
}
|
||||
}
|
||||
|
||||
private bool TakeChemicals(EntityUid uid, ChangelingComponent component, int quantity)
|
||||
{
|
||||
if (!_chemicalsSystem.RemoveChemicals(uid, component, quantity))
|
||||
{
|
||||
_popup.PopupEntity("We're lacking of chemicals!", uid);
|
||||
return false;
|
||||
}
|
||||
|
||||
_popup.PopupEntity($"Used {quantity} of chemicals.", uid);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
private void AddCurrency(EntityUid uid, EntityUid absorbed)
|
||||
{
|
||||
if (!TryComp<ImplantedComponent>(uid, out var implant))
|
||||
return;
|
||||
|
||||
foreach (var entity in implant.ImplantContainer.ContainedEntities)
|
||||
{
|
||||
if (!TryComp<StoreComponent>(entity, out var store))
|
||||
continue;
|
||||
|
||||
if (_mobStateSystem.IsDead(absorbed))
|
||||
{
|
||||
var points = _random.Next(1, 3);
|
||||
var toAdd = new Dictionary<string, FixedPoint2> { { "ChangelingPoint", points } };
|
||||
_storeSystem.TryAddCurrency(toAdd, entity, store);
|
||||
}
|
||||
else
|
||||
{
|
||||
var points = _random.Next(2, 4);
|
||||
var toAdd = new Dictionary<string, FixedPoint2> { { "ChangelingPoint", points } };
|
||||
_storeSystem.TryAddCurrency(toAdd, entity, store);
|
||||
}
|
||||
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
#endregion
|
||||
}
|
||||
24
Content.Server/Changeling/ChangelingSystem.Shop.cs
Normal file
24
Content.Server/Changeling/ChangelingSystem.Shop.cs
Normal file
@@ -0,0 +1,24 @@
|
||||
using Content.Server.Store.Components;
|
||||
using Content.Server.Store.Systems;
|
||||
using Content.Shared.Changeling;
|
||||
using Content.Shared.Implants.Components;
|
||||
using Robust.Server.GameStates;
|
||||
using Robust.Server.Placement;
|
||||
|
||||
namespace Content.Server.Changeling;
|
||||
|
||||
public sealed partial class ChangelingSystem
|
||||
{
|
||||
private void InitializeShop()
|
||||
{
|
||||
SubscribeLocalEvent<SubdermalImplantComponent, ChangelingShopActionEvent>(OnShop);
|
||||
}
|
||||
|
||||
private void OnShop(EntityUid uid, SubdermalImplantComponent component, ChangelingShopActionEvent args)
|
||||
{
|
||||
if(!TryComp<StoreComponent>(uid, out var store))
|
||||
return;
|
||||
|
||||
_storeSystem.ToggleUi(args.Performer, uid, store);
|
||||
}
|
||||
}
|
||||
81
Content.Server/Changeling/ChangelingSystem.cs
Normal file
81
Content.Server/Changeling/ChangelingSystem.cs
Normal file
@@ -0,0 +1,81 @@
|
||||
using Content.Server.Store.Components;
|
||||
using Content.Server.Store.Systems;
|
||||
using Content.Shared.Actions;
|
||||
using Content.Shared.Changeling;
|
||||
using Content.Shared.Examine;
|
||||
using Content.Shared.Implants;
|
||||
using Content.Shared.Implants.Components;
|
||||
|
||||
namespace Content.Server.Changeling;
|
||||
|
||||
public sealed partial class ChangelingSystem : EntitySystem
|
||||
{
|
||||
[Dependency] private readonly SharedActionsSystem _action = default!;
|
||||
[Dependency] private readonly ChemicalsSystem _chemicalsSystem = default!;
|
||||
[Dependency] private readonly SharedSubdermalImplantSystem _implantSystem = default!;
|
||||
[Dependency] private readonly StoreSystem _storeSystem = default!;
|
||||
|
||||
public override void Initialize()
|
||||
{
|
||||
base.Initialize();
|
||||
|
||||
SubscribeLocalEvent<ChangelingComponent, ComponentInit>(OnInit);
|
||||
|
||||
SubscribeLocalEvent<AbsorbedComponent, ExaminedEvent>(OnExamine);
|
||||
|
||||
InitializeAbilities();
|
||||
InitializeShop();
|
||||
}
|
||||
|
||||
#region Handlers
|
||||
|
||||
private void OnInit(EntityUid uid, ChangelingComponent component, ComponentInit args)
|
||||
{
|
||||
SetupShop(uid, component);
|
||||
SetupInitActions(uid, component);
|
||||
CopyHumanoidData(uid, uid, component);
|
||||
|
||||
_chemicalsSystem.UpdateAlert(uid, component);
|
||||
component.IsInited = true;
|
||||
}
|
||||
|
||||
private void OnExamine(EntityUid uid, AbsorbedComponent component, ExaminedEvent args)
|
||||
{
|
||||
args.PushMarkup("[color=#A30000]His juices sucked up![/color]");
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region Helpers
|
||||
|
||||
private void SetupShop(EntityUid uid, ChangelingComponent component)
|
||||
{
|
||||
if (component.IsInited)
|
||||
return;
|
||||
|
||||
var coords = Transform(uid).Coordinates;
|
||||
var implant = Spawn("ChangelingShopImplant", coords);
|
||||
|
||||
if(!TryComp<SubdermalImplantComponent>(implant, out var implantComp))
|
||||
return;
|
||||
|
||||
_implantSystem.ForceImplant(uid, implant, implantComp);
|
||||
|
||||
if (!TryComp<StoreComponent>(implant, out var implantStore))
|
||||
return;
|
||||
|
||||
implantStore.Balance.Add("ChangelingPoint", component.StartingPointsBalance);
|
||||
}
|
||||
|
||||
private void SetupInitActions(EntityUid uid, ChangelingComponent component)
|
||||
{
|
||||
if (component.IsInited)
|
||||
return;
|
||||
|
||||
_action.AddAction(uid, ChangelingAbsorb);
|
||||
_action.AddAction(uid, ChangelingTransform);
|
||||
_action.AddAction(uid, ChangelingRegenerate);
|
||||
}
|
||||
|
||||
#endregion
|
||||
}
|
||||
@@ -0,0 +1,236 @@
|
||||
using System.Linq;
|
||||
using Content.Server.Changeling.Objectives.Components;
|
||||
using Content.Server.Forensics;
|
||||
using Content.Server.Mind;
|
||||
using Content.Server.Objectives.Components;
|
||||
using Content.Server.Objectives.Systems;
|
||||
using Content.Server.Shuttles.Systems;
|
||||
using Content.Shared.Changeling;
|
||||
using Content.Shared.Mind;
|
||||
using Content.Shared.Objectives.Components;
|
||||
using Robust.Shared.Random;
|
||||
|
||||
namespace Content.Server.Changeling.Objectives;
|
||||
|
||||
public sealed class ChangelingConditionsSystem : EntitySystem
|
||||
{
|
||||
[Dependency] private readonly IRobustRandom _random = default!;
|
||||
[Dependency] private readonly MetaDataSystem _metaData = default!;
|
||||
[Dependency] private readonly TargetObjectiveSystem _target = default!;
|
||||
[Dependency] private readonly MindSystem _mind = default!;
|
||||
[Dependency] private readonly EmergencyShuttleSystem _emergencyShuttle = default!;
|
||||
|
||||
public override void Initialize()
|
||||
{
|
||||
base.Initialize();
|
||||
|
||||
// Absorb DNA condition
|
||||
SubscribeLocalEvent<AbsorbDnaConditionComponent, ObjectiveAssignedEvent>(OnAbsorbDnaAssigned);
|
||||
SubscribeLocalEvent<AbsorbDnaConditionComponent, ObjectiveAfterAssignEvent>(OnAbsorbDnaAfterAssigned);
|
||||
SubscribeLocalEvent<AbsorbDnaConditionComponent, ObjectiveGetProgressEvent>(OnAbsorbDnaGetProgress);
|
||||
|
||||
|
||||
//Absorb more genomes, than others changelings
|
||||
SubscribeLocalEvent<AbsorbMoreConditionComponent, ObjectiveGetProgressEvent>(OnAbsorbMoreGetProgress);
|
||||
|
||||
//Absorb other changeling
|
||||
SubscribeLocalEvent<PickRandomChangelingComponent, ObjectiveAssignedEvent>(OnAbsorbChangelingAssigned);
|
||||
SubscribeLocalEvent<AbsorbChangelingConditionComponent, ObjectiveGetProgressEvent>(OnAbsorbChangelingGetProgress);
|
||||
|
||||
//Escape with identity
|
||||
SubscribeLocalEvent<PickRandomIdentityComponent, ObjectiveAssignedEvent>(OnEscapeWithIdentityAssigned);
|
||||
SubscribeLocalEvent<EscapeWithIdentityConditionComponent, ObjectiveGetProgressEvent>(OnEscapeWithIdentityGetProgress);
|
||||
}
|
||||
|
||||
#region AbsorbDNA
|
||||
|
||||
private void OnAbsorbDnaAssigned(EntityUid uid, AbsorbDnaConditionComponent component, ref ObjectiveAssignedEvent args)
|
||||
{
|
||||
component.NeedToAbsorb = _random.Next(2, 6);
|
||||
}
|
||||
|
||||
private void OnAbsorbDnaAfterAssigned(EntityUid uid, AbsorbDnaConditionComponent component, ref ObjectiveAfterAssignEvent args)
|
||||
{
|
||||
var title = Loc.GetString("objective-condition-absorb-dna", ("count", component.NeedToAbsorb));
|
||||
|
||||
_metaData.SetEntityName(uid, title, args.Meta);
|
||||
}
|
||||
|
||||
private void OnAbsorbDnaGetProgress(EntityUid uid, AbsorbDnaConditionComponent component, ref ObjectiveGetProgressEvent args)
|
||||
{
|
||||
args.Progress = GetAbsorbProgress(args.Mind, component.NeedToAbsorb);
|
||||
}
|
||||
|
||||
private float GetAbsorbProgress(MindComponent mind, int requiredDna)
|
||||
{
|
||||
if (!TryComp<ChangelingComponent>(mind.CurrentEntity, out var changelingComponent))
|
||||
return 0f;
|
||||
|
||||
var absorbed = changelingComponent.AbsorbedEntities.Count - 1; // Because first - it's the owner
|
||||
|
||||
if (requiredDna == absorbed)
|
||||
return 1f;
|
||||
|
||||
var progress = MathF.Min(absorbed/(float)requiredDna, 1f);
|
||||
|
||||
return progress;
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region AbsorbMoreDNA
|
||||
|
||||
private void OnAbsorbMoreGetProgress(EntityUid uid, AbsorbMoreConditionComponent comp, ref ObjectiveGetProgressEvent args)
|
||||
{
|
||||
args.Progress = GetAbsorbMoreProgress(args.Mind);
|
||||
}
|
||||
|
||||
private float GetAbsorbMoreProgress(MindComponent mind)
|
||||
{
|
||||
if (!TryComp<ChangelingComponent>(mind.CurrentEntity, out var changelingComponent))
|
||||
return 0f;
|
||||
|
||||
var selfAbsorbed = changelingComponent.AbsorbedEntities.Count - 1; // Because first - it's the owner
|
||||
|
||||
var query = EntityQueryEnumerator<ChangelingComponent>();
|
||||
|
||||
List<int> otherAbsorbed = new();
|
||||
while (query.MoveNext(out var uid, out var comp))
|
||||
{
|
||||
if (uid == mind.CurrentEntity)
|
||||
continue; //don't include self
|
||||
|
||||
var absorbed = comp.AbsorbedEntities.Count - 1;
|
||||
otherAbsorbed.Add(absorbed);
|
||||
}
|
||||
|
||||
if (otherAbsorbed.Count == 0)
|
||||
return 1f;
|
||||
|
||||
var isTheMost = otherAbsorbed.Max() < selfAbsorbed;
|
||||
|
||||
return isTheMost ? 1f : 0f;
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region AbsorbChangeling
|
||||
|
||||
private void OnAbsorbChangelingAssigned(EntityUid uid, PickRandomChangelingComponent comp, ref ObjectiveAssignedEvent args)
|
||||
{
|
||||
if (!TryComp<TargetObjectiveComponent>(uid, out var target))
|
||||
{
|
||||
args.Cancelled = true;
|
||||
return;
|
||||
}
|
||||
|
||||
if (target.Target != null)
|
||||
return;
|
||||
|
||||
foreach (var changelingRule in EntityQuery<ChangelingRuleComponent>())
|
||||
{
|
||||
var changelingMinds = changelingRule.ChangelingMinds
|
||||
.Except(new List<EntityUid> { args.MindId })
|
||||
.ToList();
|
||||
|
||||
if (changelingMinds.Count == 0)
|
||||
{
|
||||
args.Cancelled = true;
|
||||
return;
|
||||
}
|
||||
|
||||
_target.SetTarget(uid, _random.Pick(changelingMinds), target);
|
||||
}
|
||||
}
|
||||
|
||||
private void OnAbsorbChangelingGetProgress(EntityUid uid, AbsorbChangelingConditionComponent comp, ref ObjectiveGetProgressEvent args)
|
||||
{
|
||||
if (!_target.GetTarget(uid, out var target))
|
||||
return;
|
||||
|
||||
args.Progress = GetAbsorbChangelingProgress(args.Mind, target.Value);
|
||||
}
|
||||
|
||||
private float GetAbsorbChangelingProgress(MindComponent mind, EntityUid target)
|
||||
{
|
||||
if(!_mind.TryGetMind(mind.CurrentEntity!.Value, out var selfMindId, out _))
|
||||
return 0f;
|
||||
|
||||
if (!TryComp<MindComponent>(target, out var targetMind))
|
||||
return 0f;
|
||||
|
||||
if (!HasComp<ChangelingComponent>(targetMind.CurrentEntity))
|
||||
return 0f;
|
||||
|
||||
if (!TryComp<AbsorbedComponent>(targetMind.CurrentEntity, out var absorbedComponent))
|
||||
return 0f;
|
||||
|
||||
return absorbedComponent.AbsorberMind == selfMindId ? 1f : 0f;
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region EscapeWithIdentity
|
||||
|
||||
private void OnEscapeWithIdentityAssigned(EntityUid uid, PickRandomIdentityComponent component, ref ObjectiveAssignedEvent args)
|
||||
{
|
||||
if (!TryComp<TargetObjectiveComponent>(uid, out var target))
|
||||
{
|
||||
args.Cancelled = true;
|
||||
return;
|
||||
}
|
||||
|
||||
if (target.Target != null)
|
||||
return;
|
||||
|
||||
var allHumans = _mind.GetAliveHumansExcept(args.MindId);
|
||||
if (allHumans.Count == 0)
|
||||
{
|
||||
args.Cancelled = true;
|
||||
return;
|
||||
}
|
||||
|
||||
_target.SetTarget(uid, _random.Pick(allHumans), target);
|
||||
}
|
||||
|
||||
private void OnEscapeWithIdentityGetProgress(EntityUid uid, EscapeWithIdentityConditionComponent component, ref ObjectiveGetProgressEvent args)
|
||||
{
|
||||
if (!_target.GetTarget(uid, out var target))
|
||||
return;
|
||||
|
||||
args.Progress = GetEscapeWithIdentityProgress(args.Mind, target.Value);
|
||||
}
|
||||
|
||||
private float GetEscapeWithIdentityProgress(MindComponent mind, EntityUid target)
|
||||
{
|
||||
var progress = 0f;
|
||||
|
||||
if (!TryComp<DnaComponent>(mind.CurrentEntity, out var selfDna))
|
||||
return 0f;
|
||||
|
||||
if (!TryComp<MindComponent>(target, out var targetMind))
|
||||
return 0f;
|
||||
|
||||
if (!TryComp<DnaComponent>(targetMind.CurrentEntity, out var targetDna))
|
||||
return 0f;
|
||||
|
||||
if (!TryComp<ChangelingComponent>(mind.CurrentEntity, out var changeling))
|
||||
return 0f;
|
||||
|
||||
if (!changeling.AbsorbedEntities.ContainsKey(targetDna.DNA))
|
||||
return 0f;
|
||||
|
||||
//Target absorbed by this changeling, so 50% of work is done
|
||||
progress += 0.5f;
|
||||
|
||||
if (_emergencyShuttle.IsTargetEscaping(mind.CurrentEntity.Value) && selfDna.DNA == targetDna.DNA)
|
||||
progress += 0.5f;
|
||||
|
||||
if (_emergencyShuttle.ShuttlesLeft)
|
||||
return progress;
|
||||
|
||||
return progress;
|
||||
}
|
||||
|
||||
#endregion
|
||||
}
|
||||
@@ -0,0 +1,6 @@
|
||||
namespace Content.Server.Changeling.Objectives.Components;
|
||||
|
||||
[RegisterComponent]
|
||||
public sealed partial class AbsorbChangelingConditionComponent : Component
|
||||
{
|
||||
}
|
||||
@@ -0,0 +1,7 @@
|
||||
namespace Content.Server.Changeling.Objectives.Components;
|
||||
|
||||
[RegisterComponent]
|
||||
public sealed partial class AbsorbDnaConditionComponent : Component
|
||||
{
|
||||
public int NeedToAbsorb;
|
||||
}
|
||||
@@ -0,0 +1,6 @@
|
||||
namespace Content.Server.Changeling.Objectives.Components;
|
||||
|
||||
[RegisterComponent]
|
||||
public sealed partial class AbsorbMoreConditionComponent: Component
|
||||
{
|
||||
}
|
||||
@@ -0,0 +1,7 @@
|
||||
namespace Content.Server.Changeling.Objectives.Components;
|
||||
|
||||
[RegisterComponent]
|
||||
public sealed partial class EscapeWithIdentityConditionComponent : Component
|
||||
{
|
||||
|
||||
}
|
||||
@@ -0,0 +1,7 @@
|
||||
namespace Content.Server.Changeling.Objectives.Components;
|
||||
|
||||
[RegisterComponent]
|
||||
public sealed partial class PickRandomChangelingComponent : Component
|
||||
{
|
||||
|
||||
}
|
||||
@@ -0,0 +1,7 @@
|
||||
namespace Content.Server.Changeling.Objectives.Components;
|
||||
|
||||
[RegisterComponent]
|
||||
public sealed partial class PickRandomIdentityComponent : Component
|
||||
{
|
||||
|
||||
}
|
||||
8
Content.Server/Changeling/TentacleGun.cs
Normal file
8
Content.Server/Changeling/TentacleGun.cs
Normal file
@@ -0,0 +1,8 @@
|
||||
using Content.Shared.Changeling;
|
||||
|
||||
namespace Content.Server.Changeling;
|
||||
|
||||
public sealed class TentacleGun : SharedTentacleGun
|
||||
{
|
||||
|
||||
}
|
||||
9
Content.Server/Changeling/UncloneableComponent.cs
Normal file
9
Content.Server/Changeling/UncloneableComponent.cs
Normal file
@@ -0,0 +1,9 @@
|
||||
namespace Content.Server.Changeling;
|
||||
|
||||
/// <summary>
|
||||
/// This is used for the uncloneable trait.
|
||||
/// </summary>
|
||||
[RegisterComponent]
|
||||
public sealed partial class UncloneableComponent : Component
|
||||
{
|
||||
}
|
||||
43
Content.Server/Chat/Commands/ChangelingChatCommand.cs
Normal file
43
Content.Server/Chat/Commands/ChangelingChatCommand.cs
Normal file
@@ -0,0 +1,43 @@
|
||||
using Content.Server.Chat.Managers;
|
||||
using Content.Server.Chat.Systems;
|
||||
using Content.Shared.Administration;
|
||||
using Content.Shared.Changeling;
|
||||
using Robust.Shared.Console;
|
||||
using Robust.Shared.Enums;
|
||||
|
||||
namespace Content.Server.Chat.Commands;
|
||||
|
||||
[AnyCommand]
|
||||
internal sealed class ChangelingChatCommand : IConsoleCommand
|
||||
{
|
||||
public string Command => "gsay";
|
||||
public string Description => "Send changeling Hive message";
|
||||
public string Help => "gsay <text>";
|
||||
public void Execute(IConsoleShell shell, string argStr, string[] args)
|
||||
{
|
||||
if (shell.Player is not { } player)
|
||||
{
|
||||
shell.WriteError("This command cannot be run from the server.");
|
||||
return;
|
||||
}
|
||||
|
||||
if (player.AttachedEntity is not { Valid: true } entity)
|
||||
return;
|
||||
|
||||
if (args.Length < 1)
|
||||
return;
|
||||
|
||||
var entityManager = IoCManager.Resolve<IEntityManager>();
|
||||
|
||||
if (!entityManager.HasComponent<ChangelingComponent>(entity))
|
||||
return;
|
||||
|
||||
|
||||
var message = string.Join(" ", args).Trim();
|
||||
if (string.IsNullOrEmpty(message))
|
||||
return;
|
||||
|
||||
entityManager.System<ChatSystem>().TrySendInGameOOCMessage(entity, message,
|
||||
InGameOOCChatType.Changeling, false, shell, player);
|
||||
}
|
||||
}
|
||||
@@ -1,5 +1,6 @@
|
||||
using Content.Server.Administration.Logs;
|
||||
using Content.Server.Popups;
|
||||
using Content.Shared.Changeling;
|
||||
using Content.Shared.Damage;
|
||||
using Content.Shared.Damage.Prototypes;
|
||||
using Content.Shared.Database;
|
||||
@@ -31,6 +32,11 @@ namespace Content.Server.Chat
|
||||
if (_tagSystem.HasTag(victim, "CannotSuicide"))
|
||||
return false;
|
||||
|
||||
//Miracle edit
|
||||
if (TryComp<ChangelingComponent>(victim, out var changeling) && changeling.IsRegenerating)
|
||||
return false;
|
||||
//Miracle edit end
|
||||
|
||||
// Checks to see if the player is dead.
|
||||
if (!TryComp<MobStateComponent>(victim, out var mobState) || _mobState.IsDead(victim, mobState))
|
||||
return false;
|
||||
|
||||
@@ -14,6 +14,7 @@ using Content.Server._White.PandaSocket.Main;
|
||||
using Content.Shared.ActionBlocker;
|
||||
using Content.Shared.Administration;
|
||||
using Content.Shared.CCVar;
|
||||
using Content.Shared.Changeling;
|
||||
using Content.Shared.Chat;
|
||||
using Content.Shared.Database;
|
||||
using Content.Shared.Decals;
|
||||
@@ -357,6 +358,9 @@ public sealed partial class ChatSystem : SharedChatSystem
|
||||
case InGameOOCChatType.Looc:
|
||||
SendLOOC(source, player, message, hideChat);
|
||||
break;
|
||||
case InGameOOCChatType.Changeling:
|
||||
SendChangelingChat(source, player, message, hideChat);
|
||||
break;
|
||||
case InGameOOCChatType.Cult:
|
||||
SendCultChat(source, player, message, hideChat);
|
||||
break;
|
||||
@@ -767,6 +771,37 @@ public sealed partial class ChatSystem : SharedChatSystem
|
||||
_adminLogger.Add(LogType.Chat, LogImpact.Low, $"LOOC from {player:Player}: {message}");
|
||||
}
|
||||
|
||||
|
||||
private void SendChangelingChat(EntityUid source, ICommonSession player, string message, bool hideChat)
|
||||
{
|
||||
if (!TryComp<ChangelingComponent>(source, out var changeling))
|
||||
return;
|
||||
|
||||
var clients = GetChangelingChatClients();
|
||||
|
||||
var playerName = changeling.HiveName;
|
||||
|
||||
message = $"{char.ToUpper(message[0])}{message[1..]}";
|
||||
|
||||
var wrappedMessage = Loc.GetString("chat-manager-send-changeling-chat-wrap-message",
|
||||
("player", playerName),
|
||||
("message", FormattedMessage.EscapeText(message)));
|
||||
|
||||
_adminLogger.Add(LogType.Chat, LogImpact.Low, $"Changeling chat from {player:Player}-({playerName}): {message}");
|
||||
|
||||
_chatManager.ChatMessageToMany(ChatChannel.Changeling, message, wrappedMessage, source,
|
||||
hideChat, false, clients.ToList());
|
||||
}
|
||||
|
||||
private IEnumerable<INetChannel> GetChangelingChatClients()
|
||||
{
|
||||
return Filter.Empty()
|
||||
.AddWhereAttachedEntity(HasComp<GhostComponent>)
|
||||
.AddWhereAttachedEntity(HasComp<ChangelingComponent>)
|
||||
.Recipients
|
||||
.Select(p => p.Channel);
|
||||
}
|
||||
|
||||
// WD EDIT
|
||||
private void SendCultChat(EntityUid source, ICommonSession player, string message, bool hideChat)
|
||||
{
|
||||
@@ -1243,6 +1278,7 @@ public enum InGameOOCChatType : byte
|
||||
{
|
||||
Looc,
|
||||
Dead,
|
||||
Changeling,
|
||||
Cult
|
||||
}
|
||||
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
using Content.Server.Atmos.EntitySystems;
|
||||
using Content.Server.Changeling;
|
||||
using Content.Server.Chat.Systems;
|
||||
using Content.Server.Cloning.Components;
|
||||
using Content.Server.DeviceLinking.Systems;
|
||||
@@ -177,6 +178,19 @@ namespace Content.Server.Cloning
|
||||
if (_configManager.GetCVar(CCVars.BiomassEasyMode))
|
||||
cloningCost = (int) Math.Round(cloningCost * EasyModeCloningCost);
|
||||
|
||||
// Check if they have the uncloneable trait
|
||||
if (TryComp<UncloneableComponent>(bodyToClone, out _))
|
||||
{
|
||||
if (clonePod.ConnectedConsole != null)
|
||||
{
|
||||
_chatSystem.TrySendInGameICMessage(clonePod.ConnectedConsole.Value,
|
||||
Loc.GetString("cloning-console-uncloneable-trait-error"),
|
||||
InGameICChatType.Speak, false);
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
// biomass checks
|
||||
var biomassAmount = _material.GetMaterialAmount(uid, clonePod.RequiredMaterial);
|
||||
|
||||
|
||||
@@ -31,7 +31,6 @@
|
||||
<ProjectReference Include="..\Content.Shared\Content.Shared.csproj" />
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<Folder Include="Objectives\Interfaces\" />
|
||||
<Folder Include="Tools\Systems\" />
|
||||
</ItemGroup>
|
||||
<Import Project="..\RobustToolbox\MSBuild\Robust.Properties.targets" />
|
||||
|
||||
@@ -6,6 +6,7 @@ using Content.Server.GameTicking.Presets;
|
||||
using Content.Server.Maps;
|
||||
using Content.Server.Ghost;
|
||||
using Content.Shared.CCVar;
|
||||
using Content.Shared.Changeling;
|
||||
using Content.Shared.Damage;
|
||||
using Content.Shared.Damage.Prototypes;
|
||||
using Content.Shared.Database;
|
||||
@@ -229,6 +230,11 @@ namespace Content.Server.GameTicking
|
||||
return false;
|
||||
}
|
||||
|
||||
//Miracle edit
|
||||
if (TryComp<ChangelingComponent>(playerEntity, out var changeling) && changeling.IsRegenerating)
|
||||
return false;
|
||||
//Miracle edit end
|
||||
|
||||
if (HasComp<GhostComponent>(playerEntity))
|
||||
return false;
|
||||
|
||||
|
||||
@@ -174,6 +174,32 @@ public sealed class ActionContainerSystem : EntitySystem
|
||||
DebugTools.AssertEqual(oldContainer.Container.Count, 0);
|
||||
}
|
||||
|
||||
|
||||
//Miracle edit
|
||||
public void TransferAllActionsFiltered(
|
||||
EntityUid from,
|
||||
EntityUid to,
|
||||
ActionsContainerComponent? oldContainer = null,
|
||||
ActionsContainerComponent? newContainer = null)
|
||||
{
|
||||
if (!Resolve(from, ref oldContainer) || !Resolve(to, ref newContainer))
|
||||
return;
|
||||
|
||||
foreach (var action in oldContainer.Container.ContainedEntities.ToArray())
|
||||
{
|
||||
var actions = newContainer.Container.ContainedEntities;
|
||||
|
||||
var toAdd = MetaData(action).EntityPrototype?.ID;
|
||||
|
||||
if (actions.Select(act => MetaData(act).EntityPrototype?.ID).Any(ext => toAdd == ext))
|
||||
continue;
|
||||
|
||||
TransferAction(action, to, container: newContainer);
|
||||
}
|
||||
}
|
||||
|
||||
//Miracle edit end
|
||||
|
||||
/// <summary>
|
||||
/// Transfers an actions from one container to another, while changing the attached entity.
|
||||
/// </summary>
|
||||
|
||||
@@ -55,6 +55,7 @@ namespace Content.Shared.Alert
|
||||
VowOfSilence,
|
||||
VowBroken,
|
||||
Essence,
|
||||
Chemicals,
|
||||
Corporeal,
|
||||
Bleed,
|
||||
Pacified,
|
||||
|
||||
10
Content.Shared/Changeling/AbsorbedComponent.cs
Normal file
10
Content.Shared/Changeling/AbsorbedComponent.cs
Normal file
@@ -0,0 +1,10 @@
|
||||
using Robust.Shared.GameStates;
|
||||
|
||||
namespace Content.Shared.Changeling;
|
||||
|
||||
|
||||
[RegisterComponent, NetworkedComponent]
|
||||
public sealed partial class AbsorbedComponent : Component
|
||||
{
|
||||
public EntityUid AbsorberMind;
|
||||
}
|
||||
70
Content.Shared/Changeling/ChangelingComponent.cs
Normal file
70
Content.Shared/Changeling/ChangelingComponent.cs
Normal file
@@ -0,0 +1,70 @@
|
||||
using Content.Shared.Humanoid;
|
||||
using Robust.Shared.GameStates;
|
||||
using Robust.Shared.Prototypes;
|
||||
|
||||
namespace Content.Shared.Changeling;
|
||||
|
||||
|
||||
[RegisterComponent, NetworkedComponent]
|
||||
public sealed partial class ChangelingComponent : Component
|
||||
{
|
||||
[DataField("chemRegenRate")]
|
||||
public int ChemicalRegenRate = 2;
|
||||
|
||||
[DataField("chemicalCap")]
|
||||
public int ChemicalCapacity = 75;
|
||||
|
||||
[ViewVariables(VVAccess.ReadWrite), DataField("chemicalsBalance")]
|
||||
public int ChemicalsBalance = 20;
|
||||
|
||||
[ViewVariables(VVAccess.ReadWrite), DataField("pointsBalance")]
|
||||
public int StartingPointsBalance = 10;
|
||||
|
||||
[ViewVariables(VVAccess.ReadOnly)]
|
||||
public float Accumulator;
|
||||
|
||||
[ViewVariables(VVAccess.ReadOnly)]
|
||||
public float UpdateDelay = 6f;
|
||||
|
||||
[ViewVariables(VVAccess.ReadOnly)]
|
||||
public bool IsRegenerating;
|
||||
|
||||
[ViewVariables(VVAccess.ReadOnly)]
|
||||
public bool IsLesserForm;
|
||||
|
||||
[ViewVariables(VVAccess.ReadOnly)]
|
||||
public string HiveName;
|
||||
|
||||
[ViewVariables(VVAccess.ReadOnly), DataField("absorbedEntities")]
|
||||
public Dictionary<string, HumanoidData> AbsorbedEntities = new();
|
||||
|
||||
[ViewVariables(VVAccess.ReadWrite), DataField("AbsorbDNACost")]
|
||||
public int AbsorbDnaCost;
|
||||
|
||||
[ViewVariables(VVAccess.ReadWrite), DataField("AbsorbDNADelay")]
|
||||
public float AbsorbDnaDelay = 10f;
|
||||
|
||||
[ViewVariables(VVAccess.ReadWrite), DataField("TransformDelay")]
|
||||
public float TransformDelay = 2f;
|
||||
|
||||
[ViewVariables(VVAccess.ReadWrite), DataField("RegenerateDelay")]
|
||||
public float RegenerateDelay = 20f;
|
||||
|
||||
[ViewVariables(VVAccess.ReadWrite), DataField("LesserFormDelay")]
|
||||
public float LesserFormDelay = 5f;
|
||||
|
||||
public bool IsInited;
|
||||
}
|
||||
|
||||
public struct HumanoidData
|
||||
{
|
||||
public EntityPrototype EntityPrototype;
|
||||
|
||||
public MetaDataComponent? MetaDataComponent;
|
||||
|
||||
public HumanoidAppearanceComponent AppearanceComponent;
|
||||
|
||||
public string Name;
|
||||
|
||||
public string Dna;
|
||||
}
|
||||
63
Content.Shared/Changeling/ChangelingNameGenerator.cs
Normal file
63
Content.Shared/Changeling/ChangelingNameGenerator.cs
Normal file
@@ -0,0 +1,63 @@
|
||||
using System.Linq;
|
||||
using Robust.Shared.Random;
|
||||
|
||||
namespace Content.Shared.Changeling;
|
||||
|
||||
public sealed class ChangelingNameGenerator
|
||||
{
|
||||
[Dependency] private readonly IRobustRandom _random = default!;
|
||||
|
||||
private List<string> _used = new();
|
||||
|
||||
private readonly List<string> _greekAlphabet = new()
|
||||
{
|
||||
"Alpha",
|
||||
"Beta",
|
||||
"Gamma",
|
||||
"Delta",
|
||||
"Epsilon",
|
||||
"Zeta",
|
||||
"Eta",
|
||||
"Theta",
|
||||
"Iota",
|
||||
"Kappa",
|
||||
"Lambda",
|
||||
"Mu",
|
||||
"Nu",
|
||||
"Xi",
|
||||
"Omicron",
|
||||
"Pi",
|
||||
"Rho",
|
||||
"Sigma",
|
||||
"Tau",
|
||||
"Upsilon",
|
||||
"Phi",
|
||||
"Chi",
|
||||
"Psi",
|
||||
"Omega"
|
||||
};
|
||||
|
||||
private string GenWhiteLabelName()
|
||||
{
|
||||
var number = _random.Next(0,10000);
|
||||
return $"HiveMember-{number}";
|
||||
}
|
||||
|
||||
public string GetName()
|
||||
{
|
||||
_random.Shuffle(_greekAlphabet);
|
||||
|
||||
foreach (var selected in _greekAlphabet.Where(selected => !_used.Contains(selected)))
|
||||
{
|
||||
_used.Add(selected);
|
||||
return selected;
|
||||
}
|
||||
|
||||
return GenWhiteLabelName();
|
||||
}
|
||||
|
||||
public void ClearUsed()
|
||||
{
|
||||
_used.Clear();
|
||||
}
|
||||
}
|
||||
88
Content.Shared/Changeling/ChemicalsSystem.cs
Normal file
88
Content.Shared/Changeling/ChemicalsSystem.cs
Normal file
@@ -0,0 +1,88 @@
|
||||
using Content.Shared.Alert;
|
||||
using Content.Shared.Mobs.Systems;
|
||||
using Robust.Shared.Network;
|
||||
|
||||
namespace Content.Shared.Changeling;
|
||||
|
||||
public sealed class ChemicalsSystem : EntitySystem
|
||||
{
|
||||
[Dependency] private readonly MobStateSystem _mobStateSystem = default!;
|
||||
[Dependency] private readonly AlertsSystem _alertsSystem = default!;
|
||||
[Dependency] private readonly INetManager _net = default!;
|
||||
|
||||
public bool AddChemicals(EntityUid uid, ChangelingComponent component, int quantity)
|
||||
{
|
||||
if (_mobStateSystem.IsDead(uid))
|
||||
return false;
|
||||
|
||||
var toAdd = quantity;
|
||||
|
||||
if (component.ChemicalsBalance == component.ChemicalCapacity)
|
||||
return false;
|
||||
|
||||
if (component.ChemicalsBalance + toAdd > component.ChemicalCapacity)
|
||||
{
|
||||
var overflow = component.ChemicalsBalance + toAdd - component.ChemicalCapacity;
|
||||
toAdd -= overflow;
|
||||
component.ChemicalsBalance += toAdd;
|
||||
}
|
||||
|
||||
component.ChemicalsBalance += toAdd;
|
||||
Dirty(uid, component);
|
||||
|
||||
UpdateAlert(uid, component);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
public bool RemoveChemicals(EntityUid uid, ChangelingComponent component, int quantity)
|
||||
{
|
||||
if (_mobStateSystem.IsDead(uid) && !component.IsRegenerating)
|
||||
return false;
|
||||
|
||||
var toRemove = quantity;
|
||||
|
||||
if (component.ChemicalsBalance == 0)
|
||||
return false;
|
||||
|
||||
if (component.ChemicalsBalance - toRemove < 0)
|
||||
return false;
|
||||
|
||||
component.ChemicalsBalance -= toRemove;
|
||||
Dirty(uid, component);
|
||||
|
||||
UpdateAlert(uid, component);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
public override void Update(float frameTime)
|
||||
{
|
||||
base.Update(frameTime);
|
||||
|
||||
var query = EntityQueryEnumerator<ChangelingComponent>();
|
||||
|
||||
while (query.MoveNext(out var uid, out var component))
|
||||
{
|
||||
component.Accumulator += frameTime;
|
||||
|
||||
if(component.Accumulator < component.UpdateDelay)
|
||||
continue;
|
||||
|
||||
if (component.IsRegenerating)
|
||||
continue;
|
||||
|
||||
component.Accumulator = 0;
|
||||
AddChemicals(uid, component, component.ChemicalRegenRate);
|
||||
}
|
||||
}
|
||||
|
||||
public void UpdateAlert(EntityUid uid, ChangelingComponent component)
|
||||
{
|
||||
if(_net.IsServer)
|
||||
{
|
||||
_alertsSystem.ShowAlert(uid, AlertType.Chemicals,
|
||||
(short) Math.Clamp(Math.Round(component.ChemicalsBalance / 10f), 0, 7));
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,8 @@
|
||||
using Robust.Shared.GameStates;
|
||||
|
||||
namespace Content.Shared.Changeling;
|
||||
|
||||
[RegisterComponent, NetworkedComponent]
|
||||
public sealed partial class LesserFormRestrictedComponent : Component
|
||||
{
|
||||
}
|
||||
105
Content.Shared/Changeling/SharedChangeling.cs
Normal file
105
Content.Shared/Changeling/SharedChangeling.cs
Normal file
@@ -0,0 +1,105 @@
|
||||
using Content.Shared.Actions;
|
||||
using Content.Shared.DoAfter;
|
||||
using Robust.Shared.Serialization;
|
||||
|
||||
namespace Content.Shared.Changeling;
|
||||
|
||||
[Serializable, NetSerializable]
|
||||
public sealed partial class AbsorbDnaDoAfterEvent : SimpleDoAfterEvent
|
||||
{
|
||||
}
|
||||
|
||||
public sealed partial class AbsorbDnaActionEvent : EntityTargetActionEvent
|
||||
{
|
||||
}
|
||||
|
||||
|
||||
[Serializable, NetSerializable]
|
||||
public sealed partial class TransformDoAfterEvent : SimpleDoAfterEvent
|
||||
{
|
||||
public string SelectedDna;
|
||||
}
|
||||
|
||||
public sealed partial class TransformActionEvent : InstantActionEvent
|
||||
{
|
||||
|
||||
}
|
||||
|
||||
[Serializable, NetSerializable]
|
||||
public sealed partial class RegenerateDoAfterEvent : SimpleDoAfterEvent
|
||||
{
|
||||
}
|
||||
|
||||
public sealed partial class RegenerateActionEvent : InstantActionEvent
|
||||
{
|
||||
|
||||
}
|
||||
|
||||
[Serializable, NetSerializable]
|
||||
public sealed partial class LesserFormDoAfterEvent : SimpleDoAfterEvent
|
||||
{
|
||||
}
|
||||
|
||||
public sealed partial class LesserFormActionEvent : InstantActionEvent
|
||||
{
|
||||
|
||||
}
|
||||
|
||||
public sealed partial class TransformStingActionEvent : EntityTargetActionEvent
|
||||
{
|
||||
|
||||
}
|
||||
|
||||
public sealed partial class BlindStingActionEvent : EntityTargetActionEvent
|
||||
{
|
||||
|
||||
}
|
||||
|
||||
public sealed partial class MuteStingActionEvent : EntityTargetActionEvent
|
||||
{
|
||||
|
||||
}
|
||||
public sealed partial class HallucinationStingActionEvent : EntityTargetActionEvent
|
||||
{
|
||||
|
||||
}
|
||||
|
||||
public sealed partial class CryoStingActionEvent : EntityTargetActionEvent
|
||||
{
|
||||
|
||||
}
|
||||
|
||||
public sealed partial class AdrenalineSacsActionEvent : InstantActionEvent
|
||||
{
|
||||
|
||||
}
|
||||
|
||||
public sealed partial class FleshmendActionEvent : InstantActionEvent
|
||||
{
|
||||
|
||||
}
|
||||
|
||||
public sealed partial class ArmbladeActionEvent : InstantActionEvent
|
||||
{
|
||||
|
||||
}
|
||||
|
||||
public sealed partial class OrganicShieldActionEvent : InstantActionEvent
|
||||
{
|
||||
|
||||
}
|
||||
|
||||
public sealed partial class ChitinousArmorActionEvent : InstantActionEvent
|
||||
{
|
||||
|
||||
}
|
||||
|
||||
public sealed partial class TentacleArmActionEvent : InstantActionEvent
|
||||
{
|
||||
|
||||
}
|
||||
|
||||
public sealed partial class ChangelingShopActionEvent : InstantActionEvent
|
||||
{
|
||||
|
||||
}
|
||||
33
Content.Shared/Changeling/SharedChangelingChat.cs
Normal file
33
Content.Shared/Changeling/SharedChangelingChat.cs
Normal file
@@ -0,0 +1,33 @@
|
||||
namespace Content.Shared.Changeling;
|
||||
|
||||
public sealed class SharedChangelingChat : EntitySystem
|
||||
{
|
||||
public override void Initialize()
|
||||
{
|
||||
base.Initialize();
|
||||
|
||||
SubscribeLocalEvent<ChangelingComponent, ComponentStartup>(OnInit);
|
||||
SubscribeLocalEvent<ChangelingComponent, ComponentShutdown>(OnShutdown);
|
||||
}
|
||||
|
||||
private void OnInit(EntityUid uid, ChangelingComponent component, ComponentStartup args)
|
||||
{
|
||||
RaiseLocalEvent(new ChangelingUserStart(true));
|
||||
}
|
||||
|
||||
private void OnShutdown(EntityUid uid, ChangelingComponent component, ComponentShutdown args)
|
||||
{
|
||||
RaiseLocalEvent(new ChangelingUserStart(false));
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
public sealed class ChangelingUserStart
|
||||
{
|
||||
public bool Created { get; }
|
||||
|
||||
public ChangelingUserStart(bool state)
|
||||
{
|
||||
Created = state;
|
||||
}
|
||||
}
|
||||
155
Content.Shared/Changeling/SharedTentacleGun.cs
Normal file
155
Content.Shared/Changeling/SharedTentacleGun.cs
Normal file
@@ -0,0 +1,155 @@
|
||||
using System.Numerics;
|
||||
using Content.Shared.Hands.EntitySystems;
|
||||
using Content.Shared.Humanoid;
|
||||
using Content.Shared.Physics;
|
||||
using Content.Shared.Projectiles;
|
||||
using Content.Shared.Stunnable;
|
||||
using Content.Shared.Throwing;
|
||||
using Content.Shared.Weapons.Misc;
|
||||
using Content.Shared.Weapons.Ranged.Components;
|
||||
using Content.Shared.Weapons.Ranged.Systems;
|
||||
using Robust.Shared.Input;
|
||||
using Robust.Shared.Network;
|
||||
using Robust.Shared.Physics.Components;
|
||||
using Robust.Shared.Random;
|
||||
using Robust.Shared.Serialization;
|
||||
using Robust.Shared.Timing;
|
||||
using Robust.Shared.Utility;
|
||||
|
||||
namespace Content.Shared.Changeling;
|
||||
|
||||
public abstract class SharedTentacleGun : EntitySystem
|
||||
{
|
||||
[Dependency] private readonly IGameTiming _timing = default!;
|
||||
[Dependency] private readonly INetManager _netManager = default!;
|
||||
[Dependency] private readonly SharedAppearanceSystem _appearance = default!;
|
||||
[Dependency] private readonly ThrowingSystem _throwingSystem = default!;
|
||||
[Dependency] private readonly ITimerManager _timerManager = default!;
|
||||
[Dependency] private readonly SharedStunSystem _stunSystem = default!;
|
||||
[Dependency] private readonly SharedHandsSystem _handsSystem = default!;
|
||||
[Dependency] private readonly IRobustRandom _random = default!;
|
||||
|
||||
public override void Initialize()
|
||||
{
|
||||
base.Initialize();
|
||||
|
||||
SubscribeLocalEvent<TentacleGunComponent, GunShotEvent>(OnTentacleShot);
|
||||
SubscribeLocalEvent<TentacleProjectileComponent, ProjectileEmbedEvent>(OnTentacleCollide);
|
||||
}
|
||||
|
||||
private void OnTentacleShot(EntityUid uid, TentacleGunComponent component, ref GunShotEvent args)
|
||||
{
|
||||
foreach (var (shotUid, _) in args.Ammo)
|
||||
{
|
||||
if (!HasComp<TentacleProjectileComponent>(shotUid))
|
||||
continue;
|
||||
|
||||
Dirty(uid, component);
|
||||
var visuals = EnsureComp<JointVisualsComponent>(shotUid.Value);
|
||||
visuals.Sprite =
|
||||
new SpriteSpecifier.Rsi(new ResPath("Objects/Weapons/Guns/Launchers/tentacle_gun.rsi"), "frope");
|
||||
visuals.OffsetA = new Vector2(0f, 0.5f);
|
||||
visuals.Target = uid;
|
||||
Dirty(shotUid.Value, visuals);
|
||||
}
|
||||
|
||||
TryComp<AppearanceComponent>(uid, out var appearance);
|
||||
_appearance.SetData(uid, SharedTetherGunSystem.TetherVisualsStatus.Key, false, appearance);
|
||||
}
|
||||
|
||||
private void OnTentacleCollide(EntityUid uid, TentacleProjectileComponent component, ref ProjectileEmbedEvent args)
|
||||
{
|
||||
if (!_timing.IsFirstTimePredicted)
|
||||
return;
|
||||
|
||||
if (!HasComp<TentacleGunComponent>(args.Weapon))
|
||||
{
|
||||
QueueDel(uid);
|
||||
return;
|
||||
}
|
||||
|
||||
if (!TryComp<GunComponent>(args.Weapon, out var gun))
|
||||
{
|
||||
QueueDel(uid);
|
||||
return;
|
||||
}
|
||||
|
||||
if (!HasComp<HumanoidAppearanceComponent>(args.Embedded))
|
||||
{
|
||||
DeleteProjectile(uid);
|
||||
return;
|
||||
}
|
||||
|
||||
switch (gun.SelectedMode)
|
||||
{
|
||||
case SelectiveFire.PullMob when !PullMob(args):
|
||||
DeleteProjectile(uid);
|
||||
return;
|
||||
case SelectiveFire.PullMob:
|
||||
_timerManager.AddTimer(new Timer(1500, false, () =>
|
||||
{
|
||||
DeleteProjectile(uid);
|
||||
}));
|
||||
break;
|
||||
case SelectiveFire.PullItem:
|
||||
PullItem(args);
|
||||
DeleteProjectile(uid);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
private void PullItem(ProjectileEmbedEvent args)
|
||||
{
|
||||
foreach (var activeItem in _handsSystem.EnumerateHeld(args.Embedded))
|
||||
{
|
||||
if(!TryComp<PhysicsComponent>(activeItem, out var physicsComponent))
|
||||
return;
|
||||
|
||||
var coords = Transform(args.Embedded).Coordinates;
|
||||
_handsSystem.TryDrop(args.Embedded, coords);
|
||||
|
||||
var force = physicsComponent.Mass * 2.5f / 2;
|
||||
|
||||
_throwingSystem.TryThrow(activeItem, Transform(args.Shooter!.Value).Coordinates, force);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
private bool PullMob(ProjectileEmbedEvent args)
|
||||
{
|
||||
var stunTime = _random.Next(TimeSpan.FromSeconds(3), TimeSpan.FromSeconds(8));
|
||||
|
||||
if (!_stunSystem.TryParalyze(args.Embedded, stunTime, true))
|
||||
return false;
|
||||
|
||||
_throwingSystem.TryThrow(args.Embedded, Transform(args.Shooter!.Value).Coordinates, 5f);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
private void DeleteProjectile(EntityUid projUid)
|
||||
{
|
||||
TryComp<AppearanceComponent>(projUid, out var appearance);
|
||||
|
||||
if (!Deleted(projUid))
|
||||
{
|
||||
if (_netManager.IsServer)
|
||||
{
|
||||
QueueDel(projUid);
|
||||
}
|
||||
}
|
||||
|
||||
_appearance.SetData(projUid, SharedTetherGunSystem.TetherVisualsStatus.Key, true, appearance);
|
||||
}
|
||||
|
||||
[Serializable, NetSerializable]
|
||||
protected sealed class RequestTentacleMessage : EntityEventArgs
|
||||
{
|
||||
public BoundKeyFunction Key;
|
||||
|
||||
public RequestTentacleMessage(BoundKeyFunction key)
|
||||
{
|
||||
Key = key;
|
||||
}
|
||||
}
|
||||
}
|
||||
8
Content.Shared/Changeling/TentacleGunComponent.cs
Normal file
8
Content.Shared/Changeling/TentacleGunComponent.cs
Normal file
@@ -0,0 +1,8 @@
|
||||
using Robust.Shared.GameStates;
|
||||
|
||||
namespace Content.Shared.Changeling;
|
||||
|
||||
[RegisterComponent, NetworkedComponent]
|
||||
public sealed partial class TentacleGunComponent : Component
|
||||
{
|
||||
}
|
||||
9
Content.Shared/Changeling/TentacleProjectileComponent.cs
Normal file
9
Content.Shared/Changeling/TentacleProjectileComponent.cs
Normal file
@@ -0,0 +1,9 @@
|
||||
using Robust.Shared.GameStates;
|
||||
|
||||
namespace Content.Shared.Changeling;
|
||||
|
||||
|
||||
[RegisterComponent, NetworkedComponent]
|
||||
public sealed partial class TentacleProjectileComponent : Component
|
||||
{
|
||||
}
|
||||
@@ -75,17 +75,22 @@ namespace Content.Shared.Chat
|
||||
AdminChat = 1 << 12,
|
||||
|
||||
/// <summary>
|
||||
/// Unspecified.
|
||||
/// Changeling
|
||||
/// </summary>
|
||||
Unspecified = 1 << 13,
|
||||
Changeling = 1 << 13,
|
||||
|
||||
//WD EDIT
|
||||
Cult = 1 << 14,
|
||||
|
||||
/// <summary>
|
||||
/// Unspecified.
|
||||
/// </summary>
|
||||
Unspecified = 1 << 15,
|
||||
|
||||
/// <summary>
|
||||
/// Channels considered to be IC.
|
||||
/// </summary>
|
||||
IC = Local | Whisper | Radio | Dead | Emotes | Damage | Visual,
|
||||
IC = Local | Whisper | Radio | Dead | Emotes | Damage | Visual | Changeling,
|
||||
|
||||
AdminRelated = Admin | AdminAlert | AdminChat,
|
||||
}
|
||||
|
||||
@@ -15,6 +15,7 @@ public static class ChatChannelExtensions
|
||||
ChatChannel.AdminAlert => Color.Red,
|
||||
ChatChannel.AdminChat => Color.HotPink,
|
||||
ChatChannel.Whisper => Color.DarkGray,
|
||||
ChatChannel.Changeling => Color.Purple,
|
||||
ChatChannel.Cult => Color.DarkRed, // WD EDIT
|
||||
_ => Color.LightGray
|
||||
};
|
||||
|
||||
@@ -53,6 +53,8 @@
|
||||
/// </summary>
|
||||
Admin = ChatChannel.AdminChat,
|
||||
|
||||
Changeling = ChatChannel.Changeling,
|
||||
|
||||
Console = ChatChannel.Unspecified
|
||||
}
|
||||
}
|
||||
|
||||
@@ -21,6 +21,7 @@ public abstract class SharedChatSystem : EntitySystem
|
||||
public const char AdminPrefix = ']';
|
||||
public const char WhisperPrefix = ',';
|
||||
public const char DefaultChannelKey = 'h';
|
||||
public const char ChangelingPrefix = '<';
|
||||
public const char CultPrefix = '^'; // WD EDIT
|
||||
|
||||
[ValidatePrototypeId<RadioChannelPrototype>]
|
||||
|
||||
@@ -8,20 +8,20 @@ namespace Content.Shared.Eye.Blinding.Components;
|
||||
/// </summary>
|
||||
[RegisterComponent]
|
||||
[NetworkedComponent, AutoGenerateComponentState]
|
||||
[Access(typeof(BlurryVisionSystem))]
|
||||
// [Access(typeof(BlurryVisionSystem))]
|
||||
public sealed partial class BlurryVisionComponent : Component
|
||||
{
|
||||
/// <summary>
|
||||
/// Amount of "blurring". Also modifies examine ranges.
|
||||
/// </summary>
|
||||
[ViewVariables(VVAccess.ReadWrite), DataField("magnitude"), AutoNetworkedField]
|
||||
public float Magnitude;
|
||||
public float Magnitude = 4f;
|
||||
|
||||
/// <summary>
|
||||
/// Exponent that controls the magnitude of the effect.
|
||||
/// </summary>
|
||||
[ViewVariables(VVAccess.ReadWrite), DataField("correctionPower"), AutoNetworkedField]
|
||||
public float CorrectionPower;
|
||||
public float CorrectionPower = 2f;
|
||||
|
||||
public const float MaxMagnitude = 6;
|
||||
public const float DefaultCorrectionPower = 2f;
|
||||
|
||||
@@ -10,5 +10,6 @@ namespace Content.Shared.Implants.Components;
|
||||
[RegisterComponent, NetworkedComponent]
|
||||
public sealed partial class ImplantedComponent : Component
|
||||
{
|
||||
[ViewVariables(VVAccess.ReadOnly)]
|
||||
public Container ImplantContainer = default!;
|
||||
}
|
||||
|
||||
@@ -117,7 +117,8 @@ public abstract class SharedSubdermalImplantSystem : EntitySystem
|
||||
/// <param name="target">The entity to be implanted</param>
|
||||
/// <param name="implant"> The implant</param>
|
||||
/// <param name="component">The implant component</param>
|
||||
public void ForceImplant(EntityUid target, EntityUid implant, SubdermalImplantComponent component)
|
||||
/// <param name="containerForce">Should we force inserting in container</param>
|
||||
public void ForceImplant(EntityUid target, EntityUid implant, SubdermalImplantComponent component, bool containerForce = false)
|
||||
{
|
||||
//If the target doesn't have the implanted component, add it.
|
||||
var implantedComp = EnsureComp<ImplantedComponent>(target);
|
||||
@@ -175,6 +176,48 @@ public abstract class SharedSubdermalImplantSystem : EntitySystem
|
||||
RaiseLocalEvent(implant, relayEv);
|
||||
}
|
||||
}
|
||||
|
||||
//Miracle edit
|
||||
|
||||
/// <summary>
|
||||
/// Transfers all implants from one entity to another.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// This method transfers all implants from a donor entity to a recipient entity.
|
||||
/// Implants are moved from the donor's implant container to the recipient's implant container.
|
||||
/// </remarks>
|
||||
/// <param name="donor">The entity from which implants will be transferred.</param>
|
||||
/// <param name="recipient">The entity to which implants will be transferred.</param>
|
||||
public void TransferImplants(EntityUid donor, EntityUid recipient)
|
||||
{
|
||||
// Check if the donor has an ImplantedComponent, indicating the presence of implants
|
||||
if (!TryComp<ImplantedComponent>(donor, out var donorImplanted))
|
||||
return;
|
||||
|
||||
// Get the implant containers for both the donor and recipient entities
|
||||
var donorImplantContainer = donorImplanted.ImplantContainer;
|
||||
|
||||
// Get all implants from the donor's implant container
|
||||
var donorImplants = donorImplantContainer.ContainedEntities.ToArray();
|
||||
|
||||
// Transfer each implant from the donor to the recipient
|
||||
foreach (var donorImplant in donorImplants)
|
||||
{
|
||||
// Check for any conditions or filters before transferring (if needed)
|
||||
// For instance, verifying if the recipient can receive specific implants, etc.
|
||||
|
||||
// Remove the implant from the donor's implant container
|
||||
_container.Remove(donorImplant, donorImplantContainer, force: true);
|
||||
|
||||
if(!TryComp<SubdermalImplantComponent>(donorImplant, out var subdermal))
|
||||
return;
|
||||
|
||||
// Insert the implant into the recipient's implant container
|
||||
ForceImplant(recipient, donorImplant, subdermal, true);
|
||||
}
|
||||
}
|
||||
|
||||
//Miracle edit end
|
||||
}
|
||||
|
||||
public sealed class ImplantRelayEvent<T> where T : notnull
|
||||
|
||||
@@ -15,6 +15,7 @@ namespace Content.Shared.Input
|
||||
public static readonly BoundKeyFunction FocusLocalChat = "FocusLocalChatWindow";
|
||||
public static readonly BoundKeyFunction FocusEmote = "FocusEmote";
|
||||
public static readonly BoundKeyFunction FocusWhisperChat = "FocusWhisperChatWindow";
|
||||
public static readonly BoundKeyFunction FocusChangelingChat = "FocusChangelingChatWindow";
|
||||
public static readonly BoundKeyFunction FocusCultChat = "FocusCultChatWindow";
|
||||
public static readonly BoundKeyFunction FocusRadio = "FocusRadioWindow";
|
||||
public static readonly BoundKeyFunction FocusLOOC = "FocusLOOCWindow";
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
using Content.Shared.Humanoid.Markings;
|
||||
using Content.Shared.Changeling;
|
||||
using Content.Shared.Humanoid.Markings;
|
||||
using Content.Shared.Localizations;
|
||||
using Content.Shared._White.Cult.Systems;
|
||||
|
||||
@@ -10,7 +11,7 @@ namespace Content.Shared.IoC
|
||||
{
|
||||
IoCManager.Register<MarkingManager, MarkingManager>();
|
||||
IoCManager.Register<ContentLocalizationManager, ContentLocalizationManager>();
|
||||
|
||||
IoCManager.Register<ChangelingNameGenerator, ChangelingNameGenerator>();
|
||||
// WD EDIT
|
||||
IoCManager.Register<CultistWordGeneratorManager, CultistWordGeneratorManager>();
|
||||
// WD EDIT END
|
||||
|
||||
31
Content.Shared/Miracle/UI/ListViewBUI.cs
Normal file
31
Content.Shared/Miracle/UI/ListViewBUI.cs
Normal file
@@ -0,0 +1,31 @@
|
||||
using Robust.Shared.Serialization;
|
||||
|
||||
namespace Content.Shared.Miracle.UI;
|
||||
|
||||
[Serializable, NetSerializable]
|
||||
public enum ListViewSelectorUiKeyChangeling
|
||||
{
|
||||
Key
|
||||
}
|
||||
|
||||
[Serializable, NetSerializable]
|
||||
public sealed class ListViewBuiState : BoundUserInterfaceState
|
||||
{
|
||||
public Dictionary<string, string> Items { get; set; }
|
||||
|
||||
public ListViewBuiState(Dictionary<string, string> items)
|
||||
{
|
||||
Items = items;
|
||||
}
|
||||
}
|
||||
|
||||
[Serializable, NetSerializable]
|
||||
public sealed class ListViewItemSelectedMessage : BoundUserInterfaceMessage
|
||||
{
|
||||
public string SelectedItem { get; private set; }
|
||||
|
||||
public ListViewItemSelectedMessage(string selectedItem)
|
||||
{
|
||||
SelectedItem = selectedItem;
|
||||
}
|
||||
}
|
||||
35
Content.Shared/Miracle/UI/TransformStingBUI.cs
Normal file
35
Content.Shared/Miracle/UI/TransformStingBUI.cs
Normal file
@@ -0,0 +1,35 @@
|
||||
using Robust.Shared.Serialization;
|
||||
|
||||
namespace Content.Shared.Miracle.UI;
|
||||
|
||||
[Serializable, NetSerializable]
|
||||
public enum TransformStingSelectorUiKey
|
||||
{
|
||||
Key
|
||||
}
|
||||
|
||||
[Serializable, NetSerializable]
|
||||
public sealed class TransformStingBuiState : BoundUserInterfaceState
|
||||
{
|
||||
public Dictionary<string, string> Items { get; set; }
|
||||
public NetEntity Target { get; set; }
|
||||
|
||||
public TransformStingBuiState(Dictionary<string, string> items, NetEntity target)
|
||||
{
|
||||
Items = items;
|
||||
Target = target;
|
||||
}
|
||||
}
|
||||
|
||||
[Serializable, NetSerializable]
|
||||
public sealed class TransformStingItemSelectedMessage : BoundUserInterfaceMessage
|
||||
{
|
||||
public string SelectedItem { get; private set; }
|
||||
public NetEntity Target { get; private set; }
|
||||
|
||||
public TransformStingItemSelectedMessage(string selectedItem, NetEntity target)
|
||||
{
|
||||
SelectedItem = selectedItem;
|
||||
Target = target;
|
||||
}
|
||||
}
|
||||
@@ -186,4 +186,9 @@ public enum SelectiveFire : byte
|
||||
SemiAuto = 1 << 0,
|
||||
Burst = 1 << 1,
|
||||
FullAuto = 1 << 2, // Not in the building!
|
||||
|
||||
//Miracle edit
|
||||
PullItem = 1 << 3,
|
||||
PullMob = 1 << 4
|
||||
//Miracle edit end
|
||||
}
|
||||
|
||||
@@ -22,6 +22,12 @@ public sealed partial class RechargeBasicEntityAmmoComponent : Component
|
||||
Params = AudioParams.Default.WithVolume(-5f)
|
||||
};
|
||||
|
||||
//Miracle edit
|
||||
[DataField("playRechargeSound")]
|
||||
[AutoNetworkedField]
|
||||
public bool PlayRechargeSound = true;
|
||||
//Miracle edit end
|
||||
|
||||
[ViewVariables(VVAccess.ReadWrite),
|
||||
DataField("nextCharge", customTypeSerializer:typeof(TimeOffsetSerializer)),
|
||||
AutoNetworkedField]
|
||||
|
||||
@@ -51,7 +51,8 @@ public sealed class RechargeBasicEntityAmmoSystem : EntitySystem
|
||||
{
|
||||
// We don't predict this because occasionally on client it may not play.
|
||||
// PlayPredicted will still be predicted on the client.
|
||||
if (_netManager.IsServer)
|
||||
//Miracle edit start/end
|
||||
if (_netManager.IsServer && recharge.PlayRechargeSound)
|
||||
_audio.PlayPvs(recharge.RechargeSound, uid);
|
||||
}
|
||||
|
||||
|
||||
@@ -285,6 +285,10 @@ public abstract partial class SharedGunSystem : EntitySystem
|
||||
// Don't do this in the loop so we still reset NextFire.
|
||||
switch (gun.SelectedMode)
|
||||
{
|
||||
//Miracle edit
|
||||
case SelectiveFire.PullItem:
|
||||
case SelectiveFire.PullMob:
|
||||
//Miracle edit end
|
||||
case SelectiveFire.SemiAuto:
|
||||
shots = Math.Min(shots, 1 - gun.ShotCounter);
|
||||
break;
|
||||
|
||||
BIN
Resources/Audio/Ambience/Antag/changeling_start.ogg
Normal file
BIN
Resources/Audio/Ambience/Antag/changeling_start.ogg
Normal file
Binary file not shown.
3
Resources/Locale/en-US/Miracle/changeling.ftl
Normal file
3
Resources/Locale/en-US/Miracle/changeling.ftl
Normal file
@@ -0,0 +1,3 @@
|
||||
chat-manager-changeling-channel-name = HIVE
|
||||
hud-chatbox-select-channel-Changeling = Hive
|
||||
chat-manager-send-changeling-chat-wrap-message = [bold]\[HIVE\][/bold] [font size=13][italic][bold]{ $player }[/bold] hisses, "{ $message }"[/italic][/font]
|
||||
@@ -1,5 +1,6 @@
|
||||
verb-categories-antag = Antag ctrl
|
||||
admin-verb-make-traitor = Make the target into a traitor.
|
||||
admin-verb-make-changeling = Make the target into a changeling.
|
||||
admin-verb-make-zombie = Zombifies the target immediately.
|
||||
admin-verb-make-nuclear-operative = Make target into a lone Nuclear Operative.
|
||||
admin-verb-make-pirate = Make the target into a pirate. Note this doesn't configure the game rule.
|
||||
@@ -7,8 +8,9 @@ admin-verb-make-head-rev = Make the target into a Head Revolutionary.
|
||||
admin-verb-make-thief = Make the target into a thief.
|
||||
|
||||
admin-verb-text-make-traitor = Make Traitor
|
||||
admin-verb-text-make-changeling = Make Changeling
|
||||
admin-verb-text-make-zombie = Make Zombie
|
||||
admin-verb-text-make-nuclear-operative = Make Nuclear Operative
|
||||
admin-verb-text-make-pirate = Make Pirate
|
||||
admin-verb-text-make-head-rev = Make Head Rev
|
||||
admin-verb-text-make-thief = Make Thief
|
||||
admin-verb-text-make-thief = Make Thief
|
||||
|
||||
@@ -110,3 +110,6 @@ alerts-revenant-essence-desc = The power of souls. It sustains you and is used f
|
||||
|
||||
alerts-revenant-corporeal-name = Corporeal
|
||||
alerts-revenant-corporeal-desc = You have manifested physically. People around you can see and hurt you.
|
||||
|
||||
alerts-changeling-chemicals-name = Chemicals
|
||||
alerts-changeling-chemicals-desc = Our chemicals.
|
||||
|
||||
@@ -0,0 +1,13 @@
|
||||
#Changeling
|
||||
changeling-title = Changeling
|
||||
changeling-description = A changeling has boarded the station!
|
||||
changeling-not-enough-ready-players = Not enough players readied up for the game! There were {$readyPlayersCount} players readied up out of {$minimumPlayers} needed. Can't start Changeling.
|
||||
changeling-no-one-ready = No players readied up! Can't start Changeling.
|
||||
changeling-round-end-agent-name = changeling
|
||||
changeling-role-greeting =
|
||||
You are a changeling!
|
||||
Your objectives are listed in the character menu.
|
||||
Use the evolution shop to gain new abilities.
|
||||
Death to Nanotrasen!
|
||||
changeling-role-briefing-short = Use '<' to communicate with other members of the Hive.
|
||||
|
||||
@@ -0,0 +1,4 @@
|
||||
objective-condition-absorb-dna = Absorb {$count} humans.
|
||||
objective-condition-absorb-more-dna = Absorb more humans, that others in the Hive.
|
||||
objective-condition-absorb-changeling-title = Absorb {$targetName}, {CAPITALIZE($job)}.
|
||||
objective-condition-escape-with-identity-title = Escape with {$targetName}, {CAPITALIZE($job)} identity.
|
||||
@@ -1,6 +1,9 @@
|
||||
roles-antag-syndicate-agent-name = Syndicate agent
|
||||
roles-antag-syndicate-agent-objective = Complete your objectives without being caught.
|
||||
|
||||
roles-antag-changeling-name = Changeling
|
||||
roles-antag-changeling-objective = Complete your objectives without being caught.
|
||||
|
||||
roles-antag-initial-infected-name = Initial Infected
|
||||
roles-antag-initial-infected-objective = Once you turn, infect as many other crew members as possible.
|
||||
|
||||
|
||||
@@ -13,5 +13,7 @@ store-category-job = Job
|
||||
store-category-armor = Armor
|
||||
store-category-pointless = Pointless
|
||||
|
||||
# Revenant
|
||||
# Revenant && Changeling
|
||||
store-category-abilities = Abilities
|
||||
store-category-stings = Stings
|
||||
store-category-boosters = Boosters
|
||||
|
||||
@@ -9,7 +9,7 @@ store-currency-display-debugdollar = {$amount ->
|
||||
}
|
||||
store-currency-display-telecrystal = TC
|
||||
store-currency-display-stolen-essence = Stolen Essence
|
||||
|
||||
store-currency-display-changeling-point = DNA Points
|
||||
store-currency-display-space-cash = {$amount ->
|
||||
[one] One Dollar
|
||||
*[other] Dollars
|
||||
|
||||
@@ -12,6 +12,8 @@ gun-component-wrong-ammo = Wrong ammo!
|
||||
gun-SemiAuto = semi-auto
|
||||
gun-Burst = burst
|
||||
gun-FullAuto = full-auto
|
||||
gun-PullItem = grab item
|
||||
gun-PullMob = grab human
|
||||
|
||||
# BallisticAmmoProvider
|
||||
gun-ballistic-cycle = Cycle
|
||||
|
||||
208
Resources/Prototypes/Actions/changeling.yml
Normal file
208
Resources/Prototypes/Actions/changeling.yml
Normal file
@@ -0,0 +1,208 @@
|
||||
- type: entity
|
||||
id: ActionChangelingShop
|
||||
name: Shop
|
||||
description: Abilities shop.
|
||||
noSpawn: true
|
||||
components:
|
||||
- type: InstantAction
|
||||
itemIconStyle: NoItem
|
||||
icon: Interface/Actions/shop.png
|
||||
event: !type:ChangelingShopActionEvent
|
||||
- type: LesserFormRestricted
|
||||
|
||||
- type: entity
|
||||
id: ActionChangelingAbsorb
|
||||
name: Absorb
|
||||
description: Absorbs the human.
|
||||
noSpawn: true
|
||||
components:
|
||||
- type: EntityTargetAction
|
||||
itemIconStyle: NoItem
|
||||
icon: Interface/Actions/ling_absorb.png
|
||||
event: !type:AbsorbDnaActionEvent
|
||||
canTargetSelf: false
|
||||
useDelay: 10
|
||||
- type: LesserFormRestricted
|
||||
|
||||
- type: entity
|
||||
id: ActionChangelingTransform
|
||||
name: Transform
|
||||
description: Transform to the chosen person.
|
||||
noSpawn: true
|
||||
components:
|
||||
- type: InstantAction
|
||||
itemIconStyle: NoItem
|
||||
icon: Interface/Actions/transform.png
|
||||
event: !type:TransformActionEvent
|
||||
useDelay: 30
|
||||
|
||||
- type: entity
|
||||
id: ActionChangelingRegenerate
|
||||
name: Regenerate
|
||||
description: Enter in a regenerative stasis to heal self.
|
||||
noSpawn: true
|
||||
components:
|
||||
- type: InstantAction
|
||||
itemIconStyle: NoItem
|
||||
icon: Interface/Actions/reviving_stasis.png
|
||||
event: !type:RegenerateActionEvent
|
||||
checkCanInteract: false
|
||||
useDelay: 120
|
||||
- type: LesserFormRestricted
|
||||
|
||||
- type: entity
|
||||
id: ActionChangelingLesserForm
|
||||
name: Lesser Form
|
||||
description: Become a lesser form of self.
|
||||
noSpawn: true
|
||||
components:
|
||||
- type: InstantAction
|
||||
itemIconStyle: NoItem
|
||||
icon: Interface/Actions/lesser_form.png
|
||||
event: !type:LesserFormActionEvent
|
||||
useDelay: 90
|
||||
- type: LesserFormRestricted
|
||||
|
||||
- type: entity
|
||||
id: ActionTransformSting
|
||||
name: Transform Sting
|
||||
description: Injects a retrovirus that forces human victim to transform into another.
|
||||
noSpawn: true
|
||||
components:
|
||||
- type: EntityTargetAction
|
||||
itemIconStyle: NoItem
|
||||
icon: Interface/Actions/sting_transform.png
|
||||
event: !type:TransformStingActionEvent
|
||||
canTargetSelf: false
|
||||
useDelay: 120
|
||||
- type: LesserFormRestricted
|
||||
|
||||
- type: entity
|
||||
id: ActionBlindSting
|
||||
name: Blind Sting
|
||||
description: Stings a human, completely blinding them for a short time.
|
||||
noSpawn: true
|
||||
components:
|
||||
- type: EntityTargetAction
|
||||
itemIconStyle: NoItem
|
||||
icon: Interface/Actions/sting_blind.png
|
||||
event: !type:BlindStingActionEvent
|
||||
canTargetSelf: false
|
||||
useDelay: 30
|
||||
- type: LesserFormRestricted
|
||||
|
||||
- type: entity
|
||||
id: ActionMuteSting
|
||||
name: Mute Sting
|
||||
description: Silently stings a human, temporarily silencing them.
|
||||
noSpawn: true
|
||||
components:
|
||||
- type: EntityTargetAction
|
||||
itemIconStyle: NoItem
|
||||
icon: Interface/Actions/sting_mute.png
|
||||
event: !type:MuteStingActionEvent
|
||||
canTargetSelf: false
|
||||
useDelay: 30
|
||||
- type: LesserFormRestricted
|
||||
|
||||
- type: entity
|
||||
id: ActionHallucinationSting
|
||||
name: Hallucination Sting
|
||||
description: Injects large doses of hallucinogenic chemicals into their victim.
|
||||
noSpawn: true
|
||||
components:
|
||||
- type: EntityTargetAction
|
||||
itemIconStyle: NoItem
|
||||
icon: Interface/Actions/sting_hallucination.png
|
||||
event: !type:HallucinationStingActionEvent
|
||||
canTargetSelf: false
|
||||
useDelay: 30
|
||||
- type: LesserFormRestricted
|
||||
|
||||
- type: entity
|
||||
id: ActionCryoSting
|
||||
name: Cryogenic Sting
|
||||
description: Injects the target with a blend of chemicals that begins to turn their blood to ice.
|
||||
noSpawn: true
|
||||
components:
|
||||
- type: EntityTargetAction
|
||||
itemIconStyle: NoItem
|
||||
icon: Interface/Actions/sting_cryo.png
|
||||
event: !type:CryoStingActionEvent
|
||||
canTargetSelf: false
|
||||
useDelay: 30
|
||||
- type: LesserFormRestricted
|
||||
|
||||
- type: entity
|
||||
id: ActionAdrenalineSacs
|
||||
name: Adrenaline Sacs
|
||||
description: Allows to make use of additional adrenaline to instantly recover from knockdown.
|
||||
noSpawn: true
|
||||
components:
|
||||
- type: InstantAction
|
||||
itemIconStyle: NoItem
|
||||
icon: Interface/Actions/adrenaline_sacs.png
|
||||
event: !type:AdrenalineSacsActionEvent
|
||||
useDelay: 60
|
||||
- type: LesserFormRestricted
|
||||
|
||||
- type: entity
|
||||
id: ActionFleshmend
|
||||
name: Fleshmend
|
||||
description: Rapidly heal all type of damage.
|
||||
noSpawn: true
|
||||
components:
|
||||
- type: InstantAction
|
||||
itemIconStyle: NoItem
|
||||
icon: Interface/Actions/fleshmend.png
|
||||
event: !type:FleshmendActionEvent
|
||||
useDelay: 60
|
||||
- type: LesserFormRestricted
|
||||
|
||||
- type: entity
|
||||
id: ActionArmblade
|
||||
name: Arm Blade
|
||||
description: Reforms one of the changeling's arms into a grotesque blade made out of bone and flesh.
|
||||
noSpawn: true
|
||||
components:
|
||||
- type: InstantAction
|
||||
itemIconStyle: NoItem
|
||||
icon: Interface/Actions/arm_blade.png
|
||||
event: !type:ArmbladeActionEvent
|
||||
- type: LesserFormRestricted
|
||||
|
||||
- type: entity
|
||||
id: ActionShield
|
||||
name: Organic Shield
|
||||
description: Reforms one of the changeling's arms into a large, fleshy shield.
|
||||
noSpawn: true
|
||||
components:
|
||||
- type: InstantAction
|
||||
itemIconStyle: NoItem
|
||||
icon: Interface/Actions/changeling_shield.png
|
||||
event: !type:OrganicShieldActionEvent
|
||||
- type: LesserFormRestricted
|
||||
|
||||
- type: entity
|
||||
id: ActionArmor
|
||||
name: Chitinous Armor
|
||||
description: Inflates the changeling's body into an all-consuming chitinous mass of armor.
|
||||
noSpawn: true
|
||||
components:
|
||||
- type: InstantAction
|
||||
itemIconStyle: NoItem
|
||||
icon: Interface/Actions/changeling_armor.png
|
||||
event: !type:ChitinousArmorActionEvent
|
||||
- type: LesserFormRestricted
|
||||
|
||||
- type: entity
|
||||
id: ActionTentacleArm
|
||||
name: Tentacle Arm
|
||||
description: Reforms one of the arms into a tentacle. Can grab items or humans depending on mode.
|
||||
noSpawn: true
|
||||
components:
|
||||
- type: InstantAction
|
||||
itemIconStyle: NoItem
|
||||
icon: Interface/Actions/tentacle_arm.png
|
||||
event: !type:TentacleArmActionEvent
|
||||
- type: LesserFormRestricted
|
||||
@@ -9,6 +9,7 @@
|
||||
icon: Interface/Actions/scream.png
|
||||
event: !type:ScreamActionEvent
|
||||
checkCanInteract: false
|
||||
- type: LesserFormRestricted
|
||||
|
||||
- type: entity
|
||||
id: ActionTurnUndead
|
||||
|
||||
@@ -10,6 +10,7 @@
|
||||
- category: Stamina
|
||||
- alertType: SuitPower
|
||||
- category: Internals
|
||||
- alertType: Chemicals
|
||||
- alertType: Fire
|
||||
- alertType: Handcuffed
|
||||
- alertType: Ensnared
|
||||
|
||||
23
Resources/Prototypes/Alerts/changeling.yml
Normal file
23
Resources/Prototypes/Alerts/changeling.yml
Normal file
@@ -0,0 +1,23 @@
|
||||
- type: alert
|
||||
id: Chemicals
|
||||
icons:
|
||||
- sprite: /Textures/Interface/Alerts/essence_counter.rsi
|
||||
state: essence0
|
||||
- sprite: /Textures/Interface/Alerts/essence_counter.rsi
|
||||
state: essence1
|
||||
- sprite: /Textures/Interface/Alerts/essence_counter.rsi
|
||||
state: essence2
|
||||
- sprite: /Textures/Interface/Alerts/essence_counter.rsi
|
||||
state: essence3
|
||||
- sprite: /Textures/Interface/Alerts/essence_counter.rsi
|
||||
state: essence4
|
||||
- sprite: /Textures/Interface/Alerts/essence_counter.rsi
|
||||
state: essence5
|
||||
- sprite: /Textures/Interface/Alerts/essence_counter.rsi
|
||||
state: essence6
|
||||
- sprite: /Textures/Interface/Alerts/essence_counter.rsi
|
||||
state: essence7
|
||||
name: alerts-changeling-chemicals-name
|
||||
description: alerts-changeling-chemicals-desc
|
||||
minSeverity: 0
|
||||
maxSeverity: 7
|
||||
158
Resources/Prototypes/Catalog/changeling_catalog.yml
Normal file
158
Resources/Prototypes/Catalog/changeling_catalog.yml
Normal file
@@ -0,0 +1,158 @@
|
||||
# Body Transforms
|
||||
- type: listing
|
||||
id: ChangelingLesserForm
|
||||
name: Lesser Form
|
||||
description: Become a lesser form of self.
|
||||
productAction: ActionChangelingLesserForm
|
||||
cost:
|
||||
ChangelingPoint: 1
|
||||
categories:
|
||||
- ChangelingAbilities
|
||||
conditions:
|
||||
- !type:ListingLimitedStockCondition
|
||||
stock: 1
|
||||
|
||||
- type: listing
|
||||
id: ChangelingArmblade
|
||||
name: Arm Blade
|
||||
description: Reforms one of the changeling's arms into a grotesque blade made out of bone and flesh.
|
||||
productAction: ActionArmblade
|
||||
cost:
|
||||
ChangelingPoint: 3
|
||||
categories:
|
||||
- ChangelingAbilities
|
||||
conditions:
|
||||
- !type:ListingLimitedStockCondition
|
||||
stock: 1
|
||||
|
||||
- type: listing
|
||||
id: ChangelingShield
|
||||
name: Organic Shield
|
||||
description: Reforms one of the changeling's arms into a large, fleshy shield.
|
||||
productAction: ActionShield
|
||||
cost:
|
||||
ChangelingPoint: 3
|
||||
categories:
|
||||
- ChangelingAbilities
|
||||
conditions:
|
||||
- !type:ListingLimitedStockCondition
|
||||
stock: 1
|
||||
|
||||
- type: listing
|
||||
id: ChangelingArmor
|
||||
name: Chitinous Armor
|
||||
description: Inflates the changeling's body into an all-consuming chitinous mass of armor.
|
||||
productAction: ActionArmor
|
||||
cost:
|
||||
ChangelingPoint: 4
|
||||
categories:
|
||||
- ChangelingAbilities
|
||||
conditions:
|
||||
- !type:ListingLimitedStockCondition
|
||||
stock: 1
|
||||
|
||||
- type: listing
|
||||
id: ChangelingTentacleArm
|
||||
name: Tentacle Arm
|
||||
description: Reforms one of the arms into a tentacle. Can grab items or humans depending on mode.
|
||||
productAction: ActionTentacleArm
|
||||
cost:
|
||||
ChangelingPoint: 4
|
||||
categories:
|
||||
- ChangelingAbilities
|
||||
conditions:
|
||||
- !type:ListingLimitedStockCondition
|
||||
stock: 1
|
||||
|
||||
# Stings
|
||||
- type: listing
|
||||
id: ChangelingTransformSting
|
||||
name: Transform Sting
|
||||
description: Injects a retrovirus that forces human victim to transform into another.
|
||||
productAction: ActionTransformSting
|
||||
cost:
|
||||
ChangelingPoint: 3
|
||||
categories:
|
||||
- ChangelingStings
|
||||
conditions:
|
||||
- !type:ListingLimitedStockCondition
|
||||
stock: 1
|
||||
|
||||
- type: listing
|
||||
id: ChangelingBlindSting
|
||||
name: Blind Sting
|
||||
description: Stings a human, completely blinding them for a short time.
|
||||
productAction: ActionBlindSting
|
||||
cost:
|
||||
ChangelingPoint: 2
|
||||
categories:
|
||||
- ChangelingStings
|
||||
conditions:
|
||||
- !type:ListingLimitedStockCondition
|
||||
stock: 1
|
||||
|
||||
- type: listing
|
||||
id: ChangelingMuteSting
|
||||
name: Mute Sting
|
||||
description: Silently stings a human, temporarily silencing them.
|
||||
productAction: ActionMuteSting
|
||||
cost:
|
||||
ChangelingPoint: 3
|
||||
categories:
|
||||
- ChangelingStings
|
||||
conditions:
|
||||
- !type:ListingLimitedStockCondition
|
||||
stock: 1
|
||||
|
||||
- type: listing
|
||||
id: ChangelingHallucinationSting
|
||||
name: Hallucination Sting
|
||||
description: Injects large doses of hallucinogenic chemicals into their victim.
|
||||
productAction: ActionHallucinationSting
|
||||
cost:
|
||||
ChangelingPoint: 1
|
||||
categories:
|
||||
- ChangelingStings
|
||||
conditions:
|
||||
- !type:ListingLimitedStockCondition
|
||||
stock: 1
|
||||
|
||||
- type: listing
|
||||
id: ChangelingCryogenicSting
|
||||
name: Cryogenic Sting
|
||||
description: Injects the target with a blend of chemicals that begins to turn their blood to ice.
|
||||
productAction: ActionCryoSting
|
||||
cost:
|
||||
ChangelingPoint: 3
|
||||
categories:
|
||||
- ChangelingStings
|
||||
conditions:
|
||||
- !type:ListingLimitedStockCondition
|
||||
stock: 1
|
||||
|
||||
# Boosters
|
||||
- type: listing
|
||||
id: ChangelingAdrenalineSacs
|
||||
name: Adrenaline Sacs
|
||||
description: Allows to make use of additional adrenaline to instantly recover from knockdown.
|
||||
productAction: ActionAdrenalineSacs
|
||||
cost:
|
||||
ChangelingPoint: 3
|
||||
categories:
|
||||
- ChangelingBoosters
|
||||
conditions:
|
||||
- !type:ListingLimitedStockCondition
|
||||
stock: 1
|
||||
|
||||
- type: listing
|
||||
id: ChangelingFleshmend
|
||||
name: Fleshmend
|
||||
description: Rapidly heal all type of damage.
|
||||
productAction: ActionFleshmend
|
||||
cost:
|
||||
ChangelingPoint: 2
|
||||
categories:
|
||||
- ChangelingBoosters
|
||||
conditions:
|
||||
- !type:ListingLimitedStockCondition
|
||||
stock: 1
|
||||
@@ -152,6 +152,19 @@
|
||||
- type: GroupExamine
|
||||
- type: GiftIgnore
|
||||
|
||||
- type: entity
|
||||
parent: ClothingOuterArmorHeavy
|
||||
id: ClothingOuterChangeling
|
||||
name: chitinous armor
|
||||
description: Chitinous and flesh mass of armor.
|
||||
components:
|
||||
- type: Sprite
|
||||
sprite: Clothing/OuterClothing/Armor/changeling.rsi
|
||||
- type: Clothing
|
||||
sprite: Clothing/OuterClothing/Armor/changeling.rsi
|
||||
- type: Unremoveable
|
||||
deleteOnDrop: true
|
||||
|
||||
- type: entity
|
||||
parent: ClothingOuterArmorHeavy
|
||||
id: ClothingOuterArmorHeavyGreen
|
||||
@@ -196,6 +209,8 @@
|
||||
- type: Clothing
|
||||
sprite: Clothing/OuterClothing/Armor/magusred.rsi
|
||||
|
||||
|
||||
|
||||
- type: entity
|
||||
parent: ClothingOuterBaseLarge
|
||||
id: ClothingOuterArmorScaf
|
||||
|
||||
@@ -1175,6 +1175,8 @@
|
||||
interfaces:
|
||||
- key: enum.StrippingUiKey.Key
|
||||
type: StrippableBoundUserInterface
|
||||
- key: enum.ListViewSelectorUiKey.Key
|
||||
type: ListViewSelectorBui
|
||||
- type: Sprite
|
||||
drawdepth: Mobs
|
||||
layers:
|
||||
|
||||
@@ -201,6 +201,10 @@
|
||||
type: HumanoidMarkingModifierBoundUserInterface
|
||||
- key: enum.StrippingUiKey.Key
|
||||
type: StrippableBoundUserInterface
|
||||
- key: enum.ListViewSelectorUiKeyChangeling.Key
|
||||
type: ListViewSelectorBui
|
||||
- key: enum.TransformStingSelectorUiKey.Key
|
||||
type: TransformStingSelectorBui
|
||||
# WD-EDIT
|
||||
- key: enum.NameSelectorUIKey.Key
|
||||
type: NameSelectorBUI
|
||||
|
||||
@@ -163,6 +163,23 @@
|
||||
- key: enum.StoreUiKey.Key
|
||||
type: StoreBoundUserInterface
|
||||
|
||||
- type: entity
|
||||
parent: BaseSubdermalImplant
|
||||
id: ChangelingShopImplant
|
||||
name: changeling shop implant
|
||||
description: Opens evolution shop.
|
||||
noSpawn: true
|
||||
components:
|
||||
- type: SubdermalImplant
|
||||
permanent: true
|
||||
implantAction: ActionChangelingShop
|
||||
- type: Store
|
||||
preset: StorePresetChangeling
|
||||
- type: UserInterface
|
||||
interfaces:
|
||||
- key: enum.StoreUiKey.Key
|
||||
type: StoreBoundUserInterface
|
||||
|
||||
- type: entity
|
||||
parent: BaseSubdermalImplant
|
||||
id: EmpImplant
|
||||
@@ -177,7 +194,7 @@
|
||||
range: 1.75
|
||||
energyConsumption: 50000
|
||||
disableDuration: 10
|
||||
|
||||
|
||||
- type: entity
|
||||
parent: BaseSubdermalImplant
|
||||
id: ScramImplant
|
||||
|
||||
@@ -118,6 +118,48 @@
|
||||
Blunt: 1.5
|
||||
Piercing: 1.5
|
||||
|
||||
#Changeling shield
|
||||
- type: entity
|
||||
name: organic shield
|
||||
parent: BaseShield
|
||||
id: OrganicShield
|
||||
description: A large, fleshy shield.
|
||||
components:
|
||||
- type: Sprite
|
||||
state: changeling-icon
|
||||
- type: Item
|
||||
heldPrefix: changeling
|
||||
- type: Blocking
|
||||
passiveBlockModifier:
|
||||
coefficients:
|
||||
Blunt: 0.8
|
||||
Piercing: 0.8
|
||||
activeBlockModifier:
|
||||
coefficients:
|
||||
Blunt: 0.7
|
||||
Piercing: 0.7
|
||||
flatReductions:
|
||||
Blunt: 1.5
|
||||
Piercing: 1.5
|
||||
- type: Unremoveable
|
||||
deleteOnDrop: true
|
||||
- type: Destructible
|
||||
thresholds:
|
||||
- trigger:
|
||||
!type:DamageTrigger
|
||||
damage: 80
|
||||
behaviors:
|
||||
- !type:DoActsBehavior
|
||||
acts: [ "Destruction" ]
|
||||
- trigger:
|
||||
!type:DamageTrigger
|
||||
damage: 50
|
||||
behaviors:
|
||||
- !type:DoActsBehavior
|
||||
acts: [ "Destruction" ]
|
||||
- !type:PlaySoundBehavior
|
||||
sound: /Audio/Effects/gib2.ogg
|
||||
|
||||
#Craftable shields
|
||||
|
||||
- type: entity
|
||||
|
||||
@@ -261,6 +261,43 @@
|
||||
True: { state: base-unshaded }
|
||||
False: { state: base-unshaded-off }
|
||||
|
||||
- type: entity
|
||||
name: Tentacle Arm
|
||||
parent: BaseItem
|
||||
id: TentacleArmGun
|
||||
components:
|
||||
- type: TentacleGun
|
||||
- type: Gun
|
||||
soundGunshot: /Audio/Effects/gib1.ogg
|
||||
fireRate: 0.3
|
||||
selectedMode: PullItem
|
||||
availableModes:
|
||||
- PullItem
|
||||
- PullMob
|
||||
- type: BasicEntityAmmoProvider
|
||||
proto: TentacleProjectile
|
||||
capacity: 1
|
||||
count: 1
|
||||
- type: RechargeBasicEntityAmmo
|
||||
rechargeCooldown: 0.75
|
||||
playRechargeSound: false
|
||||
- type: Sprite
|
||||
sprite: Objects/Weapons/Guns/Launchers/tentacle_gun.rsi
|
||||
layers:
|
||||
- state: base
|
||||
- type: UseDelay
|
||||
delay: 1.5
|
||||
- type: Appearance
|
||||
- type: EmitSoundOnCollide
|
||||
sound:
|
||||
path: /Audio/Effects/gib3.ogg
|
||||
params:
|
||||
variation: 0.65
|
||||
volume: -10
|
||||
- type: Unremoveable
|
||||
deleteOnDrop: true
|
||||
|
||||
|
||||
# Admeme
|
||||
- type: entity
|
||||
name: tether gun
|
||||
|
||||
@@ -883,6 +883,45 @@
|
||||
- HighImpassable
|
||||
- type: GrapplingProjectile
|
||||
|
||||
- type: entity
|
||||
id: TentacleProjectile
|
||||
name: tentacle
|
||||
noSpawn: true
|
||||
components:
|
||||
- type: EmbeddableProjectile
|
||||
deleteOnRemove: true
|
||||
- type: Clickable
|
||||
- type: InteractionOutline
|
||||
- type: Ammo
|
||||
muzzleFlash: null
|
||||
- type: Sprite
|
||||
noRot: false
|
||||
sprite: Objects/Weapons/Guns/Launchers/tentacle_gun.rsi
|
||||
layers:
|
||||
- state: hook
|
||||
- type: Physics
|
||||
bodyType: Dynamic
|
||||
linearDamping: 0
|
||||
angularDamping: 0
|
||||
- type: Projectile
|
||||
deleteOnCollide: false
|
||||
damage:
|
||||
types:
|
||||
Blunt: 0
|
||||
soundHit:
|
||||
path: /Audio/Effects/gib3.ogg
|
||||
- type: Fixtures
|
||||
fixtures:
|
||||
projectile:
|
||||
shape:
|
||||
!type:PhysShapeAabb
|
||||
bounds: "-0.1,-0.1,0.1,0.1"
|
||||
hard: false
|
||||
mask:
|
||||
- Impassable
|
||||
- BulletImpassable
|
||||
- type: TentacleProjectile
|
||||
|
||||
- type: entity
|
||||
name : disabler bolt smg
|
||||
id: BulletDisablerSmg
|
||||
|
||||
@@ -22,3 +22,6 @@
|
||||
qualities:
|
||||
- Prying
|
||||
- type: Prying
|
||||
- type: Unremoveable
|
||||
deleteOnDrop: true
|
||||
- type: ToolForcePowered
|
||||
|
||||
@@ -79,6 +79,15 @@
|
||||
- type: ThiefRule #the thieves come as an extension of another gamemode
|
||||
ruleChance: 0.5
|
||||
|
||||
- type: entity
|
||||
id: Changeling
|
||||
parent: BaseGameRule
|
||||
noSpawn: true
|
||||
components:
|
||||
- type: ChangelingRule
|
||||
# - type: TraitorRule
|
||||
# ruleChance: 0.6
|
||||
|
||||
- type: entity
|
||||
id: Revolutionary
|
||||
parent: BaseGameRule
|
||||
|
||||
223
Resources/Prototypes/Objectives/changeling.yml
Normal file
223
Resources/Prototypes/Objectives/changeling.yml
Normal file
@@ -0,0 +1,223 @@
|
||||
- type: entity
|
||||
abstract: true
|
||||
parent: BaseObjective
|
||||
id: BaseChangelingObjective
|
||||
components:
|
||||
- type: Objective
|
||||
difficulty: 1.5
|
||||
issuer: syndicate
|
||||
- type: RoleRequirement
|
||||
roles:
|
||||
components:
|
||||
- ChangelingRole
|
||||
|
||||
- type: entity
|
||||
abstract: true
|
||||
parent: [BaseChangelingObjective, BaseStealObjective]
|
||||
id: BaseChangelingStealObjective
|
||||
components:
|
||||
- type: StealCondition
|
||||
verifyMapExistance: false
|
||||
- type: Objective
|
||||
difficulty: 2.75
|
||||
- type: ObjectiveLimit
|
||||
limit: 1
|
||||
|
||||
- type: entity
|
||||
noSpawn: true
|
||||
parent: BaseChangelingObjective
|
||||
id: AbsorbDnaObjective
|
||||
components:
|
||||
- type: Objective
|
||||
icon:
|
||||
sprite: /Textures/Actions/changeling.rsi # TODO: Change icon
|
||||
state: absorb
|
||||
- type: AbsorbDnaCondition
|
||||
|
||||
- type: entity
|
||||
noSpawn: true
|
||||
parent: BaseChangelingObjective
|
||||
id: AbsorbMoreDnaObjective
|
||||
name: Absorb more humans, that others in the Hive.
|
||||
description: Absorb as many you can.
|
||||
components:
|
||||
- type: Objective
|
||||
difficulty: 2.0
|
||||
icon:
|
||||
sprite: /Textures/Actions/changeling.rsi # TODO: Change icon
|
||||
state: absorb
|
||||
- type: AbsorbMoreCondition
|
||||
|
||||
- type: entity
|
||||
noSpawn: true
|
||||
parent: BaseChangelingObjective
|
||||
id: AbsorbChangelingObjective
|
||||
components:
|
||||
- type: Objective
|
||||
difficulty: 3.0
|
||||
icon:
|
||||
sprite: /Textures/Actions/changeling.rsi # TODO: Change icon
|
||||
state: blade
|
||||
- type: TargetObjective
|
||||
title: objective-condition-absorb-changeling-title
|
||||
- type: PickRandomChangeling
|
||||
- type: AbsorbChangelingCondition
|
||||
|
||||
- type: entity
|
||||
noSpawn: true
|
||||
parent: BaseChangelingObjective
|
||||
id: EscapeWithIdentityObjective
|
||||
components:
|
||||
- type: Objective
|
||||
icon:
|
||||
sprite: /Textures/Mobs/Species/Human/parts.rsi
|
||||
state: full
|
||||
- type: TargetObjective
|
||||
title: objective-condition-escape-with-identity-title
|
||||
- type: PickRandomIdentity
|
||||
- type: EscapeWithIdentityCondition
|
||||
|
||||
# steal
|
||||
|
||||
## cmo
|
||||
|
||||
- type: entity
|
||||
noSpawn: true
|
||||
parent: BaseChangelingStealObjective
|
||||
id: CMOHyposprayStealObjectiveCh
|
||||
components:
|
||||
- type: NotJobRequirement
|
||||
job: ChiefMedicalOfficer
|
||||
- type: StealCondition
|
||||
stealGroup: Hypospray
|
||||
owner: job-name-cmo
|
||||
|
||||
## rd
|
||||
|
||||
- type: entity
|
||||
noSpawn: true
|
||||
parent: BaseChangelingStealObjective
|
||||
id: RDHardsuitStealObjectiveCh
|
||||
components:
|
||||
- type: StealCondition
|
||||
stealGroup: ClothingOuterHardsuitRd
|
||||
owner: job-name-rd
|
||||
|
||||
- type: entity
|
||||
noSpawn: true
|
||||
parent: BaseChangelingStealObjective
|
||||
id: HandTeleporterStealObjectiveCh
|
||||
components:
|
||||
- type: StealCondition
|
||||
stealGroup: HandTeleporter
|
||||
owner: job-name-rd
|
||||
|
||||
## hos
|
||||
|
||||
- type: entity
|
||||
noSpawn: true
|
||||
parent: BaseChangelingStealObjective
|
||||
id: SecretDocumentsStealObjectiveCh
|
||||
components:
|
||||
- type: Objective
|
||||
# hos has a gun ce does not, higher difficulty than most
|
||||
difficulty: 3
|
||||
- type: NotJobRequirement
|
||||
job: HeadOfSecurity
|
||||
- type: StealCondition
|
||||
stealGroup: BookSecretDocuments
|
||||
owner: job-name-hos
|
||||
|
||||
## ce
|
||||
|
||||
- type: entity
|
||||
noSpawn: true
|
||||
parent: BaseChangelingStealObjective
|
||||
id: MagbootsStealObjectiveCh
|
||||
components:
|
||||
- type: NotJobRequirement
|
||||
job: ChiefEngineer
|
||||
- type: StealCondition
|
||||
stealGroup: ClothingShoesBootsMagAdv
|
||||
owner: job-name-ce
|
||||
|
||||
## qm
|
||||
|
||||
- type: entity
|
||||
noSpawn: true
|
||||
parent: BaseChangelingStealObjective
|
||||
id: ClipboardStealObjectiveCh
|
||||
components:
|
||||
- type: NotJobRequirement
|
||||
job: Quartermaster
|
||||
- type: StealCondition
|
||||
stealGroup: BoxFolderQmClipboard
|
||||
owner: job-name-qm
|
||||
|
||||
## hop
|
||||
|
||||
- type: entity
|
||||
noSpawn: true
|
||||
parent: BaseChangelingStealObjective
|
||||
id: CorgiMeatStealObjectiveCh
|
||||
components:
|
||||
- type: NotJobRequirement
|
||||
job: HeadOfPersonnel
|
||||
- type: ObjectiveLimit
|
||||
limit: 3 # ian only has 2 slices, 3 obj for drama
|
||||
- type: StealCondition
|
||||
stealGroup: FoodMeatCorgi
|
||||
owner: objective-condition-steal-Ian
|
||||
|
||||
## cap
|
||||
|
||||
- type: entity
|
||||
abstract: true
|
||||
parent: BaseChangelingStealObjective
|
||||
id: BaseCaptainObjectiveCh
|
||||
components:
|
||||
- type: Objective
|
||||
# sorry ce but your jordans are not as high security as the caps gear
|
||||
difficulty: 3.5
|
||||
- type: NotJobRequirement
|
||||
job: Captain
|
||||
|
||||
- type: entity
|
||||
noSpawn: true
|
||||
parent: BaseCaptainObjectiveCh
|
||||
id: CaptainIDStealObjectiveCh
|
||||
components:
|
||||
- type: StealCondition
|
||||
stealGroup: CaptainIDCard
|
||||
|
||||
- type: entity
|
||||
noSpawn: true
|
||||
parent: BaseCaptainObjectiveCh
|
||||
id: CaptainJetpackStealObjectiveCh
|
||||
components:
|
||||
- type: StealCondition
|
||||
stealGroup: JetpackCaptainFilled
|
||||
|
||||
- type: entity
|
||||
noSpawn: true
|
||||
parent: BaseCaptainObjectiveCh
|
||||
id: CaptainGunStealObjectiveCh
|
||||
components:
|
||||
- type: StealCondition
|
||||
stealGroup: WeaponAntiqueLaser
|
||||
owner: job-name-captain
|
||||
|
||||
- type: entity
|
||||
noSpawn: true
|
||||
parent: BaseCaptainObjectiveCh
|
||||
id: NukeDiskStealObjectiveCh
|
||||
components:
|
||||
- type: Objective
|
||||
# high difficulty since the hardest item both to steal, and to not get caught down the road,
|
||||
# since anyone with a pinpointer can track you down and kill you
|
||||
# it's close to being a stealth loneop
|
||||
difficulty: 4
|
||||
- type: NotCommandRequirement
|
||||
- type: StealCondition
|
||||
stealGroup: NukeDisk
|
||||
owner: objective-condition-steal-station
|
||||
@@ -126,4 +126,33 @@
|
||||
id: ThiefObjectiveGroupEscape
|
||||
weights:
|
||||
EscapeThiefShuttleObjective: 1
|
||||
#Changeling, crew, wizard, when you code it...
|
||||
|
||||
# Changeling
|
||||
- type: weightedRandom
|
||||
id: ChangelingObjectiveGroups
|
||||
weights:
|
||||
ChangelingObjectiveGroupSteal: 1
|
||||
ChangelingObjectiveGroupKill: 1
|
||||
|
||||
- type: weightedRandom
|
||||
id: ChangelingObjectiveGroupSteal
|
||||
weights:
|
||||
CaptainIDStealObjectiveCh: 0.5
|
||||
CMOHyposprayStealObjectiveCh: 0.5
|
||||
RDHardsuitStealObjectiveCh: 0.5
|
||||
NukeDiskStealObjectiveCh: 0.5
|
||||
MagbootsStealObjectiveCh: 0.5
|
||||
CorgiMeatStealObjectiveCh: 0.5
|
||||
ClipboardStealObjectiveCh: 0.5
|
||||
CaptainGunStealObjectiveCh: 0.5
|
||||
CaptainJetpackStealObjectiveCh: 0.5
|
||||
HandTeleporterStealObjectiveCh: 0.5
|
||||
SecretDocumentsStealObjectiveCh: 0.5
|
||||
|
||||
- type: weightedRandom
|
||||
id: ChangelingObjectiveGroupKill
|
||||
weights:
|
||||
AbsorbDnaObjective: 1
|
||||
AbsorbMoreDnaObjective: 0.75
|
||||
AbsorbChangelingObjective: 0.25
|
||||
EscapeWithIdentityObjective: 0.75
|
||||
|
||||
@@ -148,3 +148,93 @@
|
||||
revertOnDeath: true
|
||||
revertOnCrit: true
|
||||
duration: 20
|
||||
|
||||
- type: polymorph
|
||||
id: MonkeyChangeling
|
||||
entity: MobMonkey
|
||||
forced: true
|
||||
inventory: Drop
|
||||
revertOnCrit: false
|
||||
revertOnDeath: false
|
||||
transferDamage: true
|
||||
allowRepeatedMorphs: true
|
||||
|
||||
- type: polymorph
|
||||
id: MobHuman
|
||||
entity: MobHuman
|
||||
forced: true
|
||||
revertOnCrit: false
|
||||
revertOnDeath: false
|
||||
transferDamage: true
|
||||
allowRepeatedMorphs: true
|
||||
inventory: Transfer
|
||||
|
||||
- type: polymorph
|
||||
id: MobArachnid
|
||||
entity: MobArachnid
|
||||
forced: true
|
||||
revertOnCrit: false
|
||||
revertOnDeath: false
|
||||
transferDamage: true
|
||||
allowRepeatedMorphs: true
|
||||
inventory: Transfer
|
||||
|
||||
- type: polymorph
|
||||
id: MobDwarf
|
||||
entity: MobDwarf
|
||||
forced: true
|
||||
revertOnCrit: false
|
||||
revertOnDeath: false
|
||||
transferDamage: true
|
||||
allowRepeatedMorphs: true
|
||||
inventory: Transfer
|
||||
|
||||
- type: polymorph
|
||||
id: MobDiona
|
||||
entity: MobDiona
|
||||
forced: true
|
||||
revertOnCrit: false
|
||||
revertOnDeath: false
|
||||
transferDamage: true
|
||||
allowRepeatedMorphs: true
|
||||
inventory: Transfer
|
||||
|
||||
- type: polymorph
|
||||
id: MobMoth
|
||||
entity: MobMoth
|
||||
forced: true
|
||||
revertOnCrit: false
|
||||
revertOnDeath: false
|
||||
transferDamage: true
|
||||
allowRepeatedMorphs: true
|
||||
inventory: Transfer
|
||||
|
||||
- type: polymorph
|
||||
id: MobReptilian
|
||||
entity: MobReptilian
|
||||
forced: true
|
||||
revertOnCrit: false
|
||||
revertOnDeath: false
|
||||
transferDamage: true
|
||||
allowRepeatedMorphs: true
|
||||
inventory: Transfer
|
||||
|
||||
- type: polymorph
|
||||
id: MobVox
|
||||
entity: MobVox
|
||||
forced: true
|
||||
revertOnCrit: false
|
||||
revertOnDeath: false
|
||||
transferDamage: true
|
||||
allowRepeatedMorphs: true
|
||||
inventory: Transfer
|
||||
|
||||
- type: polymorph
|
||||
id: MobSlimePerson
|
||||
entity: MobSlimePerson
|
||||
forced: true
|
||||
revertOnCrit: false
|
||||
revertOnDeath: false
|
||||
transferDamage: true
|
||||
allowRepeatedMorphs: true
|
||||
inventory: Transfer
|
||||
|
||||
6
Resources/Prototypes/Roles/Antags/changeling.yml
Normal file
6
Resources/Prototypes/Roles/Antags/changeling.yml
Normal file
@@ -0,0 +1,6 @@
|
||||
- type: antag
|
||||
id: Changeling
|
||||
name: roles-antag-changeling-name
|
||||
antagonist: true
|
||||
setPreference: true
|
||||
objective: roles-antag-changeling-objective
|
||||
@@ -68,3 +68,18 @@
|
||||
id: RevenantAbilities
|
||||
name: store-category-abilities
|
||||
|
||||
# changeling
|
||||
- type: storeCategory
|
||||
id: ChangelingStings
|
||||
name: store-category-stings
|
||||
priority: 0
|
||||
|
||||
- type: storeCategory
|
||||
id: ChangelingAbilities
|
||||
name: store-category-abilities
|
||||
priority: 1
|
||||
|
||||
- type: storeCategory
|
||||
id: ChangelingBoosters
|
||||
name: store-category-boosters
|
||||
priority: 2
|
||||
|
||||
@@ -10,6 +10,11 @@
|
||||
displayName: store-currency-display-stolen-essence
|
||||
canWithdraw: false
|
||||
|
||||
- type: currency
|
||||
id: ChangelingPoint
|
||||
displayName: store-currency-display-changeling-point
|
||||
canWithdraw: false
|
||||
|
||||
- type: currency
|
||||
id: MeatyOreCoin
|
||||
displayName: Meaty Ore Coin
|
||||
|
||||
@@ -23,3 +23,13 @@
|
||||
minItems: 3
|
||||
maxItems: 10
|
||||
salesCategory: UplinkSales
|
||||
|
||||
- type: storePreset
|
||||
id: StorePresetChangeling
|
||||
storeName: Evolution Shop
|
||||
categories:
|
||||
- ChangelingAbilities
|
||||
- ChangelingStings
|
||||
- ChangelingBoosters
|
||||
currencyWhitelist:
|
||||
- ChangelingPoint
|
||||
|
||||
@@ -61,6 +61,17 @@
|
||||
- Traitor
|
||||
- BasicStationEventScheduler
|
||||
|
||||
- type: gamePreset
|
||||
id: Changeling
|
||||
alias:
|
||||
- changeling
|
||||
name: changeling-title
|
||||
description: changeling-description
|
||||
showInVote: false
|
||||
rules:
|
||||
- Changeling
|
||||
- BasicStationEventScheduler
|
||||
|
||||
- type: gamePreset
|
||||
id: Deathmatch
|
||||
alias:
|
||||
|
||||
@@ -7,4 +7,4 @@
|
||||
Revolutionary: 0.10
|
||||
Zombie: 0.05
|
||||
Survival: 0.05
|
||||
|
||||
Changeling: 0.30
|
||||
|
||||
@@ -60,7 +60,10 @@
|
||||
- type: statusEffect
|
||||
id: StaminaModifier
|
||||
|
||||
- type: statusEffect
|
||||
id: BlurryVision
|
||||
alwaysAllowed: true
|
||||
|
||||
#WD EDIT
|
||||
- type: statusEffect
|
||||
id: Incorporeal
|
||||
|
||||
|
||||
BIN
Resources/Textures/Actions/changeling.rsi/absorb.png
Normal file
BIN
Resources/Textures/Actions/changeling.rsi/absorb.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 231 B |
BIN
Resources/Textures/Actions/changeling.rsi/blade.png
Normal file
BIN
Resources/Textures/Actions/changeling.rsi/blade.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 753 B |
17
Resources/Textures/Actions/changeling.rsi/meta.json
Normal file
17
Resources/Textures/Actions/changeling.rsi/meta.json
Normal file
@@ -0,0 +1,17 @@
|
||||
{
|
||||
"version": 1,
|
||||
"license": null,
|
||||
"copyright": null,
|
||||
"size": {
|
||||
"x": 32,
|
||||
"y": 32
|
||||
},
|
||||
"states": [
|
||||
{
|
||||
"name": "blade"
|
||||
},
|
||||
{
|
||||
"name": "absorb"
|
||||
}
|
||||
]
|
||||
}
|
||||
Binary file not shown.
|
After Width: | Height: | Size: 2.6 KiB |
Binary file not shown.
|
After Width: | Height: | Size: 1.0 KiB |
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user