Ports a character customization option: Quirks (#28388)

* Makes the datum

* Initial Commit

* Gets TGUI menu not crashing

* Autodoc

* quirk menu bugged

* oh.

* Working menu + buttons

* trait defintes pt 1/?

* Moves traits around

* ACTUALLY START ON THE QUIRKS THEMSELVES

* TGUI Bundle Rebuild

* TGUI properly saves now

* Removes allergies for scope reasons, make foreigner work properly

* TGUI tweaks

* Reworks TGUI Menu

* quirks store the mob, not mind

* Adds asthma and mute

* minor tweaks

* Cloning re-adds quirks

* Adds functionality to frail

* Adds crafty

* TGUI fix

* Fixes a frail bug

* Makes sure the list is in the correct format

* TGUI size change

* Makes skittish work

* Adds glutton, removes unused traits

* Changes description and adds function to glutton

* Freerunner functioning

* Crafty buff

* Clear quirk list when changing species

* Forgot to remove light step

* Delete

* Refactor item-giving

* TGUI Bundle Rebuild

* whoops

* Apply quirks when changing species

* Removes some unneeded defines/paths

* Replaces improved charging cord with a bad charger

* coment

* Guard clause

* Changes how quirks add and remove organs

* unused var

* adding organs works, removing them doesn't yet

* oh that fixed it

* Hopefully finally once and for all fix the json issues

* Buffs asthma

* switches the processor to ssobj since it's slower

* actually spawns the items whoops

* maybe actually fixes json issues I really hope this doesn't come up again?

* moves sql to the right directory

* CULPRIT FOUND, JSON ISSUES ACTUALLY SOLVED

* adds admin tooling for quirks

* Ups the cost of lungs

* Adds removal for the drinking ones

* Fixes some duplicate code

* Removing foreigner works

* wops

* bumps sql version to 65

* prettier

* semicolon

* Foreigners can understand galcomm but not speak it, as per headmin request

* Different solution was agreed upon

* CC announcements now get auto-translated

* Apply suggestions from code review

Lewc quick suggestions

Co-authored-by: Luc <89928798+lewcc@users.noreply.github.com>
Signed-off-by: BiancaWilkson <42818125+BiancaWilkson@users.noreply.github.com>

* Move alcohol tolerance basetype

* accomodates for reset_styles

* Swap to species flags, and being dead is a universal process stopper

* adds a foreigner trait

* Xenos and the like can't understand CC's translated announcements

* TGUI Bundle Rebuild

* Lewc quick fixes

Co-authored-by: Luc <89928798+lewcc@users.noreply.github.com>
Signed-off-by: BiancaWilkson <42818125+BiancaWilkson@users.noreply.github.com>

* i forgor

* i forgor pt 2

* process properly returns true

* TGUI Bundle Rebuild

* sql update for fixing merge conflicts

* bundle changes

* actual sql fix

* Not test tiny in prod

* sql fix?

* tgui update, first pass

* Adds schema changes to main quirk branch

* persistent data works now

* TGUI Bundle Rebuild

* Fixes conflict with job assignment datumization

* TGUI Bundle Rebuild

* Adds pacifism by request, -3 points

* gives skittish a small cd

* Makes the lunch randomizer  I was thinking about doing

* GC returns a qdel hint now

* Removes hard refs

* SQL save fix

* Fixes admin tools

* Fixes wizards getting quirked up

* ragin too

* Lightweight is more lightweight

* Adds admin logging for when someone makes a languagesless character

* TGUI Bundle Rebuild

* Adds colorblind, hungry, and buffs asthma

* fixed vv post-merge conflict

* behold, my monster

* rebuild and merge master

* Adds more positive quirks, and makes monochrome give more points

* adds nearsighted, and no whispering

* sorry linter

* Adds the cool quirk, thanks to pumpkin312

* Oh dear that signal gets sent to everyone every time someone spawns in

* i forgot that slimes dont have eyes

* tgui fix

* bundle rebuild probably

* prettier

* prettier

* Skittish package fix

* force add bundle

* Makes plasmemes be unable to take foreigner

* Makes advanced lungs respect the owner's species

* makes breaking open wrapped crates/lockers a do_after_once

* Makes plasmeme/vox advanced lungs actually help

* You get glasses anyway whoops

* bundle rebuild

---------

Signed-off-by: BiancaWilkson <42818125+BiancaWilkson@users.noreply.github.com>
Co-authored-by: Burzah <116982774+Burzah@users.noreply.github.com>
Co-authored-by: Luc <89928798+lewcc@users.noreply.github.com>
Co-authored-by: warriorstar-orion <orion@snowfrost.garden>
This commit is contained in:
BiancaWilkson
2025-11-03 19:18:52 -05:00
committed by GitHub
parent 99d57fd35d
commit d558a69583
49 changed files with 971 additions and 78 deletions
+41 -5
View File
@@ -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 += "<del class='dark'>[rank]</del></td><td class='bad'><b> \[DISABILITY\]</b></td></tr>"
continue
if(job.barred_by_quirk(user.client))
html += "<del class='dark'>[rank]</del></td><td class='bad'><b> \[QUIRK\]</b></td></tr>"
continue
if(job.barred_by_missing_limbs(user.client))
html += "<del class='dark'>[rank]</del></td><td class='bad'><b> \[MISSING LIMBS\]</b></td></tr>"
continue
@@ -2274,3 +2292,21 @@
custom_emotes[custom_emote.name] = emote_text
return custom_emotes
/*
* This serves two purposes. It tallies up and returns the total quirk balance of the current loadout
* It also rebuilds then entire list of quirks, converting the JSON format it could be into a usable datum.
*/
/datum/character_save/proc/rebuild_quirks()
var/point_total = 0
if(!islist(quirks)) // If it's not a normal list then it has to be JSON. Or something went horribly wrong.
quirks = json_decode(quirks)
var/list/quirk_cache = quirks.Copy()
quirks.Cut()
for(var/quirk_name in quirk_cache)
var/datum/quirk/chosen_quirk = GLOB.quirk_paths["[quirk_name]"]
var/datum/quirk/quirk = new chosen_quirk.type // Don't want hard refs to the global list
if(!quirk)
continue
point_total += quirk.cost
quirks += quirk
return point_total
@@ -199,6 +199,7 @@
to_chat(user, "<span class='warning'>Invalid species, please pick something else.</span>")
return
if(prev_species != active_character.species)
active_character.quirks = list() //Reset their quirks
active_character.age = clamp(active_character.age, NS.min_age, NS.max_age)
var/datum/robolimb/robohead
if(NS.bodyflags & ALL_RPARTS)
@@ -682,6 +683,11 @@
loadout.ui_interact(user)
return FALSE
if("quirks")
var/datum/ui_module/quirk/quirk = new()
quirk.ui_interact(user)
return FALSE
if("nt_relation")
var/new_relation = tgui_input_list(user, "Choose your relation to NT. Note that this represents what others can find out about your character by researching your background, not what your character actually thinks.", "Character Preference", list("Loyal", "Supportive", "Neutral", "Skeptical", "Opposed"))
if(new_relation)
@@ -187,7 +187,7 @@ GLOBAL_LIST_INIT(special_role_times, list(
active_character.species = initial(active_character.species)
S = GLOB.all_species[active_character.species]
active_character.randomise()
active_character.rebuild_quirks()
dat += "<div class='statusDisplay' style='max-width: 128px; position: absolute; left: 150px; top: 150px'><img src=previewicon.png class='charPreview'><img src=previewicon2.png class='charPreview'></div>"
dat += "<table width='100%'><tr><td width='405px' height='25px' valign='top'>"
dat += "<b>Name: </b>"
@@ -397,6 +397,7 @@ GLOBAL_LIST_INIT(special_role_times, list(
dat += "<b>Socks:</b> <a href='byond://?_src_=prefs;preference=socks;task=input'>[active_character.socks]</a><BR>"
dat += "<b>Backpack Type:</b> <a href='byond://?_src_=prefs;preference=bag;task=input'>[active_character.backbag]</a><br><br>"
dat += "<a style='font-size: 1.5em;' href='byond://?_src_=prefs;preference=loadout;task=input'>Open Loadout</a><br>"
dat += "<a href='byond://?_src_=prefs;preference=quirks;task=input'>Open Quirk Menu</a><br>"
var/datum/species/myspecies = GLOB.all_species[active_character.species]
if(!isnull(myspecies))
@@ -0,0 +1,153 @@
/datum/quirk/alcohol_tolerance
var/alcohol_modifier = 1
/datum/quirk/alcohol_tolerance/apply_quirk_effects(mob/living/quirky)
..()
owner.physiology.alcohol_mod *= alcohol_modifier
/datum/quirk/alcohol_tolerance/remove_quirk_effects()
..()
owner.physiology.alcohol_mod /= alcohol_modifier
/datum/quirk/alcohol_tolerance/lightweight
name = "Lightweight"
desc = "You can't handle liquor very well, and get drunker quicker."
cost = -1
alcohol_modifier = 1.5
/datum/quirk/foreigner
name = "Foreigner"
desc = "You just recently joined the greater galactic community, and don't understand the common tongue yet. You cannot sign up for a command or security position."
cost = -2
item_to_give = /obj/item/taperecorder
blacklisted = TRUE
trait_to_apply = TRAIT_FOREIGNER
species_flags = QUIRK_PLASMAMAN_INCOMPATIBLE
/datum/quirk/foreigner/apply_quirk_effects(mob/living/quirky)
..()
owner.remove_language("Galactic Common")
if(!length(owner.languages))
log_admin("[owner] set up a character with no known languages.") // It's possible to do this but I have no idea how to prevent it without just giving them back galcom for free, so admins can ask them to not do that
return
owner.set_default_language(quirky.languages[1]) // set_default_language needs to be passed a direct reference to the user's language list
/datum/quirk/foreigner/remove_quirk_effects()
owner.add_language("Galactic Common")
..()
/datum/quirk/deaf
name = "Deafness"
desc = "You are incurably deaf, and cannot take a command or security position."
cost = -4
trait_to_apply = TRAIT_DEAF
blacklisted = TRUE
/datum/quirk/blind
name = "Blind"
desc = "You are incurably blind, and cannot take a command or security position."
cost = -4
trait_to_apply = TRAIT_BLIND
blacklisted = TRUE
item_to_give = /obj/item/clothing/glasses/sunglasses/blindfold
item_slot = ITEM_SLOT_EYES
/datum/quirk/mute
name = "Mute"
desc = "You are incurably mute, and cannot take a command or security position."
cost = -3
blacklisted = TRUE
trait_to_apply = TRAIT_MUTE
/datum/quirk/frail
name = "Frail"
desc = "You get major injuries much easier than most people."
cost = -3
trait_to_apply = TRAIT_FRAIL
#define ASTHMA_ATTACK_THRESHOLD 50
/datum/quirk/asthma
name = "Asthma"
desc = "You have trouble catching your breath, and can have violent coughing fits when exerting yourself. IPCs cannot take this."
cost = -3
species_flags = QUIRK_MACHINE_INCOMPATIBLE
trait_to_apply = TRAIT_ASTHMATIC
processes = TRUE
item_to_give = /obj/item/reagent_containers/pill/salbutamol // If an inhaler ever gets made put it here
/datum/quirk/asthma/process()
if(!..())
return
var/ease_of_breathing = owner.getOxyLoss() + owner.getStaminaLoss() / 2
if(ease_of_breathing < ASTHMA_ATTACK_THRESHOLD)
return
owner.emote("cough")
if(prob(ease_of_breathing / 4))
trigger_asthma_symptom(ease_of_breathing)
/* Causes an asthmatic flareup, which gets worse depending on how much oxygen and stamina damage the owner already has.
* If a bad attack isn't treated, it can easily feed into itself and kill the user.
*/
/datum/quirk/asthma/proc/trigger_asthma_symptom(current_severity)
owner.visible_message("<span class='notice'>[owner] violently coughs!</span>", "<span class='warning'>Your asthma flares up!</span>")
switch(current_severity)
if(50 to 75)
owner.adjustOxyLoss(5)
if(76 to 100)
owner.adjustOxyLoss(7)
if(101 to 150) // By now you're doubled over coughing
owner.adjustOxyLoss(5)
owner.AdjustLoseBreath(4 SECONDS)
owner.KnockDown(4 SECONDS)
if(151 to INFINITY)
owner.adjustOxyLoss(15)
#undef ASTHMA_ATTACK_THRESHOLD
/datum/quirk/no_apc_charging
name = "High Internal Resistance"
desc = "The station's outlets operate at a higher voltage than your chassis can handle, so you can only safely charge from recharging stations. Only IPCs can take this."
cost = -2
species_flags = QUIRK_ORGANIC_INCOMPATIBLE
trait_to_apply = TRAIT_NO_APC_CHARGING
organ_slot_to_remove = "r_arm_device" // This feels like such a dumb way to do this but I can't think of a smarter solution
/datum/quirk/pacifism
name = "Pacifist"
desc = "You can't bring yourself to hurt others, and cannot take a command or security position."
cost = -3
trait_to_apply = TRAIT_PACIFISM
/datum/quirk/hungry
name = "Hungry"
desc = "You get hungry faster."
cost = -1
/datum/quirk/hungry/apply_quirk_effects()
..()
owner.dna.species.hunger_drain += 0.03
/datum/quirk/hungry/remove_quirk_effects()
..()
owner.dna.species.hunger_drain += 0.03
/datum/quirk/colorblind
name = "Monochromacy"
desc = "You can't see any color."
cost = -2
trait_to_apply = TRAIT_COLORBLIND
species_flags = QUIRK_SLIME_INCOMPATIBLE
/datum/quirk/loudmouthed
name = "Loudmouthed"
desc = "You can't seem to lower your volume to a whisper."
cost = -1
trait_to_apply = TRAIT_NO_WHISPERING
/datum/quirk/nearsighted
name = "Nearsighted"
desc = "You can't see well without prescription glasses."
cost = -1
trait_to_apply = TRAIT_NEARSIGHT
species_flags = QUIRK_SLIME_INCOMPATIBLE
@@ -0,0 +1,136 @@
/datum/quirk/skittish
name = "Skittish"
desc = "You can hide yourself in crates by lying down, and WILL jump into a locker or crate if you bump into one while running, \
as long as you have access."
cost = 4
trait_to_apply = TRAIT_SKITTISH
/datum/quirk/freerunner
name = "Freerunner"
desc = "You're practiced in vaulting over things."
cost = 4
trait_to_apply = TRAIT_FREERUNNER
/datum/quirk/crafty
name = "Crafty"
desc = "You can craft things twice as fast."
cost = 2
trait_to_apply = TRAIT_CRAFTY
/datum/quirk/alcohol_tolerance/heavy_drinker
name = "Heavy Drinker"
desc = "You're used to the effects of alcohol, and get drunk slower than others."
cost = 1
alcohol_modifier = 0.7
/datum/quirk/meal_prepper
name = "Meal Prepper"
desc = "You thought ahead and pre-packed a meal for the day."
cost = 1
item_to_give = /obj/item/storage/box/papersack/prepped_meal
/datum/quirk/glutton
name = "Glutton"
desc = "You can eat faster, and don't suffer any ill-effects from being overweight. IPCs cannot take this."
cost = 2
trait_to_apply = TRAIT_GLUTTON
species_flags = QUIRK_MACHINE_INCOMPATIBLE
/obj/item/storage/box/papersack/prepped_meal
name = "packed meal"
var/list/entree_options = list(
/obj/item/food/sandwich,
/obj/item/food/toastedsandwich,
/obj/item/food/jellysandwich,
/obj/item/food/grilledcheese,
/obj/item/food/burger/cheese,
/obj/item/food/blt,
/obj/item/food/philly_cheesesteak,
/obj/item/food/sliced/hawaiian_pizza,
/obj/item/food/sliced/pepperoni_pizza,
/obj/item/food/meatkebab,
/obj/item/food/salmonsteak, // If anyone microwaves their leftover fish in the workplace it should be on sight
/obj/item/food/shrimp_skewer,
/obj/item/food/omelette
)
var/list/snack_options = list(
/obj/item/food/chips,
/obj/item/food/sosjerky,
/obj/item/food/pistachios,
/obj/item/food/no_raisin,
/obj/item/food/stroopwafel,
/obj/item/food/candy/toffee,
/obj/item/food/candy/chocolate_orange,
/obj/item/food/sliced/mothmallow,
/obj/item/food/sliced/apple_cake,
/obj/item/food/sliced/banarnarbread
)
var/list/drink_options = list(
/obj/item/reagent_containers/drinks/h_chocolate,
/obj/item/reagent_containers/drinks/tea,
/obj/item/reagent_containers/drinks/cans/cola,
/obj/item/reagent_containers/drinks/cans/space_mountain_wind,
/obj/item/reagent_containers/drinks/cans/dr_gibb,
/obj/item/reagent_containers/drinks/cans/space_up,
/obj/item/reagent_containers/drinks/cans/iced_tea,
/obj/item/reagent_containers/drinks/cans/starkist,
/obj/item/reagent_containers/drinks/bottle/beer // Don't tell your boss
)
/obj/item/storage/box/papersack/prepped_meal/populate_contents()
var/entree = pick(entree_options)
var/snack = pick(snack_options)
var/drink = pick(drink_options)
new entree (src)
new snack (src)
new drink (src)
/datum/quirk/upgraded_lungs
name = "Upgraded Cybernetic Lungs"
desc = "Your lungs have been replaced with upgraded cybernetics."
cost = 3
species_flags = QUIRK_MACHINE_INCOMPATIBLE
organ_to_give = /obj/item/organ/internal/lungs/cybernetic/upgraded
/datum/quirk/upgraded_lungs/give_organ(datum/source, datum/job/job, mob/living/spawned, client/player_client)
if(spawned != owner)
return
var/obj/item/organ/internal/lungs/cybernetic/new_lungs = new organ_to_give
if(isvox(owner))
new_lungs.configure_species("vox")
if(isplasmaman(owner))
new_lungs.configure_species("plasmamen")
INVOKE_ASYNC(new_lungs, TYPE_PROC_REF(/obj/item/organ/internal, insert), owner, TRUE)
/datum/quirk/culinary_implant
name = "IPC Culinary Implant"
desc = "Either you or your creator wanted you to seem more organic, and gave you an artificial mouth and stomach."
cost = 2
species_flags = QUIRK_ORGANIC_INCOMPATIBLE
organ_to_give = /obj/item/organ/internal/cyberimp/chest/ipc_food
/datum/quirk/home_cook
name = "Home Cook"
desc = "You have experience in the kitchen, and can examine kitchen machinery to see if the ingredients inside will cook into a proper meal. \
Chefs can already do this."
cost = 1
trait_to_apply = TRAIT_KNOWS_COOKING_RECIPES
/datum/quirk/pet_owner
name = "Animal Lover"
desc = "You brought one of your pets to work today! Make sure to name them with your collar."
cost = 1
item_to_give = /obj/item/petcollar
var/list/possible_pets = list(/mob/living/simple_animal/pet/dog/corgi, /mob/living/simple_animal/pet/cat, /mob/living/simple_animal/pet/dog/pug,
/mob/living/simple_animal/pet/dog/fox, /mob/living/basic/chick, /mob/living/basic/bunny, /mob/living/basic/turkey)
/datum/quirk/pet_owner/apply_quirk_effects()
mob_to_spawn = pick(possible_pets)
..()
/datum/quirk/cool
name = "Cool"
desc = "You can land every flip. You're so cool."
cost = 1
trait_to_apply = TRAIT_COOL
@@ -0,0 +1,134 @@
GLOBAL_LIST_EMPTY(quirk_paths)
/datum/quirk
/// Name of the quirk. It's important that the basetypes don't have a name, and that any quirks you want people to see to have one.
var/name
/// The (somewhat) IC explanation of what this quirk does, to be shown in the TGUI menu.
var/desc = "Uh oh sisters! No description!"
/// A positive or negative number, good quirks should be 1 to 4, bad quirks should be -1 to -4
var/cost = 0
/// The mob that this quirk gets applied to.
var/mob/living/carbon/human/owner
/// If IPCs and/or organic people can use it
var/species_flags
/// If having this bars you from rolling sec/command
var/blacklisted = FALSE
/// If this quirk needs to do something every life cycle
var/processes = FALSE
/// If this quirk applies a trait, what trait should be applied.
var/trait_to_apply
/// If this quirk lets the mob spawn with an item
var/item_to_give
/// If there's an item to give, what slot should it be equipped to roundstart?
var/item_slot = ITEM_SLOT_IN_BACKPACK
/// The path of the organ the quirk should give.
var/organ_to_give
/// What organ should be removed (if any). Must be the string name of the organ as found in the has_organ var from the species datum.
var/organ_slot_to_remove
/// If the quirk should spawn a mob with the player.
var/mob_to_spawn
/datum/quirk/Destroy(force, ...)
remove_quirk_effects()
owner.quirks.Remove(src)
owner = null
return ..()
/* For any quirk that processes, you'll want to have
*
* if(!..())
* return
* At the beginning to prevent it from firing on dead people
*/
/datum/quirk/process()
if(owner.stat == DEAD)
return FALSE
return TRUE
/*
* The proc for actually applying a quirk to a mob, most often during spawning.
*/
/datum/quirk/proc/apply_quirk_effects(mob/living/carbon/human/quirky)
SHOULD_CALL_PARENT(TRUE)
if(!istype(quirky))
log_debug("[src] did not find a mob to apply its effects to.")
return FALSE
owner = quirky
owner.quirks += src
if(processes)
START_PROCESSING(SSobj, src)
if(trait_to_apply)
ADD_TRAIT(owner, trait_to_apply, "quirk")
if(organ_slot_to_remove)
RegisterSignal(SSdcs, COMSIG_GLOB_JOB_AFTER_SPAWN, PROC_REF(remove_organ))
if(organ_to_give)
RegisterSignal(SSdcs, COMSIG_GLOB_JOB_AFTER_SPAWN, PROC_REF(give_organ))
if(mob_to_spawn)
RegisterSignal(SSdcs, COMSIG_GLOB_JOB_AFTER_SPAWN, PROC_REF(spawn_mob))
owner.update_sight()
/datum/quirk/proc/remove_organ(datum/source, datum/job/job, mob/living/spawned, client/player_client)
SIGNAL_HANDLER //COMSIG_GLOB_JOB_AFTER_SPAWN
if(spawned != owner)
return
var/obj/item/organ/to_remove = owner.get_organ_slot(organ_slot_to_remove)
INVOKE_ASYNC(to_remove, TYPE_PROC_REF(/obj/item/organ/internal, remove), owner, TRUE)
/datum/quirk/proc/give_organ(datum/source, datum/job/job, mob/living/spawned, client/player_client)
SIGNAL_HANDLER //COMSIG_GLOB_JOB_AFTER_SPAWN
if(spawned != owner)
return
var/obj/item/organ/internal/cybernetic = new organ_to_give
INVOKE_ASYNC(cybernetic, TYPE_PROC_REF(/obj/item/organ/internal, insert), owner, TRUE)
/datum/quirk/proc/spawn_mob(datum/source, datum/job/job, mob/living/spawned, client/player_client)
SIGNAL_HANDLER //COMSIG_GLOB_JOB_AFTER_SPAWN
if(spawned != owner)
return
new mob_to_spawn(owner.loc)
/// For any behavior that needs to happen before a quirk is destroyed
/datum/quirk/proc/remove_quirk_effects()
SHOULD_CALL_PARENT(TRUE)
if(trait_to_apply)
REMOVE_TRAIT(owner, trait_to_apply, "quirk")
if(processes)
STOP_PROCESSING(SSprocessing, src)
owner.update_sight()
/********************************************************************
* Mob Procs, mostly for many mob/new_player in the lobby screen *
********************************************************************/
/mob/proc/add_quirk_to_save(datum/quirk/to_add)
var/datum/character_save/active_character = src.client?.prefs?.active_character
if(!active_character)
return FALSE
if((to_add.species_flags & QUIRK_MACHINE_INCOMPATIBLE) && (active_character.species == "Machine"))
to_chat(src.client, "<span class='warning'>You can't put that quirk on a robotic character.</span>")
return FALSE
if((to_add.species_flags & QUIRK_ORGANIC_INCOMPATIBLE) && (active_character.species != "Machine"))
to_chat(src.client, "<span class='warning'>You can't put that quirk on an organic character.</span>")
return FALSE
if((to_add.species_flags & QUIRK_SLIME_INCOMPATIBLE) && (active_character.species == "Slime People")) //Since they don't have eyes
to_chat(src.client, "<span class='warning'>You can't put that quirk on a slime character, you have no eyes!</span>")
return FALSE
if((to_add.species_flags & QUIRK_PLASMAMAN_INCOMPATIBLE) && (active_character.species == "Plasmaman")) //If someone can figure out how to only let plasmaman with a secondary language take this feel free to do that
to_chat(src.client, "<span class='warning'>You can't put that quirk on a plasmaman, you have no species language!</span>")
return FALSE
active_character.quirks += to_add
return TRUE
/// Returns true if a quirk was removed, false otherwise
/mob/proc/remove_quirk_from_save(datum/quirk/to_remove)
var/datum/character_save/active_character = src.client?.prefs?.active_character
if(!active_character)
return FALSE
for(var/datum/quirk/quirk as anything in active_character.quirks)
if(quirk.name == to_remove.name)
active_character.quirks.Remove(quirk)
return TRUE
return FALSE
/mob/living/carbon/human/proc/clear_quirks()
for(var/datum/quirk/quirk in quirks)
qdel(quirk)
@@ -0,0 +1,47 @@
GLOBAL_LIST_EMPTY(quirk_tgui_info)
/datum/ui_module/quirk
name = "Quirks"
/datum/ui_module/quirk/ui_state(mob/user)
return GLOB.always_state
/datum/ui_module/quirk/ui_interact(mob/user, datum/tgui/ui)
ui = SStgui.try_update_ui(user, src, ui)
if(!ui)
ui = new(user, src, "QuirkMenu", name)
ui.set_autoupdate(FALSE)
ui.open()
/datum/ui_module/quirk/ui_data(mob/user)
var/datum/character_save/character = user?.client?.prefs.active_character
var/list/data = list("quirk_balance" = character.rebuild_quirks())
var/list/selected_quirks = list()
for(var/quirk in character?.quirks)
selected_quirks += quirk
data["selected_quirks"] = selected_quirks
return data
/datum/ui_module/quirk/ui_static_data(mob/user)
var/list/data = list(
"all_quirks" = GLOB.quirk_tgui_info
)
return data
/datum/ui_module/quirk/ui_act(action, list/params)
if(..())
return
. = TRUE
var/mob/user = usr
var/quirk_path = text2path(params["path"])
var/datum/quirk/quirk = new quirk_path
user.client.prefs.active_character.rebuild_quirks()
switch(action)
if("add_quirk")
user.add_quirk_to_save(quirk)
if("remove_quirk")
user.remove_quirk_from_save(quirk)