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")