Merge pull request #12142 from Ghommie/Ghommie-cit728
Demodularizing the arousal module.
This commit is contained in:
@@ -0,0 +1,282 @@
|
||||
/mob/living
|
||||
var/mb_cd_length = 5 SECONDS //5 second cooldown for masturbating because fuck spam.
|
||||
var/mb_cd_timer = 0 //The timer itself
|
||||
|
||||
/mob/living/carbon/human
|
||||
var/saved_underwear = ""//saves their underwear so it can be toggled later
|
||||
var/saved_undershirt = ""
|
||||
var/saved_socks = ""
|
||||
var/hidden_underwear = FALSE
|
||||
var/hidden_undershirt = FALSE
|
||||
var/hidden_socks = FALSE
|
||||
|
||||
//Mob procs
|
||||
/mob/living/carbon/human/proc/underwear_toggle()
|
||||
set name = "Toggle undergarments"
|
||||
set category = "IC"
|
||||
|
||||
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 == "Bottom")
|
||||
hidden_underwear = !hidden_underwear
|
||||
|
||||
if(confirm == "Socks")
|
||||
hidden_socks = !hidden_socks
|
||||
|
||||
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
|
||||
|
||||
update_body(TRUE)
|
||||
|
||||
|
||||
/mob/living/carbon/human/proc/adjust_arousal(strength,aphro = FALSE,maso = FALSE) // returns all genitals that were adjust
|
||||
var/list/obj/item/organ/genital/genit_list = list()
|
||||
if(!client?.prefs.arousable || (aphro && (client?.prefs.cit_toggles & NO_APHRO)) || (maso && !HAS_TRAIT(src, TRAIT_MASO)))
|
||||
return // no adjusting made here
|
||||
for(var/obj/item/organ/genital/G in internal_organs)
|
||||
if(G.genital_flags & GENITAL_CAN_AROUSE && !G.aroused_state && prob(strength*G.sensitivity))
|
||||
G.set_aroused_state(strength > 0)
|
||||
G.update_appearance()
|
||||
if(G.aroused_state)
|
||||
genit_list += G
|
||||
return genit_list
|
||||
|
||||
/obj/item/organ/genital/proc/climaxable(mob/living/carbon/human/H, silent = FALSE) //returns the fluid source (ergo reagents holder) if found.
|
||||
if(CHECK_BITFIELD(genital_flags, GENITAL_FUID_PRODUCTION))
|
||||
. = reagents
|
||||
else
|
||||
if(linked_organ)
|
||||
. = linked_organ.reagents
|
||||
if(!. && !silent)
|
||||
to_chat(H, "<span class='warning'>Your [name] is unable to produce it's own fluids, it's missing the organs for it.</span>")
|
||||
|
||||
/mob/living/carbon/human/proc/do_climax(datum/reagents/R, atom/target, obj/item/organ/genital/G, spill = TRUE)
|
||||
if(!G)
|
||||
return
|
||||
if(!target || !R)
|
||||
return
|
||||
var/turfing = isturf(target)
|
||||
G.generate_fluid()
|
||||
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))
|
||||
G.time_since_last_orgasm = 0
|
||||
R.clear_reagents()
|
||||
|
||||
/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/datum/reagents/fluid_source = G.climaxable(src, TRUE)
|
||||
if(!fluid_source)
|
||||
to_chat(src,"<span class='userdanger'>Your [G.name] cannot cum.</span>")
|
||||
return
|
||||
if(mb_time) //as long as it's not instant, give a warning
|
||||
to_chat(src,"<span class='userlove'>You feel yourself about to orgasm.</span>")
|
||||
if(!do_after(src, mb_time, target = src) || !G.climaxable(src, TRUE))
|
||||
return
|
||||
to_chat(src,"<span class='userlove'>You climax[isturf(loc) ? " onto [loc]" : ""] with your [G.name].</span>")
|
||||
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/datum/reagents/fluid_source = G.climaxable(src)
|
||||
if(!fluid_source)
|
||||
return
|
||||
if(mb_time) //Skip warning if this is an instant climax.
|
||||
to_chat(src,"<span class='userlove'>You're about to climax with [L]!</span>")
|
||||
to_chat(L,"<span class='userlove'>[src] is about to climax with you!</span>")
|
||||
if(!do_after(src, mb_time, target = src) || !in_range(src, L) || !G.climaxable(src, TRUE))
|
||||
return
|
||||
if(spillage)
|
||||
to_chat(src,"<span class='userlove'>You orgasm with [L], spilling out of them, using your [G.name].</span>")
|
||||
to_chat(L,"<span class='userlove'>[src] climaxes with you, overflowing and spilling, using [p_their()] [G.name]!</span>")
|
||||
else //knots and other non-spilling orgasms
|
||||
to_chat(src,"<span class='userlove'>You climax with [L], your [G.name] spilling nothing.</span>")
|
||||
to_chat(L,"<span class='userlove'>[src] climaxes with you, [p_their()] [G.name] spilling nothing!</span>")
|
||||
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/datum/reagents/fluid_source = G.climaxable(src)
|
||||
if(!fluid_source)
|
||||
return
|
||||
if(mb_time)
|
||||
to_chat(src,"<span class='userlove'>You start to [G.masturbation_verb] your [G.name] over [container].</span>")
|
||||
if(!do_after(src, mb_time, target = src) || !in_range(src, container) || !G.climaxable(src, TRUE))
|
||||
return
|
||||
to_chat(src,"<span class='userlove'>You used your [G.name] to fill [container].</span>")
|
||||
do_climax(fluid_source, container, G, FALSE)
|
||||
|
||||
/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
|
||||
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, "<span class='warning'>You cannot climax without available genitals.</span>")
|
||||
|
||||
/mob/living/carbon/human/proc/pick_partner(silent = FALSE)
|
||||
var/list/partners = list()
|
||||
if(pulling)
|
||||
partners += pulling
|
||||
if(pulledby)
|
||||
partners += pulledby
|
||||
//Now we got both of them, let's check if they're proper
|
||||
for(var/mob/living/L in partners)
|
||||
if(!L.client || !L.mind) // can't consent, not a partner
|
||||
partners -= L
|
||||
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)
|
||||
if(!silent)
|
||||
to_chat(src, "<span class='warning'>You cannot do this alone.</span>")
|
||||
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))
|
||||
to_chat(src,"<span class='notice'>Waiting for consent...</span>")
|
||||
var/consenting = input(target, "Do you want [src] to climax with you?","Climax mechanics","No") in list("Yes","No")
|
||||
if(consenting == "Yes")
|
||||
return target
|
||||
else
|
||||
message_admins("[src] tried to climax with [target], but [target] did not consent.")
|
||||
log_consent("[src] tried to climax with [target], but [target] did not consent.")
|
||||
|
||||
/mob/living/carbon/human/proc/pick_climax_container(silent = FALSE)
|
||||
var/list/containers_list = list()
|
||||
|
||||
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 && CanReach(SC))
|
||||
return SC
|
||||
else if(!silent)
|
||||
to_chat(src, "<span class='warning'>You cannot do this without an appropriate container.</span>")
|
||||
|
||||
/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, "<span class='warning'>You can't do that while restrained!</span>")
|
||||
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, "<span class='warning'>You need at least one free arm.</span>")
|
||||
return FALSE
|
||||
return TRUE
|
||||
|
||||
//Here's the main proc itself
|
||||
/mob/living/carbon/human/proc/mob_climax(forced_climax=FALSE) //Forced is instead of the other proc, makes you cum if you have the tools for it, ignoring restraints
|
||||
if(mb_cd_timer > world.time)
|
||||
if(!forced_climax) //Don't spam the message to the victim if forced to come too fast
|
||||
to_chat(src, "<span class='warning'>You need to wait [DisplayTimeText((mb_cd_timer - world.time), TRUE)] before you can do that again!</span>")
|
||||
return
|
||||
|
||||
if(!client?.prefs.arousable || !has_dna())
|
||||
return
|
||||
if(stat == DEAD)
|
||||
if(!forced_climax)
|
||||
to_chat(src, "<span class='warning'>You can't do that while dead!</span>")
|
||||
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
|
||||
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, "<span class='warning'>You must be conscious to do that!</span>")
|
||||
return
|
||||
|
||||
//Ok, now we check what they want to do.
|
||||
var/choice = input(src, "Select sexual activity", "Sexual activity:") as null|anything in list("Climax alone","Climax with partner", "Fill container")
|
||||
if(!choice)
|
||||
return
|
||||
|
||||
switch(choice)
|
||||
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("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
|
||||
|
||||
/mob/living/carbon/human/verb/climax_verb()
|
||||
set category = "IC"
|
||||
set name = "Climax"
|
||||
set desc = "Lets you choose a couple ways to ejaculate."
|
||||
mob_climax()
|
||||
@@ -0,0 +1,374 @@
|
||||
/obj/item/organ/genital
|
||||
color = "#fcccb3"
|
||||
w_class = WEIGHT_CLASS_SMALL
|
||||
organ_flags = ORGAN_NO_DISMEMBERMENT
|
||||
var/shape
|
||||
var/sensitivity = 1 // wow if this were ever used that'd be cool but it's not but i'm keeping it for my unshit code
|
||||
var/genital_flags //see citadel_defines.dm
|
||||
var/masturbation_verb = "masturbate"
|
||||
var/orgasm_verb = "cumming" //present continous
|
||||
var/arousal_verb = "You feel aroused"
|
||||
var/unarousal_verb = "You no longer feel aroused"
|
||||
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/datum/reagent/fluid_id = null
|
||||
var/fluid_max_volume = 50
|
||||
var/fluid_efficiency = 1
|
||||
var/fluid_rate = CUM_RATE
|
||||
var/fluid_mult = 1
|
||||
var/time_since_last_orgasm = 500
|
||||
var/aroused_state = FALSE //Boolean used in icon_state strings
|
||||
var/obj/item/organ/genital/linked_organ
|
||||
var/linked_organ_slot //used for linking an apparatus' organ to its other half on update_link().
|
||||
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(mapload, do_update = TRUE)
|
||||
. = ..()
|
||||
if(fluid_id)
|
||||
create_reagents(fluid_max_volume, NONE, NO_REAGENTS_VALUE)
|
||||
if(CHECK_BITFIELD(genital_flags, GENITAL_FUID_PRODUCTION))
|
||||
reagents.add_reagent(fluid_id, fluid_max_volume)
|
||||
if(do_update)
|
||||
update()
|
||||
|
||||
/obj/item/organ/genital/proc/set_aroused_state(new_state)
|
||||
if(!(genital_flags & GENITAL_CAN_AROUSE))
|
||||
return FALSE
|
||||
if(!((HAS_TRAIT(owner,TRAIT_PERMABONER) && !new_state) || HAS_TRAIT(owner,TRAIT_NEVERBONER) && new_state))
|
||||
aroused_state = new_state
|
||||
return aroused_state
|
||||
|
||||
/obj/item/organ/genital/proc/update()
|
||||
if(QDELETED(src))
|
||||
return
|
||||
update_size()
|
||||
update_appearance()
|
||||
if(genital_flags & UPDATE_OWNER_APPEARANCE && owner && ishuman(owner))
|
||||
var/mob/living/carbon/human/H = owner
|
||||
H.update_genitals()
|
||||
if(linked_organ_slot || (linked_organ && !owner))
|
||||
update_link()
|
||||
|
||||
//exposure and through-clothing code
|
||||
/mob/living/carbon
|
||||
var/list/exposed_genitals = list() //Keeping track of them so we don't have to iterate through every genitalia and see if exposed
|
||||
|
||||
/obj/item/organ/genital/proc/is_exposed()
|
||||
if(!owner || genital_flags & (GENITAL_INTERNAL|GENITAL_HIDDEN))
|
||||
return FALSE
|
||||
if(genital_flags & GENITAL_UNDIES_HIDDEN && ishuman(owner))
|
||||
var/mob/living/carbon/human/H = owner
|
||||
if(!(NO_UNDERWEAR in H.dna.species.species_traits))
|
||||
var/datum/sprite_accessory/underwear/top/T = H.hidden_undershirt ? null : GLOB.undershirt_list[H.undershirt]
|
||||
var/datum/sprite_accessory/underwear/bottom/B = H.hidden_underwear ? null : GLOB.underwear_list[H.underwear]
|
||||
if(zone == BODY_ZONE_CHEST ? (T?.covers_chest || B?.covers_chest) : (T?.covers_groin || B?.covers_groin))
|
||||
return FALSE
|
||||
if(genital_flags & GENITAL_THROUGH_CLOTHES)
|
||||
return TRUE
|
||||
|
||||
switch(zone) //update as more genitals are added
|
||||
if(BODY_ZONE_CHEST)
|
||||
return owner.is_chest_exposed()
|
||||
if(BODY_ZONE_PRECISE_GROIN)
|
||||
return owner.is_groin_exposed()
|
||||
|
||||
/obj/item/organ/genital/proc/toggle_visibility(visibility, update = TRUE)
|
||||
genital_flags &= ~(GENITAL_THROUGH_CLOTHES|GENITAL_HIDDEN|GENITAL_UNDIES_HIDDEN)
|
||||
if(owner)
|
||||
owner.exposed_genitals -= src
|
||||
switch(visibility)
|
||||
if(GEN_VISIBLE_ALWAYS)
|
||||
genital_flags |= GENITAL_THROUGH_CLOTHES
|
||||
if(owner)
|
||||
owner.exposed_genitals += src
|
||||
if(GEN_VISIBLE_NO_UNDIES)
|
||||
genital_flags |= GENITAL_UNDIES_HIDDEN
|
||||
if(GEN_VISIBLE_NEVER)
|
||||
genital_flags |= GENITAL_HIDDEN
|
||||
|
||||
if(update && owner && ishuman(owner)) //recast to use update genitals proc
|
||||
var/mob/living/carbon/human/H = owner
|
||||
H.update_genitals()
|
||||
|
||||
/mob/living/carbon/verb/toggle_genitals()
|
||||
set category = "IC"
|
||||
set name = "Expose/Hide genitals"
|
||||
set desc = "Allows you to toggle which genitals should show through clothes or not."
|
||||
|
||||
if(stat != CONSCIOUS)
|
||||
to_chat(usr, "<span class='warning'>You can toggle genitals visibility right now...</span>")
|
||||
return
|
||||
|
||||
var/list/genital_list = list()
|
||||
for(var/obj/item/organ/genital/G in internal_organs)
|
||||
if(!CHECK_BITFIELD(G.genital_flags, GENITAL_INTERNAL))
|
||||
genital_list += G
|
||||
if(!genital_list.len) //There is nothing to expose
|
||||
return
|
||||
//Full list of exposable genitals created
|
||||
var/obj/item/organ/genital/picked_organ
|
||||
picked_organ = input(src, "Choose which genitalia to expose/hide", "Expose/Hide genitals") as null|anything in genital_list
|
||||
if(picked_organ && (picked_organ in internal_organs))
|
||||
var/picked_visibility = input(src, "Choose visibility setting", "Expose/Hide genitals") as null|anything in GLOB.genitals_visibility_toggles
|
||||
if(picked_visibility && picked_organ && (picked_organ in internal_organs))
|
||||
picked_organ.toggle_visibility(picked_visibility)
|
||||
return
|
||||
|
||||
/mob/living/carbon/verb/toggle_arousal_state()
|
||||
set category = "IC"
|
||||
set name = "Toggle genital arousal"
|
||||
set desc = "Allows you to toggle which genitals are showing signs of arousal."
|
||||
var/list/genital_list = list()
|
||||
for(var/obj/item/organ/genital/G in internal_organs)
|
||||
if(G.genital_flags & GENITAL_CAN_AROUSE)
|
||||
genital_list += G
|
||||
if(!genital_list.len) //There's nothing that can show arousal
|
||||
return
|
||||
var/obj/item/organ/genital/picked_organ
|
||||
picked_organ = input(src, "Choose which genitalia to toggle arousal on", "Set genital arousal", null) in genital_list
|
||||
if(picked_organ)
|
||||
var/original_state = picked_organ.aroused_state
|
||||
picked_organ.set_aroused_state(!picked_organ.aroused_state)
|
||||
if(original_state != picked_organ.aroused_state)
|
||||
to_chat(src,"<span class='userlove'>[picked_organ.aroused_state ? picked_organ.arousal_verb : picked_organ.unarousal_verb].</span>")
|
||||
else
|
||||
to_chat(src,"<span class='userlove'>You can't make that genital [picked_organ.aroused_state ? "unaroused" : "aroused"]!</span>")
|
||||
picked_organ.update_appearance()
|
||||
return
|
||||
|
||||
|
||||
/obj/item/organ/genital/proc/modify_size(modifier, min = -INFINITY, max = INFINITY)
|
||||
fluid_max_volume += modifier*2.5
|
||||
fluid_rate += modifier/10
|
||||
if(reagents)
|
||||
reagents.maximum_volume = fluid_max_volume
|
||||
return
|
||||
|
||||
/obj/item/organ/genital/proc/update_size()
|
||||
return
|
||||
|
||||
/obj/item/organ/genital/proc/update_appearance()
|
||||
if(!owner || owner.stat == DEAD)
|
||||
aroused_state = FALSE
|
||||
|
||||
/obj/item/organ/genital/on_life()
|
||||
. = ..()
|
||||
if(!reagents || !.)
|
||||
return
|
||||
reagents.maximum_volume = fluid_max_volume
|
||||
if(fluid_id && CHECK_BITFIELD(genital_flags, GENITAL_FUID_PRODUCTION))
|
||||
time_since_last_orgasm++
|
||||
|
||||
/obj/item/organ/genital/proc/generate_fluid()
|
||||
var/amount = clamp(fluid_rate * time_since_last_orgasm * fluid_mult,0,fluid_max_volume)
|
||||
reagents.clear_reagents()
|
||||
reagents.add_reagent(fluid_id,amount)
|
||||
return TRUE
|
||||
|
||||
/obj/item/organ/genital/proc/update_link()
|
||||
if(owner)
|
||||
if(linked_organ)
|
||||
return FALSE
|
||||
linked_organ = owner.getorganslot(linked_organ_slot)
|
||||
if(linked_organ)
|
||||
linked_organ.linked_organ = src
|
||||
linked_organ.upon_link()
|
||||
upon_link()
|
||||
return TRUE
|
||||
if(linked_organ)
|
||||
linked_organ.linked_organ = null
|
||||
linked_organ = null
|
||||
return FALSE
|
||||
|
||||
//post organ duo making arrangements.
|
||||
/obj/item/organ/genital/proc/upon_link()
|
||||
return
|
||||
|
||||
/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)
|
||||
if(genital_flags & GENITAL_THROUGH_CLOTHES)
|
||||
owner.exposed_genitals += src
|
||||
|
||||
/obj/item/organ/genital/Remove(special = FALSE)
|
||||
. = ..()
|
||||
var/mob/living/carbon/C = .
|
||||
update()
|
||||
if(!QDELETED(C))
|
||||
if(genital_flags & UPDATE_OWNER_APPEARANCE && ishuman(C))
|
||||
var/mob/living/carbon/human/H = .
|
||||
H.update_genitals()
|
||||
C.exposed_genitals -= src
|
||||
UnregisterSignal(C, COMSIG_MOB_DEATH)
|
||||
|
||||
//proc to give a player their genitals and stuff when they log in
|
||||
/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)
|
||||
for(var/obj/item/organ/genital/G in internal_organs)
|
||||
qdel(G)
|
||||
if (NOGENITALS in dna.species.species_traits)
|
||||
return
|
||||
if(dna.features["has_vag"])
|
||||
give_genital(/obj/item/organ/genital/vagina)
|
||||
if(dna.features["has_womb"])
|
||||
give_genital(/obj/item/organ/genital/womb)
|
||||
if(dna.features["has_balls"])
|
||||
give_genital(/obj/item/organ/genital/testicles)
|
||||
if(dna.features["has_breasts"])
|
||||
give_genital(/obj/item/organ/genital/breasts)
|
||||
if(dna.features["has_cock"])
|
||||
give_genital(/obj/item/organ/genital/penis)
|
||||
|
||||
/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 G(null, FALSE)
|
||||
G.get_features(src)
|
||||
G.Insert(src)
|
||||
return G
|
||||
|
||||
/obj/item/organ/genital/proc/get_features(mob/living/carbon/human/H)
|
||||
return
|
||||
|
||||
|
||||
//procs to handle sprite overlays being applied to humans
|
||||
|
||||
/mob/living/carbon/human/equip_to_slot(obj/item/I, slot)
|
||||
. = ..()
|
||||
if(!. && I && slot && !(slot in GLOB.no_genitals_update_slots)) //the item was successfully equipped, and the chosen slot wasn't merely storage, hands or cuffs.
|
||||
update_genitals()
|
||||
|
||||
/mob/living/carbon/human/doUnEquip(obj/item/I, force, newloc, no_move, invdrop = TRUE)
|
||||
var/no_update = FALSE
|
||||
if(!I || I == l_store || I == r_store || I == s_store || I == handcuffed || I == legcuffed || get_held_index_of_item(I)) //stops storages, cuffs and held items from triggering it.
|
||||
no_update = TRUE
|
||||
. = ..()
|
||||
if(!. || no_update)
|
||||
return
|
||||
update_genitals()
|
||||
|
||||
/mob/living/carbon/human/proc/update_genitals()
|
||||
if(QDELETED(src))
|
||||
return
|
||||
var/static/list/relevant_layers = list("[GENITALS_BEHIND_LAYER]" = "BEHIND", "[GENITALS_FRONT_LAYER]" = "FRONT")
|
||||
var/static/list/layers_num
|
||||
if(!layers_num)
|
||||
for(var/L in relevant_layers)
|
||||
LAZYSET(layers_num, L, text2num(L))
|
||||
for(var/L in relevant_layers) //Less hardcode
|
||||
remove_overlay(layers_num[L])
|
||||
remove_overlay(GENITALS_EXPOSED_LAYER)
|
||||
if(!LAZYLEN(internal_organs) || ((NOGENITALS in dna.species.species_traits) && !genital_override) || HAS_TRAIT(src, TRAIT_HUSK))
|
||||
return
|
||||
|
||||
//start scanning for genitals
|
||||
|
||||
var/list/gen_index[GENITAL_LAYER_INDEX_LENGTH]
|
||||
var/list/genitals_to_add
|
||||
var/list/fully_exposed
|
||||
for(var/obj/item/organ/genital/G in internal_organs)
|
||||
if(G.is_exposed()) //Checks appropriate clothing slot and if it's through_clothes
|
||||
LAZYADD(gen_index[G.layer_index], G)
|
||||
for(var/L in gen_index)
|
||||
if(L) //skip nulls
|
||||
LAZYADD(genitals_to_add, L)
|
||||
if(!genitals_to_add)
|
||||
return
|
||||
//Now we added all genitals that aren't internal and should be rendered
|
||||
//start applying overlays
|
||||
for(var/layer in relevant_layers)
|
||||
var/list/standing = list()
|
||||
var/layertext = relevant_layers[layer]
|
||||
for(var/A in genitals_to_add)
|
||||
var/obj/item/organ/genital/G = A
|
||||
var/datum/sprite_accessory/S
|
||||
var/size = G.size
|
||||
switch(G.type)
|
||||
if(/obj/item/organ/genital/penis)
|
||||
S = GLOB.cock_shapes_list[G.shape]
|
||||
if(/obj/item/organ/genital/testicles)
|
||||
S = GLOB.balls_shapes_list[G.shape]
|
||||
if(/obj/item/organ/genital/vagina)
|
||||
S = GLOB.vagina_shapes_list[G.shape]
|
||||
if(/obj/item/organ/genital/breasts)
|
||||
S = GLOB.breasts_shapes_list[G.shape]
|
||||
|
||||
if(!S || S.icon_state == "none")
|
||||
continue
|
||||
var/aroused_state = G.aroused_state && S.alt_aroused
|
||||
var/accessory_icon = S.icon
|
||||
var/do_center = S.center
|
||||
var/dim_x = S.dimension_x
|
||||
var/dim_y = S.dimension_y
|
||||
if(G.genital_flags & GENITAL_CAN_TAUR && S.taur_icon && (!S.feat_taur || dna.features[S.feat_taur]) && dna.species.mutant_bodyparts["taur"])
|
||||
var/datum/sprite_accessory/taur/T = GLOB.taur_list[dna.features["taur"]]
|
||||
if(T?.taur_mode & S.accepted_taurs)
|
||||
accessory_icon = S.taur_icon
|
||||
do_center = TRUE
|
||||
dim_x = S.taur_dimension_x
|
||||
dim_y = S.taur_dimension_y
|
||||
|
||||
var/mutable_appearance/genital_overlay = mutable_appearance(accessory_icon, layer = -layer)
|
||||
if(do_center)
|
||||
genital_overlay = center_image(genital_overlay, dim_x, dim_y)
|
||||
|
||||
if(dna.species.use_skintones && dna.features["genitals_use_skintone"])
|
||||
genital_overlay.color = SKINTONE2HEX(skin_tone)
|
||||
else
|
||||
switch(S.color_src)
|
||||
if("cock_color")
|
||||
genital_overlay.color = "#[dna.features["cock_color"]]"
|
||||
if("balls_color")
|
||||
genital_overlay.color = "#[dna.features["balls_color"]]"
|
||||
if("breasts_color")
|
||||
genital_overlay.color = "#[dna.features["breasts_color"]]"
|
||||
if("vag_color")
|
||||
genital_overlay.color = "#[dna.features["vag_color"]]"
|
||||
|
||||
genital_overlay.icon_state = "[G.slot]_[S.icon_state]_[size][(dna.species.use_skintones && !dna.skin_tone_override) ? "_s" : ""]_[aroused_state]_[layertext]"
|
||||
|
||||
if(layers_num[layer] == GENITALS_FRONT_LAYER && G.genital_flags & GENITAL_THROUGH_CLOTHES)
|
||||
genital_overlay.layer = -GENITALS_EXPOSED_LAYER
|
||||
LAZYADD(fully_exposed, genital_overlay)
|
||||
else
|
||||
genital_overlay.layer = -layers_num[layer]
|
||||
standing += genital_overlay
|
||||
|
||||
if(LAZYLEN(standing))
|
||||
overlays_standing[layers_num[layer]] = standing
|
||||
|
||||
if(LAZYLEN(fully_exposed))
|
||||
overlays_standing[GENITALS_EXPOSED_LAYER] = fully_exposed
|
||||
apply_overlay(GENITALS_EXPOSED_LAYER)
|
||||
|
||||
for(var/L in relevant_layers)
|
||||
apply_overlay(layers_num[L])
|
||||
|
||||
|
||||
//Checks to see if organs are new on the mob, and changes their colours so that they don't get crazy colours.
|
||||
/mob/living/carbon/human/proc/emergent_genital_call()
|
||||
if(!client.prefs.arousable)
|
||||
return FALSE
|
||||
|
||||
var/organCheck = locate(/obj/item/organ/genital) in internal_organs
|
||||
var/breastCheck = getorganslot(ORGAN_SLOT_BREASTS)
|
||||
var/willyCheck = getorganslot(ORGAN_SLOT_PENIS)
|
||||
|
||||
if(organCheck == FALSE)
|
||||
if(ishuman(src) && dna.species.use_skintones)
|
||||
dna.features["genitals_use_skintone"] = TRUE
|
||||
if(src.dna.species.fixed_mut_color)
|
||||
dna.features["cock_color"] = "[dna.species.fixed_mut_color]"
|
||||
dna.features["breasts_color"] = "[dna.species.fixed_mut_color]"
|
||||
return
|
||||
//So people who haven't set stuff up don't get rainbow surprises.
|
||||
dna.features["cock_color"] = "[dna.features["mcolor"]]"
|
||||
dna.features["breasts_color"] = "[dna.features["mcolor"]]"
|
||||
else //If there's a new organ, make it the same colour.
|
||||
if(breastCheck == FALSE)
|
||||
dna.features["breasts_color"] = dna.features["cock_color"]
|
||||
else if (willyCheck == FALSE)
|
||||
dna.features["cock_color"] = dna.features["breasts_color"]
|
||||
return TRUE
|
||||
@@ -0,0 +1,121 @@
|
||||
/datum/sprite_accessory
|
||||
var/alt_aroused = FALSE //CIT CODE if this is TRUE, then the genitals will use an alternate icon_state when aroused.
|
||||
var/taur_icon //leave null if the genital doesn't have a taur counterpart.
|
||||
var/accepted_taurs = STYLE_HOOF_TAURIC|STYLE_PAW_TAURIC //Types that match with the accessory.
|
||||
var/feat_taur //the text string of the dna feature to check for those who want to opt out.
|
||||
var/taur_dimension_y = 32
|
||||
var/taur_dimension_x = 32
|
||||
|
||||
|
||||
//DICKS,COCKS,PENISES,WHATEVER YOU WANT TO CALL THEM
|
||||
/datum/sprite_accessory/penis
|
||||
icon = 'icons/obj/genitals/penis_onmob.dmi'
|
||||
name = "penis" //the preview name of the accessory
|
||||
color_src = "cock_color"
|
||||
alt_aroused = TRUE
|
||||
feat_taur = "cock_taur"
|
||||
|
||||
/datum/sprite_accessory/penis/human
|
||||
icon_state = "human"
|
||||
name = "Human"
|
||||
|
||||
/datum/sprite_accessory/penis/knotted
|
||||
icon_state = "knotted"
|
||||
name = "Knotted"
|
||||
taur_icon = 'icons/obj/genitals/taur_penis_onmob.dmi'
|
||||
taur_dimension_x = 64
|
||||
|
||||
/datum/sprite_accessory/penis/flared
|
||||
icon_state = "flared"
|
||||
name = "Flared"
|
||||
taur_icon = 'icons/obj/genitals/taur_penis_onmob.dmi'
|
||||
taur_dimension_x = 64
|
||||
|
||||
/datum/sprite_accessory/penis/barbknot
|
||||
icon_state = "barbknot"
|
||||
name = "Barbed, Knotted"
|
||||
|
||||
/datum/sprite_accessory/penis/tapered
|
||||
icon_state = "tapered"
|
||||
name = "Tapered"
|
||||
taur_icon = 'icons/obj/genitals/taur_penis_onmob.dmi'
|
||||
taur_dimension_x = 64
|
||||
|
||||
/datum/sprite_accessory/penis/tentacle
|
||||
icon_state = "tentacle"
|
||||
name = "Tentacled"
|
||||
|
||||
/datum/sprite_accessory/penis/hemi
|
||||
icon_state = "hemi"
|
||||
name = "Hemi"
|
||||
|
||||
/datum/sprite_accessory/penis/hemiknot
|
||||
icon_state = "hemiknot"
|
||||
name = "Knotted Hemi"
|
||||
|
||||
//Testicles
|
||||
/datum/sprite_accessory/testicles
|
||||
icon = 'icons/obj/genitals/testicles_onmob.dmi'
|
||||
icon_state = "testicle"
|
||||
name = "testicle" //the preview name of the accessory
|
||||
color_src = "balls_color"
|
||||
|
||||
/datum/sprite_accessory/testicles/single
|
||||
icon_state = "single"
|
||||
name = "Single" //Single as "single pair", for clarity.
|
||||
|
||||
//Vaginas
|
||||
/datum/sprite_accessory/vagina
|
||||
icon = 'icons/obj/genitals/vagina_onmob.dmi'
|
||||
name = "vagina"
|
||||
color_src = "vag_color"
|
||||
alt_aroused = TRUE
|
||||
|
||||
/datum/sprite_accessory/vagina/human
|
||||
icon_state = "human"
|
||||
name = "Human"
|
||||
|
||||
/datum/sprite_accessory/vagina/tentacles
|
||||
icon_state = "tentacle"
|
||||
name = "Tentacle"
|
||||
|
||||
/datum/sprite_accessory/vagina/dentata
|
||||
icon_state = "dentata"
|
||||
name = "Dentata"
|
||||
|
||||
/datum/sprite_accessory/vagina/hairy
|
||||
icon_state = "hairy"
|
||||
name = "Hairy"
|
||||
alt_aroused = FALSE
|
||||
|
||||
/datum/sprite_accessory/vagina/spade
|
||||
icon_state = "spade"
|
||||
name = "Spade"
|
||||
alt_aroused = FALSE
|
||||
|
||||
/datum/sprite_accessory/vagina/furred
|
||||
icon_state = "furred"
|
||||
name = "Furred"
|
||||
alt_aroused = FALSE
|
||||
|
||||
/datum/sprite_accessory/vagina/gaping
|
||||
icon_state = "gaping"
|
||||
name = "Gaping"
|
||||
|
||||
//BREASTS BE HERE
|
||||
/datum/sprite_accessory/breasts
|
||||
icon = 'icons/obj/genitals/breasts_onmob.dmi'
|
||||
name = "breasts"
|
||||
color_src = "breasts_color"
|
||||
|
||||
/datum/sprite_accessory/breasts/pair
|
||||
icon_state = "pair"
|
||||
name = "Pair"
|
||||
|
||||
/datum/sprite_accessory/breasts/quad
|
||||
icon_state = "quad"
|
||||
name = "Quad"
|
||||
|
||||
/datum/sprite_accessory/breasts/sextuple
|
||||
icon_state = "sextuple"
|
||||
name = "Sextuple"
|
||||
@@ -0,0 +1,137 @@
|
||||
#define BREASTS_ICON_MIN_SIZE 1
|
||||
#define BREASTS_ICON_MAX_SIZE 6
|
||||
|
||||
/obj/item/organ/genital/breasts
|
||||
name = "breasts"
|
||||
desc = "Female milk producing organs."
|
||||
icon_state = "breasts"
|
||||
icon = 'icons/obj/genitals/breasts.dmi'
|
||||
zone = BODY_ZONE_CHEST
|
||||
slot = ORGAN_SLOT_BREASTS
|
||||
size = BREASTS_SIZE_DEF // "c". Refer to the breast_values static list below for the cups associated number values
|
||||
fluid_id = /datum/reagent/consumable/milk
|
||||
fluid_rate = MILK_RATE
|
||||
shape = DEF_BREASTS_SHAPE
|
||||
genital_flags = CAN_MASTURBATE_WITH|CAN_CLIMAX_WITH|GENITAL_FUID_PRODUCTION|GENITAL_CAN_AROUSE|UPDATE_OWNER_APPEARANCE|GENITAL_UNDIES_HIDDEN
|
||||
masturbation_verb = "massage"
|
||||
arousal_verb = "Your breasts start feeling sensitive"
|
||||
unarousal_verb = "Your breasts no longer feel sensitive"
|
||||
orgasm_verb = "leaking"
|
||||
fluid_transfer_factor = 0.5
|
||||
var/static/list/breast_values = list("a" = 1, "b" = 2, "c" = 3, "d" = 4, "e" = 5, "f" = 6, "g" = 7, "h" = 8, "i" = 9, "j" = 10, "k" = 11, "l" = 12, "m" = 13, "n" = 14, "o" = 15, "huge" = 16, "flat" = 0)
|
||||
var/cached_size //these two vars pertain size modifications and so should be expressed in NUMBERS.
|
||||
var/prev_size //former cached_size value, to allow update_size() to early return should be there no significant changes.
|
||||
|
||||
/obj/item/organ/genital/breasts/Initialize(mapload, do_update = TRUE)
|
||||
if(do_update)
|
||||
cached_size = breast_values[size]
|
||||
prev_size = cached_size
|
||||
return ..()
|
||||
|
||||
/obj/item/organ/genital/breasts/update_appearance()
|
||||
. = ..()
|
||||
var/lowershape = lowertext(shape)
|
||||
switch(lowershape)
|
||||
if("pair")
|
||||
desc = "You see a pair of breasts."
|
||||
if("quad")
|
||||
desc = "You see two pairs of breast, one just under the other."
|
||||
if("sextuple")
|
||||
desc = "You see three sets of breasts, running from their chest to their belly."
|
||||
else
|
||||
desc = "You see some breasts, they seem to be quite exotic."
|
||||
if(size == "huge")
|
||||
desc = "You see [pick("some serious honkers", "a real set of badonkers", "some dobonhonkeros", "massive dohoonkabhankoloos", "two big old tonhongerekoogers", "a couple of giant bonkhonagahoogs", "a pair of humongous hungolomghnonoloughongous")]. Their volume is way beyond cupsize now, measuring in about [round(cached_size)]cm in diameter."
|
||||
else
|
||||
if (size == "flat")
|
||||
desc += " They're very small and flatchested, however."
|
||||
else
|
||||
desc += " You estimate that they're [uppertext(size)]-cups."
|
||||
|
||||
if(CHECK_BITFIELD(genital_flags, GENITAL_FUID_PRODUCTION) && aroused_state)
|
||||
var/datum/reagent/R = GLOB.chemical_reagents_list[fluid_id]
|
||||
if(R)
|
||||
desc += " They're leaking [lowertext(R.name)]."
|
||||
var/datum/sprite_accessory/S = GLOB.breasts_shapes_list[shape]
|
||||
var/icon_shape = S ? S.icon_state : "pair"
|
||||
var/icon_size = clamp(breast_values[size], BREASTS_ICON_MIN_SIZE, BREASTS_ICON_MAX_SIZE)
|
||||
icon_state = "breasts_[icon_shape]_[icon_size]"
|
||||
if(owner)
|
||||
if(owner.dna.species.use_skintones && owner.dna.features["genitals_use_skintone"])
|
||||
if(ishuman(owner)) // Check before recasting type, although someone fucked up if you're not human AND have use_skintones somehow...
|
||||
var/mob/living/carbon/human/H = owner // only human mobs have skin_tone, which we need.
|
||||
color = SKINTONE2HEX(H.skin_tone)
|
||||
if(!H.dna.skin_tone_override)
|
||||
icon_state += "_s"
|
||||
else
|
||||
color = "#[owner.dna.features["breasts_color"]]"
|
||||
|
||||
//Allows breasts to grow and change size, with sprite changes too.
|
||||
//maximum wah
|
||||
//Comical sizes slow you down in movement and actions.
|
||||
//Ridiculous sizes makes you more cumbersome.
|
||||
//this is far too lewd wah
|
||||
|
||||
/obj/item/organ/genital/breasts/modify_size(modifier, min = -INFINITY, max = INFINITY)
|
||||
var/new_value = clamp(cached_size + modifier, min, max)
|
||||
if(new_value == cached_size)
|
||||
return
|
||||
prev_size = cached_size
|
||||
cached_size = new_value
|
||||
update()
|
||||
..()
|
||||
|
||||
/obj/item/organ/genital/breasts/update_size()//wah
|
||||
var/rounded_cached = round(cached_size)
|
||||
if(cached_size < 0)//I don't actually know what round() does to negative numbers, so to be safe!!fixed
|
||||
if(owner)
|
||||
to_chat(owner, "<span class='warning'>You feel your breasts shrinking away from your body as your chest flattens out.</span>")
|
||||
QDEL_IN(src, 1)
|
||||
return
|
||||
var/enlargement = FALSE
|
||||
switch(rounded_cached)
|
||||
if(0) //flatchested
|
||||
size = "flat"
|
||||
if(1 to 8) //modest
|
||||
size = breast_values[rounded_cached]
|
||||
if(9 to 15) //massive
|
||||
size = breast_values[rounded_cached]
|
||||
enlargement = TRUE
|
||||
if(16 to INFINITY) //rediculous
|
||||
size = "huge"
|
||||
enlargement = TRUE
|
||||
if(owner)
|
||||
var/status_effect = owner.has_status_effect(STATUS_EFFECT_BREASTS_ENLARGEMENT)
|
||||
if(enlargement && !status_effect)
|
||||
owner.apply_status_effect(STATUS_EFFECT_BREASTS_ENLARGEMENT)
|
||||
else if(!enlargement && status_effect)
|
||||
owner.remove_status_effect(STATUS_EFFECT_BREASTS_ENLARGEMENT)
|
||||
|
||||
if(rounded_cached < 16 && owner)//Because byond doesn't count from 0, I have to do this.
|
||||
var/mob/living/carbon/human/H = owner
|
||||
var/r_prev_size = round(prev_size)
|
||||
if (rounded_cached > r_prev_size)
|
||||
to_chat(H, "<span class='warning'>Your breasts [pick("swell up to", "flourish into", "expand into", "burst forth into", "grow eagerly into", "amplify into")] a [uppertext(size)]-cup.</span>")
|
||||
else if (rounded_cached < r_prev_size)
|
||||
to_chat(H, "<span class='warning'>Your breasts [pick("shrink down to", "decrease into", "diminish into", "deflate into", "shrivel regretfully into", "contracts into")] a [uppertext(size)]-cup.</span>")
|
||||
|
||||
/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"]
|
||||
if(!D.features["breasts_producing"])
|
||||
DISABLE_BITFIELD(genital_flags, GENITAL_FUID_PRODUCTION|CAN_CLIMAX_WITH|CAN_MASTURBATE_WITH)
|
||||
if(!isnum(size))
|
||||
cached_size = breast_values[size]
|
||||
else
|
||||
cached_size = size
|
||||
size = breast_values[size]
|
||||
prev_size = cached_size
|
||||
toggle_visibility(D.features["breasts_visibility"], FALSE)
|
||||
|
||||
#undef BREASTS_ICON_MIN_SIZE
|
||||
#undef BREASTS_ICON_MAX_SIZE
|
||||
@@ -0,0 +1,109 @@
|
||||
/obj/item/organ/genital/penis
|
||||
name = "penis"
|
||||
desc = "A male reproductive organ."
|
||||
icon_state = "penis"
|
||||
icon = 'icons/obj/genitals/penis.dmi'
|
||||
zone = BODY_ZONE_PRECISE_GROIN
|
||||
slot = ORGAN_SLOT_PENIS
|
||||
masturbation_verb = "stroke"
|
||||
arousal_verb = "You pop a boner"
|
||||
unarousal_verb = "Your boner goes down"
|
||||
genital_flags = CAN_MASTURBATE_WITH|CAN_CLIMAX_WITH|GENITAL_CAN_AROUSE|UPDATE_OWNER_APPEARANCE|GENITAL_UNDIES_HIDDEN|GENITAL_CAN_TAUR
|
||||
linked_organ_slot = ORGAN_SLOT_TESTICLES
|
||||
fluid_transfer_factor = 0.5
|
||||
shape = DEF_COCK_SHAPE
|
||||
size = 2 //arbitrary value derived from length and diameter for sprites.
|
||||
layer_index = PENIS_LAYER_INDEX
|
||||
var/length = 6 //inches
|
||||
|
||||
var/prev_length = 6 //really should be renamed to prev_length
|
||||
var/diameter = 4.38
|
||||
var/diameter_ratio = COCK_DIAMETER_RATIO_DEF //0.25; check citadel_defines.dm
|
||||
|
||||
/obj/item/organ/genital/penis/modify_size(modifier, min = -INFINITY, max = INFINITY)
|
||||
var/new_value = clamp(length + modifier, min, max)
|
||||
if(new_value == length)
|
||||
return
|
||||
prev_length = length
|
||||
length = clamp(length + modifier, min, max)
|
||||
update()
|
||||
..()
|
||||
|
||||
/obj/item/organ/genital/penis/update_size(modified = FALSE)
|
||||
if(length <= 0)//I don't actually know what round() does to negative numbers, so to be safe!!
|
||||
if(owner)
|
||||
to_chat(owner, "<span class='warning'>You feel your tallywacker shrinking away from your body as your groin flattens out!</b></span>")
|
||||
QDEL_IN(src, 1)
|
||||
if(linked_organ)
|
||||
QDEL_IN(linked_organ, 1)
|
||||
return
|
||||
var/rounded_length = round(length)
|
||||
var/new_size
|
||||
var/enlargement = FALSE
|
||||
switch(rounded_length)
|
||||
if(0 to 6) //If modest size
|
||||
new_size = 1
|
||||
if(7 to 11) //If large
|
||||
new_size = 2
|
||||
if(12 to 20) //If massive
|
||||
new_size = 3
|
||||
if(21 to 34) //If massive and due for large effects
|
||||
new_size = 3
|
||||
enlargement = TRUE
|
||||
if(35 to INFINITY) //If comical
|
||||
new_size = 4 //no new sprites for anything larger yet
|
||||
enlargement = TRUE
|
||||
if(owner)
|
||||
var/status_effect = owner.has_status_effect(STATUS_EFFECT_PENIS_ENLARGEMENT)
|
||||
if(enlargement && !status_effect)
|
||||
owner.apply_status_effect(STATUS_EFFECT_PENIS_ENLARGEMENT)
|
||||
else if(!enlargement && status_effect)
|
||||
owner.remove_status_effect(STATUS_EFFECT_PENIS_ENLARGEMENT)
|
||||
if(linked_organ)
|
||||
linked_organ.size = clamp(size + new_size, BALLS_SIZE_MIN, BALLS_SIZE_MAX)
|
||||
linked_organ.update()
|
||||
size = new_size
|
||||
|
||||
if(owner)
|
||||
if (round(length) > round(prev_length))
|
||||
to_chat(owner, "<span class='warning'>Your [pick(GLOB.dick_nouns)] [pick("swells up to", "flourishes into", "expands into", "bursts forth into", "grows eagerly into", "amplifys into")] a [uppertext(round(length))] inch penis.</b></span>")
|
||||
else if ((round(length) < round(prev_length)) && (length > 0.5))
|
||||
to_chat(owner, "<span class='warning'>Your [pick(GLOB.dick_nouns)] [pick("shrinks down to", "decreases into", "diminishes into", "deflates into", "shrivels regretfully into", "contracts into")] a [uppertext(round(length))] inch penis.</b></span>")
|
||||
icon_state = sanitize_text("penis_[shape]_[size]")
|
||||
diameter = (length * diameter_ratio)//Is it just me or is this ludicous, why not make it exponentially decay?
|
||||
|
||||
|
||||
/obj/item/organ/genital/penis/update_appearance()
|
||||
. = ..()
|
||||
var/datum/sprite_accessory/S = GLOB.cock_shapes_list[shape]
|
||||
var/icon_shape = S ? S.icon_state : "human"
|
||||
icon_state = "penis_[icon_shape]_[size]"
|
||||
var/lowershape = lowertext(shape)
|
||||
|
||||
if(owner)
|
||||
if(owner.dna.species.use_skintones && owner.dna.features["genitals_use_skintone"])
|
||||
if(ishuman(owner)) // Check before recasting type, although someone fucked up if you're not human AND have use_skintones somehow...
|
||||
var/mob/living/carbon/human/H = owner // only human mobs have skin_tone, which we need.
|
||||
color = SKINTONE2HEX(H.skin_tone)
|
||||
if(!H.dna.skin_tone_override)
|
||||
icon_state += "_s"
|
||||
else
|
||||
color = "#[owner.dna.features["cock_color"]]"
|
||||
if(genital_flags & GENITAL_CAN_TAUR && S?.taur_icon && (!S.feat_taur || owner.dna.features[S.feat_taur]) && owner.dna.species.mutant_bodyparts["taur"])
|
||||
var/datum/sprite_accessory/taur/T = GLOB.taur_list[owner.dna.features["taur"]]
|
||||
if(T.taur_mode & S.accepted_taurs) //looks out of place on those.
|
||||
lowershape = "taur, [lowershape]"
|
||||
|
||||
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(diameter, 0.25)] inch[round(diameter, 0.25) != 1 ? "es" : ""] in diameter."
|
||||
|
||||
/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
|
||||
color = "#[D.features["cock_color"]]"
|
||||
length = D.features["cock_length"]
|
||||
diameter_ratio = D.features["cock_diameter_ratio"]
|
||||
shape = D.features["cock_shape"]
|
||||
prev_length = length
|
||||
toggle_visibility(D.features["cock_visibility"], FALSE)
|
||||
@@ -0,0 +1,67 @@
|
||||
/obj/item/organ/genital/testicles
|
||||
name = "testicles"
|
||||
desc = "A male reproductive organ."
|
||||
icon_state = "testicles"
|
||||
icon = 'icons/obj/genitals/testicles.dmi'
|
||||
zone = BODY_ZONE_PRECISE_GROIN
|
||||
slot = ORGAN_SLOT_TESTICLES
|
||||
size = BALLS_SIZE_MIN
|
||||
arousal_verb = "Your balls ache a little"
|
||||
unarousal_verb = "Your balls finally stop aching, again"
|
||||
linked_organ_slot = ORGAN_SLOT_PENIS
|
||||
genital_flags = CAN_MASTURBATE_WITH|MASTURBATE_LINKED_ORGAN|GENITAL_FUID_PRODUCTION|UPDATE_OWNER_APPEARANCE|GENITAL_UNDIES_HIDDEN
|
||||
var/size_name = "average"
|
||||
shape = DEF_BALLS_SHAPE
|
||||
fluid_id = /datum/reagent/consumable/semen
|
||||
masturbation_verb = "massage"
|
||||
layer_index = TESTICLES_LAYER_INDEX
|
||||
|
||||
/obj/item/organ/genital/testicles/generate_fluid()
|
||||
if(!linked_organ && !update_link())
|
||||
return FALSE
|
||||
return ..()
|
||||
// in memoriam "Your balls finally feel full, again." ??-2020
|
||||
|
||||
/obj/item/organ/genital/testicles/upon_link()
|
||||
size = linked_organ.size
|
||||
update_size()
|
||||
update_appearance()
|
||||
|
||||
/obj/item/organ/genital/testicles/update_size(modified = FALSE)
|
||||
switch(size)
|
||||
if(BALLS_SIZE_MIN)
|
||||
size_name = "average"
|
||||
if(BALLS_SIZE_DEF)
|
||||
size_name = "enlarged"
|
||||
if(BALLS_SIZE_MAX)
|
||||
size_name = "engorged"
|
||||
else
|
||||
size_name = "nonexistant"
|
||||
|
||||
/obj/item/organ/genital/testicles/update_appearance()
|
||||
. = ..()
|
||||
desc = "You see an [size_name] pair of testicles."
|
||||
var/datum/sprite_accessory/S = GLOB.balls_shapes_list[shape]
|
||||
var/icon_shape = S ? S.icon_state : "single"
|
||||
icon_state = "testicles_[icon_shape]_[size]"
|
||||
if(owner)
|
||||
if(owner.dna.species.use_skintones && owner.dna.features["genitals_use_skintone"])
|
||||
if(ishuman(owner)) // Check before recasting type, although someone fucked up if you're not human AND have use_skintones somehow...
|
||||
var/mob/living/carbon/human/H = owner // only human mobs have skin_tone, which we need.
|
||||
color = SKINTONE2HEX(H.skin_tone)
|
||||
if(!H.dna.skin_tone_override)
|
||||
icon_state += "_s"
|
||||
else
|
||||
color = "#[owner.dna.features["balls_color"]]"
|
||||
|
||||
/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"]]"
|
||||
shape = D.features["balls_shape"]
|
||||
fluid_rate = D.features["balls_cum_rate"]
|
||||
fluid_mult = D.features["balls_cum_mult"]
|
||||
fluid_efficiency = D.features["balls_efficiency"]
|
||||
toggle_visibility(D.features["balls_visibility"], FALSE)
|
||||
@@ -0,0 +1,74 @@
|
||||
/obj/item/organ/genital/vagina
|
||||
name = "vagina"
|
||||
desc = "A female reproductive organ."
|
||||
icon = '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
|
||||
shape = DEF_VAGINA_SHAPE
|
||||
genital_flags = CAN_MASTURBATE_WITH|CAN_CLIMAX_WITH|GENITAL_CAN_AROUSE|GENITAL_UNDIES_HIDDEN
|
||||
masturbation_verb = "finger"
|
||||
arousal_verb = "You feel wetness on your crotch"
|
||||
unarousal_verb = "You no longer feel wet"
|
||||
fluid_transfer_factor = 0.1 //Yes, some amount is exposed to you, go get your AIDS
|
||||
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/list/vag_types = list("tentacle", "dentata", "hairy", "spade", "furred")
|
||||
|
||||
/obj/item/organ/genital/vagina/update_appearance()
|
||||
. = ..()
|
||||
icon_state = "vagina"
|
||||
var/lowershape = lowertext(shape)
|
||||
var/details
|
||||
|
||||
switch(lowershape)
|
||||
if("tentacle")
|
||||
details = "Its opening is lined with several tentacles and "
|
||||
if("dentata")
|
||||
details = "There's teeth inside it and it "
|
||||
if("hairy")
|
||||
details = "It has quite a bit of hair growing on it and "
|
||||
if("human")
|
||||
details = "It is taut with smooth skin, though without much hair and "
|
||||
if("gaping")
|
||||
details = "It is gaping slightly open, though without much hair and "
|
||||
if("spade")
|
||||
details = "It is a plush canine spade, it "
|
||||
if("furred")
|
||||
details = "It has neatly groomed fur around the outer folds, it "
|
||||
else
|
||||
details = "It has an exotic shape and "
|
||||
if(aroused_state)
|
||||
details += "is slick with female arousal."
|
||||
else
|
||||
details += "seems to be dry."
|
||||
|
||||
desc = "You see a vagina. [details]"
|
||||
|
||||
if(owner)
|
||||
if(owner.dna.species.use_skintones && owner.dna.features["genitals_use_skintone"])
|
||||
if(ishuman(owner)) // Check before recasting type, although someone fucked up if you're not human AND have use_skintones somehow...
|
||||
var/mob/living/carbon/human/H = owner // only human mobs have skin_tone, which we need.
|
||||
color = SKINTONE2HEX(H.skin_tone)
|
||||
if(!H.dna.skin_tone_override)
|
||||
icon_state += "_s"
|
||||
else
|
||||
color = "#[owner.dna.features["vag_color"]]"
|
||||
if(ishuman(owner))
|
||||
var/mob/living/carbon/human/H = owner
|
||||
H.update_genitals()
|
||||
|
||||
/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
|
||||
color = "[D.features["vag_color"]]"
|
||||
shape = "[D.features["vag_shape"]]"
|
||||
toggle_visibility(D.features["vag_visibility"], FALSE)
|
||||
@@ -0,0 +1,10 @@
|
||||
/obj/item/organ/genital/womb
|
||||
name = "womb"
|
||||
desc = "A female reproductive organ."
|
||||
icon = 'icons/obj/genitals/vagina.dmi'
|
||||
icon_state = "womb"
|
||||
zone = BODY_ZONE_PRECISE_GROIN
|
||||
slot = ORGAN_SLOT_WOMB
|
||||
genital_flags = GENITAL_INTERNAL|GENITAL_FUID_PRODUCTION
|
||||
fluid_id = /datum/reagent/consumable/femcum
|
||||
linked_organ_slot = ORGAN_SLOT_VAGINA
|
||||
@@ -0,0 +1,159 @@
|
||||
//////////
|
||||
//DILDOS//
|
||||
//////////
|
||||
/obj/item/dildo
|
||||
name = "dildo"
|
||||
desc = "Floppy!"
|
||||
icon = 'icons/obj/genitals/dildo.dmi'
|
||||
force = 0
|
||||
hitsound = 'sound/weapons/tap.ogg'
|
||||
throwforce = 0
|
||||
icon_state = "dildo_knotted_2"
|
||||
alpha = 192//transparent
|
||||
var/can_customize = FALSE
|
||||
var/dildo_shape = "human"
|
||||
var/dildo_size = 2
|
||||
var/dildo_type = "dildo"//pretty much just used for the icon state
|
||||
var/random_color = TRUE
|
||||
var/random_size = FALSE
|
||||
var/random_shape = FALSE
|
||||
var/is_knotted = FALSE
|
||||
//Lists moved to _cit_helpers.dm as globals so they're not instanced individually
|
||||
|
||||
/obj/item/dildo/proc/update_appearance()
|
||||
icon_state = "[dildo_type]_[dildo_shape]_[dildo_size]"
|
||||
var/sizeword = ""
|
||||
switch(dildo_size)
|
||||
if(1)
|
||||
sizeword = "small "
|
||||
if(3)
|
||||
sizeword = "big "
|
||||
if(4)
|
||||
sizeword = "huge "
|
||||
if(5)
|
||||
sizeword = "gigantic "
|
||||
|
||||
name = "[sizeword][dildo_shape] [can_customize ? "custom " : ""][dildo_type]"
|
||||
|
||||
/obj/item/dildo/AltClick(mob/living/user)
|
||||
. = ..()
|
||||
if(!istype(user) || !user.canUseTopic(src, BE_CLOSE, FALSE, NO_TK))
|
||||
return
|
||||
customize(user)
|
||||
return TRUE
|
||||
|
||||
/obj/item/dildo/proc/customize(mob/living/user)
|
||||
if(!can_customize)
|
||||
return FALSE
|
||||
if(src && !user.incapacitated() && in_range(user,src))
|
||||
var/color_choice = input(user,"Choose a color for your dildo.","Dildo Color") as null|anything in GLOB.dildo_colors
|
||||
if(src && color_choice && !user.incapacitated() && in_range(user,src))
|
||||
sanitize_inlist(color_choice, GLOB.dildo_colors, "Red")
|
||||
color = GLOB.dildo_colors[color_choice]
|
||||
update_appearance()
|
||||
if(src && !user.incapacitated() && in_range(user,src))
|
||||
var/shape_choice = input(user,"Choose a shape for your dildo.","Dildo Shape") as null|anything in GLOB.dildo_shapes
|
||||
if(src && shape_choice && !user.incapacitated() && in_range(user,src))
|
||||
sanitize_inlist(shape_choice, GLOB.dildo_colors, "Knotted")
|
||||
dildo_shape = GLOB.dildo_shapes[shape_choice]
|
||||
update_appearance()
|
||||
if(src && !user.incapacitated() && in_range(user,src))
|
||||
var/size_choice = input(user,"Choose the size for your dildo.","Dildo Size") as null|anything in GLOB.dildo_sizes
|
||||
if(src && size_choice && !user.incapacitated() && in_range(user,src))
|
||||
sanitize_inlist(size_choice, GLOB.dildo_colors, "Medium")
|
||||
dildo_size = GLOB.dildo_sizes[size_choice]
|
||||
update_appearance()
|
||||
if(src && !user.incapacitated() && in_range(user,src))
|
||||
var/transparency_choice = input(user,"Choose the transparency of your dildo. Lower is more transparent!(192-255)","Dildo Transparency") as null|num
|
||||
if(src && transparency_choice && !user.incapacitated() && in_range(user,src))
|
||||
sanitize_integer(transparency_choice, 192, 255, 192)
|
||||
alpha = transparency_choice
|
||||
update_appearance()
|
||||
return TRUE
|
||||
|
||||
/obj/item/dildo/Initialize()
|
||||
. = ..()
|
||||
if(random_color == TRUE)
|
||||
var/randcolor = pick(GLOB.dildo_colors)
|
||||
color = GLOB.dildo_colors[randcolor]
|
||||
if(random_shape == TRUE)
|
||||
var/randshape = pick(GLOB.dildo_shapes)
|
||||
dildo_shape = GLOB.dildo_shapes[randshape]
|
||||
if(random_size == TRUE)
|
||||
var/randsize = pick(GLOB.dildo_sizes)
|
||||
dildo_size = GLOB.dildo_sizes[randsize]
|
||||
update_appearance()
|
||||
alpha = rand(192, 255)
|
||||
pixel_y = rand(-7,7)
|
||||
pixel_x = rand(-7,7)
|
||||
|
||||
/obj/item/dildo/examine(mob/user)
|
||||
. = ..()
|
||||
if(can_customize)
|
||||
. += "<span class='notice'>Alt-Click \the [src.name] to customize it.</span>"
|
||||
|
||||
/obj/item/dildo/random//totally random
|
||||
name = "random dildo"//this name will show up in vendors and shit so you know what you're vending(or don't, i guess :^))
|
||||
random_color = TRUE
|
||||
random_shape = TRUE
|
||||
random_size = TRUE
|
||||
|
||||
/obj/item/dildo/knotted
|
||||
dildo_shape = "knotted"
|
||||
name = "knotted dildo"
|
||||
attack_verb = list("penetrated", "knotted", "slapped", "inseminated")
|
||||
|
||||
obj/item/dildo/human
|
||||
dildo_shape = "human"
|
||||
name = "human dildo"
|
||||
attack_verb = list("penetrated", "slapped", "inseminated")
|
||||
|
||||
obj/item/dildo/plain
|
||||
dildo_shape = "plain"
|
||||
name = "plain dildo"
|
||||
attack_verb = list("penetrated", "slapped", "inseminated")
|
||||
|
||||
obj/item/dildo/flared
|
||||
dildo_shape = "flared"
|
||||
name = "flared dildo"
|
||||
attack_verb = list("penetrated", "slapped", "neighed", "gaped", "prolapsed", "inseminated")
|
||||
|
||||
obj/item/dildo/flared/huge
|
||||
name = "literal horse cock"
|
||||
desc = "THIS THING IS HUGE!"
|
||||
dildo_size = 4
|
||||
|
||||
obj/item/dildo/custom
|
||||
name = "customizable dildo"
|
||||
desc = "Thanks to significant advances in synthetic nanomaterials, this dildo is capable of taking on many different forms to fit the user's preferences! Pricy!"
|
||||
can_customize = TRUE
|
||||
random_color = TRUE
|
||||
random_shape = TRUE
|
||||
random_size = TRUE
|
||||
|
||||
// Suicide acts, by request
|
||||
|
||||
/obj/item/dildo/proc/manual_suicide(mob/living/user)
|
||||
user.visible_message("<span class='suicide'>[user] finally finishes deepthroating the [src], and their life.</span>")
|
||||
user.adjustOxyLoss(200)
|
||||
user.death(0)
|
||||
|
||||
/obj/item/dildo/suicide_act(mob/living/user)
|
||||
// is_knotted = ((src.dildo_shape == "knotted")?"They swallowed the knot":"Their face is turning blue")
|
||||
if(do_after(user,17,target=src))
|
||||
user.visible_message("<span class='suicide'>[user] tears-up and gags as they shove [src] down their throat! It looks like [user.p_theyre()] trying to commit suicide!</span>")
|
||||
playsound(loc, 'sound/weapons/gagging.ogg', 50, 1, -1)
|
||||
user.Stun(150)
|
||||
user.adjust_blurriness(8)
|
||||
var/obj/item/organ/eyes/eyes = user.getorganslot(ORGAN_SLOT_EYES)
|
||||
eyes?.applyOrganDamage(10)
|
||||
return MANUAL_SUICIDE
|
||||
|
||||
/obj/item/dildo/flared/huge/suicide_act(mob/living/user)
|
||||
if(do_after(user,35,target=src))
|
||||
user.visible_message("<span class='suicide'>[user] tears-up and gags as they try to deepthroat the [src]! WHY WOULD THEY DO THAT? It looks like [user.p_theyre()] trying to commit suicide!!</span>")
|
||||
playsound(loc, 'sound/weapons/gagging.ogg', 50, 2, -1)
|
||||
user.Stun(300)
|
||||
user.adjust_blurriness(8)
|
||||
return MANUAL_SUICIDE
|
||||
|
||||
Reference in New Issue
Block a user