From 2cbb659eb9a40c34cc9be9aafddedad037008dcd Mon Sep 17 00:00:00 2001
From: Ghommie <42542238+Ghommie@users.noreply.github.com>
Date: Sun, 3 May 2020 06:15:40 +0200
Subject: [PATCH 01/62] Implementing the NO_PUBLIC_LATHE build_type flag.
---
code/game/machinery/autolathe.dm | 6 +--
.../autolathe_designs_construction.dm | 4 +-
...utolathe_designs_medical_and_dinnerware.dm | 4 +-
.../autolathe_designs_sec_and_hacked.dm | 38 +++++++++----------
.../autolathe_designs_tools.dm | 5 ++-
.../research/designs/autoylathe_designs.dm | 6 +++
6 files changed, 33 insertions(+), 30 deletions(-)
diff --git a/code/game/machinery/autolathe.dm b/code/game/machinery/autolathe.dm
index edb31e9dbd..91be9e2676 100644
--- a/code/game/machinery/autolathe.dm
+++ b/code/game/machinery/autolathe.dm
@@ -17,7 +17,6 @@
var/list/L = list()
var/list/LL = list()
var/hacked = FALSE
- var/hackable = TRUE
var/disabled = 0
var/shocked = FALSE
var/hack_wire
@@ -420,8 +419,6 @@
/obj/machinery/autolathe/proc/adjust_hacked(state)
hacked = state
- if(!hackable && hacked)
- return
for(var/id in SSresearch.techweb_designs)
var/datum/design/D = SSresearch.techweb_design_by_id(id)
if((D.build_type & AUTOLATHE) && ("hacked" in D.category))
@@ -436,8 +433,7 @@
/obj/machinery/autolathe/secure
name = "secured autolathe"
- desc = "An autolathe reprogrammed with security protocols to prevent hacking."
- hackable = FALSE
+ desc = "It produces items using metal and glass. This model was reprogrammed without some of the more hazardous designs."
circuit = /obj/item/circuitboard/machine/autolathe/secure
stored_research = /datum/techweb/specialized/autounlocking/autolathe/public
diff --git a/code/modules/research/designs/autolathe_desings/autolathe_designs_construction.dm b/code/modules/research/designs/autolathe_desings/autolathe_designs_construction.dm
index ee9ee14e4a..f8cf5c1d84 100644
--- a/code/modules/research/designs/autolathe_desings/autolathe_designs_construction.dm
+++ b/code/modules/research/designs/autolathe_desings/autolathe_designs_construction.dm
@@ -112,8 +112,8 @@
/datum/design/rcd_ammo_large
name = "Large Compressed Matter Cartridge"
id = "rcd_ammo_large"
- build_type = AUTOLATHE | PROTOLATHE
+ build_type = AUTOLATHE | PROTOLATHE | NO_PUBLIC_LATHE
materials = list(/datum/material/iron = 48000, /datum/material/glass = 32000)
build_path = /obj/item/rcd_ammo/large
- category = list("Tool Designs")
+ category = list("hacked", "Construction", "Tool Designs")
departmental_flags = DEPARTMENTAL_FLAG_ENGINEERING
diff --git a/code/modules/research/designs/autolathe_desings/autolathe_designs_medical_and_dinnerware.dm b/code/modules/research/designs/autolathe_desings/autolathe_designs_medical_and_dinnerware.dm
index b62f30e041..76ec6224b8 100644
--- a/code/modules/research/designs/autolathe_desings/autolathe_designs_medical_and_dinnerware.dm
+++ b/code/modules/research/designs/autolathe_desings/autolathe_designs_medical_and_dinnerware.dm
@@ -8,7 +8,7 @@
/datum/design/kitchen_knife
name = "Kitchen Knife"
id = "kitchen_knife"
- build_type = AUTOLATHE
+ build_type = AUTOLATHE | NO_PUBLIC_LATHE
materials = list(/datum/material/iron = 12000)
build_path = /obj/item/kitchen/knife
category = list("initial","Dinnerware")
@@ -140,7 +140,7 @@
/datum/design/healthanalyzer
name = "Health Analyzer"
id = "healthanalyzer"
- build_type = AUTOLATHE | PROTOLATHE
+ build_type = AUTOLATHE | PROTOLATHE | NO_PUBLIC_LATHE
materials = list(/datum/material/iron = 500, /datum/material/glass = 50)
build_path = /obj/item/healthanalyzer
category = list("initial", "Medical")
diff --git a/code/modules/research/designs/autolathe_desings/autolathe_designs_sec_and_hacked.dm b/code/modules/research/designs/autolathe_desings/autolathe_designs_sec_and_hacked.dm
index 173240e196..20a5370793 100644
--- a/code/modules/research/designs/autolathe_desings/autolathe_designs_sec_and_hacked.dm
+++ b/code/modules/research/designs/autolathe_desings/autolathe_designs_sec_and_hacked.dm
@@ -36,7 +36,7 @@
/datum/design/large_welding_tool
name = "Industrial Welding Tool"
id = "large_welding_tool"
- build_type = AUTOLATHE
+ build_type = AUTOLATHE | NO_PUBLIC_LATHE
materials = list(/datum/material/iron = 70, /datum/material/glass = 60)
build_path = /obj/item/weldingtool/largetank
category = list("hacked", "Tools")
@@ -44,7 +44,7 @@
/datum/design/flamethrower
name = "Flamethrower"
id = "flamethrower"
- build_type = AUTOLATHE
+ build_type = AUTOLATHE | NO_PUBLIC_LATHE
materials = list(/datum/material/iron = 500)
build_path = /obj/item/flamethrower/full
category = list("hacked", "Security")
@@ -52,7 +52,7 @@
/datum/design/rcd
name = "Rapid Construction Device (RCD)"
id = "rcd"
- build_type = AUTOLATHE
+ build_type = AUTOLATHE | NO_PUBLIC_LATHE
materials = list(/datum/material/iron = 30000)
build_path = /obj/item/construction/rcd
category = list("hacked", "Construction")
@@ -60,7 +60,7 @@
/datum/design/rpd
name = "Rapid Pipe Dispenser (RPD)"
id = "rpd"
- build_type = AUTOLATHE
+ build_type = AUTOLATHE | NO_PUBLIC_LATHE
materials = list(/datum/material/iron = 75000, /datum/material/glass = 37500)
build_path = /obj/item/pipe_dispenser
category = list("hacked", "Construction")
@@ -68,7 +68,7 @@
/datum/design/handcuffs
name = "Handcuffs"
id = "handcuffs"
- build_type = AUTOLATHE
+ build_type = AUTOLATHE | NO_PUBLIC_LATHE
materials = list(/datum/material/iron = 500)
build_path = /obj/item/restraints/handcuffs
category = list("hacked", "Security")
@@ -76,7 +76,7 @@
/datum/design/receiver
name = "Modular Receiver"
id = "receiver"
- build_type = AUTOLATHE
+ build_type = AUTOLATHE | NO_PUBLIC_LATHE
materials = list(/datum/material/iron = 15000)
build_path = /obj/item/weaponcrafting/receiver
category = list("hacked", "Security")
@@ -84,7 +84,7 @@
/datum/design/shotgun_slug
name = "Shotgun Slug"
id = "shotgun_slug"
- build_type = AUTOLATHE
+ build_type = AUTOLATHE | NO_PUBLIC_LATHE
materials = list(/datum/material/iron = 4000)
build_path = /obj/item/ammo_casing/shotgun
category = list("hacked", "Security")
@@ -92,7 +92,7 @@
/datum/design/buckshot_shell
name = "Buckshot Shell"
id = "buckshot_shell"
- build_type = AUTOLATHE
+ build_type = AUTOLATHE | NO_PUBLIC_LATHE
materials = list(/datum/material/iron = 4000)
build_path = /obj/item/ammo_casing/shotgun/buckshot
category = list("hacked", "Security")
@@ -100,7 +100,7 @@
/datum/design/shotgun_dart
name = "Shotgun Dart"
id = "shotgun_dart"
- build_type = AUTOLATHE
+ build_type = AUTOLATHE | NO_PUBLIC_LATHE
materials = list(/datum/material/iron = 4000)
build_path = /obj/item/ammo_casing/shotgun/dart
category = list("hacked", "Security")
@@ -108,7 +108,7 @@
/datum/design/incendiary_slug
name = "Incendiary Slug"
id = "incendiary_slug"
- build_type = AUTOLATHE
+ build_type = AUTOLATHE | NO_PUBLIC_LATHE
materials = list(/datum/material/iron = 4000)
build_path = /obj/item/ammo_casing/shotgun/incendiary
category = list("hacked", "Security")
@@ -116,7 +116,7 @@
/datum/design/riot_dart
name = "Foam Riot Dart"
id = "riot_dart"
- build_type = AUTOLATHE
+ build_type = AUTOLATHE | NO_PUBLIC_LATHE
materials = list(/datum/material/iron = 1125) //Discount for making individually - no box = less metal!
build_path = /obj/item/ammo_casing/caseless/foam_dart/riot
category = list("hacked", "Security")
@@ -124,7 +124,7 @@
/datum/design/riot_darts
name = "Foam Riot Dart Box"
id = "riot_darts"
- build_type = AUTOLATHE
+ build_type = AUTOLATHE | NO_PUBLIC_LATHE
materials = list(/datum/material/iron = 50000) //Comes with 40 darts
build_path = /obj/item/ammo_box/foambox/riot
category = list("hacked", "Security")
@@ -132,7 +132,7 @@
/datum/design/a357
name = "Revolver Bullet (.357)"
id = "a357"
- build_type = AUTOLATHE
+ build_type = AUTOLATHE | NO_PUBLIC_LATHE
materials = list(/datum/material/iron = 4000)
build_path = /obj/item/ammo_casing/a357
category = list("hacked", "Security")
@@ -140,7 +140,7 @@
/datum/design/a762
name = "Rifle Bullet (7.62mm)"
id = "a762"
- build_type = AUTOLATHE
+ build_type = AUTOLATHE | NO_PUBLIC_LATHE
materials = list(/datum/material/iron = 5000) //need seclathe for clips
build_path = /obj/item/ammo_casing/a762
category = list("hacked", "Security")
@@ -148,7 +148,7 @@
/datum/design/c10mm
name = "Ammo Box (10mm)"
id = "c10mm"
- build_type = AUTOLATHE
+ build_type = AUTOLATHE | NO_PUBLIC_LATHE
materials = list(/datum/material/iron = 30000)
build_path = /obj/item/ammo_box/c10mm
category = list("hacked", "Security")
@@ -156,7 +156,7 @@
/datum/design/c45
name = "Ammo Box (.45)"
id = "c45"
- build_type = AUTOLATHE
+ build_type = AUTOLATHE | NO_PUBLIC_LATHE
materials = list(/datum/material/iron = 30000)
build_path = /obj/item/ammo_box/c45
category = list("hacked", "Security")
@@ -164,7 +164,7 @@
/datum/design/c9mm
name = "Ammo Box (9mm)"
id = "c9mm"
- build_type = AUTOLATHE
+ build_type = AUTOLATHE | NO_PUBLIC_LATHE
materials = list(/datum/material/iron = 30000)
build_path = /obj/item/ammo_box/c9mm
category = list("hacked", "Security")
@@ -172,7 +172,7 @@
/datum/design/electropack
name = "Electropack"
id = "electropack"
- build_type = AUTOLATHE
+ build_type = AUTOLATHE | NO_PUBLIC_LATHE
materials = list(/datum/material/iron = 10000, /datum/material/glass = 2500)
build_path = /obj/item/electropack
category = list("hacked", "Security")
@@ -180,7 +180,7 @@
/datum/design/cleaver
name = "Butcher's Cleaver"
id = "cleaver"
- build_type = AUTOLATHE
+ build_type = AUTOLATHE | NO_PUBLIC_LATHE
materials = list(/datum/material/iron = 18000)
build_path = /obj/item/kitchen/knife/butcher
category = list("hacked", "Dinnerware")
diff --git a/code/modules/research/designs/autolathe_desings/autolathe_designs_tools.dm b/code/modules/research/designs/autolathe_desings/autolathe_designs_tools.dm
index 8288ceff23..f6c31a9808 100644
--- a/code/modules/research/designs/autolathe_desings/autolathe_designs_tools.dm
+++ b/code/modules/research/designs/autolathe_desings/autolathe_designs_tools.dm
@@ -49,7 +49,7 @@
/datum/design/multitool
name = "Multitool"
id = "multitool"
- build_type = AUTOLATHE | PROTOLATHE
+ build_type = AUTOLATHE | PROTOLATHE | NO_PUBLIC_LATHE
materials = list(/datum/material/iron = 50, /datum/material/glass = 20)
build_path = /obj/item/multitool
category = list("initial","Tools","Tool Designs")
@@ -75,7 +75,7 @@
/datum/design/weldingtool
name = "Welding Tool"
id = "welding_tool"
- build_type = AUTOLATHE
+ build_type = AUTOLATHE | NO_PUBLIC_LATHE
materials = list(/datum/material/iron = 70, /datum/material/glass = 20)
build_path = /obj/item/weldingtool
category = list("initial","Tools","Tool Designs")
@@ -97,6 +97,7 @@
build_path = /obj/item/screwdriver
category = list("initial","Tools","Tool Designs")
departmental_flags = DEPARTMENTAL_FLAG_ENGINEERING | DEPARTMENTAL_FLAG_SCIENCE
+
/datum/design/wirecutters
name = "Wirecutters"
id = "wirecutters"
diff --git a/code/modules/research/designs/autoylathe_designs.dm b/code/modules/research/designs/autoylathe_designs.dm
index 0e572fee7a..c2076db121 100644
--- a/code/modules/research/designs/autoylathe_designs.dm
+++ b/code/modules/research/designs/autoylathe_designs.dm
@@ -562,6 +562,7 @@
materials = list(/datum/material/plastic = 4000, /datum/material/iron = 500)
build_path = /obj/item/gun/ballistic/automatic/x9/toy
category = list("initial", "Rifles")
+ build_type = TOYLATHE | NO_PUBLIC_LATHE
/datum/design/foam_dart
name = "Box of Foam Darts"
@@ -586,6 +587,7 @@
materials = list(/datum/material/plastic = 4000, /datum/material/iron = 500)
build_path = /obj/item/gun/ballistic/automatic/toy/magrifle
category = list("initial", "Rifles")
+ build_type = TOYLATHE | NO_PUBLIC_LATHE
/datum/design/foam_hyperburst
name = "MagTag Hyper Rifle"
@@ -618,6 +620,7 @@
materials = list(/datum/material/plastic = 4000, /datum/material/iron = 500)
build_path = /obj/item/gun/ballistic/automatic/AM4C
category = list("initial", "Rifles")
+ build_type = TOYLATHE | NO_PUBLIC_LATHE
/datum/design/foam_f3
name = "Replica F3 Justicar"
@@ -650,6 +653,7 @@
materials = list(/datum/material/plastic = 2000, /datum/material/iron = 250)
build_path = /obj/item/gun/ballistic/automatic/toy/unrestricted
category = list("initial", "Pistols")
+ build_type = TOYLATHE | NO_PUBLIC_LATHE
/datum/design/foam_pistol
name = "Foam Force Pistol"
@@ -698,6 +702,7 @@
materials = list(/datum/material/plastic = 4000, /datum/material/iron = 500)
build_path = /obj/item/gun/ballistic/automatic/c20r/toy/unrestricted
category = list("hacked", "Rifles")
+ build_type = TOYLATHE | NO_PUBLIC_LATHE
/datum/design/foam_l6
name = "Donksoft LMG"
@@ -706,3 +711,4 @@
materials = list(/datum/material/plastic = 4000, /datum/material/iron = 500)
build_path = /obj/item/gun/ballistic/automatic/l6_saw/toy/unrestricted
category = list("hacked", "Rifles")
+ build_type = TOYLATHE | NO_PUBLIC_LATHE
From 843f43b4ad972c367b9b5e26eb84edbc46ebda88 Mon Sep 17 00:00:00 2001
From: Ghommie <42542238+Ghommie@users.noreply.github.com>
Date: Sun, 3 May 2020 06:27:38 +0200
Subject: [PATCH 02/62] Some autolathe issue.
---
code/game/machinery/autolathe.dm | 4 +++-
1 file changed, 3 insertions(+), 1 deletion(-)
diff --git a/code/game/machinery/autolathe.dm b/code/game/machinery/autolathe.dm
index 91be9e2676..bb3b2d068f 100644
--- a/code/game/machinery/autolathe.dm
+++ b/code/game/machinery/autolathe.dm
@@ -421,7 +421,9 @@
hacked = state
for(var/id in SSresearch.techweb_designs)
var/datum/design/D = SSresearch.techweb_design_by_id(id)
- if((D.build_type & AUTOLATHE) && ("hacked" in D.category))
+ if(D.build_type & stored_research.design_autounlock_skip_types)
+ continue
+ if((D.build_type & stored_research.design_autounlock_buildtypes) && ("hacked" in D.category))
if(hacked)
stored_research.add_design(D)
else
From 161da560b6ade7f9accf935859cf08987b92d84f Mon Sep 17 00:00:00 2001
From: Ghom <42542238+Ghommie@users.noreply.github.com>
Date: Sun, 3 May 2020 07:17:18 +0200
Subject: [PATCH 03/62] Update autolathe.dm
---
code/game/machinery/autolathe.dm | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/code/game/machinery/autolathe.dm b/code/game/machinery/autolathe.dm
index bb3b2d068f..530f782452 100644
--- a/code/game/machinery/autolathe.dm
+++ b/code/game/machinery/autolathe.dm
@@ -31,7 +31,7 @@
var/selected_category
var/screen = 1
- var/datum/techweb/stored_research = /datum/techweb/specialized/autounlocking/autolathe
+ var/datum/techweb/specialized/autounlocking/stored_research = /datum/techweb/specialized/autounlocking/autolathe
var/list/categories = list(
"Tools",
"Electronics",
From 87389243e66de7c3387061f2f61e101e3857751d Mon Sep 17 00:00:00 2001
From: Ghommie <42542238+Ghommie@users.noreply.github.com>
Date: Mon, 4 May 2020 17:30:15 +0200
Subject: [PATCH 04/62] r p g skills WIP.
---
code/__DEFINES/skills/skills.dm | 22 +++++++
code/datums/skills/_skill.dm | 98 +++++++++++++++++++++++++++--
code/datums/skills/_skill_holder.dm | 21 ++++---
code/datums/skills/engineering.dm | 3 +
tgstation.dme | 1 +
5 files changed, 130 insertions(+), 15 deletions(-)
create mode 100644 code/datums/skills/engineering.dm
diff --git a/code/__DEFINES/skills/skills.dm b/code/__DEFINES/skills/skills.dm
index b97b921550..d8c37bec59 100644
--- a/code/__DEFINES/skills/skills.dm
+++ b/code/__DEFINES/skills/skills.dm
@@ -1,3 +1,5 @@
+#define GET_SKILL_DATUM(path) GLOB.skill_datums[path]
+
/// true/false
#define SKILL_PROGRESSION_BINARY 1
/// numerical
@@ -5,11 +7,31 @@
/// Enum
#define SKILL_PROGRESSION_ENUM 3
+
/// Max value of skill for numerical skills
#define SKILL_NUMERICAL_MAX 100
/// Min value of skill for numerical skills
#define SKILL_NUMERICAL_MIN 0
+// Values for experience skills
+#define STD_XP_LVL_UP 100
+#define STD_XP_LVL_MULTI 2
+#define STD_MAX_LVL 4
+
+#define RPG_MAX_LVL 100
+
+#define DORF_XP_LVL_UP 400
+#define DORF_XP_LVL_MULTI 100
+#define DORF_MAX_LVL 20 // Dabbling, novice, adequate, [...], legendary +3, legendary +4, legendary +5
+
+//How experience levels are calculated.
+#define XP_LEVEL(std, multi, lvl) (std * (multi**lvl))
+#define DORF_XP_LEVEL(std, extra, lvl) (std*lvl+extra*(lvl*(lvl/2+0.5)))
+
+//level up methods defines
+#define STANDARD_LEVEL_UP "standard_level_up"
+#define DWARFY_LEVEL_UP "dwarfy_level_up"
+
// Standard values for job starting skills
#define STARTING_SKILL_SURGERY_MEDICAL 35 //out of SKILL_NUMERICAL_MAX
diff --git a/code/datums/skills/_skill.dm b/code/datums/skills/_skill.dm
index 2dd321c4c6..f7225283cd 100644
--- a/code/datums/skills/_skill.dm
+++ b/code/datums/skills/_skill.dm
@@ -9,16 +9,13 @@ GLOBAL_LIST_INIT(skill_datums, init_skill_datums())
S = new path
.[S.type] = S
-/proc/get_skill_datum(path)
- return GLOB.skill_datums[path]
-
/proc/sanitize_skill_value(path, value)
- var/datum/skill/S = get_skill_datum(path)
+ var/datum/skill/S = GET_SKILL_DATUM(path)
// don't check, if we runtime let it happen.
return S.sanitize_value(value)
/proc/is_skill_value_greater(path, existing, new_value)
- var/datum/skill/S = get_skill_datum(path)
+ var/datum/skill/S = GET_SKILL_DATUM(path)
// don't check, if we runtime let it happen.
return S.is_value_greater(existing, new_value)
@@ -30,6 +27,8 @@ GLOBAL_LIST_INIT(skill_datums, init_skill_datums())
var/name
/// Our description
var/desc
+ /// Color of the name as shown in the html readout
+ var/name_color = "#000000"
/// Our progression type
var/progression_type
/// Abstract type
@@ -41,6 +40,13 @@ GLOBAL_LIST_INIT(skill_datums, init_skill_datums())
/datum/skill/proc/sanitize_value(new_value)
return new_value
+/**
+ * Sets the new value of this skill in the holder skills list.
+ * As well as possible feedback messages or secondary effects on value change, that's on you.
+ */
+/datum/skill/proc/set_skill(datum/skill_holder/H, value, mob/owner)
+ H.skills[type] = value
+
/**
* Checks if a value is greater
*/
@@ -93,3 +99,85 @@ GLOBAL_LIST_INIT(skill_datums, init_skill_datums())
/datum/skill/enum/sanitize_value(new_value)
if(new_value in valid_values)
return new_value
+
+/**
+ * Classing r p g styled skills, tiered by lvl, and current/nextlvl experience.
+ */
+/datum/skill/experience
+ abstract_type = /datum/skill/experience
+ var/standard_xp_lvl_up = STD_XP_LVL_UP //the standard required to level up. def: 100
+ var/xp_lvl_multiplier = STD_XP_LVL_UP //standard required level up exp multiplier. def: 2 (100, 200, 400, 800 etc.)
+ var/max_lvl = STD_MAX_LVL
+ var/level_up_method = STANDARD_LEVEL_UP //how levels are calculated.
+ var/list/levels = list() //level thresholds, if associative, these will be preceded by tiers such as "novice" or "trained"
+ var/associative = FALSE //See above.
+ var/unskilled_tier = "Unskilled" //Only relevant for associative experience levels
+
+//Builds the levels list.
+/datum/skill/experience/New()
+ . = ..()
+ var/max_assoc = ""
+ var/max_assoc_start = 1
+ for(var/lvl in 1 to max_lvl)
+ var/value
+ switch(level_up_method)
+ if(STANDARD_LEVEL_UP)
+ value = XP_LEVEL(standard_xp_lvl_up, xp_lvl_multiplier, lvl)
+ if(DWARFY_LEVEL_UP)
+ value = DORF_XP_LEVEL(standard_xp_lvl_up, xp_lvl_multiplier, lvl)
+ value = round(value)
+ if(!associative)
+ levels += value
+ continue
+ if(max_assoc)
+ levels["[max_assoc] +[max_assoc_start++]"] = value
+ continue
+ var/key = LAZYACCESS(levels, lvl)
+ if(!key)
+ if(lvl == 1) //You dun goof it.
+ stack_trace("Skill datum [src] was set to have an associative levels list despite the latted having no key.")
+ associative = FALSE
+ levels += value
+ continue
+ max_assoc = levels[lvl-1]
+ levels["[max_assoc] +[max_assoc_start++]"] = value
+ levels[key] = value
+
+
+/datum/skill/experience/sanitize_value(new_value)
+ return round(max(new_value, 0))
+
+/datum/skill/experience/set_skill(datum/skill_holder/H, value, mob/owner)
+ var/old_value = H.skills[type]
+ H.skills[type] = value
+ if(value > old_value)
+
+/datum/skill/experience/standard_render_value(value)
+ var/current_lvl = associative ? unskilled_tier : 0
+ var/current_lvl_xp_sum = 0
+ var/next_lvl_xp_sum
+ for(var/lvl in 1 to max_lvl)
+ next_lvl_xp_sum = associative ? levels[levels[lvl]] : levels[lvl]
+ if(value < next_lvl_xp_sum)
+ break
+ current_lvl_xp_sum = next_lvl_xp_sum
+ current_lvl = associative ? levels[lvl] : current_lvl+1
+
+ return "[associative ? current_lvl : "Lvl. [current_lvl]"] ([value - current_lvl_xp_sum]/[next_lvl_xp_sum])[value > next_lvl_xp_sum ? " \[MAX!\]" : ""]"
+
+/datum/skill/experience/job
+ levels = ("Basic", "Trained", "Experienced", "Master")
+ associative = TRUE
+
+//quite the reference, no?
+/datum/skill/experience/dwarfy
+ abstract_type = /datum/skill/experience/dwarfy
+ standard_xp_lvl_up = DORF_XP_LVL_UP
+ xp_lvl_multiplier = DORF_XP_LVL_MULTI
+ max_lvl = DORF_MAX_LVL
+ levels = list("Novice", "Adequate", "Competent", "Skilled",
+ "Proficient", "Talented", "Adept", "Expert",
+ "Professional", "Accomplished", "Great", "Master",
+ "High Master", "Grand Master", "Legendary")
+ associative = TRUE
+ unskilled_tier = "Dabbling"
diff --git a/code/datums/skills/_skill_holder.dm b/code/datums/skills/_skill_holder.dm
index 352adc46ff..71744b7d43 100644
--- a/code/datums/skills/_skill_holder.dm
+++ b/code/datums/skills/_skill_holder.dm
@@ -33,36 +33,37 @@
/**
* Sets the value of a skill.
*/
-/datum/skill_holder/proc/set_skill_value(skill, value)
- if(!ispath(skill))
+/datum/skill_holder/proc/set_skill_value(skill, value, owner)
+ if(!ispath(skill, /datum/skill))
CRASH("Invalid set_skill_value call. Use typepaths.") //until a time when we somehow need text ids for dynamic skills, I'm enforcing this.
- LAZYINITLIST(skills)
- value = sanitize_skill_value(skill, value)
+ var/datum/skill/S = GET_SKILL_DATUM(path)
+ value = S.sanitize_value(value)
if(!isnull(value))
- skills[skill] = value
+ LAZYINITLIST(skills)
+ S.set_skill(src, value, owner)
return TRUE
return FALSE
/**
* Boosts a skill to a value if not aobve
*/
-/datum/skill_holder/proc/boost_skill_value_to(skill, value)
+/datum/skill_holder/proc/boost_skill_value_to(skill, value, mob/owner)
var/current = get_skill_value(skill)
if(!is_skill_value_greater(skill, current, value))
return FALSE
- set_skill_value(skill, value)
+ set_skill_value(skill, value, owner)
return TRUE
/**
* Automatic skill increase, multiplied by skill affinity if existing.
* Only works if skill is numerical.
*/
-/datum/skill_holder/proc/auto_gain_experience(skill, value)
+/datum/skill_holder/proc/auto_gain_experience(skill, value, mob/owner)
if(!ispath(skill, /datum/skill/numerical))
CRASH("You cannot auto increment a non numerical skill!")
var/current = get_skill_value(skill)
var/affinity = get_skill_affinity(skill)
- boost_skill_value_to(skill, current + (value * affinity))
+ boost_skill_value_to(skill, current + (value * affinity), owner)
/**
* Generates a HTML readout of our skills.
@@ -73,6 +74,6 @@
out += "
| Skill | Value |
"
for(var/path in skills)
var/datum/skill/S = GLOB.skill_datums[path]
- out += "| [S.name] | [S.standard_render_value(skills[path])] |
"
+ out += "| [S.name] | [S.standard_render_value(skills[path])] |
"
out += "
"
return out.Join("")
diff --git a/code/datums/skills/engineering.dm b/code/datums/skills/engineering.dm
new file mode 100644
index 0000000000..c06accea77
--- /dev/null
+++ b/code/datums/skills/engineering.dm
@@ -0,0 +1,3 @@
+/datum/skill/experience/job/wiring
+ name = "Wiring"
+ desc = "How proficient and knowledged you are at wiring beyond laying cables on the floor."
diff --git a/tgstation.dme b/tgstation.dme
index 9aa47506d3..68ae7669f3 100755
--- a/tgstation.dme
+++ b/tgstation.dme
@@ -575,6 +575,7 @@
#include "code\datums\skills\_check_skills.dm"
#include "code\datums\skills\_skill.dm"
#include "code\datums\skills\_skill_holder.dm"
+#include "code\datums\skills\engineering.dm"
#include "code\datums\skills\medical.dm"
#include "code\datums\status_effects\buffs.dm"
#include "code\datums\status_effects\debuffs.dm"
From 643ef48f0e66e52138b62844059133214f34b762 Mon Sep 17 00:00:00 2001
From: Ghommie <42542238+Ghommie@users.noreply.github.com>
Date: Tue, 5 May 2020 04:42:34 +0200
Subject: [PATCH 05/62] Macros, generic skill implementations.
---
.../skills/{skills.dm => defines.dm} | 62 +++++++++++------
code/__DEFINES/skills/helpers.dm | 44 ++++++++++++
code/_onclick/item_attack.dm | 56 +++++++++++----
code/datums/mind.dm | 3 +-
code/datums/skills/_skill.dm | 69 ++++++++++++-------
code/datums/skills/_skill_holder.dm | 37 ++++++++--
code/datums/skills/engineering.dm | 3 +-
code/datums/skills/medical.dm | 1 +
code/datums/wires/_wires.dm | 37 ++++++++--
code/datums/wires/airalarm.dm | 1 +
code/datums/wires/airlock.dm | 5 +-
code/datums/wires/apc.dm | 1 +
code/datums/wires/autolathe.dm | 1 +
code/datums/wires/emitter.dm | 2 +
code/datums/wires/explosive.dm | 2 +
code/datums/wires/microwave.dm | 2 +
code/datums/wires/mulebot.dm | 1 +
code/datums/wires/particle_accelerator.dm | 2 +
code/datums/wires/r_n_d.dm | 1 +
code/datums/wires/radio.dm | 2 +
code/datums/wires/robot.dm | 2 +
code/datums/wires/suit_storage_unit.dm | 2 +
code/datums/wires/syndicatebomb.dm | 2 +
code/datums/wires/tesla_coil.dm | 1 +
code/datums/wires/vending.dm | 1 +
code/game/machinery/camera/camera_assembly.dm | 3 +-
code/game/machinery/constructable_frame.dm | 1 -
code/game/machinery/doors/firedoor.dm | 3 +-
code/game/machinery/firealarm.dm | 4 +-
code/game/machinery/shieldgen.dm | 5 +-
code/game/mecha/mecha_defense.dm | 3 +-
code/game/objects/items.dm | 18 ++++-
.../objects/items/grenades/chem_grenade.dm | 3 +-
.../game/objects/items/stacks/sheets/glass.dm | 2 +-
code/game/objects/structures/ai_core.dm | 2 +-
code/game/objects/structures/barsigns.dm | 3 +-
.../objects/structures/windoor_assembly.dm | 3 +-
.../atmospherics/machinery/airalarm.dm | 5 +-
code/modules/clothing/under/_under.dm | 2 +-
code/modules/hydroponics/plant_genes.dm | 3 +-
code/modules/jobs/job_types/_job.dm | 2 +-
code/modules/jobs/job_types/ai.dm | 2 +
.../jobs/job_types/atmospheric_technician.dm | 4 ++
code/modules/jobs/job_types/chief_engineer.dm | 3 +
code/modules/jobs/job_types/cyborg.dm | 2 +
code/modules/jobs/job_types/roboticist.dm | 4 ++
.../jobs/job_types/station_engineer.dm | 3 +
code/modules/mining/ores_coins.dm | 3 +-
.../mob/living/carbon/carbon_defense.dm | 6 +-
.../mob/living/carbon/human/species.dm | 15 +---
.../modules/mob/living/silicon/robot/robot.dm | 13 ++--
.../living/simple_animal/bot/construction.dm | 5 +-
.../modular_computers/hardware/_hardware.dm | 3 +-
code/modules/power/apc.dm | 20 +++---
code/modules/power/cable.dm | 4 +-
code/modules/power/floodlight.dm | 3 +-
code/modules/power/lighting.dm | 3 +-
.../particle_accelerator.dm | 3 +-
.../particle_accelerator/particle_control.dm | 3 +-
code/modules/power/smes.dm | 5 +-
.../projectiles/guns/ballistic/revolver.dm | 3 +-
.../surgery/bodyparts/robot_bodyparts.dm | 15 ++--
code/modules/surgery/surgery_step.dm | 9 ++-
tgstation.dme | 3 +-
64 files changed, 358 insertions(+), 178 deletions(-)
rename code/__DEFINES/skills/{skills.dm => defines.dm} (51%)
create mode 100644 code/__DEFINES/skills/helpers.dm
diff --git a/code/__DEFINES/skills/skills.dm b/code/__DEFINES/skills/defines.dm
similarity index 51%
rename from code/__DEFINES/skills/skills.dm
rename to code/__DEFINES/skills/defines.dm
index d8c37bec59..00859bbd40 100644
--- a/code/__DEFINES/skills/skills.dm
+++ b/code/__DEFINES/skills/defines.dm
@@ -1,4 +1,3 @@
-#define GET_SKILL_DATUM(path) GLOB.skill_datums[path]
/// true/false
#define SKILL_PROGRESSION_BINARY 1
@@ -6,6 +5,8 @@
#define SKILL_PROGRESSION_NUMERICAL 2
/// Enum
#define SKILL_PROGRESSION_ENUM 3
+/// Levels
+#define SKILL_PROGRESSION_LEVEL 4
/// Max value of skill for numerical skills
@@ -13,7 +14,35 @@
/// Min value of skill for numerical skills
#define SKILL_NUMERICAL_MIN 0
-// Values for experience skills
+// Standard values for job starting skills
+
+#define STARTING_SKILL_SURGERY_MEDICAL 35 //out of SKILL_NUMERICAL_MAX
+
+// Standard values for job starting skill affinities
+
+#define STARTING_SKILL_AFFINITY_SURGERY_MEDICAL 1.2
+
+#define STARTING_SKILL_AFFINITY_WIRING_ENGI_ROBO 1.2
+
+// Standard values for skill gain (this is multiplied by affinity)
+
+#define DEF_SKILL_GAIN 1
+#define SKILL_GAIN_SURGERY_PER_STEP 0.25
+
+///Items skill_flags and other defines
+#define SKILL_USE_TOOL (1<<0)
+#define SKILL_TRAINING_TOOL (1<<1)
+#define SKILL_ATTACK_MOB (1<<2)
+#define SKILL_TRAIN_ATTACK_MOB (1<<3)
+#define SKILL_ATTACK_OBJ (1<<4)
+#define SKILL_TRAIN_ATTACK_OBJ (1<<5)
+
+///competency_threshold index defines
+#define THRESHOLD_COMPETENT 1
+#define THRESHOLD_EXPERT 2
+#define THRESHOLD_MASTER 3
+
+/// Level/Experience skills defines.
#define STD_XP_LVL_UP 100
#define STD_XP_LVL_MULTI 2
#define STD_MAX_LVL 4
@@ -24,27 +53,18 @@
#define DORF_XP_LVL_MULTI 100
#define DORF_MAX_LVL 20 // Dabbling, novice, adequate, [...], legendary +3, legendary +4, legendary +5
-//How experience levels are calculated.
-#define XP_LEVEL(std, multi, lvl) (std * (multi**lvl))
-#define DORF_XP_LEVEL(std, extra, lvl) (std*lvl+extra*(lvl*(lvl/2+0.5)))
-
//level up methods defines
#define STANDARD_LEVEL_UP "standard_level_up"
#define DWARFY_LEVEL_UP "dwarfy_level_up"
-// Standard values for job starting skills
+//job skill level defines
+#define JOB_SKILL_UNTRAINED 0
+#define JOB_SKILL_BASIC 1
+#define JOB_SKILL_TRAINED 2
+#define JOB_SKILL_EXPERT 3
+#define JOB_SKILL_MASTER 4
-#define STARTING_SKILL_SURGERY_MEDICAL 35 //out of SKILL_NUMERICAL_MAX
-
-// Standard values for job starting skill affinities
-
-#define STARTING_SKILL_AFFINITY_SURGERY_MEDICAL 1.2
-
-// Standard values for skill gain (this is multiplied by affinity)
-
-#define SKILL_GAIN_SURGERY_PER_STEP 0.25
-
-// Misc
-
-/// 40% speedup at 100 skill
-#define SURGERY_SKILL_SPEEDUP_NUMERICAL_SCALE(number) clamp(number / 250, 1, 2)
+//other skill level defines, not an exhaustive catalogue, only contains be most relevant ones.
+#define DORF_SKILL_COMPETENT 3
+#define DORF_SKILL_EXPERT 8
+#define DORF_SKILL_MASTER 12
diff --git a/code/__DEFINES/skills/helpers.dm b/code/__DEFINES/skills/helpers.dm
new file mode 100644
index 0000000000..165b9091ae
--- /dev/null
+++ b/code/__DEFINES/skills/helpers.dm
@@ -0,0 +1,44 @@
+/**
+ *Generic delay calculation macro for various delayed actions.
+ *The skill to check must be an instance, not a path or list.
+ */
+#define SKILL_MODIFIER(to_check, holder, target, threshold) \
+ var/___value;\
+ switch(to_check.progression_type){\
+ if(SKILL_PROGRESSION_LEVEL){\
+ ___value = LAZYACCESS(holder.skill_levels, to_check.type)\
+ } else {\
+ ___value = LAZYACCESS(holder.skills, to_check.type)\
+ }\
+ }\
+ target /= (1+(___value-to_check.competency_thresholds[threshold])*to_check.competency_mults[threshold])
+
+/// This is the one that accepts typepaths and lists.
+#define LIST_SKILL_MODIFIER(to_check, holder, target, threshold) \
+ if(!islist(to_check)){\
+ SKILL_MODIFIER(GLOB.skill_datums[to_check], holder, target, threshold)\
+ } else {\
+ var/___sum = 0;\
+ var/list/___L = to_check;\
+ for(var/_S in ___L){\
+ var/___value;\
+ var/datum/skill/___S = GLOB.skill_datums[_S];\
+ switch(___S.progression_type){\
+ if(SKILL_PROGRESSION_LEVEL){\
+ ___value = LAZYACCESS(holder.skill_levels, ___S.type)\
+ } else {\
+ ___value = LAZYACCESS(holder.skills, ___S.type)\
+ }\
+ }\
+ ___sum += (1+(___value - ___S.competency_thresholds[threshold])*___S.competency_mults[threshold])\
+ }\
+ target /= (___sum/length(___L))\
+ }
+
+//How experience levels are calculated.
+#define XP_LEVEL(std, multi, lvl) (std*((multi**lvl)/(multi-1))-std/(multi-1)) //don't use 1 as multi, you'll get division by zero errors
+#define DORF_XP_LEVEL(std, extra, lvl) (std*lvl+extra*(lvl*(lvl/2+0.5)))
+
+//More experience value getter macros
+#define GET_STANDARD_LVL(lvl) XP_LEVEL(STD_XP_LVL_UP, STD_XP_LVL_MULTI, lvl)
+#define GET_DORF_LVL(lvl) DORF_XP_LEVEL(DORF_XP_LVL_UP, DORF_XP_LVL_MULTI, lvl)
diff --git a/code/_onclick/item_attack.dm b/code/_onclick/item_attack.dm
index 98db89a100..dbcf171663 100644
--- a/code/_onclick/item_attack.dm
+++ b/code/_onclick/item_attack.dm
@@ -111,26 +111,30 @@
return
/obj/attacked_by(obj/item/I, mob/living/user)
- if(I.force)
+ var/totitemdamage = I.force
+ if(I.used_skills && user.mind)
+ if(I.skill_flags & SKILL_ATTACK_OBJ)
+ LIST_SKILL_MODIFIER(used_skills, user.mind.skill_holder, totitemdamage, I.skill_difficulty)
+ if(I.skill_flags & SKILL_TRAIN_ATTACK_OBJ)
+ if(!islist(I.used_skills))
+ user.mind.skill_holder.boost_skill_value_to(used_skills, I.skill_gain)
+ else
+ for(var/skill in used_skills)
+ user.mind.skill_holder.boost_skill_value_to(skill, I.skill_gain)
+ if(totitemdamage)
visible_message("[user] has hit [src] with [I]!", null, null, COMBAT_MESSAGE_RANGE)
//only witnesses close by and the victim see a hit message.
log_combat(user, src, "attacked", I)
- take_damage(I.force, I.damtype, "melee", 1)
+ take_damage(totitemdamage, I.damtype, "melee", 1)
/mob/living/attacked_by(obj/item/I, mob/living/user)
- //CIT CHANGES START HERE - combatmode and resting checks
- var/totitemdamage = I.force
- if(!(user.combat_flags & COMBAT_FLAG_COMBAT_ACTIVE))
- totitemdamage *= 0.5
- if(!CHECK_MOBILITY(user, MOBILITY_STAND))
- totitemdamage *= 0.5
- //CIT CHANGES END HERE
+ var/totitemdamage = calculate_item_force(I, user, TRUE)
if((user != src) && run_block(I, totitemdamage, "the [I.name]", ATTACK_TYPE_MELEE, I.armour_penetration, user) & BLOCK_SUCCESS)
return FALSE
send_item_attack_message(I, user)
- I.do_stagger_action(src, user)
+ I.do_stagger_action(src, user, totitemdamage)
if(I.force)
- apply_damage(totitemdamage, I.damtype) //CIT CHANGE - replaces I.force with totitemdamage
+ apply_damage(totitemdamage, I.damtype)
if(I.damtype == BRUTE)
if(prob(33))
I.add_mob_blood(src)
@@ -146,6 +150,28 @@
else
return ..()
+/mob/living/proc/pre_attacked_by(obj/item/I, mob/living/user, pre_attack = FALSE)
+ . = I.force
+ if(!(user.combat_flags & COMBAT_FLAG_COMBAT_ACTIVE))
+ . *= 0.5
+ if(!CHECK_MOBILITY(user, MOBILITY_STAND))
+ . *= 0.5
+ if(!pre_attack || !user.mind || !I.used_skills)
+ return
+ if(. && I.skill_flags & SKILL_ATTACK_MOB)
+ LIST_SKILL_MODIFIER(used_skills, user.mind.skill_holder, ., I.skill_difficulty)
+ if(I.skill_flags & SKILL_TRAIN_ATTACK_MOB)
+ if(!islist(I.used_skills))
+ user.mind.skill_holder.boost_skill_value_to(used_skills, I.skill_gain)
+ else
+ for(var/skill in used_skills)
+ user.mind.skill_holder.boost_skill_value_to(skill, I.skill_gain)
+
+/mob/living/carbon/proc/pre_attacked_by(obj/item/I, mob/living/user, pre_attack = FALSE)
+ . = ..()
+ if(!(combat_flags & COMBAT_FLAG_COMBAT_ACTIVE))
+ . *= 1.5
+
// Proximity_flag is 1 if this afterattack was called on something adjacent, in your square, or on your person.
// Click parameters is the params string from byond Click() code, see that documentation.
/obj/item/proc/afterattack(atom/target, mob/user, proximity_flag, click_parameters)
@@ -183,18 +209,18 @@
return total_mass || w_class * 1.25
/// How long this staggers for. 0 and negatives supported.
-/obj/item/proc/melee_stagger_duration()
+/obj/item/proc/melee_stagger_duration(force_override)
if(!isnull(stagger_force))
return stagger_force
/// totally not an untested, arbitrary equation.
- return clamp((1.5 + (w_class/7.5)) * (force / 2), 0, 10 SECONDS)
+ return clamp((1.5 + (w_class/7.5)) * ((force_override || force) / 2), 0, 10 SECONDS)
-/obj/item/proc/do_stagger_action(mob/living/target, mob/living/user)
+/obj/item/proc/do_stagger_action(mob/living/target, mob/living/user, force_override)
if(!CHECK_BITFIELD(target.status_flags, CANSTAGGER))
return FALSE
if(target.combat_flags & COMBAT_FLAG_SPRINT_ACTIVE)
target.do_staggered_animation()
- var/duration = melee_stagger_duration()
+ var/duration = melee_stagger_duration(force_override)
if(!duration) //0
return FALSE
else if(duration > 0)
diff --git a/code/datums/mind.dm b/code/datums/mind.dm
index 728c4fb51b..54fcbbc6aa 100644
--- a/code/datums/mind.dm
+++ b/code/datums/mind.dm
@@ -67,7 +67,7 @@
var/datum/skill_holder/skill_holder
/datum/mind/New(var/key)
- skill_holder = new
+ skill_holder = new(src)
src.key = key
soulOwner = src
martial_art = default_martial_art
@@ -80,6 +80,7 @@
if(antag_datum.delete_on_mind_deletion)
qdel(i)
antag_datums = null
+ QDEL_NULL(skill_holder)
return ..()
/datum/mind/proc/get_language_holder()
diff --git a/code/datums/skills/_skill.dm b/code/datums/skills/_skill.dm
index f7225283cd..039afacfa5 100644
--- a/code/datums/skills/_skill.dm
+++ b/code/datums/skills/_skill.dm
@@ -1,4 +1,4 @@
-GLOBAL_LIST_INIT(skill_datums, init_skill_datums())
+GLOBAL_LIST_INIT_TYPED(skill_datums, /datum/skill, init_skill_datums())
/proc/init_skill_datums()
. = list()
@@ -10,12 +10,12 @@ GLOBAL_LIST_INIT(skill_datums, init_skill_datums())
.[S.type] = S
/proc/sanitize_skill_value(path, value)
- var/datum/skill/S = GET_SKILL_DATUM(path)
+ var/datum/skill/S = GLOB.skill_datums[path]
// don't check, if we runtime let it happen.
return S.sanitize_value(value)
/proc/is_skill_value_greater(path, existing, new_value)
- var/datum/skill/S = GET_SKILL_DATUM(path)
+ var/datum/skill/S = GLOB.skill_datums[path]
// don't check, if we runtime let it happen.
return S.is_value_greater(existing, new_value)
@@ -33,6 +33,10 @@ GLOBAL_LIST_INIT(skill_datums, init_skill_datums())
var/progression_type
/// Abstract type
var/abstract_type = /datum/skill
+ /// skill threshold used in generic skill modifiers calculations.
+ var/list/competency_thresholds = list(0, 0, 0)
+ /// Multiplier of the difference of the holder skill value and the selected threshold.
+ var/list/competency_mults = list(0, 0, 0)
/**
* Ensures what someone's setting as a value for this skill is valid.
@@ -44,7 +48,7 @@ GLOBAL_LIST_INIT(skill_datums, init_skill_datums())
* Sets the new value of this skill in the holder skills list.
* As well as possible feedback messages or secondary effects on value change, that's on you.
*/
-/datum/skill/proc/set_skill(datum/skill_holder/H, value, mob/owner)
+/datum/skill/proc/set_skill_value(datum/skill_holder/H, value, mob/owner)
H.skills[type] = value
/**
@@ -67,6 +71,8 @@ GLOBAL_LIST_INIT(skill_datums, init_skill_datums())
/datum/skill/binary
abstract_type = /datum/skill/binary
progression_type = SKILL_PROGRESSION_BINARY
+ competency_thresholds = list(FALSE, TRUE, TRUE)
+ competency_mults = list(0.5, 0.5, 0.5)
/datum/skill/binary/sanitize_value(new_value)
return new_value? TRUE : FALSE
@@ -103,22 +109,23 @@ GLOBAL_LIST_INIT(skill_datums, init_skill_datums())
/**
* Classing r p g styled skills, tiered by lvl, and current/nextlvl experience.
*/
-/datum/skill/experience
- abstract_type = /datum/skill/experience
+/datum/skill/level
+ abstract_type = /datum/skill/level
+ progression_type = SKILL_PROGRESSION_LEVEL
var/standard_xp_lvl_up = STD_XP_LVL_UP //the standard required to level up. def: 100
var/xp_lvl_multiplier = STD_XP_LVL_UP //standard required level up exp multiplier. def: 2 (100, 200, 400, 800 etc.)
- var/max_lvl = STD_MAX_LVL
+ var/max_levels = STD_MAX_LVL
var/level_up_method = STANDARD_LEVEL_UP //how levels are calculated.
var/list/levels = list() //level thresholds, if associative, these will be preceded by tiers such as "novice" or "trained"
var/associative = FALSE //See above.
var/unskilled_tier = "Unskilled" //Only relevant for associative experience levels
//Builds the levels list.
-/datum/skill/experience/New()
+/datum/skill/level/New()
. = ..()
var/max_assoc = ""
var/max_assoc_start = 1
- for(var/lvl in 1 to max_lvl)
+ for(var/lvl in 1 to max_levels)
var/value
switch(level_up_method)
if(STANDARD_LEVEL_UP)
@@ -143,41 +150,57 @@ GLOBAL_LIST_INIT(skill_datums, init_skill_datums())
levels["[max_assoc] +[max_assoc_start++]"] = value
levels[key] = value
+/datum/skill/level/sanitize_value(new_value)
+ return max(new_value, 0)
-/datum/skill/experience/sanitize_value(new_value)
- return round(max(new_value, 0))
-
-/datum/skill/experience/set_skill(datum/skill_holder/H, value, mob/owner)
- var/old_value = H.skills[type]
+/datum/skill/level/set_skill_value(datum/skill_holder/H, value, datum/mind/M, silent = FALSE)
H.skills[type] = value
- if(value > old_value)
+ var/new_level
+ for(var/k in levels)
+ if(value < (associative ? levels[k] : k))
+ break
+ new_level++
+ var/old_level = LAZYACCESS(H.skill_levels, type)
+ LAZYSET(H.skill_levels, type, new_level)
+ . = new_level - old_level
+ if(silent || !(M?.current))
+ return
+ if(. > 0)
+ to_chat(M.current, "I feel like I've become more proficient at [name]!")
+ else if(. < 0)
+ to_chat(M.current, "I feel like I've become worse at [name]!")
-/datum/skill/experience/standard_render_value(value)
+/datum/skill/level/standard_render_value(value)
var/current_lvl = associative ? unskilled_tier : 0
var/current_lvl_xp_sum = 0
var/next_lvl_xp_sum
- for(var/lvl in 1 to max_lvl)
+ for(var/lvl in 1 to max_levels)
next_lvl_xp_sum = associative ? levels[levels[lvl]] : levels[lvl]
if(value < next_lvl_xp_sum)
break
current_lvl_xp_sum = next_lvl_xp_sum
- current_lvl = associative ? levels[lvl] : current_lvl+1
+ current_lvl = associative ? levels[lvl] : lvl+1
return "[associative ? current_lvl : "Lvl. [current_lvl]"] ([value - current_lvl_xp_sum]/[next_lvl_xp_sum])[value > next_lvl_xp_sum ? " \[MAX!\]" : ""]"
-/datum/skill/experience/job
- levels = ("Basic", "Trained", "Experienced", "Master")
+/datum/skill/level/job
+ levels = list("Basic", "Trained", "Experienced", "Master")
+ competency_thresholds = list(JOB_SKILL_TRAINED, JOB_SKILL_EXPERT, JOB_SKILL_MASTER)
+ competency_mults = list(0.15, 0.1, 0.1)
associative = TRUE
//quite the reference, no?
-/datum/skill/experience/dwarfy
- abstract_type = /datum/skill/experience/dwarfy
+/datum/skill/level/dwarfy
+ abstract_type = /datum/skill/level/dwarfy
standard_xp_lvl_up = DORF_XP_LVL_UP
xp_lvl_multiplier = DORF_XP_LVL_MULTI
- max_lvl = DORF_MAX_LVL
+ max_levels = DORF_MAX_LVL
+ level_up_method = DWARFY_LEVEL_UP
levels = list("Novice", "Adequate", "Competent", "Skilled",
"Proficient", "Talented", "Adept", "Expert",
"Professional", "Accomplished", "Great", "Master",
"High Master", "Grand Master", "Legendary")
+ competency_thresholds = list(DORF_SKILL_COMPETENT, DORF_SKILL_EXPERT, DORF_SKILL_MASTER)
+ competency_mults = list(0.15, 0.1, 0.08)
associative = TRUE
unskilled_tier = "Dabbling"
diff --git a/code/datums/skills/_skill_holder.dm b/code/datums/skills/_skill_holder.dm
index 71744b7d43..d1b6b8bd0d 100644
--- a/code/datums/skills/_skill_holder.dm
+++ b/code/datums/skills/_skill_holder.dm
@@ -2,10 +2,18 @@
* Skill holder datums
*/
/datum/skill_holder
+ var/datum/mind/owner
/// Our list of skills and values. Lazylist. Associative. Keys are datum typepaths to the skill.
var/list/skills
/// Same as [skills] but affinities, which are multiplied to increase amount when gaining skills.
var/list/skill_affinities
+ /// Let's say we want to get a specific skill "level" without looping through a proc everytime.
+ /// Only supported by skills with tiers or levels.
+ var/list/skill_levels
+
+/datum/skill_holder/New(datum/mind/M)
+ . = ..()
+ owner = M
/**
* Grabs the value of a skill.
@@ -17,6 +25,16 @@
return null
return skills[skill]
+/**
+ * Grabs the level of a skill. Only supported by skills with tiers or levels.
+ */
+/datum/skill_holder/proc/get_skill_level(skill)
+ if(!ispath(skill))
+ CRASH("Invalid get_skill_value call. Use typepaths.") //until a time when we somehow need text ids for dynamic skills, I'm enforcing this.
+ if(!skill_levels)
+ return 0
+ return skill_levels[skill]
+
/**
* Grabs our affinity for a skill. !!This is a multiplier!!
*/
@@ -33,37 +51,42 @@
/**
* Sets the value of a skill.
*/
-/datum/skill_holder/proc/set_skill_value(skill, value, owner)
+/datum/skill_holder/proc/set_skill_value(skill, value, silent = FALSE)
if(!ispath(skill, /datum/skill))
CRASH("Invalid set_skill_value call. Use typepaths.") //until a time when we somehow need text ids for dynamic skills, I'm enforcing this.
- var/datum/skill/S = GET_SKILL_DATUM(path)
+ var/datum/skill/S = GLOB.skill_datums[skill]
value = S.sanitize_value(value)
if(!isnull(value))
LAZYINITLIST(skills)
- S.set_skill(src, value, owner)
+ S.set_skill_value(src, value, owner, silent)
return TRUE
return FALSE
/**
* Boosts a skill to a value if not aobve
*/
-/datum/skill_holder/proc/boost_skill_value_to(skill, value, mob/owner)
+/datum/skill_holder/proc/boost_skill_value_to(skill, value, silent = FALSE)
var/current = get_skill_value(skill)
if(!is_skill_value_greater(skill, current, value))
return FALSE
- set_skill_value(skill, value, owner)
+ set_skill_value(skill, value, silent)
return TRUE
/**
* Automatic skill increase, multiplied by skill affinity if existing.
* Only works if skill is numerical.
*/
-/datum/skill_holder/proc/auto_gain_experience(skill, value, mob/owner)
+/datum/skill_holder/proc/auto_gain_experience(skill, value, maximum, silent = FALSE)
if(!ispath(skill, /datum/skill/numerical))
CRASH("You cannot auto increment a non numerical skill!")
var/current = get_skill_value(skill)
var/affinity = get_skill_affinity(skill)
- boost_skill_value_to(skill, current + (value * affinity), owner)
+ var/target_value = current + (value * affinity)
+ if(maximum)
+ target_value = max(target_value, maximum)
+ if(target_value == maximum) //no more experience to gain, early return.
+ return
+ boost_skill_value_to(skill, target_value, silent)
/**
* Generates a HTML readout of our skills.
diff --git a/code/datums/skills/engineering.dm b/code/datums/skills/engineering.dm
index c06accea77..b3eeb9d57d 100644
--- a/code/datums/skills/engineering.dm
+++ b/code/datums/skills/engineering.dm
@@ -1,3 +1,4 @@
-/datum/skill/experience/job/wiring
+/datum/skill/level/job/wiring
name = "Wiring"
desc = "How proficient and knowledged you are at wiring beyond laying cables on the floor."
+ competency_thresholds = list(JOB_SKILL_BASIC, JOB_SKILL_EXPERT, JOB_SKILL_MASTER)
diff --git a/code/datums/skills/medical.dm b/code/datums/skills/medical.dm
index 3b67cacb04..1b0774b0ce 100644
--- a/code/datums/skills/medical.dm
+++ b/code/datums/skills/medical.dm
@@ -1,3 +1,4 @@
/datum/skill/numerical/surgery
name = "Surgery"
desc = "How proficient you are at doing surgery."
+ competency_mults = list(0.025, 0.025, 0.025) // 60% surgery speed up at max value of 100.
diff --git a/code/datums/wires/_wires.dm b/code/datums/wires/_wires.dm
index ad24126e45..fc31cb9286 100644
--- a/code/datums/wires/_wires.dm
+++ b/code/datums/wires/_wires.dm
@@ -33,6 +33,9 @@
var/list/assemblies = list() // List of attached assemblies.
var/randomize = 0 // If every instance of these wires should be random.
// Prevents wires from showing up in station blueprints
+ var/req_knowledge = INFINITY //wiring skill level on which the functions are revealed.
+ var/req_skill = JOB_SKILL_BASIC //used in user's cutting/pulsing/mending speed calculations.
+ var/list/current_users //list of untrained people currently interacting with this set of wires.
/datum/wires/New(atom/holder)
..()
@@ -130,8 +133,20 @@
cut_wires += wire
on_cut(wire, mend = FALSE)
-/datum/wires/proc/cut_color(color)
+/datum/wires/proc/cut_color(color, mob/living/user)
+ LAZYINITLIST(current_users)
+ if(current_users[user])
+ return FALSE
+ if(req_skill && user?.mind)
+ var/level_diff = req_skill - user.mind.skill_holder.get_skill_level(/datum/skill/level/job/wiring)
+ if(level_diff > 0)
+ to_chat(user, "You begin cutting [holder]'s [color] wire...")
+ if(!do_after(user, 1.5 SECONDS * level_diff, target = holder) || !interactable(user))
+ return FALSE
+ user.mind?.skill_holder.auto_gain_experience(/datum/skill/level/job/wiring, DEF_SKILL_GAIN*level_diff)
+ to_chat(user, "You cut [holder]'s [color] wire.")
cut(get_wire(color))
+ return TRUE
/datum/wires/proc/cut_random()
cut(wires[rand(1, wires.len)])
@@ -146,7 +161,19 @@
on_pulse(wire, user)
/datum/wires/proc/pulse_color(color, mob/living/user)
+ LAZYINITLIST(current_users)
+ if(current_users[user])
+ return FALSE
+ if(req_skill && user?.mind)
+ var/level_diff = req_skill - user.mind.skill_holder.get_skill_level(/datum/skill/level/job/wiring)
+ if(level_diff > 0)
+ to_chat(user, "You begin pulsing [holder]'s [color] wire...")
+ if(!do_after(user, 1.5 SECONDS * level_diff, target = holder) || !interactable(user))
+ return FALSE
+ user.mind?.skill_holder.auto_gain_experience(/datum/skill/level/job/wiring, DEF_SKILL_GAIN*level_diff)
+ to_chat(user, "You pulse [holder]'s [color] wire.")
pulse(get_wire(color), user)
+ return TRUE
/datum/wires/proc/pulse_assembly(obj/item/assembly/S)
for(var/color in assemblies)
@@ -224,7 +251,7 @@
var/reveal_wires = FALSE
// Admin ghost can see a purpose of each wire.
- if(IsAdminGhost(user))
+ if(IsAdminGhost(user) || user.mind.skill_holder.get_skill_level(/datum/skill/level/job/wiring) >= req_knowledge)
reveal_wires = TRUE
// Same for anyone with an abductor multitool.
@@ -259,18 +286,16 @@
if("cut")
I = L.is_holding_tool_quality(TOOL_WIRECUTTER)
if(I || IsAdminGhost(usr))
- if(I && holder)
+ if(cut_color(target_wire) && I && holder)
I.play_tool_sound(holder, 20)
- cut_color(target_wire)
. = TRUE
else
to_chat(L, "You need wirecutters!")
if("pulse")
I = L.is_holding_tool_quality(TOOL_MULTITOOL)
if(I || IsAdminGhost(usr))
- if(I && holder)
+ if(pulse_color(target_wire, L) && I && holder)
I.play_tool_sound(holder, 20)
- pulse_color(target_wire, L)
. = TRUE
else
to_chat(L, "You need a multitool!")
diff --git a/code/datums/wires/airalarm.dm b/code/datums/wires/airalarm.dm
index 0c4715e27e..dae92d3c42 100644
--- a/code/datums/wires/airalarm.dm
+++ b/code/datums/wires/airalarm.dm
@@ -1,6 +1,7 @@
/datum/wires/airalarm
holder_type = /obj/machinery/airalarm
proper_name = "Air Alarm"
+ req_knowledge = JOB_SKILL_MASTER
/datum/wires/airalarm/New(atom/holder)
wires = list(
diff --git a/code/datums/wires/airlock.dm b/code/datums/wires/airlock.dm
index b6699540c3..c2b5db9540 100644
--- a/code/datums/wires/airlock.dm
+++ b/code/datums/wires/airlock.dm
@@ -52,10 +52,11 @@
/datum/wires/airlock/interactable(mob/user)
var/obj/machinery/door/airlock/A = holder
+ if(!A.panel_open)
+ return FALSE
if(!A.hasSiliconAccessInArea(user) && A.isElectrified() && A.shock(user, 100))
return FALSE
- if(A.panel_open)
- return TRUE
+ return TRUE
/datum/wires/airlock/get_status()
var/obj/machinery/door/airlock/A = holder
diff --git a/code/datums/wires/apc.dm b/code/datums/wires/apc.dm
index dc126f612e..997c77aa51 100644
--- a/code/datums/wires/apc.dm
+++ b/code/datums/wires/apc.dm
@@ -1,6 +1,7 @@
/datum/wires/apc
holder_type = /obj/machinery/power/apc
proper_name = "APC"
+ req_knowledge = JOB_SKILL_MASTER
/datum/wires/apc/New(atom/holder)
wires = list(
diff --git a/code/datums/wires/autolathe.dm b/code/datums/wires/autolathe.dm
index f83bab5910..01e87228d4 100644
--- a/code/datums/wires/autolathe.dm
+++ b/code/datums/wires/autolathe.dm
@@ -1,6 +1,7 @@
/datum/wires/autolathe
holder_type = /obj/machinery/autolathe
proper_name = "Autolathe"
+ req_knowledge = JOB_SKILL_EXPERT
/datum/wires/autolathe/New(atom/holder)
wires = list(
diff --git a/code/datums/wires/emitter.dm b/code/datums/wires/emitter.dm
index d0e1352c0e..6d82a7db5a 100644
--- a/code/datums/wires/emitter.dm
+++ b/code/datums/wires/emitter.dm
@@ -1,6 +1,8 @@
/datum/wires/emitter
holder_type = /obj/machinery/power/emitter
+ req_knowledge = JOB_SKILL_TRAINED
+ req_skill = JOB_SKILL_UNTRAINED
/datum/wires/emitter/New(atom/holder)
wires = list(WIRE_ZAP,WIRE_HACK)
diff --git a/code/datums/wires/explosive.dm b/code/datums/wires/explosive.dm
index 3d1ed8b746..dc4db9e85d 100644
--- a/code/datums/wires/explosive.dm
+++ b/code/datums/wires/explosive.dm
@@ -15,6 +15,7 @@
/datum/wires/explosive/c4
holder_type = /obj/item/grenade/plastic/c4
randomize = TRUE //Same behaviour since no wire actually disarms it
+ req_skill = JOB_SKILL_UNTRAINED
/datum/wires/explosive/c4/interactable(mob/user)
var/obj/item/grenade/plastic/c4/P = holder
@@ -29,6 +30,7 @@
/datum/wires/explosive/pizza
holder_type = /obj/item/pizzabox
randomize = TRUE
+ req_skill = JOB_SKILL_MASTER
/datum/wires/explosive/pizza/New(atom/holder)
wires = list(
diff --git a/code/datums/wires/microwave.dm b/code/datums/wires/microwave.dm
index 8c74abfa46..c6e6aa13b5 100644
--- a/code/datums/wires/microwave.dm
+++ b/code/datums/wires/microwave.dm
@@ -1,6 +1,8 @@
/datum/wires/microwave
holder_type = /obj/machinery/microwave
proper_name = "Microwave"
+ req_knowledge = JOB_SKILL_TRAINED
+ req_skill = JOB_SKILL_UNTRAINED
/datum/wires/microwave/New(atom/holder)
wires = list(
diff --git a/code/datums/wires/mulebot.dm b/code/datums/wires/mulebot.dm
index a452b46830..988487727b 100644
--- a/code/datums/wires/mulebot.dm
+++ b/code/datums/wires/mulebot.dm
@@ -1,6 +1,7 @@
/datum/wires/mulebot
holder_type = /mob/living/simple_animal/bot/mulebot
randomize = TRUE
+ req_knowledge = JOB_SKILL_MASTER
/datum/wires/mulebot/New(atom/holder)
wires = list(
diff --git a/code/datums/wires/particle_accelerator.dm b/code/datums/wires/particle_accelerator.dm
index af89f0d6f1..b782e589dd 100644
--- a/code/datums/wires/particle_accelerator.dm
+++ b/code/datums/wires/particle_accelerator.dm
@@ -1,6 +1,8 @@
/datum/wires/particle_accelerator/control_box
holder_type = /obj/machinery/particle_accelerator/control_box
proper_name = "Particle Accelerator"
+ req_knowledge = JOB_SKILL_EXPERT
+ req_skill = JOB_SKILL_TRAINED
/datum/wires/particle_accelerator/control_box/New(atom/holder)
wires = list(
diff --git a/code/datums/wires/r_n_d.dm b/code/datums/wires/r_n_d.dm
index ea61c1779e..25d3b73fe0 100644
--- a/code/datums/wires/r_n_d.dm
+++ b/code/datums/wires/r_n_d.dm
@@ -1,6 +1,7 @@
/datum/wires/rnd
holder_type = /obj/machinery/rnd
randomize = TRUE
+ req_knowledge = JOB_SKILL_EXPERT
/datum/wires/rnd/New(atom/holder)
wires = list(
diff --git a/code/datums/wires/radio.dm b/code/datums/wires/radio.dm
index a1118da6d7..5db6ddd186 100644
--- a/code/datums/wires/radio.dm
+++ b/code/datums/wires/radio.dm
@@ -1,6 +1,8 @@
/datum/wires/radio
holder_type = /obj/item/radio
proper_name = "Radio"
+ req_knowledge = JOB_SKILL_TRAINED
+ req_skill = JOB_SKILL_UNTRAINED
/datum/wires/radio/New(atom/holder)
wires = list(
diff --git a/code/datums/wires/robot.dm b/code/datums/wires/robot.dm
index aa7c3cc4c3..6ede0415da 100644
--- a/code/datums/wires/robot.dm
+++ b/code/datums/wires/robot.dm
@@ -1,6 +1,8 @@
/datum/wires/robot
holder_type = /mob/living/silicon/robot
randomize = TRUE
+ req_knowledge = JOB_SKILL_MASTER
+ req_skill = JOB_SKILL_TRAINED
/datum/wires/robot/New(atom/holder)
wires = list(
diff --git a/code/datums/wires/suit_storage_unit.dm b/code/datums/wires/suit_storage_unit.dm
index eb7781203b..4387d0dc76 100644
--- a/code/datums/wires/suit_storage_unit.dm
+++ b/code/datums/wires/suit_storage_unit.dm
@@ -1,6 +1,8 @@
/datum/wires/suit_storage_unit
holder_type = /obj/machinery/suit_storage_unit
proper_name = "Suit Storage Unit"
+ req_knowledge = JOB_SKILL_TRAINED
+ req_skill = JOB_SKILL_UNTRAINED
/datum/wires/suit_storage_unit/New(atom/holder)
wires = list(
diff --git a/code/datums/wires/syndicatebomb.dm b/code/datums/wires/syndicatebomb.dm
index 53c1a1b9bc..8e1cf5002f 100644
--- a/code/datums/wires/syndicatebomb.dm
+++ b/code/datums/wires/syndicatebomb.dm
@@ -1,6 +1,8 @@
/datum/wires/syndicatebomb
holder_type = /obj/machinery/syndicatebomb
randomize = TRUE
+ req_skill = JOB_SKILL_EXPERT //good luck, wannabe hero.
+
/datum/wires/syndicatebomb/New(atom/holder)
wires = list(
diff --git a/code/datums/wires/tesla_coil.dm b/code/datums/wires/tesla_coil.dm
index fa858a9a8f..68fe8e72fc 100644
--- a/code/datums/wires/tesla_coil.dm
+++ b/code/datums/wires/tesla_coil.dm
@@ -2,6 +2,7 @@
/datum/wires/tesla_coil
randomize = 1 //Only one wire don't need blueprints
holder_type = /obj/machinery/power/tesla_coil
+ req_knowledge = JOB_SKILL_TRAINED
/datum/wires/tesla_coil/New(atom/holder)
wires = list(WIRE_ZAP)
diff --git a/code/datums/wires/vending.dm b/code/datums/wires/vending.dm
index 114791e873..6c7e59c24c 100644
--- a/code/datums/wires/vending.dm
+++ b/code/datums/wires/vending.dm
@@ -1,6 +1,7 @@
/datum/wires/vending
holder_type = /obj/machinery/vending
proper_name = "Vending Unit"
+ req_knowledge = JOB_SKILL_EXPERT
/datum/wires/vending/New(atom/holder)
wires = list(
diff --git a/code/game/machinery/camera/camera_assembly.dm b/code/game/machinery/camera/camera_assembly.dm
index 1766391709..b6af0600e4 100644
--- a/code/game/machinery/camera/camera_assembly.dm
+++ b/code/game/machinery/camera/camera_assembly.dm
@@ -48,8 +48,7 @@
if(2)
// State 2
if(istype(W, /obj/item/stack/cable_coil))
- var/obj/item/stack/cable_coil/C = W
- if(C.use(2))
+ if(W.use_tool(src, user, 0, 2))
to_chat(user, "You add wires to the assembly.")
state = 3
else
diff --git a/code/game/machinery/constructable_frame.dm b/code/game/machinery/constructable_frame.dm
index b007bc0161..5ee8223625 100644
--- a/code/game/machinery/constructable_frame.dm
+++ b/code/game/machinery/constructable_frame.dm
@@ -83,7 +83,6 @@
if(istype(P, /obj/item/stack/cable_coil))
if(!P.tool_start_check(user, amount=5))
return
-
to_chat(user, "You start to add cables to the frame...")
if(P.use_tool(src, user, 20, volume=50, amount=5))
to_chat(user, "You add cables to the frame.")
diff --git a/code/game/machinery/doors/firedoor.dm b/code/game/machinery/doors/firedoor.dm
index 1b83bb4b96..28b9796bb6 100644
--- a/code/game/machinery/doors/firedoor.dm
+++ b/code/game/machinery/doors/firedoor.dm
@@ -400,12 +400,11 @@
"You begin adding wires to [src]...")
playsound(get_turf(src), 'sound/items/deconstruct.ogg', 50, 1)
if(do_after(user, 60, target = src))
- if(constructionStep != CONSTRUCTION_GUTTED || B.get_amount() < 5 || !B)
+ if(constructionStep != CONSTRUCTION_GUTTED || !B.use_tool(src, user, 0, 5))
return
user.visible_message("[user] adds wires to [src].", \
"You wire [src].")
playsound(get_turf(src), 'sound/items/deconstruct.ogg', 50, 1)
- B.use(5)
constructionStep = CONSTRUCTION_WIRES_EXPOSED
update_icon()
return
diff --git a/code/game/machinery/firealarm.dm b/code/game/machinery/firealarm.dm
index f4f308c490..4a0042e1da 100644
--- a/code/game/machinery/firealarm.dm
+++ b/code/game/machinery/firealarm.dm
@@ -205,11 +205,9 @@
return
if(1)
if(istype(W, /obj/item/stack/cable_coil))
- var/obj/item/stack/cable_coil/coil = W
- if(coil.get_amount() < 5)
+ if(!W.use_tool(src, user, 0, 5))
to_chat(user, "You need more cable for this!")
else
- coil.use(5)
buildstage = 2
to_chat(user, "You wire \the [src].")
update_icon()
diff --git a/code/game/machinery/shieldgen.dm b/code/game/machinery/shieldgen.dm
index 34db7b0e08..6793d361b5 100644
--- a/code/game/machinery/shieldgen.dm
+++ b/code/game/machinery/shieldgen.dm
@@ -160,10 +160,7 @@
to_chat(user, "You need one length of cable to repair [src]!")
return
to_chat(user, "You begin to replace the wires...")
- if(do_after(user, 30, target = src))
- if(coil.get_amount() < 1)
- return
- coil.use(1)
+ if(W.use_tool(src, user, 30, 1))
obj_integrity = max_integrity
stat &= ~BROKEN
to_chat(user, "You repair \the [src].")
diff --git a/code/game/mecha/mecha_defense.dm b/code/game/mecha/mecha_defense.dm
index 581283d339..53768fe9a9 100644
--- a/code/game/mecha/mecha_defense.dm
+++ b/code/game/mecha/mecha_defense.dm
@@ -220,8 +220,7 @@
return
else if(istype(W, /obj/item/stack/cable_coil))
if(state == 3 && (internal_damage & MECHA_INT_SHORT_CIRCUIT))
- var/obj/item/stack/cable_coil/CC = W
- if(CC.use(2))
+ if(W.use_tool(src, user, 0, 2))
clearInternalDamage(MECHA_INT_SHORT_CIRCUIT)
to_chat(user, "You replace the fused wires.")
else
diff --git a/code/game/objects/items.dm b/code/game/objects/items.dm
index c8e5b15f72..9d3cc8293b 100644
--- a/code/game/objects/items.dm
+++ b/code/game/objects/items.dm
@@ -132,6 +132,12 @@ GLOBAL_VAR_INIT(rpg_loot_items, FALSE)
var/list/grind_results //A reagent list containing the reagents this item produces when ground up in a grinder - this can be an empty list to allow for reagent transferring only
var/list/juice_results //A reagent list containing blah blah... but when JUICED in a grinder!
+ ///Skills vars
+ var/used_skills //path/s of skill/s exercised when using this item.
+ var/skill_flags = NONE //better defines which tasks the the skill/s is/are exercised on.
+ var/skill_difficulty = THRESHOLD_COMPETENT //how difficult it's to use this item in general.
+ var/skill_gain = DEF_SKILL_GAIN //base skill value gain from using this item.
+
/obj/item/Initialize()
if (attack_verb)
@@ -783,7 +789,7 @@ GLOBAL_VAR_INIT(rpg_loot_items, FALSE)
// Called when a mob tries to use the item as a tool.
// Handles most checks.
-/obj/item/proc/use_tool(atom/target, mob/living/user, delay, amount=0, volume=0, datum/callback/extra_checks)
+/obj/item/proc/use_tool(atom/target, mob/living/user, delay, amount=0, volume=0, datum/callback/extra_checks, skill_gain_mult = 1, max_level = INFINITY)
// No delay means there is no start message, and no reason to call tool_start_check before use_tool.
// Run the start check here so we wouldn't have to call it manually.
if(!delay && !tool_start_check(user, amount))
@@ -795,6 +801,9 @@ GLOBAL_VAR_INIT(rpg_loot_items, FALSE)
play_tool_sound(target, volume)
if(delay)
+ if(user.mind && used_skills && skill_flags & SKILL_USE_TOOL)
+ LIST_SKILL_MODIFIER(used_skills, user.mind.skill_holder, delay, skill_difficulty)
+
// Create a callback with checks that would be called every tick by do_after.
var/datum/callback/tool_check = CALLBACK(src, .proc/tool_check_callback, user, amount, extra_checks)
@@ -819,6 +828,13 @@ GLOBAL_VAR_INIT(rpg_loot_items, FALSE)
if(delay >= MIN_TOOL_SOUND_DELAY)
play_tool_sound(target, volume)
+ if(user.mind && used_skills && skill_flags & SKILL_TRAINING_TOOL && skill_gain_mult)
+ if(!islist(used_skills))
+ user.mind.skill_holder.boost_skill_value_to(used_skills, skill_gain*skill_gain_mult, GET_STANDARD_LVL(max_level))
+ else
+ for(var/skill in used_skills)
+ user.mind.skill_holder.boost_skill_value_to(skill, skill_gain*skill_gain_mult, GET_STANDARD_LVL(max_level))
+
return TRUE
// Called before use_tool if there is a delay, or by use_tool if there isn't.
diff --git a/code/game/objects/items/grenades/chem_grenade.dm b/code/game/objects/items/grenades/chem_grenade.dm
index acd71c57ac..91dde2899d 100644
--- a/code/game/objects/items/grenades/chem_grenade.dm
+++ b/code/game/objects/items/grenades/chem_grenade.dm
@@ -97,8 +97,7 @@
to_chat(user, "You add [A] to the [initial(name)] assembly.")
else if(stage == EMPTY && istype(I, /obj/item/stack/cable_coil))
- var/obj/item/stack/cable_coil/C = I
- if (C.use(1))
+ if (I.use_tool(src, user, 0, 1, max_level = JOB_SKILL_BASIC))
det_time = 50 // In case the cable_coil was removed and readded.
stage_change(WIRED)
to_chat(user, "You rig the [initial(name)] assembly.")
diff --git a/code/game/objects/items/stacks/sheets/glass.dm b/code/game/objects/items/stacks/sheets/glass.dm
index c8ee6b05c7..1648598c4e 100644
--- a/code/game/objects/items/stacks/sheets/glass.dm
+++ b/code/game/objects/items/stacks/sheets/glass.dm
@@ -69,7 +69,7 @@ GLOBAL_LIST_INIT(glass_recipes, list ( \
if (get_amount() < 1 || CC.get_amount() < 5)
to_chat(user, "You attach wire to the [name].")
var/obj/item/stack/light_w/new_tile = new(user.loc)
diff --git a/code/game/objects/structures/ai_core.dm b/code/game/objects/structures/ai_core.dm
index 3e35c12314..1090bae9f8 100644
--- a/code/game/objects/structures/ai_core.dm
+++ b/code/game/objects/structures/ai_core.dm
@@ -131,7 +131,7 @@
if(C.get_amount() >= 5)
playsound(loc, 'sound/items/deconstruct.ogg', 50, 1)
to_chat(user, "You start to add cables to the frame...")
- if(do_after(user, 20, target = src) && state == SCREWED_CORE && C.use(5))
+ if(do_after(user, 20, target = src) && state == SCREWED_CORE && C.use_tool(src, user, 0, 5))
to_chat(user, "You add cables to the frame.")
state = CABLED_CORE
update_icon()
diff --git a/code/game/objects/structures/barsigns.dm b/code/game/objects/structures/barsigns.dm
index d2fff9649d..821409c26b 100644
--- a/code/game/objects/structures/barsigns.dm
+++ b/code/game/objects/structures/barsigns.dm
@@ -84,7 +84,6 @@
panel_open = FALSE
else if(istype(I, /obj/item/stack/cable_coil) && panel_open)
- var/obj/item/stack/cable_coil/C = I
if(obj_flags & EMAGGED) //Emagged, not broken by EMP
to_chat(user, "Sign has been damaged beyond repair!")
return
@@ -92,7 +91,7 @@
to_chat(user, "This sign is functioning properly!")
return
- if(C.use(2))
+ if(I.use_tool(src, user, 0, 2))
to_chat(user, "You replace the burnt wiring.")
broken = FALSE
else
diff --git a/code/game/objects/structures/windoor_assembly.dm b/code/game/objects/structures/windoor_assembly.dm
index 04c3909696..4544cab3da 100644
--- a/code/game/objects/structures/windoor_assembly.dm
+++ b/code/game/objects/structures/windoor_assembly.dm
@@ -169,8 +169,7 @@
if(do_after(user, 40, target = src))
if(!src || !anchored || src.state != "01")
return
- var/obj/item/stack/cable_coil/CC = W
- if(!CC.use(1))
+ if(!W.use_tool(src, user, 0, 1))
to_chat(user, "You need more cable to do this!")
return
to_chat(user, "You wire the windoor.")
diff --git a/code/modules/atmospherics/machinery/airalarm.dm b/code/modules/atmospherics/machinery/airalarm.dm
index a4617462fe..0838c7b091 100644
--- a/code/modules/atmospherics/machinery/airalarm.dm
+++ b/code/modules/atmospherics/machinery/airalarm.dm
@@ -786,9 +786,8 @@
return
user.visible_message("[user.name] wires the air alarm.", \
"You start wiring the air alarm...")
- if (do_after(user, 20, target = src))
- if (cable.get_amount() >= 5 && buildstage == 1)
- cable.use(5)
+ if (W.use_tool(src, user, 20, 5))
+ if (buildstage == 1)
to_chat(user, "You wire the air alarm.")
wires.repair()
aidisabled = 0
diff --git a/code/modules/clothing/under/_under.dm b/code/modules/clothing/under/_under.dm
index 0d08b59be9..11fdb47eb6 100644
--- a/code/modules/clothing/under/_under.dm
+++ b/code/modules/clothing/under/_under.dm
@@ -32,7 +32,7 @@
/obj/item/clothing/under/attackby(obj/item/I, mob/user, params)
if((has_sensor == BROKEN_SENSORS) && istype(I, /obj/item/stack/cable_coil))
var/obj/item/stack/cable_coil/C = I
- C.use(1)
+ I.use_tool(src, user, 0, 1)
has_sensor = HAS_SENSORS
to_chat(user,"You repair the suit sensors on [src] with [C].")
return 1
diff --git a/code/modules/hydroponics/plant_genes.dm b/code/modules/hydroponics/plant_genes.dm
index ef2eff707f..b18f4396d6 100644
--- a/code/modules/hydroponics/plant_genes.dm
+++ b/code/modules/hydroponics/plant_genes.dm
@@ -392,8 +392,7 @@
/datum/plant_gene/trait/battery/on_attackby(obj/item/reagent_containers/food/snacks/grown/G, obj/item/I, mob/user)
if(istype(I, /obj/item/stack/cable_coil))
- var/obj/item/stack/cable_coil/C = I
- if(C.use(5))
+ if(I.use_tool(src, user, 0, 5, max_level = JOB_SKILL_EXPERT))
to_chat(user, "You add some cable to [G] and slide it inside the battery encasing.")
var/obj/item/stock_parts/cell/potato/pocell = new /obj/item/stock_parts/cell/potato(user.loc)
pocell.icon_state = G.icon_state
diff --git a/code/modules/jobs/job_types/_job.dm b/code/modules/jobs/job_types/_job.dm
index e40c278e92..e10f009cec 100644
--- a/code/modules/jobs/job_types/_job.dm
+++ b/code/modules/jobs/job_types/_job.dm
@@ -174,7 +174,7 @@
if(!starting_skills)
return
for(var/skill in starting_skills)
- M.skill_holder.boost_skill_value_to(skill, starting_skills[skill])
+ M.skill_holder.boost_skill_value_to(skill, starting_skills[skill], TRUE) //silent
// do wipe affinities though
M.skill_holder.skill_affinities = list()
for(var/skill in skill_affinities)
diff --git a/code/modules/jobs/job_types/ai.dm b/code/modules/jobs/job_types/ai.dm
index efe574ab66..1395b90b39 100644
--- a/code/modules/jobs/job_types/ai.dm
+++ b/code/modules/jobs/job_types/ai.dm
@@ -17,6 +17,8 @@
var/do_special_check = TRUE
threat = 5
+ starting_skills = list(/datum/skill/level/job/wiring = GET_STANDARD_LVL(JOB_SKILL_BASIC))
+
/datum/job/ai/equip(mob/living/carbon/human/H, visualsOnly, announce, latejoin, datum/outfit/outfit_override, client/preference_source = null)
if(visualsOnly)
CRASH("dynamic preview is unsupported")
diff --git a/code/modules/jobs/job_types/atmospheric_technician.dm b/code/modules/jobs/job_types/atmospheric_technician.dm
index 019e50799e..770cbe3bda 100644
--- a/code/modules/jobs/job_types/atmospheric_technician.dm
+++ b/code/modules/jobs/job_types/atmospheric_technician.dm
@@ -17,6 +17,10 @@
ACCESS_EXTERNAL_AIRLOCKS, ACCESS_CONSTRUCTION, ACCESS_ATMOSPHERICS, ACCESS_MINERAL_STOREROOM)
minimal_access = list(ACCESS_ATMOSPHERICS, ACCESS_MAINT_TUNNELS, ACCESS_EXTERNAL_AIRLOCKS, ACCESS_ENGINE,
ACCESS_ENGINE_EQUIP, ACCESS_EMERGENCY_STORAGE, ACCESS_CONSTRUCTION, ACCESS_MINERAL_STOREROOM)
+
+ starting_skills = list(/datum/skill/level/job/wiring = GET_STANDARD_LVL(JOB_SKILL_BASIC))
+ skill_affinities = list(/datum/skill/level/job/wiring = STARTING_SKILL_AFFINITY_WIRING_ENGI_ROBO)
+
display_order = JOB_DISPLAY_ORDER_ATMOSPHERIC_TECHNICIAN
threat = 0.5
diff --git a/code/modules/jobs/job_types/chief_engineer.dm b/code/modules/jobs/job_types/chief_engineer.dm
index 0d70e872ac..bd4732f3c3 100644
--- a/code/modules/jobs/job_types/chief_engineer.dm
+++ b/code/modules/jobs/job_types/chief_engineer.dm
@@ -27,6 +27,9 @@
ACCESS_HEADS, ACCESS_CONSTRUCTION, ACCESS_SEC_DOORS, ACCESS_MINISAT,
ACCESS_CE, ACCESS_RC_ANNOUNCE, ACCESS_KEYCARD_AUTH, ACCESS_TCOMSAT, ACCESS_MINERAL_STOREROOM)
+ starting_skills = list(/datum/skill/level/job/wiring = GET_STANDARD_LVL(JOB_SKILL_TRAINED))
+ skill_affinities = list(/datum/skill/level/job/wiring = STARTING_SKILL_AFFINITY_WIRING_ENGI_ROBO)
+
display_order = JOB_DISPLAY_ORDER_CHIEF_ENGINEER
blacklisted_quirks = list(/datum/quirk/mute, /datum/quirk/brainproblems, /datum/quirk/paraplegic, /datum/quirk/insanity)
threat = 2
diff --git a/code/modules/jobs/job_types/cyborg.dm b/code/modules/jobs/job_types/cyborg.dm
index f84bd0d8d6..d88d888aec 100644
--- a/code/modules/jobs/job_types/cyborg.dm
+++ b/code/modules/jobs/job_types/cyborg.dm
@@ -12,6 +12,8 @@
exp_requirements = 120
exp_type = EXP_TYPE_CREW
+ starting_skills = list(/datum/skill/level/job/wiring = GET_STANDARD_LVL(JOB_SKILL_BASIC))
+
display_order = JOB_DISPLAY_ORDER_CYBORG
/datum/job/cyborg/equip(mob/living/carbon/human/H, visualsOnly = FALSE, announce = TRUE, latejoin = FALSE, datum/outfit/outfit_override = null, client/preference_source = null)
diff --git a/code/modules/jobs/job_types/roboticist.dm b/code/modules/jobs/job_types/roboticist.dm
index f9f1d20d3b..d9e891f057 100644
--- a/code/modules/jobs/job_types/roboticist.dm
+++ b/code/modules/jobs/job_types/roboticist.dm
@@ -16,6 +16,10 @@
access = list(ACCESS_ROBOTICS, ACCESS_TOX, ACCESS_TOX_STORAGE, ACCESS_TECH_STORAGE, ACCESS_MORGUE, ACCESS_RESEARCH, ACCESS_MINERAL_STOREROOM, ACCESS_XENOBIOLOGY, ACCESS_GENETICS)
minimal_access = list(ACCESS_ROBOTICS, ACCESS_TECH_STORAGE, ACCESS_MORGUE, ACCESS_RESEARCH, ACCESS_MINERAL_STOREROOM)
+ starting_skills = list(/datum/skill/level/job/wiring = GET_STANDARD_LVL(JOB_SKILL_TRAINED))
+ skill_affinities = list(/datum/skill/level/job/wiring = STARTING_SKILL_AFFINITY_WIRING_ENGI_ROBO)
+
+
display_order = JOB_DISPLAY_ORDER_ROBOTICIST
threat = 1
diff --git a/code/modules/jobs/job_types/station_engineer.dm b/code/modules/jobs/job_types/station_engineer.dm
index 66ddc90e9b..c5280d72db 100644
--- a/code/modules/jobs/job_types/station_engineer.dm
+++ b/code/modules/jobs/job_types/station_engineer.dm
@@ -18,6 +18,9 @@
minimal_access = list(ACCESS_ENGINE, ACCESS_ENGINE_EQUIP, ACCESS_TECH_STORAGE, ACCESS_MAINT_TUNNELS,
ACCESS_EXTERNAL_AIRLOCKS, ACCESS_CONSTRUCTION, ACCESS_TCOMSAT, ACCESS_MINERAL_STOREROOM)
+ starting_skills = list(/datum/skill/level/job/wiring = GET_STANDARD_LVL(JOB_SKILL_TRAINED))
+ skill_affinities = list(/datum/skill/level/job/wiring = STARTING_SKILL_AFFINITY_WIRING_ENGI_ROBO)
+
display_order = JOB_DISPLAY_ORDER_STATION_ENGINEER
threat = 1
diff --git a/code/modules/mining/ores_coins.dm b/code/modules/mining/ores_coins.dm
index c7c34b0389..a0c2cc73e8 100644
--- a/code/modules/mining/ores_coins.dm
+++ b/code/modules/mining/ores_coins.dm
@@ -364,12 +364,11 @@ GLOBAL_LIST_INIT(sand_recipes, list(\
/obj/item/coin/attackby(obj/item/W, mob/user, params)
if(istype(W, /obj/item/stack/cable_coil))
- var/obj/item/stack/cable_coil/CC = W
if(string_attached)
to_chat(user, "There already is a string attached to this coin!")
return
- if (CC.use(1))
+ if (W.use_tool(src, user, 0, 1, max_level = JOB_SKILL_BASIC))
add_overlay("coin_string_overlay")
string_attached = 1
to_chat(user, "You attach a string to the coin.")
diff --git a/code/modules/mob/living/carbon/carbon_defense.dm b/code/modules/mob/living/carbon/carbon_defense.dm
index 9c4f0316b7..42e702c5d9 100644
--- a/code/modules/mob/living/carbon/carbon_defense.dm
+++ b/code/modules/mob/living/carbon/carbon_defense.dm
@@ -9,7 +9,7 @@
if(istype(src.glasses, /obj/item/clothing/glasses)) //glasses
var/obj/item/clothing/glasses/GFP = src.glasses
number += GFP.flash_protect
-
+
if(istype(src.wear_mask, /obj/item/clothing/mask)) //mask
var/obj/item/clothing/mask/MFP = src.wear_mask
number += MFP.flash_protect
@@ -77,15 +77,13 @@
SEND_SIGNAL(src, COMSIG_ADD_MOOD_EVENT, "embedded", /datum/mood_event/embedded)
/mob/living/carbon/attacked_by(obj/item/I, mob/living/user)
- //CIT CHANGES START HERE - combatmode and resting checks
- var/totitemdamage = I.force
+ var/totitemdamage = pre_attacked_by(I, user, TRUE)
if(!(user.combat_flags & COMBAT_FLAG_COMBAT_ACTIVE))
totitemdamage *= 0.5
if(!CHECK_MOBILITY(user, MOBILITY_STAND))
totitemdamage *= 0.5
if(!(combat_flags & COMBAT_FLAG_COMBAT_ACTIVE))
totitemdamage *= 1.5
- //CIT CHANGES END HERE
var/impacting_zone = (user == src)? check_zone(user.zone_selected) : ran_zone(user.zone_selected)
if((user != src) && (run_block(I, totitemdamage, "the [I]", ATTACK_TYPE_MELEE, I.armour_penetration, user, impacting_zone) & BLOCK_SUCCESS))
return FALSE
diff --git a/code/modules/mob/living/carbon/human/species.dm b/code/modules/mob/living/carbon/human/species.dm
index 683c3e18fd..124f862244 100644
--- a/code/modules/mob/living/carbon/human/species.dm
+++ b/code/modules/mob/living/carbon/human/species.dm
@@ -1712,9 +1712,10 @@ GLOBAL_LIST_EMPTY(roundstart_race_names)
disarm(M, H, attacker_style)
/datum/species/proc/spec_attacked_by(obj/item/I, mob/living/user, obj/item/bodypart/affecting, intent, mob/living/carbon/human/H)
+ var/totitemdamage = H.pre_attacked_by(I, user, TRUE)
// Allows you to put in item-specific reactions based on species
if(user != H)
- if(H.run_block(I, I.force, "the [I.name]", ATTACK_TYPE_MELEE, I.armour_penetration, user, affecting.body_zone) & BLOCK_SUCCESS)
+ if(H.run_block(I, totitemdamage, "the [I.name]", ATTACK_TYPE_MELEE, I.armour_penetration, user, affecting.body_zone) & BLOCK_SUCCESS)
return 0
if(H.check_martial_melee_block())
H.visible_message("[H] blocks [I]!")
@@ -1730,16 +1731,6 @@ GLOBAL_LIST_EMPTY(roundstart_race_names)
var/armor_block = H.run_armor_check(affecting, "melee", "Your armor has protected your [hit_area].", "Your armor has softened a hit to your [hit_area].",I.armour_penetration)
armor_block = min(90,armor_block) //cap damage reduction at 90%
var/Iforce = I.force //to avoid runtimes on the forcesay checks at the bottom. Some items might delete themselves if you drop them. (stunning yourself, ninja swords)
- //CIT CHANGES START HERE - combatmode and resting checks
- var/totitemdamage = I.force
- if(!(user.combat_flags & COMBAT_FLAG_COMBAT_ACTIVE))
- totitemdamage *= 0.5
- if(!CHECK_MOBILITY(user, MOBILITY_STAND))
- totitemdamage *= 0.5
- if(istype(H))
- if(!(H.combat_flags & COMBAT_FLAG_COMBAT_ACTIVE))
- totitemdamage *= 1.5
- //CIT CHANGES END HERE
var/weakness = H.check_weakness(I, user)
apply_damage(totitemdamage * weakness, I.damtype, def_zone, armor_block, H) //CIT CHANGE - replaces I.force with totitemdamage
@@ -1747,7 +1738,7 @@ GLOBAL_LIST_EMPTY(roundstart_race_names)
I.do_stagger_action(H, user)
- if(!I.force)
+ if(!totitemdamage)
return 0 //item force is zero
//dismemberment
diff --git a/code/modules/mob/living/silicon/robot/robot.dm b/code/modules/mob/living/silicon/robot/robot.dm
index b389e386d4..508dc67dd7 100644
--- a/code/modules/mob/living/silicon/robot/robot.dm
+++ b/code/modules/mob/living/silicon/robot/robot.dm
@@ -427,24 +427,23 @@
else if(istype(W, /obj/item/stack/cable_coil) && wiresexposed)
user.changeNext_move(CLICK_CD_MELEE)
- var/obj/item/stack/cable_coil/coil = W
if (getFireLoss() > 0 || getToxLoss() > 0)
- if(src == user && coil.use(1))
+ if(src == user)
to_chat(user, "You start fixing yourself...")
- if(!do_after(user, 50, target = src))
+ if(!W.use_tool(src, user, 50, 1, max_level = JOB_SKILL_TRAINED))
+ to_chat(user, "You need more cable to repair [src]!")
return
adjustFireLoss(-10)
adjustToxLoss(-10)
- if (coil.use(1))
+ else
to_chat(user, "You start fixing [src]...")
- if(!do_after(user, 30, target = src))
+ if(!W.use_tool(src, user, 30, 1))
+ to_chat(user, "You need more cable to repair [src]!")
return
adjustFireLoss(-30)
adjustToxLoss(-30)
updatehealth()
user.visible_message("[user] has fixed some of the burnt wires on [src].", "You fix some of the burnt wires on [src].")
- else
- to_chat(user, "You need more cable to repair [src]!")
else
to_chat(user, "The wires seem fine, there's no need to fix them.")
diff --git a/code/modules/mob/living/simple_animal/bot/construction.dm b/code/modules/mob/living/simple_animal/bot/construction.dm
index 6c4680a477..08fffc2cf8 100644
--- a/code/modules/mob/living/simple_animal/bot/construction.dm
+++ b/code/modules/mob/living/simple_animal/bot/construction.dm
@@ -147,9 +147,8 @@
to_chat(user, "You need one length of cable to wire the ED-209!")
return
to_chat(user, "You start to wire [src]...")
- if(do_after(user, 40, target = src))
- if(coil.get_amount() >= 1 && build_step == 6)
- coil.use(1)
+ if(coil.use_tool(src, user, 40, 1))
+ if(build_step == 6)
to_chat(user, "You wire [src].")
name = "wired ED-209 assembly"
build_step++
diff --git a/code/modules/modular_computers/hardware/_hardware.dm b/code/modules/modular_computers/hardware/_hardware.dm
index 744db1572f..37f3fc434e 100644
--- a/code/modules/modular_computers/hardware/_hardware.dm
+++ b/code/modules/modular_computers/hardware/_hardware.dm
@@ -41,11 +41,10 @@
// Cable coil. Works as repair method, but will probably require multiple applications and more cable.
if(istype(I, /obj/item/stack/cable_coil))
- var/obj/item/stack/S = I
if(obj_integrity == max_integrity)
to_chat(user, "\The [src] doesn't seem to require repairs.")
return 1
- if(S.use(1))
+ if(I.use_tool(src, user, 0, 1))
to_chat(user, "You patch up \the [src] with a bit of \the [I].")
obj_integrity = min(obj_integrity + 10, max_integrity)
return 1
diff --git a/code/modules/power/apc.dm b/code/modules/power/apc.dm
index 7eabeafcb1..977a6484b7 100644
--- a/code/modules/power/apc.dm
+++ b/code/modules/power/apc.dm
@@ -593,19 +593,15 @@
user.visible_message("[user.name] adds cables to the APC frame.", \
"You start adding cables to the APC frame...")
playsound(src.loc, 'sound/items/deconstruct.ogg', 50, 1)
- if(do_after(user, 20, target = src))
- if (C.get_amount() < 10 || !C)
+ if(C.use_tool(src, user, 20, 10) && !terminal && opened && has_electronics)
+ var/turf/T = get_turf(src)
+ var/obj/structure/cable/N = T.get_cable_node()
+ if (prob(50) && electrocute_mob(usr, N, N, 1, TRUE))
+ do_sparks(5, TRUE, src)
return
- if (C.get_amount() >= 10 && !terminal && opened && has_electronics)
- var/turf/T = get_turf(src)
- var/obj/structure/cable/N = T.get_cable_node()
- if (prob(50) && electrocute_mob(usr, N, N, 1, TRUE))
- do_sparks(5, TRUE, src)
- return
- C.use(10)
- to_chat(user, "You add cables to the APC frame.")
- make_terminal()
- terminal.connect_to_network()
+ to_chat(user, "You add cables to the APC frame.")
+ make_terminal()
+ terminal.connect_to_network()
else if (istype(W, /obj/item/electronics/apc) && opened)
if (has_electronics)
to_chat(user, "There is already a board inside the [src]!")
diff --git a/code/modules/power/cable.dm b/code/modules/power/cable.dm
index eecc1394de..1e2650d17f 100644
--- a/code/modules/power/cable.dm
+++ b/code/modules/power/cable.dm
@@ -495,6 +495,8 @@ By design, d1 is the smallest direction and d2 is the highest
full_w_class = WEIGHT_CLASS_SMALL
grind_results = list(/datum/reagent/copper = 2) //2 copper per cable in the coil
usesound = 'sound/items/deconstruct.ogg'
+ used_skills = /datum/skill/level/job/wiring
+ skill_flags = SKILL_USE_TOOL|SKILL_TRAINING_TOOL
/obj/item/stack/cable_coil/cyborg
is_cyborg = 1
@@ -576,8 +578,6 @@ By design, d1 is the smallest direction and d2 is the highest
amount += extra
update_icon()
-
-
///////////////////////////////////////////////
// Cable laying procedures
//////////////////////////////////////////////
diff --git a/code/modules/power/floodlight.dm b/code/modules/power/floodlight.dm
index 9fb1a1ce7d..e0b3f5f316 100644
--- a/code/modules/power/floodlight.dm
+++ b/code/modules/power/floodlight.dm
@@ -15,8 +15,7 @@
state = FLOODLIGHT_NEEDS_WIRES
desc = "A bare metal frame looking vaguely like a floodlight. Requires wiring."
else if(istype(O, /obj/item/stack/cable_coil) && (state == FLOODLIGHT_NEEDS_WIRES))
- var/obj/item/stack/S = O
- if(S.use(5))
+ if(O.use_tool(src, user, 0, 5))
to_chat(user, "You wire [src].")
name = "wired [name]"
desc = "A bare metal frame looking vaguely like a floodlight. Requires securing with a screwdriver."
diff --git a/code/modules/power/lighting.dm b/code/modules/power/lighting.dm
index 7ad9f3a6ce..2f7f9ad729 100644
--- a/code/modules/power/lighting.dm
+++ b/code/modules/power/lighting.dm
@@ -118,8 +118,7 @@
return
if(istype(W, /obj/item/stack/cable_coil))
- var/obj/item/stack/cable_coil/coil = W
- if(coil.use(1))
+ if(W.use_tool(src, user, 0, 1, max_level = JOB_SKILL_TRAINED))
icon_state = "[fixture_type]-construct-stage2"
stage = 2
user.visible_message("[user.name] adds wires to [src].", \
diff --git a/code/modules/power/singularity/particle_accelerator/particle_accelerator.dm b/code/modules/power/singularity/particle_accelerator/particle_accelerator.dm
index 59792b6539..e33116b02c 100644
--- a/code/modules/power/singularity/particle_accelerator/particle_accelerator.dm
+++ b/code/modules/power/singularity/particle_accelerator/particle_accelerator.dm
@@ -79,8 +79,7 @@
construction_state = PA_CONSTRUCTION_UNSECURED
did_something = TRUE
else if(istype(W, /obj/item/stack/cable_coil))
- var/obj/item/stack/cable_coil/CC = W
- if(CC.use(1))
+ if(W.use_tool(src, user, 0, 1))
user.visible_message("[user.name] adds wires to the [name].", \
"You add some wires.")
construction_state = PA_CONSTRUCTION_PANEL_OPEN
diff --git a/code/modules/power/singularity/particle_accelerator/particle_control.dm b/code/modules/power/singularity/particle_accelerator/particle_control.dm
index af83f7ebca..957ceb986f 100644
--- a/code/modules/power/singularity/particle_accelerator/particle_control.dm
+++ b/code/modules/power/singularity/particle_accelerator/particle_control.dm
@@ -288,8 +288,7 @@
construction_state = PA_CONSTRUCTION_UNSECURED
did_something = TRUE
else if(istype(W, /obj/item/stack/cable_coil))
- var/obj/item/stack/cable_coil/CC = W
- if(CC.use(1))
+ if(W.use_tool(src, user, 0, 1))
user.visible_message("[user.name] adds wires to the [name].", \
"You add some wires.")
construction_state = PA_CONSTRUCTION_PANEL_OPEN
diff --git a/code/modules/power/smes.dm b/code/modules/power/smes.dm
index 41ed28f0a5..85999707a2 100644
--- a/code/modules/power/smes.dm
+++ b/code/modules/power/smes.dm
@@ -125,15 +125,12 @@
to_chat(user, "You start building the power terminal...")
playsound(src.loc, 'sound/items/deconstruct.ogg', 50, 1)
- if(do_after(user, 20, target = src) && C.get_amount() >= 10)
- if(C.get_amount() < 10 || !C)
- return
+ if(C.use_tool(src, user, 20, 10))
var/obj/structure/cable/N = T.get_cable_node() //get the connecting node cable, if there's one
if (prob(50) && electrocute_mob(usr, N, N, 1, TRUE)) //animate the electrocution if uncautious and unlucky
do_sparks(5, TRUE, src)
return
- C.use(10)
user.visible_message(\
"[user.name] has built a power terminal.",\
"You build the power terminal.")
diff --git a/code/modules/projectiles/guns/ballistic/revolver.dm b/code/modules/projectiles/guns/ballistic/revolver.dm
index b43ac3057a..afe2b9ca3e 100644
--- a/code/modules/projectiles/guns/ballistic/revolver.dm
+++ b/code/modules/projectiles/guns/ballistic/revolver.dm
@@ -331,8 +331,7 @@
/obj/item/gun/ballistic/revolver/doublebarrel/improvised/attackby(obj/item/A, mob/user, params)
..()
if(istype(A, /obj/item/stack/cable_coil) && !sawn_off)
- var/obj/item/stack/cable_coil/C = A
- if(C.use(10))
+ if(A.use_tool(src, user, 0, 10, max_level = JOB_SKILL_BASIC))
slot_flags = ITEM_SLOT_BACK
to_chat(user, "You tie the lengths of cable to the shotgun, making a sling.")
slung = TRUE
diff --git a/code/modules/surgery/bodyparts/robot_bodyparts.dm b/code/modules/surgery/bodyparts/robot_bodyparts.dm
index a3f8b883b4..39e660203e 100644
--- a/code/modules/surgery/bodyparts/robot_bodyparts.dm
+++ b/code/modules/surgery/bodyparts/robot_bodyparts.dm
@@ -19,7 +19,7 @@
flags_1 = CONDUCT_1
icon_state = "borg_l_arm"
status = BODYPART_ROBOTIC
-
+
brute_reduction = 2
burn_reduction = 1
@@ -40,7 +40,7 @@
flags_1 = CONDUCT_1
icon_state = "borg_r_arm"
status = BODYPART_ROBOTIC
-
+
brute_reduction = 2
burn_reduction = 1
@@ -61,7 +61,7 @@
flags_1 = CONDUCT_1
icon_state = "borg_l_leg"
status = BODYPART_ROBOTIC
-
+
brute_reduction = 2
burn_reduction = 1
@@ -82,7 +82,7 @@
flags_1 = CONDUCT_1
icon_state = "borg_r_leg"
status = BODYPART_ROBOTIC
-
+
brute_reduction = 2
burn_reduction = 1
@@ -102,7 +102,7 @@
flags_1 = CONDUCT_1
icon_state = "borg_chest"
status = BODYPART_ROBOTIC
-
+
brute_reduction = 2
burn_reduction = 1
@@ -131,8 +131,7 @@
if(src.wired)
to_chat(user, "You have already inserted wire!")
return
- var/obj/item/stack/cable_coil/coil = W
- if (coil.use(1))
+ if (W.use_tool(src, user, 0, 1))
src.wired = 1
to_chat(user, "You insert the wire.")
else
@@ -164,7 +163,7 @@
flags_1 = CONDUCT_1
icon_state = "borg_head"
status = BODYPART_ROBOTIC
-
+
brute_reduction = 5
burn_reduction = 4
diff --git a/code/modules/surgery/surgery_step.dm b/code/modules/surgery/surgery_step.dm
index 7127b41ee3..23dffdea87 100644
--- a/code/modules/surgery/surgery_step.dm
+++ b/code/modules/surgery/surgery_step.dm
@@ -58,10 +58,9 @@
return FALSE
if(tool)
speed_mod = tool.toolspeed
- var/skill_mod = 1
- if(user?.mind?.skill_holder)
- skill_mod = SURGERY_SKILL_SPEEDUP_NUMERICAL_SCALE(user.mind.skill_holder.get_skill_value(/datum/skill/numerical/surgery))
- if(do_after(user, time * speed_mod * skill_mod, target = target))
+ if(user.mind)
+ SKILL_MODIFIER(GLOB.skill_datums[/datum/skill/numerical/surgery], user.mind.skill_holder, speed_mod, THRESHOLD_COMPETENT)
+ if(do_after(user, time * speed_mod, target = target))
var/prob_chance = 100
if(implement_type) //this means it isn't a require hand or any item step.
prob_chance = implements[implement_type]
@@ -69,7 +68,7 @@
if((prob(prob_chance) || (iscyborg(user) && !silicons_obey_prob)) && chem_check(target) && !try_to_fail)
if(success(user, target, target_zone, tool, surgery))
- user?.mind?.skill_holder?.auto_gain_experience(/datum/skill/numerical/surgery, SKILL_GAIN_SURGERY_PER_STEP)
+ user.mind?.skill_holder.auto_gain_experience(/datum/skill/numerical/surgery, SKILL_GAIN_SURGERY_PER_STEP)
advance = TRUE
else
if(failure(user, target, target_zone, tool, surgery))
diff --git a/tgstation.dme b/tgstation.dme
index 68ae7669f3..8245ef02f4 100755
--- a/tgstation.dme
+++ b/tgstation.dme
@@ -125,7 +125,8 @@
#include "code\__DEFINES\dcs\signals.dm"
#include "code\__DEFINES\flags\shields.dm"
#include "code\__DEFINES\misc\return_values.dm"
-#include "code\__DEFINES\skills\skills.dm"
+#include "code\__DEFINES\skills\defines.dm"
+#include "code\__DEFINES\skills\helpers.dm"
#include "code\__HELPERS\_cit_helpers.dm"
#include "code\__HELPERS\_lists.dm"
#include "code\__HELPERS\_logging.dm"
From 0adca2d9b85767efc17fb05ceabf32c6205f16e9 Mon Sep 17 00:00:00 2001
From: Ghommie <42542238+Ghommie@users.noreply.github.com>
Date: Tue, 5 May 2020 04:45:43 +0200
Subject: [PATCH 06/62] Oh.
---
code/_onclick/item_attack.dm | 10 +++++-----
code/modules/mob/living/carbon/carbon_defense.dm | 2 +-
code/modules/mob/living/carbon/human/species.dm | 2 +-
3 files changed, 7 insertions(+), 7 deletions(-)
diff --git a/code/_onclick/item_attack.dm b/code/_onclick/item_attack.dm
index dbcf171663..17d1e07d91 100644
--- a/code/_onclick/item_attack.dm
+++ b/code/_onclick/item_attack.dm
@@ -114,7 +114,7 @@
var/totitemdamage = I.force
if(I.used_skills && user.mind)
if(I.skill_flags & SKILL_ATTACK_OBJ)
- LIST_SKILL_MODIFIER(used_skills, user.mind.skill_holder, totitemdamage, I.skill_difficulty)
+ LIST_SKILL_MODIFIER(I.used_skills, user.mind.skill_holder, totitemdamage, I.skill_difficulty)
if(I.skill_flags & SKILL_TRAIN_ATTACK_OBJ)
if(!islist(I.used_skills))
user.mind.skill_holder.boost_skill_value_to(used_skills, I.skill_gain)
@@ -128,7 +128,7 @@
take_damage(totitemdamage, I.damtype, "melee", 1)
/mob/living/attacked_by(obj/item/I, mob/living/user)
- var/totitemdamage = calculate_item_force(I, user, TRUE)
+ var/totitemdamage = calculate_item_force(I, user)
if((user != src) && run_block(I, totitemdamage, "the [I.name]", ATTACK_TYPE_MELEE, I.armour_penetration, user) & BLOCK_SUCCESS)
return FALSE
send_item_attack_message(I, user)
@@ -150,7 +150,7 @@
else
return ..()
-/mob/living/proc/pre_attacked_by(obj/item/I, mob/living/user, pre_attack = FALSE)
+/mob/living/proc/pre_attacked_by(obj/item/I, mob/living/user)
. = I.force
if(!(user.combat_flags & COMBAT_FLAG_COMBAT_ACTIVE))
. *= 0.5
@@ -159,7 +159,7 @@
if(!pre_attack || !user.mind || !I.used_skills)
return
if(. && I.skill_flags & SKILL_ATTACK_MOB)
- LIST_SKILL_MODIFIER(used_skills, user.mind.skill_holder, ., I.skill_difficulty)
+ LIST_SKILL_MODIFIER(I.used_skills, user.mind.skill_holder, ., I.skill_difficulty)
if(I.skill_flags & SKILL_TRAIN_ATTACK_MOB)
if(!islist(I.used_skills))
user.mind.skill_holder.boost_skill_value_to(used_skills, I.skill_gain)
@@ -167,7 +167,7 @@
for(var/skill in used_skills)
user.mind.skill_holder.boost_skill_value_to(skill, I.skill_gain)
-/mob/living/carbon/proc/pre_attacked_by(obj/item/I, mob/living/user, pre_attack = FALSE)
+/mob/living/carbon/pre_attacked_by(obj/item/I, mob/living/user)
. = ..()
if(!(combat_flags & COMBAT_FLAG_COMBAT_ACTIVE))
. *= 1.5
diff --git a/code/modules/mob/living/carbon/carbon_defense.dm b/code/modules/mob/living/carbon/carbon_defense.dm
index 42e702c5d9..f1d3cf0356 100644
--- a/code/modules/mob/living/carbon/carbon_defense.dm
+++ b/code/modules/mob/living/carbon/carbon_defense.dm
@@ -77,7 +77,7 @@
SEND_SIGNAL(src, COMSIG_ADD_MOOD_EVENT, "embedded", /datum/mood_event/embedded)
/mob/living/carbon/attacked_by(obj/item/I, mob/living/user)
- var/totitemdamage = pre_attacked_by(I, user, TRUE)
+ var/totitemdamage = pre_attacked_by(I, user)
if(!(user.combat_flags & COMBAT_FLAG_COMBAT_ACTIVE))
totitemdamage *= 0.5
if(!CHECK_MOBILITY(user, MOBILITY_STAND))
diff --git a/code/modules/mob/living/carbon/human/species.dm b/code/modules/mob/living/carbon/human/species.dm
index 124f862244..71cdcd3f38 100644
--- a/code/modules/mob/living/carbon/human/species.dm
+++ b/code/modules/mob/living/carbon/human/species.dm
@@ -1712,7 +1712,7 @@ GLOBAL_LIST_EMPTY(roundstart_race_names)
disarm(M, H, attacker_style)
/datum/species/proc/spec_attacked_by(obj/item/I, mob/living/user, obj/item/bodypart/affecting, intent, mob/living/carbon/human/H)
- var/totitemdamage = H.pre_attacked_by(I, user, TRUE)
+ var/totitemdamage = H.pre_attacked_by(I, user)
// Allows you to put in item-specific reactions based on species
if(user != H)
if(H.run_block(I, totitemdamage, "the [I.name]", ATTACK_TYPE_MELEE, I.armour_penetration, user, affecting.body_zone) & BLOCK_SUCCESS)
From c34655d0940cef922da6f236e506e185ac96337b Mon Sep 17 00:00:00 2001
From: Ghommie <42542238+Ghommie@users.noreply.github.com>
Date: Tue, 5 May 2020 04:48:49 +0200
Subject: [PATCH 07/62] Uh uh.
---
code/modules/mob/living/carbon/carbon_defense.dm | 2 +-
code/modules/mob/living/carbon/human/species.dm | 2 +-
2 files changed, 2 insertions(+), 2 deletions(-)
diff --git a/code/modules/mob/living/carbon/carbon_defense.dm b/code/modules/mob/living/carbon/carbon_defense.dm
index f1d3cf0356..03bfd87376 100644
--- a/code/modules/mob/living/carbon/carbon_defense.dm
+++ b/code/modules/mob/living/carbon/carbon_defense.dm
@@ -92,7 +92,7 @@
affecting = bodyparts[1]
SEND_SIGNAL(I, COMSIG_ITEM_ATTACK_ZONE, src, user, affecting)
send_item_attack_message(I, user, affecting.name)
- I.do_stagger_action(src, user)
+ I.do_stagger_action(src, user, totitemdamage)
if(I.force)
apply_damage(totitemdamage, I.damtype, affecting) //CIT CHANGE - replaces I.force with totitemdamage
if(I.damtype == BRUTE && affecting.status == BODYPART_ORGANIC)
diff --git a/code/modules/mob/living/carbon/human/species.dm b/code/modules/mob/living/carbon/human/species.dm
index 71cdcd3f38..8221134276 100644
--- a/code/modules/mob/living/carbon/human/species.dm
+++ b/code/modules/mob/living/carbon/human/species.dm
@@ -1736,7 +1736,7 @@ GLOBAL_LIST_EMPTY(roundstart_race_names)
H.send_item_attack_message(I, user, hit_area)
- I.do_stagger_action(H, user)
+ I.do_stagger_action(H, user, totitemdamage)
if(!totitemdamage)
return 0 //item force is zero
From 791b05871f5effdcd13e1cb3260f958a2f64f5f0 Mon Sep 17 00:00:00 2001
From: Ghommie <42542238+Ghommie@users.noreply.github.com>
Date: Tue, 5 May 2020 05:24:40 +0200
Subject: [PATCH 08/62] Hey.
---
code/_onclick/item_attack.dm | 12 ++++++------
1 file changed, 6 insertions(+), 6 deletions(-)
diff --git a/code/_onclick/item_attack.dm b/code/_onclick/item_attack.dm
index 17d1e07d91..36a718b4b5 100644
--- a/code/_onclick/item_attack.dm
+++ b/code/_onclick/item_attack.dm
@@ -117,9 +117,9 @@
LIST_SKILL_MODIFIER(I.used_skills, user.mind.skill_holder, totitemdamage, I.skill_difficulty)
if(I.skill_flags & SKILL_TRAIN_ATTACK_OBJ)
if(!islist(I.used_skills))
- user.mind.skill_holder.boost_skill_value_to(used_skills, I.skill_gain)
+ user.mind.skill_holder.boost_skill_value_to(I.used_skills, I.skill_gain)
else
- for(var/skill in used_skills)
+ for(var/skill in I.used_skills)
user.mind.skill_holder.boost_skill_value_to(skill, I.skill_gain)
if(totitemdamage)
visible_message("[user] has hit [src] with [I]!", null, null, COMBAT_MESSAGE_RANGE)
@@ -128,7 +128,7 @@
take_damage(totitemdamage, I.damtype, "melee", 1)
/mob/living/attacked_by(obj/item/I, mob/living/user)
- var/totitemdamage = calculate_item_force(I, user)
+ var/totitemdamage = pre_attacked_by(I, user)
if((user != src) && run_block(I, totitemdamage, "the [I.name]", ATTACK_TYPE_MELEE, I.armour_penetration, user) & BLOCK_SUCCESS)
return FALSE
send_item_attack_message(I, user)
@@ -156,15 +156,15 @@
. *= 0.5
if(!CHECK_MOBILITY(user, MOBILITY_STAND))
. *= 0.5
- if(!pre_attack || !user.mind || !I.used_skills)
+ if(!user.mind || !I.used_skills)
return
if(. && I.skill_flags & SKILL_ATTACK_MOB)
LIST_SKILL_MODIFIER(I.used_skills, user.mind.skill_holder, ., I.skill_difficulty)
if(I.skill_flags & SKILL_TRAIN_ATTACK_MOB)
if(!islist(I.used_skills))
- user.mind.skill_holder.boost_skill_value_to(used_skills, I.skill_gain)
+ user.mind.skill_holder.boost_skill_value_to(I.used_skills, I.skill_gain)
else
- for(var/skill in used_skills)
+ for(var/skill in I.used_skills)
user.mind.skill_holder.boost_skill_value_to(skill, I.skill_gain)
/mob/living/carbon/pre_attacked_by(obj/item/I, mob/living/user)
From 001109cfcd745589ac06c032b88a5ba2f869f193 Mon Sep 17 00:00:00 2001
From: Ghommie <42542238+Ghommie@users.noreply.github.com>
Date: Tue, 5 May 2020 20:58:44 +0200
Subject: [PATCH 09/62] About ready.
---
code/__DEFINES/combat.dm | 9 +++++
code/__DEFINES/skills/defines.dm | 3 ++
code/__DEFINES/skills/helpers.dm | 40 ++++++++++---------
code/_onclick/item_attack.dm | 50 ++++++++++++++----------
code/datums/skills/_skill.dm | 34 ++++++++--------
code/datums/skills/_skill_holder.dm | 19 +++++----
code/datums/wires/_wires.dm | 10 +++--
code/game/objects/items.dm | 18 ++++-----
code/game/objects/items/melee/misc.dm | 2 +-
code/game/objects/items/stunbaton.dm | 2 +-
code/game/objects/items/toys.dm | 5 +--
code/modules/mob/living/carbon/carbon.dm | 2 +-
code/modules/power/cable.dm | 3 +-
13 files changed, 111 insertions(+), 86 deletions(-)
diff --git a/code/__DEFINES/combat.dm b/code/__DEFINES/combat.dm
index 7de162207d..74ce8519a2 100644
--- a/code/__DEFINES/combat.dm
+++ b/code/__DEFINES/combat.dm
@@ -248,6 +248,15 @@ GLOBAL_LIST_INIT(shove_disarming_types, typecacheof(list(
#define TOTAL_MASS_MEDIEVAL_WEAPON 3.6 //very, very generic average sword/warpick/etc. weight in pounds.
#define TOTAL_MASS_TOY_SWORD 1.5
+//stamina cost defines.
+#define STAM_COST_ATTACK_OBJ_MULT 1.2
+#define STAM_COST_ATTACK_MOB_MULT 0.8
+#define STAM_COST_BATON_MOB_MULT 1
+#define STAM_COST_NO_COMBAT_MULT 1.25
+#define STAM_COST_W_CLASS_MULT 1.25
+#define STAM_COST_THROW_MULT 2
+
+
//bullet_act() return values
#define BULLET_ACT_HIT "HIT" //It's a successful hit, whatever that means in the context of the thing it's hitting.
#define BULLET_ACT_BLOCK "BLOCK" //It's a blocked hit, whatever that means in the context of the thing it's hitting.
diff --git a/code/__DEFINES/skills/defines.dm b/code/__DEFINES/skills/defines.dm
index 00859bbd40..58b51b88e7 100644
--- a/code/__DEFINES/skills/defines.dm
+++ b/code/__DEFINES/skills/defines.dm
@@ -36,6 +36,9 @@
#define SKILL_TRAIN_ATTACK_MOB (1<<3)
#define SKILL_ATTACK_OBJ (1<<4)
#define SKILL_TRAIN_ATTACK_OBJ (1<<5)
+#define SKILL_STAMINA_COST (1<<6) //Influences the stamina cost from weapon usage.
+#define SKILL_THROW_STAM_COST (1<<7)
+#define SKILL_COMBAT_MODE (1<<8) //The user must have combat mode on.
///competency_threshold index defines
#define THRESHOLD_COMPETENT 1
diff --git a/code/__DEFINES/skills/helpers.dm b/code/__DEFINES/skills/helpers.dm
index 165b9091ae..88ad481e87 100644
--- a/code/__DEFINES/skills/helpers.dm
+++ b/code/__DEFINES/skills/helpers.dm
@@ -13,26 +13,28 @@
}\
target /= (1+(___value-to_check.competency_thresholds[threshold])*to_check.competency_mults[threshold])
-/// This is the one that accepts typepaths and lists.
-#define LIST_SKILL_MODIFIER(to_check, holder, target, threshold) \
- if(!islist(to_check)){\
- SKILL_MODIFIER(GLOB.skill_datums[to_check], holder, target, threshold)\
- } else {\
- var/___sum = 0;\
- var/list/___L = to_check;\
- for(var/_S in ___L){\
- var/___value;\
- var/datum/skill/___S = GLOB.skill_datums[_S];\
- switch(___S.progression_type){\
- if(SKILL_PROGRESSION_LEVEL){\
- ___value = LAZYACCESS(holder.skill_levels, ___S.type)\
- } else {\
- ___value = LAZYACCESS(holder.skills, ___S.type)\
- }\
- }\
- ___sum += (1+(___value - ___S.competency_thresholds[threshold])*___S.competency_mults[threshold])\
+/// This is the one that accepts typepaths and lists. if flags are enabled, an associative value check will be done.
+#define LIST_SKILL_MODIFIER(list, holder, target, threshold, flags, bad_flags) \
+ var/___sum = 0;\
+ var/___divisor = 0;\
+ for(var/_S in list){\
+ if((flags && !(list[_S] & (flags))) || (bad_flags && list[_S] & (bad_flags))){\
+ continue\
}\
- target /= (___sum/length(___L))\
+ var/___value;\
+ var/datum/skill/___S = GLOB.skill_datums[_S];\
+ switch(___S.progression_type){\
+ if(SKILL_PROGRESSION_LEVEL){\
+ ___value = LAZYACCESS(holder.skill_levels, ___S.type)\
+ } else {\
+ ___value = LAZYACCESS(holder.skills, ___S.type)\
+ }\
+ }\
+ ___sum += (1+(___value - ___S.competency_thresholds[threshold])*___S.competency_mults[threshold]);\
+ ___divisor++\
+ }\
+ if(___divisor){\
+ target /= (___sum/___divisor)\
}
//How experience levels are calculated.
diff --git a/code/_onclick/item_attack.dm b/code/_onclick/item_attack.dm
index 36a718b4b5..cdbab52d15 100644
--- a/code/_onclick/item_attack.dm
+++ b/code/_onclick/item_attack.dm
@@ -91,7 +91,7 @@
log_combat(user, M, "attacked", src.name, "(INTENT: [uppertext(user.a_intent)]) (DAMTYPE: [uppertext(damtype)])")
add_fingerprint(user)
- user.adjustStaminaLossBuffered(getweight()*0.8)//CIT CHANGE - makes attacking things cause stamina loss
+ user.adjustStaminaLossBuffered(getweight(user, STAM_COST_ATTACK_MOB_MULT))//CIT CHANGE - makes attacking things cause stamina loss
//the equivalent of the standard version of attack() but for object targets.
/obj/item/proc/attack_obj(obj/O, mob/living/user)
@@ -102,7 +102,7 @@
if(IS_STAMCRIT(user)) // CIT CHANGE - makes it impossible to attack in stamina softcrit
to_chat(user, "You're too exhausted.") // CIT CHANGE - ditto
return // CIT CHANGE - ditto
- user.adjustStaminaLossBuffered(getweight()*1.2)//CIT CHANGE - makes attacking things cause stamina loss
+ user.adjustStaminaLossBuffered(getweight(user, STAM_COST_ATTACK_OBJ_MULT))//CIT CHANGE - makes attacking things cause stamina loss
user.changeNext_move(CLICK_CD_MELEE)
user.do_attack_animation(O)
O.attacked_by(src, user)
@@ -112,15 +112,17 @@
/obj/attacked_by(obj/item/I, mob/living/user)
var/totitemdamage = I.force
+ var/bad_flag = NONE
+ if(!(user.combat_flags & COMBAT_FLAG_COMBAT_ACTIVE) && iscarbon(user))
+ totitemdamage *= 0.5
+ bad_flag |= SKILL_COMBAT_MODE //blacklist combat skills.
if(I.used_skills && user.mind)
- if(I.skill_flags & SKILL_ATTACK_OBJ)
- LIST_SKILL_MODIFIER(I.used_skills, user.mind.skill_holder, totitemdamage, I.skill_difficulty)
- if(I.skill_flags & SKILL_TRAIN_ATTACK_OBJ)
- if(!islist(I.used_skills))
- user.mind.skill_holder.boost_skill_value_to(I.used_skills, I.skill_gain)
- else
- for(var/skill in I.used_skills)
- user.mind.skill_holder.boost_skill_value_to(skill, I.skill_gain)
+ if(totitemdamage)
+ LIST_SKILL_MODIFIER(I.used_skills, user.mind.skill_holder, totitemdamage, I.skill_difficulty, SKILL_ATTACK_OBJ, bad_flag)
+ for(var/skill in I.used_skills)
+ if(!(I.used_skills[skill] & SKILL_TRAIN_ATTACK_OBJ))
+ continue
+ user.mind.skill_holder.auto_gain_experience(skill, I.skill_gain)
if(totitemdamage)
visible_message("[user] has hit [src] with [I]!", null, null, COMBAT_MESSAGE_RANGE)
//only witnesses close by and the victim see a hit message.
@@ -152,20 +154,20 @@
/mob/living/proc/pre_attacked_by(obj/item/I, mob/living/user)
. = I.force
- if(!(user.combat_flags & COMBAT_FLAG_COMBAT_ACTIVE))
+ var/bad_flag = NONE
+ if(!(user.combat_flags & COMBAT_FLAG_COMBAT_ACTIVE) && iscarbon(user))
. *= 0.5
+ bad_flag |= SKILL_COMBAT_MODE //blacklist combat skills.
if(!CHECK_MOBILITY(user, MOBILITY_STAND))
. *= 0.5
if(!user.mind || !I.used_skills)
return
- if(. && I.skill_flags & SKILL_ATTACK_MOB)
- LIST_SKILL_MODIFIER(I.used_skills, user.mind.skill_holder, ., I.skill_difficulty)
- if(I.skill_flags & SKILL_TRAIN_ATTACK_MOB)
- if(!islist(I.used_skills))
- user.mind.skill_holder.boost_skill_value_to(I.used_skills, I.skill_gain)
- else
- for(var/skill in I.used_skills)
- user.mind.skill_holder.boost_skill_value_to(skill, I.skill_gain)
+ if(.)
+ LIST_SKILL_MODIFIER(I.used_skills, user.mind.skill_holder, ., I.skill_difficulty, SKILL_ATTACK_MOB, bad_flag)
+ for(var/skill in I.used_skills)
+ if(!(I.used_skills[skill] & SKILL_TRAIN_ATTACK_MOB))
+ continue
+ user.mind.skill_holder.auto_gain_experience(skill, I.skill_gain)
/mob/living/carbon/pre_attacked_by(obj/item/I, mob/living/user)
. = ..()
@@ -205,8 +207,14 @@
return 1
/// How much stamina this takes to swing this is not for realism purposes hecc off.
-/obj/item/proc/getweight()
- return total_mass || w_class * 1.25
+/obj/item/proc/getweight(mob/living/user, multiplier = 1, flags = NONE)
+ . = (total_mass || w_class * STAM_COST_W_CLASS_MULT) * multiplier
+ var/bad_flag = NONE
+ if(iscarbon(user) && !(user.combat_flags & COMBAT_FLAG_COMBAT_ACTIVE))
+ . *= STAM_COST_NO_COMBAT_MULT
+ bad_flag |= SKILL_COMBAT_MODE
+ if(used_skills && user.mind)
+ LIST_SKILL_MODIFIER(used_skills, user.mind.skill_holder, ., skill_difficulty, SKILL_STAMINA_COST|flags, bad_flag)
/// How long this staggers for. 0 and negatives supported.
/obj/item/proc/melee_stagger_duration(force_override)
diff --git a/code/datums/skills/_skill.dm b/code/datums/skills/_skill.dm
index 039afacfa5..acfa44389e 100644
--- a/code/datums/skills/_skill.dm
+++ b/code/datums/skills/_skill.dm
@@ -28,7 +28,7 @@ GLOBAL_LIST_INIT_TYPED(skill_datums, /datum/skill, init_skill_datums())
/// Our description
var/desc
/// Color of the name as shown in the html readout
- var/name_color = "#000000"
+ var/name_color = "#FFFFFF"
/// Our progression type
var/progression_type
/// Abstract type
@@ -62,7 +62,7 @@ GLOBAL_LIST_INIT_TYPED(skill_datums, /datum/skill, init_skill_datums())
/**
* Standard value "render"
*/
-/datum/skill/proc/standard_render_value(value)
+/datum/skill/proc/standard_render_value(value, level)
return value
// Just saying, the choice to use different sub-parent-types is to force coders to resolve issues as I won't be implementing custom procs to grab skill levels in a certain context.
@@ -77,7 +77,7 @@ GLOBAL_LIST_INIT_TYPED(skill_datums, /datum/skill, init_skill_datums())
/datum/skill/binary/sanitize_value(new_value)
return new_value? TRUE : FALSE
-/datum/skill/binary/standard_render_value(value)
+/datum/skill/binary/standard_render_value(value, level)
return value? "Yes" : "No"
/datum/skill/numerical
@@ -93,7 +93,7 @@ GLOBAL_LIST_INIT_TYPED(skill_datums, /datum/skill, init_skill_datums())
/datum/skill/numerical/sanitize_value(new_value)
return clamp(new_value, min_value, max_value)
-/datum/skill/numerical/standard_render_value(value)
+/datum/skill/numerical/standard_render_value(value, level)
return display_as_percent? "[round(value/max_value/100, 0.01)]%" : "[value] / [max_value]"
/datum/skill/enum
@@ -113,7 +113,7 @@ GLOBAL_LIST_INIT_TYPED(skill_datums, /datum/skill, init_skill_datums())
abstract_type = /datum/skill/level
progression_type = SKILL_PROGRESSION_LEVEL
var/standard_xp_lvl_up = STD_XP_LVL_UP //the standard required to level up. def: 100
- var/xp_lvl_multiplier = STD_XP_LVL_UP //standard required level up exp multiplier. def: 2 (100, 200, 400, 800 etc.)
+ var/xp_lvl_multiplier = STD_XP_LVL_MULTI //standard required level up exp multiplier. def: 2 (100, 200, 400, 800 etc.)
var/max_levels = STD_MAX_LVL
var/level_up_method = STANDARD_LEVEL_UP //how levels are calculated.
var/list/levels = list() //level thresholds, if associative, these will be preceded by tiers such as "novice" or "trained"
@@ -132,7 +132,7 @@ GLOBAL_LIST_INIT_TYPED(skill_datums, /datum/skill, init_skill_datums())
value = XP_LEVEL(standard_xp_lvl_up, xp_lvl_multiplier, lvl)
if(DWARFY_LEVEL_UP)
value = DORF_XP_LEVEL(standard_xp_lvl_up, xp_lvl_multiplier, lvl)
- value = round(value)
+ value = round(value, 1)
if(!associative)
levels += value
continue
@@ -142,7 +142,7 @@ GLOBAL_LIST_INIT_TYPED(skill_datums, /datum/skill, init_skill_datums())
var/key = LAZYACCESS(levels, lvl)
if(!key)
if(lvl == 1) //You dun goof it.
- stack_trace("Skill datum [src] was set to have an associative levels list despite the latted having no key.")
+ stack_trace("Skill datum [src] was set to have an associative levels list despite the latter having no key value.")
associative = FALSE
levels += value
continue
@@ -170,18 +170,18 @@ GLOBAL_LIST_INIT_TYPED(skill_datums, /datum/skill, init_skill_datums())
else if(. < 0)
to_chat(M.current, "I feel like I've become worse at [name]!")
-/datum/skill/level/standard_render_value(value)
- var/current_lvl = associative ? unskilled_tier : 0
+/datum/skill/level/standard_render_value(value, level)
+ var/current_lvl = associative ? (!level ? unskilled_tier : levels[level]) : level
var/current_lvl_xp_sum = 0
- var/next_lvl_xp_sum
- for(var/lvl in 1 to max_levels)
- next_lvl_xp_sum = associative ? levels[levels[lvl]] : levels[lvl]
- if(value < next_lvl_xp_sum)
- break
- current_lvl_xp_sum = next_lvl_xp_sum
- current_lvl = associative ? levels[lvl] : lvl+1
+ if(level)
+ current_lvl_xp_sum = associative ? levels[levels[level]] : levels[level]
+ var/next_index = max(max_levels, level+1)
+ var/next_lvl_xp = associative ? levels[levels[next_index]] : levels[next_index]
+ if(next_lvl_xp > current_lvl_xp_sum)
+ next_lvl_xp -= current_lvl_xp_sum
- return "[associative ? current_lvl : "Lvl. [current_lvl]"] ([value - current_lvl_xp_sum]/[next_lvl_xp_sum])[value > next_lvl_xp_sum ? " \[MAX!\]" : ""]"
+
+ return "[associative ? current_lvl : "Lvl. [current_lvl]"] ([value - current_lvl_xp_sum]/[next_lvl_xp])[level == max_levels ? " \[MAX!\]" : ""]"
/datum/skill/level/job
levels = list("Basic", "Trained", "Experienced", "Master")
diff --git a/code/datums/skills/_skill_holder.dm b/code/datums/skills/_skill_holder.dm
index db9a84bb05..8b72b5c96d 100644
--- a/code/datums/skills/_skill_holder.dm
+++ b/code/datums/skills/_skill_holder.dm
@@ -29,8 +29,8 @@
* Grabs the level of a skill. Only supported by skills with tiers or levels.
*/
/datum/skill_holder/proc/get_skill_level(skill)
- if(!ispath(skill))
- CRASH("Invalid get_skill_value call. Use typepaths.") //until a time when we somehow need text ids for dynamic skills, I'm enforcing this.
+ if(!ispath(skill, /datum/skill))
+ CRASH("Invalid get_skill_value call. Use skill typepaths.") //until a time when we somehow need text ids for dynamic skills, I'm enforcing this.
if(!skill_levels)
return 0
return skill_levels[skill]
@@ -39,8 +39,8 @@
* Grabs our affinity for a skill. !!This is a multiplier!!
*/
/datum/skill_holder/proc/get_skill_affinity(skill)
- if(!ispath(skill))
- CRASH("Invalid get_skill_affinity call. Use typepaths.") //until a time when we somehow need text ids for dynamic skills, I'm enforcing this.
+ if(!ispath(skill, /datum/skill))
+ CRASH("Invalid get_skill_affinity call. Use skill typepaths.") //until a time when we somehow need text ids for dynamic skills, I'm enforcing this.
var/affinity = LAZYACCESS(skill_affinities, skill)
if(isnull(affinity))
return 1
@@ -51,7 +51,7 @@
*/
/datum/skill_holder/proc/set_skill_value(skill, value, silent = FALSE)
if(!ispath(skill, /datum/skill))
- CRASH("Invalid set_skill_value call. Use typepaths.") //until a time when we somehow need text ids for dynamic skills, I'm enforcing this.
+ CRASH("Invalid set_skill_value call. Use skill typepaths.") //until a time when we somehow need text ids for dynamic skills, I'm enforcing this.
var/datum/skill/S = GLOB.skill_datums[skill]
value = S.sanitize_value(value)
if(!isnull(value))
@@ -75,8 +75,11 @@
* Only works if skill is numerical.
*/
/datum/skill_holder/proc/auto_gain_experience(skill, value, maximum, silent = FALSE)
- if(!ispath(skill, /datum/skill/numerical))
- CRASH("You cannot auto increment a non numerical skill!")
+ if(!ispath(skill, /datum/skill))
+ CRASH("Invalid set_skill_value call. Use skill typepaths.")
+ var/datum/skill/S = GLOB.skill_datums[skill]
+ if(S.progression_type != SKILL_PROGRESSION_NUMERICAL && S.progression_type != SKILL_PROGRESSION_LEVEL)
+ CRASH("You cannot auto increment a non numerical(experience skill!")
var/current = get_skill_value(skill)
var/affinity = get_skill_affinity(skill)
var/target_value = current + (value * affinity)
@@ -95,6 +98,6 @@
out += "| Skill | Value |
"
for(var/path in skills)
var/datum/skill/S = GLOB.skill_datums[path]
- out += "| [S.name] | [S.standard_render_value(skills[path])] |
"
+ out += "| [S.name] | [S.standard_render_value(skills[path], LAZYACCESS(skill_levels, path) || 0)] |
"
out += "
"
return out.Join("")
diff --git a/code/datums/wires/_wires.dm b/code/datums/wires/_wires.dm
index fc31cb9286..d2f719c50c 100644
--- a/code/datums/wires/_wires.dm
+++ b/code/datums/wires/_wires.dm
@@ -140,10 +140,12 @@
if(req_skill && user?.mind)
var/level_diff = req_skill - user.mind.skill_holder.get_skill_level(/datum/skill/level/job/wiring)
if(level_diff > 0)
+ LAZYSET(current_users, user, TRUE)
to_chat(user, "You begin cutting [holder]'s [color] wire...")
if(!do_after(user, 1.5 SECONDS * level_diff, target = holder) || !interactable(user))
+ LAZYREMOVE(current_users, user)
return FALSE
- user.mind?.skill_holder.auto_gain_experience(/datum/skill/level/job/wiring, DEF_SKILL_GAIN*level_diff)
+ LAZYREMOVE(current_users, user)
to_chat(user, "You cut [holder]'s [color] wire.")
cut(get_wire(color))
return TRUE
@@ -167,10 +169,12 @@
if(req_skill && user?.mind)
var/level_diff = req_skill - user.mind.skill_holder.get_skill_level(/datum/skill/level/job/wiring)
if(level_diff > 0)
+ LAZYSET(current_users, user, TRUE)
to_chat(user, "You begin pulsing [holder]'s [color] wire...")
if(!do_after(user, 1.5 SECONDS * level_diff, target = holder) || !interactable(user))
+ LAZYREMOVE(current_users, user)
return FALSE
- user.mind?.skill_holder.auto_gain_experience(/datum/skill/level/job/wiring, DEF_SKILL_GAIN*level_diff)
+ LAZYREMOVE(current_users, user)
to_chat(user, "You pulse [holder]'s [color] wire.")
pulse(get_wire(color), user)
return TRUE
@@ -286,7 +290,7 @@
if("cut")
I = L.is_holding_tool_quality(TOOL_WIRECUTTER)
if(I || IsAdminGhost(usr))
- if(cut_color(target_wire) && I && holder)
+ if(cut_color(target_wire, L) && I && holder)
I.play_tool_sound(holder, 20)
. = TRUE
else
diff --git a/code/game/objects/items.dm b/code/game/objects/items.dm
index c0c8f449e8..e781b6859c 100644
--- a/code/game/objects/items.dm
+++ b/code/game/objects/items.dm
@@ -133,8 +133,7 @@ GLOBAL_VAR_INIT(rpg_loot_items, FALSE)
var/list/juice_results //A reagent list containing blah blah... but when JUICED in a grinder!
///Skills vars
- var/used_skills //path/s of skill/s exercised when using this item.
- var/skill_flags = NONE //better defines which tasks the the skill/s is/are exercised on.
+ var/list/used_skills //list of skills exercised when using this item. An associated bitfield indicates how the skill is exercised.
var/skill_difficulty = THRESHOLD_COMPETENT //how difficult it's to use this item in general.
var/skill_gain = DEF_SKILL_GAIN //base skill value gain from using this item.
@@ -801,8 +800,8 @@ GLOBAL_VAR_INIT(rpg_loot_items, FALSE)
play_tool_sound(target, volume)
if(delay)
- if(user.mind && used_skills && skill_flags & SKILL_USE_TOOL)
- LIST_SKILL_MODIFIER(used_skills, user.mind.skill_holder, delay, skill_difficulty)
+ if(user.mind && used_skills)
+ LIST_SKILL_MODIFIER(used_skills, user.mind.skill_holder, delay, skill_difficulty, SKILL_USE_TOOL, NONE)
// Create a callback with checks that would be called every tick by do_after.
var/datum/callback/tool_check = CALLBACK(src, .proc/tool_check_callback, user, amount, extra_checks)
@@ -828,12 +827,11 @@ GLOBAL_VAR_INIT(rpg_loot_items, FALSE)
if(delay >= MIN_TOOL_SOUND_DELAY)
play_tool_sound(target, volume)
- if(user.mind && used_skills && skill_flags & SKILL_TRAINING_TOOL && skill_gain_mult)
- if(!islist(used_skills))
- user.mind.skill_holder.boost_skill_value_to(used_skills, skill_gain*skill_gain_mult, GET_STANDARD_LVL(max_level))
- else
- for(var/skill in used_skills)
- user.mind.skill_holder.boost_skill_value_to(skill, skill_gain*skill_gain_mult, GET_STANDARD_LVL(max_level))
+ if(user.mind && used_skills && skill_gain_mult)
+ for(var/skill in used_skills)
+ if(!(used_skills[skill] & SKILL_TRAINING_TOOL))
+ continue
+ user.mind.skill_holder.auto_gain_experience(skill, skill_gain*skill_gain_mult, GET_STANDARD_LVL(max_level))
return TRUE
diff --git a/code/game/objects/items/melee/misc.dm b/code/game/objects/items/melee/misc.dm
index c2db529675..3504ed2f50 100644
--- a/code/game/objects/items/melee/misc.dm
+++ b/code/game/objects/items/melee/misc.dm
@@ -325,7 +325,7 @@
else
target.LAssailant = WEAKREF(user)
cooldown_check = world.time + cooldown
- user.adjustStaminaLossBuffered(getweight())//CIT CHANGE - makes swinging batons cost stamina
+ user.adjustStaminaLossBuffered(getweight(user, STAM_COST_BATON_MOB_MULT))
else
var/wait_desc = get_wait_description()
if(wait_desc)
diff --git a/code/game/objects/items/stunbaton.dm b/code/game/objects/items/stunbaton.dm
index 0fc47f649a..8a817f6d62 100644
--- a/code/game/objects/items/stunbaton.dm
+++ b/code/game/objects/items/stunbaton.dm
@@ -163,7 +163,7 @@
if(status)
if(baton_stun(M, user, disarming))
user.do_attack_animation(M)
- user.adjustStaminaLossBuffered(getweight()) //CIT CHANGE - makes stunbatonning others cost stamina
+ user.adjustStaminaLossBuffered(getweight(user, STAM_COST_BATON_MOB_MULT))
else if(user.a_intent != INTENT_HARM) //they'll try to bash in the last proc.
M.visible_message("[user] has prodded [M] with [src]. Luckily it was off.", \
"[user] has prodded you with [src]. Luckily it was off")
diff --git a/code/game/objects/items/toys.dm b/code/game/objects/items/toys.dm
index 9291b72467..722f6178bc 100644
--- a/code/game/objects/items/toys.dm
+++ b/code/game/objects/items/toys.dm
@@ -241,11 +241,13 @@
playsound(user, activation_sound, transform_volume, 1)
w_class = WEIGHT_CLASS_BULKY
AddElement(/datum/element/sword_point)
+ total_mass = total_mass_on
else
to_chat(user, "[deactivation_message]")
playsound(user, deactivation_sound, transform_volume, 1)
w_class = WEIGHT_CLASS_SMALL
RemoveElement(/datum/element/sword_point)
+ total_mass = initial(total_mass)
update_icon()
add_fingerprint(user)
@@ -287,9 +289,6 @@
else
return ..()
-/obj/item/toy/sword/getweight()
- return (active ? total_mass_on : total_mass) || w_class *1.25
-
/obj/item/toy/sword/cx
name = "\improper DX Non-Euplastic LightSword"
desc = "A deluxe toy replica of an energy sword. Realistic visuals and sounds! Ages 8 and up."
diff --git a/code/modules/mob/living/carbon/carbon.dm b/code/modules/mob/living/carbon/carbon.dm
index 69c81229a7..821b432ea5 100644
--- a/code/modules/mob/living/carbon/carbon.dm
+++ b/code/modules/mob/living/carbon/carbon.dm
@@ -199,7 +199,7 @@
to_chat(src, "You set [I] down gently on the ground.")
return
- adjustStaminaLossBuffered(I.getweight()*2)//CIT CHANGE - throwing items shall be more tiring than swinging em. Doubly so.
+ adjustStaminaLossBuffered(I.getweight(src, STAM_COST_THROW_MULT, SKILL_THROW_STAM_COST))
if(thrown_thing)
visible_message("[src] has thrown [thrown_thing].")
diff --git a/code/modules/power/cable.dm b/code/modules/power/cable.dm
index 1e2650d17f..a633f83b2b 100644
--- a/code/modules/power/cable.dm
+++ b/code/modules/power/cable.dm
@@ -495,8 +495,7 @@ By design, d1 is the smallest direction and d2 is the highest
full_w_class = WEIGHT_CLASS_SMALL
grind_results = list(/datum/reagent/copper = 2) //2 copper per cable in the coil
usesound = 'sound/items/deconstruct.ogg'
- used_skills = /datum/skill/level/job/wiring
- skill_flags = SKILL_USE_TOOL|SKILL_TRAINING_TOOL
+ used_skills = list(/datum/skill/level/job/wiring = SKILL_USE_TOOL|SKILL_TRAINING_TOOL)
/obj/item/stack/cable_coil/cyborg
is_cyborg = 1
From 81dde7debd1f1553a94d6f896f61024fad6dae4f Mon Sep 17 00:00:00 2001
From: Ghommie <42542238+Ghommie@users.noreply.github.com>
Date: Tue, 5 May 2020 21:04:19 +0200
Subject: [PATCH 10/62] Obligatory.
---
code/_onclick/item_attack.dm | 2 ++
1 file changed, 2 insertions(+)
diff --git a/code/_onclick/item_attack.dm b/code/_onclick/item_attack.dm
index cdbab52d15..88e0ad8627 100644
--- a/code/_onclick/item_attack.dm
+++ b/code/_onclick/item_attack.dm
@@ -209,6 +209,8 @@
/// How much stamina this takes to swing this is not for realism purposes hecc off.
/obj/item/proc/getweight(mob/living/user, multiplier = 1, flags = NONE)
. = (total_mass || w_class * STAM_COST_W_CLASS_MULT) * multiplier
+ if(!user)
+ return
var/bad_flag = NONE
if(iscarbon(user) && !(user.combat_flags & COMBAT_FLAG_COMBAT_ACTIVE))
. *= STAM_COST_NO_COMBAT_MULT
From 91d6faaf21a2d40f24f2c5c3beec8197ee67355b Mon Sep 17 00:00:00 2001
From: Ghommie <42542238+Ghommie@users.noreply.github.com>
Date: Tue, 5 May 2020 21:06:53 +0200
Subject: [PATCH 11/62] more obligatory changes.
---
code/_onclick/item_attack.dm | 4 ++--
1 file changed, 2 insertions(+), 2 deletions(-)
diff --git a/code/_onclick/item_attack.dm b/code/_onclick/item_attack.dm
index 88e0ad8627..4d00b877a0 100644
--- a/code/_onclick/item_attack.dm
+++ b/code/_onclick/item_attack.dm
@@ -207,7 +207,7 @@
return 1
/// How much stamina this takes to swing this is not for realism purposes hecc off.
-/obj/item/proc/getweight(mob/living/user, multiplier = 1, flags = NONE)
+/obj/item/proc/getweight(mob/living/user, multiplier = 1, flags = SKILL_STAMINA_COST)
. = (total_mass || w_class * STAM_COST_W_CLASS_MULT) * multiplier
if(!user)
return
@@ -216,7 +216,7 @@
. *= STAM_COST_NO_COMBAT_MULT
bad_flag |= SKILL_COMBAT_MODE
if(used_skills && user.mind)
- LIST_SKILL_MODIFIER(used_skills, user.mind.skill_holder, ., skill_difficulty, SKILL_STAMINA_COST|flags, bad_flag)
+ LIST_SKILL_MODIFIER(used_skills, user.mind.skill_holder, ., skill_difficulty, flags, bad_flag)
/// How long this staggers for. 0 and negatives supported.
/obj/item/proc/melee_stagger_duration(force_override)
From b5a4bceafb65f86a4d65dbf90b820d61c28eae0b Mon Sep 17 00:00:00 2001
From: Ghommie <42542238+Ghommie@users.noreply.github.com>
Date: Wed, 6 May 2020 17:46:15 +0200
Subject: [PATCH 12/62] Fixing some sprite issues and nit-picks.
---
.../mob/living/simple_animal/friendly/cat.dm | 2 +-
icons/mob/clothing/back.dmi | Bin 96843 -> 103186 bytes
icons/mob/clothing/feet.dmi | Bin 23048 -> 23990 bytes
icons/mob/clothing/feet_digi.dmi | Bin 29508 -> 30398 bytes
icons/mob/clothing/head.dmi | Bin 223291 -> 223277 bytes
5 files changed, 1 insertion(+), 1 deletion(-)
diff --git a/code/modules/mob/living/simple_animal/friendly/cat.dm b/code/modules/mob/living/simple_animal/friendly/cat.dm
index 1297fddb69..7d41068020 100644
--- a/code/modules/mob/living/simple_animal/friendly/cat.dm
+++ b/code/modules/mob/living/simple_animal/friendly/cat.dm
@@ -87,7 +87,6 @@
pass_flags = PASSMOB
mob_size = MOB_SIZE_SMALL
collar_type = "kitten"
- held_icon = "cat"
//RUNTIME IS ALIVE! SQUEEEEEEEE~
/mob/living/simple_animal/pet/cat/Runtime
@@ -99,6 +98,7 @@
gender = FEMALE
gold_core_spawnable = NO_SPAWN
unique_pet = TRUE
+ held_icon = "cat"
var/list/family = list()//var restored from savefile, has count of each child type
var/list/children = list()//Actual mob instances of children
var/cats_deployed = 0
diff --git a/icons/mob/clothing/back.dmi b/icons/mob/clothing/back.dmi
index 66a47d21f2fd7fea2ff891ed08f2dee77459f720..d307b5b6c9c75846602a46b004b9c5624de863da 100644
GIT binary patch
literal 103186
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