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>
This commit is contained in:
CrudeWax
2021-12-29 06:58:48 +03:00
committed by GitHub
parent 277f3320f5
commit 299c4be328
44 changed files with 836 additions and 7 deletions

View File

@@ -314,6 +314,9 @@ namespace Content.Client.Entry
"NukeCodePaper", "NukeCodePaper",
"GhostRadio", "GhostRadio",
"Armor", "Armor",
"Guardian",
"GuardianCreator",
"GuardianHost",
"Udder", "Udder",
"PneumaticCannon", "PneumaticCannon",
"Spreader", "Spreader",

View 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"));
}
}
}
}

View 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;
}
}

View 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;
}
}

View 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!;
}
}

View 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;
}
}
}
}

View File

@@ -18,6 +18,7 @@ namespace Content.Shared.Actions
DebugTargetEntity, DebugTargetEntity,
DebugTargetEntityRepeat, DebugTargetEntityRepeat,
SpellPie, SpellPie,
ManifestGuardian,
PAIMidi PAIMidi
} }

View File

@@ -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();
} }
} }

View File

@@ -111,7 +111,7 @@ namespace Content.Shared.MobState.EntitySystems
private void OnStartPullAttempt(EntityUid uid, MobStateComponent component, StartPullAttemptEvent args) private void OnStartPullAttempt(EntityUid uid, MobStateComponent component, StartPullAttemptEvent args)
{ {
if(component.IsIncapacitated()) if (component.IsIncapacitated())
args.Cancel(); args.Cancel();
} }
@@ -135,7 +135,7 @@ namespace Content.Shared.MobState.EntitySystems
private void OnStandAttempt(EntityUid uid, MobStateComponent component, StandAttemptEvent args) private void OnStandAttempt(EntityUid uid, MobStateComponent component, StandAttemptEvent args)
{ {
if(component.IsIncapacitated()) if (component.IsIncapacitated())
args.Cancel(); args.Cancel();
} }
} }

Binary file not shown.

Binary file not shown.

Binary file not shown.

View 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!

View 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

View File

@@ -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.

View File

@@ -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

View File

@@ -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

View 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

View File

@@ -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

Binary file not shown.

After

Width:  |  Height:  |  Size: 423 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 386 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 411 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 412 B

View File

@@ -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
}
]
}

Binary file not shown.

After

Width:  |  Height:  |  Size: 418 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 392 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 411 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 458 B

View File

@@ -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
}
]
}

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.9 KiB

View File

@@ -21,6 +21,9 @@
}, },
{ {
"name": "harm" "name": "harm"
},
{
"name": "manifest"
} }
] ]
} }

Binary file not shown.

After

Width:  |  Height:  |  Size: 924 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.3 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.1 KiB

View File

@@ -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
}
]
}

Binary file not shown.

After

Width:  |  Height:  |  Size: 681 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.7 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 801 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 854 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.7 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 930 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.5 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 952 B

View 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"
}
]
}