Skip to content
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

The CUM Update - First Iteration. #49

Merged
merged 9 commits into from
Aug 5, 2024
36 changes: 36 additions & 0 deletions Content.Server/FloofStation/Traits/HasBoobsComponent.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
using Content.Shared.FixedPoint;
using Content.Shared.Chemistry.Components;
using Content.Shared.Chemistry.Reagent;
using Robust.Shared.Prototypes;
using Robust.Shared.Serialization.TypeSerializers.Implementations.Custom;

namespace Content.Server.FloofStation.Traits;

[RegisterComponent, Access(typeof(HasBoobsSystem))]
public sealed partial class HasBoobsComponent : Component
{
[DataField, ViewVariables(VVAccess.ReadOnly)]
public ProtoId<ReagentPrototype> ReagentId = "Milk";

[DataField, ViewVariables(VVAccess.ReadOnly)]
public string SolutionName = "breasts";

[DataField]
public Entity<SolutionComponent>? Solution = null;

[DataField, ViewVariables(VVAccess.ReadOnly)]
public FixedPoint2 QuantityPerUpdate = 25;

[DataField]
public FixedPoint2 CumMaxVolume = FixedPoint2.New(200);

[DataField, ViewVariables(VVAccess.ReadWrite)]
public float HungerUsage = 10f;

[DataField, ViewVariables(VVAccess.ReadWrite)]
public TimeSpan GrowthDelay = TimeSpan.FromMinutes(1);

[DataField(customTypeSerializer: typeof(TimeOffsetSerializer)), ViewVariables(VVAccess.ReadWrite)]
public TimeSpan NextGrowth = TimeSpan.FromSeconds(0);
}

156 changes: 156 additions & 0 deletions Content.Server/FloofStation/Traits/HasBoobsSystem.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,156 @@
using Content.Server.Chemistry.Containers.EntitySystems;
using Content.Server.Popups;
using Content.Shared.Chemistry.Components;
using Content.Shared.DoAfter;
using Content.Shared.IdentityManagement;
using Content.Shared.Mobs.Systems;
using Content.Shared.Nutrition.Components;
using Content.Shared.Nutrition.EntitySystems;
using Content.Shared.Popups;
using Content.Shared.Verbs;
using Content.Shared.FloofStation.Traits.Events;
using Robust.Shared.Timing;
using JetBrains.Annotations;

namespace Content.Server.FloofStation.Traits;

[UsedImplicitly]
public sealed class HasBoobsSystem : EntitySystem
{
[Dependency] private readonly HungerSystem _hunger = default!;
[Dependency] private readonly IGameTiming _timing = default!;
[Dependency] private readonly MobStateSystem _mobState = default!;
[Dependency] private readonly PopupSystem _popupSystem = default!;
[Dependency] private readonly SharedDoAfterSystem _doAfterSystem = default!;
[Dependency] private readonly SolutionContainerSystem _solutionContainer = default!;

public override void Initialize()
{
base.Initialize();

SubscribeLocalEvent<HasBoobsComponent, ComponentInit>(OnComponentInit);
SubscribeLocalEvent<HasBoobsComponent, GetVerbsEvent<AlternativeVerb>>(AddMilkVerb);
SubscribeLocalEvent<HasBoobsComponent, MilkingDoAfterEvent>(OnDoAfter);
}

//From UdderSystem.cs
public override void Update(float frameTime)
{
base.Update(frameTime);

var query = EntityQueryEnumerator<HasBoobsComponent>();
var now = _timing.CurTime;
while (query.MoveNext(out var uid, out var boobs))
{
if (now < boobs.NextGrowth)
continue;

boobs.NextGrowth = now + boobs.GrowthDelay;

if (_mobState.IsDead(uid))
continue;

// Actually there is food digestion so no problem with instant reagent generation "OnFeed"
if (EntityManager.TryGetComponent(uid, out HungerComponent? hunger))
{
// Is there enough nutrition to produce reagent?
if (_hunger.GetHungerThreshold(hunger) < HungerThreshold.Okay)
continue;

_hunger.ModifyHunger(uid, -boobs.HungerUsage, hunger);
}

if (!_solutionContainer.ResolveSolution(uid, boobs.SolutionName, ref boobs.Solution))
continue;

_solutionContainer.TryAddReagent(boobs.Solution.Value, boobs.ReagentId, boobs.QuantityPerUpdate, out _);
}
}

private void AttemptMilk(Entity<HasBoobsComponent?> penis, EntityUid userUid, EntityUid containerUid)
{
if (!Resolve(penis, ref penis.Comp))
return;

var doargs = new DoAfterArgs(EntityManager, userUid, 5, new MilkingDoAfterEvent(), penis, penis, used: containerUid)
{
BreakOnUserMove = true,
BreakOnDamage = true,
BreakOnTargetMove = true,
MovementThreshold = 1.0f,
};

_doAfterSystem.TryStartDoAfter(doargs);
}

private void OnDoAfter(Entity<HasBoobsComponent> entity, ref MilkingDoAfterEvent args)
{
if (args.Cancelled || args.Handled || args.Args.Used == null)
return;

if (!_solutionContainer.ResolveSolution(entity.Owner, entity.Comp.SolutionName, ref entity.Comp.Solution, out var solution))
return;

if (!_solutionContainer.TryGetRefillableSolution(args.Args.Used.Value, out var targetSoln, out var targetSolution))
return;

args.Handled = true;
var quantity = solution.Volume;
if (quantity == 0)
{
_popupSystem.PopupEntity(Loc.GetString("milk-verb-dry"), entity.Owner, args.Args.User);
return;
}

if (quantity > targetSolution.AvailableVolume)
quantity = targetSolution.AvailableVolume;

var split = _solutionContainer.SplitSolution(entity.Comp.Solution.Value, quantity);
_solutionContainer.TryAddSolution(targetSoln.Value, split);

_popupSystem.PopupEntity(Loc.GetString("milk-verb-success", ("amount", quantity), ("target", Identity.Entity(args.Args.Used.Value, EntityManager))), entity.Owner,
args.Args.User, PopupType.Medium);
}

//Based on BloodstreamSystem.cs
private void OnComponentInit(Entity<HasBoobsComponent> entity, ref ComponentInit args)
{
var cumSolution = _solutionContainer.EnsureSolution(entity.Owner, entity.Comp.SolutionName);

cumSolution.MaxVolume = entity.Comp.CumMaxVolume;

// Fill breasts solution with MILK
cumSolution.AddReagent(entity.Comp.ReagentId, entity.Comp.CumMaxVolume - cumSolution.Volume);
}

//Based on UdderSystem.cs
/*TO-DO:
* Check for suit (-loincloth) to prevent action?
* Be able to milk yourself w/o a container.
* -Add a check for a container in the active hand? Yes = fill container. No = Milk go on ground.
* Better text for actions. (Says you/your instead of the person.)
*/
public void AddMilkVerb(Entity<HasBoobsComponent> entity, ref GetVerbsEvent<AlternativeVerb> args)
{
if (args.Using == null ||
!args.CanInteract ||
!EntityManager.HasComponent<RefillableSolutionComponent>(args.Using.Value)) //see if removing this part lets you milk on the ground.
return;

var cumContainer = entity.Comp.Solution;
var uid = entity.Owner;
var user = args.User;
var used = args.Using.Value;

AlternativeVerb verb = new()
{
Act = () =>
{
AttemptMilk(uid, user, used);
},
Text = Loc.GetString("milk-verb-get-text"),
Priority = 2
};
args.Verbs.Add(verb);
}
}
36 changes: 36 additions & 0 deletions Content.Server/FloofStation/Traits/HasPenisComponent.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
using Content.Shared.FixedPoint;
using Content.Shared.Chemistry.Components;
using Content.Shared.Chemistry.Reagent;
using Robust.Shared.Prototypes;
using Robust.Shared.Serialization.TypeSerializers.Implementations.Custom;

namespace Content.Server.FloofStation.Traits;

[RegisterComponent, Access(typeof(HasPenisSystem))]
public sealed partial class HasPenisComponent : Component
{
[DataField, ViewVariables(VVAccess.ReadOnly)]
public ProtoId<ReagentPrototype> ReagentId = "Cum";

[DataField, ViewVariables(VVAccess.ReadOnly)]
public string SolutionName = "penis";

[DataField]
public Entity<SolutionComponent>? Solution = null;

[DataField, ViewVariables(VVAccess.ReadOnly)]
public FixedPoint2 QuantityPerUpdate = 25;

[DataField]
public FixedPoint2 CumMaxVolume = FixedPoint2.New(100);

[DataField, ViewVariables(VVAccess.ReadWrite)]
public float HungerUsage = 10f;

[DataField, ViewVariables(VVAccess.ReadWrite)]
public TimeSpan GrowthDelay = TimeSpan.FromMinutes(1);

[DataField(customTypeSerializer: typeof(TimeOffsetSerializer)), ViewVariables(VVAccess.ReadWrite)]
public TimeSpan NextGrowth = TimeSpan.FromSeconds(0);
}

156 changes: 156 additions & 0 deletions Content.Server/FloofStation/Traits/HasPenisSystem.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,156 @@
using Content.Server.Chemistry.Containers.EntitySystems;
using Content.Server.Popups;
using Content.Shared.Chemistry.Components;
using Content.Shared.DoAfter;
using Content.Shared.IdentityManagement;
using Content.Shared.Mobs.Systems;
using Content.Shared.Nutrition.Components;
using Content.Shared.Nutrition.EntitySystems;
using Content.Shared.Popups;
using Content.Shared.Verbs;
using Content.Shared.FloofStation.Traits.Events;
using Robust.Shared.Timing;
using JetBrains.Annotations;

namespace Content.Server.FloofStation.Traits;

[UsedImplicitly]
public sealed class HasPenisSystem : EntitySystem
{
[Dependency] private readonly HungerSystem _hunger = default!;
[Dependency] private readonly IGameTiming _timing = default!;
[Dependency] private readonly MobStateSystem _mobState = default!;
[Dependency] private readonly PopupSystem _popupSystem = default!;
[Dependency] private readonly SharedDoAfterSystem _doAfterSystem = default!;
[Dependency] private readonly SolutionContainerSystem _solutionContainer = default!;

public override void Initialize()
{
base.Initialize();

SubscribeLocalEvent<HasPenisComponent, ComponentInit>(OnComponentInit);
SubscribeLocalEvent<HasPenisComponent, GetVerbsEvent<AlternativeVerb>>(AddCumVerb);
SubscribeLocalEvent<HasPenisComponent, CummingDoAfterEvent>(OnDoAfter);
}

//From UdderSystem.cs
public override void Update(float frameTime)
{
base.Update(frameTime);

var query = EntityQueryEnumerator<HasPenisComponent>();
var now = _timing.CurTime;
while (query.MoveNext(out var uid, out var penis))
{
if (now < penis.NextGrowth)
continue;

penis.NextGrowth = now + penis.GrowthDelay;

if (_mobState.IsDead(uid))
continue;

// Actually there is food digestion so no problem with instant reagent generation "OnFeed"
if (EntityManager.TryGetComponent(uid, out HungerComponent? hunger))
{
// Is there enough nutrition to produce reagent?
if (_hunger.GetHungerThreshold(hunger) < HungerThreshold.Okay)
continue;

_hunger.ModifyHunger(uid, -penis.HungerUsage, hunger);
}

if (!_solutionContainer.ResolveSolution(uid, penis.SolutionName, ref penis.Solution))
continue;

_solutionContainer.TryAddReagent(penis.Solution.Value, penis.ReagentId, penis.QuantityPerUpdate, out _);
}
}

private void AttemptCum(Entity<HasPenisComponent?> penis, EntityUid userUid, EntityUid containerUid)
{
if (!Resolve(penis, ref penis.Comp))
return;

var doargs = new DoAfterArgs(EntityManager, userUid, 5, new CummingDoAfterEvent(), penis, penis, used: containerUid)
{
BreakOnUserMove = true,
BreakOnDamage = true,
BreakOnTargetMove = true,
MovementThreshold = 1.0f,
};

_doAfterSystem.TryStartDoAfter(doargs);
}

private void OnDoAfter(Entity<HasPenisComponent> entity, ref CummingDoAfterEvent args)
{
if (args.Cancelled || args.Handled || args.Args.Used == null)
return;

if (!_solutionContainer.ResolveSolution(entity.Owner, entity.Comp.SolutionName, ref entity.Comp.Solution, out var solution))
return;

if (!_solutionContainer.TryGetRefillableSolution(args.Args.Used.Value, out var targetSoln, out var targetSolution))
return;

args.Handled = true;
var quantity = solution.Volume;
if (quantity == 0)
{
_popupSystem.PopupEntity(Loc.GetString("cum-verb-dry"), entity.Owner, args.Args.User);
return;
}

if (quantity > targetSolution.AvailableVolume)
quantity = targetSolution.AvailableVolume;

var split = _solutionContainer.SplitSolution(entity.Comp.Solution.Value, quantity);
_solutionContainer.TryAddSolution(targetSoln.Value, split);

_popupSystem.PopupEntity(Loc.GetString("cum-verb-success", ("amount", quantity), ("target", Identity.Entity(args.Args.Used.Value, EntityManager))), entity.Owner,
args.Args.User, PopupType.Medium);
}

//Based on BloodstreamSystem.cs
private void OnComponentInit(Entity<HasPenisComponent> entity, ref ComponentInit args)
{
var cumSolution = _solutionContainer.EnsureSolution(entity.Owner, entity.Comp.SolutionName);

cumSolution.MaxVolume = entity.Comp.CumMaxVolume;

// Fill penis solution with CUM
cumSolution.AddReagent(entity.Comp.ReagentId, entity.Comp.CumMaxVolume - cumSolution.Volume);
}

//Based on UdderSystem.cs
/*TO-DO/Design choices:
* Check for suit (-loincloth) to prevent action?
* Be able to cum on the ground w/o a container.
* -Add a check for a container in the active hand? Yes = fill container. No = Splooge on the ground.
* Better text for actions. (Says you/your instead of the person.)
*/
public void AddCumVerb(Entity<HasPenisComponent> entity, ref GetVerbsEvent<AlternativeVerb> args)
{
if (args.Using == null ||
!args.CanInteract ||
!EntityManager.HasComponent<RefillableSolutionComponent>(args.Using.Value)) //see if removing this part lets you cum on the ground?
return;

var cumContainer = entity.Comp.Solution;
var uid = entity.Owner;
var user = args.User;
var used = args.Using.Value;

AlternativeVerb verb = new()
{
Act = () =>
{
AttemptCum(uid, user, used);
},
Text = Loc.GetString("cum-verb-get-text"),
Priority = 2
};
args.Verbs.Add(verb);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
using Content.Shared.DoAfter;
using Robust.Shared.Serialization;

namespace Content.Shared.FloofStation.Traits.Events;

[Serializable, NetSerializable]
public sealed partial class CummingDoAfterEvent : SimpleDoAfterEvent
{
}
Loading
Loading