diff --git a/code/__DEFINES/quirk_defines.dm b/code/__DEFINES/quirk_defines.dm
new file mode 100644
index 00000000000..70e2a031c0d
--- /dev/null
+++ b/code/__DEFINES/quirk_defines.dm
@@ -0,0 +1,4 @@
+#define QUIRK_ORGANIC_INCOMPATIBLE (1<<1) // For quirks that organics shouldn't be able to take
+#define QUIRK_MACHINE_INCOMPATIBLE (1<<2) // For quirks that IPCs shouldn't be able to take
+#define QUIRK_SLIME_INCOMPATIBLE (1<<3)
+#define QUIRK_PLASMAMAN_INCOMPATIBLE (1<<4)
diff --git a/code/__DEFINES/vv.dm b/code/__DEFINES/vv.dm
index 5bbc06f9e49..d162890c010 100644
--- a/code/__DEFINES/vv.dm
+++ b/code/__DEFINES/vv.dm
@@ -77,6 +77,8 @@
#define VV_HK_ADDVERB "addverb"
#define VV_HK_REMVERB "remverb"
#define VV_HK_GIB "gib"
+#define VV_HK_ADDQUIRK "addquirk"
+#define VV_HK_REMQUIRK "remquirk"
#define GET_VV_TARGET locateUID(href_list[VV_HK_TARGET])
diff --git a/code/__HELPERS/global_lists.dm b/code/__HELPERS/global_lists.dm
index eb9594e6ab0..c47c775bf0b 100644
--- a/code/__HELPERS/global_lists.dm
+++ b/code/__HELPERS/global_lists.dm
@@ -116,6 +116,20 @@
GLOB.gear_datums[gear_type] = gear
+ for(var/quirk_path in subtypesof(/datum/quirk))
+ var/datum/quirk/quirk = quirk_path
+ if(!quirk || !quirk.name) // Filter out quirks without names so we don't show basetypes
+ continue
+ quirk = new quirk_path
+ var/list/data = list(
+ "name" = quirk.name,
+ "desc" = quirk.desc,
+ "cost" = quirk.cost,
+ "path" = quirk.type
+ )
+ GLOB.quirk_paths[quirk.name] = quirk.type // This will let us get the datum of a quirk with just the name later.
+ GLOB.quirk_tgui_info += list(data)
+
// Setup a list of robolimbs
GLOB.basic_robolimb = new()
for(var/limb_type in typesof(/datum/robolimb))
diff --git a/code/__HELPERS/mob_helpers.dm b/code/__HELPERS/mob_helpers.dm
index 9897ddedd94..df1abe1ef93 100644
--- a/code/__HELPERS/mob_helpers.dm
+++ b/code/__HELPERS/mob_helpers.dm
@@ -485,7 +485,7 @@
#define DOAFTERONCE_MAGIC "Magic~~"
GLOBAL_LIST_EMPTY(do_after_once_tracker)
-/proc/do_after_once(mob/user, delay, needhand = 1, atom/target = null, progress = 1, allow_moving, must_be_held, attempt_cancel_message = "Attempt cancelled.", special_identifier, hidden = FALSE, interaction_key = null, list/extra_checks = list())
+/proc/do_after_once(mob/user, delay, needhand = 1, atom/target = null, progress = 1, allow_moving, must_be_held, attempt_cancel_message = "Attempt cancelled.", special_identifier, hidden = FALSE, interaction_key = null, list/extra_checks = list(), allow_moving_target = FALSE)
if(!user || !target)
return
@@ -497,7 +497,7 @@ GLOBAL_LIST_EMPTY(do_after_once_tracker)
extra_checks += CALLBACK(GLOBAL_PROC, GLOBAL_PROC_REF(do_after_once_checks), cache_key, hidden)
GLOB.do_after_once_tracker[cache_key] = TRUE
- . = do_after(user, delay, needhand, target, progress, allow_moving, must_be_held, extra_checks, interaction_key = interaction_key)
+ . = do_after(user, delay, needhand, target, progress, allow_moving, must_be_held, extra_checks, interaction_key = interaction_key, allow_moving_target = allow_moving_target)
GLOB.do_after_once_tracker[cache_key] = FALSE
// Please don't use this unless you absolutely need to. Just have a direct call to do_after_once whenever possible.
diff --git a/code/__HELPERS/trait_helpers.dm b/code/__HELPERS/trait_helpers.dm
index cc56db5b1cc..0a5574357d7 100644
--- a/code/__HELPERS/trait_helpers.dm
+++ b/code/__HELPERS/trait_helpers.dm
@@ -208,6 +208,7 @@ Remember to update _globalvars/traits.dm if you're adding/removing/renaming trai
#define TRAIT_COLORBLIND "colorblind"
#define TRAIT_WINGDINGS "wingdings"
#define TRAIT_WATERBREATH "waterbreathing"
+#define TRAIT_ALCOHOL_TOLERANCE "alcohol_tolerance"
#define TRAIT_NOFAT "no_fatness"
#define TRAIT_NOGERMS "no_germs"
#define TRAIT_NODECAY "no_decay"
@@ -492,8 +493,6 @@ Remember to update _globalvars/traits.dm if you're adding/removing/renaming trai
#define MENTOR_OBSERVING "mobserving"
#define TIPPED_OVER "tipped_over"
-//quirk traits
-#define TRAIT_ALCOHOL_TOLERANCE "alcohol_tolerance"
//traits that should be properly converted to genetic mutations one day
#define TRAIT_LASEREYES "laser_eyes"
@@ -542,6 +541,20 @@ Remember to update _globalvars/traits.dm if you're adding/removing/renaming trai
#define STATION_TRAIT_MESSY "station_trait_messy"
#define STATION_TRAIT_TRIAI "station_trait_triai"
+/*********************
+* QUIRK TRAITS *
+**********************/
+#define TRAIT_FRAIL "frail"
+#define TRAIT_ASTHMATIC "asthma"
+#define TRAIT_SKITTISH "skittish"
+#define TRAIT_FREERUNNER "freerunner"
+#define TRAIT_GLUTTON "glutton"
+#define TRAIT_NO_APC_CHARGING "no_apc_charging"
+#define TRAIT_FOREIGNER "foreigner"
+#define TRAIT_HUNGRY "hungry"
+#define TRAIT_NO_WHISPERING "no_whisper"
+#define TRAIT_COOL "cool"
+
//***** TURF TRAITS *****//
/// Removes slowdown while walking on these tiles.
#define TRAIT_BLUESPACE_SPEED "bluespace_speed_trait"
diff --git a/code/datums/job_selection/job_candidate.dm b/code/datums/job_selection/job_candidate.dm
index bdd77a18366..813358b9bdd 100644
--- a/code/datums/job_selection/job_candidate.dm
+++ b/code/datums/job_selection/job_candidate.dm
@@ -153,6 +153,8 @@ RESTRICT_TYPE(/datum/job_candidate)
return FALSE
if(is_barred_by_missing_limbs(job))
return FALSE
+ if(is_barred_by_quirk(job))
+ return FALSE
return TRUE
@@ -177,6 +179,16 @@ RESTRICT_TYPE(/datum/job_candidate)
return TRUE
return FALSE
+/datum/job_candidate/proc/is_barred_by_quirk(datum/job/job)
+ if(!length(job.blacklisted_disabilities))
+ return FALSE
+ active_character.rebuild_quirks() // Gotta rebuild to make sure they aren't still JSON
+ var/list/active_character_quirks = active_character.quirks
+ for(var/datum/quirk/quirk in active_character_quirks)
+ if(quirk.blacklisted)
+ return TRUE
+ return FALSE
+
/datum/job_candidate/proc/get_job_exp_restrictions(datum/job/job)
if(is_job_playable(job))
return null
diff --git a/code/datums/job_selection/job_selector.dm b/code/datums/job_selection/job_selector.dm
index 100c192a54b..1342a2469eb 100644
--- a/code/datums/job_selection/job_selector.dm
+++ b/code/datums/job_selection/job_selector.dm
@@ -305,7 +305,7 @@ RESTRICT_TYPE(/datum/job_selector)
if(candidate.get_job_exp_restrictions(job))
young++
continue
- if(candidate.is_barred_by_disability(job) || candidate.is_barred_by_missing_limbs(job))
+ if(candidate.is_barred_by_disability(job) || candidate.is_barred_by_missing_limbs(job) || candidate.is_barred_by_quirk(job))
disabled++
continue
if(candidate.wants_job(job, 1))
diff --git a/code/datums/status_effects/debuffs.dm b/code/datums/status_effects/debuffs.dm
index 5cc0070658c..cab365d2e2d 100644
--- a/code/datums/status_effects/debuffs.dm
+++ b/code/datums/status_effects/debuffs.dm
@@ -444,7 +444,9 @@
var/actual_strength = strength
var/datum/mind/M = owner.mind
var/is_robot = ismachineperson(owner) || issilicon(owner)
-
+ if(ishuman(owner))
+ var/mob/living/carbon/human/drunkard = owner
+ actual_strength *= drunkard.physiology.alcohol_mod
if(HAS_TRAIT(owner, TRAIT_ALCOHOL_TOLERANCE))
alcohol_resistance = 2
diff --git a/code/defines/procs/announcer_datum.dm b/code/defines/procs/announcer_datum.dm
index 06436880ef7..de564377615 100644
--- a/code/defines/procs/announcer_datum.dm
+++ b/code/defines/procs/announcer_datum.dm
@@ -33,7 +33,8 @@ GLOBAL_DATUM_INIT(major_announcement, /datum/announcer, new(config_type = /datum
msg_sanitized = FALSE,
msg_language,
new_sound2 = null,
- new_subtitle = null
+ new_subtitle = null,
+ force_translation = FALSE // If this announcement should be displayed to everyone no matter if they know the language or not
)
if(!message)
@@ -49,7 +50,7 @@ GLOBAL_DATUM_INIT(major_announcement, /datum/announcer, new(config_type = /datum
var/datum/language/message_language = GLOB.all_languages[msg_language ? msg_language : language]
- var/list/combined_receivers = Get_Receivers(message_language)
+ var/list/combined_receivers = Get_Receivers(message_language, force_translation)
var/list/receivers = combined_receivers[1]
var/list/garbled_receivers = combined_receivers[2]
@@ -75,7 +76,7 @@ GLOBAL_DATUM_INIT(major_announcement, /datum/announcer, new(config_type = /datum
if(config.add_log)
Log(message, title)
-/datum/announcer/proc/Get_Receivers(datum/language/message_language)
+/datum/announcer/proc/Get_Receivers(datum/language/message_language, force_translation = FALSE)
var/list/receivers = list()
var/list/garbled_receivers = list()
@@ -84,6 +85,8 @@ GLOBAL_DATUM_INIT(major_announcement, /datum/announcer, new(config_type = /datum
if(!isnewplayer(M) && M.client)
receivers |= M
if(!M.say_understands(null, message_language))
+ if(force_translation && HAS_TRAIT(M, TRAIT_FOREIGNER))
+ continue
receivers -= M
garbled_receivers |= M
else
@@ -94,6 +97,8 @@ GLOBAL_DATUM_INIT(major_announcement, /datum/announcer, new(config_type = /datum
receivers -= M
continue
if(!M.say_understands(null, message_language))
+ if(force_translation && HAS_TRAIT(M, TRAIT_FOREIGNER))
+ continue
receivers -= M
garbled_receivers |= M
for(var/mob/M in GLOB.dead_mob_list)
diff --git a/code/game/gamemodes/wizard/raginmages.dm b/code/game/gamemodes/wizard/raginmages.dm
index 712f341be30..9e58f62848f 100644
--- a/code/game/gamemodes/wizard/raginmages.dm
+++ b/code/game/gamemodes/wizard/raginmages.dm
@@ -135,6 +135,7 @@
var/mob/living/carbon/human/new_character = new(pick(GLOB.latejoin))
G.client.prefs.active_character.copy_to(new_character)
new_character.key = G.key
+ new_character.clear_quirks()
return new_character
/datum/game_mode/wizard/raginmages/declare_completion()
diff --git a/code/game/gamemodes/wizard/wizard.dm b/code/game/gamemodes/wizard/wizard.dm
index 960be8e8375..176188daf03 100644
--- a/code/game/gamemodes/wizard/wizard.dm
+++ b/code/game/gamemodes/wizard/wizard.dm
@@ -33,6 +33,8 @@
for(var/datum/mind/wiz in pre_wizards)
wiz.add_antag_datum(/datum/antagonist/wizard)
wiz.current.forceMove(pick(GLOB.wizardstart))
+ var/mob/living/carbon/human/human_wizard = wiz.current // Honestly if the wizard isn't a human then someone else has gone horribly wrong
+ human_wizard.clear_quirks()
pre_wizards.Cut()
..()
diff --git a/code/game/jobs/job/job.dm b/code/game/jobs/job/job.dm
index b7853eedf2e..ac8ccf791a0 100644
--- a/code/game/jobs/job/job.dm
+++ b/code/game/jobs/job/job.dm
@@ -130,6 +130,13 @@
return TRUE
return FALSE
+/datum/job/proc/barred_by_quirk(client/C)
+ if(!C || !length(blacklisted_disabilities)) // If there's no disability locks, there won't be a quirk lock either.
+ return FALSE
+ for(var/datum/quirk/quirk in C.prefs.active_character.quirks)
+ if(quirk.blacklisted)
+ return TRUE
+ return FALSE
/// Returns true if the character has amputated limbs when their selected job doesn't allow it
/datum/job/proc/barred_by_missing_limbs(client/C)
if(!C || missing_limbs_allowed)
@@ -234,24 +241,31 @@
H.sec_hud_set_ID()
imprint_pda(H)
+ var/list/leftover_items = list()
+ for(var/datum/quirk/quirk as anything in H.quirks)
+ if(quirk.item_to_give)
+ var/obj/item/new_item = new quirk.item_to_give
+ leftover_items += new_item
- if(length(gear_leftovers))
- for(var/datum/gear/G in gear_leftovers)
- var/atom/placed_in = H.equip_or_collect(G.spawn_item(null, H.client.prefs.active_character.get_gear_metadata(G)))
- if(istype(placed_in))
- if(isturf(placed_in))
- to_chat(H, "Placing [G.display_name] on [placed_in]!")
- else
- to_chat(H, "Placing [G.display_name] in your [placed_in.name].")
- continue
- if(H.equip_to_appropriate_slot(G))
- to_chat(H, "Placing [G.display_name] in your inventory!")
- continue
- if(H.put_in_hands(G))
- to_chat(H, "Placing [G.display_name] in your hands!")
- continue
- to_chat(H, "Failed to locate a storage object on your mob, either you spawned with no hands free and no backpack or this is a bug.")
- qdel(G)
+ for(var/datum/gear/G in gear_leftovers)
+ leftover_items += G.spawn_item(null, H.client.prefs.active_character.get_gear_metadata(G))
+
+ for(var/obj/item/item in leftover_items)
+ var/atom/placed_in = H.equip_or_collect(item)
+ if(istype(placed_in))
+ if(isturf(placed_in))
+ to_chat(H, "Placing [item] on [placed_in]!")
+ else
+ to_chat(H, "Placing [item] in your [placed_in.name].")
+ continue
+ if(H.equip_to_appropriate_slot(item))
+ to_chat(H, "Placing [item] in your inventory!")
+ continue
+ if(H.put_in_hands(item))
+ to_chat(H, "Placing [item] in your hands!")
+ continue
+ to_chat(H, "Failed to locate a storage object on your mob, either you spawned with no hands free and no backpack or this is a bug.")
+ qdel(item)
gear_leftovers.Cut()
diff --git a/code/game/machinery/clonepod.dm b/code/game/machinery/clonepod.dm
index eb1f05d3fa2..d54710d21c4 100644
--- a/code/game/machinery/clonepod.dm
+++ b/code/game/machinery/clonepod.dm
@@ -353,7 +353,9 @@
organ.damage = desired_data.organs[candidate_for_insertion][1]
organ.status = desired_data.organs[candidate_for_insertion][2]
-
+ for(var/datum/quirk/quirk as anything in patient_data.quirks)
+ quirk.apply_quirk_effects(clone)
+
clone.updatehealth("droplimb")
clone.regenerate_icons()
diff --git a/code/game/machinery/clonescanner.dm b/code/game/machinery/clonescanner.dm
index db1d1b451cb..2875eb4d3b3 100644
--- a/code/game/machinery/clonescanner.dm
+++ b/code/game/machinery/clonescanner.dm
@@ -15,6 +15,8 @@
/// The patient's DNA
var/datum/dna/genetic_info
+ /// What quirks the patient has
+ var/list/quirks = list()
//The cloning scanner itself.
/obj/machinery/clonescanner
@@ -125,6 +127,7 @@
scan_result.name = scanned.dna.real_name
scan_result.mindUID = scanned.mind.UID()
scan_result.genetic_info = scanned.dna.Clone()
+ scan_result.quirks = scanned.quirks.Copy()
for(var/limb in scanned.dna.species.has_limbs)
if(scanned.bodyparts_by_name[limb])
diff --git a/code/game/objects/items/lighters.dm b/code/game/objects/items/lighters.dm
index c018857f244..ba6b4c1c0e2 100644
--- a/code/game/objects/items/lighters.dm
+++ b/code/game/objects/items/lighters.dm
@@ -58,7 +58,7 @@
/obj/item/lighter/proc/attempt_light(mob/living/user)
if(prob(75) || issilicon(user)) // Robots can never burn themselves trying to light it.
to_chat(user, "You light [src].")
- else if(HAS_TRAIT(user, TRAIT_BADASS))
+ else if(HAS_TRAIT(user, TRAIT_BADASS) || HAS_TRAIT(user, TRAIT_COOL))
to_chat(user, "[src]'s flames lick your hand as you light it, but you don't flinch.")
else
var/mob/living/carbon/human/H = user
diff --git a/code/game/objects/items/sport.dm b/code/game/objects/items/sport.dm
index 5d0dce17d29..140ca8f0929 100644
--- a/code/game/objects/items/sport.dm
+++ b/code/game/objects/items/sport.dm
@@ -86,7 +86,7 @@
/obj/structure/holohoop/hitby(atom/movable/AM, skipcatch, hitpush, blocked, datum/thrownthing/throwingdatum)
if(isitem(AM) && !isprojectile(AM))
var/atom/movable/thrower = throwingdatum?.get_thrower()
- if(prob(50) || HAS_TRAIT(thrower, TRAIT_BADASS))
+ if(prob(50) || HAS_TRAIT(thrower, TRAIT_BADASS) || HAS_TRAIT(thrower, TRAIT_COOL))
AM.forceMove(get_turf(src))
visible_message("Swish! [AM] lands in [src].")
else
diff --git a/code/game/objects/structures.dm b/code/game/objects/structures.dm
index f1189ccb50c..4ec63703ab3 100644
--- a/code/game/objects/structures.dm
+++ b/code/game/objects/structures.dm
@@ -83,14 +83,14 @@
if(!isturf(loc))
return FALSE
-
+ var/freerunner_multiplier = HAS_TRAIT(user, TRAIT_FREERUNNER) ? 0.8 : 1
if(HAS_MIND_TRAIT(user, TRAIT_TABLE_LEAP))
user.visible_message("[user] gets ready to vault up onto [src]!")
- if(!do_after(user, 0.5 SECONDS, target = src))
+ if(!do_after(user, 0.5 SECONDS * freerunner_multiplier, target = src))
return FALSE
else
user.visible_message("[user] starts climbing onto [src]!")
- if(!do_after(user, 5 SECONDS, target = src))
+ if(!do_after(user, 5 SECONDS * freerunner_multiplier, target = src))
return FALSE
if(!can_touch(user) || !climbable)
diff --git a/code/game/objects/structures/crates_lockers/closets.dm b/code/game/objects/structures/crates_lockers/closets.dm
index d116953b9b4..4f45de3d156 100644
--- a/code/game/objects/structures/crates_lockers/closets.dm
+++ b/code/game/objects/structures/crates_lockers/closets.dm
@@ -172,6 +172,26 @@
return TRUE
return (!density)
+/obj/structure/closet/Bumped(atom/movable/AM)
+ ..()
+ if(!iscarbon(AM))
+ return
+ var/mob/living/carbon/human/user = AM
+ if(!HAS_TRAIT(user, TRAIT_SKITTISH))
+ return
+ if(user.m_intent != MOVE_INTENT_RUN)
+ return
+ if(!COOLDOWN_FINISHED(user, skittish_cooldown))
+ to_chat(user, "Wait a few seconds to do that again.")
+ return
+ if(locked && !allowed(user))
+ return
+ locked = FALSE
+ if(opened || open())
+ user.forceMove(src)
+ close()
+ COOLDOWN_START(user, skittish_cooldown, 2 SECONDS)
+
/obj/structure/closet/proc/can_open()
if(welded)
return FALSE
diff --git a/code/game/objects/structures/crates_lockers/crates.dm b/code/game/objects/structures/crates_lockers/crates.dm
index 91d88325b26..8af09563ec9 100644
--- a/code/game/objects/structures/crates_lockers/crates.dm
+++ b/code/game/objects/structures/crates_lockers/crates.dm
@@ -47,7 +47,8 @@
if(L.electrocute_act(17, src))
do_sparks(5, 1, src)
return 2
-
+ if(climbable)
+ structure_shaken()
playsound(loc, open_sound, open_sound_volume, TRUE, -3)
for(var/obj/O in src) //Objects
O.forceMove(loc)
@@ -56,9 +57,6 @@
icon_state = icon_opened
opened = TRUE
- if(climbable)
- structure_shaken()
-
return TRUE
/obj/structure/closet/crate/close()
@@ -74,9 +72,9 @@
break
if(O.density || O.anchored || istype(O,/obj/structure/closet))
continue
- if(ismob(O) && !HAS_TRAIT(O, TRAIT_CONTORTED_BODY))
+ if(ismob(O) && !(HAS_TRAIT(O, TRAIT_CONTORTED_BODY) || HAS_TRAIT(O, TRAIT_SKITTISH)))
continue
- if(O.has_buckled_mobs()) // You can't put mobs into crates, so naturally if a mob is attached to something, it shouldn't be able to go in the crate
+ if(O.has_buckled_mobs()) // You can't put (most) mobs into crates, so naturally if a mob is attached to something, it shouldn't be able to go in the crate
continue
O.forceMove(src)
itemcount++
@@ -216,6 +214,9 @@
if(broken)
to_chat(user, "The crate appears to be broken.")
return FALSE
+ if(user.loc == src)
+ to_chat(user, "You can't reach the lock from inside.")
+ return FALSE
if(allowed(user))
locked = !locked
visible_message("The crate has been [locked ? null : "un"]locked by [user].")
diff --git a/code/modules/admin/verbs/randomverbs.dm b/code/modules/admin/verbs/randomverbs.dm
index d05bf13d590..fb5e356cb87 100644
--- a/code/modules/admin/verbs/randomverbs.dm
+++ b/code/modules/admin/verbs/randomverbs.dm
@@ -653,12 +653,15 @@ Traitors and the like can also be revived with the previous role mostly intact.
switch(alert("Should this be announced to the general population?", null,"Yes","No", "Cancel"))
if("Yes")
var/beepsound = input(usr, "What sound should the announcement make?", "Announcement Sound", "") as anything in MsgSound
-
+ var/should_translate = alert(usr, "Should it be auto-translated for all human mobs?", "Translation", "Yes", "No")
+ if(should_translate != "Yes")
+ should_translate = null // We can just do it like this since force_translation just needs any value to not be false/null
GLOB.major_announcement.Announce(
message,
new_title = type,
new_subtitle = subtitle,
- new_sound = MsgSound[beepsound]
+ new_sound = MsgSound[beepsound],
+ force_translation = should_translate
)
print_command_report(message, subtitle)
if("No")
diff --git a/code/modules/client/login_processing/20-load_characters.dm b/code/modules/client/login_processing/20-load_characters.dm
index 641f4c68260..754ad8a0ff5 100644
--- a/code/modules/client/login_processing/20-load_characters.dm
+++ b/code/modules/client/login_processing/20-load_characters.dm
@@ -64,7 +64,8 @@
height,
cyborg_brain_type,
body_type,
- pda_ringtone
+ pda_ringtone,
+ quirks
FROM characters WHERE ckey=:ckey"}, list(
"ckey" = C.ckey
))
diff --git a/code/modules/client/preference/character.dm b/code/modules/client/preference/character.dm
index c38f4e74130..26de22570f5 100644
--- a/code/modules/client/preference/character.dm
+++ b/code/modules/client/preference/character.dm
@@ -110,6 +110,8 @@
var/runechat_color = "#FFFFFF"
/// The ringtone their PDA should start with
var/pda_ringtone
+ /// A list/JSON of the quirk datums to attach to the character
+ var/list/quirks = list()
// Fuckery to prevent null characters
/datum/character_save/New()
@@ -131,6 +133,8 @@
playertitlelist = list2params(player_alt_titles)
if(length(loadout_gear))
gearlist = json_encode(loadout_gear)
+ if(islist(quirks))
+ quirks = json_encode(quirks)
var/datum/db_query/firstquery = SSdbcore.NewQuery("SELECT slot FROM characters WHERE ckey=:ckey ORDER BY slot", list(
"ckey" = C.ckey
@@ -221,7 +225,8 @@
runechat_color=:runechat_color,
cyborg_brain_type=:cyborg_brain_type,
body_type=:body_type,
- pda_ringtone=:pda_ringtone
+ pda_ringtone=:pda_ringtone,
+ quirks=:quirks
WHERE ckey=:ckey
AND slot=:slot"}, list(
// OH GOD SO MANY PARAMETERS
@@ -286,7 +291,8 @@
"cyborg_brain_type" = cyborg_brain_type,
"pda_ringtone" = pda_ringtone,
"ckey" = C.ckey,
- "slot" = slot_number
+ "slot" = slot_number,
+ "quirks" = quirks
))
if(!query.warn_execute())
@@ -325,7 +331,7 @@
player_alt_titles,
disabilities, organ_data, rlimb_data, nanotrasen_relation, physique, height, speciesprefs,
socks, body_accessory, gear, autohiss,
- hair_gradient, hair_gradient_offset, hair_gradient_colour, hair_gradient_alpha, custom_emotes, runechat_color, cyborg_brain_type, body_type, pda_ringtone)
+ hair_gradient, hair_gradient_offset, hair_gradient_colour, hair_gradient_alpha, custom_emotes, runechat_color, cyborg_brain_type, body_type, pda_ringtone, quirks)
VALUES
(:ckey, :slot, :metadata, :name, :be_random_name, :gender,
:age, :species, :language,
@@ -352,7 +358,7 @@
:playertitlelist,
:disabilities, :organ_list, :rlimb_list, :nanotrasen_relation, :physique, :height, :speciesprefs,
:socks, :body_accessory, :gearlist, :autohiss_mode,
- :h_grad_style, :h_grad_offset, :h_grad_colour, :h_grad_alpha, :custom_emotes, :runechat_color, :cyborg_brain_type, :body_type, :pda_ringtone)
+ :h_grad_style, :h_grad_offset, :h_grad_colour, :h_grad_alpha, :custom_emotes, :runechat_color, :cyborg_brain_type, :body_type, :pda_ringtone, :quirks)
"}, list(
// This has too many params for anyone to look at this without going insae
"ckey" = C.ckey,
@@ -416,7 +422,8 @@
"custom_emotes" = json_encode(custom_emotes),
"runechat_color" = runechat_color,
"cyborg_brain_type" = cyborg_brain_type,
- "pda_ringtone" = pda_ringtone
+ "pda_ringtone" = pda_ringtone,
+ "quirks" = quirks
))
if(!query.warn_execute())
@@ -512,6 +519,8 @@
body_type = query.item[60]
pda_ringtone = query.item[61]
+ quirks = query.item[62]
+
//Sanitize
var/datum/species/SP = GLOB.all_species[species]
if(!SP)
@@ -599,6 +608,7 @@
runechat_color = sanitize_hexcolor(runechat_color)
cyborg_brain_type = sanitize_inlist(cyborg_brain_type, GLOB.borg_brain_choices, initial(cyborg_brain_type))
pda_ringtone = sanitize_inlist(pda_ringtone, GLOB.pda_ringtone_choices, initial(pda_ringtone))
+ quirks = sanitize_json(quirks)
if(!player_alt_titles)
player_alt_titles = new()
if(!organ_data)
@@ -1885,6 +1895,11 @@
character.set_species(S.type, delay_icon_update = TRUE) // Yell at me if this causes everything to melt
if(be_random_name)
real_name = random_name(gender, species)
+ var/balance_check = rebuild_quirks()
+ for(var/datum/quirk/to_add in quirks)
+ to_add.apply_quirk_effects(character)
+ if(balance_check > 0)
+ log_debug("[src] spawned in with more quirks than they should have been able to. Quirk balance of [balance_check].")
character.add_language(language)
character.real_name = real_name
@@ -2135,6 +2150,9 @@
if(job.barred_by_disability(user.client))
html += "[rank]