Move spawning collections of EntitySpawnEntry out of StorageSystem, make Butcherable use it (#7305)

Co-authored-by: metalgearsloth <31366439+metalgearsloth@users.noreply.github.com>
This commit is contained in:
mirrorcult
2022-03-28 09:58:13 -07:00
committed by GitHub
parent 2f9f626ea9
commit 2d610ebb52
24 changed files with 316 additions and 253 deletions

View File

@@ -1,4 +1,5 @@
using Content.Server.Storage; using Content.Server.Storage;
using Content.Shared.Storage;
namespace Content.Server.Drone.Components namespace Content.Server.Drone.Components
{ {

View File

@@ -20,6 +20,8 @@ using Content.Server.Hands.Components;
using Content.Server.UserInterface; using Content.Server.UserInterface;
using Robust.Shared.Player; using Robust.Shared.Player;
using Content.Shared.Hands.EntitySystems; using Content.Shared.Hands.EntitySystems;
using Content.Shared.Storage;
using Robust.Shared.Random;
using Robust.Shared.Timing; using Robust.Shared.Timing;
namespace Content.Server.Drone namespace Content.Server.Drone
@@ -32,6 +34,7 @@ namespace Content.Server.Drone
[Dependency] private readonly SharedHandsSystem _handsSystem = default!; [Dependency] private readonly SharedHandsSystem _handsSystem = default!;
[Dependency] private readonly SharedActionsSystem _actionsSystem = default!; [Dependency] private readonly SharedActionsSystem _actionsSystem = default!;
[Dependency] private readonly IGameTiming _gameTiming = default!; [Dependency] private readonly IGameTiming _gameTiming = default!;
[Dependency] private readonly IRobustRandom _robustRandom = default!;
public override void Initialize() public override void Initialize()
{ {
@@ -120,9 +123,10 @@ namespace Content.Server.Drone
if (TryComp<HandsComponent>(uid, out var hands) && hands.Count >= drone.Tools.Count) if (TryComp<HandsComponent>(uid, out var hands) && hands.Count >= drone.Tools.Count)
{ {
foreach (var entry in drone.Tools) var items = EntitySpawnCollection.GetSpawns(drone.Tools, _robustRandom);
foreach (var entry in items)
{ {
var item = EntityManager.SpawnEntity(entry.PrototypeId, spawnCoord); var item = EntityManager.SpawnEntity(entry, spawnCoord);
AddComp<UnremoveableComponent>(item); AddComp<UnremoveableComponent>(item);
if (!_handsSystem.TryPickupAnyHand(uid, item, checkActionBlocker: false)) if (!_handsSystem.TryPickupAnyHand(uid, item, checkActionBlocker: false))
{ {

View File

@@ -15,13 +15,12 @@ namespace Content.Server.Kitchen.Components
[RegisterComponent, Friend(typeof(KitchenSpikeSystem))] [RegisterComponent, Friend(typeof(KitchenSpikeSystem))]
public sealed class KitchenSpikeComponent : SharedKitchenSpikeComponent, ISuicideAct public sealed class KitchenSpikeComponent : SharedKitchenSpikeComponent, ISuicideAct
{ {
public int MeatParts; public List<string>? PrototypesToSpawn;
public string? MeatPrototype;
// TODO: Spiking alive mobs? (Replace with uid) (deal damage to their limbs on spiking, kill on first butcher attempt?) // TODO: Spiking alive mobs? (Replace with uid) (deal damage to their limbs on spiking, kill on first butcher attempt?)
public string MeatSource1p = "?"; public string MeatSource1p = "?";
public string MeatSource0 = "?"; public string MeatSource0 = "?";
public string MeatName = "?"; public string Victim = "?";
// Prevents simultaneous spiking of two bodies (could be replaced with CancellationToken, but I don't see any situation where Cancel could be called) // Prevents simultaneous spiking of two bodies (could be replaced with CancellationToken, but I don't see any situation where Cancel could be called)
public bool InUse; public bool InUse;

View File

@@ -12,6 +12,8 @@ using Robust.Shared.IoC;
using Robust.Shared.Localization; using Robust.Shared.Localization;
using Robust.Shared.Player; using Robust.Shared.Player;
using System; using System;
using Content.Shared.Storage;
using Robust.Shared.Random;
using static Content.Shared.Kitchen.Components.SharedKitchenSpikeComponent; using static Content.Shared.Kitchen.Components.SharedKitchenSpikeComponent;
namespace Content.Server.Kitchen.EntitySystems namespace Content.Server.Kitchen.EntitySystems
@@ -20,6 +22,7 @@ namespace Content.Server.Kitchen.EntitySystems
{ {
[Dependency] private readonly PopupSystem _popupSystem = default!; [Dependency] private readonly PopupSystem _popupSystem = default!;
[Dependency] private readonly DoAfterSystem _doAfter = default!; [Dependency] private readonly DoAfterSystem _doAfter = default!;
[Dependency] private readonly IRobustRandom _random = default!;
public override void Initialize() public override void Initialize()
{ {
@@ -64,14 +67,14 @@ namespace Content.Server.Kitchen.EntitySystems
if (Spikeable(uid, args.User, args.Dragged, component)) if (Spikeable(uid, args.User, args.Dragged, component))
TrySpike(uid, args.User, args.Dragged, component); TrySpike(uid, args.User, args.Dragged, component);
} }
private void OnInteractHand(EntityUid uid, KitchenSpikeComponent component, InteractHandEvent args) private void OnInteractHand(EntityUid uid, KitchenSpikeComponent component, InteractHandEvent args)
{ {
if (args.Handled) if (args.Handled)
return; return;
if (component.MeatParts > 0) { if (component.PrototypesToSpawn?.Count > 0) {
_popupSystem.PopupEntity(Loc.GetString("comp-kitchen-spike-knife-needed"), uid, Filter.Entities(args.User)); _popupSystem.PopupEntity(Loc.GetString("comp-kitchen-spike-knife-needed"), uid, Filter.Entities(args.User));
args.Handled = true; args.Handled = true;
} }
@@ -92,13 +95,13 @@ namespace Content.Server.Kitchen.EntitySystems
if (!Resolve(uid, ref component) || !Resolve(victimUid, ref butcherable)) if (!Resolve(uid, ref component) || !Resolve(victimUid, ref butcherable))
return; return;
component.MeatPrototype = butcherable.SpawnedPrototype; // TODO VERY SUS
component.MeatParts = butcherable.Pieces; component.PrototypesToSpawn = EntitySpawnCollection.GetSpawns(butcherable.SpawnedEntities, _random);
// This feels not okay, but entity is getting deleted on "Spike", for now... // This feels not okay, but entity is getting deleted on "Spike", for now...
component.MeatSource1p = Loc.GetString("comp-kitchen-spike-remove-meat", ("victim", victimUid)); component.MeatSource1p = Loc.GetString("comp-kitchen-spike-remove-meat", ("victim", victimUid));
component.MeatSource0 = Loc.GetString("comp-kitchen-spike-remove-meat-last", ("victim", victimUid)); component.MeatSource0 = Loc.GetString("comp-kitchen-spike-remove-meat-last", ("victim", victimUid));
component.MeatName = Loc.GetString("comp-kitchen-spike-meat-name", ("victim", victimUid)); component.Victim = Name(victimUid);
UpdateAppearance(uid, null, component); UpdateAppearance(uid, null, component);
@@ -114,7 +117,7 @@ namespace Content.Server.Kitchen.EntitySystems
private bool TryGetPiece(EntityUid uid, EntityUid user, EntityUid used, private bool TryGetPiece(EntityUid uid, EntityUid user, EntityUid used,
KitchenSpikeComponent? component = null, UtensilComponent? utensil = null) KitchenSpikeComponent? component = null, UtensilComponent? utensil = null)
{ {
if (!Resolve(uid, ref component) || component.MeatParts == 0) if (!Resolve(uid, ref component) || component.PrototypesToSpawn == null || component.PrototypesToSpawn.Count == 0)
return false; return false;
// Is using knife // Is using knife
@@ -123,15 +126,13 @@ namespace Content.Server.Kitchen.EntitySystems
return false; return false;
} }
component.MeatParts--; var item = _random.PickAndTake(component.PrototypesToSpawn);
if (!string.IsNullOrEmpty(component.MeatPrototype)) var ent = Spawn(item, Transform(uid).Coordinates);
{ MetaData(ent).EntityName =
var meat = EntityManager.SpawnEntity(component.MeatPrototype, Transform(uid).Coordinates); Loc.GetString("comp-kitchen-spike-meat-name", ("name", Name(ent)), ("victim", component.Victim));
MetaData(meat).EntityName = component.MeatName;
}
if (component.MeatParts != 0) if (component.PrototypesToSpawn.Count != 0)
{ {
_popupSystem.PopupEntity(component.MeatSource1p, uid, Filter.Entities(user)); _popupSystem.PopupEntity(component.MeatSource1p, uid, Filter.Entities(user));
} }
@@ -149,7 +150,7 @@ namespace Content.Server.Kitchen.EntitySystems
if (!Resolve(uid, ref component, ref appearance, false)) if (!Resolve(uid, ref component, ref appearance, false))
return; return;
appearance.SetData(KitchenSpikeVisuals.Status, (component.MeatParts > 0) ? KitchenSpikeStatus.Bloody : KitchenSpikeStatus.Empty); appearance.SetData(KitchenSpikeVisuals.Status, (component.PrototypesToSpawn?.Count > 0) ? KitchenSpikeStatus.Bloody : KitchenSpikeStatus.Empty);
} }
private bool Spikeable(EntityUid uid, EntityUid userUid, EntityUid victimUid, private bool Spikeable(EntityUid uid, EntityUid userUid, EntityUid victimUid,
@@ -158,13 +159,13 @@ namespace Content.Server.Kitchen.EntitySystems
if (!Resolve(uid, ref component)) if (!Resolve(uid, ref component))
return false; return false;
if (component.MeatParts > 0) if (component.PrototypesToSpawn?.Count > 0)
{ {
_popupSystem.PopupEntity(Loc.GetString("comp-kitchen-spike-deny-collect", ("this", uid)), uid, Filter.Entities(userUid)); _popupSystem.PopupEntity(Loc.GetString("comp-kitchen-spike-deny-collect", ("this", uid)), uid, Filter.Entities(userUid));
return false; return false;
} }
if (!Resolve(victimUid, ref butcherable, false) || butcherable.SpawnedPrototype == null) if (!Resolve(victimUid, ref butcherable, false))
{ {
_popupSystem.PopupEntity(Loc.GetString("comp-kitchen-spike-deny-butcher", ("victim", victimUid), ("this", uid)), victimUid, Filter.Entities(userUid)); _popupSystem.PopupEntity(Loc.GetString("comp-kitchen-spike-deny-butcher", ("victim", victimUid), ("this", uid)), victimUid, Filter.Entities(userUid));
return false; return false;

View File

@@ -6,8 +6,10 @@ using Content.Shared.Interaction;
using Content.Shared.MobState.Components; using Content.Shared.MobState.Components;
using Content.Shared.Nutrition.Components; using Content.Shared.Nutrition.Components;
using Content.Shared.Popups; using Content.Shared.Popups;
using Content.Shared.Storage;
using Content.Shared.Verbs; using Content.Shared.Verbs;
using Robust.Shared.Player; using Robust.Shared.Player;
using Robust.Shared.Random;
namespace Content.Server.Kitchen.EntitySystems; namespace Content.Server.Kitchen.EntitySystems;
@@ -15,6 +17,7 @@ public sealed class SharpSystem : EntitySystem
{ {
[Dependency] private readonly DoAfterSystem _doAfterSystem = default!; [Dependency] private readonly DoAfterSystem _doAfterSystem = default!;
[Dependency] private readonly SharedPopupSystem _popupSystem = default!; [Dependency] private readonly SharedPopupSystem _popupSystem = default!;
[Dependency] private readonly IRobustRandom _robustRandom = default!;
public override void Initialize() public override void Initialize()
{ {
@@ -77,10 +80,12 @@ public sealed class SharpSystem : EntitySystem
sharp.Butchering.Remove(ev.Entity); sharp.Butchering.Remove(ev.Entity);
var spawnEntities = EntitySpawnCollection.GetSpawns(butcher.SpawnedEntities, _robustRandom);
var coords = Transform(ev.Entity).Coordinates;
EntityUid popupEnt = default; EntityUid popupEnt = default;
for (int i = 0; i < butcher.Pieces; i++) foreach (var proto in spawnEntities)
{ {
popupEnt = Spawn(butcher.SpawnedPrototype, Transform(ev.Entity).Coordinates); popupEnt = Spawn(proto, coords);
} }
_popupSystem.PopupEntity(Loc.GetString("butcherable-knife-butchered-success", ("target", ev.Entity), ("knife", ev.Sharp)), _popupSystem.PopupEntity(Loc.GetString("butcherable-knife-butchered-success", ("target", ev.Entity), ("knife", ev.Sharp)),
@@ -118,7 +123,7 @@ public sealed class SharpSystem : EntitySystem
message = Loc.GetString("butcherable-mob-isnt-dead"); message = Loc.GetString("butcherable-mob-isnt-dead");
} }
if (args.Using is null || !TryComp<SharpComponent>(args.Using, out var sharp)) if (args.Using is null || !HasComp<SharpComponent>(args.Using))
{ {
disabled = true; disabled = true;
message = Loc.GetString("butcherable-need-knife"); message = Loc.GetString("butcherable-need-knife");

View File

@@ -1,5 +1,6 @@
using System.Collections.Generic; using System.Collections.Generic;
using Content.Shared.Sound; using Content.Shared.Sound;
using Content.Shared.Storage;
using Robust.Shared.GameObjects; using Robust.Shared.GameObjects;
using Robust.Shared.Serialization.Manager.Attributes; using Robust.Shared.Serialization.Manager.Attributes;
@@ -16,7 +17,7 @@ namespace Content.Server.Storage.Components
/// </summary> /// </summary>
/// <returns></returns> /// <returns></returns>
[DataField("items", required: true)] [DataField("items", required: true)]
public List<EntitySpawnEntry> Items = new List<EntitySpawnEntry>(); public List<EntitySpawnEntry> Items = new();
/// <summary> /// <summary>
/// A sound to play when the items are spawned. For example, gift boxes being unwrapped. /// A sound to play when the items are spawned. For example, gift boxes being unwrapped.

View File

@@ -1,5 +1,6 @@
using System.Collections.Generic; using System.Collections.Generic;
using Content.Server.Storage.EntitySystems; using Content.Server.Storage.EntitySystems;
using Content.Shared.Storage;
using Robust.Shared.Analyzers; using Robust.Shared.Analyzers;
using Robust.Shared.GameObjects; using Robust.Shared.GameObjects;
using Robust.Shared.Serialization.Manager.Attributes; using Robust.Shared.Serialization.Manager.Attributes;

View File

@@ -1,59 +0,0 @@
using System;
using Robust.Shared.Prototypes;
using Robust.Shared.Serialization.Manager;
using Robust.Shared.Serialization.Manager.Attributes;
using Robust.Shared.Serialization.TypeSerializers.Implementations.Custom.Prototype;
namespace Content.Server.Storage
{
/// <summary>
/// Dictates a list of items that can be spawned.
/// </summary>
[Serializable]
[DataDefinition]
public struct EntitySpawnEntry : IPopulateDefaultValues
{
[DataField("id", required: true, customTypeSerializer: typeof(PrototypeIdSerializer<EntityPrototype>))]
public string PrototypeId;
/// <summary>
/// The probability that an item will spawn. Takes decimal form so 0.05 is 5%, 0.50 is 50% etc.
/// </summary>
[DataField("prob")]
public float SpawnProbability;
/// <summary>
/// orGroup signifies to pick between entities designated with an ID.
///
/// <example>
/// <para>To define an orGroup in a StorageFill component you
/// need to add it to the entities you want to choose between and
/// add a prob field. In this example there is a 50% chance the storage
/// spawns with Y or Z.
///
/// </para>
/// <code>
/// - type: StorageFill
/// contents:
/// - name: X
/// - name: Y
/// prob: 0.50
/// orGroup: YOrZ
/// - name: Z
/// orGroup: YOrZ
/// </code>
/// </example>
/// </summary>
[DataField("orGroup")]
public string? GroupId;
[DataField("amount")]
public int Amount;
public void PopulateDefaultValues()
{
Amount = 1;
SpawnProbability = 1;
}
}
}

View File

@@ -1,6 +1,7 @@
using Content.Server.Storage.Components; using Content.Server.Storage.Components;
using Content.Shared.Hands.EntitySystems; using Content.Shared.Hands.EntitySystems;
using Content.Shared.Interaction.Events; using Content.Shared.Interaction.Events;
using Content.Shared.Storage;
using Robust.Shared.Audio; using Robust.Shared.Audio;
using Robust.Shared.Player; using Robust.Shared.Player;
using Robust.Shared.Random; using Robust.Shared.Random;
@@ -24,25 +25,13 @@ namespace Content.Server.Storage.EntitySystems
if (args.Handled) if (args.Handled)
return; return;
var alreadySpawnedGroups = new List<string>(); var coords = Transform(args.User).Coordinates;
var spawnEntities = EntitySpawnCollection.GetSpawns(component.Items, _random);
EntityUid? entityToPlaceInHands = null; EntityUid? entityToPlaceInHands = null;
foreach (var storageItem in component.Items)
foreach (var proto in spawnEntities)
{ {
if (!string.IsNullOrEmpty(storageItem.GroupId) && entityToPlaceInHands = Spawn(proto, coords);
alreadySpawnedGroups.Contains(storageItem.GroupId)) continue;
if (storageItem.SpawnProbability != 1f &&
!_random.Prob(storageItem.SpawnProbability))
{
continue;
}
for (var i = 0; i < storageItem.Amount; i++)
{
entityToPlaceInHands = EntityManager.SpawnEntity(storageItem.PrototypeId, EntityManager.GetComponent<TransformComponent>(args.User).Coordinates);
}
if (!string.IsNullOrEmpty(storageItem.GroupId)) alreadySpawnedGroups.Add(storageItem.GroupId);
} }
if (component.Sound != null) if (component.Sound != null)

View File

@@ -1,6 +1,7 @@
using Content.Server.Storage.Components; using Content.Server.Storage.Components;
using Robust.Shared.Random; using Robust.Shared.Random;
using System.Linq; using System.Linq;
using Content.Shared.Storage;
namespace Content.Server.Storage.EntitySystems; namespace Content.Server.Storage.EntitySystems;
@@ -18,67 +19,15 @@ public sealed partial class StorageSystem
var coordinates = Transform(uid).Coordinates; var coordinates = Transform(uid).Coordinates;
var orGroupedSpawns = new Dictionary<string, OrGroup>(); var spawnItems = EntitySpawnCollection.GetSpawns(component.Contents, _random);
foreach (var item in spawnItems)
// collect groups together, create singular items that pass probability
foreach (var entry in component.Contents)
{ {
// Handle "Or" groups var ent = EntityManager.SpawnEntity(item, coordinates);
if (!string.IsNullOrEmpty(entry.GroupId))
{
if (!orGroupedSpawns.TryGetValue(entry.GroupId, out OrGroup? orGroup))
{
orGroup = new();
orGroupedSpawns.Add(entry.GroupId, orGroup);
}
orGroup.Entries.Add(entry);
orGroup.CumulativeProbability += entry.SpawnProbability;
continue;
}
// else if (storage.Insert(ent)) continue;
// Check random spawn
// ReSharper disable once CompareOfFloatsByEqualityOperator
if (entry.SpawnProbability != 1f && !_random.Prob(entry.SpawnProbability)) continue;
for (var i = 0; i < entry.Amount; i++) Logger.ErrorS("storage", $"Tried to StorageFill {item} inside {uid} but can't.");
{ EntityManager.DeleteEntity(ent);
var ent = EntityManager.SpawnEntity(entry.PrototypeId, coordinates);
if (storage.Insert(ent)) continue;
Logger.ErrorS("storage", $"Tried to StorageFill {entry.PrototypeId} inside {uid} but can't.");
EntityManager.DeleteEntity(ent);
}
} }
// handle orgroup spawns
foreach (var spawnValue in orGroupedSpawns.Values)
{
// For each group use the added cumulative probability to roll a double in that range
double diceRoll = _random.NextDouble() * spawnValue.CumulativeProbability;
// Add the entry's spawn probability to this value, if equals or lower, spawn item, otherwise continue to next item.
double cumulative = 0.0;
foreach (var entry in spawnValue.Entries)
{
cumulative += entry.SpawnProbability;
if (diceRoll > cumulative) continue;
// Dice roll succeeded, spawn item and break loop
for (var index = 0; index < entry.Amount; index++)
{
var ent = EntityManager.SpawnEntity(entry.PrototypeId, coordinates);
if (storage.Insert(ent)) continue;
Logger.ErrorS("storage", $"Tried to StorageFill {entry.PrototypeId} inside {uid} but can't.");
EntityManager.DeleteEntity(ent);
}
break;
}
}
}
private sealed class OrGroup
{
public List<EntitySpawnEntry> Entries { get; set; } = new();
public float CumulativeProbability { get; set; } = 0f;
} }
} }

View File

@@ -1,4 +1,5 @@
using Content.Shared.DragDrop; using Content.Shared.DragDrop;
using Content.Shared.Storage;
using Robust.Shared.GameObjects; using Robust.Shared.GameObjects;
using Robust.Shared.Prototypes; using Robust.Shared.Prototypes;
using Robust.Shared.Serialization.Manager.Attributes; using Robust.Shared.Serialization.Manager.Attributes;
@@ -13,14 +14,9 @@ namespace Content.Shared.Nutrition.Components
[RegisterComponent] [RegisterComponent]
public sealed class SharedButcherableComponent : Component, IDraggable public sealed class SharedButcherableComponent : Component, IDraggable
{ {
//TODO: List for sub-products like animal-hides, organs and etc?
[ViewVariables] [ViewVariables]
[DataField("spawned", customTypeSerializer:typeof(PrototypeIdSerializer<EntityPrototype>))] [DataField("spawned", required: true)]
public string SpawnedPrototype = "FoodMeat"; public List<EntitySpawnEntry> SpawnedEntities = new();
[ViewVariables]
[DataField("pieces")]
public int Pieces = 5;
[DataField("butcherDelay")] [DataField("butcherDelay")]
public float ButcherDelay = 8.0f; public float ButcherDelay = 8.0f;

View File

@@ -0,0 +1,132 @@
using Robust.Shared.Prototypes;
using Robust.Shared.Random;
using Robust.Shared.Serialization.Manager;
using Robust.Shared.Serialization.TypeSerializers.Implementations.Custom.Prototype;
namespace Content.Shared.Storage;
/// <summary>
/// Dictates a list of items that can be spawned.
/// </summary>
[Serializable]
[DataDefinition]
public struct EntitySpawnEntry : IPopulateDefaultValues
{
[DataField("id", required: true, customTypeSerializer: typeof(PrototypeIdSerializer<EntityPrototype>))]
public string PrototypeId;
/// <summary>
/// The probability that an item will spawn. Takes decimal form so 0.05 is 5%, 0.50 is 50% etc.
/// </summary>
[DataField("prob")] public float SpawnProbability;
/// <summary>
/// orGroup signifies to pick between entities designated with an ID.
/// <example>
/// <para>
/// To define an orGroup in a StorageFill component you
/// need to add it to the entities you want to choose between and
/// add a prob field. In this example there is a 50% chance the storage
/// spawns with Y or Z.
/// </para>
/// <code>
/// - type: StorageFill
/// contents:
/// - name: X
/// - name: Y
/// prob: 0.50
/// orGroup: YOrZ
/// - name: Z
/// orGroup: YOrZ
/// </code>
/// </example>
/// </summary>
[DataField("orGroup")] public string? GroupId;
[DataField("amount")] public int Amount;
public void PopulateDefaultValues()
{
Amount = 1;
SpawnProbability = 1;
}
}
public static class EntitySpawnCollection
{
private sealed class OrGroup
{
public List<EntitySpawnEntry> Entries { get; set; } = new();
public float CumulativeProbability { get; set; } = 0f;
}
/// <summary>
/// Using a collection of entity spawn entries, picks a random list of entity prototypes to spawn from that collection.
/// </summary>
/// <remarks>
/// This does not spawn the entities. The caller is responsible for doing so, since it may want to do something
/// special to those entities (offset them, insert them into storage, etc)
/// </remarks>
/// <param name="entries">The entity spawn entries.</param>
/// <param name="random">Resolve param.</param>
/// <returns>A list of entity prototypes that should be spawned.</returns>
public static List<string> GetSpawns(IEnumerable<EntitySpawnEntry> entries,
IRobustRandom? random = null)
{
IoCManager.Resolve(ref random);
var spawned = new List<string>();
var orGroupedSpawns = new Dictionary<string, OrGroup>();
// collect groups together, create singular items that pass probability
foreach (var entry in entries)
{
// Handle "Or" groups
if (!string.IsNullOrEmpty(entry.GroupId))
{
if (!orGroupedSpawns.TryGetValue(entry.GroupId, out OrGroup? orGroup))
{
orGroup = new();
orGroupedSpawns.Add(entry.GroupId, orGroup);
}
orGroup.Entries.Add(entry);
orGroup.CumulativeProbability += entry.SpawnProbability;
continue;
}
// else
// Check random spawn
// ReSharper disable once CompareOfFloatsByEqualityOperator
if (entry.SpawnProbability != 1f && !random.Prob(entry.SpawnProbability)) continue;
for (var i = 0; i < entry.Amount; i++)
{
spawned.Add(entry.PrototypeId);
}
}
// handle orgroup spawns
foreach (var spawnValue in orGroupedSpawns.Values)
{
// For each group use the added cumulative probability to roll a double in that range
double diceRoll = random.NextDouble() * spawnValue.CumulativeProbability;
// Add the entry's spawn probability to this value, if equals or lower, spawn item, otherwise continue to next item.
var cumulative = 0.0;
foreach (var entry in spawnValue.Entries)
{
cumulative += entry.SpawnProbability;
if (diceRoll > cumulative) continue;
// Dice roll succeeded, add item and break loop
for (var index = 0; index < entry.Amount; index++)
{
spawned.Add(entry.PrototypeId);
}
break;
}
}
return spawned;
}
}

View File

@@ -15,4 +15,4 @@ comp-kitchen-spike-knife-needed = You need a knife to do this.
comp-kitchen-spike-remove-meat = You remove some meat from { THE($victim) }. comp-kitchen-spike-remove-meat = You remove some meat from { THE($victim) }.
comp-kitchen-spike-remove-meat-last = You remove the last piece of meat from { THE($victim) }! comp-kitchen-spike-remove-meat-last = You remove the last piece of meat from { THE($victim) }!
comp-kitchen-spike-meat-name = { $victim } meat comp-kitchen-spike-meat-name = { $name } ({ $victim })

View File

@@ -12,5 +12,6 @@
state: icon state: icon
- type: Butcherable - type: Butcherable
butcheringType: Knife butcheringType: Knife
spawned: MaterialCloth1 spawned:
pieces: 2 - id: MaterialCloth1
amount: 2

View File

@@ -10,5 +10,6 @@
state: icon state: icon
- type: Butcherable - type: Butcherable
butcheringType: Knife butcheringType: Knife
spawned: MaterialCloth1 spawned:
pieces: 1 - id: MaterialCloth1
amount: 1

View File

@@ -22,8 +22,9 @@
path: /Audio/Items/jumpsuit_equip.ogg path: /Audio/Items/jumpsuit_equip.ogg
- type: Butcherable - type: Butcherable
butcheringType: Knife butcheringType: Knife
spawned: MaterialCloth1 spawned:
pieces: 3 - id: MaterialCloth1
amount: 3
- type: entity - type: entity
abstract: true abstract: true
@@ -39,5 +40,6 @@
path: /Audio/Items/jumpsuit_equip.ogg path: /Audio/Items/jumpsuit_equip.ogg
- type: Butcherable - type: Butcherable
butcheringType: Knife butcheringType: Knife
spawned: MaterialCloth1 spawned:
pieces: 3 - id: MaterialCloth1
amount: 3

View File

@@ -36,8 +36,9 @@
crit: dead crit: dead
dead: dead dead: dead
- type: Butcherable - type: Butcherable
spawned: FoodMeat spawned:
pieces: 1 - id: FoodMeat
amount: 1
- type: InteractionPopup - type: InteractionPopup
successChance: 0.2 successChance: 0.2
interactSuccessString: petting-success-soft-floofy interactSuccessString: petting-success-soft-floofy
@@ -139,8 +140,9 @@
crit: dead-0 crit: dead-0
dead: dead-0 dead: dead-0
- type: Butcherable - type: Butcherable
spawned: FoodMeatChicken spawned:
pieces: 1 - id: FoodMeatChicken
amount: 1
- type: InteractionPopup - type: InteractionPopup
successChance: 0.8 successChance: 0.8
interactSuccessString: petting-success-bird interactSuccessString: petting-success-bird
@@ -169,8 +171,9 @@
crit: dead-0 crit: dead-0
dead: dead-0 dead: dead-0
- type: Butcherable - type: Butcherable
spawned: FoodMeatDuck spawned:
pieces: 1 - id: FoodMeatDuck
amount: 1
- type: InteractionPopup - type: InteractionPopup
successChance: 0.9 successChance: 0.9
interactSuccessString: petting-success-bird interactSuccessString: petting-success-bird
@@ -199,8 +202,9 @@
crit: dead-1 crit: dead-1
dead: dead-1 dead: dead-1
- type: Butcherable - type: Butcherable
spawned: FoodMeatDuck spawned:
pieces: 1 - id: FoodMeatDuck
amount: 1
- type: InteractionPopup - type: InteractionPopup
successChance: 0.9 successChance: 0.9
interactSuccessString: petting-success-bird interactSuccessString: petting-success-bird
@@ -229,8 +233,9 @@
crit: dead-2 crit: dead-2
dead: dead-2 dead: dead-2
- type: Butcherable - type: Butcherable
spawned: FoodMeatDuck spawned:
pieces: 1 - id: FoodMeatDuck
amount: 1
- type: InteractionPopup - type: InteractionPopup
successChance: 0.9 successChance: 0.9
interactSuccessString: petting-success-bird interactSuccessString: petting-success-bird
@@ -336,8 +341,9 @@
quantity: 25 quantity: 25
updateRate: 30 updateRate: 30
- type: Butcherable - type: Butcherable
spawned: FoodMeat spawned:
pieces: 5 - id: FoodMeat
amount: 5
- type: Grammar - type: Grammar
attributes: attributes:
gender: female # Here because of UdderComponent gender: female # Here because of UdderComponent
@@ -380,8 +386,9 @@
dead: dead dead: dead
- type: AsteroidRockVisualizer - type: AsteroidRockVisualizer
- type: Butcherable - type: Butcherable
spawned: FoodMeatCrab spawned:
pieces: 2 - id: FoodMeatCrab
amount: 2
- type: InteractionPopup - type: InteractionPopup
successChance: 0.5 successChance: 0.5
interactSuccessString: petting-success-crab interactSuccessString: petting-success-crab
@@ -420,8 +427,9 @@
quantity: 25 quantity: 25
updateRate: 20 updateRate: 20
- type: Butcherable - type: Butcherable
spawned: FoodMeat spawned:
pieces: 4 - id: FoodMeat
amount: 4
- type: Grammar - type: Grammar
attributes: attributes:
gender: female # Here because of UdderComponent gender: female # Here because of UdderComponent
@@ -450,8 +458,9 @@
crit: dead crit: dead
dead: dead dead: dead
- type: Butcherable - type: Butcherable
spawned: FoodMeatChicken spawned:
pieces: 2 - id: FoodMeatChicken
amount: 2
- type: InteractionPopup # TODO: Make it so there's a separate chance to make certain animals outright hostile towards you. - type: InteractionPopup # TODO: Make it so there's a separate chance to make certain animals outright hostile towards you.
successChance: 0.1 # Yeah, good luck with that. successChance: 0.1 # Yeah, good luck with that.
interactSuccessString: petting-success-goose interactSuccessString: petting-success-goose
@@ -491,8 +500,9 @@
crit: dead crit: dead
dead: dead dead: dead
- type: Butcherable - type: Butcherable
spawned: FoodMeat spawned:
pieces: 4 - id: FoodMeat
amount: 4
- type: Bloodstream - type: Bloodstream
bloodMaxVolume: 300 bloodMaxVolume: 300
@@ -602,8 +612,9 @@
normalState: Monkey_burning normalState: Monkey_burning
- type: Butcherable - type: Butcherable
butcheringType: Spike butcheringType: Spike
spawned: FoodMeat spawned:
pieces: 3 - id: FoodMeat
amount: 3
- type: MonkeyAccent - type: MonkeyAccent
- type: entity - type: entity
@@ -667,8 +678,9 @@
- ReagentId: Blood - ReagentId: Blood
Quantity: 50 Quantity: 50
- type: Butcherable - type: Butcherable
spawned: FoodMeat spawned:
pieces: 1 - id: FoodMeat
amount: 1
- type: ReplacementAccent - type: ReplacementAccent
accent: mouse accent: mouse
- type: Tag - type: Tag
@@ -770,8 +782,9 @@
crit: dead crit: dead
dead: dead dead: dead
- type: Butcherable - type: Butcherable
spawned: FoodMeat spawned:
pieces: 1 - id: FoodMeat
amount: 1
- type: InteractionPopup - type: InteractionPopup
successChance: 0.3 successChance: 0.3
interactSuccessString: petting-success-reptile interactSuccessString: petting-success-reptile
@@ -817,8 +830,9 @@
crit: dead crit: dead
dead: dead dead: dead
- type: Butcherable - type: Butcherable
spawned: FoodMeat spawned:
pieces: 1 - id: FoodMeat
amount: 1
- type: InteractionPopup - type: InteractionPopup
successChance: 0.6 successChance: 0.6
interactSuccessString: petting-success-frog interactSuccessString: petting-success-frog
@@ -862,8 +876,9 @@
crit: dead crit: dead
dead: dead dead: dead
- type: Butcherable - type: Butcherable
spawned: FoodMeat spawned:
pieces: 1 - id: FoodMeat
amount: 1
- type: InteractionPopup - type: InteractionPopup
successChance: 0.6 successChance: 0.6
interactSuccessString: petting-success-bird interactSuccessString: petting-success-bird
@@ -903,8 +918,9 @@
crit: penguin_dead crit: penguin_dead
dead: penguin_dead dead: penguin_dead
- type: Butcherable - type: Butcherable
spawned: FoodMeatPenguin spawned:
pieces: 3 - id: FoodMeatPenguin
amount: 3
- type: InteractionPopup - type: InteractionPopup
successChance: 0.5 successChance: 0.5
interactSuccessString: petting-success-bird interactSuccessString: petting-success-bird
@@ -951,8 +967,9 @@
crit: dead crit: dead
dead: dead dead: dead
- type: Butcherable - type: Butcherable
spawned: FoodMeatPenguin spawned:
pieces: 3 - id: FoodMeatPenguin
amount: 3
- type: UnarmedCombat - type: UnarmedCombat
range: 0.5 range: 0.5
arcwidth: 0 arcwidth: 0
@@ -965,7 +982,7 @@
- type: OnUseTimerTrigger - type: OnUseTimerTrigger
delay: 10 delay: 10
beepSound: beepSound:
path: /Audio/Weapons/Guns/MagOut/pistol_magout.ogg #funny sfx use path: /Audio/Weapons/Guns/MagOut/pistol_magout.ogg #funny sfx use
beepInterval: 1 beepInterval: 1
- type: Explosive - type: Explosive
devastationRange: 1 devastationRange: 1
@@ -1009,8 +1026,9 @@
# dead: dead # dead: dead
# crit: dead # crit: dead
- type: Butcherable - type: Butcherable
spawned: FoodMeat spawned:
pieces: 1 - id: FoodMeat
amount: 1
- type: InteractionPopup - type: InteractionPopup
successChance: 0.6 successChance: 0.6
interactSuccessString: petting-success-reptile interactSuccessString: petting-success-reptile
@@ -1055,8 +1073,9 @@
crit: tarantula_dead crit: tarantula_dead
dead: tarantula_dead dead: tarantula_dead
- type: Butcherable - type: Butcherable
spawned: FoodMeatSpider spawned:
pieces: 2 - id: FoodMeatSpider
amount: 2
- type: InteractionPopup - type: InteractionPopup
successChance: 0.5 successChance: 0.5
interactSuccessString: petting-success-tarantula interactSuccessString: petting-success-tarantula
@@ -1108,8 +1127,9 @@
crit: possum_dead # TODO: Make it so possums can "play dead." Probably need AI changes crit: possum_dead # TODO: Make it so possums can "play dead." Probably need AI changes
dead: possum_dead dead: possum_dead
- type: Butcherable - type: Butcherable
spawned: FoodMeat spawned:
pieces: 3 - id: FoodMeat
amount: 3
- type: InteractionPopup - type: InteractionPopup
successChance: 0.2 # Low when undomesticated. successChance: 0.2 # Low when undomesticated.
interactSuccessString: petting-success-possum # Possums don't really make much noise when they're happy. They make clicking noises as a mating call, but that is NOT the same thing! interactSuccessString: petting-success-possum # Possums don't really make much noise when they're happy. They make clicking noises as a mating call, but that is NOT the same thing!
@@ -1166,8 +1186,9 @@
crit: raccoon_dead crit: raccoon_dead
dead: raccoon_dead dead: raccoon_dead
- type: Butcherable - type: Butcherable
spawned: FoodMeat spawned:
pieces: 3 - id: FoodMeat
amount: 3
- type: InteractionPopup - type: InteractionPopup
successChance: 0.2 # Low when undomesticated. successChance: 0.2 # Low when undomesticated.
interactSuccessString: petting-success-soft-floofy interactSuccessString: petting-success-soft-floofy
@@ -1222,8 +1243,9 @@
crit: fox_dead crit: fox_dead
dead: fox_dead dead: fox_dead
- type: Butcherable - type: Butcherable
spawned: FoodMeat spawned:
pieces: 3 - id: FoodMeat
amount: 3
- type: InteractionPopup - type: InteractionPopup
successChance: 0.5 successChance: 0.5
interactSuccessString: petting-success-soft-floofy interactSuccessString: petting-success-soft-floofy

View File

@@ -43,8 +43,10 @@
crit: crit crit: crit
dead: dead dead: dead
- type: Butcherable - type: Butcherable
spawned: FoodMeat # TODO: CrapMeat or FishMeat # - 2022-02-17 LMAO crap meat # TODO: CrapMeat or FishMeat # - 2022-02-17 LMAO crap meat
pieces: 2 spawned:
- id: FoodMeat
amount: 2
- type: UnarmedCombat - type: UnarmedCombat
range: 1.5 range: 1.5
arcwidth: 0 arcwidth: 0

View File

@@ -33,8 +33,9 @@
crit: corgi_dead crit: corgi_dead
dead: corgi_dead dead: corgi_dead
- type: Butcherable - type: Butcherable
spawned: FoodMeat spawned:
pieces: 3 - id: FoodMeat
amount: 3
- type: ReplacementAccent - type: ReplacementAccent
accent: dog accent: dog
- type: InteractionPopup - type: InteractionPopup
@@ -211,8 +212,9 @@
crit: cat_dead crit: cat_dead
dead: cat_dead dead: cat_dead
- type: Butcherable - type: Butcherable
spawned: FoodMeat spawned:
pieces: 2 - id: FoodMeat
amount: 3
- type: ReplacementAccent - type: ReplacementAccent
accent: cat accent: cat
- type: InteractionPopup - type: InteractionPopup
@@ -362,8 +364,9 @@
crit: sloth_dead crit: sloth_dead
dead: sloth_dead dead: sloth_dead
- type: Butcherable - type: Butcherable
spawned: FoodMeat spawned:
pieces: 3 - id: FoodMeat
amount: 3
- type: InteractionPopup - type: InteractionPopup
successChance: 0.9 successChance: 0.9
interactSuccessString: petting-success-sloth interactSuccessString: petting-success-sloth
@@ -405,8 +408,9 @@
crit: ferret_dead crit: ferret_dead
dead: ferret_dead dead: ferret_dead
- type: Butcherable - type: Butcherable
spawned: FoodMeat spawned:
pieces: 3 - id: FoodMeat
amount: 3
- type: InteractionPopup - type: InteractionPopup
successChance: 0.8 successChance: 0.8
interactDelay: 1.5 # Avoids overlapping SFX due to spam - these SFX are a little longer than the typical 1 second. interactDelay: 1.5 # Avoids overlapping SFX due to spam - these SFX are a little longer than the typical 1 second.
@@ -449,7 +453,7 @@
mass: 10 mass: 10
mask: mask:
- Impassable - Impassable
- MobImpassable - MobImpassable
- VaultImpassable - VaultImpassable
- SmallImpassable - SmallImpassable
layer: layer:
@@ -461,10 +465,11 @@
crit: bingus_dead crit: bingus_dead
dead: bingus_dead dead: bingus_dead
- type: Butcherable - type: Butcherable
spawned: FoodMeat spawned:
pieces: 2 - id: FoodMeat
amount: 2
- type: InteractionPopup - type: InteractionPopup
successChance: 0.9 successChance: 0.9
interactSuccessString: petting-success-cat interactSuccessString: petting-success-cat
interactFailureString: petting-failure-generic interactFailureString: petting-failure-generic
interactSuccessSound: interactSuccessSound:
@@ -472,7 +477,7 @@
- type: Grammar - type: Grammar
attributes: attributes:
gender: epicene gender: epicene
- type: entity - type: entity
name: mcgriff name: mcgriff
parent: SimpleMobBase parent: SimpleMobBase
@@ -494,7 +499,7 @@
mass: 10 mass: 10
mask: mask:
- Impassable - Impassable
- MobImpassable - MobImpassable
- SmallImpassable - SmallImpassable
- VaultImpassable - VaultImpassable
layer: layer:
@@ -506,12 +511,13 @@
crit: mcgriff_dead crit: mcgriff_dead
dead: mcgriff_dead dead: mcgriff_dead
- type: Butcherable - type: Butcherable
spawned: FoodMeat spawned:
pieces: 2 - id: FoodMeat
amount: 2
- type: ReplacementAccent - type: ReplacementAccent
accent: dog accent: dog
- type: InteractionPopup - type: InteractionPopup
successChance: 0.5 successChance: 0.5
interactSuccessString: petting-success-dog interactSuccessString: petting-success-dog
interactFailureString: petting-failure-generic interactFailureString: petting-failure-generic
interactSuccessSound: interactSuccessSound:
@@ -540,8 +546,9 @@
crit: paperwork_dead crit: paperwork_dead
dead: paperwork_dead dead: paperwork_dead
- type: Butcherable - type: Butcherable
spawned: FoodMeat spawned:
pieces: 3 - id: FoodMeat
amount: 3
- type: InteractionPopup - type: InteractionPopup
successChance: 1 successChance: 1
interactSuccessString: petting-success-sloth interactSuccessString: petting-success-sloth
@@ -550,7 +557,7 @@
attributes: attributes:
proper: true proper: true
gender: male gender: male
- type: entity - type: entity
name: walter name: walter
parent: SimpleMobBase parent: SimpleMobBase
@@ -572,7 +579,7 @@
mass: 10 mass: 10
mask: mask:
- Impassable - Impassable
- MobImpassable - MobImpassable
- SmallImpassable - SmallImpassable
- VaultImpassable - VaultImpassable
layer: layer:
@@ -584,8 +591,9 @@
crit: walter_dead crit: walter_dead
dead: walter_dead dead: walter_dead
- type: Butcherable - type: Butcherable
spawned: FoodMeat spawned:
pieces: 3 - id: FoodMeat
amount: 3
- type: ReplacementAccent - type: ReplacementAccent
accent: dog accent: dog
- type: InteractionPopup - type: InteractionPopup
@@ -597,4 +605,4 @@
- type: Grammar - type: Grammar
attributes: attributes:
proper: true proper: true
gender: male gender: male

View File

@@ -45,8 +45,9 @@
normal: alive normal: alive
dead: dead dead: dead
- type: Butcherable - type: Butcherable
spawned: FoodMeatXeno spawned:
pieces: 1 - id: FoodMeatXeno
amount: 1
- type: Bloodstream - type: Bloodstream
bloodMaxVolume: 50 bloodMaxVolume: 50
- type: UnarmedCombat - type: UnarmedCombat

View File

@@ -69,9 +69,10 @@
dead: dead dead: dead
- type: Puller - type: Puller
- type: Butcherable - type: Butcherable
spawned: FoodMeatXeno
butcheringType: Spike butcheringType: Spike
pieces: 5 spawned:
- id: FoodMeatXeno
amount: 5
- type: GhostTakeoverAvailable - type: GhostTakeoverAvailable
makeSentient: true makeSentient: true
name: xeno name: xeno

View File

@@ -284,7 +284,9 @@
- type: Puller - type: Puller
- type: Butcherable - type: Butcherable
butcheringType: Spike # TODO human. butcheringType: Spike # TODO human.
spawned: FoodMeat spawned:
- id: FoodMeat
amount: 5
# - type: Recyclable Turns out turning off recycler safeties without considering the instagib is a bad idea # - type: Recyclable Turns out turning off recycler safeties without considering the instagib is a bad idea
# safe: false # safe: false
- type: Speech - type: Speech

View File

@@ -136,7 +136,9 @@
probability: 0.25 probability: 0.25
- type: Butcherable - type: Butcherable
butcheringType: Spike butcheringType: Spike
spawned: FoodMeatSlime spawned:
- id: FoodMeatSlime
amount: 5
- type: entity - type: entity
save: false save: false

View File

@@ -111,4 +111,6 @@
speciesId: vox speciesId: vox
- type: Butcherable - type: Butcherable
butcheringType: Spike butcheringType: Spike
spawned: FoodMeatChicken spawned:
- id: FoodMeatChicken
amount: 5