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:
15
Content.Server/_CP14/Workbench/CP14WorkbenchComponent.cs
Normal file
15
Content.Server/_CP14/Workbench/CP14WorkbenchComponent.cs
Normal 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();
|
||||
}
|
||||
229
Content.Server/_CP14/Workbench/CP14WorkbenchSystem.cs
Normal file
229
Content.Server/_CP14/Workbench/CP14WorkbenchSystem.cs
Normal 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;
|
||||
}
|
||||
}
|
||||
@@ -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
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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!;
|
||||
}
|
||||
19
Content.Shared/_CP14/Workbench/SharedCP14WorkbenchSystem.cs
Normal file
19
Content.Shared/_CP14/Workbench/SharedCP14WorkbenchSystem.cs
Normal 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;
|
||||
}
|
||||
5
Resources/Locale/en-US/_CP14/workbench/workbench.ftl
Normal file
5
Resources/Locale/en-US/_CP14/workbench/workbench.ftl
Normal 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!
|
||||
5
Resources/Locale/ru-RU/_CP14/workbench/workbench.ftl
Normal file
5
Resources/Locale/ru-RU/_CP14/workbench/workbench.ftl
Normal file
@@ -0,0 +1,5 @@
|
||||
cp14-verb-categories-craft = Выберите крафт:
|
||||
|
||||
cp14-workbench-recipe-list = Рецепт:
|
||||
|
||||
cp14-workbench-no-resource = Не хватает ингредиентов!
|
||||
@@ -18,6 +18,7 @@
|
||||
layer:
|
||||
- TableLayer
|
||||
- type: Sprite
|
||||
snapCardinals: true
|
||||
sprite: _CP14/Structures/Furniture/Tables/wood.rsi
|
||||
state: frame
|
||||
- type: Icon
|
||||
|
||||
@@ -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
|
||||
@@ -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:
|
||||
|
||||
@@ -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
|
||||
|
||||
16
Resources/Prototypes/_CP14/Recipes/Workbench/workbench.yml
Normal file
16
Resources/Prototypes/_CP14/Recipes/Workbench/workbench.yml
Normal 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 |
@@ -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"
|
||||
}
|
||||
]
|
||||
}
|
||||
Reference in New Issue
Block a user