diff --git a/.github/workflows/ci_suite.yml b/.github/workflows/ci_suite.yml
index 6e7f97cf53035..6dfb59a563434 100644
--- a/.github/workflows/ci_suite.yml
+++ b/.github/workflows/ci_suite.yml
@@ -288,10 +288,11 @@ jobs:
completion_gate: # Serves as a non-moving target for branch rulesets
if: always() && !cancelled()
name: Completion Gate
- needs: [ test_windows, compare_screenshots, compile_all_maps, run_linters ]
+ needs: [ test_windows, compare_screenshots, compile_all_maps, run_all_tests, run_alternate_tests, run_linters ]
runs-on: ubuntu-latest
steps:
- name: Decide whether the needed jobs succeeded or failed
uses: re-actors/alls-green@release/v1
with:
jobs: ${{ toJSON(needs) }}
+ allowed-skips: compare_screenshots
diff --git a/.github/workflows/generate_documentation.yml b/.github/workflows/generate_documentation.yml
index 2062fb545689d..423c7f10ad61e 100644
--- a/.github/workflows/generate_documentation.yml
+++ b/.github/workflows/generate_documentation.yml
@@ -27,7 +27,7 @@ jobs:
touch dmdoc/.nojekyll
echo codedocs.tgstation13.org > dmdoc/CNAME
- name: Deploy
- uses: JamesIves/github-pages-deploy-action@v4.7.1
+ uses: JamesIves/github-pages-deploy-action@v4.7.2
with:
branch: gh-pages
clean: true
diff --git a/_maps/RandomRuins/IceRuins/icemoon_surface_phonebooth.dmm b/_maps/RandomRuins/IceRuins/icemoon_surface_phonebooth.dmm
index 905403954b67b..d252e77417aca 100644
--- a/_maps/RandomRuins/IceRuins/icemoon_surface_phonebooth.dmm
+++ b/_maps/RandomRuins/IceRuins/icemoon_surface_phonebooth.dmm
@@ -1,99 +1,119 @@
//MAP CONVERTED BY dmm2tgm.py THIS HEADER COMMENT PREVENTS RECONVERSION, DO NOT REMOVE
"a" = (
-/obj/machinery/holopad,
-/turf/open/floor/iron/dark/smooth_edge{
+/obj/machinery/atmospherics/components/unary/vent_pump/on/layer4{
dir = 4
},
+/turf/open/floor/iron/dark/smooth_large,
/area/ruin/powered/icemoon_phone_booth)
"c" = (
-/obj/machinery/door/window/left/directional/north,
-/obj/machinery/door/window/left/directional/south,
-/turf/open/floor/iron/dark/smooth_edge{
- dir = 4
- },
-/area/ruin/powered/icemoon_phone_booth)
+/turf/open/floor/plating/snowed/smoothed/icemoon,
+/area/icemoon/underground/explored)
"e" = (
+/obj/structure/lattice,
/turf/open/misc/asteroid/snow/icemoon,
-/area/ruin/powered/icemoon_phone_booth)
+/area/icemoon/underground/explored)
"k" = (
-/obj/machinery/vending/coffee,
+/obj/machinery/vending/cigarette{
+ all_products_free = 0
+ },
/obj/structure/window/reinforced/spawner/directional/east,
-/turf/open/floor/plating/icemoon,
-/area/ruin/powered/icemoon_phone_booth)
-"q" = (
-/obj/machinery/airalarm/directional/south,
/obj/effect/turf_decal/tile/yellow/half/contrasted{
- dir = 4
+ dir = 8
},
-/obj/machinery/atmospherics/components/tank/air/layer4{
+/obj/effect/turf_decal/weather/snow/corner{
dir = 8
},
-/obj/structure/window/reinforced/spawner/directional/west,
+/obj/effect/turf_decal/weather/snow,
/turf/open/floor/plating/icemoon,
/area/ruin/powered/icemoon_phone_booth)
-"v" = (
+"q" = (
/obj/structure/lattice,
/obj/structure/billboard/Phone_booth,
/turf/open/misc/asteroid/snow/icemoon,
-/area/ruin/powered/icemoon_phone_booth)
-"z" = (
-/obj/machinery/atmospherics/components/unary/vent_pump/on/layer4{
+/area/icemoon/underground/explored)
+"v" = (
+/obj/structure/window/reinforced/spawner/directional/west,
+/obj/machinery/light/small/directional/north,
+/obj/effect/turf_decal/tile/yellow/half/contrasted{
dir = 4
},
+/obj/item/gps/spaceruin{
+ gpstag = "Public Holophone"
+ },
+/obj/effect/turf_decal/weather/snow/corner{
+ dir = 5
+ },
+/obj/effect/turf_decal/weather/snow,
+/turf/open/floor/plating/icemoon,
+/area/ruin/powered/icemoon_phone_booth)
+"z" = (
+/obj/machinery/door/window/left/directional/north,
+/obj/machinery/door/window/left/directional/south,
+/obj/effect/turf_decal/weather/snow/corner,
/turf/open/floor/iron/dark/smooth_edge{
- dir = 4
+ dir = 1
},
/area/ruin/powered/icemoon_phone_booth)
"J" = (
/obj/effect/turf_decal/tile/yellow/half/contrasted{
dir = 4
},
-/obj/item/gps/spaceruin{
- gpstag = "Public Holophone"
- },
-/obj/machinery/light/small{
- dir = 1
+/obj/machinery/atmospherics/components/tank/air/layer4{
+ dir = 8
},
/obj/structure/window/reinforced/spawner/directional/west,
+/obj/effect/turf_decal/weather/snow/corner{
+ dir = 4
+ },
+/obj/effect/turf_decal/weather/snow,
/turf/open/floor/plating/icemoon,
/area/ruin/powered/icemoon_phone_booth)
"M" = (
/turf/closed/wall/ice,
/area/ruin/powered/icemoon_phone_booth)
"R" = (
-/obj/effect/spawner/structure/window/hollow/reinforced,
-/turf/open/floor/plating/icemoon,
+/obj/machinery/holopad,
+/obj/machinery/airalarm/directional/north,
+/obj/effect/mapping_helpers/airalarm/all_access,
+/turf/open/floor/iron/dark/smooth_large,
/area/ruin/powered/icemoon_phone_booth)
"V" = (
-/obj/structure/lattice,
-/turf/open/misc/asteroid/snow/icemoon,
-/area/ruin/powered/icemoon_phone_booth)
-"W" = (
-/obj/machinery/vending/cigarette{
- all_products_free = 0
- },
+/obj/machinery/vending/coffee,
/obj/structure/window/reinforced/spawner/directional/east,
+/obj/effect/turf_decal/tile/yellow/half/contrasted{
+ dir = 8
+ },
+/obj/effect/turf_decal/weather/snow/corner{
+ dir = 8
+ },
+/obj/effect/turf_decal/weather/snow,
/turf/open/floor/plating/icemoon,
/area/ruin/powered/icemoon_phone_booth)
+"W" = (
+/turf/open/misc/asteroid/snow/icemoon,
+/area/icemoon/underground/explored)
(1,1,1) = {"
-v
+q
M
+V
k
-W
M
+c
"}
(2,1,1) = {"
-e
+W
+M
R
a
z
c
"}
(3,1,1) = {"
-V
+e
M
+v
J
-q
M
+c
"}
diff --git a/_maps/RandomRuins/IceRuins/icemoon_underground_frozen_comms.dmm b/_maps/RandomRuins/IceRuins/icemoon_underground_frozen_comms.dmm
index eb537cd26f6c6..54b33b5e8111d 100644
--- a/_maps/RandomRuins/IceRuins/icemoon_underground_frozen_comms.dmm
+++ b/_maps/RandomRuins/IceRuins/icemoon_underground_frozen_comms.dmm
@@ -311,7 +311,7 @@
},
/obj/structure/rack,
/obj/item/wrench,
-/obj/item/crowbar/large/heavy,
+/obj/item/crowbar/large/twenty_force,
/obj/machinery/light/small/built/directional/south,
/turf/open/floor/plating/icemoon,
/area/ruin/powered/shuttle)
diff --git a/_maps/RandomRuins/IceRuins/icemoon_underground_syndidome.dmm b/_maps/RandomRuins/IceRuins/icemoon_underground_syndidome.dmm
index 228c55292fbb0..e41c2db686323 100644
--- a/_maps/RandomRuins/IceRuins/icemoon_underground_syndidome.dmm
+++ b/_maps/RandomRuins/IceRuins/icemoon_underground_syndidome.dmm
@@ -3503,7 +3503,7 @@
/area/ruin/syndibiodome)
"Pw" = (
/obj/structure/table/reinforced/plastitaniumglass,
-/obj/item/surgery_tray/full,
+/obj/effect/spawner/surgery_tray/full,
/obj/effect/turf_decal/siding/wideplating/dark{
dir = 5
},
diff --git a/_maps/RandomRuins/SpaceRuins/DJstation/kitchen_1.dmm b/_maps/RandomRuins/SpaceRuins/DJstation/kitchen_1.dmm
index 5d7b0c4bee510..4f032c8ac053b 100644
--- a/_maps/RandomRuins/SpaceRuins/DJstation/kitchen_1.dmm
+++ b/_maps/RandomRuins/SpaceRuins/DJstation/kitchen_1.dmm
@@ -30,7 +30,6 @@
/area/ruin/space/djstation)
"t" = (
/obj/structure/closet/secure_closet/freezer/fridge/all_access,
-/obj/machinery/light/directional/south,
/obj/effect/turf_decal/tile/bar/opposingcorners,
/obj/machinery/light/small/directional/south,
/turf/open/floor/iron,
diff --git a/_maps/RandomRuins/SpaceRuins/anomaly_research.dmm b/_maps/RandomRuins/SpaceRuins/anomaly_research.dmm
index c396ddb4121e7..ccbe450085dc9 100644
--- a/_maps/RandomRuins/SpaceRuins/anomaly_research.dmm
+++ b/_maps/RandomRuins/SpaceRuins/anomaly_research.dmm
@@ -351,7 +351,7 @@
"kp" = (
/obj/effect/spawner/random/environmentally_safe_anomaly/immobile,
/turf/template_noop,
-/area/space)
+/area/space/nearstation)
"kt" = (
/obj/effect/turf_decal/tile/purple/half/contrasted{
dir = 1
diff --git a/_maps/RandomRuins/SpaceRuins/bus.dmm b/_maps/RandomRuins/SpaceRuins/bus.dmm
index a8a4a968d976d..1ecb40291e33a 100644
--- a/_maps/RandomRuins/SpaceRuins/bus.dmm
+++ b/_maps/RandomRuins/SpaceRuins/bus.dmm
@@ -285,7 +285,7 @@
/turf/open/misc/asteroid/airless,
/area/ruin/space)
"ET" = (
-/mob/living/basic/lizard,
+/mob/living/basic/lizard/space,
/turf/open/misc/asteroid/airless,
/area/ruin/space)
"Fo" = (
diff --git a/_maps/RandomRuins/SpaceRuins/dangerous_research.dmm b/_maps/RandomRuins/SpaceRuins/dangerous_research.dmm
index 52a288e64e6d1..42fa98704a300 100644
--- a/_maps/RandomRuins/SpaceRuins/dangerous_research.dmm
+++ b/_maps/RandomRuins/SpaceRuins/dangerous_research.dmm
@@ -98,7 +98,7 @@
/turf/open/floor/iron/white,
/area/ruin/space/has_grav/dangerous_research/medical)
"aZ" = (
-/obj/item/surgery_tray/full,
+/obj/effect/spawner/surgery_tray/full,
/obj/structure/table,
/turf/open/floor/plating/rust,
/area/ruin/space/has_grav/dangerous_research/medical)
diff --git a/_maps/RandomRuins/SpaceRuins/garbagetruck4.dmm b/_maps/RandomRuins/SpaceRuins/garbagetruck4.dmm
index aa15bb27e8b98..35b18eff47ff8 100644
--- a/_maps/RandomRuins/SpaceRuins/garbagetruck4.dmm
+++ b/_maps/RandomRuins/SpaceRuins/garbagetruck4.dmm
@@ -122,7 +122,6 @@
/area/ruin/space/has_grav/garbagetruck/toystore)
"lm" = (
/obj/structure/spider/stickyweb,
-/obj/structure/spider/stickyweb/very_sticky,
/turf/open/floor/plating,
/area/ruin/space/has_grav/garbagetruck/toystore)
"mf" = (
@@ -182,7 +181,6 @@
/turf/open/floor/plating,
/area/ruin/space/has_grav/garbagetruck/toystore)
"qX" = (
-/obj/structure/spider/stickyweb/very_sticky,
/obj/item/food/badrecipe/moldy,
/obj/structure/spider/stickyweb,
/obj/item/food/spidereggs{
diff --git a/_maps/RandomRuins/SpaceRuins/interdyne.dmm b/_maps/RandomRuins/SpaceRuins/interdyne.dmm
index 9c802b0be434c..46e22d19fb67b 100644
--- a/_maps/RandomRuins/SpaceRuins/interdyne.dmm
+++ b/_maps/RandomRuins/SpaceRuins/interdyne.dmm
@@ -1015,7 +1015,7 @@
/turf/open/floor/mineral/plastitanium/red,
/area/ruin/space/has_grav/interdyne)
"PD" = (
-/obj/item/surgery_tray/full,
+/obj/effect/spawner/surgery_tray/full,
/obj/structure/table/reinforced/rglass,
/turf/open/floor/mineral/plastitanium,
/area/ruin/space/has_grav/interdyne)
diff --git a/_maps/RandomRuins/SpaceRuins/meatderelict.dmm b/_maps/RandomRuins/SpaceRuins/meatderelict.dmm
index 3e4bece11e8e5..96959954e2678 100644
--- a/_maps/RandomRuins/SpaceRuins/meatderelict.dmm
+++ b/_maps/RandomRuins/SpaceRuins/meatderelict.dmm
@@ -484,7 +484,7 @@
/obj/effect/turf_decal/siding/blue{
dir = 6
},
-/obj/item/surgery_tray/full/deployed,
+/obj/effect/spawner/surgery_tray/full/deployed,
/turf/open/indestructible/white,
/area/ruin/space/has_grav/powered/biooutpost)
"kh" = (
@@ -728,8 +728,8 @@
/obj/effect/turf_decal/siding/purple{
dir = 1
},
-/obj/item/ammo_casing/a357/spent,
-/obj/item/ammo_casing/a357/spent,
+/obj/item/ammo_casing/c357/spent,
+/obj/item/ammo_casing/c357/spent,
/turf/open/indestructible/white,
/area/ruin/space/has_grav/powered/biooutpost)
"oQ" = (
@@ -887,7 +887,7 @@
/obj/effect/turf_decal/siding/purple{
dir = 1
},
-/obj/item/ammo_casing/a357/spent,
+/obj/item/ammo_casing/c357/spent,
/turf/open/indestructible/white,
/area/ruin/space/has_grav/powered/biooutpost)
"rl" = (
@@ -1561,9 +1561,9 @@
/turf/open/indestructible/plating,
/area/ruin/space/has_grav/powered/biooutpost)
"DC" = (
-/obj/item/ammo_casing/a357/spent,
-/obj/item/ammo_casing/a357/spent,
-/obj/item/ammo_casing/a357/spent,
+/obj/item/ammo_casing/c357/spent,
+/obj/item/ammo_casing/c357/spent,
+/obj/item/ammo_casing/c357/spent,
/turf/open/indestructible/white,
/area/ruin/space/has_grav/powered/biooutpost)
"DJ" = (
diff --git a/_maps/RandomRuins/SpaceRuins/mimesvsclowns.dmm b/_maps/RandomRuins/SpaceRuins/mimesvsclowns.dmm
index c98a43e89c2ab..7a76e71debf6e 100644
--- a/_maps/RandomRuins/SpaceRuins/mimesvsclowns.dmm
+++ b/_maps/RandomRuins/SpaceRuins/mimesvsclowns.dmm
@@ -6,7 +6,7 @@
/area/ruin)
"dI" = (
/obj/item/grown/bananapeel,
-/obj/item/ammo_casing/a357/spent{
+/obj/item/ammo_casing/c357/spent{
dir = 9;
pixel_x = -13;
pixel_y = 10
@@ -78,19 +78,19 @@
},
/obj/effect/decal/cleanable/blood/gibs,
/obj/machinery/light/small/broken/directional/south,
-/obj/item/ammo_casing/a357/spent,
+/obj/item/ammo_casing/c357/spent,
/turf/open/floor/iron/checker/airless,
/area/ruin)
"uc" = (
/obj/effect/decal/cleanable/blood/footprints{
dir = 4
},
-/obj/item/ammo_casing/a357/spent{
+/obj/item/ammo_casing/c357/spent{
pixel_x = -5;
dir = 5;
pixel_y = 6
},
-/obj/item/ammo_casing/a357/spent,
+/obj/item/ammo_casing/c357/spent,
/obj/item/gps/spaceruin,
/turf/open/floor/plating/airless,
/area/ruin)
@@ -204,7 +204,7 @@
/obj/effect/mob_spawn/corpse/human/clown,
/obj/effect/decal/cleanable/blood/footprints,
/obj/effect/decal/cleanable/dirt,
-/obj/item/ammo_casing/a357/spent,
+/obj/item/ammo_casing/c357/spent,
/turf/open/floor/plating/airless,
/area/ruin)
"Ij" = (
@@ -253,7 +253,7 @@
"Pq" = (
/obj/machinery/light/broken/directional/north,
/obj/structure/reagent_dispensers/watertank,
-/obj/item/ammo_casing/a357/spent,
+/obj/item/ammo_casing/c357/spent,
/turf/open/floor/iron/checker/airless,
/area/ruin)
"Qb" = (
diff --git a/_maps/RandomRuins/SpaceRuins/old_infiltrator.dmm b/_maps/RandomRuins/SpaceRuins/old_infiltrator.dmm
index 4b42f668c3377..85c2910d1fab8 100644
--- a/_maps/RandomRuins/SpaceRuins/old_infiltrator.dmm
+++ b/_maps/RandomRuins/SpaceRuins/old_infiltrator.dmm
@@ -210,8 +210,8 @@
"nF" = (
/obj/item/radio/intercom/directional/south,
/obj/effect/decal/cleanable/blood/old,
-/obj/item/ammo_casing/a357/match,
-/obj/item/ammo_casing/a357/match{
+/obj/item/ammo_casing/c357/match,
+/obj/item/ammo_casing/c357/match{
pixel_y = -4;
pixel_x = -7
},
@@ -306,7 +306,7 @@
/area/ruin/space/unpowered)
"uS" = (
/obj/machinery/newscaster/directional/south,
-/obj/item/ammo_casing/a357/match,
+/obj/item/ammo_casing/c357/match,
/turf/open/floor/mineral/plastitanium/red,
/area/ruin/space/unpowered)
"vi" = (
@@ -425,7 +425,7 @@
/turf/open/floor/mineral/plastitanium/red/airless,
/area/ruin/space/unpowered)
"Cv" = (
-/obj/item/ammo_casing/a357/match,
+/obj/item/ammo_casing/c357/match,
/turf/open/floor/mineral/plastitanium/red,
/area/ruin/space/unpowered)
"CC" = (
diff --git a/_maps/RandomRuins/SpaceRuins/prison_shuttle.dmm b/_maps/RandomRuins/SpaceRuins/prison_shuttle.dmm
index 6acec2ccc4393..7b0f8a6e8fbcc 100644
--- a/_maps/RandomRuins/SpaceRuins/prison_shuttle.dmm
+++ b/_maps/RandomRuins/SpaceRuins/prison_shuttle.dmm
@@ -147,8 +147,8 @@
/turf/open/floor/iron/dark/airless,
/area/ruin/space/prison_shuttle)
"O" = (
-/mob/living/basic/cockroach,
/obj/effect/turf_decal/tile/brown/fourcorners,
+/obj/effect/decal/cleanable/xenoblood/xsplatter,
/turf/open/floor/iron/dark/airless,
/area/ruin/space/prison_shuttle)
"P" = (
diff --git a/_maps/RandomRuins/SpaceRuins/russian_derelict.dmm b/_maps/RandomRuins/SpaceRuins/russian_derelict.dmm
index 5377a113df53c..592b425385099 100644
--- a/_maps/RandomRuins/SpaceRuins/russian_derelict.dmm
+++ b/_maps/RandomRuins/SpaceRuins/russian_derelict.dmm
@@ -4146,14 +4146,14 @@
"Eb" = (
/obj/structure/cable,
/obj/effect/mapping_helpers/burnt_floor,
-/obj/item/ammo_casing/a357{
+/obj/item/ammo_casing/c357{
pixel_x = -6;
pixel_y = 3
},
-/obj/item/ammo_casing/a357{
+/obj/item/ammo_casing/c357{
pixel_x = -5
},
-/obj/item/ammo_casing/a357{
+/obj/item/ammo_casing/c357{
pixel_x = 6;
dir = 9;
pixel_y = -3
@@ -5018,8 +5018,8 @@
/turf/open/floor/plating/airless,
/area/space/nearstation)
"IG" = (
-/obj/item/ammo_casing/a357,
-/obj/item/ammo_casing/a357{
+/obj/item/ammo_casing/c357,
+/obj/item/ammo_casing/c357{
pixel_x = 5;
pixel_y = 6
},
@@ -5893,8 +5893,8 @@
/turf/open/floor/plating,
/area/ruin/space/ks13/science/ordnance)
"Nk" = (
-/obj/effect/spawner/random/maintenance,
/obj/structure/lattice,
+/obj/effect/spawner/random/maintenance/no_decals,
/turf/template_noop,
/area/space/nearstation)
"Nl" = (
@@ -6330,11 +6330,11 @@
/turf/open/floor/iron/airless,
/area/ruin/space/ks13/hallway/aft)
"Pv" = (
-/obj/item/ammo_casing/a357{
+/obj/item/ammo_casing/c357{
pixel_x = 4;
pixel_y = -7
},
-/obj/item/ammo_casing/a357{
+/obj/item/ammo_casing/c357{
pixel_x = -5
},
/obj/structure/cable,
@@ -6847,12 +6847,12 @@
"Sk" = (
/obj/structure/cable,
/obj/effect/mapping_helpers/burnt_floor,
-/obj/item/ammo_casing/a357{
+/obj/item/ammo_casing/c357{
pixel_x = 6;
pixel_y = -4
},
-/obj/item/ammo_casing/a357,
-/obj/item/ammo_casing/a357{
+/obj/item/ammo_casing/c357,
+/obj/item/ammo_casing/c357{
pixel_x = -10;
pixel_y = 7;
dir = 9
@@ -7599,12 +7599,12 @@
/area/ruin/space/ks13/service/hydro)
"VH" = (
/obj/effect/mapping_helpers/burnt_floor,
-/obj/item/ammo_casing/a357{
+/obj/item/ammo_casing/c357{
pixel_x = 8;
dir = 8;
pixel_y = 6
},
-/obj/item/ammo_casing/a357{
+/obj/item/ammo_casing/c357{
pixel_x = 6;
pixel_y = -4
},
diff --git a/_maps/RandomRuins/SpaceRuins/shuttlerelic.dmm b/_maps/RandomRuins/SpaceRuins/shuttlerelic.dmm
index 71074aa4451fd..3534df8db024a 100644
--- a/_maps/RandomRuins/SpaceRuins/shuttlerelic.dmm
+++ b/_maps/RandomRuins/SpaceRuins/shuttlerelic.dmm
@@ -63,7 +63,7 @@
/obj/structure/chair/old{
dir = 1
},
-/obj/item/crowbar/large/heavy,
+/obj/item/crowbar/large/twenty_force,
/turf/open/floor/oldshuttle,
/area/ruin/space/has_grav/powered)
"o" = (
diff --git a/_maps/RandomRuins/SpaceRuins/the_faceoff.dmm b/_maps/RandomRuins/SpaceRuins/the_faceoff.dmm
index b31a11cba31ca..a358d59956e83 100644
--- a/_maps/RandomRuins/SpaceRuins/the_faceoff.dmm
+++ b/_maps/RandomRuins/SpaceRuins/the_faceoff.dmm
@@ -1,15 +1,15 @@
//MAP CONVERTED BY dmm2tgm.py THIS HEADER COMMENT PREVENTS RECONVERSION, DO NOT REMOVE
"aj" = (
-/obj/item/ammo_casing/a357/spent{
+/obj/item/ammo_casing/c357/spent{
pixel_x = 5;
pixel_y = 12
},
-/obj/item/ammo_casing/a357/spent{
+/obj/item/ammo_casing/c357/spent{
dir = 8;
pixel_x = -7;
pixel_y = 6
},
-/obj/item/ammo_casing/a357/spent{
+/obj/item/ammo_casing/c357/spent{
dir = 4;
pixel_x = 14;
pixel_y = 14
@@ -45,40 +45,40 @@
/turf/template_noop,
/area/template_noop)
"bM" = (
-/obj/item/ammo_casing/a357/spent{
+/obj/item/ammo_casing/c357/spent{
dir = 1;
pixel_x = 9
},
-/obj/item/ammo_casing/a357/spent{
+/obj/item/ammo_casing/c357/spent{
dir = 1;
pixel_x = -1;
pixel_y = 5
},
-/obj/item/ammo_casing/a357/spent{
+/obj/item/ammo_casing/c357/spent{
dir = 8;
pixel_x = -7;
pixel_y = 6
},
-/obj/item/ammo_casing/a357/spent{
+/obj/item/ammo_casing/c357/spent{
dir = 8;
pixel_x = 2;
pixel_y = -5
},
-/obj/item/ammo_casing/a357/spent{
+/obj/item/ammo_casing/c357/spent{
pixel_x = 5;
pixel_y = 2
},
-/obj/item/ammo_casing/a357/spent{
+/obj/item/ammo_casing/c357/spent{
dir = 4;
pixel_x = 14;
pixel_y = 14
},
-/obj/item/ammo_casing/a357/spent{
+/obj/item/ammo_casing/c357/spent{
dir = 8;
pixel_x = 2;
pixel_y = -5
},
-/obj/item/ammo_casing/a357/spent{
+/obj/item/ammo_casing/c357/spent{
dir = 8;
pixel_x = 2;
pixel_y = 12
@@ -168,7 +168,7 @@
/turf/open/floor/iron/dark/airless,
/area/ruin/space)
"eI" = (
-/obj/item/ammo_casing/a357/spent{
+/obj/item/ammo_casing/c357/spent{
dir = 1;
pixel_x = -1;
pixel_y = 5
@@ -213,17 +213,17 @@
/turf/open/misc/asteroid/basalt/airless,
/area/ruin/space)
"gq" = (
-/obj/item/ammo_casing/a357/spent,
+/obj/item/ammo_casing/c357/spent,
/obj/structure/barricade/wooden,
/turf/open/floor/iron/dark/airless,
/area/ruin/space)
"gx" = (
-/obj/item/ammo_casing/a357/spent{
+/obj/item/ammo_casing/c357/spent{
dir = 8;
pixel_x = -7;
pixel_y = 6
},
-/obj/item/ammo_casing/a357/spent{
+/obj/item/ammo_casing/c357/spent{
pixel_x = 5;
pixel_y = 2
},
@@ -280,41 +280,41 @@
/turf/open/floor/iron/dark/textured/airless,
/area/ruin/space)
"je" = (
-/obj/item/ammo_casing/a357/spent{
+/obj/item/ammo_casing/c357/spent{
dir = 8;
pixel_x = 10;
pixel_y = 5
},
-/obj/item/ammo_casing/a357/spent{
+/obj/item/ammo_casing/c357/spent{
dir = 8;
pixel_x = 2;
pixel_y = 12
},
-/obj/item/ammo_casing/a357/spent{
+/obj/item/ammo_casing/c357/spent{
dir = 8;
pixel_x = -2;
pixel_y = -5
},
-/obj/item/ammo_casing/a357/spent{
+/obj/item/ammo_casing/c357/spent{
pixel_x = 5;
pixel_y = 2
},
-/obj/item/ammo_casing/a357/spent{
+/obj/item/ammo_casing/c357/spent{
dir = 8;
pixel_x = 2;
pixel_y = 1
},
-/obj/item/ammo_casing/a357/spent{
+/obj/item/ammo_casing/c357/spent{
pixel_x = 2;
pixel_y = 6;
dir = 1
},
-/obj/item/ammo_casing/a357/spent{
+/obj/item/ammo_casing/c357/spent{
dir = 8;
pixel_x = 2;
pixel_y = -6
},
-/obj/item/ammo_casing/a357/spent{
+/obj/item/ammo_casing/c357/spent{
dir = 8;
pixel_y = 13;
pixel_x = -5
@@ -322,7 +322,7 @@
/turf/open/floor/mineral/plastitanium/red/airless,
/area/ruin/space)
"jX" = (
-/obj/item/ammo_casing/a357/spent{
+/obj/item/ammo_casing/c357/spent{
dir = 1;
pixel_x = 9;
pixel_y = 9
@@ -354,7 +354,7 @@
/turf/open/floor/iron/dark/airless,
/area/ruin/space)
"mC" = (
-/obj/item/ammo_casing/a357/spent{
+/obj/item/ammo_casing/c357/spent{
dir = 4;
pixel_x = 14;
pixel_y = 14
@@ -392,7 +392,7 @@
/turf/open/floor/iron/dark/airless,
/area/ruin/space)
"pl" = (
-/obj/item/ammo_casing/a357/spent{
+/obj/item/ammo_casing/c357/spent{
dir = 8;
pixel_x = 2;
pixel_y = -5
@@ -434,11 +434,11 @@
/area/ruin/space)
"qX" = (
/obj/effect/mapping_helpers/broken_floor,
-/obj/item/ammo_casing/a357/spent{
+/obj/item/ammo_casing/c357/spent{
pixel_x = 2;
pixel_y = 12
},
-/obj/item/ammo_casing/a357/spent{
+/obj/item/ammo_casing/c357/spent{
dir = 1;
pixel_x = 9;
pixel_y = 9
@@ -455,7 +455,7 @@
/area/ruin/space)
"rU" = (
/obj/effect/mapping_helpers/broken_floor,
-/obj/item/ammo_casing/a357/spent{
+/obj/item/ammo_casing/c357/spent{
dir = 1;
pixel_x = 9
},
@@ -482,7 +482,7 @@
/area/ruin/space)
"su" = (
/obj/effect/mapping_helpers/broken_floor,
-/obj/item/ammo_casing/a357/spent{
+/obj/item/ammo_casing/c357/spent{
dir = 8;
pixel_x = 2;
pixel_y = 12
@@ -541,7 +541,7 @@
/turf/open/misc/asteroid/basalt/airless,
/area/ruin/space)
"vA" = (
-/obj/item/ammo_casing/a357/spent{
+/obj/item/ammo_casing/c357/spent{
dir = 1;
pixel_x = -1;
pixel_y = 5
@@ -549,7 +549,7 @@
/turf/open/floor/iron/dark/airless,
/area/ruin/space)
"vZ" = (
-/obj/item/ammo_casing/a357/spent{
+/obj/item/ammo_casing/c357/spent{
dir = 8;
pixel_x = -2;
pixel_y = -5
@@ -588,7 +588,7 @@
/turf/open/floor/mineral/plastitanium/airless,
/area/ruin/space)
"xi" = (
-/obj/item/ammo_casing/a357/spent{
+/obj/item/ammo_casing/c357/spent{
dir = 4;
pixel_x = 14;
pixel_y = 14
@@ -726,7 +726,7 @@
/area/ruin/space)
"Dm" = (
/obj/effect/decal/cleanable/dirt,
-/obj/item/ammo_casing/a357/spent{
+/obj/item/ammo_casing/c357/spent{
pixel_x = 5;
pixel_y = 12
},
@@ -786,7 +786,7 @@
/turf/open/misc/asteroid/basalt/airless,
/area/ruin/space)
"FQ" = (
-/obj/item/ammo_casing/a357/spent{
+/obj/item/ammo_casing/c357/spent{
pixel_x = 5;
pixel_y = 2
},
@@ -804,7 +804,7 @@
/turf/open/floor/iron/dark/airless,
/area/ruin/space)
"GA" = (
-/obj/item/ammo_casing/a357/spent{
+/obj/item/ammo_casing/c357/spent{
dir = 4;
pixel_x = 14;
pixel_y = 14
@@ -822,7 +822,7 @@
/area/ruin/space)
"HE" = (
/obj/structure/table/reinforced/plastitaniumglass,
-/obj/item/ammo_casing/a357/spent{
+/obj/item/ammo_casing/c357/spent{
pixel_x = 2;
pixel_y = 12
},
@@ -881,7 +881,7 @@
/turf/open/floor/mineral/plastitanium/airless,
/area/ruin/space)
"JP" = (
-/obj/item/ammo_casing/a357/spent{
+/obj/item/ammo_casing/c357/spent{
pixel_x = 2;
pixel_y = 12
},
@@ -897,11 +897,11 @@
/turf/open/floor/mineral/plastitanium/red/airless,
/area/ruin/space)
"Kd" = (
-/obj/item/ammo_casing/a357/spent{
+/obj/item/ammo_casing/c357/spent{
dir = 1;
pixel_x = 9
},
-/obj/item/ammo_casing/a357/spent{
+/obj/item/ammo_casing/c357/spent{
dir = 1;
pixel_x = 9;
pixel_y = 9
@@ -909,7 +909,7 @@
/turf/open/floor/iron/dark/airless,
/area/ruin/space)
"Kp" = (
-/obj/item/ammo_casing/a357/spent{
+/obj/item/ammo_casing/c357/spent{
dir = 8;
pixel_x = 10;
pixel_y = 5
@@ -940,14 +940,14 @@
/turf/open/floor/iron/dark/textured/airless,
/area/ruin/space)
"LM" = (
-/obj/item/ammo_casing/a357/spent{
+/obj/item/ammo_casing/c357/spent{
pixel_x = 2;
pixel_y = 12
},
/turf/open/floor/iron/dark/airless,
/area/ruin/space)
"LX" = (
-/obj/item/ammo_casing/a357/spent{
+/obj/item/ammo_casing/c357/spent{
dir = 8;
pixel_x = 2;
pixel_y = 12
@@ -973,7 +973,7 @@
/turf/open/floor/mineral/plastitanium/red/airless,
/area/ruin/space)
"Nc" = (
-/obj/item/ammo_casing/a357/spent{
+/obj/item/ammo_casing/c357/spent{
dir = 8;
pixel_x = 10;
pixel_y = 5
@@ -1047,7 +1047,7 @@
/turf/open/floor/iron/dark/airless,
/area/ruin/space)
"TH" = (
-/obj/item/ammo_casing/a357/spent{
+/obj/item/ammo_casing/c357/spent{
dir = 1;
pixel_x = 9
},
@@ -1065,7 +1065,7 @@
/turf/open/floor/iron/dark/airless,
/area/ruin/space)
"Uy" = (
-/obj/item/ammo_casing/a357/spent{
+/obj/item/ammo_casing/c357/spent{
dir = 8;
pixel_x = -7;
pixel_y = 6
@@ -1102,36 +1102,36 @@
/turf/open/floor/mineral/plastitanium/airless,
/area/ruin/space)
"Vn" = (
-/obj/item/ammo_casing/a357/spent{
+/obj/item/ammo_casing/c357/spent{
dir = 4;
pixel_x = 14;
pixel_y = 14
},
-/obj/item/ammo_casing/a357/spent,
-/obj/item/ammo_casing/a357/spent{
+/obj/item/ammo_casing/c357/spent,
+/obj/item/ammo_casing/c357/spent{
dir = 8;
pixel_x = 10;
pixel_y = 5
},
-/obj/item/ammo_casing/a357/spent{
+/obj/item/ammo_casing/c357/spent{
dir = 1;
pixel_x = 9
},
-/obj/item/ammo_casing/a357/spent{
+/obj/item/ammo_casing/c357/spent{
dir = 8;
pixel_x = 10;
pixel_y = 5
},
-/obj/item/ammo_casing/a357/spent{
+/obj/item/ammo_casing/c357/spent{
dir = 8;
pixel_x = -7;
pixel_y = 6
},
-/obj/item/ammo_casing/a357/spent{
+/obj/item/ammo_casing/c357/spent{
pixel_x = 2;
pixel_y = 12
},
-/obj/item/ammo_casing/a357/spent{
+/obj/item/ammo_casing/c357/spent{
dir = 1;
pixel_x = 9;
pixel_y = 9
@@ -1152,7 +1152,7 @@
/turf/open/floor/mineral/plastitanium/airless,
/area/ruin/space)
"VB" = (
-/obj/item/ammo_casing/a357/spent{
+/obj/item/ammo_casing/c357/spent{
pixel_x = 5;
pixel_y = 2
},
@@ -1209,40 +1209,40 @@
/turf/open/floor/iron/dark/airless,
/area/ruin/space)
"Zg" = (
-/obj/item/ammo_casing/a357/spent{
+/obj/item/ammo_casing/c357/spent{
dir = 8;
pixel_x = 10;
pixel_y = 5
},
-/obj/item/ammo_casing/a357/spent{
+/obj/item/ammo_casing/c357/spent{
pixel_x = 5;
pixel_y = 7
},
-/obj/item/ammo_casing/a357/spent{
+/obj/item/ammo_casing/c357/spent{
dir = 8;
pixel_x = -4;
pixel_y = -7
},
-/obj/item/ammo_casing/a357/spent{
+/obj/item/ammo_casing/c357/spent{
pixel_x = 2;
pixel_y = 12
},
-/obj/item/ammo_casing/a357/spent{
+/obj/item/ammo_casing/c357/spent{
dir = 1;
pixel_x = -1;
pixel_y = 8
},
-/obj/item/ammo_casing/a357/spent{
+/obj/item/ammo_casing/c357/spent{
dir = 8;
pixel_x = -2;
pixel_y = -4
},
-/obj/item/ammo_casing/a357/spent{
+/obj/item/ammo_casing/c357/spent{
dir = 8;
pixel_x = -7;
pixel_y = 8
},
-/obj/item/ammo_casing/a357/spent{
+/obj/item/ammo_casing/c357/spent{
pixel_x = 5;
pixel_y = -4
},
@@ -1259,7 +1259,7 @@
/turf/open/floor/mineral/plastitanium/airless,
/area/ruin/space)
"ZP" = (
-/obj/item/ammo_casing/a357/spent{
+/obj/item/ammo_casing/c357/spent{
dir = 8;
pixel_x = 2;
pixel_y = 1
diff --git a/_maps/RandomRuins/SpaceRuins/the_outlet.dmm b/_maps/RandomRuins/SpaceRuins/the_outlet.dmm
index 719088322f21e..922715ca8d7f3 100644
--- a/_maps/RandomRuins/SpaceRuins/the_outlet.dmm
+++ b/_maps/RandomRuins/SpaceRuins/the_outlet.dmm
@@ -701,7 +701,7 @@
/area/ruin/space/has_grav/the_outlet/employeesection)
"rF" = (
/obj/structure/table/reinforced/rglass,
-/obj/item/surgery_tray/full,
+/obj/effect/spawner/surgery_tray/full,
/obj/item/reagent_containers/syringe/lethal/execution,
/turf/open/floor/iron/freezer,
/area/ruin/space/has_grav/the_outlet/researchrooms)
diff --git a/_maps/RandomZLevels/moonoutpost19.dmm b/_maps/RandomZLevels/moonoutpost19.dmm
index a8f52784b343a..c830fdc968fa1 100644
--- a/_maps/RandomZLevels/moonoutpost19.dmm
+++ b/_maps/RandomZLevels/moonoutpost19.dmm
@@ -4131,7 +4131,7 @@
/area/awaymission/moonoutpost19/arrivals/shed)
"Bf" = (
/obj/effect/decal/cleanable/dirt,
-/obj/item/ammo_casing/a357{
+/obj/item/ammo_casing/c357{
pixel_x = -2;
pixel_y = -7
},
@@ -6467,7 +6467,7 @@
"QZ" = (
/obj/effect/decal/cleanable/dirt,
/obj/structure/cable,
-/obj/item/ammo_casing/a357{
+/obj/item/ammo_casing/c357{
pixel_x = 11;
pixel_y = 8
},
@@ -7514,7 +7514,7 @@
/obj/item/stack/ore/glass{
pixel_x = 8
},
-/obj/item/ammo_casing/a357{
+/obj/item/ammo_casing/c357{
pixel_x = -10;
pixel_y = -7
},
diff --git a/_maps/deathmatch/arena_station.dmm b/_maps/deathmatch/arena_station.dmm
index 50089df45e8a0..8009387c50258 100644
--- a/_maps/deathmatch/arena_station.dmm
+++ b/_maps/deathmatch/arena_station.dmm
@@ -79,7 +79,7 @@
/obj/structure/closet/crate/cardboard,
/obj/effect/turf_decal/tile/brown/fourcorners,
/obj/item/mail/junkmail,
-/obj/item/ammo_casing/a357,
+/obj/item/ammo_casing/c357,
/obj/item/reagent_containers/syringe/plasma,
/turf/open/indestructible,
/area/deathmatch)
diff --git a/_maps/deathmatch/species_warfare.dmm b/_maps/deathmatch/species_warfare.dmm
index e80485c2b5e5c..66e7e9b2275aa 100644
--- a/_maps/deathmatch/species_warfare.dmm
+++ b/_maps/deathmatch/species_warfare.dmm
@@ -23,7 +23,7 @@
dir = 1
},
/obj/structure/table/glass,
-/obj/item/surgery_tray/full,
+/obj/effect/spawner/surgery_tray/full,
/turf/open/indestructible/white,
/area/deathmatch)
"bX" = (
@@ -228,7 +228,7 @@
"iM" = (
/obj/effect/turf_decal/tile/blue/half/contrasted,
/obj/structure/table/glass,
-/obj/item/surgery_tray/full,
+/obj/effect/spawner/surgery_tray/full,
/turf/open/indestructible/white,
/area/deathmatch)
"iV" = (
diff --git a/_maps/map_files/Birdshot/birdshot.dmm b/_maps/map_files/Birdshot/birdshot.dmm
index f18d092fd15a4..de9e1338f2939 100644
--- a/_maps/map_files/Birdshot/birdshot.dmm
+++ b/_maps/map_files/Birdshot/birdshot.dmm
@@ -130,7 +130,7 @@
/obj/effect/turf_decal/siding/white,
/obj/machinery/light/small/directional/south,
/obj/structure/table/reinforced,
-/obj/item/surgery_tray/full/morgue,
+/obj/effect/spawner/surgery_tray/full/morgue,
/turf/open/floor/iron/small,
/area/station/medical/morgue)
"adL" = (
@@ -10716,7 +10716,7 @@
/obj/machinery/camera/autoname/directional/north,
/obj/structure/sign/poster/official/random/directional/north,
/obj/machinery/status_display/ai/directional/west,
-/obj/item/surgery_tray/full/deployed,
+/obj/effect/spawner/surgery_tray/full/deployed,
/turf/open/floor/iron/showroomfloor,
/area/station/medical/surgery/theatre)
"dZk" = (
@@ -10872,7 +10872,10 @@
/obj/effect/turf_decal/tile/yellow{
dir = 1
},
-/obj/machinery/door/window/right/directional/east,
+/obj/machinery/door/window/right/directional/east{
+ name = "Engineering Deliveries";
+ req_access = list("engineering")
+ },
/turf/open/floor/iron/smooth,
/area/station/engineering/main)
"ecn" = (
@@ -33933,7 +33936,7 @@
/obj/effect/turf_decal/tile/dark_red/opposingcorners,
/obj/machinery/airalarm/directional/north,
/obj/structure/rack,
-/obj/item/crowbar/large/heavy,
+/obj/item/crowbar/large,
/obj/item/wirecutters,
/obj/item/wrench,
/turf/open/floor/iron,
@@ -34878,7 +34881,7 @@
/area/station/tcommsat/server)
"lPO" = (
/obj/structure/table,
-/obj/item/surgery_tray/full{
+/obj/effect/spawner/surgery_tray/full{
pixel_y = -5
},
/obj/item/wirecutters{
@@ -43772,7 +43775,7 @@
/area/station/science/ordnance/burnchamber)
"oZL" = (
/obj/structure/table,
-/obj/item/crowbar/large/heavy,
+/obj/item/crowbar/large,
/obj/item/stack/cable_coil,
/obj/machinery/light/small/directional/south,
/turf/open/floor/iron,
@@ -47524,7 +47527,7 @@
/obj/machinery/door/airlock/maintenance{
name = "Atmospherics Maintenance"
},
-/obj/effect/mapping_helpers/airlock/access/all/engineering/maintenance,
+/obj/effect/mapping_helpers/airlock/access/any/engineering/general,
/turf/open/floor/plating,
/area/station/maintenance/department/engine/atmos)
"qmz" = (
@@ -57400,6 +57403,9 @@
/obj/item/gun/energy/ionrifle{
pixel_y = 3
},
+/obj/item/gun/ballistic/automatic/battle_rifle{
+ pixel_y = 5
+ },
/obj/item/clothing/suit/hooded/ablative,
/turf/open/floor/iron/dark/small,
/area/station/ai_monitored/security/armory)
diff --git a/_maps/map_files/Deltastation/DeltaStation2.dmm b/_maps/map_files/Deltastation/DeltaStation2.dmm
index 08d3aa51e9bfb..a690aede2a2f6 100644
--- a/_maps/map_files/Deltastation/DeltaStation2.dmm
+++ b/_maps/map_files/Deltastation/DeltaStation2.dmm
@@ -53109,6 +53109,9 @@
"nmT" = (
/obj/structure/rack,
/obj/item/gun/energy/ionrifle,
+/obj/item/gun/ballistic/automatic/battle_rifle{
+ pixel_y = 3
+ },
/obj/item/clothing/suit/hooded/ablative,
/obj/item/gun/energy/temperature/security,
/obj/structure/window/reinforced/spawner/directional/south,
@@ -75220,7 +75223,7 @@
/area/station/service/abandoned_gambling_den/gaming)
"sOp" = (
/obj/structure/table/glass,
-/obj/item/surgery_tray/full,
+/obj/effect/spawner/surgery_tray/full,
/obj/structure/window/reinforced/spawner/directional/west,
/obj/item/clothing/gloves/latex,
/obj/item/clothing/suit/apron/surgical,
@@ -79128,7 +79131,7 @@
dir = 4
},
/obj/structure/table/reinforced,
-/obj/item/surgery_tray/full/morgue,
+/obj/effect/spawner/surgery_tray/full/morgue,
/obj/effect/turf_decal/tile/dark_blue/half/contrasted{
dir = 4
},
@@ -94584,7 +94587,7 @@
/area/station/cargo/storage)
"xES" = (
/obj/structure/table/glass,
-/obj/item/surgery_tray/full,
+/obj/effect/spawner/surgery_tray/full,
/obj/item/clothing/gloves/latex,
/obj/item/clothing/suit/apron/surgical,
/obj/effect/turf_decal/tile/neutral/fourcorners,
diff --git a/_maps/map_files/IceBoxStation/IceBoxStation.dmm b/_maps/map_files/IceBoxStation/IceBoxStation.dmm
index 498bba9c2e803..aa3df4dfd8a93 100644
--- a/_maps/map_files/IceBoxStation/IceBoxStation.dmm
+++ b/_maps/map_files/IceBoxStation/IceBoxStation.dmm
@@ -803,7 +803,7 @@
dir = 4
},
/turf/open/floor/plating/icemoon,
-/area/station/science/ordnance/bomb)
+/area/station/science/ordnance/bomb/planet)
"anP" = (
/obj/structure/table,
/obj/machinery/light_switch/directional/north,
@@ -3327,7 +3327,7 @@
dir = 10
},
/turf/open/floor/plating/icemoon,
-/area/station/science/ordnance/bomb)
+/area/station/science/ordnance/bomb/planet)
"aWc" = (
/obj/structure/railing{
dir = 8
@@ -4003,7 +4003,7 @@
desc = "A wall impregnated with Fixium, able to withstand massive explosions with ease";
name = "hyper-reinforced wall"
},
-/area/station/science/ordnance/bomb)
+/area/station/science/ordnance/bomb/planet)
"bff" = (
/obj/machinery/atmospherics/components/unary/vent_pump/on/layer4,
/turf/open/floor/iron,
@@ -4828,7 +4828,7 @@
/obj/structure/window/reinforced/spawner/directional/north,
/obj/effect/turf_decal/stripes/line,
/turf/open/floor/plating/icemoon,
-/area/station/science/ordnance/bomb)
+/area/station/science/ordnance/bomb/planet)
"bqt" = (
/obj/machinery/airalarm/directional/west,
/turf/open/floor/circuit,
@@ -5503,7 +5503,7 @@
"byB" = (
/obj/effect/spawner/random/engineering/tracking_beacon,
/turf/open/floor/plating/icemoon,
-/area/station/science/ordnance/bomb)
+/area/station/science/ordnance/bomb/planet)
"byH" = (
/obj/machinery/atmospherics/pipe/smart/simple/green/visible,
/turf/open/floor/iron/dark,
@@ -9895,7 +9895,7 @@
dir = 9
},
/turf/open/floor/plating/icemoon,
-/area/station/science/ordnance/bomb)
+/area/station/science/ordnance/bomb/planet)
"cJC" = (
/obj/structure/closet,
/obj/effect/spawner/random/maintenance/four,
@@ -15815,7 +15815,7 @@
/area/station/security/prison/rec)
"evT" = (
/turf/open/floor/plating/icemoon,
-/area/station/science/ordnance/bomb)
+/area/station/science/ordnance/bomb/planet)
"ewi" = (
/obj/machinery/navbeacon{
codes_txt = "delivery;dir=8";
@@ -18953,7 +18953,7 @@
pixel_x = -4;
pixel_y = 3
},
-/obj/item/surgery_tray/full,
+/obj/effect/spawner/surgery_tray/full,
/obj/effect/turf_decal/tile/blue/half/contrasted{
dir = 1
},
@@ -25910,7 +25910,7 @@
/area/station/science/xenobiology)
"hvt" = (
/obj/effect/turf_decal/tile/neutral/fourcorners,
-/obj/item/surgery_tray/full/morgue,
+/obj/effect/spawner/surgery_tray/full/morgue,
/obj/structure/table/reinforced,
/obj/machinery/requests_console/auto_name/directional/north,
/obj/effect/turf_decal/bot_white,
@@ -26458,7 +26458,7 @@
"hDA" = (
/obj/effect/turf_decal/stripes/line,
/turf/open/floor/plating/icemoon,
-/area/station/science/ordnance/bomb)
+/area/station/science/ordnance/bomb/planet)
"hDG" = (
/obj/docking_port/stationary/random/icemoon{
dir = 4;
@@ -30139,7 +30139,7 @@
dir = 10
},
/turf/open/floor/plating/icemoon,
-/area/station/science/ordnance/bomb)
+/area/station/science/ordnance/bomb/planet)
"iJK" = (
/obj/structure/lattice/catwalk,
/obj/structure/railing,
@@ -30183,7 +30183,7 @@
dir = 1
},
/turf/open/floor/plating/icemoon,
-/area/station/science/ordnance/bomb)
+/area/station/science/ordnance/bomb/planet)
"iKd" = (
/obj/structure/window/reinforced/spawner/directional/south,
/obj/effect/turf_decal/siding/white,
@@ -38618,7 +38618,7 @@
dir = 6
},
/turf/open/floor/plating/icemoon,
-/area/station/science/ordnance/bomb)
+/area/station/science/ordnance/bomb/planet)
"lhu" = (
/obj/machinery/camera/directional/north{
c_tag = "Xenobiology Lab Access";
@@ -41112,7 +41112,7 @@
dir = 9
},
/turf/open/floor/plating/icemoon,
-/area/station/science/ordnance/bomb)
+/area/station/science/ordnance/bomb/planet)
"lRf" = (
/obj/machinery/teleport/station,
/turf/open/floor/plating,
@@ -42518,7 +42518,7 @@
dir = 1
},
/turf/open/floor/plating/icemoon,
-/area/station/science/ordnance/bomb)
+/area/station/science/ordnance/bomb/planet)
"mpH" = (
/obj/effect/landmark/event_spawn,
/obj/effect/turf_decal/tile/neutral/fourcorners,
@@ -42635,7 +42635,7 @@
pixel_x = -4;
pixel_y = 3
},
-/obj/item/surgery_tray/full,
+/obj/effect/spawner/surgery_tray/full,
/obj/effect/turf_decal/tile/blue/half/contrasted{
dir = 1
},
@@ -42913,7 +42913,7 @@
"mvE" = (
/obj/effect/spawner/structure/window/reinforced,
/turf/open/floor/plating/icemoon,
-/area/station/science/ordnance/bomb)
+/area/station/science/ordnance/bomb/planet)
"mvG" = (
/obj/machinery/atmospherics/pipe/smart/manifold/yellow/visible{
dir = 8
@@ -44043,7 +44043,7 @@
dir = 5
},
/turf/open/floor/plating/icemoon,
-/area/station/science/ordnance/bomb)
+/area/station/science/ordnance/bomb/planet)
"mNi" = (
/obj/effect/turf_decal/stripes/line{
dir = 8
@@ -50619,7 +50619,7 @@
dir = 8
},
/turf/open/floor/plating/icemoon,
-/area/station/science/ordnance/bomb)
+/area/station/science/ordnance/bomb/planet)
"oDg" = (
/obj/structure/chair/stool/directional/west,
/obj/effect/decal/cleanable/dirt,
@@ -51275,7 +51275,7 @@
dir = 6
},
/turf/open/floor/plating/icemoon,
-/area/station/science/ordnance/bomb)
+/area/station/science/ordnance/bomb/planet)
"oLz" = (
/obj/structure/disposalpipe/segment,
/obj/structure/cable,
@@ -55096,7 +55096,7 @@
/area/station/medical/treatment_center)
"pRa" = (
/turf/closed/wall,
-/area/station/science/ordnance/bomb)
+/area/station/science/ordnance/bomb/planet)
"pRj" = (
/turf/closed/wall,
/area/station/maintenance/port/aft)
@@ -58093,7 +58093,7 @@
dir = 5
},
/turf/open/floor/plating/icemoon,
-/area/station/science/ordnance/bomb)
+/area/station/science/ordnance/bomb/planet)
"qKQ" = (
/obj/effect/spawner/structure/window/reinforced,
/obj/structure/cable,
@@ -71028,7 +71028,7 @@
dir = 4
},
/turf/open/floor/plating/icemoon,
-/area/station/science/ordnance/bomb)
+/area/station/science/ordnance/bomb/planet)
"uyW" = (
/obj/machinery/washing_machine,
/obj/effect/turf_decal/siding/blue{
@@ -77464,6 +77464,9 @@
"wyF" = (
/obj/structure/rack,
/obj/item/gun/energy/ionrifle,
+/obj/item/gun/ballistic/automatic/battle_rifle{
+ pixel_y = 3
+ },
/obj/item/gun/energy/temperature/security,
/obj/item/clothing/suit/hooded/ablative,
/obj/effect/turf_decal/tile/red/half/contrasted{
@@ -79919,7 +79922,7 @@
name = "BOMB RANGE"
},
/turf/closed/wall,
-/area/station/science/ordnance/bomb)
+/area/station/science/ordnance/bomb/planet)
"xgK" = (
/obj/structure/table,
/obj/item/transfer_valve{
diff --git a/_maps/map_files/MetaStation/MetaStation.dmm b/_maps/map_files/MetaStation/MetaStation.dmm
index 1bc583699ef54..6b4adfe0d4896 100644
--- a/_maps/map_files/MetaStation/MetaStation.dmm
+++ b/_maps/map_files/MetaStation/MetaStation.dmm
@@ -9670,7 +9670,7 @@
/obj/effect/turf_decal/tile/blue/fourcorners,
/obj/machinery/status_display/evac/directional/west,
/obj/machinery/digital_clock/directional/south,
-/obj/item/surgery_tray/full,
+/obj/effect/spawner/surgery_tray/full,
/turf/open/floor/iron/white,
/area/station/medical/surgery/theatre)
"dyr" = (
@@ -11421,6 +11421,9 @@
"eew" = (
/obj/structure/rack,
/obj/item/gun/energy/ionrifle,
+/obj/item/gun/ballistic/automatic/battle_rifle{
+ pixel_y = 3
+ },
/obj/item/gun/energy/temperature/security,
/obj/item/clothing/suit/hooded/ablative,
/obj/effect/turf_decal/tile/red/half/contrasted{
@@ -12775,7 +12778,7 @@
/obj/effect/turf_decal/tile/blue/half/contrasted{
dir = 1
},
-/obj/item/surgery_tray/full,
+/obj/effect/spawner/surgery_tray/full,
/obj/item/clothing/gloves/latex,
/obj/item/clothing/suit/apron/surgical,
/turf/open/floor/iron/white,
@@ -15055,7 +15058,7 @@
/obj/structure/table/glass,
/obj/effect/turf_decal/tile/blue/fourcorners,
/obj/machinery/status_display/evac/directional/west,
-/obj/item/surgery_tray/full,
+/obj/effect/spawner/surgery_tray/full,
/turf/open/floor/iron/white,
/area/station/medical/surgery/theatre)
"fpj" = (
@@ -61156,7 +61159,7 @@
dir = 8
},
/obj/machinery/light/small/directional/north,
-/obj/item/surgery_tray/full/morgue,
+/obj/effect/spawner/surgery_tray/full/morgue,
/turf/open/floor/iron/dark/smooth_edge{
dir = 8
},
diff --git a/_maps/map_files/NebulaStation/NebulaStation.dmm b/_maps/map_files/NebulaStation/NebulaStation.dmm
index 15bcb272d73f0..7fda323c9ce19 100644
--- a/_maps/map_files/NebulaStation/NebulaStation.dmm
+++ b/_maps/map_files/NebulaStation/NebulaStation.dmm
@@ -423,7 +423,7 @@
"adt" = (
/obj/machinery/defibrillator_mount/directional/south,
/obj/structure/table/glass,
-/obj/item/surgery_tray/full,
+/obj/effect/spawner/surgery_tray/full,
/obj/effect/turf_decal/box/white/corners{
dir = 4
},
@@ -545,7 +545,7 @@
id = "cmoprivacy2";
req_access = list("cmo")
},
-/obj/item/surgery_tray/full,
+/obj/effect/spawner/surgery_tray/full,
/turf/open/floor/iron/white/herringbone,
/area/station/command/heads_quarters/cmo)
"aer" = (
@@ -10569,6 +10569,9 @@
"bDU" = (
/obj/structure/rack,
/obj/item/gun/energy/ionrifle,
+/obj/item/gun/ballistic/automatic/battle_rifle{
+ pixel_y = 3
+ },
/obj/item/clothing/suit/hooded/ablative,
/obj/item/gun/energy/temperature/security,
/obj/effect/turf_decal/bot/right,
@@ -12767,6 +12770,7 @@
/turf/open/floor/iron/dark/textured,
/area/station/engineering/atmos)
"bUo" = (
+/obj/structure/cable,
/turf/open/floor/iron/solarpanel/airless,
/area/station/solars/port/aft)
"bUs" = (
@@ -56574,7 +56578,7 @@
"isM" = (
/obj/machinery/defibrillator_mount/directional/south,
/obj/structure/table/glass,
-/obj/item/surgery_tray/full,
+/obj/effect/spawner/surgery_tray/full,
/obj/effect/turf_decal/box/white/corners{
dir = 1
},
@@ -62233,7 +62237,7 @@
/obj/structure/table/reinforced,
/obj/effect/turf_decal/trimline/red/corner,
/obj/effect/turf_decal/siding/thinplating_new/dark/corner,
-/obj/item/surgery_tray/full,
+/obj/effect/spawner/surgery_tray/full,
/turf/open/floor/iron/dark/side{
dir = 9
},
@@ -62788,7 +62792,7 @@
/obj/structure/table/glass,
/obj/structure/window/reinforced/spawner/directional/north,
/obj/machinery/digital_clock/directional/east,
-/obj/item/surgery_tray/full,
+/obj/effect/spawner/surgery_tray/full,
/turf/open/floor/iron/dark/small,
/area/station/science/robotics/augments)
"jqo" = (
@@ -63650,6 +63654,7 @@
dir = 8
},
/obj/machinery/atmospherics/pipe/smart/manifold4w/scrubbers/hidden/layer2,
+/obj/machinery/firealarm/directional/north,
/turf/open/floor/iron/dark/side{
dir = 8
},
@@ -64542,7 +64547,7 @@
/area/station/engineering/supermatter/room/upper)
"jBX" = (
/obj/structure/table/reinforced,
-/obj/item/surgery_tray/full/morgue,
+/obj/effect/spawner/surgery_tray/full/morgue,
/obj/effect/turf_decal/siding/dark/corner{
dir = 8
},
@@ -81361,6 +81366,7 @@
dir = 5
},
/obj/structure/marker_beacon/lime,
+/obj/machinery/firealarm/directional/south,
/turf/open/floor/iron/white,
/area/station/hallway/primary/fore)
"mcX" = (
@@ -85748,7 +85754,7 @@
/area/space/nearstation)
"mLK" = (
/obj/structure/rack,
-/obj/item/crowbar/large/heavy,
+/obj/item/crowbar/large,
/obj/item/wirecutters,
/obj/item/wrench,
/obj/effect/turf_decal/siding/thinplating_new/dark{
@@ -113584,7 +113590,7 @@
/turf/open/floor/engine/n2,
/area/station/engineering/atmos)
"qOe" = (
-/obj/item/ammo_casing/a357/spent{
+/obj/item/ammo_casing/c357/spent{
pixel_y = 6;
pixel_x = 9
},
@@ -120901,7 +120907,7 @@
/area/station/maintenance/fore/lesser)
"rUg" = (
/obj/structure/table/reinforced,
-/obj/item/surgery_tray/full,
+/obj/effect/spawner/surgery_tray/full,
/obj/machinery/status_display/ai/directional/east,
/obj/effect/turf_decal/trimline/dark_red/filled/corner{
dir = 1
diff --git a/_maps/map_files/generic/CentCom.dmm b/_maps/map_files/generic/CentCom.dmm
index e730dea04bc99..8f24d290d1750 100644
--- a/_maps/map_files/generic/CentCom.dmm
+++ b/_maps/map_files/generic/CentCom.dmm
@@ -9929,7 +9929,7 @@
/area/centcom/central_command_areas/armory)
"Vh" = (
/obj/structure/table/reinforced,
-/obj/item/surgery_tray/full{
+/obj/effect/spawner/surgery_tray/full{
pixel_y = 10;
pixel_x = 2
},
@@ -62229,7 +62229,7 @@ xt
xt
xt
xt
-xt
+PG
xt
Vd
zm
@@ -62486,7 +62486,7 @@ xt
xt
xt
xt
-rq
+xt
xt
xt
zl
diff --git a/_maps/map_files/tramstation/maintenance_modules/dormenginelower_1.dmm b/_maps/map_files/tramstation/maintenance_modules/dormenginelower_1.dmm
index 7e99bf96803cd..daec11b31a2c6 100644
--- a/_maps/map_files/tramstation/maintenance_modules/dormenginelower_1.dmm
+++ b/_maps/map_files/tramstation/maintenance_modules/dormenginelower_1.dmm
@@ -2735,7 +2735,7 @@ gs
IK
IK
IK
-pD
+mg
"}
(53,1,1) = {"
AN
@@ -2771,7 +2771,7 @@ IK
IK
IK
IK
-pD
+mg
"}
(54,1,1) = {"
AN
@@ -2804,10 +2804,10 @@ gs
IK
IK
IK
-pD
-pD
-pD
-pD
+mg
+mg
+mg
+mg
"}
(55,1,1) = {"
AN
@@ -2840,7 +2840,7 @@ IK
IK
IK
IK
-pD
+mg
IK
IK
IK
@@ -2875,8 +2875,8 @@ cV
cV
mg
sf
-pD
-pD
+mg
+mg
IK
IK
IK
diff --git a/_maps/map_files/tramstation/maintenance_modules/dormenginelower_2.dmm b/_maps/map_files/tramstation/maintenance_modules/dormenginelower_2.dmm
index e7c0c22ad0575..dd68accab503b 100644
--- a/_maps/map_files/tramstation/maintenance_modules/dormenginelower_2.dmm
+++ b/_maps/map_files/tramstation/maintenance_modules/dormenginelower_2.dmm
@@ -2907,7 +2907,7 @@ gs
IK
IK
IK
-pD
+mg
"}
(53,1,1) = {"
AN
@@ -2943,7 +2943,7 @@ IK
IK
IK
IK
-pD
+mg
"}
(54,1,1) = {"
AN
@@ -2976,10 +2976,10 @@ gs
IK
IK
IK
-pD
-pD
-pD
-pD
+mg
+mg
+mg
+mg
"}
(55,1,1) = {"
AN
@@ -3012,7 +3012,7 @@ IK
IK
IK
IK
-pD
+mg
IK
IK
IK
@@ -3047,8 +3047,8 @@ cV
cV
mg
sf
-pD
-pD
+mg
+mg
IK
IK
gs
diff --git a/_maps/map_files/tramstation/maintenance_modules/dormenginelower_3.dmm b/_maps/map_files/tramstation/maintenance_modules/dormenginelower_3.dmm
index 334f49ad8b602..83d22aaf3d8ed 100644
--- a/_maps/map_files/tramstation/maintenance_modules/dormenginelower_3.dmm
+++ b/_maps/map_files/tramstation/maintenance_modules/dormenginelower_3.dmm
@@ -2638,7 +2638,7 @@ gs
IK
IK
IK
-pD
+mg
"}
(53,1,1) = {"
AN
@@ -2674,7 +2674,7 @@ IK
IK
IK
IK
-pD
+mg
"}
(54,1,1) = {"
AN
@@ -2707,10 +2707,10 @@ gs
IK
IK
IK
-pD
-pD
-pD
-pD
+mg
+mg
+mg
+mg
"}
(55,1,1) = {"
AN
@@ -2743,7 +2743,7 @@ IK
IK
IK
IK
-pD
+mg
IK
IK
IK
@@ -2778,8 +2778,8 @@ cV
cV
mg
sf
-pD
-pD
+mg
+mg
IK
IK
gs
diff --git a/_maps/map_files/tramstation/maintenance_modules/servicecargolower_1.dmm b/_maps/map_files/tramstation/maintenance_modules/servicecargolower_1.dmm
index 5eae1f6e7ec96..cc4cbdcdd9677 100644
--- a/_maps/map_files/tramstation/maintenance_modules/servicecargolower_1.dmm
+++ b/_maps/map_files/tramstation/maintenance_modules/servicecargolower_1.dmm
@@ -1037,7 +1037,7 @@ a
J
J
J
-W
+t
J
J
J
@@ -1060,10 +1060,10 @@ Z
"}
(29,1,1) = {"
m
-W
-W
-W
-W
+t
+t
+t
+t
J
J
J
@@ -1086,7 +1086,7 @@ Z
"}
(30,1,1) = {"
J
-W
+t
J
J
J
@@ -1111,8 +1111,8 @@ Z
Z
"}
(31,1,1) = {"
-W
-W
+t
+t
J
J
a
diff --git a/_maps/map_files/tramstation/maintenance_modules/servicecargolower_2.dmm b/_maps/map_files/tramstation/maintenance_modules/servicecargolower_2.dmm
index 02960cfadc516..df7d703de1557 100644
--- a/_maps/map_files/tramstation/maintenance_modules/servicecargolower_2.dmm
+++ b/_maps/map_files/tramstation/maintenance_modules/servicecargolower_2.dmm
@@ -136,10 +136,6 @@
/obj/effect/decal/cleanable/dirt,
/turf/open/floor/catwalk_floor,
/area/station/maintenance/starboard/greater)
-"oT" = (
-/obj/effect/turf_decal/sand/plating,
-/turf/open/floor/plating/airless,
-/area/station/asteroid)
"oV" = (
/obj/effect/decal/cleanable/dirt,
/obj/machinery/button/door/directional/west{
@@ -1112,7 +1108,7 @@ Rk
cV
cV
cV
-oT
+ja
cV
sj
PC
@@ -1135,10 +1131,10 @@ fX
"}
(29,1,1) = {"
Cn
-oT
-oT
-oT
-oT
+ja
+ja
+ja
+ja
cV
sj
sj
@@ -1161,7 +1157,7 @@ fX
"}
(30,1,1) = {"
cV
-oT
+ja
cV
cV
cV
@@ -1186,8 +1182,8 @@ fX
fX
"}
(31,1,1) = {"
-oT
-oT
+ja
+ja
cV
cV
Rk
diff --git a/_maps/map_files/tramstation/maintenance_modules/servicecargolower_3.dmm b/_maps/map_files/tramstation/maintenance_modules/servicecargolower_3.dmm
index 02618e94a681c..12357ca0a2f8f 100644
--- a/_maps/map_files/tramstation/maintenance_modules/servicecargolower_3.dmm
+++ b/_maps/map_files/tramstation/maintenance_modules/servicecargolower_3.dmm
@@ -948,7 +948,7 @@ a
J
J
J
-W
+t
J
J
J
@@ -971,10 +971,10 @@ Z
"}
(29,1,1) = {"
m
-W
-W
-W
-W
+t
+t
+t
+t
J
J
J
@@ -997,7 +997,7 @@ Z
"}
(30,1,1) = {"
J
-W
+t
J
J
J
@@ -1022,8 +1022,8 @@ Z
Z
"}
(31,1,1) = {"
-W
-W
+t
+t
J
J
a
diff --git a/_maps/map_files/tramstation/tramstation.dmm b/_maps/map_files/tramstation/tramstation.dmm
index 0c1245591ec47..5701e877f72ae 100644
--- a/_maps/map_files/tramstation/tramstation.dmm
+++ b/_maps/map_files/tramstation/tramstation.dmm
@@ -77,7 +77,7 @@
/obj/effect/turf_decal/sand/plating,
/obj/structure/cable,
/turf/open/floor/plating/airless,
-/area/station/solars/starboard/fore)
+/area/station/solars/starboard/fore/asteriod)
"aan" = (
/turf/open/floor/iron/stairs/medium{
dir = 8
@@ -103,7 +103,7 @@
/obj/effect/turf_decal/sand/plating,
/obj/machinery/light/small/directional/west,
/turf/open/floor/plating/airless,
-/area/station/solars/starboard/fore)
+/area/station/solars/starboard/fore/asteriod)
"aat" = (
/obj/item/stack/cable_coil,
/turf/open/misc/asteroid/airless,
@@ -112,7 +112,7 @@
/obj/effect/turf_decal/sand/plating,
/obj/machinery/light/small/directional/east,
/turf/open/floor/plating/airless,
-/area/station/solars/starboard/fore)
+/area/station/solars/starboard/fore/asteriod)
"aaw" = (
/obj/item/storage/toolbox/electrical,
/turf/open/misc/asteroid/airless,
@@ -136,7 +136,7 @@
/obj/effect/turf_decal/sand/plating,
/obj/structure/cable/layer1,
/turf/open/floor/plating/airless,
-/area/station/solars/starboard/fore)
+/area/station/solars/starboard/fore/asteriod)
"aaD" = (
/obj/structure/ore_box,
/turf/open/misc/asteroid/airless,
@@ -1340,19 +1340,19 @@
id = "portsolar"
},
/turf/open/floor/plating/airless,
-/area/station/solars/port)
+/area/station/solars/port/asteriod)
"ael" = (
/obj/effect/turf_decal/sand/plating,
/obj/structure/cable/layer1,
/obj/structure/cable,
/turf/open/floor/plating/airless,
-/area/station/solars/port)
+/area/station/solars/port/asteriod)
"aem" = (
/obj/item/storage/toolbox/electrical,
/obj/effect/turf_decal/sand/plating,
/obj/structure/cable/layer1,
/turf/open/floor/plating/airless,
-/area/station/solars/port)
+/area/station/solars/port/asteriod)
"aen" = (
/obj/effect/turf_decal/sand/plating,
/obj/item/stack/ore/glass,
@@ -5913,7 +5913,7 @@
/area/station/cargo/sorting)
"aST" = (
/turf/closed/wall/r_wall,
-/area/station/solars/port)
+/area/station/maintenance/solars/port)
"aTa" = (
/obj/effect/turf_decal/trimline/purple/filled/line{
dir = 1
@@ -6259,7 +6259,7 @@
/obj/structure/cable,
/obj/effect/decal/cleanable/dirt,
/turf/open/floor/catwalk_floor,
-/area/station/solars/starboard/fore)
+/area/station/maintenance/solars/starboard/fore)
"bcq" = (
/obj/effect/landmark/secequipment,
/obj/effect/turf_decal/bot,
@@ -6952,7 +6952,7 @@
/obj/effect/turf_decal/sand/plating,
/obj/machinery/light/floor,
/turf/open/floor/plating,
-/area/station/solars/starboard/fore)
+/area/station/maintenance/solars/starboard/fore)
"bsW" = (
/obj/machinery/duct,
/obj/machinery/atmospherics/pipe/smart/manifold4w/supply/hidden/layer4,
@@ -7855,6 +7855,9 @@
/obj/structure/rack,
/obj/item/clothing/suit/hooded/ablative,
/obj/item/gun/energy/ionrifle,
+/obj/item/gun/ballistic/automatic/battle_rifle{
+ pixel_y = 3
+ },
/obj/item/gun/energy/temperature/security,
/obj/structure/reagent_dispensers/wall/peppertank/directional/north,
/obj/effect/turf_decal/tile/neutral/fourcorners,
@@ -8392,10 +8395,6 @@
/obj/structure/water_source/puddle,
/turf/open/misc/asteroid,
/area/station/security/prison/workout)
-"bRE" = (
-/obj/structure/lattice/catwalk,
-/turf/open/floor/plating/airless,
-/area/station/solars/port)
"bSd" = (
/obj/effect/turf_decal/siding/wood,
/turf/open/floor/wood/large,
@@ -9568,7 +9567,7 @@
dir = 1
},
/turf/open/floor/plating,
-/area/station/solars/starboard/fore)
+/area/station/maintenance/solars/starboard/fore)
"ckr" = (
/obj/structure/table,
/obj/item/assembly/timer{
@@ -10564,7 +10563,7 @@
/obj/structure/cable,
/obj/effect/decal/cleanable/dirt,
/turf/open/floor/catwalk_floor,
-/area/station/solars/starboard/fore)
+/area/station/maintenance/solars/starboard/fore)
"cCr" = (
/obj/structure/cable,
/obj/machinery/light/warm/directional/east,
@@ -10958,7 +10957,7 @@
dir = 1
},
/turf/open/openspace,
-/area/station/solars/starboard/fore)
+/area/station/maintenance/solars/starboard/fore)
"cII" = (
/obj/structure/cable,
/obj/machinery/atmospherics/components/unary/vent_scrubber/on/layer2{
@@ -12237,7 +12236,7 @@
/obj/effect/turf_decal/stripes/box,
/obj/effect/decal/cleanable/dirt,
/turf/open/floor/plating,
-/area/station/solars/starboard/fore)
+/area/station/maintenance/solars/starboard/fore)
"deU" = (
/obj/structure/closet/emcloset,
/obj/machinery/light/dim/directional/west,
@@ -13990,7 +13989,7 @@
/obj/effect/decal/cleanable/dirt,
/obj/machinery/light/dim/directional/south,
/turf/open/floor/catwalk_floor,
-/area/station/solars/port)
+/area/station/maintenance/solars/port)
"dMw" = (
/obj/machinery/door/airlock/hatch{
name = "Emergency Exit"
@@ -14954,7 +14953,7 @@
/obj/structure/ladder,
/obj/effect/decal/cleanable/dirt,
/turf/open/floor/plating,
-/area/station/solars/port)
+/area/station/maintenance/solars/port)
"edE" = (
/obj/effect/turf_decal/trimline/yellow/filled/corner{
dir = 8
@@ -15514,7 +15513,7 @@
/obj/structure/cable,
/obj/effect/turf_decal/sand/plating,
/turf/open/floor/catwalk_floor,
-/area/station/solars/port)
+/area/station/maintenance/solars/port)
"eqK" = (
/obj/effect/decal/cleanable/dirt,
/obj/effect/decal/cleanable/dirt,
@@ -16131,7 +16130,7 @@
/obj/effect/turf_decal/stripes/box,
/obj/effect/decal/cleanable/dirt,
/turf/open/floor/plating,
-/area/station/solars/starboard/fore)
+/area/station/maintenance/solars/starboard/fore)
"eBd" = (
/obj/effect/turf_decal/trimline/brown/filled/line{
dir = 9
@@ -16912,6 +16911,9 @@
},
/turf/open/floor/iron,
/area/station/hallway/primary/tram/right)
+"eSh" = (
+/turf/closed/wall,
+/area/station/solars/starboard/fore/asteriod)
"eSj" = (
/obj/structure/table,
/obj/item/storage/box/firingpins,
@@ -17508,7 +17510,7 @@
},
/obj/structure/cable,
/turf/open/floor/plating,
-/area/station/solars/port)
+/area/station/maintenance/solars/port)
"feP" = (
/obj/effect/turf_decal/trimline/purple/filled/line{
dir = 5
@@ -18486,7 +18488,7 @@
/obj/structure/cable/layer1,
/obj/effect/decal/cleanable/dirt,
/turf/open/floor/catwalk_floor,
-/area/station/solars/starboard/fore)
+/area/station/maintenance/solars/starboard/fore)
"fvM" = (
/obj/structure/stairs/north,
/turf/open/floor/iron/stairs/medium,
@@ -19614,7 +19616,7 @@
/obj/structure/cable/multilayer/connected,
/obj/effect/decal/cleanable/dirt,
/turf/open/floor/catwalk_floor,
-/area/station/solars/port)
+/area/station/maintenance/solars/port)
"fSM" = (
/obj/effect/turf_decal/trimline/brown/filled/line{
dir = 10
@@ -20060,7 +20062,7 @@
/obj/structure/cable,
/obj/effect/decal/cleanable/dirt,
/turf/open/floor/catwalk_floor,
-/area/station/solars/port)
+/area/station/maintenance/solars/port)
"gbl" = (
/obj/structure/chair/office{
dir = 1
@@ -21068,7 +21070,7 @@
},
/obj/structure/cable,
/turf/open/openspace,
-/area/station/solars/starboard/fore)
+/area/station/maintenance/solars/starboard/fore)
"gtZ" = (
/obj/effect/turf_decal/trimline/neutral/filled/line,
/obj/machinery/airalarm/directional/south,
@@ -21371,7 +21373,7 @@
/obj/structure/cable/layer1,
/obj/structure/cable,
/turf/open/floor/plating,
-/area/station/solars/port)
+/area/station/maintenance/solars/port)
"gAH" = (
/obj/effect/turf_decal/trimline/neutral/filled/corner{
dir = 1
@@ -21760,7 +21762,7 @@
/obj/effect/turf_decal/sand/plating,
/obj/machinery/light/small/directional/south,
/turf/open/floor/plating/airless,
-/area/station/solars/port)
+/area/station/solars/port/asteriod)
"gGV" = (
/obj/structure/table,
/obj/item/instrument/harmonica,
@@ -23359,7 +23361,7 @@
/obj/effect/turf_decal/sand/plating,
/obj/structure/cable,
/turf/open/floor/plating/airless,
-/area/station/solars/starboard/fore)
+/area/station/solars/starboard/fore/asteriod)
"hmb" = (
/obj/effect/turf_decal/tile/bar{
dir = 8
@@ -24058,7 +24060,7 @@
/obj/structure/cable,
/obj/structure/cable/layer1,
/turf/open/floor/plating/airless,
-/area/station/solars/starboard/fore)
+/area/station/solars/starboard/fore/asteriod)
"hDF" = (
/obj/machinery/computer/apc_control{
dir = 1
@@ -26447,17 +26449,6 @@
/obj/machinery/status_display/evac/directional/east,
/turf/open/floor/circuit/green,
/area/station/ai_monitored/turret_protected/ai_upload)
-"ixH" = (
-/obj/structure/lattice/catwalk,
-/obj/structure/cable,
-/obj/structure/railing{
- dir = 4
- },
-/obj/structure/railing{
- dir = 8
- },
-/turf/open/space/openspace,
-/area/space/nearstation)
"ixO" = (
/obj/structure/chair/comfy/brown{
dir = 8;
@@ -27807,7 +27798,7 @@
},
/obj/structure/cable,
/turf/open/openspace,
-/area/station/solars/starboard/fore)
+/area/station/maintenance/solars/starboard/fore)
"iXx" = (
/obj/machinery/atmospherics/pipe/smart/manifold4w/scrubbers/visible/layer2,
/obj/structure/lattice,
@@ -28136,7 +28127,7 @@
"jcT" = (
/obj/effect/turf_decal/sand/plating,
/turf/open/floor/plating/airless,
-/area/station/solars/port)
+/area/station/solars/port/asteriod)
"jde" = (
/obj/machinery/door/airlock/engineering{
name = "Vacant Office A"
@@ -28337,7 +28328,7 @@
/obj/machinery/power/apc/auto_name/directional/west,
/obj/effect/turf_decal/sand/plating,
/turf/open/floor/catwalk_floor,
-/area/station/solars/starboard/fore)
+/area/station/maintenance/solars/starboard/fore)
"jgn" = (
/obj/structure/table/reinforced,
/obj/structure/displaycase/forsale/kitchen{
@@ -31478,7 +31469,7 @@
/obj/structure/cable/multilayer/multiz,
/obj/effect/decal/cleanable/dirt,
/turf/open/floor/plating,
-/area/station/solars/port)
+/area/station/maintenance/solars/port)
"kgG" = (
/obj/effect/turf_decal/stripes/line{
dir = 1
@@ -31905,7 +31896,7 @@
"koo" = (
/obj/effect/decal/cleanable/dirt,
/turf/open/floor/catwalk_floor,
-/area/station/solars/starboard/fore)
+/area/station/maintenance/solars/starboard/fore)
"koq" = (
/obj/machinery/air_sensor/oxygen_tank,
/turf/open/floor/engine/o2,
@@ -34503,7 +34494,7 @@
/obj/structure/cable/multilayer/connected,
/obj/effect/decal/cleanable/dirt,
/turf/open/floor/catwalk_floor,
-/area/station/solars/starboard/fore)
+/area/station/maintenance/solars/starboard/fore)
"lhV" = (
/obj/structure/displaycase/trophy,
/obj/machinery/light/warm/directional/west,
@@ -35166,7 +35157,7 @@
/obj/structure/cable,
/obj/effect/decal/cleanable/dirt,
/turf/open/floor/catwalk_floor,
-/area/station/solars/port)
+/area/station/maintenance/solars/port)
"lsx" = (
/obj/structure/chair/stool/directional/north,
/obj/effect/turf_decal/trimline/dark_blue/corner{
@@ -35593,7 +35584,7 @@
/obj/effect/mapping_helpers/airlock/access/all/engineering/general,
/obj/effect/mapping_helpers/airlock/cyclelink_helper,
/turf/open/floor/plating,
-/area/station/solars/starboard/fore)
+/area/station/maintenance/solars/starboard/fore)
"lzo" = (
/obj/machinery/door/window/left/directional/south,
/turf/open/floor/grass,
@@ -36326,7 +36317,7 @@
/obj/structure/cable/layer1,
/obj/effect/decal/cleanable/dirt,
/turf/open/floor/catwalk_floor,
-/area/station/solars/port)
+/area/station/maintenance/solars/port)
"lMJ" = (
/obj/effect/turf_decal/sand/plating,
/obj/effect/turf_decal/siding/thinplating/dark{
@@ -36362,7 +36353,7 @@
/obj/effect/turf_decal/sand/plating,
/obj/structure/cable/layer1,
/turf/open/floor/plating/airless,
-/area/station/solars/port)
+/area/station/solars/port/asteriod)
"lMZ" = (
/obj/effect/turf_decal/trimline/red/filled/corner{
dir = 8
@@ -37925,7 +37916,7 @@
/obj/effect/mapping_helpers/airlock/cyclelink_helper,
/obj/structure/cable,
/turf/open/floor/plating,
-/area/station/solars/port)
+/area/station/maintenance/solars/port)
"mns" = (
/obj/effect/turf_decal/trimline/brown/filled/corner{
dir = 1
@@ -38298,7 +38289,7 @@
},
/obj/effect/turf_decal/sand/plating,
/turf/open/floor/catwalk_floor,
-/area/station/solars/port)
+/area/station/maintenance/solars/port)
"muZ" = (
/obj/effect/turf_decal/bot,
/obj/effect/turf_decal/tile/neutral/fourcorners,
@@ -38910,7 +38901,7 @@
pixel_y = 8
},
/obj/structure/window/reinforced/spawner/directional/north,
-/obj/item/surgery_tray/full/morgue,
+/obj/effect/spawner/surgery_tray/full/morgue,
/obj/structure/window/reinforced/spawner/directional/west,
/turf/open/floor/iron/dark,
/area/station/medical/morgue)
@@ -39095,7 +39086,7 @@
"mJF" = (
/obj/effect/turf_decal/sand/plating,
/turf/open/floor/catwalk_floor,
-/area/station/solars/starboard/fore)
+/area/station/maintenance/solars/starboard/fore)
"mJG" = (
/obj/structure/chair/stool/bar/directional/north,
/obj/effect/turf_decal/siding/thinplating{
@@ -39123,7 +39114,7 @@
/obj/structure/cable/layer1,
/obj/structure/cable,
/turf/open/floor/plating,
-/area/station/solars/port)
+/area/station/maintenance/solars/port)
"mKh" = (
/obj/effect/turf_decal/trimline/yellow/filled/line{
dir = 1
@@ -39596,7 +39587,7 @@
},
/obj/effect/turf_decal/sand/plating,
/turf/open/floor/plating,
-/area/station/solars/starboard/fore)
+/area/station/maintenance/solars/starboard/fore)
"mWC" = (
/obj/machinery/holopad,
/obj/machinery/firealarm/directional/north,
@@ -39928,7 +39919,7 @@
/obj/effect/turf_decal/sand/plating,
/obj/structure/cable,
/turf/open/floor/plating/airless,
-/area/station/solars/port)
+/area/station/solars/port/asteriod)
"ncF" = (
/turf/closed/wall,
/area/station/maintenance/tram/left)
@@ -40056,7 +40047,7 @@
},
/obj/effect/decal/cleanable/dirt,
/turf/open/floor/plating,
-/area/station/solars/starboard/fore)
+/area/station/maintenance/solars/starboard/fore)
"neC" = (
/obj/structure/railing{
dir = 1
@@ -40476,7 +40467,7 @@
"nkU" = (
/obj/effect/turf_decal/sand/plating,
/turf/open/floor/plating/airless,
-/area/station/solars/starboard/fore)
+/area/station/solars/starboard/fore/asteriod)
"nle" = (
/obj/machinery/door/airlock/security{
name = "Evidence Storage"
@@ -41341,7 +41332,7 @@
/obj/structure/cable,
/obj/structure/cable/layer1,
/turf/open/floor/plating/airless,
-/area/station/solars/port)
+/area/station/solars/port/asteriod)
"nzO" = (
/mob/living/carbon/human/species/monkey,
/turf/open/misc/dirt/jungle{
@@ -42046,7 +42037,7 @@
"nNw" = (
/obj/effect/turf_decal/sand/plating,
/turf/open/floor/catwalk_floor,
-/area/station/solars/port)
+/area/station/maintenance/solars/port)
"nNz" = (
/obj/effect/turf_decal/stripes/corner{
dir = 1
@@ -42081,7 +42072,7 @@
/area/station/tcommsat/server)
"nOd" = (
/turf/open/openspace,
-/area/station/solars/starboard/fore)
+/area/station/maintenance/solars/starboard/fore)
"nOe" = (
/obj/effect/turf_decal/trimline/red/filled/line{
dir = 6
@@ -43266,7 +43257,7 @@
},
/obj/effect/decal/cleanable/dirt,
/turf/open/floor/plating,
-/area/station/solars/port)
+/area/station/maintenance/solars/port)
"ojQ" = (
/obj/effect/turf_decal/trimline/blue/filled/corner,
/obj/effect/turf_decal/trimline/blue/filled/corner{
@@ -44030,7 +44021,7 @@
/obj/structure/cable,
/obj/effect/turf_decal/sand/plating,
/turf/open/floor/catwalk_floor,
-/area/station/solars/starboard/fore)
+/area/station/maintenance/solars/starboard/fore)
"oAn" = (
/obj/machinery/airlock_sensor/incinerator_ordmix{
pixel_x = 23;
@@ -45349,7 +45340,7 @@
/obj/structure/cable,
/obj/machinery/light/small/directional/west,
/turf/open/floor/plating/airless,
-/area/station/solars/starboard/fore)
+/area/station/solars/starboard/fore/asteriod)
"pdf" = (
/obj/machinery/camera/directional/north{
network = list("ss13","engineering","Security");
@@ -45775,7 +45766,7 @@
/area/station/cargo/miningdock)
"plk" = (
/turf/closed/wall/r_wall,
-/area/station/solars/starboard/fore)
+/area/station/maintenance/solars/starboard/fore)
"pln" = (
/obj/effect/turf_decal/trimline/neutral/filled/corner{
dir = 8
@@ -46054,7 +46045,7 @@
/obj/structure/cable,
/obj/machinery/light/floor,
/turf/open/floor/plating,
-/area/station/solars/port)
+/area/station/maintenance/solars/port)
"ppU" = (
/obj/structure/railing,
/obj/effect/turf_decal/trimline/tram/filled/line{
@@ -47368,7 +47359,7 @@
/obj/structure/cable/layer1,
/obj/effect/turf_decal/sand/plating,
/turf/open/floor/catwalk_floor,
-/area/station/solars/starboard/fore)
+/area/station/maintenance/solars/starboard/fore)
"pKZ" = (
/obj/structure/table/wood,
/obj/item/flashlight/lamp,
@@ -48162,7 +48153,7 @@
},
/obj/structure/railing,
/turf/open/openspace,
-/area/station/solars/port)
+/area/station/maintenance/solars/port)
"pZH" = (
/obj/structure/lattice/catwalk,
/turf/open/space/basic,
@@ -49051,6 +49042,9 @@
/obj/machinery/microwave,
/turf/open/floor/iron/white,
/area/station/commons/vacant_room)
+"qoG" = (
+/turf/closed/wall,
+/area/station/solars/port/asteriod)
"qoX" = (
/obj/effect/turf_decal/trimline/yellow/filled/line{
dir = 6
@@ -49854,15 +49848,6 @@
/obj/effect/landmark/event_spawn,
/turf/open/floor/iron,
/area/station/cargo/miningdock)
-"qCU" = (
-/obj/structure/lattice/catwalk,
-/obj/machinery/power/solar{
- name = "Starboard Solar Array";
- id = "forestarboard"
- },
-/obj/structure/cable,
-/turf/open/space/basic,
-/area/space/nearstation)
"qCW" = (
/obj/effect/turf_decal/trimline/purple/filled/line{
dir = 1
@@ -51055,7 +51040,7 @@
dir = 8
},
/turf/open/openspace,
-/area/station/solars/port)
+/area/station/maintenance/solars/port)
"qZx" = (
/obj/effect/turf_decal/trimline/neutral/filled/line,
/obj/effect/decal/cleanable/dirt,
@@ -54170,7 +54155,7 @@
},
/obj/structure/railing,
/turf/open/openspace,
-/area/station/solars/port)
+/area/station/maintenance/solars/port)
"sgB" = (
/obj/effect/turf_decal/trimline/red/filled/corner,
/obj/structure/cable,
@@ -54834,7 +54819,7 @@
/area/station/engineering/supermatter/room)
"srF" = (
/turf/open/openspace,
-/area/station/solars/port)
+/area/station/maintenance/solars/port)
"srN" = (
/obj/effect/turf_decal/trimline/neutral/filled/line,
/obj/structure/cable,
@@ -55585,7 +55570,7 @@
"sEZ" = (
/obj/effect/spawner/structure/window/reinforced,
/turf/open/floor/plating,
-/area/station/solars/starboard/fore)
+/area/station/maintenance/solars/starboard/fore)
"sFc" = (
/obj/structure/cable,
/obj/structure/disposalpipe/segment{
@@ -55657,7 +55642,7 @@
/obj/structure/cable,
/obj/machinery/light/floor,
/turf/open/floor/plating,
-/area/station/solars/starboard/fore)
+/area/station/maintenance/solars/starboard/fore)
"sGO" = (
/obj/machinery/atmospherics/pipe/smart/manifold4w/supply/hidden/layer4,
/obj/machinery/atmospherics/pipe/smart/manifold4w/scrubbers/hidden/layer2,
@@ -61857,7 +61842,7 @@
/obj/structure/railing/corner,
/obj/effect/decal/cleanable/dirt,
/turf/open/floor/plating,
-/area/station/solars/port)
+/area/station/maintenance/solars/port)
"uIk" = (
/obj/machinery/atmospherics/pipe/smart/simple/purple/visible,
/obj/effect/turf_decal/trimline/purple/filled/line{
@@ -62299,7 +62284,7 @@
/obj/structure/table/glass,
/obj/effect/turf_decal/trimline/blue/filled/line,
/obj/item/radio/intercom/directional/south,
-/obj/item/surgery_tray/full,
+/obj/effect/spawner/surgery_tray/full,
/turf/open/floor/iron/white,
/area/station/medical/surgery/fore)
"uPv" = (
@@ -63603,7 +63588,7 @@
/obj/structure/cable/layer1,
/obj/effect/turf_decal/sand/plating,
/turf/open/floor/catwalk_floor,
-/area/station/solars/port)
+/area/station/maintenance/solars/port)
"vob" = (
/obj/structure/bookcase/random/religion,
/turf/open/floor/iron/dark,
@@ -64163,7 +64148,7 @@
/obj/structure/cable,
/obj/machinery/light/floor,
/turf/open/floor/plating,
-/area/station/solars/port)
+/area/station/maintenance/solars/port)
"vwT" = (
/obj/structure/cable,
/obj/structure/disposalpipe/segment{
@@ -64328,11 +64313,11 @@
},
/obj/effect/decal/cleanable/dirt,
/turf/open/floor/plating,
-/area/station/solars/starboard/fore)
+/area/station/maintenance/solars/starboard/fore)
"vAx" = (
/obj/effect/decal/cleanable/dirt,
/turf/open/floor/catwalk_floor,
-/area/station/solars/port)
+/area/station/maintenance/solars/port)
"vAF" = (
/obj/structure/chair,
/obj/effect/turf_decal/trimline/neutral/filled/line,
@@ -65378,7 +65363,7 @@
/obj/effect/decal/cleanable/dirt,
/obj/machinery/light/dim/directional/north,
/turf/open/floor/catwalk_floor,
-/area/station/solars/starboard/fore)
+/area/station/maintenance/solars/starboard/fore)
"vTj" = (
/obj/effect/turf_decal/trimline/neutral/warning,
/obj/machinery/door/window/brigdoor/left/directional/north{
@@ -65691,7 +65676,7 @@
/obj/structure/railing,
/obj/structure/cable,
/turf/open/openspace,
-/area/station/solars/starboard/fore)
+/area/station/maintenance/solars/starboard/fore)
"vZZ" = (
/obj/machinery/duct,
/obj/structure/cable,
@@ -69771,7 +69756,7 @@
"xCe" = (
/obj/effect/spawner/structure/window/reinforced,
/turf/open/floor/plating,
-/area/station/solars/port)
+/area/station/maintenance/solars/port)
"xCm" = (
/obj/item/kirbyplants/random,
/obj/effect/turf_decal/tile/blue/anticorner/contrasted,
@@ -69818,7 +69803,7 @@
/obj/effect/mapping_helpers/airlock/access/all/engineering/general,
/obj/effect/mapping_helpers/airlock/cyclelink_helper,
/turf/open/floor/plating,
-/area/station/solars/starboard/fore)
+/area/station/maintenance/solars/starboard/fore)
"xDJ" = (
/obj/machinery/light/cold/directional/south,
/turf/open/floor/iron/freezer,
@@ -70579,7 +70564,7 @@
dir = 8
},
/turf/open/openspace,
-/area/station/solars/port)
+/area/station/maintenance/solars/port)
"xTM" = (
/obj/machinery/atmospherics/components/unary/vent_pump/siphon/monitored/nitrogen_output{
dir = 1
@@ -71004,7 +70989,7 @@
dir = 1
},
/obj/item/radio/intercom/directional/north,
-/obj/item/surgery_tray/full,
+/obj/effect/spawner/surgery_tray/full,
/turf/open/floor/iron/white,
/area/station/medical/surgery/aft)
"yct" = (
@@ -91049,7 +91034,7 @@ aac
aac
aac
gGy
-qEH
+qoG
aac
aac
aac
@@ -93883,7 +93868,7 @@ aST
ncE
ncE
ncE
-bRE
+jcT
oQf
oQf
oQf
@@ -95154,7 +95139,7 @@ aac
aac
aac
aac
-jcT
+lMS
lMS
lMS
aac
@@ -107144,7 +107129,7 @@ aac
aac
aac
aac
-csA
+eSh
aac
aac
aac
@@ -110744,7 +110729,7 @@ aac
aac
aac
aac
-csA
+eSh
nkU
aac
aac
@@ -113046,7 +113031,7 @@ vXM
pZH
vXM
vXM
-qCU
+bwp
vXM
vXM
aev
@@ -114344,7 +114329,7 @@ aac
aac
aac
hlS
-csA
+eSh
aac
aac
aac
@@ -117944,7 +117929,7 @@ aac
aac
aac
aac
-csA
+eSh
aac
aac
aac
@@ -159683,7 +159668,7 @@ opb
oOJ
oOJ
stK
-ixH
+gnp
sOg
oOJ
uXM
diff --git a/_maps/map_files/wawastation/wawastation.dmm b/_maps/map_files/wawastation/wawastation.dmm
index dafa2c4d1a09e..01d06908ce85d 100644
--- a/_maps/map_files/wawastation/wawastation.dmm
+++ b/_maps/map_files/wawastation/wawastation.dmm
@@ -151,6 +151,9 @@
/obj/item/clothing/suit/hooded/ablative,
/obj/item/gun/energy/temperature/security,
/obj/item/gun/energy/ionrifle,
+/obj/item/gun/ballistic/automatic/battle_rifle{
+ pixel_y = 3
+ },
/obj/effect/turf_decal/tile/red/half/contrasted{
dir = 1
},
@@ -2165,9 +2168,7 @@
/area/station/commons/fitness/recreation)
"aJM" = (
/obj/machinery/light/small/directional/north,
-/obj/item/surgery_tray/full/morgue{
- is_portable = 0
- },
+/obj/effect/spawner/surgery_tray/full/morgue/deployed,
/turf/open/floor/iron/dark/textured,
/area/station/medical/morgue)
"aJP" = (
@@ -4663,7 +4664,7 @@
/obj/structure/chair{
dir = 4
},
-/obj/item/ammo_casing/a357/spent,
+/obj/item/ammo_casing/c357/spent,
/obj/effect/decal/cleanable/dirt/dust,
/turf/open/floor/iron/white/small,
/area/station/science/lobby)
@@ -21694,7 +21695,7 @@
/obj/effect/decal/cleanable/blood/tracks{
dir = 4
},
-/obj/item/ammo_casing/a357/spent,
+/obj/item/ammo_casing/c357/spent,
/obj/machinery/atmospherics/components/unary/vent_pump/on/layer4{
dir = 8
},
@@ -34388,7 +34389,7 @@
"mhI" = (
/obj/machinery/power/apc/auto_name/directional/west,
/obj/structure/cable,
-/obj/item/surgery_tray/full/deployed,
+/obj/effect/spawner/surgery_tray/full/deployed,
/obj/effect/turf_decal/tile/blue/full,
/obj/machinery/light_switch/directional/south,
/turf/open/floor/iron/white,
@@ -34886,7 +34887,7 @@
/area/station/engineering/atmos)
"moT" = (
/obj/effect/turf_decal/siding/white,
-/obj/item/ammo_casing/a357/spent,
+/obj/item/ammo_casing/c357/spent,
/turf/open/floor/iron/white/small,
/area/station/science/lobby)
"moU" = (
@@ -38134,7 +38135,7 @@
/turf/open/floor/glass/reinforced,
/area/station/security/prison)
"nvT" = (
-/obj/item/surgery_tray/full/deployed,
+/obj/effect/spawner/surgery_tray/full/deployed,
/obj/effect/turf_decal/tile/blue/fourcorners,
/obj/machinery/power/apc/auto_name/directional/west,
/obj/structure/cable,
@@ -58322,7 +58323,7 @@
/turf/open/openspace,
/area/station/hallway/secondary/exit/departure_lounge)
"uxw" = (
-/obj/item/surgery_tray/full/deployed,
+/obj/effect/spawner/surgery_tray/full/deployed,
/obj/effect/turf_decal/tile/blue/fourcorners,
/obj/machinery/airalarm/directional/west,
/obj/effect/decal/cleanable/dirt/dust,
@@ -65380,7 +65381,7 @@
/turf/open/floor/circuit/green/telecomms/mainframe,
/area/station/tcommsat/server)
"wZU" = (
-/obj/item/crowbar/large/heavy,
+/obj/item/crowbar/large/old,
/turf/open/misc/asteroid,
/area/station/asteroid)
"xad" = (
@@ -68229,7 +68230,7 @@
},
/obj/effect/decal/cleanable/dirt/dust,
/obj/effect/mapping_helpers/broken_floor,
-/obj/item/crowbar/large/heavy,
+/obj/item/crowbar/large,
/turf/open/floor/plating,
/area/station/maintenance/department/medical/central)
"ybh" = (
diff --git a/_maps/modular_generic/ice_l_storage.dmm b/_maps/modular_generic/ice_l_storage.dmm
index 6aca9bec648fb..a130f69cdd1b7 100644
--- a/_maps/modular_generic/ice_l_storage.dmm
+++ b/_maps/modular_generic/ice_l_storage.dmm
@@ -213,7 +213,7 @@
"M" = (
/obj/effect/turf_decal/bot/right,
/obj/structure/closet/crate/large,
-/obj/item/crowbar/large/heavy,
+/obj/item/crowbar/large/twenty_force,
/turf/open/floor/plating,
/area/template_noop)
"N" = (
diff --git a/_maps/modular_generic/station_l_morgue.dmm b/_maps/modular_generic/station_l_morgue.dmm
index 4b4eb63458d1b..3b5adf14d410b 100644
--- a/_maps/modular_generic/station_l_morgue.dmm
+++ b/_maps/modular_generic/station_l_morgue.dmm
@@ -361,7 +361,7 @@
dir = 4
},
/obj/structure/table/reinforced/plastitaniumglass,
-/obj/item/surgery_tray/full/morgue,
+/obj/effect/spawner/surgery_tray/full/morgue,
/obj/structure/railing{
dir = 4
},
diff --git a/_maps/shuttles/emergency_birdshot.dmm b/_maps/shuttles/emergency_birdshot.dmm
index cdf903f034595..1cf44657848d0 100644
--- a/_maps/shuttles/emergency_birdshot.dmm
+++ b/_maps/shuttles/emergency_birdshot.dmm
@@ -749,7 +749,7 @@
/area/shuttle/escape)
"Ko" = (
/obj/structure/table/optable,
-/obj/item/surgery_tray/full,
+/obj/effect/spawner/surgery_tray/full,
/obj/item/clothing/mask/surgical,
/obj/effect/mapping_helpers/broken_floor,
/turf/open/floor/mineral/titanium/white,
diff --git a/_maps/shuttles/emergency_donut.dmm b/_maps/shuttles/emergency_donut.dmm
index f81c084e90bf6..27f9cd7f1ace4 100644
--- a/_maps/shuttles/emergency_donut.dmm
+++ b/_maps/shuttles/emergency_donut.dmm
@@ -317,7 +317,7 @@
/turf/open/floor/plating/airless,
/area/shuttle/escape)
"ed" = (
-/obj/item/surgery_tray/full,
+/obj/effect/spawner/surgery_tray/full,
/obj/item/clothing/suit/apron/surgical,
/obj/item/clothing/mask/surgical,
/obj/structure/table/optable,
diff --git a/_maps/shuttles/emergency_fish.dmm b/_maps/shuttles/emergency_fish.dmm
index b99aa01b096bf..43ec2aa162013 100644
--- a/_maps/shuttles/emergency_fish.dmm
+++ b/_maps/shuttles/emergency_fish.dmm
@@ -844,7 +844,7 @@
/area/shuttle/escape)
"VD" = (
/obj/structure/table/glass,
-/obj/item/surgery_tray/full{
+/obj/effect/spawner/surgery_tray/full{
pixel_y = 6
},
/obj/effect/turf_decal/tile/blue/anticorner/contrasted{
diff --git a/_maps/shuttles/emergency_humpback.dmm b/_maps/shuttles/emergency_humpback.dmm
index 195f342cd3caa..89ab755acbf55 100644
--- a/_maps/shuttles/emergency_humpback.dmm
+++ b/_maps/shuttles/emergency_humpback.dmm
@@ -418,7 +418,7 @@
/area/shuttle/escape)
"zn" = (
/obj/structure/table,
-/obj/item/surgery_tray/full,
+/obj/effect/spawner/surgery_tray/full,
/turf/open/floor/iron/showroomfloor,
/area/shuttle/escape)
"zr" = (
diff --git a/_maps/shuttles/emergency_lance.dmm b/_maps/shuttles/emergency_lance.dmm
index c17d1767b13af..2f62bc945bdd9 100644
--- a/_maps/shuttles/emergency_lance.dmm
+++ b/_maps/shuttles/emergency_lance.dmm
@@ -62,7 +62,7 @@
/area/shuttle/escape)
"bV" = (
/obj/machinery/door/airlock/external/ruin{
- name = "Emegency Shuttle External Airlock"
+ name = "Emergency Shuttle External Airlock"
},
/obj/effect/turf_decal/trimline/dark_blue/arrow_ccw{
dir = 8
@@ -125,7 +125,7 @@
/area/shuttle/escape)
"dW" = (
/obj/machinery/door/airlock/external/ruin{
- name = "Emegency Shuttle External Airlock"
+ name = "Emergency Shuttle External Airlock"
},
/obj/effect/mapping_helpers/airlock/cyclelink_helper{
dir = 8
@@ -287,7 +287,7 @@
/area/shuttle/escape)
"jo" = (
/obj/machinery/door/airlock/external/ruin{
- name = "Emegency Shuttle External Airlock"
+ name = "Emergency Shuttle External Airlock"
},
/obj/effect/turf_decal/trimline/dark_blue/arrow_ccw{
dir = 8
@@ -533,7 +533,7 @@
/area/shuttle/escape)
"pu" = (
/obj/machinery/door/airlock/external/ruin{
- name = "Emegency Shuttle External Airlock"
+ name = "Emergency Shuttle External Airlock"
},
/obj/effect/mapping_helpers/airlock/cyclelink_helper{
dir = 4
@@ -573,7 +573,7 @@
/area/shuttle/escape)
"qe" = (
/obj/machinery/door/airlock/external/ruin{
- name = "Emegency Shuttle External Airlock"
+ name = "Emergency Shuttle External Airlock"
},
/obj/effect/mapping_helpers/airlock/access/any/engineering/general,
/obj/effect/mapping_helpers/airlock/cyclelink_helper{
@@ -609,7 +609,7 @@
/area/shuttle/escape)
"rw" = (
/obj/machinery/door/airlock/external/ruin{
- name = "Emegency Shuttle External Airlock"
+ name = "Emergency Shuttle External Airlock"
},
/obj/effect/mapping_helpers/airlock/access/any/engineering/general,
/obj/effect/mapping_helpers/airlock/cyclelink_helper,
@@ -682,7 +682,7 @@
/area/shuttle/escape)
"uK" = (
/obj/machinery/door/airlock/external/ruin{
- name = "Emegency Shuttle External Airlock"
+ name = "Emergency Shuttle External Airlock"
},
/obj/effect/mapping_helpers/airlock/cyclelink_helper{
dir = 4
@@ -868,7 +868,7 @@
/area/shuttle/escape)
"Cx" = (
/obj/machinery/door/airlock/external/ruin{
- name = "Emegency Shuttle External Airlock"
+ name = "Emergency Shuttle External Airlock"
},
/obj/effect/mapping_helpers/airlock/cyclelink_helper{
dir = 8
@@ -887,7 +887,7 @@
/area/shuttle/escape)
"CR" = (
/obj/machinery/door/airlock/external/ruin{
- name = "Emegency Shuttle External Airlock"
+ name = "Emergency Shuttle External Airlock"
},
/obj/effect/mapping_helpers/airlock/cyclelink_helper{
dir = 4
@@ -968,8 +968,8 @@
/obj/item/book/manual/wiki/surgery{
pixel_x = -15
},
-/obj/item/surgery_tray/full,
-/obj/item/surgery_tray/full{
+/obj/effect/spawner/surgery_tray/full,
+/obj/effect/spawner/surgery_tray/full{
pixel_x = 5
},
/obj/effect/turf_decal/tile/dark_blue/half/contrasted{
@@ -1199,7 +1199,7 @@
/area/shuttle/escape)
"LD" = (
/obj/machinery/door/airlock/external/ruin{
- name = "Emegency Shuttle External Airlock"
+ name = "Emergency Shuttle External Airlock"
},
/obj/effect/mapping_helpers/airlock/cyclelink_helper{
dir = 8
diff --git a/_maps/shuttles/emergency_mini.dmm b/_maps/shuttles/emergency_mini.dmm
index 73c5f42b38f06..89579283e9317 100644
--- a/_maps/shuttles/emergency_mini.dmm
+++ b/_maps/shuttles/emergency_mini.dmm
@@ -227,7 +227,7 @@
"W" = (
/obj/structure/table,
/obj/item/clothing/suit/apron/surgical,
-/obj/item/surgery_tray/full,
+/obj/effect/spawner/surgery_tray/full,
/turf/open/floor/mineral/titanium/white,
/area/shuttle/escape)
"X" = (
diff --git a/_maps/shuttles/emergency_nature.dmm b/_maps/shuttles/emergency_nature.dmm
index 0f793c714753e..16e1b10721268 100644
--- a/_maps/shuttles/emergency_nature.dmm
+++ b/_maps/shuttles/emergency_nature.dmm
@@ -497,7 +497,7 @@
/area/shuttle/escape)
"sF" = (
/obj/effect/turf_decal/trimline/blue/filled/line,
-/obj/item/surgery_tray/full{
+/obj/effect/spawner/surgery_tray/full{
pixel_y = 5
},
/obj/structure/rack,
diff --git a/_maps/shuttles/emergency_nebula.dmm b/_maps/shuttles/emergency_nebula.dmm
index 7b729f72167f4..39359e5c29e9d 100644
--- a/_maps/shuttles/emergency_nebula.dmm
+++ b/_maps/shuttles/emergency_nebula.dmm
@@ -2447,7 +2447,7 @@
"XV" = (
/obj/structure/table/reinforced,
/obj/item/radio/intercom/directional/east,
-/obj/item/surgery_tray/full,
+/obj/effect/spawner/surgery_tray/full,
/obj/machinery/defibrillator_mount/directional/south,
/obj/machinery/light/small/directional/south,
/turf/open/floor/iron/kitchen_coldroom/freezerfloor,
diff --git a/_maps/shuttles/emergency_northstar.dmm b/_maps/shuttles/emergency_northstar.dmm
index 798a4d9671a4e..672dd9b58e23b 100644
--- a/_maps/shuttles/emergency_northstar.dmm
+++ b/_maps/shuttles/emergency_northstar.dmm
@@ -127,7 +127,7 @@
"nC" = (
/obj/structure/table/reinforced/rglass,
/obj/item/defibrillator/loaded,
-/obj/item/surgery_tray/full{
+/obj/effect/spawner/surgery_tray/full{
pixel_y = 13
},
/obj/effect/turf_decal/tile/blue/anticorner{
diff --git a/_maps/shuttles/emergency_raven.dmm b/_maps/shuttles/emergency_raven.dmm
index 7e3937568001f..7eb255fc2d42d 100644
--- a/_maps/shuttles/emergency_raven.dmm
+++ b/_maps/shuttles/emergency_raven.dmm
@@ -742,7 +742,7 @@
/area/shuttle/escape)
"cd" = (
/obj/machinery/door/airlock/external/ruin{
- name = "Emegency Shuttle External Airlock"
+ name = "Emergency Shuttle External Airlock"
},
/turf/open/floor/plating,
/area/shuttle/escape)
@@ -1456,7 +1456,7 @@
/area/shuttle/escape)
"eo" = (
/obj/machinery/door/airlock/external/ruin{
- name = "Emegency Shuttle External Airlock"
+ name = "Emergency Shuttle External Airlock"
},
/obj/docking_port/mobile/emergency{
name = "CentCom Raven Cruiser"
diff --git a/_maps/shuttles/emergency_russiafightpit.dmm b/_maps/shuttles/emergency_russiafightpit.dmm
index 33baf94c7ecfe..a00f6804bbcb8 100644
--- a/_maps/shuttles/emergency_russiafightpit.dmm
+++ b/_maps/shuttles/emergency_russiafightpit.dmm
@@ -438,7 +438,7 @@
"iJ" = (
/obj/effect/decal/cleanable/dirt,
/obj/structure/table,
-/obj/item/surgery_tray/full,
+/obj/effect/spawner/surgery_tray/full,
/obj/item/clothing/gloves/fingerless,
/turf/open/floor/iron,
/area/shuttle/escape)
diff --git a/_maps/shuttles/emergency_shadow.dmm b/_maps/shuttles/emergency_shadow.dmm
index 5afa72919cb28..f5c2cd049f11d 100644
--- a/_maps/shuttles/emergency_shadow.dmm
+++ b/_maps/shuttles/emergency_shadow.dmm
@@ -1000,7 +1000,7 @@
"Sb" = (
/obj/structure/table,
/obj/structure/window/reinforced/spawner/directional/west,
-/obj/item/surgery_tray/full,
+/obj/effect/spawner/surgery_tray/full,
/obj/item/clothing/suit/apron/surgical,
/obj/item/clothing/mask/surgical,
/obj/item/clothing/gloves/latex/nitrile{
diff --git a/_maps/shuttles/emergency_tram.dmm b/_maps/shuttles/emergency_tram.dmm
index 5ec40d242d670..38b2608865be4 100644
--- a/_maps/shuttles/emergency_tram.dmm
+++ b/_maps/shuttles/emergency_tram.dmm
@@ -203,7 +203,7 @@
/area/shuttle/escape)
"aX" = (
/obj/structure/table/optable,
-/obj/item/surgery_tray/full,
+/obj/effect/spawner/surgery_tray/full,
/obj/item/clothing/mask/surgical,
/turf/open/floor/mineral/titanium/blue,
/area/shuttle/escape)
diff --git a/_maps/shuttles/emergency_tranquility.dmm b/_maps/shuttles/emergency_tranquility.dmm
index ae3924becbc02..0de15be89cbc2 100644
--- a/_maps/shuttles/emergency_tranquility.dmm
+++ b/_maps/shuttles/emergency_tranquility.dmm
@@ -2530,7 +2530,7 @@
/obj/effect/turf_decal/tile/blue/opposingcorners,
/obj/structure/table,
/obj/item/lazarus_injector,
-/obj/item/surgery_tray/full,
+/obj/effect/spawner/surgery_tray/full,
/obj/item/clothing/gloves/latex/nitrile{
pixel_y = 4
},
diff --git a/_maps/shuttles/hunter_mi13_foodtruck.dmm b/_maps/shuttles/hunter_mi13_foodtruck.dmm
index 34e6bb730ca61..4dca72eaae8c3 100644
--- a/_maps/shuttles/hunter_mi13_foodtruck.dmm
+++ b/_maps/shuttles/hunter_mi13_foodtruck.dmm
@@ -15,7 +15,7 @@
/area/shuttle/hunter/mi13_foodtruck)
"af" = (
/obj/structure/table/reinforced/plastitaniumglass,
-/obj/item/surgery_tray/full,
+/obj/effect/spawner/surgery_tray/full,
/obj/structure/sign/poster/contraband/hacking_guide/directional/south,
/turf/open/floor/circuit/red/off,
/area/shuttle/hunter/mi13_foodtruck)
diff --git a/_maps/shuttles/ruin_cyborg_mothership.dmm b/_maps/shuttles/ruin_cyborg_mothership.dmm
index ea07e4354de65..a0cb642c086a0 100644
--- a/_maps/shuttles/ruin_cyborg_mothership.dmm
+++ b/_maps/shuttles/ruin_cyborg_mothership.dmm
@@ -709,7 +709,7 @@
/area/shuttle/ruin/cyborg_mothership)
"Oq" = (
/obj/structure/table,
-/obj/item/surgery_tray/full,
+/obj/effect/spawner/surgery_tray/full,
/obj/effect/turf_decal/bot,
/obj/structure/sink/directional/east,
/obj/item/toy/figure/borg{
diff --git a/_maps/shuttles/whiteship_birdshot.dmm b/_maps/shuttles/whiteship_birdshot.dmm
index ed1936e3a1cf8..e076c841605c0 100644
--- a/_maps/shuttles/whiteship_birdshot.dmm
+++ b/_maps/shuttles/whiteship_birdshot.dmm
@@ -146,7 +146,7 @@
pixel_y = 3
},
/obj/item/reagent_containers/blood,
-/obj/item/surgery_tray/full{
+/obj/effect/spawner/surgery_tray/full{
pixel_x = 2;
pixel_y = 9
},
@@ -736,7 +736,6 @@
/turf/open/floor/iron/grimy,
/area/shuttle/abandoned/crew)
"yM" = (
-/obj/machinery/light/cold/directional/south,
/obj/effect/decal/cleanable/dirt,
/obj/machinery/light/broken/directional/south,
/obj/machinery/firealarm/directional/east,
@@ -1084,15 +1083,6 @@
},
/turf/open/floor/iron/small,
/area/shuttle/abandoned/pod)
-"Jk" = (
-/obj/effect/decal/cleanable/dirt,
-/obj/machinery/power/apc/auto_name/directional/south,
-/obj/structure/cable,
-/obj/machinery/atmospherics/pipe/smart/manifold4w/supply/hidden{
- dir = 4
- },
-/turf/open/floor/iron/smooth_large,
-/area/shuttle/abandoned/cargo)
"Jn" = (
/obj/structure/dresser,
/obj/effect/decal/cleanable/dirt,
@@ -1712,7 +1702,7 @@ wE
RX
gV
EX
-Jk
+Ui
kQ
Jn
iS
diff --git a/_maps/shuttles/whiteship_box.dmm b/_maps/shuttles/whiteship_box.dmm
index ed2e2a17b9077..9a20e38ebcd5d 100644
--- a/_maps/shuttles/whiteship_box.dmm
+++ b/_maps/shuttles/whiteship_box.dmm
@@ -104,7 +104,7 @@
},
/obj/machinery/airalarm/directional/north,
/obj/effect/mapping_helpers/airalarm/all_access,
-/obj/item/surgery_tray/full{
+/obj/effect/spawner/surgery_tray/full{
pixel_y = 4
},
/obj/item/clothing/suit/apron/surgical,
diff --git a/_maps/shuttles/whiteship_delta.dmm b/_maps/shuttles/whiteship_delta.dmm
index f75f87001b517..e7981b169b8aa 100644
--- a/_maps/shuttles/whiteship_delta.dmm
+++ b/_maps/shuttles/whiteship_delta.dmm
@@ -1621,7 +1621,7 @@
/area/shuttle/abandoned/medbay)
"dO" = (
/obj/structure/table,
-/obj/item/surgery_tray/full{
+/obj/effect/spawner/surgery_tray/full{
pixel_y = 4
},
/obj/effect/decal/cleanable/dirt,
diff --git a/_maps/templates/holodeck_medicalsim.dmm b/_maps/templates/holodeck_medicalsim.dmm
index 7e4162ce074a4..269b9c41f017c 100644
--- a/_maps/templates/holodeck_medicalsim.dmm
+++ b/_maps/templates/holodeck_medicalsim.dmm
@@ -109,10 +109,12 @@
/area/template_noop)
"gr" = (
/obj/structure/table/glass,
-/obj/item/retractor,
/obj/effect/turf_decal/tile/red/half/contrasted{
dir = 1
},
+/obj/item/stack/medical/gauze,
+/obj/item/retractor,
+/obj/item/cautery,
/turf/open/floor/holofloor{
icon_state = "white"
},
@@ -467,12 +469,10 @@
},
/area/template_noop)
"Qu" = (
-/obj/structure/table/glass,
-/obj/item/stack/medical/gauze,
-/obj/item/cautery,
/obj/effect/turf_decal/tile/red/half/contrasted{
dir = 1
},
+/obj/structure/closet/crate/freezer/organ,
/turf/open/floor/holofloor{
icon_state = "white"
},
diff --git a/_maps/templates/lazy_templates/ninja_den.dmm b/_maps/templates/lazy_templates/ninja_den.dmm
index 324ebc3209380..f96ce777568f9 100644
--- a/_maps/templates/lazy_templates/ninja_den.dmm
+++ b/_maps/templates/lazy_templates/ninja_den.dmm
@@ -1041,7 +1041,6 @@
/obj/machinery/vending/coffee{
default_price = 0;
extra_price = 0;
- fair_market_price = 0;
name = "\improper Jim Norton's Quebecois Coffee"
},
/turf/open/floor/wood/large,
@@ -1652,7 +1651,7 @@
/area/centcom/central_command_areas/holding)
"Mz" = (
/obj/structure/closet,
-/obj/item/surgery_tray/full,
+/obj/effect/spawner/surgery_tray/full,
/obj/machinery/iv_drip,
/obj/item/emergency_bed,
/obj/item/storage/medkit/regular,
diff --git a/_maps/templates/lazy_templates/nukie_base.dmm b/_maps/templates/lazy_templates/nukie_base.dmm
index 60c5b1dcb5825..5178b0f935630 100644
--- a/_maps/templates/lazy_templates/nukie_base.dmm
+++ b/_maps/templates/lazy_templates/nukie_base.dmm
@@ -68,6 +68,17 @@
/obj/effect/turf_decal/siding/thinplating_new/light{
dir = 8
},
+/obj/structure/closet/syndicate/personal,
+/obj/item/reagent_containers/cup/beaker/large,
+/obj/item/reagent_containers/cup/beaker/large,
+/obj/item/reagent_containers/cup/beaker/large,
+/obj/item/reagent_containers/cup/beaker/large,
+/obj/item/reagent_containers/cup/beaker/large,
+/obj/item/reagent_containers/cup/beaker/large,
+/obj/item/reagent_containers/cup/beaker/large,
+/obj/item/reagent_containers/cup/beaker/large,
+/obj/item/reagent_containers/cup/beaker/large,
+/obj/item/reagent_containers/cup/beaker/large,
/turf/open/floor/mineral/plastitanium,
/area/centcom/syndicate_mothership/expansion_chemicalwarfare)
"bo" = (
@@ -111,7 +122,7 @@
dir = 5
},
/obj/structure/table/reinforced/plasmarglass,
-/obj/item/surgery_tray/full{
+/obj/effect/spawner/surgery_tray/full{
pixel_y = -11
},
/obj/item/storage/belt/medical,
diff --git a/_maps/virtual_domains/island_brawl.dmm b/_maps/virtual_domains/island_brawl.dmm
index 62a63f81bab2e..f1f291b152a26 100644
--- a/_maps/virtual_domains/island_brawl.dmm
+++ b/_maps/virtual_domains/island_brawl.dmm
@@ -854,10 +854,6 @@
},
/turf/open/floor/plating,
/area/virtual_domain)
-"kI" = (
-/obj/effect/turf_decal/sand,
-/turf/closed/wall/mineral/wood,
-/area/virtual_domain)
"kJ" = (
/obj/machinery/shower/directional/south,
/obj/effect/turf_decal/siding/wood{
@@ -3177,10 +3173,6 @@
},
/turf/open/floor/iron/white/textured_large,
/area/virtual_domain)
-"NZ" = (
-/obj/effect/turf_decal/sand,
-/turf/closed/wall/mineral/wood,
-/area/virtual_domain/fullbright)
"Of" = (
/obj/item/reagent_containers/cup/soda_cans/space_mountain_wind{
pixel_x = -17;
@@ -7866,9 +7858,9 @@ bX
ev
ka
ka
-NZ
-NZ
-eK
+Bq
+Bq
+Bq
JN
JN
ka
@@ -7948,7 +7940,7 @@ bX
Yo
sz
ka
-NZ
+Bq
YI
LD
JN
@@ -8030,9 +8022,9 @@ Yo
Yo
sz
ka
-NZ
-kI
-eK
+Bq
+Bq
+Bq
JN
JN
ka
@@ -8112,7 +8104,7 @@ Yo
ka
ka
ka
-NZ
+Bq
Qk
Uf
JN
@@ -8194,9 +8186,9 @@ ka
th
ka
eK
-kI
-kI
-eK
+Bq
+Bq
+Bq
JN
JN
ka
@@ -8276,7 +8268,7 @@ ka
ka
ka
ka
-NZ
+Bq
ZB
sa
JN
@@ -8358,9 +8350,9 @@ ka
ka
ka
ka
-eK
-eK
-eK
+Bq
+Bq
+Bq
JN
JN
ka
@@ -11225,8 +11217,8 @@ cO
oa
th
ka
-NZ
-NZ
+eK
+eK
eK
ka
ka
@@ -11307,7 +11299,7 @@ cO
wb
MT
ka
-NZ
+eK
EA
xe
ka
diff --git a/_maps/virtual_domains/meta_central.dmm b/_maps/virtual_domains/meta_central.dmm
index 2fc87ae17c818..3fc4ed7f21e1f 100644
--- a/_maps/virtual_domains/meta_central.dmm
+++ b/_maps/virtual_domains/meta_central.dmm
@@ -4780,7 +4780,7 @@
"NO" = (
/obj/machinery/airalarm/directional/west,
/obj/structure/broken_flooring/singular/always_floorplane/directional/east,
-/obj/item/surgery_tray/full/deployed,
+/obj/effect/spawner/surgery_tray/full/deployed,
/turf/open/floor/plating,
/area/virtual_domain)
"NR" = (
diff --git a/code/__DEFINES/_flags.dm b/code/__DEFINES/_flags.dm
index 9b3b239034b96..4dff2007b39e3 100644
--- a/code/__DEFINES/_flags.dm
+++ b/code/__DEFINES/_flags.dm
@@ -142,6 +142,8 @@ GLOBAL_LIST_INIT(bitflags, list(1, 2, 4, 8, 16, 32, 64, 128, 256, 512, 1024, 204
#define NO_BOH (1<<18)
/// This area prevents fishing from removing unique/limited loot from sources that're also used outside of it.
#define UNLIMITED_FISHING (1<<19)
+/// This area is prevented from having gravity (ie. space, nearstation, or outside solars)
+#define NO_GRAVITY (1<<20)
/*
These defines are used specifically with the atom/pass_flags bitmask
diff --git a/code/__DEFINES/anomaly.dm b/code/__DEFINES/anomaly.dm
index 7422af3fc65d7..6ca1db70678da 100644
--- a/code/__DEFINES/anomaly.dm
+++ b/code/__DEFINES/anomaly.dm
@@ -4,7 +4,7 @@
*/
///Time in ticks before the anomaly goes poof/explodes depending on type.
-#define ANOMALY_COUNTDOWN_TIMER (99 SECONDS)
+#define ANOMALY_COUNTDOWN_TIMER (120 SECONDS)
/**
* Nuisance/funny anomalies
diff --git a/code/__DEFINES/colors.dm b/code/__DEFINES/colors.dm
index d1fff3b20edf5..6e9af2cdb9929 100644
--- a/code/__DEFINES/colors.dm
+++ b/code/__DEFINES/colors.dm
@@ -11,6 +11,19 @@
#define FIXED_COLOUR_PRIORITY 4
///how many colour priority levels there are.
#define COLOUR_PRIORITY_AMOUNT 4
+/// If this is a plain atom color
+#define ATOM_COLOR_TYPE_NORMAL "normal"
+/// If this is a color filter
+#define ATOM_COLOR_TYPE_FILTER "filter"
+// Indexes for color arrays
+#define ATOM_COLOR_VALUE_INDEX 1
+#define ATOM_COLOR_TYPE_INDEX 2
+#define ATOM_PRIORITY_COLOR_FILTER "atom_priority_color"
+#define ATOM_PRIORITY_COLOR_FILTER_PRIORITY -1
+/// Multiply pixel's saturation by color's saturation. Paints accents while keeping dim areas dim.
+#define SATURATION_MULTIPLY "multiply"
+/// Always affects the original pixel's saturation and lightness.
+#define SATURATION_OVERRIDE "always"
#define COLOR_DARKMODE_BACKGROUND "#202020"
#define COLOR_DARKMODE_DARKBACKGROUND "#171717"
diff --git a/code/__DEFINES/dcs/signals/signals_mob/signals_mob_living.dm b/code/__DEFINES/dcs/signals/signals_mob/signals_mob_living.dm
index ba43900425bde..3569b9af1e61d 100644
--- a/code/__DEFINES/dcs/signals/signals_mob/signals_mob_living.dm
+++ b/code/__DEFINES/dcs/signals/signals_mob/signals_mob_living.dm
@@ -244,6 +244,7 @@
///from /mob/living/proc/check_block(): (atom/hit_by, damage, attack_text, attack_type, armour_penetration, damage_type)
#define COMSIG_LIVING_CHECK_BLOCK "living_check_block"
+ #define FAILED_BLOCK NONE
#define SUCCESSFUL_BLOCK (1<<0)
///Hit by successful disarm attack (mob/living/attacker, zone_targeted, item/weapon)
@@ -287,6 +288,8 @@
/// From /datum/element/basic_eating/finish_eating()
#define COMSIG_MOB_ATE "mob_ate"
+ ///cancel post eating
+ #define COMSIG_MOB_TERMINATE_EAT (1<<0)
///From mob/living/carbon/proc/throw_mode_on and throw_mode_off
#define COMSIG_LIVING_THROW_MODE_TOGGLE "living_throw_mode_toggle"
diff --git a/code/__DEFINES/dcs/signals/signals_object.dm b/code/__DEFINES/dcs/signals/signals_object.dm
index 7e7a5f17837ac..63ebfdf98b21f 100644
--- a/code/__DEFINES/dcs/signals/signals_object.dm
+++ b/code/__DEFINES/dcs/signals/signals_object.dm
@@ -546,3 +546,8 @@
///Sent from /obj/item/skillchip/on_remove()
#define COMSIG_SKILLCHIP_REMOVED "skillchip_removed"
+
+/// Sent from /obj/item/organ/wings/functional/proc/open_wings(): (mob/living/carbon/owner)
+#define COMSIG_WINGS_OPENED "wings_opened"
+/// Sent from /obj/item/organ/wings/functional/proc/close_wings(): (mob/living/carbon/owner)
+#define COMSIG_WINGS_CLOSED "wings_closed"
diff --git a/code/__DEFINES/fish.dm b/code/__DEFINES/fish.dm
index efa29f98dc2a0..abaa2224029ba 100644
--- a/code/__DEFINES/fish.dm
+++ b/code/__DEFINES/fish.dm
@@ -135,6 +135,17 @@
///The coefficient for maximum weight/size divergence relative to the averages.
#define MAX_FISH_DEVIATION_COEFF 2.5
+/**
+ * Base multiplier of the difference between current size and weight and their maximum value
+ * used to calculate how much fish grow each time they're fed, alongside with the current hunger,
+ * and the current size and weight, meaning bigger fish naturally tend to grow way slowier
+ */
+#define FISH_GROWTH_MULT 0.38
+/// Growth peaks at 45% hunger but very rapidly wanes past that.
+#define FISH_GROWTH_PEAK 0.45
+/// Used as part of the divisor to slow down growth of bigger fish
+#define FISH_SIZE_WEIGHT_GROWTH_MALUS 0.5
+
///The volume of the grind results is multiplied by the fish' weight and divided by this.
#define FISH_GRIND_RESULTS_WEIGHT_DIVISOR 500
///The number of fillets is multiplied by the fish' size and divided by this.
diff --git a/code/__DEFINES/gravity.dm b/code/__DEFINES/gravity.dm
index da81c0465cabc..83177b7ebb3f4 100644
--- a/code/__DEFINES/gravity.dm
+++ b/code/__DEFINES/gravity.dm
@@ -47,7 +47,8 @@
* This should only be possible on multi-z maps because it works like shit on maps that aren't.
*/
#define NEGATIVE_GRAVITY -1
-
+/// Used to indicate no gravity
+#define ZERO_GRAVITY 0
#define STANDARD_GRAVITY 1 //Anything above this is high gravity, anything below no grav until negative gravity
/// The gravity strength threshold for high gravity damage.
#define GRAVITY_DAMAGE_THRESHOLD 3
diff --git a/code/__DEFINES/inventory.dm b/code/__DEFINES/inventory.dm
index 1ead820c702e5..c7d7706bc67ee 100644
--- a/code/__DEFINES/inventory.dm
+++ b/code/__DEFINES/inventory.dm
@@ -282,6 +282,38 @@ GLOBAL_LIST_INIT(mining_suit_allowed, list(
/obj/item/gun/ballistic/bow, // DOPPLER EDIT ADDITION
))
+/// List of all "tools" that can fit into belts or work from toolboxes
+
+GLOBAL_LIST_INIT(tool_items, list(
+ /obj/item/airlock_painter,
+ /obj/item/analyzer,
+ /obj/item/assembly/signaler,
+ /obj/item/construction/rcd,
+ /obj/item/construction/rld,
+ /obj/item/construction/rtd,
+ /obj/item/crowbar,
+ /obj/item/extinguisher/mini,
+ /obj/item/flashlight,
+ /obj/item/forcefield_projector,
+ /obj/item/geiger_counter,
+ /obj/item/holosign_creator/atmos,
+ /obj/item/holosign_creator/engineering,
+ /obj/item/inducer,
+ /obj/item/lightreplacer,
+ /obj/item/multitool,
+ /obj/item/pipe_dispenser,
+ /obj/item/pipe_painter,
+ /obj/item/plunger,
+ /obj/item/radio,
+ /obj/item/screwdriver,
+ /obj/item/stack/cable_coil,
+ /obj/item/t_scanner,
+ /obj/item/weldingtool,
+ /obj/item/wirecutters,
+ /obj/item/wrench,
+ /obj/item/spess_knife,
+))
+
/// String for items placed into the left pocket.
#define LOCATION_LPOCKET "in your left pocket"
/// String for items placed into the right pocket
diff --git a/code/__DEFINES/machines.dm b/code/__DEFINES/machines.dm
index 25893cae63f72..a75fa6fac8e5a 100644
--- a/code/__DEFINES/machines.dm
+++ b/code/__DEFINES/machines.dm
@@ -23,8 +23,12 @@
#define STATIC_TO_DYNAMIC_CHANNEL(static_channel) (static_channel - (AREA_USAGE_STATIC_START - AREA_USAGE_DYNAMIC_START))
//Power use
+
+/// dont use power
#define NO_POWER_USE 0
+/// use idle_power_usage i.e. the power needed just to keep the machine on
#define IDLE_POWER_USE 1
+/// use active_power_usage i.e. the power the machine consumes to perform a specific task
#define ACTIVE_POWER_USE 2
///Base global power consumption for idling machines
@@ -69,6 +73,8 @@
// DOPPLER EDIT ADDITION
/// Can be made by the orderable colony fabricator
#define COLONY_FABRICATOR (1<<11)
+/// Can be made in emergency damage lathes
+#define DAMAGE_FAB (1<<12)
// DOPPLER EDIT END
#define HYPERTORUS_INACTIVE 0 // No or minimal energy
diff --git a/code/__DEFINES/mobs.dm b/code/__DEFINES/mobs.dm
index 1fda506768bc5..0c0865bd97baf 100644
--- a/code/__DEFINES/mobs.dm
+++ b/code/__DEFINES/mobs.dm
@@ -664,6 +664,7 @@
// Hair masks
#define HAIR_MASK_HIDE_ABOVE_45_DEG_MEDIUM "hide_above_45deg_medium"
#define HAIR_MASK_HIDE_ABOVE_45_DEG_LOW "hide_above_45deg_low"
+#define HAIR_MASK_HIDE_WINTERHOOD "hide_winterhood"
// Height defines
// - They are numbers so you can compare height values (x height < y height)
diff --git a/code/__DEFINES/pipe_construction.dm b/code/__DEFINES/pipe_construction.dm
index 540cadabc47a2..415df95bb344a 100644
--- a/code/__DEFINES/pipe_construction.dm
+++ b/code/__DEFINES/pipe_construction.dm
@@ -1,12 +1,21 @@
//Construction Categories
-#define PIPE_STRAIGHT 0 //2 directions: N/S, E/W
-#define PIPE_BENDABLE 1 //6 directions: N/S, E/W, N/E, N/W, S/E, S/W
-#define PIPE_TRINARY 2 //4 directions: N/E/S, E/S/W, S/W/N, W/N/E
-#define PIPE_TRIN_M 3 //8 directions: N->S+E, S->N+E, N->S+W, S->N+W, E->W+S, W->E+S, E->W+N, W->E+N
-#define PIPE_UNARY 4 //4 directions: N, S, E, W
-#define PIPE_ONEDIR 5 //1 direction: N/S/E/W
-#define PIPE_UNARY_FLIPPABLE 6 //8 directions: N/S/E/W/N-flipped/S-flipped/E-flipped/W-flipped
-#define PIPE_ONEDIR_FLIPPABLE 7 //2 direction: N/S/E/W, N-flipped/S-flipped/E-flipped/W-flipped
+
+///2 directions: N/S, E/W
+#define PIPE_STRAIGHT 0
+///6 directions: N/S, E/W, N/E, N/W, S/E, S/W
+#define PIPE_BENDABLE 1
+///4 directions: N/E/S, E/S/W, S/W/N, W/N/E
+#define PIPE_TRINARY 2
+///8 directions: N->S+E, S->N+E, N->S+W, S->N+W, E->W+S, W->E+S, E->W+N, W->E+N
+#define PIPE_TRIN_M 3
+///4 directions: N, S, E, W
+#define PIPE_UNARY 4
+///1 direction: N/S/E/W
+#define PIPE_ONEDIR 5
+///8 directions: N/S/E/W/N-flipped/S-flipped/E-flipped/W-flipped
+#define PIPE_UNARY_FLIPPABLE 6
+///2 direction: N/S/E/W, N-flipped/S-flipped/E-flipped/W-flipped
+#define PIPE_ONEDIR_FLIPPABLE 7
//Disposal pipe relative connection directions
#define DISP_DIR_BASE 0
diff --git a/code/__DEFINES/projectiles.dm b/code/__DEFINES/projectiles.dm
index c3e861b56c99f..b9b13eb8fdc4b 100644
--- a/code/__DEFINES/projectiles.dm
+++ b/code/__DEFINES/projectiles.dm
@@ -62,6 +62,8 @@
#define CALIBER_TENTACLE "tentacle"
/// The caliber used by pipeguns and pipe pistols
#define CALIBER_JUNK "junk"
+/// The caliber used by the (gatfruit) peashooter
+#define CALIBER_PEA "pea"
/// For gunpoints, how many tiles around the target the shooter can roam without losing their shot
#define GUNPOINT_SHOOTER_STRAY_RANGE 2
diff --git a/code/__DEFINES/rust_g.dm b/code/__DEFINES/rust_g.dm
index d7a04afeede49..84f0c5d0334c0 100644
--- a/code/__DEFINES/rust_g.dm
+++ b/code/__DEFINES/rust_g.dm
@@ -161,10 +161,19 @@
#define rustg_git_revparse(rev) RUSTG_CALL(RUST_G, "rg_git_revparse")(rev)
/**
- * Returns the date of the given revision in the format YYYY-MM-DD.
- * Returns null if the revision is invalid.
+ * Returns the date of the given revision using the provided format.
+ * Defaults to returning %F which is YYYY-MM-DD.
*/
-#define rustg_git_commit_date(rev) RUSTG_CALL(RUST_G, "rg_git_commit_date")(rev)
+/proc/rustg_git_commit_date(rev, format = "%F")
+ return RUSTG_CALL(RUST_G, "rg_git_commit_date")(rev, format)
+
+/**
+ * Returns the formatted datetime string of HEAD using the provided format.
+ * Defaults to returning %F which is YYYY-MM-DD.
+ * This is different to rustg_git_commit_date because it only needs the logs directory.
+ */
+/proc/rustg_git_commit_date_head(format = "%F")
+ return RUSTG_CALL(RUST_G, "rg_git_commit_date_head")(format)
#define RUSTG_HTTP_METHOD_GET "get"
#define RUSTG_HTTP_METHOD_PUT "put"
@@ -187,6 +196,20 @@
#define rustg_noise_get_at_coordinates(seed, x, y) RUSTG_CALL(RUST_G, "noise_get_at_coordinates")(seed, x, y)
+/**
+ * Generates a 2D poisson disk distribution ('blue noise'), which is relatively uniform.
+ *
+ * params:
+ * `seed`: str
+ * `width`: int, width of the noisemap (see world.maxx)
+ * `length`: int, height of the noisemap (see world.maxy)
+ * `radius`: int, distance between points on the noisemap
+ *
+ * returns:
+ * a width*length length string of 1s and 0s representing a 2D poisson sample collapsed into a 1D string
+ */
+#define rustg_noise_poisson_map(seed, width, length, radius) RUSTG_CALL(RUST_G, "noise_poisson_map")(seed, width, length, radius)
+
/*
* Takes in a string and json_encode()"d lists to produce a sanitized string.
* This function operates on whitelists, there is currently no way to blacklist.
@@ -238,3 +261,45 @@
#define url_decode(text) rustg_url_decode(text)
#endif
+/// Provided a static RSC file path or a raw text file path, returns the duration of the file in deciseconds as a float.
+/proc/rustg_sound_length(file_path)
+ var/static/list/sound_cache
+ if(isnull(sound_cache))
+ sound_cache = list()
+
+ . = 0
+
+ if(!istext(file_path))
+ if(!isfile(file_path))
+ CRASH("rustg_sound_length error: Passed non-text object")
+
+ if(length("[file_path]")) // Runtime generated RSC references stringify into 0-length strings.
+ file_path = "[file_path]"
+ else
+ CRASH("rustg_sound_length does not support non-static file refs.")
+
+ var/cached_length = sound_cache[file_path]
+ if(!isnull(cached_length))
+ return cached_length
+
+ var/ret = RUSTG_CALL(RUST_G, "sound_len")(file_path)
+ var/as_num = text2num(ret)
+ if(isnull(ret))
+ . = 0
+ CRASH("rustg_sound_length error: [ret]")
+
+ sound_cache[file_path] = as_num
+ return as_num
+
+
+#define RUSTG_SOUNDLEN_SUCCESSES "successes"
+#define RUSTG_SOUNDLEN_ERRORS "errors"
+/**
+ * Returns a nested key-value list containing "successes" and "errors"
+ * The format is as follows:
+ * list(
+ * RUSTG_SOUNDLEN_SUCCESES = list("sounds/test.ogg" = 25.34),
+ * RUSTG_SOUNDLEN_ERRORS = list("sound/bad.png" = "SoundLen: Unable to decode file."),
+ *)
+*/
+#define rustg_sound_length_list(file_paths) json_decode(RUSTG_CALL(RUST_G, "sound_len_list")(json_encode(file_paths)))
diff --git a/code/__DEFINES/say.dm b/code/__DEFINES/say.dm
index 80c316f3585a9..d905129b19b74 100644
--- a/code/__DEFINES/say.dm
+++ b/code/__DEFINES/say.dm
@@ -53,6 +53,9 @@
#define MODE_VOCALCORDS "cords"
#define MODE_KEY_VOCALCORDS "x"
+/// Automatically playing a set of lines
+#define MODE_SEQUENTIAL "sequential"
+
#define MODE_MAFIA "mafia"
/// Applies singing characters to the message
diff --git a/code/__DEFINES/shuttles.dm b/code/__DEFINES/shuttles.dm
index 759121e3b8dd8..12f15ab1e68dc 100644
--- a/code/__DEFINES/shuttles.dm
+++ b/code/__DEFINES/shuttles.dm
@@ -62,6 +62,7 @@
#define ENGINE_COEFF_MIN 0.5
#define ENGINE_COEFF_MAX 2
#define ENGINE_DEFAULT_MAXSPEED_ENGINES 5
+#define ENGINE_START_TIME 100
// Alert level related
#define ALERT_COEFF_AUTOEVAC_NORMAL 2.5
@@ -120,3 +121,12 @@
#define SHUTTLE_EVENT_MISS_SHUTTLE 1 << 0
///spawned stuff should hit the shuttle
#define SHUTTLE_EVENT_HIT_SHUTTLE 1 << 1
+
+// Hijack stages
+
+#define HIJACK_NOT_BEGUN 0
+#define HIJACK_STAGE_1 1
+#define HIJACK_STAGE_2 2
+#define HIJACK_STAGE_3 3
+#define HIJACK_STAGE_4 4
+#define HIJACK_COMPLETED 5
diff --git a/code/__DEFINES/stat.dm b/code/__DEFINES/stat.dm
index b180c7b33494b..955e046edefc6 100644
--- a/code/__DEFINES/stat.dm
+++ b/code/__DEFINES/stat.dm
@@ -13,10 +13,15 @@
#define MAX_SATIETY 600
// bitflags for machine stat variable
+
+/// physically broken
#define BROKEN (1<<0)
+/// not powered
#define NOPOWER (1<<1)
-#define MAINT (1<<2) // under maintaince
-#define EMPED (1<<3) // temporary broken by EMP pulse
+/// under maintaince
+#define MAINT (1<<2)
+/// temporary broken by EMP pulse
+#define EMPED (1<<3)
//ai power requirement defines
#define POWER_REQ_ALL 1
diff --git a/code/__DEFINES/subsystems.dm b/code/__DEFINES/subsystems.dm
index c14acc5a4985a..f5f5067311143 100644
--- a/code/__DEFINES/subsystems.dm
+++ b/code/__DEFINES/subsystems.dm
@@ -167,6 +167,7 @@
#define INIT_ORDER_OUTPUTS 35
#define INIT_ORDER_RESTAURANT 34
#define INIT_ORDER_TTS 33
+#define INIT_ORDER_FLUIDS 32 // Needs to be above atoms, as some atoms may want to start fluids/gases on init
#define INIT_ORDER_ATOMS 30
#define INIT_ORDER_LANGUAGE 25
#define INIT_ORDER_MACHINES 20
diff --git a/code/__DEFINES/traits/declarations.dm b/code/__DEFINES/traits/declarations.dm
index 7a20d3ea4c91e..e868097b579c4 100644
--- a/code/__DEFINES/traits/declarations.dm
+++ b/code/__DEFINES/traits/declarations.dm
@@ -1159,8 +1159,6 @@ Remember to update _globalvars/traits.dm if you're adding/removing/renaming trai
#define TRAIT_MAGNETIC_ID_CARD "magnetic_id_card"
/// ID cards with this trait have special appraisal text.
#define TRAIT_TASTEFULLY_THICK_ID_CARD "impressive_very_nice"
-/// things with this trait are treated as having no access in /atom/movable/proc/check_access(obj/item)
-#define TRAIT_ALWAYS_NO_ACCESS "alwaysnoaccess"
///The entity has Silicon 'access', so is either a silicon, has an access wand, or is an admin ghost AI.
///This is put on the mob, it is used on the client for Admins but they are the exception as they use `isAdminGhostAI`.
@@ -1392,6 +1390,8 @@ Remember to update _globalvars/traits.dm if you're adding/removing/renaming trai
* (This may be changed later but I chose to do it this way to avoid messing up interactions which require combat mode)
*/
#define TRAIT_COMBAT_MODE_SKIP_INTERACTION "combat_mode_skip_interaction"
+// bars change of combat mode
+#define TRAIT_COMBAT_MODE_LOCK "combat_mode_lock"
///A "fake" effect that should not be subject to normal effect removal methods (like the effect remover component)
#define TRAIT_ILLUSORY_EFFECT "illusory_effect"
diff --git a/code/__DEFINES/traits/sources.dm b/code/__DEFINES/traits/sources.dm
index 4deb392514123..c4e952ed77b7a 100644
--- a/code/__DEFINES/traits/sources.dm
+++ b/code/__DEFINES/traits/sources.dm
@@ -312,3 +312,6 @@
/// From the aquarium component
#define AQUARIUM_TRAIT "aquarium"
+
+/// Trait aquired from being painted a certain color
+#define ATOM_COLOR_TRAIT "atom_color"
diff --git a/code/__DEFINES/wounds.dm b/code/__DEFINES/wounds.dm
index 5f824f42cce92..57415c0b50bac 100644
--- a/code/__DEFINES/wounds.dm
+++ b/code/__DEFINES/wounds.dm
@@ -313,8 +313,8 @@ GLOBAL_LIST_INIT(biotypes_to_scar_file, list(
// ~random wound balance defines
/// how quickly sanitization removes infestation and decays per second
#define WOUND_BURN_SANITIZATION_RATE 0.075
-/// how much blood you can lose per tick per slash max.
-#define WOUND_SLASH_MAX_BLOODFLOW 4.5
+/// how much blood you can lose per tick per wound max.
+#define WOUND_MAX_BLOODFLOW 4.5
/// further slash attacks on a bodypart with a slash wound have their blood_flow further increased by damage * this (10 damage slash adds .25 flow)
#define WOUND_SLASH_DAMAGE_FLOW_COEFF 0.025
/// if we suffer a bone wound to the head that creates brain traumas, the timer for the trauma cycle is +/- by this percent (0-100)
diff --git a/code/__DEFINES/~doppler_defines/techweb_nodes.dm b/code/__DEFINES/~doppler_defines/techweb_nodes.dm
index 1243df6ce241d..2a6be496862c9 100644
--- a/code/__DEFINES/~doppler_defines/techweb_nodes.dm
+++ b/code/__DEFINES/~doppler_defines/techweb_nodes.dm
@@ -6,3 +6,4 @@
#define TECHWEB_NODE_COLONY_FLATPACKS "colony_fabricator_flatpacks"
#define TECHWEB_NODE_COLONY_STRUCTURES "colony_fabricator_structures"
#define TECHWEB_NODE_COLONY_TOOLS "colony_fabricator_tools"
+#define TECHWEB_NODE_EMERGENCY_FAB "emergency_fab_designs"
diff --git a/code/__DEFINES/~doppler_defines/traits/declarations.dm b/code/__DEFINES/~doppler_defines/traits/declarations.dm
index 8ee2c0d8e7f75..ca28e9171419c 100644
--- a/code/__DEFINES/~doppler_defines/traits/declarations.dm
+++ b/code/__DEFINES/~doppler_defines/traits/declarations.dm
@@ -1,2 +1,5 @@
/// The trait that determines if someone has the system shock quirk.
#define TRAIT_SYSTEM_SHOCK "trait_system_shock"
+
+/// The trait given to people with bad vibes.
+#define TRAIT_BAD_VIBES "trait_bad_vibes"
diff --git a/code/__HELPERS/areas.dm b/code/__HELPERS/areas.dm
index 1d247c12e6ee7..8e818e0e7f468 100644
--- a/code/__HELPERS/areas.dm
+++ b/code/__HELPERS/areas.dm
@@ -136,7 +136,7 @@ GLOBAL_LIST_INIT(typecache_powerfailure_safe_areas, typecacheof(list(
return
newA = new area_choice
newA.setup(str)
- newA.has_gravity = oldA.has_gravity
+ newA.default_gravity = oldA.default_gravity
require_area_resort() //new area registered. resort the names
else
newA = area_choice
diff --git a/code/__HELPERS/colors.dm b/code/__HELPERS/colors.dm
index f5dd398d72fcd..b0b61f986420a 100644
--- a/code/__HELPERS/colors.dm
+++ b/code/__HELPERS/colors.dm
@@ -153,3 +153,116 @@
return "#[num2hex(c, 2)][num2hex(m, 2)][num2hex(x, 2)]"
#define RANDOM_COLOUR (rgb(rand(0,255),rand(0,255),rand(0,255)))
+
+/* Generates an HSL color transition matrix filter which nicely paints an object
+ * without making it a deep fried blob of color
+ * saturation_behavior determines how we handle color saturation:
+ * SATURATION_MULTIPLY - Multiply pixel's saturation by color's saturation. Paints accents while keeping dim areas dim.
+ * SATURATION_OVERRIDE- Affects original lightness/saturation to ensure that pale objects still get doused in color
+ */
+/proc/color_transition_filter(new_color, saturation_behavior = SATURATION_MULTIPLY)
+ if (islist(new_color))
+ new_color = rgb(new_color[1], new_color[2], new_color[3])
+ new_color = rgb2num(new_color, COLORSPACE_HSL)
+ var/hue = new_color[1] / 360
+ var/saturation = new_color[2] / 100
+ var/added_saturation = 0
+ var/deducted_light = 0
+ if (saturation_behavior == SATURATION_OVERRIDE)
+ added_saturation = saturation * 0.75
+ deducted_light = saturation * 0.5
+ saturation = min(saturation, 1 - added_saturation)
+
+ var/list/new_matrix = list(
+ 0, 0, 0, 0, // Ignore original hue
+ 0, saturation, 0, 0, // Multiply the saturation by ours
+ 0, 0, 1 - deducted_light, 0, // If we're highly saturated then remove a bit of lightness to keep some color in
+ 0, 0, 0, 1, // Preserve alpha
+ hue, added_saturation, 0, 0, // And apply our preferred hue and some saturation if we're oversaturated
+ )
+ return color_matrix_filter(new_matrix, FILTER_COLOR_HSL)
+
+/// Applies a color filter to a hex/RGB list color
+/proc/apply_matrix_to_color(color, list/matrix, colorspace = COLORSPACE_HSL)
+ if (islist(color))
+ color = rgb(color[1], color[2], color[3], color[4])
+ color = rgb2num(color, colorspace)
+ // Pad alpha if we're lacking it
+ if (length(color) < 4)
+ color += 255
+
+ // Do we have a constants row?
+ var/has_constants = FALSE
+ // Do we have an alpha row/parameters?
+ var/has_alpha = FALSE
+
+ switch (length(matrix))
+ if (9)
+ has_constants = FALSE
+ has_alpha = FALSE
+ if (12)
+ has_constants = TRUE
+ has_alpha = FALSE
+ if (16)
+ has_constants = FALSE
+ has_alpha = TRUE
+ if (20)
+ has_constants = TRUE
+ has_alpha = TRUE
+ else
+ CRASH("Matrix of invalid length [length(matrix)] was passed into apply_matrix_to_color!")
+
+ var/list/new_color = list(0, 0, 0, 0)
+ var/row_length = 3
+ if (has_alpha)
+ row_length = 4
+ else
+ new_color[4] = 255
+
+ for (var/row_index in 1 to length(matrix) / row_length)
+ for (var/row_elem in 1 to row_length)
+ var/elem = matrix[(row_index - 1) * row_length + row_elem]
+ if (!has_constants || row_index != (length(matrix) / row_length))
+ new_color[row_index] += color[row_elem] * elem
+ continue
+
+ // Constant values at the end of the list (if we have such)
+ if (colorspace != COLORSPACE_HSV && colorspace != COLORSPACE_HCY && colorspace != COLORSPACE_HSL)
+ new_color[row_elem] += elem * 255
+ continue
+
+ // HSV/HSL/HCY have non-255 maximums for their values
+ var/multiplier = 255
+ switch (row_elem)
+ // Hue goes from 0 to 360
+ if (1)
+ multiplier = 360
+ // Value, luminance, chroma, etc go from 0 to 100
+ if (2 to 3)
+ multiplier = 100
+ // Alpha still goes from 0 to 255
+ if (4)
+ multiplier = 255
+ new_color[row_elem] += elem * multiplier
+
+ var/rgbcolor = rgb(new_color[1], new_color[2], new_color[3], new_color[4], space = colorspace)
+ return rgbcolor
+
+/// Recursively applies a filter to a passed in static appearance, returns the modified appearance
+/proc/filter_appearance_recursive(mutable_appearance/filter, filter_to_apply)
+ var/mutable_appearance/modify = new(filter)
+ var/list/existing_filters = modify.filters.Copy()
+ modify.filters = list(filter_to_apply) + existing_filters
+
+ // Ideally this should be recursive to check for KEEP_APART elements that need this applied to it
+ // and RESET_COLOR flags but this is much simpler, and hopefully we don't have that point of layering here
+ if(modify.appearance_flags & KEEP_TOGETHER)
+ return modify
+
+ for(var/overlay_index in 1 to length(modify.overlays))
+ modify.overlays[overlay_index] = filter_appearance_recursive(modify.overlays[overlay_index], filter_to_apply)
+
+ for(var/underlay_index in 1 to length(modify.underlays))
+ modify.underlays[underlay_index] = filter_appearance_recursive(modify.underlays[underlay_index], filter_to_apply)
+
+ return modify
diff --git a/code/__HELPERS/filters.dm b/code/__HELPERS/filters.dm
index 930c889b09cab..f84c812e0be79 100644
--- a/code/__HELPERS/filters.dm
+++ b/code/__HELPERS/filters.dm
@@ -322,3 +322,8 @@ GLOBAL_LIST_INIT(master_filter_info, list(
continue
animate(filter, x = 0, y = 0, size = 0, offset = 0, time = remove_duration)
addtimer(CALLBACK(in_atom, TYPE_PROC_REF(/datum, remove_filter), "wibbly-[i]"), remove_duration)
+
+/proc/convert_list_to_filter(list/list_filter)
+ var/list/arguments = list_filter.Copy()
+ arguments -= "priority"
+ return filter(arglist(arguments))
diff --git a/code/__HELPERS/mobs.dm b/code/__HELPERS/mobs.dm
index f3a2e4bd375b9..f4491d336a038 100644
--- a/code/__HELPERS/mobs.dm
+++ b/code/__HELPERS/mobs.dm
@@ -208,7 +208,7 @@ GLOBAL_LIST_INIT(skin_tone_names, list(
var/atom/target_loc = target?.loc
var/drifting = FALSE
- if(GLOB.move_manager.processing_on(user, SSnewtonian_movement))
+ if(!isnull(user.drift_handler))
drifting = TRUE
var/holding = user.get_active_held_item()
@@ -237,7 +237,7 @@ GLOBAL_LIST_INIT(skin_tone_names, list(
if(!QDELETED(progbar))
progbar.update(world.time - starttime)
- if(drifting && !GLOB.move_manager.processing_on(user, SSnewtonian_movement))
+ if(drifting && isnull(user.drift_handler))
drifting = FALSE
user_loc = user.loc
diff --git a/code/__HELPERS/movement.dm b/code/__HELPERS/movement.dm
index e820b3dfff125..49b7fb35432b0 100644
--- a/code/__HELPERS/movement.dm
+++ b/code/__HELPERS/movement.dm
@@ -1,2 +1,5 @@
/// Converts w_class into newtons from throwing it, in (0.6 ~ 2.2) range
#define WEIGHT_TO_NEWTONS(w_class, arguments...) 0.2 NEWTONS + w_class * 0.4 NEWTONS
+
+/// Converts movement delay into drift force required to achieve that speed
+#define MOVE_DELAY_TO_DRIFT(move_delay) ((DEFAULT_INERTIA_SPEED / move_delay - 1) / INERTIA_SPEED_COEF + 1)
diff --git a/code/__HELPERS/shuttle.dm b/code/__HELPERS/shuttle.dm
new file mode 100644
index 0000000000000..4f866e22384dd
--- /dev/null
+++ b/code/__HELPERS/shuttle.dm
@@ -0,0 +1,52 @@
+/// Helper proc that tests to ensure all whiteship templates can spawn at their docking port, and logs their sizes
+/// This should be a unit test, but too much of our other code breaks during shuttle movement, so not yet, not yet.
+/proc/test_whiteship_sizes()
+ var/obj/docking_port/stationary/port_type = /obj/docking_port/stationary/picked/whiteship
+ var/datum/turf_reservation/docking_yard = SSmapping.request_turf_block_reservation(
+ initial(port_type.width),
+ initial(port_type.height),
+ 1,
+ )
+ var/turf/bottom_left = docking_yard.bottom_left_turfs[1]
+ var/turf/spawnpoint = locate(
+ bottom_left.x + initial(port_type.dwidth),
+ bottom_left.y + initial(port_type.dheight),
+ bottom_left.z,
+ )
+
+ var/obj/docking_port/stationary/picked/whiteship/port = new(spawnpoint)
+ var/list/ids = port.shuttlekeys
+ var/height = 0
+ var/width = 0
+ var/dheight = 0
+ var/dwidth = 0
+ var/delta_height = 0
+ var/delta_width = 0
+ for(var/id in ids)
+ var/datum/map_template/shuttle/our_template = SSmapping.shuttle_templates[id]
+ // We do a standard load here so any errors will properly runtimes
+ var/obj/docking_port/mobile/ship = SSshuttle.action_load(our_template, port)
+ if(ship)
+ ship.jumpToNullSpace()
+ ship = null
+ // Yes this is very hacky, but we need to both allow loading a template that's too big to be an error state
+ // And actually get the sizing information from every shuttle
+ SSshuttle.load_template(our_template)
+ var/obj/docking_port/mobile/theoretical_ship = SSshuttle.preview_shuttle
+ if(theoretical_ship)
+ height = max(theoretical_ship.height, height)
+ width = max(theoretical_ship.width, width)
+ dheight = max(theoretical_ship.dheight, dheight)
+ dwidth = max(theoretical_ship.dwidth, dwidth)
+ delta_height = max(theoretical_ship.height - theoretical_ship.dheight, delta_height)
+ delta_width = max(theoretical_ship.width - theoretical_ship.dwidth, delta_width)
+ theoretical_ship.jumpToNullSpace()
+ qdel(port, TRUE)
+ log_world("Whiteship sizing information. Use this to set the docking port, and the map size\n\
+ Max Height: [height] \n\
+ Max Width: [width] \n\
+ Max DHeight: [dheight] \n\
+ Max DWidth: [dwidth] \n\
+ The following are the safest bet for map sizing. Anything smaller then this could in the worst case not fit in the docking port\n\
+ Max Combined Width: [height + dheight] \n\
+ Max Combinded Height [width + dwidth]")
diff --git a/code/__HELPERS/type2type.dm b/code/__HELPERS/type2type.dm
index 03d308e34d635..aae96c3860145 100644
--- a/code/__HELPERS/type2type.dm
+++ b/code/__HELPERS/type2type.dm
@@ -99,6 +99,9 @@ GLOBAL_LIST_INIT(modulo_angle_to_dir, list(NORTH,NORTHEAST,EAST,SOUTHEAST,SOUTH,
else
return null
+///Returns a single dir rotated by x degrees clockwise, adhering to the cardinal directions.
+#define turn_cardinal(dir, rotation) ( angle2dir_cardinal ( dir2angle(dir) + rotation ) )
+
//Returns the angle in english
/proc/angle2text(degree)
return dir2text(angle2dir(degree))
diff --git a/code/_globalvars/lists/pipe_recipes.dm b/code/_globalvars/lists/pipe_recipes.dm
new file mode 100644
index 0000000000000..384248623bc1e
--- /dev/null
+++ b/code/_globalvars/lists/pipe_recipes.dm
@@ -0,0 +1,70 @@
+GLOBAL_LIST_INIT(atmos_pipe_recipes, list(
+ "Pipes" = list(
+ new /datum/pipe_info/pipe("Pipe", /obj/machinery/atmospherics/pipe/smart, TRUE),
+ new /datum/pipe_info/pipe("Layer Adapter", /obj/machinery/atmospherics/pipe/layer_manifold, TRUE),
+ new /datum/pipe_info/pipe("Color Adapter", /obj/machinery/atmospherics/pipe/color_adapter, TRUE),
+ new /datum/pipe_info/pipe("Bridge Pipe", /obj/machinery/atmospherics/pipe/bridge_pipe, TRUE),
+ new /datum/pipe_info/pipe("Multi-Deck Adapter", /obj/machinery/atmospherics/pipe/multiz, FALSE),
+ ),
+ "Binary" = list(
+ new /datum/pipe_info/pipe("Manual Valve", /obj/machinery/atmospherics/components/binary/valve, TRUE),
+ new /datum/pipe_info/pipe("Digital Valve", /obj/machinery/atmospherics/components/binary/valve/digital, TRUE),
+ new /datum/pipe_info/pipe("Gas Pump", /obj/machinery/atmospherics/components/binary/pump, TRUE),
+ new /datum/pipe_info/pipe("Volume Pump", /obj/machinery/atmospherics/components/binary/volume_pump, TRUE),
+ new /datum/pipe_info/pipe("Passive Gate", /obj/machinery/atmospherics/components/binary/passive_gate, TRUE),
+ new /datum/pipe_info/pipe("Pressure Valve", /obj/machinery/atmospherics/components/binary/pressure_valve, TRUE),
+ new /datum/pipe_info/pipe("Temperature Gate", /obj/machinery/atmospherics/components/binary/temperature_gate, TRUE),
+ new /datum/pipe_info/pipe("Temperature Pump", /obj/machinery/atmospherics/components/binary/temperature_pump, TRUE),
+ ),
+ "Devices" = list(
+ new /datum/pipe_info/pipe("Gas Filter", /obj/machinery/atmospherics/components/trinary/filter, TRUE),
+ new /datum/pipe_info/pipe("Gas Mixer", /obj/machinery/atmospherics/components/trinary/mixer, TRUE),
+ new /datum/pipe_info/pipe("Connector", /obj/machinery/atmospherics/components/unary/portables_connector, TRUE),
+ new /datum/pipe_info/pipe("Injector", /obj/machinery/atmospherics/components/unary/outlet_injector, TRUE),
+ new /datum/pipe_info/pipe("Scrubber", /obj/machinery/atmospherics/components/unary/vent_scrubber, TRUE),
+ new /datum/pipe_info/pipe("Unary Vent", /obj/machinery/atmospherics/components/unary/vent_pump, TRUE),
+ new /datum/pipe_info/pipe("Passive Vent", /obj/machinery/atmospherics/components/unary/passive_vent, TRUE),
+ new /datum/pipe_info/meter("Meter"),
+ ),
+ "Heat Exchange" = list(
+ new /datum/pipe_info/pipe("Pipe", /obj/machinery/atmospherics/pipe/heat_exchanging/simple, FALSE),
+ new /datum/pipe_info/pipe("Manifold", /obj/machinery/atmospherics/pipe/heat_exchanging/manifold, FALSE),
+ new /datum/pipe_info/pipe("4-Way Manifold", /obj/machinery/atmospherics/pipe/heat_exchanging/manifold4w, FALSE),
+ new /datum/pipe_info/pipe("Junction", /obj/machinery/atmospherics/pipe/heat_exchanging/junction, FALSE),
+ new /datum/pipe_info/pipe("Heat Exchanger", /obj/machinery/atmospherics/components/unary/heat_exchanger, FALSE),
+ )
+))
+
+GLOBAL_LIST_INIT(disposal_pipe_recipes, list(
+ "Disposal Pipes" = list(
+ new /datum/pipe_info/disposal("Pipe", /obj/structure/disposalpipe/segment, PIPE_BENDABLE),
+ new /datum/pipe_info/disposal("Junction", /obj/structure/disposalpipe/junction, PIPE_TRIN_M),
+ new /datum/pipe_info/disposal("Y-Junction", /obj/structure/disposalpipe/junction/yjunction),
+ new /datum/pipe_info/disposal("Sort Junction", /obj/structure/disposalpipe/sorting/mail, PIPE_TRIN_M),
+ new /datum/pipe_info/disposal("Rotator", /obj/structure/disposalpipe/rotator, PIPE_ONEDIR_FLIPPABLE),
+ new /datum/pipe_info/disposal("Trunk", /obj/structure/disposalpipe/trunk),
+ new /datum/pipe_info/disposal("Down Turn", /obj/structure/disposalpipe/trunk/multiz/down),
+ new /datum/pipe_info/disposal("Up Turn", /obj/structure/disposalpipe/trunk/multiz),
+ new /datum/pipe_info/disposal("Bin", /obj/machinery/disposal/bin, PIPE_ONEDIR),
+ new /datum/pipe_info/disposal("Outlet", /obj/structure/disposaloutlet),
+ new /datum/pipe_info/disposal("Chute", /obj/machinery/disposal/delivery_chute),
+ )
+))
+
+GLOBAL_LIST_INIT(transit_tube_recipes, list(
+ "Transit Tubes" = list(
+ new /datum/pipe_info/transit("Straight Tube", /obj/structure/c_transit_tube, PIPE_STRAIGHT),
+ new /datum/pipe_info/transit("Straight Tube with Crossing", /obj/structure/c_transit_tube/crossing, PIPE_STRAIGHT),
+ new /datum/pipe_info/transit("Curved Tube", /obj/structure/c_transit_tube/curved, PIPE_UNARY_FLIPPABLE),
+ new /datum/pipe_info/transit("Diagonal Tube", /obj/structure/c_transit_tube/diagonal, PIPE_STRAIGHT),
+ new /datum/pipe_info/transit("Diagonal Tube with Crossing", /obj/structure/c_transit_tube/diagonal/crossing, PIPE_STRAIGHT),
+ new /datum/pipe_info/transit("Junction", /obj/structure/c_transit_tube/junction, PIPE_UNARY_FLIPPABLE),
+ ),
+ "Station Equipment" = list(
+ new /datum/pipe_info/transit("Through Tube Station", /obj/structure/c_transit_tube/station, PIPE_STRAIGHT),
+ new /datum/pipe_info/transit("Terminus Tube Station", /obj/structure/c_transit_tube/station/reverse, PIPE_UNARY_FLIPPABLE),
+ new /datum/pipe_info/transit("Through Tube Dispenser Station", /obj/structure/c_transit_tube/station/dispenser, PIPE_STRAIGHT),
+ new /datum/pipe_info/transit("Terminus Tube Dispenser Station", /obj/structure/c_transit_tube/station/dispenser/reverse, PIPE_UNARY_FLIPPABLE),
+ new /datum/pipe_info/transit("Transit Tube Pod", /obj/structure/c_transit_tube_pod, PIPE_ONEDIR),
+ )
+))
diff --git a/code/_globalvars/pipe_info.dm b/code/_globalvars/pipe_info.dm
new file mode 100644
index 0000000000000..5e0f279d314bc
--- /dev/null
+++ b/code/_globalvars/pipe_info.dm
@@ -0,0 +1,106 @@
+///Pipe info
+/datum/pipe_info
+ ///Name of this pipe
+ var/name
+ ///Icon state of this pipe
+ var/icon_state
+ ///Type path of this recipe
+ var/id = -1
+ /// see code/__DEFINES/pipe_construction.dm
+ var/dirtype = PIPE_BENDABLE
+ /// Is this pipe layer indenpendent
+ var/all_layers
+
+/datum/pipe_info/pipe/New(label, obj/machinery/atmospherics/path, use_five_layers)
+ name = label
+ id = path
+ all_layers = use_five_layers
+ icon_state = initial(path.pipe_state)
+ var/obj/item/pipe/c = initial(path.construction_type)
+ dirtype = initial(c.RPD_type)
+
+/**
+ * Get preview image of an pipe
+ * Arguments
+ *
+ * * selected_dir - the direction of the pipe to get preview of
+ * * selected - is this pipe meant to be highlighted in the UI
+ */
+/datum/pipe_info/proc/get_preview(selected_dir, selected = FALSE)
+ SHOULD_BE_PURE(TRUE)
+
+ var/list/dirs
+ switch(dirtype)
+ if(PIPE_STRAIGHT, PIPE_BENDABLE)
+ dirs = list("[NORTH]" = "Vertical", "[EAST]" = "Horizontal")
+ if(dirtype == PIPE_BENDABLE)
+ dirs += list("[NORTHWEST]" = "West to North", "[NORTHEAST]" = "North to East",
+ "[SOUTHWEST]" = "South to West", "[SOUTHEAST]" = "East to South")
+ if(PIPE_TRINARY)
+ dirs = list("[NORTH]" = "West South East", "[SOUTH]" = "East North West",
+ "[EAST]" = "North West South", "[WEST]" = "South East North")
+ if(PIPE_TRIN_M)
+ dirs = list("[NORTH]" = "North East South", "[SOUTHWEST]" = "North West South",
+ "[NORTHEAST]" = "South East North", "[SOUTH]" = "South West North",
+ "[WEST]" = "West North East", "[SOUTHEAST]" = "West South East",
+ "[NORTHWEST]" = "East North West", "[EAST]" = "East South West",)
+ if(PIPE_UNARY)
+ dirs = list("[NORTH]" = "North", "[SOUTH]" = "South", "[WEST]" = "West", "[EAST]" = "East")
+ if(PIPE_ONEDIR)
+ dirs = list("[SOUTH]" = name)
+ if(PIPE_UNARY_FLIPPABLE)
+ dirs = list("[NORTH]" = "North", "[EAST]" = "East", "[SOUTH]" = "South", "[WEST]" = "West",
+ "[NORTHEAST]" = "North Flipped", "[SOUTHEAST]" = "East Flipped", "[SOUTHWEST]" = "South Flipped", "[NORTHWEST]" = "West Flipped")
+ if(PIPE_ONEDIR_FLIPPABLE)
+ dirs = list("[SOUTH]" = name, "[SOUTHEAST]" = "[name] Flipped")
+
+ var/list/rows = list()
+ for(var/dir in dirs)
+ var/numdir = text2num(dir)
+ var/flipped = ((dirtype == PIPE_TRIN_M) || (dirtype == PIPE_UNARY_FLIPPABLE) || (dirtype == PIPE_ONEDIR_FLIPPABLE)) && (ISDIAGONALDIR(numdir))
+ var/is_variant_selected = selected && (!selected_dir ? FALSE : (dirtype == PIPE_ONEDIR ? TRUE : (numdir == selected_dir)))
+ rows += list(list(
+ "selected" = is_variant_selected,
+ "dir" = dir2text(numdir),
+ "dir_name" = dirs[dir],
+ "icon_state" = icon_state,
+ "flipped" = flipped,
+ ))
+
+ return rows
+
+//==============================================================================================
+
+///Meter pipe info
+/datum/pipe_info/meter
+ icon_state = "meter"
+ dirtype = PIPE_ONEDIR
+ all_layers = TRUE
+
+/datum/pipe_info/meter/New(label)
+ name = label
+
+//==============================================================================================
+
+///Disposal pipe info
+/datum/pipe_info/disposal/New(label, obj/path, dt=PIPE_UNARY)
+ name = label
+ id = path
+
+ icon_state = initial(path.icon_state)
+ if(ispath(path, /obj/structure/disposalpipe))
+ icon_state = "con[icon_state]"
+
+ dirtype = dt
+
+
+//==============================================================================================
+
+///Transient tube pipe info
+/datum/pipe_info/transit/New(label, obj/path, dt=PIPE_UNARY)
+ name = label
+ id = path
+ dirtype = dt
+ icon_state = initial(path.icon_state)
+ if(dt == PIPE_UNARY_FLIPPABLE)
+ icon_state = "[icon_state]_preview"
diff --git a/code/_globalvars/traits/_traits.dm b/code/_globalvars/traits/_traits.dm
index 4d1d0f702c4b4..952d44a8e1767 100644
--- a/code/_globalvars/traits/_traits.dm
+++ b/code/_globalvars/traits/_traits.dm
@@ -152,7 +152,6 @@ GLOBAL_LIST_INIT(traits_by_type, list(
"TRAIT_ALCOHOL_TOLERANCE" = TRAIT_ALCOHOL_TOLERANCE,
"TRAIT_ALLOWED_HONORBOUND_ATTACK" = TRAIT_ALLOWED_HONORBOUND_ATTACK,
"TRAIT_ALLOW_HERETIC_CASTING" = TRAIT_ALLOW_HERETIC_CASTING,
- "TRAIT_ALWAYS_NO_ACCESS" = TRAIT_ALWAYS_NO_ACCESS,
"TRAIT_ALWAYS_WANTED" = TRAIT_ALWAYS_WANTED,
"TRAIT_ANALGESIA" = TRAIT_ANALGESIA,
"TRAIT_ANGELIC" = TRAIT_ANGELIC,
@@ -206,6 +205,7 @@ GLOBAL_LIST_INIT(traits_by_type, list(
"TRAIT_CLUMSY" = TRAIT_CLUMSY,
"TRAIT_COAGULATING" = TRAIT_COAGULATING,
"TRAIT_CORPSELOCKED" = TRAIT_CORPSELOCKED,
+ "TRAIT_COMBAT_MODE_LOCK" = TRAIT_COMBAT_MODE_LOCK,
"TRAIT_CRITICAL_CONDITION" = TRAIT_CRITICAL_CONDITION,
"TRAIT_CULT_HALO" = TRAIT_CULT_HALO,
"TRAIT_CURSED" = TRAIT_CURSED,
@@ -586,6 +586,7 @@ GLOBAL_LIST_INIT(traits_by_type, list(
"TRAIT_IGNORE_FIRE_PROTECTION" = TRAIT_IGNORE_FIRE_PROTECTION,
"TRAIT_LEFT_EYE_SCAR" = TRAIT_LEFT_EYE_SCAR,
"TRAIT_RIGHT_EYE_SCAR" = TRAIT_RIGHT_EYE_SCAR,
+ "TRAIT_BAD_VIBES" = TRAIT_BAD_VIBES, // DOPPLER EDIT ADDITION
),
/obj/item = list(
"TRAIT_APC_SHOCKING" = TRAIT_APC_SHOCKING,
diff --git a/code/_globalvars/traits/admin_tooling.dm b/code/_globalvars/traits/admin_tooling.dm
index 814c5e8376a1a..1b62e3c5a1892 100644
--- a/code/_globalvars/traits/admin_tooling.dm
+++ b/code/_globalvars/traits/admin_tooling.dm
@@ -38,7 +38,6 @@ GLOBAL_LIST_INIT(admin_visible_traits, list(
"TRAIT_AGEUSIA" = TRAIT_AGEUSIA,
"TRAIT_ALCOHOL_TOLERANCE" = TRAIT_ALCOHOL_TOLERANCE,
"TRAIT_ALLOW_HERETIC_CASTING" = TRAIT_ALLOW_HERETIC_CASTING,
- "TRAIT_ALWAYS_NO_ACCESS" = TRAIT_ALWAYS_NO_ACCESS,
"TRAIT_ALWAYS_WANTED" = TRAIT_ALWAYS_WANTED,
"TRAIT_ANOSMIA" = TRAIT_ANOSMIA,
"TRAIT_ANTENNAE" = TRAIT_ANTENNAE,
@@ -334,6 +333,7 @@ GLOBAL_LIST_INIT(admin_visible_traits, list(
"TRAIT_IGNORE_FIRE_PROTECTION" = TRAIT_IGNORE_FIRE_PROTECTION,
"TRAIT_LEFT_EYE_SCAR" = TRAIT_LEFT_EYE_SCAR,
"TRAIT_RIGHT_EYE_SCAR" = TRAIT_RIGHT_EYE_SCAR,
+ "TRAIT_BAD_VIBES" = TRAIT_BAD_VIBES, // DOPPLER EDIT ADDITION
),
/obj/item = list(
"TRAIT_APC_SHOCKING" = TRAIT_APC_SHOCKING,
diff --git a/code/_onclick/hud/action_button.dm b/code/_onclick/hud/action_button.dm
index 71d8a81b397c3..562266f51c031 100644
--- a/code/_onclick/hud/action_button.dm
+++ b/code/_onclick/hud/action_button.dm
@@ -3,6 +3,7 @@
var/datum/hud/our_hud
var/actiontooltipstyle = ""
screen_loc = null
+ mouse_over_pointer = MOUSE_HAND_POINTER
/// The icon state of our active overlay, used to prevent re-applying identical overlays
var/active_overlay_icon_state
@@ -269,6 +270,7 @@
icon = 'icons/hud/64x16_actions.dmi'
icon_state = "screen_gen_palette"
screen_loc = ui_action_palette
+ mouse_over_pointer = MOUSE_HAND_POINTER
var/datum/hud/our_hud
var/expanded = FALSE
/// Id of any currently running timers that set our color matrix
@@ -394,6 +396,7 @@ GLOBAL_LIST_INIT(palette_removed_matrix, list(1.4,0,0,0, 0.7,0.4,0,0, 0.4,0,0.6,
/atom/movable/screen/palette_scroll
icon = 'icons/hud/screen_gen.dmi'
screen_loc = ui_palette_scroll
+ mouse_over_pointer = MOUSE_HAND_POINTER
/// How should we move the palette's actions?
/// Positive scrolls down the list, negative scrolls back
var/scroll_direction = 0
diff --git a/code/_onclick/hud/ai.dm b/code/_onclick/hud/ai.dm
index aaad7457f6d3c..0a4cf277712b0 100644
--- a/code/_onclick/hud/ai.dm
+++ b/code/_onclick/hud/ai.dm
@@ -1,5 +1,6 @@
/atom/movable/screen/ai
icon = 'icons/hud/screen_ai.dmi'
+ mouse_over_pointer = MOUSE_HAND_POINTER
/atom/movable/screen/ai/Click()
if(isobserver(usr) || usr.incapacitated)
diff --git a/code/_onclick/hud/alert.dm b/code/_onclick/hud/alert.dm
index 73916264a8b3c..7cab2074c5485 100644
--- a/code/_onclick/hud/alert.dm
+++ b/code/_onclick/hud/alert.dm
@@ -122,6 +122,7 @@
. = ..()
if(clickable_glow)
add_filter("clickglow", 2, outline_filter(color = COLOR_GOLD, size = 1))
+ mouse_over_pointer = MOUSE_HAND_POINTER
/atom/movable/screen/alert/MouseEntered(location,control,params)
. = ..()
diff --git a/code/_onclick/hud/blob_overmind.dm b/code/_onclick/hud/blob_overmind.dm
index b8e9800fdf9b7..7a876cf7911c6 100644
--- a/code/_onclick/hud/blob_overmind.dm
+++ b/code/_onclick/hud/blob_overmind.dm
@@ -1,6 +1,7 @@
/atom/movable/screen/blob
icon = 'icons/hud/blob.dmi'
+ mouse_over_pointer = MOUSE_HAND_POINTER
/atom/movable/screen/blob/MouseEntered(location,control,params)
. = ..()
diff --git a/code/_onclick/hud/ghost.dm b/code/_onclick/hud/ghost.dm
index 9f90076a3ac71..3ee08df77cb87 100644
--- a/code/_onclick/hud/ghost.dm
+++ b/code/_onclick/hud/ghost.dm
@@ -1,5 +1,6 @@
/atom/movable/screen/ghost
icon = 'icons/hud/screen_ghost.dmi'
+ mouse_over_pointer = MOUSE_HAND_POINTER
/atom/movable/screen/ghost/MouseEntered(location, control, params)
. = ..()
@@ -118,4 +119,3 @@
if (istype(O) && O.observetarget)
return
. = ..()
-
diff --git a/code/_onclick/hud/guardian.dm b/code/_onclick/hud/guardian.dm
index 32a3f233c8591..68a480d47f8f0 100644
--- a/code/_onclick/hud/guardian.dm
+++ b/code/_onclick/hud/guardian.dm
@@ -101,6 +101,7 @@
/atom/movable/screen/guardian
icon = 'icons/hud/guardian.dmi'
+ mouse_over_pointer = MOUSE_HAND_POINTER
/atom/movable/screen/guardian/manifest
icon_state = "manifest"
diff --git a/code/_onclick/hud/human.dm b/code/_onclick/hud/human.dm
index 922241321ab91..50954584a0df2 100644
--- a/code/_onclick/hud/human.dm
+++ b/code/_onclick/hud/human.dm
@@ -4,6 +4,7 @@
/atom/movable/screen/human/toggle
name = "toggle"
icon_state = "toggle"
+ mouse_over_pointer = MOUSE_HAND_POINTER
/atom/movable/screen/human/toggle/Click()
@@ -26,6 +27,7 @@
/atom/movable/screen/human/equip
name = "equip"
icon_state = "act_equip"
+ mouse_over_pointer = MOUSE_HAND_POINTER
/atom/movable/screen/human/equip/Click()
if(ismecha(usr.loc)) // stops inventory actions in a mech
@@ -45,6 +47,7 @@
name = "current sting"
screen_loc = ui_lingstingdisplay
invisibility = INVISIBILITY_ABSTRACT
+ mouse_over_pointer = MOUSE_HAND_POINTER
/atom/movable/screen/ling/sting/Click()
if(isobserver(usr))
diff --git a/code/_onclick/hud/new_player.dm b/code/_onclick/hud/new_player.dm
index b4092877a770a..01f79aa147684 100644
--- a/code/_onclick/hud/new_player.dm
+++ b/code/_onclick/hud/new_player.dm
@@ -118,8 +118,9 @@
screen_loc = "TOP,CENTER:-61"
/atom/movable/screen/lobby/button
+ mouse_over_pointer = MOUSE_HAND_POINTER
///Is the button currently enabled?
- var/enabled = TRUE
+ VAR_PROTECTED/enabled = TRUE
///Is the button currently being hovered over with the mouse?
var/highlighted = FALSE
///Should this button play the select sound?
@@ -182,6 +183,7 @@
return FALSE
enabled = status
update_appearance(UPDATE_ICON)
+ mouse_over_pointer = enabled ? MOUSE_HAND_POINTER : MOUSE_INACTIVE_POINTER
return TRUE
///Prefs menu
@@ -257,10 +259,10 @@
icon = 'icons/hud/lobby/join.dmi'
icon_state = "" //Default to not visible
base_icon_state = "join_game"
- enabled = FALSE
/atom/movable/screen/lobby/button/join/Initialize(mapload, datum/hud/hud_owner)
. = ..()
+ set_button_status(FALSE)
switch(SSticker.current_state)
if(GAME_STATE_PREGAME, GAME_STATE_STARTUP)
RegisterSignal(SSticker, COMSIG_TICKER_ENTER_SETTING_UP, PROC_REF(show_join_button))
@@ -328,13 +330,13 @@
icon = 'icons/hud/lobby/observe.dmi'
icon_state = "observe_disabled"
base_icon_state = "observe"
- enabled = FALSE
/atom/movable/screen/lobby/button/observe/Initialize(mapload, datum/hud/hud_owner)
. = ..()
if(SSticker.current_state > GAME_STATE_STARTUP)
set_button_status(TRUE)
else
+ set_button_status(FALSE)
RegisterSignal(SSticker, COMSIG_TICKER_ENTER_PREGAME, PROC_REF(enable_observing))
/atom/movable/screen/lobby/button/observe/Click(location, control, params)
diff --git a/code/_onclick/hud/robot.dm b/code/_onclick/hud/robot.dm
index 3ca14979ebccf..f7c0551236c6c 100644
--- a/code/_onclick/hud/robot.dm
+++ b/code/_onclick/hud/robot.dm
@@ -1,5 +1,6 @@
/atom/movable/screen/robot
icon = 'icons/hud/screen_cyborg.dmi'
+ mouse_over_pointer = MOUSE_HAND_POINTER
/atom/movable/screen/robot/module
name = "cyborg module"
diff --git a/code/_onclick/hud/screen_objects.dm b/code/_onclick/hud/screen_objects.dm
index cbe26c0046be3..b72924fdbe573 100644
--- a/code/_onclick/hud/screen_objects.dm
+++ b/code/_onclick/hud/screen_objects.dm
@@ -103,6 +103,7 @@
/atom/movable/screen/swap_hand
plane = HUD_PLANE
name = "swap hand"
+ mouse_over_pointer = MOUSE_HAND_POINTER
/atom/movable/screen/swap_hand/Click()
// At this point in client Click() code we have passed the 1/10 sec check and little else
@@ -123,6 +124,7 @@
icon = 'icons/hud/screen_midnight.dmi'
icon_state = "navigate"
screen_loc = ui_navigate_menu
+ mouse_over_pointer = MOUSE_HAND_POINTER
/atom/movable/screen/navigate/Click()
if(!isliving(usr))
@@ -141,6 +143,7 @@
icon = 'icons/hud/screen_midnight.dmi'
icon_state = "area_edit"
screen_loc = ui_building
+ mouse_over_pointer = MOUSE_HAND_POINTER
/atom/movable/screen/area_creator/Click()
if(usr.incapacitated || (isobserver(usr) && !isAdminGhostAI(usr)))
@@ -156,6 +159,7 @@
icon = 'icons/hud/screen_midnight.dmi'
icon_state = "talk_wheel"
screen_loc = ui_language_menu
+ mouse_over_pointer = MOUSE_HAND_POINTER
/atom/movable/screen/language_menu/Click()
usr.get_language_holder().open_language_menu(usr)
@@ -285,6 +289,7 @@
plane = ABOVE_HUD_PLANE
icon = 'icons/hud/screen_midnight.dmi'
icon_state = "storage_close"
+ mouse_over_pointer = MOUSE_HAND_POINTER
/atom/movable/screen/close/Initialize(mapload, datum/hud/hud_owner, new_master)
. = ..()
@@ -302,6 +307,7 @@
icon = 'icons/hud/screen_midnight.dmi'
icon_state = "act_drop"
plane = HUD_PLANE
+ mouse_over_pointer = MOUSE_HAND_POINTER
/atom/movable/screen/drop/Click()
if(usr.stat == CONSCIOUS)
@@ -312,6 +318,7 @@
icon = 'icons/hud/screen_midnight.dmi'
icon_state = "combat_off"
screen_loc = ui_combat_toggle
+ mouse_over_pointer = MOUSE_HAND_POINTER
/atom/movable/screen/combattoggle/Initialize(mapload, datum/hud/hud_owner)
. = ..()
@@ -358,6 +365,7 @@
icon = 'icons/hud/screen_midnight.dmi'
icon_state = "floor_change"
screen_loc = ui_floor_changer
+ mouse_over_pointer = MOUSE_HAND_POINTER
var/vertical = FALSE
/atom/movable/screen/floor_changer/Click(location,control,params)
@@ -390,6 +398,7 @@
name = "run/walk toggle"
icon = 'icons/hud/screen_midnight.dmi'
icon_state = "running"
+ mouse_over_pointer = MOUSE_HAND_POINTER
/atom/movable/screen/mov_intent/Click()
toggle(usr)
@@ -415,6 +424,7 @@
icon = 'icons/hud/screen_midnight.dmi'
icon_state = "pull"
base_icon_state = "pull"
+ mouse_over_pointer = MOUSE_HAND_POINTER
/atom/movable/screen/pull/Click()
if(isobserver(usr))
@@ -430,6 +440,7 @@
icon = 'icons/hud/screen_midnight.dmi'
icon_state = "act_resist"
plane = HUD_PLANE
+ mouse_over_pointer = MOUSE_HAND_POINTER
/atom/movable/screen/resist/Click()
if(isliving(usr))
@@ -442,6 +453,7 @@
icon_state = "act_rest"
base_icon_state = "act_rest"
plane = HUD_PLANE
+ mouse_over_pointer = MOUSE_HAND_POINTER
/atom/movable/screen/rest/Click()
if(isliving(usr))
@@ -526,6 +538,7 @@
name = "throw/catch"
icon = 'icons/hud/screen_midnight.dmi'
icon_state = "act_throw_off"
+ mouse_over_pointer = MOUSE_HAND_POINTER
/atom/movable/screen/throw_catch/Click()
if(iscarbon(usr))
@@ -536,6 +549,7 @@
name = "damage zone"
icon_state = "zone_sel"
screen_loc = ui_zonesel
+ mouse_over_pointer = MOUSE_HAND_POINTER
var/overlay_icon = 'icons/hud/screen_gen.dmi'
var/static/list/hover_overlays_cache = list()
var/hovering
@@ -713,6 +727,7 @@
/atom/movable/screen/healthdoll
name = "health doll"
screen_loc = ui_healthdoll
+ mouse_over_pointer = MOUSE_HAND_POINTER
/atom/movable/screen/healthdoll/Click()
if (iscarbon(usr))
@@ -810,6 +825,7 @@
name = "mood"
icon_state = "mood5"
screen_loc = ui_mood
+ mouse_over_pointer = MOUSE_HAND_POINTER
/atom/movable/screen/splash
icon = 'icons/blanks/blank_title.png'
@@ -859,6 +875,7 @@ INITIALIZE_IMMEDIATE(/atom/movable/screen/splash)
/atom/movable/screen/component_button
+ mouse_over_pointer = MOUSE_HAND_POINTER
var/atom/movable/screen/parent
/atom/movable/screen/component_button/Initialize(mapload, atom/movable/screen/parent)
diff --git a/code/controllers/subsystem/fluids.dm b/code/controllers/subsystem/fluids.dm
index 6b68ae717222a..2b4ce47295653 100644
--- a/code/controllers/subsystem/fluids.dm
+++ b/code/controllers/subsystem/fluids.dm
@@ -22,6 +22,7 @@ SUBSYSTEM_DEF(fluids)
wait = 0 // Will be autoset to whatever makes the most sense given the spread and effect waits.
flags = SS_KEEP_TIMING
runlevels = RUNLEVEL_GAME|RUNLEVEL_POSTGAME
+ init_order = INIT_ORDER_FLUIDS
priority = FIRE_PRIORITY_FLUIDS
// Fluid spread processing:
diff --git a/code/controllers/subsystem/map_vote.dm b/code/controllers/subsystem/map_vote.dm
index 44aa82172f3f0..ced1e65e3a215 100644
--- a/code/controllers/subsystem/map_vote.dm
+++ b/code/controllers/subsystem/map_vote.dm
@@ -53,7 +53,7 @@ SUBSYSTEM_DEF(map_vote)
last_message_at = world.time
var/list/messages = args.Copy()
- to_chat(world, span_purple(examine_block("Map Vote\n
\n[messages.Join("\n")]")))
+ to_chat(world, span_purple(examine_block("Map Vote\n
[messages.Join("\n")]")))
/datum/controller/subsystem/map_vote/proc/finalize_map_vote(datum/vote/map_vote/map_vote)
if(already_voted)
@@ -74,14 +74,9 @@ SUBSYSTEM_DEF(map_vote)
send_map_vote_notice("Admin Override is in effect. Map will not be changed.", "Tallies are recorded and saved.")
return
- var/list/valid_maps = filter_cache_to_valid_maps()
- if(!length(valid_maps))
- send_map_vote_notice("No valid maps.")
- return
-
var/winner
var/winner_amount = 0
- for(var/map in valid_maps)
+ for(var/map in map_vote.choices)
if(!winner_amount)
winner = map
winner_amount = map_vote_cache[map]
@@ -98,7 +93,7 @@ SUBSYSTEM_DEF(map_vote)
messages += tally_printout
// do not reset tallies if only one map is even possible
- if(length(valid_maps) > 1)
+ if(length(map_vote.choices) > 1)
map_vote_cache[winner] = CONFIG_GET(number/map_vote_minimum_tallies)
write_cache()
update_tally_printout()
@@ -175,4 +170,4 @@ SUBSYSTEM_DEF(map_vote)
for(var/map_id in map_vote_cache)
var/datum/map_config/map = config.maplist[map_id]
data += "[map.map_name] - [map_vote_cache[map_id]]"
- tally_printout = examine_block("Current Tallies\n
\n[data.Join("\n")]")
+ tally_printout = examine_block("Current Tallies\n
[data.Join("\n")]")
diff --git a/code/controllers/subsystem/mapping.dm b/code/controllers/subsystem/mapping.dm
index 2eee9cbb0c371..328d05636d4bb 100644
--- a/code/controllers/subsystem/mapping.dm
+++ b/code/controllers/subsystem/mapping.dm
@@ -252,23 +252,23 @@ SUBSYSTEM_DEF(mapping)
// Generate mining ruins
var/list/lava_ruins = levels_by_trait(ZTRAIT_LAVA_RUINS)
if (lava_ruins.len)
- seedRuins(lava_ruins, CONFIG_GET(number/lavaland_budget), list(/area/lavaland/surface/outdoors/unexplored), themed_ruins[ZTRAIT_LAVA_RUINS], clear_below = TRUE, mineral_budget = 15, mineral_budget_update = OREGEN_PRESET_LAVALAND)
+ seedRuins(lava_ruins, CONFIG_GET(number/lavaland_budget), list(/area/lavaland/surface/outdoors/unexplored), themed_ruins[ZTRAIT_LAVA_RUINS], clear_below = TRUE, mineral_budget = 15, mineral_budget_update = OREGEN_PRESET_LAVALAND, ruins_type = ZTRAIT_LAVA_RUINS)
var/list/ice_ruins = levels_by_trait(ZTRAIT_ICE_RUINS)
if (ice_ruins.len)
// needs to be whitelisted for underground too so place_below ruins work
- seedRuins(ice_ruins, CONFIG_GET(number/icemoon_budget), list(/area/icemoon/surface/outdoors/unexplored, /area/icemoon/underground/unexplored), themed_ruins[ZTRAIT_ICE_RUINS], clear_below = TRUE, mineral_budget = 4, mineral_budget_update = OREGEN_PRESET_TRIPLE_Z)
+ seedRuins(ice_ruins, CONFIG_GET(number/icemoon_budget), list(/area/icemoon/surface/outdoors/unexplored, /area/icemoon/underground/unexplored), themed_ruins[ZTRAIT_ICE_RUINS], clear_below = TRUE, mineral_budget = 4, mineral_budget_update = OREGEN_PRESET_TRIPLE_Z, ruins_type = ZTRAIT_ICE_RUINS)
var/list/ice_ruins_underground = levels_by_trait(ZTRAIT_ICE_RUINS_UNDERGROUND)
if (ice_ruins_underground.len)
- seedRuins(ice_ruins_underground, CONFIG_GET(number/icemoon_budget), list(/area/icemoon/underground/unexplored), themed_ruins[ZTRAIT_ICE_RUINS_UNDERGROUND], clear_below = TRUE, mineral_budget = 21)
+ seedRuins(ice_ruins_underground, CONFIG_GET(number/icemoon_budget), list(/area/icemoon/underground/unexplored), themed_ruins[ZTRAIT_ICE_RUINS_UNDERGROUND], clear_below = TRUE, mineral_budget = 21, ruins_type = ZTRAIT_ICE_RUINS_UNDERGROUND)
// Generate deep space ruins
var/list/space_ruins = levels_by_trait(ZTRAIT_SPACE_RUINS)
if (space_ruins.len)
// Create a proportional budget by multiplying the amount of space ruin levels in the current map over the default amount
var/proportional_budget = round(CONFIG_GET(number/space_budget) * (space_ruins.len / DEFAULT_SPACE_RUIN_LEVELS))
- seedRuins(space_ruins, proportional_budget, list(/area/space), themed_ruins[ZTRAIT_SPACE_RUINS], mineral_budget = 0)
+ seedRuins(space_ruins, proportional_budget, list(/area/space), themed_ruins[ZTRAIT_SPACE_RUINS], mineral_budget = 0, ruins_type = ZTRAIT_SPACE_RUINS)
/// Sets up rivers, and things that behave like rivers. So lava/plasma rivers, and chasms
/// It is important that this happens AFTER generating mineral walls and such, since we rely on them for river logic
diff --git a/code/controllers/subsystem/movement/newtonian_movement.dm b/code/controllers/subsystem/movement/newtonian_movement.dm
index aeb03a576dae0..e4143669678b2 100644
--- a/code/controllers/subsystem/movement/newtonian_movement.dm
+++ b/code/controllers/subsystem/movement/newtonian_movement.dm
@@ -13,9 +13,10 @@ MOVEMENT_SUBSYSTEM_DEF(newtonian_movement)
return ..()
/datum/controller/subsystem/movement/newtonian_movement/fire(resumed = FALSE)
- . = ..()
- if (!resumed)
+ if(!resumed)
+ canonical_time = world.time
currentrun = processing.Copy()
+
//cache for sanic speed (lists are references anyways)
var/list/current_run = currentrun
@@ -29,3 +30,21 @@ MOVEMENT_SUBSYSTEM_DEF(newtonian_movement)
STOP_PROCESSING(src, thing)
if (MC_TICK_CHECK)
return
+
+ for(var/list/bucket_info as anything in sorted_buckets)
+ var/time = bucket_info[MOVEMENT_BUCKET_TIME]
+ if(time > canonical_time || MC_TICK_CHECK)
+ return
+ pour_bucket(bucket_info)
+
+/datum/controller/subsystem/movement/newtonian_movement/proc/fire_moveloop(datum/move_loop/loop)
+ // Loop isn't even running right now
+ if(!(loop.status & MOVELOOP_STATUS_QUEUED))
+ return
+ // Drop the loop, process it, and if its still valid - queue it again
+ dequeue_loop(loop)
+ loop.process()
+ if(QDELETED(loop))
+ return
+ loop.timer = world.time + loop.delay
+ queue_loop(loop)
diff --git a/code/controllers/subsystem/processing/quirks.dm b/code/controllers/subsystem/processing/quirks.dm
index 0199b7498cc5b..7e550e43e3cac 100644
--- a/code/controllers/subsystem/processing/quirks.dm
+++ b/code/controllers/subsystem/processing/quirks.dm
@@ -26,7 +26,8 @@ GLOBAL_LIST_INIT_TYPED(quirk_blacklist, /list/datum/quirk, list(
list(/datum/quirk/photophobia, /datum/quirk/nyctophobia),
list(/datum/quirk/item_quirk/settler, /datum/quirk/freerunning),
list(/datum/quirk/numb, /datum/quirk/selfaware),
- list(/datum/quirk/empath, /datum/quirk/evil),
+ list(/datum/quirk/empath, /datum/quirk/bad_vibes), //DOPPLER EDIT ADDITION - Bad Vibes & Unholy Aura
+ list(/datum/quirk/item_quirk/spiritual, /datum/quirk/unholy), //DOPPLER EDIT ADDITION END
))
GLOBAL_LIST_INIT(quirk_string_blacklist, generate_quirk_string_blacklist())
diff --git a/code/datums/actions/mobs/projectileattack.dm b/code/datums/actions/mobs/projectileattack.dm
index f833e05dde2e5..8ac67bdecc493 100644
--- a/code/datums/actions/mobs/projectileattack.dm
+++ b/code/datums/actions/mobs/projectileattack.dm
@@ -399,7 +399,7 @@
/datum/action/cooldown/mob_cooldown/projectile_attack/wave/attack_sequence(mob/living/firer, atom/target)
wendigo_scream(firer)
- var/shots_per = 7
+ var/shots_per = 6
var/difference = 360 / shots_per
var/wave_direction = pick(-1, 1)
switch(wave_direction)
@@ -407,9 +407,9 @@
projectile_type = /obj/projectile/colossus/wendigo_shockwave/wave/alternate
if(1)
projectile_type = /obj/projectile/colossus/wendigo_shockwave/wave
- for(var/shoot_times in 1 to 32)
+ for(var/shoot_times in 1 to 12)
for(var/shot in 1 to shots_per)
var/angle = shot * difference + shoot_times * 5 * wave_direction * -1
shoot_projectile(firer, target, angle, firer, null, null)
- SLEEP_CHECK_DEATH(2, firer)
+ SLEEP_CHECK_DEATH(0.6 SECONDS, firer)
SLEEP_CHECK_DEATH(3 SECONDS, firer)
diff --git a/code/datums/bodypart_overlays/texture_bodypart_overlay.dm b/code/datums/bodypart_overlays/texture_bodypart_overlay.dm
index 623a61b8912f0..6a50f431c0610 100644
--- a/code/datums/bodypart_overlays/texture_bodypart_overlay.dm
+++ b/code/datums/bodypart_overlays/texture_bodypart_overlay.dm
@@ -26,3 +26,7 @@
/datum/bodypart_overlay/texture/carpskin
texture_icon_state = "carpskin"
texture_icon = 'icons/mob/human/textures.dmi'
+
+/datum/bodypart_overlay/texture/checkered
+ texture_icon_state = "checkered"
+ texture_icon = 'icons/mob/human/textures.dmi'
diff --git a/code/datums/brain_damage/hypnosis.dm b/code/datums/brain_damage/hypnosis.dm
index 5630073c95551..a9400578025b4 100644
--- a/code/datums/brain_damage/hypnosis.dm
+++ b/code/datums/brain_damage/hypnosis.dm
@@ -55,7 +55,10 @@
to_chat(owner, span_userdanger("You suddenly snap out of your hypnosis. The phrase '[hypnotic_phrase]' no longer feels important to you."))
owner.clear_alert(ALERT_HYPNOSIS)
..()
+ if (!isnull(antagonist))
+ antagonist.trauma = null
owner.mind.remove_antag_datum(/datum/antagonist/hypnotized)
+ antagonist = null
/datum/brain_trauma/hypnosis/on_life(seconds_per_tick, times_fired)
..()
diff --git a/code/datums/chatmessage.dm b/code/datums/chatmessage.dm
index 869e93371871a..35511a9dcafb5 100644
--- a/code/datums/chatmessage.dm
+++ b/code/datums/chatmessage.dm
@@ -230,9 +230,12 @@
var/remaining_time = time_before_fade * (CHAT_MESSAGE_EXP_DECAY ** idx++) * (CHAT_MESSAGE_HEIGHT_DECAY ** combined_height)
// Ensure we don't accidentially spike alpha up or something silly like that
m.message.alpha = m.get_current_alpha(time_spent)
- if (remaining_time > 0)
+ if(remaining_time > 0)
+ if(time_spent < CHAT_MESSAGE_SPAWN_TIME)
+ // We haven't even had the time to fade in yet!
+ animate(m.message, alpha = 255, CHAT_MESSAGE_SPAWN_TIME - time_spent)
// Stay faded in for a while, then
- animate(m.message, alpha = 255, remaining_time)
+ animate(m.message, alpha = 255, remaining_time, flags=ANIMATION_CONTINUE)
// Fade out
animate(alpha = 0, time = CHAT_MESSAGE_EOL_FADE)
m.animate_lifespan = remaining_time + CHAT_MESSAGE_EOL_FADE
diff --git a/code/datums/components/COMPONENT_TEMPLATE.md b/code/datums/components/COMPONENT_TEMPLATE.md
index 7b08205888522..79dfddf1d0811 100644
--- a/code/datums/components/COMPONENT_TEMPLATE.md
+++ b/code/datums/components/COMPONENT_TEMPLATE.md
@@ -40,10 +40,10 @@ See _component.dm for detailed explanations
*/
/*
-/datum/component/mycomponent/PreTransfer()
- send_to_playing_players("Goodbye [parent], I'm getting adopted")
+/datum/component/mycomponent/PreTransfer(datum/new_parent)
+ send_to_playing_players("Goodbye [new_parent], I'm getting adopted")
-/datum/component/mycomponent/PostTransfer()
+/datum/component/mycomponent/PostTransfer(datum/new_parent)
send_to_playing_players("Hello my new parent, [parent]! It's nice to meet you!")
*/
diff --git a/code/datums/components/_component.dm b/code/datums/components/_component.dm
index 2887fc55f8dc4..000f8790084f1 100644
--- a/code/datums/components/_component.dm
+++ b/code/datums/components/_component.dm
@@ -218,7 +218,7 @@
*
* Do not call `qdel(src)` from this function, `return COMPONENT_INCOMPATIBLE` instead
*/
-/datum/component/proc/PostTransfer()
+/datum/component/proc/PostTransfer(datum/new_parent)
return COMPONENT_INCOMPATIBLE //Do not support transfer by default as you must properly support it
/**
diff --git a/code/datums/components/aquarium.dm b/code/datums/components/aquarium.dm
index 86e7e3ae47769..f207ecd9f5510 100644
--- a/code/datums/components/aquarium.dm
+++ b/code/datums/components/aquarium.dm
@@ -189,6 +189,8 @@
/datum/component/aquarium/proc/on_click_alt(atom/movable/source, mob/living/user)
SIGNAL_HANDLER
+ if(!user.can_perform_action(source))
+ return
var/closing = HAS_TRAIT(parent, TRAIT_AQUARIUM_PANEL_OPEN)
if(closing)
REMOVE_TRAIT(parent, TRAIT_AQUARIUM_PANEL_OPEN, AQUARIUM_TRAIT)
@@ -358,7 +360,7 @@
types_to_mate_with = types_to_mate_with & types_to_check
for(var/obj/item/fish/fish_type as anything in types_to_mate_with)
- var/list/type_fishes = types_to_mate_with[fish_type]
+ var/list/type_fishes = tracked_fish_by_type[fish_type]
if(length(type_fishes) >= initial(fish_type.stable_population))
continue
candidates += type_fishes
diff --git a/code/datums/components/combustible_flooder.dm b/code/datums/components/combustible_flooder.dm
index 07df03671c16c..c24fae55b843b 100644
--- a/code/datums/components/combustible_flooder.dm
+++ b/code/datums/components/combustible_flooder.dm
@@ -57,7 +57,11 @@
message_admins(admin_message)
if(delete_parent && !QDELETED(parent))
- qdel(parent) // For things with the explodable component like plasma mats this isn't necessary, but there's no harm.
+ if(isobj(parent))
+ var/obj/obj_parent = parent
+ obj_parent.deconstruct(disassembled = FALSE)
+ else
+ qdel(parent) // For things with the explodable component like plasma mats this isn't necessary, but there's no harm.
qdel(src)
/// fire_act reaction.
diff --git a/code/datums/components/crafting/chemistry.dm b/code/datums/components/crafting/chemistry.dm
index 70d6c76dea249..66847281ec463 100644
--- a/code/datums/components/crafting/chemistry.dm
+++ b/code/datums/components/crafting/chemistry.dm
@@ -150,6 +150,8 @@
category = CAT_CHEMISTRY
/datum/crafting_recipe/improvised_chem_heater/on_craft_completion(mob/user, atom/result)
+ if(!istype(user))
+ return
var/obj/item/stock_parts/power_store/cell/cell = locate(/obj/item/stock_parts/power_store/cell) in range(1)
if(!cell)
return
diff --git a/code/datums/components/explodable.dm b/code/datums/components/explodable.dm
index 9dc8db3bbc4f1..db77f865fd7ae 100644
--- a/code/datums/components/explodable.dm
+++ b/code/datums/components/explodable.dm
@@ -147,18 +147,20 @@
return // If we don't do this and this doesn't delete it can lock the MC into only processing Input, Timers, and Explosions.
var/atom/bomb = parent
- var/log = TRUE
- if(light_impact_range < 1)
- log = FALSE
+ var/do_log = light_impact_range >= 1
exploding = TRUE
- explosion(bomb, devastation_range, heavy_impact_range, light_impact_range, flame_range, flash_range, log, uncapped) //epic explosion time
+ explosion(bomb, devastation_range, heavy_impact_range, light_impact_range, flame_range, flash_range, do_log, uncapped) //epic explosion time
switch(delete_after)
if(EXPLODABLE_DELETE_SELF)
qdel(src)
if(EXPLODABLE_DELETE_PARENT)
- qdel(bomb)
+ if(isobj(bomb))
+ var/obj/obj_bomb = bomb
+ obj_bomb.deconstruct(disassembled = FALSE)
+ else
+ qdel(bomb)
else
addtimer(CALLBACK(src, PROC_REF(reset_exploding)), 0.1 SECONDS)
diff --git a/code/datums/components/food_storage.dm b/code/datums/components/food_storage.dm
index 843f611e5ff3e..32fb27c7b729a 100644
--- a/code/datums/components/food_storage.dm
+++ b/code/datums/components/food_storage.dm
@@ -4,7 +4,7 @@
/datum/component/food_storage
/// Reference to what we have in our food.
- var/obj/item/stored_item
+ VAR_FINAL/obj/item/stored_item
/// The amount of volume the food has on creation - Used for probabilities
var/initial_volume = 10
/// Minimum size items that can be inserted
@@ -13,16 +13,14 @@
var/bad_chance_of_discovery = 0
/// What are the odds we see the stored item before we bite it?
var/good_chance_of_discovery = 100
- /// The stored item was found out somehow.
- var/discovered = FALSE
/datum/component/food_storage/Initialize(_minimum_weight_class = WEIGHT_CLASS_SMALL, _bad_chance = 0, _good_chance = 100)
RegisterSignal(parent, COMSIG_ATOM_ITEM_INTERACTION_SECONDARY, PROC_REF(try_inserting_item))
+ RegisterSignal(parent, COMSIG_ATOM_REQUESTING_CONTEXT_FROM_ITEM, PROC_REF(on_requesting_context_from_item))
RegisterSignal(parent, COMSIG_CLICK_CTRL, PROC_REF(try_removing_item))
RegisterSignal(parent, COMSIG_FOOD_EATEN, PROC_REF(consume_food_storage))
- RegisterSignal(parent, COMSIG_ATOM_REQUESTING_CONTEXT_FROM_ITEM, PROC_REF(on_requesting_context_from_item))
-
+ RegisterSignals(parent, list(COMSIG_FOOD_CONSUMED, COMSIG_OBJ_DECONSTRUCT), PROC_REF(storage_consumed))
var/atom/food = parent
initial_volume = food.reagents.total_volume
@@ -33,12 +31,19 @@
food.flags_1 |= HAS_CONTEXTUAL_SCREENTIPS_1
-/datum/component/food_storage/Destroy(force)
- if(stored_item)
- stored_item.forceMove(stored_item.drop_location())
- stored_item.dropped()
- stored_item = null
- . = ..()
+/datum/component/food_storage/UnregisterFromParent()
+ UnregisterSignal(parent, list(
+ COMSIG_ATOM_ITEM_INTERACTION_SECONDARY,
+ COMSIG_ATOM_REQUESTING_CONTEXT_FROM_ITEM,
+ COMSIG_CLICK_CTRL,
+ COMSIG_FOOD_CONSUMED,
+ COMSIG_FOOD_EATEN,
+ COMSIG_OBJ_DECONSTRUCT,
+ ))
+ if(QDELING(parent) || QDELETED(stored_item))
+ return
+ stored_item.forceMove(stored_item.drop_location())
+ stored_item = null
/** Begins the process of inserted an item.
*
@@ -60,19 +65,17 @@
return NONE
if(inserted_item.w_class > minimum_weight_class)
- to_chat(user, span_warning("\The [inserted_item.name] won't fit in \the [parent]."))
+ to_chat(user, span_warning("[inserted_item] won't fit in [parent]."))
return ITEM_INTERACT_BLOCKING
if(!QDELETED(stored_item))
- to_chat(user, span_warning("There's something in \the [parent]."))
- return ITEM_INTERACT_BLOCKING
-
- if(HAS_TRAIT(inserted_item, TRAIT_NODROP))
- to_chat(user, span_warning("\the [inserted_item] is stuck to your hand, you can't put into \the [parent]!"))
+ to_chat(user, span_warning("There's something in [parent]."))
return ITEM_INTERACT_BLOCKING
- user.visible_message(span_notice("[user.name] begins inserting [inserted_item.name] into \the [parent]."), \
- span_notice("You start to insert the [inserted_item.name] into \the [parent]."))
+ user.visible_message(
+ span_notice("[user] begins inserting [inserted_item] into [parent]."),
+ span_notice("You start to insert the [inserted_item] into [parent]."),
+ )
INVOKE_ASYNC(src, PROC_REF(insert_item), inserted_item, user)
return ITEM_INTERACT_SUCCESS
@@ -89,14 +92,11 @@
var/atom/food = parent
- if(QDELETED(stored_item))
- return CLICK_ACTION_BLOCKING
-
if(!food.can_interact(user))
return CLICK_ACTION_BLOCKING
- user.visible_message(span_notice("[user.name] begins tearing at \the [parent]."), \
- span_notice("You start to rip into \the [parent]."))
+ user.visible_message(span_notice("[user] begins tearing at [parent]."), \
+ span_notice("You start to rip into [parent]."))
INVOKE_ASYNC(src, PROC_REF(begin_remove_item), user)
return CLICK_ACTION_SUCCESS
@@ -110,9 +110,12 @@
/datum/component/food_storage/proc/insert_item(obj/item/inserted_item, mob/user)
if(!do_after(user, 1.5 SECONDS, target = parent))
return
+ if(!user.temporarilyRemoveItemFromInventory(inserted_item))
+ to_chat(user, span_warning("You can't seem to insert [inserted_item] into [parent]."))
+ return
var/atom/food = parent
- to_chat(user, span_notice("You slip [inserted_item.name] inside \the [parent]."))
+ to_chat(user, span_notice("You slip [inserted_item] inside [parent]."))
inserted_item.forceMove(food)
user.log_message("inserted [inserted_item] into [parent].", LOG_ATTACK)
food.add_fingerprint(user)
@@ -126,19 +129,22 @@
* user - person removing the item.
*/
/datum/component/food_storage/proc/begin_remove_item(mob/user)
- if(do_after(user, 10 SECONDS, target = parent))
- remove_item(user)
+ if(!do_after(user, 10 SECONDS, target = parent))
+ return
+ if(QDELETED(stored_item))
+ to_chat(user, span_warning("There's nothing in [parent]."))
+ return
+ remove_item(user)
/**
* Removes the stored item, putting it in user's hands or on the ground, then updates the reference.
*/
/datum/component/food_storage/proc/remove_item(mob/user)
if(user.put_in_hands(stored_item))
- user.visible_message(span_warning("[user.name] slowly pulls [stored_item.name] out of \the [parent]."), \
- span_warning("You slowly pull [stored_item.name] out of \the [parent]."))
+ user.visible_message(span_warning("[user] slowly pulls [stored_item] out of [parent]."), \
+ span_warning("You slowly pull [stored_item] out of [parent]."))
else
- stored_item.dropped()
- stored_item.visible_message(span_warning("[stored_item.name] falls out of \the [parent]."))
+ stored_item.visible_message(span_warning("[stored_item] falls out of [parent]."))
update_stored_item()
@@ -167,9 +173,10 @@
/// Chance of finding the held item = bad chance - 50
good_chance_of_discovery = bad_chance_of_discovery - 50
+ var/discovered = FALSE
if(prob(good_chance_of_discovery)) //finding the item, without biting it
discovered = TRUE
- to_chat(target, span_warning("It feels like there's something in \the [parent]...!"))
+ to_chat(target, span_warning("It feels like there's something in [parent]...!"))
else if(prob(bad_chance_of_discovery)) //finding the item, BY biting it
user.log_message("just fed [key_name(target)] \a [stored_item] which was hidden in [parent].", LOG_ATTACK)
@@ -179,6 +186,14 @@
if(!QDELETED(stored_item) && discovered)
INVOKE_ASYNC(src, PROC_REF(remove_item), user)
+/// When fully consumed, just drop the item out on the ground.
+/datum/component/food_storage/proc/storage_consumed(datum/source, mob/living/target, mob/living/user)
+ SIGNAL_HANDLER
+ if(QDELETED(stored_item))
+ return
+ stored_item.forceMove(stored_item.drop_location())
+ stored_item = null
+
/** Updates the reference of the stored item.
*
* Checks the food's contents for if an alternate item was placed into the food.
diff --git a/code/datums/components/happiness.dm b/code/datums/components/happiness.dm
index a131e86960eb3..beaad4b7ca93b 100644
--- a/code/datums/components/happiness.dm
+++ b/code/datums/components/happiness.dm
@@ -105,8 +105,7 @@
/datum/component/happiness/proc/view_happiness(mob/living/source, mob/living/clicker)
if(HAS_TRAIT(source, TRAIT_MOB_HIDE_HAPPINESS) || !istype(clicker) || !COOLDOWN_FINISHED(src, happiness_inspect) || !clicker.CanReach(source))
return
- var/list/offset_to_add = get_icon_dimensions(source.icon)
- var/y_position = offset_to_add["height"] + 1
+ var/y_position = source.get_cached_height() + 1
var/obj/effect/overlay/happiness_overlay/hearts = new
hearts.pixel_y = y_position
hearts.set_hearts(happiness_level/maximum_happiness)
diff --git a/code/datums/components/jetpack.dm b/code/datums/components/jetpack.dm
index 0a52d2250be93..c7ff096029b97 100644
--- a/code/datums/components/jetpack.dm
+++ b/code/datums/components/jetpack.dm
@@ -3,8 +3,10 @@
// So propulsion through space on move, that sort of thing
/datum/component/jetpack
dupe_mode = COMPONENT_DUPE_UNIQUE_PASSARGS
- /// Checks to ensure if we can move & if we can activate
+ /// Checks to ensure if we can move
var/datum/callback/check_on_move
+ /// Checks to ensure we can activate
+ var/datum/callback/check_on_activation
/// If we should stabilize ourselves when not drifting
var/stabilize = FALSE
/// The signal we listen for as an activation
@@ -23,6 +25,10 @@
var/stabilization_force
/// Our current user
var/mob/user
+ /// Last tick on which we triggered, to prevent double-dipping
+ var/last_force_tick
+ /// Last tick on which we stabilized
+ var/last_stabilization_tick
/**
* Arguments:
@@ -35,7 +41,7 @@
* * check_on_move - Callback we call each time we attempt a move, we expect it to retun true if the move is ok, false otherwise. It expects an arg, TRUE if fuel should be consumed, FALSE othewise
* * effect_type - Type of trail_follow to spawn
*/
-/datum/component/jetpack/Initialize(stabilize, drift_force = 1 NEWTONS, stabilization_force = 1 NEWTONS, activation_signal, deactivation_signal, return_flag, datum/callback/check_on_move, datum/effect_system/trail_follow/effect_type)
+/datum/component/jetpack/Initialize(stabilize, drift_force = 1 NEWTONS, stabilization_force = 1 NEWTONS, activation_signal, deactivation_signal, return_flag, datum/callback/check_on_move, datum/callback/check_on_activation, datum/effect_system/trail_follow/effect_type)
. = ..()
if(!isatom(parent))
return COMPONENT_INCOMPATIBLE
@@ -48,6 +54,7 @@
src.stabilize = stabilize
src.check_on_move = check_on_move
+ src.check_on_activation = check_on_activation
src.activation_signal = activation_signal
src.deactivation_signal = deactivation_signal
src.return_flag = return_flag
@@ -55,7 +62,7 @@
src.drift_force = drift_force
src.stabilization_force = stabilization_force
-/datum/component/jetpack/InheritComponent(datum/component/component, original, stabilize, drift_force = 1 NEWTONS, stabilization_force = 1 NEWTONS, activation_signal, deactivation_signal, return_flag, datum/callback/check_on_move, datum/effect_system/trail_follow/effect_type)
+/datum/component/jetpack/InheritComponent(datum/component/component, original, stabilize, drift_force = 1 NEWTONS, stabilization_force = 1 NEWTONS, activation_signal, deactivation_signal, return_flag, datum/callback/check_on_move, datum/callback/check_on_activation, datum/effect_system/trail_follow/effect_type)
UnregisterSignal(parent, src.activation_signal)
if(src.deactivation_signal)
UnregisterSignal(parent, src.deactivation_signal)
@@ -65,6 +72,7 @@
src.stabilize = stabilize
src.check_on_move = check_on_move
+ src.check_on_activation = check_on_activation
src.activation_signal = activation_signal
src.deactivation_signal = deactivation_signal
src.return_flag = return_flag
@@ -80,6 +88,7 @@
QDEL_NULL(trail)
user = null
check_on_move = null
+ check_on_activation = null
return ..()
/datum/component/jetpack/proc/setup_trail(mob/user)
@@ -93,7 +102,7 @@
/datum/component/jetpack/proc/activate(datum/source, mob/new_user)
SIGNAL_HANDLER
- if(!check_on_move.Invoke(TRUE))
+ if(!isnull(check_on_activation) && !check_on_activation.Invoke())
return return_flag
user = new_user
@@ -101,8 +110,11 @@
RegisterSignal(user, COMSIG_MOVABLE_PRE_MOVE, PROC_REF(pre_move_react))
RegisterSignal(user, COMSIG_MOB_CLIENT_MOVE_NOGRAV, PROC_REF(on_client_move))
RegisterSignal(user, COMSIG_MOB_ATTEMPT_HALT_SPACEMOVE, PROC_REF(on_pushoff))
+ RegisterSignal(user, COMSIG_MOVABLE_DRIFT_BLOCK_INPUT, PROC_REF(on_input_block))
+ last_stabilization_tick = world.time
START_PROCESSING(SSnewtonian_movement, src)
- setup_trail(user)
+ if (effect_type)
+ setup_trail(user)
/datum/component/jetpack/proc/deactivate(datum/source, mob/old_user)
SIGNAL_HANDLER
@@ -119,7 +131,7 @@
if (!should_trigger(source))
return
- if(source.client.intended_direction && check_on_move.Invoke(FALSE))//You use jet when press keys. yes.
+ if(source.client.intended_direction && check_on_move.Invoke(FALSE) && trail) //You use jet when press keys. yes.
trail.generate_effect()
/datum/component/jetpack/proc/should_trigger(mob/source)
@@ -142,24 +154,62 @@
trail.oldposition = get_turf(source)
/datum/component/jetpack/process(seconds_per_tick)
+ if (last_stabilization_tick == world.time)
+ return
+
+ last_stabilization_tick = world.time
+
if (!should_trigger(user) || !stabilize || isnull(user.drift_handler))
return
- var/max_drift_force = (DEFAULT_INERTIA_SPEED / user.cached_multiplicative_slowdown - 1) / INERTIA_SPEED_COEF + 1
+ var/max_drift_force = MOVE_DELAY_TO_DRIFT(user.cached_multiplicative_slowdown)
user.drift_handler.stabilize_drift(user.client.intended_direction ? dir2angle(user.client.intended_direction) : null, user.client.intended_direction ? max_drift_force : 0, stabilization_force * (seconds_per_tick * 1 SECONDS))
+/datum/component/jetpack/proc/on_input_block(mob/source)
+ SIGNAL_HANDLER
+
+ if (!should_trigger(source))
+ return
+
+ if (!check_on_move.Invoke(TRUE))
+ return
+
+ return DRIFT_ALLOW_INPUT
+
/datum/component/jetpack/proc/on_client_move(mob/source, list/move_args)
SIGNAL_HANDLER
if (!should_trigger(source))
return
+ if (last_force_tick == world.time)
+ return
+
if (!check_on_move.Invoke(TRUE))
return
- var/max_drift_force = (DEFAULT_INERTIA_SPEED / source.cached_multiplicative_slowdown - 1) / INERTIA_SPEED_COEF + 1
- source.newtonian_move(dir2angle(source.client.intended_direction), instant = TRUE, drift_force = drift_force, controlled_cap = max_drift_force)
- source.setDir(source.client.intended_direction)
+ var/max_drift_force = MOVE_DELAY_TO_DRIFT(source.cached_multiplicative_slowdown)
+ var/applied_force = drift_force
+ var/move_dir = source.client.intended_direction
+ // Try to see if we can simulate pushing off a wall
+ var/atom/movable/backup = source.get_spacemove_backup(move_dir, FALSE, include_floors = TRUE)
+ if (backup && !(backup.dir & move_dir))
+ applied_force = max_drift_force
+
+ // We don't want to force the loop to fire before stabilizing if we're going to, otherwise its effects will be delayed until the next tick which is jank
+ var/force_stabilize = FALSE
+ if (last_stabilization_tick < world.time)
+ force_stabilize = TRUE
+
+ source.newtonian_move(dir2angle(move_dir), instant = TRUE, drift_force = applied_force, controlled_cap = max_drift_force, force_loop = !force_stabilize)
+ source.setDir(move_dir)
+ last_force_tick = world.time
+
+ if (force_stabilize)
+ // Newphys is an SS_TICKER subsystem and under ideal circumstances should be firing every tick, thus a period of world.tick_lag
+ // However, since our servers are jank, even SSinput can end up overtiming - which is also an SS_TICKER subsystem that just so
+ // happens to be what is calling this proc - so we can be assured that this is not above world.tick_lag, or at least should not be
+ process(world.tick_lag)
/datum/component/jetpack/proc/on_pushoff(mob/source, movement_dir, continuous_move, atom/backup)
SIGNAL_HANDLER
@@ -167,7 +217,10 @@
if (get_dir(source, backup) == movement_dir || source.loc == backup.loc)
return
- if (!source.client?.intended_direction || (source.client.intended_direction & get_dir(source, backup)))
+ if (!source.client?.intended_direction || source.client.intended_direction == get_dir(source, backup))
+ return
+
+ if (isnull(source.drift_handler))
return
if (!should_trigger(source) || !check_on_move.Invoke(FALSE))
diff --git a/code/datums/components/leanable.dm b/code/datums/components/leanable.dm
index b95fd734ad043..b823cf5ec503c 100644
--- a/code/datums/components/leanable.dm
+++ b/code/datums/components/leanable.dm
@@ -2,26 +2,21 @@
/datum/component/leanable
/// How much will mobs that lean onto this object be offset
var/leaning_offset = 11
- /// List of click modifiers that are required to be present for leaning to trigger
- var/list/click_mods = null
- /// Callback called for additional checks if a lean is valid
- var/datum/callback/lean_check = null
- /// Whenever this object can be leaned on from the same turf as its' own. Do not use without a custom lean_check!
- var/same_turf = FALSE
/// List of mobs currently leaning on our parent
var/list/leaning_mobs = list()
-/datum/component/leanable/Initialize(leaning_offset = 11, list/click_mods = null, datum/callback/lean_check = null, same_turf = FALSE)
+/datum/component/leanable/Initialize(mob/living/leaner, leaning_offset = 11)
. = ..()
src.leaning_offset = leaning_offset
- src.click_mods = click_mods
- src.lean_check = lean_check
- src.same_turf = same_turf
+ mousedrop_receive(parent, leaner, leaner)
/datum/component/leanable/RegisterWithParent()
RegisterSignal(parent, COMSIG_MOUSEDROPPED_ONTO, PROC_REF(mousedrop_receive))
RegisterSignal(parent, COMSIG_MOVABLE_MOVED, PROC_REF(on_moved))
+/datum/component/leanable/UnregisterFromParent()
+ UnregisterSignal(parent, list(COMSIG_MOUSEDROPPED_ONTO, COMSIG_MOVABLE_MOVED))
+
/datum/component/leanable/Destroy(force)
for (var/mob/living/leaner as anything in leaning_mobs)
leaner.stop_leaning()
@@ -30,17 +25,13 @@
/datum/component/leanable/proc/on_moved(datum/source)
SIGNAL_HANDLER
+
for (var/mob/living/leaner as anything in leaning_mobs)
leaner.stop_leaning()
/datum/component/leanable/proc/mousedrop_receive(atom/source, atom/movable/dropped, mob/user, params)
if (dropped != user)
return
- if (islist(click_mods))
- var/list/modifiers = params2list(params)
- for (var/modifier in click_mods)
- if (!LAZYACCESS(modifiers, modifier))
- return
if (!iscarbon(dropped) && !iscyborg(dropped))
return
var/mob/living/leaner = dropped
@@ -49,9 +40,7 @@
if (HAS_TRAIT_FROM(leaner, TRAIT_UNDENSE, LEANING_TRAIT))
return
var/turf/checked_turf = get_step(leaner, REVERSE_DIR(leaner.dir))
- if (checked_turf != get_turf(source) && (!same_turf || get_turf(source) != get_turf(leaner)))
- return
- if (!isnull(lean_check) && !lean_check.Invoke(dropped, params))
+ if (checked_turf != get_turf(source))
return
leaner.start_leaning(source, leaning_offset)
leaning_mobs += leaner
@@ -63,6 +52,13 @@
leaning_mobs -= source
UnregisterSignal(source, list(COMSIG_LIVING_STOPPED_LEANING, COMSIG_QDELETING))
+/**
+ * Makes the mob lean on an atom
+ * Arguments
+ *
+ * * atom/lean_target - the target the mob is trying to lean on
+ * * leaning_offset - pixel offset to apply on the mob when leaning
+ */
/mob/living/proc/start_leaning(atom/lean_target, leaning_offset)
var/new_x = lean_target.pixel_x + base_pixel_x + body_position_pixel_x_offset
var/new_y = lean_target.pixel_y + base_pixel_y + body_position_pixel_y_offset
@@ -95,6 +91,7 @@
/// You fall on your face if you get teleported while leaning
/mob/living/proc/teleport_away_while_leaning()
SIGNAL_HANDLER
+
// Make sure we unregister signal handlers and reset animation
stop_leaning()
// -1000 aura
@@ -103,6 +100,7 @@
/mob/living/proc/stop_leaning()
SIGNAL_HANDLER
+
UnregisterSignal(src, list(
COMSIG_MOB_CLIENT_PRE_MOVE,
COMSIG_LIVING_DISARM_HIT,
@@ -117,5 +115,6 @@
/mob/living/proc/lean_dir_changed(atom/source, old_dir, new_dir)
SIGNAL_HANDLER
+
if (old_dir != new_dir)
INVOKE_ASYNC(src, PROC_REF(stop_leaning))
diff --git a/code/datums/components/life_link.dm b/code/datums/components/life_link.dm
index 314a3d7931bde..26484674e8080 100644
--- a/code/datums/components/life_link.dm
+++ b/code/datums/components/life_link.dm
@@ -127,16 +127,14 @@
if(isnull(holder))
return
holder.icon_state = "hud[RoundHealth(host)]"
- var/icon/size_check = icon(mob_parent.icon, mob_parent.icon_state, mob_parent.dir)
- holder.pixel_y = size_check.Height() - ICON_SIZE_Y
+ holder.pixel_y = mob_parent.get_cached_height() - ICON_SIZE_Y
/// Update our vital status on the medical hud
/datum/component/life_link/proc/update_med_hud_status(mob/living/mob_parent)
var/image/holder = mob_parent.hud_list?[STATUS_HUD]
if(isnull(holder))
return
- var/icon/size_check = icon(mob_parent.icon, mob_parent.icon_state, mob_parent.dir)
- holder.pixel_y = size_check.Height() - ICON_SIZE_Y
+ holder.pixel_y = mob_parent.get_cached_height() - ICON_SIZE_Y
if(host.stat == DEAD || HAS_TRAIT(host, TRAIT_FAKEDEATH))
holder.icon_state = "huddead"
else
diff --git a/code/datums/components/seethrough_mob.dm b/code/datums/components/seethrough_mob.dm
index b6951c5489b6d..a788ef6e8611f 100644
--- a/code/datums/components/seethrough_mob.dm
+++ b/code/datums/components/seethrough_mob.dm
@@ -55,9 +55,8 @@
for(var/atom/movable/screen/plane_master/seethrough as anything in our_hud.get_true_plane_masters(SEETHROUGH_PLANE))
seethrough.unhide_plane(fool)
- var/icon/current_mob_icon = icon(fool.icon, fool.icon_state)
render_source_atom.pixel_x = -fool.pixel_x
- render_source_atom.pixel_y = ((current_mob_icon.Height() - 32) * 0.5)
+ render_source_atom.pixel_y = (fool.get_cached_height() - ICON_SIZE_Y * 0.5)
initial_render_target_value = fool.render_target
fool.render_target = "*transparent_bigmob[personal_uid]"
diff --git a/code/datums/components/tameable.dm b/code/datums/components/tameable.dm
index 0d77688a22e7a..747d729ee1dcf 100644
--- a/code/datums/components/tameable.dm
+++ b/code/datums/components/tameable.dm
@@ -2,8 +2,6 @@
/datum/component/tameable
///If true, this atom can only be domesticated by one person
var/unique
- ///What the mob eats, typically used for taming or animal husbandry.
- var/list/food_types
///Starting success chance for taming.
var/tame_chance
///Added success chance after every failed tame attempt.
@@ -15,8 +13,6 @@
if(!isatom(parent)) //yes, you could make a tameable toolbox.
return COMPONENT_INCOMPATIBLE
- if(food_types)
- src.food_types = food_types
if(tame_chance)
src.tame_chance = tame_chance
src.current_tame_chance = tame_chance
@@ -24,35 +20,30 @@
src.bonus_tame_chance = bonus_tame_chance
src.unique = unique
- RegisterSignal(parent, COMSIG_ATOM_ATTACKBY, PROC_REF(try_tame))
+ if(food_types && !HAS_TRAIT(parent, TRAIT_MOB_EATER))
+ parent.AddElement(/datum/element/basic_eating, food_types = food_types)
+
+ RegisterSignal(parent, COMSIG_MOB_ATE, PROC_REF(try_tame))
RegisterSignal(parent, COMSIG_SIMPLEMOB_SENTIENCEPOTION, PROC_REF(on_tame)) //Instantly succeeds
RegisterSignal(parent, COMSIG_SIMPLEMOB_TRANSFERPOTION, PROC_REF(on_tame)) //Instantly succeeds
-/datum/component/tameable/proc/try_tame(datum/source, obj/item/food, mob/living/attacker, params)
+/datum/component/tameable/proc/try_tame(atom/source, obj/item/food, mob/living/attacker)
SIGNAL_HANDLER
- if(!is_type_in_list(food, food_types))
+
+ if(isnull(attacker) || already_friends(attacker))
return
- if(isliving(source))
- var/mob/living/potentially_dead_horse = source
- if(potentially_dead_horse.stat == DEAD)
- to_chat(attacker, span_warning("[parent] is dead!"))
- return COMPONENT_CANCEL_ATTACK_CHAIN
- var/atom/atom_parent = source
var/inform_tamer = FALSE
- atom_parent.balloon_alert(attacker, "fed")
var/modified_tame_chance = current_tame_chance
if(HAS_TRAIT(attacker, TRAIT_BEAST_EMPATHY))
modified_tame_chance += 50
inform_tamer = TRUE
- if(unique || !already_friends(attacker))
- if(prob(modified_tame_chance)) //note: lack of feedback message is deliberate, keep them guessing unless they're an expert!
- on_tame(source, attacker, food, inform_tamer)
- else
- current_tame_chance += bonus_tame_chance
- qdel(food)
- return COMPONENT_CANCEL_ATTACK_CHAIN
+ source.balloon_alert(attacker, "eats from your hand")
+ if(prob(modified_tame_chance)) //note: lack of feedback message is deliberate, keep them guessing unless they're an expert!
+ on_tame(source, attacker, food, inform_tamer)
+ else
+ current_tame_chance += bonus_tame_chance
/// Check if the passed mob is already considered one of our friends
/datum/component/tameable/proc/already_friends(mob/living/potential_friend)
diff --git a/code/datums/components/throwbonus_on_windup.dm b/code/datums/components/throwbonus_on_windup.dm
index ed505d69697de..a96d9294e8010 100644
--- a/code/datums/components/throwbonus_on_windup.dm
+++ b/code/datums/components/throwbonus_on_windup.dm
@@ -52,8 +52,7 @@
return
if(throw_text)
to_chat(our_holder, span_warning(throw_text))
- var/list/offset_to_add = get_icon_dimensions(our_holder.icon)
- var/x_position = CEILING(offset_to_add["width"] * 0.5, 1)
+ var/x_position = CEILING(our_holder.get_cached_width() * 0.5, 1)
our_bar = new()
our_bar.maximum_count = maximum_bonus
our_bar.pixel_x = x_position
diff --git a/code/datums/drift_handler.dm b/code/datums/drift_handler.dm
index 2153058436470..dcf0771d8a5e8 100644
--- a/code/datums/drift_handler.dm
+++ b/code/datums/drift_handler.dm
@@ -36,7 +36,6 @@
RegisterSignal(drifting_loop, COMSIG_MOVELOOP_PREPROCESS_CHECK, PROC_REF(before_move))
RegisterSignal(drifting_loop, COMSIG_MOVELOOP_POSTPROCESS, PROC_REF(after_move))
RegisterSignal(drifting_loop, COMSIG_QDELETING, PROC_REF(loop_death))
- RegisterSignal(parent, COMSIG_MOB_ATTEMPT_HALT_SPACEMOVE, PROC_REF(attempt_halt))
if(drifting_loop.status & MOVELOOP_STATUS_RUNNING)
drifting_start(drifting_loop) // There's a good chance it'll autostart, gotta catch that
@@ -49,6 +48,9 @@
visual_delay = start_delay
apply_initial_visuals(visual_delay)
+ // Fire the engines!
+ if (drifting_loop.timer <= world.time)
+ SSnewtonian_movement.fire_moveloop(drifting_loop)
/datum/drift_handler/Destroy()
inertia_last_loc = null
@@ -76,7 +78,7 @@
//It's ok if it's not, it's just important if it is.
mob_parent.client?.visual_delay = MOVEMENT_ADJUSTED_GLIDE_SIZE(visual_delay, SSnewtonian_movement.visual_delay)
-/datum/drift_handler/proc/newtonian_impulse(inertia_angle, start_delay, additional_force, controlled_cap)
+/datum/drift_handler/proc/newtonian_impulse(inertia_angle, start_delay, additional_force, controlled_cap, force_loop = TRUE)
SIGNAL_HANDLER
inertia_last_loc = parent.loc
// We've been told to move in the middle of deletion process, tell parent to create a new handler instead
@@ -96,6 +98,9 @@
drifting_loop.set_angle(delta_to_angle(force_x, force_y))
drifting_loop.set_delay(get_loop_delay(parent))
+ // We have to forcefully fire it here to avoid stuttering in case of server lag
+ if (drifting_loop.timer <= world.time && force_loop)
+ SSnewtonian_movement.fire_moveloop(drifting_loop)
return TRUE
/datum/drift_handler/proc/drifting_start()
@@ -202,28 +207,28 @@
if(world.time < block_inputs_until)
return COMSIG_MOB_CLIENT_BLOCK_PRE_MOVE
-/datum/drift_handler/proc/attempt_halt(mob/source, movement_dir, continuous_move, atom/backup)
- SIGNAL_HANDLER
-
- if ((backup.density || !backup.CanPass(source, get_dir(backup, source))) && (get_dir(source, backup) == movement_dir || source.loc == backup.loc))
+/datum/drift_handler/proc/attempt_halt(movement_dir, continuous_move, atom/backup)
+ if ((backup.density || !backup.CanPass(parent, get_dir(backup, parent))) && (get_dir(parent, backup) == movement_dir || parent.loc == backup.loc))
if (drift_force >= INERTIA_FORCE_THROW_FLOOR)
- source.throw_at(backup, 1, floor(1 + (drift_force - INERTIA_FORCE_THROW_FLOOR) / INERTIA_FORCE_PER_THROW_FORCE), spin = FALSE)
- return
+ parent.throw_at(backup, 1, floor(1 + (drift_force - INERTIA_FORCE_THROW_FLOOR) / INERTIA_FORCE_PER_THROW_FORCE), spin = FALSE)
+ return FALSE
if (drift_force < INERTIA_FORCE_SPACEMOVE_GRAB || isnull(drifting_loop))
- return
+ return FALSE
- if (!isnull(source.client) && source.client.intended_direction)
- if ((source.client.intended_direction & movement_dir) && !(get_dir(source, backup) & movement_dir))
- return
+ if (ismob(parent))
+ var/mob/source_user = parent
+ if (!isnull(source_user.client) && source_user.client.intended_direction)
+ if ((source_user.client.intended_direction & movement_dir) && !(get_dir(source_user, backup) & movement_dir))
+ return FALSE
- if (drift_force <= INERTIA_FORCE_SPACEMOVE_REDUCTION / source.inertia_force_weight)
- glide_to_halt(get_loop_delay(source))
- return COMPONENT_PREVENT_SPACEMOVE_HALT
+ if (drift_force <= INERTIA_FORCE_SPACEMOVE_REDUCTION / parent.inertia_force_weight)
+ glide_to_halt(get_loop_delay(parent))
+ return TRUE
- drift_force -= INERTIA_FORCE_SPACEMOVE_REDUCTION / source.inertia_force_weight
- drifting_loop.set_delay(get_loop_delay(source))
- return COMPONENT_PREVENT_SPACEMOVE_HALT
+ drift_force -= INERTIA_FORCE_SPACEMOVE_REDUCTION / parent.inertia_force_weight
+ drifting_loop.set_delay(get_loop_delay(parent))
+ return TRUE
/datum/drift_handler/proc/get_loop_delay(atom/movable/movable)
return (DEFAULT_INERTIA_SPEED / ((1 - INERTIA_SPEED_COEF) + drift_force * INERTIA_SPEED_COEF)) * movable.inertia_move_multiplier
diff --git a/code/datums/elements/basic_eating.dm b/code/datums/elements/basic_eating.dm
index 4f4f493e0ef33..75caa272ef9bd 100644
--- a/code/datums/elements/basic_eating.dm
+++ b/code/datums/elements/basic_eating.dm
@@ -30,16 +30,25 @@
src.drinking = drinking
src.food_types = food_types
- //this lets players eat
+ RegisterSignal(target, COMSIG_ATOM_ITEM_INTERACTION, PROC_REF(try_feed))
RegisterSignal(target, COMSIG_LIVING_UNARMED_ATTACK, PROC_REF(on_unarm_attack))
- //this lets ai eat. yes, i'm serious
- RegisterSignal(target, COMSIG_HOSTILE_PRE_ATTACKINGTARGET, PROC_REF(on_pre_attackingtarget))
/datum/element/basic_eating/Detach(datum/target)
REMOVE_TRAIT(target, TRAIT_MOB_EATER, REF(src))
- UnregisterSignal(target, list(COMSIG_LIVING_UNARMED_ATTACK, COMSIG_HOSTILE_PRE_ATTACKINGTARGET))
+
+ UnregisterSignal(target, list(
+ COMSIG_LIVING_UNARMED_ATTACK,
+ COMSIG_ATOM_ITEM_INTERACTION,
+ ))
return ..()
+/datum/element/basic_eating/proc/try_feed(atom/source, mob/living/user, atom/possible_food)
+ SIGNAL_HANDLER
+ if(user.combat_mode || !is_type_in_list(possible_food, food_types))
+ return NONE
+
+ try_eating(source, possible_food, user)
+
/datum/element/basic_eating/proc/on_unarm_attack(mob/living/eater, atom/target, proximity, modifiers)
SIGNAL_HANDLER
if(!proximity)
@@ -49,14 +58,10 @@
return COMPONENT_CANCEL_ATTACK_CHAIN
return NONE
-/datum/element/basic_eating/proc/on_pre_attackingtarget(mob/living/eater, atom/target)
- SIGNAL_HANDLER
- try_eating(eater, target)
-
-/datum/element/basic_eating/proc/try_eating(mob/living/eater, atom/target)
+/datum/element/basic_eating/proc/try_eating(mob/living/eater, atom/target, mob/living/feeder)
if(!is_type_in_list(target, food_types))
return FALSE
- if(SEND_SIGNAL(eater, COMSIG_MOB_PRE_EAT, target) & COMSIG_MOB_CANCEL_EAT)
+ if(SEND_SIGNAL(eater, COMSIG_MOB_PRE_EAT, target, feeder) & COMSIG_MOB_CANCEL_EAT)
return FALSE
var/eat_verb
if(drinking)
@@ -75,21 +80,22 @@
if (damage_amount > 0 && damage_type)
eater.apply_damage(damage_amount, damage_type)
eater.visible_message(span_notice("[eater] [eat_verb]s [target], and seems to hurt itself."), span_notice("You [eat_verb] [target], hurting yourself in the process."))
- finish_eating(eater, target)
+ finish_eating(eater, target, feeder)
return TRUE
eater.visible_message(span_notice("[eater] [eat_verb]s [target]."), span_notice("You [eat_verb] [target]."))
- finish_eating(eater, target)
+ finish_eating(eater, target, feeder)
return TRUE
-/datum/element/basic_eating/proc/finish_eating(mob/living/eater, atom/target)
+/datum/element/basic_eating/proc/finish_eating(mob/living/eater, atom/target, mob/living/feeder)
set waitfor = FALSE
- SEND_SIGNAL(eater, COMSIG_MOB_ATE)
if(drinking)
playsound(eater.loc,'sound/items/drink.ogg', rand(10,50), TRUE)
else
playsound(eater.loc,'sound/items/eatfood.ogg', rand(10,50), TRUE)
var/atom/final_target = target
+ if(SEND_SIGNAL(eater, COMSIG_MOB_ATE, final_target, feeder) & COMSIG_MOB_TERMINATE_EAT)
+ return
if(isstack(target)) //if stack, only consume 1
var/obj/item/stack/food_stack = target
final_target = food_stack.split_stack(eater, 1)
diff --git a/code/datums/elements/dryable.dm b/code/datums/elements/dryable.dm
index d66ea96d8c1f1..583c74764eec9 100644
--- a/code/datums/elements/dryable.dm
+++ b/code/datums/elements/dryable.dm
@@ -22,10 +22,13 @@
/datum/element/dryable/proc/finish_drying(atom/source, datum/weakref/drying_user)
SIGNAL_HANDLER
+ var/static/list/dried_color
+ if (isnull(dried_color))
+ dried_color = color_transition_filter(COLOR_DRIED_TAN)
var/atom/dried_atom = source
if(dry_result == dried_atom.type)//if the dried type is the same as our currrent state, don't bother creating a whole new item, just re-color it.
var/atom/movable/resulting_atom = dried_atom
- resulting_atom.add_atom_colour(COLOR_DRIED_TAN, FIXED_COLOUR_PRIORITY)
+ resulting_atom.add_atom_colour(dried_color, FIXED_COLOUR_PRIORITY)
apply_dried_status(resulting_atom, drying_user)
return
else if(isstack(source)) //Check if its a sheet
diff --git a/code/datums/elements/food/fried_item.dm b/code/datums/elements/food/fried_item.dm
index bc21e51f24cd7..7f8613a537f70 100644
--- a/code/datums/elements/food/fried_item.dm
+++ b/code/datums/elements/food/fried_item.dm
@@ -2,18 +2,21 @@
/datum/element/fried_item
/// List of colors to apply the element target.
/// Each index corresponds to a different level.
- var/static/list/fried_colors = list(
- COLOR_LIGHT_BROWN,
- COLOR_BROWNER_BROWN,
- COLOR_DARKER_BROWN,
- COLOR_BLACK,
- )
+ var/static/list/fried_colors
/datum/element/fried_item/Attach(datum/target, fry_time)
. = ..()
if(!isatom(target))
return ELEMENT_INCOMPATIBLE
+ if (isnull(fried_colors))
+ fried_colors = list(
+ color_transition_filter(COLOR_LIGHT_BROWN, SATURATION_OVERRIDE),
+ color_transition_filter(COLOR_BROWNER_BROWN, SATURATION_OVERRIDE),
+ color_transition_filter(COLOR_DARKER_BROWN, SATURATION_OVERRIDE),
+ color_transition_filter(COLOR_BLACK, SATURATION_OVERRIDE),
+ )
+
var/atom/this_food = target
switch(fry_time)
diff --git a/code/datums/looping_sounds/_looping_sound.dm b/code/datums/looping_sounds/_looping_sound.dm
index d0ad6544ca6e6..1dc2e69f2ca84 100644
--- a/code/datums/looping_sounds/_looping_sound.dm
+++ b/code/datums/looping_sounds/_looping_sound.dm
@@ -174,7 +174,7 @@
if(!each_once)
. = play_from
while(!isfile(.) && !isnull(.))
- . = pick_weight(.)
+ . = pick_weight_recursive(.)
return .
if(in_order)
@@ -192,7 +192,7 @@
// Tree is a list of lists containign files
// If an entry in the tree goes to 0 length, we cut it from the list
tree += list(.)
- . = pick_weight(.)
+ . = pick_weight_recursive(.)
if(!isfile(.))
return
diff --git a/code/datums/looping_sounds/machinery_sounds.dm b/code/datums/looping_sounds/machinery_sounds.dm
index c4648a929b300..9e33a124e3b45 100644
--- a/code/datums/looping_sounds/machinery_sounds.dm
+++ b/code/datums/looping_sounds/machinery_sounds.dm
@@ -1,13 +1,17 @@
/datum/looping_sound/showering
start_sound = 'sound/machines/shower/shower_start.ogg'
start_length = 2
- mid_sounds = list('sound/machines/shower/shower_mid1.ogg' = 1, 'sound/machines/shower/shower_mid2.ogg' = 1, 'sound/machines/shower/shower_mid3.ogg' = 1)
+ mid_sounds = list(
+ 'sound/machines/shower/shower_mid1.ogg',
+ 'sound/machines/shower/shower_mid2.ogg',
+ 'sound/machines/shower/shower_mid3.ogg',
+ )
mid_length = 10
end_sound = 'sound/machines/shower/shower_end.ogg'
volume = 20
/datum/looping_sound/supermatter
- mid_sounds = list('sound/machines/sm/loops/calm.ogg' = 1)
+ mid_sounds = list('sound/machines/sm/loops/calm.ogg')
mid_length = 60
volume = 40
extra_range = 25
@@ -16,14 +20,14 @@
vary = TRUE
/datum/looping_sound/destabilized_crystal
- mid_sounds = list('sound/machines/sm/loops/delamming.ogg' = 1)
+ mid_sounds = list('sound/machines/sm/loops/delamming.ogg')
mid_length = 60
volume = 55
extra_range = 15
vary = TRUE
/datum/looping_sound/hypertorus
- mid_sounds = list('sound/machines/hypertorus/loops/hypertorus_nominal.ogg' = 1)
+ mid_sounds = list('sound/machines/hypertorus/loops/hypertorus_nominal.ogg')
mid_length = 60
volume = 55
extra_range = 15
@@ -32,35 +36,41 @@
/datum/looping_sound/generator
start_sound = 'sound/machines/generator/generator_start.ogg'
start_length = 4
- mid_sounds = list('sound/machines/generator/generator_mid1.ogg' = 1, 'sound/machines/generator/generator_mid2.ogg' = 1, 'sound/machines/generator/generator_mid3.ogg' = 1)
+ mid_sounds = list(
+ 'sound/machines/generator/generator_mid1.ogg',
+ 'sound/machines/generator/generator_mid2.ogg',
+ 'sound/machines/generator/generator_mid3.ogg',
+ )
mid_length = 4
end_sound = 'sound/machines/generator/generator_end.ogg'
volume = 40
-
/datum/looping_sound/deep_fryer
start_sound = 'sound/machines/fryer/deep_fryer_immerse.ogg' //my immersions
start_length = 10
- mid_sounds = list('sound/machines/fryer/deep_fryer_1.ogg' = 1, 'sound/machines/fryer/deep_fryer_2.ogg' = 1)
+ mid_sounds = list(
+ 'sound/machines/fryer/deep_fryer_1.ogg',
+ 'sound/machines/fryer/deep_fryer_2.ogg',
+ )
mid_length = 2
end_sound = 'sound/machines/fryer/deep_fryer_emerge.ogg'
volume = 15
/datum/looping_sound/clock
- mid_sounds = list('sound/ambience/misc/ticking_clock.ogg' = 1)
+ mid_sounds = list('sound/ambience/misc/ticking_clock.ogg')
mid_length = 40
volume = 50
ignore_walls = FALSE
/datum/looping_sound/grill
- mid_sounds = list('sound/machines/grill/grillsizzle.ogg' = 1)
+ mid_sounds = list('sound/machines/grill/grillsizzle.ogg')
mid_length = 18
volume = 50
/datum/looping_sound/oven
start_sound = 'sound/machines/oven/oven_loop_start.ogg' //my immersions
start_length = 12
- mid_sounds = list('sound/machines/oven/oven_loop_mid.ogg' = 1)
+ mid_sounds = list('sound/machines/oven/oven_loop_mid.ogg')
mid_length = 13
end_sound = 'sound/machines/oven/oven_loop_end.ogg'
volume = 100
@@ -68,19 +78,25 @@
/datum/looping_sound/deep_fryer
mid_length = 2
- mid_sounds = list('sound/machines/fryer/deep_fryer_1.ogg' = 1, 'sound/machines/fryer/deep_fryer_2.ogg' = 1)
+ mid_sounds = list(
+ 'sound/machines/fryer/deep_fryer_1.ogg',
+ 'sound/machines/fryer/deep_fryer_2.ogg',
+ )
volume = 30
/datum/looping_sound/microwave
start_sound = 'sound/machines/microwave/microwave-start.ogg'
start_length = 10
- mid_sounds = list('sound/machines/microwave/microwave-mid1.ogg' = 10, 'sound/machines/microwave/microwave-mid2.ogg' = 1)
+ mid_sounds = list(
+ 'sound/machines/microwave/microwave-mid1.ogg' = 10,
+ 'sound/machines/microwave/microwave-mid2.ogg' = 1,
+ )
mid_length = 10
end_sound = 'sound/machines/microwave/microwave-end.ogg'
volume = 90
/datum/looping_sound/lathe_print
- mid_sounds = list('sound/machines/lathe/lathe_print.ogg' = 1)
+ mid_sounds = list('sound/machines/lathe/lathe_print.ogg')
mid_length = 20
volume = 50
vary = TRUE
@@ -90,19 +106,19 @@
/datum/looping_sound/jackpot
mid_length = 11
- mid_sounds = list('sound/machines/roulette/roulettejackpot.ogg' = 1)
+ mid_sounds = list('sound/machines/roulette/roulettejackpot.ogg')
volume = 85
vary = TRUE
/datum/looping_sound/server
mid_sounds = list(
- 'sound/machines/tcomms/tcomms_mid1.ogg' = 1,
- 'sound/machines/tcomms/tcomms_mid2.ogg' = 1,
- 'sound/machines/tcomms/tcomms_mid3.ogg' = 1,
- 'sound/machines/tcomms/tcomms_mid4.ogg' = 1,
- 'sound/machines/tcomms/tcomms_mid5.ogg' = 1,
- 'sound/machines/tcomms/tcomms_mid6.ogg' = 1,
- 'sound/machines/tcomms/tcomms_mid7.ogg' = 1,
+ 'sound/machines/tcomms/tcomms_mid1.ogg',
+ 'sound/machines/tcomms/tcomms_mid2.ogg',
+ 'sound/machines/tcomms/tcomms_mid3.ogg',
+ 'sound/machines/tcomms/tcomms_mid4.ogg',
+ 'sound/machines/tcomms/tcomms_mid5.ogg',
+ 'sound/machines/tcomms/tcomms_mid6.ogg',
+ 'sound/machines/tcomms/tcomms_mid7.ogg',
)
mid_length = 1.8 SECONDS
extra_range = -8
@@ -116,7 +132,10 @@
start_sound = 'sound/machines/computer/computer_start.ogg'
start_length = 7.2 SECONDS
start_volume = 10
- mid_sounds = list('sound/machines/computer/computer_mid1.ogg' = 1, 'sound/machines/computer/computer_mid2.ogg' = 1)
+ mid_sounds = list(
+ 'sound/machines/computer/computer_mid1.ogg',
+ 'sound/machines/computer/computer_mid2.ogg',
+ )
mid_length = 1.8 SECONDS
end_sound = 'sound/machines/computer/computer_end.ogg'
end_volume = 10
@@ -141,7 +160,12 @@
falloff_exponent = 20
/datum/looping_sound/firealarm
- mid_sounds = list('sound/machines/fire_alarm/FireAlarm1.ogg' = 1,'sound/machines/fire_alarm/FireAlarm2.ogg' = 1,'sound/machines/fire_alarm/FireAlarm3.ogg' = 1,'sound/machines/fire_alarm/FireAlarm4.ogg' = 1)
+ mid_sounds = list(
+ 'sound/machines/fire_alarm/FireAlarm1.ogg',
+ 'sound/machines/fire_alarm/FireAlarm2.ogg',
+ 'sound/machines/fire_alarm/FireAlarm3.ogg',
+ 'sound/machines/fire_alarm/FireAlarm4.ogg',
+ )
mid_length = 2.4 SECONDS
volume = 30
@@ -151,30 +175,30 @@
falloff_exponent = 5
/datum/looping_sound/boiling
- mid_sounds = list('sound/effects/bubbles/bubbles2.ogg' = 1)
+ mid_sounds = list('sound/effects/bubbles/bubbles2.ogg')
mid_length = 7 SECONDS
volume = 25
/datum/looping_sound/typing
mid_sounds = list(
- 'sound/machines/terminal/terminal_button01.ogg' = 1,
- 'sound/machines/terminal/terminal_button02.ogg' = 1,
- 'sound/machines/terminal/terminal_button03.ogg' = 1,
- 'sound/machines/terminal/terminal_button04.ogg' = 1,
- 'sound/machines/terminal/terminal_button05.ogg' = 1,
- 'sound/machines/terminal/terminal_button06.ogg' = 1,
- 'sound/machines/terminal/terminal_button07.ogg' = 1,
- 'sound/machines/terminal/terminal_button08.ogg' = 1,
+ 'sound/machines/terminal/terminal_button01.ogg',
+ 'sound/machines/terminal/terminal_button02.ogg',
+ 'sound/machines/terminal/terminal_button03.ogg',
+ 'sound/machines/terminal/terminal_button04.ogg',
+ 'sound/machines/terminal/terminal_button05.ogg',
+ 'sound/machines/terminal/terminal_button06.ogg',
+ 'sound/machines/terminal/terminal_button07.ogg',
+ 'sound/machines/terminal/terminal_button08.ogg',
)
mid_length = 0.3 SECONDS
/datum/looping_sound/soup
mid_sounds = list(
- 'sound/effects/soup_boil/soup_boil1.ogg' = 1,
- 'sound/effects/soup_boil/soup_boil2.ogg' = 1,
- 'sound/effects/soup_boil/soup_boil3.ogg' = 1,
- 'sound/effects/soup_boil/soup_boil4.ogg' = 1,
- 'sound/effects/soup_boil/soup_boil5.ogg' = 1,
+ 'sound/effects/soup_boil/soup_boil1.ogg',
+ 'sound/effects/soup_boil/soup_boil2.ogg',
+ 'sound/effects/soup_boil/soup_boil3.ogg',
+ 'sound/effects/soup_boil/soup_boil4.ogg',
+ 'sound/effects/soup_boil/soup_boil5.ogg',
)
mid_length = 3 SECONDS
volume = 80
diff --git a/code/datums/quirks/negative_quirks/chronic_illness.dm b/code/datums/quirks/negative_quirks/chronic_illness.dm
index f0809b55d2b0f..04a676965dbe0 100644
--- a/code/datums/quirks/negative_quirks/chronic_illness.dm
+++ b/code/datums/quirks/negative_quirks/chronic_illness.dm
@@ -1,11 +1,11 @@
/datum/quirk/item_quirk/chronic_illness
- name = "Chronic Illness"
- desc = "You have a chronic illness that requires constant medication to keep under control."
+ name = "Eradicative Chronic Illness"
+ desc = "You have an anomalous chronic illness that requires constant medication to keep under control, or else causes timestream correction."
icon = FA_ICON_DISEASE
value = -12
- gain_text = span_danger("You feel a bit off today.")
- lose_text = span_notice("You feel a bit better today.")
- medical_record_text = "Patient has a chronic illness that requires constant medication to keep under control."
+ gain_text = span_danger("You feel like you are fading away...")
+ lose_text = span_notice("You suddenly feel more substantial.")
+ medical_record_text = "Patient has an anomalous chronic illness that requires constant medication to keep under control."
hardcore_value = 12
mail_goodies = list(/obj/item/storage/pill_bottle/sansufentanyl)
diff --git a/code/datums/quirks/neutral_quirks/evil.dm b/code/datums/quirks/neutral_quirks/evil.dm
index 6753a7d034cfd..99e805ccd7c9b 100644
--- a/code/datums/quirks/neutral_quirks/evil.dm
+++ b/code/datums/quirks/neutral_quirks/evil.dm
@@ -1,3 +1,4 @@
+/* DOPPLER EDIT REMOVAL - Fundamentally Evil replaced with Unholy Aura and Bad Vibes
/datum/quirk/evil
name = "Fundamentally Evil"
desc = "Where you would have a soul is but an ink-black void. While you are committed to maintaining your social standing, \
@@ -10,3 +11,4 @@
lose_text = span_notice("You suddenly care more about others and their needs.")
medical_record_text = "Patient has passed all our social fitness tests with flying colours, but had trouble on the empathy tests."
mail_goodies = list(/obj/item/food/grown/citrus/lemon)
+*/
diff --git a/code/datums/status_effects/debuffs/debuffs.dm b/code/datums/status_effects/debuffs/debuffs.dm
index 414a90ecf8fb3..34eae9515ff7c 100644
--- a/code/datums/status_effects/debuffs/debuffs.dm
+++ b/code/datums/status_effects/debuffs/debuffs.dm
@@ -704,7 +704,7 @@
/datum/status_effect/go_away
id = "go_away"
- duration = 100
+ duration = 10 SECONDS
status_type = STATUS_EFFECT_REPLACE
tick_interval = 0.2 SECONDS
alert_type = /atom/movable/screen/alert/status_effect/go_away
@@ -720,6 +720,17 @@
var/turf/T = get_step(owner, direction)
owner.forceMove(T)
+/datum/status_effect/go_away/deletes_mob
+ id = "go_away_deletes_mob"
+ duration = INFINITY
+
+/datum/status_effect/go_away/deluxe/on_creation(mob/living/new_owner, set_duration)
+ . = ..()
+ RegisterSignal(new_owner, COMSIG_MOVABLE_Z_CHANGED, PROC_REF(wipe_bozo))
+
+/datum/status_effect/go_away/deluxe/proc/wipe_bozo()
+ qdel(owner)
+
/atom/movable/screen/alert/status_effect/go_away
name = "TO THE STARS AND BEYOND!"
desc = "I must go, my people need me!"
diff --git a/code/datums/status_effects/neutral.dm b/code/datums/status_effects/neutral.dm
index 933ae42f6e051..d080de4d2c014 100644
--- a/code/datums/status_effects/neutral.dm
+++ b/code/datums/status_effects/neutral.dm
@@ -691,17 +691,14 @@
icon_state = "shower_regen_catgirl"
/atom/movable/screen/alert/status_effect/washing_regen/dislike
- name = "Washing"
desc = "This water feels dirty..."
icon_state = "shower_regen_dirty"
/atom/movable/screen/alert/status_effect/washing_regen/bloody_like
- name = "Washing"
desc = "Mhhhmmmm... the crimson red drops of life. How delightful."
icon_state = "shower_regen_blood_happy"
/atom/movable/screen/alert/status_effect/washing_regen/bloody_dislike
- name = "Washing"
desc = "Is that... blood? What the fuck!"
icon_state = "shower_regen_blood_bad"
diff --git a/code/datums/status_effects/stacking_effect.dm b/code/datums/status_effects/stacking_effect.dm
index acdefd8290c88..d3f128045ce00 100644
--- a/code/datums/status_effects/stacking_effect.dm
+++ b/code/datums/status_effects/stacking_effect.dm
@@ -119,8 +119,7 @@
return FALSE
status_overlay = mutable_appearance(overlay_file, "[overlay_state][stacks]")
status_underlay = mutable_appearance(underlay_file, "[underlay_state][stacks]")
- var/icon/I = icon(owner.icon, owner.icon_state, owner.dir)
- var/icon_height = I.Height()
+ var/icon_height = owner.get_cached_height()
status_overlay.pixel_x = -owner.pixel_x
status_overlay.pixel_y = FLOOR(icon_height * 0.25, 1)
status_overlay.transform = matrix() * (icon_height/ICON_SIZE_Y) //scale the status's overlay size based on the target's icon size
diff --git a/code/datums/votes/map_vote.dm b/code/datums/votes/map_vote.dm
index b4f938a42e451..c5f90f16d1361 100644
--- a/code/datums/votes/map_vote.dm
+++ b/code/datums/votes/map_vote.dm
@@ -10,6 +10,7 @@
default_choices = SSmap_vote.get_valid_map_vote_choices()
/datum/vote/map_vote/create_vote()
+ default_choices = SSmap_vote.get_valid_map_vote_choices()
. = ..()
if(!.)
return FALSE
diff --git a/code/datums/world_topic.dm b/code/datums/world_topic.dm
index a9971f6068c98..2a4af3a781aa0 100644
--- a/code/datums/world_topic.dm
+++ b/code/datums/world_topic.dm
@@ -26,7 +26,7 @@
var/require_comms_key = FALSE
/datum/world_topic/proc/TryRun(list/input)
- key_valid = config && (CONFIG_GET(string/comms_key) == input["key"])
+ key_valid = (CONFIG_GET(string/comms_key) == input["key"]) && CONFIG_GET(string/comms_key) && input["key"]
input -= "key"
if(require_comms_key && !key_valid)
. = "Bad Key"
diff --git a/code/datums/wounds/burns.dm b/code/datums/wounds/burns.dm
index a4ef3bd7b7df7..b3d04c2148573 100644
--- a/code/datums/wounds/burns.dm
+++ b/code/datums/wounds/burns.dm
@@ -159,7 +159,7 @@
if(4 to INFINITY)
bandage_condition = "clean"
- condition += " underneath a dressing of [bandage_condition] [limb.current_gauze.name]"
+ condition += " underneath a dressing of [bandage_condition] [limb.current_gauze.name]."
else
switch(infestation)
if(WOUND_INFECTION_MODERATE to WOUND_INFECTION_SEVERE)
@@ -223,25 +223,13 @@
new burn common procs
*/
-/// if someone is using ointment or mesh on our burns
-/datum/wound/burn/flesh/proc/ointmentmesh(obj/item/stack/medical/I, mob/user)
- user.visible_message(span_notice("[user] begins applying [I] to [victim]'s [limb.plaintext_zone]..."), span_notice("You begin applying [I] to [user == victim ? "your" : "[victim]'s"] [limb.plaintext_zone]..."))
- if (I.amount <= 0)
+/// Checks if the wound is in a state that ointment or flesh will help
+/datum/wound/burn/flesh/proc/can_be_ointmented_or_meshed()
+ if(infestation > 0 || sanitization < infestation)
return TRUE
- if(!do_after(user, (user == victim ? I.self_delay : I.other_delay), target = victim, extra_checks = CALLBACK(src, PROC_REF(still_exists))))
+ if(flesh_damage > 0 || flesh_healing <= flesh_damage)
return TRUE
-
- limb.heal_damage(I.heal_brute, I.heal_burn)
- user.visible_message(span_green("[user] applies [I] to [victim]."), span_green("You apply [I] to [user == victim ? "your" : "[victim]'s"] [limb.plaintext_zone]."))
- I.use(1)
- sanitization += I.sanitization
- flesh_healing += I.flesh_regeneration
-
- if((infestation <= 0 || sanitization >= infestation) && (flesh_damage <= 0 || flesh_healing > flesh_damage))
- to_chat(user, span_notice("You've done all you can with [I], now you must wait for the flesh on [victim]'s [limb.plaintext_zone] to recover."))
- return TRUE
- else
- return try_treating(I, user)
+ return FALSE
/// Paramedic UV penlights
/datum/wound/burn/flesh/proc/uv(obj/item/flashlight/pen/paramedic/I, mob/user)
@@ -258,15 +246,7 @@
return TRUE
/datum/wound/burn/flesh/treat(obj/item/I, mob/user)
- if(istype(I, /obj/item/stack/medical/ointment))
- return ointmentmesh(I, user)
- else if(istype(I, /obj/item/stack/medical/mesh))
- var/obj/item/stack/medical/mesh/mesh_check = I
- if(!mesh_check.is_open)
- to_chat(user, span_warning("You need to open [mesh_check] first."))
- return
- return ointmentmesh(mesh_check, user)
- else if(istype(I, /obj/item/flashlight/pen/paramedic))
+ if(istype(I, /obj/item/flashlight/pen/paramedic))
return uv(I, user)
// people complained about burns not healing on stasis beds, so in addition to checking if it's cured, they also get the special ability to very slowly heal on stasis beds if they have the healing effects stored
diff --git a/code/datums/wounds/pierce.dm b/code/datums/wounds/pierce.dm
index 66cb36915e6d2..bb4bc85d8e95f 100644
--- a/code/datums/wounds/pierce.dm
+++ b/code/datums/wounds/pierce.dm
@@ -80,11 +80,12 @@
if (!victim || HAS_TRAIT(victim, TRAIT_STASIS))
return
- set_blood_flow(min(blood_flow, WOUND_SLASH_MAX_BLOODFLOW))
if(limb.can_bleed())
if(victim.bodytemperature < (BODYTEMP_NORMAL - 10))
adjust_blood_flow(-0.1 * seconds_per_tick)
+ if(QDELETED(src))
+ return
if(SPT_PROB(2.5, seconds_per_tick))
to_chat(victim, span_notice("You feel the [LOWER_TEXT(name)] in your [limb.plaintext_zone] firming up from the cold!"))
@@ -92,15 +93,16 @@
adjust_blood_flow(0.25 * seconds_per_tick) // old heparin used to just add +2 bleed stacks per tick, this adds 0.5 bleed flow to all open cuts which is probably even stronger as long as you can cut them first
if(limb.current_gauze)
- adjust_blood_flow(-limb.current_gauze.absorption_rate * gauzed_clot_rate * seconds_per_tick)
- limb.current_gauze.absorption_capacity -= limb.current_gauze.absorption_rate * seconds_per_tick
+ var/gauze_power = limb.current_gauze.absorption_rate
+ limb.seep_gauze(gauze_power * seconds_per_tick)
+ adjust_blood_flow(-gauze_power * gauzed_clot_rate * seconds_per_tick)
- if(blood_flow <= 0)
- qdel(src)
-
-/datum/wound/pierce/bleed/on_stasis(seconds_per_tick, times_fired)
+/datum/wound/pierce/bleed/adjust_blood_flow(adjust_by, minimum)
. = ..()
- if(blood_flow <= 0)
+ if(blood_flow > WOUND_MAX_BLOODFLOW)
+ blood_flow = WOUND_MAX_BLOODFLOW
+ if(blood_flow <= 0 && !QDELETED(src))
+ to_chat(victim, span_green("The holes on your [limb.plaintext_zone] have [!limb.can_bleed() ? "healed up" : "stopped bleeding"]!"))
qdel(src)
/datum/wound/pierce/bleed/check_grab_treatments(obj/item/I, mob/user)
@@ -108,9 +110,7 @@
return TRUE
/datum/wound/pierce/bleed/treat(obj/item/I, mob/user)
- if(istype(I, /obj/item/stack/medical/suture))
- return suture(I, user)
- else if(I.tool_behaviour == TOOL_CAUTERY || I.get_temperature())
+ if(I.tool_behaviour == TOOL_CAUTERY || I.get_temperature())
return tool_cauterize(I, user)
/datum/wound/pierce/bleed/on_xadone(power)
@@ -123,32 +123,6 @@
. = ..()
adjust_blood_flow(-0.025 * reac_volume) // 20u * 0.05 = -1 blood flow, less than with slashes but still good considering smaller bleed rates
-/// If someone is using a suture to close this puncture
-/datum/wound/pierce/bleed/proc/suture(obj/item/stack/medical/suture/I, mob/user)
- var/self_penalty_mult = (user == victim ? 1.4 : 1)
- var/treatment_delay = base_treat_time * self_penalty_mult
-
- if(HAS_TRAIT(src, TRAIT_WOUND_SCANNED))
- treatment_delay *= 0.5
- user.visible_message(span_notice("[user] begins expertly stitching [victim]'s [limb.plaintext_zone] with [I]..."), span_notice("You begin stitching [user == victim ? "your" : "[victim]'s"] [limb.plaintext_zone] with [I], keeping the holo-image information in mind..."))
- else
- user.visible_message(span_notice("[user] begins stitching [victim]'s [limb.plaintext_zone] with [I]..."), span_notice("You begin stitching [user == victim ? "your" : "[victim]'s"] [limb.plaintext_zone] with [I]..."))
-
- if(!do_after(user, treatment_delay, target = victim, extra_checks = CALLBACK(src, PROC_REF(still_exists))))
- return TRUE
- var/bleeding_wording = (!limb.can_bleed() ? "holes" : "bleeding")
- user.visible_message(span_green("[user] stitches up some of the [bleeding_wording] on [victim]."), span_green("You stitch up some of the [bleeding_wording] on [user == victim ? "yourself" : "[victim]"]."))
- var/blood_sutured = I.stop_bleeding / self_penalty_mult
- adjust_blood_flow(-blood_sutured)
- limb.heal_damage(I.heal_brute, I.heal_burn)
- I.use(1)
-
- if(blood_flow > 0)
- return try_treating(I, user)
- else
- to_chat(user, span_green("You successfully close the hole in [user == victim ? "your" : "[victim]'s"] [limb.plaintext_zone]."))
- return TRUE
-
/// If someone is using either a cautery tool or something with heat to cauterize this pierce
/datum/wound/pierce/bleed/proc/tool_cauterize(obj/item/I, mob/user)
@@ -163,9 +137,13 @@
else
user.visible_message(span_danger("[user] begins cauterizing [victim]'s [limb.plaintext_zone] with [I]..."), span_warning("You begin cauterizing [user == victim ? "your" : "[victim]'s"] [limb.plaintext_zone] with [I]..."))
+ playsound(user, 'sound/items/handling/surgery/cautery1.ogg', 75, TRUE)
+
if(!do_after(user, treatment_delay, target = victim, extra_checks = CALLBACK(src, PROC_REF(still_exists))))
return TRUE
+ playsound(user, 'sound/items/handling/surgery/cautery2.ogg', 75, TRUE)
+
var/bleeding_wording = (!limb.can_bleed() ? "holes" : "bleeding")
user.visible_message(span_green("[user] cauterizes some of the [bleeding_wording] on [victim]."), span_green("You cauterize some of the [bleeding_wording] on [victim]."))
victim.apply_damage(2 + severity, BURN, limb, wound_bonus = CANT_WOUND)
diff --git a/code/datums/wounds/slash.dm b/code/datums/wounds/slash.dm
index 00581c12af579..2ec5700e03c09 100644
--- a/code/datums/wounds/slash.dm
+++ b/code/datums/wounds/slash.dm
@@ -36,9 +36,6 @@
/// Once the blood flow drops below minimum_flow, we demote it to this type of wound. If there's none, we're all better
var/demotes_to
- /// The maximum flow we've had so far
- var/highest_flow
-
/// A bad system I'm using to track the worst scar we earned (since we can demote, we want the biggest our wound has been, not what it was when it was cured (probably moderate))
var/datum/scar/highest_scar
@@ -133,44 +130,23 @@
return BLOOD_FLOW_INCREASING
/datum/wound/slash/flesh/handle_process(seconds_per_tick, times_fired)
-
if (!victim || HAS_TRAIT(victim, TRAIT_STASIS))
return
// in case the victim has the NOBLOOD trait, the wound will simply not clot on its own
if(limb.can_bleed())
- set_blood_flow(min(blood_flow, WOUND_SLASH_MAX_BLOODFLOW))
+ if(clot_rate > 0)
+ adjust_blood_flow(-clot_rate * seconds_per_tick)
+ if(QDELETED(src))
+ return
if(HAS_TRAIT(victim, TRAIT_BLOODY_MESS))
adjust_blood_flow(0.25) // old heparin used to just add +2 bleed stacks per tick, this adds 0.5 bleed flow to all open cuts which is probably even stronger as long as you can cut them first
- //gauze always reduces blood flow, even for non bleeders
if(limb.current_gauze)
- if(clot_rate > 0)
- adjust_blood_flow(-clot_rate * seconds_per_tick)
- adjust_blood_flow(-limb.current_gauze.absorption_rate * seconds_per_tick)
- limb.seep_gauze(limb.current_gauze.absorption_rate * seconds_per_tick)
- //otherwise, only clot if it's a bleeder
- else if(limb.can_bleed())
- adjust_blood_flow(-clot_rate * seconds_per_tick)
-
- if(blood_flow > highest_flow)
- highest_flow = blood_flow
-
- if(blood_flow < minimum_flow)
- if(demotes_to)
- replace_wound(new demotes_to)
- else
- to_chat(victim, span_green("The cut on your [limb.plaintext_zone] has [!limb.can_bleed() ? "healed up" : "stopped bleeding"]!"))
- qdel(src)
-
-/datum/wound/slash/flesh/on_stasis(seconds_per_tick, times_fired)
- if(blood_flow >= minimum_flow)
- return
- if(demotes_to)
- replace_wound(new demotes_to)
- return
- qdel(src)
+ var/gauze_power = limb.current_gauze.absorption_rate
+ limb.seep_gauze(gauze_power * seconds_per_tick)
+ adjust_blood_flow(-gauze_power * seconds_per_tick)
/* BEWARE, THE BELOW NONSENSE IS MADNESS. bones.dm looks more like what I have in mind and is sufficiently clean, don't pay attention to this messiness */
@@ -185,8 +161,6 @@
return las_cauterize(I, user)
else if(I.tool_behaviour == TOOL_CAUTERY || I.get_temperature())
return tool_cauterize(I, user)
- else if(istype(I, /obj/item/stack/medical/suture))
- return suture(I, user)
/datum/wound/slash/flesh/try_handling(mob/living/user)
if(user.pulling != victim || !HAS_TRAIT(user, TRAIT_WOUND_LICKER) || !victim.try_inject(user, injection_flags = INJECT_TRY_SHOW_ERROR_MESSAGE))
@@ -212,8 +186,7 @@
/// if a felinid is licking this cut to reduce bleeding
/datum/wound/slash/flesh/proc/lick_wounds(mob/living/carbon/human/user)
// transmission is one way patient -> felinid since google said cat saliva is antiseptic or whatever, and also because felinids are already risking getting beaten for this even without people suspecting they're spreading a deathvirus
- for(var/i in victim.diseases)
- var/datum/disease/iter_disease = i
+ for(var/datum/disease/iter_disease as anything in victim.diseases)
if(iter_disease.spread_flags & (DISEASE_SPREAD_SPECIAL | DISEASE_SPREAD_NON_CONTAGIOUS))
continue
user.ForceContractDisease(iter_disease)
@@ -225,18 +198,28 @@
user.visible_message(span_notice("[user] licks the wounds on [victim]'s [limb.plaintext_zone]."), span_notice("You lick some of the wounds on [victim]'s [limb.plaintext_zone]"), ignored_mobs=victim)
to_chat(victim, span_green("[user] licks the wounds on your [limb.plaintext_zone]!"))
+ var/mob/victim_stored = victim
adjust_blood_flow(-0.5)
if(blood_flow > minimum_flow)
try_handling(user)
else if(demotes_to)
- to_chat(user, span_green("You successfully lower the severity of [victim]'s cuts."))
+ to_chat(user, span_green("You successfully lower the severity of [user == victim_stored ? "your" : "[victim_stored]'s"] cuts."))
-/datum/wound/slash/flesh/on_xadone(power)
+/datum/wound/slash/flesh/adjust_blood_flow(adjust_by, minimum)
. = ..()
+ if(blood_flow > WOUND_MAX_BLOODFLOW)
+ blood_flow = WOUND_MAX_BLOODFLOW
+ if(blood_flow < minimum_flow && !QDELETED(src))
+ if(demotes_to)
+ replace_wound(new demotes_to)
+ else
+ to_chat(victim, span_green("The cut on your [limb.plaintext_zone] has [!limb.can_bleed() ? "healed up" : "stopped bleeding"]!"))
+ qdel(src)
- if (limb) // parent can cause us to be removed, so its reasonable to check if we're still applied
- adjust_blood_flow(-0.03 * power) // i think it's like a minimum of 3 power, so .09 blood_flow reduction per tick is pretty good for 0 effort
+/datum/wound/slash/flesh/on_xadone(power)
+ . = ..()
+ adjust_blood_flow(-0.03 * power) // i think it's like a minimum of 3 power, so .09 blood_flow reduction per tick is pretty good for 0 effort
/datum/wound/slash/flesh/on_synthflesh(reac_volume)
. = ..()
@@ -271,50 +254,29 @@
else
user.visible_message(span_danger("[user] begins cauterizing [victim]'s [limb.plaintext_zone] with [I]..."), span_warning("You begin cauterizing [user == victim ? "your" : "[victim]'s"] [limb.plaintext_zone] with [I]..."))
+ playsound(user, 'sound/items/handling/surgery/cautery1.ogg', 75, TRUE)
+
if(!do_after(user, treatment_delay, target = victim, extra_checks = CALLBACK(src, PROC_REF(still_exists))))
return
+
+ playsound(user, 'sound/items/handling/surgery/cautery2.ogg', 75, TRUE)
+
var/bleeding_wording = (!limb.can_bleed() ? "cuts" : "bleeding")
user.visible_message(span_green("[user] cauterizes some of the [bleeding_wording] on [victim]."), span_green("You cauterize some of the [bleeding_wording] on [victim]."))
victim.apply_damage(2 + severity, BURN, limb, wound_bonus = CANT_WOUND)
if(prob(30))
victim.emote("scream")
var/blood_cauterized = (0.6 / (self_penalty_mult * improv_penalty_mult))
+ var/mob/victim_stored = victim
adjust_blood_flow(-blood_cauterized)
if(blood_flow > minimum_flow)
return try_treating(I, user)
else if(demotes_to)
- to_chat(user, span_green("You successfully lower the severity of [user == victim ? "your" : "[victim]'s"] cuts."))
+ to_chat(user, span_green("You successfully lower the severity of [user == victim_stored ? "your" : "[victim_stored]'s"] cuts."))
return TRUE
return FALSE
-/// If someone is using a suture to close this cut
-/datum/wound/slash/flesh/proc/suture(obj/item/stack/medical/suture/I, mob/user)
- var/self_penalty_mult = (user == victim ? 1.4 : 1)
- var/treatment_delay = base_treat_time * self_penalty_mult
-
- if(HAS_TRAIT(src, TRAIT_WOUND_SCANNED))
- treatment_delay *= 0.5
- user.visible_message(span_notice("[user] begins expertly stitching [victim]'s [limb.plaintext_zone] with [I]..."), span_notice("You begin stitching [user == victim ? "your" : "[victim]'s"] [limb.plaintext_zone] with [I], keeping the holo-image information in mind..."))
- else
- user.visible_message(span_notice("[user] begins stitching [victim]'s [limb.plaintext_zone] with [I]..."), span_notice("You begin stitching [user == victim ? "your" : "[victim]'s"] [limb.plaintext_zone] with [I]..."))
-
- if(!do_after(user, treatment_delay, target = victim, extra_checks = CALLBACK(src, PROC_REF(still_exists))))
- return TRUE
- var/bleeding_wording = (!limb.can_bleed() ? "cuts" : "bleeding")
- user.visible_message(span_green("[user] stitches up some of the [bleeding_wording] on [victim]."), span_green("You stitch up some of the [bleeding_wording] on [user == victim ? "yourself" : "[victim]"]."))
- var/blood_sutured = I.stop_bleeding / self_penalty_mult
- adjust_blood_flow(-blood_sutured)
- limb.heal_damage(I.heal_brute, I.heal_burn)
- I.use(1)
-
- if(blood_flow > minimum_flow)
- return try_treating(I, user)
- else if(demotes_to)
- to_chat(user, span_green("You successfully lower the severity of [user == victim ? "your" : "[victim]'s"] cuts."))
- return TRUE
- return TRUE
-
/datum/wound/slash/get_limb_examine_description()
return span_warning("The flesh on this limb appears badly lacerated.")
diff --git a/code/game/area/areas.dm b/code/game/area/areas.dm
index b988fa0b6daa8..450a29d9cbd35 100644
--- a/code/game/area/areas.dm
+++ b/code/game/area/areas.dm
@@ -80,8 +80,8 @@
var/power_light = TRUE
var/power_environ = TRUE
var/power_apc_charge = TRUE
-
- var/has_gravity = FALSE
+ /// The default gravity for the area
+ var/default_gravity = ZERO_GRAVITY
var/parallax_movedir = 0
diff --git a/code/game/area/areas/away_content.dm b/code/game/area/areas/away_content.dm
index 5ff0143c0a1a9..648ef4c8d3160 100644
--- a/code/game/area/areas/away_content.dm
+++ b/code/game/area/areas/away_content.dm
@@ -8,7 +8,7 @@ Unused icons for new areas are "awaycontent1" ~ "awaycontent30"
name = "Strange Location"
icon = 'icons/area/areas_away_missions.dmi'
icon_state = "away"
- has_gravity = STANDARD_GRAVITY
+ default_gravity = STANDARD_GRAVITY
ambience_index = AMBIENCE_AWAY
sound_environment = SOUND_ENVIRONMENT_ROOM
area_flags = UNIQUE_AREA
@@ -34,7 +34,7 @@ Unused icons for new areas are "awaycontent1" ~ "awaycontent30"
static_lighting = FALSE
base_lighting_alpha = 255
area_flags = UNIQUE_AREA|NOTELEPORT
- has_gravity = STANDARD_GRAVITY
+ default_gravity = STANDARD_GRAVITY
/area/awaymission/secret
area_flags = UNIQUE_AREA|NOTELEPORT|HIDDEN_AREA
@@ -46,7 +46,7 @@ Unused icons for new areas are "awaycontent1" ~ "awaycontent30"
outdoors = TRUE
/area/awaymission/secret/unpowered/no_grav
- has_gravity = FALSE
+ default_gravity = ZERO_GRAVITY
/area/awaymission/secret/fullbright
static_lighting = FALSE
diff --git a/code/game/area/areas/centcom.dm b/code/game/area/areas/centcom.dm
index 28b3496c4e18a..022e0d91c177f 100644
--- a/code/game/area/areas/centcom.dm
+++ b/code/game/area/areas/centcom.dm
@@ -7,7 +7,7 @@
icon_state = "centcom"
static_lighting = TRUE
requires_power = FALSE
- has_gravity = STANDARD_GRAVITY
+ default_gravity = STANDARD_GRAVITY
area_flags = UNIQUE_AREA | NOTELEPORT
flags_1 = NONE
@@ -151,7 +151,7 @@
icon_state = "wizards_den"
static_lighting = TRUE
requires_power = FALSE
- has_gravity = STANDARD_GRAVITY
+ default_gravity = STANDARD_GRAVITY
area_flags = UNIQUE_AREA | NOTELEPORT
flags_1 = NONE
@@ -164,7 +164,7 @@
area_flags = UNIQUE_AREA | NOTELEPORT
static_lighting = FALSE
base_lighting_alpha = 255
- has_gravity = STANDARD_GRAVITY
+ default_gravity = STANDARD_GRAVITY
flags_1 = NONE
//Syndicates
@@ -172,7 +172,7 @@
name = "Syndicate Mothership"
icon_state = "syndie-ship"
requires_power = FALSE
- has_gravity = STANDARD_GRAVITY
+ default_gravity = STANDARD_GRAVITY
area_flags = UNIQUE_AREA | NOTELEPORT
flags_1 = NONE
ambience_index = AMBIENCE_DANGER
@@ -217,7 +217,7 @@
base_lighting_alpha = 255
requires_power = FALSE
- has_gravity = STANDARD_GRAVITY
+ default_gravity = STANDARD_GRAVITY
flags_1 = NONE
area_flags = BLOCK_SUICIDE | UNIQUE_AREA
@@ -228,7 +228,7 @@
requires_power = FALSE
static_lighting = FALSE
base_lighting_alpha = 255
- has_gravity = STANDARD_GRAVITY
+ default_gravity = STANDARD_GRAVITY
flags_1 = NONE
area_flags = UNIQUE_AREA | NOTELEPORT | NO_DEATH_MESSAGE | BLOCK_SUICIDE
@@ -273,7 +273,7 @@
name = "\improper Asteroid"
icon_state = "asteroid"
requires_power = FALSE
- has_gravity = STANDARD_GRAVITY
+ default_gravity = STANDARD_GRAVITY
area_flags = UNIQUE_AREA
ambience_index = AMBIENCE_MINING
flags_1 = CAN_BE_DIRTY_1
diff --git a/code/game/area/areas/mining.dm b/code/game/area/areas/mining.dm
index be6db4e077fec..4254e23d62bc6 100644
--- a/code/game/area/areas/mining.dm
+++ b/code/game/area/areas/mining.dm
@@ -2,7 +2,7 @@
/area/mine
icon = 'icons/area/areas_station.dmi'
icon_state = "mining"
- has_gravity = STANDARD_GRAVITY
+ default_gravity = STANDARD_GRAVITY
area_flags = VALID_TERRITORY | UNIQUE_AREA | FLORA_ALLOWED | CULT_PERMITTED
ambient_buzz = 'sound/ambience/lavaland/magma.ogg'
@@ -130,7 +130,7 @@
/area/lavaland
icon = 'icons/area/areas_station.dmi'
icon_state = "mining"
- has_gravity = STANDARD_GRAVITY
+ default_gravity = STANDARD_GRAVITY
flags_1 = NONE
area_flags = VALID_TERRITORY | UNIQUE_AREA | FLORA_ALLOWED
sound_environment = SOUND_AREA_LAVALAND
@@ -190,7 +190,7 @@
/area/icemoon
icon = 'icons/area/areas_station.dmi'
icon_state = "mining"
- has_gravity = STANDARD_GRAVITY
+ default_gravity = STANDARD_GRAVITY
flags_1 = NONE
area_flags = UNIQUE_AREA | FLORA_ALLOWED
ambience_index = AMBIENCE_ICEMOON
diff --git a/code/game/area/areas/misc.dm b/code/game/area/areas/misc.dm
index 8aa6adc738c4a..00ce0d6f46410 100644
--- a/code/game/area/areas/misc.dm
+++ b/code/game/area/areas/misc.dm
@@ -5,13 +5,12 @@
requires_power = TRUE
always_unpowered = TRUE
static_lighting = FALSE
-
base_lighting_alpha = 255
base_lighting_color = COLOR_STARLIGHT
power_light = FALSE
power_equip = FALSE
power_environ = FALSE
- area_flags = UNIQUE_AREA
+ area_flags = UNIQUE_AREA|NO_GRAVITY
outdoors = TRUE
ambience_index = AMBIENCE_SPACE
flags_1 = CAN_BE_DIRTY_1
@@ -22,7 +21,6 @@
/area/space/nearstation
icon_state = "space_near"
- area_flags = UNIQUE_AREA
static_lighting = TRUE
base_lighting_alpha = 0
base_lighting_color = null
@@ -33,12 +31,12 @@
requires_power = FALSE
static_lighting = FALSE
base_lighting_alpha = 255
- has_gravity = STANDARD_GRAVITY
+ default_gravity = STANDARD_GRAVITY
ambient_buzz = null
/area/misc/testroom
requires_power = FALSE
- has_gravity = STANDARD_GRAVITY
+ default_gravity = STANDARD_GRAVITY
// Mobs should be able to see inside the testroom
static_lighting = FALSE
base_lighting_alpha = 255
diff --git a/code/game/area/areas/ruins/_ruins.dm b/code/game/area/areas/ruins/_ruins.dm
index 69e17d365caf8..46cf851b1ebd1 100644
--- a/code/game/area/areas/ruins/_ruins.dm
+++ b/code/game/area/areas/ruins/_ruins.dm
@@ -4,7 +4,7 @@
name = "\improper Unexplored Location"
icon = 'icons/area/areas_ruins.dmi'
icon_state = "ruins"
- has_gravity = STANDARD_GRAVITY
+ default_gravity = STANDARD_GRAVITY
area_flags = HIDDEN_AREA | BLOBS_ALLOWED | UNIQUE_AREA
ambience_index = AMBIENCE_RUINS
flags_1 = CAN_BE_DIRTY_1
@@ -14,7 +14,7 @@
always_unpowered = TRUE
/area/ruin/unpowered/no_grav
- has_gravity = FALSE
+ default_gravity = ZERO_GRAVITY
/area/ruin/powered
requires_power = FALSE
diff --git a/code/game/area/areas/ruins/space.dm b/code/game/area/areas/ruins/space.dm
index 2e25aeb2d6fd4..7484e208872de 100644
--- a/code/game/area/areas/ruins/space.dm
+++ b/code/game/area/areas/ruins/space.dm
@@ -1,7 +1,7 @@
//Space Ruin Parents
/area/ruin/space
- has_gravity = FALSE
+ default_gravity = ZERO_GRAVITY
area_flags = UNIQUE_AREA
/area/ruin/space/unpowered
@@ -11,7 +11,7 @@
power_environ = FALSE
/area/ruin/space/has_grav
- has_gravity = STANDARD_GRAVITY
+ default_gravity = STANDARD_GRAVITY
/area/ruin/space/has_grav/powered
requires_power = FALSE
@@ -529,18 +529,18 @@
/area/ruin/space/djstation
name = "\improper Ruskie DJ Station"
icon_state = "DJ"
- has_gravity = STANDARD_GRAVITY
+ default_gravity = STANDARD_GRAVITY
/area/ruin/space/djstation/solars
name = "\improper DJ Station Solars"
icon_state = "DJ"
area_flags = UNIQUE_AREA
- has_gravity = STANDARD_GRAVITY
+ default_gravity = ZERO_GRAVITY
/area/ruin/space/djstation/service
name = "\improper DJ Station Service"
icon_state = "DJ"
- has_gravity = STANDARD_GRAVITY
+ default_gravity = STANDARD_GRAVITY
//ABANDONED TELEPORTER
diff --git a/code/game/area/areas/shuttles.dm b/code/game/area/areas/shuttles.dm
index f128805924fe8..490a4d60c2ebb 100644
--- a/code/game/area/areas/shuttles.dm
+++ b/code/game/area/areas/shuttles.dm
@@ -6,7 +6,7 @@
name = "Shuttle"
requires_power = FALSE
static_lighting = TRUE
- has_gravity = STANDARD_GRAVITY
+ default_gravity = STANDARD_GRAVITY
always_unpowered = FALSE
// Loading the same shuttle map at a different time will produce distinct area instances.
area_flags = NONE
@@ -256,7 +256,7 @@
// ----------- Arena Shuttle
/area/shuttle/shuttle_arena
name = "arena"
- has_gravity = STANDARD_GRAVITY
+ default_gravity = STANDARD_GRAVITY
requires_power = FALSE
/obj/effect/forcefield/arena_shuttle
diff --git a/code/game/area/areas/station/science.dm b/code/game/area/areas/station/science.dm
index 2787a4ff87feb..40bdd3786571e 100644
--- a/code/game/area/areas/station/science.dm
+++ b/code/game/area/areas/station/science.dm
@@ -126,4 +126,7 @@
/area/station/science/ordnance/bomb
name = "\improper Ordnance Bomb Site"
icon_state = "ord_boom"
- area_flags = BLOBS_ALLOWED | UNIQUE_AREA | CULT_PERMITTED
+ area_flags = BLOBS_ALLOWED | UNIQUE_AREA | CULT_PERMITTED | NO_GRAVITY
+
+/area/station/science/ordnance/bomb/planet
+ area_flags = /area/station/science/ordnance/bomb::area_flags & ~NO_GRAVITY
diff --git a/code/game/area/areas/station/solars.dm b/code/game/area/areas/station/solars.dm
index 57376e2fb17be..8d3a81420e7d7 100644
--- a/code/game/area/areas/station/solars.dm
+++ b/code/game/area/areas/station/solars.dm
@@ -5,11 +5,12 @@
/area/station/solars
icon_state = "panels"
requires_power = FALSE
- area_flags = UNIQUE_AREA
+ area_flags = UNIQUE_AREA|NO_GRAVITY
flags_1 = NONE
ambience_index = AMBIENCE_ENGI
airlock_wires = /datum/wires/airlock/engineering
sound_environment = SOUND_AREA_SPACE
+ default_gravity = ZERO_GRAVITY
/area/station/solars/fore
name = "\improper Fore Solar Array"
@@ -40,10 +41,20 @@
name = "\improper Starboard Bow Solar Array"
icon_state = "panelsFS"
+/area/station/solars/starboard/fore/asteriod
+ name = "\improper Starboard Bow Asteriod Solar Array"
+ icon_state = "panelsFS"
+ area_flags = UNIQUE_AREA // solar areas directly on asteriod have gravity
+
/area/station/solars/port
name = "\improper Port Solar Array"
icon_state = "panelsP"
+/area/station/solars/port/asteriod
+ name = "\improper Port Asteriod Solar Array"
+ icon_state = "panelsP"
+ area_flags = UNIQUE_AREA // solar areas directly on asteriod have gravity
+
/area/station/solars/port/aft
name = "\improper Port Quarter Solar Array"
icon_state = "panelsAP"
diff --git a/code/game/atom/_atom.dm b/code/game/atom/_atom.dm
index ba2d2d358fd47..1d5bc1f936929 100644
--- a/code/game/atom/_atom.dm
+++ b/code/game/atom/_atom.dm
@@ -763,6 +763,7 @@
* Gravity situations:
* * No gravity if you're not in a turf
* * No gravity if this atom is in is a space turf
+ * * No gravity if the area has NO_GRAVITY flag (space, ordnance bomb site, nearstation, solars)
* * Gravity if the area it's in always has gravity
* * Gravity if there's a gravity generator on the z level
* * Gravity if the Z level has an SSMappingTrait for ZTRAIT_GRAVITY
@@ -790,7 +791,7 @@
var/area/turf_area = gravity_turf.loc
- return !gravity_turf.force_no_gravity && (SSmapping.gravity_by_z_level[gravity_turf.z] || turf_area.has_gravity)
+ return (!gravity_turf.force_no_gravity && !(turf_area.area_flags & NO_GRAVITY)) && (SSmapping.gravity_by_z_level[gravity_turf.z] || turf_area.default_gravity)
/**
* Used to set something as 'open' if it's being used as a supplypod
diff --git a/code/game/atom/atom_color.dm b/code/game/atom/atom_color.dm
index 2508e86f44d90..4b20a00422806 100644
--- a/code/game/atom/atom_color.dm
+++ b/code/game/atom/atom_color.dm
@@ -3,6 +3,9 @@
A System that gives finer control over which atom colour to colour the atom with.
The "highest priority" one is always displayed as opposed to the default of
"whichever was set last is displayed"
+
+ It can also be used for color filters, since some effects (using non-RGB space matrices)
+ are impossible to achieve with just the color variable
*/
/atom
@@ -12,6 +15,8 @@
* its inherent color, the colored paint applied on it, special color effect etc...
*/
var/list/atom_colours
+ /// Currently used color filter - cached because its applied to all of our overlays because BYOND is horrific
+ var/cached_color_filter
///Adds an instance of colour_type to the atom's atom_colours list
/atom/proc/add_atom_colour(coloration, colour_priority)
@@ -22,7 +27,12 @@
return
if(colour_priority > atom_colours.len)
return
- atom_colours[colour_priority] = coloration
+ var/color_type = ATOM_COLOR_TYPE_NORMAL
+ if (islist(coloration))
+ var/list/color_matrix = coloration
+ if (color_matrix["type"] == "color")
+ color_type = ATOM_COLOR_TYPE_FILTER
+ atom_colours[colour_priority] = list(coloration, color_type)
update_atom_colour()
@@ -32,8 +42,13 @@
return
if(colour_priority > atom_colours.len)
return
- if(coloration && atom_colours[colour_priority] != coloration)
- return //if we don't have the expected color (for a specific priority) to remove, do nothing
+ if(coloration && atom_colours[colour_priority])
+ if (atom_colours[colour_priority][ATOM_COLOR_TYPE_INDEX] == ATOM_COLOR_TYPE_NORMAL)
+ if (atom_colours[colour_priority][ATOM_COLOR_VALUE_INDEX] != coloration)
+ return //if we don't have the expected color (for a specific priority) to remove, do nothing
+ else
+ if (!islist(coloration) || !compare_list(coloration, atom_colours[colour_priority][ATOM_COLOR_VALUE_INDEX]["color"]))
+ return
atom_colours[colour_priority] = null
update_atom_colour()
@@ -43,29 +58,69 @@
*/
/atom/proc/is_atom_colour(looking_for_color, min_priority_index = 1, max_priority_index = COLOUR_PRIORITY_AMOUNT)
// make sure uppertext hex strings don't mess with LOWER_TEXT hex strings
- looking_for_color = LOWER_TEXT(looking_for_color)
+ if (!islist(looking_for_color))
+ looking_for_color = LOWER_TEXT(looking_for_color)
if(!LAZYLEN(atom_colours))
// no atom colors list has been set up, just check the color var
- return LOWER_TEXT(color) == looking_for_color
+ if (!islist(color))
+ return LOWER_TEXT(color) == looking_for_color
+ if (!islist(looking_for_color))
+ return FALSE
+ return compare_list(color, looking_for_color)
for(var/i in min_priority_index to max_priority_index)
- if(LOWER_TEXT(atom_colours[i]) == looking_for_color)
+ if (!atom_colours[i])
+ continue
+
+ if (!islist(looking_for_color))
+ if (islist(atom_colours[i][ATOM_COLOR_VALUE_INDEX]))
+ continue
+
+ if (LOWER_TEXT(atom_colours[i][ATOM_COLOR_VALUE_INDEX]) == looking_for_color)
+ return TRUE
+
+ continue
+
+ var/compared_matrix = atom_colours[i][ATOM_COLOR_VALUE_INDEX]
+ if (atom_colours[i][ATOM_COLOR_TYPE_INDEX] == ATOM_COLOR_TYPE_FILTER)
+ compared_matrix = atom_colours[i][ATOM_COLOR_VALUE_INDEX]["color"]
+
+ if (compare_list(looking_for_color, compared_matrix))
return TRUE
return FALSE
///Resets the atom's color to null, and then sets it to the highest priority colour available
/atom/proc/update_atom_colour()
+ var/old_filter = cached_color_filter
color = null
- if(!atom_colours)
+ cached_color_filter = null
+ remove_filter(ATOM_PRIORITY_COLOR_FILTER)
+ REMOVE_KEEP_TOGETHER(src, ATOM_COLOR_TRAIT)
+
+ if (!atom_colours)
+ if (old_filter)
+ update_appearance()
return
- for(var/checked_color in atom_colours)
- if(islist(checked_color))
- var/list/color_list = checked_color
- if(color_list.len)
- color = color_list
- return
- else if(checked_color)
- color = checked_color
- return
+
+ for (var/list/checked_color in atom_colours)
+ if (checked_color[ATOM_COLOR_TYPE_INDEX] == ATOM_COLOR_TYPE_FILTER)
+ add_filter(ATOM_PRIORITY_COLOR_FILTER, ATOM_PRIORITY_COLOR_FILTER_PRIORITY, checked_color[ATOM_COLOR_VALUE_INDEX])
+ cached_color_filter = checked_color[ATOM_COLOR_VALUE_INDEX]
+ break
+
+ if (length(checked_color[ATOM_COLOR_VALUE_INDEX]))
+ color = checked_color[ATOM_COLOR_VALUE_INDEX]
+ break
+
+ ADD_KEEP_TOGETHER(src, ATOM_COLOR_TRAIT)
+ if (cached_color_filter != old_filter)
+ update_appearance()
+
+/// Same as update_atom_color, but simplifies overlay coloring
+/atom/proc/color_atom_overlay(mutable_appearance/overlay)
+ overlay.color = color
+ if (!cached_color_filter)
+ return overlay
+ return filter_appearance_recursive(overlay, cached_color_filter)
diff --git a/code/game/atoms_movable.dm b/code/game/atoms_movable.dm
index 48166f3651631..b4a780f9adfbb 100644
--- a/code/game/atoms_movable.dm
+++ b/code/game/atoms_movable.dm
@@ -1140,6 +1140,7 @@
pulledby.stop_pulling()
var/same_loc = oldloc == destination
+ var/movement_successful = TRUE
var/area/old_area = get_area(oldloc)
var/area/destarea = get_area(destination)
var/movement_dir = get_dir(src, destination)
@@ -1148,7 +1149,13 @@
loc = destination
- if(!same_loc)
+ if(!same_loc && loc == oldloc)
+ // when attempting to move an atom A into an atom B which already contains A, BYOND seems
+ // to silently refuse to move A to the new loc. This can really break stuff (see #77067)
+ stack_trace("Attempt to move [src] to [destination] was rejected by BYOND, possibly due to cyclic contents")
+ movement_successful = FALSE
+
+ if(movement_successful && !same_loc)
if(is_multi_tile && isturf(destination))
var/list/new_locs = block(
destination,
@@ -1177,7 +1184,7 @@
if(destarea && old_area != destarea)
destarea.Entered(src, old_area)
- . = TRUE
+ . = movement_successful
//If no destination, move the atom into nullspace (don't do this unless you know what you're doing)
else
@@ -1275,12 +1282,12 @@
/// Only moves the object if it's under no gravity
/// Accepts the direction to move, if the push should be instant, and an optional parameter to fine tune the start delay
/// Drift force determines how much acceleration should be applied. Controlled cap, if set, will ensure that if the object was moving slower than the cap before, it cannot accelerate past the cap from this move.
-/atom/movable/proc/newtonian_move(inertia_angle, instant = FALSE, start_delay = 0, drift_force = 1 NEWTONS, controlled_cap = null)
+/atom/movable/proc/newtonian_move(inertia_angle, instant = FALSE, start_delay = 0, drift_force = 1 NEWTONS, controlled_cap = null, force_loop = TRUE)
if(!isturf(loc) || Process_Spacemove(angle2dir(inertia_angle), continuous_move = TRUE))
return FALSE
if (!isnull(drift_handler))
- if (drift_handler.newtonian_impulse(inertia_angle, start_delay, drift_force, controlled_cap))
+ if (drift_handler.newtonian_impulse(inertia_angle, start_delay, drift_force, controlled_cap, force_loop))
return TRUE
new /datum/drift_handler(src, inertia_angle, instant, start_delay, drift_force)
diff --git a/code/game/data_huds.dm b/code/game/data_huds.dm
index ceaf03685532a..f583efc0ca5b8 100644
--- a/code/game/data_huds.dm
+++ b/code/game/data_huds.dm
@@ -171,8 +171,7 @@ Medical HUD! Basic mode needs suit sensors on.
return
holder.icon_state = "hud[RoundHealth(src)]"
- var/icon/I = icon(icon, icon_state, dir)
- holder.pixel_y = I.Height() - ICON_SIZE_Y
+ holder.pixel_y = get_cached_height() - ICON_SIZE_Y
//for carbon suit sensors
/mob/living/carbon/med_hud_set_health()
@@ -184,8 +183,7 @@ Medical HUD! Basic mode needs suit sensors on.
if (isnull(holder))
return
- var/icon/I = icon(icon, icon_state, dir)
- holder.pixel_y = I.Height() - ICON_SIZE_Y
+ holder.pixel_y = get_cached_height() - ICON_SIZE_Y
if(stat == DEAD || (HAS_TRAIT(src, TRAIT_FAKEDEATH)))
holder.icon_state = "huddead"
else
@@ -196,9 +194,8 @@ Medical HUD! Basic mode needs suit sensors on.
if (isnull(holder))
return
- var/icon/I = icon(icon, icon_state, dir)
var/virus_threat = check_virus()
- holder.pixel_y = I.Height() - ICON_SIZE_Y
+ holder.pixel_y = get_cached_height() - ICON_SIZE_Y
if(HAS_TRAIT(src, TRAIT_XENO_HOST))
holder.icon_state = "hudxeno"
else if(stat == DEAD || (HAS_TRAIT(src, TRAIT_FAKEDEATH)))
@@ -242,8 +239,7 @@ FAN HUDs! For identifying other fans on-sight.
/mob/living/carbon/human/proc/fan_hud_set_fandom()
var/image/holder = hud_list[FAN_HUD]
- var/icon/hud_icon = icon(icon, icon_state, dir)
- holder.pixel_y = hud_icon.Height() - ICON_SIZE_Y
+ holder.pixel_y = get_cached_height() - ICON_SIZE_Y
holder.icon_state = "hudfan_no"
var/obj/item/clothing/under/undershirt = w_uniform
@@ -273,8 +269,7 @@ Security HUDs! Basic mode shows only the job.
/mob/living/carbon/human/proc/sec_hud_set_ID()
var/image/holder = hud_list[ID_HUD]
- var/icon/I = icon(icon, icon_state, dir)
- holder.pixel_y = I.Height() - ICON_SIZE_Y
+ holder.pixel_y = get_cached_height() - ICON_SIZE_Y
var/sechud_icon_state = wear_id?.get_sechud_job_icon_state()
if(!sechud_icon_state || HAS_TRAIT(src, TRAIT_UNKNOWN))
sechud_icon_state = "hudno_id"
@@ -294,24 +289,21 @@ Security HUDs! Basic mode shows only the job.
switch(security_slot)
if(1)
holder = hud_list[IMPSEC_FIRST_HUD]
- var/icon/IC = icon(icon, icon_state, dir)
- holder.pixel_y = IC.Height() - ICON_SIZE_Y
+ holder.pixel_y = get_cached_height() - ICON_SIZE_Y
holder.icon_state = current_implant.hud_icon_state
set_hud_image_active(IMPSEC_FIRST_HUD)
security_slot++
if(2) //Theoretically if we somehow get multiple sec implants, whatever the most recently implanted implant is will take over the 2nd position
holder = hud_list[IMPSEC_SECOND_HUD]
- var/icon/IC = icon(icon, icon_state, dir)
- holder.pixel_y = IC.Height() - ICON_SIZE_Y
+ holder.pixel_y = get_cached_height() - ICON_SIZE_Y
holder.pixel_x = initial(holder.pixel_x) + (ICON_SIZE_X / 4 - 1) //Adds an offset that mirrors the hud blip to the other side of the mob.
holder.icon_state = current_implant.hud_icon_state
set_hud_image_active(IMPSEC_SECOND_HUD)
if(HAS_TRAIT(src, TRAIT_MINDSHIELD))
holder = hud_list[IMPLOYAL_HUD]
- var/icon/IC = icon(icon, icon_state, dir)
- holder.pixel_y = IC.Height() - ICON_SIZE_Y
+ holder.pixel_y = get_cached_height() - ICON_SIZE_Y
holder.icon_state = "hud_imp_loyal"
set_hud_image_active(IMPLOYAL_HUD)
@@ -321,8 +313,7 @@ Security HUDs! Basic mode shows only the job.
return
var/image/holder = hud_list[WANTED_HUD]
- var/icon/sec_icon = icon(icon, icon_state, dir)
- holder.pixel_y = sec_icon.Height() - ICON_SIZE_Y
+ holder.pixel_y = get_cached_height() - ICON_SIZE_Y
if (HAS_TRAIT(src, TRAIT_ALWAYS_WANTED))
holder.icon_state = "hudwanted"
@@ -398,8 +389,7 @@ Diagnostic HUDs!
//Sillycone hooks
/mob/living/silicon/proc/diag_hud_set_health()
var/image/holder = hud_list[DIAG_HUD]
- var/icon/I = icon(icon, icon_state, dir)
- holder.pixel_y = I.Height() - ICON_SIZE_Y
+ holder.pixel_y = get_cached_height() - ICON_SIZE_Y
if(stat == DEAD)
holder.icon_state = "huddiagdead"
else
@@ -407,8 +397,7 @@ Diagnostic HUDs!
/mob/living/silicon/proc/diag_hud_set_status()
var/image/holder = hud_list[DIAG_STAT_HUD]
- var/icon/I = icon(icon, icon_state, dir)
- holder.pixel_y = I.Height() - ICON_SIZE_Y
+ holder.pixel_y = get_cached_height() - ICON_SIZE_Y
switch(stat)
if(CONSCIOUS)
holder.icon_state = "hudstat"
@@ -420,8 +409,7 @@ Diagnostic HUDs!
//Borgie battery tracking!
/mob/living/silicon/robot/proc/diag_hud_set_borgcell()
var/image/holder = hud_list[DIAG_BATT_HUD]
- var/icon/I = icon(icon, icon_state, dir)
- holder.pixel_y = I.Height() - ICON_SIZE_Y
+ holder.pixel_y = get_cached_height() - ICON_SIZE_Y
if(cell)
var/chargelvl = (cell.charge/cell.maxcharge)
holder.icon_state = "hudbatt[RoundDiagBar(chargelvl)]"
@@ -431,8 +419,7 @@ Diagnostic HUDs!
//borg-AI shell tracking
/mob/living/silicon/robot/proc/diag_hud_set_aishell() //Shows tracking beacons on the mech
var/image/holder = hud_list[DIAG_TRACK_HUD]
- var/icon/I = icon(icon, icon_state, dir)
- holder.pixel_y = I.Height() - ICON_SIZE_Y
+ holder.pixel_y = get_cached_height() - ICON_SIZE_Y
if(!shell) //Not an AI shell
holder.icon_state = null
set_hud_image_inactive(DIAG_TRACK_HUD)
@@ -446,8 +433,7 @@ Diagnostic HUDs!
//AI side tracking of AI shell control
/mob/living/silicon/ai/proc/diag_hud_set_deployed() //Shows tracking beacons on the mech
var/image/holder = hud_list[DIAG_TRACK_HUD]
- var/icon/I = icon(icon, icon_state, dir)
- holder.pixel_y = I.Height() - ICON_SIZE_Y
+ holder.pixel_y = get_cached_height() - ICON_SIZE_Y
if(!deployed_shell)
holder.icon_state = null
set_hud_image_inactive(DIAG_TRACK_HUD)
@@ -460,15 +446,13 @@ Diagnostic HUDs!
~~~~~~~~~~~~~~~~~~~~~*/
/obj/vehicle/sealed/mecha/proc/diag_hud_set_mechhealth()
var/image/holder = hud_list[DIAG_MECH_HUD]
- var/icon/I = icon(icon, icon_state, dir)
- holder.pixel_y = I.Height() - ICON_SIZE_Y
+ holder.pixel_y = get_cached_height() - ICON_SIZE_Y
holder.icon_state = "huddiag[RoundDiagBar(atom_integrity/max_integrity)]"
/obj/vehicle/sealed/mecha/proc/diag_hud_set_mechcell()
var/image/holder = hud_list[DIAG_BATT_HUD]
- var/icon/I = icon(icon, icon_state, dir)
- holder.pixel_y = I.Height() - ICON_SIZE_Y
+ holder.pixel_y = get_cached_height() - ICON_SIZE_Y
if(cell)
var/chargelvl = cell.charge/cell.maxcharge
holder.icon_state = "hudbatt[RoundDiagBar(chargelvl)]"
@@ -477,8 +461,7 @@ Diagnostic HUDs!
/obj/vehicle/sealed/mecha/proc/diag_hud_set_mechstat()
var/image/holder = hud_list[DIAG_STAT_HUD]
- var/icon/I = icon(icon, icon_state, dir)
- holder.pixel_y = I.Height() - ICON_SIZE_Y
+ holder.pixel_y = get_cached_height() - ICON_SIZE_Y
if(internal_damage)
holder.icon_state = "hudwarn"
set_hud_image_active(DIAG_STAT_HUD)
@@ -489,8 +472,7 @@ Diagnostic HUDs!
///Shows tracking beacons on the mech
/obj/vehicle/sealed/mecha/proc/diag_hud_set_mechtracking()
var/image/holder = hud_list[DIAG_TRACK_HUD]
- var/icon/I = icon(icon, icon_state, dir)
- holder.pixel_y = I.Height() - ICON_SIZE_Y
+ holder.pixel_y = get_cached_height() - ICON_SIZE_Y
var/new_icon_state //This var exists so that the holder's icon state is set only once in the event of multiple mech beacons.
for(var/obj/item/mecha_parts/mecha_tracking/T in trackers)
if(T.ai_beacon) //Beacon with AI uplink
@@ -503,8 +485,7 @@ Diagnostic HUDs!
///Shows inbuilt camera on the mech; if the camera's view range was affected by an EMP, shows a red blip while it's affected
/obj/vehicle/sealed/mecha/proc/diag_hud_set_camera()
var/image/holder = hud_list[DIAG_CAMERA_HUD]
- var/icon/I = icon(icon, icon_state, dir)
- holder.pixel_y = I.Height() - ICON_SIZE_Y
+ holder.pixel_y = get_cached_height() - ICON_SIZE_Y
if(chassis_camera?.is_emp_scrambled)
holder.icon_state = "hudcamera_empd"
return
@@ -515,14 +496,12 @@ Diagnostic HUDs!
~~~~~~~~~~*/
/mob/living/simple_animal/bot/proc/diag_hud_set_bothealth()
var/image/holder = hud_list[DIAG_HUD]
- var/icon/I = icon(icon, icon_state, dir)
- holder.pixel_y = I.Height() - ICON_SIZE_Y
+ holder.pixel_y = get_cached_height() - ICON_SIZE_Y
holder.icon_state = "huddiag[RoundDiagBar(health/maxHealth)]"
/mob/living/simple_animal/bot/proc/diag_hud_set_botstat() //On (With wireless on or off), Off, EMP'ed
var/image/holder = hud_list[DIAG_STAT_HUD]
- var/icon/I = icon(icon, icon_state, dir)
- holder.pixel_y = I.Height() - ICON_SIZE_Y
+ holder.pixel_y = get_cached_height() - ICON_SIZE_Y
if(bot_mode_flags & BOT_MODE_ON)
holder.icon_state = "hudstat"
else if(stat) //Generally EMP causes this
@@ -532,8 +511,7 @@ Diagnostic HUDs!
/mob/living/simple_animal/bot/proc/diag_hud_set_botmode() //Shows a bot's current operation
var/image/holder = hud_list[DIAG_BOT_HUD]
- var/icon/I = icon(icon, icon_state, dir)
- holder.pixel_y = I.Height() - ICON_SIZE_Y
+ holder.pixel_y = get_cached_height() - ICON_SIZE_Y
if(client) //If the bot is player controlled, it will not be following mode logic!
holder.icon_state = "hudsentient"
return
@@ -554,13 +532,13 @@ Diagnostic HUDs!
/mob/living/simple_animal/bot/mulebot/proc/diag_hud_set_mulebotcell()
var/image/holder = hud_list[DIAG_BATT_HUD]
- var/icon/I = icon(icon, icon_state, dir)
- holder.pixel_y = I.Height() - ICON_SIZE_Y
+ holder.pixel_y = get_cached_height() - ICON_SIZE_Y
if(cell)
var/chargelvl = (cell.charge/cell.maxcharge)
holder.icon_state = "hudbatt[RoundDiagBar(chargelvl)]"
else
holder.icon_state = "hudnobatt"
+
/*~~~~~~~~~~~~
Airlocks!
~~~~~~~~~~~~~*/
@@ -579,3 +557,21 @@ Diagnostic HUDs!
holder.loc = get_turf(src)
SET_PLANE(holder,ABOVE_LIGHTING_PLANE,src)
set_hud_image_active(MALF_APC_HUD)
+
+#define CACHED_WIDTH_INDEX "width"
+#define CACHED_HEIGHT_INDEX "height"
+
+/atom/proc/get_cached_width()
+ if (isnull(icon))
+ return 0
+ var/list/dimensions = get_icon_dimensions(icon)
+ return dimensions[CACHED_WIDTH_INDEX]
+
+/atom/proc/get_cached_height()
+ if (isnull(icon))
+ return 0
+ var/list/dimensions = get_icon_dimensions(icon)
+ return dimensions[CACHED_HEIGHT_INDEX]
+
+#undef CACHED_WIDTH_INDEX
+#undef CACHED_HEIGHT_INDEX
diff --git a/code/game/machinery/_machinery.dm b/code/game/machinery/_machinery.dm
index 4e2b27797c293..b962a902f17f4 100644
--- a/code/game/machinery/_machinery.dm
+++ b/code/game/machinery/_machinery.dm
@@ -96,61 +96,62 @@
layer = BELOW_OBJ_LAYER //keeps shit coming out of the machine from ending up underneath it.
flags_ricochet = RICOCHET_HARD
receive_ricochet_chance_mod = 0.3
-
anchored = TRUE
interaction_flags_atom = INTERACT_ATOM_ATTACK_HAND | INTERACT_ATOM_UI_INTERACT
blocks_emissive = EMISSIVE_BLOCK_GENERIC
initial_language_holder = /datum/language_holder/speaking_machine
+ armor_type = /datum/armor/obj_machinery
+ ///see code/__DEFINES/stat.dm
var/machine_stat = NONE
+ ///see code/__DEFINES/machines.dm
var/use_power = IDLE_POWER_USE
- //0 = dont use power
- //1 = use idle_power_usage
- //2 = use active_power_usage
///the amount of static power load this machine adds to its area's power_usage list when use_power = IDLE_POWER_USE
var/idle_power_usage = BASE_MACHINE_IDLE_CONSUMPTION
///the amount of static power load this machine adds to its area's power_usage list when use_power = ACTIVE_POWER_USE
var/active_power_usage = BASE_MACHINE_ACTIVE_CONSUMPTION
///the current amount of static power usage this machine is taking from its area
var/static_power_usage = 0
+ //AREA_USAGE_EQUIP,AREA_USAGE_ENVIRON or AREA_USAGE_LIGHT
var/power_channel = AREA_USAGE_EQUIP
- //AREA_USAGE_EQUIP,AREA_USAGE_ENVIRON or AREA_USAGE_LIGHT
///A combination of factors such as having power, not being broken and so on. Boolean.
var/is_operational = TRUE
- var/wire_compatible = FALSE
-
- var/list/component_parts = null //list of all the parts used to build it, if made from certain kinds of frames.
+ ///list of all the parts used to build it, if made from certain kinds of frames.
+ var/list/component_parts = null
+ ///Is the machines maintainence panel open.
var/panel_open = FALSE
+ ///Is the machine open or closed
var/state_open = FALSE
- var/critical_machine = FALSE //If this machine is critical to station operation and should have the area be excempted from power failures.
- var/list/occupant_typecache //if set, turned into typecache in Initialize, other wise, defaults to mob/living typecache
+ ///If this machine is critical to station operation and should have the area be excempted from power failures.
+ var/critical_machine = FALSE
+ ///if set, turned into typecache in Initialize, other wise, defaults to mob/living typecache
+ var/list/occupant_typecache
+ ///The mob that is sealed inside the machine
var/atom/movable/occupant = null
- /// Viable flags to go here are START_PROCESSING_ON_INIT, or START_PROCESSING_MANUALLY. See code\__DEFINES\machines.dm for more information on these flags.
+ ///Viable flags to go here are START_PROCESSING_ON_INIT, or START_PROCESSING_MANUALLY. See code\__DEFINES\machines.dm for more information on these flags.
var/processing_flags = START_PROCESSING_ON_INIT
- /// What subsystem this machine will use, which is generally SSmachines or SSfastprocess. By default all machinery use SSmachines. This fires a machine's process() roughly every 2 seconds.
+ ///What subsystem this machine will use, which is generally SSmachines or SSfastprocess. By default all machinery use SSmachines. This fires a machine's process() roughly every 2 seconds.
var/subsystem_type = /datum/controller/subsystem/machines
- var/obj/item/circuitboard/circuit // Circuit to be created and inserted when the machinery is created
-
- var/interaction_flags_machine = INTERACT_MACHINE_WIRES_IF_OPEN|INTERACT_MACHINE_ALLOW_SILICON|INTERACT_MACHINE_OPEN_SILICON
- var/fair_market_price = 69
- var/market_verb = "Customer"
+ ///Circuit to be created and inserted when the machinery is created
+ var/obj/item/circuitboard/circuit
+ ///See code/DEFINES/interaction_flags.dm
+ var/interaction_flags_machine = INTERACT_MACHINE_WIRES_IF_OPEN | INTERACT_MACHINE_ALLOW_SILICON | INTERACT_MACHINE_OPEN_SILICON
+ ///The department we are paying to use this machine
var/payment_department = ACCOUNT_ENG
-
+ ///Used in NAP violation, pay fine
+ var/fair_market_price = 5
///Is this machine currently in the atmos machinery queue?
var/atmos_processing = FALSE
- /// world.time of last use by [/mob/living]
+ ///world.time of last use by [/mob/living]
var/last_used_time = 0
- /// Mobtype of last user. Typecast to [/mob/living] for initial() usage
+ ///Mobtype of last user. Typecast to [/mob/living] for initial() usage
var/mob/living/last_user_mobtype
- /// Do we want to hook into on_enter_area and on_exit_area?
- /// Disables some optimizations
+ ///Do we want to hook into on_enter_area and on_exit_area?
+ ///Disables some optimizations
var/always_area_sensitive = FALSE
- ///Multiplier for power consumption.
- var/machine_power_rectifier = 1
- /// What was our power state the last time we updated its appearance?
- /// TRUE for on, FALSE for off, -1 for never checked
+ ///What was our power state the last time we updated its appearance?
+ ///TRUE for on, FALSE for off, -1 for never checked
var/appearance_power_state = -1
- armor_type = /datum/armor/obj_machinery
/datum/armor/obj_machinery
melee = 25
@@ -187,15 +188,6 @@
SHOULD_NOT_OVERRIDE(TRUE)
post_machine_initialize()
-/obj/machinery/Destroy(force)
- SSmachines.unregister_machine(src)
- end_processing()
-
- clear_components()
- unset_static_power()
-
- return ..()
-
/**
* Called in LateInitialize meant to be the machine replacement to it
* This sets up power for the machine and requires parent be called,
@@ -203,13 +195,25 @@
* This is the proc to override if you want to do anything in LateInitialize.
*/
/obj/machinery/proc/post_machine_initialize()
+ PROTECTED_PROC(TRUE)
SHOULD_CALL_PARENT(TRUE)
+
power_change()
if(use_power == NO_POWER_USE)
return
update_current_power_usage()
setup_area_power_relationship()
+
+/obj/machinery/Destroy(force)
+ SSmachines.unregister_machine(src)
+ end_processing()
+
+ clear_components()
+ unset_static_power()
+
+ return ..()
+
/**
* proc to call when the machine starts to require power after a duration of not requiring power
* sets up power related connections to its area if it exists and becomes area sensitive
@@ -271,19 +275,16 @@
SEND_SIGNAL(src, COMSIG_MACHINERY_SET_OCCUPANT, new_occupant)
occupant = new_occupant
-/// Helper proc for telling a machine to start processing with the subsystem type that is located in its `subsystem_type` var.
+/// Helper proc for telling a machine to start processing
/obj/machinery/proc/begin_processing()
var/datum/controller/subsystem/processing/subsystem = locate(subsystem_type) in Master.subsystems
START_PROCESSING(subsystem, src)
-/// Helper proc for telling a machine to stop processing with the subsystem type that is located in its `subsystem_type` var.
+/// Helper proc for telling a machine to stop processing
/obj/machinery/proc/end_processing()
var/datum/controller/subsystem/processing/subsystem = locate(subsystem_type) in Master.subsystems
STOP_PROCESSING(subsystem, src)
-/obj/machinery/proc/locate_machinery()
- return
-
///Early process for machines added to SSmachines.processing_early to prioritize power draw
/obj/machinery/proc/process_early()
set waitfor = FALSE
@@ -303,6 +304,8 @@
///Called when we want to change the value of the machine_stat variable. Holds bitflags.
/obj/machinery/proc/set_machine_stat(new_value)
+ SHOULD_NOT_OVERRIDE(TRUE)
+
if(new_value == machine_stat)
return
. = machine_stat
@@ -312,6 +315,8 @@
///Called when the value of `machine_stat` changes, so we can react to it.
/obj/machinery/proc/on_set_machine_stat(old_value)
+ PROTECTED_PROC(TRUE)
+
//From off to on.
if((old_value & (NOPOWER|BROKEN|MAINT)) && !(machine_stat & (NOPOWER|BROKEN|MAINT)))
set_is_operational(TRUE)
@@ -335,13 +340,6 @@
remove_all_languages(source = LANGUAGE_EMP)
grant_random_uncommon_language(source = LANGUAGE_EMP)
-/obj/machinery/base_item_interaction(mob/living/user, obj/item/tool, list/modifiers)
- //takes priority in case material container or other atoms that hook onto item interaction signals won't give it a chance
- if(istype(tool, /obj/item/storage/part_replacer))
- return tool.interact_with_atom(src, user, modifiers)
-
- return ..()
-
/**
* Opens the machine.
*
@@ -496,6 +494,7 @@
///internal proc that removes all static power usage from the current area
/obj/machinery/proc/unset_static_power()
SHOULD_NOT_OVERRIDE(TRUE)
+
var/old_usage = static_power_usage
var/area/our_area = get_area(src)
@@ -582,6 +581,8 @@
///Called when we want to change the value of the `is_operational` variable. Boolean.
/obj/machinery/proc/set_is_operational(new_value)
+ SHOULD_NOT_OVERRIDE(TRUE)
+
if(new_value == is_operational)
return
. = is_operational
@@ -591,10 +592,14 @@
///Called when the value of `is_operational` changes, so we can react to it.
/obj/machinery/proc/on_set_is_operational(old_value)
+ PROTECTED_PROC(TRUE)
+
return
///Called when we want to change the value of the `panel_open` variable. Boolean.
/obj/machinery/proc/set_panel_open(new_value)
+ SHOULD_NOT_OVERRIDE(TRUE)
+
if(panel_open == new_value)
return
var/old_value = panel_open
@@ -603,10 +608,14 @@
///Called when the value of `panel_open` changes, so we can react to it.
/obj/machinery/proc/on_set_panel_open(old_value)
+ PROTECTED_PROC(TRUE)
+
return
/// Toggles the panel_open var. Defined for convienience
/obj/machinery/proc/toggle_panel_open()
+ SHOULD_NOT_OVERRIDE(TRUE)
+
set_panel_open(!panel_open)
/obj/machinery/can_interact(mob/user)
@@ -662,7 +671,14 @@
return TRUE // If we passed all of those checks, woohoo! We can interact with this machine.
+/**
+ * Checks for NAP non aggression principle, an anarcho capitalist event triggered by admins
+ * where using machines cost money
+ */
/obj/machinery/proc/check_nap_violations()
+ PROTECTED_PROC(TRUE)
+ SHOULD_NOT_OVERRIDE(TRUE)
+
if(!SSeconomy.full_ancap)
return TRUE
if(!occupant || state_open)
@@ -670,16 +686,16 @@
var/mob/living/occupant_mob = occupant
var/obj/item/card/id/occupant_id = occupant_mob.get_idcard(TRUE)
if(!occupant_id)
- say("[market_verb] NAP Violation: No ID card found.")
+ say("Customer NAP Violation: No ID card found.")
nap_violation(occupant_mob)
return FALSE
var/datum/bank_account/insurance = occupant_id.registered_account
if(!insurance)
- say("[market_verb] NAP Violation: No bank account found.")
+ say("Customer NAP Violation: No bank account found.")
nap_violation(occupant_mob)
return FALSE
if(!insurance.adjust_money(-fair_market_price))
- say("[market_verb] NAP Violation: Unable to pay.")
+ say("Customer NAP Violation: Unable to pay.")
nap_violation(occupant_mob)
return FALSE
var/datum/bank_account/department_account = SSeconomy.get_dep_account(payment_department)
@@ -687,7 +703,15 @@
department_account.adjust_money(fair_market_price)
return TRUE
+/**
+ * Actions to take in case of NAP violation
+ * Arguments
+ *
+ * * mob/violator - the mob who violated the NAP aggrement
+ */
/obj/machinery/proc/nap_violation(mob/violator)
+ PROTECTED_PROC(TRUE)
+
return
////////////////////////////////////////////////////////////////////////////////////////////
@@ -795,10 +819,14 @@
if(SEND_SIGNAL(user, COMSIG_TRY_USE_MACHINE, src) & COMPONENT_CANT_USE_MACHINE_TOOLS)
return ITEM_INTERACT_BLOCKING
+ //takes priority in case material container or other atoms that hook onto item interaction signals won't give it a chance
+ if(istype(tool, /obj/item/storage/part_replacer))
+ update_last_used(user)
+ return tool.interact_with_atom(src, user, modifiers)
+
. = ..()
if(.)
update_last_used(user)
- return .
/obj/machinery/_try_interact(mob/user)
if((interaction_flags_machine & INTERACT_MACHINE_WIRES_IF_OPEN) && panel_open && (attempt_wire_interaction(user) == WIRE_INTERACTION_BLOCK))
@@ -832,6 +860,8 @@
SEND_SIGNAL(src, COMSIG_MACHINERY_REFRESH_PARTS)
/obj/machinery/proc/default_pry_open(obj/item/crowbar, close_after_pry = FALSE, open_density = FALSE, closed_density = TRUE)
+ PROTECTED_PROC(TRUE)
+
. = !(state_open || panel_open || is_operational) && crowbar.tool_behaviour == TOOL_CROWBAR
if(!.)
return
@@ -842,6 +872,8 @@
close_machine(density_to_set = closed_density)
/obj/machinery/proc/default_deconstruction_crowbar(obj/item/crowbar, ignore_panel = 0, custom_deconstruct = FALSE)
+ PROTECTED_PROC(TRUE)
+
. = (panel_open || ignore_panel) && crowbar.tool_behaviour == TOOL_CROWBAR
if(!. || custom_deconstruct)
return
diff --git a/code/game/machinery/buttons.dm b/code/game/machinery/buttons.dm
index 7b66872f52bb1..aa93bbb431d20 100644
--- a/code/game/machinery/buttons.dm
+++ b/code/game/machinery/buttons.dm
@@ -12,6 +12,7 @@
idle_power_usage = BASE_MACHINE_IDLE_CONSUMPTION * 0.02
resistance_flags = LAVA_PROOF | FIRE_PROOF
interaction_flags_machine = parent_type::interaction_flags_machine | INTERACT_MACHINE_OPEN
+ mouse_over_pointer = MOUSE_HAND_POINTER
///Icon suffix for the skin of the front pannel that is added to base_icon_state
var/skin = ""
///Whether it is possible to change the panel skin
diff --git a/code/game/machinery/dna_infuser/organ_sets/fly_organs.dm b/code/game/machinery/dna_infuser/organ_sets/fly_organs.dm
index 0ca652c3ab92c..a9401189b40aa 100644
--- a/code/game/machinery/dna_infuser/organ_sets/fly_organs.dm
+++ b/code/game/machinery/dna_infuser/organ_sets/fly_organs.dm
@@ -49,12 +49,10 @@
"S" = "Z",
)
-/obj/item/organ/tongue/fly/New(class, timer, datum/mutation/human/copymut)
- . = ..()
- AddComponent(/datum/component/speechmod, replacements = speech_replacements, should_modify_speech = CALLBACK(src, PROC_REF(should_modify_speech)))
/obj/item/organ/tongue/fly/Initialize(mapload)
. = ..()
+ AddComponent(/datum/component/speechmod, replacements = speech_replacements, should_modify_speech = CALLBACK(src, PROC_REF(should_modify_speech)))
AddElement(/datum/element/organ_set_bonus, /datum/status_effect/organ_set_bonus/fly)
/obj/item/organ/tongue/fly/get_possible_languages()
diff --git a/code/game/machinery/doors/airlock.dm b/code/game/machinery/doors/airlock.dm
index 0e9fcc6b43a81..7cbd5126ca487 100644
--- a/code/game/machinery/doors/airlock.dm
+++ b/code/game/machinery/doors/airlock.dm
@@ -545,7 +545,7 @@
else
. += get_airlock_overlay("fill_[frame_state]", icon, src, em_block = TRUE)
- if(lights && hasPower())
+ if(lights && hasPower() && light_state)
. += get_airlock_overlay("lights_[light_state]", overlays_file, src, em_block = FALSE)
if(panel_open)
@@ -1549,6 +1549,7 @@
assembly.previous_assembly = previous_airlock
assembly.update_name()
assembly.update_appearance()
+ assembly.dir = dir
/obj/machinery/door/airlock/on_deconstruction(disassembled)
var/obj/structure/door_assembly/A
@@ -2465,6 +2466,10 @@
opacity = FALSE
glass = TRUE
+/obj/machinery/door/airlock/multi_tile/setDir(newdir)
+ . = ..()
+ set_bounds()
+
/obj/structure/fluff/airlock_filler
name = "airlock fluff"
desc = "You shouldn't be able to see this fluff!"
diff --git a/code/game/machinery/doors/firedoor.dm b/code/game/machinery/doors/firedoor.dm
index 36828273f1944..b238f3416681f 100644
--- a/code/game/machinery/doors/firedoor.dm
+++ b/code/game/machinery/doors/firedoor.dm
@@ -577,19 +577,23 @@
else
close()
-/obj/machinery/door/firedoor/proc/handle_held_open_adjacency(mob/user)
+/obj/machinery/door/firedoor/proc/handle_held_open_adjacency(atom/crowbar_owner)
SIGNAL_HANDLER
- var/mob/living/living_user = user
- if(!QDELETED(user) && Adjacent(user) && isliving(user) && (living_user.body_position == STANDING_UP))
- return
+
+ if(!QDELETED(crowbar_owner) && crowbar_owner.CanReach(src))
+ if(!ismob(crowbar_owner))
+ return
+ var/mob/living/mob_user = crowbar_owner
+ if(isliving(mob_user) && (mob_user.body_position == STANDING_UP))
+ return
being_held_open = FALSE
correct_state()
- UnregisterSignal(user, COMSIG_MOVABLE_MOVED)
- UnregisterSignal(user, COMSIG_LIVING_SET_BODY_POSITION)
- UnregisterSignal(user, COMSIG_QDELETING)
- if(user)
- user.balloon_alert_to_viewers("released firelock", "released firelock")
+ UnregisterSignal(crowbar_owner, COMSIG_MOVABLE_MOVED)
+ UnregisterSignal(crowbar_owner, COMSIG_LIVING_SET_BODY_POSITION)
+ UnregisterSignal(crowbar_owner, COMSIG_QDELETING)
+ if(crowbar_owner)
+ crowbar_owner.balloon_alert_to_viewers("released firelock", "released firelock")
/obj/machinery/door/firedoor/attack_ai(mob/user)
add_fingerprint(user)
diff --git a/code/game/machinery/embedded_controller/access_controller.dm b/code/game/machinery/embedded_controller/access_controller.dm
index 83b1626900286..7a5e562dd3ecf 100644
--- a/code/game/machinery/embedded_controller/access_controller.dm
+++ b/code/game/machinery/embedded_controller/access_controller.dm
@@ -4,6 +4,7 @@
idle_power_usage = BASE_MACHINE_IDLE_CONSUMPTION * 0.05
active_power_usage = BASE_MACHINE_ACTIVE_CONSUMPTION * 0.04
resistance_flags = INDESTRUCTIBLE | LAVA_PROOF | FIRE_PROOF | UNACIDABLE | ACID_PROOF
+ mouse_over_pointer = MOUSE_HAND_POINTER
var/idSelf
/obj/machinery/door_buttons/attackby(obj/O, mob/user)
@@ -109,6 +110,7 @@
icon_state = "access_control_standby"
base_icon_state = "access_control"
interaction_flags_machine = INTERACT_MACHINE_WIRES_IF_OPEN|INTERACT_MACHINE_ALLOW_SILICON|INTERACT_MACHINE_OPEN_SILICON
+ mouse_over_pointer = MOUSE_HAND_POINTER
///the id of the interior airlock
var/idInterior
///the id of the exterior airlock
diff --git a/code/game/machinery/firealarm.dm b/code/game/machinery/firealarm.dm
index b22959ce461be..4505be8be47eb 100644
--- a/code/game/machinery/firealarm.dm
+++ b/code/game/machinery/firealarm.dm
@@ -18,6 +18,7 @@
max_integrity = 250
integrity_failure = 0.4
armor_type = /datum/armor/machinery_firealarm
+ mouse_over_pointer = MOUSE_HAND_POINTER
idle_power_usage = BASE_MACHINE_IDLE_CONSUMPTION * 0.05
active_power_usage = BASE_MACHINE_ACTIVE_CONSUMPTION * 0.02
power_channel = AREA_USAGE_ENVIRON
diff --git a/code/game/machinery/flatpacker.dm b/code/game/machinery/flatpacker.dm
index 4a0e78f52071f..c26d9ad036d53 100644
--- a/code/game/machinery/flatpacker.dm
+++ b/code/game/machinery/flatpacker.dm
@@ -477,3 +477,9 @@
return ITEM_INTERACT_SUCCESS
#undef MAX_FLAT_PACKS
+
+/obj/item/flatpack/flatpacker // a roundstart flatpacker is NICE you can gahdamn tell the time and everythin'
+ board = /obj/item/circuitboard/machine/flatpacker
+
+/obj/item/flatpack/mailsorter // to have a roundstart mail sorter at cargo
+ board = /obj/item/circuitboard/machine/mailsorter
diff --git a/code/game/machinery/lightswitch.dm b/code/game/machinery/lightswitch.dm
index c8d0249fca02d..ceb5716849a79 100644
--- a/code/game/machinery/lightswitch.dm
+++ b/code/game/machinery/lightswitch.dm
@@ -7,6 +7,7 @@
desc = "Make dark."
power_channel = AREA_USAGE_LIGHT
idle_power_usage = BASE_MACHINE_IDLE_CONSUMPTION * 0.02
+ mouse_over_pointer = MOUSE_HAND_POINTER
/// Set this to a string, path, or area instance to control that area
/// instead of the switch's location.
var/area/area = null
diff --git a/code/game/machinery/pipe/construction.dm b/code/game/machinery/pipe/construction.dm
index 39d6fe7d2ea0c..abd481d525d0c 100644
--- a/code/game/machinery/pipe/construction.dm
+++ b/code/game/machinery/pipe/construction.dm
@@ -134,7 +134,7 @@ Buildable meters
return ..()
/obj/item/pipe/proc/make_from_existing(obj/machinery/atmospherics/make_from)
- p_init_dir = make_from.initialize_directions
+ p_init_dir = make_from.get_init_directions()
setDir(make_from.dir)
pipename = make_from.name
add_atom_colour(make_from.color, FIXED_COLOUR_PRIORITY)
diff --git a/code/game/machinery/recharger.dm b/code/game/machinery/recharger.dm
index 7454f18551150..226e19bfe84f1 100644
--- a/code/game/machinery/recharger.dm
+++ b/code/game/machinery/recharger.dm
@@ -17,6 +17,7 @@
/obj/item/melee/baton/security,
/obj/item/ammo_box/magazine/recharge,
/obj/item/modular_computer,
+ /obj/item/gun/ballistic/automatic/battle_rifle,
))
/obj/machinery/recharger/RefreshParts()
@@ -55,6 +56,11 @@
var/obj/item/ammo_box/magazine/recharge/power_pack = charging
. += span_notice("- \The [charging]'s cell is at [PERCENT(power_pack.stored_ammo.len/power_pack.max_ammo)]%.")
return
+ if(istype(charging, /obj/item/gun/ballistic/automatic/battle_rifle))
+ var/obj/item/gun/ballistic/automatic/battle_rifle/recalibrating_gun = charging
+ . += span_notice("- \The [charging]'s system degradation is at stage [recalibrating_gun.degradation_stage] of [recalibrating_gun.degradation_stage_max]%.")
+ . += span_notice("- \The [charging]'s degradation buffer is at [PERCENT(recalibrating_gun.shots_before_degradation/recalibrating_gun.max_shots_before_degradation)]%.")
+ return
. += span_notice("- \The [charging] is not reporting a power level.")
/obj/machinery/recharger/Entered(atom/movable/arrived, atom/old_loc, list/atom/old_locs)
@@ -158,6 +164,23 @@
using_power = TRUE
update_appearance()
return
+
+ if(istype(charging, /obj/item/gun/ballistic/automatic/battle_rifle))
+ var/obj/item/gun/ballistic/automatic/battle_rifle/recalibrating_gun = charging
+
+ if(recalibrating_gun.degradation_stage)
+ recalibrating_gun.attempt_recalibration(FALSE)
+ use_energy(active_power_usage * recharge_coeff * seconds_per_tick)
+ using_power = TRUE
+
+ else if(recalibrating_gun.shots_before_degradation < recalibrating_gun.max_shots_before_degradation)
+ recalibrating_gun.attempt_recalibration(TRUE, 1 * recharge_coeff)
+ use_energy(active_power_usage * recharge_coeff * seconds_per_tick)
+ using_power = TRUE
+
+ update_appearance()
+ return
+
if(!using_power && !finished_recharging) //Inserted thing is at max charge/ammo, notify those around us
finished_recharging = TRUE
playsound(src, 'sound/machines/ping.ogg', 30, TRUE)
diff --git a/code/game/objects/effects/anomalies/_anomalies.dm b/code/game/objects/effects/anomalies/_anomalies.dm
index f249d22500c30..035a6e05d9044 100644
--- a/code/game/objects/effects/anomalies/_anomalies.dm
+++ b/code/game/objects/effects/anomalies/_anomalies.dm
@@ -11,10 +11,13 @@
var/obj/item/assembly/signaler/anomaly/anomaly_core = /obj/item/assembly/signaler/anomaly
var/area/impact_area
+ /// How long till we seppuku? Blocked by immortal
var/lifespan = ANOMALY_COUNTDOWN_TIMER
var/death_time
+ /// Color of the countdown effect
var/countdown_colour
+ /// Reference to the countdown effect
var/obj/effect/countdown/anomaly/countdown
/// Do we drop a core when we're neutralized?
@@ -129,7 +132,6 @@
to_chat(user, span_notice("Analyzing... [src]'s unstable field is not fluctuating along a stable frequency."))
return ITEM_INTERACT_BLOCKING
-
///Stabilize an anomaly, letting it stay around forever or untill destabilizes by a player. An anomaly without a core can't be signalled, but can be destabilized
/obj/effect/anomaly/proc/stabilize(anchor = FALSE, has_core = TRUE)
immortal = TRUE
diff --git a/code/game/objects/effects/anomalies/anomalies_bioscrambler.dm b/code/game/objects/effects/anomalies/anomalies_bioscrambler.dm
index d722d90ed1172..379143081998d 100644
--- a/code/game/objects/effects/anomalies/anomalies_bioscrambler.dm
+++ b/code/game/objects/effects/anomalies/anomalies_bioscrambler.dm
@@ -3,9 +3,10 @@
name = "bioscrambler anomaly"
icon_state = "bioscrambler"
anomaly_core = /obj/item/assembly/signaler/anomaly/bioscrambler
- immortal = TRUE
pass_flags = PASSTABLE | PASSGLASS | PASSGRILLE | PASSCLOSEDTURF | PASSMACHINE | PASSSTRUCTURE | PASSDOORS
layer = ABOVE_MOB_LAYER
+ lifespan = ANOMALY_COUNTDOWN_TIMER * 2
+
/// Who are we moving towards?
var/datum/weakref/pursuit_target
/// Cooldown for every anomaly pulse
@@ -80,6 +81,10 @@
/obj/effect/anomaly/bioscrambler/docile/update_target()
return
+/obj/effect/anomaly/bioscrambler/detonate()
+ COOLDOWN_RESET(src, pulse_cooldown)
+ anomalyEffect()
+
/// Visual effect spawned when the bioscrambler scrambles your bio
/obj/effect/temp_visual/circle_wave
icon = 'icons/effects/64x64.dmi'
diff --git a/code/game/objects/effects/anomalies/anomalies_bluespace.dm b/code/game/objects/effects/anomalies/anomalies_bluespace.dm
index 7b1de41e5640a..5c941050bfd00 100644
--- a/code/game/objects/effects/anomalies/anomalies_bluespace.dm
+++ b/code/game/objects/effects/anomalies/anomalies_bluespace.dm
@@ -24,6 +24,9 @@
do_teleport(AM, locate(AM.x, AM.y, AM.z), 8, channel = TELEPORT_CHANNEL_BLUESPACE)
/obj/effect/anomaly/bluespace/detonate()
+ new /obj/effect/temp_visual/circle_wave/bluespace(get_turf(src))
+ playsound(src, 'sound/effects/magic/cosmic_energy.ogg', vol = 50)
+
var/turf/T = pick(get_area_turfs(impact_area))
if(!T)
return
@@ -110,3 +113,8 @@
var/mob/living/living = bumpee
living.apply_status_effect(/datum/status_effect/teleport_madness)
+
+/obj/effect/temp_visual/circle_wave/bluespace
+ color = COLOR_BLUE_LIGHT
+ duration = 1 SECONDS
+ amount_to_scale = 5
diff --git a/code/game/objects/effects/anomalies/anomalies_dimensional.dm b/code/game/objects/effects/anomalies/anomalies_dimensional.dm
index 53129c0e9ce3f..accbe993ab189 100644
--- a/code/game/objects/effects/anomalies/anomalies_dimensional.dm
+++ b/code/game/objects/effects/anomalies/anomalies_dimensional.dm
@@ -3,7 +3,7 @@
name = "dimensional anomaly"
icon_state = "dimensional"
anomaly_core = /obj/item/assembly/signaler/anomaly/dimensional
- immortal = TRUE
+ lifespan = ANOMALY_COUNTDOWN_TIMER * 20 // will generally be killed off by reaching max teleports first
move_chance = 0
/// Range of effect, if left alone anomaly will convert a 2(range)+1 squared area.
var/range = 3
@@ -13,6 +13,12 @@
var/datum/dimension_theme/theme
/// Effect displaying on the anomaly to represent the theme.
var/mutable_appearance/theme_icon
+ /// How many times we can still teleport. Delete self if it hits 0 and we try to teleport. If immortal, will simply stay where it is
+ var/teleports_left
+ /// Minimum teleports it will do before going away permanently
+ var/minimum_teleports = 1
+ /// Maximum teleports it will do before going away permanently
+ var/maximum_teleports = 4
/obj/effect/anomaly/dimensional/Initialize(mapload, new_lifespan, drops_core)
. = ..()
@@ -21,6 +27,8 @@
animate(src, transform = matrix()*0.85, time = 3, loop = -1)
animate(transform = matrix(), time = 3, loop = -1)
+ teleports_left = rand(minimum_teleports, maximum_teleports)
+
/obj/effect/anomaly/dimensional/Destroy()
theme = null
target_turfs = null
@@ -37,6 +45,10 @@
if (!theme)
prepare_area()
if (!target_turfs.len)
+ if(teleports_left <= 0 && !immortal)
+ detonate()
+ return
+ teleports_left--
relocate()
return
@@ -80,6 +92,9 @@
src.forceMove(new_turf)
prepare_area()
+/obj/effect/anomaly/dimensional/detonate()
+ qdel(src)
+
/obj/effect/temp_visual/transmute_tile_flash
icon = 'icons/effects/effects.dmi'
icon_state = "shield-flash"
diff --git a/code/game/objects/effects/anomalies/anomalies_gravity.dm b/code/game/objects/effects/anomalies/anomalies_gravity.dm
index 82b55542246c7..40cdcbcb15e49 100644
--- a/code/game/objects/effects/anomalies/anomalies_gravity.dm
+++ b/code/game/objects/effects/anomalies/anomalies_gravity.dm
@@ -82,6 +82,10 @@
A.throw_at(target, 5, 1)
boing = 0
+/obj/effect/anomaly/grav/detonate()
+ new /obj/effect/temp_visual/circle_wave/gravity(get_turf(src))
+ playsound(src, 'sound/effects/magic/cosmic_energy.ogg', vol = 50)
+
/obj/effect/anomaly/grav/high
var/datum/proximity_monitor/advanced/gravity/grav_field
@@ -93,6 +97,7 @@
grav_field = new(src, 7, TRUE, rand(0, 3))
/obj/effect/anomaly/grav/high/detonate()
+ ..()
for(var/obj/machinery/gravity_generator/main/the_generator as anything in SSmachines.get_machines_by_type_and_subtypes(/obj/machinery/gravity_generator/main))
if(is_station_level(the_generator.z))
the_generator.blackout()
@@ -111,3 +116,6 @@
. = ..()
transform *= 3
+
+/obj/effect/temp_visual/circle_wave/gravity
+ color = COLOR_NAVY
diff --git a/code/game/objects/effects/anomalies/anomalies_vortex.dm b/code/game/objects/effects/anomalies/anomalies_vortex.dm
index 0313f63146b52..4c3a63ae5a641 100644
--- a/code/game/objects/effects/anomalies/anomalies_vortex.dm
+++ b/code/game/objects/effects/anomalies/anomalies_vortex.dm
@@ -62,3 +62,13 @@
SSexplosions.medturf += T
if(EXPLODE_LIGHT)
SSexplosions.lowturf += T
+
+/obj/effect/anomaly/bhole/detonate()
+ new /obj/effect/temp_visual/circle_wave/vortex(get_turf(src))
+ playsound(src, 'sound/effects/hallucinations/far_noise.ogg', vol = 50)
+
+/obj/effect/temp_visual/circle_wave/vortex
+ color = COLOR_BLACK
+ duration = 3 SECONDS
+ amount_to_scale = 4
+
diff --git a/code/game/objects/effects/decals/cleanable/humans.dm b/code/game/objects/effects/decals/cleanable/humans.dm
index c3bdfbdb89543..ad8d9b9f13b64 100644
--- a/code/game/objects/effects/decals/cleanable/humans.dm
+++ b/code/game/objects/effects/decals/cleanable/humans.dm
@@ -272,9 +272,9 @@
for(var/Ddir in GLOB.cardinals)
if(old_entered_dirs & Ddir)
- entered_dirs |= angle2dir_cardinal(dir2angle(Ddir) + ang_change)
+ entered_dirs |= turn_cardinal(Ddir, ang_change)
if(old_exited_dirs & Ddir)
- exited_dirs |= angle2dir_cardinal(dir2angle(Ddir) + ang_change)
+ exited_dirs |= turn_cardinal(Ddir, ang_change)
update_appearance()
return ..()
diff --git a/code/game/objects/effects/particles/smoke.dm b/code/game/objects/effects/particles/smoke.dm
index f7c07231cf2c1..cc4fec3583911 100644
--- a/code/game/objects/effects/particles/smoke.dm
+++ b/code/game/objects/effects/particles/smoke.dm
@@ -107,16 +107,16 @@
"steam_cloud_4" = 1,
"steam_cloud_5" = 1,
)
- color = "#FFFFFFAA"
- count = 6
- spawning = 0.5
+ color = "#FFFFFF8A"
+ count = 5
+ spawning = 0.3
lifespan = 3 SECONDS
fade = 1.2 SECONDS
fadein = 0.4 SECONDS
position = generator(GEN_BOX, list(-17,-15,0), list(24,15,0), NORMAL_RAND)
scale = generator(GEN_VECTOR, list(0.9,0.9), list(1.1,1.1), NORMAL_RAND)
drift = generator(GEN_SPHERE, list(-0.01,0), list(0.01,0.01), UNIFORM_RAND)
- spin = generator(GEN_NUM, list(-3,3), NORMAL_RAND)
+ spin = generator(GEN_NUM, list(-2,2), NORMAL_RAND)
gravity = list(0.05, 0.28)
friction = 0.3
grow = 0.037
diff --git a/code/game/objects/effects/temporary_visuals/miscellaneous.dm b/code/game/objects/effects/temporary_visuals/miscellaneous.dm
index f132c7a01537d..0df5c1baee624 100644
--- a/code/game/objects/effects/temporary_visuals/miscellaneous.dm
+++ b/code/game/objects/effects/temporary_visuals/miscellaneous.dm
@@ -443,8 +443,7 @@
var/size_matrix = matrix()
if(size_calc_target)
layer = size_calc_target.layer + 0.01
- var/icon/I = icon(size_calc_target.icon, size_calc_target.icon_state, size_calc_target.dir)
- size_matrix = matrix() * (I.Height()/ICON_SIZE_Y)
+ size_matrix = matrix() * (size_calc_target.get_cached_height()/ICON_SIZE_Y)
transform = size_matrix //scale the bleed overlay's size based on the target's icon size
var/matrix/M = transform
if(shrink)
diff --git a/code/game/objects/items/blueprints.dm b/code/game/objects/items/blueprints.dm
index d11c6e21d6981..a1f0001f651f8 100644
--- a/code/game/objects/items/blueprints.dm
+++ b/code/game/objects/items/blueprints.dm
@@ -239,10 +239,11 @@
/obj/item/blueprints/slime/edit_area(mob/user)
. = ..()
var/area/area = get_area(src)
+ var/list/turf_matrix = color_transition_filter("#2956B2")
for(var/list/zlevel_turfs as anything in area.get_zlevel_turf_lists())
for(var/turf/area_turf as anything in zlevel_turfs)
area_turf.remove_atom_colour(WASHABLE_COLOUR_PRIORITY)
- area_turf.add_atom_colour("#2956B2", FIXED_COLOUR_PRIORITY)
+ area_turf.add_atom_colour(turf_matrix, FIXED_COLOUR_PRIORITY)
area.area_flags |= XENOBIOLOGY_COMPATIBLE
qdel(src)
diff --git a/code/game/objects/items/charter.dm b/code/game/objects/items/charter.dm
index c91326afc97ff..6db6d8cb35c43 100644
--- a/code/game/objects/items/charter.dm
+++ b/code/game/objects/items/charter.dm
@@ -57,7 +57,9 @@
to_chat(user, span_notice("Your name has been sent to your employers for approval."))
// Autoapproves after a certain time
response_timer_id = addtimer(CALLBACK(src, PROC_REF(rename_station), new_name, user.name, user.real_name, key_name(user)), approval_time, TIMER_STOPPABLE)
- to_chat(GLOB.admins, span_adminnotice("CUSTOM STATION RENAME:[ADMIN_LOOKUPFLW(user)] proposes to rename the [name_type] to [new_name] (will autoapprove in [DisplayTimeText(approval_time)]). [ADMIN_SMITE(user)] (REJECT) [ADMIN_CENTCOM_REPLY(user)]"))
+ to_chat(GLOB.admins,
+ span_adminnotice("CUSTOM STATION RENAME:[ADMIN_LOOKUPFLW(user)] proposes to rename the [name_type] to [new_name] (will autoapprove in [DisplayTimeText(approval_time)]). [ADMIN_SMITE(user)] (REJECT) [ADMIN_CENTCOM_REPLY(user)]"),
+ type = MESSAGE_TYPE_PRAYER)
for(var/client/admin_client in GLOB.admins)
if(admin_client.prefs.toggles & SOUND_ADMINHELP)
window_flash(admin_client, ignorepref = TRUE)
diff --git a/code/game/objects/items/circuitboards/machines/machine_circuitboards.dm b/code/game/objects/items/circuitboards/machines/machine_circuitboards.dm
index 619cd42ce5cf6..d8155b5032ace 100644
--- a/code/game/objects/items/circuitboards/machines/machine_circuitboards.dm
+++ b/code/game/objects/items/circuitboards/machines/machine_circuitboards.dm
@@ -1471,6 +1471,16 @@
/datum/stock_part/scanning_module = 1,
/datum/stock_part/card_reader = 1)
+/obj/item/circuitboard/machine/mailsorter
+ name = "Mail Sorter"
+ greyscale_colors = CIRCUIT_COLOR_SUPPLY
+ build_path = /obj/machinery/mailsorter
+ req_components = list(
+ /obj/item/stack/sheet/glass = 1,
+ /datum/stock_part/matter_bin = 2,
+ /datum/stock_part/scanning_module = 1)
+ needs_anchored = TRUE
+
//Tram
/obj/item/circuitboard/machine/crossing_signal
name = "Crossing Signal"
diff --git a/code/game/objects/items/crayons.dm b/code/game/objects/items/crayons.dm
index 77b94761a7a5b..18da163bce4a6 100644
--- a/code/game/objects/items/crayons.dm
+++ b/code/game/objects/items/crayons.dm
@@ -202,7 +202,7 @@
/obj/item/toy/crayon/suicide_act(mob/living/user)
user.visible_message(span_suicide("[user] is jamming [src] up [user.p_their()] nose and into [user.p_their()] brain. It looks like [user.p_theyre()] trying to commit suicide!"))
- user.add_atom_colour(paint_color, ADMIN_COLOUR_PRIORITY)
+ user.add_atom_colour(color_transition_filter(paint_color, SATURATION_OVERRIDE), ADMIN_COLOUR_PRIORITY)
return (BRUTELOSS|OXYLOSS)
/obj/item/toy/crayon/Initialize(mapload)
@@ -435,7 +435,7 @@
if(!isValidSurface(target))
target.balloon_alert(user, "can't use there!")
- return
+ return ITEM_INTERACT_BLOCKING
var/drawing = drawtype
switch(drawtype)
@@ -471,7 +471,7 @@
if (istagger)
cost *= 0.5
if(check_empty(user, cost))
- return
+ return ITEM_INTERACT_BLOCKING
var/temp = "rune"
var/ascii = (length(drawing) == 1)
@@ -521,10 +521,10 @@
wait_time *= 0.5
if(!instant && !do_after(user, wait_time, target = target, max_interact_count = 4))
- return
+ return ITEM_INTERACT_BLOCKING
if(!use_charges(user, cost))
- return
+ return ITEM_INTERACT_BLOCKING
if(length(text_buffer))
drawing = text_buffer[1]
@@ -548,7 +548,7 @@
affected_turfs += right
else
balloon_alert(user, "no room!")
- return
+ return ITEM_INTERACT_BLOCKING
created_art.add_hiddenprint(user)
if(istagger)
created_art.AddElement(/datum/element/art, GOOD_ART)
@@ -575,15 +575,14 @@
for(var/turf/draw_turf as anything in affected_turfs)
reagents.expose(draw_turf, methods = TOUCH, volume_modifier = volume_multiplier)
check_empty(user)
- return
+ return ITEM_INTERACT_SUCCESS
/obj/item/toy/crayon/interact_with_atom(atom/interacting_with, mob/living/user, list/modifiers)
if (!check_allowed_items(interacting_with))
return NONE
if(can_use_on(interacting_with, user, modifiers))
- use_on(interacting_with, user, modifiers)
- return ITEM_INTERACT_BLOCKING
+ return use_on(interacting_with, user, modifiers)
return NONE
/obj/item/toy/crayon/get_writing_implement_details()
@@ -790,6 +789,12 @@
post_noise = FALSE
interaction_flags_click = NEED_DEXTERITY|NEED_HANDS|ALLOW_RESTING
+ /// Types which use their color var for additional logic, so we need to avoid using transition filters on them.
+ var/static/list/direct_color_types = typecacheof(list(
+ /obj/item/paper, // Uses color for TGUI backgrounds, doesn't look very good either
+ /obj/item/fish, // Used for aquarium sprites
+ ))
+
/obj/item/toy/crayon/spraycan/Initialize(mapload)
. = ..()
var/static/list/slapcraft_recipe_list = list(/datum/crafting_recipe/improvised_coolant)
@@ -819,13 +824,14 @@
return .
context[SCREENTIP_CONTEXT_LMB] = "Paint"
+ context[SCREENTIP_CONTEXT_RMB] = "Coat with paint"
if(isbodypart(target))
var/obj/item/bodypart/limb = target
if(IS_ROBOTIC_LIMB(limb))
- context[SCREENTIP_CONTEXT_RMB] = "Restyle robotic limb"
+ context[SCREENTIP_CONTEXT_CTRL_LMB] = "Restyle robotic limb"
else
- context[SCREENTIP_CONTEXT_RMB] = "Copy color"
+ context[SCREENTIP_CONTEXT_CTRL_LMB] = "Copy color"
return CONTEXTUAL_SCREENTIP_SET
@@ -881,12 +887,15 @@
return ..()
/obj/item/toy/crayon/spraycan/use_on(atom/target, mob/user, list/modifiers)
+ if (LAZYACCESS(modifiers, CTRL_CLICK))
+ return ctrl_interact(target, user)
+
if(is_capped)
balloon_alert(user, "take the cap off first!")
- return
+ return ITEM_INTERACT_BLOCKING
if(check_empty(user))
- return
+ return ITEM_INTERACT_BLOCKING
if(iscarbon(target))
if(pre_noise || post_noise)
@@ -912,11 +921,15 @@
else if(actually_paints && target.is_atom_colour(paint_color, min_priority_index = WASHABLE_COLOUR_PRIORITY))
balloon_alert(user, "[target.p_theyre()] already that color!")
- return
+ return ITEM_INTERACT_BLOCKING
+
+ var/saturation_mode = SATURATION_MULTIPLY
+ if (LAZYACCESS(modifiers, RIGHT_CLICK))
+ saturation_mode = SATURATION_OVERRIDE
if(ismob(target) && (HAS_TRAIT(target, TRAIT_SPRAY_PAINTABLE)))
if(actually_paints)
- target.add_atom_colour(paint_color, WASHABLE_COLOUR_PRIORITY)
+ target.add_atom_colour(color_transition_filter(paint_color, saturation_mode), WASHABLE_COLOUR_PRIORITY)
SEND_SIGNAL(target, COMSIG_LIVING_MOB_PAINTED)
use_charges(user, 2, requires_full = FALSE)
reagents.trans_to(target, ., volume_multiplier, transferred_by = user, methods = VAPOR)
@@ -924,45 +937,13 @@
if(pre_noise || post_noise)
playsound(user.loc, 'sound/effects/spray.ogg', 5, TRUE, 5)
user.visible_message(span_notice("[user] coats [target] with spray paint!"), span_notice("You coat [target] with spray paint."))
- return
-
- if(isobj(target) && !(target.flags_1 & UNPAINTABLE_1))
- var/color_is_dark = FALSE
- if(actually_paints)
- color_is_dark = is_color_dark(paint_color)
-
- if (color_is_dark && !(target.flags_1 & ALLOW_DARK_PAINTS_1))
- to_chat(user, span_warning("A color that dark on an object like this? Surely not..."))
- return
-
- if(istype(target, /obj/item/pipe))
- if(GLOB.pipe_color_name.Find(paint_color))
- var/obj/item/pipe/target_pipe = target
- target_pipe.pipe_color = paint_color
- target.add_atom_colour(paint_color, FIXED_COLOUR_PRIORITY)
- balloon_alert(user, "painted in [GLOB.pipe_color_name[paint_color]] color")
- else
- balloon_alert(user, "invalid pipe color!")
- return
- else if(istype(target, /obj/machinery/atmospherics))
- if(GLOB.pipe_color_name.Find(paint_color))
- var/obj/machinery/atmospherics/target_pipe = target
- target_pipe.paint(paint_color)
- balloon_alert(user, "painted in [GLOB.pipe_color_name[paint_color]] color")
- else
- balloon_alert(user, "invalid pipe color!")
- return
- else
- target.add_atom_colour(paint_color, WASHABLE_COLOUR_PRIORITY)
+ return ITEM_INTERACT_SUCCESS
- if(isitem(target) && isliving(target.loc))
- var/obj/item/target_item = target
- var/mob/living/holder = target.loc
- if(holder.is_holding(target_item))
- holder.update_held_items()
- else
- holder.update_clothing(target_item.slot_flags)
+ if(!isobj(target) || (target.flags_1 & UNPAINTABLE_1))
+ return ..()
+ var/color_is_dark = is_color_dark(paint_color)
+ if(!actually_paints)
if(!(SEND_SIGNAL(target, COMSIG_OBJ_PAINTED, user, src, color_is_dark) & DONT_USE_SPRAYCAN_CHARGES))
use_charges(user, 2, requires_full = FALSE)
reagents.trans_to(target, ., volume_multiplier, transferred_by = user, methods = VAPOR)
@@ -970,11 +951,50 @@
if(pre_noise || post_noise)
playsound(user.loc, 'sound/effects/spray.ogg', 5, TRUE, 5)
user.visible_message(span_notice("[user] coats [target] with spray paint!"), span_notice("You coat [target] with spray paint."))
- return
+ return ITEM_INTERACT_SUCCESS
- return ..()
+ if (color_is_dark && saturation_mode == SATURATION_OVERRIDE && !(target.flags_1 & ALLOW_DARK_PAINTS_1))
+ to_chat(user, span_warning("A color that dark on an object like this? Surely not..."))
+ return ITEM_INTERACT_BLOCKING
+
+ if(istype(target, /obj/item/pipe))
+ if(!GLOB.pipe_color_name.Find(paint_color))
+ balloon_alert(user, "invalid pipe color!")
+ return ITEM_INTERACT_BLOCKING
+ var/obj/item/pipe/target_pipe = target
+ target_pipe.pipe_color = paint_color
+ target.add_atom_colour(paint_color, FIXED_COLOUR_PRIORITY)
+ balloon_alert(user, "painted in [GLOB.pipe_color_name[paint_color]] color")
+ else if(istype(target, /obj/machinery/atmospherics))
+ if(!GLOB.pipe_color_name.Find(paint_color))
+ balloon_alert(user, "invalid pipe color!")
+ return ITEM_INTERACT_BLOCKING
+ var/obj/machinery/atmospherics/target_pipe = target
+ target_pipe.paint(paint_color)
+ balloon_alert(user, "painted in [GLOB.pipe_color_name[paint_color]] color")
+ else if (is_type_in_typecache(target, direct_color_types))
+ target.add_atom_colour(paint_color, WASHABLE_COLOUR_PRIORITY)
+ else
+ target.add_atom_colour(color_transition_filter(paint_color, saturation_mode), WASHABLE_COLOUR_PRIORITY)
+
+ if(isitem(target) && isliving(target.loc))
+ var/obj/item/target_item = target
+ var/mob/living/holder = target.loc
+ if(holder.is_holding(target_item))
+ holder.update_held_items()
+ else
+ holder.update_clothing(target_item.slot_flags)
+
+ if(!(SEND_SIGNAL(target, COMSIG_OBJ_PAINTED, user, src, color_is_dark) & DONT_USE_SPRAYCAN_CHARGES))
+ use_charges(user, 2, requires_full = FALSE)
+ reagents.trans_to(target, ., volume_multiplier, transferred_by = user, methods = VAPOR)
-/obj/item/toy/crayon/spraycan/interact_with_atom_secondary(atom/interacting_with, mob/living/user, list/modifiers)
+ if(pre_noise || post_noise)
+ playsound(user.loc, 'sound/effects/spray.ogg', 5, TRUE, 5)
+ user.visible_message(span_notice("[user] coats [target] with spray paint!"), span_notice("You coat [target] with spray paint."))
+ return ITEM_INTERACT_SUCCESS
+
+/obj/item/toy/crayon/spraycan/proc/ctrl_interact(atom/interacting_with, mob/living/user)
if(is_capped)
if(!interacting_with.color)
// let's be generous and assume if they're trying to match something with no color, while capped,
@@ -982,32 +1002,41 @@
return NONE
balloon_alert(user, "take the cap off first!")
return ITEM_INTERACT_BLOCKING
+
if(check_empty(user))
return ITEM_INTERACT_BLOCKING
- /* DOPPLER EDIT REMOVAL START
- if(isbodypart(interacting_with) && actually_paints)
- var/obj/item/bodypart/limb = interacting_with
- if(IS_ROBOTIC_LIMB(limb))
- var/list/skins = list()
- var/static/list/style_list_icons = list("standard" = 'icons/mob/augmentation/augments.dmi', "engineer" = 'icons/mob/augmentation/augments_engineer.dmi', "security" = 'icons/mob/augmentation/augments_security.dmi', "mining" = 'icons/mob/augmentation/augments_mining.dmi')
- for(var/skin_option in style_list_icons)
- var/image/part_image = image(icon = style_list_icons[skin_option], icon_state = "[limb.limb_id]_[limb.body_zone]")
- if(limb.aux_zone) //Hands
- part_image.overlays += image(icon = style_list_icons[skin_option], icon_state = "[limb.limb_id]_[limb.aux_zone]")
- skins += list("[skin_option]" = part_image)
- var/choice = show_radial_menu(user, src, skins, require_near = TRUE)
- if(choice && (use_charges(user, 5, requires_full = FALSE)))
- playsound(user.loc, 'sound/effects/spray.ogg', 5, TRUE, 5)
- limb.change_appearance(style_list_icons[choice], greyscale = FALSE)
- return ITEM_INTERACT_SUCCESS
- DOPPLER EDIT REMOVAL END */
- if(interacting_with.color)
- paint_color = interacting_with.color
- balloon_alert(user, "matched colour of target")
- update_appearance()
+
+ if(!isbodypart(interacting_with) || !actually_paints)
+ if(interacting_with.color)
+ paint_color = interacting_with.color
+ balloon_alert(user, "matched colour of target")
+ update_appearance()
+ return ITEM_INTERACT_BLOCKING
+ balloon_alert(user, "can't match those colours!")
+ return ITEM_INTERACT_BLOCKING
+
+ var/obj/item/bodypart/limb = interacting_with
+ if(!IS_ROBOTIC_LIMB(limb))
return ITEM_INTERACT_BLOCKING
- balloon_alert(user, "can't match those colours!")
- return ITEM_INTERACT_BLOCKING
+
+ var/list/skins = list()
+ var/static/list/style_list_icons = list(
+ "standard" = 'icons/mob/augmentation/augments.dmi',
+ "engineer" = 'icons/mob/augmentation/augments_engineer.dmi',
+ "security" = 'icons/mob/augmentation/augments_security.dmi',
+ "mining" = 'icons/mob/augmentation/augments_mining.dmi',
+ )
+
+ for(var/skin_option in style_list_icons)
+ var/image/part_image = image(icon = style_list_icons[skin_option], icon_state = "[limb.limb_id]_[limb.body_zone]")
+ if(limb.aux_zone) //Hands
+ part_image.overlays += image(icon = style_list_icons[skin_option], icon_state = "[limb.limb_id]_[limb.aux_zone]")
+ skins += list("[skin_option]" = part_image)
+ var/choice = show_radial_menu(user, src, skins, require_near = TRUE)
+ if(choice && (use_charges(user, 5, requires_full = FALSE)))
+ playsound(user.loc, 'sound/effects/spray.ogg', 5, TRUE, 5)
+ limb.change_appearance(style_list_icons[choice], greyscale = FALSE)
+ return ITEM_INTERACT_SUCCESS
/obj/item/toy/crayon/spraycan/click_alt(mob/user)
if(!has_cap)
diff --git a/code/game/objects/items/devices/radio/radio.dm b/code/game/objects/items/devices/radio/radio.dm
index 72f3747b01121..ac9cbfec8211f 100644
--- a/code/game/objects/items/devices/radio/radio.dm
+++ b/code/game/objects/items/devices/radio/radio.dm
@@ -351,8 +351,8 @@
if(isliving(talking_movable))
var/mob/living/talking_living = talking_movable
var/volume_modifier = (talking_living.client?.prefs.read_preference(/datum/preference/numeric/sound_radio_noise))
- if(radio_noise && talking_living.can_hear() && volume_modifier && signal.frequency != FREQ_COMMON)
- var/sound/radio_noise = sound(sound('sound/items/radio/radio_talk.ogg', volume = volume_modifier))
+ if(radio_noise && talking_living.can_hear() && volume_modifier && signal.frequency != FREQ_COMMON && !LAZYACCESS(message_mods, MODE_SEQUENTIAL))
+ var/sound/radio_noise = sound('sound/items/radio/radio_talk.ogg', volume = volume_modifier)
radio_noise.frequency = get_rand_frequency_low_range()
SEND_SOUND(talking_living, radio_noise)
diff --git a/code/game/objects/items/devices/taperecorder.dm b/code/game/objects/items/devices/taperecorder.dm
index df0fbb928ed8c..433df9869d224 100644
--- a/code/game/objects/items/devices/taperecorder.dm
+++ b/code/game/objects/items/devices/taperecorder.dm
@@ -262,7 +262,7 @@
balloon_alert(usr, "recording ended")
stoplag(1 SECONDS) //prevents multiple balloon alerts covering each other
break
- say("[mytape.storedinfo[i]]", sanitize=FALSE)//We want to display this properly, don't double encode
+ say("[mytape.storedinfo[i]]", sanitize=FALSE, message_mods = list(MODE_SEQUENTIAL = TRUE))//We want to display this properly, don't double encode
if(mytape.storedinfo.len < i + 1)
playsleepseconds = 1
sleep(1 SECONDS)
@@ -270,7 +270,7 @@
playsleepseconds = mytape.timestamp[i + 1] - mytape.timestamp[i]
if(playsleepseconds > 14 SECONDS)
sleep(1 SECONDS)
- say("Skipping [playsleepseconds/10] seconds of silence.")
+ say("Skipping [playsleepseconds/10] seconds of silence.", message_mods = list(MODE_SEQUENTIAL = TRUE))
playsleepseconds = 1 SECONDS
i++
diff --git a/code/game/objects/items/devices/traitordevices.dm b/code/game/objects/items/devices/traitordevices.dm
index d8b7c1999aea4..341231d767a14 100644
--- a/code/game/objects/items/devices/traitordevices.dm
+++ b/code/game/objects/items/devices/traitordevices.dm
@@ -329,7 +329,7 @@ effective or pretty fucking useless.
/obj/item/jammer
name = "radio jammer"
- desc = "Device used to disrupt nearby radio communication. Alternate function creates a powerful distruptor wave which disables all nearby listening devices."
+ desc = "Device used to disrupt nearby radio communication. Alternate function creates a powerful disruptor wave which disables all nearby listening devices."
icon = 'icons/obj/devices/syndie_gadget.dmi'
icon_state = "jammer"
var/active = FALSE
@@ -342,7 +342,7 @@ effective or pretty fucking useless.
register_context()
/obj/item/jammer/add_context(atom/source, list/context, obj/item/held_item, mob/user)
- context[SCREENTIP_CONTEXT_LMB] = "Release distruptor wave"
+ context[SCREENTIP_CONTEXT_LMB] = "Release disruptor wave"
context[SCREENTIP_CONTEXT_RMB] = "Toggle"
return CONTEXTUAL_SCREENTIP_SET
@@ -352,8 +352,8 @@ effective or pretty fucking useless.
user.balloon_alert(user, "on cooldown!")
return
- user.balloon_alert(user, "distruptor wave released!")
- to_chat(user, span_notice("You release a distruptor wave, disabling all nearby radio devices."))
+ user.balloon_alert(user, "disruptor wave released!")
+ to_chat(user, span_notice("You release a disruptor wave, disabling all nearby radio devices."))
for (var/atom/potential_owner in view(7, user))
disable_radios_on(potential_owner)
COOLDOWN_START(src, jam_cooldown, jam_cooldown_duration)
@@ -379,8 +379,8 @@ effective or pretty fucking useless.
user.balloon_alert(user, "out of reach!")
return
- interacting_with.balloon_alert(user, "radio distrupted!")
- to_chat(user, span_notice("You release a directed distruptor wave, disabling all radio devices on [interacting_with]."))
+ interacting_with.balloon_alert(user, "radio disrupted!")
+ to_chat(user, span_notice("You release a directed disruptor wave, disabling all radio devices on [interacting_with]."))
disable_radios_on(interacting_with)
return ITEM_INTERACT_SUCCESS
diff --git a/code/game/objects/items/grenades/_grenade.dm b/code/game/objects/items/grenades/_grenade.dm
index 780311fa4d149..664a31c226207 100644
--- a/code/game/objects/items/grenades/_grenade.dm
+++ b/code/game/objects/items/grenades/_grenade.dm
@@ -159,7 +159,7 @@
if(istype(user))
user.add_mob_memory(/datum/memory/bomb_planted, antagonist = src)
active = TRUE
- icon_state = initial(icon_state) + "_active"
+ icon_state = (base_icon_state || initial(icon_state)) + "_active"
SEND_SIGNAL(src, COMSIG_GRENADE_ARMED, det_time, delayoverride)
addtimer(CALLBACK(src, PROC_REF(detonate)), isnull(delayoverride)? det_time : delayoverride)
@@ -255,7 +255,7 @@
if(det_time == 0)
det_time = "Instant"
else
- det_time = num2text(det_time * 0.1)
+ det_time = num2text(det_time * 0.1)
var/old_selection = possible_fuse_time.Find(det_time) //Position of det_time in the list
if(old_selection >= possible_fuse_time.len)
diff --git a/code/game/objects/items/grenades/flashbang.dm b/code/game/objects/items/grenades/flashbang.dm
index 2300d2c67174d..2bc9401c77451 100644
--- a/code/game/objects/items/grenades/flashbang.dm
+++ b/code/game/objects/items/grenades/flashbang.dm
@@ -54,6 +54,7 @@
/obj/item/grenade/stingbang
name = "stingbang"
icon_state = "timeg_locked"
+ base_icon_state = "timeg"
inhand_icon_state = "flashbang"
lefthand_file = 'icons/mob/inhands/equipment/security_lefthand.dmi'
righthand_file = 'icons/mob/inhands/equipment/security_righthand.dmi'
@@ -64,6 +65,8 @@
/obj/item/grenade/stingbang/mega
name = "mega stingbang"
+ icon_state = "timeg_mega_locked"
+ base_icon_state = "timeg_mega"
shrapnel_type = /obj/projectile/bullet/pellet/stingball/mega
shrapnel_radius = 12
@@ -122,6 +125,7 @@
name = "rotfrag grenade"
desc = "A grenade that generates more shrapnel the more you rotate it in your hand after pulling the pin. This one releases shrapnel shards."
icon_state = "timeg_locked"
+ base_icon_state = "timeg"
inhand_icon_state = "flashbang"
lefthand_file = 'icons/mob/inhands/equipment/security_lefthand.dmi'
righthand_file = 'icons/mob/inhands/equipment/security_righthand.dmi'
diff --git a/code/game/objects/items/inducer.dm b/code/game/objects/items/inducer.dm
index 0e430589981e4..948606d6d4068 100644
--- a/code/game/objects/items/inducer.dm
+++ b/code/game/objects/items/inducer.dm
@@ -61,6 +61,20 @@
/obj/item/inducer/examine(mob/living/user)
. = ..()
+ . += examine_hints(user)
+
+/**
+ * Gives description for this inducer
+ * Arguments
+ *
+ * * mob/living/user - the mob we are returning the description to
+ */
+/obj/item/inducer/proc/examine_hints(mob/living/user)
+ PROTECTED_PROC(TRUE)
+ SHOULD_BE_PURE(TRUE)
+
+ . = list()
+
var/obj/item/stock_parts/power_store/our_cell = get_cell(src, user)
if(!QDELETED(our_cell))
. += span_notice("Its display shows: [display_energy(our_cell.charge)].")
@@ -231,3 +245,24 @@
desc = "A tool for inductively charging internal power cells. This one has a suspicious colour scheme, and seems to be rigged to transfer charge at a much faster rate."
power_transfer_multiplier = 2 // 2x the base speed
powerdevice = /obj/item/stock_parts/power_store/battery/super
+
+/obj/item/inducer/cyborg
+ name = "internal inducer"
+ icon = 'icons/obj/tools.dmi'
+ icon_state = "inducer-engi"
+ powerdevice = null
+
+/obj/item/inducer/cyborg/add_context(atom/source, list/context, obj/item/held_item, mob/user)
+ return NONE
+
+/obj/item/inducer/cyborg/examine_hints(mob/living/user)
+ return list()
+
+/obj/item/inducer/cyborg/get_cell(atom/movable/interface, mob/living/silicon/robot/silicon_friend)
+ return istype(silicon_friend) ? silicon_friend.cell : null
+
+/obj/item/inducer/cyborg/screwdriver_act(mob/living/user, obj/item/tool)
+ return ITEM_INTERACT_FAILURE
+
+/obj/item/inducer/cyborg/item_interaction(mob/living/user, obj/item/tool, list/modifiers)
+ return ITEM_INTERACT_FAILURE
diff --git a/code/game/objects/items/paint.dm b/code/game/objects/items/paint.dm
index 66e0b15e99fd7..7d5cc7eabe8f2 100644
--- a/code/game/objects/items/paint.dm
+++ b/code/game/objects/items/paint.dm
@@ -118,7 +118,10 @@
if(paintleft <= 0)
return NONE
paintleft--
- interacting_with.add_atom_colour(paint_color, WASHABLE_COLOUR_PRIORITY)
+ var/color_type = SATURATION_MULTIPLY
+ if (LAZYACCESS(modifiers, RIGHT_CLICK))
+ color_type = SATURATION_OVERRIDE
+ interacting_with.add_atom_colour(color_transition_filter(paint_color, color_type), WASHABLE_COLOUR_PRIORITY)
if(paintleft <= 0)
icon_state = "paint_empty"
return ITEM_INTERACT_SUCCESS
diff --git a/code/game/objects/items/rcd/RPD.dm b/code/game/objects/items/rcd/RPD.dm
index 07db9978e3e09..933f8c1c64876 100644
--- a/code/game/objects/items/rcd/RPD.dm
+++ b/code/game/objects/items/rcd/RPD.dm
@@ -9,164 +9,15 @@
#define DESTROY_MODE (1<<2)
#define REPROGRAM_MODE (1<<3)
-#define PIPE_LAYER(num) (1<<(num-1))
+///Maximum number of pipe layers the RPD can support
+#define MAX_PIPE_LAYERS 5
+
+///Converts the pipe layer into a bitflag so we can append multiple layers into 1 bitfield
+#define PIPE_LAYER(num) (1 << (num - 1))
///Sound to make when we use the item to build/destroy something
#define RPD_USE_SOUND 'sound/items/deconstruct.ogg'
-GLOBAL_LIST_INIT(atmos_pipe_recipes, list(
- "Pipes" = list(
- new /datum/pipe_info/pipe("Pipe", /obj/machinery/atmospherics/pipe/smart, TRUE),
- new /datum/pipe_info/pipe("Layer Adapter", /obj/machinery/atmospherics/pipe/layer_manifold, TRUE),
- new /datum/pipe_info/pipe("Color Adapter", /obj/machinery/atmospherics/pipe/color_adapter, TRUE),
- new /datum/pipe_info/pipe("Bridge Pipe", /obj/machinery/atmospherics/pipe/bridge_pipe, TRUE),
- new /datum/pipe_info/pipe("Multi-Deck Adapter", /obj/machinery/atmospherics/pipe/multiz, FALSE),
- ),
- "Binary" = list(
- new /datum/pipe_info/pipe("Manual Valve", /obj/machinery/atmospherics/components/binary/valve, TRUE),
- new /datum/pipe_info/pipe("Digital Valve", /obj/machinery/atmospherics/components/binary/valve/digital, TRUE),
- new /datum/pipe_info/pipe("Gas Pump", /obj/machinery/atmospherics/components/binary/pump, TRUE),
- new /datum/pipe_info/pipe("Volume Pump", /obj/machinery/atmospherics/components/binary/volume_pump, TRUE),
- new /datum/pipe_info/pipe("Passive Gate", /obj/machinery/atmospherics/components/binary/passive_gate, TRUE),
- new /datum/pipe_info/pipe("Pressure Valve", /obj/machinery/atmospherics/components/binary/pressure_valve, TRUE),
- new /datum/pipe_info/pipe("Temperature Gate", /obj/machinery/atmospherics/components/binary/temperature_gate, TRUE),
- new /datum/pipe_info/pipe("Temperature Pump", /obj/machinery/atmospherics/components/binary/temperature_pump, TRUE),
- ),
- "Devices" = list(
- new /datum/pipe_info/pipe("Gas Filter", /obj/machinery/atmospherics/components/trinary/filter, TRUE),
- new /datum/pipe_info/pipe("Gas Mixer", /obj/machinery/atmospherics/components/trinary/mixer, TRUE),
- new /datum/pipe_info/pipe("Connector", /obj/machinery/atmospherics/components/unary/portables_connector, TRUE),
- new /datum/pipe_info/pipe("Injector", /obj/machinery/atmospherics/components/unary/outlet_injector, TRUE),
- new /datum/pipe_info/pipe("Scrubber", /obj/machinery/atmospherics/components/unary/vent_scrubber, TRUE),
- new /datum/pipe_info/pipe("Unary Vent", /obj/machinery/atmospherics/components/unary/vent_pump, TRUE),
- new /datum/pipe_info/pipe("Passive Vent", /obj/machinery/atmospherics/components/unary/passive_vent, TRUE),
- new /datum/pipe_info/meter("Meter"),
- ),
- "Heat Exchange" = list(
- new /datum/pipe_info/pipe("Pipe", /obj/machinery/atmospherics/pipe/heat_exchanging/simple, FALSE),
- new /datum/pipe_info/pipe("Manifold", /obj/machinery/atmospherics/pipe/heat_exchanging/manifold, FALSE),
- new /datum/pipe_info/pipe("4-Way Manifold", /obj/machinery/atmospherics/pipe/heat_exchanging/manifold4w, FALSE),
- new /datum/pipe_info/pipe("Junction", /obj/machinery/atmospherics/pipe/heat_exchanging/junction, FALSE),
- new /datum/pipe_info/pipe("Heat Exchanger", /obj/machinery/atmospherics/components/unary/heat_exchanger, FALSE),
- )
-))
-
-GLOBAL_LIST_INIT(disposal_pipe_recipes, list(
- "Disposal Pipes" = list(
- new /datum/pipe_info/disposal("Pipe", /obj/structure/disposalpipe/segment, PIPE_BENDABLE),
- new /datum/pipe_info/disposal("Junction", /obj/structure/disposalpipe/junction, PIPE_TRIN_M),
- new /datum/pipe_info/disposal("Y-Junction", /obj/structure/disposalpipe/junction/yjunction),
- new /datum/pipe_info/disposal("Sort Junction", /obj/structure/disposalpipe/sorting/mail, PIPE_TRIN_M),
- new /datum/pipe_info/disposal("Rotator", /obj/structure/disposalpipe/rotator, PIPE_ONEDIR_FLIPPABLE),
- new /datum/pipe_info/disposal("Trunk", /obj/structure/disposalpipe/trunk),
- new /datum/pipe_info/disposal("Down Turn", /obj/structure/disposalpipe/trunk/multiz/down),
- new /datum/pipe_info/disposal("Up Turn", /obj/structure/disposalpipe/trunk/multiz),
- new /datum/pipe_info/disposal("Bin", /obj/machinery/disposal/bin, PIPE_ONEDIR),
- new /datum/pipe_info/disposal("Outlet", /obj/structure/disposaloutlet),
- new /datum/pipe_info/disposal("Chute", /obj/machinery/disposal/delivery_chute),
- )
-))
-
-GLOBAL_LIST_INIT(transit_tube_recipes, list(
- "Transit Tubes" = list(
- new /datum/pipe_info/transit("Straight Tube", /obj/structure/c_transit_tube, PIPE_STRAIGHT),
- new /datum/pipe_info/transit("Straight Tube with Crossing", /obj/structure/c_transit_tube/crossing, PIPE_STRAIGHT),
- new /datum/pipe_info/transit("Curved Tube", /obj/structure/c_transit_tube/curved, PIPE_UNARY_FLIPPABLE),
- new /datum/pipe_info/transit("Diagonal Tube", /obj/structure/c_transit_tube/diagonal, PIPE_STRAIGHT),
- new /datum/pipe_info/transit("Diagonal Tube with Crossing", /obj/structure/c_transit_tube/diagonal/crossing, PIPE_STRAIGHT),
- new /datum/pipe_info/transit("Junction", /obj/structure/c_transit_tube/junction, PIPE_UNARY_FLIPPABLE),
- ),
- "Station Equipment" = list(
- new /datum/pipe_info/transit("Through Tube Station", /obj/structure/c_transit_tube/station, PIPE_STRAIGHT),
- new /datum/pipe_info/transit("Terminus Tube Station", /obj/structure/c_transit_tube/station/reverse, PIPE_UNARY_FLIPPABLE),
- new /datum/pipe_info/transit("Through Tube Dispenser Station", /obj/structure/c_transit_tube/station/dispenser, PIPE_STRAIGHT),
- new /datum/pipe_info/transit("Terminus Tube Dispenser Station", /obj/structure/c_transit_tube/station/dispenser/reverse, PIPE_UNARY_FLIPPABLE),
- new /datum/pipe_info/transit("Transit Tube Pod", /obj/structure/c_transit_tube_pod, PIPE_ONEDIR),
- )
-))
-
-/datum/pipe_info
- var/name
- var/icon_state
- var/id = -1
- var/dirtype = PIPE_BENDABLE
- var/all_layers
-
-/datum/pipe_info/proc/get_preview(selected_dir, selected = FALSE)
- var/list/dirs
- switch(dirtype)
- if(PIPE_STRAIGHT, PIPE_BENDABLE)
- dirs = list("[NORTH]" = "Vertical", "[EAST]" = "Horizontal")
- if(dirtype == PIPE_BENDABLE)
- dirs += list("[NORTHWEST]" = "West to North", "[NORTHEAST]" = "North to East",
- "[SOUTHWEST]" = "South to West", "[SOUTHEAST]" = "East to South")
- if(PIPE_TRINARY)
- dirs = list("[NORTH]" = "West South East", "[SOUTH]" = "East North West",
- "[EAST]" = "North West South", "[WEST]" = "South East North")
- if(PIPE_TRIN_M)
- dirs = list("[NORTH]" = "North East South", "[SOUTHWEST]" = "North West South",
- "[NORTHEAST]" = "South East North", "[SOUTH]" = "South West North",
- "[WEST]" = "West North East", "[SOUTHEAST]" = "West South East",
- "[NORTHWEST]" = "East North West", "[EAST]" = "East South West",)
- if(PIPE_UNARY)
- dirs = list("[NORTH]" = "North", "[SOUTH]" = "South", "[WEST]" = "West", "[EAST]" = "East")
- if(PIPE_ONEDIR)
- dirs = list("[SOUTH]" = name)
- if(PIPE_UNARY_FLIPPABLE)
- dirs = list("[NORTH]" = "North", "[EAST]" = "East", "[SOUTH]" = "South", "[WEST]" = "West",
- "[NORTHEAST]" = "North Flipped", "[SOUTHEAST]" = "East Flipped", "[SOUTHWEST]" = "South Flipped", "[NORTHWEST]" = "West Flipped")
- if(PIPE_ONEDIR_FLIPPABLE)
- dirs = list("[SOUTH]" = name, "[SOUTHEAST]" = "[name] Flipped")
-
- var/list/rows = list()
- for(var/dir in dirs)
- var/numdir = text2num(dir)
- var/flipped = ((dirtype == PIPE_TRIN_M) || (dirtype == PIPE_UNARY_FLIPPABLE) || (dirtype == PIPE_ONEDIR_FLIPPABLE)) && (ISDIAGONALDIR(numdir))
- var/is_variant_selected = selected && (!selected_dir ? FALSE : (dirtype == PIPE_ONEDIR ? TRUE : (numdir == selected_dir)))
- rows += list(list(
- "selected" = is_variant_selected,
- "dir" = dir2text(numdir),
- "dir_name" = dirs[dir],
- "icon_state" = icon_state,
- "flipped" = flipped,
- ))
-
- return rows
-
-/datum/pipe_info/pipe/New(label, obj/machinery/atmospherics/path, use_five_layers)
- name = label
- id = path
- all_layers = use_five_layers
- icon_state = initial(path.pipe_state)
- var/obj/item/pipe/c = initial(path.construction_type)
- dirtype = initial(c.RPD_type)
-
-/datum/pipe_info/meter
- icon_state = "meter"
- dirtype = PIPE_ONEDIR
- all_layers = TRUE
-
-/datum/pipe_info/meter/New(label)
- name = label
-
-/datum/pipe_info/disposal/New(label, obj/path, dt=PIPE_UNARY)
- name = label
- id = path
-
- icon_state = initial(path.icon_state)
- if(ispath(path, /obj/structure/disposalpipe))
- icon_state = "con[icon_state]"
-
- dirtype = dt
-
-/datum/pipe_info/transit/New(label, obj/path, dt=PIPE_UNARY)
- name = label
- id = path
- dirtype = dt
- icon_state = initial(path.icon_state)
- if(dt == PIPE_UNARY_FLIPPABLE)
- icon_state = "[icon_state]_preview"
-
/obj/item/pipe_dispenser
name = "rapid pipe dispenser"
desc = "A device used to rapidly pipe things."
@@ -210,8 +61,6 @@ GLOBAL_LIST_INIT(transit_tube_recipes, list(
var/pipe_layers = PIPE_LAYER(3)
///Are we laying multiple layers per click
var/multi_layer = FALSE
- ///Layer for disposal ducts
- var/ducting_layer = DUCT_LAYER_DEFAULT
///Stores the current device to spawn
var/datum/pipe_info/recipe
///Stores the first atmos device
@@ -245,8 +94,7 @@ GLOBAL_LIST_INIT(transit_tube_recipes, list(
register_item_context()
/obj/item/pipe_dispenser/Destroy()
- qdel(spark_system)
- spark_system = null
+ QDEL_NULL(spark_system)
return ..()
/obj/item/pipe_dispenser/examine(mob/user)
@@ -254,6 +102,15 @@ GLOBAL_LIST_INIT(transit_tube_recipes, list(
. += span_notice("You can scroll your mouse wheel to change the piping layer.")
. += span_notice("You can right click a pipe to set the RPD to its color and layer.")
+/obj/item/pipe_dispenser/add_item_context(obj/item/source, list/context, atom/target, mob/living/user)
+ . = NONE
+
+ if(istype(target, /obj/machinery/atmospherics))
+ var/obj/machinery/atmospherics/atmos_target = target
+ if(atmos_target.pipe_color && atmos_target.piping_layer)
+ context[SCREENTIP_CONTEXT_RMB] = "Copy piping color and layer"
+ return CONTEXTUAL_SCREENTIP_SET
+
/obj/item/pipe_dispenser/equipped(mob/user, slot, initial)
. = ..()
if(slot & ITEM_SLOT_HANDS)
@@ -265,13 +122,6 @@ GLOBAL_LIST_INIT(transit_tube_recipes, list(
UnregisterSignal(user, COMSIG_MOUSE_SCROLL_ON)
return ..()
-/obj/item/pipe_dispenser/proc/get_active_pipe_layers()
- var/list/layer_nums = list()
- for(var/pipe_layer_number in 1 to 5)
- if(PIPE_LAYER(pipe_layer_number) & pipe_layers)
- layer_nums += pipe_layer_number
- return layer_nums
-
/obj/item/pipe_dispenser/cyborg_unequip(mob/user)
UnregisterSignal(user, COMSIG_MOUSE_SCROLL_ON)
return ..()
@@ -279,30 +129,23 @@ GLOBAL_LIST_INIT(transit_tube_recipes, list(
/obj/item/pipe_dispenser/attack_self(mob/user)
ui_interact(user)
-/obj/item/pipe_dispenser/pre_attack_secondary(obj/machinery/atmospherics/target, mob/user, params)
- if(!istype(target, /obj/machinery/atmospherics))
- return SECONDARY_ATTACK_CANCEL_ATTACK_CHAIN
- if(target.pipe_color && target.piping_layer)
- paint_color = GLOB.pipe_color_name[target.pipe_color]
- pipe_layers = PIPE_LAYER(target.piping_layer)
- balloon_alert(user, "color/layer copied")
- return SECONDARY_ATTACK_CANCEL_ATTACK_CHAIN
-
-/obj/item/pipe_dispenser/add_item_context(obj/item/source, list/context, atom/target, mob/living/user)
- . = ..()
- if(istype(target, /obj/machinery/atmospherics))
- var/obj/machinery/atmospherics/atmos_target = target
- if(atmos_target.pipe_color && atmos_target.piping_layer)
- context[SCREENTIP_CONTEXT_RMB] = "Copy piping color and layer"
- return CONTEXTUAL_SCREENTIP_SET
-
-
/obj/item/pipe_dispenser/suicide_act(mob/living/user)
user.visible_message(span_suicide("[user] points the end of the RPD down [user.p_their()] throat and presses a button! It looks like [user.p_theyre()] trying to commit suicide..."))
playsound(get_turf(user), SFX_TOOL_SWITCH, 20, TRUE)
playsound(get_turf(user), RPD_USE_SOUND, 50, TRUE)
return BRUTELOSS
+///Converts pipe_layers bitflag into its corresponding list of actual pipe layers
+/obj/item/pipe_dispenser/proc/get_active_pipe_layers()
+ PRIVATE_PROC(TRUE)
+ RETURN_TYPE(/list)
+
+ var/list/layer_nums = list()
+ for(var/pipe_layer_number in 1 to MAX_PIPE_LAYERS)
+ if(PIPE_LAYER(pipe_layer_number) & pipe_layers)
+ layer_nums += pipe_layer_number
+ return layer_nums
+
/obj/item/pipe_dispenser/ui_assets(mob/user)
return list(
get_asset_datum(/datum/asset/spritesheet/pipes),
@@ -315,15 +158,16 @@ GLOBAL_LIST_INIT(transit_tube_recipes, list(
ui.open()
/obj/item/pipe_dispenser/ui_static_data(mob/user)
- var/list/data = list("paint_colors" = GLOB.pipe_paint_colors)
- return data
+ return list(
+ "paint_colors" = GLOB.pipe_paint_colors,
+ "max_pipe_layers" = MAX_PIPE_LAYERS,
+ )
/obj/item/pipe_dispenser/ui_data(mob/user)
var/list/data = list(
"category" = category,
"multi_layer" = multi_layer,
"pipe_layers" = pipe_layers,
- "ducting_layer" = ducting_layer,
"categories" = list(),
"selected_recipe" = recipe.name,
"selected_color" = paint_color,
@@ -370,10 +214,11 @@ GLOBAL_LIST_INIT(transit_tube_recipes, list(
playsound(src, SFX_TOOL_SWITCH, 20, TRUE)
- var/playeffect = TRUE
switch(action)
if("color")
paint_color = params["paint_color"]
+ return TRUE
+
if("category")
category = text2num(params["category"])
switch(category)
@@ -384,66 +229,87 @@ GLOBAL_LIST_INIT(transit_tube_recipes, list(
if(TRANSIT_CATEGORY)
recipe = first_transit
p_dir = NORTH
- playeffect = FALSE
+ return TRUE
+
if("pipe_layers")
var/selected_layers = text2num(params["pipe_layers"])
+
+ //is valid
var/valid_layer = FALSE
- for(var/pipe_layer_number in 1 to 5)
+ for(var/pipe_layer_number in 1 to MAX_PIPE_LAYERS)
if(!(PIPE_LAYER(pipe_layer_number) & selected_layers))
continue
valid_layer = TRUE
+ break
if(!valid_layer)
- return
+ return FALSE
+
+ //append or set the layer
if(multi_layer)
if(pipe_layers != selected_layers)
pipe_layers ^= selected_layers
else
pipe_layers = selected_layers
- playeffect = FALSE
+
+ return TRUE
+
if("toggle_multi_layer")
if(multi_layer)
pipe_layers = PIPE_LAYER(max(get_active_pipe_layers()))
multi_layer = !multi_layer
- if("ducting_layer")
- ducting_layer = text2num(params["ducting_layer"])
- playeffect = FALSE
+
if("pipe_type")
var/static/list/recipes
if(!recipes)
recipes = GLOB.disposal_pipe_recipes + GLOB.atmos_pipe_recipes + GLOB.transit_tube_recipes
recipe = recipes[params["category"]][text2num(params["pipe_type"])]
p_dir = NORTH
+
if("setdir")
p_dir = text2dir(params["dir"])
p_flipped = text2num(params["flipped"])
- playeffect = FALSE
+ return TRUE
+
if("mode")
var/selected_mode = text2num(params["mode"])
mode ^= selected_mode
+
if("init_dir_setting")
var/target_dir = p_init_dir ^ text2dir(params["dir_flag"])
// Refuse to create a smart pipe that can only connect in one direction (it would act weirdly and lack an icon)
if (ISNOTSTUB(target_dir))
p_init_dir = target_dir
else
- to_chat(usr, span_warning("\The [src]'s screen flashes a warning: Can't configure a pipe to only connect in one direction."))
- playeffect = FALSE
+ to_chat(ui.user, span_warning("\The [src]'s screen flashes a warning: Can't configure a pipe to only connect in one direction."))
+ return FALSE
+
if("init_reset")
p_init_dir = ALL_CARDINALS
- if(playeffect)
- spark_system.start()
- playsound(get_turf(src), 'sound/effects/pop.ogg', 50, FALSE)
+
+ spark_system.start()
+ playsound(get_turf(src), 'sound/effects/pop.ogg', 50, FALSE)
return TRUE
-/obj/item/pipe_dispenser/pre_attack(atom/atom_to_attack, mob/user, params)
- if(!ISADVANCEDTOOLUSER(user) || istype(atom_to_attack, /turf/open/space/transit))
- return ..()
+/obj/item/pipe_dispenser/interact_with_atom(atom/attack_target, mob/living/user, list/modifiers)
+ . = NONE
- if(istype(atom_to_attack, /obj/item/rpd_upgrade))
- install_upgrade(atom_to_attack, user)
- return TRUE
+ if(!ISADVANCEDTOOLUSER(user) || HAS_TRAIT(attack_target, TRAIT_COMBAT_MODE_SKIP_INTERACTION) || istype(attack_target, /turf/open/space/transit))
+ return
- var/atom/attack_target = atom_to_attack
+ if(istype(attack_target, /obj/item/rpd_upgrade))
+ var/obj/item/rpd_upgrade/rpd_disk = attack_target
+
+ // Check if the upgrade's already present
+ if(rpd_disk.upgrade_flags & upgrade_flags)
+ balloon_alert(user, "already installed!")
+ return ITEM_INTERACT_BLOCKING
+
+ // Adds the upgrade from the disk and then deletes the disk
+ upgrade_flags |= rpd_disk.upgrade_flags
+ playsound(loc, 'sound/machines/click.ogg', 50, vary = TRUE)
+ balloon_alert(user, "upgrade installed")
+ qdel(rpd_disk)
+ return ITEM_INTERACT_SUCCESS
//So that changing the menu settings doesn't affect the pipes already being built.
var/queued_pipe_type = recipe.id
@@ -454,20 +320,29 @@ GLOBAL_LIST_INIT(transit_tube_recipes, list(
if((mode & DESTROY_MODE) && (upgrade_flags & RPD_UPGRADE_UNWRENCH) && istype(attack_target, /obj/machinery/atmospherics) && !(DOING_INTERACTION_WITH_TARGET(user, attack_target)))
attack_target = attack_target.wrench_act(user, src)
if(!isatom(attack_target)) //can return null, FALSE if do_after() fails see /obj/machinery/atmospherics/wrench_act()
- return TRUE
+ return ITEM_INTERACT_FAILURE
if(istype(attack_target, /obj/machinery/atmospherics) && (mode & BUILD_MODE))
attack_target = get_turf(attack_target)
- var/can_make_pipe = check_can_make_pipe(attack_target)
-
- . = TRUE
-
- if((mode & DESTROY_MODE) && istype(attack_target, /obj/item/pipe) || istype(attack_target, /obj/structure/disposalconstruct) || istype(attack_target, /obj/structure/c_transit_tube) || istype(attack_target, /obj/structure/c_transit_tube_pod) || istype(attack_target, /obj/item/pipe_meter) || istype(attack_target, /obj/structure/disposalpipe/broken))
- playsound(get_turf(src), SFX_TOOL_SWITCH, 20, TRUE)
- playsound(get_turf(src), RPD_USE_SOUND, 50, TRUE)
+ var/can_destroy = FALSE
+ if((mode & DESTROY_MODE) && istype(attack_target, /obj/item/pipe))
+ can_destroy = TRUE
+ if(!can_destroy)
+ var/static/list/destroyables = list(
+ /obj/structure/disposalconstruct,
+ /obj/structure/c_transit_tube,
+ /obj/structure/c_transit_tube_pod,
+ /obj/item/pipe_meter,
+ /obj/structure/disposalpipe/broken
+ )
+ can_destroy = is_type_in_list(attack_target, destroyables)
+ if(can_destroy)
+ var/turf/ground = get_turf(src)
+ playsound(ground, SFX_TOOL_SWITCH, 20, TRUE)
+ playsound(ground, RPD_USE_SOUND, 50, TRUE)
qdel(attack_target)
- return
+ return ITEM_INTERACT_SUCCESS
if(mode & REPROGRAM_MODE)
// If this is a placed smart pipe, try to reprogram it
@@ -475,16 +350,16 @@ GLOBAL_LIST_INIT(transit_tube_recipes, list(
if(istype(target_smart_pipe))
if(target_smart_pipe.dir == ALL_CARDINALS)
balloon_alert(user, "has no unconnected directions!")
- return
+ return ITEM_INTERACT_FAILURE
var/old_init_dir = target_smart_pipe.get_init_directions()
if(old_init_dir == p_init_dir)
balloon_alert(user, "already configured!")
- return
+ return ITEM_INTERACT_FAILURE
// Check for differences in unconnected directions
var/target_differences = (p_init_dir ^ old_init_dir) & ~target_smart_pipe.connections
if(!target_differences)
balloon_alert(user, "already configured for its directions!")
- return
+ return ITEM_INTERACT_FAILURE
playsound(get_turf(src), SFX_TOOL_SWITCH, 20, TRUE)
@@ -494,7 +369,7 @@ GLOBAL_LIST_INIT(transit_tube_recipes, list(
// Double check to make sure that nothing has changed. If anything we were about to change was connected during do_after, abort
if(target_differences & target_smart_pipe.connections)
balloon_alert(user, "can't configure for its direction!")
- return
+ return ITEM_INTERACT_FAILURE
// Grab the current initializable directions, which may differ from old_init_dir if someone else was working on the same pipe at the same time
var/current_init_dir = target_smart_pipe.get_init_directions()
// Access p_init_dir directly. The RPD can change target layer and initializable directions (though not pipe type or dir) while working to dispense and connect a component,
@@ -503,7 +378,7 @@ GLOBAL_LIST_INIT(transit_tube_recipes, list(
// Don't make a smart pipe with only one connection
if(ISSTUB(new_init_dir))
balloon_alert(user, "no one directional pipes allowed!")
- return
+ return ITEM_INTERACT_FAILURE
target_smart_pipe.set_init_directions(new_init_dir)
// We're now reconfigured.
// We can never disconnect from existing connections, but we can connect to previously unconnected directions, and should immediately do so
@@ -529,108 +404,146 @@ GLOBAL_LIST_INIT(transit_tube_recipes, list(
// Finally, update our internal state - update_pipe_icon also updates dir and connections
target_smart_pipe.update_pipe_icon()
user.visible_message(span_notice("[user] reprograms \the [target_smart_pipe]."), span_notice("You reprogram \the [target_smart_pipe]."))
- return
+ return ITEM_INTERACT_SUCCESS
+
// If this is an unplaced smart pipe, try to reprogram it
var/obj/item/pipe/quaternary/target_unsecured_pipe = attack_target
if(istype(target_unsecured_pipe) && ispath(target_unsecured_pipe.pipe_type, /obj/machinery/atmospherics/pipe/smart))
// An unplaced pipe never has any existing connections, so just directly assign the new configuration
target_unsecured_pipe.p_init_dir = p_init_dir
target_unsecured_pipe.update()
+ return ITEM_INTERACT_SUCCESS
if(mode & BUILD_MODE)
switch(category) //if we've gotten this var, the target is valid
if(ATMOS_CATEGORY) //Making pipes
- if(!do_pipe_build(attack_target, user, params))
- return ..()
+ return do_pipe_build(attack_target, user) ? ITEM_INTERACT_SUCCESS : ITEM_INTERACT_FAILURE
if(DISPOSALS_CATEGORY) //Making disposals pipes
- if(!can_make_pipe)
- return ..()
+ if(!check_can_make_pipe(attack_target))
+ return ITEM_INTERACT_FAILURE
attack_target = get_turf(attack_target)
if(isclosedturf(attack_target))
balloon_alert(user, "target is blocked!")
- return
+ return ITEM_INTERACT_FAILURE
playsound(get_turf(src), SFX_TOOL_SWITCH, 20, TRUE)
- if(do_after(user, disposal_build_speed, target = attack_target))
- var/obj/structure/disposalconstruct/new_disposals_segment = new (attack_target, queued_pipe_type, queued_pipe_dir, queued_pipe_flipped)
- if(!new_disposals_segment.can_place())
- balloon_alert(user, "not enough room!")
- qdel(new_disposals_segment)
- return
+ if(!do_after(user, disposal_build_speed, target = attack_target))
+ return ITEM_INTERACT_FAILURE
- playsound(get_turf(src), RPD_USE_SOUND, 50, TRUE)
+ var/obj/structure/disposalconstruct/new_disposals_segment = new (attack_target, queued_pipe_type, queued_pipe_dir, queued_pipe_flipped)
- new_disposals_segment.add_fingerprint(usr)
- new_disposals_segment.update_appearance()
- if(mode & WRENCH_MODE)
- new_disposals_segment.wrench_act(user, src)
- return
+ if(!new_disposals_segment.can_place())
+ balloon_alert(user, "not enough room!")
+ qdel(new_disposals_segment)
+ return ITEM_INTERACT_FAILURE
+
+ playsound(get_turf(src), RPD_USE_SOUND, 50, TRUE)
+
+ new_disposals_segment.add_fingerprint(user)
+ new_disposals_segment.update_appearance()
+ if(mode & WRENCH_MODE)
+ new_disposals_segment.wrench_act(user, src)
+ return ITEM_INTERACT_SUCCESS
if(TRANSIT_CATEGORY) //Making transit tubes
- if(!can_make_pipe)
- return ..()
+ if(!check_can_make_pipe(attack_target))
+ return ITEM_INTERACT_FAILURE
attack_target = get_turf(attack_target)
if(isclosedturf(attack_target))
balloon_alert(user, "something in the way!")
- return
+ return ITEM_INTERACT_FAILURE
var/turf/target_turf = get_turf(attack_target)
if(target_turf.is_blocked_turf(exclude_mobs = TRUE))
balloon_alert(user, "something in the way!")
- return
+ return ITEM_INTERACT_FAILURE
playsound(get_turf(src), SFX_TOOL_SWITCH, 20, TRUE)
- if(do_after(user, transit_build_speed, target = attack_target))
- playsound(get_turf(src), RPD_USE_SOUND, 50, TRUE)
- if(queued_pipe_type == /obj/structure/c_transit_tube_pod)
- var/obj/structure/c_transit_tube_pod/pod = new /obj/structure/c_transit_tube_pod(attack_target)
- pod.add_fingerprint(usr)
- if(mode & WRENCH_MODE)
- pod.wrench_act(user, src)
-
- else
- var/obj/structure/c_transit_tube/tube = new queued_pipe_type(attack_target)
- tube.setDir(queued_pipe_dir)
-
- if(queued_pipe_flipped)
- tube.setDir(turn(queued_pipe_dir, 45 + ROTATION_FLIP))
- tube.post_rotation(user, ROTATION_FLIP)
-
- tube.add_fingerprint(usr)
- if(mode & WRENCH_MODE)
- tube.wrench_act(user, src)
- return
- else
- return ..()
+ if(!do_after(user, transit_build_speed, target = attack_target))
+ return ITEM_INTERACT_FAILURE
+
+ playsound(get_turf(src), RPD_USE_SOUND, 50, TRUE)
+ if(queued_pipe_type == /obj/structure/c_transit_tube_pod)
+ var/obj/structure/c_transit_tube_pod/pod = new /obj/structure/c_transit_tube_pod(attack_target)
+ pod.add_fingerprint(user)
+ if(mode & WRENCH_MODE)
+ pod.wrench_act(user, src)
+
+ else
+ var/obj/structure/c_transit_tube/tube = new queued_pipe_type(attack_target)
+ tube.setDir(queued_pipe_dir)
+ if(queued_pipe_flipped)
+ tube.setDir(turn(queued_pipe_dir, 45 + ROTATION_FLIP))
+ tube.post_rotation(user, ROTATION_FLIP)
+
+ tube.add_fingerprint(user)
+ if(mode & WRENCH_MODE)
+ tube.wrench_act(user, src)
+ return ITEM_INTERACT_SUCCESS
+
+/obj/item/pipe_dispenser/interact_with_atom_secondary(obj/machinery/atmospherics/target, mob/living/user, list/modifiers)
+ . = NONE
+
+ if(!istype(target))
+ return
+
+ if(target.pipe_color && target.piping_layer)
+ paint_color = GLOB.pipe_color_name[target.pipe_color]
+ pipe_layers = PIPE_LAYER(target.piping_layer)
+ balloon_alert(user, "color/layer copied")
+ return ITEM_INTERACT_SUCCESS
+
+/**
+ * Can we make a pipe on the target
+ * Arguments
+ *
+ * * atom/target_of_attack - the target we are trying to build a pipe on
+ */
/obj/item/pipe_dispenser/proc/check_can_make_pipe(atom/target_of_attack)
+ PRIVATE_PROC(TRUE)
+ SHOULD_BE_PURE(TRUE)
+
+ if(isturf(target_of_attack))
+ return TRUE
+
//make sure what we're clicking is valid for the current category
- var/static/list/make_pipe_whitelist = typecacheof(list(/obj/structure/lattice, /obj/structure/girder, /obj/item/pipe, /obj/structure/window, /obj/structure/grille))
- var/can_we_make_pipe = (isturf(target_of_attack) || is_type_in_typecache(target_of_attack, make_pipe_whitelist))
- return can_we_make_pipe
+ var/static/list/make_pipe_whitelist = typecacheof(
+ list(
+ /obj/structure/lattice,
+ /obj/structure/girder,
+ /obj/item/pipe,
+ /obj/structure/window,
+ /obj/structure/grille
+ )
+ )
+ return is_type_in_typecache(target_of_attack, make_pipe_whitelist)
+
+/**
+ * Build pipe on the target
+ * Arguments
+ *
+ * * atom/atom_to_target - the target we are trying to build the pipe on
+ * * mob/user - mob performing the action
+ */
+/obj/item/pipe_dispenser/proc/do_pipe_build(atom/atom_to_target, mob/user)
+ PRIVATE_PROC(TRUE)
+
+ if(!check_can_make_pipe(atom_to_target))
+ return FALSE
-/obj/item/pipe_dispenser/proc/do_pipe_build(atom/atom_to_target, mob/user, params)
//So that changing the menu settings doesn't affect the pipes already being built.
var/queued_pipe_type = recipe.id
var/queued_pipe_dir = p_dir
var/queued_pipe_flipped = p_flipped
- var/can_make_pipe = check_can_make_pipe(atom_to_target)
var/list/pipe_layer_numbers = get_active_pipe_layers()
- var/continued_build = FALSE
- for(var/pipe_layer_num in 1 to length(pipe_layer_numbers))
- var/layer_to_build = pipe_layer_numbers[pipe_layer_num]
- if(layer_to_build != pipe_layer_numbers[1])
- continued_build = TRUE
- if(!layer_to_build)
- return FALSE
- if(!can_make_pipe)
- return FALSE
+ for(var/layer_to_build in pipe_layer_numbers)
playsound(get_turf(src), SFX_TOOL_SWITCH, 20, vary = TRUE)
- if(!continued_build && !do_after(user, atmos_build_speed, target = atom_to_target))
+ if(!do_after(user, atmos_build_speed, target = atom_to_target))
return FALSE
- if(!recipe.all_layers && (layer_to_build == 1 || layer_to_build == 5))
+ if(!recipe.all_layers && (layer_to_build == 1 || layer_to_build == MAX_PIPE_LAYERS))
balloon_alert(user, "can't build on layer [layer_to_build]!")
if(multi_layer)
continue
@@ -657,7 +570,7 @@ GLOBAL_LIST_INIT(transit_tube_recipes, list(
new_flippable_pipe.flipped = queued_pipe_flipped
pipe_type.update()
- pipe_type.add_fingerprint(usr)
+ pipe_type.add_fingerprint(user)
pipe_type.set_piping_layer(layer_to_build)
if(ispath(queued_pipe_type, /obj/machinery/atmospherics) && !ispath(queued_pipe_type, /obj/machinery/atmospherics/pipe/color_adapter))
pipe_type.add_atom_colour(GLOB.pipe_paint_colors[paint_color], FIXED_COLOUR_PRIORITY)
@@ -665,24 +578,6 @@ GLOBAL_LIST_INIT(transit_tube_recipes, list(
pipe_type.wrench_act(user, src)
return TRUE
-/obj/item/pipe_dispenser/attackby(obj/item/item, mob/user, params)
- if(istype(item, /obj/item/rpd_upgrade))
- install_upgrade(item, user)
- return TRUE
- return ..()
-
-/// Installs an upgrade into the RPD after checking if it is already installed
-/obj/item/pipe_dispenser/proc/install_upgrade(obj/item/rpd_upgrade/rpd_disk, mob/user)
- // Check if the upgrade's already present
- if(rpd_disk.upgrade_flags & upgrade_flags)
- balloon_alert(user, "already installed!")
- return
- // Adds the upgrade from the disk and then deletes the disk
- upgrade_flags |= rpd_disk.upgrade_flags
- playsound(loc, 'sound/machines/click.ogg', 50, vary = TRUE)
- balloon_alert(user, "upgrade installed")
- qdel(rpd_disk)
-
///Changes the piping layer when the mousewheel is scrolled up or down.
/obj/item/pipe_dispenser/proc/mouse_wheeled(mob/source_mob, atom/A, delta_x, delta_y, params)
SIGNAL_HANDLER
@@ -695,7 +590,7 @@ GLOBAL_LIST_INIT(transit_tube_recipes, list(
return
if(delta_y < 0)
- pipe_layers = min(PIPE_LAYER(5), pipe_layers << 1)
+ pipe_layers = min(PIPE_LAYER(MAX_PIPE_LAYERS), pipe_layers << 1)
else if(delta_y > 0)
pipe_layers = max(PIPE_LAYER(1), pipe_layers >> 1)
else //mice with side-scrolling wheels are apparently a thing and fuck this up
@@ -730,3 +625,4 @@ GLOBAL_LIST_INIT(transit_tube_recipes, list(
#undef PIPE_LAYER
#undef RPD_USE_SOUND
+#undef MAX_PIPE_LAYERS
diff --git a/code/game/objects/items/robot/robot_upgrades.dm b/code/game/objects/items/robot/robot_upgrades.dm
index b65c4a7ae90df..bbebd91e7cb0c 100644
--- a/code/game/objects/items/robot/robot_upgrades.dm
+++ b/code/game/objects/items/robot/robot_upgrades.dm
@@ -647,27 +647,8 @@
require_model = TRUE
model_type = list(/obj/item/robot_model/engineering, /obj/item/robot_model/saboteur)
model_flags = BORG_MODEL_ENGINEERING
-
items_to_add = list(/obj/item/inducer/cyborg)
-/obj/item/inducer/cyborg
- name = "Internal inducer"
- icon = 'icons/obj/tools.dmi'
- icon_state = "inducer-engi"
- powerdevice = null
-
-/obj/item/inducer/cyborg/get_cell()
- var/obj/item/robot_model/possible_model = loc
- var/mob/living/silicon/robot/silicon_friend = istype(possible_model) ? possible_model.robot : possible_model
- if(istype(silicon_friend))
- . = silicon_friend.cell
-
-/obj/item/inducer/cyborg/screwdriver_act(mob/living/user, obj/item/tool)
- return NONE
-
-/obj/item/inducer/cyborg/item_interaction(mob/living/user, obj/item/tool, list/modifiers)
- return ITEM_INTERACT_FAILURE
-
/obj/item/borg/upgrade/pinpointer
name = "medical cyborg crew pinpointer"
desc = "A crew pinpointer module for the medical cyborg. Permits remote access to the crew monitor."
diff --git a/code/game/objects/items/stacks/medical.dm b/code/game/objects/items/stacks/medical.dm
index 0f435f2a6198a..6eda30d328dea 100644
--- a/code/game/objects/items/stacks/medical.dm
+++ b/code/game/objects/items/stacks/medical.dm
@@ -32,16 +32,35 @@
var/sanitization
/// How much we add to flesh_healing for burn wounds on application
var/flesh_regeneration
- /// Time it takes to assess injuries when looping healing
- var/assessing_injury_delay = 1 SECONDS
/obj/item/stack/medical/interact_with_atom(atom/interacting_with, mob/living/user, list/modifiers)
if(!isliving(interacting_with))
return NONE
- if(!begin_heal_loop(interacting_with, user))
+ if(!begin_heal_loop(interacting_with, user, auto_change_zone = TRUE))
return NONE // [ITEM_INTERACT_BLOCKING] would be redundant as we are nobludgeon
return ITEM_INTERACT_SUCCESS
+/obj/item/stack/medical/interact_with_atom_secondary(atom/interacting_with, mob/living/user, list/modifiers)
+ if(!isliving(interacting_with))
+ return NONE
+ if(!begin_heal_loop(interacting_with, user, auto_change_zone = FALSE))
+ return NONE // see above
+ return ITEM_INTERACT_SUCCESS
+
+/obj/item/stack/medical/Initialize(mapload, new_amount, merge, list/mat_override, mat_amt)
+ . = ..()
+ register_item_context()
+
+/obj/item/stack/medical/add_item_context(obj/item/source, list/context, atom/target, mob/living/user)
+ if(!isliving(target))
+ return NONE
+ if(iscarbon(target))
+ context[SCREENTIP_CONTEXT_LMB] = "Auto Heal"
+ context[SCREENTIP_CONTEXT_RMB] = "Manual Heal"
+ else
+ context[SCREENTIP_CONTEXT_LMB] = "Heal"
+ return CONTEXTUAL_SCREENTIP_SET
+
/obj/item/stack/medical/apply_fantasy_bonuses(bonus)
. = ..()
if(heal_brute)
@@ -65,134 +84,251 @@
/// Used to begin the recursive healing loop.
/// Returns TRUE if we entered the loop, FALSE if we didn't
-/obj/item/stack/medical/proc/begin_heal_loop(mob/living/patient, mob/user)
- if(!can_heal(patient, user))
+/obj/item/stack/medical/proc/begin_heal_loop(mob/living/patient, mob/living/user, auto_change_zone = TRUE)
+ if(DOING_INTERACTION_WITH_TARGET(user, patient))
return FALSE
-
- INVOKE_ASYNC(src, PROC_REF(try_heal), patient, user)
+ var/heal_zone = check_zone(user.zone_selected)
+ if(!try_heal_checks(patient, user, heal_zone))
+ return FALSE
+ SSblackbox.record_feedback("nested tally", "medical_item_used", 1, list(type, auto_change_zone ? "auto" : "manual"))
+ patient.balloon_alert(user, "treating [parse_zone(heal_zone)]...")
+ INVOKE_ASYNC(src, PROC_REF(try_heal), patient, user, heal_zone, FALSE, iscarbon(patient) && auto_change_zone) // auto change is useless for non-carbons
return TRUE
-/// Checks if the passed patient can be healed by the passed user
-/obj/item/stack/medical/proc/can_heal(mob/living/patient, mob/user)
- return patient.try_inject(user, injection_flags = INJECT_TRY_SHOW_ERROR_MESSAGE)
-
-/// In which we print the message that we're starting to heal someone, then we try healing them. Does the do_after whether or not it can actually succeed on a targeted mob
-/obj/item/stack/medical/proc/try_heal(mob/living/patient, mob/user, silent = FALSE, looping = FALSE)
- if(!try_heal_checks(patient, user, heal_brute, heal_burn, looping))
- return
- var/new_self_delay = self_delay
- var/new_other_delay = other_delay
- if(iscarbon(patient))
- new_self_delay = looping ? clamp((self_delay - assessing_injury_delay), 0, self_delay) : self_delay
- new_other_delay = looping ? clamp((other_delay - assessing_injury_delay), 0, other_delay) : other_delay
+/**
+ * What actually handles printing the message that we're starting to heal someone, and trying to heal them
+ *
+ * This proc is recursively called until we run out of charges OR until the patient is fully healed
+ * OR until the target zone is fully healed (if auto_change_zone is FALSE)
+ *
+ * * patient - The mob we're trying to heal
+ * * user - The mob that's trying to heal the patient
+ * * healed_zone - The zone we're trying to heal on the patient
+ * Disregarded if auto_change_zone is TRUE
+ * * silent - If we should not print the message that we're starting to heal the patient
+ * Used so looping the proc doesn't spam messages
+ * * auto_change_zone - Handles the behavior when we finish healing a zone
+ * If auto_change_zone is set to TRUE, it picks the next most damaged zone to heal
+ * If auto_change_zone is set to FALSE, it'll give the user a chance to pick a new zone to heal
+ */
+/obj/item/stack/medical/proc/try_heal(mob/living/patient, mob/living/user, healed_zone, silent = FALSE, auto_change_zone = TRUE)
if(patient == user)
if(!silent)
+ user.balloon_alert(user, "treating [parse_zone(healed_zone)]...")
user.visible_message(
span_notice("[user] starts to apply [src] on [user.p_them()]self..."),
span_notice("You begin applying [src] on yourself..."),
+ visible_message_flags = ALWAYS_SHOW_SELF_MESSAGE,
)
if(!do_after(
user,
- new_self_delay,
+ self_delay * (auto_change_zone ? 1 : 0.9),
patient,
- extra_checks = CALLBACK(src, PROC_REF(can_heal), patient, user),
+ extra_checks = CALLBACK(src, PROC_REF(can_heal), patient, user, healed_zone),
))
return
+ if(!auto_change_zone)
+ healed_zone = check_zone(user.zone_selected)
+ if(!try_heal_checks(patient, user, healed_zone))
+ return
else if(other_delay)
if(!silent)
+ patient.balloon_alert(user, "treating [parse_zone(healed_zone)]...")
user.visible_message(
span_notice("[user] starts to apply [src] on [patient]."),
span_notice("You begin applying [src] on [patient]..."),
+ visible_message_flags = ALWAYS_SHOW_SELF_MESSAGE,
)
if(!do_after(
user,
- new_other_delay,
+ other_delay * (auto_change_zone ? 1 : 0.9),
patient,
- extra_checks = CALLBACK(src, PROC_REF(can_heal), patient, user),
+ extra_checks = CALLBACK(src, PROC_REF(can_heal), patient, user, healed_zone),
))
return
+ if(!auto_change_zone)
+ healed_zone = check_zone(user.zone_selected)
+ if(!try_heal_checks(patient, user, healed_zone))
+ return
- if(!heal(patient, user))
- return
- log_combat(user, patient, "healed", name)
+ else
+ if(!silent)
+ user.visible_message(
+ span_notice("[user] applies [src] on [patient]."),
+ span_notice("You apply [src] on [patient]."),
+ visible_message_flags = ALWAYS_SHOW_SELF_MESSAGE,
+ )
+
+ if(iscarbon(patient))
+ if(!heal_carbon(patient, user, healed_zone))
+ return
+ else if(isanimal_or_basicmob(patient))
+ if(!heal_simplemob(patient, user))
+ return
+ else
+ CRASH("Stack medical item healing a non-carbon, non-animal mob [patient] ([patient.type])")
+
+ log_combat(user, patient, "healed", src)
if(!use(1) || !repeating || amount <= 0)
+ var/atom/alert_loc = QDELETED(src) ? user : src
+ alert_loc.balloon_alert(user, repeating ? "all used up!" : "treated [parse_zone(healed_zone)]")
+ return
+
+ // first, just try looping
+ // 1. we can keep healing the current target
+ // 2. the user's changed their target (and thus we should heal that limb instead)
+ var/preferred_target = check_zone(user.zone_selected)
+ if(try_heal_checks(patient, user, preferred_target, silent = TRUE))
+ if(preferred_target != healed_zone)
+ patient.balloon_alert(user, "treating [parse_zone(preferred_target)]...")
+ try_heal(patient, user, preferred_target, TRUE, auto_change_zone)
return
- if(!can_heal(patient, user))
+
+ // second, handle what happens otherwise
+ if(!iscarbon(patient))
+ // behavior 0: non-carbons have no limbs so we can assume they are fully healed
+ patient.balloon_alert(user, "fully treated")
+ else if(auto_change_zone)
+ // behavior 1: automatically pick another zone to heal
+ try_heal_auto_change_zone(patient, user, preferred_target, healed_zone)
+ else
+ // behavior 2: assess injury, giving the user time to manually pick another zone
+ try_heal_manual_target(patient, user)
+
+/obj/item/stack/medical/proc/try_heal_auto_change_zone(mob/living/carbon/patient, mob/living/user, preferred_target, last_zone)
+ PRIVATE_PROC(TRUE)
+
+ var/list/other_affected_limbs = list()
+ for(var/obj/item/bodypart/limb as anything in patient.bodyparts)
+ if(!try_heal_checks(patient, user, limb.body_zone, silent = TRUE))
+ continue
+ other_affected_limbs += limb.body_zone
+
+ if(!length(other_affected_limbs))
+ patient.balloon_alert(user, "fully treated")
return
- try_heal(patient, user, silent = TRUE, looping = TRUE)
-/// Apply the actual effects of the healing if it's a simple animal, goes to [/obj/item/stack/medical/proc/heal_carbon] if it's a carbon, returns TRUE if it works, FALSE if it doesn't
-/obj/item/stack/medical/proc/heal(mob/living/patient, mob/user)
+ var/next_picked = (preferred_target in other_affected_limbs) ? preferred_target : other_affected_limbs[1]
+ if(next_picked != last_zone)
+ user.balloon_alert(user, "treating [parse_zone(next_picked)]...")
+ try_heal(patient, user, next_picked, silent = TRUE, auto_change_zone = TRUE)
+
+/obj/item/stack/medical/proc/try_heal_manual_target(mob/living/carbon/patient, mob/living/user)
+ PRIVATE_PROC(TRUE)
+
+ patient.balloon_alert(user, "assessing injury...")
+ if(!do_after(user, 1 SECONDS, patient))
+ return
+ var/new_zone = check_zone(user.zone_selected)
+ if(!try_heal_checks(patient, user, new_zone))
+ return
+ patient.balloon_alert(user, "treating [parse_zone(new_zone)]...")
+ try_heal(patient, user, new_zone, silent = TRUE, auto_change_zone = FALSE)
+
+/// Checks if the passed patient can be healed by the passed user
+/obj/item/stack/medical/proc/can_heal(mob/living/patient, mob/living/user, healed_zone, silent = FALSE)
+ return patient.try_inject(user, healed_zone, injection_flags = silent ? NONE : INJECT_TRY_SHOW_ERROR_MESSAGE)
+
+/// Checks a bunch of stuff to see if we can heal the patient, including can_heal
+/// Gives a feedback if we can't ultimatly heal the patient (unless silent is TRUE)
+/obj/item/stack/medical/proc/try_heal_checks(mob/living/patient, mob/living/user, healed_zone, silent = FALSE)
+ if(!can_heal(patient, user, healed_zone, silent))
+ // has its own feedback
+ return FALSE
if(patient.stat == DEAD)
- patient.balloon_alert(user, "they're dead!")
+ if(!silent)
+ patient.balloon_alert(user, "[patient.p_theyre()] dead!")
return FALSE
- if(iscarbon(patient))
- return heal_carbon(patient, user, heal_brute, heal_burn)
- else if(isanimal_or_basicmob(patient))
- if(!try_heal_checks(patient, user, heal_brute, heal_burn))
- return FALSE
- if(patient.heal_bodypart_damage((heal_brute * patient.maxHealth/100)))
- user.visible_message(span_infoplain(span_green("[user] applies [src] on [patient].")), span_infoplain(span_green("You apply [src] on [patient].")))
- return TRUE
- patient.balloon_alert(user, "can't heal [patient]!")
- return FALSE
-/obj/item/stack/medical/proc/try_heal_checks(mob/living/patient, mob/user, brute, burn, looping = FALSE)
if(iscarbon(patient))
- if(looping)
- balloon_alert(user, "assessing injuries...")
- if(!do_after(user, assessing_injury_delay, patient))
- return FALSE
var/mob/living/carbon/carbon_patient = patient
- var/obj/item/bodypart/affecting = carbon_patient.get_bodypart(check_zone(user.zone_selected))
+ var/obj/item/bodypart/affecting = carbon_patient.get_bodypart(healed_zone)
if(!affecting) //Missing limb?
- carbon_patient.balloon_alert(user, "no [parse_zone(user.zone_selected)]!")
+ if(!silent)
+ carbon_patient.balloon_alert(user, "no [parse_zone(healed_zone)]!")
return FALSE
if(!IS_ORGANIC_LIMB(affecting)) //Limb must be organic to be healed - RR
- carbon_patient.balloon_alert(user, "[affecting.plaintext_zone] is not organic!")
+ if(!silent)
+ carbon_patient.balloon_alert(user, "[affecting.plaintext_zone] is not organic!")
return FALSE
- if(!(affecting.brute_dam && brute) && !(affecting.burn_dam && burn))
- if(!affecting.brute_dam && !affecting.burn_dam)
- if(patient != user || !looping)
+
+ var/datum/wound/burn/flesh/any_burn_wound = locate() in affecting.wounds
+ var/can_heal_burn_wounds = (flesh_regeneration || sanitization) && any_burn_wound?.can_be_ointmented_or_meshed()
+ var/can_suture_bleeding = stop_bleeding && affecting.get_modified_bleed_rate() > 0
+ var/brute_to_heal = heal_brute && affecting.brute_dam > 0
+ var/burn_to_heal = heal_burn && affecting.burn_dam > 0
+
+ if(!brute_to_heal && !burn_to_heal && !can_heal_burn_wounds && !can_suture_bleeding)
+ if(!silent)
+ if(!brute_to_heal && stop_bleeding) // no brute, no bleeding
+ carbon_patient.balloon_alert(user, "[affecting.plaintext_zone] is not bleeding or bruised!")
+ else if(!burn_to_heal && (flesh_regeneration || sanitization) && any_burn_wound) // no burns, existing burn wounds are treated
+ carbon_patient.balloon_alert(user, "[affecting.plaintext_zone] has been fully treated!")
+ else if(!affecting.brute_dam && !affecting.burn_dam) // not hurt at all
carbon_patient.balloon_alert(user, "[affecting.plaintext_zone] is not hurt!")
- else
- carbon_patient.balloon_alert(user, "can't heal [affecting.plaintext_zone] with [name]!")
+ else // probably hurt in some way but we are not the right item for this
+ carbon_patient.balloon_alert(user, "can't heal [affecting.plaintext_zone] with [name]!")
return FALSE
return TRUE
+
if(isanimal_or_basicmob(patient))
- if(patient.stat == DEAD)
- patient.balloon_alert(user, "they're dead!")
- return FALSE
if(!heal_brute) // only brute can heal
- patient.balloon_alert(user, "can't heal with [name]!")
+ if(!silent)
+ patient.balloon_alert(user, "can't heal with [name]!")
return FALSE
if(!(patient.mob_biotypes & MOB_ORGANIC))
- patient.balloon_alert(user, "no organic tissue!")
+ if(!silent)
+ patient.balloon_alert(user, "no organic tissue!")
return FALSE
if(patient.health == patient.maxHealth)
- patient.balloon_alert(user, "not hurt!")
+ if(!silent)
+ patient.balloon_alert(user, "not hurt!")
return FALSE
return TRUE
+ return FALSE
-/// The healing effects on a carbon patient. Since we have extra details for dealing with bodyparts, we get our own fancy proc. Still returns TRUE on success and FALSE on fail
-/obj/item/stack/medical/proc/heal_carbon(mob/living/carbon/patient, mob/user, brute, burn)
- var/obj/item/bodypart/affecting = patient.get_bodypart(check_zone(user.zone_selected))
- if(!try_heal_checks(patient, user, brute, burn))
- return FALSE
+/// The healing effects on a carbon patient.
+/// Since we have extra details for dealing with bodyparts, we get our own fancy proc.
+/// Still returns TRUE on success and FALSE on fail
+/obj/item/stack/medical/proc/heal_carbon(mob/living/carbon/patient, mob/living/user, healed_zone)
+ var/obj/item/bodypart/affecting = patient.get_bodypart(healed_zone)
user.visible_message(
- span_infoplain(span_green("[user] applies [src] on [patient]'s [affecting.plaintext_zone].")),
- span_infoplain(span_green("You apply [src] on [patient]'s [affecting.plaintext_zone]."))
+ span_green("[user] applies [src] on [patient]'s [affecting.plaintext_zone]."),
+ span_green("You apply [src] on [patient]'s [affecting.plaintext_zone]."),
+ visible_message_flags = ALWAYS_SHOW_SELF_MESSAGE,
)
var/previous_damage = affecting.get_damage()
- if(affecting.heal_damage(brute, burn))
+ if(affecting.heal_damage(heal_brute, heal_burn))
patient.update_damage_overlays()
+ if(stop_bleeding)
+ for(var/datum/wound/wound as anything in affecting.wounds)
+ if(wound.blood_flow)
+ wound.adjust_blood_flow(-1 * stop_bleeding * (user == patient ? 0.7 : 1))
+ break // one at a time
+ affecting.adjustBleedStacks(-1 * stop_bleeding, 0)
+ if(flesh_regeneration || sanitization)
+ for(var/datum/wound/burn/flesh/wound as anything in affecting.wounds)
+ if(wound.can_be_ointmented_or_meshed())
+ wound.flesh_healing += flesh_regeneration
+ wound.sanitization += sanitization
+ break // one at a time
post_heal_effects(max(previous_damage - affecting.get_damage(), 0), patient, user)
return TRUE
-///Override this proc for special post heal effects.
-/obj/item/stack/medical/proc/post_heal_effects(amount_healed, mob/living/carbon/healed_mob, mob/user)
+/// Healing a simple mob, just an adjustbruteloss call
+/obj/item/stack/medical/proc/heal_simplemob(mob/living/patient, mob/living/user)
+ patient.adjustBruteLoss(-1 * (heal_brute * patient.maxHealth / 100))
+ user.visible_message(
+ span_green("[user] applies [src] on [patient]."),
+ span_green("You apply [src] on [patient]."),
+ visible_message_flags = ALWAYS_SHOW_SELF_MESSAGE,
+ )
+ return TRUE
+
+///Override this proc for special post heal effects. Only called for carbon patients.
+/obj/item/stack/medical/proc/post_heal_effects(amount_healed, mob/living/carbon/healed_mob, mob/living/user)
return
/obj/item/stack/medical/bruise_pack
@@ -239,47 +375,79 @@
SEND_SIGNAL(gauzed_bodypart, COMSIG_BODYPART_UNGAUZED, src)
gauzed_bodypart = null
-// gauze is only relevant for wounds, which are handled in the wounds themselves
-/obj/item/stack/medical/gauze/try_heal(mob/living/patient, mob/user, silent, looping)
-
- var/treatment_delay = (user == patient ? self_delay : other_delay)
+/obj/item/stack/medical/gauze/add_item_context(obj/item/source, list/context, atom/target, mob/living/user)
+ if(iscarbon(target))
+ context[SCREENTIP_CONTEXT_LMB] = "Apply Gauze"
+ return CONTEXTUAL_SCREENTIP_SET
+ return NONE
- var/obj/item/bodypart/limb = patient.get_bodypart(check_zone(user.zone_selected))
- if(!limb)
- patient.balloon_alert(user, "missing limb!")
- return
+/obj/item/stack/medical/gauze/try_heal_checks(mob/living/patient, mob/living/user, healed_zone, silent = FALSE)
+ var/obj/item/bodypart/limb = patient.get_bodypart(healed_zone)
+ if(isnull(limb))
+ if(!silent)
+ patient.balloon_alert(user, "no [parse_zone(healed_zone)]!")
+ return FALSE
if(!LAZYLEN(limb.wounds))
- patient.balloon_alert(user, "no wounds!") // good problem to have imo
- return
-
- var/gauzeable_wound = FALSE
- var/datum/wound/woundies
- for(var/i in limb.wounds)
- woundies = i
+ if(!silent)
+ patient.balloon_alert(user, "no wounds!") // good problem to have imo
+ return FALSE
+ if(limb.current_gauze && (limb.current_gauze.absorption_capacity * 1.2 > absorption_capacity)) // ignore if our new wrap is < 20% better than the current one, so someone doesn't bandage it 5 times in a row
+ if(!silent)
+ patient.balloon_alert(user, pick("already bandaged!", "bandage is clean!")) // good enough
+ return FALSE
+ for(var/datum/wound/woundies as anything in limb.wounds)
if(woundies.wound_flags & ACCEPTS_GAUZE)
- gauzeable_wound = TRUE
- break
- if(!gauzeable_wound)
- patient.balloon_alert(user, "can't heal those!")
- return
+ return TRUE
+ if(!silent)
+ patient.balloon_alert(user, "can't gauze!")
+ return FALSE
- if(limb.current_gauze && (limb.current_gauze.absorption_capacity * 1.2 > absorption_capacity)) // ignore if our new wrap is < 20% better than the current one, so someone doesn't bandage it 5 times in a row
- patient.balloon_alert(user, pick("already bandaged!", "bandage is clean!")) // good enough
- return
+// gauze is only relevant for wounds, which are handled in the wounds themselves
+/obj/item/stack/medical/gauze/try_heal(mob/living/patient, mob/living/user, silent, healed_zone, auto_change_zone)
+ var/obj/item/bodypart/limb = patient.get_bodypart(healed_zone)
+ var/treatment_delay = (user == patient ? self_delay : other_delay)
+ var/any_scanned = FALSE
+ for(var/datum/wound/woundies as anything in limb.wounds)
+ if(HAS_TRAIT(woundies, TRAIT_WOUND_SCANNED))
+ any_scanned = TRUE
+ break
- if(HAS_TRAIT(woundies, TRAIT_WOUND_SCANNED))
+ if(any_scanned)
treatment_delay *= 0.5
if(user == patient)
- to_chat(user, span_notice("You keep in mind the indications from the holo-image about your injury, and expertly begin wrapping your wounds with [src]."))
+ if(!silent)
+ user.visible_message(
+ span_warning("[user] begins expertly wrapping the wounds on [p_their()]'s [limb.plaintext_zone] with [src]..."),
+ span_warning("You begin quickly wrapping the wounds on your [limb.plaintext_zone] with [src], keeping the holo-image indications in mind..."),
+ visible_message_flags = ALWAYS_SHOW_SELF_MESSAGE,
+ )
else
- user.visible_message(span_warning("[user] begins expertly wrapping the wounds on [patient]'s [limb.plaintext_zone] with [src]..."), span_warning("You begin quickly wrapping the wounds on [patient]'s [limb.plaintext_zone] with [src], keeping the holo-image indications in mind..."))
+ if(!silent)
+ user.visible_message(
+ span_warning("[user] begins expertly wrapping the wounds on [patient]'s [limb.plaintext_zone] with [src]..."),
+ span_warning("You begin quickly wrapping the wounds on [patient]'s [limb.plaintext_zone] with [src], keeping the holo-image indications in mind..."),
+ visible_message_flags = ALWAYS_SHOW_SELF_MESSAGE,
+ )
else
- user.visible_message(span_warning("[user] begins wrapping the wounds on [patient]'s [limb.plaintext_zone] with [src]..."), span_warning("You begin wrapping the wounds on [user == patient ? "your" : "[patient]'s"] [limb.plaintext_zone] with [src]..."))
+ if(!silent)
+ user.visible_message(
+ span_warning("[user] begins wrapping the wounds on [patient]'s [limb.plaintext_zone] with [src]..."),
+ span_warning("You begin wrapping the wounds on [user == patient ? "your" : "[patient]'s"] [limb.plaintext_zone] with [src]..."),
+ visible_message_flags = ALWAYS_SHOW_SELF_MESSAGE,
+ )
+
+ patient.balloon_alert(user, "wrapping [parse_zone(healed_zone)]...")
if(!do_after(user, treatment_delay, target = patient))
return
- user.visible_message(span_infoplain(span_green("[user] applies [src] to [patient]'s [limb.plaintext_zone].")), span_infoplain(span_green("You bandage the wounds on [user == patient ? "your" : "[patient]'s"] [limb.plaintext_zone].")))
+ if(!silent)
+ patient.balloon_alert(user, "wrapped [parse_zone(healed_zone)]")
+ user.visible_message(
+ span_green("[user] applies [src] to [patient]'s [limb.plaintext_zone]."),
+ span_green("You bandage the wounds on [user == patient ? "your" : "[patient]'s"] [limb.plaintext_zone]."),
+ visible_message_flags = ALWAYS_SHOW_SELF_MESSAGE,
+ )
limb.apply_gauze(src)
/obj/item/stack/medical/gauze/twelve
@@ -413,10 +581,11 @@
return ..()
icon_state = "regen_mesh_closed"
-/obj/item/stack/medical/mesh/try_heal(mob/living/patient, mob/user, silent = FALSE, looping)
+/obj/item/stack/medical/mesh/try_heal_checks(mob/living/patient, mob/living/user, silent = FALSE)
if(!is_open)
- balloon_alert(user, "open it first!")
- return
+ if(!silent)
+ balloon_alert(user, "open it first!")
+ return FALSE
return ..()
/obj/item/stack/medical/mesh/click_alt(mob/living/user)
@@ -475,6 +644,10 @@
grind_results = list(/datum/reagent/consumable/aloejuice = 1)
merge_type = /obj/item/stack/medical/aloe
+/obj/item/stack/medical/aloe/Initialize(mapload, new_amount, merge, list/mat_override, mat_amt)
+ . = ..()
+ AddComponent(/datum/component/bakeable, /obj/item/food/badrecipe, rand(10 SECONDS, 15 SECONDS), FALSE)
+
/obj/item/stack/medical/aloe/fresh
amount = 2
@@ -529,7 +702,11 @@
/obj/item/stack/medical/poultice
name = "mourning poultices"
singular_name = "mourning poultice"
- desc = "A type of primitive herbal poultice.\nWhile traditionally used to prepare corpses for the mourning feast, it can also treat scrapes and burns on the living, however, it is liable to cause shortness of breath when employed in this manner.\nIt is imbued with ancient wisdom."
+ desc = "A type of primitive herbal poultice.\n\
+ While traditionally used to prepare corpses for the mourning feast, \
+ it can also treat scrapes and burns on the living, however, \
+ it is liable to cause shortness of breath when employed in this manner.\n\
+ It is imbued with ancient wisdom."
icon_state = "poultice"
amount = 15
max_amount = 15
@@ -543,14 +720,9 @@
hitsound = 'sound/misc/moist_impact.ogg'
merge_type = /obj/item/stack/medical/poultice
-/obj/item/stack/medical/poultice/heal(mob/living/patient, mob/user)
- if(iscarbon(patient))
- playsound(src, 'sound/misc/soggy.ogg', 30, TRUE)
- return heal_carbon(patient, user, heal_brute, heal_burn)
- return ..()
-
-/obj/item/stack/medical/poultice/post_heal_effects(amount_healed, mob/living/carbon/healed_mob, mob/user)
+/obj/item/stack/medical/poultice/post_heal_effects(amount_healed, mob/living/carbon/healed_mob, mob/living/user)
. = ..()
+ playsound(src, 'sound/misc/soggy.ogg', 30, TRUE)
healed_mob.adjustOxyLoss(amount_healed)
/obj/item/stack/medical/bandage
diff --git a/code/game/objects/items/storage/belt.dm b/code/game/objects/items/storage/belt.dm
index 3964ccf6f80ba..f7784b9d3b8a8 100644
--- a/code/game/objects/items/storage/belt.dm
+++ b/code/game/objects/items/storage/belt.dm
@@ -46,35 +46,9 @@
. = ..()
atom_storage.max_specific_storage = WEIGHT_CLASS_NORMAL
atom_storage.max_total_storage = 21
- atom_storage.set_holdable(list(
- /obj/item/airlock_painter,
- /obj/item/analyzer,
- /obj/item/assembly/signaler,
+ atom_storage.set_holdable(GLOB.tool_items + list(
/obj/item/clothing/gloves,
- /obj/item/construction/rcd,
- /obj/item/construction/rld,
- /obj/item/construction/rtd,
- /obj/item/crowbar,
- /obj/item/extinguisher/mini,
- /obj/item/flashlight,
- /obj/item/forcefield_projector,
- /obj/item/geiger_counter,
- /obj/item/holosign_creator/atmos,
- /obj/item/holosign_creator/engineering,
- /obj/item/inducer,
- /obj/item/lightreplacer,
- /obj/item/multitool,
- /obj/item/pipe_dispenser,
- /obj/item/pipe_painter,
- /obj/item/plunger,
/obj/item/radio,
- /obj/item/screwdriver,
- /obj/item/stack/cable_coil,
- /obj/item/t_scanner,
- /obj/item/weldingtool,
- /obj/item/wirecutters,
- /obj/item/wrench,
- /obj/item/spess_knife,
/obj/item/melee/sickly_blade/lock,
/obj/item/reagent_containers/cup/soda_cans,
))
@@ -749,7 +723,7 @@
atom_storage.set_holdable(list(
/obj/item/ammo_casing/strilka310,
/obj/item/ammo_casing/shotgun,
- /obj/item/ammo_casing/a357,
+ /obj/item/ammo_casing/c357,
/obj/item/ammo_casing/junk,
))
diff --git a/code/game/objects/items/storage/toolbox.dm b/code/game/objects/items/storage/toolbox.dm
index 709476e8881a9..0374b7a3744e7 100644
--- a/code/game/objects/items/storage/toolbox.dm
+++ b/code/game/objects/items/storage/toolbox.dm
@@ -23,6 +23,15 @@
var/latches = "single_latch"
var/has_latches = TRUE
wound_bonus = 5
+ /// How many interactions are we currently performing
+ var/current_interactions = 0
+ /// Items we should not interact with when left clicking
+ var/static/list/lmb_exception_typecache = typecacheof(list(
+ /obj/structure/table,
+ /obj/structure/rack,
+ /obj/structure/closet,
+ /obj/machinery/disposal,
+ ))
/obj/item/storage/toolbox/Initialize(mapload)
. = ..()
@@ -32,11 +41,79 @@
latches = "double_latch"
if(prob(1))
latches = "triple_latch"
+ if(prob(0.1))
+ latches = "quad_latch" // like winning the lottery, but worse
update_appearance()
atom_storage.open_sound = 'sound/items/handling/toolbox/toolbox_open.ogg'
atom_storage.rustle_sound = 'sound/items/handling/toolbox/toolbox_rustle.ogg'
AddElement(/datum/element/falling_hazard, damage = force, wound_bonus = wound_bonus, hardhat_safety = TRUE, crushes = FALSE, impact_sound = hitsound)
+/obj/item/storage/toolbox/interact_with_atom(atom/interacting_with, mob/living/user, list/modifiers)
+ if (user.combat_mode || !user.has_hand_for_held_index(user.get_inactive_hand_index()))
+ return NONE
+
+ if (is_type_in_typecache(interacting_with, lmb_exception_typecache) && !LAZYACCESS(modifiers, RIGHT_CLICK))
+ return NONE
+
+ if (current_interactions)
+ var/obj/item/other_tool = user.get_inactive_held_item()
+ if (!istype(other_tool)) // what even
+ return NONE
+ INVOKE_ASYNC(src, PROC_REF(use_tool_on), interacting_with, user, modifiers, other_tool)
+ return ITEM_INTERACT_SUCCESS
+
+ if (user.get_inactive_held_item())
+ user.balloon_alert(user, "hands busy!")
+ return ITEM_INTERACT_BLOCKING
+
+ var/list/item_radial = list()
+ for (var/obj/item/tool in atom_storage.real_location)
+ if(is_type_in_list(tool, GLOB.tool_items))
+ item_radial[tool] = tool.appearance
+
+ if (!length(item_radial))
+ return NONE
+
+ playsound(user, 'sound/items/handling/toolbox/toolbox_open.ogg', 50)
+ var/obj/item/picked_item = show_radial_menu(user, interacting_with, item_radial, require_near = TRUE)
+ if (!picked_item)
+ return ITEM_INTERACT_BLOCKING
+
+ playsound(user, 'sound/items/handling/toolbox/toolbox_rustle.ogg', 50)
+ if (!user.put_in_inactive_hand(picked_item))
+ return ITEM_INTERACT_BLOCKING
+
+ atom_storage.animate_parent()
+ if (istype(picked_item, /obj/item/weldingtool))
+ var/obj/item/weldingtool/welder = picked_item
+ if (!welder.welding)
+ welder.attack_self(user)
+
+ if (istype(picked_item, /obj/item/spess_knife))
+ picked_item.attack_self(user)
+
+ INVOKE_ASYNC(src, PROC_REF(use_tool_on), interacting_with, user, modifiers, picked_item)
+ return ITEM_INTERACT_SUCCESS
+
+/obj/item/storage/toolbox/proc/use_tool_on(atom/interacting_with, mob/living/user, list/modifiers, obj/item/picked_tool)
+ current_interactions += 1
+ picked_tool.melee_attack_chain(user, interacting_with, list2params(modifiers))
+ current_interactions -= 1
+
+ if (QDELETED(picked_tool) || picked_tool.loc != user || !user.CanReach(picked_tool))
+ current_interactions = 0
+ return
+
+ if (current_interactions)
+ return
+
+ if (istype(picked_tool, /obj/item/weldingtool))
+ var/obj/item/weldingtool/welder = picked_tool
+ if (welder.welding)
+ welder.attack_self(user)
+
+ atom_storage.attempt_insert(picked_tool, user)
+
/obj/item/storage/toolbox/update_overlays()
. = ..()
if(has_latches)
@@ -107,6 +184,12 @@
/obj/item/storage/toolbox/mechanical/old/heirloom/PopulateContents()
return
+// version of below that isn't a traitor item
+/obj/item/storage/toolbox/mechanical/old/cleaner
+ name = "old blue toolbox"
+ icon_state = "oldtoolboxclean"
+ icon_state = "toolbox_blue_old"
+
/obj/item/storage/toolbox/mechanical/old/clean // the assistant traitor toolbox, damage scales with TC inside
name = "toolbox"
desc = "An old, blue toolbox, it looks robust."
@@ -225,6 +308,51 @@
new /obj/item/stack/pipe_cleaner_coil/white(src)
new /obj/item/stack/pipe_cleaner_coil/brown(src)
+/obj/item/storage/toolbox/medical
+ name = "medical toolbox"
+ desc = "A toolbox painted soft white and light blue. This is getting ridiculous."
+ icon_state = "medical"
+ inhand_icon_state = "toolbox_medical"
+ attack_verb_continuous = list("treats", "surgeries", "tends", "tends wounds on")
+ attack_verb_simple = list("treat", "surgery", "tend", "tend wounds on")
+ w_class = WEIGHT_CLASS_BULKY
+ material_flags = NONE
+ force = 5 // its for healing
+ wound_bonus = 25 // wounds are medical right?
+ /// Tray we steal the og contents from.
+ var/obj/item/surgery_tray/tray_type = /obj/item/surgery_tray
+
+/obj/item/storage/toolbox/medical/Initialize(mapload)
+ . = ..()
+ // what do any of these numbers fucking mean
+ atom_storage.max_total_storage = 20
+ atom_storage.max_slots = 11
+
+/obj/item/storage/toolbox/medical/PopulateContents()
+ var/atom/fake_tray = new tray_type(get_turf(src)) // not in src lest it fill storage that we need for its tools later
+ for(var/atom/movable/thingy in fake_tray)
+ thingy.forceMove(src)
+ qdel(fake_tray)
+
+/obj/item/storage/toolbox/medical/full
+ tray_type = /obj/item/surgery_tray/full
+
+/obj/item/storage/toolbox/medical/coroner
+ name = "coroner toolbox"
+ desc = "A toolbox painted soft white and dark grey. This is getting beyond ridiculous."
+ icon_state = "coroner"
+ inhand_icon_state = "toolbox_coroner"
+ attack_verb_continuous = list("dissects", "autopsies", "corones")
+ attack_verb_simple = list("dissect", "autopsy", "corone")
+ w_class = WEIGHT_CLASS_BULKY
+ material_flags = NONE
+ force = 17 // it's not for healing
+ tray_type = /obj/item/surgery_tray/full/morgue
+
+/obj/item/storage/toolbox/medical/coroner/Initialize(mapload)
+ . = ..()
+ AddElement(/datum/element/bane, mob_biotypes = MOB_UNDEAD, damage_multiplier = 1) //Just in case one of the tennants get uppity
+
/obj/item/storage/toolbox/ammobox
name = "ammo canister"
desc = "A metal canister designed to hold ammunition"
diff --git a/code/game/objects/items/storage/uplink_kits.dm b/code/game/objects/items/storage/uplink_kits.dm
index 3f813e50b45db..d945a3fd711db 100644
--- a/code/game/objects/items/storage/uplink_kits.dm
+++ b/code/game/objects/items/storage/uplink_kits.dm
@@ -461,7 +461,7 @@
/obj/item/storage/box/syndie_kit/chemical/Initialize(mapload)
. = ..()
- atom_storage.max_slots = 14
+ atom_storage.max_slots = 15
/obj/item/storage/box/syndie_kit/chemical/PopulateContents()
new /obj/item/reagent_containers/cup/bottle/polonium(src)
@@ -469,6 +469,7 @@
new /obj/item/reagent_containers/cup/bottle/fentanyl(src)
new /obj/item/reagent_containers/cup/bottle/formaldehyde(src)
new /obj/item/reagent_containers/cup/bottle/spewium(src)
+ new /obj/item/reagent_containers/cup/bottle/syndol(src)
new /obj/item/reagent_containers/cup/bottle/cyanide(src)
new /obj/item/reagent_containers/cup/bottle/histamine(src)
new /obj/item/reagent_containers/cup/bottle/initropidril(src)
diff --git a/code/game/objects/items/surgery_tray.dm b/code/game/objects/items/surgery_tray.dm
index 4e84bd3ac3559..9485be4c3585e 100644
--- a/code/game/objects/items/surgery_tray.dm
+++ b/code/game/objects/items/surgery_tray.dm
@@ -1,3 +1,4 @@
+
/**
* Surgery Trays
* A storage object that displays tools in its contents based on tier, better tools are more visible.
@@ -16,11 +17,17 @@
/// If true we're currently portable
var/is_portable = TRUE
+ /// List of contents to populate with in populatecontents()
+ var/list/starting_items = list()
+
/// Fills the tray with items it should contain on creation
/obj/item/surgery_tray/proc/populate_contents()
+ for(var/obj in starting_items)
+ new obj(src)
+ update_appearance(UPDATE_ICON)
return
-/obj/item/surgery_tray/Initialize(mapload)
+/obj/item/surgery_tray/Initialize(mapload, effect_spawner = FALSE)
. = ..()
AddElement(/datum/element/drag_pickup)
create_storage(storage_type = /datum/storage/surgery_tray)
@@ -159,57 +166,100 @@
is_portable = FALSE
/obj/item/surgery_tray/full
+ starting_items = list(
+ /obj/item/blood_filter,
+ /obj/item/bonesetter,
+ /obj/item/cautery,
+ /obj/item/circular_saw,
+ /obj/item/clothing/mask/surgical,
+ /obj/item/hemostat,
+ /obj/item/razor/surgery,
+ /obj/item/retractor,
+ /obj/item/scalpel,
+ /obj/item/stack/medical/bone_gel,
+ /obj/item/stack/sticky_tape/surgical,
+ /obj/item/surgical_drapes,
+ /obj/item/surgicaldrill,
+ )
/obj/item/surgery_tray/full/deployed
is_portable = FALSE
-/obj/item/surgery_tray/full/populate_contents()
- new /obj/item/blood_filter(src)
- new /obj/item/bonesetter(src)
- new /obj/item/cautery(src)
- new /obj/item/circular_saw(src)
- new /obj/item/clothing/mask/surgical(src)
- new /obj/item/hemostat(src)
- new /obj/item/razor/surgery(src)
- new /obj/item/retractor(src)
- new /obj/item/scalpel(src)
- new /obj/item/stack/medical/bone_gel(src)
- new /obj/item/stack/sticky_tape/surgical(src)
- new /obj/item/surgical_drapes(src)
- new /obj/item/surgicaldrill(src)
- update_appearance(UPDATE_OVERLAYS)
-
/obj/item/surgery_tray/full/morgue
name = "autopsy tray"
desc = "A Deforest brand surgery tray, made for use in morgues. It is a folding model, \
meaning the wheels on the bottom can be extended outwards, making it a cart."
+ starting_items = list(
+ /obj/item/blood_filter,
+ /obj/item/bonesetter,
+ /obj/item/cautery/cruel,
+ /obj/item/circular_saw,
+ /obj/item/clothing/mask/surgical,
+ /obj/item/hemostat/cruel,
+ /obj/item/razor/surgery,
+ /obj/item/retractor/cruel,
+ /obj/item/scalpel/cruel,
+ /obj/item/stack/medical/bone_gel,
+ /obj/item/stack/sticky_tape/surgical,
+ /obj/item/surgical_drapes,
+ /obj/item/surgicaldrill,
+ )
-/obj/item/surgery_tray/full/morgue/populate_contents()
- new /obj/item/blood_filter(src)
- new /obj/item/bonesetter(src)
- new /obj/item/cautery/cruel(src)
- new /obj/item/circular_saw(src)
- new /obj/item/clothing/mask/surgical(src)
- new /obj/item/hemostat/cruel(src)
- new /obj/item/razor/surgery(src)
- new /obj/item/retractor/cruel(src)
- new /obj/item/scalpel/cruel(src)
- new /obj/item/stack/medical/bone_gel(src)
- new /obj/item/stack/sticky_tape/surgical(src)
- new /obj/item/surgical_drapes(src)
- new /obj/item/surgicaldrill(src)
+/obj/item/surgery_tray/full/morgue/deployed
+ is_portable = FALSE
/// Surgery tray with advanced tools for debug
/obj/item/surgery_tray/full/advanced
+ starting_items = list(
+ /obj/item/scalpel/advanced,
+ /obj/item/retractor/advanced,
+ /obj/item/cautery/advanced,
+ /obj/item/surgical_drapes,
+ /obj/item/reagent_containers/medigel/sterilizine,
+ /obj/item/bonesetter,
+ /obj/item/blood_filter,
+ /obj/item/stack/medical/bone_gel,
+ /obj/item/stack/sticky_tape/surgical,
+ /obj/item/clothing/mask/surgical,
+ )
+
+/obj/effect/spawner/surgery_tray
+ name = "surgery tray spawner"
+ icon = 'icons/obj/medical/medicart.dmi'
+ icon_state = "tray"
+ /// Tray to usually spawn in.
+ var/tray_to_spawn = /obj/item/surgery_tray
+ /// Toolbox to sometimes replace the above tray with.
+ var/rare_toolbox_replacement = /obj/item/storage/toolbox/medical
+ /// Chance for replacement
+ var/toolbox_chance = 1
+
+/obj/effect/spawner/surgery_tray/Initialize(mapload)
+ . = ..()
+ if(prob(toolbox_chance))
+ new rare_toolbox_replacement(loc)
+ return
+ new tray_to_spawn(loc, TRUE)
+
+/obj/effect/spawner/surgery_tray/full
+ name = "full surgery tray spawner"
+ icon_state = "tray"
+ tray_to_spawn = /obj/item/surgery_tray/full
+ rare_toolbox_replacement = /obj/item/storage/toolbox/medical/full
+
+/obj/effect/spawner/surgery_tray/full/deployed
+ name = "full deployed tray spawner"
+ icon_state = "medicart"
+ tray_to_spawn = /obj/item/surgery_tray/full
+
+/obj/effect/spawner/surgery_tray/full/morgue
+ name = "full autopsy tray spawner"
+ icon_state = "tray"
+ tray_to_spawn = /obj/item/surgery_tray/full/morgue
+ rare_toolbox_replacement = /obj/item/storage/toolbox/medical/coroner
+ toolbox_chance = 3 // tray is rarer, so toolbox is more common
-/obj/item/surgery_tray/full/advanced/populate_contents()
- new /obj/item/scalpel/advanced(src)
- new /obj/item/retractor/advanced(src)
- new /obj/item/cautery/advanced(src)
- new /obj/item/surgical_drapes(src)
- new /obj/item/reagent_containers/medigel/sterilizine(src)
- new /obj/item/bonesetter(src)
- new /obj/item/blood_filter(src)
- new /obj/item/stack/medical/bone_gel(src)
- new /obj/item/stack/sticky_tape/surgical(src)
- new /obj/item/clothing/mask/surgical(src)
+/obj/effect/spawner/surgery_tray/full/morgue/deployed
+ name = "full deployed autopsy tray spawner"
+ icon_state = "medicart"
+ tray_to_spawn = /obj/item/surgery_tray/full/morgue/deployed
diff --git a/code/game/objects/items/tanks/jetpack.dm b/code/game/objects/items/tanks/jetpack.dm
index 6976c936b665f..674e50caade0a 100644
--- a/code/game/objects/items/tanks/jetpack.dm
+++ b/code/game/objects/items/tanks/jetpack.dm
@@ -53,6 +53,7 @@
COMSIG_JETPACK_DEACTIVATED, \
JETPACK_ACTIVATION_FAILED, \
thrust_callback, \
+ thrust_callback, \
/datum/effect_system/trail_follow/ion, \
)
diff --git a/code/game/objects/items/tanks/watertank.dm b/code/game/objects/items/tanks/watertank.dm
index 1c23937d2b589..eba0bc82054d2 100644
--- a/code/game/objects/items/tanks/watertank.dm
+++ b/code/game/objects/items/tanks/watertank.dm
@@ -290,6 +290,9 @@
/obj/item/extinguisher/mini/nozzle/interact_with_atom(atom/interacting_with, mob/living/user, list/modifiers)
if(AttemptRefill(interacting_with, user))
return NONE
+ return ..()
+
+/obj/item/extinguisher/mini/nozzle/ranged_interact_with_atom(atom/interacting_with, mob/living/user, list/modifiers)
if(nozzle_mode == EXTINGUISHER)
return ..()
diff --git a/code/game/objects/items/tools/crowbar.dm b/code/game/objects/items/tools/crowbar.dm
index 2d5a508076d9c..38ca59038f04a 100644
--- a/code/game/objects/items/tools/crowbar.dm
+++ b/code/game/objects/items/tools/crowbar.dm
@@ -88,7 +88,7 @@
custom_materials = list(/datum/material/wood=SMALL_MATERIAL_AMOUNT*0.5, /datum/material/iron=SMALL_MATERIAL_AMOUNT*0.7)
wound_bonus = 35
-/obj/item/crowbar/large/heavy //from space ruin
+/obj/item/crowbar/large/twenty_force //from space ruin
name = "heavy crowbar"
desc = "It's a big crowbar. It doesn't fit in your pockets, because it's big. It feels oddly heavy.."
force = 20
diff --git a/code/game/objects/items/tools/weldingtool.dm b/code/game/objects/items/tools/weldingtool.dm
index 41bcc25eba652..e48a19743c137 100644
--- a/code/game/objects/items/tools/weldingtool.dm
+++ b/code/game/objects/items/tools/weldingtool.dm
@@ -234,7 +234,7 @@
// /Switches the welder on
/obj/item/weldingtool/proc/switched_on(mob/user)
if(!status)
- to_chat(user, span_warning("[src] can't be turned on while unsecured!"))
+ balloon_alert(user, "unsecured!")
return
set_welding(!welding)
if(welding)
diff --git a/code/game/objects/structures/crates_lockers/closets.dm b/code/game/objects/structures/crates_lockers/closets.dm
index 6046d356892b8..207b85122dd21 100644
--- a/code/game/objects/structures/crates_lockers/closets.dm
+++ b/code/game/objects/structures/crates_lockers/closets.dm
@@ -236,7 +236,8 @@ GLOBAL_LIST_EMPTY(roundstart_station_closets)
. += door_overlay
door_overlay.overlays += emissive_blocker(door_overlay.icon, door_overlay.icon_state, src, alpha = door_overlay.alpha) // If we don't do this the door doesn't block emissives and it looks weird.
else if(has_closed_overlay)
- . += "[icon_door || overlay_state]_door"
+ var/mutable_appearance/door_overlay = mutable_appearance(icon, "[icon_door || overlay_state]_door", alpha = src.alpha)
+ . += door_overlay
if(opened)
return
diff --git a/code/game/objects/structures/crates_lockers/crates.dm b/code/game/objects/structures/crates_lockers/crates.dm
index f7a0ccd66dfdd..bb25ba5158bd8 100644
--- a/code/game/objects/structures/crates_lockers/crates.dm
+++ b/code/game/objects/structures/crates_lockers/crates.dm
@@ -299,6 +299,21 @@
new /obj/item/bodypart/leg/right/robot/surplus(src)
new /obj/item/bodypart/leg/right/robot/surplus(src)
+/obj/structure/closet/crate/freezer/organ
+ name = "organ freezer"
+ desc = "A freezer containing a set of organic organs."
+
+/obj/structure/closet/crate/freezer/organ/PopulateContents()
+ . = ..()
+ new /obj/item/organ/heart(src)
+ new /obj/item/organ/lungs(src)
+ new /obj/item/organ/eyes(src)
+ new /obj/item/organ/ears(src)
+ new /obj/item/organ/tongue(src)
+ new /obj/item/organ/liver(src)
+ new /obj/item/organ/stomach(src)
+ new /obj/item/organ/appendix(src)
+
/obj/structure/closet/crate/freezer/food
name = "food icebox"
icon_state = "food"
diff --git a/code/game/objects/structures/lattice.dm b/code/game/objects/structures/lattice.dm
index a0cf53487cf34..0a52c3cd9f638 100644
--- a/code/game/objects/structures/lattice.dm
+++ b/code/game/objects/structures/lattice.dm
@@ -34,6 +34,13 @@
. = ..()
. += deconstruction_hints(user)
+/obj/structure/lattice/Destroy(force) // so items on the lattice fall when the lattice is destroyed
+ var/turf/turfloc = loc
+ . = ..()
+ if(isturf(turfloc))
+ for(var/thing_that_falls as anything in turfloc) // as anything because turfloc can only contain movables
+ turfloc.zFall((thing_that_falls))
+
/obj/structure/lattice/proc/deconstruction_hints(mob/user)
return span_notice("The rods look like they could be cut. There's space for more rods or a tile.")
diff --git a/code/game/objects/structures/water_structures/sink.dm b/code/game/objects/structures/water_structures/sink.dm
index 1cd3f7d7aaa53..3a6dfbb2a2c14 100644
--- a/code/game/objects/structures/water_structures/sink.dm
+++ b/code/game/objects/structures/water_structures/sink.dm
@@ -73,10 +73,13 @@ MAPPING_DIRECTIONAL_HELPERS(/obj/structure/sink, (-14))
if(busy)
to_chat(user, span_warning("Someone's already washing here!"))
return
+
var/selected_area = user.parse_zone_with_bodypart(user.zone_selected)
- var/washing_face = 0
+ var/washing_face = FALSE
if(selected_area in list(BODY_ZONE_HEAD, BODY_ZONE_PRECISE_MOUTH, BODY_ZONE_PRECISE_EYES))
- washing_face = 1
+ washing_face = TRUE
+
+ playsound(src, 'sound/machines/sink-faucet.ogg', 50)
user.visible_message(span_notice("[user] starts washing [user.p_their()] [washing_face ? "face" : "hands"]..."), \
span_notice("You start washing your [washing_face ? "face" : "hands"]..."))
busy = TRUE
@@ -206,6 +209,7 @@ MAPPING_DIRECTIONAL_HELPERS(/obj/structure/sink, (-14))
if(!user.combat_mode || (O.item_flags & NOBLUDGEON))
to_chat(user, span_notice("You start washing [O]..."))
+ playsound(src, 'sound/machines/sink-faucet.ogg', 50)
busy = TRUE
if(!do_after(user, 4 SECONDS, target = src))
busy = FALSE
diff --git a/code/game/objects/structures/window.dm b/code/game/objects/structures/window.dm
index 71c3ef9b6713b..9924713d93908 100644
--- a/code/game/objects/structures/window.dm
+++ b/code/game/objects/structures/window.dm
@@ -82,12 +82,11 @@
/obj/structure/window/mouse_drop_receive(atom/dropping, mob/user, params)
. = ..()
- if (added_leaning || (flags_1 & ON_BORDER_1))
+ if (flags_1 & ON_BORDER_1)
return
- /// For performance reasons and to cut down on init times we are "lazy-loading" the leaning component when someone drags their sprite onto us, and then calling dragging code again to trigger the component
- AddComponent(/datum/component/leanable, 11)
- added_leaning = TRUE
- dropping.base_mouse_drop_handler(src, null, null, params)
+
+ //Adds the component only once. We do it here & not in Initialize() because there are tons of windows & we don't want to add to their init times
+ LoadComponent(/datum/component/leanable, dropping)
/obj/structure/window/examine(mob/user)
. = ..()
diff --git a/code/game/say.dm b/code/game/say.dm
index a1171515dac49..5ecc4e819119a 100644
--- a/code/game/say.dm
+++ b/code/game/say.dm
@@ -165,7 +165,7 @@ GLOBAL_LIST_INIT(freqtospan, list(
if(istype(dialect) && dialect.display_icon(src))
languageicon = "[dialect.get_icon()] "
- messagepart = " [say_emphasis(messagepart)]"
+ messagepart = " [messagepart]"
return "[spanpart1][spanpart2][freqpart][languageicon][compose_track_href(speaker, namepart)][namepart][compose_job(speaker, message_language, raw_message, radio_freq)][endspanpart][messagepart]"
@@ -223,8 +223,14 @@ GLOBAL_LIST_INIT(freqtospan, list(
if(copytext_char(input, -2) == "!!")
spans |= SPAN_YELL
- var/spanned = attach_spans(input, spans)
- return "[say_mod], \"[spanned]\""
+ /* all inputs should be fully figured out past this point */
+
+ var/processed_input = say_emphasis(input) //This MUST be done first so that we don't get clipped by spans
+ processed_input = attach_spans(processed_input, spans)
+
+ var/processed_say_mod = say_emphasis(say_mod)
+
+ return "[processed_say_mod], \"[processed_input]\""
/// Transforms the speech emphasis mods from [/atom/movable/proc/say_emphasis] into the appropriate HTML tags. Includes escaping.
#define ENCODE_HTML_EMPHASIS(input, char, html, varname) \
@@ -235,8 +241,8 @@ GLOBAL_LIST_INIT(freqtospan, list(
/atom/movable/proc/say_emphasis(input)
ENCODE_HTML_EMPHASIS(input, "\\|", "i", italics)
ENCODE_HTML_EMPHASIS(input, "\\+", "b", bold)
- ENCODE_HTML_EMPHASIS(input, "_", "u", underline)
- var/static/regex/remove_escape_backlashes = regex("\\\\(_|\\+|\\|)", "g") // Removes backslashes used to escape text modification.
+ ENCODE_HTML_EMPHASIS(input, "\\_", "u", underline)
+ var/static/regex/remove_escape_backlashes = regex("\\\\(\\_|\\+|\\|)", "g") // Removes backslashes used to escape text modification.
input = remove_escape_backlashes.Replace_char(input, "$1")
return input
diff --git a/code/game/turfs/change_turf.dm b/code/game/turfs/change_turf.dm
index 32c531e34dc74..810b83cbcbe79 100644
--- a/code/game/turfs/change_turf.dm
+++ b/code/game/turfs/change_turf.dm
@@ -192,6 +192,10 @@ GLOBAL_LIST_INIT(blacklisted_automated_baseturfs, typecacheof(list(
QUEUE_SMOOTH_NEIGHBORS(src)
QUEUE_SMOOTH(src)
+ // we need to update gravity for any mob on a tile that is being created or destroyed
+ for(var/mob/living/target in new_turf.contents)
+ target.refresh_gravity()
+
return new_turf
/turf/open/ChangeTurf(path, list/new_baseturfs, flags) //Resist the temptation to make this default to keeping air.
diff --git a/code/game/turfs/closed/walls.dm b/code/game/turfs/closed/walls.dm
index 99ea3327d6c46..402d24c6c830d 100644
--- a/code/game/turfs/closed/walls.dm
+++ b/code/game/turfs/closed/walls.dm
@@ -51,13 +51,8 @@
underlays += underlay_appearance
/turf/closed/wall/mouse_drop_receive(atom/dropping, mob/user, params)
- . = ..()
- if (added_leaning)
- return
- /// For performance reasons and to cut down on init times we are "lazy-loading" the leaning component when someone drags their sprite onto us, and then calling dragging code again to trigger the component
- AddComponent(/datum/component/leanable, 11)
- added_leaning = TRUE
- dropping.base_mouse_drop_handler(src, null, null, params)
+ //Adds the component only once. We do it here & not in Initialize() because there are tons of walls & we don't want to add to their init times
+ LoadComponent(/datum/component/leanable, dropping)
/turf/closed/wall/atom_destruction(damage_flag)
. = ..()
diff --git a/code/game/turfs/open/_open.dm b/code/game/turfs/open/_open.dm
index 06d71c37a15a9..6ad32fe7a652c 100644
--- a/code/game/turfs/open/_open.dm
+++ b/code/game/turfs/open/_open.dm
@@ -344,7 +344,7 @@
movable_content.wash(CLEAN_WASH)
return TRUE
-/turf/open/handle_slip(mob/living/carbon/slipper, knockdown_amount, obj/slippable, lube, paralyze_amount, force_drop)
+/turf/open/handle_slip(mob/living/slipper, knockdown_amount, obj/slippable, lube, paralyze_amount, force_drop)
if(slipper.movement_type & MOVETYPES_NOT_TOUCHING_GROUND)
return FALSE
if(!has_gravity(src))
@@ -382,9 +382,10 @@
SEND_SIGNAL(slipper, COMSIG_ON_CARBON_SLIP)
slipper.add_mood_event("slipped", /datum/mood_event/slipped)
- if(force_drop)
+ if(force_drop && iscarbon(slipper)) //carbon specific behavior that living doesn't have
+ var/mob/living/carbon/carbon = slipper
for(var/obj/item/item in slipper.held_items)
- slipper.accident(item)
+ carbon.accident(item)
var/olddir = slipper.dir
slipper.moving_diagonally = 0 //If this was part of diagonal move slipping will stop it.
@@ -399,7 +400,7 @@
slipper.Knockdown(knockdown_amount)
slipper.Paralyze(paralyze_amount)
- if(buckled_obj)
+ if(!isnull(buckled_obj) && !ismob(buckled_obj))
buckled_obj.unbuckle_mob(slipper)
// This is added onto the end so they slip "out of their chair" (one tile)
lube |= SLIDE_ICE
diff --git a/code/game/turfs/open/water.dm b/code/game/turfs/open/water.dm
index b7bc8242bf7b4..2e2daee48bc15 100644
--- a/code/game/turfs/open/water.dm
+++ b/code/game/turfs/open/water.dm
@@ -92,6 +92,7 @@
icon_state = "tizira_water"
base_icon_state = "tizira_water"
baseturfs = /turf/open/water/beach/tizira
+ fishing_datum = /datum/fish_source/tizira
/**
* A special subtype of water with steam particles and a status effect similar to showers, that's however only applied if
@@ -109,16 +110,16 @@
immerse_overlay_alpha = 190
fishing_datum = /datum/fish_source/hot_spring
/// Holder for the steam particles
- var/obj/effect/abstract/particle_holder/cached/steam_effect
+ var/obj/effect/abstract/particle_holder/cached/particle_effect
/turf/open/water/hot_spring/Initialize(mapload)
. = ..()
icon_state = "pool_[rand(1, 4)]"
- steam_effect = new(src, /particles/hotspring_steam, 4)
+ particle_effect = new(src, /particles/hotspring_steam, 4)
//render the steam over mobs and objects on the game plane
- steam_effect.vis_flags &= ~VIS_INHERIT_PLANE
+ particle_effect.vis_flags &= ~VIS_INHERIT_PLANE
//And be unaffected by ambient occlusions, which would render the steam grey
- steam_effect.plane = MUTATE_PLANE(MASSIVE_OBJ_PLANE, src)
+ particle_effect.plane = MUTATE_PLANE(MASSIVE_OBJ_PLANE, src)
add_filter("hot_spring_waves", 1, wave_filter(y = 1, size = 1, offset = 0, flags = WAVE_BOUNDED))
var/filter = get_filter("hot_spring_waves")
animate(filter, offset = 1, time = 3 SECONDS, loop = -1, easing = SINE_EASING|EASE_IN|EASE_OUT)
@@ -126,7 +127,7 @@
/turf/open/water/hot_spring/Destroy()
- QDEL_NULL(steam_effect)
+ QDEL_NULL(particle_effect)
remove_filter("hot_spring_waves")
for(var/atom/movable/movable as anything in contents)
exit_hot_spring(movable)
diff --git a/code/modules/admin/verbs/pray.dm b/code/modules/admin/verbs/pray.dm
index 82c6bdc480432..4277cae7d81fd 100644
--- a/code/modules/admin/verbs/pray.dm
+++ b/code/modules/admin/verbs/pray.dm
@@ -58,7 +58,7 @@
for(var/client/staff as anything in GLOB.admins)
if(staff?.prefs.read_preference(/datum/preference/toggle/comms_notification))
SEND_SOUND(staff, sound('sound/misc/server-ready.ogg'))
- to_chat(GLOB.admins, msg, confidential = TRUE)
+ to_chat(GLOB.admins, msg, type = MESSAGE_TYPE_PRAYER, confidential = TRUE)
for(var/obj/machinery/computer/communications/console in GLOB.shuttle_caller_list)
console.override_cooldown()
@@ -70,7 +70,7 @@
for(var/client/staff as anything in GLOB.admins)
if(staff?.prefs.read_preference(/datum/preference/toggle/comms_notification))
SEND_SOUND(staff, sound('sound/misc/server-ready.ogg'))
- to_chat(GLOB.admins, msg, confidential = TRUE)
+ to_chat(GLOB.admins, msg, type = MESSAGE_TYPE_PRAYER, confidential = TRUE)
for(var/obj/machinery/computer/communications/console in GLOB.shuttle_caller_list)
console.override_cooldown()
@@ -81,6 +81,6 @@
msg = span_adminnotice("NUKE CODE REQUEST:[ADMIN_FULLMONTY(sender)] [ADMIN_CENTCOM_REPLY(sender)] [ADMIN_SET_SD_CODE]: [msg]")
for(var/client/staff as anything in GLOB.admins)
SEND_SOUND(staff, sound('sound/misc/server-ready.ogg'))
- to_chat(GLOB.admins, msg, confidential = TRUE)
+ to_chat(GLOB.admins, msg, type = MESSAGE_TYPE_PRAYER, confidential = TRUE)
for(var/obj/machinery/computer/communications/console in GLOB.shuttle_caller_list)
console.override_cooldown()
diff --git a/code/modules/antagonists/abductor/equipment/gland.dm b/code/modules/antagonists/abductor/equipment/gland.dm
index 9f5bae7aad53f..02af10227e8df 100644
--- a/code/modules/antagonists/abductor/equipment/gland.dm
+++ b/code/modules/antagonists/abductor/equipment/gland.dm
@@ -50,8 +50,7 @@
if(!owner)
return
var/image/holder = owner.hud_list[GLAND_HUD]
- var/icon/I = icon(owner.icon, owner.icon_state, owner.dir)
- holder.pixel_y = I.Height() - ICON_SIZE_Y
+ holder.pixel_y = owner.get_cached_height() - ICON_SIZE_Y
if(active_mind_control)
holder.icon_state = "hudgland_active"
else if(mind_control_uses)
diff --git a/code/modules/antagonists/changeling/powers/headcrab.dm b/code/modules/antagonists/changeling/powers/headcrab.dm
index 1af11d3ad15ef..70f74d4e4297e 100644
--- a/code/modules/antagonists/changeling/powers/headcrab.dm
+++ b/code/modules/antagonists/changeling/powers/headcrab.dm
@@ -5,11 +5,17 @@
button_icon_state = "last_resort"
chemical_cost = 20
dna_cost = CHANGELING_POWER_INNATE
- req_human = TRUE
req_stat = DEAD
ignores_fakedeath = TRUE
disabled_by_fire = FALSE
+/datum/action/changeling/headcrab/can_be_used_by(mob/living/user)
+ if(HAS_TRAIT(user, TRAIT_TEMPORARY_BODY))
+ return FALSE
+ if(isanimal_or_basicmob(user) && !istype(user, /mob/living/basic/headslug) && !isconstruct(user) && !(user.mob_biotypes & MOB_SPIRIT))
+ return TRUE
+ return ..()
+
/datum/action/changeling/headcrab/sting_action(mob/living/user)
set waitfor = FALSE
var/confirm = tgui_alert(user, "Are we sure we wish to destroy our body and create a headslug?", "Last Resort", list("Yes", "No"))
diff --git a/code/modules/antagonists/changeling/powers/transform.dm b/code/modules/antagonists/changeling/powers/transform.dm
index 733e0495118bc..b13b07f7f360d 100644
--- a/code/modules/antagonists/changeling/powers/transform.dm
+++ b/code/modules/antagonists/changeling/powers/transform.dm
@@ -141,8 +141,7 @@
. = ..()
if(hud_icon)
var/image/holder = user.hud_list[ID_HUD]
- var/icon/I = icon(user.icon, user.icon_state, user.dir)
- holder.pixel_y = I.Height() - ICON_SIZE_Y
+ holder.pixel_y = user.get_cached_height() - ICON_SIZE_Y
holder.icon_state = hud_icon
/**
diff --git a/code/modules/antagonists/heretic/heretic_living_heart.dm b/code/modules/antagonists/heretic/heretic_living_heart.dm
index 81d7e1224575c..b41f616b8b3a3 100644
--- a/code/modules/antagonists/heretic/heretic_living_heart.dm
+++ b/code/modules/antagonists/heretic/heretic_living_heart.dm
@@ -32,8 +32,8 @@
REMOVE_TRAIT(parent, TRAIT_LIVING_HEART, REF(src))
UnregisterSignal(parent, list(COMSIG_ORGAN_REMOVED, COMSIG_ORGAN_BEING_REPLACED))
-/datum/component/living_heart/PostTransfer()
- if(!isorgan(parent))
+/datum/component/living_heart/PostTransfer(datum/new_parent)
+ if(!isorgan(new_parent))
return COMPONENT_INCOMPATIBLE
/**
diff --git a/code/modules/antagonists/heretic/items/keyring.dm b/code/modules/antagonists/heretic/items/keyring.dm
index a37b55c17f3fb..1d8e88a8c5975 100644
--- a/code/modules/antagonists/heretic/items/keyring.dm
+++ b/code/modules/antagonists/heretic/items/keyring.dm
@@ -64,6 +64,8 @@
if(!do_teleport(teleportee, get_turf(doorstination), channel = TELEPORT_CHANNEL_MAGIC))
return
+ teleportee.client?.move_delay = 0 //make moving through smoother
+
if(!IS_HERETIC_OR_MONSTER(teleportee))
teleportee.apply_damage(20, BRUTE) //so they dont roll it like a jackpot machine to see if they can land in the armory
to_chat(teleportee, span_userdanger("You stumble through [src], battered by forces beyond your comprehension, landing anywhere but where you thought you were going."))
@@ -109,7 +111,7 @@
if(!IS_HERETIC_OR_MONSTER(user))
return
. += span_hypnophrase("Enchanted by the Mansus!")
- . += span_hypnophrase("Using an ID on this will consume it and allow you to copy its accesses.")
+ . += span_hypnophrase("Using an ID on this or using this ID on another ID will consume it and allow you to copy its accesses.")
. += span_hypnophrase("Using this in-hand allows you to change its appearance.")
. += span_hypnophrase("Using this on a pair of doors, allows you to link them together. Entering one door will transport you to the other, while heathens are instead teleported to a random airlock.")
. += span_hypnophrase("Ctrl-clicking the ID, makes the ID make inverted portals instead, which teleport you onto a random airlock onstation, while heathens are teleported to the destination.")
@@ -169,18 +171,28 @@
portal_two.destination = portal_one
balloon_alert(user, "[message]")
-/obj/item/card/id/advanced/heretic/attackby(obj/item/thing, mob/user, params)
- if(!istype(thing, /obj/item/card/id/advanced) || !IS_HERETIC(user))
+/obj/item/card/id/advanced/heretic/item_interaction(mob/living/user, obj/item/tool, list/modifiers)
+ if(!istype(tool, /obj/item/card/id/advanced) || !IS_HERETIC(user))
return ..()
- var/obj/item/card/id/card = thing
+ eat_card(tool, user)
+ return ITEM_INTERACT_SUCCESS
+
+/obj/item/card/id/advanced/heretic/proc/eat_card(obj/item/card/id/card, mob/user)
+ if(card == src)
+ return //no self vore
fused_ids[card.name] = card
card.moveToNullspace()
- playsound(drop_location(),'sound/items/eatfood.ogg', rand(10,50), TRUE)
+ playsound(drop_location(), 'sound/items/eatfood.ogg', rand(10,30), TRUE)
access += card.access
+ if(!isnull(user))
+ balloon_alert(user, "consumed card")
/obj/item/card/id/advanced/heretic/interact_with_atom(atom/target, mob/living/user, list/modifiers)
if(!IS_HERETIC(user))
return NONE
+ if(istype(target, /obj/item/card/id))
+ eat_card(target, user)
+ return ITEM_INTERACT_SUCCESS
if(istype(target, /obj/effect/lock_portal))
clear_portals()
return ITEM_INTERACT_SUCCESS
diff --git a/code/modules/antagonists/heretic/knowledge/lock_lore.dm b/code/modules/antagonists/heretic/knowledge/lock_lore.dm
index 96c6110f6df79..d323beecc1854 100644
--- a/code/modules/antagonists/heretic/knowledge/lock_lore.dm
+++ b/code/modules/antagonists/heretic/knowledge/lock_lore.dm
@@ -117,11 +117,11 @@
/datum/heretic_knowledge/limited_amount/concierge_rite // item that creates 3 max at a time heretic only barriers, probably should limit to 1 only, holy people can also pass
name = "Concierge's Rite"
- desc = "Allows you to transmute a stick of chalk, a wooden plank, and a multitool to create a Labyrinth Handbook. \
+ desc = "Allows you to transmute a crayon, a wooden plank, and a multitool to create a Labyrinth Handbook. \
It can materialize a barricade at range that only you and people resistant to magic can pass. 3 uses."
gain_text = "The Concierge scribbled my name into the Handbook. \"Welcome to your new home, fellow Steward.\""
required_atoms = list(
- /obj/item/toy/crayon/white = 1,
+ /obj/item/toy/crayon = 1,
/obj/item/stack/sheet/mineral/wood = 1,
/obj/item/multitool = 1,
)
diff --git a/code/modules/antagonists/heretic/knowledge/sacrifice_knowledge/sacrifice_map.dm b/code/modules/antagonists/heretic/knowledge/sacrifice_knowledge/sacrifice_map.dm
index 07b126fe74f2f..3dee1c0176f12 100644
--- a/code/modules/antagonists/heretic/knowledge/sacrifice_knowledge/sacrifice_map.dm
+++ b/code/modules/antagonists/heretic/knowledge/sacrifice_knowledge/sacrifice_map.dm
@@ -84,7 +84,7 @@ GLOBAL_LIST_EMPTY(heretic_sacrifice_landmarks)
/area/centcom/heretic_sacrifice
name = "Mansus"
icon_state = "heretic"
- has_gravity = STANDARD_GRAVITY
+ default_gravity = STANDARD_GRAVITY
ambience_index = AMBIENCE_SPOOKY
sound_environment = SOUND_ENVIRONMENT_CAVE
area_flags = UNIQUE_AREA | NOTELEPORT | HIDDEN_AREA | BLOCK_SUICIDE | NO_BOH
diff --git a/code/modules/antagonists/heretic/magic/burglar_finesse.dm b/code/modules/antagonists/heretic/magic/burglar_finesse.dm
index a90acb8495f14..fec0fb923bbd2 100644
--- a/code/modules/antagonists/heretic/magic/burglar_finesse.dm
+++ b/code/modules/antagonists/heretic/magic/burglar_finesse.dm
@@ -13,10 +13,13 @@
invocation_type = INVOCATION_WHISPER
spell_requirements = NONE
- cast_range = 4
+ cast_range = 6
-/datum/action/cooldown/spell/pointed/burglar_finesse/is_valid_target(atom/cast_on)
- return ..() && ishuman(cast_on) && (locate(/obj/item/storage/backpack) in cast_on.contents)
+/datum/action/cooldown/spell/pointed/burglar_finesse/is_valid_target(mob/living/carbon/human/cast_on)
+ if(!istype(cast_on))
+ return FALSE
+ var/obj/item/back_item = cast_on.get_item_by_slot(ITEM_SLOT_BACK)
+ return ..() && back_item?.atom_storage
/datum/action/cooldown/spell/pointed/burglar_finesse/cast(mob/living/carbon/human/cast_on)
. = ..()
@@ -25,12 +28,12 @@
to_chat(owner, span_danger("[cast_on] is protected by holy forces!"))
return FALSE
- var/obj/storage_item = locate(/obj/item/storage/backpack) in cast_on.contents
+ var/obj/storage_item = cast_on.get_item_by_slot(ITEM_SLOT_BACK)
if(isnull(storage_item))
return FALSE
- var/item = pick(storage_item.contents)
+ var/item = pick(storage_item.atom_storage.return_inv(recursive = FALSE))
if(isnull(item))
return FALSE
diff --git a/code/modules/antagonists/heretic/magic/caretaker.dm b/code/modules/antagonists/heretic/magic/caretaker.dm
index b882386329a89..01a9970b20477 100644
--- a/code/modules/antagonists/heretic/magic/caretaker.dm
+++ b/code/modules/antagonists/heretic/magic/caretaker.dm
@@ -24,7 +24,7 @@
/datum/action/cooldown/spell/caretaker/is_valid_target(atom/cast_on)
return isliving(cast_on)
-/datum/action/cooldown/spell/caretaker/before_cast(atom/cast_on)
+/datum/action/cooldown/spell/caretaker/before_cast(mob/living/cast_on)
. = ..()
if(. & SPELL_CANCEL_CAST)
return
@@ -34,6 +34,9 @@
owner.balloon_alert(owner, "other minds nearby!")
return . | SPELL_CANCEL_CAST
+ if(!cast_on.has_status_effect(/datum/status_effect/caretaker_refuge))
+ return SPELL_NO_IMMEDIATE_COOLDOWN // cooldown only on exit
+
/datum/action/cooldown/spell/caretaker/cast(mob/living/cast_on)
. = ..()
diff --git a/code/modules/antagonists/heretic/status_effects/mark_effects.dm b/code/modules/antagonists/heretic/status_effects/mark_effects.dm
index de895d33caac8..ba8a86340d7ba 100644
--- a/code/modules/antagonists/heretic/status_effects/mark_effects.dm
+++ b/code/modules/antagonists/heretic/status_effects/mark_effects.dm
@@ -235,12 +235,16 @@
/datum/status_effect/eldritch/lock/on_apply()
. = ..()
- ADD_TRAIT(owner, TRAIT_ALWAYS_NO_ACCESS, STATUS_EFFECT_TRAIT)
+ RegisterSignal(owner, COMSIG_MOB_TRIED_ACCESS, PROC_REF(attempt_access))
/datum/status_effect/eldritch/lock/on_remove()
- REMOVE_TRAIT(owner, TRAIT_ALWAYS_NO_ACCESS, STATUS_EFFECT_TRAIT)
+ UnregisterSignal(owner, COMSIG_MOB_TRIED_ACCESS)
return ..()
+/datum/status_effect/eldritch/lock/proc/attempt_access(datum/source, obj/door_attempt)
+ SIGNAL_HANDLER
+ return ACCESS_DISALLOWED
+
// MARK OF MOON
/datum/status_effect/eldritch/moon
diff --git a/code/modules/antagonists/voidwalker/voidwalker_kidnap.dm b/code/modules/antagonists/voidwalker/voidwalker_kidnap.dm
index f0d4c4349cef7..8a5b95c849f55 100644
--- a/code/modules/antagonists/voidwalker/voidwalker_kidnap.dm
+++ b/code/modules/antagonists/voidwalker/voidwalker_kidnap.dm
@@ -18,7 +18,7 @@ GLOBAL_LIST_EMPTY(voidwalker_void)
/area/centcom/voidwalker_void
name = "Voidwalker void"
icon_state = "voidwalker"
- has_gravity = STANDARD_GRAVITY
+ default_gravity = STANDARD_GRAVITY
ambience_index = AMBIENCE_SPOOKY
sound_environment = SOUND_ENVIRONMENT_CAVE
area_flags = UNIQUE_AREA | NOTELEPORT | HIDDEN_AREA | BLOCK_SUICIDE
diff --git a/code/modules/atmospherics/machinery/atmosmachinery.dm b/code/modules/atmospherics/machinery/atmosmachinery.dm
index 99e014d564b86..9705246fa593c 100644
--- a/code/modules/atmospherics/machinery/atmosmachinery.dm
+++ b/code/modules/atmospherics/machinery/atmosmachinery.dm
@@ -26,7 +26,7 @@
///Check if the object can be unwrenched
var/can_unwrench = FALSE
///Bitflag of the initialized directions (NORTH | SOUTH | EAST | WEST)
- var/initialize_directions = 0
+ var/initialize_directions = NONE
///The color of the pipe
var/pipe_color = COLOR_VERY_LIGHT_GRAY
///What layer the pipe is in (from 1 to 5, default 3)
@@ -41,7 +41,7 @@
var/image/pipe_vision_img = null
///The type of the device (UNARY, BINARY, TRINARY, QUATERNARY)
- var/device_type = 0
+ var/device_type = NONE
///The lists of nodes that a pipe/device has, depends on the device_type var (from 1 to 4)
var/list/obj/machinery/atmospherics/nodes
@@ -257,8 +257,7 @@
* Return a list of the nodes that can connect to other machines, get called by atmos_init()
*/
/obj/machinery/atmospherics/proc/get_node_connects()
- var/list/node_connects = list()
- node_connects.len = device_type
+ var/list/node_connects[device_type] //empty list of size device_type
var/init_directions = get_init_directions()
for(var/i in 1 to device_type)
diff --git a/code/modules/atmospherics/machinery/components/gas_recipe_machines/crystallizer_items.dm b/code/modules/atmospherics/machinery/components/gas_recipe_machines/crystallizer_items.dm
index 6308e1eee611e..a7ad6f4664fb2 100644
--- a/code/modules/atmospherics/machinery/components/gas_recipe_machines/crystallizer_items.dm
+++ b/code/modules/atmospherics/machinery/components/gas_recipe_machines/crystallizer_items.dm
@@ -29,7 +29,7 @@
to_chat(user, span_notice("You see how the [worn_item] changes color, it's now pressure proof."))
worn_item.name = "pressure-resistant [worn_item.name]"
worn_item.remove_atom_colour(WASHABLE_COLOUR_PRIORITY)
- worn_item.add_atom_colour("#00fff7", FIXED_COLOUR_PRIORITY)
+ worn_item.add_atom_colour(color_transition_filter("#00fff7", SATURATION_OVERRIDE), FIXED_COLOUR_PRIORITY)
worn_item.min_cold_protection_temperature = SPACE_SUIT_MIN_TEMP_PROTECT
worn_item.cold_protection = worn_item.body_parts_covered
worn_item.clothing_flags |= STOPSPRESSUREDAMAGE
diff --git a/code/modules/atmospherics/machinery/components/tank.dm b/code/modules/atmospherics/machinery/components/tank.dm
index 34558d8d24532..118e1444ad68e 100644
--- a/code/modules/atmospherics/machinery/components/tank.dm
+++ b/code/modules/atmospherics/machinery/components/tank.dm
@@ -31,6 +31,8 @@
///The image showing the gases inside of the tank
var/image/window
+ /// The open node directions of the tank, assuming that the tank is facing NORTH.
+ var/open_ports = NONE
/// The volume of the gas mixture
var/volume = 2500 //in liters
/// The max pressure of the gas mixture before damaging the tank
@@ -97,7 +99,8 @@
// Mapped in tanks should automatically connect to adjacent pipenets in the direction set in dir
if(mapload)
- initialize_directions = dir
+ set_portdir_relative(dir, TRUE)
+ set_init_directions()
return INITIALIZE_HINT_LATELOAD
@@ -151,28 +154,60 @@
refresh_window()
///////////////////////////////////////////////////////////////////
-// Pipenet stuff
-
-/obj/machinery/atmospherics/components/tank/return_analyzable_air()
- return air_contents
+// Port stuff
+
+/**
+ * Enables/Disables a port direction in var/open_ports. \
+ * Use this, then call set_init_directions() instead of setting initialize_directions directly \
+ * This system exists because tanks not having all initialize_directions set correctly breaks shuttle rotations
+ */
+/obj/machinery/atmospherics/components/tank/proc/set_portdir_relative(relative_port_dir, enable)
+ ASSERT(!isnull(enable), "Did not receive argument enable")
+
+ // Rotate the given dir so that it's relative to north
+ var/port_dir
+ if(dir == NORTH) // We're already facing north, no rotation needed
+ port_dir = relative_port_dir
+ else
+ var/offnorth_angle = dir2angle(dir)
+ port_dir = turn(relative_port_dir, offnorth_angle)
-/obj/machinery/atmospherics/components/tank/return_airs_for_reconcilation(datum/pipeline/requester)
- . = ..()
- if(!air_contents)
+ if(enable)
+ open_ports |= port_dir
+ else
+ open_ports &= ~port_dir
+
+/**
+ * Toggles a port direction in var/open_ports \
+ * Use this, then call set_init_directions() instead of setting initialize_directions directly \
+ * This system exists because tanks not having all initialize_directions set correctly breaks shuttle rotations
+ */
+/obj/machinery/atmospherics/components/tank/proc/toggle_portdir_relative(relative_port_dir)
+ var/toggle = ((initialize_directions & relative_port_dir) ? FALSE : TRUE)
+ set_portdir_relative(relative_port_dir, toggle)
+
+/obj/machinery/atmospherics/components/tank/set_init_directions()
+ if(!open_ports)
+ initialize_directions = NONE
return
- . += air_contents
-/obj/machinery/atmospherics/components/tank/return_pipenets_for_reconcilation(datum/pipeline/requester)
- . = ..()
- var/datum/merger/merge_group = GetMergeGroup(merger_id, merger_typecache)
- for(var/obj/machinery/atmospherics/components/tank/tank as anything in merge_group.members)
- . += tank.parents
+ //We're rotating open_ports relative to dir, and
+ //setting initialize_directions to that rotated dir
+ var/relative_port_dirs = NONE
+ var/dir_angle = dir2angle(dir)
+ for(var/cardinal in GLOB.cardinals)
+ var/current_dir = cardinal & open_ports
+ if(!current_dir)
+ continue
-/obj/machinery/atmospherics/components/tank/proc/toggle_side_port(new_dir)
- if(initialize_directions & new_dir)
- initialize_directions &= ~new_dir
- else
- initialize_directions |= new_dir
+ var/rotated_dir = turn(current_dir, -dir_angle)
+ relative_port_dirs |= rotated_dir
+
+ initialize_directions = relative_port_dirs
+
+/obj/machinery/atmospherics/components/tank/proc/toggle_side_port(port_dir)
+ toggle_portdir_relative(port_dir)
+ set_init_directions()
for(var/i in 1 to length(nodes))
var/obj/machinery/atmospherics/components/node = nodes[i]
@@ -195,6 +230,24 @@
update_parents()
+///////////////////////////////////////////////////////////////////
+// Pipenet stuff
+
+/obj/machinery/atmospherics/components/tank/return_analyzable_air()
+ return air_contents
+
+/obj/machinery/atmospherics/components/tank/return_airs_for_reconcilation(datum/pipeline/requester)
+ . = ..()
+ if(!air_contents)
+ return
+ . += air_contents
+
+/obj/machinery/atmospherics/components/tank/return_pipenets_for_reconcilation(datum/pipeline/requester)
+ . = ..()
+ var/datum/merger/merge_group = GetMergeGroup(merger_id, merger_typecache)
+ for(var/obj/machinery/atmospherics/components/tank/tank as anything in merge_group.members)
+ . += tank.parents
+
///////////////////////////////////////////////////////////////////
// Merger handling
diff --git a/code/modules/atmospherics/machinery/components/unary_devices/cryo.dm b/code/modules/atmospherics/machinery/components/unary_devices/cryo.dm
index 70cafb11be888..97b9741701f8a 100644
--- a/code/modules/atmospherics/machinery/components/unary_devices/cryo.dm
+++ b/code/modules/atmospherics/machinery/components/unary_devices/cryo.dm
@@ -74,7 +74,6 @@
circuit = /obj/item/circuitboard/machine/cryo_tube
occupant_typecache = list(/mob/living/carbon, /mob/living/simple_animal)
processing_flags = NONE
- fair_market_price = 10
payment_department = ACCOUNT_MED
use_power = IDLE_POWER_USE
idle_power_usage = BASE_MACHINE_IDLE_CONSUMPTION * 0.75
diff --git a/code/modules/awaymissions/mission_code/Beach.dm b/code/modules/awaymissions/mission_code/Beach.dm
index 7f0e27c090ae2..0755389c030ca 100644
--- a/code/modules/awaymissions/mission_code/Beach.dm
+++ b/code/modules/awaymissions/mission_code/Beach.dm
@@ -5,7 +5,7 @@
base_lighting_alpha = 255
base_lighting_color = "#FFFFCC"
requires_power = FALSE
- has_gravity = STANDARD_GRAVITY
+ default_gravity = STANDARD_GRAVITY
ambientsounds = list('sound/ambience/beach/shore.ogg', 'sound/ambience/beach/seag1.ogg','sound/ambience/beach/seag2.ogg','sound/ambience/beach/seag3.ogg','sound/ambience/misc/ambiodd.ogg','sound/ambience/medical/ambinice.ogg')
/obj/item/paper/fluff/old_pirate_note
diff --git a/code/modules/basketball/basketball_map_loading.dm b/code/modules/basketball/basketball_map_loading.dm
index 469167367c0f0..5ed18997dde40 100644
--- a/code/modules/basketball/basketball_map_loading.dm
+++ b/code/modules/basketball/basketball_map_loading.dm
@@ -31,7 +31,7 @@
requires_power = FALSE
static_lighting = FALSE
base_lighting_alpha = 255
- has_gravity = STANDARD_GRAVITY
+ default_gravity = STANDARD_GRAVITY
flags_1 = NONE
area_flags = UNIQUE_AREA | NOTELEPORT | NO_DEATH_MESSAGE | BLOCK_SUICIDE
diff --git a/code/modules/bitrunning/areas.dm b/code/modules/bitrunning/areas.dm
index 0656f9d65b389..f0ad122effc7c 100644
--- a/code/modules/bitrunning/areas.dm
+++ b/code/modules/bitrunning/areas.dm
@@ -15,7 +15,7 @@
icon_state = "bit_ruin"
icon = 'icons/area/areas_station.dmi'
area_flags = UNIQUE_AREA | NOTELEPORT | EVENT_PROTECTED | HIDDEN_AREA | UNLIMITED_FISHING
- has_gravity = STANDARD_GRAVITY
+ default_gravity = STANDARD_GRAVITY
requires_power = FALSE
/area/virtual_domain/fullbright
diff --git a/code/modules/bitrunning/components/avatar_connection.dm b/code/modules/bitrunning/components/avatar_connection.dm
index 1ed1647a672b4..9fdfe1f629ec2 100644
--- a/code/modules/bitrunning/components/avatar_connection.dm
+++ b/code/modules/bitrunning/components/avatar_connection.dm
@@ -76,15 +76,15 @@
avatar.set_temp_blindness(1 SECONDS) // I'm in
-/datum/component/avatar_connection/PostTransfer()
+/datum/component/avatar_connection/PostTransfer(datum/new_parent)
var/obj/machinery/netpod/pod = netpod_ref?.resolve()
if(isnull(pod))
return COMPONENT_INCOMPATIBLE
- if(!isliving(parent))
+ if(!isliving(new_parent))
return COMPONENT_INCOMPATIBLE
- pod.avatar_ref = WEAKREF(parent)
+ pod.avatar_ref = WEAKREF(new_parent)
/datum/component/avatar_connection/RegisterWithParent()
diff --git a/code/modules/buildmode/buttons.dm b/code/modules/buildmode/buttons.dm
index d9a0a0faf63d8..0bf7350937372 100644
--- a/code/modules/buildmode/buttons.dm
+++ b/code/modules/buildmode/buttons.dm
@@ -1,5 +1,6 @@
/atom/movable/screen/buildmode
icon = 'icons/hud/buildmode.dmi'
+ mouse_over_pointer = MOUSE_HAND_POINTER
var/datum/buildmode/bd
// If we don't do this, we get occluded by item action buttons
plane = ABOVE_HUD_PLANE
diff --git a/code/modules/capture_the_flag/ctf_player_component.dm b/code/modules/capture_the_flag/ctf_player_component.dm
index 5a02a954aba6a..0424fe13166b5 100644
--- a/code/modules/capture_the_flag/ctf_player_component.dm
+++ b/code/modules/capture_the_flag/ctf_player_component.dm
@@ -24,10 +24,10 @@
ckey_reference = player_mob.ckey
register_mob()
-/datum/component/ctf_player/PostTransfer()
- if(!istype(parent, /datum/mind))
+/datum/component/ctf_player/PostTransfer(datum/new_parent)
+ if(!istype(new_parent, /datum/mind))
return COMPONENT_INCOMPATIBLE
- var/datum/mind/true_parent = parent
+ var/datum/mind/true_parent = new_parent
player_mob = true_parent.current
register_mob()
diff --git a/code/modules/cargo/goodies.dm b/code/modules/cargo/goodies.dm
index 0b6451e569828..5abf8f2dd27f7 100644
--- a/code/modules/cargo/goodies.dm
+++ b/code/modules/cargo/goodies.dm
@@ -32,6 +32,27 @@
access_view = ACCESS_WEAPONS
contains = list(/obj/item/ammo_box/c38/match/bouncy)
+/datum/supply_pack/goody/dumdum38br
+ name = ".38 DumDum Magazine Single-Pack"
+ desc = "Contains one magazine of .38 DumDum ammunition, good for embedding in soft targets."
+ cost = PAYCHECK_CREW * 2
+ access_view = ACCESS_WEAPONS
+ contains = list(/obj/item/ammo_box/magazine/m38/dumdum)
+
+/datum/supply_pack/goody/match38br
+ name = ".38 Match Grade Magazine Single-Pack"
+ desc = "Contains one magazine of match grade .38 ammunition, perfect for showing off trickshots."
+ cost = PAYCHECK_CREW * 2
+ access_view = ACCESS_WEAPONS
+ contains = list(/obj/item/ammo_box/magazine/m38/match)
+
+/datum/supply_pack/goody/rubber
+ name = ".38 Rubber Magazine Single-Pack"
+ desc = "Contains one magazine of bouncy rubber .38 ammunition, for when you want to bounce your shots off anything and everything."
+ cost = PAYCHECK_CREW * 1.5
+ access_view = ACCESS_WEAPONS
+ contains = list(/obj/item/ammo_box/magazine/m38/match/bouncy)
+
/datum/supply_pack/goody/mars_single
name = "Colt Detective Special Single-Pack"
desc = "The HoS took your gun and your badge? No problem! Just pay the absurd taxation fee and you too can be reunited with the lethal power of a .38!"
diff --git a/code/modules/cargo/packs/medical.dm b/code/modules/cargo/packs/medical.dm
index 8255a43d0490b..4b3847bd89835 100644
--- a/code/modules/cargo/packs/medical.dm
+++ b/code/modules/cargo/packs/medical.dm
@@ -208,3 +208,4 @@
contains = list(/obj/structure/closet/body_bag/lost_crew/with_body)
crate_name = "body freezer"
crate_type = /obj/structure/closet/crate/secure/freezer
+
diff --git a/code/modules/cargo/packs/security.dm b/code/modules/cargo/packs/security.dm
index 1823ef5174f94..b233ecd78dd38 100644
--- a/code/modules/cargo/packs/security.dm
+++ b/code/modules/cargo/packs/security.dm
@@ -248,6 +248,30 @@
crate_name = "disabler smg crate"
crate_type = /obj/structure/closet/crate/secure/plasma
+/datum/supply_pack/security/armory/battle_rifle
+ name = "NT BR-38 Crate"
+ desc = "An experimental energy-based ballistc battle rifle. Only available to \
+ Nanotrasen stations for security purposes. DO NOT RESELL TO OUTSIDE COMPANIES. \
+ Contains three NT BR-38 rifles and three magazines containing .38 Standard."
+ cost = CARGO_CRATE_VALUE * 100
+ contains = list(
+ /obj/item/gun/ballistic/automatic/battle_rifle = 2,
+ /obj/item/ammo_box/magazine/m38 = 4,
+ )
+ crate_name = "battle rifle crate"
+
+/datum/supply_pack/security/armory/br_mag
+ name = "NT BR-38 Magazine Crate"
+ desc = "Six .38 magazines, able to fit into the NT BR-38. Contains \
+ two standard magazines, two Hot Shot magazines and two Iceblox magazines."
+ cost = CARGO_CRATE_VALUE * 7
+ contains = list(
+ /obj/item/ammo_box/magazine/m38 = 2,
+ /obj/item/ammo_box/magazine/m38/hotshot = 2,
+ /obj/item/ammo_box/magazine/m38/iceblox =2,
+ )
+ crate_name = ".38 magazine crate"
+
/datum/supply_pack/security/armory/exileimp
name = "Exile Implants Crate"
desc = "Contains five Exile implants."
diff --git a/code/modules/clothing/suits/wintercoats.dm b/code/modules/clothing/suits/wintercoats.dm
index f09b6ac2f8585..581d1c49964b6 100644
--- a/code/modules/clothing/suits/wintercoats.dm
+++ b/code/modules/clothing/suits/wintercoats.dm
@@ -67,7 +67,8 @@
body_parts_covered = HEAD
cold_protection = HEAD
min_cold_protection_temperature = FIRE_SUIT_MIN_TEMP_PROTECT
- flags_inv = HIDEHAIR|HIDEEARS
+ flags_inv = HIDEEARS
+ hair_mask = HAIR_MASK_HIDE_WINTERHOOD
armor_type = /datum/armor/hooded_winterhood
// An coat intended for use for general crew EVA, with values close to those of the space suits found in EVA normally
@@ -78,6 +79,7 @@
/obj/item/clothing/suit/hooded/wintercoat/eva
name = "\proper Endotherm winter coat"
desc = "A thickly padded winter coat to keep the wearer well insulated no matter the circumstances. It has a harness for a larger oxygen tank attached to the back."
+ icon_state = "coateva"
w_class = WEIGHT_CLASS_BULKY
slowdown = 0.75
armor_type = /datum/armor/wintercoat_eva
@@ -85,7 +87,6 @@
equip_delay_other = 6 SECONDS
min_cold_protection_temperature = SPACE_SUIT_MIN_TEMP_PROTECT // Protects very cold.
max_heat_protection_temperature = SPACE_SUIT_MAX_TEMP_PROTECT // Protects a little hot.
- flags_inv = HIDEJUMPSUIT
clothing_flags = THICKMATERIAL
resistance_flags = NONE
hoodtype = /obj/item/clothing/head/hooded/winterhood/eva
@@ -105,6 +106,7 @@
/obj/item/clothing/head/hooded/winterhood/eva
name = "\proper Endotherm winter hood"
desc = "A thickly padded hood attached to an even thicker coat."
+ icon_state = "hood_eva"
armor_type = /datum/armor/winterhood_eva
min_cold_protection_temperature = SPACE_HELM_MIN_TEMP_PROTECT
max_heat_protection_temperature = SPACE_HELM_MAX_TEMP_PROTECT
diff --git a/code/modules/deathmatch/deathmatch_mapping.dm b/code/modules/deathmatch/deathmatch_mapping.dm
index a0651f7da121b..62058629cdb08 100644
--- a/code/modules/deathmatch/deathmatch_mapping.dm
+++ b/code/modules/deathmatch/deathmatch_mapping.dm
@@ -1,7 +1,7 @@
/area/deathmatch
name = "Deathmatch Arena"
requires_power = FALSE
- has_gravity = STANDARD_GRAVITY
+ default_gravity = STANDARD_GRAVITY
area_flags = UNIQUE_AREA | NOTELEPORT | EVENT_PROTECTED | QUIET_LOGS | NO_DEATH_MESSAGE | BINARY_JAMMING
/area/deathmatch/fullbright
diff --git a/code/modules/events/anomaly/anomaly_bioscrambler.dm b/code/modules/events/anomaly/anomaly_bioscrambler.dm
index 08afff8dd31c7..b67e70b2412c6 100644
--- a/code/modules/events/anomaly/anomaly_bioscrambler.dm
+++ b/code/modules/events/anomaly/anomaly_bioscrambler.dm
@@ -17,4 +17,4 @@
/datum/round_event/anomaly/anomaly_bioscrambler/announce(fake)
if(isnull(impact_area))
impact_area = placer.findValidArea()
- priority_announce("Biologic limb swapping agent detected on [ANOMALY_ANNOUNCE_MEDIUM_TEXT] [impact_area.name]. Wear biosuits or other protective gear to counter the effects. Calculated half-life of %9£$T$%F3 years.", "Anomaly Alert")
+ priority_announce("Biologic limb swapping agent detected on [ANOMALY_ANNOUNCE_MEDIUM_TEXT] [impact_area.name]. Wear biosuits or other protective gear to counter the effects.", "Anomaly Alert")
diff --git a/code/modules/events/tram_malfunction.dm b/code/modules/events/tram_malfunction.dm
index 600dfed805b2f..088285e4f17e2 100644
--- a/code/modules/events/tram_malfunction.dm
+++ b/code/modules/events/tram_malfunction.dm
@@ -42,7 +42,7 @@
/datum/round_event/tram_malfunction/end()
for(var/datum/transport_controller/linear/tram/malfunctioning_controller as anything in SStransport.transports_by_type[TRANSPORT_TYPE_TRAM])
- if(malfunctioning_controller.specific_transport_id == specific_transport_id && malfunctioning_controller.malf_active)
+ if(malfunctioning_controller.specific_transport_id == specific_transport_id && malfunctioning_controller.malf_active != TRANSPORT_SYSTEM_NORMAL)
malfunctioning_controller.end_malf_event()
return
diff --git a/code/modules/fishing/fish/_fish.dm b/code/modules/fishing/fish/_fish.dm
index bfcc082afd959..0c09afb90cd2d 100644
--- a/code/modules/fishing/fish/_fish.dm
+++ b/code/modules/fishing/fish/_fish.dm
@@ -172,6 +172,8 @@
var/fish_id
///Used to redirect to another fish path so that catching this fish unlocks its entry instead.
var/obj/item/fish/fish_id_redirect_path
+ /// only used in the suicide for comedic value
+ var/suicide_slap_text = "*SLAP!*"
/obj/item/fish/Initialize(mapload, apply_qualities = TRUE)
. = ..()
@@ -208,6 +210,26 @@
register_context()
register_item_context()
+/obj/item/fish/suicide_act(mob/living/user)
+ if(force == 0)
+ user.visible_message(span_suicide("[user] slaps [user.p_them()]self with [src], but nothing happens!"))
+ return SHAME
+ user.visible_message(span_suicide("[user] starts rapidly slapping [user.p_them()]self with [src]! It looks like [user.p_theyre()] trying to commit suicide!"))
+ user.set_combat_mode(TRUE)
+ ADD_TRAIT(user, TRAIT_COMBAT_MODE_LOCK, REF(src))
+ slapperoni(user, iteration = 1)
+ return MANUAL_SUICIDE
+
+/obj/item/fish/proc/slapperoni(mob/living/user, iteration)
+ stoplag(0.1 SECONDS)
+ user.visible_message(span_bolddanger(suicide_slap_text))
+ user.attackby(src, user)
+ if(user.stat > SOFT_CRIT || (iteration > 100))
+ REMOVE_TRAIT(user, TRAIT_COMBAT_MODE_LOCK, REF(src))
+ user.gib(DROP_ORGANS|DROP_BODYPARTS|DROP_ITEMS)
+ return
+ slapperoni(user, iteration++)
+
/obj/item/fish/add_item_context(atom/source, list/context, obj/item/held_item, mob/user)
if(HAS_TRAIT(source, TRAIT_CATCH_AND_RELEASE))
context[SCREENTIP_CONTEXT_RMB] = "Release"
@@ -517,7 +539,7 @@
if(!maximum_size)
maximum_size = min(base_size * 2, average_size * MAX_FISH_DEVIATION_COEFF)
if(!maximum_weight)
- maximum_weight = min(base_weight * 2, average_size * MAX_FISH_DEVIATION_COEFF)
+ maximum_weight = min(base_weight * 2, average_weight * MAX_FISH_DEVIATION_COEFF)
///Updates weight and size, along with weight class, number of fillets you can get and grind results.
/obj/item/fish/proc/update_size_and_weight(new_size = average_size, new_weight = average_weight, update_materials = TRUE)
@@ -855,16 +877,6 @@
return
fed_reagents.remove_reagent(wrong_reagent.type, 0.1)
-/**
- * Base multiplier of the difference between current size and weight and their maximum value
- * Used to calculate how much fish grow each time they're fed, alongside with the current hunger,
- * and the current size and weight, meaning bigger fish naturally tend to grow way more slowly
- * Growth peaks at 45% hunger but very rapidly wanes past that.
- */
-#define FISH_GROWTH_MULT 0.38
-#define FISH_GROWTH_PEAK 0.45
-#define FISH_SIZE_WEIGHT_GROWTH_MALUS 0.5
-
///Proc that should be called when the fish is fed. By default, it grows the fish depending on various variables.
/obj/item/fish/proc/sate_hunger()
if(HAS_TRAIT(loc, TRAIT_STOP_FISH_REPRODUCTION_AND_GROWTH))
@@ -878,8 +890,8 @@
var/new_size = size
var/new_weight = weight
var/hunger_mult
- if(hunger < FISH_GROWTH_PEAK)
- hunger_mult = hunger * (1/FISH_GROWTH_PEAK)
+ if(hunger <= FISH_GROWTH_PEAK)
+ hunger_mult = hunger / FISH_GROWTH_PEAK
else
hunger_mult = 1 - (hunger - FISH_GROWTH_PEAK) * 4
if(hunger_mult <= 0)
@@ -896,10 +908,6 @@
if(new_size != size || new_weight != weight)
update_size_and_weight(new_size, new_weight)
-#undef FISH_SIZE_WEIGHT_GROWTH_MALUS
-#undef FISH_GROWTH_MULT
-#undef FISH_GROWTH_PEAK
-
/obj/item/fish/proc/check_flopping()
if(QDELETED(src)) //we don't care anymore
return
diff --git a/code/modules/fishing/fish/types/air_space.dm b/code/modules/fishing/fish/types/air_space.dm
index 95418f5248a3b..e25a3d7819b2e 100644
--- a/code/modules/fishing/fish/types/air_space.dm
+++ b/code/modules/fishing/fish/types/air_space.dm
@@ -104,6 +104,16 @@
/obj/item/fish/starfish/flop_animation()
DO_FLOATING_ANIM(src)
+/obj/item/fish/starfish/suicide_act(mob/living/user)
+ user.visible_message(span_suicide("[user] swallows [src], and looks upwards..."))
+ user.say("I must go. My people need me.", forced = "starfish suicide")
+ addtimer(CALLBACK(src, PROC_REF(ascension), user), 1 SECONDS)
+ return MANUAL_SUICIDE
+
+/obj/item/fish/starfish/proc/ascension(mob/living/user)
+ user.apply_status_effect(/datum/status_effect/go_away/deluxe)
+ qdel(src)
+
/obj/item/fish/baby_carp
name = "baby space carp"
fish_id = "baby_carp"
@@ -149,6 +159,37 @@
RegisterSignal(src, COMSIG_FISH_FINISH_GROWING, PROC_REF(on_growth))
update_appearance(UPDATE_OVERLAYS)
+/obj/item/fish/baby_carp/suicide_act(mob/living/user)
+ user.visible_message(span_suicide("[user] swallows [src] whole!"))
+ src.forceMove(user)
+ if(status == FISH_DEAD)
+ user.emote("gasp")
+ user.visible_message(span_suicide("[user] chokes on [src] and dies!"))
+ return OXYLOSS
+
+ // the fish grows
+ addtimer(CALLBACK(src, PROC_REF(gestation), user), 20 SECONDS)
+ user.visible_message(span_suicide("[user] starts growing unnaturally..."))
+
+ var/matrix/M = matrix()
+ M.Scale(1.8, 1.2)
+ animate(user, time = 20 SECONDS, transform = M, easing = SINE_EASING)
+ return MANUAL_SUICIDE
+
+/obj/item/fish/baby_carp/proc/gestation(mob/living/user)
+ if(QDELETED(user) || QDELETED(src))
+ return
+ // carp grow big and strong inside the nutritious innards of the human
+ var/mob/living/basic/carp/mega/babby = new(get_turf(user))
+ babby.name = user.name + " Jr."
+
+ var/obj/item/bodypart/chest = user.get_bodypart(BODY_ZONE_CHEST)
+ if(chest)
+ babby.set_greyscale(chest.species_color) // this isn't working. why isnt this working
+
+ user.gib()
+ qdel(src)
+
/obj/item/fish/baby_carp/update_overlays()
. = ..()
var/mutable_appearance/eyes = mutable_appearance(icon, "baby_carp_eyes")
diff --git a/code/modules/fishing/fish/types/freshwater.dm b/code/modules/fishing/fish/types/freshwater.dm
index cb4546e79616e..8f39d7aa2c86a 100644
--- a/code/modules/fishing/fish/types/freshwater.dm
+++ b/code/modules/fishing/fish/types/freshwater.dm
@@ -108,12 +108,16 @@
icon_state = "plastetra"
sprite_width = 4
sprite_height = 2
- average_size = 30
- average_weight = 500
+ average_size = 20
+ average_weight = 180
stable_population = 3
required_temperature_min = MIN_AQUARIUM_TEMP+20
required_temperature_max = MIN_AQUARIUM_TEMP+28
+/obj/item/fish/plasmatetra/Initialize(mapload, apply_qualities = TRUE)
+ . = ..()
+ add_traits(list(TRAIT_FISHING_BAIT, TRAIT_GOOD_QUALITY_BAIT), INNATE_TRAIT)
+
/obj/item/fish/catfish
name = "catfish"
fish_id = "catfish"
@@ -159,10 +163,22 @@
//anxiety naturally limits the amount of zipzaps per tank, so they are stronger alone
electrogenesis_power = 6.7 MEGA JOULES
beauty = FISH_BEAUTY_GOOD
+ suicide_slap_text = "*ZAP!*"
/obj/item/fish/zipzap/get_fish_taste()
return list("raw fish" = 2, "anxiety" = 1)
+/obj/item/fish/zipzap/suicide_act(mob/living/user)
+ if(!electrocute_mob(user, power_source = get_area(src), source = src, siemens_coeff = 1, dist_check = FALSE))
+ user.visible_message(span_suicide("[user] tries to slap [user.p_them()]self with [src], but they're immune to electricity!"))
+ return SHAME
+ return ..()
+
+// real suicide handled by og fish proc
+/obj/item/fish/zipzap/slapperoni(mob/living/user, iteration)
+ electrocute_mob(user, power_source = get_area(src), source = src, siemens_coeff = 1, dist_check = FALSE) // how do i make this use electrogenesis_power
+ return ..()
+
/obj/item/fish/tadpole
name = "tadpole"
fish_id = "tadpole"
@@ -214,6 +230,25 @@
/obj/item/fish/tadpole/get_export_price(price, elasticity_percent)
return 2 //two credits. Tadpoles aren't really that valueable.
+/obj/item/fish/tadpole/suicide_act(mob/living/user)
+ user.visible_message(span_suicide("[user] swallows [src] whole!"))
+ src.forceMove(user)
+ if(status == FISH_DEAD)
+ user.emote("gasp")
+ user.visible_message(span_suicide("[user] croaks!"))
+ return OXYLOSS
+
+ // the frogg grows
+ addtimer(CALLBACK(src, PROC_REF(gestation), user), 5 SECONDS)
+ return MANUAL_SUICIDE
+
+/obj/item/fish/tadpole/proc/gestation(mob/living/user)
+ if(QDELETED(user) || QDELETED(src))
+ return
+ new /mob/living/basic/frog(user)
+ user.gib()
+ qdel(src)
+
/obj/item/fish/perch
name = "perch"
fish_id = "perch"
diff --git a/code/modules/fishing/fish/types/holographic.dm b/code/modules/fishing/fish/types/holographic.dm
index 4dc304cb0ca04..96664f1496594 100644
--- a/code/modules/fishing/fish/types/holographic.dm
+++ b/code/modules/fishing/fish/types/holographic.dm
@@ -36,6 +36,15 @@
animate(src, alpha = 0, 3 SECONDS, easing = SINE_EASING)
QDEL_IN(src, 3 SECONDS)
+/obj/item/fish/holo/suicide_act(mob/living/user)
+ visible_message(span_suicide("[user] swallows [src] whole! It looks like [user.p_theyre()] trying to derez [user.p_them()]selves!"))
+ var/area/station/holodeck/holo_area = get_area(src)
+ if(!istype(holo_area))
+ user.dust(just_ash = TRUE, drop_items = FALSE)
+ return MANUAL_SUICIDE
+ holo_area.linked.add_to_spawned(user) // oh no
+ return MANUAL_SUICIDE_NONLETHAL
+
/obj/item/fish/holo/crab
name = "holographic crab"
fish_id = "holocrab"
@@ -102,6 +111,20 @@
sprite_height = 3
beauty = FISH_BEAUTY_NULL
+/obj/item/fish/holo/checkered/suicide_act(mob/living/carbon/user)
+
+ if(!iscarbon(user))
+ return ..()
+
+ for(var/obj/item/bodypart/limb in user.bodyparts)
+ limb.add_bodypart_overlay(new /datum/bodypart_overlay/texture/checkered)
+
+ var/obj/item/bodypart/head/head = user.get_bodypart(BODY_ZONE_HEAD)
+ if(!isnull(head))
+ head.head_flags &= ~HEAD_EYESPRITES
+
+ return ..()
+
/obj/item/fish/holo/halffish
name = "holographic half-fish"
fish_id = "halffish"
diff --git a/code/modules/fishing/fish/types/mining.dm b/code/modules/fishing/fish/types/mining.dm
index 73a9b8dc1752d..bc515d6674581 100644
--- a/code/modules/fishing/fish/types/mining.dm
+++ b/code/modules/fishing/fish/types/mining.dm
@@ -146,6 +146,23 @@
/obj/item/fish/boned/get_health_warnings(mob/user, always_deep = FALSE)
return list(span_deadsay("It's bones."))
+/obj/item/fish/boned/suicide_act(mob/living/user)
+ user.visible_message(span_suicide("[user] swallows [src] whole! It looks like [user.p_theyre()] trying to commit suicide!"))
+ forceMove(user)
+ addtimer(CALLBACK(src, PROC_REF(skeleton_appears), user), 2 SECONDS)
+ return MANUAL_SUICIDE_NONLETHAL // chance not to die
+
+/obj/item/fish/boned/proc/skeleton_appears(mob/living/user)
+ user.visible_message(span_warning("[user]'s skin melts off!"), span_boldwarning("Your skin melts off!"))
+ user.spawn_gibs()
+ user.drop_everything(del_on_drop = FALSE, force = FALSE, del_if_nodrop = FALSE)
+ user.set_species(/datum/species/skeleton)
+ user.say("AAAAAAAAAAAAHHHHHHHHHH!!!!!!!!!!!!!!", forced = "bone fish suicide")
+ if(prob(90))
+ addtimer(CALLBACK(user, TYPE_PROC_REF(/mob/living, death)), 3 SECONDS)
+ user.set_suicide(TRUE)
+ qdel(src)
+
/obj/item/fish/lavaloop
name = "lavaloop"
fish_id = "lavaloop"
diff --git a/code/modules/fishing/fish/types/ruins.dm b/code/modules/fishing/fish/types/ruins.dm
index da2d2ef77293b..42a84be3248c1 100644
--- a/code/modules/fishing/fish/types/ruins.dm
+++ b/code/modules/fishing/fish/types/ruins.dm
@@ -40,6 +40,25 @@
/obj/item/fish/mastodon/get_health_warnings(mob/user, always_deep = FALSE)
return list(span_deadsay("It's bones."))
+/obj/item/fish/mastodon/suicide_act(mob/living/user)
+ user.visible_message(span_suicide("[user] swallows [src] whole (somehow)! It looks like user.p_theyre()] trying to commit suicide!"))
+ forceMove(user)
+ user.update_transform(1.25) // become BIG from eating BIG fish
+ addtimer(CALLBACK(src, PROC_REF(skeleton_appears), user), 2 SECONDS)
+ return MANUAL_SUICIDE_NONLETHAL // chance not to die
+
+/obj/item/fish/mastodon/proc/skeleton_appears(mob/living/user)
+ user.visible_message(span_warning("[user]'s skin melts off!"), span_boldwarning("Your skin melts off!"))
+ user.spawn_gibs()
+ user.drop_everything(del_on_drop = FALSE, force = FALSE, del_if_nodrop = FALSE)
+ user.set_species(/datum/species/skeleton)
+ user.say("AAAAAAAAAAAAHHHHHHHHHH!!!!!!!!!!!!!!", forced = "mastodon fish suicide")
+ user.AddComponent(/datum/component/omen) // the curse of the fish
+ if(prob(75)) // rare so less likely (the curse keeps you alive)
+ addtimer(CALLBACK(user, TYPE_PROC_REF(/mob/living, death)), 3 SECONDS)
+ user.set_suicide(TRUE)
+ qdel(src)
+
///From the cursed spring
/obj/item/fish/soul
name = "soulfish"
@@ -74,6 +93,41 @@
/obj/item/fish/soul/get_fish_taste_cooked()
return list("cooked meat" = 2)
+/obj/item/fish/soul/suicide_act(mob/living/user)
+ user.visible_message(span_suicide("[user] swallows [src] whole! It looks like [user.p_theyre()] trying to commit soulcide!"))
+ src.forceMove(user)
+ addtimer(CALLBACK(src, PROC_REF(good_ending), user), 2.5 SECONDS)
+ for(var/i in 1 to 7)
+ addtimer(CALLBACK(src, PROC_REF(soul_attack), user, i), 0.2 SECONDS * i)
+ return MANUAL_SUICIDE
+
+/obj/item/fish/soul/proc/good_ending(mob/living/user)
+ var/mob/living/basic/spaceman/soulman = new(get_turf(user))
+ if(prob(80)) // the percentage is important.
+ soulman.ckey = user.ckey
+ to_chat(soulman, span_notice("You finally feel at peace."))
+ user.gib()
+ qdel(src)
+
+/obj/item/fish/soul/proc/soul_attack(mob/user, iteration)
+ var/obj/item/storage/toolbox/mechanical/old/soulbox = pick(/obj/item/storage/toolbox/mechanical/old, /obj/item/storage/toolbox/mechanical/old/cleaner)
+ soulbox = new soulbox(get_turf(user))
+ var/yeet_direction = pick(GLOB.alldirs)
+ var/yeet_distance = rand(1, 7)
+ if(ishuman(user))
+ var/mob/living/carbon/human/human_user = user
+ human_user.setDir(yeet_direction)
+ human_user.vomit(distance = yeet_distance)
+ soulbox.throw_at(get_edge_target_turf(get_turf(user), yeet_direction), yeet_distance, 2, user, spin = TRUE)
+ soulbox.AddElement(/datum/element/haunted, haunt_color = "#124CD5")
+ if(prob(86)) // 1 in 7 chance to stay
+ addtimer(CALLBACK(src, PROC_REF(soul_gone), soulbox), 1 SECONDS * iteration)
+
+/obj/item/fish/soul/proc/soul_gone(obj/soulbox)
+ soulbox.visible_message("[soulbox] disappears, as if it was never there to begin with...")
+ new /obj/effect/temp_visual/mook_dust(get_turf(soulbox))
+ qdel(soulbox)
+
///From the cursed spring
/obj/item/fish/skin_crab
name = "skin crab"
@@ -101,3 +155,30 @@
/obj/item/fish/skin_crab/get_fish_taste_cooked()
return list("cooked crab" = 2)
+
+/obj/item/fish/skin_crab/suicide_act(mob/living/carbon/human/user)
+ user.visible_message(span_suicide("[user] puts [user.p_their()] hand on [src] and focuses intently! It looks like [user.p_theyre()] trying to transfer [user.p_their()] skin to [src]!"))
+ if(!ishuman(user) || HAS_TRAIT(user, TRAIT_UNHUSKABLE))
+ user.visible_message(span_suicide("[user] has no skin! How embarrassing!"))
+ return SHAME
+
+ if(status == FISH_DEAD)
+ user.visible_message(span_suicide("[src] is dead! [user] just looks like a doofus!"))
+ return SHAME
+
+ var/skin_tone
+ for(var/obj/item/bodypart/to_wound as anything in user.bodyparts)
+ if(to_wound == user.get_bodypart(BODY_ZONE_CHEST))
+ skin_tone = to_wound.species_color || skintone2hex(to_wound.skin_tone)
+ user.cause_wound_of_type_and_severity(WOUND_SLASH, to_wound, WOUND_SEVERITY_CRITICAL, WOUND_SEVERITY_CRITICAL)
+ user.cause_wound_of_type_and_severity(WOUND_PIERCE, to_wound, WOUND_SEVERITY_CRITICAL, WOUND_SEVERITY_CRITICAL)
+ user.cause_wound_of_type_and_severity(WOUND_BLUNT, to_wound, WOUND_SEVERITY_CRITICAL, WOUND_SEVERITY_CRITICAL)
+ user.become_husk(REF(src))
+ to_wound.skin_tone = COLOR_RED // skin is gone. (if they somehow get revived, don't worry - death from loss of skin takes longer than dehydration, so it's still realistic)
+
+ // skin crab grows powerful
+ color = skin_tone //skintone2hex(skin_tone) //wait til smartkar's recolorwork
+ visible_message(span_danger("[user] starts glowing eerily..."))
+ AddElement(/datum/element/haunted, haunt_color = skin_tone)
+
+ return BRUTELOSS
diff --git a/code/modules/fishing/fish/types/saltwater.dm b/code/modules/fishing/fish/types/saltwater.dm
index a28ad497f5319..2b1cc4afdf29b 100644
--- a/code/modules/fishing/fish/types/saltwater.dm
+++ b/code/modules/fishing/fish/types/saltwater.dm
@@ -31,6 +31,14 @@
fishing_difficulty_modifier = 5
beauty = FISH_BEAUTY_GREAT
+// become lubeman. but you suicide
+/obj/item/fish/clownfish/lube/suicide_act(mob/living/user)
+ user.visible_message(span_suicide("[user] covers themselves in [src]'s residue, then swallows it whole! It looks like [user.p_theyre()] trying to commit lubide!"))
+ user.AddComponent(/datum/component/slippery, 8 SECONDS, SLIDE|GALOSHES_DONT_HELP)
+ user.AddElement(/datum/element/lube_walking)
+ qdel(src)
+ return OXYLOSS
+
/obj/item/fish/cardinal
name = "cardinalfish"
fish_id = "cardinal"
@@ -95,6 +103,10 @@
fish_traits = list(/datum/fish_trait/heavy, /datum/fish_trait/toxic)
beauty = FISH_BEAUTY_GOOD
+/obj/item/fish/pufferfish/suicide_act(mob/living/user)
+ user.visible_message(span_suicide("[user] bites into [src] and starts sucking on it! It looks like [user.p_theyre()] trying to commit suicide!"))
+ return TOXLOSS
+
/obj/item/fish/lanternfish
name = "lanternfish"
fish_id = "lanternfish"
@@ -240,6 +252,31 @@
required_temperature_max = MIN_AQUARIUM_TEMP+26
fish_traits = list(/datum/fish_trait/heavy, /datum/fish_trait/carnivore, /datum/fish_trait/predator, /datum/fish_trait/ink, /datum/fish_trait/camouflage, /datum/fish_trait/wary)
+/obj/item/fish/squid/suicide_act(mob/living/user)
+ user.visible_message(span_suicide("[user] points [src]'s ink glands at their face and presses INCREDIBLY hard! It looks like [user.p_theyre()] trying to commit squidcide!"))
+
+ // No head? Bozo.
+ var/obj/item/bodypart/head = user.get_bodypart(BODY_ZONE_HEAD)
+ if(isnull(head))
+ user.visible_message(span_suicide("[user] has no head! The ink goes flying by!"))
+ return SHAME
+
+ // get inked.
+ user.visible_message(span_warning("[user] is inked by [src]!"), span_userdanger("You've been inked by [src]!"))
+ user.AddComponent(/datum/component/face_decal/splat, \
+ color = COLOR_NEARLY_ALL_BLACK, \
+ memory_type = /datum/memory/witnessed_inking, \
+ mood_event_type = /datum/mood_event/inked, \
+ )
+ playsound(user, SFX_DESECRATION, 50, TRUE)
+
+ if(!HAS_TRAIT(user, TRAIT_STRENGTH) && !HAS_TRAIT(user, TRAIT_HULK))
+ return OXYLOSS
+
+ head.dismember(silent = FALSE)
+ user.visible_message(span_suicide("[user]'s head goes FLYING OFF from the overpressurized ink jet!"))
+ return MANUAL_SUICIDE
+
/obj/item/fish/squid/get_fish_taste()
return list("raw mollusk" = 2)
diff --git a/code/modules/fishing/fish/types/station.dm b/code/modules/fishing/fish/types/station.dm
index 353201a1afc78..ca7306038a60f 100644
--- a/code/modules/fishing/fish/types/station.dm
+++ b/code/modules/fishing/fish/types/station.dm
@@ -151,6 +151,12 @@
add_traits(list(TRAIT_FISHING_BAIT, TRAIT_GREAT_QUALITY_BAIT), INNATE_TRAIT)
ADD_TRAIT(src, TRAIT_FISH_SURVIVE_COOKING, INNATE_TRAIT)
+/obj/item/fish/fryish/suicide_act(mob/living/user)
+ user.visible_message(span_suicide("[user] swallows [src] whole! It looks like [user.p_theyre()] trying to commit suicide!"))
+ user.say("Mmmm! Delicious!", forced = "fryfish suicide")
+ qdel(src)
+ return OXYLOSS
+
/obj/item/fish/fryish/update_size_and_weight(new_size = average_size, new_weight = average_weight, update_materials = TRUE)
. = ..()
if(!next_type)
diff --git a/code/modules/fishing/fish/types/syndicate.dm b/code/modules/fishing/fish/types/syndicate.dm
index b36efd4a0c2f2..a1c32830f44ed 100644
--- a/code/modules/fishing/fish/types/syndicate.dm
+++ b/code/modules/fishing/fish/types/syndicate.dm
@@ -32,6 +32,18 @@
required_temperature_max = MIN_AQUARIUM_TEMP+28
beauty = FISH_BEAUTY_EXCELLENT
+/obj/item/fish/donkfish/suicide_act(mob/living/user)
+ user.visible_message(span_suicide("[user] swallows [src] whole! It looks like [user.p_theyre()] trying to commit suicide!"))
+ if(!ishuman(user))
+ return TOXLOSS
+
+ var/mob/living/carbon/human/human_user = user
+ for(var/i in 1 to rand(5, 15))
+ human_user.dir = pick(GLOB.alldirs)
+ human_user.vomit(vomit_flags = pick(VOMIT_CATEGORY_DEFAULT, VOMIT_CATEGORY_BLOOD), distance = rand(1, 7))
+ qdel(src)
+ return TOXLOSS
+
/obj/item/fish/jumpercable
name = "monocloning jumpercable"
fish_id = "jumpercable"
@@ -59,6 +71,13 @@
//without this, they'd sell for over 6000 each, minimum. That's a lot for a fish that requires no maintance nor partner to farm.
return ..() * 0.4
+/obj/item/fish/jumpercable/suicide_act(mob/living/user)
+ user.visible_message(span_suicide("[user] hooks both ends of [src] to their chest! It looks like [user.p_theyre()] trying to commit suicide!"))
+ electrocute_mob(user, power_source = get_area(src), source = src, siemens_coeff = 1, dist_check = FALSE)
+ tesla_zap(source = user, zap_range = 4, power = electrogenesis_power, cutoff = 1e3, zap_flags = ZAP_LOW_POWER_GEN|ZAP_MOB_DAMAGE)
+ playsound(user, 'sound/items/weapons/zapbang.ogg', 75)
+ return OXYLOSS
+
/obj/item/fish/chainsawfish
name = "chainsawfish"
fish_id = "chainsawfish"
@@ -181,6 +200,20 @@
block_chance += bonus_malus * 2
toolspeed -= bonus_malus * 0.1
+// you suicide like a real chainsaw
+/obj/item/fish/chainsawfish/suicide_act(mob/living/carbon/user)
+ if(status == FISH_DEAD)
+ user.visible_message(span_suicide("[user] smashes [src] into [user.p_their()] neck, destroying [user.p_their()] esophagus! It looks like [user.p_theyre()] trying to commit suicide!"))
+ playsound(src, 'sound/items/weapons/genhit1.ogg', 100, TRUE)
+ return BRUTELOSS
+
+ user.visible_message(span_suicide("[user] begins to tear [user.p_their()] head off with [src]! It looks like [user.p_theyre()] trying to commit suicide!"))
+ playsound(src, 'sound/items/weapons/chainsawhit.ogg', 100, TRUE)
+ var/obj/item/bodypart/head/myhead = user.get_bodypart(BODY_ZONE_HEAD)
+ if(myhead)
+ myhead.dismember()
+ return BRUTELOSS
+
/obj/item/fish/pike/armored
name = "armored pike"
fish_id = "armored_pike"
diff --git a/code/modules/fishing/fishing_rod.dm b/code/modules/fishing/fishing_rod.dm
index a4b1e5924f8f3..9fc26ee10ae15 100644
--- a/code/modules/fishing/fishing_rod.dm
+++ b/code/modules/fishing/fishing_rod.dm
@@ -140,7 +140,7 @@
var/list/block = list()
var/get_percent = HAS_MIND_TRAIT(user, TRAIT_EXAMINE_DEEPER_FISH)
block += span_info("You think you can cast it up to [get_cast_range()] tiles away.")
- block += get_stat_info(get_percent, difficulty_modifier, "Fishing will be", "easier", "harder", "with this fishing rod")
+ block += get_stat_info(get_percent, difficulty_modifier * 0.01, "Fishing will be", "easier", "harder", "with this fishing rod", offset = 0)
block += get_stat_info(get_percent, experience_multiplier, "You will gain experience", "faster", "slower")
block += get_stat_info(get_percent, completion_speed_mult, "You should complete the minigame", "faster", "slower")
block += get_stat_info(get_percent, bait_speed_mult, "Reeling is", "faster", "slower")
@@ -148,6 +148,7 @@
block += get_stat_info(get_percent, bounciness_mult, "This fishing rod is ", "bouncier", "less bouncy", "than a normal one", less_is_better = TRUE)
block += get_stat_info(get_percent, gravity_mult, "The lure will sink", "faster", "slower", span_info = TRUE)
+ list_clear_nulls(block)
. += examine_block(block.Join("\n"))
if(get_percent && (material_flags & MATERIAL_EFFECTS) && length(custom_materials))
@@ -172,15 +173,16 @@
. += examine_block(block.Join("\n"))
///Used in examine_more to reduce all the copypasta when getting more information about the various stats of the fishing rod.
-/obj/item/fishing_rod/proc/get_stat_info(get_percent, value, prefix, easier, harder, suffix = "with this fishing rod", span_info = FALSE, less_is_better = FALSE)
+/obj/item/fishing_rod/proc/get_stat_info(get_percent, value, prefix, easier, harder, suffix = "with this fishing rod", span_info = FALSE, less_is_better = FALSE, offset = 1)
if(value == 1)
return
- var/percent = get_percent ? "[abs(value)]% " : ""
- var/harder_easier = value > 1 ? easier : harder
+ value -= offset
+ var/percent = get_percent ? "[abs(value * 100)]% " : ""
+ var/harder_easier = value > 0 ? easier : harder
. = "[prefix] [percent][harder_easier] [suffix]."
if(span_info)
return span_info(.)
- if(less_is_better ? value < 1 : value > 1)
+ if(less_is_better ? value < 0 : value > 0)
return span_nicegreen(.)
return span_danger(.)
diff --git a/code/modules/fishing/sources/source_types.dm b/code/modules/fishing/sources/source_types.dm
index 014474f9c8f9e..6f2a38d4d6146 100644
--- a/code/modules/fishing/sources/source_types.dm
+++ b/code/modules/fishing/sources/source_types.dm
@@ -58,10 +58,10 @@
FISHING_DUD = 4,
/obj/item/fish/goldfish = 5,
/obj/item/fish/guppy = 5,
+ /obj/item/fish/plasmatetra = 4,
/obj/item/fish/perch = 4,
/obj/item/fish/angelfish = 4,
/obj/item/fish/catfish = 4,
- /obj/item/fish/perch = 5,
/obj/item/fish/slimefish = 2,
/obj/item/fish/sockeye_salmon = 1,
/obj/item/fish/arctic_char = 1,
@@ -113,6 +113,7 @@
/obj/item/fish/angelfish = 10,
/obj/item/fish/perch = 5,
/obj/item/fish/goldfish/three_eyes = 3,
+ /obj/item/fish/plasmatetra = 3,
)
catalog_description = "Aquarium dimension (Fishing portal generator)"
radial_state = "fish_tank"
diff --git a/code/modules/food_and_drinks/recipes/food_mixtures.dm b/code/modules/food_and_drinks/recipes/food_mixtures.dm
index be04767b55ded..e8456d880ed87 100644
--- a/code/modules/food_and_drinks/recipes/food_mixtures.dm
+++ b/code/modules/food_and_drinks/recipes/food_mixtures.dm
@@ -5,7 +5,7 @@
/datum/crafting_recipe/food/on_craft_completion(mob/user, atom/result)
SHOULD_CALL_PARENT(TRUE)
. = ..()
- if(istype(result) && !isnull(user.mind))
+ if(istype(result) && istype(user) && !isnull(user.mind))
ADD_TRAIT(result, TRAIT_FOOD_CHEF_MADE, REF(user.mind))
/datum/crafting_recipe/food/New()
diff --git a/code/modules/food_and_drinks/restaurant/_venue.dm b/code/modules/food_and_drinks/restaurant/_venue.dm
index b37128a008765..7425b7b355573 100644
--- a/code/modules/food_and_drinks/restaurant/_venue.dm
+++ b/code/modules/food_and_drinks/restaurant/_venue.dm
@@ -271,10 +271,10 @@
/obj/structure/holosign/robot_seat/attack_holosign(mob/living/user, list/modifiers)
return
-/obj/structure/holosign/robot_seat/attacked_by(obj/item/I, mob/living/user)
- . = ..()
- if(I.type == projector?.type && !linked_venue.linked_seats[src])
+/obj/structure/holosign/robot_seat/item_interaction(mob/living/user, obj/item/tool, list/modifiers)
+ if(tool.type == projector?.type && !linked_venue.linked_seats[src])
qdel(src)
+ return ITEM_INTERACT_SUCCESS
/obj/structure/holosign/robot_seat/Destroy()
linked_venue.linked_seats -= src
diff --git a/code/modules/hallucination/delusions.dm b/code/modules/hallucination/delusions.dm
index 0760d05ff46c6..da12f117803d1 100644
--- a/code/modules/hallucination/delusions.dm
+++ b/code/modules/hallucination/delusions.dm
@@ -230,6 +230,22 @@
return ..()
+/datum/hallucination/delusion/preset/seccies
+ dynamic_delusion = TRUE
+ random_hallucination_weight = 0
+ delusion_name = "Security"
+ affects_others = TRUE
+ affects_us = FALSE
+
+/datum/hallucination/delusion/preset/seccies/make_delusion_image(mob/over_who)
+ delusion_appearance = get_dynamic_human_appearance(
+ outfit_path = /datum/outfit/job/security,
+ bloody_slots = prob(5) ? ALL : NONE,
+ r_hand = prob(15) ? /obj/item/melee/baton/security/loaded : null,
+ l_hand = prob(15) ? /obj/item/melee/baton/security/loaded : null,
+ )
+ return ..()
+
/// Hallucination used by the nightmare vision goggles to turn everyone except you into mares
/datum/hallucination/delusion/preset/mare
delusion_icon_file = 'icons/obj/clothing/masks.dmi'
diff --git a/code/modules/holodeck/computer.dm b/code/modules/holodeck/computer.dm
index 1107c8c25793a..6d9c380112b7c 100644
--- a/code/modules/holodeck/computer.dm
+++ b/code/modules/holodeck/computer.dm
@@ -341,6 +341,10 @@ GLOBAL_LIST_INIT(typecache_holodeck_linked_floorcheck_ok, typecacheof(list(/turf
var/obj/item/clothing/under/holo_clothing = holo_atom
holo_clothing.dump_attachments()
+ if(istype(holo_atom, /obj/item/organ))
+ var/obj/item/organ/holo_organ = holo_atom
+ if(holo_organ.owner) // a mob has the holo organ inside them... oh dear
+ to_chat(holo_organ.owner, span_warning("\The [holo_organ] inside of you fades away!"))
if(!silent)
visible_message(span_notice("[holo_atom] fades away!"))
diff --git a/code/modules/hydroponics/grown/gatfruit.dm b/code/modules/hydroponics/grown/gatfruit.dm
index c6de17d6eb684..8772b06c57195 100644
--- a/code/modules/hydroponics/grown/gatfruit.dm
+++ b/code/modules/hydroponics/grown/gatfruit.dm
@@ -2,7 +2,7 @@
// Gatfruit
/obj/item/seeds/gatfruit
name = "gatfruit seed pack"
- desc = "These seeds grow into .357 revolvers."
+ desc = "These seeds grow into pea-shooting revolvers."
icon_state = "seed-gatfruit"
species = "gatfruit"
plantname = "Gatfruit Tree"
@@ -17,15 +17,31 @@
growthstages = 2
rarity = 60 // Obtainable only with xenobio+superluck.
growing_icon = 'icons/obj/service/hydroponics/growing_fruits.dmi'
- reagents_add = list(/datum/reagent/sulfur = 0.1, /datum/reagent/carbon = 0.1, /datum/reagent/nitrogen = 0.07, /datum/reagent/potassium = 0.05)
+ reagents_add = list(/datum/reagent/sulfur = 0.1, /datum/reagent/carbon = 0.1, /datum/reagent/nitrogen = 0.07, /datum/reagent/potassium = 0.05, /datum/reagent/toxin/gatfruit = 0.3)
/obj/item/food/grown/shell/gatfruit
seed = /obj/item/seeds/gatfruit
name = "gatfruit"
- desc = "It smells like burning."
+ desc = "It smells like burning gunpowder."
icon_state = "gatfruit"
- trash_type = /obj/item/gun/ballistic/revolver
+ trash_type = /obj/item/gun/ballistic/revolver/peashooter
bite_consumption_mod = 2
foodtypes = FRUIT
tastes = list("gunpowder" = 1)
wine_power = 90 //It burns going down, too.
+
+/obj/item/food/grown/shell/gatfruit/Initialize(mapload, obj/item/seeds/new_seed)
+ . = ..()
+ reagents.flags &= ~INJECTABLE //id rather not have this be filled with initropidril without effort
+
+/obj/item/food/grown/shell/gatfruit/generate_trash(atom/location)
+ //if you set this to anything but the revolver i will find you... and... downvote your pr...
+ var/obj/item/gun/ballistic/revolver/peashooter/gun = new trash_type(location || drop_location())
+ var/potency_percentage = CLAMP01(seed.potency / 100)
+ var/amount_to_trans = reagents.total_volume / gun.magazine.max_ammo
+ for(var/obj/item/ammo_casing/pea/casing as anything in gun.magazine.ammo_list())
+ casing.damage = floor(max(5, LERP(5, casing.max_damage, potency_percentage)))
+ if(reagents.total_volume)
+ reagents.trans_to(casing, amount_to_trans)
+ return gun
+
diff --git a/code/modules/jobs/access.dm b/code/modules/jobs/access.dm
index b31574bec33e3..e9b2e832f92b5 100644
--- a/code/modules/jobs/access.dm
+++ b/code/modules/jobs/access.dm
@@ -9,11 +9,6 @@
return TRUE
if(result_bitflags & COMPONENT_OBJ_DISALLOW) // override all other checks
return FALSE
- if(!isnull(accessor) && HAS_TRAIT(accessor, TRAIT_ALWAYS_NO_ACCESS))
- return FALSE
- //check if it doesn't require any access at all
- if(check_access(null))
- return TRUE
if(isnull(accessor)) //likely a TK user.
return FALSE
if(isAdminGhostAI(accessor))
@@ -25,6 +20,9 @@
return TRUE
if(attempted_access & ACCESS_DISALLOWED)
return FALSE
+ //check if it doesn't require any access at all
+ if(check_access(null))
+ return TRUE
if(HAS_SILICON_ACCESS(accessor))
if(ispAI(accessor))
return FALSE
diff --git a/code/modules/language/_language.dm b/code/modules/language/_language.dm
index 595b591a0dd90..1177af8346770 100644
--- a/code/modules/language/_language.dm
+++ b/code/modules/language/_language.dm
@@ -120,7 +120,7 @@
// Add it to cache, cutting old entries if the list is too long
scramble_cache[input] = scrambled_text
if(scramble_cache.len > SCRAMBLE_CACHE_LEN)
- scramble_cache.Cut(1, scramble_cache.len-SCRAMBLE_CACHE_LEN-1)
+ scramble_cache.Cut(1, 2)
/datum/language/proc/scramble(input)
diff --git a/code/modules/lost_crew/recovered_crew.dm b/code/modules/lost_crew/recovered_crew.dm
index 65c6a3715fb67..2d5181a0d4e41 100644
--- a/code/modules/lost_crew/recovered_crew.dm
+++ b/code/modules/lost_crew/recovered_crew.dm
@@ -7,3 +7,4 @@
show_to_ghosts = FALSE
silent = TRUE
block_midrounds = FALSE
+ show_in_roundend = FALSE
diff --git a/code/modules/manufactorio/_manufacturing.dm b/code/modules/manufactorio/_manufacturing.dm
index 02cc47999d50c..4b64d541fd2e7 100644
--- a/code/modules/manufactorio/_manufacturing.dm
+++ b/code/modules/manufactorio/_manufacturing.dm
@@ -1,10 +1,6 @@
-#define MANUFACTURING_FAIL_FULL -1
#define MANUFACTURING_FAIL 0
#define MANUFACTURING_SUCCESS 1
-#define POCKET_INPUT "Input"
-#define POCKET_OUTPUT "Output"
-
#define MANUFACTURING_TURF_LAG_LIMIT 10 // max items on a turf before we consider it full
/obj/machinery/power/manufacturing
@@ -108,8 +104,8 @@
return manufactury.receive_resource(sending, src, isturf(what_or_dir) ? get_dir(src, what_or_dir) : what_or_dir)
if(next_turf.is_blocked_turf(exclude_mobs = TRUE, source_atom = sending) && !ischasm(next_turf))
return MANUFACTURING_FAIL
- if(length(next_turf.contents) >= MANUFACTURING_TURF_LAG_LIMIT)
- return MANUFACTURING_FAIL_FULL
+ if(length(get_overfloor_objects(next_turf)) >= MANUFACTURING_TURF_LAG_LIMIT)
+ return MANUFACTURING_FAIL
if(isnull(sending))
return MANUFACTURING_SUCCESS // for the sake of being used as a check
if(isnull(sending.loc) || !sending.Move(next_turf, get_dir(src, next_turf)))
@@ -132,3 +128,11 @@
return
return stack.merge(merging_into)
+/obj/machinery/power/manufacturing/proc/get_overfloor_objects(turf/target)
+ . = list()
+ if(isnull(target))
+ target = get_turf(src)
+ for(var/atom/movable/thing as anything in target.contents)
+ if(thing == src || isliving(thing) || iseffect(thing) || thing.invisibility >= INVISIBILITY_ABSTRACT || HAS_TRAIT(thing, TRAIT_UNDERFLOOR))
+ continue
+ . += thing
diff --git a/code/modules/manufactorio/machines/crafter.dm b/code/modules/manufactorio/machines/crafter.dm
index d164976b81a48..6b1c9160d7679 100644
--- a/code/modules/manufactorio/machines/crafter.dm
+++ b/code/modules/manufactorio/machines/crafter.dm
@@ -6,8 +6,8 @@
circuit = /obj/item/circuitboard/machine/manucrafter
/// power used per process() spent crafting
var/power_cost = 5 KILO WATTS
- /// our output, if the way out was blocked is held here
- var/atom/movable/withheld
+ /// list of weakrefs to crafted items still on the machine that we failed to send forward
+ var/list/datum/weakref/withheld = list()
/// current recipe
var/datum/crafting_recipe/recipe
/// crafting component
@@ -45,7 +45,7 @@
/obj/machinery/power/manufacturing/crafter/receive_resource(obj/receiving, atom/from, receive_dir)
var/turf/machine_turf = get_turf(src)
if(length(machine_turf.contents) >= MANUFACTURING_TURF_LAG_LIMIT)
- return MANUFACTURING_FAIL_FULL
+ return MANUFACTURING_FAIL
receiving.forceMove(machine_turf)
return MANUFACTURING_SUCCESS
@@ -53,10 +53,8 @@
. = NONE
var/list/unavailable = list()
for(var/datum/crafting_recipe/potential_recipe as anything in cooking ? GLOB.cooking_recipes : GLOB.crafting_recipes)
- if(craftsman.is_recipe_available(potential_recipe, user))
- continue
- var/obj/result = initial(potential_recipe.result)
- if(istype(result) && initial(result.anchored))
+ var/obj/as_obj = potential_recipe.result
+ if(!(ispath(as_obj, /obj) && !ispath(as_obj, /obj/effect) && initial(as_obj.anchored)) && craftsman.is_recipe_available(potential_recipe, user))
continue
unavailable += potential_recipe
var/result = tgui_input_list(usr, "Recipe", "Select Recipe", (cooking ? GLOB.cooking_recipes : GLOB.crafting_recipes) - unavailable)
@@ -66,24 +64,14 @@
balloon_alert(user, "set")
return ITEM_INTERACT_SUCCESS
-/obj/machinery/power/manufacturing/crafter/Exited(atom/movable/gone, direction)
- . = ..()
- if(gone == withheld)
- withheld = null
-
-/obj/machinery/power/manufacturing/crafter/atom_destruction(damage_flag)
- . = ..()
- withheld?.Move(drop_location(src))
-
/obj/machinery/power/manufacturing/crafter/Destroy()
. = ..()
recipe = null
craftsman = null
- QDEL_NULL(withheld)
+ withheld.Cut()
/obj/machinery/power/manufacturing/crafter/process(seconds_per_tick)
- if(!isnull(withheld) && !send_resource(withheld, dir))
- return
+ send_withheld() // try send any pending stuff
if(!isnull(craft_timer))
if(surplus() >= power_cost)
add_load()
@@ -97,19 +85,37 @@
flick_overlay_view(mutable_appearance(icon, "crafter_printing"), recipe.time)
craft_timer = addtimer(CALLBACK(src, PROC_REF(craft), recipe), recipe.time, TIMER_STOPPABLE)
+/obj/machinery/power/manufacturing/crafter/proc/send_withheld()
+ if(!length(withheld))
+ return FALSE
+ for(var/datum/weakref/weakref as anything in withheld)
+ var/atom/movable/resolved = weakref?.resolve()
+ if(isnull(resolved))
+ withheld -= weakref
+ continue
+ if(resolved.loc != loc || send_resource(resolved, dir))
+ withheld -= weakref
+ return length(withheld)
+
/obj/machinery/power/manufacturing/crafter/proc/craft(datum/crafting_recipe/recipe)
if(QDELETED(src))
return
craft_timer = null
- var/atom/movable/result = craftsman.construct_item(src, recipe)
- if(istype(result))
- if(isitem(result))
- result.pixel_x += rand(-4, 4)
- result.pixel_y += rand(-4, 4)
- result.Move(src)
- send_resource(result, dir)
- else
- say(result)
+ var/list/prediff = get_overfloor_objects()
+ var/result = craftsman.construct_item(src, recipe)
+ if(istext(result))
+ say("Crafting failed,[result]")
+ return
+ var/list/diff = get_overfloor_objects() - prediff
+ for(var/atom/movable/diff_result as anything in diff)
+ if(iseffect(diff_result) || ismob(diff_result)) // PLEASE dont stuff cats (or other mobs) into the cat grinder 9000
+ continue
+ if(isitem(diff_result))
+ diff_result.pixel_x += rand(-4, 4)
+ diff_result.pixel_y += rand(-4, 4)
+ withheld += WEAKREF(diff_result)
+ recipe.on_craft_completion(src, diff_result)
+ send_withheld()
/obj/machinery/power/manufacturing/crafter/cooker
name = "manufacturing cooking machine" // maybe this shouldnt be available dont wanna make chef useless, though otherwise it would need a sprite
diff --git a/code/modules/manufactorio/machines/crusher.dm b/code/modules/manufactorio/machines/crusher.dm
index b8cb50bb0bb79..ee5a61a784f14 100644
--- a/code/modules/manufactorio/machines/crusher.dm
+++ b/code/modules/manufactorio/machines/crusher.dm
@@ -29,7 +29,7 @@
if(istype(receiving, /obj/item/stack/ore) || receiving.resistance_flags & INDESTRUCTIBLE || !isitem(receiving) || surplus() < crush_cost || receive_dir != REVERSE_DIR(dir))
return MANUFACTURING_FAIL
if(length(contents - circuit) >= capacity && may_merge_in_contents_and_do_so(receiving))
- return MANUFACTURING_FAIL_FULL
+ return MANUFACTURING_FAIL
receiving.Move(src, get_dir(receiving, src))
START_PROCESSING(SSmanufacturing, src)
return MANUFACTURING_SUCCESS
diff --git a/code/modules/manufactorio/machines/router.dm b/code/modules/manufactorio/machines/router.dm
index 8e1c20214339e..fa6950ea0c714 100644
--- a/code/modules/manufactorio/machines/router.dm
+++ b/code/modules/manufactorio/machines/router.dm
@@ -52,7 +52,7 @@
dir = receive_dir
update_appearance(UPDATE_OVERLAYS) // im sorry
return MANUFACTURING_SUCCESS
- return MANUFACTURING_FAIL_FULL
+ return MANUFACTURING_FAIL
/obj/machinery/power/manufacturing/router/proc/handle_stack(obj/item/stack/stack, direction)
if(stack.amount <= 1) // last implementation was just not good so lets cheap out
diff --git a/code/modules/manufactorio/machines/smelter.dm b/code/modules/manufactorio/machines/smelter.dm
index 597c9a7b43a50..84fe54d6e4ff4 100644
--- a/code/modules/manufactorio/machines/smelter.dm
+++ b/code/modules/manufactorio/machines/smelter.dm
@@ -18,7 +18,7 @@
return MANUFACTURING_FAIL
var/list/stacks = contents - circuit
if(length(stacks) >= 5 && !may_merge_in_contents_and_do_so(receiving))
- return MANUFACTURING_FAIL_FULL
+ return MANUFACTURING_FAIL
receiving.Move(src, get_dir(receiving, src))
START_PROCESSING(SSmanufacturing, src)
return MANUFACTURING_SUCCESS
diff --git a/code/modules/manufactorio/machines/sorter.dm b/code/modules/manufactorio/machines/sorter.dm
index b1e45e708eb0f..344c90e8ebd70 100644
--- a/code/modules/manufactorio/machines/sorter.dm
+++ b/code/modules/manufactorio/machines/sorter.dm
@@ -42,7 +42,7 @@
/obj/machinery/power/manufacturing/sorter/receive_resource(atom/movable/receiving, atom/from, receive_dir)
if(length(loc.contents) >= MANUFACTURING_TURF_LAG_LIMIT)
- return MANUFACTURING_FAIL_FULL
+ return MANUFACTURING_FAIL
receiving.Move(loc)
return MANUFACTURING_SUCCESS
diff --git a/code/modules/manufactorio/machines/storagebox.dm b/code/modules/manufactorio/machines/storagebox.dm
index b8a6f5cccac39..408493d4a890e 100644
--- a/code/modules/manufactorio/machines/storagebox.dm
+++ b/code/modules/manufactorio/machines/storagebox.dm
@@ -15,7 +15,7 @@
if(iscloset(receiving) && length(receiving.contents))
return MANUFACTURING_FAIL
if(length(contents - circuit) >= max_stuff && !may_merge_in_contents_and_do_so(receiving))
- return MANUFACTURING_FAIL_FULL
+ return MANUFACTURING_FAIL
receiving.Move(src,receive_dir)
return MANUFACTURING_SUCCESS
diff --git a/code/modules/manufactorio/machines/unloader.dm b/code/modules/manufactorio/machines/unloader.dm
index 982c33582684e..5220214168137 100644
--- a/code/modules/manufactorio/machines/unloader.dm
+++ b/code/modules/manufactorio/machines/unloader.dm
@@ -32,7 +32,7 @@
return MANUFACTURING_FAIL
var/list/real_contents = contents - circuit
if(length(real_contents))
- return MANUFACTURING_FAIL_FULL
+ return MANUFACTURING_FAIL
var/obj/structure/closet/as_closet = receiving
var/obj/structure/ore_box/as_orebox = receiving
diff --git a/code/modules/mapfluff/ruins/objects_and_mobs/necropolis_gate.dm b/code/modules/mapfluff/ruins/objects_and_mobs/necropolis_gate.dm
index af81563681cd0..733cd8330bc1f 100644
--- a/code/modules/mapfluff/ruins/objects_and_mobs/necropolis_gate.dm
+++ b/code/modules/mapfluff/ruins/objects_and_mobs/necropolis_gate.dm
@@ -16,6 +16,7 @@
light_range = 8
light_color = LIGHT_COLOR_LAVA
can_atmos_pass = ATMOS_PASS_DENSITY
+ move_resist = INFINITY
var/open = FALSE
var/changing_openness = FALSE
var/locked = FALSE
diff --git a/code/modules/mapfluff/ruins/spaceruin_code/anomalyresearch.dm b/code/modules/mapfluff/ruins/spaceruin_code/anomalyresearch.dm
index f290c06d78f10..ce57d47c6e9b7 100644
--- a/code/modules/mapfluff/ruins/spaceruin_code/anomalyresearch.dm
+++ b/code/modules/mapfluff/ruins/spaceruin_code/anomalyresearch.dm
@@ -68,7 +68,7 @@
icon_state = "anomaly_research"
requires_power = FALSE
area_flags = HIDDEN_AREA | UNIQUE_AREA
- has_gravity = TRUE
+ default_gravity = ZERO_GRAVITY
/obj/item/reagent_containers/cup/bottle/wittel
name = "wittel bottle"
diff --git a/code/modules/mapfluff/ruins/spaceruin_code/commsbuoy.dm b/code/modules/mapfluff/ruins/spaceruin_code/commsbuoy.dm
index 895200d487a1b..9ddf111958cdf 100644
--- a/code/modules/mapfluff/ruins/spaceruin_code/commsbuoy.dm
+++ b/code/modules/mapfluff/ruins/spaceruin_code/commsbuoy.dm
@@ -38,7 +38,7 @@
/area/ruin/space/nt_commsbuoy
name = "\improper Nanotrasen Comms Buoy"
sound_environment = SOUND_AREA_SMALL_ENCLOSED
- has_gravity = FALSE
+ default_gravity = ZERO_GRAVITY
ambientsounds = list(
'sound/ambience/engineering/ambisin2.ogg',
'sound/ambience/misc/signal.ogg',
diff --git a/code/modules/mapfluff/ruins/spaceruin_code/hilbertshotel.dm b/code/modules/mapfluff/ruins/spaceruin_code/hilbertshotel.dm
index 7e6a1bf6d6371..807bd5e816632 100644
--- a/code/modules/mapfluff/ruins/spaceruin_code/hilbertshotel.dm
+++ b/code/modules/mapfluff/ruins/spaceruin_code/hilbertshotel.dm
@@ -392,7 +392,7 @@ GLOBAL_VAR_INIT(hhMysteryRoomNumber, rand(1, 999999))
icon = 'icons/area/areas_ruins.dmi'
icon_state = "hilbertshotel"
requires_power = FALSE
- has_gravity = TRUE
+ default_gravity = STANDARD_GRAVITY
area_flags = NOTELEPORT | HIDDEN_AREA
static_lighting = TRUE
ambientsounds = list('sound/ambience/ruin/servicebell.ogg')
@@ -483,7 +483,7 @@ GLOBAL_VAR_INIT(hhMysteryRoomNumber, rand(1, 999999))
icon_state = "hilbertshotel"
requires_power = FALSE
area_flags = HIDDEN_AREA | NOTELEPORT | UNIQUE_AREA
- has_gravity = TRUE
+ default_gravity = STANDARD_GRAVITY
/obj/item/abstracthotelstorage
anchored = TRUE
diff --git a/code/modules/mapfluff/ruins/spaceruin_code/meatderelict.dm b/code/modules/mapfluff/ruins/spaceruin_code/meatderelict.dm
index 2b2216641488d..440f7ad05786e 100644
--- a/code/modules/mapfluff/ruins/spaceruin_code/meatderelict.dm
+++ b/code/modules/mapfluff/ruins/spaceruin_code/meatderelict.dm
@@ -125,6 +125,7 @@
/obj/lightning_thrower/Destroy()
. = ..()
+ clear_signals()
signal_turfs = null
STOP_PROCESSING(SSprocessing, src)
@@ -132,6 +133,8 @@
var/list/dirs = throw_diagonals ? GLOB.diagonals : GLOB.cardinals
throw_diagonals = !throw_diagonals
playsound(src, 'sound/effects/magic/lightningbolt.ogg', 25, TRUE, SHORT_RANGE_SOUND_EXTRARANGE, ignore_walls = FALSE)
+ if(length(signal_turfs))
+ clear_signals()
for(var/direction in dirs)
var/victim_turf = get_step(src, direction)
if(isclosedturf(victim_turf))
@@ -143,8 +146,7 @@
shock_victim(null, victim)
addtimer(CALLBACK(src, PROC_REF(clear_signals)), shock_duration)
-/obj/lightning_thrower/proc/clear_signals(datum/source)
- SIGNAL_HANDLER
+/obj/lightning_thrower/proc/clear_signals()
for(var/turf in signal_turfs)
UnregisterSignal(turf, COMSIG_ATOM_ENTERED)
signal_turfs -= turf
diff --git a/code/modules/mapfluff/ruins/spaceruin_code/meateor.dm b/code/modules/mapfluff/ruins/spaceruin_code/meateor.dm
index d40d9178f3a85..88b9e9f9503f4 100644
--- a/code/modules/mapfluff/ruins/spaceruin_code/meateor.dm
+++ b/code/modules/mapfluff/ruins/spaceruin_code/meateor.dm
@@ -36,7 +36,6 @@
/datum/reagent/medicine/c2/penthrite = 5,
/datum/reagent/consumable/vinegar = 5,
)
- drink_type = NONE
age_restricted = FALSE
/// Abstract holder object for shared behaviour
diff --git a/code/modules/mapping/ruins.dm b/code/modules/mapping/ruins.dm
index d6ac3ac4f9424..c157636b1219a 100644
--- a/code/modules/mapping/ruins.dm
+++ b/code/modules/mapping/ruins.dm
@@ -72,8 +72,9 @@
* @param clear_below Whether to clear the area below the ruin. Used for multiz ruins.
* @param mineral_budget The budget to spend on ruins that spawn ore vents. Map templates with vents have that defined by mineral_cost.
* @param mineral_budget_update What type of ore distribution should spawn from ruins picked by this cave generator? This list is copied from ores_spawned.dm into SSore_generation.ore_vent_minerals.
+ * @param ruin_type The type of ruins that are spawning (ZTRAIT_SPACE_RUINS, ZTRAIT_ICE_RUINS, ZTRAIT_LAVA_RUINS, etc.)
*/
-/proc/seedRuins(list/z_levels = null, budget = 0, whitelist = list(/area/space), list/potentialRuins, clear_below = FALSE, mineral_budget = 15, mineral_budget_update)
+/proc/seedRuins(list/z_levels = null, budget = 0, whitelist = list(/area/space), list/potentialRuins, clear_below = FALSE, mineral_budget = 15, mineral_budget_update, ruins_type = ZTRAIT_STATION)
if(!z_levels || !z_levels.len)
WARNING("No Z levels provided - Not generating ruins")
return
@@ -86,7 +87,7 @@
return
var/list/ruins = potentialRuins.Copy()
-
+ var/placed_ruins = 0 // our count of how many ruins have been placed
var/list/forced_ruins = list() //These go first on the z level associated (same random one by default) or if the assoc value is a turf to the specified turf.
var/list/ruins_available = list() //we can try these in the current pass
@@ -115,7 +116,7 @@
if(R.unpickable)
continue
ruins_available[R] = R.placement_weight
- while((budget > 0 || mineral_budget > 0) && (ruins_available.len || forced_ruins.len))
+ while(((budget > 0 || mineral_budget > 0) && ruins_available.len) || forced_ruins.len)
var/datum/map_template/ruin/current_pick
var/forced = FALSE
var/forced_z //If set we won't pick z level and use this one instead.
@@ -171,8 +172,9 @@
for(var/datum/map_template/ruin/R in ruins_available)
if(R.id == current_pick.id)
ruins_available -= R
- log_world("Failed to place [current_pick.name] ruin.")
+ log_mapping("Failed to place [current_pick.name] ruin!")
else
+ placed_ruins++
budget -= current_pick.cost
mineral_budget -= current_pick.mineral_cost
if(!current_pick.allow_duplicates)
@@ -203,9 +205,12 @@
if(PLACE_ISOLATED)
forced_ruins[linked] = SSmapping.get_isolated_ruin_z()
+
+ log_mapping("Successfully placed [current_pick.name] ruin.")
+
//Update the available list
for(var/datum/map_template/ruin/R in ruins_available)
if(R.cost > budget || R.mineral_cost > mineral_budget)
ruins_available -= R
- log_world("Ruin loader finished with [budget] left to spend.")
+ log_world("[ruins_type] loader finished placing [placed_ruins]/[ruins.len] ruins with [budget] left to spend.")
diff --git a/code/modules/meteors/meteor_types.dm b/code/modules/meteors/meteor_types.dm
index 287a93821aebd..8d3fcc67f53b8 100644
--- a/code/modules/meteors/meteor_types.dm
+++ b/code/modules/meteors/meteor_types.dm
@@ -45,6 +45,9 @@
/obj/effect/meteor/Destroy()
GLOB.meteor_list -= src
+ var/datum/move_loop/moveloop = GLOB.move_manager.processing_on(src, SSmovement)
+ if (!isnull(moveloop))
+ UnregisterSignal(moveloop, COMSIG_MOVELOOP_STOP)
return ..()
/obj/effect/meteor/Moved(atom/old_loc, movement_dir, forced, list/old_locs, momentum_change = TRUE)
diff --git a/code/modules/mining/equipment/monster_organs/regenerative_core.dm b/code/modules/mining/equipment/monster_organs/regenerative_core.dm
index e601ac89f8c59..bcb7bc0455f27 100644
--- a/code/modules/mining/equipment/monster_organs/regenerative_core.dm
+++ b/code/modules/mining/equipment/monster_organs/regenerative_core.dm
@@ -31,7 +31,7 @@
trigger_organ_action(TRIGGER_FORCE_AVAILABLE)
/obj/item/organ/monster_core/regenerative_core/on_triggered_internal()
- owner.revive(HEAL_ALL)
+ owner.revive(HEAL_ALL & ~HEAL_REFRESH_ORGANS)
qdel(src)
/// Log applications and apply moodlet.
diff --git a/code/modules/mining/equipment/survival_pod.dm b/code/modules/mining/equipment/survival_pod.dm
index ce0c2d923a457..a770d7f4bbf0d 100644
--- a/code/modules/mining/equipment/survival_pod.dm
+++ b/code/modules/mining/equipment/survival_pod.dm
@@ -4,7 +4,7 @@
icon_state = "away"
static_lighting = TRUE
requires_power = FALSE
- has_gravity = STANDARD_GRAVITY
+ default_gravity = STANDARD_GRAVITY
area_flags = BLOBS_ALLOWED | UNIQUE_AREA
flags_1 = CAN_BE_DIRTY_1
diff --git a/code/modules/mob/dead/observer/orbit.dm b/code/modules/mob/dead/observer/orbit.dm
index d2823eae2b580..5e77627e00d55 100644
--- a/code/modules/mob/dead/observer/orbit.dm
+++ b/code/modules/mob/dead/observer/orbit.dm
@@ -104,7 +104,7 @@ GLOBAL_DATUM_INIT(orbit_menu, /datum/orbit_menu, new)
if(isliving(mob_poi))
serialized += get_living_data(mob_poi)
- var/list/antag_data = get_antag_data(mob_poi.mind)
+ var/list/antag_data = get_antag_data(mob_poi.mind, user?.client?.holder)
if(length(antag_data))
serialized += antag_data
antagonists += list(serialized)
@@ -151,11 +151,11 @@ GLOBAL_DATUM_INIT(orbit_menu, /datum/orbit_menu, new)
/// Helper function to get threat type, group, overrides for job and icon
-/datum/orbit_menu/proc/get_antag_data(datum/mind/poi_mind) as /list
+/datum/orbit_menu/proc/get_antag_data(datum/mind/poi_mind, is_admin) as /list
var/list/serialized = list()
for(var/datum/antagonist/antag as anything in poi_mind.antag_datums)
- if(!antag.show_to_ghosts)
+ if(!antag.show_to_ghosts && !is_admin)
continue
serialized["antag"] = antag.name
diff --git a/code/modules/mob/living/basic/bots/bot_hud.dm b/code/modules/mob/living/basic/bots/bot_hud.dm
index 50d40dad01d6c..0edcaad6a7e2a 100644
--- a/code/modules/mob/living/basic/bots/bot_hud.dm
+++ b/code/modules/mob/living/basic/bots/bot_hud.dm
@@ -1,13 +1,11 @@
/mob/living/basic/bot/proc/diag_hud_set_bothealth()
var/image/holder = hud_list[DIAG_HUD]
- var/icon/icon_image = icon(icon, icon_state, dir)
- holder.pixel_y = icon_image.Height() - ICON_SIZE_Y
+ holder.pixel_y = get_cached_height() - ICON_SIZE_Y
holder.icon_state = "huddiag[RoundDiagBar(health/maxHealth)]"
/mob/living/basic/bot/proc/diag_hud_set_botstat() //On (With wireless on or off), Off, EMP'ed
var/image/holder = hud_list[DIAG_STAT_HUD]
- var/icon/our_icon = icon(icon, icon_state, dir)
- holder.pixel_y = our_icon.Height() - ICON_SIZE_Y
+ holder.pixel_y = get_cached_height() - ICON_SIZE_Y
if(bot_mode_flags & BOT_MODE_ON)
holder.icon_state = "hudstat"
return
@@ -18,8 +16,7 @@
/mob/living/basic/bot/proc/diag_hud_set_botmode() //Shows a bot's current operation
var/image/holder = hud_list[DIAG_BOT_HUD]
- var/icon/icon_image = icon(icon, icon_state, dir)
- holder.pixel_y = icon_image.Height() - ICON_SIZE_Y
+ holder.pixel_y = get_cached_height() - ICON_SIZE_Y
if(client) //If the bot is player controlled, it will not be following mode logic!
holder.icon_state = "hudsentient"
return
diff --git a/code/modules/mob/living/basic/clown/clown.dm b/code/modules/mob/living/basic/clown/clown.dm
index ebc15182c2f23..a8fb645af73b7 100644
--- a/code/modules/mob/living/basic/clown/clown.dm
+++ b/code/modules/mob/living/basic/clown/clown.dm
@@ -403,7 +403,11 @@
GRANT_ACTION(/datum/action/cooldown/regurgitate)
AddElement(/datum/element/swabable, CELL_LINE_TABLE_GLUTTON, CELL_VIRUS_TABLE_GENERIC_MOB, 1, 5)
- AddComponent(/datum/component/tameable, food_types = list(/obj/item/food/cheesiehonkers, /obj/item/food/cornchips), tame_chance = 30, bonus_tame_chance = 0)
+ var/static/list/food_types = list(
+ /obj/item/food/cheesiehonkers,
+ /obj/item/food/cornchips,
+ )
+ AddComponent(/datum/component/tameable, food_types = food_types, tame_chance = 30, bonus_tame_chance = 0)
AddElement(/datum/element/damage_threshold, 10) //lots of fat to cushion blows.
/mob/living/basic/clown/mutant/glutton/attacked_by(obj/item/item, mob/living/user)
diff --git a/code/modules/mob/living/basic/drone/_drone.dm b/code/modules/mob/living/basic/drone/_drone.dm
index 7412551d5984f..a0be86ecc6a9a 100644
--- a/code/modules/mob/living/basic/drone/_drone.dm
+++ b/code/modules/mob/living/basic/drone/_drone.dm
@@ -220,14 +220,12 @@
/mob/living/basic/drone/med_hud_set_health()
var/image/holder = hud_list[DIAG_HUD]
- var/icon/hud_icon = icon(icon, icon_state, dir)
- holder.pixel_y = hud_icon.Height() - ICON_SIZE_Y
+ holder.pixel_y = get_cached_height() - ICON_SIZE_Y
holder.icon_state = "huddiag[RoundDiagBar(health/maxHealth)]"
/mob/living/basic/drone/med_hud_set_status()
var/image/holder = hud_list[DIAG_STAT_HUD]
- var/icon/hud_icon = icon(icon, icon_state, dir)
- holder.pixel_y = hud_icon.Height() - ICON_SIZE_Y
+ holder.pixel_y = get_cached_height() - ICON_SIZE_Y
if(stat == DEAD)
holder.icon_state = "huddead2"
else if(incapacitated)
diff --git a/code/modules/mob/living/basic/farm_animals/cow/_cow.dm b/code/modules/mob/living/basic/farm_animals/cow/_cow.dm
index c26530b5f32aa..a07a70d0172d4 100644
--- a/code/modules/mob/living/basic/farm_animals/cow/_cow.dm
+++ b/code/modules/mob/living/basic/farm_animals/cow/_cow.dm
@@ -76,7 +76,6 @@
if(!food_types)
food_types = src.food_types.Copy()
AddComponent(/datum/component/tameable, food_types = food_types, tame_chance = 25, bonus_tame_chance = 15)
- AddElement(/datum/element/basic_eating, food_types = food_types)
/mob/living/basic/cow/tamed(mob/living/tamer, atom/food)
visible_message("[src] [tame_message] as it seems to bond with [tamer].", "You [self_tame_message], recognizing [tamer] as your new pal.")
diff --git a/code/modules/mob/living/basic/farm_animals/cow/cow_moonicorn.dm b/code/modules/mob/living/basic/farm_animals/cow/cow_moonicorn.dm
index 47f11a02839e4..d11968128aa67 100644
--- a/code/modules/mob/living/basic/farm_animals/cow/cow_moonicorn.dm
+++ b/code/modules/mob/living/basic/farm_animals/cow/cow_moonicorn.dm
@@ -32,7 +32,6 @@
var/static/list/food_types
if(!food_types)
food_types = src.food_types.Copy()
- AddElement(/datum/element/basic_eating, food_types = food_types)
AddComponent(/datum/component/tameable, food_types = food_types, tame_chance = 25, bonus_tame_chance = 15)
/mob/living/basic/cow/moonicorn/tamed(mob/living/tamer, atom/food)
diff --git a/code/modules/mob/living/basic/farm_animals/pig.dm b/code/modules/mob/living/basic/farm_animals/pig.dm
index 412104617a5b6..270b9b4e859bd 100644
--- a/code/modules/mob/living/basic/farm_animals/pig.dm
+++ b/code/modules/mob/living/basic/farm_animals/pig.dm
@@ -48,7 +48,8 @@
///wrapper for the tameable component addition so you can have non tamable cow subtypes
/mob/living/basic/pig/proc/make_tameable()
- AddComponent(/datum/component/tameable, food_types = list(/obj/item/food/grown/carrot), tame_chance = 25, bonus_tame_chance = 15)
+ var/list/food_types = string_list(list(/obj/item/food/grown/carrot))
+ AddComponent(/datum/component/tameable, food_types = food_types, tame_chance = 25, bonus_tame_chance = 15)
/mob/living/basic/pig/tamed(mob/living/tamer, atom/food)
AddElement(/datum/element/ridable, /datum/component/riding/creature/pig)
diff --git a/code/modules/mob/living/basic/farm_animals/pony.dm b/code/modules/mob/living/basic/farm_animals/pony.dm
index b73dac3945161..d2f897fc823ff 100644
--- a/code/modules/mob/living/basic/farm_animals/pony.dm
+++ b/code/modules/mob/living/basic/farm_animals/pony.dm
@@ -53,7 +53,10 @@
AddElement(/datum/element/ai_retaliate)
AddElement(/datum/element/ai_flee_while_injured)
AddElementTrait(TRAIT_WADDLING, INNATE_TRAIT, /datum/element/waddling)
- AddComponent(/datum/component/tameable, food_types = list(/obj/item/food/grown/apple), tame_chance = 25, bonus_tame_chance = 15, unique = unique_tamer)
+ var/static/list/food_types = list(
+ /obj/item/food/grown/apple,
+ )
+ AddComponent(/datum/component/tameable, food_types = food_types, tame_chance = 25, bonus_tame_chance = 15, unique = unique_tamer)
/mob/living/basic/pony/tamed(mob/living/tamer, atom/food)
playsound(src, 'sound/mobs/non-humanoids/pony/snort.ogg', 50)
@@ -161,4 +164,5 @@
ponycolors = list("#5d566f", pick_weight(mane_colors))
name = pick("Sunday", "Monday", "Tuesday", "Wednesday", "Thursday", "Friday", "Saturday")
// Only one person can tame these fellas, and they only need one apple
- AddComponent(/datum/component/tameable, food_types = list(/obj/item/food/grown/apple), tame_chance = 100, bonus_tame_chance = 15, unique = unique_tamer)
+ var/static/list/food_types = list(/obj/item/food/grown/apple)
+ AddComponent(/datum/component/tameable, food_types = food_types, tame_chance = 100, bonus_tame_chance = 15, unique = unique_tamer)
diff --git a/code/modules/mob/living/basic/icemoon/wolf/wolf.dm b/code/modules/mob/living/basic/icemoon/wolf/wolf.dm
index 3708d754ab4b0..b82092147f67d 100644
--- a/code/modules/mob/living/basic/icemoon/wolf/wolf.dm
+++ b/code/modules/mob/living/basic/icemoon/wolf/wolf.dm
@@ -66,7 +66,8 @@
make_tameable()
/mob/living/basic/mining/wolf/proc/make_tameable()
- AddComponent(/datum/component/tameable, food_types = list(/obj/item/food/meat/slab), tame_chance = 15, bonus_tame_chance = 5)
+ var/static/list/food_types = list(/obj/item/food/meat/slab)
+ AddComponent(/datum/component/tameable, food_types = food_types, tame_chance = 15, bonus_tame_chance = 5)
/mob/living/basic/mining/wolf/tamed(mob/living/tamer, atom/food)
new /obj/effect/temp_visual/heart(src.loc)
diff --git a/code/modules/mob/living/basic/lavaland/goldgrub/goldgrub.dm b/code/modules/mob/living/basic/lavaland/goldgrub/goldgrub.dm
index 26c0d79540a73..5a1166962be55 100644
--- a/code/modules/mob/living/basic/lavaland/goldgrub/goldgrub.dm
+++ b/code/modules/mob/living/basic/lavaland/goldgrub/goldgrub.dm
@@ -67,6 +67,7 @@
ADD_TRAIT(src, TRAIT_BOULDER_BREAKER, INNATE_TRAIT)
ADD_TRAIT(src, TRAIT_INSTANTLY_PROCESSES_BOULDERS, INNATE_TRAIT)
RegisterSignal(src, COMSIG_ATOM_PRE_BULLET_ACT, PROC_REF(block_bullets))
+ RegisterSignal(src, COMSIG_MOB_ATE, PROC_REF(on_eat))
/mob/living/basic/mining/goldgrub/proc/block_bullets(datum/source, obj/projectile/hitting_projectile)
SIGNAL_HANDLER
@@ -105,7 +106,8 @@
return ..()
/mob/living/basic/mining/goldgrub/proc/make_tameable()
- AddComponent(/datum/component/tameable, food_types = list(/obj/item/stack/ore), tame_chance = 25, bonus_tame_chance = 5)
+ var/list/food_types = string_list(list(/obj/item/stack/ore))
+ AddComponent(/datum/component/tameable, food_types = food_types, tame_chance = 25, bonus_tame_chance = 5)
/mob/living/basic/mining/goldgrub/tamed(mob/living/tamer, atom/food)
new /obj/effect/temp_visual/heart(src.loc)
@@ -127,13 +129,18 @@
. = ..()
if(!istype(arrived, /obj/item/stack/ore))
return
- playsound(src,'sound/items/eatfood.ogg', rand(10,50), TRUE)
if(!can_lay_eggs)
return
if(!istype(arrived, /obj/item/stack/ore/bluespace_crystal) || prob(60))
return
new /obj/item/food/egg/green/grub_egg(get_turf(src))
+/mob/living/basic/mining/goldgrub/proc/on_eat(atom/source, atom/movable/food, mob/feeder)
+ SIGNAL_HANDLER
+
+ food.forceMove(src)
+ return COMSIG_MOB_TERMINATE_EAT
+
/mob/living/basic/mining/goldgrub/baby
icon = 'icons/mob/simple/lavaland/lavaland_monsters.dmi'
name = "goldgrub baby"
diff --git a/code/modules/mob/living/basic/lavaland/goliath/goliath.dm b/code/modules/mob/living/basic/lavaland/goliath/goliath.dm
index 76feb1f4bd4a7..f8a7ee46bc33b 100644
--- a/code/modules/mob/living/basic/lavaland/goliath/goliath.dm
+++ b/code/modules/mob/living/basic/lavaland/goliath/goliath.dm
@@ -68,7 +68,8 @@
AddComponent(/datum/component/basic_mob_attack_telegraph)
AddComponentFrom(INNATE_TRAIT, /datum/component/shovel_hands)
if (tameable)
- AddComponent(/datum/component/tameable, food_types = list(/obj/item/food/grown/ash_flora), tame_chance = 10, bonus_tame_chance = 5)
+ var/static/list/food_types = list(/obj/item/food/grown/ash_flora)
+ AddComponent(/datum/component/tameable, food_types = food_types, tame_chance = 10, bonus_tame_chance = 5)
tentacles = new (src)
tentacles.Grant(src)
diff --git a/code/modules/mob/living/basic/lavaland/lobstrosity/lobstrosity.dm b/code/modules/mob/living/basic/lavaland/lobstrosity/lobstrosity.dm
index ebca48f2fa201..0b8babf82ec30 100644
--- a/code/modules/mob/living/basic/lavaland/lobstrosity/lobstrosity.dm
+++ b/code/modules/mob/living/basic/lavaland/lobstrosity/lobstrosity.dm
@@ -184,7 +184,7 @@
optional_checks = CALLBACK(src, PROC_REF(ready_to_grow)),\
optional_grow_behavior = CALLBACK(src, PROC_REF(grow_up))\
)
- AddComponent(/datum/component/tameable, target_foods, tame_chance = 35, bonus_tame_chance = 20)
+ AddComponent(/datum/component/tameable, tame_chance = 35, bonus_tame_chance = 20)
AddComponent(/datum/component/swarming, 16, 11)
ADD_TRAIT(src, TRAIT_MOB_HIDE_HAPPINESS, INNATE_TRAIT) //Do not let strangers know it gets happy when poked if stray.
diff --git a/code/modules/mob/living/basic/lavaland/raptor/_raptor.dm b/code/modules/mob/living/basic/lavaland/raptor/_raptor.dm
index 6e3032f5cc387..bd5563f0ac29d 100644
--- a/code/modules/mob/living/basic/lavaland/raptor/_raptor.dm
+++ b/code/modules/mob/living/basic/lavaland/raptor/_raptor.dm
@@ -97,7 +97,7 @@ GLOBAL_LIST_EMPTY(raptor_population)
ai_controller.set_blackboard_key(BB_BASIC_MOB_SPEAK_LINES, display_emote)
inherited_stats = new
inherit_properties()
- var/static/list/my_food = list(/obj/item/stack/ore)
+ var/list/my_food = string_list(list(/obj/item/stack/ore))
AddElement(/datum/element/basic_eating, food_types = my_food)
AddElement(/datum/element/ai_retaliate)
AddElement(/datum/element/ai_flee_while_injured, stop_fleeing_at = 0.5, start_fleeing_below = 0.2)
diff --git a/code/modules/mob/living/basic/pets/dog/_dog.dm b/code/modules/mob/living/basic/pets/dog/_dog.dm
index b5259d275b530..fd8920d2ca0e3 100644
--- a/code/modules/mob/living/basic/pets/dog/_dog.dm
+++ b/code/modules/mob/living/basic/pets/dog/_dog.dm
@@ -68,7 +68,11 @@
AddElement(/datum/element/pet_bonus, "woof")
AddElement(/datum/element/footstep, FOOTSTEP_MOB_CLAW)
AddElement(/datum/element/unfriend_attacker, untamed_reaction = "%SOURCE% fixes %TARGET% with a look of betrayal.")
- AddComponent(/datum/component/tameable, food_types = list(/obj/item/food/meat/slab/human/mutant/skeleton, /obj/item/stack/sheet/bone), tame_chance = 30, bonus_tame_chance = 15, unique = FALSE)
+ var/static/list/food_types = list(
+ /obj/item/food/meat/slab/human/mutant/skeleton,
+ /obj/item/stack/sheet/bone,
+ )
+ AddComponent(/datum/component/tameable, food_types = food_types, tame_chance = 30, bonus_tame_chance = 15, unique = FALSE)
AddComponent(/datum/component/obeys_commands, pet_commands)
var/dog_area = get_area(src)
for(var/obj/structure/bed/dogbed/dog_bed in dog_area)
diff --git a/code/modules/mob/living/basic/space_fauna/carp/carp.dm b/code/modules/mob/living/basic/space_fauna/carp/carp.dm
index acd72af2b3889..6f843857578a6 100644
--- a/code/modules/mob/living/basic/space_fauna/carp/carp.dm
+++ b/code/modules/mob/living/basic/space_fauna/carp/carp.dm
@@ -111,7 +111,8 @@
tamed(tamer, feedback = FALSE)
befriend(tamer)
else
- AddComponent(/datum/component/tameable, food_types = list(/obj/item/food/meat), tame_chance = 10, bonus_tame_chance = 5)
+ var/static/list/food_types = list(/obj/item/food/meat)
+ AddComponent(/datum/component/tameable, food_types = food_types, tame_chance = 10, bonus_tame_chance = 5)
teleport = new(src)
teleport.Grant(src)
diff --git a/code/modules/mob/living/basic/space_fauna/eyeball/_eyeball.dm b/code/modules/mob/living/basic/space_fauna/eyeball/_eyeball.dm
index 9c56ec044c75f..66e73b2bbfac8 100644
--- a/code/modules/mob/living/basic/space_fauna/eyeball/_eyeball.dm
+++ b/code/modules/mob/living/basic/space_fauna/eyeball/_eyeball.dm
@@ -58,7 +58,8 @@
grant_actions_by_list(innate_actions)
AddElement(/datum/element/simple_flying)
- AddComponent(/datum/component/tameable, food_types = list(/obj/item/food/grown/carrot), tame_chance = 100)
+ var/list/food_types = string_list(list(/obj/item/food/grown/carrot))
+ AddComponent(/datum/component/tameable, food_types = food_types, tame_chance = 100)
ADD_TRAIT(src, TRAIT_SPACEWALK, INNATE_TRAIT)
on_hit_overlay = mutable_appearance(icon, "[icon_state]_crying")
diff --git a/code/modules/mob/living/basic/space_fauna/regal_rat/regal_rat.dm b/code/modules/mob/living/basic/space_fauna/regal_rat/regal_rat.dm
index eae137787ed0d..07ad70a29e3bc 100644
--- a/code/modules/mob/living/basic/space_fauna/regal_rat/regal_rat.dm
+++ b/code/modules/mob/living/basic/space_fauna/regal_rat/regal_rat.dm
@@ -179,10 +179,12 @@
if(SEND_SIGNAL(target, COMSIG_RAT_INTERACT, src) & COMPONENT_RAT_INTERACTED)
return FALSE
- if(isnull(mind) || !combat_mode)
+ if(isnull(mind) || combat_mode)
return TRUE
- poison_target(target)
+ if(poison_target(target))
+ return FALSE
+
return TRUE
/// Checks if we are allowed to attack this mob. Will return TRUE if we are potentially allowed to attack, but if we end up in a case where we should NOT attack, return FALSE.
@@ -204,10 +206,17 @@
return TRUE
-/// Attempts to add rat spit to a target, effectively poisoning it to whoever eats it. Yuckers.
+/**
+ * Attempts to add rat spit to a target, effectively poisoning it to whoever eats it. Yuckers.
+ * Returns TRUE if the target is valid for adding rat spit
+ * Returns FALSE if the target is invalid for adding rat spit
+ * Arguments
+ *
+ * * atom/lean_target - the target we try to add the spit to
+ */
/mob/living/basic/regal_rat/proc/poison_target(atom/target)
if(isnull(target.reagents) || !target.is_injectable(src, allowmobs = TRUE))
- return
+ return FALSE
visible_message(
span_warning("[src] starts licking [target] passionately!"),
@@ -216,10 +225,11 @@
)
if (!do_after(src, 2 SECONDS, target, interaction_key = REGALRAT_INTERACTION))
- return
+ return TRUE // don't return false here because they tried to lick and the do_after was interrupted, otherwise cancelling the do_after will make them hit the target.
target.reagents.add_reagent(/datum/reagent/rat_spit, rand(1,3), no_react = TRUE)
balloon_alert(src, "licked")
+ return TRUE
/**
* Conditionally "eat" cheese object and heal, if injured.
diff --git a/code/modules/mob/living/basic/vermin/mouse.dm b/code/modules/mob/living/basic/vermin/mouse.dm
index a0c1faf971d06..0ded016d1a02e 100644
--- a/code/modules/mob/living/basic/vermin/mouse.dm
+++ b/code/modules/mob/living/basic/vermin/mouse.dm
@@ -83,7 +83,8 @@
if (tame)
faction |= FACTION_NEUTRAL
else
- AddComponent(/datum/component/tameable, food_types = list(/obj/item/food/cheese), tame_chance = 100)
+ var/static/list/food_types = list(/obj/item/food/cheese)
+ AddComponent(/datum/component/tameable, food_types = food_types, tame_chance = 100)
/mob/living/basic/mouse/Destroy()
SSmobs.cheeserats -= src
diff --git a/code/modules/mob/living/brain/brain_item.dm b/code/modules/mob/living/brain/brain_item.dm
index e2cf6e4fb39b5..c590dd8bf3e64 100644
--- a/code/modules/mob/living/brain/brain_item.dm
+++ b/code/modules/mob/living/brain/brain_item.dm
@@ -467,9 +467,9 @@
/obj/item/organ/brain/felinid //A bit smaller than average
brain_size = 0.8
-/obj/item/organ/brain/lizard //A bit smaller than average
+/obj/item/organ/brain/lizard
name = "lizard brain"
- desc = "This juicy piece of meat has a oversized brain stem and cerebellum, with not much of a limbic system to speak of at all. You would expect it's owner to be pretty cold blooded."
+ desc = "This juicy piece of meat has a oversized brain stem and cerebellum, with not much of a limbic system to speak of at all. You would expect its owner to be pretty cold blooded."
// organ_traits = list(TRAIT_TACKLING_TAILED_DEFENDER) // DOPPLER EDIT REMOVAL
/obj/item/organ/brain/abductor
diff --git a/code/modules/mob/living/carbon/alien/emote.dm b/code/modules/mob/living/carbon/alien/emote.dm
index 717e18c9b3166..774a69ee50425 100644
--- a/code/modules/mob/living/carbon/alien/emote.dm
+++ b/code/modules/mob/living/carbon/alien/emote.dm
@@ -6,17 +6,6 @@
key_third_person = "gnarls"
message = "gnarls and shows its teeth..."
-/datum/emote/living/alien/hiss
- key = "hiss"
- key_third_person = "hisses"
- message_alien = "hisses."
- message_larva = "hisses softly."
- emote_type = EMOTE_AUDIBLE | EMOTE_VISIBLE
-
-/datum/emote/living/alien/hiss/get_sound(mob/living/user)
- if(isalienadult(user))
- return SFX_HISS
-
/datum/emote/living/alien/roar
key = "roar"
key_third_person = "roars"
diff --git a/code/modules/mob/living/carbon/carbon.dm b/code/modules/mob/living/carbon/carbon.dm
index d423a2977a9da..ecbc642e580be 100644
--- a/code/modules/mob/living/carbon/carbon.dm
+++ b/code/modules/mob/living/carbon/carbon.dm
@@ -77,7 +77,7 @@
if(!hurt)
return
- if(victim.check_block(src, 0, "[name]", LEAP_ATTACK))
+ if(. == SUCCESSFUL_BLOCK || victim.check_block(src, 0, "[name]", LEAP_ATTACK))
blocked = TRUE
take_bodypart_damage(10 + 5 * extra_speed, check_armor = TRUE, wound_bonus = extra_speed * 5)
diff --git a/code/modules/mob/living/carbon/carbon_defines.dm b/code/modules/mob/living/carbon/carbon_defines.dm
index c13ac14b100c1..56c687df7bb1b 100644
--- a/code/modules/mob/living/carbon/carbon_defines.dm
+++ b/code/modules/mob/living/carbon/carbon_defines.dm
@@ -11,6 +11,7 @@
usable_hands = 0 //Populated on init through list/bodyparts
mobility_flags = MOBILITY_FLAGS_CARBON_DEFAULT
blocks_emissive = EMISSIVE_BLOCK_NONE
+ mouse_drop_zone = TRUE
// STOP_OVERLAY_UPDATE_BODY_PARTS is removed after we call update_body_parts() during init.
living_flags = ALWAYS_DEATHGASP|STOP_OVERLAY_UPDATE_BODY_PARTS
///List of [/obj/item/organ]s in the mob. They don't go in the contents for some reason I don't want to know.
diff --git a/code/modules/mob/living/carbon/emote.dm b/code/modules/mob/living/carbon/emote.dm
index 9ca105fa4d385..5e132f7947674 100644
--- a/code/modules/mob/living/carbon/emote.dm
+++ b/code/modules/mob/living/carbon/emote.dm
@@ -237,3 +237,18 @@
key = "wink"
key_third_person = "winks"
message = "winks."
+
+/datum/emote/living/carbon/hiss
+ key = "hiss"
+ key_third_person = "hisses"
+ message = "hisses!"
+ emote_type = EMOTE_AUDIBLE | EMOTE_VISIBLE
+ vary = TRUE
+
+/datum/emote/living/carbon/hiss/get_sound(mob/living/carbon/user)
+ . = ..()
+ if(!istype(user))
+ return
+ if(isalien(user))
+ return SFX_HISS
+ return user.dna.species.get_hiss_sound()
diff --git a/code/modules/mob/living/carbon/examine.dm b/code/modules/mob/living/carbon/examine.dm
index 1852d669dfdc7..534d0a216768e 100644
--- a/code/modules/mob/living/carbon/examine.dm
+++ b/code/modules/mob/living/carbon/examine.dm
@@ -228,6 +228,7 @@
. += "[t_He] [t_is] flushed and wheezing."
if (bodytemperature < dna.species.bodytemp_cold_damage_limit)
. += "[t_He] [t_is] shivering."
+ /* DOPPLER EDIT REMOVAL - Fundamentally Evil
if(HAS_TRAIT(src, TRAIT_EVIL))
. += "[t_His] eyes radiate with a unfeeling, cold detachment. There is nothing but darkness within [t_his] soul."
if(living_user.mind?.holy_role >= HOLY_ROLE_PRIEST)
@@ -235,6 +236,19 @@
else
living_user.add_mood_event("encountered_evil", /datum/mood_event/encountered_evil)
living_user.set_jitter_if_lower(15 SECONDS)
+ */
+ // DOPPLER EDIT ADDITION - Unholy Aura & Bad Vibes
+ if(HAS_TRAIT(src, TRAIT_EVIL) && living_user.mind?.holy_role >= HOLY_ROLE_PRIEST)
+ . += span_warning("[t_He] [t_is] cloaked in a miasma of unholy energy!")
+
+ if(HAS_TRAIT(src, TRAIT_BAD_VIBES))
+ . += span_warning("[t_He] give[p_s()] off an unsettling aura.")
+ living_user.add_mood_event("bad_vibes", /datum/mood_event/bad_vibes)
+
+ if(HAS_TRAIT(user, TRAIT_EVIL) && (mind?.holy_role || HAS_TRAIT(src, TRAIT_SPIRITUAL)))
+ . += span_warning("[t_He] shimmer[p_s()] with radiant protection.")
+ living_user.add_mood_event("holy_figure", /datum/mood_event/holy_figure)
+ // DOPPLER EDIT ADDITION END
if(HAS_TRAIT(user, TRAIT_SPIRITUAL) && mind?.holy_role)
. += "[t_He] [t_has] a holy aura about [t_him]."
@@ -287,7 +301,7 @@
. += span_notice("[t_He] appear[p_s()] to have been dissected. Useless for examination... for now.")
if(HAS_TRAIT(src, TRAIT_SURGICALLY_ANALYZED))
. += span_notice("A skilled hand has mapped this one's internal intricacies. It will be far easier to perform future experimentations upon [user.p_them()]. Exquisite.")
- if(HAS_MIND_TRAIT(user, TRAIT_EXAMINE_FITNESS))
+ if(isliving(user) && HAS_MIND_TRAIT(user, TRAIT_EXAMINE_FITNESS))
. += compare_fitness(user)
var/hud_info = get_hud_examine_info(user)
@@ -592,4 +606,10 @@
if(undershirt.has_sensor == BROKEN_SENSORS)
. += list(span_notice("The [undershirt]'s medical sensors are sparking."))
+ // DOPPLER EDIT ADDITION BEGIN - working scar examine..?
+ for(var/datum/scar/scar in all_scars)
+ if(scar.is_visible(user))
+ . += scar.get_examine_description(user)
+ // DOPPLER EDIT ADDITION END
+
#undef ADD_NEWLINE_IF_NECESSARY
diff --git a/code/modules/mob/living/carbon/human/_species.dm b/code/modules/mob/living/carbon/human/_species.dm
index e82aa1aaff0ce..c5cf56bf95bc0 100644
--- a/code/modules/mob/living/carbon/human/_species.dm
+++ b/code/modules/mob/living/carbon/human/_species.dm
@@ -1485,6 +1485,10 @@ GLOBAL_LIST_EMPTY(features_by_species)
/datum/species/proc/get_snore_sound(mob/living/carbon/human/human)
return
+/// Returns the species' hiss sound
+/datum/species/proc/get_hiss_sound(mob/living/carbon/human/human)
+ return
+
/datum/species/proc/get_mut_organs(include_brain = TRUE)
var/list/mut_organs = list()
mut_organs += mutant_organs
diff --git a/code/modules/mob/living/carbon/human/human.dm b/code/modules/mob/living/carbon/human/human.dm
index 7c82c9d19763b..1becef215b01b 100644
--- a/code/modules/mob/living/carbon/human/human.dm
+++ b/code/modules/mob/living/carbon/human/human.dm
@@ -391,10 +391,7 @@
/mob/living/carbon/human/try_inject(mob/user, target_zone, injection_flags)
. = ..()
if(!. && (injection_flags & INJECT_TRY_SHOW_ERROR_MESSAGE) && user)
- if(!target_zone)
- target_zone = get_bodypart(check_zone(user.zone_selected))
- var/obj/item/bodypart/the_part = isbodypart(target_zone) ? target_zone : get_bodypart(check_zone(target_zone)) //keep these synced
- to_chat(user, span_alert("There is no exposed flesh or thin material on [p_their()] [the_part.name]."))
+ balloon_alert(user, "no exposed skin on [target_zone || check_zone(user.zone_selected)]!")
/mob/living/carbon/human/get_butt_sprite()
var/obj/item/bodypart/chest/chest = get_bodypart(BODY_ZONE_CHEST)
diff --git a/code/modules/mob/living/carbon/human/human_defense.dm b/code/modules/mob/living/carbon/human/human_defense.dm
index e6c7f9edd9b08..73f63b8bfe110 100644
--- a/code/modules/mob/living/carbon/human/human_defense.dm
+++ b/code/modules/mob/living/carbon/human/human_defense.dm
@@ -85,8 +85,8 @@
/mob/living/carbon/human/check_block(atom/hit_by, damage, attack_text = "the attack", attack_type = MELEE_ATTACK, armour_penetration = 0, damage_type = BRUTE)
. = ..()
- if(.)
- return TRUE
+ if(. == SUCCESSFUL_BLOCK)
+ return SUCCESSFUL_BLOCK
var/block_chance_modifier = round(damage / -3)
for(var/obj/item/worn_thing in get_equipped_items(INCLUDE_HELD))
@@ -100,9 +100,9 @@
var/final_block_chance = worn_thing.block_chance - (clamp((armour_penetration - worn_thing.armour_penetration) / 2, 0, 100)) + block_chance_modifier
if(worn_thing.hit_reaction(src, hit_by, attack_text, final_block_chance, damage, attack_type, damage_type))
- return TRUE
+ return SUCCESSFUL_BLOCK
- return FALSE
+ return FAILED_BLOCK
/mob/living/carbon/human/grippedby(mob/living/carbon/user, instant = FALSE)
if(w_uniform)
@@ -191,7 +191,7 @@
var/damage = HAS_TRAIT(user, TRAIT_PERFECT_ATTACKER) ? monkey_mouth.unarmed_damage_high : rand(monkey_mouth.unarmed_damage_low, monkey_mouth.unarmed_damage_high)
if(!damage)
return FALSE
- if(check_block(user, damage, "the [user.name]"))
+ if(check_block(user, damage, "the [user.name]", attack_type = UNARMED_ATTACK))
return FALSE
apply_damage(damage, BRUTE, affecting, run_armor_check(affecting, MELEE))
return TRUE
@@ -248,9 +248,6 @@
return TRUE
apply_damage(damage, BRUTE, affecting, armor_block)
-
-
-
/mob/living/carbon/human/attack_larva(mob/living/carbon/alien/larva/L, list/modifiers)
. = ..()
if(!.)
@@ -258,7 +255,7 @@
var/damage = rand(L.melee_damage_lower, L.melee_damage_upper)
if(!damage)
return
- if(check_block(L, damage, "the [L.name]"))
+ if(check_block(L, damage, "the [L.name]", attack_type = UNARMED_ATTACK))
return FALSE
if(stat != DEAD)
L.amount_grown = min(L.amount_grown + damage, L.max_grown)
diff --git a/code/modules/mob/living/carbon/human/human_update_icons.dm b/code/modules/mob/living/carbon/human/human_update_icons.dm
index 896e1899fb1af..a4aa5e977f1fe 100644
--- a/code/modules/mob/living/carbon/human/human_update_icons.dm
+++ b/code/modules/mob/living/carbon/human/human_update_icons.dm
@@ -937,7 +937,7 @@ generate/load female uniform sprites matching all previously decided variables
standing.pixel_y += offsets[2]
standing.alpha = alpha
- standing.color = color
+ standing = color_atom_overlay(standing)
return standing
diff --git a/code/modules/mob/living/carbon/human/species_types/ethereal.dm b/code/modules/mob/living/carbon/human/species_types/ethereal.dm
index 471d474eda185..2a1b7785ba84a 100644
--- a/code/modules/mob/living/carbon/human/species_types/ethereal.dm
+++ b/code/modules/mob/living/carbon/human/species_types/ethereal.dm
@@ -181,6 +181,9 @@
'sound/mobs/humanoids/ethereal/ethereal_scream_3.ogg',
)
+/datum/species/ethereal/get_hiss_sound(mob/living/carbon/human/ethereal)
+ return 'sound/mobs/humanoids/ethereal/ethereal_hiss.ogg'
+
/datum/species/ethereal/get_physical_attributes()
return "Ethereals process electricity as their power supply, not food, and are somewhat resistant to it.\
They do so via their crystal core, their equivalent of a human heart, which will also encase them in a reviving crystal if they die.\
diff --git a/code/modules/mob/living/carbon/human/species_types/felinid.dm b/code/modules/mob/living/carbon/human/species_types/felinid.dm
index daa01c8c4f919..79705ece3c9b6 100644
--- a/code/modules/mob/living/carbon/human/species_types/felinid.dm
+++ b/code/modules/mob/living/carbon/human/species_types/felinid.dm
@@ -114,6 +114,8 @@
return SFX_SNORE_FEMALE
return SFX_SNORE_MALE
+/datum/species/human/felinid/get_hiss_sound(mob/living/carbon/human/felinid)
+ return 'sound/mobs/humanoids/felinid/felinid_hiss.ogg'
/proc/mass_purrbation()
for(var/mob in GLOB.human_list)
diff --git a/code/modules/mob/living/carbon/human/species_types/humans.dm b/code/modules/mob/living/carbon/human/species_types/humans.dm
index 68dab15d1b7c9..3baf2e59d44eb 100644
--- a/code/modules/mob/living/carbon/human/species_types/humans.dm
+++ b/code/modules/mob/living/carbon/human/species_types/humans.dm
@@ -93,6 +93,9 @@
return SFX_SNORE_FEMALE
return SFX_SNORE_MALE
+/datum/species/human/get_hiss_sound(mob/living/carbon/human/human)
+ return 'sound/mobs/humanoids/human/hiss/human_hiss.ogg'
+
/datum/species/human/get_species_description()
return "Humans are the dominant species in the known galaxy. \
Their kind extend from old Earth to the edges of known space."
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 d474173b05853..a5af8a9fe33e7 100644
--- a/code/modules/mob/living/carbon/human/species_types/lizardpeople.dm
+++ b/code/modules/mob/living/carbon/human/species_types/lizardpeople.dm
@@ -114,6 +114,9 @@
return SFX_SNORE_FEMALE
return SFX_SNORE_MALE
+/datum/species/lizard/get_hiss_sound(mob/living/carbon/human/lizard)
+ return 'sound/mobs/humanoids/lizard/lizard_hiss.ogg'
+
/datum/species/lizard/get_physical_attributes()
return "Lizardpeople can withstand slightly higher temperatures than most species, but they are very vulnerable to the cold \
and can't regulate their body-temperature internally, making the vacuum of space extremely deadly to them."
diff --git a/code/modules/mob/living/carbon/human/species_types/monkeys.dm b/code/modules/mob/living/carbon/human/species_types/monkeys.dm
index 41c7ee0afbcb3..3d7e717f67328 100644
--- a/code/modules/mob/living/carbon/human/species_types/monkeys.dm
+++ b/code/modules/mob/living/carbon/human/species_types/monkeys.dm
@@ -63,6 +63,10 @@
/datum/species/monkey/get_scream_sound(mob/living/carbon/human/monkey)
return get_sfx(SFX_SCREECH)
+/datum/species/monkey/get_hiss_sound(mob/living/carbon/human/monkey)
+ return 'sound/mobs/humanoids/human/hiss/human_hiss.ogg'
+ // we're both great apes, or something..
+
/datum/species/monkey/get_physical_attributes()
return "Monkeys are slippery, can crawl into vents, and are more dextrous than humans.. but only when stealing things. \
Natural monkeys cannot operate machinery or most tools with their paws, but unusually clever monkeys or those that were once something else can."
diff --git a/code/modules/mob/living/emote.dm b/code/modules/mob/living/emote.dm
index 8adfe4e888d27..a74d3727bacb5 100644
--- a/code/modules/mob/living/emote.dm
+++ b/code/modules/mob/living/emote.dm
@@ -303,19 +303,43 @@
key_third_person = "points"
message = "points."
message_param = "points at %t."
- hands_use_check = TRUE
+ cooldown = 1 SECONDS
+ // don't put hands use check here, everything is handled in run_emote
/datum/emote/living/point/run_emote(mob/user, params, type_override, intentional)
message_param = initial(message_param) // reset
- if(ishuman(user))
- var/mob/living/carbon/human/H = user
- if(H.usable_hands == 0)
- if(H.usable_legs != 0)
- message_param = "tries to point at %t with a leg, [span_userdanger("falling down")] in the process!"
- H.Paralyze(20)
+ if(iscarbon(user))
+ var/mob/living/carbon/our_carbon = user
+ if(our_carbon.usable_hands <= 0 || user.incapacitated & INCAPABLE_RESTRAINTS || HAS_TRAIT(user, TRAIT_HANDS_BLOCKED))
+ if(our_carbon.usable_legs > 0)
+ var/one_leg = FALSE
+ var/has_shoes = our_carbon.get_item_by_slot(ITEM_SLOT_FEET)
+ if(our_carbon.usable_legs == 1)
+ one_leg = TRUE
+ var/success_prob = 65
+ if(HAS_TRAIT(our_carbon, TRAIT_FREERUNNING))
+ success_prob += 35
+ if(one_leg)
+ success_prob -= 40
+ if(prob(success_prob))
+ message_param = "[one_leg ? "jumps into the air and " : ""]points at %t with their [has_shoes ? "leg" : "toes"]!"
+ else
+ message_param = "[one_leg ? "jumps into the air and " : ""]tries to point at %t with their [has_shoes ? "leg" : "toes"], falling down in the process!"
+ our_carbon.Paralyze(2 SECONDS)
+ TIMER_COOLDOWN_START(user, "point_verb_emote_cooldown", 1 SECONDS)
else
- message_param = "[span_userdanger("bumps [user.p_their()] head on the ground")] trying to motion towards %t."
- H.adjustOrganLoss(ORGAN_SLOT_BRAIN, 5)
+ if(our_carbon.get_organ_slot(ORGAN_SLOT_EYES))
+ message_param = "gives a meaningful glance at %t!"
+ TIMER_COOLDOWN_START(src, "point_verb_emote_cooldown", 1.5 SECONDS)
+ else
+ if(our_carbon.get_organ_slot(ORGAN_SLOT_TONGUE))
+ message_param = "motions their tongue towards %t!"
+ TIMER_COOLDOWN_START(src, "point_verb_emote_cooldown", 2 SECONDS)
+ else
+ message_param = "[span_userdanger("bumps [user.p_their()] head on the ground")] trying to motion towards %t."
+ our_carbon.adjustOrganLoss(ORGAN_SLOT_BRAIN, 5)
+ playsound(user, 'sound/effects/glass/glassbash.ogg', 50, TRUE, SHORT_RANGE_SOUND_EXTRARANGE)
+ TIMER_COOLDOWN_START(src, "point_verb_emote_cooldown", 2.5 SECONDS)
return ..()
/datum/emote/living/sneeze
@@ -647,21 +671,6 @@
to_chat(user, span_boldwarning("You cannot send IC messages (muted)."))
return FALSE
- var/our_message = params ? params : get_custom_emote_from_user()
-
- if(!emote_is_valid(user, our_message))
- return FALSE
-
- if(!params)
- var/user_emote_type = get_custom_emote_type_from_user()
-
- if(!user_emote_type)
- return FALSE
-
- emote_type = user_emote_type
-
- message = our_message
-
/datum/emote/living/custom/proc/emote_is_valid(mob/user, input)
// We're assuming clientless mobs custom emoting is something codebase-driven and not player-driven.
// If players ever get the ability to force clientless mobs to emote, we'd need to reconsider this.
@@ -719,9 +728,22 @@
return FALSE
/datum/emote/living/custom/run_emote(mob/user, params, type_override = null, intentional = FALSE)
- if(params && type_override)
- emote_type = type_override
+ var/our_message = params ? params : get_custom_emote_from_user()
+
+ if(!emote_is_valid(user, our_message))
+ return FALSE
+
+ if(!params)
+ var/user_emote_type = get_custom_emote_type_from_user()
+
+ if(!user_emote_type)
+ return FALSE
+
+ emote_type = user_emote_type
+
+ message = our_message
. = ..()
+
///Reset the message and emote type after it's run.
message = null
emote_type = EMOTE_VISIBLE
diff --git a/code/modules/mob/living/living.dm b/code/modules/mob/living/living.dm
index 7f811843f0f92..094c28d84a507 100644
--- a/code/modules/mob/living/living.dm
+++ b/code/modules/mob/living/living.dm
@@ -518,7 +518,7 @@
//same as above
/mob/living/pointed(atom/A as mob|obj|turf in view(client.view, src))
- if(incapacitated)
+ if(INCAPACITATED_IGNORING(src, INCAPABLE_RESTRAINTS))
return FALSE
return ..()
@@ -742,9 +742,7 @@
/// Returns what the body_position_pixel_y_offset should be if the current size were `value`
/mob/living/proc/get_pixel_y_offset_standing(value)
- var/icon/living_icon = icon(icon)
- var/height = living_icon.Height()
- return (value-1) * height * 0.5
+ return (value-1) * get_cached_height() * 0.5
/mob/living/proc/update_density()
if(HAS_TRAIT(src, TRAIT_UNDENSE))
@@ -834,7 +832,7 @@
if(!livingdoll.filtered)
livingdoll.filtered = TRUE
var/icon/mob_mask = icon(icon, icon_state)
- if(mob_mask.Height() > ICON_SIZE_Y || mob_mask.Width() > ICON_SIZE_X)
+ if(get_cached_height() > ICON_SIZE_Y || get_cached_width() > ICON_SIZE_X)
var/health_doll_icon_state = health_doll_icon ? health_doll_icon : "megasprite"
mob_mask = icon('icons/hud/screen_gen.dmi', health_doll_icon_state) //swap to something generic if they have no special doll
livingdoll.add_filter("mob_shape_mask", 1, alpha_mask_filter(icon = mob_mask))
@@ -1935,6 +1933,7 @@ GLOBAL_LIST_EMPTY(fire_appearances)
buckled.unbuckle_mob(src, force = TRUE)
if(has_buckled_mobs())
unbuckle_all_mobs(force = TRUE)
+ refresh_gravity()
. = ..()
if(. && client)
reset_perspective()
diff --git a/code/modules/mob/living/living_defense.dm b/code/modules/mob/living/living_defense.dm
index 4a09d0fe1f9f8..ab91cbc771e66 100644
--- a/code/modules/mob/living/living_defense.dm
+++ b/code/modules/mob/living/living_defense.dm
@@ -184,6 +184,10 @@
return 0
/mob/living/proc/set_combat_mode(new_mode, silent = TRUE)
+
+ if(HAS_TRAIT(src, TRAIT_COMBAT_MODE_LOCK))
+ return
+
if(combat_mode == new_mode)
return
. = combat_mode
@@ -201,10 +205,11 @@
/mob/living/hitby(atom/movable/AM, skipcatch, hitpush = TRUE, blocked = FALSE, datum/thrownthing/throwingdatum)
if(!isitem(AM))
// Filled with made up numbers for non-items.
- if(check_block(AM, 30, "\the [AM.name]", THROWN_PROJECTILE_ATTACK, 0, BRUTE))
+ if(check_block(AM, 30, "\the [AM.name]", THROWN_PROJECTILE_ATTACK, 0, BRUTE) & SUCCESSFUL_BLOCK)
hitpush = FALSE
skipcatch = TRUE
blocked = TRUE
+ return SUCCESSFUL_BLOCK
else
playsound(loc, 'sound/items/weapons/genhit.ogg', 50, TRUE, -1) //Item sounds are handled in the item itself
if(!isvendor(AM) && !iscarbon(AM)) //Vendors have special interactions, while carbon mobs already generate visible messages!
@@ -229,7 +234,7 @@
hitpush = FALSE
if(blocked)
- return TRUE
+ return SUCCESSFUL_BLOCK
var/mob/thrown_by = thrown_item.thrownby?.resolve()
if(thrown_by)
@@ -286,7 +291,7 @@
return FALSE
if(SEND_SIGNAL(src, COMSIG_LIVING_GRAB, target) & (COMPONENT_CANCEL_ATTACK_CHAIN|COMPONENT_SKIP_ATTACK))
return FALSE
- if(target.check_block(src, 0, "[src]'s grab"))
+ if(target.check_block(src, 0, "[src]'s grab", UNARMED_ATTACK))
return FALSE
target.grabbedby(src)
return TRUE
@@ -393,7 +398,7 @@
return FALSE
var/damage = rand(user.melee_damage_lower, user.melee_damage_upper)
- if(check_block(user, damage, "[user]'s [user.attack_verb_simple]", MELEE_ATTACK/*or UNARMED_ATTACK?*/, user.armour_penetration, user.melee_damage_type))
+ if(check_block(user, damage, "[user]'s [user.attack_verb_simple]", UNARMED_ATTACK, user.armour_penetration, user.melee_damage_type))
return FALSE
if(user.attack_sound)
@@ -508,7 +513,7 @@
/mob/living/attack_alien(mob/living/carbon/alien/adult/user, list/modifiers)
SEND_SIGNAL(src, COMSIG_MOB_ATTACK_ALIEN, user, modifiers)
if(LAZYACCESS(modifiers, RIGHT_CLICK))
- if(check_block(user, 0, "[user]'s tackle", MELEE_ATTACK, 0, BRUTE))
+ if(check_block(user, 0, "[user]'s tackle", UNARMED_ATTACK, 0, BRUTE))
return FALSE
user.do_attack_animation(src, ATTACK_EFFECT_DISARM)
return TRUE
@@ -517,7 +522,7 @@
if(HAS_TRAIT(user, TRAIT_PACIFISM))
to_chat(user, span_warning("You don't want to hurt anyone!"))
return FALSE
- if(check_block(user, user.melee_damage_upper, "[user]'s slash", MELEE_ATTACK, 0, BRUTE))
+ if(check_block(user, user.melee_damage_upper, "[user]'s slash", UNARMED_ATTACK, 0, BRUTE))
return FALSE
user.do_attack_animation(src)
return TRUE
@@ -801,6 +806,6 @@
/mob/living/proc/check_block(atom/hit_by, damage, attack_text = "the attack", attack_type = MELEE_ATTACK, armour_penetration = 0, damage_type = BRUTE)
if(SEND_SIGNAL(src, COMSIG_LIVING_CHECK_BLOCK, hit_by, damage, attack_text, attack_type, armour_penetration, damage_type) & SUCCESSFUL_BLOCK)
- return TRUE
+ return SUCCESSFUL_BLOCK
- return FALSE
+ return FAILED_BLOCK
diff --git a/code/modules/mob/living/living_movement.dm b/code/modules/mob/living/living_movement.dm
index 4522b6ca69a52..561df7849deee 100644
--- a/code/modules/mob/living/living_movement.dm
+++ b/code/modules/mob/living/living_movement.dm
@@ -23,7 +23,7 @@
var/area/old_area = old_turf.loc
var/area/new_area = new_turf.loc
// If the area gravity has changed, then it's possible that our state has changed, so update
- if(old_area.has_gravity != new_area.has_gravity)
+ if(old_area.default_gravity != new_area.default_gravity)
refresh_gravity()
/mob/living/on_changed_z_level(turf/old_turf, turf/new_turf, same_z_layer, notify_contents)
diff --git a/code/modules/mob/living/silicon/robot/robot_defines.dm b/code/modules/mob/living/silicon/robot/robot_defines.dm
index 7e2d8de24694a..07b8322bd7c48 100644
--- a/code/modules/mob/living/silicon/robot/robot_defines.dm
+++ b/code/modules/mob/living/silicon/robot/robot_defines.dm
@@ -16,6 +16,7 @@
has_limbs = TRUE
hud_type = /datum/hud/robot
unique_name = TRUE
+ mouse_drop_zone = TRUE
///Represents the cyborg's model (engineering, medical, etc.)
var/obj/item/robot_model/model = null
diff --git a/code/modules/mob/living/silicon/silicon.dm b/code/modules/mob/living/silicon/silicon.dm
index d19526e584c79..734ec24ef028c 100644
--- a/code/modules/mob/living/silicon/silicon.dm
+++ b/code/modules/mob/living/silicon/silicon.dm
@@ -77,6 +77,7 @@
TRAIT_SILICON_ACCESS,
TRAIT_REAGENT_SCANNER,
TRAIT_UNOBSERVANT,
+ TRAIT_NO_SLIP_ALL,
)
add_traits(traits_to_apply, ROUNDSTART_TRAIT)
@@ -264,7 +265,7 @@
if (lawcache_zeroth)
if (force || (lawcache_zeroth in lawcache_lawcheck))
- say("[radiomod] 0. [lawcache_zeroth]", forced = forced_log_message)
+ say("[radiomod] 0. [lawcache_zeroth]", forced = forced_log_message, message_mods = list(MODE_SEQUENTIAL = TRUE))
sleep(1 SECONDS)
for (var/index in 1 to length(lawcache_hacked))
@@ -273,7 +274,7 @@
if (length(law) <= 0)
continue
if (force || (law in lawcache_hackedcheck))
- say("[radiomod] [num]. [law]", forced = forced_log_message)
+ say("[radiomod] [num]. [law]", forced = forced_log_message, message_mods = list(MODE_SEQUENTIAL = TRUE))
sleep(1 SECONDS)
for (var/index in 1 to length(lawcache_ion))
@@ -282,7 +283,7 @@
if (length(law) <= 0)
return
if (force || (law in lawcache_ioncheck))
- say("[radiomod] [num]. [law]", forced = forced_log_message)
+ say("[radiomod] [num]. [law]", forced = forced_log_message, message_mods = list(MODE_SEQUENTIAL = TRUE))
sleep(1 SECONDS)
var/number = 1
@@ -291,7 +292,7 @@
if (length(law) <= 0)
continue
if (force || (law in lawcache_lawcheck))
- say("[radiomod] [number]. [law]", forced = forced_log_message)
+ say("[radiomod] [number]. [law]", forced = forced_log_message, message_mods = list(MODE_SEQUENTIAL = TRUE))
number++
sleep(1 SECONDS)
@@ -301,7 +302,7 @@
if (length(law) <= 0)
continue
if (force || (law in lawcache_lawcheck))
- say("[radiomod] [number]. [law]", forced = forced_log_message)
+ say("[radiomod] [number]. [law]", forced = forced_log_message, message_mods = list(MODE_SEQUENTIAL = TRUE))
number++
sleep(1 SECONDS)
diff --git a/code/modules/mob/living/silicon/silicon_defense.dm b/code/modules/mob/living/silicon/silicon_defense.dm
index 404b0324f71a0..7a78cb4be0537 100644
--- a/code/modules/mob/living/silicon/silicon_defense.dm
+++ b/code/modules/mob/living/silicon/silicon_defense.dm
@@ -82,13 +82,13 @@
/mob/living/silicon/check_block(atom/hitby, damage, attack_text, attack_type, armour_penetration, damage_type, attack_flag)
. = ..()
- if(.)
- return TRUE
+ if(. == SUCCESSFUL_BLOCK)
+ return SUCCESSFUL_BLOCK
if(damage_type == BRUTE && attack_type == UNARMED_ATTACK && attack_flag == MELEE && damage <= 10)
playsound(src, 'sound/effects/bang.ogg', 10, TRUE)
visible_message(span_danger("[attack_text] doesn't leave a dent on [src]!"), vision_distance = COMBAT_MESSAGE_RANGE)
- return TRUE
- return FALSE
+ return SUCCESSFUL_BLOCK
+ return FAILED_BLOCK
/mob/living/silicon/attack_drone(mob/living/basic/drone/user)
if(user.combat_mode)
diff --git a/code/modules/mob/living/simple_animal/hostile/megafauna/wendigo.dm b/code/modules/mob/living/simple_animal/hostile/megafauna/wendigo.dm
index aad198801adf0..4643d529d495c 100644
--- a/code/modules/mob/living/simple_animal/hostile/megafauna/wendigo.dm
+++ b/code/modules/mob/living/simple_animal/hostile/megafauna/wendigo.dm
@@ -199,6 +199,7 @@ Difficulty: Hard
/obj/projectile/colossus/wendigo_shockwave
name = "wendigo shockwave"
speed = 0.5
+
/// Amount the angle changes every pixel move
var/wave_speed = 0.5
/// Amount of movements this projectile has made
@@ -209,14 +210,16 @@ Difficulty: Hard
/obj/projectile/colossus/wendigo_shockwave/wave
speed = 0.125
- homing = TRUE
wave_speed = 0.3
/obj/projectile/colossus/wendigo_shockwave/wave/alternate
wave_speed = -0.3
-/obj/projectile/colossus/wendigo_shockwave/process_homing()
- pixel_moves++
+/obj/projectile/colossus/wendigo_shockwave/process_movement(pixels_to_move, hitscan, tile_limit)
+ . = ..()
+ if (QDELETED(src))
+ return
+ pixel_moves += .
set_angle(original_angle + pixel_moves * wave_speed)
/obj/item/wendigo_blood
diff --git a/code/modules/mob/living/simple_animal/hostile/vatbeast.dm b/code/modules/mob/living/simple_animal/hostile/vatbeast.dm
index 8eab28a52e6a5..56dcbcaf7e1fe 100644
--- a/code/modules/mob/living/simple_animal/hostile/vatbeast.dm
+++ b/code/modules/mob/living/simple_animal/hostile/vatbeast.dm
@@ -31,7 +31,13 @@
GRANT_ACTION(/datum/action/cooldown/tentacle_slap)
add_cell_sample()
- AddComponent(/datum/component/tameable, list(/obj/item/food/fries, /obj/item/food/cheesyfries, /obj/item/food/cornchips, /obj/item/food/carrotfries), tame_chance = 30, bonus_tame_chance = 0)
+ var/static/list/food_types = list(
+ /obj/item/food/fries,
+ /obj/item/food/cheesyfries,
+ /obj/item/food/cornchips,
+ /obj/item/food/carrotfries,
+ )
+ AddComponent(/datum/component/tameable, food_types = food_types, tame_chance = 30, bonus_tame_chance = 0)
/mob/living/simple_animal/hostile/vatbeast/tamed(mob/living/tamer, obj/item/food)
buckle_lying = 0
diff --git a/code/modules/mob/mob_movement.dm b/code/modules/mob/mob_movement.dm
index f19a3991ca34f..743bdad8c4511 100644
--- a/code/modules/mob/mob_movement.dm
+++ b/code/modules/mob/mob_movement.dm
@@ -281,6 +281,9 @@
if (SEND_SIGNAL(src, COMSIG_MOB_ATTEMPT_HALT_SPACEMOVE, movement_dir, continuous_move, backup) & COMPONENT_PREVENT_SPACEMOVE_HALT)
return FALSE
+ if (drift_handler?.attempt_halt(movement_dir, continuous_move, backup))
+ return FALSE
+
if(continuous_move || !istype(backup) || !movement_dir || backup.anchored)
return TRUE
@@ -300,8 +303,9 @@
/**
* Finds a target near a mob that is viable for pushing off when moving.
* Takes the intended movement direction as input, alongside if the context is checking if we're allowed to continue drifting
+ * If include_floors is TRUE, includes floors *with gravity*
*/
-/mob/get_spacemove_backup(moving_direction, continuous_move)
+/mob/get_spacemove_backup(moving_direction, continuous_move, include_floors = FALSE)
var/atom/secondary_backup
var/list/priority_dirs = (moving_direction in GLOB.cardinals) ? GLOB.cardinals : GLOB.diagonals
for(var/atom/pushover as anything in range(1, get_turf(src)))
@@ -309,13 +313,15 @@
continue
if(isarea(pushover))
continue
+ var/is_priority = pushover.loc == loc || (get_dir(src, pushover) in priority_dirs)
if(isturf(pushover))
var/turf/turf = pushover
if(isspaceturf(turf))
continue
if(!turf.density && !mob_negates_gravity())
- continue
- if (get_dir(src, pushover) in priority_dirs)
+ if (!include_floors || !turf.has_gravity())
+ continue
+ if (is_priority)
return pushover
secondary_backup = pushover
continue
@@ -343,13 +349,13 @@
if(moving_direction == get_dir(src, pushover)) // Can't push "off" of something that you're walking into
continue
if(rebound.anchored)
- if (get_dir(src, rebound) in priority_dirs)
+ if (is_priority)
return rebound
secondary_backup = rebound
continue
if(pulling == rebound)
continue
- if (get_dir(src, rebound) in priority_dirs)
+ if (is_priority)
return rebound
secondary_backup = rebound
return secondary_backup
diff --git a/code/modules/mod/mod_control.dm b/code/modules/mod/mod_control.dm
index c658cfb0dccee..8d5c5c209540d 100644
--- a/code/modules/mod/mod_control.dm
+++ b/code/modules/mod/mod_control.dm
@@ -757,7 +757,7 @@
to_chat(user, span_warning("It's too dangerous to smear [speed_potion] on [src] while it's active!"))
return SPEED_POTION_STOP
to_chat(user, span_notice("You slather the red gunk over [src], making it faster."))
- set_mod_color(COLOR_RED)
+ set_mod_color(color_transition_filter(COLOR_RED))
slowdown_inactive = 0
slowdown_active = 0
update_speed()
diff --git a/code/modules/mod/mod_types.dm b/code/modules/mod/mod_types.dm
index 626144bf3e114..91fb00c633b06 100644
--- a/code/modules/mod/mod_types.dm
+++ b/code/modules/mod/mod_types.dm
@@ -467,43 +467,43 @@
)
/// The insignia type, insignias show what sort of member of the ERT you're dealing with.
var/insignia_type = /obj/item/mod/module/insignia
- /// Additional module we add, as a treat.
- var/additional_module
+ /// Additional module (or modules if list) we add, as a treat.
+ var/additional_modules
/obj/item/mod/control/pre_equipped/responsory/Initialize(mapload, new_theme, new_skin, new_core)
applied_modules.Insert(1, insignia_type)
- if(additional_module)
- applied_modules += additional_module
- default_pins += additional_module
+ if(additional_modules)
+ applied_modules += additional_modules
+ default_pins += additional_modules
return ..()
/obj/item/mod/control/pre_equipped/responsory/commander
insignia_type = /obj/item/mod/module/insignia/commander
- additional_module = /obj/item/mod/module/power_kick
+ additional_modules = /obj/item/mod/module/power_kick
/obj/item/mod/control/pre_equipped/responsory/security
insignia_type = /obj/item/mod/module/insignia/security
- additional_module = /obj/item/mod/module/pepper_shoulders
+ additional_modules = /obj/item/mod/module/pepper_shoulders
/obj/item/mod/control/pre_equipped/responsory/engineer
insignia_type = /obj/item/mod/module/insignia/engineer
- additional_module = /obj/item/mod/module/rad_protection
+ additional_modules = /obj/item/mod/module/rad_protection
/obj/item/mod/control/pre_equipped/responsory/medic
insignia_type = /obj/item/mod/module/insignia/medic
- additional_module = /obj/item/mod/module/quick_carry
+ additional_modules = /obj/item/mod/module/quick_carry
/obj/item/mod/control/pre_equipped/responsory/janitor
insignia_type = /obj/item/mod/module/insignia/janitor
- additional_module = /obj/item/mod/module/noslip
+ additional_modules = list(/obj/item/mod/module/noslip, /obj/item/mod/module/mister/cleaner)
/obj/item/mod/control/pre_equipped/responsory/clown
insignia_type = /obj/item/mod/module/insignia/clown
- additional_module = /obj/item/mod/module/bikehorn
+ additional_modules = /obj/item/mod/module/bikehorn
/obj/item/mod/control/pre_equipped/responsory/chaplain
insignia_type = /obj/item/mod/module/insignia/chaplain
- additional_module = /obj/item/mod/module/injector
+ additional_modules = /obj/item/mod/module/injector
/obj/item/mod/control/pre_equipped/responsory/inquisitory
applied_skin = "inquisitory"
@@ -538,19 +538,19 @@
/obj/item/mod/control/pre_equipped/responsory/inquisitory/commander
insignia_type = /obj/item/mod/module/insignia/commander
- additional_module = /obj/item/mod/module/power_kick
+ additional_modules = /obj/item/mod/module/power_kick
/obj/item/mod/control/pre_equipped/responsory/inquisitory/security
insignia_type = /obj/item/mod/module/insignia/security
- additional_module = /obj/item/mod/module/pepper_shoulders
+ additional_modules = /obj/item/mod/module/pepper_shoulders
/obj/item/mod/control/pre_equipped/responsory/inquisitory/medic
insignia_type = /obj/item/mod/module/insignia/medic
- additional_module = /obj/item/mod/module/quick_carry
+ additional_modules = /obj/item/mod/module/quick_carry
/obj/item/mod/control/pre_equipped/responsory/inquisitory/chaplain
insignia_type = /obj/item/mod/module/insignia/chaplain
- additional_module = /obj/item/mod/module/injector
+ additional_modules = /obj/item/mod/module/injector
/obj/item/mod/control/pre_equipped/apocryphal
theme = /datum/mod_theme/apocryphal
diff --git a/code/modules/mod/modules/modules_general.dm b/code/modules/mod/modules/modules_general.dm
index ad81fa828350a..3f4dfe405f738 100644
--- a/code/modules/mod/modules/modules_general.dm
+++ b/code/modules/mod/modules/modules_general.dm
@@ -143,6 +143,7 @@
COMSIG_MODULE_DEACTIVATED, \
MOD_ABORT_USE, \
thrust_callback, \
+ thrust_callback, \
/datum/effect_system/trail_follow/ion/grav_allowed, \
)
diff --git a/code/modules/mod/modules/modules_maint.dm b/code/modules/mod/modules/modules_maint.dm
index 124deb1bd66c6..48089b0125c9a 100644
--- a/code/modules/mod/modules/modules_maint.dm
+++ b/code/modules/mod/modules/modules_maint.dm
@@ -324,16 +324,20 @@
if(you_fucked_up || mod.wearer.has_gravity() > NEGATIVE_GRAVITY)
return
- if (forced || SHOULD_DISABLE_FOOTSTEPS(mod.wearer))
- return
-
var/turf/open/current_turf = get_turf(mod.wearer)
var/turf/open/openspace/turf_above = get_step_multiz(mod.wearer, UP)
if(current_turf && istype(turf_above))
current_turf.zFall(mod.wearer)
+ return
+
else if(!turf_above && istype(current_turf) && current_turf.planetary_atmos) //nothing holding you down
INVOKE_ASYNC(src, PROC_REF(fly_away))
- else if(!(step_count % 2))
+ return
+
+ if (forced || (SSlag_switch.measures[DISABLE_FOOTSTEPS] && !(HAS_TRAIT(source, TRAIT_BYPASS_MEASURES))))
+ return
+
+ if(!(step_count % 2))
playsound(current_turf, 'sound/items/modsuit/atrocinator_step.ogg', 50)
step_count++
diff --git a/code/modules/mod/modules/modules_service.dm b/code/modules/mod/modules/modules_service.dm
index 70c11f069f090..9cf7c4702f70c 100644
--- a/code/modules/mod/modules/modules_service.dm
+++ b/code/modules/mod/modules/modules_service.dm
@@ -91,3 +91,21 @@
REMOVE_TRAIT(mod.wearer, TRAIT_WADDLING, REF(src))
if(is_clown_job(mod.wearer.mind?.assigned_role))
mod.wearer.clear_mood_event("clownshoes")
+
+// recharging cleaner spray module
+/obj/item/mod/module/mister/cleaner
+ name = "MOD janitorial mister module"
+ desc = "An space cleaner mister, able to clean up messes quickly. Synthesizes its own supply over time (if active)."
+ device = /obj/item/reagent_containers/spray/mister/janitor
+ volume = 100
+ active_power_cost = DEFAULT_CHARGE_DRAIN
+
+/obj/item/mod/module/mister/cleaner/Initialize(mapload)
+ . = ..()
+ reagents.flags = AMOUNT_VISIBLE
+ reagents.add_reagent(/datum/reagent/space_cleaner, volume)
+
+/obj/item/mod/module/mister/cleaner/on_active_process(seconds_per_tick)
+ var/refill_add = min(volume - reagents.total_volume, 2 * seconds_per_tick)
+ if(refill_add > 0)
+ reagents.add_reagent(/datum/reagent/space_cleaner, refill_add)
diff --git a/code/modules/pai/hud.dm b/code/modules/pai/hud.dm
index 77bcafefc82d2..cb7a5c9df390a 100644
--- a/code/modules/pai/hud.dm
+++ b/code/modules/pai/hud.dm
@@ -2,6 +2,7 @@
/atom/movable/screen/pai
icon = 'icons/hud/screen_pai.dmi'
+ mouse_over_pointer = MOUSE_HAND_POINTER
var/required_software
/atom/movable/screen/pai/Click()
diff --git a/code/modules/paperwork/fax.dm b/code/modules/paperwork/fax.dm
index 3199156106c05..e442005feb7f4 100644
--- a/code/modules/paperwork/fax.dm
+++ b/code/modules/paperwork/fax.dm
@@ -328,7 +328,10 @@ GLOBAL_VAR_INIT(nt_fax_department, pick("PA HR Department", "PA Legal Department
history_add("Send", params["name"])
GLOB.requests.fax_request(usr.client, "sent a fax message from [fax_name]/[fax_id] to [params["name"]]", fax_paper)
- to_chat(GLOB.admins, span_adminnotice("[icon2html(src.icon, GLOB.admins)]FAX REQUEST: [ADMIN_FULLMONTY(usr)]: [span_linkify("sent a fax message from [fax_name]/[fax_id][ADMIN_FLW(src)] to [html_encode(params["name"])]")] [ADMIN_SHOW_PAPER(fax_paper)] [ADMIN_PRINT_FAX(fax_paper, fax_name, params["id"])]"), confidential = TRUE)
+ to_chat(GLOB.admins,
+ span_adminnotice("[icon2html(src.icon, GLOB.admins)]FAX REQUEST: [ADMIN_FULLMONTY(usr)]: [span_linkify("sent a fax message from [fax_name]/[fax_id][ADMIN_FLW(src)] to [html_encode(params["name"])]")] [ADMIN_SHOW_PAPER(fax_paper)] [ADMIN_PRINT_FAX(fax_paper, fax_name, params["id"])]"),
+ type = MESSAGE_TYPE_PRAYER,
+ confidential = TRUE)
for(var/client/staff as anything in GLOB.admins)
if(staff?.prefs.read_preference(/datum/preference/toggle/comms_notification))
SEND_SOUND(staff, sound('sound/misc/server-ready.ogg'))
diff --git a/code/modules/point/point.dm b/code/modules/point/point.dm
index 683710bf128e4..98574373a816c 100644
--- a/code/modules/point/point.dm
+++ b/code/modules/point/point.dm
@@ -122,7 +122,15 @@
if(!(pointing_at in view(client.view, src)))
return FALSE
-
+ if(iscarbon(src)) // special interactions for carbons
+ var/mob/living/carbon/our_carbon = src
+ if(our_carbon.usable_hands <= 0 || src.incapacitated & INCAPABLE_RESTRAINTS || HAS_TRAIT(src, TRAIT_HANDS_BLOCKED))
+ if(TIMER_COOLDOWN_FINISHED(src, "point_verb_emote_cooldown"))
+ //cooldown handled in the emote.
+ our_carbon.emote("point [pointing_at]")
+ else
+ to_chat(src, span_warning("You need to wait before pointing again!"))
+ return FALSE
point_at(pointing_at, TRUE)
return TRUE
diff --git a/code/modules/power/lighting/light.dm b/code/modules/power/lighting/light.dm
index 2cc9464653da5..4d38d6944feeb 100644
--- a/code/modules/power/lighting/light.dm
+++ b/code/modules/power/lighting/light.dm
@@ -99,7 +99,7 @@
continue
if(on_turf.dir != dir)
continue
- stack_trace("Conflicting double stacked light [on_turf.type] found at ([our_location.x],[our_location.y],[our_location.z])")
+ stack_trace("Conflicting double stacked light [on_turf.type] found at [get_area(our_location)] ([our_location.x],[our_location.y],[our_location.z])")
qdel(on_turf)
if(!mapload) //sync up nightshift lighting for player made lights
@@ -222,6 +222,8 @@
var/color_set = bulb_colour
if(color)
color_set = color
+ if (cached_color_filter)
+ color_set = apply_matrix_to_color(color_set, cached_color_filter["color"], cached_color_filter["space"] || COLORSPACE_RGB)
if(reagents)
START_PROCESSING(SSmachines, src)
var/area/local_area = get_room_area()
diff --git a/code/modules/power/turbine/turbine.dm b/code/modules/power/turbine/turbine.dm
index e839800158f63..2112002e50745 100644
--- a/code/modules/power/turbine/turbine.dm
+++ b/code/modules/power/turbine/turbine.dm
@@ -1,11 +1,18 @@
+///Minimum pressure of gases pumped through the turbine
#define MINIMUM_TURBINE_PRESSURE 0.01
+///Returns the minimum pressure if it falls below the value
#define PRESSURE_MAX(value)(max((value), MINIMUM_TURBINE_PRESSURE))
+///Use emissive for overlays
+#define EMISSIVE_OVERLAY (1 << 0)
+///No turned off overlay
+#define NO_INACTIVE_OVERLAY (1 << 1)
/obj/machinery/power/turbine
+ icon = 'icons/obj/machines/engine/turbine.dmi'
density = TRUE
resistance_flags = FIRE_PROOF
can_atmos_pass = ATMOS_PASS_DENSITY
- processing_flags = NONE
+ processing_flags = START_PROCESSING_MANUALLY
///Checks if the machine is processing or not
var/active = FALSE
@@ -17,15 +24,8 @@
var/obj/item/turbine_parts/part_path
///The gas mixture this turbine part is storing
var/datum/gas_mixture/machine_gasmix
-
- ///Our overlay when active
- var/active_overlay = ""
- ///Our overlay when off
- var/off_overlay = ""
- ///Our overlay when open
- var/open_overlay = ""
- ///Should we use emissive appearance?
- var/emissive = FALSE
+ ///Flags for our overlays
+ var/overlay_flags = NONE
/obj/machinery/power/turbine/Initialize(mapload, gas_theoretical_volume)
. = ..()
@@ -59,41 +59,6 @@
deactivate_parts()
return ..()
-/**
- * Handles all the calculations needed for the gases, work done, temperature increase/decrease
- *
- * Arguments
- * * datum/gas_mixture/input_mix - the gas from the environment or from another part of the turbine
- * * datum/gas_mixture/output_mix - the gas that got pumped into this part from the input mix.
- * ideally should be same as input mix but varying texmperatur & pressures can cause varying results
- * * work_amount_to_remove - the amount of work to subtract from the actual work done to pump in the input mixture.
- * For e.g. if gas was transfered from the inlet compressor to the rotor we want to subtract the work done
- * by the inlet from the rotor to get the true work done
- * * intake_size - the percentage of gas to be fed into an turbine part, controlled by turbine computer for inlet compressor only
- */
-/obj/machinery/power/turbine/proc/transfer_gases(datum/gas_mixture/input_mix, datum/gas_mixture/output_mix, work_amount_to_remove, intake_size = 1)
- //pump gases. if no gases were transferred then no work was done
- var/output_pressure = PRESSURE_MAX(output_mix.return_pressure())
- var/datum/gas_mixture/transferred_gases = input_mix.pump_gas_to(output_mix, input_mix.return_pressure() * intake_size)
- if(!transferred_gases)
- return 0
-
- //compute work done
- var/work_done = QUANTIZE(transferred_gases.total_moles()) * R_IDEAL_GAS_EQUATION * transferred_gases.temperature * log((transferred_gases.volume * PRESSURE_MAX(transferred_gases.return_pressure())) / (output_mix.volume * output_pressure)) * TURBINE_WORK_CONVERSION_MULTIPLIER
- if(work_amount_to_remove)
- work_done = work_done - work_amount_to_remove
-
- //compute temperature & work from temperature if that is a lower value
- var/output_mix_heat_capacity = output_mix.heat_capacity()
- if(!output_mix_heat_capacity)
- return 0
- work_done = min(work_done, (output_mix_heat_capacity * output_mix.temperature - output_mix_heat_capacity * TCMB) / TURBINE_HEAT_CONVERSION_MULTIPLIER)
- output_mix.temperature = max((output_mix.temperature * output_mix_heat_capacity + work_done * TURBINE_HEAT_CONVERSION_MULTIPLIER) / output_mix_heat_capacity, TCMB)
- return work_done
-
-/obj/machinery/power/turbine/block_superconductivity()
- return TRUE
-
/obj/machinery/power/turbine/add_context(atom/source, list/context, obj/item/held_item, mob/user)
if(isnull(held_item))
return NONE
@@ -128,7 +93,7 @@
. = ..()
if(installed_part)
. += span_notice("Currently at tier [installed_part.current_tier].")
- if(installed_part.current_tier + 1 < installed_part.max_tier)
+ if(installed_part.current_tier + 1 < TURBINE_PART_TIER_FOUR)
. += span_notice("Can be upgraded by using a tier [installed_part.current_tier + 1] part.")
. += span_notice("The [installed_part.name] can be [EXAMINE_HINT("pried")] out.")
else
@@ -140,15 +105,54 @@
/obj/machinery/power/turbine/update_overlays()
. = ..()
+
if(panel_open)
- . += open_overlay
+ . += "[base_icon_state]_open"
if(active)
- . += active_overlay
- if(emissive)
- . += emissive_appearance(icon, active_overlay, src)
- else
- . += off_overlay
+ . += "[base_icon_state]_on"
+ if(overlay_flags & EMISSIVE_OVERLAY)
+ . += emissive_appearance(icon, "[base_icon_state]_on", src)
+ else if(!(overlay_flags & NO_INACTIVE_OVERLAY))
+ . += "[base_icon_state]_off"
+
+
+/**
+ * Handles all the calculations needed for the gases, work done, temperature increase/decrease
+ *
+ * Arguments
+ * * datum/gas_mixture/input_mix - the gas from the environment or from another part of the turbine
+ * * datum/gas_mixture/output_mix - the gas that got pumped into this part from the input mix.
+ * ideally should be same as input mix but varying texmperatur & pressures can cause varying results
+ * * work_amount_to_remove - the amount of work to subtract from the actual work done to pump in the input mixture.
+ * For e.g. if gas was transfered from the inlet compressor to the rotor we want to subtract the work done
+ * by the inlet from the rotor to get the true work done
+ * * intake_size - the percentage of gas to be fed into an turbine part, controlled by turbine computer for inlet compressor only
+ */
+/obj/machinery/power/turbine/proc/transfer_gases(datum/gas_mixture/input_mix, datum/gas_mixture/output_mix, work_amount_to_remove, intake_size = 1)
+ PROTECTED_PROC(TRUE)
+
+ //pump gases. if no gases were transferred then no work was done
+ var/output_pressure = PRESSURE_MAX(output_mix.return_pressure())
+ var/datum/gas_mixture/transferred_gases = input_mix.pump_gas_to(output_mix, input_mix.return_pressure() * intake_size)
+ if(!transferred_gases)
+ return 0
+
+ //compute work done
+ var/work_done = QUANTIZE(transferred_gases.total_moles()) * R_IDEAL_GAS_EQUATION * transferred_gases.temperature * log((transferred_gases.volume * PRESSURE_MAX(transferred_gases.return_pressure())) / (output_mix.volume * output_pressure)) * TURBINE_WORK_CONVERSION_MULTIPLIER
+ if(work_amount_to_remove)
+ work_done = work_done - work_amount_to_remove
+
+ //compute temperature & work from temperature if that is a lower value
+ var/output_mix_heat_capacity = output_mix.heat_capacity()
+ if(!output_mix_heat_capacity)
+ return 0
+ work_done = min(work_done, (output_mix_heat_capacity * output_mix.temperature - output_mix_heat_capacity * TCMB) / TURBINE_HEAT_CONVERSION_MULTIPLIER)
+ output_mix.temperature = max((output_mix.temperature * output_mix_heat_capacity + work_done * TURBINE_HEAT_CONVERSION_MULTIPLIER) / output_mix_heat_capacity, TCMB)
+ return work_done
+
+/obj/machinery/power/turbine/block_superconductivity()
+ return TRUE
/obj/machinery/power/turbine/screwdriver_act(mob/living/user, obj/item/tool)
. = ITEM_INTERACT_BLOCKING
@@ -165,7 +169,7 @@
deactivate_parts(user)
else
activate_parts(user)
- update_appearance()
+ update_appearance(UPDATE_OVERLAYS)
return ITEM_INTERACT_SUCCESS
@@ -229,22 +233,22 @@
if(gone == installed_part)
installed_part = null
-/obj/machinery/power/turbine/attackby(obj/item/turbine_parts/object, mob/user, params)
- //not the correct part
+/obj/machinery/power/turbine/item_interaction(mob/living/user, obj/item/turbine_parts/object, list/modifiers)
+ . = NONE
if(!istype(object, part_path))
return ..()
//not in a state to accep the part. return TRUE so we don't bash the machine and damage it
if(active)
balloon_alert(user, "turn off the machine first!")
- return TRUE
+ return ITEM_INTERACT_BLOCKING
if(!panel_open)
balloon_alert(user, "open the maintenance hatch first!")
- return TRUE
+ return ITEM_INTERACT_BLOCKING
//install the part
if(!do_after(user, 2 SECONDS, src))
- return TRUE
+ return ITEM_INTERACT_BLOCKING
if(installed_part)
user.put_in_hands(installed_part)
balloon_alert(user, "replaced part with the one in hand")
@@ -252,22 +256,21 @@
balloon_alert(user, "installed new part")
user.transferItemToLoc(object, src)
installed_part = object
- return TRUE
+ return ITEM_INTERACT_SUCCESS
/// Gets the efficiency of the installed part, returns 0 if no part is installed
/obj/machinery/power/turbine/proc/get_efficiency()
+ SHOULD_BE_PURE(TRUE)
+
return installed_part?.part_efficiency || 0
/obj/machinery/power/turbine/inlet_compressor
name = "inlet compressor"
desc = "The input side of a turbine generator, contains the compressor."
- icon = 'icons/obj/machines/engine/turbine.dmi'
icon_state = "inlet_compressor"
+ base_icon_state = "inlet"
circuit = /obj/item/circuitboard/machine/turbine_compressor
part_path = /obj/item/turbine_parts/compressor
- active_overlay = "inlet_animation"
- off_overlay = "inlet_off"
- open_overlay = "inlet_open"
/// The rotor this inlet is linked to
var/obj/machinery/power/turbine/core_rotor/rotor
@@ -296,6 +299,8 @@
* Returns temperature of the gas mix absorbed only if some work was done
*/
/obj/machinery/power/turbine/inlet_compressor/proc/compress_gases()
+ SHOULD_NOT_OVERRIDE(TRUE)
+
compressor_work = 0
compressor_pressure = MINIMUM_TURBINE_PRESSURE
if(QDELETED(input_turf))
@@ -314,16 +319,14 @@
return input_turf_mixture.temperature
+//===========================OUTLET==============================================
/obj/machinery/power/turbine/turbine_outlet
name = "turbine outlet"
desc = "The output side of a turbine generator, contains the turbine and the stator."
- icon = 'icons/obj/machines/engine/turbine.dmi'
icon_state = "turbine_outlet"
+ base_icon_state = "outlet"
circuit = /obj/item/circuitboard/machine/turbine_stator
part_path = /obj/item/turbine_parts/stator
- active_overlay = "outlet_animation"
- off_overlay = "outlet_off"
- open_overlay = "outlet_open"
/// The rotor this outlet is linked to
var/obj/machinery/power/turbine/core_rotor/rotor
@@ -343,6 +346,8 @@
/// push gases from its gas mix to output turf
/obj/machinery/power/turbine/turbine_outlet/proc/expel_gases()
+ SHOULD_NOT_OVERRIDE(TRUE)
+
if(QDELETED(output_turf))
output_turf = get_step(loc, dir)
//turf is blocked don't eject gases
@@ -358,18 +363,16 @@
//return ejected gases
return ejected_gases
+//===========================================CORE ROTOR=========================================
/obj/machinery/power/turbine/core_rotor
name = "core rotor"
desc = "The middle part of a turbine generator, contains the rotor and the main computer."
- icon = 'icons/obj/machines/engine/turbine.dmi'
icon_state = "core_rotor"
- active_overlay = "core_light"
- open_overlay = "core_open"
- active_power_usage = BASE_MACHINE_ACTIVE_CONSUMPTION
- emissive = TRUE
+ base_icon_state = "core"
can_change_cable_layer = TRUE
circuit = /obj/item/circuitboard/machine/turbine_rotor
part_path = /obj/item/turbine_parts/rotor
+ overlay_flags = EMISSIVE_OVERLAY | NO_INACTIVE_OVERLAY
///ID to easily connect the main part of the turbine to the computer
var/mapping_id
@@ -378,9 +381,9 @@
///Reference to the turbine
var/obj/machinery/power/turbine/turbine_outlet/turbine
///Rotation per minute the machine is doing
- var/rpm
+ var/rpm = 0
///Amount of power the machine is producing
- var/produced_energy
+ var/produced_energy = 0
///Check to see if all parts are connected to the core
var/all_parts_connected = FALSE
///Max rmp that the installed parts can handle, limits the rpms
@@ -462,16 +465,21 @@
//works same as regular left click
return multitool_act(user, tool)
-/// convinience proc for balloon alert which returns if viewer is null
+/**
+ * convinience proc for balloon alert which returns if viewer is null
+ * Arguments
+ *
+ * * mob/viewer - the player receiving the message
+ * * text - the message
+ */
/obj/machinery/power/turbine/core_rotor/proc/feedback(mob/viewer, text)
+ PRIVATE_PROC(TRUE)
+
if(isnull(viewer))
return
balloon_alert(viewer, text)
-/**
- * Called to activate the complete machine, checks for part presence, correct orientation and installed parts
- * Registers the input/output turfs
- */
+///Called to activate the complete machine, checks for part presence, correct orientation and installed parts
/obj/machinery/power/turbine/core_rotor/activate_parts(mob/user, check_only = FALSE)
//if this is not a checkup and all parts are connected then we have nothing to do
if(!check_only && all_parts_connected)
@@ -479,13 +487,18 @@
//locate compressor & turbine, when checking we simply check to see if they are still there
if(!check_only)
- compressor = locate(/obj/machinery/power/turbine/inlet_compressor) in get_step(src, REVERSE_DIR(dir))
- turbine = locate(/obj/machinery/power/turbine/turbine_outlet) in get_step(src, dir)
+ compressor = locate() in get_step(src, REVERSE_DIR(dir))
+ turbine = locate() in get_step(src, dir)
- //maybe look for them the other way around. we want the rotor to allign with them either way for player convinience
- if(!compressor && !turbine)
- compressor = locate(/obj/machinery/power/turbine/inlet_compressor) in get_step(src, dir)
- turbine = locate(/obj/machinery/power/turbine/turbine_outlet) in get_step(src, REVERSE_DIR(dir))
+ //maybe look for them the other way around. this means the rotor is facing the wrong way
+ if(QDELETED(compressor) && QDELETED(turbine))
+ compressor = locate() in get_step(src, dir)
+ turbine = locate() in get_step(src, REVERSE_DIR(dir))
+
+ //show corrective actions
+ if(!QDELETED(compressor) || !QDELETED(turbine))
+ feedback(user, "rotor is facing the wrong way!")
+ return (all_parts_connected = FALSE)
//sanity checks for compressor
if(QDELETED(compressor))
@@ -505,17 +518,17 @@
if(QDELETED(turbine))
feedback(user, "missing turbine!")
return (all_parts_connected = FALSE)
- if(turbine.dir != dir && turbine.dir != REVERSE_DIR(dir))
+ if(turbine.dir != dir && turbine.dir != REVERSE_DIR(dir)) //make sure it's not perpendicular to the rotor
feedback(user, "turbine not aligned with rotor!")
return (all_parts_connected = FALSE)
if(!turbine.can_connect)
- feedback(user, "turbine panel is either open or is misplaced!") //we say misplaced because can_connect becomes FALSE when this turbine is moved
+ feedback(user, "close turbine panel!") //we say misplaced because can_connect becomes FALSE when this turbine is moved
return (all_parts_connected = FALSE)
if(!turbine.installed_part)
feedback(user, "turbine is missing stator part!")
return (all_parts_connected = FALSE)
- //final sanity check to make sure turbine & compressor are facing the same direction. From an visual perspective they will appear facing away from each other actually. I know blame spriter's
+ //sanity check to make sure turbine & compressor are facing the same direction. From an visual perspective they will appear facing away from each other actually. I know blame spriter's
if(compressor.dir != turbine.dir)
feedback(user, "turbine & compressor are not facing away from each other!")
return (all_parts_connected = FALSE)
@@ -537,85 +550,60 @@
* Allows to null the various machines and references from the main core
*/
/obj/machinery/power/turbine/core_rotor/deactivate_parts()
- if(all_parts_connected)
- power_off()
+ toggle_power(force_off = TRUE)
compressor?.rotor = null
compressor = null
turbine?.rotor = null
turbine = null
all_parts_connected = FALSE
disconnect_from_network()
- SSair.stop_processing_machine(src)
/obj/machinery/power/turbine/core_rotor/on_deconstruction(disassembled)
deactivate_parts()
return ..()
/// Toggle power on and off, not safe
-/obj/machinery/power/turbine/core_rotor/proc/toggle_power()
- if(active)
- power_off()
- return
- power_on()
-
-/**
- * Activate all three parts, not safe, it assumes the machine already connected and properly working
- * It does a minimun check to ensure the parts still exist
- */
-/obj/machinery/power/turbine/core_rotor/proc/power_on()
- if(active || QDELETED(compressor) || QDELETED(turbine))
- return
- active = TRUE
- compressor.active = TRUE
- turbine.active = TRUE
- call_parts_update_appearance()
- SSair.start_processing_machine(src)
-
-/// Calls all parts update appearance proc.
-/obj/machinery/power/turbine/core_rotor/proc/call_parts_update_appearance()
- update_appearance()
- if(!QDELETED(compressor))
- compressor.update_appearance()
- if(!QDELETED(turbine))
- turbine.update_appearance()
+/obj/machinery/power/turbine/core_rotor/proc/toggle_power(force_off)
+ SHOULD_NOT_OVERRIDE(TRUE)
+
+ //toggle status
+ if(force_off)
+ if(!active) //was already off
+ return
+ active = FALSE
+ else
+ active = !active
-/**
- * Deactivate all three parts, not safe, it assumes the machine already connected and properly working
- * will try to turn off whatever components are left of this machine
- */
-/obj/machinery/power/turbine/core_rotor/proc/power_off()
- if(!active)
- return
- active = FALSE
+ //update operation status of parts
+ update_appearance(UPDATE_OVERLAYS)
if(!QDELETED(compressor))
- compressor.active = FALSE
+ compressor.active = active
+ compressor.update_appearance(UPDATE_OVERLAYS)
if(!QDELETED(turbine))
- turbine.active = FALSE
- call_parts_update_appearance()
- SSair.stop_processing_machine(src)
+ turbine.active = active
+ turbine.update_appearance(UPDATE_OVERLAYS)
-/// Returns true if all parts have their panel closed
-/obj/machinery/power/turbine/core_rotor/proc/all_parts_ready()
- if(QDELETED(compressor))
- return FALSE
- if(QDELETED(turbine))
- return FALSE
- return !panel_open && !compressor.panel_open && !turbine.panel_open
+ //start or stop processing
+ if(active)
+ update_mode_power_usage(ACTIVE_POWER_USE, active_power_usage)
+ begin_processing()
+ else
+ unset_static_power()
+ end_processing()
/// Getter for turbine integrity, return the amount in %
/obj/machinery/power/turbine/core_rotor/proc/get_turbine_integrity()
+ SHOULD_NOT_OVERRIDE(TRUE)
+
var/integrity = damage / 500
integrity = max(round(100 - integrity * 100, 0.01), 0)
return integrity
-/obj/machinery/power/turbine/core_rotor/process_atmos()
+/obj/machinery/power/turbine/core_rotor/process(seconds_per_tick)
if(!active || !activate_parts(check_only = TRUE) || (machine_stat & BROKEN) || !powered(ignore_use_power = TRUE))
- power_off()
+ deactivate_parts()
return PROCESS_KILL
- //use power to operate internal electronics & stuff
- update_mode_power_usage(ACTIVE_POWER_USE, active_power_usage)
-
//===============COMPRESSOR WORKING========//
//Transfer gases from turf to compressor
var/temperature = compressor.compress_gases()
@@ -668,9 +656,9 @@
work_done = max(work_done - compressor.compressor_work * TURBINE_COMPRESSOR_STATOR_INTERACTION_MULTIPLIER - turbine_work, 0)
//calculate final acheived rpm
rpm = ((work_done * compressor.get_efficiency()) ** turbine.get_efficiency()) * get_efficiency() / TURBINE_RPM_CONVERSION
- rpm = FLOOR(min(rpm, max_allowed_rpm), 1)
+ rpm = min(ROUND_UP(rpm), max_allowed_rpm)
//add energy into the grid, also use part of it for turbine operation
- produced_energy = rpm * TURBINE_ENERGY_RECTIFICATION_MULTIPLIER * TURBINE_RPM_CONVERSION
+ produced_energy = rpm * TURBINE_ENERGY_RECTIFICATION_MULTIPLIER * TURBINE_RPM_CONVERSION * seconds_per_tick
add_avail(produced_energy)
/obj/item/paper/guides/jobs/atmos/turbine
@@ -686,3 +674,5 @@
#undef PRESSURE_MAX
#undef MINIMUM_TURBINE_PRESSURE
+#undef EMISSIVE_OVERLAY
+#undef NO_INACTIVE_OVERLAY
diff --git a/code/modules/power/turbine/turbine_computer.dm b/code/modules/power/turbine/turbine_computer.dm
index 2ad777edd6228..7771bda03f8df 100644
--- a/code/modules/power/turbine/turbine_computer.dm
+++ b/code/modules/power/turbine/turbine_computer.dm
@@ -11,19 +11,17 @@
/obj/machinery/computer/turbine_computer/post_machine_initialize()
. = ..()
- locate_machinery()
-/obj/machinery/computer/turbine_computer/locate_machinery(multitool_connection)
if(!mapping_id)
return
for(var/obj/machinery/power/turbine/core_rotor/main as anything in SSmachines.get_machines_by_type_and_subtypes(/obj/machinery/power/turbine/core_rotor))
if(main.mapping_id != mapping_id)
continue
register_machine(main)
- return
+ break
-/obj/machinery/computer/turbine_computer/multitool_act(mob/living/user, obj/item/tool)
- var/obj/item/multitool/multitool = tool
+/obj/machinery/computer/turbine_computer/multitool_act(mob/living/user, obj/item/multitool/multitool)
+ . = ITEM_INTERACT_FAILURE
if(!istype(multitool.buffer, /obj/machinery/power/turbine/core_rotor))
to_chat(user, span_notice("Wrong machine type in [multitool] buffer..."))
return
@@ -31,12 +29,21 @@
to_chat(user, span_notice("Changing [src] bluespace network..."))
if(!do_after(user, 0.2 SECONDS, src))
return
+
playsound(get_turf(user), 'sound/machines/click.ogg', 10, TRUE)
register_machine(multitool.buffer)
to_chat(user, span_notice("You link [src] to the console in [multitool]'s buffer."))
- return TRUE
+ return ITEM_INTERACT_SUCCESS
+
+/**
+ * Links the rotor with this computer
+ * Arguments
+ *
+ * * obj/machinery/power/turbine/core_rotor/machine - the machine to link
+ */
+/obj/machinery/computer/turbine_computer/proc/register_machine(obj/machinery/power/turbine/core_rotor/machine)
+ PRIVATE_PROC(TRUE)
-/obj/machinery/computer/turbine_computer/proc/register_machine(machine)
turbine_core = WEAKREF(machine)
/obj/machinery/computer/turbine_computer/ui_interact(mob/user, datum/tgui/ui)
@@ -47,26 +54,27 @@
ui.open()
/obj/machinery/computer/turbine_computer/ui_data(mob/user)
- var/list/data = list()
+ . = list()
+ //do we have the main rotor with all parts connected
var/obj/machinery/power/turbine/core_rotor/main_control = turbine_core?.resolve()
- data["connected"] = !!QDELETED(main_control)
- if(!main_control)
+ if(QDELETED(main_control) || !main_control.all_parts_connected)
+ .["connected"] = FALSE
return
+ else
+ .["connected"] = TRUE
- data["active"] = main_control.active
- data["rpm"] = main_control.rpm ? main_control.rpm : 0
- data["power"] = main_control.produced_energy ? main_control.produced_energy : 0
- data["integrity"] = main_control.get_turbine_integrity()
- data["parts_linked"] = main_control.all_parts_connected
- data["parts_ready"] = main_control.all_parts_ready()
+ //operation status
+ .["active"] = main_control.active
+ .["rpm"] = main_control.rpm
+ .["power"] = energy_to_power(main_control.produced_energy)
+ .["integrity"] = main_control.get_turbine_integrity()
- data["max_rpm"] = main_control.max_allowed_rpm
- data["max_temperature"] = main_control.max_allowed_temperature
- data["temp"] = main_control.compressor?.input_turf?.air.temperature || 0
- data["regulator"] = QDELETED(main_control.compressor) ? 0 : main_control.compressor.intake_regulator
-
- return data
+ //running parameters
+ .["max_rpm"] = main_control.max_allowed_rpm
+ .["max_temperature"] = main_control.max_allowed_temperature
+ .["temp"] = main_control.compressor.input_turf?.air.temperature || 0
+ .["regulator"] = main_control.compressor.intake_regulator
/obj/machinery/computer/turbine_computer/ui_act(action, list/params, datum/tgui/ui, datum/ui_state/state)
. = ..()
@@ -76,19 +84,35 @@
switch(action)
if("toggle_power")
var/obj/machinery/power/turbine/core_rotor/main_control = turbine_core?.resolve()
- if(!main_control || !main_control.all_parts_connected || main_control.rpm > 1000)
- return TRUE
- if(!main_control.activate_parts(usr, check_only = TRUE))
- return TRUE
+ if(!main_control)
+ return FALSE
+
+ if(!main_control.active) //turning on the machine requires all part to be linked
+ if(!main_control.activate_parts(ui.user, check_only = TRUE))
+ return FALSE
+ else if(main_control.rpm > 1000) //turning off requires rpm to be less than 1000
+ return FALSE
+
main_control.toggle_power()
main_control.rpm = 0
main_control.produced_energy = 0
- . = TRUE
+ return TRUE
+
if("regulate")
- var/intake_size = text2num(params["regulate"])
+ var/intake_size = params["regulate"]
+ if(isnull(intake_size))
+ return FALSE
+
+ intake_size = text2num(intake_size)
+ if(isnull(intake_size))
+ return FALSE
+
var/obj/machinery/power/turbine/core_rotor/main_control = turbine_core?.resolve()
- if(intake_size == null || !main_control)
- return
- if(!QDELETED(main_control.compressor))
- main_control.compressor.intake_regulator = clamp(intake_size, 0.01, 1)
- . = TRUE
+ if(!main_control)
+ return FALSE
+
+ if(QDELETED(main_control.compressor))
+ return FALSE
+
+ main_control.compressor.intake_regulator = clamp(intake_size, 0.01, 1)
+ return TRUE
diff --git a/code/modules/power/turbine/turbine_parts.dm b/code/modules/power/turbine/turbine_parts.dm
index 4215fccf39fe0..e568f9e096469 100644
--- a/code/modules/power/turbine/turbine_parts.dm
+++ b/code/modules/power/turbine/turbine_parts.dm
@@ -1,3 +1,8 @@
+///String to access turbine part typepath to upgrade
+#define TURBINE_UPGRADE_PART "part"
+///String to access turbine part required amount to upgrade
+#define TURBINE_UPGRADE_AMOUNT "amount"
+
/obj/item/turbine_parts
name = "turbine parts"
desc = "you really should call an admin"
@@ -8,90 +13,62 @@
var/part_efficiency = 0
///Efficiency increase amount for each tier
var/part_efficiency_increase_amount = 0
-
///Current part tier
var/current_tier = TURBINE_PART_TIER_ONE
- ///Max part tier
- var/max_tier = TURBINE_PART_TIER_FOUR
-
- ///Stores the path of the material for the second tier upgrade
- var/obj/item/stack/sheet/second_tier_material = /obj/item/stack/sheet/plasteel
- ///Amount of second tier material for the upgrade
- var/second_tier_material_amount = 10
-
- ///Stores the path of the material for the third tier upgrade
- var/obj/item/stack/sheet/third_tier_material = /obj/item/stack/sheet/mineral/titanium
- ///Amount of third tier material for the upgrade
- var/third_tier_material_amount = 10
-
- ///Stores the path of the material for the fourth tier upgrade
- var/obj/item/stack/sheet/fourth_tier_material = /obj/item/stack/sheet/mineral/metal_hydrogen
- ///Amount of fourth tier material for the upgrade
- var/fourth_tier_material_amount = 5
-
///Max rpm reachable by the part
var/max_rpm = 35000
- ///Multiplier to increase the max rpm per tier, max should be around 500000 rpm
- var/max_rpm_tier_multiplier = 2.5
-
///Max temperature achievable by the part before the turbine starts to take damage
var/max_temperature = 50000
- ///Max temperature exponential value per tier
- var/max_temperature_tier_exponential = 1.2
/obj/item/turbine_parts/examine(mob/user)
. = ..()
- . += "This is a tier [current_tier] turbine part, rated for [max_rpm] rpm and [max_temperature] K."
- var/upgrade_material_name_amount
- switch(current_tier)
- if(TURBINE_PART_TIER_ONE)
- upgrade_material_name_amount = "[second_tier_material_amount] [initial(second_tier_material.name)] sheets"
- if(TURBINE_PART_TIER_TWO)
- upgrade_material_name_amount = "[third_tier_material_amount] [initial(third_tier_material.name)] sheets"
- if(TURBINE_PART_TIER_THREE)
- upgrade_material_name_amount = "[fourth_tier_material_amount] [initial(fourth_tier_material.name)] sheets"
+ . += span_notice("This is a tier [current_tier] turbine part, rated for [max_rpm] rpm and [max_temperature] K.")
- if(upgrade_material_name_amount)
- . += "Can be upgraded with [upgrade_material_name_amount]."
+ var/list/required_parts = get_tier_upgrades()
+ if(length(required_parts))
+ var/obj/item/stack/material = required_parts[TURBINE_UPGRADE_PART]
+ . += span_notice("Can be upgraded with [required_parts[TURBINE_UPGRADE_AMOUNT]] [initial(material.name)] sheets.")
else
- . += "Is already at max tier."
+ . += span_notice("Is already at max tier.")
+
+///Returns a list containing the typepath & amount of it required to upgrade to the next tier
+/obj/item/turbine_parts/proc/get_tier_upgrades()
+ PROTECTED_PROC(TRUE)
+ SHOULD_BE_PURE(TRUE)
+ RETURN_TYPE(/list)
-/obj/item/turbine_parts/attackby(obj/item/attacking_item, mob/user, params)
- if(current_tier >= max_tier)
- return FALSE
switch(current_tier)
if(TURBINE_PART_TIER_ONE)
- if(!istype(attacking_item, second_tier_material))
- return
- var/obj/item/stack/sheet/second_tier = attacking_item
- if(do_after(user, 1 SECONDS, src) && second_tier.use(second_tier_material_amount))
- current_tier = 2
- part_efficiency += part_efficiency_increase_amount
- max_rpm *= max_rpm_tier_multiplier
- max_temperature = max_temperature ** max_temperature_tier_exponential
- return TRUE
+ return list(TURBINE_UPGRADE_PART = /obj/item/stack/sheet/plasteel, TURBINE_UPGRADE_AMOUNT = 10)
if(TURBINE_PART_TIER_TWO)
- if(!istype(attacking_item, third_tier_material))
- return
- var/obj/item/stack/sheet/third_tier = attacking_item
- if(do_after(user, 2 SECONDS, src) && third_tier.use(third_tier_material_amount))
- current_tier = 3
- part_efficiency += part_efficiency_increase_amount
- max_rpm *= max_rpm_tier_multiplier
- max_temperature = max_temperature ** max_temperature_tier_exponential
- return TRUE
+ return list(TURBINE_UPGRADE_PART = /obj/item/stack/sheet/mineral/titanium, TURBINE_UPGRADE_AMOUNT = 10)
if(TURBINE_PART_TIER_THREE)
- if(!istype(attacking_item, fourth_tier_material))
- return
- var/obj/item/stack/sheet/fourth_tier = attacking_item
- if(do_after(user, 3 SECONDS, src) && fourth_tier.use(fourth_tier_material_amount))
- current_tier = 4
- part_efficiency += part_efficiency_increase_amount
- max_rpm *= max_rpm_tier_multiplier
- max_temperature = max_temperature ** max_temperature_tier_exponential
- return TRUE
-
- return ..()
+ return list(TURBINE_UPGRADE_PART = /obj/item/stack/sheet/mineral/metal_hydrogen, TURBINE_UPGRADE_AMOUNT = 5)
+
+/obj/item/turbine_parts/item_interaction(mob/living/user, obj/item/attacking_item, list/modifiers)
+ . = NONE
+
+ var/list/required_parts = get_tier_upgrades()
+ if(!length(required_parts))
+ balloon_alert(user, "already at max tier!")
+ return ITEM_INTERACT_FAILURE
+
+ var/obj/item/stack/sheet/material = attacking_item
+ if(!istype(material, required_parts[TURBINE_UPGRADE_PART]))
+ balloon_alert(user, "incorrect part!")
+ return ITEM_INTERACT_FAILURE
+
+ var/amount = required_parts[TURBINE_UPGRADE_AMOUNT]
+ if(material.amount < amount)
+ balloon_alert(user, "requires [amount] sheets!")
+ return ITEM_INTERACT_FAILURE
+
+ if(do_after(user, current_tier SECONDS, src) && material.use(amount))
+ current_tier += 1
+ part_efficiency += part_efficiency_increase_amount
+ max_rpm *= 2.5
+ max_temperature = max_temperature ** 1.2
+ return ITEM_INTERACT_SUCCESS
/obj/item/turbine_parts/compressor
name = "compressor part"
@@ -113,9 +90,15 @@
icon_state = "stator_part"
part_efficiency = 0.85
part_efficiency_increase_amount = 0.015
- second_tier_material = /obj/item/stack/sheet/mineral/titanium
- third_tier_material = /obj/item/stack/sheet/mineral/metal_hydrogen
- fourth_tier_material = /obj/item/stack/sheet/mineral/zaukerite
- second_tier_material_amount = 15
- third_tier_material_amount = 15
- fourth_tier_material_amount = 10
+
+/obj/item/turbine_parts/stator/get_tier_upgrades()
+ switch(current_tier)
+ if(TURBINE_PART_TIER_ONE)
+ return list(TURBINE_UPGRADE_PART = /obj/item/stack/sheet/mineral/titanium, TURBINE_UPGRADE_AMOUNT = 15)
+ if(TURBINE_PART_TIER_TWO)
+ return list(TURBINE_UPGRADE_PART = /obj/item/stack/sheet/mineral/metal_hydrogen, TURBINE_UPGRADE_AMOUNT = 15)
+ if(TURBINE_PART_TIER_THREE)
+ return list(TURBINE_UPGRADE_PART = /obj/item/stack/sheet/mineral/zaukerite, TURBINE_UPGRADE_AMOUNT = 10)
+
+#undef TURBINE_UPGRADE_PART
+#undef TURBINE_UPGRADE_AMOUNT
diff --git a/code/modules/projectiles/ammunition/_firing.dm b/code/modules/projectiles/ammunition/_firing.dm
index 5a704354ed3cb..25973e92e7a3a 100644
--- a/code/modules/projectiles/ammunition/_firing.dm
+++ b/code/modules/projectiles/ammunition/_firing.dm
@@ -61,6 +61,8 @@
loaded_projectile.damage *= gun.projectile_damage_multiplier * integrity_mult
loaded_projectile.stamina *= gun.projectile_damage_multiplier * integrity_mult
+ loaded_projectile.speed *= gun.projectile_speed_multiplier * integrity_mult
+
loaded_projectile.wound_bonus += gun.projectile_wound_bonus
loaded_projectile.wound_bonus *= loaded_projectile.wound_bonus >= 0 ? 1 : 2 - integrity_mult
loaded_projectile.bare_wound_bonus += gun.projectile_wound_bonus
diff --git a/code/modules/projectiles/ammunition/ballistic/revolver.dm b/code/modules/projectiles/ammunition/ballistic/revolver.dm
index 811b4309f5258..6e0c26af7359c 100644
--- a/code/modules/projectiles/ammunition/ballistic/revolver.dm
+++ b/code/modules/projectiles/ammunition/ballistic/revolver.dm
@@ -1,26 +1,26 @@
// .357 (Syndie Revolver)
-/obj/item/ammo_casing/a357
+/obj/item/ammo_casing/c357
name = ".357 bullet casing"
desc = "A .357 bullet casing."
caliber = CALIBER_357
- projectile_type = /obj/projectile/bullet/a357
+ projectile_type = /obj/projectile/bullet/c357
-/obj/item/ammo_casing/a357/spent
+/obj/item/ammo_casing/c357/spent
projectile_type = null
-/obj/item/ammo_casing/a357/match
+/obj/item/ammo_casing/c357/match
name = ".357 match bullet casing"
desc = "A .357 bullet casing, manufactured to exceedingly high standards."
- projectile_type = /obj/projectile/bullet/a357/match
+ projectile_type = /obj/projectile/bullet/c357/match
-/obj/item/ammo_casing/a357/phasic
+/obj/item/ammo_casing/c357/phasic
name = ".357 phasic bullet casing"
- projectile_type = /obj/projectile/bullet/a357/phasic
+ projectile_type = /obj/projectile/bullet/c357/phasic
-/obj/item/ammo_casing/a357/heartseeker
+/obj/item/ammo_casing/c357/heartseeker
name = ".357 heartseeker bullet casing"
- projectile_type = /obj/projectile/bullet/a357/heartseeker
+ projectile_type = /obj/projectile/bullet/c357/heartseeker
// 7.62x38mmR (Nagant Revolver)
@@ -53,6 +53,11 @@
desc = "A .38 rubber bullet casing, manufactured to exceedingly bouncy standards."
projectile_type = /obj/projectile/bullet/c38/match/bouncy
+/obj/item/ammo_casing/c38/match/true
+ name = ".38 True Strike bullet casing"
+ desc = "A .38 True Strike bullet casing."
+ projectile_type = /obj/projectile/bullet/c38/match/true
+
/obj/item/ammo_casing/c38/dumdum
name = ".38 DumDum bullet casing"
desc = "A .38 DumDum bullet casing."
@@ -67,3 +72,35 @@
name = ".38 Iceblox bullet casing"
desc = "A .38 Iceblox bullet casing."
projectile_type = /obj/projectile/bullet/c38/iceblox
+
+//gatfruit
+/obj/item/ammo_casing/pea
+ name = "pea bullet casing"
+ desc = "A bizarre pea bullet."
+ caliber = CALIBER_PEA
+ icon_state = "pea"
+ projectile_type = /obj/projectile/bullet/pea
+ /// Damage we achieve at 100 potency
+ var/max_damage = 15
+ /// Damage set by the plant
+ var/damage = 15 //max potency, is set
+
+/obj/item/ammo_casing/pea/Initialize(mapload)
+ . = ..()
+ create_reagents(60, SEALED_CONTAINER)
+
+/obj/item/ammo_casing/pea/ready_proj(atom/target, mob/living/user, quiet, zone_override, atom/fired_from)
+ . = ..()
+ if(isnull(loaded_projectile))
+ return
+ loaded_projectile.damage = damage
+
+/obj/item/ammo_casing/pea/attack_self(mob/user)
+ . = ..()
+ if(isnull(loaded_projectile))
+ return
+ var/obj/item/food/grown/peas/peas = new(user.drop_location())
+ user.put_in_hands(peas)
+ to_chat(user, span_notice("You separate [peas] from [src]."))
+ loaded_projectile = null
+ update_appearance()
diff --git a/code/modules/projectiles/boxes_magazines/ammo_boxes.dm b/code/modules/projectiles/boxes_magazines/ammo_boxes.dm
index bbd89389eb809..b664be74e87f3 100644
--- a/code/modules/projectiles/boxes_magazines/ammo_boxes.dm
+++ b/code/modules/projectiles/boxes_magazines/ammo_boxes.dm
@@ -2,7 +2,7 @@
name = "speed loader (.357)"
desc = "Designed to quickly reload revolvers."
icon_state = "357"
- ammo_type = /obj/item/ammo_casing/a357
+ ammo_type = /obj/item/ammo_casing/c357
max_ammo = 7
caliber = CALIBER_357
multiple_sprites = AMMO_BOX_PER_BULLET
@@ -13,13 +13,13 @@
/obj/item/ammo_box/a357/match
name = "speed loader (.357 Match)"
desc = "Designed to quickly reload revolvers. These rounds are manufactured within extremely tight tolerances, making them easy to show off trickshots with."
- ammo_type = /obj/item/ammo_casing/a357/match
+ ammo_type = /obj/item/ammo_casing/c357/match
ammo_band_color = "#77828a"
/obj/item/ammo_box/a357/phasic
name = "speed loader (.357 Phasic)"
desc = "Designed to quickly reload revolvers. Holds phasic ammo, also known as 'Ghost Lead', allowing it to pass through non-organic material."
- ammo_type = /obj/item/ammo_casing/a357/phasic
+ ammo_type = /obj/item/ammo_casing/c357/phasic
ammo_band_color = "#693a6a"
/obj/item/ammo_box/a357/heartseeker
@@ -27,7 +27,7 @@
desc = "Designed to quickly reload revolvers. Holds heartseeker ammo, which veers into targets with exceptional precision using \
an unknown method. It apparently predicts movement using neural pulses in the brain, but that's less marketable. \
As seen in the hit NTFlik horror-space western film, Forget-Me-Not! Brought to you by Roseus Galactic!"
- ammo_type = /obj/item/ammo_casing/a357/heartseeker
+ ammo_type = /obj/item/ammo_casing/c357/heartseeker
ammo_band_color = "#a91e1e"
/obj/item/ammo_box/c38
@@ -60,6 +60,12 @@
ammo_type = /obj/item/ammo_casing/c38/match/bouncy
ammo_band_color = "#556696"
+/obj/item/ammo_box/c38/true
+ name = "speed loader (.38 True Strike)"
+ desc = "Designed to quickly reload revolvers. Bullets bounce towards new targets with surprising accuracy."
+ ammo_type = /obj/item/ammo_casing/c38/match/true
+ ammo_band_color = "#d647b0"
+
/obj/item/ammo_box/c38/dumdum
name = "speed loader (.38 DumDum)"
desc = "Designed to quickly reload revolvers. These rounds expand on impact, allowing them to shred the target and cause massive bleeding. Very weak against armor and distant targets."
diff --git a/code/modules/projectiles/boxes_magazines/external/rifle.dm b/code/modules/projectiles/boxes_magazines/external/rifle.dm
index 882fefedec111..96916fe9bb5ad 100644
--- a/code/modules/projectiles/boxes_magazines/external/rifle.dm
+++ b/code/modules/projectiles/boxes_magazines/external/rifle.dm
@@ -21,3 +21,66 @@
/obj/item/ammo_box/magazine/m223/phasic
name = "toploader magazine (.223 Phasic)"
ammo_type = /obj/item/ammo_casing/a223/phasic
+
+// .38 (Battle Rifle) //
+
+/obj/item/ammo_box/magazine/m38
+ name = "battle rifle magazine (.38)"
+ desc = "A magazine for a BR-38 battle rifle."
+ icon_state = "38mag"
+ base_icon_state = "38mag"
+ w_class = WEIGHT_CLASS_NORMAL
+ ammo_type = /obj/item/ammo_casing/c38
+ caliber = CALIBER_38
+ max_ammo = 15
+ ammo_band_icon = "+38mag_ammo_band"
+ ammo_band_color = null
+
+/obj/item/ammo_box/magazine/m38/update_icon_state()
+ . = ..()
+ icon_state = "[base_icon_state][ammo_count() ? "-ammo" : ""]"
+
+/obj/item/ammo_box/magazine/m38/empty
+ start_empty = TRUE
+
+/obj/item/ammo_box/magazine/m38/trac
+ name = "battle rifle magazine (.38 TRAC)"
+ desc = "A magazine for a BR-38 battle rifle. TRAC bullets embed a tracking implant within the target's body and are entirely nonlethal."
+ ammo_type = /obj/item/ammo_casing/c38/trac
+ ammo_band_color = "#7b6383"
+
+/obj/item/ammo_box/magazine/m38/match
+ name = "battle rifle magazine (.38 Match)"
+ desc = "A magazine for a BR-38 battle rifle. These rounds are manufactured within extremely tight tolerances, making them easy to show off trickshots with."
+ ammo_type = /obj/item/ammo_casing/c38/match
+ ammo_band_color = "#7b6383"
+
+/obj/item/ammo_box/magazine/m38/match/bouncy
+ name = "battle rifle magazine (.38 Rubber)"
+ desc = "A magazine for a BR-38 battle rifle. These rounds are incredibly bouncy and MOSTLY nonlethal, making them great to show off trickshots with."
+ ammo_type = /obj/item/ammo_casing/c38/match/bouncy
+ ammo_band_color = "#556696"
+
+/obj/item/ammo_box/magazine/m38/true
+ name = "battle rifle magazine (.38 True Strike)"
+ desc = "A magazine for a BR-38 battle rifle. Bullets bounce towards new targets with surprising accuracy."
+ ammo_type = /obj/item/ammo_casing/c38/match/true
+ ammo_band_color = "#d647b0"
+
+/obj/item/ammo_box/magazine/m38/dumdum
+ name = "battle rifle magazine (.38 DumDum)"
+ desc = "A magazine for a BR-38 battle rifle. These rounds expand on impact, allowing them to shred the target and cause massive bleeding. Very weak against armor and distant targets."
+ ammo_type = /obj/item/ammo_casing/c38/dumdum
+ ammo_band_color = "#969578"
+
+/obj/item/ammo_box/magazine/m38/hotshot
+ name = "battle rifle magazine (.38 Hot Shot)"
+ desc = "A magazine for a BR-38 battle rifle. Hot Shot bullets contain an incendiary payload."
+ ammo_type = /obj/item/ammo_casing/c38/hotshot
+ ammo_band_color = "#805a57"
+
+/obj/item/ammo_box/magazine/m38/iceblox
+ name = "battle rifle magazine (.38 Iceblox)"
+ desc = "A magazine for a BR-38 battle rifle. Iceblox bullets contain a cryogenic payload."
+ ammo_type = /obj/item/ammo_casing/c38/iceblox
+ ammo_band_color = "#658e94"
diff --git a/code/modules/projectiles/boxes_magazines/internal/_cylinder.dm b/code/modules/projectiles/boxes_magazines/internal/_cylinder.dm
index 28df0262352d1..7f467881a9d61 100644
--- a/code/modules/projectiles/boxes_magazines/internal/_cylinder.dm
+++ b/code/modules/projectiles/boxes_magazines/internal/_cylinder.dm
@@ -1,6 +1,6 @@
/obj/item/ammo_box/magazine/internal/cylinder
name = "revolver cylinder"
- ammo_type = /obj/item/ammo_casing/a357
+ ammo_type = /obj/item/ammo_casing/c357
caliber = CALIBER_357
max_ammo = 7
diff --git a/code/modules/projectiles/boxes_magazines/internal/revolver.dm b/code/modules/projectiles/boxes_magazines/internal/revolver.dm
index e74a192d6900f..1e891abeef280 100644
--- a/code/modules/projectiles/boxes_magazines/internal/revolver.dm
+++ b/code/modules/projectiles/boxes_magazines/internal/revolver.dm
@@ -12,7 +12,7 @@
/obj/item/ammo_box/magazine/internal/cylinder/rus357
name = "\improper Russian revolver cylinder"
- ammo_type = /obj/item/ammo_casing/a357
+ ammo_type = /obj/item/ammo_casing/c357
caliber = CALIBER_357
max_ammo = 6
multiload = FALSE
@@ -21,5 +21,11 @@
/obj/item/ammo_box/magazine/internal/cylinder/rus357/Initialize(mapload)
. = ..()
for (var/i in 1 to max_ammo - 1)
- stored_ammo += new /obj/item/ammo_casing/a357/spent(src)
- stored_ammo += new /obj/item/ammo_casing/a357(src)
+ stored_ammo += new /obj/item/ammo_casing/c357/spent(src)
+ stored_ammo += new /obj/item/ammo_casing/c357(src)
+
+/obj/item/ammo_box/magazine/internal/cylinder/peashooter
+ name = "peashooter cylinder"
+ ammo_type = /obj/item/ammo_casing/pea
+ caliber = CALIBER_PEA
+ max_ammo = 7
diff --git a/code/modules/projectiles/gun.dm b/code/modules/projectiles/gun.dm
index d3b0782036b2b..76cf2d1811302 100644
--- a/code/modules/projectiles/gun.dm
+++ b/code/modules/projectiles/gun.dm
@@ -54,6 +54,10 @@
/// Even snowflakier way to modify projectile wounding bonus/potential for projectiles fired from this gun.
var/projectile_wound_bonus = 0
+ /// The most reasonable way to modify projectile speed values for projectile fired from this gun. Honest.
+ /// Lower values are better, higher values are worse.
+ var/projectile_speed_multiplier = 1
+
var/spread = 0 //Spread induced by the gun itself.
var/randomspread = 1 //Set to 0 for shotguns. This is used for weapons that don't fire all their bullets at once.
@@ -74,8 +78,9 @@
/obj/item/gun/Initialize(mapload)
. = ..()
- if(pin)
- pin = new pin(src)
+ if(ispath(pin))
+ pin = new pin
+ pin.gun_insert(new_gun = src)
add_seclight_point()
add_bayonet_point()
@@ -597,7 +602,8 @@
/obj/item/gun/proc/unlock() //used in summon guns and as a convience for admins
if(pin)
qdel(pin)
- pin = new /obj/item/firing_pin
+ var/obj/item/firing_pin/new_pin = new
+ new_pin.gun_insert(new_gun = src)
//Happens before the actual projectile creation
/obj/item/gun/proc/before_firing(atom/target,mob/user)
diff --git a/code/modules/projectiles/guns/ballistic/automatic.dm b/code/modules/projectiles/guns/ballistic/automatic.dm
index 1c158cf4a87da..89c6deaa4d2d8 100644
--- a/code/modules/projectiles/guns/ballistic/automatic.dm
+++ b/code/modules/projectiles/guns/ballistic/automatic.dm
@@ -352,3 +352,196 @@
actions_types = list()
fire_sound = 'sound/items/weapons/laser.ogg'
casing_ejector = FALSE
+
+// NT Battle Rifle //
+
+/obj/item/gun/ballistic/automatic/battle_rifle
+ name = "\improper NT BR-38 battle rifle"
+ desc = "Nanotrasen's prototype security weapon, found exclusively in the hands of their private security teams. Chambered in .38 pistol rounds. \
+ Ignore that this makes it technically a carbine. And that it functions as a designated marksman rifle. Marketing weren't being very co-operative \
+ when it came time to name the gun. That, and the endless arguments in board rooms about exactly what designation the gun is meant to be."
+ icon = 'icons/obj/weapons/guns/wide_guns.dmi'
+ icon_state = "battle_rifle"
+ inhand_icon_state = "battle_rifle"
+ base_icon_state = "battle_rifle"
+ worn_icon = 'icons/mob/clothing/back.dmi'
+ worn_icon_state = "battle_rifle"
+ slot_flags = ITEM_SLOT_BACK
+
+ weapon_weight = WEAPON_HEAVY
+ accepted_magazine_type = /obj/item/ammo_box/magazine/m38
+ w_class = WEIGHT_CLASS_BULKY
+ force = 15 //this thing is kind of oversized, okay?
+ mag_display = TRUE
+ projectile_damage_multiplier = 1.2
+ projectile_speed_multiplier = 1.2
+ fire_delay = 2
+ burst_size = 1
+ actions_types = list()
+ spread = 10 //slightly inaccurate in burst fire mode, mostly important for long range shooting
+ fire_sound = 'sound/items/weapons/thermalpistol.ogg'
+ suppressor_x_offset = 8
+
+ /// Determines how many shots we can make before the weapon needs to be maintained.
+ var/shots_before_degradation = 10
+ /// The max number of allowed shots this gun can have before degradation.
+ var/max_shots_before_degradation = 10
+ /// Determines the degradation stage. The higher the value, the more poorly the weapon performs.
+ var/degradation_stage = 0
+ /// Maximum degradation stage.
+ var/degradation_stage_max = 5
+ /// The probability of degradation increasing per shot.
+ var/degradation_probability = 10
+ /// The maximum speed malus for projectile flight speed. Projectiles probably shouldn't move too slowly or else they will start to cause problems.
+ var/maximum_speed_malus = 0.7
+ /// What is our damage multiplier if the gun is emagged?
+ var/emagged_projectile_damage_multiplier = 1.6
+
+ /// Whether or not our gun is suffering an EMP related malfunction.
+ var/emp_malfunction = FALSE
+
+ /// Our timer for when our gun is suffering an extreme malfunction. AKA it is going to explode
+ var/explosion_timer
+
+ SET_BASE_PIXEL(-8, 0)
+
+/obj/item/gun/ballistic/automatic/battle_rifle/Initialize(mapload)
+ . = ..()
+ AddComponent(/datum/component/scope, range_modifier = 2)
+ register_context()
+
+/obj/item/gun/ballistic/automatic/battle_rifle/add_context(atom/source, list/context, obj/item/held_item, mob/user)
+ . = ..()
+
+ if(held_item?.tool_behaviour == TOOL_MULTITOOL && shots_before_degradation < max_shots_before_degradation)
+ context[SCREENTIP_CONTEXT_LMB] = "Reset System"
+ return CONTEXTUAL_SCREENTIP_SET
+
+/obj/item/gun/ballistic/automatic/battle_rifle/examine_more(mob/user)
+ . = ..()
+ . += span_notice("Looking down at the [name], you recall something you read in a promotional pamphlet... ")
+
+ . += span_info("The BR-38 possesses an acceleration rail that launches bullets at higher than typical velocity.\
+ This allows even less powerful cartridges to put out significant amounts of stopping power.")
+
+ . += span_notice("However, you also remember some of the rumors... ")
+
+ . += span_notice("In a sour twist of irony for Nanotrasen's historical issues with ballistics-based security weapons, the BR-38 has one significant flaw. \
+ It is possible for the weapon to suffer from unintended discombulations due to closed heat distribution systems should the weapon be tampered with. \
+ R&D are working on this issue before the weapon sees commercial sales. That, and trying to work out why the weapon's onboard computation systems suffer \
+ from so many calculation errors.")
+
+/obj/item/gun/ballistic/automatic/battle_rifle/examine(mob/user)
+ . = ..()
+ if(shots_before_degradation)
+ . += span_notice("[src] can fire [shots_before_degradation] more times before risking system degradation.")
+ else
+ . += span_notice("[src] is in the process of system degradation. It is currently at stage [degradation_stage] of [degradation_stage_max]. Use a multitool on [src] to recalibrate. Alternatively, insert it into a weapon recharger.")
+
+/obj/item/gun/ballistic/automatic/battle_rifle/update_icon_state()
+ . = ..()
+ if(!shots_before_degradation)
+ inhand_icon_state = "[base_icon_state]-empty"
+ else
+ inhand_icon_state = "[base_icon_state]"
+
+/obj/item/gun/ballistic/automatic/battle_rifle/update_overlays()
+ . = ..()
+ if(degradation_stage)
+ . += "[base_icon_state]_empty"
+ else if(shots_before_degradation)
+ var/ratio_for_overlay = CEILING(clamp(shots_before_degradation / max_shots_before_degradation, 0, 1) * 3, 1)
+ . += "[icon_state]_stage_[ratio_for_overlay]"
+
+/obj/item/gun/ballistic/automatic/battle_rifle/emp_act(severity)
+ . = ..()
+ if (!(. & EMP_PROTECT_SELF) && prob(50 / severity))
+ shots_before_degradation = 0
+ emp_malfunction = TRUE
+ attempt_degradation(TRUE)
+
+/obj/item/gun/ballistic/automatic/battle_rifle/emag_act(mob/user, obj/item/card/emag/emag_card)
+ . = ..()
+ if(obj_flags & EMAGGED)
+ return FALSE
+ obj_flags |= EMAGGED
+ projectile_damage_multiplier = emagged_projectile_damage_multiplier
+ balloon_alert(user, "heat distribution systems deactivated")
+ return TRUE
+
+/obj/item/gun/ballistic/automatic/battle_rifle/multitool_act(mob/living/user, obj/item/tool)
+ if(!tool.use_tool(src, user, 20 SECONDS, volume = 50))
+ balloon_alert(user, "interrupted!")
+ return ITEM_INTERACT_BLOCKING
+
+ emp_malfunction = FALSE
+ shots_before_degradation = initial(shots_before_degradation)
+ degradation_stage = initial(degradation_stage)
+ projectile_speed_multiplier = initial(projectile_speed_multiplier)
+ fire_delay = initial(fire_delay)
+ update_appearance()
+ balloon_alert(user, "system reset")
+ return ITEM_INTERACT_SUCCESS
+
+/obj/item/gun/ballistic/automatic/battle_rifle/try_fire_gun(atom/target, mob/living/user, params)
+ . = ..()
+ if(!chambered || (chambered && !chambered.loaded_projectile))
+ return
+
+ if(shots_before_degradation)
+ shots_before_degradation --
+ return
+
+ else if ((obj_flags & EMAGGED) && degradation_stage == degradation_stage_max && !explosion_timer)
+ perform_extreme_malfunction(user)
+
+ else
+ attempt_degradation(FALSE)
+
+
+/obj/item/gun/ballistic/automatic/battle_rifle/process_fire(atom/target, mob/living/user, message = TRUE, params = null, zone_override = "", bonus_spread = 0)
+ if(chambered.loaded_projectile && prob(75) && (emp_malfunction || degradation_stage == degradation_stage_max))
+ balloon_alert_to_viewers("*click*")
+ playsound(src, dry_fire_sound, dry_fire_sound_volume, TRUE)
+ return
+
+ return ..()
+
+/// Proc to handle weapon degradation. Called when attempting to fire or immediately after an EMP takes place.
+/obj/item/gun/ballistic/automatic/battle_rifle/proc/attempt_degradation(force_increment = FALSE)
+ if(!prob(degradation_probability) && !force_increment || degradation_stage == degradation_stage_max)
+ return //Only update if we actually increment our degradation stage
+
+ degradation_stage = clamp(degradation_stage + (obj_flags & EMAGGED ? 2 : 1), 0, degradation_stage_max)
+ projectile_speed_multiplier = clamp(initial(projectile_speed_multiplier) + degradation_stage * 0.1, initial(projectile_speed_multiplier), maximum_speed_malus)
+ fire_delay = initial(fire_delay) + (degradation_stage * 0.5)
+ do_sparks(1, TRUE, src)
+ update_appearance()
+
+/// Called by /obj/machinery/recharger while inserted: attempts to recalibrate our gun but reducing degradation.
+/obj/item/gun/ballistic/automatic/battle_rifle/proc/attempt_recalibration(restoring_shots_before_degradation = FALSE, recharge_rate = 1)
+ emp_malfunction = FALSE
+
+ if(restoring_shots_before_degradation)
+ shots_before_degradation = clamp(round(shots_before_degradation + recharge_rate, 1), 0, max_shots_before_degradation)
+
+ else
+ degradation_stage = clamp(degradation_stage - 1, 0, degradation_stage_max)
+ if(degradation_stage)
+ projectile_speed_multiplier = clamp(initial(projectile_speed_multiplier) - degradation_stage * 0.1, maximum_speed_malus, initial(projectile_speed_multiplier))
+ fire_delay = initial(fire_delay) + (degradation_stage * 0.5)
+ else
+ projectile_speed_multiplier = initial(projectile_speed_multiplier)
+ fire_delay = initial(fire_delay)
+
+ update_appearance()
+
+/// Proc to handle the countdown for our detonation
+/obj/item/gun/ballistic/automatic/battle_rifle/proc/perform_extreme_malfunction(mob/living/user)
+ balloon_alert(user, "gun is exploding, throw it!")
+ explosion_timer = addtimer(CALLBACK(src, PROC_REF(fucking_explodes_you)), 5 SECONDS, (TIMER_UNIQUE|TIMER_OVERRIDE))
+ playsound(src, 'sound/items/weapons/gun/general/empty_alarm.ogg', 50, FALSE)
+
+/// proc to handle our detonation
+/obj/item/gun/ballistic/automatic/battle_rifle/proc/fucking_explodes_you()
+ explosion(src, devastation_range = 1, heavy_impact_range = 3, light_impact_range = 6, explosion_cause = src)
diff --git a/code/modules/projectiles/guns/ballistic/revolver.dm b/code/modules/projectiles/guns/ballistic/revolver.dm
index bb589bc011a14..6fe97628fb072 100644
--- a/code/modules/projectiles/guns/ballistic/revolver.dm
+++ b/code/modules/projectiles/guns/ballistic/revolver.dm
@@ -318,3 +318,9 @@
clumsy_check = FALSE
icon_state = "mateba"
+/obj/item/gun/ballistic/revolver/peashooter
+ name = "peashooter"
+ icon_state = "peashooter"
+ desc = "A wild plantlife mutation that shoots hardened peas. Incredible."
+ fire_sound = 'sound/items/weapons/peashoot.ogg'
+ accepted_magazine_type = /obj/item/ammo_box/magazine/internal/cylinder/peashooter
diff --git a/code/modules/projectiles/guns/special/blastcannon.dm b/code/modules/projectiles/guns/special/blastcannon.dm
index befb622251a48..80bf245aa3305 100644
--- a/code/modules/projectiles/guns/special/blastcannon.dm
+++ b/code/modules/projectiles/guns/special/blastcannon.dm
@@ -49,8 +49,6 @@
/obj/item/gun/blastcannon/Initialize(mapload)
. = ..()
- if(!pin)
- pin = new
RegisterSignal(src, COMSIG_ATOM_INTERNAL_EXPLOSION, PROC_REF(channel_blastwave))
AddElement(/datum/element/update_icon_updates_onmob)
diff --git a/code/modules/projectiles/pins.dm b/code/modules/projectiles/pins.dm
index 7ee44a10e7d83..46345e769e4bd 100644
--- a/code/modules/projectiles/pins.dm
+++ b/code/modules/projectiles/pins.dm
@@ -20,11 +20,6 @@
var/pin_removable = TRUE
var/obj/item/gun/gun
-/obj/item/firing_pin/New(newloc)
- ..()
- if(isgun(newloc))
- gun = newloc
-
/obj/item/firing_pin/interact_with_atom(atom/interacting_with, mob/living/user, list/modifiers)
if(!isgun(interacting_with))
return NONE
@@ -58,8 +53,8 @@
balloon_alert(user, "authentication checks overridden")
return TRUE
-/obj/item/firing_pin/proc/gun_insert(mob/living/user, obj/item/gun/G)
- gun = G
+/obj/item/firing_pin/proc/gun_insert(mob/living/user, obj/item/gun/new_gun)
+ gun = new_gun
forceMove(gun)
gun.pin = src
return TRUE
@@ -165,9 +160,9 @@
return TRUE //The clown op leader antag datum isn't a subtype of the normal clown op antag datum.
return FALSE
-/obj/item/firing_pin/clown/ultra/gun_insert(mob/living/user, obj/item/gun/G)
+/obj/item/firing_pin/clown/ultra/gun_insert(mob/living/user, obj/item/gun/new_gun)
..()
- G.clumsy_check = FALSE
+ new_gun.clumsy_check = FALSE
/obj/item/firing_pin/clown/ultra/gun_remove(mob/living/user)
gun.clumsy_check = initial(gun.clumsy_check)
@@ -244,14 +239,15 @@
if(pin_owner)
. += span_notice("This firing pin is currently authorized to pay into the account of [pin_owner.account_holder].")
-/obj/item/firing_pin/paywall/gun_insert(mob/living/user, obj/item/gun/G)
+/obj/item/firing_pin/paywall/gun_insert(mob/living/user, obj/item/gun/new_gun)
if(!pin_owner)
- to_chat(user, span_warning("ERROR: Please swipe valid identification card before installing firing pin!"))
- user.put_in_hands(src)
+ if(isnull(user))
+ forceMove(new_gun.drop_location())
+ else
+ to_chat(user, span_warning("ERROR: Please swipe valid identification card before installing firing pin!"))
+ user.put_in_hands(src)
return FALSE
- gun = G
- forceMove(gun)
- gun.pin = src
+ ..()
if(multi_payment)
gun.desc += span_notice(" This [gun.name] has a per-shot cost of [payment_amount] credit[( payment_amount > 1 ) ? "s" : ""].")
return TRUE
@@ -260,7 +256,7 @@
/obj/item/firing_pin/paywall/gun_remove(mob/living/user)
- gun.desc = initial(desc)
+ gun.desc = gun::desc
..()
/obj/item/firing_pin/paywall/attackby(obj/item/M, mob/living/user, params)
diff --git a/code/modules/projectiles/projectile.dm b/code/modules/projectiles/projectile.dm
index 44498febc402d..1a21ac4f82d89 100644
--- a/code/modules/projectiles/projectile.dm
+++ b/code/modules/projectiles/projectile.dm
@@ -906,6 +906,7 @@
* Normal behavior moves projectiles in a straight line through tiles, but it gets trickier with homing.
* Every pixels_per_decisecond we will stop and call process_homing(), which while a bit rough, does not have a significant performance impact
* This proc needs to be very performant, so do not add overridable logic that can be handled in homing or animations here.
+ * Return is how many tiles we've actually passed (or attempted to pass, if we ended up on a half-move)
*
* pixels_to_move determines how many pixels the projectile should move
* hitscan prevents animation logic from running
@@ -913,8 +914,9 @@
*/
/obj/projectile/proc/process_movement(pixels_to_move, hitscan = FALSE, tile_limit = FALSE)
if (!isturf(loc) || !movement_vector)
- return
+ return 0
var/total_move_distance = pixels_to_move
+ var/movements_done = 0
last_projectile_move = world.time
while (pixels_to_move > 0 && isturf(loc) && !QDELETED(src) && !deletion_queued)
// Because pixel_x/y represents offset and not actual visual position of the projectile, we add 16 pixels to each and cut the excess because projectiles are not meant to be highly offset by default
@@ -949,7 +951,7 @@
if (distance_to_border == INFINITY)
stack_trace("WARNING: Projectile had an empty movement vector and tried to process")
qdel(src)
- return
+ return movements_done
var/distance_to_move = min(distance_to_border, pixels_to_move)
// For homing we cap the maximum distance to move every loop
@@ -971,14 +973,14 @@
// We've hit an invalid turf, end of a z level or smth went wrong
if (!istype(new_turf))
qdel(src)
- return
+ return movements_done
// Move to the next tile
step_towards(src, new_turf)
SEND_SIGNAL(src, COMSIG_PROJECTILE_MOVE_PROCESS_STEP)
// We hit something and got deleted, stop the loop
if (QDELETED(src))
- return
+ return movements_done
if (loc != new_turf)
moving_turfs = FALSE
// If we've impacted something, we need to animate our movement until the actual hit
@@ -988,12 +990,13 @@
// to move in the next turf to get from entry to impact position
delete_distance = distance_to_move + sqrt((impact_x - entry_x) ** 2 + (impact_y - entry_y) ** 2)
+ movements_done += 1
// We cannot move more than one turf worth of distance per loop, so this is a safe solution
pixels_moved_last_tile += distance_to_move
if (!deletion_queued && pixels_moved_last_tile >= ICON_SIZE_ALL)
reduce_range()
if (QDELETED(src))
- return
+ return movements_done
// Similarly with range out deletion, need to calculate how many pixels we can actually move before deleting
if (deletion_queued)
delete_distance = distance_to_move - (ICON_SIZE_ALL - pixels_moved_last_tile)
@@ -1019,7 +1022,7 @@
if (!move_animate(delete_x, delete_y, animate_time, deleting = TRUE))
animate(src, pixel_x = delete_x, pixel_y = delete_y, time = animate_time, flags = ANIMATION_PARALLEL | ANIMATION_CONTINUE)
animate(alpha = 0, time = 0, flags = ANIMATION_CONTINUE)
- return
+ return movements_done
pixels_to_move -= distance_to_move
// animate() instantly changes pixel_x/y values and just interpolates them client-side so next loop processes properly
@@ -1039,16 +1042,18 @@
// We've hit a timestop field, abort any remaining movement
if (paused)
- return
+ return movements_done
// Prevents long-range high-speed projectiles from ruining the server performance by moving 100 tiles per tick when subsystem is set to a high cap
if (TICK_CHECK)
// If we ran out of time, add whatever distance we're yet to pass to overrun debt to be processed next tick and break the loop
overrun += pixels_to_move
- return
+ return movements_done
if (tile_limit && moving_turfs)
- return
+ return movements_done
+
+ return movements_done
/// Called every time projectile animates its movement, in case child wants to have custom animations.
/// Returning TRUE cancels normal animation
@@ -1060,8 +1065,8 @@
if(!homing_target)
return
var/datum/point/new_point = RETURN_PRECISE_POINT(homing_target)
- new_point.x += clamp(homing_offset_x, 1, world.maxx)
- new_point.y += clamp(homing_offset_y, 1, world.maxy)
+ new_point.pixel_x += homing_offset_x
+ new_point.pixel_y += homing_offset_y
var/new_angle = closer_angle_difference(angle, angle_between_points(RETURN_PRECISE_POINT(src), new_point))
set_angle(angle + clamp(new_angle, -homing_turn_speed, homing_turn_speed))
diff --git a/code/modules/projectiles/projectile/bullets/revolver.dm b/code/modules/projectiles/projectile/bullets/revolver.dm
index e9d6389c89d0d..6513be9cfb7c8 100644
--- a/code/modules/projectiles/projectile/bullets/revolver.dm
+++ b/code/modules/projectiles/projectile/bullets/revolver.dm
@@ -48,7 +48,6 @@
name = ".38 Rubber bullet"
damage = 10
stamina = 30
- weak_against_armour = TRUE
ricochets_max = 6
ricochet_incidence_leeway = 0
ricochet_chance = 130
@@ -57,6 +56,16 @@
sharpness = NONE
embed_type = null
+/obj/projectile/bullet/c38/match/true
+ name = ".38 True Strike bullet"
+ damage = 15
+ ricochet_auto_aim_range = 3
+ ricochet_auto_aim_angle = 100
+ ricochet_incidence_leeway = 0
+ ricochet_shoots_firer = FALSE
+ shrapnel_type = null
+ embed_type = null
+
// premium .38 ammo from cargo, weak against armor, lower base damage, but excellent at embedding and causing slice wounds at close range
/obj/projectile/bullet/c38/dumdum
name = ".38 DumDum bullet"
@@ -106,9 +115,9 @@
/obj/projectile/bullet/c38/hotshot/on_hit(atom/target, blocked = 0, pierce_hit)
. = ..()
if(iscarbon(target))
- var/mob/living/carbon/M = target
- M.adjust_fire_stacks(6)
- M.ignite_mob()
+ var/mob/living/carbon/criminal_scum = target
+ criminal_scum.adjust_fire_stacks(6)
+ criminal_scum.ignite_mob()
/obj/projectile/bullet/c38/iceblox //see /obj/projectile/temp for the original code
name = ".38 Iceblox bullet"
@@ -119,24 +128,24 @@
/obj/projectile/bullet/c38/iceblox/on_hit(atom/target, blocked = 0, pierce_hit)
. = ..()
if(isliving(target))
- var/mob/living/M = target
- M.adjust_bodytemperature(((100-blocked)/100)*(temperature - M.bodytemperature))
+ var/mob/living/criminal_scum = target
+ criminal_scum.adjust_bodytemperature(((100-blocked)/100)*(temperature - criminal_scum.bodytemperature))
// .357 (Syndie Revolver)
-/obj/projectile/bullet/a357
+/obj/projectile/bullet/c357
name = ".357 bullet"
damage = 60
wound_bonus = -30
-/obj/projectile/bullet/a357/phasic
+/obj/projectile/bullet/c357/phasic
name = ".357 phasic bullet"
icon_state = "gaussphase"
damage = 35
armour_penetration = 100
projectile_phasing = PASSTABLE | PASSGLASS | PASSGRILLE | PASSCLOSEDTURF | PASSMACHINE | PASSSTRUCTURE | PASSDOORS
-/obj/projectile/bullet/a357/heartseeker
+/obj/projectile/bullet/c357/heartseeker
name = ".357 heartseeker bullet"
icon_state = "gauss"
damage = 50
@@ -144,7 +153,7 @@
homing_turn_speed = 120
// admin only really, for ocelot memes
-/obj/projectile/bullet/a357/match
+/obj/projectile/bullet/c357/match
name = ".357 match bullet"
ricochets_max = 5
ricochet_chance = 140
@@ -152,3 +161,32 @@
ricochet_auto_aim_range = 6
ricochet_incidence_leeway = 80
ricochet_decay_chance = 1
+
+//gatfruit
+/obj/projectile/bullet/pea
+ name = "pea bullet"
+ damage = 15
+ weak_against_armour = TRUE
+ ricochets_max = 3
+ ricochet_chance = 100
+ icon_state = "pea"
+
+/obj/projectile/bullet/pea/Initialize(mapload)
+ . = ..()
+ create_reagents(100, NO_REACT) //same as the fruit itself, wont ever hit that much though i believe
+
+/obj/projectile/bullet/pea/on_hit(mob/living/carbon/target, blocked = 0, pierce_hit)
+ if(istype(target) && blocked != 100)
+ if(iszombie(target)) // https://www.youtube.com/watch?v=ssZoq1eUK-s
+ target.adjustBruteLoss(15)
+ if(target.can_inject(target_zone = def_zone)) // Pass the hit zone to see if it can inject by whether it hit the head or the body.
+ ..()
+ reagents.trans_to(target, reagents.total_volume, methods = INJECT)
+ return BULLET_ACT_HIT
+ blocked = 100
+ target.visible_message(span_danger("\The [src] is deflected!"), span_userdanger("You are protected against \the [src]!"))
+ . = ..()
+ if(reagents & NO_REACT) //first impact on a noncarbon
+ reagents.flags &= ~(NO_REACT)
+ reagents.handle_reactions()
+
diff --git a/code/modules/projectiles/projectile/magic.dm b/code/modules/projectiles/projectile/magic.dm
index 1755d43de1f6b..c7f457ee427a1 100644
--- a/code/modules/projectiles/projectile/magic.dm
+++ b/code/modules/projectiles/projectile/magic.dm
@@ -37,7 +37,7 @@
if(isliving(target))
var/mob/living/victim = target
if(victim.mob_biotypes & MOB_UNDEAD) //negative energy heals the undead
- if(victim.revive(ADMIN_HEAL_ALL, force_grab_ghost = TRUE)) // This heals suicides
+ if(victim.revive(ADMIN_HEAL_ALL & ~HEAL_REFRESH_ORGANS , force_grab_ghost = TRUE)) // This heals suicides
victim.grab_ghost(force = TRUE)
to_chat(victim, span_notice("You rise with a start, you're undead!!!"))
else if(victim.stat != DEAD)
@@ -68,7 +68,7 @@
victim.death()
return
- if(victim.revive(ADMIN_HEAL_ALL, force_grab_ghost = TRUE)) // This heals suicides
+ if(victim.revive(ADMIN_HEAL_ALL & ~HEAL_REFRESH_ORGANS , force_grab_ghost = TRUE)) // This heals suicides
to_chat(victim, span_notice("You rise with a start, you're alive!!!"))
else if(victim.stat != DEAD)
to_chat(victim, span_notice("You feel great!"))
diff --git a/code/modules/reagents/chemistry/machinery/reagentgrinder.dm b/code/modules/reagents/chemistry/machinery/reagentgrinder.dm
index 7bf1c06541f38..a009ab35dd1f3 100644
--- a/code/modules/reagents/chemistry/machinery/reagentgrinder.dm
+++ b/code/modules/reagents/chemistry/machinery/reagentgrinder.dm
@@ -9,7 +9,6 @@
circuit = /obj/item/circuitboard/machine/reagentgrinder
pass_flags = PASSTABLE
resistance_flags = ACID_PROOF
- interaction_flags_machine = parent_type::interaction_flags_machine | INTERACT_MACHINE_OFFLINE
anchored_tabletop_offset = 8
/// The maximum weight of items this grinder can hold
@@ -273,45 +272,36 @@
return NONE
/obj/machinery/reagentgrinder/wrench_act(mob/living/user, obj/item/tool)
- if(user.combat_mode)
- return NONE
+ . = NONE
- var/tool_result = ITEM_INTERACT_BLOCKING
if(operating)
balloon_alert(user, "still operating!")
- return tool_result
+ return ITEM_INTERACT_BLOCKING
if(default_unfasten_wrench(user, tool) == SUCCESSFUL_UNFASTEN)
update_appearance(UPDATE_OVERLAYS)
- tool_result = ITEM_INTERACT_SUCCESS
- return tool_result
+ return ITEM_INTERACT_SUCCESS
/obj/machinery/reagentgrinder/screwdriver_act(mob/living/user, obj/item/tool)
- if(user.combat_mode)
- return NONE
+ . = NONE
- var/tool_result = ITEM_INTERACT_BLOCKING
if(operating)
balloon_alert(user, "still operating!")
- return tool_result
+ return ITEM_INTERACT_BLOCKING
if(default_deconstruction_screwdriver(user, icon_state, icon_state, tool))
update_appearance(UPDATE_OVERLAYS)
- tool_result = ITEM_INTERACT_SUCCESS
- return tool_result
+ return ITEM_INTERACT_SUCCESS
/obj/machinery/reagentgrinder/crowbar_act(mob/living/user, obj/item/tool)
- if(user.combat_mode)
- return NONE
+ . = NONE
- var/tool_result = ITEM_INTERACT_BLOCKING
if(operating)
balloon_alert(user, "still operating!")
- return tool_result
+ return ITEM_INTERACT_BLOCKING
if(default_deconstruction_crowbar(tool))
- tool_result = ITEM_INTERACT_SUCCESS
- return tool_result
+ return ITEM_INTERACT_SUCCESS
/obj/machinery/reagentgrinder/proc/on_storage_dump(datum/source, datum/storage/storage, mob/user)
SIGNAL_HANDLER
@@ -328,9 +318,7 @@
/obj/machinery/reagentgrinder/attack_hand_secondary(mob/user, list/modifiers)
. = ..()
- if(. == SECONDARY_ATTACK_CANCEL_ATTACK_CHAIN)
- return
- if(operating || !can_interact(user) || !user.can_perform_action(src, ALLOW_SILICON_REACH | FORBID_TELEKINESIS_REACH))
+ if(. == SECONDARY_ATTACK_CANCEL_ATTACK_CHAIN || !check_interactable(user))
return
replace_beaker(user)
return SECONDARY_ATTACK_CANCEL_ATTACK_CHAIN
@@ -342,13 +330,11 @@
return attack_hand_secondary(user, modifiers)
/obj/machinery/reagentgrinder/ui_interact(mob/user)
- . = ..()
-
- //some interaction sanity checks
- if(!anchored || operating || !can_interact(user) || !user.can_perform_action(src, ALLOW_SILICON_REACH | FORBID_TELEKINESIS_REACH))
+ //sanity check
+ if(!user.can_perform_action(src, ALLOW_SILICON_REACH | FORBID_TELEKINESIS_REACH))
return
+
var/static/radial_eject = image(icon = 'icons/hud/radial.dmi', icon_state = "radial_eject")
- var/static/radial_mix = image(icon = 'icons/hud/radial.dmi', icon_state = "radial_mix")
//create list of options available
var/list/options = list()
@@ -357,7 +343,7 @@
if((to_process in component_parts) || to_process == beaker)
continue
- if(!QDELETED(beaker) && !beaker.reagents.holder_full() && is_operational && anchored)
+ if(is_operational && anchored && !QDELETED(beaker) && !beaker.reagents.holder_full())
var/static/radial_grind = image(icon = 'icons/hud/radial.dmi', icon_state = "radial_grind")
options["grind"] = radial_grind
@@ -366,16 +352,17 @@
options["eject"] = radial_eject
break
+
//eject action if we have a beaker
if(!QDELETED(beaker))
options["eject"] = radial_eject
//mix reagents present inside
- if(beaker?.reagents.total_volume && is_operational && anchored)
+ if(is_operational && anchored && beaker.reagents.total_volume)
+ var/static/radial_mix = image(icon = 'icons/hud/radial.dmi', icon_state = "radial_mix")
options["mix"] = radial_mix
+
//examine action if Ai is trying to see whats up
if(HAS_AI_ACCESS(user))
- if(machine_stat & NOPOWER)
- return
var/static/radial_examine = image(icon = 'icons/hud/radial.dmi', icon_state = "radial_examine")
options["examine"] = radial_examine
@@ -389,16 +376,18 @@
)
if(!choice)
return
+
+ //act on choice
switch(choice)
if("eject")
replace_beaker(user)
dump_inventory_contents()
if("grind", "juice")
- operate_for(60 DECISECONDS, choice == "juice", user)
+ operate_for(6 SECONDS, choice == "juice", user)
if("mix")
- mix(50 DECISECONDS, user)
+ mix(5 SECONDS, user)
if("examine")
- to_chat(user, examine_block(span_infoplain("[examine(user)]")))
+ to_chat(user, examine_block(jointext(examine(user), "\n")))
/**
* Checks if the radial menu can interact with this machine
@@ -409,13 +398,7 @@
/obj/machinery/reagentgrinder/proc/check_interactable(mob/user)
PRIVATE_PROC(TRUE)
- if(!can_interact(user))
- return FALSE
-
- if(!anchored || operating || !user.can_perform_action(src, ALLOW_SILICON_REACH))
- return FALSE
-
- return TRUE
+ return !operating && user.can_perform_action(src, ALLOW_SILICON_REACH | FORBID_TELEKINESIS_REACH)
/**
* Grinds/Juices all contents inside the grinder
@@ -428,10 +411,14 @@
/obj/machinery/reagentgrinder/proc/operate_for(time, juicing = FALSE, mob/user)
PRIVATE_PROC(TRUE)
+ if(!anchored || !is_operational || QDELETED(beaker) || beaker.reagents.holder_full())
+ operating = FALSE
+ return
+ operating = TRUE
+
var/duration = time / speed
Shake(pixelshiftx = 1, pixelshifty = 0, duration = duration)
- operating = TRUE
if(!juicing)
playsound(src, 'sound/machines/blender.ogg', 50, TRUE)
else
@@ -486,10 +473,15 @@
/obj/machinery/reagentgrinder/proc/mix(time, mob/user)
PRIVATE_PROC(TRUE)
+ if(!anchored || !is_operational || QDELETED(beaker) || !beaker.reagents.total_volume)
+ operating = FALSE
+ return
+ operating = TRUE
+
var/duration = time / speed
Shake(pixelshiftx = 1, pixelshifty = 0, duration = duration)
- operating = TRUE
+
playsound(src, 'sound/machines/juicer.ogg', 20, TRUE)
addtimer(CALLBACK(src, PROC_REF(mix_complete), duration), duration)
@@ -503,8 +495,7 @@
/obj/machinery/reagentgrinder/proc/mix_complete(duration)
PRIVATE_PROC(TRUE)
- if(QDELETED(beaker) || beaker.reagents.total_volume <= 0)
- operating = FALSE
+ if(QDELETED(src) || !is_operational)
return
//Recipe to make Butter
@@ -516,13 +507,12 @@
tasty_butter.reagents.set_all_reagents_purity(purity)
//Recipe to make Mayonnaise
- if (beaker.reagents.has_reagent(/datum/reagent/consumable/eggyolk))
- beaker.reagents.convert_reagent(/datum/reagent/consumable/eggyolk, /datum/reagent/consumable/mayonnaise)
+ beaker.reagents.convert_reagent(/datum/reagent/consumable/eggyolk, /datum/reagent/consumable/mayonnaise)
//Recipe to make whipped cream
- if (beaker.reagents.has_reagent(/datum/reagent/consumable/cream))
- beaker.reagents.convert_reagent(/datum/reagent/consumable/cream, /datum/reagent/consumable/whipped_cream)
+ beaker.reagents.convert_reagent(/datum/reagent/consumable/cream, /datum/reagent/consumable/whipped_cream)
//power consumed based on the ratio of total reagents mixed
use_energy((active_power_usage * (duration / 1 SECONDS)) * (beaker.reagents.total_volume / beaker.reagents.maximum_volume))
+
operating = FALSE
diff --git a/code/modules/reagents/chemistry/reagents/cat2_medicine_reagents.dm b/code/modules/reagents/chemistry/reagents/cat2_medicine_reagents.dm
index 1f0df3ca51a89..4b4843c4dc908 100644
--- a/code/modules/reagents/chemistry/reagents/cat2_medicine_reagents.dm
+++ b/code/modules/reagents/chemistry/reagents/cat2_medicine_reagents.dm
@@ -77,7 +77,7 @@
if(3) //VICTORY ROYALE
to_chat(affected_mob, span_hierophant("You win, and the malevolent spirits fade away as well as your wounds."))
affected_mob.client.give_award(/datum/award/achievement/jobs/helbitaljanken, affected_mob)
- affected_mob.revive(HEAL_ALL)
+ affected_mob.revive(HEAL_ALL & ~HEAL_REFRESH_ORGANS)
holder.del_reagent(type)
return
diff --git a/code/modules/reagents/chemistry/reagents/drug_reagents.dm b/code/modules/reagents/chemistry/reagents/drug_reagents.dm
index 28c299c87f0af..4a4bf137b3c4d 100644
--- a/code/modules/reagents/chemistry/reagents/drug_reagents.dm
+++ b/code/modules/reagents/chemistry/reagents/drug_reagents.dm
@@ -865,3 +865,54 @@
)
new /obj/structure/bouncy_castle(gored.loc, gored)
gored.gib()
+
+/datum/reagent/drug/syndol
+ name = "Syndol"
+ description = "A potent and addictive hallucinogen used by syndicate agents disorient certain targets. \
+ It is said that the hallucinations it causes are tailored to the user's fears, but tests have been inconclusive, \
+ with subjects in security and assistants reporting wildly different experiences."
+ color = "#c90000"
+ taste_description = "metallic"
+ ph = 7
+ overdose_threshold = 10
+ chemical_flags = REAGENT_CAN_BE_SYNTHESIZED
+ addiction_types = list(/datum/addiction/hallucinogens = 20)
+ /// Track the active hallucination we're giving out so we don't replace it by accident
+ VAR_PRIVATE/datum/weakref/active_hallucination_weakref
+
+/datum/reagent/drug/syndol/on_mob_life(mob/living/carbon/affected_mob, seconds_per_tick, times_fired)
+ . = ..()
+ var/obj/item/organ/liver = affected_mob.get_organ_slot(ORGAN_SLOT_LIVER)
+ if(isnull(liver) || !(liver.organ_flags & affected_organ_flags))
+ return
+ // non-trivial but not immediately dangerous liver damage
+ liver.apply_organ_damage(0.5 * REM * seconds_per_tick)
+ // anti-hallucinogens can counteract the effects
+ if(HAS_TRAIT(affected_mob, TRAIT_HALLUCINATION_IMMUNE) || affected_mob.reagents.has_reagent(/datum/reagent/medicine/haloperidol, amount = 3, needs_metabolizing = TRUE))
+ QDEL_NULL(active_hallucination_weakref)
+ return
+
+ // and the main event, funny hallucinations
+ if(active_hallucination_weakref?.resolve())
+ return
+ var/greatest_fear
+ if(HAS_TRAIT(liver, TRAIT_LAW_ENFORCEMENT_METABOLISM))
+ greatest_fear = /datum/hallucination/delusion/preset/syndies
+ else if(HAS_TRAIT(liver, TRAIT_MAINTENANCE_METABOLISM) || HAS_TRAIT(liver, TRAIT_COMEDY_METABOLISM))
+ greatest_fear = /datum/hallucination/delusion/preset/seccies
+
+ if(greatest_fear)
+ // 5 minutes = 15 units, roughly. we cancel the hallucination early when we exit the mob, anyway
+ active_hallucination_weakref = WEAKREF(affected_mob.cause_hallucination(greatest_fear, name, duration = 5 MINUTES, skip_nearby = !overdosed))
+ else
+ // if they're just some random schmuck, give them random hallucinations
+ affected_mob.adjust_hallucinations_up_to(4 SECONDS * REM * seconds_per_tick, 20 SECONDS)
+
+/datum/reagent/drug/syndol/on_mob_end_metabolize(mob/living/affected_mob)
+ . = ..()
+ affected_mob.adjust_hallucinations(-16 SECONDS)
+ QDEL_NULL(active_hallucination_weakref)
+
+/datum/reagent/drug/syndol/overdose_start(mob/living/affected_mob)
+ // no message, just refresh the hallucination
+ QDEL_NULL(active_hallucination_weakref)
diff --git a/code/modules/reagents/chemistry/reagents/medicine_reagents.dm b/code/modules/reagents/chemistry/reagents/medicine_reagents.dm
index 9a169257e1c0c..a60b4c171b703 100644
--- a/code/modules/reagents/chemistry/reagents/medicine_reagents.dm
+++ b/code/modules/reagents/chemistry/reagents/medicine_reagents.dm
@@ -43,7 +43,7 @@
chemical_flags = REAGENT_DEAD_PROCESS
metabolized_traits = list(TRAIT_ANALGESIA)
/// Flags to fullheal every metabolism tick
- var/full_heal_flags = ~(HEAL_BRUTE|HEAL_BURN|HEAL_TOX|HEAL_RESTRAINTS|HEAL_REFRESH_ORGANS)
+ var/full_heal_flags = ~(HEAL_BRUTE|HEAL_BURN|HEAL_TOX|HEAL_RESTRAINTS|HEAL_ORGANS)
// The best stuff there is. For testing/debugging.
/datum/reagent/medicine/adminordrazine/on_hydroponics_apply(obj/machinery/hydroponics/mytray, mob/user)
@@ -76,7 +76,7 @@
name = "Quantum Medicine"
description = "Rare and experimental particles, that apparently swap the user's body with one from an alternate dimension where it's completely healthy."
taste_description = "science"
- full_heal_flags = ~(HEAL_ADMIN|HEAL_BRUTE|HEAL_BURN|HEAL_TOX|HEAL_RESTRAINTS|HEAL_ALL_REAGENTS|HEAL_REFRESH_ORGANS)
+ full_heal_flags = ~(HEAL_ADMIN|HEAL_BRUTE|HEAL_BURN|HEAL_TOX|HEAL_RESTRAINTS|HEAL_ALL_REAGENTS|HEAL_ORGANS)
/datum/reagent/medicine/synaptizine
name = "Synaptizine"
diff --git a/code/modules/reagents/chemistry/reagents/other_reagents.dm b/code/modules/reagents/chemistry/reagents/other_reagents.dm
index 3137453d2e987..4444ef843c8cf 100644
--- a/code/modules/reagents/chemistry/reagents/other_reagents.dm
+++ b/code/modules/reagents/chemistry/reagents/other_reagents.dm
@@ -2227,13 +2227,13 @@
/datum/reagent/colorful_reagent/on_mob_life(mob/living/carbon/affected_mob, seconds_per_tick, times_fired)
. = ..()
if(can_colour_mobs)
- affected_mob.add_atom_colour(pick(random_color_list), WASHABLE_COLOUR_PRIORITY)
+ affected_mob.add_atom_colour(color_transition_filter(pick(random_color_list), SATURATION_OVERRIDE), WASHABLE_COLOUR_PRIORITY)
/// Colors anything it touches a random color.
/datum/reagent/colorful_reagent/expose_atom(atom/exposed_atom, reac_volume)
. = ..()
if(!isliving(exposed_atom) || can_colour_mobs)
- exposed_atom.add_atom_colour(pick(random_color_list), WASHABLE_COLOUR_PRIORITY)
+ exposed_atom.add_atom_colour(color_transition_filter(pick(random_color_list), SATURATION_OVERRIDE), WASHABLE_COLOUR_PRIORITY)
/datum/reagent/hair_dye
name = "Quantum Hair Dye"
diff --git a/code/modules/reagents/chemistry/reagents/toxin_reagents.dm b/code/modules/reagents/chemistry/reagents/toxin_reagents.dm
index 53aad2e05d7b0..22c5902ad4136 100644
--- a/code/modules/reagents/chemistry/reagents/toxin_reagents.dm
+++ b/code/modules/reagents/chemistry/reagents/toxin_reagents.dm
@@ -1435,3 +1435,9 @@
SIGNAL_HANDLER
if(current_cycle > 28 && !HAS_TRAIT(source, TRAIT_TETRODOTOXIN_HEALING))
return COMSIG_CARBON_BLOCK_BREATH
+
+/datum/reagent/toxin/gatfruit
+ name = "Phytotoxin"
+ description = "A poison produced by the rare and elusive gatfruit plant."
+ liver_damage_multiplier = 0
+ toxpwr = 1
diff --git a/code/modules/reagents/reagent_containers/cups/_cup.dm b/code/modules/reagents/reagent_containers/cups/_cup.dm
index 3f83bc122282e..08ef730011f64 100644
--- a/code/modules/reagents/reagent_containers/cups/_cup.dm
+++ b/code/modules/reagents/reagent_containers/cups/_cup.dm
@@ -28,7 +28,7 @@
. = ..()
if(drink_type)
var/list/types = bitfield_to_list(drink_type, FOOD_FLAGS)
- . += span_notice("It is [LOWER_TEXT(english_list(types))].")
+ . += span_notice("The label says it contains [LOWER_TEXT(english_list(types))] ingredients.")
/**
* Checks if the mob actually liked drinking this cup.
diff --git a/code/modules/reagents/reagent_containers/cups/bottle.dm b/code/modules/reagents/reagent_containers/cups/bottle.dm
index 6e960f5cf54ef..dbf88ff7e4302 100644
--- a/code/modules/reagents/reagent_containers/cups/bottle.dm
+++ b/code/modules/reagents/reagent_containers/cups/bottle.dm
@@ -37,6 +37,11 @@
desc = "A small bottle of spewium."
list_reagents = list(/datum/reagent/toxin/spewium = 30)
+/obj/item/reagent_containers/cup/bottle/syndol
+ name = "syndol bottle"
+ desc = "A small bottle of syndol."
+ list_reagents = list(/datum/reagent/drug/syndol = 30)
+
/obj/item/reagent_containers/cup/bottle/morphine
name = "morphine bottle"
desc = "A small bottle of morphine."
diff --git a/code/modules/reagents/reagent_containers/cups/glassbottle.dm b/code/modules/reagents/reagent_containers/cups/glassbottle.dm
index 5712d383f0b57..90cc93e54111f 100644
--- a/code/modules/reagents/reagent_containers/cups/glassbottle.dm
+++ b/code/modules/reagents/reagent_containers/cups/glassbottle.dm
@@ -21,7 +21,6 @@
var/broken_inhand_icon_state = "broken_beer"
lefthand_file = 'icons/mob/inhands/items/drinks_lefthand.dmi'
righthand_file = 'icons/mob/inhands/items/drinks_righthand.dmi'
- drink_type = ALCOHOL
age_restricted = TRUE // wrryy can't set an init value to see if drink_type contains ALCOHOL so here we go
///Directly relates to the 'knockdown' duration. Lowered by armor (i.e. helmets)
var/bottle_knockdown_duration = BOTTLE_KNOCKDOWN_DEFAULT_DURATION
@@ -308,6 +307,7 @@
desc = "Brewed with \"Pure Ice Asteroid Spring Water\"."
icon_state = "litebeer"
list_reagents = list(/datum/reagent/consumable/ethanol/beer/light = 30)
+ drink_type = ALCOHOL
/obj/item/reagent_containers/cup/glass/bottle/rootbeer
name = "Two-Time root beer"
@@ -333,47 +333,53 @@
desc = "A bottle of high quality gin, produced in the New London Space Station."
icon_state = "ginbottle"
list_reagents = list(/datum/reagent/consumable/ethanol/gin = 100)
+ drink_type = ALCOHOL
/obj/item/reagent_containers/cup/glass/bottle/whiskey
name = "Uncle Git's special reserve"
desc = "A premium single-malt whiskey, gently matured inside the tunnels of a nuclear shelter. TUNNEL WHISKEY RULES."
icon_state = "whiskeybottle"
list_reagents = list(/datum/reagent/consumable/ethanol/whiskey = 100)
+ drink_type = ALCOHOL
/obj/item/reagent_containers/cup/glass/bottle/kong
name = "Kong"
desc = "Makes You Go Ape!®"
list_reagents = list(/datum/reagent/consumable/ethanol/whiskey/kong = 100)
+ drink_type = ALCOHOL
/obj/item/reagent_containers/cup/glass/bottle/candycornliquor
name = "candy corn liquor"
desc = "Like they drank in 2D speakeasies."
list_reagents = list(/datum/reagent/consumable/ethanol/whiskey/candycorn = 100)
+ drink_type = ALCOHOL
/obj/item/reagent_containers/cup/glass/bottle/vodka
name = "Tunguska triple distilled"
desc = "Aah, vodka. Prime choice of drink AND fuel by Russians worldwide."
icon_state = "vodkabottle"
list_reagents = list(/datum/reagent/consumable/ethanol/vodka = 100)
+ drink_type = ALCOHOL
/obj/item/reagent_containers/cup/glass/bottle/vodka/badminka
name = "Badminka vodka"
desc = "The label's written in Cyrillic. All you can make out is the name and a word that looks vaguely like 'Vodka'."
icon_state = "badminka"
list_reagents = list(/datum/reagent/consumable/ethanol/vodka = 100)
+ drink_type = ALCOHOL
/obj/item/reagent_containers/cup/glass/bottle/tequila
name = "Caccavo guaranteed quality tequila"
desc = "Made from premium petroleum distillates, pure thalidomide and other fine quality ingredients!"
icon_state = "tequilabottle"
list_reagents = list(/datum/reagent/consumable/ethanol/tequila = 100)
+ drink_type = ALCOHOL
/obj/item/reagent_containers/cup/glass/bottle/bottleofnothing
name = "bottle of nothing"
desc = "A bottle filled with nothing."
icon_state = "bottleofnothing"
list_reagents = list(/datum/reagent/consumable/nothing = 100)
- drink_type = NONE
age_restricted = FALSE
/obj/item/reagent_containers/cup/glass/bottle/patron
@@ -381,18 +387,21 @@
desc = "Silver laced tequila, served in space night clubs across the galaxy."
icon_state = "patronbottle"
list_reagents = list(/datum/reagent/consumable/ethanol/patron = 100)
+ drink_type = ALCOHOL
/obj/item/reagent_containers/cup/glass/bottle/rum
name = "Captain Pete's Cuban spiced rum"
desc = "This isn't just rum, oh no. It's practically GRIFF in a bottle."
icon_state = "rumbottle"
list_reagents = list(/datum/reagent/consumable/ethanol/rum = 100)
+ drink_type = ALCOHOL
/obj/item/reagent_containers/cup/glass/bottle/rum/aged
name = "Captain Pete's Vintage spiced rum"
desc = "Shiver me timbers, a vintage edition of Captain Pete's rum. It's pratically GRIFF in a bottle from over 50 years ago."
icon_state = "rumbottle_gold"
list_reagents = list(/datum/reagent/consumable/ethanol/rum/aged = 100)
+ drink_type = ALCOHOL
/obj/item/reagent_containers/cup/glass/bottle/maltliquor
name = "\improper Rabid Bear malt liquor"
@@ -400,6 +409,7 @@
icon_state = "maltliquorbottle"
list_reagents = list(/datum/reagent/consumable/ethanol/beer/maltliquor = 100)
custom_price = PAYCHECK_CREW
+ drink_type = ALCOHOL
/obj/item/reagent_containers/cup/glass/bottle/holywater
name = "flask of holy water"
@@ -409,7 +419,6 @@
inhand_icon_state = "holyflask"
broken_inhand_icon_state = "broken_holyflask"
list_reagents = list(/datum/reagent/water/holywater = 100)
- drink_type = NONE
/obj/item/reagent_containers/cup/glass/bottle/holywater/add_message_overlay()
return //looks too weird...
@@ -424,6 +433,7 @@
desc = "Sweet, sweet dryness~"
icon_state = "vermouthbottle"
list_reagents = list(/datum/reagent/consumable/ethanol/vermouth = 100)
+ drink_type = ALCOHOL
/obj/item/reagent_containers/cup/glass/bottle/kahlua
name = "Robert Robust's coffee liqueur"
@@ -437,12 +447,14 @@
desc = "Because they are the only ones who will drink 100 proof cinnamon schnapps."
icon_state = "goldschlagerbottle"
list_reagents = list(/datum/reagent/consumable/ethanol/goldschlager = 100)
+ drink_type = ALCOHOL
/obj/item/reagent_containers/cup/glass/bottle/cognac
name = "Chateau de Baton premium cognac"
desc = "A sweet and strongly alchoholic drink, made after numerous distillations and years of maturing. You might as well not scream 'SHITCURITY' this time."
icon_state = "cognacbottle"
list_reagents = list(/datum/reagent/consumable/ethanol/cognac = 100)
+ drink_type = ALCOHOL
/obj/item/reagent_containers/cup/glass/bottle/wine
name = "Doublebeard's bearded special wine"
@@ -490,6 +502,7 @@
desc = "A strong alcoholic drink brewed and distributed by"
icon_state = "absinthebottle"
list_reagents = list(/datum/reagent/consumable/ethanol/absinthe = 100)
+ drink_type = ALCOHOL
/obj/item/reagent_containers/cup/glass/bottle/absinthe/Initialize(mapload)
. = ..()
@@ -539,6 +552,7 @@
name = "Gwyn's premium absinthe"
desc = "A potent alcoholic beverage, almost makes you forget the ash in your lungs."
icon_state = "absinthepremium"
+ drink_type = ALCOHOL
/obj/item/reagent_containers/cup/glass/bottle/absinthe/premium/redact()
return
@@ -556,24 +570,28 @@
icon_state = "hcider"
volume = 50
list_reagents = list(/datum/reagent/consumable/ethanol/hcider = 50)
+ drink_type = ALCOHOL
/obj/item/reagent_containers/cup/glass/bottle/amaretto
name = "Luini Amaretto"
desc = "A gentle, syrupy drink that tastes of almonds and apricots."
icon_state = "disaronno"
list_reagents = list(/datum/reagent/consumable/ethanol/amaretto = 100)
+ drink_type = ALCOHOL
/obj/item/reagent_containers/cup/glass/bottle/grappa
name = "Phillipes well-aged Grappa"
desc = "Bottle of Grappa."
icon_state = "grappabottle"
list_reagents = list(/datum/reagent/consumable/ethanol/grappa = 100)
+ drink_type = ALCOHOL
/obj/item/reagent_containers/cup/glass/bottle/sake
name = "Ryo's traditional sake"
desc = "Sweet as can be, and burns like fire going down."
icon_state = "sakebottle"
list_reagents = list(/datum/reagent/consumable/ethanol/sake = 100)
+ drink_type = ALCOHOL
/obj/item/reagent_containers/cup/glass/bottle/sake/Initialize(mapload)
if(prob(10))
@@ -596,6 +614,7 @@
desc = "A bottle of pure Fernet Bronca, produced in Cordoba Space Station"
icon_state = "fernetbottle"
list_reagents = list(/datum/reagent/consumable/ethanol/fernet = 100)
+ drink_type = ALCOHOL
/obj/item/reagent_containers/cup/glass/bottle/bitters
name = "Andromeda Bitters"
@@ -603,12 +622,14 @@
icon_state = "bitters_bottle"
volume = 30
list_reagents = list(/datum/reagent/consumable/ethanol/bitters = 30)
+ drink_type = ALCOHOL
/obj/item/reagent_containers/cup/glass/bottle/curacao
name = "Beekhof Blauw Curaçao"
desc = "Still produced on the island of Curaçao, after all these years."
icon_state = "curacao_bottle"
list_reagents = list(/datum/reagent/consumable/ethanol/curacao = 100)
+ drink_type = ALCOHOL
/obj/item/reagent_containers/cup/glass/bottle/curacao/add_message_overlay()
return //doesn't fit the sprite
@@ -618,6 +639,7 @@
desc = "Ironically named, given it's made in Bermuda."
icon_state = "navy_rum_bottle"
list_reagents = list(/datum/reagent/consumable/ethanol/navy_rum = 100)
+ drink_type = ALCOHOL
/obj/item/reagent_containers/cup/glass/bottle/grenadine
name = "Jester Grenadine"
@@ -652,6 +674,7 @@
reagent_flags = TRANSPARENT
spillable = FALSE
list_reagents = list(/datum/reagent/consumable/ethanol/champagne = 100)
+ drink_type = ALCOHOL
///Used for sabrage; increases the chance of success per 1 force of the attacking sharp item
var/sabrage_success_percentile = 5
///Whether this bottle was a victim of a successful sabrage attempt
@@ -805,6 +828,7 @@
desc = "You feel like you should give the bottle a good rub before opening."
icon_state = "blazaambottle"
list_reagents = list(/datum/reagent/consumable/ethanol/blazaam = 100)
+ drink_type = ALCOHOL
/obj/item/reagent_containers/cup/glass/bottle/trappist
name = "Mont de Requin Trappistes Bleu"
@@ -812,12 +836,14 @@
icon_state = "trappistbottle"
volume = 50
list_reagents = list(/datum/reagent/consumable/ethanol/trappist = 50)
+ drink_type = ALCOHOL
/obj/item/reagent_containers/cup/glass/bottle/hooch
name = "hooch bottle"
desc = "A bottle of rotgut. Its owner has applied some street wisdom to cleverly disguise it as a brown paper bag."
icon_state = "hoochbottle"
list_reagents = list(/datum/reagent/consumable/ethanol/hooch = 100)
+ drink_type = ALCOHOL
/obj/item/reagent_containers/cup/glass/bottle/hooch/add_message_overlay()
return //doesn't fit the sprite
@@ -827,6 +853,7 @@
desc = "It is said that the ancient Applalacians used these stoneware jugs to capture lightning in a bottle."
icon_state = "moonshinebottle"
list_reagents = list(/datum/reagent/consumable/ethanol/moonshine = 100)
+ drink_type = ALCOHOL
/obj/item/reagent_containers/cup/glass/bottle/moonshine/add_message_overlay()
return //doesn't fit the sprite
@@ -838,6 +865,7 @@
volume = 30
list_reagents = list(/datum/reagent/consumable/ethanol/mushi_kombucha = 30)
isGlass = FALSE
+ drink_type = ALCOHOL
/obj/item/reagent_containers/cup/glass/bottle/hakka_mate
name = "Hakka-Mate"
@@ -850,18 +878,21 @@
desc = "A boozier form of shochu designed for mixing. Comes straight from Mars' Dusty City itself, Shu-Kouba."
icon_state = "shochu_bottle"
list_reagents = list(/datum/reagent/consumable/ethanol/shochu = 100)
+ drink_type = ALCOHOL
/obj/item/reagent_containers/cup/glass/bottle/yuyake
name = "Moonlabor YÅ«yake"
desc = "The distilled essence of disco and flared pants, captured like lightning in a bottle."
icon_state = "yuyake_bottle"
list_reagents = list(/datum/reagent/consumable/ethanol/yuyake = 100)
+ drink_type = ALCOHOL
/obj/item/reagent_containers/cup/glass/bottle/coconut_rum
name = "Breezy Shoals Coconut Rum"
desc = "Live the breezy life with Breezy Shoals, made with only the *finest Caribbean rum."
icon_state = "coconut_rum_bottle"
list_reagents = list(/datum/reagent/consumable/ethanol/coconut_rum = 100)
+ drink_type = ALCOHOL
////////////////////////// MOLOTOV ///////////////////////
/obj/item/reagent_containers/cup/glass/bottle/molotov
diff --git a/code/modules/research/designs/autolathe/security_designs.dm b/code/modules/research/designs/autolathe/security_designs.dm
index b3bc0d8101d0a..7fbf06e122f09 100644
--- a/code/modules/research/designs/autolathe/security_designs.dm
+++ b/code/modules/research/designs/autolathe/security_designs.dm
@@ -179,7 +179,7 @@
id = "a357"
build_type = AUTOLATHE
materials = list(/datum/material/iron = SHEET_MATERIAL_AMOUNT*2)
- build_path = /obj/item/ammo_casing/a357
+ build_path = /obj/item/ammo_casing/c357
category = list(
RND_CATEGORY_HACKED,
RND_CATEGORY_WEAPONS + RND_SUBCATEGORY_WEAPONS_AMMO,
diff --git a/code/modules/research/designs/machine_designs.dm b/code/modules/research/designs/machine_designs.dm
index 15a5fed08eb08..74e00231035cf 100644
--- a/code/modules/research/designs/machine_designs.dm
+++ b/code/modules/research/designs/machine_designs.dm
@@ -1347,3 +1347,13 @@
RND_CATEGORY_MACHINE + RND_SUBCATEGORY_MACHINE_ENGINEERING
)
departmental_flags = DEPARTMENT_BITFLAG_ENGINEERING | DEPARTMENT_BITFLAG_CARGO
+
+/datum/design/board/mailsorter
+ name = "Mail Sorter"
+ desc = "The circuit board for a mail sorting unit."
+ id = "mailsorter"
+ build_path = /obj/item/circuitboard/machine/mailsorter
+ category = list(
+ RND_CATEGORY_MACHINE + RND_SUBCATEGORY_MACHINE_CARGO
+ )
+ departmental_flags = DEPARTMENT_BITFLAG_CARGO | DEPARTMENT_BITFLAG_ENGINEERING
diff --git a/code/modules/research/designs/mechfabricator_designs.dm b/code/modules/research/designs/mechfabricator_designs.dm
index 14a36843e3297..c06eb40204e96 100644
--- a/code/modules/research/designs/mechfabricator_designs.dm
+++ b/code/modules/research/designs/mechfabricator_designs.dm
@@ -2798,3 +2798,14 @@
)
departmental_flags = DEPARTMENT_BITFLAG_SCIENCE
+/datum/design/module/mister_janitor
+ name = "Cleaning Mister Module"
+ id = "mod_mister_janitor"
+ materials = list(
+ /datum/material/glass =HALF_SHEET_MATERIAL_AMOUNT,
+ /datum/material/titanium =HALF_SHEET_MATERIAL_AMOUNT * 1,
+ )
+ build_path = /obj/item/mod/module/mister/cleaner
+ category = list(
+ RND_CATEGORY_MODSUIT_MODULES + RND_SUBCATEGORY_MODSUIT_MODULES_SERVICE
+ )
diff --git a/code/modules/research/designs/weapon_designs.dm b/code/modules/research/designs/weapon_designs.dm
index ebda0f1e3b464..872821e9827b0 100644
--- a/code/modules/research/designs/weapon_designs.dm
+++ b/code/modules/research/designs/weapon_designs.dm
@@ -3,7 +3,7 @@
/////////////////////////////////////////
/datum/design/c38/sec
- id = "sec_38"
+ id = "c38_sec"
build_type = PROTOLATHE | AWAY_LATHE
category = list(
RND_CATEGORY_WEAPONS + RND_SUBCATEGORY_WEAPONS_AMMO
@@ -16,7 +16,11 @@
desc = "Designed to quickly reload revolvers. TRAC bullets embed a tracking implant within the target's body. The implant's signal is incompatible with teleporters."
id = "c38_trac"
build_type = PROTOLATHE | AWAY_LATHE
- materials = list(/datum/material/iron = SHEET_MATERIAL_AMOUNT * 10, /datum/material/silver =SHEET_MATERIAL_AMOUNT * 2.5, /datum/material/gold =HALF_SHEET_MATERIAL_AMOUNT)
+ materials = list(
+ /datum/material/iron = HALF_SHEET_MATERIAL_AMOUNT * 10,
+ /datum/material/silver = HALF_SHEET_MATERIAL_AMOUNT * 2.5,
+ /datum/material/gold = HALF_SHEET_MATERIAL_AMOUNT,
+ )
build_path = /obj/item/ammo_box/c38/trac
category = list(
RND_CATEGORY_WEAPONS + RND_SUBCATEGORY_WEAPONS_AMMO
@@ -28,7 +32,10 @@
desc = "Designed to quickly reload revolvers. Hot Shot bullets contain an incendiary payload."
id = "c38_hotshot"
build_type = PROTOLATHE | AWAY_LATHE
- materials = list(/datum/material/iron = SHEET_MATERIAL_AMOUNT * 10, /datum/material/plasma = SHEET_MATERIAL_AMOUNT * 2.5)
+ materials = list(
+ /datum/material/iron = HALF_SHEET_MATERIAL_AMOUNT * 10,
+ /datum/material/plasma = HALF_SHEET_MATERIAL_AMOUNT * 2.5,
+ )
build_path = /obj/item/ammo_box/c38/hotshot
category = list(
RND_CATEGORY_WEAPONS + RND_SUBCATEGORY_WEAPONS_AMMO
@@ -40,7 +47,10 @@
desc = "Designed to quickly reload revolvers. Iceblox bullets contain a cryogenic payload."
id = "c38_iceblox"
build_type = PROTOLATHE | AWAY_LATHE
- materials = list(/datum/material/iron = SHEET_MATERIAL_AMOUNT * 10, /datum/material/plasma = SHEET_MATERIAL_AMOUNT * 2.5)
+ materials = list(
+ /datum/material/iron = HALF_SHEET_MATERIAL_AMOUNT * 10,
+ /datum/material/plasma = HALF_SHEET_MATERIAL_AMOUNT * 2.5,
+ )
build_path = /obj/item/ammo_box/c38/iceblox
category = list(
RND_CATEGORY_WEAPONS + RND_SUBCATEGORY_WEAPONS_AMMO
@@ -52,13 +62,125 @@
desc = "Designed to quickly reload revolvers. Rubber bullets are bouncy and less-than-lethal."
id = "c38_rubber"
build_type = PROTOLATHE | AWAY_LATHE
- materials = list(/datum/material/iron = SHEET_MATERIAL_AMOUNT * 10)
+ materials = list(/datum/material/iron = HALF_SHEET_MATERIAL_AMOUNT * 10)
build_path = /obj/item/ammo_box/c38/match/bouncy
category = list(
RND_CATEGORY_WEAPONS + RND_SUBCATEGORY_WEAPONS_AMMO
)
departmental_flags = DEPARTMENT_BITFLAG_SECURITY
+/datum/design/c38_true
+ name = "Speedloader (.38 True Strike) (Lethal)"
+ desc = "Designed to quickly reload revolvers. Bullets bounce towards new targets with surprising accuracy."
+ id = "c38_true_strike"
+ build_type = PROTOLATHE | AWAY_LATHE
+ materials = list(
+ /datum/material/iron = HALF_SHEET_MATERIAL_AMOUNT * 10,
+ /datum/material/bluespace = HALF_SHEET_MATERIAL_AMOUNT,
+ )
+ build_path = /obj/item/ammo_box/magazine/m38/true
+ category = list(
+ RND_CATEGORY_WEAPONS + RND_SUBCATEGORY_WEAPONS_AMMO
+ )
+ departmental_flags = DEPARTMENT_BITFLAG_SECURITY
+
+/datum/design/c38_mag
+ name = "Magazine (.38) (Lethal)"
+ desc = "Designed to tactically reload a NT BR-38 Battle Rifle. Less powerful by design, guns chambered in .38 caliber rounds are still quite popular for use by police forces, \
+ private security firms and organizations unable to access energy-based nonlethal weaponry. The lower (relative) penetrative power is useful for preventing potential hull damage \
+ aboard space stations and shuttles."
+ id = "c38_mag"
+ build_type = PROTOLATHE | AWAY_LATHE
+ materials = list(
+ /datum/material/iron = HALF_SHEET_MATERIAL_AMOUNT * 30,
+ /datum/material/plastic = HALF_SHEET_MATERIAL_AMOUNT * 3,
+ )
+ build_path = /obj/item/ammo_box/magazine/m38
+ category = list(
+ RND_CATEGORY_WEAPONS + RND_SUBCATEGORY_WEAPONS_AMMO
+ )
+ departmental_flags = DEPARTMENT_BITFLAG_SECURITY
+
+/datum/design/c38_trac_mag
+ name = "Magazine (.38 TRAC) (Less Lethal)"
+ desc = "Designed to tactically reload a NT BR-38 Battle Rifle. TRAC bullets embed a tracking implant within the target's body. The implant's signal is incompatible with teleporters."
+ id = "c38_trac_mag"
+ build_type = PROTOLATHE | AWAY_LATHE
+ materials = list(
+ /datum/material/iron = HALF_SHEET_MATERIAL_AMOUNT * 30,
+ /datum/material/silver = HALF_SHEET_MATERIAL_AMOUNT * 2.5,
+ /datum/material/gold = HALF_SHEET_MATERIAL_AMOUNT,
+ /datum/material/plastic = HALF_SHEET_MATERIAL_AMOUNT * 3,
+ )
+ build_path = /obj/item/ammo_box/magazine/m38/trac
+ category = list(
+ RND_CATEGORY_WEAPONS + RND_SUBCATEGORY_WEAPONS_AMMO
+ )
+ departmental_flags = DEPARTMENT_BITFLAG_SECURITY
+
+/datum/design/c38_hotshot_mag
+ name = "Magazine (.38 Hot Shot) (Very Lethal)"
+ desc = "Designed to tactically reload a NT BR-38 Battle Rifle. Hot Shot bullets contain an incendiary payload."
+ id = "c38_hotshot_mag"
+ build_type = PROTOLATHE | AWAY_LATHE
+ materials = list(
+ /datum/material/iron = HALF_SHEET_MATERIAL_AMOUNT * 30,
+ /datum/material/plasma = HALF_SHEET_MATERIAL_AMOUNT * 2.5,
+ /datum/material/plastic = HALF_SHEET_MATERIAL_AMOUNT * 3,
+ )
+ build_path = /obj/item/ammo_box/magazine/m38/hotshot
+ category = list(
+ RND_CATEGORY_WEAPONS + RND_SUBCATEGORY_WEAPONS_AMMO
+ )
+ departmental_flags = DEPARTMENT_BITFLAG_SECURITY
+
+/datum/design/c38_iceblox_mag
+ name = "Magazine (.38 Iceblox) (Lethal/Very Lethal (Lizardpeople))"
+ desc = "Designed to tactically reload a NT BR-38 Battle Rifle. Iceblox bullets contain a cryogenic payload."
+ id = "c38_iceblox_mag"
+ build_type = PROTOLATHE | AWAY_LATHE
+ materials = list(
+ /datum/material/iron = HALF_SHEET_MATERIAL_AMOUNT * 30,
+ /datum/material/plasma = HALF_SHEET_MATERIAL_AMOUNT * 2.5,
+ /datum/material/plastic = HALF_SHEET_MATERIAL_AMOUNT * 3,
+ )
+ build_path = /obj/item/ammo_box/magazine/m38/iceblox
+ category = list(
+ RND_CATEGORY_WEAPONS + RND_SUBCATEGORY_WEAPONS_AMMO
+ )
+ departmental_flags = DEPARTMENT_BITFLAG_SECURITY
+
+/datum/design/c38_rubber_mag
+ name = "Magazine (.38 Rubber) (Less Lethal)"
+ desc = "Designed to tactically reload a NT BR-38 Battle Rifle. Rubber bullets are bouncy and less-than-lethal."
+ id = "c38_rubber_mag"
+ build_type = PROTOLATHE | AWAY_LATHE
+ materials = list(
+ /datum/material/iron = HALF_SHEET_MATERIAL_AMOUNT * 30,
+ /datum/material/plastic = HALF_SHEET_MATERIAL_AMOUNT * 3,
+ )
+ build_path = /obj/item/ammo_box/magazine/m38/match/bouncy
+ category = list(
+ RND_CATEGORY_WEAPONS + RND_SUBCATEGORY_WEAPONS_AMMO
+ )
+ departmental_flags = DEPARTMENT_BITFLAG_SECURITY
+
+/datum/design/c38_true_mag
+ name = "Magazine (.38 Truee Strike) (Lethal)"
+ desc = "Designed to tactically reload a NT BR-38 Battle Rifle. Bullets bounce towards new targets with surprising accuracy."
+ id = "c38_true_strike_mag"
+ build_type = PROTOLATHE | AWAY_LATHE
+ materials = list(
+ /datum/material/iron = HALF_SHEET_MATERIAL_AMOUNT * 30,
+ /datum/material/plastic = HALF_SHEET_MATERIAL_AMOUNT * 3,
+ /datum/material/bluespace = HALF_SHEET_MATERIAL_AMOUNT,
+ )
+ build_path = /obj/item/ammo_box/magazine/m38/true
+ category = list(
+ RND_CATEGORY_WEAPONS + RND_SUBCATEGORY_WEAPONS_AMMO
+ )
+ departmental_flags = DEPARTMENT_BITFLAG_SECURITY
+
/datum/design/rubbershot/sec
id = "sec_rshot"
desc = "Rubbershot shotgun shells. Fires a cloud of pellets. Rubber bullets are bouncy and less-than-lethal."
diff --git a/code/modules/research/experimentor.dm b/code/modules/research/experimentor.dm
index c78ca6601d414..75ad0146fd4b9 100644
--- a/code/modules/research/experimentor.dm
+++ b/code/modules/research/experimentor.dm
@@ -828,7 +828,6 @@
var/datum/dimension_theme/shifter = SSmaterials.dimensional_themes[new_theme_path]
for(var/turf/shiftee in range(1, user))
shifter.apply_theme(shiftee, show_effect = TRUE)
- qdel(shifter)
// prevent *total* spam conversion
min_cooldown += 2 SECONDS
max_cooldown += 2 SECONDS
diff --git a/code/modules/research/techweb/nodes/cyborg_nodes.dm b/code/modules/research/techweb/nodes/cyborg_nodes.dm
index 13725f62a4335..173c47fcc8324 100644
--- a/code/modules/research/techweb/nodes/cyborg_nodes.dm
+++ b/code/modules/research/techweb/nodes/cyborg_nodes.dm
@@ -156,7 +156,6 @@
"implantcase",
"implanter",
"locator",
- "c38_trac",
)
research_costs = list(TECHWEB_POINT_TYPE_GENERIC = TECHWEB_TIER_1_POINTS)
announce_channels = list(RADIO_CHANNEL_SECURITY, RADIO_CHANNEL_MEDICAL)
diff --git a/code/modules/research/techweb/nodes/engi_nodes.dm b/code/modules/research/techweb/nodes/engi_nodes.dm
index 4ef55e21bc97a..75c9459771c26 100644
--- a/code/modules/research/techweb/nodes/engi_nodes.dm
+++ b/code/modules/research/techweb/nodes/engi_nodes.dm
@@ -155,6 +155,7 @@
"manulathe",
"manusorter",
"manurouter",
+ "mailsorter",
)
/datum/techweb_node/energy_manipulation
diff --git a/code/modules/research/techweb/nodes/modsuit_nodes.dm b/code/modules/research/techweb/nodes/modsuit_nodes.dm
index cc31a1fc1ef77..0ce6eb4b229ae 100644
--- a/code/modules/research/techweb/nodes/modsuit_nodes.dm
+++ b/code/modules/research/techweb/nodes/modsuit_nodes.dm
@@ -34,6 +34,7 @@
"mod_longfall",
"mod_thermal_regulator",
"mod_sign_radio",
+ "mod_mister_janitor",
)
research_costs = list(TECHWEB_POINT_TYPE_GENERIC = TECHWEB_TIER_1_POINTS)
announce_channels = list(RADIO_CHANNEL_SCIENCE)
diff --git a/code/modules/research/techweb/nodes/security_nodes.dm b/code/modules/research/techweb/nodes/security_nodes.dm
index 97d2036207c56..02679b4280ee4 100644
--- a/code/modules/research/techweb/nodes/security_nodes.dm
+++ b/code/modules/research/techweb/nodes/security_nodes.dm
@@ -7,7 +7,9 @@
"toy_armblade",
"toygun",
"c38_rubber",
- "sec_38",
+ "c38_rubber_mag",
+ "c38_sec",
+ "c38_mag",
"capbox",
"foam_dart",
"sec_beanbag_slug",
@@ -80,7 +82,13 @@
prereq_ids = list(TECHWEB_NODE_EXPLOSIVES)
design_ids = list(
"c38_hotshot",
+ "c38_hotshot_mag",
"c38_iceblox",
+ "c38_iceblox_mag",
+ "c38_trac",
+ "c38_trac_mag",
+ "c38_true_strike",
+ "c38_true_strike_mag",
"techshotshell",
)
research_costs = list(TECHWEB_POINT_TYPE_GENERIC = TECHWEB_TIER_4_POINTS)
diff --git a/code/modules/research/xenobiology/crossbreeding/_potions.dm b/code/modules/research/xenobiology/crossbreeding/_potions.dm
index f3477963f43c9..2f82396168e7a 100644
--- a/code/modules/research/xenobiology/crossbreeding/_potions.dm
+++ b/code/modules/research/xenobiology/crossbreeding/_potions.dm
@@ -125,7 +125,7 @@ Slimecrossing Potions
to_chat(user, span_notice("You slather the blue gunk over the [clothing], making it airtight."))
clothing.name = "pressure-resistant [clothing.name]"
clothing.remove_atom_colour(WASHABLE_COLOUR_PRIORITY)
- clothing.add_atom_colour(COLOR_NAVY, FIXED_COLOUR_PRIORITY)
+ clothing.add_atom_colour(color_transition_filter(COLOR_NAVY, SATURATION_OVERRIDE), FIXED_COLOUR_PRIORITY)
clothing.min_cold_protection_temperature = SPACE_SUIT_MIN_TEMP_PROTECT
clothing.cold_protection = clothing.body_parts_covered
clothing.clothing_flags |= STOPSPRESSUREDAMAGE
@@ -165,7 +165,7 @@ Slimecrossing Potions
to_chat(user, span_notice("You slather the red gunk over the [clothing], making it lavaproof."))
clothing.name = "lavaproof [clothing.name]"
clothing.remove_atom_colour(WASHABLE_COLOUR_PRIORITY)
- clothing.add_atom_colour(COLOR_MAROON, FIXED_COLOUR_PRIORITY)
+ clothing.add_atom_colour(color_transition_filter(COLOR_MAROON, SATURATION_OVERRIDE), FIXED_COLOUR_PRIORITY)
clothing.resistance_flags |= LAVA_PROOF
if (isclothing(clothing))
var/obj/item/clothing/clothing_real = clothing
diff --git a/code/modules/research/xenobiology/crossbreeding/prismatic.dm b/code/modules/research/xenobiology/crossbreeding/prismatic.dm
index 947323b0e47f4..647711192afed 100644
--- a/code/modules/research/xenobiology/crossbreeding/prismatic.dm
+++ b/code/modules/research/xenobiology/crossbreeding/prismatic.dm
@@ -14,7 +14,7 @@ Prismatic extracts:
if(!isturf(interacting_with) || isspaceturf(interacting_with))
return NONE
user.do_attack_animation(interacting_with)
- interacting_with.add_atom_colour(paintcolor, WASHABLE_COLOUR_PRIORITY)
+ interacting_with.add_atom_colour(color_transition_filter(paintcolor, SATURATION_OVERRIDE), WASHABLE_COLOUR_PRIORITY)
playsound(interacting_with, 'sound/effects/slosh.ogg', 20, TRUE)
return ITEM_INTERACT_SUCCESS
diff --git a/code/modules/research/xenobiology/crossbreeding/regenerative.dm b/code/modules/research/xenobiology/crossbreeding/regenerative.dm
index a2c5698fbb192..a0d9a1e89eff5 100644
--- a/code/modules/research/xenobiology/crossbreeding/regenerative.dm
+++ b/code/modules/research/xenobiology/crossbreeding/regenerative.dm
@@ -30,7 +30,7 @@ Regenerative extracts:
span_notice("You squeeze [src], and it bursts in your hand, splashing you with milky goo which quickly regenerates your injuries!"))
core_effect_before(H, user)
user.do_attack_animation(interacting_with)
- H.revive(HEAL_ALL)
+ H.revive(HEAL_ALL & ~HEAL_REFRESH_ORGANS)
core_effect(H, user)
playsound(H, 'sound/effects/splat.ogg', 40, TRUE)
qdel(src)
@@ -128,13 +128,13 @@ Regenerative extracts:
if(fireproofed)
target.visible_message(span_notice("Some of [target]'s clothing gets coated in the goo, and turns blue!"))
-/obj/item/slimecross/regenerative/darkblue/proc/fireproof(obj/item/clothing/C)
- C.name = "fireproofed [C.name]"
- C.remove_atom_colour(WASHABLE_COLOUR_PRIORITY)
- C.add_atom_colour(COLOR_NAVY, FIXED_COLOUR_PRIORITY)
- C.max_heat_protection_temperature = FIRE_IMMUNITY_MAX_TEMP_PROTECT
- C.heat_protection = C.body_parts_covered
- C.resistance_flags |= FIRE_PROOF
+/obj/item/slimecross/regenerative/darkblue/proc/fireproof(obj/item/clothing/clothing_piece)
+ clothing_piece.name = "fireproofed [clothing_piece.name]"
+ clothing_piece.remove_atom_colour(WASHABLE_COLOUR_PRIORITY)
+ clothing_piece.add_atom_colour(color_transition_filter(COLOR_NAVY, SATURATION_OVERRIDE), FIXED_COLOUR_PRIORITY)
+ clothing_piece.max_heat_protection_temperature = FIRE_IMMUNITY_MAX_TEMP_PROTECT
+ clothing_piece.heat_protection = clothing_piece.body_parts_covered
+ clothing_piece.resistance_flags |= FIRE_PROOF
/obj/item/slimecross/regenerative/silver
colour = SLIME_TYPE_SILVER
@@ -188,7 +188,7 @@ Regenerative extracts:
/obj/item/slimecross/regenerative/pyrite/core_effect(mob/living/target, mob/user)
target.visible_message(span_warning("The milky goo coating [target] leaves [target.p_them()] a different color!"))
- target.add_atom_colour(rgb(rand(0,255),rand(0,255),rand(0,255)),WASHABLE_COLOUR_PRIORITY)
+ target.add_atom_colour(color_transition_filter(rgb(rand(0,255), rand(0,255), rand(0,255)), SATURATION_OVERRIDE), WASHABLE_COLOUR_PRIORITY)
/obj/item/slimecross/regenerative/red
colour = SLIME_TYPE_RED
@@ -270,7 +270,7 @@ Regenerative extracts:
if(target == user)
return
var/mob/living/U = user
- U.revive(HEAL_ALL)
+ U.revive(HEAL_ALL & ~HEAL_REFRESH_ORGANS)
to_chat(U, span_notice("Some of the milky goo sprays onto you, as well!"))
/obj/item/slimecross/regenerative/adamantine
diff --git a/code/modules/research/xenobiology/xenobiology.dm b/code/modules/research/xenobiology/xenobiology.dm
index a3e760104c78c..f63ee0f088076 100644
--- a/code/modules/research/xenobiology/xenobiology.dm
+++ b/code/modules/research/xenobiology/xenobiology.dm
@@ -921,7 +921,7 @@
to_chat(user, span_notice("You slather the red gunk over the [interacting_with], making it faster."))
interacting_with.remove_atom_colour(WASHABLE_COLOUR_PRIORITY)
- interacting_with.add_atom_colour(COLOR_RED, FIXED_COLOUR_PRIORITY)
+ interacting_with.add_atom_colour(color_transition_filter(COLOR_RED, SATURATION_OVERRIDE), FIXED_COLOUR_PRIORITY)
interacting_with.drag_slowdown = 0
ADD_TRAIT(interacting_with, TRAIT_SPEED_POTIONED, SLIME_POTION_TRAIT)
qdel(src)
@@ -952,7 +952,7 @@
to_chat(user, span_notice("You slather the blue gunk over the [clothing], fireproofing it."))
clothing.name = "fireproofed [clothing.name]"
clothing.remove_atom_colour(WASHABLE_COLOUR_PRIORITY)
- clothing.add_atom_colour(COLOR_NAVY, FIXED_COLOUR_PRIORITY)
+ clothing.add_atom_colour(color_transition_filter(COLOR_NAVY, SATURATION_OVERRIDE), FIXED_COLOUR_PRIORITY)
clothing.max_heat_protection_temperature = FIRE_IMMUNITY_MAX_TEMP_PROTECT
clothing.heat_protection = clothing.body_parts_covered
clothing.resistance_flags |= FIRE_PROOF
diff --git a/code/modules/shuttle/emergency.dm b/code/modules/shuttle/emergency.dm
deleted file mode 100644
index 735b0f641a91c..0000000000000
--- a/code/modules/shuttle/emergency.dm
+++ /dev/null
@@ -1,891 +0,0 @@
-#define TIME_LEFT (SSshuttle.emergency.timeLeft())
-#define ENGINES_START_TIME 100
-#define ENGINES_STARTED (SSshuttle.emergency.mode == SHUTTLE_IGNITING)
-#define IS_DOCKED (SSshuttle.emergency.mode == SHUTTLE_DOCKED || (ENGINES_STARTED))
-#define SHUTTLE_CONSOLE_ACTION_DELAY (5 SECONDS)
-
-#define NOT_BEGUN 0
-#define STAGE_1 1
-#define STAGE_2 2
-#define STAGE_3 3
-#define STAGE_4 4
-#define HIJACKED 5
-
-/obj/machinery/computer/emergency_shuttle
- name = "emergency shuttle console"
- desc = "For shuttle control."
- icon_screen = "shuttle"
- icon_keyboard = "tech_key"
- resistance_flags = INDESTRUCTIBLE
- var/auth_need = 3
- var/list/authorized = list()
- var/list/acted_recently = list()
- var/hijack_last_stage_increase = 0 SECONDS
- var/hijack_stage_time = 5 SECONDS
- var/hijack_stage_cooldown = 5 SECONDS
- var/hijack_flight_time_increase = 30 SECONDS
- var/hijack_completion_flight_time_set = 10 SECONDS //How long in deciseconds to set shuttle's timer after hijack is done.
- var/hijack_hacking = FALSE
- var/hijack_announce = TRUE
-
-/obj/machinery/computer/emergency_shuttle/examine(mob/user)
- . = ..()
- if(hijack_announce)
- . += span_danger("Security systems present on console. Any unauthorized tampering will result in an emergency announcement.")
- if(user?.mind?.get_hijack_speed())
- . += span_danger("Alt click on this to attempt to hijack the shuttle. This will take multiple tries (current: stage [SSshuttle.emergency.hijack_status]/[HIJACKED]).")
- . += span_notice("It will take you [(hijack_stage_time * user.mind.get_hijack_speed()) / 10] seconds to reprogram a stage of the shuttle's navigational firmware, and the console will undergo automated timed lockout for [hijack_stage_cooldown/10] seconds after each stage.")
- if(hijack_announce)
- . += span_warning("It is probably best to fortify your position as to be uninterrupted during the attempt, given the automatic announcements..")
-
-/obj/machinery/computer/emergency_shuttle/attackby(obj/item/I, mob/user,params)
- if(isidcard(I))
- say("Please equip your ID card into your ID slot to authenticate.")
- . = ..()
-
-/obj/machinery/computer/emergency_shuttle/ui_state(mob/user)
- return GLOB.human_adjacent_state
-
-/obj/machinery/computer/emergency_shuttle/ui_interact(mob/user, datum/tgui/ui)
- . = ..()
- ui = SStgui.try_update_ui(user, src, ui)
- if(!ui)
- ui = new(user, src, "EmergencyShuttleConsole", name)
- ui.open()
-
-/obj/machinery/computer/emergency_shuttle/ui_data(user)
- var/list/data = list()
-
- data["timer_str"] = SSshuttle.emergency.getTimerStr()
- data["engines_started"] = ENGINES_STARTED
- data["authorizations_remaining"] = max((auth_need - authorized.len), 0)
- var/list/A = list()
- for(var/i in authorized)
- var/obj/item/card/id/ID = i
- var/name = ID.registered_name
- var/job = ID.assignment
-
- if(obj_flags & EMAGGED)
- name = Gibberish(name)
- job = Gibberish(job)
- A += list(list("name" = name, "job" = job))
- data["authorizations"] = A
-
- data["enabled"] = (IS_DOCKED && !ENGINES_STARTED) && !(user in acted_recently)
- data["emagged"] = obj_flags & EMAGGED ? 1 : 0
- return data
-
-/obj/machinery/computer/emergency_shuttle/ui_act(action, params, datum/tgui/ui)
- . = ..()
- if(.)
- return
- if(ENGINES_STARTED) // past the point of no return
- return
- if(!IS_DOCKED) // shuttle computer only has uses when onstation
- return
- if(SSshuttle.emergency.mode == SHUTTLE_DISABLED) // admins have disabled the shuttle.
- return
- if(!isliving(usr))
- return
-
- var/area/my_area = get_area(src)
- if(!istype(my_area, /area/shuttle/escape))
- say("Error - Network connectivity: Console has lost connection to the shuttle.")
- return
-
- var/mob/living/user = usr
- . = FALSE
-
- var/obj/item/card/id/ID = user.get_idcard(TRUE)
-
- if(!ID)
- to_chat(user, span_warning("You don't have an ID."))
- return
-
- if(!(ACCESS_COMMAND in ID.access))
- to_chat(user, span_warning("The access level of your card is not high enough."))
- return
-
- if (user in acted_recently)
- return
-
- var/old_len = authorized.len
- addtimer(CALLBACK(src, PROC_REF(clear_recent_action), user), SHUTTLE_CONSOLE_ACTION_DELAY)
-
- switch(action)
- if("authorize")
- . = authorize(user)
-
- if("repeal")
- authorized -= ID
-
- if("abort")
- if(authorized.len)
- // Abort. The action for when heads are fighting over whether
- // to launch early.
- authorized.Cut()
- . = TRUE
-
- if((old_len != authorized.len) && !ENGINES_STARTED)
- var/alert = (authorized.len > old_len)
- var/repeal = (authorized.len < old_len)
- var/remaining = max(0, auth_need - authorized.len)
- if(authorized.len && remaining)
- minor_announce("[remaining] authorizations needed until shuttle is launched early", null, alert)
- if(repeal)
- minor_announce("Early launch authorization revoked, [remaining] authorizations needed")
-
- acted_recently += user
- SStgui.update_user_uis(user, src)
-
-/obj/machinery/computer/emergency_shuttle/proc/authorize(mob/living/user, source)
- var/obj/item/card/id/ID = user.get_idcard(TRUE)
-
- if(ID in authorized)
- return FALSE
- for(var/i in authorized)
- var/obj/item/card/id/other = i
- if(other.registered_name == ID.registered_name)
- return FALSE // No using IDs with the same name
-
- authorized += ID
-
- message_admins("[ADMIN_LOOKUPFLW(user)] has authorized early shuttle launch")
- log_shuttle("[key_name(user)] has authorized early shuttle launch in [COORD(src)]")
- // Now check if we're on our way
- . = TRUE
- process(SSMACHINES_DT)
-
-/obj/machinery/computer/emergency_shuttle/proc/clear_recent_action(mob/user)
- acted_recently -= user
- if (!QDELETED(user))
- SStgui.update_user_uis(user, src)
-
-/obj/machinery/computer/emergency_shuttle/process()
- // Launch check is in process in case auth_need changes for some reason
- // probably external.
- . = FALSE
- if(!SSshuttle.emergency)
- return
-
- if(SSshuttle.emergency.mode == SHUTTLE_STRANDED)
- authorized.Cut()
- obj_flags &= ~(EMAGGED)
-
- if(ENGINES_STARTED || (!IS_DOCKED))
- return .
-
- // Check to see if we've reached criteria for early launch
- if((authorized.len >= auth_need) || (obj_flags & EMAGGED))
- // shuttle timers use 1/10th seconds internally
- SSshuttle.emergency.setTimer(ENGINES_START_TIME)
- var/system_error = obj_flags & EMAGGED ? "SYSTEM ERROR:" : null
- minor_announce("The emergency shuttle will launch in \
- [TIME_LEFT] seconds", system_error, alert=TRUE)
- . = TRUE
-
-/obj/machinery/computer/emergency_shuttle/proc/increase_hijack_stage()
- var/obj/docking_port/mobile/emergency/shuttle = SSshuttle.emergency
- // Begin loading this early, prevents a delay when the shuttle goes to land
- INVOKE_ASYNC(SSmapping, TYPE_PROC_REF(/datum/controller/subsystem/mapping, lazy_load_template), LAZY_TEMPLATE_KEY_NUKIEBASE)
-
- shuttle.hijack_status++
- if(hijack_announce)
- announce_hijack_stage()
- hijack_last_stage_increase = world.time
- say("Navigational protocol error! Rebooting systems.")
- if(shuttle.mode == SHUTTLE_ESCAPE)
- if(shuttle.hijack_status == HIJACKED)
- shuttle.setTimer(hijack_completion_flight_time_set)
- else
- shuttle.setTimer(shuttle.timeLeft(1) + hijack_flight_time_increase) //give the guy more time to hijack if it's already in flight.
- return shuttle.hijack_status
-
-/obj/machinery/computer/emergency_shuttle/click_alt(mob/living/user)
- if(!isliving(user))
- return NONE
- attempt_hijack_stage(user)
- return CLICK_ACTION_SUCCESS
-
-/obj/machinery/computer/emergency_shuttle/proc/attempt_hijack_stage(mob/living/user)
- if(!user.CanReach(src))
- return
- if(HAS_TRAIT(user, TRAIT_HANDS_BLOCKED))
- to_chat(user, span_warning("You need your hands free before you can manipulate [src]."))
- return
- var/area/my_area = get_area(src)
- if(!istype(my_area, /area/shuttle/escape))
- say("Error - Network connectivity: Console has lost connection to the shuttle.")
- return
- if(!user?.mind?.get_hijack_speed())
- to_chat(user, span_warning("You manage to open a user-mode shell on [src], and hundreds of lines of debugging output fly through your vision. It is probably best to leave this alone."))
- return
- if(!EMERGENCY_AT_LEAST_DOCKED) // prevent advancing hijack stages on BYOS shuttles until the shuttle has "docked"
- to_chat(user, span_warning("The flight plans for the shuttle haven't been loaded yet, you can't hack this right now."))
- return
- if(hijack_hacking == TRUE)
- return
- if(SSshuttle.emergency.hijack_status >= HIJACKED)
- to_chat(user, span_warning("The emergency shuttle is already loaded with a corrupt navigational payload. What more do you want from it?"))
- return
- if(hijack_last_stage_increase >= world.time - hijack_stage_cooldown)
- say("Error - Catastrophic software error detected. Input is currently on timeout.")
- return
- hijack_hacking = TRUE
- to_chat(user, span_boldwarning("You [SSshuttle.emergency.hijack_status == NOT_BEGUN? "begin" : "continue"] to override [src]'s navigational protocols."))
- say("Software override initiated.")
- var/turf/console_hijack_turf = get_turf(src)
- message_admins("[src] is being overriden for hijack by [ADMIN_LOOKUPFLW(user)] in [ADMIN_VERBOSEJMP(console_hijack_turf)]")
- user.log_message("is hijacking [src].", LOG_GAME)
- . = FALSE
- if(do_after(user, hijack_stage_time * (1 / user.mind.get_hijack_speed()), target = src))
- increase_hijack_stage()
- console_hijack_turf = get_turf(src)
- message_admins("[ADMIN_LOOKUPFLW(user)] has hijacked [src] in [ADMIN_VERBOSEJMP(console_hijack_turf)]. Hijack stage increased to stage [SSshuttle.emergency.hijack_status] out of [HIJACKED].")
- user.log_message("has hijacked [src]. Hijack stage increased to stage [SSshuttle.emergency.hijack_status] out of [HIJACKED].", LOG_GAME)
- . = TRUE
- to_chat(user, span_notice("You reprogram some of [src]'s programming, putting it on timeout for [hijack_stage_cooldown/10] seconds."))
- visible_message(
- span_warning("[user.name] appears to be tampering with [src]."),
- blind_message = span_hear("You hear someone tapping computer keys."),
- vision_distance = COMBAT_MESSAGE_RANGE,
- ignored_mobs = user
- )
- hijack_hacking = FALSE
-
-/obj/machinery/computer/emergency_shuttle/proc/announce_hijack_stage()
- var/msg
- switch(SSshuttle.emergency.hijack_status)
- if(NOT_BEGUN)
- return
- if(STAGE_1)
- msg = "AUTHENTICATING - FAIL. AUTHENTICATING - FAIL. AUTHENTICATING - FAI###### Welcome, technician JOHN DOE."
- if(STAGE_2)
- msg = "Warning: Navigational route fails \"IS_AUTHORIZED\". Please try againNN[scramble_message_replace_chars("againagainagainagainagain", 70)]."
- if(STAGE_3)
- msg = "CRC mismatch at ~h~ in calculated route buffer. Full reset initiated of FTL_NAVIGATION_SERVICES. Memory decrypted for automatic repair."
- if(STAGE_4)
- msg = "~ACS_directive module_load(cyberdyne.exploit.nanotrasen.shuttlenav)... NT key mismatch. Confirm load? Y...###Reboot complete. $SET transponder_state = 0; System link initiated with connected engines..."
- if(HIJACKED)
- msg = "SYSTEM OVERRIDE - Resetting course to \[[scramble_message_replace_chars("###########", 100)]\] \
- ([scramble_message_replace_chars("#######", 100)]/[scramble_message_replace_chars("#######", 100)]/[scramble_message_replace_chars("#######", 100)]) \
- {AUTH - ROOT (uid: 0)}.\
- [SSshuttle.emergency.mode == SHUTTLE_ESCAPE ? "Diverting from existing route - Bluespace exit in \
- [hijack_completion_flight_time_set >= INFINITY ? "[scramble_message_replace_chars("\[ERROR\]")]" : hijack_completion_flight_time_set/10] seconds." : ""]"
- minor_announce(scramble_message_replace_chars(msg, replaceprob = 10), "Emergency Shuttle", TRUE)
-
-/obj/machinery/computer/emergency_shuttle/emag_act(mob/user, obj/item/card/emag/emag_card)
- // How did you even get on the shuttle before it go to the station?
- if(!IS_DOCKED)
- return FALSE
-
- if((obj_flags & EMAGGED) || ENGINES_STARTED) //SYSTEM ERROR: THE SHUTTLE WILL LA-SYSTEM ERROR: THE SHUTTLE WILL LA-SYSTEM ERROR: THE SHUTTLE WILL LAUNCH IN 10 SECONDS
- balloon_alert(user, "shuttle already about to launch!")
- return FALSE
-
- var/time = TIME_LEFT
- if (user)
- message_admins("[ADMIN_LOOKUPFLW(user)] has emagged the emergency shuttle [time] seconds before launch.")
- log_shuttle("[key_name(user)] has emagged the emergency shuttle in [COORD(src)] [time] seconds before launch.")
- else
- message_admins("The emergency shuttle was emagged [time] seconds before launch, with no emagger.")
- log_shuttle("The emergency shuttle was emagged in [COORD(src)] [time] seconds before launch, with no emagger.")
-
- obj_flags |= EMAGGED
- SSshuttle.emergency.movement_force = list("KNOCKDOWN" = 60, "THROW" = 20)//YOUR PUNY SEATBELTS can SAVE YOU NOW, MORTAL
- for(var/i in 1 to 10)
- // the shuttle system doesn't know who these people are, but they
- // must be important, surely
- var/obj/item/card/id/ID = new(src)
- var/datum/job/J = pick(SSjob.joinable_occupations)
- ID.registered_name = generate_random_name_species_based(species_type = /datum/species/human)
- ID.assignment = J.title
-
- authorized += ID
-
- process(SSMACHINES_DT)
- return TRUE
-
-/obj/machinery/computer/emergency_shuttle/Destroy()
- // Our fake IDs that the emag generated are just there for colour
- // They're not supposed to be accessible
-
- for(var/obj/item/card/id/ID in src)
- qdel(ID)
- if(authorized?.len)
- authorized.Cut()
- authorized = null
-
- . = ..()
-
-/obj/docking_port/mobile/emergency
- name = "emergency shuttle"
- shuttle_id = "emergency"
- dir = EAST
- port_direction = WEST
- var/sound_played = 0 //If the launch sound has been sent to all players on the shuttle itself
- var/hijack_status = NOT_BEGUN
-
-/obj/docking_port/mobile/emergency/Initialize(mapload)
- . = ..()
-
- setup_shuttle_events()
-
-/obj/docking_port/mobile/emergency/canDock(obj/docking_port/stationary/S)
- return SHUTTLE_CAN_DOCK //If the emergency shuttle can't move, the whole game breaks, so it will force itself to land even if it has to crush a few departments in the process
-
-/obj/docking_port/mobile/emergency/register()
- . = ..()
- SSshuttle.emergency = src
-
-/obj/docking_port/mobile/emergency/Destroy(force)
- if(force)
- // This'll make the shuttle subsystem use the backup shuttle.
- if(src == SSshuttle.emergency)
- // If we're the selected emergency shuttle
- SSshuttle.emergencyDeregister()
-
- . = ..()
-
-/// DOPPLER EDIT ADDITION: add silent mode support
-/obj/docking_port/mobile/emergency/request(obj/docking_port/stationary/S, area/signal_origin, reason, red_alert, set_coefficient=null, silent=FALSE)
- if(!isnum(set_coefficient))
- set_coefficient = SSsecurity_level.current_security_level.shuttle_call_time_mod
- alert_coeff = set_coefficient
- var/call_time = SSshuttle.emergency_call_time * alert_coeff * engine_coeff
- switch(mode)
- // The shuttle can not normally be called while "recalling", so
- // if this proc is called, it's via admin fiat
- if(SHUTTLE_RECALL, SHUTTLE_IDLE, SHUTTLE_CALL)
- mode = SHUTTLE_CALL
- setTimer(call_time)
- else
- return
-
- SSshuttle.emergencyCallAmount++
-
- if(prob(70))
- SSshuttle.emergency_last_call_loc = signal_origin
- else
- SSshuttle.emergency_last_call_loc = null
-
- if(!silent) /// DOPPLER ADDITION BEGIN
- priority_announce(
- text = "The emergency shuttle has been called. [red_alert ? "Red Alert state confirmed: Dispatching priority shuttle. " : "" ]It will arrive in [(timeLeft(60 SECONDS))] minutes.[reason][SSshuttle.emergency_last_call_loc ? "\n\nCall signal traced. Results can be viewed on any communications console." : "" ][SSshuttle.admin_emergency_no_recall ? "\n\nWarning: Shuttle recall subroutines disabled; Recall not possible." : ""]",
- title = "Emergency Shuttle Dispatched",
- sound = ANNOUNCER_SHUTTLECALLED,
- sender_override = "Emergency Shuttle Uplink Alert",
- color_override = "orange",
- )
- /// DOPPLER EDIT END
-
-/obj/docking_port/mobile/emergency/cancel(area/signalOrigin)
- if(mode != SHUTTLE_CALL)
- return
- if(SSshuttle.emergency_no_recall)
- return
-
- invertTimer()
- mode = SHUTTLE_RECALL
-
- if(prob(70))
- SSshuttle.emergency_last_call_loc = signalOrigin
- else
- SSshuttle.emergency_last_call_loc = null
- priority_announce(
- text = "The emergency shuttle has been recalled.[SSshuttle.emergency_last_call_loc ? " Recall signal traced. Results can be viewed on any communications console." : "" ]",
- title = "Emergency Shuttle Recalled",
- sound = ANNOUNCER_SHUTTLERECALLED,
- sender_override = "Emergency Shuttle Uplink Alert",
- color_override = "orange",
- )
-
- SSticker.emergency_reason = null
-
-/**
- * Proc that handles checking if the emergency shuttle was successfully hijacked via being the only people present on the shuttle for the elimination hijack or highlander objective
- *
- * Checks for all mobs on the shuttle, checks their status, and checks if they're
- * borgs or simple animals. Depending on the args, certain mobs may be ignored,
- * and the presence of other antags may or may not invalidate a hijack.
- * Args:
- * filter_by_human, default TRUE, tells the proc that only humans should block a hijack. Borgs and animals are ignored and will not block if this is TRUE.
- * solo_hijack, default FALSE, tells the proc to fail with multiple hijackers, such as for Highlander mode.
- */
-/obj/docking_port/mobile/emergency/proc/elimination_hijack(filter_by_human = TRUE, solo_hijack = FALSE)
- var/has_people = FALSE
- var/hijacker_count = 0
- for(var/mob/living/player in GLOB.player_list)
- if(player.mind)
- if(player.stat != DEAD)
- if(issilicon(player) && filter_by_human) //Borgs are technically dead anyways
- continue
- if(isanimal_or_basicmob(player) && filter_by_human) //animals don't count
- continue
- if(isbrain(player)) //also technically dead
- continue
- if(shuttle_areas[get_area(player)])
- has_people = TRUE
- var/location = get_area(player.mind.current)
- //Non-antag present. Can't hijack.
- if(!(player.mind.has_antag_datum(/datum/antagonist)) && !istype(location, /area/shuttle/escape/brig))
- return FALSE
- //Antag present, doesn't stop but let's see if we actually want to hijack
- var/prevent = FALSE
- for(var/datum/antagonist/A in player.mind.antag_datums)
- if(A.can_elimination_hijack == ELIMINATION_ENABLED)
- hijacker_count += 1
- prevent = FALSE
- break //If we have both prevent and hijacker antags assume we want to hijack.
- else if(A.can_elimination_hijack == ELIMINATION_PREVENT)
- prevent = TRUE
- if(prevent)
- return FALSE
-
- //has people AND either there's only one hijacker or there's any but solo_hijack is disabled
- return has_people && ((hijacker_count == 1) || (hijacker_count && !solo_hijack))
-
-/obj/docking_port/mobile/emergency/proc/is_hijacked()
- return hijack_status == HIJACKED
-
-/obj/docking_port/mobile/emergency/proc/ShuttleDBStuff()
- set waitfor = FALSE
- if(!SSdbcore.Connect())
- return
- var/datum/db_query/query_round_shuttle_name = SSdbcore.NewQuery({"
- UPDATE [format_table_name("round")] SET shuttle_name = :name WHERE id = :round_id
- "}, list("name" = name, "round_id" = GLOB.round_id))
- query_round_shuttle_name.Execute()
- qdel(query_round_shuttle_name)
-
-/obj/docking_port/mobile/emergency/check()
- if(!timer)
- return
- var/time_left = timeLeft(1)
-
- // The emergency shuttle doesn't work like others so this
- // ripple check is slightly different
- if(!ripples.len && (time_left <= SHUTTLE_RIPPLE_TIME) && ((mode == SHUTTLE_CALL) || (mode == SHUTTLE_ESCAPE)))
- var/destination
- if(mode == SHUTTLE_CALL)
- destination = SSshuttle.getDock("emergency_home")
- else if(mode == SHUTTLE_ESCAPE)
- destination = SSshuttle.getDock("emergency_away")
- create_ripples(destination)
-
- switch(mode)
- if(SHUTTLE_RECALL)
- if(time_left <= 0)
- mode = SHUTTLE_IDLE
- timer = 0
- if(SHUTTLE_CALL)
- if(time_left <= 0)
- //move emergency shuttle to station
- if(initiate_docking(SSshuttle.getDock("emergency_home")) != DOCKING_SUCCESS)
- setTimer(20)
- return
- mode = SHUTTLE_DOCKED
- setTimer(SSshuttle.emergency_dock_time)
- send2adminchat("Server", "The Emergency Shuttle has docked with the station.")
- priority_announce(
- text = "[SSshuttle.emergency] has docked with the station. You have [DisplayTimeText(SSshuttle.emergency_dock_time)] to board the emergency shuttle.",
- title = "Emergency Shuttle Arrival",
- sound = ANNOUNCER_SHUTTLEDOCK,
- sender_override = "Emergency Shuttle Uplink Alert",
- color_override = "orange",
- )
- ShuttleDBStuff()
- addtimer(CALLBACK(src, PROC_REF(announce_shuttle_events)), 20 SECONDS)
-
-
- if(SHUTTLE_DOCKED)
- if(time_left <= ENGINES_START_TIME)
- mode = SHUTTLE_IGNITING
- SSshuttle.checkHostileEnvironment()
- if(mode == SHUTTLE_STRANDED)
- return
- for(var/A in SSshuttle.mobile_docking_ports)
- var/obj/docking_port/mobile/M = A
- if(M.launch_status == UNLAUNCHED) //Pods will not launch from the mine/planet, and other ships won't launch unless we tell them to.
- M.check_transit_zone()
-
- if(SHUTTLE_IGNITING)
- var/success = TRUE
- SSshuttle.checkHostileEnvironment()
- if(mode == SHUTTLE_STRANDED)
- return
-
- success &= (check_transit_zone() == TRANSIT_READY)
- for(var/A in SSshuttle.mobile_docking_ports)
- var/obj/docking_port/mobile/M = A
- if(M.launch_status == UNLAUNCHED)
- success &= (M.check_transit_zone() == TRANSIT_READY)
- if(!success)
- setTimer(ENGINES_START_TIME)
-
- if(time_left <= 50 && !sound_played) //4 seconds left:REV UP THOSE ENGINES BOYS. - should sync up with the launch
- sound_played = 1 //Only rev them up once.
- var/list/areas = list()
- for(var/area/shuttle/escape/E in GLOB.areas)
- areas += E
- hyperspace_sound(HYPERSPACE_WARMUP, areas)
-
- if(time_left <= 0 && !SSshuttle.emergency_no_escape)
- //move each escape pod (or applicable spaceship) to its corresponding transit dock
- for(var/A in SSshuttle.mobile_docking_ports)
- var/obj/docking_port/mobile/M = A
- M.on_emergency_launch()
-
- //now move the actual emergency shuttle to its transit dock
- var/list/areas = list()
- for(var/area/shuttle/escape/E in GLOB.areas)
- areas += E
- hyperspace_sound(HYPERSPACE_LAUNCH, areas)
- enterTransit()
-
- //Tell the events we're starting, so they can time their spawns or do some other stuff
- for(var/datum/shuttle_event/event as anything in event_list)
- event.start_up_event(SSshuttle.emergency_escape_time * engine_coeff)
-
- mode = SHUTTLE_ESCAPE
- launch_status = ENDGAME_LAUNCHED
- setTimer(SSshuttle.emergency_escape_time * engine_coeff)
- priority_announce(
- text = "The emergency shuttle has left the station. Estimate [timeLeft(60 SECONDS)] minutes until the shuttle docks at [command_name()].",
- title = "Emergency Shuttle Departure",
- sender_override = "Emergency Shuttle Uplink Alert",
- color_override = "orange",
- )
- INVOKE_ASYNC(SSticker, TYPE_PROC_REF(/datum/controller/subsystem/ticker, poll_hearts))
- INVOKE_ASYNC(SSvote, TYPE_PROC_REF(/datum/controller/subsystem/vote, initiate_vote), /datum/vote/map_vote, vote_initiator_name = "Map Rotation", forced = TRUE)
-
- if(!is_reserved_level(z))
- CRASH("Emergency shuttle did not move to transit z-level!")
-
- if(SHUTTLE_STRANDED, SHUTTLE_DISABLED)
- SSshuttle.checkHostileEnvironment()
-
-
- if(SHUTTLE_ESCAPE)
- if(sound_played && time_left <= HYPERSPACE_END_TIME)
- var/list/areas = list()
- for(var/area/shuttle/escape/E in GLOB.areas)
- areas += E
- hyperspace_sound(HYPERSPACE_END, areas)
- if(time_left <= PARALLAX_LOOP_TIME)
- var/area_parallax = FALSE
- for(var/place in shuttle_areas)
- var/area/shuttle/shuttle_area = place
- if(shuttle_area.parallax_movedir)
- area_parallax = TRUE
- break
- if(area_parallax)
- parallax_slowdown()
- for(var/A in SSshuttle.mobile_docking_ports)
- var/obj/docking_port/mobile/M = A
- if(M.launch_status == ENDGAME_LAUNCHED)
- if(istype(M, /obj/docking_port/mobile/pod))
- M.parallax_slowdown()
-
- process_events()
-
- if(time_left <= 0)
- //move each escape pod to its corresponding escape dock
- for(var/obj/docking_port/mobile/port as anything in SSshuttle.mobile_docking_ports)
- port.on_emergency_dock()
-
- // now move the actual emergency shuttle to centcom
- // unless the shuttle is "hijacked"
- var/destination_dock = "emergency_away"
- if(is_hijacked() || elimination_hijack())
- // just double check
- SSmapping.lazy_load_template(LAZY_TEMPLATE_KEY_NUKIEBASE)
- destination_dock = "emergency_syndicate"
- minor_announce("Corruption detected in \
- shuttle navigation protocols. Please contact your \
- supervisor.", "SYSTEM ERROR:", sound_override = 'sound/announcer/announcement/announce_syndi.ogg')
-
- dock_id(destination_dock)
- mode = SHUTTLE_ENDGAME
- timer = 0
-
-/obj/docking_port/mobile/emergency/transit_failure()
- ..()
- message_admins("Moving emergency shuttle directly to centcom dock to prevent deadlock.")
-
- mode = SHUTTLE_ESCAPE
- launch_status = ENDGAME_LAUNCHED
- setTimer(SSshuttle.emergency_escape_time)
- priority_announce(
- text = "The emergency shuttle is preparing for direct jump. Estimate [timeLeft(60 SECONDS)] minutes until the shuttle docks at [command_name()].",
- title = "Emergency Shuttle Transit Failure",
- sender_override = "Emergency Shuttle Uplink Alert",
- color_override = "orange",
- )
-
-///Generate a list of events to run during the departure
-/obj/docking_port/mobile/emergency/proc/setup_shuttle_events()
- var/list/names = list()
- for(var/datum/shuttle_event/event as anything in subtypesof(/datum/shuttle_event))
- if(prob(initial(event.event_probability)))
- add_shuttle_event(event)
- names += initial(event.name)
- if(LAZYLEN(names))
- log_game("[capitalize(name)] has selected the following shuttle events: [english_list(names)].")
-
-/obj/docking_port/mobile/monastery
- name = "monastery pod"
- shuttle_id = "mining_common" //set so mining can call it down
- launch_status = UNLAUNCHED //required for it to launch as a pod.
-
-/obj/docking_port/mobile/monastery/on_emergency_dock()
- if(launch_status == ENDGAME_LAUNCHED)
- initiate_docking(SSshuttle.getDock("pod_away")) //docks our shuttle as any pod would
- mode = SHUTTLE_ENDGAME
-
-/obj/docking_port/mobile/pod
- name = "escape pod"
- shuttle_id = "pod"
- launch_status = UNLAUNCHED
-
-/obj/docking_port/mobile/pod/request(obj/docking_port/stationary/S)
- var/obj/machinery/computer/shuttle/connected_computer = get_control_console()
- if(!istype(connected_computer, /obj/machinery/computer/shuttle/pod))
- return FALSE
- if(!(SSsecurity_level.get_current_level_as_number() >= SEC_LEVEL_RED) && !(connected_computer.obj_flags & EMAGGED))
- to_chat(usr, span_warning("Escape pods will only launch during \"Code Red\" security alert."))
- return FALSE
- if(launch_status == UNLAUNCHED)
- launch_status = EARLY_LAUNCHED
- return ..()
-
-/obj/docking_port/mobile/pod/cancel()
- return
-
-/obj/machinery/computer/shuttle/pod
- name = "pod control computer"
- locked = TRUE
- possible_destinations = "pod_asteroid"
- icon = 'icons/obj/machines/wallmounts.dmi'
- icon_state = "pod_off"
- circuit = /obj/item/circuitboard/computer/emergency_pod
- light_color = LIGHT_COLOR_BLUE
- density = FALSE
- icon_keyboard = null
- icon_screen = "pod_on"
-
-/obj/machinery/computer/shuttle/pod/Initialize(mapload)
- . = ..()
- RegisterSignal(SSsecurity_level, COMSIG_SECURITY_LEVEL_CHANGED, PROC_REF(check_lock))
-
-/obj/machinery/computer/shuttle/pod/emag_act(mob/user, obj/item/card/emag/emag_card)
- if(obj_flags & EMAGGED)
- return FALSE
- obj_flags |= EMAGGED
- locked = FALSE
- balloon_alert(user, "alert level checking disabled")
- icon_screen = "emagged_general"
- update_appearance()
- return TRUE
-
-/obj/machinery/computer/shuttle/pod/connect_to_shuttle(mapload, obj/docking_port/mobile/port, obj/docking_port/stationary/dock)
- . = ..()
- if(port)
- //Checks if the computer has already added the shuttle destination with the initial id
- //This has to be done because connect_to_shuttle is called again after its ID is updated
- //due to conflicting id names
- var/base_shuttle_destination = ";[initial(port.shuttle_id)]_lavaland"
- var/shuttle_destination = ";[port.shuttle_id]_lavaland"
-
- var/position = findtext(possible_destinations, base_shuttle_destination)
- if(position)
- if(base_shuttle_destination == shuttle_destination)
- return
- possible_destinations = splicetext(possible_destinations, position, position + length(base_shuttle_destination), shuttle_destination)
- return
-
- possible_destinations += shuttle_destination
-
-/**
- * Signal handler for checking if we should lock or unlock escape pods accordingly to a newly set security level
- *
- * Arguments:
- * * source The datum source of the signal
- * * new_level The new security level that is in effect
- */
-/obj/machinery/computer/shuttle/pod/proc/check_lock(datum/source, new_level)
- SIGNAL_HANDLER
-
- if(obj_flags & EMAGGED)
- return
- locked = (new_level < SEC_LEVEL_RED)
-
-/obj/docking_port/stationary/random
- name = "escape pod"
- shuttle_id = "pod"
- hidden = TRUE
- override_can_dock_checks = TRUE
- /// The area the pod tries to land at
- var/target_area = /area/lavaland/surface/outdoors
- /// Minimal distance from the map edge, setting this too low can result in shuttle landing on the edge and getting "sliced"
- var/edge_distance = 16
-
-/obj/docking_port/stationary/random/Initialize(mapload)
- . = ..()
- if(!mapload)
- return
-
- var/list/turfs = get_area_turfs(target_area)
- var/original_len = turfs.len
- while(turfs.len)
- var/turf/picked_turf = pick(turfs)
- if(picked_turf.x stationary_dock.dwidth)
+ return SHUTTLE_DWIDTH_TOO_LARGE
+
+ if(width-dwidth > stationary_dock.width-stationary_dock.dwidth)
+ return SHUTTLE_WIDTH_TOO_LARGE
+
+ if(dheight > stationary_dock.dheight)
+ return SHUTTLE_DHEIGHT_TOO_LARGE
+
+ if(height-dheight > stationary_dock.height-stationary_dock.dheight)
+ return SHUTTLE_HEIGHT_TOO_LARGE
+
+ //check the dock isn't occupied
+ var/currently_docked = stationary_dock.get_docked()
+ if(currently_docked)
+ // by someone other than us
+ if(currently_docked != src)
+ return SHUTTLE_SOMEONE_ELSE_DOCKED
+ else
+ // This isn't an error, per se, but we can't let the shuttle code
+ // attempt to move us where we currently are, it will get weird.
+ return SHUTTLE_ALREADY_DOCKED
+
+ return SHUTTLE_CAN_DOCK
+
+/obj/docking_port/mobile/proc/check_dock(obj/docking_port/stationary/S, silent = FALSE)
+ var/status = canDock(S)
+ if(status == SHUTTLE_CAN_DOCK)
+ return TRUE
+ else
+ if(status != SHUTTLE_ALREADY_DOCKED && !silent) // SHUTTLE_ALREADY_DOCKED is no cause for error
+ message_admins("Shuttle [src] cannot dock at [S], error: [status]")
+ // We're already docked there, don't need to do anything.
+ // Triggering shuttle movement code in place is weird
+ return FALSE
+
+/obj/docking_port/mobile/proc/transit_failure()
+ message_admins("Shuttle [src] repeatedly failed to create transit zone.")
+
+/**
+ * Calls the shuttle to the destination port, respecting its ignition and call timers
+ *
+ * Arguments:
+ * * destination_port - Stationary docking port to move the shuttle to
+ */
+/obj/docking_port/mobile/proc/request(obj/docking_port/stationary/destination_port)
+ if(!check_dock(destination_port))
+ testing("check_dock failed on request for [src]")
+ return
+
+ if(mode == SHUTTLE_IGNITING && destination == destination_port)
+ return
+
+ switch(mode)
+ if(SHUTTLE_CALL)
+ if(destination_port == destination)
+ if(timeLeft(1) < callTime * engine_coeff)
+ setTimer(callTime * engine_coeff)
+ else
+ destination = destination_port
+ setTimer(callTime * engine_coeff)
+ if(SHUTTLE_RECALL)
+ if(destination_port == destination)
+ setTimer(callTime * engine_coeff - timeLeft(1))
+ else
+ destination = destination_port
+ setTimer(callTime * engine_coeff)
+ mode = SHUTTLE_CALL
+ if(SHUTTLE_IDLE, SHUTTLE_IGNITING)
+ destination = destination_port
+ mode = SHUTTLE_IGNITING
+ setTimer(ignitionTime)
+
+//recall the shuttle to where it was previously
+/obj/docking_port/mobile/proc/cancel()
+ if(mode != SHUTTLE_CALL)
+ return
+
+ remove_ripples()
+
+ invertTimer()
+ mode = SHUTTLE_RECALL
+
+/obj/docking_port/mobile/proc/enterTransit()
+ if((SSshuttle.lockdown && is_station_level(z)) || !canMove()) //emp went off, no escape
+ mode = SHUTTLE_IDLE
+ return
+ previous = null
+ if(!destination)
+ // sent to transit with no destination -> unlimited timer
+ timer = INFINITY
+ var/obj/docking_port/stationary/S0 = get_docked()
+ var/obj/docking_port/stationary/S1 = assigned_transit
+ if(S1)
+ if(initiate_docking(S1) != DOCKING_SUCCESS)
+ WARNING("shuttle \"[shuttle_id]\" could not enter transit space. Docked at [S0 ? S0.shuttle_id : "null"]. Transit dock [S1 ? S1.shuttle_id : "null"].")
+ else if(S0)
+ if(S0.delete_after)
+ qdel(S0, TRUE)
+ else
+ previous = S0
+ else
+ WARNING("shuttle \"[shuttle_id]\" could not enter transit space. S0=[S0 ? S0.shuttle_id : "null"] S1=[S1 ? S1.shuttle_id : "null"]")
+
+
+/obj/docking_port/mobile/proc/jumpToNullSpace()
+ // Destroys the docking port and the shuttle contents.
+ // Not in a fancy way, it just ceases.
+ var/obj/docking_port/stationary/current_dock = get_docked()
+
+ var/underlying_area_type = SHUTTLE_DEFAULT_UNDERLYING_AREA
+ // If the shuttle is docked to a stationary port, restore its normal
+ // "empty" area and turf
+ if(current_dock?.area_type)
+ underlying_area_type = current_dock.area_type
+
+ var/list/old_turfs = return_ordered_turfs(x, y, z, dir)
+
+ var/area/underlying_area = GLOB.areas_by_type[underlying_area_type]
+ if(!underlying_area)
+ underlying_area = new underlying_area_type(null)
+
+ for(var/i in 1 to old_turfs.len)
+ var/turf/oldT = old_turfs[i]
+ if(!oldT || !istype(oldT.loc, area_type))
+ continue
+ oldT.change_area(oldT.loc, underlying_area)
+ oldT.empty(FALSE)
+
+ // Here we locate the bottommost shuttle boundary and remove all turfs above it
+ var/shuttle_tile_depth = oldT.depth_to_find_baseturf(/turf/baseturf_skipover/shuttle)
+ if (!isnull(shuttle_tile_depth))
+ oldT.ScrapeAway(shuttle_tile_depth)
+
+ qdel(src, force=TRUE)
+
+/**
+ * Ghosts and marks as escaped (for greentext purposes) all mobs, then deletes the shuttle.
+ * Used by the Shuttle Manipulator
+ */
+/obj/docking_port/mobile/proc/intoTheSunset()
+ // Loop over mobs
+ for(var/turf/turfs as anything in return_turfs())
+ for(var/mob/living/sunset_mobs in turfs.get_all_contents())
+ // If they have a mind and they're not in the brig, they escaped
+ if(sunset_mobs.mind && !istype(get_area(sunset_mobs), /area/shuttle/escape/brig))
+ sunset_mobs.mind.force_escaped = TRUE
+ // Ghostize them and put them in nullspace stasis (for stat & possession checks)
+ ADD_TRAIT(sunset_mobs, TRAIT_NO_TRANSFORM, REF(src))
+ sunset_mobs.ghostize(FALSE)
+ sunset_mobs.moveToNullspace()
+
+ // Now that mobs are stowed, delete the shuttle
+ jumpToNullSpace()
+
+/obj/docking_port/mobile/proc/create_ripples(obj/docking_port/stationary/S1, animate_time)
+ var/list/turfs = ripple_area(S1)
+ for(var/t in turfs)
+ ripples += new /obj/effect/abstract/ripple(t, animate_time)
+
+/obj/docking_port/mobile/proc/remove_ripples()
+ QDEL_LIST(ripples)
+
+/obj/docking_port/mobile/proc/ripple_area(obj/docking_port/stationary/S1)
+ var/list/L0 = return_ordered_turfs(x, y, z, dir)
+ var/list/L1 = return_ordered_turfs(S1.x, S1.y, S1.z, S1.dir)
+
+ var/list/ripple_turfs = list()
+ var/stop = min(L0.len, L1.len)
+ for(var/i in 1 to stop)
+ var/turf/T0 = L0[i]
+ var/turf/T1 = L1[i]
+ if(!istype(T0.loc, area_type) || istype(T0.loc, /area/shuttle/transit))
+ continue // not part of the shuttle
+ ripple_turfs += T1
+
+ return ripple_turfs
+
+/obj/docking_port/mobile/proc/check_poddoors()
+ for(var/obj/machinery/door/poddoor/shuttledock/pod as anything in SSmachines.get_machines_by_type_and_subtypes(/obj/machinery/door/poddoor/shuttledock))
+ pod.check()
+
+/obj/docking_port/mobile/proc/dock_id(id)
+ var/port = SSshuttle.getDock(id)
+ if(port)
+ . = initiate_docking(port)
+ else
+ . = null
+
+//used by shuttle subsystem to check timers
+/obj/docking_port/mobile/proc/check()
+ check_effects()
+ //process_events() if you were to add events to non-escape shuttles, uncomment this
+
+ if(mode == SHUTTLE_IGNITING)
+ check_transit_zone()
+
+ if(timeLeft(1) > 0)
+ return
+ // If we can't dock or we don't have a transit slot, wait for 20 ds,
+ // then try again
+ switch(mode)
+ if(SHUTTLE_CALL, SHUTTLE_PREARRIVAL)
+ if(prearrivalTime && mode != SHUTTLE_PREARRIVAL)
+ mode = SHUTTLE_PREARRIVAL
+ setTimer(prearrivalTime)
+ return
+ var/error = initiate_docking(destination, preferred_direction)
+ if(error && error & (DOCKING_NULL_DESTINATION | DOCKING_NULL_SOURCE))
+ var/msg = "A mobile dock in transit exited initiate_docking() with an error. This is most likely a mapping problem: Error: [error], ([src]) ([previous][ADMIN_JMP(previous)] -> [destination][ADMIN_JMP(destination)])"
+ WARNING(msg)
+ message_admins(msg)
+ mode = SHUTTLE_IDLE
+ return
+ else if(error)
+ setTimer(20)
+ return
+ if(rechargeTime)
+ mode = SHUTTLE_RECHARGING
+ setTimer(rechargeTime)
+ return
+ if(SHUTTLE_RECALL)
+ if(initiate_docking(previous) != DOCKING_SUCCESS)
+ setTimer(20)
+ return
+ if(SHUTTLE_IGNITING)
+ if(check_transit_zone() != TRANSIT_READY)
+ setTimer(20)
+ return
+ else
+ mode = SHUTTLE_CALL
+ setTimer(callTime * engine_coeff)
+ enterTransit()
+ return
+
+ mode = SHUTTLE_IDLE
+ timer = 0
+ destination = null
+
+/obj/docking_port/mobile/proc/check_effects()
+ if(!ripples.len)
+ if((mode == SHUTTLE_CALL) || (mode == SHUTTLE_RECALL))
+ var/tl = timeLeft(1)
+ if(tl <= SHUTTLE_RIPPLE_TIME)
+ create_ripples(destination, tl)
+
+ var/obj/docking_port/stationary/S0 = get_docked()
+ if(istype(S0, /obj/docking_port/stationary/transit) && timeLeft(1) <= PARALLAX_LOOP_TIME)
+ for(var/place in shuttle_areas)
+ var/area/shuttle/shuttle_area = place
+ if(shuttle_area.parallax_movedir)
+ parallax_slowdown()
+
+/obj/docking_port/mobile/proc/parallax_slowdown()
+ for(var/place in shuttle_areas)
+ var/area/shuttle/shuttle_area = place
+ shuttle_area.parallax_movedir = FALSE
+ if(assigned_transit?.assigned_area)
+ assigned_transit.assigned_area.parallax_movedir = FALSE
+ var/list/L0 = return_ordered_turfs(x, y, z, dir)
+ for (var/thing in L0)
+ var/turf/T = thing
+ if(!T || !istype(T.loc, area_type))
+ continue
+ for (var/atom/movable/movable as anything in T)
+ if (movable.client_mobs_in_contents)
+ movable.update_parallax_contents()
+
+/obj/docking_port/mobile/proc/check_transit_zone()
+ if(assigned_transit)
+ return TRANSIT_READY
+ else
+ SSshuttle.request_transit_dock(src)
+
+/obj/docking_port/mobile/proc/setTimer(wait)
+ timer = world.time + wait
+ last_timer_length = wait
+
+/obj/docking_port/mobile/proc/modTimer(multiple)
+ var/time_remaining = timer - world.time
+ if(time_remaining < 0 || !last_timer_length)
+ return
+ time_remaining *= multiple
+ last_timer_length *= multiple
+ setTimer(time_remaining)
+
+/obj/docking_port/mobile/proc/alert_coeff_change(new_coeff)
+ if(isnull(new_coeff))
+ return
+
+ var/time_multiplier = new_coeff / alert_coeff
+ var/time_remaining = timer - world.time
+ if(time_remaining < 0 || !last_timer_length)
+ return
+
+ time_remaining *= time_multiplier
+ last_timer_length *= time_multiplier
+ alert_coeff = new_coeff
+ setTimer(time_remaining)
+
+/obj/docking_port/mobile/proc/invertTimer()
+ if(!last_timer_length)
+ return
+ var/time_remaining = timer - world.time
+ if(time_remaining > 0)
+ var/time_passed = last_timer_length - time_remaining
+ setTimer(time_passed)
+
+//returns timeLeft
+/obj/docking_port/mobile/proc/timeLeft(divisor)
+ if(divisor <= 0)
+ divisor = 10
+
+ var/ds_remaining
+ if(!timer)
+ ds_remaining = callTime * engine_coeff
+ else
+ ds_remaining = max(0, timer - world.time)
+
+ . = round(ds_remaining / divisor, 1)
+
+// returns 3-letter mode string, used by status screens and mob status panel
+/obj/docking_port/mobile/proc/getModeStr()
+ switch(mode)
+ if(SHUTTLE_IGNITING)
+ return "IGN"
+ if(SHUTTLE_RECALL)
+ return "RCL"
+ if(SHUTTLE_CALL)
+ return "ETA"
+ if(SHUTTLE_DOCKED)
+ return "ETD"
+ if(SHUTTLE_ESCAPE)
+ return "ESC"
+ if(SHUTTLE_STRANDED)
+ return "ERR"
+ if(SHUTTLE_RECHARGING)
+ return "RCH"
+ if(SHUTTLE_PREARRIVAL)
+ return "LDN"
+ if(SHUTTLE_DISABLED)
+ return "DIS"
+ return ""
+
+// returns 5-letter timer string, used by status screens and mob status panel
+/obj/docking_port/mobile/proc/getTimerStr()
+ if(mode == SHUTTLE_STRANDED || mode == SHUTTLE_DISABLED)
+ return "--:--"
+
+ var/timeleft = timeLeft()
+ if(timeleft > 1 HOURS)
+ return "--:--"
+ else if(timeleft > 0)
+ return "[add_leading(num2text((timeleft / 60) % 60), 2, "0")]:[add_leading(num2text(timeleft % 60), 2, "0")]"
+ else
+ return "00:00"
+
+/**
+ * Gets shuttle location status in a form of string for tgui interfaces
+ */
+/obj/docking_port/mobile/proc/get_status_text_tgui()
+ var/obj/docking_port/stationary/dockedAt = get_docked()
+ var/docked_at = dockedAt?.name || "Unknown"
+ if(!istype(dockedAt, /obj/docking_port/stationary/transit))
+ return docked_at
+ if(timeLeft() > 1 HOURS)
+ return "Hyperspace"
+ else
+ var/obj/docking_port/stationary/dst = (mode == SHUTTLE_RECALL) ? previous : destination
+ return "In transit to [dst?.name || "unknown location"]"
+
+/obj/docking_port/mobile/proc/getStatusText()
+ var/obj/docking_port/stationary/dockedAt = get_docked()
+ var/docked_at = dockedAt?.name || "unknown"
+ if(istype(dockedAt, /obj/docking_port/stationary/transit))
+ if (timeLeft() > 1 HOURS)
+ return "hyperspace"
+ else
+ var/obj/docking_port/stationary/dst
+ if(mode == SHUTTLE_RECALL)
+ dst = previous
+ else
+ dst = destination
+ . = "transit towards [dst?.name || "unknown location"] ([getTimerStr()])"
+ else if(mode == SHUTTLE_RECHARGING)
+ return "[docked_at], recharging [getTimerStr()]"
+ else
+ return docked_at
+
+/obj/docking_port/mobile/proc/getDbgStatusText()
+ var/obj/docking_port/stationary/dockedAt = get_docked()
+ . = (dockedAt?.name) ? dockedAt.name : "unknown"
+ if(istype(dockedAt, /obj/docking_port/stationary/transit))
+ var/obj/docking_port/stationary/dst
+ if(mode == SHUTTLE_RECALL)
+ dst = previous
+ else
+ dst = destination
+ if(dst)
+ . = "(transit to) [dst.name || dst.shuttle_id]"
+ else
+ . = "(transit to) nowhere"
+ else if(dockedAt)
+ . = dockedAt.name || dockedAt.shuttle_id
+ else
+ . = "unknown"
+
+
+// attempts to locate /obj/machinery/computer/shuttle with matching ID inside the shuttle
+/obj/docking_port/mobile/proc/get_control_console()
+ for(var/area/shuttle/shuttle_area as anything in shuttle_areas)
+ var/obj/machinery/computer/shuttle/shuttle_computer = locate(/obj/machinery/computer/shuttle) in shuttle_area
+ if(!shuttle_computer)
+ continue
+ if(shuttle_computer.shuttleId == shuttle_id)
+ return shuttle_computer
+ return null
+
+/obj/docking_port/mobile/proc/hyperspace_sound(phase, list/areas)
+ var/selected_sound
+ switch(phase)
+ if(HYPERSPACE_WARMUP)
+ selected_sound = "hyperspace_begin"
+ if(HYPERSPACE_LAUNCH)
+ selected_sound = "hyperspace_progress"
+ if(HYPERSPACE_END)
+ selected_sound = "hyperspace_end"
+ else
+ CRASH("Invalid hyperspace sound phase: [phase]")
+ // This previously was played from each door at max volume, and was one of the worst things I had ever seen.
+ // Now it's instead played from the nearest engine if close, or the first engine in the list if far since it doesn't really matter.
+ // Or a door if for some reason the shuttle has no engine, fuck oh hi daniel fuck it
+ var/range = (engine_coeff * max(width, height))
+ var/long_range = range * 2.5
+ var/atom/distant_source
+
+ if(engine_list.len)
+ distant_source = engine_list[1]
+ else
+ for(var/our_area in areas)
+ distant_source = locate(/obj/machinery/door) in our_area
+ if(distant_source)
+ break
+
+ if(!distant_source)
+ return
+ for(var/mob/zlevel_mobs as anything in SSmobs.clients_by_zlevel[z])
+ var/dist_far = get_dist(zlevel_mobs, distant_source)
+ if(dist_far <= long_range && dist_far > range)
+ zlevel_mobs.playsound_local(distant_source, "sound/runtime/hyperspace/[selected_sound]_distance.ogg", 100)
+ else if(dist_far <= range)
+ var/source
+ if(!engine_list.len)
+ source = distant_source
+ else
+ var/closest_dist = 10000
+ for(var/obj/machinery/power/shuttle_engine/engines as anything in engine_list)
+ var/dist_near = get_dist(zlevel_mobs, engines)
+ if(dist_near < closest_dist)
+ source = engines
+ closest_dist = dist_near
+ zlevel_mobs.playsound_local(source, "sound/runtime/hyperspace/[selected_sound].ogg", 100)
+
+// Losing all initial engines should get you 2
+// Adding another set of engines at 0.5 time
+/obj/docking_port/mobile/proc/alter_engines(mod)
+ if(!mod)
+ return
+ var/old_coeff = engine_coeff
+ engine_coeff = get_engine_coeff(mod)
+ current_engine_power = max(0, current_engine_power + mod)
+ if(in_flight())
+ var/delta_coeff = engine_coeff / old_coeff
+ modTimer(delta_coeff)
+
+// Double initial engines to get to 0.5 minimum
+// Lose all initial engines to get to 2
+//For 0 engine shuttles like BYOS 5 engines to get to doublespeed
+/obj/docking_port/mobile/proc/get_engine_coeff(engine_mod)
+ var/new_value = max(0, current_engine_power + engine_mod)
+ if(new_value == initial_engine_power)
+ return 1
+ if(new_value > initial_engine_power)
+ var/delta = new_value - initial_engine_power
+ var/change_per_engine = (1 - ENGINE_COEFF_MIN) / ENGINE_DEFAULT_MAXSPEED_ENGINES // 5 by default
+ if(initial_engine_power > 0)
+ change_per_engine = (1 - ENGINE_COEFF_MIN) / initial_engine_power // or however many it had
+ return clamp(1 - delta * change_per_engine,ENGINE_COEFF_MIN, ENGINE_COEFF_MAX)
+ if(new_value < initial_engine_power)
+ var/delta = initial_engine_power - new_value
+ var/change_per_engine = 1 //doesn't really matter should not be happening for 0 engine shuttles
+ if(initial_engine_power > 0)
+ change_per_engine = (ENGINE_COEFF_MAX - 1) / initial_engine_power //just linear drop to max delay
+ return clamp(1 + delta * change_per_engine, ENGINE_COEFF_MIN, ENGINE_COEFF_MAX)
+
+
+/obj/docking_port/mobile/proc/in_flight()
+ switch(mode)
+ if(SHUTTLE_CALL,SHUTTLE_RECALL,SHUTTLE_PREARRIVAL)
+ return TRUE
+ if(SHUTTLE_IDLE,SHUTTLE_IGNITING)
+ return FALSE
+ return FALSE // hmm
+
+/obj/docking_port/mobile/emergency/in_flight()
+ switch(mode)
+ if(SHUTTLE_ESCAPE)
+ return TRUE
+ if(SHUTTLE_STRANDED,SHUTTLE_ENDGAME)
+ return FALSE
+ return ..()
+
+//Called when emergency shuttle leaves the station
+/obj/docking_port/mobile/proc/on_emergency_launch()
+ if(launch_status == UNLAUNCHED) //Pods will not launch from the mine/planet, and other ships won't launch unless we tell them to.
+ launch_status = ENDGAME_LAUNCHED
+ enterTransit()
+
+///Let people know shits about to go down
+/obj/docking_port/mobile/proc/announce_shuttle_events()
+ for(var/datum/shuttle_event/event as anything in event_list)
+ notify_ghosts("The [name] has selected: [event.name]")
+
+/obj/docking_port/mobile/emergency/on_emergency_launch()
+ return
+
+//Called when emergency shuttle docks at centcom
+/obj/docking_port/mobile/proc/on_emergency_dock()
+ // Mapping a new docking point for each ship mappers could potentially want docking with centcom would take up lots of space,
+ // just let them keep flying off "into the sunset" for their greentext.
+ if(launch_status == ENDGAME_LAUNCHED)
+ launch_status = ENDGAME_TRANSIT
+
+/obj/docking_port/mobile/pod/on_emergency_dock()
+ if(launch_status == ENDGAME_LAUNCHED)
+ initiate_docking(SSshuttle.getDock("[shuttle_id]_away")) //Escape pods dock at centcom
+ mode = SHUTTLE_ENDGAME
+
+/obj/docking_port/mobile/emergency/on_emergency_dock()
+ return
+
+///Process all the shuttle events for every shuttle tick we get
+/obj/docking_port/mobile/proc/process_events()
+ var/list/removees
+ for(var/datum/shuttle_event/event as anything in event_list)
+ if(event.event_process() == SHUTTLE_EVENT_CLEAR) //if we return SHUTTLE_EVENT_CLEAR, we clean them up
+ LAZYADD(removees, event)
+ for(var/item in removees)
+ event_list.Remove(item)
+
+/// Give a typepath of a shuttle event to add to the shuttle. If added during endgame transit, will insta start the event
+/obj/docking_port/mobile/proc/add_shuttle_event(typepath)
+ var/datum/shuttle_event/event = new typepath (src)
+ event_list.Add(event)
+ if(launch_status == ENDGAME_LAUNCHED)
+ event.start_up_event(0)
+ return event
diff --git a/code/modules/shuttle/docking.dm b/code/modules/shuttle/mobile_port/shuttle_move.dm
similarity index 98%
rename from code/modules/shuttle/docking.dm
rename to code/modules/shuttle/mobile_port/shuttle_move.dm
index 32a1ca4950afa..b7e125826dce2 100644
--- a/code/modules/shuttle/docking.dm
+++ b/code/modules/shuttle/mobile_port/shuttle_move.dm
@@ -1,4 +1,5 @@
-/// This is the main proc. It instantly moves our mobile port to stationary port `new_dock`.
+/// This is the main proc. Despite what the name suggests,
+/// it instantly moves our mobile port to stationary port `new_dock`.
/obj/docking_port/mobile/proc/initiate_docking(obj/docking_port/stationary/new_dock, movement_direction, force=FALSE)
// Crashing this ship with NO SURVIVORS
diff --git a/code/modules/shuttle/on_move.dm b/code/modules/shuttle/mobile_port/shuttle_move_callbacks.dm
similarity index 99%
rename from code/modules/shuttle/on_move.dm
rename to code/modules/shuttle/mobile_port/shuttle_move_callbacks.dm
index d4f7c3ddfcd09..1aac6e4c1aec8 100644
--- a/code/modules/shuttle/on_move.dm
+++ b/code/modules/shuttle/mobile_port/shuttle_move_callbacks.dm
@@ -230,11 +230,6 @@ All ShuttleMove procs go here
. = ..()
recharging_turf = get_step(loc, dir)
-/obj/machinery/atmospherics/afterShuttleMove(turf/oldT, list/movement_force, shuttle_dir, shuttle_preferred_direction, move_dir, rotation)
- . = ..()
- if(pipe_vision_img)
- pipe_vision_img.loc = loc
-
/obj/machinery/computer/auxiliary_base/afterShuttleMove(turf/oldT, list/movement_force, shuttle_dir, shuttle_preferred_direction, move_dir, rotation)
. = ..()
if(is_mining_level(z)) //Avoids double logging and landing on other Z-levels due to badminnery
@@ -242,6 +237,9 @@ All ShuttleMove procs go here
/obj/machinery/atmospherics/afterShuttleMove(turf/oldT, list/movement_force, shuttle_dir, shuttle_preferred_direction, move_dir, rotation)
. = ..()
+ if(pipe_vision_img)
+ pipe_vision_img.loc = loc
+
var/missing_nodes = FALSE
for(var/i in 1 to device_type)
if(nodes[i])
diff --git a/code/modules/shuttle/shuttle_rotate.dm b/code/modules/shuttle/mobile_port/shuttle_rotate_callbacks.dm
similarity index 94%
rename from code/modules/shuttle/shuttle_rotate.dm
rename to code/modules/shuttle/mobile_port/shuttle_rotate_callbacks.dm
index 15af6db6a4f6d..7afd43a0d3540 100644
--- a/code/modules/shuttle/shuttle_rotate.dm
+++ b/code/modules/shuttle/mobile_port/shuttle_rotate_callbacks.dm
@@ -82,7 +82,11 @@ If ever any of these procs are useful for non-shuttles, rename it to proc/rotate
/obj/machinery/atmospherics/shuttleRotate(rotation, params)
var/list/real_node_connect = get_node_connects()
for(var/i in 1 to device_type)
- real_node_connect[i] = angle2dir(rotation+dir2angle(real_node_connect[i]))
+ var/node_dir = real_node_connect[i]
+ if(isnull(node_dir))
+ continue
+
+ real_node_connect[i] = turn(node_dir, -rotation)
. = ..()
set_init_directions()
@@ -90,7 +94,11 @@ If ever any of these procs are useful for non-shuttles, rename it to proc/rotate
var/list/nodes_copy = nodes.Copy()
for(var/i in 1 to device_type)
- var/new_pos = supposed_node_connect.Find(real_node_connect[i])
+ var/node_dir = real_node_connect[i]
+ if(isnull(node_dir))
+ continue
+
+ var/new_pos = supposed_node_connect.Find(node_dir)
nodes[new_pos] = nodes_copy[i]
//prevents shuttles attempting to rotate this since it messes up sprites
diff --git a/code/modules/shuttle/arrivals.dm b/code/modules/shuttle/mobile_port/variants/arrivals.dm
similarity index 100%
rename from code/modules/shuttle/arrivals.dm
rename to code/modules/shuttle/mobile_port/variants/arrivals.dm
diff --git a/code/modules/shuttle/assault_pod.dm b/code/modules/shuttle/mobile_port/variants/assault_pod.dm
similarity index 100%
rename from code/modules/shuttle/assault_pod.dm
rename to code/modules/shuttle/mobile_port/variants/assault_pod.dm
diff --git a/code/modules/shuttle/battlecruiser_starfury.dm b/code/modules/shuttle/mobile_port/variants/battlecruiser_starfury.dm
similarity index 100%
rename from code/modules/shuttle/battlecruiser_starfury.dm
rename to code/modules/shuttle/mobile_port/variants/battlecruiser_starfury.dm
diff --git a/code/modules/shuttle/elevator.dm b/code/modules/shuttle/mobile_port/variants/elevator.dm
similarity index 100%
rename from code/modules/shuttle/elevator.dm
rename to code/modules/shuttle/mobile_port/variants/elevator.dm
diff --git a/code/modules/shuttle/mobile_port/variants/emergency/emergency.dm b/code/modules/shuttle/mobile_port/variants/emergency/emergency.dm
new file mode 100644
index 0000000000000..58fe035fe8586
--- /dev/null
+++ b/code/modules/shuttle/mobile_port/variants/emergency/emergency.dm
@@ -0,0 +1,313 @@
+/obj/docking_port/mobile/emergency
+ name = "emergency shuttle"
+ shuttle_id = "emergency"
+ dir = EAST
+ port_direction = WEST
+ var/sound_played = 0 //If the launch sound has been sent to all players on the shuttle itself
+ var/hijack_status = HIJACK_NOT_BEGUN
+
+/obj/docking_port/mobile/emergency/Initialize(mapload)
+ . = ..()
+
+ setup_shuttle_events()
+
+/obj/docking_port/mobile/emergency/canDock(obj/docking_port/stationary/S)
+ return SHUTTLE_CAN_DOCK //If the emergency shuttle can't move, the whole game breaks, so it will force itself to land even if it has to crush a few departments in the process
+
+/obj/docking_port/mobile/emergency/register()
+ . = ..()
+ SSshuttle.emergency = src
+
+/obj/docking_port/mobile/emergency/Destroy(force)
+ if(force)
+ // This'll make the shuttle subsystem use the backup shuttle.
+ if(src == SSshuttle.emergency)
+ // If we're the selected emergency shuttle
+ SSshuttle.emergencyDeregister()
+
+ . = ..()
+
+/obj/docking_port/mobile/emergency/request(obj/docking_port/stationary/S, area/signal_origin, reason, red_alert, set_coefficient=null, silent = FALSE) // DOPPLER EDIT - ORIGINAL - /obj/docking_port/mobile/emergency/request(obj/docking_port/stationary/S, area/signal_origin, reason, red_alert, set_coefficient=null)
+ if(!isnum(set_coefficient))
+ set_coefficient = SSsecurity_level.current_security_level.shuttle_call_time_mod
+ alert_coeff = set_coefficient
+ var/call_time = SSshuttle.emergency_call_time * alert_coeff * engine_coeff
+ switch(mode)
+ // The shuttle can not normally be called while "recalling", so
+ // if this proc is called, it's via admin fiat
+ if(SHUTTLE_RECALL, SHUTTLE_IDLE, SHUTTLE_CALL)
+ mode = SHUTTLE_CALL
+ setTimer(call_time)
+ else
+ return
+
+ SSshuttle.emergencyCallAmount++
+
+ if(prob(70))
+ SSshuttle.emergency_last_call_loc = signal_origin
+ else
+ SSshuttle.emergency_last_call_loc = null
+
+ if(silent) // DOPPLER EDIT ADDITION - SEE modular_doppler/autotransfer/shuttle.dm
+ return // DOPPLER EDIT ADDITION
+ priority_announce(
+ text = "The emergency shuttle has been called. [red_alert ? "Red Alert state confirmed: Dispatching priority shuttle. " : "" ]It will arrive in [(timeLeft(60 SECONDS))] minutes.[reason][SSshuttle.emergency_last_call_loc ? "\n\nCall signal traced. Results can be viewed on any communications console." : "" ][SSshuttle.admin_emergency_no_recall ? "\n\nWarning: Shuttle recall subroutines disabled; Recall not possible." : ""]",
+ title = "Emergency Shuttle Dispatched",
+ sound = ANNOUNCER_SHUTTLECALLED,
+ sender_override = "Emergency Shuttle Uplink Alert",
+ color_override = "orange",
+ )
+
+/obj/docking_port/mobile/emergency/cancel(area/signalOrigin)
+ if(mode != SHUTTLE_CALL)
+ return
+ if(SSshuttle.emergency_no_recall)
+ return
+
+ invertTimer()
+ mode = SHUTTLE_RECALL
+
+ if(prob(70))
+ SSshuttle.emergency_last_call_loc = signalOrigin
+ else
+ SSshuttle.emergency_last_call_loc = null
+ priority_announce(
+ text = "The emergency shuttle has been recalled.[SSshuttle.emergency_last_call_loc ? " Recall signal traced. Results can be viewed on any communications console." : "" ]",
+ title = "Emergency Shuttle Recalled",
+ sound = ANNOUNCER_SHUTTLERECALLED,
+ sender_override = "Emergency Shuttle Uplink Alert",
+ color_override = "orange",
+ )
+
+ SSticker.emergency_reason = null
+
+/**
+ * Proc that handles checking if the emergency shuttle was successfully hijacked via being the only people present on the shuttle for the elimination hijack or highlander objective
+ *
+ * Checks for all mobs on the shuttle, checks their status, and checks if they're
+ * borgs or simple animals. Depending on the args, certain mobs may be ignored,
+ * and the presence of other antags may or may not invalidate a hijack.
+ * Args:
+ * filter_by_human, default TRUE, tells the proc that only humans should block a hijack. Borgs and animals are ignored and will not block if this is TRUE.
+ * solo_hijack, default FALSE, tells the proc to fail with multiple hijackers, such as for Highlander mode.
+ */
+/obj/docking_port/mobile/emergency/proc/elimination_hijack(filter_by_human = TRUE, solo_hijack = FALSE)
+ var/has_people = FALSE
+ var/hijacker_count = 0
+ for(var/mob/living/player in GLOB.player_list)
+ if(player.mind)
+ if(player.stat != DEAD)
+ if(issilicon(player) && filter_by_human) //Borgs are technically dead anyways
+ continue
+ if(isanimal_or_basicmob(player) && filter_by_human) //animals don't count
+ continue
+ if(isbrain(player)) //also technically dead
+ continue
+ if(shuttle_areas[get_area(player)])
+ has_people = TRUE
+ var/location = get_area(player.mind.current)
+ //Non-antag present. Can't hijack.
+ if(!(player.mind.has_antag_datum(/datum/antagonist)) && !istype(location, /area/shuttle/escape/brig))
+ return FALSE
+ //Antag present, doesn't stop but let's see if we actually want to hijack
+ var/prevent = FALSE
+ for(var/datum/antagonist/A in player.mind.antag_datums)
+ if(A.can_elimination_hijack == ELIMINATION_ENABLED)
+ hijacker_count += 1
+ prevent = FALSE
+ break //If we have both prevent and hijacker antags assume we want to hijack.
+ else if(A.can_elimination_hijack == ELIMINATION_PREVENT)
+ prevent = TRUE
+ if(prevent)
+ return FALSE
+
+ //has people AND either there's only one hijacker or there's any but solo_hijack is disabled
+ return has_people && ((hijacker_count == 1) || (hijacker_count && !solo_hijack))
+
+/obj/docking_port/mobile/emergency/proc/is_hijacked()
+ return hijack_status == HIJACK_COMPLETED
+
+/obj/docking_port/mobile/emergency/proc/ShuttleDBStuff()
+ set waitfor = FALSE
+ if(!SSdbcore.Connect())
+ return
+ var/datum/db_query/query_round_shuttle_name = SSdbcore.NewQuery({"
+ UPDATE [format_table_name("round")] SET shuttle_name = :name WHERE id = :round_id
+ "}, list("name" = name, "round_id" = GLOB.round_id))
+ query_round_shuttle_name.Execute()
+ qdel(query_round_shuttle_name)
+
+/obj/docking_port/mobile/emergency/check()
+ if(!timer)
+ return
+ var/time_left = timeLeft(1)
+
+ // The emergency shuttle doesn't work like others so this
+ // ripple check is slightly different
+ if(!ripples.len && (time_left <= SHUTTLE_RIPPLE_TIME) && ((mode == SHUTTLE_CALL) || (mode == SHUTTLE_ESCAPE)))
+ var/destination
+ if(mode == SHUTTLE_CALL)
+ destination = SSshuttle.getDock("emergency_home")
+ else if(mode == SHUTTLE_ESCAPE)
+ destination = SSshuttle.getDock("emergency_away")
+ create_ripples(destination)
+
+ switch(mode)
+ if(SHUTTLE_RECALL)
+ if(time_left <= 0)
+ mode = SHUTTLE_IDLE
+ timer = 0
+ if(SHUTTLE_CALL)
+ if(time_left <= 0)
+ //move emergency shuttle to station
+ if(initiate_docking(SSshuttle.getDock("emergency_home")) != DOCKING_SUCCESS)
+ setTimer(20)
+ return
+ mode = SHUTTLE_DOCKED
+ setTimer(SSshuttle.emergency_dock_time)
+ send2adminchat("Server", "The Emergency Shuttle has docked with the station.")
+ priority_announce(
+ text = "[SSshuttle.emergency] has docked with the station. You have [DisplayTimeText(SSshuttle.emergency_dock_time)] to board the emergency shuttle.",
+ title = "Emergency Shuttle Arrival",
+ sound = ANNOUNCER_SHUTTLEDOCK,
+ sender_override = "Emergency Shuttle Uplink Alert",
+ color_override = "orange",
+ )
+ ShuttleDBStuff()
+ addtimer(CALLBACK(src, PROC_REF(announce_shuttle_events)), 20 SECONDS)
+
+
+ if(SHUTTLE_DOCKED)
+ if(time_left <= ENGINE_START_TIME)
+ mode = SHUTTLE_IGNITING
+ SSshuttle.checkHostileEnvironment()
+ if(mode == SHUTTLE_STRANDED)
+ return
+ for(var/A in SSshuttle.mobile_docking_ports)
+ var/obj/docking_port/mobile/M = A
+ if(M.launch_status == UNLAUNCHED) //Pods will not launch from the mine/planet, and other ships won't launch unless we tell them to.
+ M.check_transit_zone()
+
+ if(SHUTTLE_IGNITING)
+ var/success = TRUE
+ SSshuttle.checkHostileEnvironment()
+ if(mode == SHUTTLE_STRANDED)
+ return
+
+ success &= (check_transit_zone() == TRANSIT_READY)
+ for(var/A in SSshuttle.mobile_docking_ports)
+ var/obj/docking_port/mobile/M = A
+ if(M.launch_status == UNLAUNCHED)
+ success &= (M.check_transit_zone() == TRANSIT_READY)
+ if(!success)
+ setTimer(ENGINE_START_TIME)
+
+ if(time_left <= 50 && !sound_played) //4 seconds left:REV UP THOSE ENGINES BOYS. - should sync up with the launch
+ sound_played = 1 //Only rev them up once.
+ var/list/areas = list()
+ for(var/area/shuttle/escape/E in GLOB.areas)
+ areas += E
+ hyperspace_sound(HYPERSPACE_WARMUP, areas)
+
+ if(time_left <= 0 && !SSshuttle.emergency_no_escape)
+ //move each escape pod (or applicable spaceship) to its corresponding transit dock
+ for(var/A in SSshuttle.mobile_docking_ports)
+ var/obj/docking_port/mobile/M = A
+ M.on_emergency_launch()
+
+ //now move the actual emergency shuttle to its transit dock
+ var/list/areas = list()
+ for(var/area/shuttle/escape/E in GLOB.areas)
+ areas += E
+ hyperspace_sound(HYPERSPACE_LAUNCH, areas)
+ enterTransit()
+
+ //Tell the events we're starting, so they can time their spawns or do some other stuff
+ for(var/datum/shuttle_event/event as anything in event_list)
+ event.start_up_event(SSshuttle.emergency_escape_time * engine_coeff)
+
+ mode = SHUTTLE_ESCAPE
+ launch_status = ENDGAME_LAUNCHED
+ setTimer(SSshuttle.emergency_escape_time * engine_coeff)
+ priority_announce(
+ text = "The emergency shuttle has left the station. Estimate [timeLeft(60 SECONDS)] minutes until the shuttle docks at [command_name()].",
+ title = "Emergency Shuttle Departure",
+ sender_override = "Emergency Shuttle Uplink Alert",
+ color_override = "orange",
+ )
+ INVOKE_ASYNC(SSticker, TYPE_PROC_REF(/datum/controller/subsystem/ticker, poll_hearts))
+ INVOKE_ASYNC(SSvote, TYPE_PROC_REF(/datum/controller/subsystem/vote, initiate_vote), /datum/vote/map_vote, vote_initiator_name = "Map Rotation", forced = TRUE)
+
+ if(!is_reserved_level(z))
+ CRASH("Emergency shuttle did not move to transit z-level!")
+
+ if(SHUTTLE_STRANDED, SHUTTLE_DISABLED)
+ SSshuttle.checkHostileEnvironment()
+
+
+ if(SHUTTLE_ESCAPE)
+ if(sound_played && time_left <= HYPERSPACE_END_TIME)
+ var/list/areas = list()
+ for(var/area/shuttle/escape/E in GLOB.areas)
+ areas += E
+ hyperspace_sound(HYPERSPACE_END, areas)
+ if(time_left <= PARALLAX_LOOP_TIME)
+ var/area_parallax = FALSE
+ for(var/place in shuttle_areas)
+ var/area/shuttle/shuttle_area = place
+ if(shuttle_area.parallax_movedir)
+ area_parallax = TRUE
+ break
+ if(area_parallax)
+ parallax_slowdown()
+ for(var/A in SSshuttle.mobile_docking_ports)
+ var/obj/docking_port/mobile/M = A
+ if(M.launch_status == ENDGAME_LAUNCHED)
+ if(istype(M, /obj/docking_port/mobile/pod))
+ M.parallax_slowdown()
+
+ process_events()
+
+ if(time_left <= 0)
+ //move each escape pod to its corresponding escape dock
+ for(var/obj/docking_port/mobile/port as anything in SSshuttle.mobile_docking_ports)
+ port.on_emergency_dock()
+
+ // now move the actual emergency shuttle to centcom
+ // unless the shuttle is "hijacked"
+ var/destination_dock = "emergency_away"
+ if(is_hijacked() || elimination_hijack())
+ // just double check
+ SSmapping.lazy_load_template(LAZY_TEMPLATE_KEY_NUKIEBASE)
+ destination_dock = "emergency_syndicate"
+ minor_announce("Corruption detected in \
+ shuttle navigation protocols. Please contact your \
+ supervisor.", "SYSTEM ERROR:", sound_override = 'sound/announcer/announcement/announce_syndi.ogg')
+
+ dock_id(destination_dock)
+ mode = SHUTTLE_ENDGAME
+ timer = 0
+
+/obj/docking_port/mobile/emergency/transit_failure()
+ ..()
+ message_admins("Moving emergency shuttle directly to centcom dock to prevent deadlock.")
+
+ mode = SHUTTLE_ESCAPE
+ launch_status = ENDGAME_LAUNCHED
+ setTimer(SSshuttle.emergency_escape_time)
+ priority_announce(
+ text = "The emergency shuttle is preparing for direct jump. Estimate [timeLeft(60 SECONDS)] minutes until the shuttle docks at [command_name()].",
+ title = "Emergency Shuttle Transit Failure",
+ sender_override = "Emergency Shuttle Uplink Alert",
+ color_override = "orange",
+ )
+
+///Generate a list of events to run during the departure
+/obj/docking_port/mobile/emergency/proc/setup_shuttle_events()
+ var/list/names = list()
+ for(var/datum/shuttle_event/event as anything in subtypesof(/datum/shuttle_event))
+ if(prob(initial(event.event_probability)))
+ add_shuttle_event(event)
+ names += initial(event.name)
+ if(LAZYLEN(names))
+ log_game("[capitalize(name)] has selected the following shuttle events: [english_list(names)].")
diff --git a/code/modules/shuttle/mobile_port/variants/emergency/emergency_console.dm b/code/modules/shuttle/mobile_port/variants/emergency/emergency_console.dm
new file mode 100644
index 0000000000000..b46bfff274307
--- /dev/null
+++ b/code/modules/shuttle/mobile_port/variants/emergency/emergency_console.dm
@@ -0,0 +1,316 @@
+#define ENGINES_STARTED (SSshuttle.emergency.mode == SHUTTLE_IGNITING)
+#define IS_DOCKED (SSshuttle.emergency.mode == SHUTTLE_DOCKED || (ENGINES_STARTED))
+#define SHUTTLE_CONSOLE_ACTION_DELAY (5 SECONDS)
+#define TIME_LEFT (SSshuttle.emergency.timeLeft())
+
+/obj/machinery/computer/emergency_shuttle
+ name = "emergency shuttle console"
+ desc = "For shuttle control."
+ icon_screen = "shuttle"
+ icon_keyboard = "tech_key"
+ resistance_flags = INDESTRUCTIBLE
+ var/auth_need = 3
+ var/list/authorized = list()
+ var/list/acted_recently = list()
+ var/hijack_last_stage_increase = 0 SECONDS
+ var/hijack_stage_time = 5 SECONDS
+ var/hijack_stage_cooldown = 5 SECONDS
+ var/hijack_flight_time_increase = 30 SECONDS
+ var/hijack_completion_flight_time_set = 10 SECONDS //How long in deciseconds to set shuttle's timer after hijack is done.
+ var/hijack_hacking = FALSE
+ var/hijack_announce = TRUE
+
+/obj/machinery/computer/emergency_shuttle/Destroy()
+ // Our fake IDs that the emag generated are just there for colour
+ // They're not supposed to be accessible
+
+ for(var/obj/item/card/id/ID in src)
+ qdel(ID)
+ if(authorized?.len)
+ authorized.Cut()
+ authorized = null
+
+ . = ..()
+
+/obj/machinery/computer/emergency_shuttle/examine(mob/user)
+ . = ..()
+ if(hijack_announce)
+ . += span_danger("Security systems present on console. Any unauthorized tampering will result in an emergency announcement.")
+ if(user?.mind?.get_hijack_speed())
+ . += span_danger("Alt click on this to attempt to hijack the shuttle. This will take multiple tries (current: stage [SSshuttle.emergency.hijack_status]/[HIJACK_COMPLETED]).")
+ . += span_notice("It will take you [(hijack_stage_time * user.mind.get_hijack_speed()) / 10] seconds to reprogram a stage of the shuttle's navigational firmware, and the console will undergo automated timed lockout for [hijack_stage_cooldown/10] seconds after each stage.")
+ if(hijack_announce)
+ . += span_warning("It is probably best to fortify your position as to be uninterrupted during the attempt, given the automatic announcements..")
+
+/obj/machinery/computer/emergency_shuttle/attackby(obj/item/I, mob/user,params)
+ if(isidcard(I))
+ say("Please equip your ID card into your ID slot to authenticate.")
+ . = ..()
+
+/obj/machinery/computer/emergency_shuttle/ui_state(mob/user)
+ return GLOB.human_adjacent_state
+
+/obj/machinery/computer/emergency_shuttle/ui_interact(mob/user, datum/tgui/ui)
+ . = ..()
+ ui = SStgui.try_update_ui(user, src, ui)
+ if(!ui)
+ ui = new(user, src, "EmergencyShuttleConsole", name)
+ ui.open()
+
+/obj/machinery/computer/emergency_shuttle/ui_data(user)
+ var/list/data = list()
+
+ data["timer_str"] = SSshuttle.emergency.getTimerStr()
+ data["engines_started"] = ENGINES_STARTED
+ data["authorizations_remaining"] = max((auth_need - authorized.len), 0)
+ var/list/A = list()
+ for(var/i in authorized)
+ var/obj/item/card/id/ID = i
+ var/name = ID.registered_name
+ var/job = ID.assignment
+
+ if(obj_flags & EMAGGED)
+ name = Gibberish(name)
+ job = Gibberish(job)
+ A += list(list("name" = name, "job" = job))
+ data["authorizations"] = A
+
+ data["enabled"] = (IS_DOCKED && !ENGINES_STARTED) && !(user in acted_recently)
+ data["emagged"] = obj_flags & EMAGGED ? 1 : 0
+ return data
+
+/obj/machinery/computer/emergency_shuttle/ui_act(action, params, datum/tgui/ui)
+ . = ..()
+ if(.)
+ return
+ if(ENGINES_STARTED) // past the point of no return
+ return
+ if(!IS_DOCKED) // shuttle computer only has uses when onstation
+ return
+ if(SSshuttle.emergency.mode == SHUTTLE_DISABLED) // admins have disabled the shuttle.
+ return
+ if(!isliving(usr))
+ return
+
+ var/area/my_area = get_area(src)
+ if(!istype(my_area, /area/shuttle/escape))
+ say("Error - Network connectivity: Console has lost connection to the shuttle.")
+ return
+
+ var/mob/living/user = usr
+ . = FALSE
+
+ var/obj/item/card/id/ID = user.get_idcard(TRUE)
+
+ if(!ID)
+ to_chat(user, span_warning("You don't have an ID."))
+ return
+
+ if(!(ACCESS_COMMAND in ID.access))
+ to_chat(user, span_warning("The access level of your card is not high enough."))
+ return
+
+ if (user in acted_recently)
+ return
+
+ var/old_len = authorized.len
+ addtimer(CALLBACK(src, PROC_REF(clear_recent_action), user), SHUTTLE_CONSOLE_ACTION_DELAY)
+
+ switch(action)
+ if("authorize")
+ . = authorize(user)
+
+ if("repeal")
+ authorized -= ID
+
+ if("abort")
+ if(authorized.len)
+ // Abort. The action for when heads are fighting over whether
+ // to launch early.
+ authorized.Cut()
+ . = TRUE
+
+ if((old_len != authorized.len) && !ENGINES_STARTED)
+ var/alert = (authorized.len > old_len)
+ var/repeal = (authorized.len < old_len)
+ var/remaining = max(0, auth_need - authorized.len)
+ if(authorized.len && remaining)
+ minor_announce("[remaining] authorizations needed until shuttle is launched early", null, alert)
+ if(repeal)
+ minor_announce("Early launch authorization revoked, [remaining] authorizations needed")
+
+ acted_recently += user
+ SStgui.update_user_uis(user, src)
+
+/obj/machinery/computer/emergency_shuttle/proc/authorize(mob/living/user, source)
+ var/obj/item/card/id/ID = user.get_idcard(TRUE)
+
+ if(ID in authorized)
+ return FALSE
+ for(var/i in authorized)
+ var/obj/item/card/id/other = i
+ if(other.registered_name == ID.registered_name)
+ return FALSE // No using IDs with the same name
+
+ authorized += ID
+
+ message_admins("[ADMIN_LOOKUPFLW(user)] has authorized early shuttle launch")
+ log_shuttle("[key_name(user)] has authorized early shuttle launch in [COORD(src)]")
+ // Now check if we're on our way
+ . = TRUE
+ process(SSMACHINES_DT)
+
+/obj/machinery/computer/emergency_shuttle/proc/clear_recent_action(mob/user)
+ acted_recently -= user
+ if (!QDELETED(user))
+ SStgui.update_user_uis(user, src)
+
+/obj/machinery/computer/emergency_shuttle/process()
+ // Launch check is in process in case auth_need changes for some reason
+ // probably external.
+ . = FALSE
+ if(!SSshuttle.emergency)
+ return
+
+ if(SSshuttle.emergency.mode == SHUTTLE_STRANDED)
+ authorized.Cut()
+ obj_flags &= ~(EMAGGED)
+
+ if(ENGINES_STARTED || (!IS_DOCKED))
+ return .
+
+ // Check to see if we've reached criteria for early launch
+ if((authorized.len >= auth_need) || (obj_flags & EMAGGED))
+ // shuttle timers use 1/10th seconds internally
+ SSshuttle.emergency.setTimer(ENGINE_START_TIME)
+ var/system_error = obj_flags & EMAGGED ? "SYSTEM ERROR:" : null
+ minor_announce("The emergency shuttle will launch in \
+ [TIME_LEFT] seconds", system_error, alert=TRUE)
+ . = TRUE
+
+/obj/machinery/computer/emergency_shuttle/proc/increase_hijack_stage()
+ var/obj/docking_port/mobile/emergency/shuttle = SSshuttle.emergency
+ // Begin loading this early, prevents a delay when the shuttle goes to land
+ INVOKE_ASYNC(SSmapping, TYPE_PROC_REF(/datum/controller/subsystem/mapping, lazy_load_template), LAZY_TEMPLATE_KEY_NUKIEBASE)
+
+ shuttle.hijack_status++
+ if(hijack_announce)
+ announce_hijack_stage()
+ hijack_last_stage_increase = world.time
+ say("Navigational protocol error! Rebooting systems.")
+ if(shuttle.mode == SHUTTLE_ESCAPE)
+ if(shuttle.hijack_status == HIJACK_COMPLETED)
+ shuttle.setTimer(hijack_completion_flight_time_set)
+ else
+ shuttle.setTimer(shuttle.timeLeft(1) + hijack_flight_time_increase) //give the guy more time to hijack if it's already in flight.
+ return shuttle.hijack_status
+
+/obj/machinery/computer/emergency_shuttle/click_alt(mob/living/user)
+ if(!isliving(user))
+ return NONE
+ attempt_hijack_stage(user)
+ return CLICK_ACTION_SUCCESS
+
+/obj/machinery/computer/emergency_shuttle/proc/attempt_hijack_stage(mob/living/user)
+ if(!user.CanReach(src))
+ return
+ if(HAS_TRAIT(user, TRAIT_HANDS_BLOCKED))
+ to_chat(user, span_warning("You need your hands free before you can manipulate [src]."))
+ return
+ var/area/my_area = get_area(src)
+ if(!istype(my_area, /area/shuttle/escape))
+ say("Error - Network connectivity: Console has lost connection to the shuttle.")
+ return
+ if(!user?.mind?.get_hijack_speed())
+ to_chat(user, span_warning("You manage to open a user-mode shell on [src], and hundreds of lines of debugging output fly through your vision. It is probably best to leave this alone."))
+ return
+ if(!EMERGENCY_AT_LEAST_DOCKED) // prevent advancing hijack stages on BYOS shuttles until the shuttle has "docked"
+ to_chat(user, span_warning("The flight plans for the shuttle haven't been loaded yet, you can't hack this right now."))
+ return
+ if(hijack_hacking == TRUE)
+ return
+ if(SSshuttle.emergency.hijack_status >= HIJACK_COMPLETED)
+ to_chat(user, span_warning("The emergency shuttle is already loaded with a corrupt navigational payload. What more do you want from it?"))
+ return
+ if(hijack_last_stage_increase >= world.time - hijack_stage_cooldown)
+ say("Error - Catastrophic software error detected. Input is currently on timeout.")
+ return
+ hijack_hacking = TRUE
+ to_chat(user, span_boldwarning("You [SSshuttle.emergency.hijack_status == HIJACK_NOT_BEGUN? "begin" : "continue"] to override [src]'s navigational protocols."))
+ say("Software override initiated.")
+ var/turf/console_hijack_turf = get_turf(src)
+ message_admins("[src] is being overriden for hijack by [ADMIN_LOOKUPFLW(user)] in [ADMIN_VERBOSEJMP(console_hijack_turf)]")
+ user.log_message("is hijacking [src].", LOG_GAME)
+ . = FALSE
+ if(do_after(user, hijack_stage_time * (1 / user.mind.get_hijack_speed()), target = src))
+ increase_hijack_stage()
+ console_hijack_turf = get_turf(src)
+ message_admins("[ADMIN_LOOKUPFLW(user)] has hijacked [src] in [ADMIN_VERBOSEJMP(console_hijack_turf)]. Hijack stage increased to stage [SSshuttle.emergency.hijack_status] out of [HIJACK_COMPLETED].")
+ user.log_message("has hijacked [src]. Hijack stage increased to stage [SSshuttle.emergency.hijack_status] out of [HIJACK_COMPLETED].", LOG_GAME)
+ . = TRUE
+ to_chat(user, span_notice("You reprogram some of [src]'s programming, putting it on timeout for [hijack_stage_cooldown/10] seconds."))
+ visible_message(
+ span_warning("[user.name] appears to be tampering with [src]."),
+ blind_message = span_hear("You hear someone tapping computer keys."),
+ vision_distance = COMBAT_MESSAGE_RANGE,
+ ignored_mobs = user
+ )
+ hijack_hacking = FALSE
+
+/obj/machinery/computer/emergency_shuttle/proc/announce_hijack_stage()
+ var/msg
+ switch(SSshuttle.emergency.hijack_status)
+ if(HIJACK_NOT_BEGUN)
+ return
+ if(HIJACK_STAGE_1)
+ msg = "AUTHENTICATING - FAIL. AUTHENTICATING - FAIL. AUTHENTICATING - FAI###### Welcome, technician JOHN DOE."
+ if(HIJACK_STAGE_2)
+ msg = "Warning: Navigational route fails \"IS_AUTHORIZED\". Please try againNN[scramble_message_replace_chars("againagainagainagainagain", 70)]."
+ if(HIJACK_STAGE_3)
+ msg = "CRC mismatch at ~h~ in calculated route buffer. Full reset initiated of FTL_NAVIGATION_SERVICES. Memory decrypted for automatic repair."
+ if(HIJACK_STAGE_4)
+ msg = "~ACS_directive module_load(cyberdyne.exploit.nanotrasen.shuttlenav)... NT key mismatch. Confirm load? Y...###Reboot complete. $SET transponder_state = 0; System link initiated with connected engines..."
+ if(HIJACK_COMPLETED)
+ msg = "SYSTEM OVERRIDE - Resetting course to \[[scramble_message_replace_chars("###########", 100)]\] \
+ ([scramble_message_replace_chars("#######", 100)]/[scramble_message_replace_chars("#######", 100)]/[scramble_message_replace_chars("#######", 100)]) \
+ {AUTH - ROOT (uid: 0)}.\
+ [SSshuttle.emergency.mode == SHUTTLE_ESCAPE ? "Diverting from existing route - Bluespace exit in \
+ [hijack_completion_flight_time_set >= INFINITY ? "[scramble_message_replace_chars("\[ERROR\]")]" : hijack_completion_flight_time_set/10] seconds." : ""]"
+ minor_announce(scramble_message_replace_chars(msg, replaceprob = 10), "Emergency Shuttle", TRUE)
+
+/obj/machinery/computer/emergency_shuttle/emag_act(mob/user, obj/item/card/emag/emag_card)
+ // How did you even get on the shuttle before it go to the station?
+ if(!IS_DOCKED)
+ return FALSE
+
+ if((obj_flags & EMAGGED) || ENGINES_STARTED) //SYSTEM ERROR: THE SHUTTLE WILL LA-SYSTEM ERROR: THE SHUTTLE WILL LA-SYSTEM ERROR: THE SHUTTLE WILL LAUNCH IN 10 SECONDS
+ balloon_alert(user, "shuttle already about to launch!")
+ return FALSE
+
+ var/time = TIME_LEFT
+ if (user)
+ message_admins("[ADMIN_LOOKUPFLW(user)] has emagged the emergency shuttle [time] seconds before launch.")
+ log_shuttle("[key_name(user)] has emagged the emergency shuttle in [COORD(src)] [time] seconds before launch.")
+ else
+ message_admins("The emergency shuttle was emagged [time] seconds before launch, with no emagger.")
+ log_shuttle("The emergency shuttle was emagged in [COORD(src)] [time] seconds before launch, with no emagger.")
+
+ obj_flags |= EMAGGED
+ SSshuttle.emergency.movement_force = list("KNOCKDOWN" = 60, "THROW" = 20)//YOUR PUNY SEATBELTS can SAVE YOU NOW, MORTAL
+ for(var/i in 1 to 10)
+ // the shuttle system doesn't know who these people are, but they
+ // must be important, surely
+ var/obj/item/card/id/ID = new(src)
+ var/datum/job/J = pick(SSjob.joinable_occupations)
+ ID.registered_name = generate_random_name_species_based(species_type = /datum/species/human)
+ ID.assignment = J.title
+
+ authorized += ID
+
+ process(SSMACHINES_DT)
+ return TRUE
+
+#undef TIME_LEFT
+#undef ENGINES_STARTED
+#undef IS_DOCKED
+#undef SHUTTLE_CONSOLE_ACTION_DELAY
diff --git a/code/modules/shuttle/mobile_port/variants/emergency/emergency_types.dm b/code/modules/shuttle/mobile_port/variants/emergency/emergency_types.dm
new file mode 100644
index 0000000000000..6030999698b00
--- /dev/null
+++ b/code/modules/shuttle/mobile_port/variants/emergency/emergency_types.dm
@@ -0,0 +1,39 @@
+/// Fallback shuttle
+/obj/docking_port/mobile/emergency/backup
+ name = "backup shuttle"
+ shuttle_id = "backup"
+ dir = EAST
+
+/obj/docking_port/mobile/emergency/backup/Initialize(mapload)
+ // We want to be a valid emergency shuttle
+ // but not be the main one, keep whatever's set
+ // valid.
+ // backup shuttle ignores `timid` because THERE SHOULD BE NO TOUCHING IT
+ var/current_emergency = SSshuttle.emergency
+ . = ..()
+ SSshuttle.emergency = current_emergency
+ SSshuttle.backup_shuttle = src
+
+/obj/docking_port/mobile/emergency/backup/Destroy(force)
+ if(SSshuttle.backup_shuttle == src)
+ SSshuttle.backup_shuttle = null
+ return ..()
+
+/// Monastery shuttle
+/obj/docking_port/mobile/monastery
+ name = "monastery pod"
+ shuttle_id = "mining_common" //set so mining can call it down
+ launch_status = UNLAUNCHED //required for it to launch as a pod.
+
+/obj/docking_port/mobile/monastery/on_emergency_dock()
+ if(launch_status == ENDGAME_LAUNCHED)
+ initiate_docking(SSshuttle.getDock("pod_away")) //docks our shuttle as any pod would
+ mode = SHUTTLE_ENDGAME
+
+/// Build Your Own Shuttle (BYOS) kit
+/obj/docking_port/mobile/emergency/shuttle_build
+
+/obj/docking_port/mobile/emergency/shuttle_build/postregister()
+ . = ..()
+ initiate_docking(SSshuttle.getDock("emergency_home"))
+
diff --git a/code/modules/shuttle/mobile_port/variants/emergency/pods.dm b/code/modules/shuttle/mobile_port/variants/emergency/pods.dm
new file mode 100644
index 0000000000000..1d8e1bae6bc03
--- /dev/null
+++ b/code/modules/shuttle/mobile_port/variants/emergency/pods.dm
@@ -0,0 +1,211 @@
+// THIS FILE CONTAINS: Pod mobile/stationary docking port, pod control console, pod storage and pod items
+
+/obj/docking_port/mobile/pod
+ name = "escape pod"
+ shuttle_id = "pod"
+ launch_status = UNLAUNCHED
+
+/obj/docking_port/mobile/pod/request(obj/docking_port/stationary/S)
+ var/obj/machinery/computer/shuttle/connected_computer = get_control_console()
+ if(!istype(connected_computer, /obj/machinery/computer/shuttle/pod))
+ return FALSE
+ if(!(SSsecurity_level.get_current_level_as_number() >= SEC_LEVEL_RED) && !(connected_computer.obj_flags & EMAGGED))
+ to_chat(usr, span_warning("Escape pods will only launch during \"Code Red\" security alert."))
+ return FALSE
+ if(launch_status == UNLAUNCHED)
+ launch_status = EARLY_LAUNCHED
+ return ..()
+
+/obj/docking_port/mobile/pod/cancel()
+ return
+
+/obj/machinery/computer/shuttle/pod
+ name = "pod control computer"
+ locked = TRUE
+ possible_destinations = "pod_asteroid"
+ icon = 'icons/obj/machines/wallmounts.dmi'
+ icon_state = "pod_off"
+ circuit = /obj/item/circuitboard/computer/emergency_pod
+ light_color = LIGHT_COLOR_BLUE
+ density = FALSE
+ icon_keyboard = null
+ icon_screen = "pod_on"
+
+/obj/machinery/computer/shuttle/pod/Initialize(mapload)
+ . = ..()
+ RegisterSignal(SSsecurity_level, COMSIG_SECURITY_LEVEL_CHANGED, PROC_REF(check_lock))
+
+/obj/machinery/computer/shuttle/pod/emag_act(mob/user, obj/item/card/emag/emag_card)
+ if(obj_flags & EMAGGED)
+ return FALSE
+ obj_flags |= EMAGGED
+ locked = FALSE
+ balloon_alert(user, "alert level checking disabled")
+ icon_screen = "emagged_general"
+ update_appearance()
+ return TRUE
+
+/obj/machinery/computer/shuttle/pod/connect_to_shuttle(mapload, obj/docking_port/mobile/port, obj/docking_port/stationary/dock)
+ . = ..()
+ if(port)
+ //Checks if the computer has already added the shuttle destination with the initial id
+ //This has to be done because connect_to_shuttle is called again after its ID is updated
+ //due to conflicting id names
+ var/base_shuttle_destination = ";[initial(port.shuttle_id)]_lavaland"
+ var/shuttle_destination = ";[port.shuttle_id]_lavaland"
+
+ var/position = findtext(possible_destinations, base_shuttle_destination)
+ if(position)
+ if(base_shuttle_destination == shuttle_destination)
+ return
+ possible_destinations = splicetext(possible_destinations, position, position + length(base_shuttle_destination), shuttle_destination)
+ return
+
+ possible_destinations += shuttle_destination
+
+/**
+ * Signal handler for checking if we should lock or unlock escape pods accordingly to a newly set security level
+ *
+ * Arguments:
+ * * source The datum source of the signal
+ * * new_level The new security level that is in effect
+ */
+/obj/machinery/computer/shuttle/pod/proc/check_lock(datum/source, new_level)
+ SIGNAL_HANDLER
+
+ if(obj_flags & EMAGGED)
+ return
+ locked = (new_level < SEC_LEVEL_RED)
+
+/obj/docking_port/stationary/random
+ name = "escape pod"
+ shuttle_id = "pod"
+ hidden = TRUE
+ override_can_dock_checks = TRUE
+ /// The area the pod tries to land at
+ var/target_area = /area/lavaland/surface/outdoors
+ /// Minimal distance from the map edge, setting this too low can result in shuttle landing on the edge and getting "sliced"
+ var/edge_distance = 16
+
+/obj/docking_port/stationary/random/Initialize(mapload)
+ . = ..()
+ if(!mapload)
+ return
+
+ var/list/turfs = get_area_turfs(target_area)
+ var/original_len = turfs.len
+ while(turfs.len)
+ var/turf/picked_turf = pick(turfs)
+ if(picked_turf.x stationary_dock.dwidth)
- return SHUTTLE_DWIDTH_TOO_LARGE
-
- if(width-dwidth > stationary_dock.width-stationary_dock.dwidth)
- return SHUTTLE_WIDTH_TOO_LARGE
-
- if(dheight > stationary_dock.dheight)
- return SHUTTLE_DHEIGHT_TOO_LARGE
-
- if(height-dheight > stationary_dock.height-stationary_dock.dheight)
- return SHUTTLE_HEIGHT_TOO_LARGE
-
- //check the dock isn't occupied
- var/currently_docked = stationary_dock.get_docked()
- if(currently_docked)
- // by someone other than us
- if(currently_docked != src)
- return SHUTTLE_SOMEONE_ELSE_DOCKED
- else
- // This isn't an error, per se, but we can't let the shuttle code
- // attempt to move us where we currently are, it will get weird.
- return SHUTTLE_ALREADY_DOCKED
-
- return SHUTTLE_CAN_DOCK
-
-/obj/docking_port/mobile/proc/check_dock(obj/docking_port/stationary/S, silent = FALSE)
- var/status = canDock(S)
- if(status == SHUTTLE_CAN_DOCK)
- return TRUE
- else
- if(status != SHUTTLE_ALREADY_DOCKED && !silent) // SHUTTLE_ALREADY_DOCKED is no cause for error
- message_admins("Shuttle [src] cannot dock at [S], error: [status]")
- // We're already docked there, don't need to do anything.
- // Triggering shuttle movement code in place is weird
- return FALSE
-
-/obj/docking_port/mobile/proc/transit_failure()
- message_admins("Shuttle [src] repeatedly failed to create transit zone.")
-
-/**
- * Calls the shuttle to the destination port, respecting its ignition and call timers
- *
- * Arguments:
- * * destination_port - Stationary docking port to move the shuttle to
- */
-/obj/docking_port/mobile/proc/request(obj/docking_port/stationary/destination_port)
- if(!check_dock(destination_port))
- testing("check_dock failed on request for [src]")
- return
-
- if(mode == SHUTTLE_IGNITING && destination == destination_port)
- return
-
- switch(mode)
- if(SHUTTLE_CALL)
- if(destination_port == destination)
- if(timeLeft(1) < callTime * engine_coeff)
- setTimer(callTime * engine_coeff)
- else
- destination = destination_port
- setTimer(callTime * engine_coeff)
- if(SHUTTLE_RECALL)
- if(destination_port == destination)
- setTimer(callTime * engine_coeff - timeLeft(1))
- else
- destination = destination_port
- setTimer(callTime * engine_coeff)
- mode = SHUTTLE_CALL
- if(SHUTTLE_IDLE, SHUTTLE_IGNITING)
- destination = destination_port
- mode = SHUTTLE_IGNITING
- setTimer(ignitionTime)
-
-//recall the shuttle to where it was previously
-/obj/docking_port/mobile/proc/cancel()
- if(mode != SHUTTLE_CALL)
- return
-
- remove_ripples()
-
- invertTimer()
- mode = SHUTTLE_RECALL
-
-/obj/docking_port/mobile/proc/enterTransit()
- if((SSshuttle.lockdown && is_station_level(z)) || !canMove()) //emp went off, no escape
- mode = SHUTTLE_IDLE
- return
- previous = null
- if(!destination)
- // sent to transit with no destination -> unlimited timer
- timer = INFINITY
- var/obj/docking_port/stationary/S0 = get_docked()
- var/obj/docking_port/stationary/S1 = assigned_transit
- if(S1)
- if(initiate_docking(S1) != DOCKING_SUCCESS)
- WARNING("shuttle \"[shuttle_id]\" could not enter transit space. Docked at [S0 ? S0.shuttle_id : "null"]. Transit dock [S1 ? S1.shuttle_id : "null"].")
- else if(S0)
- if(S0.delete_after)
- qdel(S0, TRUE)
- else
- previous = S0
- else
- WARNING("shuttle \"[shuttle_id]\" could not enter transit space. S0=[S0 ? S0.shuttle_id : "null"] S1=[S1 ? S1.shuttle_id : "null"]")
-
-
-/obj/docking_port/mobile/proc/jumpToNullSpace()
- // Destroys the docking port and the shuttle contents.
- // Not in a fancy way, it just ceases.
- var/obj/docking_port/stationary/current_dock = get_docked()
-
- var/underlying_area_type = SHUTTLE_DEFAULT_UNDERLYING_AREA
- // If the shuttle is docked to a stationary port, restore its normal
- // "empty" area and turf
- if(current_dock?.area_type)
- underlying_area_type = current_dock.area_type
-
- var/list/old_turfs = return_ordered_turfs(x, y, z, dir)
-
- var/area/underlying_area = GLOB.areas_by_type[underlying_area_type]
- if(!underlying_area)
- underlying_area = new underlying_area_type(null)
-
- for(var/i in 1 to old_turfs.len)
- var/turf/oldT = old_turfs[i]
- if(!oldT || !istype(oldT.loc, area_type))
- continue
- oldT.change_area(oldT.loc, underlying_area)
- oldT.empty(FALSE)
-
- // Here we locate the bottommost shuttle boundary and remove all turfs above it
- var/shuttle_tile_depth = oldT.depth_to_find_baseturf(/turf/baseturf_skipover/shuttle)
- if (!isnull(shuttle_tile_depth))
- oldT.ScrapeAway(shuttle_tile_depth)
-
- qdel(src, force=TRUE)
-
-/**
- * Ghosts and marks as escaped (for greentext purposes) all mobs, then deletes the shuttle.
- * Used by the Shuttle Manipulator
- */
-/obj/docking_port/mobile/proc/intoTheSunset()
- // Loop over mobs
- for(var/turf/turfs as anything in return_turfs())
- for(var/mob/living/sunset_mobs in turfs.get_all_contents())
- // If they have a mind and they're not in the brig, they escaped
- if(sunset_mobs.mind && !istype(get_area(sunset_mobs), /area/shuttle/escape/brig))
- sunset_mobs.mind.force_escaped = TRUE
- // Ghostize them and put them in nullspace stasis (for stat & possession checks)
- ADD_TRAIT(sunset_mobs, TRAIT_NO_TRANSFORM, REF(src))
- sunset_mobs.ghostize(FALSE)
- sunset_mobs.moveToNullspace()
-
- // Now that mobs are stowed, delete the shuttle
- jumpToNullSpace()
-
-/obj/docking_port/mobile/proc/create_ripples(obj/docking_port/stationary/S1, animate_time)
- var/list/turfs = ripple_area(S1)
- for(var/t in turfs)
- ripples += new /obj/effect/abstract/ripple(t, animate_time)
-
-/obj/docking_port/mobile/proc/remove_ripples()
- QDEL_LIST(ripples)
-
-/obj/docking_port/mobile/proc/ripple_area(obj/docking_port/stationary/S1)
- var/list/L0 = return_ordered_turfs(x, y, z, dir)
- var/list/L1 = return_ordered_turfs(S1.x, S1.y, S1.z, S1.dir)
-
- var/list/ripple_turfs = list()
- var/stop = min(L0.len, L1.len)
- for(var/i in 1 to stop)
- var/turf/T0 = L0[i]
- var/turf/T1 = L1[i]
- if(!istype(T0.loc, area_type) || istype(T0.loc, /area/shuttle/transit))
- continue // not part of the shuttle
- ripple_turfs += T1
-
- return ripple_turfs
-
-/obj/docking_port/mobile/proc/check_poddoors()
- for(var/obj/machinery/door/poddoor/shuttledock/pod as anything in SSmachines.get_machines_by_type_and_subtypes(/obj/machinery/door/poddoor/shuttledock))
- pod.check()
-
-/obj/docking_port/mobile/proc/dock_id(id)
- var/port = SSshuttle.getDock(id)
- if(port)
- . = initiate_docking(port)
- else
- . = null
-
-//used by shuttle subsystem to check timers
-/obj/docking_port/mobile/proc/check()
- check_effects()
- //process_events() if you were to add events to non-escape shuttles, uncomment this
-
- if(mode == SHUTTLE_IGNITING)
- check_transit_zone()
-
- if(timeLeft(1) > 0)
- return
- // If we can't dock or we don't have a transit slot, wait for 20 ds,
- // then try again
- switch(mode)
- if(SHUTTLE_CALL, SHUTTLE_PREARRIVAL)
- if(prearrivalTime && mode != SHUTTLE_PREARRIVAL)
- mode = SHUTTLE_PREARRIVAL
- setTimer(prearrivalTime)
- return
- var/error = initiate_docking(destination, preferred_direction)
- if(error && error & (DOCKING_NULL_DESTINATION | DOCKING_NULL_SOURCE))
- var/msg = "A mobile dock in transit exited initiate_docking() with an error. This is most likely a mapping problem: Error: [error], ([src]) ([previous][ADMIN_JMP(previous)] -> [destination][ADMIN_JMP(destination)])"
- WARNING(msg)
- message_admins(msg)
- mode = SHUTTLE_IDLE
- return
- else if(error)
- setTimer(20)
- return
- if(rechargeTime)
- mode = SHUTTLE_RECHARGING
- setTimer(rechargeTime)
- return
- if(SHUTTLE_RECALL)
- if(initiate_docking(previous) != DOCKING_SUCCESS)
- setTimer(20)
- return
- if(SHUTTLE_IGNITING)
- if(check_transit_zone() != TRANSIT_READY)
- setTimer(20)
- return
- else
- mode = SHUTTLE_CALL
- setTimer(callTime * engine_coeff)
- enterTransit()
- return
-
- mode = SHUTTLE_IDLE
- timer = 0
- destination = null
-
-/obj/docking_port/mobile/proc/check_effects()
- if(!ripples.len)
- if((mode == SHUTTLE_CALL) || (mode == SHUTTLE_RECALL))
- var/tl = timeLeft(1)
- if(tl <= SHUTTLE_RIPPLE_TIME)
- create_ripples(destination, tl)
-
- var/obj/docking_port/stationary/S0 = get_docked()
- if(istype(S0, /obj/docking_port/stationary/transit) && timeLeft(1) <= PARALLAX_LOOP_TIME)
- for(var/place in shuttle_areas)
- var/area/shuttle/shuttle_area = place
- if(shuttle_area.parallax_movedir)
- parallax_slowdown()
-
-/obj/docking_port/mobile/proc/parallax_slowdown()
- for(var/place in shuttle_areas)
- var/area/shuttle/shuttle_area = place
- shuttle_area.parallax_movedir = FALSE
- if(assigned_transit?.assigned_area)
- assigned_transit.assigned_area.parallax_movedir = FALSE
- var/list/L0 = return_ordered_turfs(x, y, z, dir)
- for (var/thing in L0)
- var/turf/T = thing
- if(!T || !istype(T.loc, area_type))
- continue
- for (var/atom/movable/movable as anything in T)
- if (movable.client_mobs_in_contents)
- movable.update_parallax_contents()
-
-/obj/docking_port/mobile/proc/check_transit_zone()
- if(assigned_transit)
- return TRANSIT_READY
- else
- SSshuttle.request_transit_dock(src)
-
-/obj/docking_port/mobile/proc/setTimer(wait)
- timer = world.time + wait
- last_timer_length = wait
-
-/obj/docking_port/mobile/proc/modTimer(multiple)
- var/time_remaining = timer - world.time
- if(time_remaining < 0 || !last_timer_length)
- return
- time_remaining *= multiple
- last_timer_length *= multiple
- setTimer(time_remaining)
-
-/obj/docking_port/mobile/proc/alert_coeff_change(new_coeff)
- if(isnull(new_coeff))
- return
-
- var/time_multiplier = new_coeff / alert_coeff
- var/time_remaining = timer - world.time
- if(time_remaining < 0 || !last_timer_length)
- return
-
- time_remaining *= time_multiplier
- last_timer_length *= time_multiplier
- alert_coeff = new_coeff
- setTimer(time_remaining)
-
-/obj/docking_port/mobile/proc/invertTimer()
- if(!last_timer_length)
- return
- var/time_remaining = timer - world.time
- if(time_remaining > 0)
- var/time_passed = last_timer_length - time_remaining
- setTimer(time_passed)
-
-//returns timeLeft
-/obj/docking_port/mobile/proc/timeLeft(divisor)
- if(divisor <= 0)
- divisor = 10
-
- var/ds_remaining
- if(!timer)
- ds_remaining = callTime * engine_coeff
- else
- ds_remaining = max(0, timer - world.time)
-
- . = round(ds_remaining / divisor, 1)
-
-// returns 3-letter mode string, used by status screens and mob status panel
-/obj/docking_port/mobile/proc/getModeStr()
- switch(mode)
- if(SHUTTLE_IGNITING)
- return "IGN"
- if(SHUTTLE_RECALL)
- return "RCL"
- if(SHUTTLE_CALL)
- return "ETA"
- if(SHUTTLE_DOCKED)
- return "ETD"
- if(SHUTTLE_ESCAPE)
- return "ESC"
- if(SHUTTLE_STRANDED)
- return "ERR"
- if(SHUTTLE_RECHARGING)
- return "RCH"
- if(SHUTTLE_PREARRIVAL)
- return "LDN"
- if(SHUTTLE_DISABLED)
- return "DIS"
- return ""
-
-// returns 5-letter timer string, used by status screens and mob status panel
-/obj/docking_port/mobile/proc/getTimerStr()
- if(mode == SHUTTLE_STRANDED || mode == SHUTTLE_DISABLED)
- return "--:--"
-
- var/timeleft = timeLeft()
- if(timeleft > 1 HOURS)
- return "--:--"
- else if(timeleft > 0)
- return "[add_leading(num2text((timeleft / 60) % 60), 2, "0")]:[add_leading(num2text(timeleft % 60), 2, "0")]"
- else
- return "00:00"
-
-/**
- * Gets shuttle location status in a form of string for tgui interfaces
- */
-/obj/docking_port/mobile/proc/get_status_text_tgui()
- var/obj/docking_port/stationary/dockedAt = get_docked()
- var/docked_at = dockedAt?.name || "Unknown"
- if(!istype(dockedAt, /obj/docking_port/stationary/transit))
- return docked_at
- if(timeLeft() > 1 HOURS)
- return "Hyperspace"
- else
- var/obj/docking_port/stationary/dst = (mode == SHUTTLE_RECALL) ? previous : destination
- return "In transit to [dst?.name || "unknown location"]"
-
-/obj/docking_port/mobile/proc/getStatusText()
- var/obj/docking_port/stationary/dockedAt = get_docked()
- var/docked_at = dockedAt?.name || "unknown"
- if(istype(dockedAt, /obj/docking_port/stationary/transit))
- if (timeLeft() > 1 HOURS)
- return "hyperspace"
- else
- var/obj/docking_port/stationary/dst
- if(mode == SHUTTLE_RECALL)
- dst = previous
- else
- dst = destination
- . = "transit towards [dst?.name || "unknown location"] ([getTimerStr()])"
- else if(mode == SHUTTLE_RECHARGING)
- return "[docked_at], recharging [getTimerStr()]"
- else
- return docked_at
-
-/obj/docking_port/mobile/proc/getDbgStatusText()
- var/obj/docking_port/stationary/dockedAt = get_docked()
- . = (dockedAt?.name) ? dockedAt.name : "unknown"
- if(istype(dockedAt, /obj/docking_port/stationary/transit))
- var/obj/docking_port/stationary/dst
- if(mode == SHUTTLE_RECALL)
- dst = previous
- else
- dst = destination
- if(dst)
- . = "(transit to) [dst.name || dst.shuttle_id]"
- else
- . = "(transit to) nowhere"
- else if(dockedAt)
- . = dockedAt.name || dockedAt.shuttle_id
- else
- . = "unknown"
-
-
-// attempts to locate /obj/machinery/computer/shuttle with matching ID inside the shuttle
-/obj/docking_port/mobile/proc/get_control_console()
- for(var/area/shuttle/shuttle_area as anything in shuttle_areas)
- var/obj/machinery/computer/shuttle/shuttle_computer = locate(/obj/machinery/computer/shuttle) in shuttle_area
- if(!shuttle_computer)
- continue
- if(shuttle_computer.shuttleId == shuttle_id)
- return shuttle_computer
- return null
-
-/obj/docking_port/mobile/proc/hyperspace_sound(phase, list/areas)
- var/selected_sound
- switch(phase)
- if(HYPERSPACE_WARMUP)
- selected_sound = "hyperspace_begin"
- if(HYPERSPACE_LAUNCH)
- selected_sound = "hyperspace_progress"
- if(HYPERSPACE_END)
- selected_sound = "hyperspace_end"
- else
- CRASH("Invalid hyperspace sound phase: [phase]")
- // This previously was played from each door at max volume, and was one of the worst things I had ever seen.
- // Now it's instead played from the nearest engine if close, or the first engine in the list if far since it doesn't really matter.
- // Or a door if for some reason the shuttle has no engine, fuck oh hi daniel fuck it
- var/range = (engine_coeff * max(width, height))
- var/long_range = range * 2.5
- var/atom/distant_source
-
- if(engine_list.len)
- distant_source = engine_list[1]
- else
- for(var/our_area in areas)
- distant_source = locate(/obj/machinery/door) in our_area
- if(distant_source)
- break
-
- if(!distant_source)
- return
- for(var/mob/zlevel_mobs as anything in SSmobs.clients_by_zlevel[z])
- var/dist_far = get_dist(zlevel_mobs, distant_source)
- if(dist_far <= long_range && dist_far > range)
- zlevel_mobs.playsound_local(distant_source, "sound/runtime/hyperspace/[selected_sound]_distance.ogg", 100)
- else if(dist_far <= range)
- var/source
- if(!engine_list.len)
- source = distant_source
- else
- var/closest_dist = 10000
- for(var/obj/machinery/power/shuttle_engine/engines as anything in engine_list)
- var/dist_near = get_dist(zlevel_mobs, engines)
- if(dist_near < closest_dist)
- source = engines
- closest_dist = dist_near
- zlevel_mobs.playsound_local(source, "sound/runtime/hyperspace/[selected_sound].ogg", 100)
-
-// Losing all initial engines should get you 2
-// Adding another set of engines at 0.5 time
-/obj/docking_port/mobile/proc/alter_engines(mod)
- if(!mod)
- return
- var/old_coeff = engine_coeff
- engine_coeff = get_engine_coeff(mod)
- current_engine_power = max(0, current_engine_power + mod)
- if(in_flight())
- var/delta_coeff = engine_coeff / old_coeff
- modTimer(delta_coeff)
-
-// Double initial engines to get to 0.5 minimum
-// Lose all initial engines to get to 2
-//For 0 engine shuttles like BYOS 5 engines to get to doublespeed
-/obj/docking_port/mobile/proc/get_engine_coeff(engine_mod)
- var/new_value = max(0, current_engine_power + engine_mod)
- if(new_value == initial_engine_power)
- return 1
- if(new_value > initial_engine_power)
- var/delta = new_value - initial_engine_power
- var/change_per_engine = (1 - ENGINE_COEFF_MIN) / ENGINE_DEFAULT_MAXSPEED_ENGINES // 5 by default
- if(initial_engine_power > 0)
- change_per_engine = (1 - ENGINE_COEFF_MIN) / initial_engine_power // or however many it had
- return clamp(1 - delta * change_per_engine,ENGINE_COEFF_MIN, ENGINE_COEFF_MAX)
- if(new_value < initial_engine_power)
- var/delta = initial_engine_power - new_value
- var/change_per_engine = 1 //doesn't really matter should not be happening for 0 engine shuttles
- if(initial_engine_power > 0)
- change_per_engine = (ENGINE_COEFF_MAX - 1) / initial_engine_power //just linear drop to max delay
- return clamp(1 + delta * change_per_engine, ENGINE_COEFF_MIN, ENGINE_COEFF_MAX)
-
-
-/obj/docking_port/mobile/proc/in_flight()
- switch(mode)
- if(SHUTTLE_CALL,SHUTTLE_RECALL,SHUTTLE_PREARRIVAL)
- return TRUE
- if(SHUTTLE_IDLE,SHUTTLE_IGNITING)
- return FALSE
- return FALSE // hmm
-
-/obj/docking_port/mobile/emergency/in_flight()
- switch(mode)
- if(SHUTTLE_ESCAPE)
- return TRUE
- if(SHUTTLE_STRANDED,SHUTTLE_ENDGAME)
- return FALSE
- return ..()
-
-//Called when emergency shuttle leaves the station
-/obj/docking_port/mobile/proc/on_emergency_launch()
- if(launch_status == UNLAUNCHED) //Pods will not launch from the mine/planet, and other ships won't launch unless we tell them to.
- launch_status = ENDGAME_LAUNCHED
- enterTransit()
-
-///Let people know shits about to go down
-/obj/docking_port/mobile/proc/announce_shuttle_events()
- for(var/datum/shuttle_event/event as anything in event_list)
- notify_ghosts("The [name] has selected: [event.name]")
-
-/obj/docking_port/mobile/emergency/on_emergency_launch()
- return
-
-//Called when emergency shuttle docks at centcom
-/obj/docking_port/mobile/proc/on_emergency_dock()
- // Mapping a new docking point for each ship mappers could potentially want docking with centcom would take up lots of space,
- // just let them keep flying off "into the sunset" for their greentext.
- if(launch_status == ENDGAME_LAUNCHED)
- launch_status = ENDGAME_TRANSIT
-
-/obj/docking_port/mobile/pod/on_emergency_dock()
- if(launch_status == ENDGAME_LAUNCHED)
- initiate_docking(SSshuttle.getDock("[shuttle_id]_away")) //Escape pods dock at centcom
- mode = SHUTTLE_ENDGAME
-
-/obj/docking_port/mobile/emergency/on_emergency_dock()
- return
-
-///Process all the shuttle events for every shuttle tick we get
-/obj/docking_port/mobile/proc/process_events()
- var/list/removees
- for(var/datum/shuttle_event/event as anything in event_list)
- if(event.event_process() == SHUTTLE_EVENT_CLEAR) //if we return SHUTTLE_EVENT_CLEAR, we clean them up
- LAZYADD(removees, event)
- for(var/item in removees)
- event_list.Remove(item)
-
-/// Give a typepath of a shuttle event to add to the shuttle. If added during endgame transit, will insta start the event
-/obj/docking_port/mobile/proc/add_shuttle_event(typepath)
- var/datum/shuttle_event/event = new typepath (src)
- event_list.Add(event)
- if(launch_status == ENDGAME_LAUNCHED)
- event.start_up_event(0)
- return event
-
-#ifdef TESTING
-#undef DOCKING_PORT_HIGHLIGHT
-#endif
diff --git a/code/modules/shuttle/monastery.dm b/code/modules/shuttle/shuttle_consoles/monastery.dm
similarity index 100%
rename from code/modules/shuttle/monastery.dm
rename to code/modules/shuttle/shuttle_consoles/monastery.dm
diff --git a/code/modules/shuttle/navigation_computer.dm b/code/modules/shuttle/shuttle_consoles/navigation_computer.dm
similarity index 100%
rename from code/modules/shuttle/navigation_computer.dm
rename to code/modules/shuttle/shuttle_consoles/navigation_computer.dm
diff --git a/code/modules/shuttle/computer.dm b/code/modules/shuttle/shuttle_consoles/shuttle_console.dm
similarity index 100%
rename from code/modules/shuttle/computer.dm
rename to code/modules/shuttle/shuttle_consoles/shuttle_console.dm
diff --git a/code/modules/shuttle/syndicate.dm b/code/modules/shuttle/shuttle_consoles/syndicate.dm
similarity index 100%
rename from code/modules/shuttle/syndicate.dm
rename to code/modules/shuttle/shuttle_consoles/syndicate.dm
diff --git a/code/modules/shuttle/white_ship.dm b/code/modules/shuttle/shuttle_consoles/white_ship.dm
similarity index 100%
rename from code/modules/shuttle/white_ship.dm
rename to code/modules/shuttle/shuttle_consoles/white_ship.dm
diff --git a/code/modules/shuttle/stationary_port/port_types.dm b/code/modules/shuttle/stationary_port/port_types.dm
new file mode 100644
index 0000000000000..047856566c2db
--- /dev/null
+++ b/code/modules/shuttle/stationary_port/port_types.dm
@@ -0,0 +1,100 @@
+/// Subtype for escape pod ports so that we can give them trait behaviour
+/obj/docking_port/stationary/escape_pod
+ name = "escape pod loader"
+ height = 5
+ width = 3
+ dwidth = 1
+ roundstart_template = /datum/map_template/shuttle/escape_pod/default
+ /// Set to true if you have a snowflake escape pod dock which needs to always have the normal pod or some other one
+ var/enforce_specific_pod = FALSE
+
+/obj/docking_port/stationary/escape_pod/Initialize(mapload)
+ . = ..()
+ if (enforce_specific_pod)
+ return
+
+ if (HAS_TRAIT(SSstation, STATION_TRAIT_SMALLER_PODS))
+ roundstart_template = /datum/map_template/shuttle/escape_pod/cramped
+ return
+ if (HAS_TRAIT(SSstation, STATION_TRAIT_BIGGER_PODS))
+ roundstart_template = /datum/map_template/shuttle/escape_pod/luxury
+
+// should fit the syndicate infiltrator, and smaller ships like the battlecruiser corvettes and fighters
+/obj/docking_port/stationary/syndicate
+ name = "near the station"
+ dheight = 1
+ dwidth = 12
+ height = 17
+ width = 23
+ shuttle_id = "syndicate_nearby"
+
+/obj/docking_port/stationary/syndicate/northwest
+ name = "northwest of station"
+ shuttle_id = "syndicate_nw"
+
+/obj/docking_port/stationary/syndicate/northeast
+ name = "northeast of station"
+ shuttle_id = "syndicate_ne"
+
+/obj/docking_port/stationary/transit
+ name = "In Transit"
+ override_can_dock_checks = TRUE
+ /// The turf reservation returned by the transit area request
+ var/datum/turf_reservation/reserved_area
+ /// The area created during the transit area reservation
+ var/area/shuttle/transit/assigned_area
+ /// The mobile port that owns this transit port
+ var/obj/docking_port/mobile/owner
+
+/obj/docking_port/stationary/transit/Initialize(mapload)
+ . = ..()
+ SSshuttle.transit_docking_ports += src
+
+/obj/docking_port/stationary/transit/Destroy(force=FALSE)
+ if(force)
+ if(get_docked())
+ log_world("A transit dock was destroyed while something was docked to it.")
+ SSshuttle.transit_docking_ports -= src
+ if(owner)
+ if(owner.assigned_transit == src)
+ owner.assigned_transit = null
+ owner = null
+ if(!QDELETED(reserved_area))
+ qdel(reserved_area)
+ reserved_area = null
+ return ..()
+
+/obj/docking_port/stationary/picked
+ ///Holds a list of map name strings for the port to pick from
+ var/list/shuttlekeys
+
+/obj/docking_port/stationary/picked/Initialize(mapload)
+ . = ..()
+ if(!LAZYLEN(shuttlekeys))
+ WARNING("Random docking port [shuttle_id] loaded with no shuttle keys")
+ return
+ var/selectedid = pick(shuttlekeys)
+ roundstart_template = SSmapping.shuttle_templates[selectedid]
+
+/obj/docking_port/stationary/picked/whiteship
+ name = "Deep Space"
+ shuttle_id = "whiteship_away"
+ height = 45 //Width and height need to remain in sync with the size of whiteshipdock.dmm, otherwise we'll get overflow
+ width = 44
+ dheight = 18
+ dwidth = 18
+ dir = 2
+ shuttlekeys = list(
+ "whiteship_meta",
+ "whiteship_pubby",
+ "whiteship_box",
+ "whiteship_cere",
+ "whiteship_kilo",
+ "whiteship_donut",
+ "whiteship_delta",
+ "whiteship_tram",
+ "whiteship_personalshuttle",
+ "whiteship_obelisk",
+ "whiteship_birdshot",
+ )
+
diff --git a/code/modules/shuttle/stationary_port/stationary_port.dm b/code/modules/shuttle/stationary_port/stationary_port.dm
new file mode 100644
index 0000000000000..49437730cb071
--- /dev/null
+++ b/code/modules/shuttle/stationary_port/stationary_port.dm
@@ -0,0 +1,91 @@
+
+/obj/docking_port/stationary
+ name = "dock"
+
+ var/last_dock_time
+
+ /// Map template to load when the dock is loaded
+ var/datum/map_template/shuttle/roundstart_template
+ /// Used to check if the shuttle template is enabled in the config file
+ var/json_key
+ ///If true, the shuttle can always dock at this docking port, despite its area checks, or if something is already docked
+ var/override_can_dock_checks = FALSE
+
+/obj/docking_port/stationary/Initialize(mapload)
+ . = ..()
+ register()
+ if(!area_type)
+ var/area/place = get_area(src)
+ area_type = place?.type // We might be created in nullspace
+
+ if(mapload)
+ for(var/turf/T in return_turfs())
+ T.turf_flags |= NO_RUINS
+
+ if(SSshuttle.initialized)
+ INVOKE_ASYNC(SSshuttle, TYPE_PROC_REF(/datum/controller/subsystem/shuttle, setup_shuttles), list(src))
+
+#ifdef TESTING
+ highlight("#f00")
+#endif
+
+/obj/docking_port/stationary/Destroy(force)
+ if(force)
+ unregister()
+ return ..()
+
+/obj/docking_port/stationary/register(replace = FALSE)
+ . = ..()
+ if(!shuttle_id)
+ shuttle_id = "dock"
+ else
+ port_destinations = shuttle_id
+
+ if(!name)
+ name = "dock"
+
+ var/counter = SSshuttle.assoc_stationary[shuttle_id]
+ if(!replace || !counter)
+ if(counter)
+ counter++
+ SSshuttle.assoc_stationary[shuttle_id] = counter
+ shuttle_id = "[shuttle_id]_[counter]"
+ name = "[name] [counter]"
+ else
+ SSshuttle.assoc_stationary[shuttle_id] = 1
+
+ if(!port_destinations)
+ port_destinations = shuttle_id
+
+ SSshuttle.stationary_docking_ports += src
+
+/obj/docking_port/stationary/unregister()
+ . = ..()
+ SSshuttle.stationary_docking_ports -= src
+
+/obj/docking_port/stationary/Moved(atom/old_loc, movement_dir, forced, list/old_locs, momentum_change = TRUE)
+ . = ..()
+ if(area_type) // We already have one
+ return
+ var/area/newarea = get_area(src)
+ area_type = newarea?.type
+
+/obj/docking_port/stationary/proc/load_roundstart()
+ if(json_key)
+ var/sid = SSmapping.current_map.shuttles[json_key]
+ roundstart_template = SSmapping.shuttle_templates[sid]
+ if(!roundstart_template)
+ CRASH("json_key:[json_key] value \[[sid]\] resulted in a null shuttle template for [src]")
+ else if(roundstart_template) // passed a PATH
+ var/sid = "[initial(roundstart_template.port_id)]_[initial(roundstart_template.suffix)]"
+
+ roundstart_template = SSmapping.shuttle_templates[sid]
+ if(!roundstart_template)
+ CRASH("Invalid path ([sid]/[roundstart_template]) passed to docking port.")
+
+ if(roundstart_template)
+ SSshuttle.action_load(roundstart_template, src)
+
+//returns first-found touching shuttleport
+/obj/docking_port/stationary/get_docked()
+ . = locate(/obj/docking_port/mobile) in loc
diff --git a/code/modules/station_goals/meteor_shield.dm b/code/modules/station_goals/meteor_shield.dm
index 84a61395a4b9c..e4b76f600ca80 100644
--- a/code/modules/station_goals/meteor_shield.dm
+++ b/code/modules/station_goals/meteor_shield.dm
@@ -61,13 +61,14 @@
name = "\improper Meteor Shield Satellite"
desc = "A meteor point-defense satellite."
mode = "M-SHIELD"
- processing_flags = START_PROCESSING_MANUALLY
- subsystem_type = /datum/controller/subsystem/processing/fastprocess
/// the range a meteor shield sat can destroy meteors
var/kill_range = 14
//emag behavior dark matt-eor stuff
+ /// Proximity monitor associated with this atom, needed for it to work.
+ var/datum/proximity_monitor/proximity_monitor
+
/// amount of emagged active meteor shields
var/static/emagged_active_meteor_shields = 0
/// the highest amount of shields you've ever emagged
@@ -94,34 +95,43 @@
return FALSE
return TRUE
-/obj/machinery/satellite/meteor_shield/process()
- if(obj_flags & EMAGGED)
- //kills the processing because emagged meteor shields no longer stop meteors in any way
- return PROCESS_KILL
- if(!active)
+/obj/machinery/satellite/meteor_shield/Initialize(mapload)
+ . = ..()
+ proximity_monitor = new(src, /* range = */ 0)
+
+/obj/machinery/satellite/meteor_shield/HasProximity(atom/movable/proximity_check_mob)
+ . = ..()
+ if(!istype(proximity_check_mob, /obj/effect/meteor))
return
- for(var/obj/effect/meteor/meteor_to_destroy in GLOB.meteor_list)
- if(meteor_to_destroy.z != z)
- continue
- if(get_dist(meteor_to_destroy, src) > kill_range)
- continue
- if(space_los(meteor_to_destroy))
- var/turf/beam_from = get_turf(src)
- beam_from.Beam(get_turf(meteor_to_destroy), icon_state="sat_beam", time = 5)
- if(meteor_to_destroy.shield_defense(src))
- qdel(meteor_to_destroy)
+ var/obj/effect/meteor/meteor_to_destroy = proximity_check_mob
+ if(space_los(meteor_to_destroy))
+ var/turf/beam_from = get_turf(src)
+ beam_from.Beam(get_turf(meteor_to_destroy), icon_state="sat_beam", time = 5)
+ if(meteor_to_destroy.shield_defense(src))
+ qdel(meteor_to_destroy)
/obj/machinery/satellite/meteor_shield/toggle(user)
+ if(user)
+ balloon_alert(user, "looking for [active ? "off" : "on"] button")
+ if(user && !do_after(user, 2 SECONDS, src, IGNORE_HELD_ITEM))
+ return FALSE
if(!..(user))
return FALSE
if(obj_flags & EMAGGED)
update_emagged_meteor_sat(user)
+ if(active)
+ proximity_monitor.set_range(kill_range)
+ else
+ proximity_monitor.set_range(0)
+
+
var/datum/station_goal/station_shield/goal = SSstation.get_station_goal(/datum/station_goal/station_shield)
goal?.update_coverage()
/obj/machinery/satellite/meteor_shield/Destroy()
. = ..()
+ QDEL_NULL(proximity_monitor)
if(obj_flags & EMAGGED)
//satellites that are destroying are not active, this will count down the number of emagged sats
update_emagged_meteor_sat()
@@ -181,6 +191,7 @@
for(var/datum/round_event_control/stray_meteor/stray_meteor in SSevents.control)
stray_meteor.weight *= mod
+
#undef EMAGGED_METEOR_SHIELD_THRESHOLD_ONE
#undef EMAGGED_METEOR_SHIELD_THRESHOLD_TWO
#undef EMAGGED_METEOR_SHIELD_THRESHOLD_THREE
diff --git a/code/modules/surgery/bodyparts/_bodyparts.dm b/code/modules/surgery/bodyparts/_bodyparts.dm
index 64750e21c9b6d..d6e6e101c6e39 100644
--- a/code/modules/surgery/bodyparts/_bodyparts.dm
+++ b/code/modules/surgery/bodyparts/_bodyparts.dm
@@ -1271,14 +1271,11 @@
* * gauze- Just the gauze stack we're taking a sheet from to apply here
*/
/obj/item/bodypart/proc/apply_gauze(obj/item/stack/medical/gauze/new_gauze)
- if(!istype(new_gauze) || !new_gauze.absorption_capacity)
+ if(!istype(new_gauze) || !new_gauze.absorption_capacity || !new_gauze.use(1))
return
- var/newly_gauzed = FALSE
- if(!current_gauze)
- newly_gauzed = TRUE
+ var/newly_gauzed = !current_gauze
QDEL_NULL(current_gauze)
current_gauze = new new_gauze.type(src, 1)
- new_gauze.use(1)
current_gauze.gauzed_bodypart = src
if(newly_gauzed)
SEND_SIGNAL(src, COMSIG_BODYPART_GAUZED, current_gauze, new_gauze)
diff --git a/code/modules/surgery/organs/external/wings/functional_wings.dm b/code/modules/surgery/organs/external/wings/functional_wings.dm
index 775894458349a..0d445cb031428 100644
--- a/code/modules/surgery/organs/external/wings/functional_wings.dm
+++ b/code/modules/surgery/organs/external/wings/functional_wings.dm
@@ -11,7 +11,7 @@
/datum/action/innate/flight/Activate()
var/mob/living/carbon/human/human = owner
var/obj/item/organ/wings/functional/wings = human.get_organ_slot(ORGAN_SLOT_EXTERNAL_WINGS)
- if(wings?.can_fly(human))
+ if(wings?.can_fly())
wings.toggle_flight(human)
///The true wings that you can use to fly and shit (you cant actually shit with them)
@@ -29,6 +29,23 @@
// grind_results = list(/datum/reagent/flightpotion = 5)
food_reagents = list(/datum/reagent/flightpotion = 5)
+ var/drift_force = FUNCTIONAL_WING_FORCE
+ var/stabilizer_force = FUNCTIONAL_WING_STABILIZATION
+
+/obj/item/organ/wings/functional/Initialize(mapload)
+ . = ..()
+ AddComponent( \
+ /datum/component/jetpack, \
+ TRUE, \
+ drift_force, \
+ stabilizer_force, \
+ COMSIG_WINGS_OPENED, \
+ COMSIG_WINGS_CLOSED, \
+ null, \
+ CALLBACK(src, PROC_REF(can_fly)), \
+ CALLBACK(src, PROC_REF(can_fly)), \
+ )
+
/obj/item/organ/wings/functional/Destroy()
QDEL_NULL(fly)
return ..()
@@ -54,14 +71,14 @@
/obj/item/organ/wings/functional/proc/handle_flight(mob/living/carbon/human/human)
if(!HAS_TRAIT_FROM(human, TRAIT_MOVE_FLOATING, SPECIES_FLIGHT_TRAIT))
return FALSE
- if(!can_fly(human))
+ if(!can_fly())
toggle_flight(human)
return FALSE
return TRUE
-
///Check if we're still eligible for flight (wings covered, atmosphere too thin, etc)
-/obj/item/organ/wings/functional/proc/can_fly(mob/living/carbon/human/human)
+/obj/item/organ/wings/functional/proc/can_fly()
+ var/mob/living/carbon/human/human = owner
if(human.stat || human.body_position == LYING_DOWN || isnull(human.client))
return FALSE
//Jumpsuits have tail holes, so it makes sense they have wing holes too
@@ -105,13 +122,10 @@
/obj/item/organ/wings/functional/proc/toggle_flight(mob/living/carbon/human/human)
if(!HAS_TRAIT_FROM(human, TRAIT_MOVE_FLOATING, SPECIES_FLIGHT_TRAIT))
human.physiology.stun_mod *= 2
- human.add_traits(list(TRAIT_NO_FLOATING_ANIM, TRAIT_MOVE_FLOATING, TRAIT_IGNORING_GRAVITY, TRAIT_NOGRAV_ALWAYS_DRIFT), SPECIES_FLIGHT_TRAIT)
+ human.add_traits(list(TRAIT_MOVE_FLOATING, TRAIT_IGNORING_GRAVITY, TRAIT_NOGRAV_ALWAYS_DRIFT), SPECIES_FLIGHT_TRAIT)
human.add_movespeed_modifier(/datum/movespeed_modifier/jetpack/wings)
human.AddElement(/datum/element/forced_gravity, 0)
passtable_on(human, SPECIES_FLIGHT_TRAIT)
- RegisterSignal(human, COMSIG_MOB_CLIENT_MOVE_NOGRAV, PROC_REF(on_client_move))
- RegisterSignal(human, COMSIG_MOB_ATTEMPT_HALT_SPACEMOVE, PROC_REF(on_pushoff))
- START_PROCESSING(SSnewtonian_movement, src)
open_wings()
to_chat(human, span_notice("You beat your wings and begin to hover gently above the ground..."))
human.set_resting(FALSE, TRUE)
@@ -119,50 +133,21 @@
return
human.physiology.stun_mod *= 0.5
- human.remove_traits(list(TRAIT_NO_FLOATING_ANIM, TRAIT_MOVE_FLOATING, TRAIT_IGNORING_GRAVITY, TRAIT_NOGRAV_ALWAYS_DRIFT), SPECIES_FLIGHT_TRAIT)
+ human.remove_traits(list(TRAIT_MOVE_FLOATING, TRAIT_IGNORING_GRAVITY, TRAIT_NOGRAV_ALWAYS_DRIFT), SPECIES_FLIGHT_TRAIT)
human.remove_movespeed_modifier(/datum/movespeed_modifier/jetpack/wings)
human.RemoveElement(/datum/element/forced_gravity, 0)
passtable_off(human, SPECIES_FLIGHT_TRAIT)
- UnregisterSignal(human, list(COMSIG_MOB_CLIENT_MOVE_NOGRAV, COMSIG_MOB_ATTEMPT_HALT_SPACEMOVE))
- STOP_PROCESSING(SSnewtonian_movement, src)
to_chat(human, span_notice("You settle gently back onto the ground..."))
close_wings()
human.refresh_gravity()
-/obj/item/organ/wings/functional/proc/on_client_move(mob/source, list/move_args)
- SIGNAL_HANDLER
-
- if (!can_fly(source))
- return
-
- var/max_drift_force = (DEFAULT_INERTIA_SPEED / source.cached_multiplicative_slowdown - 1) / INERTIA_SPEED_COEF + 1
- source.newtonian_move(dir2angle(source.client.intended_direction), instant = TRUE, drift_force = FUNCTIONAL_WING_FORCE, controlled_cap = max_drift_force)
- source.setDir(source.client.intended_direction)
-
-/obj/item/organ/wings/functional/proc/on_pushoff(mob/source, movement_dir, continuous_move, atom/backup)
- SIGNAL_HANDLER
-
- if (get_dir(source, backup) == movement_dir || source.loc == backup.loc)
- return
-
- if (!can_fly(source) || !source.client.intended_direction || (source.client.intended_direction & get_dir(source, backup)))
- return
-
- return COMPONENT_PREVENT_SPACEMOVE_HALT
-
-/obj/item/organ/wings/functional/process(seconds_per_tick)
- if (!owner || !can_fly(owner) || isnull(owner.drift_handler))
- return
-
- var/max_drift_force = (DEFAULT_INERTIA_SPEED / owner.cached_multiplicative_slowdown - 1) / INERTIA_SPEED_COEF + 1
- owner.drift_handler.stabilize_drift(owner.client.intended_direction ? dir2angle(owner.client.intended_direction) : null, owner.client.intended_direction ? max_drift_force : 0, FUNCTIONAL_WING_STABILIZATION * (seconds_per_tick * 1 SECONDS))
-
///SPREAD OUR WINGS AND FLLLLLYYYYYY
/obj/item/organ/wings/functional/proc/open_wings()
var/datum/bodypart_overlay/mutant/wings/functional/overlay = bodypart_overlay
overlay.open_wings()
wings_open = TRUE
owner.update_body_parts()
+ SEND_SIGNAL(src, COMSIG_WINGS_OPENED, owner)
///close our wings
/obj/item/organ/wings/functional/proc/close_wings()
@@ -175,6 +160,8 @@
var/turf/location = loc
location.Entered(src, NONE)
+ SEND_SIGNAL(src, COMSIG_WINGS_CLOSED, owner)
+
///Bodypart overlay of function wings, including open and close functionality!
/datum/bodypart_overlay/mutant/wings/functional
///Are our wings currently open? Change through open_wings or close_wings()
diff --git a/code/modules/surgery/organs/external/wings/moth_wings.dm b/code/modules/surgery/organs/external/wings/moth_wings.dm
index 265a9ee751226..da35ea25d50e8 100644
--- a/code/modules/surgery/organs/external/wings/moth_wings.dm
+++ b/code/modules/surgery/organs/external/wings/moth_wings.dm
@@ -16,18 +16,31 @@
///Store our old datum here for if our burned wings are healed
var/original_sprite_datum
+ var/drift_force = MOTH_WING_FORCE
+ var/stabilizer_force = MOTH_WING_FORCE
+
+/obj/item/organ/wings/moth/Initialize(mapload)
+ . = ..()
+ AddComponent( \
+ /datum/component/jetpack, \
+ TRUE, \
+ drift_force, \
+ stabilizer_force, \
+ COMSIG_ORGAN_IMPLANTED, \
+ COMSIG_ORGAN_REMOVED, \
+ null, \
+ CALLBACK(src, PROC_REF(allow_flight)), \
+ null, \
+ )
+
/obj/item/organ/wings/moth/on_mob_insert(mob/living/carbon/receiver)
. = ..()
RegisterSignal(receiver, COMSIG_HUMAN_BURNING, PROC_REF(try_burn_wings))
RegisterSignal(receiver, COMSIG_LIVING_POST_FULLY_HEAL, PROC_REF(heal_wings))
- RegisterSignal(receiver, COMSIG_MOB_CLIENT_MOVE_NOGRAV, PROC_REF(on_client_move))
- RegisterSignal(receiver, COMSIG_MOB_ATTEMPT_HALT_SPACEMOVE, PROC_REF(on_pushoff))
- START_PROCESSING(SSnewtonian_movement, src)
/obj/item/organ/wings/moth/on_mob_remove(mob/living/carbon/organ_owner)
. = ..()
- UnregisterSignal(organ_owner, list(COMSIG_HUMAN_BURNING, COMSIG_LIVING_POST_FULLY_HEAL, COMSIG_MOB_CLIENT_MOVE_NOGRAV, COMSIG_MOB_ATTEMPT_HALT_SPACEMOVE))
- STOP_PROCESSING(SSnewtonian_movement, src)
+ UnregisterSignal(organ_owner, list(COMSIG_HUMAN_BURNING, COMSIG_LIVING_POST_FULLY_HEAL))
/obj/item/organ/wings/moth/make_flap_sound(mob/living/carbon/wing_owner)
playsound(wing_owner, 'sound/mobs/humanoids/moth/moth_flutter.ogg', 50, TRUE)
@@ -38,14 +51,6 @@
/obj/item/organ/wings/moth/proc/allow_flight()
if(!owner || !owner.client)
return FALSE
- if(!isturf(owner.loc))
- return FALSE
- if(!(owner.movement_type & FLOATING) || owner.buckled)
- return FALSE
- if(owner.pulledby)
- return FALSE
- if(owner.throwing)
- return FALSE
if(owner.has_gravity())
return FALSE
if(ishuman(owner))
@@ -59,34 +64,6 @@
return TRUE
return FALSE
-/obj/item/organ/wings/moth/process(seconds_per_tick)
- if (!owner || !allow_flight() || isnull(owner.drift_handler))
- return
-
- var/max_drift_force = (DEFAULT_INERTIA_SPEED / owner.cached_multiplicative_slowdown - 1) / INERTIA_SPEED_COEF + 1
- owner.drift_handler.stabilize_drift(owner.client.intended_direction ? dir2angle(owner.client.intended_direction) : null, owner.client.intended_direction ? max_drift_force : 0, MOTH_WING_FORCE * (seconds_per_tick * 1 SECONDS))
-
-/obj/item/organ/wings/moth/proc/on_client_move(mob/source, list/move_args)
- SIGNAL_HANDLER
-
- if (!allow_flight())
- return
-
- var/max_drift_force = (DEFAULT_INERTIA_SPEED / source.cached_multiplicative_slowdown - 1) / INERTIA_SPEED_COEF + 1
- source.newtonian_move(dir2angle(source.client.intended_direction), instant = TRUE, drift_force = MOTH_WING_FORCE, controlled_cap = max_drift_force)
- source.setDir(source.client.intended_direction)
-
-/obj/item/organ/wings/moth/proc/on_pushoff(mob/source, movement_dir, continuous_move, atom/backup)
- SIGNAL_HANDLER
-
- if (get_dir(source, backup) == movement_dir || source.loc == backup.loc)
- return
-
- if (!allow_flight() || !source.client.intended_direction || (source.client.intended_direction & get_dir(source, backup)))
- return
-
- return COMPONENT_PREVENT_SPACEMOVE_HALT
-
///check if our wings can burn off ;_;
/obj/item/organ/wings/moth/proc/try_burn_wings(mob/living/carbon/human/human)
SIGNAL_HANDLER
diff --git a/code/modules/surgery/organs/internal/cyberimp/augments_chest.dm b/code/modules/surgery/organs/internal/cyberimp/augments_chest.dm
index 8b77ef5ec78b5..603acc0eae9d5 100644
--- a/code/modules/surgery/organs/internal/cyberimp/augments_chest.dm
+++ b/code/modules/surgery/organs/internal/cyberimp/augments_chest.dm
@@ -175,6 +175,7 @@
COMSIG_THRUSTER_DEACTIVATED, \
THRUSTER_ACTIVATION_FAILED, \
CALLBACK(src, PROC_REF(allow_thrust), 0.01), \
+ CALLBACK(src, PROC_REF(allow_thrust), 0.01), \
/datum/effect_system/trail_follow/ion, \
)
diff --git a/code/modules/surgery/organs/internal/cyberimp/augments_internal.dm b/code/modules/surgery/organs/internal/cyberimp/augments_internal.dm
index 42f0e5ac7b237..ffd3f022e7e2a 100644
--- a/code/modules/surgery/organs/internal/cyberimp/augments_internal.dm
+++ b/code/modules/surgery/organs/internal/cyberimp/augments_internal.dm
@@ -6,16 +6,6 @@
organ_flags = ORGAN_ROBOTIC
failing_desc = "seems to be broken."
var/implant_color = COLOR_WHITE
- var/implant_overlay
-
-/obj/item/organ/cyberimp/New(mob/implanted_mob = null)
- if(iscarbon(implanted_mob))
- src.Insert(implanted_mob)
- if(implant_overlay)
- var/mutable_appearance/overlay = mutable_appearance(icon, implant_overlay)
- overlay.color = implant_color
- add_overlay(overlay)
- return ..()
//[[[[BRAIN]]]]
diff --git a/code/modules/surgery/organs/internal/tongue/_tongue.dm b/code/modules/surgery/organs/internal/tongue/_tongue.dm
index e5e54a1e68a4f..abf74a03bdf11 100644
--- a/code/modules/surgery/organs/internal/tongue/_tongue.dm
+++ b/code/modules/surgery/organs/internal/tongue/_tongue.dm
@@ -197,7 +197,7 @@
new /regex(@"\bX([\-|r|R]|\b)", "g") = "ECKS$1",
)
-/obj/item/organ/tongue/lizard/New(class, timer, datum/mutation/human/copymut)
+/obj/item/organ/tongue/lizard/Initialize(mapload)
. = ..()
AddComponent(/datum/component/speechmod, replacements = speech_replacements, should_modify_speech = CALLBACK(src, PROC_REF(should_modify_speech)))
diff --git a/code/modules/surgery/repair_puncture.dm b/code/modules/surgery/repair_puncture.dm
index 10b4925f4e061..77b36d264cdff 100644
--- a/code/modules/surgery/repair_puncture.dm
+++ b/code/modules/surgery/repair_puncture.dm
@@ -144,7 +144,7 @@
)
log_combat(user, target, "dressed burns in", addition="COMBAT MODE: [uppertext(user.combat_mode)]")
pierce_wound.adjust_blood_flow(-0.5)
- if(pierce_wound.blood_flow > 0)
+ if(!QDELETED(pierce_wound) && pierce_wound.blood_flow > 0)
surgery.status = REALIGN_INNARDS
to_chat(user, span_notice("There still seems to be misaligned blood vessels to finish..."))
else
diff --git a/code/modules/transport/elevator/elev_panel.dm b/code/modules/transport/elevator/elev_panel.dm
index 659049a7448ca..162c70f390cee 100644
--- a/code/modules/transport/elevator/elev_panel.dm
+++ b/code/modules/transport/elevator/elev_panel.dm
@@ -19,6 +19,7 @@
icon_state = "elevpanel0"
base_icon_state = "elevpanel"
+ mouse_over_pointer = MOUSE_HAND_POINTER
power_channel = AREA_USAGE_ENVIRON
// Indestructible until someone wants to make these constructible, with all the chaos that implies
resistance_flags = INDESTRUCTIBLE | LAVA_PROOF | FIRE_PROOF | UNACIDABLE | ACID_PROOF
diff --git a/code/modules/transport/tram/tram_controller.dm b/code/modules/transport/tram/tram_controller.dm
index 37220034b664b..1eeed96375f08 100644
--- a/code/modules/transport/tram/tram_controller.dm
+++ b/code/modules/transport/tram/tram_controller.dm
@@ -54,7 +54,7 @@
var/recovery_clear_count = 0
///if the tram's next stop will be the tram malfunction event sequence
- var/malf_active = FALSE
+ var/malf_active = TRANSPORT_SYSTEM_NORMAL
///fluff information of the tram, such as ongoing kill count and age
var/datum/tram_mfg_info/tram_registration
@@ -259,14 +259,16 @@
playsound(paired_cabinet, 'sound/machines/synth/synth_yes.ogg', 40, vary = FALSE, extrarange = SHORT_RANGE_SOUND_EXTRARANGE)
paired_cabinet.say("Controller reset.")
- if(malf_active)
- addtimer(CALLBACK(src, PROC_REF(announce_malf_event)), 1 SECONDS)
-
SEND_SIGNAL(src, COMSIG_TRAM_TRAVEL, idle_platform, destination_platform)
for(var/obj/structure/transport/linear/tram/transport_module as anything in transport_modules) //only thing everyone needs to know is the new location.
if(transport_module.travelling) //wee woo wee woo there was a double action queued. damn multi tile structs
return //we don't care to undo cover_locked controls, though, as that will resolve itself
+ if(malf_active == TRANSPORT_LOCAL_WARNING)
+ if(transport_module.check_for_humans())
+ throw_chance *= 1.75
+ malf_active = TRANSPORT_LOCAL_FAULT
+ addtimer(CALLBACK(src, PROC_REF(announce_malf_event)), 1 SECONDS)
transport_module.verify_transport_contents()
transport_module.glide_size_override = DELAY_TO_GLIDE_SIZE(speed_limiter)
transport_module.set_travelling(TRUE)
@@ -296,7 +298,7 @@
return PROCESS_KILL
if(!travel_remaining)
- if(!controller_operational || malf_active)
+ if(!controller_operational || malf_active == TRANSPORT_LOCAL_FAULT)
degraded_stop()
else
normal_stop()
@@ -370,10 +372,10 @@
paired_cabinet.say("Controller reset.")
log_transport("TC: [specific_transport_id] position data successfully reset. ")
speed_limiter = initial(speed_limiter)
- if(malf_active)
+ if(malf_active == TRANSPORT_LOCAL_FAULT)
set_status_code(SYSTEM_FAULT, TRUE)
addtimer(CALLBACK(src, PROC_REF(cycle_doors), CYCLE_OPEN), 2 SECONDS)
- malf_active = FALSE
+ malf_active = TRANSPORT_SYSTEM_NORMAL
throw_chance = initial(throw_chance)
playsound(paired_cabinet, 'sound/machines/buzz/buzz-sigh.ogg', 60, vary = FALSE, extrarange = SHORT_RANGE_SOUND_EXTRARANGE)
paired_cabinet.say("Controller error. Please contact your engineering department.")
@@ -417,6 +419,7 @@
* Performs a reset of the tram's position data by finding a predetermined reference landmark, then driving to it.
*/
/datum/transport_controller/linear/tram/proc/reset_position()
+ malf_active = TRANSPORT_SYSTEM_NORMAL
if(idle_platform)
if(get_turf(idle_platform) == get_turf(nav_beacon))
set_status_code(SYSTEM_FAULT, FALSE)
@@ -602,7 +605,8 @@
* Tram malfunction random event. Set comm error, requiring engineering or AI intervention.
*/
/datum/transport_controller/linear/tram/proc/start_malf_event()
- malf_active = TRUE
+ malf_active = TRANSPORT_LOCAL_WARNING
+ paired_cabinet.update_appearance()
throw_chance *= 1.25
log_transport("TC: [specific_transport_id] starting Tram Malfunction event.")
@@ -615,7 +619,8 @@
/datum/transport_controller/linear/tram/proc/end_malf_event()
if(!(malf_active))
return
- malf_active = FALSE
+ malf_active = TRANSPORT_SYSTEM_NORMAL
+ paired_cabinet.update_appearance()
throw_chance = initial(throw_chance)
log_transport("TC: [specific_transport_id] ending Tram Malfunction event.")
@@ -978,7 +983,7 @@
. += emissive_appearance(icon, "[base_icon_state]-estop", src, alpha = src.alpha)
return
- if(controller_datum.controller_status & SYSTEM_FAULT || controller_datum.malf_active)
+ if(controller_datum.controller_status & SYSTEM_FAULT || controller_datum.malf_active != TRANSPORT_SYSTEM_NORMAL)
. += mutable_appearance(icon, "[base_icon_state]-fault")
. += emissive_appearance(icon, "[base_icon_state]-fault", src, alpha = src.alpha)
return
@@ -1079,7 +1084,7 @@
"recoveryMode" = controller_datum.recovery_mode,
"currentSpeed" = controller_datum.current_speed,
"currentLoad" = controller_datum.current_load,
- "statusSF" = controller_datum.controller_status & SYSTEM_FAULT,
+ "statusSF" = controller_datum.controller_status & SYSTEM_FAULT || controller_datum.malf_active != TRANSPORT_SYSTEM_NORMAL,
"statusCE" = controller_datum.controller_status & COMM_ERROR,
"statusES" = controller_datum.controller_status & EMERGENCY_STOP,
"statusPD" = controller_datum.controller_status & PRE_DEPARTURE,
diff --git a/code/modules/transport/tram/tram_structures.dm b/code/modules/transport/tram/tram_structures.dm
index 346cb5e680283..9b04bba6ce7e3 100644
--- a/code/modules/transport/tram/tram_structures.dm
+++ b/code/modules/transport/tram/tram_structures.dm
@@ -474,8 +474,8 @@
canSmoothWith = null
/// Position of the spoiler
var/deployed = FALSE
- /// Malfunctioning due to tampering or emag
- var/malfunctioning = FALSE
+ /// Locked in position
+ var/locked = FALSE
/// Weakref to the tram piece we control
var/datum/weakref/tram_ref
/// The tram we're attached to
@@ -494,7 +494,7 @@
context[SCREENTIP_CONTEXT_LMB] = "repair"
if(held_item?.tool_behaviour == TOOL_WELDER && atom_integrity >= max_integrity)
- context[SCREENTIP_CONTEXT_LMB] = "[malfunctioning ? "repair" : "lock"]"
+ context[SCREENTIP_CONTEXT_LMB] = "[locked ? "repair" : "sabotage"]"
return CONTEXTUAL_SCREENTIP_SET
@@ -503,22 +503,19 @@
if(obj_flags & EMAGGED)
. += span_warning("The electronics panel is sparking occasionally. It can be reset with a [EXAMINE_HINT("multitool.")]")
- if(malfunctioning)
+ if(locked)
. += span_warning("The spoiler is [EXAMINE_HINT("welded")] in place!")
else
- . += span_notice("The spoiler can be locked in to place with a [EXAMINE_HINT("welder.")]")
+ . += span_notice("The spoiler can be locked in place with a [EXAMINE_HINT("welder.")]")
/obj/structure/tram/spoiler/proc/set_spoiler(source, controller, controller_active, controller_status, travel_direction)
SIGNAL_HANDLER
var/spoiler_direction = travel_direction
- if(obj_flags & EMAGGED && !malfunctioning)
- malfunctioning = TRUE
-
- if(malfunctioning || controller_status & COMM_ERROR)
+ if(locked || controller_status & COMM_ERROR || obj_flags & EMAGGED)
if(!deployed)
// Bring out the blades
- if(malfunctioning)
+ if(locked)
visible_message(span_danger("\the [src] locks up due to its servo overheating!"))
do_sparks(3, cardinal_only = FALSE, source = src)
deploy_spoiler()
@@ -583,14 +580,14 @@
return FALSE
if(atom_integrity >= max_integrity)
- to_chat(user, span_warning("You begin to weld \the [src], [malfunctioning ? "repairing damage" : "preventing retraction"]."))
+ to_chat(user, span_warning("You begin to weld \the [src], [locked ? "repairing damage" : "preventing retraction"]."))
if(!tool.use_tool(src, user, 4 SECONDS, volume = 50))
return
- malfunctioning = !malfunctioning
- user.visible_message(span_warning("[user] [malfunctioning ? "welds \the [src] in place" : "repairs \the [src]"] with [tool]."), \
- span_warning("You finish welding \the [src], [malfunctioning ? "locking it in place." : "it can move freely again!"]"), null, COMBAT_MESSAGE_RANGE)
+ locked = !locked
+ user.visible_message(span_warning("[user] [locked ? "welds \the [src] in place" : "repairs \the [src]"] with [tool]."), \
+ span_warning("You finish welding \the [src], [locked ? "locking it in place." : "it can move freely again!"]"), null, COMBAT_MESSAGE_RANGE)
- if(malfunctioning)
+ if(locked)
deploy_spoiler()
update_appearance()
@@ -606,7 +603,7 @@
/obj/structure/tram/spoiler/update_overlays()
. = ..()
- if(deployed && malfunctioning)
+ if(deployed && locked)
. += mutable_appearance(icon, "tram-spoiler-welded")
/obj/structure/chair/sofa/bench/tram
diff --git a/code/modules/transport/transport_module.dm b/code/modules/transport/transport_module.dm
index b0497ed3b2e9c..d1384067a0d1a 100644
--- a/code/modules/transport/transport_module.dm
+++ b/code/modules/transport/transport_module.dm
@@ -171,6 +171,13 @@
if(!(movable_contents.loc in locs))
remove_item_from_transport(movable_contents)
+/obj/structure/transport/linear/proc/check_for_humans()
+ for(var/atom/movable/movable_contents as anything in transport_contents)
+ if(ishuman(movable_contents))
+ return TRUE
+
+ return FALSE
+
///signal handler for COMSIG_MOVABLE_UPDATE_GLIDE_SIZE: when a movable in transport_contents changes its glide_size independently.
///adds that movable to a lazy list, movables in that list have their glide_size updated when the tram next moves
/obj/structure/transport/linear/proc/on_changed_glide_size(atom/movable/moving_contents, new_glide_size)
diff --git a/code/modules/tutorials/tutorial_skip.dm b/code/modules/tutorials/tutorial_skip.dm
index 803aebc865054..48c4a9550dbc1 100644
--- a/code/modules/tutorials/tutorial_skip.dm
+++ b/code/modules/tutorials/tutorial_skip.dm
@@ -6,6 +6,7 @@
alpha = 0
mouse_opacity = MOUSE_OPACITY_OPAQUE
layer = TUTORIAL_INSTRUCTIONS_LAYER
+ mouse_over_pointer = MOUSE_HAND_POINTER
var/atom/movable/screen/tutorial_skip_text/skip_text
/atom/movable/screen/tutorial_skip/Initialize(mapload, datum/hud/hud_owner)
diff --git a/code/modules/unit_tests/fish_unit_tests.dm b/code/modules/unit_tests/fish_unit_tests.dm
index 767c1ceeaebfa..7ed7851b86af2 100644
--- a/code/modules/unit_tests/fish_unit_tests.dm
+++ b/code/modules/unit_tests/fish_unit_tests.dm
@@ -23,8 +23,7 @@
/datum/unit_test/fish_size_weight/Run()
var/obj/structure/table/table = allocate(/obj/structure/table)
- var/obj/item/fish/testdummy/fish = new /obj/item/fish/testdummy (table.loc)
- allocated += fish
+ var/obj/item/fish/testdummy/fish = allocate(__IMPLIED_TYPE__, table.loc)
var/datum/reagent/reagent = fish.reagents?.has_reagent(/datum/reagent/fishdummy)
TEST_ASSERT(reagent, "the test fish doesn't have the test reagent.[fish.reagents ? "" : " It doesn't even have a reagent holder."]")
var/expected_units = FISH_REAGENT_AMOUNT * fish.weight / FISH_WEIGHT_BITE_DIVISOR
@@ -42,14 +41,32 @@
allocated += content
TEST_ASSERT_EQUAL(counted_fillets, expected_num_fillets, "the test fish yielded [counted_fillets] fillets when it should have been [expected_num_fillets]")
+/// Make sure fish don't stay hungry after being fed
+/datum/unit_test/fish_feeding
+
+/datum/unit_test/fish_feeding/Run()
+ var/obj/item/fish/testdummy/hungry = allocate(__IMPLIED_TYPE__)
+ hungry.last_feeding = 0 //the fish should be hungry.
+ TEST_ASSERT(hungry.get_hunger(), "the fish doesn't seem to be hungry in the slightest")
+ var/obj/item/reagent_containers/cup/fish_feed/yummy = allocate(__IMPLIED_TYPE__)
+ hungry.feed(yummy.reagents)
+ TEST_ASSERT(!hungry.get_hunger(), "the fish is still hungry despite having been just fed")
+
+ ///Try feeding it again, but this time with the right hunger so they actually grow
+ hungry.last_feeding = world.time - (hungry.feeding_frequency * FISH_GROWTH_PEAK)
+ var/old_size = hungry.size
+ var/old_weight = hungry.weight
+ hungry.feed(yummy.reagents)
+ TEST_ASSERT(hungry.size > old_size, "the fish size didn't increase after being properly fed")
+ TEST_ASSERT(hungry.weight > old_weight, "the fish weight didn't increase after being properly fed")
+
///Checks that fish breeding works correctly.
/datum/unit_test/fish_breeding
/datum/unit_test/fish_breeding/Run()
- var/obj/item/fish/fish = allocate(/obj/item/fish/testdummy)
+ var/obj/item/fish_tank/reproduction/fish_tank = allocate(__IMPLIED_TYPE__)
///Check if the fishes can generate offsprings at all.
- var/obj/item/fish/fish_two = allocate(/obj/item/fish/testdummy/two)
- var/obj/item/fish/new_fish = fish.create_offspring(fish_two.type, fish_two)
+ var/obj/item/fish/new_fish = fish_tank.fish.try_to_reproduce()
TEST_ASSERT(new_fish, "the two test fishes couldn't generate an offspring")
var/traits_len = length(new_fish.fish_traits)
TEST_ASSERT_NOTEQUAL(traits_len, 2, "the offspring of the test fishes has both parents' traits, which are incompatible with each other")
@@ -66,6 +83,20 @@
TEST_ASSERT(cloner_jr, "The test aquarium's cloner fish didn't manage to reproduce when it should have")
TEST_ASSERT_NOTEQUAL(cloner_jr.type, aquarium.sterile.type, "The test aquarium's cloner fish mated with the sterile fish")
+/obj/item/fish_tank/reproduction
+ var/obj/item/fish/testdummy/small/fish
+ var/obj/item/fish/testdummy/small/partner
+
+/obj/item/fish_tank/reproduction/Initialize(mapload)
+ . = ..()
+ fish = new(src)
+ partner = new(src)
+
+/obj/item/fish_tank/reproduction/Destroy()
+ fish = null
+ partner = null
+ return ..()
+
///Checks that fish evolutions work correctly.
/datum/unit_test/fish_evolution
@@ -104,6 +135,10 @@
fish_id_redirect_path = /obj/item/fish/goldfish //Stops SSfishing from complaining
var/expected_num_fillets = 0 //used to know how many fillets should be gotten out of this fish
+/obj/item/fish/testdummy/small
+ // The parent type is too big to reproduce inside the more compact fish tank
+ average_size = /obj/item/fish_tank::max_total_size * 0.2
+
/obj/item/fish/testdummy/add_fillet_type()
expected_num_fillets = ..()
return expected_num_fillets
diff --git a/code/modules/unit_tests/unit_test.dm b/code/modules/unit_tests/unit_test.dm
index 6a2bda4ee25af..241d7b54c39af 100644
--- a/code/modules/unit_tests/unit_test.dm
+++ b/code/modules/unit_tests/unit_test.dm
@@ -254,6 +254,8 @@ GLOBAL_VAR_INIT(focused_tests, focused_tests())
//Yet more templates
/obj/machinery/restaurant_portal,
//Template type
+ /obj/machinery/power/turbine,
+ //Template type
/obj/effect/mob_spawn,
//Template type
/obj/structure/holosign/robot_seat,
diff --git a/code/modules/uplink/uplink_items.dm b/code/modules/uplink/uplink_items.dm
index dc8b776131e59..1782f836bea70 100644
--- a/code/modules/uplink/uplink_items.dm
+++ b/code/modules/uplink/uplink_items.dm
@@ -194,7 +194,8 @@
return
QDEL_NULL(gun_reward.pin)
- gun_reward.pin = new /obj/item/firing_pin(gun_reward)
+ var/obj/item/firing_pin/pin = new
+ pin.gun_insert(new_gun = gun_reward)
///For special overrides if an item can be bought or not.
/datum/uplink_item/proc/can_be_bought(datum/uplink_handler/source)
diff --git a/code/modules/uplink/uplink_items/job.dm b/code/modules/uplink/uplink_items/job.dm
index 953a6cf7ace5d..935461509ab5d 100644
--- a/code/modules/uplink/uplink_items/job.dm
+++ b/code/modules/uplink/uplink_items/job.dm
@@ -258,7 +258,7 @@
progression_minimum = 15 MINUTES
item = /obj/item/gun/chem
cost = 12
- restricted_roles = list(JOB_CHEMIST, JOB_CHIEF_MEDICAL_OFFICER, JOB_BOTANIST)
+ restricted_roles = list(JOB_CHEMIST, JOB_MEDICAL_DOCTOR, JOB_CHIEF_MEDICAL_OFFICER, JOB_BOTANIST)
/datum/uplink_item/role_restricted/pie_cannon
name = "Banana Cream Pie Cannon"
diff --git a/code/modules/vehicles/mecha/combat/durand.dm b/code/modules/vehicles/mecha/combat/durand.dm
index 1497c4c615de7..6995206942938 100644
--- a/code/modules/vehicles/mecha/combat/durand.dm
+++ b/code/modules/vehicles/mecha/combat/durand.dm
@@ -274,7 +274,7 @@ own integrity back to max. Shield is automatically dropped if we run out of powe
flick("shield_impact", src)
if(!.)
return
- if(!chassis.use_energy(. * (STANDARD_CELL_CHARGE / 15)))
+ if(!chassis.use_energy(. * (STANDARD_CELL_CHARGE / 150)))
chassis.cell?.charge = 0
for(var/O in chassis.occupants)
var/mob/living/occupant = O
diff --git a/code/modules/vehicles/mecha/equipment/tools/other_tools.dm b/code/modules/vehicles/mecha/equipment/tools/other_tools.dm
index 4d3b682b4277b..15cc3e6b8cdb5 100644
--- a/code/modules/vehicles/mecha/equipment/tools/other_tools.dm
+++ b/code/modules/vehicles/mecha/equipment/tools/other_tools.dm
@@ -285,7 +285,7 @@
///Maximum fuel capacity of the generator, in units
var/max_fuel = 75 * SHEET_MATERIAL_AMOUNT
///Energy recharged per second
- var/rechargerate = 0.005 * STANDARD_CELL_RATE
+ var/rechargerate = 0.05 * STANDARD_CELL_RATE
/obj/item/mecha_parts/mecha_equipment/generator/Initialize(mapload)
. = ..()
diff --git a/code/modules/vending/mail.dm b/code/modules/vending/mail.dm
new file mode 100644
index 0000000000000..1e091a3128756
--- /dev/null
+++ b/code/modules/vending/mail.dm
@@ -0,0 +1,309 @@
+#define STATE_SORTING "sorting"
+#define STATE_IDLE "idle"
+#define STATE_YES "yes"
+#define STATE_NO "no"
+#define MAIL_CAPACITY 100
+
+/obj/machinery/mailsorter
+ name = "mail sorter"
+ desc = "A large mail sorting unit. Sorting mail since 1987!"
+ icon = 'icons/obj/machines/mailsorter.dmi'
+ icon_state = "mailsorter"
+ base_icon_state = "mailsorter"
+ layer = BELOW_OBJ_LAYER
+ density = TRUE
+ max_integrity = 300
+ integrity_failure = 0.33
+ req_access = list(ACCESS_CARGO)
+ circuit = /obj/item/circuitboard/machine/mailsorter
+
+ var/light_mask = "mailsorter-light-mask"
+ var/panel_type = "panel"
+
+ /// What the machine is currently doing. Can be "sorting", "idle", "yes", "no".
+ var/currentstate = STATE_IDLE
+ /// List of all mail that's inside the mailbox.
+ var/list/mail_list = list()
+ /// The direction in which the mail will be unloaded.
+ var/output_dir = SOUTH
+ /// List of the departments to sort the mail for.
+ var/static/list/sorting_departments = list(
+ DEPARTMENT_ENGINEERING,
+ DEPARTMENT_SECURITY,
+ DEPARTMENT_MEDICAL,
+ DEPARTMENT_SCIENCE,
+ DEPARTMENT_CARGO,
+ DEPARTMENT_SERVICE,
+ DEPARTMENT_COMMAND,
+ )
+ var/static/list/choices = list(
+ "Eject" = icon('icons/hud/radial.dmi', "radial_eject"),
+ "Dump" = icon('icons/hud/radial.dmi', "mail_dump"),
+ "Sort" = icon('icons/hud/radial.dmi', "mail_sort"),
+ )
+
+/// Steps one tile in the `output_dir`. Returns `turf`.
+/obj/machinery/mailsorter/proc/get_unload_turf()
+ return get_step(src, output_dir)
+
+/obj/machinery/mailsorter/screwdriver_act(mob/living/user, obj/item/tool)
+ default_deconstruction_screwdriver(user, "[base_icon_state]-off", base_icon_state, tool)
+ update_appearance(UPDATE_OVERLAYS)
+ return ITEM_INTERACT_SUCCESS
+
+/obj/machinery/mailsorter/crowbar_act(mob/living/user, obj/item/tool)
+ default_deconstruction_crowbar(tool)
+ return ITEM_INTERACT_SUCCESS
+
+/obj/machinery/mailsorter/examine(mob/user)
+ . = ..()
+ . += span_notice("There is[length(mail_list) < 100 ? " " : " no more "]space for [length(mail_list) < 100 ? "[100 - length(mail_list)] " : ""]envelope\s inside.")
+ . += span_notice("There [length(mail_list) >= 2 ? "are" : "is"] [length(mail_list) ? length(mail_list) : "no"] envelope\s inside.")
+ if(panel_open)
+ . += span_notice("Alt-click to rotate the output direction.")
+
+/obj/machinery/mailsorter/Destroy()
+ drop_all_mail()
+ . = ..()
+
+/// Drops all enevlopes on the machine turf. Only occurs when the machine is broken.
+/obj/machinery/mailsorter/proc/drop_all_mail(damage_flag)
+ if(!isturf(get_turf(src)))
+ QDEL_LIST(mail_list)
+ return
+ for(var/obj/item/mail in mail_list)
+ mail.forceMove(src)
+ mail_list -= mail
+
+/// Dumps all envelopes on the `unload_turf`.
+/obj/machinery/mailsorter/proc/dump_all_mail()
+ if(!isturf(get_turf(src)))
+ QDEL_LIST(mail_list)
+ return
+ var/turf/unload_turf = get_unload_turf()
+ for(var/obj/item/mail in mail_list)
+ mail.forceMove(unload_turf)
+ mail.throw_at(unload_turf, 2, 3)
+ mail_list -= mail
+
+/// Validates whether the inserted item is acceptable.
+/obj/machinery/mailsorter/proc/accept_check(obj/item/weapon)
+ var/static/list/accepted_items = list(
+ /obj/item/mail,
+ /obj/item/mail/envelope,
+ /obj/item/mail/junkmail,
+ /obj/item/mail/mail_strike,
+ /obj/item/mail/traitor,
+ /obj/item/paper,
+ )
+ return is_type_in_list(weapon, accepted_items)
+
+/obj/machinery/mailsorter/interact(mob/user)
+ if (!allowed(user))
+ to_chat(user, span_warning("Access denied."))
+ return
+ if (currentstate != STATE_IDLE)
+ return
+ if (length(mail_list) == 0)
+ to_chat(user, span_warning("There's no mail inside!"))
+ return
+ var/choice = show_radial_menu(
+ user,
+ src,
+ choices,
+ require_near = !HAS_SILICON_ACCESS(user),
+ autopick_single_option = FALSE,
+ )
+ if (!choice)
+ return
+ switch (choice)
+ if ("Eject")
+ pick_mail(user)
+ if ("Dump")
+ playsound(src, 'sound/machines/buzz/buzz-sigh.ogg', 20, TRUE)
+ to_chat(user, span_notice("[src] dumps [length(mail_list)] envelope\s on the floor."))
+ dump_all_mail()
+ if ("Sort")
+ sort_mail(user)
+
+/// Prompts the player to select a department to sort the mail for. Returns if `null`.
+/obj/machinery/mailsorter/proc/sort_mail(mob/user)
+ var/sorting_dept = tgui_input_list(user, "Choose the department to sort mail for","Mail Sorting", sorting_departments)
+ if (!sorting_dept)
+ return
+ currentstate = STATE_SORTING
+ update_appearance(UPDATE_OVERLAYS)
+ playsound(src, 'sound/machines/mail_sort.ogg', 20, TRUE)
+ addtimer(CALLBACK(src, PROC_REF(continue_sort), user, sorting_dept), 5 SECONDS)
+
+/// Sorts the mail based on the picked department. Ejects the sorted envelopes onto the `unload_turf`.
+/obj/machinery/mailsorter/proc/continue_sort(mob/user, sorting_dept)
+ var/list/sorted_mail = list()
+ var/total_to_sort = length(mail_list)
+ var/sorted = 0
+ var/unable_to_sort = 0
+
+ for (var/obj/item/mail/some_mail in mail_list)
+ if (!some_mail.recipient_ref)
+ unable_to_sort ++
+ continue
+ var/datum/mind/some_recipient = some_mail.recipient_ref.resolve()
+ if (some_recipient)
+ var/datum/job/recipient_job = some_recipient.assigned_role
+ var/datum/job_department/primary_department = recipient_job.departments_list?[1]
+ var/datum/job_department/main_department = primary_department.department_name
+ if (main_department == sorting_dept)
+ sorted_mail.Add(some_mail)
+ sorted ++
+ else
+ unable_to_sort ++
+ if (length(sorted_mail) == 0)
+ currentstate = STATE_NO
+ update_appearance(UPDATE_OVERLAYS)
+ playsound(src, 'sound/machines/buzz/buzz-sigh.ogg', 20, TRUE)
+ say("No mail for the following department: [sorting_dept].")
+ else
+ currentstate = STATE_YES
+ update_appearance(UPDATE_OVERLAYS)
+ say("[sorted] envelope\s sorted successfully.")
+ playsound(src, 'sound/machines/ping.ogg', 20, TRUE)
+ to_chat(user, span_notice("[src] ejects [length(sorted_mail)] envelope\s."))
+ var/turf/unload_turf = get_unload_turf()
+ for (var/obj/item/mail/mail_in_list in sorted_mail)
+ mail_in_list.forceMove(unload_turf)
+ sorted_mail -= mail_in_list
+ mail_list -= mail_in_list
+ addtimer(CALLBACK(src, PROC_REF(check_sorted), unable_to_sort, total_to_sort), 1 SECONDS)
+
+/// Informs the player of the amount of processed envelopes.
+/obj/machinery/mailsorter/proc/check_sorted(mob/user, unable_to_sort, total_to_sort)
+ if (unable_to_sort > 0)
+ playsound(src, 'sound/machines/buzz/buzz-sigh.ogg', 20, TRUE)
+ say("Couldn't sort [unable_to_sort] envelope\s.")
+ else
+ playsound(src, 'sound/machines/ping.ogg', 20, TRUE)
+ say("[total_to_sort] envelope\s processed.")
+ addtimer(CALLBACK(src, PROC_REF(update_state_after_sorting)), 1 SECONDS)
+
+/obj/machinery/mailsorter/proc/update_state_after_sorting()
+ currentstate = STATE_IDLE
+ update_appearance(UPDATE_OVERLAYS)
+
+/obj/machinery/mailsorter/item_interaction(mob/user, obj/item/thingy, params)
+ if (istype(thingy, /obj/item/storage/bag/mail))
+ if (length(thingy.contents) < 1)
+ to_chat(user, span_warning("The [thingy] is empty!"))
+ return
+ var/loaded = 0
+ for (var/obj/item/mail in thingy.contents)
+ if (!(mail.item_flags & ABSTRACT) && \
+ !(mail.flags_1 & HOLOGRAM_1) && \
+ accept_check(mail) \
+ )
+ if (length(mail_list) + 1 > MAIL_CAPACITY )
+ to_chat(user, span_warning("There is no space for more mail in [src]!"))
+ return FALSE
+ else if (load(mail, user))
+ loaded++
+ mail_list += mail
+ if(loaded)
+ user.visible_message(span_notice("[user] loads \the [src] with \the [thingy]."), \
+ span_notice("You load \the [src] with \the [thingy]."))
+ if(length(thingy.contents))
+ to_chat(user, span_warning("Some items are refused."))
+ return TRUE
+ else
+ to_chat(user, span_warning("There is nothing in \the [thingy] to put in the [src]!"))
+ return FALSE
+ else if (istype(thingy, /obj/item/mail))
+ if (length(mail_list) + 1 > MAIL_CAPACITY )
+ to_chat(user, span_warning("There is no space for more mail in [src]!"))
+ else
+ thingy.forceMove(src)
+ mail_list += thingy
+ to_chat(user, span_notice("The [src] whizzles as it accepts the [thingy]."))
+
+/// Prompts the user to select an anvelope from the list of all the envelopes inside.
+/obj/machinery/mailsorter/proc/pick_mail(mob/user)
+ if(!length(mail_list))
+ return
+ var/obj/item/mail/mail_throw = tgui_input_list(user, "Choose the envelope to eject","Mail Sorting", mail_list)
+ if(!mail_throw)
+ return
+ currentstate = STATE_SORTING
+ update_appearance(UPDATE_OVERLAYS)
+ playsound(src, 'sound/machines/mail_sort.ogg', 20, TRUE)
+ addtimer(CALLBACK(src, PROC_REF(pick_envelope), user, mail_throw), 50)
+
+/// Ejects a single envelope the player has picked onto the `unload_turf`.
+/obj/machinery/mailsorter/proc/pick_envelope(mob/user, obj/item/mail/mail_throw)
+ to_chat(user, span_notice("[src] reluctantly spits out [mail_throw]."))
+ var/turf/unload_turf = get_unload_turf()
+ mail_throw.forceMove(unload_turf)
+ mail_throw.throw_at(unload_turf, 2, 3)
+ mail_list -= mail_throw
+ currentstate = STATE_IDLE
+ update_appearance(UPDATE_OVERLAYS)
+
+/// Tries to load something into the machine.
+/obj/machinery/mailsorter/proc/load(obj/item/thingy, mob/user)
+ if(ismob(thingy.loc))
+ var/mob/owner = thingy.loc
+ if(!owner.transferItemToLoc(thingy, src))
+ to_chat(owner, span_warning("\the [thingy] is stuck to your hand, you cannot put it in \the [src]!"))
+ return FALSE
+ return TRUE
+ else
+ if(thingy.loc.atom_storage)
+ return thingy.loc.atom_storage.attempt_remove(thingy, src, silent = TRUE)
+ else
+ thingy.forceMove(src)
+ return TRUE
+
+/obj/machinery/mailsorter/click_alt(mob/living/user)
+ if(!panel_open)
+ return CLICK_ACTION_BLOCKING
+ output_dir = turn(output_dir, -90)
+ to_chat(user, span_notice("You change [src]'s I/O settings, setting the output to [dir2text(output_dir)]."))
+ update_appearance(UPDATE_OVERLAYS)
+ return CLICK_ACTION_SUCCESS
+
+
+/obj/machinery/mailsorter/update_overlays()
+ . = ..()
+ if(!powered())
+ return
+ if(!(machine_stat & BROKEN))
+ var/image/mail_output = image(icon='icons/obj/doors/airlocks/station/overlays.dmi', icon_state="unres_[output_dir]")
+ switch(output_dir)
+ if(NORTH)
+ mail_output.pixel_y = 32
+ if(SOUTH)
+ mail_output.pixel_y = -32
+ if(EAST)
+ mail_output.pixel_x = 32
+ if(WEST)
+ mail_output.pixel_x = -32
+ mail_output.color = COLOR_CRAYON_ORANGE
+ var/mutable_appearance/light_out = emissive_appearance(mail_output.icon, mail_output.icon_state, offset_spokesman = src, alpha = mail_output.alpha)
+ light_out.pixel_y = mail_output.pixel_y
+ light_out.pixel_x = mail_output.pixel_x
+ . += mail_output
+ . += light_out
+ . += mutable_appearance(base_icon_state, currentstate)
+ if(panel_open)
+ . += panel_type
+ if(light_mask && !(machine_stat & BROKEN))
+ . += emissive_appearance(icon, light_mask, src)
+
+/obj/machinery/mailsorter/update_icon_state()
+ icon_state = "[base_icon_state][powered() ? null : "-off"]"
+ if(machine_stat & BROKEN)
+ icon_state = "[base_icon_state]-broken"
+ return ..()
+
+#undef STATE_SORTING
+#undef STATE_IDLE
+#undef STATE_YES
+#undef STATE_NO
+#undef MAIL_CAPACITY
diff --git a/code/modules/vending/wardrobes.dm b/code/modules/vending/wardrobes.dm
index cbc2fa73185cf..6fdd8f5d25e1c 100644
--- a/code/modules/vending/wardrobes.dm
+++ b/code/modules/vending/wardrobes.dm
@@ -202,6 +202,7 @@ GLOBAL_VAR_INIT(roaches_deployed, FALSE)
/obj/item/storage/bag/mail = 3,
/obj/item/radio/headset/headset_cargo = 3,
/obj/item/clothing/accessory/pocketprotector = 3,
+ /obj/item/flatpack/mailsorter = 1,
)
premium = list(
/obj/item/clothing/head/costume/mailman = 1,
diff --git a/dependencies.sh b/dependencies.sh
index d9b286e61aed5..1046b72c12d77 100644
--- a/dependencies.sh
+++ b/dependencies.sh
@@ -8,7 +8,7 @@ export BYOND_MAJOR=515
export BYOND_MINOR=1637
#rust_g git tag
-export RUST_G_VERSION=3.3.0
+export RUST_G_VERSION=3.5.1
#node version
export NODE_VERSION_LTS=22.11.0
diff --git a/html/changelogs/AutoChangeLog-pr-88254.yml b/html/changelogs/AutoChangeLog-pr-88254.yml
new file mode 100644
index 0000000000000..e2ee830005d84
--- /dev/null
+++ b/html/changelogs/AutoChangeLog-pr-88254.yml
@@ -0,0 +1,5 @@
+author: "SyncIt21"
+delete-after: True
+changes:
+ - bugfix: "Turbine converts energy to power correctly & shows correct reading with multitool"
+ - refactor: "turbine code has been overall improved. report bugs on github"
\ No newline at end of file
diff --git a/html/changelogs/AutoChangeLog-pr-88319.yml b/html/changelogs/AutoChangeLog-pr-88319.yml
deleted file mode 100644
index bb09fca18c3ce..0000000000000
--- a/html/changelogs/AutoChangeLog-pr-88319.yml
+++ /dev/null
@@ -1,4 +0,0 @@
-author: "SmArtKar"
-delete-after: True
-changes:
- - qol: "Shifted the escape menu stat panel down a bit"
\ No newline at end of file
diff --git a/html/changelogs/AutoChangeLog-pr-88321.yml b/html/changelogs/AutoChangeLog-pr-88321.yml
deleted file mode 100644
index 4da9add265994..0000000000000
--- a/html/changelogs/AutoChangeLog-pr-88321.yml
+++ /dev/null
@@ -1,4 +0,0 @@
-author: "13spacemen"
-delete-after: True
-changes:
- - rscdel: "Time Dilation no longer shows in the hub text"
\ No newline at end of file
diff --git a/html/changelogs/AutoChangeLog-pr-88344.yml b/html/changelogs/AutoChangeLog-pr-88344.yml
deleted file mode 100644
index e2b3493e8c5d8..0000000000000
--- a/html/changelogs/AutoChangeLog-pr-88344.yml
+++ /dev/null
@@ -1,5 +0,0 @@
-author: "Ben10Omintrix"
-delete-after: True
-changes:
- - bugfix: "repairbots now gain their destructive abilities when hacked by an AI"
- - bugfix: "repairbot crafting recipes have been updated"
\ No newline at end of file
diff --git a/html/changelogs/AutoChangeLog-pr-88435.yml b/html/changelogs/AutoChangeLog-pr-88435.yml
new file mode 100644
index 0000000000000..0f91aa47c2209
--- /dev/null
+++ b/html/changelogs/AutoChangeLog-pr-88435.yml
@@ -0,0 +1,4 @@
+author: "tontyGH"
+delete-after: True
+changes:
+ - bugfix: "Underlining your messages in loud mode shouldn't break anymore"
\ No newline at end of file
diff --git a/html/changelogs/AutoChangeLog-pr-88492.yml b/html/changelogs/AutoChangeLog-pr-88492.yml
new file mode 100644
index 0000000000000..3b1598c748c0f
--- /dev/null
+++ b/html/changelogs/AutoChangeLog-pr-88492.yml
@@ -0,0 +1,4 @@
+author: "SmArtKar"
+delete-after: True
+changes:
+ - qol: "Jetpacks should ACTUALLY feel better now"
\ No newline at end of file
diff --git a/html/changelogs/AutoChangeLog-pr-88508.yml b/html/changelogs/AutoChangeLog-pr-88508.yml
new file mode 100644
index 0000000000000..30b95cd438a3c
--- /dev/null
+++ b/html/changelogs/AutoChangeLog-pr-88508.yml
@@ -0,0 +1,4 @@
+author: "JoshAdamPowell"
+delete-after: True
+changes:
+ - map: "In the new year's budget the syndicate have decided that chemists need beakers to do their job properly."
\ No newline at end of file
diff --git a/html/changelogs/archive/2024-12.yml b/html/changelogs/archive/2024-12.yml
index c2df09a887411..210a0f1e6f04a 100644
--- a/html/changelogs/archive/2024-12.yml
+++ b/html/changelogs/archive/2024-12.yml
@@ -128,3 +128,269 @@
Wallem:
- image: Updates slime potion sprites, adds some new colors and rearranges some
others.
+2024-12-05:
+ 13spacemen:
+ - rscdel: Time Dilation no longer shows in the hub text
+ Ben10Omintrix:
+ - bugfix: repairbots now gain their destructive abilities when hacked by an AI
+ - bugfix: repairbot crafting recipes have been updated
+ Majkl-J:
+ - bugfix: Ruins will now correctly spawn their tied ruins in
+ - bugfix: The map_logging test now runs proper
+ - code_imp: The stacked_lights test now screams with area names too.
+ SmArtKar:
+ - qol: Shifted the escape menu stat panel down a bit
+2024-12-06:
+ Autisem:
+ - refactor: Nanotrasen has introducted new upgrades into the aging station shield
+ statalites, they require a but longer to toggle on however
+ OrionTheFox:
+ - bugfix: fixed the Icebox Phonebooth air alarm being on the outside, thus triggering
+ because the planet is, indeed, cold. It is now inside and all-access so that
+ callers can turn it off when they decide the phone's more important than their
+ health and safety.
+ SmArtKar:
+ - bugfix: Fixed atrocinator not yeeting you up
+ - bugfix: Fixed a qdel loop in hypnosis brain trauma
+ imedial:
+ - bugfix: Map vote now cares about current player count
+2024-12-07:
+ Ben10Omintrix:
+ - qol: u can now directly feed animals from ur hands, like raptors or cats, by clicking
+ on them with their preferred food.
+ - balance: u can now heal ur raptors mid or post battles by hand feeding them ores
+ FlufflesTheDog:
+ - spellcheck: paywall firing pins no longer set the gun description to the pin's
+ description on removal
+ LT3:
+ - bugfix: Tram spoilers correctly provide welder or multitool hints depending on
+ their damage
+ - bugfix: Malfunctioning tram controller flashes orange and can be preemptively
+ fixed before it crashes
+ SmArtKar:
+ - qol: AI laws and tape recorders no longer cause radio blips
+ - balance: Removed organ "refreshing" from legion cores, magic wands and regenerative
+ crossbreeds so they no longer get rid of your implants
+ - bugfix: Fixed an edge case with meteor moveloop code
+ - bugfix: Fixed projectile homing
+ - rscadd: Toolboxes can be used on any object to pull out and use a tool from it
+ as long as your offhand is free.
+ - qol: Jetpacks are significantly smoother and nicer to use now - and not affected
+ by lag anymore!
+ - code_imp: Cleaned up spacemove/jetpack code a bit and moved some common code to
+ helpers.
+ - refactor: Wings are now... jetpacks. They behave exactly the same and this should
+ reduce the amount of copypaste code in spacemove significantly.
+ SmArtKar, Kapu:
+ - code_imp: Implemented caching for icon sizes which should significantly improve
+ mob health performance due to HUDs constantly fetching icons
+ SyncIt21:
+ - code_imp: improved code for machinery
+ - code_imp: slightly improved code for borg inducer
+ - spellcheck: fixes examines & screentips for borg inducer
+ - code_imp: condensed code for reagent grinder
+ - bugfix: reagent grinder won't break when 2 or more people are simultaneously interacting
+ with it
+ - bugfix: ejecting contents & examining the reagent grinder as an AI via the radial
+ menu does not require it to be powered or anchored
+ - bugfix: examine block for reagent grinder as an AI is properly formatted
+ Time-Green:
+ - balance: Bioscramblers are no longer immortal
+ - balance: Anomalies give 20 extra seconds to defuse! Or 20 extra seconds for them
+ to reach havoc...
+ - balance: Material anomalies only teleport 1-4 times before detonating
+ carlarctg:
+ - bugfix: Recovered crew no longer show up on roundend report
+ - rscadd: Surgery trays now have a small chance to become medical toolboxes. Autopsy
+ trays can become coroner toolboxes.
+ - rscadd: Added a 1 in 1.000.000 chance for a toolbox to have four latches.
+ mc-oofert:
+ - balance: A mutation in gatfruit seeds has led to a drastic alteration in the observable
+ traits of the plant, which now fires hardened peas that deal less damage, but
+ poison the target. Additionally, its poison can be, with some botanical engineering,
+ replaced with whatever you wish.
+ - balance: burglars finesse spell range increased from 4 to 6 and it may loot any
+ back storage object, caretakers refuge cooldown is only applied when exiting
+ refuge, labyrinth handbook accepts any crayon instead of a white crayon
+ - qol: you may click an id with the knock heretic id card to make it consume it
+ - rscadd: janitor modsuit space cleaner mister module
+ - bugfix: manufacturing assembling machine crafts junk shells and lizard boots properly,
+ may no longer craft anchored objects (broken check), and sends its crafted stuff
+ at once
+ - balance: changeling last resort works as a monkey or animal
+ timothymtorres:
+ - bugfix: Fix drink labels for alcohol bottles
+ - rscadd: Add medical human organ crate emergency medical holodeck simulation
+ - bugfix: Fix gravity not updating for mobs when teleporting, wormhole jaunters,
+ wizard spells, tile creation/destruction, mech entry/ejection and other methods.
+ - bugfix: Fix gravity for areas in space near station (solars, nearspace, bomb testing,
+ etc.)
+ tontyGH:
+ - bugfix: runetext fades in correctly in bulk. signers rejoice
+ zxaber:
+ - bugfix: Mechs with crowbar-like tools can now hold adjacent firelocks open correctly.
+2024-12-08:
+ SyncIt21:
+ - refactor: improved attack chain code for rapid pipe dispenser
+ - code_imp: organized lists & global vars for rapid pipe dispenser into their own
+ respective files & improved a bunch of code
+2024-12-09:
+ FlufflesTheDog:
+ - bugfix: Dimensional shifter relics work more reliably.
+ Ghommie:
+ - bugfix: You should be once again able to fish moonfish and other fish used in
+ lizard cuisine from tiziran water turfs.
+ Melbert:
+ - rscdel: Some crowbars on Wawa, Nebula, and Birdboat are significantly less heavy
+ SmArtKar:
+ - image: Added unique sprites for Endotherm wintercoats
+ - image: Wintercoat hoods now show a bit of your hair!
+ grungussuss:
+ - rscadd: pointing now has interactions with the amount of limbs/organs you have
+ - balance: you can now point while restrained
+ - sound: pointing with your head makes a sound
+ grungussuss and Sothanforax:
+ - rscadd: hiss emote
+ - sound: hissssssing sounds
+ mc-oofert:
+ - bugfix: The Men in Grey may no longer access birdshots engineering via a certain
+ maintenance airlock
+ - bugfix: multitile airlock assemblies from a broken multitile airlock are the same
+ direction
+ mcbalaam:
+ - rscadd: Added the mail sorting unit - working with mail has never been simpler!
+ - rscadd: Added two flatpack pre-defined subtypes for the flatpacker and the mail
+ sorter.
+ timothymtorres:
+ - code_imp: Add better logging for ruins
+ tontyGH:
+ - bugfix: Pubby's whiteship no longer breaks when it tries to dock
+ - bugfix: /datum/component/PostTransfer() procs that didn't have their new_parent
+ arguments have now been fixed
+ - bugfix: This means that turning into a Domain gondola shouldn't RR people anymore
+2024-12-10:
+ MelokGleb:
+ - qol: changes Chronic Illness quirk name, description and icon to match it's dangerousness
+2024-12-11:
+ Ghommie:
+ - bugfix: Fixed feeding fish only increasing their size but not the weight.
+ - bugfix: Fixing remote/ghost alt-click functioning on aquariums
+ - bugfix: Added missing plasma tetra to freshwater fishing spots.
+ - balance: plasma tetra is now smaller and qualifies as baitfish.
+ LT3:
+ - bugfix: Map vote will no longer sometimes ignore the winning choice and pick a
+ cached, ineligible map
+ SmArtKar:
+ - bugfix: Removed rogue sand decals from Island Brawl domain walls
+ WebcomicArtist:
+ - bugfix: Durand shield now uses proper amount of power upon taking damage
+ - bugfix: Mech plasma generator now produces the correct amount of charge, previously
+ bugged to be 10% of intended.
+ carlarctg:
+ - rscadd: Adds suicides to fish. Like, a lot of suicides. Almost all of them very
+ unique. I'm too lazy to make a video, but they've been thoroughly tested.
+ grungussuss:
+ - bugfix: fixed *me emote being called when using the *help emote
+ - bugfix: you will no longer slip off your mount when traversing slippery surfaces
+ - bugfix: fixed being unable to remove bar seating holograms
+ - bugfix: fixed an error with slipping
+ - bugfix: fixed items not falling from a lattice after being deconstructed/destroyed
+2024-12-12:
+ Kocma-san:
+ - admin: moves all admin requests in admin stuff section. Now they have type Prayers
+ SmArtKar:
+ - bugfix: Made wendigo's bullet hell lag less, at cost of its visuals.
+ - bugfix: Toolboxes can now be placed onto tables/into crates
+ - bugfix: Fixed toolboxes automatically using the first item in them
+2024-12-13:
+ Majkl-J:
+ - bugfix: Fauna can no longer push necropolis gates
+ Melbert:
+ - bugfix: Words in other languages will be randomized far less often (depending
+ on how commonly they are used). This bug was 10 years old.
+ - qol: Hovering over clickable screen elements will now update your mouse cursor
+ to indicate they're clickable
+ - qol: Hovering over small wall mounts (light switches, buttons, fire alarms) will
+ now update to mouse cursor indicating you're hovering them
+ - qol: Cauterizing bleeding wounds now plays the cautery sfx.
+ - qol: Bleeding wounds will now go away the moment they're fully healed, rather
+ than a second or two later.
+ - qol: Suture / Mesh treatment is now uniform! meaning healing bruises with a suture
+ is now the same thing as healing cuts with a suture. This has very little difference
+ in practice, but it should generally result in a lot smoother experience.
+ - qol: 'Suture / Mesh usage has been reworked slightly, and now offers two modes
+ of use:'
+ - qol: LEFT CLICKING will heal in AUTO MODE, which will AUTOMATICALLY switch between
+ damaged bodyparts, prioritizing your targeted limb. You cannot change target
+ mid-heal; changing target simply changes your priority for your NEXT heal.
+ - qol: RIGHT CLICKING will heal in MANUAL MODE, which functions like it does currently
+ - allowing you to change your target before you finish your heal and giving
+ you a 1 second "assessment" step to change your target when you're done healing
+ a limb. Manual mode is 10% faster than Auto mode.
+ SmArtKar:
+ - image: Scarves have received a minor update to their sprites
+ - bugfix: Fixed moths only being able to fly if they spawn in zero gravity
+ grungussuss:
+ - bugfix: '*me emote works again'
+2024-12-14:
+ EnterTheJake:
+ - bugfix: temporary blocks such as blade heretic orbiting knives properly stop body
+ throws.
+ Ghommie:
+ - bugfix: Actually fixed alt-clicking aquariums.
+ Majkl-J:
+ - bugfix: Aloe cream no longer catches fire seconds after finishing baking
+ - bugfix: Overcharged SMESes no longer spam runtime when timers chug up
+ NecromancerAnne (code), orcacora (sprites):
+ - rscadd: Adds NT BR-38 Battle Rifles. A hybrid weapon. Find it in your local armory
+ and cargo catalogue today. (Keep away from EMPs)
+ Runi-c:
+ - balance: medical doctors can buy Reagent Dartgun from traitor uplink
+ SmArtKar:
+ - bugfix: Fixes primed stingbangs being invisible
+ SmArtKar, LemonInTheDark:
+ - rscadd: Changed how spraycans color items - "old" mode is still availible via
+ right click.
+ - refactor: Refactored how some items and effects color things so that they look
+ prettier.
+ SyncIt21:
+ - code_imp: improved code for leaning
+ distributivgesetz:
+ - code_imp: Fixed rare cases where moving an object somewhere could silently fail,
+ but still run unintended code. Report any weird issues on Github
+ grungussuss:
+ - bugfix: fixed regal rat attack logic
+ - bugfix: fixed access on birdshot engi mulebot delivery window
+ mcbalaam:
+ - qol: Now all antagonists are visible to an admin in the orbit menu!
+ norsvenska:
+ - spellcheck: The Lance and Raven shuttle airlocks are now properly labelled emergency
+ airlocks, rather than emegency airlocks.
+ - spellcheck: The radio jammer now releases disruptor waves, rather than distruptor
+ waves.
+ timothymtorres:
+ - sound: Add water sound to sinks
+2024-12-15:
+ Ghommie:
+ - bugfix: Fixed the displayed stats when examining fishing rods twice.
+ LT3:
+ - bugfix: Fixed unconstructed solar panels on Nebulastation port aft solars
+ Melbert:
+ - rscadd: Adds Syndol to the chemical kit, an addictive hallucinogen that applies
+ bonus effects when security officers, assistants, or clowns are exposed.
+ - bugfix: Metalgen works as a lockpick; igniting a crate metalgen'd into plasma
+ will properly drop its contents.
+ - code_imp: Hiding stuff in food should generally work more consistently now.
+ - bugfix: Fixes players not doing the "searching for item" do-after for items hidden
+ in food.
+ - qol: When dragging an item (like, with your mouse cursor. not physically), your
+ cursor updates when hovering humans or cyborgs to indicate you're hovering over
+ a human or cyborg.
+ Paxilmaniac:
+ - bugfix: Fixes resin sprayers not working if the target is more than one tile away
+ from you
+ necromanceranne:
+ - code_imp: Various mob attack procs are treated as unarmed attacks as a baseline
+ assumption, rather than melee attacks.
+ timothymtorres:
+ - code_imp: Improve looping sounds to allow nested and non-associative lists
diff --git a/icons/hud/radial.dmi b/icons/hud/radial.dmi
index f6e141ab6855a..5e32a89fe5d06 100644
Binary files a/icons/hud/radial.dmi and b/icons/hud/radial.dmi differ
diff --git a/icons/mob/clothing/back.dmi b/icons/mob/clothing/back.dmi
index a67830ac5e917..9fd317494ad8b 100644
Binary files a/icons/mob/clothing/back.dmi and b/icons/mob/clothing/back.dmi differ
diff --git a/icons/mob/clothing/belt_mirror.dmi b/icons/mob/clothing/belt_mirror.dmi
index 95f7bc00ae9d0..0ffdb70219cf4 100644
Binary files a/icons/mob/clothing/belt_mirror.dmi and b/icons/mob/clothing/belt_mirror.dmi differ
diff --git a/icons/mob/clothing/head/winterhood.dmi b/icons/mob/clothing/head/winterhood.dmi
index ba722a5a0f281..a173364c99454 100644
Binary files a/icons/mob/clothing/head/winterhood.dmi and b/icons/mob/clothing/head/winterhood.dmi differ
diff --git a/icons/mob/clothing/neck.dmi b/icons/mob/clothing/neck.dmi
index bd57cb6eee916..cb02e72603bbe 100644
Binary files a/icons/mob/clothing/neck.dmi and b/icons/mob/clothing/neck.dmi differ
diff --git a/icons/mob/clothing/suits/wintercoat.dmi b/icons/mob/clothing/suits/wintercoat.dmi
index 9bcfca4d6a3a1..921e3991846ff 100644
Binary files a/icons/mob/clothing/suits/wintercoat.dmi and b/icons/mob/clothing/suits/wintercoat.dmi differ
diff --git a/icons/mob/human/hair_masks.dmi b/icons/mob/human/hair_masks.dmi
index 5dbd4917a87e3..bb7b55e0cc3a4 100644
Binary files a/icons/mob/human/hair_masks.dmi and b/icons/mob/human/hair_masks.dmi differ
diff --git a/icons/mob/human/textures.dmi b/icons/mob/human/textures.dmi
index 4408c3e067281..78bf3a18e10dc 100644
Binary files a/icons/mob/human/textures.dmi and b/icons/mob/human/textures.dmi differ
diff --git a/icons/mob/inhands/equipment/toolbox_lefthand.dmi b/icons/mob/inhands/equipment/toolbox_lefthand.dmi
index e3aca82d9e839..3dbd5ea013d42 100644
Binary files a/icons/mob/inhands/equipment/toolbox_lefthand.dmi and b/icons/mob/inhands/equipment/toolbox_lefthand.dmi differ
diff --git a/icons/mob/inhands/equipment/toolbox_righthand.dmi b/icons/mob/inhands/equipment/toolbox_righthand.dmi
index a7b538a130002..13dc226fcea31 100644
Binary files a/icons/mob/inhands/equipment/toolbox_righthand.dmi and b/icons/mob/inhands/equipment/toolbox_righthand.dmi differ
diff --git a/icons/mob/inhands/weapons/guns_lefthand.dmi b/icons/mob/inhands/weapons/guns_lefthand.dmi
index 90df2a892f984..369365131c6de 100644
Binary files a/icons/mob/inhands/weapons/guns_lefthand.dmi and b/icons/mob/inhands/weapons/guns_lefthand.dmi differ
diff --git a/icons/mob/inhands/weapons/guns_righthand.dmi b/icons/mob/inhands/weapons/guns_righthand.dmi
index eebed61656aa4..fb77baa515b08 100644
Binary files a/icons/mob/inhands/weapons/guns_righthand.dmi and b/icons/mob/inhands/weapons/guns_righthand.dmi differ
diff --git a/icons/obj/clothing/head/winterhood.dmi b/icons/obj/clothing/head/winterhood.dmi
index 34e0abf39beef..591f99ec313fb 100644
Binary files a/icons/obj/clothing/head/winterhood.dmi and b/icons/obj/clothing/head/winterhood.dmi differ
diff --git a/icons/obj/clothing/neck.dmi b/icons/obj/clothing/neck.dmi
index ca90eb8a3291b..3a1bd3d2ccff3 100644
Binary files a/icons/obj/clothing/neck.dmi and b/icons/obj/clothing/neck.dmi differ
diff --git a/icons/obj/clothing/suits/wintercoat.dmi b/icons/obj/clothing/suits/wintercoat.dmi
index 377c9ef61e30e..a70b4eb6cbe98 100644
Binary files a/icons/obj/clothing/suits/wintercoat.dmi and b/icons/obj/clothing/suits/wintercoat.dmi differ
diff --git a/icons/obj/machines/engine/turbine.dmi b/icons/obj/machines/engine/turbine.dmi
index 1ae45eb2a1b1f..afd9839af1341 100644
Binary files a/icons/obj/machines/engine/turbine.dmi and b/icons/obj/machines/engine/turbine.dmi differ
diff --git a/icons/obj/machines/mailsorter.dmi b/icons/obj/machines/mailsorter.dmi
new file mode 100644
index 0000000000000..8d09e36796f95
Binary files /dev/null and b/icons/obj/machines/mailsorter.dmi differ
diff --git a/icons/obj/machines/vending.dmi b/icons/obj/machines/vending.dmi
index 319771e4e7fb5..8c39296a155f7 100644
Binary files a/icons/obj/machines/vending.dmi and b/icons/obj/machines/vending.dmi differ
diff --git a/icons/obj/storage/toolbox.dmi b/icons/obj/storage/toolbox.dmi
index 9ca99565f3174..49385d5b73cf2 100644
Binary files a/icons/obj/storage/toolbox.dmi and b/icons/obj/storage/toolbox.dmi differ
diff --git a/icons/obj/weapons/grenade.dmi b/icons/obj/weapons/grenade.dmi
index c65f6d0e9fb80..628b271d423e5 100644
Binary files a/icons/obj/weapons/grenade.dmi and b/icons/obj/weapons/grenade.dmi differ
diff --git a/icons/obj/weapons/guns/ammo.dmi b/icons/obj/weapons/guns/ammo.dmi
index 2dab0cb3d8d08..0f3b0620198ee 100644
Binary files a/icons/obj/weapons/guns/ammo.dmi and b/icons/obj/weapons/guns/ammo.dmi differ
diff --git a/icons/obj/weapons/guns/ballistic.dmi b/icons/obj/weapons/guns/ballistic.dmi
index ef61f1d24949d..6f208f4d8b1cd 100644
Binary files a/icons/obj/weapons/guns/ballistic.dmi and b/icons/obj/weapons/guns/ballistic.dmi differ
diff --git a/icons/obj/weapons/guns/projectiles.dmi b/icons/obj/weapons/guns/projectiles.dmi
index d3ecd385a7094..2776fbd4961a8 100644
Binary files a/icons/obj/weapons/guns/projectiles.dmi and b/icons/obj/weapons/guns/projectiles.dmi differ
diff --git a/icons/obj/weapons/guns/wide_guns.dmi b/icons/obj/weapons/guns/wide_guns.dmi
index 7e18f60eeb864..a193dcc53015f 100644
Binary files a/icons/obj/weapons/guns/wide_guns.dmi and b/icons/obj/weapons/guns/wide_guns.dmi differ
diff --git a/icons/turf/floors.dmi b/icons/turf/floors.dmi
index ff9d1e62e9b31..3b67fc7927e51 100644
Binary files a/icons/turf/floors.dmi and b/icons/turf/floors.dmi differ
diff --git a/modular_doppler/damage_control/code/breach_gear.dm b/modular_doppler/damage_control/code/breach_gear.dm
new file mode 100644
index 0000000000000..c8666c9913ea9
--- /dev/null
+++ b/modular_doppler/damage_control/code/breach_gear.dm
@@ -0,0 +1,200 @@
+// Yellow tank but empty, for the printer
+
+/obj/item/tank/internals/oxygen/yellow/empty
+
+/obj/item/tank/internals/oxygen/yellow/empty/populate_gas()
+ return
+
+// Breach helmet, its a hardhat but cooler
+
+/obj/item/clothing/head/utility/hardhat/welding/doppler_dc
+ name = "breach helmet"
+ desc = "A heavy-duty hardhat with a pressure seal and fireproofing for when things go very wrong. \
+ The headlamp is red to preserve eyesight in dark, damaged environments."
+ icon = 'modular_doppler/damage_control/icons/gear.dmi'
+ icon_state = "hardhat0_dc"
+ hat_type = "dc"
+ worn_icon = 'modular_doppler/damage_control/icons/mob/gear.dmi'
+ mask_overlay_icon = 'modular_doppler/damage_control/icons/mob/gear.dmi'
+ dog_fashion = null
+
+ resistance_flags = FIRE_PROOF
+ clothing_flags = STOPSPRESSUREDAMAGE | STACKABLE_HELMET_EXEMPT
+ heat_protection = HEAD
+ max_heat_protection_temperature = FIRE_HELM_MAX_TEMP_PROTECT
+ cold_protection = HEAD
+ min_cold_protection_temperature = FIRE_HELM_MIN_TEMP_PROTECT
+ light_range = 4
+ light_color = "#ee243d"
+
+/obj/item/clothing/head/utility/hardhat/welding/doppler_dc/worn_overlays(mutable_appearance/standing, isinhands, icon_file)
+ . = ..()
+ if(!isinhands)
+ . += emissive_appearance(icon_file, "hardhat_dc_emissive", src, alpha = src.alpha)
+
+// Reflective fire jacket, like the shiny type super serious firefighters have to use
+
+/obj/item/clothing/suit/utility/fire/doppler
+ name = "reflective firecoat"
+ desc = "A bulky firesuit clad in reflective material to deflect heat away from the wearer."
+ icon = 'modular_doppler/damage_control/icons/gear.dmi'
+ icon_state = "firecoat"
+ worn_icon = 'modular_doppler/damage_control/icons/mob/gear.dmi'
+ inhand_icon_state = "taperobe"
+ w_class = WEIGHT_CLASS_BULKY
+ slowdown = 0.75
+ flags_inv = NONE
+
+// Skinsuit, tight fitting jumpsuit that keeps you from exploding thanks to pressure damage
+
+/obj/item/clothing/under/rank/engineering/breach_skinsuit
+ name = "breach skinsuit"
+ desc = "A special jumpsuit that, when properly worn and adjusted, fits extremely tightly around the wearer's body. \
+ This is uncomfortable and slow to move in, but protects the wearer from exposure to vacuum."
+ icon = 'modular_doppler/damage_control/icons/gear.dmi'
+ icon_state = "skinsuit"
+ worn_icon = 'modular_doppler/damage_control/icons/mob/gear.dmi'
+ equip_sound = 'sound/items/equip/glove_equip.ogg'
+ can_adjust = FALSE
+ resistance_flags = FIRE_PROOF
+ clothing_flags = STOPSPRESSUREDAMAGE
+ cold_protection = CHEST|GROIN|LEGS|FEET|ARMS|HANDS
+ min_cold_protection_temperature = FIRE_SUIT_MIN_TEMP_PROTECT
+ slowdown = 0.5
+
+/obj/item/clothing/under/rank/engineering/breach_skinsuit/worn_overlays(mutable_appearance/standing, isinhands, icon_file)
+ . = ..()
+ if(!isinhands)
+ . += emissive_appearance(icon_file, "[icon_state]-emissive", src, alpha = src.alpha)
+
+// Gas masks but they aren't flammable (its an atmos gas mask without the extra filters)
+
+/obj/item/clothing/mask/gas/breach
+ name = "gas mask"
+ desc = "A fireproofed gas mask with a special layer on the faceplate that prevents being blinded by fire reflash. \
+ Will not protect you from more intense lighting than that, however."
+ icon = 'modular_doppler/damage_control/icons/gear.dmi'
+ icon_state = "mask"
+ worn_icon = 'modular_doppler/damage_control/icons/mob/gear.dmi'
+ worn_icon_state = "mask"
+ supported_bodyshapes = list(BODYSHAPE_HUMANOID)
+ bodyshape_icon_files = list(
+ BODYSHAPE_HUMANOID_T = 'modular_doppler/damage_control/icons/mob/gear.dmi',
+ )
+ armor_type = /datum/armor/gas_atmos
+ resistance_flags = FIRE_PROOF
+
+// Bag for storing the breach ensemble without cluttering fire lockers too bad
+
+/obj/item/storage/bag/breach_bag
+ name = "damage control ensemble bag"
+ desc = "A fire and chemical proofed bag for storing breach gear when not in use. \
+ A small label states that the bags should be inspected for contents every six months, \
+ though the last inspection signature looks much older than that."
+ icon = 'modular_doppler/damage_control/icons/gear.dmi'
+ icon_state = "breach_bag"
+ slot_flags = NONE
+ resistance_flags = FIRE_PROOF | ACID_PROOF
+
+/obj/item/storage/bag/breach_bag/Initialize(mapload)
+ . = ..()
+ atom_storage.max_specific_storage = WEIGHT_CLASS_BULKY
+ atom_storage.numerical_stacking = FALSE
+ atom_storage.max_total_storage = 200
+ atom_storage.max_slots = 6
+ atom_storage.insert_preposition = "in"
+ atom_storage.set_holdable(list(
+ /obj/item/clothing/head/utility,
+ /obj/item/clothing/suit/utility,
+ /obj/item/clothing/under/rank/engineering,
+ /obj/item/clothing/mask/gas,
+ /obj/item/clothing/shoes,
+ /obj/item/clothing/gloves,
+ ))
+
+/obj/item/storage/bag/breach_bag/PopulateContents()
+ new /obj/item/clothing/head/utility/hardhat/welding/doppler_dc(src)
+ new /obj/item/clothing/suit/utility/fire/doppler(src)
+ new /obj/item/clothing/under/rank/engineering/breach_skinsuit(src)
+ new /obj/item/clothing/mask/gas/breach(src)
+ new /obj/item/clothing/shoes/jackboots/frontier_colonist(src)
+ new /obj/item/clothing/gloves/frontier_colonist(src)
+
+// Metal H2 axe if you ever actually saw one once in 200 rounds
+
+/obj/item/fireaxe/metal_h2_axe/emergency_extraction
+ name = "emergency extraction axe"
+
+// Edits to fire lockers to include the new breach kit
+
+/obj/structure/closet/firecloset/PopulateContents()
+ new /obj/item/storage/bag/breach_bag(src)
+ new /obj/item/storage/bag/breach_bag(src)
+ new /obj/item/tank/internals/oxygen/yellow(src)
+ new /obj/item/tank/internals/oxygen/yellow(src)
+ new /obj/item/extinguisher(src)
+ new /obj/item/crowbar/large/emergency(src)
+ new /obj/item/emergency_bed(src)
+
+ if(prob(30))
+ new /obj/item/flatpacked_machine/damage_lathe(src)
+
+/obj/structure/closet/firecloset/full/PopulateContents()
+ new /obj/item/storage/bag/breach_bag(src)
+ new /obj/item/storage/bag/breach_bag(src)
+ new /obj/item/tank/internals/oxygen/yellow(src)
+ new /obj/item/tank/internals/oxygen/yellow(src)
+ new /obj/item/extinguisher(src)
+ new /obj/item/crowbar/large/emergency(src)
+ new /obj/item/emergency_bed(src)
+ new /obj/item/door_seal(src)
+ new /obj/item/door_seal(src)
+ new /obj/item/door_seal(src)
+ new /obj/item/stack/sheet/iron/ten(src)
+
+ if(prob(50))
+ new /obj/item/flatpacked_machine/damage_lathe(src)
+
+// Emergency closets get a little lovin too
+
+/obj/structure/closet/emcloset/PopulateContents()
+ var/list/possible_spawn_pools = list(
+ "air_only" = 3,
+ "stretcher" = 2,
+ "mixture" = 3,
+ "get_out_while_you_still_can" = 2,
+ )
+
+ new /obj/item/storage/toolbox/emergency(src)
+
+ switch (pick_weight(possible_spawn_pools))
+ if ("air_only")
+ new /obj/item/tank/internals/emergency_oxygen(src)
+ new /obj/item/tank/internals/emergency_oxygen(src)
+ new /obj/item/tank/internals/emergency_oxygen(src)
+ new /obj/item/clothing/mask/gas/breach(src)
+ new /obj/item/clothing/mask/gas/breach(src)
+ new /obj/item/clothing/mask/gas/breach(src)
+ if ("stretcher")
+ new /obj/item/clothing/head/utility/hardhat/welding/doppler_dc(src)
+ new /obj/item/clothing/head/utility/hardhat/welding/doppler_dc(src)
+ new /obj/item/storage/backpack/duffelbag/deforest_medkit/stocked(src)
+ new /obj/item/emergency_bed(src)
+ if ("mixture")
+ new /obj/item/clothing/head/utility/hardhat/welding/doppler_dc(src)
+ new /obj/item/clothing/head/utility/hardhat/welding/doppler_dc(src)
+ new /obj/item/tank/internals/emergency_oxygen(src)
+ new /obj/item/tank/internals/emergency_oxygen(src)
+ new /obj/item/clothing/mask/gas/breach(src)
+ new /obj/item/clothing/mask/gas/breach(src)
+ new /obj/item/storage/medkit/emergency(src)
+ new /obj/item/emergency_bed(src)
+ if ("get_out_while_you_still_can")
+ new /obj/item/clothing/head/utility/hardhat/welding/doppler_dc(src)
+ new /obj/item/clothing/head/utility/hardhat/welding/doppler_dc(src)
+ new /obj/item/tank/internals/emergency_oxygen(src)
+ new /obj/item/tank/internals/emergency_oxygen(src)
+ new /obj/item/clothing/mask/gas/breach(src)
+ new /obj/item/clothing/mask/gas/breach(src)
+ new /obj/item/emergency_bed(src)
+ new /obj/item/fireaxe/metal_h2_axe/emergency_extraction(src)
diff --git a/modular_doppler/damage_control/code/emergency_fab.dm b/modular_doppler/damage_control/code/emergency_fab.dm
new file mode 100644
index 0000000000000..b831e3b6018f5
--- /dev/null
+++ b/modular_doppler/damage_control/code/emergency_fab.dm
@@ -0,0 +1,83 @@
+/obj/machinery/rnd/production/damage_control_fab
+ name = "emergency repair lathe"
+ desc = "A small little machine with no material insertion ports and no power connectors. \
+ Able to use the small trickle of power an internal source creates to slowly create \
+ essential damage control equipment."
+ icon = 'modular_doppler/damage_control/icons/machines.dmi'
+ icon_state = "damage_fab"
+ base_icon_state = "damage_fab"
+ circuit = null
+ production_animation = "damage_fab_working"
+ light_color = LIGHT_COLOR_INTENSE_RED
+ light_power = 5
+ allowed_buildtypes = DAMAGE_FAB
+ use_power = FALSE
+ /// The item we turn into when repacked
+ var/repacked_type = /obj/item/flatpacked_machine/damage_lathe
+
+/obj/machinery/rnd/production/damage_control_fab/Initialize(mapload)
+ . = ..()
+ AddElement(/datum/element/repackable, repacked_type, 5 SECONDS)
+ stored_research = locate(/datum/techweb/admin) in SSresearch.techwebs
+ if(!mapload)
+ flick("damage_fab_deploy", src)
+
+// formerly NO_DECONSTRUCTION
+/obj/machinery/rnd/production/damage_control_fab/default_deconstruction_screwdriver(mob/user, icon_state_open, icon_state_closed, obj/item/screwdriver)
+ return NONE
+
+/obj/machinery/rnd/production/damage_control_fab/default_deconstruction_crowbar(obj/item/crowbar, ignore_panel, custom_deconstruct)
+ return NONE
+
+/obj/machinery/rnd/production/damage_control_fab/default_pry_open(obj/item/crowbar, close_after_pry, open_density, closed_density)
+ return NONE
+
+/obj/machinery/rnd/production/damage_control_fab/start_printing_visuals()
+ set_light(l_range = 1.5)
+ icon_state = "colony_lathe_working"
+ update_appearance()
+
+/obj/machinery/rnd/production/damage_control_fab/finalize_build()
+ . = ..()
+ set_light(l_range = 0)
+ icon_state = base_icon_state
+ update_appearance()
+ flick("colony_lathe_finish_print", src)
+
+/obj/machinery/rnd/production/damage_control_fab/build_efficiency()
+ return 1
+
+// We take from all nodes even unresearched ones
+/obj/machinery/rnd/production/damage_control_fab/update_designs()
+ var/previous_design_count = cached_designs.len
+
+ cached_designs.Cut()
+
+ for(var/design_id in SSresearch.techweb_designs)
+ var/datum/design/design = SSresearch.techweb_designs[design_id]
+
+ if((isnull(allowed_department_flags) || (design.departmental_flags & allowed_department_flags)) && (design.build_type & allowed_buildtypes))
+ cached_designs |= design
+
+ var/design_delta = cached_designs.len - previous_design_count
+
+ if(design_delta > 0)
+ say("Received [design_delta] new design[design_delta == 1 ? "" : "s"].")
+ playsound(src, 'sound/machines/beep/twobeep_high.ogg', 50, TRUE)
+
+ update_static_data_for_all_viewers()
+
+// Item for carrying the lathe around and building it
+
+/obj/item/flatpacked_machine/damage_lathe
+ name = "packed emergency repair lathe"
+ /// For all flatpacked machines, set the desc to the type_to_deploy followed by ::desc to reuse the type_to_deploy's description
+ desc = /obj/machinery/rnd/production/damage_control_fab::desc
+ icon = 'modular_doppler/damage_control/icons/packed_machines.dmi'
+ icon_state = "damage_lathe_packed"
+ w_class = WEIGHT_CLASS_BULKY
+ type_to_deploy = /obj/machinery/rnd/production/damage_control_fab
+ deploy_time = 4 SECONDS
+
+/obj/item/flatpacked_machine/damage_lathe/give_manufacturer_examine()
+ return
diff --git a/modular_doppler/damage_control/code/emergency_fab_designs.dm b/modular_doppler/damage_control/code/emergency_fab_designs.dm
new file mode 100644
index 0000000000000..c06d0e8fb7a51
--- /dev/null
+++ b/modular_doppler/damage_control/code/emergency_fab_designs.dm
@@ -0,0 +1,231 @@
+/datum/techweb_node/emergency_fab_designs
+ id = TECHWEB_NODE_EMERGENCY_FAB
+ display_name = "Emergency Repair Lathe Designs"
+ description = "All of the designs that work for the emergency repair lathe."
+ design_ids = list(
+ "breach_bag_doppla",
+ "breach_helmet_doppla",
+ "empty_yellow_tank_doppla",
+ "door_seal_doppla",
+ "damage_fab_plastic_wall_panel",
+ "damage_fab_medbed",
+ "damage_fab_crowbar",
+ "damage_fab_welder",
+ "damage_fab_flare",
+ "damage_fab_foam",
+ "damage_fab_oxcandle",
+ "damage_fab_radio",
+ "damage_fab_battery",
+ )
+ research_costs = list(TECHWEB_POINT_TYPE_GENERIC = 50000000000000) // God save you
+ hidden = TRUE
+ show_on_wiki = FALSE
+ starting_node = TRUE
+
+// Breach helmet
+
+/datum/design/breach_helmet_doppla
+ name = "Breach Helmet"
+ id = "breach_helmet_doppla"
+ build_type = DAMAGE_FAB
+ build_path = /obj/item/storage/bag/breach_bag
+ construction_time = 2 MINUTES
+ materials = list(
+ /datum/material/iron = SMALL_MATERIAL_AMOUNT,
+ )
+ category = list(
+ RND_CATEGORY_INITIAL,
+ RND_CATEGORY_EQUIPMENT + RND_SUBCATEGORY_EQUIPMENT_ATMOSPHERICS,
+ )
+
+// Entire filled breach bag
+
+/datum/design/breach_bag_doppla
+ name = "Damage Control Ensemble Bag"
+ id = "breach_bag_doppla"
+ build_type = DAMAGE_FAB
+ build_path = /obj/item/clothing/head/utility/hardhat/welding/doppler_dc
+ construction_time = 7 MINUTES
+ materials = list(
+ /datum/material/iron = SMALL_MATERIAL_AMOUNT,
+ )
+ category = list(
+ RND_CATEGORY_INITIAL,
+ RND_CATEGORY_EQUIPMENT + RND_SUBCATEGORY_EQUIPMENT_ATMOSPHERICS,
+ )
+
+// Entire filled breach bag
+
+/datum/design/empty_yellow_tank_doppla
+ name = "Emergency Internals Tank"
+ id = "empty_yellow_tank_doppla"
+ build_type = DAMAGE_FAB
+ build_path = /obj/item/tank/internals/emergency_oxygen/engi
+ construction_time = 1 MINUTES
+ materials = list(
+ /datum/material/iron = SMALL_MATERIAL_AMOUNT,
+ )
+ category = list(
+ RND_CATEGORY_INITIAL,
+ RND_CATEGORY_EQUIPMENT + RND_SUBCATEGORY_EQUIPMENT_ATMOSPHERICS,
+ )
+
+// Door seals
+
+/datum/design/door_seal_doppla
+ name = "Door Seal"
+ id = "door_seal_doppla"
+ build_type = DAMAGE_FAB
+ build_path = /obj/item/door_seal
+ construction_time = 2 MINUTES
+ materials = list(
+ /datum/material/iron = SMALL_MATERIAL_AMOUNT,
+ )
+ category = list(
+ RND_CATEGORY_INITIAL,
+ RND_CATEGORY_EQUIPMENT + RND_SUBCATEGORY_EQUIPMENT_ENGINEERING,
+ )
+
+// Plastic wall panels are good for sealing holes in the wall
+
+/datum/design/damage_fab_plastic_wall
+ name = "Plastic Paneling"
+ id = "damage_fab_plastic_wall_panel"
+ build_type = DAMAGE_FAB
+ materials = list(
+ /datum/material/iron = SMALL_MATERIAL_AMOUNT,
+ )
+ build_path = /obj/item/stack/sheet/plastic_wall_panel
+ category = list(
+ RND_CATEGORY_INITIAL,
+ RND_CATEGORY_EQUIPMENT + RND_SUBCATEGORY_EQUIPMENT_ENGINEERING,
+ )
+ construction_time = 10 SECONDS
+
+// Rollerbeds for saving people
+
+/datum/design/damage_fab_rollerbed
+ name = "Emergency Medical Bed"
+ id = "damage_fab_medbed"
+ build_type = DAMAGE_FAB
+ materials = list(
+ /datum/material/iron = SMALL_MATERIAL_AMOUNT,
+ )
+ build_path = /obj/item/emergency_bed
+ category = list(
+ RND_CATEGORY_INITIAL,
+ RND_CATEGORY_EQUIPMENT + RND_SUBCATEGORY_EQUIPMENT_MEDICAL,
+ )
+ construction_time = 1 MINUTES
+
+// Bar with crows in it
+
+/datum/design/damage_fab_crowbar
+ name = "Emergency Crowbar"
+ id = "damage_fab_crowbar"
+ build_type = DAMAGE_FAB
+ materials = list(
+ /datum/material/iron = SMALL_MATERIAL_AMOUNT,
+ )
+ build_path = /obj/item/crowbar/red
+ category = list(
+ RND_CATEGORY_INITIAL,
+ RND_CATEGORY_EQUIPMENT + RND_SUBCATEGORY_EQUIPMENT_ENGINEERING,
+ )
+ construction_time = 1 MINUTES
+
+// Bar with crows in it
+
+/datum/design/damage_fab_emergency_welder
+ name = "Emergency Welding Tool"
+ id = "damage_fab_welder"
+ build_type = DAMAGE_FAB
+ materials = list(
+ /datum/material/iron = SMALL_MATERIAL_AMOUNT,
+ )
+ build_path = /obj/item/weldingtool/mini
+ category = list(
+ RND_CATEGORY_INITIAL,
+ RND_CATEGORY_EQUIPMENT + RND_SUBCATEGORY_EQUIPMENT_ENGINEERING,
+ )
+ construction_time = 1 MINUTES
+
+// Flare
+
+/datum/design/damage_fab_flare
+ name = "Flare"
+ id = "damage_fab_flare"
+ build_type = DAMAGE_FAB
+ materials = list(
+ /datum/material/iron = SMALL_MATERIAL_AMOUNT,
+ )
+ build_path = /obj/item/flashlight/flare
+ category = list(
+ RND_CATEGORY_INITIAL,
+ RND_CATEGORY_EQUIPMENT + RND_SUBCATEGORY_EQUIPMENT_ENGINEERING,
+ )
+ construction_time = 30 SECONDS
+
+// Budget metal foam grenade
+
+/datum/design/damage_fab_foam
+ name = "Foam Grenade"
+ id = "damage_fab_foam"
+ build_type = DAMAGE_FAB
+ materials = list(
+ /datum/material/iron = SMALL_MATERIAL_AMOUNT,
+ )
+ build_path = /obj/item/grenade/chem_grenade/budget_smart_metal
+ category = list(
+ RND_CATEGORY_INITIAL,
+ RND_CATEGORY_EQUIPMENT + RND_SUBCATEGORY_EQUIPMENT_ENGINEERING,
+ )
+ construction_time = 2 MINUTES
+
+// Oxygen candle
+
+/datum/design/damage_fab_oxygencandle
+ name = "Oxygen Candle"
+ id = "damage_fab_oxcandle"
+ build_type = DAMAGE_FAB
+ materials = list(
+ /datum/material/iron = SMALL_MATERIAL_AMOUNT,
+ )
+ build_path = /obj/item/oxygen_candle
+ category = list(
+ RND_CATEGORY_INITIAL,
+ RND_CATEGORY_EQUIPMENT + RND_SUBCATEGORY_EQUIPMENT_ATMOSPHERICS,
+ )
+ construction_time = 2 MINUTES
+
+// Handheld radio
+
+/datum/design/damage_fab_radio
+ name = "Handheld Radio"
+ id = "damage_fab_radio"
+ build_type = DAMAGE_FAB
+ materials = list(
+ /datum/material/iron = SMALL_MATERIAL_AMOUNT,
+ )
+ build_path = /obj/item/radio/off
+ category = list(
+ RND_CATEGORY_INITIAL,
+ RND_CATEGORY_EQUIPMENT + RND_SUBCATEGORY_EQUIPMENT_TELECOMMS,
+ )
+ construction_time = 1 MINUTES
+
+// AA Battery
+
+/datum/design/damage_fab_battery
+ name = "AA Battery"
+ id = "damage_fab_battery"
+ build_type = DAMAGE_FAB
+ materials = list(
+ /datum/material/iron = SMALL_MATERIAL_AMOUNT,
+ )
+ build_path = /obj/item/stock_parts/power_store/cell/crap
+ category = list(
+ RND_CATEGORY_INITIAL,
+ RND_CATEGORY_EQUIPMENT + RND_SUBCATEGORY_EQUIPMENT_ENGINEERING,
+ )
+ construction_time = 1 MINUTES
diff --git a/modular_doppler/damage_control/icons/gear.dmi b/modular_doppler/damage_control/icons/gear.dmi
new file mode 100644
index 0000000000000..83aca04bf0e4f
Binary files /dev/null and b/modular_doppler/damage_control/icons/gear.dmi differ
diff --git a/modular_doppler/damage_control/icons/machines.dmi b/modular_doppler/damage_control/icons/machines.dmi
new file mode 100644
index 0000000000000..1249748fd156f
Binary files /dev/null and b/modular_doppler/damage_control/icons/machines.dmi differ
diff --git a/modular_doppler/damage_control/icons/mob/gear.dmi b/modular_doppler/damage_control/icons/mob/gear.dmi
new file mode 100644
index 0000000000000..9a26c23d4d788
Binary files /dev/null and b/modular_doppler/damage_control/icons/mob/gear.dmi differ
diff --git a/modular_doppler/damage_control/icons/packed_machines.dmi b/modular_doppler/damage_control/icons/packed_machines.dmi
new file mode 100644
index 0000000000000..caf5689163173
Binary files /dev/null and b/modular_doppler/damage_control/icons/packed_machines.dmi differ
diff --git a/modular_doppler/deforest_medical_items/code/cargo_packs.dm b/modular_doppler/deforest_medical_items/code/cargo_packs.dm
index 5dced06ad9d9e..5904f201097f5 100644
--- a/modular_doppler/deforest_medical_items/code/cargo_packs.dm
+++ b/modular_doppler/deforest_medical_items/code/cargo_packs.dm
@@ -29,34 +29,15 @@
/obj/item/storage/medkit/combat_surgeon/stocked = 3,
)
-/datum/supply_pack/medical/kit_technician
- name = "Heavy Duty Medical Kit Crate - Technician"
- crate_name = "technician kit crate"
- desc = "Contains a pink medical technician kit."
- access = ACCESS_MEDICAL
- cost = CARGO_CRATE_VALUE * 5.5
- contains = list(
- /obj/item/storage/backpack/duffelbag/deforest_paramedic/stocked,
- )
-
-/datum/supply_pack/medical/kit_surgical
- name = "Heavy Duty Medical Kit Crate - Surgical"
- crate_name = "surgical kit crate"
- desc = "Contains a grey first responder surgical kit."
- access = ACCESS_MEDICAL
- cost = CARGO_CRATE_VALUE * 5
- contains = list(
- /obj/item/storage/backpack/duffelbag/deforest_surgical/stocked,
- )
-
/datum/supply_pack/medical/kit_medical
- name = "Heavy Duty Medical Kit Crate - Medical"
+ name = "Heavy Duty Medical Kit Crate"
crate_name = "medical kit crate"
- desc = "Contains an orange satchel medical kit."
+ desc = "Contains two heavy-duty medical kits for emergencies."
access = ACCESS_MEDICAL
- cost = CARGO_CRATE_VALUE * 4.5
+ cost = CARGO_CRATE_VALUE * 11
contains = list(
/obj/item/storage/backpack/duffelbag/deforest_medkit/stocked,
+ /obj/item/storage/backpack/duffelbag/deforest_surgical/stocked,
)
/datum/supply_pack/medical/deforest_vendor_refill
diff --git a/modular_doppler/deforest_medical_items/code/healing_stack_items.dm b/modular_doppler/deforest_medical_items/code/healing_stack_items.dm
index 245ba6699f93a..9c8cf1f35ca58 100644
--- a/modular_doppler/deforest_medical_items/code/healing_stack_items.dm
+++ b/modular_doppler/deforest_medical_items/code/healing_stack_items.dm
@@ -33,7 +33,7 @@
var/treatment_sound = 'sound/items/duct_tape/duct_tape_rip.ogg'
// This is only relevant for the types of wounds defined, we can't work if there are none
-/obj/item/stack/medical/wound_recovery/try_heal(mob/living/patient, mob/user, silent, looping)
+/obj/item/stack/medical/wound_recovery/try_heal(mob/living/patient, mob/user, silent, looping, auto_change_zone = FALSE)
if(patient.has_status_effect(/datum/status_effect/vulnerable_to_damage))
patient.balloon_alert(user, "still recovering from last use!")
diff --git a/modular_doppler/deforest_medical_items/code/illegal_injectors.dm b/modular_doppler/deforest_medical_items/code/illegal_injectors.dm
index 7c3059f5d976e..59b91db488b64 100644
--- a/modular_doppler/deforest_medical_items/code/illegal_injectors.dm
+++ b/modular_doppler/deforest_medical_items/code/illegal_injectors.dm
@@ -1,18 +1,3 @@
-// Twitch, because having sandevistans be implants is for losers, just inject it!
-/obj/item/reagent_containers/hypospray/medipen/deforest/twitch
- name = "TWitch sensory stimulant injector"
- desc = "A Deforest branded autoinjector, loaded with 'TWitch' among other reagents. This drug is known to make \
- those who take it 'see faster', whatever that means."
- base_icon_state = "twitch"
- icon_state = "twitch"
- list_reagents = list(
- /datum/reagent/drug/twitch = 10,
- /datum/reagent/drug/maint/tar = 5,
- /datum/reagent/medicine/silibinin = 5,
- /datum/reagent/toxin/leadacetate = 5,
- )
- custom_price = PAYCHECK_COMMAND * 3.5
-
// Demoneye, for when you feel the need to become "fucking invincible"
/obj/item/reagent_containers/hypospray/medipen/deforest/demoneye
name = "DemonEye steroid injector"
@@ -54,10 +39,11 @@
/datum/reagent/medicine/c2/penthrite = 5,
/datum/reagent/medicine/polypyr = 5,
/datum/reagent/medicine/silibinin = 5,
- /datum/reagent/medicine/omnizine = 5,
+ /datum/reagent/medicine/omnizine = 10,
/datum/reagent/inverse/healing/tirimol = 5,
)
custom_price = PAYCHECK_COMMAND * 2.5
+ volume = 30
// Combat stimulant that makes you immune to slowdowns for a bit
/obj/item/reagent_containers/hypospray/medipen/deforest/synalvipitol
diff --git a/modular_doppler/deforest_medical_items/code/injectors.dm b/modular_doppler/deforest_medical_items/code/injectors.dm
index 02558b52bb102..27ab2969f9d45 100644
--- a/modular_doppler/deforest_medical_items/code/injectors.dm
+++ b/modular_doppler/deforest_medical_items/code/injectors.dm
@@ -77,7 +77,7 @@
/datum/reagent/medicine/synaptizine = 5,
/datum/reagent/medicine/inaprovaline = 5,
/datum/reagent/determination = 10,
- /datum/reagent/toxin/histamine = 5,
+ /datum/reagent/toxin/lipolicide = 5,
)
// Morpital, heals a small amount of damage and kills pain for a bit
@@ -87,9 +87,9 @@
base_icon_state = "morpital"
icon_state = "morpital"
list_reagents = list(
- /datum/reagent/medicine/morphine = 5,
- /datum/reagent/medicine/omnizine/protozine = 15,
- /datum/reagent/toxin/staminatoxin = 5,
+ /datum/reagent/medicine/mine_salve = 5,
+ /datum/reagent/medicine/omnizine = 15,
+ /datum/reagent/toxin/lipolicide = 5,
)
// Lipital, heals more damage than morpital but doesnt work much at higher damages
@@ -102,7 +102,7 @@
list_reagents = list(
/datum/reagent/medicine/lidocaine = 5,
/datum/reagent/medicine/omnizine = 5,
- /datum/reagent/medicine/c2/probital = 10,
+ /datum/reagent/medicine/c2/libital = 10,
)
// Anti-poisoning injector, with a little bit of radiation healing as a treat
@@ -115,7 +115,7 @@
list_reagents = list(
/datum/reagent/medicine/c2/multiver = 10,
/datum/reagent/medicine/potass_iodide = 10,
- /datum/reagent/nitrous_oxide = 5,
+ /datum/reagent/toxin/lipolicide = 5,
)
// Epinephrine and helps a little bit against stuns and stamina damage
@@ -141,7 +141,7 @@
/datum/reagent/medicine/atropine = 10,
/datum/reagent/medicine/coagulant/fabricated = 5,
/datum/reagent/medicine/salbutamol = 5,
- /datum/reagent/toxin/staminatoxin = 5,
+ /datum/reagent/toxin/lipolicide = 5,
)
// Coagulant, really not a whole lot more
@@ -180,7 +180,7 @@
/datum/reagent/medicine/mine_salve = 5,
/datum/reagent/medicine/leporazine = 5,
/datum/reagent/medicine/c2/lenturi = 10,
- /datum/reagent/toxin/staminatoxin = 5,
+ /datum/reagent/toxin/lipolicide = 5,
)
// Stabilizes a lot of stats like drowsiness, sanity, dizziness, so on
diff --git a/modular_doppler/deforest_medical_items/code/medstation.dm b/modular_doppler/deforest_medical_items/code/medstation.dm
deleted file mode 100644
index 019cc57320d9e..0000000000000
--- a/modular_doppler/deforest_medical_items/code/medstation.dm
+++ /dev/null
@@ -1,55 +0,0 @@
-/obj/machinery/biogenerator/medstation
- name = "wall med-station"
- desc = "An advanced machine seen in frontier outposts and colonies capable of turning organic plant matter into \
- various emergency medical supplies and injectors. You can find one of these in the medical sections of just about \
- any frontier installation."
- icon = 'modular_doppler/deforest_medical_items/icons/medstation.dmi'
- circuit = null
- anchored = TRUE
- density = FALSE
- efficiency = 1
- productivity = 1
- show_categories = list(
- RND_CATEGORY_DEFOREST_MEDICAL,
- RND_CATEGORY_DEFOREST_BLOOD,
- )
- /// The item we turn into when repacked
- var/repacked_type = /obj/item/wallframe/frontier_medstation
-
-MAPPING_DIRECTIONAL_HELPERS(/obj/machinery/biogenerator/medstation, 29)
-
-/obj/machinery/biogenerator/medstation/RefreshParts()
- . = ..()
- efficiency = 1
- productivity = 1
-
-/obj/machinery/biogenerator/medstation/default_unfasten_wrench(mob/user, obj/item/wrench/tool, time)
- user.balloon_alert(user, "deconstructing...")
- tool.play_tool_sound(src)
- if(tool.use_tool(src, user, 1 SECONDS))
- playsound(loc, 'sound/items/deconstruct.ogg', 50, TRUE)
- deconstruct(TRUE)
- return
-
-/obj/machinery/biogenerator/medstation/on_deconstruction(disassembled)
- if(disassembled)
- new repacked_type(drop_location())
-
-/obj/machinery/biogenerator/medstation/default_deconstruction_crowbar()
- return
-
-// Deployable item for cargo for the medstation
-
-/obj/item/wallframe/frontier_medstation
- name = "unmounted wall med-station"
- desc = "The innovative technology of a biogenerator to print medical supplies, but able to be mounted neatly on a wall out of the way."
- icon = 'modular_doppler/deforest_medical_items/icons/medstation.dmi'
- icon_state = "biogenerator_parts"
- w_class = WEIGHT_CLASS_NORMAL
- result_path = /obj/machinery/biogenerator/medstation
- pixel_shift = 29
- custom_materials = list(
- /datum/material/iron = SHEET_MATERIAL_AMOUNT * 5,
- /datum/material/silver = SHEET_MATERIAL_AMOUNT * 3,
- /datum/material/gold = SHEET_MATERIAL_AMOUNT,
- )
diff --git a/modular_doppler/deforest_medical_items/code/medstation_designs/blood.dm b/modular_doppler/deforest_medical_items/code/medstation_designs/blood.dm
deleted file mode 100644
index 0d523d14f4fa7..0000000000000
--- a/modular_doppler/deforest_medical_items/code/medstation_designs/blood.dm
+++ /dev/null
@@ -1,131 +0,0 @@
-/datum/design/organic_bloodbag_aplus
- name = "A+ Blood Pack"
- id = "organic_bloodbag_aplus"
- build_type = BIOGENERATOR
- materials = list(/datum/material/biomass = 100)
- build_path = /obj/item/reagent_containers/blood/a_plus
- category = list(
- RND_CATEGORY_INITIAL,
- RND_CATEGORY_DEFOREST_BLOOD,
- )
-
-/datum/design/organic_bloodbag_aminus
- name = "A- Blood Pack"
- id = "organic_bloodbag_aminus"
- build_type = BIOGENERATOR
- materials = list(/datum/material/biomass = 100)
- build_path = /obj/item/reagent_containers/blood/a_minus
- category = list(
- RND_CATEGORY_INITIAL,
- RND_CATEGORY_DEFOREST_BLOOD,
- )
-
-/datum/design/organic_bloodbag_bplus
- name = "B+ Blood Pack"
- id = "organic_bloodbag_bplus"
- build_type = BIOGENERATOR
- materials = list(/datum/material/biomass = 100)
- build_path = /obj/item/reagent_containers/blood/b_plus
- category = list(
- RND_CATEGORY_INITIAL,
- RND_CATEGORY_DEFOREST_BLOOD,
- )
-
-/datum/design/organic_bloodbag_bminus
- name = "B- Blood Pack"
- id = "organic_bloodbag_bminus"
- build_type = BIOGENERATOR
- materials = list(/datum/material/biomass = 100)
- build_path = /obj/item/reagent_containers/blood/b_minus
- category = list(
- RND_CATEGORY_INITIAL,
- RND_CATEGORY_DEFOREST_BLOOD,
- )
-
-/datum/design/organic_bloodbag_oplus
- name = "O+ Blood Pack"
- id = "organic_bloodbag_oplus"
- build_type = BIOGENERATOR
- materials = list(/datum/material/biomass = 100)
- build_path = /obj/item/reagent_containers/blood/o_plus
- category = list(
- RND_CATEGORY_INITIAL,
- RND_CATEGORY_DEFOREST_BLOOD,
- )
-
-/datum/design/organic_bloodbag_ominus
- name = "O- Blood Pack"
- id = "organic_bloodbag_ominus"
- build_type = BIOGENERATOR
- materials = list(/datum/material/biomass = 150)
- build_path = /obj/item/reagent_containers/blood/o_minus
- category = list(
- RND_CATEGORY_INITIAL,
- RND_CATEGORY_DEFOREST_BLOOD,
- )
-
-/datum/design/organic_bloodbag_lizard
- name = "L Blood Pack"
- id = "organic_bloodbag_lizard"
- build_type = BIOGENERATOR
- materials = list(/datum/material/biomass = 100)
- build_path = /obj/item/reagent_containers/blood/lizard
- category = list(
- RND_CATEGORY_INITIAL,
- RND_CATEGORY_DEFOREST_BLOOD,
- )
-
-/datum/design/organic_bloodbag_ethereal
- name = "LE Blood Pack"
- id = "organic_bloodbag_ethereal"
- build_type = BIOGENERATOR
- materials = list(/datum/material/biomass = 100)
- build_path = /obj/item/reagent_containers/blood/ethereal
- category = list(
- RND_CATEGORY_INITIAL,
- RND_CATEGORY_DEFOREST_BLOOD,
- )
-
-/datum/design/organic_bloodbag_plant
- name = "H2O Blood Pack"
- id = "organic_bloodbag_plant"
- build_type = BIOGENERATOR
- materials = list(/datum/material/biomass = 50)
- build_path = /obj/item/reagent_containers/blood/podperson
- category = list(
- RND_CATEGORY_INITIAL,
- RND_CATEGORY_DEFOREST_BLOOD,
- )
-
-/datum/design/organic_bloodbag_slimeperson
- name = "TOX Blood Pack"
- id = "organic_bloodbag_slimeperson"
- build_type = BIOGENERATOR
- materials = list(/datum/material/biomass = 100)
- build_path = /obj/item/reagent_containers/blood/toxin
- category = list(
- RND_CATEGORY_INITIAL,
- RND_CATEGORY_DEFOREST_BLOOD,
- )
-
-/datum/design/organic_bloodbag_robot
- name = "R Blood Pack"
- id = "organic_bloodbag_robot"
- build_type = BIOGENERATOR
- materials = list(/datum/material/biomass = 100)
- build_path = /obj/item/reagent_containers/blood/robot
- category = list(
- RND_CATEGORY_INITIAL,
- RND_CATEGORY_DEFOREST_BLOOD,
- )
-
-/datum/design/organic_bloodbag_bug
- name = "I Blood Pack"
- id = "organic_bloodbag_bug"
- build_type = BIOGENERATOR
- materials = list(/datum/material/biomass = 100)
- build_path = /obj/item/reagent_containers/blood/bug
- category = list(
- RND_CATEGORY_INITIAL,
- RND_CATEGORY_DEFOREST_BLOOD,
- )
diff --git a/modular_doppler/deforest_medical_items/code/medstation_designs/medical.dm b/modular_doppler/deforest_medical_items/code/medstation_designs/medical.dm
deleted file mode 100644
index 7eb8106e3a4e4..0000000000000
--- a/modular_doppler/deforest_medical_items/code/medstation_designs/medical.dm
+++ /dev/null
@@ -1,98 +0,0 @@
-/datum/design/organic_printer_balm
- name = "Red Sun Balm"
- id = "organic_sun_balm"
- build_type = BIOGENERATOR
- materials = list(/datum/material/biomass = 50)
- build_path = /obj/item/stack/medical/ointment/red_sun
- category = list(
- RND_CATEGORY_INITIAL,
- RND_CATEGORY_DEFOREST_MEDICAL,
- )
-
-/datum/design/organic_printer_gauze
- name = "Sealed Aseptic Guaze"
- id = "organic_gauze"
- build_type = BIOGENERATOR
- materials = list(/datum/material/biomass = 50)
- build_path = /obj/item/stack/medical/gauze/sterilized
- category = list(
- RND_CATEGORY_INITIAL,
- RND_CATEGORY_DEFOREST_MEDICAL,
- )
-
-/datum/design/organic_printer_coagulant_f
- name = "Coagulant-F Packet"
- id = "organic_coagulant_pack"
- build_type = BIOGENERATOR
- materials = list(/datum/material/biomass = 50)
- build_path = /obj/item/stack/medical/suture/coagulant
- category = list(
- RND_CATEGORY_INITIAL,
- RND_CATEGORY_DEFOREST_MEDICAL,
- )
-
-/datum/design/organic_printer_ointment
- name = "Ointment"
- id = "organic_ointment"
- build_type = BIOGENERATOR
- materials = list(/datum/material/biomass = 25)
- build_path = /obj/item/stack/medical/ointment
- category = list(
- RND_CATEGORY_INITIAL,
- RND_CATEGORY_DEFOREST_MEDICAL,
- )
-
-/datum/design/organic_printer_bruise_pack
- name = "Bruise Packs"
- id = "organic_bruise_packs"
- build_type = BIOGENERATOR
- materials = list(/datum/material/biomass = 25)
- build_path = /obj/item/stack/medical/bruise_pack
- category = list(
- RND_CATEGORY_INITIAL,
- RND_CATEGORY_DEFOREST_MEDICAL,
- )
-
-/datum/design/organic_printer_amollin_pill
- name = "Amollin Painkiller"
- id = "organic_printer_amollin_pill"
- build_type = BIOGENERATOR
- materials = list(/datum/material/biomass = 25)
- build_path = /obj/item/reagent_containers/pill/amollin
- category = list(
- RND_CATEGORY_INITIAL,
- RND_CATEGORY_DEFOREST_MEDICAL,
- )
-
-/datum/design/organic_printer_bandaid
- name = "First Aid Bandage"
- id = "organic_bandaid"
- build_type = BIOGENERATOR
- materials = list(/datum/material/biomass = 100)
- build_path = /obj/item/stack/medical/bandage
- category = list(
- RND_CATEGORY_INITIAL,
- RND_CATEGORY_DEFOREST_MEDICAL,
- )
-
-/datum/design/organic_printer_bone_gel
- name = "Bone Gel"
- id = "organic_bone_gel"
- build_type = BIOGENERATOR
- materials = list(/datum/material/biomass = 100)
- build_path = /obj/item/stack/medical/bone_gel
- category = list(
- RND_CATEGORY_INITIAL,
- RND_CATEGORY_DEFOREST_MEDICAL,
- )
-
-/datum/design/organic_printer_surgical_tape
- name = "Surgical Tape"
- id = "organic_surgical_tape"
- build_type = BIOGENERATOR
- materials = list(/datum/material/biomass = 100)
- build_path = /obj/item/stack/sticky_tape/surgical
- category = list(
- RND_CATEGORY_INITIAL,
- RND_CATEGORY_DEFOREST_MEDICAL,
- )
diff --git a/modular_doppler/deforest_medical_items/code/storage_items.dm b/modular_doppler/deforest_medical_items/code/storage_items.dm
index 4380b66587921..bff050345dea5 100644
--- a/modular_doppler/deforest_medical_items/code/storage_items.dm
+++ b/modular_doppler/deforest_medical_items/code/storage_items.dm
@@ -393,104 +393,3 @@
/obj/item/bodybag,
/obj/item/storage/hypospraykit,
))
-
-// Midrange bag for paramedics, hypospray and more flexible item wise than surgical, but restricted to small items only
-/obj/item/storage/backpack/duffelbag/deforest_paramedic
- name = "medical technician kit"
- desc = "Compared to its sibling the first responder surgical kit, this variant is equipped with a hypospray hit for roving paramedics. Featuring rapid access pockets that are lightweight, it can however only hold smaller items."
- icon = 'modular_doppler/deforest_medical_items/icons/storage.dmi'
- icon_state = "technician"
- lefthand_file = 'modular_doppler/deforest_medical_items/icons/inhands/cases_lefthand.dmi'
- righthand_file = 'modular_doppler/deforest_medical_items/icons/inhands/cases_righthand.dmi'
- inhand_icon_state = "technician"
- worn_icon = 'modular_doppler/deforest_medical_items/icons/worn/worn.dmi'
-// worn_icon_teshari = 'modular_doppler/deforest_medical_items/icons/worn/worn_teshari.dmi'
- equip_sound = 'sound/items/equip/jumpsuit_equip.ogg'
- pickup_sound = 'sound/items/handling/cloth_pickup.ogg'
- drop_sound = 'sound/items/handling/cloth_drop.ogg'
- slot_flags = ITEM_SLOT_BACK | ITEM_SLOT_BELT
- storage_type = /datum/storage/duffel/deforest_paramedic
- zip_slowdown = 0.3 // Midrange between the other two bags
- unzip_duration = 1.2 SECONDS
-
-/obj/item/storage/backpack/duffelbag/deforest_paramedic/stocked
-
-/obj/item/storage/backpack/duffelbag/deforest_paramedic/stocked/PopulateContents()
- var/static/items_inside = list(
- /obj/item/scalpel = 1,
- /obj/item/hemostat = 1,
- /obj/item/retractor = 1,
- /obj/item/circular_saw = 1,
- /obj/item/bonesetter = 1,
- /obj/item/cautery = 1,
- /obj/item/surgical_drapes = 1,
- /obj/item/stack/medical/bone_gel = 1,
- /obj/item/stack/medical/wound_recovery = 1,
- /obj/item/stack/medical/wound_recovery/rapid_coagulant = 1,
- /obj/item/stack/medical/mesh/advanced = 2,
- /obj/item/stack/medical/suture/medicated = 2,
- /obj/item/stack/medical/gauze/sterilized = 1,
- /obj/item/storage/pill_bottle/painkiller = 1,
- /obj/item/hypospray/mkii/piercing/atropine = 1,
- /obj/item/reagent_containers/cup/hypovial/small/libital = 1,
- /obj/item/reagent_containers/cup/hypovial/small/lenturi = 1,
- /obj/item/reagent_containers/cup/hypovial/small/seiver = 1,
- /obj/item/healthanalyzer = 1,
- )
- generate_items_inside(items_inside,src)
-
-/datum/storage/duffel/deforest_paramedic
- max_specific_storage = WEIGHT_CLASS_SMALL
- max_total_storage = 21 * WEIGHT_CLASS_SMALL
- max_slots = 21
-
-/datum/storage/duffel/deforest_paramedic/New()
- . = ..()
-
- can_hold = typecacheof(list(
- /obj/item/bonesetter,
- /obj/item/cautery,
- /obj/item/circular_saw,
- /obj/item/clothing/neck/stethoscope,
- /obj/item/clothing/mask/breath,
- /obj/item/clothing/mask/muzzle,
- /obj/item/clothing/mask/surgical,
- /obj/item/clothing/suit/toggle/labcoat/hospitalgown,
- /obj/item/dnainjector,
- /obj/item/extinguisher/mini,
- /obj/item/flashlight/pen,
- /obj/item/geiger_counter,
- /obj/item/healthanalyzer,
- /obj/item/hemostat,
- /obj/item/holosign_creator/medical,
- /obj/item/hypospray,
- /obj/item/implant,
- /obj/item/implantcase,
- /obj/item/implanter,
- /obj/item/lazarus_injector,
- /obj/item/lighter,
- /obj/item/pinpointer/crew,
- /obj/item/reagent_containers/blood,
- /obj/item/reagent_containers/dropper,
- /obj/item/reagent_containers/cup/beaker,
- /obj/item/reagent_containers/cup/bottle,
- /obj/item/reagent_containers/cup/hypovial,
- /obj/item/reagent_containers/cup/tube,
- /obj/item/reagent_containers/hypospray,
- /obj/item/reagent_containers/medigel,
- /obj/item/reagent_containers/pill,
- /obj/item/reagent_containers/spray,
- /obj/item/reagent_containers/syringe,
- /obj/item/retractor,
- /obj/item/scalpel,
- /obj/item/surgical_drapes,
- /obj/item/stack/medical,
- /obj/item/stack/sticky_tape,
- /obj/item/sensor_device,
- /obj/item/storage/fancy/cigarettes,
- /obj/item/storage/pill_bottle,
- /obj/item/tank/internals/emergency_oxygen,
- /obj/item/storage/box/bandages,
- /obj/item/bodybag,
- /obj/item/storage/hypospraykit,
- ))
diff --git a/modular_doppler/deforest_medical_items/code/treatment_zone_projector.dm b/modular_doppler/deforest_medical_items/code/treatment_zone_projector.dm
index 78a9a0dd7dc7b..0ecdefeb3f81e 100644
--- a/modular_doppler/deforest_medical_items/code/treatment_zone_projector.dm
+++ b/modular_doppler/deforest_medical_items/code/treatment_zone_projector.dm
@@ -5,7 +5,7 @@
desc = "A massive glowing holosign warning you to keep out of it, there's probably some important stuff happening in there!"
icon = 'modular_doppler/deforest_medical_items/icons/telegraph_96x96.dmi'
icon_state = "treatment_zone"
- layer = BELOW_OBJ_LAYER
+ layer = BOT_PATH_LAYER
pixel_x = -32
pixel_y = -32
use_vis_overlay = FALSE
diff --git a/modular_doppler/deforest_medical_items/icons/inhands/cases_lefthand.dmi b/modular_doppler/deforest_medical_items/icons/inhands/cases_lefthand.dmi
index 61e9fb5f7501c..c53eab3ba9d76 100644
Binary files a/modular_doppler/deforest_medical_items/icons/inhands/cases_lefthand.dmi and b/modular_doppler/deforest_medical_items/icons/inhands/cases_lefthand.dmi differ
diff --git a/modular_doppler/deforest_medical_items/icons/inhands/cases_righthand.dmi b/modular_doppler/deforest_medical_items/icons/inhands/cases_righthand.dmi
index b09d02d258a6c..c5a2099e96b20 100644
Binary files a/modular_doppler/deforest_medical_items/icons/inhands/cases_righthand.dmi and b/modular_doppler/deforest_medical_items/icons/inhands/cases_righthand.dmi differ
diff --git a/modular_doppler/deforest_medical_items/icons/injectors.dmi b/modular_doppler/deforest_medical_items/icons/injectors.dmi
index 62db86d583b21..50f4f2c4db8a4 100644
Binary files a/modular_doppler/deforest_medical_items/icons/injectors.dmi and b/modular_doppler/deforest_medical_items/icons/injectors.dmi differ
diff --git a/modular_doppler/deforest_medical_items/icons/medstation.dmi b/modular_doppler/deforest_medical_items/icons/medstation.dmi
deleted file mode 100644
index 2de94bc420a8a..0000000000000
Binary files a/modular_doppler/deforest_medical_items/icons/medstation.dmi and /dev/null differ
diff --git a/modular_doppler/deforest_medical_items/icons/storage.dmi b/modular_doppler/deforest_medical_items/icons/storage.dmi
index 513aed6e2a1bd..bd4c2f7b0dd0a 100644
Binary files a/modular_doppler/deforest_medical_items/icons/storage.dmi and b/modular_doppler/deforest_medical_items/icons/storage.dmi differ
diff --git a/modular_doppler/deforest_medical_items/icons/telegraph_96x96.dmi b/modular_doppler/deforest_medical_items/icons/telegraph_96x96.dmi
index 8596bce378d80..5c99df6db6e34 100644
Binary files a/modular_doppler/deforest_medical_items/icons/telegraph_96x96.dmi and b/modular_doppler/deforest_medical_items/icons/telegraph_96x96.dmi differ
diff --git a/modular_doppler/deforest_medical_items/icons/worn/worn.dmi b/modular_doppler/deforest_medical_items/icons/worn/worn.dmi
index 942715fe56a7c..bf394833ef356 100644
Binary files a/modular_doppler/deforest_medical_items/icons/worn/worn.dmi and b/modular_doppler/deforest_medical_items/icons/worn/worn.dmi differ
diff --git a/modular_doppler/deforest_medical_items/icons/worn/worn_teshari.dmi b/modular_doppler/deforest_medical_items/icons/worn/worn_teshari.dmi
index 276804f908b30..ea7249f36dc4d 100644
Binary files a/modular_doppler/deforest_medical_items/icons/worn/worn_teshari.dmi and b/modular_doppler/deforest_medical_items/icons/worn/worn_teshari.dmi differ
diff --git a/modular_doppler/epic_loot/code/random_spawners_real.dm b/modular_doppler/epic_loot/code/random_spawners_real.dm
index eab701f4a1391..a10e736d3396c 100644
--- a/modular_doppler/epic_loot/code/random_spawners_real.dm
+++ b/modular_doppler/epic_loot/code/random_spawners_real.dm
@@ -473,7 +473,6 @@
/obj/item/flatpacked_machine/wind_turbine = 2,
/obj/item/folded_navigation_gigabeacon = 1,
/obj/item/wallframe/cell_charger_multi = 2,
- /obj/item/wallframe/frontier_medstation = 1,
/obj/item/wallframe/wall_heater = 2,
/obj/item/wallframe/digital_clock = 1,
// Other things
diff --git a/modular_doppler/hearthkin/primitive_genemod/code/map_items.dm b/modular_doppler/hearthkin/primitive_genemod/code/map_items.dm
index de68e55406d6b..77e1639df6214 100644
--- a/modular_doppler/hearthkin/primitive_genemod/code/map_items.dm
+++ b/modular_doppler/hearthkin/primitive_genemod/code/map_items.dm
@@ -21,7 +21,7 @@
// Hotspring water with icebox air
/turf/open/water/hot_spring/icemoon
- initial_gas_mix = "ICEMOON_ATMOS"
+ initial_gas_mix = ICEMOON_DEFAULT_ATMOS
// The area
diff --git a/modular_doppler/kahraman_equipment/code/clothing/clothing.dm b/modular_doppler/kahraman_equipment/code/clothing/clothing.dm
index 59d461f9877a1..9a3002f51f5f7 100644
--- a/modular_doppler/kahraman_equipment/code/clothing/clothing.dm
+++ b/modular_doppler/kahraman_equipment/code/clothing/clothing.dm
@@ -34,7 +34,7 @@
BODYSHAPE_DIGITIGRADE_T = 'modular_doppler/kahraman_equipment/icons/clothes/clothing_worn_digi.dmi')
worn_icon_state = "boots"
armor_type = /datum/armor/colonist_clothing
- resistance_flags = NONE
+ resistance_flags = FIRE_PROOF
/obj/item/clothing/shoes/jackboots/frontier_colonist/Initialize(mapload)
. = ..()
@@ -178,7 +178,7 @@
min_cold_protection_temperature = GLOVES_MIN_TEMP_PROTECT
heat_protection = HANDS
max_heat_protection_temperature = GLOVES_MAX_TEMP_PROTECT
- resistance_flags = NONE
+ resistance_flags = FIRE_PROOF
clothing_traits = list(TRAIT_QUICK_CARRY)
/obj/item/clothing/gloves/frontier_colonist/Initialize(mapload)
diff --git a/modular_doppler/kahraman_equipment/code/organic_printer_designs/equipment.dm b/modular_doppler/kahraman_equipment/code/organic_printer_designs/equipment.dm
index 0ead8a75f6416..cc019598dc927 100644
--- a/modular_doppler/kahraman_equipment/code/organic_printer_designs/equipment.dm
+++ b/modular_doppler/kahraman_equipment/code/organic_printer_designs/equipment.dm
@@ -22,17 +22,6 @@
RND_CATEGORY_AKHTER_EQUIPMENT,
)
-/datum/design/frontier_medtech_belt
- name = "Medical Technician Kit"
- id = "frontier_medtech_belt"
- build_type = BIOGENERATOR
- materials = list(/datum/material/biomass = 200)
- build_path = /obj/item/storage/backpack/duffelbag/deforest_paramedic
- category = list(
- RND_CATEGORY_INITIAL,
- RND_CATEGORY_AKHTER_EQUIPMENT,
- )
-
/datum/design/frontier_medkit
name = "Frontier Medical Kit"
id = "frontier_medkit"
diff --git a/modular_doppler/loadout_categories/categories/belts.dm b/modular_doppler/loadout_categories/categories/belts.dm
index 3f67ccafa4c24..38694c991b551 100644
--- a/modular_doppler/loadout_categories/categories/belts.dm
+++ b/modular_doppler/loadout_categories/categories/belts.dm
@@ -129,10 +129,6 @@
name = "Frontier Med-kit"
item_path = /obj/item/storage/medkit/frontier/stocked
-/datum/loadout_item/belts/deforest_paramed
- name = "Satchel Tech-kit"
- item_path = /obj/item/storage/backpack/duffelbag/deforest_paramedic
-
/datum/loadout_item/belts/deforest_surgical
name = "Firest Responder Med-kit"
item_path = /obj/item/storage/backpack/duffelbag/deforest_surgical
diff --git a/modular_doppler/modular_cosmetics/code/hats/doppler_command_hats.dm b/modular_doppler/modular_cosmetics/code/hats/doppler_command_hats.dm
index 03dae2a276706..58a142223e279 100644
--- a/modular_doppler/modular_cosmetics/code/hats/doppler_command_hats.dm
+++ b/modular_doppler/modular_cosmetics/code/hats/doppler_command_hats.dm
@@ -8,12 +8,24 @@
name = "generic command hardhat"
desc = "A heavy-duty hardhat for protecting the heads of the heads when everything starts to go wrong."
icon = 'modular_doppler/modular_cosmetics/icons/obj/head/doppler_command_hardhats.dmi'
- worn_icon = 'modular_doppler/modular_cosmetics/icons/mob/head/doppler_command_hardhats.dmi'
- resistance_flags = FIRE_PROOF | ACID_PROOF
icon_state = null
hat_type = null
+ worn_icon = 'modular_doppler/modular_cosmetics/icons/mob/head/doppler_command_hardhats.dmi'
mask_overlay_icon = 'modular_doppler/modular_cosmetics/icons/mob/head/doppler_command_hardhats.dmi'
+ resistance_flags = FIRE_PROOF | ACID_PROOF
+ clothing_flags = STOPSPRESSUREDAMAGE | STACKABLE_HELMET_EXEMPT
+ heat_protection = HEAD
+ max_heat_protection_temperature = FIRE_HELM_MAX_TEMP_PROTECT
+ cold_protection = HEAD
+ min_cold_protection_temperature = FIRE_HELM_MIN_TEMP_PROTECT
+ light_range = 4
+
+/obj/item/clothing/head/utility/hardhat/welding/doppler_command/worn_overlays(mutable_appearance/standing, isinhands, icon_file)
+ . = ..()
+ if(!isinhands)
+ . += emissive_appearance(icon_file, "hardhat_emissive", src, alpha = src.alpha)
+
/obj/item/clothing/head/utility/hardhat/welding/doppler_command/medical
name = "medical command hardhat"
icon_state = "hardhat0_med"
diff --git a/modular_doppler/modular_cosmetics/code/neck/doppler_command_mantles.dm b/modular_doppler/modular_cosmetics/code/neck/doppler_command_mantles.dm
index 157179f2d697a..449221da02e09 100644
--- a/modular_doppler/modular_cosmetics/code/neck/doppler_command_mantles.dm
+++ b/modular_doppler/modular_cosmetics/code/neck/doppler_command_mantles.dm
@@ -9,6 +9,11 @@
body_parts_covered = CHEST|ARMS
resistance_flags = FIRE_PROOF | ACID_PROOF
+/obj/item/clothing/neck/doppler_mantle/worn_overlays(mutable_appearance/standing, isinhands, icon_file)
+ . = ..()
+ if(!isinhands)
+ . += emissive_appearance(icon_file, "mantle-emissive", src, alpha = src.alpha)
+
/obj/item/clothing/neck/doppler_mantle/medical
name = "medical command mantle"
icon_state = "doppler_med"
diff --git a/modular_doppler/modular_cosmetics/icons/mob/head/doppler_command_hardhats.dmi b/modular_doppler/modular_cosmetics/icons/mob/head/doppler_command_hardhats.dmi
index d35fc86f23462..bb116dbd0c717 100644
Binary files a/modular_doppler/modular_cosmetics/icons/mob/head/doppler_command_hardhats.dmi and b/modular_doppler/modular_cosmetics/icons/mob/head/doppler_command_hardhats.dmi differ
diff --git a/modular_doppler/modular_cosmetics/icons/mob/neck/doppler_mantles.dmi b/modular_doppler/modular_cosmetics/icons/mob/neck/doppler_mantles.dmi
index 2b42a8a04307c..0bcbf4e013017 100644
Binary files a/modular_doppler/modular_cosmetics/icons/mob/neck/doppler_mantles.dmi and b/modular_doppler/modular_cosmetics/icons/mob/neck/doppler_mantles.dmi differ
diff --git a/modular_doppler/modular_customization/markings/icons/markings/other_markings.dmi b/modular_doppler/modular_customization/markings/icons/markings/other_markings.dmi
index 3fea12fa5ca59..d699536db4b57 100644
Binary files a/modular_doppler/modular_customization/markings/icons/markings/other_markings.dmi and b/modular_doppler/modular_customization/markings/icons/markings/other_markings.dmi differ
diff --git a/modular_doppler/modular_medical/surgery/organs/internal/cyberimp.dm b/modular_doppler/modular_medical/surgery/organs/internal/cyberimp.dm
index 67dd334612759..7c0f5285f0da9 100644
--- a/modular_doppler/modular_medical/surgery/organs/internal/cyberimp.dm
+++ b/modular_doppler/modular_medical/surgery/organs/internal/cyberimp.dm
@@ -176,7 +176,6 @@
icon = 'modular_doppler/modular_medical/icons/implants.dmi'
icon_state = "steel"
items_to_create = list(/obj/item/pickaxe/drill/implant)
- implant_overlay = null
implant_color = null
/// The bodypart overlay datum we should apply to whatever mob we are put into's someone's arm
var/datum/bodypart_overlay/simple/steel_drill/drill_overlay
diff --git a/modular_doppler/modular_quirks/evil_replacements/bad_vibes.dm b/modular_doppler/modular_quirks/evil_replacements/bad_vibes.dm
new file mode 100644
index 0000000000000..0e2b0c927387b
--- /dev/null
+++ b/modular_doppler/modular_quirks/evil_replacements/bad_vibes.dm
@@ -0,0 +1,15 @@
+/datum/quirk/bad_vibes
+ name = "Bad Vibes"
+ desc = "By a quirk of your personality or exposure to too many horrible sights, you give off a bad aura which affects \
+ empaths and anyone else who looks too closely."
+ icon = FA_ICON_HAND_MIDDLE_FINGER
+ value = 0
+ mob_trait = TRAIT_BAD_VIBES
+ gain_text = span_notice("You give off a negative aura.")
+ lose_text = span_notice("You try to be more approachable.")
+ medical_record_text = "Patient scared away a nurse prior to physical examination."
+
+/datum/mood_event/bad_vibes
+ description = "Some people truly disturb me... What could happen to make someone like that?"
+ mood_change = -4
+ timeout = 1 MINUTES
diff --git a/modular_doppler/modular_quirks/evil_replacements/unholy_aura.dm b/modular_doppler/modular_quirks/evil_replacements/unholy_aura.dm
new file mode 100644
index 0000000000000..3689dcbe8682d
--- /dev/null
+++ b/modular_doppler/modular_quirks/evil_replacements/unholy_aura.dm
@@ -0,0 +1,15 @@
+/datum/quirk/unholy
+ name = "Unholy Aura"
+ desc = "Whether it's as a punishment for your actions or due to the circumstances of your birth, you've been cursed by \
+ whatever dieties take an interest in this sector. You have a harder time interacting with holy figures."
+ icon = FA_ICON_SMOG
+ value = -2
+ mob_trait = TRAIT_EVIL
+ gain_text = span_warning("A dispassionate gaze from on high weighs on you.")
+ lose_text = span_notice("The deities' gaze turns away.")
+ medical_record_text = "Patient has a strong aversion to religious figures."
+
+/datum/mood_event/holy_figure
+ description = "Holy people are anathema to me. I must be more careful..."
+ mood_change = -4
+ timeout = 1 MINUTES
diff --git a/modular_doppler/modular_species/species_types/android/android.dm b/modular_doppler/modular_species/species_types/android/android.dm
index dc7dca1bbed82..7287f5ec20501 100644
--- a/modular_doppler/modular_species/species_types/android/android.dm
+++ b/modular_doppler/modular_species/species_types/android/android.dm
@@ -1,7 +1,7 @@
/// The starter amount for the android's core
#define ENERGY_START_AMT 5 MEGA JOULES
/// The amount at which mob energy decreases
-#define ENERGY_DRAIN_AMT 5 KILO JOULES
+#define ENERGY_DRAIN_AMT 2.5 KILO JOULES
/datum/species/android
name = "Android"
@@ -71,6 +71,8 @@
QDEL_NULL(energy_tracker)
/datum/species/android/spec_revival(mob/living/carbon/human/target)
+ if(core_energy < 0.5 MEGA JOULES)
+ core_energy += 0.5 MEGA JOULES
playsound(target.loc, 'sound/machines/chime.ogg', 50, TRUE)
target.visible_message(span_notice("[target]'s LEDs flicker to life!"), span_notice("All systems nominal. You're back online!"))
@@ -91,11 +93,6 @@
return
if(core_energy > 0)
core_energy -= ENERGY_DRAIN_AMT
- // alerts
- if(core_energy <= 0.75 MEGA JOULES)
- if(prob(10))
- target.balloon_alert_to_viewers("power low!")
- playsound(target, 'sound/machines/beep/triple_beep.ogg', 50, FALSE)
// alerts end, death begins
if(core_energy <= 0)
target.death() // You can do a lot in a day.
diff --git a/modular_doppler/modular_species/species_types/android/android_hud.dm b/modular_doppler/modular_species/species_types/android/android_hud.dm
index 4b4786cf0d952..77c1da9166a57 100644
--- a/modular_doppler/modular_species/species_types/android/android_hud.dm
+++ b/modular_doppler/modular_species/species_types/android/android_hud.dm
@@ -1,5 +1,5 @@
/// 1 tile down
-#define UI_ENERGY_DISPLAY "WEST:6,CENTER-1:0"
+#define UI_ENERGY_DISPLAY "WEST:0,CENTER-1:0"
///Maptext define for Hemophage HUDs
#define FORMAT_ANDROID_HUD_TEXT(valuecolor, value) MAPTEXT("[round((value/1000000), 0.01)]MJ
")
@@ -11,9 +11,14 @@
name = "Energy Tracker"
icon_state = "energy_display"
screen_loc = UI_ENERGY_DISPLAY
+ maptext_width = 48
/atom/movable/screen/android/energy/proc/update_energy_hud(core_energy)
maptext = FORMAT_ANDROID_HUD_TEXT(hud_text_color(core_energy), core_energy)
+ if(core_energy <= 1.5 MEGA JOULES)
+ icon_state = "energy_display_low"
+ else
+ icon_state = "energy_display"
/atom/movable/screen/android/energy/proc/hud_text_color(core_energy)
return core_energy > 1.5 MEGA JOULES ? "#ffffff" : "#b64b4b"
diff --git a/modular_doppler/modular_species/species_types/android/icons/android_hud.dmi b/modular_doppler/modular_species/species_types/android/icons/android_hud.dmi
index b524cf6328041..7a08492320196 100644
Binary files a/modular_doppler/modular_species/species_types/android/icons/android_hud.dmi and b/modular_doppler/modular_species/species_types/android/icons/android_hud.dmi differ
diff --git a/modular_doppler/modular_vending/icons/de_forest_vendors.dmi b/modular_doppler/modular_vending/icons/de_forest_vendors.dmi
index 906191c451b36..25ad4c2c0f025 100644
Binary files a/modular_doppler/modular_vending/icons/de_forest_vendors.dmi and b/modular_doppler/modular_vending/icons/de_forest_vendors.dmi differ
diff --git a/modular_doppler/deforest_medical_items/code/chemicals/twitch.dm b/modular_doppler/the-business/code/twitch.dm
similarity index 80%
rename from modular_doppler/deforest_medical_items/code/chemicals/twitch.dm
rename to modular_doppler/the-business/code/twitch.dm
index 8d6b2ca392c6c..6951f8f07628d 100644
--- a/modular_doppler/deforest_medical_items/code/chemicals/twitch.dm
+++ b/modular_doppler/the-business/code/twitch.dm
@@ -8,9 +8,6 @@
// Reaction to make twitch, makes 10u from 17u input reagents
/datum/chemical_reaction/twitch
- results = list(
- /datum/reagent/drug/twitch = 10,
- )
required_reagents = list(
/datum/reagent/impedrezene = 5,
/datum/reagent/bluespace = 10,
@@ -19,14 +16,36 @@
mob_react = FALSE
reaction_tags = REACTION_TAG_EASY | REACTION_TAG_DRUG | REACTION_TAG_ORGAN | REACTION_TAG_DAMAGING
-// Twitch drug, makes the takers of it faster and able to dodge bullets while in their system, to potentially bad side effects
+/datum/chemical_reaction/twitch/on_reaction(datum/reagents/holder, datum/equilibrium/reaction, created_volume)
+ var/location = get_turf(holder.my_atom)
+ for(var/iteration in 1 to created_volume)
+ var/obj/item/reagent_containers/hypospray/medipen/deforest/twitch/new_injector = new(location)
+ new_injector.pixel_x = rand(-6, 6)
+ new_injector.pixel_y = rand(-6, 6)
+
+// Injector, because it needs to look kickass
+/obj/item/reagent_containers/hypospray/medipen/deforest/twitch
+ name = "T-WITCH vial"
+ desc = "An almost cartoonish looking glass injector filled with a horribly corrosive green liquid that slowly swirls around. \
+ A heavily regulated substance called T-WITCH that is claimed to make the users of it 'see faster'."
+ icon = 'modular_doppler/the-business/icons/items.dmi'
+ base_icon_state = "twitch"
+ icon_state = "twitch"
+ list_reagents = list(
+ /datum/reagent/drug/twitch = 10,
+ /datum/reagent/drug/maint/tar = 5,
+ /datum/reagent/medicine/silibinin = 5,
+ /datum/reagent/toxin/leadacetate = 5,
+ )
+
+// T-WITCH, makes the user faster in movement and attacks, they can even dodge projectiles when overdosing on it
/datum/reagent/drug/twitch
- name = "TWitch"
- description = "A drug originally developed by and for plutonians to assist them during raids. \
- Does not see wide use due to the whole reality-disassociation and heart disease thing afterwards. \
- Can be intentionally overdosed to increase the drug's effects"
+ name = "T-WITCH"
+ description = "An invention by the drug ~artists~ of Europa, a wicked stimulant that both slow's the user's \
+ perception of time and speeds of their actions. This is a recipe for some extreme performance, at heavy \
+ cost to the user's health in most cases."
reagent_state = LIQUID
- color = "#c22a44"
+ color = "#91db69"
taste_description = "television static"
metabolization_rate = 0.65 * REAGENTS_METABOLISM
ph = 3
@@ -41,7 +60,6 @@
/// How much the mob heating is multiplied by, if the target is a robot or has muscled veins
var/mob_heating_muliplier = 5
-
/datum/reagent/drug/twitch/on_mob_metabolize(mob/living/our_guy)
. = ..()
@@ -71,7 +89,6 @@
game_plane_master_controller.add_filter(TWITCH_SCREEN_BLUR, 1, list("type" = "radial_blur", "size" = 0.02))
-
/datum/reagent/drug/twitch/on_mob_end_metabolize(mob/living/carbon/our_guy)
. = ..()
@@ -80,8 +97,6 @@
our_guy.sound_environment_override = NONE
- speech_effect_span = "hierophant"
-
UnregisterSignal(our_guy, COMSIG_MOVABLE_MOVED)
UnregisterSignal(our_guy, COMSIG_MOVABLE_HEAR)
if(overdosed)
@@ -90,13 +105,13 @@
if(constant_dose_time < CONSTANT_DOSE_SAFE_LIMIT) // Anything less than this and you'll come out fiiiine, aside from a big hit of stamina damage
if(!(our_guy.mob_biotypes & MOB_ROBOTIC))
our_guy.visible_message(
- span_danger("[our_guy] suddenly slows from [our_guy.p_their()] inhuman speeds, coming back with a wicked nosebleed!"),
- span_danger("You suddenly slow back to normal, a stream of blood gushing from your nose!")
+ span_danger("[our_guy] stops dead, [our_guy.p_their()] afterimages quickly catching up to them!"),
+ span_danger("You suddenly stop dead in your tracks, a stream of blood gushing from your nose!")
)
else
our_guy.visible_message(
- span_danger("[our_guy] suddenly slows from [our_guy.p_their()] inhuman speeds!"),
- span_danger("You suddenly slow back to normal speed!")
+ span_danger("[our_guy] stops dead, [our_guy.p_their()] afterimages quickly catching up to them!"),
+ span_danger("You suddenly stop dead in your tracks!")
)
our_guy.adjustStaminaLoss(constant_dose_time)
@@ -105,13 +120,13 @@
our_guy.spray_blood(our_guy.dir, 2) // The before mentioned coughing up blood
our_guy.emote("cough")
our_guy.visible_message(
- span_danger("[our_guy] suddenly snaps back from [our_guy.p_their()] inhuman speeds, coughing up a spray of blood!"),
- span_danger("As you snap back to normal speed you cough up a worrying amount of blood. You feel like you've just been run over by a power loader.")
+ span_danger("[our_guy] stops dead, coughing up a spray of blood!"),
+ span_danger("As you stop dead in your tracks, you cough up a worrying amount of blood.")
)
else
our_guy.visible_message(
- span_danger("[our_guy] suddenly snaps back from [our_guy.p_their()] inhuman speeds!"),
- span_danger("You suddenly snap back to normal speeds. You feel like you've just been run over by a power loader.")
+ span_danger("[our_guy] stops dead, heat venting from [our_guy.p_their()] body!"),
+ span_danger("You suddenly stop dead in your tracks, superheated air venting from your body!")
)
our_guy.adjustStaminaLoss(constant_dose_time)
if(!HAS_TRAIT(our_guy, TRAIT_TWITCH_ADAPTED))
@@ -120,18 +135,18 @@
if(!our_guy.hud_used)
return
+ our_guy.Stun(1 SECONDS)
+
var/atom/movable/plane_master_controller/game_plane_master_controller = our_guy.hud_used.plane_master_controllers[PLANE_MASTERS_GAME]
game_plane_master_controller.remove_filter(TWITCH_SCREEN_FILTER)
game_plane_master_controller.remove_filter(TWITCH_SCREEN_BLUR)
-
/// Leaves an afterimage behind the mob when they move
/datum/reagent/drug/twitch/proc/on_movement(mob/living/carbon/our_guy, atom/old_loc)
SIGNAL_HANDLER
new /obj/effect/temp_visual/decoy/twitch_afterimage(old_loc, our_guy)
-
/// Tries to dodge incoming bullets if we aren't disabled for any reasons
/datum/reagent/drug/twitch/proc/dodge_bullets(mob/living/carbon/human/source, obj/projectile/hitting_projectile, def_zone)
SIGNAL_HANDLER
@@ -147,7 +162,6 @@
addtimer(CALLBACK(source, TYPE_PROC_REF(/datum, remove_filter), TWITCH_BLUR_EFFECT), 0.5 SECONDS)
return COMPONENT_BULLET_PIERCED
-
/datum/reagent/drug/twitch/on_mob_life(mob/living/carbon/our_guy, seconds_per_tick, times_fired)
. = ..()
@@ -168,10 +182,11 @@
if(locate(/datum/reagent/drug/kronkaine) in our_guy.reagents.reagent_list) // Kronkaine, another heart-straining drug, could cause problems if mixed with this
our_guy.ForceContractDisease(new /datum/disease/adrenal_crisis(), FALSE, TRUE)
-
/datum/reagent/drug/twitch/overdose_start(mob/living/our_guy)
. = ..()
+ speech_effect_span = "hierophant"
+
RegisterSignal(our_guy, COMSIG_ATOM_PRE_BULLET_ACT, PROC_REF(dodge_bullets))
our_guy.next_move_modifier -= 0.2 // Overdosing makes you a liiitle faster but you know has some really bad consequences
@@ -186,7 +201,6 @@
for(var/filter in game_plane_master_controller.get_filters(TWITCH_SCREEN_FILTER))
animate(filter, loop = -1, color = col_filter_ourple, time = 4 SECONDS, easing = BOUNCE_EASING)
-
/datum/reagent/drug/twitch/overdose_process(mob/living/carbon/our_guy, seconds_per_tick, times_fired)
. = ..()
our_guy.set_jitter_if_lower(10 SECONDS * REM * seconds_per_tick)
@@ -205,9 +219,8 @@
our_guy.adjustToxLoss(1 * REM * seconds_per_tick, updating_health = FALSE, forced = TRUE, required_biotype = affected_biotype)
if(SPT_PROB(5, seconds_per_tick) && !(our_guy.mob_biotypes & MOB_ROBOTIC))
- to_chat(our_guy, span_danger("You cough up a splatter of blood!"))
- our_guy.spray_blood(our_guy.dir, 1)
- our_guy.emote("cough")
+ our_guy.playsound_local(our_guy, 'sound/effects/singlebeat.ogg', 100, TRUE)
+ flash_color(our_guy, flash_color = "#ff0000", flash_time = 3 SECONDS)
if(SPT_PROB(10, seconds_per_tick))
our_guy.add_filter(TWITCH_OVERDOSE_BLUR_EFFECT, 2, phase_filter(8))
@@ -218,7 +231,6 @@
SIGNAL_HANDLER
hearing_args[HEARING_RAW_MESSAGE] = "[hearing_args[HEARING_RAW_MESSAGE]]"
-
/// Cool filter that I'm using for some of this :)))
/proc/phase_filter(size)
. = list("type" = "wave")
@@ -226,7 +238,6 @@
if(!isnull(size))
.["size"] = size
-
// Temp visual that changes color for that bootleg sandevistan effect
/obj/effect/temp_visual/decoy/twitch_afterimage
duration = 0.75 SECONDS
diff --git a/modular_doppler/the-business/icons/items.dmi b/modular_doppler/the-business/icons/items.dmi
new file mode 100644
index 0000000000000..805f33631ea88
Binary files /dev/null and b/modular_doppler/the-business/icons/items.dmi differ
diff --git a/rust_g.dll b/rust_g.dll
index d3aebf7121706..157fb64acaf33 100644
Binary files a/rust_g.dll and b/rust_g.dll differ
diff --git a/sound/attributions.txt b/sound/attributions.txt
index a6cbf21f8369b..ff83a24ab8230 100644
--- a/sound/attributions.txt
+++ b/sound/attributions.txt
@@ -23,7 +23,6 @@ champagne_pop.ogg is credited to ultradust on freesound https://freesound.org/pe
can_open.ogg adapted from https://freesound.org/people/MaxDemianAGL/sounds/130031/
can_shake.ogg adapted from https://freesound.org/people/mcmast/sounds/456703/
-
splatter.ogg adapted from https://freesound.org/people/Rocktopus/sounds/233418/
hohoho.ogg and hehe.ogg are cut from a recording by Nanakisan on freesound: https://freesound.org/people/Nanakisan/sounds/253534/
mbox_full.ogg and mbox_end.ogg make use of The Ragtime Drummer by James Lent, in the public domain
@@ -181,8 +180,6 @@ https://freesound.org/people/shw489/sounds/234389/
soup_boil1.ogg through soup_boil5.ogg and soup_boil_end.ogg are taken from Boiling Soup from Freesoung.org (CC4) and converted to OGG / split apart (but is otherwise unchanged):
https://freesound.org/people/jorickhoofd/sounds/632783/
-
-
valve_opening.ogg was made by mixing water flowing samples from:
https://freesound.org/people/scriotxstudios/sounds/349111/?attribution=1 and squeaky scrape sound from:
https://freesound.org/people/Department64/sounds/669028/ which was modified with lower pitch
@@ -191,7 +188,6 @@ liquid_pour2.ogg and liquid_pour3.ogg were cut from
https://freesound.org/people/MattRuthSound/sounds/561896/
https://freesound.org/people/MattRuthSound/sounds/561895/
-
roaring_fire.ogg made from: 10835 big fire loop.wav by Robinhood76 -- https://freesound.org/s/612277/ -- License: Attribution NonCommercial 4.0
fire_puff made from: Bonfire Being Lit by samararaine -- https://freesound.org/s/186374/ -- License: Creative Commons 0
@@ -208,8 +204,6 @@ Bottle Tap.wav by alex_alexalex -- https://freesound.org/s/395492/ -- License: A
beaker_place.ogg was made by cutting and lowering pitch:
place glass object.wav by milpower -- https://freesound.org/s/353105/ -- License: Creative Commons 0
-
-
glass_reverse.ogg is adapted from a combination of:
https://freesound.org/people/C_Rogers/sounds/203368/ -- glass-shattering-hit_01.ogg by C_Rogers on freesound.org (CC0)
https://freesound.org/people/Czarcazas/sounds/330800/ -- Audio reversal/fading of Shattering Glass (Small) by Czarcazas -- https://freesound.org/s/330800/ -- License: Attribution 3.0
@@ -217,3 +211,5 @@ https://freesound.org/people/Czarcazas/sounds/330800/ -- Audio reversal/fading o
sound/effects/bonk.ogg - recorded by oranges on a coke zero bottle, edited by ninjanomnom, released to public domain
sound\items\weapons\hammer_death_scream.ogg - Undefeatablesos' scream recorded by Niron3206, edited by Niron3206, License: Creative Commons 0
+
+sound/machines/sink-faucet.ogg -- https://freesound.org/people/FOSSarts/sounds/740086/ -- by FOSSarts (CC0)
diff --git a/sound/items/weapons/peashoot.ogg b/sound/items/weapons/peashoot.ogg
new file mode 100644
index 0000000000000..de4d5c1e46458
Binary files /dev/null and b/sound/items/weapons/peashoot.ogg differ
diff --git a/sound/machines/license.txt b/sound/machines/license.txt
index dbccfd7ea096d..69e52c94e4b74 100644
--- a/sound/machines/license.txt
+++ b/sound/machines/license.txt
@@ -4,3 +4,5 @@ This is licensed under CC-BY 4.0, found at https://creativecommons.org/licenses/
shutter.ogg adapted from Joseph Sardin on BigSoundBank
https://bigsoundbank.com/detail-2475-manual-roller-shutter-closing-out-2.html
+
+mail_sort.ogg adapted from csigusz_foxoup ob Freesound https://freesound.org/people/csigusz_foxoup/sounds/711428/
\ No newline at end of file
diff --git a/sound/machines/mail_sort.ogg b/sound/machines/mail_sort.ogg
new file mode 100644
index 0000000000000..66ec79468d50b
Binary files /dev/null and b/sound/machines/mail_sort.ogg differ
diff --git a/sound/machines/sink-faucet.ogg b/sound/machines/sink-faucet.ogg
new file mode 100644
index 0000000000000..7102a3940308f
Binary files /dev/null and b/sound/machines/sink-faucet.ogg differ
diff --git a/sound/mobs/humanoids/ethereal/credits.txt b/sound/mobs/humanoids/ethereal/credits.txt
new file mode 100644
index 0000000000000..a157ceacf9edd
--- /dev/null
+++ b/sound/mobs/humanoids/ethereal/credits.txt
@@ -0,0 +1,2 @@
+ethereal_hiss.ogg majorly edited/mixed by Sothanforax, based off of the original audio:
+Remix of 101127__CGEffex__Bug_Zapper_Long_moth_electrocution_Remix.wav by Timbre -- https://freesound.org/s/101334/ -- License: Attribution NonCommercial 4.0
diff --git a/sound/mobs/humanoids/ethereal/ethereal_hiss.ogg b/sound/mobs/humanoids/ethereal/ethereal_hiss.ogg
new file mode 100644
index 0000000000000..969944ea4daa8
Binary files /dev/null and b/sound/mobs/humanoids/ethereal/ethereal_hiss.ogg differ
diff --git a/sound/mobs/humanoids/felinid/attribution.txt b/sound/mobs/humanoids/felinid/attribution.txt
new file mode 100644
index 0000000000000..dfd416150c6b3
--- /dev/null
+++ b/sound/mobs/humanoids/felinid/attribution.txt
@@ -0,0 +1 @@
+felinid_hiss is catHisses2.wav by Zabuhailo -- https://freesound.org/s/146962/ -- License: Creative Commons 0
\ No newline at end of file
diff --git a/sound/mobs/humanoids/felinid/felinid_hiss.ogg b/sound/mobs/humanoids/felinid/felinid_hiss.ogg
new file mode 100644
index 0000000000000..f343bd77fd1ff
Binary files /dev/null and b/sound/mobs/humanoids/felinid/felinid_hiss.ogg differ
diff --git a/sound/mobs/humanoids/human/attribution.txt b/sound/mobs/humanoids/human/attribution.txt
index 20b8c14889a06..f56dc03f794ba 100644
--- a/sound/mobs/humanoids/human/attribution.txt
+++ b/sound/mobs/humanoids/human/attribution.txt
@@ -1,4 +1,5 @@
The male sharp gasps are from https://freesound.org/people/bacruz666/sounds/341908/ and https://freesound.org/people/nettoi/sounds/677540/, the female sharp gasps are from https://freesound.org/people/drotzruhn/sounds/405203/
+human_hiss.ogg is all original work by Sothanforax, hereby licensed under CC BY-SA 3.0
{
male_sniff.ogg - https://freesound.org/people/Fluffayfish/sounds/327799/ , License: CC BY-NC 3.0
diff --git a/sound/mobs/humanoids/human/hiss/human_hiss.ogg b/sound/mobs/humanoids/human/hiss/human_hiss.ogg
new file mode 100644
index 0000000000000..15f643b422086
Binary files /dev/null and b/sound/mobs/humanoids/human/hiss/human_hiss.ogg differ
diff --git a/sound/mobs/humanoids/lizard/credits.txt b/sound/mobs/humanoids/lizard/credits.txt
index 814b758f44da9..820a38fb59376 100644
--- a/sound/mobs/humanoids/lizard/credits.txt
+++ b/sound/mobs/humanoids/lizard/credits.txt
@@ -1,2 +1,3 @@
lizard_scream_1 by n Beats. Lizard_scream_2 and lizard_scream_3 by -sihiL. Lizard_scream_3 edited Lord Saladin. Original PR by super12pl.
deathsound.ogg is originally "demon dying.wav" by THE_bizniss. It was converted and compressed into .ogg format. It and a link to its license can be found at https://freesound.org/s/37823/
+lizard_hiss was originally recorded by Garuda1982, minor editing by Sothanforax. license is at https://freesound.org/s/541656/
diff --git a/sound/mobs/humanoids/lizard/lizard_hiss.ogg b/sound/mobs/humanoids/lizard/lizard_hiss.ogg
new file mode 100644
index 0000000000000..202c7929a1372
Binary files /dev/null and b/sound/mobs/humanoids/lizard/lizard_hiss.ogg differ
diff --git a/tgstation.dme b/tgstation.dme
index 0d753fb4ab736..a6670aafba7e4 100644
--- a/tgstation.dme
+++ b/tgstation.dme
@@ -515,6 +515,7 @@
#include "code\__HELPERS\screen_objs.dm"
#include "code\__HELPERS\see_through_maps.dm"
#include "code\__HELPERS\shell.dm"
+#include "code\__HELPERS\shuttle.dm"
#include "code\__HELPERS\spatial_info.dm"
#include "code\__HELPERS\spawns.dm"
#include "code\__HELPERS\stack_trace.dm"
@@ -579,6 +580,7 @@
#include "code\_globalvars\lighting.dm"
#include "code\_globalvars\logging.dm"
#include "code\_globalvars\phobias.dm"
+#include "code\_globalvars\pipe_info.dm"
#include "code\_globalvars\rcd.dm"
#include "code\_globalvars\religion.dm"
#include "code\_globalvars\silo.dm"
@@ -601,6 +603,7 @@
#include "code\_globalvars\lists\names.dm"
#include "code\_globalvars\lists\objects.dm"
#include "code\_globalvars\lists\ores_spawned.dm"
+#include "code\_globalvars\lists\pipe_recipes.dm"
#include "code\_globalvars\lists\plumbing.dm"
#include "code\_globalvars\lists\poll_ignore.dm"
#include "code\_globalvars\lists\quirks.dm"
@@ -5997,28 +6000,32 @@
#include "code\modules\research\xenobiology\vatgrowing\samples\viruses\_virus.dm"
#include "code\modules\security_levels\keycard_authentication.dm"
#include "code\modules\security_levels\security_level_datums.dm"
-#include "code\modules\shuttle\arrivals.dm"
-#include "code\modules\shuttle\assault_pod.dm"
-#include "code\modules\shuttle\battlecruiser_starfury.dm"
-#include "code\modules\shuttle\computer.dm"
-#include "code\modules\shuttle\docking.dm"
-#include "code\modules\shuttle\elevator.dm"
-#include "code\modules\shuttle\emergency.dm"
-#include "code\modules\shuttle\ferry.dm"
-#include "code\modules\shuttle\infiltrator.dm"
-#include "code\modules\shuttle\manipulator.dm"
-#include "code\modules\shuttle\medisim.dm"
-#include "code\modules\shuttle\monastery.dm"
-#include "code\modules\shuttle\navigation_computer.dm"
-#include "code\modules\shuttle\on_move.dm"
-#include "code\modules\shuttle\ripple.dm"
#include "code\modules\shuttle\shuttle.dm"
-#include "code\modules\shuttle\shuttle_rotate.dm"
-#include "code\modules\shuttle\spaceship_navigation_beacon.dm"
-#include "code\modules\shuttle\special.dm"
-#include "code\modules\shuttle\supply.dm"
-#include "code\modules\shuttle\syndicate.dm"
-#include "code\modules\shuttle\white_ship.dm"
+#include "code\modules\shuttle\misc\manipulator.dm"
+#include "code\modules\shuttle\misc\medisim.dm"
+#include "code\modules\shuttle\misc\ripple.dm"
+#include "code\modules\shuttle\misc\spaceship_navigation_beacon.dm"
+#include "code\modules\shuttle\misc\special.dm"
+#include "code\modules\shuttle\mobile_port\mobile_port.dm"
+#include "code\modules\shuttle\mobile_port\shuttle_move.dm"
+#include "code\modules\shuttle\mobile_port\shuttle_move_callbacks.dm"
+#include "code\modules\shuttle\mobile_port\shuttle_rotate_callbacks.dm"
+#include "code\modules\shuttle\mobile_port\variants\arrivals.dm"
+#include "code\modules\shuttle\mobile_port\variants\assault_pod.dm"
+#include "code\modules\shuttle\mobile_port\variants\battlecruiser_starfury.dm"
+#include "code\modules\shuttle\mobile_port\variants\elevator.dm"
+#include "code\modules\shuttle\mobile_port\variants\ferry.dm"
+#include "code\modules\shuttle\mobile_port\variants\infiltrator.dm"
+#include "code\modules\shuttle\mobile_port\variants\supply.dm"
+#include "code\modules\shuttle\mobile_port\variants\emergency\emergency.dm"
+#include "code\modules\shuttle\mobile_port\variants\emergency\emergency_console.dm"
+#include "code\modules\shuttle\mobile_port\variants\emergency\emergency_types.dm"
+#include "code\modules\shuttle\mobile_port\variants\emergency\pods.dm"
+#include "code\modules\shuttle\shuttle_consoles\monastery.dm"
+#include "code\modules\shuttle\shuttle_consoles\navigation_computer.dm"
+#include "code\modules\shuttle\shuttle_consoles\shuttle_console.dm"
+#include "code\modules\shuttle\shuttle_consoles\syndicate.dm"
+#include "code\modules\shuttle\shuttle_consoles\white_ship.dm"
#include "code\modules\shuttle\shuttle_events\_shuttle_events.dm"
#include "code\modules\shuttle\shuttle_events\blackhole.dm"
#include "code\modules\shuttle\shuttle_events\carp.dm"
@@ -6028,6 +6035,8 @@
#include "code\modules\shuttle\shuttle_events\player_controlled.dm"
#include "code\modules\shuttle\shuttle_events\projectile.dm"
#include "code\modules\shuttle\shuttle_events\turbulence.dm"
+#include "code\modules\shuttle\stationary_port\port_types.dm"
+#include "code\modules\shuttle\stationary_port\stationary_port.dm"
#include "code\modules\spatial_grid\cell_tracker.dm"
#include "code\modules\spells\spell.dm"
#include "code\modules\spells\spell_types\madness_curse.dm"
@@ -6379,6 +6388,7 @@
#include "code\modules\vending\liberation.dm"
#include "code\modules\vending\liberation_toy.dm"
#include "code\modules\vending\magivend.dm"
+#include "code\modules\vending\mail.dm"
#include "code\modules\vending\medical.dm"
#include "code\modules\vending\medical_wall.dm"
#include "code\modules\vending\megaseed.dm"
@@ -6634,6 +6644,9 @@
#include "modular_doppler\cryosleep\code\mood.dm"
#include "modular_doppler\cryosleep\code\objective.dm"
#include "modular_doppler\cryosleep\code\objects\structures\signs\signs_maps.dm"
+#include "modular_doppler\damage_control\code\breach_gear.dm"
+#include "modular_doppler\damage_control\code\emergency_fab.dm"
+#include "modular_doppler\damage_control\code\emergency_fab_designs.dm"
#include "modular_doppler\deathmatch\deathmatch_loadouts.dm"
#include "modular_doppler\deathmatch\deathmatch_maps.dm"
#include "modular_doppler\deathmatch\deathmatch_structures.dm"
@@ -6641,16 +6654,12 @@
#include "modular_doppler\deforest_medical_items\code\healing_stack_items.dm"
#include "modular_doppler\deforest_medical_items\code\illegal_injectors.dm"
#include "modular_doppler\deforest_medical_items\code\injectors.dm"
-#include "modular_doppler\deforest_medical_items\code\medstation.dm"
#include "modular_doppler\deforest_medical_items\code\storage_items.dm"
#include "modular_doppler\deforest_medical_items\code\storage_items_robotics.dm"
#include "modular_doppler\deforest_medical_items\code\synth_healing.dm"
#include "modular_doppler\deforest_medical_items\code\treatment_zone_projector.dm"
#include "modular_doppler\deforest_medical_items\code\vulnerable_status_effect.dm"
#include "modular_doppler\deforest_medical_items\code\chemicals\demoneye.dm"
-#include "modular_doppler\deforest_medical_items\code\chemicals\twitch.dm"
-#include "modular_doppler\deforest_medical_items\code\medstation_designs\blood.dm"
-#include "modular_doppler\deforest_medical_items\code\medstation_designs\medical.dm"
#include "modular_doppler\disable_suicide\config_entries.dm"
#include "modular_doppler\doppler_command_uniforms\hop\overrides.dm"
#include "modular_doppler\dopplerboop\dopplerboops.dm"
@@ -7080,6 +7089,8 @@
#include "modular_doppler\modular_quirks\entombed\code\entombed.dm"
#include "modular_doppler\modular_quirks\entombed\code\entombed_alt_actions.dm"
#include "modular_doppler\modular_quirks\entombed\code\entombed_mod.dm"
+#include "modular_doppler\modular_quirks\evil_replacements\bad_vibes.dm"
+#include "modular_doppler\modular_quirks\evil_replacements\unholy_aura.dm"
#include "modular_doppler\modular_quirks\excitable\excitable.dm"
#include "modular_doppler\modular_quirks\fragility\code\fragile.dm"
#include "modular_doppler\modular_quirks\hardened_soles\hardened_soles.dm"
@@ -7202,6 +7213,7 @@
#include "modular_doppler\stone\code\ore_veins.dm"
#include "modular_doppler\stone\code\stone.dm"
#include "modular_doppler\tableflip\tableflip.dm"
+#include "modular_doppler\the-business\code\twitch.dm"
#include "modular_doppler\time_clock\code\console.dm"
#include "modular_doppler\time_clock\code\console_tgui.dm"
#include "modular_doppler\time_clock\code\mind.dm"
diff --git a/tgui/packages/tgui/interfaces/RapidPipeDispenser.tsx b/tgui/packages/tgui/interfaces/RapidPipeDispenser.tsx
index 3bfe22f816479..46b58c4e381bd 100644
--- a/tgui/packages/tgui/interfaces/RapidPipeDispenser.tsx
+++ b/tgui/packages/tgui/interfaces/RapidPipeDispenser.tsx
@@ -48,29 +48,6 @@ const TOOLS = [
},
];
-const LAYERS = [
- {
- name: '1',
- bitmask: 1,
- },
- {
- name: '2',
- bitmask: 2,
- },
- {
- name: '3',
- bitmask: 4,
- },
- {
- name: '4',
- bitmask: 8,
- },
- {
- name: '5',
- bitmask: 16,
- },
-] as const;
-
type DirectionsAllowed = {
north: BooleanLike;
south: BooleanLike;
@@ -113,19 +90,19 @@ type Preview = {
};
type Data = {
+ // Static
+ paint_colors: Colors;
+ max_pipe_layers: number;
// Dynamic
category: number;
pipe_layers: number;
multi_layer: BooleanLike;
- ducting_layer: number;
categories: Category[];
selected_recipe: string;
selected_color: string;
selected_category: string;
mode: number;
init_directions: DirectionsAllowed;
- // Static
- paint_colors: Colors;
};
export const ColorItem = (props) => {
@@ -168,13 +145,14 @@ const ModeItem = (props) => {
act('mode', {
mode: tool.bitmask,
})
}
- />
+ >
+ {tool.name}
+
))}
);
@@ -201,7 +179,7 @@ const CategoryItem = (props) => {
};
const SelectionSection = (props) => {
- const { act, data } = useBackend();
+ const { data } = useBackend();
const { category: rootCategoryIndex } = data;
return (
@@ -217,37 +195,46 @@ const SelectionSection = (props) => {
const LayerSelect = (props) => {
const { act, data } = useBackend();
- const { pipe_layers } = data;
- const { multi_layer } = data;
+ const { pipe_layers, multi_layer, max_pipe_layers } = data;
+ const layer_to_bitmask = (layer: number) => {
+ return 1 << layer;
+ };
+
return (
- {LAYERS.map((layer) => (
- act('pipe_layers', { pipe_layers: layer.bitmask })}
- />
- ))}
+ {Array(max_pipe_layers)
+ .keys()
+ .map((layer) => (
+
+ act('pipe_layers', { pipe_layers: layer_to_bitmask(layer) })
+ }
+ >
+ {layer + 1}
+
+ ))}
{
act('toggle_multi_layer');
}}
- />
+ >
+ Multi
+
);
};
const PreviewSelect = (props) => {
- const { act, data } = useBackend();
+ const { act } = useBackend();
return (
{props.previews.map((preview) => (
@@ -288,8 +275,8 @@ const PreviewSelect = (props) => {
};
const PipeTypeSection = (props) => {
- const { act, data } = useBackend();
- const { categories = [], selected_category, selected_recipe } = data;
+ const { data } = useBackend();
+ const { categories = [], selected_category } = data;
const [categoryName, setCategoryName] = useState(selected_category);
const shownCategory =
categories.find((category) => category.cat_name === categoryName) ||
@@ -411,7 +398,7 @@ export const SmartPipeBlockSection = (props) => {
};
export const RapidPipeDispenser = (props) => {
- const { act, data } = useBackend();
+ const { data } = useBackend();
const { category: rootCategoryIndex } = data;
return (
diff --git a/tgui/packages/tgui/interfaces/TurbineComputer.tsx b/tgui/packages/tgui/interfaces/TurbineComputer.tsx
index 4d1305b88b5c8..f67a09f2687a8 100644
--- a/tgui/packages/tgui/interfaces/TurbineComputer.tsx
+++ b/tgui/packages/tgui/interfaces/TurbineComputer.tsx
@@ -9,7 +9,9 @@ import {
NumberInput,
ProgressBar,
Section,
+ Stack,
} from '../components';
+import { formatPower } from '../format';
import { Window } from '../layouts';
type TurbineInfo = {
@@ -19,105 +21,99 @@ type TurbineInfo = {
power: number;
temp: number;
integrity: number;
- parts_linked: BooleanLike;
- parts_ready: BooleanLike;
max_rpm: number;
max_temperature: number;
regulator: number;
};
-export const TurbineComputer = (props) => {
+const TurbineDisplay = (props) => {
const { act, data } = useBackend();
- const parts_not_connected = !data.parts_linked && (
-
-
- {
- 'Parts not connected, use a multitool on the core rotor before trying again'
- }
-
-
+
+ return (
+ = 1000)}
+ onClick={() => act('toggle_power')}
+ >
+ {data.active ? 'Online' : 'Offline'}
+
+ }
+ >
+
+
+
+ act('regulate', {
+ regulate: value * 0.01,
+ })
+ }
+ />
+
+
+
+
+
+ {data.rpm} RPM
+
+
+ {data.max_rpm} RPM
+
+
+ {data.temp} K
+
+
+ {data.max_temperature} K
+
+
+ {formatPower(data.power)}
+
+
+
);
- const parts_not_ready = data.parts_linked && !data.parts_ready && (
+};
+
+const OutOfService = (props) => {
+ return (
-
- {
- 'Some parts have open maintenance hatchet, please close them before starting'
- }
-
+
+
+
+ {
+ 'Parts not connected, close all mantainence panels/use a multitool on the rotor before trying again'
+ }
+
+
+
);
+};
+
+export const TurbineComputer = (props) => {
+ const { data } = useBackend();
+
return (
- = 1000) || !data.parts_linked}
- onClick={() => act('toggle_power')}
- />
- }
- >
- {parts_not_connected}
- {parts_not_ready}
-
-
-
- act('regulate', {
- regulate: value * 0.01,
- })
- }
- />
-
-
-
-
-
- {data.rpm} RPM
-
-
- {data.max_rpm} RPM
-
-
- {data.temp} K
-
-
- {data.max_temperature} K
-
-
- {data.power * 4 * 0.001} kW
-
-
-
+ {data.connected ? : }
);
diff --git a/tools/UpdatePaths/Scripts/repaths_a357_to_c357.txt b/tools/UpdatePaths/Scripts/repaths_a357_to_c357.txt
new file mode 100644
index 0000000000000..28ad97efff1ab
--- /dev/null
+++ b/tools/UpdatePaths/Scripts/repaths_a357_to_c357.txt
@@ -0,0 +1,3 @@
+#comment Repaths instances of a357 with c357, so as to acknowledge that it is consistent with other casings.
+
+/obj/item/ammo_casing/c357 : /obj/item/ammo_casing/a357{@OLD}
diff --git a/tools/ci/ci_config.txt b/tools/ci/ci_config.txt
index 51e08e6328ba1..1e6f19eb692c9 100644
--- a/tools/ci/ci_config.txt
+++ b/tools/ci/ci_config.txt
@@ -5,6 +5,4 @@ FEEDBACK_DATABASE tg_ci
FEEDBACK_TABLEPREFIX
FEEDBACK_LOGIN root
FEEDBACK_PASSWORD
-LAVALAND_BUDGET 0
-SPACE_BUDGET 0
AUXTOOLS_ENABLED