Guardians (Holoparasites) (#5140)
Co-authored-by: metalgearsloth <31366439+metalgearsloth@users.noreply.github.com> Co-authored-by: Leon Friedrich <60421075+ElectroJr@users.noreply.github.com> Co-authored-by: Metal Gear Sloth <metalgearsloth@gmail.com>
@@ -314,6 +314,9 @@ namespace Content.Client.Entry
|
|||||||
"NukeCodePaper",
|
"NukeCodePaper",
|
||||||
"GhostRadio",
|
"GhostRadio",
|
||||||
"Armor",
|
"Armor",
|
||||||
|
"Guardian",
|
||||||
|
"GuardianCreator",
|
||||||
|
"GuardianHost",
|
||||||
"Udder",
|
"Udder",
|
||||||
"PneumaticCannon",
|
"PneumaticCannon",
|
||||||
"Spreader",
|
"Spreader",
|
||||||
|
|||||||
38
Content.Server/Actions/Actions/GuardianToggleAction.cs
Normal file
@@ -0,0 +1,38 @@
|
|||||||
|
using Content.Server.Guardian;
|
||||||
|
using Content.Shared.Actions.Behaviors;
|
||||||
|
using Content.Shared.Cooldown;
|
||||||
|
using Content.Shared.Popups;
|
||||||
|
using JetBrains.Annotations;
|
||||||
|
using Robust.Shared.GameObjects;
|
||||||
|
using Robust.Shared.IoC;
|
||||||
|
using Robust.Shared.Localization;
|
||||||
|
using Robust.Shared.Serialization.Manager.Attributes;
|
||||||
|
|
||||||
|
namespace Content.Server.Actions.Actions
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Manifests the guardian saved in the action, using the system
|
||||||
|
/// </summary>
|
||||||
|
[UsedImplicitly]
|
||||||
|
[DataDefinition]
|
||||||
|
public class ToggleGuardianAction : IInstantAction
|
||||||
|
{
|
||||||
|
[DataField("cooldown")] public float Cooldown { get; [UsedImplicitly] private set; }
|
||||||
|
|
||||||
|
public void DoInstantAction(InstantActionEventArgs args)
|
||||||
|
{
|
||||||
|
var entManager = IoCManager.Resolve<IEntityManager>();
|
||||||
|
|
||||||
|
if (entManager.TryGetComponent(args.Performer, out GuardianHostComponent? hostComponent) &&
|
||||||
|
hostComponent.HostedGuardian != null)
|
||||||
|
{
|
||||||
|
EntitySystem.Get<GuardianSystem>().ToggleGuardian(hostComponent);
|
||||||
|
args.PerformerActions?.Cooldown(args.ActionType, Cooldowns.SecondsFromNow(Cooldown));
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
args.Performer.PopupMessage(Loc.GetString("guardian-missing-invalid-action"));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
39
Content.Server/Guardian/GuardianComponent.cs
Normal file
@@ -0,0 +1,39 @@
|
|||||||
|
using Robust.Shared.GameObjects;
|
||||||
|
using Robust.Shared.Serialization.Manager.Attributes;
|
||||||
|
using Robust.Shared.ViewVariables;
|
||||||
|
|
||||||
|
namespace Content.Server.Guardian
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Given to guardians to monitor their link with the host
|
||||||
|
/// </summary>
|
||||||
|
[RegisterComponent]
|
||||||
|
[ComponentProtoName("Guardian")]
|
||||||
|
public class GuardianComponent : Component
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// The guardian host entity
|
||||||
|
/// </summary>
|
||||||
|
public EntityUid Host;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Percentage of damage reflected from the guardian to the host
|
||||||
|
/// </summary>
|
||||||
|
[ViewVariables]
|
||||||
|
[DataField("damageShare")]
|
||||||
|
public float DamageShare { get; set; } = 0.85f;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Maximum distance the guardian can travel before it's forced to recall, use YAML to set
|
||||||
|
/// </summary>
|
||||||
|
[ViewVariables]
|
||||||
|
[DataField("distanceAllowed")]
|
||||||
|
public float DistanceAllowed { get; set; } = 5f;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// If the guardian is currently manifested
|
||||||
|
/// </summary>
|
||||||
|
public bool GuardianLoose = false;
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
||||||
39
Content.Server/Guardian/GuardianCreatorComponent.cs
Normal file
@@ -0,0 +1,39 @@
|
|||||||
|
using Robust.Shared.GameObjects;
|
||||||
|
using Robust.Shared.Prototypes;
|
||||||
|
using Robust.Shared.Serialization.Manager.Attributes;
|
||||||
|
using Robust.Shared.Serialization.TypeSerializers.Implementations.Custom.Prototype;
|
||||||
|
using Robust.Shared.ViewVariables;
|
||||||
|
|
||||||
|
namespace Content.Server.Guardian
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Creates a GuardianComponent attached to the user's GuardianHost.
|
||||||
|
/// </summary>
|
||||||
|
[RegisterComponent]
|
||||||
|
[ComponentProtoName("GuardianCreator")]
|
||||||
|
public sealed class GuardianCreatorComponent : Component
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Counts as spent upon exhausting the injection
|
||||||
|
/// </summary>
|
||||||
|
/// <remarks>
|
||||||
|
/// We don't mark as deleted as examine depends on this.
|
||||||
|
/// </remarks>
|
||||||
|
public bool Used = false;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// The prototype of the guardian entity which will be created
|
||||||
|
/// </summary>
|
||||||
|
[ViewVariables]
|
||||||
|
[DataField("guardianProto", customTypeSerializer:typeof(PrototypeIdSerializer<EntityPrototype>), required: true)]
|
||||||
|
public string GuardianProto { get; set; } = default!;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// How long it takes to inject someone.
|
||||||
|
/// </summary>
|
||||||
|
[DataField("delay")]
|
||||||
|
public float InjectionDelay = 5f;
|
||||||
|
|
||||||
|
public bool Injecting = false;
|
||||||
|
}
|
||||||
|
}
|
||||||
27
Content.Server/Guardian/GuardianHostComponent.cs
Normal file
@@ -0,0 +1,27 @@
|
|||||||
|
using Robust.Shared.Containers;
|
||||||
|
using Robust.Shared.GameObjects;
|
||||||
|
using Robust.Shared.ViewVariables;
|
||||||
|
|
||||||
|
namespace Content.Server.Guardian
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Given to guardian users upon establishing a guardian link with the entity
|
||||||
|
/// </summary>
|
||||||
|
[RegisterComponent]
|
||||||
|
[ComponentProtoName("GuardianHost")]
|
||||||
|
public sealed class GuardianHostComponent : Component
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Guardian hosted within the component
|
||||||
|
/// </summary>
|
||||||
|
/// <remarks>
|
||||||
|
/// Can be null if the component is added at any time.
|
||||||
|
/// </remarks>
|
||||||
|
public EntityUid? HostedGuardian;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Container which holds the guardian
|
||||||
|
/// </summary>
|
||||||
|
[ViewVariables] public ContainerSlot GuardianContainer = default!;
|
||||||
|
}
|
||||||
|
}
|
||||||
349
Content.Server/Guardian/GuardianSystem.cs
Normal file
@@ -0,0 +1,349 @@
|
|||||||
|
using Content.Server.Actions;
|
||||||
|
using Content.Server.DoAfter;
|
||||||
|
using Content.Server.Hands.Components;
|
||||||
|
using Content.Server.Inventory.Components;
|
||||||
|
using Content.Server.Popups;
|
||||||
|
using Content.Shared.Actions;
|
||||||
|
using Content.Shared.Actions.Components;
|
||||||
|
using Content.Shared.Audio;
|
||||||
|
using Content.Shared.Damage;
|
||||||
|
using Content.Shared.Examine;
|
||||||
|
using Content.Shared.Interaction;
|
||||||
|
using Content.Shared.MobState;
|
||||||
|
using Content.Shared.MobState.EntitySystems;
|
||||||
|
using Robust.Server.GameObjects;
|
||||||
|
using Robust.Shared.Audio;
|
||||||
|
using Robust.Shared.Containers;
|
||||||
|
using Robust.Shared.GameObjects;
|
||||||
|
using Robust.Shared.IoC;
|
||||||
|
using Robust.Shared.Localization;
|
||||||
|
using Robust.Shared.Log;
|
||||||
|
using Robust.Shared.Player;
|
||||||
|
using Robust.Shared.Utility;
|
||||||
|
|
||||||
|
namespace Content.Server.Guardian
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// A guardian has a host it's attached to that it fights for. A fighting spirit.
|
||||||
|
/// </summary>
|
||||||
|
public sealed class GuardianSystem : EntitySystem
|
||||||
|
{
|
||||||
|
[Dependency] private readonly DoAfterSystem _doAfterSystem = default!;
|
||||||
|
[Dependency] private readonly PopupSystem _popupSystem = default!;
|
||||||
|
[Dependency] private readonly DamageableSystem _damageSystem = default!;
|
||||||
|
|
||||||
|
public override void Initialize()
|
||||||
|
{
|
||||||
|
base.Initialize();
|
||||||
|
SubscribeLocalEvent<GuardianCreatorComponent, UseInHandEvent>(OnCreatorUse);
|
||||||
|
SubscribeLocalEvent<GuardianCreatorComponent, AfterInteractEvent>(OnCreatorInteract);
|
||||||
|
SubscribeLocalEvent<GuardianCreatorComponent, ExaminedEvent>(OnCreatorExamine);
|
||||||
|
SubscribeLocalEvent<GuardianCreatorInjectedEvent>(OnCreatorInject);
|
||||||
|
SubscribeLocalEvent<GuardianCreatorInjectCancelledEvent>(OnCreatorCancelled);
|
||||||
|
|
||||||
|
SubscribeLocalEvent<GuardianComponent, MoveEvent>(OnGuardianMove);
|
||||||
|
SubscribeLocalEvent<GuardianComponent, DamageChangedEvent>(OnGuardianDamaged);
|
||||||
|
SubscribeLocalEvent<GuardianComponent, PlayerAttachedEvent>(OnGuardianPlayer);
|
||||||
|
SubscribeLocalEvent<GuardianComponent, PlayerDetachedEvent>(OnGuardianUnplayer);
|
||||||
|
|
||||||
|
SubscribeLocalEvent<GuardianHostComponent, ComponentInit>(OnHostInit);
|
||||||
|
SubscribeLocalEvent<GuardianHostComponent, MoveEvent>(OnHostMove);
|
||||||
|
SubscribeLocalEvent<GuardianHostComponent, MobStateChangedEvent>(OnHostStateChange);
|
||||||
|
SubscribeLocalEvent<GuardianHostComponent, ComponentShutdown>(OnHostShutdown);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void OnGuardianUnplayer(EntityUid uid, GuardianComponent component, PlayerDetachedEvent args)
|
||||||
|
{
|
||||||
|
var host = component.Host;
|
||||||
|
|
||||||
|
if (!TryComp<GuardianHostComponent>(host, out var hostComponent)) return;
|
||||||
|
|
||||||
|
RetractGuardian(hostComponent, component);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void OnGuardianPlayer(EntityUid uid, GuardianComponent component, PlayerAttachedEvent args)
|
||||||
|
{
|
||||||
|
var host = component.Host;
|
||||||
|
|
||||||
|
if (!HasComp<GuardianHostComponent>(host)) return;
|
||||||
|
|
||||||
|
_popupSystem.PopupEntity(Loc.GetString("guardian-available"), host, Filter.Entities(host));
|
||||||
|
}
|
||||||
|
|
||||||
|
private void OnHostInit(EntityUid uid, GuardianHostComponent component, ComponentInit args)
|
||||||
|
{
|
||||||
|
component.GuardianContainer = uid.EnsureContainer<ContainerSlot>("GuardianContainer");
|
||||||
|
}
|
||||||
|
|
||||||
|
private void OnHostShutdown(EntityUid uid, GuardianHostComponent component, ComponentShutdown args)
|
||||||
|
{
|
||||||
|
if (component.HostedGuardian == null) return;
|
||||||
|
EntityManager.QueueDeleteEntity(component.HostedGuardian.Value);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void ToggleGuardian(GuardianHostComponent hostComponent)
|
||||||
|
{
|
||||||
|
if (hostComponent.HostedGuardian == null ||
|
||||||
|
!TryComp(hostComponent.HostedGuardian, out GuardianComponent? guardianComponent)) return;
|
||||||
|
|
||||||
|
if (guardianComponent.GuardianLoose)
|
||||||
|
{
|
||||||
|
RetractGuardian(hostComponent, guardianComponent);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
ReleaseGuardian(hostComponent, guardianComponent);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Adds the guardian host component to the user and spawns the guardian inside said component
|
||||||
|
/// </summary>
|
||||||
|
private void OnCreatorUse(EntityUid uid, GuardianCreatorComponent component, UseInHandEvent args)
|
||||||
|
{
|
||||||
|
if (args.Handled) return;
|
||||||
|
args.Handled = true;
|
||||||
|
UseCreator(args.User, args.User, component);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void OnCreatorInteract(EntityUid uid, GuardianCreatorComponent component, AfterInteractEvent args)
|
||||||
|
{
|
||||||
|
if (args.Handled || args.Target == null) return;
|
||||||
|
args.Handled = true;
|
||||||
|
UseCreator(args.User, args.Target.Value, component);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void OnCreatorCancelled(GuardianCreatorInjectCancelledEvent ev)
|
||||||
|
{
|
||||||
|
ev.Component.Injecting = false;
|
||||||
|
}
|
||||||
|
|
||||||
|
private void UseCreator(EntityUid user, EntityUid target, GuardianCreatorComponent component)
|
||||||
|
{
|
||||||
|
if (component.Used)
|
||||||
|
{
|
||||||
|
_popupSystem.PopupEntity(Loc.GetString("guardian-activator-empty-invalid-creation"), user, Filter.Entities(user));
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// If user is already a host don't duplicate.
|
||||||
|
if (HasComp<GuardianHostComponent>(target))
|
||||||
|
{
|
||||||
|
_popupSystem.PopupEntity(Loc.GetString("guardian-already-present-invalid-creation"), user, Filter.Entities(user));
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Can't work without actions
|
||||||
|
EntityManager.EnsureComponent<ServerActionsComponent>(target);
|
||||||
|
|
||||||
|
if (component.Injecting) return;
|
||||||
|
|
||||||
|
component.Injecting = true;
|
||||||
|
|
||||||
|
_doAfterSystem.DoAfter(new DoAfterEventArgs(user, component.InjectionDelay, target: target)
|
||||||
|
{
|
||||||
|
BroadcastFinishedEvent = new GuardianCreatorInjectedEvent(user, target, component),
|
||||||
|
BroadcastCancelledEvent = new GuardianCreatorInjectCancelledEvent(target, component),
|
||||||
|
BreakOnTargetMove = true,
|
||||||
|
BreakOnUserMove = true,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
private void OnCreatorInject(GuardianCreatorInjectedEvent ev)
|
||||||
|
{
|
||||||
|
var comp = ev.Component;
|
||||||
|
|
||||||
|
if (comp.Deleted ||
|
||||||
|
comp.Used ||
|
||||||
|
!TryComp<HandsComponent>(ev.User, out var hands) ||
|
||||||
|
!hands.IsHolding(comp.Owner) ||
|
||||||
|
HasComp<GuardianHostComponent>(ev.Target) ||
|
||||||
|
!TryComp<SharedActionsComponent>(ev.Target, out var actions))
|
||||||
|
{
|
||||||
|
comp.Injecting = false;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
var hostXform = EntityManager.GetComponent<TransformComponent>(ev.Target);
|
||||||
|
var host = EntityManager.EnsureComponent<GuardianHostComponent>(ev.Target);
|
||||||
|
// Use map position so it's not inadvertantly parented to the host + if it's in a container it spawns outside I guess.
|
||||||
|
var guardian = EntityManager.SpawnEntity(comp.GuardianProto, hostXform.MapPosition);
|
||||||
|
|
||||||
|
host.GuardianContainer.Insert(guardian);
|
||||||
|
host.HostedGuardian = guardian;
|
||||||
|
|
||||||
|
if (TryComp(guardian, out GuardianComponent? guardianComponent))
|
||||||
|
{
|
||||||
|
guardianComponent.Host = ev.Target;
|
||||||
|
|
||||||
|
// Grant the user the recall action and notify them
|
||||||
|
actions.Grant(ActionType.ManifestGuardian);
|
||||||
|
SoundSystem.Play(Filter.Entities(ev.Target), "/Audio/Effects/guardian_inject.ogg", ev.Target);
|
||||||
|
|
||||||
|
_popupSystem.PopupEntity(Loc.GetString("guardian-created"), ev.Target, Filter.Entities(ev.Target));
|
||||||
|
// Exhaust the activator
|
||||||
|
comp.Used = true;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
Logger.ErrorS("guardian", $"Tried to spawn a guardian that doesn't have {nameof(GuardianComponent)}");
|
||||||
|
EntityManager.QueueDeleteEntity(guardian);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Triggers when the host receives damage which puts the host in either critical or killed state
|
||||||
|
/// </summary>
|
||||||
|
private void OnHostStateChange(EntityUid uid, GuardianHostComponent component, MobStateChangedEvent args)
|
||||||
|
{
|
||||||
|
if (component.HostedGuardian == null) return;
|
||||||
|
|
||||||
|
if (args.CurrentMobState.IsCritical())
|
||||||
|
{
|
||||||
|
_popupSystem.PopupEntity(Loc.GetString("guardian-critical-warn"), component.HostedGuardian.Value, Filter.Entities(component.HostedGuardian.Value));
|
||||||
|
SoundSystem.Play(Filter.Entities(component.HostedGuardian.Value), "/Audio/Effects/guardian_warn.ogg", component.HostedGuardian.Value);
|
||||||
|
}
|
||||||
|
else if (args.CurrentMobState.IsDead())
|
||||||
|
{
|
||||||
|
SoundSystem.Play(Filter.Pvs(uid), "/Audio/Voice/Human/malescream_guardian.ogg", uid, AudioHelpers.WithVariation(0.20f));
|
||||||
|
EntityManager.RemoveComponent<GuardianHostComponent>(uid);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Handles guardian receiving damage and splitting it with the host according to his defence percent
|
||||||
|
/// </summary>
|
||||||
|
private void OnGuardianDamaged(EntityUid uid, GuardianComponent component, DamageChangedEvent args)
|
||||||
|
{
|
||||||
|
if (args.DamageDelta == null) return;
|
||||||
|
|
||||||
|
_damageSystem.TryChangeDamage(component.Host, args.DamageDelta * component.DamageShare);
|
||||||
|
_popupSystem.PopupEntity(Loc.GetString("guardian-entity-taking-damage"), component.Host, Filter.Entities(component.Host));
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Triggers while trying to examine an activator to see if it's used
|
||||||
|
/// </summary>
|
||||||
|
private void OnCreatorExamine(EntityUid uid, GuardianCreatorComponent component, ExaminedEvent args)
|
||||||
|
{
|
||||||
|
if (component.Used)
|
||||||
|
{
|
||||||
|
args.PushMarkup(Loc.GetString("guardian-activator-empty-examine"));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Called every time the host moves, to make sure the distance between the host and the guardian isn't too far
|
||||||
|
/// </summary>
|
||||||
|
private void OnHostMove(EntityUid uid, GuardianHostComponent component, ref MoveEvent args)
|
||||||
|
{
|
||||||
|
if (component.HostedGuardian == null ||
|
||||||
|
!TryComp(component.HostedGuardian, out GuardianComponent? guardianComponent) ||
|
||||||
|
!guardianComponent.GuardianLoose) return;
|
||||||
|
|
||||||
|
CheckGuardianMove(uid, component.HostedGuardian.Value, component);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Called every time the guardian moves: makes sure it's not out of it's allowed distance
|
||||||
|
/// </summary>
|
||||||
|
private void OnGuardianMove(EntityUid uid, GuardianComponent component, ref MoveEvent args)
|
||||||
|
{
|
||||||
|
if (!component.GuardianLoose) return;
|
||||||
|
|
||||||
|
CheckGuardianMove(component.Host, uid, guardianComponent: component);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Retract the guardian if either the host or the guardian move away from each other.
|
||||||
|
/// </summary>
|
||||||
|
private void CheckGuardianMove(
|
||||||
|
EntityUid hostUid,
|
||||||
|
EntityUid guardianUid,
|
||||||
|
GuardianHostComponent? hostComponent = null,
|
||||||
|
GuardianComponent? guardianComponent = null,
|
||||||
|
TransformComponent? hostXform = null,
|
||||||
|
TransformComponent? guardianXform = null)
|
||||||
|
{
|
||||||
|
if (!Resolve(hostUid, ref hostComponent, ref hostXform) ||
|
||||||
|
!Resolve(guardianUid, ref guardianComponent, ref guardianXform))
|
||||||
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!guardianComponent.GuardianLoose) return;
|
||||||
|
|
||||||
|
if (!guardianXform.Coordinates.InRange(EntityManager, hostXform.Coordinates, guardianComponent.DistanceAllowed))
|
||||||
|
{
|
||||||
|
RetractGuardian(hostComponent, guardianComponent);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private bool CanRelease(GuardianHostComponent host, GuardianComponent guardian)
|
||||||
|
{
|
||||||
|
return HasComp<ActorComponent>(guardian.Owner);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void ReleaseGuardian(GuardianHostComponent hostComponent, GuardianComponent guardianComponent)
|
||||||
|
{
|
||||||
|
if (guardianComponent.GuardianLoose)
|
||||||
|
{
|
||||||
|
DebugTools.Assert(!hostComponent.GuardianContainer.Contains(guardianComponent.Owner));
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!CanRelease(hostComponent, guardianComponent))
|
||||||
|
{
|
||||||
|
_popupSystem.PopupEntity(Loc.GetString("guardian-no-soul"), hostComponent.Owner, Filter.Entities(hostComponent.Owner));
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
DebugTools.Assert(hostComponent.GuardianContainer.Contains(guardianComponent.Owner));
|
||||||
|
hostComponent.GuardianContainer.Remove(guardianComponent.Owner);
|
||||||
|
DebugTools.Assert(!hostComponent.GuardianContainer.Contains(guardianComponent.Owner));
|
||||||
|
|
||||||
|
guardianComponent.GuardianLoose = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
private void RetractGuardian(GuardianHostComponent hostComponent, GuardianComponent guardianComponent)
|
||||||
|
{
|
||||||
|
if (!guardianComponent.GuardianLoose)
|
||||||
|
{
|
||||||
|
DebugTools.Assert(hostComponent.GuardianContainer.Contains(guardianComponent.Owner));
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
hostComponent.GuardianContainer.Insert(guardianComponent.Owner);
|
||||||
|
DebugTools.Assert(hostComponent.GuardianContainer.Contains(guardianComponent.Owner));
|
||||||
|
_popupSystem.PopupEntity(Loc.GetString("guardian-entity-recall"), hostComponent.Owner, Filter.Pvs(hostComponent.Owner));
|
||||||
|
guardianComponent.GuardianLoose = false;
|
||||||
|
}
|
||||||
|
|
||||||
|
private sealed class GuardianCreatorInjectedEvent : EntityEventArgs
|
||||||
|
{
|
||||||
|
public EntityUid User { get; }
|
||||||
|
public EntityUid Target { get; }
|
||||||
|
public GuardianCreatorComponent Component { get; }
|
||||||
|
|
||||||
|
public GuardianCreatorInjectedEvent(EntityUid user, EntityUid target, GuardianCreatorComponent component)
|
||||||
|
{
|
||||||
|
User = user;
|
||||||
|
Target = target;
|
||||||
|
Component = component;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private sealed class GuardianCreatorInjectCancelledEvent : EntityEventArgs
|
||||||
|
{
|
||||||
|
public EntityUid Target { get; }
|
||||||
|
public GuardianCreatorComponent Component { get; }
|
||||||
|
|
||||||
|
public GuardianCreatorInjectCancelledEvent(EntityUid target, GuardianCreatorComponent component)
|
||||||
|
{
|
||||||
|
Target = target;
|
||||||
|
Component = component;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -18,6 +18,7 @@ namespace Content.Shared.Actions
|
|||||||
DebugTargetEntity,
|
DebugTargetEntity,
|
||||||
DebugTargetEntityRepeat,
|
DebugTargetEntityRepeat,
|
||||||
SpellPie,
|
SpellPie,
|
||||||
|
ManifestGuardian,
|
||||||
PAIMidi
|
PAIMidi
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -5,6 +5,7 @@ using System.Linq;
|
|||||||
using Content.Shared.Alert;
|
using Content.Shared.Alert;
|
||||||
using Content.Shared.Damage;
|
using Content.Shared.Damage;
|
||||||
using Content.Shared.FixedPoint;
|
using Content.Shared.FixedPoint;
|
||||||
|
using Content.Shared.MobState.EntitySystems;
|
||||||
using Content.Shared.MobState.State;
|
using Content.Shared.MobState.State;
|
||||||
using Robust.Shared.GameObjects;
|
using Robust.Shared.GameObjects;
|
||||||
using Robust.Shared.GameStates;
|
using Robust.Shared.GameStates;
|
||||||
@@ -318,7 +319,6 @@ namespace Content.Shared.MobState.Components
|
|||||||
|
|
||||||
var message = new MobStateChangedEvent(this, old, state);
|
var message = new MobStateChangedEvent(this, old, state);
|
||||||
entMan.EventBus.RaiseLocalEvent(Owner, message);
|
entMan.EventBus.RaiseLocalEvent(Owner, message);
|
||||||
|
|
||||||
Dirty();
|
Dirty();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
BIN
Resources/Audio/Effects/guardian_inject.ogg
Normal file
BIN
Resources/Audio/Effects/guardian_warn.ogg
Normal file
BIN
Resources/Audio/Voice/Human/malescream_guardian.ogg
Normal file
22
Resources/Locale/en-US/guardian/guardian.ftl
Normal file
@@ -0,0 +1,22 @@
|
|||||||
|
|
||||||
|
## Guardian host specific
|
||||||
|
|
||||||
|
guardian-created = You feel... Haunted.
|
||||||
|
guardian-missing-invalid-action = There is no guardian under your control!
|
||||||
|
guardian-already-present-invalid-creation = You are NOT re-living that haunting experience!
|
||||||
|
guardian-no-actions-invalid-creation = You don't have the ability to host a guardian!
|
||||||
|
guardian-activator-empty-invalid-creation = The injector is spent.
|
||||||
|
guardian-activator-empty-examine = [color=#ba1919]The injector is spent.[/color]
|
||||||
|
guardian-no-soul = Your guardian has no soul.
|
||||||
|
guardian-available = Your guardian now has a soul.
|
||||||
|
|
||||||
|
## Guardian entity specific
|
||||||
|
|
||||||
|
guardian-entity-recall = The guardian vanishes into thin air!
|
||||||
|
guardian-entity-taking-damage = Your guardian is taking damage!
|
||||||
|
|
||||||
|
## Health warnings
|
||||||
|
guardian-host-critical-warn = YOUR HOST IS WOUNDED!
|
||||||
|
guardian-host-death-warn = YOUR FORM SUCCUMBS TO NONEXISTENCE!
|
||||||
|
guardian-death-warn = YOUR BODY IS PIERCED BY SUBATOMIC PAIN AS IT DISINTEGRATES!
|
||||||
|
|
||||||
12
Resources/Prototypes/Actions/guardian_actions.yml
Normal file
@@ -0,0 +1,12 @@
|
|||||||
|
#This is the action the host gets to control the guardian
|
||||||
|
- type: action
|
||||||
|
actionType: ManifestGuardian
|
||||||
|
icon: Interface/Actions/manifest.png
|
||||||
|
name: "Toggle guardian manifestation"
|
||||||
|
description: "Either manifests the guardian or recalls it back into your body"
|
||||||
|
filters:
|
||||||
|
- guardian
|
||||||
|
behaviorType: Instant
|
||||||
|
behavior: !type:ToggleGuardianAction
|
||||||
|
cooldown: 2
|
||||||
|
|
||||||
@@ -16,3 +16,32 @@
|
|||||||
Higher will burn the engine out and eventually make it explode. Don't.
|
Higher will burn the engine out and eventually make it explode. Don't.
|
||||||
Don't forget to refuel it, it tends to stop at the worst possible time.
|
Don't forget to refuel it, it tends to stop at the worst possible time.
|
||||||
|
|
||||||
|
- type: entity
|
||||||
|
parent: BaseItem
|
||||||
|
id: HoloparasiteInfo
|
||||||
|
name: "Holoparasite terms and conditions"
|
||||||
|
description: A tiny volumetric display for documents, makes one wonder if Cybersun's legal budget is way too high.
|
||||||
|
components:
|
||||||
|
- type: Sprite
|
||||||
|
netsync: false
|
||||||
|
sprite: Objects/Misc/guardian_info.rsi
|
||||||
|
state: guardian_info
|
||||||
|
- type: UserInterface
|
||||||
|
interfaces:
|
||||||
|
- key: enum.PaperUiKey.Key
|
||||||
|
type: PaperBoundUserInterface
|
||||||
|
- type: Paper
|
||||||
|
content: |
|
||||||
|
Thanks for choosing our holoparasite package!
|
||||||
|
At cybersun, we pride ourselves on cutting-edge military and industrial technology, and greatly appreciate your contribution to our establishment!
|
||||||
|
Guardians are helpful and intelligent beings which nest within your body, completely immune to common hazards such as pressure, temperature and even bullets!
|
||||||
|
You have purchased the holoparasite package, which contains a holoparasite activator, an instruction booklet, and our softcap merchandise.
|
||||||
|
Instructions for use:
|
||||||
|
1. Activate the holoparasite injector (preferably in a secluded area).
|
||||||
|
2. Wait for the tingling and/or painful metaphysical sensation.
|
||||||
|
3. Check your holoparasite for the ability to communicate and cooperate, and capacity to understand your orders.
|
||||||
|
4. Use your recall-manifest ability to summon or recall the holoparasite back into your body.
|
||||||
|
5. Keep the holoparasite within a short distance from yourself, otherwise it will be recalled by force!
|
||||||
|
WARNING: Guardians are metaphysical beings, but draw from your HEALTH in order to exist. Direct damage done to guardians will be partially transferred to you!
|
||||||
|
Cybersun inc. is not responsible for complete annihilation following the misuse of Holoparasite technology.
|
||||||
|
|
||||||
|
|||||||
@@ -107,6 +107,18 @@
|
|||||||
price: 2
|
price: 2
|
||||||
icon: /Textures/Objects/Weapons/Guns/Ammunition/SpeedLoaders/Magnum/magnum_sl.rsi/icon.png
|
icon: /Textures/Objects/Weapons/Guns/Ammunition/SpeedLoaders/Magnum/magnum_sl.rsi/icon.png
|
||||||
|
|
||||||
|
#Utility
|
||||||
|
|
||||||
|
- type: uplinkListing
|
||||||
|
id: UplinkHoloparaKit
|
||||||
|
category: Utility
|
||||||
|
itemId: BoxHoloparasite
|
||||||
|
listingName: Holoparasite Kit
|
||||||
|
description: The pride and joy of Cybersun. Contains an injector that hosts a sentient metaphysical guardian made of hard light which resides in the user's body when not active. The guardian can punch rapidly and is immune to hazardous environments and bullets, but shares any damage it takes with the user.
|
||||||
|
icon: /Textures/Objects/Misc/guardian_info.rsi/icon.png
|
||||||
|
price: 16
|
||||||
|
|
||||||
|
|
||||||
# Bundles
|
# Bundles
|
||||||
|
|
||||||
- type: uplinkListing
|
- type: uplinkListing
|
||||||
@@ -209,6 +221,8 @@
|
|||||||
id: UplinkDecoyDisk
|
id: UplinkDecoyDisk
|
||||||
category: Misc
|
category: Misc
|
||||||
itemId: NukeDiskFake
|
itemId: NukeDiskFake
|
||||||
|
listingName: Decoy nuclear disk
|
||||||
|
description: A piece of plastic with a lenticular printing, made to look like a nuclear auth disk.
|
||||||
price: 1
|
price: 1
|
||||||
|
|
||||||
- type: uplinkListing
|
- type: uplinkListing
|
||||||
@@ -235,16 +249,14 @@
|
|||||||
itemId: lanternextrabright
|
itemId: lanternextrabright
|
||||||
price: 2
|
price: 2
|
||||||
|
|
||||||
# Pointless
|
|
||||||
|
|
||||||
- type: uplinkListing
|
- type: uplinkListing
|
||||||
id: UplinkCostumeCentcom
|
id: UplinkCostumeCentcom
|
||||||
category: Pointless
|
category: Misc
|
||||||
itemId: ClothingBackpackDuffelSyndicateCostumeCentcom
|
itemId: ClothingBackpackDuffelSyndicateCostumeCentcom
|
||||||
price: 4
|
price: 4
|
||||||
|
|
||||||
- type: uplinkListing
|
- type: uplinkListing
|
||||||
id: UplinkCostumeClown
|
id: UplinkCostumeClown
|
||||||
category: Pointless
|
category: Misc
|
||||||
itemId: ClothingBackpackDuffelSyndicateCostumeClown
|
itemId: ClothingBackpackDuffelSyndicateCostumeClown
|
||||||
price: 4
|
price: 4
|
||||||
|
|||||||
@@ -239,3 +239,25 @@
|
|||||||
sprite: Clothing/Head/Soft/yellowsoft_flipped.rsi
|
sprite: Clothing/Head/Soft/yellowsoft_flipped.rsi
|
||||||
- type: Clothing
|
- type: Clothing
|
||||||
sprite: Clothing/Head/Soft/yellowsoft_flipped.rsi
|
sprite: Clothing/Head/Soft/yellowsoft_flipped.rsi
|
||||||
|
|
||||||
|
- type: entity
|
||||||
|
parent: ClothingHeadBase
|
||||||
|
id: ClothingHeadHatBizarreSoft
|
||||||
|
name: troublemaker's soft
|
||||||
|
description: A truly.. bizarre accessory.
|
||||||
|
components:
|
||||||
|
- type: Sprite
|
||||||
|
sprite: Clothing/Head/Soft/bizarresoft.rsi
|
||||||
|
- type: Clothing
|
||||||
|
sprite: Clothing/Head/Soft/bizarresoft.rsi
|
||||||
|
|
||||||
|
- type: entity
|
||||||
|
parent: ClothingHeadBase
|
||||||
|
id: ClothingHeadHatBizarreSoftFlipped
|
||||||
|
name: troublemaker's soft flipped
|
||||||
|
description: A truly.. bizarre accessory, flipped.
|
||||||
|
components:
|
||||||
|
- type: Sprite
|
||||||
|
sprite: Clothing/Head/Soft/bizarresoft_flipped.rsi
|
||||||
|
- type: Clothing
|
||||||
|
sprite: Clothing/Head/Soft/bizarresoft_flipped.rsi
|
||||||
|
|||||||
73
Resources/Prototypes/Entities/Mobs/Player/guardian.yml
Normal file
@@ -0,0 +1,73 @@
|
|||||||
|
# Does not inherit from simplemob
|
||||||
|
- type: entity
|
||||||
|
name: Holoparasite
|
||||||
|
id: MobHoloparasite
|
||||||
|
description: A mesmerising whirl of hard-light patterns weaves a marvelous, yet oddly familiar visage. It stands proud, tuning into its owner's life to sustain itself.
|
||||||
|
components:
|
||||||
|
- type: GhostTakeoverAvailable
|
||||||
|
makeSentient: true
|
||||||
|
name: Holoparasite
|
||||||
|
description: Listen to your owner. Don't tank damage. Punch people hard.
|
||||||
|
- type: Input
|
||||||
|
context: "human"
|
||||||
|
- type: PlayerMobMover
|
||||||
|
- type: PlayerInputMover
|
||||||
|
- type: MovementSpeedModifier
|
||||||
|
baseWalkSpeed : 7
|
||||||
|
baseSprintSpeed : 7
|
||||||
|
- type: DamageOnHighSpeedImpact
|
||||||
|
damage:
|
||||||
|
types:
|
||||||
|
Blunt: 5
|
||||||
|
soundHit:
|
||||||
|
path: /Audio/Effects/hit_kick.ogg
|
||||||
|
# TODO: Randomise sprites and randomise the layer color
|
||||||
|
- type: Sprite
|
||||||
|
drawdepth: Mobs
|
||||||
|
sprite: Mobs/Aliens/Guardians/guardians.rsi
|
||||||
|
layers:
|
||||||
|
- state: tech_base
|
||||||
|
- state: tech_flare
|
||||||
|
color: "#40a7d7"
|
||||||
|
shader: unshaded
|
||||||
|
noRot: true
|
||||||
|
- type: Clickable
|
||||||
|
- type: InteractionOutline
|
||||||
|
- type: Physics
|
||||||
|
bodyType: KinematicController
|
||||||
|
- type: Fixtures
|
||||||
|
fixtures:
|
||||||
|
- shape:
|
||||||
|
!type:PhysShapeCircle
|
||||||
|
radius: 0.35
|
||||||
|
mass: 10
|
||||||
|
mask:
|
||||||
|
- Impassable
|
||||||
|
- SmallImpassable
|
||||||
|
- MobImpassable
|
||||||
|
layer:
|
||||||
|
- Opaque
|
||||||
|
- type: Damageable
|
||||||
|
damageContainer: Biological
|
||||||
|
- type: MobState
|
||||||
|
thresholds:
|
||||||
|
0: !type:NormalMobState {}
|
||||||
|
- type: HeatResistance
|
||||||
|
- type: CombatMode
|
||||||
|
- type: Internals
|
||||||
|
- type: Examiner
|
||||||
|
- type: Speech
|
||||||
|
- type: Pullable
|
||||||
|
- type: UnarmedCombat
|
||||||
|
range: 2
|
||||||
|
arcwidth: 30
|
||||||
|
arc: fist
|
||||||
|
cooldownTime: 0.7
|
||||||
|
arcCooldownTime: 0.7
|
||||||
|
damage:
|
||||||
|
types:
|
||||||
|
Blunt: 40
|
||||||
|
- type: Actions
|
||||||
|
innateActions:
|
||||||
|
- CombatMode
|
||||||
|
- type: Guardian
|
||||||
@@ -0,0 +1,26 @@
|
|||||||
|
- type: entity
|
||||||
|
name: holoparasite injector
|
||||||
|
id: HoloparasiteInjector
|
||||||
|
parent: BaseItem
|
||||||
|
description: A complex artwork of handheld machinery allowing the user to host a holoparasite guardian.
|
||||||
|
components:
|
||||||
|
- type: Sprite
|
||||||
|
sprite: Objects/Specific/Medical/hypospray.rsi
|
||||||
|
state: combat_hypo
|
||||||
|
netsync: false
|
||||||
|
- type: GuardianCreator
|
||||||
|
guardianProto: MobHoloparasite
|
||||||
|
|
||||||
|
- type: entity
|
||||||
|
name: holoparasite box
|
||||||
|
parent: BoxBase
|
||||||
|
id: BoxHoloparasite
|
||||||
|
description: A box containing a holoparasite injector
|
||||||
|
components:
|
||||||
|
- type: StorageFill
|
||||||
|
contents:
|
||||||
|
- id: HoloparasiteInjector
|
||||||
|
- id: HoloparasiteInfo
|
||||||
|
- type: Sprite
|
||||||
|
layers:
|
||||||
|
- state: box
|
||||||
|
After Width: | Height: | Size: 423 B |
BIN
Resources/Textures/Clothing/Head/Soft/bizarresoft.rsi/icon.png
Normal file
|
After Width: | Height: | Size: 386 B |
|
After Width: | Height: | Size: 411 B |
|
After Width: | Height: | Size: 412 B |
@@ -0,0 +1,26 @@
|
|||||||
|
{
|
||||||
|
"version": 1,
|
||||||
|
"license": "CC-BY-NC-SA-3.0",
|
||||||
|
"copyright": "Taken from civstation at commit https://github.com/Civ13/Civ13/commit/ec52cbb95d59b717d4d8c480b35ac133e5b58088#diff-fba188fb2db5d16e5d41985147336a8f96085f761b903e016fffd869b63e497d",
|
||||||
|
"size": {
|
||||||
|
"x": 32,
|
||||||
|
"y": 32
|
||||||
|
},
|
||||||
|
"states": [
|
||||||
|
{
|
||||||
|
"name": "icon"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "equipped-HELMET",
|
||||||
|
"directions": 4
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "inhand-left",
|
||||||
|
"directions": 4
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "inhand-right",
|
||||||
|
"directions": 4
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
After Width: | Height: | Size: 418 B |
|
After Width: | Height: | Size: 392 B |
|
After Width: | Height: | Size: 411 B |
|
After Width: | Height: | Size: 458 B |
@@ -0,0 +1,26 @@
|
|||||||
|
{
|
||||||
|
"version": 1,
|
||||||
|
"license": "CC-BY-NC-SA-3.0",
|
||||||
|
"copyright": "Taken from civstation at commit https://github.com/Civ13/Civ13/commit/ec52cbb95d59b717d4d8c480b35ac133e5b58088#diff-fba188fb2db5d16e5d41985147336a8f96085f761b903e016fffd869b63e497d",
|
||||||
|
"size": {
|
||||||
|
"x": 32,
|
||||||
|
"y": 32
|
||||||
|
},
|
||||||
|
"states": [
|
||||||
|
{
|
||||||
|
"name": "icon"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "equipped-HELMET",
|
||||||
|
"directions": 4
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "inhand-left",
|
||||||
|
"directions": 4
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "inhand-right",
|
||||||
|
"directions": 4
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
BIN
Resources/Textures/Interface/Actions/manifest.png
Normal file
|
After Width: | Height: | Size: 1.9 KiB |
@@ -21,6 +21,9 @@
|
|||||||
},
|
},
|
||||||
{
|
{
|
||||||
"name": "harm"
|
"name": "harm"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "manifest"
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
|
|||||||
BIN
Resources/Textures/Mobs/Aliens/Guardians/guardians.rsi/magic.png
Normal file
|
After Width: | Height: | Size: 924 B |
|
After Width: | Height: | Size: 1.3 KiB |
|
After Width: | Height: | Size: 1.1 KiB |
@@ -0,0 +1,57 @@
|
|||||||
|
{
|
||||||
|
"version": 1,
|
||||||
|
"license": "CC-BY-NC-SA-3.0",
|
||||||
|
"copyright": "taken from /tg/ station at commit https://github.com/tgstation/tgstation/commit/02756c2bc2cf3000080d030955e994242bab39b5",
|
||||||
|
"size": {
|
||||||
|
"x": 32,
|
||||||
|
"y": 32
|
||||||
|
},
|
||||||
|
"states": [
|
||||||
|
{
|
||||||
|
"name": "magic_flare",
|
||||||
|
"directions": 4
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "magic_base",
|
||||||
|
"directions": 4
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "miner_flare",
|
||||||
|
"directions": 4
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "miner_base",
|
||||||
|
"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
|
||||||
|
]
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "tech_flare",
|
||||||
|
"directions": 4
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "tech_base",
|
||||||
|
"directions": 4
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
BIN
Resources/Textures/Mobs/Aliens/Guardians/guardians.rsi/miner.png
Normal file
|
After Width: | Height: | Size: 681 B |
|
After Width: | Height: | Size: 2.7 KiB |
|
After Width: | Height: | Size: 801 B |
BIN
Resources/Textures/Mobs/Aliens/Guardians/guardians.rsi/tech.png
Normal file
|
After Width: | Height: | Size: 854 B |
|
After Width: | Height: | Size: 1.7 KiB |
|
After Width: | Height: | Size: 930 B |
|
After Width: | Height: | Size: 1.5 KiB |
BIN
Resources/Textures/Objects/Misc/guardian_info.rsi/icon.png
Normal file
|
After Width: | Height: | Size: 952 B |
25
Resources/Textures/Objects/Misc/guardian_info.rsi/meta.json
Normal file
@@ -0,0 +1,25 @@
|
|||||||
|
{
|
||||||
|
"version": 1,
|
||||||
|
"license": "CC-BY-NC-SA-4.0",
|
||||||
|
"copyright": "original asset provided by https://github.com/CrudeWax",
|
||||||
|
"size": {
|
||||||
|
"x": 32,
|
||||||
|
"y": 32
|
||||||
|
},
|
||||||
|
"states": [
|
||||||
|
{
|
||||||
|
"name": "guardian_info",
|
||||||
|
"delays": [
|
||||||
|
[
|
||||||
|
0.2,
|
||||||
|
0.1,
|
||||||
|
0.2,
|
||||||
|
0.1
|
||||||
|
]
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "icon"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||