Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
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;

[RegisterComponent, NetworkedComponent, AutoGenerateComponentState]
public sealed partial class ReagentProducerComponent : Component
{
[DataField, AutoNetworkedField]
public List<ProtoId<ReagentProductionTypePrototype>> ProductionTypes = [];

/// <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,206 @@
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 Robust.Shared.Prototypes;
using Robust.Shared.Timing;
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!;
[Dependency] private readonly IPrototypeManager _protoManager = default!;
[Dependency] private readonly MobStateSystem _mobState = default!;
[Dependency] private readonly SharedDoAfterSystem _doAfter = default!;
[Dependency] private readonly SharedPopupSystem _popup = default!;
[Dependency] private readonly SharedSolutionContainerSystem _solutionContainer = default!;

public static readonly VerbCategory ReagentFillCategory = new("verb-categories-fill", "/Textures/_DEN/Interface/VerbIcons/lewd.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);

if (solution == null)
continue;
// do some math to figure out how much we can add
var amountToAdd = FixedPoint2.Clamp(
solution.MaxVolume - solution.Volume,
FixedPoint2.Zero,
productionType.UnitsPerProduction);

if (amountToAdd <= 0)
return;
//and add it :)
solution.AddReagent(productionType.Reagent, amountToAdd);
}
}
}

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;

var verb = new InteractionVerb
{
Category = ReagentFillCategory,
Act = () => StartFillDoAfter((user, producerComp), container, productionTypeId),
Text = CultureInfo.CurrentCulture.TextInfo.ToTitleCase(reagent.LocalizedName),
Priority = -1,
CloseMenu = false,
};
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;
}

public void AddProductionType(EntityUid entity, ReagentProductionTypePrototype prototypeType)
{
EnsureComp<ReagentProducerComponent>(entity);

RaiseLocalEvent(entity, new ReagentProductionTypeAdded(prototypeType));
}

public void RemoveProductionType(EntityUid entity, ReagentProductionTypePrototype prototypeType)
{
EnsureComp<ReagentProducerComponent>(entity);

RaiseLocalEvent(entity, new ReagentProductionTypeRemoved(prototypeType));
}

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);
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,39 @@
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
{
[Serializable, NetSerializable,]
public sealed class ReagentProductionTypeAdded(ProtoId<ReagentProductionTypePrototype> productionType) : EntityEventArgs
{
public ProtoId<ReagentProductionTypePrototype> ProductionType { get; } = productionType;
}

[Serializable, NetSerializable,]
public sealed class ReagentProductionTypeRemoved(ProtoId<ReagentProductionTypePrototype> productionType) : EntityEventArgs
{
public ProtoId<ReagentProductionTypePrototype> ProductionType { get; } = productionType;
}
}

[Serializable, NetSerializable,]
public sealed partial class ReagentProductionFillEvent : DoAfterEvent
{
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,52 @@
using Content.Shared.Chemistry.Reagent;
using Content.Shared.FixedPoint;
using Robust.Shared.Prototypes;

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>
/// Popup that occurs when your solution is empty
/// </summary>
[DataField]
public string DryPopup = "cum-verb-dry";

[DataField]
public string SuccessPopup = "cum-verb-success";
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,46 @@
using Content.Shared._DEN.ReagentProduction.EntitySystems;
using Content.Shared._DEN.ReagentProduction.Prototypes;
using JetBrains.Annotations;
using Robust.Shared.Prototypes;

namespace Content.Shared._DEN.Traits.TraitFunctions;

[UsedImplicitly]
public sealed partial class AddReagentProductionTrait : ITraitFunction
{
/// <summary>
/// Reagent Production types this trait provides.
/// </summary>
[DataField(required: true)] public List<ProtoId<ReagentProductionTypePrototype>> ProductionTypes { get; private set; } = [];

[ViewVariables] public List<IComponent>? AddedComponents = null;

public void OnTraitAdded(EntityUid owner, EntityManager entityManager)
{

var prototypeManager = IoCManager.Resolve<IPrototypeManager>();
var reagentProduction = entityManager.System<ReagentProductionSystem>();

foreach (var productionType in ProductionTypes)
{
if (!prototypeManager.TryIndex(productionType, out var prototype))
continue;

reagentProduction.AddProductionType(owner, prototype);
}
}

public void OnTraitRemoved(EntityUid owner, EntityManager entityManager)
{
var prototypeManager = IoCManager.Resolve<IPrototypeManager>();
var reagentProduction = entityManager.System<ReagentProductionSystem>();

foreach (var productionType in ProductionTypes)
{
if (!prototypeManager.TryIndex(productionType, out var prototype))
continue;

reagentProduction.RemoveProductionType(owner, prototype);
}
}
}
2 changes: 2 additions & 0 deletions Resources/Locale/en-US/_DEN/guidebook/guides.ftl
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
# Chemical Tabs
guide-entry-lewd = Lewd
5 changes: 5 additions & 0 deletions Resources/Locale/en-US/_DEN/reagents/meta/chemicals.ftl
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
reagent-name-glycerol = glycerol
reagent-desc-glycerol = A sweet tasting, colorless, and oderless hydrating chemical.

reagent-name-hydro-cellulose = hydroxyethyl cellulose
reagent-desc-hydro-cellulose = A commonly used thickening agent, often found in foods, lubes, and cosmetics.
11 changes: 11 additions & 0 deletions Resources/Locale/en-US/_DEN/reagents/meta/lewd.ftl
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
reagent-name-cum = cum
reagent-desc-cum = A sticky cloudy-white liquid.

reagent-name-nat-lube = natural lubricant
reagent-desc-nat-lube = A slippery clear liquid.

reagent-name-synth-cum = synthetic cum
reagent-desc-synth-cum = A flavorless lubricant made to look like cum.

reagent-name-synth-lube = synthetic lube
reagent-desc-synth-lube = Flavorless lubricant, made with maximum slipperyness in mind! Silicon free.
1 change: 1 addition & 0 deletions Resources/Locale/en-US/_DEN/traits/categories.ftl
Original file line number Diff line number Diff line change
@@ -1,2 +1,3 @@
trait-category-species-morphs = Morphotypes
trait-category-species-specific = Species-specific
trait-category-lewd = Lewd
Loading