-
Notifications
You must be signed in to change notification settings - Fork 37
[Feature/Port] Reagent Production #68
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: master
Are you sure you want to change the base?
Changes from all commits
14d41cf
9bca604
ca05f2c
b33cf61
b7c8068
8a4fdee
28f8f24
add8e72
8e99ddc
80875c8
fa4b203
c179385
c4f93d1
0c1eb19
7b6937a
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,31 @@ | ||
| using Content.Shared._DEN.ReagentProduction.Prototypes; | ||
| using Robust.Shared.GameStates; | ||
| using Robust.Shared.Prototypes; | ||
| using Robust.Shared.Serialization.TypeSerializers.Implementations.Custom; | ||
|
|
||
| namespace Content.Shared._DEN.ReagentProduction.Components; | ||
| /// <summary> | ||
| /// Entities with this component produce reagents based on | ||
| /// what types of <see cref="ReagentProductionTypePrototype"/> this component has. | ||
| /// </summary> | ||
| [RegisterComponent, NetworkedComponent, AutoGenerateComponentState] | ||
| public sealed partial class ReagentProducerComponent : Component | ||
| { | ||
| /// <summary> | ||
| /// A list of production types this component manages. | ||
| /// </summary> | ||
| [DataField, AutoNetworkedField] | ||
| public List<ProtoId<ReagentProductionTypePrototype>> ProductionTypes = []; | ||
|
honeyed-lemons marked this conversation as resolved.
|
||
|
|
||
| /// <summary> | ||
| /// The next time to fill solution | ||
| /// </summary> | ||
| [DataField(customTypeSerializer: typeof(TimeOffsetSerializer))] | ||
| public TimeSpan NextUpdate; | ||
|
|
||
| /// <summary> | ||
| /// The interval between updates. | ||
| /// </summary> | ||
| [DataField] | ||
| public TimeSpan UpdateInterval = TimeSpan.FromSeconds(10); | ||
| } | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,227 @@ | ||
| using System.Globalization; | ||
| using Content.Shared._DEN.ReagentProduction.Components; | ||
| using Content.Shared._DEN.ReagentProduction.Events; | ||
| using Content.Shared._DEN.ReagentProduction.Prototypes; | ||
| using Content.Shared.Chemistry.Components; | ||
| using Content.Shared.Chemistry.Components.SolutionManager; | ||
| using Content.Shared.Chemistry.EntitySystems; | ||
| using Content.Shared.DoAfter; | ||
| using Content.Shared.FixedPoint; | ||
| using Content.Shared.IdentityManagement; | ||
| using Content.Shared.Mobs.Systems; | ||
| using Content.Shared.Popups; | ||
| using Content.Shared.Verbs; | ||
| using JetBrains.Annotations; | ||
| using Robust.Shared.Prototypes; | ||
| using Robust.Shared.Timing; | ||
| using Robust.Shared.Utility; | ||
| using Enumerable = System.Linq.Enumerable; | ||
| using static Content.Shared._DEN.ReagentProduction.Events.ReagentProductionEvents; | ||
|
|
||
| namespace Content.Shared._DEN.ReagentProduction.EntitySystems; | ||
|
|
||
| public sealed class ReagentProductionSystem : EntitySystem | ||
| { | ||
| [Dependency] private readonly IGameTiming _gameTiming = default!; | ||
|
Check failure on line 25 in Content.Shared/_DEN/ReagentProduction/EntitySystems/ReagentProductionSystem.cs
|
||
| [Dependency] private readonly IPrototypeManager _protoManager = default!; | ||
|
Check failure on line 26 in Content.Shared/_DEN/ReagentProduction/EntitySystems/ReagentProductionSystem.cs
|
||
| [Dependency] private readonly MobStateSystem _mobState = default!; | ||
|
Check failure on line 27 in Content.Shared/_DEN/ReagentProduction/EntitySystems/ReagentProductionSystem.cs
|
||
| [Dependency] private readonly SharedDoAfterSystem _doAfter = default!; | ||
|
Check failure on line 28 in Content.Shared/_DEN/ReagentProduction/EntitySystems/ReagentProductionSystem.cs
|
||
| [Dependency] private readonly SharedPopupSystem _popup = default!; | ||
|
Check failure on line 29 in Content.Shared/_DEN/ReagentProduction/EntitySystems/ReagentProductionSystem.cs
|
||
| [Dependency] private readonly SharedSolutionContainerSystem _solutionContainer = default!; | ||
|
|
||
| public static readonly VerbCategory ReagentFillCategory = new("verb-categories-fill", "/Textures/Interface/VerbIcons/spill.svg.192dpi.png"); | ||
|
|
||
| public override void Initialize() | ||
| { | ||
| base.Initialize(); | ||
| SubscribeLocalEvent<ReagentProducerComponent, ReagentProductionTypeAdded>(ProductionTypeAdded); | ||
| SubscribeLocalEvent<ReagentProducerComponent, ReagentProductionTypeRemoved>(ProductionTypeRemoved); | ||
|
|
||
| SubscribeLocalEvent<RefillableSolutionComponent, GetVerbsEvent<InteractionVerb>>(AddVerbs); | ||
|
|
||
| SubscribeLocalEvent<ReagentProducerComponent, ReagentProductionFillEvent>(FinishFillDoAfter); | ||
| SubscribeLocalEvent<ReagentProducerComponent, MapInitEvent>(OnMapInit); | ||
| } | ||
|
|
||
| private void OnMapInit(Entity<ReagentProducerComponent> ent, ref MapInitEvent args) | ||
| { | ||
| ent.Comp.NextUpdate = _gameTiming.CurTime + ent.Comp.UpdateInterval; | ||
| } | ||
|
|
||
| public override void Update(float frameTime) | ||
| { | ||
| base.Update(frameTime); | ||
| var query = EntityQueryEnumerator<ReagentProducerComponent, SolutionContainerManagerComponent>(); | ||
| while (query.MoveNext(out var uid, out var producerComponent, out _)) | ||
| { | ||
| // If the mob is dead OR it isnt time for the next update, don't move foward | ||
| if (!_mobState.IsAlive(uid) || _gameTiming.CurTime < producerComponent.NextUpdate) | ||
| continue; | ||
|
|
||
| producerComponent.NextUpdate += producerComponent.UpdateInterval; | ||
|
|
||
| // for every production type the producer has | ||
| foreach (var productionType in Enumerable.Select(producerComponent.ProductionTypes, productionTypeId => _protoManager.Index(productionTypeId))) | ||
| { | ||
| // ensure there's a solution to add to | ||
| _solutionContainer.EnsureSolution(uid, productionType.SolutionName, out var solution, productionType.MaximumCapacity); | ||
|
Check failure on line 67 in Content.Shared/_DEN/ReagentProduction/EntitySystems/ReagentProductionSystem.cs
|
||
|
|
||
| if (solution == null) | ||
|
Check failure on line 69 in Content.Shared/_DEN/ReagentProduction/EntitySystems/ReagentProductionSystem.cs
|
||
| continue; | ||
|
|
||
| // do some math to figure out how much we can add | ||
| var amountToAdd = FixedPoint2.Min(solution.AvailableVolume, productionType.UnitsPerProduction); | ||
|
Check failure on line 73 in Content.Shared/_DEN/ReagentProduction/EntitySystems/ReagentProductionSystem.cs
|
||
|
|
||
| if (amountToAdd <= 0) | ||
| continue; | ||
| //and add it :) | ||
| solution.AddReagent(productionType.Reagent, amountToAdd); | ||
|
Check failure on line 78 in Content.Shared/_DEN/ReagentProduction/EntitySystems/ReagentProductionSystem.cs
|
||
| } | ||
| } | ||
| } | ||
|
|
||
| private void AddVerbs(Entity<RefillableSolutionComponent> container, ref GetVerbsEvent<InteractionVerb> args) | ||
| { | ||
| var user = args.User; | ||
|
|
||
| if (!args.Using.HasValue || !args.CanInteract || !args.CanAccess) | ||
| return; | ||
|
|
||
| if (!TryComp<ReagentProducerComponent>(user, out var producerComp)) | ||
| return; | ||
|
|
||
| // Add a verb for every production type the producer has | ||
| foreach (var productionTypeId in producerComp.ProductionTypes) | ||
| { | ||
| if (!_protoManager.TryIndex(productionTypeId, out var productionType) || | ||
| !_protoManager.TryIndex(productionType.Reagent, out var reagent)) | ||
| continue; | ||
|
|
||
| // I'd love if I could specify this via yaml. alas YOU CANT DEFINE SPRITES VIA - | ||
| var icon = productionType.NsfwVerbIcon | ||
| ? new SpriteSpecifier.Texture(new ResPath("/Textures/_DEN/Interface/VerbIcons/lewd.svg.192dpi.png")) | ||
| : null; | ||
|
|
||
| var verb = new InteractionVerb | ||
| { | ||
| Category = ReagentFillCategory, | ||
| Act = () => StartFillDoAfter((user, producerComp), container, productionTypeId), | ||
| Text = CultureInfo.CurrentCulture.TextInfo.ToTitleCase(reagent.LocalizedName), | ||
| Priority = -1, | ||
| CloseMenu = false, | ||
| Icon = icon, | ||
| }; | ||
| args.Verbs.Add(verb); | ||
| } | ||
| } | ||
|
|
||
| private void StartFillDoAfter( | ||
| Entity<ReagentProducerComponent> user, | ||
| Entity<RefillableSolutionComponent> target, | ||
| ProtoId<ReagentProductionTypePrototype> productionTypeId | ||
| ) | ||
| { | ||
| var productionType = _protoManager.Index(productionTypeId); | ||
|
|
||
| _doAfter.TryStartDoAfter( | ||
| new DoAfterArgs(EntityManager, user, productionType.DoAfterLength, new ReagentProductionFillEvent(productionTypeId), user, target: target) | ||
| { | ||
| BreakOnMove = true, | ||
| BreakOnDropItem = true, | ||
| }); | ||
| } | ||
|
|
||
| private void FinishFillDoAfter(Entity<ReagentProducerComponent> ent, ref ReagentProductionFillEvent args) | ||
| { | ||
| if (!_protoManager.TryIndex(args.ProductionType, out var productionType) || args.Target == null || args.Cancelled || args.Handled) | ||
| return; | ||
|
|
||
| if (!TryComp<RefillableSolutionComponent>(args.Target.Value, out var refillableSolution)) | ||
| return; | ||
|
|
||
| if (!_solutionContainer.TryGetSolution(ent.Owner, productionType.SolutionName, out var userSolutionComp)|| | ||
| !_solutionContainer.TryGetSolution(args.Target.Value, refillableSolution.Solution, out var targetSolutionComp)) | ||
| return; | ||
|
|
||
| var targetSolution = targetSolutionComp.Value.Comp.Solution; | ||
|
|
||
| // If there's no cum to cum you cant cum okay? | ||
| if (userSolutionComp.Value.Comp.Solution.Volume <= 0) | ||
| { | ||
| _popup.PopupPredicted(Loc.GetString(productionType.DryPopup),args.Args.User,args.Args.User); | ||
| return; | ||
| } | ||
|
|
||
| // Get available volume in target solution | ||
| var targetAvailableVolume = targetSolution.MaxVolume - targetSolution.Volume; | ||
|
|
||
| // If theres no room just silently return | ||
| if (targetAvailableVolume <= 0) | ||
| return; | ||
|
|
||
| // Get amount to add, attempts to add the largest amount with the maximum set from production type | ||
| var amountToAdd = | ||
| FixedPoint2.Clamp(targetAvailableVolume, FixedPoint2.Zero, productionType.MaximumLoad); | ||
|
|
||
| var split = _solutionContainer.SplitSolution(userSolutionComp.Value, amountToAdd); | ||
| var quantity = _solutionContainer.AddSolution(targetSolutionComp.Value, split); | ||
|
|
||
| _popup.PopupPredicted( | ||
| Loc.GetString( | ||
| productionType.SuccessPopup, | ||
| ("amount", quantity), | ||
| ("target", Identity.Entity(args.Target.Value, EntityManager))), | ||
| args.Args.User, | ||
| args.Args.User, | ||
| PopupType.Medium); | ||
|
|
||
| args.Handled = true; | ||
| } | ||
|
|
||
| /// <summary> | ||
| /// Raise an event on an entity to add the provided production type, | ||
| /// adds the <see cref="ReagentProducerComponent"/> if it does not exist. | ||
| /// </summary> | ||
| /// <param name="entity">Entity to add the production type to.</param> | ||
| /// <param name="production">The <see cref="ReagentProductionTypePrototype"/> to add.</param> | ||
| [PublicAPI] | ||
| public void AddProductionType(EntityUid entity, ReagentProductionTypePrototype production) | ||
| { | ||
| EnsureComp<ReagentProducerComponent>(entity); | ||
|
|
||
| RaiseLocalEvent(entity, new ReagentProductionTypeAdded(production)); | ||
| } | ||
|
|
||
| /// <summary> | ||
| /// Raise an event on an entity to remove the provided production type, | ||
| /// removes the <see cref="ReagentProducerComponent"/> if there is no production type remaining. | ||
| /// </summary> | ||
| /// <param name="entity">Entity to remove the production type from.</param> | ||
| /// <param name="production">The <see cref="ReagentProductionTypePrototype"/> to remove.</param> | ||
| [PublicAPI] | ||
| public void RemoveProductionType(EntityUid entity, ReagentProductionTypePrototype production) | ||
| { | ||
| EnsureComp<ReagentProducerComponent>(entity); | ||
|
|
||
| RaiseLocalEvent(entity, new ReagentProductionTypeRemoved(production)); | ||
| } | ||
|
|
||
| private void ProductionTypeAdded(Entity<ReagentProducerComponent> ent, ref ReagentProductionTypeAdded args) | ||
| { | ||
| if (!_protoManager.TryIndex(args.ProductionType, out var productionType)) | ||
| return; | ||
|
|
||
| ent.Comp.ProductionTypes.Add(args.ProductionType); | ||
|
|
||
| _solutionContainer.EnsureSolution(ent.Owner, productionType.SolutionName,out _, out var solution, productionType.MaximumCapacity); | ||
|
Check failure on line 216 in Content.Shared/_DEN/ReagentProduction/EntitySystems/ReagentProductionSystem.cs
|
||
| solution?.AddReagent(productionType.Reagent, productionType.MaximumCapacity); | ||
| } | ||
|
|
||
| private void ProductionTypeRemoved(Entity<ReagentProducerComponent> ent, ref ReagentProductionTypeRemoved args) | ||
| { | ||
| ent.Comp.ProductionTypes.Remove(args.ProductionType); | ||
| //If there are no more production types, just remove the component | ||
| if (ent.Comp.ProductionTypes.Count == 0) | ||
| RemCompDeferred<ReagentProducerComponent>(ent); | ||
| } | ||
| } | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,55 @@ | ||
| using Content.Shared._DEN.ReagentProduction.EntitySystems; | ||
| using Content.Shared._DEN.ReagentProduction.Prototypes; | ||
| using Content.Shared.DoAfter; | ||
| using Robust.Shared.Prototypes; | ||
| using Robust.Shared.Serialization; | ||
|
|
||
|
|
||
| namespace Content.Shared._DEN.ReagentProduction.Events; | ||
|
|
||
| public sealed class ReagentProductionEvents | ||
| { | ||
| /// <summary> | ||
| /// When this event is called, a production type is added to the entity it's called on. | ||
| /// Do not call this directly, and instead use <see cref="ReagentProductionSystem.AddProductionType"/> | ||
| /// </summary> | ||
| /// <param name="productionType">Production type to add.</param> | ||
| [Serializable, NetSerializable,] | ||
| public sealed class ReagentProductionTypeAdded(ProtoId<ReagentProductionTypePrototype> productionType) : EntityEventArgs | ||
|
honeyed-lemons marked this conversation as resolved.
|
||
| { | ||
| public ProtoId<ReagentProductionTypePrototype> ProductionType { get; } = productionType; | ||
| } | ||
|
|
||
| /// <summary> | ||
| /// When this event is called, a production type is removed from the entity it's called on. | ||
| /// Do not call this directly, and instead use <see cref="ReagentProductionSystem.RemoveProductionType"/> | ||
| /// </summary> | ||
| /// <param name="productionType">Production type to remove.</param> | ||
| [Serializable, NetSerializable,] | ||
| public sealed class ReagentProductionTypeRemoved(ProtoId<ReagentProductionTypePrototype> productionType) : EntityEventArgs | ||
|
honeyed-lemons marked this conversation as resolved.
|
||
| { | ||
| public ProtoId<ReagentProductionTypePrototype> ProductionType { get; } = productionType; | ||
| } | ||
| } | ||
| /// <summary> | ||
| /// Classic doafter event, called when attempting to fill a solution container with a specific production type. | ||
| /// </summary> | ||
| [Serializable, NetSerializable,] | ||
| public sealed partial class ReagentProductionFillEvent : DoAfterEvent | ||
|
honeyed-lemons marked this conversation as resolved.
|
||
| { | ||
| /// <summary> | ||
| /// Production type to fill with. | ||
| /// </summary> | ||
| public ProtoId<ReagentProductionTypePrototype> ProductionType; | ||
|
|
||
| public ReagentProductionFillEvent( ProtoId<ReagentProductionTypePrototype> productionType) | ||
| { | ||
| ProductionType = productionType; | ||
| } | ||
|
|
||
| public override DoAfterEvent Clone() | ||
| { | ||
| return this; | ||
| } | ||
| } | ||
|
|
||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,59 @@ | ||
| using Content.Shared.Chemistry.Reagent; | ||
| using Content.Shared.FixedPoint; | ||
| using Robust.Shared.Prototypes; | ||
| using Robust.Shared.Utility; | ||
|
|
||
| namespace Content.Shared._DEN.ReagentProduction.Prototypes; | ||
|
|
||
| [Prototype] | ||
| public sealed partial class ReagentProductionTypePrototype : IPrototype | ||
| { | ||
| [IdDataField] | ||
| public string ID { get; private set; } = default!; | ||
|
|
||
| /// <summary> | ||
| /// Reagent to produce | ||
| /// </summary> | ||
| [DataField] | ||
| public ProtoId<ReagentPrototype> Reagent = "Water"; | ||
| /// <summary> | ||
| /// The solution to produce into | ||
| /// </summary> | ||
| [DataField] | ||
| public string SolutionName = "balls"; //cum is stored in the balls? | ||
| /// <summary> | ||
| /// Maximum capacity of the solution | ||
| /// </summary> | ||
| [DataField] | ||
| public FixedPoint2 MaximumCapacity = 30; | ||
|
|
||
| /// <summary> | ||
| /// Maximum amount you can expel at once | ||
| /// </summary> | ||
| [DataField] | ||
| public FixedPoint2 MaximumLoad = 10; | ||
|
|
||
| [DataField] | ||
| public TimeSpan DoAfterLength = TimeSpan.FromSeconds(3); | ||
|
|
||
| /// <summary> | ||
| /// How many units are produced each update | ||
| /// </summary> | ||
| [DataField] | ||
| public FixedPoint2 UnitsPerProduction = 5; | ||
|
|
||
| /// <summary> | ||
| /// Determines if the verb icon is NSFW or not.. I'd love to specify an actual texture here but YOU CANT SPECIFY SPECIFIC TEXTURES IN YAML !!!!!!!!!! | ||
| /// </summary> | ||
| [DataField] | ||
| public bool NsfwVerbIcon; | ||
|
|
||
| /// <summary> | ||
| /// Popup that occurs when your solution is empty | ||
| /// </summary> | ||
| [DataField] | ||
| public string DryPopup = "cum-verb-dry"; | ||
|
|
||
| [DataField] | ||
| public string SuccessPopup = "cum-verb-success"; | ||
| } |
Uh oh!
There was an error while loading. Please reload this page.