Port Cortical Borers (real) (#4466)
* ITS ALL C# GOOD LORD BORN TO PORT WORLD IS A PR 鬼神 Merge Em All 2025 I am yaml man 410,757,864,530 UPSTREAM MERGE CONFLICTS * that was, uh. too easy. * very small tweak * comment and reuse fixes * forgot the guidebook Oops * the guidebookening * the reusening
|
|
@ -0,0 +1,90 @@
|
|||
using System.Numerics;
|
||||
using Content.Shared.Alert.Components;
|
||||
using Robust.Client.GameObjects;
|
||||
using Robust.Client.Graphics;
|
||||
|
||||
namespace Content.Client.Alerts;
|
||||
|
||||
/// <summary>
|
||||
/// This handles <see cref="GenericCounterAlertComponent"/>
|
||||
/// </summary>
|
||||
public sealed class GenericCounterAlertSystem : EntitySystem
|
||||
{
|
||||
[Dependency] private readonly SpriteSystem _sprite = default!;
|
||||
|
||||
/// <inheritdoc/>
|
||||
public override void Initialize()
|
||||
{
|
||||
SubscribeLocalEvent<GenericCounterAlertComponent, UpdateAlertSpriteEvent>(OnUpdateAlertSprite);
|
||||
}
|
||||
|
||||
private void OnUpdateAlertSprite(Entity<GenericCounterAlertComponent> ent, ref UpdateAlertSpriteEvent args)
|
||||
{
|
||||
var sprite = args.SpriteViewEnt.Comp;
|
||||
|
||||
var ev = new GetGenericAlertCounterAmountEvent(args.Alert);
|
||||
RaiseLocalEvent(args.ViewerEnt, ref ev);
|
||||
|
||||
if (!ev.Handled)
|
||||
return;
|
||||
|
||||
// It cannot be null if its handled, but good to check to avoid ugly null ignores.
|
||||
if (ev.Amount == null)
|
||||
return;
|
||||
|
||||
// How many digits can we display
|
||||
var maxDigitCount = GetMaxDigitCount((ent, ent, sprite));
|
||||
|
||||
// Clamp it to a positive number that we can actually display in full (no rollover to 0)
|
||||
var amount = (int) Math.Clamp(ev.Amount.Value, 0, Math.Pow(10, maxDigitCount) - 1);
|
||||
|
||||
// This is super wack but ig it works?
|
||||
var digitCount = ent.Comp.HideLeadingZeroes
|
||||
? amount.ToString().Length
|
||||
: maxDigitCount;
|
||||
|
||||
if (ent.Comp.HideLeadingZeroes)
|
||||
{
|
||||
for (var i = 0; i < ent.Comp.DigitKeys.Count; i++)
|
||||
{
|
||||
if (!_sprite.LayerMapTryGet(ent.Owner, ent.Comp.DigitKeys[i], out var layer, false))
|
||||
continue;
|
||||
|
||||
_sprite.LayerSetVisible(ent.Owner, layer, i <= digitCount - 1);
|
||||
}
|
||||
}
|
||||
|
||||
// ReSharper disable once PossibleLossOfFraction
|
||||
var baseOffset = (ent.Comp.AlertSize.X - digitCount * ent.Comp.GlyphWidth) / 2 * (1f / EyeManager.PixelsPerMeter);
|
||||
|
||||
for (var i = 0; i < ent.Comp.DigitKeys.Count; i++)
|
||||
{
|
||||
if (!_sprite.LayerMapTryGet(ent.Owner, ent.Comp.DigitKeys[i], out var layer, false))
|
||||
continue;
|
||||
|
||||
var result = amount / (int) Math.Pow(10, i) % 10;
|
||||
_sprite.LayerSetRsiState(ent.Owner, layer, result.ToString());
|
||||
|
||||
if (ent.Comp.CenterGlyph)
|
||||
{
|
||||
var offset = baseOffset + (digitCount - 1 - i) * ent.Comp.GlyphWidth * (1f / EyeManager.PixelsPerMeter);
|
||||
_sprite.LayerSetOffset(ent.Owner, layer, new Vector2(offset, 0));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets the number of digits that we can display.
|
||||
/// </summary>
|
||||
/// <returns>The number of digits.</returns>
|
||||
private int GetMaxDigitCount(Entity<GenericCounterAlertComponent, SpriteComponent> ent)
|
||||
{
|
||||
for (var i = ent.Comp1.DigitKeys.Count - 1; i >= 0; i--)
|
||||
{
|
||||
if (_sprite.LayerExists((ent.Owner, ent.Comp2), ent.Comp1.DigitKeys[i]))
|
||||
return i + 1;
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
|
|
@ -11,11 +11,14 @@ public record struct UpdateAlertSpriteEvent
|
|||
{
|
||||
public Entity<SpriteComponent> SpriteViewEnt;
|
||||
|
||||
public EntityUid ViewerEnt;
|
||||
|
||||
public AlertPrototype Alert;
|
||||
|
||||
public UpdateAlertSpriteEvent(Entity<SpriteComponent> spriteViewEnt, AlertPrototype alert)
|
||||
public UpdateAlertSpriteEvent(Entity<SpriteComponent> spriteViewEnt, EntityUid viewerEnt, AlertPrototype alert)
|
||||
{
|
||||
SpriteViewEnt = spriteViewEnt;
|
||||
ViewerEnt = viewerEnt;
|
||||
Alert = alert;
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,4 +1,6 @@
|
|||
using Content.Client.Alerts;
|
||||
using Content.Shared.Alert;
|
||||
using Content.Shared.Alert.Components;
|
||||
using Content.Shared.Revenant;
|
||||
using Content.Shared.Revenant.Components;
|
||||
using Robust.Client.GameObjects;
|
||||
|
|
@ -15,7 +17,7 @@ public sealed class RevenantSystem : EntitySystem
|
|||
base.Initialize();
|
||||
|
||||
SubscribeLocalEvent<RevenantComponent, AppearanceChangeEvent>(OnAppearanceChange);
|
||||
SubscribeLocalEvent<RevenantComponent, UpdateAlertSpriteEvent>(OnUpdateAlert);
|
||||
SubscribeLocalEvent<RevenantComponent, GetGenericAlertCounterAmountEvent>(OnGetCounterAmount);
|
||||
}
|
||||
|
||||
private void OnAppearanceChange(EntityUid uid, RevenantComponent component, ref AppearanceChangeEvent args)
|
||||
|
|
@ -40,14 +42,14 @@ public sealed class RevenantSystem : EntitySystem
|
|||
}
|
||||
}
|
||||
|
||||
private void OnUpdateAlert(Entity<RevenantComponent> ent, ref UpdateAlertSpriteEvent args)
|
||||
private void OnGetCounterAmount(Entity<RevenantComponent> ent, ref GetGenericAlertCounterAmountEvent args)
|
||||
{
|
||||
if (args.Alert.ID != ent.Comp.EssenceAlert)
|
||||
if (args.Handled)
|
||||
return;
|
||||
|
||||
var essence = Math.Clamp(ent.Comp.Essence.Int(), 0, 999);
|
||||
_sprite.LayerSetRsiState(args.SpriteViewEnt.AsNullable(), RevenantVisualLayers.Digit1, $"{(essence / 100) % 10}");
|
||||
_sprite.LayerSetRsiState(args.SpriteViewEnt.AsNullable(), RevenantVisualLayers.Digit2, $"{(essence / 10) % 10}");
|
||||
_sprite.LayerSetRsiState(args.SpriteViewEnt.AsNullable(), RevenantVisualLayers.Digit3, $"{essence % 10}");
|
||||
if (ent.Comp.EssenceAlert != args.Alert)
|
||||
return;
|
||||
|
||||
args.Amount = ent.Comp.Essence.Int();
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -98,7 +98,8 @@ public sealed class AlertsUIController : UIController, IOnStateEntered<GameplayS
|
|||
if (!EntityManager.TryGetComponent<SpriteComponent>(spriteViewEnt, out var sprite))
|
||||
return;
|
||||
|
||||
var ev = new UpdateAlertSpriteEvent((spriteViewEnt, sprite), alert);
|
||||
var ev = new UpdateAlertSpriteEvent((spriteViewEnt, sprite), player, alert);
|
||||
EntityManager.EventBus.RaiseLocalEvent(player, ref ev);
|
||||
EntityManager.EventBus.RaiseLocalEvent(spriteViewEnt, ref ev);
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -57,10 +57,15 @@ namespace Content.Client.UserInterface.Systems.Alerts.Controls
|
|||
_sprite = _entityManager.System<SpriteSystem>();
|
||||
TooltipSupplier = SupplyTooltip;
|
||||
Alert = alert;
|
||||
|
||||
HorizontalAlignment = HAlignment.Left;
|
||||
_severity = severity;
|
||||
_icon = new SpriteView
|
||||
{
|
||||
Scale = new Vector2(2, 2)
|
||||
Scale = new Vector2(2, 2),
|
||||
MaxSize = new Vector2(64, 64),
|
||||
Stretch = SpriteView.StretchMode.None,
|
||||
HorizontalAlignment = HAlignment.Left
|
||||
};
|
||||
|
||||
SetupIcon();
|
||||
|
|
|
|||
|
|
@ -0,0 +1,50 @@
|
|||
// SPDX-FileCopyrightText: 2025 Coenx-flex
|
||||
//
|
||||
// SPDX-License-Identifier: AGPL-3.0-or-later
|
||||
|
||||
using Content.Client.UserInterface.Controls;
|
||||
using Content.Shared._Mono.CorticalBorer;
|
||||
using JetBrains.Annotations;
|
||||
using Robust.Client.UserInterface;
|
||||
|
||||
namespace Content.Client._Mono.CorticalBorer
|
||||
{
|
||||
[UsedImplicitly]
|
||||
public sealed class CorticalBorerDispenserBoundUserInterface : BoundUserInterface
|
||||
{
|
||||
private CorticalBorerDispenserWindow? _window;
|
||||
|
||||
public CorticalBorerDispenserBoundUserInterface(EntityUid owner, Enum uiKey) : base(owner, uiKey)
|
||||
{
|
||||
|
||||
}
|
||||
|
||||
protected override void Open()
|
||||
{
|
||||
base.Open();
|
||||
|
||||
_window = this.CreateWindow<CorticalBorerDispenserWindow>();
|
||||
_window.SetInfoFromEntity(EntMan, Owner);
|
||||
|
||||
// Setup static button actions.
|
||||
_window.AmountGrid.OnButtonPressed += s => SendMessage(new CorticalBorerDispenserSetInjectAmountMessage(s));
|
||||
|
||||
_window.OnDispenseReagentButtonPressed += id => SendMessage(new CorticalBorerDispenserInjectMessage(id));
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Update the UI each time new state data is sent from the server.
|
||||
/// </summary>
|
||||
/// <param name="state">
|
||||
/// Data of the <see cref="ReagentDispenserComponent"/> that this UI represents.
|
||||
/// Sent from the server.
|
||||
/// </param>
|
||||
protected override void UpdateState(BoundUserInterfaceState state)
|
||||
{
|
||||
base.UpdateState(state);
|
||||
|
||||
var castState = (CorticalBorerDispenserBoundUserInterfaceState) state;
|
||||
_window?.UpdateState(castState);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,43 @@
|
|||
<controls:FancyWindow
|
||||
xmlns="https://spacestation14.io"
|
||||
xmlns:gfx="clr-namespace:Robust.Client.Graphics;assembly=Robust.Client"
|
||||
xmlns:controls="clr-namespace:Content.Client.UserInterface.Controls"
|
||||
xmlns:ui="clr-namespace:Content.Client.Chemistry.UI"
|
||||
Title="{Loc 'reagent-dispenser-bound-user-interface-title'}"
|
||||
MinSize="400 175"
|
||||
SetSize="600 300">
|
||||
<BoxContainer Orientation="Horizontal">
|
||||
<BoxContainer Orientation="Vertical" MinWidth="170">
|
||||
<Label Text="{Loc 'reagent-dispenser-window-amount-to-dispense-label'}" HorizontalAlignment="Center" />
|
||||
<ui:ButtonGrid
|
||||
Name="AmountGrid"
|
||||
Access="Public"
|
||||
Columns="3"
|
||||
HorizontalAlignment="Center"
|
||||
Margin="5"
|
||||
ButtonList="1,5,10,15,20,25,30,50,100"
|
||||
RadioGroup="True">
|
||||
</ui:ButtonGrid>
|
||||
</BoxContainer>
|
||||
<SplitContainer Orientation="Vertical"
|
||||
HorizontalExpand="True"
|
||||
VerticalExpand="True">
|
||||
<ScrollContainer HScrollEnabled="False"
|
||||
HorizontalExpand="True"
|
||||
VerticalExpand="True"
|
||||
MinHeight="50"
|
||||
SizeFlagsStretchRatio="2.5">
|
||||
<GridContainer Name="ReagentList"
|
||||
HorizontalExpand="True"
|
||||
VerticalExpand="True"
|
||||
Access="Public"
|
||||
Columns="3" />
|
||||
</ScrollContainer>
|
||||
<ScrollContainer HScrollEnabled="False"
|
||||
HorizontalExpand="True"
|
||||
VerticalExpand="True"
|
||||
MinHeight="50">
|
||||
</ScrollContainer>
|
||||
</SplitContainer>
|
||||
</BoxContainer>
|
||||
</controls:FancyWindow>
|
||||
|
|
@ -0,0 +1,51 @@
|
|||
// SPDX-FileCopyrightText: 2025 Coenx-flex
|
||||
//
|
||||
// SPDX-License-Identifier: AGPL-3.0-or-later
|
||||
|
||||
using Content.Client.UserInterface.Controls;
|
||||
using Content.Shared._Mono.CorticalBorer;
|
||||
using Robust.Client.AutoGenerated;
|
||||
using Robust.Client.UserInterface.XAML;
|
||||
|
||||
namespace Content.Client._Mono.CorticalBorer;
|
||||
|
||||
[GenerateTypedNameReferences]
|
||||
public sealed partial class CorticalBorerDispenserWindow : FancyWindow
|
||||
{
|
||||
|
||||
public event Action<string>? OnDispenseReagentButtonPressed;
|
||||
|
||||
public CorticalBorerDispenserWindow()
|
||||
{
|
||||
RobustXamlLoader.Load(this);
|
||||
}
|
||||
|
||||
public void UpdateState(BoundUserInterfaceState state)
|
||||
{
|
||||
var castState = (CorticalBorerDispenserBoundUserInterfaceState)state;
|
||||
|
||||
UpdateReagentsList(castState.DisList);
|
||||
|
||||
AmountGrid.Selected = castState.SelectedDispenseAmount.ToString();
|
||||
}
|
||||
|
||||
public void UpdateReagentsList(List<CorticalBorerDispenserItem> chemicals)
|
||||
{
|
||||
if (ReagentList == null)
|
||||
return;
|
||||
|
||||
ReagentList.Children.Clear();
|
||||
|
||||
chemicals.Sort((x, y) => x.ReagentName.CompareTo(y.ReagentName));
|
||||
|
||||
foreach (var chem in chemicals)
|
||||
{
|
||||
var card = new CorticalBorerReagentCardControl(chem);
|
||||
card.OnPressed += OnDispenseReagentButtonPressed;
|
||||
ReagentList.Children.Add(card);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
|
||||
|
|
@ -0,0 +1,33 @@
|
|||
// SPDX-FileCopyrightText: 2025 Coenx-flex
|
||||
//
|
||||
// SPDX-License-Identifier: AGPL-3.0-or-later
|
||||
|
||||
using Content.Shared._Mono.CorticalBorer;
|
||||
using Content.Shared.Chemistry;
|
||||
using Robust.Client.AutoGenerated;
|
||||
using Robust.Client.Graphics;
|
||||
using Robust.Client.UserInterface;
|
||||
using Robust.Client.UserInterface.XAML;
|
||||
|
||||
namespace Content.Client._Mono.CorticalBorer;
|
||||
|
||||
[GenerateTypedNameReferences]
|
||||
public sealed partial class CorticalBorerReagentCardControl : Control
|
||||
{
|
||||
public Action<string>? OnPressed;
|
||||
|
||||
public CorticalBorerReagentCardControl(CorticalBorerDispenserItem set)
|
||||
{
|
||||
RobustXamlLoader.Load(this);
|
||||
|
||||
ColorPanel.PanelOverride = new StyleBoxFlat{ BackgroundColor = set.ReagentColor};
|
||||
ReagentNameLabel.Text = set.ReagentName;
|
||||
FillLabel.Text = Loc.GetString("cortical-borer-dispenser-window-cost", ("cost", set.Cost * set.Amount));
|
||||
|
||||
// disable the button if you can't afford it, makes it easier
|
||||
MainButton.Disabled = set.Chems < set.Cost * set.Amount;
|
||||
|
||||
MainButton.OnPressed += args => OnPressed?.Invoke(set.ReagentId);
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -0,0 +1,27 @@
|
|||
<Control xmlns="https://spacestation14.io" HorizontalExpand="True">
|
||||
<BoxContainer Name="MainContainer"
|
||||
Orientation="Horizontal"
|
||||
HorizontalExpand="True">
|
||||
<PanelContainer Name="ColorPanel"
|
||||
VerticalExpand="True"
|
||||
SetWidth="7"
|
||||
Margin="0 1 0 0" />
|
||||
<Button Name="MainButton"
|
||||
HorizontalExpand="True"
|
||||
VerticalExpand="True"
|
||||
StyleClasses="ButtonSquare"
|
||||
Margin="-1 0 0 0">
|
||||
<BoxContainer Orientation="Horizontal" HorizontalExpand="True">
|
||||
<BoxContainer Orientation="Vertical"
|
||||
VerticalExpand="True"
|
||||
HorizontalExpand="True"
|
||||
Margin="-5 0 0 0">
|
||||
<Label Name="ReagentNameLabel" />
|
||||
<Label Name="FillLabel"
|
||||
StyleClasses="LabelSubText"
|
||||
Margin="0 -5 0 0" />
|
||||
</BoxContainer>
|
||||
</BoxContainer>
|
||||
</Button>
|
||||
</BoxContainer>
|
||||
</Control>
|
||||
|
|
@ -0,0 +1,31 @@
|
|||
// SPDX-FileCopyrightText: 2025 Coenx-flex
|
||||
//
|
||||
// SPDX-License-Identifier: AGPL-3.0-or-later
|
||||
|
||||
using Content.Shared._Mono.CorticalBorer;
|
||||
using Content.Shared.Alert.Components;
|
||||
|
||||
namespace Content.Client._Mono.CorticalBorer;
|
||||
|
||||
/// <inheritdoc/>
|
||||
public sealed class CorticalBorerSystem : SharedCorticalBorerSystem
|
||||
{
|
||||
/// <inheritdoc/>
|
||||
public override void Initialize()
|
||||
{
|
||||
base.Initialize();
|
||||
|
||||
SubscribeLocalEvent<CorticalBorerComponent, GetGenericAlertCounterAmountEvent>(OnGetCounterAmount);
|
||||
}
|
||||
|
||||
private void OnGetCounterAmount(Entity<CorticalBorerComponent> ent, ref GetGenericAlertCounterAmountEvent args)
|
||||
{
|
||||
if (args.Handled)
|
||||
return;
|
||||
|
||||
if (ent.Comp.ChemicalAlert != args.Alert)
|
||||
return;
|
||||
|
||||
args.Amount = ent.Comp.ChemicalPoints;
|
||||
}
|
||||
}
|
||||
|
|
@ -251,6 +251,17 @@ public sealed partial class ChatSystem : SharedChatSystem
|
|||
if (string.IsNullOrEmpty(message))
|
||||
return;
|
||||
|
||||
// Begin Mono Changes - Is this being sent direct
|
||||
var targetEv = new CheckTargetedSpeechEvent();
|
||||
RaiseLocalEvent(source, targetEv);
|
||||
|
||||
if (targetEv.Targets.Count > 0)
|
||||
{
|
||||
SendEntityDirect(source, message, range, nameOverride, targetEv.Targets);
|
||||
return;
|
||||
}
|
||||
// End Mono Changes - Is this being sent direct
|
||||
|
||||
// This message may have a radio prefix, and should then be whispered to the resolved radio channel
|
||||
if (checkRadioPrefix)
|
||||
{
|
||||
|
|
@ -591,6 +602,72 @@ public sealed partial class ChatSystem : SharedChatSystem
|
|||
}
|
||||
}
|
||||
|
||||
// Begin Mono Changes
|
||||
private void SendEntityDirect(
|
||||
EntityUid source,
|
||||
string originalMessage,
|
||||
ChatTransmitRange range,
|
||||
string? nameOverride,
|
||||
List<EntityUid> recipients,
|
||||
bool hideLog = false,
|
||||
bool ignoreActionBlocker = false)
|
||||
{
|
||||
var message = TransformSpeech(source, FormattedMessage.RemoveMarkupOrThrow(originalMessage));
|
||||
if (message.Length == 0)
|
||||
return;
|
||||
|
||||
string name;
|
||||
if (nameOverride != null)
|
||||
{
|
||||
name = nameOverride;
|
||||
}
|
||||
else
|
||||
{
|
||||
var nameEv = new TransformSpeakerNameEvent(source, Name(source));
|
||||
RaiseLocalEvent(source, nameEv);
|
||||
name = nameEv.VoiceName;
|
||||
}
|
||||
name = FormattedMessage.EscapeText(name);
|
||||
|
||||
var wrappedMessage = Loc.GetString("chat-manager-entity-whisper-wrap-message",
|
||||
("entityName", name), ("message", FormattedMessage.EscapeText(message)));
|
||||
|
||||
foreach (var (session, data) in GetRecipients(source, WhisperMuffledRange))
|
||||
{
|
||||
EntityUid listener;
|
||||
|
||||
if (session.AttachedEntity is not { Valid: true } playerEntity)
|
||||
continue;
|
||||
listener = session.AttachedEntity.Value;
|
||||
|
||||
if (MessageRangeCheck(session, data, range) != MessageRangeCheckResult.Full ||
|
||||
!recipients.Contains(listener) &&
|
||||
!HasComp<GhostComponent>(listener))
|
||||
continue;
|
||||
|
||||
_chatManager.ChatMessageToOne(ChatChannel.Local, message, wrappedMessage, source, false, session.Channel); // DeltaV - no collective mind chat channel, use local..?
|
||||
}
|
||||
|
||||
if (!hideLog)
|
||||
if (originalMessage == message)
|
||||
{
|
||||
if (name != Name(source))
|
||||
_adminLogger.Add(LogType.Chat, LogImpact.Low, $"Direct messaged from {ToPrettyString(source):user} as {name}: {originalMessage}.");
|
||||
else
|
||||
_adminLogger.Add(LogType.Chat, LogImpact.Low, $"Direct messaged from {ToPrettyString(source):user}: {originalMessage}.");
|
||||
}
|
||||
else
|
||||
{
|
||||
if (name != Name(source))
|
||||
_adminLogger.Add(LogType.Chat, LogImpact.Low,
|
||||
$"Direct messaged from {ToPrettyString(source):user} as {name}, original: {originalMessage}, transformed: {message}.");
|
||||
else
|
||||
_adminLogger.Add(LogType.Chat, LogImpact.Low,
|
||||
$"Direct messaged from {ToPrettyString(source):user}, original: {originalMessage}, transformed: {message}.");
|
||||
}
|
||||
}
|
||||
// End Mono Changes
|
||||
|
||||
private void SendEntityEmote(
|
||||
EntityUid source,
|
||||
string action,
|
||||
|
|
@ -959,6 +1036,13 @@ public sealed class CheckIgnoreSpeechBlockerEvent : EntityEventArgs
|
|||
}
|
||||
}
|
||||
|
||||
// Begin Mono Changes
|
||||
public sealed class CheckTargetedSpeechEvent : EntityEventArgs
|
||||
{
|
||||
public List<EntityUid> Targets = new List<EntityUid>();
|
||||
}
|
||||
// End Mono Changes
|
||||
|
||||
/// <summary>
|
||||
/// Raised on an entity when it speaks, either through 'say' or 'whisper'.
|
||||
/// </summary>
|
||||
|
|
|
|||
|
|
@ -186,7 +186,7 @@ public sealed class HealthAnalyzerSystem : EntitySystem
|
|||
/// <param name="healthAnalyzer">The health analyzer that should receive the updates</param>
|
||||
/// <param name="target">The entity to start analyzing</param>
|
||||
/// <param name="part">Shitmed Change: The body part to analyze, if any</param>
|
||||
private void BeginAnalyzingEntity(Entity<HealthAnalyzerComponent> healthAnalyzer, EntityUid target, EntityUid? part = null)
|
||||
public void BeginAnalyzingEntity(Entity<HealthAnalyzerComponent> healthAnalyzer, EntityUid target, EntityUid? part = null) // Mono - make public
|
||||
{
|
||||
//Link the health analyzer to the scanned entity
|
||||
healthAnalyzer.Comp.ScannedEntity = target;
|
||||
|
|
@ -202,7 +202,7 @@ public sealed class HealthAnalyzerSystem : EntitySystem
|
|||
/// </summary>
|
||||
/// <param name="healthAnalyzer">The health analyzer that's receiving the updates</param>
|
||||
/// <param name="target">The entity to analyze</param>
|
||||
private void StopAnalyzingEntity(Entity<HealthAnalyzerComponent> healthAnalyzer, EntityUid target)
|
||||
public void StopAnalyzingEntity(Entity<HealthAnalyzerComponent> healthAnalyzer, EntityUid target) // Mono - make public
|
||||
{
|
||||
//Unlink the analyzer
|
||||
healthAnalyzer.Comp.ScannedEntity = null;
|
||||
|
|
|
|||
|
|
@ -0,0 +1,86 @@
|
|||
// SPDX-FileCopyrightText: 2025 Coenx-flex
|
||||
// SPDX-FileCopyrightText: 2025 Cojoke
|
||||
//
|
||||
// SPDX-License-Identifier: AGPL-3.0-or-later
|
||||
|
||||
using Content.Shared._Mono.CorticalBorer;
|
||||
using Content.Shared._Shitmed.Body.Events;
|
||||
using Content.Shared.Body.Part;
|
||||
using Content.Shared.Examine;
|
||||
using Content.Shared.Mind.Components;
|
||||
using Content.Shared.Mobs;
|
||||
using Robust.Server.Containers;
|
||||
using Robust.Shared.Containers;
|
||||
using Robust.Shared.Timing;
|
||||
|
||||
namespace Content.Server._Mono.CorticalBorer;
|
||||
|
||||
public sealed class CorticalBorerInfestedSystem : EntitySystem
|
||||
{
|
||||
[Dependency] private readonly ContainerSystem _container = default!;
|
||||
[Dependency] private readonly IGameTiming _timing = default!;
|
||||
[Dependency] private readonly CorticalBorerSystem _borer = default!;
|
||||
|
||||
/// <inheritdoc/>
|
||||
public override void Initialize()
|
||||
{
|
||||
SubscribeLocalEvent<CorticalBorerInfestedComponent, MapInitEvent>(OnInit);
|
||||
SubscribeLocalEvent<CorticalBorerInfestedComponent, ExaminedEvent>(OnExaminedInfested);
|
||||
|
||||
SubscribeLocalEvent<CorticalBorerInfestedComponent, BodyPartRemovedEvent>(OnBodyPartRemoved);
|
||||
SubscribeLocalEvent<CorticalBorerInfestedComponent, MobStateChangedEvent>(OnStateChange);
|
||||
SubscribeLocalEvent<CorticalBorerInfestedComponent, MindRemovedMessage>(OnMindRemoved);
|
||||
}
|
||||
|
||||
private void OnInit(Entity<CorticalBorerInfestedComponent> infested, ref MapInitEvent args)
|
||||
{
|
||||
infested.Comp.ControlContainer = _container.EnsureContainer<Container>(infested, "ControlContainer");
|
||||
infested.Comp.InfestationContainer = _container.EnsureContainer<Container>(infested, "InfestationContainer");
|
||||
}
|
||||
|
||||
private void OnExaminedInfested(Entity<CorticalBorerInfestedComponent> infected, ref ExaminedEvent args)
|
||||
{
|
||||
if (!args.IsInDetailsRange
|
||||
|| args.Examined != args.Examiner)
|
||||
return;
|
||||
|
||||
if (!infected.Comp.Borer.Comp.ControlingHost)
|
||||
return;
|
||||
|
||||
if (infected.Comp.ControlTimeEnd is { } cte)
|
||||
{
|
||||
var timeRemaining = Math.Floor((cte - _timing.CurTime).TotalSeconds);
|
||||
args.PushMarkup(Loc.GetString("infested-control-examined", ("timeremaining", timeRemaining)));
|
||||
}
|
||||
|
||||
args.PushMarkup(Loc.GetString("cortical-borer-self-examine", ("chempoints", infected.Comp.Borer.Comp.ChemicalPoints)));
|
||||
}
|
||||
|
||||
private void OnStateChange(Entity<CorticalBorerInfestedComponent> infected, ref MobStateChangedEvent args)
|
||||
{
|
||||
if (args.NewMobState != MobState.Dead)
|
||||
return;
|
||||
|
||||
if(infected.Comp.Borer.Comp.ControlingHost)
|
||||
_borer.EndControl(infected.Comp.Borer);
|
||||
}
|
||||
|
||||
private void OnBodyPartRemoved(Entity<CorticalBorerInfestedComponent> infected, ref BodyPartRemovedEvent args)
|
||||
{
|
||||
if (TryComp<BodyPartComponent>(args.Part, out var part) &&
|
||||
part.PartType == BodyPartType.Head)
|
||||
{
|
||||
_borer.EndControl(infected.Comp.Borer);
|
||||
_borer.TryEjectBorer(infected.Comp.Borer);
|
||||
}
|
||||
}
|
||||
|
||||
private void OnMindRemoved(Entity<CorticalBorerInfestedComponent> infected, ref MindRemovedMessage args)
|
||||
{
|
||||
if (infected.Comp.Borer.Comp.ControlingHost)
|
||||
{
|
||||
_borer.EndControl(infected.Comp.Borer);
|
||||
_borer.TryEjectBorer(infected.Comp.Borer);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,224 @@
|
|||
// SPDX-FileCopyrightText: 2025 Coenx-flex
|
||||
// SPDX-FileCopyrightText: 2025 Cojoke
|
||||
// SPDX-FileCopyrightText: 2025 ark1368
|
||||
//
|
||||
// SPDX-License-Identifier: AGPL-3.0-or-later
|
||||
|
||||
using Content.Server.Body.Components;
|
||||
using Content.Server.Medical;
|
||||
using Content.Shared._Mono.CorticalBorer;
|
||||
using Content.Shared._Shitmed.Medical.Surgery;
|
||||
using Content.Shared.Body.Components;
|
||||
using Content.Shared.DoAfter;
|
||||
using Content.Shared.IdentityManagement;
|
||||
using Content.Shared.Mobs;
|
||||
using Content.Shared.Mobs.Components;
|
||||
using Content.Shared.Popups;
|
||||
|
||||
namespace Content.Server._Mono.CorticalBorer;
|
||||
|
||||
public sealed partial class CorticalBorerSystem
|
||||
{
|
||||
[Dependency] private readonly VomitSystem _vomit = default!;
|
||||
|
||||
private void SubscribeAbilities()
|
||||
{
|
||||
SubscribeLocalEvent<CorticalBorerComponent, CorticalInfestEvent>(OnInfest);
|
||||
SubscribeLocalEvent<CorticalBorerComponent, CorticalInfestDoAfterEvent>(OnInfestDoAfter);
|
||||
|
||||
SubscribeLocalEvent<CorticalBorerComponent, CorticalEjectEvent>(OnEjectHost);
|
||||
SubscribeLocalEvent<CorticalBorerComponent, CorticalTakeControlEvent>(OnTakeControl);
|
||||
|
||||
SubscribeLocalEvent<CorticalBorerComponent, CorticalChemMenuActionEvent>(OnChemcialMenu);
|
||||
SubscribeLocalEvent<CorticalBorerComponent, CorticalCheckBloodEvent>(OnCheckBlood);
|
||||
|
||||
|
||||
SubscribeLocalEvent<CorticalBorerInfestedComponent, CorticalEndControlEvent>(OnEndControl);
|
||||
SubscribeLocalEvent<CorticalBorerInfestedComponent, CorticalLayEggEvent>(OnLayEgg);
|
||||
}
|
||||
|
||||
private void OnChemcialMenu(Entity<CorticalBorerComponent> ent, ref CorticalChemMenuActionEvent args)
|
||||
{
|
||||
if(!TryComp<UserInterfaceComponent>(ent, out var uic))
|
||||
return;
|
||||
|
||||
if (ent.Comp.Host is null)
|
||||
{
|
||||
_popup.PopupEntity(Loc.GetString("cortical-borer-no-host"), ent, ent, PopupType.Medium);
|
||||
return;
|
||||
}
|
||||
|
||||
_ui.TryToggleUi((ent, uic), CorticalBorerDispenserUiKey.Key, ent);
|
||||
}
|
||||
|
||||
private void OnInfest(Entity<CorticalBorerComponent> ent, ref CorticalInfestEvent args)
|
||||
{
|
||||
var (uid, comp) = ent;
|
||||
var target = args.Target;
|
||||
var targetIdentity = Identity.Entity(target, EntityManager);
|
||||
|
||||
if (comp.Host is not null)
|
||||
{
|
||||
_popup.PopupEntity(Loc.GetString("cortical-borer-has-host"), uid, uid, PopupType.Medium);
|
||||
return;
|
||||
}
|
||||
|
||||
if (HasComp<CorticalBorerInfestedComponent>(target))
|
||||
{
|
||||
_popup.PopupEntity(Loc.GetString("cortical-borer-host-already-infested", ("target", targetIdentity)), uid, uid, PopupType.Medium);
|
||||
return;
|
||||
}
|
||||
|
||||
// anything with bloodstream
|
||||
if (!HasComp<BloodstreamComponent>(target))
|
||||
{
|
||||
_popup.PopupEntity(Loc.GetString("cortical-borer-invalid-host", ("target", targetIdentity)), uid, uid, PopupType.Medium);
|
||||
return;
|
||||
}
|
||||
|
||||
// target is on sugar for some reason, can't go in there
|
||||
if (!CanUseAbility(ent, target))
|
||||
return;
|
||||
|
||||
var infestAttempt = new InfestHostAttempt();
|
||||
RaiseLocalEvent(target, infestAttempt);
|
||||
|
||||
if (infestAttempt.Cancelled)
|
||||
{
|
||||
_popup.PopupEntity(Loc.GetString("cortical-borer-face-covered", ("target", targetIdentity)), uid, uid, PopupType.Medium);
|
||||
return;
|
||||
}
|
||||
|
||||
_popup.PopupEntity(Loc.GetString("cortical-borer-start-infest", ("target", targetIdentity)), uid, uid, PopupType.Medium);
|
||||
|
||||
var infestArgs = new DoAfterArgs(EntityManager, uid, TimeSpan.FromSeconds(3), new CorticalInfestDoAfterEvent(), uid, target)
|
||||
{
|
||||
DistanceThreshold = 1.5f,
|
||||
BreakOnDamage = true,
|
||||
BreakOnMove = true,
|
||||
BreakOnWeightlessMove = true,
|
||||
AttemptFrequency = AttemptFrequency.StartAndEnd,
|
||||
Hidden = true,
|
||||
};
|
||||
_doAfter.TryStartDoAfter(infestArgs);
|
||||
}
|
||||
|
||||
private void OnInfestDoAfter(Entity<CorticalBorerComponent> ent, ref CorticalInfestDoAfterEvent args)
|
||||
{
|
||||
if (args.Handled)
|
||||
return;
|
||||
|
||||
if (args.Args.Target is not { } target)
|
||||
return;
|
||||
|
||||
if (args.Cancelled || HasComp<CorticalBorerInfestedComponent>(target))
|
||||
return;
|
||||
|
||||
InfestTarget(ent, target);
|
||||
args.Handled = true;
|
||||
}
|
||||
|
||||
private void OnEjectHost(Entity<CorticalBorerComponent> ent, ref CorticalEjectEvent args)
|
||||
{
|
||||
if (args.Handled)
|
||||
return;
|
||||
|
||||
var (uid, comp) = ent;
|
||||
|
||||
if (comp.Host is null)
|
||||
{
|
||||
_popup.PopupEntity(Loc.GetString("cortical-borer-no-host"), uid, uid, PopupType.Medium);
|
||||
return;
|
||||
}
|
||||
|
||||
if (!CanUseAbility(ent, comp.Host.Value))
|
||||
return;
|
||||
|
||||
TryEjectBorer(ent);
|
||||
|
||||
args.Handled = true;
|
||||
}
|
||||
|
||||
private void OnCheckBlood(Entity<CorticalBorerComponent> ent, ref CorticalCheckBloodEvent args)
|
||||
{
|
||||
if (args.Handled)
|
||||
return;
|
||||
|
||||
if (ent.Comp.Host is null)
|
||||
{
|
||||
_popup.PopupEntity(Loc.GetString("cortical-borer-no-host"), ent, ent, PopupType.Medium);
|
||||
return;
|
||||
}
|
||||
|
||||
TryToggleCheckBlood(ent);
|
||||
|
||||
args.Handled = true;
|
||||
}
|
||||
|
||||
private void OnTakeControl(Entity<CorticalBorerComponent> ent, ref CorticalTakeControlEvent args)
|
||||
{
|
||||
if (args.Handled)
|
||||
return;
|
||||
|
||||
if (ent.Comp.Host is null)
|
||||
{
|
||||
_popup.PopupEntity(Loc.GetString("cortical-borer-no-host"), ent, ent, PopupType.Medium);
|
||||
return;
|
||||
}
|
||||
|
||||
// Host is dead, you can't take control
|
||||
if (TryComp<MobStateComponent>(ent.Comp.Host, out var mobState) &&
|
||||
mobState.CurrentState == MobState.Dead)
|
||||
{
|
||||
_popup.PopupEntity(Loc.GetString("cortical-borer-dead-host"), ent, ent, PopupType.Medium);
|
||||
return;
|
||||
}
|
||||
|
||||
if (!TryComp<CorticalBorerInfestedComponent>(ent.Comp.Host, out var infestedComp))
|
||||
return;
|
||||
|
||||
if (!CanUseAbility(ent, ent.Comp.Host.Value))
|
||||
return;
|
||||
|
||||
// idk how you would cause this...
|
||||
if (ent.Comp.ControlingHost)
|
||||
{
|
||||
_popup.PopupEntity(Loc.GetString("cortical-borer-already-control"), ent, ent, PopupType.Medium);
|
||||
return;
|
||||
}
|
||||
|
||||
TakeControlHost(ent, infestedComp);
|
||||
|
||||
args.Handled = true;
|
||||
}
|
||||
|
||||
private void OnEndControl(Entity<CorticalBorerInfestedComponent> host, ref CorticalEndControlEvent args)
|
||||
{
|
||||
if (args.Handled)
|
||||
return;
|
||||
|
||||
EndControl(host.Comp.Borer);
|
||||
|
||||
args.Handled = true;
|
||||
}
|
||||
|
||||
private void OnLayEgg(Entity<CorticalBorerInfestedComponent> host, ref CorticalLayEggEvent args)
|
||||
{
|
||||
if (args.Handled)
|
||||
return;
|
||||
|
||||
var borer = host.Comp.Borer;
|
||||
|
||||
if (borer.Comp.EggCost > borer.Comp.ChemicalPoints)
|
||||
{
|
||||
_popup.PopupEntity(Loc.GetString("cortical-borer-not-enough-chem"), host, host, PopupType.Medium);
|
||||
return;
|
||||
}
|
||||
|
||||
_vomit.Vomit(host, -20, -20); // half as much chem vomit, a lot that is coming up is the egg
|
||||
LayEgg(borer);
|
||||
UpdateChems(borer, -borer.Comp.EggCost);
|
||||
|
||||
args.Handled = true;
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,390 @@
|
|||
// SPDX-FileCopyrightText: 2025 Coenx-flex
|
||||
// SPDX-FileCopyrightText: 2025 Cojoke
|
||||
// SPDX-FileCopyrightText: 2025 ScyronX
|
||||
//
|
||||
// SPDX-License-Identifier: AGPL-3.0-or-later
|
||||
|
||||
using Content.Server.Body.Components;
|
||||
using Content.Server.Body.Systems;
|
||||
using Content.Server.Chat.Managers;
|
||||
using Content.Server.Chat.Systems;
|
||||
using Content.Server.DoAfter;
|
||||
using Content.Server.Ghost.Roles;
|
||||
using Content.Server.Ghost.Roles.Components;
|
||||
using Content.Server.Medical;
|
||||
using Content.Server.Medical.Components;
|
||||
using Content.Server.Nutrition.Components;
|
||||
using Content.Shared._Mono.CorticalBorer;
|
||||
using Content.Shared.Administration.Logs;
|
||||
using Content.Shared.Alert;
|
||||
using Content.Shared.Chemistry.Components;
|
||||
using Content.Shared.Chemistry.Reagent;
|
||||
using Content.Shared.Chat; // Einstein Engines - Languages
|
||||
using Content.Shared.Database;
|
||||
using Content.Shared.Inventory;
|
||||
using Content.Shared.MedicalScanner;
|
||||
using Content.Shared.Mind;
|
||||
using Content.Shared.Mind.Components;
|
||||
using Content.Shared.Mobs;
|
||||
using Content.Shared.Mobs.Components;
|
||||
using Content.Shared.Nutrition.Components; // DeltaV - IngestionBlocker in shared
|
||||
using Content.Shared.Popups;
|
||||
using Content.Shared.SSDIndicator;
|
||||
using Robust.Server.GameObjects;
|
||||
using Robust.Shared.Map;
|
||||
using Robust.Shared.Prototypes;
|
||||
using Robust.Shared.Timing;
|
||||
|
||||
namespace Content.Server._Mono.CorticalBorer;
|
||||
|
||||
public sealed partial class CorticalBorerSystem : SharedCorticalBorerSystem
|
||||
{
|
||||
[Dependency] private readonly IGameTiming _timing = default!;
|
||||
[Dependency] private readonly BloodstreamSystem _blood = default!;
|
||||
[Dependency] private readonly HealthAnalyzerSystem _analyzer = default!;
|
||||
[Dependency] private readonly DoAfterSystem _doAfter = default!;
|
||||
[Dependency] private readonly IPrototypeManager _proto = default!;
|
||||
[Dependency] private readonly InventorySystem _inventory = default!;
|
||||
[Dependency] private readonly UserInterfaceSystem _userInterfaceSystem = default!;
|
||||
[Dependency] private readonly ISharedAdminLogManager _admin = default!;
|
||||
[Dependency] private readonly SharedMindSystem _mind = default!;
|
||||
[Dependency] private readonly IChatManager _chat = default!;
|
||||
[Dependency] private readonly AlertsSystem _alerts = default!;
|
||||
[Dependency] private readonly GhostRoleSystem _ghost = default!;
|
||||
|
||||
public override void Initialize()
|
||||
{
|
||||
SubscribeAbilities();
|
||||
|
||||
SubscribeLocalEvent<CorticalBorerComponent, ComponentStartup>(OnStartup);
|
||||
|
||||
SubscribeLocalEvent<CorticalBorerComponent, CorticalBorerDispenserInjectMessage>(OnInjectReagentMessage);
|
||||
SubscribeLocalEvent<CorticalBorerComponent, CorticalBorerDispenserSetInjectAmountMessage>(OnSetInjectAmountMessage);
|
||||
|
||||
SubscribeLocalEvent<InventoryComponent, InfestHostAttempt>(OnInfestHostAttempt);
|
||||
SubscribeLocalEvent<CorticalBorerComponent, CheckTargetedSpeechEvent>(OnSpeakEvent);
|
||||
|
||||
SubscribeLocalEvent<CorticalBorerComponent, MindRemovedMessage>(OnMindRemoved);
|
||||
}
|
||||
|
||||
private void OnStartup(Entity<CorticalBorerComponent> ent, ref ComponentStartup args)
|
||||
{
|
||||
//add actions
|
||||
foreach (var actionId in ent.Comp.InitialCorticalBorerActions)
|
||||
_actions.AddAction(ent, actionId);
|
||||
|
||||
_alerts.ShowAlert(ent, ent.Comp.ChemicalAlert);
|
||||
UpdateUiState(ent);
|
||||
}
|
||||
|
||||
public override void Update(float frameTime)
|
||||
{
|
||||
base.Update(frameTime);
|
||||
|
||||
foreach (var comp in EntityManager.EntityQuery<CorticalBorerComponent>())
|
||||
{
|
||||
if (_timing.CurTime < comp.UpdateTimer)
|
||||
continue;
|
||||
|
||||
comp.UpdateTimer = _timing.CurTime + TimeSpan.FromSeconds(comp.UpdateCooldown);
|
||||
|
||||
if (comp.Host != null)
|
||||
UpdateChems((comp.Owner, comp), comp.ChemicalGenerationRate);
|
||||
}
|
||||
|
||||
foreach (var comp in EntityManager.EntityQuery<CorticalBorerInfestedComponent>())
|
||||
{
|
||||
if (_timing.CurTime >= comp.ControlTimeEnd)
|
||||
EndControl(comp.Borer);
|
||||
}
|
||||
}
|
||||
|
||||
private void OnSpeakEvent(Entity<CorticalBorerComponent> ent, ref CheckTargetedSpeechEvent args)
|
||||
{
|
||||
//args.ChatTypeIgnore.Add(InGameICChatType.CollectiveMind); // DeltaV - eradicate language code
|
||||
|
||||
if (ent.Comp.Host.HasValue)
|
||||
{
|
||||
args.Targets.Add(ent);
|
||||
args.Targets.Add(ent.Comp.Host.Value);
|
||||
}
|
||||
}
|
||||
|
||||
public void UpdateChems(Entity<CorticalBorerComponent> ent, int change)
|
||||
{
|
||||
var (_, comp) = ent;
|
||||
|
||||
if (comp.ChemicalPoints + change >= comp.ChemicalPointCap)
|
||||
comp.ChemicalPoints = comp.ChemicalPointCap;
|
||||
else if (comp.ChemicalPoints + change <= 0)
|
||||
comp.ChemicalPoints = 0;
|
||||
else
|
||||
comp.ChemicalPoints += change;
|
||||
|
||||
if (comp.ChemicalPoints % comp.UiUpdateInterval == 0)
|
||||
UpdateUiState(ent);
|
||||
|
||||
_alerts.ShowAlert(ent, ent.Comp.ChemicalAlert);
|
||||
|
||||
Dirty(ent);
|
||||
}
|
||||
|
||||
public void OnInfestHostAttempt(Entity<InventoryComponent> entity, ref InfestHostAttempt args)
|
||||
{
|
||||
IngestionBlockerComponent? blocker;
|
||||
|
||||
if (_inventory.TryGetSlotEntity(entity.Owner, "head", out var headUid) &&
|
||||
TryComp(headUid, out blocker) &&
|
||||
blocker.Enabled)
|
||||
{
|
||||
args.Blocker = headUid;
|
||||
args.Cancel();
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Attempts to inject the Borer's host with chems
|
||||
/// </summary>
|
||||
public bool TryInjectHost(Entity<CorticalBorerComponent> ent,
|
||||
CorticalBorerChemicalPrototype chemicalPrototype,
|
||||
float chemAmount)
|
||||
{
|
||||
var (uid, comp) = ent;
|
||||
|
||||
// Need a host to inject something
|
||||
if (!comp.Host.HasValue)
|
||||
{
|
||||
_popup.PopupEntity(Loc.GetString("cortical-borer-no-host"), uid, uid, PopupType.Medium);
|
||||
return false;
|
||||
}
|
||||
|
||||
// Sugar block from injecting stuff
|
||||
if (!CanUseAbility(ent, comp.Host.Value))
|
||||
return false;
|
||||
|
||||
// Make sure you can even hold the amount of chems you need
|
||||
if (chemicalPrototype.Cost > comp.ChemicalPointCap)
|
||||
{
|
||||
_popup.PopupEntity(Loc.GetString("cortical-borer-not-enough-chem-storage"), uid, uid, PopupType.Medium);
|
||||
return false;
|
||||
}
|
||||
|
||||
// Make sure you have enough chems
|
||||
if (chemicalPrototype.Cost > comp.ChemicalPoints)
|
||||
{
|
||||
_popup.PopupEntity(Loc.GetString("cortical-borer-not-enough-chem"), uid, uid, PopupType.Medium);
|
||||
return false;
|
||||
}
|
||||
|
||||
// no injecting things that don't have blood silly
|
||||
if (!TryComp<BloodstreamComponent>(comp.Host, out var bloodstream))
|
||||
return false;
|
||||
|
||||
var solution = new Solution();
|
||||
solution.AddReagent(chemicalPrototype.Reagent, chemAmount);
|
||||
|
||||
// add the chemicals to the bloodstream of the host
|
||||
if (!_blood.TryAddToChemicals(comp.Host.Value, solution, bloodstream))
|
||||
return false;
|
||||
|
||||
UpdateChems(ent, -((int)chemAmount * chemicalPrototype.Cost));
|
||||
return true;
|
||||
}
|
||||
|
||||
private void OnInjectReagentMessage(Entity<CorticalBorerComponent> ent, ref CorticalBorerDispenserInjectMessage message)
|
||||
{
|
||||
CorticalBorerChemicalPrototype? chemProto = null;
|
||||
foreach (var chem in _proto.EnumeratePrototypes<CorticalBorerChemicalPrototype>())
|
||||
{
|
||||
if (chem.Reagent.Equals(message.ChemProtoId))
|
||||
{
|
||||
chemProto = chem;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (chemProto != null)
|
||||
TryInjectHost(ent, chemProto, ent.Comp.InjectAmount);
|
||||
|
||||
UpdateUiState(ent);
|
||||
}
|
||||
|
||||
private void OnSetInjectAmountMessage(Entity<CorticalBorerComponent> ent, ref CorticalBorerDispenserSetInjectAmountMessage message)
|
||||
{
|
||||
ent.Comp.InjectAmount = message.CorticalBorerDispenserDispenseAmount;
|
||||
UpdateUiState(ent);
|
||||
}
|
||||
|
||||
private List<CorticalBorerDispenserItem> GetAllBorerChemicals(Entity<CorticalBorerComponent> ent)
|
||||
{
|
||||
var clones = new List<CorticalBorerDispenserItem>();
|
||||
foreach (var prototype in _proto.EnumeratePrototypes<CorticalBorerChemicalPrototype>())
|
||||
{
|
||||
if (!_proto.TryIndex(prototype.Reagent, out ReagentPrototype? proto))
|
||||
continue;
|
||||
|
||||
var reagentName = proto.LocalizedName;
|
||||
var reagentId = proto.ID;
|
||||
var cost = prototype.Cost;
|
||||
var amount = ent.Comp.InjectAmount;
|
||||
var chems = ent.Comp.ChemicalPoints;
|
||||
var color = proto.SubstanceColor;
|
||||
|
||||
clones.Add(new CorticalBorerDispenserItem(reagentName,reagentId, cost, amount, chems, color)); // need color and name
|
||||
}
|
||||
|
||||
return clones;
|
||||
}
|
||||
|
||||
private void UpdateUiState(Entity<CorticalBorerComponent> ent)
|
||||
{
|
||||
var chems = GetAllBorerChemicals(ent);
|
||||
|
||||
var state = new CorticalBorerDispenserBoundUserInterfaceState(chems, (int)ent.Comp.InjectAmount);
|
||||
_userInterfaceSystem.SetUiState(ent.Owner, CorticalBorerDispenserUiKey.Key, state);
|
||||
}
|
||||
|
||||
public bool TryToggleCheckBlood(Entity<CorticalBorerComponent> ent)
|
||||
{
|
||||
if(!TryComp<UserInterfaceComponent>(ent, out var uic))
|
||||
return false;
|
||||
|
||||
if (!TryComp<HealthAnalyzerComponent>(ent, out var health))
|
||||
return false;
|
||||
|
||||
_ui.TryToggleUi((ent, uic), HealthAnalyzerUiKey.Key, ent);
|
||||
|
||||
if (health.ScannedEntity is null && ent.Comp.Host.HasValue)
|
||||
OpenCheckBlood(ent, uic);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
public void OpenCheckBlood(Entity<CorticalBorerComponent> ent, UserInterfaceComponent uic)
|
||||
{
|
||||
if (!ent.Comp.Host.HasValue)
|
||||
return;
|
||||
|
||||
if (!TryComp<HealthAnalyzerComponent>(ent, out var health))
|
||||
return;
|
||||
|
||||
if (!_ui.IsUiOpen((ent,uic), HealthAnalyzerUiKey.Key))
|
||||
_ui.OpenUi((ent, uic), HealthAnalyzerUiKey.Key, ent);
|
||||
_analyzer.BeginAnalyzingEntity((ent, health), ent.Comp.Host.Value);
|
||||
}
|
||||
|
||||
public void CloseCheckBlood(Entity<CorticalBorerComponent> ent, UserInterfaceComponent uic)
|
||||
{
|
||||
if (!ent.Comp.Host.HasValue)
|
||||
return;
|
||||
|
||||
if (!TryComp<HealthAnalyzerComponent>(ent, out var health))
|
||||
return;
|
||||
|
||||
if(!health.ScannedEntity.HasValue)
|
||||
return;
|
||||
|
||||
_ui.CloseUi((ent, uic), HealthAnalyzerUiKey.Key, ent);
|
||||
_analyzer.StopAnalyzingEntity((ent, health), health.ScannedEntity.Value);
|
||||
}
|
||||
|
||||
public void TakeControlHost(Entity<CorticalBorerComponent> ent, CorticalBorerInfestedComponent infestedComp)
|
||||
{
|
||||
var (worm, comp) = ent;
|
||||
|
||||
if (comp.Host is not { } host)
|
||||
return;
|
||||
|
||||
// make sure they aren't dead, would throw the worm into a ghost mode and just kill em
|
||||
if (TryComp<MobStateComponent>(ent.Comp.Host, out var mobState) &&
|
||||
mobState.CurrentState == MobState.Dead)
|
||||
return;
|
||||
|
||||
if (TryComp<MindContainerComponent>(host, out var mindContainer) &&
|
||||
mindContainer.HasMind ||
|
||||
HasComp<GhostRoleComponent>(host))
|
||||
infestedComp.ControlTimeEnd = _timing.CurTime + comp.ControlDuration;
|
||||
|
||||
if (_mind.TryGetMind(worm, out var wormMind, out _))
|
||||
infestedComp.BorerMindId = wormMind;
|
||||
|
||||
if (_mind.TryGetMind(host, out var controledMind, out _))
|
||||
{
|
||||
infestedComp.OrigininalMindId = controledMind; // set this var here just in case somehow the mind changes from when the infestation started
|
||||
|
||||
// fish head...
|
||||
var dummy = Spawn("FoodMeatFish", MapCoordinates.Nullspace);
|
||||
_container.Insert(dummy, infestedComp.ControlContainer);
|
||||
|
||||
_mind.TransferTo(controledMind, dummy);
|
||||
}
|
||||
else
|
||||
{
|
||||
infestedComp.OrigininalMindId = null;
|
||||
}
|
||||
|
||||
comp.ControlingHost = true;
|
||||
_mind.TransferTo(wormMind, host);
|
||||
|
||||
if (TryComp<GhostRoleComponent>(worm, out var ghostRole))
|
||||
_ghost.UnregisterGhostRole((worm, ghostRole)); // prevent players from taking the worm role once mind isn't in the worm
|
||||
|
||||
// add the end control and vomit egg action
|
||||
if (_actions.AddAction(host, "ActionEndControlHost") is {} actionEnd)
|
||||
infestedComp.RemoveAbilities.Add(actionEnd);
|
||||
if (comp.CanReproduce &&
|
||||
infestedComp.ControlTimeEnd != null) // you can't lay eggs with something you can control forever
|
||||
{
|
||||
if (_actions.AddAction(host, "ActionLayEggHost") is {} actionLay)
|
||||
infestedComp.RemoveAbilities.Add(actionLay);
|
||||
}
|
||||
|
||||
var str = $"{ToPrettyString(worm)} has taken control over {ToPrettyString(host)}";
|
||||
|
||||
Log.Info(str);
|
||||
_admin.Add(LogType.Mind, LogImpact.High, $"{ToPrettyString(worm)} has taken control over {ToPrettyString(host)}");
|
||||
_chat.SendAdminAlert(str);
|
||||
}
|
||||
|
||||
public void EndControl(Entity<CorticalBorerComponent> worm)
|
||||
{
|
||||
var (uid, comp) = worm;
|
||||
|
||||
if (comp.Host is not { } host)
|
||||
return;
|
||||
|
||||
if (!TryComp<CorticalBorerInfestedComponent>(host, out var infestedComp))
|
||||
return;
|
||||
|
||||
// not controlling anyone
|
||||
if (!comp.ControlingHost)
|
||||
return;
|
||||
|
||||
comp.ControlingHost = false;
|
||||
|
||||
// remove all the actions set to remove
|
||||
foreach (var ability in infestedComp.RemoveAbilities)
|
||||
{
|
||||
_actions.RemoveAction(host, ability);
|
||||
}
|
||||
infestedComp.RemoveAbilities = new(); // clear out the list
|
||||
|
||||
if (TryComp<GhostRoleComponent>(worm, out var ghostRole))
|
||||
_ghost.RegisterGhostRole((worm, ghostRole)); // re-enable the ghost role after you return to the body
|
||||
|
||||
// Return everyone to their own bodies
|
||||
if (!TerminatingOrDeleted(infestedComp.BorerMindId))
|
||||
_mind.TransferTo(infestedComp.BorerMindId, infestedComp.Borer);
|
||||
if (!TerminatingOrDeleted(infestedComp.OrigininalMindId) && infestedComp.OrigininalMindId.HasValue)
|
||||
_mind.TransferTo(infestedComp.OrigininalMindId.Value, host);
|
||||
|
||||
infestedComp.ControlTimeEnd = null;
|
||||
_container.CleanContainer(infestedComp.ControlContainer);
|
||||
}
|
||||
|
||||
private void OnMindRemoved(Entity<CorticalBorerComponent> ent, ref MindRemovedMessage args)
|
||||
{
|
||||
if (!ent.Comp.ControlingHost)
|
||||
TryEjectBorer(ent); // No storing them in hosts if you don't have a soul
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,63 @@
|
|||
using Robust.Shared.GameStates;
|
||||
|
||||
namespace Content.Shared.Alert.Components;
|
||||
|
||||
/// <summary>
|
||||
/// This is used for an alert which simply displays a generic number over a texture.
|
||||
/// </summary>
|
||||
[RegisterComponent, NetworkedComponent]
|
||||
public sealed partial class GenericCounterAlertComponent : Component
|
||||
{
|
||||
/// <summary>
|
||||
/// The width, in pixels, of an individual glyph, accounting for the space between glyphs.
|
||||
/// A 3 pixel wide glyph with one pixel of space between it and the next would be a width of 4.
|
||||
/// </summary>
|
||||
[DataField]
|
||||
public int GlyphWidth = 6;
|
||||
|
||||
/// <summary>
|
||||
/// Whether the numbers should be centered on the glyph or just follow a static position.
|
||||
/// </summary>
|
||||
[DataField]
|
||||
public bool CenterGlyph = true;
|
||||
|
||||
/// <summary>
|
||||
/// Whether leading zeros should be hidden.
|
||||
/// If true, "005" would display as "5".
|
||||
/// </summary>
|
||||
[DataField]
|
||||
public bool HideLeadingZeroes = true;
|
||||
|
||||
/// <summary>
|
||||
/// The size of the alert sprite.
|
||||
/// Used to calculate offsets.
|
||||
/// </summary>
|
||||
[DataField]
|
||||
public Vector2i AlertSize = new(32, 32);
|
||||
|
||||
/// <summary>
|
||||
/// Digits that can be displayed by the alert, represented by their sprite layer.
|
||||
/// Order defined corresponds to the digit it affects. 1st defined will affect 1st digit, 2nd affect 2nd digit and so on.
|
||||
/// In this case ones would be on layer "1", tens on layer "10" etc.
|
||||
/// </summary>
|
||||
[DataField]
|
||||
public List<string> DigitKeys = new()
|
||||
{
|
||||
"1",
|
||||
"10",
|
||||
"100",
|
||||
"1000",
|
||||
"10000"
|
||||
};
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Event raised to gather the amount the alert will display.
|
||||
/// </summary>
|
||||
/// <param name="Alert">The alert which is currently requesting an update.</param>
|
||||
/// <param name="Amount">The number to display on the alert.</param>
|
||||
[ByRefEvent]
|
||||
public record struct GetGenericAlertCounterAmountEvent(AlertPrototype Alert, int? Amount = null)
|
||||
{
|
||||
public bool Handled => Amount.HasValue;
|
||||
}
|
||||
|
|
@ -48,6 +48,12 @@ public sealed partial class GenericStatusEffect : EntityEffect
|
|||
{
|
||||
statusSys.TryAddStatusEffect(args.TargetEntity, Key, TimeSpan.FromSeconds(time), Refresh, Component);
|
||||
}
|
||||
// Begin Mono Changes - just add status without component
|
||||
else if (Type == StatusEffectMetabolismType.Add)
|
||||
{
|
||||
statusSys.TryAddStatusEffect(args.TargetEntity, Key, TimeSpan.FromSeconds(time), Refresh);
|
||||
}
|
||||
// End Mono Changes - just add status without component
|
||||
else if (Type == StatusEffectMetabolismType.Remove)
|
||||
{
|
||||
statusSys.TryRemoveTime(args.TargetEntity, Key, TimeSpan.FromSeconds(time));
|
||||
|
|
|
|||
|
|
@ -104,7 +104,7 @@ public enum RevenantVisuals : byte
|
|||
}
|
||||
|
||||
[NetSerializable, Serializable]
|
||||
public enum RevenantVisualLayers : byte
|
||||
public enum RevenantVisualLayers : byte // DeltaV - kept for impstation rev compatibility
|
||||
{
|
||||
Digit1,
|
||||
Digit2,
|
||||
|
|
|
|||
|
|
@ -0,0 +1,109 @@
|
|||
// SPDX-FileCopyrightText: 2025 Coenx-flex
|
||||
// SPDX-FileCopyrightText: 2025 Cojoke
|
||||
//
|
||||
// SPDX-License-Identifier: AGPL-3.0-or-later
|
||||
|
||||
using Content.Shared.Alert;
|
||||
using Robust.Shared.GameStates;
|
||||
using Robust.Shared.Prototypes;
|
||||
|
||||
namespace Content.Shared._Mono.CorticalBorer;
|
||||
|
||||
[RegisterComponent, NetworkedComponent, AutoGenerateComponentState]
|
||||
public sealed partial class CorticalBorerComponent : Component
|
||||
{
|
||||
/// <summary>
|
||||
/// Host of this Borer
|
||||
/// </summary>
|
||||
[ViewVariables]
|
||||
public EntityUid? Host = null;
|
||||
|
||||
/// <summary>
|
||||
/// Current number of chemical points this Borer has, used to level up and buy chems
|
||||
/// </summary>
|
||||
[ViewVariables(VVAccess.ReadWrite)]
|
||||
[AutoNetworkedField]
|
||||
[DataField]
|
||||
public int ChemicalPoints = 50;
|
||||
|
||||
/// <summary>
|
||||
/// Chemicals added every second WHILE IN A HOST
|
||||
/// </summary>
|
||||
[ViewVariables(VVAccess.ReadWrite)]
|
||||
[DataField]
|
||||
public int ChemicalGenerationRate = 1;
|
||||
|
||||
/// <summary>
|
||||
/// Max Chemicals that can be held
|
||||
/// </summary>
|
||||
[ViewVariables(VVAccess.ReadWrite)]
|
||||
[DataField]
|
||||
public int ChemicalPointCap = 250;
|
||||
|
||||
/// <summary>
|
||||
/// Reagent injection amount
|
||||
/// </summary>
|
||||
public int InjectAmount = 10;
|
||||
|
||||
/// <summary>
|
||||
/// At what interval does the chem ui update
|
||||
/// </summary>
|
||||
public int UiUpdateInterval = 5; // every 6 to prevent constant update on cap
|
||||
|
||||
/// <summary>
|
||||
/// The max duration you can take control of your host
|
||||
/// </summary>
|
||||
[ViewVariables(VVAccess.ReadWrite)]
|
||||
[DataField]
|
||||
public TimeSpan ControlDuration = TimeSpan.FromSeconds(40);
|
||||
|
||||
/// <summary>
|
||||
/// Cooldown between chem regen events.
|
||||
/// </summary>
|
||||
public TimeSpan UpdateTimer = TimeSpan.Zero;
|
||||
public float UpdateCooldown = 1f;
|
||||
|
||||
/// <summary>
|
||||
/// Can this borer make more
|
||||
/// </summary>
|
||||
[ViewVariables(VVAccess.ReadWrite)]
|
||||
[DataField]
|
||||
public bool CanReproduce = true;
|
||||
|
||||
/// <summary>
|
||||
/// What does it vomit out of its mouth when it lays an egg
|
||||
/// </summary>
|
||||
[ViewVariables(VVAccess.ReadWrite)]
|
||||
[DataField]
|
||||
public string EggProto = "CorticalBorerEgg";
|
||||
|
||||
/// <summary>
|
||||
/// cost to lay an egg... will not update ability desc if changed
|
||||
/// </summary>
|
||||
[ViewVariables(VVAccess.ReadWrite)]
|
||||
[DataField]
|
||||
public int EggCost = 200;
|
||||
|
||||
[DataField]
|
||||
public bool ControlingHost;
|
||||
|
||||
[DataField]
|
||||
public ComponentRegistry? AddOnInfest;
|
||||
|
||||
[DataField]
|
||||
public ComponentRegistry? RemoveOnInfest;
|
||||
|
||||
[DataField]
|
||||
public ProtoId<AlertPrototype> ChemicalAlert = "Chemicals";
|
||||
|
||||
public readonly List<EntProtoId> InitialCorticalBorerActions = new()
|
||||
{
|
||||
"ActionCorticalBorerInfest",
|
||||
"ActionCorticalBorerEject",
|
||||
"ActionCorticalBorerChemMenu",
|
||||
"ActionCheckBlood",
|
||||
"ActionControlHost",
|
||||
};
|
||||
}
|
||||
|
||||
|
||||
|
|
@ -0,0 +1,50 @@
|
|||
// SPDX-FileCopyrightText: 2025 Coenx-flex
|
||||
// SPDX-FileCopyrightText: 2025 Cojoke
|
||||
//
|
||||
// SPDX-License-Identifier: AGPL-3.0-or-later
|
||||
|
||||
using Robust.Shared.GameStates;
|
||||
using Robust.Shared.Containers;
|
||||
|
||||
namespace Content.Shared._Mono.CorticalBorer;
|
||||
|
||||
|
||||
[RegisterComponent, NetworkedComponent]
|
||||
public sealed partial class CorticalBorerInfestedComponent : Robust.Shared.GameObjects.Component
|
||||
{
|
||||
/// <summary>
|
||||
/// Borer in the person
|
||||
/// </summary>
|
||||
[ViewVariables]
|
||||
public Entity<CorticalBorerComponent> Borer = new();
|
||||
|
||||
/// <summary>
|
||||
/// Container for borer
|
||||
/// </summary>
|
||||
public Container InfestationContainer = new();
|
||||
|
||||
/// <summary>
|
||||
/// is the person under the borer's control
|
||||
/// </summary>
|
||||
[ViewVariables(VVAccess.ReadWrite)]
|
||||
public TimeSpan? ControlTimeEnd;
|
||||
|
||||
[ViewVariables]
|
||||
public EntityUid? OrigininalMindId;
|
||||
|
||||
[ViewVariables]
|
||||
public EntityUid BorerMindId;
|
||||
|
||||
/// <summary>
|
||||
/// Where the mind gets hidden when the worm takes control
|
||||
/// </summary>
|
||||
public Container ControlContainer;
|
||||
|
||||
/// <summary>
|
||||
/// Abilities to be removed once host gets control back
|
||||
/// </summary>
|
||||
public List<EntityUid> RemoveAbilities = new();
|
||||
}
|
||||
|
||||
[RegisterComponent, NetworkedComponent]
|
||||
public sealed partial class SurgeryCorticalBorerConditionComponent : Robust.Shared.GameObjects.Component;
|
||||
|
|
@ -0,0 +1,10 @@
|
|||
// SPDX-FileCopyrightText: 2025 Coenx-flex
|
||||
//
|
||||
// SPDX-License-Identifier: AGPL-3.0-or-later
|
||||
|
||||
using Robust.Shared.GameStates;
|
||||
|
||||
namespace Content.Shared._Mono.CorticalBorer;
|
||||
|
||||
[RegisterComponent, NetworkedComponent]
|
||||
public sealed partial class SurgeryStepRemoveCorticalBorerComponent : Component { }
|
||||
|
|
@ -0,0 +1,15 @@
|
|||
// SPDX-FileCopyrightText: 2025 Coenx-flex
|
||||
//
|
||||
// SPDX-License-Identifier: AGPL-3.0-or-later
|
||||
|
||||
using Content.Shared.Actions;
|
||||
|
||||
namespace Content.Shared._Mono.CorticalBorer;
|
||||
|
||||
public sealed partial class CorticalInfestEvent : EntityTargetActionEvent { }
|
||||
public sealed partial class CorticalEjectEvent : InstantActionEvent { }
|
||||
public sealed partial class CorticalChemMenuActionEvent : InstantActionEvent {}
|
||||
public sealed partial class CorticalCheckBloodEvent : InstantActionEvent {}
|
||||
public sealed partial class CorticalTakeControlEvent : InstantActionEvent {}
|
||||
public sealed partial class CorticalEndControlEvent : InstantActionEvent {}
|
||||
public sealed partial class CorticalLayEggEvent : InstantActionEvent {}
|
||||
|
|
@ -0,0 +1,11 @@
|
|||
// SPDX-FileCopyrightText: 2025 Coenx-flex
|
||||
//
|
||||
// SPDX-License-Identifier: AGPL-3.0-or-later
|
||||
|
||||
using Content.Shared.DoAfter;
|
||||
using Robust.Shared.Serialization;
|
||||
|
||||
namespace Content.Shared._Mono.CorticalBorer;
|
||||
|
||||
[Serializable, NetSerializable]
|
||||
public sealed partial class CorticalInfestDoAfterEvent : SimpleDoAfterEvent { }
|
||||
|
|
@ -0,0 +1,27 @@
|
|||
// SPDX-FileCopyrightText: 2025 Coenx-flex
|
||||
//
|
||||
// SPDX-License-Identifier: AGPL-3.0-or-later
|
||||
|
||||
using Content.Shared.Chemistry.Reagent;
|
||||
using Robust.Shared.Prototypes;
|
||||
|
||||
namespace Content.Shared._Mono.CorticalBorer;
|
||||
|
||||
[Prototype("borerChemical")]
|
||||
public sealed partial class CorticalBorerChemicalPrototype : IPrototype
|
||||
{
|
||||
[IdDataField]
|
||||
public string ID { get; } = default!;
|
||||
|
||||
/// <summary>
|
||||
/// Chemical cost per u of reagent
|
||||
/// </summary>
|
||||
[DataField]
|
||||
public int Cost { get; set; } = 5;
|
||||
|
||||
/// <summary>
|
||||
/// Reagent to inject into host
|
||||
/// </summary>
|
||||
[DataField]
|
||||
public string Reagent { get; set; } = "";
|
||||
}
|
||||
|
|
@ -0,0 +1,241 @@
|
|||
// SPDX-FileCopyrightText: 2025 Coenx-flex
|
||||
// SPDX-FileCopyrightText: 2025 Cojoke
|
||||
//
|
||||
// SPDX-License-Identifier: AGPL-3.0-or-later
|
||||
|
||||
using System.Linq;
|
||||
using Content.Shared.Actions;
|
||||
using Content.Shared.Body.Components;
|
||||
using Content.Shared.Body.Part;
|
||||
using Content.Shared.Body.Systems;
|
||||
using Content.Shared.Containers.ItemSlots;
|
||||
using Content.Shared.Examine;
|
||||
using Content.Shared.MedicalScanner;
|
||||
using Content.Shared.Popups;
|
||||
using Content.Shared.StatusEffect;
|
||||
using Content.Shared.Coordinates;
|
||||
using Content.Shared.Damage;
|
||||
using Content.Shared.IdentityManagement;
|
||||
using Robust.Shared.Containers;
|
||||
using Robust.Shared.Serialization;
|
||||
using Robust.Shared.Serialization.Manager;
|
||||
|
||||
namespace Content.Shared._Mono.CorticalBorer;
|
||||
|
||||
public partial class SharedCorticalBorerSystem : EntitySystem
|
||||
{
|
||||
[Dependency] private readonly SharedBodySystem _bodySystem = default!;
|
||||
[Dependency] private readonly StatusEffectsSystem _statusEffects = default!;
|
||||
[Dependency] private readonly SharedTransformSystem _transform = default!;
|
||||
[Dependency] private readonly ISerializationManager _serManager = default!;
|
||||
[Dependency] private readonly DamageableSystem _damage = default!;
|
||||
[Dependency] protected readonly SharedPopupSystem _popup = default!;
|
||||
[Dependency] protected readonly SharedUserInterfaceSystem _ui = default!;
|
||||
[Dependency] protected readonly SharedActionsSystem _actions = default!;
|
||||
[Dependency] protected readonly SharedContainerSystem _container = default!;
|
||||
|
||||
public bool CanUseAbility(Entity<CorticalBorerComponent> ent, EntityUid target)
|
||||
{
|
||||
if (_statusEffects.HasStatusEffect(target,
|
||||
"CorticalBorerProtection")) // hardcoded the status effect because...
|
||||
{
|
||||
_popup.PopupEntity(Loc.GetString("cortical-borer-sugar-block"), ent.Owner, ent.Owner, PopupType.Medium);
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
public void InfestTarget(Entity<CorticalBorerComponent> ent, EntityUid target)
|
||||
{
|
||||
var (uid, comp) = ent;
|
||||
|
||||
// Make sure the infected person is infected right
|
||||
var infestedComp = EnsureComp<CorticalBorerInfestedComponent>(target);
|
||||
|
||||
// Make sure they get into the target
|
||||
if (!_container.Insert(uid, infestedComp.InfestationContainer))
|
||||
{
|
||||
RemCompDeferred<CorticalBorerInfestedComponent>(target); // oh no it didn't work somehow so remove the comp you just added...
|
||||
return;
|
||||
}
|
||||
|
||||
// Set up the Borer
|
||||
infestedComp.Borer = ent;
|
||||
comp.Host = target;
|
||||
|
||||
if (comp.AddOnInfest is not null)
|
||||
{
|
||||
foreach (var (key, compReg) in comp.AddOnInfest)
|
||||
{
|
||||
var compType = compReg.Component.GetType();
|
||||
if (HasComp(ent, compType))
|
||||
continue;
|
||||
|
||||
var newComp = (Component) _serManager.CreateCopy(compReg.Component, notNullableOverride: true);
|
||||
EntityManager.AddComponent(ent, newComp, true);
|
||||
}
|
||||
}
|
||||
|
||||
if (comp.RemoveOnInfest is not null)
|
||||
{
|
||||
foreach (var (key, compReg) in comp.RemoveOnInfest)
|
||||
RemCompDeferred(ent, compReg.Component.GetType());
|
||||
}
|
||||
|
||||
if (TryComp<DamageableComponent>(ent, out var damComp))
|
||||
_damage.SetAllDamage(ent, damComp, 0);
|
||||
}
|
||||
|
||||
public bool TryEjectBorer(Entity<CorticalBorerComponent> ent)
|
||||
{
|
||||
var (uid, comp) = ent;
|
||||
|
||||
if (ent.Comp.Host is not { } host)
|
||||
return false;
|
||||
|
||||
// Make sure they get out of the host
|
||||
if (!_container.TryRemoveFromContainer(uid))
|
||||
return false;
|
||||
|
||||
// close all the UIs that relate to host
|
||||
if (TryComp<UserInterfaceComponent>(ent, out var uic))
|
||||
{
|
||||
_ui.CloseUi((ent.Owner,uic), HealthAnalyzerUiKey.Key);
|
||||
_ui.CloseUi((ent.Owner,uic), CorticalBorerDispenserUiKey.Key);
|
||||
}
|
||||
|
||||
RemCompDeferred<CorticalBorerInfestedComponent>(ent.Comp.Host.Value);
|
||||
ent.Comp.Host = null;
|
||||
|
||||
if (comp.RemoveOnInfest is not null)
|
||||
{
|
||||
foreach (var (key, compReg) in comp.RemoveOnInfest)
|
||||
{
|
||||
var compType = compReg.Component.GetType();
|
||||
if (HasComp(ent, compType))
|
||||
continue;
|
||||
|
||||
var newComp = (Component) _serManager.CreateCopy(compReg.Component, notNullableOverride: true);
|
||||
EntityManager.AddComponent(ent, newComp, true);
|
||||
}
|
||||
}
|
||||
|
||||
if (comp.AddOnInfest is not null)
|
||||
{
|
||||
foreach (var (key, compReg) in comp.AddOnInfest)
|
||||
RemCompDeferred(ent, compReg.Component.GetType());
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
public void LayEgg(Entity<CorticalBorerComponent> ent)
|
||||
{
|
||||
if (ent.Comp.Host is not { } host)
|
||||
return;
|
||||
|
||||
if (ent.Comp.EggProto is not {} egg)
|
||||
return;
|
||||
|
||||
var coordinates = _transform.ToMapCoordinates(host.ToCoordinates());
|
||||
var spawnedEgg = Spawn(egg, coordinates);
|
||||
}
|
||||
}
|
||||
|
||||
public sealed class InfestHostAttempt : CancellableEntityEventArgs
|
||||
{
|
||||
/// <summary>
|
||||
/// The equipment that is blocking the entrance
|
||||
/// </summary>
|
||||
public EntityUid? Blocker = null;
|
||||
}
|
||||
|
||||
[Serializable, NetSerializable]
|
||||
public enum CorticalBorerDispenserUiKey
|
||||
{
|
||||
Key
|
||||
}
|
||||
|
||||
|
||||
[Serializable, NetSerializable]
|
||||
public sealed class CorticalBorerDispenserSetInjectAmountMessage : BoundUserInterfaceMessage
|
||||
{
|
||||
public readonly int CorticalBorerDispenserDispenseAmount;
|
||||
|
||||
public CorticalBorerDispenserSetInjectAmountMessage(int amount)
|
||||
{
|
||||
CorticalBorerDispenserDispenseAmount = amount;
|
||||
}
|
||||
|
||||
public CorticalBorerDispenserSetInjectAmountMessage(String s)
|
||||
{
|
||||
switch (s)
|
||||
{
|
||||
case "1":
|
||||
CorticalBorerDispenserDispenseAmount = 1;
|
||||
break;
|
||||
case "5":
|
||||
CorticalBorerDispenserDispenseAmount = 5;
|
||||
break;
|
||||
case "10":
|
||||
CorticalBorerDispenserDispenseAmount = 10;
|
||||
break;
|
||||
case "15":
|
||||
CorticalBorerDispenserDispenseAmount = 15;
|
||||
break;
|
||||
case "20":
|
||||
CorticalBorerDispenserDispenseAmount = 20;
|
||||
break;
|
||||
case "25":
|
||||
CorticalBorerDispenserDispenseAmount = 25;
|
||||
break;
|
||||
case "30":
|
||||
CorticalBorerDispenserDispenseAmount = 30;
|
||||
break;
|
||||
case "50":
|
||||
CorticalBorerDispenserDispenseAmount = 50;
|
||||
break;
|
||||
case "100":
|
||||
CorticalBorerDispenserDispenseAmount = 100;
|
||||
break;
|
||||
default:
|
||||
throw new Exception($"Cannot convert the string `{s}` into a valid DispenseAmount");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
[Serializable, NetSerializable]
|
||||
public sealed class CorticalBorerDispenserInjectMessage : BoundUserInterfaceMessage
|
||||
{
|
||||
public readonly string ChemProtoId;
|
||||
|
||||
public CorticalBorerDispenserInjectMessage(string proto)
|
||||
{
|
||||
ChemProtoId = proto;
|
||||
}
|
||||
}
|
||||
|
||||
[Serializable, NetSerializable]
|
||||
public sealed class CorticalBorerDispenserBoundUserInterfaceState : BoundUserInterfaceState
|
||||
{
|
||||
public readonly List<CorticalBorerDispenserItem> DisList;
|
||||
|
||||
public readonly int SelectedDispenseAmount;
|
||||
public CorticalBorerDispenserBoundUserInterfaceState(List<CorticalBorerDispenserItem> disList, int dispenseAmount)
|
||||
{
|
||||
DisList = disList;
|
||||
SelectedDispenseAmount = dispenseAmount;
|
||||
}
|
||||
}
|
||||
|
||||
[Serializable, NetSerializable]
|
||||
public sealed class CorticalBorerDispenserItem(string reagentName, string reagentId, int cost, int amount, int chems, Color reagentColor)
|
||||
{
|
||||
public string ReagentName = reagentName;
|
||||
public string ReagentId = reagentId;
|
||||
public int Cost = cost;
|
||||
public int Amount = amount;
|
||||
public int Chems = chems;
|
||||
public Color ReagentColor = reagentColor;
|
||||
}
|
||||
|
|
@ -26,6 +26,7 @@ using Content.Shared.Popups;
|
|||
using Robust.Shared.Prototypes;
|
||||
using Robust.Shared.Toolshed.TypeParsers;
|
||||
using System.Linq;
|
||||
using Content.Shared._Mono.CorticalBorer; // Mono
|
||||
|
||||
namespace Content.Shared._Shitmed.Medical.Surgery;
|
||||
|
||||
|
|
@ -47,6 +48,7 @@ public abstract partial class SharedSurgerySystem
|
|||
|
||||
SubSurgery<SurgeryTendWoundsEffectComponent>(OnTendWoundsStep, OnTendWoundsCheck);
|
||||
SubSurgery<SurgeryStepCavityEffectComponent>(OnCavityStep, OnCavityCheck);
|
||||
SubSurgery<SurgeryStepRemoveCorticalBorerComponent>(OnCorticalBorerRemovalStep, OnCorticalBorerRemovalCheck); // Mono
|
||||
SubSurgery<SurgeryAddPartStepComponent>(OnAddPartStep, OnAddPartCheck);
|
||||
SubSurgery<SurgeryAffixPartStepComponent>(OnAffixPartStep, OnAffixPartCheck);
|
||||
SubSurgery<SurgeryRemovePartStepComponent>(OnRemovePartStep, OnRemovePartCheck);
|
||||
|
|
@ -427,6 +429,21 @@ public abstract partial class SharedSurgerySystem
|
|||
args.Cancelled = true;
|
||||
}
|
||||
|
||||
// Begin Mono Changes - borer
|
||||
private void OnCorticalBorerRemovalStep(Entity<SurgeryStepRemoveCorticalBorerComponent> ent, ref SurgeryStepEvent args)
|
||||
{
|
||||
if (TryComp<CorticalBorerInfestedComponent>(args.Body, out var infested) &&
|
||||
infested.InfestationContainer.ContainedEntities.Count != 0)
|
||||
_corticalBorer.TryEjectBorer(infested.Borer);
|
||||
}
|
||||
|
||||
private void OnCorticalBorerRemovalCheck(Entity<SurgeryStepRemoveCorticalBorerComponent> ent, ref SurgeryStepCompleteCheckEvent args)
|
||||
{
|
||||
if (HasComp<CorticalBorerInfestedComponent>(args.Body))
|
||||
args.Cancelled = true;
|
||||
}
|
||||
// End Mono Changes - borer
|
||||
|
||||
private void OnAddPartStep(Entity<SurgeryAddPartStepComponent> ent, ref SurgeryStepEvent args)
|
||||
{
|
||||
if (!TryComp(args.Surgery, out SurgeryPartRemovedConditionComponent? removedComp))
|
||||
|
|
|
|||
|
|
@ -1,4 +1,5 @@
|
|||
using System.Linq;
|
||||
using Content.Shared._Mono.CorticalBorer; // Mono
|
||||
using Content.Shared._Shitmed.Medical.Surgery.Conditions;
|
||||
using Content.Shared._Shitmed.Medical.Surgery.Effects.Complete;
|
||||
using Content.Shared.Body.Systems;
|
||||
|
|
@ -50,6 +51,7 @@ public abstract partial class SharedSurgerySystem : EntitySystem
|
|||
[Dependency] private readonly StandingStateSystem _standing = default!;
|
||||
[Dependency] private readonly SharedTransformSystem _transform = default!;
|
||||
[Dependency] private readonly TagSystem _tagSystem = default!; // DeltaV: surgery can operate through some clothing
|
||||
[Dependency] private readonly SharedCorticalBorerSystem _corticalBorer = default!; // Mono
|
||||
|
||||
/// <summary>
|
||||
/// Cache of all surgery prototypes' singleton entities.
|
||||
|
|
@ -87,6 +89,7 @@ public abstract partial class SharedSurgerySystem : EntitySystem
|
|||
SubscribeLocalEvent<SurgeryPartComponentConditionComponent, SurgeryValidEvent>(OnPartComponentConditionValid);
|
||||
SubscribeLocalEvent<SurgeryOrganOnAddConditionComponent, SurgeryValidEvent>(OnOrganOnAddConditionValid);
|
||||
//SubscribeLocalEvent<SurgeryRemoveLarvaComponent, SurgeryCompletedEvent>(OnRemoveLarva);
|
||||
SubscribeLocalEvent<SurgeryCorticalBorerConditionComponent, SurgeryValidEvent>(OnCorticalBorerValid); // Mono
|
||||
SubscribeLocalEvent<PrototypesReloadedEventArgs>(OnPrototypesReloaded);
|
||||
|
||||
InitializeSteps();
|
||||
|
|
@ -161,6 +164,15 @@ public abstract partial class SharedSurgerySystem : EntitySystem
|
|||
args.Cancelled = true;
|
||||
}*/
|
||||
|
||||
// Begin Mono Changes - borer
|
||||
private void OnCorticalBorerValid(Entity<SurgeryCorticalBorerConditionComponent> ent, ref SurgeryValidEvent args)
|
||||
{
|
||||
if (!HasComp<CorticalBorerInfestedComponent>(args.Body) ||
|
||||
!HasComp<IncisionOpenComponent>(args.Part))
|
||||
args.Cancelled = true;
|
||||
}
|
||||
// End Mono Changes - borer
|
||||
|
||||
private void OnBodyComponentConditionValid(Entity<SurgeryBodyComponentConditionComponent> ent, ref SurgeryValidEvent args)
|
||||
{
|
||||
var present = true;
|
||||
|
|
|
|||
|
|
@ -0,0 +1,2 @@
|
|||
alerts-borer-chemical-name = Chemicals
|
||||
alerts-borer-chemical-desc = Chemicals made in your body, used for your abilities.
|
||||
|
|
@ -0,0 +1,28 @@
|
|||
|
||||
## Infest Messages
|
||||
cortical-borer-has-host = You already have a host.
|
||||
cortical-borer-host-already-infested = {THE($target)} is already infested.
|
||||
cortical-borer-invalid-host = {THE($target)} is not a valid host.
|
||||
cortical-borer-face-covered = {THE($target)}'s face is covered.
|
||||
cortical-borer-headless = {THE($target)} does not have a head!
|
||||
cortical-borer-start-infest = You begin to crawl into {THE($target)}.
|
||||
|
||||
## Generic messages
|
||||
cortical-borer-no-host = You do not have a host.
|
||||
cortical-borer-dead-host = Your host is dead.
|
||||
cortical-borer-not-enough-chem = You do not have enough chemicals.
|
||||
cortical-borer-not-enough-chem-storage = You cannot hold enough chemicals.
|
||||
cortical-borer-sugar-block = You taste something sweet.
|
||||
|
||||
## Control messages
|
||||
cortical-borer-already-control = You are already controlling your host.
|
||||
cortical-borer-vomit = {$name} vomits out a {$egg}!
|
||||
|
||||
## UI
|
||||
cortical-borer-dispenser-window-cost = {$cost} chemicals
|
||||
cortical-borer-ghostrole-name = Cortical Borer
|
||||
cortical-borer-ghostrole-desc = A space worm with the sole purpose in life to enter peoples heads and lay its eggs.
|
||||
|
||||
## Examine Text
|
||||
infested-control-examined = You have [color=#d842fc]{$timeremaining}[/color] seconds left controlling this body.
|
||||
cortical-borer-self-examine = You have [color=#d842fc]{$chempoints}[/color] chemicals.
|
||||
|
|
@ -0,0 +1 @@
|
|||
surgery-popup-step-SurgeryStepRemoveCorticalBorer = {$user} is removing the Cortical Borer from {$target}'s {$part}!
|
||||
|
|
@ -1,4 +1,4 @@
|
|||
- type: alert
|
||||
- type: alert
|
||||
id: Essence
|
||||
category: Health #it's like ghostie health
|
||||
icons:
|
||||
|
|
@ -18,12 +18,15 @@
|
|||
id: AlertEssenceSpriteView
|
||||
categories: [ HideSpawnMenu ]
|
||||
components:
|
||||
- type: GenericCounterAlert
|
||||
centerGlyph: false
|
||||
hideLeadingZeroes: false
|
||||
- type: Sprite
|
||||
sprite: /Textures/Interface/Alerts/essence_counter.rsi
|
||||
layers:
|
||||
- map: [ "enum.AlertVisualLayers.Base" ]
|
||||
- map: [ "enum.RevenantVisualLayers.Digit1" ]
|
||||
- map: [ "enum.RevenantVisualLayers.Digit2" ]
|
||||
offset: 0.125, 0
|
||||
- map: [ "enum.RevenantVisualLayers.Digit3" ]
|
||||
- map: [ "1" ]
|
||||
offset: 0.25, 0
|
||||
- map: [ "10" ]
|
||||
offset: 0.125, 0
|
||||
- map: [ "100" ]
|
||||
|
|
|
|||
|
|
@ -42,6 +42,8 @@
|
|||
- id: MobWhimperlet # DeltaV - added Whimperlet
|
||||
weight: 0.03
|
||||
#- id: MobMouseCancer # DeltaV - no
|
||||
- id: MobCorticalBorer # DeltaV - ported Borer
|
||||
weight: 0.015 # DeltaV - original probability was 0.001 but that seems WAY too low
|
||||
# Events always spawn a critter regardless of Probability https://github.com/space-wizards/space-station-14/issues/28480 I added the Rat King to their own event with a player cap.
|
||||
|
||||
- type: entity
|
||||
|
|
@ -96,6 +98,8 @@
|
|||
weight: 0.21
|
||||
- id: MobWhimperlet # DeltaV - Added Whimperlet and its weight
|
||||
weight: 0.02
|
||||
- id: MobCorticalBorer # DeltaV - ported Borer
|
||||
weight: 0.015 # DeltaV - original probability was 0.001 but that seems WAY too low
|
||||
|
||||
- type: entity
|
||||
id: SnailMigrationLowPop
|
||||
|
|
@ -140,7 +144,9 @@
|
|||
- id: MobSnail
|
||||
weight: 0.84
|
||||
- id: MobSnailSpeed
|
||||
weight: 0.08
|
||||
weight: 0.07 # DeltaV - was 0.08
|
||||
- id: MobSnailMoth
|
||||
weight: 0.08
|
||||
weight: 0.07 # DeltaV - was 0.08
|
||||
#- id: MobSnailInstantDeath # DeltaV - no
|
||||
- id: MobCorticalBorer # DeltaV - ported Borer
|
||||
weight: 0.02 # DeltaV - 2% chance on snails
|
||||
|
|
|
|||
|
|
@ -90,6 +90,11 @@
|
|||
reagent: Nutriment
|
||||
min: 0.1
|
||||
factor: 1
|
||||
- !type:GenericStatusEffect # Mono change
|
||||
key: CorticalBorerProtection
|
||||
type: Add
|
||||
time: 3 # May be too much, needs testing
|
||||
refresh: false
|
||||
plantMetabolism:
|
||||
- !type:PlantAdjustNutrition
|
||||
amount: 0.1
|
||||
|
|
|
|||
|
|
@ -0,0 +1,87 @@
|
|||
# SPDX-FileCopyrightText: 2025 Coenx-flex
|
||||
# SPDX-FileCopyrightText: 2025 Cojoke
|
||||
#
|
||||
# SPDX-License-Identifier: AGPL-3.0-or-later
|
||||
|
||||
- type: entity # DeltaV - rework for action refactor
|
||||
id: ActionCorticalBorerChemMenu
|
||||
name: Chemicals
|
||||
description: Open the chemicals menu.
|
||||
components:
|
||||
- type: Action
|
||||
icon: _Mono/Interface/Action/borer_chem.png
|
||||
- type: InstantAction
|
||||
event: !type:CorticalChemMenuActionEvent
|
||||
|
||||
- type: entity # DeltaV - rework for action refactor
|
||||
id: ActionCorticalBorerInfest
|
||||
name: Infest
|
||||
description: Infest target Humanoid. Target host must have a bloodstream.
|
||||
components:
|
||||
- type: Action
|
||||
itemIconStyle: NoItem
|
||||
icon: _Mono/Interface/Action/borer_infest.png
|
||||
useDelay: 5
|
||||
- type: TargetAction
|
||||
interactOnMiss: false
|
||||
- type: EntityTargetAction
|
||||
whitelist:
|
||||
components:
|
||||
- Body
|
||||
canTargetSelf: false
|
||||
event: !type:CorticalInfestEvent {}
|
||||
|
||||
- type: entity # DeltaV - rework for action refactor
|
||||
id: ActionCorticalBorerEject
|
||||
name: Eject Host
|
||||
description: Eject yourself from your host.
|
||||
components:
|
||||
- type: Action
|
||||
icon: _Mono/Interface/Action/borer_eject.png
|
||||
useDelay: 5
|
||||
- type: InstantAction
|
||||
event: !type:CorticalEjectEvent
|
||||
|
||||
- type: entity # DeltaV - rework for action refactor
|
||||
id: ActionCheckBlood
|
||||
name: Check Blood
|
||||
description: Inspect the blood of your host to see how they are doing.
|
||||
components:
|
||||
- type: Action
|
||||
icon: _Mono/Interface/Action/borer_bloodcheck.png
|
||||
useDelay: 5
|
||||
- type: InstantAction
|
||||
event: !type:CorticalCheckBloodEvent
|
||||
|
||||
- type: entity # DeltaV - rework for action refactor
|
||||
id: ActionControlHost
|
||||
name: Take Control
|
||||
description: Take full control of your host, lasts for 40 seconds on awake hosts.
|
||||
components:
|
||||
- type: Action
|
||||
icon: _Mono/Interface/Action/borer_control.png
|
||||
useDelay: 340 #5 minutes and 40 seconds
|
||||
- type: InstantAction
|
||||
event: !type:CorticalTakeControlEvent
|
||||
|
||||
- type: entity # DeltaV - rework for action refactor
|
||||
id: ActionEndControlHost
|
||||
name: Relinquish Control
|
||||
description: Give up hold of the host's body early.
|
||||
components:
|
||||
- type: Action
|
||||
icon: _Mono/Interface/Action/borer_eject.png
|
||||
useDelay: 5
|
||||
- type: InstantAction
|
||||
event: !type:CorticalEndControlEvent
|
||||
|
||||
- type: entity # DeltaV - rework for action refactor
|
||||
id: ActionLayEggHost
|
||||
name: Lay Egg
|
||||
description: Force your host to vomit an egg. Costs 200 chemicals. # needs to be changed if the base chem cost is changed
|
||||
components:
|
||||
- type: Action
|
||||
icon: _Mono/Interface/Action/borer_egg.png
|
||||
useDelay: 5
|
||||
- type: InstantAction
|
||||
event: !type:CorticalLayEggEvent
|
||||
|
|
@ -0,0 +1,28 @@
|
|||
# SPDX-FileCopyrightText: 2025 Coenx-flex
|
||||
# SPDX-FileCopyrightText: 2025 Cojoke
|
||||
#
|
||||
# SPDX-License-Identifier: AGPL-3.0-or-later
|
||||
|
||||
- type: alert
|
||||
id: Chemicals
|
||||
category: Battery
|
||||
icons:
|
||||
- sprite: /Textures/Interface/Alerts/generic_counter.rsi
|
||||
state: base
|
||||
alertViewEntity: AlertChemicalsSpriteView
|
||||
name: alerts-borer-chemical-name
|
||||
description: alerts-borer-chemical-desc
|
||||
|
||||
- type: entity
|
||||
id: AlertChemicalsSpriteView
|
||||
categories: [ HideSpawnMenu ]
|
||||
components:
|
||||
- type: GenericCounterAlert
|
||||
hideLeadingZeroes: false
|
||||
- type: Sprite
|
||||
sprite: /Textures/Interface/Alerts/generic_counter.rsi
|
||||
layers:
|
||||
- map: [ "enum.AlertVisualLayers.Base" ]
|
||||
- map: [ "1" ]
|
||||
- map: [ "10" ]
|
||||
- map: [ "100" ]
|
||||
|
|
@ -0,0 +1,64 @@
|
|||
# SPDX-FileCopyrightText: 2025 Coenx-flex
|
||||
#
|
||||
# SPDX-License-Identifier: AGPL-3.0-or-later
|
||||
|
||||
|
||||
- type: borerChemical
|
||||
id: borerBicaridine
|
||||
cost: 4
|
||||
reagent: Bicaridine
|
||||
|
||||
- type: borerChemical
|
||||
id: borerKelotane
|
||||
cost: 4
|
||||
reagent: Kelotane
|
||||
|
||||
- type: borerChemical
|
||||
id: borerSaline
|
||||
cost: 2
|
||||
reagent: Saline
|
||||
|
||||
- type: borerChemical
|
||||
id: borerEthanol
|
||||
cost: 5
|
||||
reagent: Ethanol
|
||||
|
||||
- type: borerChemical
|
||||
id: borerMuteToxin
|
||||
cost: 10 # 1u is 10 seconds, 1 chem per second of mute
|
||||
reagent: MuteToxin
|
||||
|
||||
- type: borerChemical
|
||||
id: borerCharcoal
|
||||
cost: 3
|
||||
reagent: Charcoal
|
||||
|
||||
- type: borerChemical
|
||||
id: borerHappiness
|
||||
cost: 5
|
||||
reagent: Happiness
|
||||
|
||||
- type: borerChemical
|
||||
id: borerEphedrine
|
||||
cost: 10
|
||||
reagent: Ephedrine
|
||||
|
||||
- type: borerChemical
|
||||
id: borerNorepinephricAcid
|
||||
cost: 2 # needs 20u of the acid to even start the blind
|
||||
reagent: NorepinephricAcid
|
||||
|
||||
- type: borerChemical
|
||||
id: borerSalbutamol # DeltaV - dex+ replaced with salbu
|
||||
cost: 10
|
||||
reagent: Salbutamol
|
||||
|
||||
- type: borerChemical
|
||||
id: borerHeartbreakerToxin
|
||||
cost: 10
|
||||
reagent: HeartbreakerToxin
|
||||
|
||||
- type: borerChemical
|
||||
id: borerNocturine
|
||||
cost: 10 # DeltaV - need way less of it here to sleep, doubled price
|
||||
reagent: Nocturine
|
||||
|
|
@ -0,0 +1,149 @@
|
|||
# SPDX-FileCopyrightText: 2025 Ark
|
||||
# SPDX-FileCopyrightText: 2025 Coenx-flex
|
||||
# SPDX-FileCopyrightText: 2025 Cojoke
|
||||
# SPDX-FileCopyrightText: 2025 Redrover1760
|
||||
# SPDX-FileCopyrightText: 2025 ark1368
|
||||
# SPDX-FileCopyrightText: 2025 tonotom
|
||||
#
|
||||
# SPDX-License-Identifier: AGPL-3.0-or-later
|
||||
|
||||
- type: entity
|
||||
name: Cortical Borer
|
||||
parent: SimpleSpaceMobBase
|
||||
id: MobCorticalBorer
|
||||
description: A worm that burrows into brains and lays its eggs. # DeltaV - added a period
|
||||
components:
|
||||
- type: GhostRole
|
||||
makeSentient: true
|
||||
allowSpeech: False
|
||||
allowMovement: true
|
||||
requirements:
|
||||
- !type:OverallPlaytimeRequirement
|
||||
time: 36000 # 10 hrs
|
||||
name: cortical-borer-ghostrole-name
|
||||
description: cortical-borer-ghostrole-desc
|
||||
rules: ghost-role-information-freeagent-rules
|
||||
mindRoles:
|
||||
- MindRoleGhostRoleFreeAgent
|
||||
- type: GhostTakeoverAvailable
|
||||
- type: Sprite
|
||||
drawdepth: SmallMobs
|
||||
sprite: _Mono/Mobs/Aliens/cortical_borer.rsi
|
||||
layers:
|
||||
- map: [ "enum.DamageStateVisualLayers.Base" ]
|
||||
state: cortical_borer
|
||||
- type: DamageStateVisuals
|
||||
states:
|
||||
Alive:
|
||||
Base: cortical_borer
|
||||
Dead:
|
||||
Base: dead
|
||||
- type: Item
|
||||
size: Tiny
|
||||
heldPrefix: 0
|
||||
- type: Physics
|
||||
- type: Fixtures
|
||||
fixtures:
|
||||
fix1:
|
||||
shape:
|
||||
!type:PhysShapeCircle
|
||||
radius: 0.2
|
||||
density: 100
|
||||
mask:
|
||||
- SmallMobMask
|
||||
layer:
|
||||
- SmallMobLayer
|
||||
- type: MobState
|
||||
- type: MobThresholds
|
||||
thresholds:
|
||||
0: Alive
|
||||
20: Dead
|
||||
- type: Barotrauma # take away space immunity but still not gasp
|
||||
damage:
|
||||
types:
|
||||
Blunt: 0.15
|
||||
- type: Bloodstream
|
||||
bloodlossDamage:
|
||||
types:
|
||||
Bloodloss: 0.5
|
||||
bloodlossHealDamage:
|
||||
types:
|
||||
Bloodloss: -1
|
||||
bleedReductionAmount: 2 # bleeding stops faster
|
||||
- type: MovementSpeedModifier
|
||||
baseWalkSpeed : 1.9
|
||||
baseSprintSpeed : 7.5
|
||||
- type: Tag
|
||||
tags:
|
||||
- Meat
|
||||
- type: ThermalVision
|
||||
color: "#ae65bf"
|
||||
lightRadius: 15
|
||||
activateSound: null
|
||||
deactivateSound: null
|
||||
- type: CorticalBorer
|
||||
eggProto: "CorticalBorerEgg"
|
||||
addOnInfest:
|
||||
- type: PressureImmunity
|
||||
removeOnInfest:
|
||||
- type: CanEscapeInventory
|
||||
- type: UserInterface
|
||||
interfaces:
|
||||
enum.CorticalBorerDispenserUiKey.Key:
|
||||
type: CorticalBorerDispenserBoundUserInterface
|
||||
enum.HealthAnalyzerUiKey.Key:
|
||||
type: HealthAnalyzerBoundUserInterface
|
||||
#- type: CollectiveMind # DeltaV - don't got it, maybe port it
|
||||
# channel: CorticalBorer
|
||||
- type: Speech
|
||||
enabled: false
|
||||
- type: HealthAnalyzer
|
||||
scanningEndSound:
|
||||
path: "/Audio/Voice/Slime/slime_squish.ogg"
|
||||
maxScanRange: .5
|
||||
- type: CanEscapeInventory
|
||||
#- type: GuideHelp # DeltaV - nuh uh
|
||||
# guides:
|
||||
# - CorticalBorerGuide
|
||||
- type: Body
|
||||
#thermalVisibility: false # DeltaV - we dont got this (sad!)
|
||||
#- type: UniversalLanguageSpeaker # DeltaV - no language (sad!)
|
||||
|
||||
- type: entity
|
||||
parent: BaseItem
|
||||
id: CorticalBorerEgg
|
||||
name: cortical borer egg
|
||||
description: This egg is so nice and wet and soft...
|
||||
components:
|
||||
- type: Sprite
|
||||
sprite: _Mono/Mobs/Aliens/cortical_borer_egg.rsi
|
||||
state: icon
|
||||
- type: Item
|
||||
size: Tiny
|
||||
- type: GhostRole
|
||||
name: cortical-borer-ghostrole-name
|
||||
description: cortical-borer-ghostrole-desc
|
||||
rules: ghost-role-information-freeagent-rules
|
||||
requirements:
|
||||
- !type:OverallPlaytimeRequirement
|
||||
time: 36000 # 10 hrs
|
||||
mindRoles:
|
||||
- MindRoleGhostRoleFreeAgent
|
||||
- type: GhostRoleMobSpawner
|
||||
prototype: MobCorticalBorer
|
||||
- type: Damageable
|
||||
damageContainer: Biological
|
||||
- type: Destructible
|
||||
thresholds:
|
||||
- trigger:
|
||||
!type:DamageTrigger
|
||||
damage: 1
|
||||
behaviors:
|
||||
- !type:PlaySoundBehavior
|
||||
sound:
|
||||
collection: desecration
|
||||
- !type:DoActsBehavior
|
||||
acts: [ "Destruction" ]
|
||||
#- type: GuideHelp # DeltaV - nuh uh
|
||||
# guides:
|
||||
# - CorticalBorerGuide
|
||||
|
|
@ -0,0 +1,17 @@
|
|||
- type: entity
|
||||
parent: SurgeryBase
|
||||
id: SurgeryCorticalBorerRemoval
|
||||
name: Remove Cortical Borer
|
||||
description: Removal the Cortical Borer infestation from the body.
|
||||
categories: [ HideSpawnMenu ]
|
||||
components:
|
||||
- type: Surgery
|
||||
priority: -1
|
||||
requirement: SurgeryOpenIncision
|
||||
steps:
|
||||
- SurgeryStepSawBones
|
||||
- SurgeryStepClampInternalBleeders
|
||||
- SurgeryStepRemoveCorticalBorer
|
||||
- type: SurgeryCorticalBorerCondition
|
||||
- type: SurgeryPartCondition
|
||||
part: Head
|
||||
|
|
@ -0,0 +1,15 @@
|
|||
- type: entity
|
||||
parent: SurgeryStepBase
|
||||
id: SurgeryStepRemoveCorticalBorer
|
||||
name: Remove the Cortical Borer
|
||||
categories: [ HideSpawnMenu ]
|
||||
components:
|
||||
- type: SurgeryStep
|
||||
tool:
|
||||
- type: Tweezers
|
||||
duration: 8
|
||||
- type: Sprite
|
||||
sprite: _Shitmed/Objects/Specific/Medical/Surgery/hemostat.rsi
|
||||
state: hemostat
|
||||
- type: SurgeryStepRemoveCorticalBorer
|
||||
- type: SurgeryStepEmoteEffect
|
||||
|
|
@ -0,0 +1,7 @@
|
|||
# SPDX-FileCopyrightText: 2025 Coenx-flex
|
||||
#
|
||||
# SPDX-License-Identifier: AGPL-3.0-or-later
|
||||
|
||||
- type: statusEffect
|
||||
id: CorticalBorerProtection
|
||||
alwaysAllowed: true
|
||||
|
|
@ -74,4 +74,16 @@
|
|||
Slimes and spiders have no remarkable features, but will [color=cyan]infest the station[/color] from time to time regardless. Both will give chase and attack anything they see.
|
||||
- Slimes may [bold]deal extra cellular or poison damage[/bold], based upon their color. Water hurts them just as it would hurt a slime person.
|
||||
- Spiders have a venomous bite and can [bold]create webs[/bold] that are hard to move though. Webs are easily destroyed with a blade. They can also pry open doors, windoors, and airlocks.
|
||||
|
||||
|
||||
<!-- DeltaV - start cortical borer changes--># Cortical Borer
|
||||
|
||||
<Box>
|
||||
<GuideEntityEmbed Entity="MobCorticalBorer"/>
|
||||
</Box>
|
||||
Cortical Borers are a worm-like parasitic species that inhabit the sector. They burrow into one's brain and feed off the body. It's said they can speak to their host, often to try to persuade them to do certain things for unknown reasons.
|
||||
- As a Borer, you can view the health of your host, and inject them with harmful or helpful chemicals. When in a host, you regain chemicals over time, to a maximum of 250.
|
||||
- You can additionally [color=yellow]control[/color] your host for up to 40 seconds, during which time you can [color=yellow]lay an egg[/color] if you have at least 200 chemicals stored. Said egg can hatch into a new borer once it's ready...
|
||||
- A borer's abilities will temporarily stop working if the host ingests sugar. The borer can be removed from a host via surgery.
|
||||
<!-- DeltaV - end cortical borer changes-->
|
||||
</Document>
|
||||
|
|
|
|||
|
After Width: | Height: | Size: 131 B |
|
After Width: | Height: | Size: 120 B |
|
After Width: | Height: | Size: 142 B |
|
After Width: | Height: | Size: 139 B |
|
After Width: | Height: | Size: 135 B |
|
After Width: | Height: | Size: 141 B |
|
After Width: | Height: | Size: 142 B |
|
After Width: | Height: | Size: 137 B |
|
After Width: | Height: | Size: 134 B |
|
After Width: | Height: | Size: 144 B |
|
After Width: | Height: | Size: 265 B |
|
|
@ -0,0 +1,44 @@
|
|||
{
|
||||
"version": 1,
|
||||
"license": "CC-BY-SA-3.0",
|
||||
"copyright": "Created by EmoGarbage404",
|
||||
"size": {
|
||||
"x": 32,
|
||||
"y": 32
|
||||
},
|
||||
"states": [
|
||||
{
|
||||
"name": "base"
|
||||
},
|
||||
{
|
||||
"name": "0"
|
||||
},
|
||||
{
|
||||
"name": "1"
|
||||
},
|
||||
{
|
||||
"name": "2"
|
||||
},
|
||||
{
|
||||
"name": "3"
|
||||
},
|
||||
{
|
||||
"name": "4"
|
||||
},
|
||||
{
|
||||
"name": "5"
|
||||
},
|
||||
{
|
||||
"name": "6"
|
||||
},
|
||||
{
|
||||
"name": "7"
|
||||
},
|
||||
{
|
||||
"name": "8"
|
||||
},
|
||||
{
|
||||
"name": "9"
|
||||
}
|
||||
]
|
||||
}
|
||||
|
After Width: | Height: | Size: 631 B |
|
After Width: | Height: | Size: 670 B |
|
After Width: | Height: | Size: 835 B |
|
After Width: | Height: | Size: 309 B |
|
After Width: | Height: | Size: 583 B |
|
After Width: | Height: | Size: 810 B |
|
|
@ -0,0 +1,29 @@
|
|||
{
|
||||
"version": 1,
|
||||
"size": {
|
||||
"x": 32,
|
||||
"y": 32
|
||||
},
|
||||
"license": "CC-BY-SA-3.0",
|
||||
"copyright": "borer actions taken from Monkestation https://github.com/Monkestation/Monkestation2.0/pull/976, edited by Cojoke",
|
||||
"states": [
|
||||
{
|
||||
"name": "borer_chem"
|
||||
},
|
||||
{
|
||||
"name": "borer_infest"
|
||||
},
|
||||
{
|
||||
"name": "borer_bloodcheck"
|
||||
},
|
||||
{
|
||||
"name": "borer_eject"
|
||||
},
|
||||
{
|
||||
"name": "borer_egg"
|
||||
},
|
||||
{
|
||||
"name": "borer_control"
|
||||
}
|
||||
]
|
||||
}
|
||||
|
After Width: | Height: | Size: 1.2 KiB |
|
After Width: | Height: | Size: 696 B |
|
|
@ -0,0 +1,18 @@
|
|||
{
|
||||
"version": 1,
|
||||
"license": "CC-BY-NC-SA-3.0",
|
||||
"copyright": "Taken from Monkestation https://github.com/Monkestation/Monkestation2.0/pull/976",
|
||||
"size": {
|
||||
"x": 32,
|
||||
"y": 32
|
||||
},
|
||||
"states": [
|
||||
{
|
||||
"name": "cortical_borer",
|
||||
"directions": 4
|
||||
},
|
||||
{
|
||||
"name": "dead"
|
||||
}
|
||||
]
|
||||
}
|
||||
|
After Width: | Height: | Size: 309 B |
|
|
@ -0,0 +1,14 @@
|
|||
{
|
||||
"version": 1,
|
||||
"license": "CC-BY-NC-SA-3.0",
|
||||
"copyright": "Taken from Monkestation https://github.com/Monkestation/Monkestation2.0/pull/976",
|
||||
"size": {
|
||||
"x": 32,
|
||||
"y": 32
|
||||
},
|
||||
"states": [
|
||||
{
|
||||
"name": "icon"
|
||||
}
|
||||
]
|
||||
}
|
||||