Workbench (#341)

* craft prototype

* comp + empty system

* simple crafting

* 2 crafts!

* craft dound + delay

* ingredient waste

* verb message

* stack support

* sprite + collision + real crafts

* workbench craft

* fuck

* Update CP14WorkbenchSystem.cs

* fuck ItemPlacer, _lookup lets go
This commit is contained in:
Ed
2024-07-22 12:16:32 +03:00
committed by GitHub
parent 4a6a76fc06
commit f91a642428
15 changed files with 460 additions and 1 deletions

View File

@@ -0,0 +1,15 @@
using Content.Shared._CP14.Workbench.Prototypes;
using Robust.Shared.Prototypes;
namespace Content.Server._CP14.Workbench;
[RegisterComponent]
[Access(typeof(CP14WorkbenchSystem))]
public sealed partial class CP14WorkbenchComponent : Component
{
[DataField]
public float CraftSpeed = 1f;
[DataField]
public List<ProtoId<CP14WorkbenchRecipePrototype>> Recipes = new();
}

View File

@@ -0,0 +1,229 @@
using Content.Server.Popups;
using Content.Shared._CP14.Workbench;
using Content.Shared._CP14.Workbench.Prototypes;
using Content.Shared.DoAfter;
using Content.Shared.Stacks;
using Content.Shared.Verbs;
using Robust.Shared.Audio.Systems;
using Robust.Shared.Prototypes;
namespace Content.Server._CP14.Workbench;
public sealed class CP14WorkbenchSystem : SharedCP14WorkbenchSystem
{
[Dependency] private readonly SharedAudioSystem _audio = default!;
[Dependency] private readonly SharedDoAfterSystem _doAfter = default!;
[Dependency] private readonly SharedStackSystem _stack = default!;
[Dependency] private readonly IPrototypeManager _proto = default!;
[Dependency] private readonly PopupSystem _popup = default!;
[Dependency] private readonly EntityLookupSystem _lookup = default!;
private EntityQuery<MetaDataComponent> _metaQuery;
private EntityQuery<StackComponent> _stackQuery;
private const float WorkbenchRadius = 0.5f;
public override void Initialize()
{
base.Initialize();
_metaQuery = GetEntityQuery<MetaDataComponent>();
_stackQuery = GetEntityQuery<StackComponent>();
SubscribeLocalEvent<CP14WorkbenchComponent, GetVerbsEvent<InteractionVerb>>(OnInteractionVerb);
SubscribeLocalEvent<CP14WorkbenchComponent, CP14CraftDoAfterEvent>(OnCraftFinished);
}
private void OnInteractionVerb(Entity<CP14WorkbenchComponent> ent, ref GetVerbsEvent<InteractionVerb> args)
{
if (!args.CanAccess || !args.CanInteract || args.Hands is null)
return;
var placedEntities = _lookup.GetEntitiesInRange(Transform(ent).Coordinates, WorkbenchRadius);
var user = args.User;
foreach (var craftProto in ent.Comp.Recipes)
{
if (!_proto.TryIndex(craftProto, out var craft))
continue;
if (!_proto.TryIndex(craft.Result, out var result))
continue;
args.Verbs.Add(new()
{
Act = () =>
{
StartCraft(ent, user, craft);
},
Text = result.Name,
Message = GetCraftRecipeMessage(result.Description, craft),
Category = VerbCategory.CP14Craft,
Disabled = !CanCraftRecipe(craft, placedEntities),
});
}
}
private void OnCraftFinished(Entity<CP14WorkbenchComponent> ent, ref CP14CraftDoAfterEvent args)
{
if (args.Cancelled || args.Handled)
return;
if (!_proto.TryIndex(args.Recipe, out var recipe))
return;
var placedEntities = _lookup.GetEntitiesInRange(Transform(ent).Coordinates, WorkbenchRadius);
if (!CanCraftRecipe(recipe, placedEntities))
{
_popup.PopupEntity(Loc.GetString("cp14-workbench-no-resource"), ent, args.User);
return;
}
foreach (var requiredIngredient in recipe.Entities)
{
var requiredCount = requiredIngredient.Value;
foreach (var placedEntity in placedEntities)
{
var placedProto = MetaData(placedEntity).EntityPrototype?.ID;
if (placedProto != null && placedProto == requiredIngredient.Key && requiredCount > 0)
{
requiredCount--;
QueueDel(placedEntity);
}
}
}
foreach (var requiredStack in recipe.Stacks)
{
var requiredCount = requiredStack.Value;
foreach (var placedEntity in placedEntities)
{
if (!_stackQuery.TryGetComponent(placedEntity, out var stack))
continue;
if (stack.StackTypeId != requiredStack.Key)
continue;
var count = (int)MathF.Min(requiredCount, stack.Count);
_stack.SetCount(placedEntity, stack.Count - count, stack);
requiredCount -= count;
}
}
Spawn(_proto.Index(args.Recipe).Result, Transform(ent).Coordinates);
args.Handled = true;
}
private void StartCraft(Entity<CP14WorkbenchComponent> workbench, EntityUid user, CP14WorkbenchRecipePrototype recipe)
{
var craftDoAfter = new CP14CraftDoAfterEvent
{
Recipe = recipe.ID,
};
var doAfterArgs = new DoAfterArgs(EntityManager,
user,
recipe.CraftTime * workbench.Comp.CraftSpeed,
craftDoAfter,
workbench,
workbench)
{
BreakOnMove = true,
BreakOnDamage = true,
NeedHand = true,
};
_doAfter.TryStartDoAfter(doAfterArgs);
_audio.PlayPvs(recipe.CraftSound, workbench);
}
private List<CP14WorkbenchRecipePrototype> GetPossibleCrafts(Entity<CP14WorkbenchComponent> workbench, HashSet<EntityUid> ingrediEnts)
{
List<CP14WorkbenchRecipePrototype> result = new();
if (ingrediEnts.Count == 0)
return result;
foreach (var recipeProto in workbench.Comp.Recipes)
{
var recipe = _proto.Index(recipeProto);
if (CanCraftRecipe(recipe, ingrediEnts))
{
result.Add(recipe);
}
}
return result;
}
private bool CanCraftRecipe(CP14WorkbenchRecipePrototype recipe, HashSet<EntityUid> entities)
{
var indexedIngredients = IndexIngredients(entities);
foreach (var requiredIngredient in recipe.Entities)
{
if (!indexedIngredients.TryGetValue(requiredIngredient.Key, out var availableQuantity) ||
availableQuantity < requiredIngredient.Value)
return false;
}
foreach (var (key, value) in recipe.Stacks)
{
var count = 0;
foreach (var ent in entities)
{
if (_stackQuery.TryGetComponent(ent, out var stack))
{
if (stack.StackTypeId != key)
continue;
count += stack.Count;
}
}
if (count < value)
return false;
}
return true;
}
private string GetCraftRecipeMessage(string desc, CP14WorkbenchRecipePrototype recipe)
{
var result = desc + "\n \n" + Loc.GetString("cp14-workbench-recipe-list")+ "\n";
foreach (var pair in recipe.Entities)
{
var proto = _proto.Index(pair.Key);
result += $"{proto.Name} x{pair.Value}\n";
}
foreach (var pair in recipe.Stacks)
{
var proto = _proto.Index(pair.Key);
result += $"{proto.Name} x{pair.Value}\n";
}
return result;
}
private Dictionary<EntProtoId, int> IndexIngredients(HashSet<EntityUid> ingredients)
{
var indexedIngredients = new Dictionary<EntProtoId, int>();
foreach (var ingredient in ingredients)
{
var protoId = _metaQuery.GetComponent(ingredient).EntityPrototype?.ID;
if (protoId == null)
continue;
if (indexedIngredients.ContainsKey(protoId))
indexedIngredients[protoId]++;
else
indexedIngredients[protoId] = 1;
}
return indexedIngredients;
}
}

View File

@@ -88,5 +88,7 @@ namespace Content.Shared.Verbs
public static readonly VerbCategory SelectType = new("verb-categories-select-type", null);
public static readonly VerbCategory PowerLevel = new("verb-categories-power-level", null);
public static readonly VerbCategory CP14Craft = new("cp14-verb-categories-craft", null); //CP14
}
}

View File

@@ -3,7 +3,7 @@ using Robust.Shared.Prototypes;
namespace Content.Shared._CP14.Skills.Prototypes;
/// <summary>
/// A prototype of the lock category. Need a roundstart mapping to ensure that keys and locks will fit together despite randomization.
///
/// </summary>
[Prototype("CP14Skill")]
public sealed partial class CP14SkillPrototype : IPrototype

View File

@@ -0,0 +1,31 @@
using Content.Shared.Stacks;
using Robust.Shared.Audio;
using Robust.Shared.Prototypes;
namespace Content.Shared._CP14.Workbench.Prototypes;
/// <summary>
///
/// </summary>
[Prototype("CP14Recipe")]
public sealed partial class CP14WorkbenchRecipePrototype : IPrototype
{
[ViewVariables]
[IdDataField]
public string ID { get; private set; } = default!;
[DataField]
public TimeSpan CraftTime = TimeSpan.FromSeconds(1f);
[DataField]
public SoundSpecifier CraftSound = new SoundCollectionSpecifier("CP14Hammering");
[DataField]
public Dictionary<EntProtoId, int> Entities = new();
[DataField]
public Dictionary<ProtoId<StackPrototype>, int> Stacks = new();
[DataField(required: true)]
public EntProtoId Result = default!;
}

View File

@@ -0,0 +1,19 @@
using Content.Shared._CP14.Workbench.Prototypes;
using Content.Shared.DoAfter;
using Robust.Shared.Prototypes;
using Robust.Shared.Serialization;
namespace Content.Shared._CP14.Workbench;
public class SharedCP14WorkbenchSystem : EntitySystem
{
}
[Serializable, NetSerializable]
public sealed partial class CP14CraftDoAfterEvent : DoAfterEvent
{
[DataField(required: true)]
public ProtoId<CP14WorkbenchRecipePrototype> Recipe = default!;
public override DoAfterEvent Clone() => this;
}

View File

@@ -0,0 +1,5 @@
cp14-verb-categories-craft = Select recipe:
cp14-workbench-recipe-list = Recipe:
cp14-workbench-no-resource = There aren't enough ingredients!

View File

@@ -0,0 +1,5 @@
cp14-verb-categories-craft = Выберите крафт:
cp14-workbench-recipe-list = Рецепт:
cp14-workbench-no-resource = Не хватает ингредиентов!

View File

@@ -18,6 +18,7 @@
layer:
- TableLayer
- type: Sprite
snapCardinals: true
sprite: _CP14/Structures/Furniture/Tables/wood.rsi
state: frame
- type: Icon

View File

@@ -0,0 +1,92 @@
- type: entity
parent:
- BaseStructure
id: CP14BaseWorkbench
abstract: true
components:
- type: Fixtures
fixtures:
fix1:
shape:
!type:PhysShapeAabb
bounds: "-0.45,-0.45,0.45,0.45"
density: 55
mask:
- TabletopMachineMask
layer:
- Impassable
- MidImpassable
- LowImpassable
hard: false
fix2:
shape:
!type:PhysShapeAabb
bounds: "-0.45,-0.45,0.45,0.45"
density: 55
mask:
- TableMask
layer:
- TableLayer
- type: Climbable
- type: Clickable
- type: PlaceableSurface
- type: CP14Workbench
craftSpeed: 1
- type: entity
parent:
- CP14BaseWorkbench
- CP14BaseWooden
id: CP14Workbench
name: workbench
description: Table for the production of various basic tools.
components:
- type: Sprite
snapCardinals: true
sprite: _CP14/Structures/Furniture/workbench.rsi
state: icon
- type: Icon
sprite: _CP14/Structures/Furniture/workbench.rsi
state: icon
- type: Damageable
damageContainer: Inorganic
damageModifierSet: Wood
- type: Destructible
thresholds:
- trigger:
!type:DamageTypeTrigger
damageType: Heat
damage: 40
behaviors:
- !type:DoActsBehavior
acts: ["Destruction"]
- !type:PlaySoundBehavior
sound:
collection: WoodDestroy
- trigger:
!type:DamageTrigger
damage: 60
behaviors:
- !type:DoActsBehavior
acts: ["Destruction"]
- !type:PlaySoundBehavior
sound:
collection: WoodDestroy
- !type:SpawnEntitiesBehavior
spawn:
CP14WoodenPlanks1:
min: 1
max: 1
- type: FootstepModifier
footstepSoundCollection:
collection: FootstepWood
- type: FireVisuals
sprite: _CP14/Effects/fire.rsi
normalState: full
- type: Construction
graph: CP14TableWooden
node: CP14Workbench
- type: CP14Workbench
recipes:
- CP14Bucket
- CP14BaseBarrel

View File

@@ -25,6 +25,10 @@
- tool: Screwing #TODO - new tool
doAfter: 1
- to: CP14TableWooden
steps:
- tool: CP14Hammering
doAfter: 1
- to: CP14Workbench
steps:
- material: CP14Nail
amount: 1
@@ -34,6 +38,14 @@
- node: CP14TableWooden
entity: CP14TableWooden
edges:
- to: CP14TableWoodenFrame
steps:
- tool: Screwing #TODO - new tool
doAfter: 1
- node: CP14Workbench
entity: CP14Workbench
edges:
- to: CP14TableWoodenFrame
steps:

View File

@@ -90,6 +90,24 @@
conditions:
- !type:TileNotBlocked
- type: construction
crystallPunkAllowed: true
name: Workbench
description: Table for the production of various basic tools.
id: CP14Workbench
graph: CP14TableWooden
startNode: start
targetNode: CP14Workbench
category: construction-category-furniture
icon:
sprite: _CP14/Structures/Furniture/workbench.rsi
state: icon
objectType: Structure
placementMode: SnapgridCenter
canBuildInImpassable: false
conditions:
- !type:TileNotBlocked
- type: construction
crystallPunkAllowed: true
id: CP14Mannequin

View File

@@ -0,0 +1,16 @@
- type: CP14Recipe
id: CP14Bucket
craftTime: 2
entities:
CP14Rope: 1
stacks:
CP14WoodenPlanks: 3
result: CP14Bucket
- type: CP14Recipe
id: CP14BaseBarrel
craftTime: 3
stacks:
CP14WoodenPlanks: 5
CP14Nail: 2
result: CP14BaseBarrel

Binary file not shown.

After

Width:  |  Height:  |  Size: 408 B

View File

@@ -0,0 +1,14 @@
{
"version": 1,
"license": "All rights reserved for the CrystallPunk14 project only",
"copyright": "Created by jaraten(discord) for CrystallPunk14",
"size": {
"x": 32,
"y": 32
},
"states": [
{
"name": "icon"
}
]
}