diff --git a/.editorconfig b/.editorconfig
index a25dbdfc85..df93ae3a16 100644
--- a/.editorconfig
+++ b/.editorconfig
@@ -2,3 +2,7 @@
insert_final_newline = true
indent_style = tab
indent_size = 4
+
+[*.yml]
+indent_style = space
+indent_size = 2
diff --git a/.travis.yml b/.travis.yml
index bef3a69ad7..ab3b1003c3 100644
--- a/.travis.yml
+++ b/.travis.yml
@@ -1,28 +1,34 @@
language: generic
-sudo: false
dist: xenial
+sudo: false
+
branches:
except:
- ___TGS3TempBranch
- ___TGSTempBranch
+
matrix:
include:
- - env:
- - BUILD_TOOLS=true
- name: "Build Tools"
+ - name: "Run Linters"
addons:
apt:
packages:
- python3
- python3-pip
- python3-setuptools
- cache:
- directories:
- - tgui/node_modules
- - env:
- - BUILD_TESTING=true
- - BUILD_TOOLS=false
- name: "Build All Maps"
+ install:
+ - tools/travis/install_build_tools.sh
+ - tools/travis/install_dreamchecker.sh
+ script:
+ - tools/travis/check_filedirs.sh tgstation.dme
+ - tools/travis/check_changelogs.sh
+ - find . -name "*.php" -print0 | xargs -0 -n1 php -l
+ - find . -name "*.json" -not -path "./tgui/node_modules/*" -print0 | xargs -0 python3 ./tools/json_verifier.py
+ - tools/travis/build_tgui.sh
+ - tools/travis/check_grep.sh
+ - ~/dreamchecker
+
+ - name: "Compile All Maps"
addons:
apt:
packages:
@@ -30,10 +36,15 @@ matrix:
cache:
directories:
- $HOME/BYOND
- - env:
- - BUILD_TESTING=false
- - BUILD_TOOLS=false
- name: "Build and Run Unit Tests"
+ install:
+ - tools/travis/install_byond.sh
+ - source $HOME/BYOND/byond/bin/byondsetup
+ before_script:
+ - tools/travis/template_dm_generator.py
+ script:
+ - tools/travis/dm.sh -DTRAVISBUILDING -DTRAVISTESTING -DALL_MAPS tgstation.dme
+
+ - name: "Compile and Run Tests"
addons:
mariadb: '10.2'
apt:
@@ -41,7 +52,6 @@ matrix:
- ubuntu-toolchain-r-test
packages:
- libstdc++6:i386
- - libssl-dev:i386
- gcc-multilib
- g++-7
- g++-7-multilib
@@ -49,25 +59,19 @@ matrix:
- libmariadbd-dev
cache:
directories:
- - $HOME/.cargo
- $HOME/BYOND
- - $HOME/MariaDB
- - $HOME/.rustup
-
-install:
- - tools/travis/install_build_tools.sh
- - if [ $BUILD_TOOLS = false ] && [ $BUILD_TESTING = false ]; then mysql -u root -e 'CREATE DATABASE tg_travis;'; fi
- - if [ $BUILD_TOOLS = false ] && [ $BUILD_TESTING = false ]; then mysql -u root tg_travis < SQL/tgstation_schema.sql; fi
- - if [ $BUILD_TOOLS = false ] && [ $BUILD_TESTING = false ]; then mysql -u root -e 'CREATE DATABASE tg_travis_prefixed;'; fi
- - if [ $BUILD_TOOLS = false ] && [ $BUILD_TESTING = false ]; then mysql -u root tg_travis_prefixed < SQL/tgstation_schema_prefixed.sql; fi
-
-before_script:
- - tools/travis/before_build_tools.sh
- - tools/travis/before_build_byond.sh
-
-script:
- - tools/travis/check_filedirs.sh tgstation.dme
- - tools/travis/build_tools.sh || travis_terminate 1
- - tools/travis/build_dependencies.sh || travis_terminate 1
- - tools/travis/build_byond.sh
-
+ - $HOME/libmariadb
+ install:
+ - tools/travis/install_byond.sh
+ - source $HOME/BYOND/byond/bin/byondsetup
+ - tools/travis/install_libmariadb.sh
+ - tools/travis/install_rust_g.sh
+ before_script:
+ - mysql -u root -e 'CREATE DATABASE tg_travis;'
+ - mysql -u root tg_travis < SQL/tgstation_schema.sql
+ - mysql -u root -e 'CREATE DATABASE tg_travis_prefixed;'
+ - mysql -u root tg_travis_prefixed < SQL/tgstation_schema_prefixed.sql
+ - tools/travis/build_bsql.sh
+ script:
+ - tools/travis/dm.sh -DTRAVISBUILDING tgstation.dme || travis_terminate 1
+ - tools/travis/run_server.sh
diff --git a/_maps/RandomRuins/LavaRuins/lavaland_surface_elite_tumor.dmm b/_maps/RandomRuins/LavaRuins/lavaland_surface_elite_tumor.dmm
new file mode 100644
index 0000000000..6e44ec3197
--- /dev/null
+++ b/_maps/RandomRuins/LavaRuins/lavaland_surface_elite_tumor.dmm
@@ -0,0 +1,111 @@
+//MAP CONVERTED BY dmm2tgm.py THIS HEADER COMMENT PREVENTS RECONVERSION, DO NOT REMOVE
+"a" = (
+/turf/template_noop,
+/area/lavaland/surface/outdoors)
+"b" = (
+/obj/structure/elite_tumor,
+/turf/open/floor/plating/asteroid/basalt/lava_land_surface,
+/area/lavaland/surface/outdoors)
+"c" = (
+/turf/open/floor/plating/asteroid/basalt/lava_land_surface,
+/area/lavaland/surface/outdoors)
+
+(1,1,1) = {"
+a
+a
+a
+a
+a
+a
+a
+a
+a
+"}
+(2,1,1) = {"
+a
+a
+a
+a
+a
+a
+a
+a
+a
+"}
+(3,1,1) = {"
+a
+a
+a
+a
+a
+a
+a
+a
+a
+"}
+(4,1,1) = {"
+a
+a
+a
+c
+c
+c
+a
+a
+a
+"}
+(5,1,1) = {"
+a
+a
+a
+c
+b
+c
+a
+a
+a
+"}
+(6,1,1) = {"
+a
+a
+a
+c
+c
+c
+a
+a
+a
+"}
+(7,1,1) = {"
+a
+a
+a
+a
+a
+a
+a
+a
+a
+"}
+(8,1,1) = {"
+a
+a
+a
+a
+a
+a
+a
+a
+a
+"}
+(9,1,1) = {"
+a
+a
+a
+a
+a
+a
+a
+a
+a
+"}
diff --git a/_maps/RandomRuins/SpaceRuins/spacehermit.dmm b/_maps/RandomRuins/SpaceRuins/spacehermit.dmm
index 341dcf7b99..f64bfa8a4e 100644
--- a/_maps/RandomRuins/SpaceRuins/spacehermit.dmm
+++ b/_maps/RandomRuins/SpaceRuins/spacehermit.dmm
@@ -338,6 +338,9 @@
/obj/item/flashlight/lamp/bananalamp,
/turf/open/floor/plating/asteroid,
/area/ruin/unpowered)
+"bt" = (
+/turf/closed/mineral/random/low_chance/earth_like,
+/area/ruin/unpowered)
(1,1,1) = {"
aa
@@ -474,13 +477,13 @@ ab
ab
ab
ab
-ab
-ab
-ab
-ab
-ab
-ab
-ab
+bt
+bt
+bt
+bt
+bt
+bt
+bt
ab
ab
ab
@@ -520,22 +523,22 @@ aA
ab
ab
ab
-ab
-ab
-ab
-ab
-ab
-ab
-ab
-ab
-ab
-ab
-ab
-ab
-ab
-ab
-ab
-ab
+bt
+bt
+bt
+bt
+bt
+bt
+bt
+bt
+bt
+bt
+bt
+bt
+bt
+bt
+bt
+bt
ab
ab
aA
@@ -569,27 +572,27 @@ aA
aA
aA
ab
-ab
-ab
-ab
-ab
-ab
-ab
-ab
-ab
-ab
-ab
-ab
-ab
-ab
-ab
-ab
-ab
-ab
-ab
-ab
-ab
-ab
+bt
+bt
+bt
+bt
+bt
+bt
+bt
+bt
+bt
+bt
+bt
+bt
+bt
+bt
+bt
+bt
+bt
+bt
+bt
+bt
+bt
ab
aA
aa
@@ -620,29 +623,29 @@ ab
ab
aA
ab
-ab
-ab
-ab
-ab
-ab
-ab
-ab
-ab
-ab
-ab
-ab
-ab
-ab
-ab
-ab
-ab
-ab
-ab
-ab
-ab
-ab
-ab
-ab
+bt
+bt
+bt
+bt
+bt
+bt
+bt
+bt
+bt
+bt
+bt
+bt
+bt
+bt
+bt
+bt
+bt
+bt
+bt
+bt
+bt
+bt
+bt
ab
aA
aa
@@ -666,36 +669,36 @@ aM
aa
aA
ab
+bt
+bt
+bt
+bt
ab
-ab
-ab
-ab
-ab
-ab
-ab
-ab
-ab
-ab
-ab
-ab
-ab
-ab
-ab
-ab
-ab
-ab
-ab
-ab
-ab
-ab
-ab
-ab
-ab
-ab
-ab
-ab
-ab
-ab
+bt
+bt
+bt
+bt
+bt
+bt
+bt
+bt
+bt
+bt
+bt
+bt
+bt
+bt
+bt
+bt
+bt
+bt
+bt
+bt
+bt
+bt
+bt
+bt
+bt
ab
aA
aa
@@ -718,37 +721,37 @@ aa
aa
aA
ab
-ab
-ab
-ab
-ab
-ab
-ab
-ab
-ab
-ab
-ab
-ab
-ab
-ab
-ab
-ab
-ab
-ab
-ab
-ab
-ab
-ab
+bt
+bt
+bt
+bt
+bt
+bt
+bt
+bt
+bt
+bt
+bt
+bt
+bt
+bt
+bt
+bt
+bt
+bt
+bt
+bt
+bt
ae
af
ae
-ab
-ab
-ab
-ab
-ab
-ab
-ab
+bt
+bt
+bt
+bt
+bt
+bt
+bt
ab
aA
aa
@@ -770,38 +773,38 @@ aa
aa
aA
ab
-ab
-ab
-ab
-ab
-ab
-ab
-ab
-ab
-ab
-ab
-ab
-ab
-ab
-ab
-ab
-ab
-ab
-ab
-ab
-ab
-ab
+bt
+bt
+bt
+bt
+bt
+bt
+bt
+bt
+bt
+bt
+bt
+bt
+bt
+bt
+bt
+bt
+bt
+bt
+bt
+bt
+bt
af
aq
af
-ab
-ab
-ab
-ab
-ab
-ab
-ab
-ab
+bt
+bt
+bt
+bt
+bt
+bt
+bt
+bt
ab
aA
aa
@@ -822,38 +825,38 @@ aa
aA
aA
ab
-ab
-ab
-ab
-ab
-ab
-ab
-ab
-ab
-ab
-ab
-ab
-ab
-ab
-ab
-ab
-ab
-ab
-ab
-ab
-ab
+bt
+bt
+bt
+bt
+bt
+bt
+bt
+bt
+bt
+bt
+bt
+bt
+bt
+bt
+bt
+bt
+bt
+bt
+bt
+bt
ae
ae
ao
ae
ae
-ab
-ab
-ab
-ab
-ab
-ab
-ab
+bt
+bt
+bt
+bt
+bt
+bt
+bt
ab
aA
aa
@@ -874,39 +877,39 @@ aa
aA
aA
ab
-ab
-ab
-ab
-ab
-ab
-ab
-ab
-ab
-ab
-ab
-ab
-ab
-ab
-ab
-ab
-ab
-ab
-ab
-ab
-ab
+bt
+bt
+bt
+bt
+bt
+bt
+bt
+bt
+bt
+bt
+bt
+bt
+bt
+bt
+bt
+bt
+bt
+bt
+bt
+bt
af
an
an
an
af
-ab
-ab
-ab
-ab
-ab
-ab
-ab
-ab
+bt
+bt
+bt
+bt
+bt
+bt
+bt
+bt
ab
aA
aa
@@ -927,24 +930,24 @@ aA
aA
aA
ab
-ab
-ab
-ab
-ab
-ab
-ab
-ab
-ab
-ab
-ab
-ab
-ab
-ab
-ab
-ab
-ab
-ab
-ab
+bt
+bt
+bt
+bt
+bt
+bt
+bt
+bt
+bt
+bt
+bt
+bt
+bt
+bt
+bt
+bt
+bt
+bt
ae
ae
bh
@@ -952,13 +955,13 @@ an
an
ae
aZ
-ab
-ab
-ab
-ab
-ab
-ab
-ab
+bt
+bt
+bt
+bt
+bt
+bt
+bt
ab
aA
aa
@@ -979,24 +982,24 @@ aA
aA
aA
ab
-ab
-ab
-ab
-ab
-ab
-ab
-ab
-ab
-ab
-ab
-ab
-ab
-ab
-ab
-ab
-ab
-ab
-ab
+bt
+bt
+bt
+bt
+bt
+bt
+bt
+bt
+bt
+bt
+bt
+bt
+bt
+bt
+bt
+bt
+bt
+bt
af
aY
ao
@@ -1004,13 +1007,13 @@ ao
ao
ak
ba
-ab
-ab
-ab
-ab
-ab
-ab
-ab
+bt
+bt
+bt
+bt
+bt
+bt
+bt
ab
aA
aa
@@ -1031,24 +1034,24 @@ aA
aA
aA
ab
-ab
-ab
-ab
-ab
-ab
-ab
-ab
-ab
-ab
-ab
-ab
-ab
-ab
-ab
-ab
-ab
-ab
-ab
+bt
+bt
+bt
+bt
+bt
+bt
+bt
+bt
+bt
+bt
+bt
+bt
+bt
+bt
+bt
+bt
+bt
+bt
al
aj
ap
@@ -1056,13 +1059,13 @@ an
as
aj
aE
-ab
-ab
-ab
-ab
-ab
-ab
-ab
+bt
+bt
+bt
+bt
+bt
+bt
+bt
ab
aA
aa
@@ -1083,24 +1086,24 @@ aA
aA
aA
ab
-ab
-ab
-ab
-ab
-ab
-ab
-ab
-ab
-ab
-ab
-ab
-ab
-ab
-ab
-ab
-ab
-ab
-ab
+bt
+bt
+bt
+bt
+bt
+bt
+bt
+bt
+bt
+bt
+bt
+bt
+bt
+bt
+bt
+bt
+bt
+bt
af
ak
ao
@@ -1109,12 +1112,12 @@ ao
ak
ba
ac
-ab
-ab
-ab
-ab
-ab
-ab
+bt
+bt
+bt
+bt
+bt
+bt
ab
aA
aa
@@ -1134,24 +1137,24 @@ aa
aA
aA
ab
-ab
-ab
-ab
-ab
-ab
-ab
-ab
-ab
+bt
+bt
+bt
+bt
+bt
+bt
+bt
+bt
ah
az
-ab
-ab
-ab
-ab
-ab
-ab
-ab
-ab
+bt
+bt
+bt
+bt
+bt
+bt
+bt
+bt
aR
ae
ae
@@ -1161,12 +1164,12 @@ an
ae
aZ
ac
-ab
-ab
-ab
-ab
-ab
-ab
+bt
+bt
+bt
+bt
+bt
+bt
ab
aA
aM
@@ -1186,24 +1189,24 @@ aa
aA
aA
ab
-ab
-ab
-ab
-ab
-ab
-ab
-ab
-ab
+bt
+bt
+bt
+bt
+bt
+bt
+bt
+bt
ac
ac
-ab
-ab
-ab
-ab
-ab
-ab
-ab
-ab
+bt
+bt
+bt
+bt
+bt
+bt
+bt
+bt
aR
ag
ag
@@ -1213,12 +1216,12 @@ af
at
bb
ac
-ab
-ab
-ab
-ab
-ab
-ab
+bt
+bt
+bt
+bt
+bt
+bt
ab
aA
aa
@@ -1238,24 +1241,24 @@ aa
aA
aA
ab
-ab
-ab
-ab
-ab
-ab
-ab
-ab
+bt
+bt
+bt
+bt
+bt
+bt
+bt
ad
ac
ac
ac
ac
-ab
+bt
aH
aJ
aL
-ab
-ab
+bt
+bt
aT
ac
ac
@@ -1265,12 +1268,12 @@ ac
ac
ac
aR
-ab
-ab
-ab
-ab
-ab
-ab
+bt
+bt
+bt
+bt
+bt
+bt
ab
aA
aa
@@ -1289,14 +1292,14 @@ aa
aA
aA
ab
-ab
-ab
-ab
-ab
-ab
-ab
-ab
-ab
+bt
+bt
+bt
+bt
+bt
+bt
+bt
+bt
ad
au
ac
@@ -1306,8 +1309,8 @@ ac
ac
ac
ac
-ab
-ab
+bt
+bt
ac
ac
ac
@@ -1317,12 +1320,12 @@ ac
ac
ac
bg
-ab
-ab
-ab
-ab
-ab
-ab
+bt
+bt
+bt
+bt
+bt
+bt
ab
aA
aa
@@ -1341,14 +1344,14 @@ aa
aA
aA
ab
-ab
-ab
-ab
-ab
-ab
-ab
-ab
-ab
+bt
+bt
+bt
+bt
+bt
+bt
+bt
+bt
ad
aw
bs
@@ -1358,9 +1361,9 @@ ac
ac
ac
ac
-ab
-ab
-ab
+bt
+bt
+bt
ac
ac
ac
@@ -1368,13 +1371,13 @@ ac
ac
ac
ac
-ab
-ab
-ab
-ab
-ab
-ab
-ab
+bt
+bt
+bt
+bt
+bt
+bt
+bt
ab
aA
aa
@@ -1393,14 +1396,14 @@ aa
aA
aA
ab
-ab
-ab
-ab
-ab
-ab
-ab
-ab
-ab
+bt
+bt
+bt
+bt
+bt
+bt
+bt
+bt
ad
ax
ac
@@ -1409,9 +1412,9 @@ aF
ac
ac
ac
-ab
-ab
-ab
+bt
+bt
+bt
aU
bd
ac
@@ -1420,13 +1423,13 @@ bl
bm
ac
ac
-ab
-ab
-ab
-ab
-ab
-ab
-ab
+bt
+bt
+bt
+bt
+bt
+bt
+bt
ab
aA
aa
@@ -1445,14 +1448,14 @@ aa
aA
aA
ab
-ab
-ab
-ab
-ab
-ab
-ab
-ab
-ab
+bt
+bt
+bt
+bt
+bt
+bt
+bt
+bt
ad
ay
ac
@@ -1461,9 +1464,9 @@ ac
aG
ac
ac
-ab
-ab
-ab
+bt
+bt
+bt
av
be
ac
@@ -1473,12 +1476,12 @@ bn
bo
bo
bq
-ab
-ab
-ab
-ab
-ab
-ab
+bt
+bt
+bt
+bt
+bt
+bt
ab
aA
aa
@@ -1496,16 +1499,16 @@ aa
aa
aA
ab
-ab
-ab
-ab
-ab
-ab
-ab
-ab
-ab
-ab
-ab
+bt
+bt
+bt
+bt
+bt
+bt
+bt
+bt
+bt
+bt
ac
ac
ac
@@ -1513,9 +1516,9 @@ ac
ac
ac
ac
-ab
-ab
-ab
+bt
+bt
+bt
bc
bf
ac
@@ -1525,11 +1528,11 @@ ac
ac
ac
br
-ab
-ab
-ab
-ab
-ab
+bt
+bt
+bt
+bt
+bt
ab
aA
aa
@@ -1548,26 +1551,26 @@ aa
aA
aA
ab
-ab
-ab
-ab
-ab
-ab
-ab
-ab
-ab
-ab
-ab
-ab
+bt
+bt
+bt
+bt
+bt
+bt
+bt
+bt
+bt
+bt
+bt
ac
-ab
+bt
ac
ac
ac
ac
ac
-ab
-ab
+bt
+bt
ac
ac
ac
@@ -1577,11 +1580,11 @@ ac
ac
ac
ac
-ab
-ab
-ab
-ab
-ab
+bt
+bt
+bt
+bt
+bt
ab
aA
aa
@@ -1599,20 +1602,20 @@ aa
aa
aA
ab
-ab
-ab
-ab
-ab
-ab
-ab
-ab
-ab
-ab
-ab
-ab
-ab
-ab
-ab
+bt
+bt
+bt
+bt
+bt
+bt
+bt
+bt
+bt
+bt
+bt
+bt
+bt
+bt
ac
ac
ac
@@ -1628,11 +1631,11 @@ ac
ac
ac
ac
-ab
-ab
-ab
-ab
-ab
+bt
+bt
+bt
+bt
+bt
ab
aA
aa
@@ -1651,26 +1654,26 @@ aa
aa
aA
ab
-ab
-ab
-ab
-ab
-ab
-ab
-ab
-ab
-ab
-ab
-ab
-ab
-ab
-ab
-ab
+bt
+bt
+bt
+bt
+bt
+bt
+bt
+bt
+bt
+bt
+bt
+bt
+bt
+bt
+bt
ac
ac
ac
-ab
-ab
+bt
+bt
ac
ac
ac
@@ -1682,9 +1685,9 @@ ac
ac
ac
ac
-ab
-ab
-ab
+bt
+bt
+bt
ab
aa
aa
@@ -1703,26 +1706,26 @@ aa
aa
aA
ab
-ab
-ab
-ab
-ab
-ab
-ab
-ab
-ab
-ab
-ab
-ab
-ab
-ab
-ab
-ab
-ab
-ab
+bt
+bt
+bt
+bt
+bt
+bt
+bt
+bt
+bt
+bt
+bt
+bt
+bt
+bt
+bt
+bt
+bt
aK
-ab
-ab
+bt
+bt
ac
ac
ac
@@ -1734,8 +1737,8 @@ bp
ac
ac
aR
-ab
-ab
+bt
+bt
ab
aA
aa
@@ -1756,25 +1759,25 @@ aa
aa
aA
ab
-ab
-ab
-ab
-ab
-ab
-ab
-ab
-ab
-ab
-ab
-ab
-ab
-ab
-ab
-ab
-ab
-ab
-ab
-ab
+bt
+bt
+bt
+bt
+bt
+bt
+bt
+bt
+bt
+bt
+bt
+bt
+bt
+bt
+bt
+bt
+bt
+bt
+bt
aP
ac
ac
@@ -1786,8 +1789,8 @@ ac
ac
ac
aR
-ab
-ab
+bt
+bt
ab
aA
aa
@@ -1808,25 +1811,25 @@ aa
aa
aA
ab
-ab
-ab
-ab
-ab
-ab
-ab
-ab
-ab
-ab
-ab
-ab
-ab
-ab
-ab
-ab
-ab
-ab
-ab
-ab
+bt
+bt
+bt
+bt
+bt
+bt
+bt
+bt
+bt
+bt
+bt
+bt
+bt
+bt
+bt
+bt
+bt
+bt
+bt
aQ
aP
ac
@@ -1836,10 +1839,10 @@ ac
ac
ac
ac
-ab
+bt
ac
-ab
-ab
+bt
+bt
ab
aA
aa
@@ -1860,27 +1863,27 @@ aa
aa
aA
ab
-ab
-ab
-ab
-ab
-ab
-ab
-ab
-ab
-ab
-ab
-ab
-ab
-ab
-ab
-ab
-ab
-ab
-ab
-ab
-ab
-ab
+bt
+bt
+bt
+bt
+bt
+bt
+bt
+bt
+bt
+bt
+bt
+bt
+bt
+bt
+bt
+bt
+bt
+bt
+bt
+bt
+bt
ac
ac
ac
@@ -1888,10 +1891,10 @@ ac
aP
ac
bg
-ab
-ab
-ab
-ab
+bt
+bt
+bt
+bt
ab
aa
aa
@@ -1913,26 +1916,26 @@ aa
aA
ab
ab
-ab
-ab
-ab
-ab
-ab
-ab
-ab
-ab
-ab
-ab
-ab
-ab
-ab
-ab
-ab
-ab
-ab
-ab
-ab
-ab
+bt
+bt
+bt
+bt
+bt
+bt
+bt
+bt
+bt
+bt
+bt
+bt
+bt
+bt
+bt
+bt
+bt
+bt
+bt
+bt
ac
ac
ac
@@ -1940,9 +1943,9 @@ ac
ac
ac
aS
-ab
-ab
-ab
+bt
+bt
+bt
ab
aA
aa
@@ -1966,25 +1969,25 @@ aa
aA
aA
ab
-ab
-ab
-ab
-ab
-ab
-ab
-ab
-ab
-ab
-ab
-ab
-ab
-ab
-ab
-ab
-ab
-ab
-ab
-ab
+bt
+bt
+bt
+bt
+bt
+bt
+bt
+bt
+bt
+bt
+bt
+bt
+bt
+bt
+bt
+bt
+bt
+bt
+bt
bg
ac
ac
@@ -1992,9 +1995,9 @@ ac
ac
ac
aS
-ab
-ab
-ab
+bt
+bt
+bt
ab
aA
aa
@@ -2018,25 +2021,25 @@ aa
aa
aA
ab
-ab
-ab
-ab
-ab
-ab
-ab
-ab
-ab
-ab
-ab
-ab
-ab
-ab
-ab
-ab
-ab
-ab
-ab
-ab
+bt
+bt
+bt
+bt
+bt
+bt
+bt
+bt
+bt
+bt
+bt
+bt
+bt
+bt
+bt
+bt
+bt
+bt
+bt
ac
ac
bj
@@ -2044,9 +2047,9 @@ ac
ac
ac
aS
-ab
-ab
-ab
+bt
+bt
+bt
ab
aA
aa
@@ -2071,24 +2074,24 @@ aa
aa
aA
ab
-ab
-ab
-ab
-ab
-ab
-ab
-ab
-ab
-ab
-ab
-ab
-ab
-ab
-ab
-ab
-ab
-ab
-ab
+bt
+bt
+bt
+bt
+bt
+bt
+bt
+bt
+bt
+bt
+bt
+bt
+bt
+bt
+bt
+bt
+bt
+bt
aS
bi
ac
@@ -2096,9 +2099,9 @@ ac
ac
aS
aS
-ab
-ab
-ab
+bt
+bt
+bt
ab
aA
aa
@@ -2124,23 +2127,23 @@ aM
aa
aA
ab
+bt
+bt
+bt
+bt
+bt
+bt
+bt
+bt
+bt
+bt
+bt
+bt
+bt
+bt
+bt
ab
-ab
-ab
-ab
-ab
-ab
-ab
-ab
-ab
-ab
-ab
-ab
-ab
-ab
-ab
-ab
-ab
+bt
aS
ac
ac
@@ -2148,8 +2151,8 @@ ac
ac
aS
aS
-ab
-ab
+bt
+bt
ab
aA
aa
@@ -2176,20 +2179,20 @@ aa
aa
aA
ab
-ab
-ab
-ab
-ab
-ab
-ab
-ab
-ab
-ab
-ab
-ab
-ab
-ab
-ab
+bt
+bt
+bt
+bt
+bt
+bt
+bt
+bt
+bt
+bt
+bt
+bt
+bt
+bt
ab
aA
aS
@@ -2229,18 +2232,18 @@ aa
aA
aA
ab
-ab
-ab
-ab
-ab
-ab
-ab
-ab
-ab
-ab
-ab
-ab
-ab
+bt
+bt
+bt
+bt
+bt
+bt
+bt
+bt
+bt
+bt
+bt
+bt
ab
aA
aA
@@ -2282,17 +2285,17 @@ aa
aA
aA
ab
-ab
-ab
-ab
-ab
-ab
-ab
-ab
-ab
-ab
-ab
-ab
+bt
+bt
+bt
+bt
+bt
+bt
+bt
+bt
+bt
+bt
+bt
ab
aA
aA
@@ -2335,16 +2338,16 @@ aa
aA
aA
ab
-ab
-ab
-ab
-ab
-ab
-ab
-ab
-ab
-ab
-ab
+bt
+bt
+bt
+bt
+bt
+bt
+bt
+bt
+bt
+bt
ab
aA
aa
@@ -2388,16 +2391,16 @@ aa
aA
aA
ab
-ab
-ab
-ab
-ab
-ab
-ab
-ab
-ab
-ab
-ab
+bt
+bt
+bt
+bt
+bt
+bt
+bt
+bt
+bt
+bt
ab
ab
aa
@@ -2448,9 +2451,9 @@ ab
ab
ab
ab
-ab
-ab
-ab
+bt
+bt
+bt
ab
aa
aa
@@ -2933,7 +2936,7 @@ aa
aa
aa
aa
-ab
+bt
aa
aa
aa
diff --git a/_maps/shuttles/emergency_raven.dmm b/_maps/shuttles/emergency_raven.dmm
index 2c04837c79..05446968e7 100644
--- a/_maps/shuttles/emergency_raven.dmm
+++ b/_maps/shuttles/emergency_raven.dmm
@@ -56,7 +56,7 @@
/turf/open/floor/plasteel/dark,
/area/shuttle/escape)
"ah" = (
-/obj/machinery/computer/shuttle,
+/obj/machinery/computer/emergency_shuttle,
/turf/open/floor/plasteel/dark,
/area/shuttle/escape)
"ai" = (
diff --git a/code/__DEFINES/antagonists.dm b/code/__DEFINES/antagonists.dm
index b2f05fd5f8..058fd864bd 100644
--- a/code/__DEFINES/antagonists.dm
+++ b/code/__DEFINES/antagonists.dm
@@ -40,3 +40,19 @@
#define NOT_DOMINATING -1
#define MAX_LEADERS_GANG 4
#define INITIAL_DOM_ATTEMPTS 3
+
+//Bloodsucker defines
+// Bloodsucker related antag datums
+#define ANTAG_DATUM_BLOODSUCKER /datum/antagonist/bloodsucker
+#define ANTAG_DATUM_VASSAL /datum/antagonist/vassal
+//#define ANTAG_DATUM_HUNTER /datum/antagonist/vamphunter Disabled for now
+
+// BLOODSUCKER
+#define BLOODSUCKER_LEVEL_TO_EMBRACE 3
+#define BLOODSUCKER_FRENZY_TIME 25 // How long the vamp stays in frenzy.
+#define BLOODSUCKER_FRENZY_OUT_TIME 300 // How long the vamp goes back into frenzy.
+#define BLOODSUCKER_STARVE_VOLUME 5 // Amount of blood, below which a Vamp is at risk of frenzy.
+
+#define CAT_STRUCTURE "Structures"
+
+#define MARTIALART_HUNTER "hunter-fu"
diff --git a/code/__DEFINES/atom_hud.dm b/code/__DEFINES/atom_hud.dm
index 0e4f0f3a6e..322a150617 100644
--- a/code/__DEFINES/atom_hud.dm
+++ b/code/__DEFINES/atom_hud.dm
@@ -58,6 +58,7 @@
#define ANTAG_HUD_SOULLESS 21
#define ANTAG_HUD_CLOCKWORK 22
#define ANTAG_HUD_BROTHER 23
+#define ANTAG_HUD_BLOODSUCKER 24
// Notification action types
#define NOTIFY_JUMP "jump"
diff --git a/code/__DEFINES/components.dm b/code/__DEFINES/components.dm
index a8c623a889..f95ff93ac3 100644
--- a/code/__DEFINES/components.dm
+++ b/code/__DEFINES/components.dm
@@ -36,8 +36,10 @@
//////////////////////////////////////////////////////////////////
// /datum signals
-#define COMSIG_COMPONENT_ADDED "component_added" //when a component is added to a datum: (/datum/component)
-#define COMSIG_COMPONENT_REMOVING "component_removing" //before a component is removed from a datum because of RemoveComponent: (/datum/component)
+#define COMSIG_COMPONENT_ADDED "component_added" //sent to the new datum parent when a component is added to them: (/datum/component)
+#define COMSIG_COMPONENT_REMOVING "component_removing" //sent to the datum parent before a component is removed from them because of RemoveComponent: (/datum/component)
+#define COMSIG_COMPONENT_UNREGISTER_PARENT "component_unregister_parent" //sent to the component itself when unregistered from a parent
+#define COMSIG_COMPONENT_REGISTER_PARENT "component_register_parent" //sent to the component itself when registered to a parent
#define COMSIG_PARENT_PREQDELETED "parent_preqdeleted" //before a datum's Destroy() is called: (force), returning a nonzero value will cancel the qdel operation
#define COMSIG_PARENT_QDELETING "parent_qdeleting" //just before a datum's Destroy() is called: (force), at this point none of the other components chose to interrupt qdel and Destroy will be called
@@ -133,25 +135,29 @@
#define COMSIG_MOVABLE_POST_THROW "movable_post_throw" //from base of atom/movable/throw_at(): (datum/thrownthing, spin)
#define COMSIG_MOVABLE_Z_CHANGED "movable_ztransit" //from base of atom/movable/onTransitZ(): (old_z, new_z)
#define COMSIG_MOVABLE_SECLUDED_LOCATION "movable_secluded" //called when the movable is placed in an unaccessible area, used for stationloving: ()
-#define COMSIG_MOVABLE_HEAR "movable_hear" //from base of atom/movable/Hear(): (message, atom/movable/speaker, message_language, raw_message, radio_freq, list/spans, message_mode)
+#define COMSIG_MOVABLE_HEAR "movable_hear" //from base of atom/movable/Hear(): (message, atom/movable/speaker, message_language, raw_message, radio_freq, list/spans, message_mode, atom/movable/source)
#define HEARING_MESSAGE 1
#define HEARING_SPEAKER 2
// #define HEARING_LANGUAGE 3
#define HEARING_RAW_MESSAGE 4
/* #define HEARING_RADIO_FREQ 5
#define HEARING_SPANS 6
- #define HEARING_MESSAGE_MODE 7 */
+ #define HEARING_MESSAGE_MODE 7
+ #define HEARING_SOURCE 8*/
#define COMSIG_MOVABLE_DISPOSING "movable_disposing" //called when the movable is added to a disposal holder object for disposal movement: (obj/structure/disposalholder/holder, obj/machinery/disposal/source)
#define COMSIG_MOVABLE_TELEPORTED "movable_teleported" //from base of do_teleport(): (channel, turf/origin, turf/destination)
// /mind signals
-#define COMSIG_MIND_TRANSFER "mind_transfer" //from base of mind/transfer_to(): (new_character, old_character)
+#define COMSIG_PRE_MIND_TRANSFER "pre_mind_transfer" //from base of mind/transfer_to() before it's done: (new_character, old_character)
+ #define COMPONENT_STOP_MIND_TRANSFER 1 //stops the mind transfer from happening.
+#define COMSIG_MIND_TRANSFER "mind_transfer" //from base of mind/transfer_to() when it's done: (new_character, old_character)
// /mob signals
#define COMSIG_MOB_EXAMINATE "mob_examinate" //from base of /mob/verb/examinate(): (atom/A)
#define COMPONENT_ALLOW_EXAMINE 1
#define COMSIG_MOB_DEATH "mob_death" //from base of mob/death(): (gibbed)
-#define COMSIG_MOB_GHOSTIZE "mob_ghostize" //from base of mob/Ghostize(): (can_reenter_corpse)
+ #define COMPONENT_BLOCK_DEATH_BROADCAST 1 //stops the death from being broadcasted in deadchat.
+#define COMSIG_MOB_GHOSTIZE "mob_ghostize" //from base of mob/Ghostize(): (can_reenter_corpse, special, penalize)
#define COMPONENT_BLOCK_GHOSTING 1
#define COMSIG_MOB_ALLOWED "mob_allowed" //from base of obj/allowed(mob/M): (/obj) returns bool, if TRUE the mob has id access to the obj
#define COMSIG_MOB_RECEIVE_MAGIC "mob_receive_magic" //from base of mob/anti_magic_check(): (mob/user, magic, holy, tinfoil, chargecost, self, protection_sources)
@@ -163,7 +169,9 @@
#define COMSIG_MOB_ITEM_AFTERATTACK "mob_item_afterattack" //from base of obj/item/afterattack(): (atom/target, mob/user, proximity_flag, click_parameters)
#define COMSIG_MOB_ATTACK_RANGED "mob_attack_ranged" //from base of mob/RangedAttack(): (atom/A, params)
#define COMSIG_MOB_THROW "mob_throw" //from base of /mob/throw_item(): (atom/target)
-#define COMSIG_MOB_KEY_CHANGE "mob_key_change" //from base of /mob/transfer_ckey()
+#define COMSIG_MOB_KEY_CHANGE "mob_key_change" //from base of /mob/transfer_ckey(): (new_character, old_character)
+#define COMSIG_MOB_PRE_PLAYER_CHANGE "mob_pre_player_change" //sent to the target mob from base of /mob/transfer_ckey() and /mind/transfer_to(): (our_character, their_character)
+// #define COMPONENT_STOP_MIND_TRANSFER 1
#define COMSIG_MOB_UPDATE_SIGHT "mob_update_sight" //from base of /mob/update_sight(): ()
#define COMSIG_MOB_SAY "mob_say" // from /mob/living/say(): (proc args list)
#define COMPONENT_UPPERCASE_SPEECH 1
diff --git a/code/__DEFINES/role_preferences.dm b/code/__DEFINES/role_preferences.dm
index b316c003a9..d5e3f828b8 100644
--- a/code/__DEFINES/role_preferences.dm
+++ b/code/__DEFINES/role_preferences.dm
@@ -35,6 +35,8 @@
#define ROLE_LAVALAND "lavaland"
#define ROLE_INTERNAL_AFFAIRS "internal affairs agent"
#define ROLE_GANG "gangster"
+#define ROLE_BLOODSUCKER "bloodsucker"
+//#define ROLE_MONSTERHUNTER "monster hunter" Disabled for now
//Missing assignment means it's not a gamemode specific role, IT'S NOT A BUG OR ERROR.
//The gamemode specific ones are just so the gamemodes can query whether a player is old enough
@@ -60,7 +62,9 @@ GLOBAL_LIST_INIT(special_roles, list(
ROLE_OVERTHROW = /datum/game_mode/overthrow,
ROLE_INTERNAL_AFFAIRS = /datum/game_mode/traitor/internal_affairs,
ROLE_SENTIENCE,
- ROLE_GANG = /datum/game_mode/gang
+ ROLE_GANG = /datum/game_mode/gang,
+ ROLE_BLOODSUCKER = /datum/game_mode/bloodsucker
+ //ROLE_MONSTERHUNTER Disabled for now
))
//Job defines for what happens when you fail to qualify for any job during job selection
diff --git a/code/__DEFINES/status_effects.dm b/code/__DEFINES/status_effects.dm
index f9360117bb..6d52f4640c 100644
--- a/code/__DEFINES/status_effects.dm
+++ b/code/__DEFINES/status_effects.dm
@@ -46,7 +46,7 @@
#define STATUS_EFFECT_SLEEPING /datum/status_effect/incapacitating/sleeping //the affected is asleep
-#define STATUS_EFFECT_TASED /datum/status_effect/electrode //the affected has been tased, preventing fine muscle control
+#define STATUS_EFFECT_TASED /datum/status_effect/no_combat_mode/electrode/ //the affected has been tased, preventing fine muscle control
#define STATUS_EFFECT_PACIFY /datum/status_effect/pacify //the affected is pacified, preventing direct hostile actions
@@ -68,6 +68,8 @@
#define STATUS_EFFECT_SAWBLEED /datum/status_effect/saw_bleed //if the bleed builds up enough, takes a ton of damage
+#define STATUS_EFFECT_NECKSLICE /datum/status_effect/neck_slice //Creates the flavor messages for the neck-slice
+
#define STATUS_EFFECT_NECROPOLIS_CURSE /datum/status_effect/necropolis_curse
#define CURSE_BLINDING 1 //makes the edges of the target's screen obscured
#define CURSE_SPAWNING 2 //spawns creatures that attack the target only
@@ -83,6 +85,9 @@
#define STATUS_EFFECT_BREASTS_ENLARGEMENT /datum/status_effect/chem/breast_enlarger //Applied slowdown due to the ominous bulk.
#define STATUS_EFFECT_PENIS_ENLARGEMENT /datum/status_effect/chem/penis_enlarger //More applied slowdown, just like the above.
+
+#define STATUS_EFFECT_NO_COMBAT_MODE /datum/status_effect/no_combat_mode //Wont allow combat mode and will disable it
+#define STATUS_EFFECT_MESMERIZE /datum/status_effect/no_combat_mode/mesmerize //Just reskinned no_combat_mode
/////////////
// NEUTRAL //
/////////////
diff --git a/code/__DEFINES/traits.dm b/code/__DEFINES/traits.dm
index 14dbd97ffa..d17db07b50 100644
--- a/code/__DEFINES/traits.dm
+++ b/code/__DEFINES/traits.dm
@@ -66,6 +66,7 @@
#define TRAIT_HUSK "husk"
#define TRAIT_NOCLONE "noclone"
#define TRAIT_CLUMSY "clumsy"
+#define TRAIT_CHUNKYFINGERS "chunkyfingers" //means that you can't use weapons with normal trigger guards.
#define TRAIT_DUMB "dumb"
#define TRAIT_MONKEYLIKE "monkeylike" //sets IsAdvancedToolUser to FALSE
#define TRAIT_PACIFISM "pacifism"
@@ -127,6 +128,12 @@
#define TRAIT_ABDUCTOR_TRAINING "abductor-training"
#define TRAIT_ABDUCTOR_SCIENTIST_TRAINING "abductor-scientist-training"
#define TRAIT_SURGEON "surgeon"
+#define TRAIT_COLDBLOODED "coldblooded" // Your body is literal room temperature. Does not make you immune to the temp.
+#define TRAIT_NONATURALHEAL "nonaturalheal" // Only Admins can heal you. NOTHING else does it unless it's given the god tag.
+#define TRAIT_NORUNNING "norunning" // You walk!
+#define TRAIT_NOMARROW "nomarrow" // You don't make blood, with chemicals or nanites.
+#define TRAIT_NOPULSE "nopulse" // Your heart doesn't beat.
+
//non-mob traits
#define TRAIT_PARALYSIS "paralysis" //Used for limb-based paralysis, where replacing the limb will fix it
@@ -217,5 +224,6 @@
#define LOCKED_HELMET_TRAIT "locked-helmet"
#define NINJA_SUIT_TRAIT "ninja-suit"
#define ANTI_DROP_IMPLANT_TRAIT "anti-drop-implant"
+#define SLEEPING_CARP_TRAIT "sleeping_carp"
#define ABDUCTOR_ANTAGONIST "abductor-antagonist"
#define MADE_UNCLONEABLE "made-uncloneable"
diff --git a/code/__HELPERS/cmp.dm b/code/__HELPERS/cmp.dm
index 87f3a32b0d..4a86f57fcd 100644
--- a/code/__HELPERS/cmp.dm
+++ b/code/__HELPERS/cmp.dm
@@ -23,6 +23,9 @@ GLOBAL_VAR_INIT(cmp_field, "name")
/proc/cmp_records_dsc(datum/data/record/a, datum/data/record/b)
return sorttext(a.fields[GLOB.cmp_field], b.fields[GLOB.cmp_field])
+/proc/cmp_filter_data_priority(list/A, list/B)
+ return A["priority"] - B["priority"]
+
/proc/cmp_ckey_asc(client/a, client/b)
return sorttext(b.ckey, a.ckey)
@@ -92,4 +95,22 @@ GLOBAL_VAR_INIT(cmp_field, "name")
return sorttext(A.sample_object.name, B.sample_object.name)
/proc/cmp_numbered_displays_name_dsc(datum/numbered_display/A, datum/numbered_display/B)
- return sorttext(B.sample_object.name, A.sample_object.name)
\ No newline at end of file
+ return sorttext(B.sample_object.name, A.sample_object.name)
+
+/proc/cmp_quirk_asc(datum/quirk/A, datum/quirk/B)
+ var/a_sign = num2sign(initial(A.value) * -1)
+ var/b_sign = num2sign(initial(B.value) * -1)
+
+ // Neutral traits go last.
+ if(a_sign == 0)
+ a_sign = 2
+ if(b_sign == 0)
+ b_sign = 2
+
+ var/a_name = initial(A.name)
+ var/b_name = initial(B.name)
+
+ if(a_sign != b_sign)
+ return a_sign - b_sign
+ else
+ return sorttext(b_name, a_name)
\ No newline at end of file
diff --git a/code/__HELPERS/unsorted.dm b/code/__HELPERS/unsorted.dm
index 9abe42ea8e..29f5331fd3 100644
--- a/code/__HELPERS/unsorted.dm
+++ b/code/__HELPERS/unsorted.dm
@@ -1548,4 +1548,12 @@ GLOBAL_DATUM_INIT(dview_mob, /mob/dview, new)
/proc/CallAsync(datum/source, proctype, list/arguments)
set waitfor = FALSE
- return call(source, proctype)(arglist(arguments))
\ No newline at end of file
+ return call(source, proctype)(arglist(arguments))
+
+/proc/num2sign(numeric)
+ if(numeric > 0)
+ return 1
+ else if(numeric < 0)
+ return -1
+ else
+ return 0
diff --git a/code/_onclick/ai.dm b/code/_onclick/ai.dm
index 7a495b95e7..2a8289697b 100644
--- a/code/_onclick/ai.dm
+++ b/code/_onclick/ai.dm
@@ -114,7 +114,8 @@
/mob/living/silicon/ai/CtrlClickOn(var/atom/A)
A.AICtrlClick(src)
/mob/living/silicon/ai/AltClickOn(var/atom/A)
- A.AIAltClick(src)
+ if(!A.AIAltClick(src))
+ altclick_listed_turf(A)
/*
The following criminally helpful code is just the previous code cleaned up;
@@ -125,9 +126,10 @@
/* Atom Procs */
/atom/proc/AICtrlClick()
return
+
/atom/proc/AIAltClick(mob/living/silicon/ai/user)
- AltClick(user)
- return
+ return AltClick(user)
+
/atom/proc/AIShiftClick()
return
/atom/proc/AICtrlShiftClick()
@@ -151,6 +153,7 @@
shock_perm(usr)
else
shock_restore(usr)
+ return TRUE
/obj/machinery/door/airlock/AIShiftClick() // Opens and closes doors!
if(obj_flags & EMAGGED)
@@ -185,10 +188,12 @@
return
toggle_on()
add_fingerprint(usr)
+ return TRUE
/* Holopads */
/obj/machinery/holopad/AIAltClick(mob/living/silicon/ai/user)
hangup_all_calls()
+ return TRUE
//
// Override TurfAdjacent for AltClicking
diff --git a/code/_onclick/click.dm b/code/_onclick/click.dm
index e9f50d9212..4c477f3872 100644
--- a/code/_onclick/click.dm
+++ b/code/_onclick/click.dm
@@ -353,8 +353,17 @@
Unused except for AI
*/
/mob/proc/AltClickOn(atom/A)
- A.AltClick(src)
- return
+ if(!A.AltClick(src))
+ altclick_listed_turf(A)
+
+/mob/proc/altclick_listed_turf(atom/A)
+ var/turf/T = get_turf(A)
+ if(T == A.loc || T == A)
+ if(T == listed_turf)
+ listed_turf = null
+ else if(TurfAdjacent(T))
+ listed_turf = T
+ client.statpanel = T.name
/mob/living/carbon/AltClickOn(atom/A)
if(!stat && mind && iscarbon(A) && A != src)
@@ -366,18 +375,7 @@
..()
/atom/proc/AltClick(mob/user)
- SEND_SIGNAL(src, COMSIG_CLICK_ALT, user)
- var/turf/T = get_turf(src)
- if(T && user.TurfAdjacent(T))
- user.listed_turf = T
- user.client.statpanel = T.name
-
-// Use this instead of /mob/proc/AltClickOn(atom/A) where you only want turf content listing without additional atom alt-click interaction
-/atom/proc/AltClickNoInteract(mob/user, atom/A)
- var/turf/T = get_turf(A)
- if(T && user.TurfAdjacent(T))
- user.listed_turf = T
- user.client.statpanel = T.name
+ . = SEND_SIGNAL(src, COMSIG_CLICK_ALT, user)
/mob/proc/TurfAdjacent(turf/T)
return T.Adjacent(src)
diff --git a/code/_onclick/cyborg.dm b/code/_onclick/cyborg.dm
index 7f63b74945..a2d566d566 100644
--- a/code/_onclick/cyborg.dm
+++ b/code/_onclick/cyborg.dm
@@ -110,7 +110,8 @@
/mob/living/silicon/robot/CtrlClickOn(atom/A)
A.BorgCtrlClick(src)
/mob/living/silicon/robot/AltClickOn(atom/A)
- A.BorgAltClick(src)
+ if(!A.BorgAltClick(src))
+ altclick_listed_turf(A)
/atom/proc/BorgCtrlShiftClick(mob/living/silicon/robot/user) //forward to human click if not overridden
CtrlShiftClick(user)
@@ -154,20 +155,17 @@
..()
/atom/proc/BorgAltClick(mob/living/silicon/robot/user)
- AltClick(user)
- return
+ return AltClick(user)
/obj/machinery/door/airlock/BorgAltClick(mob/living/silicon/robot/user) // Eletrifies doors. Forwards to AI code.
if(get_dist(src,user) <= user.interaction_range)
- AIAltClick()
- else
- ..()
+ return AIAltClick()
+ return ..()
/obj/machinery/turretid/BorgAltClick(mob/living/silicon/robot/user) //turret lethal on/off. Forwards to AI code.
if(get_dist(src,user) <= user.interaction_range)
- AIAltClick()
- else
- ..()
+ return AIAltClick()
+ return ..()
/*
As with AI, these are not used in click code,
diff --git a/code/_onclick/hud/_defines.dm b/code/_onclick/hud/_defines.dm
index 167fa989b2..9d53703cdd 100644
--- a/code/_onclick/hud/_defines.dm
+++ b/code/_onclick/hud/_defines.dm
@@ -107,6 +107,10 @@
#define ui_internal "EAST-1:28,CENTER+1:19"//CIT CHANGE - moves internal icon up a little bit to accommodate for the stamina meter
#define ui_mood "EAST-1:28,CENTER-3:10"
+//living
+#define ui_living_pull "EAST-1:28,CENTER-2:15"
+#define ui_living_health "EAST-1:28,CENTER:15"
+
//borgs
#define ui_borg_health "EAST-1:28,CENTER-1:15" //borgs have the health display where humans have the pressure damage indicator.
diff --git a/code/_onclick/hud/action_button.dm b/code/_onclick/hud/action_button.dm
index 2b5af9ddb8..858b8dbd06 100644
--- a/code/_onclick/hud/action_button.dm
+++ b/code/_onclick/hud/action_button.dm
@@ -113,7 +113,7 @@
name = "Show Buttons"
else
name = "Hide Buttons"
- UpdateIcon()
+ update_icon()
usr.update_action_buttons()
/obj/screen/movable/action_button/hide_toggle/AltClick(mob/user)
@@ -125,6 +125,7 @@
moved = FALSE
user.update_action_buttons(TRUE)
to_chat(user, "Action button positions have been reset.")
+ return TRUE
/obj/screen/movable/action_button/hide_toggle/proc/InitialiseIcon(datum/hud/owner_hud)
@@ -134,9 +135,9 @@
hide_icon = settings["toggle_icon"]
hide_state = settings["toggle_hide"]
show_state = settings["toggle_show"]
- UpdateIcon()
+ update_icon()
-/obj/screen/movable/action_button/hide_toggle/proc/UpdateIcon()
+/obj/screen/movable/action_button/hide_toggle/update_icon()
cut_overlays()
add_overlay(mutable_appearance(hide_icon, hidden ? show_state : hide_state))
diff --git a/code/_onclick/hud/human.dm b/code/_onclick/hud/human.dm
index 7f2fca9d98..3b5f851333 100644
--- a/code/_onclick/hud/human.dm
+++ b/code/_onclick/hud/human.dm
@@ -349,6 +349,13 @@
devilsouldisplay = new /obj/screen/devil/soul_counter
infodisplay += devilsouldisplay
+ blood_display = new /obj/screen/bloodsucker/blood_counter // Blood Volume
+ infodisplay += blood_display
+ vamprank_display = new /obj/screen/bloodsucker/rank_counter // Vampire Rank
+ infodisplay += vamprank_display
+ sunlight_display = new /obj/screen/bloodsucker/sunlight_counter // Sunlight
+ infodisplay += sunlight_display
+
zone_select = new /obj/screen/zone_sel()
zone_select.icon = ui_style
zone_select.update_icon(mymob)
diff --git a/code/_onclick/hud/lavaland_elite.dm b/code/_onclick/hud/lavaland_elite.dm
new file mode 100644
index 0000000000..277ea8b898
--- /dev/null
+++ b/code/_onclick/hud/lavaland_elite.dm
@@ -0,0 +1,16 @@
+/datum/hud/lavaland_elite
+ ui_style = 'icons/mob/screen_elite.dmi'
+
+/datum/hud/lavaland_elite/New(mob/living/simple_animal/hostile/asteroid/elite)
+ ..()
+
+ pull_icon = new /obj/screen/pull()
+ pull_icon.icon = ui_style
+ pull_icon.update_icon()
+ pull_icon.screen_loc = ui_living_pull
+ pull_icon.hud = src
+ static_inventory += pull_icon
+
+ healths = new /obj/screen/healths/lavaland_elite()
+ healths.hud = src
+ infodisplay += healths
diff --git a/code/_onclick/hud/screen_objects.dm b/code/_onclick/hud/screen_objects.dm
index 5f128ff76d..86b4b0c024 100644
--- a/code/_onclick/hud/screen_objects.dm
+++ b/code/_onclick/hud/screen_objects.dm
@@ -644,6 +644,12 @@
screen_loc = ui_construct_health
mouse_opacity = MOUSE_OPACITY_TRANSPARENT
+/obj/screen/healths/lavaland_elite
+ icon = 'icons/mob/screen_elite.dmi'
+ icon_state = "elite_health0"
+ screen_loc = ui_health
+ mouse_opacity = MOUSE_OPACITY_TRANSPARENT
+
/obj/screen/healthdoll
name = "health doll"
screen_loc = ui_healthdoll
diff --git a/code/_onclick/item_attack.dm b/code/_onclick/item_attack.dm
index 9cb0c52b9a..f082f2ad16 100644
--- a/code/_onclick/item_attack.dm
+++ b/code/_onclick/item_attack.dm
@@ -110,7 +110,7 @@
totitemdamage *= 0.5
//CIT CHANGES END HERE
apply_damage(totitemdamage, I.damtype) //CIT CHANGE - replaces I.force with totitemdamage
- if(I.damtype == BRUTE)
+ if(I.damtype == BRUTE && !HAS_TRAIT(src, TRAIT_NOMARROW))
if(prob(33))
I.add_mob_blood(src)
var/turf/location = get_turf(src)
@@ -163,4 +163,3 @@
/obj/item/proc/getweight()
return total_mass || w_class * 1.25
-
diff --git a/code/_onclick/observer.dm b/code/_onclick/observer.dm
index 299d7f64f8..f76a745fd2 100644
--- a/code/_onclick/observer.dm
+++ b/code/_onclick/observer.dm
@@ -31,7 +31,7 @@
ShiftClickOn(A)
return
if(modifiers["alt"])
- AltClickNoInteract(src, A)
+ altclick_listed_turf(A)
return
if(modifiers["ctrl"])
CtrlClickOn(A)
diff --git a/code/controllers/subsystem/processing/quirks.dm b/code/controllers/subsystem/processing/quirks.dm
index 4af54b8c70..f96690126a 100644
--- a/code/controllers/subsystem/processing/quirks.dm
+++ b/code/controllers/subsystem/processing/quirks.dm
@@ -12,27 +12,40 @@ PROCESSING_SUBSYSTEM_DEF(quirks)
var/list/quirk_names_by_path = list()
var/list/quirk_points = list() //Assoc. list of quirk names and their "point cost"; positive numbers are good traits, and negative ones are bad
var/list/quirk_objects = list() //A list of all quirk objects in the game, since some may process
+ var/list/quirk_blacklist = list() //A list a list of quirks that can not be used with each other. Format: list(quirk1,quirk2),list(quirk3,quirk4)
/datum/controller/subsystem/processing/quirks/Initialize(timeofday)
if(!quirks.len)
SetupQuirks()
+ quirk_blacklist = list(list("Blind","Nearsighted"),list("Jolly","Depression","Apathetic"),list("Ageusia","Deviant Tastes"),list("Ananas Affinity","Ananas Aversion"))
return ..()
/datum/controller/subsystem/processing/quirks/proc/SetupQuirks()
- for(var/V in subtypesof(/datum/quirk))
+// Sort by Positive, Negative, Neutral; and then by name
+ var/list/quirk_list = sortList(subtypesof(/datum/quirk), /proc/cmp_quirk_asc)
+
+ for(var/V in quirk_list)
var/datum/quirk/T = V
quirks[initial(T.name)] = T
quirk_points[initial(T.name)] = initial(T.value)
quirk_names_by_path[T] = initial(T.name)
/datum/controller/subsystem/processing/quirks/proc/AssignQuirks(mob/living/user, client/cli, spawn_effects, roundstart = FALSE, datum/job/job, silent = FALSE, mob/to_chat_target)
- GenerateQuirks(cli)
- var/list/quirks = cli.prefs.character_quirks.Copy()
+ var/badquirk = FALSE
+ var/list/my_quirks = cli.prefs.all_quirks.Copy()
var/list/cut
- if(job && job.blacklisted_quirks)
+ if(job?.blacklisted_quirks)
cut = filter_quirks(quirks, job)
- for(var/V in quirks)
- user.add_quirk(V, spawn_effects)
+ for(var/V in my_quirks)
+ var/datum/quirk/Q = quirks[V]
+ if(Q)
+ user.add_quirk(Q, spawn_effects)
+ else
+ stack_trace("Invalid quirk \"[V]\" in client [cli.ckey] preferences")
+ cli.prefs.all_quirks -= V
+ badquirk = TRUE
+ if(badquirk)
+ cli.prefs.save_character()
if(!silent && LAZYLEN(cut))
to_chat(to_chat_target || user, "All of your non-neutral character quirks have been cut due to these quirks conflicting with your job assignment: [english_list(cut)].")
@@ -85,8 +98,3 @@ PROCESSING_SUBSYSTEM_DEF(quirks)
quirks -= i
return cut
-
-/datum/controller/subsystem/processing/quirks/proc/GenerateQuirks(client/user)
- if(user.prefs.character_quirks.len)
- return
- user.prefs.character_quirks = user.prefs.all_quirks
diff --git a/code/controllers/subsystem/ticker.dm b/code/controllers/subsystem/ticker.dm
index 6f619fef0b..890725fbb1 100755
--- a/code/controllers/subsystem/ticker.dm
+++ b/code/controllers/subsystem/ticker.dm
@@ -384,8 +384,8 @@ SUBSYSTEM_DEF(ticker)
captainless=0
if(player.mind.assigned_role != player.mind.special_role)
SSjob.EquipRank(N, player.mind.assigned_role, 0)
- if(CONFIG_GET(flag/roundstart_traits) && ishuman(N.new_character))
- SSquirks.AssignQuirks(N.new_character, N.client, TRUE, TRUE, SSjob.GetJob(player.mind.assigned_role), FALSE, N)
+ if(CONFIG_GET(flag/roundstart_traits) && ishuman(N.new_character))
+ SSquirks.AssignQuirks(N.new_character, N.client, TRUE, TRUE, SSjob.GetJob(player.mind.assigned_role), FALSE, N)
CHECK_TICK
if(captainless)
for(var/mob/dead/new_player/N in GLOB.player_list)
diff --git a/code/datums/brain_damage/imaginary_friend.dm b/code/datums/brain_damage/imaginary_friend.dm
index 8337a8e4d5..9005a788b7 100644
--- a/code/datums/brain_damage/imaginary_friend.dm
+++ b/code/datums/brain_damage/imaginary_friend.dm
@@ -149,8 +149,8 @@
friend_talk(message)
-/mob/camera/imaginary_friend/Hear(message, atom/movable/speaker, datum/language/message_language, raw_message, radio_freq, list/spans, message_mode)
- to_chat(src, compose_message(speaker, message_language, raw_message, radio_freq, spans, message_mode))
+/mob/camera/imaginary_friend/Hear(message, atom/movable/speaker, datum/language/message_language, raw_message, radio_freq, list/spans, message_mode, atom/movable/source)
+ to_chat(src, compose_message(speaker, message_language, raw_message, radio_freq, spans, message_mode, FALSE, source))
/mob/camera/imaginary_friend/proc/friend_talk(message)
message = capitalize(trim(copytext(sanitize(message), 1, MAX_MESSAGE_LEN)))
diff --git a/code/datums/components/_component.dm b/code/datums/components/_component.dm
index f938a70308..72b9681f91 100644
--- a/code/datums/components/_component.dm
+++ b/code/datums/components/_component.dm
@@ -53,7 +53,7 @@
// If you want/expect to be moving the component around between parents, use this to register on the parent for signals
/datum/component/proc/RegisterWithParent()
- return
+ SEND_SIGNAL(src, COMSIG_COMPONENT_REGISTER_PARENT) //CITADEL EDIT
/datum/component/proc/Initialize(...)
return
@@ -85,7 +85,7 @@
UnregisterFromParent()
/datum/component/proc/UnregisterFromParent()
- return
+ SEND_SIGNAL(src, COMSIG_COMPONENT_UNREGISTER_PARENT) //CITADEL EDIT
/datum/proc/RegisterSignal(datum/target, sig_type_or_types, proctype, override = FALSE)
if(QDELETED(src) || QDELETED(target))
diff --git a/code/datums/components/bane.dm b/code/datums/components/bane.dm
index 84f8010270..bdfcfed517 100644
--- a/code/datums/components/bane.dm
+++ b/code/datums/components/bane.dm
@@ -19,12 +19,14 @@
src.damage_multiplier = damage_multiplier
/datum/component/bane/RegisterWithParent()
+ . = ..()
if(speciestype)
RegisterSignal(parent, COMSIG_ITEM_AFTERATTACK, .proc/speciesCheck)
else
RegisterSignal(parent, COMSIG_ITEM_AFTERATTACK, .proc/mobCheck)
/datum/component/bane/UnregisterFromParent()
+ . = ..()
UnregisterSignal(parent, COMSIG_ITEM_AFTERATTACK)
/datum/component/bane/proc/speciesCheck(obj/item/source, atom/target, mob/user, proximity_flag, click_parameters)
diff --git a/code/datums/components/bouncy.dm b/code/datums/components/bouncy.dm
index f6a2a89195..c7ca85455b 100644
--- a/code/datums/components/bouncy.dm
+++ b/code/datums/components/bouncy.dm
@@ -21,9 +21,11 @@
RegisterSignal(parent, bounce, .proc/bounce_up)
/datum/component/bouncy/RegisterWithParent()
+ . = ..()
RegisterSignal(parent, bounce_signals, .proc/bounce_up)
/datum/component/bouncy/UnregisterFromParent()
+ . = ..()
UnregisterSignal(parent, bounce_signals)
/datum/component/bouncy/proc/bounce_up(datum/source)
diff --git a/code/datums/components/butchering.dm b/code/datums/components/butchering.dm
index 1518a84456..d5af47ea1f 100644
--- a/code/datums/components/butchering.dm
+++ b/code/datums/components/butchering.dm
@@ -23,17 +23,51 @@
RegisterSignal(parent, COMSIG_ITEM_ATTACK, .proc/onItemAttack)
/datum/component/butchering/proc/onItemAttack(obj/item/source, mob/living/M, mob/living/user)
- if(user.a_intent == INTENT_HARM && M.stat == DEAD && (M.butcher_results || M.guaranteed_butcher_results)) //can we butcher it?
+ if(user.a_intent != INTENT_HARM)
+ return
+ if(M.stat == DEAD && (M.butcher_results || M.guaranteed_butcher_results)) //can we butcher it?
if(butchering_enabled && (can_be_blunt || source.get_sharpness()))
INVOKE_ASYNC(src, .proc/startButcher, source, M, user)
return COMPONENT_ITEM_NO_ATTACK
+ if(ishuman(M) && source.force && source.get_sharpness())
+ var/mob/living/carbon/human/H = M
+ if((H.health <= H.crit_threshold || (user.pulling == H && user.grab_state >= GRAB_NECK) || H.IsSleeping()) && user.zone_selected == BODY_ZONE_HEAD) // Only sleeping, neck grabbed, or crit, can be sliced.
+ if(H.has_status_effect(/datum/status_effect/neck_slice))
+ user.show_message("[H]'s neck has already been already cut, you can't make the bleeding any worse!", 1, \
+ "Their neck has already been already cut, you can't make the bleeding any worse!")
+ return COMPONENT_ITEM_NO_ATTACK
+ INVOKE_ASYNC(src, .proc/startNeckSlice, source, H, user)
+ return COMPONENT_ITEM_NO_ATTACK
+
/datum/component/butchering/proc/startButcher(obj/item/source, mob/living/M, mob/living/user)
to_chat(user, "You begin to butcher [M]...")
playsound(M.loc, butcher_sound, 50, TRUE, -1)
if(do_mob(user, M, speed) && M.Adjacent(source))
Butcher(user, M)
+/datum/component/butchering/proc/startNeckSlice(obj/item/source, mob/living/carbon/human/H, mob/living/user)
+ user.visible_message("[user] is slitting [H]'s throat!", \
+ "You start slicing [H]'s throat!", \
+ "You hear a cutting noise!", ignored_mobs = H)
+ H.show_message("Your throat is being slit by [user]!", 1, \
+ "Something is cutting into your neck!", NONE)
+ log_combat(user, H, "starts slicing the throat of")
+
+ playsound(H.loc, butcher_sound, 50, TRUE, -1)
+ if(do_mob(user, H, CLAMP(500 / source.force, 30, 100)) && H.Adjacent(source))
+ if(H.has_status_effect(/datum/status_effect/neck_slice))
+ user.show_message("[H]'s neck has already been already cut, you can't make the bleeding any worse!", 1, \
+ "Their neck has already been already cut, you can't make the bleeding any worse!")
+ return
+
+ H.visible_message("[user] slits [H]'s throat!", \
+ "[user] slits your throat...")
+ log_combat(user, H, "finishes slicing the throat of")
+ H.apply_damage(source.force, BRUTE, BODY_ZONE_HEAD)
+ H.bleed_rate = CLAMP(H.bleed_rate + 20, 0, 30)
+ H.apply_status_effect(/datum/status_effect/neck_slice)
+
/datum/component/butchering/proc/Butcher(mob/living/butcher, mob/living/meat)
var/turf/T = meat.drop_location()
var/final_effectiveness = effectiveness - meat.butcher_difficulty
diff --git a/code/datums/components/decal.dm b/code/datums/components/decal.dm
index 641dbdb1cf..60317797a7 100644
--- a/code/datums/components/decal.dm
+++ b/code/datums/components/decal.dm
@@ -17,6 +17,7 @@
apply()
/datum/component/decal/RegisterWithParent()
+ . = ..()
if(first_dir)
RegisterSignal(parent, COMSIG_ATOM_DIR_CHANGE, .proc/rotate_react)
if(cleanable)
@@ -25,6 +26,7 @@
RegisterSignal(parent, COMSIG_PARENT_EXAMINE, .proc/examine)
/datum/component/decal/UnregisterFromParent()
+ . = ..()
UnregisterSignal(parent, list(COMSIG_ATOM_DIR_CHANGE, COMSIG_COMPONENT_CLEAN_ACT, COMSIG_PARENT_EXAMINE))
/datum/component/decal/Destroy()
diff --git a/code/datums/components/fantasy/_fantasy.dm b/code/datums/components/fantasy/_fantasy.dm
index 86e016784a..9e8493b6f4 100644
--- a/code/datums/components/fantasy/_fantasy.dm
+++ b/code/datums/components/fantasy/_fantasy.dm
@@ -30,11 +30,13 @@
return ..()
/datum/component/fantasy/RegisterWithParent()
+ . = ..()
var/obj/item/master = parent
originalName = master.name
modify()
/datum/component/fantasy/UnregisterFromParent()
+ . = ..()
unmodify()
/datum/component/fantasy/InheritComponent(datum/component/fantasy/newComp, original, list/arguments)
diff --git a/code/datums/components/igniter.dm b/code/datums/components/igniter.dm
index b40383e828..13944b1200 100644
--- a/code/datums/components/igniter.dm
+++ b/code/datums/components/igniter.dm
@@ -9,6 +9,7 @@
src.fire_stacks = fire_stacks
/datum/component/igniter/RegisterWithParent()
+ . = ..()
if(ismachinery(parent) || isstructure(parent) || isgun(parent)) // turrets, etc
RegisterSignal(parent, COMSIG_PROJECTILE_ON_HIT, .proc/projectile_hit)
else if(isitem(parent))
@@ -17,6 +18,7 @@
RegisterSignal(parent, COMSIG_HOSTILE_ATTACKINGTARGET, .proc/hostile_attackingtarget)
/datum/component/igniter/UnregisterFromParent()
+ . = ..()
UnregisterSignal(parent, list(COMSIG_ITEM_AFTERATTACK, COMSIG_HOSTILE_ATTACKINGTARGET, COMSIG_PROJECTILE_ON_HIT))
/datum/component/igniter/proc/item_afterattack(obj/item/source, atom/target, mob/user, proximity_flag, click_parameters)
diff --git a/code/datums/components/knockback.dm b/code/datums/components/knockback.dm
index b4fcaa2dd8..988a0e575e 100644
--- a/code/datums/components/knockback.dm
+++ b/code/datums/components/knockback.dm
@@ -10,6 +10,7 @@
src.throw_anchored = throw_anchored
/datum/component/knockback/RegisterWithParent()
+ . = ..()
if(ismachinery(parent) || isstructure(parent) || isgun(parent)) // turrets, etc
RegisterSignal(parent, COMSIG_PROJECTILE_ON_HIT, .proc/projectile_hit)
else if(isitem(parent))
@@ -18,6 +19,7 @@
RegisterSignal(parent, COMSIG_HOSTILE_ATTACKINGTARGET, .proc/hostile_attackingtarget)
/datum/component/knockback/UnregisterFromParent()
+ . = ..()
UnregisterSignal(parent, list(COMSIG_ITEM_AFTERATTACK, COMSIG_HOSTILE_ATTACKINGTARGET, COMSIG_PROJECTILE_ON_HIT))
/datum/component/knockback/proc/item_afterattack(obj/item/source, atom/target, mob/user, proximity_flag, click_parameters)
diff --git a/code/datums/components/lifesteal.dm b/code/datums/components/lifesteal.dm
index c7a78e10a3..9d62d32866 100644
--- a/code/datums/components/lifesteal.dm
+++ b/code/datums/components/lifesteal.dm
@@ -10,6 +10,7 @@
src.flat_heal = flat_heal
/datum/component/lifesteal/RegisterWithParent()
+ . = ..()
if(isgun(parent))
RegisterSignal(parent, COMSIG_PROJECTILE_ON_HIT, .proc/projectile_hit)
else if(isitem(parent))
@@ -18,6 +19,7 @@
RegisterSignal(parent, COMSIG_HOSTILE_ATTACKINGTARGET, .proc/hostile_attackingtarget)
/datum/component/lifesteal/UnregisterFromParent()
+ . = ..()
UnregisterSignal(parent, list(COMSIG_ITEM_AFTERATTACK, COMSIG_HOSTILE_ATTACKINGTARGET, COMSIG_PROJECTILE_ON_HIT))
/datum/component/lifesteal/proc/item_afterattack(obj/item/source, atom/target, mob/user, proximity_flag, click_parameters)
diff --git a/code/datums/components/mood.dm b/code/datums/components/mood.dm
index a0e6f97de0..2ecd77546d 100644
--- a/code/datums/components/mood.dm
+++ b/code/datums/components/mood.dm
@@ -150,15 +150,6 @@
if(9)
setSanity(sanity+0.4, maximum=SANITY_GREAT)
- if(HAS_TRAIT(owner, TRAIT_DEPRESSION))
- if(prob(0.05))
- add_event(null, "depression", /datum/mood_event/depression)
- clear_event(null, "jolly")
- if(HAS_TRAIT(owner, TRAIT_JOLLY))
- if(prob(0.05))
- add_event(null, "jolly", /datum/mood_event/jolly)
- clear_event(null, "depression")
-
HandleNutrition(owner)
/datum/component/mood/proc/setSanity(amount, minimum=SANITY_INSANE, maximum=SANITY_NEUTRAL)//I'm sure bunging this in here will have no negative repercussions.
diff --git a/code/datums/components/nanites.dm b/code/datums/components/nanites.dm
index 362961a24f..0ef13b514b 100644
--- a/code/datums/components/nanites.dm
+++ b/code/datums/components/nanites.dm
@@ -34,6 +34,7 @@
cloud_sync()
/datum/component/nanites/RegisterWithParent()
+ . = ..()
RegisterSignal(parent, COMSIG_HAS_NANITES, .proc/confirm_nanites)
RegisterSignal(parent, COMSIG_NANITE_UI_DATA, .proc/nanite_ui_data)
RegisterSignal(parent, COMSIG_NANITE_GET_PROGRAMS, .proc/get_programs)
@@ -57,6 +58,7 @@
RegisterSignal(parent, COMSIG_NANITE_SIGNAL, .proc/receive_signal)
/datum/component/nanites/UnregisterFromParent()
+ . = ..()
UnregisterSignal(parent, list(COMSIG_HAS_NANITES,
COMSIG_NANITE_UI_DATA,
COMSIG_NANITE_GET_PROGRAMS,
diff --git a/code/datums/components/orbiter.dm b/code/datums/components/orbiter.dm
index efa0fd14d5..05174c196b 100644
--- a/code/datums/components/orbiter.dm
+++ b/code/datums/components/orbiter.dm
@@ -20,12 +20,14 @@
begin_orbit(orbiter, radius, clockwise, rotation_speed, rotation_segments, pre_rotation)
/datum/component/orbiter/RegisterWithParent()
+ . = ..()
var/atom/target = parent
while(ismovableatom(target))
RegisterSignal(target, COMSIG_MOVABLE_MOVED, .proc/move_react)
target = target.loc
/datum/component/orbiter/UnregisterFromParent()
+ . = ..()
var/atom/target = parent
while(ismovableatom(target))
UnregisterSignal(target, COMSIG_MOVABLE_MOVED)
diff --git a/code/datums/components/rotation.dm b/code/datums/components/rotation.dm
index 81ff2d517d..422d73520e 100644
--- a/code/datums/components/rotation.dm
+++ b/code/datums/components/rotation.dm
@@ -106,6 +106,7 @@
if(!can_be_rotated.Invoke(user, rotation) || !can_user_rotate.Invoke(user, rotation))
return
BaseRot(user, rotation)
+ return TRUE
/datum/component/simple_rotation/proc/WrenchRot(datum/source, obj/item/I, mob/living/user)
if(!can_be_rotated.Invoke(user,default_rotation_direction) || !can_user_rotate.Invoke(user,default_rotation_direction))
diff --git a/code/datums/components/shrapnel.dm b/code/datums/components/shrapnel.dm
index a911221f26..4d1fe21b95 100644
--- a/code/datums/components/shrapnel.dm
+++ b/code/datums/components/shrapnel.dm
@@ -13,10 +13,12 @@
src.override_projectile_range = override_projectile_range
/datum/component/shrapnel/RegisterWithParent()
+ . = ..()
if(ismachinery(parent) || isstructure(parent) || isgun(parent)) // turrets, etc
RegisterSignal(parent, COMSIG_PROJECTILE_ON_HIT, .proc/projectile_hit)
/datum/component/shrapnel/UnregisterFromParent()
+ . = ..()
UnregisterSignal(parent, list(COMSIG_PROJECTILE_ON_HIT))
/datum/component/shrapnel/proc/projectile_hit(atom/fired_from, atom/movable/firer, atom/target, Angle)
diff --git a/code/datums/components/storage/storage.dm b/code/datums/components/storage/storage.dm
index c52427b546..76a9b2e6aa 100644
--- a/code/datums/components/storage/storage.dm
+++ b/code/datums/components/storage/storage.dm
@@ -762,7 +762,7 @@
if(!isliving(user) || !user.CanReach(parent))
return
if(check_locked(source, user, TRUE))
- return
+ return TRUE
var/atom/A = parent
if(!quickdraw)
@@ -770,19 +770,20 @@
user_show_to_mob(user)
if(rustle_sound)
playsound(A, "rustle", 50, 1, -5)
- return
+ return TRUE
- if(!user.incapacitated())
+ if(user.can_hold_items() && !user.incapacitated())
var/obj/item/I = locate() in real_location()
if(!I)
return
A.add_fingerprint(user)
remove_from_storage(I, get_turf(user))
if(!user.put_in_hands(I))
- to_chat(user, "You fumble for [I] and it falls on the floor.")
- return
+ user.visible_message("[user] fumbles with the [parent], letting [I] fall on the floor.", \
+ "You fumble with [parent], letting [I] fall on the floor.")
+ return TRUE
user.visible_message("[user] draws [I] from [parent]!", "You draw [I] from [parent].")
- return
+ return TRUE
/datum/component/storage/proc/action_trigger(datum/signal_source, datum/action/source)
gather_mode_switch(source.owner)
diff --git a/code/datums/components/summoning.dm b/code/datums/components/summoning.dm
index 552959603d..61718301b3 100644
--- a/code/datums/components/summoning.dm
+++ b/code/datums/components/summoning.dm
@@ -24,6 +24,7 @@
src.faction = faction
/datum/component/summoning/RegisterWithParent()
+ . = ..()
if(ismachinery(parent) || isstructure(parent) || isgun(parent)) // turrets, etc
RegisterSignal(parent, COMSIG_PROJECTILE_ON_HIT, .proc/projectile_hit)
else if(isitem(parent))
@@ -32,6 +33,7 @@
RegisterSignal(parent, COMSIG_HOSTILE_ATTACKINGTARGET, .proc/hostile_attackingtarget)
/datum/component/summoning/UnregisterFromParent()
+ . = ..()
UnregisterSignal(parent, list(COMSIG_ITEM_AFTERATTACK, COMSIG_HOSTILE_ATTACKINGTARGET, COMSIG_PROJECTILE_ON_HIT))
/datum/component/summoning/proc/item_afterattack(obj/item/source, atom/target, mob/user, proximity_flag, click_parameters)
diff --git a/code/datums/components/tactical.dm b/code/datums/components/tactical.dm
index 5917fc3009..ba028e2fd5 100644
--- a/code/datums/components/tactical.dm
+++ b/code/datums/components/tactical.dm
@@ -9,10 +9,12 @@
src.allowed_slot = allowed_slot
/datum/component/tactical/RegisterWithParent()
+ . = ..()
RegisterSignal(parent, COMSIG_ITEM_EQUIPPED, .proc/modify)
RegisterSignal(parent, COMSIG_ITEM_DROPPED, .proc/unmodify)
/datum/component/tactical/UnregisterFromParent()
+ . = ..()
UnregisterSignal(parent, list(COMSIG_ITEM_EQUIPPED, COMSIG_ITEM_DROPPED))
unmodify()
diff --git a/code/datums/components/virtual_reality.dm b/code/datums/components/virtual_reality.dm
index 7bad836e47..2f0405af2e 100644
--- a/code/datums/components/virtual_reality.dm
+++ b/code/datums/components/virtual_reality.dm
@@ -1,128 +1,245 @@
+/**
+ * The virtual reality turned component.
+ * Originally created to overcome issues of mob polymorphing locking the player inside virtual reality
+ * and allow for a more "immersive" virtual reality in a virtual reality experience.
+ * It relies on comically complex order of logic, expect things to break if procs such as mind/transfer_to() are revamped.
+ * In short, a barebone not so hardcoded VR framework.
+ * If you plan to add more devices that make use of this component, remember to isolate their code outta here where possible.
+ */
/datum/component/virtual_reality
can_transfer = TRUE
- var/datum/mind/mastermind // where is my mind t. pixies
+ //the player's mind (not the parent's), should something happen to them or to their mob.
+ var/datum/mind/mastermind
+ //the current mob's mind, which we need to keep track for mind transfer.
var/datum/mind/current_mind
- var/obj/machinery/vr_sleeper/vr_sleeper
+ //the action datum used by the mob to quit the vr session.
var/datum/action/quit_vr/quit_action
+ //This one's name should be self explainatory, currently used for emags.
var/you_die_in_the_game_you_die_for_real = FALSE
- var/datum/component/virtual_reality/inception //The component works on a very fragile link betwixt mind, ckey and death.
+ //Used to allow people to play recursively playing vr while playing vr without many issues.
+ var/datum/component/virtual_reality/level_below
+ var/datum/component/virtual_reality/level_above
+ //Used to stop the component from executing certain functions that'd cause us some issues otherwise.
+ //FALSE if there is a connected player, otherwise TRUE.
+ var/session_paused = TRUE
+ //Used to stop unwarranted behaviour from happening in cases where the master mind transference is unsupported. Set on Initialize().
+ var/allow_mastermind_transfer = FALSE
-/datum/component/virtual_reality/Initialize(mob/M, obj/machinery/vr_sleeper/gaming_pod, yolo = FALSE, new_char = TRUE)
- if(!ismob(parent) || !istype(M))
+/datum/component/virtual_reality/Initialize(yolo = FALSE, _allow_mastermind_transfer = FALSE)
+ var/mob/M = parent
+ if(!istype(M) || !M.mind)
return COMPONENT_INCOMPATIBLE
- var/mob/vr_M = parent
- mastermind = M.mind
- RegisterSignal(M, list(COMSIG_MOB_DEATH, COMSIG_PARENT_QDELETING), .proc/game_over)
- RegisterSignal(M, COMSIG_MOB_KEY_CHANGE, .proc/switch_player)
- RegisterSignal(mastermind, COMSIG_MIND_TRANSFER, .proc/switch_player)
you_die_in_the_game_you_die_for_real = yolo
- quit_action = new()
- if(gaming_pod)
- vr_sleeper = gaming_pod
- RegisterSignal(vr_sleeper, COMSIG_ATOM_EMAG_ACT, .proc/you_only_live_once)
- RegisterSignal(vr_sleeper, COMSIG_MACHINE_EJECT_OCCUPANT, .proc/revert_to_reality)
- vr_M.ckey = M.ckey
- var/datum/component/virtual_reality/clusterfk = M.GetComponent(/datum/component/virtual_reality)
- if(clusterfk && !clusterfk.inception)
- clusterfk.inception = src
- SStgui.close_user_uis(M, src)
+ allow_mastermind_transfer = _allow_mastermind_transfer
+ quit_action = new
+
+/datum/component/virtual_reality/Destroy()
+ QDEL_NULL(quit_action)
+ if(level_above)
+ level_above.level_below = null
+ level_above = null
+ if(level_below)
+ level_below.level_above = null
+ level_below = null
+ return ..()
/datum/component/virtual_reality/RegisterWithParent()
+ . = ..()
var/mob/M = parent
current_mind = M.mind
+ if(!quit_action)
+ quit_action = new
quit_action.Grant(M)
- RegisterSignal(quit_action, COMSIG_ACTION_TRIGGER, .proc/revert_to_reality)
+ RegisterSignal(quit_action, COMSIG_ACTION_TRIGGER, .proc/action_trigger)
RegisterSignal(M, list(COMSIG_MOB_DEATH, COMSIG_PARENT_QDELETING), .proc/game_over)
RegisterSignal(M, COMSIG_MOB_GHOSTIZE, .proc/be_a_quitter)
- RegisterSignal(M, COMSIG_MOB_KEY_CHANGE, .proc/pass_me_the_remote)
- RegisterSignal(current_mind, COMSIG_MIND_TRANSFER, .proc/pass_me_the_remote)
- mastermind.current.audiovisual_redirect = M
- if(vr_sleeper)
- vr_sleeper.vr_mob = M
+ RegisterSignal(M, COMSIG_MOB_KEY_CHANGE, .proc/on_player_transfer)
+ RegisterSignal(current_mind, COMSIG_MIND_TRANSFER, .proc/on_player_transfer)
+ RegisterSignal(current_mind, COMSIG_PRE_MIND_TRANSFER, .proc/pre_player_transfer)
+ if(mastermind?.current)
+ mastermind.current.audiovisual_redirect = M
/datum/component/virtual_reality/UnregisterFromParent()
- quit_action.Remove(parent)
+ . = ..()
+ if(quit_action)
+ quit_action.Remove(parent)
+ UnregisterSignal(quit_action, COMSIG_ACTION_TRIGGER)
UnregisterSignal(parent, list(COMSIG_MOB_DEATH, COMSIG_PARENT_QDELETING, COMSIG_MOB_KEY_CHANGE, COMSIG_MOB_GHOSTIZE))
- UnregisterSignal(current_mind, COMSIG_MIND_TRANSFER)
- UnregisterSignal(quit_action, COMSIG_ACTION_TRIGGER)
+ UnregisterSignal(current_mind, list(COMSIG_MIND_TRANSFER, COMSIG_PRE_MIND_TRANSFER))
current_mind = null
- mastermind.current.audiovisual_redirect = null
+ if(mastermind?.current)
+ mastermind.current.audiovisual_redirect = null
-/datum/component/virtual_reality/proc/switch_player(datum/source, mob/new_mob, mob/old_mob)
- if(vr_sleeper || !new_mob.mind)
- // Machineries currently don't deal up with the occupant being polymorphed et similar... Or did something fuck up?
- revert_to_reality()
- return
- old_mob.audiovisual_redirect = null
- new_mob.audiovisual_redirect = parent
-
-/datum/component/virtual_reality/proc/action_trigger(datum/signal_source, datum/action/source)
- if(source != quit_action)
- return COMPONENT_ACTION_BLOCK_TRIGGER
- revert_to_reality(signal_source)
+/**
+ * Called when attempting to connect a mob to a virtual reality mob.
+ * This will return FALSE if the mob is without player or dead. TRUE otherwise
+ */
+/datum/component/virtual_reality/proc/connect(mob/M)
+ var/mob/vr_M = parent
+ if(!M.mind || M.stat == DEAD || !vr_M.mind || vr_M.stat == DEAD)
+ return FALSE
+ var/datum/component/virtual_reality/VR = M.GetComponent(/datum/component/virtual_reality)
+ if(VR)
+ VR.level_below = src
+ level_above = VR
+ M.transfer_ckey(vr_M, FALSE)
+ mastermind = M.mind
+ mastermind.current.audiovisual_redirect = parent
+ RegisterSignal(mastermind, COMSIG_PRE_MIND_TRANSFER, .proc/switch_player)
+ RegisterSignal(M, list(COMSIG_MOB_DEATH, COMSIG_PARENT_QDELETING), .proc/game_over)
+ RegisterSignal(M, COMSIG_MOB_PRE_PLAYER_CHANGE, .proc/player_hijacked)
+ SStgui.close_user_uis(vr_M, src)
+ session_paused = FALSE
+ return TRUE
+/**
+ * emag_act() hook. Makes the game deadlier, killing the mastermind mob too should the parent die.
+ */
/datum/component/virtual_reality/proc/you_only_live_once()
- if(you_die_in_the_game_you_die_for_real || vr_sleeper?.only_current_user_can_interact)
+ if(you_die_in_the_game_you_die_for_real)
return FALSE
you_die_in_the_game_you_die_for_real = TRUE
return TRUE
-/datum/component/virtual_reality/proc/pass_me_the_remote(datum/source, mob/new_mob)
- if(new_mob == mastermind.current)
- revert_to_reality(source)
- return TRUE
- new_mob.TakeComponent(src)
- return TRUE
+/**
+ * Called when the mastermind mind is transferred to another mob.
+ * This is pretty much just going to simply quit the session until machineries support polymorphed occupants etcetera.
+ */
+/datum/component/virtual_reality/proc/switch_player(datum/source, mob/new_mob, mob/old_mob)
+ if(session_paused)
+ return
+ if(!allow_mastermind_transfer)
+ quit()
+ return COMPONENT_STOP_MIND_TRANSFER
+ UnregisterSignal(old_mob, list(COMSIG_MOB_DEATH, COMSIG_PARENT_QDELETING, COMSIG_MOB_PRE_PLAYER_CHANGE))
+ RegisterSignal(new_mob, list(COMSIG_MOB_DEATH, COMSIG_PARENT_QDELETING), .proc/game_over)
+ RegisterSignal(new_mob, COMSIG_MOB_PRE_PLAYER_CHANGE, .proc/player_hijacked)
+ old_mob.audiovisual_redirect = null
+ new_mob.audiovisual_redirect = parent
+/**
+ * Called to stop the player mind from being transferred should the new mob happen to be one of our masterminds'.
+ * Since the target's mind.current is going to be null'd in the mind transfer process,
+ * This has to be done in a different signal proc than on_player_transfer(), by then the mastermind.current will be null.
+ */
+/datum/component/virtual_reality/proc/pre_player_transfer(datum/source, mob/new_mob, mob/old_mob)
+ if(!mastermind || session_paused)
+ return
+ if(new_mob == mastermind.current)
+ quit()
+ return COMPONENT_STOP_MIND_TRANSFER
+ if(!level_above)
+ return
+ var/datum/component/virtual_reality/VR = level_above
+ while(VR)
+ if(VR.mastermind.current == new_mob)
+ VR.quit() //this will revert the ckey back to new_mob.
+ return COMPONENT_STOP_MIND_TRANSFER
+ VR = VR.level_above
+
+/**
+ * Called when someone or something else is somewhat about to replace the mastermind's mob key somehow.
+ * And potentially lock the player in a broken virtual reality plot. Not really something to be proud of.
+ */
+/datum/component/virtual_reality/proc/player_hijacked(datum/source, mob/our_character, mob/their_character)
+ if(session_paused)
+ return
+ if(!their_character)
+ quit(cleanup = TRUE)
+ return
+ var/will_it_be_handled_in_their_pre_player_transfer = FALSE
+ var/datum/component/virtual_reality/VR = src
+ while(VR)
+ if(VR.parent == their_character)
+ will_it_be_handled_in_their_pre_player_transfer = TRUE
+ break
+ VR = VR.level_below
+ if(!will_it_be_handled_in_their_pre_player_transfer) //it's not the player playing shenanigeans, abandon all ships.
+ quit(cleanup = TRUE)
+
+/**
+ * Takes care of moving the component from a mob to another when their mind or ckey is transferred.
+ * The very reason this component even exists (else one would be stuck playing as a monky if monkyified)
+ */
+/datum/component/virtual_reality/proc/on_player_transfer(datum/source, mob/new_mob, mob/old_mob)
+ new_mob.TakeComponent(src)
+
+/**
+ * Required for the component to be transferable from mob to mob.
+ */
/datum/component/virtual_reality/PostTransfer()
if(!ismob(parent))
return COMPONENT_INCOMPATIBLE
+/**
+ *The following procs simply acts as hooks for quit(), since components do not use callbacks anymore
+ */
+/datum/component/virtual_reality/proc/action_trigger(datum/signal_source, datum/action/source)
+ quit()
+ return COMPONENT_ACTION_BLOCK_TRIGGER
+
/datum/component/virtual_reality/proc/revert_to_reality(datum/source)
- quit_it()
+ quit()
/datum/component/virtual_reality/proc/game_over(datum/source)
- quit_it(TRUE, TRUE)
+ quit(you_die_in_the_game_you_die_for_real, TRUE)
+ return COMPONENT_BLOCK_DEATH_BROADCAST
-/datum/component/virtual_reality/proc/be_a_quitter(datum/source, can_reenter_corpse)
- quit_it()
- return COMPONENT_BLOCK_GHOSTING
+/datum/component/virtual_reality/proc/be_a_quitter(datum/source, can_reenter_corpse, special = FALSE, penalize = FALSE)
+ if(!special)
+ quit()
+ return COMPONENT_BLOCK_GHOSTING
-/datum/component/virtual_reality/proc/virtual_reality_in_a_virtual_reality(mob/player, killme = FALSE, datum/component/virtual_reality/yo_dawg)
+/datum/component/virtual_reality/proc/machine_destroyed(datum/source)
+ quit(cleanup = TRUE)
+
+/**
+ * Takes care of deleting itself, moving the player back to the mastermind's current and queueing the parent for deletion.
+ * It supports nested virtual realities by recursively calling vr_in_a_vr(), which in turns calls quit(),
+ * up to the deepest level, where the ckey will be transferred back to our mastermind's mob instead.
+ * The above operation is skipped when session_paused is TRUE (ergo no player in control of the current mob).
+ * vars:
+ * * deathcheck is used to kill the master, you want this FALSE unless for stuff that doesn't involve emagging.
+ * * cleanup is used to queue the parent for the next vr_clean_master's run, where they'll be deleted should they be dead.
+ * * mob/override is used for the recursive virtual reality explained above and shouldn't be used outside of vr_in_a_vr().
+ */
+/datum/component/virtual_reality/proc/quit(deathcheck = FALSE, cleanup = FALSE, mob/override)
var/mob/M = parent
- quit_it(FALSE, killme, player, yo_dawg)
- yo_dawg.inception = null
- if(killme)
- M.death(FALSE)
-
-/datum/component/virtual_reality/proc/quit_it(deathcheck = FALSE, cleanup = FALSE, mob/override)
- var/mob/M = parent
- var/mob/dreamer = override ? override : mastermind.current
- if(!mastermind)
- to_chat(M, "You feel a dreadful sensation, something terrible happened. You try to wake up, but you find yourself unable to...")
- else
- var/key_transfer = FALSE
- if(inception?.parent)
- inception.virtual_reality_in_a_virtual_reality(dreamer, cleanup, src)
+ if(!session_paused)
+ session_paused = TRUE
+ var/mob/dreamer = override || mastermind.current
+ if(!dreamer) //This shouldn't happen.
+ stack_trace("virtual reality component quit() called without a mob to transfer the parent ckey to.")
+ to_chat(M, "You feel a dreadful sensation, something terrible happened. You try to wake up, but you find yourself unable to...")
+ qdel(src)
+ return
+ if(level_below?.parent)
+ level_below.vr_in_a_vr(dreamer, deathcheck, (deathcheck && cleanup))
else
- key_transfer = TRUE
- if(key_transfer)
M.transfer_ckey(dreamer, FALSE)
- dreamer.stop_sound_channel(CHANNEL_HEARTBEAT)
- dreamer.audiovisual_redirect = null
- if(deathcheck && you_die_in_the_game_you_die_for_real)
- to_chat(mastermind, "You feel everything fading away...")
- dreamer.death(FALSE)
- if(cleanup)
- var/obj/effect/vr_clean_master/cleanbot = locate() in get_area(M)
- if(cleanbot)
- LAZYADD(cleanbot.corpse_party, M)
- if(vr_sleeper)
- vr_sleeper.vr_mob = null
- vr_sleeper = null
- qdel(src)
+ if(deathcheck)
+ to_chat(dreamer, "You feel everything fading away...")
+ dreamer.death(FALSE)
+ mastermind.current.audiovisual_redirect = null
+ if(!cleanup)
+ if(level_above)
+ level_above.level_below = null
+ level_above = null
+ UnregisterSignal(mastermind.current, list(COMSIG_MOB_DEATH, COMSIG_PARENT_QDELETING, COMSIG_MOB_PRE_PLAYER_CHANGE))
+ UnregisterSignal(mastermind, COMSIG_PRE_MIND_TRANSFER)
+ mastermind = null
+ if(cleanup)
+ var/obj/effect/vr_clean_master/cleanbot = locate() in get_area(M)
+ if(cleanbot)
+ LAZYOR(cleanbot.corpse_party, M)
+ qdel(src)
-/datum/component/virtual_reality/Destroy()
- var/datum/action/quit_vr/delet_me = quit_action
- . = ..()
- qdel(delet_me)
\ No newline at end of file
+/**
+ * Used for recursive virtual realities shenanigeans and should be called only through the above proc.
+ */
+/datum/component/virtual_reality/proc/vr_in_a_vr(mob/player, deathcheck = FALSE, lethal_cleanup = FALSE)
+ var/mob/M = parent
+ quit(deathcheck, lethal_cleanup, player)
+ M.audiovisual_redirect = null
+ if(lethal_cleanup)
+ M.death(FALSE)
diff --git a/code/datums/components/wet_floor.dm b/code/datums/components/wet_floor.dm
index 38b17993d8..d6c5c0bf83 100644
--- a/code/datums/components/wet_floor.dm
+++ b/code/datums/components/wet_floor.dm
@@ -34,10 +34,12 @@
last_process = world.time
/datum/component/wet_floor/RegisterWithParent()
+ . = ..()
RegisterSignal(parent, COMSIG_TURF_IS_WET, .proc/is_wet)
RegisterSignal(parent, COMSIG_TURF_MAKE_DRY, .proc/dry)
/datum/component/wet_floor/UnregisterFromParent()
+ . = ..()
UnregisterSignal(parent, list(COMSIG_TURF_IS_WET, COMSIG_TURF_MAKE_DRY))
/datum/component/wet_floor/Destroy()
diff --git a/code/datums/datacore.dm b/code/datums/datacore.dm
index 60bb24c8c2..cf33fa3633 100644
--- a/code/datums/datacore.dm
+++ b/code/datums/datacore.dm
@@ -255,7 +255,7 @@
M.fields["alg_d"] = "No allergies have been detected in this patient."
M.fields["cdi"] = "None"
M.fields["cdi_d"] = "No diseases have been diagnosed at the moment."
- M.fields["notes"] = "No notes."
+ M.fields["notes"] = H.get_trait_string(medical)
medical += M
//Security Record
diff --git a/code/datums/datumvars.dm b/code/datums/datumvars.dm
index e44134f01f..feac1de972 100644
--- a/code/datums/datumvars.dm
+++ b/code/datums/datumvars.dm
@@ -1398,3 +1398,29 @@
var/mob/living/carbon/human/H = locate(href_list["copyoutfit"]) in GLOB.carbon_list
if(istype(H))
H.copy_outfit()
+ else if(href_list["modquirks"])
+ if(!check_rights(R_SPAWN))
+ return
+
+ var/mob/living/carbon/human/H = locate(href_list["modquirks"]) in GLOB.mob_list
+ if(!istype(H))
+ to_chat(usr, "This can only be done to instances of type /mob/living/carbon/human")
+ return
+
+ var/list/options = list("Clear"="Clear")
+ for(var/x in subtypesof(/datum/quirk))
+ var/datum/quirk/T = x
+ var/qname = initial(T.name)
+ options[H.has_quirk(T) ? "[qname] (Remove)" : "[qname] (Add)"] = T
+
+ var/result = input(usr, "Choose quirk to add/remove","Quirk Mod") as null|anything in options
+ if(result)
+ if(result == "Clear")
+ for(var/datum/quirk/q in H.roundstart_quirks)
+ H.remove_quirk(q.type)
+ else
+ var/T = options[result]
+ if(H.has_quirk(T))
+ H.remove_quirk(T)
+ else
+ H.add_quirk(T,TRUE)
diff --git a/code/datums/dna.dm b/code/datums/dna.dm
index 8a3fd82cb9..ccc5d97cf8 100644
--- a/code/datums/dna.dm
+++ b/code/datums/dna.dm
@@ -254,7 +254,6 @@
if(icon_update)
update_body()
update_hair()
- update_body_parts()
update_mutations_overlay()// no lizard with human hulk overlay please.
diff --git a/code/datums/hud.dm b/code/datums/hud.dm
index 32bce89f4d..169b08dc98 100644
--- a/code/datums/hud.dm
+++ b/code/datums/hud.dm
@@ -27,6 +27,7 @@ GLOBAL_LIST_INIT(huds, list(
ANTAG_HUD_SOULLESS = new/datum/atom_hud/antag/hidden(),
ANTAG_HUD_CLOCKWORK = new/datum/atom_hud/antag(),
ANTAG_HUD_BROTHER = new/datum/atom_hud/antag/hidden(),
+ ANTAG_HUD_BLOODSUCKER = new/datum/atom_hud/antag/bloodsucker()
))
/datum/atom_hud
diff --git a/code/datums/martial.dm b/code/datums/martial.dm
index 26a709590c..32850c69df 100644
--- a/code/datums/martial.dm
+++ b/code/datums/martial.dm
@@ -8,9 +8,7 @@
var/deflection_chance = 0 //Chance to deflect projectiles
var/reroute_deflection = FALSE //Delete the bullet, or actually deflect it in some direction?
var/block_chance = 0 //Chance to block melee attacks using items while on throw mode.
- var/restraining = 0 //used in cqc's disarm_act to check if the disarmed is being restrained and so whether they should be put in a chokehold or not
var/help_verb
- var/no_guns = FALSE
var/pacifism_check = TRUE //are the martial arts combos/attacks unable to be used by pacifist.
var/allow_temp_override = TRUE //if this martial art can be overridden by temporary martial arts
@@ -28,14 +26,16 @@
/datum/martial_art/proc/add_to_streak(element,mob/living/carbon/human/D)
if(D != current_target)
- current_target = D
- streak = ""
- restraining = 0
+ reset_streak(D)
streak = streak+element
if(length(streak) > max_streak_length)
streak = copytext(streak,2)
return
+/datum/martial_art/proc/reset_streak(mob/living/carbon/human/new_target)
+ current_target = new_target
+ streak = ""
+
/datum/martial_art/proc/basic_hit(mob/living/carbon/human/A,mob/living/carbon/human/D)
var/damage = rand(A.dna.species.punchdamagelow, A.dna.species.punchdamagehigh)
@@ -81,7 +81,7 @@
D.forcesay(GLOB.hit_appends)
return 1
-/datum/martial_art/proc/teach(mob/living/carbon/human/H,make_temporary=0)
+/datum/martial_art/proc/teach(mob/living/carbon/human/H, make_temporary = FALSE)
if(!istype(H) || !H.mind)
return FALSE
if(H.mind.martial_art)
diff --git a/code/datums/martial/cqc.dm b/code/datums/martial/cqc.dm
index 73173a4a9a..09a493a670 100644
--- a/code/datums/martial/cqc.dm
+++ b/code/datums/martial/cqc.dm
@@ -9,24 +9,13 @@
id = MARTIALART_CQC
help_verb = /mob/living/carbon/human/proc/CQC_help
block_chance = 75
- var/just_a_cook = FALSE
- var/static/list/areas_under_siege = typecacheof(list(/area/crew_quarters/kitchen,
- /area/crew_quarters/cafeteria,
- /area/crew_quarters/bar))
+ var/old_grab_state = null
+ var/restraining = FALSE
-/datum/martial_art/cqc/under_siege
- name = "Close Quarters Cooking"
- just_a_cook = TRUE
-
-/datum/martial_art/cqc/proc/drop_restraining()
+/datum/martial_art/cqc/reset_streak(mob/living/carbon/human/new_target)
+ . = ..()
restraining = FALSE
-/datum/martial_art/cqc/can_use(mob/living/carbon/human/H)
- var/area/A = get_area(H)
- if(just_a_cook && !(is_type_in_typecache(A, areas_under_siege)))
- return FALSE
- return ..()
-
/datum/martial_art/cqc/proc/check_streak(mob/living/carbon/human/A, mob/living/carbon/human/D)
if(!can_use(A))
return FALSE
@@ -75,6 +64,7 @@
D.apply_damage(10, BRUTE)
log_combat(A, D, "kicked (CQC)")
if(D.IsKnockdown() && !D.stat)
+ log_combat(A, D, "knocked out (Head kick)(CQC)")
D.visible_message("[A] kicks [D]'s head, knocking [D.p_them()] out!", \
"[A] kicks your head, knocking you out!")
playsound(get_turf(A), 'sound/weapons/genhit1.ogg', 50, 1, -1)
@@ -85,7 +75,8 @@
/datum/martial_art/cqc/proc/Pressure(mob/living/carbon/human/A, mob/living/carbon/human/D)
if(!can_use(A))
return FALSE
- D.visible_message("[A] forces their arm on [D]'s neck!")
+ log_combat(A, D, "pressured (CQC)")
+ D.visible_message("[A] punches [D]'s neck!")
D.adjustStaminaLoss(60)
playsound(get_turf(A), 'sound/weapons/cqchit1.ogg', 50, 1, -1)
return TRUE
@@ -96,18 +87,20 @@
if(!can_use(A))
return FALSE
if(!D.stat)
+ log_combat(A, D, "restrained (CQC)")
D.visible_message("[A] locks [D] into a restraining position!", \
"[A] locks you into a restraining position!")
D.adjustStaminaLoss(20)
D.Stun(100)
restraining = TRUE
- addtimer(CALLBACK(src, .proc/drop_restraining), 50, TIMER_UNIQUE)
+ addtimer(VARSET_CALLBACK(src, restraining, FALSE), 50, TIMER_UNIQUE)
return TRUE
/datum/martial_art/cqc/proc/Consecutive(mob/living/carbon/human/A, mob/living/carbon/human/D)
if(!can_use(A))
return FALSE
if(!D.stat)
+ log_combat(A, D, "consecutive CQC'd (CQC)")
D.visible_message("[A] strikes [D]'s abdomen, neck and back consecutively", \
"[A] strikes your abdomen, neck and back consecutively!")
playsound(get_turf(D), 'sound/weapons/cqchit2.ogg', 50, 1, -1)
@@ -119,23 +112,20 @@
return TRUE
/datum/martial_art/cqc/grab_act(mob/living/carbon/human/A, mob/living/carbon/human/D)
- if(!can_use(A))
- return FALSE
- add_to_streak("G",D)
- if(check_streak(A,D))
- return TRUE
- if(A == D) // no self grab.
- return FALSE
- if(A.grab_state >= GRAB_AGGRESSIVE)
+ if(A.a_intent == INTENT_GRAB && A!=D && can_use(A)) // A!=D prevents grabbing yourself
+ add_to_streak("G",D)
+ if(check_streak(A,D)) //if a combo is made no grab upgrade is done
+ return TRUE
+ old_grab_state = A.grab_state
D.grabbedby(A, 1)
- else
- A.start_pulling(D, 1)
- if(A.pulling)
- D.stop_pulling()
+ if(old_grab_state == GRAB_PASSIVE)
+ D.drop_all_held_items()
+ A.grab_state = GRAB_AGGRESSIVE //Instant agressive grab if on grab intent
log_combat(A, D, "grabbed", addition="aggressively")
- A.grab_state = GRAB_AGGRESSIVE //Instant aggressive grab
-
- return TRUE
+ D.visible_message("[A] violently grabs [D]!", \
+ "[A] violently grabs you!")
+ return TRUE
+ return FALSE
/datum/martial_art/cqc/harm_act(mob/living/carbon/human/A, mob/living/carbon/human/D)
if(!can_use(A))
@@ -190,6 +180,7 @@
playsound(D, 'sound/weapons/punchmiss.ogg', 25, 1, -1)
log_combat(A, D, "disarmed (CQC)", "[I ? " grabbing \the [I]" : ""]")
if(restraining && A.pulling == D)
+ log_combat(A, D, "knocked out (Chokehold)(CQC)")
D.visible_message("[A] puts [D] into a chokehold!", \
"[A] puts you into a chokehold!")
D.SetSleeping(400)
@@ -208,9 +199,19 @@
to_chat(usr, "You try to remember some of the basics of CQC.")
to_chat(usr, "Slam: Grab Harm. Slam opponent into the ground, knocking them down.")
- to_chat(usr, "CQC Kick: Disarm Harm Harm. Knocks opponent away. Knocks out stunned or knocked down opponents.")
- to_chat(usr, "Restrain: Grab Grab. Locks opponents into a restraining position, disarm to knock them out with a choke hold.")
+ to_chat(usr, "CQC Kick: Harm Harm. Knocks opponent away. Knocks out stunned or knocked down opponents.")
+ to_chat(usr, "Restrain: Grab Grab. Locks opponents into a restraining position, disarm to knock them out with a chokehold.")
to_chat(usr, "Pressure: Disarm Grab. Decent stamina damage.")
to_chat(usr, "Consecutive CQC: Disarm Disarm Harm. Mainly offensive move, huge damage and decent stamina damage.")
to_chat(usr, "In addition, by having your throw mode on when being attacked, you enter an active defense mode where you have a chance to block and sometimes even counter attacks done to you.")
+
+///Subtype of CQC. Only used for the chef.
+/datum/martial_art/cqc/under_siege
+ name = "Close Quarters Cooking"
+
+///Prevents use if the cook is not in the kitchen.
+/datum/martial_art/cqc/under_siege/can_use(mob/living/carbon/human/H) //this is used to make chef CQC only work in kitchen
+ if(!istype(get_area(H), /area/crew_quarters/kitchen))
+ return FALSE
+ return ..()
diff --git a/code/datums/martial/sleeping_carp.dm b/code/datums/martial/sleeping_carp.dm
index f89374dc2a..bb652208ee 100644
--- a/code/datums/martial/sleeping_carp.dm
+++ b/code/datums/martial/sleeping_carp.dm
@@ -9,35 +9,36 @@
id = MARTIALART_SLEEPINGCARP
deflection_chance = 100
reroute_deflection = TRUE
- no_guns = TRUE
allow_temp_override = FALSE
help_verb = /mob/living/carbon/human/proc/sleeping_carp_help
+ var/old_grab_state = null
/datum/martial_art/the_sleeping_carp/proc/check_streak(mob/living/carbon/human/A, mob/living/carbon/human/D)
if(findtext(streak,WRIST_WRENCH_COMBO))
streak = ""
wristWrench(A,D)
- return 1
+ return TRUE
if(findtext(streak,BACK_KICK_COMBO))
streak = ""
backKick(A,D)
- return 1
+ return TRUE
if(findtext(streak,STOMACH_KNEE_COMBO))
streak = ""
kneeStomach(A,D)
- return 1
+ return TRUE
if(findtext(streak,HEAD_KICK_COMBO))
streak = ""
headKick(A,D)
- return 1
+ return TRUE
if(findtext(streak,ELBOW_DROP_COMBO))
streak = ""
elbowDrop(A,D)
- return 1
- return 0
+ return TRUE
+ return FALSE
/datum/martial_art/the_sleeping_carp/proc/wristWrench(mob/living/carbon/human/A, mob/living/carbon/human/D)
if(!D.stat && !D.IsStun() && !D.IsKnockdown())
+ log_combat(A, D, "wrist wrenched (Sleeping Carp)")
A.do_attack_animation(D, ATTACK_EFFECT_PUNCH)
D.visible_message("[A] grabs [D]'s wrist and wrenches it sideways!", \
"[A] grabs your wrist and violently wrenches it to the side!")
@@ -46,24 +47,29 @@
D.dropItemToGround(D.get_active_held_item())
D.apply_damage(5, BRUTE, pick(BODY_ZONE_L_ARM, BODY_ZONE_R_ARM))
D.Knockdown(60)//CIT CHANGE - makes sleepingcarp use knockdown() for its stuns instead of stun()
- return 1
- log_combat(A, D, "wrist wrenched (Sleeping Carp)")
+ return TRUE
return basic_hit(A,D)
/datum/martial_art/the_sleeping_carp/proc/backKick(mob/living/carbon/human/A, mob/living/carbon/human/D)
- if(A.dir == D.dir && !D.stat && !D.IsKnockdown())
- A.do_attack_animation(D, ATTACK_EFFECT_PUNCH)
- D.visible_message("[A] kicks [D] in the back!", \
- "[A] kicks you in the back, making you stumble and fall!")
- step_to(D,get_step(D,D.dir),1)
- D.Knockdown(80)
- playsound(get_turf(D), 'sound/weapons/punch1.ogg', 50, 1, -1)
- return 1
- log_combat(A, D, "back-kicked (Sleeping Carp)")
+ if(!D.stat && !D.IsKnockdown())
+ if(A.dir == D.dir)
+ log_combat(A, D, "back-kicked (Sleeping Carp)")
+ A.do_attack_animation(D, ATTACK_EFFECT_PUNCH)
+ D.visible_message("[A] kicks [D] in the back!", \
+ "[A] kicks you in the back, making you stumble and fall!")
+ step_to(D,get_step(D,D.dir),1)
+ D.Knockdown(80)
+ playsound(get_turf(D), 'sound/weapons/punch1.ogg', 50, 1, -1)
+ return TRUE
+ else
+ log_combat(A, D, "missed a back-kick (Sleeping Carp) on")
+ D.visible_message("[A] tries to kick [D] in the back, but misses!", \
+ "[A] tries to kick you in the back, but misses!")
return basic_hit(A,D)
/datum/martial_art/the_sleeping_carp/proc/kneeStomach(mob/living/carbon/human/A, mob/living/carbon/human/D)
if(!D.stat && !D.IsKnockdown())
+ log_combat(A, D, "stomach kneed (Sleeping Carp)")
A.do_attack_animation(D, ATTACK_EFFECT_KICK)
D.visible_message("[A] knees [D] in the stomach!", \
"[A] winds you with a knee in the stomach!")
@@ -71,12 +77,12 @@
D.losebreath += 3
D.Knockdown(40)//CIT CHANGE - makes sleepingcarp use knockdown() for its stuns instead of stun()
playsound(get_turf(D), 'sound/weapons/punch1.ogg', 50, 1, -1)
- return 1
- log_combat(A, D, "stomach kneed (Sleeping Carp)")
+ return TRUE
return basic_hit(A,D)
/datum/martial_art/the_sleeping_carp/proc/headKick(mob/living/carbon/human/A, mob/living/carbon/human/D)
if(!D.stat && !D.IsKnockdown())
+ log_combat(A, D, "head kicked (Sleeping Carp)")
A.do_attack_animation(D, ATTACK_EFFECT_KICK)
D.visible_message("[A] kicks [D] in the head!", \
"[A] kicks you in the jaw!")
@@ -84,12 +90,12 @@
D.drop_all_held_items()
playsound(get_turf(D), 'sound/weapons/punch1.ogg', 50, 1, -1)
D.Knockdown(80)//CIT CHANGE - makes sleepingcarp use knockdown() for its stuns instead of stun()
- return 1
- log_combat(A, D, "head kicked (Sleeping Carp)")
+ return TRUE
return basic_hit(A,D)
/datum/martial_art/the_sleeping_carp/proc/elbowDrop(mob/living/carbon/human/A, mob/living/carbon/human/D)
if(D.IsKnockdown() || D.resting || D.stat)
+ log_combat(A, D, "elbow dropped (Sleeping Carp)")
A.do_attack_animation(D, ATTACK_EFFECT_PUNCH)
D.visible_message("[A] elbow drops [D]!", \
"[A] piledrives you with their elbow!")
@@ -97,37 +103,29 @@
D.death() //FINISH HIM!
D.apply_damage(50, BRUTE, BODY_ZONE_CHEST)
playsound(get_turf(D), 'sound/weapons/punch1.ogg', 75, 1, -1)
- return 1
- log_combat(A, D, "elbow dropped (Sleeping Carp)")
+ return TRUE
return basic_hit(A,D)
/datum/martial_art/the_sleeping_carp/grab_act(mob/living/carbon/human/A, mob/living/carbon/human/D)
- add_to_streak("G",D)
- if(check_streak(A,D))
- return 1
- if(A == D) //no self grab stun
- return FALSE
- if(A.grab_state >= GRAB_AGGRESSIVE)
+ if(A.a_intent == INTENT_GRAB && A!=D) // A!=D prevents grabbing yourself
+ add_to_streak("G",D)
+ if(check_streak(A,D)) //if a combo is made no grab upgrade is done
+ return TRUE
+ old_grab_state = A.grab_state
D.grabbedby(A, 1)
- else
- A.start_pulling(D, 1)
- if(A.pulling)
+ if(old_grab_state == GRAB_PASSIVE)
D.drop_all_held_items()
- D.stop_pulling()
- if(A.a_intent == INTENT_GRAB)
- log_combat(A, D, "grabbed", addition="aggressively")
- D.visible_message("[A] violently grabs [D]!", \
- "[A] violently grabs you!")
- A.grab_state = GRAB_AGGRESSIVE //Instant aggressive grab
- else
- log_combat(A, D, "grabbed", addition="passively")
- A.grab_state = GRAB_PASSIVE
- return 1
+ A.grab_state = GRAB_AGGRESSIVE //Instant agressive grab if on grab intent
+ log_combat(A, D, "grabbed", addition="aggressively")
+ D.visible_message("[A] violently grabs [D]!", \
+ "[A] violently grabs you!")
+ return TRUE
+ return FALSE
/datum/martial_art/the_sleeping_carp/harm_act(mob/living/carbon/human/A, mob/living/carbon/human/D)
add_to_streak("H",D)
if(check_streak(A,D))
- return 1
+ return TRUE
A.do_attack_animation(D, ATTACK_EFFECT_PUNCH)
var/atk_verb = pick("punches", "kicks", "chops", "hits", "slams")
D.visible_message("[A] [atk_verb] [D]!", \
@@ -138,15 +136,25 @@
D.visible_message("[D] stumbles and falls!", "The blow sends you to the ground!")
D.Knockdown(80)
log_combat(A, D, "[atk_verb] (Sleeping Carp)")
- return 1
+ return TRUE
/datum/martial_art/the_sleeping_carp/disarm_act(mob/living/carbon/human/A, mob/living/carbon/human/D)
add_to_streak("D",D)
if(check_streak(A,D))
- return 1
+ return TRUE
return ..()
+/datum/martial_art/the_sleeping_carp/teach(mob/living/carbon/human/H, make_temporary = FALSE)
+ . = ..()
+ if(!.)
+ return
+ ADD_TRAIT(H, TRAIT_NOGUNS, SLEEPING_CARP_TRAIT)
+
+/datum/martial_art/the_sleeping_carp/on_remove(mob/living/carbon/human/H)
+ . = ..()
+ REMOVE_TRAIT(H, TRAIT_NOGUNS, SLEEPING_CARP_TRAIT)
+
/mob/living/carbon/human/proc/sleeping_carp_help()
set name = "Recall Teachings"
set desc = "Remember the martial techniques of the Sleeping Carp clan."
@@ -233,4 +241,4 @@
/obj/item/twohanded/bostaff/hit_reaction(mob/living/carbon/human/owner, atom/movable/hitby, attack_text = "the attack", final_block_chance = 0, damage = 0, attack_type = MELEE_ATTACK)
if(wielded)
return ..()
- return 0
+ return FALSE
diff --git a/code/datums/mind.dm b/code/datums/mind.dm
index 0ded5f9912..d1b4e51a7d 100644
--- a/code/datums/mind.dm
+++ b/code/datums/mind.dm
@@ -88,6 +88,9 @@
/datum/mind/proc/transfer_to(mob/new_character, var/force_key_move = 0)
var/old_character = current
+ var/signals = SEND_SIGNAL(new_character, COMSIG_MOB_PRE_PLAYER_CHANGE, new_character, old_character) | SEND_SIGNAL(src, COMSIG_PRE_MIND_TRANSFER, new_character, old_character)
+ if(signals & COMPONENT_STOP_MIND_TRANSFER)
+ return
if(current) // remove ourself from our old body's mind variable
current.mind = null
SStgui.on_transfer(current, new_character)
@@ -125,7 +128,6 @@
transfer_martial_arts(new_character)
if(active || force_key_move)
new_character.key = key //now transfer the key to link the client to our new body
- SEND_SIGNAL(src, COMSIG_MIND_TRANSFER, new_character, old_character)
//CIT CHANGE - makes arousal update when transfering bodies
if(isliving(new_character)) //New humans and such are by default enabled arousal. Let's always use the new mind's prefs.
@@ -134,6 +136,8 @@
L.canbearoused = L.client.prefs.arousable //Technically this should make taking over a character mean the body gain the new minds setting...
L.update_arousal_hud() //Removes the old icon
+ SEND_SIGNAL(src, COMSIG_MIND_TRANSFER, new_character, old_character)
+
/datum/mind/proc/store_memory(new_text)
if((length(memory) + length(new_text)) <= MAX_MESSAGE_LEN)
memory += "[new_text]
"
diff --git a/code/datums/mood_events/generic_negative_events.dm b/code/datums/mood_events/generic_negative_events.dm
index 94a64c3bd4..a6e4c8ed4f 100644
--- a/code/datums/mood_events/generic_negative_events.dm
+++ b/code/datums/mood_events/generic_negative_events.dm
@@ -198,3 +198,48 @@
description = "It feels quite cold out here.\n"
mood_change = -2
timeout = 1 MINUTES
+
+/datum/mood_event/vampcandle
+ description = "Something is making your mind feel... loose...\n"
+ mood_change = -10
+ timeout = 1 MINUTES
+
+/datum/mood_event/drankblood_bad
+ description = "I drank the blood of a lesser creature. Disgusting.\n"
+ mood_change = -4
+ timeout = 900
+
+/datum/mood_event/drankblood_dead
+ description = "I drank dead blood. I am better than this.\n"
+ mood_change = -7
+ timeout = 900
+
+/datum/mood_event/drankblood_synth
+ description = "I drank synthetic blood. What is wrong with me?\n"
+ mood_change = -7
+ timeout = 900
+
+/datum/mood_event/drankkilled
+ description = "I drank from my victim until they died. I feel...less human.\n"
+ mood_change = -12
+ timeout = 6000
+
+/datum/mood_event/madevamp
+ description = "A soul has been cursed to undeath by my own hand.\n"
+ mood_change = -10
+ timeout = 10000
+
+/datum/mood_event/vampatefood
+ description = "Mortal nourishment no longer sustains me. I feel unwell.\n"
+ mood_change = -6
+ timeout = 1000
+
+/datum/mood_event/daylight_1
+ description = "I slept poorly in a makeshift coffin during the day.\n"
+ mood_change = -3
+ timeout = 1000
+
+/datum/mood_event/daylight_2
+ description = "I have been scorched by the unforgiving rays of the sun.\n"
+ mood_change = -6
+ timeout = 1200
diff --git a/code/datums/mood_events/generic_positive_events.dm b/code/datums/mood_events/generic_positive_events.dm
index 9ae7ba2289..19be10e668 100644
--- a/code/datums/mood_events/generic_positive_events.dm
+++ b/code/datums/mood_events/generic_positive_events.dm
@@ -134,6 +134,15 @@
mood_change = 3
timeout = 3000
+/datum/mood_event/drankblood
+ description = "I have fed greedly from that which nourishes me.\n"
+ mood_change = 10
+ timeout = 900
+
+/datum/mood_event/coffinsleep
+ description = "I slept in a coffin during the day. I feel whole again.\n"
+ mood_change = 8
+ timeout = 1200
//Cursed stuff below.
/datum/mood_event/orgasm
@@ -148,3 +157,8 @@
/datum/mood_event/fedprey
description = "It feels quite cozy in here.\n"
mood_change = 3
+
+/datum/mood_event/hope_lavaland
+ description = "What a peculiar emblem. It makes me feel hopeful for my future.\n"
+ mood_change = 5
+
diff --git a/code/datums/mutations/hulk.dm b/code/datums/mutations/hulk.dm
index 85cecca489..c536196e95 100644
--- a/code/datums/mutations/hulk.dm
+++ b/code/datums/mutations/hulk.dm
@@ -5,7 +5,7 @@
get_chance = 15
lowest_value = 256 * 12
text_gain_indication = "Your muscles hurt!"
- species_allowed = list("human") //no skeleton/lizard hulk
+ species_allowed = list("fly") //no skeleton/lizard hulk
health_req = 25
/datum/mutation/human/hulk/on_acquiring(mob/living/carbon/human/owner)
@@ -13,6 +13,7 @@
return
ADD_TRAIT(owner, TRAIT_STUNIMMUNE, TRAIT_HULK)
ADD_TRAIT(owner, TRAIT_PUSHIMMUNE, TRAIT_HULK)
+ ADD_TRAIT(owner, TRAIT_CHUNKYFINGERS, TRAIT_HULK)
owner.update_body_parts()
SEND_SIGNAL(owner, COMSIG_ADD_MOOD_EVENT, "hulk", /datum/mood_event/hulk)
RegisterSignal(owner, COMSIG_MOB_SAY, .proc/handle_speech)
@@ -31,6 +32,7 @@
return
REMOVE_TRAIT(owner, TRAIT_STUNIMMUNE, TRAIT_HULK)
REMOVE_TRAIT(owner, TRAIT_PUSHIMMUNE, TRAIT_HULK)
+ ADD_TRAIT(owner, TRAIT_CHUNKYFINGERS, TRAIT_HULK)
owner.update_body_parts()
SEND_SIGNAL(owner, COMSIG_CLEAR_MOOD_EVENT, "hulk")
UnregisterSignal(owner, COMSIG_MOB_SAY)
diff --git a/code/datums/ruins/lavaland.dm b/code/datums/ruins/lavaland.dm
index 4f18e6fd62..7ea2d2615d 100644
--- a/code/datums/ruins/lavaland.dm
+++ b/code/datums/ruins/lavaland.dm
@@ -229,4 +229,13 @@
id = "puzzle"
description = "Mystery to be solved."
suffix = "lavaland_surface_puzzle.dmm"
- cost = 5
\ No newline at end of file
+ cost = 5
+
+/datum/map_template/ruin/lavaland/elite_tumor
+ name = "Pulsating Tumor"
+ id = "tumor"
+ description = "A strange tumor which houses a powerful beast..."
+ suffix = "lavaland_surface_elite_tumor.dmm"
+ cost = 5
+ always_place = TRUE
+ allow_duplicates = TRUE
diff --git a/code/datums/status_effects/debuffs.dm b/code/datums/status_effects/debuffs.dm
index ac2d2f37ab..f5f012e7f9 100644
--- a/code/datums/status_effects/debuffs.dm
+++ b/code/datums/status_effects/debuffs.dm
@@ -80,34 +80,55 @@
desc = "You've fallen asleep. Wait a bit and you should wake up. Unless you don't, considering how helpless you are."
icon_state = "asleep"
-//TASER
-/datum/status_effect/electrode
- id = "tased"
+/datum/status_effect/no_combat_mode/
+ id = "no_combat_mode"
blocks_combatmode = TRUE
- status_type = STATUS_EFFECT_REPLACE
alert_type = null
+ status_type = STATUS_EFFECT_REPLACE
-/datum/status_effect/electrode/on_creation(mob/living/new_owner, set_duration)
+/datum/status_effect/no_combat_mode/on_creation(mob/living/new_owner, set_duration)
if(isnum(set_duration))
duration = set_duration
. = ..()
+ if(iscarbon(owner))
+ var/mob/living/carbon/C = owner
+ if(C.combatmode)
+ C.toggle_combat_mode(TRUE)
+
+/datum/status_effect/no_combat_mode/mesmerize
+ id = "Mesmerize"
+ alert_type = /obj/screen/alert/status_effect/mesmerized
+
+/obj/screen/alert/status_effect/mesmerized
+ name = "Mesmerized"
+ desc = "You cant tear your sight from who is in front of you...Their gaze is simply too enthralling.."
+ icon = 'icons/mob/actions/bloodsucker.dmi'
+ icon_state = "power_mez"
+
+/datum/status_effect/no_combat_mode/electrode
+ id = "tased"
+
+/datum/status_effect/no_combat_mode/electrode/on_creation(mob/living/new_owner, set_duration)
+ if(isnum(set_duration)) //TODO, figure out how to grab from subtype
+ duration = set_duration
+ . = ..()
if(iscarbon(owner))
var/mob/living/carbon/C = owner
if(C.combatmode)
C.toggle_combat_mode(TRUE)
C.add_movespeed_modifier(MOVESPEED_ID_TASED_STATUS, TRUE, override = TRUE, multiplicative_slowdown = 8)
-/datum/status_effect/electrode/on_remove()
+/datum/status_effect/no_combat_mode/electrode/on_remove()
if(iscarbon(owner))
var/mob/living/carbon/C = owner
C.remove_movespeed_modifier(MOVESPEED_ID_TASED_STATUS)
. = ..()
-/datum/status_effect/electrode/tick()
+/datum/status_effect/no_combat_mode/electrode/tick()
if(owner)
owner.adjustStaminaLoss(5) //if you really want to try to stamcrit someone with a taser alone, you can, but it'll take time and good timing.
-/datum/status_effect/electrode/nextmove_modifier() //why is this a proc. its no big deal since this doesnt get called often at all but literally w h y
+/datum/status_effect/no_combat_mode/electrode/nextmove_modifier() //why is this a proc. its no big deal since this doesnt get called often at all but literally w h y
return 2
//OTHER DEBUFFS
@@ -388,6 +409,19 @@
else
new /obj/effect/temp_visual/bleed(get_turf(owner))
+/datum/status_effect/neck_slice
+ id = "neck_slice"
+ status_type = STATUS_EFFECT_UNIQUE
+ alert_type = null
+ duration = -1
+
+/datum/status_effect/neck_slice/tick()
+ var/mob/living/carbon/human/H = owner
+ if(H.stat == DEAD || H.bleed_rate <= 8)
+ H.remove_status_effect(/datum/status_effect/neck_slice)
+ if(prob(10))
+ H.emote(pick("gasp", "gag", "choke"))
+
/mob/living/proc/apply_necropolis_curse(set_curse, duration = 10 MINUTES)
var/datum/status_effect/necropolis_curse/C = has_status_effect(STATUS_EFFECT_NECROPOLIS_CURSE)
if(!set_curse)
@@ -481,7 +515,7 @@
deltimer(timerid)
-//Kindle: Used by servants of Ratvar. 10-second knockdown, reduced by 1 second per 5 damage taken while the effect is active.
+//Kindle: Used by servants of Ratvar. 10-second knockdown, reduced by 1 second per 5 damage taken while the effect is active. Does not take into account Oxy-damage
/datum/status_effect/kindle
id = "kindle"
status_type = STATUS_EFFECT_UNIQUE
@@ -489,6 +523,7 @@
duration = 100
alert_type = /obj/screen/alert/status_effect/kindle
var/old_health
+ var/old_oxyloss
/datum/status_effect/kindle/tick()
owner.Knockdown(15, TRUE, FALSE, 15)
@@ -498,7 +533,9 @@
C.stuttering = max(5, C.stuttering)
if(!old_health)
old_health = owner.health
- var/health_difference = old_health - owner.health
+ if(!old_oxyloss)
+ old_oxyloss = owner.getOxyLoss()
+ var/health_difference = old_health - owner.health - CLAMP(owner.getOxyLoss() - old_oxyloss,0, owner.getOxyLoss())
if(!health_difference)
return
owner.visible_message("The light in [owner]'s eyes dims as [owner.p_theyre()] harmed!", \
@@ -506,6 +543,7 @@
health_difference *= 2 //so 10 health difference translates to 20 deciseconds of stun reduction
duration -= health_difference
old_health = owner.health
+ old_oxyloss = owner.getOxyLoss()
/datum/status_effect/kindle/on_remove()
owner.visible_message("The light in [owner]'s eyes fades!", \
@@ -698,4 +736,4 @@ datum/status_effect/pacify
if(LAZYLEN(targets) && I)
to_chat(owner, "Your arm spasms!")
owner.log_message("threw [I] due to a Muscle Spasm", LOG_ATTACK)
- owner.throw_item(pick(targets))
\ No newline at end of file
+ owner.throw_item(pick(targets))
diff --git a/code/datums/traits/good.dm b/code/datums/traits/good.dm
index 80a5f24250..15ac70a2c7 100644
--- a/code/datums/traits/good.dm
+++ b/code/datums/traits/good.dm
@@ -8,12 +8,14 @@
mob_trait = TRAIT_ALCOHOL_TOLERANCE
gain_text = "You feel like you could drink a whole keg!"
lose_text = "You don't feel as resistant to alcohol anymore. Somehow."
+ medical_record_text = "Patient demonstrates a high tolerance for alcohol."
/datum/quirk/apathetic
name = "Apathetic"
desc = "You just don't care as much as other people. That's nice to have in a place like this, I guess."
value = 1
mood_quirk = TRUE
+ medical_record_text = "Patient was administered the Apathy Evaluation Scale but did not bother to complete it."
/datum/quirk/apathetic/add()
var/datum/component/mood/mood = quirk_holder.GetComponent(/datum/component/mood)
@@ -42,6 +44,7 @@
mob_trait = TRAIT_EMPATH
gain_text = "You feel in tune with those around you."
lose_text = "You feel isolated from others."
+ medical_record_text = "Patient is highly perceptive of and sensitive to social cues, or may possibly have ESP. Further testing needed."
/datum/quirk/freerunning
name = "Freerunning"
@@ -50,6 +53,7 @@
mob_trait = TRAIT_FREERUNNING
gain_text = "You feel lithe on your feet!"
lose_text = "You feel clumsy again."
+ medical_record_text = "Patient scored highly on cardio tests."
/datum/quirk/friendly
name = "Friendly"
@@ -59,6 +63,7 @@
gain_text = "You want to hug someone."
lose_text = "You no longer feel compelled to hug others."
mood_quirk = TRUE
+ medical_record_text = "Patient demonstrates low-inhibitions for physical contact and well-developed arms. Requesting another doctor take over this case."
/datum/quirk/jolly
name = "Jolly"
@@ -66,6 +71,11 @@
value = 1
mob_trait = TRAIT_JOLLY
mood_quirk = TRUE
+ medical_record_text = "Patient demonstrates constant euthymia irregular for environment. It's a bit much, to be honest."
+
+/datum/quirk/jolly/on_process()
+ if(prob(0.05))
+ SEND_SIGNAL(quirk_holder, COMSIG_ADD_MOOD_EVENT, "jolly", /datum/mood_event/jolly)
/datum/quirk/light_step
name = "Light Step"
@@ -74,6 +84,7 @@
mob_trait = TRAIT_LIGHT_STEP
gain_text = "You walk with a little more litheness."
lose_text = "You start tromping around like a barbarian."
+ medical_record_text = "Patient's dexterity belies a strong capacity for stealth."
/datum/quirk/quick_step
name = "Quick Step"
@@ -82,6 +93,7 @@
mob_trait = TRAIT_SPEEDY_STEP
gain_text = "You feel determined. No time to lose."
lose_text = "You feel less determined. What's the rush, man?"
+ medical_record_text = "Patient scored highly on racewalking tests."
/datum/quirk/musician
name = "Musician"
@@ -90,6 +102,7 @@
mob_trait = TRAIT_MUSICIAN
gain_text = "You know everything about musical instruments."
lose_text = "You forget how musical instruments work."
+ medical_record_text = "Patient brain scans show a highly-developed auditory pathway."
/datum/quirk/musician/on_spawn()
var/mob/living/carbon/human/H = quirk_holder
@@ -101,21 +114,6 @@
H.equip_to_slot(musicaltuner, SLOT_IN_BACKPACK)
H.regenerate_icons()
-/datum/quirk/night_vision
- name = "Night Vision"
- desc = "You can see slightly more clearly in full darkness than most people."
- value = 1
- mob_trait = TRAIT_NIGHT_VISION
- gain_text = "The shadows seem a little less dark."
- lose_text = "Everything seems a little darker."
-
-/datum/quirk/night_vision/on_spawn()
- var/mob/living/carbon/human/H = quirk_holder
- var/obj/item/organ/eyes/eyes = H.getorgan(/obj/item/organ/eyes)
- if(!eyes || eyes.lighting_alpha)
- return
- eyes.Insert(H) //refresh their eyesight and vision
-
/datum/quirk/photographer
name = "Photographer"
desc = "You know how to handle a camera, shortening the delay between each shot."
@@ -123,6 +121,7 @@
mob_trait = TRAIT_PHOTOGRAPHER
gain_text = "You know everything about photography."
lose_text = "You forget how photo cameras work."
+ medical_record_text = "Patient mentions photography as a stress-relieving hobby."
/datum/quirk/photographer/on_spawn()
var/mob/living/carbon/human/H = quirk_holder
@@ -136,12 +135,14 @@
desc = "You know your body well, and can accurately assess the extent of your wounds."
value = 2
mob_trait = TRAIT_SELF_AWARE
+ medical_record_text = "Patient demonstrates an uncanny knack for self-diagnosis."
/datum/quirk/skittish
name = "Skittish"
desc = "You can conceal yourself in danger. Ctrl-shift-click a closed locker to jump into it, as long as you have access."
value = 2
mob_trait = TRAIT_SKITTISH
+ medical_record_text = "Patient demonstrates a high aversion to danger and has described hiding in containers out of fear."
/datum/quirk/spiritual
name = "Spiritual"
@@ -150,6 +151,7 @@
mob_trait = TRAIT_SPIRITUAL
gain_text = "You feel a little more faithful to the gods today."
lose_text = "You feel less faithful in the gods."
+ medical_record_text = "Patient reports a belief in a higher power."
/datum/quirk/tagger
name = "Tagger"
@@ -158,6 +160,7 @@
mob_trait = TRAIT_TAGGER
gain_text = "You know how to tag walls efficiently."
lose_text = "You forget how to tag walls properly."
+ medical_record_text = "Patient was recently seen for possible paint huffing incident."
/datum/quirk/tagger/on_spawn()
var/mob/living/carbon/human/H = quirk_holder
@@ -173,6 +176,7 @@
mob_trait = TRAIT_VORACIOUS
gain_text = "You feel HONGRY."
lose_text = "You no longer feel HONGRY."
+ medical_record_text = "Patient demonstrates a disturbing capacity for eating."
/datum/quirk/trandening
name = "High Luminosity Eyes"
@@ -194,6 +198,7 @@
mob_trait = TRAIT_HIGH_BLOOD
gain_text = "You feel full of blood!"
lose_text = "You feel like your blood pressure went down."
+ medical_record_text = "Patient's blood tests report an abnormal concentration of red blood cells in their bloodstream."
/datum/quirk/bloodpressure/add()
var/mob/living/M = quirk_holder
diff --git a/code/datums/traits/negative.dm b/code/datums/traits/negative.dm
index 178e1aaeb0..3319259381 100644
--- a/code/datums/traits/negative.dm
+++ b/code/datums/traits/negative.dm
@@ -22,14 +22,19 @@
value = -1
gain_text = "You start feeling depressed."
lose_text = "You no longer feel depressed." //if only it were that easy!
- medical_record_text = "Patient has a severe mood disorder causing them to experience sudden moments of sadness."
+ medical_record_text = "Patient has a severe mood disorder, causing them to experience acute episodes of depression."
mood_quirk = TRUE
+/datum/quirk/depression/on_process()
+ if(prob(0.05))
+ SEND_SIGNAL(quirk_holder, COMSIG_ADD_MOOD_EVENT, "depression", /datum/mood_event/depression)
+
/datum/quirk/family_heirloom
name = "Family Heirloom"
desc = "You are the current owner of an heirloom, passed down for generations. You have to keep it safe!"
value = -1
mood_quirk = TRUE
+ medical_record_text = "Patient demonstrates an unnatural attachment to a family heirloom."
var/obj/item/heirloom
var/where
@@ -143,6 +148,7 @@
name = "Nyctophobia"
desc = "As far as you can remember, you've always been afraid of the dark. While in the dark without a light source, you instinctually act careful, and constantly feel a sense of dread."
value = -1
+ medical_record_text = "Patient demonstrates a fear of the dark. (Seriously?)"
/datum/quirk/nyctophobia/on_process()
var/mob/living/carbon/human/H = quirk_holder
@@ -163,7 +169,8 @@
desc = "Bright lights irritate you. Your eyes start to water, your skin feels itchy against the photon radiation, and your hair gets dry and frizzy. Maybe it's a medical condition. If only Nanotrasen was more considerate of your needs..."
value = -1
gain_text = "The safty of light feels off..."
- lose_text = "Enlighing."
+ lose_text = "Enlightening."
+ medical_record_text = "Despite my warnings, the patient refuses turn on the lights, only to end up rolling down a full flight of stairs and into the cellar."
/datum/quirk/lightless/on_process()
var/turf/T = get_turf(quirk_holder)
@@ -332,16 +339,19 @@
medical_record_text = "Patient has an extreme or irrational fear and aversion to an undefined stimuli."
var/datum/brain_trauma/mild/phobia/phobia
-/datum/quirk/phobia/add()
+/datum/quirk/phobia/post_add()
var/mob/living/carbon/human/H = quirk_holder
phobia = new
- H.gain_trauma(phobia, TRAUMA_RESILIENCE_SURGERY)
+ H.gain_trauma(phobia, TRAUMA_RESILIENCE_ABSOLUTE)
+
+/datum/quirk/phobia/remove()
+ var/mob/living/carbon/human/H = quirk_holder
+ H?.cure_trauma_type(phobia, TRAUMA_RESILIENCE_ABSOLUTE)
/datum/quirk/mute
name = "Mute"
desc = "Due to some accident, medical condition, or simply by choice, you are completely unable to speak."
value = -2 //HALP MAINTS
- mob_trait = TRAIT_MUTE
gain_text = "You find yourself unable to speak!"
lose_text = "You feel a growing strength in your vocal chords."
medical_record_text = "Functionally mute, patient is unable to use their voice in any capacity."
@@ -350,14 +360,17 @@
/datum/quirk/mute/add()
var/mob/living/carbon/human/H = quirk_holder
mute = new
- H.gain_trauma(mute, TRAUMA_RESILIENCE_SURGERY)
+ H.gain_trauma(mute, TRAUMA_RESILIENCE_ABSOLUTE)
+
+/datum/quirk/mute/remove()
+ var/mob/living/carbon/human/H = quirk_holder
+ H?.cure_trauma_type(mute, TRAUMA_RESILIENCE_ABSOLUTE)
/datum/quirk/mute/on_process()
if(quirk_holder.mind && LAZYLEN(quirk_holder.mind.antag_datums))
to_chat(quirk_holder, "Your antagonistic nature has caused your voice to be heard.")
qdel(src)
-
/datum/quirk/unstable
name = "Unstable"
desc = "Due to past troubles, you are unable to recover your sanity if you lose it. Be very careful managing your mood!"
@@ -373,7 +386,7 @@
value = -4
gain_text = "You can't see anything."
lose_text = "You miraculously gain back your vision."
- medical_record_text = "Subject has permanent blindness."
+ medical_record_text = "Patient has permanent blindness."
/datum/quirk/blindness/add()
quirk_holder.become_blind(ROUNDSTART_TRAIT)
@@ -384,3 +397,6 @@
if(!H.equip_to_slot_if_possible(glasses, SLOT_GLASSES, bypass_equip_delay_self = TRUE)) //if you can't put it on the user's eyes, put it in their hands, otherwise put it on their eyes eyes
H.put_in_hands(glasses)
H.regenerate_icons()
+
+/datum/quirk/blindness/remove()
+ quirk_holder?.cure_blind(ROUNDSTART_TRAIT)
diff --git a/code/datums/traits/neutral.dm b/code/datums/traits/neutral.dm
index 9e05af03b6..eae2db6a5f 100644
--- a/code/datums/traits/neutral.dm
+++ b/code/datums/traits/neutral.dm
@@ -16,6 +16,7 @@
value = 0
gain_text = "You feel an intense craving for pineapple."
lose_text = "Your feelings towards pineapples seem to return to a lukewarm state."
+ medical_record_text = "Patient demonstrates a pathological love of pineapple."
/datum/quirk/pineapple_liker/add()
var/mob/living/carbon/human/H = quirk_holder
@@ -34,6 +35,7 @@
value = 0
gain_text = "You find yourself pondering what kind of idiot actually enjoys pineapples..."
lose_text = "Your feelings towards pineapples seem to return to a lukewarm state."
+ medical_record_text = "Patient is correct to think that pineapple is disgusting."
/datum/quirk/pineapple_hater/add()
var/mob/living/carbon/human/H = quirk_holder
@@ -52,6 +54,7 @@
value = 0
gain_text = "You start craving something that tastes strange."
lose_text = "You feel like eating normal food again."
+ medical_record_text = "Patient demonstrates irregular nutrition preferences."
/datum/quirk/deviant_tastes/add()
var/mob/living/carbon/human/H = quirk_holder
@@ -92,7 +95,7 @@
value = 0
gain_text = "You feel more prudish."
lose_text = "You don't feel as prudish as before."
- medical_record_text = "Patient exhibits a special gene that makes them immune to Crocin and Hexacrocin."
+ medical_record_text = "Patient exhibits a special gene that makes them immune to aphrodisiacs."
/datum/quirk/libido
name = "Nymphomania"
@@ -134,6 +137,7 @@
value = 0
mob_trait = TRAIT_PHARMA
lose_text = "Your liver feels different."
+ medical_record_text = "Non-invasive tests report that the patient's metabolism is indeed incompatible with a certain \"stimulants\"."
var/active = FALSE
var/power = 0
var/cachedmoveCalc = 1
diff --git a/code/game/area/areas.dm b/code/game/area/areas.dm
index 612c3cba42..f5e285a5c2 100644
--- a/code/game/area/areas.dm
+++ b/code/game/area/areas.dm
@@ -327,7 +327,7 @@ GLOBAL_LIST_EMPTY(teleportlocs)
for(var/obj/machinery/light/L in src)
L.update()
-/area/proc/updateicon()
+/area/proc/update_icon()
var/weather_icon
for(var/V in SSweather.processing)
var/datum/weather/W = V
@@ -337,7 +337,7 @@ GLOBAL_LIST_EMPTY(teleportlocs)
if(!weather_icon)
icon_state = null
-/area/space/updateicon()
+/area/space/update_icon()
icon_state = null
/*
@@ -370,7 +370,7 @@ GLOBAL_LIST_EMPTY(teleportlocs)
/area/proc/power_change()
for(var/obj/machinery/M in src) // for each machine in the area
M.power_change() // reverify power status (to update icons etc.)
- updateicon()
+ update_icon()
/area/proc/usage(chan)
var/used = 0
diff --git a/code/game/atoms.dm b/code/game/atoms.dm
index 2fe5380490..7de5707032 100644
--- a/code/game/atoms.dm
+++ b/code/game/atoms.dm
@@ -809,8 +809,7 @@ Proc for attack log creation, because really why not
// Filter stuff
/atom/movable/proc/add_filter(name,priority,list/params)
- if(!filter_data)
- filter_data = list()
+ LAZYINITLIST(filter_data)
var/list/p = params.Copy()
p["priority"] = priority
filter_data[name] = p
@@ -818,7 +817,7 @@ Proc for attack log creation, because really why not
/atom/movable/proc/update_filters()
filters = null
- sortTim(filter_data,associative = TRUE)
+ filter_data = sortTim(filter_data, /proc/cmp_filter_data_priority, TRUE)
for(var/f in filter_data)
var/list/data = filter_data[f]
var/list/arguments = data.Copy()
diff --git a/code/game/gamemodes/bloodsucker/bloodsucker.dm b/code/game/gamemodes/bloodsucker/bloodsucker.dm
new file mode 100644
index 0000000000..e784fd836d
--- /dev/null
+++ b/code/game/gamemodes/bloodsucker/bloodsucker.dm
@@ -0,0 +1,292 @@
+
+/datum/game_mode
+ var/list/datum/mind/bloodsuckers = list() // List of minds belonging to this game mode.
+ var/list/datum/mind/vassals = list() // List of minds that have been turned into Vassals.
+ //var/list/datum/mind/vamphunters = list() // List of minds hunting vampires. Disabled at the moment
+ var/obj/effect/sunlight/bloodsucker_sunlight // Sunlight Timer. Created on first Bloodsucker assign. Destroyed on last removed Bloodsucker.
+
+ // LISTS //
+ var/list/vassal_allowed_antags = list(/datum/antagonist/brother, /datum/antagonist/traitor, /datum/antagonist/traitor/internal_affairs, /datum/antagonist/survivalist, \
+ /datum/antagonist/rev, /datum/antagonist/nukeop, /datum/antagonist/pirate, /datum/antagonist/cult, /datum/antagonist/abductee)
+ // The antags you're allowed to be if turning Vassal.
+
+/datum/game_mode/bloodsucker
+ name = "bloodsucker"
+ config_tag = "bloodsucker"
+ traitor_name = "Bloodsucker"
+ antag_flag = ROLE_BLOODSUCKER
+ false_report_weight = 1
+ restricted_jobs = list("AI","Cyborg")
+ protected_jobs = list("Security Officer", "Warden", "Detective", "Head of Security", "Captain", "Head of Personnel", "Chief Engineer", "Chief Medical Officer", "Research Director", "Quartermaster")
+ required_players = 20
+ required_enemies = 2
+ recommended_enemies = 4
+ reroll_friendly = FALSE
+ enemy_minimum_age = 7
+ round_ends_with_antag_death = FALSE
+
+
+ announce_span = "danger"
+ announce_text = "Filthy, bloodsucking vampires are crawling around disguised as crewmembers!\n\
+ Bloodsuckers: The crew are cattle, while you are both shepherd and slaughterhouse.\n\
+ Crew: Put an end to the undead infestation before the station is overcome!"
+
+/datum/game_mode/bloodsucker/generate_report()
+ return "Reports indicate that some of your crew may have toppled statues in the past week, angering the gods and becoming cursed with undeath and a desire for blood. Watch out for crewmembers that seem to shun the light or are found pale and delirious."
+
+// Seems to be run by game ONCE, and finds all potential players to be antag.
+/datum/game_mode/bloodsucker/pre_setup()
+
+ // Set Restricted Jobs
+ if(CONFIG_GET(flag/protect_roles_from_antagonist))
+ restricted_jobs += protected_jobs
+
+ if(CONFIG_GET(flag/protect_assistant_from_antagonist))
+ restricted_jobs += "Assistant"
+
+ // Set number of Vamps
+ recommended_enemies = CLAMP(round(num_players()/10), 1, 6);
+
+ // Select Antags
+ for(var/i = 0, i < recommended_enemies, i++)
+ if (!antag_candidates.len)
+ break
+ var/datum/mind/bloodsucker = pick(antag_candidates)
+ // Can we even BE a bloodsucker?
+ //if (can_make_bloodsucker(bloodsucker, display_warning=FALSE))
+ bloodsuckers += bloodsucker
+ bloodsucker.restricted_roles = restricted_jobs
+ log_game("[bloodsucker.key] (ckey) has been selected as a Bloodsucker.")
+ antag_candidates.Remove(bloodsucker) // Apparently you can also write antag_candidates -= bloodsucker
+
+ // Assign Hunters (as many as monsters, plus one)
+ //assign_monster_hunters(bloodsuckers.len, TRUE, bloodsuckers) // Disabled for now
+
+ // Do we have enough vamps to continue?
+ return bloodsuckers.len >= required_enemies
+
+
+// Gamemode is all done being set up. We have all our Vamps. We now pick objectives and let them know what's happening.
+/datum/game_mode/bloodsucker/post_setup()
+
+ // Sunlight (Creating Bloodsuckers manually will check to create this, too)
+ check_start_sunlight()
+
+ // Vamps
+ for(var/datum/mind/bloodsucker in bloodsuckers)
+ // spawn() --> Run block of code but game continues on past it.
+ // sleep() --> Run block of code and freeze code there (including whoever called us) until it's resolved.
+
+ //Clean Bloodsucker Species (racist?)
+ //clean_invalid_species(bloodsucker)
+ // TO-DO !!!
+
+ // Add Bloodsucker Antag Datum (or remove from list on Fail)
+ if (!make_bloodsucker(bloodsucker))
+ bloodsuckers -= bloodsucker
+
+ // NOTE: Hunters are done in ..() parent proc
+
+ return ..()
+
+// Checking for ACTUALLY Dead Vamps
+/datum/game_mode/bloodsucker/are_special_antags_dead()
+ // Bloodsucker not Final Dead
+ for(var/datum/mind/bloodsucker in bloodsuckers)
+ if(!bloodsucker.AmFinalDeath())
+ return FALSE
+ return TRUE
+
+
+// Init Sunlight (called from datum_bloodsucker.on_gain(), in case game mode isn't even Bloodsucker
+/datum/game_mode/proc/check_start_sunlight()
+ // Already Sunlight (and not about to cancel)
+ if (istype(bloodsucker_sunlight) && !bloodsucker_sunlight.cancel_me)
+ return
+ bloodsucker_sunlight = new ()
+
+// End Sun (last bloodsucker removed)
+/datum/game_mode/proc/check_cancel_sunlight()
+ // No Sunlight
+ if (!istype(bloodsucker_sunlight))
+ return
+ if (bloodsuckers.len <= 0)
+ bloodsucker_sunlight.cancel_me = TRUE
+ qdel(bloodsucker_sunlight)
+ bloodsucker_sunlight = null
+
+/datum/game_mode/proc/is_daylight()
+ return istype(bloodsucker_sunlight) && bloodsucker_sunlight.amDay
+
+//////////////////////////////////////////////////////////////////////////////
+
+
+/datum/game_mode/proc/can_make_bloodsucker(datum/mind/bloodsucker, datum/mind/creator, display_warning=TRUE) // Creator is just here so we can display fail messages to whoever is turning us.
+ // No Mind
+ if(!bloodsucker || !bloodsucker.key) // KEY is client login?
+ //if(creator) // REMOVED. You wouldn't see their name if there is no mind, so why say anything?
+ // to_chat(creator, "[bloodsucker] isn't self-aware enough to be raised as a Bloodsucker!")
+ return FALSE
+ // Current body is invalid
+ if(!ishuman(bloodsucker.current))// && !ismonkey(bloodsucker.current))
+ if(display_warning && creator)
+ to_chat(creator, "[bloodsucker] isn't evolved enough to be raised as a Bloodsucker!")
+ return FALSE
+ // Species Must have a HEART (Sorry Plasmabois)
+ var/mob/living/carbon/human/H = bloodsucker.current
+ if(NOBLOOD in H.dna.species.species_traits)
+ if(display_warning && creator)
+ to_chat(creator, "[bloodsucker]'s DNA isn't compatible!")
+ return FALSE
+ // Already a Non-Human Antag
+ if(bloodsucker.has_antag_datum(/datum/antagonist/abductor) || bloodsucker.has_antag_datum(/datum/antagonist/devil) || bloodsucker.has_antag_datum(/datum/antagonist/changeling))
+ return FALSE
+ // Already a vamp
+ if(bloodsucker.has_antag_datum(ANTAG_DATUM_BLOODSUCKER))
+ if(display_warning && creator)
+ to_chat(creator, "[bloodsucker] is already a Bloodsucker!")
+ return FALSE
+ // Not High Enough
+ if(creator)
+ var/datum/antagonist/bloodsucker/creator_bloodsucker = creator.has_antag_datum(ANTAG_DATUM_BLOODSUCKER)
+ if(!istype(creator_bloodsucker) || creator_bloodsucker.vamplevel < BLOODSUCKER_LEVEL_TO_EMBRACE)
+ to_chat(creator, "Your blood is too thin to turn this corpse!")
+ return FALSE
+ return TRUE
+
+
+/datum/game_mode/proc/make_bloodsucker(datum/mind/bloodsucker, datum/mind/creator = null) // NOTE: This is a game_mode/proc, NOT a game_mode/bloodsucker/proc! We need to access this function despite the game mode.
+ if (!can_make_bloodsucker(bloodsucker))
+ return FALSE
+
+ // Create Datum: Fledgling
+ var/datum/antagonist/bloodsucker/A
+
+ // [FLEDGLING]
+ if (creator)
+ A = new (bloodsucker)
+ A.creator = creator
+ bloodsucker.add_antag_datum(A)
+ // Log
+ message_admins("[bloodsucker] has become a Bloodsucker, and was created by [creator].")
+ log_admin("[bloodsucker] has become a Bloodsucker, and was created by [creator].")
+
+ // [MASTER]
+ else
+ A = bloodsucker.add_antag_datum(ANTAG_DATUM_BLOODSUCKER)
+
+
+ return TRUE
+
+
+/datum/game_mode/proc/remove_bloodsucker(datum/mind/bloodsucker)
+ bloodsucker.remove_antag_datum(ANTAG_DATUM_BLOODSUCKER)
+
+
+/datum/game_mode/proc/clean_invalid_species(datum/mind/bloodsucker)
+ // Only checking for Humans here
+ if (!ishuman(bloodsucker.current) || !bloodsucker.current.client)
+ return
+ var/am_valid = TRUE
+ var/mob/living/carbon/human/H = bloodsucker.current
+
+ // Check if PLASMAMAN?
+ if(NOBLOOD in H.dna.species.species_traits)
+ am_valid = FALSE
+
+ // PROBLEM:
+ //
+ // Setting species leaves clothes on. If you were a plasmaman, we need to reassign your entire outfit. Otherwise
+ // everyone will wonder why you're a human with Plasma clothes (jk they'll know you're antag)
+
+ // Convert to HUMAN (along with ID and PDA)
+ if (!am_valid)
+ H.set_species(/datum/species/human)
+ H.real_name = H.client.prefs.custom_names["human"]
+ var/obj/item/card/id/ID = H.wear_id?.GetID()
+ if(ID)
+ ID.registered_name = H.real_name
+ ID.update_label()
+
+
+/datum/game_mode/proc/can_make_vassal(mob/living/target, datum/mind/creator, display_warning=TRUE)//, check_antag_or_loyal=FALSE)
+ // Not Correct Type: Abort
+ if (!iscarbon(target) || !creator)
+ return FALSE
+ if (target.stat > UNCONSCIOUS)
+ return FALSE
+ // Check Overdose: Am I even addicted to blood? Do I even have any in me?
+ //if (!target.reagents.addiction_list || !target.reagents.reagent_list)
+ //message_admins("DEBUG2: can_make_vassal() Abort: No reagents")
+ // return 0
+ // Check Overdose: Did my current volume go over the Overdose threshold?
+ //var/am_addicted = 0
+ //for (var/datum/reagent/blood/vampblood/blood in target.reagents.addiction_list) // overdosed is tracked in reagent_list, not addiction_list.
+ //message_admins("DEBUG3: can_make_vassal() Found Blood! [blood] [blood.overdose]")
+ //if (blood.overdosed)
+ // am_addicted = 1 // Blood is present in addiction? That's all we need.
+ // break
+
+ //if (!am_addicted)
+ //message_admins("DEBUG4: can_make_vassal() Abort: No Blood")
+ // return 0
+ // No Mind!
+ if (!target.mind || !target.mind.key)
+ if (display_warning)
+ to_chat(creator, "[target] isn't self-aware enough to be made into a Vassal.")
+ return FALSE
+ // Already MY Vassal
+ var/datum/antagonist/vassal/V = target.mind.has_antag_datum(ANTAG_DATUM_VASSAL)
+ if (istype(V) && V.master)
+ if (V.master.owner == creator)
+ if (display_warning)
+ to_chat(creator, "[target] is already your loyal Vassal!")
+ else
+ if (display_warning)
+ to_chat(creator, "[target] is the loyal Vassal of another Bloodsucker!")
+ return FALSE
+ // Already Antag or Loyal (Vamp Hunters count as antags)
+ if (target.mind.enslaved_to || AmInvalidAntag(target.mind)) //!VassalCheckAntagValid(target.mind, check_antag_or_loyal)) // HAS_TRAIT(target, TRAIT_MINDSHIELD, "implant") ||
+ if (display_warning)
+ to_chat(creator, "[target] resists the power of your blood to dominate their mind!")
+ return FALSE
+ return TRUE
+
+
+/datum/game_mode/proc/AmValidAntag(datum/mind/M)
+ // No List?
+ if(!islist(M.antag_datums) || M.antag_datums.len == 0)
+ return FALSE
+ // Am I NOT an invalid Antag? NOTE: We already excluded non-antags above. Don't worry about the "No List?" check in AmInvalidIntag()
+ return !AmInvalidAntag(M)
+
+/datum/game_mode/proc/AmInvalidAntag(datum/mind/M)
+ // No List?
+ if(!islist(M.antag_datums) || M.antag_datums.len == 0)
+ return FALSE
+ // Does even ONE antag appear in this mind that isn't in the list? Then FAIL!
+ for(var/datum/antagonist/antag_datum in M.antag_datums)
+ if (!(antag_datum.type in vassal_allowed_antags)) // vassal_allowed_antags is a list stored in the game mode, above.
+ //message_admins("DEBUG VASSAL: Found Invalid: [antag_datum] // [antag_datum.type]")
+ return TRUE
+ //message_admins("DEBUG VASSAL: Valid Antags! (total of [M.antag_datums.len])")
+ // WHEN YOU DELETE THE ABOVE: Remove the 3 second timer on converting the vassal too.
+ return FALSE
+
+/datum/game_mode/proc/make_vassal(mob/living/target, datum/mind/creator)
+ if (!can_make_vassal(target,creator))
+ return FALSE
+ // Make Vassal
+ var/datum/antagonist/vassal/V = new (target.mind)
+ var/datum/antagonist/bloodsucker/B = creator.has_antag_datum(ANTAG_DATUM_BLOODSUCKER)
+ V.master = B
+ target.mind.add_antag_datum(V, V.master.get_team())
+ // Update Bloodsucker Title (we're a daddy now)
+ B.SelectTitle(am_fledgling = FALSE) // Only works if you have no title yet.
+ // Log
+ message_admins("[target] has become a Vassal, and is enslaved to [creator].")
+ log_admin("[target] has become a Vassal, and is enslaved to [creator].")
+ return TRUE
+
+/datum/game_mode/proc/remove_vassal(datum/mind/vassal)
+ vassal.remove_antag_datum(ANTAG_DATUM_VASSAL)
diff --git a/code/game/gamemodes/bloodsucker/hunter.dm b/code/game/gamemodes/bloodsucker/hunter.dm
new file mode 100644
index 0000000000..cec990e9c3
--- /dev/null
+++ b/code/game/gamemodes/bloodsucker/hunter.dm
@@ -0,0 +1,50 @@
+
+
+/*
+// Called from game mode pre_setup()
+/datum/game_mode/proc/assign_monster_hunters(monster_count = 4, guaranteed_hunters = FALSE, list/datum/mind/exclude_from_hunter)
+
+ // Not all game modes GUARANTEE a hunter
+ if (rand(0,2) == 0) // 50% of the time, we get fewer or NO Hunters
+ if (!guaranteed_hunters)
+ return
+ else
+ monster_count /= 2
+
+ var/list/no_hunter_jobs = list("AI","Cyborg")
+
+ // Set Restricted Jobs
+ if(CONFIG_GET(flag/protect_roles_from_antagonist))
+ no_hunter_jobs += list("Security Officer", "Warden", "Detective", "Head of Security", "Captain", "Head of Personnel", "Chief Engineer", "Chief Medical Officer", "Research Director", "Quartermaster")
+
+ if(CONFIG_GET(flag/protect_assistant_from_antagonist))
+ no_hunter_jobs += "Assistant"
+
+ // Find Valid Hunters
+ var/list/datum/mind/hunter_candidates = get_players_for_role(ROLE_MONSTERHUNTER)
+
+ // Assign Hunters (as many as vamps, plus one)
+ for(var/i = 1, i < monster_count, i++) // Start at 1 so we skip Hunters if there's only one sucker.
+ if (!hunter_candidates.len)
+ break
+ // Assign Hunter
+ var/datum/mind/hunter = pick(hunter_candidates)
+ hunter_candidates.Remove(hunter) // Remove Either Way
+ // Already Antag? Skip
+ if (islist(exclude_from_hunter) && (locate(hunter) in exclude_from_hunter)) //if (islist(hunter.antag_datums) && hunter.antag_datums.len)
+ i --
+ continue
+ // NOTE:
+ vamphunters += hunter
+ hunter.restricted_roles = no_hunter_jobs
+ log_game("[hunter.key] (ckey) has been selected as a Hunter.")
+
+// Called from game mode post_setup()
+/datum/game_mode/proc/finalize_monster_hunters(monster_count = 4)
+ var/amEvil = TRUE // First hunter is always an evil boi
+ for(var/datum/mind/hunter in vamphunters)
+ var/datum/antagonist/vamphunter/A = new (hunter)
+ A.bad_dude = amEvil
+ hunter.add_antag_datum(A)
+ amEvil = FALSE // Every other hunter is just a boring greytider
+*/
diff --git a/code/game/gamemodes/clown_ops/clown_weapons.dm b/code/game/gamemodes/clown_ops/clown_weapons.dm
index ee96d1fa10..9b52ddda1e 100644
--- a/code/game/gamemodes/clown_ops/clown_weapons.dm
+++ b/code/game/gamemodes/clown_ops/clown_weapons.dm
@@ -265,6 +265,7 @@
armor = list("melee" = 40, "bullet" = 40, "laser" = 50, "energy" = 35, "bomb" = 20, "bio" = 0, "rad" = 0, "fire" = 100, "acid" = 100)
max_temperature = 35000
operation_req_access = list(ACCESS_SYNDICATE)
+ internals_req_access = list(ACCESS_SYNDICATE)
wreckage = /obj/structure/mecha_wreckage/honker/dark
max_equip = 3
spawn_tracked = FALSE
diff --git a/code/game/gamemodes/dynamic/dynamic.dm b/code/game/gamemodes/dynamic/dynamic.dm
index cd9500284b..67ea855466 100644
--- a/code/game/gamemodes/dynamic/dynamic.dm
+++ b/code/game/gamemodes/dynamic/dynamic.dm
@@ -236,6 +236,9 @@ GLOBAL_VAR_INIT(dynamic_forced_threat_level, -1)
return rule.round_result()
return ..()
+/datum/game_mode/dynamic/generate_report()
+ return "Mysterious signals that demonstrate strange dynamics have been detected in your sector. Watch out for oddities."
+
/datum/game_mode/dynamic/send_intercept()
. = "Central Command Status Summary
| [sanitize(R.name)] ([price_listed]) | " + if(R.amount > 0 && ((C && C.registered_account && onstation) || (!onstation && isliving(user)))) + dat += "[R.amount] Vend | " + else + dat += "Not Available | " + dat += "
"
dat += "Flavor Text" dat += "Set Examine Text" @@ -1201,7 +1196,7 @@ GLOBAL_LIST_EMPTY(preferences_datums) dat += " " dat += " \ + dat += " \ Quirk balance remaining: [GetQuirkBalance()] " for(var/V in SSquirks.quirks) var/datum/quirk/T = SSquirks.quirks[V] @@ -1232,12 +1227,12 @@ GLOBAL_LIST_EMPTY(preferences_datums) LOCKED: [lock_reason] " else if(has_quirk) - dat += "[quirk_name] - [initial(T.desc)] \ - [has_quirk ? "Lose" : "Take"] ([quirk_cost] pts.) " + dat += "[has_quirk ? "Remove" : "Take"] ([quirk_cost] pts.) \ + [quirk_name] - [initial(T.desc)] " else - dat += "[quirk_name] - [initial(T.desc)] \ - [has_quirk ? "Lose" : "Take"] ([quirk_cost] pts.) " - dat += " " + dat += " Quirk Preferences ", 900, 600) //no reason not to reuse the occupation window, as it's cleaner that way
popup.set_window_options("can_close=0")
@@ -1251,6 +1246,12 @@ GLOBAL_LIST_EMPTY(preferences_datums)
bal -= initial(T.value)
return bal
+/datum/preferences/proc/GetPositiveQuirkCount()
+ . = 0
+ for(var/q in all_quirks)
+ if(SSquirks.quirk_points[q] > 0)
+ .++
+
/datum/preferences/Topic(href, href_list, hsrc) //yeah, gotta do this I guess..
. = ..()
if(href_list["close"])
@@ -1316,43 +1317,30 @@ GLOBAL_LIST_EMPTY(preferences_datums)
var/quirk = href_list["trait"]
if(!SSquirks.quirks[quirk])
return
+ for(var/V in SSquirks.quirk_blacklist) //V is a list
+ var/list/L = V
+ for(var/Q in all_quirks)
+ if((quirk in L) && (Q in L) && !(Q == quirk)) //two quirks have lined up in the list of the list of quirks that conflict with each other, so return (see quirks.dm for more details)
+ to_chat(user, "[quirk] is incompatible with [Q].")
+ return
var/value = SSquirks.quirk_points[quirk]
- if(value == 0)
- if(quirk in neutral_quirks)
- neutral_quirks -= quirk
- all_quirks -= quirk
- else
- neutral_quirks += quirk
- all_quirks += quirk
+ var/balance = GetQuirkBalance()
+ if(quirk in all_quirks)
+ if(balance + value < 0)
+ to_chat(user, "Refunding this would cause you to go below your balance!")
+ return
+ all_quirks -= quirk
else
- var/balance = GetQuirkBalance()
- if(quirk in positive_quirks)
- positive_quirks -= quirk
- all_quirks -= quirk
- else if(quirk in negative_quirks)
- if(balance + value < 0)
- to_chat(user, "Refunding this would cause you to go below your balance!")
- return
- negative_quirks -= quirk
- all_quirks -= quirk
- else if(value > 0)
- if(positive_quirks.len >= MAX_QUIRKS)
- to_chat(user, "You can't have more than [MAX_QUIRKS] positive quirks!")
- return
- if(balance - value < 0)
- to_chat(user, "You don't have enough balance to gain this quirk!")
- return
- positive_quirks += quirk
- all_quirks += quirk
- else
- negative_quirks += quirk
- all_quirks += quirk
+ if(GetPositiveQuirkCount() >= MAX_QUIRKS)
+ to_chat(user, "You can't have more than [MAX_QUIRKS] positive quirks!")
+ return
+ if(balance - value < 0)
+ to_chat(user, "You don't have enough balance to gain this quirk!")
+ return
+ all_quirks += quirk
SetQuirks(user)
if("reset")
all_quirks = list()
- positive_quirks = list()
- negative_quirks = list()
- neutral_quirks = list()
SetQuirks(user)
else
SetQuirks(user)
@@ -1565,10 +1553,8 @@ GLOBAL_LIST_EMPTY(preferences_datums)
var/temp_hsv = RGBtoHSV(new_mutantcolor)
if(new_mutantcolor == "#000000")
features["mcolor"] = pref_species.default_color
- update_preview_icon()
else if((MUTCOLORS_PARTSONLY in pref_species.species_traits) || ReadHSV(temp_hsv)[3] >= ReadHSV("#202020")[3]) // mutantcolors must be bright, but only if they affect the skin
features["mcolor"] = sanitize_hexcolor(new_mutantcolor)
- update_preview_icon()
else
to_chat(user, "Invalid color. Your color is not bright enough.")
@@ -1578,10 +1564,8 @@ GLOBAL_LIST_EMPTY(preferences_datums)
var/temp_hsv = RGBtoHSV(new_mutantcolor)
if(new_mutantcolor == "#000000")
features["mcolor2"] = pref_species.default_color
- update_preview_icon()
else if((MUTCOLORS_PARTSONLY in pref_species.species_traits) || ReadHSV(temp_hsv)[3] >= ReadHSV("#202020")[3]) // mutantcolors must be bright, but only if they affect the skin
features["mcolor2"] = sanitize_hexcolor(new_mutantcolor)
- update_preview_icon()
else
to_chat(user, "Invalid color. Your color is not bright enough.")
@@ -1591,10 +1575,8 @@ GLOBAL_LIST_EMPTY(preferences_datums)
var/temp_hsv = RGBtoHSV(new_mutantcolor)
if(new_mutantcolor == "#000000")
features["mcolor3"] = pref_species.default_color
- update_preview_icon()
else if((MUTCOLORS_PARTSONLY in pref_species.species_traits) || ReadHSV(temp_hsv)[3] >= ReadHSV("#202020")[3]) // mutantcolors must be bright, but only if they affect the skin
features["mcolor3"] = sanitize_hexcolor(new_mutantcolor)
- update_preview_icon()
else
to_chat(user, "Invalid color. Your color is not bright enough.")
@@ -1736,14 +1718,12 @@ GLOBAL_LIST_EMPTY(preferences_datums)
features["body_markings"] = new_body_markings
if(new_body_markings != "None")
features["mam_body_markings"] = "None"
- update_preview_icon()
if("legs")
var/new_legs
new_legs = input(user, "Choose your character's legs:", "Character Preference") as null|anything in GLOB.legs_list
if(new_legs)
features["legs"] = new_legs
- update_preview_icon()
if("insect_wings")
var/new_insect_wings
@@ -1829,7 +1809,6 @@ GLOBAL_LIST_EMPTY(preferences_datums)
else if(new_mam_body_markings == "None")
features["mam_body_markings"] = "Plain"
features["body_markings"] = "None"
- update_preview_icon()
//Xeno Bodyparts
if("xenohead")//Head or caste type
@@ -2307,8 +2286,8 @@ GLOBAL_LIST_EMPTY(preferences_datums)
pref_species = new /datum/species/human
save_character()
- character.set_species(chosen_species, icon_update = FALSE, pref_load = TRUE)
character.dna.features = features.Copy()
+ character.set_species(chosen_species, icon_update = FALSE, pref_load = TRUE)
character.dna.real_name = character.real_name
character.dna.nameless = character.nameless
character.dna.custom_species = character.custom_species
@@ -2337,7 +2316,6 @@ GLOBAL_LIST_EMPTY(preferences_datums)
if(icon_updates)
character.update_body()
character.update_hair()
- character.update_body_parts()
/datum/preferences/proc/get_default_name(name_id)
switch(name_id)
diff --git a/code/modules/client/preferences_savefile.dm b/code/modules/client/preferences_savefile.dm
index 29ea8f5821..f7f49a66af 100644
--- a/code/modules/client/preferences_savefile.dm
+++ b/code/modules/client/preferences_savefile.dm
@@ -112,7 +112,6 @@ SAVEFILE UPDATING/VERSIONING - 'Simplified', or rather, more coder-friendly ~Car
if(current_version < 24 && S["feature_exhibitionist"])
var/datum/quirk/exhibitionism/E
var/quirk_name = initial(E.name)
- neutral_quirks += quirk_name
all_quirks += quirk_name
/datum/preferences/proc/load_path(ckey,filename="preferences.sav")
@@ -386,9 +385,6 @@ SAVEFILE UPDATING/VERSIONING - 'Simplified', or rather, more coder-friendly ~Car
//Quirks
S["all_quirks"] >> all_quirks
- S["positive_quirks"] >> positive_quirks
- S["negative_quirks"] >> negative_quirks
- S["neutral_quirks"] >> neutral_quirks
//Citadel code
S["feature_genitals_use_skintone"] >> features["genitals_use_skintone"]
@@ -519,10 +515,6 @@ SAVEFILE UPDATING/VERSIONING - 'Simplified', or rather, more coder-friendly ~Car
all_quirks = SANITIZE_LIST(all_quirks)
- positive_quirks = SANITIZE_LIST(positive_quirks)
- negative_quirks = SANITIZE_LIST(negative_quirks)
- neutral_quirks = SANITIZE_LIST(neutral_quirks)
-
cit_character_pref_load(S)
return 1
@@ -598,9 +590,6 @@ SAVEFILE UPDATING/VERSIONING - 'Simplified', or rather, more coder-friendly ~Car
//Quirks
WRITE_FILE(S["all_quirks"] , all_quirks)
- WRITE_FILE(S["positive_quirks"] , positive_quirks)
- WRITE_FILE(S["negative_quirks"] , negative_quirks)
- WRITE_FILE(S["neutral_quirks"] , neutral_quirks)
cit_character_pref_save(S)
diff --git a/code/modules/client/verbs/aooc.dm b/code/modules/client/verbs/aooc.dm
index 311c22955d..3f86a617e6 100644
--- a/code/modules/client/verbs/aooc.dm
+++ b/code/modules/client/verbs/aooc.dm
@@ -116,7 +116,7 @@ GLOBAL_VAR_INIT(normal_aooc_colour, "#ce254f")
antaglisting |= M.current.client
for(var/mob/M in GLOB.player_list)
- if(M.client && (M.stat == DEAD || M.client.holder))
+ if(M.client && (M.stat == DEAD || M.client.holder || is_special_character(M)))
antaglisting |= M.client
for(var/client/C in antaglisting)
diff --git a/code/modules/clothing/clothing.dm b/code/modules/clothing/clothing.dm
index f8f1a67e5a..dc9e46c4b4 100644
--- a/code/modules/clothing/clothing.dm
+++ b/code/modules/clothing/clothing.dm
@@ -253,16 +253,14 @@ BLIND // can't see anything
H.update_suit_sensors()
/obj/item/clothing/under/AltClick(mob/user)
- if(..())
- return 1
-
+ . = ..()
if(!istype(user) || !user.canUseTopic(src, BE_CLOSE, ismonkey(user)))
return
+ if(attached_accessory)
+ remove_accessory(user)
else
- if(attached_accessory)
- remove_accessory(user)
- else
- rolldown()
+ rolldown()
+ return TRUE
/obj/item/clothing/under/verb/jumpsuit_adjust()
set name = "Adjust Jumpsuit Style"
diff --git a/code/modules/clothing/glasses/_glasses.dm b/code/modules/clothing/glasses/_glasses.dm
index 608f71cf11..206165a495 100644
--- a/code/modules/clothing/glasses/_glasses.dm
+++ b/code/modules/clothing/glasses/_glasses.dm
@@ -43,15 +43,16 @@
//called when thermal glasses are emped.
/obj/item/clothing/glasses/proc/thermal_overload()
- if(ishuman(src.loc))
- var/mob/living/carbon/human/H = src.loc
- if(!(HAS_TRAIT(H, TRAIT_BLIND)))
- if(H.glasses == src)
- to_chat(H, "[src] overloads and blinds you!")
- H.flash_act(visual = 1)
- H.blind_eyes(3)
- H.blur_eyes(5)
- H.adjust_eye_damage(5)
+ if(!ishuman(loc))
+ return
+ var/mob/living/carbon/human/H = loc
+ var/obj/item/organ/eyes/eyes = H.getorganslot(ORGAN_SLOT_EYES)
+ if((!HAS_TRAIT(H, TRAIT_BLIND) || !eyes) && H.glasses == src)
+ to_chat(H, "[src] overloads and blinds you!")
+ H.flash_act(visual = 1)
+ H.blind_eyes(3)
+ H.blur_eyes(5)
+ eyes.applyOrganDamage(5)
/obj/item/clothing/glasses/meson
name = "optical meson scanner"
@@ -427,19 +428,17 @@
..()
/obj/item/clothing/glasses/AltClick(mob/user)
+ . = ..()
if(glass_colour_type && ishuman(user))
var/mob/living/carbon/human/H = user
- if(H.client)
- if(H.client.prefs)
- if(src == H.glasses)
- H.client.prefs.uses_glasses_colour = !H.client.prefs.uses_glasses_colour
- if(H.client.prefs.uses_glasses_colour)
- to_chat(H, "You will now see glasses colors.")
- else
- to_chat(H, "You will no longer see glasses colors.")
- H.update_glasses_color(src, 1)
- else
- return ..()
+ if(H.client?.prefs && src == H.glasses)
+ H.client.prefs.uses_glasses_colour = !H.client.prefs.uses_glasses_colour
+ if(H.client.prefs.uses_glasses_colour)
+ to_chat(H, "You will now see glasses colors.")
+ else
+ to_chat(H, "You will no longer see glasses colors.")
+ H.update_glasses_color(src, 1)
+ return TRUE
/obj/item/clothing/glasses/proc/change_glass_color(mob/living/carbon/human/H, datum/client_colour/glass_colour/new_color_type)
var/old_colour_type = glass_colour_type
diff --git a/code/modules/clothing/head/hardhat.dm b/code/modules/clothing/head/hardhat.dm
index fa61b81177..19f03dfd0a 100644
--- a/code/modules/clothing/head/hardhat.dm
+++ b/code/modules/clothing/head/hardhat.dm
@@ -115,8 +115,10 @@
toggle_helmet_light(user)
/obj/item/clothing/head/hardhat/weldhat/AltClick(mob/user)
+ . = ..()
if(user.canUseTopic(src, BE_CLOSE))
toggle_welding_screen(user)
+ return TRUE
/obj/item/clothing/head/hardhat/weldhat/proc/toggle_welding_screen(mob/living/user)
if(weldingvisortoggle(user))
diff --git a/code/modules/clothing/head/jobs.dm b/code/modules/clothing/head/jobs.dm
index 8ff6fe8bd1..05116c8b97 100644
--- a/code/modules/clothing/head/jobs.dm
+++ b/code/modules/clothing/head/jobs.dm
@@ -104,16 +104,16 @@
. += "Alt-click to take a candy corn."
/obj/item/clothing/head/fedora/det_hat/AltClick(mob/user)
- if(user.canUseTopic(src, BE_CLOSE, ismonkey(user)))
- ..()
- if(loc == user)
- if(candy_cooldown < world.time)
- var/obj/item/reagent_containers/food/snacks/candy_corn/CC = new /obj/item/reagent_containers/food/snacks/candy_corn(src)
- user.put_in_hands(CC)
- to_chat(user, "You slip a candy corn from your hat.")
- candy_cooldown = world.time+1200
- else
- to_chat(user, "You just took a candy corn! You should wait a couple minutes, lest you burn through your stash.")
+ . = ..()
+ if(loc == user && user.canUseTopic(src, BE_CLOSE, ismonkey(user)))
+ if(candy_cooldown < world.time)
+ var/obj/item/reagent_containers/food/snacks/candy_corn/CC = new /obj/item/reagent_containers/food/snacks/candy_corn(src)
+ user.put_in_hands(CC)
+ to_chat(user, "You slip a candy corn from your hat.")
+ candy_cooldown = world.time+1200
+ else
+ to_chat(user, "You just took a candy corn! You should wait a couple minutes, lest you burn through your stash.")
+ return TRUE
//Mime
diff --git a/code/modules/clothing/head/misc.dm b/code/modules/clothing/head/misc.dm
index dabfaf06cd..3fa5d56d1e 100644
--- a/code/modules/clothing/head/misc.dm
+++ b/code/modules/clothing/head/misc.dm
@@ -370,13 +370,25 @@
/obj/item/clothing/head/hotel
name = "Telegram cap"
desc = "A bright red cap warn by hotel staff. Or people who want to be a singing telegram"
- icon_state = "telegramhat"
- item_color = "telegramhat"
- dog_fashion = null
+ icon_state = "telegram"
+ item_color = "telegram"
+ dog_fashion = /datum/dog_fashion/head/telegram
/obj/item/clothing/head/colour
name = "Singer cap"
desc = "A light white hat that has bands of color. Just makes you want to sing and dance!"
icon_state = "colour"
item_color = "colour"
- dog_fashion = /datum/dog_fashion/head/colour
\ No newline at end of file
+ dog_fashion = /datum/dog_fashion/head/colour
+
+/obj/item/clothing/head/christmashat
+ name = "red santa hat"
+ desc = "A red Christmas Hat! How festive!"
+ icon_state = "christmashat"
+ item_state = "christmashat"
+
+/obj/item/clothing/head/christmashatg
+ name = "green santa hat"
+ desc = "A green Christmas Hat! How festive!"
+ icon_state = "christmashatg"
+ item_state = "christmashatg"
diff --git a/code/modules/clothing/head/soft_caps.dm b/code/modules/clothing/head/soft_caps.dm
index 4daa737877..69ec1ccedd 100644
--- a/code/modules/clothing/head/soft_caps.dm
+++ b/code/modules/clothing/head/soft_caps.dm
@@ -22,11 +22,11 @@
/obj/item/clothing/head/soft/AltClick(mob/user)
- ..()
+ . = ..()
if(!user.canUseTopic(src, BE_CLOSE, ismonkey(user)))
return
- else
- flip(user)
+ flip(user)
+ return TRUE
/obj/item/clothing/head/soft/proc/flip(mob/user)
diff --git a/code/modules/clothing/masks/breath.dm b/code/modules/clothing/masks/breath.dm
index 7d0c4a455e..f4335d17e8 100644
--- a/code/modules/clothing/masks/breath.dm
+++ b/code/modules/clothing/masks/breath.dm
@@ -23,11 +23,11 @@
adjustmask(user)
/obj/item/clothing/mask/breath/AltClick(mob/user)
- ..()
+ . = ..()
if(!user.canUseTopic(src, BE_CLOSE, ismonkey(user)))
return
- else
- adjustmask(user)
+ adjustmask(user)
+ return TRUE
/obj/item/clothing/mask/breath/examine(mob/user)
. = ..()
diff --git a/code/modules/clothing/neck/_neck.dm b/code/modules/clothing/neck/_neck.dm
index 2356bb16eb..e863dc8860 100644
--- a/code/modules/clothing/neck/_neck.dm
+++ b/code/modules/clothing/neck/_neck.dm
@@ -66,7 +66,9 @@
var/obj/item/organ/heart/heart = M.getorganslot(ORGAN_SLOT_HEART)
var/obj/item/organ/lungs/lungs = M.getorganslot(ORGAN_SLOT_LUNGS)
- if(!(M.stat == DEAD || (HAS_TRAIT(M, TRAIT_FAKEDEATH))))
+ if (!do_mob(user,M,60)) // Stethoscope should take a moment to listen
+ return // FAIL
+ if(!(M.stat == DEAD || (HAS_TRAIT(M, TRAIT_FAKEDEATH)) || (HAS_TRAIT(M, TRAIT_NOPULSE))))
if(heart && istype(heart))
heart_strength = "an unstable"
if(heart.beating)
diff --git a/code/modules/clothing/shoes/miscellaneous.dm b/code/modules/clothing/shoes/miscellaneous.dm
index 080d9281cf..3d318db761 100644
--- a/code/modules/clothing/shoes/miscellaneous.dm
+++ b/code/modules/clothing/shoes/miscellaneous.dm
@@ -129,6 +129,21 @@
max_heat_protection_temperature = SHOES_MAX_TEMP_PROTECT
pocket_storage_component_path = /datum/component/storage/concrete/pockets/shoes
+/obj/item/clothing/shoes/winterboots/christmasbootsr
+ name = "red christmas boots"
+ desc = "A pair of fluffy red christmas boots!"
+ icon_state = "christmasbootsr"
+
+/obj/item/clothing/shoes/winterboots/christmasbootsg
+ name = "green christmas boots"
+ desc = "A pair of fluffy green christmas boots!"
+ icon_state = "christmasbootsg"
+
+/obj/item/clothing/shoes/winterboots/santaboots
+ name = "santa boots"
+ desc = "A pair of santa boots! How traditional!!"
+ icon_state = "santaboots"
+
/obj/item/clothing/shoes/workboots
name = "work boots"
desc = "Nanotrasen-issue Engineering lace-up work boots for the especially blue-collar."
diff --git a/code/modules/clothing/spacesuits/hardsuit.dm b/code/modules/clothing/spacesuits/hardsuit.dm
index eda12d66a9..c6eac4ac56 100644
--- a/code/modules/clothing/spacesuits/hardsuit.dm
+++ b/code/modules/clothing/spacesuits/hardsuit.dm
@@ -964,13 +964,12 @@
. += energy_overlay
/obj/item/clothing/suit/space/hardsuit/lavaknight/AltClick(mob/living/user)
- if(user.incapacitated() || !istype(user))
+ . = ..()
+ if(!in_range(src, user) || !istype(user))
+ return
+ if(user.incapacitated())
to_chat(user, "You can't do that right now!")
- return
- if(!in_range(src, user))
- return
- if(user.incapacitated() || !istype(user) || !in_range(src, user))
- return
+ return TRUE
if(alert("Are you sure you want to recolor your armor stripes?", "Confirm Repaint", "Yes", "No") == "Yes")
var/energy_color_input = input(usr,"","Choose Energy Color",energy_color) as color|null
@@ -986,6 +985,7 @@
user.update_inv_wear_suit()
light_color = energy_color
update_light()
+ return TRUE
/obj/item/clothing/suit/space/hardsuit/lavaknight/examine(mob/user)
. = ..()
diff --git a/code/modules/clothing/suits/miscellaneous.dm b/code/modules/clothing/suits/miscellaneous.dm
index b67fda421e..6548b4e7fe 100644
--- a/code/modules/clothing/suits/miscellaneous.dm
+++ b/code/modules/clothing/suits/miscellaneous.dm
@@ -828,3 +828,33 @@
body_parts_covered = CHEST|GROIN|ARMS|LEGS
flags_inv = HIDEJUMPSUIT
resistance_flags = NONE
+
+/obj/item/clothing/suit/hooded/wintercoat/christmascoatr
+ name = "red christmas coat"
+ desc = "A festive red Christmas coat! Smells like Candy Cane!"
+ icon_state = "christmascoatr"
+ item_state = "christmascoatr"
+ hoodtype = /obj/item/clothing/head/hooded/winterhood/christmashoodr
+
+/obj/item/clothing/head/hooded/winterhood/christmashoodr
+ icon_state = "christmashoodr"
+
+/obj/item/clothing/suit/hooded/wintercoat/christmascoatg
+ name = "green christmas coat"
+ desc = "A festive green Christmas coat! Smells like Candy Cane!"
+ icon_state = "christmascoatg"
+ item_state = "christmascoatg"
+ hoodtype = /obj/item/clothing/head/hooded/winterhood/christmashoodg
+
+/obj/item/clothing/head/hooded/winterhood/christmashoodg
+ icon_state = "christmashoodg"
+
+/obj/item/clothing/suit/hooded/wintercoat/christmascoatrg
+ name = "red and green christmas coat"
+ desc = "A festive red and green Christmas coat! Smells like Candy Cane!"
+ icon_state = "christmascoatrg"
+ item_state = "christmascoatrg"
+ hoodtype = /obj/item/clothing/head/hooded/winterhood/christmashoodrg
+
+/obj/item/clothing/head/hooded/winterhood/christmashoodrg
+ icon_state = "christmashoodrg"
diff --git a/code/modules/clothing/suits/toggles.dm b/code/modules/clothing/suits/toggles.dm
index f869eb5d44..559d3006a1 100644
--- a/code/modules/clothing/suits/toggles.dm
+++ b/code/modules/clothing/suits/toggles.dm
@@ -92,11 +92,11 @@
//Toggle exosuits for different aesthetic styles (hoodies, suit jacket buttons, etc)
/obj/item/clothing/suit/toggle/AltClick(mob/user)
- ..()
+ . = ..()
if(!user.canUseTopic(src, BE_CLOSE, ismonkey(user)))
return
- else
- suit_toggle(user)
+ suit_toggle(user)
+ return TRUE
/obj/item/clothing/suit/toggle/ui_action_click()
suit_toggle()
diff --git a/code/modules/clothing/under/accessories.dm b/code/modules/clothing/under/accessories.dm
index 0a0499b5f9..a4d2c17fd6 100644
--- a/code/modules/clothing/under/accessories.dm
+++ b/code/modules/clothing/under/accessories.dm
@@ -1,4 +1,4 @@
-/obj/item/clothing/accessory //Ties moved to neck slot items, but as there are still things like medals, pokadots, and armbands, this accessory system is being kept as-is
+/obj/item/clothing/accessory //Ties moved to neck slot items, but as there are still things like medals and armbands, this accessory system is being kept as-is
name = "Accessory"
desc = "Something has gone wrong!"
icon = 'icons/obj/clothing/accessories.dmi'
@@ -67,10 +67,12 @@
return
/obj/item/clothing/accessory/AltClick(mob/user)
+ . = ..()
if(istype(user) && user.canUseTopic(src, BE_CLOSE, ismonkey(user)))
if(initial(above_suit))
above_suit = !above_suit
to_chat(user, "[src] will be worn [above_suit ? "above" : "below"] your suit.")
+ return TRUE
/obj/item/clothing/accessory/examine(mob/user)
. = ..()
@@ -364,7 +366,7 @@
armor = list("melee" = 0, "bullet" = 0, "laser" = 0, "energy" = 0, "bomb" = 0, "bio" = 0, "rad" = 0, "fire" = 0, "acid" = 0)
/////////////////////
-//Synda Accessories//
+//Syndie Accessories//
/////////////////////
/obj/item/clothing/accessory/padding
@@ -387,35 +389,3 @@
icon_state = "plastics"
item_color = "nothing"
armor = list("melee" = 0, "bullet" = 0, "laser" = 20, "energy" = 10, "bomb" = 0, "bio" = 30, "rad" = 0, "fire" = 0, "acid" = -40)
-
-/////////////////////
-//Pokadots On Pants//
-/////////////////////
-
-/obj/item/clothing/accessory/attrocious_pokadots
- name = "atrocious pokadots"
- desc = "They look like something out of a thrift store. Attaches to clothing not to be worn by itself."
- icon_state = "attrocious_pokadots"
- item_color = "attrocious_pokadots"
- attack_verb = list("horrifed", "eye bleeded")
-
-/obj/item/clothing/accessory/black_white_pokadots
- name = "checkered pokadots"
- desc = "You can play a game of chess on these! Attaches to clothing not to be worn by itself."
- icon_state = "black_white_pokadots"
- item_color = "black_white_pokadots"
- attack_verb = list("check", "mate")
-
-/obj/item/clothing/accessory/nt_pokadots
- name = "blue and white pokadots"
- desc = "To show your pride in your workplace, in the most annoying possable way. Attaches to clothing not to be worn by itself."
- icon_state = "nt_pokadots"
- item_color = "nt_pokadots"
- attack_verb = list("eye bleeded", "annoyed")
-
-/obj/item/clothing/accessory/syndi_pokadots
- name = "black and red pokadots"
- desc = "King me. Attaches to clothing not to be worn by itself." //checkers!
- icon_state = "syndi_pokadots"
- item_color = "syndi_pokadots"
- attack_verb = list("jumped", "taken")
\ No newline at end of file
diff --git a/code/modules/clothing/under/miscellaneous.dm b/code/modules/clothing/under/miscellaneous.dm
index 51e34c7b3e..58df911412 100644
--- a/code/modules/clothing/under/miscellaneous.dm
+++ b/code/modules/clothing/under/miscellaneous.dm
@@ -820,4 +820,36 @@
item_color = "durathread"
can_adjust = FALSE
body_parts_covered = CHEST|GROIN|ARMS
- armor = list("melee" = 10, "laser" = 10, "fire" = 40, "acid" = 10, "bomb" = 5)
\ No newline at end of file
+ armor = list("melee" = 10, "laser" = 10, "fire" = 40, "acid" = 10, "bomb" = 5)
+
+/obj/item/clothing/under/christmas/christmasmaler
+ name = "red masculine christmas suit"
+ desc = "A simple red christmas suit that looks close to Santa's!"
+ icon_state = "christmasmaler"
+ item_state = "christmasmaler"
+ body_parts_covered = CHEST|GROIN
+ can_adjust = FALSE
+
+/obj/item/clothing/under/christmas/christmasmaleg
+ name = "green masculine christmas suit"
+ desc = "A simple green christmas suit that smells minty!"
+ icon_state = "christmasmaleg"
+ item_state = "christmasmaleg"
+ body_parts_covered = CHEST|GROIN
+ can_adjust = FALSE
+
+/obj/item/clothing/under/christmas/christmasfemaler
+ name = "red feminine christmas suit"
+ desc = "A simple red christmas suit that looks like Mrs Claus!"
+ icon_state = "christmasfemaler"
+ item_state = "christmasfemaler"
+ body_parts_covered = CHEST|GROIN
+ can_adjust = FALSE
+
+/obj/item/clothing/under/christmas/christmasfemaleg
+ name = "green feminine christmas suit"
+ desc = "A simple green christmas suit that smells minty!"
+ icon_state = "christmasfemaleg"
+ item_state = "christmasfemaleg"
+ body_parts_covered = CHEST|GROIN
+ can_adjust = FALSE
\ No newline at end of file
diff --git a/code/modules/crafting/craft.dm b/code/modules/crafting/craft.dm
index 795515835e..0e14f9f5be 100644
--- a/code/modules/crafting/craft.dm
+++ b/code/modules/crafting/craft.dm
@@ -439,4 +439,4 @@
/datum/mind/proc/teach_crafting_recipe(R)
if(!learned_recipes)
learned_recipes = list()
- learned_recipes |= R
\ No newline at end of file
+ learned_recipes |= R
diff --git a/code/modules/crafting/recipes/recipes_misc.dm b/code/modules/crafting/recipes/recipes_misc.dm
index fdfb959fe6..70dd4d1370 100644
--- a/code/modules/crafting/recipes/recipes_misc.dm
+++ b/code/modules/crafting/recipes/recipes_misc.dm
@@ -47,16 +47,17 @@
/datum/crafting_recipe/goldenbox
name = "Gold Plated Toolbox"
result = /obj/item/storage/toolbox/gold_fake
+ tools = list(/obj/item/stock_parts/cell/upgraded/plus)
reqs = list(/obj/item/stack/sheet/cardboard = 1, //so we dont null items in crafting
/obj/item/stack/cable_coil = 10,
/obj/item/stack/sheet/mineral/gold = 1,
- /obj/item/stock_parts/cell = 1,
/datum/reagent/water = 15)
time = 40
category = CAT_MISC
/datum/crafting_recipe/bronze_driver
name = "Bronze Plated Screwdriver"
+ tools = list(/obj/item/stock_parts/cell/upgraded/plus)
result = /obj/item/screwdriver/bronze
reqs = list(/obj/item/screwdriver = 1,
/obj/item/stack/cable_coil = 10,
@@ -67,6 +68,7 @@
/datum/crafting_recipe/bronze_welder
name = "Bronze Plated Welding Tool"
+ tools = list(/obj/item/stock_parts/cell/upgraded/plus)
result = /obj/item/weldingtool/bronze
reqs = list(/obj/item/weldingtool = 1,
/obj/item/stack/cable_coil = 10,
@@ -77,6 +79,7 @@
/datum/crafting_recipe/bronze_wirecutters
name = "Bronze Plated Wirecutters"
+ tools = list(/obj/item/stock_parts/cell/upgraded/plus)
result = /obj/item/wirecutters/bronze
reqs = list(/obj/item/wirecutters = 1,
/obj/item/stack/cable_coil = 10,
@@ -87,6 +90,7 @@
/datum/crafting_recipe/bronze_crowbar
name = "Bronze Plated Crowbar"
+ tools = list(/obj/item/stock_parts/cell/upgraded/plus)
result = /obj/item/crowbar/bronze
reqs = list(/obj/item/crowbar = 1,
/obj/item/stack/cable_coil = 10,
@@ -97,6 +101,7 @@
/datum/crafting_recipe/bronze_wrench
name = "Bronze Plated Wrench"
+ tools = list(/obj/item/stock_parts/cell/upgraded/plus)
result = /obj/item/wrench/bronze
reqs = list(/obj/item/wrench = 1,
/obj/item/stack/cable_coil = 10,
@@ -315,3 +320,41 @@
reqs = list(/obj/item/stack/rods = 2,
/obj/item/clothing/under/rank/security = 1)
category = CAT_MISC
+
+ /datum/crafting_recipe/bloodsucker/vassalrack
+ name = "Persuasion Rack"
+ //desc = "For converting crewmembers into loyal Vassals."
+ result = /obj/structure/bloodsucker/vassalrack
+ tools = list(/obj/item/weldingtool,
+ ///obj/item/screwdriver,
+ /obj/item/wrench
+ )
+ reqs = list(/obj/item/stack/sheet/mineral/wood = 3,
+ /obj/item/stack/sheet/metal = 2,
+ /obj/item/restraints/handcuffs/cable = 2,
+ ///obj/item/storage/belt = 1
+ ///obj/item/stack/sheet/animalhide = 1, // /obj/item/stack/sheet/leather = 1,
+ ///obj/item/stack/sheet/plasteel = 5
+ )
+ //parts = list(/obj/item/storage/belt = 1
+ // )
+
+ time = 150
+ category = CAT_MISC
+ always_availible = FALSE // Disabled til learned
+
+
+ /datum/crafting_recipe/bloodsucker/candelabrum
+ name = "Candelabrum"
+ //desc = "For converting crewmembers into loyal Vassals."
+ result = /obj/structure/bloodsucker/candelabrum
+ tools = list(/obj/item/weldingtool,
+ /obj/item/wrench
+ )
+ reqs = list(/obj/item/stack/sheet/metal = 3,
+ /obj/item/stack/rods = 1,
+ /obj/item/candle = 1
+ )
+ time = 100
+ category = CAT_MISC
+ always_availible = FALSE // Disabled til learned
diff --git a/code/modules/detectivework/scanner.dm b/code/modules/detectivework/scanner.dm
index eaa31c01bf..944fbb8df8 100644
--- a/code/modules/detectivework/scanner.dm
+++ b/code/modules/detectivework/scanner.dm
@@ -187,9 +187,11 @@
return time2text(world.time + 432000, ":ss")
/obj/item/detective_scanner/AltClick(mob/living/user)
+ . = ..()
// Best way for checking if a player can use while not incapacitated, etc
if(!user.canUseTopic(src, be_close=TRUE))
return
+ . = TRUE
if(!LAZYLEN(log))
to_chat(user, "Cannot clear logs, the scanner has no logs.")
return
diff --git a/code/modules/food_and_drinks/kitchen_machinery/icecream_vat.dm b/code/modules/food_and_drinks/kitchen_machinery/icecream_vat.dm
index a58435959c..8db6956da9 100644
--- a/code/modules/food_and_drinks/kitchen_machinery/icecream_vat.dm
+++ b/code/modules/food_and_drinks/kitchen_machinery/icecream_vat.dm
@@ -131,7 +131,6 @@
I.add_ice_cream(flavour_name, beaker.reagents)
else
I.add_ice_cream(flavour_name)
- I.add_ice_cream(flavour_name)
if(I.reagents.total_volume < 10)
I.reagents.add_reagent("sugar", 10 - I.reagents.total_volume)
updateDialog()
@@ -252,7 +251,7 @@
desc = "Delicious [cone_name] cone, but no ice cream."
-/obj/item/reagent_containers/food/snacks/icecream/proc/add_ice_cream(flavour_name, datum/reagents/R = null)
+/obj/item/reagent_containers/food/snacks/icecream/proc/add_ice_cream(flavour_name, datum/reagents/R)
name = "[flavour_name] icecream"
switch (flavour_name) // adding the actual reagents advertised in the ingredient list
if ("vanilla")
@@ -311,9 +310,11 @@
qdel(src)
/obj/machinery/icecream_vat/AltClick(mob/living/user)
+ . = ..()
if(!istype(user) || !user.canUseTopic(src, BE_CLOSE, FALSE, NO_TK))
return
replace_beaker(user)
+ return TRUE
/obj/machinery/icecream_vat/proc/replace_beaker(mob/living/user, obj/item/reagent_containers/new_beaker)
if(beaker)
diff --git a/code/modules/food_and_drinks/kitchen_machinery/microwave.dm b/code/modules/food_and_drinks/kitchen_machinery/microwave.dm
index e67de92773..4efac6f508 100644
--- a/code/modules/food_and_drinks/kitchen_machinery/microwave.dm
+++ b/code/modules/food_and_drinks/kitchen_machinery/microwave.dm
@@ -186,8 +186,10 @@
..()
/obj/machinery/microwave/AltClick(mob/user)
+ . = ..()
if(user.canUseTopic(src, !issilicon(usr)))
cook()
+ return TRUE
/obj/machinery/microwave/ui_interact(mob/user)
. = ..()
diff --git a/code/modules/games/cas.dm b/code/modules/games/cas.dm
index fe038ce3f1..4fbb931b32 100644
--- a/code/modules/games/cas.dm
+++ b/code/modules/games/cas.dm
@@ -130,9 +130,11 @@
update_icon()
/obj/item/toy/cards/singlecard/cas/AltClick(mob/living/user)
+ . = ..()
if(!ishuman(user) || !user.canUseTopic(src, BE_CLOSE))
return
Flip()
+ return TRUE
/obj/item/toy/cards/singlecard/cas/update_icon()
if(flipped)
diff --git a/code/modules/hydroponics/grown/misc.dm b/code/modules/hydroponics/grown/misc.dm
index 16992b3e96..b37e494233 100644
--- a/code/modules/hydroponics/grown/misc.dm
+++ b/code/modules/hydroponics/grown/misc.dm
@@ -141,8 +141,9 @@
endurance = 50
maturation = 3
yield = 4
- growthstages = 3
+ growthstages = 2
reagents_add = list("sugar" = 0.25)
+ mutatelist = list(/obj/item/seeds/bamboo)
/obj/item/reagent_containers/food/snacks/grown/sugarcane
seed = /obj/item/seeds/sugarcane
diff --git a/code/modules/hydroponics/grown/replicapod.dm b/code/modules/hydroponics/grown/replicapod.dm
index ce0ca0220b..bac480bee6 100644
--- a/code/modules/hydroponics/grown/replicapod.dm
+++ b/code/modules/hydroponics/grown/replicapod.dm
@@ -41,7 +41,7 @@
blood_type = B.data["blood_type"]
features = B.data["features"]
factions = B.data["factions"]
- factions = B.data["quirks"]
+ quirks = B.data["quirks"]
contains_sample = TRUE
visible_message("The [src] is injected with a fresh blood sample.")
else
diff --git a/code/modules/hydroponics/grown/towercap.dm b/code/modules/hydroponics/grown/towercap.dm
index 9d7081ad65..982122e314 100644
--- a/code/modules/hydroponics/grown/towercap.dm
+++ b/code/modules/hydroponics/grown/towercap.dm
@@ -98,6 +98,49 @@
/obj/item/grown/log/steel/CheckAccepted(obj/item/I)
return FALSE
+/obj/item/seeds/bamboo
+ name = "pack of bamboo seeds"
+ desc = "A plant known for its flexible and resistant logs."
+ icon_state = "seed-bamboo"
+ species = "bamboo"
+ plantname = "Bamboo"
+ product = /obj/item/grown/log/bamboo
+ lifespan = 80
+ endurance = 70
+ maturation = 15
+ production = 2
+ yield = 5
+ potency = 50
+ growthstages = 2
+ growing_icon = 'icons/obj/hydroponics/growing.dmi'
+ icon_dead = "bamboo-dead"
+ genes = list(/datum/plant_gene/trait/repeated_harvest)
+
+/obj/item/grown/log/bamboo
+ seed = /obj/item/seeds/bamboo
+ name = "bamboo log"
+ desc = "A long and resistant bamboo log."
+ icon_state = "bamboo"
+ plank_type = /obj/item/stack/sheet/mineral/bamboo
+ plank_name = "bamboo sticks"
+
+/obj/item/grown/log/bamboo/CheckAccepted(obj/item/I)
+ return FALSE
+
+/obj/structure/punji_sticks
+ name = "punji sticks"
+ desc = "Don't step on this."
+ icon = 'icons/obj/hydroponics/equipment.dmi'
+ icon_state = "punji"
+ resistance_flags = FLAMMABLE
+ max_integrity = 30
+ density = FALSE
+ anchored = TRUE
+
+/obj/structure/punji_sticks/Initialize(mapload)
+ . = ..()
+ AddComponent(/datum/component/caltrop, 20, 30, 100, CALTROP_BYPASS_SHOES)
+
/////////BONFIRES//////////
/obj/structure/bonfire
diff --git a/code/modules/integrated_electronics/core/assemblies.dm b/code/modules/integrated_electronics/core/assemblies.dm
index 93ddedebc0..d8bf82a4b4 100644
--- a/code/modules/integrated_electronics/core/assemblies.dm
+++ b/code/modules/integrated_electronics/core/assemblies.dm
@@ -615,6 +615,11 @@
return
..()
+/obj/item/electronic_assembly/can_trigger_gun(mob/living/user) //sanity checks against pocket death weapon circuits
+ if(!can_fire_equipped || !user.is_holding(src))
+ return FALSE
+ return ..()
+
/obj/item/electronic_assembly/default //The /default electronic_assemblys are to allow the introduction of the new naming scheme without breaking old saves.
name = "type-a electronic assembly"
diff --git a/code/modules/integrated_electronics/core/integrated_circuit.dm b/code/modules/integrated_electronics/core/integrated_circuit.dm
index cec3e2348e..40bcbe016b 100644
--- a/code/modules/integrated_electronics/core/integrated_circuit.dm
+++ b/code/modules/integrated_electronics/core/integrated_circuit.dm
@@ -402,3 +402,8 @@ a creative player the means to solve many problems. Circuits are held inside an
return TRUE
return FALSE
+
+/obj/item/integrated_circuit/can_trigger_gun(mob/living/user)
+ if(!user.is_holding(src))
+ return FALSE
+ return ..()
diff --git a/code/modules/integrated_electronics/subtypes/input.dm b/code/modules/integrated_electronics/subtypes/input.dm
index b7feedd312..46f8017b10 100644
--- a/code/modules/integrated_electronics/subtypes/input.dm
+++ b/code/modules/integrated_electronics/subtypes/input.dm
@@ -859,7 +859,7 @@
spawn_flags = IC_SPAWN_DEFAULT|IC_SPAWN_RESEARCH
power_draw_per_use = 5
-/obj/item/integrated_circuit/input/microphone/Hear(message, atom/movable/speaker, message_langs, raw_message, radio_freq, spans, message_mode)
+/obj/item/integrated_circuit/input/microphone/Hear(message, atom/movable/speaker, message_langs, raw_message, radio_freq, spans, message_mode, atom/movable/source)
. = ..()
var/translated = FALSE
if(speaker && message)
diff --git a/code/modules/integrated_electronics/subtypes/manipulation.dm b/code/modules/integrated_electronics/subtypes/manipulation.dm
index fb00897eec..0bdf0547f9 100644
--- a/code/modules/integrated_electronics/subtypes/manipulation.dm
+++ b/code/modules/integrated_electronics/subtypes/manipulation.dm
@@ -11,7 +11,7 @@
w_class = WEIGHT_CLASS_SMALL
complexity = 10
cooldown_per_use = 1
- ext_cooldown = 2
+ ext_cooldown = 4
inputs = list("direction" = IC_PINTYPE_DIR)
outputs = list("obstacle" = IC_PINTYPE_REF)
activators = list("step towards dir" = IC_PINTYPE_PULSE_IN,"on step"=IC_PINTYPE_PULSE_OUT,"blocked"=IC_PINTYPE_PULSE_OUT)
diff --git a/code/modules/integrated_electronics/subtypes/weaponized.dm b/code/modules/integrated_electronics/subtypes/weaponized.dm
index 7bccbfafcd..350f05914d 100644
--- a/code/modules/integrated_electronics/subtypes/weaponized.dm
+++ b/code/modules/integrated_electronics/subtypes/weaponized.dm
@@ -81,9 +81,13 @@
to_chat(user, "There's no weapon to remove from the mechanism.")
/obj/item/integrated_circuit/weaponized/weapon_firing/do_work()
- if(!installed_gun || !installed_gun.handle_pins())
+ if(!assembly || !installed_gun)
return
- if(!isturf(assembly.loc) && !(assembly.can_fire_equipped && ishuman(assembly.loc)))
+ if(isliving(assembly.loc))
+ var/mob/living/L = assembly.loc
+ if(!assembly.can_fire_equipped || !L.is_holding(assembly) || !installed_gun.can_trigger_gun(L)) //includes pins, hulk and other chunky fingers checks.
+ return
+ else if(!isturf(assembly.loc) || !installed_gun.handle_pins())
return
set_pin_data(IC_OUTPUT, 1, WEAKREF(installed_gun))
push_data()
@@ -92,18 +96,17 @@
var/datum/integrated_io/mode1 = inputs[3]
mode = mode1.data
- if(assembly)
- if(isnum(xo.data))
- xo.data = round(xo.data, 1)
- if(isnum(yo.data))
- yo.data = round(yo.data, 1)
+ if(isnum(xo.data))
+ xo.data = round(xo.data, 1)
+ if(isnum(yo.data))
+ yo.data = round(yo.data, 1)
- var/turf/T = get_turf(assembly)
- var/target_x = CLAMP(T.x + xo.data, 0, world.maxx)
- var/target_y = CLAMP(T.y + yo.data, 0, world.maxy)
+ var/turf/T = get_turf(assembly)
+ var/target_x = CLAMP(T.x + xo.data, 0, world.maxx)
+ var/target_y = CLAMP(T.y + yo.data, 0, world.maxy)
- assembly.visible_message("[assembly] fires [installed_gun]!")
- shootAt(locate(target_x, target_y, T.z))
+ assembly.visible_message("[assembly] fires [installed_gun]!")
+ shootAt(locate(target_x, target_y, T.z))
/obj/item/integrated_circuit/weaponized/weapon_firing/proc/shootAt(turf/target)
var/turf/T = get_turf(src)
@@ -246,26 +249,30 @@
if(!A || A.anchored || A.throwing || A == assembly || istype(A, /obj/item/twohanded) || istype(A, /obj/item/transfer_valve))
return
- if(!AT || !AT.air_contents)
+ var/obj/item/I = get_object()
+ var/turf/T = get_turf(I)
+ if(!T)
+ return
+ if(isliving(I.loc))
+ var/mob/living/L = I.loc
+ if(!I.can_trigger_gun(L)) //includes hulk and other chunky fingers checks.
+ return
+ if(HAS_TRAIT(L, TRAIT_PACIFISM) && A.throwforce)
+ to_chat(L, " [I] is lethally chambered! You don't want to risk harming anyone...")
+ return
+ else if(T != I.loc)
return
- if (istype(assembly.loc, /obj/item/implant/storage)) //Prevents the more abusive form of chestgun.
+ if(!AT || !AT.air_contents)
return
if(max_w_class && (A.w_class > max_w_class))
return
- if(!assembly.can_fire_equipped && ishuman(assembly.loc))
- return
-
// Is the target inside the assembly or close to it?
if(!check_target(A, exclude_components = TRUE))
return
- var/turf/T = get_turf(get_object())
- if(!T)
- return
-
// If the item is in mob's inventory, try to remove it from there.
if(ismob(A.loc))
var/mob/living/M = A.loc
diff --git a/code/modules/jobs/job_types/ai.dm b/code/modules/jobs/job_types/ai.dm
index ad665ab701..4bcfab5836 100644
--- a/code/modules/jobs/job_types/ai.dm
+++ b/code/modules/jobs/job_types/ai.dm
@@ -36,7 +36,7 @@
qdel(lateJoinCore)
var/mob/living/silicon/ai/AI = H
AI.apply_pref_name("ai", M.client) //If this runtimes oh well jobcode is fucked.
- AI.set_core_display_icon(M.client)
+ AI.set_core_display_icon(null, M.client)
//we may have been created after our borg
if(SSticker.current_state == GAME_STATE_SETTING_UP)
diff --git a/code/modules/language/vampiric.dm b/code/modules/language/vampiric.dm
new file mode 100644
index 0000000000..6da54ce844
--- /dev/null
+++ b/code/modules/language/vampiric.dm
@@ -0,0 +1,25 @@
+// VAMPIRE LANGUAGE //
+
+/datum/language/vampiric
+ name = "Blah-Sucker"
+ desc = "The native language of the Bloodsucker elders, learned intuitively by Fledglings and as they pass from death into immortality. Thralls are also given the ability to speak this as apart of their conversion ritual."
+ speech_verb = "growls"
+ ask_verb = "growls"
+ exclaim_verb = "snarls"
+ whisper_verb = "hisses"
+ key = "b"
+ space_chance = 40
+ default_priority = 90
+ icon_state = "bloodsucker"
+
+ flags = TONGUELESS_SPEECH | LANGUAGE_HIDE_ICON_IF_NOT_UNDERSTOOD // Hide the icon next to your text if someone doesn't know this language.
+ syllables = list(
+ "luk","cha","no","kra","pru","chi","busi","tam","pol","spu","och", // Start: Vampiric
+ "umf","ora","stu","si","ri","li","ka","red","ani","lup","ala","pro",
+ "to","siz","nu","pra","ga","ump","ort","a","ya","yach","tu","lit",
+ "wa","mabo","mati","anta","tat","tana","prol",
+ "tsa","si","tra","te","ele","fa","inz", // Start: Romanian
+ "nza","est","sti","ra","pral","tsu","ago","esch","chi","kys","praz", // Start: Custom
+ "froz","etz","tzil",
+ "t'","k'","t'","k'","th'","tz'"
+ )
diff --git a/code/modules/mining/abandoned_crates.dm b/code/modules/mining/abandoned_crates.dm
index f98f0755c8..4cfd2e447f 100644
--- a/code/modules/mining/abandoned_crates.dm
+++ b/code/modules/mining/abandoned_crates.dm
@@ -181,10 +181,11 @@
else
return ..()
+//this helps you not blow up so easily by overriding unlocking which results in an immediate boom.
/obj/structure/closet/crate/secure/loot/AltClick(mob/living/user)
- if(!user.canUseTopic(src, BE_CLOSE))
- return
- return attack_hand(user) //this helps you not blow up so easily by overriding unlocking which results in an immediate boom.
+ if(user.canUseTopic(src, BE_CLOSE))
+ attack_hand(user)
+ return TRUE
/obj/structure/closet/crate/secure/loot/attackby(obj/item/W, mob/user)
if(locked)
diff --git a/code/modules/mining/equipment/marker_beacons.dm b/code/modules/mining/equipment/marker_beacons.dm
index 00ce37b79a..9d595664ff 100644
--- a/code/modules/mining/equipment/marker_beacons.dm
+++ b/code/modules/mining/equipment/marker_beacons.dm
@@ -59,6 +59,7 @@ GLOBAL_LIST_INIT(marker_beacon_colors, list(
/obj/item/stack/marker_beacon/AltClick(mob/living/user)
if(!istype(user) || !user.canUseTopic(src, BE_CLOSE))
return
+ . = TRUE
var/input_color = input(user, "Choose a color.", "Beacon Color") as null|anything in GLOB.marker_beacon_colors
if(!istype(user) || !user.canUseTopic(src, BE_CLOSE))
return
@@ -128,9 +129,10 @@ GLOBAL_LIST_INIT(marker_beacon_colors, list(
return ..()
/obj/structure/marker_beacon/AltClick(mob/living/user)
- ..()
+ . = ..()
if(!istype(user) || !user.canUseTopic(src, BE_CLOSE))
return
+ . = TRUE
var/input_color = input(user, "Choose a color.", "Beacon Color") as null|anything in GLOB.marker_beacon_colors
if(!istype(user) || !user.canUseTopic(src, BE_CLOSE))
return
diff --git a/code/modules/mining/lavaland/necropolis_chests.dm b/code/modules/mining/lavaland/necropolis_chests.dm
index 054103e8dc..ab3b20ac9f 100644
--- a/code/modules/mining/lavaland/necropolis_chests.dm
+++ b/code/modules/mining/lavaland/necropolis_chests.dm
@@ -398,6 +398,7 @@
fire_sound = 'sound/weapons/batonextend.ogg'
max_charges = 1
item_flags = NEEDS_PERMIT | NOBLUDGEON
+ w_class = WEIGHT_CLASS_BULKY
force = 18
/obj/item/ammo_casing/magic/hook
diff --git a/code/modules/mob/dead/new_player/preferences_setup.dm b/code/modules/mob/dead/new_player/preferences_setup.dm
index d54b8256a9..7aae26526e 100644
--- a/code/modules/mob/dead/new_player/preferences_setup.dm
+++ b/code/modules/mob/dead/new_player/preferences_setup.dm
@@ -25,7 +25,7 @@
features = random_features()
age = rand(AGE_MIN,AGE_MAX)
-/datum/preferences/proc/update_preview_icon()
+/datum/preferences/proc/update_preview_icon(equip_job = TRUE)
// Determine what job is marked as 'High' priority, and dress them up as such.
var/datum/job/previewJob
var/highest_pref = 0
@@ -45,12 +45,11 @@
// Set up the dummy for its photoshoot
var/mob/living/carbon/human/dummy/mannequin = generate_or_wait_for_human_dummy(DUMMY_HUMAN_SLOT_PREFERENCES)
- mannequin.cut_overlays()
// Apply the Dummy's preview background first so we properly layer everything else on top of it.
mannequin.add_overlay(mutable_appearance('modular_citadel/icons/ui/backgrounds.dmi', bgstate, layer = SPACE_LAYER))
copy_to(mannequin)
- if(previewJob)
+ if(previewJob && equip_job)
mannequin.job = previewJob.title
previewJob.equip(mannequin, TRUE, preference_source = parent)
diff --git a/code/modules/mob/dead/new_player/sprite_accessories/socks.dm b/code/modules/mob/dead/new_player/sprite_accessories/socks.dm
index 524c1f0f13..0a35f0cd26 100644
--- a/code/modules/mob/dead/new_player/sprite_accessories/socks.dm
+++ b/code/modules/mob/dead/new_player/sprite_accessories/socks.dm
@@ -43,6 +43,18 @@
name = "Knee-High - UK"
icon_state = "uk_knee"
+/datum/sprite_accessory/underwear/socks/christmas_knee
+ name = "Knee-High - Christmas"
+ icon_state = "christmas_knee"
+
+/datum/sprite_accessory/underwear/socks/candycaner_knee
+ name = "Knee-High - Red Candy Cane"
+ icon_state = "candycaner_knee"
+
+/datum/sprite_accessory/underwear/socks/candycaneg_knee
+ name = "Knee-High - Green Candy Cane"
+ icon_state = "candycaneg_knee"
+
/datum/sprite_accessory/underwear/socks/socks_norm
name = "Normal"
icon_state = "socks_norm"
@@ -52,6 +64,18 @@
name = "Normal - Bee"
icon_state = "bee_norm"
+/datum/sprite_accessory/underwear/socks/christmas_norm
+ name = "Normal - Christmas"
+ icon_state = "christmas_norm"
+
+/datum/sprite_accessory/underwear/socks/candycaner_norm
+ name = "Normal - Red Candy Cane"
+ icon_state = "candycaner_norm"
+
+/datum/sprite_accessory/underwear/socks/candycaneg_norm
+ name = "Normal - Green Candy Cane"
+ icon_state = "candycaneg_norm"
+
/datum/sprite_accessory/underwear/socks/pantyhose
name = "Pantyhose"
icon_state = "pantyhose"
@@ -131,3 +155,15 @@
/datum/sprite_accessory/underwear/socks/uk_thigh
name = "Thigh-high - UK"
icon_state = "uk_thigh"
+
+/datum/sprite_accessory/underwear/socks/christmas_thigh
+ name = "Thigh-high - Christmas"
+ icon_state = "christmas_thigh"
+
+/datum/sprite_accessory/underwear/socks/candycaner_thigh
+ name = "Thigh-high - Red Candy Cane"
+ icon_state = "candycaner_thigh"
+
+/datum/sprite_accessory/underwear/socks/candycaneg_thigh
+ name = "Thigh-high - Green Candy Cane"
+ icon_state = "candycaneg_thigh"
\ No newline at end of file
diff --git a/code/modules/mob/dead/observer/observer.dm b/code/modules/mob/dead/observer/observer.dm
index fbbd43bbe1..5bdfd174b5 100644
--- a/code/modules/mob/dead/observer/observer.dm
+++ b/code/modules/mob/dead/observer/observer.dm
@@ -265,7 +265,7 @@ Works together with spawning an observer, noted above.
/mob/proc/ghostize(can_reenter_corpse = TRUE, special = FALSE, penalize = FALSE)
penalize = suiciding || penalize // suicide squad.
- if(!key || cmptext(copytext(key,1,2),"@") || (!special && SEND_SIGNAL(src, COMSIG_MOB_GHOSTIZE, can_reenter_corpse) & COMPONENT_BLOCK_GHOSTING))
+ if(!key || cmptext(copytext(key,1,2),"@") || (SEND_SIGNAL(src, COMSIG_MOB_GHOSTIZE, can_reenter_corpse, special, penalize) & COMPONENT_BLOCK_GHOSTING))
return //mob has no key, is an aghost or some component hijacked.
stop_sound_channel(CHANNEL_HEARTBEAT) //Stop heartbeat sounds because You Are A Ghost Now
var/mob/dead/observer/ghost = new(src) // Transfer safety to observer spawning proc.
diff --git a/code/modules/mob/dead/observer/say.dm b/code/modules/mob/dead/observer/say.dm
index 7eeab05466..69cbd0830a 100644
--- a/code/modules/mob/dead/observer/say.dm
+++ b/code/modules/mob/dead/observer/say.dm
@@ -22,7 +22,7 @@
. = say_dead(message)
-/mob/dead/observer/Hear(message, atom/movable/speaker, message_language, raw_message, radio_freq, list/spans, message_mode)
+/mob/dead/observer/Hear(message, atom/movable/speaker, message_language, raw_message, radio_freq, list/spans, message_mode, atom/movable/source)
. = ..()
var/atom/movable/to_follow = speaker
if(radio_freq)
@@ -35,6 +35,6 @@
to_follow = V.source
var/link = FOLLOW_LINK(src, to_follow)
// Recompose the message, because it's scrambled by default
- message = compose_message(speaker, message_language, raw_message, radio_freq, spans, message_mode)
+ message = compose_message(speaker, message_language, raw_message, radio_freq, spans, message_mode, FALSE, source)
to_chat(src, "[link] [message]")
diff --git a/code/modules/mob/living/blood.dm b/code/modules/mob/living/blood.dm
index 757aadaea1..8195b3d84b 100644
--- a/code/modules/mob/living/blood.dm
+++ b/code/modules/mob/living/blood.dm
@@ -36,6 +36,9 @@
if(bleed_rate <= 0)
bleed_rate = 0
+ if(HAS_TRAIT(src, TRAIT_NOMARROW)) //Bloodsuckers don't need to be here.
+ return
+
if(bodytemperature >= TCRYO && !(HAS_TRAIT(src, TRAIT_NOCLONE))) //cryosleep or husked people do not pump the blood.
//Blood regeneration if there is some space
diff --git a/code/modules/mob/living/brain/MMI.dm b/code/modules/mob/living/brain/MMI.dm
index aa3c209016..2a57506df6 100644
--- a/code/modules/mob/living/brain/MMI.dm
+++ b/code/modules/mob/living/brain/MMI.dm
@@ -125,6 +125,7 @@
else if(!brain)
brain = new(src)
brain.name = "[L.real_name]'s brain"
+ brain.organ_flags |= ORGAN_FROZEN
name = "Man-Machine Interface: [brainmob.real_name]"
update_icon()
diff --git a/code/modules/mob/living/carbon/carbon_defense.dm b/code/modules/mob/living/carbon/carbon_defense.dm
index b0e9b808df..c5aeb2c1c7 100644
--- a/code/modules/mob/living/carbon/carbon_defense.dm
+++ b/code/modules/mob/living/carbon/carbon_defense.dm
@@ -355,30 +355,30 @@
if (damage == 1)
to_chat(src, "Your eyes sting a little.")
if(prob(40))
- adjust_eye_damage(1)
+ eyes.applyOrganDamage(1)
else if (damage == 2)
to_chat(src, "Your eyes burn.")
- adjust_eye_damage(rand(2, 4))
+ eyes.applyOrganDamage(rand(2, 4))
else if( damage >= 3)
to_chat(src, "Your eyes itch and burn severely!")
- adjust_eye_damage(rand(12, 16))
+ eyes.applyOrganDamage(rand(12, 16))
- if(eyes.eye_damage > 10)
+ if(eyes.damage > 10)
blind_eyes(damage)
blur_eyes(damage * rand(3, 6))
- if(eyes.eye_damage > 20)
- if(prob(eyes.eye_damage - 20))
+ if(eyes.damage > 20)
+ if(prob(eyes.damage - 20))
if(!HAS_TRAIT(src, TRAIT_NEARSIGHT))
to_chat(src, "Your eyes start to burn badly!")
become_nearsighted(EYE_DAMAGE)
- else if(prob(eyes.eye_damage - 25))
+ else if(prob(eyes.damage - 25))
if(!HAS_TRAIT(src, TRAIT_BLIND))
to_chat(src, "You can't see anything!")
- become_blind(EYE_DAMAGE)
+ eyes.applyOrganDamage(eyes.maxHealth)
else
to_chat(src, "Your eyes are really starting to hurt. This can't be good for you!")
diff --git a/code/modules/mob/living/carbon/damage_procs.dm b/code/modules/mob/living/carbon/damage_procs.dm
index 21541e8c28..d893108bcd 100644
--- a/code/modules/mob/living/carbon/damage_procs.dm
+++ b/code/modules/mob/living/carbon/damage_procs.dm
@@ -65,6 +65,8 @@
/mob/living/carbon/adjustBruteLoss(amount, updating_health = TRUE, forced = FALSE)
+ if (!forced && amount < 0 && HAS_TRAIT(src,TRAIT_NONATURALHEAL))
+ return FALSE
if(!forced && (status_flags & GODMODE))
return FALSE
if(amount > 0)
@@ -74,6 +76,8 @@
return amount
/mob/living/carbon/adjustFireLoss(amount, updating_health = TRUE, forced = FALSE)
+ if (!forced && amount < 0 && HAS_TRAIT(src,TRAIT_NONATURALHEAL)) //Vamps don't heal naturally.
+ return FALSE
if(!forced && (status_flags & GODMODE))
return FALSE
if(amount > 0)
diff --git a/code/modules/mob/living/carbon/human/dummy.dm b/code/modules/mob/living/carbon/human/dummy.dm
index 6da188dd2d..74593a20a0 100644
--- a/code/modules/mob/living/carbon/human/dummy.dm
+++ b/code/modules/mob/living/carbon/human/dummy.dm
@@ -17,6 +17,7 @@ INITIALIZE_IMMEDIATE(/mob/living/carbon/human/dummy)
/mob/living/carbon/human/dummy/proc/wipe_state()
delete_equipment()
+ icon_render_key = null
cut_overlays(TRUE)
//Inefficient pooling/caching way.
diff --git a/code/modules/mob/living/carbon/human/examine.dm b/code/modules/mob/living/carbon/human/examine.dm
index 04ec0454f8..310ab6beeb 100644
--- a/code/modules/mob/living/carbon/human/examine.dm
+++ b/code/modules/mob/living/carbon/human/examine.dm
@@ -15,6 +15,13 @@
. = list("*---------*\nThis is [!obscure_name ? name : "Unknown"]!")
+ var/vampDesc = ReturnVampExamine(user) // Vamps recognize the names of other vamps.
+ var/vassDesc = ReturnVassalExamine(user) // Vassals recognize each other's marks.
+ if (vampDesc != "") // If we don't do it this way, we add a blank space to the string...something to do with this --> . += ""
+ . += vampDesc
+ if (vassDesc != "")
+ . += vassDesc
+
var/list/obscured = check_obscured_slots()
var/skipface = (wear_mask && (wear_mask.flags_inv & HIDEFACE)) || (head && (head.flags_inv & HIDEFACE))
@@ -237,13 +244,13 @@
if(DISGUST_LEVEL_DISGUSTED to INFINITY)
msg += "[t_He] look[p_s()] extremely disgusted.\n"
- if(blood_volume < (BLOOD_VOLUME_SAFE*blood_ratio))
+ if(ShowAsPaleExamine())
msg += "[t_He] [t_has] pale skin.\n"
if(bleedsuppress)
msg += "[t_He] [t_is] bandaged with something.\n"
else if(bleed_rate)
- if(reagents.has_reagent("heparin"))
+ if(bleed_rate >= 8) //8 is the rate at which heparin causes you to bleed
msg += "[t_He] [t_is] bleeding uncontrollably!\n"
else
msg += "[t_He] [t_is] bleeding!\n"
diff --git a/code/modules/mob/living/carbon/human/human.dm b/code/modules/mob/living/carbon/human/human.dm
index 3ae2e3bdbd..45211f7f44 100644
--- a/code/modules/mob/living/carbon/human/human.dm
+++ b/code/modules/mob/living/carbon/human/human.dm
@@ -858,6 +858,7 @@
.["Make slime"] = "?_src_=vars;[HrefToken()];makeslime=[REF(src)]"
.["Toggle Purrbation"] = "?_src_=vars;[HrefToken()];purrbation=[REF(src)]"
.["Copy outfit"] = "?_src_=vars;[HrefToken()];copyoutfit=[REF(src)]"
+ .["Add/Remove Quirks"] = "?_src_=vars;[HrefToken()];modquirks=[REF(src)]"
/mob/living/carbon/human/MouseDrop_T(mob/living/target, mob/living/user)
if(pulling == target && grab_state >= GRAB_AGGRESSIVE && stat == CONSCIOUS)
@@ -867,8 +868,9 @@
return
//If you dragged them to you and you're aggressively grabbing try to fireman carry them
else if(user != target)
- fireman_carry(target)
- return
+ if(user.a_intent == INTENT_GRAB)
+ fireman_carry(target)
+ return
. = ..()
//src is the user that will be carrying, target is the mob to be carried
@@ -890,7 +892,10 @@
return
visible_message("[src] fails to fireman carry [target]!")
else
- to_chat(src, "You can't fireman carry [target] while they're standing!")
+ if (ishuman(target))
+ to_chat(src, "You can't fireman carry [target] while they're standing!")
+ else
+ to_chat(src, "You can't seem to fireman carry that kind of species.")
/mob/living/carbon/human/proc/piggyback(mob/living/carbon/target)
if(can_piggyback(target))
diff --git a/code/modules/mob/living/carbon/human/human_defense.dm b/code/modules/mob/living/carbon/human/human_defense.dm
index f9fa5d84da..042b4af4fe 100644
--- a/code/modules/mob/living/carbon/human/human_defense.dm
+++ b/code/modules/mob/living/carbon/human/human_defense.dm
@@ -23,7 +23,7 @@
if(!d_type)
return 0
var/protection = 0
- var/list/body_parts = list(head, wear_mask, wear_suit, w_uniform, back, gloves, shoes, belt, s_store, glasses, ears, wear_id) //Everything but pockets. Pockets are l_store and r_store. (if pockets were allowed, putting something armored, gloves or hats for example, would double up on the armor)
+ var/list/body_parts = list(head, wear_mask, wear_suit, w_uniform, back, gloves, shoes, belt, s_store, glasses, ears, wear_id, wear_neck) //Everything but pockets. Pockets are l_store and r_store. (if pockets were allowed, putting something armored, gloves or hats for example, would double up on the armor)
for(var/bp in body_parts)
if(!bp)
continue
@@ -116,6 +116,10 @@
var/final_block_chance = w_uniform.block_chance - (CLAMP((armour_penetration-w_uniform.armour_penetration)/2,0,100)) + block_chance_modifier
if(w_uniform.hit_reaction(src, AM, attack_text, final_block_chance, damage, attack_type))
return 1
+ if(wear_neck)
+ var/final_block_chance = wear_neck.block_chance - (CLAMP((armour_penetration-wear_neck.armour_penetration)/2,0,100)) + block_chance_modifier
+ if(wear_neck.hit_reaction(src, AM, attack_text, final_block_chance, damage, attack_type))
+ return 1
return 0
/mob/living/carbon/human/proc/check_block()
@@ -388,43 +392,34 @@
return
var/b_loss = 0
var/f_loss = 0
- var/bomb_armor = getarmor(null, "bomb")
+ var/bomb_armor = max(0,(100-getarmor(null, "bomb"))/100)
switch (severity)
if (1)
- if(prob(bomb_armor))
- b_loss = 500
+ if(bomb_armor)
+ b_loss = 500*bomb_armor
var/atom/throw_target = get_edge_target_turf(src, get_dir(src, get_step_away(src, src)))
throw_at(throw_target, 200, 4)
- damage_clothes(400 - bomb_armor, BRUTE, "bomb")
+ damage_clothes(400*bomb_armor, BRUTE, "bomb")
else
- for(var/I in contents)
- var/atom/A = I
- A.ex_act(severity)
+ damage_clothes(400,BRUTE,"bomb")
gib()
return
if (2)
- b_loss = 60
- f_loss = 60
- if(bomb_armor)
- b_loss = 30*(2 - round(bomb_armor*0.01, 0.05))
- f_loss = b_loss
- damage_clothes(200 - bomb_armor, BRUTE, "bomb")
+ b_loss = 60*bomb_armor
+ f_loss = 60*bomb_armor
+ damage_clothes(200*bomb_armor, BRUTE, "bomb")
if (!istype(ears, /obj/item/clothing/ears/earmuffs))
adjustEarDamage(30, 120)
- if (prob(max(70 - (bomb_armor * 0.5), 0)))
- Unconscious(200)
+ Unconscious(200*bomb_armor)
if(3)
- b_loss = 30
- if(bomb_armor)
- b_loss = 15*(2 - round(bomb_armor*0.01, 0.05))
+ b_loss = 30*bomb_armor
damage_clothes(max(50 - bomb_armor, 0), BRUTE, "bomb")
if (!istype(ears, /obj/item/clothing/ears/earmuffs))
adjustEarDamage(15,60)
- if (prob(max(50 - (bomb_armor * 0.5), 0)))
- Unconscious(160)
+ Unconscious(100*bomb_armor)
take_overall_damage(b_loss,f_loss)
diff --git a/code/modules/mob/living/carbon/human/human_helpers.dm b/code/modules/mob/living/carbon/human/human_helpers.dm
index 8071d632ea..63d02af31a 100644
--- a/code/modules/mob/living/carbon/human/human_helpers.dm
+++ b/code/modules/mob/living/carbon/human/human_helpers.dm
@@ -119,16 +119,12 @@
. = ..()
if(G.trigger_guard == TRIGGER_GUARD_NORMAL)
- if(src.dna.check_mutation(HULK))
+ if(HAS_TRAIT(src, TRAIT_CHUNKYFINGERS))
to_chat(src, "Your meaty finger is much too large for the trigger guard!")
return FALSE
if(HAS_TRAIT(src, TRAIT_NOGUNS))
to_chat(src, "Your fingers don't fit in the trigger guard!")
return FALSE
- if(mind)
- if(mind.martial_art && mind.martial_art.no_guns) //great dishonor to famiry
- to_chat(src, "Use of ranged weaponry would bring dishonor to the clan.")
- return FALSE
return .
/*
diff --git a/code/modules/mob/living/carbon/human/inventory.dm b/code/modules/mob/living/carbon/human/inventory.dm
index 26703977d2..69ec619e5d 100644
--- a/code/modules/mob/living/carbon/human/inventory.dm
+++ b/code/modules/mob/living/carbon/human/inventory.dm
@@ -191,7 +191,6 @@
if(G.vision_correction)
if(HAS_TRAIT(src, TRAIT_NEARSIGHT))
overlay_fullscreen("nearsighted", /obj/screen/fullscreen/impaired, 1)
- adjust_eye_damage(0)
if(G.vision_flags || G.darkness_view || G.invis_override || G.invis_view || !isnull(G.lighting_alpha))
update_sight()
if(!QDELETED(src))
diff --git a/code/modules/mob/living/carbon/human/species.dm b/code/modules/mob/living/carbon/human/species.dm
index 96bd62b1f6..67d7cb585e 100644
--- a/code/modules/mob/living/carbon/human/species.dm
+++ b/code/modules/mob/living/carbon/human/species.dm
@@ -1279,6 +1279,8 @@ GLOBAL_LIST_EMPTY(roundstart_race_names)
////////
/datum/species/proc/handle_digestion(mob/living/carbon/human/H)
+ if(HAS_TRAIT(src, TRAIT_NOHUNGER))
+ return //hunger is for BABIES
//The fucking TRAIT_FAT mutation is the dumbest shit ever. It makes the code so difficult to work with
if(HAS_TRAIT(H, TRAIT_FAT))//I share your pain, past coder.
diff --git a/code/modules/mob/living/carbon/human/species_types/abductors.dm b/code/modules/mob/living/carbon/human/species_types/abductors.dm
index ffd129ebf7..6e54e320ff 100644
--- a/code/modules/mob/living/carbon/human/species_types/abductors.dm
+++ b/code/modules/mob/living/carbon/human/species_types/abductors.dm
@@ -4,7 +4,7 @@
say_mod = "gibbers"
sexes = FALSE
species_traits = list(NOBLOOD,NOEYES,NOGENITALS,NOAROUSAL)
- inherent_traits = list(TRAIT_VIRUSIMMUNE,TRAIT_NOGUNS,TRAIT_NOHUNGER,TRAIT_NOBREATH)
+ inherent_traits = list(TRAIT_VIRUSIMMUNE,TRAIT_CHUNKYFINGERS,TRAIT_NOHUNGER,TRAIT_NOBREATH)
mutanttongue = /obj/item/organ/tongue/abductor
/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/golems.dm b/code/modules/mob/living/carbon/human/species_types/golems.dm
index 48ce55a052..3d6c5092e1 100644
--- a/code/modules/mob/living/carbon/human/species_types/golems.dm
+++ b/code/modules/mob/living/carbon/human/species_types/golems.dm
@@ -3,7 +3,7 @@
name = "Golem"
id = "iron 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_NOGUNS,TRAIT_RADIMMUNE,TRAIT_PIERCEIMMUNE,TRAIT_NODISMEMBER)
+ 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 = list(MOB_INORGANIC, MOB_HUMANOID)
mutant_organs = list(/obj/item/organ/adamantine_resonator)
speedmod = 2
@@ -88,7 +88,7 @@
fixed_mut_color = "a3d"
meat = /obj/item/stack/ore/plasma
//Can burn and takes damage from heat
- inherent_traits = list(TRAIT_NOBREATH, TRAIT_RESISTCOLD,TRAIT_RESISTHIGHPRESSURE,TRAIT_RESISTLOWPRESSURE,TRAIT_NOGUNS,TRAIT_RADIMMUNE,TRAIT_PIERCEIMMUNE,TRAIT_NODISMEMBER) //no RESISTHEAT, NOFIRE
+ inherent_traits = list(TRAIT_NOBREATH, TRAIT_RESISTCOLD,TRAIT_RESISTHIGHPRESSURE,TRAIT_RESISTLOWPRESSURE,TRAIT_CHUNKYFINGERS,TRAIT_RADIMMUNE,TRAIT_PIERCEIMMUNE,TRAIT_NODISMEMBER) //no RESISTHEAT, NOFIRE
info_text = "As a Plasma Golem, you burn easily. Be careful, if you get hot enough while burning, you'll blow up!"
heatmod = 0 //fine until they blow up
prefix = "Plasma"
@@ -266,7 +266,7 @@
fixed_mut_color = "9E704B"
meat = /obj/item/stack/sheet/mineral/wood
//Can burn and take damage from heat
- inherent_traits = list(TRAIT_NOBREATH, TRAIT_RESISTCOLD,TRAIT_RESISTHIGHPRESSURE,TRAIT_RESISTLOWPRESSURE,TRAIT_NOGUNS,TRAIT_RADIMMUNE,TRAIT_PIERCEIMMUNE,TRAIT_NODISMEMBER)
+ inherent_traits = list(TRAIT_NOBREATH, TRAIT_RESISTCOLD,TRAIT_RESISTHIGHPRESSURE,TRAIT_RESISTLOWPRESSURE,TRAIT_CHUNKYFINGERS,TRAIT_RADIMMUNE,TRAIT_PIERCEIMMUNE,TRAIT_NODISMEMBER)
armor = 30
burnmod = 1.25
heatmod = 1.5
@@ -693,7 +693,7 @@
info_text = "As a Cloth Golem, you are able to reform yourself after death, provided your remains aren't burned or destroyed. You are, of course, very flammable. \
Being made of cloth, your body is magic resistant and faster than that of other golems, but weaker and less resilient."
species_traits = list(NOBLOOD,NO_UNDERWEAR,NOGENITALS,NOAROUSAL) //no mutcolors, and can burn
- inherent_traits = list(TRAIT_RESISTCOLD,TRAIT_NOBREATH,TRAIT_RESISTHIGHPRESSURE,TRAIT_RESISTLOWPRESSURE,TRAIT_RADIMMUNE,TRAIT_PIERCEIMMUNE,TRAIT_NODISMEMBER,TRAIT_NOGUNS)
+ inherent_traits = list(TRAIT_RESISTCOLD,TRAIT_NOBREATH,TRAIT_RESISTHIGHPRESSURE,TRAIT_RESISTLOWPRESSURE,TRAIT_RADIMMUNE,TRAIT_PIERCEIMMUNE,TRAIT_NODISMEMBER,TRAIT_CHUNKYFINGERS)
inherent_biotypes = list(MOB_UNDEAD, MOB_HUMANOID)
armor = 15 //feels no pain, but not too resistant
burnmod = 2 // don't get burned
@@ -893,7 +893,7 @@
special_names = list("Box")
info_text = "As a Cardboard Golem, you aren't very strong, but you are a bit quicker and can easily create more brethren by using cardboard on yourself."
species_traits = list(NOBLOOD,NO_UNDERWEAR,NOGENITALS,NOAROUSAL,MUTCOLORS)
- inherent_traits = list(TRAIT_NOBREATH, TRAIT_RESISTCOLD,TRAIT_RESISTHIGHPRESSURE,TRAIT_RESISTLOWPRESSURE,TRAIT_NOGUNS,TRAIT_RADIMMUNE,TRAIT_PIERCEIMMUNE,TRAIT_NODISMEMBER)
+ inherent_traits = list(TRAIT_NOBREATH, TRAIT_RESISTCOLD,TRAIT_RESISTHIGHPRESSURE,TRAIT_RESISTLOWPRESSURE,TRAIT_CHUNKYFINGERS,TRAIT_RADIMMUNE,TRAIT_PIERCEIMMUNE,TRAIT_NODISMEMBER)
fixed_mut_color = "ffffff"
limbs_id = "c_golem" //special sprites
attack_verb = "bash"
@@ -936,7 +936,7 @@
name = "Leather Golem"
id = "leather golem"
special_names = list("Face", "Man", "Belt") //Ah dude 4 strength 4 stam leather belt AHHH
- inherent_traits = list(TRAIT_NOBREATH, TRAIT_RESISTCOLD,TRAIT_RESISTHIGHPRESSURE,TRAIT_RESISTLOWPRESSURE,TRAIT_NOGUNS,TRAIT_RADIMMUNE,TRAIT_PIERCEIMMUNE,TRAIT_NODISMEMBER, TRAIT_STRONG_GRABBER)
+ inherent_traits = list(TRAIT_NOBREATH, TRAIT_RESISTCOLD,TRAIT_RESISTHIGHPRESSURE,TRAIT_RESISTLOWPRESSURE,TRAIT_CHUNKYFINGERS,TRAIT_RADIMMUNE,TRAIT_PIERCEIMMUNE,TRAIT_NODISMEMBER, TRAIT_STRONG_GRABBER)
prefix = "Leather"
fixed_mut_color = "624a2e"
info_text = "As a Leather Golem, you are flammable, but you can grab things with incredible ease, allowing all your grabs to start at a strong level."
@@ -952,7 +952,7 @@
special_names = list("Boll","Weave")
species_traits = list(NOBLOOD,NO_UNDERWEAR,NOEYES)
fixed_mut_color = null
- inherent_traits = list(TRAIT_NOBREATH, TRAIT_RESISTCOLD,TRAIT_RESISTHIGHPRESSURE,TRAIT_RESISTLOWPRESSURE,TRAIT_NOGUNS,TRAIT_RADIMMUNE,TRAIT_PIERCEIMMUNE,TRAIT_NODISMEMBER)
+ inherent_traits = list(TRAIT_NOBREATH, TRAIT_RESISTCOLD,TRAIT_RESISTHIGHPRESSURE,TRAIT_RESISTLOWPRESSURE,TRAIT_CHUNKYFINGERS,TRAIT_RADIMMUNE,TRAIT_PIERCEIMMUNE,TRAIT_NODISMEMBER)
info_text = "As a Durathread Golem, your strikes will cause those your targets to start choking, but your woven body won't withstand fire as well."
/datum/species/golem/durathread/spec_unarmedattacked(mob/living/carbon/human/user, mob/living/carbon/human/target)
@@ -974,7 +974,7 @@
fixed_mut_color = "ffffff"
attack_verb = "rattl"
species_traits = list(NOBLOOD,NO_UNDERWEAR,NOGENITALS,NOAROUSAL,MUTCOLORS)
- inherent_traits = list(TRAIT_RESISTHEAT,TRAIT_NOBREATH,TRAIT_RESISTCOLD,TRAIT_RESISTHIGHPRESSURE,TRAIT_RESISTLOWPRESSURE,TRAIT_NOFIRE,TRAIT_NOGUNS,TRAIT_RADIMMUNE,TRAIT_PIERCEIMMUNE,TRAIT_NODISMEMBER,TRAIT_FAKEDEATH,TRAIT_CALCIUM_HEALER)
+ inherent_traits = list(TRAIT_RESISTHEAT,TRAIT_NOBREATH,TRAIT_RESISTCOLD,TRAIT_RESISTHIGHPRESSURE,TRAIT_RESISTLOWPRESSURE,TRAIT_NOFIRE,TRAIT_CHUNKYFINGERS,TRAIT_RADIMMUNE,TRAIT_PIERCEIMMUNE,TRAIT_NODISMEMBER,TRAIT_FAKEDEATH,TRAIT_CALCIUM_HEALER)
info_text = "As a Bone Golem, You have a powerful spell that lets you chill your enemies with fear, and milk heals you! Just make sure to watch our for bone-hurting juice."
var/datum/action/innate/bonechill/bonechill
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 7d568ed510..5f91e3ae66 100644
--- a/code/modules/mob/living/carbon/human/species_types/jellypeople.dm
+++ b/code/modules/mob/living/carbon/human/species_types/jellypeople.dm
@@ -44,7 +44,7 @@
C.faction |= "slime"
/datum/species/jelly/spec_life(mob/living/carbon/human/H)
- if(H.stat == DEAD) //can't farm slime jelly from a dead slime/jelly person indefinitely
+ if(H.stat == DEAD || HAS_TRAIT(H, TRAIT_NOMARROW)) //can't farm slime jelly from a dead slime/jelly person indefinitely, and no regeneration for vampires
return
if(!H.blood_volume)
H.blood_volume += 5
@@ -116,6 +116,33 @@
return
to_chat(H, "...but there is not enough of you to go around! You must attain more mass to heal!")
+/datum/species/jelly/spec_death(gibbed, mob/living/carbon/human/H)
+ if(H)
+ stop_wagging_tail(H)
+
+/datum/species/jelly/spec_stun(mob/living/carbon/human/H,amount)
+ if(H)
+ stop_wagging_tail(H)
+ . = ..()
+
+/datum/species/jelly/can_wag_tail(mob/living/carbon/human/H)
+ return ("mam_tail" in mutant_bodyparts) || ("mam_waggingtail" in mutant_bodyparts)
+
+/datum/species/jelly/is_wagging_tail(mob/living/carbon/human/H)
+ return ("mam_waggingtail" in mutant_bodyparts)
+
+/datum/species/jelly/start_wagging_tail(mob/living/carbon/human/H)
+ if("mam_tail" in mutant_bodyparts)
+ mutant_bodyparts -= "mam_tail"
+ mutant_bodyparts |= "mam_waggingtail"
+ H.update_body()
+
+/datum/species/jelly/stop_wagging_tail(mob/living/carbon/human/H)
+ if("mam_waggingtail" in mutant_bodyparts)
+ mutant_bodyparts -= "mam_waggingtail"
+ mutant_bodyparts |= "mam_tail"
+ H.update_body()
+
////////////////////////////////////////////////////////SLIMEPEOPLE///////////////////////////////////////////////////////////////////
//Slime people are able to split like slimes, retaining a single mind that can swap between bodies at will, even after death.
@@ -177,6 +204,8 @@
bodies = old_species.bodies
/datum/species/jelly/slime/spec_life(mob/living/carbon/human/H)
+ if((HAS_TRAIT(H, TRAIT_NOMARROW)))
+ return
if(H.blood_volume >= BLOOD_VOLUME_SLIME_SPLIT)
if(prob(5))
to_chat(H, "You feel very bloated!")
@@ -410,34 +439,6 @@
heatmod = 1
burnmod = 1
-/datum/species/jelly/roundstartslime/spec_death(gibbed, mob/living/carbon/human/H)
- if(H)
- stop_wagging_tail(H)
-
-/datum/species/jelly/roundstartslime/spec_stun(mob/living/carbon/human/H,amount)
- if(H)
- stop_wagging_tail(H)
- . = ..()
-
-/datum/species/jelly/roundstartslime/can_wag_tail(mob/living/carbon/human/H)
- return ("mam_tail" in mutant_bodyparts) || ("mam_waggingtail" in mutant_bodyparts)
-
-/datum/species/jelly/roundstartslime/is_wagging_tail(mob/living/carbon/human/H)
- return ("mam_waggingtail" in mutant_bodyparts)
-
-/datum/species/jelly/roundstartslime/start_wagging_tail(mob/living/carbon/human/H)
- if("mam_tail" in mutant_bodyparts)
- mutant_bodyparts -= "mam_tail"
- mutant_bodyparts |= "mam_waggingtail"
- H.update_body()
-
-/datum/species/jelly/roundstartslime/stop_wagging_tail(mob/living/carbon/human/H)
- if("mam_waggingtail" in mutant_bodyparts)
- mutant_bodyparts -= "mam_waggingtail"
- mutant_bodyparts |= "mam_tail"
- H.update_body()
-
-
/datum/action/innate/slime_change
name = "Alter Form"
check_flags = AB_CHECK_CONSCIOUS
@@ -838,19 +839,16 @@
link_minds = new(src)
link_minds.Grant(C)
slimelink_owner = C
- link_mob(C)
+ link_mob(C, TRUE)
-/datum/species/jelly/stargazer/proc/link_mob(mob/living/M)
- if(QDELETED(M) || M.stat == DEAD)
+/datum/species/jelly/stargazer/proc/link_mob(mob/living/M, selflink = FALSE)
+ if(QDELETED(M) || (M in linked_mobs))
return FALSE
- if(HAS_TRAIT(M, TRAIT_MINDSHIELD)) //mindshield implant, no dice
- return FALSE
- if(M.anti_magic_check(FALSE, FALSE, TRUE, 0))
- return FALSE
- if(M in linked_mobs)
+ if(!selflink && (M.stat == DEAD || HAS_TRAIT(M, TRAIT_MINDSHIELD) || M.anti_magic_check(FALSE, FALSE, TRUE, 0)))
return FALSE
linked_mobs.Add(M)
- to_chat(M, "You are now connected to [slimelink_owner.real_name]'s Slime Link.")
+ if(!selflink)
+ to_chat(M, "You are now connected to [slimelink_owner.real_name]'s Slime Link.")
var/datum/action/innate/linked_speech/action = new(src)
linked_actions.Add(action)
action.Grant(M)
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 c0973102d6..9317a51050 100644
--- a/code/modules/mob/living/carbon/human/species_types/lizardpeople.dm
+++ b/code/modules/mob/living/carbon/human/species_types/lizardpeople.dm
@@ -94,7 +94,7 @@
id = "ashlizard"
limbs_id = "lizard"
species_traits = list(MUTCOLORS,EYECOLOR,LIPS,DIGITIGRADE)
- inherent_traits = list(TRAIT_NOGUNS)
+ inherent_traits = list(TRAIT_CHUNKYFINGERS)
mutantlungs = /obj/item/organ/lungs/ashwalker
burnmod = 0.9
brutemod = 0.9
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 90f63467a0..3247c96632 100644
--- a/code/modules/mob/living/carbon/human/species_types/shadowpeople.dm
+++ b/code/modules/mob/living/carbon/human/species_types/shadowpeople.dm
@@ -39,7 +39,7 @@
blacklisted = TRUE
no_equip = list(SLOT_WEAR_MASK, SLOT_WEAR_SUIT, SLOT_GLOVES, SLOT_SHOES, SLOT_W_UNIFORM, SLOT_S_STORE)
species_traits = list(NOBLOOD,NO_UNDERWEAR,NO_DNA_COPY,NOTRANSSTING,NOEYES,NOGENITALS,NOAROUSAL)
- inherent_traits = list(TRAIT_RESISTCOLD,TRAIT_NOBREATH,TRAIT_RESISTHIGHPRESSURE,TRAIT_RESISTLOWPRESSURE,TRAIT_NOGUNS,TRAIT_RADIMMUNE,TRAIT_VIRUSIMMUNE,TRAIT_PIERCEIMMUNE,TRAIT_NODISMEMBER,TRAIT_NOHUNGER)
+ inherent_traits = list(TRAIT_RESISTCOLD,TRAIT_NOBREATH,TRAIT_RESISTHIGHPRESSURE,TRAIT_RESISTLOWPRESSURE,TRAIT_CHUNKYFINGERS,TRAIT_RADIMMUNE,TRAIT_VIRUSIMMUNE,TRAIT_PIERCEIMMUNE,TRAIT_NODISMEMBER,TRAIT_NOHUNGER)
mutanteyes = /obj/item/organ/eyes/night_vision/nightmare
mutant_organs = list(/obj/item/organ/heart/nightmare)
mutant_brain = /obj/item/organ/brain/nightmare
diff --git a/code/modules/mob/living/carbon/life.dm b/code/modules/mob/living/carbon/life.dm
index 16a9f07ceb..0061c7ed70 100644
--- a/code/modules/mob/living/carbon/life.dm
+++ b/code/modules/mob/living/carbon/life.dm
@@ -659,6 +659,9 @@ GLOBAL_LIST_INIT(ballmer_windows_me_msg, list("Yo man, what if, we like, uh, put
//used in human and monkey handle_environment()
/mob/living/carbon/proc/natural_bodytemperature_stabilization()
+ if (HAS_TRAIT(src, TRAIT_COLDBLOODED))
+ return 0 //Return 0 as your natural temperature. Species proc handle_environment() will adjust your temperature based on this.
+
var/body_temperature_difference = BODYTEMP_NORMAL - bodytemperature
switch(bodytemperature)
if(-INFINITY to BODYTEMP_COLD_DAMAGE_LIMIT) //Cold damage limit is 50 below the default, the temperature where you start to feel effects.
diff --git a/code/modules/mob/living/carbon/status_procs.dm b/code/modules/mob/living/carbon/status_procs.dm
index 766bd376b1..6c497bb8d4 100644
--- a/code/modules/mob/living/carbon/status_procs.dm
+++ b/code/modules/mob/living/carbon/status_procs.dm
@@ -2,44 +2,6 @@
//The effects include: stun, knockdown, unconscious, sleeping, resting, jitteriness, dizziness, ear damage,
// eye damage, eye_blind, eye_blurry, druggy, TRAIT_BLIND trait, TRAIT_NEARSIGHT trait, and TRAIT_HUSK trait.
-/mob/living/carbon/damage_eyes(amount)
- var/obj/item/organ/eyes/eyes = getorganslot(ORGAN_SLOT_EYES)
- if (!eyes)
- return
- if(amount>0)
- eyes.eye_damage = amount
- if(eyes.eye_damage > 20)
- if(eyes.eye_damage > 30)
- overlay_fullscreen("eye_damage", /obj/screen/fullscreen/impaired, 2)
- else
- overlay_fullscreen("eye_damage", /obj/screen/fullscreen/impaired, 1)
-
-/mob/living/carbon/set_eye_damage(amount)
- var/obj/item/organ/eyes/eyes = getorganslot(ORGAN_SLOT_EYES)
- if (!eyes)
- return
- eyes.eye_damage = max(amount,0)
- if(eyes.eye_damage > 20)
- if(eyes.eye_damage > 30)
- overlay_fullscreen("eye_damage", /obj/screen/fullscreen/impaired, 2)
- else
- overlay_fullscreen("eye_damage", /obj/screen/fullscreen/impaired, 1)
- else
- clear_fullscreen("eye_damage")
-
-/mob/living/carbon/adjust_eye_damage(amount)
- var/obj/item/organ/eyes/eyes = getorganslot(ORGAN_SLOT_EYES)
- if (!eyes)
- return
- eyes.eye_damage = max(eyes.eye_damage+amount, 0)
- if(eyes.eye_damage > 20)
- if(eyes.eye_damage > 30)
- overlay_fullscreen("eye_damage", /obj/screen/fullscreen/impaired, 2)
- else
- overlay_fullscreen("eye_damage", /obj/screen/fullscreen/impaired, 1)
- else
- clear_fullscreen("eye_damage")
-
/mob/living/carbon/adjust_drugginess(amount)
druggy = max(druggy+amount, 0)
if(druggy)
diff --git a/code/modules/mob/living/death.dm b/code/modules/mob/living/death.dm
index 0ff418d628..8345ef916d 100644
--- a/code/modules/mob/living/death.dm
+++ b/code/modules/mob/living/death.dm
@@ -62,12 +62,8 @@
unset_machine()
timeofdeath = world.time
tod = STATION_TIME_TIMESTAMP("hh:mm:ss")
- var/turf/T = get_turf(src)
for(var/obj/item/I in contents)
I.on_mob_death(src, gibbed)
- if(mind && mind.name && mind.active && !istype(T.loc, /area/ctf))
- var/rendered = "[mind.name] has died at [get_area_name(T)]."
- deadchat_broadcast(rendered, follow_target = src, turf_target = T, message_type=DEADCHAT_DEATHRATTLE)
if(mind)
mind.store_memory("Time of death: [tod]", 0)
GLOB.alive_mob_list -= src
@@ -89,7 +85,12 @@
addtimer(CALLBACK(src, .proc/med_hud_set_status), (DEFIB_TIME_LIMIT * 10) + 1)
stop_pulling()
- SEND_SIGNAL(src, COMSIG_MOB_DEATH, gibbed)
+ var/signal = SEND_SIGNAL(src, COMSIG_MOB_DEATH, gibbed)
+
+ var/turf/T = get_turf(src)
+ if(mind && mind.name && mind.active && !istype(T.loc, /area/ctf) && !(signal & COMPONENT_BLOCK_DEATH_BROADCAST))
+ var/rendered = "[mind.name] has died at [get_area_name(T)]."
+ deadchat_broadcast(rendered, follow_target = src, turf_target = T, message_type=DEADCHAT_DEATHRATTLE)
if (client)
client.move_delay = initial(client.move_delay)
diff --git a/code/modules/mob/living/emote.dm b/code/modules/mob/living/emote.dm
index 6c1a2cfec9..041b367ebf 100644
--- a/code/modules/mob/living/emote.dm
+++ b/code/modules/mob/living/emote.dm
@@ -87,6 +87,12 @@
message_simple = S.deathmessage
. = ..()
message_simple = initial(message_simple)
+ if(. && user.deathsound)
+ if(isliving(user))
+ var/mob/living/L = user
+ if(!L.can_speak_vocal() || L.oxyloss >= 50)
+ return //stop the sound if oxyloss too high/cant speak
+ playsound(user, user.deathsound, 200, TRUE, TRUE)
if(. && isalienadult(user))
playsound(user.loc, 'sound/voice/hiss6.ogg', 80, 1, 1)
diff --git a/code/modules/mob/living/living.dm b/code/modules/mob/living/living.dm
index 6ced2a05a1..5299618177 100644
--- a/code/modules/mob/living/living.dm
+++ b/code/modules/mob/living/living.dm
@@ -502,7 +502,6 @@
bodytemperature = BODYTEMP_NORMAL
set_blindness(0)
set_blurriness(0)
- set_eye_damage(0)
cure_nearsighted()
cure_blind()
cure_husk()
@@ -1221,7 +1220,8 @@
if("eye_blind")
set_blindness(var_value)
if("eye_damage")
- set_eye_damage(var_value)
+ var/obj/item/organ/eyes/E = getorganslot(ORGAN_SLOT_EYES)
+ E?.setOrganDamage(var_value)
if("eye_blurry")
set_blurriness(var_value)
if("maxHealth")
diff --git a/code/modules/mob/living/say.dm b/code/modules/mob/living/say.dm
index 660a866271..4c670129c1 100644
--- a/code/modules/mob/living/say.dm
+++ b/code/modules/mob/living/say.dm
@@ -212,11 +212,11 @@ GLOBAL_LIST_INIT(department_radio_keys, list(
if(succumbed)
succumb()
- to_chat(src, compose_message(src, language, message, , spans, message_mode))
+ to_chat(src, compose_message(src, language, message, null, spans, message_mode))
return 1
-/mob/living/Hear(message, atom/movable/speaker, datum/language/message_language, raw_message, radio_freq, list/spans, message_mode)
+/mob/living/Hear(message, atom/movable/speaker, datum/language/message_language, raw_message, radio_freq, list/spans, message_mode, atom/movable/source)
. = ..()
if(!client)
return
@@ -231,7 +231,7 @@ GLOBAL_LIST_INIT(department_radio_keys, list(
deaf_type = 2 // Since you should be able to hear yourself without looking
// Recompose message for AI hrefs, language incomprehension.
- message = compose_message(speaker, message_language, raw_message, radio_freq, spans, message_mode)
+ message = compose_message(speaker, message_language, raw_message, radio_freq, spans, message_mode, FALSE, source)
show_message(message, MSG_AUDIBLE, deaf_message, deaf_type)
return message
@@ -244,8 +244,8 @@ GLOBAL_LIST_INIT(department_radio_keys, list(
var/list/listening = get_hearers_in_view(message_range+eavesdrop_range, source)
var/list/the_dead = list()
var/list/yellareas //CIT CHANGE - adds the ability for yelling to penetrate walls and echo throughout areas
- if(say_test(message) == "2") //CIT CHANGE - ditto
- yellareas = get_areas_in_range(message_range*0.5,src) //CIT CHANGE - ditto
+ if(!eavesdrop_range && say_test(message) == "2") //CIT CHANGE - ditto
+ yellareas = get_areas_in_range(message_range*0.5, source) //CIT CHANGE - ditto
for(var/_M in GLOB.player_list)
var/mob/M = _M
if(M.stat != DEAD) //not dead, not important
@@ -256,7 +256,7 @@ GLOBAL_LIST_INIT(department_radio_keys, list(
continue
if(!M.client || !client) //client is so that ghosts don't have to listen to mice
continue
- if(get_dist(M, src) > 7 || M.z != z) //they're out of range of normal hearing
+ if(get_dist(M, source) > 7 || M.z != z) //they're out of range of normal hearing
if(eavesdropping_modes[message_mode] && !(M.client.prefs.chat_toggles & CHAT_GHOSTWHISPER)) //they're whispering and we have hearing whispers at any range off
continue
if(!(M.client.prefs.chat_toggles & CHAT_GHOSTEARS)) //they're talking normally and we have hearing at any range off
@@ -268,15 +268,15 @@ GLOBAL_LIST_INIT(department_radio_keys, list(
var/eavesrendered
if(eavesdrop_range)
eavesdropping = stars(message)
- eavesrendered = compose_message(src, message_language, eavesdropping, , spans, message_mode)
+ eavesrendered = compose_message(src, message_language, eavesdropping, null, spans, message_mode, FALSE, source)
- var/rendered = compose_message(src, message_language, message, , spans, message_mode)
+ var/rendered = compose_message(src, message_language, message, null, spans, message_mode, FALSE, source)
for(var/_AM in listening)
var/atom/movable/AM = _AM
if(eavesdrop_range && get_dist(source, AM) > message_range && !(the_dead[AM]))
- AM.Hear(eavesrendered, src, message_language, eavesdropping, , spans, message_mode)
+ AM.Hear(eavesrendered, src, message_language, eavesdropping, null, spans, message_mode, source)
else
- AM.Hear(rendered, src, message_language, message, , spans, message_mode)
+ AM.Hear(rendered, src, message_language, message, null, spans, message_mode, source)
SEND_GLOBAL_SIGNAL(COMSIG_GLOB_LIVING_SAY_SPECIAL, src, message)
//speech bubble
diff --git a/code/modules/mob/living/silicon/ai/ai.dm b/code/modules/mob/living/silicon/ai/ai.dm
index 28208e27e4..0444458841 100644
--- a/code/modules/mob/living/silicon/ai/ai.dm
+++ b/code/modules/mob/living/silicon/ai/ai.dm
@@ -176,13 +176,13 @@
fire_stacks = 0
. = ..()
-/mob/living/silicon/ai/proc/set_core_display_icon(client/C)
+/mob/living/silicon/ai/proc/set_core_display_icon(input, client/C)
if(client && !C)
C = client
- if(!(C?.prefs?.preferred_ai_core_display))
- icon_state = display_icon_override || initial(icon_state)
+ if(!input && !C?.prefs?.preferred_ai_core_display)
+ icon_state = initial(icon_state)
else
- var/preferred_icon = display_icon_override || C.prefs.preferred_ai_core_display
+ var/preferred_icon = input ? input : C.prefs.preferred_ai_core_display
icon_state = resolve_ai_icon(preferred_icon)
/mob/living/silicon/ai/verb/pick_icon()
@@ -202,8 +202,9 @@
if(!ai_core_icon || incapacitated())
return
+
display_icon_override = ai_core_icon
- set_core_display_icon()
+ set_core_display_icon(ai_core_icon)
/mob/living/silicon/ai/Stat()
..()
@@ -600,7 +601,10 @@
if(incapacitated())
return
var/list/ai_emotions = list("Very Happy", "Happy", "Neutral", "Unsure", "Confused", "Sad", "BSOD", "Blank", "Problems?", "Awesome", "Facepalm", "Thinking", "Friend Computer", "Dorfy", "Blue Glow", "Red Glow")
- emote_display = input("Please, select a status!", "AI Status", null, null) in ai_emotions
+ var/n_emote = input("Please, select a status!", "AI Status", null, null) in ai_emotions
+ if(!n_emote)
+ return
+ emote_display = n_emote
for (var/each in GLOB.ai_status_displays) //change status of displays
var/obj/machinery/status_display/ai/M = each
M.emotion = emote_display
@@ -887,7 +891,7 @@
. = ..()
if(.) //successfully ressuscitated from death
set_eyeobj_visible(TRUE)
- set_core_display_icon()
+ set_core_display_icon(display_icon_override)
/mob/living/silicon/ai/proc/malfhacked(obj/machinery/power/apc/apc)
malfhack = null
diff --git a/code/modules/mob/living/silicon/ai/freelook/eye.dm b/code/modules/mob/living/silicon/ai/freelook/eye.dm
index c28f08d5b0..7477ba7713 100644
--- a/code/modules/mob/living/silicon/ai/freelook/eye.dm
+++ b/code/modules/mob/living/silicon/ai/freelook/eye.dm
@@ -191,7 +191,7 @@
acceleration = !acceleration
to_chat(usr, "Camera acceleration has been toggled [acceleration ? "on" : "off"].")
-/mob/camera/aiEye/Hear(message, atom/movable/speaker, datum/language/message_language, raw_message, radio_freq, list/spans, message_mode)
+/mob/camera/aiEye/Hear(message, atom/movable/speaker, datum/language/message_language, raw_message, radio_freq, list/spans, message_mode, atom/movable/source)
. = ..()
if(relay_speech && speaker && ai && !radio_freq && speaker != ai && near_camera(speaker))
ai.relay_speech(message, speaker, message_language, raw_message, radio_freq, spans, message_mode)
diff --git a/code/modules/mob/living/silicon/ai/say.dm b/code/modules/mob/living/silicon/ai/say.dm
index 112add367f..f757203237 100644
--- a/code/modules/mob/living/silicon/ai/say.dm
+++ b/code/modules/mob/living/silicon/ai/say.dm
@@ -49,7 +49,7 @@
else
padloc = "(UNKNOWN)"
src.log_talk(message, LOG_SAY, tag="HOLOPAD in [padloc]")
- send_speech(message, 7, T, "robot", language)
+ send_speech(message, 7, T, "robot", message_language = language)
to_chat(src, "Holopad transmitted, [real_name] ")
else
to_chat(src, "No holopad connected.")
diff --git a/code/modules/mob/living/silicon/login.dm b/code/modules/mob/living/silicon/login.dm
index 82c1435344..81f8fcbef1 100644
--- a/code/modules/mob/living/silicon/login.dm
+++ b/code/modules/mob/living/silicon/login.dm
@@ -4,4 +4,7 @@
var/datum/antagonist/rev/rev = mind.has_antag_datum(/datum/antagonist/rev)
if(rev)
rev.remove_revolutionary(TRUE)
+ var/datum/antagonist/bloodsucker/V = mind.has_antag_datum(/datum/antagonist/bloodsucker)
+ if(V)
+ mind.remove_antag_datum(V)
..()
diff --git a/code/modules/mob/living/silicon/robot/robot.dm b/code/modules/mob/living/silicon/robot/robot.dm
index 0484e39c1a..c5c2beb999 100644
--- a/code/modules/mob/living/silicon/robot/robot.dm
+++ b/code/modules/mob/living/silicon/robot/robot.dm
@@ -112,6 +112,8 @@
var/bellyup = 0
var/dogborg = FALSE
+ var/cansprint = 1
+
/mob/living/silicon/robot/get_cell()
return cell
@@ -159,6 +161,7 @@
else if(!mmi || !mmi.brainmob)
mmi = new (src)
mmi.brain = new /obj/item/organ/brain(mmi)
+ mmi.brain.organ_flags |= ORGAN_FROZEN
mmi.brain.name = "[real_name]'s brain"
mmi.icon_state = "mmi_full"
mmi.name = "Man-Machine Interface: [real_name]"
@@ -233,7 +236,6 @@
var/list/modulelist = list("Standard" = /obj/item/robot_module/standard, \
"Engineering" = /obj/item/robot_module/engineering, \
"Medical" = /obj/item/robot_module/medical, \
- "Medihound" = /obj/item/robot_module/medihound, \
"Miner" = /obj/item/robot_module/miner, \
"Service" = /obj/item/robot_module/butler)
if(!CONFIG_GET(flag/disable_peaceborg))
@@ -832,7 +834,7 @@
robot_suit.head.flash2.burn_out()
robot_suit.head.flash2 = null
robot_suit.head = null
- robot_suit.updateicon()
+ robot_suit.update_icon()
else
new /obj/item/robot_suit(T)
new /obj/item/bodypart/l_leg/robot(T)
diff --git a/code/modules/mob/living/silicon/robot/robot_modules.dm b/code/modules/mob/living/silicon/robot/robot_modules.dm
index 56011cb886..ccb879870a 100644
--- a/code/modules/mob/living/silicon/robot/robot_modules.dm
+++ b/code/modules/mob/living/silicon/robot/robot_modules.dm
@@ -22,7 +22,7 @@
var/moduleselect_icon = "nomod"
- var/can_be_pushed = TRUE
+ var/can_be_pushed = FALSE
var/magpulsing = FALSE
var/clean_on_move = FALSE
@@ -139,7 +139,7 @@
//Adds flavoursome dogborg items to dogborg variants without mechanical benefits
/obj/item/robot_module/proc/dogborg_equip()
- if(istype(src, /obj/item/robot_module/k9) || istype(src, /obj/item/robot_module/medihound))
+ if(istype(src, /obj/item/robot_module/k9))
return //Bandaid fix to prevent stacking until I merge these two modules into their base types
var/obj/item/I = new /obj/item/analyzer/nose/flavour(src)
basic_modules += I
@@ -322,19 +322,21 @@
/obj/item/stack/medical/gauze/cyborg,
/obj/item/organ_storage,
/obj/item/borg/lollipop,
- /obj/item/sensor_device)
+ /obj/item/sensor_device,
+ /obj/item/twohanded/shockpaddles/cyborg)
emag_modules = list(/obj/item/reagent_containers/borghypo/hacked)
ratvar_modules = list(
/obj/item/clockwork/slab/cyborg/medical,
/obj/item/clockwork/weapon/ratvarian_spear)
cyborg_base_icon = "medical"
moduleselect_icon = "medical"
- can_be_pushed = FALSE
hat_offset = 3
/obj/item/robot_module/medical/be_transformed_to(obj/item/robot_module/old_module)
var/mob/living/silicon/robot/R = loc
- var/borg_icon = input(R, "Select an icon!", "Robot Icon", null) as null|anything in list("Default", "Heavy", "Sleek", "Marina", "Droid", "Eyebot")
+ var/borg_icon = input(R, "Select an icon!", "Robot Icon", null) as null|anything in list("Default", "Heavy", "Sleek", "Marina", "Droid", "Eyebot", "Medihound", "Medihound Dark", "Vale")
+ if(R.client && R.client.ckey in list("nezuli"))
+ borg_icon += "Alina"
if(!borg_icon)
return FALSE
switch(borg_icon)
@@ -356,59 +358,46 @@
if("Heavy")
cyborg_base_icon = "heavymed"
cyborg_icon_override = 'modular_citadel/icons/mob/robots.dmi'
- return ..()
-
-/obj/item/robot_module/medihound
- name = "MediHound"
- basic_modules = list(
- /obj/item/dogborg/jaws/small,
- /obj/item/storage/bag/borgdelivery,
- /obj/item/analyzer/nose,
- /obj/item/soap/tongue,
- /obj/item/extinguisher/mini,
- /obj/item/healthanalyzer,
- /obj/item/dogborg/sleeper/medihound,
- /obj/item/roller/robo,
- /obj/item/reagent_containers/borghypo,
- /obj/item/twohanded/shockpaddles/cyborg/hound,
- /obj/item/stack/medical/gauze/cyborg,
- /obj/item/pinpointer/crew,
- /obj/item/sensor_device)
- emag_modules = list(/obj/item/dogborg/pounce)
- ratvar_modules = list(/obj/item/clockwork/slab/cyborg/medical,
- /obj/item/clockwork/weapon/ratvarian_spear)
- cyborg_base_icon = "medihound"
- moduleselect_icon = "medihound"
- moduleselect_alternate_icon = 'modular_citadel/icons/ui/screen_cyborg.dmi'
- can_be_pushed = FALSE
- hat_offset = INFINITY
- sleeper_overlay = "msleeper"
- cyborg_icon_override = 'modular_citadel/icons/mob/widerobot.dmi'
- has_snowflake_deadsprite = TRUE
- dogborg = TRUE
- cyborg_pixel_offset = -16
-
-/obj/item/robot_module/medihound/be_transformed_to(obj/item/robot_module/old_module)
- var/mob/living/silicon/robot/R = loc
- var/list/medhoundmodels = list("Default", "Dark", "Vale")
- if(R.client && R.client.ckey in list("nezuli"))
- medhoundmodels += "Alina"
- var/borg_icon = input(R, "Select an icon!", "Robot Icon", null) as null|anything in medhoundmodels
- if(!borg_icon)
- return FALSE
- switch(borg_icon)
- if("Default")
+ if("Medihound")
cyborg_base_icon = "medihound"
- if("Dark")
+ cyborg_icon_override = 'modular_citadel/icons/mob/widerobot.dmi'
+ moduleselect_icon = "medihound"
+ moduleselect_alternate_icon = 'modular_citadel/icons/ui/screen_cyborg.dmi'
+ has_snowflake_deadsprite = TRUE
+ dogborg = TRUE
+ cyborg_pixel_offset = -16
+ hat_offset = INFINITY
+ if("Medihound Dark")
cyborg_base_icon = "medihounddark"
+ cyborg_icon_override = 'modular_citadel/icons/mob/widerobot.dmi'
sleeper_overlay = "mdsleeper"
+ moduleselect_icon = "medihound"
+ moduleselect_alternate_icon = 'modular_citadel/icons/ui/screen_cyborg.dmi'
+ has_snowflake_deadsprite = TRUE
+ dogborg = TRUE
+ cyborg_pixel_offset = -16
+ hat_offset = INFINITY
if("Vale")
cyborg_base_icon = "valemed"
+ cyborg_icon_override = 'modular_citadel/icons/mob/widerobot.dmi'
sleeper_overlay = "valemedsleeper"
+ moduleselect_icon = "medihound"
+ moduleselect_alternate_icon = 'modular_citadel/icons/ui/screen_cyborg.dmi'
+ has_snowflake_deadsprite = TRUE
+ dogborg = TRUE
+ cyborg_pixel_offset = -16
+ hat_offset = INFINITY
if("Alina")
cyborg_base_icon = "alina-med"
+ cyborg_icon_override = 'modular_citadel/icons/mob/widerobot.dmi'
special_light_key = "alina"
sleeper_overlay = "alinasleeper"
+ moduleselect_icon = "medihound"
+ moduleselect_alternate_icon = 'modular_citadel/icons/ui/screen_cyborg.dmi'
+ has_snowflake_deadsprite = TRUE
+ dogborg = TRUE
+ cyborg_pixel_offset = -16
+ hat_offset = INFINITY
return ..()
/obj/item/robot_module/engineering
@@ -489,7 +478,6 @@
cyborg_icon_override = 'modular_citadel/icons/mob/robots.dmi'
if("Pup Dozer")
cyborg_base_icon = "pupdozer"
- can_be_pushed = FALSE
hat_offset = INFINITY
cyborg_icon_override = 'modular_citadel/icons/mob/widerobot.dmi'
has_snowflake_deadsprite = TRUE
@@ -498,7 +486,6 @@
sleeper_overlay = "dozersleeper"
if("Vale")
cyborg_base_icon = "valeeng"
- can_be_pushed = FALSE
hat_offset = INFINITY
cyborg_icon_override = 'modular_citadel/icons/mob/widerobot.dmi'
has_snowflake_deadsprite = TRUE
@@ -508,7 +495,6 @@
if("Alina")
cyborg_base_icon = "alina-eng"
special_light_key = "alina"
- can_be_pushed = FALSE
hat_offset = INFINITY
cyborg_icon_override = 'modular_citadel/icons/mob/widerobot.dmi'
has_snowflake_deadsprite = TRUE
@@ -533,7 +519,6 @@
/obj/item/clockwork/weapon/ratvarian_spear)
cyborg_base_icon = "sec"
moduleselect_icon = "security"
- can_be_pushed = FALSE
hat_offset = 3
/obj/item/robot_module/security/do_transform_animation()
@@ -589,7 +574,6 @@
cyborg_base_icon = "k9"
moduleselect_icon = "k9"
moduleselect_alternate_icon = 'modular_citadel/icons/ui/screen_cyborg.dmi'
- can_be_pushed = FALSE
hat_offset = INFINITY
sleeper_overlay = "ksleeper"
cyborg_icon_override = 'modular_citadel/icons/mob/widerobot.dmi'
@@ -652,7 +636,6 @@
/obj/item/clockwork/weapon/ratvarian_spear)
cyborg_base_icon = "peace"
moduleselect_icon = "standard"
- can_be_pushed = FALSE
hat_offset = -2
/obj/item/robot_module/peacekeeper/do_transform_animation()
@@ -947,7 +930,6 @@
/obj/item/clockwork/weapon/ratvarian_spear)
cyborg_base_icon = "synd_sec"
moduleselect_icon = "malf"
- can_be_pushed = FALSE
hat_offset = 3
/obj/item/robot_module/syndicate/rebuild_modules()
@@ -987,7 +969,6 @@
/obj/item/clockwork/weapon/ratvarian_spear)
cyborg_base_icon = "synd_medical"
moduleselect_icon = "malf"
- can_be_pushed = FALSE
hat_offset = 3
/obj/item/robot_module/saboteur
@@ -1026,7 +1007,6 @@
cyborg_base_icon = "synd_engi"
moduleselect_icon = "malf"
- can_be_pushed = FALSE
magpulsing = TRUE
hat_offset = -4
canDispose = TRUE
diff --git a/code/modules/mob/living/simple_animal/astral.dm b/code/modules/mob/living/simple_animal/astral.dm
index 2aafedb149..3d0c335989 100644
--- a/code/modules/mob/living/simple_animal/astral.dm
+++ b/code/modules/mob/living/simple_animal/astral.dm
@@ -27,6 +27,7 @@
unsuitable_atmos_damage = 0
minbodytemp = 0
maxbodytemp = 100000
+ blood_volume = 0
/mob/living/simple_animal/astral/death()
icon_state = "shade_dead"
diff --git a/code/modules/mob/living/simple_animal/bot/bot.dm b/code/modules/mob/living/simple_animal/bot/bot.dm
index e61cfad068..7a653f4aa4 100644
--- a/code/modules/mob/living/simple_animal/bot/bot.dm
+++ b/code/modules/mob/living/simple_animal/bot/bot.dm
@@ -14,6 +14,7 @@
atmos_requirements = list("min_oxy" = 0, "max_oxy" = 0, "min_tox" = 0, "max_tox" = 0, "min_co2" = 0, "max_co2" = 0, "min_n2" = 0, "max_n2" = 0)
maxbodytemp = INFINITY
minbodytemp = 0
+ blood_volume = 0
has_unlimited_silicon_privilege = 1
sentience_type = SENTIENCE_ARTIFICIAL
status_flags = NONE //no default canpush
diff --git a/code/modules/mob/living/simple_animal/constructs.dm b/code/modules/mob/living/simple_animal/constructs.dm
index b7067f8904..be900aa4ef 100644
--- a/code/modules/mob/living/simple_animal/constructs.dm
+++ b/code/modules/mob/living/simple_animal/constructs.dm
@@ -33,6 +33,7 @@
initial_language_holder = /datum/language_holder/construct
deathmessage = "collapses in a shattered heap."
hud_type = /datum/hud/constructs
+ blood_volume = 0
var/list/construct_spells = list()
var/playstyle_string = "You are a generic construct! Your job is to not exist, and you should probably adminhelp this."
var/master = null
@@ -459,4 +460,3 @@
hud_used.healths.icon_state = "[icon_state]_health5"
else
hud_used.healths.icon_state = "[icon_state]_health6"
-
diff --git a/code/modules/mob/living/simple_animal/friendly/cockroach.dm b/code/modules/mob/living/simple_animal/friendly/cockroach.dm
index c85890df88..26d4691d83 100644
--- a/code/modules/mob/living/simple_animal/friendly/cockroach.dm
+++ b/code/modules/mob/living/simple_animal/friendly/cockroach.dm
@@ -3,6 +3,7 @@
desc = "This station is just crawling with bugs."
icon_state = "cockroach"
icon_dead = "cockroach"
+ blood_volume = 50
health = 1
maxHealth = 1
turns_per_move = 5
diff --git a/code/modules/mob/living/simple_animal/friendly/crab.dm b/code/modules/mob/living/simple_animal/friendly/crab.dm
index d7d673ff8b..9c3e5b5def 100644
--- a/code/modules/mob/living/simple_animal/friendly/crab.dm
+++ b/code/modules/mob/living/simple_animal/friendly/crab.dm
@@ -8,6 +8,7 @@
speak_emote = list("clicks")
emote_hear = list("clicks.")
emote_see = list("clacks.")
+ blood_volume = 350
speak_chance = 1
turns_per_move = 5
butcher_results = list(/obj/item/reagent_containers/food/snacks/meat/slab = 1)
diff --git a/code/modules/mob/living/simple_animal/friendly/drone/_drone.dm b/code/modules/mob/living/simple_animal/friendly/drone/_drone.dm
index 387973b979..7e0e43055f 100644
--- a/code/modules/mob/living/simple_animal/friendly/drone/_drone.dm
+++ b/code/modules/mob/living/simple_animal/friendly/drone/_drone.dm
@@ -50,6 +50,7 @@
dextrous_hud_type = /datum/hud/dextrous/drone
lighting_alpha = LIGHTING_PLANE_ALPHA_MOSTLY_INVISIBLE
see_in_dark = 7
+ blood_volume = 0
can_be_held = TRUE
held_items = list(null, null)
var/staticChoice = "static"
diff --git a/code/modules/mob/living/simple_animal/friendly/mouse.dm b/code/modules/mob/living/simple_animal/friendly/mouse.dm
index bf45c9cc73..5b962b2dd8 100644
--- a/code/modules/mob/living/simple_animal/friendly/mouse.dm
+++ b/code/modules/mob/living/simple_animal/friendly/mouse.dm
@@ -10,6 +10,7 @@
emote_see = list("runs in a circle.", "shakes.")
speak_chance = 1
turns_per_move = 5
+ blood_volume = 250
see_in_dark = 6
maxHealth = 5
health = 5
diff --git a/code/modules/mob/living/simple_animal/guardian/guardian.dm b/code/modules/mob/living/simple_animal/guardian/guardian.dm
index e3ef14c784..1bd434b233 100644
--- a/code/modules/mob/living/simple_animal/guardian/guardian.dm
+++ b/code/modules/mob/living/simple_animal/guardian/guardian.dm
@@ -20,6 +20,7 @@ GLOBAL_LIST_EMPTY(parasites) //all currently existing/living guardians
icon_living = "magicOrange"
icon_dead = "magicOrange"
speed = 0
+ blood_volume = 0
a_intent = INTENT_HARM
stop_automated_movement = 1
movement_type = FLYING // Immunity to chasms and landmines, etc.
diff --git a/code/modules/mob/living/simple_animal/guardian/types/explosive.dm b/code/modules/mob/living/simple_animal/guardian/types/explosive.dm
index b1af34eb02..e9f767376a 100644
--- a/code/modules/mob/living/simple_animal/guardian/types/explosive.dm
+++ b/code/modules/mob/living/simple_animal/guardian/types/explosive.dm
@@ -29,6 +29,7 @@
/mob/living/simple_animal/hostile/guardian/bomb/AltClickOn(atom/movable/A)
if(!istype(A))
+ altclick_listed_turf(A)
return
if(loc == summoner)
to_chat(src, "You must be manifested to create bombs!")
diff --git a/code/modules/mob/living/simple_animal/guardian/types/support.dm b/code/modules/mob/living/simple_animal/guardian/types/support.dm
index d31809e9aa..b9783ed116 100644
--- a/code/modules/mob/living/simple_animal/guardian/types/support.dm
+++ b/code/modules/mob/living/simple_animal/guardian/types/support.dm
@@ -105,8 +105,9 @@
/mob/living/simple_animal/hostile/guardian/healer/AltClickOn(atom/movable/A)
if(!istype(A))
+ altclick_listed_turf(A)
return
- if(src.loc == summoner)
+ if(loc == summoner)
to_chat(src, "You must be manifested to warp a target!")
return
if(!beacon)
diff --git a/code/modules/mob/living/simple_animal/hostile/faithless.dm b/code/modules/mob/living/simple_animal/hostile/faithless.dm
index 4e7cb0ac70..bc766f7409 100644
--- a/code/modules/mob/living/simple_animal/hostile/faithless.dm
+++ b/code/modules/mob/living/simple_animal/hostile/faithless.dm
@@ -19,6 +19,7 @@
spacewalk = TRUE
stat_attack = UNCONSCIOUS
robust_searching = 1
+ blood_volume = 0
harm_intent_damage = 10
obj_damage = 50
@@ -42,4 +43,4 @@
var/mob/living/carbon/C = target
C.Knockdown(60)
C.visible_message("\The [src] knocks down \the [C]!", \
- "\The [src] knocks you down!")
\ No newline at end of file
+ "\The [src] knocks you down!")
diff --git a/code/modules/mob/living/simple_animal/hostile/hivebot.dm b/code/modules/mob/living/simple_animal/hostile/hivebot.dm
index ece5d7e24c..a9576a467b 100644
--- a/code/modules/mob/living/simple_animal/hostile/hivebot.dm
+++ b/code/modules/mob/living/simple_animal/hostile/hivebot.dm
@@ -28,6 +28,7 @@
gold_core_spawnable = HOSTILE_SPAWN
del_on_death = 1
loot = list(/obj/effect/decal/cleanable/robot_debris)
+ blood_volume = 0
do_footstep = TRUE
diff --git a/code/modules/mob/living/simple_animal/hostile/hostile.dm b/code/modules/mob/living/simple_animal/hostile/hostile.dm
index 0377efd16e..bd4f334476 100644
--- a/code/modules/mob/living/simple_animal/hostile/hostile.dm
+++ b/code/modules/mob/living/simple_animal/hostile/hostile.dm
@@ -427,7 +427,7 @@
if(casingtype)
var/obj/item/ammo_casing/casing = new casingtype(startloc)
playsound(src, projectilesound, 100, 1)
- casing.fire_casing(targeted_atom, src, null, null, null, ran_zone(), src)
+ casing.fire_casing(targeted_atom, src, null, null, null, ran_zone(), 0, src)
else if(projectiletype)
var/obj/item/projectile/P = new projectiletype(startloc)
playsound(src, projectilesound, 100, 1)
diff --git a/code/modules/mob/living/simple_animal/hostile/jungle/seedling.dm b/code/modules/mob/living/simple_animal/hostile/jungle/seedling.dm
index 1a894734d8..38dd17a09b 100644
--- a/code/modules/mob/living/simple_animal/hostile/jungle/seedling.dm
+++ b/code/modules/mob/living/simple_animal/hostile/jungle/seedling.dm
@@ -30,6 +30,7 @@
robust_searching = TRUE
stat_attack = UNCONSCIOUS
anchored = TRUE
+ blood_volume = 0
var/combatant_state = SEEDLING_STATE_NEUTRAL
var/obj/seedling_weakpoint/weak_point
var/mob/living/beam_debuff_target
diff --git a/code/modules/mob/living/simple_animal/hostile/megafauna/colossus.dm b/code/modules/mob/living/simple_animal/hostile/megafauna/colossus.dm
index f685ff1f97..51a9d8d62b 100644
--- a/code/modules/mob/living/simple_animal/hostile/megafauna/colossus.dm
+++ b/code/modules/mob/living/simple_animal/hostile/megafauna/colossus.dm
@@ -376,7 +376,7 @@ Difficulty: Very Hard
. += observer_desc
. += "It is activated by [activation_method]."
-/obj/machinery/anomalous_crystal/Hear(message, atom/movable/speaker, message_langs, raw_message, radio_freq, spans, message_mode)
+/obj/machinery/anomalous_crystal/Hear(message, atom/movable/speaker, message_langs, raw_message, radio_freq, spans, message_mode, atom/movable/source)
..()
if(isliving(speaker))
ActivationReaction(speaker, ACTIVATE_SPEECH)
diff --git a/code/modules/mob/living/simple_animal/hostile/megafauna/drake.dm b/code/modules/mob/living/simple_animal/hostile/megafauna/drake.dm
index 78dc050ee0..0e3cde5628 100644
--- a/code/modules/mob/living/simple_animal/hostile/megafauna/drake.dm
+++ b/code/modules/mob/living/simple_animal/hostile/megafauna/drake.dm
@@ -267,6 +267,7 @@ Difficulty: Medium
/mob/living/simple_animal/hostile/megafauna/dragon/AltClickOn(atom/movable/A)
if(!istype(A))
+ altclick_listed_turf(A)
return
if(swoop_cooldown >= world.time)
to_chat(src, "You need to wait 20 seconds between swoop attacks!")
diff --git a/code/modules/mob/living/simple_animal/hostile/megafauna/hierophant.dm b/code/modules/mob/living/simple_animal/hostile/megafauna/hierophant.dm
index cca39cfea6..397d40925b 100644
--- a/code/modules/mob/living/simple_animal/hostile/megafauna/hierophant.dm
+++ b/code/modules/mob/living/simple_animal/hostile/megafauna/hierophant.dm
@@ -50,6 +50,7 @@ Difficulty: Normal
armour_penetration = 75
melee_damage_lower = 15
melee_damage_upper = 20
+ blood_volume = 0
speed = 1
move_to_delay = 11
ranged = 1
@@ -441,7 +442,7 @@ Difficulty: Normal
/mob/living/simple_animal/hostile/megafauna/hierophant/AltClickOn(atom/A) //player control handler(don't give this to a player holy fuck)
if(!istype(A) || get_dist(A, src) <= 2)
- return
+ return altclick_listed_turf(A)
blink(A)
//Hierophant overlays
diff --git a/code/modules/mob/living/simple_animal/hostile/mining_mobs/curse_blob.dm b/code/modules/mob/living/simple_animal/hostile/mining_mobs/curse_blob.dm
index 20916c9311..51919dad24 100644
--- a/code/modules/mob/living/simple_animal/hostile/mining_mobs/curse_blob.dm
+++ b/code/modules/mob/living/simple_animal/hostile/mining_mobs/curse_blob.dm
@@ -22,6 +22,7 @@
environment_smash = ENVIRONMENT_SMASH_NONE
sentience_type = SENTIENCE_BOSS
layer = LARGE_MOB_LAYER
+ blood_volume = 0
var/doing_move_loop = FALSE
var/mob/living/set_target
var/timerid
diff --git a/code/modules/mob/living/simple_animal/hostile/mining_mobs/elites/elite.dm b/code/modules/mob/living/simple_animal/hostile/mining_mobs/elites/elite.dm
new file mode 100644
index 0000000000..04a1b4a468
--- /dev/null
+++ b/code/modules/mob/living/simple_animal/hostile/mining_mobs/elites/elite.dm
@@ -0,0 +1,366 @@
+#define TUMOR_INACTIVE 0
+#define TUMOR_ACTIVE 1
+#define TUMOR_PASSIVE 2
+
+//Elite mining mobs
+/mob/living/simple_animal/hostile/asteroid/elite
+ name = "elite"
+ desc = "An elite monster, found in one of the strange tumors on lavaland."
+ icon = 'icons/mob/lavaland/lavaland_elites.dmi'
+ faction = list("boss")
+ robust_searching = TRUE
+ ranged_ignores_vision = TRUE
+ ranged = TRUE
+ obj_damage = 5
+ vision_range = 6
+ aggro_vision_range = 18
+ environment_smash = ENVIRONMENT_SMASH_NONE //This is to prevent elites smashing up the mining station, we'll make sure they can smash minerals fine below.
+ harm_intent_damage = 0 //Punching elites gets you nowhere
+ stat_attack = UNCONSCIOUS
+ layer = LARGE_MOB_LAYER
+ sentience_type = SENTIENCE_BOSS
+ hud_type = /datum/hud/lavaland_elite
+ var/chosen_attack = 1
+ var/list/attack_action_types = list()
+ var/can_talk = FALSE
+ var/obj/loot_drop = null
+
+//Gives player-controlled variants the ability to swap attacks
+/mob/living/simple_animal/hostile/asteroid/elite/Initialize(mapload)
+ . = ..()
+ for(var/action_type in attack_action_types)
+ var/datum/action/innate/elite_attack/attack_action = new action_type()
+ attack_action.Grant(src)
+
+//Prevents elites from attacking members of their faction (can't hurt themselves either) and lets them mine rock with an attack despite not being able to smash walls.
+/mob/living/simple_animal/hostile/asteroid/elite/AttackingTarget()
+ if(istype(target, /mob/living/simple_animal/hostile))
+ var/mob/living/simple_animal/hostile/M = target
+ if(faction_check_mob(M))
+ return FALSE
+ if(istype(target, /obj/structure/elite_tumor))
+ var/obj/structure/elite_tumor/T = target
+ if(T.mychild == src && T.activity == TUMOR_PASSIVE)
+ var/elite_remove = alert("Re-enter the tumor?", "Despawn yourself?", "Yes", "No")
+ if(elite_remove == "No" || !src || QDELETED(src))
+ return
+ T.mychild = null
+ T.activity = TUMOR_INACTIVE
+ T.icon_state = "advanced_tumor"
+ qdel(src)
+ return FALSE
+ . = ..()
+ if(ismineralturf(target))
+ var/turf/closed/mineral/M = target
+ M.gets_drilled()
+
+//Elites can't talk (normally)!
+/mob/living/simple_animal/hostile/asteroid/elite/say(message, bubble_type, var/list/spans = list(), sanitize = TRUE, datum/language/language = null, ignore_spam = FALSE, forced = null)
+ if(can_talk)
+ . = ..()
+ return TRUE
+ return FALSE
+
+/*Basic setup for elite attacks, based on Whoneedspace's megafauna attack setup.
+While using this makes the system rely on OnFire, it still gives options for timers not tied to OnFire, and it makes using attacks consistent accross the board for player-controlled elites.*/
+
+/datum/action/innate/elite_attack
+ name = "Elite Attack"
+ icon_icon = 'icons/mob/actions/actions_elites.dmi'
+ button_icon_state = ""
+ background_icon_state = "bg_default"
+ var/mob/living/simple_animal/hostile/asteroid/elite/M
+ var/chosen_message
+ var/chosen_attack_num = 0
+
+/datum/action/innate/elite_attack/Grant(mob/living/L)
+ if(istype(L, /mob/living/simple_animal/hostile/asteroid/elite))
+ M = L
+ return ..()
+ return FALSE
+
+/datum/action/innate/elite_attack/Activate()
+ M.chosen_attack = chosen_attack_num
+ to_chat(M, chosen_message)
+
+/mob/living/simple_animal/hostile/asteroid/elite/updatehealth()
+ . = ..()
+ update_health_hud()
+
+/mob/living/simple_animal/hostile/asteroid/elite/update_health_hud()
+ if(hud_used)
+ var/severity = 0
+ var/healthpercent = (health/maxHealth) * 100
+ switch(healthpercent)
+ if(100 to INFINITY)
+ hud_used.healths.icon_state = "elite_health0"
+ if(80 to 100)
+ severity = 1
+ if(60 to 80)
+ severity = 2
+ if(40 to 60)
+ severity = 3
+ if(20 to 40)
+ severity = 4
+ if(10 to 20)
+ severity = 5
+ if(1 to 20)
+ severity = 6
+ else
+ severity = 7
+ hud_used.healths.icon_state = "elite_health[severity]"
+ if(severity > 0)
+ overlay_fullscreen("brute", /obj/screen/fullscreen/brute, severity)
+ else
+ clear_fullscreen("brute")
+
+//The Pulsing Tumor, the actual "spawn-point" of elites, handles the spawning, arena, and procs for dealing with basic scenarios.
+
+/obj/structure/elite_tumor
+ name = "pulsing tumor"
+ desc = "An odd, pulsing tumor sticking out of the ground. You feel compelled to reach out and touch it..."
+ armor = list("melee" = 100, "bullet" = 100, "laser" = 100, "energy" = 100, "bomb" = 100, "bio" = 100, "rad" = 100, "fire" = 100, "acid" = 100)
+ resistance_flags = INDESTRUCTIBLE
+ var/activity = TUMOR_INACTIVE
+ var/boosted = FALSE
+ var/times_won = 0
+ var/mob/living/carbon/human/activator = null
+ var/mob/living/simple_animal/hostile/asteroid/elite/mychild = null
+ var/potentialspawns = list(/mob/living/simple_animal/hostile/asteroid/elite/broodmother,
+ /mob/living/simple_animal/hostile/asteroid/elite/pandora,
+ /mob/living/simple_animal/hostile/asteroid/elite/legionnaire,
+ /mob/living/simple_animal/hostile/asteroid/elite/herald)
+ icon = 'icons/obj/lavaland/tumor.dmi'
+ icon_state = "tumor"
+ pixel_x = -16
+ light_color = LIGHT_COLOR_RED
+ light_range = 3
+ anchored = TRUE
+ density = FALSE
+ var/obj/item/gps/internal = null
+
+/obj/item/gps/internal/elite
+ icon_state = null
+ gpstag = "Menacing Signal"
+ desc = "You're not quite sure how a signal can be menacing."
+ invisibility = 100
+
+/obj/structure/elite_tumor/attack_hand(mob/user)
+ . = ..()
+ if(ishuman(user))
+ switch(activity)
+ if(TUMOR_PASSIVE)
+ activity = TUMOR_ACTIVE
+ visible_message("[src] convulses as your arm enters its radius. Your instincts tell you to step back.")
+ activator = user
+ if(boosted)
+ mychild.playsound_local(get_turf(mychild), 'sound/effects/magic.ogg', 40, 0)
+ to_chat(mychild, "Someone has activated your tumor. You will be returned to fight shortly, get ready!")
+ addtimer(CALLBACK(src, .proc/return_elite), 30)
+ INVOKE_ASYNC(src, .proc/arena_checks)
+ if(TUMOR_INACTIVE)
+ activity = TUMOR_ACTIVE
+ var/mob/dead/observer/elitemind = null
+ visible_message("[src] begins to convulse. Your instincts tell you to step back.")
+ activator = user
+ if(!boosted)
+ addtimer(CALLBACK(src, .proc/spawn_elite), 30)
+ return
+ visible_message("Something within [src] stirs...")
+ var/list/candidates = pollCandidatesForMob("Do you want to play as a lavaland elite?", ROLE_SENTIENCE, null, ROLE_SENTIENCE, 50, src, POLL_IGNORE_SENTIENCE_POTION)
+ if(candidates.len)
+ audible_message("The stirring sounds increase in volume!")
+ elitemind = pick(candidates)
+ elitemind.playsound_local(get_turf(elitemind), 'sound/effects/magic.ogg', 40, 0)
+ to_chat(elitemind, "You have been chosen to play as a Lavaland Elite.\nIn a few seconds, you will be summoned on Lavaland as a monster to fight your activator, in a fight to the death.\nYour attacks can be switched using the buttons on the top left of the HUD, and used by clicking on targets or tiles similar to a gun.\nWhile the opponent might have an upper hand with powerful mining equipment and tools, you have great power normally limited by AI mobs.\nIf you want to win, you'll have to use your powers in creative ways to ensure the kill. It's suggested you try using them all as soon as possible.\nShould you win, you'll receive extra information regarding what to do after. Good luck!")
+ addtimer(CALLBACK(src, .proc/spawn_elite, elitemind), 100)
+ else
+ visible_message("The stirring stops, and nothing emerges. Perhaps try again later.")
+ activity = TUMOR_INACTIVE
+ activator = null
+
+
+obj/structure/elite_tumor/proc/spawn_elite(var/mob/dead/observer/elitemind)
+ var/selectedspawn = pick(potentialspawns)
+ mychild = new selectedspawn(loc)
+ visible_message("[mychild] emerges from [src]!")
+ playsound(loc,'sound/effects/phasein.ogg', 200, 0, 50, TRUE, TRUE)
+ if(boosted)
+ mychild.key = elitemind.key
+ mychild.sentience_act()
+ icon_state = "tumor_popped"
+ INVOKE_ASYNC(src, .proc/arena_checks)
+
+obj/structure/elite_tumor/proc/return_elite()
+ mychild.forceMove(loc)
+ visible_message("[mychild] emerges from [src]!")
+ playsound(loc,'sound/effects/phasein.ogg', 200, 0, 50, TRUE, TRUE)
+ mychild.revive(full_heal = TRUE, admin_revive = TRUE)
+ if(boosted)
+ mychild.maxHealth = mychild.maxHealth * 2
+ mychild.health = mychild.maxHealth
+
+/obj/structure/elite_tumor/Initialize(mapload)
+ . = ..()
+ internal = new/obj/item/gps/internal/elite(src)
+ START_PROCESSING(SSobj, src)
+
+/obj/structure/elite_tumor/Destroy()
+ STOP_PROCESSING(SSobj, src)
+ mychild = null
+ activator = null
+ return ..()
+
+/obj/structure/elite_tumor/process()
+ if(isturf(loc))
+ for(var/mob/living/simple_animal/hostile/asteroid/elite/elitehere in loc)
+ if(elitehere == mychild && activity == TUMOR_PASSIVE)
+ mychild.adjustHealth(-mychild.maxHealth*0.05)
+ var/obj/effect/temp_visual/heal/H = new /obj/effect/temp_visual/heal(get_turf(mychild))
+ H.color = "#FF0000"
+
+/obj/structure/elite_tumor/attackby(obj/item/I, mob/user, params)
+ . = ..()
+ if(istype(I, /obj/item/organ/regenerative_core) && activity == TUMOR_INACTIVE && !boosted)
+ var/obj/item/organ/regenerative_core/core = I
+ if(!core.preserved)
+ return
+ visible_message("As [user] drops the core into [src], [src] appears to swell.")
+ icon_state = "advanced_tumor"
+ boosted = TRUE
+ light_range = 6
+ desc = "[desc] This one seems to glow with a strong intensity."
+ qdel(core)
+ return TRUE
+
+/obj/structure/elite_tumor/proc/arena_checks()
+ if(activity != TUMOR_ACTIVE || QDELETED(src))
+ return
+ INVOKE_ASYNC(src, .proc/fighters_check) //Checks to see if our fighters died.
+ INVOKE_ASYNC(src, .proc/arena_trap) //Gets another arena trap queued up for when this one runs out.
+ INVOKE_ASYNC(src, .proc/border_check) //Checks to see if our fighters got out of the arena somehow.
+ addtimer(CALLBACK(src, .proc/arena_checks), 50)
+
+/obj/structure/elite_tumor/proc/fighters_check()
+ if(activator != null && activator.stat == DEAD || activity == TUMOR_ACTIVE && QDELETED(activator))
+ onEliteWon()
+ if(mychild != null && mychild.stat == DEAD || activity == TUMOR_ACTIVE && QDELETED(mychild))
+ onEliteLoss()
+
+/obj/structure/elite_tumor/proc/arena_trap()
+ var/turf/T = get_turf(src)
+ if(loc == null)
+ return
+ for(var/t in RANGE_TURFS(12, T))
+ if(get_dist(t, T) == 12)
+ var/obj/effect/temp_visual/elite_tumor_wall/newwall
+ newwall = new /obj/effect/temp_visual/elite_tumor_wall(t, src)
+ newwall.activator = src.activator
+ newwall.ourelite = src.mychild
+
+/obj/structure/elite_tumor/proc/border_check()
+ if(activator != null && get_dist(src, activator) >= 12)
+ activator.forceMove(loc)
+ visible_message("[activator] suddenly reappears above [src]!")
+ playsound(loc,'sound/effects/phasein.ogg', 200, 0, 50, TRUE, TRUE)
+ if(mychild != null && get_dist(src, mychild) >= 12)
+ mychild.forceMove(loc)
+ visible_message("[mychild] suddenly reappears above [src]!")
+ playsound(loc,'sound/effects/phasein.ogg', 200, 0, 50, TRUE, TRUE)
+
+obj/structure/elite_tumor/proc/onEliteLoss()
+ playsound(loc,'sound/effects/tendril_destroyed.ogg', 200, 0, 50, TRUE, TRUE)
+ visible_message("[src] begins to convulse violently before beginning to dissipate.")
+ visible_message("As [src] closes, something is forced up from down below.")
+ var/obj/structure/closet/crate/necropolis/tendril/lootbox = new /obj/structure/closet/crate/necropolis/tendril(loc)
+ if(!boosted)
+ mychild = null
+ activator = null
+ qdel(src)
+ return
+ var/lootpick = rand(1, 2)
+ if(lootpick == 1 && mychild.loot_drop != null)
+ new mychild.loot_drop(lootbox)
+ else
+ new /obj/item/tumor_shard(lootbox)
+ mychild = null
+ activator = null
+ qdel(src)
+
+obj/structure/elite_tumor/proc/onEliteWon()
+ activity = TUMOR_PASSIVE
+ activator = null
+ mychild.revive(full_heal = TRUE, admin_revive = TRUE)
+ if(boosted)
+ times_won++
+ mychild.maxHealth = mychild.maxHealth * 0.5
+ mychild.health = mychild.maxHealth
+ if(times_won == 1)
+ mychild.playsound_local(get_turf(mychild), 'sound/effects/magic.ogg', 40, 0)
+ to_chat(mychild, "As the life in the activator's eyes fade, the forcefield around you dies out and you feel your power subside.\nDespite this inferno being your home, you feel as if you aren't welcome here anymore.\nWithout any guidance, your purpose is now for you to decide.")
+ to_chat(mychild, "Your max health has been halved, but can now heal by standing on your tumor. Note, it's your only way to heal.\nBear in mind, if anyone interacts with your tumor, you'll be resummoned here to carry out another fight. In such a case, you will regain your full max health.\nAlso, be weary of your fellow inhabitants, they likely won't be happy to see you!")
+ to_chat(mychild, "Note that you are a lavaland monster, and thus not allied to the station. You should not cooperate or act friendly with any station crew unless under extreme circumstances!")
+
+/obj/item/tumor_shard
+ name = "tumor shard"
+ desc = "A strange, sharp, crystal shard from an odd tumor on Lavaland. Stabbing the corpse of a lavaland elite with this will revive them, assuming their soul still lingers. Revived lavaland elites only have half their max health, but are completely loyal to their reviver."
+ icon = 'icons/obj/lavaland/artefacts.dmi'
+ icon_state = "crevice_shard"
+ lefthand_file = 'icons/mob/inhands/equipment/tools_lefthand.dmi'
+ righthand_file = 'icons/mob/inhands/equipment/tools_righthand.dmi'
+ item_state = "screwdriver_head"
+ throwforce = 5
+ w_class = WEIGHT_CLASS_SMALL
+ throw_speed = 3
+ throw_range = 5
+
+/obj/item/tumor_shard/afterattack(atom/target, mob/user, proximity_flag)
+ . = ..()
+ if(istype(target, /mob/living/simple_animal/hostile/asteroid/elite) && proximity_flag)
+ var/mob/living/simple_animal/hostile/asteroid/elite/E = target
+ if(E.stat != DEAD || E.sentience_type != SENTIENCE_BOSS || !E.key)
+ user.visible_message("It appears [E] is unable to be revived right now. Perhaps try again later.")
+ return
+ E.faction = list("neutral")
+ E.revive(full_heal = TRUE, admin_revive = TRUE)
+ user.visible_message("[user] stabs [E] with [src], reviving it.")
+ E.playsound_local(get_turf(E), 'sound/effects/magic.ogg', 40, 0)
+ to_chat(E, "You have been revived by [user]. While you can't speak to them, you owe [user] a great debt. Assist [user.p_them()] in achieving [user.p_their()] goals, regardless of risk.Note that you now share the loyalties of [user]. You are expected not to intentionally sabotage their faction unless commanded to!")
+ E.maxHealth = E.maxHealth * 0.5
+ E.health = E.maxHealth
+ E.desc = "[E.desc] However, this one appears appears less wild in nature, and calmer around people."
+ E.sentience_type = SENTIENCE_ORGANIC
+ qdel(src)
+ else
+ to_chat(user, "[src] only works on the corpse of a sentient lavaland elite.")
+
+/obj/effect/temp_visual/elite_tumor_wall
+ name = "magic wall"
+ icon = 'icons/turf/walls/hierophant_wall_temp.dmi'
+ icon_state = "wall"
+ duration = 50
+ smooth = SMOOTH_TRUE
+ layer = BELOW_MOB_LAYER
+ var/mob/living/carbon/human/activator = null
+ var/mob/living/simple_animal/hostile/asteroid/elite/ourelite = null
+ color = rgb(255,0,0)
+ light_range = MINIMUM_USEFUL_LIGHT_RANGE
+ light_color = LIGHT_COLOR_RED
+
+/obj/effect/temp_visual/elite_tumor_wall/Initialize(mapload, new_caster)
+ . = ..()
+ queue_smooth_neighbors(src)
+ queue_smooth(src)
+
+/obj/effect/temp_visual/elite_tumor_wall/Destroy()
+ queue_smooth_neighbors(src)
+ activator = null
+ ourelite = null
+ return ..()
+
+/obj/effect/temp_visual/elite_tumor_wall/CanPass(atom/movable/mover, turf/target)
+ if(mover == ourelite || mover == activator)
+ return FALSE
+ else
+ return TRUE
diff --git a/code/modules/mob/living/simple_animal/hostile/mining_mobs/elites/goliath_broodmother.dm b/code/modules/mob/living/simple_animal/hostile/mining_mobs/elites/goliath_broodmother.dm
new file mode 100644
index 0000000000..c18a342206
--- /dev/null
+++ b/code/modules/mob/living/simple_animal/hostile/mining_mobs/elites/goliath_broodmother.dm
@@ -0,0 +1,247 @@
+#define TENTACLE_PATCH 1
+#define SPAWN_CHILDREN 2
+#define RAGE 3
+#define CALL_CHILDREN 4
+
+/**
+ * # Goliath Broodmother
+ *
+ * A stronger, faster variation of the goliath. Has the ability to spawn baby goliaths, which it can later detonate at will.
+ * When it's health is below half, tendrils will spawn randomly around it. When it is below a quarter of health, this effect is doubled.
+ * It's attacks are as follows:
+ * - Spawns a 3x3/plus shape of tentacles on the target location
+ * - Spawns 2 baby goliaths on its tile, up to a max of 8. Children blow up when they die.
+ * - The broodmother lets out a noise, and is able to move faster for 6.5 seconds.
+ * - Summons your children around you.
+ * The broodmother is a fight revolving around stage control, as the activator has to manage the baby goliaths and the broodmother herself, along with all the tendrils.
+ */
+
+/mob/living/simple_animal/hostile/asteroid/elite/broodmother
+ name = "goliath broodmother"
+ desc = "An example of sexual dimorphism, this female goliath looks much different than the males of her species. She is, however, just as dangerous, if not more."
+ gender = FEMALE
+ icon_state = "broodmother"
+ icon_living = "broodmother"
+ icon_aggro = "broodmother"
+ icon_dead = "egg_sac"
+ icon_gib = "syndicate_gib"
+ maxHealth = 800
+ health = 800
+ melee_damage_lower = 30
+ melee_damage_upper = 30
+ armour_penetration = 30
+ attacktext = "beats down on"
+ /*attack_verb_continuous = "beats down on"
+ attack_verb_simple = "beat down on"*/
+ attack_sound = 'sound/weapons/punch1.ogg'
+ throw_message = "does nothing to the rocky hide of the"
+ speed = 2
+ move_to_delay = 5
+ mob_biotypes = list(MOB_ORGANIC, MOB_BEAST)
+ mouse_opacity = MOUSE_OPACITY_ICON
+ deathmessage = "explodes into gore!"
+ loot_drop = /obj/item/crusher_trophy/broodmother_tongue
+
+ attack_action_types = list(/datum/action/innate/elite_attack/tentacle_patch,
+ /datum/action/innate/elite_attack/spawn_children,
+ /datum/action/innate/elite_attack/rage,
+ /datum/action/innate/elite_attack/call_children)
+
+ var/rand_tent = 0
+ var/list/mob/living/simple_animal/hostile/asteroid/elite/broodmother_child/children_list = list()
+
+/datum/action/innate/elite_attack/tentacle_patch
+ name = "Tentacle Patch"
+ button_icon_state = "tentacle_patch"
+ chosen_message = "You are now attacking with a patch of tentacles."
+ chosen_attack_num = TENTACLE_PATCH
+
+/datum/action/innate/elite_attack/spawn_children
+ name = "Spawn Children"
+ button_icon_state = "spawn_children"
+ chosen_message = "You will spawn two children at your location to assist you in combat. You can have up to 8."
+ chosen_attack_num = SPAWN_CHILDREN
+
+/datum/action/innate/elite_attack/rage
+ name = "Rage"
+ button_icon_state = "rage"
+ chosen_message = "You will temporarily increase your movement speed."
+ chosen_attack_num = RAGE
+
+/datum/action/innate/elite_attack/call_children
+ name = "Call Children"
+ button_icon_state = "call_children"
+ chosen_message = "You will summon your children to your location."
+ chosen_attack_num = CALL_CHILDREN
+
+/mob/living/simple_animal/hostile/asteroid/elite/broodmother/OpenFire()
+ if(client)
+ switch(chosen_attack)
+ if(TENTACLE_PATCH)
+ tentacle_patch(target)
+ if(SPAWN_CHILDREN)
+ spawn_children()
+ if(RAGE)
+ rage()
+ if(CALL_CHILDREN)
+ call_children()
+ return
+ var/aiattack = rand(1,4)
+ switch(aiattack)
+ if(TENTACLE_PATCH)
+ tentacle_patch(target)
+ if(SPAWN_CHILDREN)
+ spawn_children()
+ if(RAGE)
+ rage()
+ if(CALL_CHILDREN)
+ call_children()
+
+/mob/living/simple_animal/hostile/asteroid/elite/broodmother/Life()
+ . = ..()
+ if(!.) //Checks if they are dead as a rock.
+ return
+ if(health < maxHealth * 0.5 && rand_tent < world.time)
+ rand_tent = world.time + 30
+ var/tentacle_amount = 5
+ if(health < maxHealth * 0.25)
+ tentacle_amount = 10
+ var/tentacle_loc = spiral_range_turfs(5, get_turf(src))
+ for(var/i in 1 to tentacle_amount)
+ var/turf/t = pick_n_take(tentacle_loc)
+ new /obj/effect/temp_visual/goliath_tentacle/broodmother(t, src)
+
+/mob/living/simple_animal/hostile/asteroid/elite/broodmother/proc/tentacle_patch(var/target)
+ ranged_cooldown = world.time + 15
+ var/tturf = get_turf(target)
+ if(!isturf(tturf))
+ return
+ visible_message("[src] digs its tentacles under [target]!")
+ new /obj/effect/temp_visual/goliath_tentacle/broodmother/patch(tturf, src)
+
+/mob/living/simple_animal/hostile/asteroid/elite/broodmother/proc/spawn_children(var/target)
+ ranged_cooldown = world.time + 40
+ visible_message("The ground churns behind [src]!")
+ for(var/i in 1 to 2)
+ if(children_list.len >= 8)
+ return
+ var/mob/living/simple_animal/hostile/asteroid/elite/broodmother_child/newchild = new /mob/living/simple_animal/hostile/asteroid/elite/broodmother_child(loc)
+ newchild.GiveTarget(target)
+ newchild.faction = faction.Copy()
+ visible_message("[newchild] appears below [src]!")
+ newchild.mother = src
+ children_list += newchild
+
+/mob/living/simple_animal/hostile/asteroid/elite/broodmother/proc/rage()
+ ranged_cooldown = world.time + 70
+ playsound(src,'sound/spookoween/insane_low_laugh.ogg', 200, 1)
+ visible_message("[src] starts picking up speed!")
+ color = "#FF0000"
+ set_varspeed(0)
+ move_to_delay = 3
+ addtimer(CALLBACK(src, .proc/reset_rage), 65)
+
+/mob/living/simple_animal/hostile/asteroid/elite/broodmother/proc/reset_rage()
+ color = "#FFFFFF"
+ set_varspeed(2)
+ move_to_delay = 5
+
+/mob/living/simple_animal/hostile/asteroid/elite/broodmother/proc/call_children()
+ ranged_cooldown = world.time + 60
+ visible_message("The ground shakes near [src]!")
+ var/list/directions = GLOB.cardinals.Copy() + GLOB.diagonals.Copy()
+ for(var/mob/child in children_list)
+ var/spawndir = pick_n_take(directions)
+ var/turf/T = get_step(src, spawndir)
+ if(T)
+ child.forceMove(T)
+ playsound(src, 'sound/effects/bamf.ogg', 100, 1)
+
+//The goliath's children. Pretty weak, simple mobs which are able to put a single tentacle under their target when at range.
+/mob/living/simple_animal/hostile/asteroid/elite/broodmother_child
+ name = "baby goliath"
+ desc = "A young goliath recently born from it's mother. While they hatch from eggs, said eggs are incubated in the mother until they are ready to be born."
+ icon = 'icons/mob/lavaland/lavaland_monsters.dmi'
+ icon_state = "goliath_baby"
+ icon_living = "goliath_baby"
+ icon_aggro = "goliath_baby"
+ icon_dead = "goliath_baby_dead"
+ icon_gib = "syndicate_gib"
+ maxHealth = 30
+ health = 30
+ melee_damage_lower = 5
+ melee_damage_upper = 5
+ attacktext = "bashes against"
+ /*attack_verb_continuous = "bashes against"
+ attack_verb_simple = "bash against"*/
+ attack_sound = 'sound/weapons/punch1.ogg'
+ throw_message = "does nothing to the rocky hide of the"
+ speed = 2
+ move_to_delay = 5
+ mob_biotypes = list(MOB_ORGANIC, MOB_BEAST)
+ mouse_opacity = MOUSE_OPACITY_ICON
+ butcher_results = list()
+ guaranteed_butcher_results = list(/obj/item/stack/sheet/animalhide/goliath_hide = 1)
+ deathmessage = "falls to the ground."
+ status_flags = CANPUSH
+ var/mob/living/simple_animal/hostile/asteroid/elite/broodmother/mother = null
+
+/mob/living/simple_animal/hostile/asteroid/elite/broodmother_child/OpenFire(target)
+ ranged_cooldown = world.time + 40
+ var/tturf = get_turf(target)
+ if(!isturf(tturf))
+ return
+ if(get_dist(src, target) <= 7)//Screen range check, so it can't attack people off-screen
+ visible_message("[src] digs one of its tentacles under [target]!")
+ new /obj/effect/temp_visual/goliath_tentacle/broodmother(tturf, src)
+
+/mob/living/simple_animal/hostile/asteroid/elite/broodmother_child/death()
+ . = ..()
+ if(mother != null)
+ mother.children_list -= src
+ visible_message("[src] explodes!")
+ explosion(get_turf(loc),0,0,0,flame_range = 3, adminlog = FALSE)
+ gib()
+
+//Tentacles have less stun time compared to regular variant, to balance being able to use them much more often. Also, 10 more damage.
+/obj/effect/temp_visual/goliath_tentacle/broodmother/trip()
+ var/latched = FALSE
+ for(var/mob/living/L in loc)
+ if((!QDELETED(spawner) && spawner.faction_check_mob(L)) || L.stat == DEAD)
+ continue
+ visible_message("[src] grabs hold of [L]!")
+ L.Stun(10)
+ L.adjustBruteLoss(rand(30,35))
+ latched = TRUE
+ if(!latched)
+ retract()
+ else
+ deltimer(timerid)
+ timerid = addtimer(CALLBACK(src, .proc/retract), 10, TIMER_STOPPABLE)
+
+/obj/effect/temp_visual/goliath_tentacle/broodmother/patch/Initialize(mapload, new_spawner)
+ . = ..()
+ var/tentacle_locs = spiral_range_turfs(1, get_turf(src))
+ for(var/T in tentacle_locs)
+ new /obj/effect/temp_visual/goliath_tentacle/broodmother(T, spawner)
+ var/list/directions = GLOB.cardinals.Copy()
+ for(var/i in directions)
+ var/turf/T = get_step(get_turf(src), i)
+ T = get_step(T, i)
+ new /obj/effect/temp_visual/goliath_tentacle/broodmother(T, spawner)
+
+// Broodmother's loot: Broodmother Tongue
+/obj/item/crusher_trophy/broodmother_tongue
+ name = "broodmother tongue"
+ desc = "The tongue of a broodmother. If attached a certain way, makes for a suitable crusher trophy."
+ icon = 'icons/obj/lavaland/elite_trophies.dmi'
+ icon_state = "broodmother_tongue"
+ denied_type = /obj/item/crusher_trophy/broodmother_tongue
+ bonus_value = 10
+
+/obj/item/crusher_trophy/broodmother_tongue/effect_desc()
+ return "mark detonation to have a [bonus_value]% chance to summon a patch of goliath tentacles at the target's location"
+
+/obj/item/crusher_trophy/broodmother_tongue/on_mark_detonation(mob/living/target, mob/living/user)
+ if(rand(1, 100) <= bonus_value && target.stat != DEAD)
+ new /obj/effect/temp_visual/goliath_tentacle/broodmother/patch(get_turf(target), user)
diff --git a/code/modules/mob/living/simple_animal/hostile/mining_mobs/elites/herald.dm b/code/modules/mob/living/simple_animal/hostile/mining_mobs/elites/herald.dm
new file mode 100644
index 0000000000..f1e7494beb
--- /dev/null
+++ b/code/modules/mob/living/simple_animal/hostile/mining_mobs/elites/herald.dm
@@ -0,0 +1,275 @@
+#define HERALD_TRISHOT 1
+#define HERALD_DIRECTIONALSHOT 2
+#define HERALD_TELESHOT 3
+#define HERALD_MIRROR 4
+
+/**
+ * # Herald
+ *
+ * A slow-moving projectile user with a few tricks up it's sleeve. Less unga-bunga than Colossus, with more cleverness in it's fighting style.
+ * As it's health gets lower, the amount of projectiles fired per-attack increases.
+ * It's attacks are as follows:
+ * - Fires three projectiles in a a given direction.
+ * - Fires a spread in every cardinal and diagonal direction at once, then does it again after a bit.
+ * - Shoots a single, golden bolt. Wherever it lands, the herald will be teleported to the location.
+ * - Spawns a mirror which reflects projectiles directly at the target.
+ * Herald is a more concentrated variation of the Colossus fight, having less projectiles overall, but more focused attacks.
+ */
+
+/mob/living/simple_animal/hostile/asteroid/elite/herald
+ name = "herald"
+ desc = "A monstrous beast which fires deadly projectiles at threats and prey."
+ icon_state = "herald"
+ icon_living = "herald"
+ icon_aggro = "herald"
+ icon_dead = "herald_dying"
+ icon_gib = "syndicate_gib"
+ maxHealth = 800
+ health = 800
+ melee_damage_lower = 20
+ melee_damage_upper = 20
+ attacktext = "preaches to"
+ /*attack_verb_continuous = "preaches to"
+ attack_verb_simple = "preach to"*/
+ attack_sound = 'sound/magic/clockwork/ratvar_attack.ogg'
+ throw_message = "doesn't affect the purity of"
+ speed = 4
+ move_to_delay = 10
+ mouse_opacity = MOUSE_OPACITY_ICON
+ deathsound = 'sound/magic/demon_dies.ogg'
+ deathmessage = "begins to shudder as it becomes transparent..."
+ loot_drop = /obj/item/clothing/neck/cloak/herald_cloak
+
+ can_talk = 1
+
+ attack_action_types = list(/datum/action/innate/elite_attack/herald_trishot,
+ /datum/action/innate/elite_attack/herald_directionalshot,
+ /datum/action/innate/elite_attack/herald_teleshot,
+ /datum/action/innate/elite_attack/herald_mirror)
+
+ var/mob/living/simple_animal/hostile/asteroid/elite/herald/mirror/my_mirror = null
+ var/is_mirror = FALSE
+
+/mob/living/simple_animal/hostile/asteroid/elite/herald/death()
+ . = ..()
+ if(!is_mirror)
+ addtimer(CALLBACK(src, .proc/become_ghost), 8)
+ if(my_mirror != null)
+ qdel(my_mirror)
+
+/mob/living/simple_animal/hostile/asteroid/elite/herald/proc/become_ghost()
+ icon_state = "herald_ghost"
+
+/mob/living/simple_animal/hostile/asteroid/elite/herald/say(message, bubble_type, var/list/spans = list(), sanitize = TRUE, datum/language/language = null, ignore_spam = FALSE, forced = null)
+ . = ..()
+ playsound(get_turf(src), 'sound/magic/clockwork/invoke_general.ogg', 20, TRUE)
+
+/datum/action/innate/elite_attack/herald_trishot
+ name = "Triple Shot"
+ button_icon_state = "herald_trishot"
+ chosen_message = "You are now firing three shots in your chosen direction."
+ chosen_attack_num = HERALD_TRISHOT
+
+/datum/action/innate/elite_attack/herald_directionalshot
+ name = "Circular Shot"
+ button_icon_state = "herald_directionalshot"
+ chosen_message = "You are firing projectiles in all directions."
+ chosen_attack_num = HERALD_DIRECTIONALSHOT
+
+/datum/action/innate/elite_attack/herald_teleshot
+ name = "Teleport Shot"
+ button_icon_state = "herald_teleshot"
+ chosen_message = "You will now fire a shot which teleports you where it lands."
+ chosen_attack_num = HERALD_TELESHOT
+
+/datum/action/innate/elite_attack/herald_mirror
+ name = "Summon Mirror"
+ button_icon_state = "herald_mirror"
+ chosen_message = "You will spawn a mirror which duplicates your attacks."
+ chosen_attack_num = HERALD_MIRROR
+
+/mob/living/simple_animal/hostile/asteroid/elite/herald/OpenFire()
+ if(client)
+ switch(chosen_attack)
+ if(HERALD_TRISHOT)
+ herald_trishot(target)
+ if(my_mirror != null)
+ my_mirror.herald_trishot(target)
+ if(HERALD_DIRECTIONALSHOT)
+ herald_directionalshot()
+ if(my_mirror != null)
+ my_mirror.herald_directionalshot()
+ if(HERALD_TELESHOT)
+ herald_teleshot(target)
+ if(my_mirror != null)
+ my_mirror.herald_teleshot(target)
+ if(HERALD_MIRROR)
+ herald_mirror()
+ return
+ var/aiattack = rand(1,4)
+ switch(aiattack)
+ if(HERALD_TRISHOT)
+ herald_trishot(target)
+ if(my_mirror != null)
+ my_mirror.herald_trishot(target)
+ if(HERALD_DIRECTIONALSHOT)
+ herald_directionalshot()
+ if(my_mirror != null)
+ my_mirror.herald_directionalshot()
+ if(HERALD_TELESHOT)
+ herald_teleshot(target)
+ if(my_mirror != null)
+ my_mirror.herald_teleshot(target)
+ if(HERALD_MIRROR)
+ herald_mirror()
+
+/mob/living/simple_animal/hostile/asteroid/elite/herald/proc/shoot_projectile(turf/marker, set_angle, var/is_teleshot)
+ var/turf/startloc = get_turf(src)
+ var/obj/item/projectile/herald/H = null
+ if(!is_teleshot)
+ H = new /obj/item/projectile/herald(startloc)
+ else
+ H = new /obj/item/projectile/herald/teleshot(startloc)
+ H.preparePixelProjectile(marker, startloc)
+ H.firer = src
+ if(target)
+ H.original = target
+ H.fire(set_angle)
+
+/mob/living/simple_animal/hostile/asteroid/elite/herald/proc/herald_trishot(target)
+ ranged_cooldown = world.time + 30
+ playsound(get_turf(src), 'sound/magic/clockwork/invoke_general.ogg', 20, TRUE)
+ var/target_turf = get_turf(target)
+ var/angle_to_target = Get_Angle(src, target_turf)
+ shoot_projectile(target_turf, angle_to_target, FALSE)
+ addtimer(CALLBACK(src, .proc/shoot_projectile, target_turf, angle_to_target, FALSE), 2)
+ addtimer(CALLBACK(src, .proc/shoot_projectile, target_turf, angle_to_target, FALSE), 4)
+ if(health < maxHealth * 0.5)
+ playsound(get_turf(src), 'sound/magic/clockwork/invoke_general.ogg', 20, TRUE)
+ addtimer(CALLBACK(src, .proc/shoot_projectile, target_turf, angle_to_target, FALSE), 10)
+ addtimer(CALLBACK(src, .proc/shoot_projectile, target_turf, angle_to_target, FALSE), 12)
+ addtimer(CALLBACK(src, .proc/shoot_projectile, target_turf, angle_to_target, FALSE), 14)
+
+/mob/living/simple_animal/hostile/asteroid/elite/herald/proc/herald_circleshot()
+ var/static/list/directional_shot_angles = list(0, 45, 90, 135, 180, 225, 270, 315)
+ for(var/i in directional_shot_angles)
+ shoot_projectile(get_turf(src), i, FALSE)
+
+/mob/living/simple_animal/hostile/asteroid/elite/herald/proc/unenrage()
+ if(stat == DEAD || is_mirror)
+ return
+ icon_state = "herald"
+
+/mob/living/simple_animal/hostile/asteroid/elite/herald/proc/herald_directionalshot()
+ ranged_cooldown = world.time + 50
+ if(!is_mirror)
+ icon_state = "herald_enraged"
+ playsound(get_turf(src), 'sound/magic/clockwork/invoke_general.ogg', 20, TRUE)
+ addtimer(CALLBACK(src, .proc/herald_circleshot), 5)
+ if(health < maxHealth * 0.5)
+ playsound(get_turf(src), 'sound/magic/clockwork/invoke_general.ogg', 20, TRUE)
+ addtimer(CALLBACK(src, .proc/herald_circleshot), 15)
+ addtimer(CALLBACK(src, .proc/unenrage), 20)
+
+/mob/living/simple_animal/hostile/asteroid/elite/herald/proc/herald_teleshot(target)
+ ranged_cooldown = world.time + 30
+ playsound(get_turf(src), 'sound/magic/clockwork/invoke_general.ogg', 20, TRUE)
+ var/target_turf = get_turf(target)
+ var/angle_to_target = Get_Angle(src, target_turf)
+ shoot_projectile(target_turf, angle_to_target, TRUE)
+
+/mob/living/simple_animal/hostile/asteroid/elite/herald/proc/herald_mirror()
+ ranged_cooldown = world.time + 40
+ playsound(get_turf(src), 'sound/magic/clockwork/invoke_general.ogg', 20, TRUE)
+ if(my_mirror != null)
+ qdel(my_mirror)
+ my_mirror = null
+ var/mob/living/simple_animal/hostile/asteroid/elite/herald/mirror/new_mirror = new /mob/living/simple_animal/hostile/asteroid/elite/herald/mirror(loc)
+ my_mirror = new_mirror
+ my_mirror.my_master = src
+ my_mirror.faction = faction.Copy()
+
+/mob/living/simple_animal/hostile/asteroid/elite/herald/mirror
+ name = "herald's mirror"
+ desc = "This fiendish work of magic copies the herald's attacks. Seems logical to smash it."
+ health = 60
+ maxHealth = 60
+ icon_state = "herald_mirror"
+ deathmessage = "shatters violently!"
+ deathsound = 'sound/effects/glassbr1.ogg'
+ movement_type = FLYING
+ del_on_death = TRUE
+ is_mirror = TRUE
+ var/mob/living/simple_animal/hostile/asteroid/elite/herald/my_master = null
+
+/mob/living/simple_animal/hostile/asteroid/elite/herald/mirror/Initialize()
+ ..()
+ toggle_ai(AI_OFF)
+
+/mob/living/simple_animal/hostile/asteroid/elite/herald/mirror/Destroy()
+ if(my_master != null)
+ my_master.my_mirror = null
+ . = ..()
+
+/obj/item/projectile/herald
+ name ="death bolt"
+ icon_state= "chronobolt"
+ damage = 15
+ armour_penetration = 60
+ speed = 2
+ eyeblur = 0
+ damage_type = BRUTE
+ pass_flags = PASSTABLE
+
+/obj/item/projectile/herald/teleshot
+ name ="golden bolt"
+ damage = 0
+ color = rgb(255,255,102)
+
+/obj/item/projectile/herald/on_hit(atom/target, blocked = FALSE)
+ . = ..()
+ if(ismineralturf(target))
+ var/turf/closed/mineral/M = target
+ M.gets_drilled()
+ return
+ else if(isliving(target))
+ var/mob/living/L = target
+ var/mob/living/F = firer
+ if(F != null && istype(F, /mob/living/simple_animal/hostile/asteroid/elite) && F.faction_check_mob(L))
+ L.heal_overall_damage(damage)
+
+/obj/item/projectile/herald/teleshot/on_hit(atom/target, blocked = FALSE)
+ . = ..()
+ firer.forceMove(get_turf(src))
+
+//Herald's loot: Cloak of the Prophet
+
+/obj/item/clothing/neck/cloak/herald_cloak
+ name = "cloak of the prophet"
+ desc = "A cloak which protects you from the heresy of the world."
+ icon = 'icons/obj/lavaland/elite_trophies.dmi'
+ icon_state = "herald_cloak"
+ body_parts_covered = CHEST|GROIN|ARMS
+ hit_reaction_chance = 10
+
+/obj/item/clothing/neck/cloak/herald_cloak/proc/reactionshot(mob/living/carbon/owner)
+ var/static/list/directional_shot_angles = list(0, 45, 90, 135, 180, 225, 270, 315)
+ for(var/i in directional_shot_angles)
+ shoot_projectile(get_turf(owner), i, owner)
+
+/obj/item/clothing/neck/cloak/herald_cloak/proc/shoot_projectile(turf/marker, set_angle, mob/living/carbon/owner)
+ var/turf/startloc = get_turf(owner)
+ var/obj/item/projectile/herald/H = null
+ H = new /obj/item/projectile/herald(startloc)
+ H.preparePixelProjectile(marker, startloc)
+ H.firer = owner
+ H.fire(set_angle)
+
+/obj/item/clothing/neck/cloak/herald_cloak/hit_reaction(mob/living/carbon/human/owner, atom/movable/hitby, attack_text = "the attack", final_block_chance = 0, damage = 0, attack_type = MELEE_ATTACK)
+ . = ..()
+ if(rand(1,100) > hit_reaction_chance)
+ return
+ owner.visible_message("[owner]'s [src] emits a loud noise as [owner] is struck!")
+ var/static/list/directional_shot_angles = list(0, 45, 90, 135, 180, 225, 270, 315)
+ playsound(get_turf(owner), 'sound/magic/clockwork/invoke_general.ogg', 20, TRUE)
+ addtimer(CALLBACK(src, .proc/reactionshot, owner), 10)
diff --git a/code/modules/mob/living/simple_animal/hostile/mining_mobs/elites/legionnaire.dm b/code/modules/mob/living/simple_animal/hostile/mining_mobs/elites/legionnaire.dm
new file mode 100644
index 0000000000..1bc9ea1e4e
--- /dev/null
+++ b/code/modules/mob/living/simple_animal/hostile/mining_mobs/elites/legionnaire.dm
@@ -0,0 +1,303 @@
+#define LEGIONNAIRE_CHARGE 1
+#define HEAD_DETACH 2
+#define BONFIRE_TELEPORT 3
+#define SPEW_SMOKE 4
+
+/**
+ * # Legionnaire
+ *
+ * A towering skeleton, embodying the power of Legion.
+ * As it's health gets lower, the head does more damage.
+ * It's attacks are as follows:
+ * - Charges at the target after a telegraph, throwing them across the arena should it connect.
+ * - Legionnaire's head detaches, attacking as it's own entity. Has abilities of it's own later into the fight. Once dead, regenerates after a brief period. If the skill is used while the head is off, it will be killed.
+ * - Leaves a pile of bones at your location. Upon using this skill again, you'll swap locations with the bone pile.
+ * - Spews a cloud of smoke from it's maw, wherever said maw is.
+ * A unique fight incorporating the head mechanic of legion into a whole new beast. Combatants will need to make sure the tag-team of head and body don't lure them into a deadly trap.
+ */
+
+/mob/living/simple_animal/hostile/asteroid/elite/legionnaire
+ name = "legionnaire"
+ desc = "A towering skeleton, embodying the terrifying power of Legion."
+ icon_state = "legionnaire"
+ icon_living = "legionnaire"
+ icon_aggro = "legionnaire"
+ icon_dead = "legionnaire_dead"
+ icon_gib = "syndicate_gib"
+ maxHealth = 800
+ health = 800
+ melee_damage_lower = 30
+ melee_damage_upper = 30
+ attacktext = "slashes its arms at"
+ /*attack_verb_continuous = "slashes its arms at"
+ attack_verb_simple = "slash your arms at"*/
+ attack_sound = 'sound/weapons/bladeslice.ogg'
+ throw_message = "doesn't affect the sturdiness of"
+ speed = 1
+ move_to_delay = 3
+ mouse_opacity = MOUSE_OPACITY_ICON
+ deathsound = 'sound/magic/curse.ogg'
+ deathmessage = "'s arms reach out before it falls apart onto the floor, lifeless."
+ loot_drop = /obj/item/crusher_trophy/legionnaire_spine
+
+ attack_action_types = list(/datum/action/innate/elite_attack/legionnaire_charge,
+ /datum/action/innate/elite_attack/head_detach,
+ /datum/action/innate/elite_attack/bonfire_teleport,
+ /datum/action/innate/elite_attack/spew_smoke)
+
+ var/mob/living/simple_animal/hostile/asteroid/elite/legionnairehead/myhead = null
+ var/obj/structure/legionnaire_bonfire/mypile = null
+ var/has_head = TRUE
+
+/datum/action/innate/elite_attack/legionnaire_charge
+ name = "Legionnaire Charge"
+ button_icon_state = "legionnaire_charge"
+ chosen_message = "You will attempt to grab your opponent and throw them."
+ chosen_attack_num = LEGIONNAIRE_CHARGE
+
+/datum/action/innate/elite_attack/head_detach
+ name = "Release Head"
+ button_icon_state = "head_detach"
+ chosen_message = "You will now detach your head or kill it if it is already released."
+ chosen_attack_num = HEAD_DETACH
+
+/datum/action/innate/elite_attack/bonfire_teleport
+ name = "Bonfire Teleport"
+ button_icon_state = "bonfire_teleport"
+ chosen_message = "You will leave a bonfire. Second use will let you swap positions with it indefintiely. Using this move on the same tile as your active bonfire removes it."
+ chosen_attack_num = BONFIRE_TELEPORT
+
+/datum/action/innate/elite_attack/spew_smoke
+ name = "Spew Smoke"
+ button_icon_state = "spew_smoke"
+ chosen_message = "Your head will spew smoke in an area, wherever it may be."
+ chosen_attack_num = SPEW_SMOKE
+
+/mob/living/simple_animal/hostile/asteroid/elite/legionnaire/OpenFire()
+ if(client)
+ switch(chosen_attack)
+ if(LEGIONNAIRE_CHARGE)
+ legionnaire_charge(target)
+ if(HEAD_DETACH)
+ head_detach(target)
+ if(BONFIRE_TELEPORT)
+ bonfire_teleport()
+ if(SPEW_SMOKE)
+ spew_smoke()
+ return
+ var/aiattack = rand(1,4)
+ switch(aiattack)
+ if(LEGIONNAIRE_CHARGE)
+ legionnaire_charge(target)
+ if(HEAD_DETACH)
+ head_detach(target)
+ if(BONFIRE_TELEPORT)
+ bonfire_teleport()
+ if(SPEW_SMOKE)
+ spew_smoke()
+
+/mob/living/simple_animal/hostile/asteroid/elite/legionnaire/proc/legionnaire_charge(target)
+ ranged_cooldown = world.time + 50
+ var/dir_to_target = get_dir(get_turf(src), get_turf(target))
+ var/turf/T = get_step(get_turf(src), dir_to_target)
+ for(var/i in 1 to 4)
+ new /obj/effect/temp_visual/dragon_swoop/legionnaire(T)
+ T = get_step(T, dir_to_target)
+ playsound(src,'sound/magic/demon_attack1.ogg', 200, 1)
+ visible_message("[src] prepares to charge!")
+ addtimer(CALLBACK(src, .proc/legionnaire_charge_2, dir_to_target, 0), 5)
+
+/mob/living/simple_animal/hostile/asteroid/elite/legionnaire/proc/legionnaire_charge_2(var/move_dir, var/times_ran)
+ if(times_ran >= 4)
+ return
+ var/turf/T = get_step(get_turf(src), move_dir)
+ if(ismineralturf(T))
+ var/turf/closed/mineral/M = T
+ M.gets_drilled()
+ if(T.density)
+ return
+ for(var/obj/structure/window/W in T.contents)
+ return
+ for(var/obj/machinery/door/D in T.contents)
+ return
+ forceMove(T)
+ playsound(src,'sound/effects/bang.ogg', 200, 1)
+ var/list/hit_things = list()
+ var/throwtarget = get_edge_target_turf(src, move_dir)
+ for(var/mob/living/L in T.contents - hit_things - src)
+ if(faction_check_mob(L))
+ return
+ hit_things += L
+ visible_message("[src] attacks [L] with much force!")
+ to_chat(L, "[src] grabs you and throws you with much force!")
+ L.safe_throw_at(throwtarget, 10, 1, src)
+ //L.Paralyze(20)
+ L.Stun(20) //substituting this for the Paralyze from the line above, because we don't have tg paralysis stuff
+ L.adjustBruteLoss(50)
+ addtimer(CALLBACK(src, .proc/legionnaire_charge_2, move_dir, (times_ran + 1)), 2)
+
+/mob/living/simple_animal/hostile/asteroid/elite/legionnaire/proc/head_detach(target)
+ ranged_cooldown = world.time + 10
+ if(myhead != null)
+ myhead.adjustBruteLoss(600)
+ return
+ if(has_head)
+ has_head = FALSE
+ icon_state = "legionnaire_headless"
+ icon_living = "legionnaire_headless"
+ icon_aggro = "legionnaire_headless"
+ visible_message("[src]'s head flies off!")
+ var/mob/living/simple_animal/hostile/asteroid/elite/legionnairehead/newhead = new /mob/living/simple_animal/hostile/asteroid/elite/legionnairehead(loc)
+ newhead.flags_1 |= (flags_1 & ADMIN_SPAWNED_1)
+ newhead.GiveTarget(target)
+ newhead.faction = faction.Copy()
+ myhead = newhead
+ myhead.body = src
+ if(health < maxHealth * 0.25)
+ myhead.melee_damage_lower = 30
+ myhead.melee_damage_upper = 30
+ else if(health < maxHealth * 0.5)
+ myhead.melee_damage_lower = 20
+ myhead.melee_damage_upper = 20
+
+/mob/living/simple_animal/hostile/asteroid/elite/legionnaire/proc/onHeadDeath()
+ myhead = null
+ addtimer(CALLBACK(src, .proc/regain_head), 50)
+
+/mob/living/simple_animal/hostile/asteroid/elite/legionnaire/proc/regain_head()
+ has_head = TRUE
+ if(stat == DEAD)
+ return
+ icon_state = "legionnaire"
+ icon_living = "legionnaire"
+ icon_aggro = "legionnaire"
+ visible_message("The top of [src]'s spine leaks a black liquid, forming into a skull!")
+
+/mob/living/simple_animal/hostile/asteroid/elite/legionnaire/proc/bonfire_teleport()
+ ranged_cooldown = world.time + 5
+ if(mypile == null)
+ var/obj/structure/legionnaire_bonfire/newpile = new /obj/structure/legionnaire_bonfire(loc)
+ mypile = newpile
+ mypile.myowner = src
+ playsound(get_turf(src),'sound/items/fulext_deploy.wav', 200, 1)
+ visible_message("[src] summons a bonfire on [get_turf(src)]!")
+ return
+ else
+ var/turf/legionturf = get_turf(src)
+ var/turf/pileturf = get_turf(mypile)
+ if(legionturf == pileturf)
+ mypile.take_damage(100)
+ mypile = null
+ return
+ playsound(pileturf,'sound/items/fulext_deploy.wav', 200, 1)
+ playsound(legionturf,'sound/items/fulext_deploy.wav', 200, 1)
+ visible_message("[src] melts down into a burning pile of bones!")
+ forceMove(pileturf)
+ visible_message("[src] forms from the bonfire!")
+ mypile.forceMove(legionturf)
+
+/mob/living/simple_animal/hostile/asteroid/elite/legionnaire/proc/spew_smoke()
+ ranged_cooldown = world.time + 60
+ var/turf/T = null
+ if(myhead != null)
+ T = get_turf(myhead)
+ else
+ T = get_turf(src)
+ if(myhead != null)
+ myhead.visible_message("[myhead] spews smoke from its maw!")
+ else if(!has_head)
+ visible_message("[src] spews smoke from the tip of their spine!")
+ else
+ visible_message("[src] spews smoke from its maw!")
+ var/datum/effect_system/smoke_spread/smoke = new
+ smoke.set_up(2, T)
+ smoke.start()
+
+//The legionnaire's head. Basically the same as any legion head, but we have to tell our creator when we die so they can generate another head.
+/mob/living/simple_animal/hostile/asteroid/elite/legionnairehead
+ name = "legionnaire head"
+ desc = "The legionnaire's head floating by itself. One shouldn't get too close, though once it sees you, you really don't have a choice."
+ icon_state = "legionnaire_head"
+ icon_living = "legionnaire_head"
+ icon_aggro = "legionnaire_head"
+ icon_dead = "legionnaire_dead"
+ icon_gib = "syndicate_gib"
+ maxHealth = 80
+ health = 80
+ melee_damage_lower = 10
+ melee_damage_upper = 10
+ attacktext = "bites at"
+ /*attack_verb_continuous = "bites at"
+ attack_verb_simple = "bite at"*/
+ attack_sound = 'sound/effects/curse1.ogg'
+ throw_message = "simply misses"
+ speed = 0
+ move_to_delay = 2
+ del_on_death = 1
+ deathmessage = "crumbles away!"
+ faction = list()
+ ranged = FALSE
+ var/mob/living/simple_animal/hostile/asteroid/elite/legionnaire/body = null
+
+/mob/living/simple_animal/hostile/asteroid/elite/legionnairehead/death()
+ . = ..()
+ if(body)
+ body.onHeadDeath()
+
+//The legionnaire's bonfire, which can be swapped positions with. Also sets flammable living beings on fire when they walk over it.
+/obj/structure/legionnaire_bonfire
+ name = "bone pile"
+ desc = "A pile of bones which seems to occasionally move a little. It's probably a good idea to smash them."
+ icon = 'icons/obj/lavaland/legionnaire_bonfire.dmi'
+ icon_state = "bonfire"
+ max_integrity = 100
+ //move_resist = MOVE_FORCE_EXTREMELY_STRONG
+ anchored = TRUE
+ density = FALSE
+ light_range = 4
+ light_color = LIGHT_COLOR_RED
+ var/mob/living/simple_animal/hostile/asteroid/elite/legionnaire/myowner = null
+
+
+/obj/structure/legionnaire_bonfire/Entered(atom/movable/mover, turf/target)
+ if(isliving(mover))
+ var/mob/living/L = mover
+ L.adjust_fire_stacks(3)
+ L.IgniteMob()
+ . = ..()
+
+/obj/structure/legionnaire_bonfire/Destroy()
+ if(myowner != null)
+ myowner.mypile = null
+ . = ..()
+
+//The visual effect which appears in front of legionnaire when he goes to charge.
+/obj/effect/temp_visual/dragon_swoop/legionnaire
+ duration = 10
+ color = rgb(0,0,0)
+
+/obj/effect/temp_visual/dragon_swoop/legionnaire/Initialize()
+ . = ..()
+ transform *= 0.33
+
+// Legionnaire's loot: Legionnaire Spine
+
+/obj/item/crusher_trophy/legionnaire_spine
+ name = "legionnaire spine"
+ desc = "The spine of a legionnaire. It almost feels like it's moving..."
+ icon = 'icons/obj/lavaland/elite_trophies.dmi'
+ icon_state = "legionnaire_spine"
+ denied_type = /obj/item/crusher_trophy/legionnaire_spine
+ bonus_value = 20
+
+/obj/item/crusher_trophy/legionnaire_spine/effect_desc()
+ return "mark detonation to have a [bonus_value]% chance to summon a loyal legion skull"
+
+/obj/item/crusher_trophy/legionnaire_spine/on_mark_detonation(mob/living/target, mob/living/user)
+ if(!rand(1, 100) <= bonus_value || target.stat == DEAD)
+ return
+ var/mob/living/simple_animal/hostile/asteroid/hivelordbrood/legion/A = new /mob/living/simple_animal/hostile/asteroid/hivelordbrood/legion(user.loc)
+ A.flags_1 |= (flags_1 & ADMIN_SPAWNED_1)
+ A.GiveTarget(target)
+ A.friends = user
+ A.faction = user.faction.Copy()
diff --git a/code/modules/mob/living/simple_animal/hostile/mining_mobs/elites/pandora.dm b/code/modules/mob/living/simple_animal/hostile/mining_mobs/elites/pandora.dm
new file mode 100644
index 0000000000..540470d505
--- /dev/null
+++ b/code/modules/mob/living/simple_animal/hostile/mining_mobs/elites/pandora.dm
@@ -0,0 +1,193 @@
+#define SINGULAR_SHOT 1
+#define MAGIC_BOX 2
+#define PANDORA_TELEPORT 3
+#define AOE_SQUARES 4
+
+/**
+ * # Pandora
+ *
+ * A box with a similar design to the Hierophant which trades large, single attacks for more frequent smaller ones.
+ * As it's health gets lower, the time between it's attacks decrease.
+ * It's attacks are as follows:
+ * - Fires hierophant blasts in a straight line. Can only fire in a straight line in 8 directions, being the diagonals and cardinals.
+ * - Creates a box of hierophant blasts around the target. If they try to run away to avoid it, they'll very likely get hit.
+ * - Teleports the pandora from one location to another, almost identical to Hierophant.
+ * - Spawns a 5x5 AOE at the location of choice, spreading out from the center.
+ * Pandora's fight mirrors Hierophant's closely, but has stark differences in attack effects. Instead of long-winded dodge times and long cooldowns, Pandora constantly attacks the opponent, but leaves itself open for attack.
+ */
+
+/mob/living/simple_animal/hostile/asteroid/elite/pandora
+ name = "pandora"
+ desc = "A large magic box with similar power and design to the Hierophant. Once it opens, it's not easy to close it."
+ icon_state = "pandora"
+ icon_living = "pandora"
+ icon_aggro = "pandora"
+ icon_dead = "pandora_dead"
+ icon_gib = "syndicate_gib"
+ maxHealth = 800
+ health = 800
+ melee_damage_lower = 15
+ melee_damage_upper = 15
+ attacktext = "smashes into the side of"
+ /*attack_verb_continuous = "smashes into the side of"
+ attack_verb_simple = "smash into the side of"*/
+ attack_sound = 'sound/weapons/sonic_jackhammer.ogg'
+ throw_message = "merely dinks off of the"
+ speed = 4
+ move_to_delay = 10
+ mouse_opacity = MOUSE_OPACITY_ICON
+ deathsound = 'sound/magic/repulse.ogg'
+ deathmessage = "'s lights flicker, before its top part falls down."
+ loot_drop = /obj/item/clothing/accessory/pandora_hope
+
+ attack_action_types = list(/datum/action/innate/elite_attack/singular_shot,
+ /datum/action/innate/elite_attack/magic_box,
+ /datum/action/innate/elite_attack/pandora_teleport,
+ /datum/action/innate/elite_attack/aoe_squares)
+
+ var/sing_shot_length = 8
+ var/cooldown_time = 20
+
+/datum/action/innate/elite_attack/singular_shot
+ name = "Singular Shot"
+ button_icon_state = "singular_shot"
+ chosen_message = "You are now creating a single linear magic square."
+ chosen_attack_num = SINGULAR_SHOT
+
+/datum/action/innate/elite_attack/magic_box
+ name = "Magic Box"
+ button_icon_state = "magic_box"
+ chosen_message = "You are now attacking with a box of magic squares."
+ chosen_attack_num = MAGIC_BOX
+
+/datum/action/innate/elite_attack/pandora_teleport
+ name = "Line Teleport"
+ button_icon_state = "pandora_teleport"
+ chosen_message = "You will now teleport to your target."
+ chosen_attack_num = PANDORA_TELEPORT
+
+/datum/action/innate/elite_attack/aoe_squares
+ name = "AOE Blast"
+ button_icon_state = "aoe_squares"
+ chosen_message = "Your attacks will spawn an AOE blast at your target location."
+ chosen_attack_num = AOE_SQUARES
+
+/mob/living/simple_animal/hostile/asteroid/elite/pandora/OpenFire()
+ if(client)
+ switch(chosen_attack)
+ if(SINGULAR_SHOT)
+ singular_shot(target)
+ if(MAGIC_BOX)
+ magic_box(target)
+ if(PANDORA_TELEPORT)
+ pandora_teleport(target)
+ if(AOE_SQUARES)
+ aoe_squares(target)
+ return
+ var/aiattack = rand(1,4)
+ switch(aiattack)
+ if(SINGULAR_SHOT)
+ singular_shot(target)
+ if(MAGIC_BOX)
+ magic_box(target)
+ if(PANDORA_TELEPORT)
+ pandora_teleport(target)
+ if(AOE_SQUARES)
+ aoe_squares(target)
+
+/mob/living/simple_animal/hostile/asteroid/elite/pandora/Life()
+ . = ..()
+ if(health >= maxHealth * 0.5)
+ cooldown_time = 20
+ return
+ if(health < maxHealth * 0.5 && health > maxHealth * 0.25)
+ cooldown_time = 15
+ return
+ else
+ cooldown_time = 10
+
+/mob/living/simple_animal/hostile/asteroid/elite/pandora/proc/singular_shot(target)
+ ranged_cooldown = world.time + (cooldown_time * 0.5)
+ var/dir_to_target = get_dir(get_turf(src), get_turf(target))
+ var/turf/T = get_step(get_turf(src), dir_to_target)
+ singular_shot_line(sing_shot_length, dir_to_target, T)
+
+/mob/living/simple_animal/hostile/asteroid/elite/pandora/proc/singular_shot_line(var/procsleft, var/angleused, var/turf/T)
+ if(procsleft <= 0)
+ return
+ new /obj/effect/temp_visual/hierophant/blast/pandora(T, src)
+ T = get_step(T, angleused)
+ procsleft = procsleft - 1
+ addtimer(CALLBACK(src, .proc/singular_shot_line, procsleft, angleused, T), 2)
+
+/mob/living/simple_animal/hostile/asteroid/elite/pandora/proc/magic_box(target)
+ ranged_cooldown = world.time + cooldown_time
+ var/turf/T = get_turf(target)
+ for(var/t in spiral_range_turfs(3, T))
+ if(get_dist(t, T) > 1)
+ new /obj/effect/temp_visual/hierophant/blast/pandora(t, src)
+
+/mob/living/simple_animal/hostile/asteroid/elite/pandora/proc/pandora_teleport(target)
+ ranged_cooldown = world.time + cooldown_time
+ var/turf/T = get_turf(target)
+ var/turf/source = get_turf(src)
+ new /obj/effect/temp_visual/hierophant/telegraph(T, src)
+ new /obj/effect/temp_visual/hierophant/telegraph(source, src)
+ playsound(source,'sound/machines/airlockopen.ogg', 200, 1)
+ addtimer(CALLBACK(src, .proc/pandora_teleport_2, T, source), 2)
+
+/mob/living/simple_animal/hostile/asteroid/elite/pandora/proc/pandora_teleport_2(var/turf/T, var/turf/source)
+ new /obj/effect/temp_visual/hierophant/telegraph/teleport(T, src)
+ new /obj/effect/temp_visual/hierophant/telegraph/teleport(source, src)
+ for(var/t in RANGE_TURFS(1, T))
+ new /obj/effect/temp_visual/hierophant/blast/pandora(t, src)
+ for(var/t in RANGE_TURFS(1, source))
+ new /obj/effect/temp_visual/hierophant/blast/pandora(t, src)
+ animate(src, alpha = 0, time = 2, easing = EASE_OUT) //fade out
+ visible_message("[src] fades out!")
+ density = FALSE
+ addtimer(CALLBACK(src, .proc/pandora_teleport_3, T), 2)
+
+/mob/living/simple_animal/hostile/asteroid/elite/pandora/proc/pandora_teleport_3(var/turf/T)
+ forceMove(T)
+ animate(src, alpha = 255, time = 2, easing = EASE_IN) //fade IN
+ density = TRUE
+ visible_message("[src] fades in!")
+
+/mob/living/simple_animal/hostile/asteroid/elite/pandora/proc/aoe_squares(target)
+ ranged_cooldown = world.time + cooldown_time
+ var/turf/T = get_turf(target)
+ new /obj/effect/temp_visual/hierophant/blast/pandora(T, src)
+ var/max_size = 2
+ addtimer(CALLBACK(src, .proc/aoe_squares_2, T, 0, max_size), 2)
+
+/mob/living/simple_animal/hostile/asteroid/elite/pandora/proc/aoe_squares_2(var/turf/T, var/ring, var/max_size)
+ if(ring > max_size)
+ return
+ for(var/t in spiral_range_turfs(ring, T))
+ if(get_dist(t, T) == ring)
+ new /obj/effect/temp_visual/hierophant/blast/pandora(t, src)
+ addtimer(CALLBACK(src, .proc/aoe_squares_2, T, (ring + 1), max_size), 2)
+
+//The specific version of hiero's squares pandora uses
+/obj/effect/temp_visual/hierophant/blast/pandora
+ damage = 20
+ monster_damage_boost = FALSE
+
+//Pandora's loot: Hope
+/obj/item/clothing/accessory/pandora_hope
+ name = "Hope"
+ desc = "Found at the bottom of Pandora. After all the evil was released, this was the only thing left inside."
+ icon = 'icons/obj/lavaland/elite_trophies.dmi'
+ icon_state = "hope"
+ resistance_flags = FIRE_PROOF
+
+/obj/item/clothing/accessory/pandora_hope/on_uniform_equip(obj/item/clothing/under/U, user)
+ var/mob/living/L = user
+ if(L && L.mind)
+ SEND_SIGNAL(L, COMSIG_ADD_MOOD_EVENT, "hope_lavaland", /datum/mood_event/hope_lavaland)
+
+/obj/item/clothing/accessory/pandora_hope/on_uniform_dropped(obj/item/clothing/under/U, user)
+ var/mob/living/L = user
+ if(L && L.mind)
+ SEND_SIGNAL(L, COMSIG_CLEAR_MOOD_EVENT, "hope_lavaland")
diff --git a/code/modules/mob/living/simple_animal/hostile/retaliate/bat.dm b/code/modules/mob/living/simple_animal/hostile/retaliate/bat.dm
index cc54ad3bef..4856ba2176 100644
--- a/code/modules/mob/living/simple_animal/hostile/retaliate/bat.dm
+++ b/code/modules/mob/living/simple_animal/hostile/retaliate/bat.dm
@@ -6,6 +6,7 @@
icon_dead = "bat_dead"
icon_gib = "bat_dead"
turns_per_move = 1
+ blood_volume = 250
response_help = "brushes aside"
response_disarm = "flails at"
response_harm = "hits"
diff --git a/code/modules/mob/living/simple_animal/hostile/retaliate/ghost.dm b/code/modules/mob/living/simple_animal/hostile/retaliate/ghost.dm
index 32e1c4d047..bfe8349192 100644
--- a/code/modules/mob/living/simple_animal/hostile/retaliate/ghost.dm
+++ b/code/modules/mob/living/simple_animal/hostile/retaliate/ghost.dm
@@ -31,6 +31,7 @@
movement_type = FLYING
pressure_resistance = 300
gold_core_spawnable = NO_SPAWN //too spooky for science
+ blood_volume = 0
var/ghost_hair_style
var/ghost_hair_color
var/mutable_appearance/ghost_hair
diff --git a/code/modules/mob/living/simple_animal/hostile/skeleton.dm b/code/modules/mob/living/simple_animal/hostile/skeleton.dm
index d0ae01f443..48b69a1f5c 100644
--- a/code/modules/mob/living/simple_animal/hostile/skeleton.dm
+++ b/code/modules/mob/living/simple_animal/hostile/skeleton.dm
@@ -12,6 +12,7 @@
emote_see = list("rattles")
a_intent = INTENT_HARM
maxHealth = 40
+ blood_volume = 0
health = 40
speed = 1
harm_intent_damage = 5
diff --git a/code/modules/mob/living/simple_animal/hostile/statue.dm b/code/modules/mob/living/simple_animal/hostile/statue.dm
index 804989e71e..da423f3788 100644
--- a/code/modules/mob/living/simple_animal/hostile/statue.dm
+++ b/code/modules/mob/living/simple_animal/hostile/statue.dm
@@ -18,6 +18,7 @@
maxHealth = 50000
health = 50000
healable = 0
+ blood_volume = 0
harm_intent_damage = 10
obj_damage = 100
diff --git a/code/modules/mob/living/simple_animal/hostile/stickman.dm b/code/modules/mob/living/simple_animal/hostile/stickman.dm
index 7a86870aa1..fa5cb151ed 100644
--- a/code/modules/mob/living/simple_animal/hostile/stickman.dm
+++ b/code/modules/mob/living/simple_animal/hostile/stickman.dm
@@ -13,6 +13,7 @@
response_disarm = "shoves"
response_harm = "hits"
speed = 0
+ blood_volume = 0
stat_attack = UNCONSCIOUS
robust_searching = 1
environment_smash = ENVIRONMENT_SMASH_NONE
diff --git a/code/modules/mob/living/simple_animal/parrot.dm b/code/modules/mob/living/simple_animal/parrot.dm
index 12bc41d9c5..3d23baae48 100644
--- a/code/modules/mob/living/simple_animal/parrot.dm
+++ b/code/modules/mob/living/simple_animal/parrot.dm
@@ -143,7 +143,7 @@
stat("Held Item", held_item)
stat("Mode",a_intent)
-/mob/living/simple_animal/parrot/Hear(message, atom/movable/speaker, message_langs, raw_message, radio_freq, list/spans, message_mode)
+/mob/living/simple_animal/parrot/Hear(message, atom/movable/speaker, message_langs, raw_message, radio_freq, list/spans, message_mode, atom/movable/source)
. = ..()
if(speaker != src && prob(50)) //Dont imitate ourselves
if(!radio_freq || prob(10))
diff --git a/code/modules/mob/living/simple_animal/shade.dm b/code/modules/mob/living/simple_animal/shade.dm
index 3bd24c4df7..32ef52a6ae 100644
--- a/code/modules/mob/living/simple_animal/shade.dm
+++ b/code/modules/mob/living/simple_animal/shade.dm
@@ -31,6 +31,7 @@
loot = list(/obj/item/ectoplasm)
del_on_death = TRUE
initial_language_holder = /datum/language_holder/construct
+ blood_volume = 0
/mob/living/simple_animal/shade/death()
deathmessage = "lets out a contented sigh as [p_their()] form unwinds."
diff --git a/code/modules/mob/living/simple_animal/simple_animal.dm b/code/modules/mob/living/simple_animal/simple_animal.dm
index 52e58aea4e..990fa008fe 100644
--- a/code/modules/mob/living/simple_animal/simple_animal.dm
+++ b/code/modules/mob/living/simple_animal/simple_animal.dm
@@ -4,6 +4,7 @@
health = 20
maxHealth = 20
gender = PLURAL //placeholder
+ blood_volume = 550 //How much blud it has for bloodsucking
status_flags = CANPUSH
diff --git a/code/modules/mob/living/simple_animal/slime/say.dm b/code/modules/mob/living/simple_animal/slime/say.dm
index a2618b711e..c48249d80f 100644
--- a/code/modules/mob/living/simple_animal/slime/say.dm
+++ b/code/modules/mob/living/simple_animal/slime/say.dm
@@ -1,4 +1,4 @@
-/mob/living/simple_animal/slime/Hear(message, atom/movable/speaker, message_langs, raw_message, radio_freq, spans, message_mode)
+/mob/living/simple_animal/slime/Hear(message, atom/movable/speaker, message_langs, raw_message, radio_freq, spans, message_mode, atom/movable/source)
. = ..()
if(speaker != src && !radio_freq && !stat)
if (speaker in Friends)
diff --git a/code/modules/mob/living/simple_animal/slime/slime.dm b/code/modules/mob/living/simple_animal/slime/slime.dm
index 185d717a00..d1e10ea693 100644
--- a/code/modules/mob/living/simple_animal/slime/slime.dm
+++ b/code/modules/mob/living/simple_animal/slime/slime.dm
@@ -26,6 +26,7 @@
health = 150
healable = 0
gender = NEUTER
+ blood_volume = 0 //Until someome reworks for them to have slime jelly
see_in_dark = 8
diff --git a/code/modules/mob/living/status_procs.dm b/code/modules/mob/living/status_procs.dm
index 0880f7f432..facc86da46 100644
--- a/code/modules/mob/living/status_procs.dm
+++ b/code/modules/mob/living/status_procs.dm
@@ -140,24 +140,28 @@
/////////////////////////////////// DISABILITIES ////////////////////////////////////
-/mob/living/proc/add_quirk(quirk, spawn_effects) //separate proc due to the way these ones are handled
- if(HAS_TRAIT(src, quirk))
+/mob/living/proc/add_quirk(quirktype, spawn_effects) //separate proc due to the way these ones are handled
+ if(has_quirk(quirktype))
return
- if(!SSquirks || !SSquirks.quirks[quirk])
+ var/datum/quirk/T = quirktype
+ var/qname = initial(T.name)
+ if(!SSquirks || !SSquirks.quirks[qname])
return
- var/datum/quirk/T = SSquirks.quirks[quirk]
- new T (src, spawn_effects)
+ new quirktype (src, spawn_effects)
return TRUE
-/mob/living/proc/remove_quirk(quirk)
- var/datum/quirk/T = roundstart_quirks[quirk]
- if(T)
- qdel(T)
- return TRUE
-
-/mob/living/proc/has_quirk(quirk)
- return roundstart_quirks[quirk]
+/mob/living/proc/remove_quirk(quirktype)
+ for(var/datum/quirk/Q in roundstart_quirks)
+ if(Q.type == quirktype)
+ qdel(Q)
+ return TRUE
+ return FALSE
+/mob/living/proc/has_quirk(quirktype)
+ for(var/datum/quirk/Q in roundstart_quirks)
+ if(Q.type == quirktype)
+ return TRUE
+ return FALSE
/////////////////////////////////// TRAIT PROCS ////////////////////////////////////
/mob/living/proc/cure_blind(list/sources)
diff --git a/code/modules/mob/living/ventcrawling.dm b/code/modules/mob/living/ventcrawling.dm
index cacd1c7c56..7f8513bfd9 100644
--- a/code/modules/mob/living/ventcrawling.dm
+++ b/code/modules/mob/living/ventcrawling.dm
@@ -8,6 +8,7 @@ GLOBAL_LIST_INIT(ventcrawl_machinery, typecacheof(list(
/mob/living/proc/handle_ventcrawl(atom/A)
if(!ventcrawler || !Adjacent(A))
return
+ . = TRUE //return value to stop the client from being shown the turf contents stat tab on alt-click.
if(stat)
to_chat(src, "You must be conscious to do this!")
return
diff --git a/code/modules/mob/mob.dm b/code/modules/mob/mob.dm
index d4528fb91f..a47ea9d5a0 100644
--- a/code/modules/mob/mob.dm
+++ b/code/modules/mob/mob.dm
@@ -465,9 +465,10 @@ mob/visible_message(message, self_message, blind_message, vision_distance = DEFA
/mob/proc/transfer_ckey(mob/new_mob, send_signal = TRUE)
if(!ckey || !new_mob)
CRASH("transfer_ckey() called [ckey ? "" : "on a ckey-less mob[new_mob ? "" : " and "]"][new_mob ? "" : "without a valid mob target"]!")
+ SEND_SIGNAL(new_mob, COMSIG_MOB_PRE_PLAYER_CHANGE, new_mob, src)
+ new_mob.ckey = ckey
if(send_signal)
SEND_SIGNAL(src, COMSIG_MOB_KEY_CHANGE, new_mob, src)
- new_mob.ckey = ckey
return TRUE
/mob/verb/cancel_camera()
diff --git a/code/modules/mob/mob_defines.dm b/code/modules/mob/mob_defines.dm
index 0cb886f11b..a9f4c94397 100644
--- a/code/modules/mob/mob_defines.dm
+++ b/code/modules/mob/mob_defines.dm
@@ -99,6 +99,12 @@
var/obj/control_object //Used by admins to possess objects. All mobs should have this var
var/atom/movable/remote_control //Calls relaymove() to whatever it is
+ /**
+ * The sound made on death
+ *
+ * leave null for no sound. used for *deathgasp
+ */
+ var/deathsound = null
var/turf/listed_turf = null //the current turf being examined in the stat panel
diff --git a/code/modules/mob/mob_movement.dm b/code/modules/mob/mob_movement.dm
index 2c8935c786..1a836c06cf 100644
--- a/code/modules/mob/mob_movement.dm
+++ b/code/modules/mob/mob_movement.dm
@@ -366,6 +366,9 @@
if(m_intent == MOVE_INTENT_RUN)
m_intent = MOVE_INTENT_WALK
else
+ if (HAS_TRAIT(src,TRAIT_NORUNNING)) // FULPSTATION 7/10/19 So you can't run during fortitude.
+ to_chat(src, "You find yourself unable to run.")
+ return FALSE
m_intent = MOVE_INTENT_RUN
if(hud_used && hud_used.static_inventory)
for(var/obj/screen/mov_intent/selector in hud_used.static_inventory)
@@ -401,4 +404,4 @@
return TRUE
/mob/proc/canZMove(direction, turf/target)
- return FALSE
\ No newline at end of file
+ return FALSE
diff --git a/code/modules/mob/status_procs.dm b/code/modules/mob/status_procs.dm
index 606d6d4f66..17311daec2 100644
--- a/code/modules/mob/status_procs.dm
+++ b/code/modules/mob/status_procs.dm
@@ -134,17 +134,6 @@
/mob/proc/Dizzy(amount)
dizziness = max(dizziness,amount,0)
-/////////////////////////////////// EYE DAMAGE ////////////////////////////////////
-
-/mob/proc/damage_eyes(amount)
- return
-
-/mob/proc/adjust_eye_damage(amount)
- return
-
-/mob/proc/set_eye_damage(amount)
- return
-
/////////////////////////////////// EYE_BLIND ////////////////////////////////////
/mob/proc/blind_eyes(amount)
diff --git a/code/modules/modular_computers/computers/item/computer.dm b/code/modules/modular_computers/computers/item/computer.dm
index 1b3501f78b..79c74de2eb 100644
--- a/code/modules/modular_computers/computers/item/computer.dm
+++ b/code/modules/modular_computers/computers/item/computer.dm
@@ -126,7 +126,7 @@
portable_drive.verb_pickup()
/obj/item/modular_computer/AltClick(mob/user)
- ..()
+ . = ..()
if(issilicon(user))
return
@@ -142,7 +142,7 @@
return
if(ai_slot)
ai_slot.try_eject(null, user)
-
+ return TRUE
// Gets IDs/access levels from card slot. Would be useful when/if PDAs would become modular PCs.
/obj/item/modular_computer/GetAccess()
diff --git a/code/modules/modular_computers/computers/item/laptop.dm b/code/modules/modular_computers/computers/item/laptop.dm
index 4d4dee1b8c..ce8ab9659c 100644
--- a/code/modules/modular_computers/computers/item/laptop.dm
+++ b/code/modules/modular_computers/computers/item/laptop.dm
@@ -86,8 +86,8 @@
/obj/item/modular_computer/laptop/AltClick(mob/user)
if(screen_on) // Close it.
try_toggle_open(user)
- else
- return ..()
+ return TRUE
+ return ..()
/obj/item/modular_computer/laptop/proc/toggle_open(mob/living/user=null)
if(screen_on)
diff --git a/code/modules/modular_computers/computers/machinery/modular_computer.dm b/code/modules/modular_computers/computers/machinery/modular_computer.dm
index b3476e7046..a988003b67 100644
--- a/code/modules/modular_computers/computers/machinery/modular_computer.dm
+++ b/code/modules/modular_computers/computers/machinery/modular_computer.dm
@@ -94,8 +94,9 @@
cpu.eject_card()
/obj/machinery/modular_computer/AltClick(mob/user)
+ . = ..()
if(cpu)
- cpu.AltClick(user)
+ return cpu.AltClick(user)
//ATTACK HAND IGNORING PARENT RETURN VALUE
// On-click handling. Turns on the computer if it's off and opens the GUI.
diff --git a/code/modules/modular_computers/hardware/ai_slot.dm b/code/modules/modular_computers/hardware/ai_slot.dm
index 47cbbff418..8428467a87 100644
--- a/code/modules/modular_computers/hardware/ai_slot.dm
+++ b/code/modules/modular_computers/hardware/ai_slot.dm
@@ -41,6 +41,13 @@
/obj/item/computer_hardware/ai_slot/try_eject(slot=0,mob/living/user = null,forced = 0)
+ if (get_dist(src,user) > 1)
+ if (iscarbon(user))
+ var/mob/living/carbon/H = user
+ if (!(H.dna && H.dna.check_mutation(TK) && tkMaxRangeCheck(src,H)))
+ return FALSE
+ else
+ return FALSE
if(!stored_card)
to_chat(user, "There is no card in \the [src].")
return FALSE
diff --git a/code/modules/modular_computers/hardware/card_slot.dm b/code/modules/modular_computers/hardware/card_slot.dm
index c68e1ad119..e4bc45dbc5 100644
--- a/code/modules/modular_computers/hardware/card_slot.dm
+++ b/code/modules/modular_computers/hardware/card_slot.dm
@@ -73,6 +73,13 @@
/obj/item/computer_hardware/card_slot/try_eject(slot=0, mob/living/user = null, forced = 0)
+ if (get_dist(src,user) > 1)
+ if (iscarbon(user))
+ var/mob/living/carbon/H = user
+ if (!(H.dna && H.dna.check_mutation(TK) && tkMaxRangeCheck(src,H)))
+ return FALSE
+ else
+ return FALSE
if(!stored_card && !stored_card2)
to_chat(user, "There are no cards in \the [src].")
return FALSE
diff --git a/code/modules/paperwork/paperplane.dm b/code/modules/paperwork/paperplane.dm
index ecef5a703a..2ca6ce2611 100644
--- a/code/modules/paperwork/paperplane.dm
+++ b/code/modules/paperwork/paperplane.dm
@@ -44,10 +44,12 @@
return ..()
/obj/item/paperplane/suicide_act(mob/living/user)
+ var/obj/item/organ/eyes/eyes = user.getorganslot(ORGAN_SLOT_EYES)
user.Stun(200)
user.visible_message("[user] jams [src] in [user.p_their()] nose. It looks like [user.p_theyre()] trying to commit suicide!")
user.adjust_blurriness(6)
- user.adjust_eye_damage(rand(6,8))
+ if(eyes)
+ eyes.applyOrganDamage(rand(6,8))
sleep(10)
return (BRUTELOSS)
@@ -111,9 +113,11 @@
if(prob(hit_probability))
if(H.is_eyes_covered())
return
+ var/obj/item/organ/eyes/eyes = H.getorganslot(ORGAN_SLOT_EYES)
visible_message("\The [src] hits [H] in the eye!")
H.adjust_blurriness(6)
- H.adjust_eye_damage(rand(6,8))
+ if(eyes)
+ eyes.applyOrganDamage(rand(6,8))
H.Knockdown(40)
H.emote("scream")
@@ -122,6 +126,7 @@
. += "Alt-click [src] to fold it into a paper plane."
/obj/item/paper/AltClick(mob/living/carbon/user, obj/item/I)
+ . = ..()
if(!istype(user) || !user.canUseTopic(src, BE_CLOSE, ismonkey(user), NO_TK))
return
to_chat(user, "You fold [src] into the shape of a plane!")
@@ -134,3 +139,4 @@
I = new plane_type(user, src)
user.put_in_hands(I)
+ return TRUE
diff --git a/code/modules/photography/camera/camera.dm b/code/modules/photography/camera/camera.dm
index 9d01eca0cf..96738e5021 100644
--- a/code/modules/photography/camera/camera.dm
+++ b/code/modules/photography/camera/camera.dm
@@ -46,13 +46,14 @@
. += "Alt-click to change its focusing, allowing you to set how big of an area it will capture."
/obj/item/camera/AltClick(mob/user)
+ . = ..()
if(!user.canUseTopic(src, BE_CLOSE))
return
var/desired_x = input(user, "How high do you want the camera to shoot, between [picture_size_x_min] and [picture_size_x_max]?", "Zoom", picture_size_x) as num
var/desired_y = input(user, "How wide do you want the camera to shoot, between [picture_size_y_min] and [picture_size_y_max]?", "Zoom", picture_size_y) as num
picture_size_x = min(CLAMP(desired_x, picture_size_x_min, picture_size_x_max), CAMERA_PICTURE_SIZE_HARD_LIMIT)
picture_size_y = min(CLAMP(desired_y, picture_size_y_min, picture_size_y_max), CAMERA_PICTURE_SIZE_HARD_LIMIT)
-
+ return TRUE
/obj/item/camera/attack(mob/living/carbon/human/M, mob/user)
return
diff --git a/code/modules/power/apc.dm b/code/modules/power/apc.dm
index 5e3888a8c5..2652026a11 100644
--- a/code/modules/power/apc.dm
+++ b/code/modules/power/apc.dm
@@ -702,11 +702,11 @@
return ..()
/obj/machinery/power/apc/AltClick(mob/user)
- ..()
+ . = ..()
if(!user.canUseTopic(src, !issilicon(user)) || !isturf(loc))
return
- else
- togglelock(user)
+ togglelock(user)
+ return TRUE
/obj/machinery/power/apc/proc/togglelock(mob/living/user)
if(obj_flags & EMAGGED)
@@ -1113,9 +1113,9 @@
if(terminal && terminal.powernet)
terminal.add_load(amount)
-/obj/machinery/power/apc/avail()
+/obj/machinery/power/apc/avail(amount)
if(terminal)
- return terminal.avail()
+ return terminal.avail(amount)
else
return 0
diff --git a/code/modules/power/cable.dm b/code/modules/power/cable.dm
index a3dc6e7394..cc03976f79 100644
--- a/code/modules/power/cable.dm
+++ b/code/modules/power/cable.dm
@@ -217,9 +217,9 @@ By design, d1 is the smallest direction and d2 is the highest
else
return 0
-/obj/structure/cable/proc/avail()
+/obj/structure/cable/proc/avail(amount)
if(powernet)
- return powernet.avail
+ return amount ? powernet.avail >= amount : powernet.avail
else
return 0
diff --git a/code/modules/power/power.dm b/code/modules/power/power.dm
index 58a259de3a..d2d3d60066 100644
--- a/code/modules/power/power.dm
+++ b/code/modules/power/power.dm
@@ -46,9 +46,9 @@
else
return 0
-/obj/machinery/power/proc/avail()
+/obj/machinery/power/proc/avail(amount)
if(powernet)
- return powernet.avail
+ return amount ? powernet.avail >= amount : powernet.avail
else
return 0
diff --git a/code/modules/power/singularity/collector.dm b/code/modules/power/singularity/collector.dm
index b1b0d2d718..054b91f273 100644
--- a/code/modules/power/singularity/collector.dm
+++ b/code/modules/power/singularity/collector.dm
@@ -110,7 +110,7 @@
if(!user.transferItemToLoc(W, src))
return
loaded_tank = W
- update_icons()
+ update_icon()
else if(W.GetID())
if(allowed(user))
if(active)
@@ -197,14 +197,14 @@
if(active)
toggle_power()
else
- update_icons()
+ update_icon()
/obj/machinery/power/rad_collector/rad_act(pulse_strength)
. = ..()
if(loaded_tank && active && pulse_strength > RAD_COLLECTOR_EFFICIENCY)
stored_power += (pulse_strength-RAD_COLLECTOR_EFFICIENCY)*RAD_COLLECTOR_COEFFICIENT
-/obj/machinery/power/rad_collector/proc/update_icons()
+/obj/machinery/power/rad_collector/update_icon()
cut_overlays()
if(loaded_tank)
add_overlay("ptank")
@@ -222,7 +222,7 @@
else
icon_state = "ca"
flick("ca_deactive", src)
- update_icons()
+ update_icon()
return
#undef RAD_COLLECTOR_EFFICIENCY
diff --git a/code/modules/projectiles/gun.dm b/code/modules/projectiles/gun.dm
index 026f725e2f..531c6082b0 100644
--- a/code/modules/projectiles/gun.dm
+++ b/code/modules/projectiles/gun.dm
@@ -198,8 +198,13 @@
/obj/item/gun/can_trigger_gun(mob/living/user)
. = ..()
+ if(!.)
+ return
if(!handle_pins(user))
return FALSE
+ if(HAS_TRAIT(user, TRAIT_PACIFISM) && chambered?.harmful) // If the user has the pacifist trait, then they won't be able to fire [src] if the round chambered inside of [src] is lethal.
+ to_chat(user, " [src] is lethally chambered! You don't want to risk harming anyone...")
+ return FALSE
/obj/item/gun/proc/handle_pins(mob/living/user)
if(pin)
@@ -275,10 +280,6 @@
addtimer(CALLBACK(src, .proc/process_burst, user, target, message, params, zone_override, sprd, randomized_gun_spread, randomized_bonus_spread, rand_spr, i), fire_delay * (i - 1))
else
if(chambered)
- if(HAS_TRAIT(user, TRAIT_PACIFISM)) // If the user has the pacifist trait, then they won't be able to fire [src] if the round chambered inside of [src] is lethal.
- if(chambered.harmful) // Is the bullet chambered harmful?
- to_chat(user, " [src] is lethally chambered! You don't want to risk harming anyone...")
- return
sprd = round((rand() - 0.5) * DUALWIELD_PENALTY_EXTRA_MULTIPLIER * (randomized_gun_spread + randomized_bonus_spread))
if(!chambered.fire_casing(target, user, params, , suppressed, zone_override, sprd, src))
shoot_with_empty_chamber(user)
diff --git a/code/modules/projectiles/guns/ballistic/shotgun.dm b/code/modules/projectiles/guns/ballistic/shotgun.dm
index 1e1b518849..571525d8f0 100644
--- a/code/modules/projectiles/guns/ballistic/shotgun.dm
+++ b/code/modules/projectiles/guns/ballistic/shotgun.dm
@@ -225,10 +225,11 @@
spread = 2
/obj/item/gun/ballistic/shotgun/automatic/combat/compact/AltClick(mob/living/user)
+ . = ..()
if(!istype(user) || !user.canUseTopic(src, BE_CLOSE, ismonkey(user)))
return
toggle_stock(user)
- . = ..()
+ return TRUE
/obj/item/gun/ballistic/shotgun/automatic/combat/compact/examine(mob/user)
. = ..()
@@ -289,8 +290,10 @@
to_chat(user, "You switch to tube A.")
/obj/item/gun/ballistic/shotgun/automatic/dual_tube/AltClick(mob/living/user)
+ . = ..()
if(!istype(user) || !user.canUseTopic(src, BE_CLOSE, ismonkey(user)))
return
pump()
+ return TRUE
// DOUBLE BARRELED SHOTGUN and IMPROVISED SHOTGUN are in revolver.dm
diff --git a/code/modules/projectiles/guns/misc/syringe_gun.dm b/code/modules/projectiles/guns/misc/syringe_gun.dm
index 8a9d1c5b6b..d947e3155d 100644
--- a/code/modules/projectiles/guns/misc/syringe_gun.dm
+++ b/code/modules/projectiles/guns/misc/syringe_gun.dm
@@ -151,3 +151,17 @@
max_syringes = 1
desc = "[initial(desc)] It has a [B] strapped to it, but it doesn't seem to be doing anything."
..()
+
+/obj/item/gun/syringe/blowgun
+ name = "blowgun"
+ desc = "Fire syringes at a short distance."
+ icon_state = "blowgun"
+ item_state = "blowgun"
+ fire_sound = 'sound/items/syringeproj.ogg'
+
+/obj/item/gun/syringe/blowgun/process_fire(atom/target, mob/living/user, message = TRUE, params = null, zone_override = "", bonus_spread = 0)
+ visible_message("[user] starts aiming with a blowgun!")
+ if(do_after(user, 25, target = src))
+ user.adjustStaminaLoss(20)
+ user.adjustOxyLoss(20)
+ ..()
diff --git a/code/modules/projectiles/projectile.dm b/code/modules/projectiles/projectile.dm
index 88e766edfc..9141a6d299 100644
--- a/code/modules/projectiles/projectile.dm
+++ b/code/modules/projectiles/projectile.dm
@@ -180,7 +180,7 @@
else
new /obj/effect/temp_visual/dir_setting/bloodsplatter(target_loca, splatter_dir, bloodtype_to_color())
- if(iscarbon(L))
+ if(iscarbon(L) && !HAS_TRAIT(L, TRAIT_NOMARROW))
var/mob/living/carbon/C = L
C.bleed(damage)
else
diff --git a/code/modules/projectiles/projectile/energy.dm b/code/modules/projectiles/projectile/energy.dm
deleted file mode 100644
index 047d50beaf..0000000000
--- a/code/modules/projectiles/projectile/energy.dm
+++ /dev/null
@@ -1,203 +0,0 @@
-/obj/item/projectile/energy
- name = "energy"
- icon_state = "spark"
- damage = 0
- damage_type = BURN
- flag = "energy"
- is_reflectable = TRUE
-
-/obj/item/projectile/energy/chameleon
- nodamage = TRUE
-
-/obj/item/projectile/energy/electrode
- name = "electrode"
- icon_state = "spark"
- color = "#FFFF00"
- nodamage = 1
- knockdown = 100
- stutter = 5
- jitter = 20
- hitsound = 'sound/weapons/taserhit.ogg'
- range = 7
- tracer_type = /obj/effect/projectile/tracer/stun
- muzzle_type = /obj/effect/projectile/muzzle/stun
- impact_type = /obj/effect/projectile/impact/stun
-
-/obj/item/projectile/energy/electrode/on_hit(atom/target, blocked = FALSE)
- . = ..()
- if(!ismob(target) || blocked >= 100) //Fully blocked by mob or collided with dense object - burst into sparks!
- do_sparks(1, TRUE, src)
- else if(iscarbon(target))
- var/mob/living/carbon/C = target
- if(C.dna && C.dna.check_mutation(HULK))
- C.say(pick(";RAAAAAAAARGH!", ";HNNNNNNNNNGGGGGGH!", ";GWAAAAAAAARRRHHH!", "NNNNNNNNGGGGGGGGHH!", ";AAAAAAARRRGH!" ))
- else if((C.status_flags & CANKNOCKDOWN) && !HAS_TRAIT(C, TRAIT_STUNIMMUNE))
- addtimer(CALLBACK(C, /mob/living/carbon.proc/do_jitter_animation, jitter), 5)
-
-/obj/item/projectile/energy/electrode/on_range() //to ensure the bolt sparks when it reaches the end of its range if it didn't hit a target yet
- do_sparks(1, TRUE, src)
- ..()
-
-/obj/item/projectile/energy/net
- name = "energy netting"
- icon_state = "e_netting"
- damage = 10
- damage_type = STAMINA
- hitsound = 'sound/weapons/taserhit.ogg'
- range = 10
-
-/obj/item/projectile/energy/net/Initialize()
- . = ..()
- SpinAnimation()
-
-/obj/item/projectile/energy/net/on_hit(atom/target, blocked = FALSE)
- if(isliving(target))
- var/turf/Tloc = get_turf(target)
- if(!locate(/obj/effect/nettingportal) in Tloc)
- new /obj/effect/nettingportal(Tloc)
- ..()
-
-/obj/item/projectile/energy/net/on_range()
- do_sparks(1, TRUE, src)
- ..()
-
-/obj/effect/nettingportal
- name = "DRAGnet teleportation field"
- desc = "A field of bluespace energy, locking on to teleport a target."
- icon = 'icons/effects/effects.dmi'
- icon_state = "dragnetfield"
- light_range = 3
- anchored = TRUE
-
-/obj/effect/nettingportal/Initialize()
- . = ..()
- var/obj/item/radio/beacon/teletarget = null
- for(var/obj/machinery/computer/teleporter/com in GLOB.machines)
- if(com.target)
- if(com.power_station && com.power_station.teleporter_hub && com.power_station.engaged)
- teletarget = com.target
-
- addtimer(CALLBACK(src, .proc/pop, teletarget), 30)
-
-/obj/effect/nettingportal/proc/pop(teletarget)
- if(teletarget)
- for(var/mob/living/L in get_turf(src))
- do_teleport(L, teletarget, 2)//teleport what's in the tile to the beacon
- else
- for(var/mob/living/L in get_turf(src))
- do_teleport(L, L, 15) //Otherwise it just warps you off somewhere.
-
- qdel(src)
-
-/obj/effect/nettingportal/singularity_act()
- return
-
-/obj/effect/nettingportal/singularity_pull()
- return
-
-
-/obj/item/projectile/energy/trap
- name = "energy snare"
- icon_state = "e_snare"
- nodamage = 1
- knockdown = 20
- hitsound = 'sound/weapons/taserhit.ogg'
- range = 4
-
-/obj/item/projectile/energy/trap/on_hit(atom/target, blocked = FALSE)
- if(!ismob(target) || blocked >= 100) //Fully blocked by mob or collided with dense object - drop a trap
- new/obj/item/restraints/legcuffs/beartrap/energy(get_turf(loc))
- else if(iscarbon(target))
- var/obj/item/restraints/legcuffs/beartrap/B = new /obj/item/restraints/legcuffs/beartrap/energy(get_turf(target))
- B.Crossed(target)
- ..()
-
-/obj/item/projectile/energy/trap/on_range()
- new /obj/item/restraints/legcuffs/beartrap/energy(loc)
- ..()
-
-/obj/item/projectile/energy/trap/cyborg
- name = "Energy Bola"
- icon_state = "e_snare"
- nodamage = 1
- knockdown = 0
- hitsound = 'sound/weapons/taserhit.ogg'
- range = 10
-
-/obj/item/projectile/energy/trap/cyborg/on_hit(atom/target, blocked = FALSE)
- if(!ismob(target) || blocked >= 100)
- do_sparks(1, TRUE, src)
- qdel(src)
- if(iscarbon(target))
- var/obj/item/restraints/legcuffs/beartrap/B = new /obj/item/restraints/legcuffs/beartrap/energy/cyborg(get_turf(target))
- B.Crossed(target)
- QDEL_IN(src, 10)
- ..()
-
-/obj/item/projectile/energy/trap/cyborg/on_range()
- do_sparks(1, TRUE, src)
- qdel(src)
-
-/obj/item/projectile/energy/declone
- name = "radiation beam"
- icon_state = "declone"
- damage = 20
- damage_type = CLONE
- irradiate = 10
- impact_effect_type = /obj/effect/temp_visual/impact_effect/green_laser
-
-/obj/item/projectile/energy/dart //ninja throwing dart
- name = "dart"
- icon_state = "toxin"
- damage = 5
- damage_type = TOX
- knockdown = 100
- range = 7
-
-/obj/item/projectile/energy/bolt //ebow bolts
- name = "bolt"
- icon_state = "cbbolt"
- damage = 8
- damage_type = TOX
- nodamage = 0
- knockdown = 100
- stutter = 5
-
-/obj/item/projectile/energy/bolt/halloween
- name = "candy corn"
- icon_state = "candy_corn"
-
-/obj/item/projectile/energy/bolt/large
- damage = 20
-
-/obj/item/projectile/energy/tesla
- name = "tesla bolt"
- icon_state = "tesla_projectile"
- impact_effect_type = /obj/effect/temp_visual/impact_effect/blue_laser
- var/chain
-
-/obj/item/projectile/energy/tesla/fire(setAngle)
- if(firer)
- chain = firer.Beam(src, icon_state = "lightning[rand(1, 12)]", time = INFINITY, maxdistance = INFINITY)
- ..()
-
-/obj/item/projectile/energy/tesla/Destroy()
- qdel(chain)
- return ..()
-
-/obj/item/projectile/energy/tesla/revolver
- name = "energy orb"
-
-/obj/item/projectile/energy/tesla/revolver/on_hit(atom/target)
- . = ..()
- if(isliving(target))
- tesla_zap(target, 3, 10000)
- qdel(src)
-
-/obj/item/projectile/energy/tesla/cannon
- name = "tesla orb"
-
-/obj/item/projectile/energy/tesla/cannon/on_hit(atom/target)
- . = ..()
- tesla_zap(target, 3, 10000, explosive = FALSE, stun_mobs = FALSE)
- qdel(src)
diff --git a/code/modules/projectiles/projectile/magic.dm b/code/modules/projectiles/projectile/magic.dm
index d91c60367d..173d9721a9 100644
--- a/code/modules/projectiles/projectile/magic.dm
+++ b/code/modules/projectiles/projectile/magic.dm
@@ -507,4 +507,32 @@
return
var/turf/T = get_turf(target)
for(var/i=0, i<50, i+=10)
- addtimer(CALLBACK(GLOBAL_PROC, .proc/explosion, T, -1, exp_heavy, exp_light, exp_flash, FALSE, FALSE, exp_fire), i)
\ No newline at end of file
+ addtimer(CALLBACK(GLOBAL_PROC, .proc/explosion, T, -1, exp_heavy, exp_light, exp_flash, FALSE, FALSE, exp_fire), i)
+
+/obj/item/projectile/magic/nuclear
+ name = "\proper blazing manliness"
+ icon_state = "nuclear"
+ nodamage = TRUE
+ var/mob/living/victim = null
+ var/used = 0
+
+/obj/item/projectile/magic/nuclear/on_hit(target)
+ if(used)
+ return
+ new/obj/effect/temp_visual/slugboom(get_turf(src))
+ if(ismob(target))
+ if(target == victim)
+ return
+ used = 1
+ visible_message("[victim] slams into [target] with explosive force!")
+ explosion(src, 2, 3, 4, -1, TRUE, FALSE, 5)
+ else
+ used = 1
+ victim.take_overall_damage(30,30)
+ victim.Knockdown(60)
+ explosion(src, -1, -1, -1, -1, FALSE, FALSE, 5)
+
+/obj/item/projectile/magic/nuclear/Destroy()
+ for(var/atom/movable/AM in contents)
+ AM.forceMove(get_turf(src))
+ . = ..()
\ No newline at end of file
diff --git a/code/modules/projectiles/projectile/megabuster.dm b/code/modules/projectiles/projectile/megabuster.dm
index bfe9f40297..8abb182a1e 100644
--- a/code/modules/projectiles/projectile/megabuster.dm
+++ b/code/modules/projectiles/projectile/megabuster.dm
@@ -6,7 +6,6 @@
damage_type = BURN
hitsound = 'sound/weapons/sear.ogg'
hitsound_wall = 'sound/weapons/effects/searwall.ogg'
- icon = 'modular_citadel/icons/obj/VGprojectile.dmi'
lefthand_file = 'modular_citadel/icons/mob/citadel/guns_lefthand.dmi'
righthand_file = 'modular_citadel/icons/mob/citadel/guns_righthand.dmi'
@@ -14,6 +13,5 @@
name = "buster pellet"
icon_state = "megabuster"
nodamage = 1
- icon = 'modular_citadel/icons/obj/VGprojectile.dmi'
lefthand_file = 'modular_citadel/icons/mob/citadel/guns_lefthand.dmi'
righthand_file = 'modular_citadel/icons/mob/citadel/guns_righthand.dmi'
diff --git a/code/modules/projectiles/projectile/plasma.dm b/code/modules/projectiles/projectile/plasma.dm
index f9adb9f3d7..038200b9df 100644
--- a/code/modules/projectiles/projectile/plasma.dm
+++ b/code/modules/projectiles/projectile/plasma.dm
@@ -1,5 +1,4 @@
obj/item/projectile/energy/plasmabolt
- icon = 'modular_citadel/icons/obj/VGProjectile.dmi'
name = "plasma bolt"
icon_state = "plasma"
flag = "energy"
diff --git a/code/modules/reagents/chemistry/machinery/chem_dispenser.dm b/code/modules/reagents/chemistry/machinery/chem_dispenser.dm
index 00c1db3594..2c5d7aa3e2 100644
--- a/code/modules/reagents/chemistry/machinery/chem_dispenser.dm
+++ b/code/modules/reagents/chemistry/machinery/chem_dispenser.dm
@@ -161,7 +161,7 @@
. = ..()
if(A == beaker)
beaker = null
- cut_overlays()
+ update_icon()
/obj/machinery/chem_dispenser/ui_interact(mob/user, ui_key = "main", datum/tgui/ui = null, force_open = FALSE, \
datum/tgui/master_ui = null, datum/ui_state/state = GLOB.default_state)
@@ -428,10 +428,11 @@
return final_list
/obj/machinery/chem_dispenser/AltClick(mob/living/user)
+ . = ..()
if(!istype(user) || !user.canUseTopic(src, BE_CLOSE, FALSE, NO_TK))
return
replace_beaker(user)
- return
+ return TRUE
/obj/machinery/chem_dispenser/drinks/Initialize()
. = ..()
diff --git a/code/modules/reagents/chemistry/machinery/chem_heater.dm b/code/modules/reagents/chemistry/machinery/chem_heater.dm
index eeb452dbb5..b4f14c69a7 100644
--- a/code/modules/reagents/chemistry/machinery/chem_heater.dm
+++ b/code/modules/reagents/chemistry/machinery/chem_heater.dm
@@ -29,10 +29,11 @@
icon_state = "mixer0b"
/obj/machinery/chem_heater/AltClick(mob/living/user)
+ . = ..()
if(!istype(user) || !user.canUseTopic(src, BE_CLOSE, FALSE, NO_TK))
return
replace_beaker(user)
- return
+ return TRUE
/obj/machinery/chem_heater/proc/replace_beaker(mob/living/user, obj/item/reagent_containers/new_beaker)
if(beaker)
diff --git a/code/modules/reagents/chemistry/machinery/chem_master.dm b/code/modules/reagents/chemistry/machinery/chem_master.dm
index f540ae850d..1ac62ba651 100644
--- a/code/modules/reagents/chemistry/machinery/chem_master.dm
+++ b/code/modules/reagents/chemistry/machinery/chem_master.dm
@@ -58,14 +58,14 @@
if(bottle)
bottle.ex_act(severity, target)
-/obj/machinery/chem_master/handle_atom_del(atom/A)
- ..()
+/obj/machinery/chem_master/Exited(atom/movable/A, atom/newloc)
+ . = ..()
if(A == beaker)
beaker = null
- reagents.clear_reagents()
update_icon()
- else if(A == bottle)
+ if(A == bottle)
bottle = null
+ update_icon()
/obj/machinery/chem_master/update_icon()
cut_overlays()
@@ -103,6 +103,10 @@
updateUsrDialog()
update_icon()
else if(!condi && istype(I, /obj/item/storage/pill_bottle))
+ . = TRUE // no afterattack
+ if(panel_open)
+ to_chat(user, "You can't use the [src.name] while its panel is opened!")
+ return
if(!user.transferItemToLoc(I, src))
return
replace_pillbottle(user, I)
@@ -112,40 +116,40 @@
return ..()
/obj/machinery/chem_master/AltClick(mob/living/user)
+ . = ..()
if(!istype(user) || !user.canUseTopic(src, BE_CLOSE, FALSE, NO_TK))
return
- replace_beaker(user)
- return
+ if(beaker)
+ replace_beaker(user)
+ else if(bottle)
+ replace_pillbottle(user)
+ return TRUE
/obj/machinery/chem_master/proc/replace_beaker(mob/living/user, obj/item/reagent_containers/new_beaker)
if(beaker)
- beaker.forceMove(drop_location())
+ var/obj/item/reagent_containers/B = beaker
+ B.forceMove(drop_location())
if(user && Adjacent(user) && !issiliconoradminghost(user))
- user.put_in_hands(beaker)
+ user.put_in_hands(B)
if(new_beaker)
beaker = new_beaker
- else
- beaker = null
update_icon()
- return TRUE
/obj/machinery/chem_master/proc/replace_pillbottle(mob/living/user, obj/item/storage/pill_bottle/new_bottle)
if(bottle)
- bottle.forceMove(drop_location())
+ var/obj/item/storage/pill_bottle/B = bottle
+ B.forceMove(drop_location())
if(user && Adjacent(user) && !issiliconoradminghost(user))
- user.put_in_hands(beaker)
+ user.put_in_hands(B)
else
- adjust_item_drop_location(bottle)
+ adjust_item_drop_location(B)
if(new_bottle)
bottle = new_bottle
- else
- bottle = null
- update_icon()
- return TRUE
/obj/machinery/chem_master/on_deconstruction()
- replace_beaker(usr)
- replace_pillbottle(usr)
+ var/atom/A = drop_location()
+ beaker.forceMove(A)
+ bottle.forceMove(A)
return ..()
/obj/machinery/chem_master/ui_interact(mob/user, ui_key = "main", datum/tgui/ui = null, force_open = FALSE, \
@@ -260,15 +264,16 @@
if(!name || !reagents.total_volume || !src || QDELETED(src) || !usr.canUseTopic(src, !issilicon(usr)))
return
var/obj/item/reagent_containers/pill/P
- var/target_loc = bottle ? bottle : drop_location()
+ var/target_loc = drop_location()
var/drop_threshold = INFINITY
if(bottle)
var/datum/component/storage/STRB = bottle.GetComponent(/datum/component/storage)
if(STRB)
drop_threshold = STRB.max_items - bottle.contents.len
+ target_loc = bottle
for(var/i in 1 to amount)
- if(i < drop_threshold)
+ if(i <= drop_threshold)
P = new(target_loc)
else
P = new(drop_location())
diff --git a/code/modules/reagents/chemistry/machinery/pandemic.dm b/code/modules/reagents/chemistry/machinery/pandemic.dm
index 05fa4d382a..6ae0a682d8 100644
--- a/code/modules/reagents/chemistry/machinery/pandemic.dm
+++ b/code/modules/reagents/chemistry/machinery/pandemic.dm
@@ -237,10 +237,11 @@
return ..()
/obj/machinery/computer/pandemic/AltClick(mob/living/user)
+ . = ..()
if(!istype(user) || !user.canUseTopic(src, BE_CLOSE, FALSE, NO_TK))
return
replace_beaker(user)
- return
+ return TRUE
/obj/machinery/computer/pandemic/proc/replace_beaker(mob/living/user, obj/item/reagent_containers/new_beaker)
if(beaker)
diff --git a/code/modules/reagents/chemistry/reagents/alcohol_reagents.dm b/code/modules/reagents/chemistry/reagents/alcohol_reagents.dm
index bd60880324..fd28ff9c47 100644
--- a/code/modules/reagents/chemistry/reagents/alcohol_reagents.dm
+++ b/code/modules/reagents/chemistry/reagents/alcohol_reagents.dm
@@ -199,17 +199,17 @@ All effects don't start immediately, but rather get worse over time; the rate is
to_chat(M, "[pick("You have a really bad headache.", "Your eyes hurt.", "You find it hard to stay still.", "You feel your heart practically beating out of your chest.")]")
if(prob(5) && iscarbon(M))
+ var/obj/item/organ/eyes/eyes = M.getorganslot(ORGAN_SLOT_EYES)
if(HAS_TRAIT(M, TRAIT_BLIND))
- var/obj/item/organ/eyes/eye = M.getorganslot(ORGAN_SLOT_EYES)
- if(istype(eye))
- eye.Remove(M)
- eye.forceMove(get_turf(M))
+ if(eyes)
+ eyes.Remove(M)
+ eyes.forceMove(get_turf(M))
to_chat(M, "You double over in pain as you feel your eyeballs liquify in your head!")
M.emote("scream")
M.adjustBruteLoss(15)
else
to_chat(M, "You scream in terror as you go blind!")
- M.become_blind(EYE_DAMAGE)
+ eyes?.applyOrganDamage(eyes.maxHealth)
M.emote("scream")
if(prob(3) && iscarbon(M))
@@ -610,6 +610,8 @@ All effects don't start immediately, but rather get worse over time; the rate is
value = 1.3
/datum/reagent/consumable/ethanol/bloody_mary/on_mob_life(mob/living/carbon/C)
+ if((HAS_TRAIT(C, TRAIT_NOMARROW)))
+ return
if(C.blood_volume < (BLOOD_VOLUME_NORMAL*C.blood_ratio))
C.blood_volume = min((BLOOD_VOLUME_NORMAL*C.blood_ratio), C.blood_volume + 3) //Bloody Mary quickly restores blood loss.
..()
diff --git a/code/modules/reagents/chemistry/reagents/medicine_reagents.dm b/code/modules/reagents/chemistry/reagents/medicine_reagents.dm
index f31613702c..60c5cd6e71 100644
--- a/code/modules/reagents/chemistry/reagents/medicine_reagents.dm
+++ b/code/modules/reagents/chemistry/reagents/medicine_reagents.dm
@@ -355,6 +355,8 @@ datum/reagent/medicine/styptic_powder/overdose_start(mob/living/M)
value = 1
/datum/reagent/medicine/salglu_solution/on_mob_life(mob/living/carbon/M)
+ if((HAS_TRAIT(M, TRAIT_NOMARROW)))
+ return
if(last_added)
M.blood_volume -= last_added
last_added = 0
@@ -793,6 +795,7 @@ datum/reagent/medicine/styptic_powder/overdose_start(mob/living/M)
var/obj/item/organ/eyes/eyes = M.getorganslot(ORGAN_SLOT_EYES)
if (!eyes)
return
+ eyes.applyOrganDamage(-2)
if(HAS_TRAIT_FROM(M, TRAIT_BLIND, EYE_DAMAGE))
if(prob(20))
to_chat(M, "Your vision slowly returns...")
@@ -807,8 +810,6 @@ datum/reagent/medicine/styptic_powder/overdose_start(mob/living/M)
else if(M.eye_blind || M.eye_blurry)
M.set_blindness(0)
M.set_blurriness(0)
- else if(eyes.eye_damage > 0)
- M.adjust_eye_damage(-1)
..()
/datum/reagent/medicine/atropine
diff --git a/code/modules/reagents/chemistry/reagents/other_reagents.dm b/code/modules/reagents/chemistry/reagents/other_reagents.dm
index bb65392057..512b8a21a1 100644
--- a/code/modules/reagents/chemistry/reagents/other_reagents.dm
+++ b/code/modules/reagents/chemistry/reagents/other_reagents.dm
@@ -1,5 +1,5 @@
/datum/reagent/blood
- data = list("donor"=null,"viruses"=null,"blood_DNA"=null, "bloodcolor" = BLOOD_COLOR_HUMAN, "blood_type"= null,"resistances"=null,"trace_chem"=null,"mind"=null,"ckey"=null,"gender"=null,"real_name"=null,"cloneable"=null,"factions"=null)
+ data = list("donor"=null,"viruses"=null,"blood_DNA"=null, "bloodcolor" = BLOOD_COLOR_HUMAN, "blood_type"= null,"resistances"=null,"trace_chem"=null,"mind"=null,"ckey"=null,"gender"=null,"real_name"=null,"cloneable"=null,"factions"=null,"quirks"=null)
name = "Blood"
id = "blood"
value = 1
@@ -30,7 +30,7 @@
if(iscarbon(L))
var/mob/living/carbon/C = L
var/blood_id = C.get_blood_id()
- if((blood_id == "blood" || blood_id == "jellyblood") && (method == INJECT || (method == INGEST && C.dna && C.dna.species && (DRINKSBLOOD in C.dna.species.species_traits))))
+ if((HAS_TRAIT(C, TRAIT_NOMARROW) || blood_id == "blood" || blood_id == "jellyblood") && (method == INJECT || (method == INGEST && C.dna && C.dna.species && (DRINKSBLOOD in C.dna.species.species_traits))))
C.blood_volume = min(C.blood_volume + round(reac_volume, 0.1), BLOOD_VOLUME_MAXIMUM * C.blood_ratio)
// we don't care about bloodtype here, we're just refilling the mob
@@ -38,6 +38,8 @@
L.add_blood_DNA(list(data["blood_DNA"] = data["blood_type"]))
/datum/reagent/blood/on_mob_life(mob/living/carbon/C) //Because lethals are preferred over stamina. damnifino.
+ if((HAS_TRAIT(C, TRAIT_NOMARROW)))
+ return //We dont want vampires getting toxed from blood
var/blood_id = C.get_blood_id()
if((blood_id == "blood" || blood_id == "jellyblood"))
if(!data || !(data["blood_type"] in get_safe_blood(C.dna.blood_type))) //we only care about bloodtype here because this is where the poisoning should be
@@ -1116,6 +1118,8 @@
color = "#c2391d"
/datum/reagent/iron/on_mob_life(mob/living/carbon/C)
+ if((HAS_TRAIT(C, TRAIT_NOMARROW)))
+ return
if(C.blood_volume < (BLOOD_VOLUME_NORMAL*C.blood_ratio))
C.blood_volume += 0.01 //we'll have synthetics from medbay.
..()
diff --git a/code/modules/reagents/reagent_containers/blood_pack.dm b/code/modules/reagents/reagent_containers/blood_pack.dm
index fe35981bfe..61449fc153 100644
--- a/code/modules/reagents/reagent_containers/blood_pack.dm
+++ b/code/modules/reagents/reagent_containers/blood_pack.dm
@@ -107,8 +107,29 @@
else
return ..()
+/obj/item/reagent_containers/blood/attack(mob/M, mob/user, def_zone)
+ if(user.a_intent == INTENT_HELP && reagents.total_volume > 0)
+ if (user != M)
+ to_chat(user, "You force [M] to drink from the [src]")
+ user.visible_message("[user] forces [M] to drink from the [src].")
+ if(!do_mob(user, M, 50))
+ return
+ else
+ if(!do_mob(user, M, 10))
+ return
+ to_chat(user, "You take a sip from the [src].")
+ user.visible_message("[user] puts the [src] up to their mouth.")
+ if(reagents.total_volume <= 0) // Safety: In case you spam clicked the blood bag on yourself, and it is now empty (below will divide by zero)
+ return
+ var/gulp_size = 5
+ var/fraction = min(gulp_size / reagents.total_volume, 1)
+ reagents.reaction(M, INGEST, fraction) //checkLiked(fraction, M) // Blood isn't food, sorry.
+ reagents.trans_to(M, gulp_size)
+ playsound(M.loc,'sound/items/drink.ogg', rand(10,50), 1)
+ ..()
+
/obj/item/reagent_containers/blood/bluespace
name = "bluespace blood pack"
desc = "Contains blood used for transfusion, this one has been made with bluespace technology to hold much more blood. Must be attached to an IV drip."
icon_state = "bsbloodpack"
- volume = 600 //its a blood bath!
\ No newline at end of file
+ volume = 600 //its a blood bath!
diff --git a/code/modules/reagents/reagent_containers/hypospray.dm b/code/modules/reagents/reagent_containers/hypospray.dm
index 85cb544667..f1a4d1554e 100644
--- a/code/modules/reagents/reagent_containers/hypospray.dm
+++ b/code/modules/reagents/reagent_containers/hypospray.dm
@@ -338,8 +338,10 @@
return FALSE
/obj/item/hypospray/mkii/AltClick(mob/user)
+ . = ..()
if(vial)
vial.attack_self(user)
+ return TRUE
// Gunna allow this for now, still really don't approve - Pooj
/obj/item/hypospray/mkii/emag_act(mob/user)
diff --git a/code/modules/reagents/reagent_containers/rags.dm b/code/modules/reagents/reagent_containers/rags.dm
index 414208d0bf..d09b18f244 100644
--- a/code/modules/reagents/reagent_containers/rags.dm
+++ b/code/modules/reagents/reagent_containers/rags.dm
@@ -104,6 +104,7 @@
msg += "'s liquids into \the [target]"
reagents.trans_to(target, reagents.total_volume)
to_chat(user, "[msg].")
+ return TRUE
/obj/item/reagent_containers/rag/towel
diff --git a/code/modules/recycling/conveyor2.dm b/code/modules/recycling/conveyor2.dm
index 5875e821a3..7701e58616 100644
--- a/code/modules/recycling/conveyor2.dm
+++ b/code/modules/recycling/conveyor2.dm
@@ -135,6 +135,9 @@ GLOBAL_LIST_EMPTY(conveyors_by_id)
addtimer(CALLBACK(src, .proc/convey, affecting), 1)
/obj/machinery/conveyor/proc/convey(list/affecting)
+ var/turf/T = get_step(src, movedir)
+ if(length(T.contents) > 150)
+ return
for(var/atom/movable/A in affecting)
if((A.loc == loc) && A.has_gravity())
A.ConveyorMove(movedir)
diff --git a/code/modules/research/designs/mechfabricator_designs.dm b/code/modules/research/designs/mechfabricator_designs.dm
index e8c8bab09f..113a6262c9 100644
--- a/code/modules/research/designs/mechfabricator_designs.dm
+++ b/code/modules/research/designs/mechfabricator_designs.dm
@@ -696,15 +696,6 @@
construction_time = 120
category = list("Cyborg Upgrade Modules")
-/datum/design/borg_upgrade_defibrillator
- name = "Cyborg Upgrade (Defibrillator)"
- id = "borg_upgrade_defibrillator"
- build_type = MECHFAB
- build_path = /obj/item/borg/upgrade/defib
- materials = list(MAT_METAL=15000, MAT_GLASS=15000, MAT_SILVER=10000, MAT_GOLD=10000, MAT_TITANIUM=5000, MAT_DIAMOND=5000)
- construction_time = 120
- category = list("Cyborg Upgrade Modules")
-
/datum/design/borg_upgrade_surgicalprocessor
name = "Cyborg Upgrade (Surgical Processor)"
id = "borg_upgrade_surgicalprocessor"
diff --git a/code/modules/research/nanites/nanite_hijacker.dm b/code/modules/research/nanites/nanite_hijacker.dm
index 88779df447..920c42b411 100644
--- a/code/modules/research/nanites/nanite_hijacker.dm
+++ b/code/modules/research/nanites/nanite_hijacker.dm
@@ -14,6 +14,7 @@
return
if(disk)
eject()
+ return TRUE
/obj/item/nanite_hijacker/examine(mob/user)
. = ..()
diff --git a/code/modules/research/nanites/nanite_programs/healing.dm b/code/modules/research/nanites/nanite_programs/healing.dm
index 50fb5efcb0..e2e1661ab7 100644
--- a/code/modules/research/nanites/nanite_programs/healing.dm
+++ b/code/modules/research/nanites/nanite_programs/healing.dm
@@ -98,7 +98,7 @@
/datum/nanite_program/blood_restoring/check_conditions()
if(iscarbon(host_mob))
var/mob/living/carbon/C = host_mob
- if(C.blood_volume >= (BLOOD_VOLUME_SAFE*C.blood_ratio))
+ if(C.blood_volume >= (BLOOD_VOLUME_SAFE*C.blood_ratio) || (HAS_TRAIT(C, TRAIT_NOMARROW)))
return FALSE
else
return FALSE
diff --git a/code/modules/research/nanites/nanite_remote.dm b/code/modules/research/nanites/nanite_remote.dm
index 3b242d28df..a7c8533521 100644
--- a/code/modules/research/nanites/nanite_remote.dm
+++ b/code/modules/research/nanites/nanite_remote.dm
@@ -35,6 +35,7 @@
update_icon()
else
to_chat(user, "Access denied.")
+ return TRUE
/obj/item/nanite_remote/emag_act(mob/user)
. = ..()
diff --git a/code/modules/research/techweb/all_nodes.dm b/code/modules/research/techweb/all_nodes.dm
index 0761354939..05eeb564e5 100644
--- a/code/modules/research/techweb/all_nodes.dm
+++ b/code/modules/research/techweb/all_nodes.dm
@@ -400,7 +400,7 @@
display_name = "Cyborg Upgrades: Medical"
description = "Medical upgrades for cyborgs."
prereq_ids = list("adv_biotech", "robotics")
- design_ids = list("borg_upgrade_defibrillator", "borg_upgrade_advhealth", "borg_upgrade_piercinghypospray", "borg_upgrade_highstrengthsynthesiser", "borg_upgrade_expandedsynthesiser", "borg_upgrade_pinpointer", "borg_upgrade_surgicalprocessor")
+ design_ids = list("borg_upgrade_advhealth", "borg_upgrade_piercinghypospray", "borg_upgrade_highstrengthsynthesiser", "borg_upgrade_expandedsynthesiser", "borg_upgrade_pinpointer", "borg_upgrade_surgicalprocessor")
research_costs = list(TECHWEB_POINT_TYPE_GENERIC = 2000)
export_price = 5000
@@ -1043,14 +1043,21 @@
display_name = "Alien Technology"
description = "Things used by the greys."
prereq_ids = list("biotech","engineering")
- boost_item_paths = list(/obj/item/gun/energy/alien, /obj/item/scalpel/alien, /obj/item/hemostat/alien, /obj/item/retractor/alien, /obj/item/circular_saw/alien,
- /obj/item/cautery/alien, /obj/item/surgicaldrill/alien, /obj/item/screwdriver/abductor, /obj/item/wrench/abductor, /obj/item/crowbar/abductor, /obj/item/multitool/abductor, /obj/item/stock_parts/cell/infinite/abductor,
- /obj/item/weldingtool/abductor, /obj/item/wirecutters/abductor, /obj/item/circuitboard/machine/abductor, /obj/item/abductor, /obj/item/stack/sheet/mineral/abductor, /obj/item/gun/energy/shrink_ray)
research_costs = list(TECHWEB_POINT_TYPE_GENERIC = 5000)
export_price = 20000
hidden = TRUE
design_ids = list("alienalloy")
+/datum/techweb_node/alientech/New()
+ . = ..()
+ boost_item_paths = typesof(/obj/item/gun/energy/alien, /obj/item/scalpel/alien, /obj/item/hemostat/alien,
+ /obj/item/retractor/alien, /obj/item/circular_saw/alien, /obj/item/cautery/alien,
+ /obj/item/surgicaldrill/alien, /obj/item/screwdriver/abductor, /obj/item/wrench/abductor,
+ /obj/item/crowbar/abductor, /obj/item/multitool/abductor,
+ /obj/item/stock_parts/cell/infinite/abductor, /obj/item/weldingtool/abductor,
+ /obj/item/wirecutters/abductor, /obj/item/circuitboard/machine/abductor,
+ /obj/item/abductor, /obj/item/stack/sheet/mineral/abductor)
+
/datum/techweb_node/alien_bio
id = "alien_bio"
display_name = "Alien Biological Tools"
diff --git a/code/modules/ruins/spaceruin_code/hilbertshotel.dm b/code/modules/ruins/spaceruin_code/hilbertshotel.dm
index cd641adb33..f64b5e4d01 100644
--- a/code/modules/ruins/spaceruin_code/hilbertshotel.dm
+++ b/code/modules/ruins/spaceruin_code/hilbertshotel.dm
@@ -284,6 +284,7 @@ GLOBAL_VAR_INIT(hhmysteryRoomNumber, 1337)
var/datum/action/peepholeCancel/PHC = new
user.overlay_fullscreen("remote_view", /obj/screen/fullscreen/impaired, 1)
PHC.Grant(user)
+ return TRUE
/turf/closed/indestructible/hoteldoor/check_eye(mob/user)
if(get_dist(get_turf(src), get_turf(user)) >= 2)
diff --git a/code/modules/spells/spell_types/godhand.dm b/code/modules/spells/spell_types/godhand.dm
index a5f371ffc2..8108b121ae 100644
--- a/code/modules/spells/spell_types/godhand.dm
+++ b/code/modules/spells/spell_types/godhand.dm
@@ -182,3 +182,38 @@
if(charges <= 0)
qdel(src)
+
+/obj/item/melee/touch_attack/nuclearfist
+ name = "\improper PURE MANLINESS"
+ desc = "SHOW THEM RAW POWER"
+ catchphrase = "I CAST FIST!"
+ on_use_sound = 'sound/weapons/nuclear_fist.ogg'
+ icon_state = "disintegrate"
+ item_state = "disintegrate"
+
+/obj/item/melee/touch_attack/nuclearfist/afterattack(atom/movable/target, mob/living/carbon/user, proximity)
+ if(!proximity || target == user || !ismob(target) || !iscarbon(user) || user.lying || user.handcuffed) //exploding after touching yourself would be bad
+ return
+ if(!user.can_speak_vocal())
+ to_chat(user, "You can't get the words out!")
+ return
+ var/mob/M = target
+ var/atom/A = M.anti_magic_check()
+ if(A)
+ if(isitem(A))
+ target.visible_message("[target]'s [A] glows brightly as it wards off the spell!")
+ user.visible_message("The feedback blows [user]'s arm off!","The spell bounces from [M]'s skin back into your arm!")
+ user.flash_act()
+ var/obj/item/bodypart/part = user.get_holding_bodypart_of_item(src)
+ if(part)
+ part.dismember()
+ return ..()
+ var/angle = dir2angle(get_dir(src, get_step_away(target, src)))
+ var/obj/item/projectile/magic/nuclear/P = new(get_turf(src))
+ P.victim = target
+ target.forceMove(P)
+ P.setAngle(angle)
+ P.original = user
+ P.firer = user
+ P.fire()
+ return ..()
\ No newline at end of file
diff --git a/code/modules/spells/spell_types/touch_attacks.dm b/code/modules/spells/spell_types/touch_attacks.dm
index 0ffe02cec2..794ed797ad 100644
--- a/code/modules/spells/spell_types/touch_attacks.dm
+++ b/code/modules/spells/spell_types/touch_attacks.dm
@@ -23,7 +23,7 @@
remove_hand(TRUE)
to_chat(user, "You draw the power out of your hand.")
return
-
+
for(var/mob/living/carbon/C in targets)
if(!attached_hand)
if(ChargeHand(C))
@@ -71,3 +71,15 @@
action_icon_state = "statue"
sound = 'sound/magic/fleshtostone.ogg'
+
+/obj/effect/proc_holder/spell/targeted/touch/nuclear_fist
+ name = "Nuclear Fist"
+ desc = "This spell channels raw manliness, allowing you punch your enemies across the galaxy, causing them to detonate violently if hitting any other living being midflight. Does not work while laying down."
+ hand_path = /obj/item/melee/touch_attack/nuclearfist
+
+ school = "evocation"
+ charge_max = 200
+ clothes_req = 0
+ cooldown_min = 40
+
+ action_icon_state = "nuclearfist"
\ No newline at end of file
diff --git a/code/modules/surgery/eye_surgery.dm b/code/modules/surgery/eye_surgery.dm
index f7a06af388..28e813ccac 100644
--- a/code/modules/surgery/eye_surgery.dm
+++ b/code/modules/surgery/eye_surgery.dm
@@ -22,6 +22,7 @@
"[user] begins to perform surgery on [target]'s eyes.")
/datum/surgery_step/fix_eyes/success(mob/user, mob/living/carbon/target, target_zone, obj/item/tool, datum/surgery/surgery)
+ var/obj/item/organ/eyes/E = target.getorganslot(ORGAN_SLOT_EYES)
display_results(user, target, "You succeed in fixing [target]'s eyes.",
"[user] successfully fixes [target]'s eyes!",
"[user] completes the surgery on [target]'s eyes.")
@@ -29,7 +30,7 @@
target.set_blindness(0)
target.cure_nearsighted(list(EYE_DAMAGE))
target.blur_eyes(35) //this will fix itself slowly.
- target.set_eye_damage(0)
+ E.setOrganDamage(0)
return TRUE
/datum/surgery_step/fix_eyes/failure(mob/user, mob/living/carbon/target, target_zone, obj/item/tool, datum/surgery/surgery)
diff --git a/code/modules/surgery/organs/augments_chest.dm b/code/modules/surgery/organs/augments_chest.dm
index 81627104e8..65172a1545 100644
--- a/code/modules/surgery/organs/augments_chest.dm
+++ b/code/modules/surgery/organs/augments_chest.dm
@@ -44,6 +44,9 @@
hunger_threshold = NUTRITION_LEVEL_HUNGRY
poison_amount = 10
+#define MAX_HEAL_COOLDOWN 15 MINUTES
+#define DEF_CONVALESCENCE_TIME 15 SECONDS
+
/obj/item/organ/cyberimp/chest/reviver
name = "Reviver implant"
desc = "This implant will attempt to revive and heal you if you lose consciousness. For the faint of heart!"
@@ -51,43 +54,49 @@
implant_color = "#AD0000"
slot = ORGAN_SLOT_HEART_AID
var/revive_cost = 0
- var/reviving = 0
+ var/reviving = FALSE
var/cooldown = 0
+ var/convalescence_time = 0
/obj/item/organ/cyberimp/chest/reviver/on_life()
if(reviving)
- if(owner.stat == UNCONSCIOUS)
- addtimer(CALLBACK(src, .proc/heal), 30)
+ var/do_heal = world.time < convalescence_time
+ if(revive_cost >= MAX_HEAL_COOLDOWN)
+ do_heal = FALSE
+ else if(owner.stat && owner.stat != DEAD)
+ do_heal = TRUE
+ else if(!do_heal)
+ convalescence_time = world.time + DEF_CONVALESCENCE_TIME
+ if(do_heal)
+ addtimer(CALLBACK(src, .proc/heal), 3 SECONDS)
else
cooldown = revive_cost + world.time
reviving = FALSE
to_chat(owner, "Your reviver implant shuts down and starts recharging. It will be ready again in [DisplayTimeText(revive_cost)].")
return
- if(cooldown > world.time)
- return
- if(owner.stat != UNCONSCIOUS)
- return
- if(owner.suiciding)
+ if(cooldown > world.time || owner.stat == CONSCIOUS || owner.stat == DEAD || owner.suiciding)
return
revive_cost = 0
+ convalescence_time = 0
reviving = TRUE
to_chat(owner, "You feel a faint buzzing as your reviver implant starts patching your wounds...")
/obj/item/organ/cyberimp/chest/reviver/proc/heal()
if(owner.getOxyLoss())
owner.adjustOxyLoss(-5)
- revive_cost += 5
+ revive_cost += 0.5 SECONDS
if(owner.getBruteLoss())
owner.adjustBruteLoss(-2)
- revive_cost += 40
+ revive_cost += 4 SECONDS
if(owner.getFireLoss())
owner.adjustFireLoss(-2)
- revive_cost += 40
+ revive_cost += 4 SECONDS
if(owner.getToxLoss())
owner.adjustToxLoss(-1)
- revive_cost += 40
+ revive_cost += 4 SECONDS
+
/obj/item/organ/cyberimp/chest/reviver/emp_act(severity)
. = ..()
@@ -95,25 +104,27 @@
return
if(reviving)
- revive_cost += 200
+ revive_cost += 20 SECONDS
else
- cooldown += 200
+ cooldown += 20 SECONDS
if(ishuman(owner))
var/mob/living/carbon/human/H = owner
if(H.stat != DEAD && prob(50 / severity) && H.can_heartattack())
H.set_heartattack(TRUE)
to_chat(H, "You feel a horrible agony in your chest!")
- addtimer(CALLBACK(src, .proc/undo_heart_attack), 600 / severity)
+ addtimer(CALLBACK(src, .proc/undo_heart_attack), 60 SECONDS / severity)
/obj/item/organ/cyberimp/chest/reviver/proc/undo_heart_attack()
var/mob/living/carbon/human/H = owner
- if(!istype(H))
+ if(!H || !istype(H))
return
H.set_heartattack(FALSE)
- if(H.stat == CONSCIOUS)
+ if(H.stat == CONSCIOUS || H.stat == SOFT_CRIT)
to_chat(H, "You feel your heart beating again!")
+#undef MAX_HEAL_COOLDOWN
+#undef DEF_CONVALESCENCE_TIME
/obj/item/organ/cyberimp/chest/thrusters
name = "implantable thrusters set"
diff --git a/code/modules/surgery/organs/eyes.dm b/code/modules/surgery/organs/eyes.dm
index 34943babdc..c61f1a8122 100644
--- a/code/modules/surgery/organs/eyes.dm
+++ b/code/modules/surgery/organs/eyes.dm
@@ -21,7 +21,6 @@
var/sight_flags = 0
var/see_in_dark = 2
- var/eye_damage = 0
var/tint = 0
var/eye_color = "" //set to a hex code to override a mob's eye color
var/old_eye_color = "fff"
diff --git a/code/modules/surgery/organs/heart.dm b/code/modules/surgery/organs/heart.dm
index 212605669d..5b74b58cd0 100644
--- a/code/modules/surgery/organs/heart.dm
+++ b/code/modules/surgery/organs/heart.dm
@@ -55,6 +55,11 @@
update_icon()
return 1
+/obj/item/organ/heart/proc/HeartStrengthMessage()
+ if(beating)
+ return "a healthy"
+ return "an unstable"
+
/obj/item/organ/heart/prepare_eat()
var/obj/S = ..()
S.icon_state = "[icon_base]-off"
diff --git a/code/modules/surgery/organs/tongue.dm b/code/modules/surgery/organs/tongue.dm
index b5f5d12ac8..760cd97f08 100644
--- a/code/modules/surgery/organs/tongue.dm
+++ b/code/modules/surgery/organs/tongue.dm
@@ -22,6 +22,7 @@
/datum/language/ratvar,
/datum/language/aphasia,
/datum/language/slime,
+ /datum/language/vampiric,
))
healing_factor = STANDARD_ORGAN_HEALING*5 //Fast!!
decay_factor = STANDARD_ORGAN_DECAY/2
diff --git a/code/modules/vehicles/ridden.dm b/code/modules/vehicles/ridden.dm
index 27da0f6cea..13a139ef2e 100644
--- a/code/modules/vehicles/ridden.dm
+++ b/code/modules/vehicles/ridden.dm
@@ -49,6 +49,7 @@
return ..()
/obj/vehicle/ridden/AltClick(mob/user)
+ . = ..()
if(inserted_key && user.canUseTopic(src, BE_CLOSE, ismonkey(user)))
if(!is_occupant(user))
to_chat(user, "You must be riding the [src] to remove [src]'s key!")
@@ -57,7 +58,7 @@
inserted_key.forceMove(drop_location())
user.put_in_hands(inserted_key)
inserted_key = null
- return ..()
+ return TRUE
/obj/vehicle/ridden/driver_move(mob/user, direction)
if(key_type && !is_key(inserted_key))
diff --git a/code/modules/vehicles/scooter.dm b/code/modules/vehicles/scooter.dm
index 8c21b050aa..0dd7ff32a8 100644
--- a/code/modules/vehicles/scooter.dm
+++ b/code/modules/vehicles/scooter.dm
@@ -94,6 +94,7 @@
qdel(src)
/obj/vehicle/ridden/scooter/skateboard/AltClick(mob/user)
+ . = ..()
var/datum/component/riding/R = src.GetComponent(/datum/component/riding)
if (!adjusted_speed)
R.vehicle_move_delay = 0
@@ -103,6 +104,7 @@
R.vehicle_move_delay = 1
to_chat(user, "You adjust the wheels on [src] to make it go slower.")
adjusted_speed = FALSE
+ return TRUE
//CONSTRUCTION
/obj/item/scooter_frame
diff --git a/code/modules/vending/clothesmate.dm b/code/modules/vending/clothesmate.dm
index 2b86d9e415..626abd51ef 100644
--- a/code/modules/vending/clothesmate.dm
+++ b/code/modules/vending/clothesmate.dm
@@ -115,8 +115,18 @@
/obj/item/clothing/ears/headphones = 10,
/obj/item/clothing/suit/apron/purple_bartender = 4,
/obj/item/clothing/under/rank/bartender/purple = 4,
- /obj/item/clothing/accessory/attrocious_pokadots = 8,
- /obj/item/clothing/accessory/black_white_pokadots = 8)
+ /obj/item/clothing/under/christmas/christmasmaler = 3,
+ /obj/item/clothing/under/christmas/christmasmaleg = 3,
+ /obj/item/clothing/under/christmas/christmasfemaler = 3,
+ /obj/item/clothing/under/christmas/christmasfemaleg = 3,
+ /obj/item/clothing/suit/hooded/wintercoat/christmascoatr = 3,
+ /obj/item/clothing/suit/hooded/wintercoat/christmascoatg = 3,
+ /obj/item/clothing/suit/hooded/wintercoat/christmascoatrg = 3,
+ /obj/item/clothing/head/christmashat = 3,
+ /obj/item/clothing/head/christmashatg = 3,
+ /obj/item/clothing/shoes/winterboots/christmasbootsr = 3,
+ /obj/item/clothing/shoes/winterboots/christmasbootsg = 3,
+ /obj/item/clothing/shoes/winterboots/santaboots = 3)
contraband = list(/obj/item/clothing/under/syndicate/tacticool = 3,
/obj/item/clothing/under/syndicate/tacticool/skirt = 3,
/obj/item/clothing/mask/balaclava = 3,
@@ -126,8 +136,7 @@
/obj/item/clothing/suit/jacket/letterman_syndie = 5,
/obj/item/clothing/under/jabroni = 2,
/obj/item/clothing/suit/vapeshirt = 2,
- /obj/item/clothing/under/geisha = 4,
- /obj/item/clothing/accessory/syndi_pokadots = 4,
+ /obj/item/clothing/under/geisha = 4,,
/obj/item/clothing/under/keyholesweater = 3)
premium = list(/obj/item/clothing/under/suit_jacket/checkered = 4,
/obj/item/clothing/head/mailman = 2,
@@ -136,8 +145,7 @@
/obj/item/clothing/suit/jacket/leather/overcoat = 4,
/obj/item/clothing/under/pants/mustangjeans = 3,
/obj/item/clothing/neck/necklace/dope = 5,
- /obj/item/clothing/suit/jacket/letterman_nanotrasen = 5,
- /obj/item/clothing/accessory/nt_pokadots = 5)
+ /obj/item/clothing/suit/jacket/letterman_nanotrasen = 5)
refill_canister = /obj/item/vending_refill/clothing
/obj/item/vending_refill/clothing
diff --git a/config/game_options.txt b/config/game_options.txt
index a44c68226e..3b031bb5b9 100644
--- a/config/game_options.txt
+++ b/config/game_options.txt
@@ -92,6 +92,7 @@ PROBABILITY SECRET_EXTENDED 0
PROBABILITY DEVIL 0
PROBABILITY DEVIL_AGENTS 0
PROBABILITY CLOWNOPS 0
+PROBABILITY BLOODSUCKER 0
## You probably want to keep sandbox off by default for secret and random.
PROBABILITY SANDBOX 0
@@ -114,6 +115,7 @@ CONTINUOUS CLOCKWORK_CULT
CONTINUOUS CHANGELING
CONTINUOUS WIZARD
#CONTINUOUS MONKEY
+CONTINUOUS BLOODSUCKER
##Note: do not toggle continuous off for these modes, as they have no antagonists and would thus end immediately!
diff --git a/dependencies.sh b/dependencies.sh
index a75940088f..0f66dcab1a 100644
--- a/dependencies.sh
+++ b/dependencies.sh
@@ -18,3 +18,9 @@ export BSQL_VERSION=v1.4.0.0
#node version
export NODE_VERSION=8
+
+# PHP version
+export PHP_VERSION=5.6
+
+# SpacemanDMM git tag
+export SPACEMAN_DMM_VERSION=suite-1.0
diff --git a/html/changelog.html b/html/changelog.html
index 61bc43a99b..8eabba83df 100644
--- a/html/changelog.html
+++ b/html/changelog.html
@@ -23,11 +23,9 @@
+
+ 07 December 2019+AffectedArc07 updated:+
Anonymous updated:+
Arturlang updated:+
Bhijn updated:+
Coconutwarrior97 updated:+
Commandersand updated:+
DeltaFire15 updated:+
Fermis updated:+
Fox McCloud updated:+
Fox McCloud, Ghommie updated:+
Ghommie updated:+
Ghommie (original PR by Denton) updated:+
Ghommie (original PR by nicbn and Menshin) updated:+
Ghommie (original PRs by ShizCalev, CRTXBacon and Niknakflak) updated:+
Ghommie, porting lot of PRs by MrDoomBringer, AnturK, nemvar and coiax. updated:+
GrayRachnid updated:+
Hatterhat updated:+
Iroquois-Pliskin updated:+
Jerry Derpington, baldest of the balds, and nemvar. updated:+
KathrinBailey updated:+
Kevinz000, Cruix, MrStonedOne, Denton, Kmc2000, Anturk, MrDoomBringer, Dennok, TheChosenEvilOne, Ghommie updated:+
Knouli updated:+
Krysonism, Ghommie updated:+
Linzolle updated:+
Mickyy5 updated:+
MrJWhit updated:+
Naksu, ShizCalev updated:+
Owai-Seek updated:+
PersianXerxes updated:+
Putnam updated:+
Putnam3145 updated:+
Robustin, Subject217 updated:+
Seris02 updated:+
ShizCalev updated:+
Swindly updated:+
Tetr4 updated:+
Trilbyspaceclone updated:+
Ty-the-Smonk updated:+
Useroth updated:+
Weblure updated:+
XDTM, ShizCalev, Naksu, Skoglol, cacogen, Rohesie (ported by Ghommie) updated:+
Xantholne updated:+
YakumoChen updated:+
actioninja updated:+
dapnee updated:+
dzahlus updated:+
him updated:+
kappa-sama updated:+
kevinz000 updated:+
kiwedespars updated:+
nemvar updated:+
nemvar, ShizCalev, Qustinnus/Floyd, Ghommie updated:+
r4d6 updated:+
04 November 20194dplanner, MMiracles updated:
|