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 ..()