diff --git a/Content.Client/Entry/IgnoredComponents.cs b/Content.Client/Entry/IgnoredComponents.cs index 5b062c43f5..2ee9e2f2e6 100644 --- a/Content.Client/Entry/IgnoredComponents.cs +++ b/Content.Client/Entry/IgnoredComponents.cs @@ -34,11 +34,12 @@ namespace Content.Client.Entry "LightBulb", "Healing", "Material", + "RandomAppearance", + "Mineable", "RangedMagazine", "Ammo", "AiController", "Computer", - "AsteroidRock", "ResearchServer", "ResearchPointSource", "ResearchClient", diff --git a/Content.Server/Mining/Components/AsteroidRockComponent.cs b/Content.Server/Mining/Components/AsteroidRockComponent.cs deleted file mode 100644 index 5d03c68b85..0000000000 --- a/Content.Server/Mining/Components/AsteroidRockComponent.cs +++ /dev/null @@ -1,50 +0,0 @@ -using System.Threading.Tasks; -using Content.Server.Weapon.Melee.Components; -using Content.Shared.Damage; -using Content.Shared.Interaction; -using Content.Shared.Mining; -using Robust.Shared.Audio; -using Robust.Shared.GameObjects; -using Robust.Shared.Prototypes; -using Robust.Shared.IoC; -using Robust.Shared.Player; -using Robust.Shared.Random; -using Robust.Shared.Serialization.Manager.Attributes; -using Robust.Shared.ViewVariables; - -namespace Content.Server.Mining.Components -{ - [RegisterComponent] - public class AsteroidRockComponent : Component, IInteractUsing - { - [Dependency] private readonly IEntityManager _entMan = default!; - [Dependency] private readonly IRobustRandom _random = default!; - - public override string Name => "AsteroidRock"; - private static readonly string[] SpriteStates = {"0", "1", "2", "3", "4"}; - - protected override void Initialize() - { - base.Initialize(); - if (_entMan.TryGetComponent(Owner, out AppearanceComponent? appearance)) - { - appearance.SetData(AsteroidRockVisuals.State, _random.Pick(SpriteStates)); - } - } - - async Task IInteractUsing.InteractUsing(InteractUsingEventArgs eventArgs) - { - var item = eventArgs.Using; - if (!_entMan.TryGetComponent(item, out MeleeWeaponComponent? meleeWeaponComponent)) - return false; - - EntitySystem.Get().TryChangeDamage(Owner, meleeWeaponComponent.Damage); - - if (!_entMan.TryGetComponent(item, out PickaxeComponent? pickaxeComponent)) - return true; - - SoundSystem.Play(Filter.Pvs(Owner), pickaxeComponent.MiningSound.GetSound(), Owner, AudioParams.Default); - return true; - } - } -} diff --git a/Content.Server/Mining/Components/MineableComponent.cs b/Content.Server/Mining/Components/MineableComponent.cs new file mode 100644 index 0000000000..498939c8bf --- /dev/null +++ b/Content.Server/Mining/Components/MineableComponent.cs @@ -0,0 +1,11 @@ +using Robust.Shared.Analyzers; +using Robust.Shared.GameObjects; + +namespace Content.Server.Mining.Components; + +[RegisterComponent, ComponentProtoName("Mineable")] +[Friend(typeof(MineableSystem))] +public class MineableComponent : Component +{ + public float BaseMineTime = 1.0f; +} diff --git a/Content.Server/Mining/Components/PickaxeComponent.cs b/Content.Server/Mining/Components/PickaxeComponent.cs index 4d9638a184..2242e4d953 100644 --- a/Content.Server/Mining/Components/PickaxeComponent.cs +++ b/Content.Server/Mining/Components/PickaxeComponent.cs @@ -1,18 +1,33 @@ +using System.Collections.Generic; +using Content.Shared.Damage; using Content.Shared.Sound; using Robust.Shared.GameObjects; using Robust.Shared.Serialization.Manager.Attributes; namespace Content.Server.Mining.Components { - [RegisterComponent] + [RegisterComponent, ComponentProtoName("Pickaxe")] public class PickaxeComponent : Component { - public override string Name => "Pickaxe"; - - [DataField("miningSound")] + [DataField("sound")] public SoundSpecifier MiningSound { get; set; } = new SoundPathSpecifier("/Audio/Items/Mining/pickaxe.ogg"); - [DataField("miningSpeedMultiplier")] - public float MiningSpeedMultiplier { get; set; } = 1f; + [DataField("timeMultiplier")] + public float MiningTimeMultiplier { get; set; } = 1f; + + /// + /// What damage should be given to objects when + /// mined using a pickaxe? + /// + [DataField("damage", required: true)] + public DamageSpecifier Damage { get; set; } = default!; + + /// + /// How many entities can this pickaxe mine at once? + /// + [DataField("maxEntities")] + public int MaxMiningEntities = 1; + + public HashSet MiningEntities = new(); } } diff --git a/Content.Server/Mining/MineableSystem.cs b/Content.Server/Mining/MineableSystem.cs new file mode 100644 index 0000000000..2301e3dbc0 --- /dev/null +++ b/Content.Server/Mining/MineableSystem.cs @@ -0,0 +1,83 @@ +using Content.Server.DoAfter; +using Content.Server.Mining.Components; +using Content.Shared.Damage; +using Content.Shared.Interaction; +using Robust.Shared.Audio; +using Robust.Shared.GameObjects; +using Robust.Shared.IoC; +using Robust.Shared.Player; + +namespace Content.Server.Mining; + +public class MineableSystem : EntitySystem +{ + [Dependency] private readonly DoAfterSystem _doAfterSystem = default!; + [Dependency] private readonly DamageableSystem _damageableSystem = default!; + + public override void Initialize() + { + base.Initialize(); + + SubscribeLocalEvent(OnInteractUsing); + SubscribeLocalEvent(OnDoafterCancel); + SubscribeLocalEvent(OnDoafterSuccess); + } + + private void OnInteractUsing(EntityUid uid, MineableComponent component, InteractUsingEvent args) + { + if (!TryComp(args.Used, out var pickaxe)) + return; + + // Can't mine too many entities at once. + if (pickaxe.MaxMiningEntities < pickaxe.MiningEntities.Count + 1) + return; + + // Can't mine one object multiple times. + if (!pickaxe.MiningEntities.Add(uid)) + return; + + var doAfter = new DoAfterEventArgs(args.User, component.BaseMineTime * pickaxe.MiningTimeMultiplier, default, uid) + { + BreakOnDamage = true, + BreakOnStun = true, + BreakOnTargetMove = true, + BreakOnUserMove = true, + MovementThreshold = 0.5f, + BroadcastCancelledEvent = new MiningDoafterCancel() { Pickaxe = args.Used, Rock = uid }, + BroadcastFinishedEvent = new MiningDoafterSuccess() { Pickaxe = args.Used, Rock = uid } + }; + + _doAfterSystem.DoAfter(doAfter); + } + + private void OnDoafterSuccess(MiningDoafterSuccess ev) + { + if (!TryComp(ev.Pickaxe, out PickaxeComponent? pickaxe)) + return; + + _damageableSystem.TryChangeDamage(ev.Rock, pickaxe.Damage); + SoundSystem.Play(Filter.Pvs(ev.Rock), pickaxe.MiningSound.GetSound(), AudioParams.Default); + pickaxe.MiningEntities.Remove(ev.Rock); + } + + private void OnDoafterCancel(MiningDoafterCancel ev) + { + if (!TryComp(ev.Pickaxe, out PickaxeComponent? pickaxe)) + return; + + pickaxe.MiningEntities.Remove(ev.Rock); + } +} + +// grumble grumble +public class MiningDoafterSuccess : EntityEventArgs +{ + public EntityUid Pickaxe; + public EntityUid Rock; +} + +public class MiningDoafterCancel : EntityEventArgs +{ + public EntityUid Pickaxe; + public EntityUid Rock; +} diff --git a/Content.Server/RandomAppearance/RandomAppearanceComponent.cs b/Content.Server/RandomAppearance/RandomAppearanceComponent.cs new file mode 100644 index 0000000000..7a41d6fdd3 --- /dev/null +++ b/Content.Server/RandomAppearance/RandomAppearanceComponent.cs @@ -0,0 +1,40 @@ +using Robust.Shared.Analyzers; +using Robust.Shared.GameObjects; +using Robust.Shared.IoC; +using Robust.Shared.Log; +using Robust.Shared.Reflection; +using Robust.Shared.Serialization; +using Robust.Shared.Serialization.Manager.Attributes; + +namespace Content.Server.RandomAppearance; + +[RegisterComponent, ComponentProtoName("RandomAppearance")] +[Friend(typeof(RandomAppearanceSystem))] +public class RandomAppearanceComponent : Component, ISerializationHooks +{ + [DataField("spriteStates")] + public string[] SpriteStates = {"0", "1", "2", "3", "4"}; + + /// + /// What appearance enum key should be set to the random sprite state? + /// + [DataField("key", required: true)] + public string EnumKeyRaw = default!; + + /// + /// The actual enum after reflection. + /// + public System.Enum? EnumKey; + + void ISerializationHooks.AfterDeserialization() + { + if (IoCManager.Resolve().TryParseEnumReference(EnumKeyRaw, out var @enum)) + { + EnumKey = @enum; + } + else + { + Logger.Error($"RandomAppearance enum key {EnumKeyRaw} could not be parsed!"); + } + } +} diff --git a/Content.Server/RandomAppearance/RandomAppearanceSystem.cs b/Content.Server/RandomAppearance/RandomAppearanceSystem.cs new file mode 100644 index 0000000000..1fa8b94958 --- /dev/null +++ b/Content.Server/RandomAppearance/RandomAppearanceSystem.cs @@ -0,0 +1,27 @@ +using Robust.Shared.GameObjects; +using Robust.Shared.IoC; +using Robust.Shared.Log; +using Robust.Shared.Random; +using Robust.Shared.Reflection; + +namespace Content.Server.RandomAppearance; + +public class RandomAppearanceSystem : EntitySystem +{ + [Dependency] private readonly IRobustRandom _random = default!; + + public override void Initialize() + { + base.Initialize(); + + SubscribeLocalEvent(OnComponentInit); + } + + private void OnComponentInit(EntityUid uid, RandomAppearanceComponent component, ComponentInit args) + { + if (TryComp(uid, out AppearanceComponent? appearance) && component.EnumKey != null) + { + appearance.SetData(component.EnumKey, _random.Pick(component.SpriteStates)); + } + } +} diff --git a/Resources/Prototypes/Entities/Mobs/NPCs/animals.yml b/Resources/Prototypes/Entities/Mobs/NPCs/animals.yml index 51253cf3e7..ac56321f16 100644 --- a/Resources/Prototypes/Entities/Mobs/NPCs/animals.yml +++ b/Resources/Prototypes/Entities/Mobs/NPCs/animals.yml @@ -49,7 +49,9 @@ - type: MovementSpeedModifier baseWalkSpeed : 7 baseSprintSpeed : 7 - - type: AsteroidRock + - type: RandomAppearance + # relic + key: enum.AsteroidRockVisuals.State - type: Sprite drawdepth: Mobs layers: diff --git a/Resources/Prototypes/Entities/Objects/Weapons/Melee/pickaxe.yml b/Resources/Prototypes/Entities/Objects/Weapons/Melee/pickaxe.yml index c6507d1856..a0ea6b899c 100644 --- a/Resources/Prototypes/Entities/Objects/Weapons/Melee/pickaxe.yml +++ b/Resources/Prototypes/Entities/Objects/Weapons/Melee/pickaxe.yml @@ -8,12 +8,17 @@ sprite: Objects/Weapons/Melee/pickaxe.rsi state: pickaxe - type: Pickaxe + damage: + types: + Piercing: 25 - type: ItemCooldown - type: MeleeWeapon damage: types: Piercing: 10 Blunt: 4 + arcCooldownTime: 3 + - type: Item size: 24 sprite: Objects/Weapons/Melee/pickaxe.rsi diff --git a/Resources/Prototypes/Entities/Structures/Walls/asteroid.yml b/Resources/Prototypes/Entities/Structures/Walls/asteroid.yml index 235a302e1a..87fd718115 100644 --- a/Resources/Prototypes/Entities/Structures/Walls/asteroid.yml +++ b/Resources/Prototypes/Entities/Structures/Walls/asteroid.yml @@ -4,14 +4,14 @@ name: asteroid rock description: That's an asteroid components: - - type: AsteroidRock - - type: InteractionOutline + - type: RandomAppearance + key: enum.AsteroidRockVisuals.State + - type: Mineable - type: Sprite sprite: Structures/Walls/asteroid_rock.rsi state: 0 - type: Damageable damageContainer: Inorganic - damageModifierSet: Metallic - type: Destructible thresholds: - trigger: