This commit is contained in:
WoolyAypa
2023-04-26 16:12:15 -03:00
77 changed files with 1535 additions and 476 deletions
+3 -3
View File
@@ -38756,12 +38756,12 @@
/turf/open/floor/engine/vacuum,
/area/maintenance/disposal/incinerator)
"coq" = (
/obj/machinery/atmospherics/components/unary/vent_pump/siphon/atmos/incinerator_output{
dir = 1
},
/obj/structure/sign/warning/vacuum/external{
pixel_y = -32
},
/obj/machinery/atmospherics/components/unary/vent_scrubber/on{
dir = 1
},
/turf/open/floor/engine/vacuum,
/area/maintenance/disposal/incinerator)
"cor" = (
@@ -18372,12 +18372,12 @@
/turf/open/floor/carpet,
/area/service/chapel/main)
"jxP" = (
/obj/machinery/atmospherics/components/unary/vent_pump/siphon/atmos/incinerator_output{
dir = 1
},
/obj/structure/sign/warning/vacuum/external{
pixel_y = -32
},
/obj/machinery/atmospherics/components/unary/vent_scrubber/on{
dir = 1
},
/turf/open/floor/engine/vacuum,
/area/maintenance/disposal/incinerator)
"jxW" = (
+1 -1
View File
@@ -65390,7 +65390,7 @@
/turf/open/floor/plasteel,
/area/engineering/atmos)
"cLY" = (
/obj/machinery/atmospherics/components/unary/vent_pump/siphon/atmos/incinerator_output{
/obj/machinery/atmospherics/components/unary/vent_scrubber/on{
dir = 4
},
/turf/open/floor/engine/vacuum,
+2 -10
View File
@@ -18916,9 +18916,6 @@
/obj/effect/mapping_helpers/airlock/cyclelink_helper{
dir = 1
},
/obj/machinery/atmospherics/pipe/simple/general/visible{
dir = 6
},
/turf/open/floor/engine,
/area/maintenance/disposal/incinerator)
"aFW" = (
@@ -18943,9 +18940,6 @@
/obj/structure/cable{
icon_state = "1-2"
},
/obj/machinery/atmospherics/pipe/simple/general/visible{
dir = 5
},
/obj/machinery/igniter{
id = "Incinerator"
},
@@ -77085,7 +77079,7 @@
/area/maintenance/port/fore)
"cwH" = (
/obj/machinery/atmospherics/components/unary/vent_scrubber/on{
dir = 8
dir = 1
},
/turf/open/floor/engine/vacuum,
/area/maintenance/disposal/incinerator)
@@ -84416,9 +84410,7 @@
/turf/open/floor/plasteel/showroomfloor,
/area/security/warden)
"jHJ" = (
/obj/machinery/atmospherics/pipe/simple/general/visible{
dir = 9
},
/obj/item/pipe/directional,
/turf/closed/wall/r_wall,
/area/maintenance/disposal/incinerator)
"jHS" = (
+2 -2
View File
@@ -29553,7 +29553,7 @@
/turf/open/floor/plating,
/area/maintenance/starboard/aft)
"cnd" = (
/obj/machinery/atmospherics/components/unary/vent_pump/siphon/atmos/incinerator_output{
/obj/machinery/atmospherics/components/unary/vent_scrubber/on{
dir = 1
},
/turf/open/floor/engine/vacuum,
@@ -46923,7 +46923,7 @@
/turf/open/floor/plating,
/area/maintenance/port/fore)
"dox" = (
/obj/machinery/atmospherics/components/unary/vent_pump/on{
/obj/machinery/atmospherics/components/unary/vent_scrubber/on{
dir = 1
},
/turf/open/floor/engine/vacuum,
@@ -36226,7 +36226,7 @@
/turf/open/floor/plasteel,
/area/engineering/atmos)
"iye" = (
/obj/machinery/atmospherics/components/unary/vent_pump/siphon/atmos/incinerator_output{
/obj/machinery/atmospherics/components/unary/vent_scrubber/on{
dir = 4
},
/turf/open/floor/engine/vacuum,
@@ -44454,7 +44454,7 @@
/turf/open/floor/engine,
/area/maintenance/disposal/incinerator)
"cbF" = (
/obj/machinery/atmospherics/components/unary/vent_pump/siphon/atmos/incinerator_output{
/obj/machinery/atmospherics/components/unary/vent_scrubber/on{
dir = 8
},
/turf/open/floor/engine,
+3 -3
View File
@@ -13544,12 +13544,12 @@
/turf/open/floor/plasteel,
/area/commons/fitness/recreation)
"eJe" = (
/obj/machinery/atmospherics/components/unary/vent_pump/siphon/atmos/incinerator_output{
dir = 1
},
/obj/structure/sign/warning/vacuum/external{
pixel_y = -32
},
/obj/machinery/atmospherics/components/unary/vent_scrubber/on{
dir = 1
},
/turf/open/floor/engine/vacuum,
/area/maintenance/disposal/incinerator)
"eJh" = (
+1 -1
View File
@@ -1,6 +1,6 @@
// tgstation-server DMAPI
#define TGS_DMAPI_VERSION "6.3.0"
#define TGS_DMAPI_VERSION "6.4.2"
// All functions and datums outside this document are subject to change with any version and should not be relied on.
+1
View File
@@ -42,3 +42,4 @@
#define TRAIT_DUMB_CUM "dumb_for_cum_base"
#define TRAIT_DUMB_CUM_CRAVE "dumb_for_cum_need"
#define TRAIT_RAD_FIEND "RadFiend"
#define TRAIT_BODY_MORPHER "body_morpher"
+3 -3
View File
@@ -54,7 +54,7 @@
var/list/modifiers = params2list(params)
if(modifiers["shift"] && modifiers["middle"])
return ShiftMiddleClickOn(A)
return ShiftMiddleClickOn(A, params)
if(modifiers["shift"] && modifiers["ctrl"])
return CtrlShiftClickOn(A)
if(modifiers["middle"])
@@ -402,8 +402,8 @@
return
A.CtrlShiftClick(src)
/mob/proc/ShiftMiddleClickOn(atom/A)
src.pointed(A)
/mob/proc/ShiftMiddleClickOn(atom/A, params)
pointed(A, params)
return
/atom/proc/CtrlShiftClick(mob/user)
+25 -6
View File
@@ -44,6 +44,8 @@
var/red_alert_access = FALSE //if TRUE, this door will always open on red alert
var/poddoor = FALSE
var/unres_sides = 0 //Unrestricted sides. A bitflag for which direction (if any) can open the door with no access
/// Whether or not the door can be opened by hand (used for blast doors and shutters)
var/can_open_with_hands = TRUE
/obj/machinery/door/examine(mob/user)
. = ..()
@@ -55,10 +57,16 @@
if(!poddoor)
. += "<span class='notice'>Its maintenance panel is <b>screwed</b> in place.</span>"
/obj/machinery/door/add_context(atom/source, list/context, obj/item/held_item, mob/living/user)
/obj/machinery/door/add_context(atom/source, list/context, obj/item/held_item, mob/user)
. = ..()
if (isnull(held_item) && !istype(src, /obj/machinery/door/firedoor)) // You cannot open/close with your hands
if(!can_open_with_hands)
return .
if(isaicamera(user) || issilicon(user))
return .
if(isnull(held_item) && Adjacent(user))
LAZYSET(context[SCREENTIP_CONTEXT_LMB], INTENT_ANY, (density ? "Open" : "Close"))
return CONTEXTUAL_SCREENTIP_SET
@@ -100,7 +108,7 @@
return ..()
/obj/machinery/door/Bumped(atom/movable/AM)
if(operating || (obj_flags & EMAGGED))
if(operating || (obj_flags & EMAGGED) || (!can_open_with_hands && density))
return
if(ismob(AM))
var/mob/B = AM
@@ -132,9 +140,9 @@
return !opacity
/obj/machinery/door/proc/bumpopen(mob/user)
if(operating)
if(operating || !can_open_with_hands)
return
src.add_fingerprint(user)
add_fingerprint(user)
if(!src.requiresID())
user = null
@@ -155,7 +163,7 @@
/obj/machinery/door/proc/try_to_activate_door(mob/user)
add_fingerprint(user)
if(operating || (obj_flags & EMAGGED))
if(operating || (obj_flags & EMAGGED) || !can_open_with_hands)
return
if(!requiresID())
user = null //so allowed(user) always succeeds
@@ -417,3 +425,14 @@
. = ..()
if(!density)
return . * EXPLOSION_DAMAGE_OPEN_DOOR_FACTOR
/obj/machinery/door/shuttleRotate(rotation, params)
. = ..()
if(!unres_sides)
return
var/new_unres_sides
for(var/direction in GLOB.cardinals)
if(unres_sides & direction)
new_unres_sides |= angle2dir_cardinal(rotation+dir2angle(direction))
unres_sides = new_unres_sides
update_icon()
+1
View File
@@ -26,6 +26,7 @@
air_tight = TRUE
attack_hand_is_action = TRUE
attack_hand_speed = CLICK_CD_MELEE
can_open_with_hands = FALSE
var/emergency_close_timer = 0
var/nextstate = null
var/boltslocked = TRUE
+1
View File
@@ -16,6 +16,7 @@
armor = list(MELEE = 50, BULLET = 100, LASER = 100, ENERGY = 100, BOMB = 50, BIO = 100, RAD = 100, FIRE = 100, ACID = 70)
resistance_flags = FIRE_PROOF
damage_deflection = 70
can_open_with_hands = FALSE
poddoor = TRUE
/obj/machinery/door/poddoor/connect_to_shuttle(obj/docking_port/mobile/port, obj/docking_port/stationary/dock, idnum, override=FALSE)
+1 -1
View File
@@ -330,7 +330,7 @@
var/obj/item/organ/genital/breasts/boobs = new(loc)
if(features["has_breasts"])
boobs.color = sanitize_hexcolor(features["breasts_color"], 6, TRUE)
boobs.size = features["breasts_size"]
boobs.size = GLOB.breast_values[features["breasts_size"]]
boobs.shape = features["breasts_shape"]
if(!features["breasts_producing"])
boobs.genital_flags &= ~(GENITAL_FUID_PRODUCTION|CAN_CLIMAX_WITH|CAN_MASTURBATE_WITH)
+2 -3
View File
@@ -36,12 +36,11 @@
var/text_buffer = ""
var/static/list/graffiti = list("amyjon","face","matt","revolution","engie","guy","end","dwarf","uboa","body","cyka","star","poseur tag","prolizard","antilizard", "tile")
var/static/list/symbols = list("danger","firedanger","electricdanger","biohazard","radiation","safe","evac","space","med","trade","shop","food","peace","like","skull","nay","heart","credit", "sonne", "ironguard", "scp", "falange")
var/static/list/symbols = list("danger","firedanger","electricdanger","biohazard","radiation","safe","evac","space","med","trade","shop","food","peace","like","skull","nay","heart","credit", "scp")
var/static/list/drawings = list("smallbrush","brush","largebrush","splatter","snake","stickman","carp","ghost","clown","taser","disk","fireaxe","toolbox","corgi","cat","toilet","blueprint","beepsky","scroll","bottle","shotgun", "boobs", "booty", "bdragon")
var/static/list/oriented = list("arrow","line","thinline","shortline","body","chevron","footprint","clawprint","pawprint") // These turn to face the same way as the drawer
var/static/list/runes = list("rune1","rune2","rune3","rune4","rune5","rune6")
var/static/list/randoms = list(RANDOM_ANY, RANDOM_RUNE, RANDOM_ORIENTED,
RANDOM_NUMBER, RANDOM_GRAFFITI, RANDOM_LETTER, RANDOM_SYMBOL, RANDOM_PUNCTUATION, RANDOM_DRAWING)
var/static/list/randoms = list(RANDOM_ANY, RANDOM_RUNE, RANDOM_ORIENTED, RANDOM_NUMBER, RANDOM_GRAFFITI, RANDOM_LETTER, RANDOM_SYMBOL, RANDOM_PUNCTUATION, RANDOM_DRAWING)
var/static/list/graffiti_large_h = list("yiffhell", "furrypride", "secborg", "paint")
var/static/list/all_drawables = graffiti + symbols + drawings + oriented + runes + graffiti_large_h
+2 -2
View File
@@ -184,7 +184,7 @@
var/custom_tone = input(user, "Choose your custom skin tone:", "Race change", default) as color|null
if(custom_tone)
var/temp_hsv = RGBtoHSV(new_s_tone)
if(ReadHSV(temp_hsv)[3] >= ReadHSV(MINIMUM_MUTANT_COLOR)[3])
if(ReadHSV(temp_hsv)[3] >= ReadHSV(MINIMUM_MUTANT_COLOR)[3] || !CONFIG_GET(flag/character_color_limits)) //SPLURT edit
to_chat(H,"<span class='danger'>Invalid color. Your color is not bright enough.</span>")
else
H.skin_tone = custom_tone
@@ -198,7 +198,7 @@
if(new_mutantcolor)
var/temp_hsv = RGBtoHSV(new_mutantcolor)
if(ReadHSV(temp_hsv)[3] >= ReadHSV(MINIMUM_MUTANT_COLOR)[3]) // mutantcolors must be bright
if(ReadHSV(temp_hsv)[3] >= ReadHSV(MINIMUM_MUTANT_COLOR)[3] || !CONFIG_GET(flag/character_color_limits)) // mutantcolors must be bright //SPLURT edit
H.dna.features["mcolor"] = sanitize_hexcolor(new_mutantcolor)
else
+2 -2
View File
@@ -199,7 +199,7 @@
aroused_state = FALSE
/obj/item/organ/genital/proc/generate_fluid(datum/reagents/R)
var/amount = clamp((fluid_rate * ((world.time - last_orgasmed) / (10 SECONDS)) * fluid_mult),0,fluid_max_volume)
var/amount = get_fluid()
R.clear_reagents()
R.maximum_volume = fluid_max_volume
if(fluid_id)
@@ -338,7 +338,7 @@
var/obj/item/organ/genital/G = A
var/datum/sprite_accessory/S
var/size = G.size
var/size = G.size_to_state()
switch(G.type)
if(/obj/item/organ/genital/penis)
S = GLOB.cock_shapes_list[G.shape]
+42 -48
View File
@@ -8,7 +8,7 @@
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
size = 3
fluid_id = /datum/reagent/consumable/milk
fluid_rate = MILK_RATE
shape = DEF_BREASTS_SHAPE
@@ -19,19 +19,12 @@
orgasm_verb = "leaking"
fluid_transfer_factor = 0.5
layer_index = BREASTS_LAYER_INDEX
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, "massive" = 17, "giga" = 25, "impossible" = 30, "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)
var/size_state = size_to_state()
switch(lowershape)
if("pair")
desc = "You see a pair of breasts."
@@ -41,13 +34,13 @@
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*(owner ? get_size(owner) : 1))]cm in diameter."
if(size_state == "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(size*(owner ? get_size(owner) : 1))]cm in diameter."
else
if (size == "flat")
if (size_state == "flat")
desc += " They're very small and flatchested, however."
else
desc += " You estimate that they're [uppertext(size)]-cups."
desc += " You estimate that they're [uppertext(size_state)]-cups."
if((genital_flags & GENITAL_FUID_PRODUCTION) && aroused_state)
var/datum/reagent/R = GLOB.chemical_reagents_list[fluid_id]
@@ -55,7 +48,7 @@
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)
var/icon_size = clamp(GLOB.breast_values[size_state], 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"])
@@ -74,45 +67,49 @@
//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)
var/new_value = clamp(size + modifier, max(min, min_size ? min_size : -INFINITY), min(max_size ? max_size : INFINITY, max))
if(new_value == size)
return
prev_size = cached_size
cached_size = new_value
prev_size = size
size = new_value
update()
..()
/obj/item/organ/genital/breasts/size_to_state()
var/str_size
switch(size)
if(0) //flatchested
str_size = "flat"
if(1 to 8) //modest
str_size = GLOB.breast_values[size]
if(9 to 15) //massive
str_size = GLOB.breast_values[size]
if(16 to 17) //ridiculous
str_size = GLOB.breast_values[size]
if(18 to 24) //AWOOOOGAAAAAAA
str_size = "massive"
if(25 to 29) //AWOOOOOOGAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
str_size = "giga"
if(30 to INFINITY) //AWWWWWWWWWWWWWOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOGGGGGAAAAAAAAAAAAAAAAAAAAAA
str_size = "impossible"
return str_size
/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
var/rounded_size = round(size)
var/size_state = size_to_state()
if(rounded_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
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]
if(16 to 17) //ridiculous
size = breast_values[rounded_cached]
if(18 to 24) //AWOOOOGAAAAAAA
size = "massive"
if(25 to 29) //AWOOOOOOGAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
size = "giga"
if(30 to INFINITY) //AWWWWWWWWWWWWWOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOGGGGGAAAAAAAAAAAAAAAAAAAAAA
size = "impossible"
if((rounded_cached < 18 || rounded_cached == 25 || rounded_cached == 30) && owner )//Because byond doesn't count from 0, I have to do this.
if((rounded_size < 18 || rounded_size == 25 || rounded_size == 30) && 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>")
if (rounded_size > 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_state)]-cup.</span>")
else if (rounded_size < 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_state)]-cup.</span>")
/obj/item/organ/genital/breasts/get_features(mob/living/carbon/human/H)
var/datum/dna/D = H.dna
@@ -120,16 +117,13 @@
color = SKINTONE2HEX(H.skin_tone)
else
color = "#[D.features["breasts_color"]]"
size = D.features["breasts_size"]
size = GLOB.breast_values[D.features["breasts_size"]]
max_size = D.features["breasts_max_size"]
min_size = D.features["breasts_min_size"]
shape = D.features["breasts_shape"]
if(!D.features["breasts_producing"])
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
prev_size = size
toggle_visibility(D.features["breasts_visibility"], FALSE)
if(D.features["breasts_stuffing"])
toggle_visibility(GEN_ALLOW_EGG_STUFFING, FALSE)
+5 -2
View File
@@ -7,6 +7,7 @@
slot = ORGAN_SLOT_BUTT
w_class = 3
size = 0
max_size = BUTT_SIZE_MAX
var/size_name = "nonexistent"
shape = "Pair" //turn this into a default constant if for some inexplicable reason we get more than one butt type but I doubt it.
genital_flags = UPDATE_OWNER_APPEARANCE|GENITAL_UNDIES_HIDDEN|CAN_CUM_INTO|HAS_EQUIPMENT
@@ -15,8 +16,8 @@
var/prev_size //former size value, to allow update_size() to early return should be there no significant changes.
layer_index = BUTT_LAYER_INDEX
/obj/item/organ/genital/butt/modify_size(modifier, min = -INFINITY, max = BUTT_SIZE_MAX)
var/new_value = clamp(size_cached + modifier, min, max)
/obj/item/organ/genital/butt/modify_size(modifier, min = -INFINITY, max = INFINITY)
var/new_value = clamp(size_cached + modifier, max(min, min_size ? min_size : -INFINITY), min(max_size ? max_size : INFINITY, max))
if(new_value == size_cached)
return
prev_size = size_cached
@@ -88,6 +89,8 @@
else
color = "#[D.features["butt_color"]]"
size = D.features["butt_size"]
max_size = D.features["butt_max_size"]
min_size = D.features["butt_min_size"]
prev_size = size
size_cached = size
toggle_visibility(D.features["butt_visibility"], FALSE)
+7 -3
View File
@@ -14,18 +14,20 @@
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/length = 6 //inches
var/max_length = 9
var/min_length = 2
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)
var/new_value = clamp(length + modifier, max(min, min_size ? min_size : -INFINITY), min(max_length ? max_length : INFINITY, max))
if(new_value == length)
return
prev_length = length
length = clamp(length + modifier, min, max)
length = new_value
update()
..()
@@ -95,6 +97,8 @@
else
color = "#[D.features["cock_color"]]"
length = D.features["cock_length"]
max_length = D.features["cock_max_length"]
min_length = D.features["cock_min_length"]
diameter_ratio = D.features["cock_diameter_ratio"]
shape = D.features["cock_shape"]
prev_length = length
+115 -28
View File
@@ -844,6 +844,8 @@ GLOBAL_LIST_EMPTY(preferences_datums)
tauric_shape = TRUE
dat += "<b>Penis Shape:</b> <a style='display:block;width:120px' href='?_src_=prefs;preference=cock_shape;task=input'>[features["cock_shape"]][tauric_shape ? " (Taur)" : ""]</a>"
dat += "<b>Penis Length:</b> <a style='display:block;width:120px' href='?_src_=prefs;preference=cock_length;task=input'>[features["cock_length"]] inch(es)</a>"
dat += "<b>Max Length:</b><a style='display:block;width:120px' href='?_src_=prefs;preference=cock_max_length;task=input'>[features["cock_max_length"] ? features["cock_max_length"] : "Disabled"]</a>"
dat += "<b>Min Length:</b><a style='display:block;width:120px' href='?_src_=prefs;preference=cock_min_length;task=input'>[features["cock_min_length"] ? features["cock_min_length"] : "Disabled"]</a>"
dat += "<b>Diameter Ratio:</b> <a style='display:block;width:120px' href='?_src_=prefs;preference=cock_diameter_ratio;task=input'>[features["cock_diameter_ratio"]]</a>" //SPLURT Edit
dat += "<b>Penis Visibility:</b><a style='display:block;width:100px' href='?_src_=prefs;preference=cock_visibility;task=input'>[features["cock_visibility"]]</a>"
dat += "<b>Egg Stuffing:</b><a style='display:block;width:50px' href='?_src_=prefs;preference=cock_stuffing'>[features["cock_stuffing"] == TRUE ? "Yes" : "No"]</a>" //SPLURT Edit
@@ -859,6 +861,8 @@ GLOBAL_LIST_EMPTY(preferences_datums)
dat += "<b>Testicles Visibility:</b><a style='display:block;width:100px' href='?_src_=prefs;preference=balls_visibility;task=input'>[features["balls_visibility"]]</a>"
//SPLURT Edit
dat += "<b>Egg Stuffing:</b><a style='display:block;width:50px' href='?_src_=prefs;preference=balls_stuffing'>[features["balls_stuffing"] == TRUE ? "Yes" : "No"]</a>"
dat += "<b>Max Size:</b><a style='display:block;width:50px' href='?_src_=prefs;preference=balls_max_size;task=input'>[features["balls_max_size"] ? features["balls_max_size"] : "Disabled"]</a>"
dat += "<b>Min Size:</b><a style='display:block;width:50px' href='?_src_=prefs;preference=balls_min_size;task=input'>[features["balls_min_size"] ? features["balls_min_size"] : "Disabled"]</a>"
dat += "<b>Produces:</b>"
var/datum/reagent/balls_fluid = find_reagent_object_from_type(features["balls_fluid"])
if(balls_fluid && (balls_fluid in GLOB.genital_fluids_list))
@@ -907,6 +911,8 @@ GLOBAL_LIST_EMPTY(preferences_datums)
dat += "<b>Lactates:</b><a style='display:block;width:50px' href='?_src_=prefs;preference=breasts_producing'>[features["breasts_producing"] == TRUE ? "Yes" : "No"]</a>"
//SPLURT Edit
dat += "<b>Egg Stuffing:</b><a style='display:block;width:50px' href='?_src_=prefs;preference=breasts_stuffing'>[features["breasts_stuffing"] == TRUE ? "Yes" : "No"]</a>"
dat += "<b>Max Size:</b><a style='display:block;width:50px' href='?_src_=prefs;preference=breasts_max_size;task=input'>[features["breasts_max_size"] ? features["breasts_max_size"] : "Disabled"]</a>"
dat += "<b>Min Size:</b><a style='display:block;width:50px' href='?_src_=prefs;preference=breasts_min_size;task=input'>[features["breasts_min_size"] ? features["breasts_min_size"] : "Disabled"]</a>"
if(features["breasts_producing"] == TRUE)
dat += "<b>Produces:</b>"
var/datum/reagent/breasts_fluid = find_reagent_object_from_type(features["breasts_fluid"])
@@ -930,6 +936,8 @@ GLOBAL_LIST_EMPTY(preferences_datums)
dat += "<b>Butt Visibility:</b><a style='display:block;width:100px' href='?_src_=prefs;preference=butt_visibility;task=input'>[features["butt_visibility"]]</a>"
//SPLURT Edit
dat += "<b>Egg Stuffing:</b><a style='display:block;width:50px' href='?_src_=prefs;preference=butt_stuffing'>[features["butt_stuffing"] == TRUE ? "Yes" : "No"]</a>"
dat += "<b>Max Size:</b><a style='display:block;width:50px' href='?_src_=prefs;preference=butt_max_size;task=input'>[features["butt_max_size"] ? features["butt_max_size"] : "Disabled"]</a>"
dat += "<b>Min Size:</b><a style='display:block;width:50px' href='?_src_=prefs;preference=butt_min_size;task=input'>[features["butt_min_size"] ? features["butt_min_size"] : "Disabled"]</a>"
dat += "<b>Butthole Sprite:</b><a style='display:block;width:50px' href='?_src_=prefs;preference=has_anus'>[features["has_anus"] == TRUE ? "Yes" : "No"]</a>"
if(features["has_anus"])
dat += "<b>Butthole Color:</b></a><BR>"
@@ -953,6 +961,8 @@ GLOBAL_LIST_EMPTY(preferences_datums)
dat += "<b>Color:</b></a><BR>"
dat += "<span style='border: 1px solid #161616; background-color: #[features["belly_color"]];'><font color='[color_hex2num(features["belly_color"]) < 200 ? "FFFFFF" : "000000"]'>#[features["belly_color"]]</font></span> <a href='?_src_=prefs;preference=belly_color;task=input'>Change</a><br>"
dat += "<b>Belly Size:</b><a style='display:block;width:50px' href='?_src_=prefs;preference=belly_size;task=input'>[features["belly_size"]]</a>"
dat += "<b>Max Size:</b><a style='display:block;width:50px' href='?_src_=prefs;preference=belly_max_size;task=input'>[features["belly_max_size"] ? features["belly_max_size"] : "Disabled" ]</a>"
dat += "<b>Min Size:</b><a style='display:block;width:50px' href='?_src_=prefs;preference=belly_min_size;task=input'>[features["belly_min_size"] ? features["belly_min_size"] : "Disabled" ]</a>"
dat += "<b>Belly Visibility:</b><a style='display:block;width:100px' href='?_src_=prefs;preference=belly_visibility;task=input'>[features["belly_visibility"]]</a>"
dat += "<b>Egg Stuffing:</b><a style='display:block;width:50px' href='?_src_=prefs;preference=belly_stuffing'>[features["belly_stuffing"] == TRUE ? "Yes" : "No"]</a>"
dat += "</td>"
@@ -2383,9 +2393,9 @@ GLOBAL_LIST_EMPTY(preferences_datums)
var/new_mutantcolor = input(user, "Choose your character's alien/mutant color:", "Character Preference","#"+features["mcolor"]) as color|null
if(new_mutantcolor)
var/temp_hsv = RGBtoHSV(new_mutantcolor)
if(new_mutantcolor == "#000000")
if(new_mutantcolor == "#000000" && features["mcolor"] != pref_species.default_color) //SPLURT EDIT
features["mcolor"] = pref_species.default_color
else if((MUTCOLORS_PARTSONLY in pref_species.species_traits) || ReadHSV(temp_hsv)[3] >= ReadHSV(MINIMUM_MUTANT_COLOR)[3]) // mutantcolors must be bright, but only if they affect the skin
else if((MUTCOLORS_PARTSONLY in pref_species.species_traits) || ReadHSV(temp_hsv)[3] >= ReadHSV(MINIMUM_MUTANT_COLOR)[3] || !CONFIG_GET(flag/character_color_limits)) // mutantcolors must be bright, but only if they affect the skin //SPLURT EDIT
features["mcolor"] = sanitize_hexcolor(new_mutantcolor, 6)
else
to_chat(user, "<span class='danger'>Invalid color. Your color is not bright enough.</span>")
@@ -2394,9 +2404,9 @@ GLOBAL_LIST_EMPTY(preferences_datums)
var/new_mutantcolor = input(user, "Choose your character's secondary alien/mutant color:", "Character Preference","#"+features["mcolor2"]) as color|null
if(new_mutantcolor)
var/temp_hsv = RGBtoHSV(new_mutantcolor)
if(new_mutantcolor == "#000000")
if(new_mutantcolor == "#000000" && features["mcolor2"] != pref_species.default_color) //SPLURT EDIT
features["mcolor2"] = pref_species.default_color
else if((MUTCOLORS_PARTSONLY in pref_species.species_traits) || ReadHSV(temp_hsv)[3] >= ReadHSV(MINIMUM_MUTANT_COLOR)[3]) // mutantcolors must be bright, but only if they affect the skin
else if((MUTCOLORS_PARTSONLY in pref_species.species_traits) || ReadHSV(temp_hsv)[3] >= ReadHSV(MINIMUM_MUTANT_COLOR)[3] || !CONFIG_GET(flag/character_color_limits)) // mutantcolors must be bright, but only if they affect the skin //SPLURT EDIT
features["mcolor2"] = sanitize_hexcolor(new_mutantcolor, 6)
else
to_chat(user, "<span class='danger'>Invalid color. Your color is not bright enough.</span>")
@@ -2405,9 +2415,9 @@ GLOBAL_LIST_EMPTY(preferences_datums)
var/new_mutantcolor = input(user, "Choose your character's tertiary alien/mutant color:", "Character Preference","#"+features["mcolor3"]) as color|null
if(new_mutantcolor)
var/temp_hsv = RGBtoHSV(new_mutantcolor)
if(new_mutantcolor == "#000000")
if(new_mutantcolor == "#000000" && features["mcolor3"] != pref_species.default_color) //SPLURT EDIT
features["mcolor3"] = pref_species.default_color
else if((MUTCOLORS_PARTSONLY in pref_species.species_traits) || ReadHSV(temp_hsv)[3] >= ReadHSV(MINIMUM_MUTANT_COLOR)[3]) // mutantcolors must be bright, but only if they affect the skin
else if((MUTCOLORS_PARTSONLY in pref_species.species_traits) || ReadHSV(temp_hsv)[3] >= ReadHSV(MINIMUM_MUTANT_COLOR)[3] || !CONFIG_GET(flag/character_color_limits)) // mutantcolors must be bright, but only if they affect the skin //SPLURT EDIT
features["mcolor3"] = sanitize_hexcolor(new_mutantcolor, 6)
else
to_chat(user, "<span class='danger'>Invalid color. Your color is not bright enough.</span>")
@@ -2421,9 +2431,9 @@ GLOBAL_LIST_EMPTY(preferences_datums)
var/new_neckfire_color = input(user, "Choose your fire's color:", "Character Preference", "#"+features["neckfire_color"]) as color|null
if(new_neckfire_color)
var/temp_hsv = RGBtoHSV(new_neckfire_color)
if(new_neckfire_color == "#000000")
if(new_neckfire_color == "#000000" && features["neckfire_color"] != pref_species.default_color) //SPLURT EDIT
features["neckfire_color"] = pref_species.default_color
else if(ReadHSV(temp_hsv)[3] >= ReadHSV(MINIMUM_MUTANT_COLOR)[3])
else if(ReadHSV(temp_hsv)[3] >= ReadHSV(MINIMUM_MUTANT_COLOR)[3] || !CONFIG_GET(flag/character_color_limits)) //SPLURT EDIT
features["neckfire_color"] = sanitize_hexcolor(new_neckfire_color, 6)
else
to_chat(user,"<span class='danger'>Invalid color. Your color is not bright enough.</span>")
@@ -2564,7 +2574,7 @@ GLOBAL_LIST_EMPTY(preferences_datums)
if("horns_color")
var/new_horn_color = input(user, "Choose your character's horn colour:", "Character Preference","#"+features["horns_color"]) as color|null
if(new_horn_color)
if (new_horn_color == "#000000")
if (new_horn_color == "#000000" && features["horns_color"] != "85615A") //SPLURT EDIT
features["horns_color"] = "85615A"
else
features["horns_color"] = sanitize_hexcolor(new_horn_color, 6)
@@ -2578,7 +2588,7 @@ GLOBAL_LIST_EMPTY(preferences_datums)
if("wings_color")
var/new_wing_color = input(user, "Choose your character's wing colour:", "Character Preference","#"+features["wings_color"]) as color|null
if(new_wing_color)
if (new_wing_color == "#000000")
if (new_wing_color == "#000000" && features["wings_color"] != "#FFFFFF") //SPLURT EDIT
features["wings_color"] = "#FFFFFF"
else
features["wings_color"] = sanitize_hexcolor(new_wing_color, 6)
@@ -2654,7 +2664,7 @@ GLOBAL_LIST_EMPTY(preferences_datums)
var/custom_tone = input(user, "Choose your custom skin tone:", "Character Preference", default) as color|null
if(custom_tone)
var/temp_hsv = RGBtoHSV(custom_tone)
if(ReadHSV(temp_hsv)[3] < ReadHSV("#333333")[3]) // rgb(50,50,50)
if(ReadHSV(temp_hsv)[3] < ReadHSV("#333333")[3] && CONFIG_GET(flag/character_color_limits)) // rgb(50,50,50) //SPLURT EDIT
to_chat(user,"<span class='danger'>Invalid color. Your color is not bright enough.</span>")
else
use_custom_skin_tone = TRUE
@@ -2749,9 +2759,9 @@ GLOBAL_LIST_EMPTY(preferences_datums)
var/new_feature_color = input(user, "Choose your character's mutant part colour:", "Character Preference","#"+features[href_list["preference"]]) as color|null
if(new_feature_color)
var/temp_hsv = RGBtoHSV(new_feature_color)
if(new_feature_color == "#000000")
if(new_feature_color == "#000000" && features[href_list["preference"]] != pref_species.default_color) //SPLURT EDIT
features[href_list["preference"]] = pref_species.default_color
else if(ReadHSV(temp_hsv)[3] >= ReadHSV(MINIMUM_MUTANT_COLOR)[3])
else if(ReadHSV(temp_hsv)[3] >= ReadHSV(MINIMUM_MUTANT_COLOR)[3] || !CONFIG_GET(flag/character_color_limits)) //SPLURT EDIT
features[href_list["preference"]] = sanitize_hexcolor(new_feature_color, 6)
else
to_chat(user,"<span class='danger'>Invalid color. Your color is not bright enough.</span>")
@@ -2778,9 +2788,9 @@ GLOBAL_LIST_EMPTY(preferences_datums)
var/new_cockcolor = input(user, "Penis color:", "Character Preference","#"+features["cock_color"]) as color|null
if(new_cockcolor)
var/temp_hsv = RGBtoHSV(new_cockcolor)
if(new_cockcolor == "#000000")
if(new_cockcolor == "#000000" && features["cock_color"] != pref_species.default_color) //SPLURT EDIT
features["cock_color"] = pref_species.default_color
else if(ReadHSV(temp_hsv)[3] >= ReadHSV(MINIMUM_MUTANT_COLOR)[3])
else if(ReadHSV(temp_hsv)[3] >= ReadHSV(MINIMUM_MUTANT_COLOR)[3] || !CONFIG_GET(flag/character_color_limits)) //SPLURT EDIT
features["cock_color"] = sanitize_hexcolor(new_cockcolor, 6)
else
to_chat(user,"<span class='danger'>Invalid color. Your color is not bright enough.</span>")
@@ -2825,9 +2835,9 @@ GLOBAL_LIST_EMPTY(preferences_datums)
var/new_ballscolor = input(user, "Testicles Color:", "Character Preference","#"+features["balls_color"]) as color|null
if(new_ballscolor)
var/temp_hsv = RGBtoHSV(new_ballscolor)
if(new_ballscolor == "#000000")
if(new_ballscolor == "#000000" && features["balls_color"] != pref_species.default_color) //SPLURT EDIT
features["balls_color"] = pref_species.default_color
else if(ReadHSV(temp_hsv)[3] >= ReadHSV(MINIMUM_MUTANT_COLOR)[3])
else if(ReadHSV(temp_hsv)[3] >= ReadHSV(MINIMUM_MUTANT_COLOR)[3] || !CONFIG_GET(flag/character_color_limits)) //SPLURT EDIT
features["balls_color"] = sanitize_hexcolor(new_ballscolor, 6)
else
to_chat(user,"<span class='danger'>Invalid color. Your color is not bright enough.</span>")
@@ -2868,9 +2878,9 @@ GLOBAL_LIST_EMPTY(preferences_datums)
var/new_breasts_color = input(user, "Breast Color:", "Character Preference","#"+features["breasts_color"]) as color|null
if(new_breasts_color)
var/temp_hsv = RGBtoHSV(new_breasts_color)
if(new_breasts_color == "#000000")
if(new_breasts_color == "#000000" && features["breasts_color"] != pref_species.default_color) //SPLURT EDIT
features["breasts_color"] = pref_species.default_color
else if(ReadHSV(temp_hsv)[3] >= ReadHSV(MINIMUM_MUTANT_COLOR)[3])
else if(ReadHSV(temp_hsv)[3] >= ReadHSV(MINIMUM_MUTANT_COLOR)[3] || !CONFIG_GET(flag/character_color_limits)) //SPLURT EDIT
features["breasts_color"] = sanitize_hexcolor(new_breasts_color, 6)
else
to_chat(user,"<span class='danger'>Invalid color. Your color is not bright enough.</span>")
@@ -2900,9 +2910,9 @@ GLOBAL_LIST_EMPTY(preferences_datums)
var/new_vagcolor = input(user, "Vagina color:", "Character Preference","#"+features["vag_color"]) as color|null
if(new_vagcolor)
var/temp_hsv = RGBtoHSV(new_vagcolor)
if(new_vagcolor == "#000000")
if(new_vagcolor == "#000000" && features["vag_color"] != pref_species.default_color) //SPLURT EDIT
features["vag_color"] = pref_species.default_color
else if(ReadHSV(temp_hsv)[3] >= ReadHSV(MINIMUM_MUTANT_COLOR)[3])
else if(ReadHSV(temp_hsv)[3] >= ReadHSV(MINIMUM_MUTANT_COLOR)[3] || !CONFIG_GET(flag/character_color_limits)) //SPLURT EDIT
features["vag_color"] = sanitize_hexcolor(new_vagcolor, 6)
else
to_chat(user,"<span class='danger'>Invalid color. Your color is not bright enough.</span>")
@@ -2926,9 +2936,9 @@ GLOBAL_LIST_EMPTY(preferences_datums)
var/new_bellycolor = input(user, "Belly Color:", "Character Preference", "#"+features["belly_color"]) as color|null
if(new_bellycolor)
var/temp_hsv = RGBtoHSV(new_bellycolor)
if(new_bellycolor == "#000000")
if(new_bellycolor == "#000000" && features["belly_color"] != pref_species.default_color) //SPLURT EDIT
features["belly_color"] = pref_species.default_color
else if(ReadHSV(temp_hsv)[3] >= ReadHSV(MINIMUM_MUTANT_COLOR)[3])
else if(ReadHSV(temp_hsv)[3] >= ReadHSV(MINIMUM_MUTANT_COLOR)[3] || !CONFIG_GET(flag/character_color_limits)) //SPLURT EDIT
features["belly_color"] = sanitize_hexcolor(new_bellycolor, 6)
else
to_chat(user,"<span class='danger'>Invalid color. Your color is not bright enough.</span>")
@@ -2937,9 +2947,9 @@ GLOBAL_LIST_EMPTY(preferences_datums)
var/new_buttcolor = input(user, "Butt color:", "Character Preference","#"+features["butt_color"]) as color|null
if(new_buttcolor)
var/temp_hsv = RGBtoHSV(new_buttcolor)
if(new_buttcolor == "#000000")
if(new_buttcolor == "#000000" && features["butt_color"] != pref_species.default_color) //SPLURT EDIT
features["butt_color"] = pref_species.default_color
else if(ReadHSV(temp_hsv)[3] >= ReadHSV(MINIMUM_MUTANT_COLOR)[3])
else if(ReadHSV(temp_hsv)[3] >= ReadHSV(MINIMUM_MUTANT_COLOR)[3] || !CONFIG_GET(flag/character_color_limits)) //SPLURT EDIT
features["butt_color"] = sanitize_hexcolor(new_buttcolor, 6)
else
to_chat(user,"<span class='danger'>Invalid color. Your color is not bright enough.</span>")
@@ -2948,9 +2958,9 @@ GLOBAL_LIST_EMPTY(preferences_datums)
var/new_anuscolor = input(user, "Butthole color:", "Character Preference", "#"+features["anus_color"]) as color|null
if(new_anuscolor)
var/temp_hsv = RGBtoHSV(new_anuscolor)
if(new_anuscolor == "#000000")
if(new_anuscolor == "#000000" && features["anus_color"] != pref_species.default_color) //SPLURT EDIT
features["anus_color"] = pref_species.default_color
else if(ReadHSV(temp_hsv)[3] >= ReadHSV(MINIMUM_MUTANT_COLOR)[3])
else if(ReadHSV(temp_hsv)[3] >= ReadHSV(MINIMUM_MUTANT_COLOR)[3] || !CONFIG_GET(flag/character_color_limits)) //SPLURT EDIT
features["anus_color"] = sanitize_hexcolor(new_anuscolor, 6)
else
to_chat(user, "<span class='danger'>Invalid color. Your color is not bright enough.</span>")
@@ -2990,6 +3000,82 @@ GLOBAL_LIST_EMPTY(preferences_datums)
if(n_vis)
features["belly_visibility"] = n_vis
if("cock_max_length")
var/max_B = CONFIG_GET(number/penis_max_inches_prefs)
var/new_size = input(user, "Max size:\n([features["cock_length"]]-[max_B])(0 = disabled)", "Character Preference") as num|null
if(new_size)
features["cock_max_length"] = clamp(round(new_size), features["cock_length"], max_B)
else
features -= "cock_max_length"
if("balls_max_size")
var/new_size = input(user, "Max size:\n([BALLS_SIZE_MIN]-[BALLS_SIZE_MAX])(0 = disabled)", "Character Preference") as num|null
if(new_size)
features["balls_max_size"] = clamp(round(new_size), BALLS_SIZE_MIN, BALLS_SIZE_MAX)
else
features -= "balls_max_size"
if("breasts_max_size")
var/new_size = input(user, "Breast Max Size (cancel to disable)", "Character Preference") as null|anything in GLOB.breast_values
if(new_size)
features["breasts_max_size"] = new_size
else
features -= "breasts_max_size"
if("belly_max_size")
var/max_B = CONFIG_GET(number/belly_max_size_prefs)
var/new_size = input(user, "Max size:\n([features["belly_size"]]-[max_B])(0 = disabled)", "Character Preference") as num|null
if(new_size)
features["belly_max_size"] = clamp(round(new_size), features["belly_size"], max_B)
else
features -= "belly_max_size"
if("butt_max_size")
var/max_B = CONFIG_GET(number/butt_max_size_prefs)
var/new_size = input(user, "Max size:\n([features["butt_size"]]-[max_B])(0 = disabled)", "Character Preference") as num|null
if(new_size)
features["butt_max_size"] = clamp(round(new_size), features["butt_size"], max_B)
else
features -= "butt_max_size"
if("cock_min_length")
var/min_B = CONFIG_GET(number/penis_min_inches_prefs)
var/new_size = input(user, "Min size:\n([min_B]-[features["cock_length"]])(0 = disabled)", "Character Preference") as num|null
if(new_size)
features["cock_min_length"] = clamp(round(new_size), min_B, features["cock_length"])
else
features -= "cock_min_length"
if("balls_min_size")
var/new_size = input(user, "Min size:\n([BALLS_SIZE_MIN]-[BALLS_SIZE_MAX])(0 = disabled)", "Character Preference") as num|null
if(new_size)
features["balls_min_size"] = clamp(round(new_size), BALLS_SIZE_MIN, BALLS_SIZE_MAX)
else
features -= "balls_min_size"
if("breasts_min_size")
var/new_size = input(user, "Breast Min Size (cancel to disable)", "Character Preference") as null|anything in GLOB.breast_values
if(new_size)
features["breasts_min_size"] = new_size
else
features -= "breasts_min_size"
if("belly_min_size")
var/min_B = CONFIG_GET(number/belly_min_size_prefs)
var/new_size = input(user, "Min size:\n([min_B]-[features["belly_size"]])(0 = disabled)", "Character Preference") as num|null
if(new_size)
features["belly_min_size"] = clamp(round(new_size), min_B, features["belly_size"])
else
features -= "belly_min_size"
if("butt_min_size")
var/min_B = CONFIG_GET(number/butt_min_size_prefs)
var/new_size = input(user, "Min size:\n([min_B]-[features["butt_size"]])(0 = disabled)", "Character Preference") as num|null
if(new_size)
features["butt_min_size"] = clamp(round(new_size), min_B, features["butt_size"])
else
features -= "butt_min_size"
if("ooccolor")
var/new_ooccolor = input(user, "Choose your OOC colour:", "Game Preference",ooccolor) as color|null
if(new_ooccolor)
@@ -3252,7 +3338,7 @@ GLOBAL_LIST_EMPTY(preferences_datums)
var/new_marking_color = input(user, "Choose your character's marking color:", "Character Preference","#"+color_list[color_number]) as color|null
if(new_marking_color)
var/temp_hsv = RGBtoHSV(new_marking_color)
if((MUTCOLORS_PARTSONLY in pref_species.species_traits) || ReadHSV(temp_hsv)[3] >= ReadHSV(MINIMUM_MUTANT_COLOR)[3]) // mutantcolors must be bright, but only if they affect the skin
if((MUTCOLORS_PARTSONLY in pref_species.species_traits) || ReadHSV(temp_hsv)[3] >= ReadHSV(MINIMUM_MUTANT_COLOR)[3] || !CONFIG_GET(flag/character_color_limits)) // mutantcolors must be bright, but only if they affect the skin //SPLURT EDIT
color_list[color_number] = "#[sanitize_hexcolor(new_marking_color, 6)]"
else
to_chat(user, "<span class='danger'>Invalid color. Your color is not bright enough.</span>")
@@ -4173,3 +4259,4 @@ GLOBAL_LIST_EMPTY(preferences_datums)
#undef DEFAULT_SLOT_AMT
#undef HANDS_SLOT_AMT
#undef BACKPACK_SLOT_AMT
@@ -1054,6 +1054,28 @@ SAVEFILE UPDATING/VERSIONING - 'Simplified', or rather, more coder-friendly ~Car
S["feature_inert_eggs"] >> features["inert_eggs"]
if(S["features_cock_max_length"])
S["features_cock_max_length"] >> features["cock_max_length"]
if(S["features_balls_max_size"])
S["features_balls_max_size"] >> features["balls_max_size"]
if(S["features_breasts_max_size"])
S["features_breasts_max_size"] >> features["breasts_max_size"]
if(S["features_belly_max_size"])
S["features_belly_max_size"] >> features["belly_max_size"]
if(S["features_butt_max_size"])
S["features_butt_max_size"] >> features["butt_max_size"]
if(S["features_cock_min_length"])
S["features_cock_min_length"] >> features["cock_min_length"]
if(S["features_balls_min_size"])
S["features_balls_min_size"] >> features["balls_min_size"]
if(S["features_breasts_min_size"])
S["features_breasts_min_size"] >> features["breasts_min_size"]
if(S["features_belly_min_size"])
S["features_belly_min_size"] >> features["belly_min_size"]
if(S["features_butt_min_size"])
S["features_butt_min_size"] >> features["butt_min_size"]
var/char_vr_path = "[vr_path]/character_[default_slot]_v2.json"
if(fexists(char_vr_path))
var/list/json_from_file = json_decode(file2text(char_vr_path))
@@ -1413,6 +1435,19 @@ SAVEFILE UPDATING/VERSIONING - 'Simplified', or rather, more coder-friendly ~Car
WRITE_FILE(S["feature_inert_eggs"], features["inert_eggs"])
WRITE_FILE(S["features_cock_max_length"], features["cock_max_length"])
WRITE_FILE(S["features_balls_max_size"], features["balls_max_size"])
WRITE_FILE(S["features_breasts_max_size"], features["breasts_max_size"])
WRITE_FILE(S["features_belly_max_size"], features["belly_max_size"])
WRITE_FILE(S["features_butt_max_size"], features["butt_max_size"])
WRITE_FILE(S["features_cock_min_length"], features["cock_min_length"])
WRITE_FILE(S["features_balls_min_size"], features["balls_min_size"])
WRITE_FILE(S["features_breasts_min_size"], features["breasts_min_size"])
WRITE_FILE(S["features_belly_min_size"], features["belly_min_size"])
WRITE_FILE(S["features_butt_min_size"], features["butt_min_size"])
WRITE_FILE(S["feature_neckfire"], features["neckfire"])
WRITE_FILE(S["feature_neckfire_color"], features["neckfire_color"])
@@ -29,7 +29,7 @@
var/new_color = input(owner, "Choose your skin color:", "Race change","#"+H.dna.features["mcolor"]) as color|null
if(new_color)
var/temp_hsv = RGBtoHSV(new_color)
if(ReadHSV(temp_hsv)[3] >= ReadHSV(MINIMUM_MUTANT_COLOR)[3]) // mutantcolors must be bright
if(ReadHSV(temp_hsv)[3] >= ReadHSV(MINIMUM_MUTANT_COLOR)[3] || !CONFIG_GET(flag/character_color_limits)) // mutantcolors must be bright //SPLURT EDIT
H.dna.features["mcolor"] = sanitize_hexcolor(new_color, 6)
H.update_body()
H.update_hair()
@@ -117,7 +117,7 @@
if(new_snout)
H.dna.features["mam_snouts"] = new_snout
H.update_body()
else if (select_alteration == "Wings")
var/new_color = input(owner, "Choose your wing color:", "Race change","#"+H.dna.features["wings_color"]) as color|null
if(new_color)
+20 -2
View File
@@ -404,7 +404,7 @@
*
* overridden here and in /mob/dead/observer for different point span classes and sanity checks
*/
/mob/verb/pointed(atom/A as mob|obj|turf in fov_view())
/mob/verb/pointed(atom/A as mob|obj|turf in fov_view(), params = "" as text)
set name = "Point To"
set category = "Object"
@@ -419,7 +419,25 @@
var/turf/our_tile = get_turf(src)
var/obj/visual = new /obj/effect/temp_visual/point(our_tile, invisibility)
animate(visual, pixel_x = (tile.x - our_tile.x) * world.icon_size + A.pixel_x, pixel_y = (tile.y - our_tile.y) * world.icon_size + A.pixel_y, time = 1.7, easing = EASE_OUT)
/// Set position
var/final_x = (tile.x - our_tile.x) * world.icon_size + A.pixel_x
var/final_y = (tile.y - our_tile.y) * world.icon_size + A.pixel_y
var/list/click_params = params2list(params)
if(length(click_params) && click_params["screen-loc"])
var/list/actual_view = getviewsize(client ? client.view : world.view)
var/list/split_coords = splittext(click_params["screen-loc"], ",")
final_x = (text2num(splittext(split_coords[1], ":")[1]) - actual_view[1] / 2) * world.icon_size + (text2num(splittext(split_coords[1], ":")[2]) - world.icon_size)
final_y = (text2num(splittext(split_coords[2], ":")[1]) - actual_view[2] / 2) * world.icon_size + (text2num(splittext(split_coords[2], ":")[2]) - world.icon_size)
//
/// Set rotation
var/matrix/rotated_matrix = new()
rotated_matrix.TurnTo(0, Get_Pixel_Angle(-final_y, -final_x))
visual.transform = rotated_matrix
//
animate(visual, pixel_x = final_x, pixel_y = final_y, time = 1.7, easing = EASE_OUT)
SEND_SIGNAL(src, COMSIG_MOB_POINTED, A)
return TRUE
+3
View File
@@ -13,6 +13,9 @@
#include "v5\_defines.dm"
#include "v5\api.dm"
#include "v5\bridge.dm"
#include "v5\chunking.dm"
#include "v5\commands.dm"
#include "v5\serializers.dm"
#include "v5\topic.dm"
#include "v5\undefs.dm"
+3
View File
@@ -5,6 +5,9 @@ This DMAPI implements bridge requests using HTTP GET requests to TGS. It has no
- [__interop_version.dm](./__interop_version.dm) contains the version of the API used between the DMAPI and TGS.
- [_defines.dm](./_defines.dm) contains constant definitions.
- [api.dm](./api.dm) contains the bulk of the API code.
- [bridge.dm](./bridge.dm) contains functions related to making bridge requests.
- [chunking.dm](./chunking.dm) contains common function for splitting large raw data sets into chunks BYOND can natively process.
- [commands.dm](./commands.dm) contains functions relating to `/datum/tgs_chat_command`s.
- [serializers.dm](./serializers.dm) contains function to help convert interop `/datum`s into a JSON encodable `list()` format.
- [topic.dm](./topic.dm) contains functions related to processing topic requests.
- [undefs.dm](./undefs.dm) Undoes the work of `_defines.dm`.
+1 -1
View File
@@ -1 +1 @@
"5.5.0"
"5.6.0"
+15
View File
@@ -4,16 +4,29 @@
#define DMAPI5_BRIDGE_DATA "data"
#define DMAPI5_TOPIC_DATA "tgs_data"
#define DMAPI5_BRIDGE_REQUEST_LIMIT 8198
#define DMAPI5_TOPIC_REQUEST_LIMIT 65529
#define DMAPI5_TOPIC_RESPONSE_LIMIT 65528
#define DMAPI5_BRIDGE_COMMAND_PORT_UPDATE 0
#define DMAPI5_BRIDGE_COMMAND_STARTUP 1
#define DMAPI5_BRIDGE_COMMAND_PRIME 2
#define DMAPI5_BRIDGE_COMMAND_REBOOT 3
#define DMAPI5_BRIDGE_COMMAND_KILL 4
#define DMAPI5_BRIDGE_COMMAND_CHAT_SEND 5
#define DMAPI5_BRIDGE_COMMAND_CHUNK 6
#define DMAPI5_PARAMETER_ACCESS_IDENTIFIER "accessIdentifier"
#define DMAPI5_PARAMETER_CUSTOM_COMMANDS "customCommands"
#define DMAPI5_CHUNK "chunk"
#define DMAPI5_CHUNK_PAYLOAD "payload"
#define DMAPI5_CHUNK_TOTAL "totalChunks"
#define DMAPI5_CHUNK_SEQUENCE_ID "sequenceId"
#define DMAPI5_CHUNK_PAYLOAD_ID "payloadId"
#define DMAPI5_MISSING_CHUNKS "missingChunks"
#define DMAPI5_RESPONSE_ERROR_MESSAGE "errorMessage"
#define DMAPI5_BRIDGE_PARAMETER_COMMAND_TYPE "commandType"
@@ -64,6 +77,8 @@
#define DMAPI5_TOPIC_COMMAND_SERVER_PORT_UPDATE 6
#define DMAPI5_TOPIC_COMMAND_HEARTBEAT 7
#define DMAPI5_TOPIC_COMMAND_WATCHDOG_REATTACH 8
#define DMAPI5_TOPIC_COMMAND_SEND_CHUNK 9
#define DMAPI5_TOPIC_COMMAND_RECEIVE_CHUNK 10
#define DMAPI5_TOPIC_PARAMETER_COMMAND_TYPE "commandType"
#define DMAPI5_TOPIC_PARAMETER_CHAT_COMMAND "chatCommand"
+7 -161
View File
@@ -17,6 +17,11 @@
var/initialized = FALSE
var/chunked_requests = 0
var/list/chunked_topics = list()
var/detached = FALSE
/datum/tgs_api/v5/ApiVersion()
return new /datum/tgs_version(
#include "__interop_version.dm"
@@ -97,13 +102,6 @@
/datum/tgs_api/v5/OnInitializationComplete()
Bridge(DMAPI5_BRIDGE_COMMAND_PRIME)
/datum/tgs_api/v5/proc/TopicResponse(error_message = null)
var/list/response = list()
if(error_message)
response[DMAPI5_RESPONSE_ERROR_MESSAGE] = error_message
return json_encode(response)
return "{}"
/datum/tgs_api/v5/OnTopic(T)
RequireInitialBridgeResponse()
var/list/params = params2list(T)
@@ -111,163 +109,11 @@
if(!json)
return FALSE // continue to /world/Topic
var/list/topic_parameters = json_decode(json)
if(!topic_parameters)
return TopicResponse("Invalid topic parameters json!");
if(!initialized)
TGS_WARNING_LOG("Missed topic due to not being initialized: [T]")
TGS_WARNING_LOG("Missed topic due to not being initialized: [json]")
return TRUE // too early to handle, but it's still our responsibility
var/their_sCK = topic_parameters[DMAPI5_PARAMETER_ACCESS_IDENTIFIER]
if(their_sCK != access_identifier)
return TopicResponse("Failed to decode [DMAPI5_PARAMETER_ACCESS_IDENTIFIER] from: [json]!");
var/command = topic_parameters[DMAPI5_TOPIC_PARAMETER_COMMAND_TYPE]
if(!isnum(command))
return TopicResponse("Failed to decode [DMAPI5_TOPIC_PARAMETER_COMMAND_TYPE] from: [json]!")
switch(command)
if(DMAPI5_TOPIC_COMMAND_CHAT_COMMAND)
intercepted_message_queue = list()
var/result = HandleCustomCommand(topic_parameters[DMAPI5_TOPIC_PARAMETER_CHAT_COMMAND])
if(!result)
result = TopicResponse("Error running chat command!")
result[DMAPI5_TOPIC_RESPONSE_CHAT_RESPONSES] = intercepted_message_queue
intercepted_message_queue = null
return result
if(DMAPI5_TOPIC_COMMAND_EVENT_NOTIFICATION)
intercepted_message_queue = list()
var/list/event_notification = topic_parameters[DMAPI5_TOPIC_PARAMETER_EVENT_NOTIFICATION]
if(!istype(event_notification))
return TopicResponse("Invalid [DMAPI5_TOPIC_PARAMETER_EVENT_NOTIFICATION]!")
var/event_type = event_notification[DMAPI5_EVENT_NOTIFICATION_TYPE]
if(!isnum(event_type))
return TopicResponse("Invalid or missing [DMAPI5_EVENT_NOTIFICATION_TYPE]!")
var/list/event_parameters = event_notification[DMAPI5_EVENT_NOTIFICATION_PARAMETERS]
if(event_parameters && !istype(event_parameters))
return TopicResponse("Invalid or missing [DMAPI5_EVENT_NOTIFICATION_PARAMETERS]!")
var/list/event_call = list(event_type)
if(event_parameters)
event_call += event_parameters
if(event_handler != null)
event_handler.HandleEvent(arglist(event_call))
var/list/response = list()
response[DMAPI5_TOPIC_RESPONSE_CHAT_RESPONSES] = intercepted_message_queue
intercepted_message_queue = null
return json_encode(response)
if(DMAPI5_TOPIC_COMMAND_CHANGE_PORT)
var/new_port = topic_parameters[DMAPI5_TOPIC_PARAMETER_NEW_PORT]
if (!isnum(new_port) || !(new_port > 0))
return TopicResponse("Invalid or missing [DMAPI5_TOPIC_PARAMETER_NEW_PORT]]")
if(event_handler != null)
event_handler.HandleEvent(TGS_EVENT_PORT_SWAP, new_port)
//the topic still completes, miraculously
//I honestly didn't believe byond could do it without exploding
if(!world.OpenPort(new_port))
return TopicResponse("Port change failed!")
return TopicResponse()
if(DMAPI5_TOPIC_COMMAND_CHANGE_REBOOT_STATE)
var/new_reboot_mode = topic_parameters[DMAPI5_TOPIC_PARAMETER_NEW_REBOOT_STATE]
if(!isnum(new_reboot_mode))
return TopicResponse("Invalid or missing [DMAPI5_TOPIC_PARAMETER_NEW_REBOOT_STATE]!")
if(event_handler != null)
event_handler.HandleEvent(TGS_EVENT_REBOOT_MODE_CHANGE, reboot_mode, new_reboot_mode)
reboot_mode = new_reboot_mode
return TopicResponse()
if(DMAPI5_TOPIC_COMMAND_INSTANCE_RENAMED)
var/new_instance_name = topic_parameters[DMAPI5_TOPIC_PARAMETER_NEW_INSTANCE_NAME]
if(!istext(new_instance_name))
return TopicResponse("Invalid or missing [DMAPI5_TOPIC_PARAMETER_NEW_INSTANCE_NAME]!")
if(event_handler != null)
event_handler.HandleEvent(TGS_EVENT_INSTANCE_RENAMED, new_instance_name)
instance_name = new_instance_name
return TopicResponse()
if(DMAPI5_TOPIC_COMMAND_CHAT_CHANNELS_UPDATE)
var/list/chat_update_json = topic_parameters[DMAPI5_TOPIC_PARAMETER_CHAT_UPDATE]
if(!istype(chat_update_json))
return TopicResponse("Invalid or missing [DMAPI5_TOPIC_PARAMETER_CHAT_UPDATE]!")
DecodeChannels(chat_update_json)
return TopicResponse()
if(DMAPI5_TOPIC_COMMAND_SERVER_PORT_UPDATE)
var/new_port = topic_parameters[DMAPI5_TOPIC_PARAMETER_NEW_PORT]
if (!isnum(new_port) || !(new_port > 0))
return TopicResponse("Invalid or missing [DMAPI5_TOPIC_PARAMETER_NEW_PORT]]")
server_port = new_port
return TopicResponse()
if(DMAPI5_TOPIC_COMMAND_HEARTBEAT)
return TopicResponse()
if(DMAPI5_TOPIC_COMMAND_WATCHDOG_REATTACH)
var/new_port = topic_parameters[DMAPI5_TOPIC_PARAMETER_NEW_PORT]
var/error_message = null
if (new_port != null)
if (!isnum(new_port) || !(new_port > 0))
error_message = "Invalid [DMAPI5_TOPIC_PARAMETER_NEW_PORT]]"
else
server_port = new_port
var/new_version_string = topic_parameters[DMAPI5_TOPIC_PARAMETER_NEW_SERVER_VERSION]
if (!istext(new_version_string))
if(error_message != null)
error_message += ", "
error_message += "Invalid or missing [DMAPI5_TOPIC_PARAMETER_NEW_SERVER_VERSION]]"
else
var/datum/tgs_version/new_version = new(new_version_string)
if (event_handler)
event_handler.HandleEvent(TGS_EVENT_WATCHDOG_REATTACH, new_version)
version = new_version
return json_encode(list(DMAPI5_RESPONSE_ERROR_MESSAGE = error_message, DMAPI5_PARAMETER_CUSTOM_COMMANDS = ListCustomCommands()))
return TopicResponse("Unknown command: [command]")
/datum/tgs_api/v5/proc/Bridge(command, list/data)
if(!data)
data = list()
data[DMAPI5_BRIDGE_PARAMETER_COMMAND_TYPE] = command
data[DMAPI5_PARAMETER_ACCESS_IDENTIFIER] = access_identifier
var/json = json_encode(data)
var/encoded_json = url_encode(json)
// This is an infinite sleep until we get a response
var/export_response = world.Export("http://127.0.0.1:[server_port]/Bridge?[DMAPI5_BRIDGE_DATA]=[encoded_json]")
if(!export_response)
TGS_ERROR_LOG("Failed export request: [json]")
return
var/response_json = file2text(export_response["CONTENT"])
if(!response_json)
TGS_ERROR_LOG("Failed export request, missing content!")
return
var/list/bridge_response = json_decode(response_json)
if(!bridge_response)
TGS_ERROR_LOG("Failed export request, bad json: [response_json]")
return
var/error = bridge_response[DMAPI5_RESPONSE_ERROR_MESSAGE]
if(error)
TGS_ERROR_LOG("Failed export request, bad request: [error]")
return
return bridge_response
return ProcessTopicJson(json, TRUE)
/datum/tgs_api/v5/OnReboot()
var/list/result = Bridge(DMAPI5_BRIDGE_COMMAND_REBOOT)
+95
View File
@@ -0,0 +1,95 @@
/datum/tgs_api/v5/proc/Bridge(command, list/data)
if(!data)
data = list()
var/single_bridge_request = CreateBridgeRequest(command, data)
if(length(single_bridge_request) <= DMAPI5_BRIDGE_REQUEST_LIMIT)
return PerformBridgeRequest(single_bridge_request)
// chunking required
var/payload_id = ++chunked_requests
var/raw_data = CreateBridgeData(command, data, FALSE)
var/list/chunk_requests = GenerateChunks(raw_data, TRUE)
var/list/response
for(var/bridge_request in chunk_requests)
response = PerformBridgeRequest(bridge_request)
if(!response)
// Abort
return
var/list/missing_sequence_ids = response[DMAPI5_MISSING_CHUNKS]
if(length(missing_sequence_ids))
do
TGS_WARNING_LOG("Server is still missing some chunks of bridge P[payload_id]! Sending missing chunks...")
if(!istype(missing_sequence_ids))
TGS_ERROR_LOG("Did not receive a list() for [DMAPI5_MISSING_CHUNKS]!")
return
for(var/missing_sequence_id in missing_sequence_ids)
if(!isnum(missing_sequence_id))
TGS_ERROR_LOG("Did not receive a num in [DMAPI5_MISSING_CHUNKS]!")
return
var/missing_chunk_request = chunk_requests[missing_sequence_id + 1]
response = PerformBridgeRequest(missing_chunk_request)
if(!response)
// Abort
return
missing_sequence_ids = response[DMAPI5_MISSING_CHUNKS]
while(length(missing_sequence_ids))
return response
/datum/tgs_api/v5/proc/CreateBridgeRequest(command, list/data)
var/json = CreateBridgeData(command, data, TRUE)
var/encoded_json = url_encode(json)
var/url = "http://127.0.0.1:[server_port]/Bridge?[DMAPI5_BRIDGE_DATA]=[encoded_json]"
return url
/datum/tgs_api/v5/proc/CreateBridgeData(command, list/data, needs_auth)
data[DMAPI5_BRIDGE_PARAMETER_COMMAND_TYPE] = command
if(needs_auth)
data[DMAPI5_PARAMETER_ACCESS_IDENTIFIER] = access_identifier
var/json = json_encode(data)
return json
/datum/tgs_api/v5/proc/PerformBridgeRequest(bridge_request)
if(detached)
// Wait up to one minute
for(var/i in 1 to 600)
sleep(1)
if(!detached)
break
// dad went out for milk cigarettes 20 years ago...
if(i == 600)
detached = FALSE
// This is an infinite sleep until we get a response
var/export_response = world.Export(bridge_request)
if(!export_response)
TGS_ERROR_LOG("Failed bridge request: [bridge_request]")
return
var/response_json = file2text(export_response["CONTENT"])
if(!response_json)
TGS_ERROR_LOG("Failed bridge request, missing content!")
return
var/list/bridge_response = json_decode(response_json)
if(!bridge_response)
TGS_ERROR_LOG("Failed bridge request, bad json: [response_json]")
return
var/error = bridge_response[DMAPI5_RESPONSE_ERROR_MESSAGE]
if(error)
TGS_ERROR_LOG("Failed bridge request, bad request: [error]")
return
return bridge_response
+43
View File
@@ -0,0 +1,43 @@
/datum/tgs_api/v5/proc/GenerateChunks(payload, bridge)
var/limit = bridge ? DMAPI5_BRIDGE_REQUEST_LIMIT : DMAPI5_TOPIC_RESPONSE_LIMIT
var/payload_id = ++chunked_requests
var/data_length = length(payload)
var/chunk_count
var/list/chunk_requests
for(chunk_count = 2; !chunk_requests; ++chunk_count)
var/max_chunk_size = -round(-(data_length / chunk_count))
if(max_chunk_size > limit)
continue
chunk_requests = list()
for(var/i in 1 to chunk_count)
var/start_index = 1 + ((i - 1) * max_chunk_size)
if (start_index > data_length)
break
var/end_index = min(1 + (i * max_chunk_size), data_length + 1)
var/chunk_payload = copytext(payload, start_index, end_index)
// sequence IDs in interop chunking are always zero indexed
var/list/chunk = list(DMAPI5_CHUNK_PAYLOAD_ID = payload_id, DMAPI5_CHUNK_SEQUENCE_ID = (i - 1), DMAPI5_CHUNK_TOTAL = chunk_count, DMAPI5_CHUNK_PAYLOAD = chunk_payload)
var/chunk_request = list(DMAPI5_CHUNK = chunk)
var/chunk_length
if(bridge)
chunk_request = CreateBridgeRequest(DMAPI5_BRIDGE_COMMAND_CHUNK, chunk_request)
chunk_length = length(chunk_request)
else
chunk_request = list(chunk_request) // wrap for adding to list
chunk_length = length(json_encode(chunk_request))
if(chunk_length > limit)
// Screwed by encoding, no way to preempt it though
chunk_requests = null
break
chunk_requests += chunk_request
return chunk_requests
+2 -2
View File
@@ -36,10 +36,10 @@
var/datum/tgs_message_content/response = sc.Run(u, params)
response = UpgradeDeprecatedCommandResponse(response, command)
var/list/topic_response = list()
var/list/topic_response = TopicResponse()
topic_response[DMAPI5_TOPIC_RESPONSE_COMMAND_RESPONSE_MESSAGE] = response?.text
topic_response[DMAPI5_TOPIC_RESPONSE_COMMAND_RESPONSE] = response?._interop_serialize()
return json_encode(topic_response)
return topic_response
return TopicResponse("Unknown custom chat command: [command]!")
// Common proc b/c it's used by the V3/V4 APIs
+258
View File
@@ -0,0 +1,258 @@
/datum/tgs_api/v5/proc/TopicResponse(error_message = null)
var/list/response = list()
if(error_message)
response[DMAPI5_RESPONSE_ERROR_MESSAGE] = error_message
return response
/datum/tgs_api/v5/proc/ProcessTopicJson(json, check_access_identifier)
var/list/result = ProcessRawTopic(json, check_access_identifier)
if(!result)
result = TopicResponse("Runtime error!")
else if(!length(result))
return "{}" // quirk of json_encode is an empty list returns "[]"
var/response_json = json_encode(result)
if(length(response_json) > DMAPI5_TOPIC_RESPONSE_LIMIT)
// cache response chunks and send the first
var/list/chunks = GenerateChunks(response_json, FALSE)
var/payload_id = chunks[1][DMAPI5_CHUNK][DMAPI5_CHUNK_PAYLOAD_ID]
var/cache_key = ResponseTopicChunkCacheKey(payload_id)
chunked_topics[cache_key] = chunks
response_json = json_encode(chunks[1])
return response_json
/datum/tgs_api/v5/proc/ProcessRawTopic(json, check_access_identifier)
var/list/topic_parameters = json_decode(json)
if(!topic_parameters)
return TopicResponse("Invalid topic parameters json: [json]!");
var/their_sCK = topic_parameters[DMAPI5_PARAMETER_ACCESS_IDENTIFIER]
if(check_access_identifier && their_sCK != access_identifier)
return TopicResponse("Failed to decode [DMAPI5_PARAMETER_ACCESS_IDENTIFIER]!")
var/command = topic_parameters[DMAPI5_TOPIC_PARAMETER_COMMAND_TYPE]
if(!isnum(command))
return TopicResponse("Failed to decode [DMAPI5_TOPIC_PARAMETER_COMMAND_TYPE]!")
return ProcessTopicCommand(command, topic_parameters)
/datum/tgs_api/v5/proc/ResponseTopicChunkCacheKey(payload_id)
return "response[payload_id]"
/datum/tgs_api/v5/proc/ProcessTopicCommand(command, list/topic_parameters)
switch(command)
if(DMAPI5_TOPIC_COMMAND_CHAT_COMMAND)
intercepted_message_queue = list()
var/list/result = HandleCustomCommand(topic_parameters[DMAPI5_TOPIC_PARAMETER_CHAT_COMMAND])
if(!result)
result = TopicResponse("Error running chat command!")
result[DMAPI5_TOPIC_RESPONSE_CHAT_RESPONSES] = intercepted_message_queue
intercepted_message_queue = null
return result
if(DMAPI5_TOPIC_COMMAND_EVENT_NOTIFICATION)
intercepted_message_queue = list()
var/list/event_notification = topic_parameters[DMAPI5_TOPIC_PARAMETER_EVENT_NOTIFICATION]
if(!istype(event_notification))
return TopicResponse("Invalid [DMAPI5_TOPIC_PARAMETER_EVENT_NOTIFICATION]!")
var/event_type = event_notification[DMAPI5_EVENT_NOTIFICATION_TYPE]
if(!isnum(event_type))
return TopicResponse("Invalid or missing [DMAPI5_EVENT_NOTIFICATION_TYPE]!")
var/list/event_parameters = event_notification[DMAPI5_EVENT_NOTIFICATION_PARAMETERS]
if(event_parameters && !istype(event_parameters))
return TopicResponse("Invalid or missing [DMAPI5_EVENT_NOTIFICATION_PARAMETERS]!")
var/list/event_call = list(event_type)
if (event_type == TGS_EVENT_WATCHDOG_DETACH)
detached = TRUE
if(event_parameters)
event_call += event_parameters
if(event_handler != null)
event_handler.HandleEvent(arglist(event_call))
var/list/response = TopicResponse()
response[DMAPI5_TOPIC_RESPONSE_CHAT_RESPONSES] = intercepted_message_queue
intercepted_message_queue = null
return response
if(DMAPI5_TOPIC_COMMAND_CHANGE_PORT)
var/new_port = topic_parameters[DMAPI5_TOPIC_PARAMETER_NEW_PORT]
if (!isnum(new_port) || !(new_port > 0))
return TopicResponse("Invalid or missing [DMAPI5_TOPIC_PARAMETER_NEW_PORT]]")
if(event_handler != null)
event_handler.HandleEvent(TGS_EVENT_PORT_SWAP, new_port)
//the topic still completes, miraculously
//I honestly didn't believe byond could do it without exploding
if(!world.OpenPort(new_port))
return TopicResponse("Port change failed!")
return TopicResponse()
if(DMAPI5_TOPIC_COMMAND_CHANGE_REBOOT_STATE)
var/new_reboot_mode = topic_parameters[DMAPI5_TOPIC_PARAMETER_NEW_REBOOT_STATE]
if(!isnum(new_reboot_mode))
return TopicResponse("Invalid or missing [DMAPI5_TOPIC_PARAMETER_NEW_REBOOT_STATE]!")
if(event_handler != null)
event_handler.HandleEvent(TGS_EVENT_REBOOT_MODE_CHANGE, reboot_mode, new_reboot_mode)
reboot_mode = new_reboot_mode
return TopicResponse()
if(DMAPI5_TOPIC_COMMAND_INSTANCE_RENAMED)
var/new_instance_name = topic_parameters[DMAPI5_TOPIC_PARAMETER_NEW_INSTANCE_NAME]
if(!istext(new_instance_name))
return TopicResponse("Invalid or missing [DMAPI5_TOPIC_PARAMETER_NEW_INSTANCE_NAME]!")
if(event_handler != null)
event_handler.HandleEvent(TGS_EVENT_INSTANCE_RENAMED, new_instance_name)
instance_name = new_instance_name
return TopicResponse()
if(DMAPI5_TOPIC_COMMAND_CHAT_CHANNELS_UPDATE)
var/list/chat_update_json = topic_parameters[DMAPI5_TOPIC_PARAMETER_CHAT_UPDATE]
if(!istype(chat_update_json))
return TopicResponse("Invalid or missing [DMAPI5_TOPIC_PARAMETER_CHAT_UPDATE]!")
DecodeChannels(chat_update_json)
return TopicResponse()
if(DMAPI5_TOPIC_COMMAND_SERVER_PORT_UPDATE)
var/new_port = topic_parameters[DMAPI5_TOPIC_PARAMETER_NEW_PORT]
if (!isnum(new_port) || !(new_port > 0))
return TopicResponse("Invalid or missing [DMAPI5_TOPIC_PARAMETER_NEW_PORT]]")
server_port = new_port
return TopicResponse()
if(DMAPI5_TOPIC_COMMAND_HEARTBEAT)
return TopicResponse()
if(DMAPI5_TOPIC_COMMAND_WATCHDOG_REATTACH)
detached = FALSE
var/new_port = topic_parameters[DMAPI5_TOPIC_PARAMETER_NEW_PORT]
var/error_message = null
if (new_port != null)
if (!isnum(new_port) || !(new_port > 0))
error_message = "Invalid [DMAPI5_TOPIC_PARAMETER_NEW_PORT]]"
else
server_port = new_port
var/new_version_string = topic_parameters[DMAPI5_TOPIC_PARAMETER_NEW_SERVER_VERSION]
if (!istext(new_version_string))
if(error_message != null)
error_message += ", "
error_message += "Invalid or missing [DMAPI5_TOPIC_PARAMETER_NEW_SERVER_VERSION]]"
else
var/datum/tgs_version/new_version = new(new_version_string)
if (event_handler)
event_handler.HandleEvent(TGS_EVENT_WATCHDOG_REATTACH, new_version)
version = new_version
var/list/reattach_response = TopicResponse(error_message)
reattach_response[DMAPI5_PARAMETER_CUSTOM_COMMANDS] = ListCustomCommands()
return reattach_response
if(DMAPI5_TOPIC_COMMAND_SEND_CHUNK)
var/list/chunk = topic_parameters[DMAPI5_CHUNK]
if(!istype(chunk))
return TopicResponse("Invalid [DMAPI5_CHUNK]!")
var/payload_id = chunk[DMAPI5_CHUNK_PAYLOAD_ID]
if(!isnum(payload_id))
return TopicResponse("[DMAPI5_CHUNK_PAYLOAD_ID] is not a number!")
// Always updated the highest known payload ID
chunked_requests = max(chunked_requests, payload_id)
var/sequence_id = chunk[DMAPI5_CHUNK_SEQUENCE_ID]
if(!isnum(sequence_id))
return TopicResponse("[DMAPI5_CHUNK_SEQUENCE_ID] is not a number!")
var/total_chunks = chunk[DMAPI5_CHUNK_TOTAL]
if(!isnum(total_chunks))
return TopicResponse("[DMAPI5_CHUNK_TOTAL] is not a number!")
if(total_chunks == 0)
return TopicResponse("[DMAPI5_CHUNK_TOTAL] is zero!")
var/payload = chunk[DMAPI5_CHUNK_PAYLOAD]
if(!istext(payload))
return TopicResponse("[DMAPI5_CHUNK_PAYLOAD] is not text!")
var/cache_key = "request[payload_id]"
var/payloads = chunked_topics[cache_key]
if(!payloads)
payloads = new /list(total_chunks)
chunked_topics[cache_key] = payloads
if(total_chunks != length(payloads))
chunked_topics -= cache_key
return TopicResponse("Received differing total chunks for same [DMAPI5_CHUNK_PAYLOAD_ID]! Invalidating [DMAPI5_CHUNK_PAYLOAD_ID]!")
var/pre_existing_chunk = payloads[sequence_id + 1]
if(pre_existing_chunk && pre_existing_chunk != payload)
chunked_topics -= cache_key
return TopicResponse("Received differing payload for same [DMAPI5_CHUNK_SEQUENCE_ID]! Invalidating [DMAPI5_CHUNK_PAYLOAD_ID]!")
payloads[sequence_id + 1] = payload
var/list/missing_sequence_ids = list()
for(var/i in 1 to total_chunks)
if(!payloads[i])
missing_sequence_ids += i - 1
if(length(missing_sequence_ids))
return list(DMAPI5_MISSING_CHUNKS = missing_sequence_ids)
chunked_topics -= cache_key
var/full_json = jointext(payloads, "")
return ProcessRawTopic(full_json, FALSE)
if(DMAPI5_TOPIC_COMMAND_RECEIVE_CHUNK)
var/payload_id = topic_parameters[DMAPI5_CHUNK_PAYLOAD_ID]
if(!isnum(payload_id))
return TopicResponse("[DMAPI5_CHUNK_PAYLOAD_ID] is not a number!")
// Always updated the highest known payload ID
chunked_requests = max(chunked_requests, payload_id)
var/list/missing_chunks = topic_parameters[DMAPI5_MISSING_CHUNKS]
if(!istype(missing_chunks) || !length(missing_chunks))
return TopicResponse("Missing or empty [DMAPI5_MISSING_CHUNKS]!")
var/sequence_id_to_send = missing_chunks[1]
if(!isnum(sequence_id_to_send))
return TopicResponse("[DMAPI5_MISSING_CHUNKS] contained a non-number!")
var/cache_key = ResponseTopicChunkCacheKey(payload_id)
var/list/chunks = chunked_topics[cache_key]
if(!chunks)
return TopicResponse("Unknown response chunk set: P[payload_id]!")
// sequence IDs in interop chunking are always zero indexed
var/chunk_to_send = chunks[sequence_id_to_send + 1]
if(!chunk_to_send)
return TopicResponse("Sequence ID [sequence_id_to_send] is not present in response chunk P[payload_id]!")
if(length(missing_chunks) == 1)
// sending last chunk, purge the cache
chunked_topics -= cache_key
return chunk_to_send
return TopicResponse("Unknown command: [command]")
+13
View File
@@ -4,16 +4,29 @@
#undef DMAPI5_BRIDGE_DATA
#undef DMAPI5_TOPIC_DATA
#undef DMAPI5_BRIDGE_REQUEST_LIMIT
#undef DMAPI5_TOPIC_REQUEST_LIMIT
#undef DMAPI5_TOPIC_RESPONSE_LIMIT
#undef DMAPI5_BRIDGE_COMMAND_PORT_UPDATE
#undef DMAPI5_BRIDGE_COMMAND_STARTUP
#undef DMAPI5_BRIDGE_COMMAND_PRIME
#undef DMAPI5_BRIDGE_COMMAND_REBOOT
#undef DMAPI5_BRIDGE_COMMAND_KILL
#undef DMAPI5_BRIDGE_COMMAND_CHAT_SEND
#undef DMAPI5_BRIDGE_COMMAND_CHUNK
#undef DMAPI5_PARAMETER_ACCESS_IDENTIFIER
#undef DMAPI5_PARAMETER_CUSTOM_COMMANDS
#undef DMAPI5_CHUNK
#undef DMAPI5_CHUNK_PAYLOAD
#undef DMAPI5_CHUNK_TOTAL
#undef DMAPI5_CHUNK_SEQUENCE_ID
#undef DMAPI5_CHUNK_PAYLOAD_ID
#undef DMAPI5_MISSING_CHUNKS
#undef DMAPI5_RESPONSE_ERROR_MESSAGE
#undef DMAPI5_BRIDGE_PARAMETER_COMMAND_TYPE
+4
View File
@@ -22,3 +22,7 @@ SM_DELAMINATION
# Protolathe access
# Comment to make protolathes and mechfabs use their access locks
PROTOLOCK_ALL_ACCESS
# Character color limits
# Uncomment to disallow players from making characters with colors that are too dark
CHARACTER_COLOR_LIMITS
+32
View File
@@ -3,3 +3,35 @@
- rscadd: Adds a lot of new suits
- bugfix: fixes the tuxedo sprites
- bugfix: fixes the double wedding crate
2023-04-06:
SandPoot:
- tweak: Pointing will make the arrow face it and you can also pixel-perfect point.
2023-04-08:
Anonymous:
- rscadd: Leashes are now available in BarkBox as well.
2023-04-09:
Anonymous:
- imageadd: 'Emojis: dawgdoin, ackshually, pain.'
CreepingRose:
- rscadd: Added Ultimate Spray Bottle
- rscadd: Added Alien Janitoring research node, requires Advanced Janitoring and
Alien Stock Parts to research for 1500 points
- imageadd: added Ultimate Spray Bottle's .dmi as janitor.dmi in modular_splurt
2023-04-12:
Anonymous:
- soundadd: 'Emotes: \*whistle_tune, \*terror.'
2023-04-18:
KeplerWasTaken:
- rscdel: Removes fascist symbols
2023-04-23:
Azarak:
- rscadd: Added new body morpher quirk, which allows people to morph like slimepeople
BongaTheProto:
- config: Makes character color limits a config (so you can make darker characters
now)
- tweak: select full black for default species color, select it again for full black
Pin-alternative:
- tweak: The genital manager is now its own menu you can enter through the right
click options
- bugfix: genitals now properly show how full of fluids they are
- code_imp: Made breasts a not so snowflake to handle
Binary file not shown.

Before

Width:  |  Height:  |  Size: 54 KiB

After

Width:  |  Height:  |  Size: 48 KiB

BIN
View File
Binary file not shown.

Before

Width:  |  Height:  |  Size: 72 KiB

After

Width:  |  Height:  |  Size: 73 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 20 KiB

After

Width:  |  Height:  |  Size: 25 KiB

@@ -7,9 +7,9 @@
features["ipc_antenna"] = sanitize_inlist(features["ipc_antenna"], GLOB.ipc_antennas_list)
//Citadel
features["flavor_text"] = sanitize_text(features["flavor_text"], initial(features["flavor_text"]))
if(!features["mcolor2"] || features["mcolor"] == "#000000")
if(!features["mcolor2"] || (features["mcolor"] == "#000000" && CONFIG_GET(flag/character_color_limits))) //SPLURT EDIT
features["mcolor2"] = pick("FFFFFF","7F7F7F", "7FFF7F", "7F7FFF", "FF7F7F", "7FFFFF", "FF7FFF", "FFFF7F")
if(!features["mcolor3"] || features["mcolor"] == "#000000")
if(!features["mcolor3"] || (features["mcolor"] == "#000000" && CONFIG_GET(flag/character_color_limits))) //SPLURT EDIT
features["mcolor3"] = pick("FFFFFF","7F7F7F", "7FFF7F", "7F7FFF", "FF7F7F", "7FFFFF", "FF7FFF", "FFFF7F")
features["mcolor2"] = sanitize_hexcolor(features["mcolor2"], 6, FALSE)
features["mcolor3"] = sanitize_hexcolor(features["mcolor3"], 6, FALSE)
@@ -68,8 +68,7 @@
B.color = "#[M.dna.features["breasts_color"]]"
else
B.color = SKINTONE2HEX(H.skin_tone)
B.size = "flat"
B.cached_size = 0
B.size = 0
B.prev_size = 0
to_chat(H, "<span class='warning'>Your chest feels warm, tingling with newfound sensitivity.</b></span>")
H.reagents.remove_reagent(type, 5)
@@ -145,12 +144,12 @@
var/obj/item/organ/genital/breasts/B = M.getorganslot(ORGAN_SLOT_BREASTS)
if(!B)
return..()
var/optimal_size = B.breast_values[M.dna.features["breasts_size"]]
var/optimal_size = GLOB.breast_values[M.dna.features["breasts_size"]]
if(!optimal_size)//Fast fix for those who don't want it.
B.modify_size(-0.1)
else if(B.cached_size > optimal_size)
else if(B.size > optimal_size)
B.modify_size(-0.05, optimal_size)
else if(B.cached_size < optimal_size)
else if(B.size < optimal_size)
B.modify_size(0.05, 0, optimal_size)
return ..()
@@ -238,7 +237,7 @@
if(B)
B.modify_size(-0.05)
if(V && (!B || B.cached_size <= 0))
if(V && (!B || B.size <= 0))
qdel(V)
if(W && (!B || B.size <= 0))
qdel(W)
@@ -136,14 +136,8 @@
else
visibility = "Hidden by clothes"
var/extras = "None"
if(CHECK_BITFIELD(genital.genital_flags, GENITAL_CAN_STUFF))
extras = "Allows egg stuffing"
genital_entry["extras"] = extras
genital_entry["visibility"] = visibility
genital_entry["possible_choices"] = GLOB.genitals_visibility_toggles
genital_entry["extra_choices"] = list(GEN_ALLOW_EGG_STUFFING)
genital_entry["can_arouse"] = (
!!CHECK_BITFIELD(genital.genital_flags, GENITAL_CAN_AROUSE) \
&& !(HAS_TRAIT(get_genitals, TRAIT_PERMABONER) \
@@ -169,37 +163,6 @@
genitals += list(simulated_ass)
.["genitals"] = genitals
//Get their genitals
var/list/genital_fluids = list()
var/mob/living/carbon/target_genitals = target || self
if(istype(target_genitals))
for(var/obj/item/organ/genital/genital in target_genitals.internal_organs)
if(!(CHECK_BITFIELD(genital.genital_flags, GENITAL_FUID_PRODUCTION)))
continue
var/fluids = (clamp(genital.fluid_rate * ((world.time - genital.last_orgasmed) / (10 SECONDS)) * genital.fluid_mult, 0, genital.fluid_max_volume) / genital.fluid_max_volume)
var/list/genital_entry = list()
genital_entry["name"] = "[genital.name]"
genital_entry["key"] = REF(genital)
genital_entry["fluid"] = fluids
genital_fluids += list(genital_entry)
.["genital_fluids"] = genital_fluids
var/list/genital_interactibles = list()
if(istype(target_genitals))
for(var/obj/item/organ/genital/genital in target_genitals.internal_organs)
if(!genital.is_exposed())
continue
var/list/equipment_names = list()
for(var/obj/equipment in genital.contents)
equipment_names += equipment.name
var/list/genital_entry = list()
genital_entry["name"] = "[genital.name]"
genital_entry["key"] = REF(genital)
genital_entry["possible_choices"] = GLOB.genitals_interactions
genital_entry["equipments"] = equipment_names
genital_interactibles += list(genital_entry)
.["genital_interactibles"] = genital_interactibles
var/datum/preferences/prefs = self?.client.prefs
if(prefs)
//Getting char prefs
@@ -41,8 +41,8 @@
if("f", "g", "h")
modifier = 3
else
if(milkers.size in milkers.breast_values)
modifier = clamp(milkers.breast_values[milkers.size] - 5, 0, INFINITY)
if(milkers.size in GLOB.breast_values)
modifier = clamp(GLOB.breast_values[milkers.size] - 5, 0, INFINITY)
else
modifier = 1
target.reagents.add_reagent(milktype, rand(1,3 * modifier))
@@ -22,8 +22,8 @@
if("f", "g", "h")
modifier = 3
else
if(B.size in B.breast_values)
modifier = clamp(B.breast_values[B.size] - 5, 0, INFINITY)
if(B.size in GLOB.breast_values)
modifier = clamp(GLOB.breast_values[B.size] - 5, 0, INFINITY)
else
modifier = 1
if(B.fluid_id)
@@ -42,8 +42,8 @@
if("f", "g", "h")
modifier = 3
else
if(B.size in B.breast_values)
modifier = clamp(B.breast_values[B.size] - 5, 0, INFINITY)
if(B.size in GLOB.breast_values)
modifier = clamp(GLOB.breast_values[B.size] - 5, 0, INFINITY)
else
modifier = 1
if(B.fluid_id)
@@ -63,8 +63,8 @@
if("f", "g", "h")
modifier = 3
else
if(B.size in B.breast_values)
modifier = clamp(B.breast_values[B.size] - 5, 0, INFINITY)
if(B.size in GLOB.breast_values)
modifier = clamp(GLOB.breast_values[B.size] - 5, 0, INFINITY)
else
modifier = 1
if(B.fluid_id)
@@ -79,8 +79,8 @@
if("f", "g", "h")
modifier = 3
else
if(milkers.size in milkers.breast_values)
modifier = clamp(milkers.breast_values[milkers.size] - 5, 0, INFINITY)
if(milkers.size in GLOB.breast_values)
modifier = clamp(GLOB.breast_values[milkers.size] - 5, 0, INFINITY)
else
modifier = 1
user.reagents.add_reagent(milktype, rand(1,3 * modifier) * user.get_fluid_mod(milkers), milkers) //SPLURT edit
@@ -4,6 +4,10 @@
var/list/obj/item/clothing/accessory/ring/attached_accessories = list()
var/list/mutable_appearance/accessory_overlays = list()
/obj/item/clothing/gloves/Destroy()
QDEL_LIST(attached_accessories)
return ..()
/obj/item/clothing/gloves/worn_overlays(isinhands = FALSE, icon_file, used_state, style_flags = NONE)
. = ..()
if(!isinhands)
@@ -2,3 +2,7 @@
. = ..()
if(!is_type_in_typecache(type, GLOB.skirt_peekable) && findlasttext("[type]", "skirt"))
LAZYSET(GLOB.skirt_peekable, type, TRUE)
/obj/item/clothing/under/Destroy()
QDEL_LIST(attached_accessories)
return ..()
@@ -88,11 +88,6 @@
basic_modules += /obj/item/reagent_containers/borghypo/borgshaker/sodashaker
basic_modules += /obj/item/reagent_containers/borghypo/borgshaker/miscshaker
. = ..()
/*
/obj/item/robot_module/miner/Initialize(mapload)
basic_modules += /obj/item/card/id/miningborg
. = ..()
*/ //Sandstorm, the main citacode already gives a mining points card to the minerborg, borgs don't need another one.
/datum/robot_energy_storage/plasma
name = "Plasma Buffer Container"
@@ -15,3 +15,5 @@ GLOBAL_LIST_INIT(breast_nouns, list("breasts", "boobs", "honkers", "tatas", "tid
GLOBAL_LIST_INIT(balls_nouns, list("balls", "nuts", "ballsack", "testicles", "sack", "cum orbs", "cum spheres", "cum tanks", "cum holders", "cum churners", "spunk orbs", "spunk spheres", "spunk tanks", "spunk holders", "spunk churners", "nut orbs", "nut spheres", "nut tanks", "nut holders", "nut churners", "jizz orbs", "jizz spheres", "jizz tanks", "jizz holders", "jizz churners", "seed orbs", "seed spheres", "seed tanks", "seed holders", "seed churners"))
GLOBAL_LIST_INIT(butt_nouns, list("ass", "butt", "dumptruck", "tush", "badonk", "booty", "rump", "behind"))
GLOBAL_LIST_INIT(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, "massive" = 17, "giga" = 25, "impossible" = 30, "flat" = 0))
@@ -11,3 +11,5 @@
/datum/config_entry/flag/sm_delamination
/datum/config_entry/flag/protolock_all_access
/datum/config_entry/flag/character_color_limits
@@ -0,0 +1,213 @@
/// Attempts to open the tgui menu
/mob/living/verb/genital_menu()
set name = "Genitals Menu"
set desc = "Manage your genital, or someone else's."
set category = "IC"
set src in view(usr.client)
if(!iscarbon(usr))
return
if(!usr.mind) //Mindless boys, honestly just don't, it's better this way
return
if(!usr.mind.genitals_menu_holder)
usr.mind.genitals_menu_holder= new(usr.mind)
usr.mind.genitals_menu_holder.target = src
usr.mind.genitals_menu_holder.ui_interact(usr)
/datum/mind
var/datum/genitals_menu/genitals_menu_holder
/datum/mind/New(key)
. = ..()
genitals_menu_holder = new(src)
/datum/genitals_menu
var/mob/living/carbon/target
/datum/genitals_menu/ui_state(mob/user)
return GLOB.conscious_state
/datum/genitals_menu/ui_interact(mob/user, datum/tgui/ui)
ui = SStgui.try_update_ui(user, src, ui)
if(!ui)
ui = new(user, src, "GenitalConfig", "Genitals")
ui.open()
/datum/genitals_menu/ui_data(mob/user)
. = ..()
var/mob/living/carbon/genital_holder = target || user
var/user_is_target = genital_holder == user
.["istargetself"] = user_is_target
if(!user_is_target)
.["target_name"] = genital_holder.name
var/list/genitals = list()
for(var/obj/item/organ/genital/genital in genital_holder.internal_organs) //Only get the genitals
if(CHECK_BITFIELD(genital.genital_flags, GENITAL_INTERNAL)) //Not those though
continue
var/list/genital_entry = list()
genital_entry["img"] = icon2base64(getFlatIcon(genital, no_anim=TRUE))
genital_entry["name"] = "[capitalize(genital.name)]" //Prevents code from adding a prefix
genital_entry["key"] = REF(genital) //The key is the reference to the object
genital_entry["description"] = genital.desc + "\n [genital.linked_organ ? "Linked organ: [genital.linked_organ.name]" : ""]"
if(user_is_target)
var/visibility = "Invalid"
if(CHECK_BITFIELD(genital.genital_flags, GENITAL_THROUGH_CLOTHES))
visibility = "Always visible"
else if(CHECK_BITFIELD(genital.genital_flags, GENITAL_UNDIES_HIDDEN))
visibility = "Hidden by underwear"
else if(CHECK_BITFIELD(genital.genital_flags, GENITAL_HIDDEN))
visibility = "Always hidden"
else
visibility = "Hidden by clothes"
var/extras = "None"
if(CHECK_BITFIELD(genital.genital_flags, GENITAL_CAN_STUFF))
extras = "Allows egg stuffing"
genital_entry["extras"] = extras
genital_entry["visibility"] = visibility
genital_entry["possible_choices"] = GLOB.genitals_visibility_toggles
genital_entry["extra_choices"] = list(GEN_ALLOW_EGG_STUFFING)
genital_entry["can_arouse"] = (
!!CHECK_BITFIELD(genital.genital_flags, GENITAL_CAN_AROUSE) \
&& !(HAS_TRAIT(genital_holder, TRAIT_PERMABONER) \
|| HAS_TRAIT(genital_holder, TRAIT_NEVERBONER)))
genital_entry["arousal_state"] = genital.aroused_state
if(istype(genital, /obj/item/organ/genital/penis))
var/obj/item/organ/genital/penis/peepee = genital
genital_entry["max_size"] = peepee.max_length ? peepee.max_length : 0
genital_entry["min_size"] = peepee.min_length ? peepee.min_length : 0
else
genital_entry["max_size"] = genital.max_size ? genital.max_size : 0
genital_entry["min_size"] = genital.min_size ? genital.min_size : 0
//fluids
if(CHECK_BITFIELD(genital.genital_flags, GENITAL_FUID_PRODUCTION) || CHECK_BITFIELD(genital?.linked_organ?.genital_flags, GENITAL_FUID_PRODUCTION))
var/fluids = genital.get_fluid_fraction()
genital_entry["fluid"] = fluids
//equipments
if(genital.is_exposed())
var/list/equipments = list()
for(var/obj/equipment in genital.contents)
var/list/equipment_entry = list()
equipment_entry["name"] = equipment.name
equipment_entry["key"] = REF(equipment)
equipments += list(equipment_entry)
genital_entry["possible_equipment_choices"] = GLOB.genitals_interactions
genital_entry["equipments"] = equipments
genitals += list(genital_entry)
if(!genital_holder.getorganslot(ORGAN_SLOT_ANUS) && user_is_target)
var/simulated_ass = list()
simulated_ass["name"] = "Anus"
simulated_ass["key"] = "anus"
var/visibility = "Invalid"
switch(genital_holder.anus_exposed)
if(1)
visibility = "Always visible"
if(0)
visibility = "Hidden by underwear"
else
visibility = "Always hidden"
simulated_ass["visibility"] = visibility
simulated_ass["possible_choices"] = GLOB.genitals_visibility_toggles - GEN_VISIBLE_NO_CLOTHES
genitals += list(simulated_ass)
.["genitals"] = genitals
/datum/genitals_menu/ui_act(action, list/params, datum/tgui/ui, datum/ui_state/state)
if(..())
return
switch(action)
if("genital")
var/mob/living/carbon/self = usr
if(self != target)
return FALSE
if("visibility" in params)
if(params["genital"] == "anus")
self.anus_toggle_visibility(params["visibility"])
return TRUE
var/obj/item/organ/genital/genital = locate(params["genital"], self.internal_organs)
if(genital && (genital in self.internal_organs))
genital.toggle_visibility(params["visibility"])
return TRUE
if("set_arousal" in params)
var/obj/item/organ/genital/genital = locate(params["genital"], self.internal_organs)
if(!genital || (genital \
&& (!CHECK_BITFIELD(genital.genital_flags, GENITAL_CAN_AROUSE) \
|| HAS_TRAIT(self, TRAIT_PERMABONER) \
|| HAS_TRAIT(self, TRAIT_NEVERBONER))))
return FALSE
var/original_state = genital.aroused_state
genital.set_aroused_state(params["set_arousal"])// i'm not making it just `!aroused_state` because
if(original_state != genital.aroused_state) // someone just might port skyrat's new genitals
to_chat(self, "<span class='userlove'>[genital.aroused_state ? genital.arousal_verb : genital.unarousal_verb].</span>")
. = TRUE
else
to_chat(self, "<span class='userlove'>You can't make that genital [genital.aroused_state ? "unaroused" : "aroused"]!</span>")
. = FALSE
genital.update_appearance()
if(ishuman(self))
var/mob/living/carbon/human/human = self
human.update_genitals()
return
if("max_size" in params)
var/obj/item/organ/genital/genital = locate(params["genital"], self.internal_organs)
if(!genital)
return FALSE
if(istype(genital, /obj/item/organ/genital/penis))
var/obj/item/organ/genital/penis/peepee = genital
if(params["max_size"])
var/new_max_size = clamp(params["max_size"], peepee.length, INFINITY)
peepee.max_length = new_max_size
else
peepee.max_length = 0
else
if(params["max_size"])
var/new_max_size = clamp(params["max_size"], genital.size, INFINITY)
genital.max_size = new_max_size
else
genital.max_size = 0
if("min_size" in params)
var/obj/item/organ/genital/genital = locate(params["genital"], self.internal_organs)
if(!genital)
return FALSE
if(istype(genital, /obj/item/organ/genital/penis))
var/obj/item/organ/genital/penis/peepee = genital
var/new_min_size = clamp(params["min_size"], 0, peepee.length)
peepee.min_length = new_min_size
else
var/new_min_size = clamp(params["min_size"], 0, genital.size)
genital.min_size = new_min_size
else
return FALSE
if("equipment")
var/mob/living/carbon/actual_target = target || usr
var/mob/living/carbon/self = usr
if(get_dist(actual_target, self) > 1)
to_chat(self, span_warning("You're too far away!"))
return FALSE
var/obj/item/organ/genital/genital = locate(params["genital"], actual_target.internal_organs)
if(!(genital && (genital in actual_target.internal_organs)))
return FALSE
switch(params["equipment_action"])
if("remove")
var/obj/item/selected_item = locate(params["equipment"], genital.contents)
if(selected_item)
if(!do_mob(self, actual_target, 5 SECONDS))
return FALSE
if(!self.put_in_hands(selected_item))
self.transferItemToLoc(get_turf(self))
return TRUE
return FALSE
if("insert")
var/obj/item/stuff = self.get_active_held_item()
if(!istype(stuff))
to_chat(self, span_warning("You need to hold an item to insert it!"))
return FALSE
stuff.insert_item_organ(self, self, genital)
@@ -820,3 +820,29 @@
var/obj/item/clothing/mask/gas/cosmetic/gasmask = new(get_turf(quirk_holder)) // Uses a custom gas mask
H.equip_to_slot(gasmask, ITEM_SLOT_MASK)
H.regenerate_icons()
/datum/quirk/body_morpher
name = "Body Morpher"
desc = "Somehow you developed an ability allowing your body to morph and shift itself to modify bodyparts, much like a slimeperson can."
value = 0
mob_trait = TRAIT_BODY_MORPHER
gain_text = span_notice("Your body feels more malleable...")
lose_text = span_notice("Your body is more firm.")
medical_record_text = "Patient's body seems unusually malleable."
var/datum/action/innate/ability/humanoid_customization/alter_form_action
/datum/quirk/body_morpher/add()
// Define quirk mob
var/mob/living/carbon/human/quirk_mob = quirk_holder
// Add quirk ability action datum
alter_form_action = new
alter_form_action.Grant(quirk_mob)
/datum/quirk/body_morpher/remove()
// Define quirk mob
var/mob/living/carbon/human/quirk_mob = quirk_holder
// Remove quirk ability action datum
alter_form_action.Remove(quirk_mob)
QDEL_NULL(alter_form_action)
@@ -139,7 +139,7 @@
for(var/obj/item/organ/genital/genital in buckled_mob.internal_organs)
if(istype(genital, /obj/item/organ/genital/breasts))
var/obj/item/organ/genital/breasts/breasts = genital
points_awarded += breasts.fluid_rate + breasts.breast_values[breasts.size] // Breasts use letters instead of numbers!
points_awarded += breasts.fluid_rate + GLOB.breast_values[breasts.size]
continue
points_awarded += genital.fluid_rate + genital.size
points_awarded *= tier
+23 -9
View File
@@ -55,10 +55,17 @@
return
if(locate(src.type) in target_organ.contents)
to_chat(user, span_notice("\The <b>[target]</b>'s [target_organ] already has a rod inside!"))
if(user == target)
to_chat(user, span_notice("You already have a rod inside your [target_organ]!"))
else
to_chat(user, span_notice("\The <b>[target]</b>'s [target_organ] already has a rod inside!"))
return
target.visible_message(span_warning("\The <b>[user]</b> is trying to insert a rod inside \the <b>[target]</b>!"),\
if(user == target)
target.visible_message(span_warning("\The <b>[user]</b> is trying to insert a rod inside themselves!"),\
span_warning("You try to insert a rod inside yourself!"))
else
target.visible_message(span_warning("\The <b>[user]</b> is trying to insert a rod inside \the <b>[target]</b>!"),\
span_warning("\The <b>[user]</b> is trying to insert a rod inside you!"))
if(!do_mob(user, target, 4 SECONDS))
@@ -81,15 +88,18 @@
return
if(locate(src.type) in target_organ.contents)
to_chat(user, span_notice("\The <b>[target]</b>'s [target_organ] already has a vibrator inside!"))
if(user == target)
to_chat(user, span_notice("You already have a vibrator inside your [target_organ]!"))
else
to_chat(user, span_notice("\The <b>[target]</b>'s [target_organ] already has a vibrator inside!"))
return
if(style == "long")
target.visible_message(span_warning("\The <b>[user]</b> is trying to insert a vibrator inside \the <b>[target]</b>!"),\
span_warning("\The <b>[user]</b> is trying to insert a vibrator inside you!"))
if(user == target)
target.visible_message(span_warning("\The <b>[user]</b> is trying to [style == "long" ? "insert" : "attach"] a vibrator [style == "long" ? "inside" : "to"] themselves!"),\
span_warning("You try to [style == "long" ? "insert" : "attach"] a vibrator [style == "long" ? "inside" : "to"] yourself!"))
else
target.visible_message(span_userlove("\The <b>[user]</b> is trying to attach a vibrator to \the <b>[target]</b>!"),\
span_warning("\The <b>[user]</b> is trying to attach a vibrator to you!"))
target.visible_message(span_warning("\The <b>[user]</b> is trying to [style == "long" ? "insert" : "attach"] a vibrator [style == "long" ? "inside" : "to"] \the <b>[target]</b>!"),\
span_warning("\The <b>[user]</b> is trying to [style == "long" ? "insert" : "attach"] a vibrator [style == "long" ? "inside" : "to"] you!"))
if(!do_mob(user, target, 5 SECONDS))
return
@@ -115,7 +125,11 @@
to_chat(user, span_warning("This genital can't be stuffed!"))
return
target.visible_message(span_warning("\The <b>[user]</b> is trying to insert an egg inside \the <b>[target]</b>!"),\
if(user == target)
target.visible_message(span_warning("\The <b>[user]</b> is trying to insert an egg inside themselves!"),\
span_warning("You try to insert an egg inside yourself!"))
else
target.visible_message(span_warning("\The <b>[user]</b> is trying to insert an egg inside \the <b>[target]</b>!"),\
span_warning("\The <b>[user]</b> is trying to insert an egg inside you!"))
if(!do_mob(user, target, 5 SECONDS))
@@ -41,7 +41,7 @@
if(do_after(src, rand(10, 20), 0, target)) //did they get deleted in that second?
// var/main_fluid = lowertext(fluid_source.get_master_reagent_name()) // doesn't work no more (should delete probably)
var/main_fluid = G.get_fluid_name()
var/fluid_ammount = clamp((G.fluid_rate * ((world.time - G.last_orgasmed) / (10 SECONDS)) * G.fluid_mult),0,G.fluid_max_volume)
var/fluid_ammount = G.get_fluid()
if (fluid_ammount <= 2)
to_chat(src, span_revennotice("[target.p_their(TRUE)] [G.name] spasms pitifully, almost nothing will come out."))
else
@@ -1,4 +1,6 @@
/obj/item/organ/genital
var/max_size = 6
var/min_size = 1
var/datum/reagents/climax_fluids
var/datum/reagent/original_fluid_id
var/datum/reagent/default_fluid_id
@@ -10,6 +12,15 @@
fluid_max_volume += (modifier*2.5)*(get_size(owner)-1)
fluid_rate += (modifier/10)*(get_size(owner)-1)
/obj/item/organ/genital/proc/size_to_state()
return size
/obj/item/organ/genital/proc/get_fluid()
return clamp(fluid_rate * ((world.time - last_orgasmed) / (10 SECONDS)) * fluid_mult, 0, fluid_max_volume)
/obj/item/organ/genital/proc/get_fluid_fraction()
return get_fluid() / fluid_max_volume
/obj/item/organ/genital/proc/climax_modify_size(mob/living/partner, obj/item/organ/genital/source_gen)
return
@@ -25,8 +25,8 @@
if(!owner)
return
/obj/item/organ/genital/belly/modify_size(modifier, min = -INFINITY, max = BELLY_SIZE_MAX)
var/new_value = clamp(size_cached + modifier, min, max)
/obj/item/organ/genital/belly/modify_size(modifier, min = -INFINITY, max = INFINITY)
var/new_value = clamp(size_cached + modifier, max(min, min_size ? min_size : -INFINITY), min(max_size ? max_size : INFINITY, max))
if(new_value == size_cached)
return
prev_size = size_cached
@@ -95,6 +95,8 @@
else
color = "#[D.features["belly_color"]]"
size = D.features["belly_size"]
max_size = D.features["belly_max_size"]
min_size = D.features["belly_min_size"]
prev_size = size
size_cached = size
original_fluid_id = fluid_id
@@ -13,8 +13,8 @@
fluid_id = initial(fluid_id)
original_fluid_id = fluid_id
. = ..()
fluid_max_volume += ((cached_size - breast_values[initial(size)])*2.5)*(owner ? get_size(owner) : 1)
fluid_rate += ((cached_size - breast_values[initial(size)])/10)*(owner ? get_size(owner) : 1)
fluid_max_volume += ((size - initial(size))*2.5)*(owner ? get_size(owner) : 1)
fluid_rate += ((size - initial(size))/10)*(owner ? get_size(owner) : 1)
/obj/item/organ/genital/breasts/climax_modify_size(mob/living/partner, obj/item/organ/genital/source_gen)
if(!(owner.client?.prefs.cit_toggles & BREAST_ENLARGEMENT))
@@ -1,6 +1,18 @@
/obj/item/organ/genital/testicles
default_fluid_id = /datum/reagent/consumable/semen
/obj/item/organ/genital/testicles/get_fluid()
if(linked_organ)
return clamp(linked_organ.fluid_rate * ((world.time - linked_organ.last_orgasmed) / (10 SECONDS)) * linked_organ.fluid_mult, 0, linked_organ.fluid_max_volume)
else
return 0
/obj/item/organ/genital/testicles/get_fluid_fraction()
if(linked_organ)
return get_fluid() / linked_organ.fluid_max_volume
else
return 0
/obj/item/organ/genital/testicles/get_features(mob/living/carbon/human/H)
var/datum/dna/D = H.dna
if(D.features["balls_fluid"])
@@ -1,6 +1,18 @@
/obj/item/organ/genital/womb
default_fluid_id = /datum/reagent/consumable/semen/femcum
/obj/item/organ/genital/womb/get_fluid()
if(linked_organ)
return clamp(linked_organ.fluid_rate * ((world.time - linked_organ.last_orgasmed) / (10 SECONDS)) * linked_organ.fluid_mult, 0, linked_organ.fluid_max_volume)
else
return 0
/obj/item/organ/genital/womb/get_fluid_fraction()
if(linked_organ)
return get_fluid() / linked_organ.fluid_max_volume
else
return 0
/obj/item/organ/genital/womb/get_features(mob/living/carbon/human/H)
var/datum/dna/D = H.dna
if(D.features["womb_fluid"])
@@ -1195,7 +1195,7 @@
var/new_marking_color = input(user, "Choose your character's marking color:", "Character Preference","#"+color_list[color_number]) as color|null
if(new_marking_color)
var/temp_hsv = RGBtoHSV(new_marking_color)
if((MUTCOLORS_PARTSONLY in pref_species.species_traits) || ReadHSV(temp_hsv)[3] >= ReadHSV(MINIMUM_MUTANT_COLOR)[3]) // mutantcolors must be bright, but only if they affect the skin
if((MUTCOLORS_PARTSONLY in pref_species.species_traits) || ReadHSV(temp_hsv)[3] >= ReadHSV(MINIMUM_MUTANT_COLOR)[3] || !CONFIG_GET(flag/character_color_limits)) // mutantcolors must be bright, but only if they affect the skin
color_list[color_number] = "#[sanitize_hexcolor(new_marking_color, 6)]"
else
to_chat(user, "<span class='danger'>Invalid color. Your color is not bright enough.</span>")
@@ -639,6 +639,36 @@
emote_sound = 'modular_splurt/sound/voice/agony.ogg'
emote_cooldown = 7 SECONDS
/datum/emote/living/audio/wtune
key = "whistle_tune"
key_third_person = "whistle_tunes"
message = "whistles a tune."
message_mime = "makes an expression as if whistling."
emote_sound = 'modular_splurt/sound/voice/wtune1.ogg'
emote_cooldown = 4.55 SECONDS // Uses longest sound's time.
/datum/emote/living/audio/wtune/run_emote(mob/user, params)
// Set random emote sound
emote_sound = pick('modular_splurt/sound/voice/wtune1.ogg', 'modular_splurt/sound/voice/wtune2.ogg')
// Return normally
. = ..()
/datum/emote/living/audio/terror
key = "terror"
key_third_person = "terrors"
message = "whistles some dreadful tune..."
message_mime = "stares with aura full of dread..."
emote_sound = 'modular_splurt/sound/voice/terror1.ogg'
emote_cooldown = 13.07 SECONDS // Uses longest sound's time.
/datum/emote/living/audio/terror/run_emote(mob/user, params)
// Set random emote sound
emote_sound = pick('modular_splurt/sound/voice/terror1.ogg', 'modular_splurt/sound/voice/terror2.ogg')
// Return normally
. = ..()
/datum/emote/living/audio/sicko
key = "sicko"
key_third_person = "sickos"
@@ -23,3 +23,17 @@
// Return normally
. = ..()
/obj/item/reagent_containers/spray/ultimate
name = "ultimate spray bottle"
desc = "A bottle to spray above them all."
volume = 750
amount_per_transfer_from_this = 2
current_range = 5
spray_range = 5
stream_range = 5
stream_amount = 2
icon = 'modular_splurt/icons/obj/janitor.dmi'
icon_state = "cleaneralien"
item_state = "cleaneralien"
custom_materials = list(/datum/material/iron = 2250, /datum/material/glass = 2250, /datum/material/plasma = 2250, /datum/material/diamond = 370, /datum/material/bluespace = 370)
@@ -1,2 +1,19 @@
/datum/design/light_replacer_blue/New()
departmental_flags |= DEPARTMENTAL_FLAG_SCIENCE
. = ..()
/datum/design/bluespacespray/New()
departmental_flags |= DEPARTMENTAL_FLAG_SERVICE
. = ..()
/datum/design/ultimatespray
name = "Ultimate Spray"
desc = "The ultimate sprayer, no mess shall go unclean today."
id = "ultimatespray"
build_type = PROTOLATHE
materials = list(/datum/material/iron = 2250, /datum/material/glass = 2250, /datum/material/plasma = 2250, /datum/material/diamond = 370, /datum/material/bluespace = 370)
build_path = /obj/item/reagent_containers/spray/ultimate
reagents_list = list(/datum/reagent/liquid_dark_matter = 10)
category = list("Misc", "Medical Designs")
departmental_flags = DEPARTMENTAL_FLAG_MEDICAL | DEPARTMENTAL_FLAG_SCIENCE | DEPARTMENTAL_FLAG_SERVICE
@@ -0,0 +1,7 @@
/datum/techweb_node/ultispray
id = "ultispray"
display_name = "Alien Janitoring"
description = "Clean messes like Abductors clean up witnesses"
prereq_ids = list("alien_stockparts", "bspspray")
design_ids = list("ultimatespray")
research_costs = list(TECHWEB_POINT_TYPE_GENERIC = 1500)
@@ -3,7 +3,8 @@
/obj/item/clothing/neck/petcollar/spike = 5,
/obj/item/clothing/neck/petcollar/holo = 5,
/obj/item/clothing/neck/petcollar/casino = 5,
/obj/item/clothing/neck/petcollar/handmade = 5
/obj/item/clothing/neck/petcollar/handmade = 5,
/obj/item/leash = 4
)
var/list/extra_contraband = list(
/obj/item/clothing/neck/petcollar/locked/spike = 2,
Binary file not shown.

After

Width:  |  Height:  |  Size: 386 B

Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
+2
View File
@@ -3493,6 +3493,7 @@
#include "code\modules\research\techweb\nodes\computer_hud_nodes.dm"
#include "code\modules\research\techweb\nodes\engineering_nodes.dm"
#include "code\modules\research\techweb\nodes\export_nodes.dm"
#include "modular_splurt\code\modules\research\techweb\nodes\janitor_nodes.dm"
#include "code\modules\research\techweb\nodes\mecha_nodes.dm"
#include "code\modules\research\techweb\nodes\medical_nodes.dm"
#include "code\modules\research\techweb\nodes\misc_nodes.dm"
@@ -4350,6 +4351,7 @@
#include "modular_splurt\code\datums\elements\smalltalk.dm"
#include "modular_splurt\code\datums\elements\spooky.dm"
#include "modular_splurt\code\datums\elements\wuv.dm"
#include "modular_splurt\code\datums\genitals\genitals_interface.dm"
#include "modular_splurt\code\datums\interactions\cuddling.dm"
#include "modular_splurt\code\datums\interactions\rope.dm"
#include "modular_splurt\code\datums\interactions\lewd\_lewd.dm"
+1
View File
@@ -7,6 +7,7 @@
// Themes
import './styles/main.scss';
import './styles/themes/abductor.scss';
import './styles/themes/hotpink.scss';
import './styles/themes/cardtable.scss';
import './styles/themes/hackerman.scss';
import './styles/themes/malfunction.scss';
@@ -0,0 +1,313 @@
import { filter } from 'common/collections';
import { flow } from 'common/fp';
import { createSearch } from 'common/string';
import { useBackend, useLocalState } from '../backend';
import { BlockQuote, Button, LabeledList, Icon, NumberInput, Input, Section, Table, Tabs, Stack, ProgressBar, Divider } from '../components';
import { Window } from '../layouts';
type GenitalInfo = {
istargetself: boolean,
target_name: String,
genitals: GenitalData[];
}
type GenitalData = {
img: string,
name: string,
key: string,
description: string,
visibility: string,
extras: string,
extra_choices: string[],
possible_choices: string[],
can_arouse: boolean,
arousal_state: boolean,
fluid: number,
possible_equipment_choices: string[],
min_size: number,
max_size: number,
equipments: Equipment[],
}
type Equipment = {
name: string,
key: string,
}
export const GenitalConfig = (props, context) => {
const { act, data } = useBackend<GenitalInfo>(context);
const genitals = data.genitals || [];
const [tabIndex, setTabIndex] = useLocalState(context, 'tabIndex', 0);
return (
<Window
width={850}
height={600}
theme="hotpink"
resizable>
<Window.Content scrollable={false}>
{data.target_name ? (
<Section>
Interacting with <b>{data.target_name}</b>
</Section>
) : null}
{genitals.length ? (
<>
<Section title="Genital">
<Stack vertical>
<Stack.Item>
<Tabs fluid textAlign="center">
{genitals.map((genital, index) => (
<Tabs.Tab
key={index}
selected={tabIndex === index}
onClick={() => setTabIndex(index)}
>
{genital.name}
</Tabs.Tab>
))}
</Tabs>
</Stack.Item>
<Stack.Item>
{genitals[tabIndex].description}
</Stack.Item>
</Stack>
</Section>
<Section title="Settings">
<Stack grow>
<Stack.Item grow>
<Stack vertical>
<Stack.Item>
<Section>
Fluid level:
<ProgressBar
key={genitals[tabIndex].key}
value={
genitals[tabIndex].fluid
? genitals[tabIndex].fluid
: 0.0
}
color="white"
/>
</Section>
</Stack.Item>
{data.istargetself
? <SelfConfig /> : null}
</Stack>
</Stack.Item>
</Stack>
</Section>
<Section
title="Items Inserted"
buttons={(
<Button
fluid
content="Insert Item"
onClick={() => act('equipment', {
genital: genitals[tabIndex].key,
equipment_action: "insert",
})}
/>
)}
>
<Equipments />
</Section>
</>
) : (
<Section align="center">
{data.target_name ? "They" : "You"} don&apos;t seem to have any genitals...
Or any that you could interact with.
</Section>
)}
</Window.Content>
</Window>
);
};
const SelfConfig = (props, context) => {
const [tabIndex] = useLocalState(context, 'tabIndex', 0);
const { act, data } = useBackend<GenitalInfo>(context);
const genital = data.genitals[tabIndex];
return (
<Stack.Item>
<Stack grow>
{genital.img ? (
<Stack.Item>
<img
src={`data:image/jpeg;base64,${genital.img}`}
style={{
'vertical-align': 'middle',
'horizontal-align': 'middle',
}} />
</Stack.Item>
) : null}
{typeof genital.max_size === "number" ? (
<Stack.Item>
<SizeButtons />
</Stack.Item>
) : null}
<Stack.Item grow>
<ToggleSettings />
</Stack.Item>
</Stack>
</Stack.Item>
);
};
const SizeButtons = (props, context) => {
const [tabIndex] = useLocalState(context, 'tabIndex', 0);
const { act, data } = useBackend<GenitalInfo>(context);
const genital = data.genitals[tabIndex];
return (
<Section>
<Stack>
<Stack.Item>
<Stack vertical>
Max growth:
<NumberInput
value={genital.max_size}
onChange={(e, value) => act('genital', {
genital: genital.key,
max_size: value,
}
)}
/>
</Stack>
</Stack.Item>
<Stack.Item>
<Stack vertical>
Min shrink:
<NumberInput
value={genital.min_size}
onChange={(e, value) => act('genital', {
genital: genital.key,
min_size: value,
}
)}
/>
</Stack>
</Stack.Item>
</Stack>
</Section>
);
};
const ModeToIcon = {
"Always visible": "eye",
"Hidden by clothes": "tshirt",
"Hidden by underwear": "low-vision",
"Always hidden": "eye-slash",
"Allows egg stuffing": "egg",
};
const ToggleSettings = (props, context) => {
const [tabIndex] = useLocalState(context, 'tabIndex', 0);
const { act, data } = useBackend<GenitalInfo>(context);
const genital = data.genitals[tabIndex];
return (
<Stack grow>
{genital.possible_choices.map(choice => (
<Stack.Item key={choice} grow>
<Button
textAlign="center"
key={choice}
tooltip={choice}
icon={ModeToIcon[choice]}
color={genital.visibility === choice ? "green" : "default"}
onClick={() => act('genital', {
genital: genital.key,
visibility: choice,
})}
fluid
/>
</Stack.Item>
))}
<Stack.Item grow>
<Button
textAlign="center"
key={genital.arousal_state}
tooltip={genital.can_arouse
? ((genital.arousal_state ? "Unarouse" : "Arouse") + " your " + genital.name.toLowerCase())
: "You cannot modify arousal on your " + genital.name.toLowerCase()}
icon="heart"
color={genital.can_arouse ? (genital.arousal_state ? "green" : "default") : "grey"}
onClick={() => act('genital', {
genital: genital.key,
set_arousal: !genital.arousal_state,
})}
fluid
/>
</Stack.Item>
{genital.extra_choices instanceof Array
? genital.extra_choices.map(choice => (
<Stack.Item key={choice} grow>
<Button
textAlign="center"
key={choice}
tooltip={choice}
icon={ModeToIcon[choice]}
color={genital.extras === choice ? "green" : "default"}
onClick={() => act('genital', {
genital: genital.key,
visibility: choice,
})}
fluid
/>
</Stack.Item>
)) : null}
</Stack>
);
};
const Equipments = (props, context) => {
const [tabIndex] = useLocalState(context, 'tabIndex', 0);
const { act, data } = useBackend<GenitalInfo>(context);
const genital = data.genitals[tabIndex];
const [searchText, setSearchText] = useLocalState(context, 'searchText', '');
const items = prepareSearch(genital.equipments, searchText) || [];
return (
<Stack vertical>
<Stack.Item>
<Input
fluid
placeholder="Search for items.."
onInput={(e, value) => setSearchText(value)}
/>
</Stack.Item>
<Stack.Item>
<Table mb={1}>
{items.map((item, index) =>
(
<Table.Row key={index} className="candystripe">
<Table.Cell bold>
{item.name}
</Table.Cell>
<Table.Cell collapsing textAlign="center">
<Button
fluid
content="Remove"
onClick={() => act('equipment', {
equipment: item.key,
genital: genital.key,
equipment_action: "remove",
})}
/>
</Table.Cell>
</Table.Row>
)
)}
</Table>
</Stack.Item>
</Stack>
);
};
export const prepareSearch = (items, searchText = '') => {
const testSearch = createSearch<Equipment>(searchText,
item => item.name);
return flow([
// Optional search term
searchText && filter(testSearch),
])(items);
};
@@ -35,33 +35,12 @@ type GenitalData = {
name: string,
key: string,
visibility: string,
extras: string,
extra_choices: string[],
possible_choices: string[],
can_arouse: boolean,
arousal_state: boolean,
always_accessible: boolean,
}
type GenitalManagerInfo = {
isTargetSelf: boolean;
genital_fluids: GenitalFluid[];
genital_interactibles: GenitalInteractionInfos[];
}
type GenitalInteractionInfos = {
name: string,
key: string,
possible_choices: string[],
equipments: string[],
}
type GenitalFluid = {
name: string,
key: string,
fluids: number,
}
type CharacterPrefsInfo = {
erp_pref: number,
noncon_pref: number,
@@ -114,7 +93,7 @@ export const MobInteraction = (props, context) => {
return (
<Window
width={530}
width={430}
height={700}
resizable>
<Window.Content overflow="auto">
@@ -170,9 +149,6 @@ export const MobInteraction = (props, context) => {
<Tabs.Tab selected={tabIndex === 3} onClick={() => setTabIndex(3)}>
Preferences
</Tabs.Tab>
<Tabs.Tab selected={tabIndex === 4} onClick={() => setTabIndex(4)}>
Genital Manager
</Tabs.Tab>
</Tabs>
{tabIndex === 0 && (
<InteractionsTab />
@@ -182,8 +158,6 @@ export const MobInteraction = (props, context) => {
<CharacterPrefsTab />
) || tabIndex === 3 && (
<ContentPreferencesTab />
) || tabIndex === 4 && (
<GenitalManagerTab />
) || ("Somehow, you've got into an invalid page, please report this.")}
</Section>
</Window.Content>
@@ -266,7 +240,6 @@ const ModeToIcon = {
"Hidden by clothes": "tshirt",
"Hidden by underwear": "low-vision",
"Always hidden": "eye-slash",
"Allows egg stuffing": "egg",
};
/*
@@ -318,19 +291,6 @@ const GenitalTab = (props, context) => {
genital: genital.key,
set_arousal: !genital.arousal_state,
})} />
{genital.extra_choices instanceof Array
? genital.extra_choices.map(choice => (
<Button
width="50%"
key={choice}
tooltip={choice}
icon={ModeToIcon[choice]}
color={genital.extras === choice ? "green" : "default"}
onClick={() => act('genital', {
genital: genital.key,
visibility: choice,
})} />
)) : null}
<Button
width="49%"
key={genital.always_accessible}
@@ -357,71 +317,6 @@ const GenitalTab = (props, context) => {
);
};
const GenitalManagerTab = (props, context) => {
const { act, data } = useBackend<GenitalManagerInfo>(context);
const isTargetSelf = data.isTargetSelf;
const genital_fluids = data.genital_fluids || [];
const genital_interactibles = data.genital_interactibles || [];
return (
genital_fluids.length || genital_interactibles.length ? (
<>
<Section title="Genital Fluids">
<LabeledList>
{genital_fluids.map(genital => (
<LabeledList.Item key={genital['key']} label={genital['name']}>
<ProgressBar
key={genital['key']}
value={genital['fluid'] ? genital['fluid'] : 0.0}
color="white" />
</LabeledList.Item>
))}
</LabeledList>
</Section>
<Section title="Actions">
{genital_interactibles.map(genital => (
<Section key={genital.key} title={genital.name}>
{
genital.equipments.length ? (
<>
<b>Equipments:</b>
<Table direction="column">
{genital.equipments.map(equipment => (
<TableRow key={equipment}>
{equipment}
</TableRow>
))}
</Table>
<Divider />
</>
) : null
}
{genital.possible_choices.map(choice => (
<Button
key={choice}
content={choice}
tooltip={choice}
onClick={() => act('genital_interaction', {
genital: genital.key,
action: choice,
})} />
))}
</Section>
))}
</Section>
</>
) : (
<Section align="center">
{
isTargetSelf
? "You don't seem to have any genitals... Or any that you could do anything with"
: "They don't seem to have any genitals... Or any that you could do anything with"
}
</Section>
)
);
};
const CharacterPrefsTab = (props, context) => {
const { act, data } = useBackend<CharacterPrefsInfo>(context);
const {
@@ -433,7 +328,7 @@ const CharacterPrefsTab = (props, context) => {
extreme_harm,
} = data;
return (
<Stack direction="column">
<Flex direction="column">
<LabeledList>
<LabeledList.Item label="ERP Preference">
<Button
@@ -569,7 +464,7 @@ const CharacterPrefsTab = (props, context) => {
</LabeledList.Item>
) : (null)}
</LabeledList>
</Stack>
</Flex>
);
};
@@ -0,0 +1,54 @@
@use 'sass:color';
@use 'sass:meta';
@use '../colors.scss' with (
$primary: #ae4ab8,
$fg-map-keys: (),
$bg-map-keys: (),
);
@use '../base.scss' with (
$color-bg: #662a6b,
$color-bg-grad-spread: 6%,
$border-radius: 2px,
);
.theme-hotpink {
// Atomic classes
@include meta.load-css('../atomic/color.scss');
// Components
@include meta.load-css('../components/Button.scss', $with: (
'color-default': colors.$primary,
'color-disabled': #363636,
'color-selected': #465899,
'color-caution': #be09a6,
'color-danger': #be0909,
));
@include meta.load-css('../components/Input.scss', $with: (
'border-color': colors.$primary,
));
@include meta.load-css('../components/NoticeBox.scss', $with: (
'background-color': #a82d55,
));
@include meta.load-css('../components/NumberInput.scss', $with: (
'border-color': colors.$primary,
));
@include meta.load-css('../components/ProgressBar.scss', $with: (
'background-color': rgba(0, 0, 0, 0.5),
));
@include meta.load-css('../components/Section.scss');
@include meta.load-css('../components/Tooltip.scss', $with: (
'background-color': #a82d55,
));
// Layouts
@include meta.load-css('../layouts/Layout.scss');
@include meta.load-css('../layouts/Window.scss');
@include meta.load-css('../layouts/TitleBar.scss', $with: (
'background-color': colors.$primary,
));
.Layout__content {
background-image: none;
}
}