mirror of
https://github.com/WWhiteDreamProject/wwdpublic.git
synced 2026-04-17 05:27:38 +03:00
Cherrypick "Shoot Over Bodies" And Related PRs (#479)
# Description This is a manual cherry-pick of the following PRs: https://github.com/space-wizards/space-station-14/pull/27905 https://github.com/space-wizards/space-station-14/pull/28072 https://github.com/space-wizards/space-station-14/pull/28571 I REQUIRE these for my work in PR #11 , and cannot complete said PR without these cherry-picks. This set of PRs from Wizden adds a feature where entities can selectively opt-out of being shot at unless a player intentionally targets them, which I can use as a simple and elegant solution to one of the largest glaring issues for Segmented Entities. I could simply give Lamia segments the new RequireProjectileTargetComponent, which adds them to the system. Future segmented entities such as the hypothetical "Heretic Worm" may or may not use this feature, depending on their intended implementation. --------- Co-authored-by: Danger Revolution! <142105406+DangerRevolution@users.noreply.github.com>
This commit is contained in:
@@ -1,4 +1,5 @@
|
||||
using System.Numerics;
|
||||
using Content.Client.Gameplay;
|
||||
using Content.Client.Items;
|
||||
using Content.Client.Weapons.Ranged.Components;
|
||||
using Content.Shared.Camera;
|
||||
@@ -12,6 +13,7 @@ using Robust.Client.GameObjects;
|
||||
using Robust.Client.Graphics;
|
||||
using Robust.Client.Input;
|
||||
using Robust.Client.Player;
|
||||
using Robust.Client.State;
|
||||
using Robust.Shared.Animations;
|
||||
using Robust.Shared.Input;
|
||||
using Robust.Shared.Map;
|
||||
@@ -27,6 +29,7 @@ public sealed partial class GunSystem : SharedGunSystem
|
||||
[Dependency] private readonly IEyeManager _eyeManager = default!;
|
||||
[Dependency] private readonly IInputManager _inputManager = default!;
|
||||
[Dependency] private readonly IPlayerManager _player = default!;
|
||||
[Dependency] private readonly IStateManager _state = default!;
|
||||
[Dependency] private readonly AnimationPlayerSystem _animPlayer = default!;
|
||||
[Dependency] private readonly InputSystem _inputSystem = default!;
|
||||
[Dependency] private readonly SharedCameraRecoilSystem _recoil = default!;
|
||||
@@ -171,10 +174,15 @@ public sealed partial class GunSystem : SharedGunSystem
|
||||
// Define target coordinates relative to gun entity, so that network latency on moving grids doesn't fuck up the target location.
|
||||
var coordinates = EntityCoordinates.FromMap(entity, mousePos, TransformSystem, EntityManager);
|
||||
|
||||
NetEntity? target = null;
|
||||
if (_state.CurrentState is GameplayStateBase screen)
|
||||
target = GetNetEntity(screen.GetClickedEntity(mousePos));
|
||||
|
||||
Log.Debug($"Sending shoot request tick {Timing.CurTick} / {Timing.CurTime}");
|
||||
|
||||
EntityManager.RaisePredictiveEvent(new RequestShootEvent
|
||||
{
|
||||
Target = target,
|
||||
Coordinates = GetNetCoordinates(coordinates),
|
||||
Gun = GetNetEntity(gunUid),
|
||||
});
|
||||
|
||||
@@ -280,6 +280,13 @@ public sealed partial class GunSystem : SharedGunSystem
|
||||
|
||||
private void ShootOrThrow(EntityUid uid, Vector2 mapDirection, Vector2 gunVelocity, GunComponent gun, EntityUid gunUid, EntityUid? user)
|
||||
{
|
||||
if (gun.Target is { } target && !TerminatingOrDeleted(target))
|
||||
{
|
||||
var targeted = EnsureComp<TargetedProjectileComponent>(uid);
|
||||
targeted.Target = target;
|
||||
Dirty(uid, targeted);
|
||||
}
|
||||
|
||||
// Do a throw
|
||||
if (!HasComp<ProjectileComponent>(uid))
|
||||
{
|
||||
|
||||
@@ -0,0 +1,14 @@
|
||||
using Robust.Shared.GameStates;
|
||||
|
||||
namespace Content.Shared.Damage.Components;
|
||||
|
||||
/// <summary>
|
||||
/// Prevent the object from getting hit by projetiles unless you target the object.
|
||||
/// </summary>
|
||||
[RegisterComponent, NetworkedComponent, AutoGenerateComponentState]
|
||||
[Access(typeof(RequireProjectileTargetSystem))]
|
||||
public sealed partial class RequireProjectileTargetComponent : Component
|
||||
{
|
||||
[DataField, AutoNetworkedField]
|
||||
public bool Active = true;
|
||||
}
|
||||
@@ -0,0 +1,51 @@
|
||||
using Content.Shared.Projectiles;
|
||||
using Content.Shared.Weapons.Ranged.Components;
|
||||
using Content.Shared.Standing;
|
||||
using Robust.Shared.Physics.Events;
|
||||
|
||||
namespace Content.Shared.Damage.Components;
|
||||
|
||||
public sealed class RequireProjectileTargetSystem : EntitySystem
|
||||
{
|
||||
public override void Initialize()
|
||||
{
|
||||
SubscribeLocalEvent<RequireProjectileTargetComponent, PreventCollideEvent>(PreventCollide);
|
||||
SubscribeLocalEvent<RequireProjectileTargetComponent, StoodEvent>(StandingBulletHit);
|
||||
SubscribeLocalEvent<RequireProjectileTargetComponent, DownedEvent>(LayingBulletPass);
|
||||
}
|
||||
|
||||
private void PreventCollide(Entity<RequireProjectileTargetComponent> ent, ref PreventCollideEvent args)
|
||||
{
|
||||
if (args.Cancelled)
|
||||
return;
|
||||
|
||||
if (!ent.Comp.Active)
|
||||
return;
|
||||
|
||||
var other = args.OtherEntity;
|
||||
if (HasComp<ProjectileComponent>(other) &&
|
||||
CompOrNull<TargetedProjectileComponent>(other)?.Target != ent)
|
||||
{
|
||||
args.Cancelled = true;
|
||||
}
|
||||
}
|
||||
|
||||
private void SetActive(Entity<RequireProjectileTargetComponent> ent, bool value)
|
||||
{
|
||||
if (ent.Comp.Active == value)
|
||||
return;
|
||||
|
||||
ent.Comp.Active = value;
|
||||
Dirty(ent);
|
||||
}
|
||||
|
||||
private void StandingBulletHit(Entity<RequireProjectileTargetComponent> ent, ref StoodEvent args)
|
||||
{
|
||||
SetActive(ent, false);
|
||||
}
|
||||
|
||||
private void LayingBulletPass(Entity<RequireProjectileTargetComponent> ent, ref DownedEvent args)
|
||||
{
|
||||
SetActive(ent, true);
|
||||
}
|
||||
}
|
||||
@@ -10,12 +10,15 @@ using Content.Shared.Item;
|
||||
using Content.Shared.Mobs.Components;
|
||||
using Content.Shared.Movement.Events;
|
||||
using Content.Shared.Pointing;
|
||||
using Content.Shared.Projectiles;
|
||||
using Content.Shared.Pulling.Events;
|
||||
using Content.Shared.Speech;
|
||||
using Content.Shared.Standing;
|
||||
using Content.Shared.Strip.Components;
|
||||
using Content.Shared.Throwing;
|
||||
using Content.Shared.Weapons.Ranged.Components;
|
||||
using Robust.Shared.Physics.Components;
|
||||
using Robust.Shared.Physics.Events;
|
||||
|
||||
namespace Content.Shared.Mobs.Systems;
|
||||
|
||||
|
||||
@@ -140,6 +140,12 @@ public sealed partial class GunComponent : Component
|
||||
[ViewVariables]
|
||||
public EntityCoordinates? ShootCoordinates = null;
|
||||
|
||||
/// <summary>
|
||||
/// Who the gun is being requested to shoot at directly.
|
||||
/// </summary>
|
||||
[ViewVariables]
|
||||
public EntityUid? Target = null;
|
||||
|
||||
/// <summary>
|
||||
/// The base value for how many shots to fire per burst.
|
||||
/// </summary>
|
||||
|
||||
@@ -0,0 +1,12 @@
|
||||
using Content.Shared.Weapons.Ranged.Systems;
|
||||
using Robust.Shared.GameStates;
|
||||
|
||||
namespace Content.Shared.Weapons.Ranged.Components;
|
||||
|
||||
[RegisterComponent, NetworkedComponent, AutoGenerateComponentState]
|
||||
[Access(typeof(SharedGunSystem))]
|
||||
public sealed partial class TargetedProjectileComponent : Component
|
||||
{
|
||||
[DataField, AutoNetworkedField]
|
||||
public EntityUid Target;
|
||||
}
|
||||
@@ -11,4 +11,5 @@ public sealed class RequestShootEvent : EntityEventArgs
|
||||
{
|
||||
public NetEntity Gun;
|
||||
public NetCoordinates Coordinates;
|
||||
public NetEntity? Target;
|
||||
}
|
||||
|
||||
@@ -139,6 +139,7 @@ public abstract partial class SharedGunSystem : EntitySystem
|
||||
|
||||
gun.ShootCoordinates = GetCoordinates(msg.Coordinates);
|
||||
Log.Debug($"Set shoot coordinates to {gun.ShootCoordinates}");
|
||||
gun.Target = GetEntity(msg.Target);
|
||||
AttemptShoot(user.Value, ent, gun);
|
||||
}
|
||||
|
||||
@@ -200,6 +201,7 @@ public abstract partial class SharedGunSystem : EntitySystem
|
||||
Log.Debug($"Stopped shooting {ToPrettyString(uid)}");
|
||||
gun.ShotCounter = 0;
|
||||
gun.ShootCoordinates = null;
|
||||
gun.Target = null;
|
||||
Dirty(uid, gun);
|
||||
}
|
||||
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
# The progenitor. This should only container the most basic components possible.
|
||||
# The progenitor. This should only container the most basic components possible.
|
||||
# Only put things on here if every mob *must* have it. This includes ghosts.
|
||||
- type: entity
|
||||
save: false
|
||||
@@ -43,6 +43,8 @@
|
||||
- type: MovementSpeedModifier
|
||||
- type: Polymorphable
|
||||
- type: StatusIcon
|
||||
- type: RequireProjectileTarget
|
||||
active: False
|
||||
|
||||
# Used for mobs that have health and can take damage.
|
||||
- type: entity
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
- type: entity
|
||||
- type: entity
|
||||
abstract: true
|
||||
parent: BaseItem
|
||||
id: BaseHandheldInstrument
|
||||
@@ -71,6 +71,7 @@
|
||||
- BulletImpassable
|
||||
- type: StaticPrice
|
||||
price: 300
|
||||
- type: RequireProjectileTarget
|
||||
|
||||
- type: entity
|
||||
parent: BasePlaceableInstrument
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
- type: entity
|
||||
abstract: true
|
||||
id: ReagentDispenserBase
|
||||
parent: ConstructibleMachine
|
||||
parent: SmallConstructibleMachine
|
||||
placement:
|
||||
mode: SnapgridCenter
|
||||
components:
|
||||
|
||||
@@ -60,3 +60,4 @@
|
||||
ents: []
|
||||
- type: LightningTarget
|
||||
priority: 1
|
||||
- type: RequireProjectileTarget
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
- type: entity
|
||||
id: BaseTabletopChemicalMachine
|
||||
parent: [ BaseMachinePowered, ConstructibleMachine ]
|
||||
parent: [ BaseMachinePowered, SmallConstructibleMachine ]
|
||||
abstract: true
|
||||
components:
|
||||
- type: Transform
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
- type: entity
|
||||
id: DiseaseDiagnoser
|
||||
parent: [ BaseMachinePowered, ConstructibleMachine ]
|
||||
parent: [ BaseMachinePowered, SmallConstructibleMachine ]
|
||||
name: Disease Diagnoser Delta Extreme
|
||||
description: A machine that analyzes disease samples.
|
||||
placement:
|
||||
@@ -43,5 +43,3 @@
|
||||
contentMargin: 12.0, 0.0, 12.0, 0.0
|
||||
# This is a narrow piece of paper
|
||||
maxWritableArea: 128.0, 0.0
|
||||
|
||||
|
||||
|
||||
@@ -24,3 +24,4 @@
|
||||
containers:
|
||||
machine_board: !type:Container
|
||||
machine_parts: !type:Container
|
||||
- type: RequireProjectileTarget
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
- type: entity
|
||||
id: MachineArtifactAnalyzer
|
||||
parent: [ BaseMachinePowered, ConstructibleMachine ]
|
||||
parent: [ BaseMachinePowered, SmallConstructibleMachine ]
|
||||
name: artifact analyzer
|
||||
description: A platform capable of performing analysis on various types of artifacts.
|
||||
components:
|
||||
@@ -35,6 +35,7 @@
|
||||
- Impassable
|
||||
- MidImpassable
|
||||
- LowImpassable
|
||||
- BulletImpassable
|
||||
hard: False
|
||||
- type: Transform
|
||||
anchored: true
|
||||
|
||||
@@ -70,3 +70,10 @@
|
||||
- machine_board
|
||||
- type: LightningTarget
|
||||
priority: 1
|
||||
|
||||
- type: entity
|
||||
abstract: true
|
||||
parent: ConstructibleMachine
|
||||
id: SmallConstructibleMachine
|
||||
components:
|
||||
- type: RequireProjectileTarget
|
||||
|
||||
@@ -65,6 +65,7 @@
|
||||
deviceNetId: Wireless
|
||||
receiveFrequencyId: Fax
|
||||
transmitFrequencyId: Fax
|
||||
- type: RequireProjectileTarget
|
||||
|
||||
# Special
|
||||
- type: entity
|
||||
|
||||
@@ -518,6 +518,7 @@
|
||||
- Sheet
|
||||
- RawMaterial
|
||||
- Ingot
|
||||
- type: RequireProjectileTarget
|
||||
|
||||
- type: entity
|
||||
id: ExosuitFabricator
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
- type: entity
|
||||
- type: entity
|
||||
id: KitchenMicrowave
|
||||
parent: [ BaseMachinePowered, ConstructibleMachine ]
|
||||
parent: [ BaseMachinePowered, SmallConstructibleMachine ]
|
||||
name: microwave
|
||||
description: It's magic.
|
||||
components:
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
- type: entity
|
||||
id: KitchenReagentGrinder
|
||||
parent: [ BaseMachinePowered, ConstructibleMachine ]
|
||||
parent: [ BaseMachinePowered, SmallConstructibleMachine ]
|
||||
name: reagent grinder
|
||||
description: From BlenderTech. Will It Blend? Let's find out!
|
||||
suffix: grinder/juicer
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
- type: entity
|
||||
abstract: true
|
||||
parent: [ BaseStructureDynamic, ConstructibleMachine ]
|
||||
parent: [ BaseStructureDynamic, SmallConstructibleMachine ]
|
||||
id: SurveillanceWirelessCameraBase
|
||||
name: wireless camera
|
||||
description: A camera. It's watching you. Kinda.
|
||||
@@ -23,6 +23,8 @@
|
||||
density: 80
|
||||
mask:
|
||||
- MachineMask
|
||||
layer:
|
||||
- BulletImpassable
|
||||
- type: SurveillanceCameraMicrophone
|
||||
blacklist:
|
||||
components:
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
- type: entity
|
||||
id: PortableScrubber
|
||||
parent: [BaseMachinePowered, ConstructibleMachine]
|
||||
parent: [BaseMachinePowered, SmallConstructibleMachine]
|
||||
name: portable scrubber
|
||||
description: It scrubs, portably!
|
||||
components:
|
||||
@@ -120,7 +120,7 @@
|
||||
layer:
|
||||
- MachineLayer
|
||||
- type: ApcPowerReceiver
|
||||
powerDisabled: true #starts off
|
||||
powerDisabled: true #starts off
|
||||
- type: Sprite
|
||||
sprite: Structures/Piping/Atmospherics/Portable/portable_sheater.rsi
|
||||
noRot: true
|
||||
@@ -195,4 +195,4 @@
|
||||
suffix: Anchored, Enabled
|
||||
components:
|
||||
- type: ApcPowerReceiver
|
||||
powerDisabled: false
|
||||
powerDisabled: false
|
||||
|
||||
@@ -390,7 +390,7 @@
|
||||
board: HellfireHeaterMachineCircuitBoard
|
||||
|
||||
- type: entity
|
||||
parent: [ BaseMachinePowered, ConstructibleMachine ]
|
||||
parent: [ BaseMachinePowered, SmallConstructibleMachine ]
|
||||
id: BaseGasCondenser
|
||||
name: condenser
|
||||
description: Condenses gases into liquids. Now we just need some plumbing.
|
||||
|
||||
@@ -91,6 +91,7 @@
|
||||
- key: enum.DisposalUnitUiKey.Key
|
||||
type: DisposalUnitBoundUserInterface
|
||||
- type: RatKingRummageable
|
||||
- type: RequireProjectileTarget
|
||||
|
||||
- type: entity
|
||||
id: MailingUnit
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
- type: entity
|
||||
id: Emitter
|
||||
name: emitter
|
||||
parent: ConstructibleMachine
|
||||
parent: SmallConstructibleMachine
|
||||
description: A heavy duty industrial laser. Shoots non-stop when turned on.
|
||||
placement:
|
||||
mode: SnapgridCenter
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
#
|
||||
#
|
||||
# You can use this Desmos sheet to calculate fuel burn rate values:
|
||||
# https://www.desmos.com/calculator/qcektq5dqs
|
||||
#
|
||||
@@ -6,7 +6,7 @@
|
||||
- type: entity
|
||||
abstract: true
|
||||
id: PortableGeneratorBase
|
||||
parent: [ BaseMachine, ConstructibleMachine ]
|
||||
parent: [ BaseMachine, SmallConstructibleMachine]
|
||||
components:
|
||||
# Basic properties
|
||||
- type: Transform
|
||||
|
||||
@@ -49,6 +49,7 @@
|
||||
onBump: false
|
||||
requirePower: true
|
||||
highVoltageNode: output
|
||||
- type: RequireProjectileTarget
|
||||
|
||||
- type: entity
|
||||
id: SolarPanel
|
||||
@@ -157,6 +158,7 @@
|
||||
graph: SolarPanel
|
||||
node: solarassembly
|
||||
defaultTarget: solarpanel
|
||||
- type: RequireProjectileTarget
|
||||
|
||||
- type: entity
|
||||
id: SolarTracker
|
||||
@@ -201,3 +203,4 @@
|
||||
- type: Construction
|
||||
graph: SolarPanel
|
||||
node: solartracker
|
||||
- type: RequireProjectileTarget
|
||||
|
||||
@@ -58,12 +58,15 @@
|
||||
density: 500
|
||||
mask:
|
||||
- TabletopMachineMask
|
||||
layer:
|
||||
- BulletImpassable
|
||||
- type: PowerChargerVisuals
|
||||
- type: ContainerContainer
|
||||
containers:
|
||||
charger_slot: !type:ContainerSlot
|
||||
machine_board: !type:Container
|
||||
machine_parts: !type:Container
|
||||
- type: RequireProjectileTarget
|
||||
|
||||
- type: entity
|
||||
parent: BaseItemRecharger
|
||||
|
||||
@@ -74,6 +74,7 @@
|
||||
node: crategenericsteel
|
||||
containers:
|
||||
- entity_storage
|
||||
- type: RequireProjectileTarget
|
||||
|
||||
- type: entity
|
||||
parent: CrateGeneric
|
||||
|
||||
@@ -156,6 +156,7 @@
|
||||
node: chestDrawer
|
||||
- type: StaticPrice
|
||||
price: 15
|
||||
- type: RequireProjectileTarget
|
||||
|
||||
- type: entity
|
||||
abstract: true
|
||||
|
||||
@@ -69,7 +69,7 @@
|
||||
acts: [ "Destruction" ]
|
||||
- type: Climbable
|
||||
delay: 2.5
|
||||
|
||||
- type: RequireProjectileTarget
|
||||
|
||||
#High
|
||||
- type: entity
|
||||
@@ -93,8 +93,10 @@
|
||||
mask:
|
||||
- FullTileMask
|
||||
layer:
|
||||
- Opaque
|
||||
- MidImpassable
|
||||
- LowImpassable
|
||||
- BulletImpassable
|
||||
- type: Construction
|
||||
graph: FenceWood
|
||||
node: straight
|
||||
@@ -120,8 +122,10 @@
|
||||
mask:
|
||||
- FullTileMask
|
||||
layer:
|
||||
- Opaque
|
||||
- MidImpassable
|
||||
- LowImpassable
|
||||
- BulletImpassable
|
||||
- type: Construction
|
||||
graph: FenceWood
|
||||
node: end
|
||||
@@ -156,8 +160,10 @@
|
||||
mask:
|
||||
- TableMask
|
||||
layer:
|
||||
- Opaque
|
||||
- MidImpassable
|
||||
- LowImpassable
|
||||
- BulletImpassable
|
||||
- type: Construction
|
||||
graph: FenceWood
|
||||
node: corner
|
||||
@@ -192,8 +198,10 @@
|
||||
mask:
|
||||
- TableMask
|
||||
layer:
|
||||
- Opaque
|
||||
- MidImpassable
|
||||
- LowImpassable
|
||||
- BulletImpassable
|
||||
- type: Construction
|
||||
graph: FenceWood
|
||||
node: tjunction
|
||||
@@ -218,8 +226,10 @@
|
||||
mask:
|
||||
- FullTileMask
|
||||
layer:
|
||||
- Opaque
|
||||
- MidImpassable
|
||||
- LowImpassable
|
||||
- BulletImpassable
|
||||
- type: InteractionOutline
|
||||
- type: Door
|
||||
openSpriteState: door_opened
|
||||
@@ -268,6 +278,7 @@
|
||||
layer:
|
||||
- MidImpassable
|
||||
- LowImpassable
|
||||
- BulletImpassable
|
||||
- type: Construction
|
||||
graph: FenceWood
|
||||
node: straight_small
|
||||
@@ -295,6 +306,7 @@
|
||||
layer:
|
||||
- MidImpassable
|
||||
- LowImpassable
|
||||
- BulletImpassable
|
||||
- type: Construction
|
||||
graph: FenceWood
|
||||
node: end_small
|
||||
@@ -331,6 +343,7 @@
|
||||
layer:
|
||||
- MidImpassable
|
||||
- LowImpassable
|
||||
- BulletImpassable
|
||||
- type: Construction
|
||||
graph: FenceWood
|
||||
node: corner_small
|
||||
@@ -367,6 +380,7 @@
|
||||
layer:
|
||||
- MidImpassable
|
||||
- LowImpassable
|
||||
- BulletImpassable
|
||||
- type: Construction
|
||||
graph: FenceWood
|
||||
node: tjunction_small
|
||||
@@ -393,6 +407,7 @@
|
||||
layer:
|
||||
- MidImpassable
|
||||
- LowImpassable
|
||||
- BulletImpassable
|
||||
- type: InteractionOutline
|
||||
- type: Door
|
||||
openSpriteState: door_opened_small
|
||||
@@ -415,4 +430,4 @@
|
||||
path: /Audio/Effects/door_close.ogg
|
||||
- type: Construction
|
||||
graph: FenceWood
|
||||
node: gate_small
|
||||
node: gate_small
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
- type: entity
|
||||
name: hydroponics tray
|
||||
parent: [ hydroponicsSoil, ConstructibleMachine]
|
||||
parent: [ hydroponicsSoil, SmallConstructibleMachine]
|
||||
id: hydroponicsTray
|
||||
description: An interstellar-grade space farmplot allowing for rapid growth and selective breeding of crops. Just... keep in mind the space weeds.
|
||||
components:
|
||||
@@ -14,6 +14,8 @@
|
||||
hard: true
|
||||
mask:
|
||||
- MachineMask
|
||||
layer:
|
||||
- BulletImpassable
|
||||
- type: Anchorable
|
||||
- type: Pullable
|
||||
- type: Sprite
|
||||
|
||||
Reference in New Issue
Block a user