diff --git a/code/__DEFINES/citadel_defines.dm b/code/__DEFINES/citadel_defines.dm index 035e660176..04e9c52527 100644 --- a/code/__DEFINES/citadel_defines.dm +++ b/code/__DEFINES/citadel_defines.dm @@ -26,14 +26,14 @@ #define GENITAL_LAYER_INDEX_LENGTH 4 //keep it updated with each new index added, thanks. //genital flags -#define GENITAL_BLACKLISTED (1 << 0) //for genitals that shouldn't be added to GLOB.genitals_list. -#define GENITAL_INTERNAL (1<<1) -#define GENITAL_HIDDEN (1<<2) -#define GENITAL_THROUGH_CLOTHES (1<<3) -#define GENITAL_FUID_PRODUCTION (1<<4) -#define CAN_MASTURBATE_WITH (1<<5) -#define MASTURBATE_LINKED_ORGAN (1<<6) //used to pass our mission to the linked organ -#define CAN_CLIMAX_WITH (1<<7) +#define GENITAL_BLACKLISTED (1<<0) //for genitals that shouldn't be added to GLOB.genitals_list. +#define GENITAL_INTERNAL (1<<1) +#define GENITAL_HIDDEN (1<<2) +#define GENITAL_THROUGH_CLOTHES (1<<3) +#define GENITAL_FUID_PRODUCTION (1<<4) +#define CAN_MASTURBATE_WITH (1<<5) +#define MASTURBATE_LINKED_ORGAN (1<<6) //used to pass our mission to the linked organ +#define CAN_CLIMAX_WITH (1<<7) #define COCK_SIZE_MIN 1 #define COCK_SIZE_MAX 20 diff --git a/code/__HELPERS/_cit_helpers.dm b/code/__HELPERS/_cit_helpers.dm index d75dd31b46..1c7b4aeb99 100644 --- a/code/__HELPERS/_cit_helpers.dm +++ b/code/__HELPERS/_cit_helpers.dm @@ -158,18 +158,20 @@ GLOBAL_VAR_INIT(miscreants_allowed, FALSE) return TRUE return FALSE -/mob/living/carbon/proc/is_groin_exposed(var/list/L) +/mob/living/carbon/proc/is_groin_exposed(list/L) if(!L) L = get_equipped_items() - for(var/obj/item/I in L) + for(var/A in L) + var/obj/item/I = A if(I.body_parts_covered & GROIN) return FALSE return TRUE -/mob/living/carbon/proc/is_chest_exposed(var/list/L) +/mob/living/carbon/proc/is_chest_exposed(list/L) if(!L) L = get_equipped_items() - for(var/obj/item/I in L) + for(var/A in L) + var/obj/item/I = A if(I.body_parts_covered & CHEST) return FALSE return TRUE diff --git a/modular_citadel/code/modules/arousal/arousal.dm b/modular_citadel/code/modules/arousal/arousal.dm index f5b27b2c15..83a5622f49 100644 --- a/modular_citadel/code/modules/arousal/arousal.dm +++ b/modular_citadel/code/modules/arousal/arousal.dm @@ -139,8 +139,6 @@ if(!canbearoused) hud_used.arousal.icon_state = "" return FALSE - if(stat == DEAD) - hud_used.arousal.icon_state = "arousal0" else var/value = FLOOR(getPercentAroused(), 10) hud_used.arousal.icon_state = "arousal[value]" @@ -194,6 +192,7 @@ if(!G) return SEND_SIGNAL(src, COMSIG_ADD_MOOD_EVENT, "orgasm", /datum/mood_event/orgasm) + setArousalLoss(min_arousal) if(!target || !R) return var/turfing = isturf(target) @@ -217,8 +216,8 @@ "You start to [G.masturbation_verb] your [G.name].") if(!do_after(src, mb_time, target = src) || !G.climaxable(src, TRUE)) return - visible_message("[src] orgasms, [PP.orgasm_verb][isturf(loc) ? " onto [loc]" : ""]!", \ - "You orgasm, [PP.orgasm_verb][isturf(loc) ? " onto [loc]" : ""].") + visible_message("[src] orgasms, [PP.orgasm_verb][isturf(loc) ? " onto [loc]" : ""] with [p_their()] [PP.name]!", \ + "You orgasm, [PP.orgasm_verb][isturf(loc) ? " onto [loc]" : ""] with your [PP.name].") do_climax(fluid_source, loc, G) /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 @@ -268,33 +267,35 @@ "You used your [G.name] to fill [container].") do_climax(fluid_source, container, G, FALSE) -/mob/living/carbon/human/proc/pick_masturbate_genitals() - var/list/genitals_list = list() +/mob/living/carbon/human/proc/pick_masturbate_genitals(silent = FALSE) + var/list/genitals_list var/list/worn_stuff = get_equipped_items() for(var/obj/item/organ/genital/G in internal_organs) if(CHECK_BITFIELD(G.genital_flags, CAN_MASTURBATE_WITH) && G.is_exposed(worn_stuff)) //filter out what you can't masturbate with if(CHECK_BITFIELD(G.genital_flags, MASTURBATE_LINKED_ORGAN) && !G.linked_organ) continue - genitals_list += G - if(genitals_list.len) - var/obj/item/organ/genital/ret_organ = input(src, "with what?", "Masturbate", null) as null|obj in genitals_list + LAZYADD(genitals_list, G) + if(LAZYLEN(genitals_list)) + var/obj/item/organ/genital/ret_organ = input(src, "with what?", "Masturbate", null) as null|obj in genitals_list return ret_organ + else if(!silent) + to_chat(src, "You cannot masturbate without available genitals.") - -/mob/living/carbon/human/proc/pick_climax_genitals() - var/list/genitals_list = list() +/mob/living/carbon/human/proc/pick_climax_genitals(silent = FALSE) + var/list/genitals_list var/list/worn_stuff = get_equipped_items() for(var/obj/item/organ/genital/G in internal_organs) if(CHECK_BITFIELD(G.genital_flags, CAN_CLIMAX_WITH) && G.is_exposed(worn_stuff)) //filter out what you can't masturbate with - genitals_list += G - if(genitals_list.len) - var/obj/item/organ/genital/ret_organ = input(src, "with what?", "Climax", null) as null|obj in genitals_list + LAZYADD(genitals_list, G) + if(LAZYLEN(genitals_list)) + var/obj/item/organ/genital/ret_organ = input(src, "with what?", "Climax", null) as null|obj in genitals_list return ret_organ + else if(!silent) + to_chat(src, "You cannot climax without available genitals.") - -/mob/living/carbon/human/proc/pick_partner() +/mob/living/carbon/human/proc/pick_partner(silent = FALSE) var/list/partners = list() if(pulling) partners += pulling @@ -308,30 +309,42 @@ partners -= C //NOW the list should only contain correct partners if(!partners.len) + if(!silent) + to_chat(src, "You cannot do this alone.") return //No one left. var/mob/living/target = input(src, "With whom?", "Sexual partner", null) as null|anything in partners //pick one, default to null if(target && in_range(src, target)) return target -/mob/living/carbon/human/proc/pick_climax_container() +/mob/living/carbon/human/proc/pick_climax_container(silent = FALSE) 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 + for(var/obj/item/reagent_containers/C in held_items) + if(C.is_open_container() || istype(C, /obj/item/reagent_containers/food/snacks)) + containers_list += C + for(var/obj/item/reagent_containers/C in range(1, src)) + if((C.is_open_container() || istype(C, /obj/item/reagent_containers/food/snacks)) && CanReach(C)) + containers_list += C if(containers_list.len) var/obj/item/reagent_containers/SC = input(src, "Into or onto what?(Cancel for nowhere)", null) as null|obj in containers_list - if(SC && in_range(src, SC)) + if(SC && CanReach(SC)) return SC - return null //If nothing correct, give null. + else if(!silent) + to_chat(src, "You cannot do this without an appropriate container.") -/mob/living/carbon/human/proc/available_rosie_palms(silent = FALSE) +/mob/living/carbon/human/proc/available_rosie_palms(silent = FALSE, list/whitelist_typepaths = list(/obj/item/dildo)) if(restrained(TRUE)) //TRUE ignores grabs if(!silent) to_chat(src, "You can't do that while restrained!") return FALSE if(!get_num_arms() || !get_empty_held_indexes()) + if(whitelist_typepaths) + if(!islist(whitelist_typepaths)) + whitelist_typepaths = list(whitelist_typepaths) + for(var/path in whitelist_typepaths) + if(is_holding_item_of_type(path)) + return TRUE if(!silent) to_chat(src, "You need at least one free arm.") return FALSE @@ -343,93 +356,97 @@ 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!") return - mb_cd_timer = world.time + mb_cd_length - if(canbearoused && has_dna()) - if(stat == DEAD) - if(!forced_climax) - 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(!CHECK_BITFIELD(G.genital_flags, CAN_CLIMAX_WITH)) //Skip things like wombs and testicles - continue - var/mob/living/partner - var/check_target - var/list/worn_stuff = get_equipped_items() + if(!canbearoused || !has_dna()) + return + if(stat == DEAD) + if(!forced_climax) + 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(!CHECK_BITFIELD(G.genital_flags, CAN_CLIMAX_WITH)) //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(pulling) //Are we pulling someone? Priority target, we can't be making option menus for this, has to be quick - if(isliving(pulling)) //Don't fuck objects - check_target = pulling - if(pulledby && !check_target) //prioritise pulled over pulledby - if(isliving(pulledby)) - check_target = 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(G.is_exposed(worn_stuff)) + if(pulling) //Are we pulling someone? Priority target, we can't be making option menus for this, has to be quick + if(isliving(pulling)) //Don't fuck objects + check_target = pulling + if(pulledby && !check_target) //prioritise pulled over pulledby + if(isliving(pulledby)) + check_target = 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 + mb_cd_timer = world.time + mb_cd_length + return + //If we get here, then this is not a forced climax and we gotta check a few things. - if(stat == UNCONSCIOUS) //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!") - return + if(stat == UNCONSCIOUS) //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!") + return - //Ok, now we check what they want to do. - var/choice = input(src, "Select sexual activity", "Sexual activity:") as null|anything in list("Masturbate", "Climax alone", "Climax with partner", "Fill container") + //Ok, now we check what they want to do. + var/choice = input(src, "Select sexual activity", "Sexual activity:") as null|anything in list("Masturbate", "Climax alone", "Climax with partner", "Fill container") + if(!choice) + return - switch(choice) - if("Masturbate") - if(!available_rosie_palms()) - return - //We got hands, let's pick an organ - var/obj/item/organ/genital/picked_organ = pick_masturbate_genitals() - if(picked_organ && available_rosie_palms(TRUE)) - mob_masturbate(picked_organ) - return + switch(choice) + if("Masturbate") + if(!available_rosie_palms()) + return + //We got hands, let's pick an organ + var/obj/item/organ/genital/picked_organ = pick_masturbate_genitals() + if(picked_organ && available_rosie_palms(TRUE)) + mob_masturbate(picked_organ) + return - if("Climax alone") - if(!available_rosie_palms()) - return - var/obj/item/organ/genital/picked_organ = pick_climax_genitals() - if(picked_organ && available_rosie_palms(TRUE)) - mob_climax_outside(picked_organ) + if("Climax alone") + if(!available_rosie_palms()) + return + var/obj/item/organ/genital/picked_organ = pick_climax_genitals() + if(picked_organ && available_rosie_palms(TRUE)) + mob_climax_outside(picked_organ) - 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 = 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 null|anything in list("Yes", "No") - if(spillage && in_range(src, partner)) - mob_climax_partner(picked_organ, partner, spillage == "Yes" ? TRUE : FALSE) + 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 = 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 null|anything in list("Yes", "No") + if(spillage && in_range(src, partner)) + mob_climax_partner(picked_organ, partner, spillage == "Yes" ? TRUE : FALSE) - if("Fill container") - //We'll need hands and no restraints. - if(!available_rosie_palms()) - 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 && available_rosie_palms(TRUE)) - mob_fill_container(picked_organ, fluid_container) - return \ No newline at end of file + if("Fill container") + //We'll need hands and no restraints. + if(!available_rosie_palms(FALSE, /obj/item/reagent_containers)) + 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 && available_rosie_palms(TRUE, /obj/item/reagent_containers)) + mob_fill_container(picked_organ, fluid_container) + + mb_cd_timer = world.time + mb_cd_length \ No newline at end of file diff --git a/modular_citadel/code/modules/arousal/genitals.dm b/modular_citadel/code/modules/arousal/genitals.dm index 38c39d4756..3545c96a50 100644 --- a/modular_citadel/code/modules/arousal/genitals.dm +++ b/modular_citadel/code/modules/arousal/genitals.dm @@ -47,7 +47,7 @@ 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 || CHECK_BITFIELD(genital_flags, GENITAL_INTERNAL|GENITAL_HIDDEN)) + if(!owner || CHECK_BITFIELD(genital_flags, GENITAL_INTERNAL) || CHECK_BITFIELD(genital_flags, GENITAL_HIDDEN)) return FALSE if(CHECK_BITFIELD(genital_flags, GENITAL_THROUGH_CLOTHES)) return TRUE @@ -58,8 +58,6 @@ if(BODY_ZONE_PRECISE_GROIN) return owner.is_groin_exposed() - return FALSE - /obj/item/organ/genital/proc/toggle_visibility(visibility) switch(visibility) if("Always visible") @@ -119,7 +117,7 @@ generate_fluid() /obj/item/organ/genital/proc/generate_fluid() - if(owner.stat != DEAD && reagents.total_volume < reagents.maximum_volume) + if(reagents.total_volume < reagents.maximum_volume) 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 return TRUE @@ -137,13 +135,13 @@ linked_organ = null return FALSE -/obj/item/organ/genital/Insert(mob/living/carbon/M, special = 0) +/obj/item/organ/genital/Insert(mob/living/carbon/M, special = FALSE, drop_if_replaced = TRUE) . = ..() if(.) update() - RegisterSignal(owner, COMSIG_MOB_DEATH, .proc/update_appearance) + RegisterSignal(owner, COMSIG_MOB_DEATH, .proc/update) -/obj/item/organ/genital/Remove(mob/living/carbon/M, special = 0) +/obj/item/organ/genital/Remove(mob/living/carbon/M, special = FALSE, drop_if_replaced = TRUE) . = ..() if(.) update(TRUE) @@ -176,12 +174,11 @@ /mob/living/carbon/human/proc/give_genital(obj/item/organ/genital/G) if(!dna || (NOGENITALS in dna.species.species_traits) || getorganslot(initial(G.slot))) return FALSE - G = new - if(istype(G, /obj/item/organ/genital)) //badminnery-proofing. - G.get_features(src) + G = new G + G.get_features(src) G.Insert(src) -/obj/item/organ/genital/proc/get_features(datum/dna/D) +/obj/item/organ/genital/proc/get_features(mob/living/carbon/human/H) return /datum/species/proc/genitals_layertext(layer) @@ -217,7 +214,6 @@ if(!LAZYLEN(H.internal_organs) || (NOGENITALS in species_traits) || HAS_TRAIT(H, TRAIT_HUSK)) return - var/list/genitals_to_add[GENITAL_LAYER_INDEX_LENGTH] var/list/relevant_layers = list(GENITALS_BEHIND_LAYER, GENITALS_ADJ_LAYER, GENITALS_FRONT_LAYER) var/list/standing = list() var/size @@ -228,15 +224,22 @@ //start scanning for genitals //var/list/worn_stuff = H.get_equipped_items()//cache this list so it's not built again + + var/list/gen_index[GENITAL_LAYER_INDEX_LENGTH] + var/list/genitals_to_add for(var/obj/item/organ/genital/G in H.internal_organs) if(G.is_exposed()) //Checks appropriate clothing slot and if it's through_clothes - LAZYADD(genitals_to_add[G.layer_index], G) + LAZYADD(gen_index[G.layer_index], G) + for(var/L in gen_index) + if(L) //skip nulls + LAZYADD(genitals_to_add, L) //Now we added all genitals that aren't internal and should be rendered //start applying overlays for(var/layer in relevant_layers) - var/layertext = flatten_list(genitals_layertext(layer)) - for(var/obj/item/organ/genital/G in genitals_to_add) + var/layertext = genitals_layertext(layer) + for(var/A in genitals_to_add) + var/obj/item/organ/genital/G = A var/datum/sprite_accessory/S size = G.size aroused_state = G.aroused_state diff --git a/modular_citadel/code/modules/arousal/organs/penis.dm b/modular_citadel/code/modules/arousal/organs/penis.dm index 0441f78774..b387fa96f5 100644 --- a/modular_citadel/code/modules/arousal/organs/penis.dm +++ b/modular_citadel/code/modules/arousal/organs/penis.dm @@ -22,11 +22,11 @@ return var/new_size switch(length) - if(-INFINITY to 5) + if(-INFINITY to 6) new_size = 1 - if(5 to 9) + if(6 to 12) new_size = 2 - if(15 to INFINITY) + if(12 to INFINITY) new_size = 3 //no new sprites for anything larger yet. girth = (length * girth_ratio) cached_length = length diff --git a/modular_citadel/code/modules/arousal/organs/testicles.dm b/modular_citadel/code/modules/arousal/organs/testicles.dm index e06a10b9fc..06f40b5b51 100644 --- a/modular_citadel/code/modules/arousal/organs/testicles.dm +++ b/modular_citadel/code/modules/arousal/organs/testicles.dm @@ -9,7 +9,7 @@ linked_organ_slot = ORGAN_SLOT_PENIS genital_flags = CAN_MASTURBATE_WITH|MASTURBATE_LINKED_ORGAN|GENITAL_FUID_PRODUCTION var/size_name = "average" - shape = "single" + shape = "Single" var/sack_size = BALLS_SACK_SIZE_DEF fluid_id = "semen" masturbation_verb = "massage" @@ -20,20 +20,16 @@ if(!linked_organ && !update_link()) return FALSE . = ..() - if(.) - send_full_message() + if(. && reagents.holder_full()) + to_chat(owner, "Your balls finally feel full, again.") /obj/item/organ/genital/testicles/update_link(removing = FALSE) . = ..() if(. && !size_linked) size = linked_organ.size + update() size_linked = TRUE -/obj/item/organ/genital/testicles/proc/send_full_message(msg = "Your balls finally feel full, again.") - if(owner && istext(msg)) - to_chat(owner, msg) - return TRUE - /obj/item/organ/genital/testicles/update_size(new_size) if(new_size) size = CLAMP(size + new_size, BALLS_SIZE_MIN, BALLS_SIZE_MAX) @@ -71,7 +67,6 @@ color = "#[skintone2hex(H.skin_tone)]" else color = "#[D.features["balls_color"]]" - size = D.features["balls_size"] sack_size = D.features["balls_sack_size"] shape = D.features["balls_shape"] if(D.features["balls_shape"] == "Hidden")