diff --git a/code/__DEFINES/movespeed_modification.dm b/code/__DEFINES/movespeed_modification.dm
index bc1e6f7d5f..4d40474a3c 100644
--- a/code/__DEFINES/movespeed_modification.dm
+++ b/code/__DEFINES/movespeed_modification.dm
@@ -59,6 +59,7 @@
#define MOVESPEED_ID_SPECIES "SPECIES_SPEED_MOD"
+#define MOVESPEED_ID_SMALL_STRIDE "SMALL_STRIDE"
#define MOVESPEED_ID_PRONE_DRAGGING "PRONE_DRAG"
#define MOVESPEED_ID_HUMAN_CARRYING "HUMAN_CARRY"
#define MOVESPEED_ID_SHRINK_RAY "SHRUNKEN_SPEED_MODIFIER"
diff --git a/code/__HELPERS/mobs.dm b/code/__HELPERS/mobs.dm
index febd333578..e022c10007 100644
--- a/code/__HELPERS/mobs.dm
+++ b/code/__HELPERS/mobs.dm
@@ -209,7 +209,8 @@
"ipc_antenna" = "None",
"flavor_text" = "",
"meat_type" = "Mammalian",
- "body_model" = MALE
+ "body_model" = MALE,
+ "body_size" = RESIZE_DEFAULT_SIZE
))
/proc/random_hair_style(gender)
diff --git a/code/controllers/configuration/entries/game_options.dm b/code/controllers/configuration/entries/game_options.dm
index f253bc543d..f11093cba7 100644
--- a/code/controllers/configuration/entries/game_options.dm
+++ b/code/controllers/configuration/entries/game_options.dm
@@ -449,3 +449,27 @@
key_mode = KEY_MODE_TEXT
value_mode = VALUE_MODE_FLAG
config_entry_value = list(GEN_VISIBLE_NO_CLOTHES, GEN_VISIBLE_NO_UNDIES, GEN_VISIBLE_NEVER) //refer to cit_helpers for all toggles.
+
+//Body size configs, the feature will be disabled if both min and max have the same value.
+/datum/config_entry/number/body_size_min
+ config_entry_value = RESIZE_DEFAULT_SIZE
+ min_val = 0.1 //to avoid issues with zeros and negative values.
+ max_val = RESIZE_DEFAULT_SIZE
+
+/datum/config_entry/number/body_size_max
+ config_entry_value = RESIZE_DEFAULT_SIZE
+ min_val = RESIZE_DEFAULT_SIZE
+
+//Pun-Pun movement slowdown given to characters with a body size smaller than this value,
+//to compensate for their smaller hitbox.
+//To disable, just make sure the value is lower than 'body_size_min'
+/datum/config_entry/number/threshold_body_size_slowdown
+ config_entry_value = RESIZE_DEFAULT_SIZE * 0.85
+ min_val = 0
+ max_val = RESIZE_DEFAULT_SIZE
+
+//multiplicative slowdown multiplier. See 'dna.update_body_size' for the operation.
+//doesn't apply to floating or crawling mobs
+/datum/config_entry/number/body_size_slowdown_multiplier
+ config_entry_value = 0.25
+ min_val = 0.1 //To encourage folks to disable the slowdown through the above config instead.
diff --git a/code/datums/dna.dm b/code/datums/dna.dm
index 95b45b6bc2..1c866ea535 100644
--- a/code/datums/dna.dm
+++ b/code/datums/dna.dm
@@ -41,6 +41,7 @@
/datum/dna/proc/transfer_identity(mob/living/carbon/destination, transfer_SE = 0)
if(!istype(destination))
return
+ var/old_size = destination.dna.features["body_size"]
destination.dna.unique_enzymes = unique_enzymes
destination.dna.uni_identity = uni_identity
destination.dna.blood_type = blood_type
@@ -56,6 +57,8 @@
if(transfer_SE)
destination.dna.mutation_index = mutation_index
+ destination.dna.update_body_size(old_size)
+
SEND_SIGNAL(destination, COMSIG_CARBON_IDENTITY_TRANSFERRED_TO, src, transfer_SE)
/datum/dna/proc/copy_dna(datum/dna/new_dna)
@@ -368,7 +371,9 @@
/mob/living/carbon/human/proc/hardset_dna(ui, list/mutation_index, newreal_name, newblood_type, datum/species/mrace, newfeatures)
if(newfeatures)
+ var/old_size = dna.features["body_size"]
dna.features = newfeatures
+ dna.update_body_size(old_size)
if(mrace)
var/datum/species/newrace = new mrace.type
@@ -644,3 +649,15 @@
gib()
else
set_species(/datum/species/dullahan)
+
+/datum/dna/proc/update_body_size(old_size)
+ if(!holder || features["body_size"] == old_size)
+ return
+ holder.resize = features["body_size"] / old_size
+ holder.update_transform()
+ var/danger = CONFIG_GET(number/threshold_body_size_slowdown)
+ if(features["body_size"] < danger)
+ var/slowdown = 1 + round(danger/features["body_size"], 0.1) * CONFIG_GET(number/body_size_slowdown_multiplier)
+ holder.add_movespeed_modifier(MOVESPEED_ID_SMALL_STRIDE, TRUE, 100, NONE, TRUE, slowdown, ALL, FLOATING|CRAWLING)
+ else if(old_size < danger)
+ holder.remove_movespeed_modifier(MOVESPEED_ID_SMALL_STRIDE)
diff --git a/code/modules/client/preferences.dm b/code/modules/client/preferences.dm
index adfcd547bc..f7d251f32d 100644
--- a/code/modules/client/preferences.dm
+++ b/code/modules/client/preferences.dm
@@ -150,7 +150,8 @@ GLOBAL_LIST_EMPTY(preferences_datums)
"ipc_antenna" = "None",
"flavor_text" = "",
"meat_type" = "Mammalian",
- "body_model" = MALE
+ "body_model" = MALE,
+ "body_size" = RESIZE_DEFAULT_SIZE
)
var/list/custom_names = list()
@@ -294,7 +295,7 @@ GLOBAL_LIST_EMPTY(preferences_datums)
dat += "[real_name]
"
dat += "Be nameless: [nameless ? "Yes" : "No"]
"
- dat += "Gender: [gender == MALE ? "Male" : (gender == FEMALE ? "Female" : (gender == PLURAL ? "Non-binary" : "Object"))]
"
+ dat += "Gender: [gender == MALE ? "Male" : (gender == FEMALE ? "Female" : (gender == PLURAL ? "Non-binary" : "Object"))]
"
dat += "Age: [age]
"
dat += "Special Names:
"
@@ -345,7 +346,7 @@ GLOBAL_LIST_EMPTY(preferences_datums)
else
dat += "[TextPreview(features["flavor_text"])]...
"
dat += "
Body
"
- dat += "Gender:[gender == MALE ? "Male" : (gender == FEMALE ? "Female" : (gender == PLURAL ? "Non-binary" : "Object"))]
"
+ dat += "Gender:[gender == MALE ? "Male" : (gender == FEMALE ? "Female" : (gender == PLURAL ? "Non-binary" : "Object"))]
"
if(gender != NEUTER && pref_species.sexes)
dat += "Body Model:[features["body_model"] == MALE ? "Masculine" : "Feminine"]
"
dat += "Species:[pref_species.name]
"
@@ -379,6 +380,9 @@ GLOBAL_LIST_EMPTY(preferences_datums)
dat += " Change
"
mutant_colors = TRUE
+ if (CONFIG_GET(number/body_size_min) != CONFIG_GET(number/body_size_max))
+ dat += "Sprite Size: [features["body_size"]]%
"
+
if((EYECOLOR in pref_species.species_traits) && !(NOEYES in pref_species.species_traits))
if(!use_skintones && !mutant_colors)
@@ -2119,6 +2123,38 @@ GLOBAL_LIST_EMPTY(preferences_datums)
if(new_toggle_color)
hud_toggle_color = new_toggle_color
+ if("gender")
+ var/chosengender = input(user, "Select your character's gender.", "Gender Selection", gender) as null|anything in list(MALE,FEMALE,"nonbinary","object")
+ if(!chosengender)
+ return
+ switch(chosengender)
+ if("nonbinary")
+ chosengender = PLURAL
+ features["body_model"] = pick(MALE, FEMALE)
+ if("object")
+ chosengender = NEUTER
+ features["body_model"] = MALE
+ else
+ features["body_model"] = chosengender
+ gender = chosengender
+ facial_hair_style = random_facial_hair_style(gender)
+ hair_style = random_hair_style(gender)
+
+ if("body_size")
+ var/min = CONFIG_GET(number/body_size_min)
+ var/max = CONFIG_GET(number/body_size_max)
+ var/danger = CONFIG_GET(number/threshold_body_size_slowdown)
+ var/new_body_size = input(user, "Choose your desired sprite size:\n([min*100]%-[max*100]%), Warning: May make your character look distorted[danger > min ? ", and an exponential slowdown will occur for those smaller than [danger*100]%!" : "!"]", "Character Preference", features["body_size"]*100) as num|null
+ if (new_body_size)
+ new_body_size = CLAMP(new_body_size * 0.01, min, max)
+ var/dorfy
+ if(danger > new_body_size)
+ dorfy = alert(user, "The chosen size appears to be smaller than the threshold of [danger*100]%, which will lead to an added exponential slowdown. Are you sure about that?", "Dwarfism Alert", "Yes", "Move it to the threshold", "No")
+ if(!dorfy || dorfy == "Move it above the threshold")
+ new_body_size = danger
+ if(dorfy != "No")
+ features["body_size"] = new_body_size
+
else
switch(href_list["preference"])
//CITADEL PREFERENCES EDIT - I can't figure out how to modularize these, so they have to go here. :c -Pooj
@@ -2171,22 +2207,6 @@ GLOBAL_LIST_EMPTY(preferences_datums)
if("publicity")
if(unlock_content)
toggles ^= MEMBER_PUBLIC
- if("gender")
- var/chosengender = input(user, "Select your character's gender.", "Gender Selection", gender) as null|anything in list(MALE,FEMALE,"nonbinary","object")
- if(!chosengender)
- return
- switch(chosengender)
- if("nonbinary")
- chosengender = PLURAL
- features["body_model"] = pick(MALE, FEMALE)
- if("object")
- chosengender = NEUTER
- features["body_model"] = MALE
- else
- features["body_model"] = chosengender
- gender = chosengender
- facial_hair_style = random_facial_hair_style(gender)
- hair_style = random_hair_style(gender)
if("body_model")
features["body_model"] = features["body_model"] == MALE ? FEMALE : MALE
@@ -2431,6 +2451,8 @@ GLOBAL_LIST_EMPTY(preferences_datums)
pref_species = new /datum/species/human
save_character()
+ var/old_size = character.dna.features["body_size"]
+
character.dna.features = features.Copy()
character.set_species(chosen_species, icon_update = FALSE, pref_load = TRUE)
character.dna.real_name = character.real_name
@@ -2452,6 +2474,8 @@ GLOBAL_LIST_EMPTY(preferences_datums)
character.give_genitals(TRUE) //character.update_genitals() is already called on genital.update_appearance()
+ character.dna.update_body_size(old_size)
+
SEND_SIGNAL(character, COMSIG_HUMAN_PREFS_COPIED_TO, src, icon_updates, roundstart_checks)
//let's be sure the character updates
diff --git a/code/modules/client/preferences_savefile.dm b/code/modules/client/preferences_savefile.dm
index ca51a27dae..7da9d5a1dc 100644
--- a/code/modules/client/preferences_savefile.dm
+++ b/code/modules/client/preferences_savefile.dm
@@ -395,6 +395,7 @@ SAVEFILE UPDATING/VERSIONING - 'Simplified', or rather, more coder-friendly ~Car
S["body_is_always_random"] >> be_random_body
S["gender"] >> gender
S["body_model"] >> features["body_model"]
+ S["body_size"] >> features["body_size"]
S["age"] >> age
S["hair_color"] >> hair_color
S["facial_hair_color"] >> facial_hair_color
@@ -568,6 +569,14 @@ SAVEFILE UPDATING/VERSIONING - 'Simplified', or rather, more coder-friendly ~Car
features["insect_markings"] = sanitize_inlist(features["insect_markings"], GLOB.insect_markings_list, "None")
features["insect_wings"] = sanitize_inlist(features["insect_wings"], GLOB.insect_wings_list)
+ var/static/size_min
+ if(!size_min)
+ size_min = CONFIG_GET(number/body_size_min)
+ var/static/size_max
+ if(!size_max)
+ size_max = CONFIG_GET(number/body_size_max)
+ features["body_size"] = sanitize_integer(features["body_size"], size_min, size_max, RESIZE_DEFAULT_SIZE)
+
var/static/list/B_sizes
if(!B_sizes)
var/list/L = CONFIG_GET(keyed_list/breasts_cups_prefs)
@@ -643,6 +652,7 @@ SAVEFILE UPDATING/VERSIONING - 'Simplified', or rather, more coder-friendly ~Car
WRITE_FILE(S["body_is_always_random"] , be_random_body)
WRITE_FILE(S["gender"] , gender)
WRITE_FILE(S["body_model"] , features["body_model"])
+ WRITE_FILE(S["body_size"] , features["body_size"])
WRITE_FILE(S["age"] , age)
WRITE_FILE(S["hair_color"] , hair_color)
WRITE_FILE(S["facial_hair_color"] , facial_hair_color)
diff --git a/config/game_options.txt b/config/game_options.txt
index c3930fd325..8c48ac6ede 100644
--- a/config/game_options.txt
+++ b/config/game_options.txt
@@ -620,3 +620,16 @@ BREASTS_CUPS_PREFS e
## Minimum and maximum limits for penis length from the character creation menu.
PENIS_MIN_INCHES_PREFS 1
PENIS_MAX_INCHES_PREFS 20
+
+## Body size configs, the feature will be disabled if both min and max have the same value.
+BODY_SIZE_MIN 1
+BODY_SIZE_MAX 1
+
+## Pun-Pun movement slowdown given to characters with a body size smaller than this value,
+## to compensate for their smaller hitbox.
+## To disable, just make sure the value is lower than 'body_size_min'
+THRESHOLD_BODY_SIZE_SLOWDOWN 0.85
+
+## Multiplier used in the smaller strides slowdown calculation.
+## Doesn't apply to floating or crawling mobs.
+BODY_SIZE_SLOWDOWN_MULTIPLIER 0.25
\ No newline at end of file