diff --git a/Content.Server/Lathe/LatheSystem.cs b/Content.Server/Lathe/LatheSystem.cs index 5ece533a62..6d7d4e4533 100644 --- a/Content.Server/Lathe/LatheSystem.cs +++ b/Content.Server/Lathe/LatheSystem.cs @@ -155,10 +155,10 @@ namespace Content.Server.Lathe { var ev = new LatheGetRecipesEvent(uid, getUnavailable) { - Recipes = new List>(component.StaticRecipes) + Recipes = new HashSet>(component.StaticRecipes) }; RaiseLocalEvent(uid, ev); - return ev.Recipes; + return ev.Recipes.ToList(); } public static List> GetAllBaseRecipes(LatheComponent component) diff --git a/Content.Shared/Lathe/LatheComponent.cs b/Content.Shared/Lathe/LatheComponent.cs index 7924a0ec94..de4311e559 100644 --- a/Content.Shared/Lathe/LatheComponent.cs +++ b/Content.Shared/Lathe/LatheComponent.cs @@ -83,7 +83,7 @@ namespace Content.Shared.Lathe public bool getUnavailable; - public List> Recipes = new(); + public HashSet> Recipes = new(); public LatheGetRecipesEvent(EntityUid lathe, bool forced) { diff --git a/Content.Shared/Research/Components/BlueprintComponent.cs b/Content.Shared/Research/Components/BlueprintComponent.cs new file mode 100644 index 0000000000..71ed3da034 --- /dev/null +++ b/Content.Shared/Research/Components/BlueprintComponent.cs @@ -0,0 +1,19 @@ +using Content.Shared.Research.Prototypes; +using Content.Shared.Research.Systems; +using Robust.Shared.GameStates; +using Robust.Shared.Prototypes; + +namespace Content.Shared.Research.Components; + +/// +/// This is used for an item that is inserted directly into a given lathe to provide it with a recipe. +/// +[RegisterComponent, NetworkedComponent, Access(typeof(BlueprintSystem))] +public sealed partial class BlueprintComponent : Component +{ + /// + /// The recipes that this blueprint provides. + /// + [DataField(required: true)] + public HashSet> ProvidedRecipes = new(); +} diff --git a/Content.Shared/Research/Components/BlueprintReceiverComponent.cs b/Content.Shared/Research/Components/BlueprintReceiverComponent.cs new file mode 100644 index 0000000000..94c323eb86 --- /dev/null +++ b/Content.Shared/Research/Components/BlueprintReceiverComponent.cs @@ -0,0 +1,18 @@ +using Content.Shared.Research.Systems; +using Content.Shared.Whitelist; +using Robust.Shared.GameStates; + +namespace Content.Shared.Research.Components; + +/// +/// This is used for a lathe that can utilize s to gain more recipes. +/// +[RegisterComponent, NetworkedComponent, Access(typeof(BlueprintSystem))] +public sealed partial class BlueprintReceiverComponent : Component +{ + [DataField] + public string ContainerId = "blueprint"; + + [DataField(required: true)] + public EntityWhitelist Whitelist = new(); +} diff --git a/Content.Shared/Research/Systems/BlueprintSystem.cs b/Content.Shared/Research/Systems/BlueprintSystem.cs new file mode 100644 index 0000000000..237ff70300 --- /dev/null +++ b/Content.Shared/Research/Systems/BlueprintSystem.cs @@ -0,0 +1,114 @@ +using Content.Shared.IdentityManagement; +using Content.Shared.Interaction; +using Content.Shared.Lathe; +using Content.Shared.Popups; +using Content.Shared.Research.Components; +using Content.Shared.Research.Prototypes; +using Content.Shared.Whitelist; +using Robust.Shared.Containers; +using Robust.Shared.Prototypes; + +namespace Content.Shared.Research.Systems; + +public sealed class BlueprintSystem : EntitySystem +{ + [Dependency] private readonly SharedContainerSystem _container = default!; + [Dependency] private readonly EntityWhitelistSystem _entityWhitelist = default!; + [Dependency] private readonly SharedPopupSystem _popup = default!; + + /// + public override void Initialize() + { + SubscribeLocalEvent(OnStartup); + SubscribeLocalEvent(OnAfterInteract); + SubscribeLocalEvent(OnGetRecipes); + } + + private void OnStartup(Entity ent, ref ComponentStartup args) + { + _container.EnsureContainer(ent, ent.Comp.ContainerId); + } + + private void OnAfterInteract(Entity ent, ref AfterInteractUsingEvent args) + { + if (args.Handled || !args.CanReach || !TryComp(args.Used, out var blueprintComponent)) + return; + args.Handled = TryInsertBlueprint(ent, (args.Used, blueprintComponent), args.User); + } + + private void OnGetRecipes(Entity ent, ref LatheGetRecipesEvent args) + { + var recipes = GetBlueprintRecipes(ent); + foreach (var recipe in recipes) + { + args.Recipes.Add(recipe); + } + } + + public bool TryInsertBlueprint(Entity ent, Entity blueprint, EntityUid? user) + { + if (!CanInsertBlueprint(ent, blueprint, user)) + return false; + + if (user is not null) + { + var userId = Identity.Entity(user.Value, EntityManager); + var bpId = Identity.Entity(blueprint, EntityManager); + var machineId = Identity.Entity(ent, EntityManager); + var msg = Loc.GetString("blueprint-receiver-popup-insert", + ("user", userId), + ("blueprint", bpId), + ("receiver", machineId)); + _popup.PopupPredicted(msg, ent, user); + } + + _container.Insert(blueprint.Owner, _container.GetContainer(ent, ent.Comp.ContainerId)); + + var ev = new TechnologyDatabaseModifiedEvent(); + RaiseLocalEvent(ent, ref ev); + return true; + } + + public bool CanInsertBlueprint(Entity ent, Entity blueprint, EntityUid? user) + { + if (_entityWhitelist.IsWhitelistFail(ent.Comp.Whitelist, blueprint)) + { + return false; + } + + if (blueprint.Comp.ProvidedRecipes.Count == 0) + { + Log.Error($"Attempted to insert blueprint {ToPrettyString(blueprint)} with no recipes."); + return false; + } + + // Don't add new blueprints if there are no new recipes. + var currentRecipes = GetBlueprintRecipes(ent); + if (currentRecipes.Count != 0 && currentRecipes.IsSupersetOf(blueprint.Comp.ProvidedRecipes)) + { + _popup.PopupPredicted(Loc.GetString("blueprint-receiver-popup-recipe-exists"), ent, user); + return false; + } + + return _container.CanInsert(blueprint, _container.GetContainer(ent, ent.Comp.ContainerId)); + } + + public HashSet> GetBlueprintRecipes(Entity ent) + { + var contained = _container.GetContainer(ent, ent.Comp.ContainerId); + + var recipes = new HashSet>(); + foreach (var blueprint in contained.ContainedEntities) + { + if (!TryComp(blueprint, out var blueprintComponent)) + continue; + + foreach (var provided in blueprintComponent.ProvidedRecipes) + { + recipes.Add(provided); + } + } + + return recipes; + } +} diff --git a/Resources/Locale/en-US/research/components/blueprint.ftl b/Resources/Locale/en-US/research/components/blueprint.ftl new file mode 100644 index 0000000000..34c3a3c80e --- /dev/null +++ b/Resources/Locale/en-US/research/components/blueprint.ftl @@ -0,0 +1,2 @@ +blueprint-receiver-popup-insert = { CAPITALIZE(THE($user)) } inserted { THE($blueprint) } into { THE($receiver) }. +blueprint-receiver-popup-recipe-exists = The same blueprint was already inserted! diff --git a/Resources/Prototypes/Entities/Markers/Spawners/Random/Salvage/tables_loot.yml b/Resources/Prototypes/Entities/Markers/Spawners/Random/Salvage/tables_loot.yml index 5e128e8f96..61b3d10817 100644 --- a/Resources/Prototypes/Entities/Markers/Spawners/Random/Salvage/tables_loot.yml +++ b/Resources/Prototypes/Entities/Markers/Spawners/Random/Salvage/tables_loot.yml @@ -207,6 +207,7 @@ id: SalvageEquipmentRare table: !type:GroupSelector children: + - id: BlueprintFlare - id: FultonBeacon - id: Fulton amount: !type:RangeNumberSelector @@ -228,6 +229,8 @@ id: SalvageEquipmentLegendary table: !type:GroupSelector children: + - id: BlueprintFulton + - id: BlueprintSeismicCharge - id: WeaponCrusherGlaive - id: ClothingOuterHardsuitSalvage - id: OmnizineChemistryBottle diff --git a/Resources/Prototypes/Entities/Objects/Tools/blueprint.yml b/Resources/Prototypes/Entities/Objects/Tools/blueprint.yml new file mode 100644 index 0000000000..ba26baf362 --- /dev/null +++ b/Resources/Prototypes/Entities/Objects/Tools/blueprint.yml @@ -0,0 +1,49 @@ +- type: entity + parent: BaseItem + id: BaseBlueprint + name: blueprint + description: A blueprint for some machine. It can be inserted into an autolathe. + abstract: true + components: + - type: Sprite + sprite: Objects/Tools/blueprint.rsi + state: icon + - type: Item + sprite: Objects/Tools/blueprint.rsi + size: Normal + - type: Blueprint + - type: StaticPrice + price: 1000 + - type: Tag + tags: + - BlueprintAutolathe + +- type: entity + parent: BaseBlueprint + id: BlueprintFulton + name: fulton blueprint + description: A blueprint with a schematic of a fulton. It can be inserted into an autolathe. + components: + - type: Blueprint + providedRecipes: + - Fulton + +- type: entity + parent: BaseBlueprint + id: BlueprintSeismicCharge + name: seismic charge blueprint + description: A blueprint with a schematic of a seismic charge. It can be inserted into an autolathe. + components: + - type: Blueprint + providedRecipes: + - SeismicCharge + +- type: entity + parent: BaseBlueprint + id: BlueprintFlare + name: flare blueprint + description: A blueprint with a schematic of a flare. It can be inserted into an autolathe. + components: + - type: Blueprint + providedRecipes: + - Flare diff --git a/Resources/Prototypes/Entities/Structures/Machines/lathe.yml b/Resources/Prototypes/Entities/Structures/Machines/lathe.yml index bf4d4de783..91ac7b049a 100644 --- a/Resources/Prototypes/Entities/Structures/Machines/lathe.yml +++ b/Resources/Prototypes/Entities/Structures/Machines/lathe.yml @@ -89,7 +89,7 @@ id: Autolathe parent: BaseLatheLube name: autolathe - description: It produces basic items using metal and glass. + description: It produces basic items using metal and glass. Has the ability to process blueprints to print new recipes. components: - type: Sprite sprite: Structures/Machines/autolathe.rsi @@ -226,6 +226,18 @@ - RiotShield - SpeedLoaderMagnum - SpeedLoaderMagnumEmpty + - type: BlueprintReceiver + whitelist: + tags: + - BlueprintAutolathe + - type: ContainerContainer + containers: + machine_board: !type:Container + machine_parts: !type:Container + blueprint: !type:Container + - type: EmptyOnMachineDeconstruct + containers: + - blueprint - type: entity id: AutolatheHyperConvection diff --git a/Resources/Prototypes/Recipes/Lathes/salvage.yml b/Resources/Prototypes/Recipes/Lathes/salvage.yml index 84047ae75d..2def767e91 100644 --- a/Resources/Prototypes/Recipes/Lathes/salvage.yml +++ b/Resources/Prototypes/Recipes/Lathes/salvage.yml @@ -1,20 +1,32 @@ - type: latheRecipe id: Fulton result: Fulton1 + category: Tools completetime: 1 materials: Steel: 200 - Cloth: 100 + Cloth: 500 - type: latheRecipe id: FultonBeacon result: FultonBeacon + category: Tools completetime: 5 materials: Steel: 1000 Glass: 500 # If they get spammed make it cost silver. +- type: latheRecipe + id: SeismicCharge + result: SeismicCharge + category: Tools + completetime: 1 + materials: + Plastic: 1500 + Steel: 100 + Silver: 100 + - type: latheRecipe id: MiningDrill result: MiningDrill diff --git a/Resources/Prototypes/tags.yml b/Resources/Prototypes/tags.yml index 9b11570a25..b52e2c4e8c 100644 --- a/Resources/Prototypes/tags.yml +++ b/Resources/Prototypes/tags.yml @@ -60,6 +60,9 @@ - type: Tag id: Bloodpack +- type: Tag + id: BlueprintAutolathe + - type: Tag id: BodyBag diff --git a/Resources/Textures/Objects/Tools/blueprint.rsi/icon.png b/Resources/Textures/Objects/Tools/blueprint.rsi/icon.png new file mode 100644 index 0000000000..fe8b37fa25 Binary files /dev/null and b/Resources/Textures/Objects/Tools/blueprint.rsi/icon.png differ diff --git a/Resources/Textures/Objects/Tools/blueprint.rsi/inhand-left.png b/Resources/Textures/Objects/Tools/blueprint.rsi/inhand-left.png new file mode 100644 index 0000000000..8aecdd7268 Binary files /dev/null and b/Resources/Textures/Objects/Tools/blueprint.rsi/inhand-left.png differ diff --git a/Resources/Textures/Objects/Tools/blueprint.rsi/inhand-right.png b/Resources/Textures/Objects/Tools/blueprint.rsi/inhand-right.png new file mode 100644 index 0000000000..37dd59e5c3 Binary files /dev/null and b/Resources/Textures/Objects/Tools/blueprint.rsi/inhand-right.png differ diff --git a/Resources/Textures/Objects/Tools/blueprint.rsi/meta.json b/Resources/Textures/Objects/Tools/blueprint.rsi/meta.json new file mode 100644 index 0000000000..ab9e7fb2a9 --- /dev/null +++ b/Resources/Textures/Objects/Tools/blueprint.rsi/meta.json @@ -0,0 +1,22 @@ +{ + "version": 1, + "license": "CC-BY-SA-3.0", + "copyright": "Taken from https://github.com/vgstation-coders/vgstation13/commit/dd749c36c416a6960782732cecf25e5ebac326e8", + "size": { + "x": 32, + "y": 32 + }, + "states": [ + { + "name": "inhand-left", + "directions": 4 + }, + { + "name": "inhand-right", + "directions": 4 + }, + { + "name": "icon" + } + ] +}