From e7f455e7afa578093569dad9cabd2af497ed74c4 Mon Sep 17 00:00:00 2001
From: Ghom <42542238+Ghommie@users.noreply.github.com>
Date: Sat, 2 May 2020 00:42:21 +0200
Subject: [PATCH 01/89] makes the uranium mat three times less radioactive
---
code/datums/materials/basemats.dm | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/code/datums/materials/basemats.dm b/code/datums/materials/basemats.dm
index f07b1d8792..f189a91353 100644
--- a/code/datums/materials/basemats.dm
+++ b/code/datums/materials/basemats.dm
@@ -72,7 +72,7 @@ Unless you know what you're doing, only use the first three numbers. They're in
/datum/material/uranium/on_applied(atom/source, amount, material_flags)
. = ..()
- source.AddComponent(/datum/component/radioactive, amount / 20, source, 0) //half-life of 0 because we keep on going.
+ source.AddComponent(/datum/component/radioactive, amount / 60, source, 0) //half-life of 0 because we keep on going.
/datum/material/uranium/on_removed(atom/source, material_flags)
. = ..()
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 02/89] 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 03/89] 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 76e1ee80b762f3e66dd48eda81ae61adb60e5444 Mon Sep 17 00:00:00 2001
From: Ghommie <42542238+Ghommie@users.noreply.github.com>
Date: Sun, 3 May 2020 07:12:41 +0200
Subject: [PATCH 04/89] Porting "Mobs now turn towards and are shifted based on
grab state towards something pulling them"
---
code/__DEFINES/atmospherics.dm | 4 +-
code/__DEFINES/mobs.dm | 4 ++
code/datums/components/riding.dm | 7 ++++
code/game/atoms_movable.dm | 4 ++
code/game/atoms_movement.dm | 18 +++-----
code/game/objects/buckling.dm | 11 ++++-
.../atmospherics/machinery/pipes/manifold.dm | 2 +
.../machinery/pipes/manifold4w.dm | 2 +
code/modules/mob/living/living.dm | 41 +++++++++++++++++++
code/modules/mob/living/living_defense.dm | 1 +
code/modules/mob/living/living_movement.dm | 14 ++++++-
code/modules/mob/mob_movement.dm | 2 +
12 files changed, 92 insertions(+), 18 deletions(-)
diff --git a/code/__DEFINES/atmospherics.dm b/code/__DEFINES/atmospherics.dm
index e728d49956..88c1b763a5 100644
--- a/code/__DEFINES/atmospherics.dm
+++ b/code/__DEFINES/atmospherics.dm
@@ -255,10 +255,10 @@
//HELPERS
#define PIPING_LAYER_SHIFT(T, PipingLayer) \
- if(T.dir & NORTH || T.dir & SOUTH) { \
+ if(T.dir & (NORTH|SOUTH)) { \
T.pixel_x = (PipingLayer - PIPING_LAYER_DEFAULT) * PIPING_LAYER_P_X;\
} \
- if(T.dir & WEST || T.dir & EAST) { \
+ if(T.dir & (WEST|EAST)) { \
T.pixel_y = (PipingLayer - PIPING_LAYER_DEFAULT) * PIPING_LAYER_P_Y;\
}
diff --git a/code/__DEFINES/mobs.dm b/code/__DEFINES/mobs.dm
index c89bd4843e..61850ed2fd 100644
--- a/code/__DEFINES/mobs.dm
+++ b/code/__DEFINES/mobs.dm
@@ -288,6 +288,10 @@
#define HUMAN_FIRE_STACK_ICON_NUM 3
+#define GRAB_PIXEL_SHIFT_PASSIVE 6
+#define GRAB_PIXEL_SHIFT_AGGRESSIVE 12
+#define GRAB_PIXEL_SHIFT_NECK 16
+
#define PULL_PRONE_SLOWDOWN 0.6
#define FIREMAN_CARRY_SLOWDOWN 0
#define PIGGYBACK_CARRY_SLOWDOWN 1
diff --git a/code/datums/components/riding.dm b/code/datums/components/riding.dm
index 00bb392787..651acda6e1 100644
--- a/code/datums/components/riding.dm
+++ b/code/datums/components/riding.dm
@@ -20,6 +20,8 @@
var/ride_check_ridden_incapacitated = FALSE
var/list/offhands = list() // keyed list containing all the current riding offsets associated by mob
+ var/del_on_unbuckle_all = FALSE
+
/datum/component/riding/Initialize()
if(!ismovable(parent))
return COMPONENT_INCOMPATIBLE
@@ -28,8 +30,11 @@
RegisterSignal(parent, COMSIG_MOVABLE_MOVED, .proc/vehicle_moved)
/datum/component/riding/proc/vehicle_mob_unbuckle(datum/source, mob/living/M, force = FALSE)
+ var/atom/movable/AM = parent
restore_position(M)
unequip_buckle_inhands(M)
+ if(del_on_unbuckle_all && !AM.has_buckled_mobs())
+ qdel(src)
/datum/component/riding/proc/vehicle_mob_buckle(datum/source, mob/living/M, force = FALSE)
handle_vehicle_offsets()
@@ -194,6 +199,7 @@
///////Yes, I said humans. No, this won't end well...//////////
/datum/component/riding/human
+ del_on_unbuckle_all = TRUE
var/fireman_carrying = FALSE
/datum/component/riding/human/Initialize()
@@ -261,6 +267,7 @@
user.visible_message("[AM] pushes [user] off of [AM.p_them()]!")
/datum/component/riding/cyborg
+ del_on_unbuckle_all = TRUE
/datum/component/riding/cyborg/Initialize()
. = ..()
diff --git a/code/game/atoms_movable.dm b/code/game/atoms_movable.dm
index b86c7e9729..32dde45195 100644
--- a/code/game/atoms_movable.dm
+++ b/code/game/atoms_movable.dm
@@ -25,6 +25,7 @@
var/inertia_move_delay = 5
var/pass_flags = 0
var/moving_diagonally = 0 //0: not doing a diagonal move. 1 and 2: doing the first/second step of the diagonal move
+ var/atom/movable/moving_from_pull //attempt to resume grab after moving instead of before.
var/list/client_mobs_in_contents // This contains all the client mobs within this container
var/list/acted_explosions //for explosion dodging
glide_size = 8
@@ -207,6 +208,7 @@
if(!Process_Spacemove(get_dir(pulling.loc, A)))
return
step(pulling, get_dir(pulling.loc, A))
+ return TRUE
/atom/movable/proc/check_pulling()
if(pulling)
@@ -224,6 +226,8 @@
if(pulling.anchored || pulling.move_resist > move_force)
stop_pulling()
return
+ if(pulledby && moving_diagonally != FIRST_DIAG_STEP && get_dist(src, pulledby) > 1) //separated from our puller and not in the middle of a diagonal move.
+ pulledby.stop_pulling()
/atom/movable/Destroy(force)
QDEL_NULL(proximity_monitor)
diff --git a/code/game/atoms_movement.dm b/code/game/atoms_movement.dm
index d418652cd8..3d2b6d9e91 100644
--- a/code/game/atoms_movement.dm
+++ b/code/game/atoms_movement.dm
@@ -51,13 +51,8 @@
/atom/movable/Move(atom/newloc, direct)
var/atom/movable/pullee = pulling
var/turf/T = loc
- if(pulling)
- if(pullee && get_dist(src, pullee) > 1)
- stop_pulling()
-
- if(pullee && pullee.loc != loc && !isturf(pullee.loc) ) //to be removed once all code that changes an object's loc uses forceMove().
- log_game("DEBUG:[src]'s pull on [pullee] wasn't broken despite [pullee] being in [pullee.loc]. Pull stopped manually.")
- stop_pulling()
+ if(!moving_from_pull)
+ check_pulling()
if(!loc || !newloc)
return FALSE
var/atom/oldloc = loc
@@ -130,19 +125,16 @@
if(has_buckled_mobs() && !handle_buckled_mob_movement(loc,direct)) //movement failed due to buckled mob(s)
return FALSE
- if(pulling && pulling == pullee) //we were pulling a thing and didn't lose it during our move.
+ if(pulling && pulling == pullee && pulling != moving_from_pull) //we were pulling a thing and didn't lose it during our move.
if(pulling.anchored)
stop_pulling()
else
var/pull_dir = get_dir(src, pulling)
//puller and pullee more than one tile away or in diagonal position
if(get_dist(src, pulling) > 1 || (moving_diagonally != SECOND_DIAG_STEP && ((pull_dir - 1) & pull_dir)))
+ pulling.moving_from_pull = src
pulling.Move(T, get_dir(pulling, T)) //the pullee tries to reach our previous position
- if(pulling && get_dist(src, pulling) > 1) //the pullee couldn't keep up
- stop_pulling()
- if(pulledby && moving_diagonally != FIRST_DIAG_STEP && get_dist(src, pulledby) > 1)//separated from our puller and not in the middle of a diagonal move.
- pulledby.stop_pulling()
-
+ pulling.moving_from_pull = null
Moved(oldloc, direct)
/atom/movable/proc/handle_buckled_mob_movement(newloc,direct)
diff --git a/code/game/objects/buckling.dm b/code/game/objects/buckling.dm
index 0e14af75a9..bacb8c6669 100644
--- a/code/game/objects/buckling.dm
+++ b/code/game/objects/buckling.dm
@@ -55,8 +55,12 @@
M.buckling = null
return FALSE
- if(M.pulledby && buckle_prevents_pull)
- M.pulledby.stop_pulling()
+ if(M.pulledby)
+ if(buckle_prevents_pull)
+ M.pulledby.stop_pulling()
+ else if(isliving(M.pulledby))
+ var/mob/living/L = M.pulledby
+ L.reset_pull_offsets(M, TRUE)
if(!check_loc && M.loc != loc)
M.forceMove(loc)
@@ -137,4 +141,7 @@
"You unbuckle yourself from [src].",\
"You hear metal clanking.")
add_fingerprint(user)
+ if(isliving(M.pulledby))
+ var/mob/living/L = M.pulledby
+ L.set_pull_offsets(M, L.grab_state)
return M
diff --git a/code/modules/atmospherics/machinery/pipes/manifold.dm b/code/modules/atmospherics/machinery/pipes/manifold.dm
index 77452fd6fd..aa8ee65bd8 100644
--- a/code/modules/atmospherics/machinery/pipes/manifold.dm
+++ b/code/modules/atmospherics/machinery/pipes/manifold.dm
@@ -28,6 +28,8 @@
/obj/machinery/atmospherics/pipe/manifold/update_icon()
cut_overlays()
+ if(!center)
+ center = mutable_appearance(icon, "manifold_center")
PIPING_LAYER_DOUBLE_SHIFT(center, piping_layer)
add_overlay(center)
diff --git a/code/modules/atmospherics/machinery/pipes/manifold4w.dm b/code/modules/atmospherics/machinery/pipes/manifold4w.dm
index 1bcca8d5ae..56c0408d18 100644
--- a/code/modules/atmospherics/machinery/pipes/manifold4w.dm
+++ b/code/modules/atmospherics/machinery/pipes/manifold4w.dm
@@ -26,6 +26,8 @@
/obj/machinery/atmospherics/pipe/manifold4w/update_icon()
cut_overlays()
+ if(!center)
+ center = mutable_appearance(icon, "manifold_center")
PIPING_LAYER_DOUBLE_SHIFT(center, piping_layer)
add_overlay(center)
diff --git a/code/modules/mob/living/living.dm b/code/modules/mob/living/living.dm
index 89f8381087..5189f47fbc 100644
--- a/code/modules/mob/living/living.dm
+++ b/code/modules/mob/living/living.dm
@@ -316,6 +316,45 @@
update_pull_movespeed()
+ set_pull_offsets(M, state)
+
+/mob/living/proc/set_pull_offsets(mob/living/M, grab_state = GRAB_PASSIVE)
+ if(M.buckled)
+ return //don't make them change direction or offset them if they're buckled into something.
+ var/offset = 0
+ switch(grab_state)
+ if(GRAB_PASSIVE)
+ offset = GRAB_PIXEL_SHIFT_PASSIVE
+ if(GRAB_AGGRESSIVE)
+ offset = GRAB_PIXEL_SHIFT_AGGRESSIVE
+ if(GRAB_NECK)
+ offset = GRAB_PIXEL_SHIFT_NECK
+ if(GRAB_KILL)
+ offset = GRAB_PIXEL_SHIFT_NECK
+ M.setDir(get_dir(M, src))
+ switch(M.dir)
+ if(NORTH)
+ animate(M, pixel_x = 0, pixel_y = offset, 3)
+ if(SOUTH)
+ animate(M, pixel_x = 0, pixel_y = -offset, 3)
+ if(EAST)
+ if(M.lying == 270) //update the dragged dude's direction if we've turned
+ M.lying = 90
+ M.update_transform() //force a transformation update, otherwise it'll take a few ticks for update_mobility() to do so
+ M.lying_prev = M.lying
+ animate(M, pixel_x = offset, pixel_y = 0, 3)
+ if(WEST)
+ if(M.lying == 90)
+ M.lying = 270
+ M.update_transform()
+ M.lying_prev = M.lying
+ animate(M, pixel_x = -offset, pixel_y = 0, 3)
+
+/mob/living/proc/reset_pull_offsets(mob/living/M, override)
+ if(!override && M.buckled)
+ return
+ animate(M, pixel_x = 0, pixel_y = 0, 1)
+
//mob verbs are a lot faster than object verbs
//for more info on why this is not atom/pull, see examinate() in mob.dm
/mob/living/verb/pulled(atom/movable/AM as mob|obj in oview(1))
@@ -328,6 +367,8 @@
stop_pulling()
/mob/living/stop_pulling()
+ if(ismob(pulling))
+ reset_pull_offsets(pulling)
..()
update_pull_movespeed()
update_pull_hud_icon()
diff --git a/code/modules/mob/living/living_defense.dm b/code/modules/mob/living/living_defense.dm
index 39a94232a3..3e4fbd9625 100644
--- a/code/modules/mob/living/living_defense.dm
+++ b/code/modules/mob/living/living_defense.dm
@@ -264,6 +264,7 @@
update_mobility() //we fall down
if(!buckled && !density)
Move(user.loc)
+ user.set_pull_offsets(src, grab_state)
return 1
/mob/living/attack_hand(mob/user)
diff --git a/code/modules/mob/living/living_movement.dm b/code/modules/mob/living/living_movement.dm
index ddcf3e6292..6a84162ea8 100644
--- a/code/modules/mob/living/living_movement.dm
+++ b/code/modules/mob/living/living_movement.dm
@@ -75,8 +75,12 @@
. = ..()
- if(pulledby && moving_diagonally != FIRST_DIAG_STEP && get_dist(src, pulledby) > 1)//separated from our puller and not in the middle of a diagonal move.
+ if(pulledby && moving_diagonally != FIRST_DIAG_STEP && get_dist(src, pulledby) > 1 && (pulledby != moving_from_pull))//separated from our puller and not in the middle of a diagonal move.
pulledby.stop_pulling()
+ else
+ if(isliving(pulledby))
+ var/mob/living/L = pulledby
+ L.set_pull_offsets(src, pulledby.grab_state)
if(active_storage && !(CanReach(active_storage.parent,view_only = TRUE)))
active_storage.close(src)
@@ -84,6 +88,14 @@
if(lying && !buckled && prob(getBruteLoss()*200/maxHealth))
makeTrail(newloc, T, old_direction)
+
+/mob/living/Move_Pulled(atom/A)
+ . = ..()
+ if(!. || !isliving(A))
+ return
+ var/mob/living/L = A
+ set_pull_offsets(L, grab_state)
+
/mob/living/forceMove(atom/destination)
stop_pulling()
if(buckled)
diff --git a/code/modules/mob/mob_movement.dm b/code/modules/mob/mob_movement.dm
index fdc60b30cb..adf3e349b7 100644
--- a/code/modules/mob/mob_movement.dm
+++ b/code/modules/mob/mob_movement.dm
@@ -109,6 +109,8 @@
/// Process_Grab(): checks for grab, attempts to break if so. Return TRUE to prevent movement.
/client/proc/Process_Grab()
if(mob.pulledby)
+ if((mob.pulledby == mob.pulling) && (mob.pulledby.grab_state == GRAB_PASSIVE)) //Don't autoresist passive grabs if we're grabbing them too.
+ return
if(mob.incapacitated(ignore_restraints = 1))
move_delay = world.time + 10
return TRUE
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 05/89] 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 65899bf90668d98a020fee8261572da293107ba3 Mon Sep 17 00:00:00 2001
From: Ghommie <42542238+Ghommie@users.noreply.github.com>
Date: Sun, 3 May 2020 19:56:00 +0200
Subject: [PATCH 06/89] Removing cargo "paperwork" and a few other bloat packs.
---
.../crafting/recipes/recipes_misc.dm | 9 -------
code/game/objects/structures/loom.dm | 3 ---
code/modules/cargo/exports/manifest.dm | 16 -----------
code/modules/cargo/packs/materials.dm | 8 ------
code/modules/cargo/packs/misc.dm | 27 +------------------
5 files changed, 1 insertion(+), 62 deletions(-)
diff --git a/code/datums/components/crafting/recipes/recipes_misc.dm b/code/datums/components/crafting/recipes/recipes_misc.dm
index 9eb3fcaaea..cca1682723 100644
--- a/code/datums/components/crafting/recipes/recipes_misc.dm
+++ b/code/datums/components/crafting/recipes/recipes_misc.dm
@@ -404,15 +404,6 @@
subcategory = CAT_MISCELLANEOUS
category = CAT_MISC
-/datum/crafting_recipe/paperwork
- name = "Filed Paper Work"
- result = /obj/item/folder/paperwork_correct
- time = 10 //Takes time for people to file and complete paper work!
- tools = list(/obj/item/pen)
- reqs = list(/obj/item/folder/paperwork = 1)
- subcategory = CAT_MISCELLANEOUS
- category = CAT_MISC
-
/datum/crafting_recipe/coconut_bong
name = "Coconut Bong"
result = /obj/item/bong/coconut
diff --git a/code/game/objects/structures/loom.dm b/code/game/objects/structures/loom.dm
index 2244742f3a..ad128b01c2 100644
--- a/code/game/objects/structures/loom.dm
+++ b/code/game/objects/structures/loom.dm
@@ -38,7 +38,4 @@
user.show_message("You weave \the [S.name] into a workable fabric.", MSG_VISUAL)
return TRUE
-/obj/structure/loom/unanchored
- anchored = FALSE
-
#undef FABRIC_PER_SHEET
\ No newline at end of file
diff --git a/code/modules/cargo/exports/manifest.dm b/code/modules/cargo/exports/manifest.dm
index 60515781f9..d59ef1093f 100644
--- a/code/modules/cargo/exports/manifest.dm
+++ b/code/modules/cargo/exports/manifest.dm
@@ -76,19 +76,3 @@
/datum/export/manifest_correct_denied/get_cost(obj/O)
var/obj/item/paper/fluff/jobs/cargo/manifest/M = O
return ..() - M.order_cost
-
-// Paper work done correctly
-
-/datum/export/paperwork_correct
- cost = 120 // finicky number 20 x 120 = 2400 per crate
- k_elasticity = 0
- unit_name = "correct paperwork"
- export_types = list(/obj/item/folder/paperwork_correct)
-
-// Paper work not done retruned
-
-/datum/export/paperwork_incorrect
- cost = -500 // Failed to meet NT standers
- k_elasticity = 0
- unit_name = "returned incorrect paperwork"
- export_types = list(/obj/item/folder/paperwork)
diff --git a/code/modules/cargo/packs/materials.dm b/code/modules/cargo/packs/materials.dm
index 6c1742896f..1ce3a6b084 100644
--- a/code/modules/cargo/packs/materials.dm
+++ b/code/modules/cargo/packs/materials.dm
@@ -129,14 +129,6 @@
/obj/item/rcd_ammo)
crate_name = "rcd ammo"
-/datum/supply_pack/materials/loom
- name = "Loom"
- desc = "A large pre-made loom."
- cost = 1000
- contains = list(/obj/structure/loom/unanchored)
- crate_name = "loom crate"
- crate_type = /obj/structure/closet/crate/large
-
//////////////////////////////////////////////////////////////////////////////
///////////////////////////// Canisters //////////////////////////////////////
//////////////////////////////////////////////////////////////////////////////
diff --git a/code/modules/cargo/packs/misc.dm b/code/modules/cargo/packs/misc.dm
index dfaa6f0027..5265b2c2f6 100644
--- a/code/modules/cargo/packs/misc.dm
+++ b/code/modules/cargo/packs/misc.dm
@@ -87,37 +87,12 @@
crate_type = /obj/structure/closet/crate/wooden
crate_name = "calligraphy crate"
-/datum/supply_pack/misc/paper_work
- name = "Freelance Paper work"
- desc = "The Nanotrasen Primary Bureaucratic Database Intelligence (PDBI) reports that the station has not completed its funding and grant paperwork this solar cycle. In order to gain further funding, your station is required to fill out (10) ten of these forms or no additional capital will be disbursed. We have sent you ten copies of the following form and we expect every one to be up to Nanotrasen Standards." // Disbursement. It's not a typo, look it up.
- cost = 700 // Net of 0 credits but makes (120 x 10 = 1200)
- contains = list(/obj/item/folder/paperwork,
- /obj/item/pen/fountain
- )
- crate_name = "Paperwork"
-
-/datum/supply_pack/misc/paper_work/generate()
- . = ..()
- for(var/i in 1 to 9)
- new /obj/item/folder/paperwork(.)
//////////////////////////////////////////////////////////////////////////////
//////////////////////////////// Entertainment ///////////////////////////////
//////////////////////////////////////////////////////////////////////////////
-/datum/supply_pack/misc/randombedsheets
- name = "Bedsheet Crate (R)"
- desc = "Snuggle up in some sweet sheets with this assorted bedsheet crate. Each set comes with eight random bedsheets for your slumbering pleasure!"
- cost = 2000
- contains = list(/obj/item/bedsheet/random)
- crate_name = "random bedsheet crate"
-
-/datum/supply_pack/misc/randombedsheets/generate()
- . = ..()
- for(var/i in 1 to 7)
- new /obj/item/bedsheet/random(.)
-
/datum/supply_pack/misc/coloredsheets
- name = "Bedsheet Crate (C)"
+ name = "Bedsheet Crate"
desc = "Give your night life a splash of color with this crate filled with bedsheets! Contains a total of nine different-colored sheets."
cost = 1250
contains = list(/obj/item/bedsheet/blue,
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 07/89] 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 08/89] 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 09/89] 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 10/89] 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 11/89] 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 ccfce7cb6498898f404948983e5ccc75d866490b Mon Sep 17 00:00:00 2001
From: Ghommie <42542238+Ghommie@users.noreply.github.com>
Date: Tue, 5 May 2020 06:22:58 +0200
Subject: [PATCH 12/89] Makes wings/horns colors dna features instead of human
defines
---
code/__HELPERS/mobs.dm | 6 ++--
code/modules/client/preferences.dm | 25 ++++++-------
code/modules/client/preferences_savefile.dm | 36 ++++++-------------
.../mob/dead/new_player/preferences_setup.dm | 2 --
.../mob/living/carbon/human/human_defines.dm | 6 +---
.../mob/living/carbon/human/species.dm | 16 ++++-----
6 files changed, 32 insertions(+), 59 deletions(-)
diff --git a/code/__HELPERS/mobs.dm b/code/__HELPERS/mobs.dm
index e69052ad8d..e0714a87b3 100644
--- a/code/__HELPERS/mobs.dm
+++ b/code/__HELPERS/mobs.dm
@@ -163,9 +163,11 @@
"tail_lizard" = pick(GLOB.tails_list_lizard),
"tail_human" = "None",
"wings" = "None",
+ "wings_color" = "FFF",
"deco_wings" = "None",
"snout" = pick(GLOB.snouts_list),
- "horns" = pick(GLOB.horns_list),
+ "horns" = "None",
+ "horns_color" = "85615a",
"ears" = "None",
"frills" = pick(GLOB.frills_list),
"spines" = pick(GLOB.spines_list),
@@ -174,7 +176,7 @@
"caps" = pick(GLOB.caps_list),
"insect_wings" = pick(GLOB.insect_wings_list),
"insect_fluff" = "None",
- "insect_markings" = pick(GLOB.insect_markings_list),
+ "insect_markings" = pick(GLOB.insect_markings_list),
"taur" = "None",
"mam_body_markings" = snowflake_markings_list.len ? pick(snowflake_markings_list) : "None",
"mam_ears" = snowflake_ears_list ? pick(snowflake_ears_list) : "None",
diff --git a/code/modules/client/preferences.dm b/code/modules/client/preferences.dm
index abe43e38ae..bb3a435258 100644
--- a/code/modules/client/preferences.dm
+++ b/code/modules/client/preferences.dm
@@ -94,8 +94,6 @@ GLOBAL_LIST_EMPTY(preferences_datums)
var/skin_tone = "caucasian1" //Skin color
var/use_custom_skin_tone = FALSE
var/eye_color = "000" //Eye color
- var/horn_color = "85615a" //Horn color
- var/wing_color = "fff" //Wing color
var/datum/species/pref_species = new /datum/species/human() //Mutant race
var/list/features = list("mcolor" = "FFF",
"mcolor2" = "FFF",
@@ -104,8 +102,10 @@ GLOBAL_LIST_EMPTY(preferences_datums)
"tail_human" = "None",
"snout" = "Round",
"horns" = "None",
+ "horns_color" = "85615a",
"ears" = "None",
"wings" = "None",
+ "wings_color" = "FFF",
"frills" = "None",
"deco_wings" = "None",
"spines" = "None",
@@ -496,7 +496,7 @@ GLOBAL_LIST_EMPTY(preferences_datums)
dat += "Horns
"
dat += "[features["horns"]]"
- dat += " Change
"
+ dat += " Change
"
mutant_category++
@@ -609,7 +609,7 @@ GLOBAL_LIST_EMPTY(preferences_datums)
dat += "Decorative wings
"
dat += "[features["deco_wings"]]"
- dat += " Change
"
+ dat += " Change
"
if(pref_species.mutant_bodyparts["insect_wings"])
if(!mutant_category)
@@ -618,7 +618,7 @@ GLOBAL_LIST_EMPTY(preferences_datums)
dat += "Insect wings
"
dat += "[features["insect_wings"]]"
- dat += " Change
"
+ dat += " Change
"
mutant_category++
if(mutant_category >= MAX_MUTANT_ROWS)
dat += ""
@@ -1770,12 +1770,12 @@ GLOBAL_LIST_EMPTY(preferences_datums)
features["horns"] = new_horns
if("horns_color")
- var/new_horn_color = input(user, "Choose your character's horn colour:", "Character Preference","#"+horn_color) as color|null
+ var/new_horn_color = input(user, "Choose your character's horn colour:", "Character Preference","#"+features["horns_color"]) as color|null
if(new_horn_color)
if (new_horn_color == "#000000")
- horn_color = "#85615A"
+ features["horns_color"] = "85615A"
else
- horn_color = sanitize_hexcolor(new_horn_color)
+ features["horns_color"] = sanitize_hexcolor(new_horn_color)
if("wings")
var/new_wings
@@ -1784,12 +1784,12 @@ GLOBAL_LIST_EMPTY(preferences_datums)
features["wings"] = new_wings
if("wings_color")
- var/new_wing_color = input(user, "Choose your character's wing colour:", "Character Preference","#"+wing_color) as color|null
+ var/new_wing_color = input(user, "Choose your character's wing colour:", "Character Preference","#"+features["wings_color"]) as color|null
if(new_wing_color)
if (new_wing_color == "#000000")
- wing_color = "#FFFFFF"
+ features["wings_color"] = "#FFFFFF"
else
- wing_color = sanitize_hexcolor(new_wing_color)
+ features["wings_color"] = sanitize_hexcolor(new_wing_color)
if("frills")
var/new_frills
@@ -2459,9 +2459,6 @@ GLOBAL_LIST_EMPTY(preferences_datums)
organ_eyes.old_eye_color = eye_color
character.hair_color = hair_color
character.facial_hair_color = facial_hair_color
- character.horn_color = horn_color
- character.wing_color = wing_color
-
character.skin_tone = skin_tone
character.dna.skin_tone_override = use_custom_skin_tone ? skin_tone : null
character.hair_style = hair_style
diff --git a/code/modules/client/preferences_savefile.dm b/code/modules/client/preferences_savefile.dm
index 98b87f8d70..6872def704 100644
--- a/code/modules/client/preferences_savefile.dm
+++ b/code/modules/client/preferences_savefile.dm
@@ -5,7 +5,7 @@
// You do not need to raise this if you are adding new values that have sane defaults.
// Only raise this value when changing the meaning/format/name/layout of an existing value
// where you would want the updater procs below to run
-#define SAVEFILE_VERSION_MAX 29
+#define SAVEFILE_VERSION_MAX 30
/*
SAVEFILE UPDATING/VERSIONING - 'Simplified', or rather, more coder-friendly ~Carn
@@ -181,7 +181,7 @@ SAVEFILE UPDATING/VERSIONING - 'Simplified', or rather, more coder-friendly ~Car
if(lickable)
ENABLE_BITFIELD(vore_flags,LICKABLE)
- if(current_version < 29)
+ if(current_version < 30)
switch(features["taur"])
if("Husky", "Lab", "Shepherd", "Fox", "Wolf")
features["taur"] = "Canine"
@@ -190,6 +190,10 @@ SAVEFILE UPDATING/VERSIONING - 'Simplified', or rather, more coder-friendly ~Car
if("Cow")
features["taur"] = "Cow (Spotted)"
+ if(current_version < 31)
+ S["wing_color"] >> features["wings_color"]
+ S["horn_color"] >> features["horns_color"]
+
/datum/preferences/proc/load_path(ckey,filename="preferences.sav")
if(!ckey)
return
@@ -421,15 +425,6 @@ SAVEFILE UPDATING/VERSIONING - 'Simplified', or rather, more coder-friendly ~Car
if(newtype)
pref_species = new newtype
- if(!S["features["mcolor"]"] || S["features["mcolor"]"] == "#000")
- WRITE_FILE(S["features["mcolor"]"] , "#FFF")
-
- if(!S["features["horn_color"]"] || S["features["horn_color"]"] == "#000")
- WRITE_FILE(S["features["horn_color"]"] , "#85615a")
-
- if(!S["features["wing_color"]"] || S["features["wing_color"]"] == "#000")
- WRITE_FILE(S["features["wing_color"]"] , "#FFF")
-
//Character
S["real_name"] >> real_name
S["nameless"] >> nameless
@@ -453,8 +448,6 @@ SAVEFILE UPDATING/VERSIONING - 'Simplified', or rather, more coder-friendly ~Car
S["shirt_color"] >> shirt_color
S["socks"] >> socks
S["socks_color"] >> socks_color
- S["horn_color"] >> horn_color
- S["wing_color"] >> wing_color
S["backbag"] >> backbag
S["jumpsuit_style"] >> jumpsuit_style
S["uplink_loc"] >> uplink_spawn_loc
@@ -564,15 +557,6 @@ SAVEFILE UPDATING/VERSIONING - 'Simplified', or rather, more coder-friendly ~Car
if(!custom_names[custom_name_id])
custom_names[custom_name_id] = get_default_name(custom_name_id)
- if(!features["mcolor"] || features["mcolor"] == "#000")
- features["mcolor"] = pick("FFFFFF","7F7F7F", "7FFF7F", "7F7FFF", "FF7F7F", "7FFFFF", "FF7FFF", "FFFF7F")
-
- if(!features["horn_color"] || features["horn_color"] == "#000")
- features["horn_color"] = "85615a"
-
- if(!features["wing_color"] || features["wing_color"] == "#000")
- features["wing_color"] = "FFFFFF"
-
nameless = sanitize_integer(nameless, 0, 1, initial(nameless))
be_random_name = sanitize_integer(be_random_name, 0, 1, initial(be_random_name))
be_random_body = sanitize_integer(be_random_body, 0, 1, initial(be_random_body))
@@ -599,8 +583,8 @@ SAVEFILE UPDATING/VERSIONING - 'Simplified', or rather, more coder-friendly ~Car
else
skin_tone = sanitize_inlist(skin_tone, GLOB.skin_tones - GLOB.nonstandard_skin_tones, initial(skin_tone))
- horn_color = sanitize_hexcolor(horn_color, 3, FALSE)
- wing_color = sanitize_hexcolor(wing_color, 3, FALSE, "#FFFFFF")
+ features["horns_color"] = sanitize_hexcolor(features["horns_color"], 3, FALSE, "85615a")
+ features["wings_color"] = sanitize_hexcolor(features["wings_color"], 3, FALSE, "FFFFFF")
backbag = sanitize_inlist(backbag, GLOB.backbaglist, initial(backbag))
jumpsuit_style = sanitize_inlist(jumpsuit_style, GLOB.jumpsuitlist, initial(jumpsuit_style))
uplink_spawn_loc = sanitize_inlist(uplink_spawn_loc, GLOB.uplink_spawn_loc_list, initial(uplink_spawn_loc))
@@ -714,8 +698,8 @@ SAVEFILE UPDATING/VERSIONING - 'Simplified', or rather, more coder-friendly ~Car
WRITE_FILE(S["shirt_color"] , shirt_color)
WRITE_FILE(S["socks"] , socks)
WRITE_FILE(S["socks_color"] , socks_color)
- WRITE_FILE(S["horn_color"] , horn_color)
- WRITE_FILE(S["wing_color"] , wing_color)
+ WRITE_FILE(S["horns_color"] , features["horns_color"])
+ WRITE_FILE(S["wings_color"] , features["wings_color"])
WRITE_FILE(S["backbag"] , backbag)
WRITE_FILE(S["jumpsuit_style"] , jumpsuit_style)
WRITE_FILE(S["uplink_loc"] , uplink_spawn_loc)
diff --git a/code/modules/mob/dead/new_player/preferences_setup.dm b/code/modules/mob/dead/new_player/preferences_setup.dm
index 317efc2c1f..9a0d9f9242 100644
--- a/code/modules/mob/dead/new_player/preferences_setup.dm
+++ b/code/modules/mob/dead/new_player/preferences_setup.dm
@@ -18,8 +18,6 @@
hair_color = random_short_color()
facial_hair_color = hair_color
eye_color = random_eye_color()
- horn_color = "85615a"
- wing_color = "fff"
if(!pref_species)
var/rando_race = pick(GLOB.roundstart_races)
pref_species = new rando_race()
diff --git a/code/modules/mob/living/carbon/human/human_defines.dm b/code/modules/mob/living/carbon/human/human_defines.dm
index 68c312b19c..59766b03a4 100644
--- a/code/modules/mob/living/carbon/human/human_defines.dm
+++ b/code/modules/mob/living/carbon/human/human_defines.dm
@@ -11,7 +11,7 @@
status_flags = CANSTUN|CANKNOCKDOWN|CANUNCONSCIOUS|CANPUSH|CANSTAGGER
blocks_emissive = EMISSIVE_BLOCK_UNIQUE
-
+
//Hair colour and style
var/hair_color = "000"
var/hair_style = "Bald"
@@ -23,10 +23,6 @@
//Eye colour
var/eye_color = "000"
- var/horn_color = "85615a" //specific horn colors, because why not?
-
- var/wing_color = "fff" //wings too
-
var/skin_tone = "caucasian1" //Skin tone
var/lip_style = null //no lipstick by default- arguably misleading, as it could be used for general makeup
diff --git a/code/modules/mob/living/carbon/human/species.dm b/code/modules/mob/living/carbon/human/species.dm
index 978976111c..aeab0042ac 100644
--- a/code/modules/mob/living/carbon/human/species.dm
+++ b/code/modules/mob/living/carbon/human/species.dm
@@ -35,10 +35,6 @@ GLOBAL_LIST_EMPTY(roundstart_race_names)
var/hair_color // this allows races to have specific hair colors... if null, it uses the H's hair/facial hair colors. if "mutcolor", it uses the H's mutant_color
var/hair_alpha = 255 // the alpha used by the hair. 255 is completely solid, 0 is transparent.
-
- var/horn_color //specific horn colors, because why not?
- var/wing_color
-
var/use_skintones = NO_SKINTONES // does it use skintones or not? (spoiler alert this is only used by humans)
var/exotic_blood = "" // If your race wants to bleed something other than bog standard blood, change this to reagent id.
var/exotic_bloodtype = "" //If your race uses a non standard bloodtype (A+, O-, AB-, etc)
@@ -882,9 +878,9 @@ GLOBAL_LIST_EMPTY(roundstart_race_names)
if(EYECOLOR)
accessory_overlay.color = "#[H.eye_color]"
if(HORNCOLOR)
- accessory_overlay.color = "#[H.horn_color]"
+ accessory_overlay.color = "#[H.dna.features["horns_color"]]"
if(WINGCOLOR)
- accessory_overlay.color = "#[H.wing_color]"
+ accessory_overlay.color = "#[H.dna.features["wings_color"]]"
else
accessory_overlay.color = forced_colour
else
@@ -938,9 +934,9 @@ GLOBAL_LIST_EMPTY(roundstart_race_names)
extra_accessory_overlay.color = "#[H.eye_color]"
if(HORNCOLOR)
- extra_accessory_overlay.color = "#[H.horn_color]"
+ extra_accessory_overlay.color = "#[H.dna.features["horns_color"]]"
if(WINGCOLOR)
- extra_accessory_overlay.color = "#[H.wing_color]"
+ extra_accessory_overlay.color = "#[H.dna.features["wings_color"]]"
if(OFFSET_MUTPARTS in H.dna.species.offset_features)
extra_accessory_overlay.pixel_x += H.dna.species.offset_features[OFFSET_MUTPARTS][1]
@@ -979,9 +975,9 @@ GLOBAL_LIST_EMPTY(roundstart_race_names)
else
extra2_accessory_overlay.color = "#[H.hair_color]"
if(HORNCOLOR)
- extra2_accessory_overlay.color = "#[H.horn_color]"
+ extra2_accessory_overlay.color = "#[H.dna.features["horns_color"]]"
if(WINGCOLOR)
- extra2_accessory_overlay.color = "#[H.wing_color]"
+ extra2_accessory_overlay.color = "#[H.dna.features["wings_color"]]"
if(OFFSET_MUTPARTS in H.dna.species.offset_features)
extra2_accessory_overlay.pixel_x += H.dna.species.offset_features[OFFSET_MUTPARTS][1]
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 13/89] 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 14/89] 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 15/89] 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 e9ccc3ac6225a70ab21c3f9605fbdaf0116a1acc Mon Sep 17 00:00:00 2001
From: Ghommie <42542238+Ghommie@users.noreply.github.com>
Date: Wed, 6 May 2020 01:03:54 +0200
Subject: [PATCH 16/89] Test and update.
---
code/modules/mob/living/living.dm | 4 ++--
code/modules/mob/mob_movement.dm | 3 +--
2 files changed, 3 insertions(+), 4 deletions(-)
diff --git a/code/modules/mob/living/living.dm b/code/modules/mob/living/living.dm
index 5189f47fbc..4736ae5b4a 100644
--- a/code/modules/mob/living/living.dm
+++ b/code/modules/mob/living/living.dm
@@ -319,8 +319,8 @@
set_pull_offsets(M, state)
/mob/living/proc/set_pull_offsets(mob/living/M, grab_state = GRAB_PASSIVE)
- if(M.buckled)
- return //don't make them change direction or offset them if they're buckled into something.
+ if(M.buckled || M.combat_flags & COMBAT_FLAG_COMBAT_ACTIVE)
+ return //don't make them change direction or offset them if they're buckled into something or in combat mode.
var/offset = 0
switch(grab_state)
if(GRAB_PASSIVE)
diff --git a/code/modules/mob/mob_movement.dm b/code/modules/mob/mob_movement.dm
index adf3e349b7..872889fd53 100644
--- a/code/modules/mob/mob_movement.dm
+++ b/code/modules/mob/mob_movement.dm
@@ -100,8 +100,7 @@
if(mob.throwing)
mob.throwing.finalize(FALSE)
- var/atom/movable/P = mob.pulling
- if(P && !ismob(P) && P.density)
+ if(mob.pulling && !(combat_flags & COMBAT_FLAG_COMBAT_ACTIVE))
mob.setDir(turn(mob.dir, 180))
SEND_SIGNAL(mob, COMSIG_MOB_CLIENT_MOVE, src, direction, n, oldloc)
From 94d67ab640b5d7a18c592f2aad71035ee0ce9169 Mon Sep 17 00:00:00 2001
From: Ghom <42542238+Ghommie@users.noreply.github.com>
Date: Wed, 6 May 2020 01:15:30 +0200
Subject: [PATCH 17/89] Update mob_movement.dm
---
code/modules/mob/mob_movement.dm | 5 +++--
1 file changed, 3 insertions(+), 2 deletions(-)
diff --git a/code/modules/mob/mob_movement.dm b/code/modules/mob/mob_movement.dm
index 872889fd53..a607da195a 100644
--- a/code/modules/mob/mob_movement.dm
+++ b/code/modules/mob/mob_movement.dm
@@ -100,8 +100,9 @@
if(mob.throwing)
mob.throwing.finalize(FALSE)
- if(mob.pulling && !(combat_flags & COMBAT_FLAG_COMBAT_ACTIVE))
- mob.setDir(turn(mob.dir, 180))
+ var/mob/living/L = mob
+ if(L.pulling && !(L.combat_flags & COMBAT_FLAG_COMBAT_ACTIVE))
+ L.setDir(turn(L.dir, 180))
SEND_SIGNAL(mob, COMSIG_MOB_CLIENT_MOVE, src, direction, n, oldloc)
From 106fb6496eb6d5b972ece921e0410af96d4e3c63 Mon Sep 17 00:00:00 2001
From: Trilbyspaceclone <30435998+Trilbyspaceclone@users.noreply.github.com>
Date: Wed, 6 May 2020 05:30:54 -0400
Subject: [PATCH 18/89] Update hypospray.dm
---
code/modules/reagents/reagent_containers/hypospray.dm | 4 ++--
1 file changed, 2 insertions(+), 2 deletions(-)
diff --git a/code/modules/reagents/reagent_containers/hypospray.dm b/code/modules/reagents/reagent_containers/hypospray.dm
index 9555f5b64b..b24e6ab733 100644
--- a/code/modules/reagents/reagent_containers/hypospray.dm
+++ b/code/modules/reagents/reagent_containers/hypospray.dm
@@ -207,8 +207,8 @@
#define DELUXE_SELF_SPRAY 10
#define DELUXE_SELF_INJECT 10
-#define COMBAT_WAIT_SPRAY 0
-#define COMBAT_WAIT_INJECT 0
+#define COMBAT_WAIT_SPRAY 15
+#define COMBAT_WAIT_INJECT 15
#define COMBAT_SELF_SPRAY 0
#define COMBAT_SELF_INJECT 0
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 19/89] 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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