From 8db668700610846ef28692e85087e7c5667d4b2a Mon Sep 17 00:00:00 2001
From: sarcoph <83266791+sarcoph@users.noreply.github.com>
Date: Thu, 5 May 2022 08:17:59 -0800
Subject: [PATCH] documentation and modularization
"mob_climax_cover" is now separate from "mob_climax_partner", and impreg chance is separated from those as well
---
.../code/modules/arousal/arousalhud.dm | 15 +-
.../code/modules/arousal/organs/genitals.dm | 6 +
hyperstation/code/obj/fleshlight.dm | 41 +-
.../code/modules/arousal/arousal.dm | 391 +++++++++---------
tgstation.dme | 1 +
5 files changed, 241 insertions(+), 213 deletions(-)
create mode 100644 hyperstation/code/modules/arousal/organs/genitals.dm
diff --git a/hyperstation/code/modules/arousal/arousalhud.dm b/hyperstation/code/modules/arousal/arousalhud.dm
index 757fd2330..586e6608c 100644
--- a/hyperstation/code/modules/arousal/arousalhud.dm
+++ b/hyperstation/code/modules/arousal/arousalhud.dm
@@ -334,7 +334,7 @@ obj/screen/arousal/proc/kiss()
return
//We got hands, let's pick an organ
var/obj/item/organ/genital/picked_organ
- picked_organ = pick_masturbate_genitals()
+ picked_organ = pick_climax_genitals(masturbation=TRUE, title="Masturbation")
if(picked_organ)
src << browse(null, "window=arousal") //closes the window
mob_masturbate(picked_organ)
@@ -378,7 +378,7 @@ obj/screen/arousal/proc/kiss()
to_chat(src, "You cannot climax without choosing genitals.")
return
src << browse(null, "window=arousal") // close arousal window
- mob_climax_partner_spillage(picked_organ, partner, FALSE)
+ mob_climax_partner_spillage(picked_organ, partner, impreg=FALSE)
/mob/living/carbon/human/proc/climaxover(mob/living/carbon/human/partner)
@@ -398,7 +398,7 @@ obj/screen/arousal/proc/kiss()
if(P.sounding)
to_chat(src, "You cannot do this action with a sounding in.")
return
- mob_climax_partner(picked_organ, partner, FALSE, FALSE, TRUE)
+ mob_climax_cover(picked_organ, partner)
/mob/living/carbon/human/proc/clothesplosion()
@@ -435,7 +435,7 @@ obj/screen/arousal/proc/kiss()
to_chat(src, "You cannot impregnate someone with a sounding rod in.")
return
src << browse(null, "window=arousal") //alls fine, we can close the window now.
- mob_climax_partner_spillage(picked_organ, partner, TRUE)
+ mob_climax_partner_spillage(picked_organ, partner, impreg=TRUE)
/mob/living/carbon/human/proc/cumcontainer(mob/living/T)
@@ -476,6 +476,11 @@ obj/screen/arousal/proc/kiss()
cum_splatter_icon.Blend(icon('hyperstation/icons/effects/cumoverlay.dmi', "cum_obj"), ICON_MULTIPLY)
add_overlay(cum_splatter_icon)
+/mob/living/carbon/add_cum_overlay(type="normal")
+ var/mutable_appearance/cumoverlay = mutable_appearance('hyperstation/icons/effects/cumoverlay.dmi')
+ cumoverlay.icon_state = "cum_[type]"
+ add_overlay(cumoverlay)
+
/atom/proc/wash_cum()
cut_overlay(mutable_appearance('hyperstation/icons/effects/cumoverlay.dmi', "cum_normal"))
cut_overlay(mutable_appearance('hyperstation/icons/effects/cumoverlay.dmi', "cum_large"))
@@ -499,7 +504,7 @@ obj/screen/arousal/proc/kiss()
return
//We got hands, let's pick an organ
var/obj/item/organ/genital/picked_organ
- picked_organ = pick_masturbate_genitals()
+ picked_organ = pick_climax_genitals(masturbation=TRUE, title="Masturbation")
if(picked_organ)
src << browse(null, "window=arousal") //closes the window
mob_masturbate(picked_organ, cover = TRUE)
diff --git a/hyperstation/code/modules/arousal/organs/genitals.dm b/hyperstation/code/modules/arousal/organs/genitals.dm
new file mode 100644
index 000000000..9da552021
--- /dev/null
+++ b/hyperstation/code/modules/arousal/organs/genitals.dm
@@ -0,0 +1,6 @@
+/obj/item/organ/genital/proc/get_fluid_source()
+ if(producing)
+ return src.reagents
+ if(linked_organ && linked_organ.producing)
+ return linked_organ.reagents
+ return null
\ No newline at end of file
diff --git a/hyperstation/code/obj/fleshlight.dm b/hyperstation/code/obj/fleshlight.dm
index 8a6932906..3d5c7ab48 100644
--- a/hyperstation/code/obj/fleshlight.dm
+++ b/hyperstation/code/obj/fleshlight.dm
@@ -102,29 +102,29 @@
else
. += "The device is paired, and awaiting input. "
-/obj/item/portallight/attack(mob/living/carbon/holder, mob/living/user)
+/obj/item/portallight/attack(mob/living/carbon/user, mob/living/holder)
if(inuse) //just to stop stacking and causing people to cum instantly
return
if(!UpdateUsability())
- to_chat(user, "\The [src] appears to be currently unusable.")
+ to_chat(holder, "\The [src] appears to be currently unusable.")
return
var/option = "Fuck"
- if(holder == user)
+ if(user == holder)
option = input(usr, "Choose action", "Portal Fleshlight", "Fuck") in list("Fuck", "Lick", "Touch")
var/obj/item/organ/genital/target_genital = portalunderwear.loc
var/mob/living/carbon/human/target = target_genital.owner
- var/obj/item/organ/genital/penis/P = holder.getorganslot("penis")
+ var/obj/item/organ/genital/penis/P = user.getorganslot("penis")
if(option == "Fuck" && !P.is_exposed())
- to_chat(user, "You don't see anywhere to use this on.")
+ to_chat(holder, "You don't see anywhere to use this on.")
return
/*
WARMUP START - prevents spam/instant climax
*/
inuse = TRUE
- if(holder != user)
- holder.visible_message("[user] is trying to use [src] on [holder]'s penis.",\
- "[user] is trying to use [src] on your penis.")
- if(!do_mob(user, holder, 3 SECONDS))
+ if(user != holder)
+ user.visible_message("[holder] is trying to use [src] on [user]'s penis.",\
+ "[holder] is trying to use [src] on your penis.")
+ if(!do_mob(holder, user, 3 SECONDS))
inuse = FALSE
return
inuse = FALSE
@@ -134,31 +134,32 @@
switch(option)
if("Fuck")
playsound(src, 'sound/lewd/slaps.ogg', 30, 1, -1)
- if(holder == user)
- user.visible_message("[user] pumps [src] on their penis.",
+ if(user == holder)
+ holder.visible_message("[holder] pumps [src] on their penis.",
"You pump the fleshlight on your penis.")
else
- holder.visible_message("[user] pumps [src] on [holder]'s penis.",\
- "[user] pumps [src] up and down on your penis.")
+ user.visible_message("[holder] pumps [src] on [user]'s penis.",\
+ "[holder] pumps [src] up and down on your penis.")
to_chat(target, "You feel a [P.length] inch, [P.shape] shaped penis pumping through the portal into your [target_genital.name].")
if(prob(30))
- holder.emote("moan")
- holder.adjustArousalLoss(20)
+ user.emote("moan")
+ user.adjustArousalLoss(20)
target.adjustArousalLoss(20)
target.do_jitter_animation()
if(target.can_orgasm() && prob(5))
target.mob_climax_outside(target_genital, spillage = target_genital.is_exposed())
- if(holder.can_orgasm())
- var/mob/living/carbon/human/O = holder
+ if(user.can_orgasm())
+ var/mob/living/carbon/human/O = user
var/impreg_chance = target_genital.name == "vagina" && !P.condom && !P.sounding
- O.mob_climax_partner(P, target, FALSE, impreg_chance, FALSE, TRUE)
+ if(O.mob_climax_partner(P, target, spillage=FALSE, remote=TRUE) && impreg_chance)
+ target.impregnate(by=O)
if("Lick")
- user.visible_message("[user] licks into [src].",\
+ holder.visible_message("[holder] licks into [src].",\
"You lick into [src].")
to_chat(target, "You feel a tongue lick you through the portal against your [target_genital.name].")
target.adjustArousalLoss(10)
if("Touch")
- user.visible_message("[user] touches softly against [src].",\
+ holder.visible_message("[holder] touches softly against [src].",\
"You touch softly on [src].")
to_chat(target, "You feel someone touching your [target_genital.name] through the portal.")
target.adjustArousalLoss(5)
diff --git a/modular_citadel/code/modules/arousal/arousal.dm b/modular_citadel/code/modules/arousal/arousal.dm
index f41d1ee58..8d2de3d34 100644
--- a/modular_citadel/code/modules/arousal/arousal.dm
+++ b/modular_citadel/code/modules/arousal/arousal.dm
@@ -189,19 +189,30 @@
if(M.canbearoused)
ui_interact(usr)
+/**
+ * Checks if this mob can orgasm. Criteria includes:
+ * - Mob is human
+ * - Mob is capable of arousal
+ * - Mob has DNA
+ * - Mob is aroused past a certain threshold
+ *
+ * Arguments:
+ * - `arousal` - The minimum arousal needed to pass the check. Default is `100`.
+ *
+ * Returns: `TRUE/FALSE` - Whether or not the mob passes all of the above checks.
+ */
/mob/living/proc/can_orgasm(arousal = 100)
return src.canbearoused && src.getArousalLoss() >= arousal && ishuman(src) && src.has_dna()
+
/mob/living/proc/mob_climax()
-// This is just so I can test this shit without being forced to add actual content
-// to get rid of arousal. Will be a very basic proc for a while.
set name = "Masturbate"
set category = "IC"
if(canbearoused && !restrained() && !stat)
if(mb_cd_timer <= world.time)
//start the cooldown even if it fails
mb_cd_timer = world.time + mb_cd_length
- if(getArousalLoss() >= isPercentAroused(33))//33% arousal or greater required
+ if(isPercentAroused(33))//33% arousal or greater required
src.visible_message("[src] starts masturbating!", \
"You start masturbating.")
if(do_after(src, 30, target = src))
@@ -213,33 +224,24 @@
to_chat(src, "You aren't aroused enough for that.")
-//These are various procs that we'll use later, split up for readability instead of having one, huge proc.
-//For all of these, we assume the arguments given are proper and have been checked beforehand.
+
/mob/living/carbon/human/proc/mob_masturbate(obj/item/organ/genital/G, cover = FALSE, mb_time = 30)
-//Masturbation, keep it gender-neutral
var/total_fluids = 0
- var/datum/reagents/fluid_source = null
- var/condomed = 0
- var/sounded = 0
- if(G.name == "penis")//if the select organ is a penis
+ var/datum/reagents/fluid_source = G.get_fluid_source()
+ var/condomed = FALSE
+ var/sounded = FALSE
+ if(G.name == "penis")
var/obj/item/organ/genital/penis/P = src.getorganslot("penis")
- if(P.condom) //if the penis is condomed
- condomed = 1
- if(P.sounding)
- sounded = 1
- if(G.producing) //Can it produce its own fluids, such as breasts?
- fluid_source = G.reagents
- else
- if(!G.linked_organ)
- to_chat(src, "Your [G.name] is unable to produce it's own fluids, it's missing the organs for it.")
- return
- fluid_source = G.linked_organ.reagents
+ condomed = P.condom
+ sounded = P.sounding
+ if(!fluid_source)
+ to_chat(src, "Your [G.name] is unable to produce it's own fluids, it's missing the organs for it.")
+ return
total_fluids = fluid_source.total_volume
if(mb_time)
src.visible_message("[src] starts to [G.masturbation_verb] [p_their()] [G.name].", \
"You start to [G.masturbation_verb] your [G.name].", \
"You start to [G.masturbation_verb] your [G.name].")
-
if(do_after(src, mb_time, target = src))
if(total_fluids > 5 &&!condomed &&!sounded)
fluid_source.reaction(src.loc, TOUCH, 1, 0)
@@ -248,14 +250,11 @@
src.visible_message("[src] orgasms, cumming[istype(src.loc, /turf/open/floor) ? " onto [src.loc]" : ""]!", \
"You cum[istype(src.loc, /turf/open/floor) ? " onto [src.loc]" : ""].", \
"You have relieved yourself.")
-
if(!condomed &&!sounded && cover)//For when you want to make a mess of yourself.
fluid_source.trans_to(src, total_fluids*G.fluid_transfer_factor)
total_fluids -= total_fluids*G.fluid_transfer_factor
if(total_fluids > 80) // now thats a big cum!
- var/mutable_appearance/cumoverlaylarge = mutable_appearance('hyperstation/icons/effects/cumoverlay.dmi')
- cumoverlaylarge.icon_state = "cum_large"
- src.add_overlay(cumoverlaylarge)
+ src.add_cum_overlay("large")
if(total_fluids > 5)
fluid_source.reaction(src.loc, TOUCH, 1, 0)
var/mob/living/carbon/human/H = src
@@ -265,10 +264,7 @@
src.visible_message("[src] climaxes over [p_their()] self, using [p_their()] [G.name]!", \
"You orgasm over yourself, using your [G.name].", \
"You have climaxed over something, using your [G.name].")
- var/mutable_appearance/cumoverlay = mutable_appearance('hyperstation/icons/effects/cumoverlay.dmi')
- cumoverlay.icon_state = "cum_normal"
- src.add_overlay(cumoverlay)
-
+ src.add_cum_overlay()
if(condomed) //condomed
src.visible_message("[src] orgasms, climaxing into [p_their()] condom!", \
"You cum into your condom.", \
@@ -279,7 +275,6 @@
"You don't quite feel totally relieved.")
if(total_fluids > 0 &&condomed &&!sounded)
src.condomclimax()
-
SEND_SIGNAL(src, COMSIG_ADD_MOOD_EVENT, "orgasm", /datum/mood_event/orgasm)
if(G.can_climax)
setArousalLoss(min_arousal)
@@ -288,19 +283,12 @@
/mob/living/carbon/human/proc/mob_climax_outside(obj/item/organ/genital/G, mb_time = 30, spillage = TRUE)
//This is used for forced orgasms and other hands-free climaxes
var/total_fluids = 0
- var/datum/reagents/fluid_source = null
+ var/datum/reagents/fluid_source = G.get_fluid_source()
var/unable_to_come = FALSE
-
- if(G.producing) //Can it produce its own fluids, such as breasts?
- fluid_source = G.reagents
+ if(fluid_source)
total_fluids = fluid_source.total_volume
else
- if(!G.linked_organ)
- unable_to_come = TRUE
- else
- fluid_source = G.linked_organ.reagents
- total_fluids = fluid_source.total_volume
-
+ unable_to_come = TRUE
if(unable_to_come)
src.visible_message("[src] shudders, their [G.name] unable to cum.", \
"Your [G.name] cannot cum, giving no relief.", \
@@ -323,108 +311,107 @@
src.visible_message("[src] orgasms with [p_their()] [G.name]!", \
"You climax with your [G.name].", \
"You climax using your [G.name].")
-
SEND_SIGNAL(src, COMSIG_ADD_MOOD_EVENT, "orgasm", /datum/mood_event/orgasm)
if(G.can_climax)
setArousalLoss(min_arousal)
-/mob/living/carbon/human/proc/mob_climax_partner(obj/item/organ/genital/G, atom/partner, spillage = TRUE, impreg = FALSE,cover = FALSE,remote = FALSE, mb_time = 30) //Used for climaxing with any person
- if(isnoncarbon(partner)) // deny animals
- return
+/mob/living/carbon/human/proc/mob_climax_cover(obj/item/organ/genital/genital, atom/partner, mb_time=3 SECONDS)
var/total_fluids = 0
- var/datum/reagents/fluid_source = null
- if(G.producing) //Can it produce its own fluids, such as breasts?
- fluid_source = G.reagents
- else
- if(!G.linked_organ)
- to_chat(src, "Your [G.name] is unable to produce it's own fluids, it's missing the organs for it.")
- return
- fluid_source = G.linked_organ.reagents
+ var/datum/reagents/fluid_source = genital.get_fluid_source()
+ if(!fluid_source)
+ to_chat(src, "Your [genital.name] is unable to produce it's own fluids, it's missing the organs for it.")
+ return
total_fluids = fluid_source.total_volume
- if(mb_time && !remote) //Skip warning if this is an instant climax.
+ var/mob/living/carbon/partner_carbon = partner
+ if(mb_time)
src.visible_message("[src] is about to climax with [partner]!", \
"You're about to climax with [partner]!", \
"You're preparing to climax with something!")
+ if(!do_after(src, mb_time, target = src))
+ return
+ if(!in_range(src, partner))
+ return
+ var/transferred_units = total_fluids * genital.fluid_transfer_factor
+ fluid_source.trans_to(partner, transferred_units)
+ total_fluids -= transferred_units
+ if(total_fluids > 80 && iscarbon(partner)) // now thats a big cum!
+ partner_carbon.add_cum_overlay("large")
+ if(total_fluids > 5)
+ fluid_source.reaction(partner.loc, TOUCH, 1, 0)
+ var/mob/living/carbon/human/H = partner
+ if(H)
+ H.cumdrip_rate += rand(5,10)
+ fluid_source.clear_reagents()
+ if(iscarbon(partner))
+ partner_carbon.add_cum_overlay("normal")
+ else
+ partner.add_cum_overlay()
+ src.visible_message("[src] climaxes over [partner], using [p_their()] [genital.name]!", \
+ "You orgasm over [partner], using your [genital.name].", \
+ "You have climaxed over something, using your [genital.name].")
+ SEND_SIGNAL(src, COMSIG_ADD_MOOD_EVENT, "orgasm", /datum/mood_event/orgasm)
+ setArousalLoss(min_arousal)
+
+
+/mob/living/carbon/human/proc/mob_climax_partner(obj/item/organ/genital/G, atom/partner, spillage=TRUE, remote=FALSE, mb_time = 3 SECONDS)
+ if(isnoncarbon(partner)) // deny animals
+ return FALSE
+ var/total_fluids = 0
+ var/datum/reagents/fluid_source = G.get_fluid_source()
+ if(!fluid_source)
+ to_chat(src, "Your [G.name] is unable to produce it's own fluids, it's missing the organs for it.")
+ return FALSE
+ total_fluids = fluid_source.total_volume
if(remote)
src.visible_message("[src] is about to climax with someone!", \
"You're about to climax with someone!", \
"You're preparing to climax with something!")
- if(cover)//covering the partner in cum, this overrides other options.
- if(do_after(src, mb_time, target = src) && in_range(src, partner))
- fluid_source.trans_to(partner, total_fluids*G.fluid_transfer_factor)
- total_fluids -= total_fluids*G.fluid_transfer_factor
- if(total_fluids > 80 && iscarbon(partner)) // now thats a big cum!
- var/mutable_appearance/cumoverlaylarge = mutable_appearance('hyperstation/icons/effects/cumoverlay.dmi')
- cumoverlaylarge.icon_state = "cum_large"
- partner.add_overlay(cumoverlaylarge)
- if(total_fluids > 5)
- fluid_source.reaction(partner.loc, TOUCH, 1, 0)
- var/mob/living/carbon/human/H = partner
- if(H)
- H.cumdrip_rate += rand(5,10)
- fluid_source.clear_reagents()
- src.visible_message("[src] climaxes over [partner], using [p_their()] [G.name]!", \
- "You orgasm over [partner], using your [G.name].", \
- "You have climaxed over something, using your [G.name].")
- SEND_SIGNAL(src, COMSIG_ADD_MOOD_EVENT, "orgasm", /datum/mood_event/orgasm)
- if(iscarbon(partner))
- var/mutable_appearance/cumoverlay = mutable_appearance('hyperstation/icons/effects/cumoverlay.dmi')
- cumoverlay.icon_state = "cum_normal"
- partner.add_overlay(cumoverlay)
- else
- partner.add_cum_overlay()
- setArousalLoss(min_arousal)
- if(G.can_climax)
- setArousalLoss(min_arousal)
- if(spillage && !cover)
- if(do_after(src, mb_time, target = src))
- if(!in_range(src, partner) && !remote)
- return
- fluid_source.trans_to(partner, total_fluids*G.fluid_transfer_factor)
- total_fluids -= total_fluids*G.fluid_transfer_factor
- if(total_fluids > 5)
- fluid_source.reaction(partner.loc, TOUCH, 1, 0)
- var/mob/living/carbon/human/H = partner
- if(H)
- H.cumdrip_rate += rand(5,10)
- fluid_source.clear_reagents()
- src.visible_message("[src] climaxes with [partner][spillage ? ", overflowing and spilling":""], using [p_their()] [G.name]!", \
- "You orgasm with [partner][spillage ? ", spilling out of them":""], using your [G.name].", \
- "You have climaxed with someone[spillage ? ", spilling out of them":""], using your [G.name].")
- SEND_SIGNAL(src, COMSIG_ADD_MOOD_EVENT, "orgasm", /datum/mood_event/orgasm)
- SEND_SIGNAL(partner, COMSIG_ADD_MOOD_EVENT, "orgasm", /datum/mood_event/orgasm)
- if(G.can_climax)
- setArousalLoss(min_arousal)
+ else if(mb_time)
+ src.visible_message("[src] is about to climax with [partner]!", \
+ "You're about to climax with [partner]!", \
+ "You're preparing to climax with something!")
+ if(!do_after(src, mb_time, target = src))
+ return FALSE
+ if(!remote && !in_range(src, partner))
+ return FALSE
+ if(spillage)
+ fluid_source.trans_to(partner, total_fluids*G.fluid_transfer_factor)
+ total_fluids -= total_fluids*G.fluid_transfer_factor
+ if(total_fluids > 5)
+ fluid_source.reaction(partner.loc, TOUCH, 1, 0)
+ var/mob/living/carbon/human/H = partner
+ if(H)
+ H.cumdrip_rate += rand(5,10)
+ fluid_source.clear_reagents()
+ src.visible_message("[src] climaxes with [partner][spillage ? ", overflowing and spilling":""], using [p_their()] [G.name]!",\
+ "You orgasm with [partner][spillage ? ", spilling out of them":""], using your [G.name].",\
+ "You have climaxed with someone[spillage ? ", spilling out of them":""], using your [G.name].")
+ SEND_SIGNAL(src, COMSIG_ADD_MOOD_EVENT, "orgasm", /datum/mood_event/orgasm)
+ SEND_SIGNAL(partner, COMSIG_ADD_MOOD_EVENT, "orgasm", /datum/mood_event/orgasm)
else //knots, portal fleshlights, and other non-spilling orgasms
- if(!cover)
- if(!remote && !in_range(src, partner))
- return
- if(do_after(src, mb_time, target = src))
- var/mob/living/carbon/H = partner
- if(!spillage && total_fluids > 80) //hyper inflation; requires a big cumshot to expand
- H.expand_belly(1)
- var/obj/item/organ/genital/penis/P = G
- if (P.condom)//condomed.
- src.condomclimax()
- else
- fluid_source.trans_to(partner, total_fluids)
- total_fluids = 0
- if(!remote)
- src.visible_message("[src] climaxes with [partner], [p_their()] [G.name] spilling nothing!", \
- "You ejaculate with [partner], your [G.name] spilling nothing.", \
- "You have climaxed inside someone, your [G.name] spilling nothing.")
- else
- src.visible_message("[src] climaxes with someone, using [p_their()] [G.name]!", \
- "You ejaculate with someone, using your [G.name].", \
- "You have climaxed inside someone, using your [G.name].")
- to_chat(partner, "You feel someone ejaculate inside you.")
-
- SEND_SIGNAL(src, COMSIG_ADD_MOOD_EVENT, "orgasm", /datum/mood_event/orgasm)
- SEND_SIGNAL(partner, COMSIG_ADD_MOOD_EVENT, "orgasm", /datum/mood_event/orgasm)
-
- if(G.can_climax)
- setArousalLoss(min_arousal)
+ var/mob/living/carbon/H = partner
+ if(!spillage && total_fluids > 80) //hyper inflation; requires a big cumshot to expand
+ H.expand_belly(1)
+ var/obj/item/organ/genital/penis/P = G
+ if(P.condom)//condomed.
+ src.condomclimax()
+ else
+ fluid_source.trans_to(partner, total_fluids)
+ total_fluids = 0
+ if(!remote)
+ src.visible_message("[src] climaxes with [partner], [p_their()] [G.name] spilling nothing!", \
+ "You ejaculate with [partner], your [G.name] spilling nothing.", \
+ "You have climaxed inside someone, your [G.name] spilling nothing.")
+ else
+ src.visible_message("[src] climaxes with someone, using [p_their()] [G.name]!", \
+ "You ejaculate with someone, using your [G.name].", \
+ "You have climaxed inside someone, using your [G.name].")
+ to_chat(partner, "You feel someone ejaculate inside you.")
+ SEND_SIGNAL(src, COMSIG_ADD_MOOD_EVENT, "orgasm", /datum/mood_event/orgasm)
+ SEND_SIGNAL(partner, COMSIG_ADD_MOOD_EVENT, "orgasm", /datum/mood_event/orgasm)
+ if(G.can_climax)
+ setArousalLoss(min_arousal)
//Hyper - antag code
var/mob/living/carbon/partner_carbon = partner
if(src.mind.special_role == ROLE_LEWD_TRAITOR)
@@ -432,98 +419,107 @@
if (partner_carbon.mind == obj.target)
partner_carbon.mind.sexed = TRUE //sexed
to_chat(src, "You feel deep satisfaction with yourself.")
- //Hyper - impreg
- if(impreg)
- partner_carbon.impregnate(by = src)
+ return TRUE
/mob/living/carbon/human/proc/mob_climax_partner_spillage(obj/item/organ/genital/picked_organ, mob/living/carbon/partner, impreg = FALSE)
var/obj/item/organ/genital/penis/_penis = picked_organ
if(picked_organ.name != "penis" || _penis.sounding || _penis.condom)
- mob_climax_partner(picked_organ, partner, FALSE, FALSE, FALSE)
+ mob_climax_partner(picked_organ, partner, spillage=FALSE)
return
var/_question = "Would your fluids spill outside?"
var/_title = "Choose overflowing option"
var/spillage = input(src, _question, _title, "Yes") as anything in list("Yes", "No")
- mob_climax_partner(picked_organ, partner, spillage == "Yes", impreg, FALSE)
+ if(mob_climax_partner(picked_organ, partner, spillage == "Yes") && impreg)
+ partner.impregnate(by=src)
-/mob/living/carbon/human/proc/mob_fill_container(obj/item/organ/genital/G, obj/item/reagent_containers/container, mb_time = 30)
-// For beaker-filling, beware the bartender
- var/total_fluids = 0
- var/datum/reagents/fluid_source = null
- if(G.name == "penis")
- var/obj/item/organ/genital/penis/P = src.getorganslot("penis")
- if(P.condom)
- to_chat(src, "You cannot fill containers when there is a condom over your [G.name].")
- return
- if(P.sounding)
- to_chat(src, "You cannot fill containers when there is a rod inside your [G.name].")
- return
- if(G.producing) //Can it produce its own fluids, such as breasts?
- fluid_source = G.reagents
- else
- if(!G.linked_organ)
- to_chat(src, "Your [G.name] is unable to produce it's own fluids, it's missing the organs for it.")
- return
- fluid_source = G.linked_organ.reagents
- total_fluids = fluid_source.total_volume
-
+/**
+ * Attempt to climax into a container it, filling it with whatever fluids are
+ * associated to the selected genitals.
+ *
+ * Arguments:
+ * - `genital`: The genital being used for climax.
+ * - `container`: The container being targeted.
+ * - `mb_time`: The warm-up time for this interaction.
+ */
+/mob/living/carbon/human/proc/mob_fill_container(obj/item/organ/genital/genital, obj/item/reagent_containers/container, mb_time = 30)
if(!container) //Something weird happened
to_chat(src, "You need a container to do this!")
return
-
- src.visible_message("[src] starts to [G.masturbation_verb] their [G.name] over [container].", \
- "You start to [G.masturbation_verb] your [G.name] over [container].", \
- "You start to [G.masturbation_verb] your [G.name] over something.")
+ var/total_fluids = 0
+ var/datum/reagents/fluid_source = genital.get_fluid_source()
+ if(!fluid_source)
+ to_chat(src, "Your [genital.name] is unable to produce it's own fluids, it's missing the organs for it.")
+ return
+ total_fluids = fluid_source.total_volume
+ if(genital.name == "penis")
+ var/obj/item/organ/genital/penis/P = src.getorganslot("penis")
+ if(P.condom)
+ to_chat(src, "You cannot fill containers when there is a condom over your [genital.name].")
+ return
+ if(P.sounding)
+ to_chat(src, "You cannot fill containers when there is a rod inside your [genital.name].")
+ return
+ src.visible_message("[src] starts to [genital.masturbation_verb] their [genital.name] over [container].", \
+ "You start to [genital.masturbation_verb] your [genital.name] over [container].", \
+ "You start to [genital.masturbation_verb] your [genital.name] over something.")
if(do_after(src, mb_time, target = src) && in_range(src, container))
fluid_source.trans_to(container, total_fluids)
- src.visible_message("[src] uses [p_their()] [G.name] to fill [container]!", \
- "You used your [G.name] and fill [container] with a total of [total_fluids]u's.", \
- "You have relieved some pressure.")
+ src.visible_message("[src] uses [p_their()] [genital.name] to fill [container]!", \
+ "You used your [genital.name] and fill [container] with a total of [total_fluids]u's.", \
+ "You have relieved some pressure.")
SEND_SIGNAL(src, COMSIG_ADD_MOOD_EVENT, "orgasm", /datum/mood_event/orgasm)
container.add_cum_overlay() //your aim is bad...
- if(G.can_climax)
+ if(genital.can_climax)
setArousalLoss(min_arousal)
-/mob/living/carbon/human/proc/pick_masturbate_genitals()
- var/obj/item/organ/genital/ret_organ
- var/list/genitals_list = list()
- for(var/obj/item/organ/genital/G in internal_organs)
- if(G.can_masturbate_with) //filter out what you can't masturbate with
- if(G.is_exposed()) //Nude or through_clothing
- if(!G.dontlist)
- genitals_list += G
- if(genitals_list.len)
- ret_organ = input(src, "with what?", "Masturbate", null) as null|obj in genitals_list
- return ret_organ
- return null //error stuff
-
-/mob/living/carbon/human/proc/target_genitals(mob/living/carbon/human/T) //used for targeting others
+/**
+ * Prompts the user to select one of their target's genitals to interact with.
+ * The target must have their genitals visible to be selectable.
+ *
+ * Returns: `null`|`obj/item/organ/genital`: The selected genital.
+ */
+/mob/living/carbon/human/proc/target_genitals(mob/living/carbon/human/T)
var/obj/item/organ/genital/ret_organ
var/list/genitals_list = list()
for(var/obj/item/organ/genital/G in T.internal_organs)
- if(G.is_exposed()) //Nude or through_clothing
- if(!G.dontlist)
- genitals_list += G
+ if(G.is_exposed() && !G.dontlist)
+ genitals_list += G
if(genitals_list.len)
ret_organ = input(src, "", "Genitals", null) as null|obj in genitals_list
return ret_organ
return null //error stuff
-/mob/living/carbon/human/proc/pick_climax_genitals()
+
+/**
+ * Prompts the user to select genitals. The genitals must be exposed to be selectable.
+ *
+ * Arguments:
+ * - `masturbation`: Whether or not this is being used for masturbation interactions.
+ * - `title`: The title of the prompt window.
+ *
+ * Returns: `null`|`obj/item/organ/genital`: The selected genital.
+ */
+/mob/living/carbon/human/proc/pick_climax_genitals(masturbation=FALSE, title="Climax")
var/obj/item/organ/genital/ret_organ
var/list/genitals_list = list()
for(var/obj/item/organ/genital/G in internal_organs)
if(G.can_climax && G.is_exposed() && !G.dontlist)
- genitals_list += G
+ if(!masturbation || G.can_masturbate_with)
+ genitals_list += G
if(genitals_list.len)
- ret_organ = input(src, "with what?", "Climax", null) as null|obj in genitals_list
+ ret_organ = input(src, "with what?", title, null) as null|obj in genitals_list
return ret_organ
return null //error stuff
+/**
+ * Prompts the user to select a mob that they are either pulling or being pulled by.
+ *
+ * Returns: `null`|`mob/living/carbon`: The selected mob.
+ */
/mob/living/carbon/human/proc/pick_partner(needs_exposed = TRUE)
var/list/partners = list(src.pulling, src.pulledby)
for(var/I in partners)
@@ -537,9 +533,14 @@
partners -= I
if(!partners.len)
return null
- return input(src, "With whom?", "Sexual partner", null) as null|anything in partners
+ return input(src, "With whom?", "Sexual partner", null) as null|mob in partners
+/**
+ * Prompts the user to provide a container (or food item) based on what's in their hands.
+ *
+ * Returns: `null`|`obj/item/reagent_containers` - The selected container.
+ */
/mob/living/carbon/human/proc/pick_climax_container()
var/obj/item/reagent_containers/SC = null
var/list/containers_list = list()
@@ -551,12 +552,14 @@
if(SC)
if(in_range(src, SC))
return SC
- return null //If nothing correct, give null.
+ return null
+/**
+ * The old arousal menu. This shows a BYOND-style input selection menu that
+ * emulates the same functionality as the newer one.
+ */
/mob/living/carbon/human/mob_climax()
- if(stat == DEAD) //corpses can't cum
- return
if(!canbearoused || !has_dna())
return
if(mb_cd_timer > world.time)
@@ -627,7 +630,7 @@
return
//We got hands, let's pick an organ
var/obj/item/organ/genital/picked_organ
- picked_organ = pick_masturbate_genitals()
+ picked_organ = pick_climax_genitals(masturbation=TRUE, title="Masturbation")
if(picked_organ)
mob_masturbate(picked_organ)
return
@@ -684,7 +687,7 @@
if(picked_organ)
var/mob/living/carbon/partner = pick_partner(FALSE) //Get your partner, clothed or not.
if(partner)
- mob_climax_partner(picked_organ, partner, FALSE, FALSE, TRUE)
+ mob_climax_cover(picked_organ, partner)
else
to_chat(src, "You cannot do this alone.")
@@ -722,6 +725,12 @@
return //Just end it in that case.
+/**
+ * Causes a mob to climax on the spot. There is no warmup time, nor a
+ * menu for selecting genitals. Instead, the mob will climax using all
+ * climaxable genitals, either onto their location (floor) or on whatever
+ * is pulling/being pulled by them.
+ */
/mob/living/carbon/proc/mob_climax_instant()
if(stat == DEAD) //corpses can't cum
to_chat(src, "You can't do that while dead!")
@@ -756,6 +765,12 @@
return
+/**
+ * Rolls a chance for the mob to get pregnant, assuming they are capable of doing so.
+ *
+ * Arguments:
+ * - `by` - The mob that is impregnating this one.
+ */
/mob/living/carbon/proc/impregnate(mob/living/carbon/by)
var/obj/item/organ/genital/womb/W = getorganslot("womb")
if(!W || W.pregnant || !breedable)
diff --git a/tgstation.dme b/tgstation.dme
index 7ab0343dd..b6c7c761e 100644
--- a/tgstation.dme
+++ b/tgstation.dme
@@ -3145,6 +3145,7 @@
#include "hyperstation\code\modules\arousal\arousalhud.dm"
#include "hyperstation\code\modules\arousal\creampie.dm"
#include "hyperstation\code\modules\arousal\expansion.dm"
+#include "hyperstation\code\modules\arousal\organs\genitals.dm"
#include "hyperstation\code\modules\busy_space\air_traffic.dm"
#include "hyperstation\code\modules\busy_space\atc_controller.dm"
#include "hyperstation\code\modules\busy_space\loremaster.dm"