From 5731ea7cad052ca3637b9465081dd4937b51993b Mon Sep 17 00:00:00 2001 From: ktccd Date: Sun, 20 Aug 2017 04:01:02 +0200 Subject: [PATCH] Climaxing/masturbation changes, exposing through clothing, breastmilk and masturbation (#2357) * Genital refactoring and masturbation rework Refactors a lot of genitalia code. Reworks masturbation code. Adds the ability to expose onself through clothing (Still bugfixing it). * Fixes it so everything works Toggling exposing your genitals now works properly. You can now expose genitals through clothing. Genitalia is rendered a bit more modularly and less hardcoded. Genitalia no longer has linked_penis and such, but instead linked_organ. More variables made general for genitals. Breasts can now be used to masturbate and climax, also letting you fill beakers and others with milk. Both being pulled and pulling someone can let you count them as a partner for climaxing verbs. Mob_climax split up into more sub-procs. Hexacrocin made less verbose, and not as spam-happy with forced orgasms. Hexacamphor also made less verbose. * Removes outcommented code Forgot to remove the old masturbation code that I commented out. * Changes one tab further on line 248 As pointed out to me ^^. * Climaxing with partner tabulation Also copied the command for the spillage version. * capitalisation and timers Removed timers from forced climaxes and made it a default value parameter instead, so it's adjustable. Changed capitalisation of genitals and the mentions of them now use the .name to avoid including a "The" in the mention. * I accidentally runtimestation Accidentally committed changing the map to runtimestation when I debugged my code. Changed it back to basemap now. --- code/citadel/cit_arousal.dm | 538 +++++++++++++++++++------------ code/citadel/cit_reagents.dm | 9 +- code/citadel/organs/breasts.dm | 14 +- code/citadel/organs/eggsack.dm | 2 +- code/citadel/organs/genitals.dm | 99 ++++-- code/citadel/organs/penis.dm | 55 ++-- code/citadel/organs/testicles.dm | 19 +- code/citadel/organs/vagina.dm | 29 +- code/citadel/organs/womb.dm | 25 +- 9 files changed, 495 insertions(+), 295 deletions(-) diff --git a/code/citadel/cit_arousal.dm b/code/citadel/cit_arousal.dm index cf47dab1da..6005758144 100644 --- a/code/citadel/cit_arousal.dm +++ b/code/citadel/cit_arousal.dm @@ -181,224 +181,364 @@ 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, mb_time = 30) //Masturbation, keep it gender-neutral + 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) + 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) + fluid_source.reaction(src.loc, TOUCH, 1, 0) + fluid_source.clear_reagents() + 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(G.can_climax) + setArousalLoss(min_arousal) + + +/mob/living/carbon/human/proc/mob_climax_outside(obj/item/organ/genital/G, mb_time = 30) //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(total_fluids > 5) + fluid_source.reaction(src.loc, TOUCH, 1, 0) + fluid_source.clear_reagents() + src.visible_message("[src] orgasms[istype(src.loc, /turf/open/floor) ? ", spilling onto [src.loc]" : ""], using [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].") + 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, 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) //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 someone!") + if(spillage) + 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 > 5) + fluid_source.reaction(L.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].") + if(G.can_climax) + setArousalLoss(min_arousal) + else //knots and other non-spilling orgasms + if(do_after(src, mb_time, target = src) && in_range(src, L)) + fluid_source.trans_to(L, total_fluids) + total_fluids = 0 + 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.") + if(G.can_climax) + setArousalLoss(min_arousal) + + +/mob/living/carbon/human/proc/mob_fill_container(obj/item/organ/genital/G, obj/item/weapon/reagent_containers/container, mb_time = 30) //For beaker-filling, beware the bartender + 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(!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.") + 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] to fill [container].", \ + "You have relieved some pressure.") + if(G.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() + 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 + 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/pick_climax_genitals() + 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 + genitals_list += G + if(genitals_list.len) + ret_organ = input(src, "with what?", "Climax", null) as null|obj in genitals_list + return ret_organ + return null //error stuff + + +/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 + 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(!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_climax_container() + var/obj/item/weapon/reagent_containers/SC = null + var/list/containers_list = list() + + for(var/obj/item/weapon/reagent_containers/container in held_items) + if(container.is_open_container() || istype(container, /obj/item/weapon/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 + if(SC) + if(in_range(src, SC)) + return SC + return null //If nothing correct, give 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(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 [round((mb_cd_timer - world.time)/(20))] seconds before you can do that again!") return mb_cd_timer = (world.time + mb_cd_length) - var/list/genitals_list = list() - var/obj/item/organ/genital/SG = null//originally selected_genital - var/list/containers_list = list() - var/obj/item/weapon/reagent_containers/SC = null - var/datum/reagents/fluid_source = null - var/into_container = 0 - var/free_hands = get_num_arms() //arms was only used to know if we had ANY at all - var/total_cum = 0 - var/finished = 0 - var/mb_time = 30 + + 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/genital/G in internal_organs) - if(G.can_masturbate_with) //All capable genitals will orgasm with this - var/unable_to_come = FALSE - switch(G.type) - if(/obj/item/organ/genital/penis) - var/obj/item/organ/genital/penis/P = G - if(!P.linked_balls) - unable_to_come = TRUE - else - fluid_source = P.linked_balls.reagents + 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 + else //A cat is fine too + partner = check_target + 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(/obj/item/organ/genital/vagina) - var/obj/item/organ/genital/vagina/V = G - if(!V.linked_womb) - unable_to_come = TRUE - else - fluid_source = V.linked_womb.reagents - else //Weird, undefined genitalia behaviour - 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.", \ - "Your [G.name] cannot cum, giving no relief.") - else - if(fluid_source) - total_cum = fluid_source.total_volume - 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(total_cum > 5) - fluid_source.reaction(src.loc, TOUCH, 1, 0) - fluid_source.clear_reagents() - fluid_source = null //cleanup so this can be used for the next genitalia - - src.visible_message("[src] orgasms, cumming[istype(src.loc, /turf/open/floor) ? " onto [src.loc]" : ""]!", \ - "You're forced to cum[istype(src.loc, /turf/open/floor) ? " onto [src.loc]" : ""] with your [G].", \ - "Your [G] have been forced to climax.") - finished = 1 - if(finished) - setArousalLoss(min_arousal) - return //Do not proceed to masturbating if all genitals have been forced to orgasm. - if(stat==1) //Sleeping people can be forced chemically or with electrical stimulants, for example. + if(stat==1) //No sleep-masturbation, you're unconscious. to_chat(src, "You must be conscious to do that!") return - if(restrained()) - to_chat(src, "You can't do that while restrained!") - return - if(getArousalLoss() < 33)//flat number instead of percentage + if(getArousalLoss() < 33) //flat number instead of percentage to_chat(src, "You aren't aroused enough for that!") return - if(!is_groin_exposed()) - to_chat(src, "You need to undress, first!") - return - 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 need at least one free hand.") - return - for(var/obj/item/organ/genital/G in internal_organs) - if(G.can_masturbate_with)//filter out what you can't masturbate with - genitals_list += G - if(genitals_list.len) - SG = input(src, "with what?", "Masturbate") as null|obj in genitals_list - if(SG) - for(var/obj/item/weapon/reagent_containers/container in held_items) - if(container.is_open_container() || istype(container, /obj/item/weapon/reagent_containers/food/snacks/pie)) - containers_list += container - if(containers_list.len) - SC = input(src, "Into or onto what?(Cancel for nowhere)", "Masturbate") as null|obj in containers_list - if(SC) - if(in_range(src, SC)) - into_container = 1 - SG.update() - switch(SG.type) - //Penis - if(/obj/item/organ/genital/penis) - var/obj/item/organ/genital/penis/P = SG - if(!P.linked_balls) - to_chat(src, "You need a pair of testicles to do this.") - return - fluid_source = P.linked_balls.reagents - total_cum = fluid_source.total_volume - if(into_container)//into a glass or beaker or whatever - src.visible_message("[src] starts [pick("jerking off","stroking")] their [SG.name] over [SC].", \ - "You start jerking off over [SC.name].", \ - "You start masturbating.") - if(do_after(src, mb_time, target = src) && in_range(src, SC)) - fluid_source.trans_to(SC, total_cum) - src.visible_message("[src] orgasms, [pick("cumming into", "emptying themself into")] [SC]!", \ - "You cum into [SC].", \ - "You have relieved yourself.") - finished = 1 - else //Not in a container - if(src.pulling) - if(iscarbon(src.pulling)) - var/mob/living/carbon/C = src.pulling - if(!C.is_groin_exposed()) - to_chat(src, "You must undress someone to climax inside them.") - return - if(isliving(src.pulling)) //Gotta be alive to fuck it, don't wanna have to code fucking objects that ain't containers... - var/mob/living/partner = src.pulling - src.visible_message("[src] is about to climax inside [partner]!", \ - "You're about to climax inside [partner]!", \ - "You're preparing to climax inside someone!") - switch(grab_state) - if(GRAB_PASSIVE) - if(do_after(src, mb_time, target = src) && in_range(src, partner)) - var/spillage = 0.5 //Leaks a bit on passive grab - var/did_spill = FALSE - fluid_source.trans_to(partner, total_cum*(1-spillage)) - total_cum = total_cum*spillage - if(total_cum > 5) - fluid_source.reaction(partner.loc, TOUCH, 1, 0) - did_spill = TRUE - fluid_source.clear_reagents() + //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", "Fill container") - src.visible_message("[src] ejaculates inside [partner][did_spill ? ", overflowing and spilling":""]!", \ - "You ejaculate inside [partner][did_spill ? ", spilling out of them":""].", \ - "You have climaxed inside someone[did_spill ? ", spilling out of them":""].") - finished = 1 - else //Aggressive or higher - if(do_after(src, mb_time, target = src) && in_range(src, partner)) - var/spillage = 0.0 //Leakproofing seals - fluid_source.trans_to(partner, total_cum*(1-spillage)) - total_cum = total_cum*spillage - if(total_cum > 5) - fluid_source.reaction(partner.loc, TOUCH, 1, 0) - fluid_source.clear_reagents() + switch(choice) + 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 - src.visible_message("[src] ejaculates inside [partner], spilling nothing!", \ - "You ejaculate inside [partner], spilling nothing.", \ - "You have climaxed inside someone, spilling nothing.") - finished = 1 - //Don't care, not coding you fucking a unanchored girder - else //No pulling, or pulling non-living things - src.visible_message("[src] starts [pick("jerking off","stroking")] their [SG].", \ - "You start masturbating.", \ - "You start masturbating.") - if(do_after(src, mb_time, target = src)) - if(total_cum > 5) - fluid_source.reaction(src.loc, TOUCH, 1, 0) - fluid_source.clear_reagents() + 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 - 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.") - finished = 1 + 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/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) + else + mob_climax_partner(picked_organ, partner, 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(/obj/item/organ/genital/vagina) - var/obj/item/organ/genital/vagina/V = SG - if(!V.linked_womb) - to_chat(src, "You need a womb to do this.") - return - fluid_source = V.linked_womb.reagents - total_cum = fluid_source.total_volume - if(into_container)//into a glass or beaker or whatever - src.visible_message("[src] starts fingering their [SG.name] over [SC].", \ - "You start fingering over [SC.name].", \ - "You start masturbating.") - if(do_after(src, mb_time, target = src) && in_range(src, SC)) - fluid_source.trans_to(SC, total_cum) - src.visible_message("[src] orgasms, [pick("cumming into", "emptying themself into")] [SC]!", \ - "You cum into [SC].", \ - "You have relieved yourself.") - finished = 1 - - else//not into a container - src.visible_message("[src] starts fingering their vagina.", \ - "You start fingering your vagina.", \ - "You start masturbating.") - if(do_after(src, mb_time, target = src)) - if(total_cum > 5) - fluid_source.reaction(src.loc, TOUCH, 1, 0) - fluid_source.clear_reagents() - 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.") - finished = 1 - - else//backup message, just in case - src.visible_message("[src] starts masturbating!", \ - "You start masturbating.") - if(do_after(src, mb_time, target = src)) - src.visible_message("[src] [pick("relieves themself!", "shudders and moans in orgasm!")]", \ - "You have relieved yourself.") - finished = 1 - if(finished) - setArousalLoss(min_arousal) - - else - to_chat(src, "You have no genitals!") - 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/weapon/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. diff --git a/code/citadel/cit_reagents.dm b/code/citadel/cit_reagents.dm index 428729d30f..4a0e91479b 100644 --- a/code/citadel/cit_reagents.dm +++ b/code/citadel/cit_reagents.dm @@ -153,14 +153,13 @@ if(prob(33)) if(M.getArousalLoss() >= 100 && ishuman(M) && M.has_dna()) var/mob/living/carbon/human/H = M - to_chat(H, "Your libido is going haywire!") - H.mob_climax(forced_climax=TRUE) + if(prob(50)) //Less spam + to_chat(H, "Your libido is going haywire!") + H.mob_climax(forced_climax=TRUE) if(M.min_arousal < 50) M.min_arousal += 1 - to_chat(M, "You're having a hard time thinkin about things other than sex!") if(M.min_arousal < M.max_arousal) M.min_arousal += 1 - to_chat(M, "You feel your libido permanently increasing.") M.adjustArousalLoss(2) ..() @@ -197,10 +196,8 @@ if(prob(33)) if(M.min_arousal > 0) M.min_arousal -= 1 - to_chat(M, "You feel your libido returning to more normal levels.") if(M.min_arousal > 50) M.min_arousal -= 1 - to_chat(M, "You feel like your overactive libido is calming down.") M.adjustArousalLoss(-2) ..() diff --git a/code/citadel/organs/breasts.dm b/code/citadel/organs/breasts.dm index ce45b88cfb..dda5e248ee 100644 --- a/code/citadel/organs/breasts.dm +++ b/code/citadel/organs/breasts.dm @@ -1,5 +1,5 @@ /obj/item/organ/genital/breasts - name = "Breasts" + name = "breasts" desc = "Female milk producing organs." icon_state = "breasts" icon = 'code/citadel/icons/breasts.dmi' @@ -11,6 +11,10 @@ var/amount = 2 producing = TRUE shape = "pair" + can_masturbate_with = TRUE + masturbation_verb = "massage" + can_climax = TRUE + fluid_transfer_factor =0.5 /obj/item/organ/genital/breasts/Initialize() . = ..() @@ -54,4 +58,10 @@ var/mob/living/carbon/human/H = owner // only human mobs have skin_tone, which we need. color = "#[skintone2hex(H.skin_tone)]" else - color = "#[owner.dna.features["breasts_color"]]" \ No newline at end of file + color = "#[owner.dna.features["breasts_color"]]" + +/obj/item/organ/genital/breasts/is_exposed() + . = ..() + if(.) + return TRUE + return owner.is_chest_exposed() \ No newline at end of file diff --git a/code/citadel/organs/eggsack.dm b/code/citadel/organs/eggsack.dm index 718a353b87..1486310d61 100644 --- a/code/citadel/organs/eggsack.dm +++ b/code/citadel/organs/eggsack.dm @@ -7,7 +7,7 @@ slot = "testicles" color = null //don't use the /genital color since it already is colored w_class = 3 - var/internal = TRUE + internal = TRUE var/egg_girth = EGG_GIRTH_DEF var/cum_mult = CUM_RATE_MULT var/cum_rate = CUM_RATE diff --git a/code/citadel/organs/genitals.dm b/code/citadel/organs/genitals.dm index 6bd86e7146..1f46778aaf 100644 --- a/code/citadel/organs/genitals.dm +++ b/code/citadel/organs/genitals.dm @@ -3,7 +3,10 @@ var/shape = "human" var/sensitivity = 1 var/list/genital_flags = list() - var/can_masturbate_with = 0 + var/can_masturbate_with = FALSE + var/masturbation_verb = "masturbate" + var/can_climax = FALSE + var/fluid_transfer_factor = 0.0 //How much would a partner get in them if they climax using this? var/size = 2 //can vary between num or text, just used in icon_state strings var/fluid_id = null var/fluid_max_volume = 50 @@ -13,6 +16,9 @@ var/producing = FALSE var/aroused_state = FALSE //Boolean used in icon_state strings var/aroused_amount = 50 //This is a num from 0 to 100 for arousal percentage for when to use arousal state icons. + var/obj/item/organ/genital/linked_organ + var/through_clothes = FALSE + var/internal = FALSE /obj/item/organ/genital/Initialize() . = ..() @@ -34,6 +40,52 @@ update_appearance() update_link() +//exposure and through-clothing code +/mob/living/carbon + var/list/exposed_genitals = list() //Keeping track of them so we don't have to iterate through every genitalia and see if exposed + +/obj/item/organ/genital/proc/is_exposed() + if(!owner) + return FALSE + if(internal) + return FALSE + if(through_clothes) + return TRUE + +/obj/item/organ/genital/proc/toggle_through_clothes() + if(through_clothes) + through_clothes = FALSE + owner.exposed_genitals -= src + else + through_clothes = TRUE + owner.exposed_genitals += src + if(ishuman(owner)) //recast to use update genitals proc + var/mob/living/carbon/human/H = owner + H.update_genitals() + +/mob/living/carbon/verb/toggle_genitals() + set category = "IC" + set name = "Expose/Hide genitals" + set desc = "Allows you to toggle which genitals should show through clothes or not." + + var/list/genital_list = list() + 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.internal) + genital_list += G + if(!genital_list.len) //There is nothing to expose + return + //Full list of exposable genitals created + var/obj/item/organ/genital/picked_organ + picked_organ = input(src, "Expose/Hide genitals", "Choose which genitalia to expose/hide", null) in genital_list + if(picked_organ) + picked_organ.toggle_through_clothes() + return + + + + /obj/item/organ/genital/proc/update_size() /obj/item/organ/genital/proc/update_appearance() @@ -41,6 +93,9 @@ /obj/item/organ/genital/proc/update_link() /obj/item/organ/genital/proc/remove_ref() + if(linked_organ) + linked_organ.linked_organ = null + linked_organ = null /obj/item/organ/genital/Insert(mob/living/carbon/M, special = 0) ..() @@ -58,21 +113,21 @@ var/obj/item/organ/genital/GtoClean for(GtoClean in internal_organs) qdel(GtoClean) - - if(dna.features["has_cock"]) - give_penis() - if(dna.features["has_balls"]) - give_balls() - else if(dna.features["has_ovi"]) - give_ovipositor() - if(dna.features["has_eggsack"]) - give_eggsack() + //Order should be very important. FIRST vagina, THEN testicles, THEN penis, as this affects the order they are rendered in. if(dna.features["has_breasts"]) give_breasts() if(dna.features["has_vag"]) give_vagina() - if(dna.features["has_womb"]) - give_womb() + if(dna.features["has_womb"]) + give_womb() + if(dna.features["has_balls"]) + give_balls() + if(dna.features["has_cock"]) + give_penis() + if(dna.features["has_ovi"]) + give_ovipositor() + if(dna.features["has_eggsack"]) + give_eggsack() /mob/living/carbon/human/proc/give_penis() if(!dna) @@ -208,17 +263,15 @@ if(H.disabilities & HUSK) return //start scanning for genitals - var/list/worn_stuff = H.get_equipped_items()//cache this list so it's not built again - if(H.is_groin_exposed(worn_stuff)) - //ORDER is important here. Vaginas first, theoretical testes after, and penis LAST. - //The latter is always drawn on top of the former. - if(H.has_vagina()) - genitals_to_add += H.getorganslot("vagina") - if(H.has_penis()) - genitals_to_add += H.getorganslot("penis") - if(H.is_chest_exposed(worn_stuff)) - if(H.has_breasts()) - genitals_to_add += H.getorganslot("breasts") + //var/list/worn_stuff = H.get_equipped_items()//cache this list so it's not built again + + for(var/obj/item/organ/O in H.internal_organs) + if(istype(O, /obj/item/organ/genital)) + var/obj/item/organ/genital/G = O + if(G.is_exposed()) //Checks appropriate clothing slot and if it's through_clothes + genitals_to_add += H.getorganslot(G.slot) + //Now we added all genitals that aren't internal and should be rendered + var/image/I //start applying overlays for(var/layer in relevant_layers) diff --git a/code/citadel/organs/penis.dm b/code/citadel/organs/penis.dm index 9e2ce00666..7348e8c969 100644 --- a/code/citadel/organs/penis.dm +++ b/code/citadel/organs/penis.dm @@ -1,21 +1,23 @@ /obj/item/organ/genital/penis - name = "Penis" - desc = "A male reproductive organ." - icon_state = "penis" - icon = 'code/citadel/icons/penis.dmi' - zone = "groin" - slot = "penis" - w_class = 3 - can_masturbate_with = 1 - size = 2 //arbitrary value derived from length and girth for sprites. - var/length = 6 //inches + name = "penis" + desc = "A male reproductive organ." + icon_state = "penis" + icon = 'code/citadel/icons/penis.dmi' + zone = "groin" + slot = "penis" + w_class = 3 + can_masturbate_with = TRUE + masturbation_verb = "stroke" + can_climax = TRUE + fluid_transfer_factor = 0.5 + size = 2 //arbitrary value derived from length and girth for sprites. + var/length = 6 //inches var/cached_length //used to detect a change in length - var/girth = 0 - var/girth_ratio = COCK_GIRTH_RATIO_DEF //0.73; check citadel_defines.dm - var/knot_girth_ratio = KNOT_GIRTH_RATIO_DEF - var/list/dickflags = list() - var/list/knotted_types = list("knotted", "barbknot") - var/obj/item/organ/genital/testicles/linked_balls + var/girth = 0 + var/girth_ratio = COCK_GIRTH_RATIO_DEF //0.73; check citadel_defines.dm + var/knot_girth_ratio = KNOT_GIRTH_RATIO_DEF + var/list/dickflags = list() + var/list/knotted_types = list("knotted", "barbknot") /obj/item/organ/genital/penis/update_size() if(length == cached_length) @@ -55,15 +57,16 @@ /obj/item/organ/genital/penis/update_link() if(owner) - linked_balls = (owner.getorganslot("testicles")) - if(linked_balls) - linked_balls.linked_penis = src + linked_organ = (owner.getorganslot("testicles")) + if(linked_organ) + linked_organ.linked_organ = src else - if(linked_balls) - linked_balls.linked_penis = null - linked_balls = null + if(linked_organ) + linked_organ.linked_organ = null + linked_organ = null -/obj/item/organ/genital/penis/remove_ref() - if(linked_balls) - linked_balls.linked_penis = null - linked_balls = null +/obj/item/organ/genital/penis/is_exposed() + . = ..() + if(.) + return TRUE + return owner.is_groin_exposed() \ No newline at end of file diff --git a/code/citadel/organs/testicles.dm b/code/citadel/organs/testicles.dm index a759aeab52..bb3ade6048 100644 --- a/code/citadel/organs/testicles.dm +++ b/code/citadel/organs/testicles.dm @@ -1,18 +1,17 @@ /obj/item/organ/genital/testicles - name = "Testicles" + name = "testicles" desc = "A male reproductive organ." icon_state = "testicles" icon = 'code/citadel/icons/penis.dmi' zone = "groin" slot = "testicles" w_class = 3 - var/internal = FALSE + internal = TRUE size = BALLS_SIZE_DEF var/sack_size = BALLS_SACK_SIZE_DEF fluid_id = "semen" producing = TRUE var/sent_full_message = 1 //defaults to 1 since they're full to start - var/obj/item/organ/genital/penis/linked_penis /obj/item/organ/genital/testicles/Initialize() . = ..() @@ -33,20 +32,20 @@ return FALSE sent_full_message = 0 update_link() - if(!linked_penis) + if(!linked_organ) return FALSE reagents.isolate_reagent(fluid_id)//remove old reagents if it changed and just clean up generally reagents.add_reagent(fluid_id, (fluid_mult * fluid_rate))//generate the cum /obj/item/organ/genital/testicles/update_link() if(owner && !QDELETED(src)) - linked_penis = (owner.getorganslot("penis")) - if(linked_penis) - linked_penis.linked_balls = src + linked_organ = (owner.getorganslot("penis")) + if(linked_organ) + linked_organ.linked_organ = src else - if(linked_penis) - linked_penis.linked_balls = null - linked_penis = null + if(linked_organ) + linked_organ.linked_organ = null + linked_organ = null /obj/item/organ/genital/testicles/proc/send_full_message(msg = "Your balls finally feel full, again.") if(owner && istext(msg)) diff --git a/code/citadel/organs/vagina.dm b/code/citadel/organs/vagina.dm index fa1f00bda2..f170ced9b6 100644 --- a/code/citadel/organs/vagina.dm +++ b/code/citadel/organs/vagina.dm @@ -1,12 +1,15 @@ /obj/item/organ/genital/vagina - name = "Vagina" + name = "vagina" desc = "A female reproductive organ." icon = 'code/citadel/icons/vagina.dmi' icon_state = "vagina" zone = "groin" slot = "vagina" size = 1 //There is only 1 size right now - can_masturbate_with = 1 + can_masturbate_with = TRUE + masturbation_verb = "finger" + can_climax = TRUE + fluid_transfer_factor = 0.1 //Yes, some amount is exposed to you, go get your AIDS w_class = 3 var/cap_length = 8//D E P T H (cap = capacity) var/cap_girth = 12 @@ -15,7 +18,6 @@ var/clit_diam = 0.25 var/clit_len = 0.25 var/list/vag_types = list("tentacle", "dentata", "hairy") - var/obj/item/organ/genital/womb/linked_womb /obj/item/organ/genital/vagina/update_appearance() @@ -56,15 +58,16 @@ /obj/item/organ/genital/vagina/update_link() if(owner) - linked_womb = (owner.getorganslot("womb")) - if(linked_womb) - linked_womb.linked_vag = src + linked_organ = (owner.getorganslot("womb")) + if(linked_organ) + linked_organ.linked_organ = src else - if(linked_womb) - linked_womb.linked_vag = null - linked_womb = null + if(linked_organ) + linked_organ.linked_organ = null + linked_organ = null -/obj/item/organ/genital/vagina/remove_ref() - if(linked_womb) - linked_womb.linked_vag = null - linked_womb = null +/obj/item/organ/genital/vagina/is_exposed() + . = ..() + if(.) + return TRUE + return owner.is_groin_exposed() \ No newline at end of file diff --git a/code/citadel/organs/womb.dm b/code/citadel/organs/womb.dm index 3ebe5c2f98..433f005623 100644 --- a/code/citadel/organs/womb.dm +++ b/code/citadel/organs/womb.dm @@ -1,15 +1,15 @@ /obj/item/organ/genital/womb - name = "Womb" + name = "womb" desc = "A female reproductive organ." icon = 'code/citadel/icons/vagina.dmi' icon_state = "womb" zone = "groin" slot = "womb" w_class = 3 - var/internal = FALSE + internal = TRUE fluid_id = "femcum" producing = TRUE - var/obj/item/organ/genital/vagina/linked_vag + /obj/item/organ/genital/womb/Initialize() . = ..() @@ -24,25 +24,20 @@ /obj/item/organ/genital/womb/proc/generate_femcum() reagents.maximum_volume = fluid_max_volume update_link() - if(!linked_vag) + if(!linked_organ) return FALSE reagents.isolate_reagent(fluid_id)//remove old reagents if it changed and just clean up generally reagents.add_reagent(fluid_id, (fluid_mult * fluid_rate))//generate the cum /obj/item/organ/genital/womb/update_link() if(owner) - linked_vag = (owner.getorganslot("vagina")) - if(linked_vag) - linked_vag.linked_womb = src + linked_organ = (owner.getorganslot("vagina")) + if(linked_organ) + linked_organ.linked_organ = src else - if(linked_vag) - linked_vag.linked_womb = null - linked_vag = null - -/obj/item/organ/genital/womb/remove_ref() - if(linked_vag) - linked_vag.linked_womb = null - linked_vag = null + if(linked_organ) + linked_organ.linked_organ = null + linked_organ = null /obj/item/organ/genital/womb/Destroy() return ..()