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.Shared.Storage;
namespace Content.Server.Drone.Components
{

View File

@@ -20,6 +20,8 @@ using Content.Server.Hands.Components;
using Content.Server.UserInterface;
using Robust.Shared.Player;
using Content.Shared.Hands.EntitySystems;
using Content.Shared.Storage;
using Robust.Shared.Random;
using Robust.Shared.Timing;
namespace Content.Server.Drone
@@ -32,6 +34,7 @@ namespace Content.Server.Drone
[Dependency] private readonly SharedHandsSystem _handsSystem = default!;
[Dependency] private readonly SharedActionsSystem _actionsSystem = default!;
[Dependency] private readonly IGameTiming _gameTiming = default!;
[Dependency] private readonly IRobustRandom _robustRandom = default!;
public override void Initialize()
{
@@ -120,9 +123,10 @@ namespace Content.Server.Drone
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);
if (!_handsSystem.TryPickupAnyHand(uid, item, checkActionBlocker: false))
{

View File

@@ -15,13 +15,12 @@ namespace Content.Server.Kitchen.Components
[RegisterComponent, Friend(typeof(KitchenSpikeSystem))]
public sealed class KitchenSpikeComponent : SharedKitchenSpikeComponent, ISuicideAct
{
public int MeatParts;
public string? MeatPrototype;
public List<string>? PrototypesToSpawn;
// TODO: Spiking alive mobs? (Replace with uid) (deal damage to their limbs on spiking, kill on first butcher attempt?)
public string MeatSource1p = "?";
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)
public bool InUse;

View File

@@ -12,6 +12,8 @@ using Robust.Shared.IoC;
using Robust.Shared.Localization;
using Robust.Shared.Player;
using System;
using Content.Shared.Storage;
using Robust.Shared.Random;
using static Content.Shared.Kitchen.Components.SharedKitchenSpikeComponent;
namespace Content.Server.Kitchen.EntitySystems
@@ -20,6 +22,7 @@ namespace Content.Server.Kitchen.EntitySystems
{
[Dependency] private readonly PopupSystem _popupSystem = default!;
[Dependency] private readonly DoAfterSystem _doAfter = default!;
[Dependency] private readonly IRobustRandom _random = default!;
public override void Initialize()
{
@@ -64,14 +67,14 @@ namespace Content.Server.Kitchen.EntitySystems
if (Spikeable(uid, args.User, args.Dragged, component))
TrySpike(uid, args.User, args.Dragged, component);
}
private void OnInteractHand(EntityUid uid, KitchenSpikeComponent component, InteractHandEvent args)
{
if (args.Handled)
return;
if (component.MeatParts > 0) {
if (component.PrototypesToSpawn?.Count > 0) {
_popupSystem.PopupEntity(Loc.GetString("comp-kitchen-spike-knife-needed"), uid, Filter.Entities(args.User));
args.Handled = true;
}
@@ -92,13 +95,13 @@ namespace Content.Server.Kitchen.EntitySystems
if (!Resolve(uid, ref component) || !Resolve(victimUid, ref butcherable))
return;
component.MeatPrototype = butcherable.SpawnedPrototype;
component.MeatParts = butcherable.Pieces;
// TODO VERY SUS
component.PrototypesToSpawn = EntitySpawnCollection.GetSpawns(butcherable.SpawnedEntities, _random);
// 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.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);
@@ -114,7 +117,7 @@ namespace Content.Server.Kitchen.EntitySystems
private bool TryGetPiece(EntityUid uid, EntityUid user, EntityUid used,
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;
// Is using knife
@@ -123,15 +126,13 @@ namespace Content.Server.Kitchen.EntitySystems
return false;
}
component.MeatParts--;
var item = _random.PickAndTake(component.PrototypesToSpawn);
if (!string.IsNullOrEmpty(component.MeatPrototype))
{
var meat = EntityManager.SpawnEntity(component.MeatPrototype, Transform(uid).Coordinates);
MetaData(meat).EntityName = component.MeatName;
}
var ent = Spawn(item, Transform(uid).Coordinates);
MetaData(ent).EntityName =
Loc.GetString("comp-kitchen-spike-meat-name", ("name", Name(ent)), ("victim", component.Victim));
if (component.MeatParts != 0)
if (component.PrototypesToSpawn.Count != 0)
{
_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))
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,
@@ -158,13 +159,13 @@ namespace Content.Server.Kitchen.EntitySystems
if (!Resolve(uid, ref component))
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));
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));
return false;

View File

@@ -6,8 +6,10 @@ using Content.Shared.Interaction;
using Content.Shared.MobState.Components;
using Content.Shared.Nutrition.Components;
using Content.Shared.Popups;
using Content.Shared.Storage;
using Content.Shared.Verbs;
using Robust.Shared.Player;
using Robust.Shared.Random;
namespace Content.Server.Kitchen.EntitySystems;
@@ -15,6 +17,7 @@ public sealed class SharpSystem : EntitySystem
{
[Dependency] private readonly DoAfterSystem _doAfterSystem = default!;
[Dependency] private readonly SharedPopupSystem _popupSystem = default!;
[Dependency] private readonly IRobustRandom _robustRandom = default!;
public override void Initialize()
{
@@ -77,10 +80,12 @@ public sealed class SharpSystem : EntitySystem
sharp.Butchering.Remove(ev.Entity);
var spawnEntities = EntitySpawnCollection.GetSpawns(butcher.SpawnedEntities, _robustRandom);
var coords = Transform(ev.Entity).Coordinates;
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)),
@@ -118,7 +123,7 @@ public sealed class SharpSystem : EntitySystem
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;
message = Loc.GetString("butcherable-need-knife");

View File

@@ -1,5 +1,6 @@
using System.Collections.Generic;
using Content.Shared.Sound;
using Content.Shared.Storage;
using Robust.Shared.GameObjects;
using Robust.Shared.Serialization.Manager.Attributes;
@@ -16,7 +17,7 @@ namespace Content.Server.Storage.Components
/// </summary>
/// <returns></returns>
[DataField("items", required: true)]
public List<EntitySpawnEntry> Items = new List<EntitySpawnEntry>();
public List<EntitySpawnEntry> Items = new();
/// <summary>
/// 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 Content.Server.Storage.EntitySystems;
using Content.Shared.Storage;
using Robust.Shared.Analyzers;
using Robust.Shared.GameObjects;
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.Shared.Hands.EntitySystems;
using Content.Shared.Interaction.Events;
using Content.Shared.Storage;
using Robust.Shared.Audio;
using Robust.Shared.Player;
using Robust.Shared.Random;
@@ -24,25 +25,13 @@ namespace Content.Server.Storage.EntitySystems
if (args.Handled)
return;
var alreadySpawnedGroups = new List<string>();
var coords = Transform(args.User).Coordinates;
var spawnEntities = EntitySpawnCollection.GetSpawns(component.Items, _random);
EntityUid? entityToPlaceInHands = null;
foreach (var storageItem in component.Items)
foreach (var proto in spawnEntities)
{
if (!string.IsNullOrEmpty(storageItem.GroupId) &&
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);
entityToPlaceInHands = Spawn(proto, coords);
}
if (component.Sound != null)

View File

@@ -1,6 +1,7 @@
using Content.Server.Storage.Components;
using Robust.Shared.Random;
using System.Linq;
using Content.Shared.Storage;
namespace Content.Server.Storage.EntitySystems;
@@ -18,67 +19,15 @@ public sealed partial class StorageSystem
var coordinates = Transform(uid).Coordinates;
var orGroupedSpawns = new Dictionary<string, OrGroup>();
// collect groups together, create singular items that pass probability
foreach (var entry in component.Contents)
var spawnItems = EntitySpawnCollection.GetSpawns(component.Contents, _random);
foreach (var item in spawnItems)
{
// 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;
}
var ent = EntityManager.SpawnEntity(item, coordinates);
// else
// Check random spawn
// ReSharper disable once CompareOfFloatsByEqualityOperator
if (entry.SpawnProbability != 1f && !_random.Prob(entry.SpawnProbability)) continue;
if (storage.Insert(ent)) continue;
for (var i = 0; i < entry.Amount; i++)
{
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);
}
Logger.ErrorS("storage", $"Tried to StorageFill {item} 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;
}
}