diff --git a/code/__DEFINES/stats_and_skills.dm b/code/__DEFINES/stats_and_skills.dm
new file mode 100644
index 0000000000..3c336c0034
--- /dev/null
+++ b/code/__DEFINES/stats_and_skills.dm
@@ -0,0 +1,9 @@
+#define STAT_STR "Brawn"
+#define STAT_PER "Awareness"
+#define STAT_END "Toughness"
+#define STAT_CHA "Moxie"
+#define STAT_INT "Smarts"
+#define STAT_AGI "Deftness"
+#define STAT_LCK "Fate"
+#define STAT_GEN "Flat Roll"
+#define STAT_SUF "Crit"
diff --git a/code/_onclick/hud/screen_objects/character_actions.dm b/code/_onclick/hud/screen_objects/character_actions.dm
index e57721e5e8..d2cead1b12 100644
--- a/code/_onclick/hud/screen_objects/character_actions.dm
+++ b/code/_onclick/hud/screen_objects/character_actions.dm
@@ -291,13 +291,13 @@
/atom/movable/screen/roll_hud_button/Click(location,control,params,)
// This stuff needs to be changed because it was directly lifted from clothing
var/static/list/choices = list(
- "Brawn" = image(icon = 'icons/obj/stationary.dmi', icon_state = "fitnessweight-w"),
- "Awareness" = image(icon = 'icons/obj/status_display.dmi', icon_state = "ai_friend"),
- "Toughness" = image(icon = 'modular_coyote/icons/objects/weapons.dmi', icon_state = "imperial_kite"),
- "Moxie" = image(icon = 'icons/mob/screen_gen.dmi', icon_state = "mood9"),
- "Smarts" = image(icon = 'modular_roguetown/items/books.dmi', icon_state = "ledger0"),
- "Deftness" = image(icon = 'icons/obj/implants.dmi', icon_state = "warp"),
- "Fate" = image(icon = 'icons/obj/economy.dmi', icon_state = "coin_iron_flip"),
+ "Brawn" = image(icon = 'icons/mob/screen_gen.dmi', icon_state = "brawn"),
+ "Awareness" = image(icon = 'icons/mob/screen_gen.dmi', icon_state = "awareness"),
+ "Toughness" = image(icon = 'icons/mob/screen_gen.dmi', icon_state = "toughness"),
+ "Moxie" = image(icon = 'icons/mob/screen_gen.dmi', icon_state = "moxie"),
+ "Smarts" = image(icon = 'icons/mob/screen_gen.dmi', icon_state = "smarts"),
+ "Deftness" = image(icon = 'icons/mob/screen_gen.dmi', icon_state = "deftness"),
+ "Fate" = image(icon = 'icons/mob/screen_gen.dmi', icon_state = "fate"),
)
var/mob/user = usr
var/choice = show_radial_menu(user, src, choices, radius = 32,)
diff --git a/code/controllers/subsystem/stats_attributes_skills.dm b/code/controllers/subsystem/stats_attributes_skills.dm
new file mode 100644
index 0000000000..dda0b13e87
--- /dev/null
+++ b/code/controllers/subsystem/stats_attributes_skills.dm
@@ -0,0 +1,567 @@
+/*
+ * File: stats_n_skills.dm
+ * Author: Bingfox
+ *
+ */
+
+SUBSYSTEM_DEF(sas)
+ name = "SAS"
+ priority = FIRE_PRIORITY_MOBS
+ flags = SS_KEEP_TIMING | SS_NO_INIT
+ runlevels = RUNLEVEL_GAME | RUNLEVEL_POSTGAME
+
+ // master list of defaults
+ var/list/reference_stocks = list()
+ var/list/reference_stats = list()
+ var/list/reference_skills = list()
+ var/list/stat_invokers = list()
+ var/max_total_stats = 40
+ // everyone's thingy!
+ var/list/sheets = list()
+
+/datum/controller/subsystem/sas/Initialize(start_timeofday)
+ build_statskills()
+ build_stocks()
+ . = ..()
+
+/datum/controller/subsystem/sas/proc/build_statskills()
+ for(var/skul in subtypesof(/datum/statskill))
+ var/datum/statskill/S = new skul()
+ if(S.is_skill)
+ reference_skills[S.key] = S
+ if(S.is_stat)
+ reference_stats[S.key] = S
+
+/datum/controller/subsystem/sas/proc/build_stocks()
+ for(var/skul in subtypesof(/datum/stats_and_skills_holder))
+ var/datum/stats_and_skills_holder/S = new skul()
+ reference_stocks[S.key] = S
+
+/datum/controller/subsystem/sas/proc/give_holder_to_player(someone)
+ var/p_quid = SSeconomy.extract_quid(someone)
+ if(LAZYACCESS(sheets, p_quid))
+ return // already have their thing there!
+ var/mob/player = extract_mob(someone)
+ if(!player || player.sas_key == SAS_DEFAULT)
+ return
+ var/datum/stats_and_skills/SAS = new()
+ sheets["[p_quid]"] = SAS
+ SAS.set_player(p_quid)
+ SAS.load_stats()
+ player.sas_key = "[p_quid]"
+
+/datum/controller/subsystem/sas/proc/get_sas(someone)
+ var/mob/such = extract_mob(such)
+ if(!such)
+ return
+ var/datum/stats_and_skills/ses = LAZYACCESS(sheets, such.sas_key)
+ if(!ses) // invalid ses, give em a default (might be a player, that'll be fixed soon enuf)
+ such.sas_key = "default"
+ ses = LAZYACCESS(sheets, such.sas_key)
+ return ses
+
+/datum/controller/subsystem/sas/proc/get_stat(someone, which)
+ var/datum/stats_and_skills/ses = get_sas(someone)
+ return LAZYACCESS(ses.stats, which)
+
+/datum/controller/subsystem/sas/proc/do_roll(performer, stat, threshold, flags)
+ var/datum/stats_and_skills/doer_sass = get_sas(performer)
+ var/datum/roll_results/doer_roll = doer_sass.do_roll(stat, threshold, flags)
+ return doer_roll
+
+/datum/controller/subsystem/sas/proc/get_skills()
+ var/list/skullz = list()
+ for(var/skill in reference_skills)
+ var/datum/statskill/S = reference_skills[skill]
+ skullz += S.displayname
+
+/datum/controller/subsystem/sas/proc/get_stat_triggers()
+ var/list/skullz = list()
+ for(var/ivk in stat_invokers)
+ if(!islist(skullz[stat_invokers[ivk]]))
+ skullz[stat_invokers[ivk]] = list()
+ skullz[stat_invokers[ivk]] |= lowertext(ivk)
+ var/list/retmsg = list()
+ for(var/kind in skullz)
+ retmsg += "[kind]"
+ for(var/bingus in skullz[kind])
+ retmsg += "[bingus]"
+ return retmsg.Join("\n")
+
+/datum/controller/subsystem/sas/proc/get_num_dice(dicetring)
+ var/list/splode = splode_dice(dicetring)
+ if(splode["bad"])
+ return 1
+ return numberfy(LAZYACCESS(splode, "DICE"))
+
+/datum/controller/subsystem/sas/proc/get_crit_success(dicetring)
+ var/list/splode = splode_dice(dicetring)
+ if(splode["bad"])
+ return 20
+ return numberfy(LAZYACCESS(splode, "SIDES"))
+
+/datum/controller/subsystem/sas/proc/get_crit_fail(dicetring)
+ return 1
+
+/// Expects format: "NUMdNUM+NUM"
+/datum/controller/subsystem/sas/proc/splode_dice(dicetring)
+ dicetring = lowertext(dicetring)
+ var/list/split1 = splittext(dicetring, "d")
+ var/numdice = LAZYACCESS(split1, 1)
+ if(numdice > 2)
+ return list("bad" = TRUE)
+ replacetext(LAZYACCESS(split1, 2), "+", "BBB")
+ replacetext(LAZYACCESS(split1, 2), "-", "BBB")
+ var/list/split2 = splittext(LAZYACCESS(split1, 2), "BBB")
+ if(LAZYLEN(split2) > 2)
+ return list("bad" = TRUE)
+ var/numsides = LAZYACCESS(split2, 1)
+ var/mod = LAZYACCESS(split2, 2)
+ . = list()
+ .["DICE"] = numdice || 0
+ .["SIDES"] = numsides || 0,
+ .["MOD"] = mod || 0
+
+/datum/stats_and_skills
+ var/stock_key
+ var/owner_quid
+ var/virgin = TRUE
+ var/list/stats = list(
+ STAT_STR = 5,
+ STAT_PER = 5,
+ STAT_END = 5,
+ STAT_CHA = 5,
+ STAT_INT = 5,
+ STAT_AGI = 5,
+ STAT_LCK = 5,
+ )
+ var/list/skills = list()
+ var/max_total = 40
+
+/datum/stats_and_skills/New()
+ . = ..()
+ var/list/nustats = list()
+ // first go through the stats and get our defaults
+ for(var/stat in SSsas.reference_stats)
+ var/datum/statskill/S = SSsas.reference_stats[stat]
+ var/datum/statskill/ours = new S.type()
+ if(stats[stat])
+ ours.set_stat(stats[stat])
+ nustats[ours.key] = ours
+ stats = nustats()
+ // now the skills
+ var/list/nuskills = list()
+ // first go through the stats and get our defaults
+ for(var/skill in SSsas.reference_skills)
+ var/datum/statskill/S = SSsas.reference_skills[skill]
+ var/datum/statskill/ours = new S.type()
+ if(skills[skill])
+ ours.set_skill(skills[skill])
+ nuskills[ours.key] = ours
+ skills = nuskills()
+
+/datum/stats_and_skills/proc/load_from_prefs()
+ if(!virgin)
+ return
+ virgin = FALSE
+ var/mob/plyr = extract_mob(smth)
+ if(!plyr)
+ return
+ var/datum/preferences/P = extract_prefs(plyr)
+ if(!P)
+ return
+ for(var/stat in stats)
+ var/datum/statskill/S = stats[stat]
+ S.load_from_prefs(P)
+ set_player(plyr)
+ if(hax()) // only applies to stats loaded from prefs
+ return
+
+/datum/stats_and_skills/proc/hax()
+ var/total = 0
+ for(var/stat in stats)
+ var/datum/statskill/S = stats[stat]
+ total += S.current
+ if(total > SSsas.max_total_stats)
+ var/mob/master = get_owner()
+ if(master)
+ message_admins(span_alertalien("WEEOO WEEOO [master.name] ([master.ckey]) just spawned in with more than [max_total] stat points!!"))
+ master.gib()
+ return TRUE
+
+/datum/stats_and_skills/proc/set_player(mob/player)
+ owner_quid = SSeconomy.extract_quid(player)
+
+/datum/stats_and_skills/proc/do_roll(which, dice, threshold, flags = SAS_STAT | SAS_MODS)
+ if(!stats[which])
+ return new /datum/roll_results()
+ if(!dice)
+ dice = "1d20"
+ var/datum/statskill/S = stats[which]
+ return S.rollstat(dice, threshold, flags)
+
+/datum/statskill // issue
+ var/key
+ var/is_skill = FALSE
+ var/is_stat = FALSE
+ var/displayname = "Stat"
+ var/default = 5
+ var/maxval = 10 // only obeyed in the prefs menu!
+ var/minval = 1
+ var/current = 1
+ var/list/modifiers = list()
+ // hide from the prefs menu
+ var/hidden
+ /// rollmote stuff
+ var/list/rollmote_triggers = list()
+ var/list/rollmote_initial = list()
+ var/list/rollmote_success = list()
+ var/list/rollmote_success_crit = list(
+ "Ring-a-ding baby!",
+ "Wow!",
+ )
+ var/list/rollmote_fail = list()
+ var/list/rollmote_fail_crit = list(
+ "OOF, what a spectacular failure!",
+ "The game was rigged from the start.",
+ )
+
+/datum/statskill/New()
+ . = ..()
+ if(!SSsas.reference_stats[key])
+ for(var/inv in rollmote_triggers)
+ SSsas.stat_invokers[lowertext(inv)] = key
+ SSsas.stat_invokers[key] = key // hue
+ SSsas.stat_invokers[lowertext(key)] = key // hue
+ SSsas.stat_invokers[displayname] = key // hue
+ SSsas.stat_invokers[lowertext(displayname)] = key // hue
+
+/datum/statskill/proc/set_stat(neval)
+ current = neval
+
+/datum/statskill/proc/rollstat(dice, threshold, flags)
+ dice = formattify_dice(dice)
+ var/datum/roll_return/RT = new()
+ var/mod = get_mofidiers()
+ // var/numdice = SSsas.get_num_dice(dice)
+ var/critsucc = SSsas.get_crit_success(dice)
+ var/critfail = SSsas.get_crit_fail(dice)
+ var/porm = mod >= 0 ? "+" : "-"
+ var/randroll = roll("[dice][porm][mod]")
+ RT.resultnum = randroll
+ RT.base = current
+ RT.mods = mod
+ RT.displayname = displayname
+ RT.pre_msg = formattify(safepick(rollmote_initial))
+ if(!threshold || randroll >= threshold)
+ RT.passed = TRUE
+ RT.msg = formattify(safepick(rollmote_success))
+ else
+ RT.passed = FALSE
+ RT.msg = formattify(safepick(rollmote_fail))
+ if(randroll >= critsucc)
+ RT.crit_success = TRUE
+ RT.msg += " [formattify(safepick(rollmote_success_crit))]"
+ if(randroll <= critfail)
+ RT.crit_fail = TRUE
+ RT.msg += " [formattify(safepick(rollmote_success_fail))]"
+ return RT
+
+/datum/statskill/proc/formattify_dice(dice)
+ . = replacetext(dice, "$STATMODS", "[current + get_mods()]")
+ . = replacetext(dice, "$STAT", "[current]")
+
+/datum/statskill/proc/get_mods()
+ . = 0
+ for(var/mod in modifiers)
+ . += modifiers[mod]
+
+/datum/statskill/strength
+ key = STAT_STR
+ displayname = "Strength"
+ is_stat = TRUE
+ rollmote_triggers = list(
+ "b"
+ "brn"
+ "brawn"
+ )
+ rollmote_initial = list(
+ "%MOBNAME tests %MOBTHEIR brawn...",
+ "%MOBNAME flexes %MOBTHEIR arm(s)...",
+ "%MOBNAME prepares to lift...",
+ "%MOBNAME puts %MOBTHEIR back into it..."
+ )
+ rollmote_success = list(
+ "%MOBNAME is strong!",
+ "%MOBNAME is beefy!",
+ "%MOBNAME has some serious guns!",
+ "%MOBNAME had some strength behind it!"
+ )
+ rollmote_fail = list(
+ "%MOBNAME was too weak.",
+ "%MOBNAME was a little wet noodle.",
+ "%MOBNAME would loose an arm wrestling match with a mouse.",
+ "%MOBNAME has some serious atrophy. it's a wonder %MOBTHEY can move at all."
+ )
+
+/datum/statskill/perception
+ key = STAT_PER
+ displayname = "Perception"
+ is_stat = TRUE
+ rollmote_triggers = list(
+ "a",
+ "aware",
+ "awareness",
+ )
+ rollmote_initial = list(
+ "%MOBNAME takes a good, long look...",
+ "%MOBNAME squints...",
+ "%MOBNAME looks around...",
+ "%MOBNAME focuses in..."
+ )
+ rollmote_success = list(
+ "%MOBNAME was perceptive!",
+ "%MOBNAME noticed things!",
+ "%MOBNAME has eyes like a hawk!",
+ "%MOBNAME could find Doc Mitchell's keys!",
+ "%MOBNAME noticed whatever %MOBTHEY were trying to see!"
+ )
+ rollmote_fail = list(
+ "%MOBNAME was totally oblivious.",
+ "%MOBNAME forgot %MOBTHEIR glasses.",
+ "%MOBNAME didn't see anything."
+ )
+
+/datum/statskill/endurance
+ key = STAT_END
+ displayname = "Endurance"
+ is_stat = TRUE
+ rollmote_triggers = list(
+ "tough",
+ "tuff",
+ "end",
+ "toughness",
+ )
+ rollmote_initial = list(
+ "%MOBNAME tests %MOBTHEIR toughness...",
+ "%MOBNAME braces %MOBTHEMSELF..."
+ )
+ rollmote_success = list(
+ "%MOBNAME was tough!",
+ "%MOBNAME was one tough cookie!",
+ "%MOBNAME doesn't even flinch!",
+ "%MOBNAME is solid as an oak!",
+ "%MOBNAME endured!"
+ )
+ rollmote_fail = list(
+ "%MOBNAME is a floppy lil' noodle.",
+ "%MOBNAME is made of paper.",
+ "%MOBNAME would be torn to shreds by a light breeze.",
+ "%MOBNAME crumpled up and blew away."
+ )
+
+/datum/statskill/charisma
+ key = STAT_CHA
+ displayname = "Charisma"
+ is_stat = TRUE
+ rollmote_triggers = list(
+ "mox",
+ "moxie",
+ "cha",
+ )
+ rollmote_initial = list(
+ "%MOBNAME starts to be charismatic...",
+ "%MOBNAME puts on the charm..."
+ )
+ rollmote_success = list(
+ "%MOBNAME was charismatic!",
+ "%MOBNAME is an absolute charmer!",
+ "%MOBNAME was good and charming!"
+ )
+ rollmote_fail = list(
+ "%MOBNAME was totally uncharismatic.",
+ "%MOBNAME isn't fooling anyone.",
+ "%MOBNAME put %MOBTHEIR foot in %MOBTHEIR mouth.",
+ "%MOBNAME could hear a pin drop.",
+ "%MOBNAME's spaghetti is showing.",
+ "%MOBNAME had %MOBTHEIR charms fall flat."
+ )
+
+/datum/statskill/intelligence
+ key = STAT_INT
+ displayname = "Intelligence"
+ is_stat = TRUE
+ rollmote_triggers = list(
+ "int",
+ "i",
+ "intelligence",
+ "inteligence",
+ "intelligance",
+ "inteligance",
+ "intel",
+ "intell",
+ "intelect",
+ "intellect",
+ "smart",
+ "smartness",
+ "nerd",
+ "nerdiness",
+ "dork",
+ "dorkiness",
+ "dweeb",
+ "dweebishness",
+ "smarts",
+ )
+ rollmote_initial = list(
+ "%MOBNAME thinks hard...",
+ "%MOBNAME ponders hard...",
+ "%MOBNAME takes a moment to think...",
+ "%MOBNAME furrows %MOBTHEIR brow..."
+ )
+ rollmote_success = list(
+ "%MOBNAME was clever!",
+ "%MOBNAME is a genius!",
+ "%MOBNAME has a mind sharp as a whip!",
+ "%MOBNAME had a thought!"
+ )
+ rollmote_fail = list(
+ "%MOBNAME was dumb as a doornail.",
+ "%MOBNAME burned %MOBTHEIR last braincell years ago.",
+ "%MOBNAME is running low on braincells.",
+ "%MOBNAME is denser than a collapsed idiot."
+ )
+
+/datum/statskill/agility
+ key = STAT_AGI
+ displayname = "Agility"
+ is_stat = TRUE
+ rollmote_triggers = list(
+ "agi",
+ "a",
+ "agility",
+ "agillity",
+ "quick",
+ "quickness",
+ "fast",
+ "fastness",
+ "dex",
+ "speed",
+ "speediness",
+ "initiative",
+ "athleticism",
+ "acrobatics",
+ "escape",
+ "dodge",
+ "evade",
+ "evasion",
+ "cat",
+ "deft",
+ "deftness",
+ )
+ rollmote_initial = list(
+ "%MOBNAME tries to get agile...",
+ "%MOBNAME prepares %MOBTHEIR moves...",
+ "%MOBNAME starts to get limber..."
+ )
+ rollmote_success = list(
+ "%MOBNAME was very flexible!",
+ "%MOBNAME had some excellent footwork!",
+ "%MOBNAME was in perfect control!",
+ "%MOBNAME was agile as a cat!",
+ "%MOBNAME was agile as a fox!"
+ )
+ rollmote_fail = list(
+ "%MOBNAME fell flat on %MOBTHEIR face.",
+ "%MOBNAME fell flat on %MOBTHEIR back.",
+ "%MOBNAME triped over themself.",
+ "%MOBNAME has two left feet.",
+ "%MOBNAME was clumsy as a cat.",
+ "%MOBNAME was clumsy as a fox."
+ )
+
+/datum/statskill/luck
+ key = STAT_LCK
+ displayname = "Luck"
+ is_stat = TRUE
+ rollmote_triggers = list(
+ "l",
+ "lck",
+ "luck",
+ "lick",
+ "lock",
+ "lunk",
+ "link",
+ "chance",
+ "fortune",
+ "dice",
+ "luk",
+ "luc",
+ "fat",
+ "fate",
+ )
+ rollmote_initial = list(
+ "%MOBNAME tries %MOBTHEIR luck...",
+ "%MOBNAME takes a chance...",
+ "%MOBNAME puts it all on red..."
+ )
+ rollmote_success = list(
+ "%MOBNAME lucked out!",
+ "%MOBNAME was the luckiest son-of-a-gun in the wasteland!",
+ "%MOBNAME could make a bullet turn right around and climb back into the gun!",
+ "%MOBNAME got lucky!"
+ )
+ rollmote_fail = list(
+ "%MOBNAME was unlucky.",
+ "%MOBNAME realized %MOBTHEIR game was rigged from the start.",
+ "%MOBNAME showed that the house always wins."
+ )
+
+/datum/statskill/basic
+ key = STAT_GEN
+ displayname = "Coinflip"
+ is_stat = TRUE
+ hidden = TRUE
+ rollmote_triggers = list(
+ "x",
+ "non",
+ "none",
+ "generic",
+ "something",
+ "else",
+ "smth",
+ "?",
+ "rand",
+ "huh",
+ "stuff",
+ "roll",
+ )
+ rollmote_initial = list(
+ "%MOBNAME tests %MOBTHEIR skills...",
+ "%MOBNAME tries %MOBTHEIR skills...",
+ "%MOBNAME attempts to do a thing...",
+ "%MOBNAME puts %MOBTHEIR skills to the test..."
+ )
+ rollmote_success = list(
+ "%MOBNAME succeeded!",
+ "%MOBNAME did it!"
+ )
+ rollmote_fail = list(
+ "%MOBNAME was really bad at whatever they did.",
+ "%MOBNAME just really sucked.",
+ "%MOBNAME messed up real bad."
+ )
+
+/datum/roll_return
+ var/base = 0
+ var/crit_fail = FALSE
+ var/crit_success = FALSE
+ var/passed = FALSE
+ var/displayname = "Result"
+ var/mods = 0
+ var/msg = "Something cool happened!"
+ var/resultnum = 0
+
+/////////////////////////////////////////////
+/////////////////////////////////////////////
+/// our system of skills, attributes, and stats!
+/mob/var/sas_key
diff --git a/code/modules/mob/living/living.dm b/code/modules/mob/living/living.dm
index e84eb98fe8..aa342ee004 100644
--- a/code/modules/mob/living/living.dm
+++ b/code/modules/mob/living/living.dm
@@ -18,8 +18,10 @@
for(var/datum/atom_hud/data/diagnostic/diag_hud in GLOB.huds)
diag_hud.add_to_hud(src)
faction += "[REF(src)]"
+ init_SAS()
GLOB.mob_living_list += src
clienthud.add_hud_to(src)
+
if(coolshadow)
add_filter("cool_shadow",10, list(
"type"="drop_shadow",
@@ -67,6 +69,7 @@
ranged_ability.remove_ranged_ability(src)
if(buckled)
buckled.unbuckle_mob(src,force=1)
+ QDEL_NULL(SAS)
remove_from_all_data_huds()
GLOB.mob_living_list -= src
diff --git a/code/modules/mob/living/living_defines.dm b/code/modules/mob/living/living_defines.dm
index 49b2c6e674..876528130e 100644
--- a/code/modules/mob/living/living_defines.dm
+++ b/code/modules/mob/living/living_defines.dm
@@ -179,3 +179,4 @@
var/icon_resting
var/coolshadow = TRUE
+
diff --git a/code/modules/mob/living/login.dm b/code/modules/mob/living/login.dm
index 3c7d8945fd..e1668395d9 100644
--- a/code/modules/mob/living/login.dm
+++ b/code/modules/mob/living/login.dm
@@ -31,3 +31,4 @@
hud_client_check()
SSstatpanels.collect_horny_demographic(src)
+ SSsas.
diff --git a/code/modules/mob/living/skillcheck.dm b/code/modules/mob/living/skillcheck.dm
index fe0fdd95ff..a93d09605d 100644
--- a/code/modules/mob/living/skillcheck.dm
+++ b/code/modules/mob/living/skillcheck.dm
@@ -1,290 +1,51 @@
-#define EMOTE_SPECIAL_STR "Brawn"
-#define EMOTE_SPECIAL_PER "Awareness"
-#define EMOTE_SPECIAL_END "Toughness"
-#define EMOTE_SPECIAL_CHA "Moxie"
-#define EMOTE_SPECIAL_INT "Smarts"
-#define EMOTE_SPECIAL_AGI "Deftness"
-#define EMOTE_SPECIAL_LCK "Fate"
-#define EMOTE_SPECIAL_GEN "Flat Roll"
-#define EMOTE_SPECIAL_SUF "Crit"
GLOBAL_LIST_INIT(special_skill_list, list(
- EMOTE_SPECIAL_STR,
- EMOTE_SPECIAL_PER,
- EMOTE_SPECIAL_END,
- EMOTE_SPECIAL_CHA,
- EMOTE_SPECIAL_INT,
- EMOTE_SPECIAL_AGI,
- EMOTE_SPECIAL_LCK,
- EMOTE_SPECIAL_GEN))
-
-GLOBAL_LIST_INIT(special_triggers, list(
- EMOTE_SPECIAL_STR = list(
- "b",
- "brn",
- "brawn",
- ),
- EMOTE_SPECIAL_PER = list(
- "a",
- "aware",
- "awareness",
- ),
- EMOTE_SPECIAL_END = list(
- "tough",
- "tuff",
- "end",
- "toughness",
- ),
- EMOTE_SPECIAL_CHA = list(
- "mox",
- "moxie",
- "Cha",
- ),
- EMOTE_SPECIAL_INT = list(
- "int",
- "i",
- "intelligence",
- "inteligence",
- "intelligance",
- "inteligance",
- "intel",
- "intell",
- "intelect",
- "intellect",
- "smart",
- "smartness",
- "nerd",
- "nerdiness",
- "dork",
- "dorkiness",
- "dweeb",
- "dweebishness",
- "smarts",
- ),
- EMOTE_SPECIAL_AGI = list(
- "agi",
- "a",
- "agility",
- "agillity",
- "quick",
- "quickness",
- "fast",
- "fastness",
- "dex",
- "speed",
- "speediness",
- "initiative",
- "athleticism",
- "acrobatics",
- "escape",
- "dodge",
- "evade",
- "evasion",
- "cat",
- "deft",
- "deftness",
- ),
- EMOTE_SPECIAL_LCK = list(
- "l",
- "lck",
- "luck",
- "lick",
- "lock",
- "lunk",
- "link",
- "chance",
- "fortune",
- "dice",
- "luk",
- "luc",
- "fat",
- "fate",
- ),
- EMOTE_SPECIAL_GEN = list(
- "x",
- "non",
- "none",
- "generic",
- "something",
- "else",
- "smth",
- "?",
- "rand",
- "huh",
- "stuff",
- "roll")))
-
-GLOBAL_LIST_INIT(special_phrases, list(
- EMOTE_SPECIAL_STR = list(
- "initial" = list(
- "tests their brawn...",
- "flexes their arm(s)...",
- "prepares to lift...",
- "puts their back into it..."),
- "success" = list(
- "is strong!",
- "is beefy!",
- "has some serious guns!",
- "had some strength behind it!"),
- "failure" = list(
- "was too weak.",
- "was a little wet noodle.",
- "would loose an arm wrestling match with a mouse.",
- "has some serious atrophy. it's a wonder they can move at all.")),
- EMOTE_SPECIAL_PER = list(
- "initial" = list(
- "takes a good, long look...",
- "squints...",
- "looks around...",
- "focuses in..."),
- "success" = list(
- "was perceptive!",
- "noticed things!",
- "has eyes like a hawk!",
- "could find Doc Mitchell's keys!",
- "noticed whatever they were trying to see!"),
- "failure" = list(
- "was totally oblivious.",
- "forgot their glasses.",
- "didn't see anything.")),
- EMOTE_SPECIAL_END = list(
- "initial" = list(
- "tests their toughness...",
- "braces themself..."),
- "success" = list(
- "was tough!",
- "was one tough cookie!",
- "doesn't even flinch!",
- "is solid as an oak!",
- "endured!"),
- "failure" = list(
- "is a floppy lil' noodle.",
- "is made of paper.",
- "would be torn to shreds by a light breeze.",
- "crumpled up and blew away.")),
- EMOTE_SPECIAL_CHA = list(
- "initial" = list(
- "starts to be charismatic...",
- "puts on the charm..."),
- "success" = list(
- "was charismatic!",
- "is an absolute charmer!",
- "was good and charming!"),
- "failure" = list(
- "was totally uncharismatic.",
- "isn't fooling anyone.",
- "put their foot in their mouth.",
- "could hear a pin drop.",
- "miiiiight have some frontal lobe damage.",
- "had their charms fall flat.")),
- EMOTE_SPECIAL_INT = list(
- "initial" = list(
- "thinks hard...",
- "ponders hard...",
- "takes a moment to think...",
- "furrows their brow..."),
- "success" = list(
- "was clever!",
- "is a genius!",
- "has a mind sharp as a whip!",
- "had a thought!"),
- "failure" = list(
- "was dumb as a doornail.",
- "burned their last braincell years ago.",
- "is running low on braincells.",
- "was dense as a brick.")),
- EMOTE_SPECIAL_AGI = list(
- "initial" = list(
- "tries to get agile...",
- "prepares their moves...",
- "starts to get limber..."),
- "success" = list(
- "was very flexible!",
- "had some excellent footwork!",
- "was in perfect control!",
- "was agile as a cat!",
- "was agile as a fox!"),
- "failure" = list(
- "fell flat on their face.",
- "fell flat on their back.",
- "triped over themself.",
- "has two left feet.",
- "was clumsy as a cat.",
- "was clumsy as a fox.")),
- EMOTE_SPECIAL_LCK = list(
- "initial" = list(
- "tries their luck...",
- "takes a chance...",
- "puts it all on red..."),
- "success" = list(
- "lucked out!",
- "was the luckiest son-of-a-gun in the wasteland!",
- "could make a bullet turn right around and climb back into the gun!",
- "got lucky!"),
- "failure" = list(
- "was unlucky.",
- "realized their game was rigged from the start.",
- "showed that the house always wins.")),
- EMOTE_SPECIAL_GEN = list(
- "initial" = list(
- "tests their skills...",
- "tries their skills...",
- "attempts to do a thing...",
- "puts their skills to the test..."),
- "success" = list(
- "succeeded!",
- "did it!"),
- "failure" = list(
- "was really bad at whatever they did.",
- "just really sucked.",
- "messed up real bad.")),
- EMOTE_SPECIAL_SUF = list(
- "initial" = list(
- "shouldnt see this lol"),
- "success" = list(
- "Ring-a-ding baby!",
- "Wow!"),
- "failure" = list(
- "How could someone mess up so badly?",
- "The game was rigged from the start."))))
-
-/mob/living/verb/emote_special_str()
+ STAT_STR,
+ STAT_PER,
+ STAT_END,
+ STAT_CHA,
+ STAT_INT,
+ STAT_AGI,
+ STAT_LCK,
+ STAT_GEN))
+
+/mob/living/verb/STAT_str()
set name = "Roll Brawn"
set desc = "Roll for bicep circumfrence."
set category = "Roleplaying"
emote("special_strength")
-/mob/living/verb/emote_special_per()
+/mob/living/verb/STAT_per()
set name = "Roll Awareness"
set desc = "Roll for eyeball circumfrence."
set category = "Roleplaying"
emote("special_perception")
-/mob/living/verb/emote_special_end()
+/mob/living/verb/STAT_end()
set name = "Roll Toughness"
set desc = "Roll for heart circumfrence."
set category = "Roleplaying"
emote("special_endurance")
-/mob/living/verb/emote_special_cha()
+/mob/living/verb/STAT_cha()
set name = "Roll Moxie"
set desc = "Roll for beauty circumfrence."
set category = "Roleplaying"
emote("special_charisma")
-/mob/living/verb/emote_special_int()
+/mob/living/verb/STAT_int()
set name = "Roll Smarts"
set desc = "Roll for brain circumfrence."
set category = "Roleplaying"
emote("special_intelligence")
-/mob/living/verb/emote_special_agi()
+/mob/living/verb/STAT_agi()
set name = "Roll Deftness"
set desc = "Roll for wiggly circumfrence."
set category = "Roleplaying"
emote("special_agility")
-/mob/living/verb/emote_special_luc()
+/mob/living/verb/STAT_luc()
set name = "Roll Fate"
set desc = "Roll for some sort of circumfrence."
set category = "Roleplaying"
@@ -302,31 +63,31 @@ GLOBAL_LIST_INIT(special_phrases, list(
/datum/emote/living/special/s
key = "special_strength"
- special_override = EMOTE_SPECIAL_STR
+ special_override = STAT_STR
/datum/emote/living/special/p
key = "special_perception"
- special_override = EMOTE_SPECIAL_PER
+ special_override = STAT_PER
/datum/emote/living/special/e
key = "special_endurance"
- special_override = EMOTE_SPECIAL_END
+ special_override = STAT_END
/datum/emote/living/special/c
key = "special_charisma"
- special_override = EMOTE_SPECIAL_CHA
+ special_override = STAT_CHA
/datum/emote/living/special/i
key = "special_intelligence"
- special_override = EMOTE_SPECIAL_INT
+ special_override = STAT_INT
/datum/emote/living/special/a
key = "special_agility"
- special_override = EMOTE_SPECIAL_AGI
+ special_override = STAT_AGI
/datum/emote/living/special/l
key = "special_luck"
- special_override = EMOTE_SPECIAL_LCK
+ special_override = STAT_LCK
/datum/emote/living/special/run_emote(mob/user, params, type_override, intentional = FALSE)
if(!can_run_emote(user, TRUE, intentional))
@@ -338,54 +99,28 @@ GLOBAL_LIST_INIT(special_phrases, list(
to_chat(user, "You cannot send IC messages (muted).")
return FALSE
- if(isnull(user.special_a))
- to_chat(user, span_phobia("You arent special."))
- to_chat(user, span_notice("Mainly because you're playing a mob withough any special skills. This is probably a bug~"))
- return FALSE
-
- var/special_noun = null
var/special_phrase_input = special_override ? lowertext(special_override) : lowertext(params)
+ var/datum/statskill/SK = SSsas.get_stat_by_trigger(special_phrase_input)
- for(var/which_special in GLOB.special_skill_list)
- /// if the thing we said after the emote is in one of these lists, pick the corresponding key
- if(special_phrase_input in GLOB.special_triggers[which_special])
- special_noun = which_special
-
- if(!(special_noun in GLOB.special_skill_list) || !special_noun)
- to_chat(user, span_alert("That's not a valid SPECIAL stat!"))
+ if(!SK)
+ to_chat(user, span_alert("That's not a valid stat!"))
to_chat(user, span_notice("To use this emote, type '*special' followed by a SPECIAL stat. For instance, '*special luck' will do a (luck*10)% roll and say if you passed or not."))
- var/valid_specials
- for(var/word in GLOB.special_triggers)
- valid_specials += "[GLOB.special_triggers[word][1]], "
- valid_specials += "[GLOB.special_triggers[word][2]]. "
+ var/valid_specials = SSsas.get_stat_triggers()
to_chat(user, span_notice("Some of the valid SPECIAL keywords are:[valid_specials]."))
return
-
- var/special_skill = null
- switch(special_noun)
- if(EMOTE_SPECIAL_STR)
- special_skill = user.special_s
- if(EMOTE_SPECIAL_PER)
- special_skill = user.special_p
- if(EMOTE_SPECIAL_END)
- special_skill = user.special_e
- if(EMOTE_SPECIAL_CHA)
- special_skill = user.special_c
- if(EMOTE_SPECIAL_INT)
- special_skill = user.special_i
- if(EMOTE_SPECIAL_AGI)
- special_skill = user.special_a
- if(EMOTE_SPECIAL_LCK)
- special_skill = user.special_l
- if(EMOTE_SPECIAL_GEN) // generic random 50% chance
- special_skill = 5
-
- var/first_phrase = pick(GLOB.special_phrases[special_noun]["initial"])
- var/message_first = span_notice("\[[special_noun], [special_skill]0%] [user] [first_phrase].") // [Luck, 100%] User tests their Luck.
-
- user.visible_message(
+
+ var/init_message = SK.get_rollmote_initial_msg(user)
+ var/datum/roll_return/RT = SSsas.do_roll(user, SK.key, "1d20", 15)
+ var/modshow = ""
+ if(modshow > 0)
+ modshow = "+[RT.mods]"
+ if(modshow < 0)
+ modshow = "[RT.mods]"
+ var/message_first = span_notice("\[[SK.displayname], 1d20+[RT.base][modshow] DC 15] [user] [RT.pre_msg].") // [Luck, 100%] User tests their Luck.
+ var/turf/T = get_turf(user)
+
+ T.visible_message(
message = message_first,
- self_message = message_first,
blind_message = message_first)
user.emote_for_ghost_sight(message_first)
@@ -397,26 +132,20 @@ GLOBAL_LIST_INIT(special_phrases, list(
if(!can_run_emote(user, TRUE,intentional))
return
- var/message_second
- if(prob(special_skill * 10))
- var/success_phrase = pick(GLOB.special_phrases[special_noun]["success"])
- if(prob(5)) // crit success!
- success_phrase += " [pick(GLOB.special_phrases[EMOTE_SPECIAL_SUF]["success"])]"
- message_second = span_green("\[Success\] [user] [success_phrase]") // [Success] User is pretty lucky!
+ var/message_second = RT.msg
+ if(RT.passed)
+ message_second = span_green(message_second)
else
- var/fail_phrase = pick(GLOB.special_phrases[special_noun]["failure"])
- if(prob(5)) // crit fail!
- fail_phrase += " [pick(GLOB.special_phrases[EMOTE_SPECIAL_SUF]["failure"])]"
- message_second = span_red("\[Failure\] [user] [fail_phrase]") // [Failure} User isn't very lucky...
+ message_second = span_red(span_green(message_second))
- user.visible_message(
+ T.visible_message(
message = message_second,
- self_message = message_second,
blind_message = message_second)
user.emote_for_ghost_sight(message_second)
-
+////////////////////////////////////////////////////
+////////////////////////////////////////////////////
/datum/emote/living/rollfor
key = "rollfor"
@@ -438,47 +167,32 @@ GLOBAL_LIST_INIT(special_phrases, list(
var/paramlist = splittext(params, " ")
var/skill_name
- for(var/which_special in GLOB.special_skill_list)
- /// if the thing we said after the emote is in one of these lists, pick the corresponding key
- if(paramlist[1] in GLOB.special_triggers[which_special])
- skill_name = which_special
+ var/special_phrase_input = LAZYACCESS(paramlist, 1)
+ var/datum/statskill/SK = SSsas.get_stat_by_trigger(special_phrase_input)
- if(!(skill_name in GLOB.special_skill_list) || !skill_name)
- to_chat(user, span_alert("That's not a valid SPECIAL stat!"))
+ if(!SK)
+ to_chat(user, span_alert("That's not a valid stat!"))
to_chat(user, span_notice("To use this emote, type '*special' followed by a SPECIAL stat. For instance, '*special luck' will do a (luck*10)% roll and say if you passed or not."))
- var/valid_specials
- for(var/word in GLOB.special_triggers)
- valid_specials += "[GLOB.special_triggers[word][1]], "
- valid_specials += "[GLOB.special_triggers[word][2]]. "
- to_chat(user, span_notice("Some of the valid SPECIAL keywords are:[valid_specials]."))
+ var/valid_specials = SSsas.get_stat_triggers()
+ to_chat(user, span_notice("Some of the valid SPECIAL keywords are:\n[valid_specials]."))
return
- var/skill = null
- switch(skill_name)
- if(EMOTE_SPECIAL_STR)
- skill = user.special_s
- if(EMOTE_SPECIAL_PER)
- skill = user.special_p
- if(EMOTE_SPECIAL_END)
- skill = user.special_e
- if(EMOTE_SPECIAL_CHA)
- skill = user.special_c
- if(EMOTE_SPECIAL_INT)
- skill = user.special_i
- if(EMOTE_SPECIAL_AGI)
- skill = user.special_a
- if(EMOTE_SPECIAL_LCK)
- skill = user.special_l
- if(EMOTE_SPECIAL_GEN) // flat roll
- skill = 0
-
var/dice = paramlist[2]
- if(!roll(dice))
+ if(isnull(roll(dice)))
return
- var/first_phrase = pick(GLOB.special_phrases[skill_name]["initial"])
- var/message_first = span_notice("\[[skill_name], [dice]+[skill]\] [user] [first_phrase].") // [Luck, 1d20+5] Player tests their luck.
- user.visible_message(
+ var/init_message = SK.get_rollmote_initial_msg(user)
+ var/datum/roll_return/RT = SSsas.do_roll(user, SK.key, dice)
+ var/first_phrase = RT.msg_pre
+ var/modshow = ""
+ if(modshow > 0)
+ modshow = "+[RT.mods]"
+ if(modshow < 0)
+ modshow = "[RT.mods]"
+ var/turf/T = get_turf(user)
+
+ var/message_first = span_notice("\[[SK.displayname], [dice]+[RT.base][modshow]\] [user] [first_phrase].") // [Luck, 1d20+5] Player tests their luck.
+ T.visible_message(
message = message_first,
self_message = message_first,
blind_message = message_first)
@@ -492,17 +206,20 @@ GLOBAL_LIST_INIT(special_phrases, list(
if(!can_run_emote(user, TRUE,intentional))
return
- var/result_message = span_notice("[user] rolled [roll(dice)+skill]!")
- user.visible_message(
+ var/result_message = span_notice("[user] rolled [RT.resultnum]!")
+ T.visible_message(
message = result_message,
self_message = result_message,
blind_message = result_message)
user.emote_for_ghost_sight(result_message)
+
+//////////////////////////////////////////////////
+//////////////////////////////////////////////////
/mob/living/verb/suggest_skillcheck(mob/living/target)
set category = "Roleplaying"
set name = "Suggest Skillcheck"
- var/skill = input(usr, "Which skill?", "Skill Suggester 9000", null) as null|anything in GLOB.special_skill_list
+ var/skill = input(usr, "Which skill?", "Skill Suggester 9000", null) as null|anything in SSsas.get_skills()
if(isnull(skill))
return
to_chat(target, span_info("[usr.name] would like you to perform a [skill] check."))
@@ -511,13 +228,13 @@ GLOBAL_LIST_INIT(special_phrases, list(
set category = "Roleplaying"
set name = "Skill Contest"
// decide skill
- var/skill = input(usr, "Which skill?", "Skill Chooser 9001", null) as null|anything in GLOB.special_skill_list
+ var/skill = input(usr, "Which skill?", "Skill Chooser 9001", null) as null|anything in SSsas.get_skills()
if(isnull(skill))
return
// decide the contest
var/dice = input(usr, "What kind of dice? (ex: 1d20) \nIf nothing happens, you probably typed this wrong.", null) as null|text
- if(!roll(dice))
+ if(isnull(roll(dice)))
return
// get consent
var/consent = alert(target, "[usr.name] would like to compete in a [dice] [skill] contest.",, "Agree", "Deny")
@@ -525,51 +242,20 @@ GLOBAL_LIST_INIT(special_phrases, list(
to_chat(usr, span_info("[target.name] doesn't seem like they want to participate in a contest."))
return
- // run contest
- var/usr_skill = 0
- var/target_skill = 0
- switch(skill)
- if(EMOTE_SPECIAL_STR)
- usr_skill = usr.special_s
- target_skill = target.special_s
- if(EMOTE_SPECIAL_PER)
- usr_skill = usr.special_p
- target_skill = target.special_p
- if(EMOTE_SPECIAL_END)
- usr_skill = usr.special_e
- target_skill = target.special_e
- if(EMOTE_SPECIAL_CHA)
- usr_skill = usr.special_c
- target_skill = target.special_c
- if(EMOTE_SPECIAL_INT)
- usr_skill = usr.special_i
- target_skill = target.special_i
- if(EMOTE_SPECIAL_AGI)
- usr_skill = usr.special_a
- target_skill = target.special_a
- if(EMOTE_SPECIAL_LCK)
- usr_skill = usr.special_l
- target_skill = target.special_l
-
- var/usr_result = roll(dice) + usr_skill
- var/target_result = roll(dice) + target_skill
-
-
- var/winner
- var/winner_result
- var/loser
- var/loser_result
-
- if(usr_result > target_result)
+ var/datum/statskill/SK = SSsas.get_stat_by_trigger(skill)
+ var/datum/return_roll/my_roll = SSsas.do_roll(usr, SK.key, dice)
+ var/datum/return_roll/their_roll = SSsas.do_roll(target, SK.key, dice)
+
+ if(my_roll.resultnum > their_roll.resultnum)
winner = usr
- winner_result = usr_result
+ winner_result = my_roll.resultnum
loser = target
- loser_result = target_result
+ loser_result = their_roll.resultnum
else
winner = target
- winner_result = target_result
+ winner_result = their_roll.resultnum
loser = usr
- loser_result = usr_result
+ loser_result = my_roll.resultnum
to_chat(winner, span_green("You won the [skill] contest! ([winner_result] vs [loser_result])"))
to_chat(loser, span_alert("You lost the [skill] contest! ([loser_result] vs [winner_result])"))
diff --git a/code/modules/mob/stats_n_skills.dm b/code/modules/mob/stats_n_skills.dm
new file mode 100644
index 0000000000..8b13789179
--- /dev/null
+++ b/code/modules/mob/stats_n_skills.dm
@@ -0,0 +1 @@
+
diff --git a/icons/mob/screen_gen.dmi b/icons/mob/screen_gen.dmi
index 2fd373ed59..26a0fb8cd6 100644
Binary files a/icons/mob/screen_gen.dmi and b/icons/mob/screen_gen.dmi differ
diff --git a/modular_coyote/code/pmon_mob.dm b/modular_coyote/code/pmon_mob.dm
index ca5be691ee..d2f63a3abf 100644
--- a/modular_coyote/code/pmon_mob.dm
+++ b/modular_coyote/code/pmon_mob.dm
@@ -38,6 +38,7 @@
dextrous_hud_type = /datum/hud/dextrous/drone
//Need this to have the hands appear on the HUD
held_items = list(null, null)
+ sas_key = null // prevent sassery
///The pokemon-types that this mob has. Used to auto-generate moves(abilities) and some other attributes.
var/list/p_types = list()
///Moves that aren't automatically granted based on their type. Will be assigned during Initialize()