diff --git a/Content.Server/Construction/Components/MachineComponent.cs b/Content.Server/Construction/Components/MachineComponent.cs index 4e3013d1b1..59a251c568 100644 --- a/Content.Server/Construction/Components/MachineComponent.cs +++ b/Content.Server/Construction/Components/MachineComponent.cs @@ -10,7 +10,9 @@ namespace Content.Server.Construction.Components [DataField("board", customTypeSerializer: typeof(PrototypeIdSerializer))] public string? BoardPrototype { get; private set; } + [ViewVariables] public Container BoardContainer = default!; + [ViewVariables] public Container PartContainer = default!; } diff --git a/Content.Server/Construction/Components/PartExchangerComponent.cs b/Content.Server/Construction/Components/PartExchangerComponent.cs new file mode 100644 index 0000000000..d51a6609c0 --- /dev/null +++ b/Content.Server/Construction/Components/PartExchangerComponent.cs @@ -0,0 +1,32 @@ +using System.Threading; +using Robust.Shared.Audio; + +namespace Content.Server.Construction.Components; + +[RegisterComponent] +public sealed class PartExchangerComponent : Component +{ + /// + /// How long it takes to exchange the parts + /// + [DataField("exchangeDuration")] + public float ExchangeDuration = 3; + + /// + /// Whether or not the distance check is needed. + /// Good for BRPED. + /// + /// + /// I fucking hate BRPED and if you ever add it + /// i will personally kill your dog. + /// + [DataField("doDistanceCheck")] + public bool DoDistanceCheck = true; + + [DataField("exchangeSound")] + public SoundSpecifier ExchangeSound = new SoundPathSpecifier("/Audio/Items/rped.ogg"); + + public IPlayingAudioStream? AudioStream; + + public CancellationTokenSource? Token; +} diff --git a/Content.Server/Construction/PartExchangerSystem.cs b/Content.Server/Construction/PartExchangerSystem.cs new file mode 100644 index 0000000000..e84a24c5a5 --- /dev/null +++ b/Content.Server/Construction/PartExchangerSystem.cs @@ -0,0 +1,142 @@ +using System.Linq; +using System.Threading; +using Content.Server.Construction.Components; +using Content.Server.DoAfter; +using Content.Server.Storage.Components; +using Content.Server.Storage.EntitySystems; +using Content.Server.Wires; +using Content.Shared.Interaction; +using Content.Shared.Popups; +using Robust.Shared.Containers; +using Robust.Shared.Player; +using Robust.Shared.Utility; + +namespace Content.Server.Construction; + +public sealed class PartExchangerSystem : EntitySystem +{ + [Dependency] private readonly ConstructionSystem _construction = default!; + [Dependency] private readonly DoAfterSystem _doAfter = default!; + [Dependency] private readonly SharedPopupSystem _popup = default!; + [Dependency] private readonly SharedContainerSystem _container = default!; + [Dependency] private readonly SharedAudioSystem _audio = default!; + [Dependency] private readonly StorageSystem _storage = default!; + + /// + public override void Initialize() + { + SubscribeLocalEvent(OnAfterInteract); + SubscribeLocalEvent(OnFinished); + SubscribeLocalEvent(OnCancelled); + } + + private void OnFinished(EntityUid uid, PartExchangerComponent component, RpedExchangeFinishedEvent args) + { + component.Token = null; + component.AudioStream?.Stop(); + + if (!TryComp(args.Target, out var machine)) + return; + + if (!TryComp(uid, out var storage) || storage.Storage == null) + return; //the parts are stored in here + + var board = machine.BoardContainer.ContainedEntities.FirstOrNull(); + + if (board == null || !TryComp(board, out var macBoardComp)) + return; + + var machineParts = new List(); + + foreach (var ent in storage.Storage.ContainedEntities) //get parts in RPED + { + if (TryComp(ent, out var part)) + machineParts.Add(part); + } + foreach (var ent in new List(machine.PartContainer.ContainedEntities)) //clone so don't modify during enumeration + { + if (TryComp(ent, out var part)) + { + machineParts.Add(part); + _container.RemoveEntity(machine.Owner, ent); + } + } + + //order by highest rating + machineParts = machineParts.OrderByDescending(p => p.Rating).ToList(); + + var updatedParts = new List(); + foreach (var (type, amount) in macBoardComp.Requirements) + { + var target = machineParts.Where(p => p.PartType == type).Take(amount); + updatedParts.AddRange(target); + } + foreach (var part in updatedParts) + { + machine.PartContainer.Insert(part.Owner, EntityManager); + machineParts.Remove(part); + } + + //put the unused parts back into rped. (this also does the "swapping") + foreach (var unused in machineParts) + { + storage.Storage.Insert(unused.Owner); + _storage.Insert(uid, unused.Owner, null, false); + } + _construction.RefreshParts(machine); + } + + private void OnCancelled(EntityUid uid, PartExchangerComponent component, RpedExchangeCancelledEvent args) + { + component.Token = null; + component.AudioStream?.Stop(); + } + + private void OnAfterInteract(EntityUid uid, PartExchangerComponent component, AfterInteractEvent args) + { + if (component.Token != null) + return; + + if (component.DoDistanceCheck && !args.CanReach) + return; + + if (args.Target == null) + return; + + if (!HasComp(args.Target)) + return; + + if (TryComp(args.Target, out var wires) && !wires.IsPanelOpen) + { + _popup.PopupEntity(Loc.GetString("construction-step-condition-wire-panel-open"), + args.Target.Value, Filter.Pvs(args.Target.Value, entityManager: EntityManager)); + return; + } + + component.AudioStream = _audio.PlayPvs(component.ExchangeSound, uid); + + component.Token = new CancellationTokenSource(); + _doAfter.DoAfter(new DoAfterEventArgs(args.User, component.ExchangeDuration, component.Token.Token, args.Target, args.Used) + { + BreakOnDamage = true, + BreakOnStun = true, + BreakOnUserMove = true, + UsedFinishedEvent = new RpedExchangeFinishedEvent(args.Target.Value), + UsedCancelledEvent = new RpedExchangeCancelledEvent() + }); + } +} + +public sealed class RpedExchangeFinishedEvent : EntityEventArgs +{ + public readonly EntityUid Target; + + public RpedExchangeFinishedEvent(EntityUid target) + { + Target = target; + } +} + +public readonly struct RpedExchangeCancelledEvent +{ +} diff --git a/Content.Server/DoAfter/DoAfterEventArgs.cs b/Content.Server/DoAfter/DoAfterEventArgs.cs index ad41407af3..7ba46c64ea 100644 --- a/Content.Server/DoAfter/DoAfterEventArgs.cs +++ b/Content.Server/DoAfter/DoAfterEventArgs.cs @@ -89,6 +89,16 @@ namespace Content.Server.DoAfter /// public object? UserFinishedEvent { get; set; } + /// + /// Event to be raised directed to the entity when the DoAfter is cancelled. + /// + public object? UsedCancelledEvent { get; set; } + + /// + /// Event to be raised directed to the entity when the DoAfter is finished successfully. + /// + public object? UsedFinishedEvent { get; set; } + /// /// Event to be raised directed to the entity when the DoAfter is cancelled. /// diff --git a/Content.Server/DoAfter/DoAfterSystem.cs b/Content.Server/DoAfter/DoAfterSystem.cs index b6587dee17..4606ff8d08 100644 --- a/Content.Server/DoAfter/DoAfterSystem.cs +++ b/Content.Server/DoAfter/DoAfterSystem.cs @@ -153,6 +153,9 @@ namespace Content.Server.DoAfter if(EntityManager.EntityExists(doAfter.EventArgs.User) && doAfter.EventArgs.UserCancelledEvent != null) RaiseLocalEvent(doAfter.EventArgs.User, doAfter.EventArgs.UserCancelledEvent, false); + if (doAfter.EventArgs.Used is {} used && EntityManager.EntityExists(used) && doAfter.EventArgs.UsedCancelledEvent != null) + RaiseLocalEvent(used, doAfter.EventArgs.UsedCancelledEvent); + if(doAfter.EventArgs.Target is {} target && EntityManager.EntityExists(target) && doAfter.EventArgs.TargetCancelledEvent != null) RaiseLocalEvent(target, doAfter.EventArgs.TargetCancelledEvent, false); @@ -167,6 +170,9 @@ namespace Content.Server.DoAfter if(EntityManager.EntityExists(doAfter.EventArgs.User) && doAfter.EventArgs.UserFinishedEvent != null) RaiseLocalEvent(doAfter.EventArgs.User, doAfter.EventArgs.UserFinishedEvent, false); + if(doAfter.EventArgs.Used is {} used && EntityManager.EntityExists(used) && doAfter.EventArgs.UsedFinishedEvent != null) + RaiseLocalEvent(used, doAfter.EventArgs.UsedFinishedEvent); + if(doAfter.EventArgs.Target is {} target && EntityManager.EntityExists(target) && doAfter.EventArgs.TargetFinishedEvent != null) RaiseLocalEvent(target, doAfter.EventArgs.TargetFinishedEvent, false); diff --git a/Resources/Audio/Items/attributions.yml b/Resources/Audio/Items/attributions.yml index 84c0d544d0..92bce1cd2d 100644 --- a/Resources/Audio/Items/attributions.yml +++ b/Resources/Audio/Items/attributions.yml @@ -6,4 +6,9 @@ - files: ["trayhit2.ogg"] license: "CC-BY-SA-3.0" copyright: "Time immemorial" - source: "https://github.com/tgstation/tgstation/blob/172b533d0257fcc1f8a05406f1c9fad514c14d88/sound/items/trayhit2.ogg" \ No newline at end of file + source: "https://github.com/tgstation/tgstation/blob/172b533d0257fcc1f8a05406f1c9fad514c14d88/sound/items/trayhit2.ogg" + +- files: ["rped.ogg"] + license: "CC-BY-SA-3.0" + copyright: "Time immemorial" + source: "https://github.com/tgstation/tgstation/blob/172b533d0257fcc1f8a05406f1c9fad514c14d88/sound/items/rped.ogg" diff --git a/Resources/Audio/Items/rped.ogg b/Resources/Audio/Items/rped.ogg new file mode 100644 index 0000000000..93dca60d34 Binary files /dev/null and b/Resources/Audio/Items/rped.ogg differ diff --git a/Resources/Prototypes/Catalog/Research/technologies.yml b/Resources/Prototypes/Catalog/Research/technologies.yml index b6492dad4a..bd59ea06f7 100644 --- a/Resources/Prototypes/Catalog/Research/technologies.yml +++ b/Resources/Prototypes/Catalog/Research/technologies.yml @@ -245,6 +245,7 @@ - FireAlarmElectronics - MailingUnitElectronics - HolofanProjector + - RPED - type: technology name: technologies-material-sheet-printing @@ -438,7 +439,6 @@ requiredPoints: 8000 requiredTechnologies: - BasicPartsTechnology - - IndustrialEngineering - PowerCellBasic unlockedRecipes: - AdvancedCapacitorStockPart diff --git a/Resources/Prototypes/Entities/Objects/Specific/Research/rped.yml b/Resources/Prototypes/Entities/Objects/Specific/Research/rped.yml new file mode 100644 index 0000000000..37fcd55100 --- /dev/null +++ b/Resources/Prototypes/Entities/Objects/Specific/Research/rped.yml @@ -0,0 +1,18 @@ +- type: entity + parent: BaseStorageItem + id: RPED + name: RPED + description: A Rapid Part Exchange Device, perfect for quickly upgrading machines. + components: + - type: Sprite + sprite: Objects/Specific/Research/rped.rsi + state: icon + - type: Item + sprite: Objects/Specific/Research/rped.rsi + size: 50 + - type: PartExchanger + - type: Storage + capacity: 150 + whitelist: + components: + - MachinePart \ No newline at end of file diff --git a/Resources/Prototypes/Recipes/Lathes/tools.yml b/Resources/Prototypes/Recipes/Lathes/tools.yml index 85e8789fb7..5886fb45d0 100644 --- a/Resources/Prototypes/Recipes/Lathes/tools.yml +++ b/Resources/Prototypes/Recipes/Lathes/tools.yml @@ -189,3 +189,15 @@ Glass: 350 Plastic: 150 Gold: 10 + +- type: latheRecipe + id: RPED + icon: + sprite: Objects/Specific/Research/rped.rsi + state: icon + result: RPED + completetime: 10 + materials: + Steel: 650 + Plastic: 150 + Gold: 50 diff --git a/Resources/Textures/Objects/Specific/Research/rped.rsi/icon.png b/Resources/Textures/Objects/Specific/Research/rped.rsi/icon.png new file mode 100644 index 0000000000..cd57f65eb3 Binary files /dev/null and b/Resources/Textures/Objects/Specific/Research/rped.rsi/icon.png differ diff --git a/Resources/Textures/Objects/Specific/Research/rped.rsi/inhand-left.png b/Resources/Textures/Objects/Specific/Research/rped.rsi/inhand-left.png new file mode 100644 index 0000000000..25702f772a Binary files /dev/null and b/Resources/Textures/Objects/Specific/Research/rped.rsi/inhand-left.png differ diff --git a/Resources/Textures/Objects/Specific/Research/rped.rsi/inhand-right.png b/Resources/Textures/Objects/Specific/Research/rped.rsi/inhand-right.png new file mode 100644 index 0000000000..31843e5081 Binary files /dev/null and b/Resources/Textures/Objects/Specific/Research/rped.rsi/inhand-right.png differ diff --git a/Resources/Textures/Objects/Specific/Research/rped.rsi/meta.json b/Resources/Textures/Objects/Specific/Research/rped.rsi/meta.json new file mode 100644 index 0000000000..99acaed848 --- /dev/null +++ b/Resources/Textures/Objects/Specific/Research/rped.rsi/meta.json @@ -0,0 +1,22 @@ +{ + "version": 1, + "license": "CC-BY-SA-3.0", + "copyright": "Taken from tgstation at https://github.com/tgstation/tgstation/commit/40d89d11ea4a5cb81d61dc1018b46f4e7d32c62a, inhands created by EmoGarbage404", + "size": { + "x": 32, + "y": 32 + }, + "states": [ + { + "name": "icon" + }, + { + "name": "inhand-left", + "directions": 4 + }, + { + "name": "inhand-right", + "directions": 4 + } + ] +}