diff --git a/code/__DEFINES/hyper_arousal.dm b/code/__DEFINES/hyper_arousal.dm index d3f9d6ceb..a0450b138 100644 --- a/code/__DEFINES/hyper_arousal.dm +++ b/code/__DEFINES/hyper_arousal.dm @@ -5,4 +5,9 @@ #define BELLY_STRETCH_SIZE 6 // for flavor text #define BUTT_MIN_SIZE 0 -#define BUTT_MAX_SIZE 8 \ No newline at end of file +#define BUTT_MAX_SIZE_SELECTABLE 5 +#define BUTT_MAX_SIZE 8 + +#define GENITALS_HIDDEN "hidden" +#define GENITALS_CLOTHES "clothes" +#define GENITALS_VISIBLE "visible" diff --git a/code/__DEFINES/is_helpers.dm b/code/__DEFINES/is_helpers.dm index c91c0fda4..363d4c38b 100644 --- a/code/__DEFINES/is_helpers.dm +++ b/code/__DEFINES/is_helpers.dm @@ -60,6 +60,8 @@ GLOBAL_LIST_INIT(turfs_without_ground, typecacheof(list( #define isbrain(A) (istype(A, /mob/living/brain)) +#define isnoncarbon(A) (isliving(A) && !iscarbon(A)) + //Carbon mobs #define iscarbon(A) (istype(A, /mob/living/carbon)) diff --git a/code/modules/client/preferences.dm b/code/modules/client/preferences.dm index fb1a9fa74..d0abb7d46 100644 --- a/code/modules/client/preferences.dm +++ b/code/modules/client/preferences.dm @@ -2435,9 +2435,10 @@ GLOBAL_LIST_EMPTY(preferences_datums) features["belly_size"] = clamp(new_bellysize, BELLY_MIN_SIZE, BELLY_MAX_SIZE) if("butt_size") - var/new_buttsize = input(user, "Butt size :\n([BUTT_MIN_SIZE]-5)", "Character Preference") as num|null + var/new_buttsize = input(user, "Butt size :\n([BUTT_MIN_SIZE]-[BUTT_MAX_SIZE_SELECTABLE])", "Character Preference") as num|null if(new_buttsize != null) - features["butt_size"] = clamp(new_buttsize, BUTT_MIN_SIZE, 5) //Restricted to 5 in menu, because we have chems to make them big IC, like with breasts and what not. + features["butt_size"] = clamp(new_buttsize, BUTT_MIN_SIZE, BUTT_MAX_SIZE_SELECTABLE) + //Restricted to 5 in menu, because we have chems to make them big IC, like with breasts and what not. if("vag_shape") var/new_shape diff --git a/code/modules/mob/living/carbon/human/species.dm b/code/modules/mob/living/carbon/human/species.dm index a036dbb7f..fc87bd816 100644 --- a/code/modules/mob/living/carbon/human/species.dm +++ b/code/modules/mob/living/carbon/human/species.dm @@ -1647,8 +1647,8 @@ GLOBAL_LIST_EMPTY(roundstart_races) ) if (target.canbearoused) target.adjustArousalLoss(5) - if (target.getArousalLoss() >= 100 && ishuman(target) && HAS_TRAIT(target, TRAIT_MASO) && target.has_dna()) - target.mob_climax(forced_climax=TRUE) + if (HAS_TRAIT(target, TRAIT_MASO)) + target.mob_climax_instant() if (!HAS_TRAIT(target, TRAIT_NYMPHO)) stop_wagging_tail(target) @@ -2005,8 +2005,7 @@ GLOBAL_LIST_EMPTY(roundstart_races) H.update_damage_overlays() if(HAS_TRAIT(H, TRAIT_MASO)) H.adjustArousalLoss(damage * brutemod * H.physiology.brute_mod) - if (H.getArousalLoss() >= 100 && ishuman(H) && H.has_dna()) - H.mob_climax(forced_climax=TRUE) + H.mob_climax_instant() else//no bodypart, we deal damage with a more general method. H.adjustBruteLoss(damage * hit_percent * brutemod * H.physiology.brute_mod) diff --git a/code/modules/mob/living/carbon/life.dm b/code/modules/mob/living/carbon/life.dm index ca11c14d0..fc3c25c15 100644 --- a/code/modules/mob/living/carbon/life.dm +++ b/code/modules/mob/living/carbon/life.dm @@ -77,8 +77,7 @@ var/mob/living/carbon/human/B = src if(HAS_TRAIT(B, TRAIT_CHOKE_SLUT)) B.adjustArousalLoss(7) - if (B.getArousalLoss() >= 100 && ishuman(B) && B.has_dna()) - B.mob_climax(forced_climax=TRUE) + B.mob_climax_instant() else SEND_SIGNAL(src, COMSIG_ADD_MOOD_EVENT, "suffocation", /datum/mood_event/suffocation) else diff --git a/code/modules/ruins/objects_and_mobs/sin_ruins.dm b/code/modules/ruins/objects_and_mobs/sin_ruins.dm index 18e90b3a6..5fba1aa4b 100644 --- a/code/modules/ruins/objects_and_mobs/sin_ruins.dm +++ b/code/modules/ruins/objects_and_mobs/sin_ruins.dm @@ -282,16 +282,16 @@ . = ..() if(.) return - if(chalice_taken == 1) + if(chalice_taken) to_chat(user, "You hear a voice in your head... \"My chalice has already been taken, dear. I cannot give you another...\"") return if(user.getArousalLoss() < 100) to_chat(user, "You hear a voice in your head... \"You are not horny enough to receive my blessing, dear~\"") return - if (ishuman(user) && user.has_dna()) - user.mob_climax(forced_climax=TRUE) - to_chat(user, "You hear a voice in your head... \"You are worth of my blessing dear~\"") - to_chat(user, "You feel overpowering pleasure surge through your entire body.") - var/A = new /obj/item/reagent_containers/chalice/lust - user.put_in_hands(A) - chalice_taken = 1 \ No newline at end of file + var/mob/living/carbon/user_carbon = user + user_carbon.mob_climax_instant() + to_chat(user, "You hear a voice in your head... \"You are worthy of my blessing, dear~\"") + to_chat(user, "You feel overpowering pleasure surge through your entire body.") + var/A = new /obj/item/reagent_containers/chalice/lust + user.put_in_hands(A) + chalice_taken = TRUE \ No newline at end of file diff --git a/code/modules/surgery/organs/vocal_cords.dm b/code/modules/surgery/organs/vocal_cords.dm index b2d19c586..7c6fe05ca 100644 --- a/code/modules/surgery/organs/vocal_cords.dm +++ b/code/modules/surgery/organs/vocal_cords.dm @@ -581,7 +581,7 @@ for(var/V in listeners) var/mob/living/carbon/human/H = V if(H.canbearoused && H.has_dna() && HAS_TRAIT(H, TRAIT_NYMPHO)) // probably a redundant check but for good measure - H.mob_climax(forced_climax=TRUE) + H.mob_climax_instant() */ else if((findtext(message, family_friendly_words))) cooldown = COOLDOWN_MEME @@ -1123,7 +1123,7 @@ if(E.phase > 1) if(HAS_TRAIT(H, TRAIT_NYMPHO) && H.canbearoused && E.lewd) // probably a redundant check but for good measure addtimer(CALLBACK(GLOBAL_PROC, .proc/to_chat, H, "Your [E.enthrallGender] pushes you over the limit, overwhelming your body with pleasure."), 5) - H.mob_climax(forced_climax=TRUE) + H.mob_climax_instant() H.SetStun(20) H.setArousalLoss(H.min_arousal) E.resistanceTally = 0 //makes resistance 0, but resets arousal, resistance buildup is faster unaroused (massively so). diff --git a/hyperstation/code/modules/arousal/arousalhud.dm b/hyperstation/code/modules/arousal/arousalhud.dm index 509438813..318edefd0 100644 --- a/hyperstation/code/modules/arousal/arousalhud.dm +++ b/hyperstation/code/modules/arousal/arousalhud.dm @@ -11,7 +11,15 @@ for(var/obj/item/organ/genital/G in U.internal_organs) if(!G.nochange) if(!G.dontlist) - dat += "[G.mode == "hidden" ? "[G.name] (Hidden)" : (G.mode == "clothes" ? "[G.name] (Hidden by Clothes)" : (G.mode == "visable" ? "[G.name] (Visable)" : "[G.name] (Visable)"))]
" + var/genital_visibility = "[G.name] (Visible)" + switch(G.mode) + if(GENITALS_HIDDEN) + genital_visibility = "[G.name] (Hidden)" + if(GENITALS_CLOTHES) + genital_visibility = "[G.name] (Hidden by Clothes)" + if(GENITALS_VISIBLE) + genital_visibility = "[G.name] (Visible)" + dat += "[genital_visibility]
" dat += {"
Contexual Options

"} var/obj/item/organ/genital/penis/P = user.getorganslot("penis") @@ -45,20 +53,17 @@ if(Belly.inflatable) dat += "Deflate belly" dat += "(Shrink your belly down a size)
" - - - if(user.pulling) + + if(user.pulling && !isnoncarbon(user.pulling)) // do not fuck animals dat += "Climax over [user.pulling]" //you can cum on objects if you really want... dat += "(Orgasm over a person or object.)
" - if(isliving(user.pulling)) - if(iscarbon(user.pulling)) - dat += "Climax with [user.pulling]" - dat += {"(Orgasm with another person.)
"} - - var/mob/living/carbon/human/H = user.pulling - if(H.breedable && P && H) - dat += "Impregnate [U.pulling] ([clamp(U.impregchance,0,100)]%)" - dat += "(Climax inside another person, and attempt to knock them up.)
" + if(iscarbon(user.pulling)) + dat += "Climax with [user.pulling]" + dat += {"(Orgasm with another person.)
"} + var/mob/living/carbon/human/H = user.pulling + if(H && H.breedable && P) + dat += "Impregnate [U.pulling] ([clamp(U.impregchance,0,100)]%)" + dat += "(Climax inside another person, and attempt to knock them up.)
" else dat += "Climax over" dat += "(Requires a partner)
" @@ -81,11 +86,9 @@ dat += "Old Menu" dat += "Toggle Undergarments " dat += "
" - var/datum/browser/popup = new(user, "arousal", "Arousal Panel") popup.set_content(dat) popup.set_title_image(user.browse_rsc_icon(icon, icon_state), 500,600) - popup.open() @@ -140,7 +143,7 @@ T.toggle_visibility(picked_visibility) if(href_list["masturbate"]) - if (H.arousalloss >= (H.max_arousal / 100) * 33) //requires 33% arousal. + if (H.isPercentAroused(33)) //requires 33% arousal. H.solomasturbate() return else @@ -148,7 +151,7 @@ return if(href_list["container"]) - if (H.arousalloss >= (H.max_arousal / 100) * 33) //requires 33% arousal. + if (H.isPercentAroused(33)) //requires 33% arousal. H.cumcontainer() return else @@ -156,7 +159,7 @@ return if(href_list["clothesplosion"]) - if (H.arousalloss >= (H.max_arousal / 100) * 33) //Requires 33% arousal. + if (H.isPercentAroused(33)) //Requires 33% arousal. H.clothesplosion() return else @@ -164,7 +167,7 @@ return if(href_list["climaxself"]) - if (H.arousalloss >= (H.max_arousal / 100) * 33) //requires 33% arousal. + if (H.isPercentAroused(33)) //requires 33% arousal. H.climaxself() return else @@ -172,7 +175,7 @@ return if(href_list["climax"]) - if (H.arousalloss >= (H.max_arousal / 100) * 33) //requires 33% arousal. + if (H.isPercentAroused(33)) //requires 33% arousal. H.climaxalone(FALSE) return else @@ -180,7 +183,7 @@ return if(href_list["climaxover"]) - if (H.arousalloss >= (H.max_arousal / 100) * 33) //requires 33% arousal. + if (H.isPercentAroused(33)) //requires 33% arousal. H.climaxover(usr.pulling) return else @@ -188,7 +191,7 @@ return if(href_list["climaxwith"]) - if (H.arousalloss >= (H.max_arousal / 100) * 33) //requires 33% arousal. + if (H.isPercentAroused(33)) //requires 33% arousal. H.climaxwith(usr.pulling) return else @@ -196,7 +199,7 @@ return if(href_list["impreg"]) - if (H.arousalloss >= (H.max_arousal / 100) * 33) //requires 33% arousal. + if (H.isPercentAroused(33)) //requires 33% arousal. H.impregwith(usr.pulling) return else @@ -331,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) @@ -365,55 +368,31 @@ obj/screen/arousal/proc/kiss() to_chat(src, "You cannot climax without choosing genitals.") return -/mob/living/carbon/human/proc/climaxwith(mob/living/T) - - var/mob/living/carbon/human/L = pick_partner() +/mob/living/carbon/human/proc/climaxwith(mob/living/carbon/human/partner) + if(!partner) + to_chat(src, "You cannot do this alone.") + return var/obj/item/organ/genital/picked_organ picked_organ = pick_climax_genitals() - if(picked_organ) - var/mob/living/partner = L - if(partner) - src << browse(null, "window=arousal") //alls fine, we can close the window now. - var/obj/item/organ/genital/penis/P = picked_organ - var/spillage = "No" //default to no, just incase player has items on to prevent climax - if(!P.condom == 1&&!P.sounding == 1) //you cant climax with a condom on or sounding in. - spillage = input(src, "Would your fluids spill outside?", "Choose overflowing option", "Yes") as anything in list("Yes", "No") - if(spillage == "Yes") - mob_climax_partner(picked_organ, partner, TRUE, FALSE, FALSE) - else - mob_climax_partner(picked_organ, partner, FALSE, FALSE, FALSE) - else - to_chat(src, "You cannot do this alone.") - return - else //They either lack organs that can masturbate, or they didn't pick one. + if(!picked_organ) to_chat(src, "You cannot climax without choosing genitals.") return + src << browse(null, "window=arousal") // close arousal window + mob_climax_inside_spillage(picked_organ, partner, impreg=FALSE) - -/mob/living/carbon/human/proc/climaxover(mob/living/T) - - var/mob/living/carbon/human/L = T +/mob/living/carbon/human/proc/climaxover(atom/partner) var/obj/item/organ/genital/picked_organ picked_organ = pick_climax_genitals() - if(picked_organ) - src << browse(null, "window=arousal") //alls fine, we can close the window now. - var/mob/living/partner = L - if(partner) - var/obj/item/organ/genital/penis/P = picked_organ - if(P.condom == 1) - to_chat(src, "You cannot do this action with a condom on.") - return - if(P.sounding == 1) - to_chat(src, "You cannot do this action with a sounding in.") - return - mob_climax_partner(picked_organ, partner, FALSE, FALSE, TRUE) - else - to_chat(src, "You cannot do this alone.") - return - else //They either lack organs that can masturbate, or they didn't pick one. + if(!picked_organ) to_chat(src, "You cannot climax without choosing genitals.") return + src << browse(null, "window=arousal") //alls fine, we can close the window now. + if(!partner || isnoncarbon(partner)) + to_chat(src, "You cannot do this alone.") + return + mob_climax_cover(picked_organ, partner) + /mob/living/carbon/human/proc/clothesplosion() if(usr.restrained(TRUE)) @@ -428,39 +407,29 @@ obj/screen/arousal/proc/kiss() H.visible_message("[H] explodes out of their clothes!'") - -/mob/living/carbon/human/proc/impregwith(mob/living/T) - - var/mob/living/carbon/human/L = pick_partner() - var/obj/item/organ/genital/picked_organ - picked_organ = src.getorganslot("penis") //Impregnation must be done with a penis. - if(picked_organ) - var/mob/living/partner = L - if(partner) - if(!partner.breedable)//check if impregable. - to_chat(src, "Your partner cannot be impregnated.")//some fuckary happening, you shouldnt even get to this point tbh. - return - var/obj/item/organ/genital/penis/P = picked_organ - //you cant impreg with a condom on or sounding in. - if(P.condom == 1) - to_chat(src, "You cannot do this action with a condom on.") - return - if(P.sounding == 1) - to_chat(src, "You cannot do this action with a sounding in.") - return - src << browse(null, "window=arousal") //alls fine, we can close the window now. - //Keeping this for messy fun - var/spillage = input(src, "Would your fluids spill outside?", "Choose overflowing option", "Yes") as anything in list("Yes", "No") - if(spillage == "Yes") - mob_climax_partner(picked_organ, partner, TRUE, TRUE, FALSE) - else - mob_climax_partner(picked_organ, partner, FALSE, TRUE, FALSE) - else - to_chat(src, "You cannot do this alone.") - return - else //no penis :( - to_chat(src, "You cannot impregnate without a penis.") +/mob/living/carbon/human/proc/impregwith(mob/living/carbon/T) + var/mob/living/carbon/human/partner = pick_partner() + var/obj/item/organ/genital/penis/picked_organ = src.getorganslot("penis") + // wow that is a lot of conditionals huh + if(!picked_organ) // no penis :( + to_chat(src, "You cannot impregnate someone without a penis.") return + if(!partner) + to_chat(src, "You cannot do this alone.") + return + if(!partner.breedable) //check if impregable. + to_chat(src, "Your partner cannot be impregnated.") + //some fuckery happening, you shouldnt even get to this point tbh. + return + if(picked_organ.condom) + to_chat(src, "You cannot impregnate someone with a condom on.") + return + if(picked_organ.sounding) + 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_inside_spillage(picked_organ, partner, impreg=TRUE) + /mob/living/carbon/human/proc/cumcontainer(mob/living/T) //We'll need hands and no restraints. @@ -500,6 +469,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")) @@ -523,7 +497,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/condom.dm b/hyperstation/code/obj/condom.dm index 727ff4739..2c3122fe2 100644 --- a/hyperstation/code/obj/condom.dm +++ b/hyperstation/code/obj/condom.dm @@ -115,7 +115,6 @@ obj/item/condom/update_icon() return if(!P.condom) return - var/obj/item/condom/filled/C = new P.linked_organ.reagents.trans_to(C, P.linked_organ.reagents.total_volume) C.loc = loc diff --git a/hyperstation/code/obj/farming/farming.dm b/hyperstation/code/obj/farming/farming.dm index 8c203d78d..ad4671774 100644 --- a/hyperstation/code/obj/farming/farming.dm +++ b/hyperstation/code/obj/farming/farming.dm @@ -1,5 +1,5 @@ -/obj/item/reagent_containers/food/snacks/grown/wheat/ - var stacktype = /obj/item/stack/tile/hay +/obj/item/reagent_containers/food/snacks/grown/wheat + var/stacktype = /obj/item/stack/tile/hay var/tile_coefficient = 0.02 // same as grass /obj/item/reagent_containers/food/snacks/grown/wheat/attack_self(mob/user) diff --git a/hyperstation/code/obj/fleshlight.dm b/hyperstation/code/obj/fleshlight.dm index 125c7ec34..421cfb8f6 100644 --- a/hyperstation/code/obj/fleshlight.dm +++ b/hyperstation/code/obj/fleshlight.dm @@ -45,11 +45,9 @@ inuse = 1 if(!(C == user)) //if we are targeting someone else. C.visible_message("[user] is trying to use [src] on [C]'s penis.", "[user] is trying to use [src] on your penis.") - if(!do_mob(user, C, 3 SECONDS)) //3 second delay inuse = 0 return - //checked if not used on yourself, if not, carry on. playsound(src, 'sound/lewd/slaps.ogg', 30, 1, -1) //slapping sound inuse = 0 @@ -57,27 +55,22 @@ C.visible_message("[user] pumps [src] on [C]'s penis.", "[user] pumps [src] up and down on your penis.") else user.visible_message("[user] pumps [src] on their penis.", "You pump the fleshlight on your penis.") - if(prob(30)) //30% chance to make them moan. C.emote("moan") - C.do_jitter_animation() C.adjustArousalLoss(20) //make the target more aroused. - if (C.getArousalLoss() >= 100 && ishuman(C) && C.has_dna()) - C.mob_climax(forced_climax=TRUE) //make them cum if they are over the edge. - + if(C.can_orgasm() && ishuman(C)) + var/mob/living/carbon/human/user_human = C + user_human.mob_climax_outside(P, mb_time=0) // instant, because we already did a warmup return - else to_chat(user, "You don't see anywhere to use this on.") - inuse = 0 ..() //Hyperstation 13 portal fleshlight //kinky! - /obj/item/portallight name = "portal fleshlight" desc = "A silver love(TM) fleshlight, used to stimulate someones penis, with bluespace tech that allows lovers to hump at a distance." @@ -95,7 +88,6 @@ var/paired = 0 var/obj/item/portalunderwear var/useable = FALSE - var/option = "" /obj/item/portallight/examine(mob/user) . = ..() @@ -104,136 +96,109 @@ else . += "The device is paired, and awaiting input. " -/obj/item/portallight/attack(mob/living/carbon/C, mob/living/user) //use portallight! nearly the same as the fleshlight apart from you have a buddy! - var/obj/item/organ/genital/penis/P = C.getorganslot("penis") - +/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(!useable) - to_chat(user, "It seems the device has failed or your partner is not wearing their device.") - - if(C == user)//if your using it on yourself, more options! otherwise, just fuck. + if(!UpdateUsability()) + to_chat(holder, "\The [src] appears to be currently unusable.") + return + var/option = "Fuck" + if(user == holder) option = input(usr, "Choose action", "Portal Fleshlight", "Fuck") in list("Fuck", "Lick", "Touch") - else - option = "Fuck" - - var/obj/item/organ/genital/G - if(istype(portalunderwear.loc, /obj/item/organ/genital)) //Sanity check. Without this it will runtime error. - G = portalunderwear.loc - if(!G) + 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 = user.getorganslot("penis") + if(option == "Fuck" && !P.is_exposed()) + to_chat(holder, "You don't see anywhere to use this on.") return - var/mob/living/carbon/human/M = G.owner - - if(option == "Fuck"&&!P.is_exposed()) //we are trying to fuck with no penis! - to_chat(user, "You don't see anywhere to use this on.") - return - else //other options dont need checks - inuse = 1 - if(!(C == user)) //if we are targeting someone else. - C.visible_message("[user] is trying to use [src] on [C]'s penis.", "[user] is trying to use [src] on your penis.") - - if(!do_mob(user, C, 3 SECONDS)) //3 second delay - inuse = 0 - return - - //checked if not used on yourself, if not, carry on. - if(option == "Fuck") - playsound(src, 'sound/lewd/slaps.ogg', 30, 1, -1) //slapping sound for fuck. - - inuse = 0 - if(!(C == user)) - C.visible_message("[user] pumps [src] on [C]'s penis.", "[user] pumps [src] up and down on your penis.") - else - if(option == "Fuck") - user.visible_message("[user] pumps [src] on their penis.", "You pump the fleshlight on your penis.") - if(option == "Lick") - user.visible_message("[user] licks into [src].", "You lick into [src].") - if(option == "Touch") - user.visible_message("[user] touches softly against [src].", "You touch softly on [src].") - - - if(prob(30)) //30% chance to make your partner moan. - M.emote("moan") - - if(option == "Fuck")// normal fuck - to_chat(M, "You feel a [P.length] inch, [P.shape] shaped penis pumping through the portal into your [G.name].")//message your partner, and kinky! - if(prob(30)) //30% chance to make them moan. - C.emote("moan") - if(prob(30)) //30% chance to make your partner moan. - M.emote("moan") - C.adjustArousalLoss(20) - M.adjustArousalLoss(20) - M.do_jitter_animation() //make your partner shake too! - - if (M.getArousalLoss() >= 100 && ishuman(M) && prob(5))//Why not have a probability to cum when someone's getting nailed with max arousal?~ - if(G.is_exposed()) //Oh yea, if vagina is not exposed, the climax will not cause a spill - M.mob_climax_outside(G, spillage = TRUE) - else - M.mob_climax_outside(G, spillage = FALSE) - - if (C.getArousalLoss() >= 100 && ishuman(C) && C.has_dna()) - var/mob/living/carbon/human/O = C - - if( (P.condom == 1) || (P.sounding == 1)) //If coundomed and/or sounded, do not fire impreg chance - O.mob_climax_partner(P, M, FALSE, FALSE, FALSE, TRUE) - else //Else, fire impreg chance - if(G.name == "vagina") //no more spontaneous impregnations through the butt! - O.mob_climax_partner(P, M, FALSE, TRUE, FALSE, TRUE) - else - O.mob_climax_partner(P, M, FALSE, FALSE, FALSE, TRUE) - - if(option == "Lick") - to_chat(M, "You feel a tongue lick you through the portal against your [G.name].") - M.adjustArousalLoss(10) - if(option == "Touch") - to_chat(M, "You feel someone touching your [G.name] through the portal.") - M.adjustArousalLoss(5) - + /* + WARMUP START - prevents spam/instant climax + */ + inuse = TRUE + 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 + /* + WARMUP END - proceed with the action + */ + switch(option) + if("Fuck") + playsound(src, 'sound/lewd/slaps.ogg', 30, 1, -1) + if(user == holder) + holder.visible_message("[holder] pumps [src] on their penis.", + "You pump the fleshlight on your penis.") + else + 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)) + 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(user.can_orgasm()) + var/mob/living/carbon/human/O = user + var/impreg_chance = target_genital.name == "vagina" && !P.condom && !P.sounding + if(O.mob_climax_in_partner(P, target, spillage=FALSE, remote=TRUE) && impreg_chance) + target.impregnate(by=O) + if("Lick") + 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") + 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) + if(prob(30)) + target.emote("moan") ..() /obj/item/portallight/proc/updatesleeve() //get their looks and vagina colour! - cut_overlays()//remove current overlays - - var/obj/item/organ/genital/G - if(istype(portalunderwear.loc, /obj/item/organ/genital)) //Sanity check. Without this it will runtime. - G = portalunderwear.loc - if(!G) - useable = FALSE + cut_overlays() + if(!UpdateUsability()) return + var/obj/item/organ/genital/G = portalunderwear.loc + var/mob/living/carbon/human/H = G.owner // H is verified by UpdateUsability() + var/sleeve_species = "normal" + switch(H.dna.species.name) + if("Lizardperson") + sleeve_species = "lizard" + if("Slimeperson") + sleeve_species = "slime" + if("Avian") + sleeve_species = "avian" + sleeve = mutable_appearance('hyperstation/icons/obj/fleshlight.dmi', "portal_sleeve_[sleeve_species]") + sleeve.color = "#" + H.dna.features["mcolor"] + add_overlay(sleeve) + if(G.name == "vagina") + organ = mutable_appearance('hyperstation/icons/obj/fleshlight.dmi', "portal_vag") + organ.color = portalunderwear.loc.color + if(G.name == "anus") + organ = mutable_appearance('hyperstation/icons/obj/fleshlight.dmi', "portal_anus") + organ.color = "#" + H.dna.features["mcolor"] + add_overlay(organ) - var/mob/living/carbon/human/H = G.owner +/obj/item/portallight/proc/UpdateUsability() + var/useable = FALSE + if(portalunderwear && istype(portalunderwear.loc, /obj/item/organ/genital)) + var/obj/item/organ/genital/loc_genitals = portalunderwear.loc + if(loc_genitals.owner && ishuman(loc_genitals.owner)) + useable = TRUE + src.useable = useable + return useable - if(H) //if the portal panties are on someone. - sleeve = mutable_appearance('hyperstation/icons/obj/fleshlight.dmi', "portal_sleeve_normal") - if(H.dna.species.name == "Lizardperson") // lizard nerd - sleeve = mutable_appearance('hyperstation/icons/obj/fleshlight.dmi', "portal_sleeve_lizard") - - if(H.dna.species.name == "Slimeperson") // slime nerd - sleeve = mutable_appearance('hyperstation/icons/obj/fleshlight.dmi', "portal_sleeve_slime") - - if(H.dna.species.name == "Avian") // bird nerd - sleeve = mutable_appearance('hyperstation/icons/obj/fleshlight.dmi', "portal_sleeve_avian") - - sleeve.color = "#" + H.dna.features["mcolor"] - add_overlay(sleeve) - - if(G.name == "vagina") - organ = mutable_appearance('hyperstation/icons/obj/fleshlight.dmi', "portal_vag") - organ.color = portalunderwear.loc.color - if(G.name == "anus") - organ = mutable_appearance('hyperstation/icons/obj/fleshlight.dmi', "portal_anus") - organ.color = "#" + H.dna.features["mcolor"] - - useable = TRUE - add_overlay(organ) - else - useable = FALSE //Hyperstation 13 portal underwear //can be attached to vagina or anus, just like the vibrator, still requires pairing with the portallight - /obj/item/portalpanties name = "portal panties" desc = "A silver love(TM) pair of portal underwear, with bluespace tech allows lovers to hump at a distance. Needs to be paired with a portal fleshlight before use." @@ -252,7 +217,6 @@ else . += "The device is paired, and awaiting attachment. " - /obj/item/portalpanties/attackby(obj/item/I, mob/living/user) //pairing if(istype(I, /obj/item/portallight)) var/obj/item/portallight/P = I @@ -268,11 +232,9 @@ ..() //just allows people to hit it with other objects, if they so wished. /obj/item/portalpanties/attack(mob/living/carbon/C, mob/living/user) - if(!portallight) //we arent paired yet! noobie trap, let them know. to_chat(user, "[src] can only be attached once paired with a portal fleshlight.") return - var/obj/item/organ/genital/picked_organ var/mob/living/carbon/human/S = user var/mob/living/carbon/human/T = C @@ -282,21 +244,17 @@ "[user] is trying to put [src] on you!") if(!do_mob(user, C, 3 SECONDS))//warn them and have a delay of 5 seconds to apply. return - if((picked_organ.name == "vagina")||(picked_organ.name == "anus")) //only fits on a vagina or anus - src.shapetype = picked_organ.name if(!picked_organ.equipment) to_chat(user, "You wrap [src] around [T]'s [picked_organ.name].") else to_chat(user, "They already have [picked_organ.equipment.name] there.") return - if(!user.transferItemToLoc(src, picked_organ)) //check if you can put it in return src.attached = TRUE picked_organ.equipment = src - var/obj/item/portallight/P = portallight //now we need to send what they look like, but saddly if the person changes colour for what ever reason, it wont update. but dont tell people shh. if(P) //just to make sure @@ -312,6 +270,7 @@ var/obj/item/portallight/P = portallight P.updatesleeve() + /obj/item/storage/box/portallight name = "Portal Fleshlight and Underwear" icon = 'hyperstation/icons/obj/fleshlight.dmi' diff --git a/hyperstation/code/obj/pregnancytester.dm b/hyperstation/code/obj/pregnancytester.dm index 974fda3ab..d08610271 100644 --- a/hyperstation/code/obj/pregnancytester.dm +++ b/hyperstation/code/obj/pregnancytester.dm @@ -8,7 +8,6 @@ var/results = "null" w_class = WEIGHT_CLASS_TINY - /obj/item/pregnancytest/attack_self(mob/user) if(QDELETED(src)) return @@ -16,28 +15,26 @@ return if(isAI(user)) return - if(user.stat > 0)//unconscious or dead + if(user.stat > CONSCIOUS)//unconscious or dead return - if(status == 1) - return //Already been used once, pregnancy tests only work once. - test(user) - -/obj/item/pregnancytest/proc/force(mob/living/user) - //Force it negative - icon_state = "negative" - name = "[results] pregnancy test" - status = 1 - to_chat(user, "You use the pregnancy test, the display reads negative!") - - -/obj/item/pregnancytest/proc/test(mob/living/user) + Test(user) +/obj/item/pregnancytest/proc/Test(mob/living/user) + if(status) + return + var/_results = FALSE var/obj/item/organ/genital/womb/W = user.getorganslot("womb") - if(W.pregnant == 1) - results = "positive" - icon_state = "positive" - name = "[results] pregnancy test" - status = 1 - to_chat(user, "You use the pregnancy test, the display reads positive!") - else - force(user) \ No newline at end of file + if(!W) + return // no reason to waste the single-use on them + if(W.pregnant) + _results = TRUE + UpdateResult(user, _results) + +/obj/item/pregnancytest/proc/UpdateResult(mob/living/user, results) + var/result_text = results ? "positive" : "negative" + src.results = result_text + icon_state = result_text + name = "[results] preganancy test" + status = TRUE + if(user) + to_chat(user, "You use the pregnancy test, the display reads [results]!") \ No newline at end of file diff --git a/hyperstation/code/obj/vibrator.dm b/hyperstation/code/obj/vibrator.dm index 51f495945..40e1446e9 100644 --- a/hyperstation/code/obj/vibrator.dm +++ b/hyperstation/code/obj/vibrator.dm @@ -132,14 +132,13 @@ Code: if(2) //med, can make you cum to_chat(U, "You feel intense pleasure surge through your [G.name]") U.do_jitter_animation() - if (U.getArousalLoss() >= 100 && ishuman(U) && U.has_dna()) - U.mob_climax(forced_climax=TRUE) + U.mob_climax_instant() if(3) //high, makes you stun to_chat(U, "You feel overpowering pleasure surge through your [G.name]") U.Jitter(3) U.Stun(30) - if (U.getArousalLoss() >= 100 && ishuman(U) && U.has_dna()) - U.mob_climax(forced_climax=TRUE) + if (U.can_orgasm()) + U.mob_climax_instant() if(prob(50)) U.emote("moan") diff --git a/modular_citadel/code/datums/status_effects/chems.dm b/modular_citadel/code/datums/status_effects/chems.dm index 92aa3b21b..433c20135 100644 --- a/modular_citadel/code/datums/status_effects/chems.dm +++ b/modular_citadel/code/datums/status_effects/chems.dm @@ -620,7 +620,7 @@ else if (lowertext(customTriggers[trigger]) == "cum")//aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa if (HAS_TRAIT(C, TRAIT_NYMPHO) && lewd) if (C.getArousalLoss() > 80) - C.mob_climax(forced_climax=TRUE) + C.mob_climax_instant() C.SetStun(10)//We got your stun effects in somewhere, Kev. else C.adjustArousalLoss(10) diff --git a/modular_citadel/code/modules/arousal/arousal.dm b/modular_citadel/code/modules/arousal/arousal.dm index 5e37cbbe7..8b8780b46 100644 --- a/modular_citadel/code/modules/arousal/arousal.dm +++ b/modular_citadel/code/modules/arousal/arousal.dm @@ -11,7 +11,6 @@ /mob/living/carbon/human canbearoused = TRUE - var/saved_underwear = ""//saves their underwear so it can be toggled later var/saved_undershirt = "" var/saved_socks = "" @@ -62,7 +61,6 @@ /mob/living/proc/handle_arousal() - /mob/living/carbon/handle_arousal() if(canbearoused && dna) var/datum/species/S @@ -110,8 +108,11 @@ if(updating_arousal) updatearousal() +/** + * despite the name of this, it actually returns a number between 0 and 100 + */ /mob/living/proc/getPercentAroused() - var/percentage = ((100 / max_arousal) * arousalloss) + var/percentage = ((arousalloss / max_arousal) * 100) return percentage /mob/living/proc/isPercentAroused(percentage)//returns true if the mob's arousal (measured in a percent of 100) is greater than the arg percentage. @@ -127,26 +128,25 @@ /mob/living/carbon/updatearousal() . = ..() - - for(var/obj/item/organ/genital/G in internal_organs) - if(istype(G)) + for(var/obj/item/organ/genital/genital in internal_organs) + if(istype(genital)) var/datum/sprite_accessory/S - switch(G.type) + switch(genital.type) if(/obj/item/organ/genital/penis) - S = GLOB.cock_shapes_list[G.shape] + S = GLOB.cock_shapes_list[genital.shape] if(/obj/item/organ/genital/testicles) - S = GLOB.balls_shapes_list[G.shape] + S = GLOB.balls_shapes_list[genital.shape] if(/obj/item/organ/genital/vagina) - S = GLOB.vagina_shapes_list[G.shape] + S = GLOB.vagina_shapes_list[genital.shape] if(/obj/item/organ/genital/breasts) - S = GLOB.breasts_shapes_list[G.shape] + S = GLOB.breasts_shapes_list[genital.shape] if(S?.alt_aroused) - G.aroused_state = isPercentAroused(G.aroused_amount) - if(getArousalLoss() >= ((max_arousal / 100) * 33)) - G.aroused_state = TRUE + genital.aroused_state = isPercentAroused(genital.aroused_amount) + if(getArousalLoss() >= isPercentAroused(33)) + genital.aroused_state = TRUE else - G.aroused_state = FALSE - G.update_appearance() + genital.aroused_state = FALSE + genital.update_appearance() @@ -166,41 +166,13 @@ return FALSE else if(hud_used.arousal) - if(stat == DEAD) - hud_used.arousal.icon_state = "arousal0" - return TRUE - if(getArousalLoss() == max_arousal) - hud_used.arousal.icon_state = "arousal100" - return TRUE - if(getArousalLoss() >= (max_arousal / 100) * 90)//M O D U L A R , W O W - hud_used.arousal.icon_state = "arousal90" - return TRUE - if(getArousalLoss() >= (max_arousal / 100) * 80)//M O D U L A R , W O W - hud_used.arousal.icon_state = "arousal80" - return TRUE - if(getArousalLoss() >= (max_arousal / 100) * 70)//M O D U L A R , W O W - hud_used.arousal.icon_state = "arousal70" - return TRUE - if(getArousalLoss() >= (max_arousal / 100) * 60)//M O D U L A R , W O W - hud_used.arousal.icon_state = "arousal60" - return TRUE - if(getArousalLoss() >= (max_arousal / 100) * 50)//M O D U L A R , W O W - hud_used.arousal.icon_state = "arousal50" - return TRUE - if(getArousalLoss() >= (max_arousal / 100) * 40)//M O D U L A R , W O W - hud_used.arousal.icon_state = "arousal40" - return TRUE - if(getArousalLoss() >= (max_arousal / 100) * 30)//M O D U L A R , W O W - hud_used.arousal.icon_state = "arousal30" - return TRUE - if(getArousalLoss() >= (max_arousal / 100) * 20)//M O D U L A R , W O W - hud_used.arousal.icon_state = "arousal10" - return TRUE - if(getArousalLoss() >= (max_arousal / 100) * 10)//M O D U L A R , W O W - hud_used.arousal.icon_state = "arousal10" - return TRUE - else - hud_used.arousal.icon_state = "arousal0" + var/arousal_state = "arousal0" + if(stat != DEAD) + var/arousal_percent = getPercentAroused() + var/arousal_rounded = FLOOR(arousal_percent, 10) + arousal_state = "arousal[arousal_rounded]" + hud_used.arousal.icon_state = arousal_state + return TRUE /obj/screen/arousal name = "arousal" @@ -217,637 +189,654 @@ if(M.canbearoused) ui_interact(usr) -/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. +/** + * 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) + if(src.canbearoused && src.getArousalLoss() >= arousal && ishuman(src) && src.has_dna()) + return TRUE + return FALSE + + +/mob/living/proc/mob_climax() 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() >= ((max_arousal / 100) * 33))//33% arousal or greater required + if(isPercentAroused(33))//33% arousal or greater required src.visible_message("[src] starts masturbating!", \ - "You start masturbating.") + "You start masturbating.") if(do_after(src, 30, target = src)) src.visible_message("[src] relieves [p_them()]self!", \ - "You have relieved yourself.") + "You have relieved yourself.") SEND_SIGNAL(src, COMSIG_ADD_MOOD_EVENT, "orgasm", /datum/mood_event/orgasm) setArousalLoss(min_arousal) else 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 +/** + * Arguments: + * - `genital`: Genitals being used for this interaction. + * - `cover`: Whether or not the mob is climaxing over themselves. + * - `mb_time`: Warm-up time for this interaction. + */ +/mob/living/carbon/human/proc/mob_masturbate(obj/item/organ/genital/genital, cover=FALSE, mb_time=3 SECONDS) 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 = genital.get_fluid_source() + var/condomed = FALSE + var/sounded = FALSE + if(genital.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 [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) - 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].") - + src.visible_message("[src] starts to [genital.masturbation_verb] [p_their()] [genital.name].", \ + "You start to [genital.masturbation_verb] your [genital.name].", \ + "You start to [genital.masturbation_verb] your [genital.name].") if(do_after(src, mb_time, target = src)) if(total_fluids > 5 &&!condomed &&!sounded) fluid_source.reaction(src.loc, TOUCH, 1, 0) fluid_source.clear_reagents() - if(!condomed && !sounded && !cover) - 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! - if(isliving(src)) - var/mutable_appearance/cumoverlaylarge = mutable_appearance('hyperstation/icons/effects/cumoverlay.dmi') - cumoverlaylarge.icon_state = "cum_large" - src.add_overlay(cumoverlaylarge) - - if(total_fluids > 5) - fluid_source.reaction(src.loc, TOUCH, 1, 0) - var/mob/living/carbon/human/H = src - if(H && G.name == "penis") - H.cumdrip_rate += rand(5,10) - fluid_source.clear_reagents() - 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].") - if(isliving(src)) - var/mutable_appearance/cumoverlay = mutable_appearance('hyperstation/icons/effects/cumoverlay.dmi') - cumoverlay.icon_state = "cum_normal" - src.add_overlay(cumoverlay) - else - src.add_cum_overlay() - - if(condomed) //condomed - src.visible_message("[src] orgasms, climaxing into [p_their()] condom ", \ - "You cum into your condom.", \ - "You have relieved yourself.") - if(sounded) //sounded - src.visible_message("[src] orgasms, but the rod blocks anything from leaking out!", \ - "You cum with the rod inside.", \ - "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) - - -/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/unable_to_come = FALSE - - if(G.producing) //Can it produce its own fluids, such as breasts? - fluid_source = G.reagents - 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 - - if(unable_to_come) - src.visible_message("[src] shudders, their [G.name] unable to cum.", \ - "Your [G.name] cannot cum, giving no relief.", \ - "Your [G.name] cannot cum, giving no relief.") - else - total_fluids = fluid_source.total_volume - if(mb_time) //as long as it's not instant, give a warning - src.visible_message("[src] looks like they're about to cum.", \ - "You feel yourself about to orgasm.", \ - "You feel yourself about to orgasm.") - if(do_after(src, mb_time, target = src)) - if(spillage) + if(!condomed && !sounded)//For when you want to make a mess of yourself. + if(cover) + fluid_source.trans_to(src, total_fluids*genital.fluid_transfer_factor) + total_fluids -= total_fluids*genital.fluid_transfer_factor + if(total_fluids > 80) // now thats a big cum! + src.add_cum_overlay("large") if(total_fluids > 5) fluid_source.reaction(src.loc, TOUCH, 1, 0) - + var/mob/living/carbon/human/H = src + if(H && genital.name == "penis") + H.cumdrip_rate += rand(5,10) fluid_source.clear_reagents() - src.visible_message("[src] orgasms[istype(src.loc, /turf/open/floor) ? ", spilling onto [src.loc]" : ""], with [p_their()] [G.name]!", \ - "You climax[istype(src.loc, /turf/open/floor) ? ", spilling onto [src.loc]" : ""] with your [G.name].", \ - "You climax using your [G.name].") - else //Else from spillage check, also note subtle text change - 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, mob/living/L, spillage = TRUE, impreg = FALSE,cover = FALSE,remote = FALSE, mb_time = 30) //Used for climaxing with any living thing - 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 - total_fluids = fluid_source.total_volume - - if(mb_time && !remote) //Skip warning if this is an instant climax. - src.visible_message("[src] is about to climax with [L]!", \ - "You're about to climax with [L]!", \ - "You're preparing to climax with something!") - 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, L)) - fluid_source.trans_to(L, total_fluids*G.fluid_transfer_factor) - total_fluids -= total_fluids*G.fluid_transfer_factor - if(total_fluids > 80) // now thats a big cum! - if(isliving(L)) - var/mutable_appearance/cumoverlaylarge = mutable_appearance('hyperstation/icons/effects/cumoverlay.dmi') - cumoverlaylarge.icon_state = "cum_large" - L.add_overlay(cumoverlaylarge) - - if(total_fluids > 5) - fluid_source.reaction(L.loc, TOUCH, 1, 0) - var/mob/living/carbon/human/H = L - if(H) - H.cumdrip_rate += rand(5,10) - fluid_source.clear_reagents() - src.visible_message("[src] climaxes over [L], using [p_their()] [G.name]!", \ - "You orgasm over [L], 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(isliving(L)) - var/mutable_appearance/cumoverlay = mutable_appearance('hyperstation/icons/effects/cumoverlay.dmi') - cumoverlay.icon_state = "cum_normal" - L.add_overlay(cumoverlay) + src.visible_message("[src] climaxes over [p_their()] self, using [p_their()] [genital.name]!", \ + "You orgasm over yourself, using your [genital.name].", \ + "You have climaxed over something, using your [genital.name].") + src.add_cum_overlay() else - L.add_cum_overlay() - - setArousalLoss(min_arousal) - if(G.can_climax) + 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) //condomed + src.visible_message("[src] orgasms, climaxing into [p_their()] condom!", \ + "You cum into your condom.", \ + "You have relieved yourself.") + if(sounded) //sounded + src.visible_message("[src] orgasms, but the rod blocks anything from leaking out!", \ + "You cum with the rod inside.", \ + "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(genital.can_climax) setArousalLoss(min_arousal) - if(spillage && !cover) - if(do_after(src, mb_time, target = src)) - if(!in_range(src, L) && !remote) - return - fluid_source.trans_to(L, total_fluids*G.fluid_transfer_factor) - total_fluids -= total_fluids*G.fluid_transfer_factor + +/** + * Mob climaxes using provided genitals without a partner. This is in contrast with `mob_climax_instant()`, + * which does not have a warm-up time and uses all genitals at once to climax. + * + * Arguments: + * - `genital`: The selected genitals for this interaction. Determines fluid type. + * - `mb_time`: The warm-up time for this interaction. + * - `spillage`: Whether or not climaxing causes this mob to spill fluids on the floor. + */ +/mob/living/carbon/human/proc/mob_climax_outside(obj/item/organ/genital/genital, spillage=TRUE, mb_time=3 SECONDS) + var/total_fluids = 0 + var/datum/reagents/fluid_source = genital.get_fluid_source() + var/unable_to_come = FALSE + if(fluid_source) + total_fluids = fluid_source.total_volume + else + unable_to_come = TRUE + if(unable_to_come) + src.visible_message("[src] shudders, their [genital.name] unable to cum.", \ + "Your [genital.name] cannot cum, giving no relief.", \ + "Your [genital.name] cannot cum, giving no relief.") + return + total_fluids = fluid_source.total_volume + if(mb_time) //as long as it's not instant, give a warning + src.visible_message("[src] looks like they're about to cum.", \ + "You feel yourself about to orgasm.", \ + "You feel yourself about to orgasm.") + if(do_after(src, mb_time, target = src)) + if(spillage) if(total_fluids > 5) - fluid_source.reaction(L.loc, TOUCH, 1, 0) - var/mob/living/carbon/human/H = L - if(H) - H.cumdrip_rate += rand(5,10) + fluid_source.reaction(src.loc, TOUCH, 1, 0) fluid_source.clear_reagents() - src.visible_message("[src] climaxes with [L][spillage ? ", overflowing and spilling":""], using [p_their()] [G.name]!", \ - "You orgasm with [L][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(L, COMSIG_ADD_MOOD_EVENT, "orgasm", /datum/mood_event/orgasm) + src.visible_message("[src] orgasms[istype(src.loc, /turf/open/floor) ? ", spilling onto [src.loc]" : ""], with [p_their()] [genital.name]!", \ + "You climax[istype(src.loc, /turf/open/floor) ? ", spilling onto [src.loc]" : ""] with your [genital.name].", \ + "You climax using your [genital.name].") + else + src.visible_message("[src] orgasms with [p_their()] [genital.name]!", \ + "You climax with your [genital.name].", \ + "You climax using your [genital.name].") + SEND_SIGNAL(src, COMSIG_ADD_MOOD_EVENT, "orgasm", /datum/mood_event/orgasm) + if(genital.can_climax) + setArousalLoss(min_arousal) - if(G.can_climax) - setArousalLoss(min_arousal) +/** + * Climax over a partner with a selected organ. This is external and creates spills on the partner/floor, + * and does not have a chance to impregnate. For this interaction, the partner may be an object instead of + * a person (but it cannot be an animal). + * + * Arguments: + * - `genital`: The genital being used for this interaction. Determines fluid type and volume. + * - `partner`: The thing being used as a partner. Generally an object or a person. + * - `mb_time`: The warm-up time for this interaction. Default is 3 seconds + */ +/mob/living/carbon/human/proc/mob_climax_cover(obj/item/organ/genital/genital, atom/partner, mb_time=3 SECONDS) + if(isnoncarbon(partner)) // deny animals + return FALSE + if(istype(genital, /obj/item/organ/genital/penis)) + var/obj/item/organ/genital/penis/P = genital + if(P.condom) + to_chat(src, "You cannot do this action with a condom on.") + return + if(P.sounding) + to_chat(src, "You cannot do this action with a sounding in.") + return + 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(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 > 5) + fluid_source.reaction(partner.loc, TOUCH, 1, 0) + fluid_source.clear_reagents() + if(ismob(partner)) + var/mob/living/carbon/partner_carbon = partner + if(partner_carbon) + var/cum_overlay = "normal" + if(total_fluids > 80) + cum_overlay = "large" + partner_carbon.add_cum_overlay(cum_overlay) + var/mob/living/carbon/human/partner_human = partner_carbon + if(partner_human) + partner_human.cumdrip_rate += rand(5,10) + 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) + + +/** + * Climax inside a partner using a selected organ. + * + * Arguments: + * - `genital`: The genital being used for this interaction. Determines type/volume of fluids. + * - `partner`: The person that this mob is climaxing in. + * - `spillage`: Whether or not the fluids will spill on the ground. + * - `remote`: Whether or not this interaction is being triggered long-distance. + * - `mb_time`: Warm-up time for this interaction. + * + * Returns: `TRUE`/`FALSE` - whether or not this interaction succeeded. + */ +/mob/living/carbon/human/proc/mob_climax_in_partner(obj/item/organ/genital/genital, mob/living/carbon/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 = 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 FALSE + total_fluids = fluid_source.total_volume + var/partner_text = partner + if(remote) + partner_text = "someone" + src.visible_message("[src] is about to climax with [partner_text]!", \ + "You're about to climax with [partner_text]!", \ + "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*genital.fluid_transfer_factor) + total_fluids -= total_fluids*genital.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], overflowing and spilling, using [p_their()] [genital.name]!",\ + "You orgasm with [partner], spilling out of them, using your [genital.name].",\ + "You have climaxed with someone, spilling out of them, using your [genital.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, L)) - return - if(do_after(src, mb_time, target = src)) - var/mob/living/carbon/H = L - 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(L, total_fluids) - total_fluids = 0 - if(!remote) - src.visible_message("[src] climaxes with [L], [p_their()] [G.name] spilling nothing!", \ - "You ejaculate with [L], 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(L, "You feel someone ejaculate inside you.") - - SEND_SIGNAL(src, COMSIG_ADD_MOOD_EVENT, "orgasm", /datum/mood_event/orgasm) - SEND_SIGNAL(L, COMSIG_ADD_MOOD_EVENT, "orgasm", /datum/mood_event/orgasm) - - if(G.can_climax) - setArousalLoss(min_arousal) - + var/can_inflate = TRUE + if(istype(genital, /obj/item/organ/genital/penis)) + var/obj/item/organ/genital/penis/P = genital + if(P.condom)//condomed. + can_inflate = FALSE + src.condomclimax() + if(can_inflate) + fluid_source.trans_to(partner, total_fluids) + if(!spillage && total_fluids > 80) // hyper - cum inflation, ONLY if not wearing a condom + partner.expand_belly(1) + src.visible_message("[src] climaxes with [partner_text], [p_their()] [genital.name] spilling nothing!", \ + "You ejaculate with [partner_text], your [genital.name] spilling nothing.", \ + "You have climaxed inside someone, your [genital.name] spilling nothing.") + if(remote) + 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(genital.can_climax) + setArousalLoss(min_arousal) //Hyper - antag code + var/mob/living/carbon/partner_carbon = partner if(src.mind.special_role == ROLE_LEWD_TRAITOR) for(var/datum/objective/obj in src.mind.objectives) - if (L.mind == obj.target) - L.mind.sexed = TRUE //sexed + 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) - //Role them odds, only people with the dicks can send the chance to the person with the settings enabled at the momment. - if(prob(L.impregchance)) - var/obj/item/organ/genital/womb/W = L.getorganslot("womb") - if(W) //check if they have a womb. - if (L.breedable == 1 && W.pregnant == 0) //Dont get pregnant again, if you are pregnant. - log_game("Debug: [L] has been impregnated by [src]") - to_chat(L, "You feel your hormones change, and a motherly instinct take over.") //leting them know magic has happened. - W.pregnant = 1 - if (HAS_TRAIT(L, TRAIT_HEAT)) - SEND_SIGNAL(L, COMSIG_ADD_MOOD_EVENT, "heat", /datum/mood_event/heat) //well done you perv. - REMOVE_TRAIT(L, TRAIT_HEAT, ROUNDSTART_TRAIT) //take the heat away, you satisfied it! - - //Make breasts produce quicker. - var/obj/item/organ/genital/breasts/B = L.getorganslot("breasts") - if (B.fluid_mult < 0.5 && B) - B.fluid_mult = 0.5 + return TRUE -/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 +/** + * User chooses whether or not their climax will spill outside of their partner. + * + * Arguments: + * - `picked_organ`: The user's genital being used. + * - `partner`: This mob's partner for the interaction. + * - `impreg`: Whether or not this interaction has a chance to impregnate. + */ +/mob/living/carbon/human/proc/mob_climax_inside_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_in_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") + if(mob_climax_in_partner(picked_organ, partner, spillage == "Yes") && impreg) + partner.impregnate(by=src) - if(G.name == "penis")//if the select organ is a penis - var/obj/item/organ/genital/penis/P = src.getorganslot("penis") - if(P.condom) //if the penis is condomed - to_chat(src, "You cannot fill containers when there is a condom over your [G.name].") - return - if(P.sounding) //if the penis is sounded - 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=3 SECONDS) 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() +/** + * 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() - var/list/worn_stuff = get_equipped_items() - - 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(worn_stuff)) //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 - var/obj/item/organ/genital/ret_organ - var/list/genitals_list = list() - var/list/worn_stuff = get_equipped_items() - - for(var/obj/item/organ/genital/G in T.internal_organs) - if(G.is_exposed(worn_stuff)) //Nude or through_clothing - if(!G.dontlist) - genitals_list += G + for(var/obj/item/organ/genital/genital in T.internal_organs) + if(genital.is_exposed() && !genital.dontlist) + genitals_list += genital 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() - var/list/worn_stuff = get_equipped_items() - - for(var/obj/item/organ/genital/G in internal_organs) - if(G.can_climax) //filter out what you can't masturbate with - if(G.is_exposed(worn_stuff)) //Nude or through_clothing - if(!G.dontlist) - genitals_list += G + for(var/obj/item/organ/genital/genital in internal_organs) + if(genital.can_climax && genital.is_exposed() && !genital.dontlist) + if(!masturbation || genital.can_masturbate_with) + genitals_list += genital 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 -/mob/living/carbon/human/proc/pick_partner_overide() //used for cumming on people without genitals exposed - var/list/partners = list() - if(src.pulling) - partners += src.pulling //Yes, even objects for now - if(src.pulledby) - partners += src.pulledby - //Now we got both of them, let's check if they're proper - for(var/I in partners) - if(isliving(I)) - else - partners -= I //No fucking objects - //NOW the list should only contain correct partners - if(!partners.len) - return null //No one left. - return input(src, "With whom?", "Sexual partner", null) in partners //pick one, default to null -/mob/living/carbon/human/proc/pick_partner() - var/list/partners = list() - if(src.pulling) - partners += src.pulling //Yes, even objects for now - if(src.pulledby) - partners += src.pulledby - //Now we got both of them, let's check if they're proper +/** + * Prompts the user to select a mob that they are either pulling or being pulled by. + * + * Arguments: + * - `needs_exposed`: Whether or not the partner must have exposed genitals. + * 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) - if(isliving(I)) - if(iscarbon(I)) - var/mob/living/carbon/C = I - if(!C.exposed_genitals.len) //Nothing through_clothing - if(!C.is_groin_exposed()) //No pants undone - if(!C.is_chest_exposed()) //No chest exposed - partners -= I //Then not proper, remove them - else - partners -= I //No fucking objects - //NOW the list should only contain correct partners + if(!iscarbon(I)) + partners -= I + continue + if(!needs_exposed) + continue + var/mob/living/carbon/C = I + if(!C.exposed_genitals.len && !C.is_groin_exposed() && !C.is_chest_exposed()) + partners -= I if(!partners.len) - return null //No one left. - return input(src, "With whom?", "Sexual partner", null) in partners //pick one, default to null + return null + 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() - for(var/obj/item/reagent_containers/container in held_items) if(container.is_open_container() || istype(container, /obj/item/reagent_containers/food/snacks)) containers_list += container - if(containers_list.len) - SC = input(src, "Into or onto what?(Cancel for nowhere)", null) as null|obj in containers_list + SC = input(src, "Into or onto what? (Cancel for nowhere)", null) as null|obj in containers_list if(SC) if(in_range(src, SC)) return SC - return null //If nothing correct, give null. + return null -//Here's the main proc itself -/mob/living/carbon/human/mob_climax(forced_climax=FALSE) //Forced is instead of the other proc, makes you cum if you have the tools for it, ignoring restraints - if(stat == DEAD) //corpses can't cum +/** + * 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(!canbearoused || !has_dna()) return if(mb_cd_timer > world.time) - if(!forced_climax) //Don't spam the message to the victim if forced to come too fast - to_chat(src, "You need to wait [DisplayTimeText((mb_cd_timer - world.time), TRUE)] before you can do that again!") + to_chat(src, "You need to wait [DisplayTimeText((mb_cd_timer - world.time), TRUE)] before you can do that again!") return mb_cd_timer = (world.time + mb_cd_length) - - - if(canbearoused && has_dna()) - if(stat==2) - to_chat(src, "You can't do that while dead!") - return - if(forced_climax) //Something forced us to cum, this is not a masturbation thing and does not progress to the other checks - for(var/obj/item/organ/O in internal_organs) - if(istype(O, /obj/item/organ/genital)) - var/obj/item/organ/genital/G = O - if(!G.can_climax) //Skip things like wombs and testicles - continue - var/mob/living/partner - var/check_target - var/list/worn_stuff = get_equipped_items() - - if(G.is_exposed(worn_stuff)) - if(src.pulling) //Are we pulling someone? Priority target, we can't be making option menus for this, has to be quick - if(isliving(src.pulling)) //Don't fuck objects - check_target = src.pulling - if(src.pulledby && !check_target) //prioritise pulled over pulledby - if(isliving(src.pulledby)) - check_target = src.pulledby - //Now we should have a partner, or else we have to come alone - if(check_target) - if(iscarbon(check_target)) //carbons can have clothes - var/mob/living/carbon/C = check_target - if(C.exposed_genitals.len || C.is_groin_exposed() || C.is_chest_exposed()) //Are they naked enough? - partner = C - if(partner) //Did they pass the clothing checks? - mob_climax_partner(G, partner, mb_time = 0) //Instant climax due to forced - continue //You've climaxed once with this organ, continue on - //not exposed OR if no partner was found while exposed, climax alone - mob_climax_outside(G, mb_time = 0) //removed climax timer for sudden, forced orgasms - //Now all genitals that could climax, have. - //Since this was a forced climax, we do not need to continue with the other stuff - return - //If we get here, then this is not a forced climax and we gotta check a few things. - - if(stat==1) //No sleep-masturbation, you're unconscious. - to_chat(src, "You must be conscious to do that!") - return - if(getArousalLoss() < 33) //flat number instead of percentage - to_chat(src, "You aren't aroused enough for that!") + if(stat == DEAD) + to_chat(src, "You can't do that while dead!") + return + if(stat == UNCONSCIOUS) + to_chat(src, "You must be conscious to do that!") + return + if(getArousalLoss() < 33) + to_chat(src, "You aren't aroused enough for that!") + return + var/list/choices = list("Masturbate", "Climax alone", "Climax with partner","Climax over partner", "Fill container", "Remove condom", "Remove sounding rod") + var/choice = input(src, "Select sexual activity", "Sexual activity:") as null|anything in choices + switch(choice) + if("Remove sounding rod") + if(restrained(TRUE)) //TRUE ignores grabs + to_chat(src, "You can't do that while restrained!") + return + var/free_hands = get_num_arms() + if(!free_hands) + to_chat(src, "You need at least one free arm.") + return + var/obj/item/organ/genital/penis/P = src.getorganslot("penis") + if(!P.sounding) + to_chat(src, "You don't have a rod inside!") + return + if(P.sounding) + to_chat(src, "You pull the rod off from the tip of your penis!") + src.removesounding() + return return - //Ok, now we check what they want to do. - var/choice = input(src, "Select sexual activity", "Sexual activity:") in list("Masturbate", "Climax alone", "Climax with partner","Climax over partner", "Fill container", "Remove condom", "Remove sounding rod") + if("Remove condom") + if(restrained(TRUE)) //TRUE ignores grabs + to_chat(src, "You can't do that while restrained!") + return + var/free_hands = get_num_arms() + if(!free_hands) + to_chat(src, "You need at least one free arm.") + return + var/obj/item/organ/genital/penis/P = src.getorganslot("penis") + if(!P.condom) + to_chat(src, "You don't have a condom on!") + return + if(P.condom) + to_chat(src, "You tug the condom off the end of your penis!") + src.removecondom() + return + return - switch(choice) - if("Remove sounding rod") - if(restrained(TRUE)) //TRUE ignores grabs - to_chat(src, "You can't do that while restrained!") - return - var/free_hands = get_num_arms() - if(!free_hands) - to_chat(src, "You need at least one free arm.") - return - var/obj/item/organ/genital/penis/P = src.getorganslot("penis") - if(!P.sounding) - to_chat(src, "You don't have a rod inside!") - return - if(P.sounding) - to_chat(src, "You pull the rod off from the tip of your penis!") - src.removesounding() - return + if("Masturbate") + if(restrained(TRUE)) //TRUE ignores grabs + to_chat(src, "You can't do that while restrained!") + return + var/free_hands = get_num_arms() + if(!free_hands) + to_chat(src, "You need at least one free arm.") + return + for(var/helditem in held_items)//how many hands are free + if(isobj(helditem)) + free_hands-- + if(free_hands <= 0) + to_chat(src, "You're holding too many things.") + return + //We got hands, let's pick an organ + var/obj/item/organ/genital/picked_organ + picked_organ = pick_climax_genitals(masturbation=TRUE, title="Masturbation") + if(picked_organ) + mob_masturbate(picked_organ) + return + else //They either lack organs that can masturbate, or they didn't pick one. + to_chat(src, "You cannot masturbate without choosing genitals.") return - if("Remove condom") - if(restrained(TRUE)) //TRUE ignores grabs - to_chat(src, "You can't do that while restrained!") - return - var/free_hands = get_num_arms() - if(!free_hands) - to_chat(src, "You need at least one free arm.") - return - var/obj/item/organ/genital/penis/P = src.getorganslot("penis") - if(!P.condom) - to_chat(src, "You don't have a condom on!") - return - if(P.condom) - to_chat(src, "You tug the condom off the end of your penis!") - src.removecondom() - return + if("Climax alone") + if(restrained(TRUE)) //TRUE ignores grabs + to_chat(src, "You can't do that while restrained!") + return + var/free_hands = get_num_arms() + if(!free_hands) + to_chat(src, "You need at least one free arm.") + return + for(var/helditem in held_items)//how many hands are free + if(isobj(helditem)) + free_hands-- + if(free_hands <= 0) + to_chat(src, "You're holding too many things.") + return + //We got hands, let's pick an organ + var/obj/item/organ/genital/picked_organ + picked_organ = pick_climax_genitals() + if(picked_organ) + mob_climax_outside(picked_organ) + return + else //They either lack organs that can masturbate, or they didn't pick one. + to_chat(src, "You cannot climax without choosing genitals.") return - if("Masturbate") - if(restrained(TRUE)) //TRUE ignores grabs - to_chat(src, "You can't do that while restrained!") - return - var/free_hands = get_num_arms() - if(!free_hands) - to_chat(src, "You need at least one free arm.") - return - for(var/helditem in held_items)//how many hands are free - if(isobj(helditem)) - free_hands-- - if(free_hands <= 0) - to_chat(src, "You're holding too many things.") - return - //We got hands, let's pick an organ - var/obj/item/organ/genital/picked_organ - picked_organ = pick_masturbate_genitals() - if(picked_organ) - mob_masturbate(picked_organ) - return - else //They either lack organs that can masturbate, or they didn't pick one. - to_chat(src, "You cannot masturbate without choosing genitals.") - return + if("Climax with partner") + //We need no hands, we can be restrained and so on, so let's pick an organ + var/obj/item/organ/genital/picked_organ + picked_organ = pick_climax_genitals() + if(!picked_organ) + to_chat(src, "You cannot climax without choosing genitals.") + return + var/mob/living/carbon/partner = pick_partner() //Get someone + if(!partner) + to_chat(src, "You cannot do this alone.") + return + var/obj/item/organ/genital/penis/P = picked_organ + var/impreg = "No" + if(partner.breedable && picked_organ.name == "penis" && !P.condom && !P.sounding) + var/impreg_question = "Would this action carry the risk of pregnancy?" + var/impreg_title = "Choose a option" + impreg = input(src, impreg_question, impreg_title, "Yes") as anything in list("Yes", "No") + mob_climax_inside_spillage(picked_organ, partner, impreg == "Yes") - if("Climax alone") - if(restrained(TRUE)) //TRUE ignores grabs - to_chat(src, "You can't do that while restrained!") - return - var/free_hands = get_num_arms() - if(!free_hands) - to_chat(src, "You need at least one free arm.") - return - for(var/helditem in held_items)//how many hands are free - if(isobj(helditem)) - free_hands-- - if(free_hands <= 0) - to_chat(src, "You're holding too many things.") - return - //We got hands, let's pick an organ - var/obj/item/organ/genital/picked_organ - picked_organ = pick_climax_genitals() - if(picked_organ) - mob_climax_outside(picked_organ) - return - else //They either lack organs that can masturbate, or they didn't pick one. - to_chat(src, "You cannot climax without choosing genitals.") - return + if("Climax over partner") + var/obj/item/organ/genital/picked_organ + picked_organ = pick_climax_genitals() + if(picked_organ) + var/mob/living/carbon/partner = pick_partner(needs_exposed=FALSE) //Get your partner, clothed or not. + if(partner) + mob_climax_cover(picked_organ, partner) + else + to_chat(src, "You cannot do this alone.") - if("Climax with partner") - //We need no hands, we can be restrained and so on, so let's pick an organ - var/obj/item/organ/genital/picked_organ - picked_organ = pick_climax_genitals() - if(picked_organ) - var/mob/living/partner = pick_partner() //Get someone - if(partner) - var/obj/item/organ/genital/penis/P = picked_organ - if(partner.breedable == 1 && picked_organ.name == "penis"&&!P.condom == 1&&!P.sounding == 1) - var/impreg = input(src, "Would this action carry the risk of pregnancy?", "Choose a option", "Yes") as anything in list("Yes", "No") - if(impreg == "Yes") //If we are impregging - var/spillage = input(src, "Would your fluids spill outside?", "Choose overflowing option", "Yes") as anything in list("Yes", "No") - if(spillage == "Yes") - mob_climax_partner(picked_organ, partner, TRUE, TRUE, FALSE) - else - mob_climax_partner(picked_organ, partner, FALSE, TRUE, FALSE) - else - var/spillage = input(src, "Would your fluids spill outside?", "Choose overflowing option", "Yes") as anything in list("Yes", "No") - if(spillage == "Yes") - mob_climax_partner(picked_organ, partner, TRUE, FALSE, FALSE) - else - mob_climax_partner(picked_organ, partner, FALSE, FALSE, FALSE) //Wow, im trash at coding, I need to find a better way of coding this, ill rewrite it later.-quote - return + if("Fill container") + //We'll need hands and no restraints. + if(restrained(TRUE)) //TRUE ignores grabs + to_chat(src, "You can't do that while restrained!") + return + var/free_hands = get_num_arms() + if(!free_hands) + to_chat(src, "You need at least one free arm.") + return + for(var/helditem in held_items)//how many hands are free + if(isobj(helditem)) + free_hands-- + if(free_hands <= 0) + to_chat(src, "You're holding too many things.") + return + //We got hands, let's pick an organ + var/obj/item/organ/genital/picked_organ + picked_organ = pick_climax_genitals() //Gotta be climaxable, not just masturbation, to fill with fluids. + if(picked_organ) + //Good, got an organ, time to pick a container + var/obj/item/reagent_containers/fluid_container = pick_climax_container() + if(fluid_container) + mob_fill_container(picked_organ, fluid_container) + return + else + to_chat(src, "You cannot do this without anything to fill.") + return + else //They either lack organs that can climax, or they didn't pick one. + to_chat(src, "You cannot fill anything without choosing genitals.") + return + else //Somehow another option was taken, maybe something interrupted the selection or it was cancelled + return //Just end it in that case. - else //If we arent impregging - var/spillage = input(src, "Would your fluids spill outside?", "Choose overflowing option", "Yes") as anything in list("Yes", "No") - if(spillage == "Yes") - mob_climax_partner(picked_organ, partner, TRUE, FALSE, FALSE) - else - mob_climax_partner(picked_organ, partner, FALSE, FALSE, FALSE) - return - else - to_chat(src, "You cannot do this alone.") - return - else //They either lack organs that can masturbate, or they didn't pick one. - to_chat(src, "You cannot climax without choosing genitals.") - return - if("Climax over partner") - var/obj/item/organ/genital/picked_organ - picked_organ = pick_climax_genitals() - if(picked_organ) - var/mob/living/partner = pick_partner_overide() //Get your partner, clothed or not. - if(partner) - mob_climax_partner(picked_organ, partner, FALSE, FALSE, TRUE) - else - to_chat(src, "You cannot do this alone.") +/** + * 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(arousal=100) + if(stat == DEAD) //corpses can't cum + to_chat(src, "You can't do that while dead!") + return + if(!can_orgasm(arousal)) + return + var/mob/living/carbon/human/user_human = src + for(var/obj/item/organ/O in internal_organs) + if(!istype(O, /obj/item/organ/genital)) + continue + var/obj/item/organ/genital/current_genital = O + if(!current_genital.can_climax) + continue // no wombs or testicles + var/mob/living/carbon/partner + var/check_target + if(current_genital.is_exposed()) + if(src.pulling) + if(iscarbon(src.pulling)) + check_target = src.pulling + else if(src.pulledby) + if(iscarbon(src.pulledby)) + check_target = src.pulledby + if(check_target) + var/mob/living/carbon/C = check_target + if(C.exposed_genitals.len || C.is_groin_exposed() || C.is_chest_exposed()) + partner = C + if(partner) + user_human.mob_climax_in_partner(current_genital, partner, mb_time=0) + continue + user_human.mob_climax_outside(current_genital, mb_time=0) + return - if("Fill container") - //We'll need hands and no restraints. - if(restrained(TRUE)) //TRUE ignores grabs - to_chat(src, "You can't do that while restrained!") - return - var/free_hands = get_num_arms() - if(!free_hands) - to_chat(src, "You need at least one free arm.") - return - for(var/helditem in held_items)//how many hands are free - if(isobj(helditem)) - free_hands-- - if(free_hands <= 0) - to_chat(src, "You're holding too many things.") - return - //We got hands, let's pick an organ - var/obj/item/organ/genital/picked_organ - picked_organ = pick_climax_genitals() //Gotta be climaxable, not just masturbation, to fill with fluids. - if(picked_organ) - //Good, got an organ, time to pick a container - var/obj/item/reagent_containers/fluid_container = pick_climax_container() - if(fluid_container) - mob_fill_container(picked_organ, fluid_container) - return - else - to_chat(src, "You cannot do this without anything to fill.") - return - else //They either lack organs that can climax, or they didn't pick one. - to_chat(src, "You cannot fill anything without choosing genitals.") - return - else //Somehow another option was taken, maybe something interrupted the selection or it was cancelled - return //Just end it in that case. +/** + * 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) + return + if(!prob(impregchance)) + return + W.pregnant = TRUE + log_game("DEBUG: [src] has been impregnated by [by]") + to_chat(src, "You feel your hormones change, and a motherly instinct take over.") + if(HAS_TRAIT(src, TRAIT_HEAT)) + SEND_SIGNAL(src, COMSIG_ADD_MOOD_EVENT, "heat", /datum/mood_event/heat) + REMOVE_TRAIT(src, TRAIT_HEAT, ROUNDSTART_TRAIT) + var/obj/item/organ/genital/breasts/B = src.getorganslot("breasts") + if (B && B.fluid_mult < 0.5) + B.fluid_mult = 0.5 \ No newline at end of file diff --git a/modular_citadel/code/modules/arousal/organs/genitals.dm b/modular_citadel/code/modules/arousal/organs/genitals.dm index bdf4d7856..9edea910a 100644 --- a/modular_citadel/code/modules/arousal/organs/genitals.dm +++ b/modular_citadel/code/modules/arousal/organs/genitals.dm @@ -78,22 +78,21 @@ if("Always visible") through_clothes = TRUE hidden = FALSE - mode = "visible" + mode = GENITALS_VISIBLE if(!(src in owner.exposed_genitals)) owner.exposed_genitals += src if("Hidden by clothes") through_clothes = FALSE hidden = FALSE - mode = "clothes" + mode = GENITALS_CLOTHES if(src in owner.exposed_genitals) owner.exposed_genitals -= src if("Always hidden") through_clothes = FALSE hidden = TRUE - mode = "hidden" + mode = GENITALS_HIDDEN if(src in owner.exposed_genitals) owner.exposed_genitals -= src - if(ishuman(owner)) //recast to use update genitals proc var/mob/living/carbon/human/H = owner H.update_genitals() diff --git a/modular_citadel/code/modules/integrated_electronics/subtypes/manipulation.dm b/modular_citadel/code/modules/integrated_electronics/subtypes/manipulation.dm index 547c1a576..e3eab59c5 100644 --- a/modular_citadel/code/modules/integrated_electronics/subtypes/manipulation.dm +++ b/modular_citadel/code/modules/integrated_electronics/subtypes/manipulation.dm @@ -25,12 +25,12 @@ if(ismob(M) && M.canbearoused && arousal_gain != 0) var/orgasm = FALSE if(arousal_gain > 0) - if(M.getArousalLoss() >= 100 && ishuman(M) && M.has_dna()) + if(M.can_orgasm()) var/mob/living/carbon/human/H = M var/orgasm_message = pick("A sharp pulse of electricity pushes you to orgasm!", "You feel a jolt of electricity force you into orgasm!") H.visible_message("\The [assembly] electrodes shock [H]!", "[orgasm_message]") playsound(src, "sound/effects/light_flicker.ogg", 30, 1) - H.mob_climax(forced_climax=TRUE) + H.mob_climax_instant() orgasm = TRUE else M.adjustArousalLoss(arousal_gain) diff --git a/modular_citadel/code/modules/reagents/reagents/cit_reagents.dm b/modular_citadel/code/modules/reagents/reagents/cit_reagents.dm index 324b44db3..30c528cc0 100644 --- a/modular_citadel/code/modules/reagents/reagents/cit_reagents.dm +++ b/modular_citadel/code/modules/reagents/reagents/cit_reagents.dm @@ -197,7 +197,7 @@ /datum/reagent/drug/aphrodisiacplus/overdose_process(mob/living/M) if(M && M.canbearoused && !(M.client?.prefs.cit_toggles & NO_APHRO) && prob(33)) - if(prob(5) && M.getArousalLoss() >= 100 && ishuman(M) && M.has_dna()) + if(prob(5) && M.can_orgasm()) if(prob(5)) //Less spam to_chat(M, "Your libido is going haywire!") if(M.min_arousal < 50) diff --git a/modular_citadel/icons/obj/genitals/hud.dmi b/modular_citadel/icons/obj/genitals/hud.dmi index bf2adcb1f..e090dbd84 100644 Binary files a/modular_citadel/icons/obj/genitals/hud.dmi and b/modular_citadel/icons/obj/genitals/hud.dmi differ 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"