diff --git a/Content.Server/Chemistry/Components/SolutionManager/RandomFillSolutionComponent.cs b/Content.Server/Chemistry/Components/SolutionManager/RandomFillSolutionComponent.cs index bf75df03a7..409665fb0d 100644 --- a/Content.Server/Chemistry/Components/SolutionManager/RandomFillSolutionComponent.cs +++ b/Content.Server/Chemistry/Components/SolutionManager/RandomFillSolutionComponent.cs @@ -1,5 +1,4 @@ using Content.Server.Chemistry.EntitySystems; -using Content.Shared.FixedPoint; using Content.Shared.Random; using Robust.Shared.Serialization.TypeSerializers.Implementations.Custom.Prototype; @@ -18,14 +17,8 @@ public sealed class RandomFillSolutionComponent : Component public string Solution { get; set; } = "default"; /// - /// Weighted random prototype Id. Used to pick reagent. + /// Weighted random fill prototype Id. Used to pick reagent and quantity. /// - [DataField("weightedRandomId", required: true, customTypeSerializer: typeof(PrototypeIdSerializer))] + [DataField("weightedRandomId", required: true, customTypeSerializer: typeof(PrototypeIdSerializer))] public string WeightedRandomId { get; set; } = "default"; - - /// - /// Amount of reagent to add. - /// - [DataField("quantity")] - public FixedPoint2 Quantity { get; set; } = 0; } diff --git a/Content.Server/Chemistry/EntitySystems/SolutionRandomFillSystem.cs b/Content.Server/Chemistry/EntitySystems/SolutionRandomFillSystem.cs index 0d653e1da9..78afda9910 100644 --- a/Content.Server/Chemistry/EntitySystems/SolutionRandomFillSystem.cs +++ b/Content.Server/Chemistry/EntitySystems/SolutionRandomFillSystem.cs @@ -20,18 +20,20 @@ public sealed class SolutionRandomFillSystem : EntitySystem SubscribeLocalEvent(OnRandomSolutionFillMapInit); } - public void OnRandomSolutionFillMapInit(EntityUid uid, RandomFillSolutionComponent component, MapInitEvent args) + private void OnRandomSolutionFillMapInit(EntityUid uid, RandomFillSolutionComponent component, MapInitEvent args) { var target = _solutionsSystem.EnsureSolution(uid, component.Solution); - var reagent = _proto.Index(component.WeightedRandomId).Pick(_random); + var pick = _proto.Index(component.WeightedRandomId).Pick(_random); - if (!_proto.TryIndex(reagent, out ReagentPrototype? reagentProto)) + var reagent = pick.reagent; + var quantity = pick.quantity; + + if (!_proto.HasIndex(reagent)) { - Logger.Error( - $"Tried to add invalid reagent Id {reagent} using SolutionRandomFill."); + Log.Error($"Tried to add invalid reagent Id {reagent} using SolutionRandomFill."); return; } - target.AddReagent(reagent, component.Quantity); + target.AddReagent(reagent, quantity); } } diff --git a/Content.Server/Gatherable/GatherableSystem.cs b/Content.Server/Gatherable/GatherableSystem.cs index b9a375d284..abee3e4549 100644 --- a/Content.Server/Gatherable/GatherableSystem.cs +++ b/Content.Server/Gatherable/GatherableSystem.cs @@ -25,25 +25,29 @@ public sealed partial class GatherableSystem : EntitySystem { base.Initialize(); + SubscribeLocalEvent(OnActivate); SubscribeLocalEvent(OnInteractUsing); SubscribeLocalEvent(OnDoAfter); InitializeProjectile(); } - private void OnInteractUsing(EntityUid uid, GatherableComponent component, InteractUsingEvent args) + private void Gather(EntityUid gatheredUid, EntityUid user, EntityUid used, GatheringToolComponent? tool = null, GatherableComponent? component = null) { - if (!TryComp(args.Used, out var tool) || component.ToolWhitelist?.IsValid(args.Used) == false) + if (!Resolve(used, ref tool, false) || !Resolve(gatheredUid, ref component, false) || + component.ToolWhitelist?.IsValid(used) == false) + { return; + } // Can't gather too many entities at once. if (tool.MaxGatheringEntities < tool.GatheringEntities.Count + 1) return; - var damageRequired = _destructible.DestroyedAt(uid); + var damageRequired = _destructible.DestroyedAt(gatheredUid); var damageTime = (damageRequired / tool.Damage.Total).Float(); damageTime = Math.Max(1f, damageTime); - var doAfter = new DoAfterArgs(args.User, damageTime, new GatherableDoAfterEvent(), uid, target: uid, used: args.Used) + var doAfter = new DoAfterArgs(user, damageTime, new GatherableDoAfterEvent(), gatheredUid, target: gatheredUid, used: used) { BreakOnDamage = true, BreakOnTargetMove = true, @@ -54,6 +58,16 @@ public sealed partial class GatherableSystem : EntitySystem _doAfterSystem.TryStartDoAfter(doAfter); } + private void OnActivate(EntityUid uid, GatherableComponent component, ActivateInWorldEvent args) + { + Gather(uid, args.User, args.User); + } + + private void OnInteractUsing(EntityUid uid, GatherableComponent component, InteractUsingEvent args) + { + Gather(uid, args.User, args.Used, component: component); + } + private void OnDoAfter(EntityUid uid, GatherableComponent component, GatherableDoAfterEvent args) { if(!TryComp(args.Args.Used, out var tool)) diff --git a/Content.Shared/Random/Helpers/SharedRandomExtensions.cs b/Content.Shared/Random/Helpers/SharedRandomExtensions.cs index 4544bf65fa..d68fac2509 100644 --- a/Content.Shared/Random/Helpers/SharedRandomExtensions.cs +++ b/Content.Shared/Random/Helpers/SharedRandomExtensions.cs @@ -1,5 +1,6 @@ using System.Linq; using Content.Shared.Dataset; +using Content.Shared.FixedPoint; using Robust.Shared.Random; namespace Content.Shared.Random.Helpers @@ -75,5 +76,61 @@ namespace Content.Shared.Random.Helpers throw new InvalidOperationException($"Invalid weighted pick"); } + + public static (string reagent, FixedPoint2 quantity) Pick(this WeightedRandomFillSolutionPrototype prototype, IRobustRandom? random = null) + { + var randomFill = prototype.PickRandomFill(random); + + IoCManager.Resolve(ref random); + + var sum = randomFill.Reagents.Count; + var accumulated = 0f; + + var rand = random.NextFloat() * sum; + + foreach (var reagent in randomFill.Reagents) + { + accumulated += 1f; + + if (accumulated >= rand) + { + return (reagent, randomFill.Quantity); + } + } + + // Shouldn't happen + throw new InvalidOperationException($"Invalid weighted pick for {prototype.ID}!"); + } + + public static RandomFillSolution PickRandomFill(this WeightedRandomFillSolutionPrototype prototype, IRobustRandom? random = null) + { + IoCManager.Resolve(ref random); + + var fills = prototype.Fills; + Dictionary picks = new(); + + foreach (var fill in fills) + { + picks[fill] = fill.Weight; + } + + var sum = picks.Values.Sum(); + var accumulated = 0f; + + var rand = random.NextFloat() * sum; + + foreach (var (randSolution, weight) in picks) + { + accumulated += weight; + + if (accumulated >= rand) + { + return randSolution; + } + } + + // Shouldn't happen + throw new InvalidOperationException($"Invalid weighted pick for {prototype.ID}!"); + } } } diff --git a/Content.Shared/Random/RandomFillSolution.cs b/Content.Shared/Random/RandomFillSolution.cs new file mode 100644 index 0000000000..acbd97db87 --- /dev/null +++ b/Content.Shared/Random/RandomFillSolution.cs @@ -0,0 +1,33 @@ +using Content.Shared.Chemistry.Reagent; +using Content.Shared.FixedPoint; +using Robust.Shared.Serialization; +using Robust.Shared.Serialization.TypeSerializers.Implementations.Custom.Prototype; +using Robust.Shared.Serialization.TypeSerializers.Implementations.Custom.Prototype.List; + +namespace Content.Shared.Random; + +/// +/// Data that specifies reagents that share the same weight and quantity for use with WeightedRandomSolution. +/// +[Serializable, NetSerializable] +[DataDefinition] +public sealed class RandomFillSolution +{ + /// + /// Quantity of listed reagents. + /// + [DataField("quantity")] + public FixedPoint2 Quantity = 0; + + /// + /// Random weight of listed reagents. + /// + [DataField("weight")] + public float Weight = 0; + + /// + /// Listed reagents that the weight and quantity apply to. + /// + [DataField("reagents", required: true, customTypeSerializer: typeof(PrototypeIdListSerializer))] + public List Reagents = new(); +} diff --git a/Content.Shared/Random/WeightedRandomFillSolutionPrototype.cs b/Content.Shared/Random/WeightedRandomFillSolutionPrototype.cs new file mode 100644 index 0000000000..8a6f133d09 --- /dev/null +++ b/Content.Shared/Random/WeightedRandomFillSolutionPrototype.cs @@ -0,0 +1,18 @@ +using Robust.Shared.Prototypes; + +namespace Content.Shared.Random; + +/// +/// Random weighting dataset for solutions, able to specify reagents quantity. +/// +[Prototype("weightedRandomFillSolution")] +public sealed class WeightedRandomFillSolutionPrototype : IPrototype +{ + [IdDataField] public string ID { get; } = default!; + + /// + /// List of RandomFills that can be picked from. + /// + [DataField("fills", required: true)] + public List Fills = new(); +} diff --git a/Resources/Locale/en-US/flavors/flavor-profiles.ftl b/Resources/Locale/en-US/flavors/flavor-profiles.ftl index 54e763ae6c..ebf341ed18 100644 --- a/Resources/Locale/en-US/flavors/flavor-profiles.ftl +++ b/Resources/Locale/en-US/flavors/flavor-profiles.ftl @@ -42,6 +42,7 @@ flavor-base-cheap = cheap flavor-base-piquant = piquant flavor-base-sharp = sharp flavor-base-syrupy = syrupy +flavor-base-spaceshroom = mysterious # lmao flavor-base-terrible = terrible @@ -145,6 +146,7 @@ flavor-complex-electrons = like electrons flavor-complex-parents = like someone's parents flavor-complex-plastic = like plastic flavor-complex-glue = like glue +flavor-complex-spaceshroom-cooked = like space umami # Drink-specific flavors. diff --git a/Resources/Prototypes/Entities/Markers/Spawners/Random/maintenance.yml b/Resources/Prototypes/Entities/Markers/Spawners/Random/maintenance.yml index 8f0f576dfd..c1a1d13500 100644 --- a/Resources/Prototypes/Entities/Markers/Spawners/Random/maintenance.yml +++ b/Resources/Prototypes/Entities/Markers/Spawners/Random/maintenance.yml @@ -131,3 +131,20 @@ - ClothingEyesBlindfold chance: 0.6 offset: 0.0 + +- type: entity + name: Maint Loot Spawner + suffix: Plants + id: MaintenancePlantSpawner + parent: MarkerBase + components: + - type: Sprite + layers: + - state: red + - sprite: Objects/Misc/spaceshroom.rsi + state: spaceshroom + - type: RandomSpawner + prototypes: + - FoodSpaceshroom + chance: 0.6 + offset: 0.0 diff --git a/Resources/Prototypes/Entities/Mobs/Species/base.yml b/Resources/Prototypes/Entities/Mobs/Species/base.yml index ebbd76d7a5..1746e091e5 100644 --- a/Resources/Prototypes/Entities/Mobs/Species/base.yml +++ b/Resources/Prototypes/Entities/Mobs/Species/base.yml @@ -34,6 +34,10 @@ - type: Polymorphable - type: Identity - type: Hands + - type: GatheringTool + damage: + types: + Structural: 50 - type: MovementSpeedModifier - type: MovedByPressure - type: Barotrauma diff --git a/Resources/Prototypes/Entities/Objects/Misc/spaceshroom.yml b/Resources/Prototypes/Entities/Objects/Misc/spaceshroom.yml new file mode 100644 index 0000000000..9ec6ce0ed1 --- /dev/null +++ b/Resources/Prototypes/Entities/Objects/Misc/spaceshroom.yml @@ -0,0 +1,123 @@ +- type: entity + name: spaceshroom + parent: BaseStructure + id: Spaceshroom + suffix: Structure + description: A cluster of wild mushrooms that likes to grow in dark, moist environments. + components: + - type: Sprite + sprite: Objects/Misc/spaceshroom.rsi + state: structure + - type: Transform + anchored: true + - type: Physics + bodyType: Static + - type: Fixtures + fixtures: + fix1: + shape: + !type:PhysShapeCircle + radius: 0.2 + - type: InteractionOutline + # TODO: Nuke this shit + - type: OreVein + oreChance: 1.0 + currentOre: SpaceShrooms + - type: Gatherable + whitelist: + components: + - Hands + - type: Damageable + damageContainer: Inorganic + damageModifierSet: Wood + - type: Destructible + thresholds: + - trigger: + !type:DamageTrigger + damage: 150 + behaviors: + - !type:DoActsBehavior + acts: [ "Destruction" ] + +- type: entity + name: spaceshroom + parent: FoodProduceBase + id: FoodSpaceshroom + description: A wild mushroom. There's no telling what effect it could have... + components: + - type: Produce + - type: Sprite + sprite: Objects/Misc/spaceshroom.rsi + state: object + - type: FlavorProfile + flavors: + - spaceshroom + - type: Extractable + juiceSolution: + reagents: + - ReagentId: SpaceDrugs + Quantity: 10 + - type: SolutionContainerManager + solutions: + food: + maxVol: 20 + reagents: + - ReagentId: Nutriment + Quantity: 3 + - ReagentId: Vitamin + Quantity: 2 + - type: RandomFillSolution + solution: food + weightedRandomId: RandomFillSpaceshroom + - type: StaticPrice + price: 20 + +- type: weightedRandomFillSolution + id: RandomFillSpaceshroom + fills: + - quantity: 10 + weight: 10 + reagents: + - SpaceDrugs + - quantity: 0 + weight: 5 + reagents: + - Water + - quantity: 10 + weight: 3 + reagents: + - Ephedrine + - quantity: 10 + weight: 1 + reagents: + - Lexorin + - quantity: 15 + weight: 1 + reagents: + - Amatoxin + +# Cooked Object +- type: entity + name: cooked spaceshroom + parent: FoodProduceBase + id: FoodSpaceshroomCooked + description: A wild mushroom that has been cooked through. It seems the heat has removed its chemical effects. + components: + - type: FlavorProfile + flavors: + - spaceshroomcooked + - type: SolutionContainerManager + solutions: + food: + maxVol: 15 + reagents: + - ReagentId: Nutriment + Quantity: 10 + - ReagentId: Vitamin + Quantity: 5 + - type: Sprite + sprite: Objects/Misc/spaceshroom.rsi + state: spaceshroom_cooked + - type: Produce + - type: StaticPrice + price: 40 diff --git a/Resources/Prototypes/Entities/Objects/Specific/Medical/randompill.yml b/Resources/Prototypes/Entities/Objects/Specific/Medical/randompill.yml index 14bc13343b..fcd738f0db 100644 --- a/Resources/Prototypes/Entities/Objects/Specific/Medical/randompill.yml +++ b/Resources/Prototypes/Entities/Objects/Specific/Medical/randompill.yml @@ -1,38 +1,46 @@ -- type: weightedRandom +- type: weightedRandomFillSolution id: RandomFillStrangePill - weights: - # Elements - Aluminium: 1 - Carbon: 1 - Chlorine: 1 - Copper: 1 - Fluorine: 1 - Hydrogen: 1 - Iodine: 1 - Lithium: 1 - Mercury: 1 - Potassium: 1 - Phosphorus: 1 - Radium: 1 - Silicon: 1 - Sulfur: 1 - Sodium: 1 - # Medicines - Ipecac: 3 - Omnizine: 2 - Tricordrazine: 3 - # Narcotics - Desoxyephedrine: 3 - Ephedrine: 3 - SpaceDrugs: 5 - Nocturine: 3 - MuteToxin: 3 - NorepinephricAcid: 3 - # Toxins - ChloralHydrate: 3 - Mold: 3 - Pax: 3 - Toxin: 5 + fills: + - quantity: 20 + weight: 1 + reagents: + - Aluminium + - Carbon + - Chlorine + - Copper + - Fluorine + - Hydrogen + - Iodine + - Lithium + - Mercury + - Potassium + - Phosphorus + - Radium + - Silicon + - Sulfur + - Sodium + - quantity: 20 + weight: 2 + reagents: + - Omnizine + - quantity: 20 + weight: 3 + reagents: + - ChloralHydrate + - Desoxyephedrine + - Ephedrine + - Ipecac + - Mold + - MuteToxin + - Nocturine + - NorepinephricAcid + - Pax + - Tricordrazine + - quantity: 20 + weight: 5 + reagents: + - SpaceDrugs + - Toxin - type: entity name: strange pill @@ -47,7 +55,6 @@ - type: RandomFillSolution solution: food weightedRandomId: RandomFillStrangePill - quantity: 20 - type: Sprite sprite: Objects/Specific/Chemistry/pills.rsi layers: diff --git a/Resources/Prototypes/Flavors/flavors.yml b/Resources/Prototypes/Flavors/flavors.yml index 7e61b24334..ab82a13fcb 100644 --- a/Resources/Prototypes/Flavors/flavors.yml +++ b/Resources/Prototypes/Flavors/flavors.yml @@ -823,3 +823,13 @@ id: rocksandstones flavorType: Complex description: flavor-complex-rocksandstones + +- type: flavor + id: spaceshroom + flavorType: Base + description: flavor-base-spaceshroom + +- type: flavor + id: spaceshroomcooked + flavorType: Complex + description: flavor-complex-spaceshroom-cooked \ No newline at end of file diff --git a/Resources/Prototypes/Recipes/Cooking/meal_recipes.yml b/Resources/Prototypes/Recipes/Cooking/meal_recipes.yml index 2cb601ccd7..0b26f65dcc 100644 --- a/Resources/Prototypes/Recipes/Cooking/meal_recipes.yml +++ b/Resources/Prototypes/Recipes/Cooking/meal_recipes.yml @@ -1632,3 +1632,11 @@ solids: FoodChevreSlice: 1 FoodBreadBaguetteSlice: 1 + +- type: microwaveMealRecipe + id: RecipeCookedSpaceshroom + name: cooked spaceshroom recipe + result: FoodSpaceshroomCooked + time: 5 + solids: + FoodSpaceshroom: 1 \ No newline at end of file diff --git a/Resources/Prototypes/ore.yml b/Resources/Prototypes/ore.yml index 33f17a5e01..b17e0839a2 100644 --- a/Resources/Prototypes/ore.yml +++ b/Resources/Prototypes/ore.yml @@ -1,3 +1,10 @@ +# TODO: Kill ore veins +# Split it into 2 components, 1 for "spawn XYZ on destruction" and 1 for "randomly select one of these for spawn on destruction" +# You could even just use an entityspawncollection instead. +- type: ore + id: SpaceShrooms + oreEntity: FoodSpaceshroom + # High yields - type: ore id: OreSteel diff --git a/Resources/Textures/Objects/Misc/spaceshroom.rsi/meta.json b/Resources/Textures/Objects/Misc/spaceshroom.rsi/meta.json new file mode 100644 index 0000000000..46d9ba04e4 --- /dev/null +++ b/Resources/Textures/Objects/Misc/spaceshroom.rsi/meta.json @@ -0,0 +1,20 @@ +{ + "version": 1, + "license": "CC0-1.0", + "copyright": "Created by @UbaserB (GitHub)", + "size": { + "x": 32, + "y": 32 + }, + "states": [ + { + "name": "object" + }, + { + "name": "spaceshroom_cooked" + }, + { + "name": "structure" + } + ] +} \ No newline at end of file diff --git a/Resources/Textures/Objects/Misc/spaceshroom.rsi/object.png b/Resources/Textures/Objects/Misc/spaceshroom.rsi/object.png new file mode 100644 index 0000000000..6a5b84993d Binary files /dev/null and b/Resources/Textures/Objects/Misc/spaceshroom.rsi/object.png differ diff --git a/Resources/Textures/Objects/Misc/spaceshroom.rsi/spaceshroom_cooked.png b/Resources/Textures/Objects/Misc/spaceshroom.rsi/spaceshroom_cooked.png new file mode 100644 index 0000000000..633d4a2675 Binary files /dev/null and b/Resources/Textures/Objects/Misc/spaceshroom.rsi/spaceshroom_cooked.png differ diff --git a/Resources/Textures/Objects/Misc/spaceshroom.rsi/structure.png b/Resources/Textures/Objects/Misc/spaceshroom.rsi/structure.png new file mode 100644 index 0000000000..c23bed8ee0 Binary files /dev/null and b/Resources/Textures/Objects/Misc/spaceshroom.rsi/structure.png differ