diff --git a/SQL/paradise_schema.sql b/SQL/paradise_schema.sql
index c6045cd835e..f36d62f7339 100644
--- a/SQL/paradise_schema.sql
+++ b/SQL/paradise_schema.sql
@@ -78,6 +78,7 @@ CREATE TABLE `characters` (
`rlimb_data` mediumtext NOT NULL,
`nanotrasen_relation` varchar(45) NOT NULL,
`speciesprefs` int(1) 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 338d57dd616..785e4718617 100644
--- a/code/modules/client/preferences.dm
+++ b/code/modules/client/preferences.dm
@@ -127,6 +127,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
@@ -185,17 +186,17 @@ datum/preferences
// jukebox volume
var/volume = 100
-
+
// BYOND membership
var/unlock_content = 0
-
+
/datum/preferences/New(client/C)
b_type = pick(4;"O-", 36;"O+", 3;"A-", 28;"A+", 1;"B-", 20;"B+", 1;"AB-", 5;"AB+")
if(istype(C))
if(!IsGuestKey(C.key))
unlock_content = C.IsByondMember()
if(unlock_content)
- max_save_slots = MAX_SAVE_SLOTS_MEMBER
+ max_save_slots = MAX_SAVE_SLOTS_MEMBER
var/loaded_preferences_successfully = load_preferences(C)
if(loaded_preferences_successfully)
if(load_character(C))
@@ -364,10 +365,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
@@ -385,7 +390,7 @@ datum/preferences
dat += "Ghost radio: [(toggles & CHAT_GHOSTRADIO) ? "Nearest Speakers" : "All Chatter"]
"
if(config.allow_Metadata)
dat += "OOC notes: Edit
"
-
+
if(user.client)
if(user.client.holder)
dat += "Adminhelp sound: "
@@ -393,7 +398,7 @@ datum/preferences
if(check_rights(R_ADMIN,0))
dat += "OOC: Change
"
-
+
if(unlock_content)
dat += "BYOND Membership Publicity: [(toggles & MEMBER_PUBLIC) ? "Public" : "Hidden"]
"
@@ -1160,10 +1165,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)
@@ -1225,7 +1247,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))
@@ -1360,7 +1382,7 @@ datum/preferences
if("publicity")
if(unlock_content)
toggles ^= MEMBER_PUBLIC
-
+
if("gender")
if(gender == MALE)
gender = FEMALE
@@ -1549,6 +1571,9 @@ datum/preferences
character.underwear = underwear
character.undershirt = undershirt
+ 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 96e51448c6d..f89a76d7689 100644
--- a/code/modules/client/preferences_mysql.dm
+++ b/code/modules/client/preferences_mysql.dm
@@ -148,6 +148,7 @@
rlimb_data = params2list(query.item[51])
nanotrasen_relation = query.item[52]
speciesprefs = text2num(query.item[53])
+ body_accessory = query.item[54]
//Sanitize
metadata = sanitize_text(metadata, initial(metadata))
@@ -267,7 +268,8 @@
organ_data='[organlist]',
rlimb_data='[rlimblist]',
nanotrasen_relation='[nanotrasen_relation]',
- speciesprefs='[speciesprefs]'
+ speciesprefs='[speciesprefs]',
+ body_accessory='[body_accessory]'
WHERE ckey='[C.ckey]'
AND slot='[default_slot]'"}
)
@@ -295,7 +297,8 @@
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)
+ disabilities, organ_data, rlimb_data, nanotrasen_relation, speciesprefs,
+ 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)]',
@@ -312,7 +315,8 @@
'[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]')
+ '[disabilities]', '[organlist]', '[rlimblist]', '[nanotrasen_relation]', '[speciesprefs]',
+ '[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..4ad65f05ffb
--- /dev/null
+++ b/code/modules/mob/living/carbon/human/body_accessories.dm
@@ -0,0 +1,79 @@
+var/global/list/body_accessory_by_name = list("None" = null)
+
+/hook/startup/proc/initalize_body_accessories()
+ //each accessory subtype needs it's own loop
+ for(var/type in subtypesof(/datum/body_accessory/body))
+ var/datum/body_accessory/ac1 = new type
+ if(istype(ac1))
+ body_accessory_by_name["[ac1.name]"] = ac1
+
+ for(var/type in subtypesof(/datum/body_accessory/tail))
+ var/datum/body_accessory/ac2 = new type
+ if(istype(ac2))
+ body_accessory_by_name["[ac2.name]"] = ac2
+
+ 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/A in accessory.allowed_species)
+ body_accessory_by_species[A] += accessory
+
+ if(body_accessory_by_species.len)
+ 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/list/pixel_offsets = list("x" = 0, "y" = 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_pixel_x(var/mob/living/carbon/human/H)
+ return pixel_offsets["x"]
+
+/datum/body_accessory/proc/get_pixel_y(var/mob/living/carbon/human/H)
+ return pixel_offsets["y"]
+
+
+//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_offsets = list("x" = -16, "y" = 0)
+
+//Tails
+/datum/body_accessory/tail
+ 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
\ No newline at end of file
diff --git a/code/modules/mob/living/carbon/human/human_defines.dm b/code/modules/mob/living/carbon/human/human_defines.dm
index bc10d54b1c8..38523536807 100644
--- a/code/modules/mob/living/carbon/human/human_defines.dm
+++ b/code/modules/mob/living/carbon/human/human_defines.dm
@@ -81,4 +81,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
\ No newline at end of file
diff --git a/code/modules/mob/living/carbon/human/update_icons.dm b/code/modules/mob/living/carbon/human/update_icons.dm
index 2e7a5e2be23..1fb591ba8a3 100644
--- a/code/modules/mob/living/carbon/human/update_icons.dm
+++ b/code/modules/mob/living/carbon/human/update_icons.dm
@@ -337,7 +337,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
@@ -756,12 +756,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)
@@ -896,10 +896,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.get_pixel_x(src), "pixel_y" = body_accessory.get_pixel_y(src))
+
+
+ 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)
@@ -913,7 +923,14 @@ 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)
+ if(body_accessory.animated_icon && body_accessory.animated_icon_state)
+ var/icon/accessory_s = new/icon("icon" = body_accessory.animated_icon, "icon_state" = body_accessory.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.get_pixel_x(src), "pixel_y" = body_accessory.get_pixel_y(src))
+
+ 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)
@@ -925,14 +942,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_64.dmi b/icons/mob/body_accessory_64.dmi
new file mode 100644
index 00000000000..4dfe1fc23a9
Binary files /dev/null and b/icons/mob/body_accessory_64.dmi differ
diff --git a/paradise.dme b/paradise.dme
index b17128a95b3..7e36a94c82d 100644
--- a/paradise.dme
+++ b/paradise.dme
@@ -1324,6 +1324,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"