diff --git a/Content.Client/Entry/IgnoredComponents.cs b/Content.Client/Entry/IgnoredComponents.cs index 997bffad24..48256031b0 100644 --- a/Content.Client/Entry/IgnoredComponents.cs +++ b/Content.Client/Entry/IgnoredComponents.cs @@ -7,7 +7,6 @@ namespace Content.Client.Entry { "AirlockPainter", "AmmoBox", - "Pickaxe", "IngestionBlocker", "Charger", "CloningPod", @@ -49,7 +48,6 @@ namespace Content.Client.Entry "DiseaseZombie", "DiseaseBuildup", "ZombieTransfer", - "Mineable", "RangedMagazine", "RandomMetadata", "Ammo", @@ -307,6 +305,7 @@ namespace Content.Client.Entry "ExtensionCableReceiver", "ExtensionCableProvider", "ApcNetworkConnection", + "Gatherable", "SuitSensor", "CrewMonitoringConsole", "ApcNetSwitch", @@ -321,6 +320,7 @@ namespace Content.Client.Entry "FireAlarm", "AirAlarm", "RadarConsole", + "GatheringTool", "Guardian", "GuardianCreator", "GuardianHost", diff --git a/Content.Server/Gatherable/Components/GatherableComponent.cs b/Content.Server/Gatherable/Components/GatherableComponent.cs new file mode 100644 index 0000000000..bb9b0be9c1 --- /dev/null +++ b/Content.Server/Gatherable/Components/GatherableComponent.cs @@ -0,0 +1,33 @@ +using Content.Shared.EntityList; +using Content.Shared.Whitelist; + +namespace Content.Server.Gatherable.Components; + +[RegisterComponent] +[Friend(typeof(GatherableSystem))] +public sealed class GatherableComponent : Component +{ + /// + /// Whitelist for specifying the kind of tools can be used on a resource + /// Supports multiple tags. + /// + [ViewVariables] + [DataField("whitelist", required: true)] + public EntityWhitelist? ToolWhitelist; + + /// + /// YAML example below + /// (Tag1, Tag2, LootTableID1, LootTableID2 are placeholders for example) + /// -------------------- + /// useMappedLoot: true + /// whitelist: + /// tags: + /// - Tag1 + /// - Tag2 + /// mappedLoot: + /// Tag1: LootTableID1 + /// Tag2: LootTableID2 + /// + [DataField("loot")] + public Dictionary? MappedLoot = new(); +} diff --git a/Content.Server/Gatherable/Components/GatheringToolComponent.cs b/Content.Server/Gatherable/Components/GatheringToolComponent.cs new file mode 100644 index 0000000000..14e9888d41 --- /dev/null +++ b/Content.Server/Gatherable/Components/GatheringToolComponent.cs @@ -0,0 +1,44 @@ +using System.Threading; +using Content.Shared.Damage; +using Content.Shared.Sound; + +namespace Content.Server.Gatherable.Components +{ + /// + /// When interacting with an allows it to spawn entities. + /// + [RegisterComponent] + public sealed class GatheringToolComponent : Component + { + /// + /// Sound that is made once you completed gathering + /// + [ViewVariables(VVAccess.ReadWrite)] + [DataField("sound")] + public SoundSpecifier GatheringSound { get; set; } = new SoundPathSpecifier("/Audio/Items/Mining/pickaxe.ogg"); + + /// + /// This directly plugs into the time delay for gathering. + /// + [ViewVariables(VVAccess.ReadWrite)] + [DataField("gatheringTime")] + public float GatheringTime { get; set; } = 1f; + + /// + /// What damage should be given to objects when + /// gathered using this tool? (0 for infinite gathering) + /// + [DataField("damage", required: true)] + public DamageSpecifier Damage { get; set; } = default!; + + /// + /// How many entities can this tool gather from at once? + /// + [ViewVariables(VVAccess.ReadWrite)] + [DataField("maxEntities")] + public int MaxGatheringEntities = 1; + + [ViewVariables] + public readonly Dictionary GatheringEntities = new(); + } +} diff --git a/Content.Server/Gatherable/GatherableSystem.cs b/Content.Server/Gatherable/GatherableSystem.cs new file mode 100644 index 0000000000..e55667b374 --- /dev/null +++ b/Content.Server/Gatherable/GatherableSystem.cs @@ -0,0 +1,111 @@ +using System.Threading; +using Content.Server.DoAfter; +using Content.Server.Gatherable.Components; +using Content.Shared.Damage; +using Content.Shared.EntityList; +using Content.Shared.Interaction; +using Content.Shared.Tag; +using Robust.Shared.Audio; +using Robust.Shared.Player; +using Robust.Shared.Prototypes; +using Robust.Shared.Random; + +namespace Content.Server.Gatherable; + +public sealed class GatherableSystem : EntitySystem +{ + [Dependency] private readonly DoAfterSystem _doAfterSystem = default!; + [Dependency] private readonly DamageableSystem _damageableSystem = default!; + [Dependency] private readonly IRobustRandom _random = null!; + [Dependency] private readonly TagSystem _tagSystem = Get(); + [Dependency] private readonly IPrototypeManager _prototypeManager = default!; + + public override void Initialize() + { + base.Initialize(); + + SubscribeLocalEvent(OnInteractUsing); + SubscribeLocalEvent(OnDoafterCancel); + SubscribeLocalEvent(OnDoafterSuccess); + } + + private void OnInteractUsing(EntityUid uid, GatherableComponent component, InteractUsingEvent args) + { + if (!TryComp(args.Used, out var tool) || + component.ToolWhitelist?.IsValid(args.Used) == false || + tool.GatheringEntities.TryGetValue(uid, out var cancelToken)) + return; + + // Can't gather too many entities at once. + if (tool.MaxGatheringEntities < tool.GatheringEntities.Count + 1) + return; + + cancelToken = new CancellationTokenSource(); + tool.GatheringEntities[uid] = cancelToken; + + var doAfter = new DoAfterEventArgs(args.User, tool.GatheringTime, cancelToken.Token, uid) + { + BreakOnDamage = true, + BreakOnStun = true, + BreakOnTargetMove = true, + BreakOnUserMove = true, + MovementThreshold = 0.25f, + BroadcastCancelledEvent = new GatheringDoafterCancel { Tool = args.Used, Resource = uid }, + TargetFinishedEvent = new GatheringDoafterSuccess { Tool = args.Used, Resource = uid, Player = args.User } + }; + + _doAfterSystem.DoAfter(doAfter); + } + + private void OnDoafterSuccess(EntityUid uid, GatherableComponent component, GatheringDoafterSuccess ev) + { + if (!TryComp(ev.Tool, out GatheringToolComponent? tool)) + return; + + // Complete the gathering process + _damageableSystem.TryChangeDamage(ev.Resource, tool.Damage); + SoundSystem.Play(Filter.Pvs(ev.Resource, entityManager: EntityManager), tool.GatheringSound.GetSound(), ev.Resource); + tool.GatheringEntities.Remove(ev.Resource); + + // Spawn the loot! + if (component.MappedLoot == null) return; + + var playerPos = Transform(ev.Player).MapPosition; + + foreach (var (tag, table) in component.MappedLoot) + { + if (tag != "All") + { + if (!_tagSystem.HasTag(tool.Owner, tag)) continue; + } + var getLoot = _prototypeManager.Index(table); + var spawnLoot = getLoot.GetSpawns(); + var spawnPos = playerPos.Offset(_random.NextVector2(0.3f)); + Spawn(spawnLoot[0], spawnPos); + } + } + + private void OnDoafterCancel(GatheringDoafterCancel ev) + { + if (!TryComp(ev.Tool, out var tool)) + return; + + tool.GatheringEntities.Remove(ev.Resource); + } + + private sealed class GatheringDoafterCancel : EntityEventArgs + { + public EntityUid Tool; + public EntityUid Resource; + } + + private sealed class GatheringDoafterSuccess : EntityEventArgs + { + public EntityUid Tool; + public EntityUid Resource; + public EntityUid Player; + } +} + + + diff --git a/Content.Server/Mining/Components/MineableComponent.cs b/Content.Server/Mining/Components/MineableComponent.cs deleted file mode 100644 index 0368bcc9e1..0000000000 --- a/Content.Server/Mining/Components/MineableComponent.cs +++ /dev/null @@ -1,12 +0,0 @@ -using System.Threading; -using Content.Shared.Storage; - -namespace Content.Server.Mining.Components; - -[RegisterComponent] -[Friend(typeof(MineableSystem))] -public sealed class MineableComponent : Component -{ - [DataField("ores")] public List Ores = new(); - public float BaseMineTime = 1.0f; -} diff --git a/Content.Server/Mining/Components/PickaxeComponent.cs b/Content.Server/Mining/Components/PickaxeComponent.cs deleted file mode 100644 index 181ed2757a..0000000000 --- a/Content.Server/Mining/Components/PickaxeComponent.cs +++ /dev/null @@ -1,38 +0,0 @@ -using System.Threading; -using Content.Shared.Damage; -using Content.Shared.Sound; - -namespace Content.Server.Mining.Components -{ - /// - /// When interacting with an allows it to spawn entities. - /// - [RegisterComponent] - public sealed class PickaxeComponent : Component - { - [ViewVariables(VVAccess.ReadWrite)] - [DataField("sound")] - public SoundSpecifier MiningSound { get; set; } = new SoundPathSpecifier("/Audio/Items/Mining/pickaxe.ogg"); - - [ViewVariables(VVAccess.ReadWrite)] - [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? - /// - [ViewVariables(VVAccess.ReadWrite)] - [DataField("maxEntities")] - public int MaxMiningEntities = 1; - - [ViewVariables] - public readonly Dictionary MiningEntities = new(); - } -} diff --git a/Content.Server/Mining/MineableSystem.cs b/Content.Server/Mining/MineableSystem.cs deleted file mode 100644 index 7b25f1e9f5..0000000000 --- a/Content.Server/Mining/MineableSystem.cs +++ /dev/null @@ -1,99 +0,0 @@ -using System.Threading; -using Content.Server.DoAfter; -using Content.Server.Mining.Components; -using Content.Shared.Damage; -using Content.Shared.Interaction; -using Content.Shared.Storage; -using Robust.Shared.Audio; -using Robust.Shared.Player; -using Robust.Shared.Random; - -namespace Content.Server.Mining; - -public sealed class MineableSystem : EntitySystem -{ - [Dependency] private readonly DoAfterSystem _doAfterSystem = default!; - [Dependency] private readonly DamageableSystem _damageableSystem = default!; - [Dependency] private readonly IRobustRandom _random = null!; - - 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; - - if (pickaxe.MiningEntities.TryGetValue(uid, out var cancelToken)) - { - cancelToken.Cancel(); - pickaxe.MiningEntities.Remove(uid); - return; - } - - // Can't mine too many entities at once. - if (pickaxe.MaxMiningEntities < pickaxe.MiningEntities.Count + 1) - return; - - cancelToken = new CancellationTokenSource(); - pickaxe.MiningEntities[uid] = cancelToken; - - var doAfter = new DoAfterEventArgs(args.User, component.BaseMineTime * pickaxe.MiningTimeMultiplier, cancelToken.Token, uid) - { - BreakOnDamage = true, - BreakOnStun = true, - BreakOnTargetMove = true, - BreakOnUserMove = true, - MovementThreshold = 0.25f, - BroadcastCancelledEvent = new MiningDoafterCancel { Pickaxe = args.Used, Rock = uid }, - TargetFinishedEvent = new MiningDoafterSuccess { Pickaxe = args.Used, Rock = uid, Player = args.User } - }; - - _doAfterSystem.DoAfter(doAfter); - } - - private void OnDoafterSuccess(EntityUid uid, MineableComponent component, MiningDoafterSuccess ev) - { - if (!TryComp(ev.Pickaxe, out PickaxeComponent? pickaxe)) - return; - - _damageableSystem.TryChangeDamage(ev.Rock, pickaxe.Damage); - SoundSystem.Play(Filter.Pvs(ev.Rock, entityManager: EntityManager), pickaxe.MiningSound.GetSound(), ev.Rock); - pickaxe.MiningEntities.Remove(ev.Rock); - - var spawnOre = EntitySpawnCollection.GetSpawns(component.Ores, _random); - var playerPos = Transform(ev.Player).MapPosition; - var spawnPos = playerPos.Offset(_random.NextVector2(0.3f)); - EntityManager.SpawnEntity(spawnOre[0], spawnPos); - pickaxe.MiningEntities.Remove(uid); - } - - private void OnDoafterCancel(MiningDoafterCancel ev) - { - if (!TryComp(ev.Pickaxe, out var pickaxe)) - return; - - pickaxe.MiningEntities.Remove(ev.Rock); - } - - private sealed class MiningDoafterCancel : EntityEventArgs - { - public EntityUid Pickaxe; - public EntityUid Rock; - } -} - -// grumble grumble -public sealed class MiningDoafterSuccess : EntityEventArgs -{ - public EntityUid Pickaxe; - public EntityUid Rock; - public EntityUid Player; -} - diff --git a/Resources/Prototypes/Entities/Objects/Misc/paper.yml b/Resources/Prototypes/Entities/Objects/Misc/paper.yml index ac588ce999..5ffeb25795 100644 --- a/Resources/Prototypes/Entities/Objects/Misc/paper.yml +++ b/Resources/Prototypes/Entities/Objects/Misc/paper.yml @@ -85,14 +85,11 @@ - type: Tag tags: - Write + - Pickaxe - type: Sprite sprite: Objects/Misc/bureaucracy.rsi state: overpriced_pen netsync: false - - type: Pickaxe - damage: - types: - Piercing: 5 - type: ItemCooldown - type: MeleeWeapon damage: diff --git a/Resources/Prototypes/Entities/Objects/Weapons/Melee/pickaxe.yml b/Resources/Prototypes/Entities/Objects/Weapons/Melee/pickaxe.yml index 6a6993e02a..f3a8036caf 100644 --- a/Resources/Prototypes/Entities/Objects/Weapons/Melee/pickaxe.yml +++ b/Resources/Prototypes/Entities/Objects/Weapons/Melee/pickaxe.yml @@ -4,10 +4,13 @@ id: Pickaxe description: Notched to perfection, for jamming it into rocks components: + - type: Tag + tags: + - Pickaxe - type: Sprite sprite: Objects/Weapons/Melee/pickaxe.rsi state: pickaxe - - type: Pickaxe + - type: GatheringTool damage: types: Piercing: 25 @@ -29,18 +32,21 @@ id: MiningDrill description: Powerful tool used to quickly drill through rocks components: - - type: Sprite - sprite: Objects/Tools/handdrill.rsi - state: handdrill - - type: Pickaxe - damage: - types: - Piercing: 25 - timeMultiplier: 0.75 - - type: ItemCooldown - - type: MeleeWeapon - damage: - types: - Piercing: 10 - Blunt: 4 - arcCooldownTime: 3 + - type: Tag + tags: + - Pickaxe + - type: Sprite + sprite: Objects/Tools/handdrill.rsi + state: handdrill + - type: GatheringTool + damage: + types: + Piercing: 25 + gatheringTime: 0.75 + - type: ItemCooldown + - type: MeleeWeapon + damage: + types: + Piercing: 10 + Blunt: 4 + arcCooldownTime: 3 diff --git a/Resources/Prototypes/Entities/Structures/Walls/asteroid.yml b/Resources/Prototypes/Entities/Structures/Walls/asteroid.yml index f3eaad3fd5..7e0a25cfad 100644 --- a/Resources/Prototypes/Entities/Structures/Walls/asteroid.yml +++ b/Resources/Prototypes/Entities/Structures/Walls/asteroid.yml @@ -4,26 +4,12 @@ name: asteroid rock description: An asteroid. components: - - type: Mineable - ores: - - id: SteelOre1 - prob: 0.25 - orGroup: Asteroid - - id: GoldOre1 - prob: 0.05 - orGroup: Asteroid - - id: SpaceQuartz1 - prob: 0.20 - orGroup: Asteroid - - id: PlasmaOre1 - prob: 0.10 - orGroup: Asteroid - - id: SilverOre1 - prob: 0.025 - orGroup: Asteroid - - id: UraniumOre1 - prob: 0.025 - orGroup: Asteroid + - type: Gatherable + whitelist: + tags: + - Pickaxe + loot: + Pickaxe: MiningLootTable - type: Sprite sprite: Structures/Walls/asteroid_rock.rsi state: full diff --git a/Resources/Prototypes/LootTables/mining_loot_table.yml b/Resources/Prototypes/LootTables/mining_loot_table.yml new file mode 100644 index 0000000000..34eb4192eb --- /dev/null +++ b/Resources/Prototypes/LootTables/mining_loot_table.yml @@ -0,0 +1,21 @@ +- type: entityLootTable + id: MiningLootTable + entries: + - id: SteelOre1 + prob: 0.25 + orGroup: Asteroid + - id: GoldOre1 + prob: 0.05 + orGroup: Asteroid + - id: SpaceQuartz1 + prob: 0.20 + orGroup: Asteroid + - id: PlasmaOre1 + prob: 0.10 + orGroup: Asteroid + - id: SilverOre1 + prob: 0.025 + orGroup: Asteroid + - id: UraniumOre1 + prob: 0.025 + orGroup: Asteroid \ No newline at end of file diff --git a/Resources/Prototypes/tags.yml b/Resources/Prototypes/tags.yml index a7fd26174b..3d8add8cc6 100644 --- a/Resources/Prototypes/tags.yml +++ b/Resources/Prototypes/tags.yml @@ -244,7 +244,7 @@ id: PercussionInstrument - type: Tag - id: Plastic + id: Pickaxe - type: Tag id: Pill @@ -266,6 +266,9 @@ - type: Tag id: PlantSampleTaker + +- type: Tag + id: Plastic - type: Tag id: Powerdrill