diff --git a/SQL/paradise_schema.sql b/SQL/paradise_schema.sql
index 22369d5470d..ba319ac8491 100644
--- a/SQL/paradise_schema.sql
+++ b/SQL/paradise_schema.sql
@@ -79,6 +79,7 @@ CREATE TABLE `characters` (
`nanotrasen_relation` varchar(45) NOT NULL,
`speciesprefs` int(1) NOT NULL,
`socks` mediumtext NOT NULL,
+ `body_accessory` mediumtext NOT NULL,
PRIMARY KEY (`id`)
) ENGINE=InnoDB AUTO_INCREMENT=18747 DEFAULT CHARSET=utf8;
/*!40101 SET character_set_client = @saved_cs_client */;
diff --git a/code/modules/client/preferences.dm b/code/modules/client/preferences.dm
index 37e2f588fc3..bb8065b845e 100644
--- a/code/modules/client/preferences.dm
+++ b/code/modules/client/preferences.dm
@@ -128,6 +128,7 @@ datum/preferences
var/species = "Human"
var/language = "None" //Secondary language
+ var/body_accessory = null
var/speciesprefs = 0//I hate having to do this, I really do (Using this for oldvox code, making names universal I guess
@@ -366,10 +367,14 @@ datum/preferences
dat += "
Eyes
"
dat += "Change Color
"
- if(species == "Unathi" || species == "Tajaran" || species == "Skrell" || species == "Slime People" || species == "Vulpkanin")
+ if(species == "Unathi" || species == "Tajaran" || species == "Skrell" || species == "Slime People" || species == "Vulpkanin" || body_accessory_by_species[species] || check_rights(R_ADMIN, 1, user)) //admins can always fuck with this, because they are admins
dat += "
Body Color
"
dat += "Change Color "
+ if(body_accessory_by_species[species] || check_rights(R_ADMIN, 1, user))
+ dat += "
Body Accessory
"
+ dat += "Accessory: [body_accessory ? "[body_accessory]" : "None"]
"
+
dat += "
"
if (1) // General Preferences
@@ -1165,10 +1170,27 @@ datum/preferences
valid_hairstyles[hairstyle] = hair_styles_list[hairstyle]
- var/new_h_style = input(user, "Choose your character's hair style:", "Character Preference") as null|anything in valid_hairstyles
+ var/new_h_style = input(user, "Choose your character's hair style:", "Character Preference") as null|anything in valid_hairstyles
if(new_h_style)
h_style = new_h_style
+ if("body_accessory")
+ var/list/possible_body_accessories = list()
+ if(check_rights(R_ADMIN, 1, user))
+ possible_body_accessories = body_accessory_by_name.Copy()
+ else
+ for(var/B in body_accessory_by_name)
+ var/datum/body_accessory/accessory = body_accessory_by_name[B]
+ if(!istype(accessory))
+ possible_body_accessories += "None" //the only null entry should be the "None" option
+ continue
+ if(species in accessory.allowed_species)
+ possible_body_accessories += B
+
+ var/new_body_accessory = input(user, "Choose your body accessory:", "Character Preference") as null|anything in possible_body_accessories
+ if(new_body_accessory)
+ body_accessory = new_body_accessory
+
if("facial")
var/new_facial = input(user, "Choose your character's facial-hair colour:", "Character Preference") as color|null
if(new_facial)
@@ -1246,7 +1268,7 @@ datum/preferences
s_tone = 35 - max(min( round(new_s_tone), 220),1)
if("skin")
- if(species == "Unathi" || species == "Tajaran" || species == "Skrell" || species == "Slime People"|| species == "Vulpkanin")
+ if(species == "Unathi" || species == "Tajaran" || species == "Skrell" || species == "Slime People"|| species == "Vulpkanin" || body_accessory_by_species[species] || check_rights(R_ADMIN, 1, user))
var/new_skin = input(user, "Choose your character's skin colour: ", "Character Preference") as color|null
if(new_skin)
r_skin = hex2num(copytext(new_skin, 2, 4))
@@ -1571,6 +1593,9 @@ datum/preferences
character.undershirt = undershirt
character.socks = socks
+ if(body_accessory)
+ character.body_accessory = body_accessory_by_name["[body_accessory]"]
+
if(backbag > 4 || backbag < 1)
backbag = 1 //Same as above
character.backbag = backbag
diff --git a/code/modules/client/preferences_mysql.dm b/code/modules/client/preferences_mysql.dm
index 8470ece80a9..f60b315d912 100644
--- a/code/modules/client/preferences_mysql.dm
+++ b/code/modules/client/preferences_mysql.dm
@@ -151,7 +151,8 @@
//socks
socks = query.item[54]
-
+ body_accessory = query.item[55]
+
//Sanitize
metadata = sanitize_text(metadata, initial(metadata))
real_name = reject_bad_name(real_name)
@@ -200,7 +201,7 @@
be_special = sanitize_integer(be_special, 0, 65535, initial(be_special))
socks = sanitize_text(socks, initial(socks))
-
+ body_accessory = sanitize_text(body_accessory, initial(body_accessory))
// if(isnull(disabilities)) disabilities = 0
if(!player_alt_titles) player_alt_titles = new()
@@ -274,7 +275,8 @@
rlimb_data='[rlimblist]',
nanotrasen_relation='[nanotrasen_relation]',
speciesprefs='[speciesprefs]',
- socks='[socks]'
+ socks='[socks]',
+ body_accessory='[body_accessory]'
WHERE ckey='[C.ckey]'
AND slot='[default_slot]'"}
)
@@ -302,7 +304,9 @@
job_karma_high, job_karma_med, job_karma_low,
flavor_text, med_record, sec_record, gen_record,
player_alt_titles, be_special,
- disabilities, organ_data, rlimb_data, nanotrasen_relation, speciesprefs, socks)
+ disabilities, organ_data, rlimb_data, nanotrasen_relation, speciesprefs,
+ socks, body_accessory)
+
VALUES
('[C.ckey]', '[default_slot]', '[sql_sanitize_text(metadata)]', '[sql_sanitize_text(real_name)]', '[be_random_name]','[gender]',
'[age]', '[sql_sanitize_text(species)]', '[sql_sanitize_text(language)]',
@@ -319,7 +323,9 @@
'[job_karma_high]', '[job_karma_med]', '[job_karma_low]',
'[sql_sanitize_text(flavor_text)]', '[sql_sanitize_text(med_record)]', '[sql_sanitize_text(sec_record)]', '[sql_sanitize_text(gen_record)]',
'[playertitlelist]', '[be_special]',
- '[disabilities]', '[organlist]', '[rlimblist]', '[nanotrasen_relation]', '[speciesprefs]','[socks]')
+ '[disabilities]', '[organlist]', '[rlimblist]', '[nanotrasen_relation]', '[speciesprefs]',
+ '[socks]', '[body_accessory]')
+
"}
)
diff --git a/code/modules/mob/living/carbon/human/body_accessories.dm b/code/modules/mob/living/carbon/human/body_accessories.dm
new file mode 100644
index 00000000000..2a8dfa4e458
--- /dev/null
+++ b/code/modules/mob/living/carbon/human/body_accessories.dm
@@ -0,0 +1,128 @@
+
+var/global/list/body_accessory_by_name = list("None" = null)
+
+/hook/startup/proc/initalize_body_accessories()
+
+// __init_body_accessory(/datum/body_accessory/body)
+ __init_body_accessory(/datum/body_accessory/tail)
+
+ if(body_accessory_by_name.len)
+ if(initialize_body_accessory_by_species())
+ return 1
+
+ return 0 //fail if no bodies are found
+
+var/global/list/body_accessory_by_species = list("None" = null)
+
+/proc/initialize_body_accessory_by_species()
+ for(var/B in body_accessory_by_name)
+ var/datum/body_accessory/accessory = body_accessory_by_name[B]
+ if(!istype(accessory)) continue
+
+ for(var/species in accessory.allowed_species)
+ if(!body_accessory_by_species["[species]"]) body_accessory_by_species["[species]"] = list()
+ body_accessory_by_species["[species]"] += accessory
+
+ if(body_accessory_by_species.len)
+ return 1
+ return 0
+
+/proc/__init_body_accessory(var/ba_path)
+ if(ispath(ba_path))
+ var/_added_counter = 0
+
+ for(var/A in subtypesof(ba_path))
+ var/datum/body_accessory/B = new A
+ if(istype(B))
+ body_accessory_by_name[B.name] += B
+ ++_added_counter
+
+ if(_added_counter)
+ return 1
+ return 0
+
+
+/datum/body_accessory
+ var/name = "default"
+
+ var/icon = null
+ var/icon_state = ""
+
+ var/animated_icon = null
+ var/animated_icon_state = ""
+
+ var/blend_mode = null
+
+ var/pixel_x_offset = 0
+ var/pixel_y_offset = 0
+
+ var/list/allowed_species = list()
+
+/datum/body_accessory/proc/try_restrictions(var/mob/living/carbon/human/H)
+ return 1
+
+/datum/body_accessory/proc/get_animated_icon() //return animated if it has it, return static if it does not.
+ if(animated_icon)
+ return animated_icon
+
+ else return icon
+
+/datum/body_accessory/proc/get_animated_icon_state()
+ if(animated_icon_state)
+ return animated_icon_state
+
+ else return icon_state
+
+/*
+//Bodies
+/datum/body_accessory/body
+ blend_mode = ICON_MULTIPLY
+
+/datum/body_accessory/body/snake
+ name = "Snake"
+
+ icon = 'icons/mob/body_accessory_64.dmi'
+ icon_state = "naga"
+
+ pixel_x_offset = -16*/
+
+//Tails
+/datum/body_accessory/tail
+ icon = 'icons/mob/body_accessory.dmi'
+ animated_icon = 'icons/mob/body_accessory.dmi'
+ blend_mode = ICON_ADD
+
+/datum/body_accessory/tail/try_restrictions(var/mob/living/carbon/human/H)
+ if(!H.wear_suit || !(H.wear_suit.flags_inv & HIDETAIL) && !istype(H.wear_suit, /obj/item/clothing/suit/space))
+ return 1
+ return 0
+
+
+//vulpakin
+/datum/body_accessory/tail/vulpkanin_2
+ name = "Vulpkanin Alt 1 (Bushy)"
+
+ icon_state = "vulptail2"
+ animated_icon_state = "vulptail2_a"
+ allowed_species = list("Vulpkanin")
+
+/datum/body_accessory/tail/vulpkanin_3
+ name = "Vulpkanin Alt 2 (Straight)"
+
+ icon_state = "vulptail3"
+ animated_icon_state = "vulptail3_a"
+ allowed_species = list("Vulpkanin")
+
+/datum/body_accessory/tail/vulpkanin_4
+ name = "Vulpkanin Alt 3 (Tiny)"
+
+ icon_state = "vulptail4"
+ animated_icon_state = "vulptail4_a"
+ allowed_species = list("Vulpkanin")
+
+/datum/body_accessory/tail/vulpkanin_5
+ name = "Vulpkanin Alt 4 (Short)"
+
+ icon_state = "vulptail5"
+ animated_icon_state = "vulptail5_a"
+ allowed_species = list("Vulpkanin")
\ No newline at end of file
diff --git a/code/modules/mob/living/carbon/human/emote.dm b/code/modules/mob/living/carbon/human/emote.dm
index 1054a639819..abbe2d2cf3d 100644
--- a/code/modules/mob/living/carbon/human/emote.dm
+++ b/code/modules/mob/living/carbon/human/emote.dm
@@ -116,7 +116,12 @@
m_type = 1
if("wag")
- if(species.bodyflags & TAIL_WAGGING)
+ if(body_accessory)
+ if(body_accessory.try_restrictions(src))
+ message = "[src] starts wagging \his tail."
+ start_tail_wagging(1)
+
+ else if(species.bodyflags & TAIL_WAGGING)
if(!wear_suit || !(wear_suit.flags_inv & HIDETAIL) && !istype(wear_suit, /obj/item/clothing/suit/space))
message = "[src] starts wagging \his tail."
src.start_tail_wagging(1)
@@ -126,7 +131,7 @@
return
if("swag")
- if(species.bodyflags & TAIL_WAGGING)
+ if(species.bodyflags & TAIL_WAGGING || body_accessory)
message = "[src] stops wagging \his tail."
src.stop_tail_wagging(1)
else
diff --git a/code/modules/mob/living/carbon/human/human_defines.dm b/code/modules/mob/living/carbon/human/human_defines.dm
index 7d7d400c9c3..eead644cc58 100644
--- a/code/modules/mob/living/carbon/human/human_defines.dm
+++ b/code/modules/mob/living/carbon/human/human_defines.dm
@@ -82,4 +82,6 @@
var/lastFart = 0 // Toxic fart cooldown.
var/fire_dmi = 'icons/mob/OnFire.dmi'
- var/fire_sprite = "Standing"
\ No newline at end of file
+ var/fire_sprite = "Standing"
+
+ var/datum/body_accessory/body_accessory = null
diff --git a/code/modules/mob/living/carbon/human/update_icons.dm b/code/modules/mob/living/carbon/human/update_icons.dm
index 1590db1c214..e55ee2d1149 100644
--- a/code/modules/mob/living/carbon/human/update_icons.dm
+++ b/code/modules/mob/living/carbon/human/update_icons.dm
@@ -344,7 +344,7 @@ var/global/list/damage_icon_parts = list()
stand_icon.Blend(lips, ICON_OVERLAY)
//tail
- update_tail_showing(0)
+ update_tail_layer(0)
//HAIR OVERLAY
@@ -763,12 +763,12 @@ var/global/list/damage_icon_parts = list()
overlays_standing[SUIT_LAYER] = standing
- update_tail_showing(0)
+ update_tail_layer(0)
else
overlays_standing[SUIT_LAYER] = null
- update_tail_showing(0)
+ update_tail_layer(0)
update_collar(0)
@@ -893,10 +893,20 @@ var/global/list/damage_icon_parts = list()
overlays_standing[L_HAND_LAYER] = null
if(update_icons) update_icons()
-/mob/living/carbon/human/proc/update_tail_showing(var/update_icons=1)
+
+
+/mob/living/carbon/human/proc/update_tail_layer(var/update_icons=1)
overlays_standing[TAIL_LAYER] = null
- if(species.tail && species.bodyflags & HAS_TAIL)
+ if(body_accessory)
+ if(body_accessory.try_restrictions(src))
+ var/icon/accessory_s = new/icon("icon" = body_accessory.icon, "icon_state" = body_accessory.icon_state)
+ accessory_s.Blend(rgb(r_skin, g_skin, b_skin), body_accessory.blend_mode)
+
+ overlays_standing[TAIL_LAYER] = image(accessory_s, "pixel_x" = body_accessory.pixel_x_offset, "pixel_y" = body_accessory.pixel_y_offset)
+
+
+ else if(species.tail && species.bodyflags & HAS_TAIL) //no tailless tajaran
if(!wear_suit || !(wear_suit.flags_inv & HIDETAIL) && !istype(wear_suit, /obj/item/clothing/suit/space))
var/icon/tail_s = new/icon("icon" = 'icons/effects/species.dmi', "icon_state" = "[species.tail]_s")
tail_s.Blend(rgb(r_skin, g_skin, b_skin), ICON_ADD)
@@ -910,7 +920,13 @@ var/global/list/damage_icon_parts = list()
/mob/living/carbon/human/proc/start_tail_wagging(var/update_icons=1)
overlays_standing[TAIL_LAYER] = null
- if(species.tail && species.bodyflags & HAS_TAIL)
+ if(body_accessory)
+ var/icon/accessory_s = new/icon("icon" = body_accessory.get_animated_icon(), "icon_state" = body_accessory.get_animated_icon_state())
+ accessory_s.Blend(rgb(r_skin, g_skin, b_skin), body_accessory.blend_mode)
+
+ overlays_standing[TAIL_LAYER] = image(accessory_s, "pixel_x" = body_accessory.pixel_x_offset, "pixel_y" = body_accessory.pixel_y_offset)
+
+ else if(species.tail && species.bodyflags & HAS_TAIL)
var/icon/tailw_s = new/icon("icon" = 'icons/effects/species.dmi', "icon_state" = "[species.tail]w_s")
tailw_s.Blend(rgb(r_skin, g_skin, b_skin), ICON_ADD)
@@ -922,14 +938,11 @@ var/global/list/damage_icon_parts = list()
/mob/living/carbon/human/proc/stop_tail_wagging(var/update_icons=1)
overlays_standing[TAIL_LAYER] = null
- if(species.tail && species.bodyflags & HAS_TAIL)
- var/icon/tail_s = new/icon("icon" = 'icons/effects/species.dmi', "icon_state" = "[species.tail]_s")
- tail_s.Blend(rgb(r_skin, g_skin, b_skin), ICON_ADD)
+ update_tail_layer(update_icons) //just trigger a full update for normal stationary sprites
- overlays_standing[TAIL_LAYER] = image(tail_s)
-
- if(update_icons)
- update_icons()
+/mob/living/carbon/human/handle_transform_change()
+ ..()
+ update_tail_layer()
//Adds a collar overlay above the helmet layer if the suit has one
// Suit needs an identically named sprite in icons/mob/collar.dmi
diff --git a/code/modules/mob/living/carbon/update_icons.dm b/code/modules/mob/living/carbon/update_icons.dm
index 02fe01c39a1..e22ad5e66dc 100644
--- a/code/modules/mob/living/carbon/update_icons.dm
+++ b/code/modules/mob/living/carbon/update_icons.dm
@@ -28,4 +28,8 @@
if(changed)
animate(src, transform = ntransform, time = 2, pixel_y = final_pixel_y, dir = final_dir, easing = EASE_IN|EASE_OUT)
- floating = 0 // If we were without gravity, the bouncing animation got stopped, so we make sure we restart it in next life().
\ No newline at end of file
+ handle_transform_change()
+ floating = 0 // If we were without gravity, the bouncing animation got stopped, so we make sure we restart it in next life().
+
+/mob/living/carbon/proc/handle_transform_change()
+ return
\ No newline at end of file
diff --git a/icons/mob/body_accessory.dmi b/icons/mob/body_accessory.dmi
new file mode 100644
index 00000000000..fac217e71fa
Binary files /dev/null and b/icons/mob/body_accessory.dmi differ
diff --git a/paradise.dme b/paradise.dme
index 083fa7a3c81..c56fea4d065 100644
--- a/paradise.dme
+++ b/paradise.dme
@@ -1328,6 +1328,7 @@
#include "code\modules\mob\living\carbon\brain\posibrain.dm"
#include "code\modules\mob\living\carbon\brain\say.dm"
#include "code\modules\mob\living\carbon\human\appearance.dm"
+#include "code\modules\mob\living\carbon\human\body_accessories.dm"
#include "code\modules\mob\living\carbon\human\death.dm"
#include "code\modules\mob\living\carbon\human\emote.dm"
#include "code\modules\mob\living\carbon\human\examine.dm"