diff --git a/code/__DEFINES/DNA.dm b/code/__DEFINES/DNA.dm
index 4ed099d9e2..95c2c70801 100644
--- a/code/__DEFINES/DNA.dm
+++ b/code/__DEFINES/DNA.dm
@@ -122,3 +122,8 @@
#define ORGAN_SLOT_BRAIN_ANTISTUN "brain_antistun"
#define ORGAN_SLOT_TAIL "tail"
#define ORGAN_SLOT_PENIS "penis"
+#define ORGAN_SLOT_WOMB "womb"
+#define ORGAN_SLOT_VAGINA "vagina"
+#define ORGAN_SLOT_TESTICLES "testicles"
+#define ORGAN_SLOT_BREASTS "breasts"
+
diff --git a/code/__DEFINES/citadel_defines.dm b/code/__DEFINES/citadel_defines.dm
index 2abe0db04e..8b75784ab2 100644
--- a/code/__DEFINES/citadel_defines.dm
+++ b/code/__DEFINES/citadel_defines.dm
@@ -18,6 +18,15 @@
#define CIT_FILTER_STAMINACRIT filter(type="drop_shadow", x=0, y=0, size=-3, border=0, color="#04080F")
//organ defines
+#define VAGINA_LAYER_INDEX 1
+#define TESTICLES_LAYER_INDEX 2
+#define GENITAL_LAYER_INDEX 3
+#define PENIS_LAYER_INDEX 4
+
+#define GENITAL_LAYER_INDEX_LENGTH 4 //keep it updated with each new index added, thanks.
+
+#define MASTURBATE_LINKED_ORGAN 2 //special value used to pass our mission to the linked organ
+
#define COCK_SIZE_MIN 1
#define COCK_SIZE_MAX 20
diff --git a/code/__DEFINES/traits.dm b/code/__DEFINES/traits.dm
index 7bcb4881f0..1546717c7a 100644
--- a/code/__DEFINES/traits.dm
+++ b/code/__DEFINES/traits.dm
@@ -136,13 +136,14 @@
#define TRAIT_SKITTISH "skittish"
#define TRAIT_POOR_AIM "poor_aim"
#define TRAIT_PROSOPAGNOSIA "prosopagnosia"
-#define TRAIT_DRUNK_HEALING "drunk_healing"
-#define TRAIT_TAGGER "tagger"
-#define TRAIT_PHOTOGRAPHER "photographer"
-#define TRAIT_MUSICIAN "musician"
-#define TRAIT_CROCRIN_IMMUNE "crocin_immune"
+#define TRAIT_DRUNK_HEALING "drunk_healing"
+#define TRAIT_TAGGER "tagger"
+#define TRAIT_PHOTOGRAPHER "photographer"
+#define TRAIT_MUSICIAN "musician"
+#define TRAIT_CROCRIN_IMMUNE "crocin_immune"
#define TRAIT_NYMPHO "nymphomania"
#define TRAIT_MASO "masochism"
+#define TRAIT_EXHIBITIONIST "exhibitionist"
#define TRAIT_PARA "paraplegic"
#define TRAIT_EMPATH "empath"
#define TRAIT_FRIENDLY "friendly"
diff --git a/code/__HELPERS/_cit_helpers.dm b/code/__HELPERS/_cit_helpers.dm
index 668b151b6e..d75dd31b46 100644
--- a/code/__HELPERS/_cit_helpers.dm
+++ b/code/__HELPERS/_cit_helpers.dm
@@ -56,6 +56,7 @@ GLOBAL_LIST_EMPTY(ipc_screens_list)
GLOBAL_LIST_EMPTY(ipc_antennas_list)
//Genitals and Arousal Lists
+GLOBAL_LIST_EMPTY(genitals_list)
GLOBAL_LIST_EMPTY(cock_shapes_list)//global_lists.dm for the list initializations //Now also _DATASTRUCTURES globals.dm
GLOBAL_LIST_EMPTY(cock_shapes_icons) //Associated list for names->icon_states for cockshapes.
GLOBAL_LIST_EMPTY(balls_shapes_list)
@@ -124,41 +125,39 @@ GLOBAL_VAR_INIT(miscreants_allowed, FALSE)
SSblackbox.record_feedback("tally", "admin_verb", 1, "TLOOC") //If you are copy-pasting this, ensure the 2nd parameter is unique to the new proc!
/mob/living/carbon/proc/has_penis()
- if(getorganslot("penis"))//slot shared with ovipositor
- if(istype(getorganslot("penis"), /obj/item/organ/genital/penis))
- return TRUE
+ var/obj/item/organ/genital/G = getorganslot(ORGAN_SLOT_PENIS)
+ if(G && istype(G, /obj/item/organ/genital/penis))
+ return TRUE
return FALSE
/mob/living/carbon/proc/has_balls()
- if(getorganslot("balls"))
- if(istype(getorganslot("balls"), /obj/item/organ/genital/testicles))
- return TRUE
+ var/obj/item/organ/genital/G = getorganslot(ORGAN_SLOT_TESTICLES)
+ if(G && istype(G, /obj/item/organ/genital/testicles))
+ return TRUE
return FALSE
/mob/living/carbon/proc/has_vagina()
- if(getorganslot("vagina"))
+ if(getorganslot(ORGAN_SLOT_VAGINA))
return TRUE
return FALSE
/mob/living/carbon/proc/has_breasts()
- if(getorganslot("breasts"))
+ if(getorganslot(ORGAN_SLOT_BREASTS))
return TRUE
return FALSE
/mob/living/carbon/proc/has_ovipositor()
- if(getorganslot("penis"))//shared slot
- if(istype(getorganslot("penis"), /obj/item/organ/genital/ovipositor))
- return TRUE
+ var/obj/item/organ/genital/G = getorganslot(ORGAN_SLOT_PENIS)
+ if(G && istype(G, /obj/item/organ/genital/ovipositor))
+ return TRUE
return FALSE
/mob/living/carbon/human/proc/has_eggsack()
- if(getorganslot("balls"))
- if(istype(getorganslot("balls"), /obj/item/organ/genital/eggsack))
- return TRUE
+ var/obj/item/organ/genital/G = getorganslot(ORGAN_SLOT_TESTICLES)
+ if(G && istype(G, /obj/item/organ/genital/eggsack))
+ return TRUE
return FALSE
-/mob/living/carbon/human/proc/is_bodypart_exposed(bodypart)
-
/mob/living/carbon/proc/is_groin_exposed(var/list/L)
if(!L)
L = get_equipped_items()
@@ -188,9 +187,9 @@ GLOBAL_VAR_INIT(miscreants_allowed, FALSE)
message_admins("[src] gave everyone genitals.")
for(var/mob/living/carbon/human/H in GLOB.mob_list)
if(H.gender == MALE)
- H.give_penis()
- H.give_balls()
+ H.give_genital(/obj/item/organ/genital/penis)
+ H.give_genital(/obj/item/organ/genital/testicles)
else
- H.give_vagina()
- H.give_womb()
- H.give_breasts()
+ H.give_genital(/obj/item/organ/genital/vagina)
+ H.give_genital(/obj/item/organ/genital/womb)
+ H.give_genital(/obj/item/organ/genital/breasts)
diff --git a/code/__HELPERS/global_lists.dm b/code/__HELPERS/global_lists.dm
index c8a33959ed..b0b0f32345 100644
--- a/code/__HELPERS/global_lists.dm
+++ b/code/__HELPERS/global_lists.dm
@@ -61,6 +61,10 @@
for(var/K in GLOB.balls_shapes_list)
var/datum/sprite_accessory/testicles/value = GLOB.balls_shapes_list[K]
GLOB.balls_shapes_icons[K] = value.icon_state
+
+ for(var/gpath in subtypesof(/obj/item/organ/genital))
+ var/obj/item/organ/genital/G = gpath
+ GLOB.genitals_list[initial(G.name)] = gpath
//END OF CIT CHANGES
//Species
diff --git a/code/__HELPERS/mobs.dm b/code/__HELPERS/mobs.dm
index 5ec839130a..81801cd6a2 100644
--- a/code/__HELPERS/mobs.dm
+++ b/code/__HELPERS/mobs.dm
@@ -154,7 +154,6 @@
"xenodorsal" = "Standard",
"xenohead" = "Standard",
"xenotail" = "Xenomorph Tail",
- "exhibitionist" = FALSE,
"genitals_use_skintone" = FALSE,
"has_cock" = FALSE,
"cock_shape" = pick(GLOB.cock_shapes_list),
diff --git a/code/datums/traits/neutral.dm b/code/datums/traits/neutral.dm
index f70e3a3c68..156ee00d75 100644
--- a/code/datums/traits/neutral.dm
+++ b/code/datums/traits/neutral.dm
@@ -94,6 +94,41 @@
lose_text = "You don't feel as prudish as before."
medical_record_text = "Patient exhibits a special gene that makes them immune to Crocin and Hexacrocin."
+/datum/quirk/libido
+ name = "Nymphomania"
+ desc = "You're always feeling a bit in heat. Also, you get aroused faster than usual."
+ value = 0
+ mob_trait = TRAIT_NYMPHO
+ gain_text = "You are feeling extra wild."
+ lose_text = "You don't feel that burning sensation anymore."
+
+/datum/quirk/libido/add()
+ var/mob/living/M = quirk_holder
+ M.min_arousal = 16
+ M.arousal_rate = 3
+
+/datum/quirk/libido/remove()
+ var/mob/living/M = quirk_holder
+ M.min_arousal = initial(M.min_arousal)
+ M.arousal_rate = initial(M.arousal_rate)
+
+/datum/quirk/maso
+ name = "Masochism"
+ desc = "You are aroused by pain."
+ value = 0
+ mob_trait = TRAIT_MASO
+ gain_text = "You desire to be hurt."
+ lose_text = "Pain has become less exciting for you."
+
+/datum/quirk/exhibitionism
+ name = "Exhibitionism"
+ desc = "You don't mind showing off your bare body to strangers, in fact you find it quite satistying. Not the best excuse to break Space Law anyway." //as if they'd care anyway.
+ value = 0
+ medical_record_text = "Patient has been diagnosed with exhibitionistic disorder."
+ mob_trait = TRAIT_EXHIBITIONIST
+ gain_text = "You feel like exposing yourself to the world."
+ lose_text = "Indecent exposure doesn't sound as charming to you anymore."
+
/datum/quirk/assblastusa
name = "Buns of Steel"
desc = "You've never skipped ass day. With this trait, you are completely immune to all forms of ass slapping and anyone who tries to slap your rock hard ass usually gets a broken hand."
diff --git a/code/modules/admin/create_mob.dm b/code/modules/admin/create_mob.dm
index fad7410a6a..eb856237bf 100644
--- a/code/modules/admin/create_mob.dm
+++ b/code/modules/admin/create_mob.dm
@@ -25,6 +25,9 @@
H.facial_hair_color = H.hair_color
H.eye_color = random_eye_color()
H.dna.blood_type = random_blood_type()
+ H.saved_underwear = H.underwear
+ H.saved_undershirt = H.undershirt
+ H.saved_socks = H.socks
// Mutant randomizing, doesn't affect the mob appearance unless it's the specific mutant.
H.dna.features["mcolor"] = random_short_color()
diff --git a/code/modules/client/preferences.dm b/code/modules/client/preferences.dm
index 54d75e8437..bac9ed0c89 100644
--- a/code/modules/client/preferences.dm
+++ b/code/modules/client/preferences.dm
@@ -108,7 +108,6 @@ GLOBAL_LIST_EMPTY(preferences_datums)
"xenohead" = "Standard",
"xenotail" = "Xenomorph Tail",
"taur" = "None",
- "exhibitionist" = FALSE,
"genitals_use_skintone" = FALSE,
"has_cock" = FALSE,
"cock_shape" = "Human",
@@ -790,7 +789,6 @@ GLOBAL_LIST_EMPTY(preferences_datums)
dat +="
"
dat += "Citadel Preferences" //Because fuck me if preferences can't be fucking modularized and expected to update in a reasonable timeframe.
dat += "Arousal:[arousable == TRUE ? "Enabled" : "Disabled"] "
- dat += "Exhibitionist:[features["exhibitionist"] == TRUE ? "Yes" : "No"] "
dat += "Voracious MediHound sleepers: [(cit_toggles & MEDIHOUND_SLEEPER) ? "Yes" : "No"] "
dat += "Hear Vore Sounds: [(cit_toggles & EATING_NOISES) ? "Yes" : "No"] "
dat += "Hear Vore Digestion Sounds: [(cit_toggles & DIGESTION_NOISES) ? "Yes" : "No"] "
@@ -2048,8 +2046,6 @@ GLOBAL_LIST_EMPTY(preferences_datums)
features["has_womb"] = FALSE
if("has_womb")
features["has_womb"] = !features["has_womb"]
- if("exhibitionist")
- features["exhibitionist"] = !features["exhibitionist"]
if("widescreenpref")
widescreenpref = !widescreenpref
user.client.change_view(CONFIG_GET(string/default_view))
diff --git a/code/modules/client/preferences_savefile.dm b/code/modules/client/preferences_savefile.dm
index b9c5cb7ef9..175cd9613a 100644
--- a/code/modules/client/preferences_savefile.dm
+++ b/code/modules/client/preferences_savefile.dm
@@ -5,7 +5,7 @@
// You do not need to raise this if you are adding new values that have sane defaults.
// Only raise this value when changing the meaning/format/name/layout of an existing value
// where you would want the updater procs below to run
-#define SAVEFILE_VERSION_MAX 20
+#define SAVEFILE_VERSION_MAX 21
/*
SAVEFILE UPDATING/VERSIONING - 'Simplified', or rather, more coder-friendly ~Carn
@@ -49,6 +49,11 @@ SAVEFILE UPDATING/VERSIONING - 'Simplified', or rather, more coder-friendly ~Car
pda_style = "mono"
if(current_version < 20)
pda_color = "#808000"
+ if(current_version < 21 && S["feature_exhibitionist"])
+ var/datum/quirk/exhibitionism/E
+ var/quirk_name = initial(E.name)
+ neutral_quirks += quirk_name
+ all_quirks += quirk_name
/datum/preferences/proc/load_path(ckey,filename="preferences.sav")
if(!ckey)
diff --git a/modular_citadel/code/datums/traits/neutral.dm b/modular_citadel/code/datums/traits/neutral.dm
deleted file mode 100644
index 05aeb27361..0000000000
--- a/modular_citadel/code/datums/traits/neutral.dm
+++ /dev/null
@@ -1,33 +0,0 @@
-// Citadel-specific Neutral Traits
-
-/datum/quirk/libido
- name = "Nymphomania"
- desc = "You're always feeling a bit in heat. Also, you get aroused faster than usual."
- value = 0
- mob_trait = TRAIT_NYMPHO
- gain_text = "You are feeling extra wild."
- lose_text = "You don't feel that burning sensation anymore."
-
-/datum/quirk/libido/add()
- var/mob/living/M = quirk_holder
- M.min_arousal = 16
- M.arousal_rate = 3
-
-/datum/quirk/libido/remove()
- var/mob/living/M = quirk_holder
- M.min_arousal = initial(M.min_arousal)
- M.arousal_rate = initial(M.arousal_rate)
-
-/datum/quirk/libido/on_process()
- var/mob/living/M = quirk_holder
- if(M.canbearoused == FALSE)
- to_chat(quirk_holder, "Having high libido is useless when you can't feel arousal at all!")
- qdel(src)
-
-/datum/quirk/maso
- name = "Masochism"
- desc = "You are aroused by pain."
- value = 0
- mob_trait = TRAIT_MASO
- gain_text = "You desire to be hurt."
- lose_text = "Pain has become less exciting for you."
diff --git a/modular_citadel/code/modules/arousal/arousal.dm b/modular_citadel/code/modules/arousal/arousal.dm
index 27f7576e7f..8826cb0774 100644
--- a/modular_citadel/code/modules/arousal/arousal.dm
+++ b/modular_citadel/code/modules/arousal/arousal.dm
@@ -19,11 +19,6 @@
var/hidden_undershirt = FALSE
var/hidden_socks = FALSE
-/mob/living/carbon/human/New()
- ..()
- saved_underwear = underwear
- saved_undershirt = undershirt
-
//Species vars
/datum/species
var/arousal_gain_rate = AROUSAL_START_VALUE //Rate at which this species becomes aroused
@@ -35,60 +30,53 @@
//Mob procs
/mob/living/carbon/human/proc/underwear_toggle()
set name = "Toggle undergarments"
- set category = "Object"
- if(ishuman(src))
- var/mob/living/carbon/human/humz = src
- var/confirm = input(src, "Select what part of your form to alter", "Undergarment Toggling", "Cancel") in list("Top", "Bottom", "Socks", "All", "Cancel")
- if(confirm == "Top")
- humz.hidden_undershirt = !humz.hidden_undershirt
+ set category = "IC"
- if(confirm == "Bottom")
- humz.hidden_underwear = !humz.hidden_underwear
+ var/confirm = input(src, "Select what part of your form to alter", "Undergarment Toggling") as null|anything in list("Top", "Bottom", "Socks", "All")
+ if(!confirm)
+ return
+ if(confirm == "Top")
+ hidden_undershirt = !hidden_undershirt
- if(confirm == "Socks")
- humz.hidden_socks = !humz.hidden_socks
+ if(confirm == "Bottom")
+ hidden_underwear = !hidden_underwear
- if(confirm == "All")
- humz.hidden_undershirt = !humz.hidden_undershirt
- humz.hidden_underwear = !humz.hidden_underwear
- humz.hidden_socks = !humz.hidden_socks
+ if(confirm == "Socks")
+ hidden_socks = !hidden_socks
- if(confirm == "Cancel")
- return
- src.update_body()
+ if(confirm == "All")
+ var/on_off = (hidden_undershirt || hidden_underwear || hidden_socks) ? FALSE : TRUE
+ hidden_undershirt = on_off
+ hidden_underwear = on_off
+ hidden_socks = on_off
- else
- to_chat(src, "Humans only. How the fuck did you get this verb anyway.")
+ update_body()
-/mob/living/proc/handle_arousal()
-
-
-/mob/living/carbon/handle_arousal()
- if(canbearoused && dna)
- var/datum/species/S
- S = dna.species
- if(S && !(SSmobs.times_fired % 36) && getArousalLoss() < max_arousal)//Totally stolen from breathing code. Do this every 36 ticks.
- adjustArousalLoss(arousal_rate * S.arousal_gain_rate)
- if(dna.features["exhibitionist"] && client)
- var/amt_nude = 0
- if(is_chest_exposed() && (getorganslot("breasts")))
- amt_nude++
- if(is_groin_exposed())
- if(getorganslot("penis"))
- amt_nude++
- if(getorganslot("vagina"))
- amt_nude++
- if(amt_nude)
- var/watchers = 0
- for(var/mob/_M in view(world.view, src))
- var/mob/living/M = _M
- if(!istype(M))
- continue
- if(M.client && !M.stat && !M.eye_blind && (locate(src) in viewers(world.view,M)))
- watchers++
- if(watchers)
- adjustArousalLoss((amt_nude * watchers) + S.arousal_gain_rate)
+/mob/living/proc/handle_arousal(times_fired)
+ return
+/mob/living/carbon/handle_arousal(times_fired)
+ if(!canbearoused || !dna)
+ return
+ var/datum/species/S = dna.species
+ if(!S || (times_fired % 36) || !getArousalLoss() >= max_arousal)//Totally stolen from breathing code. Do this every 36 ticks.
+ return
+ var/our_loss = arousal_rate * S.arousal_gain_rate
+ if(HAS_TRAIT(src, TRAIT_EXHIBITIONIST) && client)
+ var/amt_nude = 0
+ for(var/obj/item/organ/genital/G in internal_organs)
+ if(G.is_exposed())
+ amt_nude++
+ if(amt_nude)
+ var/watchers = 0
+ for(var/mob/living/L in view(world.view, src))
+ if(!istype(L))
+ continue
+ if(L.client && !L.stat && !L.eye_blind && (locate(src) in viewers(world.view, L)))
+ watchers++
+ if(watchers)
+ our_loss += (amt_nude * watchers) + S.arousal_gain_rate
+ adjustArousalLoss(our_loss)
/mob/living/proc/getArousalLoss()
return arousalloss
@@ -138,8 +126,6 @@
S = GLOB.breasts_shapes_list[G.shape]
if(S?.alt_aroused)
G.aroused_state = isPercentAroused(G.aroused_amount)
- if(getArousalLoss() >= ((max_arousal / 100) * 33))
- G.aroused_state = TRUE
else
G.aroused_state = FALSE
G.update_appearance()
@@ -147,54 +133,18 @@
/mob/living/proc/update_arousal_hud()
return FALSE
-/datum/species/proc/update_arousal_hud(mob/living/carbon/human/H)
- return FALSE
-
/mob/living/carbon/human/update_arousal_hud()
- if(!client || !hud_used)
- return FALSE
- if(dna.species.update_arousal_hud())
+ if(!client || !(hud_used?.arousal))
return FALSE
if(!canbearoused)
hud_used.arousal.icon_state = ""
return FALSE
+ if(stat == DEAD)
+ hud_used.arousal.icon_state = "arousal0"
else
- if(hud_used.arousal)
- if(stat == DEAD)
- hud_used.arousal.icon_state = "arousal0"
- return TRUE
- if(getArousalLoss() == max_arousal)
- hud_used.arousal.icon_state = "arousal100"
- return TRUE
- if(getArousalLoss() >= (max_arousal / 100) * 90)//M O D U L A R , W O W
- hud_used.arousal.icon_state = "arousal90"
- return TRUE
- if(getArousalLoss() >= (max_arousal / 100) * 80)//M O D U L A R , W O W
- hud_used.arousal.icon_state = "arousal80"
- return TRUE
- if(getArousalLoss() >= (max_arousal / 100) * 70)//M O D U L A R , W O W
- hud_used.arousal.icon_state = "arousal70"
- return TRUE
- if(getArousalLoss() >= (max_arousal / 100) * 60)//M O D U L A R , W O W
- hud_used.arousal.icon_state = "arousal60"
- return TRUE
- if(getArousalLoss() >= (max_arousal / 100) * 50)//M O D U L A R , W O W
- hud_used.arousal.icon_state = "arousal50"
- return TRUE
- if(getArousalLoss() >= (max_arousal / 100) * 40)//M O D U L A R , W O W
- hud_used.arousal.icon_state = "arousal40"
- return TRUE
- if(getArousalLoss() >= (max_arousal / 100) * 30)//M O D U L A R , W O W
- hud_used.arousal.icon_state = "arousal30"
- return TRUE
- if(getArousalLoss() >= (max_arousal / 100) * 20)//M O D U L A R , W O W
- hud_used.arousal.icon_state = "arousal10"
- return TRUE
- if(getArousalLoss() >= (max_arousal / 100) * 10)//M O D U L A R , W O W
- hud_used.arousal.icon_state = "arousal10"
- return TRUE
- else
- hud_used.arousal.icon_state = "arousal0"
+ var/value = FLOOR(getPercentAroused(), 10)
+ hud_used.arousal.icon_state = "arousal[value]"
+ return TRUE
/obj/screen/arousal
name = "arousal"
@@ -213,7 +163,6 @@
to_chat(M, "Arousal is disabled. Feature is unavailable.")
-
/mob/living/proc/mob_climax()//This is just so I can test this shit without being forced to add actual content to get rid of arousal. Will be a very basic proc for a while.
set name = "Masturbate"
set category = "IC"
@@ -221,212 +170,150 @@
if(mb_cd_timer <= world.time)
//start the cooldown even if it fails
mb_cd_timer = world.time + mb_cd_length
- if(getArousalLoss() >= ((max_arousal / 100) * 33))//33% arousal or greater required
- src.visible_message("[src] starts masturbating!", \
+ if(getArousalLoss() >= 33)//one third of average max_arousal or greater required
+ visible_message("[src] starts masturbating!", \
"You start masturbating.")
if(do_after(src, 30, target = src))
- src.visible_message("[src] relieves [p_them()]self!", \
+ visible_message("[src] relieves [p_them()]self!", \
"You have relieved yourself.")
SEND_SIGNAL(src, COMSIG_ADD_MOOD_EVENT, "orgasm", /datum/mood_event/orgasm)
setArousalLoss(min_arousal)
else
to_chat(src, "You aren't aroused enough for that.")
+/obj/item/organ/genital/proc/climaxable(mob/living/carbon/human/H, silent = FALSE) //returns the fluid source (ergo reagents holder) if found.
+ if(producing)
+ . = reagents
+ else
+ if(linked_organ)
+ . = linked_organ.reagents
+ else if(!silent)
+ to_chat(H, "Your [name] is unable to produce it's own fluids, it's missing the organs for it.")
+
+/mob/living/carbon/human/proc/do_climax(datum/reagents/R, atom/target, obj/item/organ/genital/G, spill = TRUE)
+ if(!G)
+ return
+ SEND_SIGNAL(src, COMSIG_ADD_MOOD_EVENT, "orgasm", /datum/mood_event/orgasm)
+ if(!target || !R)
+ return
+ var/turfing = isturf(target)
+ if(spill & R.total_volume >= 5)
+ R.reaction(turfing ? target : target.loc, TOUCH, 1, 0)
+ if(!turfing)
+ R.trans_to(target, R.total_volume * (spill ? G.fluid_transfer_factor : 1))
+ R.clear_reagents()
//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
+ var/datum/reagents/fluid_source = G.climaxable(src)
+ if(!fluid_source)
+ return
+ var/obj/item/organ/genital/PP = G.can_masturbate_with == MASTURBATE_LINKED_ORGAN ? G.linked_organ : G
+ if(!PP)
+ to_chat(src, "You shudder, unable to cum with your [name].")
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].", \
+ visible_message("[src] starts to [G.masturbation_verb] [p_their()] [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.")
- SEND_SIGNAL(src, COMSIG_ADD_MOOD_EVENT, "orgasm", /datum/mood_event/orgasm)
- if(G.can_climax)
- setArousalLoss(min_arousal)
-
+ 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]" : ""].")
+ 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
- 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.", \
+ var/datum/reagents/fluid_source = G.climaxable(src, TRUE)
+ if(!fluid_source)
+ visible_message("[src] shudders, their [G.name] unable to cum.", \
"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].")
- SEND_SIGNAL(src, COMSIG_ADD_MOOD_EVENT, "orgasm", /datum/mood_event/orgasm)
- if(G.can_climax)
- setArousalLoss(min_arousal)
-
+ return
+ if(mb_time) //as long as it's not instant, give a warning
+ visible_message("[src] looks like they're about to cum.", \
+ "You feel yourself about to orgasm.")
+ if(!do_after(src, mb_time, target = src) || !G.climaxable(src, TRUE))
+ return
+ visible_message("[src] orgasms[isturf(loc) ? " onto [loc]" : ""], using [p_their()] [G.name]!", \
+ "You climax[isturf(loc) ? " onto [loc]" : ""] with your [G.name].")
+ do_climax(fluid_source, loc, G)
/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
+ var/datum/reagents/fluid_source = G.climaxable(src)
+ if(!fluid_source)
+ return
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!")
+ visible_message("[src] is about to climax with [L]!", \
+ "You're about to climax with [L]!")
+ if(!do_after(src, mb_time, target = src) || !in_range(src, L) || !G.climaxable(src, TRUE))
+ return
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].")
- SEND_SIGNAL(src, COMSIG_ADD_MOOD_EVENT, "orgasm", /datum/mood_event/orgasm)
- SEND_SIGNAL(L, COMSIG_ADD_MOOD_EVENT, "orgasm", /datum/mood_event/orgasm)
- if(G.can_climax)
- setArousalLoss(min_arousal)
+ visible_message("[src] climaxes with [L], overflowing and spilling, using [p_their()] [G.name]!", \
+ "You orgasm with [L], spilling out of them, using your [G.name].")
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.")
- SEND_SIGNAL(src, COMSIG_ADD_MOOD_EVENT, "orgasm", /datum/mood_event/orgasm)
- SEND_SIGNAL(L, COMSIG_ADD_MOOD_EVENT, "orgasm", /datum/mood_event/orgasm)
- if(G.can_climax)
- setArousalLoss(min_arousal)
+ visible_message("[src] climaxes with [L], [p_their()] [G.name] spilling nothing!", \
+ "You ejaculate with [L], your [G.name] spilling nothing.")
+ SEND_SIGNAL(L, COMSIG_ADD_MOOD_EVENT, "orgasm", /datum/mood_event/orgasm)
+ do_climax(fluid_source, spillage ? loc : L, G, spillage)
/mob/living/carbon/human/proc/mob_fill_container(obj/item/organ/genital/G, obj/item/reagent_containers/container, mb_time = 30) //For beaker-filling, beware the bartender
- var/total_fluids = 0
- var/datum/reagents/fluid_source = null
-
- 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.")
+ var/datum/reagents/fluid_source = G.climaxable(src)
+ if(!fluid_source)
+ return
+ if(mb_time)
+ visible_message("[src] starts to [G.masturbation_verb] their [G.name] over [container].", \
+ "You start to [G.masturbation_verb] your [G.name] over [container].")
+ if(!do_after(src, mb_time, target = src) || !in_range(src, container) || !G.climaxable(src, TRUE))
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.")
- SEND_SIGNAL(src, COMSIG_ADD_MOOD_EVENT, "orgasm", /datum/mood_event/orgasm)
- if(G.can_climax)
- setArousalLoss(min_arousal)
+ visible_message("[src] uses [p_their()] [G.name] to fill [container]!", \
+ "You used your [G.name] to fill [container].")
+ do_climax(fluid_source, container, G, FALSE)
/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(G.can_masturbate_with && G.is_exposed(worn_stuff)) //filter out what you can't masturbate with
+ if(G.can_masturbate_with == MASTURBATE_LINKED_ORGAN && !G.linked_organ)
+ continue
+ genitals_list += G
if(genitals_list.len)
- ret_organ = input(src, "with what?", "Masturbate", null) as null|obj in genitals_list
+ var/obj/item/organ/genital/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(G.can_climax && G.is_exposed(worn_stuff)) //filter out what you can't masturbate with
+ genitals_list += G
if(genitals_list.len)
- ret_organ = input(src, "with what?", "Climax", null) as null|obj in genitals_list
+ var/obj/item/organ/genital/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
+ if(pulling)
+ partners += pulling
+ if(pulledby)
+ partners += 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
+ for(var/mob/living/L in partners)
+ if(iscarbon(L))
+ var/mob/living/carbon/C = L
+ if(!C.exposed_genitals.len && !C.is_groin_exposed() && !C.is_chest_exposed()) //Nothing through_clothing, no proper partner.
+ partners -= C
//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
+ 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()
- var/obj/item/reagent_containers/SC = null
var/list/containers_list = list()
for(var/obj/item/reagent_containers/container in held_items)
@@ -434,12 +321,21 @@
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
+ 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))
+ return SC
return null //If nothing correct, give null.
+/mob/living/carbon/human/proc/available_rosie_palms(silent = FALSE)
+ 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(!silent)
+ to_chat(src, "You need at least one free arm.")
+ return FALSE
+ return TRUE
//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
@@ -447,49 +343,47 @@
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)
-
+ mb_cd_timer = world.time + mb_cd_length
if(canbearoused && has_dna())
- if(stat==2)
- to_chat(src, "You can't do that while dead!")
+ 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/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()
+ for(var/obj/item/organ/genital/G in internal_organs)
+ 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
+ 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(stat==1) //No sleep-masturbation, you're unconscious.
+ 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
@@ -497,91 +391,38 @@
return
//Ok, now we check what they want to do.
- var/choice = input(src, "Select sexual activity", "Sexual activity:") in list("Masturbate", "Climax alone", "Climax with partner", "Fill container")
+ var/choice = input(src, "Select sexual activity", "Sexual activity:") as null|anything in list("Masturbate", "Climax alone", "Climax with partner", "Fill container")
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.")
+ if(!available_rosie_palms())
return
//We got hands, let's pick an organ
- var/obj/item/organ/genital/picked_organ
- picked_organ = pick_masturbate_genitals()
- if(picked_organ)
+ var/obj/item/organ/genital/picked_organ = pick_masturbate_genitals()
+ if(picked_organ && available_rosie_palms(TRUE))
mob_masturbate(picked_organ)
return
- else //They either lack organs that can masturbate, or they didn't pick one.
- to_chat(src, "You cannot masturbate without choosing genitals.")
- return
if("Climax alone")
- if(restrained(TRUE)) //TRUE ignores grabs
- to_chat(src, "You can't do that while restrained!")
+ if(!available_rosie_palms())
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)
+ var/obj/item/organ/genital/picked_organ = pick_climax_genitals()
+ if(picked_organ && available_rosie_palms(TRUE))
mob_climax_outside(picked_organ)
- return
- else //They either lack organs that can masturbate, or they didn't pick one.
- to_chat(src, "You cannot climax without choosing genitals.")
- return
if("Climax 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()
+ 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 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
+ 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(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.")
+ if(!available_rosie_palms())
return
//We got hands, let's pick an organ
var/obj/item/organ/genital/picked_organ
@@ -589,14 +430,6 @@
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)
+ if(fluid_container && available_rosie_palms(TRUE))
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.
+ return
\ No newline at end of file
diff --git a/modular_citadel/code/modules/arousal/organs/genitals.dm b/modular_citadel/code/modules/arousal/organs/_genitals.dm
similarity index 60%
rename from modular_citadel/code/modules/arousal/organs/genitals.dm
rename to modular_citadel/code/modules/arousal/organs/_genitals.dm
index 63d6834409..2222624050 100644
--- a/modular_citadel/code/modules/arousal/organs/genitals.dm
+++ b/modular_citadel/code/modules/arousal/organs/_genitals.dm
@@ -6,8 +6,9 @@
var/list/genital_flags = list()
var/can_masturbate_with = FALSE
var/masturbation_verb = "masturbate"
+ var/orgasm_verb = "cumming" //present continous
var/can_climax = FALSE
- var/fluid_transfer_factor = 0.0 //How much would a partner get in them if they climax using this?
+ var/fluid_transfer_factor = 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
@@ -18,48 +19,49 @@
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/linked_organ_slot //only one of the two organs needs this to be set up. update_link() will handle linking the rest.
var/through_clothes = FALSE
var/internal = FALSE
var/hidden = FALSE
+ var/layer_index = GENITAL_LAYER_INDEX //Order should be very important. FIRST vagina, THEN testicles, THEN penis, as this affects the order they are rendered in.
/obj/item/organ/genital/Initialize()
. = ..()
- if(!reagents)
+ if(fluid_id)
create_reagents(fluid_max_volume)
+ if(producing)
+ reagents.add_reagent(fluid_id, fluid_max_volume)
update()
/obj/item/organ/genital/Destroy()
- remove_ref()
+ if(linked_organ)
+ update_link(TRUE)//this should remove any other links it has
if(owner)
- Remove(owner, 1)//this should remove references to it, so it can be GCd correctly
- update_link()//this should remove any other links it has
+ Remove(owner, TRUE)//this should remove references to it, so it can be GCd correctly
return ..()
-/obj/item/organ/genital/proc/update()
+/obj/item/organ/genital/proc/update(removing = FALSE)
if(QDELETED(src))
return
update_size()
update_appearance()
- update_link()
+ if(linked_organ_slot || (linked_organ && removing))
+ update_link(removing)
//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(hidden)
- return FALSE
- if(internal)
+ if(!owner || hidden || internal)
return FALSE
if(through_clothes)
return TRUE
switch(zone) //update as more genitals are added
- if("chest")
+ if(BODY_ZONE_CHEST)
return owner.is_chest_exposed()
- if("groin")
+ if(BODY_ZONE_PRECISE_GROIN)
return owner.is_groin_exposed()
return FALSE
@@ -107,22 +109,37 @@
picked_organ.toggle_visibility(picked_visibility)
return
-
-
-
/obj/item/organ/genital/proc/update_size()
return
/obj/item/organ/genital/proc/update_appearance()
return
-/obj/item/organ/genital/proc/update_link()
- return
+/obj/item/organ/genital/on_life()
+ if(!reagents || !owner)
+ return
+ reagents.maximum_volume = fluid_max_volume
+ if(fluid_id && producing)
+ generate_fluid()
-/obj/item/organ/genital/proc/remove_ref()
- if(linked_organ)
- linked_organ.linked_organ = null
+/obj/item/organ/genital/proc/generate_fluid()
+ if(owner.stat != DEAD && 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
+ return FALSE
+
+/obj/item/organ/genital/proc/update_link(removing = FALSE)
+ if(!removing && owner)
+ linked_organ = owner.getorganslot(linked_organ_slot)
+ if(linked_organ)
+ linked_organ.linked_organ = src
+ return TRUE
+ else
+ if(linked_organ)
+ linked_organ.linked_organ = null
linked_organ = null
+ return FALSE
/obj/item/organ/genital/Insert(mob/living/carbon/M, special = 0)
..()
@@ -130,127 +147,42 @@
/obj/item/organ/genital/Remove(mob/living/carbon/M, special = 0)
..()
- update()
+ update(TRUE)
//proc to give a player their genitals and stuff when they log in
-/mob/living/carbon/human/proc/give_genitals(clean=0)//clean will remove all pre-existing genitals. proc will then give them any genitals that are enabled in their DNA
+/mob/living/carbon/human/proc/give_genitals(clean = FALSE)//clean will remove all pre-existing genitals. proc will then give them any genitals that are enabled in their DNA
if(clean)
- var/obj/item/organ/genital/GtoClean
- for(GtoClean in internal_organs)
- qdel(GtoClean)
+ for(var/obj/item/organ/genital/G in internal_organs)
+ qdel(G)
if (NOGENITALS in dna.species.species_traits)
return
- //Order should be very important. FIRST vagina, THEN testicles, THEN penis, as this affects the order they are rendered in.
if(dna.features["has_vag"])
- give_vagina()
+ give_genital(/obj/item/organ/genital/vagina)
if(dna.features["has_womb"])
- give_womb()
+ give_genital(/obj/item/organ/genital/womb)
if(dna.features["has_balls"])
- give_balls()
+ give_genital(/obj/item/organ/genital/testicles)
if(dna.features["has_breasts"]) // since we have multi-boobs as a thing, we'll want to at least draw over these. but not over the pingas.
- give_breasts()
+ give_genital(/obj/item/organ/genital/breasts)
if(dna.features["has_cock"])
- give_penis()
+ give_genital(/obj/item/organ/genital/penis)
+ /*
if(dna.features["has_ovi"])
- give_ovipositor()
+ give_genital(/obj/item/organ/genital/ovipositor)
if(dna.features["has_eggsack"])
- give_eggsack()
+ give_genital(/obj/item/organ/genital/eggsack)
+ */
-/mob/living/carbon/human/proc/give_penis()
- if(!dna)
+/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
- if(NOGENITALS in dna.species.species_traits)
- return FALSE
- if(!getorganslot("penis"))
- var/obj/item/organ/genital/penis/P = new
- P.Insert(src)
- if(P)
- if(dna.species.use_skintones && dna.features["genitals_use_skintone"])
- P.color = "#[skintone2hex(skin_tone)]"
- else
- P.color = "#[dna.features["cock_color"]]"
- P.length = dna.features["cock_length"]
- P.girth_ratio = dna.features["cock_girth_ratio"]
- P.shape = dna.features["cock_shape"]
- P.update()
+ G = new
+ if(istype(G, /obj/item/organ/genital)) //badminnery-proofing.
+ G.get_features(src)
+ G.Insert(src)
-/mob/living/carbon/human/proc/give_balls()
- if(!dna)
- return FALSE
- if(NOGENITALS in dna.species.species_traits)
- return FALSE
- if(!getorganslot("testicles"))
- var/obj/item/organ/genital/testicles/T = new
- T.Insert(src)
- if(T)
- if(dna.species.use_skintones && dna.features["genitals_use_skintone"])
- T.color = "#[skintone2hex(skin_tone)]"
- else
- T.color = "#[dna.features["balls_color"]]"
- T.size = dna.features["balls_size"]
- T.sack_size = dna.features["balls_sack_size"]
- T.shape = dna.features["balls_shape"]
- if(dna.features["balls_shape"] == "Hidden")
- T.internal = TRUE
- else
- T.internal = FALSE
- T.fluid_id = dna.features["balls_fluid"]
- T.fluid_rate = dna.features["balls_cum_rate"]
- T.fluid_mult = dna.features["balls_cum_mult"]
- T.fluid_efficiency = dna.features["balls_efficiency"]
- T.update()
-
-/mob/living/carbon/human/proc/give_breasts()
- if(!dna)
- return FALSE
- if(NOGENITALS in dna.species.species_traits)
- return FALSE
- if(!getorganslot("breasts"))
- var/obj/item/organ/genital/breasts/B = new
- B.Insert(src)
- if(B)
- if(dna.species.use_skintones && dna.features["genitals_use_skintone"])
- B.color = "#[skintone2hex(skin_tone)]"
- else
- B.color = "#[dna.features["breasts_color"]]"
- B.size = dna.features["breasts_size"]
- B.shape = dna.features["breasts_shape"]
- B.fluid_id = dna.features["breasts_fluid"]
- B.update()
-
-
-/mob/living/carbon/human/proc/give_ovipositor()
+/obj/item/organ/genital/proc/get_features(datum/dna/D)
return
-/mob/living/carbon/human/proc/give_eggsack()
- return
-
-/mob/living/carbon/human/proc/give_vagina()
- if(!dna)
- return FALSE
- if(NOGENITALS in dna.species.species_traits)
- return FALSE
- if(!getorganslot("vagina"))
- var/obj/item/organ/genital/vagina/V = new
- V.Insert(src)
- if(V)
- if(dna.species.use_skintones && dna.features["genitals_use_skintone"])
- V.color = "#[skintone2hex(skin_tone)]"
- else
- V.color = "[dna.features["vag_color"]]"
- V.shape = "[dna.features["vag_shape"]]"
- V.update()
-
-/mob/living/carbon/human/proc/give_womb()
- if(!dna)
- return FALSE
- if(NOGENITALS in dna.species.species_traits)
- return FALSE
- if(!getorganslot("womb"))
- var/obj/item/organ/genital/womb/W = new
- W.Insert(src)
- if(W)
- W.update()
-
/datum/species/proc/genitals_layertext(layer)
switch(layer)
@@ -267,7 +199,7 @@
if(ishuman(user))
var/mob/living/carbon/human/H = user
H.update_genitals()
- ..()
+ . = ..()
/mob/living/carbon/human/doUnEquip(obj/item/I, force)
. = ..()
@@ -276,20 +208,16 @@
update_genitals()
/mob/living/carbon/human/proc/update_genitals()
- if(src && !QDELETED(src))
+ if(!QDELETED(src))
dna.species.handle_genitals(src)
/datum/species/proc/handle_genitals(mob/living/carbon/human/H)
if(!H)//no args
CRASH("H = null")
- if(!LAZYLEN(H.internal_organs))//if they have no organs, we're done
- return
- if(NOGENITALS in species_traits)//golems and such
- return
- if(HAS_TRAIT(H, TRAIT_HUSK))
+ if(!LAZYLEN(H.internal_organs) || (NOGENITALS in species_traits) || HAS_TRAIT(H, TRAIT_HUSK))
return
- var/list/genitals_to_add = list()
+ 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
@@ -300,18 +228,16 @@
//start scanning for genitals
//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(isgenital(O))
- var/obj/item/organ/genital/G = O
- if(G.hidden)
- return //we're gunna just hijack this for updates.
- if(G.is_exposed()) //Checks appropriate clothing slot and if it's through_clothes
- genitals_to_add += H.getorganslot(G.slot)
+ for(var/obj/item/organ/genital/G in H.internal_organs)
+ if(G.hidden)
+ return //we're gunna just hijack this for updates.
+ if(G.is_exposed()) //Checks appropriate clothing slot and if it's through_clothes
+ LAZYADD(genitals_to_add[G.layer_index], G)
//Now we added all genitals that aren't internal and should be rendered
//start applying overlays
for(var/layer in relevant_layers)
- var/layertext = genitals_layertext(layer)
+ var/layertext = flatten_list(genitals_layertext(layer))
for(var/obj/item/organ/genital/G in genitals_to_add)
var/datum/sprite_accessory/S
size = G.size
@@ -357,3 +283,4 @@
for(var/L in relevant_layers)
H.apply_overlay(L)
+
diff --git a/modular_citadel/code/modules/arousal/organs/breasts.dm b/modular_citadel/code/modules/arousal/organs/breasts.dm
index 1223f0b616..f6d5673a53 100644
--- a/modular_citadel/code/modules/arousal/organs/breasts.dm
+++ b/modular_citadel/code/modules/arousal/organs/breasts.dm
@@ -1,38 +1,20 @@
/obj/item/organ/genital/breasts
- name = "breasts"
- desc = "Female milk producing organs."
- icon_state = "breasts"
- icon = 'modular_citadel/icons/obj/genitals/breasts.dmi'
- zone = "chest"
- slot = "breasts"
- size = BREASTS_SIZE_DEF
- fluid_id = "milk"
- 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()
- . = ..()
- reagents.add_reagent(fluid_id, fluid_max_volume)
-
-/obj/item/organ/genital/breasts/on_life()
- if(QDELETED(src))
- return
- if(!reagents || !owner)
- return
- reagents.maximum_volume = fluid_max_volume
- if(fluid_id && producing)
- generate_milk()
-
-/obj/item/organ/genital/breasts/proc/generate_milk()
- if(owner.stat == DEAD)
- return FALSE
- reagents.isolate_reagent(fluid_id)
- reagents.add_reagent(fluid_id, (fluid_mult * fluid_rate))
+ name = "breasts"
+ desc = "Female milk producing organs."
+ icon_state = "breasts"
+ icon = 'modular_citadel/icons/obj/genitals/breasts.dmi'
+ zone = BODY_ZONE_CHEST
+ slot = ORGAN_SLOT_BREASTS
+ size = BREASTS_SIZE_DEF
+ fluid_id = "milk"
+ var/amount = 2
+ producing = TRUE
+ shape = "pair"
+ can_masturbate_with = TRUE
+ masturbation_verb = "massage"
+ orgasm_verb = "leaking"
+ can_climax = TRUE
+ fluid_transfer_factor = 0.5
/obj/item/organ/genital/breasts/update_appearance()
var/lowershape = lowertext(shape)
@@ -65,3 +47,13 @@
var/mob/living/carbon/human/H = owner
icon_state = sanitize_text(string)
H.update_genitals()
+
+/obj/item/organ/genital/breasts/get_features(mob/living/carbon/human/H)
+ var/datum/dna/D = H.dna
+ if(D.species.use_skintones && D.features["genitals_use_skintone"])
+ color = "#[skintone2hex(H.skin_tone)]"
+ else
+ color = "#[D.features["breasts_color"]]"
+ size = D.features["breasts_size"]
+ shape = D.features["breasts_shape"]
+ fluid_id = D.features["breasts_fluid"]
diff --git a/modular_citadel/code/modules/arousal/organs/eggsack.dm b/modular_citadel/code/modules/arousal/organs/eggsack.dm
index 402d246e40..3282bad5f9 100644
--- a/modular_citadel/code/modules/arousal/organs/eggsack.dm
+++ b/modular_citadel/code/modules/arousal/organs/eggsack.dm
@@ -1,14 +1,14 @@
/obj/item/organ/genital/eggsack
- name = "Egg sack"
- desc = "An egg producing reproductive organ."
- icon_state = "egg_sack"
- icon = 'modular_citadel/icons/obj/genitals/ovipositor.dmi'
- zone = "groin"
- slot = "testicles"
- color = null //don't use the /genital color since it already is colored
+ name = "Egg sack"
+ desc = "An egg producing reproductive organ."
+ icon_state = "egg_sack"
+ icon = 'modular_citadel/icons/obj/genitals/ovipositor.dmi'
+ zone = BODY_ZONE_PRECISE_GROIN
+ slot = ORGAN_SLOT_TESTICLES
+ linked_organ_slot = ORGAN_SLOT_PENIS
+ color = null //don't use the /genital color since it already is colored
internal = TRUE
var/egg_girth = EGG_GIRTH_DEF
var/cum_mult = CUM_RATE_MULT
var/cum_rate = CUM_RATE
var/cum_efficiency = CUM_EFFICIENCY
- var/obj/item/organ/ovipositor/linked_ovi
diff --git a/modular_citadel/code/modules/arousal/organs/genitals_sprite_accessories.dm b/modular_citadel/code/modules/arousal/organs/genitals_sprite_accessories.dm
index b913a90fb6..4ba2e373a2 100644
--- a/modular_citadel/code/modules/arousal/organs/genitals_sprite_accessories.dm
+++ b/modular_citadel/code/modules/arousal/organs/genitals_sprite_accessories.dm
@@ -5,11 +5,9 @@
//DICKS,COCKS,PENISES,WHATEVER YOU WANT TO CALL THEM
/datum/sprite_accessory/penis
icon = 'modular_citadel/icons/obj/genitals/penis_onmob.dmi'
- icon_state = null
name = "penis" //the preview name of the accessory
- gender_specific = 0 //Might be needed somewhere down the list.
color_src = "cock_color"
- locked = 0
+ alt_aroused = TRUE
/datum/sprite_accessory/penis/human
icon_state = "human"
@@ -62,27 +60,21 @@
icon_state = "testicle"
name = "testicle" //the preview name of the accessory
color_src = "balls_color"
- locked = 0
/datum/sprite_accessory/testicles/hidden
- icon_state = "hidden"
+ icon_state = "none"
name = "Hidden"
- alt_aroused = TRUE
/datum/sprite_accessory/testicles/single
icon_state = "single"
name = "Single"
- alt_aroused = TRUE
//Vaginas
/datum/sprite_accessory/vagina
icon = 'modular_citadel/icons/obj/genitals/vagina_onmob.dmi'
icon_state = null
name = "vagina"
- gender_specific = 0
color_src = "vag_color"
- locked = 0
- alt_aroused = FALSE //if this is TRUE, then the genitals will use an alternate sprite for aroused states
/datum/sprite_accessory/vagina/human
icon_state = "human"
@@ -114,35 +106,28 @@
//BREASTS BE HERE
/datum/sprite_accessory/breasts
icon = 'modular_citadel/icons/obj/genitals/breasts_onmob.dmi'
- icon_state = null
name = "breasts"
- gender_specific = 0
color_src = "breasts_color"
- locked = 0
+ alt_aroused = TRUE
/datum/sprite_accessory/breasts/pair
icon_state = "pair"
name = "Pair"
- alt_aroused = TRUE
/datum/sprite_accessory/breasts/quad
icon_state = "quad"
name = "Quad"
- alt_aroused = TRUE
/datum/sprite_accessory/breasts/sextuple
icon_state = "sextuple"
name = "Sextuple"
- alt_aroused = TRUE
//OVIPOSITORS BE HERE
/datum/sprite_accessory/ovipositor
icon = 'modular_citadel/icons/obj/genitals/penis_onmob.dmi'
icon_state = null
name = "Ovipositor" //the preview name of the accessory
- gender_specific = 0 //Might be needed somewhere down the list.
color_src = "cock_color"
- locked = 0
/datum/sprite_accessory/ovipositor/knotted
icon_state = "knotted"
diff --git a/modular_citadel/code/modules/arousal/organs/ovipositor.dm b/modular_citadel/code/modules/arousal/organs/ovipositor.dm
index 76bf60d93c..92b608f905 100644
--- a/modular_citadel/code/modules/arousal/organs/ovipositor.dm
+++ b/modular_citadel/code/modules/arousal/organs/ovipositor.dm
@@ -3,14 +3,13 @@
desc = "An egg laying reproductive organ."
icon_state = "ovi_knotted_2"
icon = 'modular_citadel/icons/obj/genitals/ovipositor.dmi'
- zone = "groin"
- slot = "penis"
- w_class = 3
+ zone = BODY_ZONE_PRECISE_GROIN
+ slot = ORGAN_SLOT_PENIS
shape = "knotted"
size = 3
+ layer_index = PENIS_LAYER_INDEX
var/length = 6 //inches
var/girth = 0
var/girth_ratio = COCK_GIRTH_RATIO_DEF //citadel_defines.dm for these defines
var/knot_girth_ratio = KNOT_GIRTH_RATIO_DEF
var/list/oviflags = list()
- var/obj/item/organ/eggsack/linked_eggsack
diff --git a/modular_citadel/code/modules/arousal/organs/penis.dm b/modular_citadel/code/modules/arousal/organs/penis.dm
index b6cb8fa4b2..2593460a32 100644
--- a/modular_citadel/code/modules/arousal/organs/penis.dm
+++ b/modular_citadel/code/modules/arousal/organs/penis.dm
@@ -1,22 +1,23 @@
/obj/item/organ/genital/penis
- name = "penis"
- desc = "A male reproductive organ."
- icon_state = "penis"
- icon = 'modular_citadel/icons/obj/genitals/penis.dmi'
- zone = "groin"
- slot = ORGAN_SLOT_PENIS
- 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", "barbed, knotted")
+ name = "penis"
+ desc = "A male reproductive organ."
+ icon_state = "penis"
+ icon = 'modular_citadel/icons/obj/genitals/penis.dmi'
+ zone = BODY_ZONE_PRECISE_GROIN
+ slot = ORGAN_SLOT_PENIS
+ 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.
+ layer_index = PENIS_LAYER_INDEX
+ 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", "barbed, knotted")
/obj/item/organ/genital/penis/update_size()
if(length == cached_length)
@@ -38,7 +39,7 @@
/obj/item/organ/genital/penis/update_appearance()
var/string
var/lowershape = lowertext(shape)
- desc = "You see [aroused_state ? "an erect" : "a flaccid"] [lowershape] penis. You estimate it's about [round(length, 0.25)] inch[round(length, 0.25) != 1 ? "es" : ""] long and [round(girth, 0.25)] inch[round(girth, 0.25) != 1 ? "es" : ""] in girth."
+ desc = "You see [aroused_state ? "an erect" : "a flaccid"] [lowershape] [name]. You estimate it's about [round(length, 0.25)] inch[round(length, 0.25) != 1 ? "es" : ""] long and [round(girth, 0.25)] inch[round(girth, 0.25) != 1 ? "es" : ""] in girth."
if(owner)
if(owner.dna.species.use_skintones && owner.dna.features["genitals_use_skintone"])
@@ -54,13 +55,12 @@
icon_state = sanitize_text(string)
H.update_genitals()
-/obj/item/organ/genital/penis/update_link()
- if(owner)
- linked_organ = (owner.getorganslot("testicles"))
- if(linked_organ)
- linked_organ.linked_organ = src
- linked_organ.size = size
+/obj/item/organ/genital/penis/get_features(mob/living/carbon/human/H)
+ var/datum/dna/D = H.dna
+ if(D.species.use_skintones && D.features["genitals_use_skintone"])
+ color = "#[skintone2hex(H.skin_tone)]"
else
- if(linked_organ)
- linked_organ.linked_organ = null
- linked_organ = null
+ color = "#[D.features["cock_color"]]"
+ length = D.features["cock_length"]
+ girth_ratio = D.features["cock_girth_ratio"]
+ shape = D.features["cock_shape"]
diff --git a/modular_citadel/code/modules/arousal/organs/testicles.dm b/modular_citadel/code/modules/arousal/organs/testicles.dm
index 1e6b4d62d4..de7b6d6f70 100644
--- a/modular_citadel/code/modules/arousal/organs/testicles.dm
+++ b/modular_citadel/code/modules/arousal/organs/testicles.dm
@@ -1,56 +1,34 @@
/obj/item/organ/genital/testicles
- name = "testicles"
- desc = "A male reproductive organ."
- icon_state = "testicles"
- icon = 'modular_citadel/icons/obj/genitals/testicles.dmi'
- zone = "groin"
- slot = "testicles"
- size = BALLS_SIZE_MIN
- var/size_name = "average"
- shape = "single"
- var/sack_size = BALLS_SACK_SIZE_DEF
- fluid_id = "semen"
- producing = TRUE
- can_masturbate_with = FALSE
- masturbation_verb = "massage"
- can_climax = TRUE
- var/sent_full_message = TRUE //defaults to 1 since they're full to start
+ name = "testicles"
+ desc = "A male reproductive organ."
+ icon_state = "testicles"
+ icon = 'modular_citadel/icons/obj/genitals/testicles.dmi'
+ zone = BODY_ZONE_PRECISE_GROIN
+ slot = ORGAN_SLOT_TESTICLES
+ size = BALLS_SIZE_MIN
+ linked_organ_slot = ORGAN_SLOT_PENIS
+ var/size_name = "average"
+ shape = "single"
+ var/sack_size = BALLS_SACK_SIZE_DEF
+ fluid_id = "semen"
+ producing = TRUE
+ can_masturbate_with = MASTURBATE_LINKED_ORGAN
+ masturbation_verb = "massage"
+ layer_index = TESTICLES_LAYER_INDEX
+ var/size_linked = FALSE
-/obj/item/organ/genital/testicles/Initialize()
+/obj/item/organ/genital/testicles/generate_fluid()
+ if(!linked_organ && !update_link())
+ return FALSE
. = ..()
- reagents.add_reagent(fluid_id, fluid_max_volume)
+ if(.)
+ send_full_message()
-/obj/item/organ/genital/testicles/on_life()
- if(QDELETED(src))
- return
- if(reagents && producing)
- generate_cum()
-
-/obj/item/organ/genital/testicles/proc/generate_cum()
- reagents.maximum_volume = fluid_max_volume
- if(reagents.total_volume >= reagents.maximum_volume)
- if(!sent_full_message)
- send_full_message()
- sent_full_message = TRUE
- return FALSE
- sent_full_message = FALSE
- update_link()
- 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_organ = (owner.getorganslot("penis"))
- if(linked_organ)
- linked_organ.linked_organ = src
- size = linked_organ.size
-
- else
- if(linked_organ)
- linked_organ.linked_organ = null
- linked_organ = null
+/obj/item/organ/genital/testicles/update_link(removing = FALSE)
+ . = ..()
+ if(. && !size_linked)
+ size = linked_organ.size
+ size_linked = TRUE
/obj/item/organ/genital/testicles/proc/send_full_message(msg = "Your balls finally feel full, again.")
if(owner && istext(msg))
@@ -59,19 +37,16 @@
/obj/item/organ/genital/testicles/update_appearance()
switch(size)
- if(0.1 to 1)
+ if(BALLS_SIZE_MIN)
size_name = "average"
- if(1.1 to 2)
+ if(BALLS_SIZE_DEF)
size_name = "enlarged"
- if(2.1 to INFINITY)
+ if(BALLS_SIZE_MAX)
size_name = "engorged"
else
size_name = "nonexistant"
- if(!internal)
- desc = "You see an [size_name] pair of testicles."
- else
- desc = "They don't have any testicles you can see."
+ desc = "You see an [size_name] pair of testicles."
if(owner)
var/string
@@ -87,3 +62,21 @@
var/mob/living/carbon/human/H = owner
icon_state = sanitize_text(string)
H.update_genitals()
+
+/obj/item/organ/genital/testicles/get_features(mob/living/carbon/human/H)
+ var/datum/dna/D = H.dna
+ if(D.species.use_skintones && D.features["genitals_use_skintone"])
+ 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")
+ internal = TRUE
+ else
+ internal = FALSE
+ fluid_id = D.features["balls_fluid"]
+ fluid_rate = D.features["balls_cum_rate"]
+ fluid_mult = D.features["balls_cum_mult"]
+ fluid_efficiency = D.features["balls_efficiency"]
diff --git a/modular_citadel/code/modules/arousal/organs/vagina.dm b/modular_citadel/code/modules/arousal/organs/vagina.dm
index 8c15aa5437..af2beb5f79 100644
--- a/modular_citadel/code/modules/arousal/organs/vagina.dm
+++ b/modular_citadel/code/modules/arousal/organs/vagina.dm
@@ -1,25 +1,24 @@
/obj/item/organ/genital/vagina
- name = "vagina"
- desc = "A female reproductive organ."
- icon = 'modular_citadel/icons/obj/genitals/vagina.dmi'
- icon_state = "vagina"
- zone = "groin"
- slot = "vagina"
- size = 1 //There is only 1 size right now
- can_masturbate_with = TRUE
- masturbation_verb = "finger"
- can_climax = TRUE
+ name = "vagina"
+ desc = "A female reproductive organ."
+ icon = 'modular_citadel/icons/obj/genitals/vagina.dmi'
+ icon_state = ORGAN_SLOT_VAGINA
+ zone = BODY_ZONE_PRECISE_GROIN
+ slot = "vagina"
+ size = 1 //There is only 1 size right now
+ 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
+ layer_index = VAGINA_LAYER_INDEX
+ var/cap_length = 8//D E P T H (cap = capacity)
+ var/cap_girth = 12
var/cap_girth_ratio = 1.5
- var/clits = 1
- var/clit_diam = 0.25
- var/clit_len = 0.25
+ var/clits = 1
+ var/clit_diam = 0.25
+ var/clit_len = 0.25
var/list/vag_types = list("tentacle", "dentata", "hairy", "spade", "furred")
-
/obj/item/organ/genital/vagina/update_appearance()
var/string //Keeping this code here, so making multiple sprites for the different kinds is easier.
var/lowershape = lowertext(shape)
@@ -63,12 +62,10 @@
icon_state = sanitize_text(string)
H.update_genitals()
-/obj/item/organ/genital/vagina/update_link()
- if(owner)
- linked_organ = (owner.getorganslot("womb"))
- if(linked_organ)
- linked_organ.linked_organ = src
+/obj/item/organ/genital/vagina/get_features(mob/living/carbon/human/H)
+ var/datum/dna/D = H.dna
+ if(D.species.use_skintones && D.features["genitals_use_skintone"])
+ color = "#[skintone2hex(H.skin_tone)]"
else
- if(linked_organ)
- linked_organ.linked_organ = null
- linked_organ = null
+ color = "[D.features["vag_color"]]"
+ shape = "[D.features["vag_shape"]]"
diff --git a/modular_citadel/code/modules/arousal/organs/womb.dm b/modular_citadel/code/modules/arousal/organs/womb.dm
index 686d9059a0..d99206d0f1 100644
--- a/modular_citadel/code/modules/arousal/organs/womb.dm
+++ b/modular_citadel/code/modules/arousal/organs/womb.dm
@@ -1,41 +1,11 @@
/obj/item/organ/genital/womb
- name = "womb"
- desc = "A female reproductive organ."
- icon = 'modular_citadel/icons/obj/genitals/vagina.dmi'
- icon_state = "womb"
- zone = "groin"
- slot = "womb"
- internal = TRUE
- fluid_id = "femcum"
- producing = TRUE
-
-/obj/item/organ/genital/womb/Initialize()
- . = ..()
- reagents.add_reagent(fluid_id, fluid_max_volume)
-
-/obj/item/organ/genital/womb/on_life()
- if(QDELETED(src))
- return
- if(reagents && producing)
- generate_femcum()
-
-/obj/item/organ/genital/womb/proc/generate_femcum()
- reagents.maximum_volume = fluid_max_volume
- update_link()
- 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_organ = (owner.getorganslot("vagina"))
- if(linked_organ)
- linked_organ.linked_organ = src
- else
- if(linked_organ)
- linked_organ.linked_organ = null
- linked_organ = null
-
-/obj/item/organ/genital/womb/Destroy()
- return ..()
+ name = "womb"
+ desc = "A female reproductive organ."
+ icon = 'modular_citadel/icons/obj/genitals/vagina.dmi'
+ icon_state = "womb"
+ zone = BODY_ZONE_PRECISE_GROIN
+ slot = ORGAN_SLOT_WOMB
+ internal = TRUE
+ fluid_id = "femcum"
+ producing = TRUE
+ linked_organ_slot = ORGAN_SLOT_VAGINA
\ No newline at end of file
diff --git a/modular_citadel/code/modules/client/preferences_savefile.dm b/modular_citadel/code/modules/client/preferences_savefile.dm
index 2921f70684..be9e92d4c3 100644
--- a/modular_citadel/code/modules/client/preferences_savefile.dm
+++ b/modular_citadel/code/modules/client/preferences_savefile.dm
@@ -32,7 +32,6 @@
WRITE_FILE(S["feature_ipc_antenna"], features["ipc_antenna"])
//Citadel
WRITE_FILE(S["feature_genitals_use_skintone"], features["genitals_use_skintone"])
- WRITE_FILE(S["feature_exhibitionist"], features["exhibitionist"])
WRITE_FILE(S["feature_mcolor2"], features["mcolor2"])
WRITE_FILE(S["feature_mcolor3"], features["mcolor3"])
WRITE_FILE(S["feature_mam_body_markings"], features["mam_body_markings"])
diff --git a/modular_citadel/code/modules/mob/living/carbon/human/life.dm b/modular_citadel/code/modules/mob/living/carbon/human/life.dm
index e728d70c97..fbaa1b4b58 100644
--- a/modular_citadel/code/modules/mob/living/carbon/human/life.dm
+++ b/modular_citadel/code/modules/mob/living/carbon/human/life.dm
@@ -1,7 +1,7 @@
-/mob/living/carbon/human/Life()
+/mob/living/carbon/human/Life(seconds, times_fired)
//citadel code
if(stat != DEAD)
- handle_arousal()
+ handle_arousal(times_fired)
. = ..()
/mob/living/carbon/human/calculate_affecting_pressure(pressure)
diff --git a/modular_citadel/code/modules/mob/living/carbon/human/species_types/jellypeople.dm b/modular_citadel/code/modules/mob/living/carbon/human/species_types/jellypeople.dm
index fa6ded8065..0e6c12053a 100644
--- a/modular_citadel/code/modules/mob/living/carbon/human/species_types/jellypeople.dm
+++ b/modular_citadel/code/modules/mob/living/carbon/human/species_types/jellypeople.dm
@@ -78,42 +78,30 @@
else
H.facial_hair_style = "Shaved"
//handle normal hair
- var/new_style = input(owner, "Select a hair style", "Hair Alterations") as null|anything in GLOB.hair_styles_list
+ var/new_style = input(owner, "Select a hair style", "Hair Alterations") as null|anything in GLOB.hair_styles_list
if(new_style)
H.hair_style = new_style
H.update_hair()
else if (select_alteration == "Genitals")
- var/list/organs = list()
var/operation = input("Select organ operation.", "Organ Manipulation", "cancel") in list("add sexual organ", "remove sexual organ", "cancel")
switch(operation)
if("add sexual organ")
- var/new_organ = input("Select sexual organ:", "Organ Manipulation") in list("Penis", "Testicles", "Breasts", "Vagina", "Womb", "Cancel")
- if(new_organ == "Penis")
- H.give_penis()
- else if(new_organ == "Testicles")
- H.give_balls()
- else if(new_organ == "Breasts")
- H.give_breasts()
- else if(new_organ == "Vagina")
- H.give_vagina()
- else if(new_organ == "Womb")
- H.give_womb()
- else
+ var/new_organ = input("Select sexual organ:", "Organ Manipulation") as null|anything in GLOB.genitals_list
+ if(!new_organ)
return
+ H.give_genital(GLOB.genitals_list[new_organ])
+
if("remove sexual organ")
+ var/list/organs = list()
for(var/obj/item/organ/genital/X in H.internal_organs)
var/obj/item/organ/I = X
organs["[I.name] ([I.type])"] = I
- var/obj/item/organ = input("Select sexual organ:", "Organ Manipulation", null) in organs
- organ = organs[organ]
- if(!organ)
+ var/obj/item/O = input("Select sexual organ:", "Organ Manipulation", null) as null|anything in organs
+ var/obj/item/organ/genital/G = organs[O]
+ if(!G)
return
- var/obj/item/organ/genital/O
- if(isorgan(organ))
- O = organ
- O.Remove(H)
- organ.forceMove(get_turf(H))
- qdel(organ)
+ G.forceMove(get_turf(H))
+ qdel(G)
H.update_genitals()
else if (select_alteration == "Ears")
@@ -203,8 +191,8 @@
if(new_shape)
H.dna.features["cock_shape"] = new_shape
H.update_genitals()
- H.give_balls()
- H.give_penis()
+ H.give_genital(/obj/item/organ/genital/testicles)
+ H.give_genital(/obj/item/organ/genital/penis)
H.apply_overlay()
@@ -216,8 +204,8 @@
if(new_shape)
H.dna.features["vag_shape"] = new_shape
H.update_genitals()
- H.give_womb()
- H.give_vagina()
+ H.give_genital(/obj/item/organ/genital/womb)
+ H.give_genital(/obj/item/organ/genital/vagina)
H.apply_overlay()
else if (select_alteration == "Penis Length")
@@ -229,8 +217,8 @@
H.dna.features["cock_length"] = max(min( round(text2num(new_length)), COCK_SIZE_MAX),COCK_SIZE_MIN)
H.update_genitals()
H.apply_overlay()
- H.give_balls()
- H.give_penis()
+ H.give_genital(/obj/item/organ/genital/testicles)
+ H.give_genital(/obj/item/organ/genital/penis)
else if (select_alteration == "Breast Size")
for(var/obj/item/organ/genital/breasts/X in H.internal_organs)
@@ -241,7 +229,7 @@
H.dna.features["breasts_size"] = new_size
H.update_genitals()
H.apply_overlay()
- H.give_breasts()
+ H.give_genital(/obj/item/organ/genital/breasts)
else if (select_alteration == "Breast Shape")
for(var/obj/item/organ/genital/breasts/X in H.internal_organs)
@@ -252,7 +240,7 @@
H.dna.features["breasts_shape"] = new_shape
H.update_genitals()
H.apply_overlay()
- H.give_breasts()
+ H.give_genital(/obj/item/organ/genital/breasts)
else
return
diff --git a/modular_citadel/icons/obj/genitals/testicles_onmob.dmi b/modular_citadel/icons/obj/genitals/testicles_onmob.dmi
index 33659cf13c..7d4d2610c5 100644
Binary files a/modular_citadel/icons/obj/genitals/testicles_onmob.dmi and b/modular_citadel/icons/obj/genitals/testicles_onmob.dmi differ
diff --git a/tgstation.dme b/tgstation.dme
index 722ebae9be..7c9fa1e899 100755
--- a/tgstation.dme
+++ b/tgstation.dme
@@ -2844,7 +2844,6 @@
#include "modular_citadel\code\datums\mood_events\moodular.dm"
#include "modular_citadel\code\datums\mutations\hulk.dm"
#include "modular_citadel\code\datums\status_effects\debuffs.dm"
-#include "modular_citadel\code\datums\traits\neutral.dm"
#include "modular_citadel\code\datums\wires\airlock.dm"
#include "modular_citadel\code\datums\wires\autoylathe.dm"
#include "modular_citadel\code\game\area\cit_areas.dm"
@@ -2912,9 +2911,9 @@
#include "modular_citadel\code\modules\admin\secrets.dm"
#include "modular_citadel\code\modules\admin\topic.dm"
#include "modular_citadel\code\modules\arousal\arousal.dm"
+#include "modular_citadel\code\modules\arousal\organs\_genitals.dm"
#include "modular_citadel\code\modules\arousal\organs\breasts.dm"
#include "modular_citadel\code\modules\arousal\organs\eggsack.dm"
-#include "modular_citadel\code\modules\arousal\organs\genitals.dm"
#include "modular_citadel\code\modules\arousal\organs\genitals_sprite_accessories.dm"
#include "modular_citadel\code\modules\arousal\organs\ovipositor.dm"
#include "modular_citadel\code\modules\arousal\organs\penis.dm"
|