CUM 4 - The Backending (#3899)

## About The Pull Request

As you know, every ss13 spessman is a system of beakers connected with
tubes; usually 2. This PR adds to that;
The genital organs used to operate on snowflake shitcode that was
basically trying to replicate the reagent system while not really doing
it, and not even being used. This PR aims to make the genitals (which
for some reason include breasts, don't ask) actually use normal reagent
code, and lays the groundwork for further PRs implementing
species-specific and perhaps quirk-selected functionality.

From the player side; you won't notice that much changing other than how
much you cum into beakers and some arousal tweaks (mostly removing the
really dumb stuff) - the only major feature is that you can cum into
someone's mouth.

## Why It's Good For The Game

shitcode = bad, more robust functionality = good
the whole pr mostly exists to enable other stuff down the line

## Proof Of Testing

I did test it on localhost, seems to be working fine

## Changelog

🆑
add: cumming into someone's mouth actually makes them ingest cum
code: orgasm and arousal tweaks
code: completely changed how organs/genital work under the hood, now
actually uses reagent system
/🆑

---------

Co-authored-by: Roxy <75404941+TealSeer@users.noreply.github.com>
This commit is contained in:
sippykot
2025-07-14 21:53:40 -04:00
committed by GitHub
co-authored by Roxy
parent fdb8396c8e
commit a9bdb687d8
10 changed files with 92 additions and 117 deletions
@@ -7,6 +7,10 @@
#define CLIMAX_OPEN_CONTAINER "Fill reagent container"
#define CLIMAX_PORTAL "Through the portal"
// both of these are pretty much arbitrary
#define MIN_CUM_THRESHOLD 5
#define MIN_VAGINA_WETNESS_THRESHOLD 3.33
/mob/living/carbon/human
/// Used to prevent nightmare scenarios.
var/refractory_period
@@ -55,22 +59,27 @@
var/self_orgasm = FALSE
var/self_their = p_their()
var/obj/item/organ/genital/testicles/testicles = get_organ_slot(ORGAN_SLOT_TESTICLES)
testicles?.calculate_cumshot()
if(climax_choice == CLIMAX_PENIS || climax_choice == CLIMAX_BOTH)
var/obj/item/organ/genital/penis/penis = get_organ_slot(ORGAN_SLOT_PENIS)
if(!get_organ_slot(ORGAN_SLOT_TESTICLES)) //If we have no god damn balls, we can't cum anywhere... GET BALLS!
if(!testicles || testicles.reagents.total_volume < MIN_CUM_THRESHOLD) //If we have no god damn balls, we can't cum anywhere... GET BALLS! , OR theres so little in your balls that nothing comes out...
visible_message(span_userlove("[src] orgasms, but nothing comes out of [self_their] penis!"), \
span_userlove("You orgasm, it feels great, but nothing comes out of your penis!"))
else if(is_wearing_condom())
var/obj/item/clothing/sextoy/condom/condom = src.penis // bruh 💀⚰️💀⚰️💀⚰️💀⚰️💀
condom.condom_use()
condom.condom_use() // condoms probably should become reagent containers at some point
visible_message(span_userlove("[src] shoots [self_their] load into the [condom], filling it up!"), \
span_userlove("You shoot your thick load into the [condom] and it catches it all!"))
testicles.reagents.remove_all(testicles.cumshot_size)
else if(!is_bottomless() && penis.visibility_preference != GENITAL_ALWAYS_SHOW)
visible_message(span_userlove("[src] cums inside [self_their] clothes!"), \
span_userlove("You shoot your load, but you weren't naked, so you mess up your clothes!"))
self_orgasm = TRUE
testicles.reagents.remove_all(testicles.cumshot_size)
else
var/list/interactable_inrange_humans = list()
@@ -105,6 +114,7 @@
create_cum_decal = TRUE
visible_message(span_userlove("[src] shoots [self_their] sticky load onto the floor!"), \
span_userlove("You shoot string after string of hot cum, hitting the floor!"))
testicles.reagents.remove_all(testicles.cumshot_size)
else if(penis_climax_choice == CLIMAX_OPEN_CONTAINER)
var/target_choice = tgui_input_list(src, "Choose a container to cum into.", "Choose target!", interactable_inrange_open_containers)
@@ -112,12 +122,12 @@
create_cum_decal = TRUE
visible_message(span_userlove("[src] shoots [self_their] sticky load onto the floor!"), \
span_userlove("You shoot string after string of hot cum, hitting the floor!"))
testicles.reagents.remove_all(testicles.cumshot_size)
else
var/obj/item/reagent_containers/cup/target_open_container = interactable_inrange_open_containers[target_choice]
if(target_open_container.is_refillable() && target_open_container.is_drainable())
// here's where we actually do the cumming(?)
var/obj/item/organ/genital/testicles/src_testicles = src.get_organ_slot(ORGAN_SLOT_TESTICLES)
var/cum_volume = src_testicles.genital_size * 10
var/cum_volume = testicles.cumshot_size
var/total_volume_w_cum = cum_volume + target_open_container.reagents.total_volume
conditional_pref_sound(get_turf(src), SFX_DESECRATION, 50, TRUE, pref_to_check = /datum/preference/toggle/erp/sounds)
if(target_open_container.reagents.holder_full())
@@ -126,7 +136,7 @@
visible_message(span_userlove("[src] tries to cum into the [target_open_container], but it's already full, spilling their hot load onto the floor!"), \
span_userlove("You try to cum into the [target_open_container], but it's already full, so it all hits the floor instead!"))
else
target_open_container.reagents.add_reagent(/datum/reagent/consumable/cum, cum_volume)
testicles.reagents.trans_to(target_open_container, testicles.cumshot_size, transferred_by = src)
if(total_volume_w_cum > target_open_container.volume)
// overflow, make the decal
add_cum_splatter_floor(get_turf(target_open_container))
@@ -140,6 +150,7 @@
create_cum_decal = TRUE
visible_message(span_userlove("[src] shoots [self_their] sticky load onto the floor!"), \
span_userlove("You shoot string after string of hot cum, hitting the floor!"))
testicles.reagents.remove_all(testicles.cumshot_size)
else if(penis_climax_choice == CLIMAX_PORTAL)
to_chat(src, "You shoot string after string of hot cum, hitting whatever is on the other side!")
@@ -181,12 +192,18 @@
visible_message(span_userlove("[src] shoots their sticky load onto [target_human]!"), \
span_userlove("You shoot string after string of hot cum onto [target_human]!"))
else
visible_message(span_userlove("[src] hilts [self_their] cock into [target_human]'s [climax_into_choice], shooting cum into [target_human_them]!"), \
visible_message(
span_userlove("[src] hilts [self_their] cock into [target_human]'s [climax_into_choice], shooting cum into [target_human_them]!"),
span_userlove("You hilt your cock into [target_human]'s [climax_into_choice], shooting cum into [target_human_them]!"))
to_chat(target_human, span_userlove("Your [climax_into_choice] fills with warm cum as [src] shoots [self_their] load into it."))
var/obj/item/organ/genital/testicles/testicles = get_organ_slot(ORGAN_SLOT_TESTICLES)
testicles.transfer_internal_fluid(null, testicles.internal_fluid_count * 0.6) // yep. we are sending semen to nullspace
if(climax_into_choice == "mouth")
testicles.reagents.trans_to(target_human, testicles.cumshot_size, transferred_by = src, methods = INGEST)
// if someone wanted to, they could add code for actually putting cum inside of a vagina since it's a reagent container now...
// would need to change up the fluid_generation.dm and tweak the organ itself for it to make sense though
// same for other organs
if(create_cum_decal)
add_cum_splatter_floor(get_turf(src))
@@ -201,12 +218,21 @@
if(climax_choice == CLIMAX_VAGINA || climax_choice == CLIMAX_BOTH)
var/obj/item/organ/genital/vagina/vagina = get_organ_slot(ORGAN_SLOT_VAGINA)
if(is_bottomless() || vagina.visibility_preference == GENITAL_ALWAYS_SHOW)
visible_message(span_userlove("[src] twitches and moans as [p_they()] climax from their vagina!"), span_userlove("You twitch and moan as you climax from your vagina!"))
add_cum_splatter_floor(get_turf(src), female = TRUE)
visible_message(
span_userlove("[src] twitches and moans as [p_they()] climax from their vagina!"),
span_userlove("You twitch and moan as you climax from your vagina!"))
if(vagina.reagents.total_volume >= MIN_VAGINA_WETNESS_THRESHOLD)
add_cum_splatter_floor(get_turf(src), female = TRUE)
else
visible_message(span_userlove("[src] cums in [self_their] underwear from [self_their] vagina!"), \
span_userlove("You cum in your underwear from your vagina! Eww."))
self_orgasm = TRUE
if(vagina.reagents.total_volume >= MIN_VAGINA_WETNESS_THRESHOLD)
visible_message(
span_userlove("[src] cums in [self_their] underwear from [self_their] vagina!"),
span_userlove("You cum in your underwear from your vagina! Eww."))
self_orgasm = TRUE
else
visible_message(
span_userlove("[src] cums in [self_their] underwear from [self_their] vagina!"),
span_userlove("You cum in your underwear from your vagina, but you aren't wet enough to mess it up."))
apply_status_effect(/datum/status_effect/climax)
apply_status_effect(/datum/status_effect/climax_cooldown)
@@ -220,4 +246,7 @@
#undef CLIMAX_ON_FLOOR
#undef CLIMAX_IN_OR_ON
#undef CLIMAX_OPEN_CONTAINER
#undef MIN_CUM_THRESHOLD
#undef MIN_VAGINA_WETNESS_THRESHOLD
#undef CLIMAX_PORTAL
@@ -3,7 +3,6 @@
#define MASOCHISM_PAIN_OFFSET 0.5
#define NEVERBONER_PLEASURE_OFFSET 50
#define BLUEBALL_AROUSAL_MODIFIER 0.08
#define TOO_MUCH_PAIN_MODIFIER 0.1
#define ITS_PRETTY_HOT_IN_HERE_MODIFIER 0.1
@@ -25,10 +24,6 @@
var/temp_pleasure = BASE_PAIN_AND_PLEASURE_ADJUSTMENT
var/temp_pain = BASE_PAIN_AND_PLEASURE_ADJUSTMENT
var/obj/item/organ/genital/testicles/balls = affected_mob.get_organ_slot(ORGAN_SLOT_TESTICLES)
if(balls && balls.internal_fluid_full())
temp_arousal += BLUEBALL_AROUSAL_MODIFIER
if(HAS_TRAIT(affected_mob, TRAIT_MASOCHISM))
temp_pain -= MASOCHISM_PAIN_OFFSET
if(HAS_TRAIT(affected_mob, TRAIT_NEVERBONER))
@@ -55,7 +50,6 @@
#undef MASOCHISM_PAIN_OFFSET
#undef NEVERBONER_PLEASURE_OFFSET
#undef BLUEBALL_AROUSAL_MODIFIER
#undef TOO_MUCH_PAIN_MODIFIER
#undef ITS_PRETTY_HOT_IN_HERE_MODIFIER
@@ -15,6 +15,10 @@
duration = STATUS_EFFECT_PERMANENT
alert_type = null
//
// VAGINA
//
/datum/status_effect/body_fluid_regen/vagina
id = "vagina fluid regen"
@@ -28,10 +32,14 @@
return FALSE
if(affected_human.arousal > AROUSAL_LOW)
var/regen = (affected_human.arousal / AROUSAL_MULTIPLIER) * (vagina.internal_fluid_maximum / VAGINA_MULTIPLIER) * BASE_MULTIPLIER
vagina.adjust_internal_fluid(regen)
var/regen = (affected_human.arousal / AROUSAL_MULTIPLIER) * (vagina.reagents.maximum_volume / VAGINA_MULTIPLIER) * BASE_MULTIPLIER
vagina.reagents.add_reagent(vagina.internal_fluid_datum, regen)
else
vagina.adjust_internal_fluid(VAGINA_FLUID_REMOVAL_AMOUNT)
vagina.reagents.remove_reagent(vagina.internal_fluid_datum, VAGINA_FLUID_REMOVAL_AMOUNT)
//
// BALLS
//
/datum/status_effect/body_fluid_regen/testes
id = "testes fluid regen"
@@ -42,11 +50,13 @@
return FALSE
var/obj/item/organ/genital/testicles/testes = owner.get_organ_slot(ORGAN_SLOT_TESTICLES)
if(!testes || (affected_human.arousal < AROUSAL_LOW))
return FALSE
var/regen = (affected_human.arousal / AROUSAL_MULTIPLIER) * (testes.internal_fluid_maximum / TESTES_MULTIPLIER) * BASE_MULTIPLIER
testes.internal_fluid_count += regen
var/regen = (50 / AROUSAL_MULTIPLIER) * (testes.reagents.maximum_volume / TESTES_MULTIPLIER) * BASE_MULTIPLIER // this is really quite stupid, the bare number is replacing the arousal value previously there
testes.reagents.add_reagent(testes.internal_fluid_datum, regen)
//
// BREASTS
//
/datum/status_effect/body_fluid_regen/breasts
id = " breast milk regen"
@@ -60,10 +70,15 @@
if(!breasts || !breasts.lactates)
return FALSE
var/regen = ((owner.nutrition / (NUTRITION_LEVEL_WELL_FED / NUTRITION_MULTIPLIER)) / NUTRITION_MULTIPLIER) * (breasts.internal_fluid_maximum / BREASTS_MULTIPLIER) * BASE_MULTIPLIER
if(!breasts.internal_fluid_full())
var/regen = ((owner.nutrition / (NUTRITION_LEVEL_WELL_FED / NUTRITION_MULTIPLIER)) / NUTRITION_MULTIPLIER) * (breasts.reagents.maximum_volume / BREASTS_MULTIPLIER) * BASE_MULTIPLIER
if(breasts.reagents.total_volume < breasts.reagents.maximum_volume)
var/free_space = breasts.reagents.maximum_volume
var/occp_space = breasts.reagents.total_volume
free_space -= occp_space // how much free space remaining?
if(regen > free_space)
regen = free_space // so we aren't draining nutrition for milk that isn't actually being generated
owner.adjust_nutrition(-regen / NUTRITION_COST_MULTIPLIER)
breasts.adjust_internal_fluid(regen)
breasts.reagents.add_reagent(breasts.internal_fluid_datum, regen)
#undef AROUSAL_MULTIPLIER
#undef TESTES_MULTIPLIER
@@ -1,62 +0,0 @@
#define CUM_VOLUME_MULTIPLIER 10
/obj/item/hand_item/coom
name = "cum"
desc = "C-can I watch...?"
icon = 'icons/obj/service/hydroponics/harvest.dmi'
icon_state = "eggplant"
inhand_icon_state = "nothing"
// Jerk off into bottles and onto people.
/obj/item/hand_item/coom/interact_with_atom(atom/interacting_with, mob/living/user, list/modifiers)
do_masturbate(interacting_with, user)
/// Handles masturbation onto a living mob, or an atom.
/// Attempts to fill the atom's reagent container, if it has one, and it isn't full.
/obj/item/hand_item/coom/proc/do_masturbate(atom/target, mob/user)
if (CONFIG_GET(flag/disable_erp_preferences) || user.stat >= DEAD)
return
var/mob/living/carbon/human/affected_human = user
var/obj/item/organ/genital/testicles/testicles = affected_human.get_organ_slot(ORGAN_SLOT_TESTICLES)
var/obj/item/organ/genital/penis/penis = affected_human.get_organ_slot(ORGAN_SLOT_PENIS)
var/datum/sprite_accessory/genital/penis_sprite = SSaccessories.sprite_accessories[ORGAN_SLOT_PENIS][affected_human.dna.species.mutant_bodyparts[ORGAN_SLOT_PENIS][MUTANT_INDEX_NAME]]
if(penis_sprite.is_hidden(affected_human))
to_chat(user, span_notice("You need to expose yourself in order to masturbate."))
return
else if(penis.aroused != AROUSAL_FULL)
to_chat(user, span_notice("You need to be aroused in order to masturbate."))
return
var/cum_volume = testicles.genital_size * CUM_VOLUME_MULTIPLIER
if(target == user)
user.visible_message(span_warning("[user] starts masturbating onto [target.p_them()]self!"), span_danger("You start masturbating onto yourself!"))
else if(target.is_refillable() && target.is_drainable())
if(target.reagents.holder_full())
to_chat(user, span_warning("[target] is full."))
return
user.visible_message(span_warning("[user] starts masturbating into [target]!"), span_danger("You start masturbating into [target]!"))
else
user.visible_message(span_warning("[user] starts masturbating onto [target]!"), span_danger("You start masturbating onto [target]!"))
if(do_after(user, 6 SECONDS, target))
if(target == user)
user.visible_message(span_warning("[user] cums on [target.p_them()]self!"), span_danger("You cum on yourself!"))
else if(target.is_refillable() && target.is_drainable())
var/datum/reagents/applied_reagents = new/datum/reagents(50)
applied_reagents.add_reagent(/datum/reagent/consumable/cum, cum_volume)
user.visible_message(span_warning("[user] cums into [target]!"), span_danger("You cum into [target]!"))
conditional_pref_sound(target, SFX_DESECRATION, 50, TRUE)
applied_reagents.trans_to(target, cum_volume)
else
user.visible_message(span_warning("[user] cums on [target]!"), span_danger("You cum on [target]!"))
conditional_pref_sound(target, SFX_DESECRATION, 50, TRUE)
affected_human.add_cum_splatter_floor(get_turf(target))
log_combat(user, target, "came on")
if(prob(40))
affected_human.try_lewd_autoemote("moan")
qdel(src)
#undef CUM_VOLUME_MULTIPLIER
@@ -1,3 +1,5 @@
// This whole device is kind of cursed and working by it's own logic
#define MILKING_PUMP_MODE_OFF "off"
#define MILKING_PUMP_MODE_LOW "low"
#define MILKING_PUMP_MODE_MEDIUM "medium"
@@ -391,10 +393,10 @@
if(/obj/item/organ/genital/testicles)
target_container = semen_vessel
if(!target_container || current_selected_organ.internal_fluid_count <= 0)
if(!target_container || current_selected_organ.reagents.total_volume <= 0)
return FALSE
current_selected_organ.transfer_internal_fluid(target_container.reagents, fluid_retrieve_amount[current_mode] * fluid_multiplier * seconds_per_tick)
current_selected_organ.reagents.trans_to(target_container, fluid_retrieve_amount[current_mode] * fluid_multiplier * seconds_per_tick)
return TRUE
// Handling the process of the impact of the machine on the organs of the mob
@@ -1,8 +1,6 @@
/obj/item/organ/genital
/// The fluid count of the genital.
var/internal_fluid_count = 0
/// The maximum amount of fluid that can be stored in the genital.
/// should only be used during generation/maximum size updates
var/internal_fluid_maximum = 0
/// The datum to be used for the tracked fluid, should it need to be added to a fluid container.
@@ -11,21 +9,6 @@
/// The currently inserted sex toy.
var/obj/item/inserted_item
/// Helper proc for checking if internal fluids are full or not.
/obj/item/organ/genital/proc/internal_fluid_full()
return internal_fluid_count >= internal_fluid_maximum
/// Adds the given amount to the internal fluid count, clamping it between 0 and internal_fluid_maximum.
/obj/item/organ/genital/proc/adjust_internal_fluid(amount)
internal_fluid_count = clamp(internal_fluid_count + amount, 0, internal_fluid_maximum)
/// Tries to add the specified amount to the target reagent container, or removes it if none are available. Keeps in mind internal_fluid_count.
/obj/item/organ/genital/proc/transfer_internal_fluid(datum/reagents/reagent_container = null, attempt_amount)
if(!internal_fluid_datum || !internal_fluid_count || !internal_fluid_maximum)
return FALSE
attempt_amount = clamp(attempt_amount, 0, internal_fluid_count)
if(reagent_container)
reagent_container.add_reagent(internal_fluid_datum, attempt_amount)
internal_fluid_count -= attempt_amount
/obj/item/organ/genital/Initialize(mapload)
. = ..()
create_reagents(internal_fluid_maximum, REAGENT_HOLDER_ALIVE)
@@ -1,5 +1,6 @@
/obj/item/organ/genital/breasts
internal_fluid_datum = /datum/reagent/consumable/breast_milk
internal_fluid_maximum = 60
/obj/item/organ/genital/breasts/build_from_dna(datum/dna/DNA, associated_key)
. = ..()
@@ -17,4 +18,7 @@
breasts_capacity = 3
internal_fluid_maximum = size * breasts_capacity * 60 // This seems like it could balloon drastically out of proportion with larger breast sizes.
if(internal_fluid_maximum > 3500)
internal_fluid_maximum = 3500
internal_fluid_maximum = 3500 // this is fucking grim and should be reworked at some point; remember, a wooden barrel the size of a small person has like 600u capacity
reagents.maximum_volume = internal_fluid_maximum
// i guess you start dry? - you should only have milk if you are lactating
@@ -1,5 +1,9 @@
/obj/item/organ/genital/testicles
internal_fluid_datum = /datum/reagent/consumable/cum
internal_fluid_maximum = 10
var/cumshot_size = 0
var/cumshot_size_mod = 1
var/cumshot_size_mult = 0.5
/obj/item/organ/genital/testicles/build_from_dna(datum/dna/DNA, associated_key)
. = ..()
@@ -10,3 +14,9 @@
internal_fluid_maximum = size * 20
if(internal_fluid_maximum > 300)
internal_fluid_maximum = 300
reagents.maximum_volume = internal_fluid_maximum
reagents.add_reagent(internal_fluid_datum, internal_fluid_maximum) // should make you start with full balls? (cum is stored in the balls in ss13)
/obj/item/organ/genital/testicles/proc/calculate_cumshot()
cumshot_size = (reagents.total_volume * cumshot_size_mod) * cumshot_size_mult
@@ -1,6 +1,7 @@
/obj/item/organ/genital/vagina
internal_fluid_datum = /datum/reagent/consumable/femcum
internal_fluid_maximum = 10
/obj/item/organ/genital/vagina/build_from_dna(datum/dna/DNA, associated_key)
. = ..()
internal_fluid_maximum = 10
reagents.maximum_volume = internal_fluid_maximum
-1
View File
@@ -8307,7 +8307,6 @@
#include "modular_skyrat\modules\modular_items\lewd_items\code\lewd_helpers\species.dm"
#include "modular_skyrat\modules\modular_items\lewd_items\code\lewd_items\_erp_disabled_item_enforcement.dm"
#include "modular_skyrat\modules\modular_items\lewd_items\code\lewd_items\_kits.dm"
#include "modular_skyrat\modules\modular_items\lewd_items\code\lewd_items\_masturbation_item.dm"
#include "modular_skyrat\modules\modular_items\lewd_items\code\lewd_items\_sex_toy.dm"
#include "modular_skyrat\modules\modular_items\lewd_items\code\lewd_items\attachable_vibrator.dm"
#include "modular_skyrat\modules\modular_items\lewd_items\code\lewd_items\buttplug.dm"