Goliath mob (#30839)

* Goliath mob

* Update asteroid.yml

* mcfuck yourself

* add cloak

* fixes

* Update materials.yml
This commit is contained in:
Nemanja
2024-08-18 12:22:36 -04:00
committed by GitHub
parent d673bdfe67
commit a540b8840e
29 changed files with 718 additions and 24 deletions

View File

@@ -154,21 +154,6 @@ public sealed class ActionOnInteractSystem : EntitySystem
args.Handled = true;
}
private bool ValidAction(BaseActionComponent action, bool canReach = true)
{
if (!action.Enabled)
return false;
if (action.Charges.HasValue && action.Charges <= 0)
return false;
var curTime = _timing.CurTime;
if (action.Cooldown.HasValue && action.Cooldown.Value.End > curTime)
return false;
return canReach || action is BaseTargetActionComponent { CheckCanAccess: false };
}
private List<(EntityUid Id, T Comp)> GetValidActions<T>(List<EntityUid>? actions, bool canReach = true) where T : BaseActionComponent
{
var valid = new List<(EntityUid Id, T Comp)>();
@@ -180,7 +165,7 @@ public sealed class ActionOnInteractSystem : EntitySystem
{
if (!_actions.TryGetActionData(id, out var baseAction) ||
baseAction as T is not { } action ||
!ValidAction(action, canReach))
!_actions.ValidAction(action, canReach))
{
continue;
}

View File

@@ -0,0 +1,27 @@
using Content.Server.NPC.Systems;
using Content.Shared.Actions;
using Robust.Shared.Prototypes;
namespace Content.Server.NPC.Components;
/// <summary>
/// This is used for an NPC that constantly tries to use an action on a given target.
/// </summary>
[RegisterComponent, Access(typeof(NPCUseActionOnTargetSystem))]
public sealed partial class NPCUseActionOnTargetComponent : Component
{
/// <summary>
/// HTN blackboard key for the target entity
/// </summary>
[DataField]
public string TargetKey = "Target";
/// <summary>
/// Action that's going to attempt to be used.
/// </summary>
[DataField(required: true)]
public EntProtoId<EntityWorldTargetActionComponent> ActionId;
[DataField]
public EntityUid? ActionEnt;
}

View File

@@ -34,6 +34,7 @@ public sealed partial class NPCBlackboard : IEnumerable<KeyValuePair<string, obj
{"RangedRange", 10f},
{"RotateSpeed", float.MaxValue},
{"VisionRadius", 10f},
{"AggroVisionRadius", 10f},
};
/// <summary>
@@ -269,6 +270,13 @@ public sealed partial class NPCBlackboard : IEnumerable<KeyValuePair<string, obj
return _blackboard.Remove(key);
}
public string GetVisionRadiusKey(IEntityManager entMan)
{
return TryGetValue<EntityUid>("Target", out _, entMan)
? AggroVisionRadius
: VisionRadius;
}
// I Ummd and Ahhd about using strings vs enums and decided on tags because
// if a fork wants to do their own thing they don't need to touch the enum.
@@ -317,9 +325,11 @@ public sealed partial class NPCBlackboard : IEnumerable<KeyValuePair<string, obj
public const string PathfindKey = "MovementPathfind";
public const string RotateSpeed = "RotateSpeed";
public const string VisionRadius = "VisionRadius";
public const string UtilityTarget = "UtilityTarget";
private const string VisionRadius = "VisionRadius";
private const string AggroVisionRadius = "AggroVisionRadius";
/// <summary>
/// A configurable "order" enum that can be given to an NPC from an external source.
/// </summary>

View File

@@ -0,0 +1,68 @@
using Content.Server.NPC.Components;
using Content.Server.NPC.HTN;
using Content.Shared.Actions;
using Robust.Shared.Timing;
namespace Content.Server.NPC.Systems;
public sealed class NPCUseActionOnTargetSystem : EntitySystem
{
[Dependency] private readonly IGameTiming _timing = default!;
[Dependency] private readonly SharedActionsSystem _actions = default!;
/// <inheritdoc/>
public override void Initialize()
{
base.Initialize();
SubscribeLocalEvent<NPCUseActionOnTargetComponent, MapInitEvent>(OnMapInit);
}
private void OnMapInit(Entity<NPCUseActionOnTargetComponent> ent, ref MapInitEvent args)
{
ent.Comp.ActionEnt = _actions.AddAction(ent, ent.Comp.ActionId);
}
public bool TryUseTentacleAttack(Entity<NPCUseActionOnTargetComponent?> user, EntityUid target)
{
if (!Resolve(user, ref user.Comp, false))
return false;
if (!TryComp<EntityWorldTargetActionComponent>(user.Comp.ActionEnt, out var action))
return false;
if (!_actions.ValidAction(action))
return false;
if (action.Event != null)
{
action.Event.Performer = user;
action.Event.Action = user.Comp.ActionEnt.Value;
action.Event.Coords = Transform(target).Coordinates;
}
_actions.PerformAction(user,
null,
user.Comp.ActionEnt.Value,
action,
action.BaseEvent,
_timing.CurTime,
false);
return true;
}
public override void Update(float frameTime)
{
base.Update(frameTime);
// Tries to use the attack on the current target.
var query = EntityQueryEnumerator<NPCUseActionOnTargetComponent, HTNComponent>();
while (query.MoveNext(out var uid, out var comp, out var htn))
{
if (!htn.Blackboard.TryGetValue<EntityUid>(comp.TargetKey, out var target, EntityManager))
continue;
TryUseTentacleAttack((uid, comp), target);
}
}
}

View File

@@ -264,7 +264,7 @@ public sealed class NPCUtilitySystem : EntitySystem
}
case TargetDistanceCon:
{
var radius = blackboard.GetValueOrDefault<float>(NPCBlackboard.VisionRadius, EntityManager);
var radius = blackboard.GetValueOrDefault<float>(blackboard.GetVisionRadiusKey(EntityManager), EntityManager);
if (!TryComp(targetUid, out TransformComponent? targetXform) ||
!TryComp(owner, out TransformComponent? xform))
@@ -309,13 +309,13 @@ public sealed class NPCUtilitySystem : EntitySystem
}
case TargetInLOSCon:
{
var radius = blackboard.GetValueOrDefault<float>(NPCBlackboard.VisionRadius, EntityManager);
var radius = blackboard.GetValueOrDefault<float>(blackboard.GetVisionRadiusKey(EntityManager), EntityManager);
return _examine.InRangeUnOccluded(owner, targetUid, radius + 0.5f, null) ? 1f : 0f;
}
case TargetInLOSOrCurrentCon:
{
var radius = blackboard.GetValueOrDefault<float>(NPCBlackboard.VisionRadius, EntityManager);
var radius = blackboard.GetValueOrDefault<float>(blackboard.GetVisionRadiusKey(EntityManager), EntityManager);
const float bufferRange = 0.5f;
if (blackboard.TryGetValue<EntityUid>("Target", out var currentTarget, EntityManager) &&
@@ -375,7 +375,7 @@ public sealed class NPCUtilitySystem : EntitySystem
private void Add(NPCBlackboard blackboard, HashSet<EntityUid> entities, UtilityQuery query)
{
var owner = blackboard.GetValue<EntityUid>(NPCBlackboard.Owner);
var vision = blackboard.GetValueOrDefault<float>(NPCBlackboard.VisionRadius, EntityManager);
var vision = blackboard.GetValueOrDefault<float>(blackboard.GetVisionRadiusKey(EntityManager), EntityManager);
switch (query)
{

View File

@@ -0,0 +1,31 @@
using Content.Shared.Actions;
using Robust.Shared.Prototypes;
namespace Content.Shared.Abilities.Goliath;
public sealed partial class GoliathSummonTentacleAction : EntityWorldTargetActionEvent
{
/// <summary>
/// The ID of the entity that is spawned.
/// </summary>
[DataField]
public EntProtoId EntityId = "EffectGoliathTentacleSpawn";
/// <summary>
/// Directions determining where the entities will spawn.
/// </summary>
[DataField]
public List<Direction> OffsetDirections = new()
{
Direction.North,
Direction.South,
Direction.East,
Direction.West,
};
/// <summary>
/// How many entities will spawn beyond the original one at the target location?
/// </summary>
[DataField]
public int ExtraSpawns = 3;
};

View File

@@ -0,0 +1,69 @@
using Content.Shared.Directions;
using Content.Shared.Maps;
using Content.Shared.Physics;
using Content.Shared.Popups;
using Content.Shared.Stunnable;
using Robust.Shared.Map;
using Robust.Shared.Map.Components;
using Robust.Shared.Network;
using Robust.Shared.Random;
namespace Content.Shared.Abilities.Goliath;
public sealed class GoliathTentacleSystem : EntitySystem
{
[Dependency] private readonly IRobustRandom _random = default!;
[Dependency] private readonly INetManager _net = default!;
[Dependency] private readonly SharedMapSystem _map = default!;
[Dependency] private readonly SharedStunSystem _stun = default!;
[Dependency] private readonly SharedTransformSystem _transform = default!;
[Dependency] private readonly TurfSystem _turf = default!;
[Dependency] private readonly SharedPopupSystem _popup = default!;
/// <inheritdoc/>
public override void Initialize()
{
SubscribeLocalEvent<GoliathSummonTentacleAction>(OnSummonAction);
}
private void OnSummonAction(GoliathSummonTentacleAction args)
{
if (args.Handled || args.Coords is not { } coords)
return;
// TODO: animation
_popup.PopupPredicted(Loc.GetString("tentacle-ability-use-popup", ("entity", args.Performer)), args.Performer, args.Performer, type: PopupType.SmallCaution);
_stun.TryStun(args.Performer, TimeSpan.FromSeconds(0.8f), false);
List<EntityCoordinates> spawnPos = new();
spawnPos.Add(coords);
var dirs = new List<Direction>();
dirs.AddRange(args.OffsetDirections);
for (var i = 0; i < 3; i++)
{
var dir = _random.PickAndTake(dirs);
spawnPos.Add(coords.Offset(dir));
}
if (_transform.GetGrid(coords) is not { } grid || !TryComp<MapGridComponent>(grid, out var gridComp))
return;
foreach (var pos in spawnPos)
{
if (!_map.TryGetTileRef(grid, gridComp, pos, out var tileRef) ||
tileRef.IsSpace() ||
_turf.IsTileBlocked(tileRef, CollisionGroup.Impassable))
{
continue;
}
if (_net.IsServer)
Spawn(args.EntityId, pos);
}
args.Handled = true;
}
}

View File

@@ -958,6 +958,21 @@ public abstract class SharedActionsSystem : EntitySystem
// See client-side system for UI code.
}
public bool ValidAction(BaseActionComponent action, bool canReach = true)
{
if (!action.Enabled)
return false;
if (action.Charges.HasValue && action.Charges <= 0)
return false;
var curTime = GameTiming.CurTime;
if (action.Cooldown.HasValue && action.Cooldown.Value.End > curTime)
return false;
return canReach || action is BaseTargetActionComponent { CheckCanAccess: false };
}
#endregion
#region EquipHandlers

View File

@@ -14,16 +14,22 @@ using Content.Shared.Movement.Systems;
using Content.Shared.Standing;
using Content.Shared.StatusEffect;
using Content.Shared.Throwing;
using Content.Shared.Whitelist;
using Robust.Shared.Audio.Systems;
using Robust.Shared.Physics.Components;
using Robust.Shared.Physics.Events;
using Robust.Shared.Physics.Systems;
namespace Content.Shared.Stunnable;
public abstract class SharedStunSystem : EntitySystem
{
[Dependency] private readonly ActionBlockerSystem _blocker = default!;
[Dependency] private readonly SharedBroadphaseSystem _broadphase = default!;
[Dependency] private readonly ISharedAdminLogManager _adminLogger = default!;
[Dependency] private readonly MovementSpeedModifierSystem _movementSpeedModifier = default!;
[Dependency] private readonly SharedAudioSystem _audio = default!;
[Dependency] private readonly EntityWhitelistSystem _entityWhitelist = default!;
[Dependency] private readonly StandingStateSystem _standingState = default!;
[Dependency] private readonly StatusEffectsSystem _statusEffect = default!;
@@ -45,6 +51,9 @@ public abstract class SharedStunSystem : EntitySystem
SubscribeLocalEvent<StunnedComponent, ComponentStartup>(UpdateCanMove);
SubscribeLocalEvent<StunnedComponent, ComponentShutdown>(UpdateCanMove);
SubscribeLocalEvent<StunOnContactComponent, ComponentStartup>(OnStunOnContactStartup);
SubscribeLocalEvent<StunOnContactComponent, StartCollideEvent>(OnStunOnContactCollide);
// helping people up if they're knocked down
SubscribeLocalEvent<KnockedDownComponent, InteractHandEvent>(OnInteractHand);
SubscribeLocalEvent<SlowedDownComponent, RefreshMovementSpeedModifiersEvent>(OnRefreshMovespeed);
@@ -104,6 +113,27 @@ public abstract class SharedStunSystem : EntitySystem
_blocker.UpdateCanMove(uid);
}
private void OnStunOnContactStartup(Entity<StunOnContactComponent> ent, ref ComponentStartup args)
{
if (TryComp<PhysicsComponent>(ent, out var body))
_broadphase.RegenerateContacts(ent, body);
}
private void OnStunOnContactCollide(Entity<StunOnContactComponent> ent, ref StartCollideEvent args)
{
if (args.OurFixtureId != ent.Comp.FixtureId)
return;
if (_entityWhitelist.IsBlacklistPass(ent.Comp.Blacklist, args.OtherEntity))
return;
if (!TryComp<StatusEffectsComponent>(args.OtherEntity, out var status))
return;
TryStun(args.OtherEntity, ent.Comp.Duration, true, status);
TryKnockdown(args.OtherEntity, ent.Comp.Duration, true, status);
}
private void OnKnockInit(EntityUid uid, KnockedDownComponent component, ComponentInit args)
{
_standingState.Down(uid);

View File

@@ -0,0 +1,23 @@
using Content.Shared.Whitelist;
using Robust.Shared.GameStates;
namespace Content.Shared.Stunnable;
[RegisterComponent, NetworkedComponent, Access(typeof(SharedStunSystem))]
public sealed partial class StunOnContactComponent : Component
{
/// <summary>
/// The fixture the entity must collide with to be stunned
/// </summary>
[DataField]
public string FixtureId = "fix";
/// <summary>
/// The duration of the stun.
/// </summary>
[DataField]
public TimeSpan Duration = TimeSpan.FromSeconds(5);
[DataField]
public EntityWhitelist Blacklist = new();
}

View File

@@ -0,0 +1 @@
tentacle-ability-use-popup = {CAPITALIZE(THE($entity))} digs its tentacles under the ground!

View File

@@ -0,0 +1,182 @@
- type: entity
id: BaseMobAsteroid
parent:
- BaseMob
- MobDamageable
- MobAtmosExposed
- MobCombat
abstract: true
components:
- type: Reactive
groups:
Flammable: [Touch]
Extinguish: [Touch]
Acidic: [Touch, Ingestion]
- type: Body
prototype: Animal
- type: Climbing
- type: NameIdentifier
group: GenericNumber
- type: StatusEffects
allowed:
- SlowedDown
- Stutter
- Stun
- Electrocution
- TemporaryBlindness
- RadiationProtection
- Drowsiness
- type: StandingState
- type: Tag
tags:
- DoorBumpOpener
- type: entity
id: MobGoliath
parent: BaseMobAsteroid
name: goliath
description: A massive beast that uses long tentacles to ensnare its prey, threatening them is not advised under any conditions.
components:
- type: Sprite
sprite: Mobs/Aliens/Asteroid/goliath.rsi
layers:
- map: ["enum.DamageStateVisualLayers.Base"]
state: goliath
- type: DamageStateVisuals
states:
Alive:
Base: goliath
Dead:
Base: goliath_dead
- type: MovementSpeedModifier
baseWalkSpeed : 2.50
baseSprintSpeed : 2.50
- type: MobThresholds
thresholds:
0: Alive
300: Dead
- type: MeleeWeapon
soundHit:
path: "/Audio/Weapons/smash.ogg"
angle: 0
attackRate: 0.75
animation: WeaponArcPunch
damage:
types:
Slash: 15
Piercing: 10
- type: NpcFactionMember
factions:
- SimpleHostile
- type: HTN
rootTask:
task: GoliathCompound
blackboard:
VisionRadius: !type:Single
6
AggroVisionRadius: !type:Single
10
- type: NPCUseActionOnTarget
actionId: ActionGoliathTentacle
- type: Tag
tags:
- CannotSuicide
- Goliath
- FootstepSound
- type: NoSlip
- type: Butcherable
spawned:
- id: FoodMeatGoliath
amount: 3
- id: MaterialGoliathHide1
- type: entity
id: ActionGoliathTentacle
name: "[color=red]Tentacle Slam[/color]"
description: Use your tentacles to grab and stun a target player!
components:
- type: EntityWorldTargetAction
raiseOnUser: true
icon:
sprite: Mobs/Aliens/Asteroid/goliath.rsi
state: goliath_tentacle_spawn
iconOn:
sprite: Mobs/Aliens/Asteroid/goliath.rsi
state: goliath_tentacle_wiggle
sound:
path: "/Audio/Weapons/slash.ogg"
event: !type:GoliathSummonTentacleAction
useDelay: 8
range: 10
- type: entity
id: GoliathTentacle
name: tentacle
components:
- type: Transform
anchored: True
- type: Physics
bodyType: Static
canCollide: true
- type: InteractionOutline
- type: Sprite
sprite: Mobs/Aliens/Asteroid/goliath.rsi
layers:
- state: goliath_tentacle_wiggle
- type: StunOnContact
blacklist:
tags:
- Goliath
- type: Fixtures
fixtures:
fix:
shape:
!type:PhysShapeAabb
bounds: "-0.45,-0.45,0.45,0.45"
mask:
- Impassable
layer:
- Impassable
hard: false
- type: TimedDespawn #do this shit by hand because of fucking course.
lifetime: 0.4
- type: SpawnOnDespawn
prototype: EffectGoliathTentacleRetract
- type: entity
id: BaseEffectGoliathTentacleSpawn
categories: [ HideSpawnMenu ]
name: tentacle
abstract: true
components:
- type: Transform
anchored: True
- type: Physics
bodyType: Static
canCollide: false
- type: Sprite
sprite: Mobs/Aliens/Asteroid/goliath.rsi
- type: InteractionOutline
- type: TimedDespawn
lifetime: 0.7
- type: entity
id: EffectGoliathTentacleSpawn
parent: BaseEffectGoliathTentacleSpawn
categories: [ HideSpawnMenu ]
name: tentacle
components:
- type: Sprite
state: goliath_tentacle_spawn
- type: SpawnOnDespawn
prototype: GoliathTentacle
- type: entity
id: EffectGoliathTentacleRetract
parent: BaseEffectGoliathTentacleSpawn
categories: [ HideSpawnMenu ]
components:
- type: Sprite
state: goliath_tentacle_retract
- type: EffectVisuals
- type: AnimationPlayer

View File

@@ -654,3 +654,40 @@
Gunpowder: 100
- type: Item
size: Tiny
- type: entity
parent: MaterialBase
id: MaterialGoliathHide
name: goliath hide plates
description: Pieces of a goliath's rocky hide, these might be able to make your suit a bit more durable to attack from the local fauna.
suffix: Full
components:
- type: Sprite
sprite: Objects/Materials/hide.rsi
layers:
- state: goliath_hide
map: [ "base" ]
- type: StaticPrice
price: 0
- type: StackPrice
price: 1500
- type: Appearance
- type: Stack
stackType: GoliathHide
baseLayer: base
layerStates:
- goliath_hide
- goliath_hide_2
- goliath_hide_3
- type: Item
size: Large
shape:
- 0,0,2,2
- type: entity
parent: MaterialGoliathHide
id: MaterialGoliathHide1
suffix: 1
components:
- type: Stack
count: 1

View File

@@ -0,0 +1,75 @@
- type: htnCompound
id: GoliathCompound
branches:
- tasks:
- !type:HTNCompoundTask
task: GoliathMeleeCombatPrecondition
- tasks:
- !type:HTNCompoundTask
task: IdleCompound
- type: htnCompound
id: GoliathMeleeCombatPrecondition
branches:
- preconditions:
- !type:BuckledPrecondition
isBuckled: true
tasks:
- !type:HTNPrimitiveTask
operator: !type:UnbuckleOperator
shutdownState: TaskFinished
- preconditions:
- !type:InContainerPrecondition
isInContainer: true
tasks:
- !type:HTNCompoundTask
task: EscapeCompound
- preconditions:
- !type:PulledPrecondition
isPulled: true
tasks:
- !type:HTNPrimitiveTask
operator: !type:UnPullOperator
shutdownState: TaskFinished
- tasks:
- !type:HTNPrimitiveTask
operator: !type:UtilityOperator
proto: NearbyMeleeTargets
- !type:HTNCompoundTask
task: GoliathAttackTargetCompound
- type: htnCompound
id: GoliathAttackTargetCompound
branches:
- preconditions:
- !type:KeyExistsPrecondition
key: Target
tasks:
- !type:HTNPrimitiveTask
operator: !type:MoveToOperator
shutdownState: PlanFinished
pathfindInPlanning: true
removeKeyOnFinish: false
targetKey: TargetCoordinates
pathfindKey: TargetPathfind
rangeKey: MeleeRange
- !type:HTNPrimitiveTask
operator: !type:JukeOperator
jukeType: AdjacentTile
- !type:HTNPrimitiveTask
operator: !type:MeleeOperator
targetKey: Target
preconditions:
- !type:KeyExistsPrecondition
key: Target
- !type:TargetInRangePrecondition
targetKey: Target
rangeKey: MeleeRange
services:
- !type:UtilityService
id: MeleeService
proto: NearbyMeleeTargets
key: Target

View File

@@ -145,7 +145,7 @@
minCount: 5
maxCount: 8
groups:
- id: MobXeno
- id: MobGoliath
amount: 1
#- type: dungeonConfig
@@ -175,7 +175,7 @@
minCount: 20
maxCount: 30
groups:
- id: MobXeno
- id: MobGoliath
amount: 1
#- type: dungeonConfig

View File

@@ -88,3 +88,10 @@
icon: { sprite: /Textures/Objects/Misc/reagent_fillings.rsi, state: powderpile }
spawn: MaterialGunpowder
maxCount: 60
- type: stack
id: GoliathHide
name: goliath hide
icon: { sprite: /Textures/Objects/Materials/hide.rsi, state: goliath_hide }
spawn: MaterialGoliathHide1
maxCount: 30

View File

@@ -656,6 +656,9 @@
- type: Tag
id: Goat
- type: Tag
id: Goliath
- type: Tag
id: GPS

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.9 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.2 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 968 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.1 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.9 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.9 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.8 KiB

View File

@@ -0,0 +1,111 @@
{
"version": 1,
"license": "CC-BY-SA-3.0",
"copyright": "Taken from Paradise at https://github.com/ParadiseSS13/Paradise/blob/5a68c5f6d3b60ee82c06e0287d1eb8108d2e1fe2/icons/mob/lavaland/lavaland_monsters.dmi",
"size": {
"x": 32,
"y": 32
},
"states": [
{
"name": "goliath",
"directions": 4
},
{
"name": "goliath_alert",
"directions": 4,
"delays": [
[
1,
0.2,
1,
0.5
],
[
1,
0.2,
1,
0.5
],
[
1,
0.2,
1,
0.5
],
[
1,
0.2,
1,
0.5
]
]
},
{
"name": "goliath_preattack",
"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
]
]
},
{
"name": "goliath_dead"
},
{
"name": "goliath_tentacle_spawn",
"delays": [
[
0.2,
0.2,
0.2,
0.1
]
]
},
{
"name": "goliath_tentacle_wiggle",
"delays": [
[
0.1,
0.1,
0.1,
0.1
]
]
},
{
"name": "goliath_tentacle_retract",
"delays": [
[
0.1,
0.2,
0.2,
0.2
]
]
}
]
}

Binary file not shown.

After

Width:  |  Height:  |  Size: 353 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 479 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 554 B

View File

@@ -0,0 +1,20 @@
{
"version": 1,
"license": "CC-BY-SA-3.0",
"copyright": "Taken from Paradise at https://github.com/ParadiseSS13/Paradise/blob/88d236d5c91e9a57e103957555c2e9a8c63b68fa/icons/obj/stacks/organic.dmi",
"size": {
"x": 32,
"y": 32
},
"states": [
{
"name": "goliath_hide"
},
{
"name": "goliath_hide_2"
},
{
"name": "goliath_hide_3"
}
]
}