mirror of
https://github.com/SPLURT-Station/S.P.L.U.R.T-Station-13.git
synced 2026-08-30 08:07:03 +01:00
Merge branch 'master' of https://github.com/SPLURT-Station/S.P.L.U.R.T-Station-13 into Chastity-system
This commit is contained in:
@@ -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)
|
||||
|
||||
+20
-2
@@ -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"
|
||||
|
||||
+7
-161
@@ -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
|
||||
|
||||
Reference in New Issue
Block a user