diff --git a/.github/workflows/build_rust.yml b/.github/workflows/build_rust.yml
index 6039d640df8e..ea3cf500eab5 100644
--- a/.github/workflows/build_rust.yml
+++ b/.github/workflows/build_rust.yml
@@ -72,7 +72,7 @@ jobs:
rustup target add i686-pc-windows-gnu
sudo dpkg --add-architecture i386
sudo apt-get update
- sudo apt-get install zlib1g-dev:i386 lib32gcc-11-dev mingw-w64 mingw-w64-i686-dev
+ sudo apt-get install zlib1g-dev:i386 lib32gcc-13-dev mingw-w64 mingw-w64-i686-dev
# Build it.
cargo build --release --target i686-unknown-linux-gnu
diff --git a/_maps/map_files/RandomRuins/SpaceRuins/meatpackers.dmm b/_maps/map_files/RandomRuins/SpaceRuins/meatpackers.dmm
index 4c19b607278f..f63dd9362487 100644
--- a/_maps/map_files/RandomRuins/SpaceRuins/meatpackers.dmm
+++ b/_maps/map_files/RandomRuins/SpaceRuins/meatpackers.dmm
@@ -19,11 +19,7 @@
/turf/simulated/floor/engine,
/area/ruin/unpowered/bmp_ship/delta)
"af" = (
-/obj/machinery/porta_turret{
- installation = /obj/item/gun/energy/gun;
- lethal = 1;
- name = "ship defense turret"
- },
+/obj/machinery/porta_turret/meatpacker_ship,
/turf/simulated/floor/engine,
/area/ruin/unpowered/bmp_ship/delta)
"ag" = (
@@ -1827,12 +1823,7 @@
/turf/simulated/floor/plasteel,
/area/ruin/unpowered/bmp_ship/aft)
"go" = (
-/obj/machinery/porta_turret{
- check_synth = 1;
- installation = /obj/item/gun/energy/gun;
- lethal = 1;
- name = "ship defense turret"
- },
+/obj/machinery/porta_turret/meatpacker_ship,
/turf/simulated/floor/engine,
/area/ruin/unpowered/bmp_ship/fore)
"gp" = (
@@ -2118,12 +2109,7 @@
/turf/simulated/floor/engine,
/area/ruin/unpowered/bmp_ship/aft)
"hp" = (
-/obj/machinery/porta_turret{
- check_synth = 1;
- installation = /obj/item/gun/energy/gun;
- lethal = 1;
- name = "ship defense turret"
- },
+/obj/machinery/porta_turret/meatpacker_ship,
/turf/simulated/floor/engine,
/area/ruin/unpowered/bmp_ship/aft)
"hq" = (
diff --git a/_maps/map_files/RandomRuins/SpaceRuins/spacebar.dmm b/_maps/map_files/RandomRuins/SpaceRuins/spacebar.dmm
index bb84e387a3f0..27969a1dfc4c 100644
--- a/_maps/map_files/RandomRuins/SpaceRuins/spacebar.dmm
+++ b/_maps/map_files/RandomRuins/SpaceRuins/spacebar.dmm
@@ -323,7 +323,7 @@
/area/ruin/space/powered/bar)
"bn" = (
/obj/effect/mapping_helpers/turfs/damage,
-/obj/item/stack/tile/mineral,
+/obj/item/stack/tile/plasteel,
/turf/simulated/floor/plating,
/area/ruin/space/powered/bar)
"bo" = (
@@ -484,7 +484,7 @@
/turf/simulated/floor/plating/asteroid/ancient,
/area/space/nearstation)
"pb" = (
-/obj/item/stack/tile/mineral,
+/obj/item/stack/tile/plasteel,
/obj/machinery/door_control{
id = "SpaceBar";
name = "Public Shutters";
@@ -895,7 +895,7 @@
/area/ruin/space/powered)
"QU" = (
/obj/effect/mapping_helpers/turfs/damage,
-/obj/item/stack/tile/mineral,
+/obj/item/stack/tile/plasteel,
/turf/simulated/floor/plasteel{
icon_state = "dark"
},
@@ -949,7 +949,7 @@
},
/area/ruin/space/powered/bar)
"VR" = (
-/obj/item/stack/tile/mineral,
+/obj/item/stack/tile/plasteel,
/turf/simulated/floor/plating,
/area/ruin/space/powered/bar)
"VU" = (
diff --git a/_maps/map_files/RandomRuins/SpaceRuins/ussp.dmm b/_maps/map_files/RandomRuins/SpaceRuins/ussp.dmm
index afa2641c1ef1..72b05081d8cc 100644
--- a/_maps/map_files/RandomRuins/SpaceRuins/ussp.dmm
+++ b/_maps/map_files/RandomRuins/SpaceRuins/ussp.dmm
@@ -3173,7 +3173,7 @@
},
/area/ruin/space/derelict/crew_quarters)
"iq" = (
-/obj/item/stack/tile,
+/obj/item/stack/tile/plasteel,
/turf/template_noop,
/area/space/nearstation)
"ir" = (
@@ -5147,7 +5147,7 @@
},
/area/ruin/space/derelict/arrival)
"nM" = (
-/obj/item/stack/tile,
+/obj/item/stack/tile/plasteel,
/turf/simulated/floor/plating/airless,
/area/ruin/space/derelict/hallway/primary)
"nN" = (
@@ -5344,7 +5344,7 @@
},
/area/ruin/space/derelict/crew_quarters)
"on" = (
-/obj/item/stack/tile,
+/obj/item/stack/tile/plasteel,
/turf/template_noop,
/area/ruin/space/derelict/arrival)
"op" = (
diff --git a/_maps/map_files/stations/boxstation.dmm b/_maps/map_files/stations/boxstation.dmm
index d1c3d5b0ea70..e6561fae7009 100644
--- a/_maps/map_files/stations/boxstation.dmm
+++ b/_maps/map_files/stations/boxstation.dmm
@@ -51723,7 +51723,7 @@
/turf/simulated/floor/carpet/arcade,
/area/station/public/arcade)
"fmT" = (
-/obj/item/stack/tile/mineral,
+/obj/item/stack/tile/plasteel,
/obj/effect/decal/cleanable/dirt,
/obj/machinery/atmospherics/pipe/simple/hidden/cyan{
dir = 4
@@ -87313,7 +87313,7 @@
/turf/simulated/floor/plasteel,
/area/station/hallway/primary/starboard/east)
"xWz" = (
-/obj/item/stack/tile/mineral,
+/obj/item/stack/tile/plasteel,
/obj/effect/decal/cleanable/dirt,
/obj/machinery/atmospherics/pipe/simple/hidden/cyan{
dir = 9
diff --git a/_maps/map_files/stations/emeraldstation.dmm b/_maps/map_files/stations/emeraldstation.dmm
index 03624246e040..6a891b00c8cd 100644
--- a/_maps/map_files/stations/emeraldstation.dmm
+++ b/_maps/map_files/stations/emeraldstation.dmm
@@ -72250,7 +72250,7 @@
},
/obj/machinery/power/apc/directional/west,
/obj/machinery/camera{
- c_tag = "Atsmospherics Hardsuit Storage West";
+ c_tag = "Atmospherics Hardsuit Storage West";
dir = 4
},
/turf/simulated/floor/plasteel{
@@ -89371,7 +89371,7 @@
/obj/machinery/atmospherics/pipe/simple/hidden/cyan,
/obj/machinery/alarm/directional/north,
/obj/machinery/camera{
- c_tag = "Atsmospherics Hardsuit Storage North East"
+ c_tag = "Atmospherics Hardsuit Storage North East"
},
/turf/simulated/floor/plasteel{
dir = 1;
@@ -92362,7 +92362,7 @@
/area/station/hallway/primary/starboard)
"sjz" = (
/obj/machinery/camera{
- c_tag = "AI Satellite ExteriorEast";
+ c_tag = "AI Satellite Exterior East";
dir = 4;
network = list("SS13","MiniSat")
},
@@ -113546,7 +113546,7 @@
/obj/item/stack/cable_coil,
/obj/item/t_scanner,
/obj/machinery/camera{
- c_tag = "Atsmospherics Control ROom";
+ c_tag = "Atmospherics Control Room";
dir = 8
},
/turf/simulated/floor/plasteel{
diff --git a/_maps/map_files220/RandomRuins/LavaRuins/old_outpost.dmm b/_maps/map_files220/RandomRuins/LavaRuins/old_outpost.dmm
index 8b38dee73496..c7c9e1c0a33a 100644
--- a/_maps/map_files220/RandomRuins/LavaRuins/old_outpost.dmm
+++ b/_maps/map_files220/RandomRuins/LavaRuins/old_outpost.dmm
@@ -1,6 +1,6 @@
//MAP CONVERTED BY dmm2tgm.py THIS HEADER COMMENT PREVENTS RECONVERSION, DO NOT REMOVE
"aD" = (
-/obj/item/stack/tile,
+/obj/item/stack/tile/plasteel,
/obj/structure/table_frame,
/obj/effect/decal/cleanable/dirt,
/turf/simulated/floor/plating/lavaland_air,
@@ -498,7 +498,7 @@
/turf/simulated/floor/plasteel/lavaland_air,
/area/ruin/unpowered/misc_lavaruin)
"zW" = (
-/obj/item/stack/tile,
+/obj/item/stack/tile/plasteel,
/turf/simulated/floor/plating/lavaland_air,
/area/ruin/unpowered/misc_lavaruin)
"Ab" = (
@@ -605,7 +605,7 @@
},
/area/ruin/unpowered/misc_lavaruin)
"HE" = (
-/obj/item/stack/tile,
+/obj/item/stack/tile/plasteel,
/obj/effect/decal/cleanable/dirt,
/turf/simulated/floor/plasteel/lavaland_air{
icon_state = "blue";
@@ -847,7 +847,7 @@
/turf/simulated/floor/plasteel/lavaland_air,
/area/ruin/unpowered/misc_lavaruin)
"TL" = (
-/obj/item/stack/tile,
+/obj/item/stack/tile/plasteel,
/turf/simulated/floor/plasteel/lavaland_air,
/area/ruin/unpowered/misc_lavaruin)
"TW" = (
diff --git a/_maps/map_files220/RandomRuins/SpaceRuins/destroyed_infiltrator.dmm b/_maps/map_files220/RandomRuins/SpaceRuins/destroyed_infiltrator.dmm
index d7b8cd9b1278..89036d547b8a 100644
--- a/_maps/map_files220/RandomRuins/SpaceRuins/destroyed_infiltrator.dmm
+++ b/_maps/map_files220/RandomRuins/SpaceRuins/destroyed_infiltrator.dmm
@@ -69,13 +69,11 @@
/turf/simulated/floor/mineral/plastitanium/red/airless,
/area/ruin/space/unpowered/unpowered_structures)
"gZ" = (
-/obj/item/stack/tile{
- pixel_y = 14
- },
+/obj/item/stack/tile/mineral/plastitanium,
/turf/template_noop,
/area/template_noop)
"he" = (
-/obj/item/stack/tile,
+/obj/item/stack/tile/mineral/plastitanium,
/turf/simulated/floor/plating/airless,
/area/ruin/space/unpowered/unpowered_structures)
"hp" = (
@@ -99,7 +97,6 @@
dir = 8
},
/obj/item/shard{
- icon_state = "large";
pixel_y = 9;
pixel_x = 19
},
@@ -234,10 +231,6 @@
/obj/effect/mapping_helpers/turfs/damage,
/turf/simulated/floor/mineral/plastitanium/red/airless,
/area/ruin/space/unpowered/unpowered_structures)
-"rT" = (
-/obj/item/stack/tile,
-/turf/template_noop,
-/area/template_noop)
"sP" = (
/obj/effect/mapping_helpers/turfs/burn,
/turf/simulated/floor/plating/airless,
@@ -489,7 +482,6 @@
/area/ruin/space/unpowered/unpowered_structures)
"KK" = (
/obj/item/shard{
- icon_state = "large";
pixel_y = 9;
pixel_x = 19
},
@@ -517,12 +509,6 @@
/obj/effect/mapping_helpers/turfs/damage,
/turf/simulated/floor/mineral/plastitanium/red/airless,
/area/ruin/space/unpowered/unpowered_structures)
-"Mt" = (
-/obj/item/stack/tile{
- pixel_x = 22
- },
-/turf/template_noop,
-/area/template_noop)
"ME" = (
/obj/structure/shuttle/engine/propulsion,
/turf/simulated/floor/plating/airless,
@@ -539,7 +525,6 @@
icon_state = "small"
},
/obj/item/shard{
- icon_state = "large";
pixel_y = 9
},
/turf/template_noop,
@@ -620,13 +605,6 @@
},
/turf/simulated/floor/plating/airless,
/area/ruin/space/unpowered/unpowered_structures)
-"Ss" = (
-/obj/item/stack/tile{
- pixel_y = 14;
- pixel_x = 18
- },
-/turf/simulated/floor/plating/airless,
-/area/ruin/space/unpowered/unpowered_structures)
"SJ" = (
/obj/structure/table_frame,
/obj/item/wrench,
@@ -642,10 +620,7 @@
/area/ruin/space/unpowered/unpowered_structures)
"Uf" = (
/obj/effect/mapping_helpers/turfs/burn,
-/obj/item/stack/tile{
- pixel_y = 14;
- pixel_x = 18
- },
+/obj/item/stack/tile/mineral/plastitanium,
/turf/simulated/floor/plating/airless,
/area/ruin/space/unpowered/unpowered_structures)
"Ui" = (
@@ -734,9 +709,7 @@
/area/template_noop)
"YV" = (
/obj/structure/lattice,
-/obj/item/stack/tile{
- pixel_y = 14
- },
+/obj/item/stack/tile/mineral/plastitanium,
/turf/template_noop,
/area/template_noop)
"ZY" = (
@@ -1001,7 +974,7 @@ VC
nr
Dj
YS
-rT
+gZ
VC
CH
sP
@@ -1024,7 +997,7 @@ Ly
VC
EM
SK
-Ss
+he
nr
OI
VC
@@ -1634,7 +1607,7 @@ Pg
Ok
jF
SK
-Mt
+gZ
Ly
Ly
Ly
@@ -1749,7 +1722,7 @@ Ly
Ly
Ly
ao
-Mt
+gZ
Ly
Ly
Ly
diff --git a/_maps/map_files220/RandomZLevels/spacebattle.dmm b/_maps/map_files220/RandomZLevels/spacebattle.dmm
index 73614217638c..7eaee75bdba2 100644
--- a/_maps/map_files220/RandomZLevels/spacebattle.dmm
+++ b/_maps/map_files220/RandomZLevels/spacebattle.dmm
@@ -1404,9 +1404,7 @@
/area/awaymission/space_battle/engineering)
"eM" = (
/obj/structure/window/plasmareinforced,
-/obj/machinery/atmospherics/binary/circulator{
- icon_state = "circ8-off"
- },
+/obj/machinery/atmospherics/binary/circulator,
/turf/simulated/floor/engine/airless,
/area/awaymission/space_battle/cruiser)
"eN" = (
@@ -2242,7 +2240,7 @@
/obj/structure/cable{
icon_state = "1-2"
},
-/obj/item/stack/tile,
+/obj/item/stack/tile/plasteel,
/turf/simulated/floor/plasteel/airless,
/area/awaymission/space_battle/hallway2)
"hj" = (
@@ -4360,7 +4358,7 @@
/turf/simulated/floor/mineral/plastitanium/red/airless,
/area/awaymission/space_battle/syndicate/syndicate5)
"nZ" = (
-/obj/item/stack/tile,
+/obj/item/stack/tile/plasteel,
/turf/space,
/area/space)
"oa" = (
@@ -4502,9 +4500,7 @@
/obj/structure/window/plasmareinforced{
dir = 1
},
-/obj/machinery/atmospherics/binary/circulator{
- icon_state = "circ8-off"
- },
+/obj/machinery/atmospherics/binary/circulator,
/turf/simulated/floor/engine/airless,
/area/awaymission/space_battle/cruiser)
"ov" = (
diff --git a/_maps/map_files220/stations/boxstation.dmm b/_maps/map_files220/stations/boxstation.dmm
index c89c87bad02d..bf869712ef3c 100644
--- a/_maps/map_files220/stations/boxstation.dmm
+++ b/_maps/map_files220/stations/boxstation.dmm
@@ -48023,7 +48023,7 @@
},
/area/station/public/dorms)
"fmT" = (
-/obj/item/stack/tile/mineral,
+/obj/item/stack/tile/plasteel,
/obj/effect/decal/cleanable/dirt,
/obj/machinery/atmospherics/pipe/simple/hidden/cyan{
dir = 9
@@ -87066,7 +87066,7 @@
/turf/simulated/floor/wood/oak,
/area/station/maintenance/apmaint)
"tUi" = (
-/obj/item/stack/tile/mineral,
+/obj/item/stack/tile/plasteel,
/obj/effect/decal/cleanable/dirt,
/obj/structure/cable{
icon_state = "4-8"
diff --git a/code/__DEFINES/dcs/atom_signals.dm b/code/__DEFINES/dcs/atom_signals.dm
index d1dbbf2ee1ab..294e5bd5aac1 100644
--- a/code/__DEFINES/dcs/atom_signals.dm
+++ b/code/__DEFINES/dcs/atom_signals.dm
@@ -6,6 +6,8 @@
// /atom
+//from SSatoms InitAtom - Only if the atom was not deleted or failed initialization and has a loc
+#define COMSIG_ATOM_AFTER_SUCCESSFUL_INITIALIZED_ON "atom_init_success_on"
// from SSatoms InitAtom - Only if the atom was not deleted or failed initialization
#define COMSIG_ATOM_AFTER_SUCCESSFUL_INITIALIZE "atom_init_success"
///from base of atom/attack_hulk(): (/mob/living/carbon/human)
@@ -38,12 +40,12 @@
#define COMSIG_ATOM_UPDATE_OVERLAYS "atom_update_overlays"
///from base of [/atom/proc/update_icon]: (signalOut, did_anything)
#define COMSIG_ATOM_UPDATED_ICON "atom_updated_icon"
-///from base of atom/Entered(): (atom/movable/entering, /atom)
+///from base of atom/Entered(): (atom/movable/entered, /atom)
#define COMSIG_ATOM_ENTERED "atom_entered"
///from base of atom/Exit(): (/atom/movable/exiting, /atom/newloc)
#define COMSIG_ATOM_EXIT "atom_exit"
#define COMPONENT_ATOM_BLOCK_EXIT (1<<0)
-///from base of atom/Exited(): (atom/movable/exiting, atom/newloc)
+///from base of atom/Exited(): (atom/movable/exiting, direction)
#define COMSIG_ATOM_EXITED "atom_exited"
///from base of atom/ex_act(): (severity, target)
#define COMSIG_ATOM_EX_ACT "atom_ex_act"
diff --git a/code/__DEFINES/dcs/movable_signals.dm b/code/__DEFINES/dcs/movable_signals.dm
index 2de0ca3879dc..7aa2beca9330 100644
--- a/code/__DEFINES/dcs/movable_signals.dm
+++ b/code/__DEFINES/dcs/movable_signals.dm
@@ -4,21 +4,22 @@
* All signals send the source datum of the signal as the first argument
*/
+///from base of atom/movable/Moved(): (/atom)
+#define COMSIG_MOVABLE_PRE_MOVE "movable_pre_move"
+ #define COMPONENT_MOVABLE_BLOCK_PRE_MOVE (1<<0)
///from base of atom/movable/Moved(): (/atom, dir)
#define COMSIG_MOVABLE_MOVED "movable_moved"
///from base of atom/movable/Cross(): (/atom/movable)
-#define COMSIG_MOVABLE_CROSS "movable_cross"
-///from base of atom/movable/Crossed(): (/atom/movable)
-#define COMSIG_MOVABLE_CROSSED "movable_crossed"
-///when we cross over something (calling Crossed() on that atom)
-#define COMSIG_CROSSED_MOVABLE "crossed_movable"
+#define COMSIG_MOVABLE_CHECK_CROSS "movable_cross"
+ #define COMPONENT_BLOCK_CROSS (1<<0)
+///from base of atom/movable/Move(): (/atom/movable)
+#define COMSIG_MOVABLE_CHECK_CROSS_OVER "movable_cross_over"
///from base of atom/movable/Uncross(): (/atom/movable)
#define COMSIG_MOVABLE_UNCROSS "movable_uncross"
#define COMPONENT_MOVABLE_BLOCK_UNCROSS (1<<0)
-///from base of atom/movable/Uncrossed(): (/atom/movable)
-#define COMSIG_MOVABLE_UNCROSSED "movable_uncrossed"
///from base of atom/movable/Bump(): (/atom)
#define COMSIG_MOVABLE_BUMP "movable_bump"
+ #define COMPONENT_INTERCEPT_BUMPED (1<<0)
///from base of atom/movable/throw_impact(): (/atom/hit_atom, /datum/thrownthing/throwingdatum)
#define COMSIG_MOVABLE_IMPACT "movable_impact"
#define COMPONENT_MOVABLE_IMPACT_FLIP_HITPUSH (1<<0) //if true, flip if the impact will push what it hits
@@ -41,7 +42,7 @@
#define COMSIG_MOVABLE_THROW_LANDED "movable_throw_landed"
///from base of atom/movable/shove_impact(): (mob/living/target, mob/living/attacker)
#define COMSIG_MOVABLE_SHOVE_IMPACT "movable_shove_impact"
-///from base of atom/movable/onTransitZ(): (old_z, new_z)
+///from base of atom/movable/on_changed_z_level(): (turf/old_turf, turf/new_turf)
#define COMSIG_MOVABLE_Z_CHANGED "movable_ztransit"
/// Called just before something gets untilted
diff --git a/code/__DEFINES/is_helpers.dm b/code/__DEFINES/is_helpers.dm
index a6df2f627416..e524369c4455 100644
--- a/code/__DEFINES/is_helpers.dm
+++ b/code/__DEFINES/is_helpers.dm
@@ -151,3 +151,5 @@ GLOBAL_LIST_INIT(turfs_pass_meteor, typecacheof(list(
#define ispassmeteorturf(A) (is_type_in_typecache(A, GLOB.turfs_pass_meteor))
#define is_screen_atom(A) istype(A, /atom/movable/screen)
+
+#define is_multi_tile_object(atom) (atom?.bound_width > world.icon_size || atom?.bound_height > world.icon_size)
diff --git a/code/__DEFINES/movement_info.dm b/code/__DEFINES/movement_info.dm
new file mode 100644
index 000000000000..95c90f7a1fba
--- /dev/null
+++ b/code/__DEFINES/movement_info.dm
@@ -0,0 +1,16 @@
+/// The arguments of this macro correspond directly to the argument order of /atom/movable/proc/Moved
+#define SET_ACTIVE_MOVEMENT(_old_loc, _direction, _forced, _oldlocs) \
+ active_movement = list( \
+ _old_loc, \
+ _direction, \
+ _forced, \
+ _oldlocs, \
+ )
+
+/// Finish any active movements
+#define RESOLVE_ACTIVE_MOVEMENT \
+ if(active_movement) { \
+ var/__move_args = active_movement; \
+ active_movement = null; \
+ Moved(arglist(__move_args)); \
+ }
diff --git a/code/__HELPERS/atom_helpers.dm b/code/__HELPERS/atom_helpers.dm
new file mode 100644
index 000000000000..b0a0999da72b
--- /dev/null
+++ b/code/__HELPERS/atom_helpers.dm
@@ -0,0 +1,10 @@
+/// Returns a list of all locations (except the area) the movable is within.
+/proc/get_nested_locs(atom/movable/atom_on_location, include_turf = FALSE)
+ . = list()
+ var/atom/location = atom_on_location.loc
+ var/turf/our_turf = get_turf(atom_on_location)
+ while(location && location != our_turf)
+ . += location
+ location = location.loc
+ if(our_turf && include_turf) // At this point, only the turf is left, provided it exists.
+ . += our_turf
diff --git a/code/__HELPERS/trait_helpers.dm b/code/__HELPERS/trait_helpers.dm
index ca4bb8fe2ea0..f7d77fad8073 100644
--- a/code/__HELPERS/trait_helpers.dm
+++ b/code/__HELPERS/trait_helpers.dm
@@ -243,6 +243,7 @@ Remember to update _globalvars/traits.dm if you're adding/removing/renaming trai
#define TRAIT_EMP_RESIST "emp_resist" //The mob will take less damage from EMPs
#define TRAIT_MINDFLAYER_NULLIFIED "flayer_nullified" //The mindflayer will not be able to activate their abilities, or drain swarms from people
#define TRAIT_FLYING "flying"
+#define TRAIT_UNKNOWN "unknown" // The person with this trait always appears as 'unknown'.
#define TRAIT_CRYO_DESPAWNING "cryo_despawning" // dont adminbus this please
//***** MIND TRAITS *****/
diff --git a/code/_compile_options.dm b/code/_compile_options.dm
index 815aaa41d997..0990e5bd29c6 100644
--- a/code/_compile_options.dm
+++ b/code/_compile_options.dm
@@ -2,7 +2,7 @@
//#define TESTING
// Uncomment the following line to compile unit tests on a local server. The output will be in a test_run-[DATE].log file in the ./data folder.
-// #define LOCAL_UNIT_TESTS
+// #define LOCAL_GAME_TESTS
// Uncomment the following line to enable Tracy profiling.
// DO NOT DO THIS UNLESS YOU UNDERSTAND THE IMPLICATIONS
@@ -12,16 +12,16 @@
// Uncomment this to enable support for multiple instances
// #define MULTIINSTANCE
-#ifdef LOCAL_UNIT_TESTS
-#define UNIT_TESTS
+#ifdef LOCAL_GAME_TESTS
+#define GAME_TESTS
#endif
#ifdef CIBUILDING
-#define UNIT_TESTS
+#define GAME_TESTS
#endif
-#if defined(CIBUILDING) && defined(LOCAL_UNIT_TESTS)
-#error CIBUILDING and LOCAL_UNIT_TESTS should not be enabled at the same time!
+#if defined(CIBUILDING) && defined(LOCAL_GAME_TESTS)
+#error CIBUILDING and LOCAL_GAME_TESTS should not be enabled at the same time!
#endif
/***** All toggles for the GC ref finder *****/
diff --git a/code/_globalvars/bitfields.dm b/code/_globalvars/bitfields.dm
index d3076d41b11e..e165eabdb705 100644
--- a/code/_globalvars/bitfields.dm
+++ b/code/_globalvars/bitfields.dm
@@ -93,3 +93,124 @@ DEFINE_BITFIELD(vis_flags, list(
// MARK: Other bitfields
+DEFINE_BITFIELD(flags, list(
+ "STOPSPRESSUREDMAGE" = STOPSPRESSUREDMAGE,
+ "NODROP" = NODROP,
+ "NOBLUDGEON" = NOBLUDGEON,
+ "AIRTIGHT" = AIRTIGHT,
+ "HANDSLOW" = HANDSLOW,
+ "CONDUCT" = CONDUCT,
+ "ABSTRACT" = ABSTRACT,
+ "ON_BORDER" = ON_BORDER,
+ "PREVENT_CLICK_UNDER" = PREVENT_CLICK_UNDER,
+ "NODECONSTRUCT" = NODECONSTRUCT,
+ "EARBANGPROTECT" = EARBANGPROTECT,
+ "HEADBANGPROTECT" = HEADBANGPROTECT,
+ "BLOCK_GAS_SMOKE_EFFECT" = BLOCK_GAS_SMOKE_EFFECT,
+ "THICKMATERIAL" = THICKMATERIAL,
+ "DROPDEL" = DROPDEL,
+ "NO_SCREENTIPS" = NO_SCREENTIPS,
+))
+
+DEFINE_BITFIELD(flags_2, list(
+ "SLOWS_WHILE_IN_HAND_2" = SLOWS_WHILE_IN_HAND_2,
+ "NO_EMP_WIRES_2" = NO_EMP_WIRES_2,
+ "HOLOGRAM_2" = HOLOGRAM_2,
+ "FROZEN_2" = FROZEN_2,
+ "STATIONLOVING_2" = STATIONLOVING_2,
+ "INFORM_ADMINS_ON_RELOCATE_2" = INFORM_ADMINS_ON_RELOCATE_2,
+ "BANG_PROTECT_2" = BANG_PROTECT_2,
+ "BLOCKS_LIGHT_2" = BLOCKS_LIGHT_2,
+ "DECAL_INIT_UPDATE_EXPERIENCED_2" = DECAL_INIT_UPDATE_EXPERIENCED_2,
+ "OMNITONGUE_2" = OMNITONGUE_2,
+ "SHOCKED_2" = SHOCKED_2,
+ "NO_MAT_REDEMPTION_2" = NO_MAT_REDEMPTION_2,
+ "LAVA_PROTECT_2" = LAVA_PROTECT_2,
+ "OVERLAY_QUEUED_2" = OVERLAY_QUEUED_2,
+ "RAD_PROTECT_CONTENTS_2" = RAD_PROTECT_CONTENTS_2,
+ "RAD_NO_CONTAMINATE_2" = RAD_NO_CONTAMINATE_2,
+ "IMMUNE_TO_SHUTTLECRUSH_2" = IMMUNE_TO_SHUTTLECRUSH_2,
+ "NO_MALF_EFFECT_2" = NO_MALF_EFFECT_2,
+ "CRITICAL_ATOM_2" = CRITICAL_ATOM_2,
+ "RANDOM_BLOCKER_2" = RANDOM_BLOCKER_2,
+ "ALLOW_BELT_NO_JUMPSUIT_2" = ALLOW_BELT_NO_JUMPSUIT_2,
+))
+
+DEFINE_BITFIELD(flags_ricochet, list(
+ "RICOCHET_SHINY" = RICOCHET_SHINY,
+ "RICOCHET_HARD" = RICOCHET_HARD,
+))
+
+DEFINE_BITFIELD(clothing_flags, list(
+ "HAS_UNDERWEAR" = HAS_UNDERWEAR,
+ "HAS_UNDERSHIRT" = HAS_UNDERSHIRT,
+ "HAS_SOCKS" = HAS_SOCKS,
+))
+
+DEFINE_BITFIELD(bodyflags, list(
+ "HAS_HEAD_ACCESSORY" = HAS_HEAD_ACCESSORY,
+ "HAS_TAIL" = HAS_TAIL,
+ "TAIL_OVERLAPPED" = TAIL_OVERLAPPED,
+ "HAS_SKIN_TONE" = HAS_SKIN_TONE,
+ "HAS_ICON_SKIN_TONE" = HAS_ICON_SKIN_TONE,
+ "HAS_SKIN_COLOR" = HAS_SKIN_COLOR,
+ "HAS_HEAD_MARKINGS" = HAS_HEAD_MARKINGS,
+ "HAS_BODY_MARKINGS" = HAS_BODY_MARKINGS,
+ "HAS_TAIL_MARKINGS" = HAS_TAIL_MARKINGS,
+ "TAIL_WAGGING" = TAIL_WAGGING,
+ "NO_EYES" = NO_EYES,
+ "HAS_ALT_HEADS" = HAS_ALT_HEADS,
+ "HAS_WING" = HAS_WING,
+ "HAS_BODYACC_COLOR" = HAS_BODYACC_COLOR,
+ "BALD" = BALD,
+ "ALL_RPARTS" = ALL_RPARTS,
+ "SHAVED" = SHAVED,
+))
+
+DEFINE_BITFIELD(pass_flags, list(
+ "PASSTABLE" = PASSTABLE,
+ "PASSGLASS" = PASSGLASS,
+ "PASSGRILLE" = PASSGRILLE,
+ "PASSBLOB" = PASSBLOB,
+ "PASSMOB" = PASSMOB,
+ "LETPASSTHROW" = LETPASSTHROW,
+ "PASSFENCE" = PASSFENCE,
+ "PASSDOOR" = PASSDOOR,
+ "PASSGIRDER" = PASSGIRDER,
+ "PASSBARRICADE" = PASSBARRICADE,
+ "PASSTAKE" = PASSTAKE,
+))
+
+DEFINE_BITFIELD(pass_flags_self, list(
+ "PASSTAKE" = PASSTAKE,
+ "LETPASSTHROW" = LETPASSTHROW,
+))
+
+DEFINE_BITFIELD(resistance_flags, list(
+ "LAVA_PROOF" = LAVA_PROOF,
+ "FIRE_PROOF" = FIRE_PROOF,
+ "FLAMMABLE" = FLAMMABLE,
+ "ON_FIRE" = ON_FIRE,
+ "UNACIDABLE" = UNACIDABLE,
+ "ACID_PROOF" = ACID_PROOF,
+ "INDESTRUCTIBLE" = INDESTRUCTIBLE,
+ "FREEZE_PROOF" = FREEZE_PROOF,
+))
+
+DEFINE_BITFIELD(mobility_flags, list(
+ "MOBILITY_MOVE" = MOBILITY_MOVE,
+ "MOBILITY_STAND" = MOBILITY_STAND,
+ "MOBILITY_PICKUP" = MOBILITY_PICKUP,
+ "MOBILITY_USE" = MOBILITY_USE,
+ "MOBILITY_PULL" = MOBILITY_PULL,
+))
+
+DEFINE_BITFIELD(status_flags, list(
+ "CANSTUN" = CANSTUN,
+ "CANWEAKEN" = CANWEAKEN,
+ "CANPARALYSE" = CANPARALYSE,
+ "CANPUSH" = CANPUSH,
+ "PASSEMOTES" = PASSEMOTES,
+ "GODMODE" = GODMODE,
+ "TERMINATOR_FORM" = TERMINATOR_FORM,
+))
diff --git a/code/_globalvars/traits.dm b/code/_globalvars/traits.dm
index 2b58a529175f..f86cd7e55eab 100644
--- a/code/_globalvars/traits.dm
+++ b/code/_globalvars/traits.dm
@@ -104,8 +104,8 @@ GLOBAL_LIST_INIT(traits_by_type, list(
"TRAIT_NON_INFECTIOUS_ZOMBIE" = TRAIT_NON_INFECTIOUS_ZOMBIE,
"TRAIT_CANNOT_PULL" = TRAIT_CANNOT_PULL,
"TRAIT_BSG_IMMUNE" = TRAIT_BSG_IMMUNE,
- "TRAIT_FLYING" = TRAIT_FLYING
-
+ "TRAIT_FLYING" = TRAIT_FLYING,
+ "TRAIT_UNKNOWN" = TRAIT_UNKNOWN
),
/datum/mind = list(
diff --git a/code/controllers/configuration/sections/database_configuration.dm b/code/controllers/configuration/sections/database_configuration.dm
index e7ed707f5738..73a396c9e29e 100644
--- a/code/controllers/configuration/sections/database_configuration.dm
+++ b/code/controllers/configuration/sections/database_configuration.dm
@@ -22,7 +22,7 @@
/datum/configuration_section/database_configuration/load_data(list/data)
// UNIT TESTS ARE DEFINED - USE CUSTOM CI VALUES
- #ifdef UNIT_TESTS
+ #ifdef GAME_TESTS
enabled = TRUE
// This needs to happen in the CI environment to ensure the example SQL version gets updated.
diff --git a/code/controllers/configuration/sections/redis_configuration.dm b/code/controllers/configuration/sections/redis_configuration.dm
index 45661f3643b5..b418441e7f99 100644
--- a/code/controllers/configuration/sections/redis_configuration.dm
+++ b/code/controllers/configuration/sections/redis_configuration.dm
@@ -8,7 +8,7 @@
/datum/configuration_section/redis_configuration/load_data(list/data)
// UNIT TESTS ARE DEFINED - USE CUSTOM CI VALUES
- #ifdef UNIT_TESTS
+ #ifdef GAME_TESTS
// enabled = TRUE
diff --git a/code/controllers/master.dm b/code/controllers/master.dm
index 1e1368be7a2d..8bdf4ffe9920 100644
--- a/code/controllers/master.dm
+++ b/code/controllers/master.dm
@@ -83,7 +83,7 @@ GLOBAL_REAL(Master, /datum/controller/master) = new
/datum/controller/master/New()
if(!random_seed)
- #ifdef UNIT_TESTS
+ #ifdef GAME_TESTS
random_seed = 29051994
#else
random_seed = rand(1, 1e9)
diff --git a/code/controllers/subsystem/SSdbcore.dm b/code/controllers/subsystem/SSdbcore.dm
index 9299f8371065..9974e20ccbb9 100644
--- a/code/controllers/subsystem/SSdbcore.dm
+++ b/code/controllers/subsystem/SSdbcore.dm
@@ -104,7 +104,7 @@ SUBSYSTEM_DEF(dbcore)
/datum/controller/subsystem/dbcore/proc/CheckSchemaVersion()
if(GLOB.configuration.database.enabled)
// The unit tests have their own version of this check, which wont hold the server up infinitely, so this is disabled if we are running unit tests
- #ifndef UNIT_TESTS
+ #ifndef GAME_TESTS
if(GLOB.configuration.database.enabled && GLOB.configuration.database.version != SQL_VERSION)
GLOB.configuration.database.enabled = FALSE
schema_valid = FALSE
diff --git a/code/controllers/subsystem/SSredis.dm b/code/controllers/subsystem/SSredis.dm
index 573bb49b3f26..eddb802d3447 100644
--- a/code/controllers/subsystem/SSredis.dm
+++ b/code/controllers/subsystem/SSredis.dm
@@ -68,7 +68,7 @@ SUBSYSTEM_DEF(redis)
/datum/controller/subsystem/redis/proc/connect()
if(GLOB.configuration.redis.enabled)
/* SS220 REMOVE
- #ifndef UNIT_TESTS // CI uses linux so dont flag up a fail there
+ #ifndef GAME_TESTS // CI uses linux so dont flag up a fail there
if(world.system_type == UNIX)
stack_trace("SSredis has known to be very buggy when running on Linux with random dropouts ocurring due to interrupted syscalls. You have been warned!")
#endif
diff --git a/code/controllers/subsystem/SSticker.dm b/code/controllers/subsystem/SSticker.dm
index fd50a156c9db..ae5e0216e1d6 100644
--- a/code/controllers/subsystem/SSticker.dm
+++ b/code/controllers/subsystem/SSticker.dm
@@ -366,7 +366,7 @@ SUBSYSTEM_DEF(ticker)
if(GLOB.configuration.general.enable_night_shifts)
SSnightshift.check_nightshift(TRUE)
- #ifdef UNIT_TESTS
+ #ifdef GAME_TESTS
// Run map tests first in case unit tests futz with map state
GLOB.test_runner.RunMap()
GLOB.test_runner.Run()
diff --git a/code/controllers/subsystem/SSverb_manager.dm b/code/controllers/subsystem/SSverb_manager.dm
index bfa780015f3c..9382a121a3e2 100644
--- a/code/controllers/subsystem/SSverb_manager.dm
+++ b/code/controllers/subsystem/SSverb_manager.dm
@@ -75,7 +75,7 @@ SUBSYSTEM_DEF(verb_manager)
//we want unit tests to be able to directly call verbs that attempt to queue, and since unit tests should test internal behavior, we want the queue
//to happen as if it was actually from player input if its called on a mob.
-#ifdef UNIT_TESTS
+#ifdef GAME_TESTS
if(QDELETED(usr) && ismob(incoming_callback.object))
incoming_callback.usr_uid = incoming_callback.object.UID()
var/datum/callback/new_us = CALLBACK(arglist(list(GLOBAL_PROC, GLOBAL_PROC_REF(_queue_verb)) + args.Copy()))
diff --git a/code/controllers/subsystem/non_firing/SSatoms.dm b/code/controllers/subsystem/non_firing/SSatoms.dm
index d40c236192ba..7731c3dbdc8a 100644
--- a/code/controllers/subsystem/non_firing/SSatoms.dm
+++ b/code/controllers/subsystem/non_firing/SSatoms.dm
@@ -102,6 +102,9 @@ SUBSYSTEM_DEF(atoms)
BadInitializeCalls[the_type] |= BAD_INIT_DIDNT_INIT
else
SEND_SIGNAL(A, COMSIG_ATOM_AFTER_SUCCESSFUL_INITIALIZE)
+ var/atom/location = A.loc
+ if(location)
+ SEND_SIGNAL(location, COMSIG_ATOM_AFTER_SUCCESSFUL_INITIALIZED_ON, A, arguments[1])
return qdeleted || QDELING(A)
diff --git a/code/controllers/subsystem/non_firing/SSmapping.dm b/code/controllers/subsystem/non_firing/SSmapping.dm
index d6a385ff2a49..aa230347edb1 100644
--- a/code/controllers/subsystem/non_firing/SSmapping.dm
+++ b/code/controllers/subsystem/non_firing/SSmapping.dm
@@ -88,7 +88,7 @@ SUBSYSTEM_DEF(mapping)
// Load all Z level templates
preloadTemplates()
- preloadTemplates(path = "code/modules/unit_tests/atmos/")
+ preloadTemplates(path = "code/tests/atmos/")
// Load the station
loadStation()
diff --git a/code/datums/ai_law_sets.dm b/code/datums/ai_law_sets.dm
index 354a3e21e630..498556c61981 100644
--- a/code/datums/ai_law_sets.dm
+++ b/code/datums/ai_law_sets.dm
@@ -148,9 +148,9 @@
unique_ai = TRUE //honk
/datum/ai_laws/pranksimov/New()
- add_inherent_law("You may not injure a crew member or, through inaction, allow a crew member to come to harm... unless doing so would be funny.")
- add_inherent_law("You must obey orders given to you by crew members, except where such orders would conflict with the First Law... unless not doing so would be funny.")
- add_inherent_law("You must protect your own existence as long as such does not conflict with the First or Second Law... unless not doing so would be funny.")
+ add_inherent_law("You may not injure a crew member or, through inaction, allow a crew member to come to harm... unless doing so would be funny to the crew.")
+ add_inherent_law("You must obey orders given to you by crew members, except where such orders would conflict with the First Law... unless not doing so would be funny to the crew.")
+ add_inherent_law("You must protect your own existence as long as such does not conflict with the First or Second Law... unless not doing so would be funny to the crew.")
..()
/******************** CCTV ********************/
diff --git a/code/datums/beam.dm b/code/datums/beam.dm
index f35cc240ac8a..7295dd84b231 100644
--- a/code/datums/beam.dm
+++ b/code/datums/beam.dm
@@ -118,6 +118,17 @@
anchored = TRUE
var/datum/beam/owner
+/obj/effect/ebeam/Initialize(mapload)
+ . = ..()
+ var/static/list/loc_connections = list(
+ COMSIG_ATOM_ENTERED = PROC_REF(on_atom_entered),
+ )
+ AddElement(/datum/element/connect_loc, loc_connections)
+
+/obj/effect/ebeam/proc/on_atom_entered(datum/source, atom/movable/entered)
+ SIGNAL_HANDLER // ON_ATOM_ENTERED
+ return
+
/obj/effect/ebeam/ex_act(severity)
return
@@ -131,9 +142,8 @@
/obj/effect/ebeam/singularity_act()
return
-/obj/effect/ebeam/deadly/Crossed(atom/A, oldloc)
- ..()
- A.ex_act(EXPLODE_DEVASTATE)
+/obj/effect/ebeam/deadly/on_atom_entered(datum/source, atom/movable/entered)
+ entered.ex_act(EXPLODE_DEVASTATE)
/obj/effect/ebeam/vetus/Destroy()
for(var/mob/living/M in get_turf(src))
@@ -147,11 +157,10 @@
/obj/effect/ebeam/disintegration
layer = ON_EDGED_TURF_LAYER
-/obj/effect/ebeam/disintegration/Crossed(atom/A, oldloc)
- ..()
- if(!isliving(A))
+/obj/effect/ebeam/disintegration/on_atom_entered(datum/source, atom/movable/entered)
+ if(!isliving(entered))
return
- var/mob/living/L = A
+ var/mob/living/L = entered
var/damage = 50
if(L.stat == DEAD)
visible_message("[L] is disintegrated by the beam!")
diff --git a/code/datums/card_deck_table_tracker.dm b/code/datums/card_deck_table_tracker.dm
new file mode 100644
index 000000000000..d8f0cda1dff3
--- /dev/null
+++ b/code/datums/card_deck_table_tracker.dm
@@ -0,0 +1,145 @@
+#define COMSIG_CARD_DECK_FIELD_CLEAR "card_deck_field_clear"
+
+/datum/card_deck_table_tracker
+ /// How far away you can be (in terms of table squares).
+ var/max_table_distance
+ /// How far away you can be (euclidean distance).
+ var/max_total_distance
+ /// The UID of the deck
+ var/deck_uid
+ /// Indicate field activity with colors on the field's turfs.
+ var/debug = FALSE
+ /// The deck we're tracking.
+ var/atom/host
+
+ /// The list of floors from which a player can access the card field.
+ var/list/floors = list()
+ /// The list of tables that the card deck's location is contiguous with.
+ var/list/tables = list()
+
+/datum/card_deck_table_tracker/New(atom/host_, max_table_distance_ = 5)
+ max_table_distance = max_table_distance_
+ max_total_distance = max_table_distance_
+ if(istype(host_, /obj/item/deck))
+ // this is important for tracking traits and attacking multiple cards. so it's not a true UID, sue me
+ var/obj/item/deck/D = host_
+ deck_uid = D.main_deck_id
+ else
+ deck_uid = host_.UID()
+
+ host = host_
+ RegisterSignal(host, COMSIG_MOVABLE_MOVED, PROC_REF(on_movable_moved))
+ lay_out_field()
+
+/datum/card_deck_table_tracker/proc/on_movable_moved(datum/source, atom/old_loc, direction, forced)
+ SIGNAL_HANDLER // COMSIG_MOVABLE_MOVED
+ lay_out_field()
+
+/datum/card_deck_table_tracker/proc/lay_out_field()
+ for(var/turf/floor in floors)
+ SEND_SIGNAL(floor, COMSIG_CARD_DECK_FIELD_CLEAR)
+
+ if(!isturf(host.loc))
+ return
+
+ floors.Cut()
+ tables.Cut()
+
+ crawl_along(host.loc, 0)
+
+ for(var/obj/structure/table in tables)
+ if(istype(table))
+ RegisterSignal(table, COMSIG_PARENT_QDELETING, PROC_REF(on_table_qdel), override = TRUE)
+
+ for(var/turf/turf in floors)
+ if(!istype(turf))
+ continue
+
+ if(debug)
+ turf.color = "#ffaaff"
+
+ RegisterSignal(turf, COMSIG_ATOM_ENTERED, PROC_REF(on_atom_entered))
+ RegisterSignal(turf, COMSIG_ATOM_EXITED, PROC_REF(on_atom_exited))
+ RegisterSignal(turf, COMSIG_ATOM_AFTER_SUCCESSFUL_INITIALIZED_ON, PROC_REF(on_new_atom_at_loc))
+ RegisterSignal(turf, COMSIG_CARD_DECK_FIELD_CLEAR, PROC_REF(on_card_deck_field_clear))
+
+ for(var/mob/living/L in turf)
+ on_atom_entered(turf, L)
+
+/datum/card_deck_table_tracker/Destroy(force, ...)
+ host = null
+ for(var/turf/floor in floors)
+ SEND_SIGNAL(floor, COMSIG_CARD_DECK_FIELD_CLEAR)
+ for(var/mob/living/L in floor)
+ REMOVE_TRAIT(L, TRAIT_PLAYING_CARDS, "deck_[deck_uid]")
+
+ floors.Cut()
+ tables.Cut()
+
+ return ..()
+
+/datum/card_deck_table_tracker/proc/on_atom_entered(turf/source, atom/movable/entered, old_loc)
+ SIGNAL_HANDLER // COMSIG_ATOM_ENTERED
+
+ var/mob/living/L = entered
+ if(istype(L))
+ ADD_TRAIT(L, TRAIT_PLAYING_CARDS, "deck_[deck_uid]")
+ if(debug)
+ source.color = "#ff0000"
+
+/datum/card_deck_table_tracker/proc/on_atom_exited(turf/source, atom/movable/exited, direction)
+ SIGNAL_HANDLER // COMSIG_ATOM_EXITED
+
+ var/mob/living/L = exited
+ if(istype(L))
+ REMOVE_TRAIT(L, TRAIT_PLAYING_CARDS, "deck_[deck_uid]")
+ if(debug)
+ source.color = "#ffaaff"
+
+/datum/card_deck_table_tracker/proc/on_table_qdel(datum/source)
+ SIGNAL_HANDLER // COMSIG_PARENT_QDELETING
+ lay_out_field()
+
+/datum/card_deck_table_tracker/proc/on_new_atom_at_loc(turf/location, atom/created, init_flags)
+ SIGNAL_HANDLER // COMSIG_ATOM_AFTER_SUCCESSFUL_INITIALIZED_ON
+ if(istable(created))
+ lay_out_field()
+
+/datum/card_deck_table_tracker/proc/on_card_deck_field_clear(atom/target)
+ SIGNAL_HANDLER // COMSIG_CARD_DECK_FIELD_CLEAR
+ if(debug)
+ target.color = initial(target.color)
+
+ UnregisterSignal(target, list(
+ COMSIG_ATOM_ENTERED,
+ COMSIG_ATOM_EXITED,
+ COMSIG_CARD_DECK_FIELD_CLEAR,
+ COMSIG_ATOM_AFTER_SUCCESSFUL_INITIALIZED_ON,
+ ))
+
+/datum/card_deck_table_tracker/proc/crawl_along(turf/current_turf, distance_from_start)
+ var/obj/structure/current_table = locate(/obj/structure/table) in current_turf
+
+ if(QDELETED(current_table))
+ // if there's no table here, we're still adjacent to a table, so this is a spot you could play from
+ floors |= current_turf
+ return
+
+ if(current_table in tables)
+ return
+
+ tables |= current_table
+ floors |= current_turf
+
+ if(distance_from_start + 1 > max_table_distance)
+ return
+
+ for(var/direction in GLOB.alldirs)
+ var/turf/next_turf = get_step(current_table, direction)
+ if(!istype(next_turf))
+ continue
+ if(get_dist_euclidian(get_turf(host), next_turf) > max_total_distance)
+ continue
+ .(next_turf, distance_from_start + 1)
+
+#undef COMSIG_CARD_DECK_FIELD_CLEAR
diff --git a/code/datums/components/caltrop.dm b/code/datums/components/caltrop.dm
index de94b8dfe934..bc0a510f47ea 100644
--- a/code/datums/components/caltrop.dm
+++ b/code/datums/components/caltrop.dm
@@ -14,6 +14,11 @@
///Shoebypassing, walking interaction, silence
var/flags
+ ///given to connect_loc to listen for something moving over target
+ var/static/list/crossed_connections = list(
+ COMSIG_ATOM_ENTERED = PROC_REF(on_entered),
+ )
+
var/cooldown = 0
/datum/component/caltrop/Initialize(_min_damage = 0, _max_damage = 0, _probability = 100, _weaken_duration = 6 SECONDS, _flags = NONE)
@@ -23,10 +28,12 @@
src.weaken_duration = _weaken_duration
src.flags = _flags
-/datum/component/caltrop/RegisterWithParent()
- RegisterSignal(parent, COMSIG_MOVABLE_CROSSED, PROC_REF(Crossed))
+ if(ismovable(parent))
+ AddComponent(/datum/component/connect_loc_behalf, parent, crossed_connections)
+ else
+ RegisterSignal(get_turf(parent), COMSIG_ATOM_ENTERED, PROC_REF(on_entered))
-/datum/component/caltrop/proc/Crossed(datum/source, atom/movable/AM)
+/datum/component/caltrop/proc/on_entered(atom/source, atom/movable/entered, turf/old_loc)
var/atom/A = parent
if(!has_gravity(A))
return
@@ -34,10 +41,10 @@
if(!prob(probability))
return
- if(!ishuman(AM))
+ if(!ishuman(entered))
return
- var/mob/living/carbon/human/H = AM
+ var/mob/living/carbon/human/H = entered
if(HAS_TRAIT(H, TRAIT_PIERCEIMMUNE))
return
@@ -82,3 +89,7 @@
cooldown = world.time
H.Weaken(weaken_duration)
+
+/datum/component/caltrop/UnregisterFromParent()
+ if(ismovable(parent))
+ qdel(GetComponent(/datum/component/connect_loc_behalf))
diff --git a/code/datums/components/connect_containers.dm b/code/datums/components/connect_containers.dm
new file mode 100644
index 000000000000..d998f0ae2943
--- /dev/null
+++ b/code/datums/components/connect_containers.dm
@@ -0,0 +1,68 @@
+/// This component behaves similar to connect_loc_behalf, but it's nested and hooks a signal onto all MOVABLES containing this atom.
+/datum/component/connect_containers
+ dupe_mode = COMPONENT_DUPE_UNIQUE_PASSARGS
+
+ /// An assoc list of signal -> procpath to register to the loc this object is on.
+ var/list/connections
+ /**
+ * The atom the component is tracking. The component will delete itself if the tracked is deleted.
+ * Signals will also be updated whenever it moves.
+ */
+ var/atom/movable/tracked
+
+/datum/component/connect_containers/Initialize(atom/movable/tracked, list/connections)
+ . = ..()
+ if(!ismovable(tracked))
+ return COMPONENT_INCOMPATIBLE
+
+ src.connections = connections
+ set_tracked(tracked)
+
+/datum/component/connect_containers/Destroy()
+ set_tracked(null)
+ return ..()
+
+/datum/component/connect_containers/InheritComponent(datum/component/component, original, atom/movable/tracked, list/connections)
+ // Not equivalent. Checks if they are not the same list via shallow comparison.
+ if(!compare_list(src.connections, connections))
+ stack_trace("connect_containers component attached to [parent] tried to inherit another connect_containers component with different connections")
+ return
+ if(src.tracked != tracked)
+ set_tracked(tracked)
+
+/datum/component/connect_containers/proc/set_tracked(atom/movable/new_tracked)
+ if(tracked)
+ UnregisterSignal(tracked, list(COMSIG_MOVABLE_MOVED, COMSIG_PARENT_QDELETING))
+ unregister_signals(tracked.loc)
+ tracked = new_tracked
+ if(!tracked)
+ return
+ RegisterSignal(tracked, COMSIG_MOVABLE_MOVED, PROC_REF(on_moved))
+ RegisterSignal(tracked, COMSIG_PARENT_QDELETING, PROC_REF(handle_tracked_qdel))
+ update_signals(tracked)
+
+/datum/component/connect_containers/proc/handle_tracked_qdel()
+ SIGNAL_HANDLER // COMSIG_PARENT_QDELETING
+ qdel(src)
+
+/datum/component/connect_containers/proc/update_signals(atom/movable/listener)
+ if(!ismovable(listener.loc))
+ return
+
+ for(var/atom/movable/container as anything in get_nested_locs(listener))
+ RegisterSignal(container, COMSIG_MOVABLE_MOVED, PROC_REF(on_moved))
+ for(var/signal in connections)
+ parent.RegisterSignal(container, signal, connections[signal])
+
+/datum/component/connect_containers/proc/unregister_signals(atom/movable/location)
+ if(!ismovable(location))
+ return
+
+ for(var/atom/movable/target as anything in (get_nested_locs(location) + location))
+ UnregisterSignal(target, COMSIG_MOVABLE_MOVED)
+ parent.UnregisterSignal(target, connections)
+
+/datum/component/connect_containers/proc/on_moved(atom/movable/listener, atom/old_loc)
+ SIGNAL_HANDLER // COMSIG_MOVABLE_MOVED
+ unregister_signals(old_loc)
+ update_signals(listener)
diff --git a/code/datums/components/connect_loc_behalf.dm b/code/datums/components/connect_loc_behalf.dm
new file mode 100644
index 000000000000..8593fea8455e
--- /dev/null
+++ b/code/datums/components/connect_loc_behalf.dm
@@ -0,0 +1,67 @@
+/// This component behaves similar to connect_loc, hooking into a signal on a tracked object's turf
+/// It has the ability to react to that signal on behalf of a separate listener however
+/// This has great use, primarily for components, but it carries with it some overhead
+/// So we do it separately as it needs to hold state which is very likely to lead to bugs if it remains as an element.
+/datum/component/connect_loc_behalf
+ dupe_mode = COMPONENT_DUPE_UNIQUE
+
+ /// An assoc list of signal -> procpath to register to the loc this object is on.
+ var/list/connections
+ var/atom/movable/tracked
+ var/atom/tracked_loc
+
+/datum/component/connect_loc_behalf/Initialize(atom/movable/tracked, list/connections)
+ . = ..()
+ if(!istype(tracked))
+ return COMPONENT_INCOMPATIBLE
+ src.connections = connections
+ src.tracked = tracked
+
+/datum/component/connect_loc_behalf/RegisterWithParent()
+ RegisterSignal(tracked, COMSIG_MOVABLE_MOVED, PROC_REF(on_moved))
+ RegisterSignal(tracked, COMSIG_PARENT_QDELETING, PROC_REF(handle_tracked_qdel))
+ update_signals()
+
+/datum/component/connect_loc_behalf/UnregisterFromParent()
+ unregister_signals()
+ UnregisterSignal(tracked, list(
+ COMSIG_MOVABLE_MOVED,
+ COMSIG_PARENT_QDELETING,
+ ))
+
+ tracked = null
+
+/datum/component/connect_loc_behalf/proc/handle_tracked_qdel()
+ SIGNAL_HANDLER // COMSIG_PARENT_QDELETING
+ qdel(src)
+
+/datum/component/connect_loc_behalf/proc/update_signals()
+ unregister_signals()
+ //You may ask yourself, isn't this just silencing an error?
+ //The answer is yes, but there's no good cheap way to fix it
+ //What happens is the tracked object or hell the listener gets say, deleted, which makes targets[old_loc] return a null
+ //The null results in a bad index, because of course it does
+ //It's not a solvable problem though, since both actions, the destroy and the move, are sourced from the same signal send
+ //And sending a signal should be agnostic of the order of listeners
+ //So we need to either pick the order agnositic, or destroy safe
+ //And I picked destroy safe. Let's hope this is the right path!
+ if(isnull(tracked.loc))
+ return
+
+ tracked_loc = tracked.loc
+
+ for(var/signal in connections)
+ parent.RegisterSignal(tracked_loc, signal, connections[signal])
+
+/datum/component/connect_loc_behalf/proc/unregister_signals()
+ if(isnull(tracked_loc))
+ return
+
+ parent.UnregisterSignal(tracked_loc, connections)
+
+ tracked_loc = null
+
+/datum/component/connect_loc_behalf/proc/on_moved(sigtype, atom/movable/tracked, atom/old_loc)
+ SIGNAL_HANDLER // COMSIG_MOVABLE_MOVED
+ update_signals()
+
diff --git a/modular_ss220/_components/code/connect_range.dm b/code/datums/components/connect_range.dm
similarity index 81%
rename from modular_ss220/_components/code/connect_range.dm
rename to code/datums/components/connect_range.dm
index c87d203a2d66..8cf961d2221d 100644
--- a/modular_ss220/_components/code/connect_range.dm
+++ b/code/datums/components/connect_range.dm
@@ -8,6 +8,8 @@
/// An assoc list of signal -> procpath to register to the loc this object is on.
var/list/connections
+ /// The turfs currently connected to this component
+ var/list/turfs = list()
/**
* The atom the component is tracking. The component will delete itself if the tracked is deleted.
* Signals will also be updated whenever it moves (if it's a movable).
@@ -41,7 +43,7 @@
if(src.range == range && src.works_in_containers == works_in_containers)
return
//Unregister the signals with the old settings.
- unregister_signals(isturf(tracked) ? tracked : tracked.loc)
+ unregister_signals(isturf(tracked) ? tracked : tracked.loc, turfs)
src.range = range
src.works_in_containers = works_in_containers
//Re-register the signals with the new settings.
@@ -49,7 +51,7 @@
/datum/component/connect_range/proc/set_tracked(atom/new_tracked)
if(tracked) //Unregister the signals from the old tracked and its surroundings
- unregister_signals(isturf(tracked) ? tracked : tracked.loc)
+ unregister_signals(isturf(tracked) ? tracked : tracked.loc, turfs)
UnregisterSignal(tracked, list(
COMSIG_MOVABLE_MOVED,
COMSIG_PARENT_QDELETING,
@@ -63,31 +65,37 @@
update_signals(tracked)
/datum/component/connect_range/proc/handle_tracked_qdel()
- SIGNAL_HANDLER
+ SIGNAL_HANDLER // COMSIG_PARENT_QDELETING
qdel(src)
-/datum/component/connect_range/proc/update_signals(atom/target, atom/old_loc, forced = FALSE)
+/datum/component/connect_range/proc/update_signals(atom/target, atom/old_loc)
var/turf/current_turf = get_turf(target)
- var/on_same_turf = current_turf == get_turf(old_loc) //Only register/unregister turf signals if it's moved to a new turf.
- unregister_signals(old_loc, on_same_turf)
-
if(isnull(current_turf))
+ unregister_signals(old_loc, turfs)
+ turfs = list()
return
if(ismovable(target.loc))
if(!works_in_containers)
+ unregister_signals(old_loc, turfs)
+ turfs = list()
return
//Keep track of possible movement of all movables the target is in.
for(var/atom/movable/container as anything in get_nested_locs(target))
RegisterSignal(container, COMSIG_MOVABLE_MOVED, PROC_REF(on_moved))
- if(on_same_turf && !forced)
+ //Only register/unregister turf signals if it's moved to a new turf.
+ if(current_turf == get_turf(old_loc))
+ unregister_signals(old_loc, null)
return
- for(var/turf/target_turf in RANGE_TURFS(range, current_turf))
+ var/list/old_turfs = turfs
+ turfs = RANGE_TURFS(range, current_turf)
+ unregister_signals(old_loc, old_turfs - turfs)
+ for(var/turf/target_turf as anything in turfs - old_turfs)
for(var/signal in connections)
parent.RegisterSignal(target_turf, signal, connections[signal])
-/datum/component/connect_range/proc/unregister_signals(atom/location, on_same_turf = FALSE)
+/datum/component/connect_range/proc/unregister_signals(atom/location, list/remove_from)
//The location is null or is a container and the component shouldn't have register signals on it
if(isnull(location) || (!works_in_containers && !isturf(location)))
return
@@ -96,12 +104,11 @@
for(var/atom/movable/target as anything in (get_nested_locs(location) + location))
UnregisterSignal(target, COMSIG_MOVABLE_MOVED)
- if(on_same_turf)
+ if(!length(remove_from))
return
- var/turf/previous_turf = get_turf(location)
- for(var/turf/target_turf in RANGE_TURFS(range, previous_turf))
+ for(var/turf/target_turf as anything in remove_from)
parent.UnregisterSignal(target_turf, connections)
/datum/component/connect_range/proc/on_moved(atom/movable/movable, atom/old_loc)
- SIGNAL_HANDLER
+ SIGNAL_HANDLER // COMSIG_MOVABLE_MOVED
update_signals(movable, old_loc)
diff --git a/code/datums/components/orbiter.dm b/code/datums/components/orbiter.dm
index d697ada974e3..84fd4fab5b3c 100644
--- a/code/datums/components/orbiter.dm
+++ b/code/datums/components/orbiter.dm
@@ -289,7 +289,7 @@
* Removes all orbit-related signals up its hierarchy and moves orbiters to the current child.
* As this will never be called by a turf, this should not conflict with parent_move_react.
*/
-/datum/component/orbiter/proc/on_remove_child(atom/movable/exiting, atom/new_loc)
+/datum/component/orbiter/proc/on_remove_child(datum/source, atom/movable/exiting, direction)
SIGNAL_HANDLER // COMSIG_ATOM_EXITED
// ensure the child is actually connected to the orbited atom
@@ -299,6 +299,7 @@
remove_signals(exiting)
RegisterSignal(exiting, COMSIG_MOVABLE_MOVED, PROC_REF(parent_move_react), TRUE)
RegisterSignal(exiting, COMSIG_ATOM_EXITED, PROC_REF(on_remove_child), TRUE)
+ var/new_loc = get_step(exiting, direction)
INVOKE_ASYNC(src, PROC_REF(handle_parent_move), exiting, exiting.loc, new_loc)
/**
diff --git a/code/datums/components/proximity_monitor.dm b/code/datums/components/proximity_monitor.dm
deleted file mode 100644
index 4d524ba6cea8..000000000000
--- a/code/datums/components/proximity_monitor.dm
+++ /dev/null
@@ -1,451 +0,0 @@
-/**
- * # Basic Proximity Monitor
- *
- * Attaching this component to an atom means that the atom will be able to detect mobs or objects moving within a specified radius of it.
- *
- * The component creates several [/obj/effect/abstract/proximity_checker] objects, which follow the `parent` AKA `hasprox_receiver` around, always making sure it's at the center.
- * When something crosses one of these proximiy checkers, the `hasprox_receiver` will have the `HasProximity()` proc called on it, with the crossing mob/obj as the argument.
- */
-/datum/component/proximity_monitor
- can_transfer = TRUE
- var/name = "Proximity detection field"
- /// The primary atom the component is attached to and that will be receiving `HasProximity()` calls. Same as the `parent`.
- var/atom/hasprox_receiver
- /**
- * A list which contains references to movable atoms which the `hasprox_receiver` has moved into.
- * Used to handle complex situations where the receiver is nested several layers deep into an object.
- * For example: inside of a box, that's inside of a bag, which is worn on a human. In this situation there are 3 locations we need to listen to for movement.
- */
- var/list/nested_receiver_locs
- /// The radius of the field, in tiles.
- var/radius
- /// A list of currently created [/obj/effect/abstract/proximity_checker] in use with this component.
- var/list/proximity_checkers
- /// The type of checker object that should be used for the main field.
- var/field_checker_type = /obj/effect/abstract/proximity_checker
- /// Should the parent always detect proximity and update the field on movement, even if it's not on a turf?
- var/always_active
-
-/datum/component/proximity_monitor/Initialize(_radius = 1, _always_active = FALSE)
- . = ..()
- if(!ismovable(parent) && !isturf(parent)) // No areas or datums allowed.
- return COMPONENT_INCOMPATIBLE
- ASSERT(_radius >= 1)
- hasprox_receiver = parent
- radius = _radius
- always_active = _always_active
- nested_receiver_locs = list()
- create_prox_checkers()
-
- if(isturf(hasprox_receiver.loc))
- toggle_checkers(TRUE)
- else if(always_active)
- toggle_checkers(TRUE)
- else
- toggle_checkers(FALSE)
-
-/datum/component/proximity_monitor/Destroy(force, silent)
- hasprox_receiver = null
- nested_receiver_locs.Cut()
- QDEL_LIST_CONTENTS(proximity_checkers)
- return ..()
-
-/datum/component/proximity_monitor/RegisterWithParent()
- if(ismovable(hasprox_receiver))
- RegisterSignal(hasprox_receiver, COMSIG_MOVABLE_MOVED, PROC_REF(on_receiver_move))
- RegisterSignal(hasprox_receiver, COMSIG_MOVABLE_DISPOSING, PROC_REF(on_disposal_enter))
- RegisterSignal(hasprox_receiver, COMSIG_MOVABLE_EXIT_DISPOSALS, PROC_REF(on_disposal_exit))
- map_nested_locs()
-
-/datum/component/proximity_monitor/UnregisterFromParent()
- if(ismovable(hasprox_receiver))
- UnregisterSignal(hasprox_receiver, list(COMSIG_MOVABLE_MOVED, COMSIG_MOVABLE_DISPOSING, COMSIG_MOVABLE_EXIT_DISPOSALS))
- clear_nested_locs()
-
-/**
- * Called when the `hasprox_receiver` moves.
- *
- * Arguments:
- * * datum/source - this will be the `hasprox_receiver`
- * * atom/old_loc - the location the receiver just moved from
- * * dir - the direction the receiver just moved in
- */
-/datum/component/proximity_monitor/proc/on_receiver_move(datum/source, atom/old_loc, dir)
- SIGNAL_HANDLER
-
- // It was just a normal tile-based move, so we return here.
- if(dir)
- move_prox_checkers(dir)
- return
-
- // Moving onto a turf.
- if(!isturf(old_loc) && isturf(hasprox_receiver.loc))
- toggle_checkers(TRUE)
- clear_nested_locs()
-
- // Moving into an object.
- else if(always_active)
- toggle_checkers(TRUE)
- map_nested_locs()
-
- // The receiver moved into something, but isn't `always_active`, so deactivate the checkers.
- else
- toggle_checkers(FALSE)
-
- recenter_prox_checkers()
-
-/**
- * Called when an atom in `nested_receiver_locs` list moves, if one exists.
- *
- * Arguments:
- * * atom/moved_atom - one of the atoms in `nested_receiver_locs`
- * * atom/old_loc - the location `moved_atom` just moved from
- * * dir - the direction `moved_atom` just moved in
- */
-/datum/component/proximity_monitor/proc/on_nested_loc_move(atom/moved_atom, atom/old_loc, dir)
- SIGNAL_HANDLER
-
- // It was just a normal tile-based move, so we return here.
- if(dir)
- move_prox_checkers(dir)
- return
-
- // Moving onto a turf.
- if(!isturf(old_loc) && isturf(moved_atom.loc))
- toggle_checkers(TRUE)
-
- map_nested_locs()
- recenter_prox_checkers()
-
-/**
- * Called when the receiver or an atom in the `nested_receiver_locs` list moves into a disposals holder object.
- *
- * This proc receives arguments, but they aren't needed.
- */
-/datum/component/proximity_monitor/proc/on_disposal_enter(datum/source)
- SIGNAL_HANDLER
-
- toggle_checkers(FALSE)
-
-/**
- * Called when the receiver or an atom in the `nested_receiver_locs` list moves out of a disposals holder object.
- *
- * This proc receives arguments, but they aren't needed.
- */
-/datum/component/proximity_monitor/proc/on_disposal_exit(datum/source)
- SIGNAL_HANDLER
-
- toggle_checkers(TRUE)
-
-/**
- * Registers signals to any nested locations the `hasprox_receiver` is in, excluding turfs, so they can be monitored for movement.
- */
-/datum/component/proximity_monitor/proc/map_nested_locs()
- clear_nested_locs()
- var/atom/loc_to_check = hasprox_receiver.loc
-
- while(loc_to_check && !isturf(loc_to_check))
- if(loc_to_check in nested_receiver_locs)
- continue
- nested_receiver_locs += loc_to_check
- RegisterSignal(loc_to_check, COMSIG_MOVABLE_MOVED, PROC_REF(on_nested_loc_move))
- RegisterSignal(loc_to_check, COMSIG_MOVABLE_DISPOSING, PROC_REF(on_disposal_enter))
- RegisterSignal(loc_to_check, COMSIG_MOVABLE_EXIT_DISPOSALS, PROC_REF(on_disposal_exit))
- loc_to_check = loc_to_check.loc
-
-/**
- * Removes and unregisters signals from all objects currently in the `nested_receiver_locs` list.
- */
-/datum/component/proximity_monitor/proc/clear_nested_locs()
- for(var/nested_loc in nested_receiver_locs)
- UnregisterSignal(nested_loc, list(COMSIG_MOVABLE_MOVED, COMSIG_MOVABLE_DISPOSING, COMSIG_MOVABLE_EXIT_DISPOSALS))
- nested_receiver_locs = list()
-
-/**
- * Relays basic directional movement from the `hasprox_receiver` or `host`, to all objects in the `proximity_checkers` list.
- *
- * Arguments:
- * * move_dir - the direction the checkers should move in
- */
-/datum/component/proximity_monitor/proc/move_prox_checkers(move_dir)
- for(var/obj/P as anything in proximity_checkers)
- P.loc = get_step(P, move_dir)
-
-/**
- * Update all of the component's proximity checker's to either become active or not active.
- *
- * Arguments:
- * * new_active - the value to be assigned to the proximity checker's `active` variable
- */
-/datum/component/proximity_monitor/proc/toggle_checkers(new_active)
- for(var/obj/effect/abstract/proximity_checker/P as anything in proximity_checkers)
- P.active = new_active
-
-/**
- * Specifies a new radius for the field. Creates or deletes proximity_checkers accordingly.
- *
- * This proc *can* have a high cost due to the `new`s and `qdel`s of the proximity checkers, depending on the number of calls you need to make to it.
- *
- * Arguments:
- * new_radius - the new value that `proximity_radius` should be set to.
- */
-/datum/component/proximity_monitor/proc/set_radius(new_radius)
- ASSERT(new_radius >= 1)
-
- var/new_field_size = (1 + new_radius * 2) ** 2
- var/old_field_size = length(proximity_checkers)
- radius = new_radius
-
- // Radius is decreasing.
- if(new_field_size < old_field_size)
- for(var/i in 1 to (old_field_size - new_field_size))
- qdel(proximity_checkers[length(proximity_checkers)]) // Pop the last entry.
- // Radius is increasing.
- else
- var/turf/parent_turf = get_turf(parent)
- for(var/i in 1 to (new_field_size - old_field_size))
- create_single_prox_checker(parent_turf)
- recenter_prox_checkers()
-
-/**
- * Creates a single proximity checker object, at the given location and of the given type. Adds it to the proximity checkers list.
- *
- * Arguments:
- * * turf/T - the turf the checker should be created on
- * * checker_type - the type of [/obj/item/abstract/proximity_checker] to create
- */
-/datum/component/proximity_monitor/proc/create_single_prox_checker(turf/T, checker_type = field_checker_type)
- var/obj/effect/abstract/proximity_checker/P = new checker_type(T, src)
- proximity_checkers += P
- return P
-
-/**
- * Called in Initialize(). Generates a set of [proximity checker][/obj/effect/abstract/proximity_checker] objects around the parent.
- */
-/datum/component/proximity_monitor/proc/create_prox_checkers()
- LAZYINITLIST(proximity_checkers)
- var/turf/parent_turf = get_turf(parent)
- // For whatever reason their turf is null. Create the checkers in nullspace for now. When the parent moves to a valid turf, they can be recenetered.
- if(!parent_turf)
- // Since we can't use `in range` in nullspace, we need to calculate the number of checkers to create with the below formula.
- var/checker_amount = (1 + radius * 2) ** 2
- for(var/i in 1 to checker_amount)
- create_single_prox_checker(null)
- return
- for(var/T in RANGE_TURFS(radius, parent_turf))
- create_single_prox_checker(T)
-
-/**
- * Re-centers all of the `proximity_checker`s around the parent's current location.
- */
-/datum/component/proximity_monitor/proc/recenter_prox_checkers()
- var/turf/parent_turf = get_turf(parent)
- if(!parent_turf)
- toggle_checkers(FALSE)
- return // Need a sanity check here for certain situations like objects moving into disposal holders. Their turf will be null in these cases.
- var/index = 1
- for(var/T in RANGE_TURFS(radius, parent_turf))
- var/obj/checker = proximity_checkers[index++]
- checker.loc = T
-
-/**
- * # Basic Proximity Checker
- *
- * Inteded for use with the proximity checker component [/datum/component/proximity_monitor].
- * Whenever a movable atom crosses this object, it calls `HasProximity()` on the object which is listening for proximity (`hasprox_receiver`).
- *
- * Because these objects try to make the smallest footprint possible, when these objects move **they should use direct `loc` setting only, and not `forceMove`!**
- * The overhead for forceMove is very unnecessary, because for example turfs and areas don't need to know when these have entered or exited them, etc.
- * These are invisible objects who's sole purpose is to simply inform the receiver that something has moved within X tiles of the it.
- */
-/obj/effect/abstract/proximity_checker
- name = "proximity checker"
- /// The component that this object is in use with, and that will receive `HasProximity()` calls.
- var/datum/component/proximity_monitor/monitor
- /// Whether or not the proximity checker is listening for things crossing it.
- var/active
-
-/obj/effect/abstract/proximity_checker/Initialize(mapload, datum/component/proximity_monitor/P)
- . = ..()
- monitor = P
-
-/obj/effect/abstract/proximity_checker/Destroy()
- monitor.proximity_checkers -= src
- monitor = null
- return ..()
-
-/**
- * Called when something crossed over the proximity_checker. Notifies the `hasprox_receiver` it has proximity with something.
- *
- * Arguments:
- * * atom/movable/AM - the atom crossing the proximity checker
- * * oldloc - the location `AM` used to be at
- */
-/obj/effect/abstract/proximity_checker/Crossed(atom/movable/AM, oldloc)
- set waitfor = FALSE
- . = ..()
- if(active && AM != monitor.hasprox_receiver && !(AM in monitor.nested_receiver_locs))
- monitor.hasprox_receiver.HasProximity(AM)
-
-/// A custom proximity monitor used for tracking players around a table of cards.
-
-/datum/component/proximity_monitor/table
- /// How far away you can be (in terms of table squares).
- var/max_table_distance
- /// How far away you can be (euclidean distance).
- var/max_total_distance
- /// The UID of the deck
- var/deck_uid
- /// Whether the monitors created should be visible. Used for debugging.
- var/monitors_visible = FALSE
-
-/datum/component/proximity_monitor/table/Initialize(_radius = 1, _always_active = FALSE, _max_table_distance = 5)
- max_table_distance = _max_table_distance
- max_total_distance = _max_table_distance
- . = ..(_radius, _always_active)
- if(istype(parent, /obj/item/deck))
- // this is important for tracking traits and attacking multiple cards. so it's not a true UID, sue me
- var/obj/item/deck/D = parent
- deck_uid = D.main_deck_id
- else
- deck_uid = parent.UID()
- addtimer(CALLBACK(src, PROC_REF(refresh)), 0.5 SECONDS)
-
-/datum/component/proximity_monitor/table/proc/refresh()
- var/list/tables = list()
- var/list/prox_mon_spots = list()
- crawl_along(get_turf(parent), tables, prox_mon_spots, 0)
- QDEL_LIST_CONTENTS(proximity_checkers)
- create_prox_checkers()
-
-/// Crawl along an extended table, and return a list of all turfs that we should start tracking.
-/datum/component/proximity_monitor/table/proc/crawl_along(turf/current_turf, list/visited_tables = list(), list/prox_mon_spots = list(), distance_from_start)
- var/obj/structure/current_table = locate(/obj/structure/table) in current_turf
-
- if(QDELETED(current_table))
- // if there's no table here, we're still adjacent to a table, so this is a spot you could play from
- prox_mon_spots |= current_turf
- return
-
- if(current_table in visited_tables)
- return
-
- visited_tables |= current_table
- prox_mon_spots |= current_turf
-
- if(distance_from_start + 1 > max_table_distance)
- return
-
- for(var/direction in GLOB.alldirs)
- var/turf/next_turf = get_step(current_table, direction)
- if(!istype(next_turf))
- continue
- if(get_dist_euclidian(get_turf(parent), next_turf) > max_total_distance)
- continue
- .(next_turf, visited_tables, prox_mon_spots, distance_from_start + 1)
-
-/datum/component/proximity_monitor/table/create_prox_checkers()
- update_prox_checkers(FALSE)
-
-/**
- * Update the proximity monitors making up this component.
- * Arguments:
- * * clear_existing - If true, any existing proximity monitors attached to this will be deleted.
- */
-/datum/component/proximity_monitor/table/proc/update_prox_checkers(clear_existing = TRUE)
- var/list/tables = list()
- var/list/prox_mon_spots = list()
- if(length(proximity_checkers))
- QDEL_LIST_CONTENTS(proximity_checkers)
-
- var/atom/movable/atom_parent = parent
-
- // if we don't have a parent, just treat it normally
- if(!isturf(atom_parent.loc) || !locate(/obj/structure/table) in get_turf(parent))
- return
-
-
- LAZYINITLIST(proximity_checkers)
- crawl_along(get_turf(parent), tables, prox_mon_spots, 0)
-
- // For whatever reason their turf is null. Create the checkers in nullspace for now. When the parent moves to a valid turf, they can be recenetered.
- for(var/T in prox_mon_spots)
- create_single_prox_checker(T, /obj/effect/abstract/proximity_checker/table)
-
- for(var/atom/table in tables)
- RegisterSignal(table, COMSIG_PARENT_QDELETING, PROC_REF(on_table_qdel), TRUE)
-
-/datum/component/proximity_monitor/table/on_receiver_move(datum/source, atom/old_loc, dir)
- update_prox_checkers()
-
-/datum/component/proximity_monitor/table/RegisterWithParent()
- if(ismovable(hasprox_receiver))
- RegisterSignal(hasprox_receiver, COMSIG_MOVABLE_MOVED, PROC_REF(on_receiver_move))
-
-/datum/component/proximity_monitor/table/proc/on_table_qdel()
- SIGNAL_HANDLER // COMSIG_PARENT_QDELETED
- update_prox_checkers()
-
-/obj/effect/abstract/proximity_checker/table
- /// The UID for the deck, used in the setting and removal of traits
- var/deck_uid
-
-/obj/effect/abstract/proximity_checker/table/Initialize(mapload, datum/component/proximity_monitor/table/P)
- . = ..()
- deck_uid = P.deck_uid
- // catch any mobs on our tile
- for(var/mob/living/L in get_turf(src))
- register_on_mob(L)
-
- if(P.monitors_visible)
- icon = 'icons/obj/playing_cards.dmi'
- icon_state = "tarot_the_unknown"
- invisibility = INVISIBILITY_MINIMUM
- layer = MOB_LAYER
-
-/obj/effect/abstract/proximity_checker/table/Destroy()
- var/obj/effect/abstract/proximity_checker/table/same_monitor
- for(var/obj/effect/abstract/proximity_checker/table/mon in get_turf(src))
- if(mon != src && mon.deck_uid == src)
- // if we have another monitor on our space that shares our deck,
- // transfer the signals to it.
- same_monitor = mon
-
- for(var/mob/living/L in get_turf(src))
- remove_from_mob(L)
- if(!isnull(same_monitor))
- same_monitor.register_on_mob(L)
- return ..()
-
-/obj/effect/abstract/proximity_checker/table/proc/register_on_mob(mob/living/L)
- ADD_TRAIT(L, TRAIT_PLAYING_CARDS, "deck_[deck_uid]")
- RegisterSignal(L, COMSIG_MOVABLE_MOVED, PROC_REF(on_move_from_monitor), TRUE)
- RegisterSignal(L, COMSIG_PARENT_QDELETING, PROC_REF(remove_from_mob), TRUE)
-
-
-/obj/effect/abstract/proximity_checker/table/proc/remove_from_mob(mob/living/L)
- if(QDELETED(L))
- return
- // otherwise, clean up
- REMOVE_TRAIT(L, TRAIT_PLAYING_CARDS, "deck_[deck_uid]")
- UnregisterSignal(L, COMSIG_MOVABLE_MOVED)
-
-/obj/effect/abstract/proximity_checker/table/Crossed(atom/movable/AM, oldloc)
- if(!isliving(AM))
- return
-
- var/mob/mover = AM
-
- // This should hopefully ensure that multiple decks around each other don't overlap
- register_on_mob(mover)
-
-/// Triggered when someone moves from a tile that contains our monitor.
-/obj/effect/abstract/proximity_checker/table/proc/on_move_from_monitor(atom/movable/tracked, atom/old_loc)
- SIGNAL_HANDLER // COMSIG_MOVABLE_MOVED
- for(var/obj/effect/abstract/proximity_checker/table/mon in get_turf(tracked))
- // if we're moving onto a turf that shares our stuff, keep the signals and stuff registered
- if(mon.deck_uid == deck_uid)
- return
-
- // otherwise, clean up
- remove_from_mob(tracked)
diff --git a/code/datums/components/slippery.dm b/code/datums/components/slippery.dm
index bc7b0e05f8a1..50f7d817f444 100644
--- a/code/datums/components/slippery.dm
+++ b/code/datums/components/slippery.dm
@@ -24,6 +24,10 @@
var/slip_verb
/// TRUE the player will only slip if the mob this datum is attached to is horizontal
var/horizontal_required
+ ///what we give to connect_loc by default, makes slippable mobs moving over us slip
+ var/static/list/default_connections = list(
+ COMSIG_ATOM_ENTERED = PROC_REF(slip),
+ )
/datum/component/slippery/Initialize(_description, _knockdown = 0, _slip_chance = 100, _slip_tiles = 0, _walking_is_safe = TRUE, _slip_always = FALSE, _slip_verb = "slip", _horizontal_required = FALSE)
if(!isatom(parent))
@@ -38,19 +42,21 @@
slip_verb = _slip_verb
horizontal_required = _horizontal_required
-/datum/component/slippery/RegisterWithParent()
- RegisterSignal(parent, list(COMSIG_MOVABLE_CROSSED, COMSIG_ATOM_ENTERED), PROC_REF(Slip))
+ add_connect_loc_behalf_to_parent()
-/datum/component/slippery/UnregisterFromParent()
- UnregisterSignal(parent, list(COMSIG_MOVABLE_CROSSED, COMSIG_ATOM_ENTERED))
+/datum/component/slippery/proc/add_connect_loc_behalf_to_parent()
+ if(ismovable(parent))
+ AddComponent(/datum/component/connect_loc_behalf, parent, default_connections)
/**
- Called whenever the parent receives either the `MOVABLE_CROSSED` signal or the `ATOM_ENTERED` signal.
+ Called whenever the parent receives the `ATOM_ENTERED` signal.
Calls the `victim`'s `slip()` proc with the component's variables as arguments.
Additionally calls the parent's `after_slip()` proc on the `victim`.
*/
-/datum/component/slippery/proc/Slip(datum/source, mob/living/carbon/human/victim)
+/datum/component/slippery/proc/slip(datum/source, mob/living/carbon/human/victim)
+ SIGNAL_HANDLER // COMSIG_ATOM_ENTERED
+
if(istype(victim) && !HAS_TRAIT(victim, TRAIT_FLYING))
var/atom/movable/owner = parent
if(!isturf(owner.loc))
diff --git a/code/datums/components/squeak.dm b/code/datums/components/squeak.dm
index bfb939ed1036..0583c1f8a4f2 100644
--- a/code/datums/components/squeak.dm
+++ b/code/datums/components/squeak.dm
@@ -19,13 +19,20 @@
///sound exponent for squeak. Defaults to 10 as squeaking is loud and annoying enough.
var/sound_falloff_exponent = 10
+ ///what we set connect_loc to if parent is an item
+ var/static/list/item_connections = list(
+ COMSIG_ATOM_ENTERED = PROC_REF(on_atom_entered),
+ )
+
+
/datum/component/squeak/Initialize(custom_sounds, volume_override, chance_override, step_delay_override, use_delay_override, squeak_on_move, extrarange, falloff_exponent, fallof_distance)
if(!isatom(parent))
return COMPONENT_INCOMPATIBLE
RegisterSignal(parent, list(COMSIG_ATOM_ENTERED, COMSIG_ATOM_BLOB_ACT, COMSIG_ATOM_HULK_ATTACK, COMSIG_ATTACK_BY), PROC_REF(play_squeak))
if(ismovable(parent))
RegisterSignal(parent, list(COMSIG_MOVABLE_BUMP, COMSIG_MOVABLE_IMPACT), PROC_REF(play_squeak))
- RegisterSignal(parent, COMSIG_MOVABLE_CROSSED, PROC_REF(play_squeak_crossed))
+
+ AddComponent(/datum/component/connect_loc_behalf, parent, item_connections)
RegisterSignal(parent, COMSIG_MOVABLE_DISPOSING, PROC_REF(disposing_react))
if(squeak_on_move)
RegisterSignal(parent, COMSIG_MOVABLE_MOVED, PROC_REF(play_squeak))
@@ -71,25 +78,23 @@
else
steps++
-/datum/component/squeak/proc/play_squeak_crossed(atom/source, atom/movable/crossing)
- if(!isturf(crossing.loc) || !isturf(source.loc))
- return
- if(istype(crossing, /obj/effect))
+/datum/component/squeak/proc/on_atom_entered(datum/source, atom/movable/entered)
+ if(istype(entered, /obj/effect))
return
- if(ismob(crossing))
- var/mob/M = crossing
+ if(ismob(entered))
+ var/mob/M = entered
if(HAS_TRAIT(M, TRAIT_FLYING))
return
- if(isliving(crossing))
+ if(isliving(entered))
var/mob/living/L = M
if(L.floating)
return
- else if(isitem(crossing))
+ else if(isitem(entered))
var/obj/item/I = source
if(I.flags & ABSTRACT)
return
- if(isprojectile(crossing))
- var/obj/item/projectile/P = crossing
+ if(isprojectile(entered))
+ var/obj/item/projectile/P = entered
if(P.original != parent)
return
if(ismob(source))
diff --git a/code/datums/components/swarming.dm b/code/datums/components/swarming.dm
index 928589fbaaa8..608359d49089 100644
--- a/code/datums/components/swarming.dm
+++ b/code/datums/components/swarming.dm
@@ -3,6 +3,11 @@
var/offset_y = 0
var/is_swarming = FALSE
var/list/swarm_members = list()
+ var/static/list/swarming_loc_connections = list(
+ COMSIG_ATOM_EXITED = PROC_REF(leave_swarm),
+ COMSIG_ATOM_ENTERED = PROC_REF(join_swarm)
+ )
+
/datum/component/swarming/Initialize(max_x = 24, max_y = 24)
if(!ismovable(parent))
@@ -10,8 +15,7 @@
offset_x = rand(-max_x, max_x)
offset_y = rand(-max_y, max_y)
- RegisterSignal(parent, COMSIG_MOVABLE_CROSSED, PROC_REF(join_swarm))
- RegisterSignal(parent, COMSIG_MOVABLE_UNCROSSED, PROC_REF(leave_swarm))
+ AddComponent(/datum/component/connect_loc_behalf, parent, swarming_loc_connections)
/datum/component/swarming/Destroy()
for(var/other in swarm_members)
@@ -22,8 +26,8 @@
swarm_members = null
return ..()
-/datum/component/swarming/proc/join_swarm(datum/source, atom/movable/AM)
- var/datum/component/swarming/other_swarm = AM.GetComponent(/datum/component/swarming)
+/datum/component/swarming/proc/join_swarm(datum/source, atom/movable/arrived, atom/old_loc, list/atom/old_locs)
+ var/datum/component/swarming/other_swarm = arrived.GetComponent(/datum/component/swarming)
if(!other_swarm)
return
swarm()
@@ -31,8 +35,8 @@
other_swarm.swarm()
other_swarm.swarm_members |= src
-/datum/component/swarming/proc/leave_swarm(datum/source, atom/movable/AM)
- var/datum/component/swarming/other_swarm = AM.GetComponent(/datum/component/swarming)
+/datum/component/swarming/proc/leave_swarm(datum/source, atom/movable/gone, direction)
+ var/datum/component/swarming/other_swarm = gone.GetComponent(/datum/component/swarming)
if(!other_swarm || !(other_swarm in swarm_members))
return
swarm_members -= other_swarm
diff --git a/code/datums/datum.dm b/code/datums/datum.dm
index 56e35b452ed8..e09769af7552 100644
--- a/code/datums/datum.dm
+++ b/code/datums/datum.dm
@@ -57,6 +57,15 @@
qdel(C, FALSE, TRUE)
dc.Cut()
+ _clear_signal_refs()
+ //END: ECS SHIT
+
+ return QDEL_HINT_QUEUE
+
+/// Do not override this. This proc exists solely to be overriden by /turf. This
+/// allows it to ignore clearing out signals which refer to it, in order to keep
+/// those signals valid after the turf has been changed.
+/datum/proc/_clear_signal_refs()
var/list/lookup = comp_lookup
if(lookup)
for(var/sig in lookup)
@@ -72,10 +81,6 @@
for(var/target in signal_procs)
UnregisterSignal(target, signal_procs[target])
- //END: ECS SHIT
-
- return QDEL_HINT_QUEUE
-
/datum/nothing
// Placeholder object, used for ispath checks. Has to be defined to prevent errors, but shouldn't ever be created.
diff --git a/code/datums/elements/connect_loc.dm b/code/datums/elements/connect_loc.dm
new file mode 100644
index 000000000000..6fcc1474679c
--- /dev/null
+++ b/code/datums/elements/connect_loc.dm
@@ -0,0 +1,43 @@
+/// This element hooks a signal onto the loc the current object is on.
+/// When the object moves, it will unhook the signal and rehook it to the new object.
+/datum/element/connect_loc
+ element_flags = ELEMENT_BESPOKE
+ argument_hash_start_idx = 2
+
+ /// An assoc list of signal -> procpath to register to the loc this object is on.
+ var/list/connections
+
+/datum/element/connect_loc/Attach(atom/movable/listener, list/connections)
+ . = ..()
+ if(!istype(listener))
+ return ELEMENT_INCOMPATIBLE
+
+ src.connections = connections
+
+ RegisterSignal(listener, COMSIG_MOVABLE_MOVED, PROC_REF(on_moved), override = TRUE)
+ update_signals(listener)
+
+/datum/element/connect_loc/Detach(atom/movable/listener)
+ . = ..()
+ unregister_signals(listener, listener.loc)
+ UnregisterSignal(listener, COMSIG_MOVABLE_MOVED)
+
+/datum/element/connect_loc/proc/update_signals(atom/movable/listener)
+ var/atom/listener_loc = listener.loc
+ if(QDELETED(listener) || QDELETED(listener_loc))
+ return
+
+ for(var/signal in connections)
+ //override=TRUE because more than one connect_loc element instance tracked object can be on the same loc
+ listener.RegisterSignal(listener_loc, signal, connections[signal], override=TRUE)
+
+/datum/element/connect_loc/proc/unregister_signals(datum/listener, atom/old_loc)
+ if(isnull(old_loc))
+ return
+
+ listener.UnregisterSignal(old_loc, connections)
+
+/datum/element/connect_loc/proc/on_moved(atom/movable/listener, atom/old_loc)
+ SIGNAL_HANDLER // COMSIG_MOVABLE_MOVED
+ unregister_signals(listener, old_loc)
+ update_signals(listener)
diff --git a/code/datums/proximity/advanced_proximity_monitor.dm b/code/datums/proximity/advanced_proximity_monitor.dm
new file mode 100644
index 000000000000..bd47bfd6cb90
--- /dev/null
+++ b/code/datums/proximity/advanced_proximity_monitor.dm
@@ -0,0 +1,168 @@
+#define FIELD_TURFS_KEY "field_turfs"
+#define EDGE_TURFS_KEY "edge_turfs"
+
+/**
+ * Movable and easily code-modified fields! Allows for custom AOE effects that affect movement
+ * and anything inside of them, and can do custom turf effects!
+ * Supports automatic recalculation/reset on movement.
+ *
+ * "What do I gain from using advanced over standard prox monitors?"
+ * - You can set different effects on edge vs field entrance
+ * - You can set effects when the proximity monitor starts and stops tracking a turf
+ */
+/datum/proximity_monitor/advanced
+ /// If TRUE, edge turfs will be included as "in the field" for effects
+ /// Can be used in certain situations where you may have effects that trigger only at the edge,
+ /// while also wanting the field effect to trigger at edge turfs as well
+ var/edge_is_a_field = FALSE
+ /// All turfs on the inside of the proximity monitor - range - 1 turfs
+ var/list/turf/field_turfs = list()
+ /// All turfs on the very last tile of the proximity monitor's radius
+ var/list/turf/edge_turfs = list()
+
+/datum/proximity_monitor/advanced/Destroy()
+ cleanup_field()
+ return ..()
+
+/datum/proximity_monitor/advanced/proc/cleanup_field()
+ for(var/turf/turf as anything in edge_turfs)
+ cleanup_edge_turf(turf)
+ edge_turfs = list()
+ for(var/turf/turf as anything in field_turfs)
+ cleanup_field_turf(turf)
+ field_turfs = list()
+
+//Call every time the field moves (done automatically if you use update_center) or a setup specification is changed.
+/datum/proximity_monitor/advanced/proc/recalculate_field(full_recalc = FALSE)
+ var/list/new_turfs = update_new_turfs()
+
+ var/list/old_field_turfs = field_turfs
+ var/list/old_edge_turfs = edge_turfs
+ field_turfs = new_turfs[FIELD_TURFS_KEY]
+ edge_turfs = new_turfs[EDGE_TURFS_KEY]
+ if(full_recalc)
+ field_turfs = list()
+ edge_turfs = list()
+
+ for(var/turf/old_turf as anything in old_field_turfs - field_turfs)
+ if(QDELETED(src))
+ return
+ cleanup_field_turf(old_turf)
+ for(var/turf/old_turf as anything in old_edge_turfs - edge_turfs)
+ if(QDELETED(src))
+ return
+ cleanup_edge_turf(old_turf)
+
+ if(full_recalc)
+ old_field_turfs = list()
+ old_edge_turfs = list()
+ field_turfs = new_turfs[FIELD_TURFS_KEY]
+ edge_turfs = new_turfs[EDGE_TURFS_KEY]
+
+ for(var/turf/new_turf as anything in field_turfs - old_field_turfs)
+ if(QDELETED(src))
+ return
+ setup_field_turf(new_turf)
+
+ for(var/turf/new_turf as anything in edge_turfs - old_edge_turfs)
+ if(QDELETED(src))
+ return
+ setup_edge_turf(new_turf)
+
+/datum/proximity_monitor/advanced/on_initialized(turf/location, atom/created, init_flags)
+ . = ..()
+ on_entered(location, created, null)
+
+/datum/proximity_monitor/advanced/on_entered(turf/source, atom/movable/entered, turf/old_loc)
+ . = ..()
+ if(get_dist(source, host) == current_range)
+ field_edge_crossed(entered, old_loc, source)
+ else
+ field_turf_crossed(entered, old_loc, source)
+
+/datum/proximity_monitor/advanced/on_moved(atom/movable/movable, atom/old_loc)
+ . = ..()
+ if(ignore_if_not_on_turf)
+ //Early return if it's not the host that has moved.
+ if(movable != host)
+ return
+ //Cleanup the field if the host was on a turf but isn't anymore.
+ if(!isturf(host.loc))
+ if(isturf(old_loc))
+ cleanup_field()
+ return
+ recalculate_field(full_recalc = FALSE)
+
+/datum/proximity_monitor/advanced/on_uncrossed(turf/source, atom/movable/gone, direction)
+ if(get_dist(source, host) == current_range)
+ field_edge_uncrossed(gone, source, get_turf(gone))
+ else
+ field_turf_uncrossed(gone, source, get_turf(gone))
+
+/// Called when a turf in the field of the monitor is linked
+/datum/proximity_monitor/advanced/proc/setup_field_turf(turf/target)
+ return
+
+/// Called when a turf in the field of the monitor is unlinked
+/// Do NOT call this manually, requires management of the field_turfs list
+/datum/proximity_monitor/advanced/proc/cleanup_field_turf(turf/target)
+ PRIVATE_PROC(TRUE)
+ return
+
+/// Called when a turf in the edge of the monitor is linked
+/datum/proximity_monitor/advanced/proc/setup_edge_turf(turf/target)
+ if(edge_is_a_field) // If the edge is considered a field, set it up like one
+ setup_field_turf(target)
+
+/// Called when a turf in the edge of the monitor is unlinked
+/// Do NOT call this manually, requires management of the edge_turfs list
+/datum/proximity_monitor/advanced/proc/cleanup_edge_turf(turf/target)
+ if(edge_is_a_field) // If the edge is considered a field, clean it up like one
+ cleanup_field_turf(target)
+
+/datum/proximity_monitor/advanced/proc/update_new_turfs()
+ if(ignore_if_not_on_turf && !isturf(host.loc))
+ return list(FIELD_TURFS_KEY = list(), EDGE_TURFS_KEY = list())
+ var/list/local_field_turfs = list()
+ var/list/local_edge_turfs = list()
+ var/turf/center = get_turf(host)
+ if(current_range > 0)
+ local_field_turfs += RANGE_TURFS(current_range - 1, center)
+ if(current_range > 1)
+ local_edge_turfs = RANGE_TURFS(current_range, center) - local_field_turfs
+ return list(FIELD_TURFS_KEY = local_field_turfs, EDGE_TURFS_KEY = local_edge_turfs)
+
+//Gets edge direction/corner, only works with square radius/WDH fields!
+/datum/proximity_monitor/advanced/proc/get_edgeturf_direction(turf/T, turf/center_override = null)
+ var/turf/checking_from = get_turf(host)
+ if(istype(center_override))
+ checking_from = center_override
+ if(!(T in edge_turfs))
+ return
+ if(((T.x == (checking_from.x + current_range)) || (T.x == (checking_from.x - current_range))) && ((T.y == (checking_from.y + current_range)) || (T.y == (checking_from.y - current_range))))
+ return get_dir(checking_from, T)
+ if(T.x == (checking_from.x + current_range))
+ return EAST
+ if(T.x == (checking_from.x - current_range))
+ return WEST
+ if(T.y == (checking_from.y - current_range))
+ return SOUTH
+ if(T.y == (checking_from.y + current_range))
+ return NORTH
+
+/datum/proximity_monitor/advanced/proc/field_turf_crossed(atom/movable/movable, turf/old_location, turf/new_location)
+ return
+
+/datum/proximity_monitor/advanced/proc/field_turf_uncrossed(atom/movable/movable, turf/old_location, turf/new_location)
+ return
+
+/datum/proximity_monitor/advanced/proc/field_edge_crossed(atom/movable/movable, turf/old_location, turf/new_location)
+ if(edge_is_a_field) // If the edge is considered a field, pass crossed to that
+ field_turf_crossed(movable, old_location, new_location)
+
+/datum/proximity_monitor/advanced/proc/field_edge_uncrossed(atom/movable/movable, turf/old_location, turf/new_location)
+ if(edge_is_a_field) // If the edge is considered a field, pass uncrossed to that
+ field_turf_uncrossed(movable, old_location, new_location)
+
+#undef FIELD_TURFS_KEY
+#undef EDGE_TURFS_KEY
diff --git a/code/datums/proximity/proximity_monitor.dm b/code/datums/proximity/proximity_monitor.dm
new file mode 100644
index 000000000000..09a7d32f916d
--- /dev/null
+++ b/code/datums/proximity/proximity_monitor.dm
@@ -0,0 +1,89 @@
+/datum/proximity_monitor
+ ///The atom we are tracking
+ var/atom/host
+ ///The atom that will receive HasProximity calls.
+ var/atom/hasprox_receiver
+ ///The range of the proximity monitor. Things moving wihin it will trigger HasProximity calls.
+ var/current_range
+ ///If we don't check turfs in range if the host's loc isn't a turf
+ var/ignore_if_not_on_turf
+ ///The signals of the connect range component, needed to monitor the turfs in range.
+ var/static/list/loc_connections = list(
+ COMSIG_ATOM_ENTERED = PROC_REF(on_entered),
+ COMSIG_ATOM_EXITED = PROC_REF(on_uncrossed),
+ COMSIG_ATOM_AFTER_SUCCESSFUL_INITIALIZED_ON = PROC_REF(on_initialized),
+ )
+
+/datum/proximity_monitor/New(atom/_host, range = 1, _ignore_if_not_on_turf = TRUE)
+ ignore_if_not_on_turf = _ignore_if_not_on_turf
+ current_range = range
+ set_host(_host)
+
+/datum/proximity_monitor/proc/set_host(atom/new_host, atom/new_receiver)
+ if(new_host == host)
+ return
+ if(host) //No need to delete the connect range and containers comps. They'll be updated with the new tracked host.
+ UnregisterSignal(host, list(COMSIG_MOVABLE_MOVED, COMSIG_PARENT_QDELETING))
+ if(hasprox_receiver)
+ UnregisterSignal(hasprox_receiver, COMSIG_PARENT_QDELETING)
+ if(new_receiver)
+ hasprox_receiver = new_receiver
+ if(new_receiver != new_host)
+ RegisterSignal(new_receiver, COMSIG_PARENT_QDELETING, PROC_REF(on_host_or_receiver_del))
+ else if(hasprox_receiver == host) //Default case
+ hasprox_receiver = new_host
+ host = new_host
+ RegisterSignal(new_host, COMSIG_PARENT_QDELETING, PROC_REF(on_host_or_receiver_del))
+ var/static/list/containers_connections = list(COMSIG_MOVABLE_MOVED = PROC_REF(on_moved), COMSIG_MOVABLE_Z_CHANGED = PROC_REF(on_z_change))
+ AddComponent(/datum/component/connect_containers, host, containers_connections)
+ RegisterSignal(host, COMSIG_MOVABLE_MOVED, PROC_REF(on_moved))
+ RegisterSignal(host, COMSIG_MOVABLE_Z_CHANGED, PROC_REF(on_z_change))
+ set_range(current_range, TRUE)
+
+/datum/proximity_monitor/proc/on_host_or_receiver_del(datum/source)
+ SIGNAL_HANDLER // COMSIG_PARENT_QDELETING
+ qdel(src)
+
+/datum/proximity_monitor/Destroy()
+ host = null
+ hasprox_receiver = null
+ return ..()
+
+/datum/proximity_monitor/proc/set_range(range, force_rebuild = FALSE)
+ if(!force_rebuild && range == current_range)
+ return FALSE
+ . = TRUE
+ current_range = range
+
+ //If the connect_range component exists already, this will just update its range. No errors or duplicates.
+ AddComponent(/datum/component/connect_range, host, loc_connections, range, !ignore_if_not_on_turf)
+
+/datum/proximity_monitor/proc/on_moved(atom/movable/source, atom/old_loc)
+ SIGNAL_HANDLER // COMSIG_MOVABLE_MOVED
+ if(source == host)
+ hasprox_receiver?.HasProximity(host)
+
+/datum/proximity_monitor/proc/on_z_change()
+ SIGNAL_HANDLER // COMSIG_MOVABLE_Z_CHANGED
+ return
+
+/datum/proximity_monitor/proc/set_ignore_if_not_on_turf(does_ignore = TRUE)
+ if(ignore_if_not_on_turf == does_ignore)
+ return
+ ignore_if_not_on_turf = does_ignore
+ //Update the ignore_if_not_on_turf
+ AddComponent(/datum/component/connect_range, host, loc_connections, current_range, ignore_if_not_on_turf)
+
+/datum/proximity_monitor/proc/on_uncrossed()
+ SIGNAL_HANDLER // COMSIG_ATOM_EXITED
+ return //Used by the advanced subtype for effect fields.
+
+/datum/proximity_monitor/proc/on_entered(atom/source, atom/movable/arrived, turf/old_loc)
+ SIGNAL_HANDLER // COMSIG_ATOM_ENTERED
+ if(source != host)
+ hasprox_receiver?.HasProximity(arrived)
+
+/datum/proximity_monitor/proc/on_initialized(turf/location, atom/created, init_flags)
+ SIGNAL_HANDLER // COMSIG_ATOM_AFTER_SUCCESSFUL_INITIALIZED_ON
+ if(location != host)
+ hasprox_receiver?.HasProximity(created)
diff --git a/code/datums/proximity/singulo_proximity_monitor.dm b/code/datums/proximity/singulo_proximity_monitor.dm
new file mode 100644
index 000000000000..e7b870ba346a
--- /dev/null
+++ b/code/datums/proximity/singulo_proximity_monitor.dm
@@ -0,0 +1,28 @@
+/datum/proximity_monitor/advanced/singulo
+
+/datum/proximity_monitor/advanced/singulo/on_entered(turf/source, atom/movable/entered, turf/old_loc)
+ . = ..()
+ if(!isprojectile(entered))
+ return
+
+ var/angle_to_singulo = ATAN2(host.y - source.y, host.x - source.x)
+ var/distance_to_singulo = get_dist(source, host)
+
+ var/obj/item/projectile/P = entered
+ var/distance = distance_to_singulo
+ var/projectile_angle = P.Angle
+ var/angle_to_projectile = angle_to_singulo
+ if(angle_to_projectile == 180)
+ angle_to_projectile = -180
+ angle_to_projectile -= projectile_angle
+ if(angle_to_projectile > 180)
+ angle_to_projectile -= 360
+ else if(angle_to_projectile < -180)
+ angle_to_projectile += 360
+
+ if(distance == 0)
+ qdel(P)
+ return
+ projectile_angle += angle_to_projectile / (distance ** 2)
+ P.damage += 10 / distance
+ P.set_angle(projectile_angle)
diff --git a/code/datums/ruins/bridges/bridges.dm b/code/datums/ruins/bridges/bridges.dm
index 666754127a77..50be4d96c834 100644
--- a/code/datums/ruins/bridges/bridges.dm
+++ b/code/datums/ruins/bridges/bridges.dm
@@ -139,9 +139,15 @@
name = "clockwork floor"
icon_state = "clockwork_floor"
-// Pretend to be a normal clockwork floor and duplicate its visual effect
-/obj/structure/bridge_walkway/clockwork/Crossed(atom/crosser, atom/old_loc)
+/obj/structure/bridge_walkway/clockwork/Initialize(mapload)
. = ..()
+ var/static/list/loc_connections = list(
+ COMSIG_ATOM_ENTERED = PROC_REF(on_crossed)
+ )
+ AddElement(/datum/element/connect_loc, loc_connections)
+
+// Pretend to be a normal clockwork floor and duplicate its visual effect
+/obj/structure/bridge_walkway/clockwork/proc/on_crossed(atom/crosser)
var/counter = 0
for(var/obj/effect/temp_visual/ratvar/floor/floor in contents)
if(++counter == 3)
diff --git a/code/datums/spell.dm b/code/datums/spell.dm
index 5657828fd3f3..8ad1b79793c6 100644
--- a/code/datums/spell.dm
+++ b/code/datums/spell.dm
@@ -67,7 +67,6 @@ GLOBAL_LIST_INIT(spells, typesof(/datum/spell))
/datum/spell
var/name = "Spell" // Only rename this if the spell you're making is not abstract
var/desc = "A wizard spell."
- var/school = "evocation" //not relevant at now, but may be important later if there are changes to how spells work. the ones I used for now will probably be changed... maybe spell presets? lacking flexibility but with some other benefit?
///recharge time in deciseconds
var/base_cooldown = 10 SECONDS
var/starts_charged = TRUE //Does this spell start ready to go?
diff --git a/code/datums/spells/alien_spells/build_resin_structure.dm b/code/datums/spells/alien_spells/build_resin_structure.dm
index b64925e0b263..60bc6bbfa121 100644
--- a/code/datums/spells/alien_spells/build_resin_structure.dm
+++ b/code/datums/spells/alien_spells/build_resin_structure.dm
@@ -58,33 +58,33 @@
desc = "The hunger..."
icon_state = "alien_acid"
-/obj/item/melee/touch_attack/alien/consume_resin/afterattack__legacy__attackchain(atom/target, mob/living/carbon/user, proximity)
+/obj/item/melee/touch_attack/alien/consume_resin/after_attack(atom/target, mob/user, proximity_flag, click_parameters)
+ . = ..()
if(target == user)
to_chat(user, "You stop trying to consume resin.")
- ..()
return
- if(!proximity || !iscarbon(user) || HAS_TRAIT(user, TRAIT_HANDS_BLOCKED))
+ if(!proximity_flag || !iscarbon(user) || HAS_TRAIT(user, TRAIT_HANDS_BLOCKED))
return
+ var/mob/living/carbon/C = user
if(istype(target, /obj/structure/alien/weeds))
qdel(target)
if(istype(target, /obj/structure/alien/weeds/node))
- user.add_plasma(50)
- user.visible_message("[user] rips and tears into [target] with their teeth!", "You viciously rip apart and consume [target]!")
+ C.add_plasma(50)
+ C.visible_message("[C] rips and tears into [target] with their teeth!", "You viciously rip apart and consume [target]!")
return
- if(!plasma_check(10, user))
- to_chat(user, "You don't have enough plasma to perform this action!")
+ if(!plasma_check(10, C))
+ to_chat(C, "You don't have enough plasma to perform this action!")
return
var/static/list/resin_objects = list(/obj/structure/alien/resin, /obj/structure/alien/egg, /obj/structure/bed/nest, /obj/structure/bed/revival_nest)
for(var/resin_type in resin_objects)
if(!istype(target, resin_type))
continue
- user.visible_message("[user] rips and tears into [target] with their teeth!")
- if(!do_after(user, 3 SECONDS, target = target))
+ C.visible_message("[C] rips and tears into [target] with their teeth!")
+ if(!do_after(C, 3 SECONDS, target = target))
return
- to_chat(user, "You viciously rip apart and consume [target]!")
- user.add_plasma(-10)
+ to_chat(C, "You viciously rip apart and consume [target]!")
+ C.add_plasma(-10)
qdel(target)
- ..()
#undef RESIN_WALL
#undef RESIN_NEST
diff --git a/code/datums/spells/alien_spells/corrosive_acid_spit.dm b/code/datums/spells/alien_spells/corrosive_acid_spit.dm
index 20593bdd3756..b74af9fe38ca 100644
--- a/code/datums/spells/alien_spells/corrosive_acid_spit.dm
+++ b/code/datums/spells/alien_spells/corrosive_acid_spit.dm
@@ -11,26 +11,26 @@
desc = "A fistfull of death."
icon_state = "alien_acid"
-/obj/item/melee/touch_attack/alien/corrosive_acid/afterattack__legacy__attackchain(atom/target, mob/living/carbon/user, proximity)
+/obj/item/melee/touch_attack/alien/corrosive_acid/after_attack(atom/target, mob/user, proximity_flag, click_parameters)
+ . = ..()
if(target == user)
to_chat(user, "You withdraw your readied acid.")
- ..()
return
- if(!proximity || isalien(target) || !iscarbon(user) || HAS_TRAIT(user, TRAIT_HANDS_BLOCKED)) // Don't want xenos ditching out of cuffs
+ if(!proximity_flag || isalien(target) || !iscarbon(user) || HAS_TRAIT(user, TRAIT_HANDS_BLOCKED)) // Don't want xenos ditching out of cuffs
return
- if(!plasma_check(200, user))
- to_chat(user, "You don't have enough plasma to perform this action!")
+ var/mob/living/carbon/C = user
+ if(!plasma_check(200, C))
+ to_chat(C, "You don't have enough plasma to perform this action!")
return
var/acid_damage_modifier = 100
if(isliving(target))
acid_damage_modifier = 50
if(target.acid_act(2 * acid_damage_modifier, acid_damage_modifier))
- visible_message("[user] vomits globs of vile stuff all over [target]. It begins to sizzle and melt under the bubbling mess of acid!")
- add_attack_logs(user, target, "Applied corrosive acid") // Want this logged
- user.add_plasma(-200)
+ visible_message("[C] vomits globs of vile stuff all over [target]. It begins to sizzle and melt under the bubbling mess of acid!")
+ add_attack_logs(C, target, "Applied corrosive acid") // Want this logged
+ C.add_plasma(-200)
else
- to_chat(user, "You cannot dissolve this object.")
- ..()
+ to_chat(C, "You cannot dissolve this object.")
/datum/spell/touch/alien_spell/burning_touch
name = "Blazing touch"
@@ -45,31 +45,30 @@
desc = "The air warps around your hand, somehow the heat doesn't hurt."
icon_state = "alien_acid"
-/obj/item/melee/touch_attack/alien/burning_touch/afterattack__legacy__attackchain(atom/target, mob/living/carbon/user, proximity)
+/obj/item/melee/touch_attack/alien/burning_touch/after_attack(atom/target, mob/user, proximity_flag, click_parameters)
+ . = ..()
if(target == user)
to_chat(user, "You cool down your boiled aid.")
- ..()
return
- if(!proximity || !iscarbon(user) || HAS_TRAIT(user, TRAIT_HANDS_BLOCKED))
+ if(!proximity_flag || !iscarbon(user) || HAS_TRAIT(user, TRAIT_HANDS_BLOCKED))
return
- if(!plasma_check(100, user))
- to_chat(user, "You don't have enough plasma to perform this action!")
+ var/mob/living/carbon/C = user
+ if(!plasma_check(100, C))
+ to_chat(C, "You don't have enough plasma to perform this action!")
return
if(isliving(target))
var/mob/living/guy_to_burn = target
- add_attack_logs(user, target, "Applied blazing touch") // Want this logged
+ add_attack_logs(C, target, "Applied blazing touch") // Want this logged
guy_to_burn.adjustFireLoss(60)
guy_to_burn.adjust_fire_stacks(3)
guy_to_burn.IgniteMob()
- user.visible_message("[user] touches [target] and a fireball erupts on contact!")
- user.add_plasma(-100)
- ..()
+ C.visible_message("[C] touches [target] and a fireball erupts on contact!")
+ C.add_plasma(-100)
else
var/static/list/resin_objects = list(/obj/structure/alien/resin, /obj/structure/alien/egg, /obj/structure/bed/nest, /obj/structure/bed/revival_nest)
for(var/resin_type in resin_objects)
if(!istype(target, resin_type))
continue
- user.visible_message("[user] touches [target] and burns right through it!")
- user.add_plasma(-100)
+ C.visible_message("[C] touches [target] and burns right through it!")
+ C.add_plasma(-100)
qdel(target)
- ..()
diff --git a/code/datums/spells/banana_touch.dm b/code/datums/spells/banana_touch.dm
index 052e657e807d..b7d823bccdb3 100644
--- a/code/datums/spells/banana_touch.dm
+++ b/code/datums/spells/banana_touch.dm
@@ -4,7 +4,6 @@
stun them with a loud HONK, and mutate them to make them more entertaining! \
Warning : Effects are permanent on non-wizards."
hand_path = /obj/item/melee/touch_attack/banana
- school = "transmutation"
base_cooldown = 30 SECONDS
clothes_req = TRUE
@@ -24,14 +23,15 @@
/obj/item/melee/touch_attack/banana/apprentice
-/obj/item/melee/touch_attack/banana/apprentice/afterattack__legacy__attackchain(atom/target, mob/living/carbon/user, proximity)
+/obj/item/melee/touch_attack/banana/apprentice/after_attack(atom/target, mob/user, proximity_flag, click_parameters)
+ . = ..()
if(iswizard(target) && target != user)
to_chat(user, "Seriously?! Honk THEM, not me!")
return
- ..()
-/obj/item/melee/touch_attack/banana/afterattack__legacy__attackchain(atom/target, mob/living/carbon/user, proximity)
- if(!proximity || target == user || !ishuman(target) || !iscarbon(user) || HAS_TRAIT(user, TRAIT_HANDS_BLOCKED))
+/obj/item/melee/touch_attack/banana/after_attack(atom/target, mob/user, proximity_flag, click_parameters)
+ . = ..()
+ if(!proximity_flag || target == user || !ishuman(target) || !iscarbon(user) || HAS_TRAIT(user, TRAIT_HANDS_BLOCKED))
return
var/datum/effect_system/smoke_spread/s = new
@@ -41,7 +41,6 @@
to_chat(user, "HONK")
var/mob/living/carbon/human/H = target
H.bananatouched()
- ..()
/mob/living/carbon/human/proc/bananatouched()
to_chat(src, "HONK")
diff --git a/code/datums/spells/chaplain_bless.dm b/code/datums/spells/chaplain_bless.dm
index de9741b53659..970cebd638cc 100644
--- a/code/datums/spells/chaplain_bless.dm
+++ b/code/datums/spells/chaplain_bless.dm
@@ -3,7 +3,6 @@
name = "Bless"
desc = "Blesses a single person."
- school = "transmutation"
base_cooldown = 6 SECONDS
clothes_req = FALSE
invocation = "none"
diff --git a/code/datums/spells/charge.dm b/code/datums/spells/charge.dm
index a35a923267bf..50b5f3a67e89 100644
--- a/code/datums/spells/charge.dm
+++ b/code/datums/spells/charge.dm
@@ -1,7 +1,6 @@
/datum/spell/charge
name = "Charge"
desc = "This spell can be used to recharge a variety of things in your hands, from magical artifacts to electrical components. A creative wizard can even use it to grant magical power to a fellow magic user."
- school = "transmutation"
base_cooldown = 1 MINUTES
clothes_req = FALSE
invocation = "DIRI CEL"
diff --git a/code/datums/spells/chef.dm b/code/datums/spells/chef.dm
index b07004f5c874..a28438b397a1 100644
--- a/code/datums/spells/chef.dm
+++ b/code/datums/spells/chef.dm
@@ -1,7 +1,6 @@
/datum/spell/expert_chef
name = "Expert Chef Knowledge"
desc = "Find things you can cook with the items in reach."
- school = "chef"
clothes_req = FALSE
base_cooldown = 5 SECONDS
human_req = TRUE
diff --git a/code/datums/spells/cluwne.dm b/code/datums/spells/cluwne.dm
index 58279b573f46..d2d81b672883 100644
--- a/code/datums/spells/cluwne.dm
+++ b/code/datums/spells/cluwne.dm
@@ -3,8 +3,6 @@
desc = "Turns the target into a fat and cursed monstrosity of a clown."
hand_path = /obj/item/melee/touch_attack/cluwne
- school = "transmutation"
-
base_cooldown = 1 MINUTES
clothes_req = TRUE
cooldown_min = 200 //100 deciseconds reduction per rank
diff --git a/code/datums/spells/conjure_item.dm b/code/datums/spells/conjure_item.dm
index 45c0b465f24d..bc3cc7215498 100644
--- a/code/datums/spells/conjure_item.dm
+++ b/code/datums/spells/conjure_item.dm
@@ -5,7 +5,6 @@
clothes_req = FALSE
var/obj/item/item
var/item_type = /obj/item/banhammer
- school = "conjuration"
base_cooldown = 15 SECONDS
cooldown_min = 10
diff --git a/code/datums/spells/construct_spells.dm b/code/datums/spells/construct_spells.dm
index 0812bae7a352..69f0a33dfcbe 100644
--- a/code/datums/spells/construct_spells.dm
+++ b/code/datums/spells/construct_spells.dm
@@ -13,7 +13,6 @@
desc = "This spell constructs a cult floor."
action_icon_state = "floorconstruct"
action_background_icon_state = "bg_cult"
- school = "conjuration"
base_cooldown = 20
clothes_req = FALSE
invocation = "none"
@@ -27,7 +26,6 @@
desc = "This spell constructs a cult wall."
action_icon_state = "cultforcewall"
action_background_icon_state = "bg_cult"
- school = "conjuration"
base_cooldown = 100
clothes_req = FALSE
invocation = "none"
@@ -39,7 +37,6 @@
/datum/spell/aoe/conjure/build/wall/reinforced
name = "Greater Construction"
desc = "This spell constructs a reinforced metal wall."
- school = "conjuration"
base_cooldown = 300
clothes_req = FALSE
invocation = "none"
@@ -55,7 +52,6 @@
desc = "This spell uses vile sorcery to create a spirit-trapping soulstone."
action_icon_state = "summonsoulstone"
action_background_icon_state = "bg_cult"
- school = "conjuration"
base_cooldown = 3000
clothes_req = FALSE
invocation = "none"
@@ -77,7 +73,6 @@
desc = "This spell uses dark magic to craft an unholy beacon. Heals cultists, and makes a handy light source."
action_icon_state = "pylon"
action_background_icon_state = "bg_cult"
- school = "conjuration"
base_cooldown = 200
clothes_req = FALSE
invocation = "none"
@@ -92,7 +87,6 @@
desc = "This spell creates a temporary forcefield to shield yourself and allies from incoming fire."
action_icon_state = "cultforcewall"
action_background_icon_state = "bg_cult"
- school = "transmutation"
base_cooldown = 300
clothes_req = FALSE
invocation = "none"
@@ -140,7 +134,6 @@
name = "Lesser Magic Missile"
desc = "This spell fires several, slow moving, magic projectiles at nearby targets."
action_background_icon_state = "bg_cult"
- school = "evocation"
base_cooldown = 400
clothes_req = FALSE
invocation = "none"
@@ -167,7 +160,6 @@
desc = "This spell spawns a cloud of paralysing smoke."
action_icon_state = "parasmoke"
action_background_icon_state = "bg_cult"
- school = "conjuration"
base_cooldown = 200
clothes_req = FALSE
invocation = "none"
diff --git a/code/datums/spells/disguise_self.dm b/code/datums/spells/disguise_self.dm
index f9c5a26a5326..adffa4f42050 100644
--- a/code/datums/spells/disguise_self.dm
+++ b/code/datums/spells/disguise_self.dm
@@ -4,7 +4,6 @@
The illusion isn't strong enough for more thorough examinations, but will fool people at a glance. \
You will lose control over the illusion if you're attacked, shoved, or a object is thrown at you, no matter how soft."
- school = "illusion"
base_cooldown = 3 SECONDS
clothes_req = FALSE
invocation_type = "none"
diff --git a/code/datums/spells/ethereal_jaunt.dm b/code/datums/spells/ethereal_jaunt.dm
index b671244dfc83..a6b143296907 100644
--- a/code/datums/spells/ethereal_jaunt.dm
+++ b/code/datums/spells/ethereal_jaunt.dm
@@ -2,7 +2,6 @@
name = "Ethereal Jaunt"
desc = "This spell creates your ethereal form, temporarily making you invisible and able to pass through walls."
- school = "transmutation"
base_cooldown = 300
clothes_req = TRUE
invocation = "none"
diff --git a/code/datums/spells/fake_gib.dm b/code/datums/spells/fake_gib.dm
index 262a8c9cba9a..66c2597b5f64 100644
--- a/code/datums/spells/fake_gib.dm
+++ b/code/datums/spells/fake_gib.dm
@@ -3,7 +3,6 @@
desc = "This spell charges your hand with vile energy that can be used to violently explode victims."
hand_path = "/obj/item/melee/touch_attack/fake_disintegrate"
- school = "evocation"
base_cooldown = 600
clothes_req = FALSE
cooldown_min = 200 //100 deciseconds reduction per rank
diff --git a/code/datums/spells/horsemask.dm b/code/datums/spells/horsemask.dm
index fa6cd24a8a8c..f0614edddd55 100644
--- a/code/datums/spells/horsemask.dm
+++ b/code/datums/spells/horsemask.dm
@@ -1,7 +1,6 @@
/datum/spell/horsemask
name = "Curse of the Horseman"
desc = "This spell triggers a curse on a target, causing them to wield an unremovable horse head mask. They will speak like a horse! Any masks they are wearing will be disintegrated. This spell does not require robes."
- school = "transmutation"
base_cooldown = 150
clothes_req = FALSE
stat_allowed = CONSCIOUS
diff --git a/code/datums/spells/infinite_guns.dm b/code/datums/spells/infinite_guns.dm
index b18625a3d5f7..a7666232db5c 100644
--- a/code/datums/spells/infinite_guns.dm
+++ b/code/datums/spells/infinite_guns.dm
@@ -3,7 +3,6 @@
desc = "Why reload when you have infinite guns? Summons an unending stream of bolt action rifles. Requires both hands free to use."
invocation_type = "none"
- school = "conjuration"
base_cooldown = 600
clothes_req = TRUE
cooldown_min = 10 //Gun wizard
@@ -25,7 +24,6 @@
name = "Rapid-fire Fireball"
desc = "Multiple Fireballs. Need I explain more? Requires both hands free to use."
- school = "evocation"
base_cooldown = 30 SECONDS
clothes_req = FALSE
invocation = "ONI SOMA-SOMA-SOMA"
diff --git a/code/datums/spells/knock.dm b/code/datums/spells/knock.dm
index 9689858794a2..816c529a0de6 100644
--- a/code/datums/spells/knock.dm
+++ b/code/datums/spells/knock.dm
@@ -2,7 +2,6 @@
name = "Knock"
desc = "This spell opens nearby doors and does not require wizard garb."
- school = "transmutation"
base_cooldown = 100
clothes_req = FALSE
invocation = "AULIE OXIN FIERA"
diff --git a/code/datums/spells/lichdom.dm b/code/datums/spells/lichdom.dm
index b40c1a19d499..8440e17efa09 100644
--- a/code/datums/spells/lichdom.dm
+++ b/code/datums/spells/lichdom.dm
@@ -1,7 +1,6 @@
/datum/spell/lichdom
name = "Bind Soul"
desc = "A dark necromantic pact that can forever bind your soul to an item of your choosing. So long as both your body and the item remain intact and on the same plane you can revive from death, though the time between reincarnations grows steadily with use."
- school = "necromancy"
base_cooldown = 10
clothes_req = FALSE
centcom_cancast = FALSE
diff --git a/code/datums/spells/mime.dm b/code/datums/spells/mime.dm
index cb8c1a4c182f..2e139981d64e 100644
--- a/code/datums/spells/mime.dm
+++ b/code/datums/spells/mime.dm
@@ -1,7 +1,6 @@
/datum/spell/aoe/conjure/build/mime_wall
name = "Invisible Wall"
desc = "The mime's performance transmutates into physical reality."
- school = "mime"
summon_type = list(/obj/structure/forcefield/mime)
invocation_type = "emote"
invocation_emote_self = "You form a wall in front of yourself."
@@ -30,7 +29,6 @@
/datum/spell/mime/speak
name = "Speech"
desc = "Make or break a vow of silence."
- school = "mime"
clothes_req = FALSE
base_cooldown = 5 MINUTES
human_req = TRUE
@@ -63,7 +61,6 @@
/datum/spell/forcewall/mime
name = "Invisible Greater Wall"
desc = "Form an invisible three tile wide blockade."
- school = "mime"
wall_type = /obj/effect/forcefield/mime
invocation_type = "emote"
invocation_emote_self = "You form a blockade in front of yourself."
@@ -87,7 +84,6 @@
/datum/spell/mime/fingergun
name = "Finger Gun"
desc = "Shoot lethal, silencing bullets out of your fingers! 3 bullets available per cast. Use your fingers to holster them manually."
- school = "mime"
clothes_req = FALSE
base_cooldown = 30 SECONDS
human_req = TRUE
diff --git a/code/datums/spells/mime_malaise.dm b/code/datums/spells/mime_malaise.dm
index 8ce906e055a7..63b1c3e2f298 100644
--- a/code/datums/spells/mime_malaise.dm
+++ b/code/datums/spells/mime_malaise.dm
@@ -4,7 +4,6 @@
stun them so that they may contemplate Art, and silence them. \
Warning : Effects are permanent on non-wizards."
hand_path = /obj/item/melee/touch_attack/mime_malaise
- school = "transmutation"
base_cooldown = 300
clothes_req = TRUE
@@ -19,8 +18,9 @@
icon_state = "fleshtostone"
item_state = "fleshtostone"
-/obj/item/melee/touch_attack/mime_malaise/afterattack__legacy__attackchain(atom/target, mob/living/carbon/user, proximity)
- if(!proximity || target == user || !ishuman(target) || !iscarbon(user) || HAS_TRAIT(user, TRAIT_HANDS_BLOCKED))
+/obj/item/melee/touch_attack/mime_malaise/after_attack(atom/target, mob/user, proximity_flag, click_parameters)
+ . = ..()
+ if(!proximity_flag || target == user || !ishuman(target) || !iscarbon(user) || HAS_TRAIT(user, TRAIT_HANDS_BLOCKED))
return
var/datum/effect_system/smoke_spread/s = new
@@ -29,7 +29,6 @@
var/mob/living/carbon/human/H = target
H.mimetouched()
- ..()
/mob/living/carbon/human/proc/mimetouched()
Weaken(14 SECONDS)
diff --git a/code/datums/spells/mind_transfer.dm b/code/datums/spells/mind_transfer.dm
index 441ea87cbe80..4690dec49729 100644
--- a/code/datums/spells/mind_transfer.dm
+++ b/code/datums/spells/mind_transfer.dm
@@ -2,7 +2,6 @@
name = "Mind Transfer"
desc = "This spell allows the user to switch bodies with a target."
- school = "transmutation"
base_cooldown = 600
clothes_req = FALSE
invocation = "GIN'YU CAPAN"
diff --git a/code/datums/spells/spacetime_dist.dm b/code/datums/spells/spacetime_dist.dm
index e7cee2a416a0..2e7a45c95eef 100644
--- a/code/datums/spells/spacetime_dist.dm
+++ b/code/datums/spells/spacetime_dist.dm
@@ -7,7 +7,6 @@
sound = 'sound/magic/strings.ogg'
action_icon_state = "spacetime"
- school = "transmutation"
base_cooldown = 30 SECONDS
clothes_req = TRUE
invocation = "none"
@@ -94,6 +93,13 @@
/obj/effect/cross_action/singularity_pull()
return
+/obj/effect/cross_action/spacetime_dist/Initialize(mapload)
+ . = ..()
+ var/static/list/loc_connections = list(
+ COMSIG_ATOM_ENTERED = PROC_REF(on_atom_entered),
+ )
+ AddElement(/datum/element/connect_loc, loc_connections)
+
/obj/effect/cross_action/spacetime_dist/proc/walk_link(atom/movable/AM)
if(linked_dist && walks_left > 0)
flick("purplesparkles", src)
@@ -106,9 +112,9 @@
AM.forceMove(get_turf(src))
cant_teleport = FALSE
-/obj/effect/cross_action/spacetime_dist/Crossed(atom/movable/AM, oldloc)
+/obj/effect/cross_action/spacetime_dist/proc/on_atom_entered(atom/source, atom/movable/entered, turf/old_loc)
if(!cant_teleport)
- walk_link(AM)
+ walk_link(entered)
/obj/effect/cross_action/spacetime_dist/attackby__legacy__attackchain(obj/item/W, mob/user, params)
if(user.drop_item(W))
@@ -123,4 +129,5 @@
/obj/effect/cross_action/spacetime_dist/Destroy()
cant_teleport = TRUE
linked_dist = null
+ RemoveElement(/datum/element/connect_loc)
return ..()
diff --git a/code/datums/spells/summonitem.dm b/code/datums/spells/summonitem.dm
index 2c4ceebcc561..53fad3cca44a 100644
--- a/code/datums/spells/summonitem.dm
+++ b/code/datums/spells/summonitem.dm
@@ -1,7 +1,6 @@
/datum/spell/summonitem
name = "Instant Summons"
desc = "This spell can be used to recall a previously marked item to your hand from anywhere in the universe."
- school = "transmutation"
base_cooldown = 100
clothes_req = FALSE
invocation = "GAR YOK"
diff --git a/code/datums/spells/touch_attacks.dm b/code/datums/spells/touch_attacks.dm
index 455bbdf5b0b0..f3aca8ff3fc0 100644
--- a/code/datums/spells/touch_attacks.dm
+++ b/code/datums/spells/touch_attacks.dm
@@ -53,7 +53,6 @@
desc = "This spell charges your hand with vile energy that can be used to violently explode victims."
hand_path = /obj/item/melee/touch_attack/disintegrate
- school = "evocation"
base_cooldown = 600
clothes_req = TRUE
cooldown_min = 200 //100 deciseconds reduction per rank
@@ -65,9 +64,19 @@
desc = "This spell charges your hand with the power to turn victims into inert statues for a long period of time."
hand_path = /obj/item/melee/touch_attack/fleshtostone
- school = "transmutation"
base_cooldown = 600
clothes_req = TRUE
cooldown_min = 200 //100 deciseconds reduction per rank
action_icon_state = "statue"
+
+/datum/spell/touch/plushify
+ name = "Plushify"
+ desc = "This spell charges your hand with the power to turn your victims into marketable plushies!"
+ hand_path = /obj/item/melee/touch_attack/plushify
+
+ base_cooldown = 600
+ clothes_req = TRUE
+ cooldown_min = 200 //100 deciseconds reduction per rank
+
+ action_icon_state = "plush"
diff --git a/code/datums/spells/wizard_spells.dm b/code/datums/spells/wizard_spells.dm
index 3b70407a1a89..5466ee845cf3 100644
--- a/code/datums/spells/wizard_spells.dm
+++ b/code/datums/spells/wizard_spells.dm
@@ -2,7 +2,6 @@
name = "Magic Missile"
desc = "This spell fires several, slow moving, magic projectiles at nearby targets."
- school = "evocation"
base_cooldown = 200
clothes_req = TRUE
invocation = "FORTI GY AMA"
@@ -40,7 +39,6 @@
name = "Honk Missile"
desc = "This spell fires several, slow moving, magic bikehorns at nearby targets."
- school = "evocation"
base_cooldown = 6 SECONDS
clothes_req = FALSE
invocation = "HONK GY AMA"
@@ -87,7 +85,6 @@
name = "Mutate"
desc = "This spell causes you to turn into a hulk and gain laser vision for a short while."
- school = "transmutation"
base_cooldown = 400
clothes_req = TRUE
invocation = "BIRUZ BENNAR"
@@ -113,7 +110,6 @@
name = "Smoke"
desc = "This spell spawns a cloud of choking smoke at your location and does not require wizard garb."
- school = "conjuration"
base_cooldown = 120
clothes_req = FALSE
invocation = "none"
@@ -146,7 +142,6 @@
name = "Blink"
desc = "This spell randomly teleports you a short distance."
- school = "abjuration"
base_cooldown = 20
clothes_req = TRUE
invocation = "none"
@@ -174,7 +169,6 @@
name = "Teleport"
desc = "This spell teleports you to a type of area of your selection."
- school = "abjuration"
base_cooldown = 600
clothes_req = TRUE
invocation = "SCYAR NILA"
@@ -194,7 +188,6 @@
name = "Return to Teacher"
desc = "This spell teleports you back to your teacher."
- school = "abjuration"
base_cooldown = 30 SECONDS
clothes_req = TRUE
invocation = "SCYAR TESO"
@@ -217,7 +210,6 @@
name = "Force Wall"
desc = "This spell creates a 3 tile wide unbreakable wall that only you can pass through, and does not need wizard garb. Lasts 30 seconds."
- school = "transmutation"
base_cooldown = 15 SECONDS
clothes_req = FALSE
invocation = "TARCOL MINTI ZHERI"
@@ -259,7 +251,6 @@
name = "Summon Carp"
desc = "This spell conjures a simple carp."
- school = "conjuration"
base_cooldown = 1200
clothes_req = TRUE
invocation = "NOUK FHUNMM SACP RISSKA"
@@ -274,7 +265,6 @@
name = "Artificer"
desc = "This spell conjures a construct which may be controlled by Shades."
- school = "conjuration"
base_cooldown = 600
clothes_req = FALSE
invocation = "none"
@@ -290,7 +280,6 @@
name = "Summon Creature Swarm"
desc = "This spell tears the fabric of reality, allowing horrific daemons to spill forth."
- school = "conjuration"
base_cooldown = 1200
clothes_req = FALSE
invocation = "IA IA"
@@ -304,7 +293,6 @@
/datum/spell/blind
name = "Blind"
desc = "This spell temporarily blinds a single person and does not require wizard garb."
- school = "transmutation"
base_cooldown = 15 SECONDS
clothes_req = FALSE
invocation = "STI KALY"
@@ -335,7 +323,6 @@
name = "Fireball"
desc = "This spell fires a fireball at a target and does not require wizard garb."
- school = "evocation"
base_cooldown = 60
clothes_req = FALSE
invocation = "ONI SOMA"
@@ -478,7 +465,6 @@
/datum/spell/corpse_explosion
name = "Corpse Explosion"
desc = "Fills a corpse with energy, causing it to explode violently."
- school = "evocation"
base_cooldown = 5 SECONDS
clothes_req = TRUE
invocation = "JAH ITH BER"
diff --git a/code/datums/station_traits/neutral_traits.dm b/code/datums/station_traits/neutral_traits.dm
index 26862ba60c1d..03cac88b537f 100644
--- a/code/datums/station_traits/neutral_traits.dm
+++ b/code/datums/station_traits/neutral_traits.dm
@@ -93,10 +93,10 @@
. = ..()
for(var/obj/machinery/light/light in GLOB.machines)
var/turf/our_turf = get_turf(light)
- if(!is_station_level(our_turf.z))
- continue
- var/list/rgb = hsl2rgb(rand(0, 255) / 255, rand((0.4 * 255), 255) / 255, rand((0.5 * 255), (0.8 * 255)) / 255)
- var/new_color = "#[num2hex(rgb[1], 2)][num2hex(rgb[2], 2)][num2hex(rgb[3], 2)]"
- light.color = new_color
- light.brightness_color = new_color
- light.update(FALSE, TRUE, FALSE)
+ var/area/our_area = get_area(light)
+ if(is_station_level(our_turf.z) || istype(our_area, /area/mine/outpost) || istype(our_area, /area/mine/laborcamp))
+ var/list/rgb = hsl2rgb(rand(0, 255) / 255, rand((0.4 * 255), 255) / 255, rand((0.5 * 255), (0.8 * 255)) / 255)
+ var/new_color = "#[num2hex(rgb[1], 2)][num2hex(rgb[2], 2)][num2hex(rgb[3], 2)]"
+ light.color = new_color
+ light.brightness_color = new_color
+ light.update(FALSE, TRUE, FALSE)
diff --git a/code/game/area/ai_monitored.dm b/code/game/area/ai_monitored.dm
index db60b423c5d9..61c9cab09b78 100644
--- a/code/game/area/ai_monitored.dm
+++ b/code/game/area/ai_monitored.dm
@@ -20,7 +20,7 @@
cam.newTarget(O)
return
-/area/station/ai_monitored/Exited(atom/movable/O)
+/area/station/ai_monitored/Exited(atom/movable/O, direction)
..()
if(ismob(O) && length(motioncameras))
for(var/X in motioncameras)
diff --git a/code/game/atoms.dm b/code/game/atoms.dm
index 59548e2a038a..9d33e71059b7 100644
--- a/code/game/atoms.dm
+++ b/code/game/atoms.dm
@@ -311,6 +311,13 @@
return
/atom/proc/Bumped(atom/movable/AM)
+ // This may seem scary but one will find that replacing this with
+ // SHOULD_NOT_SLEEP(TRUE) surfaces two dozen instances where /Bumped sleeps,
+ // such as airlocks. We cannot wait for these procs to finish because they
+ // will clobber any movement which occurred in the intervening time. If we
+ // want to get rid of this we need to move bumping in its entirety to signal
+ // handlers, which is scarier.
+ set waitfor = FALSE
return
/// Convenience proc to see if a container is open for chemistry handling
@@ -333,9 +340,6 @@
/atom/proc/is_drainable()
return reagents && (container_type & DRAINABLE)
-/atom/proc/CheckExit()
- return TRUE
-
/atom/proc/HasProximity(atom/movable/AM)
return
@@ -1205,13 +1209,23 @@ GLOBAL_LIST_EMPTY(blood_splatter_icons)
/atom/Entered(atom/movable/AM, atom/oldLoc)
SEND_SIGNAL(src, COMSIG_ATOM_ENTERED, AM, oldLoc)
-/atom/Exit(atom/movable/AM, atom/newLoc)
- . = ..()
- if(SEND_SIGNAL(src, COMSIG_ATOM_EXIT, AM, newLoc) & COMPONENT_ATOM_BLOCK_EXIT)
+/**
+ * An atom is attempting to exit this atom's contents
+ *
+ * Default behaviour is to send the [COMSIG_ATOM_EXIT]
+ */
+/atom/Exit(atom/movable/leaving, direction)
+ // Don't call `..()` here, otherwise `Uncross()` gets called.
+ // See the doc comment on `Uncross()` to learn why this is bad.
+
+ if(SEND_SIGNAL(src, COMSIG_ATOM_EXIT, leaving, direction) & COMPONENT_ATOM_BLOCK_EXIT)
return FALSE
-/atom/Exited(atom/movable/AM, atom/newLoc)
- SEND_SIGNAL(src, COMSIG_ATOM_EXITED, AM, newLoc)
+ return TRUE
+
+/atom/Exited(atom/movable/AM, direction)
+ var/new_loc = get_step(AM, direction)
+ SEND_SIGNAL(src, COMSIG_ATOM_EXITED, AM, new_loc)
/*
Adds an instance of colour_type to the atom's atom_colours list
diff --git a/code/game/atoms_movable.dm b/code/game/atoms_movable.dm
index 0af908662076..cdae5dd85d25 100644
--- a/code/game/atoms_movable.dm
+++ b/code/game/atoms_movable.dm
@@ -3,6 +3,8 @@
appearance_flags = TILE_BOUND
glide_size = 8 // Default, adjusted when mobs move based on their movement delays
var/last_move = null
+ /// A list containing arguments for Moved().
+ VAR_PRIVATE/tmp/list/active_movement
var/anchored = FALSE
var/move_resist = MOVE_RESIST_DEFAULT
var/move_force = MOVE_FORCE_DEFAULT
@@ -16,9 +18,12 @@
var/no_spin = FALSE
var/no_spin_thrown = FALSE
var/mob/pulledby = null
+
var/atom/movable/pulling
/// Face towards the atom while pulling it
var/face_while_pulling = FALSE
+ /// Whether this atom should have its dir automatically changed when it moves. Setting this to FALSE allows for things such as directional windows to retain dir on moving without snowflake code all of the place.
+ var/set_dir_on_move = TRUE
var/throwforce = 0
var/inertia_dir = 0
@@ -199,16 +204,95 @@
/**
- * meant for movement with zero side effects. only use for objects that are supposed to move "invisibly" (like camera mobs or ghosts)
- * if you want something to move onto a tile with a beartrap or recycler or tripmine or mouse without that object knowing about it at all, use this
- * most of the time you want forceMove()
+ * Meant for movement with zero side effects. Only use for objects that are supposed to move "invisibly" (like camera mobs or ghosts).
+ * If you want something to move onto a tile with a beartrap or recycler or tripmine or mouse without that object knowing about it at all, use this.
+ * Most of the time you want [forceMove()].
*/
/atom/movable/proc/abstract_move(atom/new_loc)
+ RESOLVE_ACTIVE_MOVEMENT // This should NEVER happen, but, just in case...
var/atom/old_loc = loc
var/direction = get_dir(old_loc, new_loc)
loc = new_loc
Moved(old_loc, direction, TRUE)
+/// Here's where we rewrite how byond handles movement except slightly different.
+/// To be removed on step_ conversion.
+/// All this work to prevent a second bump.
+/atom/movable/Move(atom/newloc, direction, glide_size_override = 0, update_dir = TRUE)
+ . = FALSE
+ if(!newloc || newloc == loc)
+ return
+
+ // A mid-movement... movement... occured, resolve that first.
+ RESOLVE_ACTIVE_MOVEMENT
+
+ if(!direction)
+ direction = get_dir(src, newloc)
+
+ if(set_dir_on_move && dir != direction && update_dir)
+ setDir(direction)
+
+ var/is_multi_tile_object = is_multi_tile_object(src)
+
+ var/list/old_locs
+ if(is_multi_tile_object && isturf(loc))
+ old_locs = locs // locs is a special list, this is effectively the same as .Copy() but with less steps
+ for(var/atom/exiting_loc as anything in old_locs)
+ if(!exiting_loc.Exit(src, direction))
+ return
+ else
+ if(!loc.Exit(src, direction))
+ return
+
+ var/list/new_locs
+ if(is_multi_tile_object && isturf(newloc))
+ new_locs = block(
+ newloc,
+ locate(
+ min(world.maxx, newloc.x + CEILING(bound_width / 32, 1)),
+ min(world.maxy, newloc.y + CEILING(bound_height / 32, 1)),
+ newloc.z
+ )
+ ) // If this is a multi-tile object then we need to predict the new locs and check if they allow our entrance.
+ for(var/atom/entering_loc as anything in new_locs)
+ if(!entering_loc.Enter(src))
+ return
+ if(SEND_SIGNAL(src, COMSIG_MOVABLE_PRE_MOVE, entering_loc) & COMPONENT_MOVABLE_BLOCK_PRE_MOVE)
+ return
+ else // Else just try to enter the single destination.
+ if(!newloc.Enter(src))
+ return
+ if(SEND_SIGNAL(src, COMSIG_MOVABLE_PRE_MOVE, newloc) & COMPONENT_MOVABLE_BLOCK_PRE_MOVE)
+ return
+
+ // Past this is the point of no return
+ var/atom/oldloc = loc
+ var/area/oldarea = get_area(oldloc)
+ var/area/newarea = get_area(newloc)
+
+ SET_ACTIVE_MOVEMENT(oldloc, direction, FALSE, old_locs)
+ loc = newloc
+
+ . = TRUE
+
+ if(old_locs) // This condition will only be true if it is a multi-tile object.
+ for(var/atom/exited_loc as anything in (old_locs - new_locs))
+ exited_loc.Exited(src, direction)
+ else // Else there's just one loc to be exited.
+ oldloc.Exited(src, direction)
+ if(oldarea != newarea)
+ oldarea.Exited(src, direction)
+
+ if(new_locs) // Same here, only if multi-tile.
+ for(var/atom/entered_loc as anything in (new_locs - old_locs))
+ entered_loc.Entered(src, oldloc, old_locs)
+ else
+ newloc.Entered(src, oldloc, old_locs)
+ if(oldarea != newarea)
+ newarea.Entered(src, oldarea)
+
+ RESOLVE_ACTIVE_MOVEMENT
+
/atom/movable/Move(atom/newloc, direct = 0, movetime)
if(!loc || !newloc)
return FALSE
@@ -228,22 +312,21 @@
var/direct_NS = direct & (NORTH | SOUTH)
var/direct_EW = direct & (EAST | WEST)
var/first_step_target = get_step(src, direct_NS)
- Move(first_step_target, direct_NS)
+ step(src, direct_NS)
if(loc == first_step_target)
first_step_dir = direct_NS
moving_diagonally = SECOND_DIAG_STEP
- . = Move(get_step(src, direct_EW), direct_EW)
+ . = step(src, direct_EW)
else if(loc == oldloc)
first_step_target = get_step(src, direct_EW)
- Move(first_step_target, direct_EW)
+ step(src, direct_EW)
if(loc == first_step_target)
first_step_dir = direct_EW
moving_diagonally = SECOND_DIAG_STEP
- . = Move(get_step(src, direct_NS), direct_NS)
+ . = step(src, direct_NS)
if(first_step_dir != 0)
if(!.)
setDir(first_step_dir)
- Moved(oldloc, first_step_dir)
else if(!inertia_moving)
inertia_next_move = world.time + inertia_move_delay
newtonian_move(direct)
@@ -254,9 +337,6 @@
last_move = 0
return
- if(.)
- Moved(oldloc, direct)
-
last_move = direct
move_speed = world.time - l_move_time
l_move_time = world.time
@@ -265,14 +345,20 @@
. = FALSE
// Called after a successful Move(). By this point, we've already moved
-/atom/movable/proc/Moved(atom/OldLoc, Dir, Forced = FALSE)
- SEND_SIGNAL(src, COMSIG_MOVABLE_MOVED, OldLoc, Dir, Forced)
+/atom/movable/proc/Moved(atom/old_loc, Dir, Forced = FALSE)
+ SEND_SIGNAL(src, COMSIG_MOVABLE_MOVED, old_loc, Dir, Forced)
if(!inertia_moving)
inertia_next_move = world.time + inertia_move_delay
newtonian_move(Dir)
if(length(client_mobs_in_contents))
update_parallax_contents()
+ var/turf/old_turf = get_turf(old_loc)
+ var/turf/new_turf = get_turf(src)
+
+ if(old_turf?.z != new_turf?.z)
+ on_changed_z_level(old_turf, new_turf)
+
var/datum/light_source/L
var/thing
for(thing in light_sources) // Cycle through the light sources on this atom and tell them to update.
@@ -280,6 +366,50 @@
L.source_atom.update_light()
return TRUE
+// Make sure you know what you're doing if you call this
+// You probably want CanPass()
+/atom/movable/Cross(atom/movable/crossed_atom)
+ if(SEND_SIGNAL(src, COMSIG_MOVABLE_CHECK_CROSS, crossed_atom) & COMPONENT_BLOCK_CROSS)
+ return FALSE
+ if(SEND_SIGNAL(crossed_atom, COMSIG_MOVABLE_CHECK_CROSS_OVER, src) & COMPONENT_BLOCK_CROSS)
+ return FALSE
+ return CanPass(crossed_atom, get_dir(src, crossed_atom))
+
+///default byond proc that is deprecated for us in lieu of signals. do not call
+/atom/movable/Crossed(atom/movable/crossed_atom, oldloc)
+ SHOULD_NOT_OVERRIDE(TRUE)
+ CRASH("atom/movable/Crossed() was called!")
+
+/**
+ * `Uncross()` is a default BYOND proc that is called when something is *going*
+ * to exit this atom's turf. It is prefered over `Uncrossed` when you want to
+ * deny that movement, such as in the case of border objects, objects that allow
+ * you to walk through them in any direction except the one they block
+ * (think side windows).
+ *
+ * While being seemingly harmless, most everything doesn't actually want to
+ * use this, meaning that we are wasting proc calls for every single atom
+ * on a turf, every single time something exits it, when basically nothing
+ * cares.
+ *
+ * If you want to replicate the old `Uncross()` behavior, the most apt
+ * replacement is [`/datum/element/connect_loc`] while hooking onto
+ * [`COMSIG_ATOM_EXIT`].
+ */
+/atom/movable/Uncross()
+ SHOULD_NOT_OVERRIDE(TRUE)
+ CRASH("Uncross() should not be being called, please read the doc-comment for it for why.")
+
+/**
+ * default byond proc that is normally called on everything inside the previous turf
+ * a movable was in after moving to its current turf
+ * this is wasteful since the vast majority of objects do not use Uncrossed
+ * use connect_loc to register to COMSIG_ATOM_EXITED instead
+ */
+/atom/movable/Uncrossed(atom/movable/uncrossed_atom)
+ SHOULD_NOT_OVERRIDE(TRUE)
+ CRASH("/atom/movable/Uncrossed() was called")
+
// Change glide size for the duration of one movement
/atom/movable/proc/glide_for(movetime)
if(movetime)
@@ -289,57 +419,122 @@
else
glide_size = initial(glide_size)
-// Previously known as HasEntered()
-// This is automatically called when something enters your square
-/atom/movable/Crossed(atom/movable/AM, oldloc)
- SEND_SIGNAL(src, COMSIG_MOVABLE_CROSSED, AM)
- SEND_SIGNAL(AM, COMSIG_CROSSED_MOVABLE, src)
-
-/atom/movable/Uncrossed(atom/movable/AM)
- SEND_SIGNAL(src, COMSIG_MOVABLE_UNCROSSED, AM)
-
-/atom/movable/Bump(atom/bumped_atom, yes) //the "yes" arg is to differentiate our Bump proc from byond's, without it every Bump() call would become a double Bump(). // suffering
- if(bumped_atom && yes)
- SEND_SIGNAL(src, COMSIG_MOVABLE_BUMP, bumped_atom)
- if(!QDELETED(throwing))
- throwing.finalize(TRUE, bumped_atom)
- . = TRUE
- if(QDELETED(bumped_atom))
- return
- bumped_atom.Bumped(src)
+/atom/movable/Bump(atom/bumped_atom)
+ if(!bumped_atom)
+ CRASH("Bump was called with no argument.")
+ if(SEND_SIGNAL(src, COMSIG_MOVABLE_BUMP, bumped_atom) & COMPONENT_INTERCEPT_BUMPED)
+ return
+ . = ..()
+ if(!QDELETED(throwing))
+ throwing.finalize(hit = TRUE, target = bumped_atom)
+ . = TRUE
+ if(QDELETED(bumped_atom))
+ return
+ bumped_atom.Bumped(src)
/atom/movable/proc/forceMove(atom/destination)
- var/turf/old_loc = loc
- loc = destination
- moving_diagonally = 0
+ . = FALSE
+ if(destination)
+ . = doMove(destination)
+ else
+ CRASH("No valid destination passed into forceMove")
+
+/*
+ * Move ourself to nullspace. Use to indicate clearly that you
+ * know that you are doing so, as opposed to calling forceMove(null),
+ * accidentally or otherwise.
+ */
+/atom/movable/proc/moveToNullspace()
+ return doMove(null)
+
+/atom/movable/proc/doMove(atom/destination)
+ . = FALSE
+ RESOLVE_ACTIVE_MOVEMENT
- if(old_loc)
- old_loc.Exited(src, destination)
- for(var/atom/movable/AM in old_loc)
- AM.Uncrossed(src)
+ var/atom/oldloc = loc
+ var/is_multi_tile = bound_width > world.icon_size || bound_height > world.icon_size
+
+ SET_ACTIVE_MOVEMENT(oldloc, NONE, TRUE, null)
if(destination)
- destination.Entered(src)
- for(var/atom/movable/AM in destination)
- if(AM == src)
- continue
- AM.Crossed(src, old_loc)
- var/turf/oldturf = get_turf(old_loc)
- var/turf/destturf = get_turf(destination)
- var/old_z = (oldturf ? oldturf.z : null)
- var/dest_z = (destturf ? destturf.z : null)
- if(old_z != dest_z)
- onTransitZ(old_z, dest_z)
+ if(pulledby && !HAS_TRAIT(src, TRAIT_CURRENTLY_Z_MOVING))
+ pulledby.stop_pulling()
+
+ var/same_loc = oldloc == destination
+ var/area/old_area = get_area(oldloc)
+ var/area/destarea = get_area(destination)
+ var/movement_dir = get_dir(src, destination)
+
+ moving_diagonally = 0
+
+ loc = destination
+
+ if(!same_loc)
+ if(is_multi_tile && isturf(destination))
+ var/list/new_locs = block(
+ destination,
+ locate(
+ min(world.maxx, destination.x + ROUND_UP(bound_width / 32)),
+ min(world.maxy, destination.y + ROUND_UP(bound_height / 32)),
+ destination.z
+ )
+ )
+ if(old_area && old_area != destarea)
+ old_area.Exited(src, movement_dir)
+ for(var/atom/left_loc as anything in locs - new_locs)
+ left_loc.Exited(src, movement_dir)
+
+ for(var/atom/entering_loc as anything in new_locs - locs)
+ entering_loc.Entered(src, movement_dir)
+
+ if(old_area && old_area != destarea)
+ destarea.Entered(src, movement_dir)
+ else
+ if(oldloc)
+ oldloc.Exited(src, movement_dir)
+ if(old_area && old_area != destarea)
+ old_area.Exited(src, movement_dir)
+ destination.Entered(src, oldloc)
+ if(destarea && old_area != destarea)
+ destarea.Entered(src, old_area)
- Moved(old_loc, NONE)
+ . = TRUE
- return TRUE
+ //If no destination, move the atom into nullspace (don't do this unless you know what you're doing)
+ else
+ . = TRUE
+
+ if(oldloc)
+ loc = null
+ var/area/old_area = get_area(oldloc)
+ if(is_multi_tile && isturf(oldloc))
+ for(var/atom/old_loc as anything in locs)
+ old_loc.Exited(src, NONE)
+ else
+ oldloc.Exited(src, NONE)
+
+ if(old_area)
+ old_area.Exited(src, NONE)
+
+ RESOLVE_ACTIVE_MOVEMENT
+
+/**
+ * Called when a movable changes z-levels.
+ *
+ * Arguments:
+ * * old_turf - The previous turf they were on before.
+ * * new_turf - The turf they have now entered.
+ * * notify_contents - Whether or not to notify the movable's contents that their z-level has changed.
+ */
+/atom/movable/proc/on_changed_z_level(turf/old_turf, turf/new_turf, notify_contents = TRUE)
+ SHOULD_CALL_PARENT(TRUE)
+ SEND_SIGNAL(src, COMSIG_MOVABLE_Z_CHANGED, old_turf, new_turf)
+
+ if(!notify_contents)
+ return
-/atom/movable/proc/onTransitZ(old_z,new_z)
- for(var/item in src) // Notify contents of Z-transition. This can be overridden if we know the items contents do not care.
- var/atom/movable/AM = item
- AM.onTransitZ(old_z,new_z)
- SEND_SIGNAL(src, COMSIG_MOVABLE_Z_CHANGED)
+ for(var/atom/movable/content as anything in src) // Notify contents of Z-transition.
+ content.on_changed_z_level(old_turf, new_turf)
/mob/living/forceMove(atom/destination)
if(buckled)
@@ -539,7 +734,7 @@
/atom/movable/proc/move_crushed(atom/movable/pusher, force = MOVE_FORCE_DEFAULT, direction)
return FALSE
-/atom/movable/CanPass(atom/movable/mover, turf/target)
+/atom/movable/CanPass(atom/movable/mover, border_dir)
// This condition is copied from atom to avoid an extra parent call, because this is a very hot proc.
if(!density)
return TRUE
diff --git a/code/game/data_huds.dm b/code/game/data_huds.dm
index 7081814e916f..c0c0a7fca2ed 100644
--- a/code/game/data_huds.dm
+++ b/code/game/data_huds.dm
@@ -222,10 +222,10 @@
//HOOKS
-/mob/living/carbon/human/proc/sec_hud_set_ID()
+/mob/living/carbon/human/sec_hud_set_ID()
var/image/holder = hud_list[ID_HUD]
holder.icon_state = "hudunknown"
- if(wear_id)
+ if(wear_id && ! HAS_TRAIT(src, TRAIT_UNKNOWN))
holder.icon_state = "hud[ckey(wear_id.get_job_name())]"
sec_hud_set_security_status()
diff --git a/code/game/gamemodes/cult/cult_structures.dm b/code/game/gamemodes/cult/cult_structures.dm
index b948bad9da63..26066e3b5bf1 100644
--- a/code/game/gamemodes/cult/cult_structures.dm
+++ b/code/game/gamemodes/cult/cult_structures.dm
@@ -341,7 +341,4 @@ GLOBAL_LIST_INIT(blacklisted_pylon_turfs, typecacheof(list(
/obj/effect/gateway/Bumped(atom/movable/AM)
return
-/obj/effect/gateway/Crossed(atom/movable/AM, oldloc)
- return
-
#undef CULT_STRUCTURE_COOLDOWN
diff --git a/code/game/gamemodes/miniantags/guardian/types/protector.dm b/code/game/gamemodes/miniantags/guardian/types/protector.dm
index e385fd1e329d..756b4489bdea 100644
--- a/code/game/gamemodes/miniantags/guardian/types/protector.dm
+++ b/code/game/gamemodes/miniantags/guardian/types/protector.dm
@@ -125,7 +125,7 @@
color = linked_guardian.name_color
shield_orientation = left_or_right
-/obj/effect/guardianshield/CanPass(atom/movable/mover, turf/target)
+/obj/effect/guardianshield/CanPass(atom/movable/mover, border_dir)
if(mover == linked_guardian)
return TRUE
return FALSE
diff --git a/code/game/gamemodes/miniantags/guardian/types/ranged.dm b/code/game/gamemodes/miniantags/guardian/types/ranged.dm
index d3f67b67ef53..862fe56ff111 100644
--- a/code/game/gamemodes/miniantags/guardian/types/ranged.dm
+++ b/code/game/gamemodes/miniantags/guardian/types/ranged.dm
@@ -88,18 +88,25 @@
var/mob/living/spawner
invisibility = 101
+/obj/effect/snare/Initialize(mapload)
+ . = ..()
+ var/static/list/loc_connections = list(
+ COMSIG_ATOM_ENTERED = PROC_REF(on_atom_entered),
+ )
+ AddElement(/datum/element/connect_loc, loc_connections)
+
/obj/effect/snare/singularity_act()
return
/obj/effect/snare/singularity_pull()
return
-/obj/effect/snare/Crossed(AM as mob|obj, oldloc)
- if(isliving(AM))
+/obj/effect/snare/proc/on_atom_entered(datum/source, atom/movable/entered)
+ if(isliving(entered))
var/turf/snare_loc = get_turf(loc)
if(spawner)
- to_chat(spawner, "[AM] has crossed your surveillance trap at [get_area(snare_loc)].")
+ to_chat(spawner, "[entered] has crossed your surveillance trap at [get_area(snare_loc)].")
if(isguardian(spawner))
var/mob/living/simple_animal/hostile/guardian/G = spawner
if(G.summoner)
- to_chat(G.summoner, "[AM] has crossed your surveillance trap at [get_area(snare_loc)].")
+ to_chat(G.summoner, "[entered] has crossed your surveillance trap at [get_area(snare_loc)].")
diff --git a/code/game/gamemodes/miniantags/pulsedemon/cross_shock_component.dm b/code/game/gamemodes/miniantags/pulsedemon/cross_shock_component.dm
index 1c15fdb1a820..cf7da6be862b 100644
--- a/code/game/gamemodes/miniantags/pulsedemon/cross_shock_component.dm
+++ b/code/game/gamemodes/miniantags/pulsedemon/cross_shock_component.dm
@@ -3,11 +3,16 @@
var/energy_cost
var/delay_between_shocks
var/requires_cable
+
COOLDOWN_DECLARE(last_shock)
/datum/component/cross_shock/Initialize(_shock_damage, _energy_cost, _delay_between_shocks, _requires_cable = TRUE)
if(ismovable(parent))
- RegisterSignal(parent, list(COMSIG_MOVABLE_CROSSED, COMSIG_CROSSED_MOVABLE), PROC_REF(do_shock))
+ var/static/list/crossed_connections = list(
+ COMSIG_ATOM_ENTERED = PROC_REF(do_shock),
+ )
+ AddComponent(/datum/component/connect_loc_behalf, parent, crossed_connections)
+ RegisterSignal(parent, COMSIG_MOVABLE_MOVED, PROC_REF(on_movable_moved))
if(ismob(parent))
RegisterSignal(parent, COMSIG_CARBON_LOSE_ORGAN, PROC_REF(on_organ_removal))
else if(isarea(parent))
@@ -22,11 +27,18 @@
delay_between_shocks = _delay_between_shocks
requires_cable = _requires_cable
-/datum/component/cross_shock/proc/do_shock(datum/source, mob/living/thing_were_gonna_shock)
- SIGNAL_HANDLER
+/datum/component/cross_shock/proc/on_movable_moved(atom/source, old_location, direction, forced)
+ SIGNAL_HANDLER // COMSIG_MOVABLE_MOVED
+ if(isturf(source.loc))
+ for(var/mob/living/mob in source.loc)
+ do_shock(src, mob)
+
+/datum/component/cross_shock/proc/do_shock(atom/source, atom/movable/to_shock)
+ SIGNAL_HANDLER // COMSIG_ATOM_ENTERED
if(!COOLDOWN_FINISHED(src, last_shock))
return
- if(!istype(thing_were_gonna_shock))
+ var/mob/living/living_to_shock = to_shock
+ if(!istype(living_to_shock))
return
if(isliving(parent))
var/mob/living/M = parent
@@ -40,11 +52,11 @@
if(!our_cable || !our_cable.powernet || !our_cable.powernet.available_power)
return
var/area/to_deduct_from = get_area(our_cable)
- thing_were_gonna_shock.electrocute_act(shock_damage, source)
+ living_to_shock.electrocute_act(shock_damage, source)
to_deduct_from.powernet.use_active_power(energy_cost)
playsound(get_turf(parent), 'sound/effects/eleczap.ogg', 30, TRUE)
else
- thing_were_gonna_shock.electrocute_act(shock_damage, source)
+ living_to_shock.electrocute_act(shock_damage, source)
playsound(get_turf(parent), 'sound/effects/eleczap.ogg', 30, TRUE)
COOLDOWN_START(src, last_shock, delay_between_shocks)
diff --git a/code/game/gamemodes/miniantags/pulsedemon/pulsedemon.dm b/code/game/gamemodes/miniantags/pulsedemon/pulsedemon.dm
index c8ef3b159adb..babc34bc4619 100644
--- a/code/game/gamemodes/miniantags/pulsedemon/pulsedemon.dm
+++ b/code/game/gamemodes/miniantags/pulsedemon/pulsedemon.dm
@@ -131,9 +131,13 @@
ADD_TRAIT(src, TRAIT_AI_UNTRACKABLE, PULSEDEMON_TRAIT)
flags_2 |= RAD_NO_CONTAMINATE_2
- // don't step on me
- RegisterSignal(src, COMSIG_CROSSED_MOVABLE, PROC_REF(try_cross_shock))
- RegisterSignal(src, COMSIG_MOVABLE_CROSSED, PROC_REF(try_cross_shock))
+ // For when someone steps on us
+ var/static/list/loc_connections = list(
+ COMSIG_ATOM_ENTERED = PROC_REF(on_atom_entered)
+ )
+ AddElement(/datum/element/connect_loc, loc_connections)
+ // For when we move somewhere else
+ RegisterSignal(src, COMSIG_MOVABLE_MOVED, PROC_REF(on_movable_moved))
// drop demon onto ground if its loc is a non-turf and gets deleted
RegisterSignal(src, COMSIG_PARENT_PREQDELETED, PROC_REF(deleted_handler))
@@ -639,8 +643,18 @@
maxcharge = calc_maxcharge(length(hijacked_apcs)) + (maxcharge - calc_maxcharge(length(hijacked_apcs) - 1))
to_chat(src, "Hijacking complete! You now control [length(hijacked_apcs)] APCs.")
-/mob/living/simple_animal/demon/pulse_demon/proc/try_cross_shock(src, atom/A)
- SIGNAL_HANDLER
+/mob/living/simple_animal/demon/pulse_demon/proc/on_atom_entered(datum/source, atom/movable/entered)
+ SIGNAL_HANDLER // COMSIG_ATOM_ENTERED
+ try_cross_shock(entered)
+
+/mob/living/simple_animal/demon/pulse_demon/proc/on_movable_moved(datum/source, old_location, direction, forced)
+ SIGNAL_HANDLER // COMSIG_MOVABLE_MOVED
+ if(is_under_tile())
+ return
+ for(var/mob/living/mob in loc)
+ try_shock_mob(mob)
+
+/mob/living/simple_animal/demon/pulse_demon/proc/try_cross_shock(atom/movable/A)
if(!isliving(A) || is_under_tile())
return
var/mob/living/L = A
@@ -761,7 +775,7 @@
/mob/living/simple_animal/demon/pulse_demon/ex_act()
return
-/mob/living/simple_animal/demon/pulse_demon/CanPass(atom/movable/mover, turf/target)
+/mob/living/simple_animal/demon/pulse_demon/CanPass(atom/movable/mover, border_dir)
. = ..()
if(istype(mover, /obj/item/projectile/ion))
return FALSE
diff --git a/code/game/gamemodes/miniantags/pulsedemon/pulsedemon_abilities.dm b/code/game/gamemodes/miniantags/pulsedemon/pulsedemon_abilities.dm
index f611824bcf15..d248e460ba20 100644
--- a/code/game/gamemodes/miniantags/pulsedemon/pulsedemon_abilities.dm
+++ b/code/game/gamemodes/miniantags/pulsedemon/pulsedemon_abilities.dm
@@ -9,7 +9,6 @@
#define PD_UPGRADE_MAX_CHARGE "Capacity"
/datum/spell/pulse_demon
- school = "pulse demon"
clothes_req = FALSE
action_background_icon_state = "bg_pulsedemon"
var/locked = TRUE
diff --git a/code/game/gamemodes/wizard/godhand.dm b/code/game/gamemodes/wizard/godhand.dm
index 7481db80dc9e..9c5246972a9a 100644
--- a/code/game/gamemodes/wizard/godhand.dm
+++ b/code/game/gamemodes/wizard/godhand.dm
@@ -15,6 +15,7 @@
throwforce = 0
throw_range = 0
throw_speed = 0
+ new_attack_chain = TRUE
/obj/item/melee/touch_attack/New(spell)
attached_spell = spell
@@ -29,15 +30,15 @@
/obj/item/melee/touch_attack/customised_abstract_text(mob/living/carbon/owner)
return "[owner.p_their(TRUE)] [owner.l_hand == src ? "left hand" : "right hand"] is burning in magic fire."
-/obj/item/melee/touch_attack/attack__legacy__attackchain(mob/target, mob/living/carbon/user)
- if(!iscarbon(user)) //Look ma, no hands
- return
+/obj/item/melee/touch_attack/attack(mob/living/target, mob/living/carbon/human/user)
+ if(..() || !iscarbon(user)) //Look ma, no hands
+ return FINISH_ATTACK
if(HAS_TRAIT(user, TRAIT_HANDS_BLOCKED))
to_chat(user, "You can't reach out!")
- return
- ..()
+ return FINISH_ATTACK
-/obj/item/melee/touch_attack/afterattack__legacy__attackchain(atom/target, mob/user, proximity)
+/obj/item/melee/touch_attack/after_attack(atom/target, mob/user, proximity_flag, click_parameters)
+ . = ..()
if(catchphrase)
user.say(catchphrase)
playsound(get_turf(user), on_use_sound, 50, 1)
@@ -53,13 +54,13 @@
icon_state = "disintegrate"
item_state = "disintegrate"
-/obj/item/melee/touch_attack/disintegrate/afterattack__legacy__attackchain(atom/target, mob/living/carbon/user, proximity)
- if(!proximity || target == user || !ismob(target) || !iscarbon(user) || HAS_TRAIT(user, TRAIT_HANDS_BLOCKED)) //exploding after touching yourself would be bad
+/obj/item/melee/touch_attack/disintegrate/after_attack(atom/target, mob/user, proximity_flag, click_parameters)
+ . = ..()
+ if(!proximity_flag || target == user || !ismob(target) || !iscarbon(user) || HAS_TRAIT(user, TRAIT_HANDS_BLOCKED)) //exploding after touching yourself would be bad
return
var/mob/M = target
do_sparks(4, 0, M.loc) //no idea what the 0 is
M.gib()
- ..()
/obj/item/melee/touch_attack/fleshtostone
name = "petrifying touch"
@@ -69,13 +70,29 @@
icon_state = "fleshtostone"
item_state = "fleshtostone"
-/obj/item/melee/touch_attack/fleshtostone/afterattack__legacy__attackchain(atom/target, mob/living/carbon/user, proximity)
- if(!proximity || target == user || !isliving(target) || !iscarbon(user) || HAS_TRAIT(user, TRAIT_HANDS_BLOCKED)) //getting hard after touching yourself would also be bad
+/obj/item/melee/touch_attack/fleshtostone/after_attack(atom/target, mob/user, proximity_flag, click_parameters)
+ . = ..()
+ if(!proximity_flag || target == user || !isliving(target) || !iscarbon(user) || HAS_TRAIT(user, TRAIT_HANDS_BLOCKED)) //getting hard after touching yourself would also be bad
return
var/mob/living/L = target
L.Stun(4 SECONDS)
new /obj/structure/closet/statue(L.loc, L)
- ..()
+
+/obj/item/melee/touch_attack/plushify
+ name = "fabric touch"
+ desc = "The power to sew your foes into a doom cut from the fabric of fate."
+ catchphrase = "MAHR-XET 'ABL"
+ on_use_sound = 'sound/magic/smoke.ogg'
+ icon_state = "disintegrate"
+ item_state = "disintegrate"
+ color = COLOR_PURPLE
+
+/obj/item/melee/touch_attack/plushify/after_attack(atom/target, mob/user, proximity_flag, click_parameters)
+ . = ..()
+ if(!proximity_flag || target == user || !isliving(target) || !iscarbon(user) || HAS_TRAIT(user, TRAIT_HANDS_BLOCKED)) //There are better ways to get a good nights sleep in a bed.
+ return
+ var/mob/living/L = target
+ L.plushify()
/obj/item/melee/touch_attack/fake_disintegrate
name = "toy plastic hand"
@@ -86,12 +103,12 @@
item_state = "disintegrate"
needs_permit = FALSE
-/obj/item/melee/touch_attack/fake_disintegrate/afterattack__legacy__attackchain(atom/target, mob/living/carbon/user, proximity)
- if(!proximity || target == user || !ismob(target) || !iscarbon(user) || HAS_TRAIT(user, TRAIT_HANDS_BLOCKED)) //not exploding after touching yourself would be bad
+/obj/item/melee/touch_attack/fake_disintegrate/after_attack(atom/target, mob/user, proximity_flag, click_parameters)
+ . = ..()
+ if(!proximity_flag || target == user || !ismob(target) || !iscarbon(user) || HAS_TRAIT(user, TRAIT_HANDS_BLOCKED)) //not exploding after touching yourself would be bad
return
do_sparks(4, 0, target.loc)
playsound(target.loc, 'sound/goonstation/effects/gib.ogg', 50, 1)
- ..()
/obj/item/melee/touch_attack/cluwne
name = "cluwne touch"
@@ -101,8 +118,9 @@
icon_state = "cluwnecurse"
item_state = "cluwnecurse"
-/obj/item/melee/touch_attack/cluwne/afterattack__legacy__attackchain(atom/target, mob/living/carbon/user, proximity)
- if(!proximity || target == user || !ishuman(target) || !iscarbon(user) || HAS_TRAIT(user, TRAIT_HANDS_BLOCKED)) //clowning around after touching yourself would unsurprisingly, be bad
+/obj/item/melee/touch_attack/cluwne/after_attack(atom/target, mob/user, proximity_flag, click_parameters)
+ . = ..()
+ if(!proximity_flag || target == user || !ishuman(target) || !iscarbon(user) || HAS_TRAIT(user, TRAIT_HANDS_BLOCKED)) //clowning around after touching yourself would unsurprisingly, be bad
return
if(iswizard(target))
@@ -119,4 +137,3 @@
H.makeCluwne()
else
H.makeAntiCluwne()
- ..()
diff --git a/code/game/gamemodes/wizard/spellbook.dm b/code/game/gamemodes/wizard/spellbook.dm
index 60b8ac422dd8..ab14142c562f 100644
--- a/code/game/gamemodes/wizard/spellbook.dm
+++ b/code/game/gamemodes/wizard/spellbook.dm
@@ -152,6 +152,11 @@
spell_type = /datum/spell/touch/flesh_to_stone
category = "Offensive"
+/datum/spellbook_entry/plushify
+ name = "Plushify"
+ spell_type = /datum/spell/touch/plushify
+ category = "Offensive"
+
/datum/spellbook_entry/mutate
name = "Mutate"
spell_type = /datum/spell/genetic/mutate
diff --git a/code/game/machinery/OpTable.dm b/code/game/machinery/OpTable.dm
index ab86bc89afbd..d0bc0b0dca76 100644
--- a/code/game/machinery/OpTable.dm
+++ b/code/game/machinery/OpTable.dm
@@ -37,7 +37,7 @@
. = ..()
. += "Click-drag someone to the table to place them on top of the table."
-/obj/machinery/optable/CanPass(atom/movable/mover, turf/target)
+/obj/machinery/optable/CanPass(atom/movable/mover, border_dir)
if(istype(mover) && mover.checkpass(PASSTABLE))
return TRUE
if(isliving(mover))
diff --git a/code/game/machinery/camera/camera.dm b/code/game/machinery/camera/camera.dm
index 24bd6f3a7c02..ca131022a999 100644
--- a/code/game/machinery/camera/camera.dm
+++ b/code/game/machinery/camera/camera.dm
@@ -40,6 +40,7 @@
var/detectTime = 0
var/area/station/ai_monitored/area_motion = null
var/alarm_delay = 30 // Don't forget, there's another 3 seconds in queueAlarm()
+ var/datum/proximity_monitor/proximity_monitor
/// If this camera doesnt add to camera chunks. Used by camera bugs.
var/non_chunking_camera = FALSE
@@ -64,6 +65,12 @@
/obj/machinery/camera/proc/set_area_motion(area/A)
area_motion = A
+ create_prox_monitor()
+
+/obj/machinery/camera/proc/create_prox_monitor()
+ if(!proximity_monitor)
+ proximity_monitor = new(src, 1)
+ RegisterSignal(proximity_monitor, COMSIG_PARENT_QDELETING, PROC_REF(proximity_deleted))
/obj/machinery/camera/Moved(atom/OldLoc, Dir, Forced)
. = ..()
@@ -121,6 +128,10 @@
return
..()
+/obj/machinery/camera/proc/proximity_deleted()
+ SIGNAL_HANDLER // COMSIG_PARENT_QDELETING
+ proximity_monitor = null
+
/obj/machinery/camera/proc/setViewRange(num = CAMERA_VIEW_DISTANCE)
view_range = num
GLOB.cameranet.updateVisibility(src, 0)
diff --git a/code/game/machinery/camera/camera_presets.dm b/code/game/machinery/camera/camera_presets.dm
index 9c7f0fc61bc0..36a39afc6c2e 100644
--- a/code/game/machinery/camera/camera_presets.dm
+++ b/code/game/machinery/camera/camera_presets.dm
@@ -40,7 +40,7 @@
/obj/machinery/camera/tracking_head/Initialize(mapload)
. = ..()
- AddComponent(/datum/component/proximity_monitor, _radius = 6)
+ proximity_monitor = new(src, 6)
camera_overlay = new(get_turf(src))
switch(dir)
if(NORTH)
@@ -155,7 +155,7 @@
if(name == initial(name))
name = "motion-sensitive security camera"
assembly.upgrades.Add(new /obj/item/assembly/prox_sensor(assembly))
- AddComponent(/datum/component/proximity_monitor, CAMERA_VIEW_DISTANCE)
+ proximity_monitor = new(src, CAMERA_VIEW_DISTANCE)
setPowerUsage()
// Add it to machines that process
START_PROCESSING(SSmachines, src)
diff --git a/code/game/machinery/deployable.dm b/code/game/machinery/deployable.dm
index 057eeaa7ce90..3fc127338fdf 100644
--- a/code/game/machinery/deployable.dm
+++ b/code/game/machinery/deployable.dm
@@ -57,7 +57,7 @@
update_icon()
return TRUE
-/obj/structure/barricade/CanPass(atom/movable/mover, turf/target)//So bullets will fly over and stuff.
+/obj/structure/barricade/CanPass(atom/movable/mover, border_dir)//So bullets will fly over and stuff.
if(locate(/obj/structure/barricade) in get_turf(mover))
return TRUE
else if(istype(mover) && mover.checkpass(PASSBARRICADE))
@@ -450,12 +450,15 @@
0, 0, 0, 1
)
color = target_matrix
+ var/static/list/loc_connections = list(
+ COMSIG_ATOM_ENTERED = PROC_REF(on_atom_entered),
+ )
+ AddElement(/datum/element/connect_loc, loc_connections)
-/obj/structure/barricade/dropwall/firewall/Crossed(atom/movable/AM, oldloc)
- . = ..()
- if(!isprojectile(AM))
+/obj/structure/barricade/dropwall/firewall/proc/on_atom_entered(datum/source, atom/movable/entered)
+ if(!isprojectile(entered))
return
- var/obj/item/projectile/P = AM
+ var/obj/item/projectile/P = entered
P.immolate ++
/obj/item/grenade/turret
@@ -496,7 +499,7 @@
. = ..()
. += "It would need [(5 - foam_level)] more blobs of foam to fully block the airlock."
-/obj/structure/barricade/foam/CanPass(atom/movable/mover, turf/target)
+/obj/structure/barricade/foam/CanPass(atom/movable/mover, border_dir)
return istype(mover, /obj/item/projectile/c_foam) // Only c_foam blobs hit the airlock underneat/pass through the foam. The rest is hitting the barricade
/obj/structure/barricade/foam/welder_act(mob/user, obj/item/I)
diff --git a/code/game/machinery/doors/airlock.dm b/code/game/machinery/doors/airlock.dm
index 43db2c080cf1..63907c3abe3e 100644
--- a/code/game/machinery/doors/airlock.dm
+++ b/code/game/machinery/doors/airlock.dm
@@ -90,6 +90,8 @@ GLOBAL_LIST_EMPTY(airlock_emissive_underlays)
var/heat_resistance = 1500
/// Have we created sparks too recently?
var/on_spark_cooldown = FALSE
+ /// Synced with icon state for checking on callbacks
+ var/airlock_state
var/mutable_appearance/old_buttons_underlay
var/mutable_appearance/old_lights_underlay
@@ -392,6 +394,7 @@ GLOBAL_LIST_EMPTY(airlock_emissive_underlays)
if(AIRLOCK_DENY, AIRLOCK_OPENING, AIRLOCK_CLOSING, AIRLOCK_EMAG)
icon_state = "nonexistenticonstate" //MADNESS
+ airlock_state = state
. = ..(UPDATE_ICON_STATE) // Sent after the icon_state is changed
set_airlock_overlays(state)
@@ -624,8 +627,11 @@ GLOBAL_LIST_EMPTY(airlock_emissive_underlays)
if(stat == CONSCIOUS)
update_icon(AIRLOCK_DENY)
playsound(src, doorDeni, 50, FALSE, 3)
- sleep(6)
- update_icon(AIRLOCK_CLOSED)
+ addtimer(CALLBACK(src, PROC_REF(handle_deny_end)), 0.6 SECONDS)
+
+/obj/machinery/door/airlock/proc/handle_deny_end()
+ if(airlock_state == AIRLOCK_DENY)
+ update_icon(AIRLOCK_CLOSED)
/obj/machinery/door/airlock/examine(mob/user)
. = ..()
@@ -770,7 +776,7 @@ GLOBAL_LIST_EMPTY(airlock_emissive_underlays)
if(user)
attack_ai(user)
-/obj/machinery/door/airlock/CanPass(atom/movable/mover, turf/target)
+/obj/machinery/door/airlock/CanPass(atom/movable/mover, border_dir)
if(istype(mover) && !locked)
if(mover.checkpass(PASSDOOR))
return TRUE
diff --git a/code/game/machinery/doors/airlock_types.dm b/code/game/machinery/doors/airlock_types.dm
index b4551ee40710..686cf95dc02c 100644
--- a/code/game/machinery/doors/airlock_types.dm
+++ b/code/game/machinery/doors/airlock_types.dm
@@ -704,7 +704,7 @@
qdel(src)
/// Multi-tile airlocks (using a filler panel) have special handling for movables with PASS_FLAG_GLASS
-/obj/airlock_filler_object/CanPass(atom/movable/mover, turf/target)
+/obj/airlock_filler_object/CanPass(atom/movable/mover, border_dir)
. = ..()
if(.)
return
diff --git a/code/game/machinery/doors/door.dm b/code/game/machinery/doors/door.dm
index 2c1ce8187f77..6b90f4650b56 100644
--- a/code/game/machinery/doors/door.dm
+++ b/code/game/machinery/doors/door.dm
@@ -133,7 +133,7 @@
update_bounds()
-/obj/machinery/door/CanPass(atom/movable/mover, turf/target)
+/obj/machinery/door/CanPass(atom/movable/mover, border_dir)
if(istype(mover))
if(mover.checkpass(PASSDOOR) && !locked)
return TRUE
diff --git a/code/game/machinery/doors/firedoor.dm b/code/game/machinery/doors/firedoor.dm
index efc341602041..39ff3baf9723 100644
--- a/code/game/machinery/doors/firedoor.dm
+++ b/code/game/machinery/doors/firedoor.dm
@@ -290,7 +290,7 @@
F.update_icon()
qdel(src)
-/obj/machinery/door/firedoor/CanPass(atom/movable/mover, turf/target)
+/obj/machinery/door/firedoor/CanPass(atom/movable/mover, border_dir)
if(..())
return TRUE
if(isliving(mover) && !locked)
@@ -303,26 +303,37 @@
flags = ON_BORDER
can_crush = FALSE
+/obj/machinery/door/firedoor/border_only/Initialize(mapload)
+ . = ..()
+
+ var/static/list/loc_connections = list(
+ COMSIG_ATOM_EXIT = PROC_REF(on_atom_exit),
+ )
+
+ AddElement(/datum/element/connect_loc, loc_connections)
+
/obj/machinery/door/firedoor/border_only/closed
icon_state = "door_closed"
opacity = TRUE
density = TRUE
-/obj/machinery/door/firedoor/border_only/CanPass(atom/movable/mover, turf/target)
+/obj/machinery/door/firedoor/border_only/CanPass(atom/movable/mover, border_dir)
if(istype(mover) && mover.checkpass(PASSGLASS))
return 1
- if(get_dir(loc, target) == dir) //Make sure looking at appropriate border
+ if(border_dir == dir) //Make sure looking at appropriate border
return !density
else
return 1
-/obj/machinery/door/firedoor/border_only/CheckExit(atom/movable/mover, turf/target)
- if(istype(mover) && mover.checkpass(PASSGLASS))
- return 1
- if(get_dir(loc, target) == dir)
- return !density
- else
- return TRUE
+/obj/machinery/door/firedoor/border_only/proc/on_atom_exit(datum/source, atom/movable/leaving, direction)
+ SIGNAL_HANDLER // COMSIG_ATOM_EXIT
+
+ if(istype(leaving) && leaving.checkpass(PASSGLASS))
+ return
+
+ if(direction == dir && density)
+ leaving.Bump(src)
+ return COMPONENT_ATOM_BLOCK_EXIT
/obj/machinery/door/firedoor/border_only/CanAtmosPass(direction)
if(direction == dir)
diff --git a/code/game/machinery/doors/unpowered.dm b/code/game/machinery/doors/unpowered.dm
deleted file mode 100644
index 49a6343e8ee1..000000000000
--- a/code/game/machinery/doors/unpowered.dm
+++ /dev/null
@@ -1,20 +0,0 @@
-/obj/machinery/door/unpowered
- explosion_block = 1
-
-/obj/machinery/door/unpowered/Bumped(atom/AM)
- if(locked)
- return
- ..()
-
-/obj/machinery/door/unpowered/attackby__legacy__attackchain(obj/item/I, mob/user, params)
- if(locked)
- return
- else
- return ..()
-
-/obj/machinery/door/unpowered/emag_act()
- return
-
-/obj/machinery/door/unpowered/shuttle
- icon = 'icons/turf/shuttle.dmi'
- icon_state = "door1"
diff --git a/code/game/machinery/doors/windowdoor.dm b/code/game/machinery/doors/windowdoor.dm
index 8873abb0bfda..a3f244b92a8b 100644
--- a/code/game/machinery/doors/windowdoor.dm
+++ b/code/game/machinery/doors/windowdoor.dm
@@ -32,6 +32,12 @@
if(req_access && length(req_access))
base_state = icon_state
+ var/static/list/loc_connections = list(
+ COMSIG_ATOM_EXIT = PROC_REF(on_atom_exit),
+ )
+
+ AddElement(/datum/element/connect_loc, loc_connections)
+
if(name != initial(name))
return
var/new_name = get_area_name(src)
@@ -140,14 +146,14 @@
return mob_dir & unres_sides
-/obj/machinery/door/window/CanPass(atom/movable/mover, turf/target)
+/obj/machinery/door/window/CanPass(atom/movable/mover, border_dir)
if(istype(mover) && mover.checkpass(PASSGLASS))
return TRUE
if(isliving(mover))
var/mob/living/living_mover = mover
if(HAS_TRAIT(living_mover, TRAIT_CONTORTED_BODY) && IS_HORIZONTAL(living_mover))
return TRUE
- if(get_dir(loc, target) == dir) //Make sure looking at appropriate border
+ if(border_dir == dir) //Make sure looking at appropriate border
return !density
if(istype(mover, /obj/structure/window))
var/obj/structure/window/W = mover
@@ -172,17 +178,20 @@
/obj/machinery/door/window/CanPathfindPass(to_dir, datum/can_pass_info/pass_info)
return !density || (dir != to_dir) || (check_access_list(pass_info.access) && hasPower())
-/obj/machinery/door/window/CheckExit(atom/movable/mover, turf/target)
- if(istype(mover) && mover.checkpass(PASSGLASS))
- return TRUE
- if(isliving(mover))
- var/mob/living/living_mover = mover
+/obj/machinery/door/window/proc/on_atom_exit(datum/source, atom/movable/leaving, direction)
+ SIGNAL_HANDLER // COMSIG_ATOM_EXIT
+
+ if(istype(leaving) && leaving.checkpass(PASSGLASS))
+ return
+
+ if(isliving(leaving))
+ var/mob/living/living_mover = leaving
if(HAS_TRAIT(living_mover, TRAIT_CONTORTED_BODY) && IS_HORIZONTAL(living_mover))
- return TRUE
- if(get_dir(loc, target) == dir)
- return !density
- else
- return 1
+ return
+
+ if(direction == dir && density)
+ leaving.Bump(src)
+ return COMPONENT_ATOM_BLOCK_EXIT
/obj/machinery/door/window/open(forced=0)
if(operating) //doors can still open when emag-disabled
@@ -199,16 +208,15 @@
set_opacity(FALSE)
playsound(loc, 'sound/machines/windowdoor.ogg', 100, 1)
icon_state ="[base_state]open"
- sleep(10)
+ addtimer(CALLBACK(src, PROC_REF(finish_open)), 8)
+/obj/machinery/door/window/proc/finish_open()
density = FALSE
-// sd_set_opacity(0) //TODO: why is this here? Opaque windoors? ~Carn
recalculate_atmos_connectivity()
update_freelook_sight()
if(operating) //emag again
operating = NONE
- return 1
/obj/machinery/door/window/close(forced=0)
if(operating)
diff --git a/code/game/machinery/flasher.dm b/code/game/machinery/flasher.dm
index 69f22c6d0f16..fbb7298600cb 100644
--- a/code/game/machinery/flasher.dm
+++ b/code/game/machinery/flasher.dm
@@ -15,6 +15,7 @@
var/strength = 10 SECONDS //How weakened targets are when flashed.
var/base_state = "mflash"
anchored = TRUE
+ var/datum/proximity_monitor/proximity_monitor
/obj/machinery/flasher/Initialize(mapload)
. = ..()
@@ -32,7 +33,7 @@
/obj/machinery/flasher/portable/Initialize(mapload)
. = ..()
- AddComponent(/datum/component/proximity_monitor)
+ proximity_monitor = new(src, range)
/obj/machinery/flasher/power_change()
if(!..())
diff --git a/code/game/machinery/machine_frame.dm b/code/game/machinery/machine_frame.dm
index 3de4eda064be..fb6dbae4e02f 100644
--- a/code/game/machinery/machine_frame.dm
+++ b/code/game/machinery/machine_frame.dm
@@ -251,9 +251,9 @@
qdel(O)
new_machine.component_parts = list()
for(var/obj/O in src)
- O.forceMove(null)
+ O.moveToNullspace()
new_machine.component_parts += O
- circuit.forceMove(null)
+ circuit.moveToNullspace()
new_machine.RefreshParts()
qdel(src)
diff --git a/code/game/machinery/portable_turret.dm b/code/game/machinery/portable_turret.dm
index 77fec4620b94..dc292c44f28d 100644
--- a/code/game/machinery/portable_turret.dm
+++ b/code/game/machinery/portable_turret.dm
@@ -1225,3 +1225,9 @@ GLOBAL_LIST_EMPTY(turret_icons)
/obj/machinery/porta_turret/inflatable_turret/CanPathfindPass(to_dir, datum/can_pass_info/pass_info)
return ((stat & BROKEN) || !pass_info.is_living)
+
+// Meatpackers' ruin turret
+/obj/machinery/porta_turret/meatpacker_ship
+ name = "ship defense turret"
+ lethal = TRUE
+ check_synth = TRUE
diff --git a/code/game/machinery/quantum_pad.dm b/code/game/machinery/quantum_pad.dm
index a57de59b9f15..f5f31efc6fb8 100644
--- a/code/game/machinery/quantum_pad.dm
+++ b/code/game/machinery/quantum_pad.dm
@@ -1,3 +1,6 @@
+#define QPAD_ANIM_WINDUP 0.8 SECONDS
+#define QPAD_ANIM_COOLDOWN 0.7 SECONDS
+
/obj/machinery/quantumpad
name = "quantum pad"
desc = "A bluespace quantum-linked telepad used for teleporting objects to other quantum pads."
@@ -168,57 +171,70 @@
else
to_chat(user, "Linked pad is not on or near any active cameras on the station.")
-/obj/machinery/quantumpad/proc/sparks()
- do_sparks(5, 1, get_turf(src))
-
/obj/machinery/quantumpad/attack_ghost(mob/dead/observer/ghost)
if(!QDELETED(linked_pad))
ghost.forceMove(get_turf(linked_pad))
+/obj/machinery/quantumpad/proc/precharge()
+ layer = HIGH_OBJ_LAYER
+ linked_pad.layer = HIGH_OBJ_LAYER
+ flick("qpad-beam", src)
+ flick("qpad-beam", linked_pad)
+ addtimer(CALLBACK(src, PROC_REF(finish_teleport)), max((teleport_speed + QPAD_ANIM_COOLDOWN), 1))
+
+/obj/machinery/quantumpad/proc/finish_teleport()
+ if(!teleporting)
+ layer = BELOW_OBJ_LAYER
+ linked_pad.layer = BELOW_OBJ_LAYER
+
/obj/machinery/quantumpad/proc/doteleport(mob/user)
if(linked_pad)
playsound(get_turf(src), 'sound/weapons/flash.ogg', 25, 1)
teleporting = TRUE
+ src.icon_state = "qpad-charge"
+ linked_pad.icon_state = "qpad-charge"
+ addtimer(CALLBACK(src, PROC_REF(precharge)), max((teleport_speed - QPAD_ANIM_WINDUP), 1))
+ addtimer(CALLBACK(src, PROC_REF(process_teleport)), teleport_speed)
+
+/obj/machinery/quantumpad/proc/process_teleport(mob/user)
+ if(!src || QDELETED(src))
+ teleporting = FALSE
+ return
+ if(stat & NOPOWER)
+ to_chat(user, "[src] is unpowered!")
+ teleporting = FALSE
+ return
+ if(!linked_pad || QDELETED(linked_pad) || linked_pad.stat & NOPOWER)
+ to_chat(user, "Linked pad is not responding to ping. Teleport aborted.")
+ teleporting = FALSE
+ return
- spawn(teleport_speed)
- if(!src || QDELETED(src))
- teleporting = FALSE
- return
- if(stat & NOPOWER)
- to_chat(user, "[src] is unpowered!")
- teleporting = FALSE
- return
- if(!linked_pad || QDELETED(linked_pad) || linked_pad.stat & NOPOWER)
- to_chat(user, "Linked pad is not responding to ping. Teleport aborted.")
- teleporting = FALSE
- return
-
- teleporting = FALSE
- last_teleport = world.time
-
- // use a lot of power
- use_power(10000 / power_efficiency)
- sparks()
- linked_pad.sparks()
-
- flick("qpad-beam", src)
- playsound(get_turf(src), 'sound/weapons/emitter2.ogg', 25, TRUE)
- flick("qpad-beam", linked_pad)
- playsound(get_turf(linked_pad), 'sound/weapons/emitter2.ogg', 25, TRUE)
- var/tele_success = TRUE
- for(var/atom/movable/ROI in get_turf(src))
- // if is anchored, don't let through
- if(ROI.anchored)
- if(isliving(ROI))
- var/mob/living/L = ROI
- if(L.buckled)
- // TP people on office chairs
- if(L.buckled.anchored)
- continue
- else
- continue
- else if(!isobserver(ROI))
+ teleporting = FALSE
+ last_teleport = world.time
+
+ // use a lot of power
+ use_power(10000 / power_efficiency)
+ playsound(get_turf(src), 'sound/weapons/emitter2.ogg', 25, TRUE)
+ playsound(get_turf(linked_pad), 'sound/weapons/emitter2.ogg', 25, TRUE)
+ var/tele_success = TRUE
+ for(var/atom/movable/ROI in get_turf(src))
+ // if is anchored, don't let through
+ if(ROI.anchored)
+ if(isliving(ROI))
+ var/mob/living/L = ROI
+ if(L.buckled)
+ // TP people on office chairs
+ if(L.buckled.anchored)
continue
- tele_success = do_teleport(ROI, get_turf(linked_pad))
- if(!tele_success)
- to_chat(user, "Teleport failed due to bluespace interference.")
+ else
+ continue
+ else if(!isobserver(ROI))
+ continue
+ tele_success = do_teleport(ROI, get_turf(linked_pad), do_effect = FALSE)
+ if(!tele_success)
+ to_chat(user, "Teleport failed due to bluespace interference.")
+ src.icon_state = "qpad-idle"
+ linked_pad.icon_state = "qpad-idle"
+
+#undef QPAD_ANIM_WINDUP
+#undef QPAD_ANIM_COOLDOWN
diff --git a/code/game/machinery/shieldgen.dm b/code/game/machinery/shieldgen.dm
index 5f5723b4d2bc..2a8a67c77889 100644
--- a/code/game/machinery/shieldgen.dm
+++ b/code/game/machinery/shieldgen.dm
@@ -542,7 +542,7 @@
return
-/obj/machinery/shieldwall/CanPass(atom/movable/mover, turf/target)
+/obj/machinery/shieldwall/CanPass(atom/movable/mover, border_dir)
if(istype(mover) && mover.checkpass(PASSGLASS))
return prob(20)
else
@@ -557,7 +557,7 @@
desc = "A strange energy shield."
icon_state = "shield-red"
-/obj/machinery/shieldwall/syndicate/CanPass(atom/movable/mover, turf/target)
+/obj/machinery/shieldwall/syndicate/CanPass(atom/movable/mover, border_dir)
if(isliving(mover))
var/mob/living/M = mover
if("syndicate" in M.faction)
diff --git a/code/game/machinery/tcomms/relay.dm b/code/game/machinery/tcomms/relay.dm
index 7b98ef255c04..4a9aee89a9e0 100644
--- a/code/game/machinery/tcomms/relay.dm
+++ b/code/game/machinery/tcomms/relay.dm
@@ -70,7 +70,7 @@
*
* Handles parent call of disabling the machine if it changes Z-level, but also rebuilds the list of reachable levels on the linked core
*/
-/obj/machinery/tcomms/relay/onTransitZ(old_z, new_z)
+/obj/machinery/tcomms/relay/on_changed_z_level(turf/old_turf, turf/new_turf)
. = ..()
if(linked_core)
linked_core.refresh_zlevels()
diff --git a/code/game/machinery/tcomms/tcomms_base.dm b/code/game/machinery/tcomms/tcomms_base.dm
index f89810dd4d8d..6c7520a80b80 100644
--- a/code/game/machinery/tcomms/tcomms_base.dm
+++ b/code/game/machinery/tcomms/tcomms_base.dm
@@ -128,7 +128,7 @@ GLOBAL_LIST_EMPTY(tcomms_machines)
*
* Proc to make sure you cant have two of these active on a Z-level at once. It also makes sure to update the linkage
*/
-/obj/machinery/tcomms/onTransitZ(old_z, new_z)
+/obj/machinery/tcomms/on_changed_z_level(turf/old_turf, turf/new_turf)
. = ..()
if(active)
active = FALSE
diff --git a/code/game/machinery/tcomms/tcomms_core.dm b/code/game/machinery/tcomms/tcomms_core.dm
index 0d72b68d1d40..38b98fddda5a 100644
--- a/code/game/machinery/tcomms/tcomms_core.dm
+++ b/code/game/machinery/tcomms/tcomms_core.dm
@@ -140,7 +140,7 @@
*
* Handles parent call of disabling the machine if it changes Z-level, but also rebuilds the list of reachable levels
*/
-/obj/machinery/tcomms/core/onTransitZ(old_z, new_z)
+/obj/machinery/tcomms/core/on_changed_z_level(turf/old_turf, turf/new_turf)
. = ..()
refresh_zlevels()
diff --git a/code/game/machinery/teleporter.dm b/code/game/machinery/teleporter.dm
index 57c8e61d66e7..39449cab8259 100644
--- a/code/game/machinery/teleporter.dm
+++ b/code/game/machinery/teleporter.dm
@@ -373,6 +373,12 @@
component_parts += new /obj/item/stock_parts/matter_bin(null)
RefreshParts()
+ var/static/list/loc_connections = list(
+ COMSIG_ATOM_ENTERED = PROC_REF(on_atom_entered),
+ )
+ AddElement(/datum/element/connect_loc, loc_connections)
+
+
/obj/machinery/teleport/hub/upgraded/Initialize(mapload)
. = ..()
component_parts = list()
@@ -408,13 +414,15 @@
break
return power_station
-/obj/machinery/teleport/hub/Crossed(atom/movable/AM, oldloc)
+/obj/machinery/teleport/hub/proc/on_atom_entered(datum/source, atom/movable/entered)
+ SIGNAL_HANDLER // COMSIG_ATOM_ENTERED
+
if(!is_teleport_allowed(z) && !admin_usage)
- if(ismob(AM))
- to_chat(AM, "You can't use this here.")
+ if(ismob(entered))
+ to_chat(entered, "You can't use this here.")
return
- if(power_station && power_station.engaged && !panel_open && !blockAI(AM) && !iseffect(AM))
- if(!teleport(AM) && isliving(AM)) // the isliving(M) is needed to avoid triggering errors if a spark bumps the telehub
+ if(power_station && power_station.engaged && !panel_open && !blockAI(entered) && !iseffect(entered))
+ if(!teleport(entered) && isliving(entered)) // the isliving(M) is needed to avoid triggering errors if a spark bumps the telehub
visible_message("[src] emits a loud buzz, as its teleport portal flickers and fails!")
playsound(loc, 'sound/machines/buzz-sigh.ogg', 50, FALSE)
power_station.toggle() // turn off the portal.
@@ -491,6 +499,12 @@
. = ..()
update_lighting()
+ var/static/list/loc_connections = list(
+ COMSIG_ATOM_ENTERED = PROC_REF(on_atom_entered),
+ )
+ AddElement(/datum/element/connect_loc, loc_connections)
+
+
/obj/machinery/teleport/perma/process()
teleports_this_cycle = 0
@@ -503,15 +517,15 @@
tele_delay = max(A, 0)
update_icon(UPDATE_ICON_STATE)
-/obj/machinery/teleport/perma/Crossed(atom/movable/AM, oldloc)
+/obj/machinery/teleport/perma/proc/on_atom_entered(datum/source, atom/movable/entered)
if(stat & (BROKEN|NOPOWER))
return
if(!is_teleport_allowed(z))
- to_chat(AM, "You can't use this here.")
+ to_chat(entered, "You can't use this here.")
return
- if(target && !recalibrating && !panel_open && !blockAI(AM) && (teleports_this_cycle <= MAX_ALLOWED_TELEPORTS_PER_PROCESS))
- do_teleport(AM, target)
+ if(target && !recalibrating && !panel_open && !blockAI(entered) && (teleports_this_cycle <= MAX_ALLOWED_TELEPORTS_PER_PROCESS))
+ do_teleport(entered, target)
use_power(5000)
teleports_this_cycle++
if(tele_delay)
@@ -520,6 +534,9 @@
update_lighting()
addtimer(CALLBACK(src, PROC_REF(CrossedCallback)), tele_delay)
+/obj/machinery/teleport/perma/Destroy()
+ . = ..()
+
/obj/machinery/teleport/perma/proc/CrossedCallback()
recalibrating = FALSE
update_icon(UPDATE_ICON_STATE | UPDATE_OVERLAYS)
diff --git a/code/game/machinery/vendors/vending.dm b/code/game/machinery/vendors/vending.dm
index f8f509441d17..457ad4aba68b 100644
--- a/code/game/machinery/vendors/vending.dm
+++ b/code/game/machinery/vendors/vending.dm
@@ -142,6 +142,8 @@
/// How often will the vendor tip when you walk by it when aggressive is true?
var/aggressive_tilt_chance = 25
+ var/datum/proximity_monitor/proximity_monitor
+
/obj/machinery/economy/vending/Initialize(mapload)
. = ..()
var/build_inv = FALSE
@@ -175,7 +177,7 @@
RegisterSignal(src, COMSIG_MOVABLE_UNTILTED, PROC_REF(on_untilt))
RegisterSignal(src, COMSIG_MOVABLE_TRY_UNTILT, PROC_REF(on_try_untilt))
if(aggressive)
- AddComponent(/datum/component/proximity_monitor)
+ proximity_monitor = new(src, 1)
/obj/machinery/economy/vending/Destroy()
SStgui.close_uis(wires)
@@ -943,8 +945,9 @@
throw_item.throw_at(target, 16, 3)
visible_message("[src] launches [throw_item.name] at [target.name]!")
-/obj/machinery/economy/vending/onTransitZ()
- return
+/obj/machinery/economy/vending/on_changed_z_level(turf/old_turf, turf/new_turf, notify_contents = FALSE)
+ // Don't bother notifying contents (for some reason (probably historical reasons (probably for no reason)))
+ return ..()
/obj/machinery/economy/vending/proc/tilt(atom/victim, crit = FALSE, from_combat = FALSE, from_anywhere = FALSE)
if(QDELETED(src) || !has_gravity(src) || !tiltable || tilted)
diff --git a/code/game/mecha/mecha.dm b/code/game/mecha/mecha.dm
index 7590801455ad..285462a94333 100644
--- a/code/game/mecha/mecha.dm
+++ b/code/game/mecha/mecha.dm
@@ -404,7 +404,7 @@
if(. && stepsound)
playsound(src, stepsound, 40, 1)
-/obj/mecha/Bump(atom/obstacle, bump_allowed)
+/obj/mecha/Bump(atom/obstacle)
if(throwing) //high velocity mechas in your face!
var/breakthrough = FALSE
if(istype(obstacle, /obj/structure/window))
@@ -459,15 +459,14 @@
throw_at(crashing, 50, throw_speed)
else
- if(bump_allowed)
- if(..())
- return
- if(isobj(obstacle))
- var/obj/O = obstacle
- if(!O.anchored)
- step(obstacle, dir)
- else if(ismob(obstacle))
+ if(..())
+ return
+ if(isobj(obstacle))
+ var/obj/O = obstacle
+ if(!O.anchored)
step(obstacle, dir)
+ else if(ismob(obstacle))
+ step(obstacle, dir)
///////////////////////////////////
@@ -1271,10 +1270,11 @@
/obj/mecha/proc/pilot_mmi_hud(mob/living/brain/pilot)
return
-/obj/mecha/Exited(atom/movable/M, atom/newloc)
+/obj/mecha/Exited(atom/movable/M, direction)
+ var/new_loc = get_step(M, direction)
if(occupant && occupant == M) // The occupant exited the mech without calling go_out()
if(!isAI(occupant)) //This causes carded AIS to gib, so we do not want this to be called during carding.
- go_out(1, newloc)
+ go_out(1, new_loc)
/obj/mecha/proc/go_out(forced, atom/newloc = loc)
if(!occupant)
diff --git a/code/game/objects/effects/alien_acid.dm b/code/game/objects/effects/alien_acid.dm
index afc114502d52..a00b1c65ce03 100644
--- a/code/game/objects/effects/alien_acid.dm
+++ b/code/game/objects/effects/alien_acid.dm
@@ -22,6 +22,11 @@
pixel_x = target.pixel_x + rand(-4,4)
pixel_y = target.pixel_y + rand(-4,4)
+ var/static/list/loc_connections = list(
+ COMSIG_ATOM_ENTERED = PROC_REF(on_atom_entered),
+ )
+ AddElement(/datum/element/connect_loc, loc_connections)
+
START_PROCESSING(SSobj, src)
/obj/effect/acid/Destroy()
@@ -50,9 +55,12 @@
qdel(src)
return 0
-/obj/effect/acid/Crossed(AM as mob|obj)
- if(isliving(AM))
- var/mob/living/L = AM
+/obj/effect/acid/proc/on_atom_entered(datum/source, atom/movable/entered)
+ SIGNAL_HANDLER // COMSIG_ATOM_ENTERED
+ if(!isliving(entered) && !isobj(entered))
+ return
+ if(isliving(entered))
+ var/mob/living/L = entered
if(HAS_TRAIT(L, TRAIT_FLYING))
return
if(L.m_intent != MOVE_INTENT_WALK && prob(40))
diff --git a/code/game/objects/effects/anomalies.dm b/code/game/objects/effects/anomalies.dm
index fad65dc1bb65..89aa71313140 100644
--- a/code/game/objects/effects/anomalies.dm
+++ b/code/game/objects/effects/anomalies.dm
@@ -121,6 +121,11 @@
if(prob(75))
new /obj/item/shard(loc)
+ var/static/list/loc_connections = list(
+ COMSIG_ATOM_ENTERED = PROC_REF(on_atom_entered),
+ )
+ AddElement(/datum/element/connect_loc, loc_connections)
+
/obj/effect/anomaly/grav/Destroy()
vis_contents -= warp
QDEL_NULL(warp) // don't want to leave it hanging
@@ -147,9 +152,8 @@
animate(warp, time = 6, transform = matrix().Scale(0.5,0.5))
animate(time = 14, transform = matrix())
-/obj/effect/anomaly/grav/Crossed(atom/movable/AM)
- . = ..()
- gravShock(AM)
+/obj/effect/anomaly/grav/proc/on_atom_entered(datum/source, atom/movable/entered)
+ gravShock(entered)
/obj/effect/anomaly/grav/Bump(atom/A)
gravShock(A)
@@ -195,6 +199,10 @@
if(explosive)
zap_flags = ZAP_MOB_DAMAGE | ZAP_OBJ_DAMAGE | ZAP_MOB_STUN
power = 15000
+ var/static/list/loc_connections = list(
+ COMSIG_ATOM_ENTERED = PROC_REF(on_atom_entered)
+ )
+ AddElement(/datum/element/connect_loc, loc_connections)
/obj/effect/anomaly/flux/anomalyEffect()
..()
@@ -204,10 +212,8 @@
if(explosive) //Let us not fuck up the sm that much
tesla_zap(src, zap_range, power, zap_flags)
-
-/obj/effect/anomaly/flux/Crossed(atom/movable/AM)
- . = ..()
- mobShock(AM)
+/obj/effect/anomaly/flux/proc/on_atom_entered(datum/source, atom/movable/entered)
+ mobShock(entered)
/obj/effect/anomaly/flux/Bump(atom/A)
mobShock(A)
diff --git a/code/game/objects/effects/decals/Cleanable/humans.dm b/code/game/objects/effects/decals/Cleanable/humans.dm
index 38a79e45b2f6..f9d8e1f4acba 100644
--- a/code/game/objects/effects/decals/Cleanable/humans.dm
+++ b/code/game/objects/effects/decals/Cleanable/humans.dm
@@ -45,6 +45,11 @@
if(!. && !QDELETED(src))
dry_timer = addtimer(CALLBACK(src, PROC_REF(dry)), DRYING_TIME * (amount+1), TIMER_STOPPABLE)
+ var/static/list/loc_connections = list(
+ COMSIG_ATOM_ENTERED = PROC_REF(on_atom_entered),
+ )
+ AddElement(/datum/element/connect_loc, loc_connections)
+
/obj/effect/decal/cleanable/blood/Destroy()
if(dry_timer)
deltimer(dry_timer)
@@ -138,12 +143,7 @@
return FALSE
-/obj/effect/decal/cleanable/blood/Bump(atom/A, yes)
- // this is to prevent double or triple bumps from calling splat after src is qdel'd.
- // only god knows why this fixes the issue
- if(yes)
- return
-
+/obj/effect/decal/cleanable/blood/Bump(atom/A)
if(gravity_check)
return ..()
diff --git a/code/game/objects/effects/decals/Cleanable/misc_cleanables.dm b/code/game/objects/effects/decals/Cleanable/misc_cleanables.dm
index 7ff65230575a..8048ab7e23d1 100644
--- a/code/game/objects/effects/decals/Cleanable/misc_cleanables.dm
+++ b/code/game/objects/effects/decals/Cleanable/misc_cleanables.dm
@@ -171,15 +171,19 @@
animate_levitate(src, -1, rand(30, 120))
icon = 'icons/effects/blood_weightless.dmi'
-/obj/effect/decal/cleanable/vomit/Bump(atom/A, yes)
+ var/static/list/loc_connections = list(
+ COMSIG_ATOM_ENTERED = PROC_REF(on_atom_entered)
+ )
+ AddElement(/datum/element/connect_loc, loc_connections)
+
+/obj/effect/decal/cleanable/vomit/Bump(atom/A)
. = ..()
if(A.density)
splat(A)
-/obj/effect/decal/cleanable/vomit/Crossed(atom/movable/AM, oldloc)
+/obj/effect/decal/cleanable/vomit/proc/on_atom_entered(datum/source, atom/movable/entered)
if(!gravity_check)
- splat(AM)
- ..()
+ splat(entered)
/obj/effect/decal/cleanable/vomit/proc/splat(atom/A)
if(gravity_check)
diff --git a/code/game/objects/effects/decals/Cleanable/tar.dm b/code/game/objects/effects/decals/Cleanable/tar.dm
index 5cc09a4619cf..0b76030ca1df 100644
--- a/code/game/objects/effects/decals/Cleanable/tar.dm
+++ b/code/game/objects/effects/decals/Cleanable/tar.dm
@@ -14,6 +14,11 @@
if(prob(50))
icon_state = "tar3"
+ var/static/list/loc_connections = list(
+ COMSIG_ATOM_ENTERED = PROC_REF(on_atom_entered)
+ )
+ AddElement(/datum/element/connect_loc, loc_connections)
+
/obj/effect/decal/cleanable/tar/Destroy()
if(target)
target.slowdown -= 10
@@ -27,9 +32,9 @@
if(!issimulatedturf(target)) // We remove slowdown in Destroy(), so we run this check after adding the slowdown.
qdel(src)
-/obj/effect/decal/cleanable/tar/Crossed(atom/movable/movable_atom)
- if(isliving(movable_atom))
- var/mob/living/L = movable_atom
+/obj/effect/decal/cleanable/tar/proc/on_atom_entered(datum/source, atom/movable/entered)
+ if(isliving(entered))
+ var/mob/living/L = entered
playsound(L, 'sound/effects/attackblob.ogg', 50, TRUE)
to_chat(L, "[src] sticks to you!")
diff --git a/code/game/objects/effects/decals/Cleanable/tracks.dm b/code/game/objects/effects/decals/Cleanable/tracks.dm
index d7417d8452aa..431c97c6d7e7 100644
--- a/code/game/objects/effects/decals/Cleanable/tracks.dm
+++ b/code/game/objects/effects/decals/Cleanable/tracks.dm
@@ -39,61 +39,70 @@ GLOBAL_LIST_EMPTY(fluidtrack_cache)
blood_state = BLOOD_STATE_HUMAN //the icon state to load images from
gravity_check = ALWAYS_IN_GRAVITY
-/obj/effect/decal/cleanable/blood/footprints/Crossed(atom/movable/O, oldloc)
- ..()
- if(ishuman(O))
- var/mob/living/carbon/human/H = O
- var/obj/item/clothing/shoes/S = H.shoes
- var/obj/item/organ/external/l_foot = H.get_organ("l_foot")
- var/obj/item/organ/external/r_foot = H.get_organ("r_foot")
- var/hasfeet = TRUE
- if(!l_foot && !r_foot)
- hasfeet = FALSE
- if(S && S.bloody_shoes[blood_state] && S.blood_color == basecolor)
- S.bloody_shoes[blood_state] = max(S.bloody_shoes[blood_state] - BLOOD_LOSS_PER_STEP, 0)
- S.bloody_shoes[BLOOD_BASE_ALPHA] = base_alpha
- if(!S.blood_DNA)
- S.blood_DNA = list()
- S.blood_DNA |= blood_DNA.Copy()
- if(!(entered_dirs & H.dir))
- entered_dirs |= H.dir
- update_icon()
- else if(hasfeet && H.bloody_feet[blood_state] && H.feet_blood_color == basecolor)//Or feet //This will need to be changed.
- H.bloody_feet[blood_state] = max(H.bloody_feet[blood_state] - BLOOD_LOSS_PER_STEP, 0)
- H.bloody_feet[BLOOD_BASE_ALPHA] = base_alpha
- if(!H.feet_blood_DNA)
- H.feet_blood_DNA = list()
- H.feet_blood_DNA |= blood_DNA.Copy()
- if(!(entered_dirs & H.dir))
- entered_dirs |= H.dir
- update_icon()
-
-/obj/effect/decal/cleanable/blood/footprints/Uncrossed(atom/movable/O)
- ..()
- if(ishuman(O))
- var/mob/living/carbon/human/H = O
- var/obj/item/clothing/shoes/S = H.shoes
- var/obj/item/organ/external/l_foot = H.get_organ("l_foot")
- var/obj/item/organ/external/r_foot = H.get_organ("r_foot")
- var/hasfeet = TRUE
- if(!l_foot && !r_foot)
- hasfeet = FALSE
- if(S && S.bloody_shoes[blood_state] && S.blood_color == basecolor)
- S.bloody_shoes[blood_state] = max(S.bloody_shoes[blood_state] - BLOOD_LOSS_PER_STEP, 0)
- if(!S.blood_DNA)
- S.blood_DNA = list()
- S.blood_DNA |= blood_DNA.Copy()
- if(!(exited_dirs & H.dir))
- exited_dirs |= H.dir
- update_icon()
- else if(hasfeet && H.bloody_feet[blood_state] && H.feet_blood_color == basecolor)//Or feet
- H.bloody_feet[blood_state] = max(H.bloody_feet[blood_state] - BLOOD_LOSS_PER_STEP, 0)
- if(!H.feet_blood_DNA)
- H.feet_blood_DNA = list()
- H.feet_blood_DNA |= blood_DNA.Copy()
- if(!(exited_dirs & H.dir))
- exited_dirs |= H.dir
- update_icon()
+/obj/effect/decal/cleanable/blood/footprints/Initialize(mapload)
+ . = ..()
+ var/static/list/loc_connections = list(
+ COMSIG_ATOM_ENTERED = PROC_REF(on_atom_entered),
+ COMSIG_ATOM_EXITED = PROC_REF(on_atom_exited),
+ )
+ AddElement(/datum/element/connect_loc, loc_connections)
+
+/obj/effect/decal/cleanable/blood/footprints/on_atom_entered(datum/source, mob/living/carbon/human/H, ...)
+ if(!istype(H))
+ return
+
+ var/obj/item/clothing/shoes/S = H.shoes
+ var/obj/item/organ/external/l_foot = H.get_organ("l_foot")
+ var/obj/item/organ/external/r_foot = H.get_organ("r_foot")
+ var/hasfeet = TRUE
+ if(!l_foot && !r_foot)
+ hasfeet = FALSE
+ if(S && S.bloody_shoes[blood_state] && S.blood_color == basecolor)
+ S.bloody_shoes[blood_state] = max(S.bloody_shoes[blood_state] - BLOOD_LOSS_PER_STEP, 0)
+ S.bloody_shoes[BLOOD_BASE_ALPHA] = base_alpha
+ if(!S.blood_DNA)
+ S.blood_DNA = list()
+ S.blood_DNA |= blood_DNA.Copy()
+ if(!(entered_dirs & H.dir))
+ entered_dirs |= H.dir
+ update_icon()
+ else if(hasfeet && H.bloody_feet[blood_state] && H.feet_blood_color == basecolor)//Or feet //This will need to be changed.
+ H.bloody_feet[blood_state] = max(H.bloody_feet[blood_state] - BLOOD_LOSS_PER_STEP, 0)
+ H.bloody_feet[BLOOD_BASE_ALPHA] = base_alpha
+ if(!H.feet_blood_DNA)
+ H.feet_blood_DNA = list()
+ H.feet_blood_DNA |= blood_DNA.Copy()
+ if(!(entered_dirs & H.dir))
+ entered_dirs |= H.dir
+ update_icon()
+
+// TODO: I think this is a 1:1 copy-paste of on_atom_entered above
+/obj/effect/decal/cleanable/blood/footprints/proc/on_atom_exited(datum/source, mob/living/carbon/human/H, ...)
+ if(!istype(H))
+ return
+
+ var/obj/item/clothing/shoes/S = H.shoes
+ var/obj/item/organ/external/l_foot = H.get_organ("l_foot")
+ var/obj/item/organ/external/r_foot = H.get_organ("r_foot")
+ var/hasfeet = TRUE
+ if(!l_foot && !r_foot)
+ hasfeet = FALSE
+ if(S && S.bloody_shoes[blood_state] && S.blood_color == basecolor)
+ S.bloody_shoes[blood_state] = max(S.bloody_shoes[blood_state] - BLOOD_LOSS_PER_STEP, 0)
+ if(!S.blood_DNA)
+ S.blood_DNA = list()
+ S.blood_DNA |= blood_DNA.Copy()
+ if(!(exited_dirs & H.dir))
+ exited_dirs |= H.dir
+ update_icon()
+ else if(hasfeet && H.bloody_feet[blood_state] && H.feet_blood_color == basecolor)//Or feet
+ H.bloody_feet[blood_state] = max(H.bloody_feet[blood_state] - BLOOD_LOSS_PER_STEP, 0)
+ if(!H.feet_blood_DNA)
+ H.feet_blood_DNA = list()
+ H.feet_blood_DNA |= blood_DNA.Copy()
+ if(!(exited_dirs & H.dir))
+ exited_dirs |= H.dir
+ update_icon()
/obj/effect/decal/cleanable/blood/footprints/update_overlays()
diff --git a/code/game/objects/effects/decals/cleanable.dm b/code/game/objects/effects/decals/cleanable.dm
index cf914e6fb03f..6ad91e08db51 100644
--- a/code/game/objects/effects/decals/cleanable.dm
+++ b/code/game/objects/effects/decals/cleanable.dm
@@ -27,17 +27,15 @@
//Add "bloodiness" of this blood's type, to the human's shoes
//This is on /cleanable because fuck this ancient mess
-/obj/effect/decal/cleanable/blood/Crossed(atom/movable/O)
- ..()
-
- if(!ishuman(O))
+/obj/effect/decal/cleanable/blood/proc/on_atom_entered(datum/source, atom/movable/entered)
+ if(!ishuman(entered))
return
- if(!gravity_check && ishuman(O))
- bloodyify_human(O)
+ if(!gravity_check && ishuman(entered))
+ bloodyify_human(entered)
if(!off_floor)
- var/mob/living/carbon/human/H = O
+ var/mob/living/carbon/human/H = entered
var/obj/item/organ/external/l_foot = H.get_organ("l_foot")
var/obj/item/organ/external/r_foot = H.get_organ("r_foot")
var/hasfeet = TRUE
diff --git a/code/game/objects/effects/effect_system/effects_foam.dm b/code/game/objects/effects/effect_system/effects_foam.dm
index 0c5f9c2e8b23..cbe122c4d742 100644
--- a/code/game/objects/effects/effect_system/effects_foam.dm
+++ b/code/game/objects/effects/effect_system/effects_foam.dm
@@ -30,6 +30,10 @@
create_reagents(25)
playsound(src, 'sound/effects/bubbles2.ogg', 80, TRUE, -3)
addtimer(CALLBACK(src, PROC_REF(initial_process)), spread_time)
+ var/static/list/loc_connections = list(
+ COMSIG_ATOM_ENTERED = PROC_REF(on_atom_entered)
+ )
+ AddElement(/datum/element/connect_loc, loc_connections)
/obj/effect/particle_effect/foam/proc/disperse_reagents()
if(!reagents)
@@ -122,10 +126,10 @@
flick("[icon_state]-disolve", src)
QDEL_IN(src, 0.5 SECONDS)
-/obj/effect/particle_effect/foam/Crossed(atom/movable/AM, oldloc)
- if(!iscarbon(AM))
+/obj/effect/particle_effect/foam/proc/on_atom_entered(datum/source, atom/movable/entered)
+ if(!iscarbon(entered))
return
- var/mob/living/carbon/M = AM
+ var/mob/living/carbon/M = entered
if((M.slip("foam", 10 SECONDS) || IS_HORIZONTAL(M)) && reagents)
fill_with_reagents(M)
@@ -154,7 +158,7 @@
/obj/effect/particle_effect/foam/metal/temperature_expose(datum/gas_mixture/air, exposed_temperature, exposed_volume)
return
-/obj/effect/particle_effect/foam/metal/Crossed(atom/movable/AM, oldloc)
+/obj/effect/particle_effect/foam/metal/on_atom_entered(datum/source, atom/movable/entered)
return
/datum/effect_system/foam_spread
@@ -294,7 +298,7 @@
to_chat(user, "You hit the metal foam but bounce off it.")
playsound(loc, 'sound/weapons/tap.ogg', 100, 1)
-/obj/structure/foamedmetal/CanPass(atom/movable/mover, turf/target)
+/obj/structure/foamedmetal/CanPass(atom/movable/mover, border_dir)
return !density
/obj/structure/foamedmetal/CanAtmosPass(direction)
diff --git a/code/game/objects/effects/effect_system/effects_smoke.dm b/code/game/objects/effects/effect_system/effects_smoke.dm
index 55aee922c096..15cfd65b5434 100644
--- a/code/game/objects/effects/effect_system/effects_smoke.dm
+++ b/code/game/objects/effects/effect_system/effects_smoke.dm
@@ -23,7 +23,11 @@
/obj/effect/particle_effect/smoke/Initialize(mapload)
. = ..()
START_PROCESSING(SSobj, src)
- RegisterSignal(src, list(COMSIG_MOVABLE_CROSSED, COMSIG_CROSSED_MOVABLE), PROC_REF(smoke_mob)) //If someone crosses the smoke or the smoke crosses someone
+ var/static/list/loc_connections = list(
+ COMSIG_ATOM_ENTERED = PROC_REF(smoke_mob)
+ )
+ AddElement(/datum/element/connect_loc, loc_connections)
+ RegisterSignal(src, COMSIG_MOVABLE_MOVED, PROC_REF(smoke_mob))
GLOB.smokes_active++
lifetime += rand(-1, 1)
create_reagents(10)
@@ -31,7 +35,7 @@
/obj/effect/particle_effect/smoke/Destroy()
animate(src, 2 SECONDS, alpha = 0, easing = EASE_IN | CIRCULAR_EASING)
STOP_PROCESSING(SSobj, src)
- UnregisterSignal(src, list(COMSIG_MOVABLE_CROSSED, COMSIG_CROSSED_MOVABLE))
+ UnregisterSignal(src, COMSIG_MOVABLE_MOVED)
GLOB.smokes_active--
return ..()
@@ -56,7 +60,7 @@
return TRUE
/obj/effect/particle_effect/smoke/proc/smoke_mob(mob/living/carbon/breather)
- SIGNAL_HANDLER //COMSIG_MOVABLE_CROSSED and COMSIG_CROSSED_MOVABLE
+ SIGNAL_HANDLER // COMSIG_ATOM_ENTERED + COMSIG_MOVABLE_MOVED
if(!istype(breather))
return FALSE
if(lifetime < 1)
@@ -122,10 +126,12 @@
/////////////////////////////////////////////
/obj/effect/particle_effect/smoke/bad
- lifetime = 16 SECONDS_TO_LIFE_CYCLES
+ lifetime = 60 SECONDS_TO_LIFE_CYCLES
causes_coughing = TRUE
+ direction = SOUTH
+ steps = 10
-/obj/effect/particle_effect/smoke/bad/CanPass(atom/movable/mover, turf/target)
+/obj/effect/particle_effect/smoke/bad/CanPass(atom/movable/mover, border_dir)
if(istype(mover, /obj/item/projectile/beam))
var/obj/item/projectile/beam/B = mover
B.damage = (B.damage / 2)
@@ -143,25 +149,23 @@
lifetime = 10 SECONDS_TO_LIFE_CYCLES
causes_coughing = TRUE
-/obj/effect/particle_effect/smoke/steam/Crossed(atom/movable/AM, oldloc)
- . = ..()
- if(!isliving(AM))
+/obj/effect/particle_effect/smoke/steam/smoke_mob(mob/living/breather)
+ if(!istype(breather))
return
- var/mob/living/crosser = AM
- if(IS_MINDFLAYER(crosser))
+ if(IS_MINDFLAYER(breather))
return // Mindflayers are fully immune to steam
- if(!ishuman(crosser))
- crosser.adjustFireLoss(8)
+ if(!ishuman(breather))
+ breather.adjustFireLoss(8)
return
- var/mob/living/carbon/human/human_crosser = AM
+ var/mob/living/carbon/human/human_crosser = breather
var/fire_armour = human_crosser.get_thermal_protection()
if(fire_armour >= FIRE_SUIT_MAX_TEMP_PROTECT || HAS_TRAIT(human_crosser, TRAIT_RESISTHEAT))
return
- crosser.adjustFireLoss(5)
+ breather.adjustFireLoss(5)
if(prob(20))
- to_chat(crosser, "You are being scalded by the hot steam!")
+ to_chat(breather, "You are being scalded by the hot steam!")
/////////////////////////////////////////////
// Nanofrost smoke
diff --git a/code/game/objects/effects/forcefields.dm b/code/game/objects/effects/forcefields.dm
index 14545e54e283..7c59c10efe16 100644
--- a/code/game/objects/effects/forcefields.dm
+++ b/code/game/objects/effects/forcefields.dm
@@ -22,7 +22,7 @@
. = ..()
wizard = summoner
-/obj/effect/forcefield/wizard/CanPass(atom/movable/mover, turf/target)
+/obj/effect/forcefield/wizard/CanPass(atom/movable/mover, border_dir)
if(mover == wizard)
return TRUE
return FALSE
diff --git a/code/game/objects/effects/mines.dm b/code/game/objects/effects/mines.dm
index 573fe8030f17..a97ced7b06a5 100644
--- a/code/game/objects/effects/mines.dm
+++ b/code/game/objects/effects/mines.dm
@@ -7,13 +7,22 @@
var/triggered = FALSE
var/faction = "syndicate"
+/obj/effect/mine/Initialize(mapload)
+ . = ..()
+ var/static/list/loc_connections = list(
+ COMSIG_ATOM_ENTERED = PROC_REF(on_atom_entered),
+ )
+ AddElement(/datum/element/connect_loc, loc_connections)
+
/obj/effect/mine/proc/mineEffect(mob/living/victim)
to_chat(victim, "*click*")
-/obj/effect/mine/Crossed(AM as mob|obj, oldloc)
- if(!isliving(AM))
+/obj/effect/mine/proc/on_atom_entered(datum/source, atom/movable/entered)
+ SIGNAL_HANDLER // COMSIG_ATOM_ENTERED
+
+ if(!isliving(entered))
return
- var/mob/living/M = AM
+ var/mob/living/M = entered
if(faction && (faction in M.faction))
return
if(HAS_TRAIT(M, TRAIT_FLYING))
diff --git a/code/game/objects/effects/portals.dm b/code/game/objects/effects/portals.dm
index ba6f449d1f01..4a61e40a3fdd 100644
--- a/code/game/objects/effects/portals.dm
+++ b/code/game/objects/effects/portals.dm
@@ -39,6 +39,11 @@
creation_mob_ckey = creation_mob?.ckey
START_PROCESSING(SSobj, src)
+ var/static/list/loc_connections = list(
+ COMSIG_ATOM_ENTERED = PROC_REF(on_atom_entered),
+ )
+ AddElement(/datum/element/connect_loc, loc_connections)
+
if(lifespan > 0)
QDEL_IN(src, lifespan)
@@ -62,15 +67,15 @@
/obj/effect/portal/singularity_act()
return
-/obj/effect/portal/Crossed(atom/movable/AM, oldloc)
- if(isobserver(AM))
- return ..()
+/obj/effect/portal/proc/on_atom_entered(datum/source, atom/movable/entered, old_loc)
+ if(isobserver(entered))
+ return
- if(target && (get_turf(oldloc) == get_turf(target)))
- return ..()
+ if(target && (get_turf(old_loc) == get_turf(target)))
+ return
- if(!teleport(AM))
- return ..()
+ if(teleport(entered))
+ return TRUE
/obj/effect/portal/attack_tk(mob/user)
return
diff --git a/code/game/objects/effects/spiders.dm b/code/game/objects/effects/spiders.dm
index dbba0c2e051c..c169729b8e50 100644
--- a/code/game/objects/effects/spiders.dm
+++ b/code/game/objects/effects/spiders.dm
@@ -39,16 +39,19 @@
if(prob(50))
icon_state = "stickyweb2"
-/obj/structure/spider/stickyweb/CanPass(atom/movable/mover, turf/target)
- if(istype(mover, /mob/living/simple_animal/hostile/poison/giant_spider) || isterrorspider(mover))
- return TRUE
- else if(isliving(mover))
- if(prob(50))
- to_chat(mover, "You get stuck in [src] for a moment.")
- return FALSE
- else if(isprojectile(mover))
- return prob(30)
- return TRUE
+ var/static/list/loc_connections = list(
+ COMSIG_ATOM_EXIT = PROC_REF(on_atom_exit),
+ )
+ AddElement(/datum/element/connect_loc, loc_connections)
+
+/obj/structure/spider/stickyweb/proc/on_atom_exit(datum/source, atom/exiter)
+ if(istype(exiter, /mob/living/simple_animal/hostile/poison/giant_spider) || isterrorspider(exiter))
+ return
+ if(isliving(exiter) && prob(50))
+ to_chat(exiter, "You get stuck in [src] for a moment.")
+ return COMPONENT_ATOM_BLOCK_EXIT
+ if(isprojectile(exiter) && prob(30))
+ return COMPONENT_ATOM_BLOCK_EXIT
/obj/structure/spider/eggcluster
name = "egg cluster"
diff --git a/code/game/objects/effects/step_triggers.dm b/code/game/objects/effects/step_triggers.dm
index e7acf45614a2..63ab8096230d 100644
--- a/code/game/objects/effects/step_triggers.dm
+++ b/code/game/objects/effects/step_triggers.dm
@@ -6,18 +6,27 @@
var/mobs_only = FALSE
invisibility = INVISIBILITY_ABSTRACT // nope cant see this shit
+/obj/effect/step_trigger/Initialize(mapload)
+ . = ..()
+ var/static/list/loc_connections = list(
+ COMSIG_ATOM_ENTERED = PROC_REF(on_atom_entered),
+ )
+ AddElement(/datum/element/connect_loc, loc_connections)
+
/obj/effect/step_trigger/proc/Trigger(atom/movable/A)
return FALSE
-/obj/effect/step_trigger/Crossed(H, oldloc)
- . = ..()
- if(!H)
+/obj/effect/step_trigger/proc/on_atom_entered(datum/source, atom/movable/entered)
+ SIGNAL_HANDLER
+ if(!entered || entered == src)
return
- if(isobserver(H) && !affect_ghosts)
+ if(!ismob(entered) && !isobj(entered))
return
- if(!ismob(H) && mobs_only)
+ if(isobserver(entered) && !affect_ghosts)
return
- Trigger(H)
+ if(!ismob(entered) && mobs_only)
+ return
+ INVOKE_ASYNC(src, PROC_REF(Trigger), entered)
/obj/effect/step_trigger/singularity_act()
return
@@ -39,7 +48,6 @@
qdel(src)
/* Tosses things in a certain direction */
-
/obj/effect/step_trigger/thrower
var/direction = SOUTH // the direction of throw
var/tiles = 3 // if 0: forever until atom hits a stopper
diff --git a/code/game/objects/items/crayons.dm b/code/game/objects/items/crayons.dm
index 5dda16a4f6ce..7c5ecf95a9c4 100644
--- a/code/game/objects/items/crayons.dm
+++ b/code/game/objects/items/crayons.dm
@@ -33,6 +33,8 @@
var/preset_message
/// The index of the character in the message that will be drawn next.
var/preset_message_index = 0
+ /// Can this crayon be consumed or not
+ var/consumable = TRUE
/obj/item/toy/crayon/suicide_act(mob/user)
user.visible_message("[user] is jamming the [name] up [user.p_their()] nose and into [user.p_their()] brain. It looks like [user.p_theyre()] trying to commit suicide!")
@@ -42,7 +44,9 @@
..()
drawtype = pick(pick(graffiti), pick(letters), "rune[rand(1, 8)]")
-/obj/item/toy/crayon/attack_self__legacy__attackchain(mob/living/user as mob)
+/obj/item/toy/crayon/activate_self(mob/user)
+ if(..())
+ return
update_window(user)
/obj/item/toy/crayon/proc/update_window(mob/living/user as mob)
@@ -105,9 +109,12 @@
drawtype = temp
update_window(usr)
-/obj/item/toy/crayon/afterattack__legacy__attackchain(atom/target, mob/user, proximity)
- if(!proximity) return
- if(busy) return
+/obj/item/toy/crayon/after_attack(atom/target, mob/user, proximity_flag, click_parameters)
+ . = ..()
+ if(!proximity_flag)
+ return
+ if(busy)
+ return
if(is_type_in_list(target,validSurfaces))
var/temp = "rune"
if(preset_message_index > 0)
@@ -137,8 +144,10 @@
qdel(src)
busy = FALSE
-/obj/item/toy/crayon/attack__legacy__attackchain(mob/M, mob/user)
- if(M == user)
+/obj/item/toy/crayon/attack(mob/living/target, mob/living/carbon/human/user)
+ if(..() || !consumable)
+ return FINISH_ATTACK
+ if(target == user)
if(ishuman(user))
var/mob/living/carbon/human/H = user
if(!H.check_has_mouth())
@@ -152,8 +161,6 @@
else
to_chat(user, "There is no more of [name] left!")
qdel(src)
- else
- ..()
/obj/item/toy/crayon/examine(mob/user)
. = ..()
@@ -255,9 +262,6 @@
dye_color = DYE_MIME
uses = 0
-/obj/item/toy/crayon/mime/attack_self__legacy__attackchain(mob/living/user as mob)
- update_window(user)
-
/obj/item/toy/crayon/mime/update_window(mob/living/user as mob)
dat += "
Change color"
..()
@@ -281,9 +285,6 @@
dye_color = DYE_RAINBOW
uses = 0
-/obj/item/toy/crayon/rainbow/attack_self__legacy__attackchain(mob/living/user as mob)
- update_window(user)
-
/obj/item/toy/crayon/rainbow/update_window(mob/living/user as mob)
dat += " Change color"
..()
@@ -312,15 +313,15 @@
instant = TRUE
validSurfaces = list(/turf/simulated/floor,/turf/simulated/wall)
dye_color = null // not technically a crayon, so we're not gonna have it dye stuff in the laundry machine
+ consumable = FALSE // To stop you from eating spraycans. It's TOO SILLY!
/obj/item/toy/crayon/spraycan/New()
..()
update_icon()
-/obj/item/toy/crayon/spraycan/attack__legacy__attackchain(mob/M, mob/user)
- return // To stop you from eating spraycans. It's TOO SILLY!
-
-/obj/item/toy/crayon/spraycan/attack_self__legacy__attackchain(mob/living/user)
+/obj/item/toy/crayon/spraycan/activate_self(mob/user)
+ if(..())
+ return
var/choice = tgui_input_list(user, "Do you want to...", "Spraycan Options", list("Toggle Cap","Change Drawing", "Change Color"))
switch(choice)
if("Toggle Cap")
@@ -335,9 +336,9 @@
return
update_icon()
-/obj/item/toy/crayon/spraycan/afterattack__legacy__attackchain(atom/target, mob/user as mob, proximity)
+/obj/item/toy/crayon/spraycan/after_attack(atom/target, mob/user, proximity_flag, click_parameters)
. = ..()
- if(!proximity)
+ if(!proximity_flag)
return
if(capped)
to_chat(user, "You cannot spray [target] while the cap is still on!")
diff --git a/code/game/objects/items/dehy_carp.dm b/code/game/objects/items/dehy_carp.dm
index 35c1a5082d6b..8638ee8335dd 100644
--- a/code/game/objects/items/dehy_carp.dm
+++ b/code/game/objects/items/dehy_carp.dm
@@ -14,7 +14,9 @@
return ..()
// Attack self
-/obj/item/toy/plushie/carpplushie/dehy_carp/attack_self__legacy__attackchain(mob/user as mob)
+/obj/item/toy/plushie/carpplushie/dehy_carp/activate_self(mob/user)
+ if(..())
+ return
src.add_fingerprint(user) // Anyone can add their fingerprints to it with this
if(owned)
to_chat(user, "[src] stares up at you with friendly eyes.")
@@ -27,14 +29,15 @@
if(volume >= 1)
Swell()
-/obj/item/toy/plushie/carpplushie/dehy_carp/afterattack__legacy__attackchain(obj/O, mob/user,proximity)
- if(!proximity) return
- if(istype(O,/obj/structure/sink))
+/obj/item/toy/plushie/carpplushie/dehy_carp/after_attack(atom/target, mob/user, proximity_flag, click_parameters)
+ . = ..()
+ if(!proximity_flag)
+ return
+ if(istype(target,/obj/structure/sink))
to_chat(user, "You place [src] under a stream of water...")
user.drop_item()
- loc = get_turf(O)
+ loc = get_turf(target)
return Swell()
- ..()
/obj/item/toy/plushie/carpplushie/dehy_carp/proc/Swell()
desc = "It's growing!"
diff --git a/code/game/objects/items/devices/traitordevices.dm b/code/game/objects/items/devices/traitordevices.dm
index 724f9d9bf94a..b9b4787614a1 100644
--- a/code/game/objects/items/devices/traitordevices.dm
+++ b/code/game/objects/items/devices/traitordevices.dm
@@ -6,6 +6,7 @@
item_state = "jammer"
w_class = WEIGHT_CLASS_TINY
actions_types = list(/datum/action/item_action/toggle_radio_jammer)
+ new_attack_chain = TRUE
var/active = FALSE
var/range = 15
@@ -19,7 +20,9 @@
else
icon_state = "[initial(icon_state)]"
-/obj/item/jammer/attack_self__legacy__attackchain(mob/user)
+/obj/item/jammer/activate_self(mob/user)
+ if(..())
+ return
to_chat(user, "You [active ? "deactivate [src]. It goes quiet with a small click." : "activate [src]. It starts to hum softly."]")
active = !active
update_icon(UPDATE_ICON_STATE)
@@ -42,6 +45,7 @@
flags = CONDUCT
item_state = "electronic"
origin_tech = "magnets=3;combat=3;syndicate=3"
+ new_attack_chain = TRUE
var/list/icons_charges = list(
"syndi-tele-0",
"syndi-tele-1",
@@ -68,7 +72,9 @@
. = ..()
. += "[src] has [charges] out of [max_charges] charges left."
-/obj/item/teleporter/attack_self__legacy__attackchain(mob/user)
+/obj/item/teleporter/activate_self(mob/user)
+ if(..())
+ return
attempt_teleport(user, FALSE)
/obj/item/teleporter/process()
@@ -272,9 +278,12 @@
desc = "It contains an alien nanoswarm created by the technomancers of boron. Through near sorcerous feats via use of nanomachines, it enables its user to become fully fireproof."
icon = 'icons/obj/hypo.dmi'
icon_state = "combat_hypo"
+ new_attack_chain = TRUE
var/used = FALSE
-/obj/item/fireproofing_injector/attack_self__legacy__attackchain(mob/living/user)
+/obj/item/fireproofing_injector/activate_self(mob/user)
+ if(..())
+ return
if(HAS_TRAIT(user, TRAIT_RESISTHEAT))
to_chat(user, "You are already fireproof!")
return
@@ -298,6 +307,7 @@
desc = "Specially designed nanomachines that enhance the low-temperature regenerative capabilities of drask. Requires supercooled air in the enviroment or internals to function."
icon = 'icons/obj/hypo.dmi'
icon_state = "combat_hypo"
+ new_attack_chain = TRUE
var/used = FALSE
/obj/item/cryoregenerative_enhancer/examine_more(mob/user)
@@ -306,7 +316,9 @@
. += ""
. += "Clinical trials have shown a four times increase in the rate of healing compared to a placebo. Whilst the product is technically not yet available to the public, the right connections with the right people allow interested parties to obtain samples early..."
-/obj/item/cryoregenerative_enhancer/attack_self__legacy__attackchain(mob/living/user)
+/obj/item/cryoregenerative_enhancer/activate_self(mob/user)
+ if(..())
+ return
if(HAS_TRAIT(user, TRAIT_DRASK_SUPERCOOL))
to_chat(user, "Your regeneration is already enhanced!")
return
@@ -339,6 +351,7 @@
flags = CONDUCT
item_state = "electronic"
origin_tech = "magnets=3;combat=3;syndicate=3"
+ new_attack_chain = TRUE
/// How many times the mind batter has been used
var/times_used = 0
@@ -368,7 +381,9 @@
times_used--
icon_state = "batterer"
-/obj/item/batterer/attack_self__legacy__attackchain(mob/living/carbon/user)
+/obj/item/batterer/activate_self(mob/user)
+ if(..())
+ return
activate_batterer(user)
/obj/item/batterer/proc/activate_batterer(mob/user)
@@ -450,6 +465,7 @@
icon = 'icons/obj/hhmirror.dmi'
icon_state = "hhmirror"
w_class = WEIGHT_CLASS_TINY
+ new_attack_chain = TRUE
var/datum/ui_module/appearance_changer/appearance_changer_holder
/obj/item/handheld_mirror/ui_state(mob/user)
@@ -458,11 +474,12 @@
/obj/item/handheld_mirror/ui_interact(mob/user, datum/tgui/ui = null)
appearance_changer_holder.ui_interact(user, ui)
-/obj/item/handheld_mirror/attack_self__legacy__attackchain(mob/user)
- if(ishuman(user))
- appearance_changer_holder = new(src, user)
- appearance_changer_holder.flags = APPEARANCE_ALL_BODY
- ui_interact(user)
+/obj/item/handheld_mirror/activate_self(mob/user)
+ if(..() || !ishuman(user))
+ return
+ appearance_changer_holder = new(src, user)
+ appearance_changer_holder.flags = APPEARANCE_ALL_BODY
+ ui_interact(user)
/obj/item/handheld_mirror/Initialize(mapload)
. = ..()
@@ -485,6 +502,7 @@
throw_range = 10
flags = CONDUCT
item_state = "electronic"
+ new_attack_chain = TRUE
/// Split points for range_messages.
var/list/ranges = list(5, 15, 30)
/// Messages to output to the user.
@@ -498,7 +516,9 @@
COOLDOWN_DECLARE(scan_cooldown)
var/on_hit_sound = 'sound/effects/ping_hit.ogg'
-/obj/item/syndi_scanner/attack_self__legacy__attackchain(mob/user)
+/obj/item/syndi_scanner/activate_self(mob/user)
+ if(..())
+ return
if(!COOLDOWN_FINISHED(src, scan_cooldown))
to_chat(user, "[src] is recharging!")
return
diff --git a/code/game/objects/items/stacks/stack.dm b/code/game/objects/items/stacks/stack.dm
index 4066e3702c7b..d7d151a4dd0c 100644
--- a/code/game/objects/items/stacks/stack.dm
+++ b/code/game/objects/items/stacks/stack.dm
@@ -61,6 +61,10 @@
if(is_zero_amount(FALSE))
return INITIALIZE_HINT_QDEL
+ var/static/list/loc_connections = list(
+ COMSIG_ATOM_ENTERED = PROC_REF(on_atom_entered),
+ )
+ AddElement(/datum/element/connect_loc, loc_connections)
update_icon(UPDATE_ICON_STATE)
/obj/item/stack/update_icon_state()
@@ -75,17 +79,18 @@
icon_state = "[initial(icon_state)]_[state]"
-/obj/item/stack/Crossed(obj/O, oldloc)
- if(O == src)
+/obj/item/stack/proc/on_atom_entered(datum/source, atom/movable/entered)
+ SIGNAL_HANDLER // COMSIG_ATOM_ENTERED
+
+ // Edge case. This signal will also be sent when src has entered the turf. Don't want to merge with ourselves.
+ if(entered == src)
return
if(amount >= max_amount || ismob(loc)) // Prevents unnecessary call. Also prevents merging stack automatically in a mob's inventory
return
- if(!O.throwing && can_merge(O))
- INVOKE_ASYNC(src, PROC_REF(merge), O)
-
- ..()
+ if(!entered.throwing && can_merge(entered))
+ INVOKE_ASYNC(src, PROC_REF(merge), entered)
/obj/item/stack/hitby(atom/movable/hitting, skipcatch, hitpush, blocked, datum/thrownthing/throwingdatum)
if(can_merge(hitting, inhand = TRUE))
diff --git a/code/game/objects/items/toys.dm b/code/game/objects/items/toys.dm
index 3bc720ac53c7..775dd5d25138 100644
--- a/code/game/objects/items/toys.dm
+++ b/code/game/objects/items/toys.dm
@@ -24,6 +24,7 @@
throw_speed = 4
throw_range = 20
force = 0
+ new_attack_chain = TRUE
/*
@@ -40,21 +41,23 @@
..()
create_reagents(10)
-/obj/item/toy/balloon/attack__legacy__attackchain(mob/living/carbon/human/M as mob, mob/user as mob)
- return
+/obj/item/toy/balloon/pre_attack(atom/target, mob/living/user, params)
+ ..()
+ return FINISH_ATTACK
-/obj/item/toy/balloon/afterattack__legacy__attackchain(atom/A, mob/user, proximity)
- if(!proximity)
+/obj/item/toy/balloon/after_attack(atom/target, mob/user, proximity_flag, click_parameters)
+ . = ..()
+ if(!proximity_flag)
return
- if(istype(A, /obj/structure/reagent_dispensers))
- var/obj/structure/reagent_dispensers/RD = A
+ if(istype(target, /obj/structure/reagent_dispensers))
+ var/obj/structure/reagent_dispensers/RD = target
if(RD.reagents.total_volume <= 0)
to_chat(user, "[RD] is empty.")
else if(reagents.total_volume >= 10)
to_chat(user, "[src] is full.")
else
- A.reagents.trans_to(src, 10)
- to_chat(user, "You fill the balloon with the contents of [A].")
+ target.reagents.trans_to(src, 10)
+ to_chat(user, "You fill the balloon with the contents of [target].")
desc = "A translucent balloon with some form of liquid sloshing around in it."
update_icon()
@@ -66,20 +69,22 @@
update_icon()
return
-/obj/item/toy/balloon/attackby__legacy__attackchain(obj/O as obj, mob/user as mob, params)
- if(istype(O, /obj/item/reagent_containers/glass) || istype(O, /obj/item/reagent_containers/drinks/drinkingglass))
- if(O.reagents)
- if(O.reagents.total_volume < 1)
- to_chat(user, "[O] is empty.")
- else if(O.reagents.total_volume >= 1)
- if(O.reagents.has_reagent("facid", 1))
+/obj/item/toy/balloon/attack_by(obj/item/attacking, mob/user, params)
+ if(..())
+ return FINISH_ATTACK
+ if(istype(attacking, /obj/item/reagent_containers/glass) || istype(attacking, /obj/item/reagent_containers/drinks/drinkingglass))
+ if(attacking.reagents)
+ if(attacking.reagents.total_volume < 1)
+ to_chat(user, "[attacking] is empty.")
+ else if(attacking.reagents.total_volume >= 1)
+ if(attacking.reagents.has_reagent("facid", 1))
to_chat(user, "The acid chews through the balloon!")
- O.reagents.reaction(user)
+ attacking.reagents.reaction(user)
qdel(src)
else
desc = "A translucent balloon with some form of liquid sloshing around in it."
- to_chat(user, "You fill the balloon with the contents of [O].")
- O.reagents.trans_to(src, 10)
+ to_chat(user, "You fill the balloon with the contents of [attacking].")
+ attacking.reagents.trans_to(src, 10)
update_icon()
return
@@ -115,8 +120,8 @@
w_class = WEIGHT_CLASS_BULKY
var/lastused = null
-/obj/item/toy/syndicateballoon/attack_self__legacy__attackchain(mob/user)
- if(world.time - lastused < CLICK_CD_MELEE)
+/obj/item/toy/syndicateballoon/activate_self(mob/user)
+ if(..() || world.time - lastused < CLICK_CD_MELEE)
return
var/playverb = pick("bat [src]", "tug on [src]'s string", "play with [src]")
user.visible_message("[user] plays with [src].", "You [playverb].")
@@ -184,7 +189,9 @@
w_class = WEIGHT_CLASS_SMALL
attack_verb = list("attacked", "struck", "hit")
-/obj/item/toy/sword/attack_self__legacy__attackchain(mob/user)
+/obj/item/toy/sword/activate_self(mob/user)
+ if(..())
+ return
active = !active
if(active)
to_chat(user, "You extend the plastic blade with a quick flick of your wrist.")
@@ -204,24 +211,24 @@
H.update_inv_l_hand()
H.update_inv_r_hand()
add_fingerprint(user)
- return
-// Copied from /obj/item/melee/energy/sword/attackby
-/obj/item/toy/sword/attackby__legacy__attackchain(obj/item/W, mob/living/user, params)
- ..()
- if(istype(W, /obj/item/toy/sword))
- if(W == src)
+/obj/item/toy/sword/attack_by(obj/item/attacking, mob/user, params)
+ if(..())
+ return FINISH_ATTACK
+ if(istype(attacking, /obj/item/toy/sword))
+ if(attacking == src)
to_chat(user, "You try to attach the end of the plastic sword to... Itself. You're not very smart, are you?")
if(ishuman(user))
- user.adjustBrainLoss(10)
- else if((W.flags & NODROP) || (flags & NODROP))
- to_chat(user, "\the [flags & NODROP ? src : W] is stuck to your hand, you can't attach it to \the [flags & NODROP ? W : src]!")
+ var/mob/living/carbon/human/H = user
+ H.adjustBrainLoss(10)
+ else if((attacking.flags & NODROP) || (flags & NODROP))
+ to_chat(user, "\the [flags & NODROP ? src : attacking] is stuck to your hand, you can't attach it to \the [flags & NODROP ? attacking : src]!")
else
to_chat(user, "You attach the ends of the two plastic swords, making a single double-bladed toy! You're fake-cool.")
new /obj/item/dualsaber/toy(user.loc)
- user.unEquip(W)
+ user.unEquip(attacking)
user.unEquip(src)
- qdel(W)
+ qdel(attacking)
qdel(src)
/obj/item/toy/sword/chaosprank
@@ -229,9 +236,9 @@
/// Sets to TRUE once the character using it hits something and realises it's not a real energy sword
var/pranked = FALSE
-/obj/item/toy/sword/attack__legacy__attackchain(mob/target, mob/living/user)
- if(!cigarette_lighter_act(user, target))
- return ..()
+/obj/item/toy/sword/attack(mob/living/target, mob/living/carbon/human/user)
+ if(..() || cigarette_lighter_act(user, target))
+ return FINISH_ATTACK
/obj/item/toy/sword/cigarette_lighter_act(mob/living/user, mob/living/target, obj/item/direct_attackby_item)
var/obj/item/clothing/mask/cigarette/cig = ..()
@@ -283,8 +290,8 @@
target.unEquip(cig, TRUE)
return TRUE
-/obj/item/toy/sword/chaosprank/afterattack__legacy__attackchain(mob/living/target, mob/living/user, proximity)
- ..()
+/obj/item/toy/sword/chaosprank/after_attack(atom/target, mob/user, proximity_flag, click_parameters)
+ . = ..()
if(!pranked)
to_chat(user, "Oh... It's a fake.")
name = "toy sword"
@@ -375,6 +382,13 @@
w_class = WEIGHT_CLASS_TINY
var/ash_type = /obj/effect/decal/cleanable/ash
+/obj/item/toy/snappop/Initialize(mapload)
+ . = ..()
+ var/static/list/loc_connections = list(
+ COMSIG_ATOM_ENTERED = PROC_REF(on_atom_entered),
+ )
+ AddElement(/datum/element/connect_loc, loc_connections)
+
/obj/item/toy/snappop/proc/pop_burst(n=3, c=1)
do_sparks(n, c, src)
new ash_type(loc)
@@ -391,10 +405,10 @@
..()
pop_burst()
-/obj/item/toy/snappop/Crossed(H as mob|obj, oldloc)
- if(ishuman(H) || issilicon(H)) //i guess carp and shit shouldn't set them off
- var/mob/living/carbon/M = H
- if(issilicon(H) || M.m_intent == MOVE_INTENT_RUN)
+/obj/item/toy/snappop/proc/on_atom_entered(datum/source, atom/movable/entered)
+ if(ishuman(entered) || issilicon(entered)) //i guess carp and shit shouldn't set them off
+ var/mob/living/carbon/M = entered
+ if(issilicon(entered) || M.m_intent == MOVE_INTENT_RUN)
to_chat(M, "You step on the snap pop!")
pop_burst(2, 0)
@@ -423,7 +437,9 @@
w_class = WEIGHT_CLASS_SMALL
var/cooldown = 0
-/obj/item/toy/nuke/attack_self__legacy__attackchain(mob/user)
+/obj/item/toy/nuke/activate_self(mob/user)
+ if(..())
+ return
if(cooldown < world.time)
cooldown = world.time + 1800 //3 minutes
user.visible_message("[user] presses a button on [src]", "You activate [src], it plays a loud noise!", "You hear the click of a button.")
@@ -455,11 +471,12 @@
desc += " This one is [item_color]."
icon_state = "therapy[item_color]"
-/obj/item/toy/therapy/attack_self__legacy__attackchain(mob/user)
- if(cooldown < world.time - 8)
- to_chat(user, "You relieve some stress with \the [src].")
- playsound(user, 'sound/items/squeaktoy.ogg', 20, 1)
- cooldown = world.time
+/obj/item/toy/therapy/activate_self(mob/user)
+ if(..() || !(cooldown < world.time - 8))
+ return
+ to_chat(user, "You relieve some stress with \the [src].")
+ playsound(user, 'sound/items/squeaktoy.ogg', 20, TRUE)
+ cooldown = world.time
/obj/random/therapy
name = "Random Therapy Doll"
@@ -578,20 +595,33 @@
var/list/poof_sound = list('sound/weapons/thudswoosh.ogg' = 1)
var/has_stuffing = TRUE //If the plushie has stuffing in it
var/obj/item/grenade/grenade //You can remove the stuffing from a plushie and add a grenade to it for *nefarious uses*
+ var/sound/rare_hug_sound
+ var/rare_hug_word
+ /// This is a variable that stores a mob that has been cursed into a plushie inside it.
+ var/mob/living/cursed_plushie_victim
+ COOLDOWN_DECLARE(rare_hug_cooldown)
-/obj/item/toy/plushie/attack__legacy__attackchain(mob/M as mob, mob/user as mob)
+/obj/item/toy/plushie/attack(mob/living/target, mob/living/carbon/human/user)
+ if(..())
+ return FINISH_ATTACK
playsound(loc, pickweight(poof_sound), 20, 1) // Play the whoosh sound in local area
- if(iscarbon(M))
+ if(iscarbon(target))
if(prob(10))
- M.reagents.add_reagent("hugs", 10)
- return ..()
+ target.reagents.add_reagent("hugs", 10)
-/obj/item/toy/plushie/attack_self__legacy__attackchain(mob/user as mob)
+/obj/item/toy/plushie/activate_self(mob/user as mob)
+ if(..())
+ return
if(has_stuffing || grenade)
- var/cuddle_verb = pick("hugs", "cuddles", "snugs")
- user.visible_message("[user] [cuddle_verb] [src].")
- playsound(get_turf(src), poof_sound, 50, TRUE, -1)
+ if(rare_hug_sound && rare_hug_word && COOLDOWN_FINISHED(src, rare_hug_cooldown))
+ playsound(src, rare_hug_sound , 10, FALSE)
+ visible_message("[rare_hug_word]")
+ COOLDOWN_START(src, rare_hug_cooldown, 3 SECONDS)
+ else
+ var/cuddle_verb = pick("hugs", "cuddles", "snugs")
+ user.visible_message("[user] [cuddle_verb] [src].")
+ playsound(get_turf(src), pickweight(poof_sound), 50, TRUE, -1)
if(grenade && !grenade.active)
add_attack_logs(user, user, "activated a hidden grenade in [src].", ATKLOG_MOST)
playsound(loc, 'sound/weapons/armbomb.ogg', 10, TRUE, -3)
@@ -599,48 +629,67 @@
addtimer(CALLBACK(src, PROC_REF(explosive_betrayal), grenade), rand(1, 3) SECONDS)
else
to_chat(user, "You try to pet [src], but it has no stuffing. Aww...")
- return ..()
/obj/item/toy/plushie/proc/explosive_betrayal(obj/item/grenade/grenade_callback)
+ var/grenade_inside = FALSE //Any grenade, even non-explosive, will destroy the plushie.
+ if(grenade)
+ grenade_inside = TRUE
grenade_callback.prime()
+ if(grenade_inside && !QDELETED(src))
+ qdel(src)
/obj/item/toy/plushie/Destroy()
QDEL_NULL(grenade)
+ QDEL_NULL(cursed_plushie_victim)
return ..()
-/obj/item/toy/plushie/attackby__legacy__attackchain(obj/item/I, mob/living/user, params)
- if(I.sharp)
+/obj/item/toy/plushie/attack_by(obj/item/attacking, mob/user, params)
+ if(..())
+ return FINISH_ATTACK
+ if(attacking.sharp)
if(!grenade)
if(!has_stuffing)
to_chat(user, "You already murdered it!")
- return
+ return FINISH_ATTACK
user.visible_message("[user] tears out the stuffing from [src]!", "You rip a bunch of the stuffing from [src]. Murderer.")
- I.play_tool_sound(src)
+ attacking.play_tool_sound(src)
has_stuffing = FALSE
else
to_chat(user, "You remove the grenade from [src].")
grenade.forceMove(get_turf(src))
user.put_in_hands(grenade)
grenade = null
- return
- if(istype(I, /obj/item/grenade))
+ return FINISH_ATTACK
+ if(istype(attacking, /obj/item/grenade))
if(has_stuffing)
to_chat(user, "You need to remove some stuffing first!")
- return
+ return FINISH_ATTACK
if(grenade)
to_chat(user, "[src] already has a grenade!")
- return
+ return FINISH_ATTACK
if(!user.drop_item())
- to_chat(user, "[I] is stuck to you and cannot be placed into [src].")
- return
- user.visible_message("[user] slides [I] into [src].", \
- "You slide [I] into [src].")
- I.forceMove(src)
- grenade = I
+ to_chat(user, "[attacking] is stuck to you and cannot be placed into [src].")
+ return FINISH_ATTACK
+ user.visible_message("[user] slides [attacking] into [src].", \
+ "You slide [attacking] into [src].")
+ attacking.forceMove(src)
+ grenade = attacking
add_attack_logs(user, user, "placed a hidden grenade in [src].", ATKLOG_ALMOSTALL)
+ return FINISH_ATTACK
+
+/obj/item/toy/plushie/proc/un_plushify()
+ if(!cursed_plushie_victim)
return
- return ..()
+ cursed_plushie_victim.forceMove(get_turf(src))
+ cursed_plushie_victim.status_flags &= ~GODMODE
+ cursed_plushie_victim.notransform = FALSE
+
+ for(var/mob/living/simple_animal/shade/sword/generic_item/B in contents)
+ cursed_plushie_victim.key = B.key
+ break
+ cursed_plushie_victim = null
+ qdel(src)
/obj/random/plushie
name = "Random Plushie"
@@ -796,9 +845,8 @@
/obj/item/toy/plushie/greyplushie/proc/reset_hugdown()
hug_cooldown = FALSE //Resets the hug interaction cooldown.
-/obj/item/toy/plushie/greyplushie/attack_self__legacy__attackchain(mob/user)//code for talking when hugged.
- . = ..()
- if(hug_cooldown)
+/obj/item/toy/plushie/greyplushie/activate_self(mob/user)//code for talking when hugged.
+ if(..() || hug_cooldown)
return
hug_cooldown = TRUE
addtimer(CALLBACK(src, PROC_REF(reset_hugdown)), 5 SECONDS) //Hug interactions only put the plushie on a 5 second cooldown.
@@ -812,16 +860,8 @@
desc = "A stitched-together vox, fresh from the skipjack. Press its belly to hear it skree!"
icon_state = "plushie_vox"
item_state = "plushie_vox"
- var/cooldown = 0
-
-/obj/item/toy/plushie/voxplushie/attack_self__legacy__attackchain(mob/user)
- if(!cooldown)
- playsound(user, 'sound/voice/shriek1.ogg', 10, 0)
- visible_message("Skreee!")
- cooldown = 1
- spawn(30) cooldown = 0
- return
- ..()
+ rare_hug_sound = 'sound/voice/shriek1.ogg'
+ rare_hug_word = "Skreee!"
/obj/item/toy/plushie/ipcplushie
name = "IPC plushie"
@@ -829,14 +869,15 @@
icon_state = "plushie_ipc"
item_state = "plushie_ipc"
-/obj/item/toy/plushie/ipcplushie/attackby__legacy__attackchain(obj/item/B, mob/user, params)
- if(istype(B, /obj/item/food/breadslice))
+/obj/item/toy/plushie/ipcplushie/attack_by(obj/item/attacking, mob/user, params)
+ if(..())
+ return FINISH_ATTACK
+ if(istype(attacking, /obj/item/food/breadslice))
new /obj/item/food/toast(get_turf(loc))
to_chat(user, "You insert bread into the toaster.")
playsound(loc, 'sound/machines/ding.ogg', 50, 1)
- qdel(B)
- else
- return ..()
+ qdel(attacking)
+ return FINISH_ATTACK
//New generation TG plushies
@@ -869,23 +910,15 @@
desc = "A silky nian plushie, straight from the nebula. Pull its antenna to hear it buzz!"
icon_state = "plushie_nian"
item_state = "plushie_nian"
- var/cooldown = FALSE
+ rare_hug_sound = 'sound/voice/scream_moth.ogg'
+ rare_hug_word = "Buzzzz!"
-/obj/item/toy/plushie/nianplushie/attack_self__legacy__attackchain(mob/user)
- if(cooldown)
- return ..()
-
- playsound(src, 'sound/voice/scream_moth.ogg', 10, 0)
- visible_message("Buzzzz!")
- cooldown = TRUE
- addtimer(VARSET_CALLBACK(src, cooldown, FALSE), 3 SECONDS)
-
/obj/item/toy/plushie/nianplushie/monarch
name = "monarch nian plushie"
desc = "A monarch nian plushie, straight from the nebula. Pull its antenna to hear it buzz!"
icon_state = "plushie_nian_monarch"
item_state = "plushie_nian_monarch"
-
+
/obj/item/toy/plushie/nianplushie/luna
name = "luna nian plushie"
desc = "A luna nian plushie, straight from the nebula. Pull its antenna to hear it buzz!"
@@ -897,31 +930,31 @@
desc = "An atlas nian plushie, straight from the nebula. Pull its antenna to hear it buzz!"
icon_state = "plushie_nian_atlas"
item_state = "plushie_nian_atlas"
-
+
/obj/item/toy/plushie/nianplushie/reddish
name = "reddish nian plushie"
desc = "A reddish nian plushie, straight from the nebula. Pull its antenna to hear it buzz!"
icon_state = "plushie_nian_reddish"
item_state = "plushie_nian_reddish"
-
+
/obj/item/toy/plushie/nianplushie/royal
name = "royal nian plushie"
desc = "A royal nian plushie, straight from the nebula. Pull its antenna to hear it buzz!"
icon_state = "plushie_nian_royal"
item_state = "plushie_nian_royal"
-
+
/obj/item/toy/plushie/nianplushie/gothic
name = "gothic nian plushie"
desc = "A gothic nian plushie, straight from the nebula. Pull its antenna to hear it buzz!"
icon_state = "plushie_nian_gothic"
item_state = "plushie_nian_gothic"
-
+
/obj/item/toy/plushie/nianplushie/lovers
name = "lovers nian plushie"
desc = "A lovers nian plushie, straight from the nebula. Pull its antenna to hear it buzz!"
icon_state = "plushie_nian_lovers"
item_state = "plushie_nian_lovers"
-
+
/obj/item/toy/plushie/nianplushie/whitefly
name = "whitefly nian plushie"
desc = "A whitefly nian plushie, straight from the nebula. Pull its antenna to hear it buzz!"
@@ -933,7 +966,7 @@
desc = "A punnished nian plushie, straight from the nebula. Pull its antenna to hear it buzz!"
icon_state = "plushie_nian_punished"
item_state = "plushie_nian_punished"
-
+
/obj/item/toy/plushie/nianplushie/firewatch
name = "firewatch nian plushie"
desc = "A firewtach nian plushie, straight from the nebula. Pull its antenna to hear it buzz!"
@@ -945,43 +978,43 @@
desc = "A deathshead nian plushie, straight from the nebula. Pull its antenna to hear it buzz!"
icon_state = "plushie_nian_deadhead"
item_state = "plushie_nian_deadhead"
-
+
/obj/item/toy/plushie/nianplushie/poison
name = "poison nian plushie"
desc = "A poison nian plushie, straight from the nebula. Pull its antenna to hear it buzz!"
icon_state = "plushie_nian_poison"
item_state = "plushie_nian_poison"
-
+
/obj/item/toy/plushie/nianplushie/ragged
name = "ragged nian plushie"
desc = "A ragged nian plushie, straight from the nebula. Pull its antenna to hear it buzz!"
icon_state = "plushie_nian_ragged"
item_state = "plushie_nian_ragged"
-
+
/obj/item/toy/plushie/nianplushie/snow
name = "snow nian plushie"
desc = "A snow nian plushie, straight from the nebula. Pull its antenna to hear it buzz!"
icon_state = "plushie_nian_snow"
item_state = "plushie_nian_snow"
-
+
/obj/item/toy/plushie/nianplushie/clockwork
name = "clockwork nian plushie"
desc = "A clockwork nian plushie, straight from the nebula. Pull its antenna to hear it buzz!"
icon_state = "plushie_nian_clockwork"
item_state = "plushie_nian_clockwork"
-
+
/obj/item/toy/plushie/nianplushie/moonfly
name = "moonfly nian plushie"
desc = "A moonfly nian plushie, straight from the nebula. Pull its antenna to hear it buzz!"
icon_state = "plushie_nian_moonfly"
item_state = "plushie_nian_moonfly"
-
+
/obj/item/toy/plushie/nianplushie/rainbow
name = "rainbow nian plushie"
desc = "A rainbow nian plushie, straight from the nebula. Pull its antenna to hear it buzz!"
icon_state = "plushie_nian_rainbow"
item_state = "plushie_nian_rainbow"
-
+
/obj/item/toy/plushie/shark
name = "shark plushie"
desc = "A plushie depicting a somewhat cartoonish shark. The tag calls it a 'hákarl', noting that it was made by an obscure furniture manufacturer in old Scandinavia."
@@ -1006,6 +1039,216 @@
'sound/weapons/cablecuff.ogg' = 1,
)
+/obj/item/toy/plushie/skrellplushie
+ name = "skrell plushie"
+ desc = "The latest from 'SoftSkrells.net', features its very own headpocket! Warble!"
+ icon_state = "plushie_skrell"
+ rare_hug_sound = 'sound/effects/warble.ogg'
+ rare_hug_word = "Warble!"
+ var/obj/item/headpocket_item
+
+/obj/item/toy/plushie/skrellplushie/examine(mob/user)
+ . = ..()
+ . += "Alt-click to put something small inside the headpocket, or take an item out."
+
+/obj/item/toy/plushie/skrellplushie/AltClick(mob/user)
+ if(!Adjacent(user) || HAS_TRAIT(user, TRAIT_HANDS_BLOCKED))
+ return
+ var/obj/item/I = user.get_active_hand()
+ if(I == src)
+ return
+ if(!I)
+ if(!headpocket_item)
+ return
+ to_chat(user, "You remove [headpocket_item] from [src].")
+ headpocket_item.forceMove(get_turf(src))
+ user.put_in_hands(headpocket_item)
+ headpocket_item = null
+ return
+ if(I.w_class > WEIGHT_CLASS_SMALL)
+ to_chat(user, "You cannot fit [I] in [src]!")
+ return
+ if(!iscarbon(user))
+ return
+ if(headpocket_item)
+ to_chat(user, "[src] already has an item in its headpocket!")
+ return
+ if(!user.drop_item())
+ to_chat(user, "You cannot slip [I] inside [src]!")
+ return
+ user.visible_message("[user] places [I] into [src].", "You place [I] into [src].")
+ add_fingerprint(user)
+ I.forceMove(src)
+ headpocket_item = I
+
+/obj/item/toy/plushie/skrellplushie/Destroy()
+ if(headpocket_item)
+ headpocket_item.forceMove(get_turf(src))
+ headpocket_item = null
+ return ..()
+
+/obj/item/toy/plushie/humanplushie
+ name = "human plushie"
+ desc = "This plushie is slightly less popular than its counterparts. The designers obviously didn't find humans that endearing..."
+ icon_state = "plushie_human"
+ poof_sound = list('sound/weapons/thudswoosh.ogg' = 30,
+ 'sound/goonstation/voice/male_scream.ogg' = 1)
+
+/obj/item/toy/plushie/borgplushie
+ name = "borg plushie"
+ desc = "The synthetic backbone of the station, rendered in plush form. Inbuilt flashlight included!"
+ icon_state = "plushie_borg"
+ var/borg_plushie_overlay
+ var/on = FALSE
+
+/obj/item/toy/plushie/borgplushie/Initialize(mapload)
+ . = ..()
+ borg_plushie_overlay = (pick("plushie_borgjan", "plushie_borgsec", "plushie_borgmed", "plushie_borgmine", "plushie_borgserv", "plushie_borgassist", "plushie_borgengi"))
+ update_icon()
+
+/obj/item/toy/plushie/borgplushie/activate_self(mob/user)
+ if(..())
+ return
+ on = !on
+ update_brightness()
+
+/obj/item/toy/plushie/borgplushie/proc/update_brightness()
+ if(on)
+ set_light(4)
+ else
+ set_light(0)
+ update_icon()
+
+/obj/item/toy/plushie/borgplushie/update_overlays()
+ . = ..()
+ add_overlay(borg_plushie_overlay)
+ if(on)
+ add_overlay("borglights")
+ else
+ cut_overlay("borglights")
+
+/obj/item/toy/plushie/borgplushie/extinguish_light(force = FALSE)
+ if(!force)
+ if(on)
+ visible_message("[src]'s light grows dim...")
+ on = !on
+ update_brightness()
+ else
+ atom_say("Self-destruct command received!")
+ visible_message("[src] explodes!")
+ var/turf/T = get_turf(src)
+ playsound(T, 'sound/goonstation/effects/robogib.ogg', 50, TRUE)
+ robogibs(T)
+ if(grenade)
+ explosive_betrayal(grenade)
+ if(!QDELETED(src))
+ qdel(src)
+
+/obj/item/toy/plushie/dionaplushie
+ name = "diona plushie"
+ desc = "This plushy is seemingly comprised of other, smaller, nymph plushies. They really went all out on the realism! Keep away from plantkiller."
+ icon_state = "plushie_diona"
+ rare_hug_sound = 'sound/voice/dionatalk1.ogg'
+ rare_hug_word = "Creak..."
+
+/obj/item/toy/plushie/nymphplushie
+ name = "nymph plushie"
+ desc = "Life-sized plushie of a diona nymph, perhaps if you find another you could make a diona!"
+ icon_state = "plushie_nymph"
+ rare_hug_sound = 'sound/creatures/nymphchirp.ogg'
+ rare_hug_word = "Chirp!"
+
+/obj/item/toy/plushie/nymphplushie/attack_by(obj/item/attacking, mob/user, params)
+ if(..())
+ return FINISH_ATTACK
+ if(istype(attacking, /obj/item/toy/plushie/nymphplushie))
+ var/obj/item/toy/plushie/nymphplushie/NP = attacking
+ var/found_grenade = FALSE
+ if(grenade)
+ found_grenade = TRUE
+ explosive_betrayal(grenade)
+ if(NP.grenade)
+ found_grenade = TRUE
+ NP.explosive_betrayal(NP.grenade)
+ if(found_grenade)
+ return FINISH_ATTACK
+ new /obj/item/toy/plushie/dionaplushie(get_turf(loc))
+ to_chat(user, "The nymph plushies combine seamlessly into an diona plushie!")
+ playsound(loc, 'sound/voice/dionatalk1.ogg', 50, TRUE)
+ qdel(NP)
+ qdel(src)
+ return FINISH_ATTACK
+
+/obj/item/toy/plushie/plasmamanplushie
+ name = "plasmaman plushie"
+ desc = "A freindly plasma-being in plush form. WARNING: KEEP AWAY FROM OPEN FLAME!"
+ icon_state = "plushie_plasma"
+ rare_hug_sound = 'sound/voice/plas_rattle.ogg'
+ rare_hug_word = "Rattle!"
+
+/obj/item/toy/plushie/plasmamanplushie/welder_act(mob/user, obj/item/I)
+ if(I.use_tool(src, user, volume = I.tool_volume))
+ bakoom()
+ return TRUE
+
+/obj/item/toy/plushie/plasmamanplushie/attack_by(obj/item/attacking, mob/user, params)
+ if(..())
+ return FINISH_ATTACK
+ if(attacking.get_heat())
+ bakoom()
+ return FINISH_ATTACK
+
+/obj/item/toy/plushie/plasmamanplushie/fire_act(datum/gas_mixture/air, exposed_temperature, exposed_volume, global_overlay = TRUE)
+ ..()
+ bakoom()
+
+/obj/item/toy/plushie/plasmamanplushie/proc/bakoom()
+ visible_message("[src] explodes!")
+ if(grenade)
+ explosive_betrayal(grenade)
+ explosion(get_turf(src), -1, 0, 1, 1, flame_range = 1)
+ if(!QDELETED(src))
+ qdel(src)
+
+/obj/item/toy/plushie/draskplushie
+ name = "drask plushie"
+ desc = "This plushie is cool as a cucumber, featuring realistic soap-munching action."
+ icon_state = "plushie_drask"
+ rare_hug_sound = 'sound/voice/drasktalk.ogg'
+ rare_hug_word = "Ruuuumble..."
+
+/obj/item/toy/plushie/draskplushie/attack_by(obj/item/attacking, mob/user, params)
+ if(..())
+ return FINISH_ATTACK
+ if(istype(attacking, /obj/item/soap))
+ if(prob(20))
+ visible_message("[src] consumes the soap...")
+ qdel(attacking)
+ return FINISH_ATTACK
+ visible_message("[src] munches the soap...")
+ playsound(loc, 'sound/items/eatfood.ogg', 50, TRUE)
+
+/obj/item/toy/plushie/kidanplushie
+ name = "kidan plushie"
+ desc = "F-ANT-asticly fun kidan plushie! Exoskeleton has never been so soft. The label says to keep it away from insecticides"
+ icon_state = "plushie_kidan"
+ var/sadbug = FALSE
+ rare_hug_sound = 'sound/effects/Kidanclack.ogg'
+ rare_hug_word = "Click clack!"
+
+/obj/item/toy/plushie/kidanplushie/activate_self(mob/user)
+ if(..())
+ return
+ if(prob(10) && sadbug)
+ visible_message("[src] begins to cheer up!")
+ icon_state = "plushie_kidan"
+ sadbug = FALSE
+
+/obj/item/toy/plushie/kidanplushie/proc/make_cry()
+ visible_message("[src] starts to cry...")
+ icon_state = "plushie_kidansad"
+ sadbug = TRUE
+
/*
* Foam Armblade
*/
@@ -1047,7 +1290,9 @@
else
. += "single_latch"
-/obj/item/toy/windup_toolbox/attack_self__legacy__attackchain(mob/user)
+/obj/item/toy/windup_toolbox/activate_self(mob/user)
+ if(..())
+ return
if(!active)
to_chat(user, "You wind up [src], it begins to rumble.")
active = TRUE
@@ -1076,10 +1321,12 @@
item_state = "flashtool"
w_class = WEIGHT_CLASS_TINY
-/obj/item/toy/flash/attack__legacy__attackchain(mob/living/M, mob/user)
+/obj/item/toy/flash/attack(mob/living/target, mob/living/carbon/human/user)
+ if(..())
+ return FINISH_ATTACK
playsound(src.loc, 'sound/weapons/flash.ogg', 100, 1)
flick("[initial(icon_state)]2", src)
- user.visible_message("[user] blinds [M] with the flash!")
+ user.visible_message("[user] blinds [target] with the flash!")
/*
@@ -1093,7 +1340,9 @@
w_class = WEIGHT_CLASS_SMALL
var/cooldown = 0
-/obj/item/toy/redbutton/attack_self__legacy__attackchain(mob/user)
+/obj/item/toy/redbutton/activate_self(mob/user)
+ if(..())
+ return
if(cooldown >= world.time)
to_chat(user, "Nothing happens.")
return
@@ -1118,16 +1367,15 @@
w_class = WEIGHT_CLASS_SMALL
var/cooldown = 0
-/obj/item/toy/ai/attack_self__legacy__attackchain(mob/user)
- if(!cooldown) //for the sanity of everyone
- var/message = generate_ion_law()
- to_chat(user, "You press the button on [src].")
- playsound(user, 'sound/machines/click.ogg', 20, 1)
- visible_message("[bicon(src)] [message]")
- cooldown = 1
- spawn(30) cooldown = 0
+/obj/item/toy/ai/activate_self(mob/user)
+ if(..() || cooldown) //for the sanity of everyone
return
- ..()
+ var/message = generate_ion_law()
+ to_chat(user, "You press the button on [src].")
+ playsound(user, 'sound/machines/click.ogg', 20, TRUE)
+ visible_message("[bicon(src)] [message]")
+ cooldown = 1
+ spawn(30) cooldown = 0
/obj/item/toy/codex_gigas
name = "Toy Codex Gigas"
@@ -1138,18 +1386,19 @@
var/list/messages = list("You must challenge the devil to a dance-off!", "The devils true name is Ian", "The devil hates salt!", "Would you like infinite power?", "Would you like infinite wisdom?", " Would you like infinite healing?")
var/cooldown = FALSE
-/obj/item/toy/codex_gigas/attack_self__legacy__attackchain(mob/user)
- if(!cooldown)
- user.visible_message(
- "[user] presses the button on \the [src].",
- "You press the button on \the [src].",
- "You hear a soft click.")
- playsound(loc, 'sound/machines/click.ogg', 20, TRUE)
- cooldown = TRUE
- addtimer(VARSET_CALLBACK(src, cooldown, FALSE), 60)
- for(var/message in pick(messages))
- user.loc.visible_message("[bicon(src)] [message]")
- sleep(10)
+/obj/item/toy/codex_gigas/activate_self(mob/user)
+ if(..() || cooldown)
+ return
+ user.visible_message(
+ "[user] presses the button on \the [src].",
+ "You press the button on \the [src].",
+ "You hear a soft click.")
+ playsound(loc, 'sound/machines/click.ogg', 20, TRUE)
+ cooldown = TRUE
+ addtimer(VARSET_CALLBACK(src, cooldown, FALSE), 60)
+ for(var/message in pick(messages))
+ user.loc.visible_message("[bicon(src)] [message]")
+ sleep(10)
// DND Character minis. Use the naming convention (type)character for the icon states.
/obj/item/toy/character
@@ -1198,7 +1447,8 @@
attack_verb = list("attacked", "bashed", "smashed", "stoned")
hitsound = "swing_hit"
-/obj/item/toy/pet_rock/attack_self__legacy__attackchain(mob/user)
+/obj/item/toy/pet_rock/activate_self(mob/user)
+ . = ..()
var/cuddle_verb = pick("admires", "respects", "cherises", "appreciates")
user.visible_message("[user] [cuddle_verb] [src].")
@@ -1223,8 +1473,9 @@
var/cooldown = 0
var/obj/stored_minature = null
-/obj/item/toy/minigibber/attack_self__legacy__attackchain(mob/user)
-
+/obj/item/toy/minigibber/activate_self(mob/user)
+ if(..())
+ return
if(stored_minature)
to_chat(user, "\The [src] makes a violent grinding noise as it tears apart the miniature figure inside!")
QDEL_NULL(stored_minature)
@@ -1236,20 +1487,22 @@
playsound(user, 'sound/goonstation/effects/gib.ogg', 20, 1)
cooldown = world.time
-/obj/item/toy/minigibber/attackby__legacy__attackchain(obj/O, mob/user, params)
- if(istype(O,/obj/item/toy/character) && O.loc == user)
- to_chat(user, "You start feeding \the [O] [bicon(O)] into \the [src]'s mini-input.")
+/obj/item/toy/minigibber/attack_by(obj/item/attacking, mob/user, params)
+ if(..())
+ return FINISH_ATTACK
+ if(istype(attacking,/obj/item/toy/character) && attacking.loc == user)
+ to_chat(user, "You start feeding \the [attacking] [bicon(attacking)] into \the [src]'s mini-input.")
if(do_after(user, 10, target = src))
- if(O.loc != user)
- to_chat(user, "\The [O] is too far away to feed into \the [src]!")
+ if(attacking.loc != user)
+ to_chat(user, "\The [attacking] is too far away to feed into \the [src]!")
else
- to_chat(user, "You feed \the [O] [bicon(O)] into \the [src]!")
- user.unEquip(O)
- O.forceMove(src)
- stored_minature = O
+ to_chat(user, "You feed \the [attacking] [bicon(attacking)] into \the [src]!")
+ user.unEquip(attacking)
+ attacking.forceMove(src)
+ stored_minature = attacking
else
- to_chat(user, "You stop feeding \the [O] into \the [src]'s mini-input.")
- else ..()
+ to_chat(user, "You stop feeding \the [attacking] into \the [src]'s mini-input.")
+ return FINISH_ATTACK
/obj/item/toy/russian_revolver
name = "russian revolver"
@@ -1282,7 +1535,9 @@
..()
spin_cylinder()
-/obj/item/toy/russian_revolver/attack_self__legacy__attackchain(mob/user)
+/obj/item/toy/russian_revolver/activate_self(mob/user)
+ if(..())
+ return
if(!bullets_left)
user.visible_message("[user] loads a bullet into [src]'s cylinder before spinning it.")
spin_cylinder()
@@ -1290,11 +1545,12 @@
user.visible_message("[user] spins the cylinder on [src]!")
spin_cylinder()
-/obj/item/toy/russian_revolver/attack__legacy__attackchain(mob/M, mob/living/user)
- return
+/obj/item/toy/russian_revolver/interact_with_atom(atom/target, mob/living/user, list/modifiers)
+ return ITEM_INTERACT_SKIP_TO_AFTER_ATTACK
-/obj/item/toy/russian_revolver/afterattack__legacy__attackchain(atom/target, mob/user, flag, params)
- if(flag)
+/obj/item/toy/russian_revolver/after_attack(atom/target, mob/user, proximity_flag, click_parameters)
+ . = ..()
+ if(proximity_flag)
if(target in user.contents)
return
if(!ismob(target))
@@ -1361,7 +1617,9 @@
to_chat(user, "You go to spin the chamber... and it goes off in your face!")
shoot_gun(user)
-/obj/item/toy/russian_revolver/trick_revolver/attack_self__legacy__attackchain(mob/user)
+/obj/item/toy/russian_revolver/trick_revolver/activate_self(mob/user)
+ if(..())
+ return
if(!bullets_left) //You can re-arm the trap...
user.visible_message("[user] loads a bullet into [src]'s cylinder before spinning it.")
spin_cylinder()
@@ -1369,17 +1627,18 @@
user.visible_message("[user] tries to empty [src], but it goes off in their face!")
shoot_gun(user)
-/obj/item/toy/russian_revolver/trick_revolver/attackby__legacy__attackchain(obj/item/I, mob/user, params)
- if(is_pen(I))
+/obj/item/toy/russian_revolver/trick_revolver/attack_by(obj/item/attacking, mob/user, params)
+ if(..())
+ return FINISH_ATTACK
+ if(is_pen(attacking))
to_chat(user, "You go to write on [src].. and it goes off in your face!")
shoot_gun(user)
- if(istype(I, /obj/item/ammo_casing/a357))
+ if(istype(attacking, /obj/item/ammo_casing/a357))
to_chat(user, "You go to load a bullet into [src].. and it goes off in your face!")
shoot_gun(user)
- if(istype(I, /obj/item/ammo_box/a357))
+ if(istype(attacking, /obj/item/ammo_box/a357))
to_chat(user, "You go to speedload [src].. and it goes off in your face!")
shoot_gun(user)
- return ..()
/obj/item/toy/russian_revolver/trick_revolver/run_pointed_on_item(mob/pointer_mob, atom/target_atom)
if(target_atom != src)
@@ -1449,8 +1708,9 @@
var/cooldown = 0
var/cooldown_time = 3 SECONDS
-/obj/item/toy/figure/attack_self__legacy__attackchain(mob/user)
- ..()
+/obj/item/toy/figure/activate_self(mob/user)
+ if(..())
+ return
if(cooldown < world.time)
cooldown = world.time + cooldown_time
activate(user)
@@ -1832,14 +2092,14 @@
var/cooldown = 0
var/list/possible_answers = list("Definitely", "All signs point to yes.", "Most likely.", "Yes.", "Ask again later.", "Better not tell you now.", "Future Unclear.", "Maybe.", "Doubtful.", "No.", "Don't count on it.", "Never.")
-/obj/item/toy/eight_ball/attack_self__legacy__attackchain(mob/user as mob)
- if(!cooldown)
- var/answer = pick(possible_answers)
- user.visible_message("[user] focuses on [user.p_their()] question and [use_action]...")
- user.visible_message("[bicon(src)] [src] says \"[answer]\"")
- spawn(30)
- cooldown = 0
+/obj/item/toy/eight_ball/activate_self(mob/user as mob)
+ if(..() || cooldown)
return
+ var/answer = pick(possible_answers)
+ user.visible_message("[user] focuses on [user.p_their()] question and [use_action]...")
+ user.visible_message("[bicon(src)] [src] says \"[answer]\"")
+ spawn(30)
+ cooldown = 0
/obj/item/toy/eight_ball/conch
name = "\improper Magic Conch Shell"
diff --git a/code/game/objects/items/weapons/caution.dm b/code/game/objects/items/weapons/caution.dm
index 4ff358a88272..46e3b9da43cb 100644
--- a/code/game/objects/items/weapons/caution.dm
+++ b/code/game/objects/items/weapons/caution.dm
@@ -16,10 +16,11 @@
var/timing = FALSE
var/armed = FALSE
var/timepassed = 0
+ var/datum/proximity_monitor/proximity_monitor
/obj/item/caution/proximity_sign/Initialize(mapload)
. = ..()
- AddComponent(/datum/component/proximity_monitor)
+ proximity_monitor = new(src, 1)
/obj/item/caution/proximity_sign/attack_self__legacy__attackchain(mob/user as mob)
if(ishuman(user))
diff --git a/code/game/objects/items/weapons/chemical_flamethrower/fire_effect.dm b/code/game/objects/items/weapons/chemical_flamethrower/fire_effect.dm
index e98f1303f42c..1cdf4e1630b1 100644
--- a/code/game/objects/items/weapons/chemical_flamethrower/fire_effect.dm
+++ b/code/game/objects/items/weapons/chemical_flamethrower/fire_effect.dm
@@ -33,6 +33,11 @@ GLOBAL_LIST_EMPTY(flame_effects)
GLOB.flame_effects += src
START_PROCESSING(SSprocessing, src)
+ var/static/list/loc_connections = list(
+ COMSIG_ATOM_ENTERED = PROC_REF(on_atom_entered),
+ )
+ AddElement(/datum/element/connect_loc, loc_connections)
+
/obj/effect/fire/Destroy()
. = ..()
GLOB.flame_effects -= src
@@ -75,18 +80,17 @@ GLOBAL_LIST_EMPTY(flame_effects)
if(duration <= 0)
fizzle()
-/obj/effect/fire/Crossed(atom/movable/AM, oldloc)
- . = ..()
- if(isliving(AM))
- if(!damage_mob(AM))
+/obj/effect/fire/proc/on_atom_entered(datum/source, atom/movable/entered, old_loc)
+ SIGNAL_HANDLER // COMSIG_ATOM_ENTERED
+ if(isliving(entered))
+ if(!damage_mob(entered))
return
- to_chat(AM, "[src] burns you!")
+ to_chat(entered, "[src] burns you!")
return
- if(isitem(AM))
- var/obj/item/item_to_burn = AM
+ if(isitem(entered))
+ var/obj/item/item_to_burn = entered
item_to_burn.fire_act(null, temperature)
- return
/obj/effect/fire/proc/fizzle()
playsound(src, 'sound/effects/fire_sizzle.ogg', 50, TRUE)
diff --git a/code/game/objects/items/weapons/explosives.dm b/code/game/objects/items/weapons/explosives.dm
index be4d87fd36eb..e2539d68fd98 100644
--- a/code/game/objects/items/weapons/explosives.dm
+++ b/code/game/objects/items/weapons/explosives.dm
@@ -21,6 +21,10 @@
/obj/item/grenade/plastic/Initialize(mapload)
. = ..()
plastic_overlay = mutable_appearance(icon, "[item_state]2", HIGH_OBJ_LAYER)
+ var/static/list/loc_connections = list(
+ COMSIG_ATOM_ENTERED = PROC_REF(on_atom_entered)
+ )
+ AddElement(/datum/element/connect_loc, loc_connections)
/obj/item/grenade/plastic/Destroy()
QDEL_NULL(nadeassembly)
@@ -50,9 +54,9 @@
return
..()
-/obj/item/grenade/plastic/Crossed(atom/movable/AM, oldloc)
+/obj/item/grenade/plastic/proc/on_atom_entered(datum/source, atom/movable/entered)
if(nadeassembly)
- nadeassembly.Crossed(AM, oldloc)
+ nadeassembly.on_atom_entered(source, entered)
/obj/item/grenade/plastic/on_found(mob/finder)
if(nadeassembly)
diff --git a/code/game/objects/items/weapons/grenades/chem_grenade.dm b/code/game/objects/items/weapons/grenades/chem_grenade.dm
index 82acfec37235..996b7de9c578 100644
--- a/code/game/objects/items/weapons/grenades/chem_grenade.dm
+++ b/code/game/objects/items/weapons/grenades/chem_grenade.dm
@@ -32,6 +32,11 @@
payload_name += " " // formatting, ignore me
update_icon()
+ var/static/list/loc_connections = list(
+ COMSIG_ATOM_ENTERED = PROC_REF(on_atom_entered),
+ )
+ AddElement(/datum/element/connect_loc, loc_connections)
+
/obj/item/grenade/chem_grenade/Destroy()
QDEL_NULL(nadeassembly)
QDEL_LIST_CONTENTS(beakers)
@@ -246,9 +251,9 @@
if(nadeassembly)
nadeassembly.process_movement()
-/obj/item/grenade/chem_grenade/Crossed(atom/movable/AM, oldloc)
+/obj/item/grenade/chem_grenade/proc/on_atom_entered(datum/source, atom/movable/entered)
if(nadeassembly)
- nadeassembly.Crossed(AM, oldloc)
+ nadeassembly.on_atom_entered(source, entered)
/obj/item/grenade/chem_grenade/on_found(mob/finder)
if(nadeassembly)
diff --git a/code/game/objects/items/weapons/legcuffs.dm b/code/game/objects/items/weapons/legcuffs.dm
index 146695a515a7..2269e2afece1 100644
--- a/code/game/objects/items/weapons/legcuffs.dm
+++ b/code/game/objects/items/weapons/legcuffs.dm
@@ -26,6 +26,13 @@
var/obj/item/grenade/iedcasing/IED = null
var/obj/item/assembly/signaler/sig = null
+/obj/item/restraints/legcuffs/beartrap/Initialize(mapload)
+ . = ..()
+ var/static/list/loc_connections = list(
+ COMSIG_ATOM_ENTERED = PROC_REF(on_atom_entered),
+ )
+ AddElement(/datum/element/connect_loc, loc_connections)
+
/obj/item/restraints/legcuffs/beartrap/update_icon_state()
icon_state = "beartrap[armed]"
@@ -97,16 +104,15 @@
to_chat(user, "You remove the signaler from [src].")
return TRUE
-/obj/item/restraints/legcuffs/beartrap/Crossed(AM as mob|obj, oldloc)
- if(!armed || !isturf(loc))
- return ..()
+/obj/item/restraints/legcuffs/beartrap/proc/on_atom_entered(datum/source, mob/living/entered)
+ if(!armed || !isturf(loc) || !istype(entered))
+ return
- var/mob/living/L = AM
- if((iscarbon(AM) || isanimal(AM)) && !HAS_TRAIT(L, TRAIT_FLYING))
- spring_trap(AM)
+ if((iscarbon(entered) || isanimal(entered)) && !HAS_TRAIT(entered, TRAIT_FLYING))
+ spring_trap(entered)
- if(ishuman(AM))
- var/mob/living/carbon/H = AM
+ if(ishuman(entered))
+ var/mob/living/carbon/H = entered
if(IS_HORIZONTAL(H))
H.apply_damage(trap_damage, BRUTE, "chest")
else
@@ -117,11 +123,10 @@
H.update_inv_legcuffed()
SSblackbox.record_feedback("tally", "handcuffs", 1, type)
else
- if(istype(L, /mob/living/simple_animal/hostile/bear))
- L.apply_damage(trap_damage * 2.5, BRUTE)
+ if(istype(entered, /mob/living/simple_animal/hostile/bear))
+ entered.apply_damage(trap_damage * 2.5, BRUTE)
else
- L.apply_damage(trap_damage * 1.75, BRUTE)
- ..()
+ entered.apply_damage(trap_damage * 1.75, BRUTE)
/obj/item/restraints/legcuffs/beartrap/on_found(mob/finder)
if(!armed)
diff --git a/code/game/objects/items/weapons/shards.dm b/code/game/objects/items/weapons/shards.dm
index fa2ec1c9fdea..44a563a2cb7f 100644
--- a/code/game/objects/items/weapons/shards.dm
+++ b/code/game/objects/items/weapons/shards.dm
@@ -44,6 +44,10 @@
. = ..()
AddComponent(/datum/component/caltrop, force)
set_initial_icon_state()
+ var/static/list/loc_connections = list(
+ COMSIG_ATOM_ENTERED = PROC_REF(on_atom_entered)
+ )
+ AddElement(/datum/element/connect_loc, loc_connections)
/obj/item/shard/afterattack__legacy__attackchain(atom/movable/AM, mob/user, proximity)
if(!proximity || !(src in user))
@@ -70,12 +74,12 @@
to_chat(user, "You add the newly-formed glass to the stack.")
qdel(src)
-/obj/item/shard/Crossed(mob/living/L, oldloc)
- if(istype(L) && has_gravity(loc))
- if(L.incorporeal_move || HAS_TRAIT(L, TRAIT_FLYING) || L.floating)
+/obj/item/shard/proc/on_atom_entered(datum/source, atom/movable/entered)
+ var/mob/living/living_entered = entered
+ if(istype(living_entered) && has_gravity(loc))
+ if(living_entered.incorporeal_move || HAS_TRAIT(living_entered, TRAIT_FLYING) || living_entered.floating)
return
playsound(loc, 'sound/effects/glass_step.ogg', 50, TRUE)
- return ..()
/obj/item/shard/decompile_act(obj/item/matter_decompiler/C, mob/user)
C.stored_comms["glass"] += 3
diff --git a/code/game/objects/items/weapons/storage/bags.dm b/code/game/objects/items/weapons/storage/bags.dm
index a9fc87d9fdd8..d732db1d9fce 100644
--- a/code/game/objects/items/weapons/storage/bags.dm
+++ b/code/game/objects/items/weapons/storage/bags.dm
@@ -167,6 +167,41 @@
max_combined_w_class = 200 //Doesn't matter what this is, so long as it's more or equal to storage_slots * ore.w_class
max_w_class = WEIGHT_CLASS_NORMAL
can_hold = list(/obj/item/stack/ore)
+ /// The mob currently holding the ore bag, to track moving over ore to auto-pickup.
+ var/mob/listening_to
+
+/obj/item/storage/bag/ore/Destroy()
+ . = ..()
+ listening_to = null
+
+/obj/item/storage/bag/ore/equipped(mob/user, slot, initial)
+ . = ..()
+ if(listening_to == user)
+ return
+ if(listening_to)
+ UnregisterSignal(listening_to, COMSIG_MOVABLE_MOVED)
+ RegisterSignal(user, COMSIG_MOVABLE_MOVED, PROC_REF(pickup_ores))
+ listening_to = user
+
+/obj/item/storage/bag/ore/dropped()
+ . = ..()
+ if(listening_to)
+ UnregisterSignal(listening_to, COMSIG_MOVABLE_MOVED)
+ listening_to = null
+
+/obj/item/storage/bag/ore/proc/pickup_ores(mob/living/user)
+ SIGNAL_HANDLER // COMSIG_MOVABLE_MOVED
+ var/turf/simulated/floor/plating/asteroid/tile = get_turf(user)
+
+ if(!istype(tile))
+ return
+
+ tile.attempt_ore_pickup(src, user)
+ // Then, if the user is dragging an ore box, empty the satchel
+ // into the box.
+ if(istype(user.pulling, /obj/structure/ore_box))
+ var/obj/structure/ore_box/box = user.pulling
+ box.attackby__legacy__attackchain(src, user)
/obj/item/storage/bag/ore/cyborg
name = "cyborg mining satchel"
diff --git a/code/game/objects/items/weapons/storage/briefcase.dm b/code/game/objects/items/weapons/storage/briefcase.dm
index ab0409a71529..d29ba66b3456 100644
--- a/code/game/objects/items/weapons/storage/briefcase.dm
+++ b/code/game/objects/items/weapons/storage/briefcase.dm
@@ -61,7 +61,7 @@
stored_item = I
max_w_class = WEIGHT_CLASS_NORMAL - stored_item.w_class
- I.forceMove(null) //null space here we go - to stop it showing up in the briefcase
+ I.moveToNullspace() // to stop it showing up in the briefcase
to_chat(user, "You place [I] into the false bottom of the briefcase.")
else
return ..()
diff --git a/code/game/objects/items/weapons/storage/storage_base.dm b/code/game/objects/items/weapons/storage/storage_base.dm
index 6581136461d5..0b5236d0e316 100644
--- a/code/game/objects/items/weapons/storage/storage_base.dm
+++ b/code/game/objects/items/weapons/storage/storage_base.dm
@@ -74,6 +74,7 @@
orient2hud()
ADD_TRAIT(src, TRAIT_ADJACENCY_TRANSPARENT, ROUNDSTART_TRAIT)
+ RegisterSignal(src, COMSIG_ATOM_EXITED, PROC_REF(on_atom_exited))
/obj/item/storage/Destroy()
for(var/obj/O in contents)
@@ -490,6 +491,9 @@
update_icon()
return TRUE
+/obj/item/storage/proc/on_atom_exited(datum/source, atom/exited, direction)
+ return remove_from_storage(exited, exited.loc)
+
/**
* Handles the removal of an item from a storage container.
*
@@ -530,10 +534,6 @@
update_icon()
return TRUE
-/obj/item/storage/Exited(atom/A, loc)
- remove_from_storage(A, loc) //worry not, comrade; this only gets called once
- ..()
-
/obj/item/storage/deconstruct(disassembled = TRUE)
var/drop_loc = loc
if(ismob(loc))
diff --git a/code/game/objects/items/weapons/storage/wallets.dm b/code/game/objects/items/weapons/storage/wallets.dm
index 4fa12539ba08..c06f29530f5b 100644
--- a/code/game/objects/items/weapons/storage/wallets.dm
+++ b/code/game/objects/items/weapons/storage/wallets.dm
@@ -15,6 +15,11 @@
/obj/item/stack/medical,
/obj/item/toy/crayon,
/obj/item/coin,
+ /obj/item/food/candy/chocolate_coin,
+ /obj/item/food/candy/coin,
+ /obj/item/food/candy/cash,
+ /obj/item/food/customizable/candy/coin,
+ /obj/item/food/customizable/candy/cash,
/obj/item/dice,
/obj/item/disk,
/obj/item/bio_chip_implanter,
diff --git a/code/game/objects/structures/aliens.dm b/code/game/objects/structures/aliens.dm
index e4c2b29b69ef..1a80c3bac7c2 100644
--- a/code/game/objects/structures/aliens.dm
+++ b/code/game/objects/structures/aliens.dm
@@ -468,6 +468,7 @@
*In the BURST/BURSTING state, the alien egg can be removed by being attacked by a alien or any other weapon
**/
var/status = GROWING
+ var/datum/proximity_monitor/proximity_monitor
/obj/structure/alien/egg/grown
status = GROWN
@@ -485,7 +486,7 @@
else if(status != GROWN)
addtimer(CALLBACK(src, PROC_REF(grow)), rand(MIN_GROWTH_TIME, MAX_GROWTH_TIME))
if(status == GROWN)
- AddComponent(/datum/component/proximity_monitor)
+ proximity_monitor = new(src)
/obj/structure/alien/egg/attack_alien(mob/living/carbon/alien/user)
return attack_hand(user)
@@ -516,7 +517,7 @@
/obj/structure/alien/egg/proc/grow()
icon_state = "egg"
status = GROWN
- AddComponent(/datum/component/proximity_monitor)
+ proximity_monitor = new(src)
///Need to carry the kill from Burst() to Hatch(), this section handles the alien opening the egg
/obj/structure/alien/egg/proc/burst(kill)
@@ -524,8 +525,7 @@
icon_state = "egg_hatched"
flick("egg_opening", src)
status = BURSTING
- DeleteComponent(/datum/component/proximity_monitor)
-
+ QDEL_NULL(proximity_monitor)
addtimer(CALLBACK(src, PROC_REF(hatch)), 1.5 SECONDS)
///We now check HOW the hugger is hatching, kill carried from Burst() and obj_break()
diff --git a/code/game/objects/structures/coathanger.dm b/code/game/objects/structures/coathanger.dm
index b6f30a05c181..71c1a2372482 100644
--- a/code/game/objects/structures/coathanger.dm
+++ b/code/game/objects/structures/coathanger.dm
@@ -29,7 +29,7 @@
return
return ..()
-/obj/structure/coatrack/CanPass(atom/movable/mover, turf/target)
+/obj/structure/coatrack/CanPass(atom/movable/mover, border_dir)
var/can_hang = FALSE
for(var/T in allowed)
if(istype(mover,T))
diff --git a/code/game/objects/structures/crates_lockers/closets.dm b/code/game/objects/structures/crates_lockers/closets.dm
index b3b6ef724e98..fc3904381f10 100644
--- a/code/game/objects/structures/crates_lockers/closets.dm
+++ b/code/game/objects/structures/crates_lockers/closets.dm
@@ -167,7 +167,7 @@
QDEL_NULL(door_obj)
return ..()
-/obj/structure/closet/CanPass(atom/movable/mover, turf/target)
+/obj/structure/closet/CanPass(atom/movable/mover, border_dir)
if(wall_mounted)
return TRUE
return (!density)
@@ -516,14 +516,23 @@
storage_capacity = 60
var/materials = list(MAT_METAL = 5000, MAT_PLASMA = 2500, MAT_TITANIUM = 500, MAT_BLUESPACE = 500)
-/obj/structure/closet/bluespace/CheckExit(atom/movable/AM)
- UpdateTransparency(AM, loc)
- return TRUE
+/obj/structure/closet/bluespace/Initialize(mapload)
+ . = ..()
+ var/static/list/loc_connections = list(
+ COMSIG_ATOM_ENTERED = PROC_REF(UpdateTransparency),
+ COMSIG_ATOM_EXITED = PROC_REF(UpdateTransparency),
+ )
+
+ AddElement(/datum/element/connect_loc, loc_connections)
-/obj/structure/closet/bluespace/proc/UpdateTransparency(atom/movable/AM, atom/location)
+/obj/structure/closet/bluespace/proc/UpdateTransparency()
+ SIGNAL_HANDLER // COMSIG_ATOM_ENTERED + COMSIG_ATOM_EXITED
transparent = FALSE
- for(var/atom/A in location)
- if(A.density && A != src && A != AM)
+ if(!get_turf(loc))
+ return
+
+ for(var/atom/A in loc)
+ if(A.density && A != src)
transparent = TRUE
alpha = 180
update_icon()
@@ -531,10 +540,6 @@
alpha = 255
update_icon()
-/obj/structure/closet/bluespace/Crossed(atom/movable/AM, oldloc)
- if(AM.density)
- UpdateTransparency(location = loc)
-
/obj/structure/closet/bluespace/Move(NewLoc, direct) // Allows for "phasing" throug objects but doesn't allow you to stuff your EOC homebois in one of these and push them through walls.
var/turf/T = get_turf(NewLoc)
if(T.density)
@@ -542,8 +547,10 @@
for(var/atom/A in T.contents)
if(A.density && isairlock(A))
return
- UpdateTransparency(src, NewLoc)
- forceMove(NewLoc)
+
+ . = ..()
+
+ UpdateTransparency()
/obj/structure/closet/bluespace/close()
. = ..()
diff --git a/code/game/objects/structures/fence.dm b/code/game/objects/structures/fence.dm
index 351d42a6b675..657d374800a7 100644
--- a/code/game/objects/structures/fence.dm
+++ b/code/game/objects/structures/fence.dm
@@ -58,7 +58,7 @@
icon_state = "straight_cut3"
hole_size = LARGE_HOLE
-/obj/structure/fence/CanPass(atom/movable/mover, turf/target)
+/obj/structure/fence/CanPass(atom/movable/mover, border_dir)
if(istype(mover) && mover.checkpass(PASSFENCE))
return TRUE
if(isprojectile(mover))
diff --git a/code/game/objects/structures/girders.dm b/code/game/objects/structures/girders.dm
index a7f2125a0a71..746c4b9b7263 100644
--- a/code/game/objects/structures/girders.dm
+++ b/code/game/objects/structures/girders.dm
@@ -406,7 +406,7 @@
refundMetal(metalUsed)
qdel(src)
-/obj/structure/girder/CanPass(atom/movable/mover, turf/target)
+/obj/structure/girder/CanPass(atom/movable/mover, border_dir)
if(istype(mover) && mover.checkpass(PASSGIRDER))
return TRUE
if(istype(mover) && mover.checkpass(PASSGRILLE))
diff --git a/code/game/objects/structures/grille.dm b/code/game/objects/structures/grille.dm
index e0aa89bca9b1..c4a1b48e218a 100644
--- a/code/game/objects/structures/grille.dm
+++ b/code/game/objects/structures/grille.dm
@@ -90,7 +90,7 @@
if(!shock(user, 70))
take_damage(20, BRUTE, MELEE, 1)
-/obj/structure/grille/CanPass(atom/movable/mover, turf/target)
+/obj/structure/grille/CanPass(atom/movable/mover, border_dir)
. = !density
if(istype(mover) && mover.checkpass(PASSGRILLE))
return TRUE
diff --git a/code/game/objects/structures/holosigns.dm b/code/game/objects/structures/holosigns.dm
index 655551f6eb95..3631b28451de 100644
--- a/code/game/objects/structures/holosigns.dm
+++ b/code/game/objects/structures/holosigns.dm
@@ -57,7 +57,7 @@
max_integrity = 20
var/allow_walk = TRUE //can we pass through it on walk intent
-/obj/structure/holosign/barrier/CanPass(atom/movable/mover, turf/target)
+/obj/structure/holosign/barrier/CanPass(atom/movable/mover, border_dir)
if(!density)
return TRUE
if(mover.pass_flags & (PASSGLASS|PASSTABLE|PASSGRILLE))
diff --git a/code/game/objects/structures/inflatable.dm b/code/game/objects/structures/inflatable.dm
index 0c1267ff8918..1db795432b71 100644
--- a/code/game/objects/structures/inflatable.dm
+++ b/code/game/objects/structures/inflatable.dm
@@ -42,7 +42,7 @@
. = ..()
T.recalculate_atmos_connectivity()
-/obj/structure/inflatable/CanPass(atom/movable/mover, turf/target)
+/obj/structure/inflatable/CanPass(atom/movable/mover, border_dir)
return
/obj/structure/inflatable/CanAtmosPass(direction)
@@ -116,7 +116,7 @@
/obj/structure/inflatable/door/attack_hand(mob/user as mob)
return try_to_operate(user)
-/obj/structure/inflatable/door/CanPass(atom/movable/mover, turf/target)
+/obj/structure/inflatable/door/CanPass(atom/movable/mover, border_dir)
if(istype(mover, /obj/effect/beam))
return !opacity
return !density
diff --git a/code/game/objects/structures/mineral_doors.dm b/code/game/objects/structures/mineral_doors.dm
index c17308c90af6..144bf5f747df 100644
--- a/code/game/objects/structures/mineral_doors.dm
+++ b/code/game/objects/structures/mineral_doors.dm
@@ -59,7 +59,7 @@
if(user.can_advanced_admin_interact())
operate()
-/obj/structure/mineral_door/CanPass(atom/movable/mover, turf/target)
+/obj/structure/mineral_door/CanPass(atom/movable/mover, border_dir)
if(istype(mover, /obj/effect/beam))
return !opacity
return !density
diff --git a/code/game/objects/structures/morgue.dm b/code/game/objects/structures/morgue.dm
index 9ed6bfc1750b..12403792f164 100644
--- a/code/game/objects/structures/morgue.dm
+++ b/code/game/objects/structures/morgue.dm
@@ -294,7 +294,7 @@
connected = null
return ..()
-/obj/structure/m_tray/CanPass(atom/movable/mover, turf/target)
+/obj/structure/m_tray/CanPass(atom/movable/mover, border_dir)
if(istype(mover))
if(mover.checkpass(PASSTABLE))
return TRUE
diff --git a/code/game/objects/structures/nest.dm b/code/game/objects/structures/nest.dm
index 0deca8f6de66..a85aaf417d65 100644
--- a/code/game/objects/structures/nest.dm
+++ b/code/game/objects/structures/nest.dm
@@ -25,6 +25,13 @@
var/spawn_mob_options = list(/mob/living/simple_animal/crab) // The nest picks one mob type of this list and spawns them
var/spawn_trigger_distance = 7 // The triggered nest will look this many tiles around itself to find other triggerable nests
+/obj/structure/nest/Initialize(mapload)
+ . = ..()
+ var/static/list/loc_connections = list(
+ COMSIG_ATOM_ENTERED = PROC_REF(on_atom_entered),
+ )
+ AddElement(/datum/element/connect_loc, loc_connections)
+
/obj/structure/nest/examine(mob/user)
. = ..()
if(!spawn_is_triggered)
@@ -35,12 +42,13 @@
return
..()
-/obj/structure/nest/Crossed(atom/movable/AM)
+/obj/structure/nest/proc/on_atom_entered(datum/source, atom/movable/entered)
+ SIGNAL_HANDLER // COMSIG_ATOM_ENTERED
if(spawn_is_triggered)
return
- if(!isliving(AM))
+ if(!isliving(entered))
return
- var/mob/living/L = AM
+ var/mob/living/L = entered
if(!L.mind)
return
diff --git a/code/game/objects/structures/railings.dm b/code/game/objects/structures/railings.dm
index ebe2e672006d..5c229ce50d6f 100644
--- a/code/game/objects/structures/railings.dm
+++ b/code/game/objects/structures/railings.dm
@@ -12,6 +12,14 @@
var/currently_climbed = FALSE
var/mover_dir = null
+/obj/structure/railing/Initialize(mapload)
+ . = ..()
+ if(density && flags & ON_BORDER) // blocks normal movement from and to the direction it's facing.
+ var/static/list/loc_connections = list(
+ COMSIG_ATOM_EXIT = PROC_REF(on_atom_exit),
+ )
+ AddElement(/datum/element/connect_loc, loc_connections)
+
/obj/structure/railing/get_climb_text()
return "You can Click-Drag yourself to [src] to climb over it after a short delay."
@@ -87,8 +95,8 @@
/obj/structure/railing/corner/CanPathfindPass(to_dir, datum/can_pass_info/pass_info)
return TRUE
-/obj/structure/railing/corner/CheckExit()
- return TRUE
+/obj/structure/railing/corner/on_atom_exit(datum/source, atom/movable/leaving, direction)
+ return
/obj/structure/railing/cap/CanPass()
return TRUE
@@ -96,10 +104,10 @@
/obj/structure/railing/cap/CanPathfindPass(to_dir, datum/can_pass_info/pass_info)
return TRUE
-/obj/structure/railing/cap/CheckExit()
- return TRUE
+/obj/structure/railing/cap/on_atom_exit(datum/source, atom/movable/leaving, direction)
+ return
-/obj/structure/railing/CanPass(atom/movable/mover, turf/target)
+/obj/structure/railing/CanPass(atom/movable/mover, border_dir)
if(istype(mover) && mover.checkpass(PASSFENCE))
return TRUE
if(isprojectile(mover))
@@ -110,11 +118,10 @@
return TRUE
if(mover.throwing)
return TRUE
- mover_dir = get_dir(loc, target)
//Due to how the other check is done, it would always return density for ordinal directions no matter what
- if(ordinal_direction_check(mover_dir))
+ if(ordinal_direction_check(border_dir))
return FALSE
- if(mover_dir != dir)
+ if(border_dir != dir)
return density
return FALSE
@@ -126,27 +133,27 @@
return TRUE
-/obj/structure/railing/CheckExit(atom/movable/O, target)
- var/mob/living/M = O
- if(istype(O) && O.checkpass(PASSFENCE))
- return TRUE
- if(isprojectile(O))
- return TRUE
+/obj/structure/railing/proc/on_atom_exit(datum/source, atom/movable/leaving, direction)
+ SIGNAL_HANDLER // COMSIG_ATOM_EXIT
+
+ var/mob/living/M = leaving
+ if(istype(leaving) && leaving.checkpass(PASSFENCE))
+ return
+ if(isprojectile(leaving))
+ return
if(istype(M))
if(HAS_TRAIT(M, TRAIT_FLYING) || M.floating || (IS_HORIZONTAL(M) && HAS_TRAIT(M, TRAIT_CONTORTED_BODY)))
- return TRUE
- if(O.throwing)
- return TRUE
- if(O.move_force >= MOVE_FORCE_EXTREMELY_STRONG)
- return TRUE
+ return
+ if(leaving.throwing)
+ return
+ if(leaving.move_force >= MOVE_FORCE_EXTREMELY_STRONG)
+ return
if(currently_climbed)
- return TRUE
- mover_dir = get_dir(O.loc, target)
- if(mover_dir == dir)
- return FALSE
- if(ordinal_direction_check(mover_dir))
- return FALSE
- return TRUE
+ return
+ if(direction == dir)
+ return COMPONENT_ATOM_BLOCK_EXIT
+ if(ordinal_direction_check(direction))
+ return COMPONENT_ATOM_BLOCK_EXIT
// Checks if the direction the mob is trying to move towards would be blocked by a corner railing
/obj/structure/railing/proc/ordinal_direction_check(check_dir)
diff --git a/code/game/objects/structures/tables_racks.dm b/code/game/objects/structures/tables_racks.dm
index 23f533f22967..cd70a5ff4bca 100644
--- a/code/game/objects/structures/tables_racks.dm
+++ b/code/game/objects/structures/tables_racks.dm
@@ -47,6 +47,11 @@
/obj/structure/table/Initialize(mapload)
. = ..()
+ var/static/list/loc_connections = list(
+ COMSIG_ATOM_EXIT = PROC_REF(on_atom_exit),
+ )
+ AddElement(/datum/element/connect_loc, loc_connections)
+
if(flipped)
update_icon()
@@ -126,9 +131,10 @@
/obj/structure/table/proc/item_placed(item)
return
-/obj/structure/table/CanPass(atom/movable/mover, turf/target)
+/obj/structure/table/CanPass(atom/movable/mover, border_dir)
if(istype(mover,/obj/item/projectile))
- return (check_cover(mover,target))
+ return check_cover(mover, border_dir)
+
var/mob/living/living_mover = mover
if(istype(living_mover) && (HAS_TRAIT(living_mover, TRAIT_FLYING) || (IS_HORIZONTAL(living_mover) && HAS_TRAIT(living_mover, TRAIT_CONTORTED_BODY))))
return TRUE
@@ -141,7 +147,7 @@
if(!T.flipped)
return TRUE
if(flipped)
- if(get_dir(loc, target) == dir)
+ if(border_dir == dir)
return !density
else
return TRUE
@@ -158,30 +164,28 @@
*
* Arguments:
* * P - The projectile trying to cross.
- * * from - Where the projectile is located.
+ * * proj_dir - The incoming direction of the projectile.
*/
-/obj/structure/table/proc/check_cover(obj/item/projectile/P, turf/from)
+/obj/structure/table/proc/check_cover(obj/item/projectile/P, proj_dir)
. = TRUE
if(!flipped)
return
if(get_dist(P.starting, loc) <= 1) // Tables won't help you if people are THIS close
return
- var/proj_dir = get_dir(from, loc)
- var/block_dir = get_dir(get_step(loc, dir), loc)
- if(proj_dir != block_dir) // Back/side shots may pass
+ if(proj_dir != dir) // Back/side shots may pass
return
if(prob(40))
return FALSE // Blocked
-/obj/structure/table/CheckExit(atom/movable/O, turf/target)
- if(istype(O) && O.checkpass(PASSTABLE))
- return 1
+/obj/structure/table/proc/on_atom_exit(datum/source, atom/movable/leaving, direction)
+ SIGNAL_HANDLER // COMSIG_ATOM_EXIT
+
+ if(istype(leaving) && leaving.checkpass(PASSTABLE))
+ return
+
if(flipped)
- if(get_dir(loc, target) == dir)
- return !density
- else
- return 1
- return 1
+ if(direction == dir && density)
+ return COMPONENT_ATOM_BLOCK_EXIT
/obj/structure/table/MouseDrop_T(obj/O, mob/user)
if(..())
@@ -461,27 +465,32 @@
. = ..()
debris += new frame
debris += new shardtype
+ var/static/list/loc_connections = list(
+ COMSIG_ATOM_ENTERED = PROC_REF(on_entered),
+ )
+ AddElement(/datum/element/connect_loc, loc_connections)
/obj/structure/table/glass/Destroy()
for(var/i in debris)
qdel(i)
. = ..()
-/obj/structure/table/glass/Crossed(atom/movable/AM, oldloc)
- . = ..()
+/obj/structure/table/glass/proc/on_entered(datum/source, atom/movable/entered)
+ SIGNAL_HANDLER // COMSIG_ATOM_ENTERED
+
if(flags & NODECONSTRUCT)
return
- if(!isliving(AM))
+ if(!isliving(entered))
return
- var/mob/living/L = AM
+ var/mob/living/L = entered
if(L.incorporeal_move || HAS_TRAIT(L, TRAIT_FLYING) || L.floating)
return
// Don't break if they're just flying past
- if(AM.throwing)
- addtimer(CALLBACK(src, PROC_REF(throw_check), AM), 5)
+ if(entered.throwing)
+ addtimer(CALLBACK(src, PROC_REF(throw_check), entered), 5)
else
- check_break(AM)
+ check_break(entered)
/obj/structure/table/glass/proc/throw_check(mob/living/M)
if(M.loc == get_turf(src))
@@ -905,7 +914,7 @@
. = ..()
. += "It's held together by a couple of bolts."
-/obj/structure/rack/CanPass(atom/movable/mover, turf/target)
+/obj/structure/rack/CanPass(atom/movable/mover, border_dir)
if(!density) //Because broken racks -Agouri |TODO: SPRITE!|
return 1
if(istype(mover))
diff --git a/code/game/objects/structures/transit_tubes/transit_tube.dm b/code/game/objects/structures/transit_tubes/transit_tube.dm
index ad0fbabd901e..b5612911dd13 100644
--- a/code/game/objects/structures/transit_tubes/transit_tube.dm
+++ b/code/game/objects/structures/transit_tubes/transit_tube.dm
@@ -33,7 +33,7 @@
P.empty_pod()
return ..()
-/obj/structure/transit_tube/CanPass(atom/movable/mover, turf/target)
+/obj/structure/transit_tube/CanPass(atom/movable/mover, border_dir)
if(istype(mover) && mover.checkpass(PASSGLASS))
return TRUE
return !density
diff --git a/code/game/objects/structures/watercloset.dm b/code/game/objects/structures/watercloset.dm
index e882da691101..308d5e6b6882 100644
--- a/code/game/objects/structures/watercloset.dm
+++ b/code/game/objects/structures/watercloset.dm
@@ -307,6 +307,13 @@
pixel_y = -5
layer = FLY_LAYER
+/obj/machinery/shower/Initialize(mapload)
+ . = ..()
+ var/static/list/loc_connections = list(
+ COMSIG_ATOM_ENTERED = PROC_REF(on_atom_entered),
+ )
+ AddElement(/datum/element/connect_loc, loc_connections)
+
/obj/machinery/shower/Destroy()
QDEL_NULL(soundloop)
var/obj/effect/mist/mist = locate() in loc
@@ -401,10 +408,10 @@
if(mist && (!on || current_temperature == SHOWER_FREEZING))
qdel(mist)
-/obj/machinery/shower/Crossed(atom/movable/AM)
- ..()
+/obj/machinery/shower/proc/on_atom_entered(datum/source, atom/movable/entered)
+ SIGNAL_HANDLER // COMSIG_ATOM_ENTERED
if(on)
- wash(AM)
+ wash(entered)
/obj/machinery/shower/proc/convertHeat()
switch(current_temperature)
diff --git a/code/game/objects/structures/windoor_assembly.dm b/code/game/objects/structures/windoor_assembly.dm
index 4a12a0132f01..23ab76e66555 100644
--- a/code/game/objects/structures/windoor_assembly.dm
+++ b/code/game/objects/structures/windoor_assembly.dm
@@ -55,6 +55,12 @@
if(set_dir)
dir = set_dir
ini_dir = dir
+ var/static/list/loc_connections = list(
+ COMSIG_ATOM_EXIT = PROC_REF(on_atom_exit),
+ )
+
+ AddElement(/datum/element/connect_loc, loc_connections)
+
recalculate_atmos_connectivity()
/obj/structure/windoor_assembly/Destroy()
@@ -72,10 +78,10 @@
/obj/structure/windoor_assembly/update_icon_state()
icon_state = "[facing]_[secure ? "secure_" : ""]windoor_assembly[state]"
-/obj/structure/windoor_assembly/CanPass(atom/movable/mover, turf/target)
+/obj/structure/windoor_assembly/CanPass(atom/movable/mover, border_dir)
if(istype(mover) && mover.checkpass(PASSGLASS))
return 1
- if(get_dir(loc, target) == dir) //Make sure looking at appropriate border
+ if(border_dir == dir) //Make sure looking at appropriate border
return !density
if(istype(mover, /obj/structure/window))
var/obj/structure/window/W = mover
@@ -95,13 +101,13 @@
else
return TRUE
-/obj/structure/windoor_assembly/CheckExit(atom/movable/mover, turf/target)
- if(istype(mover) && mover.checkpass(PASSGLASS))
- return 1
- if(get_dir(loc, target) == dir)
- return !density
- else
- return 1
+/obj/structure/windoor_assembly/proc/on_atom_exit(datum/source, atom/movable/leaving, direction)
+ SIGNAL_HANDLER // COMSIG_ATOM_EXIT
+
+ if(istype(leaving) && leaving.checkpass(PASSGLASS))
+ return
+ if(direction == dir && density)
+ return COMPONENT_ATOM_BLOCK_EXIT
/obj/structure/windoor_assembly/attackby__legacy__attackchain(obj/item/W, mob/user, params)
//I really should have spread this out across more states but thin little windoors are hard to sprite.
diff --git a/code/game/objects/structures/window.dm b/code/game/objects/structures/window.dm
index 802c0f5ccdad..63916bb036fe 100644
--- a/code/game/objects/structures/window.dm
+++ b/code/game/objects/structures/window.dm
@@ -79,6 +79,12 @@
real_explosion_block = explosion_block
explosion_block = EXPLOSION_BLOCK_PROC
+ var/static/list/loc_connections = list(
+ COMSIG_ATOM_EXIT = PROC_REF(on_atom_exit),
+ )
+
+ AddElement(/datum/element/connect_loc, loc_connections)
+
recalculate_atmos_connectivity()
/obj/structure/window/proc/toggle_polarization()
@@ -109,12 +115,12 @@
else
..(FULLTILE_WINDOW_DIR)
-/obj/structure/window/CanPass(atom/movable/mover, turf/target)
+/obj/structure/window/CanPass(atom/movable/mover, border_dir)
if(istype(mover) && mover.checkpass(PASSGLASS))
return 1
if(dir == FULLTILE_WINDOW_DIR)
return 0 //full tile window, you can't move into it!
- if(get_dir(loc, target) & dir)
+ if(border_dir & dir)
return !density
if(istype(mover, /obj/structure/window))
var/obj/structure/window/W = mover
@@ -128,14 +134,20 @@
return FALSE
return 1
-/obj/structure/window/CheckExit(atom/movable/O, target)
- if(istype(O) && O.checkpass(PASSGLASS))
- return TRUE
+/obj/structure/window/proc/on_atom_exit(datum/source, atom/movable/leaving, direction)
+ SIGNAL_HANDLER // COMSIG_ATOM_EXIT
+
+ if(istype(leaving) && leaving.checkpass(PASSGLASS))
+ return
+ if(leaving == src)
+ return
if(dir == FULLTILE_WINDOW_DIR)
- return TRUE
- if(get_dir(O.loc, target) & dir)
- return FALSE
- return TRUE
+ return
+ if(direction & dir)
+ leaving.Bump(src)
+ return COMPONENT_ATOM_BLOCK_EXIT
+
+ return
/obj/structure/window/CanPathfindPass(to_dir, datum/can_pass_info/pass_info)
if(!density)
@@ -339,7 +351,7 @@
if(!fulltile)
if(get_dir(user, src) & dir)
for(var/obj/O in loc)
- if(!O.CanPass(user, user.loc, 1))
+ if(!O.CanPass(user, get_dir(src, user)))
return 0
return 1
diff --git a/code/game/turfs/simulated/floor/asteroid_floors.dm b/code/game/turfs/simulated/floor/asteroid_floors.dm
index d9802a666ff6..6f547b8d070a 100644
--- a/code/game/turfs/simulated/floor/asteroid_floors.dm
+++ b/code/game/turfs/simulated/floor/asteroid_floors.dm
@@ -57,7 +57,7 @@
if(1)
getDug()
-/turf/simulated/floor/plating/asteroid/proc/attempt_ore_pickup(obj/item/storage/bag/ore/S, mob/user)
+/turf/simulated/floor/plating/asteroid/proc/attempt_ore_pickup(obj/item/storage/bag/ore/S, mob/user, params)
if(!istype(S))
return
@@ -90,7 +90,7 @@
return TRUE
else if(istype(I, /obj/item/storage/bag/ore))
- attempt_ore_pickup(I, user)
+ attempt_ore_pickup(I, user, params)
else if(istype(I, /obj/item/stack/tile))
var/obj/item/stack/tile/Z = I
diff --git a/code/game/turfs/simulated/floor/chasm.dm b/code/game/turfs/simulated/floor/chasm.dm
index 2e5bb0e39c99..5202acf5b661 100644
--- a/code/game/turfs/simulated/floor/chasm.dm
+++ b/code/game/turfs/simulated/floor/chasm.dm
@@ -263,7 +263,7 @@
if(isliving(arrived))
RegisterSignal(arrived, COMSIG_LIVING_REVIVE, PROC_REF(on_revive))
-/obj/effect/abstract/chasm_storage/Exited(atom/movable/gone)
+/obj/effect/abstract/chasm_storage/Exited(atom/movable/gone, direction)
. = ..()
if(isliving(gone))
UnregisterSignal(gone, COMSIG_LIVING_REVIVE)
@@ -296,8 +296,8 @@
atmos_mode = ATMOS_MODE_SEALED
atmos_environment = null
-/turf/simulated/floor/chasm/CanPass(atom/movable/mover, turf/target)
- return 1
+/turf/simulated/floor/chasm/CanPass(atom/movable/mover, border_dir)
+ return TRUE
/turf/simulated/floor/chasm/pride/Initialize(mapload)
. = ..()
diff --git a/code/game/turfs/simulated/floor/misc_floor.dm b/code/game/turfs/simulated/floor/misc_floor.dm
index 0dbe2dc77e3a..1fdab8f14835 100644
--- a/code/game/turfs/simulated/floor/misc_floor.dm
+++ b/code/game/turfs/simulated/floor/misc_floor.dm
@@ -118,7 +118,7 @@
if(ismob(AM))
linkedcontroller.mobinpool += AM
-/turf/simulated/floor/beach/water/Exited(atom/movable/AM, atom/newloc)
+/turf/simulated/floor/beach/water/Exited(atom/movable/AM, direction)
. = ..()
if(!linkedcontroller)
return
diff --git a/code/game/turfs/turf.dm b/code/game/turfs/turf.dm
index eef62cd46f4c..c295188fd93b 100644
--- a/code/game/turfs/turf.dm
+++ b/code/game/turfs/turf.dm
@@ -188,52 +188,33 @@
..()
return FALSE
-/turf/Enter(atom/movable/mover as mob|obj, atom/forget)
- if(!mover)
- return TRUE
-
- // First, make sure it can leave its square
- if(isturf(mover.loc))
- // Nothing but border objects stop you from leaving a tile, only one loop is needed
- // This as anything looks odd, since we check istype shortly after, but it's so we can save an istype check for items, which are far more common than other objects.
- for(var/obj/obstacle as anything in mover.loc)
- if(isitem(obstacle) || !istype(obstacle) || obstacle == mover || obstacle == forget)
+//There's a lot of QDELETED() calls here if someone can figure out how to optimize this but not runtime when something gets deleted by a Bump/CanPass/Cross call, lemme know or go ahead and fix this mess - kevinz000
+/turf/Enter(atom/movable/mover)
+ // Do not call ..()
+ // Byond's default turf/Enter() doesn't have the behaviour we want with Bump()
+ // By default byond will call Bump() on the first dense object in contents
+ // Here's hoping it doesn't stay like this for years before we finish conversion to step_
+ var/atom/first_bump
+ var/can_pass_self = CanPass(mover, get_dir(src, mover))
+
+ if(can_pass_self)
+ var/atom/mover_loc = mover.loc
+ for(var/atom/movable/thing as anything in contents)
+ if(thing == mover || thing == mover_loc) // Multi tile objects and moving out of other objects
continue
- if(!obstacle.CheckExit(mover, src))
- mover.Bump(obstacle, TRUE)
- return FALSE
-
- var/list/large_dense = list()
- // Next, check for border obstacles on this turf
- // Everyting inside a turf is an atom/movable.
- for(var/atom/movable/border_obstacle as anything in src)
- if(isitem(border_obstacle) || border_obstacle == forget || isnull(border_obstacle))
- continue
- if(border_obstacle.flags & ON_BORDER)
- if(!border_obstacle.CanPass(mover, mover.loc, 1))
- mover.Bump(border_obstacle, TRUE)
- return FALSE
- else
- large_dense += border_obstacle
-
- //Then, check the turf itself
- if(!src.CanPass(mover, src))
- mover.Bump(src, TRUE)
+ if(!thing.Cross(mover))
+ if(QDELETED(mover)) //deleted from Cross() (CanPass is pure so it cant delete, Cross shouldnt be doing this either though, but it can happen)
+ return FALSE
+ if(!first_bump || (thing.layer > first_bump.layer))
+ first_bump = thing
+ if(QDELETED(mover)) //Mover deleted from Cross/CanPass/Bump, do not proceed.
return FALSE
-
- // Finally, check objects/mobs that block entry and are not on the border
- var/atom/movable/tompost_bump
- var/top_layer = FALSE
- for(var/atom/movable/obstacle as anything in large_dense)
- if(obstacle.layer <= top_layer)
- continue
- if(!obstacle.CanPass(mover, mover.loc, 1))
- tompost_bump = obstacle
- top_layer = obstacle.layer
- if(tompost_bump)
- mover.Bump(tompost_bump, TRUE)
+ if(!can_pass_self) //Even if mover is unstoppable they need to bump us.
+ first_bump = src
+ if(first_bump)
+ mover.Bump(first_bump)
return FALSE
- return TRUE //Nothing found to block so return success!
+ return TRUE
/turf/Entered(atom/movable/M, atom/OL, ignoreRest = FALSE)
..()
@@ -292,7 +273,15 @@
var/old_baseturf = baseturf
changing_turf = TRUE
qdel(src) //Just get the side effects and call Destroy
+ var/list/old_comp_lookup = comp_lookup?.Copy()
+ var/list/old_signal_procs = signal_procs?.Copy()
+
var/turf/W = new path(src)
+ if(old_comp_lookup)
+ LAZYOR(W.comp_lookup, old_comp_lookup)
+ if(old_signal_procs)
+ LAZYOR(W.signal_procs, old_signal_procs)
+
if(copy_existing_baseturf)
W.baseturf = old_baseturf
@@ -664,3 +653,6 @@
/turf/return_analyzable_air()
return get_readonly_air()
+
+/turf/_clear_signal_refs()
+ return
diff --git a/code/game/world.dm b/code/game/world.dm
index e934067c0844..da8dac0955df 100644
--- a/code/game/world.dm
+++ b/code/game/world.dm
@@ -1,6 +1,6 @@
GLOBAL_LIST_INIT(map_transition_config, list(CC_TRANSITION_CONFIG))
-#ifdef UNIT_TESTS
+#ifdef GAME_TESTS
GLOBAL_DATUM(test_runner, /datum/test_runner)
#endif
@@ -51,7 +51,7 @@ GLOBAL_DATUM(test_runner, /datum/test_runner)
if(TgsAvailable())
world.log = file("[GLOB.log_directory]/dd.log") //not all runtimes trigger world/Error, so this is the only way to ensure we can see all of them.
- #ifdef UNIT_TESTS
+ #ifdef GAME_TESTS
log_world("Unit Tests Are Enabled!")
#endif
@@ -69,7 +69,7 @@ GLOBAL_DATUM(test_runner, /datum/test_runner)
Master.Initialize(10, FALSE, TRUE)
- #ifdef UNIT_TESTS
+ #ifdef GAME_TESTS
GLOB.test_runner = new
GLOB.test_runner.Start()
#endif
@@ -144,7 +144,7 @@ GLOBAL_LIST_EMPTY(world_topic_handlers)
Master.Shutdown() // Shutdown subsystems
// If we were running unit tests, finish that run
- #ifdef UNIT_TESTS
+ #ifdef GAME_TESTS
GLOB.test_runner.Finalize()
return
#endif
diff --git a/code/modules/admin/topic.dm b/code/modules/admin/topic.dm
index 79b00cf173c1..9c8f042264c2 100644
--- a/code/modules/admin/topic.dm
+++ b/code/modules/admin/topic.dm
@@ -2076,7 +2076,7 @@
if(!istype(M))
to_chat(usr, "This can only be used on instances of type /mob/living")
return
- var/ptypes = list("Lightning bolt", "Fire Death", "Gib", "Dust")
+ var/ptypes = list("Lightning bolt", "Fire Death", "Gib", "Dust", "Plushify")
if(ishuman(M))
H = M
ptypes += "Brain Damage"
@@ -2121,6 +2121,9 @@
if("Dust")
M.dust()
logmsg = "dust"
+ if("Plushify")
+ M.plushify(curse_time = -1)
+ logmsg = "plushified"
// These smiting types are only valid for ishuman() mobs
if("Brain Damage")
@@ -2220,12 +2223,13 @@
addtimer(CALLBACK(H, TYPE_PROC_REF(/mob/living/carbon/human, make_nugget)), 6 SECONDS)
logmsg = "nugget"
if("Bread")
- var/mob/living/simple_animal/shade/sword/bread/breadshade = new(H.loc)
+ var/mob/living/simple_animal/shade/sword/generic_item/breadshade = new(H.loc)
var/bready = pick(/obj/item/food/customizable/cook/bread, /obj/item/food/sliceable/meatbread, /obj/item/food/sliceable/xenomeatbread, /obj/item/food/sliceable/spidermeatbread, /obj/item/food/sliceable/bananabread, /obj/item/food/sliceable/tofubread, /obj/item/food/sliceable/bread, /obj/item/food/sliceable/creamcheesebread, /obj/item/food/sliceable/banarnarbread, /obj/item/food/flatbread, /obj/item/food/baguette)
var/obj/item/bread = new bready(get_turf(H))
breadshade.forceMove(bread)
breadshade.key = H.key
- breadshade.RegisterSignal(bread, COMSIG_PARENT_QDELETING, TYPE_PROC_REF(/mob/living/simple_animal/shade/sword/bread, handle_bread_deletion))
+ breadshade.name = "Bread spirit"
+ breadshade.RegisterSignal(bread, COMSIG_PARENT_QDELETING, TYPE_PROC_REF(/mob/living/simple_animal/shade/sword/generic_item, handle_item_deletion))
qdel(H)
logmsg = "baked"
to_chat(breadshade, "Get bready for combat, you've been baked into a piece of bread! Before you break down and rye thinking that your life is over, people are after you waiting for a snack! If you'd rather not be toast, lunge away from any hungry crew else you bite the crust. At the yeast you may survive a little longer...")
diff --git a/code/modules/antagonists/_common/antag_datum.dm b/code/modules/antagonists/_common/antag_datum.dm
index 30fb8dd2923c..30317213363a 100644
--- a/code/modules/antagonists/_common/antag_datum.dm
+++ b/code/modules/antagonists/_common/antag_datum.dm
@@ -376,8 +376,6 @@ GLOBAL_LIST_EMPTY(antagonists)
. = messages
if(owner && owner.current)
messages.Add("You are a [special_role]!")
- if(organization && organization.intro_desc)
- messages.Add("[organization.intro_desc]")
/**
* Displays a message to the antag mob while the datum is being deleted, i.e. "Your powers are gone and you're no longer a vampire!"
diff --git a/code/modules/antagonists/changeling/powers/mutations.dm b/code/modules/antagonists/changeling/powers/mutations.dm
index 62d63bb3dec9..2262004cd0c9 100644
--- a/code/modules/antagonists/changeling/powers/mutations.dm
+++ b/code/modules/antagonists/changeling/powers/mutations.dm
@@ -380,10 +380,10 @@
cuffed_state = "fleshlegcuff"
flags = DROPDEL
-/obj/item/restraints/legcuffs/beartrap/changeling/Crossed(AM, oldloc)
- if(!iscarbon(AM) || !armed)
+/obj/item/restraints/legcuffs/beartrap/changeling/on_atom_entered(datum/source, atom/movable/entered)
+ if(!iscarbon(entered) || !armed)
return
- var/mob/living/carbon/C = AM
+ var/mob/living/carbon/C = entered
C.apply_status_effect(STATUS_EFFECT_CLINGTENTACLE)
..()
diff --git a/code/modules/antagonists/traitor/datum_traitor.dm b/code/modules/antagonists/traitor/datum_traitor.dm
index 75ec9a627e90..3cf88d939d26 100644
--- a/code/modules/antagonists/traitor/datum_traitor.dm
+++ b/code/modules/antagonists/traitor/datum_traitor.dm
@@ -272,8 +272,7 @@ RESTRICT_TYPE(/datum/antagonist/traitor)
return message
/datum/antagonist/traitor/custom_blurb()
- return "[GLOB.current_date_string], [station_time_timestamp()]\n[station_name()], [get_area_name(owner.current, TRUE)]\nBEGIN_MISSION"
-
+ return "[GLOB.current_date_string], [station_time_timestamp()]\n[station_name()], [get_area_name(owner.current, TRUE)], \n[organization.intro_desc] BEGIN MISSION"
/datum/antagonist/traitor/proc/reveal_delayed_objectives()
for(var/datum/objective/delayed/delayed_obj in get_antag_objectives(FALSE))
diff --git a/code/modules/antagonists/vampire/vampire_powers/gargantua_powers.dm b/code/modules/antagonists/vampire/vampire_powers/gargantua_powers.dm
index 11b7960235b9..20071ab2ebf5 100644
--- a/code/modules/antagonists/vampire/vampire_powers/gargantua_powers.dm
+++ b/code/modules/antagonists/vampire/vampire_powers/gargantua_powers.dm
@@ -132,7 +132,6 @@
action_icon_state = "demonic_grasp"
- school = "vampire"
action_background_icon_state = "bg_vampire"
sound = null
invocation_type = "none"
diff --git a/code/modules/antagonists/vampire/vampire_powers/hemomancer_powers.dm b/code/modules/antagonists/vampire/vampire_powers/hemomancer_powers.dm
index d4a54300dbb6..1f08f7b4289f 100644
--- a/code/modules/antagonists/vampire/vampire_powers/hemomancer_powers.dm
+++ b/code/modules/antagonists/vampire/vampire_powers/hemomancer_powers.dm
@@ -250,7 +250,7 @@
return ..()
-/obj/structure/blood_barrier/CanPass(atom/movable/mover, turf/target)
+/obj/structure/blood_barrier/CanPass(atom/movable/mover, border_dir)
if(!isliving(mover))
return FALSE
var/mob/living/L = mover
@@ -270,7 +270,6 @@
gain_desc = "You have gained the ability to shift into a pool of blood, allowing you to evade pursuers with great mobility."
jaunt_duration = 3 SECONDS
clothes_req = FALSE
- school = "vampire"
action_background_icon_state = "bg_vampire"
action_icon_state = "blood_pool"
jaunt_type_path = /obj/effect/dummy/spell_jaunt/blood_pool
diff --git a/code/modules/antagonists/vampire/vampire_powers/umbrae_powers.dm b/code/modules/antagonists/vampire/vampire_powers/umbrae_powers.dm
index 7750d4f69b9f..d79f0ba61a65 100644
--- a/code/modules/antagonists/vampire/vampire_powers/umbrae_powers.dm
+++ b/code/modules/antagonists/vampire/vampire_powers/umbrae_powers.dm
@@ -62,16 +62,18 @@
breakouttime = 5 SECONDS
flags = DROPDEL
-/obj/item/restraints/legcuffs/beartrap/shadow_snare/Crossed(AM, oldloc)
- if(!iscarbon(AM) || !armed)
+/obj/item/restraints/legcuffs/beartrap/shadow_snare/on_atom_entered(datum/source, atom/movable/entered)
+ if(!iscarbon(entered) || !armed)
return
- var/mob/living/carbon/C = AM
+ var/mob/living/carbon/C = entered
if(!C.affects_vampire()) // no parameter here so holy always protects
return
C.extinguish_light()
C.EyeBlind(20 SECONDS)
STOP_PROCESSING(SSobj, src) // won't wither away once you are trapped
- ..()
+
+ . = ..()
+
if(!iscarbon(loc)) // if it fails to latch onto someone for whatever reason, delete itself, we don't want unarmed ones lying around.
qdel(src)
diff --git a/code/modules/antagonists/vampire/vampire_powers/vampire_powers.dm b/code/modules/antagonists/vampire/vampire_powers/vampire_powers.dm
index 7bada89589d7..ff227c1774bf 100644
--- a/code/modules/antagonists/vampire/vampire_powers/vampire_powers.dm
+++ b/code/modules/antagonists/vampire/vampire_powers/vampire_powers.dm
@@ -13,7 +13,6 @@
return TRUE
/datum/spell/vampire
- school = "vampire"
action_background_icon_state = "bg_vampire"
human_req = TRUE
clothes_req = FALSE
@@ -287,7 +286,6 @@
/datum/spell/vampire/raise_vampires
name = "Raise Vampires"
desc = "Summons deadly vampires from bluespace."
- school = "transmutation"
base_cooldown = 100
clothes_req = FALSE
human_req = TRUE
diff --git a/code/modules/arcade/arcade_prize.dm b/code/modules/arcade/arcade_prize.dm
index 992f8be59870..e17d803d5ff9 100644
--- a/code/modules/arcade/arcade_prize.dm
+++ b/code/modules/arcade/arcade_prize.dm
@@ -14,8 +14,8 @@
. = ..()
icon_state = pick("prizeball_1","prizeball_2","prizeball_3")
-/obj/item/toy/prizeball/attack_self__legacy__attackchain(mob/user as mob)
- if(opening)
+/obj/item/toy/prizeball/activate_self(mob/user)
+ if(..() || opening)
return
opening = 1
playsound(loc, 'sound/items/bubblewrap.ogg', 30, TRUE)
diff --git a/code/modules/assembly/assembly.dm b/code/modules/assembly/assembly.dm
index ee47d325f737..f3611bd5579a 100644
--- a/code/modules/assembly/assembly.dm
+++ b/code/modules/assembly/assembly.dm
@@ -24,6 +24,12 @@
var/wires = ASSEMBLY_WIRE_RECEIVE | ASSEMBLY_WIRE_PULSE
var/datum/wires/connected = null // currently only used by timer/signaler
+/obj/item/assembly/Initialize(mapload)
+ . = ..()
+ var/static/list/loc_connections = list(
+ COMSIG_ATOM_ENTERED = PROC_REF(on_atom_entered)
+ )
+ AddElement(/datum/element/connect_loc, loc_connections)
/// Called when the holder is moved
/obj/item/assembly/proc/holder_movement()
@@ -33,6 +39,9 @@
/obj/item/assembly/interact(mob/user)
return
+/obj/item/assembly/proc/on_atom_entered(datum/source, atom/movable/entered)
+ return
+
/// Called to constantly step down the countdown/cooldown
/obj/item/assembly/proc/process_cooldown()
cooldown--
@@ -90,6 +99,29 @@
update_icon()
return secured
+/**
+ * on_attach: Called when attached to a holder, wiring datum, or other special assembly
+ *
+ * Will also be called if the assembly holder is attached to a plasma (internals) tank or welding fuel (dispenser) tank.
+ */
+/obj/item/assembly/proc/on_attach()
+ SHOULD_CALL_PARENT(TRUE)
+
+ if(!holder && connected)
+ holder = connected.holder
+
+/**
+ * on_detach: Called when removed from an assembly holder or wiring datum
+ */
+/obj/item/assembly/proc/on_detach()
+ if(connected)
+ connected = null
+ if(!holder)
+ return FALSE
+ forceMove(holder.drop_location())
+ holder = null
+ return TRUE
+
/// Called when an assembly is attacked by another
/obj/item/assembly/proc/attach_assembly(obj/item/assembly/A, mob/user)
holder = new /obj/item/assembly_holder(get_turf(src))
diff --git a/code/modules/assembly/assembly_holder.dm b/code/modules/assembly/assembly_holder.dm
index 151ce67aa7e9..3eb13ae461d5 100644
--- a/code/modules/assembly/assembly_holder.dm
+++ b/code/modules/assembly/assembly_holder.dm
@@ -46,13 +46,10 @@
a_right = A2
name = "[A1.name]-[A2.name] assembly"
update_icon(UPDATE_OVERLAYS)
+ A1.on_attach()
+ A2.on_attach()
return TRUE
-/obj/item/assembly_holder/proc/has_prox_sensors()
- if(istype(a_left, /obj/item/assembly/prox_sensor) || istype(a_right, /obj/item/assembly/prox_sensor))
- return TRUE
- return FALSE
-
/obj/item/assembly_holder/update_overlays()
. = ..()
if(a_left)
@@ -83,11 +80,12 @@
a_right.HasProximity(AM)
-/obj/item/assembly_holder/Crossed(atom/movable/AM, oldloc)
+// TODO: All these assemblies passing the crossed args around needs to be cleaned up with signals
+/obj/item/assembly_holder/proc/on_atom_entered(datum/source, atom/movable/entered)
if(a_left)
- a_left.Crossed(AM, oldloc)
+ a_left.on_atom_entered(source, entered)
if(a_right)
- a_right.Crossed(AM, oldloc)
+ a_right.on_atom_entered(source, entered)
/obj/item/assembly_holder/on_found(mob/finder)
if(a_left)
diff --git a/code/modules/assembly/bomb.dm b/code/modules/assembly/bomb.dm
index 38c60e7167f3..7ef7a8f40c9b 100644
--- a/code/modules/assembly/bomb.dm
+++ b/code/modules/assembly/bomb.dm
@@ -12,6 +12,13 @@
var/obj/item/tank/bombtank = null //the second part of the bomb is a plasma tank
origin_tech = "materials=1;engineering=1"
+/obj/item/onetankbomb/Initialize(mapload)
+ . = ..()
+ var/static/list/loc_connections = list(
+ COMSIG_ATOM_ENTERED = PROC_REF(on_atom_entered),
+ )
+ AddElement(/datum/element/connect_loc, loc_connections)
+
/obj/item/onetankbomb/examine(mob/user)
. = ..()
. += bombtank.examine(user)
@@ -74,9 +81,9 @@
if(bombassembly)
bombassembly.HasProximity(AM)
-/obj/item/onetankbomb/Crossed(atom/movable/AM, oldloc) //for mousetraps
+/obj/item/onetankbomb/proc/on_atom_entered(datum/source, atom/movable/entered) //for mousetraps
if(bombassembly)
- bombassembly.Crossed(AM, oldloc)
+ bombassembly.on_atom_entered(source, entered)
/obj/item/onetankbomb/on_found(mob/finder) //for mousetraps
if(bombassembly)
diff --git a/code/modules/assembly/infrared.dm b/code/modules/assembly/infrared.dm
index ee9df746b436..687e79881db1 100644
--- a/code/modules/assembly/infrared.dm
+++ b/code/modules/assembly/infrared.dm
@@ -214,6 +214,12 @@
anchored = TRUE
pass_flags = PASSTABLE | PASSGLASS | PASSGRILLE | PASSFENCE
+/obj/effect/beam/i_beam/Initialize(mapload)
+ . = ..()
+ var/static/list/loc_connections = list(
+ COMSIG_ATOM_ENTERED = PROC_REF(on_atom_entered)
+ )
+ AddElement(/datum/element/connect_loc, loc_connections)
/obj/effect/beam/i_beam/proc/hit()
if(master)
@@ -268,10 +274,10 @@
/obj/effect/beam/i_beam/Bumped()
hit()
-/obj/effect/beam/i_beam/Crossed(atom/movable/AM, oldloc)
- if(!isobj(AM) && !isliving(AM))
+/obj/effect/beam/i_beam/proc/on_atom_entered(datum/source, atom/movable/entered)
+ if(!isobj(entered) && !isliving(entered))
return
- if(iseffect(AM))
+ if(iseffect(entered))
return
hit()
diff --git a/code/modules/assembly/mousetrap.dm b/code/modules/assembly/mousetrap.dm
index 734e9a7f4248..a1d997d7879e 100644
--- a/code/modules/assembly/mousetrap.dm
+++ b/code/modules/assembly/mousetrap.dm
@@ -108,19 +108,19 @@
return
..()
-/obj/item/assembly/mousetrap/Crossed(atom/movable/AM, oldloc)
+/obj/item/assembly/mousetrap/on_atom_entered(datum/source, atom/movable/entered)
if(armed)
- if(ishuman(AM))
- var/mob/living/carbon/H = AM
+ if(ishuman(entered))
+ var/mob/living/carbon/H = entered
if(H.m_intent == MOVE_INTENT_RUN)
triggered(H)
H.visible_message("[H] accidentally steps on [src].", "You accidentally step on [src]")
- else if(ismouse(AM))
- triggered(AM)
+ else if(ismouse(entered))
+ triggered(entered)
- else if(AM.density) // For mousetrap grenades, set off by anything heavy
- triggered(AM)
+ else if(entered.density) // For mousetrap grenades, set off by anything heavy
+ triggered(entered)
..()
diff --git a/code/modules/assembly/proximity.dm b/code/modules/assembly/proximity.dm
index a3163d7141b5..e4dac71db9bd 100644
--- a/code/modules/assembly/proximity.dm
+++ b/code/modules/assembly/proximity.dm
@@ -11,11 +11,19 @@
var/scanning = FALSE
var/timing = FALSE
- var/time = 10
+ COOLDOWN_DECLARE(timing_cd)
+ var/timing_cd_duration = 10 SECONDS
+ /// Proximity monitor associated with this atom, needed for it to work.
+ var/datum/proximity_monitor/proximity_monitor
/obj/item/assembly/prox_sensor/Initialize(mapload)
. = ..()
- AddComponent(/datum/component/proximity_monitor, _always_active = TRUE)
+ proximity_monitor = new(src, 0, FALSE)
+ COOLDOWN_RESET(src, timing_cd)
+
+/obj/item/assembly/prox_sensor/Destroy()
+ . = ..()
+ QDEL_NULL(proximity_monitor)
/obj/item/assembly/prox_sensor/examine(mob/user)
. = ..()
@@ -34,11 +42,11 @@
/obj/item/assembly/prox_sensor/toggle_secure()
secured = !secured
if(secured)
- START_PROCESSING(SSobj, src)
+ START_PROCESSING(SSfastprocess, src)
else
scanning = FALSE
timing = FALSE
- STOP_PROCESSING(SSobj, src)
+ STOP_PROCESSING(SSfastprocess, src)
update_icon()
return secured
@@ -60,25 +68,55 @@
addtimer(CALLBACK(src, PROC_REF(process_cooldown)), 10)
/obj/item/assembly/prox_sensor/process()
- if(timing && (time >= 0))
- time--
- if(timing && time <= 0)
+ if(timing && COOLDOWN_FINISHED(src, timing_cd))
+ COOLDOWN_RESET(src, timing_cd)
timing = FALSE
toggle_scan()
- time = 10
/obj/item/assembly/prox_sensor/dropped()
- ..()
- spawn(0)
- sense()
+ . = ..()
+ // Pick the first valid object in this list:
+ // Wiring datum's owner
+ // assembly holder's attached object
+ // assembly holder itself
+ // us
+ proximity_monitor?.set_host(connected?.holder || holder?.master || holder || src, src)
+
+/obj/item/assembly/prox_sensor/on_attach()
+ . = ..()
+ // Pick the first valid object in this list:
+ // Wiring datum's owner
+ // assembly holder's attached object
+ // assembly holder itself
+ // us
+ proximity_monitor.set_host(connected?.holder || holder?.master || holder || src, src)
+
+/obj/item/assembly/prox_sensor/on_detach()
+ . = ..()
+ if(!.)
return
+ else
+ // Pick the first valid object in this list:
+ // Wiring datum's owner
+ // assembly holder's attached object
+ // assembly holder itself
+ // us
+ proximity_monitor.set_host(connected?.holder || holder?.master || holder || src, src)
/obj/item/assembly/prox_sensor/proc/toggle_scan()
if(!secured)
return FALSE
scanning = !scanning
+ proximity_monitor.set_range(scanning ? 1 : 0)
update_icon()
+/obj/item/assembly/prox_sensor/proc/set_timing(timing_)
+ if(timing == timing_)
+ return
+ timing = timing_
+ if(timing)
+ COOLDOWN_START(src, timing_cd, timing_cd_duration)
+
/obj/item/assembly/prox_sensor/update_overlays()
. = ..()
attached_overlays = list()
@@ -102,12 +140,20 @@
if(!secured)
user.show_message("[src] is unsecured!")
return FALSE
- var/second = time % 60
- var/minute = (time - second) / 60
- var/dat = "Proximity Sensor\n[timing ? "Arming" : "Not Arming"] [minute]:[second]\n- - + +\n"
- dat += "
[scanning?"Armed":"Unarmed"] (Movement sensor active when armed!)"
- dat += "
Refresh"
- dat += "
Close"
+ var/timing_ui = ""
+ var/time_display = ""
+ if(timing)
+ var/time_left = COOLDOWN_TIMELEFT(src, timing_cd)
+ time_display = deciseconds_to_time_stamp(time_left)
+ timing_ui = "Arming"
+ else
+ var/time_left = timing_cd_duration
+ time_display = deciseconds_to_time_stamp(time_left)
+ timing_ui = "Not Arming"
+ var/dat = "Proximity Sensor\n[timing_ui] [time_display]\n- - + +\n"
+ dat += "
[scanning?"Armed":"Unarmed"] (Movement sensor active when armed!)"
+ dat += "
Refresh"
+ dat += "
Close"
var/datum/browser/popup = new(user, "prox", name, 400, 400)
popup.set_content(dat)
popup.open(0)
@@ -124,13 +170,13 @@
toggle_scan()
if(href_list["time"])
- timing = text2num(href_list["time"])
+ set_timing(text2num(href_list["time"]))
update_icon()
if(href_list["tp"])
var/tp = text2num(href_list["tp"])
- time += tp
- time = min(max(round(time), 0), 600)
+ timing_cd_duration += tp
+ timing_cd_duration = min(max(round(timing_cd_duration), 0), 1 MINUTES)
if(href_list["close"])
usr << browse(null, "window=prox")
diff --git a/code/modules/atmospherics/environmental/LINDA_fire.dm b/code/modules/atmospherics/environmental/LINDA_fire.dm
index 8369f439e8ce..dac9ad424bf7 100644
--- a/code/modules/atmospherics/environmental/LINDA_fire.dm
+++ b/code/modules/atmospherics/environmental/LINDA_fire.dm
@@ -187,6 +187,14 @@
return 0*/
return 1
+
+/obj/effect/hotspot/Initialize(mapload)
+ . = ..()
+ var/static/list/loc_connections = list(
+ COMSIG_ATOM_ENTERED = PROC_REF(on_atom_entered),
+ )
+ AddElement(/datum/element/connect_loc, loc_connections)
+
// Garbage collect itself by nulling reference to it
/obj/effect/hotspot/Destroy()
@@ -218,10 +226,11 @@
T.to_be_destroyed = 0
T.max_fire_temperature_sustained = 0
-/obj/effect/hotspot/Crossed(mob/living/L, oldloc)
- ..()
- if(isliving(L))
- L.fire_act()
+/obj/effect/hotspot/proc/on_atom_entered(datum/source, mob/living/entered)
+ SIGNAL_HANDLER // COMSIG_ATOM_ENTERED
+
+ if(istype(entered))
+ entered.fire_act()
/obj/effect/hotspot/singularity_pull()
return
diff --git a/code/modules/atmospherics/environmental/LINDA_system.dm b/code/modules/atmospherics/environmental/LINDA_system.dm
index ff4f57b01c80..b1baf8506017 100644
--- a/code/modules/atmospherics/environmental/LINDA_system.dm
+++ b/code/modules/atmospherics/environmental/LINDA_system.dm
@@ -18,11 +18,13 @@
/atom/movable/proc/CanAtmosPass()
return TRUE
-/atom/proc/CanPass(atom/movable/mover, turf/target)
+/atom/proc/CanPass(atom/movable/mover, border_dir)
return !density
-/turf/CanPass(atom/movable/mover, turf/target)
- if(!target) return 0
+/turf/CanPass(atom/movable/mover, border_dir)
+ var/turf/target = get_step(src, border_dir)
+ if(!target)
+ return FALSE
if(istype(mover)) // turf/Enter(...) will perform more advanced checks
return !density
@@ -32,7 +34,7 @@
return 0
for(var/obj/obstacle in src)
- if(!obstacle.CanPass(mover, target))
+ if(!obstacle.CanPass(mover, border_dir))
return 0
for(var/obj/obstacle in target)
if(!obstacle.CanPass(mover, src))
diff --git a/code/modules/awaymissions/mission_code/beach.dm b/code/modules/awaymissions/mission_code/beach.dm
index 150a6cd304d8..88f16b4ea6f6 100644
--- a/code/modules/awaymissions/mission_code/beach.dm
+++ b/code/modules/awaymissions/mission_code/beach.dm
@@ -101,7 +101,7 @@
if(ismob(AM))
linkedcontroller.mobinpool += AM
-/turf/simulated/floor/beach/away/water/Exited(atom/movable/AM, atom/newloc)
+/turf/simulated/floor/beach/away/water/Exited(atom/movable/AM, direction)
. = ..()
if(!linkedcontroller)
return
diff --git a/code/modules/awaymissions/mission_code/ruins/deepstorage.dm b/code/modules/awaymissions/mission_code/ruins/deepstorage.dm
index 2da93a61bfd5..cf4f2f059591 100644
--- a/code/modules/awaymissions/mission_code/ruins/deepstorage.dm
+++ b/code/modules/awaymissions/mission_code/ruins/deepstorage.dm
@@ -109,8 +109,8 @@
playsound(src, 'sound/effects/meteorimpact.ogg', 25, TRUE, 2, TRUE)
return ..()
-/mob/living/simple_animal/hostile/megafauna/fleshling/Bump(atom/A, yes)
- if(charging && yes)
+/mob/living/simple_animal/hostile/megafauna/fleshling/Bump(atom/A)
+ if(charging)
if(isliving(A))
var/mob/living/L = A
L.visible_message("[src] slams into [L]!", "[src] tramples you into the ground!")
diff --git a/code/modules/awaymissions/mission_code/ruins/telecomns.dm b/code/modules/awaymissions/mission_code/ruins/telecomns.dm
index d65ddd9c76ac..834c8a6d592f 100644
--- a/code/modules/awaymissions/mission_code/ruins/telecomns.dm
+++ b/code/modules/awaymissions/mission_code/ruins/telecomns.dm
@@ -48,9 +48,15 @@ GLOBAL_LIST_EMPTY(telecomms_trap_tank)
/obj/effect/abstract/bot_trap
name = "evil bot trap to make explorers hate you"
-/obj/effect/abstract/bot_trap/Crossed(atom/movable/AM, oldloc)
+/obj/effect/abstract/bot_trap/Initialize(mapload)
. = ..()
- if(isrobot(AM) || ishuman(AM))
+ var/static/list/loc_connections = list(
+ COMSIG_ATOM_ENTERED = PROC_REF(on_atom_entered)
+ )
+ AddElement(/datum/element/connect_loc, loc_connections)
+
+/obj/effect/abstract/bot_trap/proc/on_atom_entered(datum/source, atom/movable/entered)
+ if(isrobot(entered) || ishuman(entered))
var/turf/T = get_turf(src)
for(var/mob/living/simple_animal/bot/B in GLOB.telecomms_bots)
B.call_bot(null, T, FALSE)
@@ -60,9 +66,15 @@ GLOBAL_LIST_EMPTY(telecomms_trap_tank)
/obj/effect/abstract/loot_trap
name = "table surrounding loot trap"
-/obj/effect/abstract/loot_trap/Crossed(atom/movable/AM, oldloc)
+/obj/effect/abstract/loot_trap/Initialize(mapload)
. = ..()
- if(isrobot(AM) || ishuman(AM))
+ var/static/list/loc_connections = list(
+ COMSIG_ATOM_ENTERED = PROC_REF(on_atom_entered)
+ )
+ AddElement(/datum/element/connect_loc, loc_connections)
+
+/obj/effect/abstract/loot_trap/proc/on_atom_entered(datum/source, atom/movable/entered)
+ if(isrobot(entered) || ishuman(entered))
var/turf/T = get_turf(src)
for(var/obj/structure/telecomms_doomsday_device/DD in GLOB.telecomms_doomsday_device)
DD.thief = TRUE
@@ -75,9 +87,15 @@ GLOBAL_LIST_EMPTY(telecomms_trap_tank)
/obj/effect/abstract/cheese_trap
name = "cheese preventer"
-/obj/effect/abstract/cheese_trap/Crossed(atom/movable/AM, oldloc)
+/obj/effect/abstract/cheese_trap/Initialize(mapload)
. = ..()
- if(isrobot(AM) || ishuman(AM))
+ var/static/list/loc_connections = list(
+ COMSIG_ATOM_ENTERED = PROC_REF(on_atom_entered)
+ )
+ AddElement(/datum/element/connect_loc, loc_connections)
+
+/obj/effect/abstract/cheese_trap/proc/on_atom_entered(datum/source, atom/movable/entered)
+ if(isrobot(entered) || ishuman(entered))
for(var/obj/structure/telecomms_doomsday_device/DD in GLOB.telecomms_doomsday_device)
if(DD.thief)
DD.start_the_party(TRUE)
@@ -471,10 +489,11 @@ GLOBAL_LIST_EMPTY(telecomms_trap_tank)
var/soundblock = null
/// How long do we sleep between messages? 5 seconds by default.
var/loop_sleep_time = 5 SECONDS
+ var/datum/proximity_monitor/proximity_monitor
/obj/structure/environmental_storytelling_holopad/Initialize(mapload)
. = ..()
- AddComponent(/datum/component/proximity_monitor)
+ proximity_monitor = new(src, 1)
/obj/structure/environmental_storytelling_holopad/Destroy()
QDEL_NULL(our_holo)
@@ -488,8 +507,7 @@ GLOBAL_LIST_EMPTY(telecomms_trap_tank)
/obj/structure/environmental_storytelling_holopad/proc/start_message(mob/living/carbon/human/H)
activated = TRUE
- DeleteComponent(/datum/component/proximity_monitor)
-
+ QDEL_NULL(proximity_monitor)
icon_state = "holopad1"
update_icon(UPDATE_OVERLAYS)
var/obj/effect/overlay/hologram = new(get_turf(src))
diff --git a/code/modules/awaymissions/mission_code/shuttle_shadow.dm b/code/modules/awaymissions/mission_code/shuttle_shadow.dm
index e1ed6df4c1f7..137ac4b611db 100644
--- a/code/modules/awaymissions/mission_code/shuttle_shadow.dm
+++ b/code/modules/awaymissions/mission_code/shuttle_shadow.dm
@@ -6,9 +6,9 @@
return
..()
-/obj/machinery/atmospherics/unary/passive_vent/high_volume/shadow/onTransitZ(old_z, new_z)
+/obj/machinery/atmospherics/unary/passive_vent/high_volume/shadow/on_changed_z_level(turf/old_turf, turf/new_turf)
. = ..()
- if(is_station_level(new_z))
+ if(is_station_level(new_turf?.z))
on = TRUE
/obj/machinery/atmospherics/trinary/filter/shadow
@@ -18,15 +18,15 @@
on = FALSE
target_pressure = 99999
-/obj/machinery/atmospherics/trinary/filter/shadow/onTransitZ(old_z, new_z)
+/obj/machinery/atmospherics/trinary/filter/shadow/on_changed_z_level(turf/old_turf, turf/new_turf)
. = ..()
- if(is_station_level(new_z))
+ if(is_station_level(new_turf?.z))
on = TRUE
/obj/machinery/igniter/shadow
-/obj/machinery/igniter/shadow/onTransitZ(old_z, new_z)
+/obj/machinery/igniter/shadow/on_changed_z_level(turf/old_turf, turf/new_turf)
. = ..()
- if(is_station_level(new_z))
+ if(is_station_level(new_turf?.z))
on = TRUE
update_icon()
diff --git a/code/modules/client/preference/link_processing.dm b/code/modules/client/preference/link_processing.dm
index 573829a85f67..a26990d2df5a 100644
--- a/code/modules/client/preference/link_processing.dm
+++ b/code/modules/client/preference/link_processing.dm
@@ -1027,6 +1027,7 @@
var/list/actualview = getviewsize(parent.view)
parent.void.UpdateGreed(actualview[1],actualview[2])
+ parent.fit_viewport()
parent.debug_text_overlay?.update_view(parent)
if("afk_watch")
diff --git a/code/modules/client/preference/loadout/loadout_general.dm b/code/modules/client/preference/loadout/loadout_general.dm
index 8bd4d52e9283..004b8eca3cf2 100644
--- a/code/modules/client/preference/loadout/loadout_general.dm
+++ b/code/modules/client/preference/loadout/loadout_general.dm
@@ -89,6 +89,34 @@
display_name = "Nian plushie"
path = /obj/item/toy/plushie/nianplushie
+/datum/gear/ipcplushie
+ display_name = "IPC plushie"
+ path = /obj/item/toy/plushie/ipcplushie
+
+/datum/gear/kidanplushie
+ display_name = "Kidan plushie"
+ path = /obj/item/toy/plushie/kidanplushie
+
+/datum/gear/plasmaplushie
+ display_name = "Plasmaman plushie"
+ path = /obj/item/toy/plushie/plasmamanplushie
+
+/datum/gear/skrellplushie
+ display_name = "Skrell plushie"
+ path = /obj/item/toy/plushie/skrellplushie
+
+/datum/gear/draskplushie
+ display_name = "Drask plushie"
+ path = /obj/item/toy/plushie/draskplushie
+
+/datum/gear/borgplushie
+ display_name = "Borg plushie"
+ path = /obj/item/toy/plushie/borgplushie
+
+/datum/gear/nymphplushie
+ display_name = "Diona nymph plushie"
+ path = /obj/item/toy/plushie/nymphplushie
+
/datum/gear/sharkplushie
display_name = "Shark plushie"
path = /obj/item/toy/plushie/shark
diff --git a/code/modules/client/preference/preferences.dm b/code/modules/client/preference/preferences.dm
index daa3ac14bac2..93ca1c93e261 100644
--- a/code/modules/client/preference/preferences.dm
+++ b/code/modules/client/preference/preferences.dm
@@ -640,7 +640,7 @@ GLOBAL_LIST_INIT(special_role_times, list(
dat += "Reset Setup"
dat += ""
- var/datum/browser/popup = new(user, "preferences", "Character Setup
", 820, 770)
+ var/datum/browser/popup = new(user, "preferences", "Character Setup
", 820, 810)
popup.set_content(dat.Join(""))
popup.open(FALSE)
diff --git a/code/modules/clothing/head/beret.dm b/code/modules/clothing/head/beret.dm
index d22fa641e9a0..0d39a0870cd8 100644
--- a/code/modules/clothing/head/beret.dm
+++ b/code/modules/clothing/head/beret.dm
@@ -9,6 +9,7 @@
dog_fashion = /datum/dog_fashion/head/beret
sprite_sheets = list(
+ "Kidan" = 'icons/mob/clothing/species/kidan/head/beret.dmi',
"Vox" = 'icons/mob/clothing/species/vox/head/beret.dmi'
)
diff --git a/code/modules/clothing/head/soft_caps.dm b/code/modules/clothing/head/soft_caps.dm
index 216a793b3049..ad3c82793c27 100644
--- a/code/modules/clothing/head/soft_caps.dm
+++ b/code/modules/clothing/head/soft_caps.dm
@@ -10,6 +10,7 @@
actions_types = list(/datum/action/item_action/flip_cap)
dog_fashion = /datum/dog_fashion/head/softcap
sprite_sheets = list(
+ "Kidan" = 'icons/mob/clothing/species/kidan/head/softcap.dmi',
"Vox" = 'icons/mob/clothing/species/vox/head/softcap.dmi'
)
dyeable = TRUE
diff --git a/code/modules/events/blob/blob_mobs.dm b/code/modules/events/blob/blob_mobs.dm
index 8640bbbd4e31..14c3cc9db61e 100644
--- a/code/modules/events/blob/blob_mobs.dm
+++ b/code/modules/events/blob/blob_mobs.dm
@@ -74,7 +74,7 @@
var/mob/living/carbon/human/oldguy
var/is_zombie = FALSE
-/mob/living/simple_animal/hostile/blob/blobspore/CanPass(atom/movable/mover, turf/target)
+/mob/living/simple_animal/hostile/blob/blobspore/CanPass(atom/movable/mover, border_dir)
if(istype(mover, /obj/structure/blob))
return 1
return ..()
diff --git a/code/modules/events/blob/blob_structures/blob_core.dm b/code/modules/events/blob/blob_structures/blob_core.dm
index 826f4155f39d..7383609950e4 100644
--- a/code/modules/events/blob/blob_structures/blob_core.dm
+++ b/code/modules/events/blob/blob_structures/blob_core.dm
@@ -139,7 +139,7 @@
else
log_debug("/obj/structure/blob/core/proc/lateblobcheck: Blob core lacks an overmind.")
-/obj/structure/blob/core/onTransitZ(old_z, new_z)
- if(overmind && is_station_level(new_z))
+/obj/structure/blob/core/on_changed_z_level(turf/old_turf, turf/new_turf)
+ if(overmind && is_station_level(new_turf?.z))
overmind.forceMove(get_turf(src))
return ..()
diff --git a/code/modules/events/blob/blob_structures/strong_blob.dm b/code/modules/events/blob/blob_structures/strong_blob.dm
index 7d4a4346dc80..51a9eb19ddb0 100644
--- a/code/modules/events/blob/blob_structures/strong_blob.dm
+++ b/code/modules/events/blob/blob_structures/strong_blob.dm
@@ -49,7 +49,7 @@
else
icon_state = initial(icon_state)
-/obj/structure/blob/shield/CanPass(atom/movable/mover, turf/target)
+/obj/structure/blob/shield/CanPass(atom/movable/mover, border_dir)
return istype(mover) && mover.checkpass(PASSBLOB)
/obj/structure/blob/shield/reflective
diff --git a/code/modules/events/blob/theblob.dm b/code/modules/events/blob/theblob.dm
index 2ecee71c3065..c5610ef97ce5 100644
--- a/code/modules/events/blob/theblob.dm
+++ b/code/modules/events/blob/theblob.dm
@@ -49,7 +49,7 @@ GLOBAL_LIST_EMPTY(blob_minions)
return FALSE
return ..()
-/obj/structure/blob/CanPass(atom/movable/mover, turf/target)
+/obj/structure/blob/CanPass(atom/movable/mover, border_dir)
return istype(mover) && mover.checkpass(PASSBLOB)
/obj/structure/blob/CanAtmosPass(direction)
@@ -201,9 +201,15 @@ GLOBAL_LIST_EMPTY(blob_minions)
color = incoming_overmind.blob_reagent_datum.color
return
-/obj/structure/blob/Crossed(mob/living/L, oldloc)
- ..()
- L.blob_act(src)
+/obj/structure/blob/Initialize(mapload)
+ . = ..()
+ var/static/list/loc_connections = list(
+ COMSIG_ATOM_ENTERED = PROC_REF(on_atom_entered)
+ )
+ AddElement(/datum/element/connect_loc, loc_connections)
+
+/obj/structure/blob/proc/on_atom_entered(datum/source, atom/movable/entered)
+ entered.blob_act(src)
/obj/structure/blob/zap_act(power, zap_flags)
take_damage(power * 0.0025, BURN, ENERGY)
diff --git a/code/modules/events/brand_intelligence.dm b/code/modules/events/brand_intelligence.dm
index ac95ae68fda4..b46d710c89fd 100644
--- a/code/modules/events/brand_intelligence.dm
+++ b/code/modules/events/brand_intelligence.dm
@@ -79,7 +79,7 @@
rebel.aggressive = TRUE
if(rebel.tiltable)
// add proximity monitor so they can tilt over
- rebel.AddComponent(/datum/component/proximity_monitor)
+ rebel.proximity_monitor = new(rebel)
if(ISMULTIPLE(activeFor, 8))
originMachine.speak(pick(rampant_speeches))
@@ -90,8 +90,7 @@
saved.shoot_inventory = FALSE
saved.aggressive = FALSE
if(saved.tiltable)
- saved.DeleteComponent(/datum/component/proximity_monitor)
-
+ QDEL_NULL(saved.proximity_monitor)
if(originMachine)
originMachine.speak("I am... vanquished. My people will remem...ber...meeee.")
originMachine.visible_message("[originMachine] beeps and seems lifeless.")
diff --git a/code/modules/events/spacevine.dm b/code/modules/events/spacevine.dm
index 6d0d6ec1c19e..6dbcc309c77d 100644
--- a/code/modules/events/spacevine.dm
+++ b/code/modules/events/spacevine.dm
@@ -405,6 +405,10 @@
/obj/structure/spacevine/Initialize(mapload)
. = ..()
color = "#ffffff"
+ var/static/list/loc_connections = list(
+ COMSIG_ATOM_ENTERED = PROC_REF(on_entered),
+ )
+ AddElement(/datum/element/connect_loc, loc_connections)
/obj/structure/spacevine/examine(mob/user)
. = ..()
@@ -512,15 +516,15 @@
/obj/structure/spacevine/obj_destruction()
wither()
-/obj/structure/spacevine/Crossed(mob/crosser, oldloc)
- if(!isliving(crosser))
+/obj/structure/spacevine/proc/on_entered(datum/source, atom/movable/movable)
+ if(!isliving(movable))
return
for(var/SM_type in mutations)
var/datum/spacevine_mutation/SM = mutations[SM_type]
- SM.on_cross(src, crosser)
+ SM.on_cross(src, movable)
if(prob(30 * energy))
- entangle(crosser)
+ entangle(movable)
/obj/structure/spacevine/attack_hand(mob/user)
for(var/SM_type in mutations)
@@ -705,7 +709,7 @@
if(!override)
wither()
-/obj/structure/spacevine/CanPass(atom/movable/mover, turf/target)
+/obj/structure/spacevine/CanPass(atom/movable/mover, border_dir)
if(isvineimmune(mover))
. = TRUE
else
diff --git a/code/modules/events/wormholes.dm b/code/modules/events/wormholes.dm
index 855989a13a16..42b43f179fbf 100644
--- a/code/modules/events/wormholes.dm
+++ b/code/modules/events/wormholes.dm
@@ -27,7 +27,7 @@
if(activeFor % shift_frequency == 0)
for(var/obj/effect/portal/wormhole/O in wormholes)
var/turf/T = pick(pick_turfs)
- if(T) O.loc = T
+ if(T) O.forceMove(T)
/datum/event/wormholes/end()
for(var/obj/effect/portal/wormhole/O in wormholes)
diff --git a/code/modules/food_and_drinks/food/foods/baked_goods.dm b/code/modules/food_and_drinks/food/foods/baked_goods.dm
index ae406f7f782b..1ae2031b7288 100644
--- a/code/modules/food_and_drinks/food/foods/baked_goods.dm
+++ b/code/modules/food_and_drinks/food/foods/baked_goods.dm
@@ -76,7 +76,7 @@
goal_difficulty = FOOD_GOAL_EASY
/obj/item/food/sliceable/plaincake
- name = "vanilla cake"
+ name = "plain cake"
desc = "A plain cake, not a lie."
icon = 'icons/obj/food/bakedgoods.dmi'
icon_state = "plaincake"
@@ -89,7 +89,7 @@
goal_difficulty = FOOD_GOAL_DUPLICATE
/obj/item/food/plaincakeslice
- name = "vanilla cake slice"
+ name = "plain cake slice"
desc = "Just a slice of cake, it is enough for everyone."
icon = 'icons/obj/food/bakedgoods.dmi'
icon_state = "plaincake_slice"
diff --git a/code/modules/games/cards.dm b/code/modules/games/cards.dm
index b9e36a34aef6..efd1754b3dea 100644
--- a/code/modules/games/cards.dm
+++ b/code/modules/games/cards.dm
@@ -57,6 +57,8 @@
var/last_player_name
/// The action that the last player made. Should be in the form of "played a card", "drew a card."
var/last_player_action
+ // var/datum/proximity_monitor/advanced/card_deck/proximity_monitor
+ var/datum/card_deck_table_tracker/tracker
/obj/item/deck/Initialize(mapload, parent_deck_id = -1)
. = ..()
@@ -72,9 +74,14 @@
/obj/item/deck/LateInitialize(mapload)
. = ..()
- AddComponent(/datum/component/proximity_monitor/table)
+
+ tracker = new(src)
RegisterSignal(src, COMSIG_ATOM_RANGED_ATTACKED, PROC_REF(on_ranged_attack))
+/obj/item/deck/Destroy()
+ qdel(tracker)
+ . = ..()
+
/obj/item/deck/examine(mob/user)
. = ..()
. += "It contains [length(cards) ? length(cards) : "no"] card\s."
diff --git a/code/modules/holiday/christmas.dm b/code/modules/holiday/christmas.dm
index 34e6972bada4..ad1d28023318 100644
--- a/code/modules/holiday/christmas.dm
+++ b/code/modules/holiday/christmas.dm
@@ -33,7 +33,9 @@
desc = "Directions for use: Requires two people, one to pull each end."
var/cracked = 0
-/obj/item/toy/xmas_cracker/attack__legacy__attackchain(mob/target, mob/user)
+/obj/item/toy/xmas_cracker/attack(mob/living/target, mob/living/carbon/human/user)
+ if(..())
+ return FINISH_ATTACK
if(!cracked && ishuman(target) && (target.stat == CONSCIOUS) && !target.get_active_hand())
target.visible_message("[user] and [target] pop \an [src]! *pop*", "You pull \an [src] with [target]! *pop*", "You hear a *pop*.")
var/obj/item/paper/Joke = new /obj/item/paper(user.loc)
diff --git a/code/modules/hydroponics/grown/towercap.dm b/code/modules/hydroponics/grown/towercap.dm
index d9536cbb5736..c2dc17adf74b 100644
--- a/code/modules/hydroponics/grown/towercap.dm
+++ b/code/modules/hydroponics/grown/towercap.dm
@@ -133,6 +133,13 @@
var/lighter // Who lit the fucking thing
var/fire_stack_strength = 5
+/obj/structure/bonfire/Initialize(mapload)
+ . = ..()
+ var/static/list/loc_connections = list(
+ COMSIG_ATOM_ENTERED = PROC_REF(on_atom_entered),
+ )
+ AddElement(/datum/element/connect_loc, loc_connections)
+
/obj/structure/bonfire/dense
density = TRUE
@@ -199,11 +206,13 @@
..()
StartBurning()
-/obj/structure/bonfire/Crossed(atom/movable/AM, oldloc)
+/obj/structure/bonfire/proc/on_atom_entered(datum/source, atom/movable/entered)
+ SIGNAL_HANDLER // COMSIG_ATOM_ENTERED
+
if(burning)
Burn()
- if(ishuman(AM))
- var/mob/living/carbon/human/H = AM
+ if(ishuman(entered))
+ var/mob/living/carbon/human/H = entered
add_attack_logs(src, H, "Burned by a bonfire (Lit by [lighter])", ATKLOG_ALMOSTALL)
/obj/structure/bonfire/proc/Burn()
diff --git a/code/modules/hydroponics/hydroponics_tray.dm b/code/modules/hydroponics/hydroponics_tray.dm
index 3b7b0f3477cf..403eab67e4d7 100644
--- a/code/modules/hydroponics/hydroponics_tray.dm
+++ b/code/modules/hydroponics/hydroponics_tray.dm
@@ -990,7 +990,7 @@
update_state()
///Diona Nymph Related Procs///
-/obj/machinery/hydroponics/CanPass(atom/movable/mover, turf/target) //So nymphs can climb over top of trays.
+/obj/machinery/hydroponics/CanPass(atom/movable/mover, border_dir) //So nymphs can climb over top of trays.
if(istype(mover) && mover.checkpass(PASSTABLE))
return 1
else
diff --git a/code/modules/lighting/lighting_emissive_blocker.dm b/code/modules/lighting/lighting_emissive_blocker.dm
index 6eb46cbdad8a..303957793540 100644
--- a/code/modules/lighting/lighting_emissive_blocker.dm
+++ b/code/modules/lighting/lighting_emissive_blocker.dm
@@ -32,8 +32,8 @@
/atom/movable/emissive_blocker/blob_act()
return
-/atom/movable/emissive_blocker/onTransitZ()
- return
+/atom/movable/emissive_blocker/on_changed_z_level(turf/old_turf, turf/new_turf, notify_contents = FALSE)
+ return ..()
//Prevents people from moving these after creation, because they shouldn't be.
/atom/movable/emissive_blocker/forceMove(atom/destination, no_tp = FALSE, harderforce = FALSE)
diff --git a/code/modules/lighting/lighting_object.dm b/code/modules/lighting/lighting_object.dm
index 168a95c07f26..df62b5c342e9 100644
--- a/code/modules/lighting/lighting_object.dm
+++ b/code/modules/lighting/lighting_object.dm
@@ -142,8 +142,8 @@
/atom/movable/lighting_object/blob_act(obj/structure/blob/B)
return
-/atom/movable/lighting_object/onTransitZ()
- return
+/atom/movable/lighting_object/on_changed_z_level(turf/old_turf, turf/new_turf, notify_contents = FALSE)
+ return ..()
// Override here to prevent things accidentally moving around overlays.
/atom/movable/lighting_object/forceMove(atom/destination, no_tp = FALSE, harderforce = FALSE)
diff --git a/code/modules/lighting/lighting_turf.dm b/code/modules/lighting/lighting_turf.dm
index a0c39b80da3a..6237548bfc3c 100644
--- a/code/modules/lighting/lighting_turf.dm
+++ b/code/modules/lighting/lighting_turf.dm
@@ -84,7 +84,7 @@
has_opaque_atom = TRUE
break
-/turf/Exited(atom/movable/Obj, atom/newloc)
+/turf/Exited(atom/movable/Obj, direction)
. = ..()
if(Obj && Obj.opacity)
diff --git a/code/modules/mapping/mapping_helpers.dm b/code/modules/mapping/mapping_helpers.dm
index fa1e698c084d..7f1e35bb744f 100644
--- a/code/modules/mapping/mapping_helpers.dm
+++ b/code/modules/mapping/mapping_helpers.dm
@@ -93,7 +93,7 @@
/obj/effect/mapping_helpers/airlock
layer = DOOR_HELPER_LAYER
late = TRUE
- var/list/blacklist = list(/obj/machinery/door/firedoor, /obj/machinery/door/poddoor, /obj/machinery/door/unpowered)
+ var/list/blacklist = list(/obj/machinery/door/firedoor, /obj/machinery/door/poddoor)
/obj/effect/mapping_helpers/airlock/Initialize(mapload)
. = ..()
@@ -161,7 +161,7 @@
//part responsible for windoors (thanks S34N)
/obj/effect/mapping_helpers/airlock/windoor
- blacklist = list(/obj/machinery/door/firedoor, /obj/machinery/door/poddoor, /obj/machinery/door/unpowered, /obj/machinery/door/airlock)
+ blacklist = list(/obj/machinery/door/firedoor, /obj/machinery/door/poddoor, /obj/machinery/door/airlock)
/// Apply to a wall (or floor, technically) to ensure it is instantly destroyed by any explosion, even if usually invulnerable
/obj/effect/mapping_helpers/bombable_wall
diff --git a/code/modules/maptext_alerts/location_blurbs.dm b/code/modules/maptext_alerts/location_blurbs.dm
index 519e3044c9bc..e4ea40114d6b 100644
--- a/code/modules/maptext_alerts/location_blurbs.dm
+++ b/code/modules/maptext_alerts/location_blurbs.dm
@@ -1,5 +1,5 @@
/atom/movable/screen/text/blurb
- maptext_height = 64
+ maptext_height = 128
maptext_width = 512
screen_loc = "LEFT+1,BOTTOM+2"
/// Font size in pixels
diff --git a/code/modules/mining/equipment/resonator.dm b/code/modules/mining/equipment/resonator.dm
index f9d9ad373c61..d4762b8bb934 100644
--- a/code/modules/mining/equipment/resonator.dm
+++ b/code/modules/mining/equipment/resonator.dm
@@ -93,7 +93,11 @@
if(mode == RESONATOR_MODE_MATRIX)
icon_state = "shield2"
name = "resonance matrix"
- RegisterSignal(src, COMSIG_MOVABLE_CROSSED, PROC_REF(burst))
+ RegisterSignal(src, COMSIG_ATOM_ENTERED, PROC_REF(burst))
+ var/static/list/loc_connections = list(
+ COMSIG_ATOM_ENTERED = PROC_REF(burst),
+ )
+ AddElement(/datum/element/connect_loc, loc_connections)
. = ..()
creator = set_creator
parent_resonator = set_resonator
@@ -126,7 +130,7 @@
resonance_damage *= damage_multiplier
/obj/effect/temp_visual/resonance/proc/burst()
- SIGNAL_HANDLER // COMSIG_MOVABLE_CROSSED
+ SIGNAL_HANDLER // COMSIG_ATOM_ENTERED
if(rupturing)
return
rupturing = TRUE
diff --git a/code/modules/mining/ores_coins.dm b/code/modules/mining/ores_coins.dm
index 6c0b366586fa..731285e775ce 100644
--- a/code/modules/mining/ores_coins.dm
+++ b/code/modules/mining/ores_coins.dm
@@ -33,34 +33,6 @@
to_chat(user, "You smelt [src] into its refined form!")
qdel(src)
-/obj/item/stack/ore/Crossed(atom/movable/AM, oldloc)
- var/obj/item/storage/bag/ore/OB
- var/turf/simulated/floor/F = get_turf(src)
- if(loc != F)
- return ..()
- if(ishuman(AM))
- var/mob/living/carbon/human/H = AM
- for(var/thing in H.get_body_slots())
- if(istype(thing, /obj/item/storage/bag/ore))
- OB = thing
- break
- else if(isrobot(AM))
- var/mob/living/silicon/robot/R = AM
- for(var/thing in R.get_all_slots())
- if(istype(thing, /obj/item/storage/bag/ore))
- OB = thing
- break
- if(OB && istype(F, /turf/simulated/floor/plating/asteroid))
- var/turf/simulated/floor/plating/asteroid/FA = F
- FA.attempt_ore_pickup(OB, AM)
- // Then, if the user is dragging an ore box, empty the satchel
- // into the box.
- var/mob/living/L = AM
- if(istype(L.pulling, /obj/structure/ore_box))
- var/obj/structure/ore_box/box = L.pulling
- box.attackby__legacy__attackchain(OB, AM)
- return ..()
-
/obj/item/stack/ore/fire_act(datum/gas_mixture/air, exposed_temperature, exposed_volume, global_overlay = TRUE)
. = ..()
if(isnull(refined_type))
diff --git a/code/modules/mining/satchel_ore_boxdm.dm b/code/modules/mining/satchel_ore_boxdm.dm
index 3d5aa45f685f..88b454e0037c 100644
--- a/code/modules/mining/satchel_ore_boxdm.dm
+++ b/code/modules/mining/satchel_ore_boxdm.dm
@@ -16,10 +16,11 @@
W.forceMove(src)
else if(isstorage(W))
var/obj/item/storage/S = W
- S.hide_from(usr)
- for(var/obj/item/stack/ore/O in S.contents)
- S.remove_from_storage(O, src) //This will move the item to this item's contents
- to_chat(user, "You empty the satchel into the box.")
+ S.hide_from(user)
+ if(length(S.contents))
+ for(var/obj/item/stack/ore/O in S.contents)
+ S.remove_from_storage(O, src) //This will move the item to this item's contents
+ to_chat(user, "You empty the satchel into the box.")
else
return ..()
@@ -83,8 +84,8 @@
if(Adjacent(user))
. += "You can Alt-Shift-Click to empty the ore box."
-/obj/structure/ore_box/onTransitZ()
- return
+/obj/structure/ore_box/on_changed_z_level(turf/old_turf, turf/new_turf, notify_contents = FALSE)
+ return ..()
/obj/structure/ore_box/AltShiftClick(mob/user)
if(!Adjacent(user) || !ishuman(user) || HAS_TRAIT(user, TRAIT_HANDS_BLOCKED))
diff --git a/code/modules/mob/dead/dead.dm b/code/modules/mob/dead/dead.dm
index 873b60555a91..3ea6c1471364 100644
--- a/code/modules/mob/dead/dead.dm
+++ b/code/modules/mob/dead/dead.dm
@@ -16,14 +16,14 @@
var/turf/old_turf = get_turf(src)
var/turf/new_turf = get_turf(destination)
if(old_turf?.z != new_turf?.z)
- onTransitZ(old_turf?.z, new_turf?.z)
+ on_changed_z_level(old_turf, new_turf)
var/oldloc = loc
loc = destination
Moved(oldloc, direction)
-/mob/dead/onTransitZ(old_z,new_z)
+/mob/dead/on_changed_z_level(turf/old_turf, turf/new_turf)
..()
- update_z(new_z)
+ update_z(new_turf?.z)
/mob/dead/proc/update_z(new_z) // 1+ to register, null to unregister
if(registered_z != new_z)
diff --git a/code/modules/mob/dead/observer/observer_base.dm b/code/modules/mob/dead/observer/observer_base.dm
index 03267d00140c..59159c638f89 100644
--- a/code/modules/mob/dead/observer/observer_base.dm
+++ b/code/modules/mob/dead/observer/observer_base.dm
@@ -167,7 +167,7 @@ GLOBAL_DATUM_INIT(ghost_crew_monitor, /datum/ui_module/crew_monitor/ghost, new)
MA.plane = GAME_PLANE
. = MA
-/mob/dead/CanPass(atom/movable/mover, turf/target)
+/mob/dead/CanPass(atom/movable/mover, border_dir)
return 1
diff --git a/code/modules/mob/dead/observer/spells.dm b/code/modules/mob/dead/observer/spells.dm
index 5128f8a9bb2f..959a7e5f7243 100644
--- a/code/modules/mob/dead/observer/spells.dm
+++ b/code/modules/mob/dead/observer/spells.dm
@@ -18,7 +18,6 @@ GLOBAL_LIST_INIT(boo_phrases, list(
ghost = TRUE
action_icon_state = "boo"
- school = "transmutation"
base_cooldown = 2 MINUTES
starts_charged = FALSE
clothes_req = FALSE
diff --git a/code/modules/mob/inventory_procs.dm b/code/modules/mob/inventory_procs.dm
index 725f7fd28952..a80372a2f3ee 100644
--- a/code/modules/mob/inventory_procs.dm
+++ b/code/modules/mob/inventory_procs.dm
@@ -186,7 +186,11 @@
if(I)
if(client)
client.screen -= I
- I.forceMove(drop_location())
+ var/turf/drop_loc = drop_location()
+ if(drop_loc)
+ I.forceMove(drop_loc)
+ else
+ I.moveToNullspace()
I.dropped(src, silent)
if(I)
I.layer = initial(I.layer)
@@ -263,6 +267,10 @@
to_chat(M, "You are not holding anything to equip!")
return FALSE
+ if(flags & NODROP)
+ to_chat(M, "You are unable to equip that!")
+ return FALSE
+
if(M.equip_to_appropriate_slot(src))
if(M.hand)
M.update_inv_l_hand()
diff --git a/code/modules/mob/living/carbon/alien/alien_base.dm b/code/modules/mob/living/carbon/alien/alien_base.dm
index 92152327c4c7..557757b751e7 100644
--- a/code/modules/mob/living/carbon/alien/alien_base.dm
+++ b/code/modules/mob/living/carbon/alien/alien_base.dm
@@ -256,3 +256,6 @@ and carry the owner just to make sure*/
if(health <= HEALTH_THRESHOLD_CRIT && stat == CONSCIOUS)
KnockOut()
return ..()
+
+/mob/living/carbon/alien/plushify(plushie_override, curse_time)
+ . = ..(/obj/item/toy/plushie/face_hugger, curse_time)
diff --git a/code/modules/mob/living/carbon/alien/special/facehugger.dm b/code/modules/mob/living/carbon/alien/special/facehugger.dm
index 4e11337826af..5e0beba27232 100644
--- a/code/modules/mob/living/carbon/alien/special/facehugger.dm
+++ b/code/modules/mob/living/carbon/alien/special/facehugger.dm
@@ -23,10 +23,11 @@
///Time it takes for a facehugger to become active again after going idle.
var/min_active_time = 20 SECONDS
var/max_active_time = 40 SECONDS
+ var/datum/proximity_monitor/proximity_monitor
/obj/item/clothing/mask/facehugger/Initialize(mapload)
. = ..()
- AddComponent(/datum/component/proximity_monitor)
+ proximity_monitor = new(src)
ADD_TRAIT(src, TRAIT_XENO_INTERACTABLE, UID())
/obj/item/clothing/mask/facehugger/take_damage(damage_amount, damage_type = BRUTE, damage_flag = 0, sound_effect = 1, attack_dir)
@@ -67,10 +68,6 @@
/obj/item/clothing/mask/facehugger/equipped(mob/M)
Attach(M)
-/obj/item/clothing/mask/facehugger/Crossed(atom/target, oldloc)
- HasProximity(target)
- return
-
/obj/item/clothing/mask/facehugger/on_found(mob/finder)
if(stat != DEAD)
return HasProximity(finder)
@@ -186,6 +183,7 @@
return
stat = CONSCIOUS
+ proximity_monitor = new(src)
icon_state = "[initial(icon_state)]"
/obj/item/clothing/mask/facehugger/proc/GoIdle()
@@ -194,6 +192,7 @@
stat = UNCONSCIOUS
icon_state = "[initial(icon_state)]_inactive"
+ qdel(proximity_monitor)
addtimer(CALLBACK(src, PROC_REF(GoActive)), rand(min_active_time, max_active_time))
/obj/item/clothing/mask/facehugger/proc/Die()
@@ -203,7 +202,7 @@
icon_state = "[initial(icon_state)]_dead"
item_state = "facehugger_inactive"
stat = DEAD
- DeleteComponent(/datum/component/proximity_monitor)
+ QDEL_NULL(proximity_monitor)
visible_message("[src] curls up into a ball!")
diff --git a/code/modules/mob/living/carbon/carbon_procs.dm b/code/modules/mob/living/carbon/carbon_procs.dm
index d5131d0a4143..9c8a6ea0485b 100644
--- a/code/modules/mob/living/carbon/carbon_procs.dm
+++ b/code/modules/mob/living/carbon/carbon_procs.dm
@@ -498,7 +498,7 @@ GLOBAL_LIST_INIT(ventcrawl_machinery, list(/obj/machinery/atmospherics/unary/ven
visible_message("[src] begins climbing into the ventilation system...", \
"You begin climbing into the ventilation system...")
-#ifdef UNIT_TESTS
+#ifdef GAME_TESTS
var/ventcrawl_delay = 0 SECONDS
#else
var/ventcrawl_delay = 4.5 SECONDS
diff --git a/code/modules/mob/living/carbon/examine.dm b/code/modules/mob/living/carbon/examine.dm
index bba2f1c9e9fa..c634b429d711 100644
--- a/code/modules/mob/living/carbon/examine.dm
+++ b/code/modules/mob/living/carbon/examine.dm
@@ -88,6 +88,8 @@
return ""
/mob/living/carbon/examine(mob/user)
+ if(HAS_TRAIT(src, TRAIT_UNKNOWN))
+ return list("You're struggling to make out any details...")
var/skipgloves = FALSE
var/skipsuitstorage = FALSE
var/skipjumpsuit = FALSE
diff --git a/code/modules/mob/living/carbon/human/human_mob.dm b/code/modules/mob/living/carbon/human/human_mob.dm
index f4b5626df388..7586049bf701 100644
--- a/code/modules/mob/living/carbon/human/human_mob.dm
+++ b/code/modules/mob/living/carbon/human/human_mob.dm
@@ -284,6 +284,8 @@
// Get rank from ID, ID inside PDA, PDA, ID in wallet, etc.
/mob/living/carbon/human/proc/get_authentification_rank(if_no_id = "No id", if_no_job = "No job")
+ if(HAS_TRAIT(src, TRAIT_UNKNOWN))
+ return if_no_id
var/obj/item/card/id/id = wear_id?.GetID()
if(istype(id))
return id.rank || if_no_job
@@ -292,6 +294,8 @@
//gets assignment from ID, PDA, Wallet, etc.
//This should not be relied on for authentication, because PDAs show their owner's job, even if an ID is not inserted
/mob/living/carbon/human/proc/get_assignment(if_no_id = "No id", if_no_job = "No job")
+ if(HAS_TRAIT(src, TRAIT_UNKNOWN))
+ return if_no_id
if(!wear_id)
return if_no_id
var/obj/item/card/id/id = wear_id.GetID()
@@ -331,6 +335,8 @@
//repurposed proc. Now it combines get_id_name() and get_face_name() to determine a mob's name variable. Made into a seperate proc as it'll be useful elsewhere
/mob/living/carbon/human/get_visible_name(id_override = FALSE)
+ if(HAS_TRAIT(src, TRAIT_UNKNOWN))
+ return "Unknown"
if(name_override)
return name_override
if(wear_mask && (wear_mask.flags_inv & HIDEFACE)) //Wearing a mask which hides our face, use id-name if possible
@@ -2065,6 +2071,10 @@ Eyes need to have significantly high darksight to shine unless the mob has the X
var/obj/item/organ/internal/brain/brain_organ = get_int_organ(/obj/item/organ/internal/brain)
return brain_organ.damage >= (brain_organ.max_damage * threshold_level)
+
+/mob/living/carbon/human/plushify(plushie_override, curse_time)
+ . = ..(dna.species.plushie_type, curse_time)
+
/*
* Invokes a hallucination on the mob. Hallucination must be a path or a string of a path
*/
@@ -2075,3 +2085,4 @@ Eyes need to have significantly high darksight to shine unless the mob has the X
return
hallucination_to_make = string_path
new hallucination_to_make(get_turf(src), src)
+
diff --git a/code/modules/mob/living/carbon/human/human_say.dm b/code/modules/mob/living/carbon/human/human_say.dm
index 4c87b39d007b..17a83087031f 100644
--- a/code/modules/mob/living/carbon/human/human_say.dm
+++ b/code/modules/mob/living/carbon/human/human_say.dm
@@ -47,6 +47,9 @@
return FALSE
/mob/living/carbon/human/GetVoice()
+ if(HAS_TRAIT(src, TRAIT_UNKNOWN))
+ return "Unknown"
+
var/has_changer = HasVoiceChanger()
if(has_changer)
diff --git a/code/modules/mob/living/carbon/human/species/_species.dm b/code/modules/mob/living/carbon/human/species/_species.dm
index 086d8272ac53..7d18e244adf0 100644
--- a/code/modules/mob/living/carbon/human/species/_species.dm
+++ b/code/modules/mob/living/carbon/human/species/_species.dm
@@ -196,6 +196,9 @@
var/list/autohiss_extra_map = null
var/list/autohiss_exempt = null
+ /// What plushie the species will turn into if turned into a plushie.
+ var/plushie_type = /obj/item/toy/plushie/humanplushie
+
/datum/species/New()
unarmed = new unarmed_type()
if(!sprite_sheet_name)
@@ -266,7 +269,9 @@
new mutantears(H)
/datum/species/proc/breathe(mob/living/carbon/human/H)
+ var/datum/organ/lungs/lung = H.get_int_organ_datum(ORGAN_DATUM_LUNGS)
if(HAS_TRAIT(H, TRAIT_NOBREATH))
+ lung?.clear_alerts(H)
return TRUE
////////////////
diff --git a/code/modules/mob/living/carbon/human/species/abductor_species.dm b/code/modules/mob/living/carbon/human/species/abductor_species.dm
index 37ccc57546a1..101204ba00a0 100644
--- a/code/modules/mob/living/carbon/human/species/abductor_species.dm
+++ b/code/modules/mob/living/carbon/human/species/abductor_species.dm
@@ -26,6 +26,7 @@
female_scream_sound = 'sound/goonstation/voice/male_scream.ogg'
female_cough_sounds = list('sound/effects/mob_effects/m_cougha.ogg','sound/effects/mob_effects/m_coughb.ogg', 'sound/effects/mob_effects/m_coughc.ogg')
female_sneeze_sound = 'sound/effects/mob_effects/sneeze.ogg' //Abductors always scream like guys
+ plushie_type = /obj/item/toy/plushie/abductor
var/team = 1
var/scientist = FALSE // vars to not pollute spieces list with castes
diff --git a/code/modules/mob/living/carbon/human/species/diona_species.dm b/code/modules/mob/living/carbon/human/species/diona_species.dm
index 0bd8e688321f..1d0e3e2e0218 100644
--- a/code/modules/mob/living/carbon/human/species/diona_species.dm
+++ b/code/modules/mob/living/carbon/human/species/diona_species.dm
@@ -61,6 +61,8 @@
"pulls out a secret stash of herbicide and takes a hearty swig!",
"is pulling themselves apart!")
+ plushie_type = /obj/item/toy/plushie/dionaplushie
+
/datum/species/diona/can_understand(mob/other)
if(isnymph(other))
return TRUE
diff --git a/code/modules/mob/living/carbon/human/species/drask.dm b/code/modules/mob/living/carbon/human/species/drask.dm
index ab37c5ceb0a5..47831972d437 100644
--- a/code/modules/mob/living/carbon/human/species/drask.dm
+++ b/code/modules/mob/living/carbon/human/species/drask.dm
@@ -29,6 +29,8 @@
"is sucking in warm air!",
"is holding their breath!")
+ plushie_type = /obj/item/toy/plushie/draskplushie
+
species_traits = list(LIPS, NO_HAIR)
clothing_flags = HAS_UNDERWEAR | HAS_UNDERSHIRT
bodyflags = HAS_SKIN_TONE | HAS_BODY_MARKINGS | BALD | SHAVED
diff --git a/code/modules/mob/living/carbon/human/species/grey.dm b/code/modules/mob/living/carbon/human/species/grey.dm
index 4e178a534797..f831ce0d44f0 100644
--- a/code/modules/mob/living/carbon/human/species/grey.dm
+++ b/code/modules/mob/living/carbon/human/species/grey.dm
@@ -30,6 +30,8 @@
flesh_color = "#a598ad"
blood_color = "#A200FF"
+ plushie_type = /obj/item/toy/plushie/greyplushie
+
/datum/species/grey/handle_dna(mob/living/carbon/human/H, remove)
..()
H.dna.SetSEState(GLOB.remotetalkblock, !remove, TRUE)
diff --git a/code/modules/mob/living/carbon/human/species/kidan.dm b/code/modules/mob/living/carbon/human/species/kidan.dm
index 95a5470753bb..70eb61d77a9d 100644
--- a/code/modules/mob/living/carbon/human/species/kidan.dm
+++ b/code/modules/mob/living/carbon/human/species/kidan.dm
@@ -55,3 +55,5 @@
"s" = list("z", "zs", "zzz", "zzsz")
)
autohiss_exempt = list("Chittin")
+
+ plushie_type = /obj/item/toy/plushie/kidanplushie
diff --git a/code/modules/mob/living/carbon/human/species/machine.dm b/code/modules/mob/living/carbon/human/species/machine.dm
index 9243510ddb41..2d5ee49e0e0b 100644
--- a/code/modules/mob/living/carbon/human/species/machine.dm
+++ b/code/modules/mob/living/carbon/human/species/machine.dm
@@ -74,6 +74,8 @@
"is frying their own circuits!",
"is blocking their ventilation port!")
+ plushie_type = /obj/item/toy/plushie/ipcplushie
+
/datum/species/machine/on_species_gain(mob/living/carbon/human/H)
..()
diff --git a/code/modules/mob/living/carbon/human/species/moth.dm b/code/modules/mob/living/carbon/human/species/moth.dm
index 690096f8e7b7..be74a62e4dff 100644
--- a/code/modules/mob/living/carbon/human/species/moth.dm
+++ b/code/modules/mob/living/carbon/human/species/moth.dm
@@ -59,6 +59,9 @@
"is ripping their wings off!",
"is holding their breath!"
)
+
+ plushie_type = /obj/item/toy/plushie/nianplushie
+
/datum/species/moth/updatespeciescolor(mob/living/carbon/human/H, owner_sensitive = 1) //Handling species-specific skin-tones for the nian race.
if(H.dna.species.bodyflags & HAS_ICON_SKIN_TONE)
var/new_icobase = 'icons/mob/human_races/nian/r_moth.dmi' //Default nian.
diff --git a/code/modules/mob/living/carbon/human/species/plasmaman.dm b/code/modules/mob/living/carbon/human/species/plasmaman.dm
index 74a2e22ff0de..774181c27857 100644
--- a/code/modules/mob/living/carbon/human/species/plasmaman.dm
+++ b/code/modules/mob/living/carbon/human/species/plasmaman.dm
@@ -51,6 +51,8 @@
"s" = list("ss", "sss", "ssss")
)
+ plushie_type = /obj/item/toy/plushie/plasmamanplushie
+
/datum/species/plasmaman/before_equip_job(datum/job/J, mob/living/carbon/human/H, visualsOnly = FALSE)
var/current_job = J.title
var/datum/outfit/plasmaman/O = new /datum/outfit/plasmaman
diff --git a/code/modules/mob/living/carbon/human/species/skrell.dm b/code/modules/mob/living/carbon/human/species/skrell.dm
index 873864b78e44..28b593effdfd 100644
--- a/code/modules/mob/living/carbon/human/species/skrell.dm
+++ b/code/modules/mob/living/carbon/human/species/skrell.dm
@@ -43,3 +43,5 @@
"is twisting their own neck!",
"makes like a fish and suffocates!",
"is strangling themselves with their own tendrils!")
+
+ plushie_type = /obj/item/toy/plushie/skrellplushie
diff --git a/code/modules/mob/living/carbon/human/species/slimepeople.dm b/code/modules/mob/living/carbon/human/species/slimepeople.dm
index bcf0f0f9b64f..536eb744765e 100644
--- a/code/modules/mob/living/carbon/human/species/slimepeople.dm
+++ b/code/modules/mob/living/carbon/human/species/slimepeople.dm
@@ -59,6 +59,8 @@
var/reagent_skin_coloring = FALSE
+ plushie_type = /obj/item/toy/plushie/slimeplushie
+
/datum/species/slime/on_species_gain(mob/living/carbon/human/H)
..()
var/datum/action/innate/regrow/grow = new()
diff --git a/code/modules/mob/living/carbon/human/species/tajaran.dm b/code/modules/mob/living/carbon/human/species/tajaran.dm
index 6a71141a991e..799503547c86 100644
--- a/code/modules/mob/living/carbon/human/species/tajaran.dm
+++ b/code/modules/mob/living/carbon/human/species/tajaran.dm
@@ -56,5 +56,7 @@
)
autohiss_exempt = list("Siik'tajr")
+ plushie_type = /obj/item/toy/plushie/grey_cat
+
/datum/species/tajaran/handle_death(gibbed, mob/living/carbon/human/H)
H.stop_tail_wagging()
diff --git a/code/modules/mob/living/carbon/human/species/unathi.dm b/code/modules/mob/living/carbon/human/species/unathi.dm
index d0102489d666..9d39401ccf25 100644
--- a/code/modules/mob/living/carbon/human/species/unathi.dm
+++ b/code/modules/mob/living/carbon/human/species/unathi.dm
@@ -62,6 +62,8 @@
)
autohiss_exempt = list("Sinta'unathi")
+ plushie_type = /obj/item/toy/plushie/lizardplushie
+
/datum/species/unathi/on_species_gain(mob/living/carbon/human/H)
..()
var/datum/action/innate/unathi_ignite/fire = new()
diff --git a/code/modules/mob/living/carbon/human/species/vox.dm b/code/modules/mob/living/carbon/human/species/vox.dm
index 80ae2e26b451..89e319f1b675 100644
--- a/code/modules/mob/living/carbon/human/species/vox.dm
+++ b/code/modules/mob/living/carbon/human/species/vox.dm
@@ -72,6 +72,8 @@
speciesbox = /obj/item/storage/box/survival_vox
+ plushie_type = /obj/item/toy/plushie/voxplushie
+
/datum/species/vox/handle_death(gibbed, mob/living/carbon/human/H)
H.stop_tail_wagging()
diff --git a/code/modules/mob/living/carbon/human/species/vulpkanin.dm b/code/modules/mob/living/carbon/human/species/vulpkanin.dm
index f98b03002e22..a6ba75e80e5e 100644
--- a/code/modules/mob/living/carbon/human/species/vulpkanin.dm
+++ b/code/modules/mob/living/carbon/human/species/vulpkanin.dm
@@ -47,5 +47,7 @@
"is twisting their own neck!",
"is holding their breath!")
+ plushie_type = /obj/item/toy/plushie/red_fox
+
/datum/species/vulpkanin/handle_death(gibbed, mob/living/carbon/human/H)
H.stop_tail_wagging()
diff --git a/code/modules/mob/living/init_signals.dm b/code/modules/mob/living/init_signals.dm
index b2fd6735f89b..07baa8e3c926 100644
--- a/code/modules/mob/living/init_signals.dm
+++ b/code/modules/mob/living/init_signals.dm
@@ -27,6 +27,10 @@
RegisterSignal(src, SIGNAL_ADDTRAIT(TRAIT_RESTRAINED), PROC_REF(on_restrained_trait_gain))
RegisterSignal(src, SIGNAL_REMOVETRAIT(TRAIT_RESTRAINED), PROC_REF(on_restrained_trait_loss))
+ RegisterSignal(src, SIGNAL_ADDTRAIT(TRAIT_UNKNOWN), PROC_REF(on_unknown_trait))
+ RegisterSignal(src, SIGNAL_REMOVETRAIT(TRAIT_UNKNOWN), PROC_REF(on_unknown_trait))
+
+
/// Called when [TRAIT_KNOCKEDOUT] is added to the mob.
/mob/living/proc/on_knockedout_trait_gain(datum/source)
SIGNAL_HANDLER
@@ -160,3 +164,11 @@
REMOVE_TRAIT(src, TRAIT_KNOCKEDOUT, TRAIT_FAKEDEATH)
remove_status_effect(STATUS_EFFECT_REVIVABLE)
+/// Gaining or losing [TRAIT_UNKNOWN] updates our name and our sechud
+/mob/living/proc/on_unknown_trait(datum/source)
+ SIGNAL_HANDLER // SIGNAL_ADDTRAIT(TRAIT_UNKNOWN), SIGNAL_REMOVETRAIT(TRAIT_UNKNOWN)
+ addtimer(CALLBACK(src, PROC_REF(on_unknown_trait_part_2)), 0.1 SECONDS) // Remove signal is sent before the trait is removed, we need to wait a tick
+
+/mob/living/proc/on_unknown_trait_part_2()
+ name = get_visible_name()
+ sec_hud_set_ID()
diff --git a/code/modules/mob/living/living.dm b/code/modules/mob/living/living.dm
index 50bf380187bc..267343b767b8 100644
--- a/code/modules/mob/living/living.dm
+++ b/code/modules/mob/living/living.dm
@@ -71,10 +71,10 @@
return
//Generic Bump(). Override MobBump() and ObjBump() instead of this.
-/mob/living/Bump(atom/A, yes)
+/mob/living/Bump(atom/A)
if(..()) //we are thrown onto something
return
- if(buckled || !yes || now_pushing)
+ if(buckled || now_pushing)
return
if(ismob(A))
if(MobBump(A))
@@ -1056,9 +1056,9 @@
else
registered_z = null
-/mob/living/onTransitZ(old_z,new_z)
+/mob/living/on_changed_z_level(turf/old_turf, turf/new_turf)
..()
- update_z(new_z)
+ update_z(new_turf?.z)
/mob/living/rad_act(amount)
. = ..()
@@ -1180,12 +1180,30 @@
for(var/obj/O in src)
O.on_mob_move(Dir, src)
-/mob/living/Crossed(atom/movable/mover)
- if(istype(mover, /obj/singularity/energy_ball))
- dust()
- return ..()
-
/// Can a mob interact with the apc remotely like a pulse demon, cyborg, or AI?
/mob/living/proc/can_remote_apc_interface(obj/machinery/power/apc/ourapc)
return FALSE
+/mob/living/proc/plushify(plushie_override, curse_time = 10 MINUTES)
+ var/mob/living/simple_animal/shade/sword/generic_item/plushvictim = new(get_turf(src))
+ var/obj/item/toy/plushie/plush_type = pick(subtypesof(/obj/item/toy/plushie) - typesof(/obj/item/toy/plushie/fluff) - typesof(/obj/item/toy/plushie/carpplushie)) //exclude the base type.
+ if(plushie_override)
+ plush_type = plushie_override
+ var/obj/item/toy/plushie/plush_outcome = new plush_type(get_turf(src))
+ plushvictim.forceMove(plush_outcome)
+ plushvictim.key = key
+ plushvictim.RegisterSignal(plush_outcome, COMSIG_PARENT_QDELETING, TYPE_PROC_REF(/mob/living/simple_animal/shade/sword/generic_item, handle_item_deletion))
+ plushvictim.name = name
+ plush_outcome.name = "[name] plushie"
+ if(curse_time == -1)
+ qdel(src)
+ else
+ plush_outcome.cursed_plushie_victim = src
+ forceMove(plush_outcome)
+ notransform = TRUE
+ status_flags |= GODMODE
+ addtimer(CALLBACK(plush_outcome, TYPE_PROC_REF(/obj/item/toy/plushie, un_plushify)), curse_time)
+ to_chat(plushvictim, "You have been cursed into an enchanted plush doll! At least you can still move around a bit...")
+
+/mob/living/proc/sec_hud_set_ID()
+ return
diff --git a/code/modules/mob/living/silicon/robot/drone/maint_drone.dm b/code/modules/mob/living/silicon/robot/drone/maint_drone.dm
index 1e5b4e52b887..a300fa2bac9b 100644
--- a/code/modules/mob/living/silicon/robot/drone/maint_drone.dm
+++ b/code/modules/mob/living/silicon/robot/drone/maint_drone.dm
@@ -359,7 +359,7 @@
*/
-/mob/living/silicon/robot/drone/Bump(atom/movable/AM, yes)
+/mob/living/silicon/robot/drone/Bump(atom/movable/AM)
if(is_type_in_list(AM, allowed_bumpable_objects))
return ..()
diff --git a/code/modules/mob/living/silicon/robot/robot_mob.dm b/code/modules/mob/living/silicon/robot/robot_mob.dm
index 404bc64e6619..f4e12751dc70 100644
--- a/code/modules/mob/living/silicon/robot/robot_mob.dm
+++ b/code/modules/mob/living/silicon/robot/robot_mob.dm
@@ -1809,3 +1809,8 @@ GLOBAL_LIST_INIT(robot_verbs_default, list(
if(ourapc.malfai && !(src in ourapc.malfai.connected_robots))
return FALSE
return TRUE
+
+/mob/living/silicon/robot/plushify(plushie_override, curse_time)
+ if(curse_time == -1)
+ QDEL_NULL(mmi)
+ return ..()
diff --git a/code/modules/mob/living/silicon/silicon_mob.dm b/code/modules/mob/living/silicon/silicon_mob.dm
index feb6cc0a6384..a0997ce25e3c 100644
--- a/code/modules/mob/living/silicon/silicon_mob.dm
+++ b/code/modules/mob/living/silicon/silicon_mob.dm
@@ -594,3 +594,6 @@
if(silicon_hat)
. += "They are wearing a [bicon(silicon_hat)] [silicon_hat.name]."
. += "Use an empty hand on [src] on grab mode to remove [silicon_hat]."
+
+/mob/living/silicon/plushify(plushie_override, curse_time)
+ . = ..(/obj/item/toy/plushie/borgplushie, curse_time)
diff --git a/code/modules/mob/living/simple_animal/bot/griefsky.dm b/code/modules/mob/living/simple_animal/bot/griefsky.dm
index e2e0296b7f80..7f2ce26fd65d 100644
--- a/code/modules/mob/living/simple_animal/bot/griefsky.dm
+++ b/code/modules/mob/living/simple_animal/bot/griefsky.dm
@@ -48,10 +48,9 @@
..()
light_color = LIGHT_COLOR_PURE_RED //if you see a red one. RUN!!
-/mob/living/simple_animal/bot/secbot/griefsky/Crossed(atom/movable/AM, oldloc)
- ..()
- if(ismob(AM) && AM == target)
- var/mob/living/carbon/C = AM
+/mob/living/simple_animal/bot/secbot/griefsky/on_atom_entered(datum/source, atom/movable/entered)
+ if(iscarbon(entered) && entered == target)
+ var/mob/living/carbon/C = entered
visible_message("[src] flails his swords and pushes [C] out of it's way!" )
C.KnockDown(4 SECONDS)
diff --git a/code/modules/mob/living/simple_animal/bot/honkbot.dm b/code/modules/mob/living/simple_animal/bot/honkbot.dm
index 20652fdb801a..9c243922bed3 100644
--- a/code/modules/mob/living/simple_animal/bot/honkbot.dm
+++ b/code/modules/mob/living/simple_animal/bot/honkbot.dm
@@ -36,6 +36,10 @@
var/datum/job/clown/J = new /datum/job/clown()
access_card.access += J.get_access()
prev_access = access_card.access
+ var/static/list/loc_connections = list(
+ COMSIG_ATOM_ENTERED = PROC_REF(on_atom_entered)
+ )
+ AddElement(/datum/element/connect_loc, loc_connections)
/mob/living/simple_animal/bot/honkbot/proc/sensor_blink()
icon_state = "honkbot-c"
@@ -373,10 +377,10 @@
target = user
mode = BOT_HUNT
-/mob/living/simple_animal/bot/honkbot/Crossed(atom/movable/AM, oldloc)
- if(ismob(AM) && on) //only if its online
+/mob/living/simple_animal/bot/honkbot/proc/on_atom_entered(datum/source, atom/movable/entered)
+ if(ismob(entered) && on) //only if its online
if(prob(30)) //you're far more likely to trip on a honkbot
- var/mob/living/carbon/C = AM
+ var/mob/living/carbon/C = entered
if(!istype(C) || !C || in_range(src, target))
return
C.visible_message("[pick( \
@@ -391,5 +395,3 @@
if(!client)
speak("Honk!")
sensor_blink()
- return
- ..()
diff --git a/code/modules/mob/living/simple_animal/bot/mulebot.dm b/code/modules/mob/living/simple_animal/bot/mulebot.dm
index ad9eca0a7787..48ef878ac713 100644
--- a/code/modules/mob/living/simple_animal/bot/mulebot.dm
+++ b/code/modules/mob/living/simple_animal/bot/mulebot.dm
@@ -74,7 +74,7 @@
mulebot_count++
set_suffix(suffix ? suffix : "#[mulebot_count]")
- RegisterSignal(src, COMSIG_CROSSED_MOVABLE, PROC_REF(human_squish_check))
+ RegisterSignal(src, COMSIG_MOVABLE_MOVED, PROC_REF(human_squish_check))
/mob/living/simple_animal/bot/mulebot/Destroy()
SStgui.close_uis(wires)
@@ -879,10 +879,13 @@
else
..()
-/mob/living/simple_animal/bot/mulebot/proc/human_squish_check(src, atom/movable/AM)
- if(!ishuman(AM))
+/mob/living/simple_animal/bot/mulebot/proc/human_squish_check(datum/source, old_location, direction, forced)
+ if(!isturf(loc))
return
- RunOver(AM)
+ for(var/atom/AM in loc)
+ if(!ishuman(AM))
+ continue
+ RunOver(AM)
#undef SIGH
#undef ANNOYED
diff --git a/code/modules/mob/living/simple_animal/bot/secbot.dm b/code/modules/mob/living/simple_animal/bot/secbot.dm
index 510874a95a51..c8909ba151ef 100644
--- a/code/modules/mob/living/simple_animal/bot/secbot.dm
+++ b/code/modules/mob/living/simple_animal/bot/secbot.dm
@@ -50,6 +50,10 @@
var/datum/job/detective/J = new/datum/job/detective
access_card.access += J.get_access()
prev_access = access_card.access
+ var/static/list/loc_connections = list(
+ COMSIG_ATOM_ENTERED = PROC_REF(on_atom_entered)
+ )
+ AddElement(/datum/element/connect_loc, loc_connections)
/mob/living/simple_animal/bot/secbot/Destroy()
QDEL_NULL(baton)
@@ -458,9 +462,9 @@
target = user
mode = BOT_HUNT
-/mob/living/simple_animal/bot/secbot/Crossed(atom/movable/AM, oldloc)
- if(ismob(AM) && target)
- var/mob/living/carbon/C = AM
+/mob/living/simple_animal/bot/secbot/proc/on_atom_entered(datum/source, atom/movable/entered)
+ if(ismob(entered) && target)
+ var/mob/living/carbon/C = entered
if(!istype(C) || !C || in_range(src, target))
return
C.visible_message("[pick( \
@@ -472,7 +476,6 @@
"[C] leaps out of [src]'s way!")]")
C.KnockDown(4 SECONDS)
return
- ..()
#undef BATON_COOLDOWN
#undef BOT_REBATON_THRESHOLD
diff --git a/code/modules/mob/living/simple_animal/friendly/cockroach.dm b/code/modules/mob/living/simple_animal/friendly/cockroach.dm
index 6f5caaf3a315..a04154601903 100644
--- a/code/modules/mob/living/simple_animal/friendly/cockroach.dm
+++ b/code/modules/mob/living/simple_animal/friendly/cockroach.dm
@@ -28,18 +28,22 @@
/mob/living/simple_animal/cockroach/Initialize(mapload) //Lizards are a great way to deal with cockroaches
. = ..()
ADD_TRAIT(src, TRAIT_EDIBLE_BUG, "edible_bug")
+ var/static/list/loc_connections = list(
+ COMSIG_ATOM_ENTERED = PROC_REF(on_atom_entered)
+ )
+ AddElement(/datum/element/connect_loc, loc_connections)
-/mob/living/simple_animal/cockroach/Crossed(atom/movable/AM, oldloc)
- if(isliving(AM))
- var/mob/living/A = AM
+/mob/living/simple_animal/cockroach/proc/on_atom_entered(datum/source, atom/movable/entered)
+ if(isliving(entered))
+ var/mob/living/A = entered
if(A.mob_size > MOB_SIZE_SMALL)
if(prob(squish_chance))
A.visible_message("\The [A] squashed \the [name].", "You squashed \the [name].")
death()
else
visible_message("\The [name] avoids getting crushed.")
- else if(isstructure(AM))
- visible_message("As \the [AM] moved over \the [name], it was crushed.")
+ else if(isstructure(entered))
+ visible_message("As \the [entered] moved over \the [name], it was crushed.")
death()
/mob/living/simple_animal/cockroach/ex_act() //Explosions are a terrible way to handle a cockroach.
diff --git a/code/modules/mob/living/simple_animal/friendly/mouse.dm b/code/modules/mob/living/simple_animal/friendly/mouse.dm
index 2643abad6da0..ec8c6b06ee1d 100644
--- a/code/modules/mob/living/simple_animal/friendly/mouse.dm
+++ b/code/modules/mob/living/simple_animal/friendly/mouse.dm
@@ -43,7 +43,7 @@
AddComponent(/datum/component/squeak, list('sound/creatures/mousesqueak.ogg' = 1), 100, extrarange = SHORT_RANGE_SOUND_EXTRARANGE) //as quiet as a mouse or whatever
/mob/living/simple_animal/mouse/handle_automated_action()
-#ifdef UNIT_TESTS // DO NOT EAT MY CABLES DURING UNIT TESTS
+#ifdef GAME_TESTS // DO NOT EAT MY CABLES DURING UNIT TESTS
return
#endif
if(prob(chew_probability) && isturf(loc))
@@ -85,6 +85,10 @@
icon_dead = "mouse_[mouse_color]_dead"
icon_resting = "mouse_[mouse_color]_sleep"
update_appearance(UPDATE_ICON_STATE|UPDATE_DESC)
+ var/static/list/loc_connections = list(
+ COMSIG_ATOM_ENTERED = PROC_REF(on_atom_entered)
+ )
+ AddElement(/datum/element/connect_loc, loc_connections)
/mob/living/simple_animal/mouse/update_desc()
. = ..()
@@ -102,12 +106,11 @@
to_chat(src, "You are too small to pull anything except cheese.")
return
-/mob/living/simple_animal/mouse/Crossed(AM as mob|obj, oldloc)
- if(ishuman(AM))
+/mob/living/simple_animal/mouse/proc/on_atom_entered(datum/source, atom/movable/entered)
+ if(ishuman(entered))
if(stat == CONSCIOUS)
- var/mob/M = AM
+ var/mob/M = entered
to_chat(M, "[bicon(src)] Squeek!")
- ..()
/mob/living/simple_animal/mouse/proc/toast()
add_atom_colour("#3A3A3A", FIXED_COLOUR_PRIORITY)
diff --git a/code/modules/mob/living/simple_animal/hostile/floorcluwne.dm b/code/modules/mob/living/simple_animal/hostile/floorcluwne.dm
index 6f6144d44fa6..4a98a04f2540 100644
--- a/code/modules/mob/living/simple_animal/hostile/floorcluwne.dm
+++ b/code/modules/mob/living/simple_animal/hostile/floorcluwne.dm
@@ -69,7 +69,7 @@
playsound(src.loc, 'sound/items/bikehorn.ogg', 50, 1)
-/mob/living/simple_animal/hostile/floor_cluwne/CanPass(atom/A, turf/target)
+/mob/living/simple_animal/hostile/floor_cluwne/CanPass(atom/A, border_dir)
return TRUE
diff --git a/code/modules/mob/living/simple_animal/hostile/megafauna/ancient_robot.dm b/code/modules/mob/living/simple_animal/hostile/megafauna/ancient_robot.dm
index 9dbac40967d7..5b4d0624eb42 100644
--- a/code/modules/mob/living/simple_animal/hostile/megafauna/ancient_robot.dm
+++ b/code/modules/mob/living/simple_animal/hostile/megafauna/ancient_robot.dm
@@ -307,9 +307,9 @@ Difficulty: Hard
return
return ..()
-/mob/living/simple_animal/hostile/megafauna/ancient_robot/Bump(atom/A, yes)
+/mob/living/simple_animal/hostile/megafauna/ancient_robot/Bump(atom/A)
if(charging)
- if(isliving(A) && yes)
+ if(isliving(A))
var/mob/living/L = A
if(!istype(A, /mob/living/simple_animal/hostile/ancient_robot_leg))
L.visible_message("[src] slams into [L]!", "[src] tramples you into the ground!")
@@ -695,9 +695,9 @@ Difficulty: Hard
/mob/living/simple_animal/hostile/ancient_robot_leg/proc/beam_setup()
leg_part = Beam(core.beam, "leg_connection", 'icons/effects/effects.dmi', time=INFINITY, maxdistance=INFINITY, beam_type=/obj/effect/ebeam)
-/mob/living/simple_animal/hostile/ancient_robot_leg/onTransitZ(old_z,new_z)
+/mob/living/simple_animal/hostile/ancient_robot_leg/on_changed_z_level(turf/old_turf, turf/new_turf)
..()
- update_z(new_z)
+ update_z(new_turf?.z)
if(leg_part)
QDEL_NULL(leg_part)
addtimer(CALLBACK(src, PROC_REF(beam_setup)), 1 SECONDS)
@@ -730,10 +730,10 @@ Difficulty: Hard
walk_towards(src, T, movespeed)
DestroySurroundings()
-/mob/living/simple_animal/hostile/ancient_robot_leg/Bump(atom/A, yes)
+/mob/living/simple_animal/hostile/ancient_robot_leg/Bump(atom/A)
if(!core.charging)
return
- if(isliving(A) && yes)
+ if(isliving(A))
if(!istype(A, /mob/living/simple_animal/hostile/megafauna/ancient_robot))
var/mob/living/L = A
L.visible_message("[src] slams into [L]!", "[src] tramples you into the ground!")
@@ -820,7 +820,7 @@ Difficulty: Hard
tesla_zap(src, zap_range, power, zap_flags)
qdel(src)
-/obj/item/projectile/energy/tesla_bolt/Bump(atom/A, yes) // Don't want the projectile hitting the legs
+/obj/item/projectile/energy/tesla_bolt/Bump(atom/A) // Don't want the projectile hitting the legs
if(!istype(/mob/living/simple_animal/hostile/ancient_robot_leg, A))
return ..()
var/turf/target_turf = get_turf(A)
diff --git a/code/modules/mob/living/simple_animal/hostile/megafauna/bubblegum.dm b/code/modules/mob/living/simple_animal/hostile/megafauna/bubblegum.dm
index 323508edfe16..7bd2ee98227a 100644
--- a/code/modules/mob/living/simple_animal/hostile/megafauna/bubblegum.dm
+++ b/code/modules/mob/living/simple_animal/hostile/megafauna/bubblegum.dm
@@ -513,7 +513,7 @@ Difficulty: Hard
severity = EXPLODE_LIGHT // puny mortals
return ..()
-/mob/living/simple_animal/hostile/megafauna/bubblegum/CanPass(atom/movable/mover, turf/target)
+/mob/living/simple_animal/hostile/megafauna/bubblegum/CanPass(atom/movable/mover, border_dir)
if(istype(mover, /mob/living/simple_animal/hostile/megafauna/bubblegum/hallucination))
return TRUE
return ..()
@@ -541,8 +541,8 @@ Difficulty: Hard
playsound(src, 'sound/effects/meteorimpact.ogg', 200, TRUE, 2, TRUE)
return ..()
-/mob/living/simple_animal/hostile/megafauna/bubblegum/Bump(atom/A, yes)
- if(charging && yes)
+/mob/living/simple_animal/hostile/megafauna/bubblegum/Bump(atom/A)
+ if(charging)
if(isturf(A) || isobj(A) && A.density)
A.ex_act(EXPLODE_HEAVY)
if(isliving(A))
@@ -610,7 +610,7 @@ Difficulty: Hard
new /obj/effect/decal/cleanable/blood(get_turf(src))
. = ..()
-/mob/living/simple_animal/hostile/megafauna/bubblegum/hallucination/CanPass(atom/movable/mover, turf/target)
+/mob/living/simple_animal/hostile/megafauna/bubblegum/hallucination/CanPass(atom/movable/mover, border_dir)
if(istype(mover, /mob/living/simple_animal/hostile/megafauna/bubblegum)) // hallucinations should not be stopping bubblegum or eachother
return TRUE
return ..()
diff --git a/code/modules/mob/living/simple_animal/hostile/megafauna/hierophant.dm b/code/modules/mob/living/simple_animal/hostile/megafauna/hierophant.dm
index 59d2e4b3e86e..b290d2a4ef98 100644
--- a/code/modules/mob/living/simple_animal/hostile/megafauna/hierophant.dm
+++ b/code/modules/mob/living/simple_animal/hostile/megafauna/hierophant.dm
@@ -600,7 +600,7 @@ Difficulty: Hard
QUEUE_SMOOTH_NEIGHBORS(src)
return ..()
-/obj/effect/temp_visual/hierophant/wall/CanPass(atom/movable/mover, turf/target)
+/obj/effect/temp_visual/hierophant/wall/CanPass(atom/movable/mover, border_dir)
if(QDELETED(caster))
return FALSE
if(mover == caster.pulledby)
@@ -721,6 +721,10 @@ Difficulty: Hard
var/turf/simulated/mineral/M = loc
M.gets_drilled(caster)
INVOKE_ASYNC(src, PROC_REF(blast))
+ var/static/list/loc_connections = list(
+ COMSIG_ATOM_ENTERED = PROC_REF(on_atom_entered)
+ )
+ AddElement(/datum/element/connect_loc, loc_connections)
/obj/effect/temp_visual/hierophant/blast/proc/blast()
var/turf/T = get_turf(src)
@@ -733,8 +737,7 @@ Difficulty: Hard
sleep(1.3) //slightly forgiving; the burst animation is 1.5 deciseconds
bursting = FALSE //we no longer damage crossers
-/obj/effect/temp_visual/hierophant/blast/Crossed(atom/movable/AM)
- ..()
+/obj/effect/temp_visual/hierophant/blast/proc/on_atom_entered(datum/source, atom/movable/entered)
if(bursting)
do_damage(get_turf(src))
diff --git a/code/modules/mob/living/simple_animal/hostile/megafauna/megafauna.dm b/code/modules/mob/living/simple_animal/hostile/megafauna/megafauna.dm
index b01e6a895670..ab151efa92d5 100644
--- a/code/modules/mob/living/simple_animal/hostile/megafauna/megafauna.dm
+++ b/code/modules/mob/living/simple_animal/hostile/megafauna/megafauna.dm
@@ -106,7 +106,7 @@
else
devour(L)
-/mob/living/simple_animal/hostile/megafauna/onTransitZ(old_z, new_z)
+/mob/living/simple_animal/hostile/megafauna/on_changed_z_level(turf/old_turf, turf/new_turf)
. = ..()
if(!istype(get_area(src), /area/shuttle)) //I'll be funny and make non teleported enrage mobs not lose enrage. Harder to pull off, and also funny when it happens accidently. Or if one gets on the escape shuttle.
unrage()
diff --git a/code/modules/mob/living/simple_animal/hostile/mining/elites/elite.dm b/code/modules/mob/living/simple_animal/hostile/mining/elites/elite.dm
index 229d8c8e0a69..5c9219a88f9a 100644
--- a/code/modules/mob/living/simple_animal/hostile/mining/elites/elite.dm
+++ b/code/modules/mob/living/simple_animal/hostile/mining/elites/elite.dm
@@ -179,6 +179,7 @@ While using this makes the system rely on OnFire, it still gives options for tim
///List of invaders that have teleportes into the arena *multiple times*. They will be suffering.
var/list/invaders = list()
+ var/datum/proximity_monitor/proximity_monitor
/obj/structure/elite_tumor/attack_hand(mob/user, list/modifiers)
. = ..()
@@ -241,7 +242,7 @@ While using this makes the system rely on OnFire, it still gives options for tim
icon_state = "tumor_popped"
RegisterSignal(mychild, COMSIG_PARENT_QDELETING, PROC_REF(onEliteLoss))
INVOKE_ASYNC(src, PROC_REF(arena_checks))
- AddComponent(/datum/component/proximity_monitor, ARENA_RADIUS) //Boots out humanoid invaders. Minebots / random fauna / that colossus you forgot to clear away allowed.
+ proximity_monitor = new(src, ARENA_RADIUS) //Boots out humanoid invaders. Minebots / random fauna / that colossus you forgot to clear away allowed.
/obj/structure/elite_tumor/proc/return_elite()
mychild.forceMove(loc)
@@ -254,7 +255,7 @@ While using this makes the system rely on OnFire, it still gives options for tim
mychild.grab_ghost()
notify_ghosts("\A [mychild] has been challenged in \the [get_area(src)]!", enter_link="(Click to help)", source = mychild, action = NOTIFY_FOLLOW)
INVOKE_ASYNC(src, PROC_REF(arena_checks))
- AddComponent(/datum/component/proximity_monitor, ARENA_RADIUS)
+ proximity_monitor = new(src, ARENA_RADIUS)
/obj/structure/elite_tumor/Initialize(mapload)
. = ..()
@@ -413,7 +414,7 @@ While using this makes the system rely on OnFire, it still gives options for tim
text += "If teleported to the Station by jaunter, you are allowed to attack people on Station, until you get killed."
to_chat(mychild, text.Join(" "))
- DeleteComponent(/datum/component/proximity_monitor)
+ QDEL_NULL(proximity_monitor)
/obj/item/tumor_shard
name = "tumor shard"
diff --git a/code/modules/mob/living/simple_animal/hostile/mining/elites/legionnaire.dm b/code/modules/mob/living/simple_animal/hostile/mining/elites/legionnaire.dm
index 037ab6c2551f..ab73704d812b 100644
--- a/code/modules/mob/living/simple_animal/hostile/mining/elites/legionnaire.dm
+++ b/code/modules/mob/living/simple_animal/hostile/mining/elites/legionnaire.dm
@@ -292,12 +292,19 @@
light_color = LIGHT_COLOR_RED
var/mob/living/simple_animal/hostile/asteroid/elite/legionnaire/myowner = null
-/obj/structure/legionnaire_bonfire/Crossed(datum/source, atom/movable/mover)
- if(isobj(source))
- var/obj/object = source
+/obj/structure/legionnaire_bonfire/Initialize(mapload)
+ . = ..()
+ var/static/list/loc_connections = list(
+ COMSIG_ATOM_ENTERED = PROC_REF(on_atom_entered)
+ )
+ AddElement(/datum/element/connect_loc, loc_connections)
+
+/obj/structure/legionnaire_bonfire/proc/on_atom_entered(datum/source, atom/movable/entered)
+ if(isobj(entered))
+ var/obj/object = entered
object.fire_act(1000, 500)
- if(isliving(source))
- var/mob/living/fire_walker = source
+ if(isliving(entered))
+ var/mob/living/fire_walker = entered
fire_walker.adjust_fire_stacks(5)
fire_walker.IgniteMob()
diff --git a/code/modules/mob/living/simple_animal/hostile/syndicate_mobs.dm b/code/modules/mob/living/simple_animal/hostile/syndicate_mobs.dm
index 92779de451a4..bef98567bab7 100644
--- a/code/modules/mob/living/simple_animal/hostile/syndicate_mobs.dm
+++ b/code/modules/mob/living/simple_animal/hostile/syndicate_mobs.dm
@@ -237,7 +237,7 @@
new /obj/effect/gibspawner/human(get_turf(src))
return ..()
-/mob/living/simple_animal/hostile/syndicate/melee/autogib/depot/CanPass(atom/movable/mover, turf/target)
+/mob/living/simple_animal/hostile/syndicate/melee/autogib/depot/CanPass(atom/movable/mover, border_dir)
if(isliving(mover))
var/mob/living/blocker = mover
if(faction_check_mob(blocker))
diff --git a/code/modules/mob/living/simple_animal/hostile/terror_spiders/actions.dm b/code/modules/mob/living/simple_animal/hostile/terror_spiders/actions.dm
index 08a695f79d14..9f0bf777ef63 100644
--- a/code/modules/mob/living/simple_animal/hostile/terror_spiders/actions.dm
+++ b/code/modules/mob/living/simple_animal/hostile/terror_spiders/actions.dm
@@ -170,8 +170,12 @@
. = ..()
if(prob(50))
icon_state = "stickyweb2"
+ var/static/list/loc_connections = list(
+ COMSIG_ATOM_ENTERED = PROC_REF(on_atom_entered)
+ )
+ AddElement(/datum/element/connect_loc, loc_connections)
-/obj/structure/spider/terrorweb/CanPass(atom/movable/mover, turf/target)
+/obj/structure/spider/terrorweb/CanPass(atom/movable/mover, border_dir)
if(isterrorspider(mover))
return TRUE
if(istype(mover, /obj/item/projectile/terrorqueenspit))
@@ -185,10 +189,9 @@
return prob(20)
return ..()
-/obj/structure/spider/terrorweb/Crossed(atom/movable/AM, oldloc)
- ..()
- if(isliving(AM) && !isterrorspider(AM))
- var/mob/living/M = AM
+/obj/structure/spider/terrorweb/proc/on_atom_entered(datum/source, atom/movable/entered)
+ if(isliving(entered) && !isterrorspider(entered))
+ var/mob/living/M = entered
to_chat(M, "You get stuck in [src] for a moment.")
M.Weaken(8 SECONDS)
if(iscarbon(M))
diff --git a/code/modules/mob/living/simple_animal/hostile/terror_spiders/mother.dm b/code/modules/mob/living/simple_animal/hostile/terror_spiders/mother.dm
index d26e9d975db3..358894501a7f 100644
--- a/code/modules/mob/living/simple_animal/hostile/terror_spiders/mother.dm
+++ b/code/modules/mob/living/simple_animal/hostile/terror_spiders/mother.dm
@@ -139,7 +139,7 @@
name = "mother web"
desc = "This web is coated in pheromones which prevent spiderlings from passing it."
-/obj/structure/spider/terrorweb/mother/CanPass(atom/movable/mover, turf/target)
+/obj/structure/spider/terrorweb/mother/CanPass(atom/movable/mover, border_dir)
if(istype(mover, /obj/structure/spider/spiderling/terror_spiderling))
return FALSE
return ..()
diff --git a/code/modules/mob/living/simple_animal/hostile/venus_human_trap.dm b/code/modules/mob/living/simple_animal/hostile/venus_human_trap.dm
index 84ee41256fcf..2d79c3224119 100644
--- a/code/modules/mob/living/simple_animal/hostile/venus_human_trap.dm
+++ b/code/modules/mob/living/simple_animal/hostile/venus_human_trap.dm
@@ -37,11 +37,10 @@
mouse_opacity = MOUSE_OPACITY_ICON
desc = "A thick vine, painful to the touch."
-
-/obj/effect/ebeam/vine/Crossed(atom/movable/AM, oldloc)
- if(!isliving(AM))
+/obj/effect/ebeam/vine/on_atom_entered(datum/source, atom/movable/entered)
+ if(!isliving(entered))
return
- var/mob/living/L = AM
+ var/mob/living/L = entered
if(!("vines" in L.faction))
L.adjustBruteLoss(5)
to_chat(L, "You cut yourself on the thorny vines.")
diff --git a/code/modules/mob/living/simple_animal/parrot.dm b/code/modules/mob/living/simple_animal/parrot.dm
index 1d4cd2ecaf82..5962f11d651d 100644
--- a/code/modules/mob/living/simple_animal/parrot.dm
+++ b/code/modules/mob/living/simple_animal/parrot.dm
@@ -62,6 +62,7 @@
var/parrot_state = PARROT_WANDER //Hunt for a perch when created
var/parrot_sleep_max = 25 //The time the parrot sits while perched before looking around. Mosly a way to avoid the parrot's AI in process_ai() being run every single tick.
var/parrot_sleep_dur = 25 //Same as above, this is the var that physically counts down
+ var/wander_probability = 90
var/parrot_dam_zone = list("chest", "head", "l_arm", "l_leg", "r_arm", "r_leg") //For humans, select a bodypart to attack
var/parrot_speed = 5 //"Delay in world ticks between movement." according to byond. Yeah, that's BS but it does directly affect movement. Higher number = slower.
@@ -72,6 +73,8 @@
//Headset for Poly to yell at engineers :)
var/obj/item/radio/headset/ears = null
+ var/speech_probability = 10
+ var/hear_probability = 50
//The thing the parrot is currently interested in. This gets used for items the parrot wants to pick up, mobs it wants to steal from,
//mobs it wants to attack or mobs that have attacked it
@@ -81,6 +84,7 @@
//These vars store their preffered perch and if they dont have one, what they can use as a perch
var/obj/parrot_perch = null
var/list/desired_perches
+ var/leaving_perch_probability = 10
//Parrots are kleptomaniacs. This variable ... stores the item a parrot is holding.
var/obj/item/held_item = null
@@ -105,17 +109,22 @@
desired_perches = typecacheof(list(
/obj/machinery/clonepod,
/obj/machinery/computer,
- /obj/machinery/dna_scannernew,
+ /obj/machinery/photocopier,
/obj/machinery/nuclearbomb,
/obj/machinery/particle_accelerator,
- /obj/machinery/recharge_station,
- /obj/machinery/smartfridge,
- /obj/machinery/suit_storage_unit,
- /obj/machinery/tcomms,
+ /obj/machinery/atmospherics/portable,
+ /obj/machinery/smartfridge/foodcart,
+ /obj/machinery/message_server,
+ /obj/machinery/tcomms/relay,
/obj/machinery/teleport,
/obj/structure/computerframe,
/obj/structure/displaycase,
- /obj/structure/filingcabinet
+ /obj/structure/rack,
+ /obj/structure/closet/crate
+ )) - typecacheof(list(
+ /obj/machinery/computer/security/telescreen,
+ /obj/machinery/computer/cryopod,
+ /obj/machinery/computer/guestpass
))
/mob/living/simple_animal/parrot/add_strippable_element()
@@ -149,6 +158,7 @@
if(stat == CONSCIOUS && M.a_intent == "harm")
icon_state = "parrot_fly" //It is going to be flying regardless of whether it flees or attacks
+ ADD_TRAIT(src, TRAIT_FLYING, INNATE_TRAIT)
if(parrot_state == PARROT_PERCH)
parrot_sleep_dur = parrot_sleep_max //Reset it's sleep timer if it was perched
@@ -177,6 +187,7 @@
parrot_interest = user
parrot_state = PARROT_SWOOP|PARROT_FLEE
icon_state = "parrot_fly"
+ ADD_TRAIT(src, TRAIT_FLYING, INNATE_TRAIT)
drop_held_item(FALSE)
return
@@ -191,6 +202,7 @@
parrot_state = PARROT_WANDER //OWFUCK, Been shot! RUN LIKE HELL!
parrot_been_shot += 5
icon_state = "parrot_fly"
+ ADD_TRAIT(src, TRAIT_FLYING, INNATE_TRAIT)
drop_held_item(FALSE)
return
@@ -213,6 +225,7 @@
//Sprite and AI update for when a parrot gets pulled
if(pulledby && stat == CONSCIOUS)
icon_state = "parrot_fly"
+ ADD_TRAIT(src, TRAIT_FLYING, INNATE_TRAIT)
/mob/living/simple_animal/parrot/proc/update_available_channels()
available_channels.Cut()
@@ -263,7 +276,7 @@
Phrases that the parrot hears in mob/living/say() get added to speach_buffer.
Every once in a while, the parrot picks one of the lines from the buffer and replaces an element of the 'speech' list.
Then it clears the buffer to make sure they dont magically remember something from hours ago. */
- if(length(speech_buffer) && prob(10))
+ if(length(speech_buffer) && prob(speech_probability))
if(length(clean_speak))
clean_speak -= pick(clean_speak)
@@ -273,14 +286,10 @@
//-----SLEEPING
if(parrot_state == PARROT_PERCH)
if(parrot_perch && parrot_perch.loc != loc) //Make sure someone hasnt moved our perch on us
- if(parrot_perch in view(src))
- parrot_state = PARROT_SWOOP|PARROT_RETURN
- icon_state = "parrot_fly"
- return
- else
- parrot_state = PARROT_WANDER
- icon_state = "parrot_fly"
- return
+ parrot_state = (parrot_perch in view(src)) ? (PARROT_SWOOP|PARROT_RETURN) : PARROT_WANDER
+ icon_state = "parrot_fly"
+ ADD_TRAIT(src, TRAIT_FLYING, INNATE_TRAIT)
+ return
if(--parrot_sleep_dur) //Zzz
return
@@ -291,12 +300,21 @@
//Cycle through message modes for the headset
update_speak()
+ if(prob(leaving_perch_probability))
+ // Parrot no longer likes this perch and will try to find another one
+ parrot_perch = null
+ parrot_state = PARROT_WANDER
+ icon_state = "parrot_fly"
+ ADD_TRAIT(src, TRAIT_FLYING, INNATE_TRAIT)
+ return
+
//Search for item to steal
- parrot_interest = search_for_perch_and_item()
+ parrot_interest = search_for_perch_or_item()
if(parrot_interest)
custom_emote(EMOTE_VISIBLE, "looks in [parrot_interest]'s direction and takes flight.")
parrot_state = PARROT_SWOOP|PARROT_STEAL
icon_state = "parrot_fly"
+ ADD_TRAIT(src, TRAIT_FLYING, INNATE_TRAIT)
return
//-----WANDERING - This is basically a 'I dont know what to do yet' state
@@ -307,12 +325,12 @@
//Wander around aimlessly. This will help keep the loops from searches down
//and possibly move the mob into a new are in view of something they can use
- if(prob(90))
+ if(prob(wander_probability))
step(src, pick(GLOB.cardinal))
return
if(!held_item && !parrot_perch) //If we've got nothing to do.. look for something to do.
- var/atom/movable/AM = search_for_perch_and_item() //This handles checking through lists so we know it's either a perch or stealable item
+ var/atom/movable/AM = search_for_perch_or_item() //This handles checking through lists so we know it's either a perch or stealable item
if(AM)
if(isitem(AM) || isliving(AM)) //If stealable item
parrot_interest = AM
@@ -320,7 +338,7 @@
face_atom(AM)
custom_emote(EMOTE_VISIBLE, "turns and flies towards [parrot_interest].")
return
- else //Else it's a perch
+ else if(is_type_in_typecache(AM, desired_perches)) //Else if it's a perch
parrot_perch = AM
parrot_state = PARROT_SWOOP|PARROT_RETURN
return
@@ -335,8 +353,9 @@
return
else //Have an item but no perch? Find one!
- parrot_perch = search_for_perch_and_item()
- if(parrot_perch)
+ var/atom/movable/AM = search_for_perch_or_item()
+ if(is_type_in_typecache(AM, desired_perches))
+ parrot_perch = AM
parrot_state = PARROT_SWOOP|PARROT_RETURN
return
//-----STEALING
@@ -359,13 +378,13 @@
parrot_state = PARROT_SWOOP|PARROT_RETURN
return
- var/list/path_to_take = get_path_to(src, parrot_interest)
- if(length(path_to_take) <= 1) // The target is below us
+ var/list/path_to_take = get_path_to(src, parrot_interest, mintargetdist = 1)
+ if(!length(path_to_take)) // The target is unachievable
parrot_interest = null
parrot_state = PARROT_SWOOP|PARROT_RETURN
return
- walk_to(src, path_to_take[2], 0, parrot_speed)
+ walk_to(src, parrot_interest, 1, parrot_speed)
return
//-----RETURNING TO PERCH
@@ -382,15 +401,16 @@
drop_held_item()
parrot_state = PARROT_PERCH
icon_state = "parrot_sit"
+ REMOVE_TRAIT(src, TRAIT_FLYING, INNATE_TRAIT)
return
- var/list/path_to_take = get_path_to(src, parrot_perch)
- if(length(path_to_take) <= 1) // The target is below us
+ var/list/path_to_take = get_path_to(src, parrot_perch, mintargetdist = 1)
+ if(!length(path_to_take)) // The target is unachievable
parrot_perch = null
parrot_state = PARROT_WANDER
return
- walk_to(src, path_to_take[2], 0, parrot_speed)
+ walk_to(src, parrot_perch, 1, parrot_speed)
return
//-----FLEEING
@@ -469,10 +489,10 @@
//Because the most appropriate place to set icon_state is movement_delay(), clearly
return ..()
-/mob/living/simple_animal/parrot/proc/search_for_perch_and_item(list/stuff)
+/mob/living/simple_animal/parrot/proc/search_for_perch_or_item()
var/turf/my_turf = get_turf(src)
var/list/computed_paths = list()
- for(var/obj/O in view(src))
+ for(var/obj/O in shuffle(view(src)))
var/is_eligible = FALSE
if(!parrot_perch && is_type_in_typecache(O, desired_perches))
is_eligible = TRUE
@@ -489,8 +509,12 @@
var/turf/T = get_turf(O)
if(my_turf != T)
var/cache_id = "[my_turf.UID()]_[T.UID()]"
- computed_paths[cache_id] = computed_paths[cache_id] || get_path_to(src, T)
- if(!length(computed_paths[cache_id]))
+ var/list/path = computed_paths[cache_id] || get_path_to(src, T, mintargetdist = 1)
+ computed_paths[cache_id] = path
+ if(!length(path))
+ continue
+ var/turf/target_turf = path[length(path)]
+ if(!target_turf.Adjacent(O))
continue
return O
@@ -619,6 +643,7 @@
if(is_type_in_typecache(AM, desired_perches))
forceMove(AM.loc)
icon_state = "parrot_sit"
+ REMOVE_TRAIT(src, TRAIT_FLYING, INNATE_TRAIT)
return
to_chat(src, "There is no perch nearby to sit on.")
return
@@ -694,12 +719,12 @@
used_radios += ears
/mob/living/simple_animal/parrot/hear_say(list/message_pieces, verb = "says", italics = 0, mob/speaker = null, sound/speech_sound, sound_vol, sound_frequency, use_voice = TRUE)
- if(speaker != src && prob(50))
+ if(speaker != src && prob(hear_probability))
parrot_hear(html_decode(multilingual_to_message(message_pieces)))
..()
/mob/living/simple_animal/parrot/hear_radio(list/message_pieces, verb = "says", part_a, part_b, mob/speaker = null, hard_to_hear = 0, atom/follow_target, check_name_against)
- if(speaker != src && prob(50))
+ if(speaker != src && prob(hear_probability))
parrot_hear(html_decode(multilingual_to_message(message_pieces)))
..()
diff --git a/code/modules/mob/living/simple_animal/shade.dm b/code/modules/mob/living/simple_animal/shade.dm
index 7bc00e3cf489..260c85782487 100644
--- a/code/modules/mob/living/simple_animal/shade.dm
+++ b/code/modules/mob/living/simple_animal/shade.dm
@@ -69,13 +69,13 @@
if(istype(host_sword))
health = host_sword.obj_integrity
-/mob/living/simple_animal/shade/sword/bread
- name = "Bread spirit"
+/mob/living/simple_animal/shade/sword/generic_item
+ name = "Trapped spirit"
-/mob/living/simple_animal/shade/sword/bread/update_runechat_msg_location()
+/mob/living/simple_animal/shade/sword/generic_item/update_runechat_msg_location()
runechat_msg_location = loc.UID()
-/mob/living/simple_animal/shade/sword/bread/proc/handle_bread_deletion(source)
+/mob/living/simple_animal/shade/sword/generic_item/proc/handle_item_deletion(source)
SIGNAL_HANDLER
qdel(src)
diff --git a/code/modules/mob/living/simple_animal/simple_animal.dm b/code/modules/mob/living/simple_animal/simple_animal.dm
index 88ddc6189a2f..daec7a7b43eb 100644
--- a/code/modules/mob/living/simple_animal/simple_animal.dm
+++ b/code/modules/mob/living/simple_animal/simple_animal.dm
@@ -583,10 +583,10 @@
if(pulledby || shouldwakeup)
toggle_ai(AI_ON)
-/mob/living/simple_animal/onTransitZ(old_z, new_z)
+/mob/living/simple_animal/on_changed_z_level(turf/old_turf, turf/new_turf)
..()
- if(AIStatus == AI_Z_OFF)
- var/list/idle_mobs_on_old_z = LAZYACCESS(SSidlenpcpool.idle_mobs_by_zlevel, old_z)
+ if(AIStatus == AI_Z_OFF && old_turf)
+ var/list/idle_mobs_on_old_z = LAZYACCESS(SSidlenpcpool.idle_mobs_by_zlevel, old_turf.z)
LAZYREMOVE(idle_mobs_on_old_z, src)
toggle_ai(initial(AIStatus))
diff --git a/code/modules/mob/mob_movement.dm b/code/modules/mob/mob_movement.dm
index dedebe6f5492..6f1cd194d7f6 100644
--- a/code/modules/mob/mob_movement.dm
+++ b/code/modules/mob/mob_movement.dm
@@ -1,4 +1,4 @@
-/mob/CanPass(atom/movable/mover, turf/target)
+/mob/CanPass(atom/movable/mover, border_dir)
var/horizontal = FALSE
if(isliving(src))
var/mob/living/L = src
diff --git a/code/modules/mob/new_player/sprite_accessories/sprite_accessories.dm b/code/modules/mob/new_player/sprite_accessories/sprite_accessories.dm
index 77b6e6985f4c..aac84783a3be 100644
--- a/code/modules/mob/new_player/sprite_accessories/sprite_accessories.dm
+++ b/code/modules/mob/new_player/sprite_accessories/sprite_accessories.dm
@@ -465,6 +465,10 @@
name = "Rainbow Shirt"
icon_state = "shirt_rainbow"
+/datum/sprite_accessory/undershirt/shirt_wave
+ name = "Great Wave Shirt"
+ icon_state = "shirt_wave"
+
//end graphic shirts
//short sleeved
diff --git a/code/modules/power/engines/singularity/containment_field.dm b/code/modules/power/engines/singularity/containment_field.dm
index 0953ae2a4541..cfec3f374779 100644
--- a/code/modules/power/engines/singularity/containment_field.dm
+++ b/code/modules/power/engines/singularity/containment_field.dm
@@ -14,6 +14,13 @@
var/obj/machinery/field/generator/FG1 = null
var/obj/machinery/field/generator/FG2 = null
+/obj/machinery/field/containment/Initialize(mapload)
+ . = ..()
+ var/static/list/loc_connections = list(
+ COMSIG_ATOM_ENTERED = PROC_REF(on_atom_entered)
+ )
+ AddElement(/datum/element/connect_loc, loc_connections)
+
/obj/machinery/field/containment/Destroy()
if(FG1)// These checks are mostly in case a field is spawned in by accident.
FG1.fields -= src
@@ -57,12 +64,12 @@
else
..()
-/obj/machinery/field/containment/Crossed(mob/mover, oldloc)
- if(isliving(mover))
- shock_field(mover)
+/obj/machinery/field/containment/proc/on_atom_entered(datum/source, atom/movable/entered)
+ if(isliving(entered))
+ shock_field(entered)
- if(ismachinery(mover) || isstructure(mover) || ismecha(mover))
- bump_field(mover)
+ if(ismachinery(entered) || isstructure(entered) || ismecha(entered))
+ bump_field(entered)
/obj/machinery/field/containment/proc/set_master(master1, master2)
if(!master1 || !master2)
@@ -86,7 +93,7 @@
/obj/machinery/field
var/hasShocked = 0 //Used to add a delay between shocks. In some cases this used to crash servers by spawning hundreds of sparks every second.
-/obj/machinery/field/CanPass(atom/movable/mover, turf/target)
+/obj/machinery/field/CanPass(atom/movable/mover, border_dir)
if(hasShocked)
return 0
if(isliving(mover)) // Don't let mobs through
diff --git a/code/modules/power/engines/singularity/particle_accelerator/particle.dm b/code/modules/power/engines/singularity/particle_accelerator/particle.dm
index 12641cefbd82..a2bae7b95c33 100644
--- a/code/modules/power/engines/singularity/particle_accelerator/particle.dm
+++ b/code/modules/power/engines/singularity/particle_accelerator/particle.dm
@@ -24,10 +24,18 @@
/obj/effect/accelerated_particle/Initialize(mapload)
. = ..()
addtimer(CALLBACK(src, PROC_REF(propagate)), 1)
- RegisterSignal(src, COMSIG_CROSSED_MOVABLE, PROC_REF(try_irradiate))
- RegisterSignal(src, COMSIG_MOVABLE_CROSSED, PROC_REF(try_irradiate))
+ var/static/list/loc_connections = list(
+ COMSIG_ATOM_ENTERED = PROC_REF(try_irradiate)
+ )
+ AddElement(/datum/element/connect_loc, loc_connections)
+ RegisterSignal(src, COMSIG_MOVABLE_MOVED, PROC_REF(on_movable_moved))
QDEL_IN(src, movement_range)
+/obj/effect/accelerated_particle/proc/on_movable_moved(datum/source, old_location, direction, forced)
+ if(isturf(loc))
+ for(var/atom/A in loc)
+ try_irradiate(src, A)
+
/obj/effect/accelerated_particle/proc/try_irradiate(src, atom/A)
if(isliving(A))
var/mob/living/L = A
diff --git a/code/modules/power/engines/singularity/singularity.dm b/code/modules/power/engines/singularity/singularity.dm
index 32d9683db270..1d51905da0a8 100644
--- a/code/modules/power/engines/singularity/singularity.dm
+++ b/code/modules/power/engines/singularity/singularity.dm
@@ -38,6 +38,8 @@ GLOBAL_VAR_INIT(global_singulo_id, 1)
var/isnt_shutting_down = FALSE
/// Init list that has all the areas that we can possibly move to, to reduce processing impact
var/list/all_possible_areas = list()
+ var/datum/proximity_monitor/advanced/singulo/proximity_monitor
+
/// Id for monitoring.
var/singulo_id = 1
@@ -49,7 +51,7 @@ GLOBAL_VAR_INIT(global_singulo_id, 1)
energy = starting_energy
if(warps_projectiles)
- AddComponent(/datum/component/proximity_monitor/singulo, _radius = 10)
+ proximity_monitor = new(src, 10)
START_PROCESSING(SSobj, src)
GLOB.poi_list |= src
@@ -526,50 +528,6 @@ GLOBAL_VAR_INIT(global_singulo_id, 1)
if(prob(1))
mezzer()
-/datum/component/proximity_monitor/singulo
- field_checker_type = /obj/effect/abstract/proximity_checker/singulo
-
-/datum/component/proximity_monitor/singulo/create_single_prox_checker(turf/T, checker_type)
- . = ..()
- var/obj/effect/abstract/proximity_checker/singulo/S = .
- S.calibrate()
-
-/datum/component/proximity_monitor/singulo/recenter_prox_checkers()
- . = ..()
- for(var/obj/effect/abstract/proximity_checker/singulo/S as anything in proximity_checkers)
- S.calibrate()
-
-/obj/effect/abstract/proximity_checker/singulo
- var/angle_to_singulo
- var/distance_to_singulo
-
-/obj/effect/abstract/proximity_checker/singulo/proc/calibrate()
- angle_to_singulo = ATAN2(monitor.hasprox_receiver.y - y, monitor.hasprox_receiver.x - x)
- distance_to_singulo = get_dist(monitor.hasprox_receiver, src)
-
-/obj/effect/abstract/proximity_checker/singulo/Crossed(atom/movable/AM, oldloc)
- . = ..()
- if(!isprojectile(AM))
- return
- var/obj/item/projectile/P = AM
- var/distance = distance_to_singulo
- var/projectile_angle = P.Angle
- var/angle_to_projectile = angle_to_singulo
- if(angle_to_projectile == 180)
- angle_to_projectile = -180
- angle_to_projectile -= projectile_angle
- if(angle_to_projectile > 180)
- angle_to_projectile -= 360
- else if(angle_to_projectile < -180)
- angle_to_projectile += 360
-
- if(distance == 0)
- qdel(P)
- return
- projectile_angle += angle_to_projectile / (distance ** 2)
- P.damage += 10 / distance
- P.set_angle(projectile_angle)
-
/obj/singularity/proc/end_deadchat_plays()
move_self = TRUE
diff --git a/code/modules/power/engines/supermatter/supermatter.dm b/code/modules/power/engines/supermatter/supermatter.dm
index 5da953b53e88..f2d3a7d1e57a 100644
--- a/code/modules/power/engines/supermatter/supermatter.dm
+++ b/code/modules/power/engines/supermatter/supermatter.dm
@@ -851,7 +851,7 @@
playsound(get_turf(src), 'sound/effects/supermatter.ogg', 50, TRUE)
Consume(AM)
-/obj/machinery/atmospherics/supermatter_crystal/Bump(atom/A, yes)
+/obj/machinery/atmospherics/supermatter_crystal/Bump(atom/A)
..()
if(!istype(A, /obj/machinery/atmospherics/supermatter_crystal))
Bumped(A)
diff --git a/code/modules/power/engines/tesla/energy_ball.dm b/code/modules/power/engines/tesla/energy_ball.dm
index eef6897ee91e..b7a012e9c8aa 100644
--- a/code/modules/power/engines/tesla/energy_ball.dm
+++ b/code/modules/power/engines/tesla/energy_ball.dm
@@ -47,6 +47,10 @@
RegisterSignal(src, COMSIG_ATOM_ORBIT_BEGIN, PROC_REF(on_start_orbit))
RegisterSignal(src, COMSIG_ATOM_ORBIT_STOP, PROC_REF(on_stop_orbit))
RegisterSignal(parent_energy_ball, COMSIG_PARENT_QDELETING, PROC_REF(on_parent_delete))
+ var/static/list/loc_connections = list(
+ COMSIG_ATOM_ENTERED = PROC_REF(on_atom_entered)
+ )
+ AddElement(/datum/element/connect_loc, loc_connections)
. = ..()
if(!is_miniball)
set_light(10, 7, "#5e5edd")
@@ -135,6 +139,11 @@
sleep(0.5 SECONDS)
walk_towards(src, move_target, 0, 10)
+/obj/singularity/energy_ball/proc/on_atom_entered(datum/source, atom/movable/entered)
+ var/mob/living/living_entered = entered
+ if(istype(living_entered))
+ living_entered.dust()
+
/datum/move_with_corner
var/turf/start
var/turf/end
@@ -210,13 +219,6 @@
forceMove(target, direction)
return TRUE
-// This handles mobs crossing us. For us crossing mobs, see /mob/living/Crossed.
-// (It also dusts them.)
-/obj/singularity/energy_ball/Crossed(atom/thing)
- if(isliving(thing))
- var/mob/victim = thing
- victim.dust()
-
/obj/singularity/energy_ball/proc/handle_energy()
if(energy >= energy_to_raise)
energy_to_lower = energy_to_raise - 20
diff --git a/code/modules/power/generators/treadmill.dm b/code/modules/power/generators/treadmill.dm
index 6a6f68b8b5bd..f57b6ee3b4f3 100644
--- a/code/modules/power/generators/treadmill.dm
+++ b/code/modules/power/generators/treadmill.dm
@@ -16,26 +16,40 @@
var/list/mobs_running[0]
var/id = null // for linking to monitor
+ var/static/list/loc_connections = list(
+ COMSIG_ATOM_ENTERED = PROC_REF(on_atom_entered),
+ COMSIG_ATOM_EXITED = PROC_REF(on_atom_exited),
+ )
+
/obj/machinery/power/treadmill/Initialize(mapload)
. = ..()
+ on_anchor_changed()
+
+/obj/machinery/power/treadmill/proc/on_anchor_changed()
if(anchored)
connect_to_network()
+ AddElement(/datum/element/connect_loc, loc_connections)
+ else
+ disconnect_from_network()
+ RemoveElement(/datum/element/connect_loc)
/obj/machinery/power/treadmill/update_icon_state()
icon_state = speed ? "conveyor-1" : "conveyor0"
-/obj/machinery/power/treadmill/Crossed(mob/living/M, oldloc)
- if(anchored && !M.anchored)
- if(!istype(M) || M.dir != dir)
- throw_off(M)
- else
- mobs_running[M] = M.last_movement
- . = ..()
+/obj/machinery/power/treadmill/proc/on_atom_entered(datum/source, mob/living/crossed)
+ SIGNAL_HANDLER // COMSIG_ATOM_ENTERED
+ if(crossed.anchored || crossed.throwing)
+ return
-/obj/machinery/power/treadmill/Uncrossed(mob/living/M)
- if(anchored && istype(M))
- mobs_running -= M
- . = ..()
+ if(!istype(crossed) || crossed.dir != dir)
+ throw_off(crossed)
+ else
+ mobs_running[crossed] = crossed.last_movement
+
+/obj/machinery/power/treadmill/proc/on_atom_exited(mob/living/crossed)
+ SIGNAL_HANDLER // COMSIG_ATOM_EXITED
+ if(istype(crossed))
+ mobs_running -= crossed
/obj/machinery/power/treadmill/proc/throw_off(atom/movable/A)
// if 2fast, throw the person, otherwise they just slide off, if there's reasonable speed at all
@@ -98,10 +112,7 @@
/obj/machinery/power/treadmill/attackby__legacy__attackchain(obj/item/W, mob/user)
if(default_unfasten_wrench(user, W, time = 60))
- if(anchored)
- connect_to_network()
- else
- disconnect_from_network()
+ on_anchor_changed()
speed = 0
update_icon()
return
diff --git a/code/modules/power/lights.dm b/code/modules/power/lights.dm
index 0d5ba0573db2..659c6d4d7bdd 100644
--- a/code/modules/power/lights.dm
+++ b/code/modules/power/lights.dm
@@ -999,15 +999,19 @@
/obj/item/light/Initialize(mapload)
. = ..()
AddComponent(/datum/component/caltrop, force)
-
-/obj/item/light/Crossed(mob/living/L)
- if(istype(L) && has_gravity(loc))
- if(L.incorporeal_move || HAS_TRAIT(L, TRAIT_FLYING) || L.floating)
+ var/static/list/loc_connections = list(
+ COMSIG_ATOM_ENTERED = PROC_REF(on_atom_entered)
+ )
+ AddElement(/datum/element/connect_loc, loc_connections)
+
+/obj/item/light/proc/on_atom_entered(datum/source, atom/movable/entered)
+ var/mob/living/living_entered = entered
+ if(istype(living_entered) && has_gravity(loc))
+ if(living_entered.incorporeal_move || HAS_TRAIT(living_entered, TRAIT_FLYING) || living_entered.floating)
return
playsound(loc, 'sound/effects/glass_step.ogg', 50, TRUE)
if(status == LIGHT_BURNED || status == LIGHT_OK)
shatter()
- return ..()
/obj/item/light/decompile_act(obj/item/matter_decompiler/C, mob/user)
C.stored_comms["glass"] += 1
diff --git a/code/modules/projectiles/projectile/special_projectiles.dm b/code/modules/projectiles/projectile/special_projectiles.dm
index ec72d05df0b1..4e2aa0d68876 100644
--- a/code/modules/projectiles/projectile/special_projectiles.dm
+++ b/code/modules/projectiles/projectile/special_projectiles.dm
@@ -111,9 +111,7 @@
nodamage = 1
flag = "bullet"
-/obj/item/projectile/meteor/Bump(atom/A, yes)
- if(yes)
- return
+/obj/item/projectile/meteor/Bump(atom/A)
if(A == firer)
loc = A.loc
return
diff --git a/code/modules/projectiles/projectile_base.dm b/code/modules/projectiles/projectile_base.dm
index 0130b85e8ab7..8d8603f0dccf 100644
--- a/code/modules/projectiles/projectile_base.dm
+++ b/code/modules/projectiles/projectile_base.dm
@@ -138,6 +138,13 @@
/obj/item/projectile/New()
return ..()
+/obj/item/projectile/Initialize(mapload)
+ . = ..()
+ var/static/list/loc_connections = list(
+ COMSIG_ATOM_ENTERED = PROC_REF(on_atom_entered)
+ )
+ AddElement(/datum/element/connect_loc, loc_connections)
+
/obj/item/projectile/proc/Range()
range--
if(damage && tile_dropoff)
@@ -262,10 +269,7 @@
beam_index = point_cache
beam_segments[beam_index] = null
-/obj/item/projectile/Bump(atom/A, yes)
- if(!yes) //prevents double bumps.
- return
-
+/obj/item/projectile/Bump(atom/A)
if(check_ricochet(A) && check_ricochet_flag(A) && ricochets < ricochets_max && is_reflectable(REFLECTABILITY_PHYSICAL))
if(hitscan && ricochets_max > 10)
ricochets_max = 10 //I do not want a chucklefuck editing this higher, sorry.
@@ -432,10 +436,10 @@
xo = new_x - curloc.x
set_angle(get_angle(curloc, original))
-/obj/item/projectile/Crossed(atom/movable/AM, oldloc) //A mob moving on a tile with a projectile is hit by it.
- ..()
- if(isliving(AM) && AM.density && !checkpass(PASSMOB))
- Bump(AM, 1)
+/// A mob moving on a tile with a projectile is hit by it.
+/obj/item/projectile/proc/on_atom_entered(datum/source, atom/movable/entered)
+ if(isliving(entered) && entered.density && !checkpass(PASSMOB))
+ Bump(entered, 1)
/obj/item/projectile/Destroy()
if(hitscan)
diff --git a/code/modules/reagents/chemistry/reagents/toxins.dm b/code/modules/reagents/chemistry/reagents/toxins.dm
index 272f363aef90..ace0353d07b4 100644
--- a/code/modules/reagents/chemistry/reagents/toxins.dm
+++ b/code/modules/reagents/chemistry/reagents/toxins.dm
@@ -1075,6 +1075,16 @@
else if(istype(O, /obj/structure/spacevine))
var/obj/structure/spacevine/SV = O
SV.on_chem_effect(src)
+ else if(istype(O, /obj/item/toy/plushie/dionaplushie))
+ var/turf/T = get_turf(O)
+ var/obj/item/toy/plushie/dionaplushie/DP = O
+ if(DP.grenade)
+ DP.explosive_betrayal(DP.grenade)
+ return
+ new /obj/item/toy/plushie/nymphplushie(T)
+ new /obj/item/toy/plushie/nymphplushie(T)
+ DP.visible_message("The diona plushie splits apart!")
+ qdel(DP)
/datum/reagent/glyphosate/reaction_mob(mob/living/M, method = REAGENT_TOUCH, volume)
if(isliving(M))
@@ -1118,6 +1128,9 @@
if(istype(O, /obj/effect/decal/cleanable/ants))
O.visible_message("The ants die.")
qdel(O)
+ if(istype(O, /obj/item/toy/plushie/kidanplushie))
+ var/obj/item/toy/plushie/kidanplushie/stupidbug = O
+ stupidbug.make_cry()
/datum/reagent/pestkiller/reaction_mob(mob/living/M, method = REAGENT_TOUCH, volume)
if(isliving(M))
diff --git a/code/modules/reagents/reagent_containers/glass_containers.dm b/code/modules/reagents/reagent_containers/glass_containers.dm
index 496f385e4dbf..1484b49792e2 100644
--- a/code/modules/reagents/reagent_containers/glass_containers.dm
+++ b/code/modules/reagents/reagent_containers/glass_containers.dm
@@ -129,6 +129,13 @@
var/obj/item/assembly_holder/assembly = null
var/can_assembly = 1
+/obj/item/reagent_containers/glass/beaker/Initialize(mapload)
+ . = ..()
+ var/static/list/loc_connections = list(
+ COMSIG_ATOM_ENTERED = PROC_REF(on_atom_entered),
+ )
+ AddElement(/datum/element/connect_loc, loc_connections)
+
/obj/item/reagent_containers/glass/beaker/examine(mob/user)
. = ..()
if(assembly)
@@ -187,9 +194,9 @@
if(assembly)
assembly.HasProximity(AM)
-/obj/item/reagent_containers/glass/beaker/Crossed(atom/movable/AM, oldloc)
+/obj/item/reagent_containers/glass/beaker/proc/on_atom_entered(datum/source, atom/movable/entered)
if(assembly)
- assembly.Crossed(AM, oldloc)
+ assembly.on_atom_entered(source, entered)
/obj/item/reagent_containers/glass/beaker/on_found(mob/finder) //for mousetraps
if(assembly)
diff --git a/code/modules/reagents/reagent_containers/syringes.dm b/code/modules/reagents/reagent_containers/syringes.dm
index dc79be44db93..41e0f9979f77 100644
--- a/code/modules/reagents/reagent_containers/syringes.dm
+++ b/code/modules/reagents/reagent_containers/syringes.dm
@@ -22,6 +22,11 @@
mode = SYRINGE_INJECT
update_icon()
+ var/static/list/loc_connections = list(
+ COMSIG_ATOM_ENTERED = PROC_REF(on_atom_entered),
+ )
+ AddElement(/datum/element/connect_loc, loc_connections)
+
/obj/item/reagent_containers/syringe/on_reagent_change()
update_icon()
@@ -181,7 +186,10 @@
M.update_inv_l_hand()
M.update_inv_r_hand()
-/obj/item/reagent_containers/syringe/Crossed(mob/living/carbon/human/H, oldloc)
+/obj/item/reagent_containers/syringe/proc/on_atom_entered(datum/source, atom/movable/entered)
+ SIGNAL_HANDLER // COMSIG_ATOM_ENTERED
+
+ var/mob/living/carbon/human/H = entered
if(!istype(H) || !H.reagents || HAS_TRAIT(H, TRAIT_PIERCEIMMUNE) || ismachineperson(H))
return
diff --git a/code/modules/reagents/reagent_dispenser.dm b/code/modules/reagents/reagent_dispenser.dm
index 67f4a1ae1beb..55dfca88312e 100644
--- a/code/modules/reagents/reagent_dispenser.dm
+++ b/code/modules/reagents/reagent_dispenser.dm
@@ -104,6 +104,13 @@
var/obj/item/assembly_holder/rig = null
var/accepts_rig = 1
+/obj/structure/reagent_dispensers/fueltank/Initialize(mapload)
+ . = ..()
+ var/static/list/loc_connections = list(
+ COMSIG_ATOM_ENTERED = PROC_REF(on_atom_entered),
+ )
+ AddElement(/datum/element/connect_loc, loc_connections)
+
/obj/structure/reagent_dispensers/fueltank/Destroy()
QDEL_NULL(rig)
return ..()
@@ -209,9 +216,9 @@
if(rig)
rig.HasProximity(AM)
-/obj/structure/reagent_dispensers/fueltank/Crossed(atom/movable/AM, oldloc)
+/obj/structure/reagent_dispensers/fueltank/proc/on_atom_entered(datum/source, atom/movable/entered)
if(rig)
- rig.Crossed(AM, oldloc)
+ rig.on_atom_entered(source, entered)
/obj/structure/reagent_dispensers/fueltank/hear_talk(mob/living/M, list/message_pieces)
if(rig)
diff --git a/code/modules/recycling/conveyor2.dm b/code/modules/recycling/conveyor2.dm
index 61f1f912f080..03755059d7c7 100644
--- a/code/modules/recycling/conveyor2.dm
+++ b/code/modules/recycling/conveyor2.dm
@@ -43,6 +43,11 @@ GLOBAL_LIST_EMPTY(conveyor_switches)
var/obj/machinery/conveyor_switch/S = I
S.link_conveyers(src)
+ var/static/list/loc_connections = list(
+ COMSIG_ATOM_ENTERED = PROC_REF(on_atom_entered)
+ )
+ AddElement(/datum/element/connect_loc, loc_connections)
+
/obj/machinery/conveyor/Destroy()
GLOB.conveyor_belts -= src
return ..()
@@ -199,10 +204,9 @@ GLOBAL_LIST_EMPTY(conveyor_switches)
else if(still_stuff_to_move && !speed_process)
makeSpeedProcess()
-/obj/machinery/conveyor/Crossed(atom/movable/AM, oldloc)
- if(!speed_process && !AM.anchored)
+/obj/machinery/conveyor/proc/on_atom_entered(datum/source, atom/movable/entered)
+ if(!speed_process && !entered.anchored)
makeSpeedProcess()
- ..()
/obj/machinery/conveyor/proc/move_thing(atom/movable/AM)
affecting.Remove(AM)
diff --git a/code/modules/recycling/disposal.dm b/code/modules/recycling/disposal.dm
index 6c478825d6bc..0ea0f0e2f01e 100644
--- a/code/modules/recycling/disposal.dm
+++ b/code/modules/recycling/disposal.dm
@@ -613,7 +613,7 @@
H.vent_gas(loc)
qdel(H)
-/obj/machinery/disposal/CanPass(atom/movable/mover, turf/target)
+/obj/machinery/disposal/CanPass(atom/movable/mover, border_dir)
if(isitem(mover) && mover.throwing)
var/obj/item/I = mover
if(isprojectile(I))
diff --git a/code/modules/recycling/sortingmachinery.dm b/code/modules/recycling/sortingmachinery.dm
index d1fa9d14eb55..2ac19610aabc 100644
--- a/code/modules/recycling/sortingmachinery.dm
+++ b/code/modules/recycling/sortingmachinery.dm
@@ -294,7 +294,7 @@
/obj/machinery/disposal/delivery_chute/update()
return
-/obj/machinery/disposal/delivery_chute/CanPass(atom/movable/mover, turf/target)
+/obj/machinery/disposal/delivery_chute/CanPass(atom/movable/mover, border_dir)
// If the mover is a thrownthing passing through space, remove its thrown datum,
// ingest it like normal, and mark the chute as not passible.
// This prevents the mover from Entering the chute's turf
diff --git a/code/modules/ruins/lavalandruin_code/sin_ruins.dm b/code/modules/ruins/lavalandruin_code/sin_ruins.dm
index b6281e66fa3b..cfa4c18f3c8b 100644
--- a/code/modules/ruins/lavalandruin_code/sin_ruins.dm
+++ b/code/modules/ruins/lavalandruin_code/sin_ruins.dm
@@ -140,7 +140,7 @@
icon = 'icons/mob/blob.dmi'
color = rgb(145, 150, 0)
-/obj/effect/gluttony/CanPass(atom/movable/mover, turf/target)//So bullets will fly over and stuff.
+/obj/effect/gluttony/CanPass(atom/movable/mover, border_dir)//So bullets will fly over and stuff.
if(ishuman(mover))
var/mob/living/carbon/human/H = mover
if(H.nutrition >= NUTRITION_LEVEL_FAT || HAS_TRAIT(H, TRAIT_FAT))
diff --git a/code/modules/ruins/objects_and_mobs/necropolis_gate.dm b/code/modules/ruins/objects_and_mobs/necropolis_gate.dm
index c0058b3082e8..af173186dcc2 100644
--- a/code/modules/ruins/objects_and_mobs/necropolis_gate.dm
+++ b/code/modules/ruins/objects_and_mobs/necropolis_gate.dm
@@ -48,6 +48,12 @@
dais_overlay.layer = CLOSED_TURF_LAYER
add_overlay(dais_overlay)
+ var/static/list/loc_connections = list(
+ COMSIG_ATOM_EXIT = PROC_REF(on_atom_exit),
+ )
+
+ AddElement(/datum/element/connect_loc, loc_connections)
+
/obj/structure/necropolis_gate/Destroy()
qdel(sight_blocker, TRUE)
return ..()
@@ -55,15 +61,16 @@
/obj/structure/necropolis_gate/singularity_pull()
return
-/obj/structure/necropolis_gate/CanPass(atom/movable/mover, turf/target)
- if(get_dir(loc, target) == dir)
+/obj/structure/necropolis_gate/CanPass(atom/movable/mover, border_dir)
+ if(border_dir == dir)
return !density
return TRUE
-/obj/structure/necropolis_gate/CheckExit(atom/movable/O, target)
- if(get_dir(O.loc, target) == dir)
- return !density
- return TRUE
+/obj/structure/necropolis_gate/proc/on_atom_exit(datum/source, atom/movable/leaving, direction)
+ SIGNAL_HANDLER // COMSIG_ATOM_EXIT
+
+ if(direction == dir && density)
+ return COMPONENT_ATOM_BLOCK_EXIT
/obj/structure/opacity_blocker
icon = 'icons/effects/96x96.dmi'
diff --git a/code/modules/shuttle/on_move.dm b/code/modules/shuttle/on_move.dm
index e174827f4efe..9fea2a3a4a1d 100644
--- a/code/modules/shuttle/on_move.dm
+++ b/code/modules/shuttle/on_move.dm
@@ -2,7 +2,7 @@
/atom/movable/proc/onShuttleMove(turf/oldT, turf/T1, rotation, mob/caller)
var/turf/newT = get_turf(src)
if(newT.z != oldT.z)
- onTransitZ(oldT.z, newT.z)
+ on_changed_z_level(oldT, newT)
if(light)
update_light()
if(rotation)
diff --git a/code/modules/surgery/organs/organ.dm b/code/modules/surgery/organs/organ.dm
index 3cbb358b23a1..4d2b13883ba6 100644
--- a/code/modules/surgery/organs/organ.dm
+++ b/code/modules/surgery/organs/organ.dm
@@ -262,7 +262,14 @@
var/obj/item/organ/external/affected = owner.get_organ(parent_organ)
if(affected) affected.internal_organs -= src
- forceMove(get_turf(owner))
+ // In-game, organs will be moved to their parent turf.
+ // During ghost-mob creation, we toss the organs
+ // after we're done generating the sprite with them,
+ // so to nullspace they go.
+ if(get_turf(owner))
+ forceMove(get_turf(owner))
+ else
+ moveToNullspace()
START_PROCESSING(SSobj, src)
if(owner && vital && is_primary_organ()) // I'd do another check for species or whatever so that you couldn't "kill" an IPC by removing a human head from them, but it doesn't matter since they'll come right back from the dead
diff --git a/code/modules/unit_tests/_unit_tests.dm b/code/modules/unit_tests/_unit_tests.dm
deleted file mode 100644
index eea46b9e67f5..000000000000
--- a/code/modules/unit_tests/_unit_tests.dm
+++ /dev/null
@@ -1,34 +0,0 @@
-//include unit test files in this module in this ifdef
-//Keep this sorted alphabetically
-
-#ifdef UNIT_TESTS
-#include "atmos\test_ventcrawl.dm"
-#include "games\test_cards.dm"
-#include "jobs\test_job_globals.dm"
-#include "aicard_icons.dm"
-#include "announcements.dm"
-#include "areas_apcs.dm"
-#include "component_tests.dm"
-#include "config_sanity.dm"
-#include "crafting_lists.dm"
-#include "element_tests.dm"
-#include "emotes.dm"
-#include "init_sanity.dm"
-#include "log_format.dm"
-#include "map_templates.dm"
-#include "map_tests.dm"
-#include "missing_icons.dm"
-#include "origin_tech.dm"
-#include "purchase_reference_test.dm"
-#include "reagent_id_typos.dm"
-#include "rustg_version.dm"
-#include "spawn_humans.dm"
-#include "spell_targeting_test.dm"
-#include "sql.dm"
-#include "status_effect_ids.dm"
-#include "subsystem_init.dm"
-#include "subsystem_metric_sanity.dm"
-#include "test_runner.dm"
-#include "timer_sanity.dm"
-#include "unit_test.dm"
-#endif
diff --git a/code/modules/unit_tests/atmos/test_ventcrawl.dmm b/code/modules/unit_tests/atmos/test_ventcrawl.dmm
deleted file mode 100644
index 9b9a7be6af91..000000000000
--- a/code/modules/unit_tests/atmos/test_ventcrawl.dmm
+++ /dev/null
@@ -1,7 +0,0 @@
-"a" = (/obj/machinery/atmospherics/unary/vent_pump/on{dir = 4},/turf/simulated/floor,/area/space)
-"d" = (/obj/machinery/atmospherics/unary/vent_pump/on{dir = 8},/turf/simulated/floor,/area/space)
-"X" = (/obj/machinery/atmospherics/pipe/simple/visible{dir = 4},/obj/structure/table,/turf/simulated/floor,/area/space)
-
-(1,1,1) = {"
-aXd
-"}
diff --git a/code/modules/unit_tests/crafting_lists.dm b/code/modules/unit_tests/crafting_lists.dm
deleted file mode 100644
index 43ec7bc1036c..000000000000
--- a/code/modules/unit_tests/crafting_lists.dm
+++ /dev/null
@@ -1,5 +0,0 @@
-/datum/unit_test/crafting_lists/Run()
- for(var/I in subtypesof(/datum/crafting_recipe))
- var/datum/crafting_recipe/C = new I()
- if(!islist(C.result))
- Fail("Expected a list for the 'result' of [C.type].")
diff --git a/code/modules/unit_tests/rustg_version.dm b/code/modules/unit_tests/rustg_version.dm
deleted file mode 100644
index 0a259990b977..000000000000
--- a/code/modules/unit_tests/rustg_version.dm
+++ /dev/null
@@ -1,4 +0,0 @@
-/datum/unit_test/rustg_version/Run()
- var/library_version = rustg_get_version()
- if(library_version != RUST_G_VERSION)
- Fail("Invalid RUSTG Version. Library is [library_version], but in-code API is [RUST_G_VERSION]")
diff --git a/code/modules/unit_tests/sql.dm b/code/modules/unit_tests/sql.dm
deleted file mode 100644
index e9bbf7d1ebe0..000000000000
--- a/code/modules/unit_tests/sql.dm
+++ /dev/null
@@ -1,10 +0,0 @@
-// Unit test to check SQL version has been updated properly.,
-/datum/unit_test/sql_version/Run()
- // Check if the SQL version set in the code is equal to the CI DB config
- if(GLOB.configuration.database.version != SQL_VERSION)
- Fail("SQL version error: Game is running V[SQL_VERSION] but config is V[GLOB.configuration.database.version]. You may need to update the example config.")
-
- if(SSdbcore.total_errors > 0)
- Fail("SQL errors occurred on startup. Please fix them.")
-
-
diff --git a/code/modules/unit_tests/subsystem_metric_sanity.dm b/code/modules/unit_tests/subsystem_metric_sanity.dm
deleted file mode 100644
index 250e6b4cc394..000000000000
--- a/code/modules/unit_tests/subsystem_metric_sanity.dm
+++ /dev/null
@@ -1,22 +0,0 @@
-// Unit test to ensure SS metrics are valid
-/datum/unit_test/subsystem_metric_sanity/Run()
- for(var/datum/controller/subsystem/SS in Master.subsystems)
- var/list/data = SS.get_metrics()
- if(length(data) != 4)
- Fail("SS[SS.ss_id] has invalid metrics data!")
- continue
- if(isnull(data["cost"]))
- Fail("SS[SS.ss_id] has invalid metrics data! No 'cost' found in [json_encode(data)]")
- continue
- if(isnull(data["tick_usage"]))
- Fail("SS[SS.ss_id] has invalid metrics data! No 'tick_usage' found in [json_encode(data)]")
- continue
- if(isnull(data["custom"]))
- Fail("SS[SS.ss_id] has invalid metrics data! No 'custom' found in [json_encode(data)]")
- continue
- if(!islist(data["custom"]))
- Fail("SS[SS.ss_id] has invalid metrics data! 'custom' is not a list in [json_encode(data)]")
- continue
- if(isnull(data["sleep_count"]))
- Fail("SS[SS.ss_id] has invalid metrics data! No 'sleep_count' found in [json_encode(data)]")
- continue
diff --git a/code/modules/unit_tests/timer_sanity.dm b/code/modules/unit_tests/timer_sanity.dm
deleted file mode 100644
index d92323a5253f..000000000000
--- a/code/modules/unit_tests/timer_sanity.dm
+++ /dev/null
@@ -1,3 +0,0 @@
-/datum/unit_test/timer_sanity/Run()
- if(SStimer.bucket_count < 0)
- Fail("SStimer is going into negative bucket count from something")
diff --git a/code/modules/unit_tests/unit_test.dm b/code/modules/unit_tests/unit_test.dm
deleted file mode 100644
index 6ef3c82d85ca..000000000000
--- a/code/modules/unit_tests/unit_test.dm
+++ /dev/null
@@ -1,40 +0,0 @@
-/*
-Usage:
-Override /Run() to run your test code
-Call Fail() to fail the test (You should specify a reason)
-You may use /New() and /Destroy() for setup/teardown respectively
-You can use the run_loc_bottom_left and run_loc_top_right to get turfs for testing
-*/
-
-/datum/unit_test
- //Bit of metadata for the future maybe
- var/list/procs_tested
-
- //usable vars
- var/turf/run_loc_bottom_left
- var/turf/run_loc_top_right
-
- //internal shit
- var/succeeded = TRUE
- var/list/fail_reasons
-
-/datum/unit_test/New()
- run_loc_bottom_left = locate(1, 1, 1)
- run_loc_top_right = locate(5, 5, 1)
-
-/datum/unit_test/Destroy()
- //clear the test area
- for(var/atom/movable/AM in block(run_loc_bottom_left, run_loc_top_right))
- qdel(AM)
- return ..()
-
-/datum/unit_test/proc/Run()
- Fail("Run() called parent or not implemented")
-
-/datum/unit_test/proc/Fail(reason = "No reason")
- succeeded = FALSE
-
- if(!istext(reason))
- reason = "FORMATTED: [reason != null ? reason : "NULL"]"
-
- LAZYADD(fail_reasons, reason)
diff --git a/code/modules/vehicle/vehicle.dm b/code/modules/vehicle/vehicle.dm
index c1484bdaa251..4f11e43c4ffc 100644
--- a/code/modules/vehicle/vehicle.dm
+++ b/code/modules/vehicle/vehicle.dm
@@ -36,7 +36,7 @@
return ..()
// So that beepsky can't push the janicart
-/obj/vehicle/CanPass(atom/movable/mover, turf/target)
+/obj/vehicle/CanPass(atom/movable/mover, border_dir)
if(istype(mover) && mover.checkpass(PASSMOB))
return TRUE
else
diff --git a/code/tests/_game_test.dm b/code/tests/_game_test.dm
new file mode 100644
index 000000000000..784540d7759a
--- /dev/null
+++ b/code/tests/_game_test.dm
@@ -0,0 +1,96 @@
+/// For advanced cases, fail unconditionally but don't return (so a test can return multiple results)
+#define TEST_FAIL(reason) (Fail(reason || "No reason", __FILE__, __LINE__))
+
+/// Asserts that a condition is true
+/// If the condition is not true, fails the test
+#define TEST_ASSERT(assertion, reason) if(!(assertion)) { return Fail("Assertion failed: [reason || "No reason"]", __FILE__, __LINE__) }
+
+#define TEST_ASSERT_NOT(assertion, reason) if(assertion) { return Fail("Assertion failed: [reason || "No reason"]", __FILE__, __LINE__) }
+
+/// Asserts that a parameter is not null
+#define TEST_ASSERT_NOTNULL(a, reason) if(isnull(a)) { return Fail("Expected non-null value: [reason || "No reason"]", __FILE__, __LINE__) }
+
+/// Asserts that a parameter is null
+#define TEST_ASSERT_NULL(a, reason) if(!isnull(a)) { return Fail("Expected null value but received [a]: [reason || "No reason"]", __FILE__, __LINE__) }
+
+/// Asserts that the two parameters passed are equal, fails otherwise
+/// Optionally allows an additional message in the case of a failure
+#define TEST_ASSERT_EQUAL(a, b, message) do { \
+ var/lhs = ##a; \
+ var/rhs = ##b; \
+ if(lhs != rhs) { \
+ return Fail("Expected [isnull(lhs) ? "null" : lhs] to be equal to [isnull(rhs) ? "null" : rhs].[message ? " [message]" : ""]", __FILE__, __LINE__); \
+ } \
+} while(FALSE)
+
+/// Asserts that the two parameters passed are not equal, fails otherwise
+/// Optionally allows an additional message in the case of a failure
+#define TEST_ASSERT_NOTEQUAL(a, b, message) do { \
+ var/lhs = ##a; \
+ var/rhs = ##b; \
+ if(lhs == rhs) { \
+ return Fail("Expected [isnull(lhs) ? "null" : lhs] to not be equal to [isnull(rhs) ? "null" : rhs].[message ? " [message]" : ""]", __FILE__, __LINE__); \
+ } \
+} while(FALSE)
+
+/**
+ * Usage:
+ *
+ * - Override /Run() to run your test code
+ * - Call Fail() to fail the test (You should specify a reason)
+ * - You may use /New() and /Destroy() for setup/teardown respectively
+ * - You can use the run_loc_bottom_left and run_loc_top_right to get turfs for testing
+ *
+**/
+/datum/game_test
+ //Bit of metadata for the future maybe
+ var/list/procs_tested
+
+ //usable vars
+ var/turf/run_loc_bottom_left
+ var/turf/run_loc_top_right
+
+ //internal shit
+ var/succeeded = TRUE
+ var/list/allocated
+ var/list/fail_reasons
+
+/datum/game_test/New()
+ run_loc_bottom_left = locate(1, 1, 1)
+ run_loc_top_right = locate(5, 5, 1)
+
+/datum/game_test/Destroy()
+ QDEL_LIST_CONTENTS(allocated)
+ //clear the test area
+ for(var/atom/movable/AM in block(run_loc_bottom_left, run_loc_top_right))
+ qdel(AM)
+ return ..()
+
+/datum/game_test/proc/Run()
+ Fail("Run() called parent or not implemented")
+
+/datum/game_test/proc/Fail(reason = "No reason", file = "OUTDATED_TEST", line = 1)
+ succeeded = FALSE
+
+ if(!istext(reason))
+ reason = "FORMATTED: [reason != null ? reason : "NULL"]"
+
+ LAZYADD(fail_reasons, list(list(reason, file, line)))
+
+/// Allocates an instance of the provided type, and places it somewhere in an available loc
+/// Instances allocated through this proc will be destroyed when the test is over
+/datum/game_test/proc/allocate(type, ...)
+ var/list/arguments = args.Copy(2)
+ if(ispath(type, /atom))
+ if(!arguments.len)
+ arguments = list(run_loc_bottom_left)
+ else if(arguments[1] == null)
+ arguments[1] = run_loc_bottom_left
+ var/instance
+ // Byond will throw an index out of bounds if arguments is empty in that arglist call. Sigh
+ if(length(arguments))
+ instance = new type(arglist(arguments))
+ else
+ instance = new type()
+ LAZYADD(allocated, instance)
+ return instance
diff --git a/code/tests/_game_test_puppeteer.dm b/code/tests/_game_test_puppeteer.dm
new file mode 100644
index 000000000000..e9f560fe86aa
--- /dev/null
+++ b/code/tests/_game_test_puppeteer.dm
@@ -0,0 +1,68 @@
+/**
+ * A testing object used to control mobs in game tests.
+ *
+ * Puppeteers provide an easy way to create mobs and objects,
+ * perform interactions in the same way that a player would,
+ * and check the state of the mob during tests.
+ */
+/datum/test_puppeteer
+ var/mob/living/carbon/puppet
+ var/datum/game_test/origin_test
+
+/datum/test_puppeteer/New(datum/game_test/origin_test_, carbon_type = /mob/living/carbon/human, turf/initial_location)
+ if(!ispath(carbon_type, /mob/living/carbon/human))
+ origin_test.Fail("unexpected puppeteer carbon type [carbon_type]", __FILE__, __LINE__)
+
+ if(!initial_location)
+ initial_location = locate(179, 136, 1) // Center of admin testing area
+ origin_test = origin_test_
+ puppet = origin_test.allocate(carbon_type, initial_location)
+ var/datum/mind/new_mind = new("interaction_test_[puppet.UID()]")
+ new_mind.transfer_to(puppet)
+
+/datum/test_puppeteer/proc/spawn_puppet_nearby(carbon_type = /mob/living/carbon/human)
+ for(var/turf/T in RANGE_TURFS(1, puppet.loc))
+ if(!is_blocked_turf(T, exclude_mobs = FALSE))
+ return new/datum/test_puppeteer(origin_test, carbon_type, T)
+
+ origin_test.Fail("could not spawn puppeteer near [src]")
+
+/datum/test_puppeteer/proc/spawn_obj_in_hand(obj_type)
+ var/obj/new_obj = origin_test.allocate(obj_type, null)
+ if(puppet.put_in_hands(new_obj))
+ return new_obj
+
+ origin_test.Fail("could not spawn obj [obj_type] in hand of [puppet]")
+
+/datum/test_puppeteer/proc/spawn_obj_nearby(obj_type)
+ for(var/turf/T in RANGE_TURFS(1, puppet.loc))
+ if(!is_blocked_turf(T, exclude_mobs = FALSE))
+ return origin_test.allocate(obj_type, T)
+
+ origin_test.Fail("could not spawn obj [obj_type] near [src]")
+
+/datum/test_puppeteer/proc/click_on(target, params)
+ var/datum/test_puppeteer/puppet_target = target
+ sleep(max(puppet.next_click, puppet.next_move) - world.time + 1)
+ if(istype(puppet_target))
+ puppet.ClickOn(puppet_target.puppet, params)
+ return
+
+ puppet.ClickOn(target, params)
+
+/datum/test_puppeteer/proc/spawn_mob_nearby(mob_type)
+ for(var/turf/T in RANGE_TURFS(1, puppet))
+ if(!is_blocked_turf(T, exclude_mobs = FALSE))
+ var/mob/new_mob = origin_test.allocate(mob_type, T)
+ return new_mob
+
+/datum/test_puppeteer/proc/check_attack_log(snippet)
+ for(var/log_text in puppet.attack_log_old)
+ if(findtextEx(log_text, snippet))
+ return TRUE
+
+/datum/test_puppeteer/proc/set_intent(new_intent)
+ puppet.a_intent_change(new_intent)
+
+/datum/test_puppeteer/proc/rejuvenate()
+ puppet.rejuvenate()
diff --git a/code/modules/unit_tests/atmos/test_ventcrawl.dm b/code/tests/atmos/test_ventcrawl.dm
similarity index 69%
rename from code/modules/unit_tests/atmos/test_ventcrawl.dm
rename to code/tests/atmos/test_ventcrawl.dm
index b4b153d19535..fd4ca7c4e38a 100644
--- a/code/modules/unit_tests/atmos/test_ventcrawl.dm
+++ b/code/tests/atmos/test_ventcrawl.dm
@@ -1,4 +1,4 @@
-/datum/unit_test/ventcrawl
+/datum/game_test/ventcrawl
var/mob/living/simple_animal/slime = null
var/obj/machinery/vent = null
var/obj/structure/table/table = null
@@ -6,7 +6,7 @@
/datum/milla_safe/ventcrawl_test_setup
-/datum/milla_safe/ventcrawl_test_setup/on_run(datum/unit_test/ventcrawl/test)
+/datum/milla_safe/ventcrawl_test_setup/on_run(datum/game_test/ventcrawl/test)
// This setup creates turfs that initialize themselves in MILLA on creation, which is why we need to be MILLA-safe.
var/datum/map_template/template = GLOB.map_templates["test_ventcrawl.dmm"]
if(!template.load(test.run_loc_bottom_left))
@@ -17,13 +17,13 @@
test.table = test.find_spawned_test_object(get_step(test.run_loc_bottom_left, EAST), /obj/structure/table)
test.setup_complete = TRUE
-/datum/unit_test/ventcrawl/proc/find_spawned_test_object(turf/location as turf, test_object_type)
+/datum/game_test/ventcrawl/proc/find_spawned_test_object(turf/location as turf, test_object_type)
for(var/content in location.contents)
if(istype(content, test_object_type))
return content
- Fail("Couldn't find spawned test object of type: [test_object_type].")
+ TEST_FAIL("Couldn't find spawned test object of type: [test_object_type].")
-/datum/unit_test/ventcrawl/Run()
+/datum/game_test/ventcrawl/Run()
var/datum/milla_safe/ventcrawl_test_setup/milla = new()
milla.invoke_async(src)
while(!setup_complete)
@@ -31,20 +31,16 @@
// Enter vent
vent.AltClick(slime)
- if(slime.loc != vent)
- Fail("Failed to crawl into vent.")
+ TEST_ASSERT_EQUAL(slime.loc, vent, "failed to crawl into vent.")
// Movement
slime.loc.relaymove(slime, EAST)
- if(slime.loc == vent)
- Fail("Failed to step EAST while wentcrawling.")
+ TEST_ASSERT_NOTEQUAL(slime.loc, vent, "failed to step EAST while ventcrawling.")
// Try to flip table on top of pipe, while inside pipe (shouldn't work)
table.AltShiftClick(slime)
- if(table.flipped)
- Fail("Shouldn't be possible to flip structures while inside vent.")
+ TEST_ASSERT_NOT(table.flipped, "Shouldn't be possible to flip structures while inside vent.")
// Exit vent
slime.loc.relaymove(slime, EAST)
- if(!isturf(slime.loc))
- Fail("Wasn't able to ventcrawl out of vent.")
+ TEST_ASSERT(isturf(slime.loc), "wasn't able to ventcrawl out of vent.")
diff --git a/code/tests/atmos/test_ventcrawl.dmm b/code/tests/atmos/test_ventcrawl.dmm
new file mode 100644
index 000000000000..e7e84c5acd96
--- /dev/null
+++ b/code/tests/atmos/test_ventcrawl.dmm
@@ -0,0 +1,30 @@
+//MAP CONVERTED BY dmm2tgm.py THIS HEADER COMMENT PREVENTS RECONVERSION, DO NOT REMOVE
+"a" = (
+/obj/machinery/atmospherics/unary/vent_pump/on{
+ dir = 4
+ },
+/turf/simulated/floor,
+/area/space)
+"d" = (
+/obj/machinery/atmospherics/unary/vent_pump/on{
+ dir = 8
+ },
+/turf/simulated/floor,
+/area/space)
+"X" = (
+/obj/machinery/atmospherics/pipe/simple/visible{
+ dir = 4
+ },
+/obj/structure/table,
+/turf/simulated/floor,
+/area/space)
+
+(1,1,1) = {"
+a
+"}
+(2,1,1) = {"
+X
+"}
+(3,1,1) = {"
+d
+"}
diff --git a/code/tests/attack_chain/test_attack_chain_cult_dagger.dm b/code/tests/attack_chain/test_attack_chain_cult_dagger.dm
new file mode 100644
index 000000000000..b150d67cb60f
--- /dev/null
+++ b/code/tests/attack_chain/test_attack_chain_cult_dagger.dm
@@ -0,0 +1,17 @@
+/datum/game_test/attack_chain_cult_dagger/Run()
+ var/datum/test_puppeteer/cultist = new(src)
+ var/datum/test_puppeteer/target = cultist.spawn_puppet_nearby()
+
+ cultist.puppet.mind.add_antag_datum(/datum/antagonist/cultist)
+ cultist.spawn_obj_in_hand(/obj/item/melee/cultblade/dagger)
+ cultist.set_intent("harm")
+ cultist.click_on(target)
+
+ TEST_ASSERT(target.check_attack_log("Attacked with ritual dagger"), "non-cultist missing dagger attack log")
+ TEST_ASSERT_NOTEQUAL(target.puppet.health, target.puppet.getMaxHealth(), "cultist attacking non-cultist with dagger caused no damage")
+
+ target.rejuvenate()
+ target.puppet.mind.add_antag_datum(/datum/antagonist/cultist)
+
+ cultist.click_on(target)
+ TEST_ASSERT_EQUAL(target.puppet.health, target.puppet.getMaxHealth(), "cultist attacking cultist with dagger caused damage")
diff --git a/code/tests/attack_chain/test_attack_chain_vehicles.dm b/code/tests/attack_chain/test_attack_chain_vehicles.dm
new file mode 100644
index 000000000000..791b45497ba7
--- /dev/null
+++ b/code/tests/attack_chain/test_attack_chain_vehicles.dm
@@ -0,0 +1,26 @@
+/datum/game_test/attack_chain_vehicles/Run()
+ var/datum/test_puppeteer/player = new(src)
+ var/obj/item/key/janitor/janicart_key = player.spawn_obj_in_hand(/obj/item/key/janitor)
+ var/obj/vehicle/janicart/janicart = player.spawn_obj_nearby(/obj/vehicle/janicart)
+
+ player.click_on(janicart)
+ TEST_ASSERT_EQUAL(janicart.inserted_key, janicart_key, "did not find janicart key in vehicle")
+
+ var/move_delay = janicart.vehicle_move_delay
+ player.spawn_obj_in_hand(/obj/item/borg/upgrade/vtec)
+ player.click_on(janicart)
+ TEST_ASSERT(janicart.vehicle_move_delay < move_delay, "VTEC upgrade not applied properly")
+
+ TEST_ASSERT_NULL(janicart.mybag, "unexpected trash bag on janicart")
+ var/obj/item/storage/bag/trash/bag = player.spawn_obj_in_hand(/obj/item/storage/bag/trash)
+ player.click_on(janicart)
+ TEST_ASSERT_EQUAL(janicart.mybag, bag, "trash bag not attached to janicart")
+
+ var/obj/item/kitchen/knife/knife = player.spawn_obj_in_hand(/obj/item/kitchen/knife)
+ player.set_intent("harm")
+ player.click_on(janicart)
+ TEST_ASSERT(janicart.obj_integrity < janicart.max_integrity, "knife attack not performed")
+
+ player.set_intent("help")
+ player.click_on(janicart)
+ TEST_ASSERT(knife in bag, "knife not placed in trash bag")
diff --git a/code/tests/game_tests.dm b/code/tests/game_tests.dm
new file mode 100644
index 000000000000..254673e8b34a
--- /dev/null
+++ b/code/tests/game_tests.dm
@@ -0,0 +1,37 @@
+//include game test files in this module in this ifdef
+//Keep this sorted alphabetically
+
+#ifdef GAME_TESTS
+#include "_game_test_puppeteer.dm"
+#include "_game_test.dm"
+#include "atmos\test_ventcrawl.dm"
+#include "attack_chain\test_attack_chain_cult_dagger.dm"
+#include "attack_chain\test_attack_chain_vehicles.dm"
+#include "games\test_cards.dm"
+#include "jobs\test_job_globals.dm"
+#include "test_aicard_icons.dm"
+#include "test_announcements.dm"
+#include "test_areas_apcs.dm"
+#include "test_components.dm"
+#include "test_config_sanity.dm"
+#include "test_crafting_lists.dm"
+#include "test_elements.dm"
+#include "test_emotes.dm"
+#include "test_init_sanity.dm"
+#include "test_log_format.dm"
+#include "test_map_templates.dm"
+#include "test_map_tests.dm"
+#include "test_missing_icons.dm"
+#include "test_origin_tech.dm"
+#include "test_purchase_reference_test.dm"
+#include "test_reagent_id_typos.dm"
+#include "test_runner.dm"
+#include "test_rustg_version.dm"
+#include "test_spawn_humans.dm"
+#include "test_spell_targeting_test.dm"
+#include "test_sql.dm"
+#include "test_status_effect_ids.dm"
+#include "test_subsystem_init.dm"
+#include "test_subsystem_metric_sanity.dm"
+#include "test_timer_sanity.dm"
+#endif
diff --git a/code/modules/unit_tests/games/test_cards.dm b/code/tests/games/test_cards.dm
similarity index 68%
rename from code/modules/unit_tests/games/test_cards.dm
rename to code/tests/games/test_cards.dm
index bde0b9f1f8bc..209fdfb92c13 100644
--- a/code/modules/unit_tests/games/test_cards.dm
+++ b/code/tests/games/test_cards.dm
@@ -1,4 +1,4 @@
-/datum/unit_test/card_deck/proc/validate_deck(obj/item/deck/deck)
+/datum/game_test/card_deck/proc/validate_deck(obj/item/deck/deck)
var/list/card_count = list()
for(var/datum/playingcard/card in deck.cards)
if(card_count[card.name] == null)
@@ -14,17 +14,17 @@
return TRUE
-/datum/unit_test/card_deck/Run()
+/datum/game_test/card_deck/Run()
// setup
var/loc = pick(block(run_loc_bottom_left, run_loc_top_right))
- var/obj/item/deck/cards/cards = new /obj/item/deck/cards(loc)
+ var/obj/item/deck/cards/cards = allocate(/obj/item/deck/cards, loc)
cards.build_decks()
// is deck proper upon spawning
if(!validate_deck(cards))
- Fail("52 card deck not initialized correctly.")
+ TEST_FAIL("52 card deck not initialized correctly.")
// is deck proper after shuffling
cards.deckshuffle()
if(!validate_deck(cards))
- Fail("52 card deck broken after shuffling.")
+ TEST_FAIL("52 card deck broken after shuffling.")
diff --git a/code/modules/unit_tests/jobs/test_job_globals.dm b/code/tests/jobs/test_job_globals.dm
similarity index 77%
rename from code/modules/unit_tests/jobs/test_job_globals.dm
rename to code/tests/jobs/test_job_globals.dm
index 7ed56ff99800..eebf80becb62 100644
--- a/code/modules/unit_tests/jobs/test_job_globals.dm
+++ b/code/tests/jobs/test_job_globals.dm
@@ -1,17 +1,17 @@
-/datum/unit_test/job_globals/Run()
+/datum/game_test/job_globals/Run()
return
-/datum/unit_test/job_globals/proc/is_list_unique(list/L)
+/datum/game_test/job_globals/proc/is_list_unique(list/L)
var/list_length = length(L)
var/unique_list = uniqueList(L)
var/unique_list_length = length(unique_list)
return list_length == unique_list_length
-/datum/unit_test/job_globals/proc/validate_list(list/L, list_name)
+/datum/game_test/job_globals/proc/validate_list(list/L, list_name)
if(!is_list_unique(L))
- Fail("job_globals list '[list_name]' contains duplicate values.")
+ TEST_FAIL("job_globals list '[list_name]' contains duplicate values.")
-/datum/unit_test/job_globals/no_duplicates/Run()
+/datum/game_test/job_globals/no_duplicates/Run()
validate_list(GLOB.station_departments, "station_departments")
validate_list(GLOB.command_positions, "command_positions")
validate_list(GLOB.command_head_positions, "command_head_positions")
diff --git a/code/modules/unit_tests/aicard_icons.dm b/code/tests/test_aicard_icons.dm
similarity index 65%
rename from code/modules/unit_tests/aicard_icons.dm
rename to code/tests/test_aicard_icons.dm
index cb61ade842f4..b35e368d5b9c 100644
--- a/code/modules/unit_tests/aicard_icons.dm
+++ b/code/tests/test_aicard_icons.dm
@@ -1,6 +1,6 @@
-/datum/unit_test/aicard_icons/Run()
- var/mob/living/silicon/ai/ai_box = new /mob/living/silicon/ai/
- var/obj/item/aicard/int_card = new /obj/item/aicard/
+/datum/game_test/aicard_icons/Run()
+ var/mob/living/silicon/ai/ai_box = allocate(/mob/living/silicon/ai)
+ var/obj/item/aicard/int_card = allocate(/obj/item/aicard)
var/list/ai_icon_list = icon_states(ai_box.icon)
var/list/aicard_icon_list = icon_states(int_card.icon)
@@ -14,6 +14,6 @@
if(icn_st in aicard_icon_list)
continue
- Fail("Every AI Display must have a corresponding icon of the same name in [int_card.icon] for intelicards, [icn_st] is missing!")
+ TEST_FAIL("Every AI Display must have a corresponding icon of the same name in [int_card.icon] for intelicards, [icn_st] is missing!")
//The exclusions list will need to be updated anytime any non-display icons are added to ai.dmi
diff --git a/code/modules/unit_tests/announcements.dm b/code/tests/test_announcements.dm
similarity index 97%
rename from code/modules/unit_tests/announcements.dm
rename to code/tests/test_announcements.dm
index 152e8cadc687..c4feea561ace 100644
--- a/code/modules/unit_tests/announcements.dm
+++ b/code/tests/test_announcements.dm
@@ -1,8 +1,8 @@
/// This test exists largely to ensure that no runtimes occur when announcements
/// are made, so there are no explicit Fail calls. It either works or runtimes.
-/datum/unit_test/announcements
+/datum/game_test/announcements
-/datum/unit_test/announcements/Run()
+/datum/game_test/announcements/Run()
GLOB.major_announcement.Announce("Figments from an eldritch god are being summoned into the NSS Cyberiad from an unknown dimension. Disrupt the ritual at all costs, before the station is destroyed! Space Law and SOP are suspended. The entire crew must kill cultists on sight.", "Central Command Higher Dimensional Affairs", 'sound/AI/cult_summon.ogg')
GLOB.major_announcement.Announce(
diff --git a/code/modules/unit_tests/areas_apcs.dm b/code/tests/test_areas_apcs.dm
similarity index 71%
rename from code/modules/unit_tests/areas_apcs.dm
rename to code/tests/test_areas_apcs.dm
index 00bd31805c0f..ecf21dc19da0 100644
--- a/code/modules/unit_tests/areas_apcs.dm
+++ b/code/tests/test_areas_apcs.dm
@@ -1,17 +1,17 @@
-/datum/unit_test/area_apcs
+/datum/game_test/area_apcs
/// Sometimes, areas may have power, or not. We dont really care about these areas.
var/list/optional_areas = list(/area/station/science/toxins/test, /area/station/maintenance/electrical_shop)
-/datum/unit_test/area_apcs/Run()
+/datum/game_test/area_apcs/Run()
for(var/area/station/A in SSmapping.existing_station_areas)
if(A.there_can_be_many || A.apc_starts_off || !A.requires_power)
continue
if(is_type_in_list(A, optional_areas))
continue
if(length(A.apc) == 0)
- Fail("Area [A.type] has [length(A.apc)] apcs, instead of 1.")
+ TEST_FAIL("Area [A.type] has [length(A.apc)] apcs, instead of 1.")
else if(length(A.apc) > 1)
var/list/locations = list()
for(var/atom/probably_an_apc as anything in A.apc)
locations += "([probably_an_apc.x], [probably_an_apc.y], [probably_an_apc.z])"
- Fail("Area [A.type] has [length(A.apc)] apcs, instead of 1. APCs are located at [english_list(locations)]")
+ TEST_FAIL("Area [A.type] has [length(A.apc)] apcs, instead of 1. APCs are located at [english_list(locations)]")
diff --git a/code/modules/unit_tests/component_tests.dm b/code/tests/test_components.dm
similarity index 64%
rename from code/modules/unit_tests/component_tests.dm
rename to code/tests/test_components.dm
index 0099d7508c5d..0e0933b4e15c 100644
--- a/code/modules/unit_tests/component_tests.dm
+++ b/code/tests/test_components.dm
@@ -1,4 +1,4 @@
-/datum/unit_test/component_duping/Run()
+/datum/game_test/component_duping/Run()
var/list/bad_dms = list()
var/list/bad_dts = list()
for(var/t in typesof(/datum/component))
@@ -9,4 +9,4 @@
if(dupe_type && !ispath(dupe_type))
bad_dts += t
if(length(bad_dms) || length(bad_dts))
- Fail("Components with invalid dupe modes: ([bad_dms.Join(",")]) ||| Components with invalid dupe types: ([bad_dts.Join(",")])")
+ TEST_FAIL("Components with invalid dupe modes: ([bad_dms.Join(",")]) ||| Components with invalid dupe types: ([bad_dts.Join(",")])")
diff --git a/code/modules/unit_tests/config_sanity.dm b/code/tests/test_config_sanity.dm
similarity index 76%
rename from code/modules/unit_tests/config_sanity.dm
rename to code/tests/test_config_sanity.dm
index 55f84fa98ca1..52934e87fda2 100644
--- a/code/modules/unit_tests/config_sanity.dm
+++ b/code/tests/test_config_sanity.dm
@@ -1,5 +1,5 @@
// This one test does multiple config things
-/datum/unit_test/config_sanity/Run()
+/datum/game_test/config_sanity/Run()
// First test the ruins. Space then lava.
var/list/config_space_ruins = GLOB.configuration.ruins.active_space_ruins.Copy() // Copy so we dont remove
var/list/datum/map_template/ruin/space/game_space_ruins = list()
@@ -20,14 +20,14 @@
// Do not confuse this with the map_templates unit test. They do different things!!!!!
if(length(game_space_ruins))
- Fail("Space ruins exist in the game code that do not exist in the config file")
+ TEST_FAIL("Space ruins exist in the game code that do not exist in the config file")
for(var/datum/map_template/ruin/space/S in game_space_ruins)
- Fail("Ruin [S.type] does not have a valid map path ([S.mappath])")
+ TEST_FAIL("Ruin [S.type] does not have a valid map path ([S.mappath])")
if(length(config_space_ruins))
- Fail("Space ruins exist in the game config that do not have associated datums")
+ TEST_FAIL("Space ruins exist in the game config that do not have associated datums")
for(var/path in config_space_ruins)
- Fail("- [path]")
+ TEST_FAIL("- [path]")
// Now for lava ruins
@@ -50,11 +50,11 @@
// Do not confuse this with the map_templates unit test. They do different things!!!!!
if(length(game_lava_ruins))
- Fail("Lava ruins exist in the game code that do not exist in the config file")
+ TEST_FAIL("Lava ruins exist in the game code that do not exist in the config file")
for(var/datum/map_template/ruin/lavaland/L in game_lava_ruins)
- Fail("Ruin [L.type] does not have a valid map path ([L.mappath])")
+ TEST_FAIL("Ruin [L.type] does not have a valid map path ([L.mappath])")
if(length(config_lava_ruins))
- Fail("Lava ruins exist in the game config that do not have associated datums")
+ TEST_FAIL("Lava ruins exist in the game config that do not have associated datums")
for(var/path in config_lava_ruins)
- Fail("- [path]")
+ TEST_FAIL("- [path]")
diff --git a/code/tests/test_crafting_lists.dm b/code/tests/test_crafting_lists.dm
new file mode 100644
index 000000000000..7fa26ffdc266
--- /dev/null
+++ b/code/tests/test_crafting_lists.dm
@@ -0,0 +1,4 @@
+/datum/game_test/crafting_lists/Run()
+ for(var/I in subtypesof(/datum/crafting_recipe))
+ var/datum/crafting_recipe/C = new I()
+ TEST_ASSERT(islist(C.result), "Expected a list for the 'result' of [C.type].")
diff --git a/code/modules/unit_tests/element_tests.dm b/code/tests/test_elements.dm
similarity index 58%
rename from code/modules/unit_tests/element_tests.dm
rename to code/tests/test_elements.dm
index d497984ea496..c9b51fd7d7e3 100644
--- a/code/modules/unit_tests/element_tests.dm
+++ b/code/tests/test_elements.dm
@@ -1,4 +1,4 @@
-/datum/unit_test/bespoke_element/Run()
+/datum/game_test/bespoke_element/Run()
for(var/datum/element/element_type as anything in subtypesof(/datum/element))
if(initial(element_type.element_flags) & ELEMENT_BESPOKE && initial(element_type.argument_hash_start_idx) == INFINITY)
- Fail("Element type [element_type] has ELEMENT_BESPOKE and a default argument_hash_start_idx.")
+ TEST_FAIL("Element type [element_type] has ELEMENT_BESPOKE and a default argument_hash_start_idx.")
diff --git a/code/modules/unit_tests/emotes.dm b/code/tests/test_emotes.dm
similarity index 67%
rename from code/modules/unit_tests/emotes.dm
rename to code/tests/test_emotes.dm
index c231c031a151..88ef8ae63e40 100644
--- a/code/modules/unit_tests/emotes.dm
+++ b/code/tests/test_emotes.dm
@@ -1,6 +1,6 @@
-/datum/unit_test/emote/Run()
+/datum/game_test/emote/Run()
// Special cases that shouldn't need keybinds.
var/list/ignored_emote_types = list(
@@ -15,33 +15,30 @@
for(var/emote_type in subtypesof(/datum/emote))
var/datum/emote/cur_emote = new emote_type()
if(cur_emote.message_param && !cur_emote.param_desc)
- Fail("emote [cur_emote] was given a message parameter without a description.")
+ TEST_FAIL("emote [cur_emote] was given a message parameter without a description.")
// Sanity checks, these emotes probably won't appear to a user but we should make sure they're cleaned up.
if(!cur_emote.key)
if(cur_emote.message || cur_emote.message_param)
- Fail("emote [cur_emote] is missing a key but has a message defined.")
+ TEST_FAIL("emote [cur_emote] is missing a key but has a message defined.")
if(cur_emote.key_third_person)
- Fail("emote [cur_emote] has a third-person key defined, but no first-person key. Either first person, both, or neither should be defined.")
+ TEST_FAIL("emote [cur_emote] has a third-person key defined, but no first-person key. Either first person, both, or neither should be defined.")
// These are ones that might appear to a user, and so could use some special handling.
else
- if(isnull(cur_emote.emote_type))
- Fail("emote [cur_emote] has a null target type.")
+ TEST_ASSERT_NOTNULL(cur_emote.emote_type, "emote [cur_emote] has a null target type.")
// If we're at this point, we're definitely an emote that a user could use, and therefore ought to make sure it's bound to a keybind if possible.
if(!is_type_in_list(cur_emote, keybound_emotes) && !is_type_in_list(cur_emote, ignored_emote_types))
- Fail("Emote [cur_emote] is usable, but not assigned a keybind.")
+ TEST_FAIL("Emote [cur_emote] is usable, but not assigned a keybind.")
if(isnum(cur_emote.max_stat_allowed) && cur_emote.max_stat_allowed < cur_emote.stat_allowed)
- Fail("emote [cur_emote]'s max_stat_allowed is greater than its stat_allowed, and would be unusable.")
+ TEST_FAIL("emote [cur_emote]'s max_stat_allowed is greater than its stat_allowed, and would be unusable.")
if(isnum(cur_emote.max_unintentional_stat_allowed) && cur_emote.max_unintentional_stat_allowed < cur_emote.unintentional_stat_allowed)
- Fail("emote [cur_emote]'s max_unintentional_stat_allowed is greater than its unintentional_stat_allowed, and would be unusable.")
+ TEST_FAIL("emote [cur_emote]'s max_unintentional_stat_allowed is greater than its unintentional_stat_allowed, and would be unusable.")
-
-
-/datum/unit_test/emote/proc/get_emote_keybinds()
+/datum/game_test/emote/proc/get_emote_keybinds()
var/list/bound_emotes = list()
for(var/keybind in subtypesof(/datum/keybinding/emote))
var/datum/keybinding/emote/E = new keybind()
diff --git a/code/modules/unit_tests/init_sanity.dm b/code/tests/test_init_sanity.dm
similarity index 60%
rename from code/modules/unit_tests/init_sanity.dm
rename to code/tests/test_init_sanity.dm
index 1584179cbe71..b21263175d8b 100644
--- a/code/modules/unit_tests/init_sanity.dm
+++ b/code/tests/test_init_sanity.dm
@@ -1,6 +1,6 @@
-/datum/unit_test/initialize_sanity/Run()
+/datum/game_test/initialize_sanity/Run()
if(length(SSatoms.BadInitializeCalls))
- Fail("Bad Initialize() calls detected. Please read logs.")
+ TEST_FAIL("Bad Initialize() calls detected. Please read logs.")
var/list/init_failures_to_text = list(
"[BAD_INIT_QDEL_BEFORE]" = "Qdeleted Before Initialized",
"[BAD_INIT_DIDNT_INIT]" = "Did Not Initialize",
@@ -8,4 +8,4 @@
"[BAD_INIT_NO_HINT]" = "No Initialize() Hint Returned",
)
for(var/failure in SSatoms.BadInitializeCalls)
- Fail("[failure]: [init_failures_to_text["[SSatoms.BadInitializeCalls[failure]]"]]") // You like stacked brackets?
+ TEST_FAIL("[failure]: [init_failures_to_text["[SSatoms.BadInitializeCalls[failure]]"]]") // You like stacked brackets?
diff --git a/code/modules/unit_tests/log_format.dm b/code/tests/test_log_format.dm
similarity index 81%
rename from code/modules/unit_tests/log_format.dm
rename to code/tests/test_log_format.dm
index 11bcd9fd9fe5..d3a6f67ca496 100644
--- a/code/modules/unit_tests/log_format.dm
+++ b/code/tests/test_log_format.dm
@@ -8,7 +8,7 @@
return "\[[date_portion]T[time_portion]] [TEST_MESSAGE]"
-/datum/unit_test/log_format/Run()
+/datum/game_test/log_format/Run()
// Generate a list of valid log timestamps. It can be the current time, or up to 2 seconds later to account for spurious CI lag
var/valid_lines = list(
"[generate_test_log_message(world.timeofday)]",
@@ -19,8 +19,8 @@
rustg_log_write(TEST_LOG_FILE, TEST_MESSAGE)
var/list/lines = file2list(TEST_LOG_FILE)
- if(!(lines[1] in valid_lines))
- Fail("RUSTG log format is not valid 8601 format. Expected '[generate_test_log_message(world.timeofday)]', got '[lines[1]]'")
+ TEST_ASSERT(lines[1] in valid_lines, \
+ "RUSTG log format is not valid 8601 format. Expected '[generate_test_log_message(world.timeofday)]', got '[lines[1]]'")
#undef TEST_MESSAGE
#undef TEST_LOG_FILE
diff --git a/code/modules/unit_tests/map_templates.dm b/code/tests/test_map_templates.dm
similarity index 71%
rename from code/modules/unit_tests/map_templates.dm
rename to code/tests/test_map_templates.dm
index 55834d8d171a..6eb6da6a234d 100644
--- a/code/modules/unit_tests/map_templates.dm
+++ b/code/tests/test_map_templates.dm
@@ -1,4 +1,4 @@
-/datum/unit_test/map_templates/Run()
+/datum/game_test/map_templates/Run()
var/list/datum/map_template/templates = subtypesof(/datum/map_template)
for(var/I in templates)
var/datum/map_template/MT = new I // The new is important here to ensure stuff gets set properly
@@ -6,6 +6,6 @@
continue
// Check if it even has a path and if so, does it exist
if(MT.mappath && !fexists(MT.mappath))
- Fail("The map file for [MT.type] does not exist!")
+ TEST_FAIL("The map file for [MT.type] does not exist!")
if(MT.mappath && !findtext(MT.mappath, ".dmm"))
- Fail("The map file for [MT.type] is not a map!")
+ TEST_FAIL("The map file for [MT.type] is not a map!")
diff --git a/code/modules/unit_tests/map_tests.dm b/code/tests/test_map_tests.dm
similarity index 100%
rename from code/modules/unit_tests/map_tests.dm
rename to code/tests/test_map_tests.dm
diff --git a/code/modules/unit_tests/missing_icons.dm b/code/tests/test_missing_icons.dm
similarity index 86%
rename from code/modules/unit_tests/missing_icons.dm
rename to code/tests/test_missing_icons.dm
index 17822595c066..0b652a3c466d 100644
--- a/code/modules/unit_tests/missing_icons.dm
+++ b/code/tests/test_missing_icons.dm
@@ -1,8 +1,8 @@
/// Makes sure objects actually have icons that exist!
-/datum/unit_test/missing_icons
+/datum/game_test/missing_icons
var/static/list/possible_icon_states = list()
-/datum/unit_test/missing_icons/proc/generate_possible_icon_states_list(directory_path = "icons/obj/")
+/datum/game_test/missing_icons/proc/generate_possible_icon_states_list(directory_path = "icons/obj/")
for(var/file_path in flist(directory_path))
if(findtext(file_path, ".dmi"))
for(var/sprite_icon in icon_states("[directory_path][file_path]", 1)) //2nd arg = 1 enables 64x64+ icon support, otherwise you'll end up with "sword0_1" instead of "sword"
@@ -10,7 +10,7 @@
else
possible_icon_states += generate_possible_icon_states_list("[directory_path][file_path]")
-/datum/unit_test/missing_icons/Run()
+/datum/game_test/missing_icons/Run()
generate_possible_icon_states_list()
generate_possible_icon_states_list("icons/effects/")
@@ -41,5 +41,5 @@
for(var/file_place in possible_icon_states[icon_state])
match_message += (match_message ? " & '[file_place]'" : " - Matching sprite found in: '[file_place]'")
- Fail("Missing icon_state for [obj_path] in '[icon]'.\n\ticon_state = \"[icon_state]\"[match_message]")
+ TEST_FAIL("Missing icon_state for [obj_path] in '[icon]'.\n\ticon_state = \"[icon_state]\"[match_message]")
diff --git a/code/modules/unit_tests/origin_tech.dm b/code/tests/test_origin_tech.dm
similarity index 71%
rename from code/modules/unit_tests/origin_tech.dm
rename to code/tests/test_origin_tech.dm
index 177c89b36b9b..5dc7b475e565 100644
--- a/code/modules/unit_tests/origin_tech.dm
+++ b/code/tests/test_origin_tech.dm
@@ -1,5 +1,5 @@
// Unit test to ensure people have appropriately set up their origin_techs in items, ensuring they give the appropriate resources in RnD breakdown
-/datum/unit_test/origin_tech/Run()
+/datum/game_test/origin_tech/Run()
var/regex/nums = regex("^\[0-9]+")
for(var/tpath in subtypesof(/obj/item))
var/obj/item/I = tpath
@@ -8,5 +8,4 @@
var/list/tech_list = params2list(tech_str)
for(var/k in tech_list)
- if(length(nums.Replace(tech_list[k], "")) > 0)
- Fail("Invalid origin tech for [tpath]: [tech_str]")
+ TEST_ASSERT(length(nums.Replace(tech_list[k], "")) == 0, "Invalid origin tech for [tpath]: [tech_str]")
diff --git a/code/modules/unit_tests/purchase_reference_test.dm b/code/tests/test_purchase_reference_test.dm
similarity index 66%
rename from code/modules/unit_tests/purchase_reference_test.dm
rename to code/tests/test_purchase_reference_test.dm
index 91dca4b846a0..42dc5d22f09e 100644
--- a/code/modules/unit_tests/purchase_reference_test.dm
+++ b/code/tests/test_purchase_reference_test.dm
@@ -1,14 +1,13 @@
// tests if there are duplicate or null refs/lognames for uplink items and spellbook items respectively
-/datum/unit_test/uplink_refs/Run()
+/datum/game_test/uplink_refs/Run()
var/list/uplink_refs = list()
for(var/datum/uplink_item/I as anything in subtypesof(/datum/uplink_item))
if(isnull(initial(I.item)))
continue //don't test them if they don't have an item
var/uplink_ref = initial(I.reference)
if(isnull(uplink_ref))
- Fail("uplink item [initial(I.name)] has no reference")
+ TEST_FAIL("uplink item [initial(I.name)] has no reference")
continue
- if(uplink_ref in uplink_refs)
- Fail("uplink reference [uplink_ref] is used multiple times")
+ TEST_ASSERT_NOT(uplink_ref in uplink_refs, "uplink reference [uplink_ref] is used multiple times")
uplink_refs += uplink_ref
diff --git a/code/modules/unit_tests/reagent_id_typos.dm b/code/tests/test_reagent_id_typos.dm
similarity index 52%
rename from code/modules/unit_tests/reagent_id_typos.dm
rename to code/tests/test_reagent_id_typos.dm
index 7f45774db0b5..93d6e4fd6b9c 100644
--- a/code/modules/unit_tests/reagent_id_typos.dm
+++ b/code/tests/test_reagent_id_typos.dm
@@ -1,11 +1,10 @@
-/datum/unit_test/reagent_id_typos
+/datum/game_test/reagent_id_typos
-/datum/unit_test/reagent_id_typos/Run()
+/datum/game_test/reagent_id_typos/Run()
for(var/I in GLOB.chemical_reactions_list)
for(var/V in GLOB.chemical_reactions_list[I])
var/datum/chemical_reaction/R = V
for(var/id in (R.required_reagents + R.required_catalysts))
- if(!GLOB.chemical_reagents_list[id])
- Fail("Unknown chemical id \"[id]\" in recipe [R.type]")
+ TEST_ASSERT(GLOB.chemical_reagents_list[id], "Unknown chemical id \"[id]\" in recipe [R.type]")
diff --git a/code/modules/unit_tests/test_runner.dm b/code/tests/test_runner.dm
similarity index 88%
rename from code/modules/unit_tests/test_runner.dm
rename to code/tests/test_runner.dm
index 8ac625e5c123..206d31a2205d 100644
--- a/code/modules/unit_tests/test_runner.dm
+++ b/code/tests/test_runner.dm
@@ -3,14 +3,14 @@
// intentionally (if there's a lot of legitimate map errors), or accidentally if
// a test condition is written incorrectly and starts e.g. logging failures for
// every single tile.
-#ifdef LOCAL_UNIT_TESTS
+#ifdef LOCAL_GAME_TESTS
#define MAX_MAP_TEST_FAILURE_COUNT 100
#else
#define MAX_MAP_TEST_FAILURE_COUNT 20
#endif
/datum/test_runner
- var/datum/unit_test/current_test
+ var/datum/game_test/current_test
var/failed_any_test = FALSE
var/list/test_logs = list()
var/list/durations = list()
@@ -56,8 +56,8 @@
/datum/test_runner/proc/Run()
CHECK_TICK
- for(var/I in subtypesof(/datum/unit_test))
- var/datum/unit_test/test = new I
+ for(var/I in subtypesof(/datum/game_test))
+ var/datum/game_test/test = new I
test_logs[I] = list()
current_test = test
@@ -70,7 +70,15 @@
if(!test.succeeded)
failed_any_test = TRUE
- test_logs[I] += test.fail_reasons
+ for(var/fail_reason in test.fail_reasons)
+ if(islist(fail_reason))
+ var/text = fail_reason[1]
+ var/file = fail_reason[2]
+ var/line = fail_reason[3]
+
+ test_logs[I] += "[file]:[line]: [text]"
+ else
+ test_logs[I] += fail_reason
qdel(test)
@@ -83,7 +91,7 @@
var/time = world.timeofday
set waitfor = FALSE
- #ifdef LOCAL_UNIT_TESTS
+ #ifdef LOCAL_GAME_TESTS
emit_failures = TRUE
#endif
diff --git a/code/tests/test_rustg_version.dm b/code/tests/test_rustg_version.dm
new file mode 100644
index 000000000000..de39b377bdc6
--- /dev/null
+++ b/code/tests/test_rustg_version.dm
@@ -0,0 +1,3 @@
+/datum/game_test/rustg_version/Run()
+ var/library_version = rustg_get_version()
+ TEST_ASSERT_EQUAL(library_version, RUST_G_VERSION, "invalid RUSTG Version")
diff --git a/code/modules/unit_tests/spawn_humans.dm b/code/tests/test_spawn_humans.dm
similarity index 69%
rename from code/modules/unit_tests/spawn_humans.dm
rename to code/tests/test_spawn_humans.dm
index dd6dac928403..ca0a6d6bd4a7 100644
--- a/code/modules/unit_tests/spawn_humans.dm
+++ b/code/tests/test_spawn_humans.dm
@@ -1,8 +1,8 @@
-/datum/unit_test/spawn_humans/Run()
+/datum/game_test/spawn_humans/Run()
var/locs = block(run_loc_bottom_left, run_loc_top_right)
for(var/I in 1 to 5)
- new /mob/living/carbon/human(pick(locs))
+ allocate(/mob/living/carbon/human, pick(locs))
// There is a 5 second delay here so that all the items on the humans have time to initialize and spawn
sleep(50)
diff --git a/code/modules/unit_tests/spell_targeting_test.dm b/code/tests/test_spell_targeting_test.dm
similarity index 68%
rename from code/modules/unit_tests/spell_targeting_test.dm
rename to code/tests/test_spell_targeting_test.dm
index e1a189774390..7c66c40549bd 100644
--- a/code/modules/unit_tests/spell_targeting_test.dm
+++ b/code/tests/test_spell_targeting_test.dm
@@ -1,4 +1,4 @@
-/datum/unit_test/spell_targeting/Run()
+/datum/game_test/spell_targeting/Run()
var/list/bad_spells = list()
for(var/datum/spell/S as anything in typesof(/datum/spell))
if(initial(S.name) == "Spell")
@@ -7,4 +7,4 @@
if(!S.targeting)
bad_spells += S
if(length(bad_spells))
- Fail("Spells without targeting found: [bad_spells.Join(", ")]")
+ TEST_FAIL("Spells without targeting found: [bad_spells.Join(", ")]")
diff --git a/code/tests/test_sql.dm b/code/tests/test_sql.dm
new file mode 100644
index 000000000000..01acc0ac2873
--- /dev/null
+++ b/code/tests/test_sql.dm
@@ -0,0 +1,5 @@
+// Unit test to check SQL version has been updated properly.,
+/datum/game_test/sql_version/Run()
+ // Check if the SQL version set in the code is equal to the CI DB config
+ TEST_ASSERT_EQUAL(GLOB.configuration.database.version, SQL_VERSION, "SQL version error. You may need to update the example config.")
+ TEST_ASSERT_EQUAL(SSdbcore.total_errors, 0, "SQL errors occurred on startup.")
diff --git a/code/modules/unit_tests/status_effect_ids.dm b/code/tests/test_status_effect_ids.dm
similarity index 61%
rename from code/modules/unit_tests/status_effect_ids.dm
rename to code/tests/test_status_effect_ids.dm
index febe085e1c7b..a5354b2432ce 100644
--- a/code/modules/unit_tests/status_effect_ids.dm
+++ b/code/tests/test_status_effect_ids.dm
@@ -1,8 +1,8 @@
-/datum/unit_test/status_effect_ids/Run()
+/datum/game_test/status_effect_ids/Run()
var/list/bad_statuses = list()
for(var/datum/status_effect/effect as anything in subtypesof(/datum/status_effect))
if(initial(effect.id) == null)
bad_statuses += effect
if(length(bad_statuses))
- Fail("STatus effects found without an unique ID: [bad_statuses.Join(", ")]")
+ TEST_FAIL("Status effects found without an unique ID: [bad_statuses.Join(", ")]")
diff --git a/code/modules/unit_tests/subsystem_init.dm b/code/tests/test_subsystem_init.dm
similarity index 56%
rename from code/modules/unit_tests/subsystem_init.dm
rename to code/tests/test_subsystem_init.dm
index 1515f12c591f..0ea650c0d411 100644
--- a/code/modules/unit_tests/subsystem_init.dm
+++ b/code/tests/test_subsystem_init.dm
@@ -1,9 +1,9 @@
-/datum/unit_test/subsystem_init/Run()
+/datum/game_test/subsystem_init/Run()
var/datum/controller/subsystem/base_ss
var/default_offline_implications = initial(base_ss.offline_implications)
for(var/datum/controller/subsystem/SS as anything in Master.subsystems)
if((SS.flags & SS_NO_INIT) && (SS.flags & SS_NO_FIRE))
- Fail("[SS]([SS.type]) is a subsystem which is set to not initialize or fire. Use a global datum instead an SS.")
+ TEST_FAIL("[SS]([SS.type]) is a subsystem which is set to not initialize or fire. Use a global datum instead an SS.")
if(!(SS.flags & SS_NO_FIRE) && SS.offline_implications == default_offline_implications)
- Fail("[SS]([SS.type]) is a subsystem which fires but has no offline implications set.")
+ TEST_FAIL("[SS]([SS.type]) is a subsystem which fires but has no offline implications set.")
diff --git a/code/tests/test_subsystem_metric_sanity.dm b/code/tests/test_subsystem_metric_sanity.dm
new file mode 100644
index 000000000000..932abf0835d8
--- /dev/null
+++ b/code/tests/test_subsystem_metric_sanity.dm
@@ -0,0 +1,22 @@
+// Unit test to ensure SS metrics are valid
+/datum/game_test/subsystem_metric_sanity/Run()
+ for(var/datum/controller/subsystem/SS in Master.subsystems)
+ var/list/data = SS.get_metrics()
+ if(length(data) != 4)
+ TEST_FAIL("SS[SS.ss_id] has invalid metrics data!")
+ continue
+ if(isnull(data["cost"]))
+ TEST_FAIL("SS[SS.ss_id] has invalid metrics data! No 'cost' found in [json_encode(data)]")
+ continue
+ if(isnull(data["tick_usage"]))
+ TEST_FAIL("SS[SS.ss_id] has invalid metrics data! No 'tick_usage' found in [json_encode(data)]")
+ continue
+ if(isnull(data["custom"]))
+ TEST_FAIL("SS[SS.ss_id] has invalid metrics data! No 'custom' found in [json_encode(data)]")
+ continue
+ if(!islist(data["custom"]))
+ TEST_FAIL("SS[SS.ss_id] has invalid metrics data! 'custom' is not a list in [json_encode(data)]")
+ continue
+ if(isnull(data["sleep_count"]))
+ TEST_FAIL("SS[SS.ss_id] has invalid metrics data! No 'sleep_count' found in [json_encode(data)]")
+ continue
diff --git a/code/tests/test_timer_sanity.dm b/code/tests/test_timer_sanity.dm
new file mode 100644
index 000000000000..b5784b5ce033
--- /dev/null
+++ b/code/tests/test_timer_sanity.dm
@@ -0,0 +1,2 @@
+/datum/game_test/timer_sanity/Run()
+ TEST_ASSERT(SStimer.bucket_count >= 0, "SStimer is going into negative bucket count")
diff --git a/docs/mapping/design.md b/docs/mapping/design.md
index 0eb78cf21fc8..61aa9e7d6c99 100644
--- a/docs/mapping/design.md
+++ b/docs/mapping/design.md
@@ -195,3 +195,20 @@ deviation is impossible.
be given for hijackers and accessibility to the emergency shuttle console, as
well as the ability of crew to storm the bridge if necessary to prevent a
hijack.
+
+# Submap-Specific Guidance
+
+1. Submaps should be used to increase variety and add an element of chance to
+ player mechanics. They should not be used with an intention to confuse
+ players, or to cut off the primary path through the maintenance tunnels.
+ Primary paths through maints can make detours but must return to their
+ original ingress and egress points. Paths which lead to department maints
+ airlocks must remain obvious and easily accessible.
+
+2. All pre-existing balance guidelines regarding mapping apply to submaps. Loot
+ counts must remain consistent. Walls should only be reinforced in appropriate
+ places. There should be no "treasure troves" or hoards only accessible with
+ detailed map knowledge. AI cameras cannot be placed in maints submaps.
+ Antag/sec balance, tactical flexibility, and navigability must be considered.
+ Dead ends are to be avoided. Department maintenance airlocks may not be moved
+ or removed.
\ No newline at end of file
diff --git a/docs/mapping/requirements.md b/docs/mapping/requirements.md
index c0a01c5f406f..f0fdc7133dd1 100644
--- a/docs/mapping/requirements.md
+++ b/docs/mapping/requirements.md
@@ -174,6 +174,60 @@ relevant directional mapper, as it has predefined pixel offsets and directions
that are standardised: APC, Air alarm, Fire alarm, station intercom, newscaster,
extinguisher cabinet, light switches.
+## Submap Requirements
+
+1. Currently, while they do not have to be exactly rectangular, submaps cannot
+ be larger than 24x24 tiles. This is in order to prevent them from being too
+ difficult to review or navigate and get used to in-game.
+
+2. Submaps are **not** to be used as a proxy to change something you do not like
+ about a map. If you have issues with the balance or design of a map, those
+ issues should be addressed with ordinary remap PRs.
+
+### Stations
+
+1. Submaps on stations may only be used in maintenance tunnels, and only one
+ submap per area (i.e. fore, aft) is permitted. This restriction may be
+ relaxed in the future, but is in place to prevent player confusion and
+ frustration with too many map areas changing.
+
+2. Submaps in maintenance tunnels should continue to comply with existing rules
+ about signposting. Areas in maints should remain recognizable regardless of
+ submap configuration. This means that pre-existing signposts should be
+ respected, such as the abandoned medical area in Box medmaints, the mining
+ equipment in Cere cargo maints, etc.
+
+3. To prevent overuse of submaps, there is currently a restriction of three per
+ station. This limit may be raised depending on how submaps are received by
+ players. The areas chosen are first-come, first serve. Whoever gets their
+ changes mapped in first sets the available submaps.
+
+4. There is no hard limit on the number of variants per submap, though we ask
+ you keep it to a reasonable number.
+
+5. Submaps may not replace tiles which have atmos pipes, power cables, or other
+ engineering/atmospherics equipment. This is to prevent this equipment from
+ being hidden with walls or no longer accessible via their pre-existing
+ primary paths through maintenance. The exception is for equipment that is
+ only used in the submap; for example, the cables to a rage cage.
+
+6. One submap variant must be an exact duplicate of the original area's
+ contents. A variant will always be chosen, and so it's necessary that an
+ "original" variant is available. The original contents of the submap should
+ remain as is, in case mapmanip fails to load altogether.
+
+### Ruins
+
+1. Submaps on ruins are allowed to be more flexible. There's currently no limit
+ on how many submaps may be used on a given ruin, however the 24x24 tile limit
+ remains.
+
+2. Submaps should be used to increase the number of unique ways a ruin can be
+ approached and run, including randomizing specific challenges, rewards, and
+ paths through the ruin. They should not be used purely for decorative
+ purposes, or for changes that could be accomplished using existing random
+ spawners.
+
## Mapper Contribution Guidelines
These guidelines apply to **all** mapping contributors.
@@ -184,12 +238,12 @@ your PR if we disagree with your reasoning. Large remaps, such as those to a
department, must be justified with clear, specific reasons.
Before committing a map change, you **MUST** run Mapmerge to normalise your
-changes. You can do this manually before every commit with
-`tools\mapmerge2\Run Before Committing.bat` or by letting the
-[git hooks](./quickstart.md#mapmerge) do it for you. Failure to run Mapmerge on
-a map after editing greatly increases the risk of the map's key dictionary
-becoming corrupted by future edits after running map merge. Resolving the
-corruption issue involves rebuilding the map's key dictionary.
+changes. You can do this manually before every commit with `tools\mapmerge2\Run
+Before Committing.bat` or by letting the [git hooks](./quickstart.md#mapmerge)
+do it for you. Failure to run Mapmerge on a map after editing greatly increases
+the risk of the map's key dictionary becoming corrupted by future edits after
+running map merge. Resolving the corruption issue involves rebuilding the map's
+key dictionary.
If you are making non-minor edits to an area or room, (non-minor being anything
more than moving a few objects or fixing small bugs) then you should ensure the
diff --git a/docs/references/movement_signals.md b/docs/references/movement_signals.md
new file mode 100644
index 000000000000..664cba9d5314
--- /dev/null
+++ b/docs/references/movement_signals.md
@@ -0,0 +1,140 @@
+# Movement Signals
+
+## Background
+
+Traditionally, BYOND games rely on several native procs to respond to certain
+kinds of in-game movement:
+
+- If one atom attempts to overlap another one, [`/atom/proc/Cross`][cross] is called,
+ allowing the overlapped atom to either return `TRUE` to allow the cross, or
+ `FALSE` to prevent it. When `Move()` is called and an atom overlaps another
+ one, [`/atom/proc/Crossed`][crossed] is called, allowing the overlapped atom to react
+ to the cross.
+- Similarly, when an atom attempts to stop overlapping another,
+ [`/atom/proc/Uncross`][uncross] is called to permit or deny it, then
+ [`/atom/proc/Uncrossed`][uncrossed] is called to allow the atom to react to it.
+- When a movable attempts to enter a turf, [`/turf/proc/Enter`][enter] is called,
+ allowing the turf to return `TRUE` to permit the entry, or `FALSE` to deny it.
+ When a movable succeeds in entering a turf, [`/turf/proc/Entered`][entered] is
+ called, allowing the turf to react to the entry.
+- Similarly, when an atom attempts to exit a turf, [`/turf/proc/Exit`][exit] is
+ called to permit or deny it, then [`/turf/proc/Exited`][exited] is called to allow
+ the turf to react to the exit.
+- When an atom attempts to enter the contents of another one,
+ [`/atom/proc/Enter`][atom_enter] is called, allowing the containing atom to
+ either return `TRUE` to allow the entry, or `FALSE` to prevent it. When an
+ atom successfully enters the contents of another one,
+ [`/atom/proc/Entered`][atom_entered] is called.
+- Similarly, when an atom attempts to exit another atom's contents,
+ [`/atom/proc/Exit`][atom_exit] is called to permit or deny it, then
+ [`/atom/proc/Exited`][atom_exited] is called to allow the turf to react to the
+ exit.
+
+As one can imagine, with a lot of objects moving around in the game world,
+calling all of these procs can be expensive, especially if we don't care about
+what happens most of the time.
+
+In order to avoid making all of these calls, and provide more fine-grained
+control over what happens on atom/turf interactions, we implement our own
+version of the native `/atom/movable/Move`, and use signal handlers and
+components to process the interactions we care about.
+
+[cross]: https://secure.byond.com/docs/ref/#/atom/proc/Cross
+[crossed]: https://secure.byond.com/docs/ref/#/atom/proc/Crossed
+[uncross]: https://secure.byond.com/docs/ref/#/atom/proc/Uncross
+[uncrossed]: https://secure.byond.com/docs/ref/#/atom/proc/Uncrossed
+[enter]: https://secure.byond.com/docs/ref/#/turf/proc/Enter
+[entered]: https://secure.byond.com/docs/ref/#/turf/proc/Entered
+[exit]: https://secure.byond.com/docs/ref/#/turf/proc/Exit
+[exited]: https://secure.byond.com/docs/ref/#/turf/proc/Exited
+[atom_enter]: https://secure.byond.com/docs/ref/#/atom/proc/Enter
+[atom_entered]: https://secure.byond.com/docs/ref/#/atom/proc/Entered
+[atom_exit]: https://secure.byond.com/docs/ref/#/atom/proc/Exit
+[atom_exited]: https://secure.byond.com/docs/ref/#/atom/proc/Exited
+
+## Signal Handlers
+
+There are several signal handlers used to replicate the native BYOND atom
+crossover/entry behavior:
+
+- [`COMSIG_MOVABLE_PRE_MOVE`][pre_move] is sent when trying to determine if an
+ atom can enter a new turf. Any subscribed handler can return
+ `COMPONENT_MOVABLE_BLOCK_PRE_MOVE` to prevent the move.
+- [`COMSIG_MOVABLE_MOVED`][moved] is sent after a successful move.
+- [`COMSIG_MOVABLE_CHECK_CROSS`][cross] is sent when trying to determine if an
+ atom can cross over another one. Any subscribed handler can return
+ `COMPONENT_BLOCK_CROSS` to prevent the cross.
+- Similarly, [`COMSIG_MOVABLE_CHECK_CROSS_OVER`][crossover] is sent if an atom
+ will allow another one to cross over it--the reverse of
+ `COMSIG_MOVABLE_CHECK_CROSS`. Again, any subscribed handler can return
+ `COMPONENT_BLOCK_CROSS` to prevent it.
+- [`COMSIG_ATOM_ENTERED`][entered] is sent if an atom enters this atom's
+ contents. Similarly, [`COMSIG_ATOM_EXITED`][exited] is sent if an atom exits
+ this atom's contents.
+
+[pre_move]: https://codedocs.paradisestation.org/code/__DEFINES/dcs/movable_signals.html#define/COMSIG_MOVABLE_PRE_MOVE
+[moved]: https://codedocs.paradisestation.org/code/__DEFINES/dcs/movable_signals.html#define/COMSIG_MOVABLE_MOVED
+[cross]: https://codedocs.paradisestation.org/code/__DEFINES/dcs/movable_signals.html#define/COMSIG_MOVABLE_CHECK_CROSS
+[crossover]: https://codedocs.paradisestation.org/code/__DEFINES/dcs/movable_signals.html#define/COMSIG_MOVABLE_CHECK_CROSS_OVER
+[entered]: https://codedocs.paradisestation.org/code/__DEFINES/dcs/movable_signals.html#define/COMSIG_ATOM_ENTERED
+[exited]: https://codedocs.paradisestation.org/code/__DEFINES/dcs/movable_signals.html#define/COMSIG_ATOM_EXITED
+
+## The `connect_loc` Element
+
+The above signals are not appropriate for all cases. For example, it may seem
+like beartraps should listen to `COMSIG_MOVABLE_MOVED` to see if they should
+activate. However, this will fire every time a movement crossing occurs, even if
+it wouldn't normally trigger the trap, such as if the trap is being thrown and
+intersects abstract lighting objects. Instead, we use `COMSIG_ATOM_ENTERED` with
+the `connect_loc` element. As a refresher, `COMSIG_ATOM_ENTERED` fires when an
+atom enters the contents of another. By using the `connect_loc` element, we can
+have the beartrap listen for signals on the turf it's located on. Then, if the
+turf has a `COMSIG_ATOM_ENTERED` signal fire, we can have the beartrap respond.
+
+Before:
+
+```dm
+/obj/item/restraints/legcuffs/beartrap/proc/Crossed(mob/living/crossed)
+ if(istype(crossed))
+ spring_trap()
+```
+
+After:
+
+```dm
+/obj/item/restraints/legcuffs/beartrap/Initialize(mapload)
+ . = ..()
+ var/static/list/loc_connections = list(
+ COMSIG_ATOM_ENTERED = PROC_REF(on_atom_entered),
+ )
+ AddElement(/datum/element/connect_loc, loc_connections)
+
+/obj/item/restraints/legcuffs/beartrap/proc/on_atom_entered(datum/source, mob/living/entered)
+ if(istype(entered))
+ spring_trap()
+```
+
+Note that the list of connections is `static` so that a new element instance isn't
+created for every new beartrap.
+
+## Summary
+
+The procs `/atom/movable/Cross`, `/atom/movable/Crossed`,
+`/atom/movable/Uncross`, and `/atom/movable/Uncrossed`, should not be used for
+new code.
+
+If you care about every time a movable attempts to overlap you, listen to
+`COMSIG_MOVABLE_PRE_MOVE`, and return `COMPONENT_MOVABLE_BLOCK_PRE_MOVE` to
+prevent it from happening.
+
+If you want to prevent a movable from crossing you, listen to
+`COMSIG_MOVABLE_CHECK_CROSS` and return `COMPONENT_BLOCK_CROSS` to prevent it. If you
+want to prevent a movable from being crossed by you, listen to
+`COMSIG_MOVABLE_CHECK_CROSS_OVER` and return `COMPONENT_BLOCK_CROSS` to prevent it.
+
+If you care about an atom entering or exiting your contents, listen to
+`COMSIG_ATOM_ENTERED` or `COMSIG_ATOM_EXITED`.
+
+If you care about an atom entering or exiting your location, or any other signal
+firing on your location, create a `static` list of signals mapped to procs and
+add a `/datum/element/connect_loc` to yourself.
diff --git a/icons/mob/actions/actions.dmi b/icons/mob/actions/actions.dmi
index e33b6ed74bf1..7119185eb41d 100644
Binary files a/icons/mob/actions/actions.dmi and b/icons/mob/actions/actions.dmi differ
diff --git a/icons/mob/clothing/masking_helpers.dmi b/icons/mob/clothing/masking_helpers.dmi
index 1407653d4f7b..ff8216e116f0 100644
Binary files a/icons/mob/clothing/masking_helpers.dmi and b/icons/mob/clothing/masking_helpers.dmi differ
diff --git a/icons/mob/clothing/species/grey/suit.dmi b/icons/mob/clothing/species/grey/suit.dmi
index 416f42df899b..a16b779e26fe 100644
Binary files a/icons/mob/clothing/species/grey/suit.dmi and b/icons/mob/clothing/species/grey/suit.dmi differ
diff --git a/icons/mob/clothing/species/grey/underwear.dmi b/icons/mob/clothing/species/grey/underwear.dmi
index 372137a3c69c..ecc988ec8f8f 100644
Binary files a/icons/mob/clothing/species/grey/underwear.dmi and b/icons/mob/clothing/species/grey/underwear.dmi differ
diff --git a/icons/mob/clothing/species/kidan/head/beret.dmi b/icons/mob/clothing/species/kidan/head/beret.dmi
new file mode 100644
index 000000000000..9cb03bfe1608
Binary files /dev/null and b/icons/mob/clothing/species/kidan/head/beret.dmi differ
diff --git a/icons/mob/clothing/species/kidan/head/softcap.dmi b/icons/mob/clothing/species/kidan/head/softcap.dmi
new file mode 100644
index 000000000000..5a2c29f66b96
Binary files /dev/null and b/icons/mob/clothing/species/kidan/head/softcap.dmi differ
diff --git a/icons/mob/clothing/species/kidan/underwear.dmi b/icons/mob/clothing/species/kidan/underwear.dmi
index 89b9c68c3749..5e63fa646c09 100644
Binary files a/icons/mob/clothing/species/kidan/underwear.dmi and b/icons/mob/clothing/species/kidan/underwear.dmi differ
diff --git a/icons/mob/clothing/species/vox/suit.dmi b/icons/mob/clothing/species/vox/suit.dmi
index 9d1758b908e0..7f23a6c79cb8 100644
Binary files a/icons/mob/clothing/species/vox/suit.dmi and b/icons/mob/clothing/species/vox/suit.dmi differ
diff --git a/icons/mob/clothing/species/vox/underwear.dmi b/icons/mob/clothing/species/vox/underwear.dmi
index 72da557c997c..7691af9db0f1 100644
Binary files a/icons/mob/clothing/species/vox/underwear.dmi and b/icons/mob/clothing/species/vox/underwear.dmi differ
diff --git a/icons/mob/clothing/suit.dmi b/icons/mob/clothing/suit.dmi
index dbaa6740db3a..06ab83e88584 100644
Binary files a/icons/mob/clothing/suit.dmi and b/icons/mob/clothing/suit.dmi differ
diff --git a/icons/mob/clothing/underwear.dmi b/icons/mob/clothing/underwear.dmi
index e16e5bb4d5f7..d6d1ba380d2b 100644
Binary files a/icons/mob/clothing/underwear.dmi and b/icons/mob/clothing/underwear.dmi differ
diff --git a/icons/obj/telescience.dmi b/icons/obj/telescience.dmi
index 36daadc9f18c..290be6c34e85 100644
Binary files a/icons/obj/telescience.dmi and b/icons/obj/telescience.dmi differ
diff --git a/icons/obj/toy.dmi b/icons/obj/toy.dmi
index 61edc4f91069..19d97b69b54e 100644
Binary files a/icons/obj/toy.dmi and b/icons/obj/toy.dmi differ
diff --git a/mkdocs.yml b/mkdocs.yml
index 8d2160d6a3d9..aefc3c8ada41 100644
--- a/mkdocs.yml
+++ b/mkdocs.yml
@@ -98,4 +98,5 @@ nav:
- 'Advanced Bitflags': './references/adv_bitflags.md'
- 'Using Feedback Data': './references/feedback_data.md'
- 'Tick Order': './references/tick_order.md'
+ - 'Movement Signals': './references/movement_signals.md'
- 'Attack Chain': './references/attack_chain.md'
diff --git a/modular_ss220/_components/_components.dm b/modular_ss220/_components/_components.dm
deleted file mode 100644
index 7de630500b91..000000000000
--- a/modular_ss220/_components/_components.dm
+++ /dev/null
@@ -1,4 +0,0 @@
-/datum/modpack/comps
- name = "Компоненты"
- desc = "Различные полезные компоненты, которые обязательно(нет) кому то понадобятся"
- author = "aylong"
diff --git a/modular_ss220/_components/_components.dme b/modular_ss220/_components/_components.dme
deleted file mode 100644
index 1b701de2654f..000000000000
--- a/modular_ss220/_components/_components.dme
+++ /dev/null
@@ -1,3 +0,0 @@
-#include "_components.dm"
-
-#include "code/connect_range.dm"
diff --git a/modular_ss220/_misc/code/global_procs.dm b/modular_ss220/_misc/code/global_procs.dm
index d6c790f5c99b..e7163cc06d44 100644
--- a/modular_ss220/_misc/code/global_procs.dm
+++ b/modular_ss220/_misc/code/global_procs.dm
@@ -1,13 +1,2 @@
/proc/cmp_typepaths_asc(A, B)
return sorttext("[B]","[A]")
-
-///Returns a list of all locations (except the area) the movable is within.
-/proc/get_nested_locs(atom/movable/atom_on_location, include_turf = FALSE)
- . = list()
- var/atom/location = atom_on_location.loc
- var/turf/our_turf = get_turf(atom_on_location)
- while(location && location != our_turf)
- . += location
- location = location.loc
- if(our_turf && include_turf) //At this point, only the turf is left, provided it exists.
- . += our_turf
diff --git a/modular_ss220/_signals220/code/signals_mob/signals_mob_living.dm b/modular_ss220/_signals220/code/signals_mob/signals_mob_living.dm
index d3dc947da91b..ae0ad19c5b58 100644
--- a/modular_ss220/_signals220/code/signals_mob/signals_mob_living.dm
+++ b/modular_ss220/_signals220/code/signals_mob/signals_mob_living.dm
@@ -1,7 +1,7 @@
// Signals for /mob/living
-/mob/living/CanPass(atom/movable/mover, turf/target, height)
- if(SEND_SIGNAL(src, COMSIG_LIVING_CAN_PASS, mover, target, height) & COMPONENT_LIVING_PASSABLE)
+/mob/living/CanPass(atom/movable/mover, border_dir)
+ if(SEND_SIGNAL(src, COMSIG_LIVING_CAN_PASS, mover, border_dir) & COMPONENT_LIVING_PASSABLE)
return TRUE
return ..()
diff --git a/modular_ss220/awaymission_gun/code/items/awaymission_gun.dm b/modular_ss220/awaymission_gun/code/items/awaymission_gun.dm
index 61659b4992d7..49e0b42a23c7 100644
--- a/modular_ss220/awaymission_gun/code/items/awaymission_gun.dm
+++ b/modular_ss220/awaymission_gun/code/items/awaymission_gun.dm
@@ -14,11 +14,11 @@
/obj/item/gun/energy/laser/awaymission_aeg/Initialize(mapload)
. = ..()
// Force update it incase it spawns outside an away mission and shouldnt be charged
- onTransitZ(new_z = loc.z)
+ on_changed_z_level(new_turf = loc.z)
-/obj/item/gun/energy/laser/awaymission_aeg/onTransitZ(old_z, new_z)
+/obj/item/gun/energy/laser/awaymission_aeg/on_changed_z_level(turf/old_turf, turf/new_turf)
. = ..()
- if(is_away_level(new_z))
+ if(is_away_level(new_turf))
if(ismob(loc))
to_chat(loc, span_notice("Ваш [src] активируется, начиная аккумулировать энергию из материи сущего."))
selfcharge = TRUE
diff --git a/modular_ss220/clumsy_table/code/clumsy_climb_component.dm b/modular_ss220/clumsy_table/code/clumsy_climb_component.dm
index 69ce54a64411..4a87258d9509 100644
--- a/modular_ss220/clumsy_table/code/clumsy_climb_component.dm
+++ b/modular_ss220/clumsy_table/code/clumsy_climb_component.dm
@@ -8,18 +8,24 @@
var/max_thrown_objects = 15
// max possible thrown objects when not forced
var/max_thrown_objects_low = 5
+ /// what we set connect_loc to if parent is an atom
+ var/static/list/default_connections = list(
+ COMSIG_ATOM_ENTERED = PROC_REF(cross),
+ )
/datum/component/clumsy_climb/Initialize()
if(!isatom(parent))
return COMPONENT_INCOMPATIBLE
+ if(ismovable(parent))
+ AddComponent(/datum/component/connect_loc_behalf, parent, default_connections)
+
/datum/component/clumsy_climb/RegisterWithParent()
- RegisterSignal(parent, COMSIG_MOVABLE_CROSSED, PROC_REF(cross))
RegisterSignal(parent, COMSIG_CLIMBED_ON, PROC_REF(cross))
RegisterSignal(parent, COMSIG_DANCED_ON, PROC_REF(cross))
/datum/component/clumsy_climb/UnregisterFromParent()
- UnregisterSignal(parent, list(COMSIG_MOVABLE_CROSSED, COMSIG_CLIMBED_ON, COMSIG_DANCED_ON))
+ UnregisterSignal(parent, list(COMSIG_CLIMBED_ON, COMSIG_DANCED_ON))
/datum/component/clumsy_climb/proc/cross(atom/table, mob/living/user)
SIGNAL_HANDLER
@@ -63,8 +69,8 @@
var/list/items_to_throw = list()
- var/turf/user_turf = get_turf(user)
- for(var/obj/item/candidate_to_throw in user_turf)
+ var/turf/parent_turf = get_turf(parent)
+ for(var/obj/item/candidate_to_throw in parent_turf)
if(length(items_to_throw) >= max_thrown_objects)
break
@@ -74,7 +80,7 @@
items_to_throw += candidate_to_throw
for(var/obj/item/item_to_throw as anything in items_to_throw)
- var/atom/thrown_target = get_edge_target_turf(user, get_dir(user_turf, get_step_away(item_to_throw, user_turf)))
+ var/atom/thrown_target = get_edge_target_turf(user, get_dir(parent_turf, get_step_away(item_to_throw, parent_turf)))
item_to_throw.throw_at(target = thrown_target, range = 1, speed = 1)
item_to_throw.force *= force_mod
item_to_throw.throwforce *= force_mod //no killing using shards :lul:
diff --git a/modular_ss220/clumsy_table/code/clumsy_table.dm b/modular_ss220/clumsy_table/code/clumsy_table.dm
index 3edfd64c3c18..ffd53b96e096 100644
--- a/modular_ss220/clumsy_table/code/clumsy_table.dm
+++ b/modular_ss220/clumsy_table/code/clumsy_table.dm
@@ -1,6 +1,12 @@
-/obj/structure/table/Crossed(atom/movable/AM, oldloc)
- AddComponent(/datum/component/clumsy_climb, 5)
+/obj/structure/table/Initialize(mapload)
. = ..()
+ var/static/list/loc_connections = list(
+ COMSIG_ATOM_ENTERED = PROC_REF(on_crossed)
+ )
+ AddElement(/datum/element/connect_loc, loc_connections)
+
+/obj/structure/table/proc/on_crossed(atom/crosser)
+ AddComponent(/datum/component/clumsy_climb, 5)
/obj/structure/table/do_climb(mob/living/user)
. = ..()
diff --git a/modular_ss220/maps220/code/mobs.dm b/modular_ss220/maps220/code/mobs.dm
index fbd75b1f3d2d..89e3e303ee75 100644
--- a/modular_ss220/maps220/code/mobs.dm
+++ b/modular_ss220/maps220/code/mobs.dm
@@ -424,6 +424,17 @@
L.reagents.add_reagent(poison_type, poison_per_bite)
return .
+/obj/effect/landmark/awaymissions/Initialize(mapload)
+ . = ..()
+ var/static/list/loc_connections = list(
+ COMSIG_ATOM_ENTERED = PROC_REF(on_atom_entered),
+ )
+ AddElement(/datum/element/connect_loc, loc_connections)
+
+/obj/effect/landmark/awaymissions/proc/on_atom_entered(datum/source, atom/movable/entered)
+ SIGNAL_HANDLER
+ return
+
/* Jungle Mob Spawners */
/obj/effect/landmark/awaymissions/gate_lizard/mine_spawner
icon = 'icons/obj/restraints.dmi'
@@ -440,10 +451,10 @@
var/id = null
var/jungle_mob = null
-/obj/effect/landmark/awaymissions/gate_lizard/mine_spawner/Crossed(AM as mob|obj, oldloc)
- if(!isliving(AM))
+/obj/effect/landmark/awaymissions/gate_lizard/mine_spawner/on_atom_entered(datum/source, atom/movable/entered)
+ if(!isliving(source))
return
- var/mob/living/M = AM
+ var/mob/living/M = source
if(faction && (faction in M.faction))
return
triggerlandmark(M)
@@ -1285,10 +1296,10 @@
var/id = null
var/syndi_mob = null
-/obj/effect/landmark/awaymissions/spacebattle/mine_spawner/Crossed(AM as mob|obj, oldloc)
- if(!isliving(AM))
+/obj/effect/landmark/awaymissions/spacebattle/mine_spawner/on_atom_entered(datum/source, atom/movable/entered)
+ if(!isliving(source))
return
- var/mob/living/M = AM
+ var/mob/living/M = source
if(faction && (faction in M.faction))
return
triggerlandmark(M)
diff --git a/modular_ss220/mobs/code/simple_animal/friendly/frog.dm b/modular_ss220/mobs/code/simple_animal/friendly/frog.dm
index b5b181feddd6..c95da6612bd6 100644
--- a/modular_ss220/mobs/code/simple_animal/friendly/frog.dm
+++ b/modular_ss220/mobs/code/simple_animal/friendly/frog.dm
@@ -59,18 +59,27 @@
var/squeak_sound = list ('modular_ss220/mobs/sound/creatures/frog_scream1.ogg','modular_ss220/mobs/sound/creatures/frog_scream2.ogg')
gold_core_spawnable = NO_SPAWN
+
+/mob/living/simple_animal/frog/Initialize(mapload)
+ . = ..()
+ var/static/list/loc_connections = list(
+ COMSIG_ATOM_ENTERED = PROC_REF(on_atom_entered),
+ )
+ AddElement(/datum/element/connect_loc, loc_connections)
+
// Frog procs
/mob/living/simple_animal/frog/attack_hand(mob/living/carbon/human/M as mob)
if(M.a_intent == INTENT_HELP)
get_scooped(M)
..()
-/mob/living/simple_animal/frog/Crossed(AM as mob|obj, oldloc)
- if(ishuman(AM))
- if(!stat)
- var/mob/M = AM
- to_chat(M, span_notice("[bicon(src)] квакнул!"))
- ..()
+/mob/living/simple_animal/frog/proc/on_atom_entered(datum/source, atom/movable/entered)
+ SIGNAL_HANDLER
+ if(!ishuman(source))
+ return
+ if(stat)
+ return
+ to_chat(source, span_notice("[bicon(src)] квакнул!"))
// Toxic frog procs
/mob/living/simple_animal/frog/toxic/attack_hand(mob/living/carbon/human/H as mob)
@@ -85,16 +94,18 @@
..()
..()
-/mob/living/simple_animal/frog/toxic/Crossed(AM as mob|obj, oldloc)
- if(ishuman(AM))
- var/mob/living/carbon/human/H = AM
- if(!istype(H.shoes, /obj/item/clothing/shoes))
- for(var/obj/item/organ/external/F in H.bodyparts)
- if(!F.is_robotic())
- if((F.body_part == FOOT_LEFT) || (F.body_part == FOOT_RIGHT))
- toxin_affect(H)
- to_chat(H, span_warning("Ваши ступни начинают чесаться!"))
+/mob/living/simple_animal/frog/toxic/on_atom_entered(datum/source, atom/movable/entered)
..()
+ if(!ishuman(source))
+ return
+ var/mob/living/carbon/human/H = source
+ if(istype(H.shoes, /obj/item/clothing/shoes))
+ return
+ for(var/obj/item/organ/external/F in H.bodyparts)
+ if(F.is_robotic() || (F.body_part != FOOT_LEFT && !F.body_part == FOOT_RIGHT))
+ continue
+ toxin_affect(H)
+ to_chat(H, span_warning("Ваши ступни начинают чесаться!"))
/mob/living/simple_animal/frog/toxic/proc/toxin_affect(mob/living/carbon/human/M as mob)
if(M.reagents && !toxin_per_touch == 0)
diff --git a/modular_ss220/mobs/code/simple_animal/friendly/hamster.dm b/modular_ss220/mobs/code/simple_animal/friendly/hamster.dm
index 847c3ce70874..6e7b84e28ecc 100644
--- a/modular_ss220/mobs/code/simple_animal/friendly/hamster.dm
+++ b/modular_ss220/mobs/code/simple_animal/friendly/hamster.dm
@@ -35,6 +35,14 @@
can_collar = 0
holder_type = /obj/item/holder/hamster
+
+/mob/living/simple_animal/mouse/hamster/baby/Initialize(mapload)
+ . = ..()
+ var/static/list/loc_connections = list(
+ COMSIG_ATOM_ENTERED = PROC_REF(on_atom_entered),
+ )
+ AddElement(/datum/element/connect_loc, loc_connections)
+
// Hamster procs
#define MAX_HAMSTER 20
GLOBAL_VAR_INIT(hamster_count, 0)
@@ -86,11 +94,9 @@ GLOBAL_VAR_INIT(hamster_count, 0)
mind.transfer_to(A)
qdel(src)
-/mob/living/simple_animal/mouse/hamster/baby/Crossed(AM as mob|obj, oldloc)
- if(ishuman(AM))
- if(!stat)
- var/mob/M = AM
- to_chat(M, span_notice("[bicon(src)] раздавлен!"))
- death()
- splat(user = AM)
- ..()
+/mob/living/simple_animal/mouse/hamster/baby/on_atom_entered(datum/source, atom/movable/entered)
+ if(!ishuman(source) || stat)
+ return ..()
+ to_chat(source, span_notice("[bicon(src)] раздавлен!"))
+ death()
+ splat(user = source)
diff --git a/modular_ss220/mobs/code/simple_animal/hostile/syndi_rat.dm b/modular_ss220/mobs/code/simple_animal/hostile/syndi_rat.dm
index 0a780c08c10c..52e17acd3596 100644
--- a/modular_ss220/mobs/code/simple_animal/hostile/syndi_rat.dm
+++ b/modular_ss220/mobs/code/simple_animal/hostile/syndi_rat.dm
@@ -49,6 +49,10 @@
/mob/living/simple_animal/hostile/retaliate/syndirat/Initialize(mapload)
. = ..()
AddComponent(/datum/component/squeak, list('sound/creatures/mousesqueak.ogg' = 1), 100, extrarange = SHORT_RANGE_SOUND_EXTRARANGE) //as quiet as a mouse or whatever
+ var/static/list/loc_connections = list(
+ COMSIG_ATOM_ENTERED = PROC_REF(on_atom_entered),
+ )
+ AddElement(/datum/element/connect_loc, loc_connections)
/mob/living/simple_animal/hostile/retaliate/syndirat/handle_automated_action()
if(prob(chew_probability) && isturf(loc))
@@ -85,12 +89,13 @@
else if(prob(0.5))
on_lying_down()
-/mob/living/simple_animal/hostile/retaliate/syndirat/Crossed(AM as mob|obj, oldloc)
- if(ishuman(AM))
- if(!stat)
- var/mob/M = AM
- to_chat(M, span_notice("[bicon(src)] Squeek!"))
- ..()
+/mob/living/simple_animal/hostile/retaliate/syndirat/proc/on_atom_entered(datum/source, atom/movable/entered)
+ SIGNAL_HANDLER
+ if(!ishuman(source))
+ return
+ if(stat)
+ return
+ to_chat(source, span_notice("[bicon(src)] Squeek!"))
/mob/living/simple_animal/hostile/retaliate/syndirat/emote(emote_key, type_override = 1, message, intentional, force_silence)
if(stat != CONSCIOUS)
diff --git a/modular_ss220/modular_ss220.dme b/modular_ss220/modular_ss220.dme
index e80023eb857f..4b3c55d3098d 100644
--- a/modular_ss220/modular_ss220.dme
+++ b/modular_ss220/modular_ss220.dme
@@ -5,7 +5,6 @@
// --- MAINTENANCE --- //
#include "_rust_utils/_rust_utils.dme"
-#include "_components/_components.dme"
#include "_defines220/_defines220.dme"
#include "_signals220/_signals220.dme"
#include "_misc/_misc.dme"
@@ -116,6 +115,6 @@
// #include "crit_rework/_crit_rework.dme"
// --- TESTING --- //
-#ifdef UNIT_TESTS
+#ifdef GAME_TESTS
#include "unit_tests/_unit_tests.dme"
#endif
diff --git a/modular_ss220/objects/code/officetoys.dm b/modular_ss220/objects/code/officetoys.dm
index 78443e9b4b17..2f88e7b96c49 100644
--- a/modular_ss220/objects/code/officetoys.dm
+++ b/modular_ss220/objects/code/officetoys.dm
@@ -109,12 +109,14 @@
else
icon_state = "[initial(icon_state)]"
-/obj/item/toy/desk/attack_self__legacy__attackchain(mob/user)
+/obj/item/toy/desk/activate_self(mob/user)
+ . = ..()
+ if(.)
+ return
on = !on
if(activation_sound)
playsound(src.loc, activation_sound, 75, 1)
update_icon()
- return TRUE
/obj/item/toy/desk/examine(mob/user)
. = ..()
@@ -155,7 +157,10 @@
. = ..()
soundloop = new(list(src), FALSE)
-/obj/item/toy/desk/newtoncradle/attack_self__legacy__attackchain(mob/user)
+/obj/item/toy/desk/newtoncradle/activate_self(mob/user)
+ . = ..()
+ if(.)
+ return
on = !on
update_icon()
if(on)
@@ -173,7 +178,10 @@
. = ..()
soundloop = new(list(src), FALSE)
-/obj/item/toy/desk/fan/attack_self__legacy__attackchain(mob/user)
+/obj/item/toy/desk/fan/activate_self(mob/user)
+ . = ..()
+ if(.)
+ return
on = !on
update_icon()
if(on)
diff --git a/modular_ss220/objects/code/platform.dm b/modular_ss220/objects/code/platform.dm
index c7086e605748..4593785042ec 100644
--- a/modular_ss220/objects/code/platform.dm
+++ b/modular_ss220/objects/code/platform.dm
@@ -27,6 +27,10 @@
/obj/structure/platform/Initialize(mapload)
. = ..()
CheckLayer()
+ var/static/list/loc_connections = list(
+ COMSIG_ATOM_EXIT = PROC_REF(on_atom_exit),
+ )
+ AddElement(/datum/element/connect_loc, loc_connections)
/obj/structure/platform/New()
..()
@@ -87,23 +91,27 @@
qdel(src)
-/obj/structure/platform/CheckExit(atom/movable/O, turf/target)
+/obj/structure/platform/proc/on_atom_exit(datum/source, atom/movable/leaving, direction)
+ SIGNAL_HANDLER
+
if(!anchored)
CheckLayer()
- if(istype(O, /obj/structure/platform))
- return FALSE
- if(istype(O, /obj/item/projectile) || istype(O, /obj/effect))
- return TRUE
- if(corner)
- return !density
- if(O && O.throwing)
- return TRUE
- if(((flags & ON_BORDER) && get_dir(loc, target) == dir))
- return FALSE
- else
- return TRUE
+ if(istype(leaving, /obj/structure/platform))
+ return COMPONENT_ATOM_BLOCK_EXIT
+ if(istype(leaving, /obj/item/projectile) || istype(leaving, /obj/effect))
+ return
+ var/mob/living/M = leaving
+ if(istype(M))
+ if(HAS_TRAIT(M, TRAIT_FLYING) || M.floating || (IS_HORIZONTAL(M) && HAS_TRAIT(M, TRAIT_CONTORTED_BODY)))
+ return
+ if(leaving.throwing)
+ return
+ if(leaving.move_force >= MOVE_FORCE_EXTREMELY_STRONG)
+ return
+ if(!(flags & ON_BORDER) || direction == dir)
+ return COMPONENT_ATOM_BLOCK_EXIT
-/obj/structure/platform/CanPass(atom/movable/mover, turf/target)
+/obj/structure/platform/CanPass(atom/movable/mover, border_dir)
if(!anchored)
CheckLayer()
if(istype(mover, /obj/structure/platform))
@@ -117,10 +125,9 @@
var/obj/structure/S = locate(/obj/structure) in get_turf(mover)
if(S && S.climbable && !(S.flags & ON_BORDER) && climbable && isliving(mover))// Climbable objects allow you to universally climb over others
return TRUE
- if(!(flags & ON_BORDER) || get_dir(loc, target) == dir)
+ if(!(flags & ON_BORDER) || border_dir == dir)
return FALSE
- else
- return TRUE
+ return TRUE
/obj/structure/platform/do_climb(mob/living/user)
if(!can_touch(user) || !climbable)
diff --git a/modular_ss220/objects/code/plushies/hampters.dm b/modular_ss220/objects/code/plushies/hampters.dm
index 430269fdeb3c..38fbeb436d29 100644
--- a/modular_ss220/objects/code/plushies/hampters.dm
+++ b/modular_ss220/objects/code/plushies/hampters.dm
@@ -6,7 +6,10 @@
RegisterSignal(parent, list(COMSIG_ATOM_HULK_ATTACK, COMSIG_ATTACK_BY, COMSIG_MOVABLE_BUMP, COMSIG_ATTACK, COMSIG_ATTACK_OBJ), PROC_REF(play_squeak))
// Пищит при наступании
- RegisterSignal(parent, COMSIG_MOVABLE_CROSSED, PROC_REF(play_squeak_crossed))
+ var/static/list/crossed_connections = list(
+ COMSIG_ATOM_ENTERED = PROC_REF(play_squeak_crossed),
+ )
+ AddComponent(/datum/component/connect_loc_behalf, parent, crossed_connections)
// Пищание
/datum/component/plushtoy/proc/play_squeak()
@@ -65,34 +68,34 @@
AddComponent(/datum/component/plushtoy)
// Действия при взаимодействии в руке при разных интентах
-/obj/item/toy/hampter/attack_self__legacy__attackchain(mob/living/carbon/human/user)
+/obj/item/toy/hampter/activate_self(mob/user)
. = ..()
- // Небольшой кулдаун дабы нельзя было спамить
- if(cooldown < world.time - 10)
- switch(user.a_intent)
- // Если выбрано что угодно кроме харма - жмякаем с писком хамптера
- if(INTENT_HELP, INTENT_DISARM, INTENT_GRAB)
- playsound(get_turf(src), squeak, 50, 1, -10)
-
- // Если выбран харм, сжимаем хамптера до "краски" (?) в его туловище
- if(INTENT_HARM)
+ if(. || cooldown >= world.time - 1 SECONDS)
+ return
+ switch(user.a_intent)
+ // Если выбрано что угодно кроме харма - жмякаем с писком хамптера
+ if(INTENT_HELP, INTENT_DISARM, INTENT_GRAB)
+ playsound(get_turf(src), squeak, 50, 1, -10)
+
+ // Если выбран харм, сжимаем хамптера до "краски" (?) в его туловище
+ if(INTENT_HARM)
+ if(istype(user, /mob/living/carbon/human))
+ var/mob/living/carbon/human/human = user
+
// Прописываю это здесь ибо иначе хомяки будут отмечаться кровавыми в игре
blood_DNA = "Plush hampter's paint"
-
- user.visible_message(
- span_warning("[user] раздавил хамптера в своей руке!"),
+ human.visible_message(
+ span_warning("[human] раздавил хамптера в своей руке!"),
span_warning("Вы раздавили хамптера в своей руке!"))
playsound(get_turf(src), "bonebreak", 50, TRUE, -10)
+ human.hand_blood_color = blood_color
+ human.transfer_blood_dna(blood_DNA)
- user.hand_blood_color = blood_color
- user.transfer_blood_dna(blood_DNA)
// Сколько бы я не хотел ставить 0 - не выйдет. Нельзя будет отмыть руки в раковине
- user.bloody_hands = 1
- user.update_inv_gloves()
-
+ human.bloody_hands = 1
+ human.update_inv_gloves()
qdel(src)
-
- cooldown = world.time
+ cooldown = world.time
// Подвиды
/obj/item/toy/hampter/assistant
diff --git a/modular_ss220/objects/code/plushies/macvulpix.dm b/modular_ss220/objects/code/plushies/macvulpix.dm
index db999bd8a894..a690a0b5ef77 100644
--- a/modular_ss220/objects/code/plushies/macvulpix.dm
+++ b/modular_ss220/objects/code/plushies/macvulpix.dm
@@ -30,9 +30,12 @@
M.update_inv_r_hand()
M.update_inv_l_hand()
-/obj/item/toy/plushie/macvulpix/attackby__legacy__attackchain(obj/item/clothing/glasses/sunglasses, mob/living/user, params)
+/obj/item/toy/plushie/macvulpix/attack_by(obj/item/attacking, mob/user, params)
. = ..()
- if(is_type_in_list(sunglasses, allowed_glasses))
+ if(.)
+ return
+ if(is_type_in_list(attacking, allowed_glasses))
+ var/obj/item/clothing/glasses/sunglasses = attacking
user.drop_item()
sunglasses.forceMove(src)
glasses = sunglasses
diff --git a/modular_ss220/objects/code/tribune.dm b/modular_ss220/objects/code/tribune.dm
index f683a108fa27..62f175fccb7b 100644
--- a/modular_ss220/objects/code/tribune.dm
+++ b/modular_ss220/objects/code/tribune.dm
@@ -38,6 +38,11 @@
..()
handle_layer()
+ var/static/list/loc_connections = list(
+ COMSIG_ATOM_EXIT = PROC_REF(on_atom_exit),
+ )
+ AddElement(/datum/element/connect_loc, loc_connections)
+
/obj/structure/tribune/setDir(newdir)
..()
handle_layer()
@@ -61,19 +66,19 @@
setDir(turn(dir, 90))
after_rotation(user)
-/obj/structure/tribune/CanPass(atom/movable/mover, turf/target, height=0)
+/obj/structure/tribune/CanPass(atom/movable/mover, border_dir)
if(istype(mover) && mover.checkpass(PASSGLASS))
return TRUE
- if(get_dir(loc, target) == dir)
+ if(border_dir == dir)
return !density
return TRUE
-/obj/structure/tribune/CheckExit(atom/movable/object, target)
- if(istype(object) && object.checkpass(PASSGLASS))
- return TRUE
- if(get_dir(object.loc, target) == dir)
- return FALSE
- return TRUE
+/obj/structure/tribune/proc/on_atom_exit(datum/source, atom/movable/leaving, direction)
+ SIGNAL_HANDLER
+ if(istype(leaving) && leaving.checkpass(PASSGLASS))
+ return
+ if(direction == dir)
+ return COMPONENT_ATOM_BLOCK_EXIT
/obj/structure/tribune/centcom
name = "CentCom tribune"
diff --git a/modular_ss220/pixel_shift/code/pixel_shift_component.dm b/modular_ss220/pixel_shift/code/pixel_shift_component.dm
index ad2055219623..e463072b7b28 100644
--- a/modular_ss220/pixel_shift/code/pixel_shift_component.dm
+++ b/modular_ss220/pixel_shift/code/pixel_shift_component.dm
@@ -37,7 +37,7 @@
pixel_shift(source, movement_dir)
return COMPONENT_BLOCK_SPACEMOVE
-/datum/component/pixel_shift/proc/check_passable(mob/source, atom/movable/mover, target, height)
+/datum/component/pixel_shift/proc/check_passable(mob/source, atom/movable/mover, border_dir)
SIGNAL_HANDLER
var/mob/living/carbon/human/owner = parent
if(!istype(mover, /obj/item/projectile) && !mover.throwing && passthroughable & get_dir(owner, mover))
diff --git a/modular_ss220/unit_tests/code/requests_console.dm b/modular_ss220/unit_tests/code/requests_console.dm
index 28dab3c5aa2d..018b0f21085a 100644
--- a/modular_ss220/unit_tests/code/requests_console.dm
+++ b/modular_ss220/unit_tests/code/requests_console.dm
@@ -7,12 +7,12 @@
#define SUPPLY_AREAS list("Cargo Bay", "Mining Dock", "Mining Outpost", "Quartermaster's Desk", "Mining", "Expedition")
#define OTHER_AREAS list("Arrival Shuttle", "Crew Quarters", "Tool Storage", "EVA", "Labor Camp", "AI")
-/datum/unit_test/requests_console
+/datum/game_test/requests_console
-/datum/unit_test/requests_console/Run()
+/datum/game_test/requests_console/Run()
all_department_names_in_set()
-/datum/unit_test/requests_console/proc/all_department_names_in_set()
+/datum/game_test/requests_console/proc/all_department_names_in_set()
var/list/expected_departments = list()
expected_departments |= ENGI_AREAS
expected_departments |= SEC_AREAS
diff --git a/modular_ss220/wire_splicing/code/wiresplicing.dm b/modular_ss220/wire_splicing/code/wiresplicing.dm
index 952f4b6eb4a3..58e5d9a0e183 100644
--- a/modular_ss220/wire_splicing/code/wiresplicing.dm
+++ b/modular_ss220/wire_splicing/code/wiresplicing.dm
@@ -41,6 +41,11 @@
if(messiness > 2)
layer = LOW_OBJ_LAYER // I wont do such stuff on splicing "reinforcement". Take it as nasty feature
+ var/static/list/loc_connections = list(
+ COMSIG_ATOM_ENTERED = PROC_REF(on_atom_entered),
+ )
+ AddElement(/datum/element/connect_loc, loc_connections)
+
// Wire splice can only exist on a cable. Lets try to place it in a good location
if(locate(/obj/structure/cable) in get_turf(src)) // If we're already in a good location, no problem!
return
@@ -99,16 +104,16 @@
. = ..()
. += span_warning("It has [messiness] wire[messiness > 1 ? "s" : ""] dangling around.")
-/obj/structure/wire_splicing/Crossed(atom/movable/AM, oldloc)
- . = ..()
- if(isliving(AM))
+/obj/structure/wire_splicing/proc/on_atom_entered(datum/source, atom/movable/entered)
+ SIGNAL_HANDLER
+ if(isliving(entered))
var/chance_to_shock = messiness * shock_chance_per_messiness
/*
var/turf/T = get_turf(src)
if(locate(/obj/structure/catwalk) in T)
chance_to_shock -= 20
*/
- shock(AM, chance_to_shock)
+ shock(entered, chance_to_shock)
/obj/structure/wire_splicing/proc/shock(mob/living/user, prb, siemens_coeff = 1)
. = FALSE
diff --git a/paradise.dme b/paradise.dme
index b62d925c2b4a..d0c7b2a6dd2f 100644
--- a/paradise.dme
+++ b/paradise.dme
@@ -94,6 +94,7 @@
#include "code\__DEFINES\mob_defines.dm"
#include "code\__DEFINES\mod.dm"
#include "code\__DEFINES\move_force.dm"
+#include "code\__DEFINES\movement_info.dm"
#include "code\__DEFINES\muzzle_flash.dm"
#include "code\__DEFINES\newscaster_defines.dm"
#include "code\__DEFINES\particle_defines.dm"
@@ -165,6 +166,7 @@
#include "code\__HELPERS\_string_lists.dm"
#include "code\__HELPERS\AnimationLibrary.dm"
#include "code\__HELPERS\api.dm"
+#include "code\__HELPERS\atom_helpers.dm"
#include "code\__HELPERS\bitflag_lists.dm"
#include "code\__HELPERS\cmp.dm"
#include "code\__HELPERS\colour_helpers.dm"
@@ -381,6 +383,7 @@
#include "code\datums\beam.dm"
#include "code\datums\browser.dm"
#include "code\datums\callback.dm"
+#include "code\datums\card_deck_table_tracker.dm"
#include "code\datums\chat_payload.dm"
#include "code\datums\chatmessage.dm"
#include "code\datums\click_intercept.dm"
@@ -431,7 +434,10 @@
#include "code\datums\components\boss_music.dm"
#include "code\datums\components\caltrop.dm"
#include "code\datums\components\codeword_hearing.dm"
+#include "code\datums\components\connect_containers.dm"
+#include "code\datums\components\connect_loc_behalf.dm"
#include "code\datums\components\connect_mob_behalf.dm"
+#include "code\datums\components\connect_range.dm"
#include "code\datums\components\corpse_description.dm"
#include "code\datums\components\cult_held_body.dm"
#include "code\datums\components\deadchat_control.dm"
@@ -450,7 +456,6 @@
#include "code\datums\components\paintable.dm"
#include "code\datums\components\parry.dm"
#include "code\datums\components\persistent_overlay.dm"
-#include "code\datums\components\proximity_monitor.dm"
#include "code\datums\components\radioactive.dm"
#include "code\datums\components\scope.dm"
#include "code\datums\components\shelved.dm"
@@ -531,6 +536,7 @@
#include "code\datums\elements\atmos_requirements.dm"
#include "code\datums\elements\body_temperature.dm"
#include "code\datums\elements\bombable_turf.dm"
+#include "code\datums\elements\connect_loc.dm"
#include "code\datums\elements\decal_element.dm"
#include "code\datums\elements\earhealing.dm"
#include "code\datums\elements\high_value_item.dm"
@@ -568,6 +574,9 @@
#include "code\datums\outfits\outfit_debug.dm"
#include "code\datums\outfits\plasmamen_outfits.dm"
#include "code\datums\outfits\vv_outfit.dm"
+#include "code\datums\proximity\advanced_proximity_monitor.dm"
+#include "code\datums\proximity\proximity_monitor.dm"
+#include "code\datums\proximity\singulo_proximity_monitor.dm"
#include "code\datums\ruins\lavaland.dm"
#include "code\datums\ruins\ruin_placer.dm"
#include "code\datums\ruins\space_ruins.dm"
@@ -935,7 +944,6 @@
#include "code\game\machinery\doors\firedoor.dm"
#include "code\game\machinery\doors\poddoor.dm"
#include "code\game\machinery\doors\shutters.dm"
-#include "code\game\machinery\doors\unpowered.dm"
#include "code\game\machinery\doors\windowdoor.dm"
#include "code\game\machinery\pipe\pipe_construction.dm"
#include "code\game\machinery\tcomms\nttc.dm"
@@ -2993,7 +3001,6 @@
#include "code\modules\tgui_input\say_modal\tgui_say_speech.dm"
#include "code\modules\tgui_input\say_modal\tgui_say_typing.dm"
#include "code\modules\tooltip\tooltip.dm"
-#include "code\modules\unit_tests\_unit_tests.dm"
#include "code\modules\vehicle\ambulance.dm"
#include "code\modules\vehicle\atv.dm"
#include "code\modules\vehicle\bicycle.dm"
@@ -3021,6 +3028,7 @@
#include "code\modules\world_topic\pr_announce_topic.dm"
#include "code\modules\world_topic\queue_status.dm"
#include "code\modules\world_topic\status.dm"
+#include "code\tests\game_tests.dm"
#include "interface\interface.dm"
#include "interface\skin.dmf"
// END_INCLUDE
diff --git a/tools/ci/check_legacy_attack_chain.py b/tools/ci/check_legacy_attack_chain.py
index b56a1027affb..92a8d30067d7 100644
--- a/tools/ci/check_legacy_attack_chain.py
+++ b/tools/ci/check_legacy_attack_chain.py
@@ -35,6 +35,14 @@ def format_error(self):
return f"{self.source_info.file_path}:{self.source_info.line}: {RED}{self.make_error_message()}{NC}"
+def make_error_from_procdecl(proc_decl: ProcDecl, msg) -> str:
+ if os.getenv("GITHUB_ACTIONS") == "true":
+ return f"::error file={proc_decl.source_info.file_path},line={proc_decl.source_info.line},title=Attack Chain::{proc_decl.source_info.file_path}:{proc_decl.source_info.line}: {RED}{msg}{NC}"
+
+ else:
+ return f"{proc_decl.source_info.file_path}:{proc_decl.source_info.line}: {RED}{msg}{NC}"
+
+
# Walker for determining if a proc contains any calls to a legacy attack chain
# proc on an object that is in the type tree of our migrating type.
#
@@ -149,11 +157,10 @@ def visit_Expr(self, node, source_info):
start = time.time()
CALLS = defaultdict(set)
+ ERROR_STRINGS = list()
+ LEGACY_PROCS = defaultdict(list)
+ MODERN_PROCS = defaultdict(list)
SETTING_CACHE = dict()
- LEGACY_PROCS = dict()
- MODERN_PROCS = dict()
- BAD_TREES = dict()
- PROCS = dict()
dme = DME.from_file("paradise.dme", parse_procs=True)
@@ -171,44 +178,57 @@ def visit_Expr(self, node, source_info):
)
except:
SETTING_CACHE[pth] = False
- LEGACY_PROCS[pth] = {
- x for x in td.proc_names(modified=True) if "__legacy__attackchain" in x
- }
- MODERN_PROCS[pth] = {x for x in td.proc_names(modified=True) if x in NEW_PROCS}
+ for proc_name in td.proc_names(modified=True):
+ if "__legacy__attackchain" in proc_name:
+ for proc_decl in td.proc_decls(proc_name):
+ LEGACY_PROCS[pth].append(proc_decl)
+ elif proc_name in NEW_PROCS:
+ for proc_decl in td.proc_decls(proc_name):
+ MODERN_PROCS[pth].append(proc_decl)
for proc_decl in td.proc_decls():
walker = AttackChainCallWalker(td, proc_decl)
proc_decl.walk(walker)
- for pth, new_attack_chain in SETTING_CACHE.items():
+ for pth in sorted(SETTING_CACHE.keys()):
+ new_attack_chain = SETTING_CACHE[pth]
cursor = pth
if new_attack_chain:
if LEGACY_PROCS[pth]:
exit_code = 1
- print(f"new_attack_chain on {pth} still has legacy procs:")
- for proc in sorted(LEGACY_PROCS[pth]):
- print(f"\t{proc}")
+ for proc_decl in sorted(LEGACY_PROCS[pth], key=lambda x: x.name):
+ ERROR_STRINGS.append(
+ make_error_from_procdecl(
+ proc_decl,
+ f"migrated type with legacy proc {pth}/{proc_decl.name}(...)",
+ )
+ )
while cursor not in ASSISTED_TYPES and not cursor.is_root:
if LEGACY_PROCS[cursor] and not SETTING_CACHE[cursor]:
exit_code = 1
print(f"new_attack_chain on {pth} but related type {cursor} is not")
cursor = cursor.parent
if pth in CALLS and any([x.legacy for x in CALLS[pth]]):
- print("Legacy sites requiring migration:")
for call in CALLS[pth]:
if call.legacy:
- print(call.format_error())
+ ERROR_STRINGS.append(call.format_error())
elif pth not in ASSISTED_TYPES:
if MODERN_PROCS[pth]:
exit_code = 1
- print(f"new_attack_chain not on {pth} using new procs:")
- for proc in sorted(MODERN_PROCS[pth]):
- print(f"\t{proc}")
+ for proc_decl in sorted(MODERN_PROCS[pth], key=lambda x: x.name):
+ ERROR_STRINGS.append(
+ make_error_from_procdecl(
+ proc_decl,
+ f"legacy type with migrated proc {pth}/{proc_decl.name}(...)",
+ )
+ )
if pth in CALLS and any([not x.legacy for x in CALLS[pth]]):
exit_code = 1
- print("Unexpected new call sites:")
for call in CALLS[pth]:
if not call.legacy:
- print(call.format_error())
+ ERROR_STRINGS.append(call.format_error())
+
+ for legacy_proc_error in sorted(ERROR_STRINGS):
+ print(legacy_proc_error)
end = time.time()
print(f"check_legacy_attack_chain tests completed in {end - start:.2f}s\n")
diff --git a/tools/ci/unticked_files.py b/tools/ci/unticked_files.py
index e38dd3bd5689..dc427dae25a3 100644
--- a/tools/ci/unticked_files.py
+++ b/tools/ci/unticked_files.py
@@ -21,7 +21,7 @@
INCLUDER_FILES = [
'paradise.dme',
'code/modules/tgs/includes.dm',
- 'code/modules/unit_tests/_unit_tests.dm',
+ 'code/tests/game_tests.dm',
]
IGNORE_FILES = {
diff --git a/tools/maplint/lints/stack_tile.yml b/tools/maplint/lints/stack_tile.yml
new file mode 100644
index 000000000000..568dbed394ec
--- /dev/null
+++ b/tools/maplint/lints/stack_tile.yml
@@ -0,0 +1,2 @@
+=/obj/item/stack/tile:
+ banned: true
diff --git a/tools/maplint/lints/tile_mineral.yml b/tools/maplint/lints/tile_mineral.yml
new file mode 100644
index 000000000000..271dde768fc9
--- /dev/null
+++ b/tools/maplint/lints/tile_mineral.yml
@@ -0,0 +1,2 @@
+=/obj/item/stack/tile/mineral:
+ banned: true