diff --git a/.editorconfig b/.editorconfig
index 13dcd5e029..c44572fbf9 100644
--- a/.editorconfig
+++ b/.editorconfig
@@ -1,11 +1,14 @@
[*]
-insert_final_newline = true
indent_style = tab
indent_size = 4
+charset = utf-8
+trim_trailing_whitespace = true
+insert_final_newline = true
+#end_of_line = lf
[*.yml]
indent_style = space
indent_size = 2
-[*.txt]
-insert_final_newline = false
+[*.py]
+indent_style = space
diff --git a/.gitattributes b/.gitattributes
index a0a624af04..c447869d3e 100644
--- a/.gitattributes
+++ b/.gitattributes
@@ -1,9 +1,42 @@
-# merger hooks, run tools/hooks/install.bat or install.sh to set up
-*.dmm merge=dmm
-*.dmi merge=dmi
-
-# force changelog merging to use union
-html/changelog.html merge=union
-
-# force all files to be LF
* text=auto
+
+## Enforce text mode and LF line breaks
+## porter note: not yet LFing dm.
+# *.bat text eol=lf
+*.css text eol=lf
+# *.dm text eol=lf
+# *.dme text eol=lf
+# *.dmf text eol=lf
+*.htm text eol=lf
+*.html text eol=lf
+*.js text eol=lf
+*.json text eol=lf
+*.jsx text eol=lf
+# *.md text eol=lf
+*.py text eol=lf
+*.scss text eol=lf
+# *.sh text eol=lf
+# *.sql text eol=lf
+*.svg text eol=lf
+*.ts text eol=lf
+*.tsx text eol=lf
+# *.txt text eol=lf
+*.yaml text eol=lf
+*.yml text eol=lf
+
+## Enforce binary mode
+*.bmp binary
+*.dll binary
+*.dmb binary
+*.exe binary
+*.gif binary
+*.jpg binary
+*.png binary
+*.so binary
+
+## Merger hooks, run tools/hooks/install.bat or install.sh to set up
+*.dmm text eol=lf merge=dmm
+*.dmi binary merge=dmi
+
+## Force changelog merging to use union
+html/changelog.html text eol=lf merge=union
diff --git a/.github/workflows/autobuild_tgui.yml b/.github/workflows/autobuild_tgui.yml
deleted file mode 100644
index 226ea2b7ce..0000000000
--- a/.github/workflows/autobuild_tgui.yml
+++ /dev/null
@@ -1,36 +0,0 @@
-name: Autobuild TGUI
-
-on:
- push:
- branches:
- - 'master'
- paths:
- - 'tgui/**.js'
- - 'tgui/**.scss'
-
-jobs:
- build:
- name: Rebuild TGUI
- runs-on: ubuntu-latest
- steps:
- - name: Checkout
- uses: actions/checkout@v1
- with:
- fetch-depth: 25
- - name: Setup Node
- uses: actions/setup-node@v1
- with:
- node-version: '>=12.13'
- - name: Build TGUI
- run: bin/tgui --ci
- working-directory: ./tgui
- - name: Commit Artifacts
- run: |
- git config --local user.email "action@github.com"
- git config --local user.name "TGUI"
- git pull origin master
- git commit -m "Automatic TGUI Rebuild [ci skip]" -a || true
- - name: Push Artifacts
- uses: ad-m/github-push-action@master
- with:
- github_token: ${{ secrets.GITHUB_MASTER_KEY }}
diff --git a/.github/workflows/round_id_linker.yml b/.github/workflows/round_id_linker.yml
new file mode 100644
index 0000000000..37998a93eb
--- /dev/null
+++ b/.github/workflows/round_id_linker.yml
@@ -0,0 +1,12 @@
+name: "Round ID Linker"
+on:
+ issues:
+ types: [opened]
+
+jobs:
+ link_rounds:
+ runs-on: ubuntu-latest
+ steps:
+ - uses: Cidatel-Station-13/round_linker@master #notice: fork the round linkies from tg!!
+ with:
+ repo-token: ${{ secrets.GITHUB_TOKEN }}
diff --git a/.gitignore b/.gitignore
index f65245c021..e885761104 100644
--- a/.gitignore
+++ b/.gitignore
@@ -198,13 +198,36 @@ Temporary Items
!/.vscode/extensions.json
tools/MapAtmosFixer/MapAtmosFixer/obj/*
tools/MapAtmosFixer/MapAtmosFixer/bin/*
+tools/CreditsTool/bin/*
+tools/CreditsTool/obj/*
#GitHub Atom
.atom-build.json
-#Jukebox audio files
-/config/jukebox_music/**/*
+#KDevelop and Kate
+*.kdev4*
+*.kate-swp
#extra map stuff
/_maps/**/backup/
/_maps/templates.dm
+
+#dmdoc default folder
+/dmdoc
+
+# Ignore custom music and title screens (amend as appropriate)
+/config/jukebox_music/sounds/*
+!/config/jukebox_music/sounds/exclude
+/config/title_music/sounds/*
+!/config/title_music/sounds/exclude
+/config/title_screens/images/*
+!/config/title_screens/images/exclude
+
+#Linux docker
+tools/LinuxOneShot/SetupProgram/obj/*
+tools/LinuxOneShot/SetupProgram/bin/*
+tools/LinuxOneShot/SetupProgram/.vs
+tools/LinuxOneShot/Database
+tools/LinuxOneShot/TGS_Config
+tools/LinuxOneShot/TGS_Instances
+tools/LinuxOneShot/TGS_Logs
diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml
new file mode 100644
index 0000000000..11d27b55d3
--- /dev/null
+++ b/.gitlab-ci.yml
@@ -0,0 +1,11 @@
+build:
+ stage: build
+ rules:
+ - if: $CI_MERGE_REQUEST_IID || $CI_COMMIT_REF_NAME == "master"
+ changes:
+ - tgui/**/*.js
+ - tgui/**/*.scss
+ when: always
+ image: node:lts
+ script:
+ - tgui/bin/tgui --ci
diff --git a/.gitmodules b/.gitmodules
deleted file mode 100644
index e69de29bb2..0000000000
diff --git a/.travis.yml b/.travis.yml
index 2214df3aee..83ef6fa8ff 100644
--- a/.travis.yml
+++ b/.travis.yml
@@ -1,13 +1,13 @@
language: generic
+os: linux
dist: xenial
-sudo: false
branches:
except:
- ___TGS3TempBranch
- ___TGSTempBranch
-matrix:
+jobs:
include:
- name: "Run Linters"
addons:
@@ -16,9 +16,15 @@ matrix:
- python3
- python3-pip
- python3-setuptools
+ - pcregrep
+ - rustc
+ - cargo
+ cache:
+ directories:
+ - $HOME/SpacemanDMM
install:
- tools/travis/install_build_tools.sh
- - tools/travis/install_dreamchecker.sh
+ - tools/travis/install_spaceman_dmm.sh dreamchecker
script:
- tools/travis/check_filedirs.sh tgstation.dme
- tools/travis/check_changelogs.sh
@@ -76,3 +82,21 @@ matrix:
script:
- tools/travis/dm.sh -DTRAVISBUILDING tgstation.dme || travis_terminate 1
- tools/travis/run_server.sh
+
+ # - name: "Generate Documentation"
+ # # Only run for non-PR commits to the real master branch.
+ # if: branch = master AND head_branch IS blank
+ # install:
+ # - tools/travis/install_spaceman_dmm.sh dmdoc
+ # before_script:
+ # # Travis checks out a hash, try to get back on a branch.
+ # - git checkout $TRAVIS_BRANCH || true
+ # script:
+ # - ~/dmdoc
+ # - touch dmdoc/.nojekyll
+ # deploy:
+ # provider: pages
+ # skip_cleanup: true
+ # local_dir: dmdoc
+ # token: $DMDOC_GITHUB_TOKEN
+ # fqdn: codedocs.tgstation13.org
diff --git a/_maps/map_files/Deltastation/DeltaStation2.dmm b/_maps/map_files/Deltastation/DeltaStation2.dmm
index 17253e2665..943aa85efd 100644
--- a/_maps/map_files/Deltastation/DeltaStation2.dmm
+++ b/_maps/map_files/Deltastation/DeltaStation2.dmm
@@ -12556,15 +12556,11 @@
/area/engine/atmospherics_engine)
"aAb" = (
/turf/closed/wall,
-/area/hydroponics/garden/abandoned{
- name = "Maintenance Garden"
- })
+/area/hydroponics/garden/abandoned)
"aAc" = (
/obj/machinery/atmospherics/pipe/simple/scrubbers/hidden,
/turf/closed/wall,
-/area/hydroponics/garden/abandoned{
- name = "Maintenance Garden"
- })
+/area/hydroponics/garden/abandoned)
"aAd" = (
/obj/effect/decal/cleanable/dirt,
/obj/effect/turf_decal/tile/neutral{
@@ -13138,9 +13134,7 @@
/obj/effect/decal/cleanable/dirt,
/obj/effect/turf_decal/delivery,
/turf/open/floor/plasteel,
-/area/hydroponics/garden/abandoned{
- name = "Maintenance Garden"
- })
+/area/hydroponics/garden/abandoned)
"aBh" = (
/obj/item/kirbyplants/random,
/obj/machinery/light/small{
@@ -13149,25 +13143,19 @@
/obj/effect/decal/cleanable/dirt,
/obj/effect/turf_decal/delivery,
/turf/open/floor/plasteel,
-/area/hydroponics/garden/abandoned{
- name = "Maintenance Garden"
- })
+/area/hydroponics/garden/abandoned)
"aBi" = (
/obj/structure/table,
/obj/item/clothing/suit/apron/overalls,
/obj/item/cultivator,
/obj/effect/turf_decal/bot,
/turf/open/floor/plasteel,
-/area/hydroponics/garden/abandoned{
- name = "Maintenance Garden"
- })
+/area/hydroponics/garden/abandoned)
"aBj" = (
/obj/machinery/biogenerator,
/obj/effect/turf_decal/delivery,
/turf/open/floor/plasteel,
-/area/hydroponics/garden/abandoned{
- name = "Maintenance Garden"
- })
+/area/hydroponics/garden/abandoned)
"aBk" = (
/obj/structure/table,
/obj/item/reagent_containers/food/snacks/grown/wheat,
@@ -13182,18 +13170,14 @@
pixel_y = 4
},
/turf/open/floor/plasteel,
-/area/hydroponics/garden/abandoned{
- name = "Maintenance Garden"
- })
+/area/hydroponics/garden/abandoned)
"aBl" = (
/obj/machinery/hydroponics/soil,
/obj/item/seeds/tea,
/obj/effect/decal/cleanable/dirt,
/obj/effect/turf_decal/delivery,
/turf/open/floor/plasteel,
-/area/hydroponics/garden/abandoned{
- name = "Maintenance Garden"
- })
+/area/hydroponics/garden/abandoned)
"aBm" = (
/obj/item/kirbyplants/random,
/obj/machinery/status_display/evac{
@@ -13201,9 +13185,7 @@
},
/obj/effect/turf_decal/delivery,
/turf/open/floor/plasteel,
-/area/hydroponics/garden/abandoned{
- name = "Maintenance Garden"
- })
+/area/hydroponics/garden/abandoned)
"aBn" = (
/obj/structure/table,
/obj/item/stack/packageWrap,
@@ -13213,9 +13195,7 @@
/obj/item/reagent_containers/food/snacks/grown/cherries,
/obj/effect/turf_decal/bot,
/turf/open/floor/plasteel,
-/area/hydroponics/garden/abandoned{
- name = "Maintenance Garden"
- })
+/area/hydroponics/garden/abandoned)
"aBo" = (
/obj/item/kirbyplants/random,
/obj/machinery/light/small{
@@ -13225,18 +13205,14 @@
/obj/effect/turf_decal/delivery,
/obj/machinery/atmospherics/pipe/simple/scrubbers/hidden,
/turf/open/floor/plasteel,
-/area/hydroponics/garden/abandoned{
- name = "Maintenance Garden"
- })
+/area/hydroponics/garden/abandoned)
"aBp" = (
/obj/machinery/hydroponics/soil,
/obj/item/seeds/poppy,
/obj/effect/decal/cleanable/dirt,
/obj/effect/turf_decal/delivery,
/turf/open/floor/plasteel,
-/area/hydroponics/garden/abandoned{
- name = "Maintenance Garden"
- })
+/area/hydroponics/garden/abandoned)
"aBq" = (
/obj/structure/cable/white{
icon_state = "2-4"
@@ -13817,33 +13793,25 @@
/area/engine/atmospherics_engine)
"aCv" = (
/turf/open/floor/plasteel,
-/area/hydroponics/garden/abandoned{
- name = "Maintenance Garden"
- })
+/area/hydroponics/garden/abandoned)
"aCw" = (
/obj/effect/turf_decal/delivery,
/obj/machinery/hydroponics/soil,
/obj/item/seeds/watermelon,
/turf/open/floor/plasteel,
-/area/hydroponics/garden/abandoned{
- name = "Maintenance Garden"
- })
+/area/hydroponics/garden/abandoned)
"aCx" = (
/obj/machinery/atmospherics/components/unary/vent_scrubber/on{
dir = 4
},
/turf/open/floor/plasteel,
-/area/hydroponics/garden/abandoned{
- name = "Maintenance Garden"
- })
+/area/hydroponics/garden/abandoned)
"aCy" = (
/obj/machinery/atmospherics/pipe/manifold/scrubbers/hidden{
dir = 4
},
/turf/open/floor/plasteel,
-/area/hydroponics/garden/abandoned{
- name = "Maintenance Garden"
- })
+/area/hydroponics/garden/abandoned)
"aCz" = (
/obj/structure/cable/white{
icon_state = "1-2"
@@ -14459,21 +14427,15 @@
},
/obj/effect/turf_decal/bot,
/turf/open/floor/plasteel,
-/area/hydroponics/garden/abandoned{
- name = "Maintenance Garden"
- })
+/area/hydroponics/garden/abandoned)
"aDy" = (
/obj/machinery/atmospherics/components/unary/vent_pump/on,
/turf/open/floor/plasteel,
-/area/hydroponics/garden/abandoned{
- name = "Maintenance Garden"
- })
+/area/hydroponics/garden/abandoned)
"aDz" = (
/obj/machinery/atmospherics/pipe/simple/scrubbers/hidden,
/turf/open/floor/plasteel,
-/area/hydroponics/garden/abandoned{
- name = "Maintenance Garden"
- })
+/area/hydroponics/garden/abandoned)
"aDA" = (
/obj/structure/sink{
dir = 4;
@@ -14484,9 +14446,7 @@
},
/obj/effect/turf_decal/bot,
/turf/open/floor/plasteel,
-/area/hydroponics/garden/abandoned{
- name = "Maintenance Garden"
- })
+/area/hydroponics/garden/abandoned)
"aDB" = (
/obj/effect/decal/cleanable/dirt,
/obj/effect/decal/cleanable/dirt,
@@ -14968,34 +14928,26 @@
icon_state = "2-4"
},
/turf/open/floor/plasteel,
-/area/hydroponics/garden/abandoned{
- name = "Maintenance Garden"
- })
+/area/hydroponics/garden/abandoned)
"aEC" = (
/obj/structure/cable/white{
icon_state = "4-8"
},
/turf/open/floor/plasteel,
-/area/hydroponics/garden/abandoned{
- name = "Maintenance Garden"
- })
+/area/hydroponics/garden/abandoned)
"aED" = (
/obj/structure/cable/white{
icon_state = "4-8"
},
/obj/machinery/atmospherics/pipe/simple/supply/hidden,
/turf/open/floor/plasteel,
-/area/hydroponics/garden/abandoned{
- name = "Maintenance Garden"
- })
+/area/hydroponics/garden/abandoned)
"aEE" = (
/obj/structure/cable/white{
icon_state = "2-8"
},
/turf/open/floor/plasteel,
-/area/hydroponics/garden/abandoned{
- name = "Maintenance Garden"
- })
+/area/hydroponics/garden/abandoned)
"aEG" = (
/obj/effect/decal/cleanable/dirt,
/obj/structure/cable/white{
@@ -15628,18 +15580,14 @@
/obj/effect/decal/cleanable/dirt,
/obj/effect/turf_decal/delivery,
/turf/open/floor/plasteel,
-/area/hydroponics/garden/abandoned{
- name = "Maintenance Garden"
- })
+/area/hydroponics/garden/abandoned)
"aFK" = (
/obj/machinery/hydroponics/soil,
/obj/item/seeds/harebell,
/obj/machinery/light/small,
/obj/effect/turf_decal/delivery,
/turf/open/floor/plasteel,
-/area/hydroponics/garden/abandoned{
- name = "Maintenance Garden"
- })
+/area/hydroponics/garden/abandoned)
"aFL" = (
/obj/structure/table,
/obj/item/shovel/spade,
@@ -15658,9 +15606,7 @@
},
/obj/effect/turf_decal/bot,
/turf/open/floor/plasteel,
-/area/hydroponics/garden/abandoned{
- name = "Maintenance Garden"
- })
+/area/hydroponics/garden/abandoned)
"aFM" = (
/obj/machinery/status_display/evac{
pixel_y = -32
@@ -15669,34 +15615,26 @@
/obj/machinery/hydroponics/soil,
/obj/item/seeds/poppy/geranium,
/turf/open/floor/plasteel,
-/area/hydroponics/garden/abandoned{
- name = "Maintenance Garden"
- })
+/area/hydroponics/garden/abandoned)
"aFN" = (
/obj/structure/table,
/obj/item/plant_analyzer,
/obj/item/hatchet,
/obj/effect/turf_decal/bot,
/turf/open/floor/plasteel,
-/area/hydroponics/garden/abandoned{
- name = "Maintenance Garden"
- })
+/area/hydroponics/garden/abandoned)
"aFO" = (
/obj/machinery/atmospherics/pipe/simple/supply/hidden,
/obj/effect/turf_decal/delivery,
/turf/open/floor/plasteel,
-/area/hydroponics/garden/abandoned{
- name = "Maintenance Garden"
- })
+/area/hydroponics/garden/abandoned)
"aFP" = (
/obj/structure/cable/white{
icon_state = "1-2"
},
/obj/effect/turf_decal/delivery,
/turf/open/floor/plasteel,
-/area/hydroponics/garden/abandoned{
- name = "Maintenance Garden"
- })
+/area/hydroponics/garden/abandoned)
"aFQ" = (
/obj/structure/table,
/obj/item/crowbar,
@@ -15704,9 +15642,7 @@
/obj/item/reagent_containers/glass/bucket,
/obj/effect/turf_decal/bot,
/turf/open/floor/plasteel,
-/area/hydroponics/garden/abandoned{
- name = "Maintenance Garden"
- })
+/area/hydroponics/garden/abandoned)
"aFR" = (
/obj/machinery/hydroponics/soil,
/obj/item/seeds/tower,
@@ -15715,9 +15651,7 @@
/obj/effect/turf_decal/delivery,
/obj/machinery/atmospherics/pipe/simple/scrubbers/hidden,
/turf/open/floor/plasteel,
-/area/hydroponics/garden/abandoned{
- name = "Maintenance Garden"
- })
+/area/hydroponics/garden/abandoned)
"aFS" = (
/obj/structure/cable/white{
icon_state = "1-2"
@@ -16707,9 +16641,7 @@
"aHl" = (
/obj/effect/spawner/structure/window/reinforced,
/turf/open/floor/plating,
-/area/hydroponics/garden/abandoned{
- name = "Maintenance Garden"
- })
+/area/hydroponics/garden/abandoned)
"aHm" = (
/obj/machinery/door/airlock/maintenance_hatch{
name = "Maintenance Hatch";
@@ -16721,9 +16653,7 @@
dir = 1
},
/turf/open/floor/plasteel,
-/area/hydroponics/garden/abandoned{
- name = "Maintenance Garden"
- })
+/area/hydroponics/garden/abandoned)
"aHn" = (
/obj/machinery/door/airlock/maintenance_hatch{
name = "Maintenance Hatch";
@@ -16737,16 +16667,12 @@
dir = 1
},
/turf/open/floor/plasteel,
-/area/hydroponics/garden/abandoned{
- name = "Maintenance Garden"
- })
+/area/hydroponics/garden/abandoned)
"aHo" = (
/obj/effect/spawner/structure/window/reinforced,
/obj/machinery/atmospherics/pipe/simple/scrubbers/hidden,
/turf/open/floor/plating,
-/area/hydroponics/garden/abandoned{
- name = "Maintenance Garden"
- })
+/area/hydroponics/garden/abandoned)
"aHp" = (
/obj/effect/decal/cleanable/dirt,
/obj/structure/cable/white{
@@ -125347,9 +125273,7 @@
/obj/item/reagent_containers/glass/bucket,
/obj/effect/turf_decal/delivery,
/turf/open/floor/plasteel,
-/area/hydroponics/garden/abandoned{
- name = "Maintenance Garden"
- })
+/area/hydroponics/garden/abandoned)
"etO" = (
/obj/machinery/atmospherics/pipe/simple/supply/hidden{
dir = 4
@@ -125474,9 +125398,7 @@
/obj/machinery/hydroponics/soil,
/obj/item/seeds/grape,
/turf/open/floor/plasteel,
-/area/hydroponics/garden/abandoned{
- name = "Maintenance Garden"
- })
+/area/hydroponics/garden/abandoned)
"faI" = (
/obj/structure/cable/white{
icon_state = "1-2"
@@ -126395,9 +126317,7 @@
pixel_x = 6
},
/turf/open/floor/plasteel,
-/area/hydroponics/garden/abandoned{
- name = "Maintenance Garden"
- })
+/area/hydroponics/garden/abandoned)
"loH" = (
/obj/effect/turf_decal/tile/neutral{
dir = 1
diff --git a/_maps/map_files/KiloStation/KiloStation.dmm b/_maps/map_files/KiloStation/KiloStation.dmm
index f426ad8e97..51bfe5038e 100644
--- a/_maps/map_files/KiloStation/KiloStation.dmm
+++ b/_maps/map_files/KiloStation/KiloStation.dmm
@@ -75352,9 +75352,7 @@
/obj/machinery/atmospherics/pipe/simple/orange/visible{
dir = 4
},
-/obj/machinery/airalarm{
- pixel_z = 24
- },
+/obj/machinery/airalarm,
/obj/machinery/light/small,
/obj/effect/decal/cleanable/dirt,
/turf/open/floor/plating{
diff --git a/_maps/map_files/PubbyStation/job_changes.dm b/_maps/map_files/PubbyStation/job_changes.dm
deleted file mode 100644
index 726601725b..0000000000
--- a/_maps/map_files/PubbyStation/job_changes.dm
+++ /dev/null
@@ -1,20 +0,0 @@
-#define JOB_MODIFICATION_MAP_NAME "PubbyStation"
-
-/datum/job/hos/New()
- ..()
- MAP_JOB_CHECK
- access += ACCESS_CREMATORIUM
- minimal_access += ACCESS_CREMATORIUM
-
-/datum/job/warden/New()
- ..()
- MAP_JOB_CHECK
- access += ACCESS_CREMATORIUM
- minimal_access += ACCESS_CREMATORIUM
-
-/datum/job/officer/New()
- ..()
- MAP_JOB_CHECK
- access += ACCESS_CREMATORIUM
- minimal_access += ACCESS_CREMATORIUM
-
diff --git a/_maps/pubbystation.json b/_maps/pubbystation.json
index f99cca57c5..acf49b773b 100644
--- a/_maps/pubbystation.json
+++ b/_maps/pubbystation.json
@@ -7,5 +7,10 @@
"whiteship": "whiteship_pubby",
"ferry": "ferry_fancy",
"cargo": "cargo_box"
- }
+ },
+ "job_access_add": {
+ "/datum/job/hos": [27],
+ "/datum/job/officer": [27],
+ "/datum/job/detective": [27]
+ }
}
diff --git a/_maps/templates/hilbertshotel.dmm b/_maps/templates/hilbertshotel.dmm
index 4e4d8f8267..da5d13d93e 100644
--- a/_maps/templates/hilbertshotel.dmm
+++ b/_maps/templates/hilbertshotel.dmm
@@ -7,7 +7,7 @@
/area/hilbertshotel)
"c" = (
/obj/machinery/jukebox{
- req_access = null
+ req_one_access = null
},
/turf/open/indestructible/hotelwood,
/area/hilbertshotel)
diff --git a/code/__DEFINES/_flags/item_flags.dm b/code/__DEFINES/_flags/item_flags.dm
index 26e764c45b..7da71e22cb 100644
--- a/code/__DEFINES/_flags/item_flags.dm
+++ b/code/__DEFINES/_flags/item_flags.dm
@@ -22,12 +22,10 @@
#define SURGICAL_TOOL (1<<10)
///Can be worn on certain slots (currently belt and id) that would otherwise require an uniform.
#define NO_UNIFORM_REQUIRED (1<<11)
-///Damage when attacking people is not affected by combat mode.
-#define NO_COMBAT_MODE_FORCE_MODIFIER (1<<12)
/// This item can be used to parry. Only a basic check used to determine if we should proceed with parry chain at all.
-#define ITEM_CAN_PARRY (1<<13)
+#define ITEM_CAN_PARRY (1<<12)
/// This item can be used in the directional blocking system. Only a basic check used to determine if we should proceed with directional block handling at all.
-#define ITEM_CAN_BLOCK (1<<14)
+#define ITEM_CAN_BLOCK (1<<13)
// Flags for the clothing_flags var on /obj/item/clothing
diff --git a/code/__DEFINES/colors.dm b/code/__DEFINES/colors.dm
index 2273d34c82..fb461acfa4 100644
--- a/code/__DEFINES/colors.dm
+++ b/code/__DEFINES/colors.dm
@@ -1,54 +1,54 @@
// This is eventually for wjohn to add more color standardization stuff like I keep asking him >:(
-#define COLOR_INPUT_DISABLED "#F0F0F0"
-#define COLOR_INPUT_ENABLED "#D3B5B5"
-#define COLOR_FLOORTILE_GRAY "#8D8B8B"
-#define COLOR_ALMOST_BLACK "#333333"
-#define COLOR_BLACK "#000000"
-#define COLOR_RED "#FF0000"
-#define COLOR_RED_LIGHT "#FF3333"
-#define COLOR_MAROON "#800000"
-#define COLOR_YELLOW "#FFFF00"
-#define COLOR_OLIVE "#808000"
-#define COLOR_LIME "#32CD32"
-#define COLOR_GREEN "#008000"
-#define COLOR_CYAN "#00FFFF"
-#define COLOR_TEAL "#008080"
-#define COLOR_BLUE "#0000FF"
-#define COLOR_BLUE_LIGHT "#33CCFF"
-#define COLOR_NAVY "#000080"
-#define COLOR_PINK "#FFC0CB"
-#define COLOR_MAGENTA "#FF00FF"
-#define COLOR_PURPLE "#800080"
-#define COLOR_ORANGE "#FF9900"
-#define COLOR_BEIGE "#CEB689"
-#define COLOR_BLUE_GRAY "#75A2BB"
-#define COLOR_BROWN "#BA9F6D"
-#define COLOR_DARK_BROWN "#997C4F"
-#define COLOR_DARK_ORANGE "#C3630C"
-#define COLOR_GREEN_GRAY "#99BB76"
-#define COLOR_RED_GRAY "#B4696A"
-#define COLOR_PALE_BLUE_GRAY "#98C5DF"
-#define COLOR_PALE_GREEN_GRAY "#B7D993"
-#define COLOR_PALE_ORANGE "#FFC066"
-#define COLOR_PALE_RED_GRAY "#D59998"
-#define COLOR_PALE_PURPLE_GRAY "#CBB1CA"
-#define COLOR_PURPLE_GRAY "#AE8CA8"
+#define COLOR_INPUT_DISABLED "#F0F0F0"
+#define COLOR_INPUT_ENABLED "#D3B5B5"
+#define COLOR_FLOORTILE_GRAY "#8D8B8B"
+#define COLOR_ALMOST_BLACK "#333333"
+#define COLOR_BLACK "#000000"
+#define COLOR_RED "#FF0000"
+#define COLOR_RED_LIGHT "#FF3333"
+#define COLOR_MAROON "#800000"
+#define COLOR_YELLOW "#FFFF00"
+#define COLOR_OLIVE "#808000"
+#define COLOR_LIME "#32CD32"
+#define COLOR_GREEN "#008000"
+#define COLOR_CYAN "#00FFFF"
+#define COLOR_TEAL "#008080"
+#define COLOR_BLUE "#0000FF"
+#define COLOR_BLUE_LIGHT "#33CCFF"
+#define COLOR_NAVY "#000080"
+#define COLOR_PINK "#FFC0CB"
+#define COLOR_MAGENTA "#FF00FF"
+#define COLOR_PURPLE "#800080"
+#define COLOR_ORANGE "#FF9900"
+#define COLOR_BEIGE "#CEB689"
+#define COLOR_BLUE_GRAY "#75A2BB"
+#define COLOR_BROWN "#BA9F6D"
+#define COLOR_DARK_BROWN "#997C4F"
+#define COLOR_DARK_ORANGE "#C3630C"
+#define COLOR_GREEN_GRAY "#99BB76"
+#define COLOR_RED_GRAY "#B4696A"
+#define COLOR_PALE_BLUE_GRAY "#98C5DF"
+#define COLOR_PALE_GREEN_GRAY "#B7D993"
+#define COLOR_PALE_ORANGE "#FFC066"
+#define COLOR_PALE_RED_GRAY "#D59998"
+#define COLOR_PALE_PURPLE_GRAY "#CBB1CA"
+#define COLOR_PURPLE_GRAY "#AE8CA8"
-//Color defines used by the assembly detailer.
-#define COLOR_ASSEMBLY_BLACK "#545454"
-#define COLOR_ASSEMBLY_BGRAY "#9497AB"
-#define COLOR_ASSEMBLY_WHITE "#E2E2E2"
-#define COLOR_ASSEMBLY_RED "#CC4242"
-#define COLOR_ASSEMBLY_ORANGE "#E39751"
-#define COLOR_ASSEMBLY_BEIGE "#AF9366"
-#define COLOR_ASSEMBLY_BROWN "#97670E"
-#define COLOR_ASSEMBLY_GOLD "#AA9100"
-#define COLOR_ASSEMBLY_YELLOW "#CECA2B"
-#define COLOR_ASSEMBLY_GURKHA "#999875"
-#define COLOR_ASSEMBLY_LGREEN "#789876"
-#define COLOR_ASSEMBLY_GREEN "#44843C"
-#define COLOR_ASSEMBLY_LBLUE "#5D99BE"
-#define COLOR_ASSEMBLY_BLUE "#38559E"
-#define COLOR_ASSEMBLY_PURPLE "#6F6192"
-#define COLOR_ASSEMBLY_PINK "#ff4adc"
+// Color defines used by the assembly detailer.
+#define COLOR_ASSEMBLY_BLACK "#545454"
+#define COLOR_ASSEMBLY_BGRAY "#9497AB"
+#define COLOR_ASSEMBLY_WHITE "#E2E2E2"
+#define COLOR_ASSEMBLY_RED "#CC4242"
+#define COLOR_ASSEMBLY_ORANGE "#E39751"
+#define COLOR_ASSEMBLY_BEIGE "#AF9366"
+#define COLOR_ASSEMBLY_BROWN "#97670E"
+#define COLOR_ASSEMBLY_GOLD "#AA9100"
+#define COLOR_ASSEMBLY_YELLOW "#CECA2B"
+#define COLOR_ASSEMBLY_GURKHA "#999875"
+#define COLOR_ASSEMBLY_LGREEN "#789876"
+#define COLOR_ASSEMBLY_GREEN "#44843C"
+#define COLOR_ASSEMBLY_LBLUE "#5D99BE"
+#define COLOR_ASSEMBLY_BLUE "#38559E"
+#define COLOR_ASSEMBLY_PURPLE "#6F6192"
+#define COLOR_ASSEMBLY_PINK "#ff4adc"
diff --git a/code/__DEFINES/combat.dm b/code/__DEFINES/combat.dm
index eff5d9eca9..5f5a76b58d 100644
--- a/code/__DEFINES/combat.dm
+++ b/code/__DEFINES/combat.dm
@@ -34,6 +34,8 @@
#define COMBAT_FLAGS_DEFAULT (COMBAT_FLAG_PARRY_CAPABLE | COMBAT_FLAG_BLOCK_CAPABLE)
/// Default combat flags for everyone else (so literally everyone but humans).
#define COMBAT_FLAGS_SPRINT_EXEMPT (COMBAT_FLAG_SPRINT_ACTIVE | COMBAT_FLAG_SPRINT_TOGGLED | COMBAT_FLAG_SPRINT_FORCED | COMBAT_FLAG_PARRY_CAPABLE | COMBAT_FLAG_BLOCK_CAPABLE)
+/// Default combat flags for those in stamina combat system
+#define COMBAT_FLAGS_STAMINA_COMBAT (COMBAT_FLAG_PARRY_CAPABLE | COMBAT_FLAG_BLOCK_CAPABLE | COMBAT_FLAG_STAMINA_BUFFER)
/// The user wants sprint mode on
#define COMBAT_FLAG_SPRINT_TOGGLED (1<<0)
@@ -47,8 +49,8 @@
#define COMBAT_FLAG_RESISTING_REST (1<<4)
/// Intentionally resting
#define COMBAT_FLAG_INTENTIONALLY_RESTING (1<<5)
-/// Currently stamcritted but not as violently
-#define COMBAT_FLAG_SOFT_STAMCRIT (1<<6)
+/// This mob requires stamina buffer to do things that require stamina buffer. Not having this exempts the mob from stamina combat.
+#define COMBAT_FLAG_STAMINA_BUFFER (1<<6)
/// Force sprint mode on at all times, overrides everything including sprint disable traits.
#define COMBAT_FLAG_SPRINT_FORCED (1<<7)
/// This mob is capable of using the active parrying system.
@@ -64,26 +66,17 @@
// Helpers for getting someone's stamcrit state. Cast to living.
#define NOT_STAMCRIT 0
-#define SOFT_STAMCRIT 1
-#define HARD_STAMCRIT 2
+#define HARD_STAMCRIT 1
// Stamcrit check helpers
#define IS_STAMCRIT(mob) (CHECK_STAMCRIT(mob) != NOT_STAMCRIT)
-#define CHECK_STAMCRIT(mob) ((mob.combat_flags & COMBAT_FLAG_HARD_STAMCRIT)? HARD_STAMCRIT : ((mob.combat_flags & COMBAT_FLAG_SOFT_STAMCRIT)? SOFT_STAMCRIT : NOT_STAMCRIT))
+#define CHECK_STAMCRIT(mob) (mob.combat_flags & COMBAT_FLAG_HARD_STAMCRIT)
//stamina stuff
-///Threshold over which attacks start being hindered.
-#define STAMINA_NEAR_SOFTCRIT 90
-///softcrit for stamina damage. prevents standing up, some actions that cost stamina, etc, but doesn't force a rest or stop movement
-#define STAMINA_SOFTCRIT 100
-///sanity cap to prevent stamina actions (that are still performable) from sending you into crit.
-#define STAMINA_NEAR_CRIT 130
-///crit for stamina damage. forces a rest, and stops movement until stamina goes back to stamina softcrit
+/// crit for stamina damage. forces a rest, and stops movement until stamina goes back to stamina softcrit
#define STAMINA_CRIT 140
-///same as STAMINA_SOFTCRIT except for the more traditional health calculations
-#define STAMINA_SOFTCRIT_TRADITIONAL 0
-///ditto, but for STAMINA_CRIT
-#define STAMINA_CRIT_TRADITIONAL -40
+/// Threshold under for which you are unable to draw from stamina health to replace stamina buffer
+#define STAMINA_NO_OVERDRAW_THRESHOLD 100
#define CRAWLUNDER_DELAY 30 //Delay for crawling under a standing mob
@@ -252,36 +245,6 @@ GLOBAL_LIST_INIT(shove_disarming_types, typecacheof(list(
//We will round to this value in damage calculations.
#define DAMAGE_PRECISION 0.01
-//items total mass, used to calculate their attacks' stamina costs. If not defined, the cost will be (w_class * 1.25)
-#define TOTAL_MASS_TINY_ITEM 1.25
-#define TOTAL_MASS_SMALL_ITEM 2.5
-#define TOTAL_MASS_NORMAL_ITEM 3.75
-#define TOTAL_MASS_BULKY_ITEM 5
-#define TOTAL_MASS_HUGE_ITEM 6.25
-#define TOTAL_MASS_GIGANTIC_ITEM 7.5
-
-#define TOTAL_MASS_HAND_REPLACEMENT 5 //standard punching stamina cost. most hand replacements are huge items anyway.
-#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 1
-#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
-#define STAM_COST_THROW_MOB 2.5 //multiplied by (mob size + 1)^2.
-
-///Multiplier of the (STAMINA_NEAR_CRIT - user current stamina loss) : (STAMINA_NEAR_CRIT - STAMINA_SOFTCRIT) ratio used in damage penalties when stam soft-critted.
-#define STAM_CRIT_ITEM_ATTACK_PENALTY 0.66
-/// changeNext_move penalty multiplier of the above.
-#define STAM_CRIT_ITEM_ATTACK_DELAY 1.75
-/// Damage penalty when fighting prone.
-#define LYING_DAMAGE_PENALTY 0.7
-/// Added delay when firing guns stam-softcritted. Summed with a hardset CLICK_CD_RANGE delay, similar to STAM_CRIT_DAMAGE_DELAY otherwise.
-#define STAM_CRIT_GUN_DELAY 2.75
-
//stamina recovery defines. Blocked if combat mode is on.
#define STAM_RECOVERY_STAM_CRIT -7.5
#define STAM_RECOVERY_RESTING -6
diff --git a/code/__DEFINES/combat/stamina_combat.dm b/code/__DEFINES/combat/stamina_combat.dm
new file mode 100644
index 0000000000..1f781ca3e0
--- /dev/null
+++ b/code/__DEFINES/combat/stamina_combat.dm
@@ -0,0 +1,35 @@
+// Stamina Buffer
+
+// Standard amounts for stamina usage
+
+// Multipliers
+/// Base stamina cost for an item of a certain w_class without total_mass set.
+#define STAM_COST_W_CLASS_MULT 1.25
+
+// Flat amounts
+/// Usage for eyestabbing with a screwdriver
+#define STAMINA_COST_ITEM_EYESTAB 7.5
+/// Usage for shoving yourself off the ground instantly
+#define STAMINA_COST_SHOVE_UP 10
+
+//items total mass, used to calculate their attacks' stamina costs. If not defined, the cost will be (w_class * 1.25)
+#define TOTAL_MASS_TINY_ITEM 1.25
+#define TOTAL_MASS_SMALL_ITEM 2.5
+#define TOTAL_MASS_NORMAL_ITEM 3.75
+#define TOTAL_MASS_BULKY_ITEM 5
+#define TOTAL_MASS_HUGE_ITEM 6.25
+#define TOTAL_MASS_GIGANTIC_ITEM 7.5
+
+#define TOTAL_MASS_HAND_REPLACEMENT 5 //standard punching stamina cost. most hand replacements are huge items anyway.
+#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 0.75
+#define STAM_COST_ATTACK_MOB_MULT 1
+#define STAM_COST_BATON_MOB_MULT 0.85
+#define STAM_COST_THROW_MULT 0.75
+#define STAM_COST_THROW_MOB 1.25 //multiplied by (mob size + 1)^2.
+
+/// Damage penalty when fighting prone.
+#define LYING_DAMAGE_PENALTY 0.7
diff --git a/code/__DEFINES/configuration.dm b/code/__DEFINES/configuration.dm
index 20c1df2ec7..0428a16828 100644
--- a/code/__DEFINES/configuration.dm
+++ b/code/__DEFINES/configuration.dm
@@ -3,6 +3,8 @@
#define CONFIG_SET(X, Y) global.config.Set(/datum/config_entry/##X, ##Y)
/// Gets the datum of the object, for when editing a const define.
#define CONFIG_GET_ENTRY(X) global.config.GetEntryDatum(/datum/config_entry/##X)
+/// Caches an entry in the proc
+#define CONFIG_CACHE_ENTRY_AND_FETCH_VALUE(X, varname) var/static/datum/config_entry/##X/entrycache_##varname;if(!entrycache_##varname){entrycache_##varname=CONFIG_GET_ENTRY(##X);};var/##varname=entrycache_##varname.config_entry_value
#define CONFIG_MAPS_FILE "maps.txt"
diff --git a/code/__DEFINES/maps.dm b/code/__DEFINES/maps.dm
index c5c2dd58c2..854b6c9dcf 100644
--- a/code/__DEFINES/maps.dm
+++ b/code/__DEFINES/maps.dm
@@ -20,11 +20,6 @@ Multi-Z stations are supported and multi-Z mining and away missions would
require only minor tweaks.
*/
-// helpers for modifying jobs, used in various job_changes.dm files
-#define MAP_JOB_CHECK if(SSmapping.config.map_name != JOB_MODIFICATION_MAP_NAME) { return; }
-#define MAP_JOB_CHECK_BASE if(SSmapping.config.map_name != JOB_MODIFICATION_MAP_NAME) { return ..(); }
-#define MAP_REMOVE_JOB(jobpath) /datum/job/##jobpath/map_check() { return (SSmapping.config.map_name != JOB_MODIFICATION_MAP_NAME) && ..() }
-
#define SPACERUIN_MAP_EDGE_PAD 15
// traits
diff --git a/code/__DEFINES/mobs.dm b/code/__DEFINES/mobs.dm
index dae1b9bf95..e8849d04c8 100644
--- a/code/__DEFINES/mobs.dm
+++ b/code/__DEFINES/mobs.dm
@@ -1,17 +1,17 @@
/*ALL MOB-RELATED DEFINES THAT DON'T BELONG IN ANOTHER FILE GO HERE*/
-//Misc mob defines
+// Misc mob defines
-//Ready states at roundstart for mob/dead/new_player
+// Ready states at roundstart for mob/dead/new_player
#define PLAYER_NOT_READY 0
#define PLAYER_READY_TO_PLAY 1
#define PLAYER_READY_TO_OBSERVE 2
-//movement intent defines for the m_intent var
+// movement intent defines for the m_intent var
#define MOVE_INTENT_WALK "walk"
#define MOVE_INTENT_RUN "run"
-//Blood levels
+// Blood levels
#define BLOOD_VOLUME_MAX_LETHAL 2150
#define BLOOD_VOLUME_EXCESS 2100
#define BLOOD_VOLUME_MAXIMUM 2000
@@ -22,34 +22,34 @@
#define BLOOD_VOLUME_BAD 224
#define BLOOD_VOLUME_SURVIVE 122
-//Sizes of mobs, used by mob/living/var/mob_size
+// Sizes of mobs, used by mob/living/var/mob_size
#define MOB_SIZE_TINY 0
#define MOB_SIZE_SMALL 1
#define MOB_SIZE_HUMAN 2
#define MOB_SIZE_LARGE 3
-//Ventcrawling defines
+// Ventcrawling defines
#define VENTCRAWLER_NONE 0
#define VENTCRAWLER_NUDE 1
#define VENTCRAWLER_ALWAYS 2
-//Bloodcrawling defines
+// Bloodcrawling defines
#define BLOODCRAWL 1
#define BLOODCRAWL_EAT 2
-//Mob bio-types flags
-#define MOB_ORGANIC (1 << 0)
+// Mob bio-types flags
+#define MOB_ORGANIC (1 << 0)
#define MOB_MINERAL (1 << 1)
-#define MOB_ROBOTIC (1 << 2)
+#define MOB_ROBOTIC (1 << 2)
#define MOB_UNDEAD (1 << 3)
-#define MOB_HUMANOID (1 << 4)
-#define MOB_BUG (1 << 5)
+#define MOB_HUMANOID (1 << 4)
+#define MOB_BUG (1 << 5)
#define MOB_BEAST (1 << 6)
-#define MOB_EPIC (1 << 7) //megafauna
+#define MOB_EPIC (1 << 7) // Megafauna
#define MOB_REPTILE (1 << 8)
#define MOB_SPIRIT (1 << 9)
-//Organ defines for carbon mobs
+// Organ defines for carbon mobs
#define ORGAN_ORGANIC 1
#define ORGAN_ROBOTIC 2
@@ -70,194 +70,194 @@
#define DEFAULT_BODYPART_ICON_ROBOTIC 'icons/mob/augmentation/augments.dmi'
#define MONKEY_BODYPART "monkey"
-#define ALIEN_BODYPART "alien"
-#define LARVA_BODYPART "larva"
-#define DEVIL_BODYPART "devil"
+#define ALIEN_BODYPART "alien"
+#define LARVA_BODYPART "larva"
+#define DEVIL_BODYPART "devil"
/*see __DEFINES/inventory.dm for bodypart bitflag defines*/
// Health/damage defines for carbon mobs
#define HUMAN_MAX_OXYLOSS 3
#define HUMAN_CRIT_MAX_OXYLOSS (SSmobs.wait/30)
-#define HEAT_DAMAGE_LEVEL_1 2 //Amount of damage applied when your body temperature just passes the 360.15k safety point
-#define HEAT_DAMAGE_LEVEL_2 3 //Amount of damage applied when your body temperature passes the 400K point
-#define HEAT_DAMAGE_LEVEL_3 8 //Amount of damage applied when your body temperature passes the 460K point and you are on fire
+#define HEAT_DAMAGE_LEVEL_1 2 // Amount of damage applied when your body temperature just passes the 360.15k safety point
+#define HEAT_DAMAGE_LEVEL_2 3 // Amount of damage applied when your body temperature passes the 400K point
+#define HEAT_DAMAGE_LEVEL_3 8 // Amount of damage applied when your body temperature passes the 460K point and you are on fire
-#define COLD_DAMAGE_LEVEL_1 0.5 //Amount of damage applied when your body temperature just passes the 260.15k safety point
-#define COLD_DAMAGE_LEVEL_2 1.5 //Amount of damage applied when your body temperature passes the 200K point
-#define COLD_DAMAGE_LEVEL_3 3 //Amount of damage applied when your body temperature passes the 120K point
+#define COLD_DAMAGE_LEVEL_1 0.5 // Amount of damage applied when your body temperature just passes the 260.15k safety point
+#define COLD_DAMAGE_LEVEL_2 1.5 // Amount of damage applied when your body temperature passes the 200K point
+#define COLD_DAMAGE_LEVEL_3 3 // Amount of damage applied when your body temperature passes the 120K point
-//Note that gas heat damage is only applied once every FOUR ticks.
-#define HEAT_GAS_DAMAGE_LEVEL_1 2 //Amount of damage applied when the current breath's temperature just passes the 360.15k safety point
-#define HEAT_GAS_DAMAGE_LEVEL_2 4 //Amount of damage applied when the current breath's temperature passes the 400K point
-#define HEAT_GAS_DAMAGE_LEVEL_3 8 //Amount of damage applied when the current breath's temperature passes the 1000K point
+// Note that gas heat damage is only applied once every FOUR ticks.
+#define HEAT_GAS_DAMAGE_LEVEL_1 2 // Amount of damage applied when the current breath's temperature just passes the 360.15k safety point
+#define HEAT_GAS_DAMAGE_LEVEL_2 4 // Amount of damage applied when the current breath's temperature passes the 400K point
+#define HEAT_GAS_DAMAGE_LEVEL_3 8 // Amount of damage applied when the current breath's temperature passes the 1000K point
-#define COLD_GAS_DAMAGE_LEVEL_1 0.5 //Amount of damage applied when the current breath's temperature just passes the 260.15k safety point
-#define COLD_GAS_DAMAGE_LEVEL_2 1.5 //Amount of damage applied when the current breath's temperature passes the 200K point
-#define COLD_GAS_DAMAGE_LEVEL_3 3 //Amount of damage applied when the current breath's temperature passes the 120K point
+#define COLD_GAS_DAMAGE_LEVEL_1 0.5 // Amount of damage applied when the current breath's temperature just passes the 260.15k safety point
+#define COLD_GAS_DAMAGE_LEVEL_2 1.5 // Amount of damage applied when the current breath's temperature passes the 200K point
+#define COLD_GAS_DAMAGE_LEVEL_3 3 // Amount of damage applied when the current breath's temperature passes the 120K point
-//Brain Damage defines
-#define BRAIN_DAMAGE_MILD 20
+// Brain Damage defines
+#define BRAIN_DAMAGE_MILD 20
#define BRAIN_DAMAGE_SEVERE 100
-#define BRAIN_DAMAGE_DEATH 200
+#define BRAIN_DAMAGE_DEATH 200
#define BRAIN_TRAUMA_MILD /datum/brain_trauma/mild
#define BRAIN_TRAUMA_SEVERE /datum/brain_trauma/severe
#define BRAIN_TRAUMA_SPECIAL /datum/brain_trauma/special
#define BRAIN_TRAUMA_MAGIC /datum/brain_trauma/magic
-#define TRAUMA_RESILIENCE_BASIC 1 //Curable with chems
-#define TRAUMA_RESILIENCE_SURGERY 2 //Curable with brain surgery
-#define TRAUMA_RESILIENCE_LOBOTOMY 3 //Curable with lobotomy
-#define TRAUMA_RESILIENCE_WOUND 4 //Curable by healing the head wound
-#define TRAUMA_RESILIENCE_MAGIC 5 //Curable only with magic
-#define TRAUMA_RESILIENCE_ABSOLUTE 6 //This is here to stay
+#define TRAUMA_RESILIENCE_BASIC 1 // Curable with chems
+#define TRAUMA_RESILIENCE_SURGERY 2 // Curable with brain surgery
+#define TRAUMA_RESILIENCE_LOBOTOMY 3 // Curable with lobotomy
+#define TRAUMA_RESILIENCE_WOUND 4 // Curable by healing the head wound
+#define TRAUMA_RESILIENCE_MAGIC 5 // Curable only with magic
+#define TRAUMA_RESILIENCE_ABSOLUTE 6 // This is here to stay
-//Limit of traumas for each resilience tier
-#define TRAUMA_LIMIT_BASIC 3
-#define TRAUMA_LIMIT_SURGERY 2
-#define TRAUMA_LIMIT_WOUND 2
+// Limit of traumas for each resilience tier
+#define TRAUMA_LIMIT_BASIC 3
+#define TRAUMA_LIMIT_SURGERY 2
+#define TRAUMA_LIMIT_WOUND 2
#define TRAUMA_LIMIT_LOBOTOMY 3
-#define TRAUMA_LIMIT_MAGIC 3
+#define TRAUMA_LIMIT_MAGIC 3
#define TRAUMA_LIMIT_ABSOLUTE INFINITY
#define BRAIN_DAMAGE_INTEGRITY_MULTIPLIER 0.5
-//Surgery Defines
+// Surgery Defines
#define BIOWARE_GENERIC "generic"
#define BIOWARE_NERVES "nerves"
#define BIOWARE_CIRCULATION "circulation"
#define BIOWARE_LIGAMENTS "ligaments"
-//Health hud screws for carbon mobs
-#define SCREWYHUD_NONE 0
-#define SCREWYHUD_CRIT 1
-#define SCREWYHUD_DEAD 2
+// Health hud screws for carbon mobs
+#define SCREWYHUD_NONE 0
+#define SCREWYHUD_CRIT 1
+#define SCREWYHUD_DEAD 2
#define SCREWYHUD_HEALTHY 3
-//Threshold levels for beauty for humans
+// Threshold levels for beauty for humans
#define BEAUTY_LEVEL_HORRID -66
-#define BEAUTY_LEVEL_BAD -33
-#define BEAUTY_LEVEL_DECENT 33
-#define BEAUTY_LEVEL_GOOD 66
-#define BEAUTY_LEVEL_GREAT 100
+#define BEAUTY_LEVEL_BAD -33
+#define BEAUTY_LEVEL_DECENT 33
+#define BEAUTY_LEVEL_GOOD 66
+#define BEAUTY_LEVEL_GREAT 100
-//Moods levels for humans
-#define MOOD_LEVEL_HAPPY4 15
-#define MOOD_LEVEL_HAPPY3 10
-#define MOOD_LEVEL_HAPPY2 6
-#define MOOD_LEVEL_HAPPY1 2
-#define MOOD_LEVEL_NEUTRAL 0
-#define MOOD_LEVEL_SAD1 -3
-#define MOOD_LEVEL_SAD2 -12
-#define MOOD_LEVEL_SAD3 -18
-#define MOOD_LEVEL_SAD4 -25
+// Moods levels for humans
+#define MOOD_LEVEL_HAPPY4 15
+#define MOOD_LEVEL_HAPPY3 10
+#define MOOD_LEVEL_HAPPY2 6
+#define MOOD_LEVEL_HAPPY1 2
+#define MOOD_LEVEL_NEUTRAL 0
+#define MOOD_LEVEL_SAD1 -3
+#define MOOD_LEVEL_SAD2 -12
+#define MOOD_LEVEL_SAD3 -18
+#define MOOD_LEVEL_SAD4 -25
-//Sanity levels for humans
-#define SANITY_AMAZING 150
-#define SANITY_GREAT 125
-#define SANITY_NEUTRAL 100
+// Sanity levels for humans
+#define SANITY_AMAZING 150
+#define SANITY_GREAT 125
+#define SANITY_NEUTRAL 100
#define SANITY_DISTURBED 75
-#define SANITY_UNSTABLE 50
-#define SANITY_CRAZY 25
-#define SANITY_INSANE 0
+#define SANITY_UNSTABLE 50
+#define SANITY_CRAZY 25
+#define SANITY_INSANE 0
-//Nutrition levels for humans
-#define NUTRITION_LEVEL_FAT 600
-#define NUTRITION_LEVEL_FULL 550
+// Nutrition levels for humans
+#define NUTRITION_LEVEL_FAT 600
+#define NUTRITION_LEVEL_FULL 550
#define NUTRITION_LEVEL_WELL_FED 450
-#define NUTRITION_LEVEL_FED 350
-#define NUTRITION_LEVEL_HUNGRY 250
+#define NUTRITION_LEVEL_FED 350
+#define NUTRITION_LEVEL_HUNGRY 250
#define NUTRITION_LEVEL_STARVING 150
#define NUTRITION_LEVEL_START_MIN 250
#define NUTRITION_LEVEL_START_MAX 400
-//Disgust levels for humans
-#define DISGUST_LEVEL_MAXEDOUT 150
-#define DISGUST_LEVEL_DISGUSTED 75
-#define DISGUST_LEVEL_VERYGROSS 50
-#define DISGUST_LEVEL_GROSS 25
+// Disgust levels for humans
+#define DISGUST_LEVEL_MAXEDOUT 150
+#define DISGUST_LEVEL_DISGUSTED 75
+#define DISGUST_LEVEL_VERYGROSS 50
+#define DISGUST_LEVEL_GROSS 25
-//Charge levels for Ethereals
-#define ETHEREAL_CHARGE_NONE 0
-#define ETHEREAL_CHARGE_LOWPOWER 20
-#define ETHEREAL_CHARGE_NORMAL 50
+// Charge levels for Ethereals
+#define ETHEREAL_CHARGE_NONE 0
+#define ETHEREAL_CHARGE_LOWPOWER 20
+#define ETHEREAL_CHARGE_NORMAL 50
#define ETHEREAL_CHARGE_ALMOSTFULL 75
-#define ETHEREAL_CHARGE_FULL 100
-#define ETHEREAL_CHARGE_OVERLOAD 125
+#define ETHEREAL_CHARGE_FULL 100
+#define ETHEREAL_CHARGE_OVERLOAD 125
#define ETHEREAL_CHARGE_DANGEROUS 150
-//Slime evolution threshold. Controls how fast slimes can split/grow
+// Slime evolution threshold. Controls how fast slimes can split/grow
#define SLIME_EVOLUTION_THRESHOLD 10
-//Slime extract crossing. Controls how many extracts is required to feed to a slime to core-cross.
+// Slime extract crossing. Controls how many extracts is required to feed to a slime to core-cross.
#define SLIME_EXTRACT_CROSSING_REQUIRED 10
-//Slime commands defines
-#define SLIME_FRIENDSHIP_FOLLOW 3 //Min friendship to order it to follow
-#define SLIME_FRIENDSHIP_STOPEAT 5 //Min friendship to order it to stop eating someone
-#define SLIME_FRIENDSHIP_STOPEAT_NOANGRY 7 //Min friendship to order it to stop eating someone without it losing friendship
-#define SLIME_FRIENDSHIP_STOPCHASE 4 //Min friendship to order it to stop chasing someone (their target)
-#define SLIME_FRIENDSHIP_STOPCHASE_NOANGRY 6 //Min friendship to order it to stop chasing someone (their target) without it losing friendship
-#define SLIME_FRIENDSHIP_STAY 3 //Min friendship to order it to stay
-#define SLIME_FRIENDSHIP_ATTACK 8 //Min friendship to order it to attack
+// Slime commands defines
+#define SLIME_FRIENDSHIP_FOLLOW 3 // Min friendship to order it to follow
+#define SLIME_FRIENDSHIP_STOPEAT 5 // Min friendship to order it to stop eating someone
+#define SLIME_FRIENDSHIP_STOPEAT_NOANGRY 7 // Min friendship to order it to stop eating someone without it losing friendship
+#define SLIME_FRIENDSHIP_STOPCHASE 4 // Min friendship to order it to stop chasing someone (their target)
+#define SLIME_FRIENDSHIP_STOPCHASE_NOANGRY 6 // Min friendship to order it to stop chasing someone (their target) without it losing friendship
+#define SLIME_FRIENDSHIP_STAY 3 // Min friendship to order it to stay
+#define SLIME_FRIENDSHIP_ATTACK 8 // Min friendship to order it to attack
-//Sentience types, to prevent things like sentience potions from giving bosses sentience
-#define SENTIENCE_ORGANIC 1
+// Sentience types, to prevent things like sentience potions from giving bosses sentience
+#define SENTIENCE_ORGANIC 1
#define SENTIENCE_ARTIFICIAL 2
-// #define SENTIENCE_OTHER 3 unused
-#define SENTIENCE_MINEBOT 4
-#define SENTIENCE_BOSS 5
+//#define SENTIENCE_OTHER 3 // Unused
+#define SENTIENCE_MINEBOT 4
+#define SENTIENCE_BOSS 5
-//Mob AI Status
+// Mob AI Status
-//Hostile simple animals
-//If you add a new status, be sure to add a list for it to the simple_animals global in _globalvars/lists/mobs.dm
+// Hostile simple animals
+// If you add a new status, be sure to add a list for it to the simple_animals global in _globalvars/lists/mobs.dm
#define AI_ON 1
#define AI_IDLE 2
#define AI_OFF 3
#define AI_Z_OFF 4
-//determines if a mob can smash through it
-#define ENVIRONMENT_SMASH_NONE 0
-#define ENVIRONMENT_SMASH_STRUCTURES (1<<0) //crates, lockers, ect
-#define ENVIRONMENT_SMASH_WALLS (1<<1) //walls
-#define ENVIRONMENT_SMASH_RWALLS (1<<2) //rwalls
+// determines if a mob can smash through it
+#define ENVIRONMENT_SMASH_NONE 0
+#define ENVIRONMENT_SMASH_STRUCTURES (1<<0) // crates, lockers, ect
+#define ENVIRONMENT_SMASH_WALLS (1<<1) // walls
+#define ENVIRONMENT_SMASH_RWALLS (1<<2) // rwalls
-#define NO_SLIP_WHEN_WALKING (1<<0)
-#define SLIDE (1<<1)
-#define GALOSHES_DONT_HELP (1<<2)
-#define FLYING_DOESNT_HELP (1<<3)
-#define SLIDE_ICE (1<<4)
-#define SLIP_WHEN_CRAWLING (1<<5) //clown planet ruin amongst others
-#define SLIP_WHEN_JOGGING (1<<6) //slips prevented by walking are also dodged if the mob is nor sprinting or fatigued... unless this flag is on.
+#define NO_SLIP_WHEN_WALKING (1<<0)
+#define SLIDE (1<<1)
+#define GALOSHES_DONT_HELP (1<<2)
+#define FLYING_DOESNT_HELP (1<<3)
+#define SLIDE_ICE (1<<4)
+#define SLIP_WHEN_CRAWLING (1<<5) // clown planet ruin amongst others
+#define SLIP_WHEN_JOGGING (1<<6) // slips prevented by walking are also dodged if the mob is nor sprinting or fatigued... unless this flag is on.
#define MAX_CHICKENS 50
-///Flags used by the flags parameter of electrocute act.
+// /Flags used by the flags parameter of electrocute act.
-///Makes it so that the shock doesn't take gloves into account.
-#define SHOCK_NOGLOVES (1 << 0)
-///Used when the shock is from a tesla bolt.
-#define SHOCK_TESLA (1 << 1)
-///Used when an illusion shocks something. Makes the shock deal stamina damage and not trigger certain secondary effects.
-#define SHOCK_ILLUSION (1 << 2)
-///The shock doesn't stun.
-#define SHOCK_NOSTUN (1 << 3)
+// /Makes it so that the shock doesn't take gloves into account.
+#define SHOCK_NOGLOVES (1 << 0)
+// /Used when the shock is from a tesla bolt.
+#define SHOCK_TESLA (1 << 1)
+// /Used when an illusion shocks something. Makes the shock deal stamina damage and not trigger certain secondary effects.
+#define SHOCK_ILLUSION (1 << 2)
+// /The shock doesn't stun.
+#define SHOCK_NOSTUN (1 << 3)
-#define INCORPOREAL_MOVE_BASIC 1
-#define INCORPOREAL_MOVE_SHADOW 2 // leaves a trail of shadows
-#define INCORPOREAL_MOVE_JAUNT 3 // is blocked by holy water/salt
+#define INCORPOREAL_MOVE_BASIC 1
+#define INCORPOREAL_MOVE_SHADOW 2 // leaves a trail of shadows
+#define INCORPOREAL_MOVE_JAUNT 3 // is blocked by holy water/salt
-//Secbot and ED209 judgement criteria bitflag values
+// Secbot and ED209 judgement criteria bitflag values
#define JUDGE_EMAGGED (1<<0)
#define JUDGE_IDCHECK (1<<1)
#define JUDGE_WEAPONCHECK (1<<2)
#define JUDGE_RECORDCHECK (1<<3)
-//ED209's ignore monkeys
+// ED209's ignore monkeys
#define JUDGE_IGNOREMONKEYS (1<<4)
#define MEGAFAUNA_DEFAULT_RECOVERY_TIME 5
@@ -266,45 +266,45 @@
// Offsets defines
-#define OFFSET_UNIFORM "uniform"
-#define OFFSET_ID "id"
-#define OFFSET_GLOVES "gloves"
-#define OFFSET_GLASSES "glasses"
-#define OFFSET_EARS "ears"
-#define OFFSET_SHOES "shoes"
-#define OFFSET_S_STORE "s_store"
-#define OFFSET_FACEMASK "mask"
-#define OFFSET_HEAD "head"
-#define OFFSET_EYES "eyes"
-#define OFFSET_LIPS "lips"
-#define OFFSET_BELT "belt"
-#define OFFSET_BACK "back"
-#define OFFSET_SUIT "suit"
-#define OFFSET_NECK "neck"
-#define OFFSET_HAIR "hair"
-#define OFFSET_FHAIR "fhair"
-#define OFFSET_MUTPARTS "mutantparts"
+#define OFFSET_UNIFORM "uniform"
+#define OFFSET_ID "id"
+#define OFFSET_GLOVES "gloves"
+#define OFFSET_GLASSES "glasses"
+#define OFFSET_EARS "ears"
+#define OFFSET_SHOES "shoes"
+#define OFFSET_S_STORE "s_store"
+#define OFFSET_FACEMASK "mask"
+#define OFFSET_HEAD "head"
+#define OFFSET_EYES "eyes"
+#define OFFSET_LIPS "lips"
+#define OFFSET_BELT "belt"
+#define OFFSET_BACK "back"
+#define OFFSET_SUIT "suit"
+#define OFFSET_NECK "neck"
+#define OFFSET_HAIR "hair"
+#define OFFSET_FHAIR "fhair"
+#define OFFSET_MUTPARTS "mutantparts"
-//MINOR TWEAKS/MISC
-#define AGE_MIN 18 //youngest a character can be //CITADEL EDIT - 17 --> 18
-#define AGE_MAX 85 //oldest a character can be
-#define WIZARD_AGE_MIN 30 //youngest a wizard can be
-#define APPRENTICE_AGE_MIN 29 //youngest an apprentice can be
-#define SHOES_SLOWDOWN 0 //How much shoes slow you down by default. Negative values speed you up
-#define SHOES_SPEED_SLIGHT SHOES_SLOWDOWN - 1 // slightest speed boost to movement
-#define POCKET_STRIP_DELAY 40 //time taken (in deciseconds) to search somebody's pockets
-#define DOOR_CRUSH_DAMAGE 15 //the amount of damage that airlocks deal when they crush you
+// MINOR TWEAKS/MISC
+#define AGE_MIN 18 // youngest a character can be // CITADEL EDIT - 17 --> 18
+#define AGE_MAX 85 // oldest a character can be
+#define WIZARD_AGE_MIN 30 // youngest a wizard can be
+#define APPRENTICE_AGE_MIN 29 // youngest an apprentice can be
+#define SHOES_SLOWDOWN 0 // How much shoes slow you down by default. Negative values speed you up
+#define SHOES_SPEED_SLIGHT SHOES_SLOWDOWN - 1 // slightest speed boost to movement
+#define POCKET_STRIP_DELAY 40 // time taken (in deciseconds) to search somebody's pockets
+#define DOOR_CRUSH_DAMAGE 15 // the amount of damage that airlocks deal when they crush you
-#define HUNGER_FACTOR 0.1 //factor at which mob nutrition decreases
-#define ETHEREAL_CHARGE_FACTOR 0.08 //factor at which ethereal's charge decreases
-#define REAGENTS_METABOLISM 0.4 //How many units of reagent are consumed per tick, by default.
+#define HUNGER_FACTOR 0.1 // factor at which mob nutrition decreases
+#define ETHEREAL_CHARGE_FACTOR 0.08 // factor at which ethereal's charge decreases
+#define REAGENTS_METABOLISM 0.4 // How many units of reagent are consumed per tick, by default.
#define REAGENTS_EFFECT_MULTIPLIER (REAGENTS_METABOLISM / 0.4) // By defining the effect multiplier this way, it'll exactly adjust all effects according to how they originally were with the 0.4 metabolism
// Roundstart trait system
-#define MAX_QUIRKS 6 //The maximum amount of quirks one character can have at roundstart
+#define MAX_QUIRKS 6 // The maximum amount of quirks one character can have at roundstart
-#define MAX_REVIVE_FIRE_DAMAGE 180
+#define MAX_REVIVE_FIRE_DAMAGE 180
#define MAX_REVIVE_BRUTE_DAMAGE 180
// AI Toggles
@@ -312,28 +312,28 @@
#define AI_VOX // Comment out if you don't want VOX to be enabled and have players download the voice sounds.
// /obj/item/bodypart on_mob_life() retval flag
-#define BODYPART_LIFE_UPDATE_HEALTH (1<<0)
+#define BODYPART_LIFE_UPDATE_HEALTH (1<<0)
#define HUMAN_FIRE_STACK_ICON_NUM 3
#define TYPING_INDICATOR_TIMEOUT 20 MINUTES
-#define GRAB_PIXEL_SHIFT_PASSIVE 6
-#define GRAB_PIXEL_SHIFT_AGGRESSIVE 12
-#define GRAB_PIXEL_SHIFT_NECK 16
+#define GRAB_PIXEL_SHIFT_PASSIVE 6
+#define GRAB_PIXEL_SHIFT_AGGRESSIVE 12
+#define GRAB_PIXEL_SHIFT_NECK 16
#define SLEEP_CHECK_DEATH(X) sleep(X); if(QDELETED(src) || stat == DEAD) return;
#define INTERACTING_WITH(X, Y) (Y in X.do_afters)
-/// Field of vision defines.
+// / Field of vision defines.
#define FOV_90_DEGREES 90
#define FOV_180_DEGREES 180
#define FOV_270_DEGREES 270
-/// How far away you can be to make eye contact with someone while examining
+// / How far away you can be to make eye contact with someone while examining
#define EYE_CONTACT_RANGE 5
-/// If you examine the same atom twice in this timeframe, we call examine_more() instead of examine()
+// / If you examine the same atom twice in this timeframe, we call examine_more() instead of examine()
#define EXAMINE_MORE_TIME 1 SECONDS
-#define SILENCE_RANGED_MESSAGE (1<<0)
+#define SILENCE_RANGED_MESSAGE (1<<0)
diff --git a/code/__DEFINES/skills/defines.dm b/code/__DEFINES/skills/defines.dm
index 47aaeeb1dc..a0b34895df 100644
--- a/code/__DEFINES/skills/defines.dm
+++ b/code/__DEFINES/skills/defines.dm
@@ -40,7 +40,6 @@
#define SKILL_TRAIN_ATTACK_OBJ "train_attack_obj"
#define SKILL_STAMINA_COST "stamina_cost" //Influences the stamina cost from weapon usage.
#define SKILL_THROW_STAM_COST "throw_stam_cost"
-#define SKILL_COMBAT_MODE "combat_mode" //The user must have combat mode on.
#define SKILL_SANITY "sanity" //Is the skill affected by (in)sanity.
#define SKILL_INTELLIGENCE "intelligence" //Is the skill affected by brain damage?
diff --git a/code/__DEFINES/species.dm b/code/__DEFINES/species.dm
new file mode 100644
index 0000000000..c6cc6a5037
--- /dev/null
+++ b/code/__DEFINES/species.dm
@@ -0,0 +1,50 @@
+
+// Species ID Defines ---------------------------
+
+#define SPECIES_ABDUCTOR "abductor"
+#define SPECIES_ANDROID "android"
+#define SPECIES_ANGEL "angel"
+#define SPECIES_MAMMAL "mammal"
+#define SPECIES_INSECT "insect"
+#define SPECIES_DULLAHAN "dullahan"
+#define SPECIES_DWARF "dwarf"
+#define SPECIES_ETHEREAL "ethereal"
+#define SPECIES_FELINID "felinid"
+#define SPECIES_FLY "fly"
+#define SPECIES_GOLEM "golem"
+#define SPECIES_HUMAN "human"
+#define SPECIES_IPC "ipc"
+#define SPECIES_JELLY "jelly"
+ #define SPECIES_SLIME "slime"
+ #define SPECIES_SLIME_HYBRID "slimeperson"
+ #define SPECIES_SLIME_LUMI "slimelumi"
+ #define SPECIES_STARGAZER "stargazer"
+#define SPECIES_LIZARD "lizard"
+ #define SPECIES_ASHWALKER "ashlizard"
+#define SPECIES_MUSHROOM "mush"
+#define SPECIES_PLASMAMAN "plasmaman"
+#define SPECIES_POD "pod"
+ #define SPECIES_POD_WEAK "podweak"
+#define SPECIES_SHADOW "shadow"
+ #define SPECIES_NIGHTMARE "nightmare"
+#define SPECIES_SKELETON "skeleton"
+ #define SPECIES_SKELETON_SPACE "spaceskeleton"
+#define SPECIES_SYNTH_LIZARD "synthliz"
+#define SPECIES_SYNTH "synth"
+ #define SPECIES_SYNTH_MIL "military_synth"
+#define SPECIES_VAMPIRE "vampire"
+#define SPECIES_XENOHYBRID "xeno"
+
+// Species Category Defines ---------------------
+
+#define SPECIES_CATEGORY_ALIEN "alien"
+#define SPECIES_CATEGORY_BASIC "human"
+#define SPECIES_CATEGORY_FURRY "furry" // Weird Category, but okay.
+#define SPECIES_CATEGORY_GOLEM "golem"
+#define SPECIES_CATEGORY_INSECT "insect"
+#define SPECIES_CATEGORY_PLANT "plant"
+#define SPECIES_CATEGORY_ROBOT "robotic"
+#define SPECIES_CATEGORY_JELLY "jelly"
+#define SPECIES_CATEGORY_SHADOW "shadow"
+#define SPECIES_CATEGORY_SKELETON "skeleton"
+#define SPECIES_CATEGORY_UNDEAD "undead"
diff --git a/code/__DEFINES/traits.dm b/code/__DEFINES/traits.dm
index 80633148e9..520f205381 100644
--- a/code/__DEFINES/traits.dm
+++ b/code/__DEFINES/traits.dm
@@ -164,6 +164,7 @@
#define TRAIT_NO_MIDROUND_ANTAG "no-midround-antag" //can't be turned into an antag by random events
#define TRAIT_PUGILIST "pugilist" //This guy punches people for a living
#define TRAIT_KI_VAMPIRE "ki-vampire" //when someone with this trait rolls maximum damage on a punch and stuns the target, they regain some stamina and do clone damage
+#define TRAIT_MAULER "mauler" // this guy punches the shit out of people to hurt them, not to drain their stamina
#define TRAIT_PASSTABLE "passtable"
#define TRAIT_GIANT "giant"
#define TRAIT_DWARF "dwarf"
@@ -207,6 +208,10 @@
#define TRAIT_FAST_PUMP "fast_pump"
#define TRAIT_NO_PROCESS_FOOD "no-process-food" // You don't get benefits from nutriment, nor nutrition from reagent consumables
#define TRAIT_NICE_SHOT "nice_shot" //hnnnnnnnggggg..... you're pretty good...
+/// Prevents stamina buffer regeneration
+#define TRAIT_NO_STAMINA_BUFFER_REGENERATION "block_stamina_buffer_regen"
+/// Prevents stamina regeneration
+#define TRAIT_NO_STAMINA_REGENERATION "block_stamina_regen"
// mobility flag traits
// IN THE FUTURE, IT WOULD BE NICE TO DO SOMETHING SIMILAR TO https://github.com/tgstation/tgstation/pull/48923/files (ofcourse not nearly the same because I have my.. thoughts on it)
diff --git a/code/_globalvars/bitfields.dm b/code/_globalvars/bitfields.dm
index 85f5e4fd9b..b558b28cf2 100644
--- a/code/_globalvars/bitfields.dm
+++ b/code/_globalvars/bitfields.dm
@@ -252,7 +252,6 @@ GLOBAL_LIST_INIT(bitfields, list(
"COMBAT_FLAG_SPRINT_ACTIVE" = COMBAT_FLAG_SPRINT_ACTIVE,
"COMBAT_FLAG_ATTEMPTING_CRAWL" = COMBAT_FLAG_ATTEMPTING_CRAWL,
"COMBAT_FLAG_HARD_STAMCRIT" = COMBAT_FLAG_HARD_STAMCRIT,
- "COMBAT_FLAG_SOFT_STAMCRIT" = COMBAT_FLAG_SOFT_STAMCRIT,
"COMBAT_FLAG_INTENTIONALLY_RESTING" = COMBAT_FLAG_INTENTIONALLY_RESTING,
"COMBAT_FLAG_RESISTING_REST" = COMBAT_FLAG_RESISTING_REST,
"COMBAT_FLAG_SPRINT_FORCED" = COMBAT_FLAG_SPRINT_FORCED
diff --git a/code/_globalvars/lists/flavor_misc.dm b/code/_globalvars/lists/flavor_misc.dm
index 9d8da6c145..a327b7fcde 100644
--- a/code/_globalvars/lists/flavor_misc.dm
+++ b/code/_globalvars/lists/flavor_misc.dm
@@ -282,7 +282,7 @@ GLOBAL_LIST_INIT(unlocked_mutant_parts, list("horns", "insect_fluff"))
GLOBAL_LIST_INIT(colored_mutant_parts, list("insect_wings" = "wings_color", "deco_wings" = "wings_color", "horns" = "horns_color"))
//body ids that have greyscale sprites
-GLOBAL_LIST_INIT(greyscale_limb_types, list("human","moth","lizard","pod","plant","jelly","slime","golem","lum","stargazer","mush","ethereal","snail","c_golem","b_golem","mammal","xeno","ipc","insect","synthliz","avian","aquatic"))
+GLOBAL_LIST_INIT(greyscale_limb_types, list("human","moth","lizard","pod","plant","jelly","slime","golem","slimelumi","stargazer","mush","ethereal","snail","c_golem","b_golem","mammal","xeno","ipc","insect","synthliz","avian","aquatic"))
//body ids that have prosthetic sprites
GLOBAL_LIST_INIT(prosthetic_limb_types, list("xion","bishop","cybersolutions","grayson","hephaestus","nanotrasen","talon"))
diff --git a/code/_globalvars/lists/mobs.dm b/code/_globalvars/lists/mobs.dm
index 134f9d9cbe..64bac1f9af 100644
--- a/code/_globalvars/lists/mobs.dm
+++ b/code/_globalvars/lists/mobs.dm
@@ -40,6 +40,7 @@ GLOBAL_LIST_EMPTY(sentient_disease_instances)
GLOBAL_LIST_EMPTY(latejoin_ai_cores)
GLOBAL_LIST_EMPTY(mob_config_movespeed_type_lookup)
+GLOBAL_LIST_EMPTY(mob_config_movespeed_type_lookup_floating)
GLOBAL_LIST_EMPTY(latejoiners) //CIT CHANGE - All latejoining people, for traitor-target purposes.
@@ -47,14 +48,22 @@ GLOBAL_LIST_EMPTY(latejoiners) //CIT CHANGE - All latejoining people, for traito
// NOTE: This is entirely based on the fact that byond typesof/subtypesof gets longer/deeper paths before shallower ones.
// If that ever breaks this entire proc breaks.
var/list/mob_types = typesof(/mob)
- var/list/entry_value = CONFIG_GET(keyed_list/multiplicative_movespeed)
+ var/list/mob_types_floating = typesof(/mob)
+ var/list/entry_value = CONFIG_GET(keyed_list/multiplicative_movespeed/normal)
+ var/list/entry_value_floating = CONFIG_GET(keyed_list/multiplicative_movespeed/floating)
var/list/configured_types = list()
+ var/list/configured_types_floating = list()
for(var/path in entry_value)
var/value = entry_value[path]
if(isnull(value))
continue
// associative list sets for elements that already exist preserve order
mob_types[path] = value
+ for(var/path in entry_value_floating)
+ var/value = entry_value_floating[path]
+ if(isnull(value))
+ continue
+ mob_types_floating[path] = value
// now go back up through it to set everything, making absolute sure that base paths are overridden by child paths all the way down the path tree.
for(var/i in length(mob_types) to 1 step -1)
var/path = mob_types[i]
@@ -63,7 +72,14 @@ GLOBAL_LIST_EMPTY(latejoiners) //CIT CHANGE - All latejoining people, for traito
// we're going from bottom to top so it should be safe to do this without further checks..
for(var/subpath in typesof(path))
configured_types[subpath] = mob_types[path]
+ for(var/i in length(mob_types_floating) to 1 step -1)
+ var/path = mob_types_floating[i]
+ if(isnull(mob_types_floating[path]))
+ continue
+ for(var/subpath in typesof(path))
+ configured_types_floating[subpath] = mob_types_floating[path]
GLOB.mob_config_movespeed_type_lookup = configured_types
+ GLOB.mob_config_movespeed_type_lookup_floating = configured_types_floating
if(update_mobs)
update_mob_config_movespeeds()
diff --git a/code/_onclick/hud/radial.dm b/code/_onclick/hud/radial.dm
index 1ead41d513..929e01c6d9 100644
--- a/code/_onclick/hud/radial.dm
+++ b/code/_onclick/hud/radial.dm
@@ -318,4 +318,4 @@ GLOBAL_LIST_EMPTY(radial_menus)
if(istype(custom_check))
if(!custom_check.Invoke())
return
- return answer
\ No newline at end of file
+ return answer
diff --git a/code/_onclick/hud/screen_objects/stamina.dm b/code/_onclick/hud/screen_objects/stamina.dm
index 5484014f8f..98a387d32d 100644
--- a/code/_onclick/hud/screen_objects/stamina.dm
+++ b/code/_onclick/hud/screen_objects/stamina.dm
@@ -11,7 +11,9 @@
/obj/screen/staminas/Click(location,control,params)
if(isliving(usr))
var/mob/living/L = usr
- to_chat(L, "You have [L.getStaminaLoss()] stamina loss.
Your stamina buffer can take [L.stambuffer] stamina loss, and recharges at no cost.
Your stamina buffer is [(L.stambuffer*(100/L.stambuffer))-(L.bufferedstam*(100/L.stambuffer))]% full.")
+ CONFIG_CACHE_ENTRY_AND_FETCH_VALUE(number/stamina_combat/buffer_max, buffer_max)
+ to_chat(L, "You have [L.getStaminaLoss()] stamina loss.
\
+
Your stamina buffer is [round((L.stamina_buffer / buffer_max) * 100, 0.1)]% full.")
/obj/screen/staminas/update_icon_state()
var/mob/living/carbon/user = hud?.mymob
@@ -24,6 +26,19 @@
else
icon_state = "stamina[clamp(FLOOR(user.getStaminaLoss() /20, 1), 0, 6)]"
+/obj/screen/staminas/update_overlays()
+ . = ..()
+ var/mob/living/carbon/user = hud?.mymob
+ if(!user)
+ return
+ var/percent = user.getStaminaLoss() / STAMINA_CRIT
+ if((user.stat == DEAD) || (user.combat_flags & COMBAT_FLAG_HARD_STAMCRIT) || (user.hal_screwyhud in 1 to 2))
+ . += list("stamina_alert3")
+ else if(percent >= 0.85)
+ . += list("stamina_alert2")
+ else if(percent >= 0.7)
+ . += list("stamina_alert1")
+
//stam buffer
/obj/screen/staminabuffer
icon = 'modular_citadel/icons/ui/screen_gen.dmi'
@@ -33,29 +48,33 @@
layer = ABOVE_HUD_LAYER + 0.1
mouse_opacity = 0
-/obj/screen/staminabuffer/update_icon_state()
+/obj/screen/staminabuffer/proc/mark_dirty()
+ if(update_to_mob())
+ START_PROCESSING(SShuds, src)
+
+/obj/screen/staminabuffer/process()
+ if(!update_to_mob())
+ return PROCESS_KILL
+
+/obj/screen/staminabuffer/Destroy()
+ STOP_PROCESSING(SShuds, src)
+ return ..()
+
+/obj/screen/staminabuffer/proc/update_to_mob()
var/mob/living/carbon/user = hud?.mymob
- if(!user)
- return
+ user.UpdateStaminaBuffer(FALSE)
+ CONFIG_CACHE_ENTRY_AND_FETCH_VALUE(number/stamina_combat/buffer_max, buffer_max)
+ if(!user?.client)
+ return FALSE
if(user.stat == DEAD || (user.combat_flags & COMBAT_FLAG_HARD_STAMCRIT) || (user.hal_screwyhud in 1 to 2))
- icon_state = "stambuffer7"
- else if(user.hal_screwyhud == 5)
icon_state = "stambuffer0"
+ return FALSE
+ else if(user.hal_screwyhud == 5)
+ icon_state = "stambuffer29"
+ return FALSE
+ else if(user.stamina_buffer >= buffer_max)
+ icon_state = "stambuffer29"
+ return FALSE
else
- switch(user.bufferedstam / user.stambuffer)
- if(0.95 to INFINITY)
- icon_state = "stambuffer7"
- if(0.9 to 0.95)
- icon_state = "stambuffer6"
- if(0.8 to 0.9)
- icon_state = "stambuffer5"
- if(0.6 to 0.8)
- icon_state = "stambuffer4"
- if(0.4 to 0.6)
- icon_state = "stambuffer3"
- if(0.2 to 0.4)
- icon_state = "stambuffer2"
- if(0.05 to 0.2)
- icon_state = "stambuffer1"
- else
- icon_state = "stambuffer0"
+ icon_state = "stambuffer[FLOOR((user.stamina_buffer / buffer_max) * 29, 1)]"
+ return TRUE
diff --git a/code/_onclick/item_attack.dm b/code/_onclick/item_attack.dm
index 9e88476513..cd1ee56848 100644
--- a/code/_onclick/item_attack.dm
+++ b/code/_onclick/item_attack.dm
@@ -85,6 +85,9 @@
if(force && damtype != STAMINA && HAS_TRAIT(user, TRAIT_PACIFISM))
to_chat(user, "You don't want to harm other living beings!")
return
+
+ if(!UseStaminaBufferStandard(user, STAM_COST_ATTACK_MOB_MULT, null, TRUE))
+ return DISCARD_LAST_ACTION
if(!force)
playsound(loc, 'sound/weapons/tap.ogg', get_clamped_volume(), 1, -1)
@@ -100,10 +103,6 @@
log_combat(user, M, "attacked", src.name, "(INTENT: [uppertext(user.a_intent)]) (DAMTYPE: [uppertext(damtype)])")
add_fingerprint(user)
- var/weight = getweight(user, STAM_COST_ATTACK_MOB_MULT) //CIT CHANGE - makes attacking things cause stamina loss
- if(weight)
- user.adjustStaminaLossBuffered(weight)
-
// CIT SCREENSHAKE
if(force >= 15)
shake_camera(user, ((force - 10) * 0.01 + 1), ((force - 10) * 0.01))
@@ -120,31 +119,21 @@
if(SEND_SIGNAL(src, COMSIG_ITEM_ATTACK_OBJ, O, user) & COMPONENT_NO_ATTACK_OBJ)
return
if(item_flags & NOBLUDGEON)
- return
+ return DISCARD_LAST_ACTION
+ if(!UseStaminaBufferStandard(user, STAM_COST_ATTACK_OBJ_MULT, warn = TRUE))
+ return DISCARD_LAST_ACTION
user.do_attack_animation(O)
O.attacked_by(src, user)
- var/weight = getweight(user, STAM_COST_ATTACK_OBJ_MULT)
- if(weight)
- user.adjustStaminaLossBuffered(weight)//CIT CHANGE - makes attacking things cause stamina loss
/atom/movable/proc/attacked_by()
return
/obj/attacked_by(obj/item/I, mob/living/user, attackchain_flags = NONE, damage_multiplier = 1)
var/totitemdamage = I.force * damage_multiplier
- var/bad_trait
-
- var/stamloss = user.getStaminaLoss()
- if(stamloss > STAMINA_NEAR_SOFTCRIT) //The more tired you are, the less damage you do.
- var/penalty = (stamloss - STAMINA_NEAR_SOFTCRIT)/(STAMINA_NEAR_CRIT - STAMINA_NEAR_SOFTCRIT)*STAM_CRIT_ITEM_ATTACK_PENALTY
- totitemdamage *= 1 - penalty
-
- if(SEND_SIGNAL(user, COMSIG_COMBAT_MODE_CHECK, COMBAT_MODE_INACTIVE))
- bad_trait = SKILL_COMBAT_MODE //blacklist combat skills.
if(I.used_skills && user.mind)
if(totitemdamage)
- totitemdamage = user.mind.item_action_skills_mod(I, totitemdamage, I.skill_difficulty, SKILL_ATTACK_OBJ, bad_trait)
+ totitemdamage = user.mind.item_action_skills_mod(I, totitemdamage, I.skill_difficulty, SKILL_ATTACK_OBJ)
for(var/skill in I.used_skills)
if(!(SKILL_TRAIN_ATTACK_OBJ in I.used_skills[skill]))
continue
@@ -187,28 +176,15 @@
if(!.)
return
- var/stamloss = user.getStaminaLoss()
var/stam_mobility_mult = 1
- if(stamloss > STAMINA_NEAR_SOFTCRIT) //The more tired you are, the less damage you do.
- var/penalty = (stamloss - STAMINA_NEAR_SOFTCRIT)/(STAMINA_NEAR_CRIT - STAMINA_NEAR_SOFTCRIT)*STAM_CRIT_ITEM_ATTACK_PENALTY
- stam_mobility_mult -= penalty
if(stam_mobility_mult > LYING_DAMAGE_PENALTY && !CHECK_MOBILITY(user, MOBILITY_STAND)) //damage penalty for fighting prone, doesn't stack with the above.
stam_mobility_mult = LYING_DAMAGE_PENALTY
. *= stam_mobility_mult
- var/bad_trait
- if(!(I.item_flags & NO_COMBAT_MODE_FORCE_MODIFIER))
- if(SEND_SIGNAL(user, COMSIG_COMBAT_MODE_CHECK, COMBAT_MODE_INACTIVE))
- bad_trait = SKILL_COMBAT_MODE //blacklist combat skills.
- if(SEND_SIGNAL(src, COMSIG_COMBAT_MODE_CHECK, COMBAT_MODE_ACTIVE))
- . *= 0.8
- else if(SEND_SIGNAL(src, COMSIG_COMBAT_MODE_CHECK, COMBAT_MODE_INACTIVE))
- . *= 1.2
-
if(!user.mind || !I.used_skills)
return
if(.)
- . = user.mind.item_action_skills_mod(I, ., I.skill_difficulty, SKILL_ATTACK_MOB, bad_trait)
+ . = user.mind.item_action_skills_mod(I, ., I.skill_difficulty, SKILL_ATTACK_MOB)
for(var/skill in I.used_skills)
if(!(SKILL_TRAIN_ATTACK_MOB in I.used_skills[skill]))
continue
@@ -263,21 +239,23 @@
. = (total_mass || w_class * STAM_COST_W_CLASS_MULT) * multiplier
if(!user)
return
- var/bad_trait
- if(SEND_SIGNAL(user, COMSIG_COMBAT_MODE_CHECK, COMBAT_MODE_INACTIVE))
- . *= STAM_COST_NO_COMBAT_MULT
- bad_trait = SKILL_COMBAT_MODE
if(used_skills && user.mind)
- . = user.mind.item_action_skills_mod(src, ., skill_difficulty, trait, bad_trait, FALSE)
- var/total_health = user.getStaminaLoss()
- . = clamp(., 0, STAMINA_NEAR_CRIT - total_health)
+ . = user.mind.item_action_skills_mod(src, ., skill_difficulty, trait, null, FALSE)
+
+/**
+ * Uses the amount of stamina required for a standard hit
+ */
+/obj/item/proc/UseStaminaBufferStandard(mob/living/user, multiplier = 1, trait = SKILL_STAMINA_COST, warn = TRUE)
+ ASSERT(user)
+ var/cost = getweight(user, multiplier, trait)
+ return user.UseStaminaBuffer(cost, warn)
/// How long this staggers for. 0 and negatives supported.
/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_override || force) / 2), 0, 10 SECONDS)
+ return clamp((1.5 + (w_class/5)) * ((force_override || force) / 1.5), 0, 10 SECONDS)
/obj/item/proc/do_stagger_action(mob/living/target, mob/living/user, force_override)
if(!CHECK_BITFIELD(target.status_flags, CANSTAGGER))
diff --git a/code/controllers/configuration/entries/game_options.dm b/code/controllers/configuration/entries/game_options.dm
index 8f55293820..c91ac2e0d4 100644
--- a/code/controllers/configuration/entries/game_options.dm
+++ b/code/controllers/configuration/entries/game_options.dm
@@ -230,14 +230,6 @@
/datum/config_entry/keyed_list/multiplicative_movespeed
key_mode = KEY_MODE_TYPE
value_mode = VALUE_MODE_NUM
- config_entry_value = list( //DEFAULTS
- /mob/living/simple_animal = 1,
- /mob/living/silicon/pai = 1,
- /mob/living/carbon/alien/humanoid/sentinel = 0.25,
- /mob/living/carbon/alien/humanoid/drone = 0.5,
- /mob/living/carbon/alien/humanoid/royal/praetorian = 1,
- /mob/living/carbon/alien/humanoid/royal/queen = 3
- )
/datum/config_entry/keyed_list/multiplicative_movespeed/ValidateAndSet()
. = ..()
@@ -249,6 +241,24 @@
if(. && (var_name == NAMEOF(src, config_entry_value)))
update_config_movespeed_type_lookup(TRUE)
+/datum/config_entry/keyed_list/multiplicative_movespeed/normal
+ config_entry_value = list( //DEFAULTS
+ /mob/living/simple_animal = 1,
+ /mob/living/silicon/pai = 1,
+ /mob/living/carbon/alien/humanoid/sentinel = 0.25,
+ /mob/living/carbon/alien/humanoid/drone = 0.5,
+ /mob/living/carbon/alien/humanoid/royal/praetorian = 1,
+ /mob/living/carbon/alien/humanoid/royal/queen = 3
+ )
+
+/datum/config_entry/keyed_list/multiplicative_movespeed/floating
+ config_entry_value = list(
+ /mob/living = 0,
+ /mob/living/carbon/alien/humanoid = 0,
+ /mob/living/carbon/alien/humanoid/royal/praetorian = 0,
+ /mob/living/carbon/alien/humanoid/royal/queen = 2
+ )
+
/datum/config_entry/number/movedelay //Used for modifying movement speed for mobs.
abstract_type = /datum/config_entry/number/movedelay
integer = FALSE
@@ -280,6 +290,12 @@
/datum/config_entry/number/movedelay/sprint_speed_increase
config_entry_value = 1
+/datum/config_entry/number/movedelay/sprint_max_tiles_increase
+ config_entry_value = 5
+
+/datum/config_entry/number/movedelay/sprint_absolute_max_tiles
+ config_entry_value = 13
+
/datum/config_entry/number/movedelay/sprint_buffer_max
config_entry_value = 24
@@ -291,7 +307,7 @@
/////////////////////////////////////////////////Outdated move delay
/datum/config_entry/number/outdated_movedelay
- deprecated_by = /datum/config_entry/keyed_list/multiplicative_movespeed
+ deprecated_by = /datum/config_entry/keyed_list/multiplicative_movespeed/normal
abstract_type = /datum/config_entry/number/outdated_movedelay
var/movedelay_type
diff --git a/code/controllers/configuration/entries/stamina_combat.dm b/code/controllers/configuration/entries/stamina_combat.dm
new file mode 100644
index 0000000000..65580899f1
--- /dev/null
+++ b/code/controllers/configuration/entries/stamina_combat.dm
@@ -0,0 +1,35 @@
+/datum/config_entry/number/stamina_combat
+ integer = FALSE
+ abstract_type = /datum/config_entry/number/stamina_combat
+
+/// Maximum stamina buffer
+/datum/config_entry/number/stamina_combat/buffer_max
+ config_entry_value = 25
+
+/// Seconds until percent_regeneration_out_of_combat kicks in
+/datum/config_entry/number/stamina_combat/out_of_combat_timer
+ config_entry_value = 15
+
+/// Base regeneration per second
+/datum/config_entry/number/stamina_combat/base_regeneration
+ config_entry_value = 0.5
+
+/// Combat mode regeneration per second
+/datum/config_entry/number/stamina_combat/combat_regeneration
+ config_entry_value = 5
+
+/// After out_of_combat_timer elapses, additionally regenerate this percent of total stamina per second. Unaffected by combat mode.
+/datum/config_entry/number/stamina_combat/percent_regeneration_out_of_combat
+ config_entry_value = 30
+
+/// Seconds after an action for which your regeneration is penalized
+/datum/config_entry/number/stamina_combat/post_action_penalty_delay
+ config_entry_value = 5
+
+/// Factor to multiply by for penalizing post-action-stamina-regen
+/datum/config_entry/number/stamina_combat/post_action_penalty_factor
+ config_entry_value = 0.25
+
+/// Factor to multiply by for stamina usage past buffer into health
+/datum/config_entry/number/stamina_combat/overdraw_penalty_factor
+ config_entry_value = 1.5
diff --git a/code/controllers/subsystem/input.dm b/code/controllers/subsystem/input.dm
index 32936af9e9..bc3f6cf51b 100644
--- a/code/controllers/subsystem/input.dm
+++ b/code/controllers/subsystem/input.dm
@@ -64,10 +64,10 @@ SUBSYSTEM_DEF(input)
// Misc
macroset_classic_input["Tab"] = "\".winset \\\"mainwindow.macro=[SKIN_MACROSET_CLASSIC_HOTKEYS] map.focus=true input.background-color=[COLOR_INPUT_DISABLED]\\\"\""
macroset_classic_input["Escape"] = "\".winset \\\"input.text=\\\"\\\"\\\"\""
-
+
// FINALLY, WE CAN DO SOMETHING MORE NORMAL FOR THE SNOWFLAKE-BUT-LESS KEYSET.
-
- // HAHA - SIKE. Because of BYOND weirdness (tl;dr not specifically binding this way results in potentially duplicate chatboxes when
+
+ // HAHA - SIKE. Because of BYOND weirdness (tl;dr not specifically binding this way results in potentially duplicate chatboxes when
// conflicts occur with something like say indicator vs say), we're going to snowflake this anyways
var/list/hard_binds = list(
"O" = "ooc",
@@ -80,7 +80,7 @@ SUBSYSTEM_DEF(input)
for(var/key in hard_binds)
for(var/modifier in anti_collision_modifiers)
hard_bind_anti_collision["[modifier]+[key]"] = ".NONSENSICAL_VERB_THAT_DOES_NOTHING"
-
+
macroset_classic_hotkey = list(
"Any" = "\"KeyDown \[\[*\]\]\"",
"Any+UP" = "\"KeyUp \[\[*\]\]\"",
@@ -88,7 +88,7 @@ SUBSYSTEM_DEF(input)
"Escape" = "\".winset \\\"input.text=\\\"\\\"\\\"\"",
"Back" = "\".winset \\\"input.text=\\\"\\\"\\\"\"",
)
-
+
macroset_classic_hotkey |= hard_binds
macroset_classic_hotkey |= hard_bind_anti_collision
@@ -100,7 +100,7 @@ SUBSYSTEM_DEF(input)
"Escape" = "\".winset \\\"input.text=\\\"\\\"\\\"\"",
"Back" = "\".winset \\\"input.text=\\\"\\\"\\\"\"",
)
-
+
macroset_hotkey |= hard_binds
macroset_hotkey |= hard_bind_anti_collision
diff --git a/code/controllers/subsystem/job.dm b/code/controllers/subsystem/job.dm
index e61a3b3854..0f936bdeff 100644
--- a/code/controllers/subsystem/job.dm
+++ b/code/controllers/subsystem/job.dm
@@ -55,9 +55,10 @@ SUBSYSTEM_DEF(job)
continue
if(!job.config_check())
continue
- if(!job.map_check()) //Even though we initialize before mapping, this is fine because the config is loaded at new
+ if(!job.map_check(SSmapping.config)) //Even though we initialize before mapping, this is fine because the config is loaded at new
testing("Removed [job.type] due to map config");
continue
+ job.process_map_overrides(SSmapping.config)
occupations += job
name_occupations[job.title] = job
type_occupations[J] = job
diff --git a/code/controllers/subsystem/minimum_spawns.dm b/code/controllers/subsystem/minimum_spawns.dm
new file mode 100644
index 0000000000..caab2b1949
--- /dev/null
+++ b/code/controllers/subsystem/minimum_spawns.dm
@@ -0,0 +1,144 @@
+SUBSYSTEM_DEF(min_spawns)
+ name = "Minimum Spawns" /// this hot steaming pile of garbage makes sure theres a minimum of tendrils scattered around
+ init_order = INIT_ORDER_DEFAULT
+ flags = SS_BACKGROUND | SS_NO_FIRE | SS_ALWAYS_SHOW_STAT
+ wait = 2
+ var/where_we_droppin_boys_iterations = 0
+ var/snaxi_snowflake_check = FALSE
+ var/list/active_spawns = list() // lavaland, snaxi, etc. primary spawn list
+ var/list/active_spawns_2 = list() // snaxi underground, etc. secondary spawn list
+ var/list/valid_mining_turfs = list() // lavaland/snaxi turfs
+ var/list/valid_mining_turfs_2 = list() // snaxi underground turfs
+
+GLOBAL_LIST_INIT(minimum_lavaland_spawns, list(
+ /obj/structure/spawner/lavaland,
+ /obj/structure/spawner/lavaland/goliath,
+ /obj/structure/spawner/lavaland/legion,
+ /mob/living/simple_animal/hostile/megafauna/dragon,
+ /mob/living/simple_animal/hostile/megafauna/colossus,
+ /mob/living/simple_animal/hostile/megafauna/bubblegum
+))
+
+GLOBAL_LIST_INIT(minimum_snow_surface_spawns, list(
+ /obj/structure/spawner/ice_moon,
+ /obj/structure/spawner/ice_moon/polarbear
+))
+GLOBAL_LIST_INIT(minimum_snow_under_spawns, list(
+ /obj/structure/spawner/ice_moon/demonic_portal,
+ /obj/structure/spawner/ice_moon/demonic_portal/ice_whelp,
+ /obj/structure/spawner/ice_moon/demonic_portal/snowlegion
+))
+
+// step 1: check for which list(s) to use - done
+// step 2: check for caves - done
+// step 3: check for mobs - done
+// step 4: start throwing shit down - done
+// step 5: snaxi support - done?
+
+/datum/controller/subsystem/min_spawns/Initialize(start_timeofday)
+ var/list/snaxi_zs_list = SSmapping.levels_by_trait(ZTRAIT_ICE_RUINS) // boy if these things arent mutually exclusive
+ var/list/lavaland_zs_list = SSmapping.levels_by_trait(ZTRAIT_LAVA_RUINS) // i'm gonna fuckin scream
+ if(snaxi_zs_list.len)
+ active_spawns = GLOB.minimum_snow_surface_spawns
+ active_spawns_2 = GLOB.minimum_snow_under_spawns
+ snaxi_snowflake_check = TRUE
+ else if(lavaland_zs_list.len) //todo: recognizing maps that aren't lavaland mining but are also not snaxi
+ active_spawns = GLOB.minimum_lavaland_spawns
+ else
+ return ..() // call it a day i guess
+ // borrowing this from auxbase code - see code\modules\mining\aux_base.dm
+ if(snaxi_snowflake_check)
+ for(var/z_level in SSmapping.levels_by_trait(ZTRAIT_ICE_RUINS))
+ for(var/turf/TT in Z_TURFS(z_level))
+ if(!isarea(TT.loc))
+ continue
+ var/area/A = TT.loc
+ if(!A.mob_spawn_allowed)
+ continue
+ if(!istype(TT, /turf/open/floor/plating/asteroid))
+ continue
+ if(typesof(/turf/open/lava) in orange(9, TT))
+ continue
+ valid_mining_turfs.Add(TT)
+ for(var/z_level in SSmapping.levels_by_trait(ZTRAIT_ICE_RUINS_UNDERGROUND))
+ for(var/turf/TT in Z_TURFS(z_level))
+ if(!isarea(TT.loc))
+ continue
+ var/area/A = TT.loc
+ if(!A.mob_spawn_allowed)
+ continue
+ if(!istype(TT, /turf/open/floor/plating/asteroid))
+ continue
+ if(typesof(/turf/open/lava) in orange(9, TT))
+ continue
+ valid_mining_turfs_2.Add(TT)
+ else
+ for(var/z_level in SSmapping.levels_by_trait(ZTRAIT_LAVA_RUINS))
+ for(var/turf/TT in Z_TURFS(z_level))
+ if(!isarea(TT.loc))
+ continue
+ var/area/A = TT.loc
+ if(!A.mob_spawn_allowed)
+ continue
+ if(!istype(TT, /turf/open/floor/plating/asteroid))
+ continue
+ if(typesof(/turf/open/lava) in orange(9, TT))
+ continue
+ valid_mining_turfs.Add(TT)
+ if(!valid_mining_turfs.len)
+ return ..() // call it a day i guess
+ // if we're at this point we might as well fucking hit it
+ where_we_droppin_boys()
+ return ..()
+
+/datum/controller/subsystem/min_spawns/proc/where_we_droppin_boys()
+ while(active_spawns.len)
+ where_we_droppin_boys_iterations++
+ CHECK_TICK
+ if(where_we_droppin_boys_iterations >= 1250)
+ INIT_ANNOUNCE("Minimum Spawns subsystem stopped early on spawns list 1 - too many iterations!")
+ break
+ var/turf/RT = pick_n_take(valid_mining_turfs) //Pick a random mining Z-level turf
+ var/MS_tospawn = pick_n_take(active_spawns)
+ for(var/mob/living/simple_animal/hostile/megafauna/H in urange(70,RT)) //prevents mob clumps
+ if((istype(MS_tospawn, /mob/living/simple_animal/hostile/megafauna)) && get_dist(RT, H) <= 70)
+ active_spawns.Add(MS_tospawn)
+ continue //let's try not to dump megas too close to each other?
+ if((istype(MS_tospawn, /obj/structure/spawner)) && get_dist(RT, H) <= 40)
+ active_spawns.Add(MS_tospawn)
+ continue //let's at least /try/ to space these out?
+ for(var/obj/structure/spawner/LT in urange(70,RT)) //prevents tendril/mega clumps
+ if((istype(MS_tospawn, /mob/living/simple_animal/hostile/megafauna)) && get_dist(RT, LT) <= 70)
+ active_spawns.Add(MS_tospawn)
+ continue //let's try not to dump megas too close to each other?
+ if((istype(MS_tospawn, /obj/structure/spawner)) && get_dist(RT, LT) <= 40)
+ active_spawns.Add(MS_tospawn)
+ continue //let's at least /try/ to space these out?
+ // man the overhead on this is gonna SUCK
+ new MS_tospawn(RT)
+ while(active_spawns_2.len)
+ where_we_droppin_boys_iterations++
+ CHECK_TICK
+ if(where_we_droppin_boys_iterations >= 1250)
+ INIT_ANNOUNCE("Minimum Spawns subsystem stopped early on active list 2 - too many iterations!")
+ break
+ var/turf/RT2 = pick_n_take(valid_mining_turfs_2) //Pick a random mining Z-level turf
+ var/MS2_tospawn = pick_n_take(active_spawns_2)
+ for(var/mob/living/simple_animal/hostile/H in urange(70,RT2)) //prevents mob clumps
+ if((istype(MS2_tospawn, /mob/living/simple_animal/hostile/megafauna) || ismegafauna(H)) && get_dist(RT2, H) <= 70)
+ active_spawns_2.Add(MS2_tospawn)
+ continue //let's try not to dump megas too close to each other?
+ if((istype(MS2_tospawn, /obj/structure/spawner)) && get_dist(RT2, H) <= 40)
+ active_spawns_2.Add(MS2_tospawn)
+ continue //let's at least /try/ to space these out?
+ for(var/obj/structure/spawner/LT in urange(70,RT2)) //prevents tendril/mega clumps
+ if((istype(MS2_tospawn, /mob/living/simple_animal/hostile/megafauna)) && get_dist(RT2, LT) <= 70)
+ active_spawns_2.Add(MS2_tospawn)
+ continue //let's try not to dump megas too close to each other?
+ if((istype(MS2_tospawn, /obj/structure/spawner)) && get_dist(RT2, LT) <= 40)
+ active_spawns_2.Add(MS2_tospawn)
+ continue //let's at least /try/ to space these out?
+ // man the overhead on this is gonna SUCK
+ new MS2_tospawn(RT2)
+ if(!active_spawns.len && !active_spawns_2.len)
+ return // we're done here
diff --git a/code/controllers/subsystem/npcpool.dm b/code/controllers/subsystem/npcpool.dm
index c67deaede1..c20820c092 100644
--- a/code/controllers/subsystem/npcpool.dm
+++ b/code/controllers/subsystem/npcpool.dm
@@ -5,6 +5,10 @@ SUBSYSTEM_DEF(npcpool)
runlevels = RUNLEVEL_GAME | RUNLEVEL_POSTGAME
var/list/currentrun = list()
+ /// catches sleeping
+ var/invoking = FALSE
+ /// Invoke start time
+ var/invoke_start = 0
/datum/controller/subsystem/npcpool/stat_entry(msg)
var/list/activelist = GLOB.simple_animals[AI_ON]
@@ -12,7 +16,6 @@ SUBSYSTEM_DEF(npcpool)
return ..()
/datum/controller/subsystem/npcpool/fire(resumed = FALSE)
-
if (!resumed)
var/list/activelist = GLOB.simple_animals[AI_ON]
src.currentrun = activelist.Copy()
@@ -24,12 +27,22 @@ SUBSYSTEM_DEF(npcpool)
var/mob/living/simple_animal/SA = currentrun[currentrun.len]
--currentrun.len
- if(!SA.ckey && !SA.mob_transforming)
- if(SA.stat != DEAD)
- SA.handle_automated_movement()
- if(SA.stat != DEAD)
- SA.handle_automated_action()
- if(SA.stat != DEAD)
- SA.handle_automated_speech()
+ invoking = TRUE
+ invoke_start = world.time
+ INVOKE_ASYNC(src, .proc/invoke_process, SA)
+ if(invoking)
+ stack_trace("WARNING: [SA] ([SA.type]) slept during NPCPool processing.")
+ invoking = FALSE
+
if (MC_TICK_CHECK)
return
+
+/datum/controller/subsystem/npcpool/proc/invoke_process(mob/living/simple_animal/SA)
+ if(!SA.ckey && !SA.mob_transforming)
+ if(SA.stat != DEAD)
+ SA.handle_automated_movement()
+ if(SA.stat != DEAD)
+ SA.handle_automated_action()
+ if(SA.stat != DEAD)
+ SA.handle_automated_speech()
+ invoking = FALSE
diff --git a/code/datums/diseases/transformation.dm b/code/datums/diseases/transformation.dm
index e90eededd9..f952307685 100644
--- a/code/datums/diseases/transformation.dm
+++ b/code/datums/diseases/transformation.dm
@@ -236,12 +236,12 @@
switch(stage)
if(1)
if(ishuman(affected_mob) && affected_mob.dna)
- if(affected_mob.dna.species.id == "slime" || affected_mob.dna.species.id == "stargazer" || affected_mob.dna.species.id == "lum")
+ if(affected_mob.dna.species.id == "slime" || affected_mob.dna.species.id == "stargazer" || affected_mob.dna.species.id == "slimelumi")
stage = 5
if(3)
if(ishuman(affected_mob))
var/mob/living/carbon/human/human = affected_mob
- if(human.dna.species.id != "slime" && affected_mob.dna.species.id != "stargazer" && affected_mob.dna.species.id != "lum")
+ if(human.dna.species.id != "slime" && affected_mob.dna.species.id != "stargazer" && affected_mob.dna.species.id != "slimelumi")
human.set_species(/datum/species/jelly/slime)
/datum/disease/transformation/corgi
diff --git a/code/datums/martial/sleeping_carp.dm b/code/datums/martial/sleeping_carp.dm
index 7d884344f1..81b7ea0628 100644
--- a/code/datums/martial/sleeping_carp.dm
+++ b/code/datums/martial/sleeping_carp.dm
@@ -130,7 +130,7 @@
playsound(get_turf(A), pick('sound/weapons/bulletflyby.ogg', 'sound/weapons/bulletflyby2.ogg', 'sound/weapons/bulletflyby3.ogg'), 75, TRUE)
P.firer = A
P.setAngle(rand(0, 360))//SHING
- A.adjustStaminaLossBuffered (3) //Citadel change to stop infinite bullet sponging as you run away, but it is buffered!
+ A.adjustStaminaLoss(3)
return BULLET_ACT_FORCE_PIERCE
return BULLET_ACT_HIT
diff --git a/code/datums/ruins/lavaland.dm b/code/datums/ruins/lavaland.dm
index 933eaf082e..a08b535aff 100644
--- a/code/datums/ruins/lavaland.dm
+++ b/code/datums/ruins/lavaland.dm
@@ -114,14 +114,12 @@
description = "..."
suffix = "lavaland_surface_sloth.dmm"
// Generates nothing but atmos runtimes and salt
- cost = 0
/datum/map_template/ruin/lavaland/ratvar
name = "Dead God"
id = "ratvar"
- description = "Ratvars final resting place."
+ description = "Ratvar's final resting place."
suffix = "lavaland_surface_dead_ratvar.dmm"
- cost = 0
allow_duplicates = FALSE
/datum/map_template/ruin/lavaland/hierophant
@@ -137,7 +135,7 @@
id = "blooddrunk"
description = "A strange arrangement of stone tiles and an insane, beastly miner contemplating them."
suffix = "lavaland_surface_blooddrunk1.dmm"
- cost = 0
+ always_place = TRUE
allow_duplicates = FALSE //will only spawn one variant of the ruin
/datum/map_template/ruin/lavaland/blood_drunk_miner/guidance
diff --git a/code/datums/weather/weather_types/ash_storm.dm b/code/datums/weather/weather_types/ash_storm.dm
index 43190ef50c..6248be0de5 100644
--- a/code/datums/weather/weather_types/ash_storm.dm
+++ b/code/datums/weather/weather_types/ash_storm.dm
@@ -95,8 +95,8 @@
if(is_ash_immune(L))
return
if(is_species(L, /datum/species/lizard/ashwalker))
- if(!IS_STAMCRIT(L))
- L.adjustStaminaLossBuffered(4)
+ if(L.getStaminaLoss() < (STAMINA_CRIT - 40))
+ L.adjustStaminaLoss(4)
return
L.adjustFireLoss(4)
diff --git a/code/game/atoms_movable.dm b/code/game/atoms_movable.dm
index 0a6c2b9eca..4715c3bed3 100644
--- a/code/game/atoms_movable.dm
+++ b/code/game/atoms_movable.dm
@@ -36,6 +36,7 @@
var/grab_state = 0
var/throwforce = 0
var/datum/component/orbiter/orbiting
+ /// Used for space ztransit stuff
var/can_be_z_moved = TRUE
///If we were without gravity and another animation happened, the bouncing will stop, and we need to restart it in next life().
var/floating_need_update = FALSE
diff --git a/code/game/machinery/wishgranter.dm b/code/game/machinery/wishgranter.dm
index 12fdc2193b..6aeef4b6ac 100644
--- a/code/game/machinery/wishgranter.dm
+++ b/code/game/machinery/wishgranter.dm
@@ -84,7 +84,7 @@
if(is_station_level(T.z))
destinations += B
var/chosen_beacon = pick(destinations)
- var/obj/effect/portal/jaunt_tunnel/J = new (get_turf(src), src, 100, null, FALSE, get_turf(chosen_beacon))
+ var/obj/effect/portal/jaunt_tunnel/J = new (get_turf(src), 100, null, FALSE, get_turf(chosen_beacon))
try_move_adjacent(J)
playsound(src,'sound/effects/sparks4.ogg',50,1)
charges--
diff --git a/code/game/mecha/equipment/tools/other_tools.dm b/code/game/mecha/equipment/tools/other_tools.dm
index 8039aabd1f..4dbb57479f 100644
--- a/code/game/mecha/equipment/tools/other_tools.dm
+++ b/code/game/mecha/equipment/tools/other_tools.dm
@@ -56,7 +56,7 @@
var/turf/target_turf = pick(L)
if(!target_turf)
return
- var/list/obj/effect/portal/created = create_portal_pair(get_turf(src), target_turf, src, 300, 1, /obj/effect/portal/anom)
+ var/list/obj/effect/portal/created = create_portal_pair(get_turf(src), target_turf, 300, 1, /obj/effect/portal/anom)
var/turf/T = get_turf(target)
message_admins("[ADMIN_LOOKUPFLW(chassis.occupant)] used a Wormhole Generator in [ADMIN_VERBOSEJMP(T)]")
log_game("[key_name(chassis.occupant)] used a Wormhole Generator in [AREACOORD(T)]")
diff --git a/code/game/objects/items.dm b/code/game/objects/items.dm
index b4c114eb6a..1c75f1e533 100644
--- a/code/game/objects/items.dm
+++ b/code/game/objects/items.dm
@@ -315,7 +315,7 @@ GLOBAL_VAR_INIT(embedpocalypse, FALSE) // if true, all items will be able to emb
return
if(loc == user && current_equipped_slot && current_equipped_slot != SLOT_HANDS)
if(current_equipped_slot in user.check_obscured_slots())
- to_chat(src, "You are unable to unequip that while wearing other garments over it!")
+ to_chat(user, "You are unable to unequip that while wearing other garments over it!")
return FALSE
if(resistance_flags & ON_FIRE)
@@ -383,7 +383,7 @@ GLOBAL_VAR_INIT(embedpocalypse, FALSE) // if true, all items will be able to emb
return
if(loc == user && current_equipped_slot && current_equipped_slot != SLOT_HANDS)
if(current_equipped_slot in user.check_obscured_slots())
- to_chat(src, "You are unable to unequip that while wearing other garments over it!")
+ to_chat(user, "You are unable to unequip that while wearing other garments over it!")
return FALSE
@@ -565,7 +565,7 @@ GLOBAL_VAR_INIT(embedpocalypse, FALSE) // if true, all items will be able to emb
to_chat(user, "You cannot locate any organic eyes on this brain!")
return
- if(IS_STAMCRIT(user))//CIT CHANGE - makes eyestabbing impossible if you're in stamina softcrit
+ if(IS_STAMCRIT(user) || !user.UseStaminaBuffer(STAMINA_COST_ITEM_EYESTAB, warn = TRUE))//CIT CHANGE - makes eyestabbing impossible if you're in stamina softcrit
to_chat(user, "You're too exhausted for that.")//CIT CHANGE - ditto
return //CIT CHANGE - ditto
@@ -575,8 +575,6 @@ GLOBAL_VAR_INIT(embedpocalypse, FALSE) // if true, all items will be able to emb
user.do_attack_animation(M)
- user.adjustStaminaLossBuffered(10)//CIT CHANGE - makes eyestabbing cost stamina
-
if(M != user)
M.visible_message("[user] has stabbed [M] in the eye with [src]!", \
"[user] stabs you in the eye with [src]!")
diff --git a/code/game/objects/items/defib.dm b/code/game/objects/items/defib.dm
index 484901990a..4777796331 100644
--- a/code/game/objects/items/defib.dm
+++ b/code/game/objects/items/defib.dm
@@ -4,7 +4,7 @@
/obj/item/defibrillator
name = "defibrillator"
desc = "A device that delivers powerful shocks to detachable paddles that resuscitate incapacitated patients."
- icon = 'icons/obj/items_and_weapons.dmi'
+ icon = 'icons/obj/defibrillators.dmi'
icon_state = "defibunit"
item_state = "defibunit"
lefthand_file = 'icons/mob/inhands/equipment/medical_lefthand.dmi'
@@ -23,8 +23,8 @@
var/obj/item/stock_parts/cell/cell
var/combat = FALSE //can we revive through space suits?
var/grab_ghost = FALSE // Do we pull the ghost back into their body?
- var/healdisk = FALSE // Will we shock people dragging the body?
- var/pullshocksafely = FALSE //Dose the unit have the healdisk upgrade?
+ var/healdisk = FALSE // Does the unit have the healdisk upgrade?
+ var/pullshocksafely = FALSE // Will we shock people dragging the body?
var/primetime = 0 // is the defib faster
var/timedeath = 10
var/disarm_shock_time = 10
@@ -261,7 +261,7 @@
/obj/item/shockpaddles
name = "defibrillator paddles"
desc = "A pair of plastic-gripped paddles with flat metal surfaces that are used to deliver powerful electric shocks."
- icon = 'icons/obj/items_and_weapons.dmi'
+ icon = 'icons/obj/defibrillators.dmi'
icon_state = "defibpaddles0"
item_state = "defibpaddles0"
lefthand_file = 'icons/mob/inhands/equipment/medical_lefthand.dmi'
@@ -682,7 +682,7 @@
/obj/item/shockpaddles/cyborg
name = "cyborg defibrillator paddles"
- icon = 'icons/obj/items_and_weapons.dmi'
+ icon = 'icons/obj/defibrillators.dmi'
icon_state = "defibpaddles0"
item_state = "defibpaddles0"
req_defib = FALSE
@@ -703,7 +703,7 @@
name = "syndicate defibrillator paddles"
desc = "A pair of paddles used to revive deceased operatives. It possesses both the ability to penetrate armor and to deliver powerful shocks offensively."
combat = TRUE
- icon = 'icons/obj/items_and_weapons.dmi'
+ icon = 'icons/obj/defibrillators.dmi'
icon_state = "defibpaddles0"
item_state = "defibpaddles0"
req_defib = FALSE
diff --git a/code/game/objects/items/electrostaff.dm b/code/game/objects/items/electrostaff.dm
index 65d2fdd699..31aaff12b5 100644
--- a/code/game/objects/items/electrostaff.dm
+++ b/code/game/objects/items/electrostaff.dm
@@ -21,12 +21,11 @@
var/can_block_projectiles = FALSE //can't block guns
var/lethal_cost = 400 //10000/400*20 = 500. decent enough?
var/lethal_damage = 20
- var/lethal_stam_cost = 4
var/stun_cost = 333 //10000/333*25 = 750. stunbatons are at time of writing 10000/1000*49 = 490.
var/stun_status_effect = STATUS_EFFECT_ELECTROSTAFF //a small slowdown effect
var/stun_stamdmg = 40
var/stun_status_duration = 25
- var/stun_stam_cost = 3.5
+ var/stam_cost = 3.5
var/wielded = FALSE // track wielded status on item
// haha security desword time /s
@@ -171,7 +170,7 @@
turn_off()
/obj/item/electrostaff/attack(mob/living/target, mob/living/user)
- if(IS_STAMCRIT(user))//CIT CHANGE - makes it impossible to baton in stamina softcrit
+ if(IS_STAMCRIT(user) || !user.UseStaminaBuffer(stam_cost))//CIT CHANGE - makes it impossible to baton in stamina softcrit
to_chat(user, "You're too exhausted to use [src] properly.")//CIT CHANGE - ditto
return //CIT CHANGE - ditto
if(on && HAS_TRAIT(user, TRAIT_CLUMSY) && prob(50))
@@ -186,13 +185,11 @@
if(user.a_intent != INTENT_HARM)
if(stun_act(target, user, null, return_list))
user.do_attack_animation(target)
- user.adjustStaminaLossBuffered(stun_stam_cost)
return
else if(!harm_act(target, user, null, return_list))
return ..() //if you can't fry them just beat them with it
else //we did harm act them
user.do_attack_animation(target)
- user.adjustStaminaLossBuffered(lethal_stam_cost)
/obj/item/electrostaff/proc/stun_act(mob/living/target, mob/living/user, no_charge_and_force = FALSE, list/block_return = list())
var/stunforce = block_calculate_resultant_damage(stun_stamdmg, block_return)
diff --git a/code/game/objects/items/melee/energy.dm b/code/game/objects/items/melee/energy.dm
index 679491aeb6..7d95b8e736 100644
--- a/code/game/objects/items/melee/energy.dm
+++ b/code/game/objects/items/melee/energy.dm
@@ -190,12 +190,53 @@
/obj/item/melee/transforming/energy/sword/saber
possible_colors = list("red" = LIGHT_COLOR_RED, "blue" = LIGHT_COLOR_LIGHT_CYAN, "green" = LIGHT_COLOR_GREEN, "purple" = LIGHT_COLOR_LAVENDER)
+ unique_reskin = list("Sword" = "sword0", "saber" = "esaber0")
var/hacked = FALSE
+ var/saber = FALSE
-/obj/item/melee/transforming/energy/sword/saber/set_sword_color()
- if(LAZYLEN(possible_colors))
+/obj/item/melee/transforming/energy/sword/saber/transform_weapon(mob/living/user, supress_message_text)
+ . = ..()
+ if(.)
+ if(active)
+ if(sword_color)
+ if(saber)
+ icon_state = "esaber[sword_color]"
+ else
+ icon_state = "sword[sword_color]"
+ else
+ if(saber)
+ icon_state = "esaber0"
+ else
+ icon_state = "sword0"
+
+/obj/item/melee/transforming/energy/sword/saber/reskin_obj(mob/M)
+ . = ..()
+ if(icon_state == "esaber0")
+ saber = TRUE
+ if(active)
+ if(saber)
+ icon_state = "esaber[sword_color]"
+ else
+ icon_state = "sword[sword_color]"
+
+/obj/item/melee/transforming/energy/sword/saber/set_sword_color(var/color_forced)
+ if(color_forced) // wow i really do not like this at fucking all holy SHIT
+ if(color_forced == "red")
+ sword_color = "red"
+ light_color = LIGHT_COLOR_RED
+ else if(color_forced == "blue")
+ sword_color = "blue"
+ light_color = LIGHT_COLOR_LIGHT_CYAN
+ else if(color_forced == "green")
+ sword_color = "green"
+ light_color = LIGHT_COLOR_GREEN
+ else if(color_forced == "purple")
+ sword_color = "purple"
+ light_color = LIGHT_COLOR_LAVENDER
+ else if(LAZYLEN(possible_colors))
sword_color = pick(possible_colors)
light_color = possible_colors[sword_color]
+ return
/obj/item/melee/transforming/energy/sword/saber/process()
. = ..()
@@ -204,30 +245,59 @@
light_color = possible_colors[set_color]
update_light()
-/obj/item/melee/transforming/energy/sword/saber/red
- possible_colors = list("red" = LIGHT_COLOR_RED)
+/obj/item/melee/transforming/energy/sword/saber/red/Initialize(mapload)
+ . = ..()
+ set_sword_color("red")
-/obj/item/melee/transforming/energy/sword/saber/blue
- possible_colors = list("blue" = LIGHT_COLOR_LIGHT_CYAN)
+/obj/item/melee/transforming/energy/sword/saber/blue/Initialize(mapload)
+ . = ..()
+ set_sword_color("blue")
-/obj/item/melee/transforming/energy/sword/saber/green
- possible_colors = list("green" = LIGHT_COLOR_GREEN)
+/obj/item/melee/transforming/energy/sword/saber/green/Initialize(mapload)
+ . = ..()
+ set_sword_color("green")
-/obj/item/melee/transforming/energy/sword/saber/purple
- possible_colors = list("purple" = LIGHT_COLOR_LAVENDER)
+/obj/item/melee/transforming/energy/sword/saber/purple/Initialize(mapload)
+ . = ..()
+ set_sword_color("purple")
+
+/obj/item/melee/transforming/energy/sword/saber/proc/select_sword_color(mob/user) /// this is for the radial
+ if(!istype(user) || user.incapacitated())
+ return
+
+ var/static/list/options = list(
+ "red" = image(icon = 'icons/obj/items_and_weapons.dmi', icon_state = "swordred-blade"),
+ "blue" = image(icon = 'icons/obj/items_and_weapons.dmi', icon_state = "swordblue-blade"),
+ "green" = image(icon = 'icons/obj/items_and_weapons.dmi', icon_state = "swordgreen-blade"),
+ "purple" = image(icon = 'icons/obj/items_and_weapons.dmi', icon_state = "swordpurple-blade")
+ )
+
+ var/choice = show_radial_menu(user, src, options, custom_check = FALSE, radius = 36, require_near = TRUE)
+
+ if(src && choice && !user.incapacitated() && in_range(user,src))
+ set_sword_color(choice)
+ to_chat(user, "[src] is now [choice].")
/obj/item/melee/transforming/energy/sword/saber/attackby(obj/item/W, mob/living/user, params)
if(istype(W, /obj/item/multitool))
+ if(user.a_intent == INTENT_DISARM)
+ if(!active)
+ to_chat(user, "COLOR_SET")
+ hacked = FALSE
+ select_sword_color(user)
+ return
+ else
+ to_chat(user, "Turn it off first - getting that close to an active sword is not a great idea.")
+ return
if(!hacked)
hacked = TRUE
sword_color = "rainbow"
to_chat(user, "RNBW_ENGAGE")
-
if(active)
icon_state = "swordrainbow"
user.update_inv_hands()
else
- to_chat(user, "It's already fabulous!")
+ to_chat(user, "It's already fabulous! If you wanted to reset the color, though, try a disarming intent while it's off.")
else
return ..()
diff --git a/code/game/objects/items/melee/misc.dm b/code/game/objects/items/melee/misc.dm
index 52ff0f740e..15d10c4d11 100644
--- a/code/game/objects/items/melee/misc.dm
+++ b/code/game/objects/items/melee/misc.dm
@@ -352,6 +352,8 @@
return
else
if(cooldown_check < world.time)
+ if(!UseStaminaBufferStandard(user, STAM_COST_BATON_MOB_MULT, warn = TRUE))
+ return DISCARD_LAST_ACTION
if(target.mob_run_block(src, 0, "[user]'s [name]", ATTACK_TYPE_MELEE, 0, user, null, null) & BLOCK_SUCCESS)
playsound(target, 'sound/weapons/genhit.ogg', 50, 1)
return
@@ -373,7 +375,6 @@
else
target.LAssailant = WEAKREF(user)
cooldown_check = world.time + cooldown
- 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/mop.dm b/code/game/objects/items/mop.dm
index b420bfc002..01ef96b7e8 100644
--- a/code/game/objects/items/mop.dm
+++ b/code/game/objects/items/mop.dm
@@ -56,22 +56,20 @@
return
if(T)
+ if(!L.UseStaminaBuffer(stamusage, warn = TRUE))
+ return
user.visible_message("[user] cleans \the [T] with [src].", "You clean \the [T] with [src].")
clean(T)
user.DelayNextAction(CLICK_CD_MELEE)
user.do_attack_animation(T, used_item = src)
- if(istype(L))
- L.adjustStaminaLossBuffered(stamusage)
playsound(T, "slosh", 50, 1)
-
/obj/effect/attackby(obj/item/I, mob/user, params)
if(istype(I, /obj/item/mop) || istype(I, /obj/item/soap))
return
else
return ..()
-
/obj/item/mop/proc/janicart_insert(mob/user, obj/structure/janitorialcart/J)
if(insertable)
J.put_in_cart(src, user)
diff --git a/code/game/objects/items/powerfist.dm b/code/game/objects/items/powerfist.dm
index 2834b3b758..bd83404356 100644
--- a/code/game/objects/items/powerfist.dm
+++ b/code/game/objects/items/powerfist.dm
@@ -6,7 +6,7 @@
lefthand_file = 'icons/mob/inhands/weapons/melee_lefthand.dmi'
righthand_file = 'icons/mob/inhands/weapons/melee_righthand.dmi'
flags_1 = CONDUCT_1
- item_flags = NEEDS_PERMIT | NO_COMBAT_MODE_FORCE_MODIFIER //To avoid ambushing and oneshotting healthy crewmembers on force setting 3.
+ item_flags = NEEDS_PERMIT
attack_verb = list("whacked", "fisted", "power-punched")
force = 20
throwforce = 10
@@ -76,6 +76,9 @@
if(!tank)
to_chat(user, "\The [src] can't operate without a source of gas!")
return FALSE
+ var/weight = getweight(user, STAM_COST_ATTACK_MOB_MULT)
+ if(!user.UseStaminaBuffer(weight, warn = TRUE))
+ return FALSE
var/datum/gas_mixture/gasused = tank.air_contents.remove(gasperfist * fisto_setting)
var/turf/T = get_turf(src)
if(!T)
@@ -108,8 +111,4 @@
target.throw_at(throw_target, 5 * fisto_setting, 0.5 + (fisto_setting / 2))
log_combat(user, target, "power fisted", src)
-
- var/weight = getweight(user, STAM_COST_ATTACK_MOB_MULT)
- if(weight)
- user.adjustStaminaLossBuffered(weight)
return TRUE
diff --git a/code/game/objects/items/shields.dm b/code/game/objects/items/shields.dm
index 4952c93928..d1d57fe375 100644
--- a/code/game/objects/items/shields.dm
+++ b/code/game/objects/items/shields.dm
@@ -136,15 +136,17 @@
if(!(shield_flags & SHIELD_BASH_GROUND_SLAM))
to_chat(user, "You can't ground slam with [src]!")
return FALSE
+ if(!user.UseStaminaBuffer(shieldbash_stamcost, warn = TRUE))
+ return FALSE
bash_target(user, target, NONE, harmful)
user.do_attack_animation(target, used_item = src)
playsound(src, harmful? "swing_hit" : 'sound/weapons/thudswoosh.ogg', 75, 1)
last_shieldbash = world.time
- user.adjustStaminaLossBuffered(shieldbash_stamcost)
return TRUE
// Directional sweep!
last_shieldbash = world.time
- user.adjustStaminaLossBuffered(shieldbash_stamcost)
+ if(!user.UseStaminaBuffer(shieldbash_stamcost, warn = TRUE))
+ return FALSE
// Since we are in combat mode, we can probably safely use the user's dir instead of getting their mouse pointing cardinal dir.
var/bashdir = user.dir
do_shieldbash_effect(user, bashdir, harmful)
diff --git a/code/game/objects/items/storage/bags.dm b/code/game/objects/items/storage/bags.dm
index b64aa60cac..ab1aab32a0 100644
--- a/code/game/objects/items/storage/bags.dm
+++ b/code/game/objects/items/storage/bags.dm
@@ -234,7 +234,7 @@
STR.max_w_class = WEIGHT_CLASS_NORMAL
STR.max_combined_w_class = 100
STR.max_items = 100
- STR.can_hold = typecacheof(list(/obj/item/reagent_containers/food/snacks/grown, /obj/item/seeds, /obj/item/grown, /obj/item/reagent_containers/honeycomb))
+ STR.can_hold = typecacheof(list(/obj/item/reagent_containers/food/snacks/grown, /obj/item/seeds, /obj/item/grown, /obj/item/reagent_containers/honeycomb, /obj/item/disk/plantgene))
////////
diff --git a/code/game/objects/items/storage/belt.dm b/code/game/objects/items/storage/belt.dm
index 590df34cde..28af52a646 100755
--- a/code/game/objects/items/storage/belt.dm
+++ b/code/game/objects/items/storage/belt.dm
@@ -830,3 +830,28 @@
var/datum/component/storage/STR = GetComponent(/datum/component/storage)
STR.max_items = 2
STR.max_w_class = WEIGHT_CLASS_BULKY + WEIGHT_CLASS_NORMAL //katana and waki.
+
+/obj/item/storage/belt/plant
+ name = "botanical belt"
+ desc = "A belt used to hold most hydroponics supplies. Suprisingly, not green."
+ icon_state = "plantbelt"
+ item_state = "plantbelt"
+ content_overlays = TRUE
+
+/obj/item/storage/belt/plant/ComponentInitialize()
+ . = ..()
+ var/datum/component/storage/STR = GetComponent(/datum/component/storage)
+ STR.max_items = 6
+ STR.max_w_class = WEIGHT_CLASS_NORMAL
+ STR.can_hold = typecacheof(list(
+ /obj/item/reagent_containers/spray/plantbgone,
+ /obj/item/plant_analyzer,
+ /obj/item/seeds,
+ /obj/item/reagent_containers/glass/bottle,
+ /obj/item/reagent_containers/glass/beaker,
+ /obj/item/cultivator,
+ /obj/item/reagent_containers/spray/pestspray,
+ /obj/item/hatchet,
+ /obj/item/shovel/spade,
+ /obj/item/gun/energy/floragun
+ ))
diff --git a/code/game/objects/items/stunbaton.dm b/code/game/objects/items/stunbaton.dm
index 5e76d506ba..a7d96fe6eb 100644
--- a/code/game/objects/items/stunbaton.dm
+++ b/code/game/objects/items/stunbaton.dm
@@ -166,13 +166,12 @@
if(turned_on)
if(baton_stun(M, user, disarming))
user.do_attack_animation(M)
- 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")
return disarming || (user.a_intent != INTENT_HARM)
-/obj/item/melee/baton/proc/baton_stun(mob/living/L, mob/user, disarming = FALSE)
+/obj/item/melee/baton/proc/baton_stun(mob/living/L, mob/living/user, disarming = FALSE)
var/list/return_list = list()
if(L.mob_run_block(src, 0, "[user]'s [name]", ATTACK_TYPE_MELEE, 0, user, null, return_list) & BLOCK_SUCCESS) //No message; check_shields() handles that
playsound(L, 'sound/weapons/genhit.ogg', 50, 1)
@@ -194,6 +193,9 @@
return FALSE
stunpwr *= round(stuncharge/hitcost, 0.1)
+ if(!user.UseStaminaBuffer(getweight(user, STAM_COST_BATON_MOB_MULT), warn = TRUE))
+ return FALSE
+
if(!disarming)
if(knockdown)
L.DefaultCombatKnockdown(50, override_stamdmg = 0) //knockdown
diff --git a/code/game/objects/items/tanks/jetpack.dm b/code/game/objects/items/tanks/jetpack.dm
index 7cf25098e1..29961d12b4 100644
--- a/code/game/objects/items/tanks/jetpack.dm
+++ b/code/game/objects/items/tanks/jetpack.dm
@@ -55,6 +55,8 @@
RegisterSignal(user, COMSIG_MOVABLE_MOVED, .proc/move_react)
if(full_speed)
user.add_movespeed_modifier(/datum/movespeed_modifier/jetpack/fullspeed)
+ else
+ user.add_movespeed_modifier(/datum/movespeed_modifier/jetpack)
/obj/item/tank/jetpack/proc/turn_off(mob/user)
on = FALSE
@@ -63,6 +65,7 @@
ion_trail.stop()
UnregisterSignal(user, COMSIG_MOVABLE_MOVED)
user.remove_movespeed_modifier(/datum/movespeed_modifier/jetpack/fullspeed)
+ user.remove_movespeed_modifier(/datum/movespeed_modifier/jetpack)
/obj/item/tank/jetpack/proc/move_react(mob/user)
allow_thrust(0.01, user)
diff --git a/code/game/objects/items/tools/screwdriver.dm b/code/game/objects/items/tools/screwdriver.dm
index bb507e5725..bf767af2ed 100644
--- a/code/game/objects/items/tools/screwdriver.dm
+++ b/code/game/objects/items/tools/screwdriver.dm
@@ -161,4 +161,4 @@
item_state = "screwdriver_nuke"
usesound = 'sound/items/pshoom.ogg'
toolspeed = 0.2
- random_color = FALSE
\ No newline at end of file
+ random_color = FALSE
diff --git a/code/game/objects/objs.dm b/code/game/objects/objs.dm
index 3abee4db33..8a14d99776 100644
--- a/code/game/objects/objs.dm
+++ b/code/game/objects/objs.dm
@@ -304,18 +304,36 @@
/obj/proc/reskin_obj(mob/M)
if(!LAZYLEN(unique_reskin))
return
- var/list/skins = list()
- for(var/S in unique_reskin)
- skins[S] = image(icon = icon, icon_state = unique_reskin[S])
- var/choice = show_radial_menu(M, src, skins, custom_check = CALLBACK(src, .proc/check_skinnable, M), radius = 40, require_near = TRUE)
- if(!choice)
- return FALSE
- icon_state = unique_reskin[choice]
- current_skin = choice
- return
-/obj/proc/check_skinnable(/mob/M)
- if(current_skin || !always_reskinnable)
+ var/list/items = list()
+ for(var/reskin_option in unique_reskin)
+ var/image/item_image = image(icon = src.icon, icon_state = unique_reskin[reskin_option])
+ items += list("[reskin_option]" = item_image)
+ sortList(items)
+
+ var/pick = show_radial_menu(M, src, items, custom_check = CALLBACK(src, .proc/check_reskin_menu, M), radius = 38, require_near = TRUE)
+ if(!pick)
+ return
+ if(!unique_reskin[pick])
+ return
+ current_skin = pick
+ icon_state = unique_reskin[pick]
+ to_chat(M, "[src] is now skinned as '[pick].'")
+
+/**
+ * Checks if we are allowed to interact with a radial menu for reskins
+ *
+ * Arguments:
+ * * user The mob interacting with the menu
+ */
+/obj/proc/check_reskin_menu(mob/user)
+ if(QDELETED(src))
+ return FALSE
+ if(current_skin)
+ return FALSE
+ if(!istype(user))
+ return FALSE
+ if(user.incapacitated())
return FALSE
return TRUE
diff --git a/code/game/objects/structures/tables_racks.dm b/code/game/objects/structures/tables_racks.dm
index 9f91d2c5e0..31f11a199c 100644
--- a/code/game/objects/structures/tables_racks.dm
+++ b/code/game/objects/structures/tables_racks.dm
@@ -73,7 +73,7 @@
if(user.grab_state < GRAB_AGGRESSIVE)
to_chat(user, "You need a better grip to do that!")
return
- if(user.grab_state >= GRAB_NECK)
+ if(user.grab_state >= GRAB_NECK || HAS_TRAIT(user, TRAIT_MAULER))
tablelimbsmash(user, pushed_mob)
else
tablepush(user, pushed_mob)
@@ -147,7 +147,7 @@
pushed_mob.Knockdown(30)
var/obj/item/bodypart/banged_limb = pushed_mob.get_bodypart(user.zone_selected) || pushed_mob.get_bodypart(BODY_ZONE_HEAD)
var/extra_wound = 0
- if(HAS_TRAIT(user, TRAIT_HULK))
+ if(HAS_TRAIT(user, TRAIT_HULK) || HAS_TRAIT(user, TRAIT_MAULER))
extra_wound = 20
banged_limb.receive_damage(30, wound_bonus = extra_wound)
pushed_mob.apply_damage(60, STAMINA)
diff --git a/code/game/turfs/space/space.dm b/code/game/turfs/space/space.dm
index 3df64550b1..b150d4930e 100644
--- a/code/game/turfs/space/space.dm
+++ b/code/game/turfs/space/space.dm
@@ -133,13 +133,10 @@
else
to_chat(user, "The plating is going to need some support! Place metal rods first.")
-/turf/open/space/Entered(atom/movable/A, atom/OldLoc)
- . = ..()
-
- var/turf/old = get_turf(OldLoc)
- if(!isspaceturf(old) && ismob(A))
- var/mob/M = A
- M.update_gravity(M.mob_has_gravity())
+/turf/open/space/Entered(atom/movable/A)
+ ..()
+ if ((!(A) || src != A.loc))
+ return
if(destination_z && destination_x && destination_y && !(A.pulledby || !A.can_be_z_moved))
var/tx = destination_x
@@ -160,18 +157,27 @@
ty--
DT = locate(tx, ty, destination_z)
- var/atom/movable/AM = A.pulling
+ var/atom/movable/pulling = A.pulling
+ var/atom/movable/puller = A
A.forceMove(DT)
- if(AM)
- var/turf/T = get_step(A.loc,turn(A.dir, 180))
- AM.can_be_z_moved = FALSE
- AM.forceMove(T)
- A.start_pulling(AM)
- AM.can_be_z_moved = TRUE
+
+ while (pulling != null)
+ var/next_pulling = pulling.pulling
+
+ var/turf/T = get_step(puller.loc, turn(puller.dir, 180))
+ pulling.can_be_z_moved = FALSE
+ pulling.forceMove(T)
+ puller.start_pulling(pulling)
+ pulling.can_be_z_moved = TRUE
+
+ puller = pulling
+ pulling = next_pulling
//now we're on the new z_level, proceed the space drifting
stoplag()//Let a diagonal move finish, if necessary
A.newtonian_move(A.inertia_dir)
+ A.inertia_moving = TRUE
+
/turf/open/space/Exited(atom/movable/AM, atom/OldLoc)
. = ..()
diff --git a/code/modules/antagonists/changeling/powers/mutations.dm b/code/modules/antagonists/changeling/powers/mutations.dm
index 24288be078..05c265bc15 100644
--- a/code/modules/antagonists/changeling/powers/mutations.dm
+++ b/code/modules/antagonists/changeling/powers/mutations.dm
@@ -6,6 +6,10 @@
Shield
Armor
Tentacles
+ Hatterhat Additions:
+ claws! (glove slot)
+ jagged (thieves') claws
+ bone gauntlets for punching dudes
*/
@@ -89,7 +93,7 @@
return 1
var/mob/living/carbon/human/H = user
if(istype(H.wear_suit, suit_type) || istype(H.head, helmet_type))
- H.visible_message("[H] casts off [H.p_their()] [suit_name_simple]!", "We cast off our [suit_name_simple].", "You hear the organic matter ripping and tearing!")
+ H.visible_message("[H] casts off [H.p_their()] [suit_name_simple]!", "We cast off our [suit_name_simple].", "You hear organic matter ripping and tearing!")
H.temporarilyRemoveItemFromInventory(H.head, TRUE) //The qdel on dropped() takes care of it
H.temporarilyRemoveItemFromInventory(H.wear_suit, TRUE)
H.update_inv_wear_suit()
@@ -461,7 +465,7 @@
if(--remaining_uses < 1)
if(ishuman(loc))
var/mob/living/carbon/human/H = loc
- H.visible_message("With a sickening crunch, [H] reforms [H.p_their()] shield into an arm!", "We assimilate our shield into our body", "With a sickening crunch, [H] reforms [H.p_their()] shield into an arm!", "We assimilate our shield into our body.", "With a sickening crunch, [H] reforms [H.p_their()] [glove_name_simple] into hands!", "We assimilate our [glove_name_simple].", "You hear organic matter ripping and tearing!")
+ H.temporarilyRemoveItemFromInventory(H.gloves, TRUE) //The qdel on dropped() takes care of it
+ H.update_inv_gloves()
+ playsound(H.loc, 'sound/effects/blobattack.ogg', 30, 1)
+ if(blood_on_castoff)
+ H.add_splatter_floor()
+ playsound(H.loc, 'sound/effects/splat.ogg', 50, 1) //So real sounds
+
+ changeling.chem_recharge_slowdown -= recharge_slowdown
+ return 1
+
+/obj/effect/proc_holder/changeling/gloves/on_refund(mob/user)
+ if(!ishuman(user))
+ return
+ action.Remove(user)
+ var/mob/living/carbon/human/H = user
+ check_gloves(H)
+
+/obj/effect/proc_holder/changeling/gloves/sting_action(mob/living/carbon/human/user)
+ if(!user.canUnEquip(user.gloves))
+ to_chat(user, "\the [user.gloves] is stuck to your body, you cannot grow [glove_name_simple] over it!")
+ return
+
+ user.dropItemToGround(user.gloves)
+
+ user.equip_to_slot_if_possible(new glove_type(user), SLOT_GLOVES, 1, 1, 1)
+ playsound(user, 'sound/effects/blobattack.ogg', 30, 1)
+ var/datum/antagonist/changeling/changeling = user.mind.has_antag_datum(/datum/antagonist/changeling)
+ changeling.chem_recharge_slowdown += recharge_slowdown
+ return TRUE
+
+/obj/item/clothing/gloves/claws
+ name = "claws of doing nothing"
+ desc = "These shouldn't be here."
+ icon_state = "bracers"
+ item_state = "bracers"
+ transfer_prints = TRUE
+ body_parts_covered = HANDS
+ cold_protection = HANDS
+ min_cold_protection_temperature = GLOVES_MIN_TEMP_PROTECT
+ max_heat_protection_temperature = GLOVES_MAX_TEMP_PROTECT
+ armor = list("melee" = 20, "bullet" = 20, "laser" = 20, "energy" = 20, "bomb" = 35, "bio" = 35, "rad" = 35, "fire" = 0, "acid" = 0)
+
+/obj/item/clothing/gloves/claws/Initialize()
+ . = ..()
+ ADD_TRAIT(src, TRAIT_NODROP, CHANGELING_TRAIT)
+
+/obj/item/clothing/gloves/claws/vulture // prying shit off dead/soon to be dead dudes!
+ name = "jagged claws"
+ desc = "Good for prying things off of people and looking incredibly creepy."
+ strip_mod = 2
+
+/obj/effect/proc_holder/changeling/gloves/gauntlets
+ name = "Bone Gauntlets"
+ desc = "We turn our hands into solid bone and chitin, sacrificing dexterity for raw strength."
+ helptext = "These grotesque, bone-and-chitin gauntlets are remarkably good at beating victims senseless, and cannot be used in lesser form. This ability is loud, and might cause our blood to react violently to heat."
+ chemical_cost = 10 // same cost as armblade because its a sidegrade (sacrifice utility for punching people violently)
+ dna_cost = 2
+ loudness = 2
+ req_human = 1
+ action_icon = 'icons/mob/actions/actions_changeling.dmi'
+ action_icon_state = "ling_gauntlets"
+ action_background_icon_state = "bg_ling"
+
+ glove_type = /obj/item/clothing/gloves/fingerless/pugilist/cling // just punch his head off dude
+ glove_name_simple = "bone gauntlets"
+
+/obj/item/clothing/gloves/fingerless/pugilist/cling // switches between lesser GotNS and Big Punchy Rib Breaky Hands
+ name = "hewn bone gauntlets"
+ icon_state = "ling_gauntlets"
+ item_state = "ling_gauntlets"
+ desc = "Rough bone and chitin, pulsing with an abomination barely called \"life\". Good for punching people, not so much for firearms."
+ transfer_prints = TRUE
+ body_parts_covered = ARMS|HANDS
+ cold_protection = ARMS|HANDS
+ min_cold_protection_temperature = GLOVES_MIN_TEMP_PROTECT
+ max_heat_protection_temperature = GLOVES_MAX_TEMP_PROTECT
+ armor = list("melee" = 20, "bullet" = 20, "laser" = 20, "energy" = 20, "bomb" = 35, "bio" = 35, "rad" = 35, "fire" = 0, "acid" = 0)
+ enhancement = 9 // first, do harm. all of it. all of the harm. just fuck em up.
+ wound_enhancement = 9
+ var/fast_enhancement = 9
+ var/fast_wound_enhancement = 9
+ var/slow_enhancement = 20
+ var/slow_wound_enhancement = 20
+ silent = TRUE
+ inherited_trait = TRAIT_CHUNKYFINGERS // dummy thicc bone hands
+ secondary_trait = TRAIT_MAULER // its only violence from here, bucko
+ var/fasthands = TRUE
+
+/obj/item/clothing/gloves/fingerless/pugilist/cling/examine(mob/user)
+ . = ..()
+ . += "[src] are formed to allow for [fasthands ? "fast, precise strikes" : "crippling, damaging blows"]."
+ . += "Alt-click them to change between rapid strikes and strong blows."
+
+/obj/item/clothing/gloves/fingerless/pugilist/cling/AltClick(mob/user)
+ . = ..()
+ use_buffs(user, FALSE) // reset
+ fasthands = !fasthands
+ if(fasthands)
+ enhancement = fast_enhancement
+ wound_enhancement = fast_wound_enhancement
+ else
+ enhancement = slow_enhancement // fuck em up kiddo
+ wound_enhancement = slow_wound_enhancement // really. fuck em up.
+ to_chat(user, "[src] are now formed to allow for [fasthands ? "fast, precise strikes" : "crippling, damaging blows"].")
+ addtimer(CALLBACK(src, .proc/use_buffs, user, TRUE), 0.1) // go fuckin get em
+
+/obj/item/clothing/gloves/fingerless/pugilist/cling/Initialize()
+ . = ..()
+ ADD_TRAIT(src, TRAIT_NODROP, CHANGELING_TRAIT)
+
+/obj/item/clothing/gloves/fingerless/pugilist/cling/equipped(mob/user, slot)
+ . = ..()
+ if(current_equipped_slot == SLOT_GLOVES)
+ to_chat(user, "With [src] formed around our arms, we are ready to fight.")
+
+/obj/item/clothing/gloves/fingerless/pugilist/cling/dropped(mob/user)
+ . = ..()
+ if(wornonce)
+ to_chat(user, "With [src] assimilated, we feel less ready to punch things.")
+
+/obj/item/clothing/gloves/fingerless/pugilist/cling/Touch(atom/target, proximity = TRUE)
+ if(!isliving(target))
+ return
+ var/mob/living/M = loc
+ if(fasthands)
+ M.SetNextAction(CLICK_CD_RANGE) // fast punches
+ else
+ M.SetNextAction(CLICK_CD_GRABBING) // strong punches
+ return NO_AUTO_CLICKDELAY_HANDLING | ATTACK_IGNORE_ACTION
diff --git a/code/modules/clothing/clothing.dm b/code/modules/clothing/clothing.dm
index 2fbe738acb..ea01b0c0ed 100644
--- a/code/modules/clothing/clothing.dm
+++ b/code/modules/clothing/clothing.dm
@@ -45,13 +45,12 @@
var/dynamic_fhair_suffix = ""//mask > head for facial hair
//basically a restriction list.
- var/list/species_restricted = null
+ var/list/species_restricted
//Basically syntax is species_restricted = list("Species Name","Species Name")
//Add a "exclude" string to do the opposite, making it only only species listed that can't wear it.
//You append this to clothing objects
-
// How much clothing damage has been dealt to each of the limbs of the clothing, assuming it covers more than one limb
var/list/damage_by_parts
// How much integrity is in a specific limb before that limb is disabled (for use in [/obj/item/clothing/proc/take_damage_zone], and only if we cover multiple zones.) Set to 0 to disable shredding.
@@ -477,7 +476,6 @@ BLIND // can't see anything
return TRUE
-
/// If we're a clothing with at least 1 shredded/disabled zone, give the wearer a periodic heads up letting them know their clothes are damaged
/obj/item/clothing/proc/bristle(mob/living/L)
if(!istype(L))
diff --git a/code/modules/clothing/gloves/miscellaneous.dm b/code/modules/clothing/gloves/miscellaneous.dm
index b5d92fb780..f9f587ae5b 100644
--- a/code/modules/clothing/gloves/miscellaneous.dm
+++ b/code/modules/clothing/gloves/miscellaneous.dm
@@ -20,8 +20,14 @@
body_parts_covered = ARMS
cold_protection = ARMS
strip_delay = 300 //you can't just yank them off
+ /// did you ever get around to wearing these or no
+ var/wornonce = FALSE
///Extra damage through the punch.
var/enhancement = 0 //it's a +0 to your punches because it isn't magical
+ ///extra wound bonus through the punch (MAYBE DON'T BE GENEROUS WITH THIS)
+ var/wound_enhancement = 0
+ /// do we give the flavortext for wearing them
+ var/silent = FALSE
///Main trait added by the gloves to the user on wear.
var/inherited_trait = TRAIT_NOGUNS //what are you, dishonoroable?
///Secondary trait added by the gloves to the user on wear.
@@ -29,7 +35,18 @@
/obj/item/clothing/gloves/fingerless/pugilist/equipped(mob/user, slot)
. = ..()
- if(slot == SLOT_GLOVES)
+ if(current_equipped_slot == SLOT_GLOVES)
+ use_buffs(user, TRUE)
+ wornonce = TRUE
+
+/obj/item/clothing/gloves/fingerless/pugilist/dropped(mob/user)
+ . = ..()
+ if(wornonce)
+ use_buffs(user, FALSE)
+ wornonce = FALSE
+
+/obj/item/clothing/gloves/fingerless/pugilist/proc/use_buffs(mob/user, buff)
+ if(buff) // tarukaja
if(ishuman(user))
var/mob/living/carbon/human/H = user
ADD_TRAIT(H, TRAIT_PUGILIST, GLOVE_TRAIT)
@@ -37,17 +54,20 @@
ADD_TRAIT(H, secondary_trait, GLOVE_TRAIT)
H.dna.species.punchdamagehigh += enhancement
H.dna.species.punchdamagelow += enhancement
-
-/obj/item/clothing/gloves/fingerless/pugilist/dropped(mob/user)
-
- REMOVE_TRAIT(user, TRAIT_PUGILIST, GLOVE_TRAIT)
- REMOVE_TRAIT(user, inherited_trait, GLOVE_TRAIT)
- REMOVE_TRAIT(user, secondary_trait, GLOVE_TRAIT)
- if(ishuman(user))
- var/mob/living/carbon/human/H = user
- H.dna.species.punchdamagehigh -= enhancement
- H.dna.species.punchdamagelow -= enhancement
- return ..()
+ H.dna.species.punchwoundbonus += wound_enhancement
+ if(!silent)
+ to_chat(H, "With [src] on your arms, you feel ready to punch things.")
+ else // dekaja
+ REMOVE_TRAIT(user, TRAIT_PUGILIST, GLOVE_TRAIT)
+ REMOVE_TRAIT(user, inherited_trait, GLOVE_TRAIT)
+ REMOVE_TRAIT(user, secondary_trait, GLOVE_TRAIT)
+ if(ishuman(user))
+ var/mob/living/carbon/human/H = user
+ H.dna.species.punchdamagehigh -= enhancement
+ H.dna.species.punchdamagelow -= enhancement
+ H.dna.species.punchwoundbonus -= wound_enhancement
+ if(!silent)
+ to_chat(user, "With [src] off of your arms, you feel less ready to punch things.")
/obj/item/clothing/gloves/fingerless/pugilist/chaplain
name = "armwraps of unyielding resolve"
@@ -93,6 +113,7 @@
icon_state = "rapid"
item_state = "rapid"
enhancement = 10 //omae wa mou shindeiru
+ wound_enhancement = 10
var/warcry = "AT"
secondary_trait = TRAIT_NOSOFTCRIT //basically extra health
diff --git a/code/modules/clothing/gloves/tacklers.dm b/code/modules/clothing/gloves/tacklers.dm
index f4b4140a1a..eda36ea12f 100644
--- a/code/modules/clothing/gloves/tacklers.dm
+++ b/code/modules/clothing/gloves/tacklers.dm
@@ -65,12 +65,14 @@
heat_protection = HANDS
max_heat_protection_temperature = GLOVES_MAX_TEMP_PROTECT
resistance_flags = NONE
+ strip_mod = 1.2 // because apparently black gloves had this
/obj/item/clothing/gloves/tackler/combat/insulated
name = "guerrilla gloves"
desc = "Superior quality combative gloves, good for performing tackle takedowns as well as absorbing electrical shocks."
siemens_coefficient = 0
permeability_coefficient = 0.05
+ strip_mod = 1.5 // and combat gloves had this??
/obj/item/clothing/gloves/tackler/combat/insulated/infiltrator
name = "insidious guerrilla gloves"
diff --git a/code/modules/clothing/masks/miscellaneous.dm b/code/modules/clothing/masks/miscellaneous.dm
index 11df38cfdd..5dd35788f1 100644
--- a/code/modules/clothing/masks/miscellaneous.dm
+++ b/code/modules/clothing/masks/miscellaneous.dm
@@ -355,7 +355,7 @@
/obj/item/clothing/mask/paper/Initialize(mapload)
- .=..()
+ . = ..()
papermask_designs = list(
"Blank" = image(icon = src.icon, icon_state = "plainmask"),
"Neutral" = image(icon = src.icon, icon_state = "neutralmask"),
@@ -384,7 +384,7 @@
"Vertical" = "verticalmask", "Horizontal" = "horizontalmask", "X" ="xmask",
"Bugeyes" = "bugmask", "Double" = "doublemask", "Mark" = "markmask")
- var/choice = show_radial_menu(user,src, papermask_designs, custom_check = FALSE, radius = 36, require_near = TRUE)
+ var/choice = show_radial_menu(user, src, papermask_designs, custom_check = FALSE, radius = 36, require_near = TRUE)
if(src && choice && !user.incapacitated() && in_range(user,src))
icon_state = options[choice]
diff --git a/code/modules/events/wormholes.dm b/code/modules/events/wormholes.dm
index 0920c9ccb6..e723ddd79b 100644
--- a/code/modules/events/wormholes.dm
+++ b/code/modules/events/wormholes.dm
@@ -29,7 +29,7 @@
for(var/i = 1, i <= number_of_wormholes, i++)
var/turf/T = pick(pick_turfs)
- wormholes += new /obj/effect/portal/wormhole(T, null, 0, null, FALSE)
+ wormholes += new /obj/effect/portal/wormhole(T, 0, null, FALSE)
/datum/round_event/wormholes/announce(fake)
priority_announce("Space-time anomalies detected on the station. There is no additional data.", "Anomaly Alert", "spanomalies")
diff --git a/code/modules/food_and_drinks/food/snacks_soup.dm b/code/modules/food_and_drinks/food/snacks_soup.dm
index eea7cdb87d..38a68ad364 100644
--- a/code/modules/food_and_drinks/food/snacks_soup.dm
+++ b/code/modules/food_and_drinks/food/snacks_soup.dm
@@ -291,3 +291,12 @@
tastes = list("creamy peas"= 2, "parsnip" = 1)
filling_color = "#9dc530"
foodtype = VEGETABLES
+
+/obj/item/reagent_containers/food/snacks/soup/facehuggerpot
+ name = "pot of face hugger jambalaya"
+ desc = "An entire pot of an extremely spicy dish made using extremely exotic ingredients. Highly recommend by an interdimensional businessman."
+ icon_state = "facehuggerpot"
+ bonus_reagents = list(/datum/reagent/consumable/nutriment = 1, /datum/reagent/consumable/nutriment/vitamin = 2)
+ list_reagents = list(/datum/reagent/consumable/nutriment = 10, /datum/reagent/consumable/nutriment/vitamin = 4)
+ tastes = list("face huggers" = 1)
+ foodtype = MEAT
diff --git a/code/modules/food_and_drinks/recipes/tablecraft/recipes_soup.dm b/code/modules/food_and_drinks/recipes/tablecraft/recipes_soup.dm
index 6372137edc..c52218c913 100644
--- a/code/modules/food_and_drinks/recipes/tablecraft/recipes_soup.dm
+++ b/code/modules/food_and_drinks/recipes/tablecraft/recipes_soup.dm
@@ -291,3 +291,18 @@
)
result = /obj/item/reagent_containers/food/snacks/soup/peasoup
subcategory = CAT_SOUP
+
+/datum/crafting_recipe/food/facehuggerjambalayapot
+ name = "Facehugger jambalaya"
+ reqs = list(
+ /datum/reagent/bluespace = 10,
+ /datum/reagent/consumable/ethanol/booger =10,
+ /obj/item/organ/heart= 3,
+ /obj/item/clothing/mask/facehugger = 1,
+ /obj/item/reagent_containers/food/snacks/egg = 1,
+ /datum/reagent/consumable/blackpepper = 5,
+ /datum/reagent/toxin/acid = 5
+
+ )
+ result = /obj/item/reagent_containers/food/snacks/soup/facehuggerpot
+ subcategory = CAT_SOUP
diff --git a/code/modules/holiday/holidays.dm b/code/modules/holiday/holidays.dm
index 83bb67b879..f6e4684e21 100644
--- a/code/modules/holiday/holidays.dm
+++ b/code/modules/holiday/holidays.dm
@@ -351,7 +351,7 @@
/datum/holiday/halloween
name = HALLOWEEN
- begin_day = 28
+ begin_day = 1
begin_month = OCTOBER
end_day = 2
end_month = NOVEMBER
diff --git a/code/modules/hydroponics/hydroponics.dm b/code/modules/hydroponics/hydroponics.dm
index aa8a7b60c2..a1a177c028 100644
--- a/code/modules/hydroponics/hydroponics.dm
+++ b/code/modules/hydroponics/hydroponics.dm
@@ -42,6 +42,14 @@
icon = 'icons/obj/hydroponics/equipment.dmi'
icon_state = "hydrotray3"
+/obj/machinery/hydroponics/constructable/ComponentInitialize()
+ . = ..()
+ AddComponent(/datum/component/simple_rotation, ROTATION_ALTCLICK | ROTATION_CLOCKWISE | ROTATION_COUNTERCLOCKWISE | ROTATION_VERBS, null, CALLBACK(src, .proc/can_be_rotated))
+ AddComponent(/datum/component/plumbing/simple_demand)
+
+/obj/machinery/hydroponics/constructable/proc/can_be_rotated(mob/user, rotation_type)
+ return !anchored
+
/obj/machinery/hydroponics/constructable/RefreshParts()
var/tmp_capacity = 0
for (var/obj/item/stock_parts/matter_bin/M in component_parts)
diff --git a/code/modules/integrated_electronics/subtypes/output.dm b/code/modules/integrated_electronics/subtypes/output.dm
index efd98c4d50..833b2963ad 100644
--- a/code/modules/integrated_electronics/subtypes/output.dm
+++ b/code/modules/integrated_electronics/subtypes/output.dm
@@ -44,18 +44,18 @@
/obj/item/integrated_circuit/output/screen/large/do_work()
..()
- if(isliving(assembly.loc))//this whole block just returns if the assembly is neither in a mobs hands or on the ground
- var/mob/living/H = assembly.loc
- if(H.get_active_held_item() != assembly && H.get_inactive_held_item() != assembly)
- return
- else
- if(!isturf(assembly.loc))
- return
-
- var/atom/host = assembly || src
var/list/mobs = list()
- for(var/mob/M in range(0, get_turf(src)))
- mobs += M
+ if(isliving(assembly.loc))
+ mobs += assembly.loc
+ var/mob/living/L = assembly.loc
+ if(L.is_holding(src))
+ for(var/mob/M in range(1, get_turf(src)))
+ mobs += M
+ else
+ for(var/mob/M in range(2, get_turf(src)))
+ mobs += M
+
+ var/atom/host = assembly || src
to_chat(mobs, "[icon2html(host.icon, world, host.icon_state)] flashes a message: [stuff_to_display]")
host.investigate_log("displayed \"[html_encode(stuff_to_display)]\" as [type].", INVESTIGATE_CIRCUIT)
diff --git a/code/modules/jobs/job_types/_job.dm b/code/modules/jobs/job_types/_job.dm
index 0feceaf1a8..b0ac5248c1 100644
--- a/code/modules/jobs/job_types/_job.dm
+++ b/code/modules/jobs/job_types/_job.dm
@@ -73,6 +73,31 @@
/// Starting skill modifiers.
var/list/starting_modifiers
+/**
+ * Checks if we should be created on a certain map
+ */
+/datum/job/proc/map_check(datum/map_config/C)
+ return (length(C.job_whitelist)? (type in C.job_whitelist) : !(type in C.job_blacklist))
+
+/**
+ * Processes map specific overrides
+ */
+/datum/job/proc/process_map_overrides(datum/map_config/C)
+ if(type in C.job_override_spawn_positions)
+ spawn_positions = C.job_override_spawn_positions[type]
+ if(type in C.job_override_total_positions)
+ total_positions = C.job_override_total_positions[type]
+ if(type in C.job_access_override)
+ access = C.job_access_override[type]
+ minimal_access = access
+ else
+ if(type in C.job_access_add)
+ access += C.job_access_add[type]
+ minimal_access += C.job_access_add[type]
+ if(type in C.job_access_remove)
+ access -= C.job_access_add[type]
+ minimal_access -= C.job_access_remove[type]
+
//Only override this proc
//H is usually a human unless an /equip override transformed it
/datum/job/proc/after_spawn(mob/living/H, mob/M, latejoin = FALSE)
@@ -175,9 +200,6 @@
/datum/job/proc/config_check()
return TRUE
-/datum/job/proc/map_check()
- return TRUE
-
/datum/job/proc/radio_help_message(mob/M)
to_chat(M, "Prefix your message with :h to speak on your department's radio. To see other prefixes, look closely at your headset.")
diff --git a/code/modules/jobs/map_changes/map_changes.dm b/code/modules/jobs/map_changes/map_changes.dm
deleted file mode 100644
index 575075037b..0000000000
--- a/code/modules/jobs/map_changes/map_changes.dm
+++ /dev/null
@@ -1,4 +0,0 @@
-//this needs to come after the job_types subfolder to keep the correct ordering
-
-#include "..\..\..\..\_maps\map_files\PubbyStation\job_changes.dm"
-#undef JOB_MODIFICATION_MAP_NAME
\ No newline at end of file
diff --git a/code/modules/keybindings/bindings_client.dm b/code/modules/keybindings/bindings_client.dm
index 3a47cd2315..3d12f89cfe 100644
--- a/code/modules/keybindings/bindings_client.dm
+++ b/code/modules/keybindings/bindings_client.dm
@@ -1,6 +1,7 @@
// Clients aren't datums so we have to define these procs indpendently.
// These verbs are called for all key press and release events
/client/verb/keyDown(_key as text)
+ SHOULD_NOT_SLEEP(TRUE)
set instant = TRUE
set hidden = TRUE
@@ -83,6 +84,7 @@
keyUp("[key]")
/client/verb/keyUp(_key as text)
+ SHOULD_NOT_SLEEP(TRUE)
set instant = TRUE
set hidden = TRUE
diff --git a/code/modules/keybindings/setup.dm b/code/modules/keybindings/setup.dm
index a53820b5d2..47dbfd855f 100644
--- a/code/modules/keybindings/setup.dm
+++ b/code/modules/keybindings/setup.dm
@@ -1,12 +1,11 @@
/datum/proc/key_down(key, client/user) // Called when a key is pressed down initially
- return
+ SHOULD_NOT_SLEEP(TRUE)
/datum/proc/key_up(key, client/user) // Called when a key is released
- return
+ SHOULD_NOT_SLEEP(TRUE)
/datum/proc/keyLoop(client/user) // Called once every frame
- set waitfor = FALSE
- return
+ SHOULD_NOT_SLEEP(TRUE)
// removes all the existing macros
/client/proc/erase_all_macros()
diff --git a/code/modules/mapping/map_config.dm b/code/modules/mapping/map_config.dm
index c03ef65f43..d18ca1e1e3 100644
--- a/code/modules/mapping/map_config.dm
+++ b/code/modules/mapping/map_config.dm
@@ -43,6 +43,21 @@
/// Orientation to load in by default.
var/orientation = SOUTH //byond defaults to placing everyting SOUTH.
+ /// Jobs whitelist - if this is not empty, ONLY these jobs are allowed. Overrides blacklist.
+ var/list/job_whitelist
+ /// Jobs blacklist - if this is not empty, jobs in this aren't allowed.
+ var/list/job_blacklist
+ /// Job spawn position mod - type = number
+ var/list/job_override_spawn_positions
+ /// Job total position mod - type = number
+ var/list/job_override_total_positions
+ /// Add these accesses to jobs - type = list()
+ var/list/job_access_add
+ /// Remove these accesses from jobs - type = list()
+ var/list/job_access_remove
+ /// Override job accesses - type = list() - overrides everything else
+ var/list/job_access_override
+
/proc/load_map_config(filename = "data/next_map.json", default_to_box, delete_after, error_if_missing = TRUE)
var/datum/map_config/config = new
if (default_to_box)
@@ -161,6 +176,69 @@
allow_custom_shuttles = json["allow_custom_shuttles"] != FALSE
+ if("job_whitelist" in json)
+ job_whitelist = list()
+ for(var/path in json["job_whitelist"])
+ var/type = text2path(path)
+ if(!path)
+ log_config("map datum [config_filename] failed to validate path [path] in job overrides.")
+ continue
+ job_whitelist += type
+
+ if("job_blacklist" in json)
+ job_blacklist = list()
+ for(var/path in json["job_blacklist"])
+ var/type = text2path(path)
+ if(!path)
+ log_config("map datum [config_filename] failed to validate path [path] in job overrides.")
+ continue
+ job_blacklist += type
+
+ if("job_override_spawn_positions" in json)
+ job_override_spawn_positions = list()
+ for(var/path in json["job_override_spawn_positions"])
+ var/type = text2path(path)
+ if(!path)
+ log_config("map datum [config_filename] failed to validate path [path] in job overrides.")
+ continue
+ job_override_spawn_positions += type
+
+ if("job_override_total_positions" in json)
+ job_override_total_positions = list()
+ for(var/path in json["job_override_total_positions"])
+ var/type = text2path(path)
+ if(!path)
+ log_config("map datum [config_filename] failed to validate path [path] in job overrides.")
+ continue
+ job_override_total_positions += type
+
+ if("job_access_add" in json)
+ job_access_add = list()
+ for(var/path in json["job_acces_add"])
+ var/type = text2path(path)
+ if(!path)
+ log_config("map datum [config_filename] failed to validate path [path] in job overrides.")
+ continue
+ job_access_add[type] = json["job_access_add"]
+
+ if("job_access_remove" in json)
+ job_access_remove = list()
+ for(var/path in json["job_acces_add"])
+ var/type = text2path(path)
+ if(!path)
+ log_config("map datum [config_filename] failed to validate path [path] in job overrides.")
+ continue
+ job_access_remove[type] = json["job_access_remove"]
+
+ if("job_access_override" in json)
+ job_access_override = list()
+ for(var/path in json["job_acces_add"])
+ var/type = text2path(path)
+ if(!path)
+ log_config("map datum [config_filename] failed to validate path [path] in job overrides.")
+ continue
+ job_access_override[type] = json["job_access_override"]
+
defaulted = FALSE
return TRUE
#undef CHECK_EXISTS
@@ -190,6 +268,13 @@
jsonlist["announcertype"] = announcertype
jsonlist["orientation"] = orientation
jsonlist["allow_custom_shuttles"] = allow_custom_shuttles
+ jsonlist["job_whitelist"] = job_whitelist
+ jsonlist["job_blacklist"] = job_blacklist
+ jsonlist["job_override_spawn_positions"] = job_override_spawn_positions
+ jsonlist["job_override_total_positions"] = job_override_total_positions
+ jsonlist["job_access_add"] = job_access_add
+ jsonlist["job_access_remove"] = job_access_remove
+ jsonlist["job_access_override"] = job_access_override
if(fexists("data/next_map.json"))
fdel("data/next_map.json")
var/F = file("data/next_map.json")
diff --git a/code/modules/mapping/ruins.dm b/code/modules/mapping/ruins.dm
index 193106c294..b681c348e0 100644
--- a/code/modules/mapping/ruins.dm
+++ b/code/modules/mapping/ruins.dm
@@ -135,7 +135,12 @@
//That's done remove from priority even if it failed
if(forced)
//TODO : handle forced ruins with multiple variants
+ // this might work?
forced_ruins -= current_pick
+ if(!current_pick.allow_duplicates)
+ for(var/datum/map_template/ruin/R in forced_ruins)
+ if(R.id == current_pick.id)
+ forced_ruins -= R
forced = FALSE
if(failed_to_place)
diff --git a/code/modules/mob/living/carbon/carbon.dm b/code/modules/mob/living/carbon/carbon.dm
index 11b133f5e7..26486f255a 100644
--- a/code/modules/mob/living/carbon/carbon.dm
+++ b/code/modules/mob/living/carbon/carbon.dm
@@ -191,8 +191,8 @@
if(HAS_TRAIT(src, TRAIT_PACIFISM))
to_chat(src, "You gently let go of [throwable_mob].")
return
-
- adjustStaminaLossBuffered(STAM_COST_THROW_MOB * ((throwable_mob.mob_size+1)**2))// throwing an entire person shall be very tiring
+ if(!UseStaminaBuffer(STAM_COST_THROW_MOB * ((throwable_mob.mob_size+1)**2), TRUE))
+ return
var/turf/start_T = get_turf(loc) //Get the start and target tile for the descriptors
var/turf/end_T = get_turf(target)
if(start_T && end_T)
@@ -206,7 +206,8 @@
to_chat(src, "You set [I] down gently on the ground.")
return
- adjustStaminaLossBuffered(I.getweight(src, STAM_COST_THROW_MULT, SKILL_THROW_STAM_COST))
+ if(!UseStaminaBuffer(I.getweight(src, STAM_COST_THROW_MULT, SKILL_THROW_STAM_COST), warn = TRUE))
+ return
if(thrown_thing)
var/power_throw = 0
@@ -594,15 +595,23 @@
remove_movespeed_modifier(/datum/movespeed_modifier/carbon_softcrit)
/mob/living/carbon/update_stamina()
- var/stam = getStaminaLoss()
- if(stam > DAMAGE_PRECISION)
- var/total_health = (maxHealth - stam)
- if(total_health <= crit_threshold && !stat)
- if(CHECK_MOBILITY(src, MOBILITY_STAND))
- to_chat(src, "You're too exhausted to keep going...")
- KnockToFloor(TRUE)
- update_health_hud()
-
+ var/total_health = getStaminaLoss()
+ if(total_health)
+ if(!(combat_flags & COMBAT_FLAG_HARD_STAMCRIT) && total_health >= STAMINA_CRIT && !stat)
+ to_chat(src, "You're too exhausted to keep going...")
+ set_resting(TRUE, FALSE, FALSE)
+ SEND_SIGNAL(src, COMSIG_DISABLE_COMBAT_MODE)
+ ENABLE_BITFIELD(combat_flags, COMBAT_FLAG_HARD_STAMCRIT)
+ filters += CIT_FILTER_STAMINACRIT
+ update_mobility()
+ if((combat_flags & COMBAT_FLAG_HARD_STAMCRIT) && total_health <= STAMINA_CRIT)
+ to_chat(src, "You don't feel nearly as exhausted anymore.")
+ DISABLE_BITFIELD(combat_flags, COMBAT_FLAG_HARD_STAMCRIT)
+ filters -= CIT_FILTER_STAMINACRIT
+ update_mobility()
+ UpdateStaminaBuffer()
+ update_health_hud()
+
/mob/living/carbon/update_sight()
if(!client)
return
diff --git a/code/modules/mob/living/carbon/human/human.dm b/code/modules/mob/living/carbon/human/human.dm
index 904753f9b1..1b7ab81d59 100644
--- a/code/modules/mob/living/carbon/human/human.dm
+++ b/code/modules/mob/living/carbon/human/human.dm
@@ -254,10 +254,10 @@
if(href_list["pockets"])
var/strip_mod = 1
var/strip_silence = FALSE
- var/obj/item/clothing/gloves/g = gloves
- if (istype(g))
- strip_mod = g.strip_mod
- strip_silence = g.strip_silence
+ var/obj/item/clothing/gloves/G = gloves
+ if(istype(G))
+ strip_mod = G.strip_mod
+ strip_silence = G.strip_silence
var/pocket_side = href_list["pockets"]
var/pocket_id = (pocket_side == "right" ? SLOT_R_STORE : SLOT_L_STORE)
var/obj/item/pocket_item = (pocket_id == SLOT_R_STORE ? r_store : l_store)
@@ -805,7 +805,7 @@
hud_used.healthdoll.icon_state = "healthdoll_DEAD"
hud_used.staminas?.update_icon_state()
- hud_used.staminabuffer?.update_icon_state()
+ hud_used.staminabuffer?.mark_dirty()
/mob/living/carbon/human/fully_heal(admin_revive = FALSE)
if(admin_revive)
@@ -1044,10 +1044,9 @@
remove_movespeed_modifier(/datum/movespeed_modifier/damage_slowdown)
remove_movespeed_modifier(/datum/movespeed_modifier/damage_slowdown_flying)
return
- var/stambufferinfluence = (bufferedstam*(100/stambuffer))*0.2 //CIT CHANGE - makes stamina buffer influence movedelay
if(!HAS_TRAIT(src, TRAIT_IGNOREDAMAGESLOWDOWN)) //if we want to ignore slowdown from damage, but not from equipment
var/scaling = maxHealth / 100
- var/health_deficiency = (((maxHealth / scaling) + stambufferinfluence) - (health / scaling) + (getStaminaLoss()*0.75))//CIT CHANGE - reduces the impact of staminaloss and makes stamina buffer influence it
+ var/health_deficiency = ((maxHealth / scaling) - (health / scaling) + (getStaminaLoss()*0.75))//CIT CHANGE - reduces the impact of staminaloss and makes stamina buffer influence it
if(health_deficiency >= 40)
add_or_update_variable_movespeed_modifier(/datum/movespeed_modifier/damage_slowdown, TRUE, (health_deficiency-39) / 75)
add_or_update_variable_movespeed_modifier(/datum/movespeed_modifier/damage_slowdown_flying, TRUE, (health_deficiency-39) / 25)
diff --git a/code/modules/mob/living/carbon/human/human_defines.dm b/code/modules/mob/living/carbon/human/human_defines.dm
index a6a3f408dc..89a4570e8d 100644
--- a/code/modules/mob/living/carbon/human/human_defines.dm
+++ b/code/modules/mob/living/carbon/human/human_defines.dm
@@ -7,7 +7,7 @@
buckle_lying = FALSE
mob_biotypes = MOB_ORGANIC|MOB_HUMANOID
/// Enable stamina combat
- combat_flags = COMBAT_FLAGS_DEFAULT | COMBAT_FLAG_UNARMED_PARRY
+ combat_flags = COMBAT_FLAGS_STAMINA_COMBAT | COMBAT_FLAG_UNARMED_PARRY
status_flags = CANSTUN|CANKNOCKDOWN|CANUNCONSCIOUS|CANPUSH|CANSTAGGER
has_field_of_vision = FALSE //Handled by species.
diff --git a/code/modules/mob/living/carbon/human/human_movement.dm b/code/modules/mob/living/carbon/human/human_movement.dm
index 5ced11e35a..97c40c96d7 100644
--- a/code/modules/mob/living/carbon/human/human_movement.dm
+++ b/code/modules/mob/living/carbon/human/human_movement.dm
@@ -11,11 +11,6 @@
/mob/living/carbon/human/movement_delay()
. = ..()
- if(CHECK_MOBILITY(src, MOBILITY_STAND) && m_intent == MOVE_INTENT_RUN && (combat_flags & COMBAT_FLAG_SPRINT_ACTIVE))
- var/static/datum/config_entry/number/movedelay/sprint_speed_increase/SSI
- if(!SSI)
- SSI = CONFIG_GET_ENTRY(number/movedelay/sprint_speed_increase)
- . -= SSI.config_entry_value
if (m_intent == MOVE_INTENT_WALK && HAS_TRAIT(src, TRAIT_SPEEDY_STEP))
. -= 1.5
@@ -61,7 +56,11 @@
HM.on_move(NewLoc)
if(. && (combat_flags & COMBAT_FLAG_SPRINT_ACTIVE) && !(movement_type & FLYING) && CHECK_ALL_MOBILITY(src, MOBILITY_MOVE|MOBILITY_STAND) && m_intent == MOVE_INTENT_RUN && has_gravity(loc) && (!pulledby || (pulledby.pulledby == src)))
if(!HAS_TRAIT(src, TRAIT_FREESPRINT))
- doSprintLossTiles(1)
+ var/datum/movespeed_modifier/equipment_speedmod/MM = get_movespeed_modifier_datum(/datum/movespeed_modifier/equipment_speedmod)
+ var/amount = 1
+ if(MM?.multiplicative_slowdown >= 1)
+ amount *= (1 + (6 - (3 / MM.multiplicative_slowdown)))
+ doSprintLossTiles(amount)
if((oldpseudoheight - pseudo_z_axis) >= 8)
to_chat(src, "You trip off of the elevated surface!")
for(var/obj/item/I in held_items)
diff --git a/code/modules/mob/living/carbon/human/species.dm b/code/modules/mob/living/carbon/human/species.dm
index e68984738a..3a3171e24c 100644
--- a/code/modules/mob/living/carbon/human/species.dm
+++ b/code/modules/mob/living/carbon/human/species.dm
@@ -64,7 +64,8 @@ GLOBAL_LIST_EMPTY(roundstart_race_names)
var/stunmod = 1 // multiplier for stun duration
var/punchdamagelow = 1 //lowest possible punch damage. if this is set to 0, punches will always miss
var/punchdamagehigh = 10 //highest possible punch damage
- var/punchstunthreshold = 10//damage at which punches from this race will stun //yes it should be to the attacked race but it's not useful that way even if it's logical
+ var/punchstunthreshold = 10 //damage at which punches from this race will stun //yes it should be to the attacked race but it's not useful that way even if it's logical
+ var/punchwoundbonus = 0 // additional wound bonus. generally zero.
var/siemens_coeff = 1 //base electrocution coefficient
var/damage_overlay_type = "human" //what kind of damage overlays (if any) appear on our species when wounded?
var/fixed_mut_color = "" //to use MUTCOLOR with a fixed color that's independent of dna.feature["mcolor"]
@@ -106,7 +107,7 @@ GLOBAL_LIST_EMPTY(roundstart_race_names)
var/whitelisted = 0 //Is this species restricted to certain players?
var/whitelist = list() //List the ckeys that can use this species, if it's whitelisted.: list("John Doe", "poopface666", "SeeALiggerPullTheTrigger") Spaces & capitalization can be included or ignored entirely for each key as it checks for both.
var/icon_limbs //Overrides the icon used for the limbs of this species. Mainly for downstream, and also because hardcoded icons disgust me. Implemented and maintained as a favor in return for a downstream's implementation of synths.
- var/species_type
+ var/species_category
var/tail_type //type of tail i.e. mam_tail
var/wagging_type //type of wagging i.e. waggingtail_lizard
@@ -1361,9 +1362,10 @@ GLOBAL_LIST_EMPTY(roundstart_race_names)
if(!(attackchain_flags & ATTACK_IS_PARRY_COUNTERATTACK))
if(HAS_TRAIT(user, TRAIT_PUGILIST))//CITADEL CHANGE - makes punching cause staminaloss but funny martial artist types get a discount
- user.adjustStaminaLossBuffered(1.5)
- else
- user.adjustStaminaLossBuffered(3.5)
+ if(!user.UseStaminaBuffer(1.5, warn = TRUE))
+ return
+ else if(!user.UseStaminaBuffer(3.5, warn = TRUE))
+ return
if(attacker_style && attacker_style.harm_act(user,target))
return TRUE
@@ -1384,6 +1386,7 @@ GLOBAL_LIST_EMPTY(roundstart_race_names)
user.do_attack_animation(target, ATTACK_EFFECT_PUNCH)
var/damage = rand(user.dna.species.punchdamagelow, user.dna.species.punchdamagehigh)
+ var/punchwoundbonus = user.dna.species.punchwoundbonus
var/puncherstam = user.getStaminaLoss()
var/puncherbrute = user.getBruteLoss()
var/punchedstam = target.getStaminaLoss()
@@ -1399,6 +1402,8 @@ GLOBAL_LIST_EMPTY(roundstart_race_names)
//END OF CITADEL CHANGES
var/obj/item/bodypart/affecting = target.get_bodypart(ran_zone(user.zone_selected))
+ if(HAS_TRAIT(user, TRAIT_PUGILIST))
+ affecting = target.get_bodypart(check_zone(user.zone_selected)) // if you're going the based unarmed route you won't miss
if(!affecting) //Maybe the bodypart is missing? Or things just went wrong..
affecting = target.get_bodypart(BODY_ZONE_CHEST) //target chest instead, as failsafe. Or hugbox? You decide.
@@ -1410,8 +1415,8 @@ GLOBAL_LIST_EMPTY(roundstart_race_names)
if(user.dna.species.punchdamagelow)
if(atk_verb == ATTACK_EFFECT_KICK) //kicks never miss (provided your species deals more than 0 damage)
miss_chance = 0
- else if(HAS_TRAIT(user, TRAIT_PUGILIST)) //pugilists have a flat 10% miss chance
- miss_chance = 10
+ else if(HAS_TRAIT(user, TRAIT_PUGILIST)) //pugilists, being good at Punching People, also never miss
+ miss_chance = 0
else
miss_chance = min(10 + max(puncherstam * 0.5, puncherbrute * 0.5), 100) //probability of miss has a base of 10, and modified based on half brute total. Capped at max 100 to prevent weirdness in prob()
@@ -1425,12 +1430,13 @@ GLOBAL_LIST_EMPTY(roundstart_race_names)
var/armor_block = target.run_armor_check(affecting, "melee")
+ if(HAS_TRAIT(user, TRAIT_MAULER)) // maulers get 15 armorpierce because if you're going to punch someone you might as well do a good job of it
+ armor_block = target.run_armor_check(affecting, "melee", armour_penetration = 15) // lot of good that sec jumpsuit did you
playsound(target.loc, user.dna.species.attack_sound, 25, 1, -1)
-
- target.visible_message("[user] [atk_verb]s [target]!", \
- "[user] [atk_verb]s you!", null, COMBAT_MESSAGE_RANGE, null, \
- user, "You [atk_verb] [target]!")
+ target.visible_message("[user] [atk_verb]ed [target]!", \
+ "[user] [atk_verb]ed you!", null, COMBAT_MESSAGE_RANGE, null, \
+ user, "You [atk_verb]ed [target]!")
target.lastattacker = user.real_name
target.lastattackerckey = user.ckey
@@ -1440,11 +1446,15 @@ GLOBAL_LIST_EMPTY(roundstart_race_names)
target.dismembering_strike(user, affecting.body_zone)
if(atk_verb == ATTACK_EFFECT_KICK)//kicks deal 1.5x raw damage + 0.5x stamina damage
- target.apply_damage(damage*1.5, attack_type, affecting, armor_block)
+ target.apply_damage(damage*1.5, attack_type, affecting, armor_block, wound_bonus = punchwoundbonus)
target.apply_damage(damage*0.5, STAMINA, affecting, armor_block)
log_combat(user, target, "kicked")
- else//other attacks deal full raw damage + 2x in stamina damage
- target.apply_damage(damage, attack_type, affecting, armor_block)
+ else if(HAS_TRAIT(user, TRAIT_MAULER)) // mauler punches deal 1.3x raw damage + 1x stam damage, and have some armor pierce
+ target.apply_damage(damage*1.3, attack_type, affecting, armor_block, wound_bonus = punchwoundbonus)
+ target.apply_damage(damage, STAMINA, affecting, armor_block)
+ log_combat(user, target, "punched (mauler)")
+ else //other attacks deal full raw damage + 2x in stamina damage
+ target.apply_damage(damage, attack_type, affecting, armor_block, wound_bonus = punchwoundbonus)
target.apply_damage(damage*2, STAMINA, affecting, armor_block)
log_combat(user, target, "punched")
@@ -1494,6 +1504,8 @@ GLOBAL_LIST_EMPTY(roundstart_race_names)
return FALSE
else if(aim_for_mouth && ( target_on_help || target_restrained || target_aiming_for_mouth))
+ if(!user.UseStaminaBuffer(3, warn = TRUE))
+ return
playsound(target.loc, 'sound/weapons/slap.ogg', 50, 1, -1)
target.visible_message(\
@@ -1501,7 +1513,6 @@ GLOBAL_LIST_EMPTY(roundstart_race_names)
"[user] slaps you in the face! ",\
"You hear a slap.", target = user, target_message = "You slap [user == target ? "yourself" : "\the [target]"] in the face! ")
user.do_attack_animation(target, ATTACK_EFFECT_FACE_SLAP)
- user.adjustStaminaLossBuffered(3)
if (!HAS_TRAIT(target, TRAIT_PERMABONER))
stop_wagging_tail(target)
return FALSE
@@ -1509,8 +1520,9 @@ GLOBAL_LIST_EMPTY(roundstart_race_names)
if(target.client?.prefs.cit_toggles & NO_ASS_SLAP)
to_chat(user,"A force stays your hand, preventing you from slapping \the [target]'s ass!")
return FALSE
+ if(!user.UseStaminaBuffer(3, warn = TRUE))
+ return FALSE
user.do_attack_animation(target, ATTACK_EFFECT_ASS_SLAP)
- user.adjustStaminaLossBuffered(3)
target.adjust_arousal(20,maso = TRUE)
if (ishuman(target) && HAS_TRAIT(target, TRAIT_MASO) && target.has_dna() && prob(10))
target.mob_climax(forced_climax=TRUE)
@@ -1528,9 +1540,11 @@ GLOBAL_LIST_EMPTY(roundstart_race_names)
user.do_attack_animation(target, ATTACK_EFFECT_DISARM)
if(HAS_TRAIT(user, TRAIT_PUGILIST))//CITADEL CHANGE - makes disarmspam cause staminaloss, pugilists can do it almost effortlessly
- user.adjustStaminaLossBuffered(1)
+ if(!user.UseStaminaBuffer(1, warn = TRUE))
+ return
else
- user.adjustStaminaLossBuffered(3)
+ if(!user.UseStaminaBuffer(1, warn = TRUE))
+ return
if(attacker_style && attacker_style.disarm_act(user,target))
return TRUE
@@ -1766,9 +1780,10 @@ GLOBAL_LIST_EMPTY(roundstart_race_names)
if(CHECK_MOBILITY(user, MOBILITY_STAND))
to_chat(user, "You can only force yourself up if you're on the ground.")
return
+ if(!user.UseStaminaBuffer(STAMINA_COST_SHOVE_UP, TRUE))
+ return
user.visible_message("[user] forces [p_them()]self up to [p_their()] feet!", "You force yourself up to your feet!")
user.set_resting(FALSE, TRUE)
- user.adjustStaminaLossBuffered(user.stambuffer) //Rewards good stamina management by making it easier to instantly get up from resting
playsound(user, 'sound/weapons/thudswoosh.ogg', 50, 1, -1)
/datum/species/proc/altdisarm(mob/living/carbon/human/user, mob/living/carbon/human/target, datum/martial_art/attacker_style)
@@ -1787,8 +1802,9 @@ GLOBAL_LIST_EMPTY(roundstart_race_names)
else
if(user == target)
return
+ if(!user.UseStaminaBuffer(4, warn = TRUE))
+ return
user.do_attack_animation(target, ATTACK_EFFECT_DISARM)
- user.adjustStaminaLossBuffered(4)
playsound(target, 'sound/weapons/thudswoosh.ogg', 50, TRUE, -1)
if(target.w_uniform)
diff --git a/code/modules/mob/living/carbon/human/species_types/abductors.dm b/code/modules/mob/living/carbon/human/species_types/abductor.dm
similarity index 90%
rename from code/modules/mob/living/carbon/human/species_types/abductors.dm
rename to code/modules/mob/living/carbon/human/species_types/abductor.dm
index 38e10e8662..004ba267e8 100644
--- a/code/modules/mob/living/carbon/human/species_types/abductors.dm
+++ b/code/modules/mob/living/carbon/human/species_types/abductor.dm
@@ -1,12 +1,12 @@
/datum/species/abductor
name = "Abductor"
- id = "abductor"
+ id = SPECIES_ABDUCTOR
say_mod = "gibbers"
sexes = FALSE
species_traits = list(NOBLOOD,NOEYES,NOGENITALS,NOAROUSAL,HAS_FLESH,HAS_BONE)
inherent_traits = list(TRAIT_VIRUSIMMUNE,TRAIT_CHUNKYFINGERS,TRAIT_NOHUNGER,TRAIT_NOBREATH)
mutanttongue = /obj/item/organ/tongue/abductor
- species_type = "alien"
+ species_category = SPECIES_CATEGORY_ALIEN
/datum/species/abductor/on_species_gain(mob/living/carbon/C, datum/species/old_species)
. = ..()
diff --git a/code/modules/mob/living/carbon/human/species_types/android.dm b/code/modules/mob/living/carbon/human/species_types/android.dm
index e7c49f6d9c..187a0fdfec 100644
--- a/code/modules/mob/living/carbon/human/species_types/android.dm
+++ b/code/modules/mob/living/carbon/human/species_types/android.dm
@@ -1,6 +1,6 @@
/datum/species/android
name = "Android"
- id = "android"
+ id = SPECIES_ANDROID
say_mod = "states"
species_traits = list(NOBLOOD,NOGENITALS,NOAROUSAL,ROBOTIC_LIMBS)
inherent_traits = list(TRAIT_RESISTHEAT,TRAIT_NOBREATH,TRAIT_RESISTCOLD,TRAIT_RESISTHIGHPRESSURE,TRAIT_RESISTLOWPRESSURE,TRAIT_RADIMMUNE,TRAIT_NOFIRE,TRAIT_PIERCEIMMUNE,TRAIT_NOHUNGER,TRAIT_LIMBATTACHMENT)
@@ -11,4 +11,4 @@
mutanttongue = /obj/item/organ/tongue/robot
species_language_holder = /datum/language_holder/synthetic
limbs_id = "synth"
- species_type = "robotic"
+ species_category = SPECIES_CATEGORY_ROBOT
\ No newline at end of file
diff --git a/code/modules/mob/living/carbon/human/species_types/angel.dm b/code/modules/mob/living/carbon/human/species_types/angel.dm
index 3cae574a90..9c87f3ad5d 100644
--- a/code/modules/mob/living/carbon/human/species_types/angel.dm
+++ b/code/modules/mob/living/carbon/human/species_types/angel.dm
@@ -1,6 +1,6 @@
/datum/species/angel
name = "Angel"
- id = "angel"
+ id = SPECIES_ANGEL
default_color = "FFFFFF"
species_traits = list(EYECOLOR,HAIR,FACEHAIR,LIPS,HAS_FLESH,HAS_BONE)
mutant_bodyparts = list("tail_human" = "None", "ears" = "None", "wings" = "Angel")
@@ -9,7 +9,7 @@
blacklisted = 1
limbs_id = "human"
skinned_type = /obj/item/stack/sheet/animalhide/human
- species_type = "human" //they're a kind of human
+ species_category = SPECIES_CATEGORY_BASIC //they're a kind of human
var/datum/action/innate/flight/fly
diff --git a/code/modules/mob/living/carbon/human/species_types/furrypeople.dm b/code/modules/mob/living/carbon/human/species_types/anthromorph.dm
similarity index 87%
rename from code/modules/mob/living/carbon/human/species_types/furrypeople.dm
rename to code/modules/mob/living/carbon/human/species_types/anthromorph.dm
index 534536d6e7..95aec26d9f 100644
--- a/code/modules/mob/living/carbon/human/species_types/furrypeople.dm
+++ b/code/modules/mob/living/carbon/human/species_types/anthromorph.dm
@@ -1,6 +1,6 @@
/datum/species/mammal
name = "Anthromorph"
- id = "mammal"
+ id = SPECIES_MAMMAL
default_color = "4B4B4B"
species_traits = list(MUTCOLORS,EYECOLOR,LIPS,HAIR,HORNCOLOR,WINGCOLOR,HAS_FLESH,HAS_BONE)
inherent_biotypes = MOB_ORGANIC|MOB_HUMANOID|MOB_BEAST
@@ -15,6 +15,6 @@
tail_type = "mam_tail"
wagging_type = "mam_waggingtail"
- species_type = "furry"
+ species_category = SPECIES_CATEGORY_FURRY
- allowed_limb_ids = list("mammal","aquatic","avian")
\ No newline at end of file
+ allowed_limb_ids = list("mammal","aquatic","avian")
diff --git a/code/modules/mob/living/carbon/human/species_types/bugmen.dm b/code/modules/mob/living/carbon/human/species_types/bugmen.dm
index 6eccde61a9..773651d464 100644
--- a/code/modules/mob/living/carbon/human/species_types/bugmen.dm
+++ b/code/modules/mob/living/carbon/human/species_types/bugmen.dm
@@ -1,6 +1,6 @@
/datum/species/insect
name = "Anthromorphic Insect"
- id = "insect"
+ id = SPECIES_INSECT
say_mod = "chitters"
default_color = "00FF00"
species_traits = list(LIPS,EYECOLOR,HAIR,FACEHAIR,MUTCOLORS,HORNCOLOR,WINGCOLOR,HAS_FLESH,HAS_BONE)
@@ -18,7 +18,7 @@
tail_type = "mam_tail"
wagging_type = "mam_waggingtail"
- species_type = "insect"
+ species_category = SPECIES_CATEGORY_INSECT
allowed_limb_ids = list("insect","apid","moth","moth_not_greyscale")
diff --git a/code/modules/mob/living/carbon/human/species_types/corporate.dm b/code/modules/mob/living/carbon/human/species_types/corporate.dm
index a2597ed286..e9baa786da 100644
--- a/code/modules/mob/living/carbon/human/species_types/corporate.dm
+++ b/code/modules/mob/living/carbon/human/species_types/corporate.dm
@@ -18,4 +18,4 @@
inherent_traits = list(TRAIT_RADIMMUNE,TRAIT_VIRUSIMMUNE,TRAIT_PIERCEIMMUNE,TRAIT_NODISMEMBER,TRAIT_NOLIMBDISABLE,TRAIT_NOHUNGER)
sexes = 0
gib_types = /obj/effect/gibspawner/robot
- species_type = "robotic"
\ No newline at end of file
+ species_category = SPECIES_CATEGORY_ROBOT
diff --git a/code/modules/mob/living/carbon/human/species_types/dullahan.dm b/code/modules/mob/living/carbon/human/species_types/dullahan.dm
index f3b499093b..f756fd8816 100644
--- a/code/modules/mob/living/carbon/human/species_types/dullahan.dm
+++ b/code/modules/mob/living/carbon/human/species_types/dullahan.dm
@@ -1,6 +1,6 @@
/datum/species/dullahan
name = "Dullahan"
- id = "dullahan"
+ id = SPECIES_DULLAHAN
default_color = "FFFFFF"
species_traits = list(EYECOLOR,HAIR,FACEHAIR,LIPS,HAS_FLESH,HAS_BONE)
inherent_traits = list(TRAIT_NOHUNGER,TRAIT_NOBREATH)
@@ -14,7 +14,7 @@
limbs_id = "human"
skinned_type = /obj/item/stack/sheet/animalhide/human
has_field_of_vision = FALSE //Too much of a trouble, their vision is already bound to their severed head.
- species_type = "undead"
+ species_category = SPECIES_CATEGORY_UNDEAD
var/pumpkin = FALSE
var/obj/item/dullahan_relay/myhead
diff --git a/code/modules/mob/living/carbon/human/species_types/dwarves.dm b/code/modules/mob/living/carbon/human/species_types/dwarves.dm
index a465e7c3a9..ce67a947de 100644
--- a/code/modules/mob/living/carbon/human/species_types/dwarves.dm
+++ b/code/modules/mob/living/carbon/human/species_types/dwarves.dm
@@ -4,7 +4,7 @@ GLOBAL_LIST_INIT(dwarf_last, world.file2list("strings/names/dwarf_last.txt")) //
/datum/species/dwarf //not to be confused with the genetic manlets
name = "Dwarf"
- id = "dwarf" //Also called Homo sapiens pumilionis
+ id = SPECIES_DWARF //Also called Homo sapiens pumilionis
default_color = "FFFFFF"
species_traits = list(EYECOLOR,HAIR,FACEHAIR,LIPS,HAS_FLESH,HAS_BONE)
inherent_traits = list(TRAIT_DWARF,TRAIT_SNOB)
@@ -18,7 +18,7 @@ GLOBAL_LIST_INIT(dwarf_last, world.file2list("strings/names/dwarf_last.txt")) //
mutant_organs = list(/obj/item/organ/dwarfgland) //Dwarven alcohol gland, literal gland warrior
mutantliver = /obj/item/organ/liver/dwarf //Dwarven super liver (Otherwise they r doomed)
species_language_holder = /datum/language_holder/dwarf
- species_type = "human" //a kind of human
+ species_category = SPECIES_CATEGORY_BASIC //a kind of human
/mob/living/carbon/human/species/dwarf //species admin spawn path
race = /datum/species/dwarf //and the race the path is set to.
diff --git a/code/modules/mob/living/carbon/human/species_types/ethereal.dm b/code/modules/mob/living/carbon/human/species_types/ethereal.dm
index c182defda4..d92f4ab14a 100644
--- a/code/modules/mob/living/carbon/human/species_types/ethereal.dm
+++ b/code/modules/mob/living/carbon/human/species_types/ethereal.dm
@@ -2,7 +2,7 @@
/datum/species/ethereal
name = "Ethereal"
- id = "ethereal"
+ id = SPECIES_ETHEREAL
attack_verb = "burn"
attack_sound = 'sound/weapons/etherealhit.ogg'
miss_sound = 'sound/weapons/etherealmiss.ogg'
diff --git a/code/modules/mob/living/carbon/human/species_types/felinid.dm b/code/modules/mob/living/carbon/human/species_types/felinid.dm
index b760fd0aee..011b6b7642 100644
--- a/code/modules/mob/living/carbon/human/species_types/felinid.dm
+++ b/code/modules/mob/living/carbon/human/species_types/felinid.dm
@@ -1,7 +1,7 @@
//Subtype of human
/datum/species/human/felinid
name = "Felinid"
- id = "felinid"
+ id = SPECIES_FELINID
limbs_id = "human"
mutant_bodyparts = list("mam_tail" = "Cat", "mam_ears" = "Cat", "deco_wings" = "None")
@@ -11,7 +11,7 @@
tail_type = "mam_tail"
wagging_type = "mam_waggingtail"
- species_type = "furry"
+ species_category = SPECIES_CATEGORY_FURRY
/datum/species/human/felinid/on_species_gain(mob/living/carbon/C, datum/species/old_species, pref_load)
if(ishuman(C))
diff --git a/code/modules/mob/living/carbon/human/species_types/flypeople.dm b/code/modules/mob/living/carbon/human/species_types/flypeople.dm
index c56adaf1b0..c3e406244b 100644
--- a/code/modules/mob/living/carbon/human/species_types/flypeople.dm
+++ b/code/modules/mob/living/carbon/human/species_types/flypeople.dm
@@ -1,6 +1,6 @@
/datum/species/fly
name = "Anthromorphic Fly"
- id = "fly"
+ id = SPECIES_FLY
say_mod = "buzzes"
species_traits = list(NOEYES,HAS_FLESH,HAS_BONE)
inherent_biotypes = MOB_ORGANIC|MOB_HUMANOID|MOB_BUG
@@ -12,7 +12,7 @@
liked_food = GROSS
exotic_bloodtype = "BUG"
exotic_blood_color = BLOOD_COLOR_BUG
- species_type = "insect"
+ species_category = SPECIES_CATEGORY_INSECT
/datum/species/fly/handle_chemicals(datum/reagent/chem, mob/living/carbon/human/H)
if(istype(chem, /datum/reagent/toxin/pestkiller))
diff --git a/code/modules/mob/living/carbon/human/species_types/golems.dm b/code/modules/mob/living/carbon/human/species_types/golems.dm
index 438eaf1eea..ff0287d7b9 100644
--- a/code/modules/mob/living/carbon/human/species_types/golems.dm
+++ b/code/modules/mob/living/carbon/human/species_types/golems.dm
@@ -1,7 +1,7 @@
/datum/species/golem
// Animated beings of stone. They have increased defenses, and do not need to breathe. They're also slow as fuuuck.
name = "Golem"
- id = "iron golem"
+ id = SPECIES_GOLEM
species_traits = list(NOBLOOD,MUTCOLORS,NO_UNDERWEAR,NOGENITALS,NOAROUSAL)
inherent_traits = list(TRAIT_RESISTHEAT,TRAIT_NOBREATH,TRAIT_RESISTCOLD,TRAIT_RESISTHIGHPRESSURE,TRAIT_RESISTLOWPRESSURE,TRAIT_NOFIRE,TRAIT_CHUNKYFINGERS,TRAIT_RADIMMUNE,TRAIT_PIERCEIMMUNE,TRAIT_NODISMEMBER)
inherent_biotypes = MOB_HUMANOID|MOB_MINERAL
@@ -32,7 +32,7 @@
var/special_name_chance = 5
var/owner //dobby is a free golem
- species_type = "golem"
+ species_category = SPECIES_CATEGORY_GOLEM
/datum/species/golem/random_name(gender,unique,lastname)
var/golem_surname = pick(GLOB.golem_names)
diff --git a/code/modules/mob/living/carbon/human/species_types/humans.dm b/code/modules/mob/living/carbon/human/species_types/humans.dm
index f175ddb921..e8e0ff1a78 100644
--- a/code/modules/mob/living/carbon/human/species_types/humans.dm
+++ b/code/modules/mob/living/carbon/human/species_types/humans.dm
@@ -1,6 +1,6 @@
/datum/species/human
name = "Human"
- id = "human"
+ id = SPECIES_HUMAN
default_color = "FFFFFF"
species_traits = list(EYECOLOR,HAIR,FACEHAIR,LIPS,MUTCOLORS_PARTSONLY,WINGCOLOR,HAS_FLESH,HAS_BONE)
@@ -12,7 +12,7 @@
tail_type = "tail_human"
wagging_type = "waggingtail_human"
- species_type = "human"
+ species_category = SPECIES_CATEGORY_BASIC
/datum/species/human/spec_death(gibbed, mob/living/carbon/human/H)
if(H)
diff --git a/code/modules/mob/living/carbon/human/species_types/ipc.dm b/code/modules/mob/living/carbon/human/species_types/ipc.dm
index 9bfe661f47..6353fd5184 100644
--- a/code/modules/mob/living/carbon/human/species_types/ipc.dm
+++ b/code/modules/mob/living/carbon/human/species_types/ipc.dm
@@ -1,6 +1,6 @@
/datum/species/ipc
name = "I.P.C."
- id = "ipc"
+ id = SPECIES_IPC
say_mod = "beeps"
default_color = "00FF00"
blacklisted = 0
@@ -28,7 +28,7 @@
exotic_bloodtype = "HF"
exotic_blood_color = BLOOD_COLOR_OIL
- species_type = "robotic"
+ species_category = SPECIES_CATEGORY_ROBOT
var/datum/action/innate/monitor_change/screen
diff --git a/code/modules/mob/living/carbon/human/species_types/jellypeople.dm b/code/modules/mob/living/carbon/human/species_types/jellypeople.dm
index 6193172f06..58a10fb08e 100644
--- a/code/modules/mob/living/carbon/human/species_types/jellypeople.dm
+++ b/code/modules/mob/living/carbon/human/species_types/jellypeople.dm
@@ -1,7 +1,7 @@
/datum/species/jelly
// Entirely alien beings that seem to be made entirely out of gel. They have three eyes and a skeleton visible within them.
name = "Xenobiological Jelly Entity"
- id = "jelly"
+ id = SPECIES_JELLY
default_color = "00FF90"
say_mod = "chirps"
species_traits = list(MUTCOLORS,EYECOLOR,HAIR,FACEHAIR,WINGCOLOR,HAS_FLESH)
@@ -29,7 +29,7 @@
tail_type = "mam_tail"
wagging_type = "mam_waggingtail"
- species_type = "jelly"
+ species_category = SPECIES_CATEGORY_JELLY
/obj/item/organ/brain/jelly
name = "slime nucleus"
@@ -158,7 +158,7 @@
/datum/species/jelly/slime
name = "Xenobiological Slime Entity"
- id = "slime"
+ id = SPECIES_SLIME
default_color = "00FFFF"
species_traits = list(MUTCOLORS,EYECOLOR,HAIR,FACEHAIR)
say_mod = "says"
@@ -467,7 +467,7 @@
/datum/species/jelly/roundstartslime
name = "Xenobiological Slime Hybrid"
- id = "slimeperson"
+ id = SPECIES_SLIME_HYBRID
limbs_id = "slime"
default_color = "00FFFF"
species_traits = list(MUTCOLORS,EYECOLOR,HAIR,FACEHAIR)
@@ -777,7 +777,7 @@
/datum/species/jelly/luminescent
name = "Luminescent Slime Entity"
- id = "lum"
+ id = SPECIES_SLIME_LUMI
say_mod = "says"
var/glow_intensity = LUMINESCENT_DEFAULT_GLOW
var/obj/effect/dummy/luminescent_glow/glow
@@ -944,7 +944,7 @@
/datum/species/jelly/stargazer
name = "Stargazer Slime Entity"
- id = "stargazer"
+ id = SPECIES_STARGAZER
var/datum/action/innate/project_thought/project_thought
var/datum/action/innate/link_minds/link_minds
var/list/mob/living/linked_mobs = list()
diff --git a/code/modules/mob/living/carbon/human/species_types/lizardpeople.dm b/code/modules/mob/living/carbon/human/species_types/lizardpeople.dm
index a8e3f89957..c37a24a93a 100644
--- a/code/modules/mob/living/carbon/human/species_types/lizardpeople.dm
+++ b/code/modules/mob/living/carbon/human/species_types/lizardpeople.dm
@@ -1,7 +1,7 @@
/datum/species/lizard
// Reptilian humanoids with scaled skin and tails.
name = "Anthromorphic Lizard"
- id = "lizard"
+ id = SPECIES_LIZARD
say_mod = "hisses"
default_color = "00FF00"
species_traits = list(MUTCOLORS,EYECOLOR,HAIR,FACEHAIR,LIPS,HORNCOLOR,WINGCOLOR,HAS_FLESH,HAS_BONE)
@@ -28,7 +28,7 @@
tail_type = "tail_lizard"
wagging_type = "waggingtail_lizard"
- species_type = "lizard"
+ species_category = "lizard"
/datum/species/lizard/random_name(gender,unique,lastname)
if(unique)
@@ -46,7 +46,7 @@
*/
/datum/species/lizard/ashwalker
name = "Ash Walker"
- id = "ashlizard"
+ id = SPECIES_ASHWALKER
limbs_id = "lizard"
species_traits = list(MUTCOLORS,EYECOLOR,LIPS,DIGITIGRADE)
inherent_traits = list(TRAIT_CHUNKYFINGERS)
diff --git a/code/modules/mob/living/carbon/human/species_types/mushpeople.dm b/code/modules/mob/living/carbon/human/species_types/mushpeople.dm
index f91c3518a3..dcbdf91485 100644
--- a/code/modules/mob/living/carbon/human/species_types/mushpeople.dm
+++ b/code/modules/mob/living/carbon/human/species_types/mushpeople.dm
@@ -1,6 +1,6 @@
/datum/species/mush //mush mush codecuck
name = "Anthromorphic Mushroom"
- id = "mush"
+ id = SPECIES_MUSHROOM
mutant_bodyparts = list("caps" = "Round")
fixed_mut_color = "DBBF92"
@@ -21,7 +21,7 @@
burnmod = 1.25
heatmod = 1.5
- species_type = "plant"
+ species_category = SPECIES_CATEGORY_PLANT
mutanteyes = /obj/item/organ/eyes/night_vision/mushroom
var/datum/martial_art/mushpunch/mush
diff --git a/code/modules/mob/living/carbon/human/species_types/plasmamen.dm b/code/modules/mob/living/carbon/human/species_types/plasmamen.dm
index 8c30adb8f9..12a520b463 100644
--- a/code/modules/mob/living/carbon/human/species_types/plasmamen.dm
+++ b/code/modules/mob/living/carbon/human/species_types/plasmamen.dm
@@ -1,6 +1,6 @@
/datum/species/plasmaman
name = "Plasmaman"
- id = "plasmaman"
+ id = SPECIES_PLASMAMAN
say_mod = "rattles"
sexes = 0
meat = /obj/item/stack/sheet/mineral/plasma
@@ -22,7 +22,7 @@
liked_food = VEGETABLES
outfit_important_for_life = /datum/outfit/plasmaman
- species_type = "skeleton"
+ species_category = SPECIES_CATEGORY_SKELETON
/datum/species/plasmaman/spec_life(mob/living/carbon/human/H)
var/datum/gas_mixture/environment = H.loc.return_air()
diff --git a/code/modules/mob/living/carbon/human/species_types/podpeople.dm b/code/modules/mob/living/carbon/human/species_types/podpeople.dm
index 0f62953e6c..fae8ebfb0d 100644
--- a/code/modules/mob/living/carbon/human/species_types/podpeople.dm
+++ b/code/modules/mob/living/carbon/human/species_types/podpeople.dm
@@ -1,7 +1,7 @@
/datum/species/pod
// A mutation caused by a human being ressurected in a revival pod. These regain health in light, and begin to wither in darkness.
name = "Anthromorphic Plant"
- id = "pod"
+ id = SPECIES_POD
default_color = "59CE00"
species_traits = list(MUTCOLORS,EYECOLOR,CAN_SCAR,HAS_FLESH,HAS_BONE)
attack_verb = "slash"
@@ -19,7 +19,7 @@
var/light_burnheal = -1
var/light_bruteheal = -1
- species_type = "plant"
+ species_category = SPECIES_CATEGORY_PLANT
allowed_limb_ids = list("pod","mush")
@@ -66,7 +66,7 @@
/datum/species/pod/pseudo_weak
name = "Anthromorphic Plant"
- id = "podweak"
+ id = SPECIES_POD_WEAK
species_traits = list(EYECOLOR,HAIR,FACEHAIR,LIPS,MUTCOLORS)
mutant_bodyparts = list("mcolor" = "FFFFFF","mcolor2" = "FFFFFF","mcolor3" = "FFFFFF", "mam_snouts" = "Husky", "mam_tail" = "Husky", "mam_ears" = "Husky", "mam_body_markings" = "Husky", "taur" = "None", "legs" = "Normal Legs")
limbs_id = "pod"
diff --git a/code/modules/mob/living/carbon/human/species_types/shadowpeople.dm b/code/modules/mob/living/carbon/human/species_types/shadowpeople.dm
index a7d9ce4473..7dc8083264 100644
--- a/code/modules/mob/living/carbon/human/species_types/shadowpeople.dm
+++ b/code/modules/mob/living/carbon/human/species_types/shadowpeople.dm
@@ -4,7 +4,7 @@
/datum/species/shadow
// Humans cursed to stay in the darkness, lest their life forces drain. They regain health in shadow and die in light.
name = "???"
- id = "shadow"
+ id = SPECIES_SHADOW
sexes = 0
blacklisted = 1
ignored_by = list(/mob/living/simple_animal/hostile/faithless)
@@ -15,7 +15,7 @@
dangerous_existence = 1
mutanteyes = /obj/item/organ/eyes/night_vision
- species_type = "shadow"
+ species_category = SPECIES_CATEGORY_SHADOW
/datum/species/shadow/on_species_gain(mob/living/carbon/C, datum/species/old_species)
. = ..()
@@ -32,7 +32,7 @@
/datum/species/shadow/nightmare
name = "Nightmare"
- id = "nightmare"
+ id = SPECIES_NIGHTMARE
limbs_id = "shadow"
burnmod = 1.5
blacklisted = TRUE
diff --git a/code/modules/mob/living/carbon/human/species_types/skeletons.dm b/code/modules/mob/living/carbon/human/species_types/skeletons.dm
index 1b6bef9dc6..b0673a7df4 100644
--- a/code/modules/mob/living/carbon/human/species_types/skeletons.dm
+++ b/code/modules/mob/living/carbon/human/species_types/skeletons.dm
@@ -1,6 +1,6 @@
/datum/species/skeleton
name = "Skeleton"
- id = "skeleton"
+ id = SPECIES_SKELETON
say_mod = "rattles"
blacklisted = 0
sexes = 0
@@ -15,7 +15,7 @@
brutemod = 1.25
burnmod = 1.25
- species_type = "skeleton" //they have their own category that's disassociated from undead, paired with plasmapeople
+ species_category = SPECIES_CATEGORY_SKELETON //they have their own category that's disassociated from undead, paired with plasmapeople
/datum/species/skeleton/New()
if(SSevents.holidays && SSevents.holidays[HALLOWEEN]) //skeletons are stronger during the spooky season!
@@ -31,7 +31,7 @@
/datum/species/skeleton/space
name = "Spooky Spacey Skeleton"
- id = "spaceskeleton"
+ id = SPECIES_SKELETON_SPACE
limbs_id = "skeleton"
blacklisted = 1
inherent_traits = list(TRAIT_RESISTHEAT,TRAIT_NOBREATH,TRAIT_RESISTCOLD,TRAIT_RESISTHIGHPRESSURE,TRAIT_RESISTLOWPRESSURE,TRAIT_RADIMMUNE,TRAIT_PIERCEIMMUNE,TRAIT_NOHUNGER,TRAIT_EASYDISMEMBER,TRAIT_LIMBATTACHMENT, TRAIT_FAKEDEATH, TRAIT_CALCIUM_HEALER)
diff --git a/code/modules/mob/living/carbon/human/species_types/synthliz.dm b/code/modules/mob/living/carbon/human/species_types/synthliz.dm
index b446e3796e..88781532d3 100644
--- a/code/modules/mob/living/carbon/human/species_types/synthliz.dm
+++ b/code/modules/mob/living/carbon/human/species_types/synthliz.dm
@@ -1,6 +1,6 @@
/datum/species/synthliz
name = "Synthetic Lizardperson"
- id = "synthliz"
+ id = SPECIES_SYNTH_LIZARD
say_mod = "beeps"
default_color = "00FF00"
species_traits = list(MUTCOLORS,NOTRANSSTING,EYECOLOR,LIPS,HAIR,ROBOTIC_LIMBS,HAS_FLESH,HAS_BONE)
@@ -27,4 +27,4 @@
tail_type = "mam_tail"
wagging_type = "mam_waggingtail"
- species_type = "robotic"
+ species_category = SPECIES_CATEGORY_ROBOT
diff --git a/code/modules/mob/living/carbon/human/species_types/synths.dm b/code/modules/mob/living/carbon/human/species_types/synths.dm
index 3d55ce1027..5d2b6f4c77 100644
--- a/code/modules/mob/living/carbon/human/species_types/synths.dm
+++ b/code/modules/mob/living/carbon/human/species_types/synths.dm
@@ -1,6 +1,6 @@
/datum/species/synth
name = "Synthetic" //inherited from the real species, for health scanners and things
- id = "synth"
+ id = SPECIES_SYNTH
say_mod = "beep boops" //inherited from a user's real species
sexes = 0
species_traits = list(NOTRANSSTING,NOGENITALS,NOAROUSAL) //all of these + whatever we inherit from the real species
@@ -17,11 +17,11 @@
var/disguise_fail_health = 75 //When their health gets to this level their synthflesh partially falls off
var/datum/species/fake_species = null //a species to do most of our work for us, unless we're damaged
species_language_holder = /datum/language_holder/synthetic
- species_type = "robotic"
+ species_category = SPECIES_CATEGORY_ROBOT
/datum/species/synth/military
name = "Military Synth"
- id = "military_synth"
+ id = SPECIES_SYNTH_MIL
armor = 25
punchdamagelow = 10
punchdamagehigh = 19
diff --git a/code/modules/mob/living/carbon/human/species_types/vampire.dm b/code/modules/mob/living/carbon/human/species_types/vampire.dm
index 857fb6d8ed..4125d3beab 100644
--- a/code/modules/mob/living/carbon/human/species_types/vampire.dm
+++ b/code/modules/mob/living/carbon/human/species_types/vampire.dm
@@ -1,6 +1,6 @@
/datum/species/vampire
name = "Vampire"
- id = "vampire"
+ id = SPECIES_VAMPIRE
default_color = "FFFFFF"
species_traits = list(EYECOLOR,HAIR,FACEHAIR,LIPS,DRINKSBLOOD,HAS_FLESH,HAS_BONE)
inherent_traits = list(TRAIT_NOHUNGER,TRAIT_NOBREATH)
@@ -14,7 +14,7 @@
limbs_id = "human"
skinned_type = /obj/item/stack/sheet/animalhide/human
var/info_text = "You are a Vampire. You will slowly but constantly lose blood if outside of a coffin. If inside a coffin, you will slowly heal. You may gain more blood by grabbing a live victim and using your drain ability."
- species_type = "undead"
+ species_category = SPECIES_CATEGORY_UNDEAD
/datum/species/vampire/check_roundstart_eligible()
if(SSevents.holidays && SSevents.holidays[HALLOWEEN])
diff --git a/code/modules/mob/living/carbon/human/species_types/xeno.dm b/code/modules/mob/living/carbon/human/species_types/xeno.dm
index ddd1c86f0a..2757c624e7 100644
--- a/code/modules/mob/living/carbon/human/species_types/xeno.dm
+++ b/code/modules/mob/living/carbon/human/species_types/xeno.dm
@@ -1,7 +1,7 @@
/datum/species/xeno
// A cloning mistake, crossing human and xenomorph DNA
name = "Xenomorph Hybrid"
- id = "xeno"
+ id = SPECIES_XENOHYBRID
say_mod = "hisses"
default_color = "00FF00"
species_traits = list(MUTCOLORS,EYECOLOR,LIPS,CAN_SCAR)
@@ -15,4 +15,4 @@
exotic_bloodtype = "X*"
damage_overlay_type = "xeno"
liked_food = MEAT
- species_type = "alien"
+ species_category = SPECIES_CATEGORY_ALIEN
diff --git a/code/modules/mob/living/carbon/human/species_types/zombies.dm b/code/modules/mob/living/carbon/human/species_types/zombies.dm
index 8d86ad2fa9..f1c0b8e7c0 100644
--- a/code/modules/mob/living/carbon/human/species_types/zombies.dm
+++ b/code/modules/mob/living/carbon/human/species_types/zombies.dm
@@ -15,7 +15,7 @@
var/static/list/spooks = list('sound/hallucinations/growl1.ogg','sound/hallucinations/growl2.ogg','sound/hallucinations/growl3.ogg','sound/hallucinations/veryfar_noise.ogg','sound/hallucinations/wail.ogg')
disliked_food = NONE
liked_food = GROSS | MEAT | RAW
- species_type = "undead"
+ species_category = SPECIES_CATEGORY_UNDEAD
/datum/species/zombie/notspaceproof
id = "notspaceproofzombie"
@@ -98,7 +98,7 @@
// Your skin falls off
/datum/species/krokodil_addict
- name = "Human"
+ name = SPECIES_HUMAN
id = "goofzombies"
limbs_id = "zombie" //They look like zombies
sexes = 0
diff --git a/code/modules/mob/living/carbon/life.dm b/code/modules/mob/living/carbon/life.dm
index 8f181d47fd..30785a0103 100644
--- a/code/modules/mob/living/carbon/life.dm
+++ b/code/modules/mob/living/carbon/life.dm
@@ -502,19 +502,14 @@ GLOBAL_LIST_INIT(ballmer_windows_me_msg, list("Yo man, what if, we like, uh, put
//this updates all special effects: stun, sleeping, knockdown, druggy, stuttering, etc..
/mob/living/carbon/handle_status_effects()
..()
- if(getStaminaLoss() && !SEND_SIGNAL(src, COMSIG_COMBAT_MODE_CHECK, COMBAT_MODE_ACTIVE)) //CIT CHANGE - prevents stamina regen while combat mode is active
- adjustStaminaLoss(!CHECK_MOBILITY(src, MOBILITY_STAND) ? ((combat_flags & COMBAT_FLAG_HARD_STAMCRIT) ? STAM_RECOVERY_STAM_CRIT : STAM_RECOVERY_RESTING) : STAM_RECOVERY_NORMAL)
+ var/combat_mode = SEND_SIGNAL(src, COMSIG_COMBAT_MODE_CHECK, COMBAT_MODE_ACTIVE)
+ if(getStaminaLoss() && !HAS_TRAIT(src, TRAIT_NO_STAMINA_REGENERATION))
+ adjustStaminaLoss((!CHECK_MOBILITY(src, MOBILITY_STAND) ? ((combat_flags & COMBAT_FLAG_HARD_STAMCRIT) ? STAM_RECOVERY_STAM_CRIT : STAM_RECOVERY_RESTING) : STAM_RECOVERY_NORMAL) * (combat_mode? 0.25 : 1))
if(!(combat_flags & COMBAT_FLAG_HARD_STAMCRIT) && incomingstammult != 1)
incomingstammult = max(0.01, incomingstammult)
incomingstammult = min(1, incomingstammult*2)
- //CIT CHANGES START HERE. STAMINA BUFFER STUFF
- if(bufferedstam && world.time > stambufferregentime)
- var/drainrate = max((bufferedstam*(bufferedstam/(5)))*0.1,1)
- bufferedstam = max(bufferedstam - drainrate, 0)
- //END OF CIT CHANGES
-
var/restingpwr = 1 + 4 * !CHECK_MOBILITY(src, MOBILITY_STAND)
//Dizziness
diff --git a/code/modules/mob/living/emote.dm b/code/modules/mob/living/emote.dm
index 1d78979cbd..4ee52a08a6 100644
--- a/code/modules/mob/living/emote.dm
+++ b/code/modules/mob/living/emote.dm
@@ -562,7 +562,7 @@
if(. && iscarbon(user))
var/mob/living/carbon/C = user
if(isjellyperson(C))
- pick(playsound(C, 'sound/effects/attackblob.ogg', 50, 1),playsound(C, 'sound/effects/blobattack.ogg', 50, 1))
+ playsound(C, 'sound/effects/attackblob.ogg', 50, 1)
/datum/emote/living/audio_emote/blurp
key = "blurp"
diff --git a/code/modules/mob/living/living.dm b/code/modules/mob/living/living.dm
index 8dac56a8c9..57d688bde3 100644
--- a/code/modules/mob/living/living.dm
+++ b/code/modules/mob/living/living.dm
@@ -8,6 +8,8 @@
for(var/datum/atom_hud/data/diagnostic/diag_hud in GLOB.huds)
diag_hud.add_to_hud(src)
faction += "[REF(src)]"
+ stamina_buffer = INFINITY
+ UpdateStaminaBuffer()
GLOB.mob_living_list += src
/mob/living/prepare_huds()
@@ -842,12 +844,12 @@
return
var/strip_mod = 1
var/strip_silence = FALSE
- if (ishuman(src)) //carbon doesn't actually wear gloves
+ if(ishuman(src)) //carbon doesn't actually wear gloves
var/mob/living/carbon/C = src
- var/obj/item/clothing/gloves/g = C.gloves
- if (istype(g))
- strip_mod = g.strip_mod
- strip_silence = g.strip_silence
+ var/obj/item/clothing/gloves/G = C.gloves
+ if(istype(G))
+ strip_mod = G.strip_mod
+ strip_silence = G.strip_silence
if (!strip_silence)
who.visible_message("[src] tries to remove [who]'s [what.name].", \
"[src] tries to remove your [what.name].", target = src,
@@ -1257,7 +1259,7 @@
SetUnconscious(clamp_unconscious_to)
HealAllImmobilityUpTo(clamp_immobility_to)
adjustStaminaLoss(min(0, -stamina_boost))
- adjustStaminaLossBuffered(min(0, -stamina_buffer_boost))
+ RechargeStaminaBuffer(stamina_buffer_boost) // this MUST GO AFTER ADJUSTSTAMINALOSS.
if(scale_stamina_loss_recovery)
adjustStaminaLoss(min(-((getStaminaLoss() - stamina_loss_recovery_bypass) * scale_stamina_loss_recovery), 0))
if(put_on_feet)
diff --git a/code/modules/mob/living/living_active_block.dm b/code/modules/mob/living/living_active_block.dm
index 8e8b94463b..db472df388 100644
--- a/code/modules/mob/living/living_active_block.dm
+++ b/code/modules/mob/living/living_active_block.dm
@@ -8,6 +8,8 @@
active_block_item = null
REMOVE_TRAIT(src, TRAIT_MOBILITY_NOUSE, ACTIVE_BLOCK_TRAIT)
REMOVE_TRAIT(src, TRAIT_SPRINT_LOCKED, ACTIVE_BLOCK_TRAIT)
+ REMOVE_TRAIT(src, TRAIT_NO_STAMINA_BUFFER_REGENERATION, ACTIVE_BLOCK_TRAIT)
+ REMOVE_TRAIT(src, TRAIT_NO_STAMINA_REGENERATION, ACTIVE_BLOCK_TRAIT)
remove_movespeed_modifier(/datum/movespeed_modifier/active_block)
var/datum/block_parry_data/data = I.get_block_parry_data()
DelayNextAction(data.block_end_click_cd_add)
@@ -27,6 +29,10 @@
ADD_TRAIT(src, TRAIT_MOBILITY_NOUSE, ACTIVE_BLOCK_TRAIT) //probably should be something else at some point
if(data.block_lock_sprinting)
ADD_TRAIT(src, TRAIT_SPRINT_LOCKED, ACTIVE_BLOCK_TRAIT)
+ if(data.block_no_stamina_regeneration)
+ ADD_TRAIT(src, TRAIT_NO_STAMINA_REGENERATION, ACTIVE_BLOCK_TRAIT)
+ if(data.block_no_stambuffer_regeneration)
+ ADD_TRAIT(src, TRAIT_NO_STAMINA_BUFFER_REGENERATION, ACTIVE_BLOCK_TRAIT)
add_or_update_variable_movespeed_modifier(/datum/movespeed_modifier/active_block, multiplicative_slowdown = data.block_slowdown)
active_block_effect_start()
return TRUE
@@ -192,7 +198,7 @@
var/held_index = C.get_held_index_of_item(src)
var/obj/item/bodypart/BP = C.hand_bodyparts[held_index]
if(!BP?.body_zone)
- return C.adjustStaminaLossBuffered(stamina_amount) //nah
+ return C.adjustStaminaLoss(stamina_amount) //nah
var/zone = BP.body_zone
var/stamina_to_zone = data.block_stamina_limb_ratio * stamina_amount
var/stamina_to_chest = stamina_amount - stamina_to_zone
@@ -200,9 +206,9 @@
stamina_to_chest -= stamina_buffered
C.apply_damage(stamina_to_zone, STAMINA, zone)
C.apply_damage(stamina_to_chest, STAMINA, BODY_ZONE_CHEST)
- C.adjustStaminaLossBuffered(stamina_buffered)
+ C.adjustStaminaLoss(stamina_buffered)
else
- owner.adjustStaminaLossBuffered(stamina_amount)
+ owner.adjustStaminaLoss(stamina_amount)
/obj/item/proc/on_active_block(mob/living/owner, atom/object, damage, damage_blocked, attack_text, attack_type, armour_penetration, mob/attacker, def_zone, final_block_chance, list/block_return, override_direction)
return
diff --git a/code/modules/mob/living/living_active_parry.dm b/code/modules/mob/living/living_active_parry.dm
index 10dece1ffc..7353add745 100644
--- a/code/modules/mob/living/living_active_parry.dm
+++ b/code/modules/mob/living/living_active_parry.dm
@@ -68,12 +68,13 @@
// can always implement it later, whatever.
if((data.parry_respect_clickdelay && !CheckActionCooldown()) || ((parry_end_time_last + data.parry_cooldown) > world.time))
to_chat(src, "You are not ready to parry (again)!")
- return
+ return FALSE
// Point of no return, make sure everything is set.
parrying = method
if(method == ITEM_PARRY)
active_parry_item = using_item
- adjustStaminaLossBuffered(data.parry_stamina_cost)
+ if(!UseStaminaBuffer(data.parry_stamina_cost, TRUE))
+ return FALSE
parry_start_time = world.time
successful_parries = list()
addtimer(CALLBACK(src, .proc/end_parry_sequence), full_parry_duration)
diff --git a/code/modules/mob/living/living_blocking_parrying.dm b/code/modules/mob/living/living_blocking_parrying.dm
index 47dae8849d..08ef674b33 100644
--- a/code/modules/mob/living/living_blocking_parrying.dm
+++ b/code/modules/mob/living/living_blocking_parrying.dm
@@ -74,8 +74,12 @@ GLOBAL_LIST_EMPTY(block_parry_data)
/// Ratio of stamina incurred by chest (so after [block_stamina_limb_ratio] runs) that is buffered.
var/block_stamina_buffer_ratio = 1
- /// Stamina dealt directly via adjustStaminaLossBuffered() per SECOND of block.
+ /// Stamina dealt directly via UseStaminaBuffer() per SECOND of block.
var/block_stamina_cost_per_second = 1.5
+ /// Prevent stamina buffer regeneration while block?
+ var/block_no_stambuffer_regeneration = TRUE
+ /// Prevent stamina regeneration while block?
+ var/block_no_stamina_regeneration = FALSE
/// Bitfield for attack types that we can block while down. This will work in any direction.
var/block_resting_attack_types_anydir = ATTACK_TYPE_MELEE | ATTACK_TYPE_UNARMED | ATTACK_TYPE_TACKLE
@@ -307,7 +311,7 @@ GLOBAL_LIST_EMPTY(block_parry_data)
/mob/living/proc/handle_block_parry(seconds = 1)
if(combat_flags & COMBAT_FLAG_ACTIVE_BLOCKING)
var/datum/block_parry_data/data = return_block_parry_datum(active_block_item.block_parry_data)
- adjustStaminaLossBuffered(data.block_stamina_cost_per_second * seconds)
+ UseStaminaBuffer(data.block_stamina_cost_per_second * seconds)
/mob/living/on_item_dropped(obj/item/I)
if(I == active_block_item)
diff --git a/code/modules/mob/living/living_defines.dm b/code/modules/mob/living/living_defines.dm
index 5495d37297..5621fab9c1 100644
--- a/code/modules/mob/living/living_defines.dm
+++ b/code/modules/mob/living/living_defines.dm
@@ -53,6 +53,7 @@
var/hallucination = 0 //Directly affects how long a mob will hallucinate for
+ var/last_special = 0 //Used by the resist verb, likely used to prevent players from bypassing next_move by logging in/out.
var/timeofdeath = 0
//Allows mobs to move through dense areas without restriction. For instance, in space or out of holder objects.
@@ -148,9 +149,6 @@
var/combatmessagecooldown = 0
var/incomingstammult = 1
- var/bufferedstam = 0
- var/stambuffer = 20
- var/stambufferregentime
//Sprint buffer---
var/sprint_buffer = 42 //Tiles
@@ -159,3 +157,13 @@
var/sprint_buffer_regen_last = 0 //last world.time this was regen'd for math.
var/sprint_stamina_cost = 0.70 //stamina loss per tile while insufficient sprint buffer.
//---End
+
+ // Stamina Buffer---
+ /// Our stamina buffer
+ var/stamina_buffer
+ /// Stamina buffer regen modifier
+ var/stamina_buffer_regen_mod = 1
+ /// Last time stamina buffer regen was done
+ var/stamina_buffer_regen_last = 0
+ /// Last time we used stamina buffer
+ var/stamina_buffer_last_use = 0
diff --git a/code/modules/mob/living/living_mobility.dm b/code/modules/mob/living/living_mobility.dm
index 92de711c27..8c6cf9d207 100644
--- a/code/modules/mob/living/living_mobility.dm
+++ b/code/modules/mob/living/living_mobility.dm
@@ -176,4 +176,6 @@
else
remove_movespeed_modifier(/datum/movespeed_modifier/limbless)
+ update_movespeed()
+
return mobility_flags
diff --git a/code/modules/mob/living/living_sprint.dm b/code/modules/mob/living/living_sprint.dm
index dc0f6d2767..3ef67c9edd 100644
--- a/code/modules/mob/living/living_sprint.dm
+++ b/code/modules/mob/living/living_sprint.dm
@@ -29,6 +29,7 @@
if(combat_flags & COMBAT_FLAG_SPRINT_ACTIVE)
return
ENABLE_BITFIELD(combat_flags, COMBAT_FLAG_SPRINT_ACTIVE)
+ add_movespeed_modifier(/datum/movespeed_modifier/sprinting)
if(update_icon)
update_sprint_icon()
@@ -36,6 +37,7 @@
if(!(combat_flags & COMBAT_FLAG_SPRINT_ACTIVE) || (combat_flags & COMBAT_FLAG_SPRINT_FORCED))
return
DISABLE_BITFIELD(combat_flags, COMBAT_FLAG_SPRINT_ACTIVE)
+ remove_movespeed_modifier(/datum/movespeed_modifier/sprinting)
if(update_icon)
update_sprint_icon()
diff --git a/code/modules/mob/living/say.dm b/code/modules/mob/living/say.dm
index 66c2cd96c7..4c03e74d70 100644
--- a/code/modules/mob/living/say.dm
+++ b/code/modules/mob/living/say.dm
@@ -82,6 +82,7 @@ GLOBAL_LIST_INIT(department_radio_keys, list(
return new_msg
/mob/living/say(message, bubble_type,var/list/spans = list(), sanitize = TRUE, datum/language/language = null, ignore_spam = FALSE, forced = null)
+ set waitfor = FALSE
var/static/list/crit_allowed_modes = list(MODE_WHISPER = TRUE, MODE_CHANGELING = TRUE, MODE_ALIEN = TRUE)
var/static/list/unconscious_allowed_modes = list(MODE_CHANGELING = TRUE, MODE_ALIEN = TRUE)
var/talk_key = get_key(message)
diff --git a/code/modules/mob/living/silicon/robot/robot.dm b/code/modules/mob/living/silicon/robot/robot.dm
index 1eb308af00..83df3ffba5 100644
--- a/code/modules/mob/living/silicon/robot/robot.dm
+++ b/code/modules/mob/living/silicon/robot/robot.dm
@@ -1132,10 +1132,6 @@
bellyup = 1
update_icons()
-/mob/living/silicon/robot/adjustStaminaLossBuffered(amount, updating_health = 1)
- if(istype(cell))
- cell.charge -= amount * 5
-
/mob/living/silicon/robot/verb/viewmanifest()
set category = "Robot Commands"
set name = "View Crew Manifest"
diff --git a/code/modules/mob/living/simple_animal/bot/ed209bot.dm b/code/modules/mob/living/simple_animal/bot/ed209bot.dm
index 3f28fd6e01..8375d621d4 100644
--- a/code/modules/mob/living/simple_animal/bot/ed209bot.dm
+++ b/code/modules/mob/living/simple_animal/bot/ed209bot.dm
@@ -236,7 +236,7 @@ Auto Patrol[]"},
if(targets.len>0)
var/mob/living/carbon/t = pick(targets)
if((t.stat!=2) && (t.lying != 1) && (!t.handcuffed)) //we don't shoot people who are dead, cuffed or lying down.
- shootAt(t)
+ INVOKE_ASYNC(src, .proc/shootAt, t)
switch(mode)
if(BOT_IDLE) // idle
@@ -254,7 +254,7 @@ Auto Patrol[]"},
if(target) // make sure target exists
if(Adjacent(target) && isturf(target.loc)) // if right next to perp
- stun_attack(target)
+ INVOKE_ASYNC(src, .proc/stun_attack, target)
mode = BOT_PREP_ARREST
anchored = TRUE
diff --git a/code/modules/mob/living/simple_animal/hostile/wizard.dm b/code/modules/mob/living/simple_animal/hostile/wizard.dm
index b3523fc42c..59bd67e42a 100644
--- a/code/modules/mob/living/simple_animal/hostile/wizard.dm
+++ b/code/modules/mob/living/simple_animal/hostile/wizard.dm
@@ -62,6 +62,9 @@
/mob/living/simple_animal/hostile/wizard/handle_automated_action()
. = ..()
+ INVOKE_ASYNC(src, .proc/AutomatedCast)
+
+/mob/living/simple_animal/hostile/wizard/proc/AutomatedCast()
if(target && next_cast < world.time)
if((get_dir(src,target) in list(SOUTH,EAST,WEST,NORTH)) && fireball.cast_check(0,src)) //Lined up for fireball
src.setDir(get_dir(src,target))
diff --git a/code/modules/mob/living/simple_animal/parrot.dm b/code/modules/mob/living/simple_animal/parrot.dm
index 3ca29b6746..1ff7e2f799 100644
--- a/code/modules/mob/living/simple_animal/parrot.dm
+++ b/code/modules/mob/living/simple_animal/parrot.dm
@@ -436,12 +436,7 @@
newspeak.Add(possible_phrase)
speak = newspeak
- //Search for item to steal
- parrot_interest = search_for_item()
- if(parrot_interest)
- emote("me", EMOTE_VISIBLE, "looks in [parrot_interest]'s direction and takes flight.")
- parrot_state = PARROT_SWOOP | PARROT_STEAL
- icon_state = icon_living
+ INVOKE_ASYNC(src, .proc/attempt_item_theft)
return
//-----WANDERING - This is basically a 'I dont know what to do yet' state
@@ -620,6 +615,14 @@
parrot_lastmove = src.loc
return 0
+/mob/living/simple_animal/parrot/proc/attempt_item_theft()
+ //Search for item to steal
+ search_for_item()
+ if(parrot_interest)
+ emote("me", EMOTE_VISIBLE, "looks in [parrot_interest]'s direction and takes flight.")
+ parrot_state = PARROT_SWOOP | PARROT_STEAL
+ icon_state = icon_living
+
/mob/living/simple_animal/parrot/proc/search_for_item()
var/item
for(var/atom/movable/AM in view(src))
diff --git a/code/modules/mob/living/stamina_buffer.dm b/code/modules/mob/living/stamina_buffer.dm
new file mode 100644
index 0000000000..a410df582b
--- /dev/null
+++ b/code/modules/mob/living/stamina_buffer.dm
@@ -0,0 +1,56 @@
+/**
+ * Attempts to use an amount of stamina from our stamina buffer.
+ * Does not use anything if we don't have enough.
+ *
+ * Returns TRUE or FALSE based on if we have it.
+ */
+/mob/living/proc/UseStaminaBuffer(amount, warn = FALSE, considered_action = TRUE)
+ if(!(combat_flags & COMBAT_FLAG_STAMINA_BUFFER))
+ return TRUE
+ if(stamina_buffer < amount)
+ var/stamina_health = getStaminaLoss()
+ if(stamina_health > STAMINA_NO_OVERDRAW_THRESHOLD)
+ if(warn)
+ to_chat(src, "You do not have enough action stamina to do that!")
+ return FALSE
+ adjustStaminaLoss(amount * CONFIG_GET(number/stamina_combat/overdraw_penalty_factor))
+ else
+ stamina_buffer -= amount
+ if(considered_action)
+ stamina_buffer_last_use = world.time
+ UpdateStaminaBuffer()
+ return TRUE
+
+/**
+ * Updates our stamina buffer amount.
+ */
+/mob/living/proc/UpdateStaminaBuffer(updating_hud = TRUE)
+ var/time = world.time - stamina_buffer_regen_last
+ CONFIG_CACHE_ENTRY_AND_FETCH_VALUE(number/stamina_combat/buffer_max, buffer_max)
+ stamina_buffer_regen_last = world.time
+ if((stamina_buffer >= buffer_max) || !(combat_flags & COMBAT_FLAG_STAMINA_BUFFER))
+ stamina_buffer = buffer_max
+ return
+ else if(!time || HAS_TRAIT(src, TRAIT_NO_STAMINA_BUFFER_REGENERATION))
+ return
+ CONFIG_CACHE_ENTRY_AND_FETCH_VALUE(number/stamina_combat/out_of_combat_timer, out_of_combat_timer)
+ CONFIG_CACHE_ENTRY_AND_FETCH_VALUE(number/stamina_combat/base_regeneration, base_regeneration)
+ CONFIG_CACHE_ENTRY_AND_FETCH_VALUE(number/stamina_combat/combat_regeneration, combat_regeneration)
+ CONFIG_CACHE_ENTRY_AND_FETCH_VALUE(number/stamina_combat/percent_regeneration_out_of_combat, percent_regeneration_out_of_combat)
+ CONFIG_CACHE_ENTRY_AND_FETCH_VALUE(number/stamina_combat/post_action_penalty_delay, post_action_penalty_delay)
+ CONFIG_CACHE_ENTRY_AND_FETCH_VALUE(number/stamina_combat/post_action_penalty_factor, post_action_penalty_factor)
+ var/base_regen = (SEND_SIGNAL(src, COMSIG_COMBAT_MODE_CHECK, COMBAT_MODE_INACTIVE))? base_regeneration : combat_regeneration
+ var/time_since_last_action = world.time - stamina_buffer_last_use
+ var/action_penalty = ((time_since_last_action) < (post_action_penalty_delay * 10))? post_action_penalty_factor : 1
+ var/out_of_combat_bonus = (time_since_last_action < (out_of_combat_timer * 10))? 0 : ((buffer_max * percent_regeneration_out_of_combat * 0.01))
+ var/regen = ((base_regen * action_penalty) + out_of_combat_bonus) * time * 0.1 * stamina_buffer_regen_mod
+ stamina_buffer += min((buffer_max - stamina_buffer), regen)
+ if(updating_hud)
+ hud_used?.staminabuffer?.mark_dirty()
+
+/**
+ * Boosts our stamina buffer by this much.
+ */
+/mob/living/proc/RechargeStaminaBuffer(amount)
+ stamina_buffer += abs(amount)
+ UpdateStaminaBuffer()
diff --git a/code/modules/movespeed/_movespeed_modifier.dm b/code/modules/movespeed/_movespeed_modifier.dm
index 3bc4463531..9c8036bd55 100644
--- a/code/modules/movespeed/_movespeed_modifier.dm
+++ b/code/modules/movespeed/_movespeed_modifier.dm
@@ -38,6 +38,12 @@ Key procs
/// Multiplicative slowdown
var/multiplicative_slowdown = 0
+ /// Next two variables depend on this: Should we do advanced calculations?
+ var/complex_calculation = FALSE
+ /// Absolute max tiles we can boost to
+ var/absolute_max_tiles_per_second
+ /// Max tiles per second we can boost
+ var/max_tiles_per_second_boost
/// Movetypes this applies to
var/movetypes = ALL
@@ -53,6 +59,16 @@ Key procs
if(!id)
id = "[type]" //We turn the path into a string.
+/**
+ * Returns new multiplicative movespeed after modification.
+ */
+/datum/movespeed_modifier/proc/apply_multiplicative(existing, mob/target)
+ if(!complex_calculation || (multiplicative_slowdown > 0)) // we aren't limiting how much things can slowdown.. yet.
+ return existing + multiplicative_slowdown
+ var/current_tiles = 10 / max(existing, world.tick_lag)
+ var/minimum_speed = 10 / min(current_tiles + max_tiles_per_second_boost, max(current_tiles, absolute_max_tiles_per_second))
+ return max(minimum_speed, existing + multiplicative_slowdown)
+
GLOBAL_LIST_EMPTY(movespeed_modification_cache)
/// Grabs a STATIC MODIFIER datum from cache. YOU MUST NEVER EDIT THESE DATUMS, OR IT WILL AFFECT ANYTHING ELSE USING IT TOO!
@@ -171,13 +187,15 @@ GLOBAL_LIST_EMPTY(movespeed_modification_cache)
/// Set or update the global movespeed config on a mob
/mob/proc/update_config_movespeed()
add_or_update_variable_movespeed_modifier(/datum/movespeed_modifier/mob_config_speedmod, multiplicative_slowdown = get_config_multiplicative_speed())
+ add_or_update_variable_movespeed_modifier(/datum/movespeed_modifier/mob_config_speedmod_floating, multiplicative_slowdown = get_config_multiplicative_speed(TRUE))
/// Get the global config movespeed of a mob by type
-/mob/proc/get_config_multiplicative_speed()
- if(!islist(GLOB.mob_config_movespeed_type_lookup) || !GLOB.mob_config_movespeed_type_lookup[type])
+/mob/proc/get_config_multiplicative_speed(floating = FALSE)
+ var/list/read = floating? GLOB.mob_config_movespeed_type_lookup_floating : GLOB.mob_config_movespeed_type_lookup
+ if(!islist(read) || !read[type])
return 0
else
- return GLOB.mob_config_movespeed_type_lookup[type]
+ return read[type]
/// Go through the list of movespeed modifiers and calculate a final movespeed. ANY ADD/REMOVE DONE IN UPDATE_MOVESPEED MUST HAVE THE UPDATE ARGUMENT SET AS FALSE!
/mob/proc/update_movespeed()
@@ -198,7 +216,7 @@ GLOBAL_LIST_EMPTY(movespeed_modification_cache)
conflict_tracker[conflict] = amt
else
continue
- . += amt
+ . = M.apply_multiplicative(., src)
var/old = cached_multiplicative_slowdown // CITAEDL EDIT - To make things a bit less jarring, when in situations where
// your delay decreases, "give" the delay back to the client
cached_multiplicative_slowdown = .
@@ -220,6 +238,13 @@ GLOBAL_LIST_EMPTY(movespeed_modification_cache)
var/datum/movespeed_modifier/M = movespeed_modification[id]
. += M.multiplicative_slowdown
+/**
+ * Gets the movespeed modifier datum of a modifier on a mob. Returns null if not found.
+ * DANGER: IT IS UP TO THE PERSON USING THIS TO MAKE SURE THE MODIFIER IS NOT MODIFIED IF IT HAPPENS TO BE GLOBAL/CACHED.
+ */
+/mob/proc/get_movespeed_modifier_datum(id)
+ return movespeed_modification[id]
+
/// Checks if a move speed modifier is valid and not missing any data
/proc/movespeed_data_null_check(datum/movespeed_modifier/M) //Determines if a data list is not meaningful and should be discarded.
. = TRUE
diff --git a/code/modules/movespeed/modifiers/items.dm b/code/modules/movespeed/modifiers/items.dm
index 94dc2a1553..a8510ecd86 100644
--- a/code/modules/movespeed/modifiers/items.dm
+++ b/code/modules/movespeed/modifiers/items.dm
@@ -1,12 +1,13 @@
/datum/movespeed_modifier/jetpack
conflicts_with = MOVE_CONFLICT_JETPACK
movetypes = FLOATING
+ multiplicative_slowdown = -1
/datum/movespeed_modifier/jetpack/cybernetic
- multiplicative_slowdown = -0.5
+ multiplicative_slowdown = -1.25
/datum/movespeed_modifier/jetpack/fullspeed
- multiplicative_slowdown = -2
+ multiplicative_slowdown = -1.5
/datum/movespeed_modifier/die_of_fate
multiplicative_slowdown = 1
diff --git a/code/modules/movespeed/modifiers/mobs.dm b/code/modules/movespeed/modifiers/mobs.dm
index d17767bb1f..f6cf84eb83 100644
--- a/code/modules/movespeed/modifiers/mobs.dm
+++ b/code/modules/movespeed/modifiers/mobs.dm
@@ -111,6 +111,12 @@
/datum/movespeed_modifier/mob_config_speedmod
variable = TRUE
+ blacklisted_movetypes = FLOATING
+ flags = IGNORE_NOSLOW
+
+/datum/movespeed_modifier/mob_config_speedmod_floating
+ variable = TRUE
+ movetypes = FLOATING
flags = IGNORE_NOSLOW
/datum/movespeed_modifier/liver_cirrhosis
@@ -120,3 +126,30 @@
/datum/movespeed_modifier/active_block
variable = TRUE
flags = IGNORE_NOSLOW
+
+/datum/movespeed_modifier/sprinting
+ flags = IGNORE_NOSLOW
+ blacklisted_movetypes = FLOATING
+ priority = 100
+
+/// for speed reasons this is sorta copypasty.
+/datum/movespeed_modifier/sprinting/apply_multiplicative(existing, mob/target)
+ . = existing
+ if(target.m_intent != MOVE_INTENT_RUN)
+ return
+ if(isliving(target))
+ var/mob/living/L = target
+ if(!(L.mobility_flags & MOBILITY_STAND))
+ return
+ var/static/datum/config_entry/number/movedelay/sprint_max_tiles_increase/SMTI
+ if(!SMTI)
+ SMTI = CONFIG_GET_ENTRY(number/movedelay/sprint_max_tiles_increase)
+ var/static/datum/config_entry/number/movedelay/sprint_speed_increase/SSI
+ if(!SSI)
+ SSI = CONFIG_GET_ENTRY(number/movedelay/sprint_speed_increase)
+ var/static/datum/config_entry/number/movedelay/sprint_absolute_max_tiles/SAMT
+ if(!SAMT)
+ SAMT = CONFIG_GET_ENTRY(number/movedelay/sprint_absolute_max_tiles)
+ var/current_tiles = 10 / max(existing, world.tick_lag)
+ var/minimum_speed = 10 / min(max(SAMT.config_entry_value, current_tiles), current_tiles + SMTI.config_entry_value)
+ . = min(., max(minimum_speed, existing - SSI.config_entry_value))
diff --git a/code/modules/plumbing/plumbers/autohydro.dm b/code/modules/plumbing/plumbers/autohydro.dm
index 20b8e6012a..9c358d8d6e 100644
--- a/code/modules/plumbing/plumbers/autohydro.dm
+++ b/code/modules/plumbing/plumbers/autohydro.dm
@@ -30,20 +30,8 @@
. = ..()
START_PROCESSING(SSobj, src)
create_reagents(100 , AMOUNT_VISIBLE)
- AddComponent(/datum/component/plumbing/simple_demand)
-
-/obj/machinery/hydroponics/constructable/automagic/ComponentInitialize()
- . = ..()
- AddComponent(/datum/component/simple_rotation, ROTATION_ALTCLICK | ROTATION_CLOCKWISE | ROTATION_COUNTERCLOCKWISE | ROTATION_VERBS, null, CALLBACK(src, .proc/can_be_rotated))
-
-
-/obj/machinery/hydroponics/constructable/proc/can_be_rotated(mob/user, rotation_type)
- return !anchored
/obj/machinery/hydroponics/constructable/automagic/process()
- if(reagents)
- reagents.add_reagent(reagents)
- reagents.clear_reagents()
if(dead)
dead = 0
qdel(myseed)
diff --git a/code/modules/power/rtg.dm b/code/modules/power/rtg.dm
index b558031fab..c19927e84c 100644
--- a/code/modules/power/rtg.dm
+++ b/code/modules/power/rtg.dm
@@ -53,6 +53,33 @@
power_gen = 1250 // 2500 on T1, 10000 on T4.
circuit = /obj/item/circuitboard/machine/rtg/advanced
+/obj/machinery/power/rtg/advanced/fullupgrade //fully ugpraded stock parts
+ desc = "An advanced RTG capable of moderating isotope decay, increasing power output but reducing lifetime. It uses plasma-fueled radiation collectors to increase output even further. This model is fully upgraded with the latest tech available in this quadrant."
+
+/obj/machinery/power/rtg/advanced/fullupgrade/Initialize()
+ . = ..()
+ //This looks terrifying. And apparently instancing vars and modifying the amount variable causes runtime errors. Guess we're sticking to copy pasta, thanks, byond.
+ component_parts = list()
+ component_parts += new /obj/item/circuitboard/machine/rtg/advanced(null)
+ component_parts += new /obj/item/stack/sheet/mineral/uranium(null)
+ component_parts += new /obj/item/stack/sheet/mineral/uranium(null)
+ component_parts += new /obj/item/stack/sheet/mineral/uranium(null)
+ component_parts += new /obj/item/stack/sheet/mineral/uranium(null)
+ component_parts += new /obj/item/stack/sheet/mineral/uranium(null)
+ component_parts += new /obj/item/stack/sheet/mineral/uranium(null)
+ component_parts += new /obj/item/stack/sheet/mineral/uranium(null)
+ component_parts += new /obj/item/stack/sheet/mineral/uranium(null)
+ component_parts += new /obj/item/stack/sheet/mineral/uranium(null)
+ component_parts += new /obj/item/stack/sheet/mineral/uranium(null)
+ component_parts += new /obj/item/stack/sheet/mineral/plasma(null)
+ component_parts += new /obj/item/stack/sheet/mineral/plasma(null)
+ component_parts += new /obj/item/stack/sheet/mineral/plasma(null)
+ component_parts += new /obj/item/stack/sheet/mineral/plasma(null)
+ component_parts += new /obj/item/stack/sheet/mineral/plasma(null)
+ component_parts += new /obj/item/stock_parts/capacitor/quadratic(null)
+ component_parts += new /obj/item/stock_parts/micro_laser/quadultra(null)
+ RefreshParts()
+
// Void Core, power source for Abductor ships and bases.
// Provides a lot of power, but tends to explode when mistreated.
diff --git a/code/modules/power/supermatter/supermatter.dm b/code/modules/power/supermatter/supermatter.dm
index e3fdbd3654..f385b640aa 100644
--- a/code/modules/power/supermatter/supermatter.dm
+++ b/code/modules/power/supermatter/supermatter.dm
@@ -928,8 +928,15 @@ GLOBAL_DATUM(main_supermatter_engine, /obj/machinery/power/supermatter_crystal)
var/mob/living/user = AM
if(user.status_flags & GODMODE)
return
- message_admins("[src] has consumed [key_name_admin(user)] [ADMIN_JMP(src)].")
- investigate_log("has consumed [key_name(user)].", INVESTIGATE_SUPERMATTER)
+ var/add
+ if(user.mind?.assigned_role == "Clown")
+ var/denergy = rand(-1000, 1000)
+ var/ddamage = rand(-150, clamp(150, 0, (explosion_point - damage) + 150))
+ power += denergy
+ damage += ddamage
+ add = ", adding [denergy] energy and [ddamage] damage to the crystal"
+ message_admins("[src] has consumed [key_name_admin(user)] [ADMIN_JMP(src)][add].")
+ investigate_log("has consumed [key_name(user)][add].", INVESTIGATE_SUPERMATTER)
user.dust(force = TRUE)
if(power_changes)
matter_power += 200
diff --git a/code/modules/projectiles/gun.dm b/code/modules/projectiles/gun.dm
index 1369115b0d..1c3a0d230f 100644
--- a/code/modules/projectiles/gun.dm
+++ b/code/modules/projectiles/gun.dm
@@ -154,8 +154,8 @@
shake_camera(user, recoil + 1, recoil)
if(stam_cost) //CIT CHANGE - makes gun recoil cause staminaloss
- var/safe_cost = clamp(stam_cost, 0, STAMINA_NEAR_CRIT - user.getStaminaLoss())*(firing && burst_size >= 2 ? 1/burst_size : 1)
- user.adjustStaminaLossBuffered(safe_cost) //CIT CHANGE - ditto
+ var/safe_cost = clamp(stam_cost, 0, user.stamina_buffer)*(firing && burst_size >= 2 ? 1/burst_size : 1)
+ user.UseStaminaBuffer(safe_cost)
if(suppressed)
playsound(user, fire_sound, 10, 1)
@@ -590,6 +590,9 @@
update_icon()
/obj/item/gun/proc/getinaccuracy(mob/living/user, bonus_spread, stamloss)
+ return 0 // Replacement TBD: Exponential curved aim instability system.
+
+/*
if(inaccuracy_modifier == 0)
return bonus_spread
var/base_inaccuracy = weapon_weight * 25 * inaccuracy_modifier
@@ -612,6 +615,7 @@
if(mult < 0) //accurate weapons should provide a proper bonus with negative inaccuracy. the opposite is true too.
mult *= 1/inaccuracy_modifier
return max(bonus_spread + (base_inaccuracy * mult), 0) //no negative spread.
+*/
/obj/item/gun/proc/getstamcost(mob/living/carbon/user)
. = recoil
diff --git a/code/modules/projectiles/guns/ballistic/shotgun.dm b/code/modules/projectiles/guns/ballistic/shotgun.dm
index ecf6e538b8..7b3ac36ef6 100644
--- a/code/modules/projectiles/guns/ballistic/shotgun.dm
+++ b/code/modules/projectiles/guns/ballistic/shotgun.dm
@@ -44,10 +44,9 @@
if(HAS_TRAIT(user, TRAIT_FAST_PUMP))
recentpump = world.time + 2
else
+ if(!user.UseStaminaBuffer(2, warn = TRUE))
+ return
recentpump = world.time + 10
- if(istype(user))//CIT CHANGE - makes pumping shotguns cost a lil bit of stamina.
- user.adjustStaminaLossBuffered(2) //CIT CHANGE - DITTO. make this scale inversely to the strength stat when stats/skills are added
- return
/obj/item/gun/ballistic/shotgun/blow_up(mob/user)
. = 0
diff --git a/code/modules/projectiles/guns/energy/special.dm b/code/modules/projectiles/guns/energy/special.dm
index 19ca42022d..cd91a7670d 100644
--- a/code/modules/projectiles/guns/energy/special.dm
+++ b/code/modules/projectiles/guns/energy/special.dm
@@ -233,7 +233,7 @@
p_blue.link_portal(p_orange)
/obj/item/gun/energy/wormhole_projector/proc/create_portal(obj/item/projectile/beam/wormhole/W, turf/target)
- var/obj/effect/portal/P = new /obj/effect/portal(target, src, 300, null, FALSE, null, atmos_link)
+ var/obj/effect/portal/P = new /obj/effect/portal(target, 300, null, FALSE, null, atmos_link)
if(istype(W, /obj/item/projectile/beam/wormhole/orange))
qdel(p_orange)
p_orange = P
diff --git a/code/modules/reagents/chemistry/reagents/alcohol_reagents.dm b/code/modules/reagents/chemistry/reagents/alcohol_reagents.dm
index c2bb678904..86f6460ff7 100644
--- a/code/modules/reagents/chemistry/reagents/alcohol_reagents.dm
+++ b/code/modules/reagents/chemistry/reagents/alcohol_reagents.dm
@@ -2283,7 +2283,7 @@ All effects don't start immediately, but rather get worse over time; the rate is
boozepwr = 50
/datum/reagent/consumable/ethanol/species_drink/on_mob_life(mob/living/carbon/C)
- if(C.dna.species && C.dna.species.species_type == species_required) //species have a species_type variable that refers to one of the drinks
+ if(C.dna.species && C.dna.species.species_category == species_required) //species have a species_category variable that refers to one of the drinks
quality = RACE_DRINK
else
C.adjust_disgust(disgust)
diff --git a/code/modules/research/designs/biogenerator_designs.dm b/code/modules/research/designs/biogenerator_designs.dm
index bdaf39732f..ed12d4945e 100644
--- a/code/modules/research/designs/biogenerator_designs.dm
+++ b/code/modules/research/designs/biogenerator_designs.dm
@@ -181,6 +181,14 @@
build_path = /obj/item/storage/belt/janitor
category = list("initial","Organic Materials")
+/datum/design/plantbelt
+ name = "Botanical Belt"
+ id = "plantbelt"
+ build_type = BIOGENERATOR
+ materials = list(/datum/material/biomass= 300)
+ build_path = /obj/item/storage/belt/plant
+ category = list("initial","Organic Materials")
+
/datum/design/s_holster
name = "Shoulder Holster"
id = "s_holster"
diff --git a/code/modules/research/designs/medical_designs.dm b/code/modules/research/designs/medical_designs.dm
index 7013412e6d..b178f936b7 100644
--- a/code/modules/research/designs/medical_designs.dm
+++ b/code/modules/research/designs/medical_designs.dm
@@ -32,7 +32,7 @@
materials = list(/datum/material/glass = 3000, /datum/material/plasma = 3000, /datum/material/diamond = 250, /datum/material/bluespace = 250)
build_path = /obj/item/reagent_containers/glass/beaker/bluespace
category = list("Medical Designs")
- departmental_flags = DEPARTMENTAL_FLAG_MEDICAL | DEPARTMENTAL_FLAG_SCIENCE
+ departmental_flags = DEPARTMENTAL_FLAG_MEDICAL | DEPARTMENTAL_FLAG_SCIENCE | DEPARTMENTAL_FLAG_SERVICE
/datum/design/noreactbeaker
name = "Cryostasis Beaker"
@@ -973,6 +973,10 @@
departmental_flags = DEPARTMENTAL_FLAG_MEDICAL
+/////////////////////////////////////////
+//////////// Plumbing //////////
+/////////////////////////////////////////
+
/datum/design/acclimator
name = "Plumbing Acclimator"
desc = "A heating and cooling device for pipes!"
@@ -1126,3 +1130,14 @@
build_path = /obj/item/construction/plumbing
category = list("Misc","Medical Designs")
departmental_flags = DEPARTMENTAL_FLAG_MEDICAL | DEPARTMENTAL_FLAG_SCIENCE
+
+/datum/design/rplunger
+ name = "Reinforced Plunger"
+ desc = "A plunger designed for heavy duty clogs."
+ id = "rplunger"
+ build_type = PROTOLATHE
+ materials = list(/datum/material/plasma = 1000, /datum/material/iron = 1000, /datum/material/glass = 1000)
+ construction_time = 15
+ build_path = /obj/item/plunger/reinforced
+ category = list ("Misc","Medical Designs")
+ departmental_flags = DEPARTMENTAL_FLAG_MEDICAL | DEPARTMENTAL_FLAG_SCIENCE | DEPARTMENTAL_FLAG_CARGO
diff --git a/code/modules/research/techweb/nodes/medical_nodes.dm b/code/modules/research/techweb/nodes/medical_nodes.dm
index 150e420c09..7b9b2f2923 100644
--- a/code/modules/research/techweb/nodes/medical_nodes.dm
+++ b/code/modules/research/techweb/nodes/medical_nodes.dm
@@ -38,7 +38,7 @@
display_name = "Advanced Plumbing Technology"
description = "Plumbing RCD."
prereq_ids = list("plumbing", "adv_engi")
- design_ids = list("plumb_rcd", "autohydrotray")
+ design_ids = list("plumb_rcd", "autohydrotray", "rplunger")
research_costs = list(TECHWEB_POINT_TYPE_GENERIC = 2500)
//////////////////////Cybernetics/////////////////////
diff --git a/code/modules/unit_tests/_unit_tests.dm b/code/modules/unit_tests/_unit_tests.dm
index e758a43589..3229304c13 100644
--- a/code/modules/unit_tests/_unit_tests.dm
+++ b/code/modules/unit_tests/_unit_tests.dm
@@ -2,13 +2,41 @@
//Keep this sorted alphabetically
#ifdef UNIT_TESTS
+/// Asserts that a condition is true
+/// If the condition is not true, fails the test
+#define TEST_ASSERT(assertion, reason) if (!(assertion)) { return Fail("Assertion failed: [reason || "No reason"]") }
+
+/// Asserts that the two parameters passed are equal, fails otherwise
+/// Optionally allows an additional message in the case of a failure
+#define TEST_ASSERT_EQUAL(a, b, message) if ((a) != (b)) { return Fail("Expected [isnull(a) ? "null" : a] to be equal to [isnull(b) ? "null" : b].[message ? " [message]" : ""]") }
+
#include "anchored_mobs.dm"
+#include "bespoke_id.dm"
+// #include "binary_insert.dm"
+// #include "card_mismatch.dm" shame we don't have this!
+#include "chain_pull_through_space.dm"
#include "character_saving.dm"
#include "component_tests.dm"
+// #include "confusion.dm"
+// #include "keybinding_init.dm"
+#include "machine_disassembly.dm"
+#include "medical_wounds.dm"
+// #include "metabolizing.dm"
+// #include "outfit_sanity.dm"
+// #include "plantgrowth_tests.dm"
+// #include "quick_swap_sanity.dm" - we don't have quick swap yet
#include "reagent_id_typos.dm"
#include "reagent_recipe_collisions.dm"
+#include "resist.dm"
+// #include "say.dm" //no saymods, someone update saycode please.
+// #include "siunit.dm"
#include "spawn_humans.dm"
+// #include "species_whitelists.dm"
#include "subsystem_init.dm"
+#include "surgeries.dm"
#include "timer_sanity.dm"
#include "unit_test.dm"
+
+#undef TEST_ASSERT
+#undef TEST_ASSERT_EQUAL
#endif
diff --git a/code/modules/unit_tests/anchored_mobs.dm b/code/modules/unit_tests/anchored_mobs.dm
index 5324179bb7..103b97e7a9 100644
--- a/code/modules/unit_tests/anchored_mobs.dm
+++ b/code/modules/unit_tests/anchored_mobs.dm
@@ -6,4 +6,4 @@
L += "[i]"
if(!L.len)
return //passed!
- Fail("The following mobs are defined as anchored. This is incompatible with the new move force/resist system and needs to be revised.: [L.Join(" ")]")
\ No newline at end of file
+ Fail("The following mobs are defined as anchored. This is incompatible with the new move force/resist system and needs to be revised.: [L.Join(" ")]")
diff --git a/code/modules/unit_tests/bespoke_id.dm b/code/modules/unit_tests/bespoke_id.dm
new file mode 100644
index 0000000000..06676c626c
--- /dev/null
+++ b/code/modules/unit_tests/bespoke_id.dm
@@ -0,0 +1,8 @@
+/datum/unit_test/bespoke_id/Run()
+ var/datum/element/base = /datum/element
+ var/base_index = initial(base.id_arg_index)
+
+ for(var/i in subtypesof(/datum/element))
+ var/datum/element/faketype = i
+ if((initial(faketype.element_flags) & ELEMENT_BESPOKE) && initial(faketype.id_arg_index) == base_index)
+ Fail("A bespoke element was not configured with a proper id_arg_index: [faketype]")
diff --git a/code/modules/unit_tests/binary_insert.dm b/code/modules/unit_tests/binary_insert.dm
new file mode 100644
index 0000000000..ac7f58208e
--- /dev/null
+++ b/code/modules/unit_tests/binary_insert.dm
@@ -0,0 +1,26 @@
+/// A test to ensure the sanity of BINARY_INSERT
+/datum/unit_test/binary_insert/Run()
+ var/list/datum/binary_insert_node/nodes = list()
+
+ var/datum/binary_insert_node/node_a = new /datum/binary_insert_node(10)
+ BINARY_INSERT(node_a, nodes, /datum/binary_insert_node, node_a, x, COMPARE_KEY)
+ TEST_ASSERT_EQUAL(nodes.len, 1, "List should have one node")
+
+ var/datum/binary_insert_node/node_b = new /datum/binary_insert_node(5)
+ BINARY_INSERT(node_b, nodes, /datum/binary_insert_node, node_b, x, COMPARE_KEY)
+ TEST_ASSERT_EQUAL(nodes.len, 2, "List should have two nodes")
+ TEST_ASSERT_EQUAL(nodes[1].x, 5, "The first node should be the one with 5")
+ TEST_ASSERT_EQUAL(nodes[2].x, 10, "The second node should be the one with 10")
+
+ var/datum/binary_insert_node/node_c = new /datum/binary_insert_node(15)
+ BINARY_INSERT(node_c, nodes, /datum/binary_insert_node, node_c, x, COMPARE_KEY)
+ TEST_ASSERT_EQUAL(nodes.len, 3, "List should have three nodes")
+ TEST_ASSERT_EQUAL(nodes[1].x, 5, "The first node should be the one with 5")
+ TEST_ASSERT_EQUAL(nodes[2].x, 10, "The second node should be the one with 10")
+ TEST_ASSERT_EQUAL(nodes[3].x, 15, "The third node should be the one with 15")
+
+/datum/binary_insert_node
+ var/x
+
+/datum/binary_insert_node/New(_x)
+ x = _x
diff --git a/code/modules/unit_tests/chain_pull_through_space.dm b/code/modules/unit_tests/chain_pull_through_space.dm
new file mode 100644
index 0000000000..ffdd1bf7c9
--- /dev/null
+++ b/code/modules/unit_tests/chain_pull_through_space.dm
@@ -0,0 +1,62 @@
+/datum/unit_test/chain_pull_through_space
+ var/turf/open/space/space_tile
+ var/turf/claimed_tile
+ var/mob/living/carbon/human/alice
+ var/mob/living/carbon/human/bob
+ var/mob/living/carbon/human/charlie
+
+/datum/unit_test/chain_pull_through_space/New()
+ ..()
+
+ // Create a space tile that goes to another z-level
+ claimed_tile = run_loc_bottom_left
+
+ space_tile = new(locate(run_loc_bottom_left.x, run_loc_bottom_left.y, run_loc_bottom_left.z))
+ space_tile.destination_x = 100
+ space_tile.destination_y = 100
+ space_tile.destination_z = 5
+
+ // Create our list of humans, all adjacent to one another
+ alice = new(locate(run_loc_bottom_left.x + 2, run_loc_bottom_left.y, run_loc_bottom_left.z))
+ alice.name = "Alice"
+
+ bob = new(locate(run_loc_bottom_left.x + 3, run_loc_bottom_left.y, run_loc_bottom_left.z))
+ bob.name = "Bob"
+
+ charlie = new(locate(run_loc_bottom_left.x + 4, run_loc_bottom_left.y, run_loc_bottom_left.z))
+ charlie.name = "Charlie"
+
+/datum/unit_test/chain_pull_through_space/Destroy()
+ space_tile.copyTurf(claimed_tile)
+ qdel(alice)
+ qdel(bob)
+ qdel(charlie)
+ return ..()
+
+/datum/unit_test/chain_pull_through_space/Run()
+ // Alice pulls Bob, who pulls Charlie
+ // Normally, when Alice moves forward, the rest follow
+ alice.start_pulling(bob)
+ bob.start_pulling(charlie)
+
+ // Walk normally to the left, make sure we're still a chain
+ alice.Move(locate(run_loc_bottom_left.x + 1, run_loc_bottom_left.y, run_loc_bottom_left.z))
+ if (bob.x != run_loc_bottom_left.x + 2)
+ return Fail("During normal move, Bob was not at the correct x ([bob.x])")
+ if (charlie.x != run_loc_bottom_left.x + 3)
+ return Fail("During normal move, Charlie was not at the correct x ([charlie.x])")
+
+ // We're going through the space turf now that should teleport us
+ alice.Move(run_loc_bottom_left)
+ if (alice.z != space_tile.destination_z)
+ return Fail("Alice did not teleport to the destination z-level. Current location: ([alice.x], [alice.y], [alice.z])")
+
+ if (bob.z != space_tile.destination_z)
+ return Fail("Bob did not teleport to the destination z-level. Current location: ([bob.x], [bob.y], [bob.z])")
+ if (!bob.Adjacent(alice))
+ return Fail("Bob is not adjacent to Alice. Bob is at [bob.x], Alice is at [alice.x]")
+
+ if (charlie.z != space_tile.destination_z)
+ return Fail("Charlie did not teleport to the destination z-level. Current location: ([charlie.x], [charlie.y], [charlie.z])")
+ if (!charlie.Adjacent(bob))
+ return Fail("Charlie is not adjacent to Bob. Charlie is at [charlie.x], Bob is at [bob.x]")
diff --git a/code/modules/unit_tests/component_tests.dm b/code/modules/unit_tests/component_tests.dm
index 409d7f4322..0099d7508c 100644
--- a/code/modules/unit_tests/component_tests.dm
+++ b/code/modules/unit_tests/component_tests.dm
@@ -9,4 +9,4 @@
if(dupe_type && !ispath(dupe_type))
bad_dts += t
if(length(bad_dms) || length(bad_dts))
- Fail("Components with invalid dupe modes: ([bad_dms.Join(",")]) ||| Components with invalid dupe types: ([bad_dts.Join(",")])")
\ No newline at end of file
+ Fail("Components with invalid dupe modes: ([bad_dms.Join(",")]) ||| Components with invalid dupe types: ([bad_dts.Join(",")])")
diff --git a/code/modules/unit_tests/machine_disassembly.dm b/code/modules/unit_tests/machine_disassembly.dm
new file mode 100644
index 0000000000..bcc769bcf2
--- /dev/null
+++ b/code/modules/unit_tests/machine_disassembly.dm
@@ -0,0 +1,13 @@
+/// Ensures that when disassembling a machine, all the parts are given back
+/datum/unit_test/machine_disassembly/Run()
+ var/obj/machinery/freezer = allocate(/obj/machinery/atmospherics/components/unary/thermomachine/freezer)
+
+ var/turf/freezer_location = freezer.loc
+ freezer_location.ChangeTurf(/turf/open/floor/plasteel)
+ freezer.deconstruct()
+
+ // Check that the components are created
+ TEST_ASSERT(locate(/obj/item/stock_parts/micro_laser) in freezer_location, "Couldn't find micro-laser when disassembling freezer")
+
+ // Check that the circuit board itself is created
+ TEST_ASSERT(locate(/obj/item/circuitboard/machine/thermomachine/freezer) in freezer_location, "Couldn't find the circuit board when disassembling freezer")
diff --git a/code/modules/unit_tests/medical_wounds.dm b/code/modules/unit_tests/medical_wounds.dm
new file mode 100644
index 0000000000..75c08931f1
--- /dev/null
+++ b/code/modules/unit_tests/medical_wounds.dm
@@ -0,0 +1,87 @@
+/// This test is used to make sure a flesh-and-bone base human can suffer all the types of wounds, and that suffering more severe wounds removes and replaces the lesser wound. Also tests that [/mob/living/carbon/proc/fully_heal] removes all wounds
+/datum/unit_test/test_human_base/Run()
+ var/mob/living/carbon/human/victim = allocate(/mob/living/carbon/human)
+
+ /// the limbs have no wound resistance like the chest and head do, so let's go with the r_arm
+ var/obj/item/bodypart/tested_part = victim.get_bodypart(BODY_ZONE_R_ARM)
+ /// In order of the wound types we're trying to inflict, what sharpness do we need to deal them?
+ var/list/sharps = list(SHARP_NONE, SHARP_EDGED, SHARP_POINTY, SHARP_NONE)
+ /// Since burn wounds need burn damage, duh
+ var/list/dam_types = list(BRUTE, BRUTE, BRUTE, BURN)
+
+ var/i = 1
+ var/list/iter_test_wound_list
+
+ for(iter_test_wound_list in list(list(/datum/wound/blunt/moderate, /datum/wound/blunt/severe, /datum/wound/blunt/critical),\
+ list(/datum/wound/slash/moderate, /datum/wound/slash/severe, /datum/wound/slash/critical),\
+ list(/datum/wound/pierce/moderate, /datum/wound/pierce/severe, /datum/wound/pierce/critical),\
+ list(/datum/wound/burn/moderate, /datum/wound/burn/severe, /datum/wound/burn/critical)))
+
+ TEST_ASSERT_EQUAL(length(victim.all_wounds), 0, "Patient is somehow wounded before test")
+ var/datum/wound/iter_test_wound
+ var/threshold_penalty = 0
+
+ for(iter_test_wound in iter_test_wound_list)
+ var/threshold = initial(iter_test_wound.threshold_minimum) - threshold_penalty // just enough to guarantee the next tier of wound, given the existing wound threshold penalty
+ if(dam_types[i] == BRUTE)
+ tested_part.receive_damage(WOUND_MINIMUM_DAMAGE, 0, wound_bonus = threshold, sharpness=sharps[i])
+ else if(dam_types[i] == BURN)
+ tested_part.receive_damage(0, WOUND_MINIMUM_DAMAGE, wound_bonus = threshold, sharpness=sharps[i])
+
+ TEST_ASSERT(length(victim.all_wounds), "Patient has no wounds when one wound is expected. Severity: [initial(iter_test_wound.severity)]")
+ TEST_ASSERT_EQUAL(length(victim.all_wounds), 1, "Patient has more than one wound when only one is expected. Severity: [initial(iter_test_wound.severity)]")
+ var/datum/wound/actual_wound = victim.all_wounds[1]
+ TEST_ASSERT_EQUAL(actual_wound.type, iter_test_wound, "Patient has wound of incorrect severity. Expected: [initial(iter_test_wound.name)] Got: [actual_wound]")
+ threshold_penalty = actual_wound.threshold_penalty
+ i++
+ victim.fully_heal(TRUE) // should clear all wounds between types
+
+
+/// This test is used for making sure species with bones but no flesh (skeletons, plasmamen) can only suffer BONE_WOUNDS, and nothing tagged with FLESH_WOUND (it's possible to require both)
+/datum/unit_test/test_human_bone/Run()
+ var/mob/living/carbon/human/victim = allocate(/mob/living/carbon/human)
+
+ /// the limbs have no wound resistance like the chest and head do, so let's go with the r_arm
+ var/obj/item/bodypart/tested_part = victim.get_bodypart(BODY_ZONE_R_ARM)
+ /// In order of the wound types we're trying to inflict, what sharpness do we need to deal them?
+ var/list/sharps = list(SHARP_NONE, SHARP_EDGED, SHARP_POINTY, SHARP_NONE)
+ /// Since burn wounds need burn damage, duh
+ var/list/dam_types = list(BRUTE, BRUTE, BRUTE, BURN)
+
+ var/i = 1
+ var/list/iter_test_wound_list
+ victim.dna.species.species_traits &= HAS_FLESH // take away the base human's flesh (ouchie!) ((not actually ouchie, this just affects their wounds and dismemberment handling))
+
+ for(iter_test_wound_list in list(list(/datum/wound/blunt/moderate, /datum/wound/blunt/severe, /datum/wound/blunt/critical),\
+ list(/datum/wound/slash/moderate, /datum/wound/slash/severe, /datum/wound/slash/critical),\
+ list(/datum/wound/pierce/moderate, /datum/wound/pierce/severe, /datum/wound/pierce/critical),\
+ list(/datum/wound/burn/moderate, /datum/wound/burn/severe, /datum/wound/burn/critical)))
+
+ TEST_ASSERT_EQUAL(length(victim.all_wounds), 0, "Patient is somehow wounded before test")
+ var/datum/wound/iter_test_wound
+ var/threshold_penalty = 0
+
+ for(iter_test_wound in iter_test_wound_list)
+ var/threshold = initial(iter_test_wound.threshold_minimum) - threshold_penalty // just enough to guarantee the next tier of wound, given the existing wound threshold penalty
+ if(dam_types[i] == BRUTE)
+ tested_part.receive_damage(WOUND_MINIMUM_DAMAGE, 0, wound_bonus = threshold, sharpness=sharps[i])
+ else if(dam_types[i] == BURN)
+ tested_part.receive_damage(0, WOUND_MINIMUM_DAMAGE, wound_bonus = threshold, sharpness=sharps[i])
+
+ // so if we just tried to deal a flesh wound, make sure we didn't actually suffer it. We may have suffered a bone wound instead, but we just want to make sure we don't have a flesh wound
+ if(initial(iter_test_wound.wound_flags) & FLESH_WOUND)
+ if(!length(victim.all_wounds)) // not having a wound is good news
+ continue
+ else // we have to check that it's actually a bone wound and not the intended wound type
+ TEST_ASSERT_EQUAL(length(victim.all_wounds), 1, "Patient has more than one wound when only one is expected. Severity: [initial(iter_test_wound.severity)]")
+ var/datum/wound/actual_wound = victim.all_wounds[1]
+ TEST_ASSERT((actual_wound.wound_flags & ~FLESH_WOUND), "Patient has flesh wound despite no HAS_FLESH flag, expected either no wound or bone wound. Offending wound: [actual_wound]")
+ threshold_penalty = actual_wound.threshold_penalty
+ else // otherwise if it's a bone wound, check that we have it per usual
+ TEST_ASSERT(length(victim.all_wounds), "Patient has no wounds when one wound is expected. Severity: [initial(iter_test_wound.severity)]")
+ TEST_ASSERT_EQUAL(length(victim.all_wounds), 1, "Patient has more than one wound when only one is expected. Severity: [initial(iter_test_wound.severity)]")
+ var/datum/wound/actual_wound = victim.all_wounds[1]
+ TEST_ASSERT_EQUAL(actual_wound.type, iter_test_wound, "Patient has wound of incorrect severity. Expected: [initial(iter_test_wound.name)] Got: [actual_wound]")
+ threshold_penalty = actual_wound.threshold_penalty
+ i++
+ victim.fully_heal(TRUE) // should clear all wounds between types
diff --git a/code/modules/unit_tests/metabolizing.dm b/code/modules/unit_tests/metabolizing.dm
new file mode 100644
index 0000000000..895762c0ec
--- /dev/null
+++ b/code/modules/unit_tests/metabolizing.dm
@@ -0,0 +1,19 @@
+/datum/unit_test/metabolization/Run()
+ // Pause natural mob life so it can be handled entirely by the test
+ SSmobs.pause()
+
+ var/mob/living/carbon/human/human = allocate(/mob/living/carbon/human)
+ var/mob/living/carbon/monkey/monkey = allocate(/mob/living/carbon/monkey)
+
+ for (var/reagent_type in subtypesof(/datum/reagent))
+ test_reagent(human, reagent_type)
+ test_reagent(monkey, reagent_type)
+
+/datum/unit_test/metabolization/proc/test_reagent(mob/living/carbon/C, reagent_type)
+ C.reagents.add_reagent(reagent_type, 10)
+ C.reagents.metabolize(C, can_overdose = TRUE)
+ C.reagents.clear_reagents()
+
+/datum/unit_test/metabolization/Destroy()
+ SSmobs.ignite()
+ return ..()
diff --git a/code/modules/unit_tests/outfit_sanity.dm b/code/modules/unit_tests/outfit_sanity.dm
new file mode 100644
index 0000000000..235820f9e9
--- /dev/null
+++ b/code/modules/unit_tests/outfit_sanity.dm
@@ -0,0 +1,50 @@
+#define CHECK_OUTFIT_SLOT(outfit_key, slot_name) if (outfit.##outfit_key) { \
+ H.equip_to_slot_or_del(new outfit.##outfit_key(H), ##slot_name, TRUE); \
+ /* We don't check the result of equip_to_slot_or_del because it returns false for random jumpsuits, as they delete themselves on init */ \
+ if (!H.get_item_by_slot(##slot_name)) { \
+ Fail("[outfit.name]'s [#outfit_key] is invalid!"); \
+ } \
+}
+
+/datum/unit_test/outfit_sanity/Run()
+ var/mob/living/carbon/human/H = allocate(/mob/living/carbon/human)
+
+ for (var/outfit_type in subtypesof(/datum/outfit))
+ // Only make one human and keep undressing it because it's much faster
+ for (var/obj/item/I in H.get_equipped_items(include_pockets = TRUE))
+ qdel(I)
+
+ var/datum/outfit/outfit = new outfit_type
+ outfit.pre_equip(H, TRUE)
+
+ CHECK_OUTFIT_SLOT(uniform, ITEM_SLOT_ICLOTHING)
+ CHECK_OUTFIT_SLOT(suit, ITEM_SLOT_OCLOTHING)
+ CHECK_OUTFIT_SLOT(back, ITEM_SLOT_BACK)
+ CHECK_OUTFIT_SLOT(belt, ITEM_SLOT_BELT)
+ CHECK_OUTFIT_SLOT(gloves, ITEM_SLOT_GLOVES)
+ CHECK_OUTFIT_SLOT(shoes, ITEM_SLOT_FEET)
+ CHECK_OUTFIT_SLOT(head, ITEM_SLOT_HEAD)
+ CHECK_OUTFIT_SLOT(mask, ITEM_SLOT_MASK)
+ CHECK_OUTFIT_SLOT(neck, ITEM_SLOT_NECK)
+ CHECK_OUTFIT_SLOT(ears, ITEM_SLOT_EARS)
+ CHECK_OUTFIT_SLOT(glasses, ITEM_SLOT_EYES)
+ CHECK_OUTFIT_SLOT(id, ITEM_SLOT_ID)
+ CHECK_OUTFIT_SLOT(suit_store, ITEM_SLOT_SUITSTORE)
+ CHECK_OUTFIT_SLOT(l_pocket, ITEM_SLOT_POCKET)
+ CHECK_OUTFIT_SLOT(r_pocket, ITEM_SLOT_POCKET)
+
+ if (outfit.backpack_contents || outfit.box)
+ var/list/backpack_contents = outfit.backpack_contents?.Copy()
+ if (outfit.box)
+ if (!backpack_contents)
+ backpack_contents = list()
+ backpack_contents.Insert(1, outfit.box)
+ backpack_contents[outfit.box] = 1
+
+ for (var/path in backpack_contents)
+ var/number = backpack_contents[path] || 1
+ for (var/_ in 1 to number)
+ if (!H.equip_to_slot_or_del(new path(H), ITEM_SLOT_BACKPACK, TRUE))
+ Fail("[outfit.name]'s backpack_contents are invalid! Couldn't add [path] to backpack.")
+
+#undef CHECK_OUTFIT_SLOT
diff --git a/code/modules/unit_tests/plantgrowth_tests.dm b/code/modules/unit_tests/plantgrowth_tests.dm
new file mode 100644
index 0000000000..6b40236860
--- /dev/null
+++ b/code/modules/unit_tests/plantgrowth_tests.dm
@@ -0,0 +1,27 @@
+
+// Checks plants for broken tray icons. Use Advanced Proc Call to activate.
+// Maybe some day it would be used as unit test.
+// -------- IT IS NOW!
+/datum/unit_test/plantgrowth/Run()
+ var/list/states = icon_states('icons/obj/hydroponics/growing.dmi')
+ states |= icon_states('icons/obj/hydroponics/growing_fruits.dmi')
+ states |= icon_states('icons/obj/hydroponics/growing_flowers.dmi')
+ states |= icon_states('icons/obj/hydroponics/growing_mushrooms.dmi')
+ states |= icon_states('icons/obj/hydroponics/growing_vegetables.dmi')
+ states |= icon_states('goon/icons/obj/hydroponics.dmi')
+ var/list/paths = subtypesof(/obj/item/seeds) - /obj/item/seeds - typesof(/obj/item/seeds/sample) - /obj/item/seeds/lavaland
+
+ for(var/seedpath in paths)
+ var/obj/item/seeds/seed = new seedpath
+
+ for(var/i in 1 to seed.growthstages)
+ if("[seed.icon_grow][i]" in states)
+ continue
+ Fail("[seed.name] ([seed.type]) lacks the [seed.icon_grow][i] icon!")
+
+ if(!(seed.icon_dead in states))
+ Fail("[seed.name] ([seed.type]) lacks the [seed.icon_dead] icon!")
+
+ if(seed.icon_harvest) // mushrooms have no grown sprites, same for items with no product
+ if(!(seed.icon_harvest in states))
+ Fail("[seed.name] ([seed.type]) lacks the [seed.icon_harvest] icon!")
diff --git a/code/modules/unit_tests/quick_swap_sanity.dm b/code/modules/unit_tests/quick_swap_sanity.dm
new file mode 100644
index 0000000000..85e73f9b6a
--- /dev/null
+++ b/code/modules/unit_tests/quick_swap_sanity.dm
@@ -0,0 +1,31 @@
+/// Test that quick swap correctly swaps items and invalidates suit storage
+/datum/unit_test/quick_swap_sanity/Run()
+ // Create a human with a medical winter coat and a health analyzer in suit storage
+ var/mob/living/carbon/human/human = allocate(/mob/living/carbon/human)
+
+ var/obj/item/coat = allocate(/obj/item/clothing/suit/hooded/wintercoat/medical)
+ TEST_ASSERT(human.equip_to_slot_if_possible(coat, ITEM_SLOT_OCLOTHING), "Couldn't equip winter coat")
+
+ var/obj/item/analyzer = allocate(/obj/item/healthanalyzer)
+ TEST_ASSERT(human.equip_to_slot_if_possible(analyzer, ITEM_SLOT_SUITSTORE), "Couldn't equip health analyzer")
+
+ // Then, have them quick swap between the coat and a space suit
+ var/obj/item/hardsuit = allocate(/obj/item/clothing/suit/space/hardsuit)
+ TEST_ASSERT(human.equip_to_appropriate_slot(hardsuit, swap = TRUE), "Couldn't quick swap to hardsuit")
+
+ // Check if the human has the hardsuit on
+ TEST_ASSERT_EQUAL(human.wear_suit, hardsuit, "Human didn't equip the hardsuit")
+
+ // Make sure the health analyzer was dropped as part of the swap
+ // Since health analyzers are an invalid suit storage item
+ TEST_ASSERT_EQUAL(human.s_store, null, "Human didn't drop the health analyzer")
+
+ // Give the human an emergency oxygen tank
+ // This is valid suit storage for both the winter coat AND the hardsuit
+ var/obj/item/tank = allocate(/obj/item/tank/internals/emergency_oxygen)
+ TEST_ASSERT(human.equip_to_slot_if_possible(tank, ITEM_SLOT_SUITSTORE), "Couldn't equip emergency oxygen tank")
+
+ // Now, quick swap back to the coat
+ // Since the tank is a valid suit storage item, it should not be dropped
+ TEST_ASSERT(human.equip_to_appropriate_slot(coat, swap = TRUE), "Couldn't quick swap to coat")
+ TEST_ASSERT_EQUAL(human.s_store, tank, "Human dropped the oxygen tank, when it was a valid item to keep in suit storage")
diff --git a/code/modules/unit_tests/resist.dm b/code/modules/unit_tests/resist.dm
new file mode 100644
index 0000000000..9fe5cd1114
--- /dev/null
+++ b/code/modules/unit_tests/resist.dm
@@ -0,0 +1,29 @@
+/// Test that stop, drop, and roll lowers fire stacks
+/datum/unit_test/stop_drop_and_roll/Run()
+ var/mob/living/carbon/human/human = allocate(/mob/living/carbon/human)
+
+ TEST_ASSERT_EQUAL(human.fire_stacks, 0, "Human does not have 0 fire stacks pre-ignition")
+
+ human.adjust_fire_stacks(5)
+ human.IgniteMob()
+
+ TEST_ASSERT_EQUAL(human.fire_stacks, 5, "Human does not have 5 fire stacks pre-resist")
+
+ // Stop, drop, and roll has a sleep call. This would delay the test, and is not necessary.
+ CallAsync(human, /mob/living/verb/resist)
+
+ TEST_ASSERT(human.fire_stacks < 5, "Human did not lower fire stacks after resisting")
+
+/// Test that you can resist out of a container
+/datum/unit_test/container_resist/Run()
+ var/mob/living/carbon/human/human = allocate(/mob/living/carbon/human)
+ var/obj/structure/closet/closet = allocate(/obj/structure/closet, get_turf(human))
+
+ closet.open(human)
+ TEST_ASSERT(!(human in closet.contents), "Human was in the contents of an open closet")
+
+ closet.close(human)
+ TEST_ASSERT(human in closet.contents, "Human was not in the contents of the closed closet")
+
+ human.resist()
+ TEST_ASSERT(!(human in closet.contents), "Human resisted out of a standard closet, but was still in it")
diff --git a/code/modules/unit_tests/say.dm b/code/modules/unit_tests/say.dm
new file mode 100644
index 0000000000..3fe6675ab4
--- /dev/null
+++ b/code/modules/unit_tests/say.dm
@@ -0,0 +1,24 @@
+/// Test to verify message mods are parsed correctly
+/datum/unit_test/get_message_mods
+ var/mob/host_mob
+
+/datum/unit_test/get_message_mods/Run()
+ host_mob = allocate(/mob/living/carbon/human)
+
+ test("Hello", "Hello", list())
+ test(";HELP", "HELP", list(MODE_HEADSET = TRUE))
+ test(";%Never gonna give you up", "Never gonna give you up", list(MODE_HEADSET = TRUE, MODE_SING = TRUE))
+ test(".s Gun plz", "Gun plz", list(RADIO_KEY = RADIO_KEY_SECURITY, RADIO_EXTENSION = RADIO_CHANNEL_SECURITY))
+ test("...What", "...What", list())
+ //note to lettern: add the ++, ||, __, and the verb*text checks
+
+/datum/unit_test/get_message_mods/proc/test(message, expected_message, list/expected_mods)
+ var/list/mods = list()
+ TEST_ASSERT_EQUAL(host_mob.get_message_mods(message, mods), expected_message, "Chopped message was not what we expected. Message: [message]")
+
+ for (var/mod_key in mods)
+ TEST_ASSERT_EQUAL(mods[mod_key], expected_mods[mod_key], "The value for [mod_key] was not what we expected. Message: [message]")
+ expected_mods -= mod_key
+
+ if (expected_mods.len)
+ Fail("Some message mods were expected, but were not returned by get_message_mods: [json_encode(expected_mods)]. Message: [message]")
diff --git a/code/modules/unit_tests/spawn_humans.dm b/code/modules/unit_tests/spawn_humans.dm
index 0500deae0a..7189e87277 100644
--- a/code/modules/unit_tests/spawn_humans.dm
+++ b/code/modules/unit_tests/spawn_humans.dm
@@ -1,7 +1,7 @@
/datum/unit_test/spawn_humans/Run()
- var/locs = block(run_loc_bottom_left, run_loc_top_right)
+ var/locs = block(run_loc_bottom_left, run_loc_top_right)
- for(var/I in 1 to 5)
- new /mob/living/carbon/human(pick(locs))
+ for(var/I in 1 to 5)
+ new /mob/living/carbon/human(pick(locs))
- sleep(50)
+ sleep(50)
diff --git a/code/modules/unit_tests/subsystem_init.dm b/code/modules/unit_tests/subsystem_init.dm
index f768f03f78..7d5473bc1b 100644
--- a/code/modules/unit_tests/subsystem_init.dm
+++ b/code/modules/unit_tests/subsystem_init.dm
@@ -4,4 +4,4 @@
if(ss.flags & SS_NO_INIT)
continue
if(!ss.initialized)
- Fail("[ss]([ss.type]) is a subsystem meant to initialize but doesn't get set as initialized.")
\ No newline at end of file
+ Fail("[ss]([ss.type]) is a subsystem meant to initialize but doesn't get set as initialized.")
diff --git a/code/modules/unit_tests/surgeries.dm b/code/modules/unit_tests/surgeries.dm
new file mode 100644
index 0000000000..491b0c5645
--- /dev/null
+++ b/code/modules/unit_tests/surgeries.dm
@@ -0,0 +1,79 @@
+/datum/unit_test/amputation/Run()
+ var/mob/living/carbon/human/patient = allocate(/mob/living/carbon/human)
+ var/mob/living/carbon/human/user = allocate(/mob/living/carbon/human)
+
+ TEST_ASSERT_EQUAL(patient.get_missing_limbs().len, 0, "Patient is somehow missing limbs before surgery")
+
+ var/datum/surgery/amputation/surgery = new(patient, BODY_ZONE_R_ARM, patient.get_bodypart(BODY_ZONE_R_ARM))
+
+ var/datum/surgery_step/sever_limb/sever_limb = new
+ sever_limb.success(user, patient, BODY_ZONE_R_ARM, null, surgery)
+
+ TEST_ASSERT_EQUAL(patient.get_missing_limbs().len, 1, "Patient did not lose any limbs")
+ TEST_ASSERT_EQUAL(patient.get_missing_limbs()[1], BODY_ZONE_R_ARM, "Patient is missing a limb that isn't the one we operated on")
+
+/datum/unit_test/brain_surgery/Run()
+ var/mob/living/carbon/human/patient = allocate(/mob/living/carbon/human)
+ patient.gain_trauma_type(BRAIN_TRAUMA_MILD, TRAUMA_RESILIENCE_SURGERY)
+ patient.setOrganLoss(ORGAN_SLOT_BRAIN, 20)
+
+ TEST_ASSERT(patient.has_trauma_type(), "Patient does not have any traumas, despite being given one")
+
+ var/mob/living/carbon/human/user = allocate(/mob/living/carbon/human)
+
+ var/datum/surgery_step/fix_brain/fix_brain = new
+ fix_brain.success(user, patient)
+
+ TEST_ASSERT(!patient.has_trauma_type(), "Patient kept their brain trauma after brain surgery")
+ TEST_ASSERT(patient.getOrganLoss(ORGAN_SLOT_BRAIN) < 20, "Patient did not heal their brain damage after brain surgery")
+
+/datum/unit_test/multiple_surgeries/Run()
+ var/mob/living/carbon/human/user = allocate(/mob/living/carbon/human)
+ var/mob/living/carbon/human/patient_zero = allocate(/mob/living/carbon/human)
+ var/mob/living/carbon/human/patient_one = allocate(/mob/living/carbon/human)
+
+ var/obj/item/scalpel/scalpel = allocate(/obj/item/scalpel)
+
+ var/datum/surgery_step/incise/surgery_step = new
+ var/datum/surgery/organ_manipulation/surgery_for_zero = new
+
+ INVOKE_ASYNC(surgery_step, /datum/surgery_step/proc/initiate, user, patient_zero, BODY_ZONE_CHEST, scalpel, surgery_for_zero)
+ TEST_ASSERT(surgery_for_zero.step_in_progress, "Surgery on patient zero was not initiated")
+
+ var/datum/surgery/organ_manipulation/surgery_for_one = new
+
+ // Without waiting for the incision to complete, try to start a new surgery
+ TEST_ASSERT(!surgery_step.initiate(user, patient_one, BODY_ZONE_CHEST, scalpel, surgery_for_one), "Was allowed to start a second surgery without the rod of asclepius")
+ TEST_ASSERT(!surgery_for_one.step_in_progress, "Surgery for patient one is somehow in progress, despite not initiating")
+
+ user.apply_status_effect(STATUS_EFFECT_HIPPOCRATIC_OATH)
+ INVOKE_ASYNC(surgery_step, /datum/surgery_step/proc/initiate, user, patient_one, BODY_ZONE_CHEST, scalpel, surgery_for_one)
+ TEST_ASSERT(surgery_for_one.step_in_progress, "Surgery on patient one was not initiated, despite having rod of asclepius")
+
+/datum/unit_test/tend_wounds/Run()
+ var/mob/living/carbon/human/patient = allocate(/mob/living/carbon/human)
+ patient.take_overall_damage(100, 100)
+
+ var/mob/living/carbon/human/user = allocate(/mob/living/carbon/human)
+
+ // Test that tending wounds actually lowers damage
+ var/datum/surgery_step/heal/brute/basic/basic_brute_heal = new
+ basic_brute_heal.success(user, patient, BODY_ZONE_CHEST)
+ TEST_ASSERT(patient.getBruteLoss() < 100, "Tending brute wounds didn't lower brute damage ([patient.getBruteLoss()])")
+
+ var/datum/surgery_step/heal/burn/basic/basic_burn_heal = new
+ basic_burn_heal.success(user, patient, BODY_ZONE_CHEST)
+ TEST_ASSERT(patient.getFireLoss() < 100, "Tending burn wounds didn't lower burn damage ([patient.getFireLoss()])")
+
+ // Test that wearing clothing lowers heal amount
+ var/mob/living/carbon/human/naked_patient = allocate(/mob/living/carbon/human)
+ naked_patient.take_overall_damage(100)
+
+ var/mob/living/carbon/human/clothed_patient = allocate(/mob/living/carbon/human)
+ clothed_patient.equipOutfit(/datum/outfit/job/doctor, TRUE)
+ clothed_patient.take_overall_damage(100)
+
+ basic_brute_heal.success(user, naked_patient, BODY_ZONE_CHEST)
+ basic_brute_heal.success(user, clothed_patient, BODY_ZONE_CHEST)
+
+ TEST_ASSERT(naked_patient.getBruteLoss() < clothed_patient.getBruteLoss(), "Naked patient did not heal more from wounds tending than a clothed patient")
diff --git a/code/modules/unit_tests/unit_test.dm b/code/modules/unit_tests/unit_test.dm
index 49974f2cb0..36b406e75e 100644
--- a/code/modules/unit_tests/unit_test.dm
+++ b/code/modules/unit_tests/unit_test.dm
@@ -1,14 +1,9 @@
/*
-
Usage:
Override /Run() to run your test code
-
Call Fail() to fail the test (You should specify a reason)
-
You may use /New() and /Destroy() for setup/teardown respectively
-
You can use the run_loc_bottom_left and run_loc_top_right to get turfs for testing
-
*/
GLOBAL_DATUM(current_test, /datum/unit_test)
@@ -18,16 +13,18 @@ GLOBAL_VAR(test_log)
/datum/unit_test
//Bit of metadata for the future maybe
var/list/procs_tested
-
+
//usable vars
var/turf/run_loc_bottom_left
var/turf/run_loc_top_right
//internal shit
var/succeeded = TRUE
+ var/list/allocated
var/list/fail_reasons
/datum/unit_test/New()
+ allocated = new
run_loc_bottom_left = locate(1, 1, 1)
run_loc_top_right = locate(5, 5, 1)
@@ -35,6 +32,7 @@ GLOBAL_VAR(test_log)
//clear the test area
for(var/atom/movable/AM in block(run_loc_bottom_left, run_loc_top_right))
qdel(AM)
+ QDEL_LIST(allocated)
return ..()
/datum/unit_test/proc/Run()
@@ -48,6 +46,18 @@ GLOBAL_VAR(test_log)
LAZYADD(fail_reasons, reason)
+/// Allocates an instance of the provided type, and places it somewhere in an available loc
+/// Instances allocated through this proc will be destroyed when the test is over
+/datum/unit_test/proc/allocate(type, ...)
+ var/list/arguments = args.Copy(2)
+ if (!arguments.len)
+ arguments = list(run_loc_bottom_left)
+ else if (arguments[1] == null)
+ arguments[1] = run_loc_bottom_left
+ var/instance = new type(arglist(arguments))
+ allocated += instance
+ return instance
+
/proc/RunUnitTests()
CHECK_TICK
diff --git a/code/modules/uplink/uplink_items/uplink_clothing.dm b/code/modules/uplink/uplink_items/uplink_clothing.dm
index 745eddcc07..6163e5722a 100644
--- a/code/modules/uplink/uplink_items/uplink_clothing.dm
+++ b/code/modules/uplink/uplink_items/uplink_clothing.dm
@@ -92,8 +92,8 @@
cost = 6
exclude_modes = list(/datum/game_mode/nuclear, /datum/game_mode/nuclear/clown_ops)
-/datum/uplink_item/device_tools/guerillagloves
- name = "Guerilla Gloves"
+/datum/uplink_item/device_tools/guerrillagloves
+ name = "Guerrilla Gloves"
desc = "A pair of highly robust combat gripper gloves that excels at performing takedowns at close range, with an added lining of insulation. Careful not to hit a wall!"
item = /obj/item/clothing/gloves/tackler/combat/insulated
include_modes = list(/datum/game_mode/nuclear, /datum/game_mode/nuclear/clown_ops)
diff --git a/html/changelogs/AutoChangeLog-pr-13014.yml b/html/changelogs/AutoChangeLog-pr-13014.yml
new file mode 100644
index 0000000000..861e797669
--- /dev/null
+++ b/html/changelogs/AutoChangeLog-pr-13014.yml
@@ -0,0 +1,4 @@
+author: "Hatterhat"
+delete-after: True
+changes:
+ - rscadd: "The wastes of Lavaland and the icy caverns of Snow Taxi rumble in unison."
diff --git a/html/changelogs/AutoChangeLog-pr-13193.yml b/html/changelogs/AutoChangeLog-pr-13193.yml
new file mode 100644
index 0000000000..980a9366d8
--- /dev/null
+++ b/html/changelogs/AutoChangeLog-pr-13193.yml
@@ -0,0 +1,4 @@
+author: "silicons"
+delete-after: True
+changes:
+ - tweak: "medium screens are better now"
diff --git a/html/changelogs/AutoChangeLog-pr-13233.yml b/html/changelogs/AutoChangeLog-pr-13233.yml
new file mode 100644
index 0000000000..4366abec64
--- /dev/null
+++ b/html/changelogs/AutoChangeLog-pr-13233.yml
@@ -0,0 +1,6 @@
+author: "Hatterhat"
+delete-after: True
+changes:
+ - rscadd: "Changeling bone gauntlets! They punch the shit out of people really good."
+ - tweak: "Guerilla gloves and gorilla gloves inherit the strip modifiers of their predecessors, because apparently they had those."
+ - balance: "Pugilists now always hit the targeted limb and never miss."
diff --git a/html/changelogs/AutoChangeLog-pr-13349.yml b/html/changelogs/AutoChangeLog-pr-13349.yml
new file mode 100644
index 0000000000..2469b31ecd
--- /dev/null
+++ b/html/changelogs/AutoChangeLog-pr-13349.yml
@@ -0,0 +1,4 @@
+author: "LetterN"
+delete-after: True
+changes:
+ - code_imp: "Updates git and build tests."
diff --git a/html/changelogs/AutoChangeLog-pr-13497.yml b/html/changelogs/AutoChangeLog-pr-13497.yml
new file mode 100644
index 0000000000..c5f569bad3
--- /dev/null
+++ b/html/changelogs/AutoChangeLog-pr-13497.yml
@@ -0,0 +1,5 @@
+author: "Zandario"
+delete-after: True
+changes:
+ - code_imp: "Added some framework for future species expansions, including clothing refitting."
+ - refactor: "Made majority of the relevant Species IDs and Categories pre-defined, also for easier expansion and use."
diff --git a/html/changelogs/AutoChangeLog-pr-13525.yml b/html/changelogs/AutoChangeLog-pr-13525.yml
new file mode 100644
index 0000000000..eb441f04c3
--- /dev/null
+++ b/html/changelogs/AutoChangeLog-pr-13525.yml
@@ -0,0 +1,4 @@
+author: "Detective-Google"
+delete-after: True
+changes:
+ - bugfix: "Hilbert's jukebox works"
diff --git a/html/changelogs/AutoChangeLog-pr-13527.yml b/html/changelogs/AutoChangeLog-pr-13527.yml
new file mode 100644
index 0000000000..7e65116e83
--- /dev/null
+++ b/html/changelogs/AutoChangeLog-pr-13527.yml
@@ -0,0 +1,4 @@
+author: "ArchieBeepBoop"
+delete-after: True
+changes:
+ - rscadd: "Upgraded Advanced RTG Machine Preset"
diff --git a/html/changelogs/AutoChangeLog-pr-13530.yml b/html/changelogs/AutoChangeLog-pr-13530.yml
new file mode 100644
index 0000000000..b99ed1c545
--- /dev/null
+++ b/html/changelogs/AutoChangeLog-pr-13530.yml
@@ -0,0 +1,4 @@
+author: "silicons"
+delete-after: True
+changes:
+ - rscadd: "Clowns now have unpredictable effects on supermatter crystals when dusting from contact."
diff --git a/html/changelogs/AutoChangeLog-pr-13533.yml b/html/changelogs/AutoChangeLog-pr-13533.yml
new file mode 100644
index 0000000000..1ba14c689c
--- /dev/null
+++ b/html/changelogs/AutoChangeLog-pr-13533.yml
@@ -0,0 +1,4 @@
+author: "shellspeed1"
+delete-after: True
+changes:
+ - rscadd: "A new recipe for a spicy has been given to us by a strange business man."
diff --git a/html/changelogs/AutoChangeLog-pr-13535.yml b/html/changelogs/AutoChangeLog-pr-13535.yml
new file mode 100644
index 0000000000..b020587eb8
--- /dev/null
+++ b/html/changelogs/AutoChangeLog-pr-13535.yml
@@ -0,0 +1,7 @@
+author: "Hatterhat"
+delete-after: True
+changes:
+ - rscadd: "Energy sabre reskin for the energy sword - access via alt-click."
+ - bugfix: "Alt-click reskins are fixed."
+ - tweak: "Defibrillators and their many, many overlays were moved to another .dmi."
+ - tweak: "You can now change the color of an energy sword via multitool. Not deswords. Yet."
diff --git a/html/changelogs/AutoChangeLog-pr-13540.yml b/html/changelogs/AutoChangeLog-pr-13540.yml
new file mode 100644
index 0000000000..0d7d9b8d2c
--- /dev/null
+++ b/html/changelogs/AutoChangeLog-pr-13540.yml
@@ -0,0 +1,6 @@
+author: "ArcaneMusic, The0bserver-sys"
+delete-after: True
+changes:
+ - rscadd: "New from Hydrowear LLC: The Botanical Belt! This handy yellow belt lets you hold most of your botany gear, and a few beakers for reduced bag and floor clutter!"
+ - tweak: "Gives Hydrotrays plumbing pipes automatically, allowing you to make a self sustaining tray via plumbing."
+ - tweak: "Gives Service access to Bluespace Beakers, at last, gives Cargo, Science, and Medical the ability to construct reinforced plungers for use on lavaland."
diff --git a/html/changelogs/AutoChangeLog-pr-13544.yml b/html/changelogs/AutoChangeLog-pr-13544.yml
new file mode 100644
index 0000000000..61cf99090d
--- /dev/null
+++ b/html/changelogs/AutoChangeLog-pr-13544.yml
@@ -0,0 +1,4 @@
+author: "DeltaFire15"
+delete-after: True
+changes:
+ - bugfix: "Portals no longer runtime because of incorrect args."
diff --git a/icons/mob/actions/actions_changeling.dmi b/icons/mob/actions/actions_changeling.dmi
index ce6b81e13b..2f384c6119 100644
Binary files a/icons/mob/actions/actions_changeling.dmi and b/icons/mob/actions/actions_changeling.dmi differ
diff --git a/icons/mob/clothing/belt.dmi b/icons/mob/clothing/belt.dmi
index 4ac82ca299..a25752c1d0 100644
Binary files a/icons/mob/clothing/belt.dmi and b/icons/mob/clothing/belt.dmi differ
diff --git a/icons/mob/clothing/hands.dmi b/icons/mob/clothing/hands.dmi
index 44499649f9..b95c377e12 100644
Binary files a/icons/mob/clothing/hands.dmi and b/icons/mob/clothing/hands.dmi differ
diff --git a/icons/mob/inhands/weapons/swords_lefthand.dmi b/icons/mob/inhands/weapons/swords_lefthand.dmi
index 23d80af9ef..5ff248de68 100644
Binary files a/icons/mob/inhands/weapons/swords_lefthand.dmi and b/icons/mob/inhands/weapons/swords_lefthand.dmi differ
diff --git a/icons/mob/inhands/weapons/swords_righthand.dmi b/icons/mob/inhands/weapons/swords_righthand.dmi
index 702c0299b5..0332e60fb5 100644
Binary files a/icons/mob/inhands/weapons/swords_righthand.dmi and b/icons/mob/inhands/weapons/swords_righthand.dmi differ
diff --git a/icons/obj/clothing/belt_overlays.dmi b/icons/obj/clothing/belt_overlays.dmi
index 1c22a0ad1b..cdb2567268 100644
Binary files a/icons/obj/clothing/belt_overlays.dmi and b/icons/obj/clothing/belt_overlays.dmi differ
diff --git a/icons/obj/clothing/belts.dmi b/icons/obj/clothing/belts.dmi
index 1a98872463..75fd20b856 100644
Binary files a/icons/obj/clothing/belts.dmi and b/icons/obj/clothing/belts.dmi differ
diff --git a/icons/obj/clothing/gloves.dmi b/icons/obj/clothing/gloves.dmi
index c0f78ee604..0feb6a75db 100644
Binary files a/icons/obj/clothing/gloves.dmi and b/icons/obj/clothing/gloves.dmi differ
diff --git a/icons/obj/clothing/hats.dmi b/icons/obj/clothing/hats.dmi
index 8fbb2abe1e..4f650f8561 100644
Binary files a/icons/obj/clothing/hats.dmi and b/icons/obj/clothing/hats.dmi differ
diff --git a/icons/obj/defibrillators.dmi b/icons/obj/defibrillators.dmi
new file mode 100644
index 0000000000..f838f40030
Binary files /dev/null and b/icons/obj/defibrillators.dmi differ
diff --git a/icons/obj/food/soupsalad.dmi b/icons/obj/food/soupsalad.dmi
index ca9c150480..c4d7c184b6 100644
Binary files a/icons/obj/food/soupsalad.dmi and b/icons/obj/food/soupsalad.dmi differ
diff --git a/icons/obj/items_and_weapons.dmi b/icons/obj/items_and_weapons.dmi
index c5aca2394f..9b73fa706b 100644
Binary files a/icons/obj/items_and_weapons.dmi and b/icons/obj/items_and_weapons.dmi differ
diff --git a/modular_citadel/code/modules/mob/living/carbon/damage_procs.dm b/modular_citadel/code/modules/mob/living/carbon/damage_procs.dm
index 17c3f17ad8..0457b853b7 100644
--- a/modular_citadel/code/modules/mob/living/carbon/damage_procs.dm
+++ b/modular_citadel/code/modules/mob/living/carbon/damage_procs.dm
@@ -1,16 +1,3 @@
-/mob/living/carbon/adjustStaminaLossBuffered(amount, updating_health = 1)
- if(status_flags & GODMODE)
- return 0
- if(CONFIG_GET(flag/disable_stambuffer))
- return
- var/directstamloss = (bufferedstam + amount) - stambuffer
- if(directstamloss > 0)
- adjustStaminaLoss(directstamloss)
- bufferedstam = clamp(bufferedstam + amount, 0, stambuffer)
- stambufferregentime = world.time + 10
- if(updating_health)
- update_health_hud()
-
/mob/living/carbon/adjustStaminaLoss(amount, updating_health = TRUE, forced = FALSE, affected_zone = BODY_ZONE_CHEST)
if(!forced && (status_flags & GODMODE))
return FALSE
diff --git a/modular_citadel/code/modules/mob/living/damage_procs.dm b/modular_citadel/code/modules/mob/living/damage_procs.dm
deleted file mode 100644
index a399c17c71..0000000000
--- a/modular_citadel/code/modules/mob/living/damage_procs.dm
+++ /dev/null
@@ -1,2 +0,0 @@
-/mob/living/proc/adjustStaminaLossBuffered(amount, updating_health = TRUE, forced = FALSE)
- return
diff --git a/modular_citadel/code/modules/mob/living/living.dm b/modular_citadel/code/modules/mob/living/living.dm
index 01867e9dcc..b5be028607 100644
--- a/modular_citadel/code/modules/mob/living/living.dm
+++ b/modular_citadel/code/modules/mob/living/living.dm
@@ -1,5 +1,3 @@
-/mob/living
-
/atom
var/pseudo_z_axis
@@ -24,26 +22,3 @@
if(.)
pseudo_z_axis = newloc.get_fake_z()
pixel_z = pseudo_z_axis
-
-/mob/living/carbon/update_stamina()
- var/total_health = getStaminaLoss()
- if(total_health >= STAMINA_SOFTCRIT)
- if(!(combat_flags & COMBAT_FLAG_SOFT_STAMCRIT))
- ENABLE_BITFIELD(combat_flags, COMBAT_FLAG_SOFT_STAMCRIT)
- else
- if(combat_flags & COMBAT_FLAG_SOFT_STAMCRIT)
- DISABLE_BITFIELD(combat_flags, COMBAT_FLAG_SOFT_STAMCRIT)
- if(total_health)
- if(!(combat_flags & COMBAT_FLAG_HARD_STAMCRIT) && total_health >= STAMINA_CRIT && !stat)
- to_chat(src, "You're too exhausted to keep going...")
- set_resting(TRUE, FALSE, FALSE)
- SEND_SIGNAL(src, COMSIG_DISABLE_COMBAT_MODE)
- ENABLE_BITFIELD(combat_flags, COMBAT_FLAG_HARD_STAMCRIT)
- filters += CIT_FILTER_STAMINACRIT
- update_mobility()
- if((combat_flags & COMBAT_FLAG_HARD_STAMCRIT) && total_health <= STAMINA_SOFTCRIT)
- to_chat(src, "You don't feel nearly as exhausted anymore.")
- DISABLE_BITFIELD(combat_flags, COMBAT_FLAG_HARD_STAMCRIT | COMBAT_FLAG_SOFT_STAMCRIT)
- filters -= CIT_FILTER_STAMINACRIT
- update_mobility()
- update_health_hud()
diff --git a/modular_citadel/icons/ui/screen_gen.dmi b/modular_citadel/icons/ui/screen_gen.dmi
index d006185a3c..510d312f1c 100644
Binary files a/modular_citadel/icons/ui/screen_gen.dmi and b/modular_citadel/icons/ui/screen_gen.dmi differ
diff --git a/tgstation.dme b/tgstation.dme
index c27d68e7bc..750486e032 100644
--- a/tgstation.dme
+++ b/tgstation.dme
@@ -108,6 +108,7 @@
#include "code\__DEFINES\sight.dm"
#include "code\__DEFINES\sound.dm"
#include "code\__DEFINES\spaceman_dmm.dm"
+#include "code\__DEFINES\species.dm"
#include "code\__DEFINES\stat.dm"
#include "code\__DEFINES\stat_tracking.dm"
#include "code\__DEFINES\status_effects.dm"
@@ -138,6 +139,7 @@
#include "code\__DEFINES\combat\attack_types.dm"
#include "code\__DEFINES\combat\block.dm"
#include "code\__DEFINES\combat\block_parry.dm"
+#include "code\__DEFINES\combat\stamina_combat.dm"
#include "code\__DEFINES\dcs\flags.dm"
#include "code\__DEFINES\dcs\helpers.dm"
#include "code\__DEFINES\dcs\signals.dm"
@@ -292,6 +294,7 @@
#include "code\controllers\configuration\entries\plushies.dm"
#include "code\controllers\configuration\entries\policy.dm"
#include "code\controllers\configuration\entries\resources.dm"
+#include "code\controllers\configuration\entries\stamina_combat.dm"
#include "code\controllers\subsystem\acid.dm"
#include "code\controllers\subsystem\adjacent_air.dm"
#include "code\controllers\subsystem\air.dm"
@@ -326,6 +329,7 @@
#include "code\controllers\subsystem\materials.dm"
#include "code\controllers\subsystem\medals.dm"
#include "code\controllers\subsystem\minimaps.dm"
+#include "code\controllers\subsystem\minimum_spawns.dm"
#include "code\controllers\subsystem\minor_mapping.dm"
#include "code\controllers\subsystem\mobs.dm"
#include "code\controllers\subsystem\nightshift.dm"
@@ -2281,7 +2285,6 @@
#include "code\modules\jobs\job_types\station_engineer.dm"
#include "code\modules\jobs\job_types\virologist.dm"
#include "code\modules\jobs\job_types\warden.dm"
-#include "code\modules\jobs\map_changes\map_changes.dm"
#include "code\modules\keybindings\bindings_atom.dm"
#include "code\modules\keybindings\bindings_client.dm"
#include "code\modules\keybindings\focus.dm"
@@ -2463,6 +2466,7 @@
#include "code\modules\mob\living\login.dm"
#include "code\modules\mob\living\logout.dm"
#include "code\modules\mob\living\say.dm"
+#include "code\modules\mob\living\stamina_buffer.dm"
#include "code\modules\mob\living\status_procs.dm"
#include "code\modules\mob\living\taste.dm"
#include "code\modules\mob\living\update_icons.dm"
@@ -2545,9 +2549,10 @@
#include "code\modules\mob\living\carbon\human\status_procs.dm"
#include "code\modules\mob\living\carbon\human\typing_indicator.dm"
#include "code\modules\mob\living\carbon\human\update_icons.dm"
-#include "code\modules\mob\living\carbon\human\species_types\abductors.dm"
+#include "code\modules\mob\living\carbon\human\species_types\abductor.dm"
#include "code\modules\mob\living\carbon\human\species_types\android.dm"
#include "code\modules\mob\living\carbon\human\species_types\angel.dm"
+#include "code\modules\mob\living\carbon\human\species_types\anthromorph.dm"
#include "code\modules\mob\living\carbon\human\species_types\bugmen.dm"
#include "code\modules\mob\living\carbon\human\species_types\corporate.dm"
#include "code\modules\mob\living\carbon\human\species_types\dullahan.dm"
@@ -2555,7 +2560,6 @@
#include "code\modules\mob\living\carbon\human\species_types\ethereal.dm"
#include "code\modules\mob\living\carbon\human\species_types\felinid.dm"
#include "code\modules\mob\living\carbon\human\species_types\flypeople.dm"
-#include "code\modules\mob\living\carbon\human\species_types\furrypeople.dm"
#include "code\modules\mob\living\carbon\human\species_types\golems.dm"
#include "code\modules\mob\living\carbon\human\species_types\humans.dm"
#include "code\modules\mob\living\carbon\human\species_types\ipc.dm"
@@ -3602,7 +3606,6 @@
#include "modular_citadel\code\modules\mentor\mentorpm.dm"
#include "modular_citadel\code\modules\mentor\mentorsay.dm"
#include "modular_citadel\code\modules\mob\cit_emotes.dm"
-#include "modular_citadel\code\modules\mob\living\damage_procs.dm"
#include "modular_citadel\code\modules\mob\living\living.dm"
#include "modular_citadel\code\modules\mob\living\carbon\carbon.dm"
#include "modular_citadel\code\modules\mob\living\carbon\damage_procs.dm"
diff --git a/tools/travis/check_filedirs.sh b/tools/travis/check_filedirs.sh
index 30df015763..b7bac4b7a2 100755
--- a/tools/travis/check_filedirs.sh
+++ b/tools/travis/check_filedirs.sh
@@ -1,15 +1,15 @@
+
#!/bin/bash
if [ -n "$1" ]
then
dme=$1
else
- echo "Specify a DME to check"
+ echo "ERROR: Specify a DME to check"
exit 1
fi
if [[ $(awk '/BEGIN_FILE_DIR/{flag=1;next}/END_FILE_DIR/{flag=0}flag' $dme | wc -l) -ne 1 ]]
then
- echo "File DIR was ticked, please untick it, see: https://tgstation13.org/phpBB/viewtopic.php?f=5&t=321 for more"
+ echo "ERROR: File DIR was ticked, please untick it, see: https://tgstation13.org/phpBB/viewtopic.php?f=5&t=321 for more"
exit 1
fi
-
diff --git a/tools/travis/check_grep.sh b/tools/travis/check_grep.sh
index 2117b570eb..b85f775eb9 100755
--- a/tools/travis/check_grep.sh
+++ b/tools/travis/check_grep.sh
@@ -7,43 +7,70 @@ shopt -s globstar
st=0
if grep -El '^\".+\" = \(.+\)' _maps/**/*.dmm; then
- echo "Non-TGM formatted map detected. Please convert it using Map Merger!"
+ echo "ERROR: Non-TGM formatted map detected. Please convert it using Map Merger!"
st=1
fi;
if grep -P '^\ttag = \"icon' _maps/**/*.dmm; then
- echo "tag vars from icon state generation detected in maps, please remove them."
+ echo "ERROR: tag vars from icon state generation detected in maps, please remove them."
st=1
fi;
-if grep 'step_[xy]' _maps/**/*.dmm; then
- echo "step_x/step_y variables detected in maps, please remove them."
+if grep -P 'step_[xy]' _maps/**/*.dmm; then
+ echo "ERROR: step_x/step_y variables detected in maps, please remove them."
st=1
fi;
-if grep 'pixel_[xy] = 0' _maps/**/*.dmm; then
- echo "pixel_x/pixel_y = 0 variables detected in maps, please review to ensure they are not dirty varedits."
+if grep -P 'pixel_[^xy]' _maps/**/*.dmm; then
+ echo "ERROR: incorrect pixel offset variables detected in maps, please remove them."
+ st=1
fi;
if grep -P '\td[1-2] =' _maps/**/*.dmm; then
- echo "d1/d2 cable variables detected in maps, please remove them."
+ echo "ERROR: d1/d2 cable variables detected in maps, please remove them."
st=1
fi;
-if grep '^/area/.+[\{]' _maps/**/*.dmm; then
- echo "Vareditted /area path use detected in maps, please replace with proper paths."
+if grep -P '^/area/.+[\{]' _maps/**/*.dmm; then
+ echo "WARNING: Vareditted /area path use detected in maps, please replace with proper paths."
+ #st=1
+fi;
+if grep -P '\W\/turf\s*[,\){]' _maps/**/*.dmm; then
+ echo "ERROR: base /turf path use detected in maps, please replace with proper paths."
st=1
fi;
-if grep '\W\/turf\s*[,\){]' _maps/**/*.dmm; then
- echo "base /turf path use detected in maps, please replace with proper paths."
- st=1
-fi;
-if grep '^/*var/' code/**/*.dm; then
- echo "Unmanaged global var use detected in code, please use the helpers."
+if grep -P '^/*var/' code/**/*.dm; then
+ echo "ERROR: Unmanaged global var use detected in code, please use the helpers."
st=1
fi;
+nl='
+'
+nl=$'\n'
+while read f; do
+ t=$(tail -c2 "$f"; printf x); r1="${nl}$"; r2="${nl}${r1}"
+ if [[ ! ${t%x} =~ $r1 ]]; then
+ echo "file $f is missing a trailing newline"
+ #st=1
+ fi;
+done < <(find . -type f -name '*.dm')
if grep -i 'centcomm' code/**/*.dm; then
- echo "Misspelling(s) of CENTCOM detected in code, please remove the extra M(s)."
+ echo "ERROR: Misspelling(s) of CENTCOM detected in code, please remove the extra M(s)."
st=1
fi;
if grep -i 'centcomm' _maps/**/*.dmm; then
- echo "Misspelling(s) of CENTCOM detected in maps, please remove the extra M(s)."
+ echo "ERROR: Misspelling(s) of CENTCOM detected in maps, please remove the extra M(s)."
st=1
fi;
+if ls _maps/*.json | grep -P "[A-Z]"; then
+ echo "Uppercase in a map json detected, these must be all lowercase."
+ st=1
+fi;
+for json in _maps/*.json
+do
+ map_path=$(jq -r '.map_path' $json)
+ while read map_file; do
+ filename="_maps/$map_path/$map_file"
+ if [ ! -f $filename ]
+ then
+ echo "found invalid file reference to $filename in _maps/$json"
+ st=1
+ fi
+ done < <(jq -r '[.map_file] | flatten | .[]' $json)
+done
exit $st
diff --git a/tools/travis/install_dreamchecker.sh b/tools/travis/install_dreamchecker.sh
deleted file mode 100755
index 4423e5aafb..0000000000
--- a/tools/travis/install_dreamchecker.sh
+++ /dev/null
@@ -1,8 +0,0 @@
-#!/bin/bash
-set -euo pipefail
-
-source dependencies.sh
-
-wget -O ~/dreamchecker "https://github.com/SpaceManiac/SpacemanDMM/releases/download/$SPACEMAN_DMM_VERSION/dreamchecker"
-chmod +x ~/dreamchecker
-~/dreamchecker --version
diff --git a/tools/travis/install_spaceman_dmm.sh b/tools/travis/install_spaceman_dmm.sh
new file mode 100755
index 0000000000..39464193f8
--- /dev/null
+++ b/tools/travis/install_spaceman_dmm.sh
@@ -0,0 +1,8 @@
+#!/bin/bash
+set -euo pipefail
+
+source dependencies.sh
+
+wget -O ~/$1 "https://github.com/SpaceManiac/SpacemanDMM/releases/download/$SPACEMAN_DMM_VERSION/$1"
+chmod +x ~/$1
+~/$1 --version
diff --git a/tools/travis/run_server.sh b/tools/travis/run_server.sh
index 975e8f6884..04c491cd5c 100755
--- a/tools/travis/run_server.sh
+++ b/tools/travis/run_server.sh
@@ -2,6 +2,7 @@
set -euo pipefail
tools/deploy.sh travis_test
+rm travis_test/*.dll
mkdir travis_test/config
#test config