Merge branch 'master' of https://github.com/SPLURT-Station/S.P.L.U.R.T-Station-13 into Chastity-system
@@ -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" = (
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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" = (
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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,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.
|
||||
|
||||
|
||||
@@ -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"
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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()
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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]
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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"
|
||||
|
||||
@@ -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 @@
|
||||
"5.5.0"
|
||||
"5.6.0"
|
||||
|
||||
@@ -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"
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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
|
||||
@@ -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
|
||||
@@ -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
|
||||
|
||||
@@ -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]")
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
|
Before Width: | Height: | Size: 54 KiB After Width: | Height: | Size: 48 KiB |
|
Before Width: | Height: | Size: 72 KiB After Width: | Height: | Size: 73 KiB |
|
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
|
||||
|
||||
@@ -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,
|
||||
|
||||
|
After Width: | Height: | Size: 386 B |
@@ -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"
|
||||
|
||||
@@ -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'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;
|
||||
}
|
||||
}
|
||||