Revs (the spooky ones) (#9842)
@@ -19,7 +19,7 @@ namespace Content.Client.Ghost
|
|||||||
// No good way to get an event into the UI.
|
// No good way to get an event into the UI.
|
||||||
public int AvailableGhostRoleCount { get; private set; } = 0;
|
public int AvailableGhostRoleCount { get; private set; } = 0;
|
||||||
|
|
||||||
private bool _ghostVisibility;
|
private bool _ghostVisibility = true;
|
||||||
|
|
||||||
private bool GhostVisibility
|
private bool GhostVisibility
|
||||||
{
|
{
|
||||||
@@ -33,12 +33,9 @@ namespace Content.Client.Ghost
|
|||||||
|
|
||||||
_ghostVisibility = value;
|
_ghostVisibility = value;
|
||||||
|
|
||||||
foreach (var ghost in EntityManager.GetAllComponents(typeof(GhostComponent), true))
|
foreach (var ghost in EntityQuery<GhostComponent, SpriteComponent>(true))
|
||||||
{
|
{
|
||||||
if (EntityManager.TryGetComponent(ghost.Owner, out SpriteComponent? sprite))
|
ghost.Item2.Visible = true;
|
||||||
{
|
|
||||||
sprite.Visible = value;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -69,12 +66,6 @@ namespace Content.Client.Ghost
|
|||||||
{
|
{
|
||||||
component.Gui?.Dispose();
|
component.Gui?.Dispose();
|
||||||
component.Gui = null;
|
component.Gui = null;
|
||||||
|
|
||||||
// PlayerDetachedMsg might not fire due to deletion order so...
|
|
||||||
if (component.IsAttached)
|
|
||||||
{
|
|
||||||
GhostVisibility = false;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private void OnGhostPlayerAttach(EntityUid uid, GhostComponent component, PlayerAttachedEvent playerAttachedEvent)
|
private void OnGhostPlayerAttach(EntityUid uid, GhostComponent component, PlayerAttachedEvent playerAttachedEvent)
|
||||||
|
|||||||
16
Content.Client/Revenant/RevenantComponent.cs
Normal file
@@ -0,0 +1,16 @@
|
|||||||
|
using Content.Shared.Revenant;
|
||||||
|
|
||||||
|
namespace Content.Client.Revenant;
|
||||||
|
|
||||||
|
[RegisterComponent]
|
||||||
|
public sealed class RevenantComponent : SharedRevenantComponent
|
||||||
|
{
|
||||||
|
[DataField("state")]
|
||||||
|
public string State = "idle";
|
||||||
|
[DataField("corporealState")]
|
||||||
|
public string CorporealState = "active";
|
||||||
|
[DataField("stunnedState")]
|
||||||
|
public string StunnedState = "stunned";
|
||||||
|
[DataField("harvestingState")]
|
||||||
|
public string HarvestingState = "harvesting";
|
||||||
|
}
|
||||||
36
Content.Client/Revenant/RevenantSystem.cs
Normal file
@@ -0,0 +1,36 @@
|
|||||||
|
using Content.Shared.Revenant;
|
||||||
|
using Robust.Client.GameObjects;
|
||||||
|
|
||||||
|
namespace Content.Client.Revenant;
|
||||||
|
|
||||||
|
public sealed class RevenantSystem : EntitySystem
|
||||||
|
{
|
||||||
|
public override void Initialize()
|
||||||
|
{
|
||||||
|
base.Initialize();
|
||||||
|
|
||||||
|
SubscribeLocalEvent<RevenantComponent, AppearanceChangeEvent>(OnAppearanceChange);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void OnAppearanceChange(EntityUid uid, RevenantComponent component, ref AppearanceChangeEvent args)
|
||||||
|
{
|
||||||
|
if (args.Sprite == null)
|
||||||
|
return;
|
||||||
|
|
||||||
|
if (args.Component.TryGetData(RevenantVisuals.Harvesting, out bool harvesting) && harvesting)
|
||||||
|
{
|
||||||
|
args.Sprite.LayerSetState(0, component.HarvestingState);
|
||||||
|
}
|
||||||
|
else if (args.Component.TryGetData(RevenantVisuals.Stunned, out bool stunned) && stunned)
|
||||||
|
{
|
||||||
|
args.Sprite.LayerSetState(0, component.StunnedState);
|
||||||
|
}
|
||||||
|
else if (args.Component.TryGetData(RevenantVisuals.Corporeal, out bool corporeal))
|
||||||
|
{
|
||||||
|
if (corporeal)
|
||||||
|
args.Sprite.LayerSetState(0, component.CorporealState);
|
||||||
|
else
|
||||||
|
args.Sprite.LayerSetState(0, component.State);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
56
Content.Client/Revenant/Ui/RevenantBoundUserInterface.cs
Normal file
@@ -0,0 +1,56 @@
|
|||||||
|
using Content.Client.Traitor.Uplink;
|
||||||
|
using Content.Shared.Revenant;
|
||||||
|
using Content.Shared.Traitor.Uplink;
|
||||||
|
using JetBrains.Annotations;
|
||||||
|
using Robust.Client.GameObjects;
|
||||||
|
|
||||||
|
namespace Content.Client.Revenant.Ui;
|
||||||
|
|
||||||
|
[UsedImplicitly]
|
||||||
|
public sealed class RevenantBoundUserInterface : BoundUserInterface
|
||||||
|
{
|
||||||
|
private RevenantMenu? _menu;
|
||||||
|
|
||||||
|
public RevenantBoundUserInterface(ClientUserInterfaceComponent owner, object uiKey) : base(owner, uiKey)
|
||||||
|
{
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
protected override void Open()
|
||||||
|
{
|
||||||
|
_menu = new();
|
||||||
|
_menu.OpenCentered();
|
||||||
|
_menu.OnClose += Close;
|
||||||
|
|
||||||
|
_menu.OnListingButtonPressed += (_, listing) =>
|
||||||
|
{
|
||||||
|
SendMessage(new RevenantBuyListingMessage(listing));
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
protected override void UpdateState(BoundUserInterfaceState state)
|
||||||
|
{
|
||||||
|
base.UpdateState(state);
|
||||||
|
|
||||||
|
if (_menu == null)
|
||||||
|
return;
|
||||||
|
|
||||||
|
switch (state)
|
||||||
|
{
|
||||||
|
case RevenantUpdateState msg:
|
||||||
|
_menu.UpdateEssence(msg.Essence);
|
||||||
|
_menu.UpdateListing(msg.Listings);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
protected override void Dispose(bool disposing)
|
||||||
|
{
|
||||||
|
base.Dispose(disposing);
|
||||||
|
if (!disposing)
|
||||||
|
return;
|
||||||
|
|
||||||
|
_menu?.Close();
|
||||||
|
_menu?.Dispose();
|
||||||
|
}
|
||||||
|
}
|
||||||
21
Content.Client/Revenant/Ui/RevenantListingControl.xaml
Normal file
@@ -0,0 +1,21 @@
|
|||||||
|
<Control xmlns="https://spacestation14.io">
|
||||||
|
<BoxContainer Margin="8,8,8,8" Orientation="Vertical">
|
||||||
|
<BoxContainer Orientation="Horizontal">
|
||||||
|
<Label Name="RevenantItemName" HorizontalExpand="True" />
|
||||||
|
<Button
|
||||||
|
Name="RevenantItemBuyButton"
|
||||||
|
MinWidth="64"
|
||||||
|
HorizontalAlignment="Right"
|
||||||
|
Access="Public" />
|
||||||
|
</BoxContainer>
|
||||||
|
<PanelContainer StyleClasses="HighDivider" />
|
||||||
|
<BoxContainer HorizontalExpand="True" Orientation="Horizontal">
|
||||||
|
<TextureRect
|
||||||
|
Name="RevenantItemTexture"
|
||||||
|
Margin="0,0,4,0"
|
||||||
|
MinSize="48 48"
|
||||||
|
Stretch="KeepAspectCentered" />
|
||||||
|
<RichTextLabel Name="RevenantItemDescription" />
|
||||||
|
</BoxContainer>
|
||||||
|
</BoxContainer>
|
||||||
|
</Control>
|
||||||
26
Content.Client/Revenant/Ui/RevenantListingControl.xaml.cs
Normal file
@@ -0,0 +1,26 @@
|
|||||||
|
using Robust.Client.AutoGenerated;
|
||||||
|
using Robust.Client.Graphics;
|
||||||
|
using Robust.Client.UserInterface;
|
||||||
|
using Robust.Client.UserInterface.Controls;
|
||||||
|
using Robust.Client.UserInterface.XAML;
|
||||||
|
using Robust.Shared.Maths;
|
||||||
|
|
||||||
|
namespace Content.Client.Revenant.Ui;
|
||||||
|
|
||||||
|
[GenerateTypedNameReferences]
|
||||||
|
public sealed partial class RevenantListingControl : Control
|
||||||
|
{
|
||||||
|
public RevenantListingControl(string itemName, string itemDescription,
|
||||||
|
int itemPrice, bool canBuy, Texture? texture = null)
|
||||||
|
{
|
||||||
|
RobustXamlLoader.Load(this);
|
||||||
|
|
||||||
|
RevenantItemName.Text = itemName;
|
||||||
|
RevenantItemDescription.SetMessage(itemDescription);
|
||||||
|
|
||||||
|
RevenantItemBuyButton.Text = Loc.GetString("revenant-user-interface-cost", ("price", itemPrice));
|
||||||
|
RevenantItemBuyButton.Disabled = !canBuy;
|
||||||
|
|
||||||
|
RevenantItemTexture.Texture = texture;
|
||||||
|
}
|
||||||
|
}
|
||||||
41
Content.Client/Revenant/Ui/RevenantMenu.xaml
Normal file
@@ -0,0 +1,41 @@
|
|||||||
|
<DefaultWindow
|
||||||
|
xmlns="https://spacestation14.io"
|
||||||
|
xmlns:gfx="clr-namespace:Robust.Client.Graphics;assembly=Robust.Client"
|
||||||
|
Title="{Loc 'revenant-user-interface-title'}"
|
||||||
|
MinSize="512 512"
|
||||||
|
SetSize="512 512">
|
||||||
|
<BoxContainer Orientation="Vertical">
|
||||||
|
<BoxContainer Orientation="Vertical" VerticalExpand="True">
|
||||||
|
<BoxContainer Margin="4,4,4,4" Orientation="Horizontal">
|
||||||
|
<RichTextLabel
|
||||||
|
Name="BalanceInfo"
|
||||||
|
HorizontalAlignment="Left"
|
||||||
|
Access="Public"
|
||||||
|
HorizontalExpand="True" />
|
||||||
|
</BoxContainer>
|
||||||
|
<PanelContainer VerticalExpand="True">
|
||||||
|
<PanelContainer.PanelOverride>
|
||||||
|
<gfx:StyleBoxFlat BackgroundColor="#000000FF" />
|
||||||
|
</PanelContainer.PanelOverride>
|
||||||
|
<BoxContainer Orientation="Horizontal" VerticalExpand="True">
|
||||||
|
<ScrollContainer
|
||||||
|
Name="RevenantListingsScroll"
|
||||||
|
HScrollEnabled="False"
|
||||||
|
HorizontalExpand="True"
|
||||||
|
MinSize="100 256"
|
||||||
|
SizeFlagsStretchRatio="2"
|
||||||
|
VerticalExpand="True">
|
||||||
|
<BoxContainer
|
||||||
|
Name="RevenantListingsContainer"
|
||||||
|
MinSize="100 256"
|
||||||
|
Orientation="Vertical"
|
||||||
|
SizeFlagsStretchRatio="2"
|
||||||
|
VerticalExpand="True">
|
||||||
|
<!-- Listings are added here by code -->
|
||||||
|
</BoxContainer>
|
||||||
|
</ScrollContainer>
|
||||||
|
</BoxContainer>
|
||||||
|
</PanelContainer>
|
||||||
|
</BoxContainer>
|
||||||
|
</BoxContainer>
|
||||||
|
</DefaultWindow>
|
||||||
62
Content.Client/Revenant/Ui/RevenantMenu.xaml.cs
Normal file
@@ -0,0 +1,62 @@
|
|||||||
|
using Content.Client.Message;
|
||||||
|
using Content.Shared.FixedPoint;
|
||||||
|
using Content.Shared.Revenant;
|
||||||
|
using Robust.Client.AutoGenerated;
|
||||||
|
using Robust.Client.UserInterface.Controls;
|
||||||
|
using Robust.Client.UserInterface.CustomControls;
|
||||||
|
using Robust.Client.UserInterface.XAML;
|
||||||
|
using Robust.Client.Utility;
|
||||||
|
|
||||||
|
namespace Content.Client.Revenant.Ui;
|
||||||
|
|
||||||
|
[GenerateTypedNameReferences]
|
||||||
|
public sealed partial class RevenantMenu : DefaultWindow
|
||||||
|
{
|
||||||
|
private FixedPoint2 _essence = 0f;
|
||||||
|
|
||||||
|
public event Action<BaseButton.ButtonEventArgs, RevenantStoreListingPrototype>? OnListingButtonPressed;
|
||||||
|
|
||||||
|
public RevenantMenu()
|
||||||
|
{
|
||||||
|
RobustXamlLoader.Load(this);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void UpdateEssence(float essence)
|
||||||
|
{
|
||||||
|
// update balance label
|
||||||
|
_essence = essence;
|
||||||
|
var balanceStr = Loc.GetString("revenant-user-interface-essence-amount", ("amount", Math.Round(_essence.Float())));
|
||||||
|
BalanceInfo.SetMarkup(balanceStr);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void UpdateListing(List<RevenantStoreListingPrototype> listings)
|
||||||
|
{
|
||||||
|
// should probably chunk these out instead. to-do if this clogs the internet tubes.
|
||||||
|
// maybe read clients prototypes instead?
|
||||||
|
ClearListings();
|
||||||
|
foreach (var item in listings)
|
||||||
|
{
|
||||||
|
AddListingGui(item);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void AddListingGui(RevenantStoreListingPrototype listing)
|
||||||
|
{
|
||||||
|
var listingName = listing.ListingName;
|
||||||
|
var listingDesc = listing.Description;
|
||||||
|
var listingPrice = listing.Price;
|
||||||
|
var canBuy = _essence > listing.Price;
|
||||||
|
var texture = listing.Icon?.Frame0();
|
||||||
|
|
||||||
|
var newListing = new RevenantListingControl(listingName, listingDesc, listingPrice, canBuy, texture);
|
||||||
|
newListing.RevenantItemBuyButton.OnButtonDown += args
|
||||||
|
=> OnListingButtonPressed?.Invoke(args, listing);
|
||||||
|
|
||||||
|
RevenantListingsContainer.AddChild(newListing);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void ClearListings()
|
||||||
|
{
|
||||||
|
RevenantListingsContainer.Children.Clear();
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -119,7 +119,6 @@ namespace Content.Client.Traitor.Uplink
|
|||||||
if (texture == null)
|
if (texture == null)
|
||||||
texture = SpriteComponent.GetPrototypeIcon(prototype, _resourceCache).Default;
|
texture = SpriteComponent.GetPrototypeIcon(prototype, _resourceCache).Default;
|
||||||
|
|
||||||
|
|
||||||
var newListing = new UplinkListingControl(listingName, listingDesc, listingPrice, canBuy, texture);
|
var newListing = new UplinkListingControl(listingName, listingDesc, listingPrice, canBuy, texture);
|
||||||
newListing.UplinkItemBuyButton.OnButtonDown += args
|
newListing.UplinkItemBuyButton.OnButtonDown += args
|
||||||
=> OnListingButtonPressed?.Invoke(args, listing);
|
=> OnListingButtonPressed?.Invoke(args, listing);
|
||||||
|
|||||||
@@ -62,9 +62,8 @@ namespace Content.Server.Emag
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
var emaggedEvent = new GotEmaggedEvent(args.User);
|
var handled = DoEmag(args.Target, args.User);
|
||||||
RaiseLocalEvent(args.Target.Value, emaggedEvent, false);
|
if (handled)
|
||||||
if (emaggedEvent.Handled)
|
|
||||||
{
|
{
|
||||||
_popupSystem.PopupEntity(Loc.GetString("emag-success", ("target", Identity.Entity(args.Target.Value, EntityManager))), args.User,
|
_popupSystem.PopupEntity(Loc.GetString("emag-success", ("target", Identity.Entity(args.Target.Value, EntityManager))), args.User,
|
||||||
Filter.Entities(args.User), PopupType.Medium);
|
Filter.Entities(args.User), PopupType.Medium);
|
||||||
@@ -73,5 +72,15 @@ namespace Content.Server.Emag
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public bool DoEmag(EntityUid? target, EntityUid? user)
|
||||||
|
{
|
||||||
|
if (target == null || user == null)
|
||||||
|
return false;
|
||||||
|
|
||||||
|
var emaggedEvent = new GotEmaggedEvent(user.Value);
|
||||||
|
RaiseLocalEvent(target.Value, emaggedEvent, false);
|
||||||
|
return emaggedEvent.Handled;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -64,10 +64,9 @@ namespace Content.Server.Ghost
|
|||||||
var booCounter = 0;
|
var booCounter = 0;
|
||||||
foreach (var ent in ents)
|
foreach (var ent in ents)
|
||||||
{
|
{
|
||||||
var ghostBoo = new GhostBooEvent();
|
var handled = DoGhostBooEvent(ent);
|
||||||
RaiseLocalEvent(ent, ghostBoo, true);
|
|
||||||
|
|
||||||
if (ghostBoo.Handled)
|
if (handled)
|
||||||
booCounter++;
|
booCounter++;
|
||||||
|
|
||||||
if (booCounter >= component.BooMaxTargets)
|
if (booCounter >= component.BooMaxTargets)
|
||||||
@@ -272,5 +271,13 @@ namespace Content.Server.Ghost
|
|||||||
{
|
{
|
||||||
args.Cancel();
|
args.Cancel();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public bool DoGhostBooEvent(EntityUid target)
|
||||||
|
{
|
||||||
|
var ghostBoo = new GhostBooEvent();
|
||||||
|
RaiseLocalEvent(target, ghostBoo, true);
|
||||||
|
|
||||||
|
return ghostBoo.Handled;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
15
Content.Server/Revenant/CorporealComponent.cs
Normal file
@@ -0,0 +1,15 @@
|
|||||||
|
namespace Content.Server.Revenant;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Makes the target solid, visible, and applies a slowdown.
|
||||||
|
/// Meant to be used in conjunction with statusEffectSystem
|
||||||
|
/// </summary>
|
||||||
|
[RegisterComponent]
|
||||||
|
public sealed class CorporealComponent : Component
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// The debuff applied when the component is present.
|
||||||
|
/// </summary>
|
||||||
|
[ViewVariables(VVAccess.ReadWrite)]
|
||||||
|
public float MovementSpeedDebuff = 0.66f;
|
||||||
|
}
|
||||||
79
Content.Server/Revenant/EntitySystems/CorporealSystem.cs
Normal file
@@ -0,0 +1,79 @@
|
|||||||
|
using Content.Server.Visible;
|
||||||
|
using Content.Shared.Physics;
|
||||||
|
using Content.Shared.Revenant;
|
||||||
|
using Content.Shared.Movement;
|
||||||
|
using Robust.Server.GameObjects;
|
||||||
|
using Robust.Server.GameStates;
|
||||||
|
using Robust.Shared.Physics;
|
||||||
|
using System.Linq;
|
||||||
|
using Content.Shared.Movement.Systems;
|
||||||
|
|
||||||
|
namespace Content.Server.Revenant.EntitySystems;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Makes the revenant solid when the component is applied.
|
||||||
|
/// Additionally applies a few visual effects.
|
||||||
|
/// Used for status effect.
|
||||||
|
/// </summary>
|
||||||
|
public sealed class CorporealSystem : EntitySystem
|
||||||
|
{
|
||||||
|
[Dependency] private readonly VisibilitySystem _visibilitySystem = default!;
|
||||||
|
[Dependency] private readonly MovementSpeedModifierSystem _movement = default!;
|
||||||
|
|
||||||
|
public override void Initialize()
|
||||||
|
{
|
||||||
|
base.Initialize();
|
||||||
|
|
||||||
|
SubscribeLocalEvent<CorporealComponent, ComponentStartup>(OnStartup);
|
||||||
|
SubscribeLocalEvent<CorporealComponent, ComponentShutdown>(OnShutdown);
|
||||||
|
SubscribeLocalEvent<CorporealComponent, RefreshMovementSpeedModifiersEvent>(OnRefresh);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void OnRefresh(EntityUid uid, CorporealComponent component, RefreshMovementSpeedModifiersEvent args)
|
||||||
|
{
|
||||||
|
args.ModifySpeed(component.MovementSpeedDebuff, component.MovementSpeedDebuff);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void OnStartup(EntityUid uid, CorporealComponent component, ComponentStartup args)
|
||||||
|
{
|
||||||
|
if (TryComp<AppearanceComponent>(uid, out var app))
|
||||||
|
app.SetData(RevenantVisuals.Corporeal, true);
|
||||||
|
|
||||||
|
if (TryComp<FixturesComponent>(uid, out var fixtures) && fixtures.FixtureCount >= 1)
|
||||||
|
{
|
||||||
|
var fixture = fixtures.Fixtures.Values.First();
|
||||||
|
|
||||||
|
fixture.CollisionMask = (int) (CollisionGroup.SmallMobMask | CollisionGroup.GhostImpassable);
|
||||||
|
fixture.CollisionLayer = (int) CollisionGroup.SmallMobLayer;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (TryComp<VisibilityComponent>(uid, out var visibility))
|
||||||
|
{
|
||||||
|
_visibilitySystem.RemoveLayer(visibility, (int) VisibilityFlags.Ghost, false);
|
||||||
|
_visibilitySystem.AddLayer(visibility, (int) VisibilityFlags.Normal, false);
|
||||||
|
_visibilitySystem.RefreshVisibility(visibility);
|
||||||
|
}
|
||||||
|
_movement.RefreshMovementSpeedModifiers(uid);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void OnShutdown(EntityUid uid, CorporealComponent component, ComponentShutdown args)
|
||||||
|
{
|
||||||
|
if (TryComp<AppearanceComponent>(uid, out var app))
|
||||||
|
app.SetData(RevenantVisuals.Corporeal, false);
|
||||||
|
|
||||||
|
if (TryComp<FixturesComponent>(uid, out var fixtures) && fixtures.FixtureCount >= 1)
|
||||||
|
{
|
||||||
|
var fixture = fixtures.Fixtures.Values.First();
|
||||||
|
|
||||||
|
fixture.CollisionMask = (int) CollisionGroup.GhostImpassable;
|
||||||
|
fixture.CollisionLayer = 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (TryComp<VisibilityComponent>(uid, out var visibility))
|
||||||
|
{
|
||||||
|
_visibilitySystem.AddLayer(visibility, (int) VisibilityFlags.Ghost, false);
|
||||||
|
_visibilitySystem.RemoveLayer(visibility, (int) VisibilityFlags.Normal, false);
|
||||||
|
_visibilitySystem.RefreshVisibility(visibility);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
71
Content.Server/Revenant/EntitySystems/EssenceSystem.cs
Normal file
@@ -0,0 +1,71 @@
|
|||||||
|
using Content.Server.Mind.Components;
|
||||||
|
using Content.Shared.Examine;
|
||||||
|
using Content.Shared.MobState;
|
||||||
|
using Content.Shared.MobState.Components;
|
||||||
|
using Robust.Shared.Random;
|
||||||
|
|
||||||
|
namespace Content.Server.Revenant.EntitySystems;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Attached to entities when a revenant drains them in order to
|
||||||
|
/// manage their essence.
|
||||||
|
/// </summary>
|
||||||
|
public sealed class EssenceSystem : EntitySystem
|
||||||
|
{
|
||||||
|
[Dependency] private readonly IRobustRandom _random = default!;
|
||||||
|
|
||||||
|
public override void Initialize()
|
||||||
|
{
|
||||||
|
base.Initialize();
|
||||||
|
|
||||||
|
SubscribeLocalEvent<EssenceComponent, ComponentStartup>(UpdateEssenceAmount);
|
||||||
|
SubscribeLocalEvent<EssenceComponent, MobStateChangedEvent>(UpdateEssenceAmount);
|
||||||
|
SubscribeLocalEvent<EssenceComponent, MindAddedMessage>(UpdateEssenceAmount);
|
||||||
|
SubscribeLocalEvent<EssenceComponent, MindRemovedMessage>(UpdateEssenceAmount);
|
||||||
|
SubscribeLocalEvent<EssenceComponent, ExaminedEvent>(OnExamine);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void OnExamine(EntityUid uid, EssenceComponent component, ExaminedEvent args)
|
||||||
|
{
|
||||||
|
if (!component.SearchComplete || !HasComp<RevenantComponent>(args.Examiner))
|
||||||
|
return;
|
||||||
|
|
||||||
|
string message;
|
||||||
|
switch (component.EssenceAmount)
|
||||||
|
{
|
||||||
|
case <= 45:
|
||||||
|
message = "revenant-soul-yield-low";
|
||||||
|
break;
|
||||||
|
case >= 90:
|
||||||
|
message = "revenant-soul-yield-high";
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
message = "revenant-soul-yield-average";
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
args.PushMarkup(Loc.GetString(message, ("target", uid)));
|
||||||
|
}
|
||||||
|
|
||||||
|
private void UpdateEssenceAmount(EntityUid uid, EssenceComponent component, EntityEventArgs args)
|
||||||
|
{
|
||||||
|
if (!TryComp<MobStateComponent>(uid, out var mob))
|
||||||
|
return;
|
||||||
|
|
||||||
|
switch (mob.CurrentState)
|
||||||
|
{
|
||||||
|
case DamageState.Alive:
|
||||||
|
if (TryComp<MindComponent>(uid, out var mind) && mind.Mind != null)
|
||||||
|
component.EssenceAmount = _random.NextFloat(75f, 100f);
|
||||||
|
else
|
||||||
|
component.EssenceAmount = _random.NextFloat(45f, 70f);
|
||||||
|
break;
|
||||||
|
case DamageState.Critical:
|
||||||
|
component.EssenceAmount = _random.NextFloat(35f, 50f);
|
||||||
|
break;
|
||||||
|
case DamageState.Dead:
|
||||||
|
component.EssenceAmount = _random.NextFloat(15f, 20f);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,311 @@
|
|||||||
|
using Content.Shared.Popups;
|
||||||
|
using Content.Shared.Damage;
|
||||||
|
using Content.Shared.MobState.Components;
|
||||||
|
using Content.Server.DoAfter;
|
||||||
|
using Content.Shared.Revenant;
|
||||||
|
using Robust.Shared.Random;
|
||||||
|
using Robust.Shared.Player;
|
||||||
|
using Robust.Shared.Map;
|
||||||
|
using Content.Shared.Tag;
|
||||||
|
using Content.Shared.Maps;
|
||||||
|
using Content.Server.Storage.Components;
|
||||||
|
using Content.Server.Light.Components;
|
||||||
|
using Content.Server.Ghost;
|
||||||
|
using Robust.Shared.Physics;
|
||||||
|
using Content.Shared.Throwing;
|
||||||
|
using Content.Server.Storage.EntitySystems;
|
||||||
|
using Content.Shared.Interaction;
|
||||||
|
using Content.Server.Disease;
|
||||||
|
using Content.Server.Disease.Components;
|
||||||
|
using Content.Shared.Item;
|
||||||
|
using Content.Shared.Bed.Sleep;
|
||||||
|
using Content.Shared.MobState;
|
||||||
|
using Content.Server.Explosion.EntitySystems;
|
||||||
|
using System.Linq;
|
||||||
|
using Content.Server.Emag;
|
||||||
|
using Content.Shared.CharacterAppearance.Components;
|
||||||
|
using Robust.Shared.Utility;
|
||||||
|
|
||||||
|
namespace Content.Server.Revenant.EntitySystems;
|
||||||
|
|
||||||
|
public sealed partial class RevenantSystem : EntitySystem
|
||||||
|
{
|
||||||
|
[Dependency] private readonly IMapManager _mapManager = default!;
|
||||||
|
[Dependency] private readonly ThrowingSystem _throwing = default!;
|
||||||
|
[Dependency] private readonly EntityStorageSystem _entityStorage = default!;
|
||||||
|
[Dependency] private readonly DiseaseSystem _disease = default!;
|
||||||
|
[Dependency] private readonly ExplosionSystem _explosion = default!;
|
||||||
|
[Dependency] private readonly EmagSystem _emag = default!;
|
||||||
|
[Dependency] private readonly SharedAppearanceSystem _appearance = default!;
|
||||||
|
[Dependency] private readonly GhostSystem _ghost = default!;
|
||||||
|
|
||||||
|
private void InitializeAbilities()
|
||||||
|
{
|
||||||
|
SubscribeLocalEvent<RevenantComponent, InteractNoHandEvent>(OnInteract);
|
||||||
|
SubscribeLocalEvent<RevenantComponent, SoulSearchDoAfterComplete>(OnSoulSearchComplete);
|
||||||
|
SubscribeLocalEvent<RevenantComponent, HarvestDoAfterComplete>(OnHarvestComplete);
|
||||||
|
SubscribeLocalEvent<RevenantComponent, HarvestDoAfterCancelled>(OnHarvestCancelled);
|
||||||
|
|
||||||
|
SubscribeLocalEvent<RevenantComponent, RevenantDefileActionEvent>(OnDefileAction);
|
||||||
|
SubscribeLocalEvent<RevenantComponent, RevenantOverloadLightsActionEvent>(OnOverloadLightsAction);
|
||||||
|
SubscribeLocalEvent<RevenantComponent, RevenantBlightActionEvent>(OnBlightAction);
|
||||||
|
SubscribeLocalEvent<RevenantComponent, RevenantMalfunctionActionEvent>(OnMalfunctionAction);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void OnInteract(EntityUid uid, RevenantComponent component, InteractNoHandEvent args)
|
||||||
|
{
|
||||||
|
var target = args.Target;
|
||||||
|
if (target == args.User)
|
||||||
|
return;
|
||||||
|
|
||||||
|
if (HasComp<PoweredLightComponent>(target))
|
||||||
|
{
|
||||||
|
args.Handled = _ghost.DoGhostBooEvent(target);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!HasComp<MobStateComponent>(target) || !HasComp<HumanoidAppearanceComponent>(target) || HasComp<RevenantComponent>(target))
|
||||||
|
return;
|
||||||
|
|
||||||
|
if (!_interact.InRangeUnobstructed(uid, target))
|
||||||
|
return;
|
||||||
|
|
||||||
|
args.Handled = true;
|
||||||
|
if (!TryComp<EssenceComponent>(target, out var essence) || !essence.SearchComplete)
|
||||||
|
{
|
||||||
|
EnsureComp<EssenceComponent>(target);
|
||||||
|
BeginSoulSearchDoAfter(uid, target, component);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
BeginHarvestDoAfter(uid, target, component, essence);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void BeginSoulSearchDoAfter(EntityUid uid, EntityUid target, RevenantComponent revenant)
|
||||||
|
{
|
||||||
|
_popup.PopupEntity(Loc.GetString("revenant-soul-searching", ("target", target)), uid, Filter.Entities(uid), PopupType.Medium);
|
||||||
|
revenant.SoulSearchCancelToken = new();
|
||||||
|
var searchDoAfter = new DoAfterEventArgs(uid, revenant.SoulSearchDuration, revenant.SoulSearchCancelToken.Token, target)
|
||||||
|
{
|
||||||
|
BreakOnUserMove = true,
|
||||||
|
DistanceThreshold = 2,
|
||||||
|
UserFinishedEvent = new SoulSearchDoAfterComplete(target),
|
||||||
|
};
|
||||||
|
_doAfter.DoAfter(searchDoAfter);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void OnSoulSearchComplete(EntityUid uid, RevenantComponent component, SoulSearchDoAfterComplete args)
|
||||||
|
{
|
||||||
|
if (!TryComp<EssenceComponent>(args.Target, out var essence))
|
||||||
|
return;
|
||||||
|
essence.SearchComplete = true;
|
||||||
|
|
||||||
|
string message;
|
||||||
|
switch (essence.EssenceAmount)
|
||||||
|
{
|
||||||
|
case <= 45:
|
||||||
|
message = "revenant-soul-yield-low";
|
||||||
|
break;
|
||||||
|
case >= 90:
|
||||||
|
message = "revenant-soul-yield-high";
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
message = "revenant-soul-yield-average";
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
_popup.PopupEntity(Loc.GetString(message, ("target", args.Target)), args.Target, Filter.Entities(uid), PopupType.Medium);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void BeginHarvestDoAfter(EntityUid uid, EntityUid target, RevenantComponent revenant, EssenceComponent essence)
|
||||||
|
{
|
||||||
|
if (essence.Harvested)
|
||||||
|
{
|
||||||
|
_popup.PopupEntity(Loc.GetString("revenant-soul-harvested"), target, Filter.Entities(uid), PopupType.SmallCaution);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (TryComp<MobStateComponent>(target, out var mobstate) && mobstate.CurrentState == DamageState.Alive && !HasComp<SleepingComponent>(target))
|
||||||
|
{
|
||||||
|
_popup.PopupEntity(Loc.GetString("revenant-soul-too-powerful"), target, Filter.Entities(uid));
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
revenant.HarvestCancelToken = new();
|
||||||
|
var doAfter = new DoAfterEventArgs(uid, revenant.HarvestDebuffs.X, revenant.HarvestCancelToken.Token, target)
|
||||||
|
{
|
||||||
|
DistanceThreshold = 2,
|
||||||
|
BreakOnUserMove = true,
|
||||||
|
NeedHand = false,
|
||||||
|
UserFinishedEvent = new HarvestDoAfterComplete(target),
|
||||||
|
UserCancelledEvent = new HarvestDoAfterCancelled(),
|
||||||
|
};
|
||||||
|
|
||||||
|
_appearance.SetData(uid, RevenantVisuals.Harvesting, true);
|
||||||
|
|
||||||
|
_popup.PopupEntity(Loc.GetString("revenant-soul-begin-harvest", ("target", target)),
|
||||||
|
target, Filter.Pvs(target), PopupType.Large);
|
||||||
|
|
||||||
|
TryUseAbility(uid, revenant, 0, revenant.HarvestDebuffs);
|
||||||
|
_doAfter.DoAfter(doAfter);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void OnHarvestComplete(EntityUid uid, RevenantComponent component, HarvestDoAfterComplete args)
|
||||||
|
{
|
||||||
|
_appearance.SetData(uid, RevenantVisuals.Harvesting, false);
|
||||||
|
|
||||||
|
if (!TryComp<EssenceComponent>(args.Target, out var essence))
|
||||||
|
return;
|
||||||
|
|
||||||
|
_popup.PopupEntity(Loc.GetString("revenant-soul-finish-harvest", ("target", args.Target)),
|
||||||
|
args.Target, Filter.Pvs(args.Target), PopupType.LargeCaution);
|
||||||
|
|
||||||
|
essence.Harvested = true;
|
||||||
|
ChangeEssenceAmount(uid, essence.EssenceAmount, component);
|
||||||
|
component.StolenEssence += essence.EssenceAmount;
|
||||||
|
|
||||||
|
if (!TryComp<MobStateComponent>(args.Target, out var mobstate))
|
||||||
|
return;
|
||||||
|
|
||||||
|
if (_mobState.IsAlive(args.Target) || _mobState.IsCritical(args.Target))
|
||||||
|
{
|
||||||
|
_popup.PopupEntity(Loc.GetString("revenant-max-essence-increased"), uid, Filter.Entities(uid));
|
||||||
|
component.EssenceRegenCap += component.MaxEssenceUpgradeAmount;
|
||||||
|
}
|
||||||
|
|
||||||
|
//KILL THEMMMM
|
||||||
|
var damage = _mobState.GetEarliestDeadState(mobstate, 0)?.threshold;
|
||||||
|
if (damage == null)
|
||||||
|
return;
|
||||||
|
DamageSpecifier dspec = new();
|
||||||
|
dspec.DamageDict.Add("Cellular", damage.Value);
|
||||||
|
_damage.TryChangeDamage(args.Target, dspec, true);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void OnHarvestCancelled(EntityUid uid, RevenantComponent component, HarvestDoAfterCancelled args)
|
||||||
|
{
|
||||||
|
_appearance.SetData(uid, RevenantVisuals.Harvesting, false);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void OnDefileAction(EntityUid uid, RevenantComponent component, RevenantDefileActionEvent args)
|
||||||
|
{
|
||||||
|
if (args.Handled)
|
||||||
|
return;
|
||||||
|
|
||||||
|
if (!TryUseAbility(uid, component, component.DefileCost, component.DefileDebuffs))
|
||||||
|
return;
|
||||||
|
|
||||||
|
args.Handled = true;
|
||||||
|
|
||||||
|
//var coords = Transform(uid).Coordinates;
|
||||||
|
//var gridId = coords.GetGridUid(EntityManager);
|
||||||
|
var xform = Transform(uid);
|
||||||
|
if (!_mapManager.TryGetGrid(xform.GridUid, out var map))
|
||||||
|
return;
|
||||||
|
var tiles = map.GetTilesIntersecting(Box2.CenteredAround(xform.WorldPosition,
|
||||||
|
(component.DefileRadius*2, component.DefileRadius))).ToArray();
|
||||||
|
|
||||||
|
_random.Shuffle(tiles);
|
||||||
|
|
||||||
|
for (var i = 0; i < component.DefileTilePryAmount; i++)
|
||||||
|
{
|
||||||
|
if (!tiles.TryGetValue(i, out var value))
|
||||||
|
continue;
|
||||||
|
value.PryTile();
|
||||||
|
}
|
||||||
|
|
||||||
|
var lookup = _lookup.GetEntitiesInRange(uid, component.DefileRadius, LookupFlags.Approximate | LookupFlags.Anchored);
|
||||||
|
var tags = GetEntityQuery<TagComponent>();
|
||||||
|
var entityStorage = GetEntityQuery<EntityStorageComponent>();
|
||||||
|
var items = GetEntityQuery<ItemComponent>();
|
||||||
|
var lights = GetEntityQuery<PoweredLightComponent>();
|
||||||
|
|
||||||
|
foreach (var ent in lookup)
|
||||||
|
{
|
||||||
|
//break windows
|
||||||
|
if (tags.HasComponent(ent) && _tag.HasAnyTag(ent, "Window"))
|
||||||
|
{
|
||||||
|
//hardcoded damage specifiers til i die.
|
||||||
|
var dspec = new DamageSpecifier();
|
||||||
|
dspec.DamageDict.Add("Structural", 15);
|
||||||
|
_damage.TryChangeDamage(ent, dspec);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!_random.Prob(component.DefileEffectChance))
|
||||||
|
continue;
|
||||||
|
|
||||||
|
//randomly opens some lockers and such.
|
||||||
|
if (entityStorage.TryGetComponent(ent, out var entstorecomp))
|
||||||
|
_entityStorage.OpenStorage(ent, entstorecomp);
|
||||||
|
|
||||||
|
//chucks shit
|
||||||
|
if (items.HasComponent(ent) &&
|
||||||
|
TryComp<PhysicsComponent>(ent, out var phys) && phys.BodyType != BodyType.Static)
|
||||||
|
_throwing.TryThrow(ent, _random.NextAngle().ToWorldVec());
|
||||||
|
|
||||||
|
//flicker lights
|
||||||
|
if (lights.HasComponent(ent))
|
||||||
|
_ghost.DoGhostBooEvent(ent);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void OnOverloadLightsAction(EntityUid uid, RevenantComponent component, RevenantOverloadLightsActionEvent args)
|
||||||
|
{
|
||||||
|
if (args.Handled)
|
||||||
|
return;
|
||||||
|
|
||||||
|
if (!TryUseAbility(uid, component, component.OverloadCost, component.OverloadDebuffs))
|
||||||
|
return;
|
||||||
|
|
||||||
|
args.Handled = true;
|
||||||
|
|
||||||
|
var poweredLights = GetEntityQuery<PoweredLightComponent>();
|
||||||
|
var lookup = _lookup.GetEntitiesInRange(uid, component.OverloadRadius);
|
||||||
|
|
||||||
|
foreach (var ent in lookup)
|
||||||
|
{
|
||||||
|
if (!poweredLights.HasComponent(ent))
|
||||||
|
continue;
|
||||||
|
|
||||||
|
var ev = new GhostBooEvent(); //light go flicker
|
||||||
|
RaiseLocalEvent(ent, ev);
|
||||||
|
|
||||||
|
if (_random.Prob(component.OverloadBreakChance))
|
||||||
|
{
|
||||||
|
//values
|
||||||
|
_explosion.QueueExplosion(ent, "RevenantElectric", 15, 3, 5, canCreateVacuum: false);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void OnBlightAction(EntityUid uid, RevenantComponent component, RevenantBlightActionEvent args)
|
||||||
|
{
|
||||||
|
if (args.Handled)
|
||||||
|
return;
|
||||||
|
|
||||||
|
if (!TryUseAbility(uid, component, component.BlightCost, component.BlightDebuffs))
|
||||||
|
return;
|
||||||
|
|
||||||
|
args.Handled = true;
|
||||||
|
|
||||||
|
var emo = GetEntityQuery<DiseaseCarrierComponent>();
|
||||||
|
|
||||||
|
foreach (var ent in _lookup.GetEntitiesInRange(uid, component.BlightRadius))
|
||||||
|
if (emo.TryGetComponent(ent, out var comp))
|
||||||
|
_disease.TryInfect(comp, component.BlightDiseasePrototypeId);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void OnMalfunctionAction(EntityUid uid, RevenantComponent component, RevenantMalfunctionActionEvent args)
|
||||||
|
{
|
||||||
|
if (args.Handled)
|
||||||
|
return;
|
||||||
|
|
||||||
|
if (!TryUseAbility(uid, component, component.MalfunctionCost, component.MalfunctionDebuffs))
|
||||||
|
return;
|
||||||
|
|
||||||
|
args.Handled = true;
|
||||||
|
|
||||||
|
foreach (var ent in _lookup.GetEntitiesInRange(uid, component.MalfunctionRadius))
|
||||||
|
_emag.DoEmag(ent, ent); //it emags itself. spooky.
|
||||||
|
}
|
||||||
|
}
|
||||||
69
Content.Server/Revenant/EntitySystems/RevenantSystem.Shop.cs
Normal file
@@ -0,0 +1,69 @@
|
|||||||
|
using System.Linq;
|
||||||
|
using Content.Shared.Revenant;
|
||||||
|
using Content.Server.UserInterface;
|
||||||
|
using Robust.Server.GameObjects;
|
||||||
|
using Robust.Server.Player;
|
||||||
|
using Content.Shared.Actions.ActionTypes;
|
||||||
|
|
||||||
|
namespace Content.Server.Revenant.EntitySystems;
|
||||||
|
|
||||||
|
// TODO: Delete and replace all of this with StoreSystem once that's merged
|
||||||
|
// i'm sorry, but i'm not ultra-optimizing something that's getting deleted in a week.
|
||||||
|
// 8/7/22 -emo (bully me if this exists in the future)
|
||||||
|
public sealed partial class RevenantSystem : EntitySystem
|
||||||
|
{
|
||||||
|
private void InitializeShop()
|
||||||
|
{
|
||||||
|
SubscribeLocalEvent<RevenantComponent, RevenantShopActionEvent>(OnShop);
|
||||||
|
SubscribeLocalEvent<RevenantComponent, RevenantBuyListingMessage>(OnBuy);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void OnShop(EntityUid uid, RevenantComponent component, RevenantShopActionEvent args)
|
||||||
|
{
|
||||||
|
if (!TryComp<ActorComponent>(uid, out var actor))
|
||||||
|
return;
|
||||||
|
ToggleUi(component, actor.PlayerSession);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void OnBuy(EntityUid uid, RevenantComponent component, RevenantBuyListingMessage ev)
|
||||||
|
{
|
||||||
|
RevenantStoreListingPrototype? targetListing = null;
|
||||||
|
foreach (var listing in component.Listings)
|
||||||
|
{
|
||||||
|
if (listing.Key.ID == ev.Listing.ID)
|
||||||
|
targetListing = listing.Key;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (targetListing == null)
|
||||||
|
return;
|
||||||
|
component.Listings[targetListing] = false;
|
||||||
|
|
||||||
|
if (component.StolenEssence < ev.Listing.Price)
|
||||||
|
return;
|
||||||
|
component.StolenEssence -= ev.Listing.Price;
|
||||||
|
|
||||||
|
if (_proto.TryIndex<InstantActionPrototype>(ev.Listing.ActionId, out var action))
|
||||||
|
_action.AddAction(uid, new InstantAction(action), null);
|
||||||
|
|
||||||
|
UpdateUserInterface(component);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void ToggleUi(RevenantComponent component, IPlayerSession session)
|
||||||
|
{
|
||||||
|
var ui = component.Owner.GetUIOrNull(RevenantUiKey.Key);
|
||||||
|
ui?.Toggle(session);
|
||||||
|
|
||||||
|
UpdateUserInterface(component);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void UpdateUserInterface(RevenantComponent component)
|
||||||
|
{
|
||||||
|
var ui = component.Owner.GetUIOrNull(RevenantUiKey.Key);
|
||||||
|
if (ui == null)
|
||||||
|
return;
|
||||||
|
|
||||||
|
var filterlistings = (from e in component.Listings where e.Value select e.Key).ToList();
|
||||||
|
|
||||||
|
ui.SetState(new RevenantUpdateState(component.StolenEssence.Float(), filterlistings));
|
||||||
|
}
|
||||||
|
}
|
||||||
184
Content.Server/Revenant/EntitySystems/RevenantSystem.cs
Normal file
@@ -0,0 +1,184 @@
|
|||||||
|
using Content.Server.Actions;
|
||||||
|
using Content.Shared.Popups;
|
||||||
|
using Content.Shared.Alert;
|
||||||
|
using Content.Shared.Damage;
|
||||||
|
using Content.Shared.Interaction;
|
||||||
|
using Content.Server.DoAfter;
|
||||||
|
using Content.Shared.Stunnable;
|
||||||
|
using Content.Shared.Revenant;
|
||||||
|
using Robust.Server.GameObjects;
|
||||||
|
using Robust.Shared.Random;
|
||||||
|
using Content.Shared.StatusEffect;
|
||||||
|
using Content.Server.MobState;
|
||||||
|
using Content.Server.Visible;
|
||||||
|
using Content.Shared.Examine;
|
||||||
|
using Robust.Shared.Prototypes;
|
||||||
|
using Content.Shared.Actions.ActionTypes;
|
||||||
|
using Content.Shared.Tag;
|
||||||
|
using Content.Server.Polymorph.Systems;
|
||||||
|
using Content.Shared.FixedPoint;
|
||||||
|
using Robust.Shared.Player;
|
||||||
|
using Content.Shared.Movement.Systems;
|
||||||
|
using Content.Shared.Maps;
|
||||||
|
using Content.Shared.Physics;
|
||||||
|
|
||||||
|
namespace Content.Server.Revenant.EntitySystems;
|
||||||
|
|
||||||
|
public sealed partial class RevenantSystem : EntitySystem
|
||||||
|
{
|
||||||
|
[Dependency] private readonly IRobustRandom _random = default!;
|
||||||
|
[Dependency] private readonly IPrototypeManager _proto = default!;
|
||||||
|
[Dependency] private readonly ActionsSystem _action = default!;
|
||||||
|
[Dependency] private readonly AlertsSystem _alerts = default!;
|
||||||
|
[Dependency] private readonly DamageableSystem _damage = default!;
|
||||||
|
[Dependency] private readonly DoAfterSystem _doAfter = default!;
|
||||||
|
[Dependency] private readonly EntityLookupSystem _lookup = default!;
|
||||||
|
[Dependency] private readonly MobStateSystem _mobState = default!;
|
||||||
|
[Dependency] private readonly PolymorphableSystem _polymorphable = default!;
|
||||||
|
[Dependency] private readonly PhysicsSystem _physics = default!;
|
||||||
|
[Dependency] private readonly StatusEffectsSystem _statusEffects = default!;
|
||||||
|
[Dependency] private readonly SharedInteractionSystem _interact = default!;
|
||||||
|
[Dependency] private readonly SharedPopupSystem _popup = default!;
|
||||||
|
[Dependency] private readonly SharedStunSystem _stun = default!;
|
||||||
|
[Dependency] private readonly TagSystem _tag = default!;
|
||||||
|
[Dependency] private readonly MovementSpeedModifierSystem _movement = default!;
|
||||||
|
|
||||||
|
public override void Initialize()
|
||||||
|
{
|
||||||
|
base.Initialize();
|
||||||
|
|
||||||
|
SubscribeLocalEvent<RevenantComponent, ComponentStartup>(OnStartup);
|
||||||
|
|
||||||
|
SubscribeLocalEvent<RevenantComponent, DamageChangedEvent>(OnDamage);
|
||||||
|
SubscribeLocalEvent<RevenantComponent, ExaminedEvent>(OnExamine);
|
||||||
|
SubscribeLocalEvent<RevenantComponent, StatusEffectAddedEvent>(OnStatusAdded);
|
||||||
|
SubscribeLocalEvent<RevenantComponent, StatusEffectEndedEvent>(OnStatusEnded);
|
||||||
|
|
||||||
|
InitializeAbilities();
|
||||||
|
InitializeShop();
|
||||||
|
}
|
||||||
|
|
||||||
|
private void OnStartup(EntityUid uid, RevenantComponent component, ComponentStartup args)
|
||||||
|
{
|
||||||
|
//update the icon
|
||||||
|
ChangeEssenceAmount(uid, 0, component);
|
||||||
|
|
||||||
|
//default the visuals
|
||||||
|
_appearance.SetData(uid, RevenantVisuals.Corporeal, false);
|
||||||
|
_appearance.SetData(uid, RevenantVisuals.Harvesting, false);
|
||||||
|
_appearance.SetData(uid, RevenantVisuals.Stunned, false);
|
||||||
|
|
||||||
|
//ghost vision
|
||||||
|
if (TryComp(component.Owner, out EyeComponent? eye))
|
||||||
|
eye.VisibilityMask |= (uint) (VisibilityFlags.Ghost);
|
||||||
|
|
||||||
|
//get all the abilities
|
||||||
|
foreach (var listing in _proto.EnumeratePrototypes<RevenantStoreListingPrototype>())
|
||||||
|
component.Listings.Add(listing, true);
|
||||||
|
|
||||||
|
var shopaction = new InstantAction(_proto.Index<InstantActionPrototype>("RevenantShop"));
|
||||||
|
_action.AddAction(uid, shopaction, null);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void OnStatusAdded(EntityUid uid, RevenantComponent component, StatusEffectAddedEvent args)
|
||||||
|
{
|
||||||
|
if (args.Key == "Stun")
|
||||||
|
_appearance.SetData(uid, RevenantVisuals.Stunned, true);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void OnStatusEnded(EntityUid uid, RevenantComponent component, StatusEffectEndedEvent args)
|
||||||
|
{
|
||||||
|
if (args.Key == "Stun")
|
||||||
|
_appearance.SetData(uid, RevenantVisuals.Stunned, false);
|
||||||
|
else if (args.Key == "Corporeal")
|
||||||
|
_movement.RefreshMovementSpeedModifiers(uid);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void OnExamine(EntityUid uid, RevenantComponent component, ExaminedEvent args)
|
||||||
|
{
|
||||||
|
if (args.Examiner == args.Examined)
|
||||||
|
{
|
||||||
|
args.PushMarkup(Loc.GetString("revenant-essence-amount",
|
||||||
|
("current", component.Essence.Int()), ("max", component.EssenceRegenCap.Int())));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void OnDamage(EntityUid uid, RevenantComponent component, DamageChangedEvent args)
|
||||||
|
{
|
||||||
|
if (!HasComp<CorporealComponent>(uid) || args.DamageDelta == null)
|
||||||
|
return;
|
||||||
|
|
||||||
|
var essenceDamage = args.DamageDelta.Total.Float() * component.DamageToEssenceCoefficient * -1;
|
||||||
|
ChangeEssenceAmount(uid, essenceDamage, component);
|
||||||
|
}
|
||||||
|
|
||||||
|
public bool ChangeEssenceAmount(EntityUid uid, FixedPoint2 amount, RevenantComponent? component = null, bool allowDeath = true, bool regenCap = false)
|
||||||
|
{
|
||||||
|
if (!Resolve(uid, ref component))
|
||||||
|
return false;
|
||||||
|
|
||||||
|
if (!allowDeath && component.Essence + amount <= 0)
|
||||||
|
return false;
|
||||||
|
|
||||||
|
component.Essence += amount;
|
||||||
|
|
||||||
|
if (regenCap)
|
||||||
|
FixedPoint2.Min(component.Essence, component.EssenceRegenCap);
|
||||||
|
|
||||||
|
_alerts.ShowAlert(uid, AlertType.Essence, (short) Math.Clamp(Math.Round(component.Essence.Float() / 10f), 0, 16));
|
||||||
|
|
||||||
|
if (component.Essence <= 0)
|
||||||
|
{
|
||||||
|
component.Essence = component.EssenceRegenCap;
|
||||||
|
_polymorphable.PolymorphEntity(uid, "Ectoplasm");
|
||||||
|
}
|
||||||
|
|
||||||
|
UpdateUserInterface(component);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
private bool TryUseAbility(EntityUid uid, RevenantComponent component, FixedPoint2 abilityCost, Vector2 debuffs)
|
||||||
|
{
|
||||||
|
if (component.Essence <= abilityCost)
|
||||||
|
{
|
||||||
|
_popup.PopupEntity(Loc.GetString("revenant-not-enough-essence"), uid, Filter.Entities(uid));
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
var tileref = Transform(uid).Coordinates.GetTileRef();
|
||||||
|
if (tileref != null)
|
||||||
|
{
|
||||||
|
if(_physics.GetEntitiesIntersectingBody(uid, (int) CollisionGroup.Impassable).Count > 0)
|
||||||
|
{
|
||||||
|
_popup.PopupEntity(Loc.GetString("revenant-in-solid"), uid, Filter.Entities(uid));
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
ChangeEssenceAmount(uid, abilityCost, component, false);
|
||||||
|
|
||||||
|
_statusEffects.TryAddStatusEffect<CorporealComponent>(uid, "Corporeal", TimeSpan.FromSeconds(debuffs.Y), false);
|
||||||
|
_stun.TryStun(uid, TimeSpan.FromSeconds(debuffs.X), false);
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
public override void Update(float frameTime)
|
||||||
|
{
|
||||||
|
base.Update(frameTime);
|
||||||
|
|
||||||
|
foreach (var rev in EntityQuery<RevenantComponent>())
|
||||||
|
{
|
||||||
|
rev.Accumulator += frameTime;
|
||||||
|
|
||||||
|
if (rev.Accumulator <= 1)
|
||||||
|
continue;
|
||||||
|
rev.Accumulator -= 1;
|
||||||
|
|
||||||
|
if (rev.Essence < rev.EssenceRegenCap)
|
||||||
|
{
|
||||||
|
ChangeEssenceAmount(rev.Owner, rev.EssencePerSecond, rev, regenCap: true);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
25
Content.Server/Revenant/EssenceComponent.cs
Normal file
@@ -0,0 +1,25 @@
|
|||||||
|
namespace Content.Server.Revenant;
|
||||||
|
|
||||||
|
[RegisterComponent]
|
||||||
|
public sealed class EssenceComponent : Component
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Whether or not the entity has been harvested yet.
|
||||||
|
/// </summary>
|
||||||
|
[ViewVariables]
|
||||||
|
public bool Harvested = false;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Whether or not a revenant has searched this entity
|
||||||
|
/// for its soul yet.
|
||||||
|
/// </summary>
|
||||||
|
[ViewVariables]
|
||||||
|
public bool SearchComplete = false;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// The total amount of Essence that the entity has.
|
||||||
|
/// Changes based on mob state.
|
||||||
|
/// </summary>
|
||||||
|
[ViewVariables]
|
||||||
|
public float EssenceAmount = 0f;
|
||||||
|
}
|
||||||
220
Content.Server/Revenant/RevenantComponent.cs
Normal file
@@ -0,0 +1,220 @@
|
|||||||
|
using Content.Shared.Disease;
|
||||||
|
using Content.Shared.Revenant;
|
||||||
|
using Robust.Shared.Prototypes;
|
||||||
|
using Robust.Shared.Serialization.TypeSerializers.Implementations.Custom.Prototype;
|
||||||
|
using System.Threading;
|
||||||
|
using Content.Shared.FixedPoint;
|
||||||
|
|
||||||
|
namespace Content.Server.Revenant;
|
||||||
|
|
||||||
|
[RegisterComponent]
|
||||||
|
public sealed class RevenantComponent : SharedRevenantComponent
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// The total amount of Essence the revenant has. Functions
|
||||||
|
/// as health and is regenerated.
|
||||||
|
/// </summary>
|
||||||
|
[ViewVariables(VVAccess.ReadWrite)]
|
||||||
|
public FixedPoint2 Essence = 75;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Used for purchasing shop items.
|
||||||
|
/// </summary>
|
||||||
|
[ViewVariables(VVAccess.ReadWrite)]
|
||||||
|
public FixedPoint2 StolenEssence = 0;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// The entity's current max amount of essence. Can be increased
|
||||||
|
/// through harvesting player souls.
|
||||||
|
/// </summary>
|
||||||
|
[ViewVariables(VVAccess.ReadWrite), DataField("maxEssence")]
|
||||||
|
public FixedPoint2 EssenceRegenCap = 75;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// The coefficient of damage taken to actual health lost.
|
||||||
|
/// </summary>
|
||||||
|
[ViewVariables(VVAccess.ReadWrite), DataField("damageToEssenceCoefficient")]
|
||||||
|
public float DamageToEssenceCoefficient = 1f;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// The amount of essence passively generated per second.
|
||||||
|
/// </summary>
|
||||||
|
[ViewVariables(VVAccess.ReadWrite), DataField("essencePerSecond")]
|
||||||
|
public FixedPoint2 EssencePerSecond = 0.25f;
|
||||||
|
|
||||||
|
[ViewVariables]
|
||||||
|
public float Accumulator = 0;
|
||||||
|
|
||||||
|
// Here's the gist of the harvest ability:
|
||||||
|
// Step 1: The revenant clicks on an entity to "search" for it's soul, which creates a doafter.
|
||||||
|
// Step 2: After the doafter is completed, the soul is "found" and can be harvested.
|
||||||
|
// Step 3: Clicking the entity again begins to harvest the soul, which causes the revenant to become vulnerable
|
||||||
|
// Step 4: The second doafter for the harvest completes, killing the target and granting the revenant essence.
|
||||||
|
#region Harvest Ability
|
||||||
|
/// <summary>
|
||||||
|
/// The duration of the soul search
|
||||||
|
/// </summary>
|
||||||
|
[ViewVariables, DataField("soulSearchDuration")]
|
||||||
|
public float SoulSearchDuration = 2.5f;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// The status effects applied after the ability
|
||||||
|
/// the first float corresponds to amount of time the entity is stunned.
|
||||||
|
/// the second corresponds to the amount of time the entity is made solid.
|
||||||
|
/// </summary>
|
||||||
|
[ViewVariables, DataField("harvestDebuffs")]
|
||||||
|
public Vector2 HarvestDebuffs = (5, 5);
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// The amount that is given to the revenant each time it's max essence is upgraded.
|
||||||
|
/// </summary>
|
||||||
|
[ViewVariables(VVAccess.ReadWrite), DataField("maxEssenceUpgradeAmount")]
|
||||||
|
public float MaxEssenceUpgradeAmount = 10;
|
||||||
|
|
||||||
|
public CancellationTokenSource? SoulSearchCancelToken;
|
||||||
|
public CancellationTokenSource? HarvestCancelToken;
|
||||||
|
#endregion
|
||||||
|
|
||||||
|
//In the nearby radius, causes various objects to be thrown, messed with, and containers opened
|
||||||
|
//Generally just causes a mess
|
||||||
|
#region Defile Ability
|
||||||
|
/// <summary>
|
||||||
|
/// The amount of essence that is needed to use the ability.
|
||||||
|
/// </summary>
|
||||||
|
[ViewVariables(VVAccess.ReadWrite), DataField("defileCost")]
|
||||||
|
public FixedPoint2 DefileCost = -30;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// The status effects applied after the ability
|
||||||
|
/// the first float corresponds to amount of time the entity is stunned.
|
||||||
|
/// the second corresponds to the amount of time the entity is made solid.
|
||||||
|
/// </summary>
|
||||||
|
[ViewVariables, DataField("defileDebuffs")]
|
||||||
|
public Vector2 DefileDebuffs = (1, 4);
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// The radius around the user that this ability affects
|
||||||
|
/// </summary>
|
||||||
|
[ViewVariables(VVAccess.ReadWrite), DataField("defileRadius")]
|
||||||
|
public float DefileRadius = 3.5f;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// The amount of tiles that are uprooted by the ability
|
||||||
|
/// </summary>
|
||||||
|
[ViewVariables(VVAccess.ReadWrite), DataField("defileTilePryAmount")]
|
||||||
|
public int DefileTilePryAmount = 15;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// The chance that an individual entity will have any of the effects
|
||||||
|
/// happen to it.
|
||||||
|
/// </summary>
|
||||||
|
[ViewVariables(VVAccess.ReadWrite), DataField("defileEffectChance")]
|
||||||
|
public float DefileEffectChance = 0.5f;
|
||||||
|
#endregion
|
||||||
|
|
||||||
|
#region Overload Lights Ability
|
||||||
|
/// <summary>
|
||||||
|
/// The amount of essence that is needed to use the ability.
|
||||||
|
/// </summary>
|
||||||
|
[ViewVariables(VVAccess.ReadWrite), DataField("overloadCost")]
|
||||||
|
public FixedPoint2 OverloadCost = -40;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// The status effects applied after the ability
|
||||||
|
/// the first float corresponds to amount of time the entity is stunned.
|
||||||
|
/// the second corresponds to the amount of time the entity is made solid.
|
||||||
|
/// </summary>
|
||||||
|
[ViewVariables, DataField("overloadDebuffs")]
|
||||||
|
public Vector2 OverloadDebuffs = (3, 8);
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// The radius around the user that this ability affects
|
||||||
|
/// </summary>
|
||||||
|
[ViewVariables(VVAccess.ReadWrite), DataField("overloadRadius")]
|
||||||
|
public float OverloadRadius = 3.5f;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// The chance that each light in the radius of the ability will break and explode.
|
||||||
|
/// </summary>
|
||||||
|
[ViewVariables(VVAccess.ReadWrite), DataField("overloadBreakChance")]
|
||||||
|
public float OverloadBreakChance = 0.5f;
|
||||||
|
#endregion
|
||||||
|
|
||||||
|
#region Blight Ability
|
||||||
|
/// <summary>
|
||||||
|
/// The amount of essence that is needed to use the ability.
|
||||||
|
/// </summary>
|
||||||
|
[ViewVariables(VVAccess.ReadWrite), DataField("blightCost")]
|
||||||
|
public float BlightCost = -50;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// The status effects applied after the ability
|
||||||
|
/// the first float corresponds to amount of time the entity is stunned.
|
||||||
|
/// the second corresponds to the amount of time the entity is made solid.
|
||||||
|
/// </summary>
|
||||||
|
[ViewVariables, DataField("blightDebuffs")]
|
||||||
|
public Vector2 BlightDebuffs = (2, 5);
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// The radius around the user that this ability affects
|
||||||
|
/// </summary>
|
||||||
|
[ViewVariables(VVAccess.ReadWrite), DataField("blightRadius")]
|
||||||
|
public float BlightRadius = 3.5f;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// The disease that is given to the victims of the ability.
|
||||||
|
/// </summary>
|
||||||
|
[ViewVariables(VVAccess.ReadWrite), DataField("blightDiseasePrototypeId", customTypeSerializer: typeof(PrototypeIdSerializer<DiseasePrototype>))]
|
||||||
|
public string BlightDiseasePrototypeId = "SpectralTiredness";
|
||||||
|
#endregion
|
||||||
|
|
||||||
|
#region Malfunction Ability
|
||||||
|
/// <summary>
|
||||||
|
/// The amount of essence that is needed to use the ability.
|
||||||
|
/// </summary>
|
||||||
|
[ViewVariables(VVAccess.ReadWrite), DataField("malfunctionCost")]
|
||||||
|
public FixedPoint2 MalfunctionCost = -60;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// The status effects applied after the ability
|
||||||
|
/// the first float corresponds to amount of time the entity is stunned.
|
||||||
|
/// the second corresponds to the amount of time the entity is made solid.
|
||||||
|
/// </summary>
|
||||||
|
[ViewVariables, DataField("malfunctionDebuffs")]
|
||||||
|
public Vector2 MalfunctionDebuffs = (2, 8);
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// The radius around the user that this ability affects
|
||||||
|
/// </summary>
|
||||||
|
[ViewVariables(VVAccess.ReadWrite), DataField("malfunctionRadius")]
|
||||||
|
public float MalfunctionRadius = 3.5f;
|
||||||
|
#endregion
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Stores all of the currently unlockable abilities in the shop.
|
||||||
|
/// </summary>
|
||||||
|
[ViewVariables]
|
||||||
|
public Dictionary<RevenantStoreListingPrototype, bool> Listings = new ();
|
||||||
|
}
|
||||||
|
|
||||||
|
public sealed class SoulSearchDoAfterComplete : EntityEventArgs
|
||||||
|
{
|
||||||
|
public readonly EntityUid Target;
|
||||||
|
|
||||||
|
public SoulSearchDoAfterComplete(EntityUid target)
|
||||||
|
{
|
||||||
|
Target = target;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public sealed class HarvestDoAfterComplete : EntityEventArgs
|
||||||
|
{
|
||||||
|
public readonly EntityUid Target;
|
||||||
|
|
||||||
|
public HarvestDoAfterComplete(EntityUid target)
|
||||||
|
{
|
||||||
|
Target = target;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public sealed class HarvestDoAfterCancelled : EntityEventArgs { }
|
||||||
@@ -208,9 +208,9 @@ public sealed class EntityStorageSystem : EntitySystem
|
|||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
public bool TryOpenStorage(EntityUid user, EntityUid target)
|
public bool TryOpenStorage(EntityUid user, EntityUid target, bool silent = false)
|
||||||
{
|
{
|
||||||
if (!CanOpen(user, target))
|
if (!CanOpen(user, target, silent))
|
||||||
return false;
|
return false;
|
||||||
|
|
||||||
OpenStorage(target);
|
OpenStorage(target);
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
namespace Content.Shared.Alert
|
namespace Content.Shared.Alert
|
||||||
{
|
{
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Every kind of alert. Corresponds to alertType field in alert prototypes defined in YML
|
/// Every kind of alert. Corresponds to alertType field in alert prototypes defined in YML
|
||||||
@@ -35,6 +35,8 @@
|
|||||||
Muted,
|
Muted,
|
||||||
VowOfSilence,
|
VowOfSilence,
|
||||||
VowBroken,
|
VowBroken,
|
||||||
|
Essence,
|
||||||
|
Corporeal,
|
||||||
Debug1,
|
Debug1,
|
||||||
Debug2,
|
Debug2,
|
||||||
Debug3,
|
Debug3,
|
||||||
|
|||||||
@@ -36,4 +36,23 @@ namespace Content.Shared.Interaction
|
|||||||
Target = target;
|
Target = target;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public sealed class InteractNoHandEvent : HandledEntityEventArgs, ITargetedInteractEventArgs
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Entity that triggered the interaction.
|
||||||
|
/// </summary>
|
||||||
|
public EntityUid User { get; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Entity that was interacted on.
|
||||||
|
/// </summary>
|
||||||
|
public EntityUid Target { get; }
|
||||||
|
|
||||||
|
public InteractNoHandEvent(EntityUid user, EntityUid target)
|
||||||
|
{
|
||||||
|
User = user;
|
||||||
|
Target = target;
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -230,7 +230,14 @@ namespace Content.Shared.Interaction
|
|||||||
|
|
||||||
// Does the user have hands?
|
// Does the user have hands?
|
||||||
if (!TryComp(user, out SharedHandsComponent? hands) || hands.ActiveHand == null)
|
if (!TryComp(user, out SharedHandsComponent? hands) || hands.ActiveHand == null)
|
||||||
|
{
|
||||||
|
if (target != null)
|
||||||
|
{
|
||||||
|
var ev = new InteractNoHandEvent(user, target.Value);
|
||||||
|
RaiseLocalEvent(user, ev, true);
|
||||||
|
}
|
||||||
return;
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
var inRangeUnobstructed = target == null
|
var inRangeUnobstructed = target == null
|
||||||
? !checkAccess || InRangeUnobstructed(user, coordinates)
|
? !checkAccess || InRangeUnobstructed(user, coordinates)
|
||||||
|
|||||||
30
Content.Shared/Revenant/RevenantStoreListingPrototype.cs
Normal file
@@ -0,0 +1,30 @@
|
|||||||
|
using Content.Shared.Actions.ActionTypes;
|
||||||
|
using Robust.Shared.Prototypes;
|
||||||
|
using Robust.Shared.Serialization.TypeSerializers.Implementations.Custom.Prototype;
|
||||||
|
using Robust.Shared.Utility;
|
||||||
|
|
||||||
|
namespace Content.Shared.Revenant;
|
||||||
|
|
||||||
|
[Serializable]
|
||||||
|
[Prototype("revenantListing")]
|
||||||
|
public sealed class RevenantStoreListingPrototype : IPrototype
|
||||||
|
{
|
||||||
|
[ViewVariables]
|
||||||
|
[IdDataField]
|
||||||
|
public string ID { get; } = default!;
|
||||||
|
|
||||||
|
[DataField("actionId", customTypeSerializer:typeof(PrototypeIdSerializer<InstantActionPrototype>))]
|
||||||
|
public string ActionId { get; } = string.Empty;
|
||||||
|
|
||||||
|
[DataField("price")]
|
||||||
|
public int Price { get; } = 5;
|
||||||
|
|
||||||
|
[DataField("description")]
|
||||||
|
public string Description { get; } = string.Empty;
|
||||||
|
|
||||||
|
[DataField("listingName")]
|
||||||
|
public string ListingName { get; } = string.Empty;
|
||||||
|
|
||||||
|
[DataField("icon")]
|
||||||
|
public SpriteSpecifier? Icon { get; } = null;
|
||||||
|
}
|
||||||
49
Content.Shared/Revenant/SharedRevenant.cs
Normal file
@@ -0,0 +1,49 @@
|
|||||||
|
using Content.Shared.Actions;
|
||||||
|
using Robust.Shared.Serialization;
|
||||||
|
|
||||||
|
namespace Content.Shared.Revenant;
|
||||||
|
|
||||||
|
public sealed class RevenantShopActionEvent : InstantActionEvent { }
|
||||||
|
public sealed class RevenantDefileActionEvent : InstantActionEvent { }
|
||||||
|
public sealed class RevenantOverloadLightsActionEvent : InstantActionEvent { }
|
||||||
|
public sealed class RevenantBlightActionEvent : InstantActionEvent { }
|
||||||
|
public sealed class RevenantMalfunctionActionEvent : InstantActionEvent { }
|
||||||
|
|
||||||
|
[NetSerializable, Serializable]
|
||||||
|
public enum RevenantVisuals : byte
|
||||||
|
{
|
||||||
|
Corporeal,
|
||||||
|
Stunned,
|
||||||
|
Harvesting,
|
||||||
|
}
|
||||||
|
|
||||||
|
[NetSerializable, Serializable]
|
||||||
|
public enum RevenantUiKey : byte
|
||||||
|
{
|
||||||
|
Key
|
||||||
|
}
|
||||||
|
|
||||||
|
[Serializable, NetSerializable]
|
||||||
|
public sealed class RevenantUpdateState : BoundUserInterfaceState
|
||||||
|
{
|
||||||
|
public float Essence;
|
||||||
|
|
||||||
|
public readonly List<RevenantStoreListingPrototype> Listings;
|
||||||
|
|
||||||
|
public RevenantUpdateState(float essence, List<RevenantStoreListingPrototype> listings)
|
||||||
|
{
|
||||||
|
Essence = essence;
|
||||||
|
Listings = listings;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
[Serializable, NetSerializable]
|
||||||
|
public sealed class RevenantBuyListingMessage : BoundUserInterfaceMessage
|
||||||
|
{
|
||||||
|
public RevenantStoreListingPrototype Listing;
|
||||||
|
|
||||||
|
public RevenantBuyListingMessage (RevenantStoreListingPrototype listing)
|
||||||
|
{
|
||||||
|
Listing = listing;
|
||||||
|
}
|
||||||
|
}
|
||||||
9
Content.Shared/Revenant/SharedRevenantComponent.cs
Normal file
@@ -0,0 +1,9 @@
|
|||||||
|
using Robust.Shared.GameStates;
|
||||||
|
|
||||||
|
namespace Content.Shared.Revenant;
|
||||||
|
|
||||||
|
[NetworkedComponent]
|
||||||
|
public abstract class SharedRevenantComponent : Component
|
||||||
|
{
|
||||||
|
|
||||||
|
}
|
||||||
@@ -196,7 +196,7 @@ namespace Content.Shared.StatusEffect
|
|||||||
}
|
}
|
||||||
|
|
||||||
Dirty(status);
|
Dirty(status);
|
||||||
// event?
|
RaiseLocalEvent(uid, new StatusEffectAddedEvent(uid, key));
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -274,7 +274,7 @@ namespace Content.Shared.StatusEffect
|
|||||||
}
|
}
|
||||||
|
|
||||||
Dirty(status);
|
Dirty(status);
|
||||||
// event?
|
RaiseLocalEvent(uid, new StatusEffectEndedEvent(uid, key));
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -448,4 +448,30 @@ namespace Content.Shared.StatusEffect
|
|||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public readonly struct StatusEffectAddedEvent
|
||||||
|
{
|
||||||
|
public readonly EntityUid Uid;
|
||||||
|
|
||||||
|
public readonly string Key;
|
||||||
|
|
||||||
|
public StatusEffectAddedEvent(EntityUid uid, string key)
|
||||||
|
{
|
||||||
|
Uid = uid;
|
||||||
|
Key = key;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public readonly struct StatusEffectEndedEvent
|
||||||
|
{
|
||||||
|
public readonly EntityUid Uid;
|
||||||
|
|
||||||
|
public readonly string Key;
|
||||||
|
|
||||||
|
public StatusEffectEndedEvent(EntityUid uid, string key)
|
||||||
|
{
|
||||||
|
Uid = uid;
|
||||||
|
Key = key;
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
23
Resources/Locale/en-US/revenant/revenant.ftl
Normal file
@@ -0,0 +1,23 @@
|
|||||||
|
revenant-essence-amount = You have [color=plum]{$current} Essence[/color]. Your regen amount is [color=plum]{$max} Essence[/color].
|
||||||
|
revenant-max-essence-increased = Your max essence has increased!
|
||||||
|
|
||||||
|
revenant-not-enough-essence = Not enough essence!
|
||||||
|
revenant-in-solid = You cannot use this ability while within a solid object.
|
||||||
|
|
||||||
|
revenant-soul-too-powerful = This soul is too strong to harvest!
|
||||||
|
revenant-soul-harvested = This soul has already been harvested!
|
||||||
|
|
||||||
|
revenant-soul-searching = You search for the soul of {THE($target)}.
|
||||||
|
|
||||||
|
revenant-soul-yield-high = {CAPITALIZE(THE($target))} has an above average soul!
|
||||||
|
revenant-soul-yield-average = {CAPITALIZE(THE($target))} has an average soul.
|
||||||
|
revenant-soul-yield-low = {CAPITALIZE(THE($target))} has a below average soul.
|
||||||
|
|
||||||
|
revenant-soul-begin-harvest = {CAPITALIZE(THE($target))} suddenly rises slightly into the air, {POSS-ADJ($target)} skin turning an ashy gray.
|
||||||
|
revenant-soul-finish-harvest = {CAPITALIZE(THE($target))} slumps onto the ground!
|
||||||
|
|
||||||
|
#UI
|
||||||
|
revenant-user-interface-title = Ability Shop
|
||||||
|
revenant-user-interface-essence-amount = [color=plum]{$amount}[/color] Stolen Essence
|
||||||
|
|
||||||
|
revenant-user-interface-cost = {$price} Essence
|
||||||
38
Resources/Prototypes/Actions/revenant.yml
Normal file
@@ -0,0 +1,38 @@
|
|||||||
|
- type: instantAction
|
||||||
|
id: RevenantShop
|
||||||
|
icon: Interface/Actions/shop.png
|
||||||
|
name: Shop
|
||||||
|
description: Opens the ability shop.
|
||||||
|
serverEvent: !type:RevenantShopActionEvent
|
||||||
|
|
||||||
|
- type: instantAction
|
||||||
|
id: RevenantDefile
|
||||||
|
icon: Interface/Actions/defile.png
|
||||||
|
name: Defile
|
||||||
|
description: Costs 30 Essence.
|
||||||
|
serverEvent: !type:RevenantDefileActionEvent
|
||||||
|
useDelay: 15
|
||||||
|
|
||||||
|
- type: instantAction
|
||||||
|
id: RevenantOverloadLights
|
||||||
|
icon: Interface/Actions/overloadlight.png
|
||||||
|
name: Overload Lights
|
||||||
|
description: Costs 40 Essence.
|
||||||
|
serverEvent: !type:RevenantOverloadLightsActionEvent
|
||||||
|
useDelay: 20
|
||||||
|
|
||||||
|
- type: instantAction
|
||||||
|
id: RevenantBlight
|
||||||
|
icon: Interface/Actions/blight.png
|
||||||
|
name: Blight
|
||||||
|
description: Costs 50 Essence.
|
||||||
|
serverEvent: !type:RevenantBlightActionEvent
|
||||||
|
useDelay: 20
|
||||||
|
|
||||||
|
- type: instantAction
|
||||||
|
id: RevenantMalfunction
|
||||||
|
icon: Interface/Actions/malfunction.png
|
||||||
|
name: Malfunction
|
||||||
|
description: Costs 60 Essence.
|
||||||
|
serverEvent: !type:RevenantMalfunctionActionEvent
|
||||||
|
useDelay: 20
|
||||||
@@ -12,6 +12,7 @@
|
|||||||
- category: Buckled
|
- category: Buckled
|
||||||
- alertType: Pulling
|
- alertType: Pulling
|
||||||
- category: Piloting
|
- category: Piloting
|
||||||
|
- alertType: Corporeal
|
||||||
- alertType: Stun
|
- alertType: Stun
|
||||||
- category: Breathing # Vox gang not calling this oxygen
|
- category: Breathing # Vox gang not calling this oxygen
|
||||||
- category: Pressure
|
- category: Pressure
|
||||||
|
|||||||
48
Resources/Prototypes/Alerts/revenant.yml
Normal file
@@ -0,0 +1,48 @@
|
|||||||
|
- type: alert
|
||||||
|
id: Essence
|
||||||
|
category: Health #it's like ghostie health
|
||||||
|
icons:
|
||||||
|
- sprite: /Textures/Interface/Alerts/essence_counter.rsi
|
||||||
|
state: essence0
|
||||||
|
- sprite: /Textures/Interface/Alerts/essence_counter.rsi
|
||||||
|
state: essence1
|
||||||
|
- sprite: /Textures/Interface/Alerts/essence_counter.rsi
|
||||||
|
state: essence2
|
||||||
|
- sprite: /Textures/Interface/Alerts/essence_counter.rsi
|
||||||
|
state: essence3
|
||||||
|
- sprite: /Textures/Interface/Alerts/essence_counter.rsi
|
||||||
|
state: essence4
|
||||||
|
- sprite: /Textures/Interface/Alerts/essence_counter.rsi
|
||||||
|
state: essence5
|
||||||
|
- sprite: /Textures/Interface/Alerts/essence_counter.rsi
|
||||||
|
state: essence6
|
||||||
|
- sprite: /Textures/Interface/Alerts/essence_counter.rsi
|
||||||
|
state: essence7
|
||||||
|
- sprite: /Textures/Interface/Alerts/essence_counter.rsi
|
||||||
|
state: essence8
|
||||||
|
- sprite: /Textures/Interface/Alerts/essence_counter.rsi
|
||||||
|
state: essence9
|
||||||
|
- sprite: /Textures/Interface/Alerts/essence_counter.rsi
|
||||||
|
state: essence10
|
||||||
|
- sprite: /Textures/Interface/Alerts/essence_counter.rsi
|
||||||
|
state: essence11
|
||||||
|
- sprite: /Textures/Interface/Alerts/essence_counter.rsi
|
||||||
|
state: essence12
|
||||||
|
- sprite: /Textures/Interface/Alerts/essence_counter.rsi
|
||||||
|
state: essence13
|
||||||
|
- sprite: /Textures/Interface/Alerts/essence_counter.rsi
|
||||||
|
state: essence14
|
||||||
|
- sprite: /Textures/Interface/Alerts/essence_counter.rsi
|
||||||
|
state: essence15
|
||||||
|
- sprite: /Textures/Interface/Alerts/essence_counter.rsi
|
||||||
|
state: essence16
|
||||||
|
name: Essence
|
||||||
|
description: The power of souls. It sustains you and is used for abilities. It regenerates slowly over time.
|
||||||
|
minSeverity: 0
|
||||||
|
maxSeverity: 16
|
||||||
|
|
||||||
|
- type: alert
|
||||||
|
id: Corporeal
|
||||||
|
icons: [ /Textures/Mobs/Ghosts/revenant.rsi/icon.png ]
|
||||||
|
name: "Corporeal"
|
||||||
|
description: You have manifested physically. People around you can see and hurt you.
|
||||||
32
Resources/Prototypes/Catalog/revenant_catalog.yml
Normal file
@@ -0,0 +1,32 @@
|
|||||||
|
#TODO: generic shop system
|
||||||
|
- type: revenantListing
|
||||||
|
id: Defile
|
||||||
|
actionId: RevenantDefile
|
||||||
|
listingName: Defile
|
||||||
|
description: Costs 30 Essence per use. Defiles the surrounding area, ripping up floors, damaging windows, opening containers, and throwing items. Using it leaves you vulnerable to attacks for a short period of time.
|
||||||
|
icon: Interface/Actions/defile.png
|
||||||
|
price: 10
|
||||||
|
|
||||||
|
- type: revenantListing
|
||||||
|
id: OverloadLights
|
||||||
|
actionId: RevenantOverloadLights
|
||||||
|
listingName: Overload Lights
|
||||||
|
description: Costs 40 Essence per use. Overloads all nearby lights, causing the bulbs to shatter and sending out damaging sparks. Using it leaves you vulnerable to attacks for a long period of time.
|
||||||
|
icon: Interface/Actions/overloadlight.png
|
||||||
|
price: 25
|
||||||
|
|
||||||
|
- type: revenantListing
|
||||||
|
id: Blight
|
||||||
|
actionId: RevenantBlight
|
||||||
|
listingName: Blight
|
||||||
|
description: Costs 50 Essence per use. Infects all nearby organisms with an infectious disease that causes poison and tiredness. Using it leaves you vulnerable to attacks for a medium period of time.
|
||||||
|
icon: Interface/Actions/blight.png
|
||||||
|
price: 75
|
||||||
|
|
||||||
|
- type: revenantListing
|
||||||
|
id: Malfunction
|
||||||
|
actionId: RevenantMalfunction
|
||||||
|
listingName: Malfunction
|
||||||
|
description: Costs 60 Essence per use. Makes most nearby electronics stop working properly. Using it leaves you vulnerable to attacks for a long period of time.
|
||||||
|
icon: Interface/Actions/malfunction.png
|
||||||
|
price: 125
|
||||||
@@ -18,6 +18,33 @@
|
|||||||
reagent: Phalanximine
|
reagent: Phalanximine
|
||||||
min: 15
|
min: 15
|
||||||
|
|
||||||
|
- type: disease
|
||||||
|
id: SpectralTiredness
|
||||||
|
name: spectral tiredness
|
||||||
|
infectious: false
|
||||||
|
effects:
|
||||||
|
- !type:DiseaseGenericStatusEffect
|
||||||
|
probability: 0.03
|
||||||
|
key: ForcedSleep
|
||||||
|
component: ForcedSleeping
|
||||||
|
time: 3
|
||||||
|
type: Add
|
||||||
|
- !type:DiseaseSnough
|
||||||
|
probability: 0.025
|
||||||
|
snoughMessage: disease-yawn
|
||||||
|
snoughSound:
|
||||||
|
collection: Yawns
|
||||||
|
- !type:DiseaseHealthChange
|
||||||
|
probability: 0.02
|
||||||
|
damage:
|
||||||
|
types:
|
||||||
|
Poison: 4
|
||||||
|
cures:
|
||||||
|
- !type:DiseaseJustWaitCure
|
||||||
|
maxLength: 240
|
||||||
|
- !type:DiseaseBedrestCure
|
||||||
|
maxLength: 60
|
||||||
|
|
||||||
- type: disease
|
- type: disease
|
||||||
id: StageIIIALungCancer
|
id: StageIIIALungCancer
|
||||||
name: Stage IIIA Lung Cancer
|
name: Stage IIIA Lung Cancer
|
||||||
|
|||||||
100
Resources/Prototypes/Entities/Mobs/NPCs/revenant.yml
Normal file
@@ -0,0 +1,100 @@
|
|||||||
|
- type: entity
|
||||||
|
id: MobRevenant
|
||||||
|
name: revenant
|
||||||
|
description: A spooky ghostie.
|
||||||
|
components:
|
||||||
|
- type: Mind
|
||||||
|
- type: InputMover
|
||||||
|
- type: MobMover
|
||||||
|
- type: Input
|
||||||
|
context: "ghost"
|
||||||
|
- type: MovementSpeedModifier
|
||||||
|
baseWalkSpeed: 6
|
||||||
|
baseSprintSpeed: 6
|
||||||
|
- type: Sprite
|
||||||
|
noRot: true
|
||||||
|
drawdepth: Ghosts
|
||||||
|
netsync: false
|
||||||
|
sprite: Mobs/Ghosts/revenant.rsi
|
||||||
|
layers:
|
||||||
|
- state: active
|
||||||
|
- type: Clickable
|
||||||
|
- type: StatusEffects
|
||||||
|
allowed:
|
||||||
|
- Stun
|
||||||
|
- Corporeal
|
||||||
|
- type: InteractionOutline
|
||||||
|
- type: Physics
|
||||||
|
bodyType: KinematicController
|
||||||
|
- type: Fixtures
|
||||||
|
fixtures:
|
||||||
|
- shape:
|
||||||
|
!type:PhysShapeCircle
|
||||||
|
radius: 0.40
|
||||||
|
mass: 40
|
||||||
|
mask:
|
||||||
|
- GhostImpassable
|
||||||
|
- type: HeatResistance
|
||||||
|
- type: MovementIgnoreGravity
|
||||||
|
- type: Damageable
|
||||||
|
damageContainer: Biological
|
||||||
|
- type: Appearance
|
||||||
|
- type: Examiner
|
||||||
|
- type: NoSlip
|
||||||
|
- type: Actions
|
||||||
|
- type: Eye
|
||||||
|
drawFov: false
|
||||||
|
- type: DoAfter
|
||||||
|
- type: Alerts
|
||||||
|
- type: NameIdentifier
|
||||||
|
group: GenericNumber
|
||||||
|
- type: GhostTakeoverAvailable
|
||||||
|
makeSentient: true
|
||||||
|
name: Revenant
|
||||||
|
description: You are a Revenant. Use your powers to harvest souls and unleash chaos upon the crew. Unlock new abilities with the essence you harvest.
|
||||||
|
rules: You are an antagonist, harvest, defile, and drive the crew insane.
|
||||||
|
- type: Revenant
|
||||||
|
- type: PointLight
|
||||||
|
color: MediumPurple
|
||||||
|
radius: 1.5
|
||||||
|
softness: 0.75
|
||||||
|
- type: UserInterface
|
||||||
|
interfaces:
|
||||||
|
- key: enum.RevenantUiKey.Key
|
||||||
|
type: RevenantBoundUserInterface
|
||||||
|
- type: MobState
|
||||||
|
- type: Visibility
|
||||||
|
layer: 2 #ghost vis layer
|
||||||
|
|
||||||
|
- type: entity
|
||||||
|
parent: BaseItem
|
||||||
|
id: RevenantEctoplasm
|
||||||
|
name: ectoplasm
|
||||||
|
description: A weird bit of goo. You probably shouldn't leave it lying around like this.
|
||||||
|
noSpawn: true
|
||||||
|
components:
|
||||||
|
- type: Sprite
|
||||||
|
noRot: true
|
||||||
|
netsync: false
|
||||||
|
sprite: Mobs/Ghosts/revenant.rsi
|
||||||
|
layers:
|
||||||
|
- state: ectoplasm
|
||||||
|
- type: Eye
|
||||||
|
- type: MovementSpeedModifier
|
||||||
|
baseWalkSpeed: 0
|
||||||
|
baseSprintSpeed: 0
|
||||||
|
- type: Damageable
|
||||||
|
damageContainer: Inorganic
|
||||||
|
- type: Destructible
|
||||||
|
thresholds:
|
||||||
|
- trigger:
|
||||||
|
!type:DamageTrigger
|
||||||
|
damage: 5
|
||||||
|
behaviors:
|
||||||
|
- !type:DoActsBehavior
|
||||||
|
acts: [ "Destruction" ]
|
||||||
|
- type: DamageOnLand
|
||||||
|
ignoreResistances: true
|
||||||
|
damage:
|
||||||
|
types:
|
||||||
|
Blunt: 5
|
||||||
@@ -18,6 +18,15 @@
|
|||||||
revertOnCrit: true
|
revertOnCrit: true
|
||||||
revertOnDeath: true
|
revertOnDeath: true
|
||||||
|
|
||||||
|
- type: polymorph
|
||||||
|
id: Ectoplasm
|
||||||
|
entity: RevenantEctoplasm
|
||||||
|
forced: true
|
||||||
|
duration: 60
|
||||||
|
inventory: Drop
|
||||||
|
revertOnCrit: false
|
||||||
|
revertOnDeath: false
|
||||||
|
|
||||||
- type: polymorph
|
- type: polymorph
|
||||||
id: WizardForcedCarp
|
id: WizardForcedCarp
|
||||||
entity: MobCarpMagic
|
entity: MobCarpMagic
|
||||||
|
|||||||
@@ -54,3 +54,15 @@
|
|||||||
fireColor: Blue
|
fireColor: Blue
|
||||||
texturePath: /Textures/Effects/fire_greyscale.rsi
|
texturePath: /Textures/Effects/fire_greyscale.rsi
|
||||||
fireStates: 3
|
fireStates: 3
|
||||||
|
|
||||||
|
- type: explosion
|
||||||
|
id: RevenantElectric
|
||||||
|
damagePerIntensity:
|
||||||
|
types:
|
||||||
|
Shock: 4
|
||||||
|
tileBreakChance: [0]
|
||||||
|
tileBreakIntensity: [0]
|
||||||
|
lightColor: Plum
|
||||||
|
fireColor: Plum
|
||||||
|
texturePath: /Textures/Effects/fire_greyscale.rsi
|
||||||
|
fireStates: 3
|
||||||
|
|||||||
@@ -41,5 +41,10 @@
|
|||||||
id: Muted
|
id: Muted
|
||||||
alert: Muted
|
alert: Muted
|
||||||
|
|
||||||
|
- type: statusEffect
|
||||||
|
id: Corporeal
|
||||||
|
alert: Corporeal
|
||||||
|
|
||||||
- type: statusEffect
|
- type: statusEffect
|
||||||
id: ForcedSleep #I.e., they will not wake on damage or similar
|
id: ForcedSleep #I.e., they will not wake on damage or similar
|
||||||
|
|
||||||
|
|||||||
BIN
Resources/Textures/Interface/Actions/blight.png
Normal file
|
After Width: | Height: | Size: 1.1 KiB |
BIN
Resources/Textures/Interface/Actions/defile.png
Normal file
|
After Width: | Height: | Size: 2.6 KiB |
BIN
Resources/Textures/Interface/Actions/malfunction.png
Normal file
|
After Width: | Height: | Size: 1.0 KiB |
@@ -39,6 +39,21 @@
|
|||||||
},
|
},
|
||||||
{
|
{
|
||||||
"name": "zombie-turn"
|
"name": "zombie-turn"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "shop"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "blight"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "defile"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "overloadlight"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "malfunction"
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
|
|||||||
BIN
Resources/Textures/Interface/Actions/overloadlight.png
Normal file
|
After Width: | Height: | Size: 1.4 KiB |
BIN
Resources/Textures/Interface/Actions/shop.png
Normal file
|
After Width: | Height: | Size: 408 B |
|
After Width: | Height: | Size: 319 B |
|
After Width: | Height: | Size: 298 B |
|
After Width: | Height: | Size: 308 B |
|
After Width: | Height: | Size: 304 B |
|
After Width: | Height: | Size: 311 B |
|
After Width: | Height: | Size: 311 B |
|
After Width: | Height: | Size: 321 B |
|
After Width: | Height: | Size: 307 B |
|
After Width: | Height: | Size: 313 B |
|
After Width: | Height: | Size: 297 B |
|
After Width: | Height: | Size: 299 B |
|
After Width: | Height: | Size: 321 B |
|
After Width: | Height: | Size: 302 B |
|
After Width: | Height: | Size: 297 B |
|
After Width: | Height: | Size: 317 B |
|
After Width: | Height: | Size: 295 B |
|
After Width: | Height: | Size: 292 B |
@@ -0,0 +1,62 @@
|
|||||||
|
{
|
||||||
|
"version": 1,
|
||||||
|
"license": "CC-BY-SA-3.0",
|
||||||
|
"copyright": "Created by EmoGarbage404",
|
||||||
|
"size": {
|
||||||
|
"x": 32,
|
||||||
|
"y": 32
|
||||||
|
},
|
||||||
|
"states": [
|
||||||
|
{
|
||||||
|
"name": "essence0",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "essence1",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "essence2",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "essence3",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "essence4",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "essence5",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "essence6",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "essence7",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "essence8",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "essence9",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "essence10",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "essence11",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "essence12",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "essence13",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "essence14",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "essence15",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "essence16",
|
||||||
|
},
|
||||||
|
]
|
||||||
|
}
|
||||||
BIN
Resources/Textures/Mobs/Ghosts/revenant.rsi/active.png
Normal file
|
After Width: | Height: | Size: 2.7 KiB |
BIN
Resources/Textures/Mobs/Ghosts/revenant.rsi/ectoplasm.png
Normal file
|
After Width: | Height: | Size: 223 B |
BIN
Resources/Textures/Mobs/Ghosts/revenant.rsi/harvesting.png
Normal file
|
After Width: | Height: | Size: 14 KiB |
BIN
Resources/Textures/Mobs/Ghosts/revenant.rsi/icon.png
Normal file
|
After Width: | Height: | Size: 526 B |
BIN
Resources/Textures/Mobs/Ghosts/revenant.rsi/idle.png
Normal file
|
After Width: | Height: | Size: 1022 B |
190
Resources/Textures/Mobs/Ghosts/revenant.rsi/meta.json
Normal file
@@ -0,0 +1,190 @@
|
|||||||
|
{
|
||||||
|
"version": 1,
|
||||||
|
"license": "CC-BY-SA-3.0",
|
||||||
|
"copyright": "https://github.com/tgstation/tgstation/blob/f80e7ba62d27c77cfeac709dd71033744d0015c4/icons/mob/mob.dmi",
|
||||||
|
"size": {
|
||||||
|
"x": 32,
|
||||||
|
"y": 32
|
||||||
|
},
|
||||||
|
"states": [
|
||||||
|
{
|
||||||
|
"name": "active",
|
||||||
|
"directions": 4,
|
||||||
|
"delays": [
|
||||||
|
[
|
||||||
|
0.2,
|
||||||
|
0.2,
|
||||||
|
0.2,
|
||||||
|
0.2,
|
||||||
|
0.2,
|
||||||
|
0.2,
|
||||||
|
0.2,
|
||||||
|
0.2,
|
||||||
|
0.2,
|
||||||
|
0.2
|
||||||
|
],
|
||||||
|
[
|
||||||
|
0.2,
|
||||||
|
0.2,
|
||||||
|
0.2,
|
||||||
|
0.2,
|
||||||
|
0.2,
|
||||||
|
0.2,
|
||||||
|
0.2,
|
||||||
|
0.2,
|
||||||
|
0.2,
|
||||||
|
0.2
|
||||||
|
],
|
||||||
|
[
|
||||||
|
0.2,
|
||||||
|
0.2,
|
||||||
|
0.2,
|
||||||
|
0.2,
|
||||||
|
0.2,
|
||||||
|
0.2,
|
||||||
|
0.2,
|
||||||
|
0.2,
|
||||||
|
0.2,
|
||||||
|
0.2
|
||||||
|
],
|
||||||
|
[
|
||||||
|
0.2,
|
||||||
|
0.2,
|
||||||
|
0.2,
|
||||||
|
0.2,
|
||||||
|
0.2,
|
||||||
|
0.2,
|
||||||
|
0.2,
|
||||||
|
0.2,
|
||||||
|
0.2,
|
||||||
|
0.2
|
||||||
|
]
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "ectoplasm"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "harvesting",
|
||||||
|
"directions": 4,
|
||||||
|
"delays": [
|
||||||
|
[
|
||||||
|
0.05,
|
||||||
|
0.05,
|
||||||
|
0.05,
|
||||||
|
0.05,
|
||||||
|
0.05,
|
||||||
|
0.05,
|
||||||
|
0.05,
|
||||||
|
0.05,
|
||||||
|
0.05,
|
||||||
|
0.05,
|
||||||
|
0.05,
|
||||||
|
0.05,
|
||||||
|
0.05,
|
||||||
|
0.05,
|
||||||
|
0.05,
|
||||||
|
0.05,
|
||||||
|
0.05,
|
||||||
|
0.05,
|
||||||
|
0.05,
|
||||||
|
0.05,
|
||||||
|
0.05,
|
||||||
|
0.05
|
||||||
|
],
|
||||||
|
[
|
||||||
|
0.05
|
||||||
|
],
|
||||||
|
[
|
||||||
|
0.05,
|
||||||
|
0.05,
|
||||||
|
0.05,
|
||||||
|
0.05,
|
||||||
|
0.05,
|
||||||
|
0.05,
|
||||||
|
0.05,
|
||||||
|
0.05,
|
||||||
|
0.05,
|
||||||
|
0.05,
|
||||||
|
0.05,
|
||||||
|
0.05,
|
||||||
|
0.05,
|
||||||
|
0.05,
|
||||||
|
0.05,
|
||||||
|
0.05,
|
||||||
|
0.05,
|
||||||
|
0.05,
|
||||||
|
0.05,
|
||||||
|
0.05,
|
||||||
|
0.05,
|
||||||
|
0.05
|
||||||
|
],
|
||||||
|
[
|
||||||
|
0.05,
|
||||||
|
0.05,
|
||||||
|
0.05,
|
||||||
|
0.05,
|
||||||
|
0.05,
|
||||||
|
0.05,
|
||||||
|
0.05,
|
||||||
|
0.05,
|
||||||
|
0.05,
|
||||||
|
0.05,
|
||||||
|
0.05,
|
||||||
|
0.05,
|
||||||
|
0.05,
|
||||||
|
0.05,
|
||||||
|
0.05,
|
||||||
|
0.05,
|
||||||
|
0.05,
|
||||||
|
0.05,
|
||||||
|
0.05,
|
||||||
|
0.05,
|
||||||
|
0.05,
|
||||||
|
0.05
|
||||||
|
]
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "icon"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "idle",
|
||||||
|
"directions": 4
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "stunned",
|
||||||
|
"directions": 4,
|
||||||
|
"delays": [
|
||||||
|
[
|
||||||
|
0.1,
|
||||||
|
0.1,
|
||||||
|
0.1,
|
||||||
|
0.1,
|
||||||
|
0.1
|
||||||
|
],
|
||||||
|
[
|
||||||
|
0.1,
|
||||||
|
0.1,
|
||||||
|
0.1,
|
||||||
|
0.1,
|
||||||
|
0.1
|
||||||
|
],
|
||||||
|
[
|
||||||
|
0.1,
|
||||||
|
0.1,
|
||||||
|
0.1,
|
||||||
|
0.1,
|
||||||
|
0.1
|
||||||
|
],
|
||||||
|
[
|
||||||
|
0.1,
|
||||||
|
0.1,
|
||||||
|
0.1,
|
||||||
|
0.1,
|
||||||
|
0.1
|
||||||
|
]
|
||||||
|
]
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
BIN
Resources/Textures/Mobs/Ghosts/revenant.rsi/stunned.png
Normal file
|
After Width: | Height: | Size: 6.0 KiB |