mirror of
https://github.com/wega-team/ss14-wega.git
synced 2026-06-09 10:06:49 +02:00
Time to Nuke Vampire (#416)
* timetonukevampire * snap back to vampirity * firstfix * fixix xif
This commit is contained in:
@@ -0,0 +1,97 @@
|
||||
using Content.Shared.NullRod.Components;
|
||||
using Robust.Client.Graphics;
|
||||
using Robust.Client.Player;
|
||||
using Robust.Shared.Enums;
|
||||
using Robust.Shared.Prototypes;
|
||||
using Robust.Shared.Timing;
|
||||
|
||||
namespace Content.Client.NullRod;
|
||||
|
||||
public sealed class NullDamageOverlay : Overlay
|
||||
{
|
||||
[Dependency] private readonly IEntityManager _entityManager = default!;
|
||||
[Dependency] private readonly IPlayerManager _playerManager = default!;
|
||||
[Dependency] private readonly IPrototypeManager _prototypeManager = default!;
|
||||
[Dependency] private readonly IGameTiming _gameTiming = default!;
|
||||
|
||||
private static readonly ProtoId<ShaderPrototype> NullDamageShader = "NullDamage";
|
||||
public override OverlaySpace Space => OverlaySpace.ScreenSpace;
|
||||
public override bool RequestScreenTexture => true;
|
||||
private readonly ShaderInstance _shader;
|
||||
|
||||
private float _currentRatio;
|
||||
private float _targetRatio;
|
||||
|
||||
public NullDamageOverlay()
|
||||
{
|
||||
IoCManager.InjectDependencies(this);
|
||||
_shader = _prototypeManager.Index(NullDamageShader).InstanceUnique();
|
||||
_currentRatio = 0f;
|
||||
_targetRatio = 0f;
|
||||
}
|
||||
|
||||
protected override bool BeforeDraw(in OverlayDrawArgs args)
|
||||
{
|
||||
if (!_entityManager.TryGetComponent(_playerManager.LocalEntity, out EyeComponent? eyeComp))
|
||||
return false;
|
||||
|
||||
if (args.Viewport.Eye != eyeComp.Eye)
|
||||
return false;
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
protected override void Draw(in OverlayDrawArgs args)
|
||||
{
|
||||
if (ScreenTexture == null)
|
||||
return;
|
||||
|
||||
var playerEntity = _playerManager.LocalEntity;
|
||||
var frameTime = (float)_gameTiming.CurTime.TotalSeconds;
|
||||
|
||||
if (playerEntity == null || !_entityManager.TryGetComponent<NullDamageComponent>(playerEntity, out var nullDamage))
|
||||
{
|
||||
_targetRatio = 0f;
|
||||
UpdateRatio(frameTime);
|
||||
_shader.SetParameter("SCREEN_TEXTURE", ScreenTexture);
|
||||
_shader.SetParameter("ratio", _currentRatio);
|
||||
|
||||
var handle = args.ScreenHandle;
|
||||
handle.UseShader(_shader);
|
||||
handle.DrawRect(args.ViewportBounds, Color.White);
|
||||
handle.UseShader(null);
|
||||
return;
|
||||
}
|
||||
|
||||
var rawRatio = nullDamage.MaxNullDamage > 0
|
||||
? (float)(nullDamage.NullDamage / nullDamage.MaxNullDamage)
|
||||
: 0f;
|
||||
|
||||
_targetRatio = Math.Clamp(rawRatio, 0f, 0.85f);
|
||||
UpdateRatio(frameTime);
|
||||
|
||||
_shader.SetParameter("SCREEN_TEXTURE", ScreenTexture);
|
||||
_shader.SetParameter("ratio", _currentRatio);
|
||||
|
||||
var drawHandle = args.ScreenHandle;
|
||||
drawHandle.UseShader(_shader);
|
||||
drawHandle.DrawRect(args.ViewportBounds, Color.White);
|
||||
drawHandle.UseShader(null);
|
||||
}
|
||||
|
||||
private void UpdateRatio(float frameTime)
|
||||
{
|
||||
const float maxChangePerSecond = 3f;
|
||||
var maxDelta = maxChangePerSecond * frameTime;
|
||||
|
||||
var difference = _targetRatio - _currentRatio;
|
||||
var delta = Math.Clamp(difference, -maxDelta, maxDelta);
|
||||
|
||||
float smoothingSpeed = 5f;
|
||||
delta *= smoothingSpeed * frameTime;
|
||||
delta = Math.Clamp(delta, -maxDelta, maxDelta);
|
||||
|
||||
_currentRatio += delta;
|
||||
_currentRatio = Math.Clamp(_currentRatio, 0f, 0.85f);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,49 @@
|
||||
using Content.Shared.NullRod.Components;
|
||||
using Robust.Client.Graphics;
|
||||
using Robust.Client.Player;
|
||||
using Robust.Shared.Player;
|
||||
|
||||
namespace Content.Client.NullRod;
|
||||
|
||||
public sealed class NullDamageSystem : EntitySystem
|
||||
{
|
||||
[Dependency] private readonly IPlayerManager _player = default!;
|
||||
[Dependency] private readonly IOverlayManager _overlayMan = default!;
|
||||
|
||||
private NullDamageOverlay _overlay = default!;
|
||||
|
||||
public override void Initialize()
|
||||
{
|
||||
base.Initialize();
|
||||
|
||||
SubscribeLocalEvent<NullDamageComponent, ComponentInit>(OnInit);
|
||||
SubscribeLocalEvent<NullDamageComponent, ComponentShutdown>(OnShutdown);
|
||||
|
||||
SubscribeLocalEvent<NullDamageComponent, LocalPlayerAttachedEvent>(OnPlayerAttached);
|
||||
SubscribeLocalEvent<NullDamageComponent, LocalPlayerDetachedEvent>(OnPlayerDetached);
|
||||
|
||||
_overlay = new();
|
||||
}
|
||||
|
||||
private void OnPlayerAttached(EntityUid uid, NullDamageComponent component, LocalPlayerAttachedEvent args)
|
||||
{
|
||||
_overlayMan.AddOverlay(_overlay);
|
||||
}
|
||||
|
||||
private void OnPlayerDetached(EntityUid uid, NullDamageComponent component, LocalPlayerDetachedEvent args)
|
||||
{
|
||||
_overlayMan.RemoveOverlay(_overlay);
|
||||
}
|
||||
|
||||
private void OnInit(EntityUid uid, NullDamageComponent component, ComponentInit args)
|
||||
{
|
||||
if (_player.LocalEntity == uid)
|
||||
_overlayMan.AddOverlay(_overlay);
|
||||
}
|
||||
|
||||
private void OnShutdown(EntityUid uid, NullDamageComponent component, ComponentShutdown args)
|
||||
{
|
||||
if (_player.LocalEntity == uid)
|
||||
_overlayMan.RemoveOverlay(_overlay);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,41 @@
|
||||
using Content.Client.Eui;
|
||||
using Content.Shared.Eui;
|
||||
using Content.Shared.Vampire;
|
||||
using JetBrains.Annotations;
|
||||
|
||||
namespace Content.Client._Wega.Vampire.Ui;
|
||||
|
||||
[UsedImplicitly]
|
||||
public sealed class DissectSelectionEui : BaseEui
|
||||
{
|
||||
private DissectSelectionMenu _menu;
|
||||
|
||||
public DissectSelectionEui()
|
||||
{
|
||||
_menu = new DissectSelectionMenu();
|
||||
_menu.OnOrganSelected += selectedOrgan =>
|
||||
{
|
||||
SendMessage(new DissectOrganSelectedMessage(selectedOrgan));
|
||||
_menu.Close();
|
||||
};
|
||||
}
|
||||
|
||||
public override void Opened()
|
||||
{
|
||||
_menu.OpenCentered();
|
||||
}
|
||||
|
||||
public override void Closed()
|
||||
{
|
||||
_menu.Close();
|
||||
}
|
||||
|
||||
public override void HandleState(EuiStateBase state)
|
||||
{
|
||||
base.HandleState(state);
|
||||
if (state is not DissectSelectionEuiState dissectState)
|
||||
return;
|
||||
|
||||
_menu.Populate(dissectState);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,5 @@
|
||||
<ui:RadialMenu xmlns="https://spacestation14.io"
|
||||
xmlns:ui="clr-namespace:Content.Client.UserInterface.Controls"
|
||||
MinSize="450 450">
|
||||
<ui:RadialContainer Name="MainContainer" VerticalExpand="True" HorizontalExpand="True" InitialRadius="128"/>
|
||||
</ui:RadialMenu>
|
||||
@@ -0,0 +1,72 @@
|
||||
using System.Numerics;
|
||||
using Content.Client.UserInterface.Controls;
|
||||
using Content.Shared.Vampire;
|
||||
using Robust.Client.AutoGenerated;
|
||||
using Robust.Client.UserInterface.Controls;
|
||||
using Robust.Client.UserInterface.XAML;
|
||||
using Robust.Shared.Prototypes;
|
||||
|
||||
namespace Content.Client._Wega.Vampire.Ui;
|
||||
|
||||
[GenerateTypedNameReferences]
|
||||
public sealed partial class DissectSelectionMenu : RadialMenu
|
||||
{
|
||||
[Dependency] private readonly IEntityManager _entityManager = default!;
|
||||
[Dependency] private readonly IPrototypeManager _prototype = default!;
|
||||
|
||||
public event Action<NetEntity>? OnOrganSelected;
|
||||
|
||||
public DissectSelectionMenu()
|
||||
{
|
||||
RobustXamlLoader.Load(this);
|
||||
IoCManager.InjectDependencies(this);
|
||||
}
|
||||
|
||||
public void Populate(DissectSelectionEuiState state)
|
||||
{
|
||||
MainContainer.RemoveAllChildren();
|
||||
foreach (var organNetEntity in state.AvailableOrgans)
|
||||
{
|
||||
var organEntity = _entityManager.GetEntity(organNetEntity);
|
||||
if (!_entityManager.TryGetComponent<MetaDataComponent>(organEntity, out var metaData))
|
||||
continue;
|
||||
|
||||
var prototypeId = metaData.EntityPrototype?.ID ?? string.Empty;
|
||||
var button = new RadialMenuButton
|
||||
{
|
||||
ToolTip = metaData.EntityName,
|
||||
SetSize = new Vector2(64, 64),
|
||||
StyleClasses = { "RadialMenuButton" }
|
||||
};
|
||||
|
||||
// Try to get icon for organ
|
||||
if (!string.IsNullOrEmpty(prototypeId) && _prototype.TryIndex(prototypeId, out var prototype))
|
||||
{
|
||||
var entityView = new EntityPrototypeView
|
||||
{
|
||||
Scale = new Vector2(2, 2),
|
||||
SetSize = new Vector2(64, 64),
|
||||
Margin = new Thickness(4)
|
||||
};
|
||||
entityView.SetPrototype(prototype.ID);
|
||||
button.AddChild(entityView);
|
||||
}
|
||||
else
|
||||
{
|
||||
// Fallback to text if no icon
|
||||
var label = new Label
|
||||
{
|
||||
Text = metaData.EntityName,
|
||||
Align = Label.AlignMode.Center,
|
||||
HorizontalExpand = true,
|
||||
VerticalExpand = true
|
||||
};
|
||||
button.AddChild(label);
|
||||
}
|
||||
|
||||
var capturedOrgan = organNetEntity;
|
||||
button.OnPressed += _ => OnOrganSelected?.Invoke(capturedOrgan);
|
||||
MainContainer.AddChild(button);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,43 +0,0 @@
|
||||
using Content.Client.UserInterface.Controls;
|
||||
using Content.Shared.Vampire;
|
||||
using Robust.Client.AutoGenerated;
|
||||
using Robust.Client.UserInterface.XAML;
|
||||
using Robust.Shared.Player;
|
||||
|
||||
namespace Content.Client.Select.Class.UI;
|
||||
|
||||
[GenerateTypedNameReferences]
|
||||
public sealed partial class SelectClassMenu : RadialMenu
|
||||
{
|
||||
[Dependency] private readonly IEntityManager _entityManager = default!;
|
||||
[Dependency] private readonly IEntityNetworkManager _entityNetworkManager = default!;
|
||||
[Dependency] private readonly ISharedPlayerManager _playerManager = default!;
|
||||
|
||||
public event Action<string>? OnSelectClass;
|
||||
|
||||
public SelectClassMenu()
|
||||
{
|
||||
RobustXamlLoader.Load(this);
|
||||
IoCManager.InjectDependencies(this);
|
||||
|
||||
InitializeButtons();
|
||||
}
|
||||
|
||||
private void InitializeButtons()
|
||||
{
|
||||
HemomancerButton.OnButtonUp += _ => HandleClassSelection("Hemomancer");
|
||||
UmbraeButton.OnButtonUp += _ => HandleClassSelection("Umbrae");
|
||||
GargantuaButton.OnButtonUp += _ => HandleClassSelection("Gargantua");
|
||||
DantalionButton.OnButtonUp += _ => HandleClassSelection("Dantalion");
|
||||
//BestiaButton.OnButtonUp += _ => HandleClassSelection("Bestia");
|
||||
}
|
||||
|
||||
private void HandleClassSelection(string className)
|
||||
{
|
||||
OnSelectClass?.Invoke(className);
|
||||
var netEntity = _entityManager.GetNetEntity(_playerManager.LocalSession?.AttachedEntity ?? EntityUid.Invalid);
|
||||
_entityNetworkManager.SendSystemNetworkMessage(new VampireSelectClassMenuClosedEvent(netEntity, className));
|
||||
Close();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,46 +0,0 @@
|
||||
using Content.Client.Select.Class.UI;
|
||||
using Content.Shared.Vampire;
|
||||
using Robust.Client.Player;
|
||||
using Robust.Client.UserInterface;
|
||||
using Robust.Client.UserInterface.Controllers;
|
||||
|
||||
namespace Content.Client.UserInterface.Systems.Select.Class
|
||||
{
|
||||
public sealed class SelectClassUIController : UIController
|
||||
{
|
||||
[Dependency] private readonly IUserInterfaceManager _uiManager = default!;
|
||||
[Dependency] private readonly IEntityManager _entityManager = default!;
|
||||
|
||||
private SelectClassMenu? _menu;
|
||||
|
||||
public override void Initialize()
|
||||
{
|
||||
base.Initialize();
|
||||
SubscribeNetworkEvent<SelectClassPressedEvent>(OnSelectClassMenuReceived);
|
||||
}
|
||||
|
||||
private void OnSelectClassMenuReceived(SelectClassPressedEvent args, EntitySessionEventArgs eventArgs)
|
||||
{
|
||||
var session = IoCManager.Resolve<IPlayerManager>().LocalSession;
|
||||
var userEntity = _entityManager.GetEntity(args.Uid);
|
||||
if (session?.AttachedEntity.HasValue == true && session.AttachedEntity.Value == userEntity)
|
||||
{
|
||||
if (_menu is null)
|
||||
{
|
||||
_menu = _uiManager.CreateWindow<SelectClassMenu>();
|
||||
_menu.OnClose += OnMenuClosed;
|
||||
_menu.OpenCentered();
|
||||
}
|
||||
else
|
||||
{
|
||||
_menu.OpenCentered();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void OnMenuClosed()
|
||||
{
|
||||
_menu = null;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,68 @@
|
||||
<controls:FancyWindow xmlns="https://spacestation14.io"
|
||||
xmlns:controls="clr-namespace:Content.Client.UserInterface.Controls"
|
||||
xmlns:gfx="clr-namespace:Robust.Client.Graphics;assembly=Robust.Client"
|
||||
Title="{Loc 'vampire-trophies-ui-title'}"
|
||||
MinSize="550 680">
|
||||
|
||||
<PanelContainer Name="WindowBackground">
|
||||
<PanelContainer.PanelOverride>
|
||||
<gfx:StyleBoxFlat BackgroundColor="#0a0a0f" />
|
||||
</PanelContainer.PanelOverride>
|
||||
</PanelContainer>
|
||||
|
||||
<ScrollContainer VerticalExpand="True">
|
||||
<BoxContainer Orientation="Vertical" Margin="10">
|
||||
|
||||
<!-- Trophies -->
|
||||
<Label Text="{Loc 'vampire-trophies-ui-trophies'}" StyleClasses="LabelHeading"
|
||||
HorizontalAlignment="Center" Margin="0 0 0 5" FontColorOverride="#c92a2a"/>
|
||||
<PanelContainer Margin="0 0 0 10">
|
||||
<PanelContainer.PanelOverride>
|
||||
<gfx:StyleBoxFlat BackgroundColor="#4cc9f0" ContentMarginBottomOverride="2" ContentMarginLeftOverride="2"/>
|
||||
</PanelContainer.PanelOverride>
|
||||
</PanelContainer>
|
||||
|
||||
<PanelContainer StyleClasses="AngleRect" Margin="0 0 0 10">
|
||||
<PanelContainer.PanelOverride>
|
||||
<gfx:StyleBoxFlat BackgroundColor="#14141f" BorderColor="#c92a2a" BorderThickness="2" />
|
||||
</PanelContainer.PanelOverride>
|
||||
<GridContainer Name="OrganGrid" Columns="6" HorizontalExpand="True" Margin="5"/>
|
||||
</PanelContainer>
|
||||
|
||||
<!-- Vampire Passives -->
|
||||
<Label Text="{Loc 'vampire-trophies-ui-passives'}" StyleClasses="LabelHeading"
|
||||
HorizontalAlignment="Center" Margin="0 15 0 5" FontColorOverride="#c92a2a"/>
|
||||
<PanelContainer Margin="0 0 0 10">
|
||||
<PanelContainer.PanelOverride>
|
||||
<gfx:StyleBoxFlat BackgroundColor="#4cc9f0" ContentMarginBottomOverride="2" ContentMarginLeftOverride="2"/>
|
||||
</PanelContainer.PanelOverride>
|
||||
</PanelContainer>
|
||||
|
||||
<PanelContainer StyleClasses="AngleRect" Margin="0 0 0 10">
|
||||
<PanelContainer.PanelOverride>
|
||||
<gfx:StyleBoxFlat BackgroundColor="#14141f" BorderColor="#c92a2a" BorderThickness="2" />
|
||||
</PanelContainer.PanelOverride>
|
||||
<GridContainer Name="PassiveGrid" Columns="2" HSeparationOverride="15" VSeparationOverride="15"
|
||||
HorizontalAlignment="Center" HorizontalExpand="True" Margin="5"/>
|
||||
</PanelContainer>
|
||||
|
||||
<!-- Abilities -->
|
||||
<Label Text="{Loc 'vampire-trophies-ui-abilities'}" StyleClasses="LabelHeading"
|
||||
HorizontalAlignment="Center" Margin="0 15 0 5" FontColorOverride="#c92a2a"/>
|
||||
<PanelContainer Margin="0 0 0 10">
|
||||
<PanelContainer.PanelOverride>
|
||||
<gfx:StyleBoxFlat BackgroundColor="#4cc9f0" ContentMarginBottomOverride="2" ContentMarginLeftOverride="2"/>
|
||||
</PanelContainer.PanelOverride>
|
||||
</PanelContainer>
|
||||
|
||||
<PanelContainer StyleClasses="AngleRect" Margin="0 0 0 10">
|
||||
<PanelContainer.PanelOverride>
|
||||
<gfx:StyleBoxFlat BackgroundColor="#14141f" BorderColor="#c92a2a" BorderThickness="2" />
|
||||
</PanelContainer.PanelOverride>
|
||||
<GridContainer Name="AbilitiesContainer" Columns="4" HorizontalAlignment="Center"
|
||||
HorizontalExpand="True" Margin="5"/>
|
||||
</PanelContainer>
|
||||
|
||||
</BoxContainer>
|
||||
</ScrollContainer>
|
||||
</controls:FancyWindow>
|
||||
@@ -0,0 +1,155 @@
|
||||
using System.Numerics;
|
||||
using System.Text;
|
||||
using System.Text.RegularExpressions;
|
||||
using Content.Client.UserInterface.Controls;
|
||||
using Content.Shared.Actions.Components;
|
||||
using Content.Shared.Vampire;
|
||||
using Robust.Client.AutoGenerated;
|
||||
using Robust.Client.GameObjects;
|
||||
using Robust.Client.UserInterface;
|
||||
using Robust.Client.UserInterface.Controls;
|
||||
using Robust.Client.UserInterface.XAML;
|
||||
using Robust.Shared.Prototypes;
|
||||
|
||||
namespace Content.Client._Wega.Vampire.Ui;
|
||||
|
||||
[GenerateTypedNameReferences]
|
||||
public sealed partial class TrophiesMenu : FancyWindow
|
||||
{
|
||||
[Dependency] private readonly IEntityManager _entityManager = default!;
|
||||
private readonly SpriteSystem _spriteSystem;
|
||||
|
||||
private static readonly Dictionary<BestiaOrganType, EntProtoId> DefaultOrganProto = new()
|
||||
{
|
||||
{ BestiaOrganType.Heart, "OrganHumanHeart" },
|
||||
{ BestiaOrganType.Lungs, "OrganHumanLungs" },
|
||||
{ BestiaOrganType.Liver, "OrganHumanLiver" },
|
||||
{ BestiaOrganType.Kidneys, "OrganHumanKidneys" },
|
||||
{ BestiaOrganType.Eyes, "OrganHumanEyes" },
|
||||
{ BestiaOrganType.Stomach, "OrganHumanStomach" }
|
||||
};
|
||||
|
||||
private static readonly Regex ColorTagRegex = new Regex(
|
||||
@"\[color=[^\]]+\]|\[/color\]",
|
||||
RegexOptions.Compiled | RegexOptions.CultureInvariant
|
||||
);
|
||||
|
||||
public TrophiesMenu()
|
||||
{
|
||||
RobustXamlLoader.Load(this);
|
||||
IoCManager.InjectDependencies(this);
|
||||
|
||||
_spriteSystem = _entityManager.System<SpriteSystem>();
|
||||
}
|
||||
|
||||
public void Populate(TrophiesEuiState state)
|
||||
{
|
||||
OrganGrid.RemoveAllChildren();
|
||||
foreach (var organ in state.Organs)
|
||||
{
|
||||
var widget = CreateOrganWidget(organ);
|
||||
OrganGrid.AddChild(widget);
|
||||
}
|
||||
|
||||
PassiveGrid.RemoveAllChildren();
|
||||
foreach (var bonus in state.PassiveBonuses)
|
||||
{
|
||||
var label = new Label
|
||||
{
|
||||
Text = $"{bonus.Name}: {bonus.Value}" + (bonus.IsMaxed ? $" {Loc.GetString($"vampire-trophies-ui-max")}" : ""),
|
||||
HorizontalExpand = true,
|
||||
Margin = new Thickness(0, 2)
|
||||
};
|
||||
if (bonus.ValueColor != null) label.ModulateSelfOverride = bonus.ValueColor.Value;
|
||||
PassiveGrid.AddChild(label);
|
||||
}
|
||||
|
||||
AbilitiesContainer.RemoveAllChildren();
|
||||
foreach (var ability in state.Abilities)
|
||||
{
|
||||
var button = new TextureButton
|
||||
{
|
||||
ToolTip = BuildTooltip(ability),
|
||||
Scale = new Vector2(3f, 3f),
|
||||
VerticalExpand = true,
|
||||
Margin = new Thickness(5)
|
||||
};
|
||||
|
||||
var entity = _entityManager.GetEntity(ability.Action);
|
||||
if (_entityManager.TryGetComponent<ActionComponent>(entity, out var action) && action.Icon != null)
|
||||
button.TextureNormal = _spriteSystem.Frame0(action.Icon);
|
||||
|
||||
AbilitiesContainer.AddChild(button);
|
||||
}
|
||||
}
|
||||
|
||||
private Control CreateOrganWidget(OrganDisplayInfo organ)
|
||||
{
|
||||
var container = new BoxContainer
|
||||
{
|
||||
Orientation = BoxContainer.LayoutOrientation.Vertical,
|
||||
HorizontalExpand = true,
|
||||
VerticalExpand = true,
|
||||
Margin = new Thickness(5)
|
||||
};
|
||||
|
||||
var iconProto = DefaultOrganProto.GetValueOrDefault(organ.Type, "OrganHumanHeart");
|
||||
|
||||
string? actualProto = null;
|
||||
if (organ.PreviewEntity != null)
|
||||
{
|
||||
var entity = _entityManager.GetEntity(organ.PreviewEntity.Value);
|
||||
if (_entityManager.TryGetComponent<MetaDataComponent>(entity, out var meta))
|
||||
actualProto = meta.EntityPrototype?.ID;
|
||||
}
|
||||
if (string.IsNullOrEmpty(actualProto))
|
||||
actualProto = iconProto;
|
||||
|
||||
var icon = new EntityPrototypeView
|
||||
{
|
||||
Scale = new Vector2(2f, 2f),
|
||||
HorizontalAlignment = HAlignment.Center,
|
||||
VerticalAlignment = VAlignment.Center,
|
||||
Margin = new Thickness(0, 0, 0, 2)
|
||||
};
|
||||
icon.SetPrototype(actualProto);
|
||||
|
||||
var countLabel = new Label
|
||||
{
|
||||
Text = $"{organ.Count}/{organ.MaxCount}"
|
||||
+ $"\n{Loc.GetString($"vampire-bestia-{organ.Type.ToString().ToLower()}").ToUpper()}",
|
||||
Align = Label.AlignMode.Center,
|
||||
HorizontalExpand = true,
|
||||
ModulateSelfOverride = organ.CountColor
|
||||
};
|
||||
|
||||
container.AddChild(icon);
|
||||
container.AddChild(countLabel);
|
||||
return container;
|
||||
}
|
||||
|
||||
private string BuildTooltip(AbilityDisplayInfo ability)
|
||||
{
|
||||
var sb = new StringBuilder();
|
||||
|
||||
var cleanName = ColorTagRegex.Replace(ability.Name, string.Empty);
|
||||
|
||||
sb.AppendLine(cleanName);
|
||||
sb.AppendLine();
|
||||
|
||||
if (ability.Bonuses.Count == 0)
|
||||
{
|
||||
sb.AppendLine(Loc.GetString("vampire-trophies-ui-no-bonuses"));
|
||||
}
|
||||
else
|
||||
{
|
||||
foreach (var bonus in ability.Bonuses)
|
||||
{
|
||||
var maxTag = bonus.IsMaxed ? $" {Loc.GetString($"vampire-trophies-ui-max")}" : "";
|
||||
sb.AppendLine($"• {bonus.OrganType}: {bonus.Description}{maxTag}");
|
||||
}
|
||||
}
|
||||
|
||||
return sb.ToString();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,36 @@
|
||||
using Content.Client.Eui;
|
||||
using Content.Shared.Eui;
|
||||
using Content.Shared.Vampire;
|
||||
using JetBrains.Annotations;
|
||||
|
||||
namespace Content.Client._Wega.Vampire.Ui;
|
||||
|
||||
[UsedImplicitly]
|
||||
public sealed class TrophiesMenuEui : BaseEui
|
||||
{
|
||||
private TrophiesMenu _menu;
|
||||
|
||||
public TrophiesMenuEui()
|
||||
{
|
||||
_menu = new TrophiesMenu();
|
||||
}
|
||||
|
||||
public override void Opened()
|
||||
{
|
||||
_menu.OpenCenteredLeft();
|
||||
}
|
||||
|
||||
public override void Closed()
|
||||
{
|
||||
_menu.Close();
|
||||
}
|
||||
|
||||
public override void HandleState(EuiStateBase state)
|
||||
{
|
||||
base.HandleState(state);
|
||||
if (state is not TrophiesEuiState trophiesState)
|
||||
return;
|
||||
|
||||
_menu.Populate(trophiesState);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,31 @@
|
||||
using Content.Client.Eui;
|
||||
using Content.Shared.Vampire;
|
||||
using JetBrains.Annotations;
|
||||
|
||||
namespace Content.Client._Wega.Vampire.Ui;
|
||||
|
||||
[UsedImplicitly]
|
||||
public sealed class VampireClassSelectionEui : BaseEui
|
||||
{
|
||||
private readonly VampireClassSelectionMenu _menu;
|
||||
|
||||
public VampireClassSelectionEui()
|
||||
{
|
||||
_menu = new VampireClassSelectionMenu();
|
||||
_menu.OnClassSelected += className =>
|
||||
{
|
||||
SendMessage(new VampireClassSelectedMessage(className));
|
||||
_menu.Close();
|
||||
};
|
||||
}
|
||||
|
||||
public override void Opened()
|
||||
{
|
||||
_menu.OpenCentered();
|
||||
}
|
||||
|
||||
public override void Closed()
|
||||
{
|
||||
_menu.Close();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,23 @@
|
||||
using Content.Client.UserInterface.Controls;
|
||||
using Content.Shared.Vampire;
|
||||
using Robust.Client.AutoGenerated;
|
||||
using Robust.Client.UserInterface.XAML;
|
||||
|
||||
namespace Content.Client._Wega.Vampire.Ui;
|
||||
|
||||
[GenerateTypedNameReferences]
|
||||
public sealed partial class VampireClassSelectionMenu : RadialMenu
|
||||
{
|
||||
public event Action<VampireClassEnum>? OnClassSelected;
|
||||
|
||||
public VampireClassSelectionMenu()
|
||||
{
|
||||
RobustXamlLoader.Load(this);
|
||||
|
||||
HemomancerButton.OnButtonUp += _ => OnClassSelected?.Invoke(VampireClassEnum.Hemomancer);
|
||||
UmbraeButton.OnButtonUp += _ => OnClassSelected?.Invoke(VampireClassEnum.Umbrae);
|
||||
GargantuaButton.OnButtonUp += _ => OnClassSelected?.Invoke(VampireClassEnum.Gargantua);
|
||||
DantalionButton.OnButtonUp += _ => OnClassSelected?.Invoke(VampireClassEnum.Dantalion);
|
||||
BestiaButton.OnButtonUp += _ => OnClassSelected?.Invoke(VampireClassEnum.Bestia);
|
||||
}
|
||||
}
|
||||
+8
-12
@@ -1,40 +1,36 @@
|
||||
<ui:RadialMenu xmlns="https://spacestation14.io"
|
||||
xmlns:ui="clr-namespace:Content.Client.UserInterface.Controls"
|
||||
xmlns:local="clr-namespace:Content.Client.Select.Class.UI;assembly=Content.Client"
|
||||
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
|
||||
x:Class="Content.Client.Select.Class.UI.SelectClassMenu"
|
||||
BackButtonStyleClass="RadialMenuBackButton"
|
||||
CloseButtonStyleClass="RadialMenuCloseButton"
|
||||
VerticalExpand="True"
|
||||
HorizontalExpand="True"
|
||||
VerticalExpand="True" HorizontalExpand="True"
|
||||
MinSize="450 450">
|
||||
|
||||
<!-- Main Radial Menu Container -->
|
||||
<ui:RadialContainer Name="Main" VerticalExpand="True" HorizontalExpand="True" InitialRadius="64" ReserveSpaceForHiddenChildren="False">
|
||||
<ui:RadialContainer Name="Main" VerticalExpand="True" HorizontalExpand="True" InitialRadius="128" ReserveSpaceForHiddenChildren="False">
|
||||
<!-- Button 1: Hemomancer -->
|
||||
<ui:RadialMenuButton Name="HemomancerButton" StyleClasses="RadialMenuButton" SetSize="64 64" ToolTip="{Loc 'select-class-hemomancer'}" TargetLayerControlName="Hemomancer">
|
||||
<ui:RadialMenuButton Name="HemomancerButton" StyleClasses="RadialMenuButton" SetSize="64 64" ToolTip="{Loc 'select-class-hemomancer'}">
|
||||
<TextureRect VerticalAlignment="Center" HorizontalAlignment="Center" TextureScale="2 2" TexturePath="/Textures/_Wega/Interface/Actions/claws.png"/>
|
||||
</ui:RadialMenuButton>
|
||||
|
||||
<!-- Button 2: Umbrae -->
|
||||
<ui:RadialMenuButton Name="UmbraeButton" StyleClasses="RadialMenuButton" SetSize="64 64" ToolTip="{Loc 'select-class-umbrae'}" TargetLayerControlName="Umbrae">
|
||||
<ui:RadialMenuButton Name="UmbraeButton" StyleClasses="RadialMenuButton" SetSize="64 64" ToolTip="{Loc 'select-class-umbrae'}">
|
||||
<TextureRect VerticalAlignment="Center" HorizontalAlignment="Center" TextureScale="2 2" TexturePath="/Textures/_Wega/Interface/Actions/cloak.png"/>
|
||||
</ui:RadialMenuButton>
|
||||
|
||||
<!-- Button 3: Gargantua -->
|
||||
<ui:RadialMenuButton Name="GargantuaButton" StyleClasses="RadialMenuButton" SetSize="64 64" ToolTip="{Loc 'select-class-gargantua'}" TargetLayerControlName="Gargantua">
|
||||
<ui:RadialMenuButton Name="GargantuaButton" StyleClasses="RadialMenuButton" SetSize="64 64" ToolTip="{Loc 'select-class-gargantua'}">
|
||||
<TextureRect VerticalAlignment="Center" HorizontalAlignment="Center" TextureScale="2 2" TexturePath="/Textures/_Wega/Interface/Actions/swell.png"/>
|
||||
</ui:RadialMenuButton>
|
||||
|
||||
<!-- Button 4: Dantalion -->
|
||||
<ui:RadialMenuButton Name="DantalionButton" StyleClasses="RadialMenuButton" SetSize="64 64" ToolTip="{Loc 'select-class-dantalion'}" TargetLayerControlName="Dantalion">
|
||||
<ui:RadialMenuButton Name="DantalionButton" StyleClasses="RadialMenuButton" SetSize="64 64" ToolTip="{Loc 'select-class-dantalion'}">
|
||||
<TextureRect VerticalAlignment="Center" HorizontalAlignment="Center" TextureScale="2 2" TexturePath="/Textures/_Wega/Interface/Actions/enthrall.png"/>
|
||||
</ui:RadialMenuButton>
|
||||
|
||||
<!-- Button 5: Bestia -->
|
||||
<!-- <ui:RadialMenuButton Name="BestiaButton" StyleClasses="RadialMenuButton" SetSize="64 64" ToolTip="{Loc 'select-class-bestia'}" TargetLayerControlName="Bestia">
|
||||
<ui:RadialMenuButton Name="BestiaButton" StyleClasses="RadialMenuButton" SetSize="64 64" ToolTip="{Loc 'select-class-bestia'}">
|
||||
<TextureRect VerticalAlignment="Center" HorizontalAlignment="Center" TextureScale="2 2" TexturePath="/Textures/_Wega/Interface/Actions/rush.png"/>
|
||||
</ui:RadialMenuButton> -->
|
||||
</ui:RadialMenuButton>
|
||||
|
||||
</ui:RadialContainer>
|
||||
|
||||
@@ -1,5 +1,8 @@
|
||||
using Content.Client.Administration.Managers;
|
||||
using Content.Client.Alerts;
|
||||
using Content.Client.Movement.Systems;
|
||||
using Content.Client.Ghost;
|
||||
using Content.Shared.Administration;
|
||||
using Content.Shared.StatusIcon;
|
||||
using Content.Shared.StatusIcon.Components;
|
||||
using Content.Shared.Vampire;
|
||||
using Content.Shared.Vampire.Components;
|
||||
@@ -11,37 +14,53 @@ namespace Content.Client.Vampire;
|
||||
|
||||
public sealed class VampireSystem : SharedVampireSystem
|
||||
{
|
||||
[Dependency] private readonly IPrototypeManager _prototype = default!;
|
||||
[Dependency] private readonly GhostSystem? _ghost = default;
|
||||
[Dependency] private readonly IClientAdminManager _admin = default!;
|
||||
[Dependency] private readonly IPlayerManager _playerManager = default!;
|
||||
[Dependency] private readonly SharedEyeSystem _eye = default!;
|
||||
[Dependency] private readonly IEntityManager _entityManager = default!;
|
||||
[Dependency] private readonly IPrototypeManager _prototype = default!;
|
||||
[Dependency] private readonly SpriteSystem _sprite = default!;
|
||||
|
||||
public override void Initialize()
|
||||
{
|
||||
base.Initialize();
|
||||
|
||||
SubscribeNetworkEvent<VampireToggleFovEvent>(OnToggleFoV);
|
||||
SubscribeLocalEvent<VampireComponent, GetStatusIconsEvent>(GetVampireIcons);
|
||||
SubscribeLocalEvent<ThrallComponent, GetStatusIconsEvent>(GetThrallIcons);
|
||||
SubscribeLocalEvent<VampireComponent, UpdateAlertSpriteEvent>(OnUpdateAlert);
|
||||
|
||||
SubscribeLocalEvent<VampireDiablerieComponent, ComponentStartup>(OnDiablerieStartup);
|
||||
SubscribeLocalEvent<VampireDiablerieComponent, ComponentShutdown>(OnDiablerieShutdown);
|
||||
SubscribeLocalEvent<VampireDiablerieComponent, AfterAutoHandleStateEvent>(OnDiablerieStateUpdated);
|
||||
}
|
||||
|
||||
private void OnToggleFoV(VampireToggleFovEvent args)
|
||||
{
|
||||
var userEntity = _entityManager.GetEntity(args.User);
|
||||
var eyeComponent = _entityManager.GetComponent<EyeComponent>(userEntity);
|
||||
if (userEntity == _playerManager.LocalEntity)
|
||||
{
|
||||
eyeComponent.NetSyncEnabled = false;
|
||||
_eye.SetDrawFov(userEntity, args.Enabled, eyeComponent);
|
||||
}
|
||||
}
|
||||
|
||||
// Okey, let's go
|
||||
private void GetVampireIcons(Entity<VampireComponent> ent, ref GetStatusIconsEvent args)
|
||||
{
|
||||
var iconPrototype = _prototype.Index(ent.Comp.StatusIcon);
|
||||
args.StatusIcons.Add(iconPrototype);
|
||||
// If the local user is an admin in the ghost?
|
||||
if (_admin.HasFlag(AdminFlags.Admin) && _ghost is { IsGhost: true })
|
||||
{
|
||||
ShowIcon(_prototype.Index(ent.Comp.StatusIcon), ref args);
|
||||
return;
|
||||
}
|
||||
|
||||
// --- Admins ignore this above and see all vampires ---
|
||||
// If he's not the owner of the thralls, we will not see the icon
|
||||
if (!HasComp<ThrallOwnerComponent>(ent))
|
||||
return;
|
||||
|
||||
var localPlayer = _playerManager.LocalEntity;
|
||||
if (localPlayer == ent.Owner) // Is that you?
|
||||
{
|
||||
ShowIcon(_prototype.Index(ent.Comp.StatusIcon), ref args);
|
||||
return;
|
||||
}
|
||||
|
||||
// If we're a vampire's servant?
|
||||
if (TryComp<ThrallComponent>(localPlayer, out var thrall) && thrall.VampireOwner == ent.Owner)
|
||||
{
|
||||
ShowIcon(_prototype.Index(ent.Comp.StatusIcon), ref args);
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
private void GetThrallIcons(Entity<ThrallComponent> ent, ref GetStatusIconsEvent args)
|
||||
@@ -49,18 +68,123 @@ public sealed class VampireSystem : SharedVampireSystem
|
||||
if (HasComp<VampireComponent>(ent))
|
||||
return;
|
||||
|
||||
var iconPrototype = _prototype.Index(ent.Comp.StatusIcon);
|
||||
args.StatusIcons.Add(iconPrototype);
|
||||
// If the local user is an admin in the ghost?
|
||||
if (_admin.HasFlag(AdminFlags.Admin) && _ghost is { IsGhost: true })
|
||||
{
|
||||
ShowIcon(_prototype.Index(ent.Comp.StatusIcon), ref args);
|
||||
return;
|
||||
}
|
||||
|
||||
var localPlayer = _playerManager.LocalEntity;
|
||||
if (localPlayer == ent.Owner) // Is that you?
|
||||
{
|
||||
ShowIcon(_prototype.Index(ent.Comp.StatusIcon), ref args);
|
||||
return;
|
||||
}
|
||||
|
||||
// If we are the vampire owner of this servant?
|
||||
if (ent.Comp.VampireOwner == localPlayer)
|
||||
{
|
||||
ShowIcon(_prototype.Index(ent.Comp.StatusIcon), ref args);
|
||||
return;
|
||||
}
|
||||
|
||||
// If we were another servant of the same vampire owner?
|
||||
if (TryComp<ThrallComponent>(localPlayer, out var localThrall)
|
||||
&& localThrall.VampireOwner == ent.Comp.VampireOwner)
|
||||
{
|
||||
ShowIcon(_prototype.Index(ent.Comp.StatusIcon), ref args);
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
private void ShowIcon(FactionIconPrototype icon, ref GetStatusIconsEvent args)
|
||||
=> args.StatusIcons.Add(icon);
|
||||
|
||||
private void OnUpdateAlert(Entity<VampireComponent> ent, ref UpdateAlertSpriteEvent args)
|
||||
{
|
||||
if (args.Alert.ID != ent.Comp.BloodAlert)
|
||||
return;
|
||||
|
||||
var blood = Math.Clamp(ent.Comp.CurrentBlood.Int(), 0, 999);
|
||||
_sprite.LayerSetRsiState(args.SpriteViewEnt.Owner, VampireVisualLayers.Digit1, $"{(blood / 100) % 10}");
|
||||
_sprite.LayerSetRsiState(args.SpriteViewEnt.Owner, VampireVisualLayers.Digit2, $"{(blood / 10) % 10}");
|
||||
_sprite.LayerSetRsiState(args.SpriteViewEnt.Owner, VampireVisualLayers.Digit3, $"{blood % 10}");
|
||||
var blood = Math.Clamp(ent.Comp.CurrentBlood.Int(), 0, 9999);
|
||||
|
||||
_sprite.LayerSetRsiState(args.SpriteViewEnt.Owner, VampireVisualLayers.Digit1, $"{(blood / 1000) % 10}");
|
||||
_sprite.LayerSetRsiState(args.SpriteViewEnt.Owner, VampireVisualLayers.Digit2, $"{(blood / 100) % 10}");
|
||||
_sprite.LayerSetRsiState(args.SpriteViewEnt.Owner, VampireVisualLayers.Digit3, $"{(blood / 10) % 10}");
|
||||
_sprite.LayerSetRsiState(args.SpriteViewEnt.Owner, VampireVisualLayers.Digit4, $"{blood % 10}");
|
||||
}
|
||||
|
||||
private void OnDiablerieStartup(Entity<VampireDiablerieComponent> ent, ref ComponentStartup args)
|
||||
=> UpdateDiablerieAura(ent);
|
||||
|
||||
private void OnDiablerieShutdown(Entity<VampireDiablerieComponent> ent, ref ComponentShutdown args)
|
||||
=> RemoveDiablerieAura(ent);
|
||||
|
||||
private void OnDiablerieStateUpdated(Entity<VampireDiablerieComponent> ent, ref AfterAutoHandleStateEvent args)
|
||||
=> UpdateDiablerieAura(ent);
|
||||
|
||||
private void UpdateDiablerieAura(Entity<VampireDiablerieComponent> ent)
|
||||
{
|
||||
if (!HasComp<SpriteComponent>(ent))
|
||||
return;
|
||||
|
||||
// If the local user is an admin in the ghost?
|
||||
if (_admin.HasFlag(AdminFlags.Admin) && _ghost is { IsGhost: true })
|
||||
{
|
||||
AddOrUpdateAuraLayer(ent);
|
||||
return;
|
||||
}
|
||||
|
||||
var localPlayer = _playerManager.LocalEntity;
|
||||
var isLocalPlayer = localPlayer == ent.Owner;
|
||||
var isExaminerVampire = localPlayer != null && HasComp<VampireComponent>(localPlayer);
|
||||
var level = ent.Comp.DiablerieLevel;
|
||||
|
||||
// Level 1+ aura visible to vampires
|
||||
// Level 3+ aura visible to everyone
|
||||
var canSeeAura = level >= 1 && isExaminerVampire || level >= 3;
|
||||
|
||||
// Self visibility
|
||||
if (isLocalPlayer && level >= 1)
|
||||
canSeeAura = true;
|
||||
|
||||
if (!canSeeAura)
|
||||
{
|
||||
RemoveDiablerieAura(ent);
|
||||
return;
|
||||
}
|
||||
|
||||
AddOrUpdateAuraLayer(ent);
|
||||
}
|
||||
|
||||
private void AddOrUpdateAuraLayer(Entity<VampireDiablerieComponent> ent)
|
||||
{
|
||||
if (!_sprite.LayerMapTryGet(ent.Owner, DiablerieKey.Aura, out var layer, true))
|
||||
{
|
||||
var layerData = new PrototypeLayerData
|
||||
{
|
||||
RsiPath = "/Textures/_Wega/Interface/Misc/vampire_aura.rsi",
|
||||
State = "diablerie_aura"
|
||||
};
|
||||
|
||||
layer = _sprite.AddLayer(ent.Owner, layerData, null);
|
||||
_sprite.LayerMapSet(ent.Owner, DiablerieKey.Aura, layer);
|
||||
}
|
||||
|
||||
_sprite.LayerSetVisible(ent.Owner, layer, true);
|
||||
}
|
||||
|
||||
private void RemoveDiablerieAura(Entity<VampireDiablerieComponent> ent)
|
||||
{
|
||||
if (!HasComp<SpriteComponent>(ent))
|
||||
return;
|
||||
|
||||
if (_sprite.LayerMapTryGet(ent.Owner, DiablerieKey.Aura, out var layer, true))
|
||||
_sprite.RemoveLayer(ent.Owner, layer);
|
||||
}
|
||||
|
||||
private enum DiablerieKey
|
||||
{
|
||||
Aura
|
||||
}
|
||||
}
|
||||
|
||||
@@ -11,10 +11,10 @@ using Content.Shared.Interaction;
|
||||
using Content.Shared.Inventory;
|
||||
using Content.Shared.Mobs;
|
||||
using Content.Shared.Mobs.Systems;
|
||||
using Content.Shared.NullRod.Components; // Corvax-Wega-Vampire
|
||||
using Content.Shared.Popups;
|
||||
using Content.Shared.Stunnable; // Corvax-Wega-Vampire
|
||||
using Content.Shared.Timing;
|
||||
using Content.Shared.Vampire.Components; // Corvax-Wega-Vampire
|
||||
using Content.Shared.Verbs;
|
||||
using Robust.Shared.Audio.Systems;
|
||||
using Robust.Shared.Containers; // Corvax-Wega-Vampire
|
||||
|
||||
@@ -92,12 +92,13 @@ public sealed partial class CloningSystem : SharedCloningSystem
|
||||
_identity.QueueIdentityUpdate(clone.Value); // We have to manually refresh the identity in case we did not raise events.
|
||||
|
||||
_adminLogger.Add(LogType.Chat, LogImpact.Medium, $"The body of {original:player} was cloned as {clone.Value:player}");
|
||||
|
||||
///Corvax-Wega-Tweak: Для компонентов, использующих ивент ComponentStartup
|
||||
var finishedEv = new CloneFinishedEvent(original);
|
||||
RaiseLocalEvent(clone.Value, ref finishedEv);
|
||||
|
||||
return true;
|
||||
|
||||
// Corvax-Wega-Tweak-start
|
||||
var finishedEv = new CloneFinishedEvent(original);
|
||||
RaiseLocalEvent(clone.Value, ref finishedEv);
|
||||
// Corvax-Wega-Tweak-end
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
public override void CloneComponents(
|
||||
|
||||
@@ -1,17 +1,17 @@
|
||||
using Robust.Shared.Containers;
|
||||
using Content.Shared.Mobs.Components;
|
||||
using Robust.Shared.Timing;
|
||||
using Robust.Server.Containers;
|
||||
using Content.Shared.Whitelist;
|
||||
using Content.Shared.Damage;
|
||||
using Content.Shared.Damage.Systems;
|
||||
using Content.Shared.Damage.Components;
|
||||
using System.Linq;
|
||||
using Content.Server.Damage.Components;
|
||||
|
||||
using Content.Shared.Damage;
|
||||
using Content.Shared.Damage.Components;
|
||||
using Content.Shared.Damage.Systems;
|
||||
using Content.Shared.Mobs.Components;
|
||||
using Content.Shared.Whitelist;
|
||||
using Robust.Server.Containers;
|
||||
using Robust.Shared.Containers;
|
||||
using Robust.Shared.Timing;
|
||||
|
||||
namespace Content.Server.Damage.Systems;
|
||||
|
||||
public sealed class DamageInContainerSystem : SharedDamageInContainerSystem
|
||||
public sealed class DamageInContainerSystem : EntitySystem
|
||||
{
|
||||
[Dependency] private readonly IGameTiming _gameTiming = default!;
|
||||
[Dependency] private readonly EntityWhitelistSystem _whitelistSystem = default!;
|
||||
@@ -53,17 +53,43 @@ public sealed class DamageInContainerSystem : SharedDamageInContainerSystem
|
||||
if (_whitelistSystem.IsWhitelistFail(comp.Whitelist, contained))
|
||||
continue;
|
||||
|
||||
DamageSpecifier? finalDamage = null;
|
||||
if (comp.Damage != null && !comp.Damage.Empty && comp.Damage.DamageDict.Values.Any(x => x < 0))
|
||||
{
|
||||
finalDamage = comp.Damage.Clone();
|
||||
if (comp.DamageGroups != null && !comp.DamageGroups.Empty)
|
||||
{
|
||||
var groupsHeal = _damageable.CreateWeightedHealFromGroups(uid, comp.DamageGroups);
|
||||
finalDamage += groupsHeal;
|
||||
}
|
||||
}
|
||||
else if (comp.Damage != null && !comp.Damage.Empty)
|
||||
{
|
||||
finalDamage = comp.Damage;
|
||||
}
|
||||
else if (comp.DamageGroups != null && !comp.DamageGroups.Empty)
|
||||
{
|
||||
finalDamage = _damageable.CreateWeightedHealFromGroups(uid, comp.DamageGroups);
|
||||
}
|
||||
else
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
if (TryComp<MobStateComponent>(contained, out var mobState))
|
||||
{
|
||||
foreach (var allowedState in comp.AllowedStates)
|
||||
{
|
||||
if (allowedState == mobState.CurrentState)
|
||||
_damageable.TryChangeDamage(contained, comp.Damage, true, false);
|
||||
{
|
||||
_damageable.TryChangeDamage(contained, finalDamage, true, false);
|
||||
break;
|
||||
}
|
||||
}
|
||||
continue;
|
||||
}
|
||||
|
||||
_damageable.TryChangeDamage(contained, comp.Damage, true, false);
|
||||
_damageable.TryChangeDamage(contained, finalDamage, true, false);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -79,4 +105,3 @@ public sealed class DamageInContainerSystem : SharedDamageInContainerSystem
|
||||
AddComp<ActiveDamageInContainerComponent>(uid);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
using Robust.Shared.Serialization.TypeSerializers.Implementations.Custom;
|
||||
using Content.Shared.FixedPoint;
|
||||
using Content.Shared.Vampire;
|
||||
|
||||
namespace Content.Server.GameTicking.Rules.Components;
|
||||
|
||||
@@ -8,4 +9,13 @@ namespace Content.Server.GameTicking.Rules.Components;
|
||||
[RegisterComponent, Access(typeof(VampireRuleSystem))]
|
||||
public sealed partial class VampireRuleComponent : Component
|
||||
{
|
||||
[DataField]
|
||||
public Dictionary<EntityUid, VampireRoundInfo> VampiresInfo = new();
|
||||
}
|
||||
|
||||
public sealed partial class VampireRoundInfo
|
||||
{
|
||||
public string Name = string.Empty;
|
||||
public VampireClassEnum Class = VampireClassEnum.NonSelected;
|
||||
public FixedPoint2 TotalBloodDrank = FixedPoint2.Zero;
|
||||
}
|
||||
|
||||
@@ -1,34 +1,21 @@
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using Content.Server.Antag;
|
||||
using Content.Server.Atmos.Components;
|
||||
using Content.Server.GameTicking.Rules.Components;
|
||||
using Content.Server.Roles;
|
||||
using Content.Server.Actions;
|
||||
using Content.Shared.Atmos;
|
||||
using Content.Shared.Atmos.Rotting;
|
||||
using Content.Shared.Body.Components;
|
||||
using Content.Shared.Chemistry;
|
||||
using Content.Shared.Chemistry.Reaction;
|
||||
using Content.Shared.Clumsy;
|
||||
using Content.Shared.CombatMode.Pacification;
|
||||
using Content.Shared.FixedPoint;
|
||||
using Content.Shared.GameTicking.Components;
|
||||
using Content.Shared.Humanoid;
|
||||
using Content.Shared.Nutrition.Components;
|
||||
using Content.Shared.Temperature.Components;
|
||||
using Content.Shared.Mind;
|
||||
using Content.Shared.Vampire;
|
||||
using Content.Shared.Vampire.Components;
|
||||
using Content.Shared.Damage.Systems;
|
||||
using Content.Shared.Body;
|
||||
using Content.Shared.Metabolism;
|
||||
using System.Linq;
|
||||
|
||||
namespace Content.Server.GameTicking.Rules
|
||||
{
|
||||
public sealed class VampireRuleSystem : GameRuleSystem<VampireRuleComponent>
|
||||
{
|
||||
[Dependency] private readonly AntagSelectionSystem _antag = default!;
|
||||
[Dependency] private readonly MetabolizerSystem _metabolism = default!;
|
||||
[Dependency] private readonly ActionsSystem _actions = default!;
|
||||
[Dependency] private readonly DamageableSystem _damage = default!;
|
||||
[Dependency] private readonly SharedVisualBodySystem _visualBody = default!;
|
||||
[Dependency] private readonly SharedMindSystem _mind = default!;
|
||||
|
||||
public override void Initialize()
|
||||
{
|
||||
@@ -41,7 +28,6 @@ namespace Content.Server.GameTicking.Rules
|
||||
private void OnVampireSelected(Entity<VampireRuleComponent> mindId, ref AfterAntagEntitySelectedEvent args)
|
||||
{
|
||||
var ent = args.EntityUid;
|
||||
MakeVampire(ent);
|
||||
_antag.SendBriefing(ent, MakeBriefing(ent), Color.Purple, null);
|
||||
}
|
||||
|
||||
@@ -56,8 +42,7 @@ namespace Content.Server.GameTicking.Rules
|
||||
|
||||
private string MakeBriefing(EntityUid ent)
|
||||
{
|
||||
var isHuman = HasComp<HumanoidProfileComponent>(ent);
|
||||
var briefing = isHuman
|
||||
var briefing = HasComp<HumanoidProfileComponent>(ent)
|
||||
? Loc.GetString("vampire-role-greeting-human")
|
||||
: Loc.GetString("vampire-role-greeting-animal");
|
||||
|
||||
@@ -69,117 +54,131 @@ namespace Content.Server.GameTicking.Rules
|
||||
GameRuleComponent gameRule,
|
||||
ref RoundEndTextAppendEvent args)
|
||||
{
|
||||
var totalBloodDrank = GetTotalBloodDrankInRound();
|
||||
args.AddLine(Loc.GetString("vampires-drank-total-blood", ("bloodAmount", totalBloodDrank)));
|
||||
if (component.VampiresInfo.Count == 0)
|
||||
return;
|
||||
|
||||
var sb = new StringBuilder();
|
||||
sb.AppendLine(Loc.GetString("vampire-round-end-header"));
|
||||
|
||||
foreach (var (_, info) in component.VampiresInfo)
|
||||
{
|
||||
var name = !string.IsNullOrEmpty(info.Name) ? info.Name : Loc.GetString("generic-unknown");
|
||||
var className = Loc.GetString($"select-class-{info.Class.ToString().ToLower()}");
|
||||
var bloodAmount = info.TotalBloodDrank.Float().ToString("F2");
|
||||
var classColor = GetClassColor(info.Class);
|
||||
|
||||
var line = Loc.GetString("vampire-round-end-info", ("name", name), ("class", className),
|
||||
("blood", bloodAmount), ("color", classColor));
|
||||
|
||||
sb.AppendLine(line);
|
||||
}
|
||||
|
||||
sb.AppendLine();
|
||||
var totalBloodDrank = GetTotalBloodDrankInRound(component).ToString("F2");
|
||||
sb.AppendLine(Loc.GetString("vampires-drank-total-blood", ("bloodAmount", totalBloodDrank)));
|
||||
|
||||
args.AddLine(sb.ToString());
|
||||
}
|
||||
|
||||
private float GetTotalBloodDrankInRound()
|
||||
private Color GetClassColor(VampireClassEnum vampireClass)
|
||||
{
|
||||
return vampireClass switch
|
||||
{
|
||||
VampireClassEnum.Hemomancer => Color.FromHex("#b82e2e"),
|
||||
VampireClassEnum.Umbrae => Color.FromHex("#6709aa"),
|
||||
VampireClassEnum.Gargantua => Color.FromHex("#b34019"),
|
||||
VampireClassEnum.Dantalion => Color.FromHex("#2a9633"),
|
||||
VampireClassEnum.Bestia => Color.FromHex("#2770c4"),
|
||||
_ => Color.Yellow
|
||||
};
|
||||
}
|
||||
|
||||
private float GetTotalBloodDrankInRound(VampireRuleComponent component)
|
||||
{
|
||||
var totalBloodDrank = 0f;
|
||||
foreach (var vampireEntity in EntityQuery<VampireComponent>(true))
|
||||
{
|
||||
totalBloodDrank += vampireEntity.TotalBloodDrank;
|
||||
}
|
||||
foreach (var (_, info) in component.VampiresInfo)
|
||||
totalBloodDrank += info.TotalBloodDrank.Float();
|
||||
|
||||
return totalBloodDrank;
|
||||
}
|
||||
|
||||
private void MakeVampire(EntityUid vampire)
|
||||
{
|
||||
var vampireComponent = EnsureComp<VampireComponent>(vampire);
|
||||
#region Records
|
||||
|
||||
RemoveUnnecessaryComponents(vampire);
|
||||
HandleMetabolismAndOrgans(vampire);
|
||||
SetVampireComponents(vampire, vampireComponent);
|
||||
UpdateAppearance(vampire);
|
||||
AddVampireActions(vampire);
|
||||
}
|
||||
|
||||
private void RemoveUnnecessaryComponents(EntityUid vampire)
|
||||
public void InitVampireRecord(EntityUid vampireUid, VampireComponent? vampireComp = null)
|
||||
{
|
||||
var componentsToRemove = new[]
|
||||
if (!Resolve(vampireUid, ref vampireComp, false))
|
||||
return;
|
||||
|
||||
var rule = EntityQuery<VampireRuleComponent>().FirstOrDefault();
|
||||
if (rule == null)
|
||||
return;
|
||||
|
||||
var mindId = _mind.GetMind(vampireUid);
|
||||
if (mindId == null)
|
||||
return;
|
||||
|
||||
if (rule.VampiresInfo.ContainsKey(mindId.Value))
|
||||
return;
|
||||
|
||||
var info = new VampireRoundInfo
|
||||
{
|
||||
typeof(PacifiedComponent),
|
||||
typeof(PerishableComponent),
|
||||
typeof(BarotraumaComponent),
|
||||
typeof(TemperatureSpeedComponent),
|
||||
typeof(ThirstComponent),
|
||||
typeof(ClumsyComponent)
|
||||
Name = Name(vampireUid)
|
||||
};
|
||||
|
||||
foreach (var compType in componentsToRemove)
|
||||
{
|
||||
if (HasComp(vampire, compType))
|
||||
RemComp(vampire, compType);
|
||||
}
|
||||
rule.VampiresInfo[mindId.Value] = info;
|
||||
}
|
||||
|
||||
private void HandleMetabolismAndOrgans(EntityUid vampire)
|
||||
public void RecordBloodDrank(EntityUid vampireUid, FixedPoint2 amount)
|
||||
{
|
||||
if (TryComp<BodyComponent>(vampire, out var bodyComponent) && bodyComponent.Organs != null)
|
||||
if (amount <= 0)
|
||||
return;
|
||||
|
||||
var rule = EntityQuery<VampireRuleComponent>().FirstOrDefault();
|
||||
if (rule == null)
|
||||
return;
|
||||
|
||||
var mindId = _mind.GetMind(vampireUid);
|
||||
if (mindId == null)
|
||||
return;
|
||||
|
||||
if (!rule.VampiresInfo.TryGetValue(mindId.Value, out var info))
|
||||
{
|
||||
foreach (var organ in bodyComponent.Organs.ContainedEntities)
|
||||
info = new VampireRoundInfo
|
||||
{
|
||||
if (TryComp<MetabolizerComponent>(organ, out var metabolizer))
|
||||
{
|
||||
if (TryComp<StomachComponent>(organ, out _))
|
||||
_metabolism.ClearMetabolizerTypes(metabolizer);
|
||||
|
||||
_metabolism.TryAddMetabolizerType(metabolizer, VampireComponent.MetabolizerVampire);
|
||||
}
|
||||
}
|
||||
Name = Name(vampireUid),
|
||||
Class = CompOrNull<VampireComponent>(vampireUid)?.CurrentEvolution ?? default
|
||||
};
|
||||
rule.VampiresInfo[mindId.Value] = info;
|
||||
}
|
||||
|
||||
info.TotalBloodDrank += amount;
|
||||
}
|
||||
|
||||
private void SetVampireComponents(EntityUid vampire, VampireComponent _)
|
||||
public void RecordClassSelected(EntityUid vampireUid, VampireClassEnum selectedClass)
|
||||
{
|
||||
if (TryComp<TemperatureDamageComponent>(vampire, out var temperature))
|
||||
temperature.ColdDamageThreshold = Atmospherics.TCMB;
|
||||
var rule = EntityQuery<VampireRuleComponent>().FirstOrDefault();
|
||||
if (rule == null)
|
||||
return;
|
||||
|
||||
EnsureComp<UnholyComponent>(vampire);
|
||||
EnsureComp<VampireComponent>(vampire);
|
||||
var mindId = _mind.GetMind(vampireUid);
|
||||
if (mindId == null)
|
||||
return;
|
||||
|
||||
_damage.SetDamageModifierSetId(vampire, "Vampire");
|
||||
|
||||
if (TryComp<ReactiveComponent>(vampire, out var reactive))
|
||||
if (!rule.VampiresInfo.TryGetValue(mindId.Value, out var info))
|
||||
{
|
||||
reactive.ReactiveGroups ??= new();
|
||||
|
||||
if (!reactive.ReactiveGroups.ContainsKey("Unholy"))
|
||||
info = new VampireRoundInfo
|
||||
{
|
||||
reactive.ReactiveGroups.Add("Unholy", new() { ReactionMethod.Touch });
|
||||
}
|
||||
Name = Name(vampireUid),
|
||||
Class = selectedClass
|
||||
};
|
||||
rule.VampiresInfo[mindId.Value] = info;
|
||||
}
|
||||
else
|
||||
{
|
||||
info.Class = selectedClass;
|
||||
}
|
||||
}
|
||||
|
||||
private void UpdateAppearance(EntityUid vampire)
|
||||
{
|
||||
if (_visualBody.TryGatherMarkingsData(vampire, null, out var profiles, out _, out _))
|
||||
{
|
||||
var newEyeColor = Color.FromHex("#E22218FF");
|
||||
|
||||
var updatedProfiles = profiles.ToDictionary(
|
||||
pair => pair.Key,
|
||||
pair => pair.Value with { EyeColor = newEyeColor });
|
||||
|
||||
_visualBody.ApplyProfiles(vampire, updatedProfiles);
|
||||
}
|
||||
}
|
||||
|
||||
private void AddVampireActions(EntityUid vampire)
|
||||
{
|
||||
var actionPrototypes = new[]
|
||||
{
|
||||
VampireComponent.DrinkActionPrototype,
|
||||
VampireComponent.SelectClassActionPrototype,
|
||||
VampireComponent.RejuvenateActionPrototype,
|
||||
VampireComponent.GlareActionPrototype
|
||||
};
|
||||
|
||||
foreach (var actionPrototype in actionPrototypes)
|
||||
{
|
||||
_actions.AddAction(vampire, actionPrototype);
|
||||
}
|
||||
}
|
||||
#endregion
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,112 @@
|
||||
using Content.Server.Administration.Logs;
|
||||
using Content.Server.Bible.Components;
|
||||
using Content.Shared.Database;
|
||||
using Content.Shared.FixedPoint;
|
||||
using Content.Shared.NullRod.Components;
|
||||
using Content.Shared.Hands.EntitySystems;
|
||||
using Robust.Shared.Timing;
|
||||
using Content.Shared.Damage.Systems;
|
||||
using Content.Shared.Rejuvenate;
|
||||
|
||||
namespace Content.Server.NullRod;
|
||||
|
||||
public sealed class NullDamageSystem : EntitySystem
|
||||
{
|
||||
[Dependency] private readonly IAdminLogManager _admin = default!;
|
||||
[Dependency] private readonly IGameTiming _timing = default!;
|
||||
[Dependency] private readonly SharedHandsSystem _hands = default!;
|
||||
|
||||
public override void Initialize()
|
||||
{
|
||||
base.Initialize();
|
||||
|
||||
SubscribeLocalEvent<UnholyComponent, DamageChangedEvent>(OnUnholyDamageTaken);
|
||||
SubscribeLocalEvent<NullDamageComponent, RejuvenateEvent>(OnRejuvenate);
|
||||
}
|
||||
|
||||
public override void Update(float frameTime)
|
||||
{
|
||||
base.Update(frameTime);
|
||||
|
||||
UpdateNullDamage();
|
||||
}
|
||||
|
||||
private void UpdateNullDamage()
|
||||
{
|
||||
var currentTime = _timing.CurTime;
|
||||
|
||||
var query = EntityQueryEnumerator<NullDamageComponent>();
|
||||
while (query.MoveNext(out var uid, out var nullDamage))
|
||||
{
|
||||
if (nullDamage.NullDamage <= 0)
|
||||
{
|
||||
RemComp<NullDamageComponent>(uid);
|
||||
continue;
|
||||
}
|
||||
|
||||
if (currentTime >= nullDamage.NextNullDamageTick)
|
||||
{
|
||||
RecoverNullDamage(nullDamage, nullDamage.NullDamageRecoveryPerTick);
|
||||
|
||||
nullDamage.NextNullDamageTick = currentTime + TimeSpan.FromSeconds(nullDamage.NullDamageRecoveryInterval);
|
||||
Dirty(uid, nullDamage);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#region Null Damage Logic
|
||||
|
||||
private void OnUnholyDamageTaken(EntityUid uid, UnholyComponent component, ref DamageChangedEvent args)
|
||||
{
|
||||
if (!args.Origin.HasValue || !HasComp<BibleUserComponent>(args.Origin.Value))
|
||||
return;
|
||||
|
||||
var heldEntity = _hands.GetActiveItem(args.Origin.Value);
|
||||
if (!TryComp<NullRodComponent>(heldEntity, out var nullRodComp))
|
||||
return;
|
||||
|
||||
var nullDamage = EnsureComp<NullDamageComponent>(uid);
|
||||
var damageToApply = nullDamage.NullDamage > 0
|
||||
? nullRodComp.NullDamage
|
||||
: nullRodComp.FirstNullDamage;
|
||||
|
||||
AddNullDamage(nullDamage, damageToApply);
|
||||
|
||||
if (nullDamage.NextNullDamageTick == default)
|
||||
{
|
||||
nullDamage.NextNullDamageTick = _timing.CurTime + TimeSpan.FromSeconds(nullDamage.NullDamageRecoveryInterval);
|
||||
}
|
||||
|
||||
Dirty(uid, nullDamage);
|
||||
|
||||
_admin.Add(LogType.Damaged, LogImpact.Low,
|
||||
$"{ToPrettyString(uid):target} took {damageToApply} NullDamage from {ToPrettyString(args.Origin.Value):attacker}");
|
||||
}
|
||||
|
||||
private void OnRejuvenate(EntityUid uid, NullDamageComponent component, ref RejuvenateEvent args)
|
||||
=> RemCompDeferred<NullDamageComponent>(uid);
|
||||
|
||||
#endregion
|
||||
|
||||
#region Public API
|
||||
|
||||
public void AddNullDamage(NullDamageComponent nullDamage, FixedPoint2 amount)
|
||||
{
|
||||
nullDamage.NullDamage = FixedPoint2.Min(nullDamage.NullDamage + amount, nullDamage.MaxNullDamage);
|
||||
}
|
||||
|
||||
public void RecoverNullDamage(NullDamageComponent nullDamage, FixedPoint2 amount)
|
||||
{
|
||||
nullDamage.NullDamage = FixedPoint2.Max(nullDamage.NullDamage - amount, FixedPoint2.Zero);
|
||||
}
|
||||
|
||||
public FixedPoint2? GetNullDamage(EntityUid uid, NullDamageComponent? nullDamage = null)
|
||||
{
|
||||
if (!Resolve(uid, ref nullDamage, false))
|
||||
return null;
|
||||
|
||||
return nullDamage.NullDamage;
|
||||
}
|
||||
|
||||
#endregion
|
||||
}
|
||||
@@ -24,7 +24,7 @@ public sealed class ProjectileAoESystem : EntitySystem
|
||||
|
||||
var target = ev.Target;
|
||||
var ents = _lookup.GetEntitiesInRange<DamageableComponent>(Transform(target).Coordinates, component.DamageRadius)
|
||||
.Where(e => e.Owner != shooter);
|
||||
.Where(e => e.Owner != shooter && e.Owner != target);
|
||||
|
||||
foreach (var ent in ents)
|
||||
{
|
||||
|
||||
@@ -0,0 +1,29 @@
|
||||
using Content.Server.Disease;
|
||||
using Content.Shared.Projectiles;
|
||||
using Robust.Shared.Random;
|
||||
|
||||
namespace Content.Server.Projectiles;
|
||||
|
||||
public sealed class ProjectileInfectSystem : EntitySystem
|
||||
{
|
||||
[Dependency] private readonly DiseaseSystem _disease = default!;
|
||||
[Dependency] private readonly IRobustRandom _random = default!;
|
||||
|
||||
public override void Initialize()
|
||||
{
|
||||
base.Initialize();
|
||||
SubscribeLocalEvent<ProjectileInfectComponent, ProjectileHitEvent>(OnProjectileHit);
|
||||
}
|
||||
|
||||
private void OnProjectileHit(EntityUid entity, ProjectileInfectComponent component, ref ProjectileHitEvent ev)
|
||||
{
|
||||
var shooter = ev.Shooter;
|
||||
if (shooter == null || shooter == ev.Target)
|
||||
return;
|
||||
|
||||
if (!_random.Prob(component.Prob))
|
||||
return;
|
||||
|
||||
_disease.TryAddDisease(ev.Target, component.Infection);
|
||||
}
|
||||
}
|
||||
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,424 @@
|
||||
using System.Linq;
|
||||
using Content.Server.Administration;
|
||||
using Content.Server.Bible.Components;
|
||||
using Content.Server.Cloning;
|
||||
using Content.Server.Hallucinations;
|
||||
using Content.Server.Prayer;
|
||||
using Content.Shared.Body;
|
||||
using Content.Shared.CombatMode.Pacification;
|
||||
using Content.Shared.DoAfter;
|
||||
using Content.Shared.Humanoid;
|
||||
using Content.Shared.IdentityManagement;
|
||||
using Content.Shared.Mindshield.Components;
|
||||
using Content.Shared.Movement.Systems;
|
||||
using Content.Shared.NullRod.Components;
|
||||
using Content.Shared.Popups;
|
||||
using Content.Shared.Stealth.Components;
|
||||
using Content.Shared.Surgery.Components;
|
||||
using Content.Shared.Vampire;
|
||||
using Content.Shared.Vampire.Components;
|
||||
using Robust.Shared.Audio;
|
||||
using Robust.Shared.Player;
|
||||
using Robust.Shared.Timing;
|
||||
|
||||
namespace Content.Server.Vampire;
|
||||
|
||||
public sealed partial class VampireSystem
|
||||
{
|
||||
[Dependency] private readonly CloningSystem _cloning = default!;
|
||||
[Dependency] private readonly HallucinationsSystem _hallucinations = default!;
|
||||
[Dependency] private readonly PrayerSystem _prayerSystem = default!;
|
||||
[Dependency] private readonly QuickDialogSystem _quickDialog = default!;
|
||||
|
||||
private void InitializeDantalion()
|
||||
{
|
||||
SubscribeLocalEvent<VampireComponent, VampireEnthrallActionEvent>(OnAfterEnthrall);
|
||||
SubscribeLocalEvent<VampireComponent, EnthrallDoAfterEvent>(OnEnthrallDoAfter);
|
||||
SubscribeLocalEvent<VampireComponent, VampireCommuneActionEvent>(OnCommune);
|
||||
SubscribeLocalEvent<VampireComponent, VampirePacifyActionEvent>(OnPacify);
|
||||
SubscribeLocalEvent<VampireComponent, VampireSubspaceSwapActionEvent>(OnSubspaceSwap);
|
||||
SubscribeLocalEvent<VampireComponent, VampireDeployDecoyActionEvent>(OnDeployDecoy);
|
||||
SubscribeLocalEvent<VampireComponent, VampireRallyThrallsActionEvent>(OnRallyThralls);
|
||||
SubscribeLocalEvent<VampireComponent, VampireBloodBondActionEvent>(OnBloodBond);
|
||||
SubscribeLocalEvent<VampireComponent, VampireMassHysteriaActionEvent>(OnMassHysteria);
|
||||
SubscribeLocalEvent<VampireComponent, VampireThrallHealActionEvent>(OnThrallHeal);
|
||||
SubscribeLocalEvent<VampireComponent, VampirePacifyNearbyActionEvent>(OnPacifyNearby);
|
||||
}
|
||||
|
||||
private void OnAfterEnthrall(Entity<VampireComponent> ent, ref VampireEnthrallActionEvent args)
|
||||
{
|
||||
var target = args.Target;
|
||||
if (!TryComp<ThrallOwnerComponent>(ent, out var thrallOwner))
|
||||
return;
|
||||
|
||||
if (!CanAddThrall(thrallOwner))
|
||||
{
|
||||
_popup.PopupEntity(Loc.GetString("vampire-max-trall-reached"), ent, ent, PopupType.Medium);
|
||||
return;
|
||||
}
|
||||
|
||||
if (HasComp<VampireComponent>(target) || HasComp<MindShieldComponent>(target) || HasComp<BibleUserComponent>(target)
|
||||
|| HasComp<SyntheticOperatedComponent>(target))
|
||||
{
|
||||
_popup.PopupEntity(Loc.GetString("vampire-enthall-failed", ("target", Identity.Name(target, EntityManager))), ent, ent);
|
||||
return;
|
||||
}
|
||||
|
||||
if (TryComp<ThrallComponent>(target, out var trallComponent))
|
||||
{
|
||||
if (trallComponent.VampireOwner == ent.Owner)
|
||||
{
|
||||
_popup.PopupEntity(Loc.GetString("vampire-enthall-already", ("target", Identity.Name(target, EntityManager))), ent, ent);
|
||||
return;
|
||||
}
|
||||
else
|
||||
{
|
||||
_popup.PopupEntity(Loc.GetString("vampire-enthall-failed", ("target", Identity.Name(target, EntityManager))), ent, ent);
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
if (!CheckBloodEssence(ent.Owner, args.BloodCost))
|
||||
{
|
||||
SendFailedPopup(ent);
|
||||
return;
|
||||
}
|
||||
|
||||
args.Handled = true;
|
||||
_popup.PopupEntity(Loc.GetString("vampire-blooddrink-countion"), ent, args.Target, PopupType.MediumCaution);
|
||||
_doAfter.TryStartDoAfter(new DoAfterArgs(EntityManager, ent, TimeSpan.FromSeconds(15f), new EnthrallDoAfterEvent(args.BloodCost), ent, target)
|
||||
{
|
||||
BreakOnMove = true,
|
||||
BreakOnDamage = true,
|
||||
MovementThreshold = 0.01f,
|
||||
DistanceThreshold = 0.5f,
|
||||
NeedHand = true
|
||||
});
|
||||
}
|
||||
|
||||
private void OnEnthrallDoAfter(Entity<VampireComponent> ent, ref EnthrallDoAfterEvent args)
|
||||
{
|
||||
if (args.Cancelled || args.Args.Target == null) return;
|
||||
|
||||
var target = args.Args.Target.Value;
|
||||
if (!HasComp<ActorComponent>(target))
|
||||
return;
|
||||
|
||||
if (!TryComp<ThrallOwnerComponent>(ent, out var thrallOwner))
|
||||
return;
|
||||
|
||||
EnsureComp<UnholyComponent>(target);
|
||||
EnsureComp<BittenByVampireComponent>(target);
|
||||
var newTrall = EnsureComp<ThrallComponent>(target);
|
||||
newTrall.VampireOwner = ent.Owner;
|
||||
Dirty(target, newTrall);
|
||||
|
||||
if (TryAddThrall(thrallOwner, target))
|
||||
Dirty(ent.Owner, thrallOwner);
|
||||
|
||||
_popup.PopupEntity(Loc.GetString("vampire-enthall-success", ("target", Identity.Name(target, EntityManager))), ent, ent);
|
||||
_antag.SendBriefing(target, Loc.GetString("thrall-greeting"), Color.Red,
|
||||
new SoundPathSpecifier("/Audio/_Wega/Ambience/Antag/vampare_start.ogg"));
|
||||
SubtractBloodEssence(ent.Owner, args.BloodCost);
|
||||
}
|
||||
|
||||
private void OnCommune(Entity<VampireComponent> ent, ref VampireCommuneActionEvent args)
|
||||
{
|
||||
if (!TryComp<ThrallOwnerComponent>(ent, out var thrallOwner) || thrallOwner.ThrallOwned.Count == 0)
|
||||
{
|
||||
_popup.PopupEntity(Loc.GetString("vampire-no-thrall"), ent, ent, PopupType.Medium);
|
||||
return;
|
||||
}
|
||||
|
||||
if (!TryComp<ActorComponent>(ent, out var playerActor))
|
||||
return;
|
||||
|
||||
var playerSession = playerActor.PlayerSession;
|
||||
_quickDialog.OpenDialog(playerSession, Loc.GetString("vampire-commune-title"), Loc.GetString("vampire-commune-prompt"),
|
||||
(string message) =>
|
||||
{
|
||||
var finalMessage = string.IsNullOrWhiteSpace(message)
|
||||
? Loc.GetString("vampire-commune-default-message")
|
||||
: message;
|
||||
|
||||
foreach (var thrallUid in thrallOwner.ThrallOwned)
|
||||
{
|
||||
if (!TryComp<ActorComponent>(thrallUid, out var thrallActor))
|
||||
continue;
|
||||
|
||||
_prayerSystem.SendSubtleMessage(thrallActor.PlayerSession, thrallActor.PlayerSession, finalMessage, Loc.GetString("vampire-commune-default-message"));
|
||||
_chat.SendMessageToOne(thrallUid, finalMessage);
|
||||
}
|
||||
|
||||
_chat.SendMessageToOne(ent, finalMessage);
|
||||
});
|
||||
|
||||
args.Handled = true;
|
||||
}
|
||||
|
||||
private void OnPacify(Entity<VampireComponent> ent, ref VampirePacifyActionEvent args)
|
||||
{
|
||||
if (!CheckBloodEssence(ent.Owner, args.BloodCost))
|
||||
{
|
||||
SendFailedPopup(ent);
|
||||
return;
|
||||
}
|
||||
|
||||
var target = args.Target;
|
||||
if (HasComp<HumanoidProfileComponent>(target))
|
||||
{
|
||||
if (HasComp<NullRodOwnerComponent>(target) && !HasTruePower(ent) || HasComp<ThrallComponent>(target))
|
||||
{
|
||||
_popup.PopupEntity(Loc.GetString("vampire-pacify-failed", ("target", Identity.Name(target, EntityManager))), ent, ent);
|
||||
return;
|
||||
}
|
||||
|
||||
if (!_mobState.IsDead(target) && !HasComp<PacifiedComponent>(target))
|
||||
{
|
||||
EnsureComp<PacifiedComponent>(target);
|
||||
Timer.Spawn(args.Time, () => { RemComp<PacifiedComponent>(target); });
|
||||
|
||||
SubtractBloodEssence(ent.Owner, args.BloodCost);
|
||||
args.Handled = true;
|
||||
}
|
||||
else
|
||||
{
|
||||
_popup.PopupEntity(Loc.GetString("vampire-pacify-failed", ("target", Identity.Name(target, EntityManager))), ent, ent);
|
||||
args.Handled = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void OnSubspaceSwap(Entity<VampireComponent> ent, ref VampireSubspaceSwapActionEvent args)
|
||||
{
|
||||
if (!CheckBloodEssence(ent.Owner, args.BloodCost))
|
||||
{
|
||||
SendFailedPopup(ent);
|
||||
return;
|
||||
}
|
||||
|
||||
var target = args.Target;
|
||||
if (!HasComp<HumanoidProfileComponent>(target))
|
||||
{
|
||||
_popup.PopupEntity(Loc.GetString("vampire-teleport-failed"), ent, ent, PopupType.SmallCaution);
|
||||
return;
|
||||
}
|
||||
|
||||
if (HasComp<NullRodOwnerComponent>(target) && !HasTruePower(ent))
|
||||
return;
|
||||
|
||||
var currentCoords = Transform(ent).Coordinates;
|
||||
var targetCoords = Transform(target).Coordinates;
|
||||
_transform.SetCoordinates(ent, targetCoords);
|
||||
_transform.SetCoordinates(target, currentCoords);
|
||||
|
||||
_movementMod.TryUpdateMovementSpeedModDuration(target, MovementModStatusSystem.Slowdown, TimeSpan.FromSeconds(4f), 0.5f);
|
||||
_hallucinations.StartHallucinations(target, "Hallucinations", TimeSpan.FromSeconds(15f), true, "MindBreaker");
|
||||
|
||||
SubtractBloodEssence(ent.Owner, args.BloodCost);
|
||||
args.Handled = true;
|
||||
}
|
||||
|
||||
private void OnDeployDecoy(Entity<VampireComponent> ent, ref VampireDeployDecoyActionEvent args)
|
||||
{
|
||||
if (!CheckBloodEssence(ent.Owner, args.BloodCost))
|
||||
{
|
||||
SendFailedPopup(ent);
|
||||
return;
|
||||
}
|
||||
|
||||
if (!_cloning.TryCloning(ent, _transform.GetMapCoordinates(ent), args.Settings, out var clone))
|
||||
return;
|
||||
|
||||
EntityManager.AddComponents(clone.Value, args.EnsurableComponents);
|
||||
|
||||
var stealth = EnsureComp<StealthComponent>(ent);
|
||||
_stealth.SetVisibility(ent, 0f, stealth);
|
||||
|
||||
Timer.Spawn(args.Time, () =>
|
||||
{
|
||||
RemComp<StealthComponent>(ent);
|
||||
Del(clone);
|
||||
});
|
||||
|
||||
SubtractBloodEssence(ent.Owner, args.BloodCost);
|
||||
args.Handled = true;
|
||||
}
|
||||
|
||||
private void OnRallyThralls(Entity<VampireComponent> ent, ref VampireRallyThrallsActionEvent args)
|
||||
{
|
||||
if (!CheckBloodEssence(ent.Owner, args.BloodCost))
|
||||
{
|
||||
SendFailedPopup(ent);
|
||||
return;
|
||||
}
|
||||
|
||||
var thrallsInRange = _entityLookup.GetEntitiesInRange<ThrallComponent>(Transform(ent).Coordinates, 7f);
|
||||
foreach (var thrallEntity in thrallsInRange)
|
||||
{
|
||||
if (_mobState.IsDead(thrallEntity.Owner))
|
||||
continue;
|
||||
|
||||
TryRemoveKnockdown(thrallEntity.Owner);
|
||||
_stamina.RemoveStaminaDamage(thrallEntity.Owner);
|
||||
_status.TryRemoveStatusEffect(thrallEntity.Owner, ForceSleeping);
|
||||
}
|
||||
|
||||
SubtractBloodEssence(ent.Owner, args.BloodCost);
|
||||
args.Handled = true;
|
||||
}
|
||||
|
||||
private void OnBloodBond(Entity<VampireComponent> ent, ref VampireBloodBondActionEvent args)
|
||||
{
|
||||
var supreme = GetTruePower(ent);
|
||||
if (supreme == null)
|
||||
return;
|
||||
|
||||
if (!TryComp<ThrallOwnerComponent>(ent, out var thrallOwner) || thrallOwner.ThrallOwned.Count == 0)
|
||||
{
|
||||
_popup.PopupEntity(Loc.GetString("vampire-no-thrall"), ent, ent, PopupType.Medium);
|
||||
return;
|
||||
}
|
||||
|
||||
if (!CheckBloodEssence(ent.Owner, args.BloodCost))
|
||||
{
|
||||
SendFailedPopup(ent);
|
||||
return;
|
||||
}
|
||||
|
||||
if (supreme.Active)
|
||||
{
|
||||
supreme.Active = false;
|
||||
thrallOwner.DamageSharing = false;
|
||||
Dirty(ent.Owner, supreme);
|
||||
args.Handled = true;
|
||||
return;
|
||||
}
|
||||
|
||||
supreme.Active = true;
|
||||
thrallOwner.DamageSharing = true;
|
||||
Dirty(ent.Owner, supreme);
|
||||
|
||||
ExecuteBloodBondTick(ent, supreme, thrallOwner, args);
|
||||
args.Handled = true;
|
||||
}
|
||||
|
||||
private void OnThrallHeal(Entity<VampireComponent> ent, ref VampireThrallHealActionEvent args)
|
||||
{
|
||||
var thrallsInRange = _entityLookup.GetEntitiesInRange<ThrallComponent>(Transform(ent).Coordinates, 5);
|
||||
|
||||
int thrallCount = thrallsInRange.Count;
|
||||
if (thrallCount == 0)
|
||||
return;
|
||||
|
||||
if (!CheckBloodEssence(ent.Owner, thrallCount * args.BloodCost))
|
||||
{
|
||||
SendFailedPopup(ent);
|
||||
return;
|
||||
}
|
||||
|
||||
foreach (var thrallEntity in thrallsInRange)
|
||||
{
|
||||
ExecuteThrallHealTick(thrallEntity.Owner, ent, 0, args);
|
||||
}
|
||||
|
||||
SubtractBloodEssence(ent.Owner, thrallCount * args.BloodCost);
|
||||
args.Handled = true;
|
||||
}
|
||||
|
||||
private void OnPacifyNearby(Entity<VampireComponent> ent, ref VampirePacifyNearbyActionEvent args)
|
||||
{
|
||||
if (!CheckBloodEssence(ent.Owner, args.BloodCost))
|
||||
{
|
||||
SendFailedPopup(ent);
|
||||
return;
|
||||
}
|
||||
|
||||
var peopleInRange = _entityLookup.GetEntitiesInRange<HumanoidProfileComponent>(Transform(ent).Coordinates, 6);
|
||||
foreach (var person in peopleInRange)
|
||||
{
|
||||
if (HasComp<ThrallComponent>(person) || HasComp<VampireComponent>(person))
|
||||
continue;
|
||||
|
||||
if (HasComp<NullRodOwnerComponent>(person) && !HasTruePower(ent))
|
||||
continue;
|
||||
|
||||
if (_mobState.IsDead(person))
|
||||
continue;
|
||||
|
||||
if (HasComp<PacifiedComponent>(person))
|
||||
continue;
|
||||
|
||||
var target = person;
|
||||
EnsureComp<PacifiedComponent>(target);
|
||||
Timer.Spawn(args.Time, () => RemComp<PacifiedComponent>(target));
|
||||
}
|
||||
|
||||
SubtractBloodEssence(ent.Owner, args.BloodCost);
|
||||
args.Handled = true;
|
||||
}
|
||||
|
||||
private void OnMassHysteria(Entity<VampireComponent> ent, ref VampireMassHysteriaActionEvent args)
|
||||
{
|
||||
if (!CheckBloodEssence(ent.Owner, args.BloodCost))
|
||||
{
|
||||
SendFailedPopup(ent);
|
||||
return;
|
||||
}
|
||||
|
||||
var victimInRange = _entityLookup.GetEntitiesInRange<BodyComponent>(Transform(ent).Coordinates, 8f)
|
||||
.Where(entity => entity.Owner != ent.Owner).ToList();
|
||||
|
||||
foreach (var victimEntity in victimInRange)
|
||||
{
|
||||
if (HasComp<NullRodOwnerComponent>(victimEntity) && !HasTruePower(ent))
|
||||
continue;
|
||||
|
||||
_flash.Flash(victimEntity, ent, null, TimeSpan.FromSeconds(4f), 0.5f);
|
||||
_hallucinations.StartHallucinations(victimEntity, "Hallucinations", TimeSpan.FromSeconds(30f), true, "MindBreaker");
|
||||
}
|
||||
|
||||
SubtractBloodEssence(ent.Owner, args.BloodCost);
|
||||
args.Handled = true;
|
||||
}
|
||||
|
||||
#region Utility Methods
|
||||
|
||||
private void ExecuteBloodBondTick(Entity<VampireComponent> ent, SupremeVampireComponent supreme, ThrallOwnerComponent thrallOwner, VampireBloodBondActionEvent args)
|
||||
{
|
||||
if (!Exists(ent) || !supreme.Active)
|
||||
{
|
||||
supreme.Active = false;
|
||||
thrallOwner.DamageSharing = false;
|
||||
Dirty(ent.Owner, supreme);
|
||||
return;
|
||||
}
|
||||
|
||||
if (!CheckBloodEssence(ent.Owner, args.BloodCost))
|
||||
{
|
||||
SendFailedPopup(ent);
|
||||
|
||||
supreme.Active = false;
|
||||
thrallOwner.DamageSharing = false;
|
||||
Dirty(ent.Owner, supreme);
|
||||
return;
|
||||
}
|
||||
|
||||
SubtractBloodEssence(ent.Owner, args.BloodCost);
|
||||
|
||||
Timer.Spawn(args.TimeInterval, () => ExecuteBloodBondTick(ent, supreme, thrallOwner, args));
|
||||
}
|
||||
|
||||
private void ExecuteThrallHealTick(EntityUid thrallUid, Entity<VampireComponent> vampire, int currentTick, VampireThrallHealActionEvent args)
|
||||
{
|
||||
if (!Exists(thrallUid) || !Exists(vampire) || currentTick >= args.Repeats)
|
||||
return;
|
||||
|
||||
var healingSpec = CalculateScaledHealing(thrallUid, args.Heal, args.HealGroups);
|
||||
_damage.TryChangeDamage(thrallUid, healingSpec, true, false, origin: vampire);
|
||||
|
||||
Timer.Spawn(args.TimeInterval, () => ExecuteThrallHealTick(thrallUid, vampire, currentTick + 1, args));
|
||||
}
|
||||
|
||||
#endregion
|
||||
}
|
||||
@@ -0,0 +1,289 @@
|
||||
using System.Numerics;
|
||||
using Content.Shared.Body;
|
||||
using Content.Shared.CombatMode;
|
||||
using Content.Shared.Damage.Prototypes;
|
||||
using Content.Shared.Ensnaring.Components;
|
||||
using Content.Shared.FixedPoint;
|
||||
using Content.Shared.Movement.Components;
|
||||
using Content.Shared.Movement.Pulling.Components;
|
||||
using Content.Shared.NullRod.Components;
|
||||
using Content.Shared.Popups;
|
||||
using Content.Shared.Prying.Components;
|
||||
using Content.Shared.Vampire;
|
||||
using Content.Shared.Vampire.Components;
|
||||
using Content.Shared.Weapons.Melee;
|
||||
using Robust.Shared.Map.Components;
|
||||
using Robust.Shared.Physics.Components;
|
||||
using Robust.Shared.Prototypes;
|
||||
using Robust.Shared.Random;
|
||||
using Robust.Shared.Timing;
|
||||
|
||||
namespace Content.Server.Vampire;
|
||||
|
||||
public sealed partial class VampireSystem
|
||||
{
|
||||
public static readonly ProtoId<DamageModifierSetPrototype> BloodSwell = "VampireBloodSwell";
|
||||
|
||||
private void InitializeGargantua()
|
||||
{
|
||||
SubscribeLocalEvent<VampireComponent, VampireBloodSwellActionEvent>(OnBloodSwell);
|
||||
SubscribeLocalEvent<VampireComponent, VampireBloodRushActionEvent>(OnBloodRush);
|
||||
SubscribeLocalEvent<VampireComponent, VampireSeismicStompActionEvent>(OnSeismicStomp);
|
||||
SubscribeLocalEvent<VampireComponent, VampireOverwhelmingForceActionEvent>(OnOverwhelmingForce);
|
||||
SubscribeLocalEvent<VampireComponent, VampireDemonicGraspActionEvent>(OnDemonicGrasp);
|
||||
SubscribeLocalEvent<VampireComponent, VampireChargeActionEvent>(OnCharge);
|
||||
}
|
||||
|
||||
private void OnBloodSwell(Entity<VampireComponent> ent, ref VampireBloodSwellActionEvent args)
|
||||
{
|
||||
if (!CheckBloodEssence(ent.Owner, args.BloodCost))
|
||||
{
|
||||
SendFailedPopup(ent);
|
||||
return;
|
||||
}
|
||||
|
||||
_damage.SetDamageModifierSetId(ent.Owner, BloodSwell);
|
||||
|
||||
if (args.Advanced)
|
||||
{
|
||||
if (TryComp(ent, out MeleeWeaponComponent? meleeWeapon))
|
||||
{
|
||||
FixedPoint2? oldDamageValue = null;
|
||||
var damageDict = meleeWeapon.Damage.DamageDict;
|
||||
|
||||
if (damageDict.ContainsKey(args.BonusDamageType))
|
||||
{
|
||||
oldDamageValue = damageDict[args.BonusDamageType];
|
||||
damageDict[args.BonusDamageType] += args.BonusDamageAmount;
|
||||
}
|
||||
else
|
||||
{
|
||||
damageDict[args.BonusDamageType] = args.BonusDamageAmount;
|
||||
}
|
||||
|
||||
var savedOldDamage = oldDamageValue;
|
||||
var damageType = args.BonusDamageType;
|
||||
var bonusAmount = args.BonusDamageAmount;
|
||||
|
||||
Timer.Spawn(args.Time, () =>
|
||||
{
|
||||
if (TryComp(ent, out MeleeWeaponComponent? weapon))
|
||||
{
|
||||
if (savedOldDamage.HasValue && weapon.Damage.DamageDict.ContainsKey(damageType))
|
||||
{
|
||||
weapon.Damage.DamageDict[damageType] = savedOldDamage.Value;
|
||||
}
|
||||
else if (!savedOldDamage.HasValue)
|
||||
{
|
||||
weapon.Damage.DamageDict.Remove(damageType);
|
||||
}
|
||||
}
|
||||
|
||||
_damage.SetDamageModifierSetId(ent.Owner, VampireComponent.VampireDamageModifier);
|
||||
});
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
Timer.Spawn(args.Time, () =>
|
||||
{
|
||||
_damage.SetDamageModifierSetId(ent.Owner, VampireComponent.VampireDamageModifier);
|
||||
});
|
||||
}
|
||||
|
||||
SubtractBloodEssence(ent.Owner, args.BloodCost);
|
||||
args.Handled = true;
|
||||
}
|
||||
|
||||
private void OnBloodRush(Entity<VampireComponent> ent, ref VampireBloodRushActionEvent args)
|
||||
{
|
||||
if (!CheckBloodEssence(ent.Owner, args.BloodCost))
|
||||
{
|
||||
SendFailedPopup(ent);
|
||||
return;
|
||||
}
|
||||
|
||||
if (TryComp(ent, out MovementSpeedModifierComponent? speedmodComponent))
|
||||
{
|
||||
var originalWalkSpeed = speedmodComponent.BaseWalkSpeed;
|
||||
var originalSprintSpeed = speedmodComponent.BaseSprintSpeed;
|
||||
|
||||
_speed.ChangeBaseSpeed(ent, originalWalkSpeed * 2, originalSprintSpeed * 2, speedmodComponent.Acceleration);
|
||||
|
||||
var time = HasTruePower(ent) ? args.Time * 2 : args.Time;
|
||||
Timer.Spawn(time, () =>
|
||||
{
|
||||
_speed.ChangeBaseSpeed(ent, originalWalkSpeed, originalSprintSpeed, speedmodComponent.Acceleration);
|
||||
});
|
||||
}
|
||||
|
||||
SubtractBloodEssence(ent.Owner, args.BloodCost);
|
||||
args.Handled = true;
|
||||
}
|
||||
|
||||
private void OnSeismicStomp(Entity<VampireComponent> ent, ref VampireSeismicStompActionEvent args)
|
||||
{
|
||||
if (TryComp(ent, out EnsnareableComponent? ensnareable) && ensnareable.IsEnsnared)
|
||||
{
|
||||
_popup.PopupEntity(Loc.GetString("vampire-legs-ensnared"), ent, ent, PopupType.Medium);
|
||||
return;
|
||||
}
|
||||
|
||||
if (!CheckBloodEssence(ent.Owner, args.BloodCost))
|
||||
{
|
||||
SendFailedPopup(ent);
|
||||
return;
|
||||
}
|
||||
|
||||
var vampirePos = _transform.GetWorldPosition(ent);
|
||||
|
||||
var gridUid = _transform.GetGrid(ent.Owner);
|
||||
if (gridUid != null && TryComp<MapGridComponent>(gridUid.Value, out var grid))
|
||||
{
|
||||
var tiles = _map.GetTilesIntersecting(gridUid.Value, grid,
|
||||
Box2.CenteredAround(vampirePos, new Vector2(6, 6)), ignoreEmpty: true);
|
||||
|
||||
foreach (var tile in tiles)
|
||||
{
|
||||
if (!_random.Prob(0.5f))
|
||||
continue;
|
||||
|
||||
_tile.PryTile(tile);
|
||||
}
|
||||
}
|
||||
|
||||
var nearbyHumanoids = _entityLookup.GetEntitiesInRange<BodyComponent>(Transform(ent).Coordinates, 3f);
|
||||
foreach (var humanoid in nearbyHumanoids)
|
||||
{
|
||||
var humanoidUid = humanoid.Owner;
|
||||
if (humanoidUid == ent.Owner) continue;
|
||||
|
||||
if (HasComp<NullRodOwnerComponent>(humanoidUid) && !HasTruePower(ent))
|
||||
continue;
|
||||
|
||||
if (!TryComp(humanoid, out PhysicsComponent? physics))
|
||||
continue;
|
||||
|
||||
var humanoidPosition = _transform.GetWorldPosition(humanoid);
|
||||
var direction = (humanoidPosition - vampirePos).Normalized();
|
||||
|
||||
var force = 10f;
|
||||
if (physics.Mass <= 50f)
|
||||
force *= 2;
|
||||
|
||||
_throwing.TryThrow(humanoidUid, direction * (force / 2), force);
|
||||
}
|
||||
|
||||
_audio.PlayPvs(args.Sound, ent);
|
||||
SubtractBloodEssence(ent.Owner, args.BloodCost);
|
||||
args.Handled = true;
|
||||
}
|
||||
|
||||
private void OnOverwhelmingForce(Entity<VampireComponent> ent, ref VampireOverwhelmingForceActionEvent args)
|
||||
{
|
||||
if (TryComp(ent, out EnsnareableComponent? ensnareable) && ensnareable.IsEnsnared)
|
||||
{
|
||||
_popup.PopupEntity(Loc.GetString("vampire-legs-ensnared"), ent, ent, PopupType.Medium);
|
||||
return;
|
||||
}
|
||||
|
||||
bool prying = HasComp<PryingComponent>(ent);
|
||||
if (!prying && HasComp<PullableComponent>(ent))
|
||||
{
|
||||
if (!CheckBloodEssence(ent.Owner, args.BloodCost))
|
||||
{
|
||||
SendFailedPopup(ent);
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
if (prying)
|
||||
{
|
||||
RemComp<PryingComponent>(ent);
|
||||
EnsureComp<EnsnareableComponent>(ent);
|
||||
EnsureComp<PullableComponent>(ent);
|
||||
}
|
||||
else
|
||||
{
|
||||
var pryComponent = EnsureComp<PryingComponent>(ent);
|
||||
|
||||
pryComponent.PryPowered = true;
|
||||
pryComponent.Force = true;
|
||||
pryComponent.SpeedModifier = 2.5f;
|
||||
|
||||
SubtractBloodEssence(ent.Owner, args.BloodCost);
|
||||
|
||||
RemComp<EnsnareableComponent>(ent);
|
||||
RemComp<PullableComponent>(ent);
|
||||
}
|
||||
|
||||
args.Handled = true;
|
||||
}
|
||||
|
||||
private void OnDemonicGrasp(Entity<VampireComponent> ent, ref VampireDemonicGraspActionEvent args)
|
||||
{
|
||||
if (!TryComp<CombatModeComponent>(ent, out var combatMode))
|
||||
return;
|
||||
|
||||
if (!CheckBloodEssence(ent.Owner, args.BloodCost))
|
||||
{
|
||||
SendFailedPopup(ent);
|
||||
return;
|
||||
}
|
||||
|
||||
var target = args.Target;
|
||||
if (HasComp<NullRodOwnerComponent>(target) && !HasTruePower(ent))
|
||||
return;
|
||||
|
||||
var vampirePosition = _transform.GetWorldPosition(ent);
|
||||
var targetPosition = _transform.GetWorldPosition(target);
|
||||
var direction = (vampirePosition - targetPosition).Normalized();
|
||||
|
||||
if (HasComp<PhysicsComponent>(target))
|
||||
{
|
||||
if (!combatMode.IsInCombatMode)
|
||||
{
|
||||
_throwing.TryThrow(target, -direction * 3);
|
||||
_stun.TryUpdateStunDuration(target, TimeSpan.FromSeconds(3f));
|
||||
}
|
||||
else
|
||||
{
|
||||
_throwing.TryThrow(target, direction * 3);
|
||||
_stun.TryUpdateStunDuration(target, TimeSpan.FromSeconds(3f));
|
||||
}
|
||||
}
|
||||
|
||||
SubtractBloodEssence(ent.Owner, args.BloodCost);
|
||||
args.Handled = true;
|
||||
}
|
||||
|
||||
private void OnCharge(Entity<VampireComponent> ent, ref VampireChargeActionEvent args)
|
||||
{
|
||||
if (TryComp(ent, out EnsnareableComponent? ensnareable) && ensnareable.IsEnsnared)
|
||||
{
|
||||
_popup.PopupEntity(Loc.GetString("vampire-legs-ensnared"), ent, ent, PopupType.Medium);
|
||||
return;
|
||||
}
|
||||
|
||||
if (!CheckBloodEssence(ent.Owner, args.BloodCost))
|
||||
{
|
||||
SendFailedPopup(ent);
|
||||
return;
|
||||
}
|
||||
|
||||
var coords = args.Target;
|
||||
var vampirePosition = _transform.GetWorldPosition(ent);
|
||||
var targetPosition = _transform.ToMapCoordinates(coords, true).Position;
|
||||
var direction = (targetPosition - vampirePosition).Normalized();
|
||||
|
||||
// Well, that might cause it to deal damage when the space wind,
|
||||
// but that doesn't seem like a problem, does it?
|
||||
EntityManager.AddComponents(ent, args.EnsurableComponents, false);
|
||||
|
||||
_throwing.TryThrow(ent, direction * 3);
|
||||
|
||||
_audio.PlayPvs(args.Sound, ent);
|
||||
SubtractBloodEssence(ent.Owner, args.BloodCost);
|
||||
args.Handled = true;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,396 @@
|
||||
using System.Linq;
|
||||
using System.Numerics;
|
||||
using Content.Shared.Body.Components;
|
||||
using Content.Shared.Chemistry.Components;
|
||||
using Content.Shared.Clothing;
|
||||
using Content.Shared.Damage.Components;
|
||||
using Content.Shared.Damage.Systems;
|
||||
using Content.Shared.Fluids.Components;
|
||||
using Content.Shared.Humanoid;
|
||||
using Content.Shared.Localizations;
|
||||
using Content.Shared.Mobs.Components;
|
||||
using Content.Shared.NullRod.Components;
|
||||
using Content.Shared.Physics;
|
||||
using Content.Shared.Popups;
|
||||
using Content.Shared.Random.Helpers;
|
||||
using Content.Shared.Roles;
|
||||
using Content.Shared.Standing;
|
||||
using Content.Shared.Surgery.Components;
|
||||
using Content.Shared.Vampire;
|
||||
using Content.Shared.Vampire.Components;
|
||||
using Content.Shared.Weapons.Melee.Events;
|
||||
using Robust.Shared.Containers;
|
||||
using Robust.Shared.Map;
|
||||
using Robust.Shared.Map.Components;
|
||||
using Robust.Shared.Prototypes;
|
||||
using Robust.Shared.Random;
|
||||
using Robust.Shared.Timing;
|
||||
|
||||
namespace Content.Server.Vampire;
|
||||
|
||||
public sealed partial class VampireSystem
|
||||
{
|
||||
[Dependency] private readonly LoadoutSystem _loadout = default!;
|
||||
|
||||
private void InitializeHemomancer()
|
||||
{
|
||||
SubscribeLocalEvent<VampireClawsComponent, MeleeHitEvent>(OnClawsHit);
|
||||
SubscribeLocalEvent<VampireClawsComponent, BeforeDamageChangedEvent>(OnBeforeDamageChanged);
|
||||
|
||||
SubscribeLocalEvent<VampireComponent, VampireClawsActionEvent>(GiveVampireClaws);
|
||||
SubscribeLocalEvent<VampireComponent, VampireBloodTentacleAction>(OnBloodTendrils);
|
||||
SubscribeLocalEvent<VampireComponent, VampireBloodBarrierActionEvent>(OnBloodBarrierAction);
|
||||
SubscribeLocalEvent<VampireComponent, VampireSanguinePoolActionEvent>(OnSanguinePoolAction);
|
||||
SubscribeLocalEvent<VampireComponent, VampirePredatorSensesActionEvent>(OnVampirePredatorSensesAction);
|
||||
SubscribeLocalEvent<VampireComponent, VampireBloodEruptionActionEvent>(OnVampireBloodEruptionAction);
|
||||
SubscribeLocalEvent<VampireComponent, VampireBloodBringersRiteActionEvent>(OnBloodBringersRite);
|
||||
}
|
||||
|
||||
private void OnClawsHit(Entity<VampireClawsComponent> ent, ref MeleeHitEvent args)
|
||||
{
|
||||
if (args.HitEntities.Count == 0)
|
||||
return;
|
||||
|
||||
foreach (var hitEnt in args.HitEntities)
|
||||
{
|
||||
if (HasComp<SyntheticOperatedComponent>(hitEnt))
|
||||
continue;
|
||||
|
||||
if (!HasComp<BloodstreamComponent>(hitEnt))
|
||||
continue;
|
||||
|
||||
var groupsHeal = _damage.CreateWeightedHealFromGroups(args.User, ent.Comp.HealGroups);
|
||||
|
||||
_damage.TryChangeDamage(ent.Owner, groupsHeal, true, false, origin: ent);
|
||||
_stamina.TakeStaminaDamage(ent, ent.Comp.StaminaMod, visual: false);
|
||||
|
||||
AddBloodEssence(args.User, ent.Comp.BloodStealAmount);
|
||||
_blood.TryModifyBleedAmount(hitEnt, -ent.Comp.BloodStealAmount.Float() * 2);
|
||||
}
|
||||
}
|
||||
|
||||
private void OnBeforeDamageChanged(Entity<VampireClawsComponent> ent, ref BeforeDamageChangedEvent args)
|
||||
{
|
||||
var vampire = Transform(ent).ParentUid; // We assume that the current parent is a vampire.
|
||||
var supreme = GetTruePower(vampire);
|
||||
if (supreme == null)
|
||||
return;
|
||||
|
||||
if (supreme.Active) args.Cancelled = true;
|
||||
}
|
||||
|
||||
private void GiveVampireClaws(Entity<VampireComponent> ent, ref VampireClawsActionEvent args)
|
||||
{
|
||||
if (!CheckBloodEssence(ent.Owner, args.BloodCost))
|
||||
{
|
||||
SendFailedPopup(ent);
|
||||
return;
|
||||
}
|
||||
|
||||
var dropEvent = new DropHandItemsEvent();
|
||||
RaiseLocalEvent(ent, ref dropEvent);
|
||||
|
||||
List<ProtoId<StartingGearPrototype>> gear = new() { args.ProtoId };
|
||||
_loadout.Equip(ent, gear, null);
|
||||
|
||||
SubtractBloodEssence(ent.Owner, args.BloodCost);
|
||||
args.Handled = true;
|
||||
}
|
||||
|
||||
private void OnBloodTendrils(Entity<VampireComponent> ent, ref VampireBloodTentacleAction args)
|
||||
{
|
||||
if (!CheckBloodEssence(ent.Owner, args.BloodCost))
|
||||
{
|
||||
SendFailedPopup(ent);
|
||||
return;
|
||||
}
|
||||
|
||||
var coords = args.Target;
|
||||
List<EntityCoordinates> spawnPos = new();
|
||||
spawnPos.Add(coords);
|
||||
|
||||
var dirs = new List<Direction>();
|
||||
dirs.AddRange(args.OffsetDirections);
|
||||
|
||||
for (var i = 0; i < args.ExtraSpawns; i++)
|
||||
{
|
||||
var dir = _random.PickAndTake(dirs);
|
||||
var vector = DirectionToVector2(dir);
|
||||
spawnPos.Add(coords.Offset(vector));
|
||||
}
|
||||
|
||||
if (_transform.GetGrid(coords) is not { } grid || !TryComp<MapGridComponent>(grid, out var gridComp))
|
||||
return;
|
||||
|
||||
foreach (var pos in spawnPos)
|
||||
{
|
||||
if (!_map.TryGetTileRef(grid, gridComp, pos, out var tileRef)
|
||||
|| _turf.IsTileBlocked(tileRef, CollisionGroup.Impassable))
|
||||
continue;
|
||||
|
||||
Spawn(args.EntityId, pos);
|
||||
}
|
||||
|
||||
SubtractBloodEssence(ent.Owner, args.BloodCost);
|
||||
args.Handled = true;
|
||||
}
|
||||
|
||||
private void OnBloodBarrierAction(Entity<VampireComponent> ent, ref VampireBloodBarrierActionEvent args)
|
||||
{
|
||||
if (!CheckBloodEssence(ent.Owner, args.BloodCost))
|
||||
{
|
||||
SendFailedPopup(ent);
|
||||
return;
|
||||
}
|
||||
|
||||
var targetCoords = args.Target;
|
||||
var transform = Transform(ent);
|
||||
var direction = transform.LocalRotation.ToWorldVec().Normalized();
|
||||
|
||||
var perpendicularDirection = new Vector2(-direction.Y, direction.X);
|
||||
|
||||
var objectCount = 0;
|
||||
for (int i = -1; i <= 1 && objectCount < 3; i++)
|
||||
{
|
||||
var spawnPosition = targetCoords.Offset(perpendicularDirection * (1f * i));
|
||||
|
||||
if (TrySpawnObjectAtPosition(spawnPosition, args.EntityId, ent))
|
||||
objectCount++;
|
||||
}
|
||||
|
||||
SubtractBloodEssence(ent.Owner, args.BloodCost);
|
||||
args.Handled = true;
|
||||
}
|
||||
|
||||
private void OnSanguinePoolAction(Entity<VampireComponent> ent, ref VampireSanguinePoolActionEvent args)
|
||||
{
|
||||
if (!CheckBloodEssence(ent.Owner, args.BloodCost))
|
||||
{
|
||||
SendFailedPopup(ent);
|
||||
return;
|
||||
}
|
||||
|
||||
var polymorphedEntity = _polymorph.PolymorphEntity(ent, args.PolymorphProto);
|
||||
if (polymorphedEntity == null)
|
||||
return;
|
||||
|
||||
SubtractBloodEssence(ent.Owner, args.BloodCost);
|
||||
args.Handled = true;
|
||||
}
|
||||
|
||||
private void OnVampirePredatorSensesAction(Entity<VampireComponent> ent, ref VampirePredatorSensesActionEvent args)
|
||||
{
|
||||
var centerCoords = Transform(ent).Coordinates;
|
||||
var nearbyHumanoids = _entityLookup.GetEntitiesInRange<HumanoidProfileComponent>(centerCoords, 6f);
|
||||
|
||||
foreach (var humanoidEntity in nearbyHumanoids)
|
||||
{
|
||||
var humanoid = humanoidEntity.Owner;
|
||||
if (humanoid == ent.Owner)
|
||||
continue;
|
||||
|
||||
if (_mobState.IsIncapacitated(humanoid))
|
||||
continue;
|
||||
|
||||
Spawn(args.EntityId, Transform(humanoid).Coordinates);
|
||||
_audio.PlayPvs(args.Sound, humanoid);
|
||||
_popup.PopupEntity(Loc.GetString("vampire-predator-senses-puddle"), humanoid, humanoid, PopupType.SmallCaution);
|
||||
_stun.TryUpdateParalyzeDuration(humanoid, TimeSpan.FromSeconds(4));
|
||||
args.Handled = true;
|
||||
return;
|
||||
}
|
||||
|
||||
var closestHumanoid = FindClosestHumanoidOnMap(ent);
|
||||
if (closestHumanoid != null)
|
||||
{
|
||||
var direction = GetDirectionToTarget(ent, closestHumanoid.Value);
|
||||
var directionString = ContentLocalizationManager.FormatDirection(direction).ToLower();
|
||||
var msg = Loc.GetString("vampire-predator-senses-warning", ("direction", directionString));
|
||||
_popup.PopupEntity(msg, ent, ent, PopupType.Medium);
|
||||
}
|
||||
else
|
||||
{
|
||||
_popup.PopupEntity(Loc.GetString("vampire-predator-senses-nobody"), ent, ent, PopupType.SmallCaution);
|
||||
}
|
||||
|
||||
args.Handled = true;
|
||||
}
|
||||
|
||||
private void OnVampireBloodEruptionAction(Entity<VampireComponent> ent, ref VampireBloodEruptionActionEvent args)
|
||||
{
|
||||
if (!CheckBloodEssence(ent.Owner, args.BloodCost))
|
||||
{
|
||||
SendFailedPopup(ent);
|
||||
return;
|
||||
}
|
||||
|
||||
var puddlesInRange = _entityLookup.GetEntitiesInRange<PuddleComponent>(Transform(ent).Coordinates, 4f)
|
||||
.Where(puddle => TryComp(puddle.Owner, out ContainerManagerComponent? containerManager)
|
||||
&& containerManager.Containers.TryGetValue("solution@puddle", out var container)
|
||||
&& container.ContainedEntities.Any(containedEntity =>
|
||||
TryComp(containedEntity, out SolutionComponent? solutionComponent)
|
||||
&& solutionComponent.Solution.Contents.Any(r =>
|
||||
BloodProto.Contains(r.Reagent.Prototype))))
|
||||
.ToList();
|
||||
|
||||
foreach (var puddleEntity in puddlesInRange)
|
||||
{
|
||||
var entitiesOnPuddle = _entityLookup.GetEntitiesInRange<DamageableComponent>(Transform(puddleEntity.Owner).Coordinates, 0.1f)
|
||||
.Where(entity => entity.Owner != ent.Owner).ToList();
|
||||
|
||||
foreach (var targetEntity in entitiesOnPuddle)
|
||||
{
|
||||
if (HasComp<NullRodOwnerComponent>(targetEntity.Owner) && !HasTruePower(ent))
|
||||
continue;
|
||||
|
||||
_damage.TryChangeDamage(targetEntity.Owner, args.Damage, origin: ent);
|
||||
_stun.TryUpdateParalyzeDuration(targetEntity.Owner, TimeSpan.FromSeconds(3));
|
||||
_popup.PopupEntity(Loc.GetString("vampire-blood-eruption-effect-message"), targetEntity.Owner, ent, PopupType.MediumCaution);
|
||||
}
|
||||
}
|
||||
|
||||
SubtractBloodEssence(ent.Owner, args.BloodCost);
|
||||
args.Handled = true;
|
||||
}
|
||||
|
||||
private void OnBloodBringersRite(Entity<VampireComponent> ent, ref VampireBloodBringersRiteActionEvent args)
|
||||
{
|
||||
var supreme = GetTruePower(ent);
|
||||
if (supreme == null)
|
||||
return;
|
||||
|
||||
if (!CheckBloodEssence(ent.Owner, args.BloodCost))
|
||||
{
|
||||
SendFailedPopup(ent);
|
||||
return;
|
||||
}
|
||||
|
||||
if (supreme.Active)
|
||||
{
|
||||
supreme.Active = false;
|
||||
_alerts.ShowAlert(ent.Owner, args.Alert, 0);
|
||||
Dirty(ent.Owner, supreme);
|
||||
args.Handled = true;
|
||||
return;
|
||||
}
|
||||
|
||||
supreme.Active = true;
|
||||
_alerts.ShowAlert(ent.Owner, args.Alert, 1);
|
||||
Dirty(ent.Owner, supreme);
|
||||
|
||||
_popup.PopupEntity(Loc.GetString("vampire-blood-true-power-started"), ent, ent, PopupType.SmallCaution);
|
||||
|
||||
ExecuteBloodBringersRiteTick(ent, supreme, args);
|
||||
SubtractBloodEssence(ent.Owner, args.BloodCost);
|
||||
}
|
||||
|
||||
#region Utility Methods
|
||||
|
||||
private void ExecuteBloodBringersRiteTick(Entity<VampireComponent> ent, SupremeVampireComponent supreme, VampireBloodBringersRiteActionEvent args)
|
||||
{
|
||||
if (!Exists(ent) || !supreme.Active)
|
||||
{
|
||||
supreme.Active = false;
|
||||
_alerts.ShowAlert(ent.Owner, args.Alert, 0);
|
||||
Dirty(ent.Owner, supreme);
|
||||
return;
|
||||
}
|
||||
|
||||
if (!CheckBloodEssence(ent.Owner, args.BloodCost))
|
||||
{
|
||||
SendFailedPopup(ent);
|
||||
|
||||
supreme.Active = false;
|
||||
_alerts.ShowAlert(ent.Owner, args.Alert, 0);
|
||||
Dirty(ent.Owner, supreme);
|
||||
return;
|
||||
}
|
||||
|
||||
SubtractBloodEssence(ent.Owner, args.BloodCost);
|
||||
|
||||
var nearbyEntities = _entityLookup.GetEntitiesInRange<MobStateComponent>(Transform(ent).Coordinates, 7f)
|
||||
.Where(entity => !_mobState.IsDead(entity.Owner) && !HasComp<SyntheticOperatedComponent>(entity.Owner))
|
||||
.ToList();
|
||||
|
||||
if (nearbyEntities.Count > 0)
|
||||
{
|
||||
var groupHealSpec = _damage.CreateHealFromGroups(ent.Owner, args.HealGroups);
|
||||
var scaledHealingSpec = (args.Heal + groupHealSpec) * nearbyEntities.Count;
|
||||
|
||||
_damage.TryChangeDamage(ent.Owner, scaledHealingSpec, true, false, origin: ent);
|
||||
_stamina.TakeStaminaDamage(ent, args.StaminaMod * nearbyEntities.Count, visual: false);
|
||||
|
||||
foreach (var entity in nearbyEntities)
|
||||
{
|
||||
if (HasComp<NullRodOwnerComponent>(entity.Owner) && !HasTruePower(ent))
|
||||
continue;
|
||||
|
||||
_audio.PlayPvs(args.Sound, entity);
|
||||
_blood.TryBleedOut(entity.Owner, args.BloodCost);
|
||||
_popup.PopupEntity(Loc.GetString("vampire-blood-true-power-affected"), entity.Owner, entity.Owner, PopupType.SmallCaution);
|
||||
}
|
||||
}
|
||||
|
||||
Timer.Spawn(args.TimeInterval, () => ExecuteBloodBringersRiteTick(ent, supreme, args));
|
||||
}
|
||||
|
||||
private EntityUid? FindClosestHumanoidOnMap(Entity<VampireComponent> ent)
|
||||
{
|
||||
var currentMap = Transform(ent).MapID;
|
||||
var currentCoords = Transform(ent).Coordinates;
|
||||
|
||||
EntityUid? closestHumanoid = null;
|
||||
float closestDistance = float.MaxValue;
|
||||
|
||||
var query = EntityQueryEnumerator<HumanoidProfileComponent, TransformComponent>();
|
||||
while (query.MoveNext(out var humanoid, out _, out var transform))
|
||||
{
|
||||
if (humanoid == ent.Owner)
|
||||
continue;
|
||||
|
||||
if (transform.MapID != currentMap)
|
||||
continue;
|
||||
|
||||
if (_mobState.IsIncapacitated(humanoid))
|
||||
continue;
|
||||
|
||||
var humanoidCoords = _transform.GetMapCoordinates(humanoid, transform);
|
||||
var distance = Vector2.Distance(currentCoords.Position, humanoidCoords.Position);
|
||||
|
||||
if (distance < closestDistance)
|
||||
{
|
||||
closestDistance = distance;
|
||||
closestHumanoid = humanoid;
|
||||
}
|
||||
}
|
||||
|
||||
return closestHumanoid;
|
||||
}
|
||||
|
||||
private Direction GetDirectionToTarget(Entity<VampireComponent> source, EntityUid target)
|
||||
{
|
||||
var sourceCoords = Transform(source).Coordinates;
|
||||
var targetCoords = Transform(target).Coordinates;
|
||||
|
||||
var directionVector = targetCoords.Position - sourceCoords.Position;
|
||||
return directionVector.GetDir();
|
||||
}
|
||||
|
||||
private Vector2 DirectionToVector2(Direction direction)
|
||||
{
|
||||
return direction switch
|
||||
{
|
||||
Direction.North => new Vector2(0, 1),
|
||||
Direction.South => new Vector2(0, -1),
|
||||
Direction.East => new Vector2(1, 0),
|
||||
Direction.West => new Vector2(-1, 0),
|
||||
Direction.NorthEast => new Vector2(1, 1).Normalized(),
|
||||
Direction.NorthWest => new Vector2(-1, 1).Normalized(),
|
||||
Direction.SouthEast => new Vector2(1, -1).Normalized(),
|
||||
Direction.SouthWest => new Vector2(-1, -1).Normalized(),
|
||||
_ => Vector2.Zero,
|
||||
};
|
||||
}
|
||||
|
||||
#endregion
|
||||
}
|
||||
@@ -0,0 +1,318 @@
|
||||
using System.Linq;
|
||||
using Content.Server.Atmos.EntitySystems;
|
||||
using Content.Shared.Damage;
|
||||
using Content.Shared.Damage.Components;
|
||||
using Content.Shared.DoAfter;
|
||||
using Content.Shared.Emp;
|
||||
using Content.Shared.Mobs.Components;
|
||||
using Content.Shared.Movement.Components;
|
||||
using Content.Shared.NullRod.Components;
|
||||
using Content.Shared.Physics;
|
||||
using Content.Shared.Popups;
|
||||
using Content.Shared.Stealth.Components;
|
||||
using Content.Shared.Vampire;
|
||||
using Content.Shared.Vampire.Components;
|
||||
using Robust.Server.GameObjects;
|
||||
using Robust.Shared.Timing;
|
||||
|
||||
namespace Content.Server.Vampire;
|
||||
|
||||
public sealed partial class VampireSystem
|
||||
{
|
||||
[Dependency] private readonly AtmosphereSystem _atmosphere = default!;
|
||||
[Dependency] private readonly SharedEmpSystem _emp = default!;
|
||||
|
||||
private void InitializeUmbrae()
|
||||
{
|
||||
SubscribeLocalEvent<VampireComponent, VampireCloakOfDarknessActionEvent>(OnCloakOfDarkness);
|
||||
SubscribeLocalEvent<VampireComponent, VampireShadowSnareActionEvent>(OnShadowSnare);
|
||||
SubscribeLocalEvent<VampireComponent, VampireSoulAnchorActionEvent>(OnAfterSoulAnchor);
|
||||
SubscribeLocalEvent<VampireComponent, SoulAnchorDoAfterEvent>(OnSoulAnchorDoAfter);
|
||||
SubscribeLocalEvent<VampireComponent, VampireDarkPassageActionEvent>(OnVampireDarkPassage);
|
||||
SubscribeLocalEvent<VampireComponent, VampireExtinguishActionEvent>(OnExtinguish);
|
||||
SubscribeLocalEvent<VampireComponent, VampireShadowBoxingActionEvent>(OnShadowBoxing);
|
||||
SubscribeLocalEvent<VampireComponent, VampireEternalDarknessActionEvent>(OnEternalDarkness);
|
||||
}
|
||||
|
||||
private void OnCloakOfDarkness(Entity<VampireComponent> ent, ref VampireCloakOfDarknessActionEvent args)
|
||||
{
|
||||
if (!TryComp<StealthComponent>(ent, out var stealth))
|
||||
{
|
||||
stealth = EnsureComp<StealthComponent>(ent);
|
||||
_stealth.SetVisibility(ent, 0.3f, stealth);
|
||||
_stealth.SetEnabled(ent, false, stealth);
|
||||
}
|
||||
|
||||
if (TryComp(ent, out MovementSpeedModifierComponent? speedmodComponent))
|
||||
{
|
||||
var originalWalkSpeed = speedmodComponent.BaseWalkSpeed;
|
||||
var originalSprintSpeed = speedmodComponent.BaseSprintSpeed;
|
||||
|
||||
if (stealth.Enabled)
|
||||
{
|
||||
_stealth.SetEnabled(ent, false, stealth);
|
||||
_speed.ChangeBaseSpeed(ent, originalWalkSpeed / args.SpeedMod, originalSprintSpeed / args.SpeedMod, speedmodComponent.Acceleration);
|
||||
_popup.PopupEntity(Loc.GetString("vampire-stealth-disabled"), ent, ent, PopupType.Small);
|
||||
}
|
||||
else
|
||||
{
|
||||
if (!CheckBloodEssence(ent.Owner, args.BloodCost))
|
||||
{
|
||||
SendFailedPopup(ent);
|
||||
return;
|
||||
}
|
||||
|
||||
_stealth.SetEnabled(ent, true, stealth);
|
||||
_speed.ChangeBaseSpeed(ent, originalWalkSpeed * args.SpeedMod, originalSprintSpeed * args.SpeedMod, speedmodComponent.Acceleration);
|
||||
_popup.PopupEntity(Loc.GetString("vampire-stealth-enabled"), ent, ent, PopupType.Small);
|
||||
SubtractBloodEssence(ent.Owner, args.BloodCost);
|
||||
}
|
||||
}
|
||||
|
||||
args.Handled = true;
|
||||
}
|
||||
|
||||
private void OnShadowSnare(Entity<VampireComponent> ent, ref VampireShadowSnareActionEvent args)
|
||||
{
|
||||
if (!CheckBloodEssence(ent.Owner, args.BloodCost))
|
||||
{
|
||||
SendFailedPopup(ent);
|
||||
return;
|
||||
}
|
||||
|
||||
var targetCoords = args.Target;
|
||||
if (TrySpawnObjectAtPosition(targetCoords, args.EntityId, ent))
|
||||
{
|
||||
SubtractBloodEssence(ent.Owner, args.BloodCost);
|
||||
args.Handled = true;
|
||||
}
|
||||
}
|
||||
|
||||
private void OnAfterSoulAnchor(Entity<VampireComponent> ent, ref VampireSoulAnchorActionEvent args)
|
||||
{
|
||||
if (!CheckBloodEssence(ent.Owner, args.BloodCost))
|
||||
{
|
||||
SendFailedPopup(ent);
|
||||
return;
|
||||
}
|
||||
|
||||
EntityUid? beaconEntity = null;
|
||||
var beaconQuery = EntityQueryEnumerator<BeaconSoulComponent>();
|
||||
while (beaconQuery.MoveNext(out var beaconUid, out var beaconComp))
|
||||
{
|
||||
if (beaconComp.VampireOwner == ent.Owner)
|
||||
{
|
||||
beaconEntity = beaconUid;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (beaconEntity.HasValue)
|
||||
{
|
||||
RaiseLocalEvent(ent, new SoulAnchorDoAfterEvent(args.BloodCost));
|
||||
args.Handled = true;
|
||||
return;
|
||||
}
|
||||
|
||||
args.Handled = true;
|
||||
_doAfter.TryStartDoAfter(new DoAfterArgs(EntityManager, ent, TimeSpan.FromSeconds(15f), new SoulAnchorDoAfterEvent(args.BloodCost), ent)
|
||||
{
|
||||
BreakOnMove = false,
|
||||
NeedHand = false,
|
||||
});
|
||||
}
|
||||
|
||||
private void OnSoulAnchorDoAfter(Entity<VampireComponent> ent, ref SoulAnchorDoAfterEvent args)
|
||||
{
|
||||
EntityUid? beaconEntity = null;
|
||||
var beaconQuery = EntityQueryEnumerator<BeaconSoulComponent>();
|
||||
while (beaconQuery.MoveNext(out var beaconUid, out var beaconComp))
|
||||
{
|
||||
if (beaconComp.VampireOwner == ent.Owner)
|
||||
{
|
||||
beaconEntity = beaconUid;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (beaconEntity.HasValue)
|
||||
{
|
||||
_transform.SetCoordinates(ent, Transform(beaconEntity.Value).Coordinates);
|
||||
Del(beaconEntity.Value);
|
||||
|
||||
SubtractBloodEssence(ent.Owner, args.BloodCost);
|
||||
}
|
||||
else
|
||||
{
|
||||
var beaconEntityNew = Spawn(args.EntityId, Transform(ent).Coordinates);
|
||||
var beaconComponent = EnsureComp<BeaconSoulComponent>(beaconEntityNew);
|
||||
beaconComponent.VampireOwner = ent;
|
||||
}
|
||||
}
|
||||
|
||||
private void OnVampireDarkPassage(Entity<VampireComponent> ent, ref VampireDarkPassageActionEvent args)
|
||||
{
|
||||
var targetCoords = args.Target;
|
||||
if (!HasTruePower(ent))
|
||||
{
|
||||
if (!_interaction.InRangeUnobstructed(ent, targetCoords, range: 1000F, collisionMask: CollisionGroup.Impassable, popup: false))
|
||||
{
|
||||
_popup.PopupEntity(Loc.GetString("vampire-teleport-failed"), ent, ent, PopupType.Small);
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
if (!CheckBloodEssence(ent.Owner, args.BloodCost))
|
||||
{
|
||||
SendFailedPopup(ent);
|
||||
return;
|
||||
}
|
||||
|
||||
if (HasComp<NullRodOwnerComponent>(targetCoords.EntityId) && !HasTruePower(ent))
|
||||
return;
|
||||
|
||||
var currentCoords = Transform(ent).Coordinates;
|
||||
_transform.SetCoordinates(ent, targetCoords);
|
||||
|
||||
Spawn(args.MistEffect, currentCoords);
|
||||
Spawn(args.MistReappearEffect, targetCoords);
|
||||
|
||||
SubtractBloodEssence(ent.Owner, args.BloodCost);
|
||||
args.Handled = true;
|
||||
}
|
||||
|
||||
private void OnExtinguish(Entity<VampireComponent> ent, ref VampireExtinguishActionEvent args)
|
||||
{
|
||||
if (!CheckBloodEssence(ent.Owner, args.BloodCost))
|
||||
{
|
||||
SendFailedPopup(ent);
|
||||
return;
|
||||
}
|
||||
|
||||
DamageLightsInRange(ent, 15f, args.Damage);
|
||||
_emp.EmpPulse(Transform(ent).Coordinates, 4, 5000, TimeSpan.FromSeconds(30), ent);
|
||||
|
||||
SubtractBloodEssence(ent.Owner, args.BloodCost);
|
||||
args.Handled = true;
|
||||
}
|
||||
|
||||
private void OnShadowBoxing(Entity<VampireComponent> ent, ref VampireShadowBoxingActionEvent args)
|
||||
{
|
||||
if (!CheckBloodEssence(ent.Owner, args.BloodCost))
|
||||
{
|
||||
SendFailedPopup(ent);
|
||||
return;
|
||||
}
|
||||
|
||||
ExecuteShadowBoxingTick(ent, args, 0);
|
||||
SubtractBloodEssence(ent.Owner, args.BloodCost);
|
||||
args.Handled = true;
|
||||
}
|
||||
|
||||
private void OnEternalDarkness(Entity<VampireComponent> ent, ref VampireEternalDarknessActionEvent args)
|
||||
{
|
||||
var supreme = GetTruePower(ent);
|
||||
if (supreme == null)
|
||||
return;
|
||||
|
||||
if (!CheckBloodEssence(ent.Owner, args.BloodCost))
|
||||
{
|
||||
SendFailedPopup(ent);
|
||||
return;
|
||||
}
|
||||
|
||||
var netEntity = GetNetEntity(ent);
|
||||
|
||||
if (supreme.Active)
|
||||
{
|
||||
supreme.Active = false;
|
||||
_alerts.ClearAlert(ent.Owner, args.Alert);
|
||||
Dirty(ent, supreme);
|
||||
|
||||
args.Handled = true;
|
||||
return;
|
||||
}
|
||||
|
||||
_alerts.ShowAlert(ent.Owner, args.Alert, 0);
|
||||
supreme.Active = true;
|
||||
Dirty(ent, supreme);
|
||||
|
||||
_popup.PopupEntity(Loc.GetString("vampire-blood-true-power-started"), ent, ent, PopupType.SmallCaution);
|
||||
|
||||
ExecuteEternalDarknessTick(ent, supreme, args, netEntity);
|
||||
SubtractBloodEssence(ent.Owner, args.BloodCost);
|
||||
args.Handled = true;
|
||||
}
|
||||
|
||||
#region Utility Methods
|
||||
|
||||
private void ExecuteShadowBoxingTick(EntityUid uid, VampireShadowBoxingActionEvent args, int currentTick)
|
||||
{
|
||||
if (!Exists(uid) || currentTick >= args.Repeats)
|
||||
return;
|
||||
|
||||
Spawn(args.EntityId, Transform(uid).Coordinates);
|
||||
|
||||
Timer.Spawn(args.TimeInterval, () => ExecuteShadowBoxingTick(uid, args, currentTick + 1));
|
||||
}
|
||||
|
||||
private void ExecuteEternalDarknessTick(Entity<VampireComponent> ent, SupremeVampireComponent supreme, VampireEternalDarknessActionEvent args, NetEntity netEntity)
|
||||
{
|
||||
if (!Exists(ent) || !supreme.Active)
|
||||
{
|
||||
_alerts.ClearAlert(ent.Owner, args.Alert);
|
||||
supreme.Active = false;
|
||||
Dirty(ent, supreme);
|
||||
return;
|
||||
}
|
||||
|
||||
if (!CheckBloodEssence(ent.Owner, args.BloodCost))
|
||||
{
|
||||
SendFailedPopup(ent);
|
||||
|
||||
supreme.Active = false;
|
||||
_alerts.ClearAlert(ent.Owner, args.Alert);
|
||||
Dirty(ent, supreme);
|
||||
return;
|
||||
}
|
||||
|
||||
CoolSurroundingAtmosphere(ent);
|
||||
DamageLightsInRange(ent, 4f, args.Damage);
|
||||
SubtractBloodEssence(ent.Owner, args.BloodCost);
|
||||
|
||||
Timer.Spawn(args.TimeInterval, () => ExecuteEternalDarknessTick(ent, supreme, args, netEntity));
|
||||
}
|
||||
|
||||
private void DamageLightsInRange(Entity<VampireComponent> ent, float radius, DamageSpecifier damage)
|
||||
{
|
||||
var coords = Transform(ent).Coordinates;
|
||||
var lightsInRange = _entityLookup.GetEntitiesInRange<PointLightComponent>(coords, radius)
|
||||
.Where(entity => HasComp<DamageableComponent>(entity.Owner)
|
||||
&& !HasComp<MobStateComponent>(entity.Owner)).ToList();
|
||||
|
||||
foreach (var lightEntity in lightsInRange)
|
||||
{
|
||||
_damage.TryChangeDamage(lightEntity.Owner, damage, true, origin: ent);
|
||||
}
|
||||
}
|
||||
|
||||
private void CoolSurroundingAtmosphere(EntityUid ent)
|
||||
{
|
||||
if (_atmosphere.GetContainingMixture(ent, excite: true) is { } atmosphere)
|
||||
{
|
||||
const float targetTemperature = 233.15f;
|
||||
const float coolingRate = 20000f;
|
||||
|
||||
var deltaT = targetTemperature - atmosphere.Temperature;
|
||||
if (deltaT < 0)
|
||||
{
|
||||
var heatCapacity = _atmosphere.GetHeatCapacity(atmosphere, true);
|
||||
var energyToRemove = Math.Min(Math.Abs(deltaT) * heatCapacity, coolingRate);
|
||||
|
||||
_atmosphere.AddHeat(atmosphere, -energyToRemove);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#endregion
|
||||
}
|
||||
@@ -0,0 +1,182 @@
|
||||
using Content.Shared.Damage.Components;
|
||||
using Content.Shared.Physics;
|
||||
using Content.Shared.Popups;
|
||||
using Content.Shared.Vampire;
|
||||
using Content.Shared.Vampire.Components;
|
||||
using Robust.Shared.Map;
|
||||
using Robust.Shared.Map.Components;
|
||||
using Robust.Shared.Prototypes;
|
||||
using Content.Shared.Flash.Components;
|
||||
using Content.Server.Bible.Components;
|
||||
using Robust.Shared.Timing;
|
||||
using Content.Shared.Movement.Systems;
|
||||
using Content.Shared.Damage.Systems;
|
||||
using Content.Shared.Flash;
|
||||
using Content.Shared.StatusEffectNew;
|
||||
using Content.Shared.Stunnable;
|
||||
using Content.Shared.Damage;
|
||||
using Content.Shared.Mobs.Systems;
|
||||
using Content.Shared.Mobs;
|
||||
using Content.Shared.Stealth;
|
||||
using Content.Server.Polymorph.Systems;
|
||||
using Content.Server.Surgery;
|
||||
using Content.Shared.Surgery;
|
||||
|
||||
namespace Content.Server.Vampire;
|
||||
|
||||
public sealed partial class VampireSystem
|
||||
{
|
||||
[Dependency] private readonly IPrototypeManager _proto = default!;
|
||||
[Dependency] private readonly MobThresholdSystem _threshold = default!;
|
||||
[Dependency] private readonly MovementSpeedModifierSystem _speed = default!;
|
||||
[Dependency] private readonly SharedStaminaSystem _stamina = default!;
|
||||
[Dependency] private readonly StatusEffectsSystem _status = default!;
|
||||
[Dependency] private readonly SharedStealthSystem _stealth = default!;
|
||||
[Dependency] private readonly SharedFlashSystem _flash = default!;
|
||||
[Dependency] private readonly MovementModStatusSystem _movementMod = default!;
|
||||
[Dependency] private readonly PolymorphSystem _polymorph = default!;
|
||||
[Dependency] private readonly SurgerySystem _surgery = default!;
|
||||
|
||||
private static readonly ProtoId<InternalDamagePrototype> InternalBleeding = "ArterialBleeding";
|
||||
private static readonly EntProtoId ForceSleeping = "StatusEffectForcedSleeping";
|
||||
|
||||
private void InitializePowers()
|
||||
{
|
||||
InitializeHemomancer();
|
||||
InitializeUmbrae();
|
||||
InitializeGargantua();
|
||||
InitializeDantalion();
|
||||
InitializeBestia();
|
||||
|
||||
// Basic Abilities
|
||||
SubscribeLocalEvent<VampireComponent, VampireRejuvenateActionEvent>(OnRejuvenate);
|
||||
SubscribeLocalEvent<VampireComponent, VampireGlareActionEvent>(OnVampireGlare);
|
||||
}
|
||||
|
||||
#region Basic Abilities
|
||||
|
||||
private void OnRejuvenate(Entity<VampireComponent> ent, ref VampireRejuvenateActionEvent args)
|
||||
{
|
||||
if (_mobState.IsDead(args.Performer))
|
||||
{
|
||||
_popup.PopupEntity(Loc.GetString("vampire-heal-dead"), args.Performer, args.Performer, PopupType.MediumCaution);
|
||||
return;
|
||||
}
|
||||
|
||||
TryRemoveKnockdown(args.Performer);
|
||||
_stamina.RemoveStaminaDamage(args.Performer);
|
||||
|
||||
if (args.Advanced || ent.Comp.CurrentBlood >= args.BloodCost)
|
||||
{
|
||||
ExecuteRejuvenateHealTick(args.Performer, 0, args);
|
||||
if (TryComp<VampireDiablerieComponent>(ent, out var diablerie) && diablerie.DiablerieLevel >= 3)
|
||||
_surgery.TryRemoveInternalDamage(ent, InternalBleeding);
|
||||
}
|
||||
|
||||
args.Handled = true;
|
||||
}
|
||||
|
||||
private void OnVampireGlare(Entity<VampireComponent> ent, ref VampireGlareActionEvent args)
|
||||
{
|
||||
var target = args.Target;
|
||||
if (HasComp<VampireComponent>(target) || HasComp<FlashImmunityComponent>(target))
|
||||
return;
|
||||
|
||||
if (HasComp<BibleUserComponent>(target) && !HasTruePower(ent))
|
||||
{
|
||||
_stun.TryUpdateParalyzeDuration(args.Performer, TimeSpan.FromSeconds(5f));
|
||||
_chat.TryEmoteWithoutChat(args.Performer, _proto.Index(Scream), true);
|
||||
_damage.TryChangeDamage(args.Performer, ent.Comp.HolyDamage);
|
||||
return;
|
||||
}
|
||||
|
||||
args.Handled = true;
|
||||
|
||||
var ev = new FlashAttemptEvent(target, args.Performer, null);
|
||||
RaiseLocalEvent(target, ref ev, true);
|
||||
if (ev.Cancelled)
|
||||
return;
|
||||
|
||||
_stun.TryUpdateParalyzeDuration(target, TimeSpan.FromSeconds(5f));
|
||||
_flash.Flash(target, args.Performer, null, TimeSpan.FromSeconds(3f), 0.8f);
|
||||
_status.TryAddStatusEffectDuration(target, "Muted", TimeSpan.FromSeconds(8f));
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region Utility Methods
|
||||
|
||||
private void SendFailedPopup(EntityUid uid)
|
||||
{
|
||||
_popup.PopupEntity(Loc.GetString("vampire-blood-sacrifice-insufficient-blood"), uid, uid, PopupType.SmallCaution);
|
||||
}
|
||||
|
||||
private bool TryRemoveKnockdown(Entity<StaminaComponent?> entity)
|
||||
{
|
||||
if (!Resolve(entity, ref entity.Comp, false))
|
||||
return false;
|
||||
|
||||
_status.TryRemoveStatusEffect(entity.Owner, SharedStunSystem.StunId);
|
||||
_stun.ForceStandUp(entity.Owner);
|
||||
return true;
|
||||
}
|
||||
|
||||
private void ExecuteRejuvenateHealTick(EntityUid uid, int currentTick, VampireRejuvenateActionEvent args)
|
||||
{
|
||||
if (!Exists(uid) || currentTick >= args.Repeats)
|
||||
return;
|
||||
|
||||
var healingSpec = CalculateScaledHealing(uid, args.Heal, args.HealGroups);
|
||||
|
||||
var stomachCount = GetOrganTypeCount(uid, BestiaOrganType.Stomach);
|
||||
var bonus = stomachCount * 3;
|
||||
if (bonus > 0)
|
||||
{
|
||||
var bonusMultiplier = 1f + (bonus / 100f);
|
||||
healingSpec *= bonusMultiplier;
|
||||
}
|
||||
|
||||
_damage.TryChangeDamage(uid, healingSpec, true, false, origin: uid);
|
||||
|
||||
Timer.Spawn(args.TimeInterval, () => ExecuteRejuvenateHealTick(uid, currentTick + 1, args));
|
||||
}
|
||||
|
||||
private DamageSpecifier CalculateScaledHealing(EntityUid uid, DamageSpecifier heal, GroupHealSpecifier healGroups)
|
||||
{
|
||||
var totalDamage = _damage.GetTotalDamage(uid).Float();
|
||||
if (!_threshold.TryGetThresholdForState(uid, MobState.Dead, out var threshold))
|
||||
return new DamageSpecifier();
|
||||
|
||||
var maxDamage = threshold.Value.Float();
|
||||
var damagePercent = maxDamage > 0 ? (totalDamage / maxDamage) * 100 : 0;
|
||||
var modifier = Math.Clamp(damagePercent / 20.0, 1.0, 5.0);
|
||||
|
||||
var groupHealSpec = _damage.CreateWeightedHealFromGroups(uid, healGroups);
|
||||
var scaledHeal = (heal + groupHealSpec) * modifier;
|
||||
|
||||
return scaledHeal;
|
||||
}
|
||||
|
||||
private bool TrySpawnObjectAtPosition(EntityCoordinates coords, EntProtoId entityId, EntityUid uid)
|
||||
{
|
||||
var grid = _transform.GetGrid(coords);
|
||||
if (grid == null) return false;
|
||||
|
||||
var gridEntityUid = grid.Value;
|
||||
if (!TryComp<MapGridComponent>(gridEntityUid, out var gridComp))
|
||||
return false;
|
||||
|
||||
if (!_map.TryGetTileRef(gridEntityUid, gridComp, coords, out var tileRef)
|
||||
|| _turf.IsTileBlocked(tileRef, CollisionGroup.Impassable))
|
||||
return false;
|
||||
|
||||
var ent = Spawn(entityId, coords);
|
||||
|
||||
var comp = EnsureComp<PreventCollideComponent>(ent);
|
||||
comp.Uid = uid;
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
#endregion
|
||||
}
|
||||
@@ -0,0 +1,50 @@
|
||||
using Content.Shared.HealthExaminable;
|
||||
using Content.Shared.Surgery.Components;
|
||||
using Content.Shared.Vampire.Components;
|
||||
using Robust.Shared.Timing;
|
||||
|
||||
namespace Content.Server.Vampire;
|
||||
|
||||
/// <summary>
|
||||
/// Medics can see bite on your neck.
|
||||
/// </summary>
|
||||
public sealed class BittenByVampireSystem : EntitySystem
|
||||
{
|
||||
[Dependency] private readonly IGameTiming _timing = default!;
|
||||
|
||||
public override void Initialize()
|
||||
{
|
||||
base.Initialize();
|
||||
|
||||
SubscribeLocalEvent<BittenByVampireComponent, ComponentAdd>(OnComponentAdd);
|
||||
SubscribeLocalEvent<BittenByVampireComponent, HealthBeingExaminedEvent>(OnHealthBeingExamined);
|
||||
}
|
||||
|
||||
public override void Update(float frameTime)
|
||||
{
|
||||
base.Update(frameTime);
|
||||
|
||||
var currentTime = _timing.CurTime;
|
||||
|
||||
var query = EntityQueryEnumerator<BittenByVampireComponent>();
|
||||
while (query.MoveNext(out var uid, out var bitten))
|
||||
{
|
||||
if (currentTime >= bitten.ExpirationTime)
|
||||
RemComp<BittenByVampireComponent>(uid);
|
||||
}
|
||||
}
|
||||
|
||||
private void OnComponentAdd(EntityUid uid, BittenByVampireComponent component, ComponentAdd args)
|
||||
{
|
||||
component.ExpirationTime = _timing.CurTime + TimeSpan.FromSeconds(component.LifetimeSeconds);
|
||||
}
|
||||
|
||||
private void OnHealthBeingExamined(Entity<BittenByVampireComponent> ent, ref HealthBeingExaminedEvent args)
|
||||
{
|
||||
if (HasComp<SurgicalSkillComponent>(args.Examiner))
|
||||
{
|
||||
args.Message.PushNewline();
|
||||
args.Message.AddMarkupOrThrow(Loc.GetString("vampire-bittenbyvampire-examine"));
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,51 @@
|
||||
using Content.Server.Atmos.EntitySystems;
|
||||
using Content.Server.Chat.Systems;
|
||||
using Content.Shared.Atmos.Components;
|
||||
using Content.Shared.Chat.Prototypes;
|
||||
using Content.Shared.Popups;
|
||||
using Content.Shared.Vampire.Components;
|
||||
using Robust.Shared.Prototypes;
|
||||
|
||||
namespace Content.Server.Vampire;
|
||||
|
||||
public sealed class HolyPointSystem : EntitySystem
|
||||
{
|
||||
[Dependency] private readonly ChatSystem _chat = default!;
|
||||
[Dependency] private readonly EntityLookupSystem _entityLookup = default!;
|
||||
[Dependency] private readonly FlammableSystem _flammable = default!;
|
||||
[Dependency] private readonly IPrototypeManager _proto = default!;
|
||||
[Dependency] private readonly SharedPopupSystem _popup = default!;
|
||||
|
||||
private static readonly ProtoId<EmotePrototype> Scream = "Scream";
|
||||
private static readonly float FireStackCount = 2.5f;
|
||||
|
||||
public override void Update(float frameTime)
|
||||
{
|
||||
base.Update(frameTime);
|
||||
|
||||
var holyPointQuery = EntityQueryEnumerator<HolyPointComponent>();
|
||||
while (holyPointQuery.MoveNext(out var uid, out var holyPoint))
|
||||
{
|
||||
if (holyPoint.NextTimeTick <= 0)
|
||||
{
|
||||
holyPoint.NextTimeTick = 10;
|
||||
var vampires = _entityLookup.GetEntitiesInRange<VampireComponent>(Transform(uid).Coordinates, holyPoint.Range);
|
||||
foreach (var vampire in vampires)
|
||||
{
|
||||
if (HasComp<SupremeVampireComponent>(vampire))
|
||||
continue;
|
||||
|
||||
if (TryComp(vampire.Owner, out FlammableComponent? flammable))
|
||||
{
|
||||
flammable.FireStacks += FireStackCount;
|
||||
_flammable.Ignite(vampire.Owner, uid);
|
||||
|
||||
_chat.TryEmoteWithoutChat(vampire, _proto.Index(Scream), true);
|
||||
_popup.PopupEntity(Loc.GetString("vampire-holy-point"), vampire.Owner, vampire.Owner, PopupType.LargeCaution);
|
||||
}
|
||||
}
|
||||
}
|
||||
holyPoint.NextTimeTick -= frameTime;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,39 @@
|
||||
using Content.Server.EUI;
|
||||
using Content.Shared.Vampire;
|
||||
using Content.Shared.Eui;
|
||||
using JetBrains.Annotations;
|
||||
using Content.Shared.FixedPoint;
|
||||
|
||||
namespace Content.Server.Vampire;
|
||||
|
||||
[UsedImplicitly]
|
||||
public sealed class DissectSelectionEui : BaseEui
|
||||
{
|
||||
private readonly EntityUid _vampire;
|
||||
private readonly EntityUid _target;
|
||||
private readonly FixedPoint2 _blood;
|
||||
private readonly VampireSystem _vampireSystem;
|
||||
private readonly DissectSelectionEuiState _state;
|
||||
|
||||
public DissectSelectionEui(EntityUid vampire, EntityUid target, FixedPoint2 bloodCost, VampireSystem vampireSystem, DissectSelectionEuiState state)
|
||||
{
|
||||
_vampire = vampire;
|
||||
_target = target;
|
||||
_blood = bloodCost;
|
||||
_vampireSystem = vampireSystem;
|
||||
_state = state;
|
||||
}
|
||||
|
||||
public override EuiStateBase GetNewState() => _state;
|
||||
|
||||
public override void HandleMessage(EuiMessageBase msg)
|
||||
{
|
||||
base.HandleMessage(msg);
|
||||
|
||||
if (msg is DissectOrganSelectedMessage selected)
|
||||
{
|
||||
_vampireSystem.StartDissection(_vampire, _target, selected.Target, _blood);
|
||||
Close();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,19 @@
|
||||
using Content.Server.EUI;
|
||||
using Content.Shared.Vampire;
|
||||
using Content.Shared.Eui;
|
||||
using JetBrains.Annotations;
|
||||
|
||||
namespace Content.Server.Vampire;
|
||||
|
||||
[UsedImplicitly]
|
||||
public sealed class TrophiesMenuEui : BaseEui
|
||||
{
|
||||
private readonly TrophiesEuiState _state;
|
||||
|
||||
public TrophiesMenuEui(TrophiesEuiState state)
|
||||
{
|
||||
_state = state;
|
||||
}
|
||||
|
||||
public override EuiStateBase GetNewState() => _state;
|
||||
}
|
||||
@@ -0,0 +1,30 @@
|
||||
using Content.Server.EUI;
|
||||
using Content.Shared.Vampire;
|
||||
using Content.Shared.Eui;
|
||||
|
||||
namespace Content.Server.Vampire;
|
||||
|
||||
public sealed class VampireClassSelectionEui : BaseEui
|
||||
{
|
||||
private readonly EntityUid _vampire;
|
||||
private readonly VampireSystem _vampireSystem;
|
||||
|
||||
public VampireClassSelectionEui(EntityUid vampire, VampireSystem vampireSystem)
|
||||
{
|
||||
_vampire = vampire;
|
||||
_vampireSystem = vampireSystem;
|
||||
}
|
||||
|
||||
public override EuiStateBase GetNewState() => new VampireClassSelectionState();
|
||||
|
||||
public override void HandleMessage(EuiMessageBase msg)
|
||||
{
|
||||
base.HandleMessage(msg);
|
||||
|
||||
if (msg is VampireClassSelectedMessage selected)
|
||||
{
|
||||
_vampireSystem.OnClassSelected(_vampire, selected.SelectedClass);
|
||||
Close();
|
||||
}
|
||||
}
|
||||
}
|
||||
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,232 @@
|
||||
using Content.Server.Bible.Components;
|
||||
using Content.Shared.Administration.Systems;
|
||||
using Content.Shared.Charges.Systems;
|
||||
using Content.Shared.Database;
|
||||
using Content.Shared.Examine;
|
||||
using Content.Shared.FixedPoint;
|
||||
using Content.Shared.Humanoid;
|
||||
using Content.Shared.IdentityManagement;
|
||||
using Content.Shared.IdentityManagement.Components;
|
||||
using Content.Shared.Inventory;
|
||||
using Content.Shared.Mindshield.Components;
|
||||
using Content.Shared.Popups;
|
||||
using Content.Shared.Surgery.Components;
|
||||
using Content.Shared.Vampire;
|
||||
using Content.Shared.Vampire.Components;
|
||||
using Robust.Shared.Audio;
|
||||
using Robust.Shared.Prototypes;
|
||||
|
||||
namespace Content.Server.Vampire;
|
||||
|
||||
public sealed partial class VampireSystem
|
||||
{
|
||||
[Dependency] private readonly RejuvenateSystem _rejuvenate = default!;
|
||||
[Dependency] private readonly SharedChargesSystem _charges = default!;
|
||||
|
||||
private static readonly EntProtoId Ash = "Ash";
|
||||
|
||||
private void InitializeDiablerie()
|
||||
{
|
||||
SubscribeLocalEvent<VampireDiablerieComponent, ComponentStartup>(OnDiablerieStartup);
|
||||
SubscribeLocalEvent<VampireDiablerieComponent, ExaminedEvent>(OnDiablerieExamined);
|
||||
SubscribeLocalEvent<VampireDiablerieComponent, VampireSacramentInitiationActionEvent>(OnSacramentInitiation);
|
||||
}
|
||||
|
||||
private void OnDiablerieStartup(Entity<VampireDiablerieComponent> ent, ref ComponentStartup args)
|
||||
=> ApplyDiablerieBonuses(ent);
|
||||
|
||||
private void OnDiablerieExamined(EntityUid uid, VampireDiablerieComponent component, ExaminedEvent args)
|
||||
{
|
||||
if (!args.IsInDetailsRange)
|
||||
return;
|
||||
|
||||
var name = Identity.Name(uid, EntityManager, args.Examiner);
|
||||
|
||||
if (component.DiablerieLevel >= 1 && HasComp<VampireComponent>(args.Examiner) || component.DiablerieLevel >= 3)
|
||||
args.PushMarkup(Loc.GetString("vampire-diablerie-aura-visible", ("name", name)));
|
||||
|
||||
if (component.DiablerieLevel >= 2)
|
||||
{
|
||||
bool eyesCovered = false;
|
||||
var clothes = _inventory.GetSlotEnumerator(uid, SlotFlags.WITHOUT_POCKET);
|
||||
while (clothes.NextItem(out var cloth, out _))
|
||||
{
|
||||
if (TryComp<IdentityBlockerComponent>(cloth, out var blocker) && blocker.Coverage.HasFlag(IdentityBlockerCoverage.EYES)
|
||||
&& blocker.Enabled)
|
||||
{
|
||||
eyesCovered = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (!eyesCovered)
|
||||
{
|
||||
args.PushMarkup(Loc.GetString("vampire-diablerie-eyes-glow-examined", ("name", name)));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void OnSacramentInitiation(Entity<VampireDiablerieComponent> ent, ref VampireSacramentInitiationActionEvent args)
|
||||
{
|
||||
if (!CheckBloodEssence(ent.Owner, args.BloodCost))
|
||||
{
|
||||
SendFailedPopup(ent);
|
||||
return;
|
||||
}
|
||||
|
||||
var target = args.Target;
|
||||
if (ent.Owner == target)
|
||||
return;
|
||||
|
||||
if (!_mobState.IsDead(target))
|
||||
{
|
||||
_popup.PopupEntity(Loc.GetString("vampire-initiation-failed"), ent, ent, PopupType.SmallCaution);
|
||||
return;
|
||||
}
|
||||
|
||||
if (!HasComp<HumanoidProfileComponent>(target) || HasComp<VampireComponent>(target) || HasComp<MindShieldComponent>(target))
|
||||
{
|
||||
_popup.PopupEntity(Loc.GetString("vampire-initiation-failed"), ent, ent, PopupType.SmallCaution);
|
||||
return;
|
||||
}
|
||||
|
||||
if (HasComp<SyntheticOperatedComponent>(target) || HasComp<BibleUserComponent>(target))
|
||||
{
|
||||
_popup.PopupEntity(Loc.GetString("vampire-initiation-failed"), ent, ent, PopupType.SmallCaution);
|
||||
return;
|
||||
}
|
||||
|
||||
_rejuvenate.PerformRejuvenate(target);
|
||||
|
||||
EnsureComp<VampireInferiorComponent>(target);
|
||||
var vampire = EnsureComp<VampireComponent>(target);
|
||||
var state = EnsureComp<VampireOriginalStateComponent>(target);
|
||||
SaveOriginalState((target, vampire), state);
|
||||
|
||||
MakeVampire((target, vampire), true);
|
||||
|
||||
_antag.SendBriefing(target, Loc.GetString("free-vampire-greeting"), Color.Purple,
|
||||
new SoundPathSpecifier("/Audio/_Wega/Ambience/Antag/vampare_start.ogg"));
|
||||
|
||||
SubtractBloodEssence(ent.Owner, args.BloodCost);
|
||||
args.Handled = true;
|
||||
}
|
||||
|
||||
private bool TryPerformDiablerie(Entity<VampireComponent> vampire, Entity<VampireComponent> targetVampire, FixedPoint2 volumeToEssence)
|
||||
{
|
||||
if (vampire.Comp.CurrentEvolution == VampireClassEnum.NonSelected)
|
||||
{
|
||||
_popup.PopupEntity(Loc.GetString("vampire-diablerie-no-class"), vampire, vampire, PopupType.SmallCaution);
|
||||
return false;
|
||||
}
|
||||
|
||||
var bloodToDrain = FixedPoint2.Min(targetVampire.Comp.CurrentBlood, volumeToEssence);
|
||||
SubtractBloodEssence(targetVampire.Owner, bloodToDrain);
|
||||
|
||||
if (targetVampire.Comp.CurrentBlood <= 0)
|
||||
{
|
||||
CompleteDiablerie(vampire, targetVampire);
|
||||
return false;
|
||||
}
|
||||
else
|
||||
{
|
||||
_popup.PopupEntity(Loc.GetString("vampire-diablerie-draining",
|
||||
("target", Identity.Name(targetVampire, EntityManager))), vampire, vampire);
|
||||
|
||||
_popup.PopupEntity(Loc.GetString("vampire-diablerie-target-draining"), targetVampire, targetVampire);
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
private void CompleteDiablerie(Entity<VampireComponent> vampire, Entity<VampireComponent> targetVampire)
|
||||
{
|
||||
if (targetVampire.Comp.CurrentEvolution == VampireClassEnum.NonSelected)
|
||||
{
|
||||
_popup.PopupEntity(Loc.GetString("vampire-diablerie-target-no-class"), vampire, vampire, PopupType.MediumCaution);
|
||||
return;
|
||||
}
|
||||
|
||||
var targetName = Identity.Name(targetVampire, EntityManager);
|
||||
_popup.PopupCoordinates(Loc.GetString("vampire-diablerie-dust", ("target", targetName)),
|
||||
Transform(targetVampire).Coordinates, PopupType.Large);
|
||||
|
||||
_admin.Add(LogType.Action, LogImpact.High, $"{ToPrettyString(vampire)} performed diablerie on {ToPrettyString(targetVampire)}");
|
||||
|
||||
var diablerie = EnsureComp<VampireDiablerieComponent>(vampire);
|
||||
IncreaseDiablerieLevel((vampire, diablerie));
|
||||
|
||||
Spawn(Ash, Transform(targetVampire).Coordinates);
|
||||
foreach (var item in _inventory.GetHandOrInventoryEntities(targetVampire.Owner))
|
||||
_transform.DropNextTo(item, targetVampire.Owner);
|
||||
|
||||
QueueDel(targetVampire);
|
||||
}
|
||||
|
||||
private void IncreaseDiablerieLevel(Entity<VampireDiablerieComponent> ent)
|
||||
{
|
||||
if (ent.Comp.DiablerieLevel >= ent.Comp.MaxDiablerieLevel)
|
||||
return;
|
||||
|
||||
ent.Comp.DiablerieLevel++;
|
||||
|
||||
ApplyDiablerieBonuses(ent);
|
||||
Dirty(ent.Owner, ent.Comp);
|
||||
}
|
||||
|
||||
private void ApplyDiablerieBonuses(Entity<VampireDiablerieComponent> ent, VampireComponent? vampire = null)
|
||||
{
|
||||
if (!Resolve(ent.Owner, ref vampire, false))
|
||||
return;
|
||||
|
||||
var level = ent.Comp.DiablerieLevel;
|
||||
|
||||
switch (level)
|
||||
{
|
||||
case 1:
|
||||
{
|
||||
if (vampire.RejuvenateActionEntity != null)
|
||||
{
|
||||
_charges.SetMaxCharges(vampire.RejuvenateActionEntity.Value, 2);
|
||||
}
|
||||
_popup.PopupEntity(Loc.GetString("vampire-diablerie-level-one"), ent, ent, PopupType.Medium);
|
||||
break;
|
||||
}
|
||||
case 2:
|
||||
{
|
||||
if (vampire.GlareActionEntity != null)
|
||||
{
|
||||
_charges.SetMaxCharges(vampire.GlareActionEntity.Value, 3);
|
||||
}
|
||||
_popup.PopupEntity(Loc.GetString("vampire-diablerie-level-two"), ent, ent, PopupType.Medium);
|
||||
break;
|
||||
}
|
||||
case 3:
|
||||
_popup.PopupEntity(Loc.GetString("vampire-diablerie-level-three"), ent, ent, PopupType.Medium);
|
||||
break;
|
||||
case 4:
|
||||
{
|
||||
AnnounceVampireAscended(ent);
|
||||
GrantSacramentInitiationAbility(ent);
|
||||
_popup.PopupEntity(Loc.GetString("vampire-diablerie-level-four"), ent, ent, PopupType.Large);
|
||||
break;
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
private void AnnounceVampireAscended(Entity<VampireDiablerieComponent> ent)
|
||||
{
|
||||
_chat.DispatchGlobalAnnouncement(Loc.GetString("vampire-ascended-announcement", ("name", Name(ent))), colorOverride: Color.Red);
|
||||
_admin.Add(LogType.Action, LogImpact.Extreme, $"{ToPrettyString(ent)} has reached maximum diablerie level ({ent.Comp.MaxDiablerieLevel})");
|
||||
}
|
||||
|
||||
private void GrantSacramentInitiationAbility(Entity<VampireDiablerieComponent> ent)
|
||||
{
|
||||
if (ent.Comp.SacramentInitiationActionEntity != null)
|
||||
return;
|
||||
|
||||
ent.Comp.SacramentInitiationActionEntity = _action.AddAction(ent.Owner,
|
||||
VampireDiablerieComponent.SacramentInitiationActionPrototype);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,551 @@
|
||||
using System.Linq;
|
||||
using Content.Shared.Body;
|
||||
using Content.Shared.Eye.Blinding.Components;
|
||||
using Content.Shared.Flash.Components;
|
||||
using Content.Shared.Metabolism;
|
||||
using Content.Shared.Vampire;
|
||||
using Content.Shared.Vampire.Components;
|
||||
using Robust.Shared.Containers;
|
||||
using Robust.Shared.Prototypes;
|
||||
using Robust.Shared.Random;
|
||||
|
||||
namespace Content.Server.Vampire;
|
||||
|
||||
public sealed partial class VampireSystem
|
||||
{
|
||||
private static readonly ProtoId<MetabolismStagePrototype>[] CriticalStages = new ProtoId<MetabolismStagePrototype>[]
|
||||
{
|
||||
"Bloodstream", "Respiration"
|
||||
};
|
||||
|
||||
private static readonly ProtoId<OrganCategoryPrototype> Eyes = "Eyes";
|
||||
|
||||
private void InitializeOrgans()
|
||||
{
|
||||
SubscribeLocalEvent<BestiaContainerComponent, MapInitEvent>(OnMapInit);
|
||||
SubscribeLocalEvent<BestiaContainerComponent, ComponentShutdown>(OnShutdown);
|
||||
}
|
||||
|
||||
private void OnMapInit(EntityUid uid, BestiaContainerComponent component, ref MapInitEvent args)
|
||||
{
|
||||
component.OrgansContainer = _container.EnsureContainer<Container>(uid, BestiaContainerComponent.ContainerId);
|
||||
}
|
||||
|
||||
private void OnShutdown(EntityUid uid, BestiaContainerComponent component, ref ComponentShutdown args)
|
||||
{
|
||||
var container = component.OrgansContainer;
|
||||
if (container.ContainedEntities.Count == 0)
|
||||
return;
|
||||
|
||||
var coords = Transform(uid).Coordinates;
|
||||
foreach (var organ in container.ContainedEntities)
|
||||
{
|
||||
_container.Remove(organ, container, force: true, destination: coords);
|
||||
_throwing.TryThrow(organ, _random.NextVector2(), 5f);
|
||||
}
|
||||
}
|
||||
|
||||
#region Eui State
|
||||
|
||||
public TrophiesEuiState? GetTrophiesState(Entity<VampireComponent?, BestiaContainerComponent?> vampire)
|
||||
{
|
||||
if (!Resolve(vampire, ref vampire.Comp1, false) || !Resolve(vampire, ref vampire.Comp2, false))
|
||||
return null;
|
||||
|
||||
var organs = GetOrganDisplayInfo(vampire.Comp2);
|
||||
var passives = GetPassiveBonuses((vampire.Owner, vampire.Comp2));
|
||||
var abilities = GetAbilityBonuses(vampire.Owner, vampire.Comp1, vampire.Comp2);
|
||||
|
||||
return new TrophiesEuiState(organs, passives, abilities);
|
||||
}
|
||||
|
||||
private List<OrganDisplayInfo> GetOrganDisplayInfo(BestiaContainerComponent bestia)
|
||||
{
|
||||
var result = new List<OrganDisplayInfo>();
|
||||
var counts = GetOrganCounts(bestia.OrgansContainer);
|
||||
var maxCritical = bestia.MaxCriticalOrgans;
|
||||
var maxRegular = bestia.MaxRegularOrgans;
|
||||
|
||||
foreach (BestiaOrganType type in Enum.GetValues<BestiaOrganType>())
|
||||
{
|
||||
if (type == BestiaOrganType.Unknown)
|
||||
continue;
|
||||
|
||||
var count = counts.GetValueOrDefault(type, 0);
|
||||
var max = IsCriticalOrganType(type) ? maxCritical : maxRegular;
|
||||
var (color, _) = GetOrganProgressColor(count, max);
|
||||
var preview = GetPreviewForOrganType(bestia.OrgansContainer, type);
|
||||
|
||||
result.Add(new OrganDisplayInfo
|
||||
{
|
||||
Type = type,
|
||||
Count = count,
|
||||
MaxCount = max,
|
||||
CountColor = color,
|
||||
PreviewEntity = preview
|
||||
});
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
private List<PassiveBonusInfo> GetPassiveBonuses(Entity<BestiaContainerComponent> entity)
|
||||
{
|
||||
var heartCount = GetOrganTypeCount(entity, BestiaOrganType.Heart);
|
||||
var liverCount = GetOrganTypeCount(entity, BestiaOrganType.Liver);
|
||||
var lungsCount = GetOrganTypeCount(entity, BestiaOrganType.Lungs);
|
||||
var kidneysCount = GetOrganTypeCount(entity, BestiaOrganType.Kidneys);
|
||||
var eyesCount = GetOrganTypeCount(entity, BestiaOrganType.Eyes);
|
||||
var stomachCount = GetOrganTypeCount(entity, BestiaOrganType.Stomach);
|
||||
|
||||
var maxCritical = entity.Comp.MaxCriticalOrgans;
|
||||
var maxRegular = entity.Comp.MaxRegularOrgans;
|
||||
|
||||
var passives = new List<PassiveBonusInfo>();
|
||||
|
||||
var heartColor = GetOrganProgressColor(heartCount, maxCritical);
|
||||
passives.Add(new()
|
||||
{
|
||||
Name = Loc.GetString("vampire-bestia-passive-brute-protection"),
|
||||
Value = $"-{(heartCount * 5)}%",
|
||||
ValueColor = heartColor.color,
|
||||
IsMaxed = heartColor.isMaxed
|
||||
});
|
||||
passives.Add(new()
|
||||
{
|
||||
Name = Loc.GetString("vampire-bestia-passive-burn-protection"),
|
||||
Value = $"-{(heartCount * 5)}%",
|
||||
ValueColor = heartColor.color,
|
||||
IsMaxed = heartColor.isMaxed
|
||||
});
|
||||
|
||||
var lungsColor = GetOrganProgressColor(lungsCount, maxCritical);
|
||||
passives.Add(new()
|
||||
{
|
||||
Name = Loc.GetString("vampire-bestia-passive-oxy-protection"),
|
||||
Value = $"-{(lungsCount * 5)}%",
|
||||
ValueColor = lungsColor.color,
|
||||
IsMaxed = lungsColor.isMaxed
|
||||
});
|
||||
passives.Add(new()
|
||||
{
|
||||
Name = Loc.GetString("vampire-bestia-passive-stamina"),
|
||||
Value = $"+{(lungsCount * 5)}%",
|
||||
ValueColor = lungsColor.color,
|
||||
IsMaxed = lungsColor.isMaxed
|
||||
});
|
||||
|
||||
var liverColor = GetOrganProgressColor(liverCount, maxRegular);
|
||||
passives.Add(new()
|
||||
{
|
||||
Name = Loc.GetString("vampire-bestia-passive-toxin-protection"),
|
||||
Value = $"-{(liverCount * 3)}%",
|
||||
ValueColor = liverColor.color,
|
||||
IsMaxed = liverColor.isMaxed
|
||||
});
|
||||
passives.Add(new()
|
||||
{
|
||||
Name = Loc.GetString("vampire-bestia-passive-blood-cost-reduction"),
|
||||
Value = $"-{(liverCount * 2)}%",
|
||||
ValueColor = liverColor.color,
|
||||
IsMaxed = liverColor.isMaxed
|
||||
});
|
||||
|
||||
var kidneysColor = GetOrganProgressColor(kidneysCount, maxRegular);
|
||||
passives.Add(new()
|
||||
{
|
||||
Name = Loc.GetString("vampire-bestia-passive-suck-rate"),
|
||||
Value = $"-{(kidneysCount * 0.3)}s",
|
||||
ValueColor = kidneysColor.color,
|
||||
IsMaxed = kidneysColor.isMaxed
|
||||
});
|
||||
passives.Add(new()
|
||||
{
|
||||
Name = Loc.GetString("vampire-bestia-passive-cellular-protection"),
|
||||
Value = $"-{(kidneysCount * 3)}%",
|
||||
ValueColor = kidneysColor.color,
|
||||
IsMaxed = kidneysColor.isMaxed
|
||||
});
|
||||
|
||||
var eyesColor = GetOrganProgressColor(eyesCount, maxRegular);
|
||||
// passives.Add(new()
|
||||
// {
|
||||
// Name = Loc.GetString("vampire-bestia-passive-xray-vision"),
|
||||
// Value = eyesCount > 0 ? Loc.GetString("vampire-bestia-passive-unlocked")
|
||||
// : Loc.GetString("vampire-bestia-passive-locked"),
|
||||
// ValueColor = eyesCount > 0 ? eyesColor.color : Color.Orange,
|
||||
// IsMaxed = eyesColor.isMaxed
|
||||
// });
|
||||
passives.Add(new()
|
||||
{
|
||||
Name = Loc.GetString("vampire-bestia-passive-welding-protection"),
|
||||
Value = eyesCount > 1 ? Loc.GetString("vampire-bestia-passive-unlocked")
|
||||
: Loc.GetString("vampire-bestia-passive-locked"),
|
||||
ValueColor = eyesCount > 1 ? eyesColor.color : Color.Orange,
|
||||
IsMaxed = eyesColor.isMaxed
|
||||
});
|
||||
passives.Add(new()
|
||||
{
|
||||
Name = Loc.GetString("vampire-bestia-passive-flash-protection"),
|
||||
Value = eyesCount > 5 ? Loc.GetString("vampire-bestia-passive-unlocked")
|
||||
: Loc.GetString("vampire-bestia-passive-locked"),
|
||||
ValueColor = eyesCount > 5 ? eyesColor.color : Color.Orange,
|
||||
IsMaxed = eyesColor.isMaxed
|
||||
});
|
||||
|
||||
var stomachColor = GetOrganProgressColor(stomachCount, maxRegular);
|
||||
passives.Add(new()
|
||||
{
|
||||
Name = Loc.GetString("vampire-bestia-passive-blood-gain"),
|
||||
Value = $"+{(stomachCount * 0.25)}",
|
||||
ValueColor = stomachColor.color,
|
||||
IsMaxed = stomachColor.isMaxed
|
||||
});
|
||||
passives.Add(new()
|
||||
{
|
||||
Name = Loc.GetString("vampire-bestia-passive-healing-efficiency"),
|
||||
Value = $"+{(stomachCount * 3)}%",
|
||||
ValueColor = stomachColor.color,
|
||||
IsMaxed = stomachColor.isMaxed
|
||||
});
|
||||
|
||||
return passives;
|
||||
}
|
||||
|
||||
private List<AbilityDisplayInfo> GetAbilityBonuses(EntityUid uid, VampireComponent vampire, BestiaContainerComponent bestia)
|
||||
{
|
||||
var result = new List<AbilityDisplayInfo>();
|
||||
var skills = vampire.AcquiredSkills.Keys.ToList();
|
||||
|
||||
var targetActions = new HashSet<EntProtoId>
|
||||
{
|
||||
"ActionVampireInfectedTrophy",
|
||||
"ActionVampireLunge",
|
||||
"ActionVampireMarkPrey",
|
||||
"ActionVampireMetamorphosisBats",
|
||||
"ActionVampireSummonBats",
|
||||
"ActionVampireMetamorphosisHound"
|
||||
};
|
||||
|
||||
foreach (var actionId in skills)
|
||||
{
|
||||
if (!targetActions.Contains(actionId))
|
||||
continue;
|
||||
|
||||
var actionEntity = vampire.AcquiredSkills.GetValueOrDefault(actionId);
|
||||
if (actionEntity == null)
|
||||
continue;
|
||||
|
||||
var name = Name(actionEntity.Value) ?? actionId;
|
||||
var bonuses = GetBonusesForAbility(uid, bestia, actionId);
|
||||
|
||||
result.Add(new AbilityDisplayInfo
|
||||
{
|
||||
Action = GetNetEntity(actionEntity.Value),
|
||||
Name = name,
|
||||
Bonuses = bonuses
|
||||
});
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
private List<OrganBonusDetail> GetBonusesForAbility(EntityUid uid, BestiaContainerComponent bestia, EntProtoId actionId)
|
||||
{
|
||||
var heart = GetOrganTypeCount((uid, bestia), BestiaOrganType.Heart);
|
||||
var lungs = GetOrganTypeCount((uid, bestia), BestiaOrganType.Lungs);
|
||||
var liver = GetOrganTypeCount((uid, bestia), BestiaOrganType.Liver);
|
||||
var kidneys = GetOrganTypeCount((uid, bestia), BestiaOrganType.Kidneys);
|
||||
var eyes = GetOrganTypeCount((uid, bestia), BestiaOrganType.Eyes);
|
||||
var stomach = GetOrganTypeCount((uid, bestia), BestiaOrganType.Stomach);
|
||||
|
||||
var maxCritical = bestia.MaxCriticalOrgans;
|
||||
var maxRegular = bestia.MaxRegularOrgans;
|
||||
|
||||
var bonuses = new List<OrganBonusDetail>();
|
||||
|
||||
switch (actionId)
|
||||
{
|
||||
case "ActionVampireInfectedTrophy":
|
||||
{
|
||||
var heartBonus = heart * 0.5f;
|
||||
var liverBonus = Math.Min(40, liver * 3);
|
||||
var eyesBonus = Math.Min(0.525f, eyes * 0.035f);
|
||||
var stomachBonus = Math.Min(5f, stomach);
|
||||
|
||||
AddBonus(bonuses, BestiaOrganType.Heart,
|
||||
Loc.GetString("vampire-bestia-bonus-infected-trophy-heart", ("value", heartBonus)), heart, maxCritical);
|
||||
AddBonus(bonuses, BestiaOrganType.Liver,
|
||||
Loc.GetString("vampire-bestia-bonus-infected-trophy-liver", ("value", liverBonus)), liver, maxRegular);
|
||||
AddBonus(bonuses, BestiaOrganType.Eyes,
|
||||
Loc.GetString("vampire-bestia-bonus-infected-trophy-eyes", ("value", eyesBonus.ToString("F2"))), eyes, maxRegular);
|
||||
AddBonus(bonuses, BestiaOrganType.Stomach,
|
||||
Loc.GetString("vampire-bestia-bonus-infected-trophy-stomach", ("value", stomachBonus)), stomach, maxRegular);
|
||||
break;
|
||||
}
|
||||
case "ActionVampireLunge":
|
||||
{
|
||||
var heartBonus = heart * 0.5f;
|
||||
var lungsBonus = Math.Min(11f, 5f + lungs);
|
||||
var kidneysBonus = Math.Min(50, 5 + kidneys * 5);
|
||||
var stomachBonus = Math.Min(1.5f, 0.5f + stomach * 0.5f);
|
||||
|
||||
AddBonus(bonuses, BestiaOrganType.Heart,
|
||||
Loc.GetString("vampire-bestia-bonus-lunge-heart", ("value", heartBonus)), heart, maxCritical);
|
||||
AddBonus(bonuses, BestiaOrganType.Lungs,
|
||||
Loc.GetString("vampire-bestia-bonus-lunge-lungs", ("value", lungsBonus)), lungs, maxCritical);
|
||||
AddBonus(bonuses, BestiaOrganType.Kidneys,
|
||||
Loc.GetString("vampire-bestia-bonus-lunge-kidneys", ("value", kidneysBonus)), kidneys, maxRegular);
|
||||
AddBonus(bonuses, BestiaOrganType.Stomach,
|
||||
Loc.GetString("vampire-bestia-bonus-lunge-stomach", ("value", stomachBonus)), stomach, maxRegular);
|
||||
break;
|
||||
}
|
||||
case "ActionVampireMarkPrey":
|
||||
{
|
||||
var heartDamage = Math.Min(6, heart);
|
||||
var heartChance = Math.Min(60, heart * 10);
|
||||
var eyesBonus = Math.Min(8f, 3f + eyes * 0.5f);
|
||||
var kidneysBonus = Math.Min(15, 5 + kidneys);
|
||||
|
||||
AddBonus(bonuses, BestiaOrganType.Heart,
|
||||
Loc.GetString("vampire-bestia-bonus-mark-prey-heart", ("damage", heartDamage), ("chance", heartChance)), heart, maxCritical);
|
||||
AddBonus(bonuses, BestiaOrganType.Eyes,
|
||||
Loc.GetString("vampire-bestia-bonus-mark-prey-eyes", ("value", eyesBonus)), eyes, maxRegular);
|
||||
AddBonus(bonuses, BestiaOrganType.Kidneys,
|
||||
Loc.GetString("vampire-bestia-bonus-mark-prey-kidneys", ("value", kidneysBonus)), kidneys, maxRegular);
|
||||
break;
|
||||
}
|
||||
case "ActionVampireMetamorphosisBats":
|
||||
{
|
||||
var heartHealth = Math.Min(250f, 130f + heart * 20f);
|
||||
var heartDamage = Math.Min(8f, heart * 0.75f);
|
||||
var lungsBonus = Math.Min(30, lungs * 5);
|
||||
var liverBonus = Math.Min(3f, liver * 0.5f);
|
||||
var kidneysBonus = Math.Min(10, 1 + kidneys);
|
||||
|
||||
AddBonus(bonuses, BestiaOrganType.Heart,
|
||||
Loc.GetString("vampire-bestia-bonus-metamorphosis-bats-heart", ("health", heartHealth), ("damage", heartDamage)), heart, maxCritical);
|
||||
AddBonus(bonuses, BestiaOrganType.Lungs,
|
||||
Loc.GetString("vampire-bestia-bonus-metamorphosis-bats-lungs", ("value", lungsBonus)), lungs, maxCritical);
|
||||
AddBonus(bonuses, BestiaOrganType.Liver,
|
||||
Loc.GetString("vampire-bestia-bonus-metamorphosis-bats-liver", ("value", liverBonus)), liver, maxRegular);
|
||||
AddBonus(bonuses, BestiaOrganType.Kidneys,
|
||||
Loc.GetString("vampire-bestia-bonus-metamorphosis-bats-kidneys", ("value", kidneysBonus)), kidneys, maxRegular);
|
||||
break;
|
||||
}
|
||||
case "ActionVampireSummonBats":
|
||||
{
|
||||
var heartHealth = Math.Min(140f, 80f + heart * 10f);
|
||||
var heartDamage = Math.Min(6f, heart * 0.75f);
|
||||
var lungsBonus = Math.Min(60, lungs * 10);
|
||||
var liverBonus = Math.Min(10f, liver * 0.5f);
|
||||
var kidneysBonus = kidneys;
|
||||
|
||||
AddBonus(bonuses, BestiaOrganType.Heart,
|
||||
Loc.GetString("vampire-bestia-bonus-summon-bats-heart", ("health", heartHealth), ("damage", heartDamage)), heart, maxCritical);
|
||||
AddBonus(bonuses, BestiaOrganType.Lungs,
|
||||
Loc.GetString("vampire-bestia-bonus-summon-bats-lungs", ("value", lungsBonus)), lungs, maxCritical);
|
||||
AddBonus(bonuses, BestiaOrganType.Liver,
|
||||
Loc.GetString("vampire-bestia-bonus-summon-bats-liver", ("value", liverBonus)), liver, maxRegular);
|
||||
AddBonus(bonuses, BestiaOrganType.Kidneys,
|
||||
Loc.GetString("vampire-bestia-bonus-summon-bats-kidneys", ("value", kidneysBonus)), kidneys, maxRegular);
|
||||
break;
|
||||
}
|
||||
case "ActionVampireMetamorphosisHound":
|
||||
{
|
||||
var heartHealth = Math.Min(320f, 140f + heart * 30f);
|
||||
var heartDamage = Math.Min(6f, heart);
|
||||
var lungsBonus = Math.Min(30, lungs * 5);
|
||||
var kidneysBonus = kidneys;
|
||||
var liverBonus = Math.Min(10f, liver * 0.5f);
|
||||
|
||||
AddBonus(bonuses, BestiaOrganType.Heart,
|
||||
Loc.GetString("vampire-bestia-bonus-metamorphosis-hound-heart", ("health", heartHealth), ("damage", heartDamage)), heart, maxCritical);
|
||||
AddBonus(bonuses, BestiaOrganType.Lungs,
|
||||
Loc.GetString("vampire-bestia-bonus-metamorphosis-hound-lungs", ("value", lungsBonus)), lungs, maxCritical);
|
||||
AddBonus(bonuses, BestiaOrganType.Kidneys,
|
||||
Loc.GetString("vampire-bestia-bonus-metamorphosis-hound-kidneys", ("value", kidneysBonus)), kidneys, maxRegular);
|
||||
AddBonus(bonuses, BestiaOrganType.Liver,
|
||||
Loc.GetString("vampire-bestia-bonus-metamorphosis-hound-liver", ("value", liverBonus)), liver, maxRegular);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
return bonuses;
|
||||
}
|
||||
|
||||
private void AddBonus(List<OrganBonusDetail> list, BestiaOrganType organType, string description, int count, int max)
|
||||
{
|
||||
var (_, isMaxed) = GetOrganProgressColor(count, max);
|
||||
list.Add(new OrganBonusDetail
|
||||
{
|
||||
OrganType = Loc.GetString($"vampire-bestia-{organType.ToString().ToLower()}"),
|
||||
Description = description,
|
||||
IsMaxed = isMaxed
|
||||
});
|
||||
}
|
||||
|
||||
private NetEntity? GetPreviewForOrganType(Container container, BestiaOrganType type)
|
||||
{
|
||||
var organsOfType = new List<EntityUid>();
|
||||
foreach (var organ in container.ContainedEntities)
|
||||
{
|
||||
if (GetOrganTypeEnum(organ) == type)
|
||||
organsOfType.Add(organ);
|
||||
}
|
||||
return organsOfType.Count > 0 ? GetNetEntity(_random.Pick(organsOfType)) : null;
|
||||
}
|
||||
|
||||
private Dictionary<BestiaOrganType, int> GetOrganCounts(Container container)
|
||||
{
|
||||
var counts = new Dictionary<BestiaOrganType, int>();
|
||||
foreach (var organ in container.ContainedEntities)
|
||||
{
|
||||
var type = GetOrganTypeEnum(organ);
|
||||
if (type != BestiaOrganType.Unknown)
|
||||
counts[type] = counts.GetValueOrDefault(type) + 1;
|
||||
}
|
||||
return counts;
|
||||
}
|
||||
|
||||
private bool IsCriticalOrganType(BestiaOrganType type)
|
||||
{
|
||||
return type == BestiaOrganType.Heart || type == BestiaOrganType.Lungs;
|
||||
}
|
||||
|
||||
private (Color color, bool isMaxed) GetOrganProgressColor(int current, int max)
|
||||
{
|
||||
if (current <= 0) return (Color.Orange, false);
|
||||
|
||||
if (current >= max) return (Color.YellowGreen, true);
|
||||
|
||||
var t = (float)current / max;
|
||||
var color = Color.InterpolateBetween(Color.Orange, Color.YellowGreen, t);
|
||||
return (color, false);
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region Organs Manipulation
|
||||
|
||||
private List<NetEntity> GetAvailableOrgans(EntityUid vampire, EntityUid target, BestiaContainerComponent? bestia = null)
|
||||
{
|
||||
var organs = new List<NetEntity>();
|
||||
if (!Resolve(vampire, ref bestia, false))
|
||||
return organs;
|
||||
|
||||
if (!TryComp<BodyComponent>(target, out var body) || body.Organs == null)
|
||||
return organs;
|
||||
|
||||
foreach (var organ in body.Organs.ContainedEntities)
|
||||
{
|
||||
if (!(TryComp<OrganComponent>(organ, out var organComp) && organComp.Category == Eyes)
|
||||
&& !HasComp<MetabolizerComponent>(organ))
|
||||
continue;
|
||||
|
||||
var organType = GetOrganTypeEnum(organ);
|
||||
if (organType == BestiaOrganType.Unknown)
|
||||
continue;
|
||||
|
||||
var currentCount = GetOrganTypeCount((vampire, bestia), organType);
|
||||
var maxCount = IsCriticalOrgan(organ) ? bestia.MaxCriticalOrgans : bestia.MaxRegularOrgans;
|
||||
if (currentCount >= maxCount)
|
||||
continue;
|
||||
|
||||
organs.Add(GetNetEntity(organ));
|
||||
}
|
||||
|
||||
return organs;
|
||||
}
|
||||
|
||||
private int GetOrganTypeCount(Entity<BestiaContainerComponent> entity, BestiaOrganType organType)
|
||||
{
|
||||
var counts = GetOrganCounts(entity.Comp.OrgansContainer);
|
||||
return counts.GetValueOrDefault(organType, 0);
|
||||
}
|
||||
|
||||
private int GetOrganTypeCount(EntityUid uid, BestiaOrganType organType, BestiaContainerComponent? bestia = null)
|
||||
{
|
||||
if (!Resolve(uid, ref bestia, false))
|
||||
return 0;
|
||||
|
||||
var counts = GetOrganCounts(bestia.OrgansContainer);
|
||||
return counts.GetValueOrDefault(organType, 0);
|
||||
}
|
||||
|
||||
private BestiaOrganType GetOrganTypeEnum(EntityUid organ, OrganComponent? organComp = null)
|
||||
{
|
||||
if (Resolve(organ, ref organComp, false) && organComp.Category != null)
|
||||
{
|
||||
var id = organComp.Category.Value.Id;
|
||||
return id switch
|
||||
{
|
||||
"Heart" => BestiaOrganType.Heart,
|
||||
"Lungs" => BestiaOrganType.Lungs,
|
||||
"Liver" => BestiaOrganType.Liver,
|
||||
"Kidneys" => BestiaOrganType.Kidneys,
|
||||
"Eyes" => BestiaOrganType.Eyes,
|
||||
"Stomach" => BestiaOrganType.Stomach,
|
||||
_ => BestiaOrganType.Unknown
|
||||
};
|
||||
}
|
||||
|
||||
return BestiaOrganType.Unknown;
|
||||
}
|
||||
|
||||
private bool IsCriticalOrgan(EntityUid organ, MetabolizerComponent? metabolizer = null)
|
||||
{
|
||||
if (!Resolve(organ, ref metabolizer, false) || metabolizer.Stages == null)
|
||||
return false;
|
||||
|
||||
var stages = metabolizer.Stages;
|
||||
foreach (var stage in CriticalStages)
|
||||
{
|
||||
if (stages.Contains(stage.Id))
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
private void UpdateBestiaLimits(Entity<VampireComponent?, BestiaContainerComponent?> ent)
|
||||
{
|
||||
if (!Resolve(ent, ref ent.Comp1, false) || !Resolve(ent, ref ent.Comp2, false))
|
||||
return;
|
||||
|
||||
var blood = ent.Comp1.CurrentBlood.Float();
|
||||
|
||||
var bestia = ent.Comp2;
|
||||
var sortedCritical = bestia.CriticalOrganThresholds.Keys.OrderBy(x => x).ToList();
|
||||
foreach (var threshold in sortedCritical)
|
||||
{
|
||||
if (blood >= threshold && !bestia.UnlockedCriticalThresholds.Contains(threshold))
|
||||
{
|
||||
bestia.UnlockedCriticalThresholds.Add(threshold);
|
||||
bestia.MaxCriticalOrgans += bestia.CriticalOrganThresholds[threshold];
|
||||
}
|
||||
}
|
||||
|
||||
var sortedVictim = bestia.OrgansPerVictimThresholds.Keys.OrderBy(x => x).ToList();
|
||||
foreach (var threshold in sortedVictim)
|
||||
{
|
||||
if (blood >= threshold && !bestia.UnlockedVictimThresholds.Contains(threshold))
|
||||
{
|
||||
bestia.UnlockedVictimThresholds.Add(threshold);
|
||||
bestia.MaxOrgansPerVictim += bestia.OrgansPerVictimThresholds[threshold];
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void UpdateProtections(Entity<BestiaContainerComponent> ent)
|
||||
{
|
||||
var eyesCount = GetOrganTypeCount(ent, BestiaOrganType.Eyes);
|
||||
if (eyesCount >= 2 && !HasComp<EyeProtectionComponent>(ent))
|
||||
EnsureComp<EyeProtectionComponent>(ent);
|
||||
|
||||
if (eyesCount >= 6 && !HasComp<FlashImmunityComponent>(ent))
|
||||
EnsureComp<FlashImmunityComponent>(ent);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,125 @@
|
||||
using System.Linq;
|
||||
using Content.Shared.Damage;
|
||||
using Content.Shared.Damage.Components;
|
||||
using Content.Shared.Damage.Systems;
|
||||
using Content.Shared.IdentityManagement;
|
||||
using Content.Shared.Mindshield.Components;
|
||||
using Content.Shared.NullRod.Components;
|
||||
using Content.Shared.Popups;
|
||||
using Content.Shared.Vampire.Components;
|
||||
|
||||
namespace Content.Server.Vampire;
|
||||
|
||||
public sealed partial class VampireSystem
|
||||
{
|
||||
private void InitializeThralls()
|
||||
{
|
||||
SubscribeLocalEvent<ThrallOwnerComponent, DamageChangedEvent>(OnVampireDamageChanged);
|
||||
SubscribeLocalEvent<ThrallComponent, DamageChangedEvent>(OnThrallDamageChanged);
|
||||
SubscribeLocalEvent<MindShieldComponent, ComponentStartup>(MindShieldImplanted); // TODO: Replace this with a specific event
|
||||
}
|
||||
|
||||
#region Damage Sharing Logic
|
||||
|
||||
private void OnVampireDamageChanged(EntityUid uid, ThrallOwnerComponent component, ref DamageChangedEvent args)
|
||||
{
|
||||
if (args.DamageDelta is null || !args.DamageIncreased)
|
||||
return;
|
||||
|
||||
if (!TryComp<ThrallOwnerComponent>(uid, out var thrallOwner) || !thrallOwner.DamageSharing)
|
||||
return;
|
||||
|
||||
var aliveThralls = GetAliveThralls(thrallOwner);
|
||||
if (aliveThralls.Count == 0)
|
||||
return;
|
||||
|
||||
_damage.TryChangeDamage(uid, -args.DamageDelta, true, false);
|
||||
DistributeDamage(uid, args.DamageDelta, aliveThralls);
|
||||
}
|
||||
|
||||
private void OnThrallDamageChanged(EntityUid uid, ThrallComponent component, ref DamageChangedEvent args)
|
||||
{
|
||||
if (args.DamageDelta is null || !args.DamageIncreased)
|
||||
return;
|
||||
|
||||
if (component.VampireOwner is not { } vampireOwner)
|
||||
return;
|
||||
|
||||
if (!TryComp<ThrallOwnerComponent>(vampireOwner, out var thrallOwner) || !thrallOwner.DamageSharing)
|
||||
return;
|
||||
|
||||
var aliveThralls = GetAliveThralls(thrallOwner);
|
||||
if (aliveThralls.Count == 0)
|
||||
return;
|
||||
|
||||
_damage.TryChangeDamage(uid, -args.DamageDelta, true, false);
|
||||
DistributeDamage(vampireOwner, args.DamageDelta, aliveThralls);
|
||||
}
|
||||
|
||||
private void DistributeDamage(EntityUid vampireUid, DamageSpecifier originalDamage, List<EntityUid> aliveThralls)
|
||||
{
|
||||
var participants = new List<EntityUid> { vampireUid };
|
||||
participants.AddRange(aliveThralls);
|
||||
|
||||
if (participants.Count == 0)
|
||||
return;
|
||||
|
||||
var sharedDamage = GetSharedDamage(originalDamage, participants.Count);
|
||||
foreach (var participant in participants)
|
||||
{
|
||||
if (!HasComp<DamageableComponent>(participant))
|
||||
continue;
|
||||
|
||||
_damage.TryChangeDamage(participant, sharedDamage, true, false);
|
||||
}
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
private void MindShieldImplanted(EntityUid uid, MindShieldComponent comp, ComponentStartup init)
|
||||
{
|
||||
if (!TryComp<ThrallComponent>(uid, out var thrall) || thrall.VampireOwner is not { } owner)
|
||||
return;
|
||||
|
||||
var stunTime = TimeSpan.FromSeconds(4);
|
||||
var name = Identity.Entity(uid, EntityManager);
|
||||
|
||||
if (TryComp<ThrallOwnerComponent>(owner, out var thrallOwner))
|
||||
{
|
||||
TryRemoveThrall(thrallOwner, uid);
|
||||
Dirty(owner, thrallOwner);
|
||||
}
|
||||
|
||||
RemComp<ThrallComponent>(uid);
|
||||
RemComp<UnholyComponent>(uid);
|
||||
RemComp<NullDamageComponent>(uid);
|
||||
_stun.TryUpdateParalyzeDuration(uid, stunTime);
|
||||
_popup.PopupEntity(Loc.GetString("thrall-break-control", ("name", name)), uid);
|
||||
}
|
||||
|
||||
private void UpdateThrallCount(Entity<VampireComponent?, ThrallOwnerComponent?> ent)
|
||||
{
|
||||
if (!Resolve(ent, ref ent.Comp1, false) || !Resolve(ent, ref ent.Comp2, false))
|
||||
return;
|
||||
|
||||
var blood = ent.Comp1.CurrentBlood.Float();
|
||||
|
||||
var sortedThresholds = ent.Comp2.ThrallCountThresholds.Keys.OrderBy(x => x).ToList();
|
||||
foreach (var threshold in sortedThresholds)
|
||||
{
|
||||
if (blood >= threshold && !ent.Comp2.UnlockedThresholds.Contains(threshold))
|
||||
ent.Comp2.UnlockedThresholds.Add(threshold);
|
||||
}
|
||||
|
||||
var totalBonus = ent.Comp2.UnlockedThresholds.Sum(t => ent.Comp2.ThrallCountThresholds[t]);
|
||||
|
||||
var newMaxCount = 1 + totalBonus;
|
||||
if (ent.Comp2.MaxThrallCount != newMaxCount)
|
||||
{
|
||||
ent.Comp2.MaxThrallCount = newMaxCount;
|
||||
Dirty(ent.Owner, ent.Comp2);
|
||||
|
||||
_popup.PopupEntity(Loc.GetString("vampire-trall-count-update", ("count", newMaxCount)), ent, ent, PopupType.Medium);
|
||||
}
|
||||
}
|
||||
}
|
||||
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,64 @@
|
||||
using Content.Shared.Damage;
|
||||
using Content.Shared.Damage.Components;
|
||||
using Content.Shared.Damage.Systems;
|
||||
using Content.Shared.Mobs.Systems;
|
||||
using Content.Shared.Weapons.Melee.Events;
|
||||
using Content.Shared.Whitelist;
|
||||
|
||||
namespace Content.Server.Weapons.Marker;
|
||||
|
||||
public sealed class LeechMeleeWeaponSystem : EntitySystem
|
||||
{
|
||||
[Dependency] private readonly EntityWhitelistSystem _entityWhitelist = default!;
|
||||
[Dependency] private readonly DamageableSystem _damageable = default!;
|
||||
[Dependency] private readonly MobStateSystem _mobState = default!;
|
||||
|
||||
public override void Initialize()
|
||||
{
|
||||
base.Initialize();
|
||||
SubscribeLocalEvent<LeechMeleeWeaponComponent, MeleeHitEvent>(OnMeleeHit);
|
||||
}
|
||||
|
||||
private void OnMeleeHit(EntityUid uid, LeechMeleeWeaponComponent component, MeleeHitEvent args)
|
||||
{
|
||||
if (args.HitEntities.Count == 0)
|
||||
return;
|
||||
|
||||
DamageSpecifier? heal = component.Heal?.Clone();
|
||||
DamageSpecifier? groupsHeal = null;
|
||||
|
||||
if (component.HealGroups != null && !component.HealGroups.Empty)
|
||||
{
|
||||
if (component.Weighted)
|
||||
{
|
||||
groupsHeal = _damageable.CreateWeightedHealFromGroups(args.User, component.HealGroups);
|
||||
}
|
||||
else
|
||||
{
|
||||
groupsHeal = _damageable.CreateHealFromGroups(args.User, component.HealGroups);
|
||||
}
|
||||
}
|
||||
|
||||
if (heal == null && groupsHeal != null)
|
||||
heal = groupsHeal;
|
||||
else if (heal != null && groupsHeal != null)
|
||||
heal += groupsHeal;
|
||||
|
||||
if (heal == null || heal.Empty)
|
||||
return;
|
||||
|
||||
foreach (var hitEnt in args.HitEntities)
|
||||
{
|
||||
if (!_entityWhitelist.IsWhitelistPass(component.Whitelist, hitEnt))
|
||||
continue;
|
||||
|
||||
if (_entityWhitelist.IsWhitelistPass(component.Blacklist, hitEnt))
|
||||
continue;
|
||||
|
||||
if (_mobState.IsDead(hitEnt))
|
||||
continue;
|
||||
|
||||
_damageable.TryChangeDamage(args.User, heal, true, false, origin: args.Weapon);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -548,7 +548,9 @@ public abstract partial class SharedActionsSystem : EntitySystem
|
||||
// Note that attached entity and attached container are allowed to be null here.
|
||||
if (action.Comp.AttachedEntity != null && action.Comp.AttachedEntity != performer)
|
||||
{
|
||||
#if DEBUG // Corvax-Wega-Add | Disable Error for the non-debug version
|
||||
Log.Error($"{ToPrettyString(performer)} is attempting to perform an action {ToPrettyString(action)} that is attached to another entity {ToPrettyString(action.Comp.AttachedEntity)}");
|
||||
#endif // Corvax-Wega-Add
|
||||
return;
|
||||
}
|
||||
|
||||
|
||||
@@ -11,7 +11,7 @@ namespace Content.Shared.Damage.Prototypes
|
||||
/// to change/get/set damage in a <see cref="DamageableComponent"/>.
|
||||
/// </remarks>
|
||||
[Prototype(2)]
|
||||
[Obsolete("Do not rely on DamageGroupPrototype for anything besides grouping logically similar damage in UIs")]
|
||||
// [Obsolete("Do not rely on DamageGroupPrototype for anything besides grouping logically similar damage in UIs")] Fuck yourself
|
||||
public sealed partial class DamageGroupPrototype : IPrototype
|
||||
{
|
||||
[IdDataField] public string ID { get; private set; } = default!;
|
||||
|
||||
@@ -460,7 +460,7 @@ public abstract partial class SharedStaminaSystem : EntitySystem
|
||||
// Corvax-Wega-Add-start
|
||||
public void RemoveStaminaDamage(Entity<StaminaComponent?> ent)
|
||||
{
|
||||
if (!Resolve(ent, ref ent.Comp))
|
||||
if (!Resolve(ent, ref ent.Comp, false))
|
||||
return;
|
||||
|
||||
if (ent.Comp.StaminaDamage >= ent.Comp.CritThreshold)
|
||||
|
||||
@@ -294,22 +294,22 @@ public abstract class SharedFlashSystem : EntitySystem
|
||||
}
|
||||
// Corvax-Wega-Wielder-end
|
||||
|
||||
// Corvax-Wega-Arsenal-start
|
||||
private void OnFlashPulse(Entity<ImpulseFlashComponent> ent,ref MaskFlashActionEvent args)
|
||||
{
|
||||
FlashArea(ent,ent, ent.Comp.Range, ent.Comp.Duration, ent.Comp.SlowTo, probability: ent.Comp.Probability);
|
||||
args.Handled = true;
|
||||
}
|
||||
// Corvax-Wega-Arsenal-start
|
||||
private void OnFlashPulse(Entity<ImpulseFlashComponent> ent,ref MaskFlashActionEvent args)
|
||||
{
|
||||
FlashArea(ent,ent, ent.Comp.Range, ent.Comp.Duration, ent.Comp.SlowTo, probability: ent.Comp.Probability);
|
||||
args.Handled = true;
|
||||
}
|
||||
|
||||
private void OnGetItemActions(Entity<ImpulseFlashComponent> ent, ref GetItemActionsEvent args)
|
||||
private void OnGetItemActions(Entity<ImpulseFlashComponent> ent, ref GetItemActionsEvent args)
|
||||
{
|
||||
if (_inventorySystem.InSlotWithFlags(ent.Owner, SlotFlags.HEAD) || _inventorySystem.InSlotWithFlags(ent.Owner, SlotFlags.NECK))
|
||||
{
|
||||
var comp = ent.Comp;
|
||||
args.AddAction(ref comp.FlashActionEntity, comp.FlashAction);
|
||||
}
|
||||
}
|
||||
// Corvax-Wega-Arsenal-end
|
||||
{
|
||||
var comp = ent.Comp;
|
||||
args.AddAction(ref comp.FlashActionEntity, comp.FlashAction);
|
||||
}
|
||||
}
|
||||
// Corvax-Wega-Arsenal-end
|
||||
|
||||
private void OnTemporaryBlindnessFlashAttempt(Entity<TemporaryBlindnessComponent> ent, ref FlashAttemptEvent args)
|
||||
{
|
||||
|
||||
@@ -46,6 +46,7 @@ public abstract partial class SharedStunSystem : EntitySystem
|
||||
SubscribeLocalEvent<StunnedComponent, ComponentShutdown>(OnStunShutdown);
|
||||
|
||||
SubscribeLocalEvent<StunOnContactComponent, StartCollideEvent>(OnStunOnContactCollide);
|
||||
SubscribeLocalEvent<SlowdownOnContactComponent, StartCollideEvent>(OnSlowdownOnContactCollide); // Corvax-Wega-Add
|
||||
|
||||
// Attempt event subscriptions.
|
||||
SubscribeLocalEvent<StunnedComponent, ChangeDirectionAttemptEvent>(OnAttempt);
|
||||
|
||||
@@ -8,15 +8,18 @@ namespace Content.Shared.Damage.Components;
|
||||
[RegisterComponent, NetworkedComponent, AutoGenerateComponentState]
|
||||
public sealed partial class DamageInContainerComponent : Component
|
||||
{
|
||||
[ViewVariables(VVAccess.ReadOnly)]
|
||||
public TimeSpan NextTickTime;
|
||||
|
||||
[DataField("interval")]
|
||||
public float Interval = 1f;
|
||||
|
||||
[DataField("nextTickTime")]
|
||||
public TimeSpan NextTickTime;
|
||||
|
||||
[DataField(required: true), AutoNetworkedField]
|
||||
[DataField, AutoNetworkedField]
|
||||
public DamageSpecifier Damage = default!;
|
||||
|
||||
[DataField, AutoNetworkedField]
|
||||
public GroupHealSpecifier DamageGroups = default!;
|
||||
|
||||
[DataField]
|
||||
public EntityWhitelist? Whitelist;
|
||||
|
||||
|
||||
@@ -0,0 +1,23 @@
|
||||
using Content.Shared.Whitelist;
|
||||
using Robust.Shared.GameStates;
|
||||
|
||||
namespace Content.Shared.Damage.Components;
|
||||
|
||||
[RegisterComponent, NetworkedComponent]
|
||||
public sealed partial class LeechMeleeWeaponComponent : Component
|
||||
{
|
||||
[DataField]
|
||||
public DamageSpecifier? Heal = default!;
|
||||
|
||||
[DataField]
|
||||
public GroupHealSpecifier? HealGroups = default!;
|
||||
|
||||
[DataField]
|
||||
public bool Weighted = false;
|
||||
|
||||
[DataField]
|
||||
public EntityWhitelist? Whitelist;
|
||||
|
||||
[DataField]
|
||||
public EntityWhitelist? Blacklist;
|
||||
}
|
||||
@@ -0,0 +1,121 @@
|
||||
using System.Linq;
|
||||
using Content.Shared.Damage.Prototypes;
|
||||
using Content.Shared.FixedPoint;
|
||||
using Robust.Shared.Prototypes;
|
||||
using Robust.Shared.Serialization;
|
||||
|
||||
namespace Content.Shared.Damage;
|
||||
|
||||
/// <summary>
|
||||
/// Represents a collection of damage groups and healing values.
|
||||
/// Designed specifically for healing operations where the healing amount
|
||||
/// should be distributed only among existing damage types of each group.
|
||||
/// </summary>
|
||||
[DataDefinition, Serializable, NetSerializable]
|
||||
public sealed partial class GroupHealSpecifier : IEquatable<GroupHealSpecifier>
|
||||
{
|
||||
/// <summary>
|
||||
/// Dictionary mapping damage group prototypes to total healing amounts for that group.
|
||||
/// Healing amounts should be negative values (e.g., -8 for 8 points of healing).
|
||||
/// </summary>
|
||||
[DataField("groups")]
|
||||
public Dictionary<ProtoId<DamageGroupPrototype>, FixedPoint2> GroupHealDict { get; set; } = new();
|
||||
|
||||
/// <summary>
|
||||
/// Returns true if the specifier contains no healing entries.
|
||||
/// </summary>
|
||||
public bool Empty => GroupHealDict.Count == 0;
|
||||
|
||||
/// <summary>
|
||||
/// Returns the total healing amount across all groups.
|
||||
/// </summary>
|
||||
public FixedPoint2 GetTotalHealing()
|
||||
{
|
||||
var total = FixedPoint2.Zero;
|
||||
foreach (var value in GroupHealDict.Values)
|
||||
{
|
||||
total += value;
|
||||
}
|
||||
return total;
|
||||
}
|
||||
|
||||
public GroupHealSpecifier()
|
||||
{
|
||||
}
|
||||
|
||||
public GroupHealSpecifier(GroupHealSpecifier other)
|
||||
{
|
||||
GroupHealDict = new Dictionary<ProtoId<DamageGroupPrototype>, FixedPoint2>(other.GroupHealDict);
|
||||
}
|
||||
|
||||
public GroupHealSpecifier(ProtoId<DamageGroupPrototype> group, FixedPoint2 healingAmount)
|
||||
{
|
||||
GroupHealDict = new() { { group, healingAmount } };
|
||||
}
|
||||
|
||||
public GroupHealSpecifier Clone()
|
||||
{
|
||||
return new GroupHealSpecifier(this);
|
||||
}
|
||||
|
||||
public override string ToString()
|
||||
{
|
||||
return "GroupHealSpecifier(" + string.Join("; ", GroupHealDict.Select(x => x.Key + ":" + x.Value)) + ")";
|
||||
}
|
||||
|
||||
#region Operators
|
||||
|
||||
public static GroupHealSpecifier operator *(GroupHealSpecifier spec, FixedPoint2 factor)
|
||||
{
|
||||
var result = new GroupHealSpecifier();
|
||||
foreach (var (group, amount) in spec.GroupHealDict)
|
||||
{
|
||||
result.GroupHealDict.Add(group, amount * factor);
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
public static GroupHealSpecifier operator *(GroupHealSpecifier spec, float factor)
|
||||
{
|
||||
var result = new GroupHealSpecifier();
|
||||
foreach (var (group, amount) in spec.GroupHealDict)
|
||||
{
|
||||
result.GroupHealDict.Add(group, amount * factor);
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
public static GroupHealSpecifier operator *(FixedPoint2 factor, GroupHealSpecifier spec) => spec * factor;
|
||||
public static GroupHealSpecifier operator *(float factor, GroupHealSpecifier spec) => spec * factor;
|
||||
|
||||
public static GroupHealSpecifier operator +(GroupHealSpecifier a, GroupHealSpecifier b)
|
||||
{
|
||||
var result = new GroupHealSpecifier(a);
|
||||
foreach (var (group, amount) in b.GroupHealDict)
|
||||
{
|
||||
if (!result.GroupHealDict.TryAdd(group, amount))
|
||||
{
|
||||
result.GroupHealDict[group] += amount;
|
||||
}
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
public bool Equals(GroupHealSpecifier? other)
|
||||
{
|
||||
if (other == null || GroupHealDict.Count != other.GroupHealDict.Count)
|
||||
return false;
|
||||
|
||||
foreach (var (key, value) in GroupHealDict)
|
||||
{
|
||||
if (!other.GroupHealDict.TryGetValue(key, out var otherValue) || value != otherValue)
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
public FixedPoint2 this[string key] => GroupHealDict[key];
|
||||
}
|
||||
@@ -0,0 +1,131 @@
|
||||
using Content.Shared.Damage.Components;
|
||||
using Content.Shared.Damage.Prototypes;
|
||||
using Content.Shared.FixedPoint;
|
||||
using Robust.Shared.Prototypes;
|
||||
|
||||
namespace Content.Shared.Damage.Systems;
|
||||
|
||||
public sealed partial class DamageableSystem
|
||||
{
|
||||
/// <summary>
|
||||
/// Creates a <see cref="DamageSpecifier"/> from a <see cref="GroupHealSpecifier"/>, distributing healing
|
||||
/// evenly only among the damage types from each group that are actually present on the target.
|
||||
/// </summary>
|
||||
/// <param name="target">The target entity whose current damage is being examined</param>
|
||||
/// <param name="groupHealSpec">The group healing specification</param>
|
||||
/// <param name="damageableQuery">Optional query for accessing <see cref="DamageableComponent"/></param>
|
||||
/// <returns><see cref="DamageSpecifier"/> with healing distributed only to existing damage types</returns>
|
||||
public DamageSpecifier CreateHealFromGroups(
|
||||
EntityUid target,
|
||||
GroupHealSpecifier groupHealSpec,
|
||||
EntityQuery<DamageableComponent>? damageableQuery = null)
|
||||
{
|
||||
var result = new DamageSpecifier();
|
||||
if (groupHealSpec.Empty)
|
||||
return result;
|
||||
|
||||
var query = damageableQuery ?? _damageableQuery;
|
||||
if (!query.TryGetComponent(target, out var damageable))
|
||||
return result;
|
||||
|
||||
foreach (var (groupId, healAmount) in groupHealSpec.GroupHealDict)
|
||||
{
|
||||
if (healAmount >= FixedPoint2.Zero)
|
||||
continue;
|
||||
|
||||
if (!_prototypeManager.TryIndex(groupId, out var groupProto))
|
||||
continue;
|
||||
|
||||
var existingTypes = new List<ProtoId<DamageTypePrototype>>();
|
||||
foreach (var damageType in groupProto.DamageTypes)
|
||||
{
|
||||
if (damageable.Damage.DamageDict.TryGetValue(damageType, out var value) && value > 0)
|
||||
existingTypes.Add(damageType);
|
||||
}
|
||||
|
||||
if (existingTypes.Count == 0)
|
||||
continue;
|
||||
|
||||
var remainingHealing = -healAmount;
|
||||
var remainingTypes = existingTypes.Count;
|
||||
|
||||
foreach (var damageType in existingTypes)
|
||||
{
|
||||
var healForType = remainingHealing / remainingTypes;
|
||||
|
||||
if (result.DamageDict.TryGetValue(damageType, out var currentValue))
|
||||
result.DamageDict[damageType] = currentValue - healForType;
|
||||
else
|
||||
result.DamageDict.Add(damageType, -healForType);
|
||||
|
||||
remainingHealing -= healForType;
|
||||
remainingTypes--;
|
||||
}
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Creates a <see cref="DamageSpecifier"/> from a <see cref="GroupHealSpecifier"/>, distributing healing
|
||||
/// by weight (proportional to how much damage of each type is present) only among the damage
|
||||
/// types from each group that are actually present on the target.
|
||||
/// </summary>
|
||||
/// <param name="target">The target entity whose current damage is being examined</param>
|
||||
/// <param name="groupHealSpec">The group healing specification</param>
|
||||
/// <param name="damageableQuery">Optional query for accessing <see cref="DamageableComponent"/></param>
|
||||
/// <returns><see cref="DamageSpecifier"/> with healing distributed by weight to existing damage types</returns>
|
||||
public DamageSpecifier CreateWeightedHealFromGroups(
|
||||
EntityUid target,
|
||||
GroupHealSpecifier groupHealSpec,
|
||||
EntityQuery<DamageableComponent>? damageableQuery = null)
|
||||
{
|
||||
var result = new DamageSpecifier();
|
||||
if (groupHealSpec.Empty)
|
||||
return result;
|
||||
|
||||
var query = damageableQuery ?? _damageableQuery;
|
||||
if (!query.TryGetComponent(target, out var damageable))
|
||||
return result;
|
||||
|
||||
foreach (var (groupId, healAmount) in groupHealSpec.GroupHealDict)
|
||||
{
|
||||
if (healAmount >= FixedPoint2.Zero)
|
||||
continue;
|
||||
|
||||
if (!_prototypeManager.TryIndex(groupId, out var groupProto))
|
||||
continue;
|
||||
|
||||
var existingDamage = new List<(ProtoId<DamageTypePrototype> Type, FixedPoint2 Amount)>();
|
||||
FixedPoint2 totalDamage = FixedPoint2.Zero;
|
||||
|
||||
foreach (var damageType in groupProto.DamageTypes)
|
||||
{
|
||||
if (damageable.Damage.DamageDict.TryGetValue(damageType, out var value) && value > 0)
|
||||
{
|
||||
existingDamage.Add((damageType, value));
|
||||
totalDamage += value;
|
||||
}
|
||||
}
|
||||
|
||||
if (existingDamage.Count == 0 || totalDamage == 0)
|
||||
continue;
|
||||
|
||||
var totalHealing = -healAmount;
|
||||
foreach (var (damageType, currentDamage) in existingDamage)
|
||||
{
|
||||
var weight = currentDamage / totalDamage;
|
||||
var healForType = totalHealing * weight;
|
||||
if (healForType > 0)
|
||||
{
|
||||
if (result.DamageDict.TryGetValue(damageType, out var currentValue))
|
||||
result.DamageDict[damageType] = currentValue - healForType;
|
||||
else
|
||||
result.DamageDict.Add(damageType, -healForType);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
}
|
||||
@@ -1,6 +0,0 @@
|
||||
namespace Content.Shared.Damage.Systems;
|
||||
|
||||
public abstract class SharedDamageInContainerSystem : EntitySystem
|
||||
{
|
||||
|
||||
}
|
||||
@@ -0,0 +1,26 @@
|
||||
using Content.Shared.FixedPoint;
|
||||
using Robust.Shared.GameStates;
|
||||
|
||||
namespace Content.Shared.NullRod.Components;
|
||||
|
||||
/// <summary>
|
||||
/// Indicates the presence and amount of zero damage in an unholy entity.
|
||||
/// </summary>
|
||||
[RegisterComponent, NetworkedComponent, AutoGenerateComponentState(true)]
|
||||
public sealed partial class NullDamageComponent : Component
|
||||
{
|
||||
[ViewVariables(VVAccess.ReadOnly)]
|
||||
public TimeSpan NextNullDamageTick { get; set; }
|
||||
|
||||
[ViewVariables, AutoNetworkedField]
|
||||
public FixedPoint2 NullDamage = 0;
|
||||
|
||||
[DataField]
|
||||
public FixedPoint2 MaxNullDamage = 120;
|
||||
|
||||
[DataField]
|
||||
public FixedPoint2 NullDamageRecoveryPerTick = 2;
|
||||
|
||||
[DataField]
|
||||
public float NullDamageRecoveryInterval = 2f;
|
||||
}
|
||||
@@ -0,0 +1,7 @@
|
||||
namespace Content.Shared.NullRod.Components;
|
||||
|
||||
/// <summary>
|
||||
/// It notes whether the entity is unholy
|
||||
/// </summary>
|
||||
[RegisterComponent]
|
||||
public sealed partial class UnholyComponent : Component;
|
||||
@@ -0,0 +1,12 @@
|
||||
using Content.Shared.Disease;
|
||||
using Robust.Shared.Prototypes;
|
||||
|
||||
namespace Content.Shared.Projectiles;
|
||||
|
||||
[RegisterComponent]
|
||||
public sealed partial class ProjectileInfectComponent : Component
|
||||
{
|
||||
[DataField(required: true)]
|
||||
public ProtoId<DiseasePrototype> Infection;
|
||||
[DataField] public float Prob = 0.1f;
|
||||
}
|
||||
@@ -0,0 +1,20 @@
|
||||
using Content.Shared.Movement.Systems;
|
||||
using Robust.Shared.Physics.Events;
|
||||
|
||||
namespace Content.Shared.Stunnable;
|
||||
|
||||
public abstract partial class SharedStunSystem
|
||||
{
|
||||
[Dependency] private readonly MovementModStatusSystem _movementMod = default!;
|
||||
|
||||
private void OnSlowdownOnContactCollide(Entity<SlowdownOnContactComponent> ent, ref StartCollideEvent args)
|
||||
{
|
||||
if (args.OurFixtureId != ent.Comp.FixtureId)
|
||||
return;
|
||||
|
||||
if (_entityWhitelist.IsWhitelistPass(ent.Comp.Blacklist, args.OtherEntity))
|
||||
return;
|
||||
|
||||
_movementMod.TryUpdateMovementSpeedModDuration(args.OtherEntity, MovementModStatusSystem.Slowdown, ent.Comp.Duration, ent.Comp.Multiplier);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,20 @@
|
||||
using Content.Shared.Whitelist;
|
||||
using Robust.Shared.GameStates;
|
||||
|
||||
namespace Content.Shared.Stunnable;
|
||||
|
||||
[RegisterComponent, NetworkedComponent, Access(typeof(SharedStunSystem))]
|
||||
public sealed partial class SlowdownOnContactComponent : Component
|
||||
{
|
||||
[DataField]
|
||||
public string FixtureId = "fix";
|
||||
|
||||
[DataField]
|
||||
public TimeSpan Duration = TimeSpan.FromSeconds(5);
|
||||
|
||||
[DataField]
|
||||
public float Multiplier = 1f;
|
||||
|
||||
[DataField]
|
||||
public EntityWhitelist Blacklist = new();
|
||||
}
|
||||
@@ -0,0 +1,12 @@
|
||||
namespace Content.Shared.Vampire.Components;
|
||||
|
||||
/// <summary>
|
||||
/// Just a chill guy.
|
||||
/// Defines an entity as a beacon of the soul.
|
||||
/// </summary>
|
||||
[RegisterComponent]
|
||||
public sealed partial class BeaconSoulComponent : Component
|
||||
{
|
||||
[DataField]
|
||||
public EntityUid VampireOwner = default!;
|
||||
}
|
||||
@@ -0,0 +1,47 @@
|
||||
using Robust.Shared.Containers;
|
||||
using Robust.Shared.GameStates;
|
||||
|
||||
namespace Content.Shared.Vampire.Components;
|
||||
|
||||
[Access(typeof(SharedVampireSystem))]
|
||||
[RegisterComponent, NetworkedComponent]
|
||||
public sealed partial class BestiaContainerComponent : Component
|
||||
{
|
||||
public const string ContainerId = "organs_container";
|
||||
|
||||
[ViewVariables]
|
||||
public Container OrgansContainer = default!;
|
||||
|
||||
// Mew Mew Mew Mew Mew Mew Mew Mew~!
|
||||
[ViewVariables(VVAccess.ReadOnly)]
|
||||
public Dictionary<EntityUid, int> OrgansExtractedFromVictim = new();
|
||||
|
||||
[ViewVariables(VVAccess.ReadOnly)]
|
||||
public int MaxRegularOrgans = 10;
|
||||
|
||||
[ViewVariables(VVAccess.ReadOnly)]
|
||||
public int MaxCriticalOrgans = 2;
|
||||
|
||||
[ViewVariables(VVAccess.ReadOnly)]
|
||||
public int MaxOrgansPerVictim = 1;
|
||||
|
||||
[ViewVariables(VVAccess.ReadOnly)]
|
||||
public List<float> UnlockedCriticalThresholds = new();
|
||||
|
||||
[ViewVariables(VVAccess.ReadOnly)]
|
||||
public List<float> UnlockedVictimThresholds = new();
|
||||
|
||||
[DataField("criticalThresholds")]
|
||||
public Dictionary<float, int> CriticalOrganThresholds { get; set; } = new()
|
||||
{
|
||||
{ 600f, 2 },
|
||||
{ 1000f, 2 }
|
||||
};
|
||||
|
||||
[DataField("organsThresholds")]
|
||||
public Dictionary<float, int> OrgansPerVictimThresholds { get; set; } = new()
|
||||
{
|
||||
{ 600f, 1 },
|
||||
{ 1000f, 1 }
|
||||
};
|
||||
}
|
||||
@@ -0,0 +1,14 @@
|
||||
namespace Content.Shared.Vampire.Components;
|
||||
|
||||
/// <summary>
|
||||
/// A mark that allows you to see the presence of fang marks on the victim.
|
||||
/// </summary>
|
||||
[RegisterComponent]
|
||||
public sealed partial class BittenByVampireComponent : Component
|
||||
{
|
||||
[ViewVariables(VVAccess.ReadOnly)]
|
||||
public TimeSpan ExpirationTime { get; set; }
|
||||
|
||||
[DataField]
|
||||
public float LifetimeSeconds = 900f;
|
||||
}
|
||||
@@ -0,0 +1,13 @@
|
||||
namespace Content.Shared.Vampire.Components;
|
||||
|
||||
/// <summary>
|
||||
/// A component for testing vampire arson near holy sites.
|
||||
/// </summary>
|
||||
[RegisterComponent]
|
||||
public sealed partial class HolyPointComponent : Component
|
||||
{
|
||||
[DataField]
|
||||
public float Range = 6f;
|
||||
|
||||
public float NextTimeTick { get; set; }
|
||||
}
|
||||
@@ -0,0 +1,14 @@
|
||||
using Robust.Shared.GameStates;
|
||||
|
||||
namespace Content.Shared.Vampire.Components;
|
||||
|
||||
/// <summary>
|
||||
/// The target is a supreme vampire.
|
||||
/// </summary>
|
||||
[Access(typeof(SharedVampireSystem))]
|
||||
[RegisterComponent, NetworkedComponent, AutoGenerateComponentState(true)]
|
||||
public sealed partial class SupremeVampireComponent : Component
|
||||
{
|
||||
[ViewVariables(VVAccess.ReadOnly), AutoNetworkedField]
|
||||
public bool Active = false;
|
||||
}
|
||||
@@ -0,0 +1,19 @@
|
||||
using Content.Shared.StatusIcon;
|
||||
using Robust.Shared.GameStates;
|
||||
using Robust.Shared.Prototypes;
|
||||
|
||||
namespace Content.Shared.Vampire.Components;
|
||||
|
||||
/// <summary>
|
||||
/// Determines what the entity is and who it belongs to.
|
||||
/// </summary>
|
||||
[Access(typeof(SharedVampireSystem))]
|
||||
[RegisterComponent, NetworkedComponent, AutoGenerateComponentState(true)]
|
||||
public sealed partial class ThrallComponent : Component
|
||||
{
|
||||
[ViewVariables(VVAccess.ReadOnly), AutoNetworkedField]
|
||||
public EntityUid? VampireOwner = default!;
|
||||
|
||||
[DataField]
|
||||
public ProtoId<FactionIconPrototype> StatusIcon = "ThrallFaction";
|
||||
}
|
||||
@@ -0,0 +1,31 @@
|
||||
using Robust.Shared.GameStates;
|
||||
|
||||
namespace Content.Shared.Vampire.Components;
|
||||
|
||||
/// <summary>
|
||||
/// Determines whether an entity is the owner of the tralls and allows them to be manipulated.
|
||||
/// </summary>
|
||||
[Access(typeof(SharedVampireSystem))]
|
||||
[RegisterComponent, NetworkedComponent, AutoGenerateComponentState(true)]
|
||||
public sealed partial class ThrallOwnerComponent : Component
|
||||
{
|
||||
[DataField, ViewVariables(VVAccess.ReadOnly), AutoNetworkedField]
|
||||
public int MaxThrallCount = 1;
|
||||
|
||||
[ViewVariables(VVAccess.ReadOnly), AutoNetworkedField]
|
||||
public List<EntityUid> ThrallOwned = new();
|
||||
|
||||
[ViewVariables(VVAccess.ReadOnly)]
|
||||
public bool DamageSharing = false;
|
||||
|
||||
[ViewVariables(VVAccess.ReadOnly)]
|
||||
public List<float> UnlockedThresholds = new();
|
||||
|
||||
[DataField, ViewVariables(VVAccess.ReadOnly)]
|
||||
public Dictionary<float, int> ThrallCountThresholds { get; set; } = new()
|
||||
{
|
||||
{ 400f, 1 },
|
||||
{ 600f, 1 },
|
||||
{ 1000f, 1 }
|
||||
};
|
||||
}
|
||||
@@ -0,0 +1,13 @@
|
||||
using Content.Shared.FixedPoint;
|
||||
|
||||
namespace Content.Shared.Vampire.Components;
|
||||
|
||||
[RegisterComponent]
|
||||
public sealed partial class VampireBloodAbsorptionComponent : Component
|
||||
{
|
||||
[DataField]
|
||||
public FixedPoint2 BloodStealAmount = 0;
|
||||
|
||||
[ViewVariables(VVAccess.ReadOnly)]
|
||||
public EntityUid VampireOwner = default!;
|
||||
}
|
||||
@@ -0,0 +1,17 @@
|
||||
using Content.Shared.Damage;
|
||||
using Content.Shared.FixedPoint;
|
||||
|
||||
namespace Content.Shared.Vampire.Components;
|
||||
|
||||
[RegisterComponent]
|
||||
public sealed partial class VampireClawsComponent : Component
|
||||
{
|
||||
[DataField]
|
||||
public FixedPoint2 BloodStealAmount = 5;
|
||||
|
||||
[DataField]
|
||||
public GroupHealSpecifier HealGroups = default!;
|
||||
|
||||
[DataField]
|
||||
public float StaminaMod = -10f;
|
||||
}
|
||||
@@ -0,0 +1,4 @@
|
||||
namespace Content.Shared.Vampire.Components;
|
||||
|
||||
[RegisterComponent]
|
||||
public sealed partial class VampireCoffinComponent : Component;
|
||||
@@ -0,0 +1,123 @@
|
||||
using Content.Shared.Alert;
|
||||
using Content.Shared.Damage;
|
||||
using Content.Shared.Damage.Prototypes;
|
||||
using Content.Shared.FixedPoint;
|
||||
using Content.Shared.Metabolism;
|
||||
using Content.Shared.StatusIcon;
|
||||
using Robust.Shared.Audio;
|
||||
using Robust.Shared.GameStates;
|
||||
using Robust.Shared.Prototypes;
|
||||
|
||||
namespace Content.Shared.Vampire.Components;
|
||||
|
||||
/// <summary>
|
||||
/// The basic component that defines a vampire.
|
||||
/// </summary>
|
||||
[Access(typeof(SharedVampireSystem))]
|
||||
[RegisterComponent, NetworkedComponent, AutoGenerateComponentState(true)]
|
||||
public sealed partial class VampireComponent : Component
|
||||
{
|
||||
public static readonly ProtoId<DamageModifierSetPrototype> VampireDamageModifier = "Vampire";
|
||||
public static readonly ProtoId<MetabolizerTypePrototype> MetabolizerVampire = "Vampire";
|
||||
|
||||
public static readonly EntProtoId DrinkActionPrototype = "ActionDrinkBlood";
|
||||
public static readonly EntProtoId SelectClassActionPrototype = "ActionVampireSelectClass";
|
||||
public static readonly EntProtoId RejuvenateActionPrototype = "ActionVampireRejuvenate";
|
||||
public static readonly EntProtoId GlareActionPrototype = "ActionVampireGlare";
|
||||
|
||||
public EntityUid? DrinkActionEntity;
|
||||
public EntityUid? SelectClassActionEntity;
|
||||
public EntityUid? RejuvenateActionEntity;
|
||||
public EntityUid? GlareActionEntity;
|
||||
|
||||
[ViewVariables(VVAccess.ReadOnly)]
|
||||
public float NextSpaceDamageTick { get; set; }
|
||||
|
||||
[DataField, ViewVariables(VVAccess.ReadOnly)]
|
||||
public VampireClassEnum CurrentEvolution { get; set; } = VampireClassEnum.NonSelected;
|
||||
|
||||
[ViewVariables(VVAccess.ReadWrite), AutoNetworkedField]
|
||||
public FixedPoint2 CurrentBlood = 0;
|
||||
|
||||
[ViewVariables(VVAccess.ReadOnly)]
|
||||
public float TotalBloodDrank = 0;
|
||||
|
||||
[ViewVariables(VVAccess.ReadOnly), AutoNetworkedField]
|
||||
public Dictionary<EntityUid, FixedPoint2> BloodConsumedFromVictim = new();
|
||||
|
||||
[ViewVariables(VVAccess.ReadOnly)]
|
||||
public Dictionary<EntProtoId, EntityUid?> AcquiredSkills = new();
|
||||
|
||||
[DataField, ViewVariables(VVAccess.ReadOnly)]
|
||||
public Dictionary<VampireClassEnum, Dictionary<float, List<EntProtoId>>> ClassThresholds { get; set; } = new()
|
||||
{
|
||||
[VampireClassEnum.Hemomancer] = new()
|
||||
{
|
||||
{ 150f, new() { "ActionVampireClaws" } },
|
||||
{ 250f, new() { "ActionVampireBloodTendrils", "ActionVampireBloodBarrier" } },
|
||||
{ 400f, new() { "ActionVampireSanguinePool" } },
|
||||
{ 600f, new() { "ActionVampirePredatorSenses" } },
|
||||
{ 800f, new() { "ActionVampireBloodEruption" } },
|
||||
{ 1000f, new() { "ActionVampireBloodBringersRite" } }
|
||||
},
|
||||
[VampireClassEnum.Umbrae] = new()
|
||||
{
|
||||
{ 150f, new() { "ActionVampireCloakOfDarkness" } },
|
||||
{ 250f, new() { "ActionVampireShadowSnare", "ActionVampireSoulAnchor" } },
|
||||
{ 400f, new() { "ActionVampireDarkPassage" } },
|
||||
{ 600f, new() { "ActionVampireExtinguish" } },
|
||||
{ 800f, new() { "ActionVampireShadowBoxing" } },
|
||||
{ 1000f, new() { "ActionVampireEternalDarkness" } }
|
||||
},
|
||||
[VampireClassEnum.Gargantua] = new()
|
||||
{
|
||||
{ 150f, new() { "ActionVampireBloodSwell" } },
|
||||
{ 250f, new() { "ActionVampireBloodRush", "ActionVampireSeismicStomp" } },
|
||||
{ 400f, new() { "ActionVampireBloodSwellAdvanced" } },
|
||||
{ 600f, new() { "ActionVampireOverwhelmingForce" } },
|
||||
{ 800f, new() { "ActionDemonicGrasp" } },
|
||||
{ 1000f, new() { "ActionVampireCharge" } }
|
||||
},
|
||||
[VampireClassEnum.Dantalion] = new()
|
||||
{
|
||||
{ 150f, new() { "ActionEnthrall", "ActionCommune" } },
|
||||
{ 250f, new() { "ActionPacify", "ActionSubspaceSwap" } },
|
||||
{ 400f, new() { "ActionDeployDecoy" } },
|
||||
{ 600f, new() { "ActionRallyThralls", "ActionVampirePacifyNearby" } },
|
||||
{ 800f, new() { "ActionBloodBond", "ActionVampireThrallHeal" } },
|
||||
{ 1000f, new() { "ActionMassHysteria" } }
|
||||
},
|
||||
[VampireClassEnum.Bestia] = new()
|
||||
{
|
||||
{ 150f, new() { "ActionVampireCheckTrophies", "ActionVampireDissect", "ActionVampireInfectedTrophy" } },
|
||||
{ 250f, new() { "ActionVampireLunge", "ActionVampireMarkPrey" } },
|
||||
{ 400f, new() { "ActionVampireMetamorphosisBats" } },
|
||||
{ 600f, new() { "ActionVampireAnabiosis" } },
|
||||
{ 800f, new() { "ActionVampireSummonBats" } },
|
||||
{ 1000f, new() { "ActionVampireMetamorphosisHound" } }
|
||||
}
|
||||
};
|
||||
|
||||
[DataField]
|
||||
public ProtoId<AlertPrototype> BloodAlert = "BloodAlert";
|
||||
|
||||
[DataField]
|
||||
public ProtoId<FactionIconPrototype> StatusIcon { get; set; } = "VampireFaction";
|
||||
|
||||
[DataField]
|
||||
public DamageSpecifier HolyDamage = new()
|
||||
{
|
||||
DamageDict = { { "Heat", 10 } }
|
||||
};
|
||||
|
||||
[DataField]
|
||||
public DamageSpecifier SpaceDamage = new()
|
||||
{
|
||||
DamageDict = { { "Heat", 2.5 } }
|
||||
};
|
||||
|
||||
[DataField]
|
||||
public SoundSpecifier BloodDrainSound = new SoundPathSpecifier("/Audio/Items/drink.ogg",
|
||||
new AudioParams { Volume = -3f, MaxDistance = 3f }
|
||||
);
|
||||
}
|
||||
@@ -0,0 +1,22 @@
|
||||
using Robust.Shared.GameStates;
|
||||
using Robust.Shared.Prototypes;
|
||||
|
||||
namespace Content.Shared.Vampire.Components;
|
||||
|
||||
[Access(typeof(SharedVampireSystem))]
|
||||
[RegisterComponent, NetworkedComponent, AutoGenerateComponentState(true)]
|
||||
public sealed partial class VampireDiablerieComponent : Component
|
||||
{
|
||||
public static readonly EntProtoId SacramentInitiationActionPrototype = "ActionVampireSacramentInitiation";
|
||||
|
||||
public EntityUid? SacramentInitiationActionEntity;
|
||||
|
||||
[ViewVariables(VVAccess.ReadOnly), AutoNetworkedField]
|
||||
public int DiablerieLevel = 0;
|
||||
|
||||
[DataField]
|
||||
public int MaxDiablerieLevel = 4;
|
||||
|
||||
[DataField]
|
||||
public float SuckingBonusPerLevel = 5f;
|
||||
}
|
||||
@@ -0,0 +1,4 @@
|
||||
namespace Content.Shared.Vampire.Components;
|
||||
|
||||
[RegisterComponent]
|
||||
public sealed partial class VampireInferiorComponent : Component;
|
||||
@@ -0,0 +1,28 @@
|
||||
using Content.Shared.Damage.Prototypes;
|
||||
using Content.Shared.Metabolism;
|
||||
using Robust.Shared.Prototypes;
|
||||
|
||||
namespace Content.Shared.Vampire.Components;
|
||||
|
||||
/// <summary>
|
||||
/// Stores the creature's original data before it becomes a vampire.
|
||||
/// Used for restoration when the vampire component is removed.
|
||||
/// </summary>
|
||||
[RegisterComponent, Access(typeof(SharedVampireSystem))]
|
||||
public sealed partial class VampireOriginalStateComponent : Component
|
||||
{
|
||||
[DataField]
|
||||
public HashSet<Type> RemovedComponents = new();
|
||||
|
||||
[DataField]
|
||||
public Dictionary<EntityUid, HashSet<ProtoId<MetabolizerTypePrototype>>> OriginalMetabolizerTypes = new();
|
||||
|
||||
[DataField]
|
||||
public float? OriginalColdDamageThreshold;
|
||||
|
||||
[DataField]
|
||||
public ProtoId<DamageModifierSetPrototype>? OriginalDamageModifierSetId;
|
||||
|
||||
[DataField]
|
||||
public Color? OriginalEyeColor;
|
||||
}
|
||||
@@ -0,0 +1,8 @@
|
||||
namespace Content.Shared.Vampire.Components;
|
||||
|
||||
[RegisterComponent, Access(typeof(SharedVampireSystem))]
|
||||
public sealed partial class VampirePolymorphComponent : Component
|
||||
{
|
||||
[ViewVariables(VVAccess.ReadOnly)]
|
||||
public EntityUid Body;
|
||||
}
|
||||
@@ -0,0 +1,458 @@
|
||||
using Content.Shared.Actions;
|
||||
using Content.Shared.Alert;
|
||||
using Content.Shared.Cloning;
|
||||
using Content.Shared.Damage;
|
||||
using Content.Shared.Damage.Prototypes;
|
||||
using Content.Shared.FixedPoint;
|
||||
using Content.Shared.Polymorph;
|
||||
using Content.Shared.Projectiles;
|
||||
using Content.Shared.Roles;
|
||||
using Robust.Shared.Audio;
|
||||
using Robust.Shared.Prototypes;
|
||||
|
||||
namespace Content.Shared.Vampire;
|
||||
|
||||
/// <summary>
|
||||
/// Interface for vampire action events that require blood costs.
|
||||
/// Allows you to unify the verification and deduction of blood costs through the <see cref="BloodCost"/>.
|
||||
/// </summary>
|
||||
public interface IVampireActionEvent
|
||||
{
|
||||
FixedPoint2 BloodCost { get; }
|
||||
}
|
||||
|
||||
// Base
|
||||
public sealed partial class VampireDrinkingBloodActionEvent : EntityTargetActionEvent
|
||||
{
|
||||
[DataField]
|
||||
public TimeSpan Delay = TimeSpan.FromSeconds(3);
|
||||
}
|
||||
|
||||
public sealed partial class VampireSelectClassActionEvent : InstantActionEvent, IVampireActionEvent
|
||||
{
|
||||
[DataField] public FixedPoint2 BloodCost { get; private set; }
|
||||
}
|
||||
|
||||
public sealed partial class VampireRejuvenateActionEvent : InstantActionEvent, IVampireActionEvent
|
||||
{
|
||||
[DataField]
|
||||
public bool Advanced = false;
|
||||
|
||||
[DataField]
|
||||
public int Repeats = 5;
|
||||
|
||||
[DataField]
|
||||
public DamageSpecifier Heal = default!;
|
||||
|
||||
[DataField]
|
||||
public GroupHealSpecifier HealGroups = default!;
|
||||
|
||||
[DataField]
|
||||
public TimeSpan TimeInterval = TimeSpan.FromSeconds(3.5);
|
||||
|
||||
[DataField] public FixedPoint2 BloodCost { get; private set; }
|
||||
}
|
||||
|
||||
public sealed partial class VampireGlareActionEvent : EntityTargetActionEvent { }
|
||||
|
||||
// Diablerie
|
||||
public sealed partial class VampireSacramentInitiationActionEvent : EntityTargetActionEvent, IVampireActionEvent
|
||||
{
|
||||
[DataField] public FixedPoint2 BloodCost { get; private set; }
|
||||
}
|
||||
|
||||
// Hemomancer Abilities
|
||||
public sealed partial class VampireClawsActionEvent : InstantActionEvent, IVampireActionEvent
|
||||
{
|
||||
[DataField]
|
||||
public ProtoId<StartingGearPrototype> ProtoId = "VampireClawsGear";
|
||||
|
||||
[DataField] public FixedPoint2 BloodCost { get; private set; }
|
||||
}
|
||||
|
||||
public sealed partial class VampireBloodTentacleAction : WorldTargetActionEvent, IVampireActionEvent
|
||||
{
|
||||
[DataField]
|
||||
public EntProtoId EntityId = "EffectBloodTentacleSpawn";
|
||||
|
||||
[DataField]
|
||||
public List<Direction> OffsetDirections = new()
|
||||
{
|
||||
Direction.North,
|
||||
Direction.South,
|
||||
Direction.East,
|
||||
Direction.West,
|
||||
Direction.NorthEast,
|
||||
Direction.NorthWest,
|
||||
Direction.SouthEast,
|
||||
Direction.SouthWest,
|
||||
};
|
||||
|
||||
[DataField]
|
||||
public int ExtraSpawns = 8;
|
||||
|
||||
[DataField] public FixedPoint2 BloodCost { get; private set; }
|
||||
}
|
||||
|
||||
public sealed partial class VampireBloodBarrierActionEvent : WorldTargetActionEvent, IVampireActionEvent
|
||||
{
|
||||
[DataField]
|
||||
public EntProtoId EntityId = "BloodBarrier";
|
||||
|
||||
[DataField] public FixedPoint2 BloodCost { get; private set; }
|
||||
}
|
||||
|
||||
public sealed partial class VampireSanguinePoolActionEvent : InstantActionEvent, IVampireActionEvent
|
||||
{
|
||||
[DataField]
|
||||
public ProtoId<PolymorphPrototype> PolymorphProto = "VampireBlood";
|
||||
|
||||
[DataField] public FixedPoint2 BloodCost { get; private set; }
|
||||
}
|
||||
|
||||
public sealed partial class VampirePredatorSensesActionEvent : InstantActionEvent
|
||||
{
|
||||
[DataField]
|
||||
public EntProtoId EntityId = "PuddleBlood";
|
||||
|
||||
[DataField]
|
||||
public SoundSpecifier Sound;
|
||||
}
|
||||
|
||||
public sealed partial class VampireBloodEruptionActionEvent : InstantActionEvent, IVampireActionEvent
|
||||
{
|
||||
[DataField]
|
||||
public DamageSpecifier Damage = default!;
|
||||
|
||||
[DataField] public FixedPoint2 BloodCost { get; private set; }
|
||||
}
|
||||
|
||||
public sealed partial class VampireBloodBringersRiteActionEvent : InstantActionEvent, IVampireActionEvent
|
||||
{
|
||||
[DataField]
|
||||
public ProtoId<AlertPrototype> Alert = "AlertBloodRite";
|
||||
|
||||
[DataField]
|
||||
public DamageSpecifier Heal = default!;
|
||||
|
||||
[DataField]
|
||||
public GroupHealSpecifier HealGroups = default!;
|
||||
|
||||
[DataField]
|
||||
public float StaminaMod = -15f;
|
||||
|
||||
[DataField]
|
||||
public SoundSpecifier Sound;
|
||||
|
||||
[DataField]
|
||||
public TimeSpan TimeInterval = TimeSpan.FromSeconds(1);
|
||||
|
||||
[DataField] public FixedPoint2 BloodCost { get; private set; }
|
||||
}
|
||||
|
||||
// Umbrae Abilities
|
||||
public sealed partial class VampireCloakOfDarknessActionEvent : InstantActionEvent, IVampireActionEvent
|
||||
{
|
||||
[DataField]
|
||||
public float SpeedMod = 1.3f;
|
||||
|
||||
[DataField] public FixedPoint2 BloodCost { get; private set; }
|
||||
}
|
||||
|
||||
public sealed partial class VampireShadowSnareActionEvent : WorldTargetActionEvent, IVampireActionEvent
|
||||
{
|
||||
[DataField]
|
||||
public EntProtoId EntityId = "ShadowTrap";
|
||||
|
||||
[DataField] public FixedPoint2 BloodCost { get; private set; }
|
||||
}
|
||||
|
||||
public sealed partial class VampireSoulAnchorActionEvent : InstantActionEvent, IVampireActionEvent
|
||||
{
|
||||
[DataField] public FixedPoint2 BloodCost { get; private set; }
|
||||
}
|
||||
|
||||
public sealed partial class VampireDarkPassageActionEvent : WorldTargetActionEvent, IVampireActionEvent
|
||||
{
|
||||
[DataField]
|
||||
public EntProtoId MistEffect = "VampireMistEffect";
|
||||
|
||||
[DataField]
|
||||
public EntProtoId MistReappearEffect = "VampireMistReappearEffect";
|
||||
|
||||
[DataField] public FixedPoint2 BloodCost { get; private set; }
|
||||
}
|
||||
|
||||
public sealed partial class VampireExtinguishActionEvent : InstantActionEvent, IVampireActionEvent
|
||||
{
|
||||
[DataField]
|
||||
public DamageSpecifier Damage = default!;
|
||||
|
||||
[DataField] public FixedPoint2 BloodCost { get; private set; }
|
||||
}
|
||||
|
||||
public sealed partial class VampireShadowBoxingActionEvent : EntityTargetActionEvent, IVampireActionEvent
|
||||
{
|
||||
[DataField]
|
||||
public EntProtoId EntityId = "MobFollowerShadow";
|
||||
|
||||
[DataField]
|
||||
public int Repeats = 10;
|
||||
|
||||
[DataField]
|
||||
public TimeSpan TimeInterval = TimeSpan.FromSeconds(1);
|
||||
|
||||
[DataField] public FixedPoint2 BloodCost { get; private set; }
|
||||
}
|
||||
|
||||
public sealed partial class VampireEternalDarknessActionEvent : InstantActionEvent, IVampireActionEvent
|
||||
{
|
||||
[DataField]
|
||||
public ProtoId<AlertPrototype> Alert = "AlertEternalDarkness";
|
||||
|
||||
[DataField]
|
||||
public DamageSpecifier Damage = default!;
|
||||
|
||||
[DataField]
|
||||
public TimeSpan TimeInterval = TimeSpan.FromSeconds(1);
|
||||
|
||||
[DataField] public FixedPoint2 BloodCost { get; private set; }
|
||||
}
|
||||
|
||||
// Gargantua Abilities
|
||||
public sealed partial class VampireBloodSwellActionEvent : InstantActionEvent, IVampireActionEvent
|
||||
{
|
||||
[DataField]
|
||||
public TimeSpan Time = TimeSpan.FromSeconds(30);
|
||||
|
||||
[DataField]
|
||||
public bool Advanced = false;
|
||||
|
||||
[DataField]
|
||||
public ProtoId<DamageTypePrototype> BonusDamageType = "Blunt";
|
||||
|
||||
[DataField]
|
||||
public float BonusDamageAmount = 14f;
|
||||
|
||||
[DataField] public FixedPoint2 BloodCost { get; private set; }
|
||||
}
|
||||
|
||||
public sealed partial class VampireBloodRushActionEvent : InstantActionEvent, IVampireActionEvent
|
||||
{
|
||||
[DataField]
|
||||
public TimeSpan Time = TimeSpan.FromSeconds(10);
|
||||
|
||||
[DataField] public FixedPoint2 BloodCost { get; private set; }
|
||||
}
|
||||
|
||||
public sealed partial class VampireSeismicStompActionEvent : InstantActionEvent, IVampireActionEvent
|
||||
{
|
||||
[DataField]
|
||||
public SoundSpecifier Sound;
|
||||
|
||||
[DataField] public FixedPoint2 BloodCost { get; private set; }
|
||||
}
|
||||
|
||||
public sealed partial class VampireOverwhelmingForceActionEvent : InstantActionEvent, IVampireActionEvent
|
||||
{
|
||||
[DataField] public FixedPoint2 BloodCost { get; private set; }
|
||||
}
|
||||
|
||||
public sealed partial class VampireDemonicGraspActionEvent : EntityTargetActionEvent, IVampireActionEvent
|
||||
{
|
||||
[DataField] public FixedPoint2 BloodCost { get; private set; }
|
||||
}
|
||||
|
||||
public sealed partial class VampireChargeActionEvent : WorldTargetActionEvent, IVampireActionEvent
|
||||
{
|
||||
[DataField("components")]
|
||||
public ComponentRegistry EnsurableComponents;
|
||||
|
||||
[DataField]
|
||||
public SoundSpecifier Sound;
|
||||
|
||||
[DataField] public FixedPoint2 BloodCost { get; private set; }
|
||||
}
|
||||
|
||||
// Dantalion Abilities
|
||||
public sealed partial class VampireEnthrallActionEvent : EntityTargetActionEvent, IVampireActionEvent
|
||||
{
|
||||
[DataField] public FixedPoint2 BloodCost { get; private set; }
|
||||
}
|
||||
|
||||
public sealed partial class VampireCommuneActionEvent : InstantActionEvent { }
|
||||
|
||||
public sealed partial class VampirePacifyActionEvent : EntityTargetActionEvent, IVampireActionEvent
|
||||
{
|
||||
[DataField]
|
||||
public TimeSpan Time = TimeSpan.FromSeconds(40);
|
||||
|
||||
[DataField] public FixedPoint2 BloodCost { get; private set; }
|
||||
}
|
||||
|
||||
public sealed partial class VampireSubspaceSwapActionEvent : EntityTargetActionEvent, IVampireActionEvent
|
||||
{
|
||||
[DataField] public FixedPoint2 BloodCost { get; private set; }
|
||||
}
|
||||
|
||||
public sealed partial class VampireDeployDecoyActionEvent : InstantActionEvent, IVampireActionEvent
|
||||
{
|
||||
[DataField("components")]
|
||||
public ComponentRegistry EnsurableComponents;
|
||||
|
||||
[DataField]
|
||||
public ProtoId<CloningSettingsPrototype> Settings = "BaseClone";
|
||||
|
||||
[DataField]
|
||||
public TimeSpan Time = TimeSpan.FromSeconds(6);
|
||||
|
||||
[DataField] public FixedPoint2 BloodCost { get; private set; }
|
||||
}
|
||||
|
||||
public sealed partial class VampireRallyThrallsActionEvent : InstantActionEvent, IVampireActionEvent
|
||||
{
|
||||
[DataField] public FixedPoint2 BloodCost { get; private set; }
|
||||
}
|
||||
|
||||
public sealed partial class VampireBloodBondActionEvent : InstantActionEvent, IVampireActionEvent
|
||||
{
|
||||
[DataField]
|
||||
public TimeSpan TimeInterval = TimeSpan.FromSeconds(1);
|
||||
|
||||
[DataField] public FixedPoint2 BloodCost { get; private set; }
|
||||
}
|
||||
|
||||
public sealed partial class VampireMassHysteriaActionEvent : InstantActionEvent, IVampireActionEvent
|
||||
{
|
||||
[DataField] public FixedPoint2 BloodCost { get; private set; }
|
||||
}
|
||||
|
||||
public sealed partial class VampireThrallHealActionEvent : InstantActionEvent, IVampireActionEvent
|
||||
{
|
||||
[DataField]
|
||||
public int Repeats = 3;
|
||||
|
||||
[DataField]
|
||||
public DamageSpecifier Heal = default!;
|
||||
|
||||
[DataField]
|
||||
public GroupHealSpecifier HealGroups = default!;
|
||||
|
||||
[DataField]
|
||||
public TimeSpan TimeInterval = TimeSpan.FromSeconds(4);
|
||||
|
||||
[DataField] public FixedPoint2 BloodCost { get; private set; }
|
||||
}
|
||||
|
||||
public sealed partial class VampirePacifyNearbyActionEvent : InstantActionEvent, IVampireActionEvent
|
||||
{
|
||||
[DataField]
|
||||
public TimeSpan Time = TimeSpan.FromSeconds(8);
|
||||
|
||||
[DataField] public FixedPoint2 BloodCost { get; private set; }
|
||||
}
|
||||
|
||||
// Bestia Abilities
|
||||
public sealed partial class VampireCheckTrophiesActionEvent : InstantActionEvent { }
|
||||
|
||||
public sealed partial class VampireDissectActionEvent : EntityTargetActionEvent, IVampireActionEvent
|
||||
{
|
||||
[DataField] public FixedPoint2 BloodCost { get; private set; }
|
||||
}
|
||||
|
||||
public sealed partial class VampireInfectedTrophyActionEvent : EntityTargetActionEvent, IVampireActionEvent
|
||||
{
|
||||
[DataField]
|
||||
public EntProtoId<ProjectileComponent> ProjectileId = "ProjectileInfectedTrophy";
|
||||
|
||||
[DataField] public FixedPoint2 BloodCost { get; private set; }
|
||||
}
|
||||
|
||||
public sealed partial class VampireLungeActionEvent : WorldTargetActionEvent, IVampireActionEvent
|
||||
{
|
||||
[DataField] public FixedPoint2 BloodCost { get; private set; }
|
||||
}
|
||||
|
||||
public sealed partial class VampireMarkPreyActionEvent : EntityTargetActionEvent, IVampireActionEvent
|
||||
{
|
||||
[DataField]
|
||||
public ProtoId<DamageTypePrototype> DamageType = "Heat";
|
||||
|
||||
[DataField] public FixedPoint2 BloodCost { get; private set; }
|
||||
}
|
||||
|
||||
public sealed partial class VampireMetamorphosisBatsActionEvent : InstantActionEvent, IVampireActionEvent
|
||||
{
|
||||
[DataField]
|
||||
public ProtoId<PolymorphPrototype> PolymorphProto = "VampireBats";
|
||||
|
||||
[DataField]
|
||||
public ProtoId<DamageTypePrototype> BonusDamageType = "Piercing";
|
||||
|
||||
[DataField]
|
||||
public EntProtoId MistEffect = "VampireMistEffect";
|
||||
|
||||
[DataField]
|
||||
public EntProtoId MistReappearEffect = "VampireMistReappearEffect";
|
||||
|
||||
[DataField] public FixedPoint2 BloodCost { get; private set; }
|
||||
}
|
||||
|
||||
public sealed partial class VampireAnabiosisActionEvent : InstantActionEvent, IVampireActionEvent
|
||||
{
|
||||
[DataField]
|
||||
public EntProtoId CoffinProto = "CrateCoffinVampire";
|
||||
|
||||
[DataField]
|
||||
public TimeSpan Duration = TimeSpan.FromSeconds(30);
|
||||
|
||||
[DataField] public FixedPoint2 BloodCost { get; private set; }
|
||||
}
|
||||
|
||||
public sealed partial class VampireSummonBatsActionEvent : InstantActionEvent, IVampireActionEvent
|
||||
{
|
||||
[DataField]
|
||||
public EntProtoId BatsProto = "MobBats";
|
||||
|
||||
[DataField]
|
||||
public ProtoId<DamageTypePrototype> BonusDamageType = "Piercing";
|
||||
|
||||
[DataField]
|
||||
public SoundSpecifier Sound;
|
||||
|
||||
[DataField] public FixedPoint2 BloodCost { get; private set; }
|
||||
}
|
||||
|
||||
public sealed partial class VampireMetamorphosisHoundActionEvent : InstantActionEvent, IVampireActionEvent
|
||||
{
|
||||
[DataField]
|
||||
public ProtoId<PolymorphPrototype> PolymorphProto = "VampireHound";
|
||||
|
||||
[DataField]
|
||||
public ProtoId<DamageTypePrototype> BonusDamageType = "Piercing";
|
||||
|
||||
[DataField]
|
||||
public EntProtoId MistEffect = "VampireMistEffect";
|
||||
|
||||
[DataField]
|
||||
public EntProtoId MistReappearEffect = "VampireMistReappearEffect";
|
||||
|
||||
[DataField] public FixedPoint2 BloodCost { get; private set; }
|
||||
}
|
||||
|
||||
// Polymorph Abilities
|
||||
public sealed partial class VampireResonantShriekActionEvent : InstantActionEvent, IVampireActionEvent
|
||||
{
|
||||
[DataField]
|
||||
public DamageSpecifier Damage = default!;
|
||||
|
||||
[DataField]
|
||||
public SoundSpecifier Sound;
|
||||
|
||||
[DataField] public FixedPoint2 BloodCost { get; private set; }
|
||||
}
|
||||
|
||||
public sealed partial class VampireLungeFinaleActionEvent : InstantActionEvent, IVampireActionEvent
|
||||
{
|
||||
[DataField] public FixedPoint2 BloodCost { get; private set; }
|
||||
}
|
||||
@@ -0,0 +1,49 @@
|
||||
using Content.Shared.DoAfter;
|
||||
using Content.Shared.FixedPoint;
|
||||
using Robust.Shared.Audio;
|
||||
using Robust.Shared.Prototypes;
|
||||
using Robust.Shared.Serialization;
|
||||
|
||||
namespace Content.Shared.Vampire;
|
||||
|
||||
[Serializable, NetSerializable]
|
||||
public sealed partial class VampireDrinkingBloodDoAfterEvent : SimpleDoAfterEvent
|
||||
{
|
||||
public float Volume = 5;
|
||||
public float AbsorptionRatio = 0.33f;
|
||||
}
|
||||
|
||||
[Serializable, NetSerializable]
|
||||
public sealed partial class SoulAnchorDoAfterEvent : SimpleDoAfterEvent
|
||||
{
|
||||
public EntProtoId EntityId = "BeaconSoul";
|
||||
public FixedPoint2 BloodCost { get; }
|
||||
|
||||
public SoulAnchorDoAfterEvent(FixedPoint2 cost)
|
||||
{
|
||||
BloodCost = cost;
|
||||
}
|
||||
}
|
||||
|
||||
[Serializable, NetSerializable]
|
||||
public sealed partial class EnthrallDoAfterEvent : SimpleDoAfterEvent
|
||||
{
|
||||
public FixedPoint2 BloodCost { get; }
|
||||
|
||||
public EnthrallDoAfterEvent(FixedPoint2 cost)
|
||||
{
|
||||
BloodCost = cost;
|
||||
}
|
||||
}
|
||||
|
||||
[Serializable, NetSerializable]
|
||||
public sealed partial class VampireDissectDoAfterEvent : SimpleDoAfterEvent
|
||||
{
|
||||
public FixedPoint2 BloodCost;
|
||||
public SoundSpecifier DissectSound = new SoundCollectionSpecifier("Organ");
|
||||
|
||||
public VampireDissectDoAfterEvent(FixedPoint2 cost)
|
||||
{
|
||||
BloodCost = cost;
|
||||
}
|
||||
}
|
||||
@@ -1,6 +1,256 @@
|
||||
using System.Diagnostics.CodeAnalysis;
|
||||
using System.Linq;
|
||||
using System.Numerics;
|
||||
using Content.Shared.Body;
|
||||
using Content.Shared.Damage;
|
||||
using Content.Shared.FixedPoint;
|
||||
using Content.Shared.Mobs.Systems;
|
||||
using Content.Shared.Vampire.Components;
|
||||
using Robust.Shared.Prototypes;
|
||||
|
||||
namespace Content.Shared.Vampire;
|
||||
|
||||
public abstract class SharedVampireSystem : EntitySystem
|
||||
{
|
||||
// Clear
|
||||
[Dependency] private readonly MobStateSystem _mobState = default!;
|
||||
[Dependency] private readonly SharedVisualBodySystem _visualBody = default!;
|
||||
|
||||
#region Blood Management
|
||||
|
||||
public bool CheckBloodEssence(VampireComponent component, FixedPoint2 requiredAmount, FixedPoint2? nullDamage = null)
|
||||
{
|
||||
var adjustedQuantity = requiredAmount * (1 + (nullDamage?.Float() ?? 0) / 100);
|
||||
return component.CurrentBlood >= adjustedQuantity;
|
||||
}
|
||||
|
||||
public bool TryAddBlood(VampireComponent component, FixedPoint2 quantity, out FixedPoint2 newAmount)
|
||||
{
|
||||
newAmount = component.CurrentBlood + quantity;
|
||||
if (quantity <= 0)
|
||||
return false;
|
||||
|
||||
component.CurrentBlood = newAmount;
|
||||
component.TotalBloodDrank += (float)quantity;
|
||||
return true;
|
||||
}
|
||||
|
||||
public bool TrySubtractBlood(VampireComponent component, FixedPoint2 quantity, FixedPoint2? nullDamage = null)
|
||||
{
|
||||
var adjustedQuantity = quantity * (1 + (nullDamage?.Float() ?? 0) / 100);
|
||||
if (adjustedQuantity <= 0 || component.CurrentBlood < adjustedQuantity)
|
||||
return false;
|
||||
|
||||
component.CurrentBlood -= adjustedQuantity;
|
||||
return true;
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region Skill Management
|
||||
|
||||
public bool TryGetThresholdsForClass(VampireComponent component, [NotNullWhen(true)] out Dictionary<float, List<EntProtoId>>? thresholds)
|
||||
{
|
||||
thresholds = null;
|
||||
if (component.CurrentEvolution == VampireClassEnum.NonSelected)
|
||||
return false;
|
||||
|
||||
return component.ClassThresholds.TryGetValue(component.CurrentEvolution, out thresholds);
|
||||
}
|
||||
|
||||
public bool HasSkill(VampireComponent component, EntProtoId skill)
|
||||
{
|
||||
return component.AcquiredSkills.ContainsKey(skill);
|
||||
}
|
||||
|
||||
public List<EntProtoId> GetNewSkillsToAdd(VampireComponent component)
|
||||
{
|
||||
var newSkills = new List<EntProtoId>();
|
||||
if (component.CurrentEvolution == VampireClassEnum.NonSelected)
|
||||
return newSkills;
|
||||
|
||||
if (!TryGetThresholdsForClass(component, out var thresholds))
|
||||
return newSkills;
|
||||
|
||||
foreach (var (threshold, skills) in thresholds.OrderBy(kv => kv.Key))
|
||||
{
|
||||
if (component.CurrentBlood >= threshold)
|
||||
{
|
||||
foreach (var skill in skills)
|
||||
{
|
||||
if (!HasSkill(component, skill))
|
||||
newSkills.Add(skill);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return newSkills;
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region Thrall Management
|
||||
|
||||
public bool CanAddThrall(ThrallOwnerComponent owner)
|
||||
{
|
||||
return owner.ThrallOwned.Count < owner.MaxThrallCount;
|
||||
}
|
||||
|
||||
public bool TryAddThrall(ThrallOwnerComponent owner, EntityUid thrallUid)
|
||||
{
|
||||
if (!CanAddThrall(owner))
|
||||
return false;
|
||||
|
||||
if (!owner.ThrallOwned.Contains(thrallUid))
|
||||
{
|
||||
owner.ThrallOwned.Add(thrallUid);
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
public bool TryRemoveThrall(ThrallOwnerComponent owner, EntityUid thrallUid)
|
||||
{
|
||||
if (owner.ThrallOwned.Remove(thrallUid))
|
||||
return true;
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
public List<EntityUid> GetAliveThralls(ThrallOwnerComponent owner)
|
||||
{
|
||||
return owner.ThrallOwned.Where(Exists)
|
||||
.Where(t => !_mobState.IsDead(t))
|
||||
.ToList();
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region Bestia Management
|
||||
|
||||
public void RecordExtraction(EntityUid vampire, EntityUid victim, [NotNullWhen(true)] out BestiaContainerComponent? bestia)
|
||||
{
|
||||
if (!TryComp(vampire, out bestia))
|
||||
return;
|
||||
|
||||
if (!bestia.OrgansExtractedFromVictim.TryAdd(victim, 1))
|
||||
bestia.OrgansExtractedFromVictim[victim]++;
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region True Power Management
|
||||
|
||||
public bool ShouldHaveTruePower(FixedPoint2 currentBlood)
|
||||
{
|
||||
return currentBlood >= 1000;
|
||||
}
|
||||
|
||||
public bool HasTruePower(EntityUid uid)
|
||||
{
|
||||
return HasComp<SupremeVampireComponent>(uid);
|
||||
}
|
||||
|
||||
public SupremeVampireComponent? GetTruePower(EntityUid uid)
|
||||
{
|
||||
return CompOrNull<SupremeVampireComponent>(uid);
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region Night Vision
|
||||
|
||||
public Color GetNightVisionColorForClass(VampireClassEnum vampireClass)
|
||||
{
|
||||
return vampireClass switch
|
||||
{
|
||||
VampireClassEnum.Umbrae => Color.FromHex("#663ca3"),
|
||||
_ => Color.FromHex("#adadad")
|
||||
};
|
||||
}
|
||||
|
||||
public float GetNightVisionRadiusForClass(VampireClassEnum vampireClass, bool hasTruePower = false)
|
||||
{
|
||||
if (hasTruePower)
|
||||
return 15;
|
||||
|
||||
return vampireClass switch
|
||||
{
|
||||
VampireClassEnum.Umbrae => 12,
|
||||
_ => 8
|
||||
};
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region Eye Color Management
|
||||
|
||||
public void SetEyeColor(EntityUid uid, Color color)
|
||||
{
|
||||
if (_visualBody.TryGatherMarkingsData(uid, null, out var profiles, out _, out _))
|
||||
{
|
||||
var newProfiles = profiles.ToDictionary(
|
||||
kv => kv.Key,
|
||||
kv => kv.Value with { EyeColor = color }
|
||||
);
|
||||
_visualBody.ApplyProfiles(uid, newProfiles);
|
||||
}
|
||||
}
|
||||
|
||||
public Color GetCurrentEyeColor(EntityUid uid)
|
||||
{
|
||||
if (_visualBody.TryGatherMarkingsData(uid, null, out var profiles, out _, out _))
|
||||
{
|
||||
var firstProfile = profiles.Values.FirstOrDefault();
|
||||
return firstProfile.EyeColor;
|
||||
}
|
||||
|
||||
return Color.White;
|
||||
}
|
||||
|
||||
public Color GetVampireEyeColor(VampireClassEnum vampireClass)
|
||||
{
|
||||
return vampireClass switch
|
||||
{
|
||||
VampireClassEnum.Hemomancer => Color.FromHex("#eb251b"),
|
||||
VampireClassEnum.Umbrae => Color.FromHex("#b8188a"),
|
||||
VampireClassEnum.Gargantua => Color.FromHex("#d43a18"),
|
||||
VampireClassEnum.Dantalion => Color.FromHex("#6ab820"),
|
||||
VampireClassEnum.Bestia => Color.FromHex("#c43088"),
|
||||
_ => Color.FromHex("#e22218")
|
||||
};
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region Space Damage
|
||||
|
||||
public bool ShouldTakeSpaceDamage(VampireComponent component, float frameTime, out bool shouldDamage)
|
||||
{
|
||||
shouldDamage = false;
|
||||
|
||||
if (component.NextSpaceDamageTick <= 0)
|
||||
{
|
||||
shouldDamage = true;
|
||||
component.NextSpaceDamageTick = 1;
|
||||
return true;
|
||||
}
|
||||
|
||||
component.NextSpaceDamageTick -= frameTime;
|
||||
return false;
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region Damage Sharing
|
||||
|
||||
public DamageSpecifier GetSharedDamage(DamageSpecifier originalDamage, int participantCount)
|
||||
{
|
||||
if (participantCount <= 0)
|
||||
return originalDamage;
|
||||
|
||||
return originalDamage / participantCount;
|
||||
}
|
||||
|
||||
#endregion
|
||||
}
|
||||
|
||||
@@ -1,15 +0,0 @@
|
||||
using Content.Shared.StatusIcon;
|
||||
using Robust.Shared.GameStates;
|
||||
using Robust.Shared.Prototypes;
|
||||
|
||||
namespace Content.Shared.Vampire.Components;
|
||||
|
||||
[RegisterComponent, NetworkedComponent]
|
||||
public sealed partial class ThrallComponent : Component
|
||||
{
|
||||
[DataField]
|
||||
public EntityUid? VampireOwner = null;
|
||||
|
||||
[DataField]
|
||||
public ProtoId<FactionIconPrototype> StatusIcon = "ThrallFaction";
|
||||
}
|
||||
@@ -0,0 +1,80 @@
|
||||
using Content.Shared.Eui;
|
||||
using Robust.Shared.Prototypes;
|
||||
using Robust.Shared.Serialization;
|
||||
|
||||
namespace Content.Shared.Vampire;
|
||||
|
||||
[Serializable, NetSerializable]
|
||||
public sealed class TrophiesEuiState : EuiStateBase
|
||||
{
|
||||
public List<OrganDisplayInfo> Organs { get; }
|
||||
public List<PassiveBonusInfo> PassiveBonuses { get; }
|
||||
public List<AbilityDisplayInfo> Abilities { get; }
|
||||
|
||||
public TrophiesEuiState(
|
||||
List<OrganDisplayInfo> organs,
|
||||
List<PassiveBonusInfo> passiveBonuses,
|
||||
List<AbilityDisplayInfo> abilities)
|
||||
{
|
||||
Organs = organs;
|
||||
PassiveBonuses = passiveBonuses;
|
||||
Abilities = abilities;
|
||||
}
|
||||
}
|
||||
|
||||
[Serializable, NetSerializable]
|
||||
public sealed class OrganDisplayInfo
|
||||
{
|
||||
public BestiaOrganType Type { get; set; }
|
||||
public int Count { get; set; }
|
||||
public int MaxCount { get; set; }
|
||||
public Color CountColor { get; set; } = Color.White;
|
||||
public NetEntity? PreviewEntity { get; set; }
|
||||
}
|
||||
|
||||
[Serializable, NetSerializable]
|
||||
public sealed class PassiveBonusInfo
|
||||
{
|
||||
public string Name { get; set; } = "";
|
||||
public string Value { get; set; } = "";
|
||||
public Color? ValueColor { get; set; }
|
||||
public bool IsMaxed { get; set; }
|
||||
}
|
||||
|
||||
[Serializable, NetSerializable]
|
||||
public sealed class AbilityDisplayInfo
|
||||
{
|
||||
public NetEntity Action { get; set; }
|
||||
public string Name { get; set; } = "";
|
||||
public List<OrganBonusDetail> Bonuses { get; set; } = new();
|
||||
}
|
||||
|
||||
[Serializable, NetSerializable]
|
||||
public sealed class OrganBonusDetail
|
||||
{
|
||||
public string OrganType { get; set; } = "";
|
||||
public string Description { get; set; } = "";
|
||||
public bool IsMaxed { get; set; }
|
||||
}
|
||||
|
||||
[Serializable, NetSerializable]
|
||||
public sealed class DissectSelectionEuiState : EuiStateBase
|
||||
{
|
||||
public List<NetEntity> AvailableOrgans { get; }
|
||||
|
||||
public DissectSelectionEuiState(List<NetEntity> availableOrgans)
|
||||
{
|
||||
AvailableOrgans = availableOrgans;
|
||||
}
|
||||
}
|
||||
|
||||
[Serializable, NetSerializable]
|
||||
public sealed class DissectOrganSelectedMessage : EuiMessageBase
|
||||
{
|
||||
public NetEntity Target { get; }
|
||||
|
||||
public DissectOrganSelectedMessage(NetEntity target)
|
||||
{
|
||||
Target = target;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,16 @@
|
||||
using Content.Shared.Eui;
|
||||
using Robust.Shared.Serialization;
|
||||
|
||||
namespace Content.Shared.Vampire;
|
||||
|
||||
[Serializable, NetSerializable]
|
||||
public sealed class VampireClassSelectionState : EuiStateBase
|
||||
{
|
||||
}
|
||||
|
||||
[Serializable, NetSerializable]
|
||||
public sealed class VampireClassSelectedMessage : EuiMessageBase
|
||||
{
|
||||
public VampireClassEnum SelectedClass { get; }
|
||||
public VampireClassSelectedMessage(VampireClassEnum selectedClass) => SelectedClass = selectedClass;
|
||||
}
|
||||
@@ -1,124 +0,0 @@
|
||||
using Content.Shared.Alert;
|
||||
using Content.Shared.Damage;
|
||||
using Content.Shared.FixedPoint;
|
||||
using Content.Shared.Metabolism;
|
||||
using Content.Shared.StatusIcon;
|
||||
using Robust.Shared.Audio;
|
||||
using Robust.Shared.GameStates;
|
||||
using Robust.Shared.Prototypes;
|
||||
using Robust.Shared.Serialization;
|
||||
|
||||
namespace Content.Shared.Vampire.Components;
|
||||
|
||||
[RegisterComponent, NetworkedComponent]
|
||||
[AutoGenerateComponentState]
|
||||
public sealed partial class VampireComponent : Component
|
||||
{
|
||||
public static readonly ProtoId<MetabolizerTypePrototype> MetabolizerVampire = "Vampire";
|
||||
|
||||
public static readonly DamageSpecifier HolyDamage = new()
|
||||
{
|
||||
DamageDict = { { "Heat", 10 } }
|
||||
};
|
||||
|
||||
public static readonly DamageSpecifier SpaceDamage = new()
|
||||
{
|
||||
DamageDict = { { "Heat", 2.5 } }
|
||||
};
|
||||
|
||||
public static readonly EntProtoId DrinkActionPrototype = "ActionDrinkBlood";
|
||||
public static readonly EntProtoId SelectClassActionPrototype = "ActionVampireSelectClass";
|
||||
public static readonly EntProtoId RejuvenateActionPrototype = "ActionVampireRejuvenate";
|
||||
public static readonly EntProtoId GlareActionPrototype = "ActionVampireGlare";
|
||||
|
||||
public readonly SoundSpecifier BloodDrainSound = new SoundPathSpecifier(
|
||||
"/Audio/Items/drink.ogg",
|
||||
new AudioParams() { Volume = -3f, MaxDistance = 3f }
|
||||
);
|
||||
|
||||
[DataField, ViewVariables(VVAccess.ReadOnly)]
|
||||
public string? CurrentEvolution { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// The current amount of blood in the vampire's account.
|
||||
/// </summary>
|
||||
[DataField, ViewVariables(VVAccess.ReadWrite)]
|
||||
[AutoNetworkedField]
|
||||
public FixedPoint2 CurrentBlood = 0;
|
||||
|
||||
[DataField("vampireStatusIcon")]
|
||||
public ProtoId<FactionIconPrototype> StatusIcon { get; set; } = "VampireFaction";
|
||||
|
||||
[DataField]
|
||||
public ProtoId<AlertPrototype> BloodAlert = "BloodAlert";
|
||||
|
||||
/// <summary>
|
||||
/// Fields for counting the total amount of blood consumed after the end of the round
|
||||
/// </summary>
|
||||
public float TotalBloodDrank = 0;
|
||||
|
||||
public float NextSpaceDamageTick { get; set; }
|
||||
|
||||
public float NextNullDamageTick { get; set; }
|
||||
|
||||
public bool TruePowerActive = false;
|
||||
|
||||
public bool IsCharging = false;
|
||||
|
||||
public HashSet<EntityUid> AlreadyHit { get; } = new HashSet<EntityUid>();
|
||||
|
||||
public bool PowerActive = false;
|
||||
|
||||
public bool IsDamageSharingActive = false;
|
||||
|
||||
[DataField]
|
||||
public FixedPoint2 NullDamage = 0;
|
||||
|
||||
[DataField]
|
||||
public int ThrallCount = 0;
|
||||
|
||||
[DataField]
|
||||
public int MaxThrallCount = 1;
|
||||
|
||||
[DataField, ViewVariables(VVAccess.ReadOnly)]
|
||||
public List<EntityUid> ThrallOwned = new();
|
||||
|
||||
[DataField, ViewVariables(VVAccess.ReadOnly)]
|
||||
public List<string> AcquiredSkills = new List<string>();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Marks an entity as taking damage when hit by a bible, rather than being healed
|
||||
/// </summary>
|
||||
[RegisterComponent]
|
||||
public sealed partial class UnholyComponent : Component;
|
||||
|
||||
[RegisterComponent]
|
||||
public sealed partial class BeaconSoulComponent : Component
|
||||
{
|
||||
[DataField]
|
||||
public EntityUid VampireOwner = EntityUid.Invalid;
|
||||
}
|
||||
|
||||
[RegisterComponent, NetworkedComponent]
|
||||
public sealed partial class BittenByVampireComponent : Component;
|
||||
|
||||
/// <summary>
|
||||
/// A component for testing vampire arson near holy sites.
|
||||
/// </summary>
|
||||
[RegisterComponent]
|
||||
public sealed partial class HolyPointComponent : Component
|
||||
{
|
||||
[DataField]
|
||||
public float Range = 6f;
|
||||
|
||||
public float NextTimeTick { get; set; }
|
||||
}
|
||||
|
||||
[Serializable, NetSerializable]
|
||||
public enum VampireVisualLayers : byte
|
||||
{
|
||||
Digit1,
|
||||
Digit2,
|
||||
Digit3
|
||||
}
|
||||
@@ -0,0 +1,36 @@
|
||||
using Robust.Shared.Serialization;
|
||||
|
||||
namespace Content.Shared.Vampire;
|
||||
|
||||
[Serializable, NetSerializable]
|
||||
public enum VampireClassEnum : byte
|
||||
{
|
||||
NonSelected = 0,
|
||||
Hemomancer,
|
||||
Umbrae,
|
||||
Gargantua,
|
||||
Dantalion,
|
||||
Bestia // Here! Don't bite me!
|
||||
}
|
||||
|
||||
[Serializable, NetSerializable]
|
||||
public enum VampireVisualLayers : byte
|
||||
{
|
||||
Digit1,
|
||||
Digit2,
|
||||
Digit3,
|
||||
Digit4
|
||||
}
|
||||
|
||||
// Bestia enum
|
||||
[Serializable, NetSerializable]
|
||||
public enum BestiaOrganType : byte
|
||||
{
|
||||
Unknown = 0,
|
||||
Heart,
|
||||
Lungs,
|
||||
Liver,
|
||||
Kidneys,
|
||||
Eyes,
|
||||
Stomach
|
||||
}
|
||||
@@ -1,202 +0,0 @@
|
||||
using Content.Shared.Actions;
|
||||
using Content.Shared.DoAfter;
|
||||
using Robust.Shared.Audio;
|
||||
using Robust.Shared.Prototypes;
|
||||
using Robust.Shared.Serialization;
|
||||
using Content.Shared.Alert;
|
||||
|
||||
namespace Content.Shared.Vampire;
|
||||
|
||||
// Base
|
||||
public sealed partial class VampireSelectClassActionEvent : InstantActionEvent { }
|
||||
|
||||
public sealed partial class VampireRejuvenateActionEvent : InstantActionEvent { }
|
||||
|
||||
public sealed partial class VampireGlareActionEvent : EntityTargetActionEvent { }
|
||||
|
||||
public sealed partial class VampireDrinkingBloodActionEvent : EntityTargetActionEvent { }
|
||||
|
||||
[Serializable, NetSerializable]
|
||||
public sealed partial class VampireDrinkingBloodDoAfterEvent : SimpleDoAfterEvent
|
||||
{
|
||||
[DataField]
|
||||
public float Volume = 0;
|
||||
}
|
||||
|
||||
[Serializable, NetSerializable]
|
||||
public sealed class SelectClassPressedEvent : EntityEventArgs
|
||||
{
|
||||
public NetEntity Uid { get; }
|
||||
|
||||
public SelectClassPressedEvent(NetEntity uid)
|
||||
{
|
||||
Uid = uid;
|
||||
}
|
||||
}
|
||||
|
||||
[Serializable, NetSerializable]
|
||||
public sealed class VampireSelectClassMenuClosedEvent : EntityEventArgs
|
||||
{
|
||||
public NetEntity Uid { get; }
|
||||
public string SelectedClass { get; }
|
||||
|
||||
public VampireSelectClassMenuClosedEvent(NetEntity uid, string selectedClass)
|
||||
{
|
||||
Uid = uid;
|
||||
SelectedClass = selectedClass;
|
||||
}
|
||||
}
|
||||
|
||||
// Hemomancer Abilities
|
||||
public sealed partial class VampireClawsActionEvent : InstantActionEvent { }
|
||||
|
||||
public sealed partial class VampireBloodTentacleAction : WorldTargetActionEvent
|
||||
{
|
||||
[DataField]
|
||||
public EntProtoId EntityId = "EffectBloodTentacleSpawn";
|
||||
|
||||
[DataField]
|
||||
public List<Direction> OffsetDirections = new()
|
||||
{
|
||||
Direction.North,
|
||||
Direction.South,
|
||||
Direction.East,
|
||||
Direction.West,
|
||||
Direction.NorthEast,
|
||||
Direction.NorthWest,
|
||||
Direction.SouthEast,
|
||||
Direction.SouthWest,
|
||||
};
|
||||
|
||||
[DataField]
|
||||
public int ExtraSpawns = 8;
|
||||
}
|
||||
|
||||
public sealed partial class VampireBloodBarrierActionEvent : WorldTargetActionEvent
|
||||
{
|
||||
[DataField]
|
||||
public EntProtoId EntityId = "BloodBarrier";
|
||||
|
||||
public bool UseCasterDirection { get; set; } = true;
|
||||
}
|
||||
|
||||
public sealed partial class VampireSanguinePoolActionEvent : InstantActionEvent
|
||||
{
|
||||
[DataField]
|
||||
public string PolymorphProto = "VampireBlood";
|
||||
|
||||
public SoundSpecifier Sound = new SoundPathSpecifier("/Audio/Effects/Fluids/splat.ogg");
|
||||
}
|
||||
|
||||
public sealed partial class VampirePredatorSensesActionEvent : InstantActionEvent
|
||||
{
|
||||
[DataField]
|
||||
public string Proto = "PuddleBlood";
|
||||
|
||||
public SoundSpecifier Sound = new SoundPathSpecifier("/Audio/Effects/Fluids/splat.ogg");
|
||||
}
|
||||
|
||||
public sealed partial class VampireBloodEruptionActionEvent : InstantActionEvent { }
|
||||
|
||||
public sealed partial class VampireBloodBringersRiteActionEvent : InstantActionEvent
|
||||
{
|
||||
[DataField]
|
||||
public string Proto = "PuddleBlood";
|
||||
|
||||
public SoundSpecifier Sound = new SoundPathSpecifier("/Audio/Effects/Fluids/splat.ogg");
|
||||
}
|
||||
|
||||
public sealed partial class VampireBloodRiteAlertEvent : BaseAlertEvent;
|
||||
|
||||
// Umbrae Abilities
|
||||
public sealed partial class VampireCloakOfDarknessActionEvent : InstantActionEvent { }
|
||||
|
||||
public sealed partial class VampireShadowSnareActionEvent : WorldTargetActionEvent
|
||||
{
|
||||
[DataField]
|
||||
public EntProtoId EntityId = "ShadowTrap";
|
||||
}
|
||||
|
||||
public sealed partial class VampireSoulAnchorActionEvent : InstantActionEvent { }
|
||||
|
||||
[Serializable, NetSerializable]
|
||||
public sealed partial class SoulAnchorDoAfterEvent : SimpleDoAfterEvent { }
|
||||
|
||||
public sealed partial class VampireDarkPassageActionEvent : WorldTargetActionEvent { }
|
||||
|
||||
public sealed partial class VampireExtinguishActionEvent : InstantActionEvent { }
|
||||
|
||||
public sealed partial class VampireShadowBoxingActionEvent : EntityTargetActionEvent { }
|
||||
|
||||
public sealed partial class VampireEternalDarknessActionEvent : InstantActionEvent { }
|
||||
|
||||
[Serializable, NetSerializable]
|
||||
public sealed partial class VampireToggleFovEvent : EntityEventArgs
|
||||
{
|
||||
public NetEntity User { get; }
|
||||
public bool Enabled { get; }
|
||||
|
||||
public VampireToggleFovEvent(NetEntity user, bool enabled)
|
||||
{
|
||||
User = user;
|
||||
Enabled = enabled;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
// Gargantua Abilities
|
||||
public sealed partial class VampireRejuvenateAdvancedActionEvent : InstantActionEvent { }
|
||||
|
||||
public sealed partial class VampireBloodSwellActionEvent : InstantActionEvent { }
|
||||
|
||||
public sealed partial class VampireBloodRushActionEvent : InstantActionEvent { }
|
||||
|
||||
public sealed partial class VampireSeismicStompActionEvent : InstantActionEvent
|
||||
{
|
||||
public SoundSpecifier Sound = new SoundPathSpecifier("/Audio/Effects/Footsteps/largethud.ogg");
|
||||
}
|
||||
|
||||
public sealed partial class VampireBloodSwellAdvancedActionEvent : InstantActionEvent { }
|
||||
|
||||
public sealed partial class VampireOverwhelmingForceActionEvent : InstantActionEvent { }
|
||||
|
||||
public sealed partial class VampireDemonicGraspActionEvent : EntityTargetActionEvent { }
|
||||
|
||||
public sealed partial class VampireChargeActionEvent : WorldTargetActionEvent
|
||||
{
|
||||
public SoundSpecifier Sound = new SoundPathSpecifier("/Audio/Effects/Footsteps/largethud.ogg");
|
||||
}
|
||||
|
||||
// Dantalion Abilities
|
||||
public sealed partial class MaxThrallCountUpdateEvent : InstantActionEvent { }
|
||||
|
||||
public sealed partial class VampireEnthrallActionEvent : EntityTargetActionEvent { }
|
||||
|
||||
[Serializable, NetSerializable]
|
||||
public sealed partial class EnthrallDoAfterEvent : SimpleDoAfterEvent
|
||||
{
|
||||
public new NetEntity Target { get; set; }
|
||||
|
||||
public EnthrallDoAfterEvent(NetEntity target)
|
||||
{
|
||||
Target = target;
|
||||
}
|
||||
}
|
||||
|
||||
public sealed partial class VampireCommuneActionEvent : InstantActionEvent { }
|
||||
|
||||
public sealed partial class VampirePacifyActionEvent : EntityTargetActionEvent { }
|
||||
|
||||
public sealed partial class VampireSubspaceSwapActionEvent : EntityTargetActionEvent { }
|
||||
|
||||
//public sealed partial class VampireDeployDecoyActionEvent : InstantActionEvent { }
|
||||
|
||||
public sealed partial class VampireRallyThrallsActionEvent : InstantActionEvent { }
|
||||
|
||||
public sealed partial class VampireBloodBondActionEvent : InstantActionEvent { }
|
||||
|
||||
public sealed partial class VampireMassHysteriaActionEvent : InstantActionEvent { }
|
||||
|
||||
public sealed partial class VampireThrallHealActionEvent : InstantActionEvent { }
|
||||
|
||||
public sealed partial class VampirePacifyNearbyActionEvent : InstantActionEvent { }
|
||||
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
@@ -1,5 +1,5 @@
|
||||
alerts-vampire-blood-name = Кровь
|
||||
alerts-vampire-blood-desc = Накопленная жизненная сила вампира позволяющая ему использовать сверх естественные способности.
|
||||
alerts-vampire-blood-desc = Накопленная жизненная сила вампира позволяющая ему использовать сверхъестественные способности.
|
||||
alerts-strangle-name = Душат
|
||||
alerts-strangle-desc = Вас [color=red]ДУШАТ[/color]. Щёлкните по иконке, чтобы попытаться выбраться
|
||||
alerts-offer-name = Предложение
|
||||
@@ -7,4 +7,4 @@ alerts-offer-desc = Кто-то предлагает вам взять пред
|
||||
alerts-bloodrite-name = [color=purple]Обряд крови[/color]
|
||||
alerts-bloodrite-desc = Обратите это место в кровавую баню. Отображает статус способности [color=purple]Обряд крови[/color].
|
||||
alerts-eternaldarkness-name = [color=purple]Вечная темнота[/color]
|
||||
alerts-eternaldarkness-desc = Жертвы в радиусе 6 тайлов замерзают и вы видите через стены. Отображает статус способности [color=purple]Вечная темнота[/color].
|
||||
alerts-eternaldarkness-desc = Жертвы в радиусе 6 тайлов замерзают. Отображает статус способности [color=purple]Вечная темнота[/color].
|
||||
|
||||
@@ -4,6 +4,7 @@ disease-proto-spectral-tiredness = спектральное утомление
|
||||
disease-proto-lung-cancer = рак лёгких IIIA стадии
|
||||
disease-proto-slimeitis = слаймитис
|
||||
disease-proto-surgical-sepsis = сепсис внутренних тканей
|
||||
disease-vampire-grave-fever = могильная лихорадка
|
||||
# Infectious
|
||||
disease-proto-space-cold = космическая простуда
|
||||
disease-proto-vent-cough = трубный кашель
|
||||
|
||||
@@ -1,3 +1,6 @@
|
||||
disease-slime-drip = Ваша кожа становится липкой и вязкой.
|
||||
disease-polymorph-slime = Ваше тело становится полужидким... вы превращаетесь в разумного слайма!
|
||||
disease-surgical-wound-pain = Ваша рана от операции пульсирует и болит!
|
||||
disease-surgical-wound-pain = Ваша рана от операции пульсирует и болит!
|
||||
disease-grave-fever-start = Вы чувствуете необъяснимую слабость...
|
||||
disease-grave-fever-weakness = Ваше тело слабеет...
|
||||
disease-grave-fever-terminal = Ваше тело разрушается...
|
||||
|
||||
@@ -13,9 +13,19 @@ vampire-role-greeting-human =
|
||||
vampire-role-greeting-animal =
|
||||
Вы - кровососущее животное.
|
||||
Ваша жизнь лишь в том, что вам нужна кровь.
|
||||
free-vampire-greeting =
|
||||
Вы - Вампир!
|
||||
Вы были прокляты могущественным вампиром и восстали из мёртвых в "новом" для вас теле. Теперь вы вынуждены пить кровь, чтобы жить.
|
||||
Вы не подчиняетесь проклявшему вас вампиру и обладаете свободной волей.
|
||||
Вы не являетесь полноценным антагонистом и не должны убивать, за исключением случаев самообороны.
|
||||
|
||||
objective-issuer-vampire = [color=purple]Вампир[/color]
|
||||
vampires-drank-total-blood = Было выпито { $bloodAmount } единиц крови.
|
||||
|
||||
vampire-round-end-name = вампир
|
||||
vampire-round-end-header = [color=purple]Вампиры:[/color]
|
||||
vampire-round-end-info = [color=white]{ $name }[/color] ([color={ $color }]{ $class }[/color]) выпил [color=crimson]{ $blood }[/color] ед. крови.
|
||||
vampires-drank-total-blood = Всего было выпито [color=crimson]{ $bloodAmount }[/color] ед. крови.
|
||||
|
||||
thrall-greeting =
|
||||
Ваш разум затуманился, вы чувствуете холод.
|
||||
Ваша цель следовать приказам хозяина!
|
||||
|
||||
@@ -1,40 +1,72 @@
|
||||
# Ui
|
||||
# Classes
|
||||
select-class-nonselected = Без класса
|
||||
select-class-hemomancer = Гемомансер
|
||||
select-class-umbrae = Умбра
|
||||
select-class-gargantua = Гаргантюа
|
||||
select-class-dantalion = Данталион
|
||||
select-class-bestia = Бестия
|
||||
|
||||
# System
|
||||
vampire-holy-point = СВЯТАЯ СИЛА ВОСПЛАМЕНЯЕТ ВАШУ КОЖУ!
|
||||
vampire-blooddrink-countion = Клыки впиваются в вашу шею
|
||||
vampire-blooddrink-countion = Клыки { $vampire } впиваются в вашу шею
|
||||
vampire-blooddrink-self = Вы не можете пить свою кровь
|
||||
vampire-blooddrink-rotted = Оно гниет!
|
||||
vampire-blooddrink-not-vampire = Вы не можете испить кровь вампира
|
||||
vampire-blooddrink-rotted = { $target } гниет!
|
||||
vampire-blooddrink-not-thrall = Вы не можете испить кровь тралла
|
||||
vampire-blooddrink-not-sentient = Вы не можете испить кровь неразумного
|
||||
vampire-blooddrink-empty = Жертва иссякла
|
||||
vampire-blooddrink-maxed-out = Вы уже насытились достаточно с этой жертвы
|
||||
vampire-blooddrink-ssd = Вы не можете испить кровь спящего
|
||||
vampire-blooddrink-countion-doafter = Вы чувствуете как становится холоднее
|
||||
vampire-ingest-holyblood = ВЫ НЕ МОЖЕТЕ ПИТЬ СВЯТУЮ КРОВЬ!!!
|
||||
vampire-full-stomach = Вы не можете пить больше
|
||||
vampire-startlight-burning = ВАША КОЖА ПОЛЫХАЕТ!
|
||||
vampire-true-power = Вы чувствуете истинную силу
|
||||
vampire-bittenbyvampire-examine = [color=red]Кажется, на шее есть укус[/color]
|
||||
|
||||
# Diablerie
|
||||
vampire-diablerie-aura-visible = [color=purple]Вокруг { $name } клубится тёмная, пульсирующая аура, которая пронизывает всё вокруг.[/color]
|
||||
vampire-diablerie-eyes-glow-examined = [color=red]Глаза { $name } созерцают неестественным красным цветом, это не к добру...[/color]
|
||||
|
||||
vampire-diablerie-no-class = Вы должны выбрать класс, чтобы иметь возможность поглотить другого вампира.
|
||||
vampire-diablerie-target-no-class = Ничего не произошло...
|
||||
vampire-diablerie-draining = Вы высасываете тёмную сущность из { $target }...
|
||||
vampire-diablerie-target-draining = Вы ощущаете боль по всему телу, теряя драгоценную часть своей силы.
|
||||
vampire-diablerie-dust = { $target } рассыпается в пепел!
|
||||
|
||||
vampire-diablerie-level-one = Сила вашего "Восстановления" возросла, и вы можете применять его больше раз.
|
||||
vampire-diablerie-level-two = Сила вашего взгляда возросла, и вы можете больше раз применять "Вспышку". Ваши глаза наливаются кроваво-красным светом.
|
||||
vampire-diablerie-level-three = Сила вашего "Восстановления" возросла, и теперь с его помощью вы можете восстанавливать внутренние кровотечения. Ваша аура теперь видна даже простым смертным. Вы всего в шаге от вершины могущества!
|
||||
vampire-diablerie-level-four = Сила вашего тела возросла. Вы достигли пика своего могущества. Теперь долгом всех смертных будет уничтожить вас!
|
||||
|
||||
vampire-ascended-announcement =
|
||||
Сканерами дальнего действия зафиксирован мощный всплеск блюспейс энергии, указывающий на появление вампира особого класса.
|
||||
Его личность — { $name }. Дальнейшее возвышение вампира должно быть немедленно предотвращено.
|
||||
|
||||
vampire-initiation-failed = Это существо не подходит для таинства посвящения...
|
||||
|
||||
# Abilities
|
||||
vampire-hungry = Вы ещё слишком голодны для эволюции
|
||||
vampire-blooddrink-countion-doafter = Вы чувствуете как становится холоднее
|
||||
vampire-heal-dead = Вы мертвы
|
||||
vampire-blood-sacrifice-insufficient-blood = У вас недостаточно крови для использования
|
||||
vampire-predator-senses-warning = Вы чувствуете что жертва { $location }
|
||||
vampire-predator-senses-nobody = Вы не чувствуете жертв по близости
|
||||
|
||||
# Hemomancer
|
||||
vampire-predator-senses-warning = С { $direction }а доносится запах крови...
|
||||
vampire-predator-senses-nobody = Тишина. Ни одного бьющегося сердца поблизости...
|
||||
vampire-predator-senses-puddle = Под вашими ногами образуется лужа крови. Это не к добру...
|
||||
vampire-blood-eruption-effect-message = Кровь бурлящая под вашими ногами поражает вас и сбивает с ног
|
||||
vampire-blood-true-power-started = Вы ощущаете силу
|
||||
vampire-blood-true-power-affected = Вы чувствуете как ваша кровь изливается из вас...
|
||||
|
||||
# Umbrae
|
||||
vampire-stealth-enabled = Вы вошли в покров
|
||||
vampire-stealth-disabled = Вы вышли из покрова
|
||||
vampire-teleport-failed = Перемещение не возможно
|
||||
|
||||
# Gargantua
|
||||
vampire-legs-ensnared = Ваши ноги связаны!
|
||||
|
||||
# Dantalion
|
||||
vampire-trall-count-update = Ваше максимальное количество слуг теперь { $count }
|
||||
vampire-max-trall-reached = Вы достигли предела траллов
|
||||
vampire-enthall-failed = Невозможно обратить { $target }
|
||||
vampire-enthall-already = { $target } уже обращен
|
||||
@@ -42,5 +74,85 @@ vampire-no-thrall = У вас нету траллов
|
||||
vampire-commune-title = Связь
|
||||
vampire-commune-prompt = Сообшение
|
||||
vampire-commune-default-message = Вы слышите голос у себя в голове...
|
||||
vampire-commune-sent = Послание отправлено
|
||||
vampire-pacify-failed = Невозможно применить на { $target }
|
||||
|
||||
# Bestia
|
||||
vampire-trophies-ui-title = Статус Трофеев
|
||||
vampire-trophies-ui-trophies = Трофеи
|
||||
vampire-trophies-ui-passives = Пассивные бонусы
|
||||
vampire-trophies-ui-abilities = Способности
|
||||
vampire-trophies-ui-max = (MAX)
|
||||
vampire-trophies-ui-no-bonuses = Нет бонусов
|
||||
|
||||
vampire-bestia-unknown = неизвестно
|
||||
vampire-bestia-heart = сердце
|
||||
vampire-bestia-lungs = легкие
|
||||
vampire-bestia-liver = печень
|
||||
vampire-bestia-kidneys = почки
|
||||
vampire-bestia-eyes = глаза
|
||||
vampire-bestia-stomach = желудок
|
||||
|
||||
vampire-bestia-dissect-self = Нельзя применить это действие к самому себе
|
||||
vampire-bestia-dissect-out-of-range = Цель слишком далеко
|
||||
vampire-bestia-dissect-dead = Цель мертва. Невозможно извлечь органы из трупа
|
||||
vampire-bestia-dissect-ssd = Вы не можете извлечь органы из спящего
|
||||
vampire-bestia-dissect-no-organs = У цели нет подходящих органов для извлечения
|
||||
vampire-bestia-dissect-no-blood = В крови цели нет органики
|
||||
vampire-bestia-dissect-inappropriate = Цель не подходит для извлечения органов
|
||||
vampire-bestia-dissect-victim-limit-reached = Вы уже извлекли максимальное количество органов с этой жертвы
|
||||
vampire-bestia-dissect-limit-reached = Вы достигли лимита по органам для {$organ}
|
||||
vampire-bestia-dissect-extract-fail = Не удалось извлечь орган
|
||||
vampire-bestia-dissect-success = Вы успешно извлекли {$organ}
|
||||
vampire-bestia-anabiosis-polymorphed = Невозможно впасть в анабиоз будучи обращенным
|
||||
|
||||
vampire-bestia-passive-brute-protection = Травмы
|
||||
vampire-bestia-passive-burn-protection = Ожоги
|
||||
vampire-bestia-passive-oxy-protection = Гипоксия
|
||||
vampire-bestia-passive-toxin-protection = Токсины
|
||||
vampire-bestia-passive-stamina = Выносливость
|
||||
vampire-bestia-passive-blood-cost-reduction = Затраты крови
|
||||
vampire-bestia-passive-suck-rate = Скорость укуса
|
||||
vampire-bestia-passive-cellular-protection = Клеточный
|
||||
vampire-bestia-passive-xray-vision = X-Ray
|
||||
vampire-bestia-passive-welding-protection = Сварка
|
||||
vampire-bestia-passive-flash-protection = Вспышки
|
||||
vampire-bestia-passive-blood-gain = Восполнение
|
||||
vampire-bestia-passive-healing-efficiency = Лечение
|
||||
|
||||
vampire-bestia-passive-unlocked = Да
|
||||
vampire-bestia-passive-locked = Нет
|
||||
|
||||
# Infected Trophy
|
||||
vampire-bestia-bonus-infected-trophy-heart = Урон +{$value}
|
||||
vampire-bestia-bonus-infected-trophy-liver = Шанс болезни +{$value}%
|
||||
vampire-bestia-bonus-infected-trophy-eyes = Время жизни +{$value}с
|
||||
vampire-bestia-bonus-infected-trophy-stomach = Радиус взрыва до {$value}
|
||||
|
||||
# Lunge
|
||||
vampire-bestia-bonus-lunge-heart = Оглушение +{$value}с
|
||||
vampire-bestia-bonus-lunge-lungs = Дальность до {$value} тайлов
|
||||
vampire-bestia-bonus-lunge-kidneys = Кровь за удар до {$value}
|
||||
vampire-bestia-bonus-lunge-stomach = Радиус урона до {$value} тайлов
|
||||
|
||||
# Mark Prey
|
||||
vampire-bestia-bonus-mark-prey-heart = {$damage} урона ожогами, шанс {$chance}%
|
||||
vampire-bestia-bonus-mark-prey-eyes = Дальность до {$value} тайлов
|
||||
vampire-bestia-bonus-mark-prey-kidneys = Длительность метки +{$value}с
|
||||
|
||||
# Metamorphosis Bats
|
||||
vampire-bestia-bonus-metamorphosis-bats-heart = Здоровье {$health}, урон +{$damage}
|
||||
vampire-bestia-bonus-metamorphosis-bats-lungs = Скорость +{$value}%
|
||||
vampire-bestia-bonus-metamorphosis-bats-liver = Вампир получает {$value} крови за укус
|
||||
vampire-bestia-bonus-metamorphosis-bats-kidneys = Лечение ×{$value}
|
||||
|
||||
# Summon Bats
|
||||
vampire-bestia-bonus-summon-bats-heart = Здоровье мышей {$health}, урон +{$damage}
|
||||
vampire-bestia-bonus-summon-bats-lungs = Скорость мышей +{$value}%
|
||||
vampire-bestia-bonus-summon-bats-liver = Вампир получает {$value} крови за укус мыши
|
||||
vampire-bestia-bonus-summon-bats-kidneys = Мыши лечат себя ×{$value}
|
||||
|
||||
# Metamorphosis Hound
|
||||
vampire-bestia-bonus-metamorphosis-hound-heart = Здоровье {$health}, урон +{$damage}
|
||||
vampire-bestia-bonus-metamorphosis-hound-lungs = Скорость +{$value}%
|
||||
vampire-bestia-bonus-metamorphosis-hound-kidneys = Лечение ×{$value}
|
||||
vampire-bestia-bonus-metamorphosis-hound-liver = Вампир получает {$value} крови за укус
|
||||
|
||||
@@ -8,6 +8,10 @@ ent-ActionVampireRejuvenate = [color=purple]Восстановление[/color]
|
||||
ent-ActionVampireGlare = [color=purple]Взгляд[/color]
|
||||
.desc = Поразите свою жертву лучом яркого света так, чтобы она потеряла сознание.
|
||||
|
||||
# Diablerie
|
||||
ent-ActionVampireSacramentInitiation = [color=purple]Таинство посвящения[/color]
|
||||
.desc = Вы достигли пика своей силы. За 50 крови вы можете воскрешать мёртвых как свободных вампиров. Они не являются вашими рабами.
|
||||
|
||||
# Hemomancer
|
||||
ent-ActionVampireClaws = [color=purple]Когти вампира[/color]
|
||||
.desc = Обножи свои когти и наноси вред своим врагам.
|
||||
@@ -41,8 +45,8 @@ ent-ActionVampireEternalDarkness = [color=purple]Вечная темнота[/co
|
||||
.desc = Погрузите это место в вечную тьму.
|
||||
|
||||
# Gargantua
|
||||
ent-ActionVampireRejuvenateAdvanced = [color=purple]Восстановление[/color]
|
||||
.desc = Твой второй шанс, твое второе дыхание.
|
||||
ent-ActionVampireRejuvenateAdvanced = { ent-ActionVampireRejuvenate }
|
||||
.desc = { ent-ActionVampireRejuvenate.desc }
|
||||
ent-ActionVampireBloodSwell = [color=purple]Нарастание крови[/color]
|
||||
.desc = Твоя кровь густеет, ты становишься сильнее.
|
||||
ent-ActionVampireBloodRush = [color=purple]Прилив крови[/color]
|
||||
@@ -59,9 +63,6 @@ ent-ActionVampireCharge = [color=purple]Рывок[/color]
|
||||
.desc = Сделайте рывок.
|
||||
|
||||
# Dantalion
|
||||
ent-ActionMaxThrallCountUpdate1 = [color=purple]Улучшить траллов[/color]
|
||||
ent-ActionMaxThrallCountUpdate2 = [color=purple]Улучшить траллов[/color]
|
||||
ent-ActionMaxThrallCountUpdate3 = [color=purple]Улучшить траллов[/color]
|
||||
ent-ActionEnthrall = [color=purple]Порабощение[/color]
|
||||
.desc = Обратите человека на свою сторону.
|
||||
ent-ActionCommune = [color=purple]Общение[/color]
|
||||
@@ -70,6 +71,8 @@ ent-ActionPacify = [color=purple]Умиротворение[/color]
|
||||
.desc = Успокой своего недоброжелателя.
|
||||
ent-ActionSubspaceSwap = [color=purple]Подпространственный обмен[/color]
|
||||
.desc = Поменяйтесь местами.
|
||||
ent-ActionDeployDecoy = [color=purple]Поставить приманку[/color]
|
||||
.desc = Создаёт вашу копию, а вы становитесь невидимым.
|
||||
ent-ActionRallyThralls = [color=purple]Сплотить рабов[/color]
|
||||
.desc = Дай силу своим рабам.
|
||||
ent-ActionBloodBond = [color=purple]Кровавая связь[/color]
|
||||
@@ -80,3 +83,29 @@ ent-ActionVampirePacifyNearby = [color=purple]Псионические волн
|
||||
.desc = Создайте псионическую волну, что успокоит ваших врагов в некотором радиусе.
|
||||
ent-ActionVampireThrallHeal = [color=purple]Поддержка Лидера[/color]
|
||||
.desc = Пусть услышат ваши поданные ваш голос. Восстанавливает траллов в некотором радиусе и вводит в них стимулирующие реагенты.
|
||||
|
||||
# Bestia
|
||||
ent-ActionVampireCheckTrophies = [color=purple]Проверить трофеи[/color]
|
||||
.desc = Открыть интерфейс для просмотра всех собранных органов и получаемых от них бонусов.
|
||||
ent-ActionVampireDissect = [color=purple]Препарировать[/color]
|
||||
.desc = Хирургически извлечь органы из живой жертвы. Каждый орган даёт уникальные пассивные или активные бонусы.
|
||||
ent-ActionVampireInfectedTrophy = [color=purple]Инфицированный трофей[/color]
|
||||
.desc = Запустить снаряд, наносящий урон, заражающий цель и взрывающийся при попадании. Урон, шанс заражения, время жизни и радиус урона зависят от собранных органов.
|
||||
ent-ActionVampireLunge = [color=purple]Рывок[/color]
|
||||
.desc = Устремляетесь к указанной точке. При приземлении оглушаете и вызываете кровотечение у всех врагов в области. Дальность, длительность оглушения, кража крови и радиус зависят от собранных органов.
|
||||
ent-ActionVampireMarkPrey = [color=purple]Отметить жертву[/color]
|
||||
.desc = Отмечаете цель, замедляя её и нанося периодический урон ожогами. Длительность метки, шанс ожога, урон и дальность зависят от собранных органов.
|
||||
ent-ActionVampireMetamorphosisBats = [color=purple]Метаморфоз Летучие мыши[/color]
|
||||
.desc = Превращаетесь в рой летучих мышей. В этой форме ваше здоровье, урон, скорость, вампиризм и самолечение зависят от собранных органов.
|
||||
ent-ActionVampireAnabiosis = [color=purple]Анабиоз[/color]
|
||||
.desc = Укрываетесь в гробу, чтобы регенерировать. Гроб защищает вас и погружает в глубокий сон, восстанавливая здоровье со временем.
|
||||
ent-ActionVampireSummonBats = [color=purple]Призвать летучих мышей[/color]
|
||||
.desc = Призываете 1-4 летучих мышей для битвы. Здоровье, урон, скорость и вампиризм каждой мыши зависят от собранных органов.
|
||||
ent-ActionVampireMetamorphosisHound = [color=purple]Метаморфоз Гончая[/color]
|
||||
.desc = Превращаетесь в могучего пса. В этой форме ваше здоровье, урон, скорость и самолечение зависят от собранных органов.
|
||||
|
||||
# Polymorph
|
||||
ent-ActionVampireResonantShriek = [color=purple]Резонирующий крик[/color]
|
||||
.desc = Издаёте мощный крик, который разбивает окна, ломает лампы и оглушает всех вокруг. Длительность оглушения и дальность зависят от собранных органов.
|
||||
ent-ActionVampireLungeFinale = [color=purple]Финальный Рывок[/color]
|
||||
.desc = Выполняете серию рывков на случайных жертвах поблизости. Каждые 10 трофеев дают дополнительный прыжок. Дальность, длительность оглушения, кража крови и радиус зависят от собранных органов.
|
||||
|
||||
@@ -21,4 +21,10 @@ ent-MobWolfling = вольпин
|
||||
.desc = Собрат по генетике Вульпакинам. Их очень весело сжигать но аккуратно, у них есть план.
|
||||
|
||||
ent-MobTajkey = фарва
|
||||
.desc = Маленькая версия Таярина. Он не похож на себя побольше. Чёрт возьми, что он вообще такое?
|
||||
.desc = Маленькая версия Таярина. Он не похож на себя побольше. Чёрт возьми, что он вообще такое?
|
||||
|
||||
ent-MobBats = летучие мыши
|
||||
.desc = Стая летучих мышей, подчиняющихся воле вампира. Они роятся вокруг, атакуя врагов и высасывая кровь.
|
||||
|
||||
ent-MobHellHound = гончая
|
||||
.desc = Ужасающий пёс из преисподней, вызванный вампиром. Его когти и зубы способны разорвать даже сталь.
|
||||
|
||||
+3
@@ -2,3 +2,6 @@ ent-CrateNecropolis = сундук некрополя
|
||||
.desc = Древний саркофаг, испускающий зловещее свечение. Содержит награды за победу над ужасами некрополя. Обладает странными защитными свойствами.
|
||||
ent-HoloCreate = голоящик
|
||||
.desc = Проекция обычного ящика, которая существует 8 минут!
|
||||
ent-CrateCoffinVampire = { ent-CrateCoffin }
|
||||
.desc = { ent-CrateCoffin.desc }
|
||||
.suffix = НЕ МАППИТЬ
|
||||
|
||||
@@ -28,7 +28,7 @@
|
||||
- !type:EvenHealthChange
|
||||
conditions:
|
||||
- !type:MetabolizerTypeCondition
|
||||
type: [ Bloodsucker ]
|
||||
type: [ Bloodsucker, Vampire ] # Corvax-Wega-Edit
|
||||
damage:
|
||||
Brute: -3
|
||||
Burn: -1.25
|
||||
@@ -38,6 +38,12 @@
|
||||
damage:
|
||||
types:
|
||||
Poison: 0.2
|
||||
# Corvax-Wega-Vampire-start
|
||||
conditions:
|
||||
- !type:MetabolizerTypeCondition
|
||||
type: [ Vampire ]
|
||||
inverted: true
|
||||
# Corvax-Wega-Vampire-end
|
||||
plantMetabolism:
|
||||
- !type:PlantAdjustWater
|
||||
amount: 0.5
|
||||
|
||||
@@ -24,3 +24,18 @@
|
||||
amount: 5
|
||||
- !type:EmptyAllContainers
|
||||
- !type:DeleteEntity
|
||||
|
||||
# Corvax-Wega-Vampire-start
|
||||
- node: cratecoffinvampire
|
||||
entity: CrateCoffinVampire
|
||||
edges:
|
||||
- to: start
|
||||
steps:
|
||||
- tool: Prying
|
||||
doAfter: 31
|
||||
completed:
|
||||
- !type:SpawnPrototype
|
||||
prototype: MaterialWoodPlank1
|
||||
amount: 5
|
||||
- !type:DeleteEntity
|
||||
# Corvax-Wega-Vampire-end
|
||||
|
||||
@@ -145,3 +145,8 @@
|
||||
id: Rave
|
||||
kind: source
|
||||
path: "/Textures/_Wega/Shaders/rave.swsl"
|
||||
|
||||
- type: shader
|
||||
id: NullDamage
|
||||
kind: source
|
||||
path: "/Textures/_Wega/Shaders/nulldamage.swsl"
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
# Basic
|
||||
#region Basic
|
||||
- type: entity
|
||||
id: ActionVampireSelectClass
|
||||
parent: BaseAction
|
||||
@@ -11,6 +11,7 @@
|
||||
useDelay: 5
|
||||
- type: InstantAction
|
||||
event: !type:VampireSelectClassActionEvent
|
||||
bloodCost: 150
|
||||
|
||||
- type: entity
|
||||
id: ActionDrinkBlood
|
||||
@@ -39,10 +40,23 @@
|
||||
icon: { sprite: _Wega/Interface/Actions/actions_vampire.rsi, state: "rejuvenate" }
|
||||
checkCanInteract: false
|
||||
checkConsciousness: false
|
||||
useDelay: 20
|
||||
useDelay: 5
|
||||
priority: 1
|
||||
- type: LimitedCharges
|
||||
maxCharges: 1
|
||||
- type: AutoRecharge
|
||||
rechargeDuration: 20
|
||||
- type: InstantAction
|
||||
event: !type:VampireRejuvenateActionEvent
|
||||
bloodCost: 200
|
||||
heal:
|
||||
types:
|
||||
Asphyxiation: -5
|
||||
Heat: -2
|
||||
Poison: -2
|
||||
healGroups:
|
||||
groups:
|
||||
Brute: -2
|
||||
|
||||
- type: entity
|
||||
id: ActionVampireGlare
|
||||
@@ -57,13 +71,36 @@
|
||||
sound: !type:SoundPathSpecifier
|
||||
path: /Audio/_Wega/Effects/Vampire/glare.ogg
|
||||
checkCanInteract: false
|
||||
useDelay: 30
|
||||
useDelay: 3
|
||||
priority: 1
|
||||
- type: LimitedCharges
|
||||
maxCharges: 2
|
||||
- type: AutoRecharge
|
||||
rechargeDuration: 30
|
||||
- type: TargetAction
|
||||
- type: EntityTargetAction
|
||||
event: !type:VampireGlareActionEvent
|
||||
|
||||
# Hemomancer
|
||||
# Diablerie
|
||||
- type: entity
|
||||
id: ActionVampireSacramentInitiation
|
||||
parent: BaseAction
|
||||
name: "[color=purple]Sacrament of Initiation[/color]"
|
||||
description: "You have reached the peak of your power. For 50 blood, you can raise the dead as free-willed vampires. They are not your thralls."
|
||||
categories: [ HideSpawnMenu ]
|
||||
components:
|
||||
- type: Action
|
||||
icon: { sprite: _Wega/Interface/Actions/actions_vampire.rsi, state: "revive" }
|
||||
itemIconStyle: BigAction
|
||||
useDelay: 300
|
||||
priority: -1
|
||||
- type: TargetAction
|
||||
- type: EntityTargetAction
|
||||
event: !type:VampireSacramentInitiationActionEvent
|
||||
bloodCost: 50
|
||||
#endregion
|
||||
|
||||
#region Hemomancer
|
||||
- type: entity
|
||||
id: ActionVampireClaws
|
||||
parent: BaseAction
|
||||
@@ -77,6 +114,7 @@
|
||||
useDelay: 15
|
||||
- type: InstantAction
|
||||
event: !type:VampireClawsActionEvent
|
||||
bloodCost: 20
|
||||
|
||||
- type: entity
|
||||
id: ActionVampireBloodTendrils
|
||||
@@ -94,6 +132,7 @@
|
||||
range: 10
|
||||
- type: WorldTargetAction
|
||||
event: !type:VampireBloodTentacleAction
|
||||
bloodCost: 10
|
||||
|
||||
- type: entity
|
||||
id: ActionVampireBloodBarrier
|
||||
@@ -111,6 +150,7 @@
|
||||
range: 10
|
||||
- type: WorldTargetAction
|
||||
event: !type:VampireBloodBarrierActionEvent
|
||||
bloodCost: 20
|
||||
|
||||
- type: entity
|
||||
id: ActionVampireSanguinePool
|
||||
@@ -125,6 +165,7 @@
|
||||
useDelay: 25
|
||||
- type: InstantAction
|
||||
event: !type:VampireSanguinePoolActionEvent
|
||||
bloodCost: 30
|
||||
|
||||
- type: entity
|
||||
id: ActionVampirePredatorSenses
|
||||
@@ -139,6 +180,8 @@
|
||||
useDelay: 10
|
||||
- type: InstantAction
|
||||
event: !type:VampirePredatorSensesActionEvent
|
||||
sound:
|
||||
path: "/Audio/Effects/Fluids/splat.ogg"
|
||||
|
||||
- type: entity
|
||||
id: ActionVampireBloodEruption
|
||||
@@ -153,6 +196,10 @@
|
||||
useDelay: 60
|
||||
- type: InstantAction
|
||||
event: !type:VampireBloodEruptionActionEvent
|
||||
bloodCost: 50
|
||||
damage:
|
||||
types:
|
||||
Blunt: 20
|
||||
|
||||
- type: entity
|
||||
id: ActionVampireBloodBringersRite
|
||||
@@ -167,8 +214,18 @@
|
||||
useDelay: 10
|
||||
- type: InstantAction
|
||||
event: !type:VampireBloodBringersRiteActionEvent
|
||||
bloodCost: 5
|
||||
heal:
|
||||
types:
|
||||
Heat: -1.5
|
||||
healGroups:
|
||||
groups:
|
||||
Brute: -8
|
||||
sound:
|
||||
path: "/Audio/Effects/Fluids/splat.ogg"
|
||||
#endregion
|
||||
|
||||
# Umbrae
|
||||
#region Umbrae
|
||||
- type: entity
|
||||
id: ActionVampireCloakOfDarkness
|
||||
parent: BaseAction
|
||||
@@ -182,6 +239,7 @@
|
||||
useDelay: 2
|
||||
- type: InstantAction
|
||||
event: !type:VampireCloakOfDarknessActionEvent
|
||||
bloodCost: 10
|
||||
|
||||
- type: entity
|
||||
id: ActionVampireShadowSnare
|
||||
@@ -199,6 +257,7 @@
|
||||
range: 2
|
||||
- type: WorldTargetAction
|
||||
event: !type:VampireShadowSnareActionEvent
|
||||
bloodCost: 20
|
||||
|
||||
- type: entity
|
||||
id: ActionVampireSoulAnchor
|
||||
@@ -213,6 +272,7 @@
|
||||
useDelay: 130
|
||||
- type: InstantAction
|
||||
event: !type:VampireSoulAnchorActionEvent
|
||||
bloodCost: 30
|
||||
|
||||
- type: entity
|
||||
id: ActionVampireDarkPassage
|
||||
@@ -230,6 +290,7 @@
|
||||
checkCanAccess: false
|
||||
- type: WorldTargetAction
|
||||
event: !type:VampireDarkPassageActionEvent
|
||||
bloodCost: 30
|
||||
|
||||
- type: entity
|
||||
id: ActionVampireExtinguish
|
||||
@@ -244,6 +305,10 @@
|
||||
useDelay: 30
|
||||
- type: InstantAction
|
||||
event: !type:VampireExtinguishActionEvent
|
||||
bloodCost: 20
|
||||
damage:
|
||||
types:
|
||||
Blunt: 5
|
||||
|
||||
- type: entity
|
||||
id: ActionVampireShadowBoxing
|
||||
@@ -260,6 +325,7 @@
|
||||
range: 5
|
||||
- type: EntityTargetAction
|
||||
event: !type:VampireShadowBoxingActionEvent
|
||||
bloodCost: 50
|
||||
|
||||
- type: entity
|
||||
id: ActionVampireEternalDarkness
|
||||
@@ -274,8 +340,13 @@
|
||||
useDelay: 10
|
||||
- type: InstantAction
|
||||
event: !type:VampireEternalDarknessActionEvent
|
||||
bloodCost: 5
|
||||
damage:
|
||||
types:
|
||||
Blunt: 5
|
||||
#endregion
|
||||
|
||||
# Gargantua
|
||||
#region Gargantua
|
||||
- type: entity
|
||||
id: ActionVampireRejuvenateAdvanced
|
||||
parent: BaseAction
|
||||
@@ -287,10 +358,24 @@
|
||||
icon: { sprite: _Wega/Interface/Actions/actions_vampire.rsi, state: "rejuvenate" }
|
||||
checkCanInteract: false
|
||||
checkConsciousness: false
|
||||
useDelay: 20
|
||||
useDelay: 5
|
||||
priority: 1
|
||||
- type: LimitedCharges
|
||||
maxCharges: 1
|
||||
- type: AutoRecharge
|
||||
rechargeDuration: 20
|
||||
- type: InstantAction
|
||||
event: !type:VampireRejuvenateAdvancedActionEvent
|
||||
event: !type:VampireRejuvenateActionEvent
|
||||
bloodCost: 200
|
||||
advanced: true
|
||||
heal:
|
||||
types:
|
||||
Asphyxiation: -5
|
||||
Heat: -2
|
||||
Poison: -2
|
||||
healGroups:
|
||||
groups:
|
||||
Brute: -2
|
||||
|
||||
- type: entity
|
||||
id: ActionVampireBloodSwell
|
||||
@@ -305,6 +390,7 @@
|
||||
useDelay: 40
|
||||
- type: InstantAction
|
||||
event: !type:VampireBloodSwellActionEvent
|
||||
bloodCost: 30
|
||||
|
||||
- type: entity
|
||||
id: ActionVampireBloodRush
|
||||
@@ -319,6 +405,7 @@
|
||||
useDelay: 30
|
||||
- type: InstantAction
|
||||
event: !type:VampireBloodRushActionEvent
|
||||
bloodCost: 15
|
||||
|
||||
- type: entity
|
||||
id: ActionVampireSeismicStomp
|
||||
@@ -333,6 +420,9 @@
|
||||
useDelay: 30
|
||||
- type: InstantAction
|
||||
event: !type:VampireSeismicStompActionEvent
|
||||
bloodCost: 25
|
||||
sound:
|
||||
path: "/Audio/Effects/Footsteps/largethud.ogg"
|
||||
|
||||
- type: entity
|
||||
id: ActionVampireBloodSwellAdvanced
|
||||
@@ -346,7 +436,9 @@
|
||||
itemIconStyle: BigAction
|
||||
useDelay: 40
|
||||
- type: InstantAction
|
||||
event: !type:VampireBloodSwellAdvancedActionEvent
|
||||
event: !type:VampireBloodSwellActionEvent
|
||||
bloodCost: 30
|
||||
advanced: true
|
||||
|
||||
- type: entity
|
||||
id: ActionVampireOverwhelmingForce
|
||||
@@ -361,6 +453,7 @@
|
||||
useDelay: 2
|
||||
- type: InstantAction
|
||||
event: !type:VampireOverwhelmingForceActionEvent
|
||||
bloodCost: 80
|
||||
|
||||
- type: entity
|
||||
id: ActionDemonicGrasp
|
||||
@@ -377,6 +470,7 @@
|
||||
range: 16
|
||||
- type: EntityTargetAction
|
||||
event: !type:VampireDemonicGraspActionEvent
|
||||
bloodCost: 10
|
||||
|
||||
- type: entity
|
||||
id: ActionVampireCharge
|
||||
@@ -390,48 +484,22 @@
|
||||
itemIconStyle: BigAction
|
||||
useDelay: 30
|
||||
- type: TargetAction
|
||||
range: 16
|
||||
checkCanAccess: false
|
||||
range: 16
|
||||
- type: WorldTargetAction
|
||||
event: !type:VampireChargeActionEvent
|
||||
bloodCost: 30
|
||||
components:
|
||||
- type: DamageOtherOnHit
|
||||
damage:
|
||||
types:
|
||||
Blunt: 50
|
||||
Structural: 800
|
||||
sound:
|
||||
path: "/Audio/Effects/Footsteps/largethud.ogg"
|
||||
#endregion
|
||||
|
||||
# Dantalion
|
||||
- type: entity
|
||||
id: ActionMaxThrallCountUpdate1
|
||||
parent: BaseAction
|
||||
name: "[color=purple]Upgrade thralls[/color]"
|
||||
categories: [ HideSpawnMenu ]
|
||||
components:
|
||||
- type: Action
|
||||
icon: { sprite: _Wega/Interface/Actions/actions_vampire.rsi, state: "enthrall" }
|
||||
itemIconStyle: BigAction
|
||||
- type: InstantAction
|
||||
event: !type:MaxThrallCountUpdateEvent
|
||||
|
||||
- type: entity
|
||||
id: ActionMaxThrallCountUpdate2
|
||||
parent: BaseAction
|
||||
name: "[color=purple]Upgrade thralls[/color]"
|
||||
categories: [ HideSpawnMenu ]
|
||||
components:
|
||||
- type: Action
|
||||
icon: { sprite: _Wega/Interface/Actions/actions_vampire.rsi, state: "enthrall" }
|
||||
itemIconStyle: BigAction
|
||||
- type: InstantAction
|
||||
event: !type:MaxThrallCountUpdateEvent
|
||||
|
||||
- type: entity
|
||||
id: ActionMaxThrallCountUpdate3
|
||||
parent: BaseAction
|
||||
name: "[color=purple]Upgrade thralls[/color]"
|
||||
categories: [ HideSpawnMenu ]
|
||||
components:
|
||||
- type: Action
|
||||
icon: { sprite: _Wega/Interface/Actions/actions_vampire.rsi, state: "enthrall" }
|
||||
itemIconStyle: BigAction
|
||||
- type: InstantAction
|
||||
event: !type:MaxThrallCountUpdateEvent
|
||||
|
||||
#region Dantalion
|
||||
- type: entity
|
||||
id: ActionEnthrall
|
||||
parent: BaseAction
|
||||
@@ -446,6 +514,7 @@
|
||||
- type: TargetAction
|
||||
- type: EntityTargetAction
|
||||
event: !type:VampireEnthrallActionEvent
|
||||
bloodCost: 150
|
||||
|
||||
- type: entity
|
||||
id: ActionCommune
|
||||
@@ -473,8 +542,10 @@
|
||||
itemIconStyle: BigAction
|
||||
useDelay: 10
|
||||
- type: TargetAction
|
||||
range: 7
|
||||
- type: EntityTargetAction
|
||||
event: !type:VampirePacifyActionEvent
|
||||
bloodCost: 30
|
||||
|
||||
- type: entity
|
||||
id: ActionSubspaceSwap
|
||||
@@ -491,20 +562,37 @@
|
||||
range: 16
|
||||
- type: EntityTargetAction
|
||||
event: !type:VampireSubspaceSwapActionEvent
|
||||
bloodCost: 15
|
||||
|
||||
#- type: entity
|
||||
# id: ActionDeployDecoy
|
||||
# parent: BaseAction
|
||||
# name: "[color=purple]Deploy decoy[/color]"
|
||||
# description: ""
|
||||
# categories: [ HideSpawnMenu ]
|
||||
# components:
|
||||
# - type: Action
|
||||
# icon: { sprite: _Wega/Interface/Actions/actions_vampire.rsi, state: "decoy" }
|
||||
# itemIconStyle: BigAction
|
||||
# useDelay: 20
|
||||
# - type: InstantAction
|
||||
# event: !type:VampireDeployDecoyActionEvent
|
||||
- type: entity
|
||||
id: ActionDeployDecoy
|
||||
parent: BaseAction
|
||||
name: "[color=purple]Deploy decoy[/color]"
|
||||
description: "It creates a copy of you, and you become invisible."
|
||||
categories: [ HideSpawnMenu ]
|
||||
components:
|
||||
- type: Action
|
||||
icon: { sprite: _Wega/Interface/Actions/actions_vampire.rsi, state: "decoy" }
|
||||
itemIconStyle: BigAction
|
||||
useDelay: 20
|
||||
- type: InstantAction
|
||||
event: !type:VampireDeployDecoyActionEvent
|
||||
bloodCost: 30
|
||||
components:
|
||||
- type: HTN
|
||||
rootTask:
|
||||
task: SimpleHostileCompound
|
||||
- type: NPCIgnoringOptimize
|
||||
- type: NpcFactionMember
|
||||
factions:
|
||||
- Passive
|
||||
- type: NoSlip
|
||||
- type: Tag
|
||||
tags:
|
||||
- CannotSuicide
|
||||
- DoorBumpOpener
|
||||
- StunImmune
|
||||
- SlowImmune
|
||||
|
||||
- type: entity
|
||||
id: ActionRallyThralls
|
||||
@@ -519,6 +607,7 @@
|
||||
useDelay: 30
|
||||
- type: InstantAction
|
||||
event: !type:VampireRallyThrallsActionEvent
|
||||
bloodCost: 40
|
||||
|
||||
- type: entity
|
||||
id: ActionBloodBond
|
||||
@@ -533,6 +622,30 @@
|
||||
useDelay: 2
|
||||
- type: InstantAction
|
||||
event: !type:VampireBloodBondActionEvent
|
||||
bloodCost: 5
|
||||
|
||||
- type: entity
|
||||
id: ActionVampireThrallHeal
|
||||
parent: BaseAction
|
||||
name: "[color=purple]Leader's support[/color]"
|
||||
description: "Support your thralls with your speak. Heal them by it and make them rise."
|
||||
categories: [ HideSpawnMenu ]
|
||||
components:
|
||||
- type: Action
|
||||
icon: { sprite: _Wega/Interface/Actions/actions_vampire.rsi, state: "thrallheal" }
|
||||
itemIconStyle: BigAction
|
||||
useDelay: 90
|
||||
- type: InstantAction
|
||||
event: !type:VampireThrallHealActionEvent
|
||||
bloodCost: 50
|
||||
heal:
|
||||
types:
|
||||
Heat: -10
|
||||
Poison: -10
|
||||
healGroups:
|
||||
groups:
|
||||
Airloss: -10
|
||||
Brute: -10
|
||||
|
||||
- type: entity
|
||||
id: ActionMassHysteria
|
||||
@@ -547,7 +660,8 @@
|
||||
useDelay: 60
|
||||
- type: InstantAction
|
||||
event: !type:VampireMassHysteriaActionEvent
|
||||
|
||||
bloodCost: 40
|
||||
|
||||
- type: entity
|
||||
id: ActionVampirePacifyNearby
|
||||
parent: BaseAction
|
||||
@@ -561,17 +675,180 @@
|
||||
useDelay: 40
|
||||
- type: InstantAction
|
||||
event: !type:VampirePacifyNearbyActionEvent
|
||||
|
||||
bloodCost: 50
|
||||
#endregion
|
||||
|
||||
#region Bestia
|
||||
- type: entity
|
||||
id: ActionVampireThrallHeal
|
||||
id: ActionVampireCheckTrophies
|
||||
parent: BaseAction
|
||||
name: "[color=purple]Leader's support[/color]"
|
||||
description: "Support your thralls with your speak. Heal them by it and make them rise."
|
||||
name: "[color=purple]Check Trophies[/color]"
|
||||
description: "Open the interface to view all the organs you have collected and the bonuses they provide."
|
||||
categories: [ HideSpawnMenu ]
|
||||
components:
|
||||
- type: Action
|
||||
icon: { sprite: _Wega/Interface/Actions/actions_vampire.rsi, state: "thrallheal" }
|
||||
itemIconStyle: BigAction
|
||||
useDelay: 90
|
||||
icon: { sprite: _Wega/Interface/Actions/actions_vampire.rsi, state: "rush" }
|
||||
useDelay: 1
|
||||
- type: InstantAction
|
||||
event: !type:VampireThrallHealActionEvent
|
||||
event: !type:VampireCheckTrophiesActionEvent
|
||||
|
||||
- type: entity
|
||||
id: ActionVampireDissect
|
||||
parent: BaseAction
|
||||
name: "[color=purple]Dissect[/color]"
|
||||
description: "Surgically extract organs from a living victim. Each organ gives you unique passive or active bonuses."
|
||||
categories: [ HideSpawnMenu ]
|
||||
components:
|
||||
- type: Action
|
||||
icon: { sprite: _Wega/Interface/Actions/actions_vampire.rsi, state: "claws" }
|
||||
itemIconStyle: BigAction
|
||||
useDelay: 5
|
||||
- type: TargetAction
|
||||
- type: EntityTargetAction
|
||||
event: !type:VampireDissectActionEvent
|
||||
bloodCost: 10
|
||||
|
||||
- type: entity
|
||||
id: ActionVampireInfectedTrophy
|
||||
parent: BaseAction
|
||||
name: "[color=purple]Infected Trophy[/color]"
|
||||
description: "Launch a projectile that deals damage, infects the target, and explodes on impact. Damage, infection chance, lifetime, and damage radius scale with your collected organs."
|
||||
categories: [ HideSpawnMenu ]
|
||||
components:
|
||||
- type: Action
|
||||
icon: { sprite: _Wega/Interface/Actions/actions_vampire.rsi, state: "infected_trophy" }
|
||||
itemIconStyle: BigAction
|
||||
useDelay: 10
|
||||
- type: TargetAction
|
||||
range: 8
|
||||
- type: EntityTargetAction
|
||||
event: !type:VampireInfectedTrophyActionEvent
|
||||
bloodCost: 30
|
||||
|
||||
- type: entity
|
||||
id: ActionVampireLunge
|
||||
parent: BaseAction
|
||||
name: "[color=purple]Lunge[/color]"
|
||||
description: "Launch yourself at a target location. Upon landing, you stun and cause bleeding to all enemies in an area. Range, stun duration, blood stolen, and radius scale with your collected organs."
|
||||
categories: [ HideSpawnMenu ]
|
||||
components:
|
||||
- type: Action
|
||||
icon: { sprite: _Wega/Interface/Actions/actions_vampire.rsi, state: "charge" }
|
||||
itemIconStyle: BigAction
|
||||
useDelay: 5
|
||||
- type: TargetAction
|
||||
checkCanAccess: false
|
||||
range: 11
|
||||
- type: WorldTargetAction
|
||||
event: !type:VampireLungeActionEvent
|
||||
bloodCost: 25
|
||||
|
||||
- type: entity
|
||||
id: ActionVampireMarkPrey
|
||||
parent: BaseAction
|
||||
name: "[color=purple]Mark the Prey[/color]"
|
||||
description: "Mark a target, slowing them and dealing periodic burn damage. Mark duration, burn chance, damage, and range scale with your collected organs."
|
||||
categories: [ HideSpawnMenu ]
|
||||
components:
|
||||
- type: Action
|
||||
icon: { sprite: _Wega/Interface/Actions/actions_vampire.rsi, state: "predator_sense" }
|
||||
itemIconStyle: BigAction
|
||||
useDelay: 20
|
||||
- type: TargetAction
|
||||
range: 8
|
||||
- type: EntityTargetAction
|
||||
event: !type:VampireMarkPreyActionEvent
|
||||
bloodCost: 25
|
||||
|
||||
- type: entity
|
||||
id: ActionVampireMetamorphosisBats
|
||||
parent: BaseAction
|
||||
name: "[color=purple]Metamorphosis Bats[/color]"
|
||||
description: "Transform into a swarm of bats. In this form, your health, damage, speed, leeching, and self-healing scale with your collected organs."
|
||||
categories: [ HideSpawnMenu ]
|
||||
components:
|
||||
- type: Action
|
||||
icon: { sprite: _Wega/Interface/Actions/actions_vampire.rsi, state: "meta_bats" }
|
||||
useDelay: 30
|
||||
- type: InstantAction
|
||||
event: !type:VampireMetamorphosisBatsActionEvent
|
||||
bloodCost: 45
|
||||
|
||||
- type: entity
|
||||
id: ActionVampireAnabiosis
|
||||
parent: BaseAction
|
||||
name: "[color=purple]Anabiosis[/color]"
|
||||
description: "Retreat into a coffin to regenerate. The coffin protects you and puts you into a deep sleep, restoring your health over time."
|
||||
categories: [ HideSpawnMenu ]
|
||||
components:
|
||||
- type: Action
|
||||
icon: { sprite: _Wega/Interface/Actions/actions_vampire.rsi, state: "coffin" }
|
||||
useDelay: 180
|
||||
- type: InstantAction
|
||||
event: !type:VampireAnabiosisActionEvent
|
||||
bloodCost: 70
|
||||
|
||||
- type: entity
|
||||
id: ActionVampireSummonBats
|
||||
parent: BaseAction
|
||||
name: "[color=purple]Summon Bats[/color]"
|
||||
description: "Summon 1-4 bats to fight for you. Each bat's health, damage, speed, and leeching scale with your collected organs."
|
||||
categories: [ HideSpawnMenu ]
|
||||
components:
|
||||
- type: Action
|
||||
icon: { sprite: _Wega/Interface/Actions/actions_vampire.rsi, state: "bats" }
|
||||
useDelay: 30
|
||||
- type: InstantAction
|
||||
event: !type:VampireSummonBatsActionEvent
|
||||
bloodCost: 50
|
||||
sound:
|
||||
path: /Audio/_Wega/Effects/Vampire/bats_spawn.ogg
|
||||
|
||||
- type: entity
|
||||
id: ActionVampireMetamorphosisHound
|
||||
parent: BaseAction
|
||||
name: "[color=purple]Metamorphosis Hound[/color]"
|
||||
description: "Transform into a powerful hound. In this form, your health, damage, speed, and self-healing scale with your collected organs."
|
||||
categories: [ HideSpawnMenu ]
|
||||
components:
|
||||
- type: Action
|
||||
icon: { sprite: _Wega/Interface/Actions/actions_vampire.rsi, state: "blood_hound" }
|
||||
useDelay: 30
|
||||
- type: InstantAction
|
||||
event: !type:VampireMetamorphosisHoundActionEvent
|
||||
bloodCost: 60
|
||||
|
||||
# Polymorph
|
||||
- type: entity
|
||||
id: ActionVampireResonantShriek
|
||||
parent: BaseAction
|
||||
name: "[color=purple]Resonant Shriek[/color]"
|
||||
description: "Let out a powerful shriek that shatters windows, breaks lights, and stuns everyone nearby. Stun duration and range scale with your collected organs."
|
||||
categories: [ HideSpawnMenu ]
|
||||
components:
|
||||
- type: Action
|
||||
icon: { sprite: _Wega/Interface/Actions/actions_vampire.rsi, state: "shriek" }
|
||||
useDelay: 20
|
||||
- type: InstantAction
|
||||
event: !type:VampireResonantShriekActionEvent
|
||||
bloodCost: 40
|
||||
damage:
|
||||
types:
|
||||
Structural: 200
|
||||
sound:
|
||||
path: /Audio/_Wega/Effects/Vampire/creepyshriek.ogg
|
||||
|
||||
- type: entity
|
||||
id: ActionVampireLungeFinale
|
||||
parent: BaseAction
|
||||
name: "[color=purple]Lunge Finale[/color]"
|
||||
description: "Perform a series of lunges on random nearby victims. Each 10 trophies grants an additional jump. Range, stun duration, blood stolen, and damage radius scale with your collected organs."
|
||||
categories: [ HideSpawnMenu ]
|
||||
components:
|
||||
- type: Action
|
||||
icon: { sprite: _Wega/Interface/Actions/actions_vampire.rsi, state: "lunge_finale" }
|
||||
useDelay: 20
|
||||
- type: InstantAction
|
||||
event: !type:VampireLungeFinaleActionEvent
|
||||
bloodCost: 80
|
||||
#endregion
|
||||
|
||||
@@ -17,11 +17,13 @@
|
||||
layers:
|
||||
- map: [ "enum.AlertVisualLayers.Base" ]
|
||||
- map: [ "enum.VampireVisualLayers.Digit1" ]
|
||||
offset: 0.2, 0.25
|
||||
offset: 0.16, 0.25
|
||||
- map: [ "enum.VampireVisualLayers.Digit2" ]
|
||||
offset: 0.325, 0.25
|
||||
offset: 0.28, 0.25
|
||||
- map: [ "enum.VampireVisualLayers.Digit3" ]
|
||||
offset: 0.45, 0.25
|
||||
offset: 0.40, 0.25
|
||||
- map: [ "enum.VampireVisualLayers.Digit4" ]
|
||||
offset: 0.52, 0.25
|
||||
|
||||
- type: alert
|
||||
id: AlertBloodRite
|
||||
@@ -34,7 +36,7 @@
|
||||
description: alerts-bloodrite-desc
|
||||
minSeverity: 0
|
||||
maxSeverity: 1
|
||||
|
||||
|
||||
- type: alert
|
||||
id: AlertEternalDarkness
|
||||
icons:
|
||||
@@ -43,4 +45,4 @@
|
||||
name: alerts-eternaldarkness-name
|
||||
description: alerts-eternaldarkness-desc
|
||||
minSeverity: 0
|
||||
maxSeverity: 0
|
||||
maxSeverity: 0
|
||||
|
||||
@@ -108,10 +108,6 @@
|
||||
damage:
|
||||
types:
|
||||
Blunt: 5
|
||||
- type: DamageOtherOnHit
|
||||
damage:
|
||||
types:
|
||||
Blunt: 5
|
||||
- type: MobThresholds
|
||||
thresholds:
|
||||
0: Alive
|
||||
|
||||
@@ -4,8 +4,8 @@
|
||||
Blunt: 0.8
|
||||
Piercing: 0.8
|
||||
Cold: 0.2
|
||||
Heat: 1
|
||||
Shock: 1
|
||||
Heat: 1.5
|
||||
Shock: 1.5
|
||||
Poison: 0.0
|
||||
Radiation: 0.0
|
||||
|
||||
@@ -15,8 +15,8 @@
|
||||
Blunt: 0.4
|
||||
Piercing: 0.4
|
||||
Cold: 0.1
|
||||
Heat: 0.5
|
||||
Shock: 0.5
|
||||
Heat: 1.2
|
||||
Shock: 1.2
|
||||
Poison: 0.0
|
||||
Radiation: 0.0
|
||||
|
||||
|
||||
@@ -225,4 +225,107 @@
|
||||
- !type:DiseaseBedrestCure
|
||||
maxLength: 300
|
||||
|
||||
- type: disease
|
||||
id: VampireGraveFever
|
||||
name: disease-vampire-grave-fever
|
||||
infectious: false
|
||||
cureResist: 0.15
|
||||
stages:
|
||||
- 0
|
||||
- 180
|
||||
- 600
|
||||
effects:
|
||||
- !type:DiseasePopUp
|
||||
probability: 0.05
|
||||
message: disease-grave-fever-start
|
||||
visualType: Small
|
||||
stages:
|
||||
- 0
|
||||
- !type:DiseaseSnough
|
||||
probability: 0.03
|
||||
emote: Cough
|
||||
stages:
|
||||
- 0
|
||||
- !type:DiseaseAdjustReagent
|
||||
probability: 0.2
|
||||
reagent: Toxin
|
||||
amount: 0.5
|
||||
stages:
|
||||
- 1
|
||||
- 2
|
||||
- !type:DiseaseHealthChange
|
||||
probability: 0.1
|
||||
damage:
|
||||
groups:
|
||||
Toxin: 2
|
||||
stages:
|
||||
- 1
|
||||
- 2
|
||||
- !type:DiseasePopUp
|
||||
probability: 0.03
|
||||
message: disease-grave-fever-weakness
|
||||
visualType: Medium
|
||||
stages:
|
||||
- 1
|
||||
- 2
|
||||
- !type:DiseaseGenericStatusEffect
|
||||
probability: 0.05
|
||||
key: Stun
|
||||
component: Stunned
|
||||
time: 3
|
||||
type: Add
|
||||
stages:
|
||||
- 1
|
||||
- 2
|
||||
- !type:DiseaseHealthChange
|
||||
probability: 0.15
|
||||
damage:
|
||||
groups:
|
||||
Toxin: 3
|
||||
Airloss: 4
|
||||
stages:
|
||||
- 2
|
||||
- !type:DiseaseHealthChange
|
||||
probability: 0.05
|
||||
damage:
|
||||
types:
|
||||
Cellular: 1
|
||||
stages:
|
||||
- 2
|
||||
- !type:DiseaseGenericStatusEffect
|
||||
probability: 0.1
|
||||
key: Stun
|
||||
component: Stunned
|
||||
time: 6
|
||||
type: Add
|
||||
stages:
|
||||
- 2
|
||||
- !type:DiseaseVomit
|
||||
probability: 0.02
|
||||
stages:
|
||||
- 2
|
||||
- !type:DiseasePopUp
|
||||
probability: 0.02
|
||||
message: disease-grave-fever-terminal
|
||||
visualType: Large
|
||||
stages:
|
||||
- 2
|
||||
|
||||
cures:
|
||||
- !type:DiseaseReagentCure
|
||||
reagent: Spaceacillin
|
||||
min: 10
|
||||
stages:
|
||||
- 0
|
||||
- 1
|
||||
- 2
|
||||
- !type:DiseaseBedrestCure
|
||||
maxLength: 90
|
||||
stages:
|
||||
- 0
|
||||
- !type:DiseaseJustWaitCure
|
||||
maxLength: 1800
|
||||
stages:
|
||||
- 0
|
||||
|
||||
### Once radiation is refactored I want it to have a small chance of giving you regular cancer
|
||||
|
||||
@@ -12,9 +12,11 @@
|
||||
sprite: _Wega/Effects/bloodtentacle.rsi
|
||||
layers:
|
||||
- state: blood_tendril
|
||||
- type: StunOnContact
|
||||
- type: SlowdownOnContact
|
||||
multiplier: 0.4
|
||||
duration: 6
|
||||
blacklist:
|
||||
tags:
|
||||
components:
|
||||
- Vampire
|
||||
- type: Fixtures
|
||||
fixtures:
|
||||
@@ -74,11 +76,9 @@
|
||||
id: BloodBarrier
|
||||
name: blood barrier
|
||||
components:
|
||||
- type: TimedDespawn
|
||||
lifetime: 25
|
||||
- type: Tag
|
||||
tags:
|
||||
- Wall
|
||||
- type: Sprite
|
||||
sprite: _Wega/Effects/bloodbarrier.rsi
|
||||
state: blood_barrier
|
||||
- type: Physics
|
||||
bodyType: Static
|
||||
- type: Fixtures
|
||||
@@ -92,12 +92,6 @@
|
||||
layer:
|
||||
- WallLayer
|
||||
- type: Airtight
|
||||
- type: Sprite
|
||||
sprite: _Wega/Effects/bloodbarrier.rsi
|
||||
state: blood_barrier
|
||||
- type: Icon
|
||||
sprite: _Wega/Effects/bloodbarrier.rsi
|
||||
state: blood_barrier
|
||||
- type: Damageable
|
||||
damageContainer: Biological
|
||||
- type: Destructible
|
||||
@@ -116,6 +110,13 @@
|
||||
- !type:PlaySoundBehavior
|
||||
sound:
|
||||
path: /Audio/Items/drink.ogg
|
||||
- type: TimedDespawn
|
||||
lifetime: 25
|
||||
- type: SpawnOnDespawn
|
||||
prototype: PuddleBlood
|
||||
- type: Tag
|
||||
tags:
|
||||
- Wall
|
||||
|
||||
- type: entity
|
||||
parent: [ CollideFloorTrap, BaseShadow ]
|
||||
@@ -129,6 +130,7 @@
|
||||
lastVisibility: 0.1
|
||||
- type: TriggerOnCollide
|
||||
fixtureID: floortrap
|
||||
maxTriggers: 1
|
||||
- type: FlashOnTrigger
|
||||
- type: DamageOnTrigger
|
||||
targetUser: true
|
||||
@@ -136,11 +138,14 @@
|
||||
damage:
|
||||
types:
|
||||
Blunt: 20
|
||||
blacklist:
|
||||
components:
|
||||
- Vampire
|
||||
- type: StunOnContact
|
||||
fixtureId: floortrap
|
||||
duration: 3
|
||||
blacklist:
|
||||
tags:
|
||||
components:
|
||||
- Vampire
|
||||
|
||||
- type: entity
|
||||
@@ -153,7 +158,7 @@
|
||||
state: beacon
|
||||
- type: BeaconSoul
|
||||
- type: TimedDespawn
|
||||
lifetime: 420.0
|
||||
lifetime: 250.0 # 130s CD + 120s action
|
||||
- type: Fixtures
|
||||
fixtures:
|
||||
fix1:
|
||||
@@ -192,30 +197,34 @@
|
||||
- type: Sprite
|
||||
sprite: _Wega/Effects/shadowboxing.rsi
|
||||
state: shadow
|
||||
- type: Damageable
|
||||
damageContainer: Inorganic
|
||||
- type: Destructible
|
||||
thresholds:
|
||||
- trigger:
|
||||
!type:DamageTrigger
|
||||
damage: 20
|
||||
behaviors:
|
||||
- !type:DoActsBehavior
|
||||
acts: [ "Destruction" ]
|
||||
- type: MovementSpeedModifier
|
||||
baseWalkSpeed: 20
|
||||
baseSprintSpeed: 20
|
||||
- type: CombatMode
|
||||
- type: MeleeWeapon
|
||||
damage:
|
||||
types:
|
||||
Blunt: 15
|
||||
- type: DamageOnHit
|
||||
damage:
|
||||
types:
|
||||
Blunt: 20
|
||||
- type: NPCMeleeCombat
|
||||
- type: NPCIgnoringOptimize
|
||||
- type: NpcFactionMember
|
||||
factions:
|
||||
- Vampire
|
||||
- type: HTN
|
||||
rootTask:
|
||||
task: FollowerShadowCompound
|
||||
task: SimpleHostileCompound
|
||||
- type: TimedDespawn
|
||||
lifetime: 0.8
|
||||
|
||||
- type: htnCompound
|
||||
id: FollowerShadowCompound
|
||||
branches:
|
||||
- tasks:
|
||||
- !type:HTNCompoundTask
|
||||
task: MeleeCombatCompound
|
||||
- tasks:
|
||||
- !type:HTNCompoundTask
|
||||
task: IdleCompound
|
||||
lifetime: 20.0
|
||||
|
||||
@@ -6,4 +6,6 @@
|
||||
- type: Sprite
|
||||
layers:
|
||||
- state: blue
|
||||
- sprite: Objects/Specific/Chapel/bible.rsi
|
||||
state: icon
|
||||
- type: HolyPoint
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user