diff --git a/.github/workflows/ci_suite.yml b/.github/workflows/ci_suite.yml
index 30fa28ebbc7..9ffaf9182ae 100644
--- a/.github/workflows/ci_suite.yml
+++ b/.github/workflows/ci_suite.yml
@@ -102,7 +102,7 @@ jobs:
run: |
bash tools/ci/install_byond.sh
source $HOME/BYOND/byond/bin/byondsetup
- tools/build/build --ci dm -DCIBUILDING
+ tools/build/build --ci dm -DCIBUILDING -DCITESTING
bash tools/ci/run_server.sh
env:
CBT_BUILD_MODE: TEST_RUN
diff --git a/_maps/away_missions/archive/stationCollision.dmm b/_maps/away_missions/archive/stationCollision.dmm
index c9b52a131e2..516fd5ec2fc 100644
--- a/_maps/away_missions/archive/stationCollision.dmm
+++ b/_maps/away_missions/archive/stationCollision.dmm
@@ -58,12 +58,6 @@
},
/turf/simulated/floor/airless,
/area/awaymission/northblock)
-"as" = (
-/obj/machinery/power/solar_control{
- stat = 1
- },
-/turf/simulated/floor,
-/area/awaymission/northblock)
"at" = (
/obj/machinery/power/solar_control,
/turf/simulated/floor,
@@ -781,15 +775,11 @@
/turf/simulated/floor/airless,
/area/awaymission/research)
"dy" = (
-/obj/machinery/message_server{
- stat = 1
- },
+/obj/machinery/message_server,
/turf/simulated/floor/airless,
/area/awaymission/northblock)
"dz" = (
-/obj/machinery/computer/message_monitor{
- stat = 1
- },
+/obj/machinery/computer/message_monitor,
/turf/simulated/floor/airless,
/area/awaymission/northblock)
"dB" = (
@@ -1148,9 +1138,7 @@
/turf/simulated/floor/airless,
/area/awaymission/research)
"eX" = (
-/obj/machinery/r_n_d/server{
- stat = 1
- },
+/obj/machinery/r_n_d/server,
/turf/simulated/floor/airless,
/area/awaymission/research)
"fb" = (
@@ -5248,7 +5236,7 @@ aa
aa
aa
aj
-as
+at
ap
ap
aL
diff --git a/_maps/templates/unit_tests.dmm b/_maps/templates/unit_tests.dmm
new file mode 100644
index 00000000000..cefb42f75f2
--- /dev/null
+++ b/_maps/templates/unit_tests.dmm
@@ -0,0 +1,79 @@
+//MAP CONVERTED BY dmm2tgm.py THIS HEADER COMMENT PREVENTS RECONVERSION, DO NOT REMOVE
+"a" = (
+/turf/unsimulated/wall,
+/area/misc/testroom)
+"m" = (
+/turf/simulated/floor/tiled,
+/area/misc/testroom)
+"r" = (
+/obj/landmark/unit_test_top_right,
+/turf/simulated/floor/tiled,
+/area/misc/testroom)
+"L" = (
+/obj/landmark/unit_test_bottom_left,
+/turf/simulated/floor/tiled,
+/area/misc/testroom)
+
+(1,1,1) = {"
+a
+a
+a
+a
+a
+a
+a
+"}
+(2,1,1) = {"
+a
+m
+m
+m
+m
+L
+a
+"}
+(3,1,1) = {"
+a
+m
+m
+m
+m
+m
+a
+"}
+(4,1,1) = {"
+a
+m
+m
+m
+m
+m
+a
+"}
+(5,1,1) = {"
+a
+m
+m
+m
+m
+m
+a
+"}
+(6,1,1) = {"
+a
+r
+m
+m
+m
+m
+a
+"}
+(7,1,1) = {"
+a
+a
+a
+a
+a
+a
+a
+"}
diff --git a/citadel.dme b/citadel.dme
index 76cb10c84c8..4501a87ca48 100644
--- a/citadel.dme
+++ b/citadel.dme
@@ -18,6 +18,7 @@
#include "_maps\map_files\generic\reservation_base_level.dmm"
#include "_maps\submaps\level_specific\debrisfield_vr\debrisfield_things.dm"
#include "code\___compile_options.dm"
+#include "code\__byond_version_compat.dm"
#include "code\__global_init.dm"
#include "code\_macros.dm"
#include "code\global.dm"
@@ -261,7 +262,6 @@
#include "code\__HELPERS\path.dm"
#include "code\__HELPERS\piping_colors_lists.dm"
#include "code\__HELPERS\ref.dm"
-#include "code\__HELPERS\roundend.dm"
#include "code\__HELPERS\sanitize_values.dm"
#include "code\__HELPERS\shell.dm"
#include "code\__HELPERS\stack_trace.dm"
@@ -283,6 +283,7 @@
#include "code\__HELPERS\icons\flatten.dm"
#include "code\__HELPERS\icons\flatten_old.dm"
#include "code\__HELPERS\lists\_string_lists.dm"
+#include "code\__HELPERS\lists\asset_sorted.dm"
#include "code\__HELPERS\lists\associations.dm"
#include "code\__HELPERS\lists\bitflag_lists.dm"
#include "code\__HELPERS\lists\copy.dm"
@@ -743,6 +744,7 @@
#include "code\game\area\areas_movement.dm"
#include "code\game\area\asteroid_areas.dm"
#include "code\game\area\Away Mission areas.dm"
+#include "code\game\area\misc.dm"
#include "code\game\area\Off Station Areas.dm"
#include "code\game\area\rift_areas.dm"
#include "code\game\area\Ruin Areas.dm"
@@ -938,6 +940,7 @@
#include "code\game\gamemodes\traitor\traitor.dm"
#include "code\game\gamemodes\wizard\wizard.dm"
#include "code\game\landmarks\landmarks.dm"
+#include "code\game\landmarks\unit_test_markers.dm"
#include "code\game\landmarks\spawnpoint\_spawnpoint.dm"
#include "code\game\landmarks\spawnpoint\talon.dm"
#include "code\game\landmarks\spawnpoint\tradeport.dm"
diff --git a/code/__DEFINES/MC.dm b/code/__DEFINES/MC.dm
index e783586fb40..752d4cb1d26 100644
--- a/code/__DEFINES/MC.dm
+++ b/code/__DEFINES/MC.dm
@@ -25,35 +25,41 @@
//! SubSystem flags (Please design any new flags so that the default is off, to make adding flags to subsystems easier)
/// subsystem does not initialize.
-#define SS_NO_INIT 1
+#define SS_NO_INIT (1<<0)
/** subsystem does not fire. */
/// (like can_fire = 0, but keeps it from getting added to the processing subsystems list)
/// (Requires a MC restart to change)
-#define SS_NO_FIRE 2
+#define SS_NO_FIRE (1<<1)
/** subsystem only runs on spare cpu (after all non-background subsystems have ran that tick) */
/// SS_BACKGROUND has its own priority bracket
-#define SS_BACKGROUND 4
+#define SS_BACKGROUND (1<<2)
/// subsystem does not tick check, and should not run unless there is enough time (or its running behind (unless background))
-#define SS_NO_TICK_CHECK 8
+#define SS_NO_TICK_CHECK (1<<3)
/** Treat wait as a tick count, not DS, run every wait ticks. */
/// (also forces it to run first in the tick, above even SS_NO_TICK_CHECK subsystems)
/// (implies all runlevels because of how it works)
/// (overrides SS_BACKGROUND)
/// This is designed for basically anything that works as a mini-mc (like SStimer)
-#define SS_TICKER 16
+#define SS_TICKER (1<<4)
/** keep the subsystem's timing on point by firing early if it fired late last fire because of lag */
/// ie: if a 20ds subsystem fires say 5 ds late due to lag or what not, its next fire would be in 15ds, not 20ds.
-#define SS_KEEP_TIMING 32
+#define SS_KEEP_TIMING (1<<5)
/** Calculate its next fire after its fired. */
/// (IE: if a 5ds wait SS takes 2ds to run, its next fire should be 5ds away, not 3ds like it normally would be)
/// This flag overrides SS_KEEP_TIMING
-#define SS_POST_FIRE_TIMING 64
+#define SS_POST_FIRE_TIMING (1<<6)
+
+/// If this subsystem doesn't initialize, it should not report as a hard error in CI.
+/// This should be used for subsystems that are flaky for complicated reasons, such as
+/// the Lua subsystem, which relies on auxtools, which is unstable. //! We don't have the Lua system, but this is a good example.
+/// It should not be used simply to silence CI.
+#define SS_OK_TO_FAIL_INIT (1<<7)
DEFINE_BITFIELD(subsystem_flags, list(
BITFIELD(SS_NO_INIT),
@@ -62,6 +68,7 @@ DEFINE_BITFIELD(subsystem_flags, list(
BITFIELD(SS_TICKER),
BITFIELD(SS_KEEP_TIMING),
BITFIELD(SS_POST_FIRE_TIMING),
+ BITFIELD(SS_OK_TO_FAIL_INIT),
))
diff --git a/code/__DEFINES/icon_smoothing.dm b/code/__DEFINES/icon_smoothing.dm
index 23e2e5e3ce9..781e2cec3a6 100644
--- a/code/__DEFINES/icon_smoothing.dm
+++ b/code/__DEFINES/icon_smoothing.dm
@@ -74,7 +74,10 @@ DEFINE_BITFIELD(smoothing_junction, list(
* * Matched with the `list/canSmoothWith` variable to check whether smoothing is possible or not.
*/
-#define S_TURF(num) ((24 * 0) + num) /// Not any different from the number itself, but kept this way in case someone wants to expand it by adding stuff before it.
+// Not any different from the number itself, but kept this way in case someone wants to expand it by adding stuff before it.
+#define S_TURF(num) (#num + ",")
+
+
/* /turf only */
#define SMOOTH_GROUP_TURF_OPEN S_TURF(0) ///? /turf/simulated/floor
@@ -104,60 +107,60 @@ DEFINE_BITFIELD(smoothing_junction, list(
#define SMOOTH_GROUP_CARPET_STELLAR S_TURF(21) // /turf/open/floor/carpet/stellar
#define SMOOTH_GROUP_CARPET_DONK S_TURF(22) // /turf/open/floor/carpet/donk
-#define SMOOTH_GROUP_CLOSED_TURFS S_TURF(53) ///? /turf/simulated/wall
-#define SMOOTH_GROUP_SURVIVAL_TITANIUM_WALLS S_TURF(53) // /turf/closed/wall/mineral/titanium/survival
-#define SMOOTH_GROUP_HOTEL_WALLS S_TURF(54) // /turf/closed/indestructible/hotelwall
-#define SMOOTH_GROUP_MINERAL_WALLS S_TURF(55) ///? /turf/simulated/mineral, /turf/unsimulated/mineral
-#define SMOOTH_GROUP_BOSS_WALLS S_TURF(56) // /turf/closed/indestructible/riveted/boss
+#define SMOOTH_GROUP_CLOSED_TURFS S_TURF(23) ///? /turf/simulated/wall
+#define SMOOTH_GROUP_SURVIVAL_TITANIUM_WALLS S_TURF(24) // /turf/closed/wall/mineral/titanium/survival
+#define SMOOTH_GROUP_HOTEL_WALLS S_TURF(25) // /turf/closed/indestructible/hotelwall
+#define SMOOTH_GROUP_MINERAL_WALLS S_TURF(26) ///? /turf/simulated/mineral, /turf/unsimulated/mineral
+#define SMOOTH_GROUP_BOSS_WALLS S_TURF(27) // /turf/closed/indestructible/riveted/boss
-#define MAX_S_TURF SMOOTH_GROUP_BOSS_WALLS /// Always match this value with the one above it.
+#define MAX_S_TURF 27 //!Always match this value with the one above it.
-#define S_OBJ(num) (MAX_S_TURF + 1 + num)
+
+#define S_OBJ(num) ("-" + #num + ",")
+
/* /obj included */
-#define SMOOTH_GROUP_WALLS S_OBJ(0) ///? /turf/simulated/wall
-#define SMOOTH_GROUP_HIERO_WALL S_OBJ(1) // /obj/effect/temp_visual/elite_tumor_wall, /obj/effect/temp_visual/hierophant/wall
-#define SMOOTH_GROUP_SURVIVAL_TIANIUM_POD S_OBJ(2) // /turf/closed/wall/mineral/titanium/survival/pod, /obj/machinery/door/airlock/survival_pod, /obj/structure/window/reinforced/shuttle/survival_pod
+#define SMOOTH_GROUP_WALLS S_OBJ(1) ///? /turf/simulated/wall
+#define SMOOTH_GROUP_HIERO_WALL S_OBJ(2) // /obj/effect/temp_visual/elite_tumor_wall, /obj/effect/temp_visual/hierophant/wall
+#define SMOOTH_GROUP_SURVIVAL_TIANIUM_POD S_OBJ(3) // /turf/closed/wall/mineral/titanium/survival/pod, /obj/machinery/door/airlock/survival_pod, /obj/structure/window/reinforced/shuttle/survival_pod
-#define SMOOTH_GROUP_PAPERFRAME S_OBJ(20) // /obj/structure/window/paperframe, /obj/structure/mineral_door/paperframe
+#define SMOOTH_GROUP_PAPERFRAME S_OBJ(4) // /obj/structure/window/paperframe, /obj/structure/mineral_door/paperframe
-#define SMOOTH_GROUP_WINDOW_FULLTILE S_OBJ(21) ///? /obj/structure/window/basic/full
-#define SMOOTH_GROUP_WINDOW_FULLTILE_BRONZE S_OBJ(22) // /obj/structure/window/bronze/fulltile
-#define SMOOTH_GROUP_WINDOW_FULLTILE_PLASTITANIUM S_OBJ(23) // /turf/closed/indestructible/opsglass, /obj/structure/window/reinforced/plasma/plastitanium
-#define SMOOTH_GROUP_WINDOW_FULLTILE_SHUTTLE S_OBJ(24) ///? /obj/structure/window/shuttle
+#define SMOOTH_GROUP_WINDOW_FULLTILE S_OBJ(5) ///? /obj/structure/window/basic/full
+#define SMOOTH_GROUP_WINDOW_FULLTILE_BRONZE S_OBJ(6) // /obj/structure/window/bronze/fulltile
+#define SMOOTH_GROUP_WINDOW_FULLTILE_PLASTITANIUM S_OBJ(7) // /turf/closed/indestructible/opsglass, /obj/structure/window/reinforced/plasma/plastitanium
+#define SMOOTH_GROUP_WINDOW_FULLTILE_SHUTTLE S_OBJ(8) ///? /obj/structure/window/shuttle
-#define SMOOTH_GROUP_LATTICE S_OBJ(30) ///? /obj/structure/lattice
-#define SMOOTH_GROUP_CATWALK S_OBJ(31) ///? /obj/structure/catwalk
-#define SMOOTH_GROUP_GRILLE S_OBJ(32) ///? /obj/structure/grille
-#define SMOOTH_GROUP_LOW_WALL S_OBJ(33) // /obj/structure/low_wall
+#define SMOOTH_GROUP_LATTICE S_OBJ(9) ///? /obj/structure/lattice
+#define SMOOTH_GROUP_CATWALK S_OBJ(10) ///? /obj/structure/catwalk
+#define SMOOTH_GROUP_GRILLE S_OBJ(11) ///? /obj/structure/grille
+#define SMOOTH_GROUP_LOW_WALL S_OBJ(12) // /obj/structure/low_wall
-#define SMOOTH_GROUP_AIRLOCK S_OBJ(40) ///? /obj/machinery/door/airlock
-#define SMOOTH_GROUP_SHUTTERS_BLASTDOORS S_OBJ(41) ///? /obj/machinery/door/blast
+#define SMOOTH_GROUP_AIRLOCK S_OBJ(13) ///? /obj/machinery/door/airlock
+#define SMOOTH_GROUP_SHUTTERS_BLASTDOORS S_OBJ(14) ///? /obj/machinery/door/blast
-#define SMOOTH_GROUP_TABLES S_OBJ(50) ///? /obj/structure/table
-#define SMOOTH_GROUP_WOOD_TABLES S_OBJ(51) ///? /obj/structure/table/woodentable // TOO MANY STUBS
-#define SMOOTH_GROUP_FANCY_WOOD_TABLES S_OBJ(52) // /obj/structure/table/wood/fancy
-#define SMOOTH_GROUP_BRONZE_TABLES S_OBJ(53) // /obj/structure/table/bronze
-#define SMOOTH_GROUP_ABDUCTOR_TABLES S_OBJ(54) // /obj/structure/table/abductor
-#define SMOOTH_GROUP_GLASS_TABLES S_OBJ(55) ///? /obj/structure/table/glass
+#define SMOOTH_GROUP_TABLES S_OBJ(15) ///? /obj/structure/table
+#define SMOOTH_GROUP_WOOD_TABLES S_OBJ(16) ///? /obj/structure/table/woodentable // TOO MANY STUBS
+#define SMOOTH_GROUP_FANCY_WOOD_TABLES S_OBJ(17) // /obj/structure/table/wood/fancy
+#define SMOOTH_GROUP_BRONZE_TABLES S_OBJ(18) // /obj/structure/table/bronze
+#define SMOOTH_GROUP_ABDUCTOR_TABLES S_OBJ(19) // /obj/structure/table/abductor
+#define SMOOTH_GROUP_GLASS_TABLES S_OBJ(20) ///? /obj/structure/table/glass
-#define SMOOTH_GROUP_ALIEN_NEST S_OBJ(59) // /obj/structure/bed/nest
-#define SMOOTH_GROUP_ALIEN_RESIN S_OBJ(60) // /obj/structure/alien/resin
-#define SMOOTH_GROUP_ALIEN_WALLS S_OBJ(61) // /obj/structure/alien/resin/wall, /obj/structure/alien/resin/membrane
-#define SMOOTH_GROUP_ALIEN_WEEDS S_OBJ(62) // /obj/structure/alien/weeds
+#define SMOOTH_GROUP_ALIEN_NEST S_OBJ(21) // /obj/structure/bed/nest
+#define SMOOTH_GROUP_ALIEN_RESIN S_OBJ(22) // /obj/structure/alien/resin
+#define SMOOTH_GROUP_ALIEN_WALLS S_OBJ(23) // /obj/structure/alien/resin/wall, /obj/structure/alien/resin/membrane
+#define SMOOTH_GROUP_ALIEN_WEEDS S_OBJ(24) // /obj/structure/alien/weeds
-#define SMOOTH_GROUP_SECURITY_BARRICADE S_OBJ(63) // /obj/structure/barricade/security
-#define SMOOTH_GROUP_SANDBAGS S_OBJ(64) ///? /obj/structure/sandbag
+#define SMOOTH_GROUP_SECURITY_BARRICADE S_OBJ(25) // /obj/structure/barricade/security
+#define SMOOTH_GROUP_SANDBAGS S_OBJ(26) ///? /obj/structure/sandbag
-#define SMOOTH_GROUP_HEDGE_FLUFF S_OBJ(65) // /obj/structure/hedge
+#define SMOOTH_GROUP_HEDGE_FLUFF S_OBJ(27) // /obj/structure/hedge
-#define SMOOTH_GROUP_SHUTTLE_PARTS S_OBJ(66) ///? /obj/structure/window/shuttle
+#define SMOOTH_GROUP_SHUTTLE_PARTS S_OBJ(28) ///? /obj/structure/window/shuttle
-#define SMOOTH_GROUP_CLEANABLE_DIRT S_OBJ(67) // /obj/effect/decal/cleanable/dirt
+#define SMOOTH_GROUP_CLEANABLE_DIRT S_OBJ(29) // /obj/effect/decal/cleanable/dirt
-#define SMOOTH_GROUP_INDUSTRIAL_LIFT S_OBJ(70) // /obj/structure/industrial_lift
+#define SMOOTH_GROUP_INDUSTRIAL_LIFT S_OBJ(30) // /obj/structure/industrial_lift
-#define SMOOTH_GROUP_GAS_TANK S_OBJ(71)
-
-#define MAX_S_OBJ SMOOTH_GROUP_GAS_TANK /// Always match this value with the one above it.
+#define SMOOTH_GROUP_GAS_TANK S_OBJ(31)
diff --git a/code/__DEFINES/is_helpers.dm b/code/__DEFINES/is_helpers.dm
index 7b007e056f3..9d472bec230 100644
--- a/code/__DEFINES/is_helpers.dm
+++ b/code/__DEFINES/is_helpers.dm
@@ -14,6 +14,8 @@
//Turfs
+#define isfloorturf(A) (istype(A, /turf/simulated/floor))
+
#define isopenturf(A) istype(A, /turf/simulated/open)
#define isspaceturf(A) istype(A, /turf/space)
diff --git a/code/__DEFINES/unit_tests.dm b/code/__DEFINES/unit_tests.dm
index 111bbe60bf2..ed7cd8490ec 100644
--- a/code/__DEFINES/unit_tests.dm
+++ b/code/__DEFINES/unit_tests.dm
@@ -1,5 +1,22 @@
-#define ASCII_ESC ascii2text(27)
-#define ASCII_RED "[ASCII_ESC]\[31m"
-#define ASCII_GREEN "[ASCII_ESC]\[32m"
-#define ASCII_YELLOW "[ASCII_ESC]\[33m"
-#define ASCII_RESET "[ASCII_ESC]\[0m"
+/**
+ * Are tests enabled with no focus?
+ * Use this when performing test assertions outside of a unit test,
+ * since a focused test means that you're trying to run a test quickly.
+ * If a parameter is provided, will check if the focus is on that test name.
+ * For example, PERFORM_ALL_TESTS(log_mapping) will only run if either
+ * no test is focused, or the focus is log_mapping.
+ */
+#ifdef UNIT_TESTS
+/// Bit of a trick here, if focus isn't passed in then it'll check for /datum/unit_test/, which is never the case.
+#define PERFORM_ALL_TESTS(focus...) (isnull(GLOB.focused_test) || GLOB.focused_test == /datum/unit_test/##focus)
+#else
+/// UNLINT necessary here so that if (PERFORM_ALL_TESTS()) works
+#define PERFORM_ALL_TESTS(...) UNLINT(FALSE)
+#endif
+
+/// ASSERT(), but it only actually does anything during unit tests
+#ifdef UNIT_TESTS
+#define TEST_ONLY_ASSERT(test, explanation) if(!(test)) {CRASH(explanation)}
+#else
+#define TEST_ONLY_ASSERT(test, explanation)
+#endif
diff --git a/code/__HELPERS/icon_smoothing.dm b/code/__HELPERS/icon_smoothing.dm
index 44b3c1f735c..b7f6a6f04f8 100644
--- a/code/__HELPERS/icon_smoothing.dm
+++ b/code/__HELPERS/icon_smoothing.dm
@@ -64,6 +64,37 @@
}; \
} while(FALSE)
+/**
+ * Performs the work to set smoothing_groups and canSmoothWith.
+ * An inlined function used in both turf/Initialize and atom/Initialize.
+ */
+#define SETUP_SMOOTHING(...) \
+ if (smoothing_groups) { \
+ if (PERFORM_ALL_TESTS(focus_only/sorted_smoothing_groups)) { \
+ ASSERT_SORTED_SMOOTHING_GROUPS(smoothing_groups); \
+ } \
+ SET_BITFLAG_LIST(smoothing_groups); \
+ } \
+\
+ if (canSmoothWith) { \
+ if (PERFORM_ALL_TESTS(focus_only/sorted_smoothing_groups)) { \
+ ASSERT_SORTED_SMOOTHING_GROUPS(canSmoothWith); \
+ } \
+ if (canSmoothWith[1] == "-") { \
+ smoothing_flags |= SMOOTH_OBJ; \
+ } \
+ SET_BITFLAG_LIST(canSmoothWith); \
+ }
+
+/// Given a smoothing groups variable, will set out to the actual numbers inside it.
+#define UNWRAP_SMOOTHING_GROUPS(smoothing_groups, out) \
+ json_decode("\[[##smoothing_groups]0\]"); \
+ ##out.len--;
+
+#define ASSERT_SORTED_SMOOTHING_GROUPS(smoothing_group_variable) \
+ var/list/unwrapped = UNWRAP_SMOOTHING_GROUPS(smoothing_group_variable, unwrapped); \
+ assert_sorted(unwrapped, "[#smoothing_group_variable] ([type])"); \
+
/**
* Stole this from @DaedalusDock - @Zandario
* Checks if `src` can smooth with `target`, based on the [/area/var/area_limited_icon_smoothing] variable of their areas.
diff --git a/code/__HELPERS/lists/asset_sorted.dm b/code/__HELPERS/lists/asset_sorted.dm
new file mode 100644
index 00000000000..2c0e6b499c1
--- /dev/null
+++ b/code/__HELPERS/lists/asset_sorted.dm
@@ -0,0 +1,11 @@
+/// Runtimes if the passed in list is not sorted.
+/proc/assert_sorted(list/list, name, cmp = /proc/cmp_numeric_asc)
+ var/last_value = list[1]
+
+ for (var/index in 2 to list.len)
+ var/value = list[index]
+
+ if (call(cmp)(value, last_value) < 0)
+ stack_trace("[name] is not sorted. value at [index] ([value]) is in the wrong place compared to the previous value of [last_value] (when compared to by [cmp])")
+
+ last_value = value
diff --git a/code/__HELPERS/lists/bitflag_lists.dm b/code/__HELPERS/lists/bitflag_lists.dm
index 8e37949d691..648b2fc4fe4 100644
--- a/code/__HELPERS/lists/bitflag_lists.dm
+++ b/code/__HELPERS/lists/bitflag_lists.dm
@@ -13,13 +13,18 @@ GLOBAL_LIST_EMPTY(bitflag_lists)
*/
#define SET_BITFLAG_LIST(target) \
do { \
- var/txt_signature = target.Join("-"); \
- if(!GLOB.bitflag_lists[txt_signature]) { \
+ var/txt_signature = target; \
+ target = GLOB.bitflag_lists[txt_signature]; \
+ if(isnull(target)) { \
var/list/new_bitflag_list = list(); \
- for(var/value in target) { \
+ var/list/decoded = json_decode("\[[txt_signature]0\]"); \
+ decoded.len--; \
+ for(var/value in decoded) { \
+ if (value < 0) { \
+ value = MAX_S_TURF + 1 + abs(value); \
+ } \
new_bitflag_list["[round(value / 24)]"] |= (1 << (value % 24)); \
}; \
- GLOB.bitflag_lists[txt_signature] = new_bitflag_list; \
+ target = GLOB.bitflag_lists[txt_signature] = new_bitflag_list; \
}; \
- target = GLOB.bitflag_lists[txt_signature]; \
} while (FALSE)
diff --git a/code/__HELPERS/roundend.dm b/code/__HELPERS/roundend.dm
deleted file mode 100644
index 07c40d2ed24..00000000000
--- a/code/__HELPERS/roundend.dm
+++ /dev/null
@@ -1,8 +0,0 @@
-/datum/controller/subsystem/ticker/proc/standard_reboot()
- if(ready_for_reboot)
- if(mode.station_was_nuked)
- Reboot("Station destroyed by Nuclear Device.", 60 SECONDS)
- else
- Reboot("Round ended.")
- else
- CRASH("Attempted standard reboot without ticker roundend completion")
diff --git a/code/___compile_options.dm b/code/___compile_options.dm
index fc83239269d..551c51c4246 100644
--- a/code/___compile_options.dm
+++ b/code/___compile_options.dm
@@ -7,165 +7,201 @@
/// Disable atmospherics zone build.
// #define FASTBOOT_DISABLE_ZONES (1<<2)
-/**
- * By using the testing("message") proc you can create debug-feedback for people with this uncommented,
- * but not visible in the release version
- */
-// #define TESTING
/// Enables the ability to cache datum vars and retrieve later for debugging which vars changed.
// #define DATUMVAR_DEBUGGING_MODE
-/// Comment this out if you are debugging problems that might be obscured by custom error handling in world/Error
+
+/// Comment this out if you are debugging problems that might be obscured by custom error handling in world/Error.
#ifdef DEBUG
-#define USE_CUSTOM_ERROR_HANDLER
-#define DEBUG_SHUTTLES
+ #define USE_CUSTOM_ERROR_HANDLER
+ #define DEBUG_SHUTTLES
#endif
+/**
+ * By using the testing("message") proc you can create debug-feedback for people with this uncommented,
+ * but not visible in the release version.
+ */
+// #define TESTING
#ifdef TESTING
-#define DATUMVAR_DEBUGGING_MODE
+ #warn compiling in TESTING mode. testing() debug messages will be visible.
-/// Used to find the sources of harddels, quite laggy, don't be surpised if it freezes your client for a good while
-// #define REFERENCE_TRACKING
-#ifdef REFERENCE_TRACKING
+ #define DATUMVAR_DEBUGGING_MODE
-/// Used for doing dry runs of the reference finder, to test for feature completeness
-/// Slightly slower, higher in memory. Just not optimal
-// #define REFERENCE_TRACKING_DEBUG
+ /// Used to find the sources of harddels, quite laggy, don't be surpised if it freezes your client for a good while.
+ // #define REFERENCE_TRACKING
+ #ifdef REFERENCE_TRACKING
+ /**
+ * Used for doing dry runs of the reference finder, to test for feature completeness.
+ * Slightly slower, higher in memory. Just not optimal.
+ */
+ // #define REFERENCE_TRACKING_DEBUG
-/// Run a lookup on things hard deleting by default.
-// #define GC_FAILURE_HARD_LOOKUP
-#ifdef GC_FAILURE_HARD_LOOKUP
-/// Don't stop when searching, go till you're totally done
-#define FIND_REF_NO_CHECK_TICK
-#endif //ifdef GC_FAILURE_HARD_LOOKUP
-#endif //ifdef REFERENCE_TRACKING
+ /// Run a lookup on things hard deleting by default.
+ // #define GC_FAILURE_HARD_LOOKUP
+ #ifdef GC_FAILURE_HARD_LOOKUP
+ /// Don't stop when searching, go till you're totally done.
+ #define FIND_REF_NO_CHECK_TICK
+ #endif //ifdef GC_FAILURE_HARD_LOOKUP
+ #endif
+
+
+ /**
+ * Enables debug messages for every single reaction step.
+ * This is 1 message per 0.5s for a SINGLE reaction.
+ * Useful for tracking down bugs/asking me for help in the main reaction handiler (equilibrium.dm).
+ *
+ * * Requires TESTING to be defined to work.
+ * ! Not implemented yet.
+ */
+ // #define REAGENTS_TESTING
+
+
+ /**
+ * Displays static object lighting updates.
+ *
+ * Also enables some debug vars on sslighting that can be used to modify
+ * how extensively we prune lighting corners to update.
+ */
+ #define VISUALIZE_LIGHT_UPDATES
+
+
+ /// Highlights atmos active turfs in green.
+ #define VISUALIZE_ACTIVE_TURFS
+#endif
+
/**
- * Enables debug messages for every single reaction step.
- * This is 1 message per 0.5s for a SINGLE reaction.
- * Useful for tracking down bugs/asking me for help in the main reaction handiler (equilibrium.dm).
- *
- * * Requires TESTING to be defined to work.
- * ! Not implemented yet.
+ * If this is uncommented, we do a single run though of the game setup and tear down process with unit tests in between.
*/
-// #define REAGENTS_TESTING
-
-/**
- * Displays static object lighting updates.
- *
- * Also enables some debug vars on sslighting that can be used to modify
- * how extensively we prune lighting corners to update.
- */
-#define VISUALIZE_LIGHT_UPDATES
-
-/// Highlights atmos active turfs in green.
-#define VISUALIZE_ACTIVE_TURFS
-#endif //ifdef TESTING
-
-/// If this is uncommented, we do a single run though of the game setup and tear down process with unit tests in between
// #define UNIT_TESTS
+
/**
* If this is uncommented, will attempt to load and initialize prof.dll/libprof.so.
* We do not ship byond-tracy. Build it yourself here: https://github.com/mafemergency/byond-tracy/
*/
// #define USE_BYOND_TRACY
-/// If defined, we will NOT defer asset generation till later in the game, and will instead do it all at once, during initiialize.
+
+/**
+ * If defined, we will NOT defer asset generation till later in the game, and will instead do it all at once, during initiialize.
+ */
//#define DO_NOT_DEFER_ASSETS
-/// If this is uncommented, Autowiki will generate edits and shut down the server.
-/// Prefer the autowiki build target instead.
+
+/**
+ * If this is uncommented, Autowiki will generate edits and shut down the server.
+ * Prefer the autowiki build target instead.
+ */
// #define AUTOWIKI
-/// If this is uncommented, will profile mapload atom initializations
+
+/**
+ * If this is uncommented, will profile mapload atom initializations.
+ */
// #define PROFILE_MAPLOAD_INIT_ATOM
-/// If this is uncommented, force our verb processing into just the 2% of a tick
-/// We normally reserve for it
-/// NEVER run this on live, it's for simulating highpop only
+
+/**
+ * If this is uncommented, force our verb processing into just the 2% of a tick.
+ * We normally reserve for it.
+ *! NEVER run this on live, it's for simulating highpop only.
+ */
// #define VERB_STRESS_TEST
-
#ifdef VERB_STRESS_TEST
-/// Uncomment this to force all verbs to run into overtime all of the time
-/// Essentially negating the reserve 2%
+ #warn Hey brother, you're running in LAG MODE.
+ #warn IF YOU PUT THIS ON LIVE I WILL FIND YOU AND MAKE YOU WISH YOU WERE NEVE-
-// #define FORCE_VERB_OVERTIME
-#warn Hey brother, you're running in LAG MODE.
-#warn IF YOU PUT THIS ON LIVE I WILL FIND YOU AND MAKE YOU WISH YOU WERE NEVE-
+ /**
+ * Uncomment this to force all verbs to run into overtime all of the time.
+ * Essentially negating the reserve 2%.
+ */
+ // #define FORCE_VERB_OVERTIME
+#endif
+
+
+#ifndef PRELOAD_RSC
+ /**
+ * Set to:
+ * * 0 to allow using external resources or on-demand behaviour;
+ * * 1 to use the default behaviour;
+ * * 2 for preloading absolutely everything;
+ */
+ #define PRELOAD_RSC 2
#endif
-#ifndef PRELOAD_RSC //set to:
-/// 0 to allow using external resources or on-demand behaviour;
-#define PRELOAD_RSC 2
-#endif // 1 to use the default behaviour;
- // 2 for preloading absolutely everything;
#ifdef LOWMEMORYMODE
-#define FORCE_MAP "_maps/runtimestation.json"
+ #define FORCE_MAP "_maps/rift.json" //TODO: A PROPER runtime or ministation map. @Zandario
#endif
-//Update this whenever you need to take advantage of more recent byond features
-#define MIN_COMPILER_VERSION 513
-#define MIN_COMPILER_BUILD 1514
-#if DM_VERSION < MIN_COMPILER_VERSION || DM_BUILD < MIN_COMPILER_BUILD
-//Don't forget to update this part
-#error Your version of BYOND is too out-of-date to compile this project. Go to https://secure.byond.com/download and update.
-#error You need version 513.1514 or higher
-#endif
-//Additional code for the above flags.
-#ifdef TESTING
-#warn compiling in TESTING mode. testing() debug messages will be visible.
-#endif
+//! CBT BUILD DEFINES
#ifdef CIBUILDING
-#define UNIT_TESTS
+ #define UNIT_TESTS
#endif
#ifdef CITESTING
-#define TESTING
+ #define TESTING
#endif
+
+//! LEGACY WARNING
#if !defined(CBT) && !defined(SPACEMAN_DMM)
-#warn Building with Dream Maker is no longer supported and will result in errors.
-#warn In order to build, run BUILD.bat in the root directory.
-#warn Consider switching to VSCode editor instead, where you can press Ctrl+Shift+B to build.
+ #warn Building with Dream Maker is no longer supported and will result in errors.
+ #warn In order to build, run BUILD.bat in the root directory.
+ #warn Consider switching to VSCode editor instead, where you can press Ctrl+Shift+B to build.
#endif
-//* Modules follow
+
+
+/**
+ ** Modules follow
+ */
+
//! Atmospherics
//? Gasmixtures
-/// enable general assertions
+/// Enable general assertions.
#define GASMIXTURE_ASSERTIONS
+
//? ZAS (Environmental)
/// Uncomment to turn on Multi-Z ZAS Support!
#define MULTIZAS
-/// uncomment to enable laggy as sin ZAS debugging systems coded in for when doing bugfixes or major systems overhaulling.
+
+/// Uncomment to enable laggy as sin ZAS debugging systems coded in for when doing bugfixes or major systems overhaulling.
// #define ZAS_ASSERTIONS
-/// uncomment to enable *actually* laggy as sin ZAS debugging, like "list in contents". don't do this without a major reason.
+
+/// Uncomment to enable *actually* laggy as sin ZAS debugging, like "list in contents". don't do this without a major reason.
// #define ZAS_ASSERTIONS_EXPENSIVE
-/// uncomment to enable debugging graphics. you probably want to keep this off in live!
+
+/// Uncomment to enable debugging graphics. you probably want to keep this off in live!
// #define ZAS_DEBUG_GRAPHICS
-/// uncomment to enable some otherwise useless hook points for zas debugging
+
+/// Uncomment to enable some otherwise useless hook points for zas debugging.
// #define ZAS_BREAKPOINT_HOOKS
+
#ifdef ZAS_DEBUG_GRAPHICS
#define ZAS_BREAKPOINT_HOOKS
#endif
-//! Overlays
-// A reasonable number of maximum overlays an object needs
-// If you think you need more, rethink it
+
+//! Overlays
+/**
+ * A reasonable number of maximum overlays an object needs.
+ * If you think you need more, rethink it.
+ */
#define MAX_ATOM_OVERLAYS 100
-//! Timers
+
+//! Timers
// #define TIMER_LOOP_DEBUGGING
diff --git a/code/__byond_version_compat.dm b/code/__byond_version_compat.dm
new file mode 100644
index 00000000000..feece392c90
--- /dev/null
+++ b/code/__byond_version_compat.dm
@@ -0,0 +1,45 @@
+// This file contains defines allowing targeting byond versions newer than the supported
+
+//Update this whenever you need to take advantage of more recent byond features
+#define MIN_COMPILER_VERSION 514
+#define MIN_COMPILER_BUILD 1556
+#if (DM_VERSION < MIN_COMPILER_VERSION || DM_BUILD < MIN_COMPILER_BUILD) && !defined(SPACEMAN_DMM)
+//Don't forget to update this part
+#error Your version of BYOND is too out-of-date to compile this project. Go to https://secure.byond.com/download and update.
+#error You need version 514.1556 or higher
+#endif
+
+#if (DM_VERSION == 514 && DM_BUILD > 1575 && DM_BUILD <= 1577)
+#error Your version of BYOND currently has a crashing issue that will prevent you from running Dream Daemon test servers.
+#error We require developers to test their content, so an inability to test means we cannot allow the compile.
+#error Please consider downgrading to 514.1575 or lower.
+#endif
+
+// Keep savefile compatibilty at minimum supported level
+#if DM_VERSION >= 515
+/savefile/byond_version = MIN_COMPILER_VERSION
+#endif
+
+// 515 split call for external libraries into call_ext
+#if DM_VERSION < 515
+#define LIBCALL call
+#else
+#define LIBCALL call_ext
+#endif
+
+// So we want to have compile time guarantees these procs exist on local type, unfortunately 515 killed the .proc/procname syntax so we have to use nameof()
+#if DM_VERSION < 515
+/// Call by name proc reference, checks if the proc exists on this type or as a global proc
+#define PROC_REF(X) (.proc/##X)
+/// Call by name proc reference, checks if the proc exists on given type or as a global proc
+#define TYPE_PROC_REF(TYPE, X) (##TYPE.proc/##X)
+/// Call by name proc reference, checks if the proc is existing global proc
+#define GLOBAL_PROC_REF(X) (/proc/##X)
+#else
+/// Call by name proc reference, checks if the proc exists on this type or as a global proc
+#define PROC_REF(X) (nameof(.proc/##X))
+/// Call by name proc reference, checks if the proc exists on given type or as a global proc
+#define TYPE_PROC_REF(TYPE, X) (nameof(##TYPE.proc/##X))
+/// Call by name proc reference, checks if the proc is existing global proc
+#define GLOBAL_PROC_REF(X) (/proc/##X)
+#endif
diff --git a/code/controllers/configuration/entries/general.dm b/code/controllers/configuration/entries/general.dm
index f7ea3b0260d..c8e834abc2d 100644
--- a/code/controllers/configuration/entries/general.dm
+++ b/code/controllers/configuration/entries/general.dm
@@ -49,3 +49,7 @@
/// allows admins with relevant permissions to have their own ooc colour
/datum/config_entry/flag/allow_admin_ooccolor
default = TRUE
+
+/datum/config_entry/number/rounds_until_hard_restart
+ default = -1
+ min_val = 0
diff --git a/code/controllers/subsystem/garbage.dm b/code/controllers/subsystem/garbage.dm
index 69414c0b201..9d315624890 100644
--- a/code/controllers/subsystem/garbage.dm
+++ b/code/controllers/subsystem/garbage.dm
@@ -159,6 +159,7 @@ SUBSYSTEM_DEF(garbage)
#endif
var/type = D.type
var/datum/qdel_item/I = items[type]
+
#ifdef TESTING
log_world("## TESTING: GC: -- \ref[D] | [type] was unable to be GC'd --")
for(var/c in GLOB.admins) //Using testing() here would fill the logs with ADMIN_VV garbage
@@ -169,6 +170,7 @@ SUBSYSTEM_DEF(garbage)
testing("GC: -- \ref[src] | [type] was unable to be GC'd --")
#endif
I.failures++
+
if (GC_QUEUE_HARDDELETE)
HardDelete(D)
if (MC_TICK_CHECK)
diff --git a/code/controllers/subsystem/openspace.dm b/code/controllers/subsystem/openspace.dm
index 62f94f6258c..44ee2e7ca39 100644
--- a/code/controllers/subsystem/openspace.dm
+++ b/code/controllers/subsystem/openspace.dm
@@ -1,3 +1,5 @@
+// TODO: ZCOPY / ZMIMIC @Zandario
+
SUBSYSTEM_DEF(openspace)
name = "Open Space"
runlevels = RUNLEVEL_GAME | RUNLEVEL_LOBBY | RUNLEVEL_POSTGAME
@@ -36,6 +38,7 @@ SUBSYSTEM_DEF(openspace)
// Do the initial updates of open space turfs when the game starts. This will lag!
/datum/controller/subsystem/openspace/proc/initialize_open_space()
+ set waitfor = FALSE
// Do initial setup from bottom to top.
for(var/zlevel = 1 to world.maxz)
for(var/turf/simulated/open/T in block(locate(1, 1, zlevel), locate(world.maxx, world.maxy, zlevel)))
diff --git a/code/controllers/subsystem/overlays.dm b/code/controllers/subsystem/overlays.dm
index 825c80b2850..1e911549285 100644
--- a/code/controllers/subsystem/overlays.dm
+++ b/code/controllers/subsystem/overlays.dm
@@ -96,16 +96,16 @@ SUBSYSTEM_DEF(overlays)
if(!overlay)
continue
if (istext(overlay))
-// todo: enable
+
+// TODO: Enable in its own PR. @Zandario
/*
-#ifdef UNIT_TESTS
- // This is too expensive to run normally but running it during CI is a good test
- var/list/icon_states_available = icon_states(icon)
- if(!(overlay in icon_states_available))
- var/icon_file = "[icon]" || "Unknown Generated Icon"
- stack_trace("Invalid overlay: Icon object '[icon_file]' [REF(icon)] used in '[src]' [type] is missing icon state [overlay].")
- continue
-#endif
+ //! Unit Testing
+ if (PERFORM_ALL_TESTS(focus_only/invalid_overlays))
+ var/list/icon_states_available = icon_states(icon)
+ if(!(overlay in icon_states_available))
+ var/icon_file = "[icon]" || "Unknown Generated Icon"
+ stack_trace("Invalid overlay: Icon object '[icon_file]' [REF(icon)] used in '[src]' [type] is missing icon state [overlay].")
+ continue
*/
new_overlays += iconstate2appearance(icon, overlay)
else if(isicon(overlay))
diff --git a/code/controllers/subsystem/shuttles.dm b/code/controllers/subsystem/shuttles.dm
index d209e372e08..50069b4eb4b 100644
--- a/code/controllers/subsystem/shuttles.dm
+++ b/code/controllers/subsystem/shuttles.dm
@@ -12,25 +12,50 @@ SUBSYSTEM_DEF(shuttle)
subsystem_flags = SS_KEEP_TIMING|SS_NO_TICK_CHECK
runlevels = RUNLEVEL_GAME|RUNLEVEL_POSTGAME
- var/overmap_halted = FALSE // Whether ships can move on the overmap; used for adminbus.
- var/list/ships = list() // List of all ships.
+ /// Whether ships can move on the overmap; used for adminbus.
+ var/overmap_halted = FALSE
+ /// List of all ships.
+ var/list/ships = list()
- var/list/shuttles = list() // Maps shuttle tags to shuttle datums, so that they can be looked up.
- var/list/process_shuttles = list() // Simple list of shuttles, for processing
+ /// Maps shuttle tags to shuttle datums, so that they can be looked up.
+ var/list/shuttles = list()
+ /// Simple list of shuttles, for processing
+ var/list/process_shuttles = list()
- var/list/registered_shuttle_landmarks = list() // Maps shuttle landmark tags to instances
- var/last_landmark_registration_time // world.time of most recent addition to registered_shuttle_landmarks
- var/list/shuttle_logs = list() // (Not Implemented) Keeps records of shuttle movement, format is list(datum/shuttle = datum/shuttle_log)
- var/list/shuttle_areas = list() // All the areas of all shuttles.
- var/list/docking_registry = list() // Docking controller tag -> docking controller program, mostly for init purposes.
+ /// Maps shuttle landmark tags to instances
+ var/list/registered_shuttle_landmarks = list()
+ /// world.time of most recent addition to registered_shuttle_landmarks
+ var/last_landmark_registration_time
+ /// (Not Implemented) Keeps records of shuttle movement, format is list(datum/shuttle = datum/shuttle_log)
+ var/list/shuttle_logs = list()
+ /// All the areas of all shuttles.
+ var/list/shuttle_areas = list()
+ /// Docking controller tag -> docking controller program, mostly for init purposes.
+ var/list/docking_registry = list()
- var/list/landmarks_awaiting_sector = list() // Stores automatic landmarks that are waiting for a sector to finish loading.
- var/list/landmarks_still_needed = list() // Stores landmark_tags that need to be assigned to the sector (landmark_tag = sector) when registered.
- var/list/shuttles_to_initialize // A queue for shuttles to initialize at the appropriate time.
- var/list/sectors_to_initialize // Used to find all sector objects at the appropriate time.
- var/block_init_queue = TRUE // Block initialization of new shuttles/sectors
+ /// Stores automatic landmarks that are waiting for a sector to finish loading.
+ var/list/landmarks_awaiting_sector = list()
+ /// Stores landmark_tags that need to be assigned to the sector (landmark_tag = sector) when registered.
+ var/list/landmarks_still_needed = list()
+ /// A queue for shuttles to initialize at the appropriate time.
+ var/list/shuttles_to_initialize
+ /// Used to find all sector objects at the appropriate time.
+ var/list/sectors_to_initialize
+ /// Block initialization of new shuttles/sectors
+ var/block_init_queue = TRUE
- var/tmp/list/current_run // Shuttles remaining to process this fire() tick
+ /// Shuttles remaining to process this fire() tick
+ var/tmp/list/current_run
+
+
+ /**
+ *! I made these shitty vars so we don't search for these in GOD DAMN WORLD
+ *! If I find these are still here in 2023 I'll be very upset.
+ * @Zandario
+ */
+
+ var/list/unary_engines = list()
+ var/list/ion_engines = list()
/datum/controller/subsystem/shuttle/Initialize(timeofday)
last_landmark_registration_time = world.time
@@ -80,12 +105,12 @@ SUBSYSTEM_DEF(shuttle)
hook_up_motherships(shuttles_made)
hook_up_shuttle_objects(shuttles_made)
shuttles_to_initialize = null
- /// citadel edit - initialize overmaps shuttles here until we rewrite overmaps to not be a dumpster fire god damnit
- for(var/obj/machinery/atmospherics/component/unary/engine/E in world)
+ //! citadel edit - initialize overmaps shuttles here until we rewrite overmaps to not be a dumpster fire god damnit
+ for(var/obj/machinery/atmospherics/component/unary/engine/E in unary_engines)
if(E.linked)
continue
E.link_to_ship()
- for(var/obj/machinery/ion_engine/E in world)
+ for(var/obj/machinery/ion_engine/E in ion_engines)
if(E.linked)
continue
E.link_to_ship()
diff --git a/code/controllers/subsystem/ticker.dm b/code/controllers/subsystem/ticker.dm
index 80ab7f54619..c61ece3b53f 100644
--- a/code/controllers/subsystem/ticker.dm
+++ b/code/controllers/subsystem/ticker.dm
@@ -3,6 +3,7 @@ SUBSYSTEM_DEF(ticker)
wait = 20
init_order = INIT_ORDER_TICKER
runlevels = RUNLEVEL_LOBBY | RUNLEVEL_SETUP | RUNLEVEL_GAME | RUNLEVEL_POSTGAME
+
/// Current state of the game
var/static/current_state = GAME_STATE_INIT
@@ -14,14 +15,20 @@ SUBSYSTEM_DEF(ticker)
/// Should we immediately start?
var/start_immediately = FALSE
- /// Is everything in order for us to start a timed reboot?
+ /// All roundend preparation done with, all that's left is reboot.
var/ready_for_reboot = FALSE
/// Is round end delayed?
var/delay_end = FALSE
+ /// A message to display to anyone who tries to restart the world after a delay.
+ var/admin_delay_notice = ""
+
+ /// Boolean to track and check if our subsystem setup is done.
+ var/setup_done = FALSE
/// Force round end
var/force_ending = FALSE
- var/timeLeft //pregame timer
+ /// Pregame timer.
+ var/timeLeft
var/start_at
var/hide_mode = 0
@@ -30,18 +37,27 @@ SUBSYSTEM_DEF(ticker)
var/event_time = null
var/event = 0
- var/list/datum/mind/minds = list()//The people in the game. Used for objective tracking.
+ /// The people in the game. Used for objective tracking.
+ var/list/datum/mind/minds = list()
- var/Bible_icon_state // icon_state the chaplain has chosen for his bible
- var/Bible_item_state // item_state the chaplain has chosen for his bible
- var/Bible_name // name of the bible
+ //! Why is this in Ticker??? @Zandario
+ /// icon_state the chaplain has chosen for his bible.
+ var/Bible_icon_state
+ /// item_state the chaplain has chosen for his bible.
+ var/Bible_item_state
+ /// Name of the bible.
+ var/Bible_name
var/Bible_deity_name
- var/random_players = 0 // if set to nonzero, ALL players who latejoin or declare-ready join will have random appearances/genders
+ /// If set to nonzero, ALL players who latejoin or declare-ready join will have random appearances/genders.
+ var/random_players = 0
- var/list/syndicate_coalition = list() // list of traitor-compatible factions
- var/list/factions = list() // list of all factions
- var/list/availablefactions = list() // list of factions with openings
+ /// List of traitor-compatible factions.
+ var/list/syndicate_coalition = list()
+ /// List of all factions.
+ var/list/factions = list()
+ /// List of factions with openings.
+ var/list/availablefactions = list()
var/triai = 0//Global holder for Triumvirate
@@ -57,7 +73,7 @@ SUBSYSTEM_DEF(ticker)
/datum/controller/subsystem/ticker/Initialize()
if(!syndicate_code_phrase)
- syndicate_code_phrase = generate_code_phrase()
+ syndicate_code_phrase = generate_code_phrase()
if(!syndicate_code_response)
syndicate_code_response = generate_code_phrase()
@@ -70,13 +86,45 @@ SUBSYSTEM_DEF(ticker)
if(GAME_STATE_INIT)
// We fire after init finishes
on_mc_init_finish()
+
if(GAME_STATE_PREGAME)
process_pregame()
+
if(GAME_STATE_SETTING_UP)
setup()
+ setup_done = TRUE
+
if(GAME_STATE_PLAYING)
round_process()
+ if(!mode.explosion_in_progress && mode.check_finished(force_ending) || force_ending)
+ current_state = GAME_STATE_FINISHED
+ round_end_time = world.time
+ declare_completion()
+ Master.SetRunLevel(RUNLEVEL_POSTGAME)
+
+ callHook("roundend")
+
+ if (mode.station_was_nuked)
+ feedback_set_details("end_proper","nuke")
+ else
+ feedback_set_details("end_proper","proper completion")
+
+
+ if(blackbox)
+ blackbox.save_all_data_to_sql()
+
+ send2irc("Server", "A round of [mode.name] just ended.")
+ if(CONFIG_GET(string/chat_roundend_notice_tag))
+ var/broadcastmessage = "The round has ended."
+ if(CONFIG_GET(string/chat_reboot_role))
+ broadcastmessage += "\n\n<@&[CONFIG_GET(string/chat_reboot_role)]>, the server will reboot shortly!"
+ send2chat(broadcastmessage, CONFIG_GET(string/chat_roundend_notice_tag))
+
+ SSdbcore.SetRoundEnd()
+ SSpersistence.SavePersistence()
+
+
/datum/controller/subsystem/ticker/proc/on_mc_init_finish()
send2irc("Server lobby is loaded and open at byond://[config_legacy.serverurl ? config_legacy.serverurl : (config_legacy.server ? config_legacy.server : "[world.address]:[world.port]")]")
to_chat(world, "Welcome to the pregame lobby!")
@@ -107,7 +155,7 @@ SUBSYSTEM_DEF(ticker)
SSvote.autogamemode()
//end
-/datum/controller/subsystem/ticker/proc/Reboot(reason, delay)
+/datum/controller/subsystem/ticker/proc/Reboot(reason, end_string, delay)
set waitfor = FALSE
if(usr && !check_rights(R_SERVER, TRUE))
return
@@ -115,11 +163,12 @@ SUBSYSTEM_DEF(ticker)
if(!delay)
delay = CONFIG_GET(number/round_end_countdown) * 10
- if(delay_end)
- to_chat(world, "An admin has delayed the round end.")
+ var/skip_delay = check_rights()
+ if(delay_end && !skip_delay)
+ to_chat(world, SPAN_BOLDANNOUNCE("An admin has delayed the round end."))
return
- to_chat(world, "Rebooting World in [DisplayTimeText(delay)]. [reason]")
+ to_chat(world, SPAN_BOLDANNOUNCE("Rebooting World in [DisplayTimeText(delay)]. [reason]"))
var/start_wait = world.time
//UNTIL(round_end_sound_sent || (world.time - start_wait) > (delay * 2)) //don't wait forever
@@ -129,22 +178,25 @@ SUBSYSTEM_DEF(ticker)
var/timeleft = delay - (world.time - start_wait)
// If we have less than 10 seconds left.
if(timeleft <= 10 SECONDS)
- to_chat(world, "Rebooting in [DisplayTimeText(timeleft, 1)]")
+ to_chat(world, SPAN_BOLDANNOUNCE("Rebooting in [DisplayTimeText(timeleft, 1)]"))
sleep(10)
//If we have 30 seconds left, announce and sleep for the rest of the time.
if(timeleft <= 30 SECONDS)
var/time = timeleft - 10 SECONDS
- to_chat(world, "Rebooting in [DisplayTimeText(timeleft, 1)]")
+ to_chat(world, SPAN_BOLDANNOUNCE("Rebooting in [DisplayTimeText(timeleft, 1)]"))
sleep(time)
// Otherwise, per minute.
else
- to_chat(world, "Rebooting in [DisplayTimeText(timeleft, 1)]")
+ to_chat(world, SPAN_BOLDANNOUNCE("Rebooting in [DisplayTimeText(timeleft, 1)]"))
sleep(60 SECONDS)
- if(delay_end)
- to_chat(world, "Reboot was cancelled by an admin.")
+ if(delay_end && !skip_delay)
+ to_chat(world, SPAN_BOLDANNOUNCE("Reboot was cancelled by an admin."))
return
- log_game("World reboot triggered by ticker. [reason]")
+ // if(end_string)
+ // end_state = end_string
+
+ log_game(SPAN_BOLDANNOUNCE("Rebooting World. [reason]"))
world.Reboot()
@@ -450,38 +502,7 @@ SUBSYSTEM_DEF(ticker)
game_finished = (mode.check_finished() || (SSemergencyshuttle.returned() && SSemergencyshuttle.evac == 1)) || universe_has_ended
mode_finished = game_finished
- if(force_ending || (!mode.explosion_in_progress && game_finished && (mode_finished || post_game)))
- current_state = GAME_STATE_FINISHED
- round_end_time = world.time
- Master.SetRunLevel(RUNLEVEL_POSTGAME)
-
- declare_completion()
-
- callHook("roundend")
-
- if (mode.station_was_nuked)
- feedback_set_details("end_proper","nuke")
- else
- feedback_set_details("end_proper","proper completion")
-
-
- if(blackbox)
- blackbox.save_all_data_to_sql()
-
- send2irc("Server", "A round of [mode.name] just ended.")
- if(CONFIG_GET(string/chat_roundend_notice_tag))
- var/broadcastmessage = "The round has ended."
- if(CONFIG_GET(string/chat_reboot_role))
- broadcastmessage += "\n\n<@&[CONFIG_GET(string/chat_reboot_role)]>, the server will reboot shortly!"
- send2chat(broadcastmessage, CONFIG_GET(string/chat_roundend_notice_tag))
-
- SSdbcore.SetRoundEnd()
- SSpersistence.SavePersistence()
- ready_for_reboot = TRUE
- standard_reboot()
-
-
- else if (mode_finished)
+ if (mode_finished)
post_game = 1
mode.cleanup()
@@ -496,11 +517,16 @@ SUBSYSTEM_DEF(ticker)
return 1
/datum/controller/subsystem/ticker/proc/declare_completion()
- to_chat(world, "
A round of [mode.name] has ended!
")
- for(var/I in round_end_events)
- var/datum/callback/cb = I
- cb.InvokeAsync()
+ set waitfor = FALSE
+
+ to_chat(world, "
The round has ended.")
+ log_game("The round has ended.")
+
+ for(var/datum/callback/roundend_callbacks as anything in round_end_events)
+ roundend_callbacks.InvokeAsync()
LAZYCLEARLIST(round_end_events)
+
+
for(var/mob/Player in GLOB.player_list)
if(Player.mind && !isnewplayer(Player))
if(Player.stat != DEAD)
@@ -525,6 +551,8 @@ SUBSYSTEM_DEF(ticker)
to_chat(Player, "You did not survive the events on [station_name()]...")
to_chat(world, "
")
+ CHECK_TICK
+
for (var/mob/living/silicon/ai/aiPlayer in GLOB.mob_list)
if (aiPlayer.stat != 2)
to_chat(world, "[aiPlayer.name] (Played by: [aiPlayer.key])'s laws at the end of the round were:")
@@ -538,6 +566,8 @@ SUBSYSTEM_DEF(ticker)
robolist += "[robo.name][robo.stat?" (Deactivated) (Played by: [robo.key]), ":" (Played by: [robo.key]), "]"
to_chat(world, "[robolist]")
+ CHECK_TICK
+
var/dronecount = 0
for (var/mob/living/silicon/robot/robo in GLOB.mob_list)
@@ -555,11 +585,15 @@ SUBSYSTEM_DEF(ticker)
if(robo) //How the hell do we lose robo between here and the world messages directly above this?
robo.laws.show_laws(world)
+ CHECK_TICK
+
if(dronecount)
to_chat(world, "There [dronecount>1 ? "were" : "was"] [dronecount] industrious maintenance [dronecount>1 ? "drones" : "drone"] at the end of this round.")
mode.declare_completion()//To declare normal completion.
+ CHECK_TICK
+
//Ask the event manager to print round end information
SSevents.RoundEnd()
@@ -580,4 +614,17 @@ SUBSYSTEM_DEF(ticker)
for(var/i in total_antagonists)
log_game("[i]s[total_antagonists[i]].")
- return 1
+ CHECK_TICK
+
+ ready_for_reboot = TRUE
+ sleep(5 SECONDS)
+ standard_reboot()
+
+/datum/controller/subsystem/ticker/proc/standard_reboot()
+ if(ready_for_reboot)
+ if(mode.station_was_nuked)
+ Reboot("Station destroyed by Nuclear Device.", "nuke", 60 SECONDS)
+ else
+ Reboot("Round ended.", "proper completion")
+ else
+ CRASH("Attempted standard reboot without ticker roundend completion")
diff --git a/code/datums/position_point_vector.dm b/code/datums/position_point_vector.dm
index 8063d633612..869e47b743e 100644
--- a/code/datums/position_point_vector.dm
+++ b/code/datums/position_point_vector.dm
@@ -131,12 +131,15 @@
/datum/point/vector
- var/speed = 32 //pixels per iteration
+ /// Pixels per iteration
+ var/speed = 32
var/iteration = 0
var/angle = 0
- var/mpx = 0 //calculated x/y movement amounts to prevent having to do trig every step.
+ /// Calculated x movement amounts to prevent having to do trig every step.
+ var/mpx = 0
+ /// Calculated y movement amounts to prevent having to do trig every step.
var/mpy = 0
- var/starting_x = 0 //just like before, pixels from EDGE of map! This is set in initialize_location().
+ var/starting_x = 0 //just like before, pixels from EDGE of map! This is set in initialize_location().
var/starting_y = 0
var/starting_z = 0
@@ -169,7 +172,8 @@
speed = pixel_speed
set_angle(new_angle)
-/datum/point/vector/proc/set_angle(new_angle) //calculations use "byond angle" where north is 0 instead of 90, and south is 180 instead of 270.
+/// Calculations use "byond angle" where north is 0 instead of 90, and south is 180 instead of 270.
+/datum/point/vector/proc/set_angle(new_angle)
if(isnull(angle))
return
angle = new_angle
@@ -187,8 +191,8 @@
/datum/point/vector/proc/increment(multiplier = 1)
iteration++
- x += mpx * multiplier
- y += mpy * multiplier
+ x += mpx * (multiplier)
+ y += mpy * (multiplier)
/datum/point/vector/proc/return_vector_after_increments(amount = 7, multiplier = 1, force_simulate = FALSE)
var/datum/point/vector/v = copy_to()
@@ -202,7 +206,7 @@
/datum/point/vector/proc/on_z_change()
return
-/datum/point/vector/processed //pixel_speed is per decisecond.
+/datum/point/vector/processed // pixel_speed is per decisecond.
var/last_process = 0
var/last_move = 0
var/paused = FALSE
diff --git a/code/game/area/misc.dm b/code/game/area/misc.dm
new file mode 100644
index 00000000000..f6f6a20da3b
--- /dev/null
+++ b/code/game/area/misc.dm
@@ -0,0 +1,7 @@
+/// This is for unit testing.
+/// TODO: Lint for this area in normal maps perhaps? @Zandario
+/area/misc/testroom
+ name = "Test Room"
+ icon_state = "test_room"
+ requires_power = FALSE
+ has_gravity = TRUE
diff --git a/code/game/atoms/atoms.dm b/code/game/atoms/atoms.dm
index 999fd379d3c..ec91495183d 100644
--- a/code/game/atoms/atoms.dm
+++ b/code/game/atoms/atoms.dm
@@ -68,9 +68,17 @@
var/bottom_left_corner
/// Smoothing variable
var/bottom_right_corner
- /// What smoothing groups does this atom belongs to, to match canSmoothWith. If null, nobody can smooth with it.
+ /**
+ * What smoothing groups does this atom belongs to, to match canSmoothWith.
+ * If null, nobody can smooth with it.
+ *! Must be sorted.
+ */
var/list/smoothing_groups = null
- /// List of smoothing groups this atom can smooth with. If this is null and atom is smooth, it smooths only with itself.
+ /**
+ * List of smoothing groups this atom can smooth with.
+ * If this is null and atom is smooth, it smooths only with itself.
+ *! Must be sorted.
+ */
var/list/canSmoothWith = null
//! ## Chemistry
@@ -213,14 +221,7 @@
if(light_power && light_range)
update_light()
- if (length(smoothing_groups))
- tim_sort(smoothing_groups) //In case it's not properly ordered, let's avoid duplicate entries with the same values.
- SET_BITFLAG_LIST(smoothing_groups)
- if (length(canSmoothWith))
- tim_sort(canSmoothWith)
- if(canSmoothWith[length(canSmoothWith)] > MAX_S_TURF) //If the last element is higher than the maximum turf-only value, then it must scan turf contents for smoothing targets.
- smoothing_flags |= SMOOTH_OBJ
- SET_BITFLAG_LIST(canSmoothWith)
+ SETUP_SMOOTHING()
if(opacity && isturf(loc))
var/turf/T = loc
diff --git a/code/game/gamemodes/game_mode.dm b/code/game/gamemodes/game_mode.dm
index 43c2f10141a..a954630154a 100644
--- a/code/game/gamemodes/game_mode.dm
+++ b/code/game/gamemodes/game_mode.dm
@@ -281,18 +281,25 @@ var/global/list/additional_antag_types = list()
)
command_announcement.Announce("The presence of [pick(reasons)] in the region is tying up all available local emergency resources; emergency response teams cannot be called at this time, and post-evacuation recovery efforts will be substantially delayed.","Emergency Transmission")
-/datum/game_mode/proc/check_finished()
+/datum/game_mode/proc/check_finished(force_ending) // To be called by SSticker
+ if(!SSticker.setup_done)
+ return FALSE
if(SSemergencyshuttle.returned() || station_was_nuked)
- return 1
+ return TRUE
+ if(station_was_nuked)
+ return TRUE
+
if(end_on_antag_death && antag_templates && antag_templates.len)
for(var/datum/antagonist/antag in antag_templates)
if(!antag.antags_are_dead())
- return 0
+ return FALSE
if(config_legacy.continous_rounds)
- SSemergencyshuttle.auto_recall = 0
- return 0
- return 1
- return 0
+ SSemergencyshuttle.auto_recall = FALSE
+ return FALSE
+ return TRUE
+
+ if(force_ending)
+ return TRUE
/datum/game_mode/proc/cleanup() //This is called when the round has ended but not the game, if any cleanup would be necessary in that case.
return
diff --git a/code/game/landmarks/unit_test_markers.dm b/code/game/landmarks/unit_test_markers.dm
new file mode 100644
index 00000000000..6ab02635730
--- /dev/null
+++ b/code/game/landmarks/unit_test_markers.dm
@@ -0,0 +1,15 @@
+//! Unit Test Landmarks
+
+/**
+ * Marks the bottom left of the testing zone.
+ * In landmarks.dm and not unit_test.dm so it is always active in the mapping tools.
+ */
+/obj/landmark/unit_test_bottom_left
+ name = "unit test zone bottom left"
+
+/**
+ * Marks the top right of the testing zone.
+ * In landmarks.dm and not unit_test.dm so it is always active in the mapping tools.
+ */
+/obj/landmark/unit_test_top_right
+ name = "unit test zone top right"
diff --git a/code/game/machinery/doors/airlock.dm b/code/game/machinery/doors/airlock.dm
index f233919c5e0..c7f241dbb2c 100644
--- a/code/game/machinery/doors/airlock.dm
+++ b/code/game/machinery/doors/airlock.dm
@@ -17,7 +17,7 @@
autoclose = 1
normalspeed = 1
- smoothing_groups = list(SMOOTH_GROUP_AIRLOCK)
+ smoothing_groups = (SMOOTH_GROUP_AIRLOCK)
/**
* If -1, the control is enabled but the AI had bypassed it earlier, so if it is disabled again the AI would have no trouble getting back in.
diff --git a/code/game/machinery/doors/blast_door.dm b/code/game/machinery/doors/blast_door.dm
index aa92ae4ec5c..1c953e6c281 100644
--- a/code/game/machinery/doors/blast_door.dm
+++ b/code/game/machinery/doors/blast_door.dm
@@ -25,7 +25,7 @@
//turning this off prevents awkward zone geometry in places like medbay lobby, for example.
block_air_zones = 0
- smoothing_groups = list(SMOOTH_GROUP_SHUTTERS_BLASTDOORS)
+ smoothing_groups = (SMOOTH_GROUP_SHUTTERS_BLASTDOORS)
var/datum/material/implicit_material
// Icon states for different shutter types. Simply change this instead of rewriting the update_icon proc.
diff --git a/code/game/objects/items/stacks/sandbag.dm b/code/game/objects/items/stacks/sandbag.dm
index 7d2f0d3b6b9..9898d057bd5 100644
--- a/code/game/objects/items/stacks/sandbag.dm
+++ b/code/game/objects/items/stacks/sandbag.dm
@@ -108,8 +108,8 @@ var/global/list/datum/stack_recipe/sandbags_recipes = list( \
density = TRUE
pass_flags_self = ATOM_PASS_TABLE | ATOM_PASS_THROWN | ATOM_PASS_CLICK
smoothing_flags = SMOOTH_BITMASK
- smoothing_groups = list(SMOOTH_GROUP_SANDBAGS)
- canSmoothWith = list(SMOOTH_GROUP_SANDBAGS)
+ smoothing_groups = (SMOOTH_GROUP_SANDBAGS)
+ canSmoothWith = (SMOOTH_GROUP_SANDBAGS)
var/health = 100
var/maxhealth = 100
var/vestigial = TRUE
diff --git a/code/game/objects/structures/catwalk.dm b/code/game/objects/structures/catwalk.dm
index 8d7ed451178..9ba32c9c436 100644
--- a/code/game/objects/structures/catwalk.dm
+++ b/code/game/objects/structures/catwalk.dm
@@ -10,8 +10,8 @@
anchored = TRUE
// smoothing_flags = SMOOTH_BITMASK
- smoothing_groups = list(SMOOTH_GROUP_LATTICE, SMOOTH_GROUP_CATWALK, SMOOTH_GROUP_OPEN_FLOOR)
- canSmoothWith = list(SMOOTH_GROUP_CATWALK)
+ smoothing_groups = (SMOOTH_GROUP_CATWALK + SMOOTH_GROUP_LATTICE + SMOOTH_GROUP_OPEN_FLOOR)
+ canSmoothWith = (SMOOTH_GROUP_CATWALK)
var/health = 100
var/maxhealth = 100
diff --git a/code/game/objects/structures/grille.dm b/code/game/objects/structures/grille.dm
index 6d5476bee1e..ea810435a5c 100644
--- a/code/game/objects/structures/grille.dm
+++ b/code/game/objects/structures/grille.dm
@@ -11,8 +11,8 @@
explosion_resistance = 1
// smoothing_flags = SMOOTH_BITMASK
- smoothing_groups = list(SMOOTH_GROUP_GRILLE)
- canSmoothWith = list(SMOOTH_GROUP_GRILLE)
+ smoothing_groups = (SMOOTH_GROUP_GRILLE)
+ canSmoothWith = (SMOOTH_GROUP_GRILLE)
var/health = 10
var/destroyed = 0
diff --git a/code/game/objects/structures/lattice.dm b/code/game/objects/structures/lattice.dm
index bf80818c47e..9e64e338827 100644
--- a/code/game/objects/structures/lattice.dm
+++ b/code/game/objects/structures/lattice.dm
@@ -9,8 +9,8 @@
plane = PLATING_PLANE
// smoothing_flags = SMOOTH_BITMASK
- smoothing_groups = list(SMOOTH_GROUP_LATTICE)
- canSmoothWith = list(SMOOTH_GROUP_LATTICE, SMOOTH_GROUP_OPEN_FLOOR, SMOOTH_GROUP_WALLS)
+ smoothing_groups = (SMOOTH_GROUP_LATTICE)
+ canSmoothWith = (SMOOTH_GROUP_LATTICE + SMOOTH_GROUP_WALLS + SMOOTH_GROUP_OPEN_FLOOR)
/obj/structure/lattice/Initialize(mapload)
. = ..()
diff --git a/code/game/objects/structures/window.dm b/code/game/objects/structures/window.dm
index bde4ec6eb82..d4d091da30b 100644
--- a/code/game/objects/structures/window.dm
+++ b/code/game/objects/structures/window.dm
@@ -590,8 +590,8 @@
fulltile = TRUE
// smoothing_flags = SMOOTH_BITMASK
- smoothing_groups = list(SMOOTH_GROUP_WINDOW_FULLTILE)
- canSmoothWith = list(SMOOTH_GROUP_WINDOW_FULLTILE, SMOOTH_GROUP_WALLS, SMOOTH_GROUP_AIRLOCK, SMOOTH_GROUP_SHUTTERS_BLASTDOORS)
+ smoothing_groups = (SMOOTH_GROUP_WINDOW_FULLTILE)
+ canSmoothWith = (SMOOTH_GROUP_SHUTTERS_BLASTDOORS + SMOOTH_GROUP_AIRLOCK + SMOOTH_GROUP_WINDOW_FULLTILE + SMOOTH_GROUP_WALLS)
/obj/structure/window/phoronbasic
name = "phoron window"
@@ -611,8 +611,8 @@
fulltile = TRUE
// smoothing_flags = SMOOTH_BITMASK
- smoothing_groups = list(SMOOTH_GROUP_WINDOW_FULLTILE)
- canSmoothWith = list(SMOOTH_GROUP_WINDOW_FULLTILE, SMOOTH_GROUP_WALLS, SMOOTH_GROUP_AIRLOCK, SMOOTH_GROUP_SHUTTERS_BLASTDOORS)
+ smoothing_groups = (SMOOTH_GROUP_WINDOW_FULLTILE)
+ canSmoothWith = (SMOOTH_GROUP_SHUTTERS_BLASTDOORS + SMOOTH_GROUP_AIRLOCK + SMOOTH_GROUP_WINDOW_FULLTILE + SMOOTH_GROUP_WALLS)
/obj/structure/window/phoronreinforced
name = "reinforced borosilicate window"
@@ -633,8 +633,8 @@
fulltile = TRUE
// smoothing_flags = SMOOTH_BITMASK
- smoothing_groups = list(SMOOTH_GROUP_WINDOW_FULLTILE)
- canSmoothWith = list(SMOOTH_GROUP_WINDOW_FULLTILE, SMOOTH_GROUP_WALLS, SMOOTH_GROUP_AIRLOCK, SMOOTH_GROUP_SHUTTERS_BLASTDOORS)
+ smoothing_groups = (SMOOTH_GROUP_WINDOW_FULLTILE)
+ canSmoothWith = (SMOOTH_GROUP_SHUTTERS_BLASTDOORS + SMOOTH_GROUP_AIRLOCK + SMOOTH_GROUP_WINDOW_FULLTILE + SMOOTH_GROUP_WALLS)
/obj/structure/window/reinforced
@@ -655,8 +655,8 @@
fulltile = TRUE
// smoothing_flags = SMOOTH_BITMASK
- smoothing_groups = list(SMOOTH_GROUP_WINDOW_FULLTILE)
- canSmoothWith = list(SMOOTH_GROUP_WINDOW_FULLTILE, SMOOTH_GROUP_WALLS, SMOOTH_GROUP_AIRLOCK, SMOOTH_GROUP_SHUTTERS_BLASTDOORS)
+ smoothing_groups = (SMOOTH_GROUP_WINDOW_FULLTILE)
+ canSmoothWith = (SMOOTH_GROUP_SHUTTERS_BLASTDOORS + SMOOTH_GROUP_AIRLOCK + SMOOTH_GROUP_WINDOW_FULLTILE + SMOOTH_GROUP_WALLS)
/obj/structure/window/reinforced/tinted
name = "tinted window"
@@ -686,8 +686,8 @@
force_threshold = 7
// smoothing_flags = SMOOTH_BITMASK
- smoothing_groups = list(SMOOTH_GROUP_WINDOW_FULLTILE_SHUTTLE)
- canSmoothWith = list(SMOOTH_GROUP_WINDOW_FULLTILE_SHUTTLE, SMOOTH_GROUP_WALLS, SMOOTH_GROUP_AIRLOCK, SMOOTH_GROUP_SHUTTERS_BLASTDOORS, SMOOTH_GROUP_SHUTTLE_PARTS)
+ smoothing_groups = (SMOOTH_GROUP_WINDOW_FULLTILE_SHUTTLE)
+ canSmoothWith = (SMOOTH_GROUP_WALLS + SMOOTH_GROUP_WINDOW_FULLTILE_SHUTTLE + SMOOTH_GROUP_AIRLOCK + SMOOTH_GROUP_SHUTTERS_BLASTDOORS + SMOOTH_GROUP_SHUTTLE_PARTS)
/obj/structure/window/reinforced/polarized
name = "electrochromic window"
@@ -700,8 +700,8 @@
fulltile = TRUE
// smoothing_flags = SMOOTH_BITMASK
- smoothing_groups = list(SMOOTH_GROUP_WINDOW_FULLTILE)
- canSmoothWith = list(SMOOTH_GROUP_WINDOW_FULLTILE, SMOOTH_GROUP_WALLS, SMOOTH_GROUP_AIRLOCK, SMOOTH_GROUP_SHUTTERS_BLASTDOORS)
+ smoothing_groups = (SMOOTH_GROUP_WINDOW_FULLTILE)
+ canSmoothWith = (SMOOTH_GROUP_SHUTTERS_BLASTDOORS + SMOOTH_GROUP_AIRLOCK + SMOOTH_GROUP_WINDOW_FULLTILE + SMOOTH_GROUP_WALLS)
/obj/structure/window/reinforced/polarized/attackby(obj/item/W as obj, mob/user as mob)
if(istype(W, /obj/item/multitool) && !anchored) // Only allow programming if unanchored!
diff --git a/code/game/turfs/simulated/floor.dm b/code/game/turfs/simulated/floor.dm
index 9aed6acf790..bd0a869f877 100644
--- a/code/game/turfs/simulated/floor.dm
+++ b/code/game/turfs/simulated/floor.dm
@@ -8,8 +8,8 @@
heat_capacity = 10000
smoothing_flags = SMOOTH_CUSTOM
- smoothing_groups = list(SMOOTH_GROUP_TURF_OPEN, SMOOTH_GROUP_OPEN_FLOOR)
- canSmoothWith = list(SMOOTH_GROUP_OPEN_FLOOR, SMOOTH_GROUP_TURF_OPEN)
+ smoothing_groups = (SMOOTH_GROUP_TURF_OPEN + SMOOTH_GROUP_OPEN_FLOOR)
+ canSmoothWith = (SMOOTH_GROUP_TURF_OPEN + SMOOTH_GROUP_OPEN_FLOOR)
// Damage to flooring.
var/broken
@@ -24,7 +24,8 @@
'sound/effects/footstep/plating2.ogg',
'sound/effects/footstep/plating3.ogg',
'sound/effects/footstep/plating4.ogg',
- 'sound/effects/footstep/plating5.ogg'))
+ 'sound/effects/footstep/plating5.ogg',
+ ))
var/list/old_decals = null // Remember what decals we had between being pried up and replaced.
diff --git a/code/game/turfs/simulated/flooring/flooring_premade.dm b/code/game/turfs/simulated/flooring/flooring_premade.dm
index e4669a92324..7a4f67c5d49 100644
--- a/code/game/turfs/simulated/flooring/flooring_premade.dm
+++ b/code/game/turfs/simulated/flooring/flooring_premade.dm
@@ -5,8 +5,8 @@
initial_flooring = /singleton/flooring/carpet
// smoothing_flags = SMOOTH_BITMASK
- smoothing_groups = list(SMOOTH_GROUP_TURF_OPEN, SMOOTH_GROUP_CARPET)
- canSmoothWith = list(SMOOTH_GROUP_CARPET)
+ smoothing_groups = (SMOOTH_GROUP_TURF_OPEN + SMOOTH_GROUP_CARPET)
+ canSmoothWith = (SMOOTH_GROUP_CARPET)
/turf/simulated/floor/carpet/bcarpet
name = "black carpet"
diff --git a/code/game/turfs/simulated/floors/grass.dm b/code/game/turfs/simulated/floors/grass.dm
index 4f6cb9ad6e1..9fd20e27680 100644
--- a/code/game/turfs/simulated/floors/grass.dm
+++ b/code/game/turfs/simulated/floors/grass.dm
@@ -5,8 +5,8 @@
baseturfs = /turf/simulated/floor/outdoors/dirt
// smoothing_flags = SMOOTH_BITMASK
- smoothing_groups = list(SMOOTH_GROUP_TURF_OPEN, SMOOTH_GROUP_FLOOR_GRASS)
- canSmoothWith = list(SMOOTH_GROUP_CLOSED_TURFS, SMOOTH_GROUP_FLOOR_GRASS)
+ smoothing_groups = (SMOOTH_GROUP_TURF_OPEN + SMOOTH_GROUP_FLOOR_GRASS)
+ canSmoothWith = (SMOOTH_GROUP_FLOOR_GRASS + SMOOTH_GROUP_CLOSED_TURFS)
var/grass_chance = 20
diff --git a/code/game/turfs/simulated/floors/lava.dm b/code/game/turfs/simulated/floors/lava.dm
index 3234fcffe6d..9da76ff236e 100644
--- a/code/game/turfs/simulated/floors/lava.dm
+++ b/code/game/turfs/simulated/floors/lava.dm
@@ -16,8 +16,8 @@
special_temperature = T0C + 2200
// smoothing_flags = SMOOTH_BITMASK | SMOOTH_BORDER
- smoothing_groups = list(SMOOTH_GROUP_TURF_OPEN, SMOOTH_GROUP_FLOOR_LAVA)
- canSmoothWith = list(SMOOTH_GROUP_FLOOR_LAVA)
+ smoothing_groups = (SMOOTH_GROUP_TURF_OPEN + SMOOTH_GROUP_FLOOR_LAVA)
+ canSmoothWith = (SMOOTH_GROUP_FLOOR_LAVA)
/turf/simulated/floor/outdoors/lava/indoors
outdoors = FALSE
diff --git a/code/game/turfs/simulated/floors/snow.dm b/code/game/turfs/simulated/floors/snow.dm
index faca31a27bb..f48e2aaaaf2 100644
--- a/code/game/turfs/simulated/floors/snow.dm
+++ b/code/game/turfs/simulated/floors/snow.dm
@@ -7,8 +7,8 @@
baseturfs = /turf/simulated/floor/outdoors/dirt
// smoothing_flags = SMOOTH_BITMASK | SMOOTH_BORDER
- smoothing_groups = list(SMOOTH_GROUP_TURF_OPEN, SMOOTH_GROUP_FLOOR_SNOW)
- canSmoothWith = list(SMOOTH_GROUP_FLOOR_SNOW)
+ smoothing_groups = (SMOOTH_GROUP_TURF_OPEN + SMOOTH_GROUP_FLOOR_SNOW)
+ canSmoothWith = (SMOOTH_GROUP_FLOOR_SNOW)
var/list/crossed_dirs = list()
diff --git a/code/game/turfs/simulated/wall.dm b/code/game/turfs/simulated/wall.dm
index 7c4e4e6dbcb..003ea8644cb 100644
--- a/code/game/turfs/simulated/wall.dm
+++ b/code/game/turfs/simulated/wall.dm
@@ -18,8 +18,8 @@
edge_blending_priority = INFINITY // let's not have floors render onto us mmkay?
smoothing_flags = SMOOTH_CUSTOM
- smoothing_groups = list(SMOOTH_GROUP_CLOSED_TURFS, SMOOTH_GROUP_WALLS)
- canSmoothWith = list(SMOOTH_GROUP_SHUTTERS_BLASTDOORS, SMOOTH_GROUP_WALLS, SMOOTH_GROUP_AIRLOCK, SMOOTH_GROUP_WINDOW_FULLTILE, SMOOTH_GROUP_LOW_WALL)
+ smoothing_groups = (SMOOTH_GROUP_WALLS + SMOOTH_GROUP_CLOSED_TURFS)
+ canSmoothWith = (SMOOTH_GROUP_SHUTTERS_BLASTDOORS + SMOOTH_GROUP_AIRLOCK + SMOOTH_GROUP_LOW_WALL + SMOOTH_GROUP_WINDOW_FULLTILE + SMOOTH_GROUP_WALLS)
var/icon/wall_masks = 'icons/turf/wall_masks.dmi'
var/damage = 0
diff --git a/code/game/turfs/turf.dm b/code/game/turfs/turf.dm
index a2e535c2f8f..0232d9ec904 100644
--- a/code/game/turfs/turf.dm
+++ b/code/game/turfs/turf.dm
@@ -98,17 +98,9 @@
levelupdate()
- if(length(smoothing_groups))
- // In case it's not properly ordered, let's avoid duplicate entries with the same values.
- tim_sort(smoothing_groups)
- SET_BITFLAG_LIST(smoothing_groups)
- if(length(canSmoothWith))
- tim_sort(canSmoothWith)
- // If the last element is higher than the maximum turf-only value, then it must scan turf contents for smoothing targets.
- if(canSmoothWith[length(canSmoothWith)] > MAX_S_TURF)
- smoothing_flags |= SMOOTH_OBJ
- SET_BITFLAG_LIST(canSmoothWith)
- if(smoothing_flags & (SMOOTH_CORNERS|SMOOTH_BITMASK))
+ SETUP_SMOOTHING()
+
+ if (smoothing_flags & (SMOOTH_CORNERS|SMOOTH_BITMASK))
QUEUE_SMOOTH(src)
//atom color stuff
diff --git a/code/game/turfs/unsimulated/floor.dm b/code/game/turfs/unsimulated/floor.dm
index 2e5edd5fcf2..3005387b309 100644
--- a/code/game/turfs/unsimulated/floor.dm
+++ b/code/game/turfs/unsimulated/floor.dm
@@ -4,7 +4,7 @@
icon_state = "Floor3"
//! NOTICE: UNSIMULATED TURFS DO NOT SMOOTH, THIS IS MORESO FOR OTHER TURFS
- smoothing_groups = list(SMOOTH_GROUP_TURF_OPEN, SMOOTH_GROUP_OPEN_FLOOR)
+ smoothing_groups = (SMOOTH_GROUP_TURF_OPEN + SMOOTH_GROUP_OPEN_FLOOR)
/turf/unsimulated/mask
name = "mask"
diff --git a/code/game/turfs/unsimulated/walls.dm b/code/game/turfs/unsimulated/walls.dm
index 6dbaf262097..87d5335bc27 100644
--- a/code/game/turfs/unsimulated/walls.dm
+++ b/code/game/turfs/unsimulated/walls.dm
@@ -7,7 +7,7 @@
blocks_air = TRUE
//! NOTICE: UNSIMULATED TURFS DO NOT SMOOTH, THIS IS MORESO FOR OTHER TURFS
- smoothing_groups = list(SMOOTH_GROUP_CLOSED_TURFS, SMOOTH_GROUP_WALLS)
+ smoothing_groups = (SMOOTH_GROUP_WALLS + SMOOTH_GROUP_CLOSED_TURFS)
/turf/unsimulated/wall/fakeglass
name = "window"
diff --git a/code/game/world.dm b/code/game/world.dm
index 0ed66ba9aa2..ac41916468c 100644
--- a/code/game/world.dm
+++ b/code/game/world.dm
@@ -1,22 +1,36 @@
-//Byond is a shit. That's why this is here.
-/*
- The initialization of the game happens roughly like this:
+#define RESTART_COUNTER_PATH "data/round_counter.txt"
- 1. All global variables are initialized (including the global_init instance).
- 2. The map is initialized, and map objects are created.
- 3. world/New() runs, creating the process scheduler (and the old master controller) and spawning their setup.
- 4. processScheduler/setup() runs, creating all the processes. game_controller/setup() runs, calling initialize() on all movable atoms in the world.
- 5. The gameSSticker is created.
-
-*/
-
-GLOBAL_VAR_INIT(tgs_initialized, FALSE)
+GLOBAL_VAR(restart_counter)
GLOBAL_VAR(topic_status_lastcache)
GLOBAL_LIST(topic_status_cache)
-//This happens after the Master subsystem new(s) (it's a global datum)
-//So subsystems globals exist, but are not initialised
+/**
+ * World creation
+ *
+ * Here is where a round itself is actually begun and setup.
+ * * db connection setup
+ * * config loaded from files
+ * * loads admins
+ * * Sets up the dynamic menu system
+ * * and most importantly, calls initialize on the master subsystem, starting the game loop that causes the rest of the game to begin processing and setting up
+ *
+ *
+ * Nothing happens until something moves. ~Albert Einstein
+ *
+ * For clarity, this proc gets triggered later in the initialization pipeline, it is not the first thing to happen, as it might seem.
+ *
+ * Initialization Pipeline:
+ * Global vars are new()'ed, (including config, glob, and the master controller will also new and preinit all subsystems when it gets new()ed)
+ * Compiled in maps are loaded (mainly centcom). all areas/turfs/objs/mobs(ATOMs) in these maps will be new()ed
+ * world/New() (You are here)
+ * Once world/New() returns, client's can connect.
+ * 1 second sleep
+ * Master Controller initialization.
+ * Subsystem initialization.
+ * Non-compiled-in maps are maploaded, all atoms are new()ed
+ * All atoms in both compiled and uncompiled maps are initialized()
+ */
/world/New()
#ifdef USE_BYOND_TRACY
#warn USE_BYOND_TRACY is enabled
@@ -88,7 +102,14 @@ GLOBAL_LIST(topic_status_cache)
//Must be done now, otherwise ZAS zones and lighting overlays need to be recreated.
createRandomZlevel()
- Master.Initialize(10, FALSE)
+ if(fexists(RESTART_COUNTER_PATH))
+ GLOB.restart_counter = text2num(trim(file2text(RESTART_COUNTER_PATH)))
+ fdel(RESTART_COUNTER_PATH)
+
+ if(NO_INIT_PARAMETER in params)
+ return
+
+ Master.Initialize(10, FALSE, TRUE)
#ifdef UNIT_TESTS
HandleTestRun()
@@ -100,7 +121,6 @@ GLOBAL_LIST(topic_status_cache)
/world/proc/InitTgs()
TgsNew(new /datum/tgs_event_handler/impl, TGS_SECURITY_TRUSTED)
GLOB.revdata.load_tgs_info()
- GLOB.tgs_initialized = TRUE
/world/proc/HandleTestRun()
//trigger things to run the whole process
@@ -109,11 +129,11 @@ GLOBAL_LIST(topic_status_cache)
CONFIG_SET(number/round_end_countdown, 0)
var/datum/callback/cb
#ifdef UNIT_TESTS
- cb = CALLBACK(GLOBAL_PROC, /proc/RunUnitTests)
+ cb = CALLBACK(GLOBAL_PROC, GLOBAL_PROC_REF(RunUnitTests))
#else
cb = VARSET_CALLBACK(SSticker, force_ending, TRUE)
#endif
- SSticker.OnRoundstart(CALLBACK(GLOBAL_PROC, /proc/_addtimer, cb, 10 SECONDS))
+ SSticker.OnRoundstart(CALLBACK(GLOBAL_PROC, GLOBAL_PROC_REF(_addtimer), cb, 10 SECONDS))
/world/proc/SetupLogs()
var/override_dir = params[OVERRIDE_LOG_DIRECTORY_PARAMETER]
@@ -212,8 +232,8 @@ GLOBAL_LIST(topic_status_cache)
set waitfor = FALSE
var/list/fail_reasons
if(GLOB)
- if(global.total_runtimes != 0)
- fail_reasons = list("Total runtimes: [global.total_runtimes] - if you don't see any runtimes above, launch locally with `dreamseeker -trusted -verbose citadel.dmb` after compile and check Options and Messages. Inform a maintainer too, if this happens..")
+ if(GLOB.total_runtimes != 0)
+ fail_reasons = list("Total runtimes: [GLOB.total_runtimes]")
#ifdef UNIT_TESTS
if(GLOB.failed_any_test)
LAZYADD(fail_reasons, "Unit Tests failed!")
@@ -226,29 +246,26 @@ GLOBAL_LIST(topic_status_cache)
text2file("Success!", "[GLOB.log_directory]/clean_run.lk")
else
log_world("Test run failed!\n[fail_reasons.Join("\n")]")
- sleep(0) //yes, 0, this'll let Reboot finish and prevent byond memes
- qdel(src) //shut it down
+ sleep(0) //yes, 0, this'll let Reboot finish and prevent byond memes
+ qdel(src) //shut it down
/world/Reboot(reason = 0, fast_track = FALSE)
if (reason || fast_track) //special reboot, do none of the normal stuff
if (usr)
log_admin("[key_name(usr)] Has requested an immediate world restart via client side debugging tools")
message_admins("[key_name_admin(usr)] Has requested an immediate world restart via client side debugging tools")
- to_chat(world, "Rebooting World immediately due to host request")
+ to_chat(world, SPAN_BOLDANNOUNCE("Rebooting World immediately due to host request."))
else
- to_chat(world, "Rebooting world...")
+ to_chat(world, SPAN_BOLDANNOUNCE("Rebooting world..."))
if(blackbox)
blackbox.save_all_data_to_sql()
Master.Shutdown() //run SS shutdowns
- TgsReboot()
-
#ifdef UNIT_TESTS
FinishTestRun()
return
#endif
-/*
if(TgsAvailable())
var/do_hard_reboot
// check the hard reboot counter
@@ -269,9 +286,10 @@ GLOBAL_LIST(topic_status_cache)
log_world("World hard rebooted at [time_stamp()]")
shutdown_logging() // See comment below.
TgsEndProcess()
-*/
log_world("World rebooted at [time_stamp()]")
+
+ TgsReboot()
shutdown_logging() // Past this point, no logging procs can be used, at risk of data loss.
//! Shutdown Auxtools
@@ -286,7 +304,7 @@ GLOBAL_LIST(topic_status_cache)
var/debug_server = world.GetConfig("env", "AUXTOOLS_DEBUG_DLL")
if (debug_server)
call(debug_server, "auxtools_shutdown")()
- ..()
+ . = ..()
/hook/startup/proc/loadMode()
world.load_mode()
diff --git a/code/modules/admin/admin.dm b/code/modules/admin/admin.dm
index 754cd59a974..c49e94a39f8 100644
--- a/code/modules/admin/admin.dm
+++ b/code/modules/admin/admin.dm
@@ -908,17 +908,31 @@ var/datum/legacy_announcement/minor/admin_min_announcer = new
/datum/admins/proc/delay_end()
set category = "Server"
- set desc="Delay the game end"
- set name="Delay reboot"
+ set desc = "Prevent the server from restarting"
+ set name = "Delay Reboot"
if(!check_rights(R_SERVER))
return
- SSticker.delay_end = !SSticker.delay_end
- var/msg = "[SSticker.delay_end ? "delayed" : "undelayed"] the round end"
- log_admin("[key_name(usr)] [msg]")
- message_admins("[key_name_admin(usr)] [msg]")
- if(SSticker.ready_for_reboot && !SSticker.delay_end) //we undelayed after standard reboot would occur
- SSticker.standard_reboot()
+
+ if(SSticker.delay_end)
+ tgui_alert(usr, "The round end is already delayed. The reason for the current delay is: \"[SSticker.admin_delay_notice]\"", "Alert", list("Ok"))
+ return
+
+ var/delay_reason = input(usr, "Enter a reason for delaying the round end", "Round Delay Reason") as null|text
+
+ if(isnull(delay_reason))
+ return
+
+ if(SSticker.delay_end)
+ tgui_alert(usr, "The round end is already delayed. The reason for the current delay is: \"[SSticker.admin_delay_notice]\"", "Alert", list("Ok"))
+ return
+
+ SSticker.delay_end = TRUE
+ SSticker.admin_delay_notice = delay_reason
+
+ log_admin("[key_name(usr)] delayed the round end for reason: [SSticker.admin_delay_notice]")
+ message_admins("[key_name_admin(usr)] delayed the round end for reason: [SSticker.admin_delay_notice]")
+ // SSblackbox.record_feedback("nested tally", "admin_toggle", 1, list("Delay Round End", "Reason: [delay_reason]")) //If you are copy-pasting this, ensure the 2nd parameter is unique to the new proc!
/datum/admins/proc/delay_start()
set category = "Server"
diff --git a/code/modules/admin/verbs/server/admin_reboot.dm b/code/modules/admin/verbs/server/admin_reboot.dm
index b09effeaf2a..bc16edf05c8 100644
--- a/code/modules/admin/verbs/server/admin_reboot.dm
+++ b/code/modules/admin/verbs/server/admin_reboot.dm
@@ -1,36 +1,43 @@
/datum/admins/proc/restart()
set category = "Server"
set name = "Reboot World"
- set desc="Restarts the world immediately"
+ set desc = "Restarts the world immediately"
if (!usr.client.holder)
return
- var/list/options = list("Regular Restart", "Hard Restart (No Delay/Feeback Reason)", "Hardest Restart (No actions, just reboot)")
+ var/localhost_addresses = list("127.0.0.1", "::1")
+ var/list/options = list("Regular Restart", "Regular Restart (with delay)", "Hard Restart (No Delay/Feeback Reason)", "Hardest Restart (No actions, just reboot)")
if(world.TgsAvailable())
options += "Server Restart (Kill and restart DD)";
- var/rebootconfirm
- //if(SSticker.admin_delay_notice)
- // if(alert(usr, "Are you sure you want to reboot? An admin has already delayed the round end for the following reason: [SSticker.admin_delay_notice]", "Confirmation", "Yes", "No") == "Yes")
- if(alert(usr, "Are you sure you want to reboot?", "Confirmation", "Yes", "No") == "Yes")
- rebootconfirm = TRUE
- if(rebootconfirm)
- var/result = input(usr, "Select reboot method", "World Reboot", options[1]) as null|anything in options
- if(result)
- //SSblackbox.record_feedback("tally", "admin_verb", 1, "Reboot World") //If you are copy-pasting this, ensure the 2nd parameter is unique to the new proc!
- var/init_by = "Initiated by [usr && usr.client && usr.client.holder && usr.client.holder.fakekey ? "Admin" : usr.key]."
- switch(result)
- if("Regular Restart")
- //SSticker.Reboot(init_by, "admin reboot - by [usr.key] [usr.client.holder.fakekey ? "(stealth)" : ""]", 10)
- to_chat(world, "Rebooting world in 1 second! [init_by]")
- spawn(10)
- world.Reboot()
- if("Hard Restart (No Delay, No Feeback Reason)")
- to_chat(world, "World reboot - [init_by]")
- world.Reboot()
- if("Hardest Restart (No actions, just reboot)")
- to_chat(world, "Hard world reboot - [init_by]")
- world.Reboot(fast_track = TRUE)
- if("Server Restart (Kill and restart DD)")
- to_chat(world, "Server hard process restart - [init_by]")
- world.TgsEndProcess()
+ if(SSticker.admin_delay_notice)
+ if(alert(usr, "Are you sure? An admin has already delayed the round end for the following reason: [SSticker.admin_delay_notice]", "Confirmation", "Yes", "No") != "Yes")
+ return FALSE
+
+ var/result = input(usr, "Select reboot method", "World Reboot", options[1]) as null|anything in options
+ if(result)
+ // SSblackbox.record_feedback("tally", "admin_verb", 1, "Reboot World") //If you are copy-pasting this, ensure the 2nd parameter is unique to the new proc!
+ var/init_by = "Initiated by [usr.client.holder.fakekey ? "Admin" : usr.key]."
+ switch(result)
+ if("Regular Restart")
+ if(!(isnull(usr.client.address) || (usr.client.address in localhost_addresses)))
+ if(alert(usr, "Are you sure you want to restart the server?","This server is live", "Restart", "Cancel") != "Restart")
+ return FALSE
+ SSticker.Reboot(init_by, "admin reboot - by [usr.key] [usr.client.holder.fakekey ? "(stealth)" : ""]", 1 SECOND)
+ if("Regular Restart (with delay)")
+ var/delay = input("What delay should the restart have (in seconds)?", "Restart Delay", 5) as num|null
+ if(!delay)
+ return FALSE
+ if(!(isnull(usr.client.address) || (usr.client.address in localhost_addresses)))
+ if(alert(usr,"Are you sure you want to restart the server?","This server is live", "Restart", "Cancel") != "Restart")
+ return FALSE
+ SSticker.Reboot(init_by, "admin reboot - by [usr.key] [usr.client.holder.fakekey ? "(stealth)" : ""]", delay * 10)
+ if("Hard Restart (No Delay, No Feeback Reason)")
+ to_chat(world, "World reboot - [init_by]")
+ world.Reboot()
+ if("Hardest Restart (No actions, just reboot)")
+ to_chat(world, "Hard world reboot - [init_by]")
+ world.Reboot(fast_track = TRUE)
+ if("Server Restart (Kill and restart DD)")
+ to_chat(world, "Server restart - [init_by]")
+ world.TgsEndProcess()
diff --git a/code/modules/error_handler/error_handler.dm b/code/modules/error_handler/error_handler.dm
index a891f503229..a6f2d5af0a5 100644
--- a/code/modules/error_handler/error_handler.dm
+++ b/code/modules/error_handler/error_handler.dm
@@ -1,7 +1,7 @@
// Why? Because when you screw up too early in init, total runtimes won't be initialized. You can see why this can be a problem, right?
-GLOBAL_REAL_VAR(total_runtimes) = 0
-GLOBAL_VAR_INIT(total_runtimes_seen, 0)
+GLOBAL_VAR_INIT(total_runtimes, GLOB.total_runtimes || 0)
GLOBAL_VAR_INIT(total_runtimes_skipped, 0)
+GLOBAL_VAR_INIT(total_runtimes_seen, 0)
// to detect when someone fucks up royally and breaks error handling with preinit runtimes
GLOBAL_REAL_VAR(runtime_skip_once) = FALSE
GLOBAL_REAL_VAR(runtime_trap_triggered) = FALSE
@@ -18,7 +18,7 @@ GLOBAL_REAL_VAR(runtime_trap_triggered) = FALSE
return
#endif
- ++global.total_runtimes
+ ++GLOB.total_runtimes
var/static/list/error_last_seen = list()
var/static/list/error_cooldown = list() /* Error_cooldown items will either be positive(cooldown time) or negative(silenced error)
diff --git a/code/modules/error_handler/error_viewer.dm b/code/modules/error_handler/error_viewer.dm
index e7722af726d..87d93fd19e7 100644
--- a/code/modules/error_handler/error_viewer.dm
+++ b/code/modules/error_handler/error_viewer.dm
@@ -80,7 +80,7 @@ GLOBAL_DATUM(error_cache, /datum/error_viewer/error_cache)
/datum/error_viewer/error_cache/show_to(user, datum/error_viewer/back_to, linear)
var/html = build_header()
- html += "[global.total_runtimes] runtimes, [GLOB.total_runtimes_skipped] skipped
"
+ html += "[GLOB.total_runtimes] runtimes, [GLOB.total_runtimes_skipped] skipped
"
if (!linear)
html += "organized | [make_link("linear", null, 1)]
"
var/datum/error_viewer/error_source/error_source
diff --git a/code/modules/holodeck/HolodeckObjects.dm b/code/modules/holodeck/HolodeckObjects.dm
index 74b0f00c8f6..f46cb40da5e 100644
--- a/code/modules/holodeck/HolodeckObjects.dm
+++ b/code/modules/holodeck/HolodeckObjects.dm
@@ -20,8 +20,8 @@
initial_flooring = /singleton/flooring/carpet
// smoothing_flags = SMOOTH_BITMASK
- smoothing_groups = list(SMOOTH_GROUP_TURF_OPEN, SMOOTH_GROUP_CARPET)
- canSmoothWith = list(SMOOTH_GROUP_CARPET)
+ smoothing_groups = (SMOOTH_GROUP_TURF_OPEN + SMOOTH_GROUP_CARPET)
+ canSmoothWith = (SMOOTH_GROUP_CARPET)
/turf/simulated/floor/holofloor/tiled
name = "floor"
diff --git a/code/modules/mining/mine_turfs.dm b/code/modules/mining/mine_turfs.dm
index b0645f552e6..132cb0bdcb7 100644
--- a/code/modules/mining/mine_turfs.dm
+++ b/code/modules/mining/mine_turfs.dm
@@ -5,7 +5,7 @@
icon_state = "rock-dark"
density = 1
- smoothing_groups = list(SMOOTH_GROUP_CLOSED_TURFS, SMOOTH_GROUP_MINERAL_WALLS)
+ smoothing_groups = (SMOOTH_GROUP_CLOSED_TURFS + SMOOTH_GROUP_MINERAL_WALLS)
/turf/simulated/mineral //wall piece
name = "rock"
@@ -20,8 +20,8 @@
has_resources = 1
// smoothing_flags = SMOOTH_BITMASK | SMOOTH_BORDER
- smoothing_groups = list(SMOOTH_GROUP_CLOSED_TURFS, SMOOTH_GROUP_MINERAL_WALLS)
- canSmoothWith = list(SMOOTH_GROUP_MINERAL_WALLS)
+ smoothing_groups = (SMOOTH_GROUP_CLOSED_TURFS + SMOOTH_GROUP_MINERAL_WALLS)
+ canSmoothWith = (SMOOTH_GROUP_MINERAL_WALLS)
var/sand_icon = 'icons/turf/flooring/asteroid.dmi'
var/rock_side_icon_state = "rock_side"
diff --git a/code/modules/overmap/ships/engines/gas_thruster.dm b/code/modules/overmap/ships/engines/gas_thruster.dm
index 34f7a0447cd..c59cba6ca07 100644
--- a/code/modules/overmap/ships/engines/gas_thruster.dm
+++ b/code/modules/overmap/ships/engines/gas_thruster.dm
@@ -83,9 +83,16 @@
. = ..()
controller = new(src)
update_nearby_tiles()
+ SSshuttle.unary_engines += src
if(SSshuttle.initialized)
link_to_ship()
+/obj/machinery/atmospherics/component/unary/engine/Destroy()
+ QDEL_NULL(controller)
+ SSshuttle.unary_engines -= src
+ update_nearby_tiles()
+ . = ..()
+
/obj/machinery/atmospherics/component/unary/engine/proc/link_to_ship()
for(var/ship in SSshuttle.ships)
var/obj/effect/overmap/visitable/ship/S = ship
@@ -97,11 +104,6 @@
set_broken(FALSE)
linked = TRUE
-/obj/machinery/atmospherics/component/unary/engine/Destroy()
- QDEL_NULL(controller)
- update_nearby_tiles()
- . = ..()
-
/obj/machinery/atmospherics/component/unary/engine/proc/get_status()
. = list()
.+= "Location: [get_area(src)]."
diff --git a/code/modules/overmap/ships/engines/ion_thruster.dm b/code/modules/overmap/ships/engines/ion_thruster.dm
index 4e52fdec696..ee3a8dc51ba 100644
--- a/code/modules/overmap/ships/engines/ion_thruster.dm
+++ b/code/modules/overmap/ships/engines/ion_thruster.dm
@@ -42,6 +42,7 @@
power_channel = ENVIRON
idle_power_usage = 100
anchored = TRUE
+
// construct_state = /singleton/machine_construction/default/panel_closed
var/datum/ship_engine/ion/controller
var/thrust_limit = 1
@@ -53,9 +54,15 @@
/obj/machinery/ion_engine/Initialize(mapload)
. = ..()
controller = new(src)
+ SSshuttle.ion_engines += src
if(SSshuttle.initialized)
link_to_ship()
+/obj/machinery/ion_engine/Destroy()
+ QDEL_NULL(controller)
+ SSshuttle.ion_engines -= src
+ . = ..()
+
/obj/machinery/ion_engine/proc/link_to_ship()
for(var/ship in SSshuttle.ships)
var/obj/effect/overmap/visitable/ship/S = ship
@@ -63,10 +70,6 @@
S.engines |= controller
linked = TRUE
-/obj/machinery/ion_engine/Destroy()
- QDEL_NULL(controller)
- . = ..()
-
/obj/machinery/ion_engine/proc/get_status()
. = list()
.+= "Location: [get_area(src)]."
diff --git a/code/modules/tables/presets.dm b/code/modules/tables/presets.dm
index 3f840f938c8..aaffca8c80b 100644
--- a/code/modules/tables/presets.dm
+++ b/code/modules/tables/presets.dm
@@ -47,8 +47,8 @@
icon_state = "reinf_preview"
color = "#824B28"
- smoothing_groups = list(SMOOTH_GROUP_WOOD_TABLES) //Don't smooth with SMOOTH_GROUP_TABLES
- canSmoothWith = list(SMOOTH_GROUP_WOOD_TABLES)
+ smoothing_groups = (SMOOTH_GROUP_WOOD_TABLES) //Don't smooth with SMOOTH_GROUP_TABLES
+ canSmoothWith = (SMOOTH_GROUP_WOOD_TABLES)
/obj/structure/table/wooden_reinforced/Initialize(mapload)
material = get_material_by_name("wood")
@@ -59,8 +59,8 @@
icon_state = "plain_preview"
color = "#824B28"
- smoothing_groups = list(SMOOTH_GROUP_WOOD_TABLES) //Don't smooth with SMOOTH_GROUP_TABLES
- canSmoothWith = list(SMOOTH_GROUP_WOOD_TABLES)
+ smoothing_groups = (SMOOTH_GROUP_WOOD_TABLES) //Don't smooth with SMOOTH_GROUP_TABLES
+ canSmoothWith = (SMOOTH_GROUP_WOOD_TABLES)
/obj/structure/table/woodentable/Initialize(mapload)
material = get_material_by_name("wood")
@@ -70,8 +70,8 @@
icon_state = "plain_preview"
color = "#824B28"
- smoothing_groups = list(SMOOTH_GROUP_WOOD_TABLES) //Don't smooth with SMOOTH_GROUP_TABLES
- canSmoothWith = list(SMOOTH_GROUP_WOOD_TABLES)
+ smoothing_groups = (SMOOTH_GROUP_WOOD_TABLES) //Don't smooth with SMOOTH_GROUP_TABLES
+ canSmoothWith = (SMOOTH_GROUP_WOOD_TABLES)
/obj/structure/table/sifwoodentable/Initialize(mapload)
material = get_material_by_name("alien wood")
@@ -81,8 +81,8 @@
icon_state = "reinf_preview"
color = "#824B28"
- smoothing_groups = list(SMOOTH_GROUP_WOOD_TABLES) //Don't smooth with SMOOTH_GROUP_TABLES
- canSmoothWith = list(SMOOTH_GROUP_WOOD_TABLES)
+ smoothing_groups = (SMOOTH_GROUP_WOOD_TABLES) //Don't smooth with SMOOTH_GROUP_TABLES
+ canSmoothWith = (SMOOTH_GROUP_WOOD_TABLES)
/obj/structure/table/sifwooden_reinforced/Initialize(mapload)
material = get_material_by_name("alien wood")
@@ -93,8 +93,8 @@
icon_state = "stone_preview"
color = "#42291a"
- smoothing_groups = list(SMOOTH_GROUP_WOOD_TABLES) //Don't smooth with SMOOTH_GROUP_TABLES
- canSmoothWith = list(SMOOTH_GROUP_WOOD_TABLES)
+ smoothing_groups = (SMOOTH_GROUP_WOOD_TABLES) //Don't smooth with SMOOTH_GROUP_TABLES
+ canSmoothWith = (SMOOTH_GROUP_WOOD_TABLES)
/obj/structure/table/hardwoodtable/Initialize(mapload)
material = get_material_by_name("hardwood")
@@ -113,8 +113,8 @@
color = "#00E1FF"
alpha = 77 // 0.3 * 255
- smoothing_groups = list(SMOOTH_GROUP_GLASS_TABLES)
- canSmoothWith = list(SMOOTH_GROUP_GLASS_TABLES)
+ smoothing_groups = (SMOOTH_GROUP_GLASS_TABLES)
+ canSmoothWith = (SMOOTH_GROUP_GLASS_TABLES)
/obj/structure/table/glass/Initialize(mapload)
material = get_material_by_name("glass")
diff --git a/code/modules/tables/tables.dm b/code/modules/tables/tables.dm
index 0ddc839fe41..290349ae564 100644
--- a/code/modules/tables/tables.dm
+++ b/code/modules/tables/tables.dm
@@ -14,8 +14,8 @@ var/list/table_icon_cache = list()
connections = list("nw0", "ne0", "sw0", "se0")
// smoothing_flags = SMOOTH_BITMASK
- smoothing_groups = list(SMOOTH_GROUP_TABLES)
- canSmoothWith = list(SMOOTH_GROUP_TABLES)
+ smoothing_groups = (SMOOTH_GROUP_TABLES)
+ canSmoothWith = (SMOOTH_GROUP_TABLES)
var/flipped = 0
var/maxhealth = 10
diff --git a/code/modules/unit_tests/_unit_tests.dm b/code/modules/unit_tests/_unit_tests.dm
index f32f8aca72b..ae3ba1edf6f 100644
--- a/code/modules/unit_tests/_unit_tests.dm
+++ b/code/modules/unit_tests/_unit_tests.dm
@@ -3,9 +3,18 @@
#if defined(UNIT_TESTS) || defined(SPACEMAN_DMM)
+/// 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"]") }
+#define TEST_ASSERT(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
@@ -13,7 +22,7 @@
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]" : ""]"); \
+ return Fail("Expected [isnull(lhs) ? "null" : lhs] to be equal to [isnull(rhs) ? "null" : rhs].[message ? " [message]" : ""]", __FILE__, __LINE__); \
} \
} while (FALSE)
@@ -23,7 +32,7 @@
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]" : ""]"); \
+ return Fail("Expected [isnull(lhs) ? "null" : lhs] to not be equal to [isnull(rhs) ? "null" : rhs].[message ? " [message]" : ""]", __FILE__, __LINE__); \
} \
} while (FALSE)
@@ -32,6 +41,39 @@
/// Intended to be used in the manner of `TEST_FOCUS(/datum/unit_test/math)`
#define TEST_FOCUS(test_path) ##test_path { focus = TRUE; }
+/// Logs a noticable message on GitHub, but will not mark as an error.
+/// Use this when something shouldn't happen and is of note, but shouldn't block CI.
+/// Does not mark the test as failed.
+#define TEST_NOTICE(source, message) source.log_for_test((##message), "notice", __FILE__, __LINE__)
+
+/// Constants indicating unit test completion status
+#define UNIT_TEST_PASSED 0
+#define UNIT_TEST_FAILED 1
+#define UNIT_TEST_SKIPPED 2
+
+#define TEST_PRE 0
+#define TEST_DEFAULT 1
+/// After most test steps, used for tests that run long so shorter issues can be noticed faster
+#define TEST_LONGER 10
+/// This must be the last test to run due to the inherent nature of the test iterating every single tangible atom in the game and qdeleting all of them (while taking long sleeps to make sure the garbage collector fires properly) taking a large amount of time.
+#define TEST_CREATE_AND_DESTROY INFINITY
+
+/// Change color to red on ANSI terminal output, if enabled with -DANSICOLORS.
+#ifdef ANSICOLORS
+#define TEST_OUTPUT_RED(text) "\x1B\x5B1;31m[text]\x1B\x5B0m"
+#else
+#define TEST_OUTPUT_RED(text) (text)
+#endif
+/// Change color to green on ANSI terminal output, if enabled with -DANSICOLORS.
+#ifdef ANSICOLORS
+#define TEST_OUTPUT_GREEN(text) "\x1B\x5B1;32m[text]\x1B\x5B0m"
+#else
+#define TEST_OUTPUT_GREEN(text) (text)
+#endif
+
+/// A trait source when adding traits through unit tests
+#define TRAIT_SOURCE_UNIT_TESTS "unit_tests"
+
#include "core/_core.dm"
#include "human/_human.dm"
#include "language/_language.dm"
@@ -42,10 +84,12 @@
#include "bespoke_id.dm"
// #include "card_mismatch.dm"
// #include "chain_pull_through_space.dm"
+// #include "character_saving.dm"
// #include "combat.dm"
#include "component_tests.dm"
// #include "confusion.dm"
// #include "emoting.dm"
+#include "focus_only_tests.dm"
// #include "heretic_knowledge.dm"
// #include "holidays.dm"
#include "initialize_sanity.dm"
@@ -77,9 +121,6 @@
#include "timer_sanity.dm"
#include "unit_test.dm"
-/// CIT TESTS
-// #include "character_saving.dm"
-
#undef TEST_ASSERT
#undef TEST_ASSERT_EQUAL
#undef TEST_ASSERT_NOTEQUAL
diff --git a/code/modules/unit_tests/bespoke_id.dm b/code/modules/unit_tests/bespoke_id.dm
index 06676c626c7..e1356650ded 100644
--- a/code/modules/unit_tests/bespoke_id.dm
+++ b/code/modules/unit_tests/bespoke_id.dm
@@ -5,4 +5,4 @@
for(var/i in subtypesof(/datum/element))
var/datum/element/faketype = i
if((initial(faketype.element_flags) & ELEMENT_BESPOKE) && initial(faketype.id_arg_index) == base_index)
- Fail("A bespoke element was not configured with a proper id_arg_index: [faketype]")
+ TEST_FAIL("A bespoke element was not configured with a proper id_arg_index: [faketype]")
diff --git a/code/modules/unit_tests/chain_pull_through_space.dm b/code/modules/unit_tests/chain_pull_through_space.dm
index 0d86db2a5be..b718da91c2c 100644
--- a/code/modules/unit_tests/chain_pull_through_space.dm
+++ b/code/modules/unit_tests/chain_pull_through_space.dm
@@ -1,36 +1,42 @@
/datum/unit_test/chain_pull_through_space
- var/turf/space/space_tile
- var/turf/claimed_tile
+ var/turf/open/space/space_tile
+ var/claimed_tile
var/mob/living/carbon/human/alice
var/mob/living/carbon/human/bob
var/mob/living/carbon/human/charlie
+ var/targetz = 5
+ var/datum/turf_reservation/reserved
/datum/unit_test/chain_pull_through_space/New()
..()
- // Create a space tile that goes to another z-level
- claimed_tile = run_loc_bottom_left
+ //reserve a tile that is always empty for our z destination
+ reserved = SSmapping.RequestBlockReservation(5,5)
- space_tile = new(locate(run_loc_bottom_left.x, run_loc_bottom_left.y, run_loc_bottom_left.z))
- space_tile.destination_x = 100
- space_tile.destination_y = 100
- space_tile.destination_z = 5
+ // Create a space tile that goes to another z-level
+ claimed_tile = run_loc_floor_bottom_left.type
+
+ space_tile = new(locate(run_loc_floor_bottom_left.x, run_loc_floor_bottom_left.y, run_loc_floor_bottom_left.z))
+ space_tile.destination_x = round(reserved.bottom_left_coords[1] + (reserved.width-1) / 2)
+ space_tile.destination_y = round(reserved.bottom_left_coords[2] + (reserved.height-1) / 2)
+ space_tile.destination_z = reserved.bottom_left_coords[3]
// Create our list of humans, all adjacent to one another
- alice = new(locate(run_loc_bottom_left.x + 2, run_loc_bottom_left.y, run_loc_bottom_left.z))
+ alice = new(locate(run_loc_floor_bottom_left.x + 2, run_loc_floor_bottom_left.y, run_loc_floor_bottom_left.z))
alice.name = "Alice"
- bob = new(locate(run_loc_bottom_left.x + 3, run_loc_bottom_left.y, run_loc_bottom_left.z))
+ bob = new(locate(run_loc_floor_bottom_left.x + 3, run_loc_floor_bottom_left.y, run_loc_floor_bottom_left.z))
bob.name = "Bob"
- charlie = new(locate(run_loc_bottom_left.x + 4, run_loc_bottom_left.y, run_loc_bottom_left.z))
+ charlie = new(locate(run_loc_floor_bottom_left.x + 4, run_loc_floor_bottom_left.y, run_loc_floor_bottom_left.z))
charlie.name = "Charlie"
/datum/unit_test/chain_pull_through_space/Destroy()
- space_tile.copyTurf(claimed_tile)
+ space_tile.ChangeTurf(claimed_tile)
qdel(alice)
qdel(bob)
qdel(charlie)
+ qdel(reserved)
return ..()
/datum/unit_test/chain_pull_through_space/Run()
@@ -40,23 +46,16 @@
bob.start_pulling(charlie)
// Walk normally to the left, make sure we're still a chain
- alice.Move(locate(run_loc_bottom_left.x + 1, run_loc_bottom_left.y, run_loc_bottom_left.z))
- if (bob.x != run_loc_bottom_left.x + 2)
- return Fail("During normal move, Bob was not at the correct x ([bob.x])")
- if (charlie.x != run_loc_bottom_left.x + 3)
- return Fail("During normal move, Charlie was not at the correct x ([charlie.x])")
+ alice.Move(locate(run_loc_floor_bottom_left.x + 1, run_loc_floor_bottom_left.y, run_loc_floor_bottom_left.z))
+ TEST_ASSERT_EQUAL(bob.x, run_loc_floor_bottom_left.x + 2, "During normal move, Bob was not at the correct x ([bob.x])")
+ TEST_ASSERT_EQUAL(charlie.x, run_loc_floor_bottom_left.x + 3, "During normal move, Charlie was not at the correct x ([charlie.x])")
// We're going through the space turf now that should teleport us
- alice.Move(run_loc_bottom_left)
- if (alice.z != space_tile.destination_z)
- return Fail("Alice did not teleport to the destination z-level. Current location: ([alice.x], [alice.y], [alice.z])")
+ alice.Move(run_loc_floor_bottom_left)
+ TEST_ASSERT_EQUAL(alice.z, space_tile.destination_z, "Alice did not teleport to the destination z-level. Current location: ([alice.x], [alice.y], [alice.z])")
- if (bob.z != space_tile.destination_z)
- return Fail("Bob did not teleport to the destination z-level. Current location: ([bob.x], [bob.y], [bob.z])")
- if (!bob.Adjacent(alice))
- return Fail("Bob is not adjacent to Alice. Bob is at [bob.x], Alice is at [alice.x]")
+ TEST_ASSERT_EQUAL(bob.z, space_tile.destination_z, "Bob did not teleport to the destination z-level. Current location: ([bob.x], [bob.y], [bob.z])")
+ TEST_ASSERT(bob.Adjacent(alice), "Bob is not adjacent to Alice. Bob is at [bob.x], Alice is at [alice.x]")
- if (charlie.z != space_tile.destination_z)
- return Fail("Charlie did not teleport to the destination z-level. Current location: ([charlie.x], [charlie.y], [charlie.z])")
- if (!charlie.Adjacent(bob))
- return Fail("Charlie is not adjacent to Bob. Charlie is at [charlie.x], Bob is at [bob.x]")
+ TEST_ASSERT_EQUAL(charlie.z, space_tile.destination_z, "Charlie did not teleport to the destination z-level. Current location: ([charlie.x], [charlie.y], [charlie.z])")
+ TEST_ASSERT(charlie.Adjacent(bob), "Charlie is not adjacent to Bob. Charlie is at [charlie.x], Bob is at [bob.x]")
diff --git a/code/modules/unit_tests/combat.dm b/code/modules/unit_tests/combat.dm
index 30bad721751..3302eca9c74 100644
--- a/code/modules/unit_tests/combat.dm
+++ b/code/modules/unit_tests/combat.dm
@@ -1,33 +1,34 @@
/datum/unit_test/harm_punch/Run()
- var/mob/living/carbon/human/puncher = allocate(/mob/living/carbon/human)
- var/mob/living/carbon/human/victim = allocate(/mob/living/carbon/human)
+ var/mob/living/carbon/human/puncher = allocate(/mob/living/carbon/human/consistent)
+ var/mob/living/carbon/human/victim = allocate(/mob/living/carbon/human/consistent)
// Avoid all randomness in tests
ADD_TRAIT(puncher, TRAIT_PERFECT_ATTACKER, INNATE_TRAIT)
- puncher.a_intent_change(INTENT_HARM)
- victim.attack_hand(puncher)
+ puncher.set_combat_mode(TRUE)
+ victim.attack_hand(puncher, list(RIGHT_CLICK = FALSE))
TEST_ASSERT(victim.getBruteLoss() > 0, "Victim took no brute damage after being punched")
/datum/unit_test/harm_melee/Run()
- var/mob/living/carbon/human/tider = allocate(/mob/living/carbon/human)
- var/mob/living/carbon/human/victim = allocate(/mob/living/carbon/human)
+ var/mob/living/carbon/human/tider = allocate(/mob/living/carbon/human/consistent)
+ var/mob/living/carbon/human/victim = allocate(/mob/living/carbon/human/consistent)
var/obj/item/storage/toolbox/toolbox = allocate(/obj/item/storage/toolbox)
tider.put_in_active_hand(toolbox, forced = TRUE)
- tider.a_intent_change(INTENT_HARM)
+ tider.set_combat_mode(TRUE)
victim.attackby(toolbox, tider)
TEST_ASSERT(victim.getBruteLoss() > 0, "Victim took no brute damage after being hit by a toolbox")
/datum/unit_test/harm_different_damage/Run()
- var/mob/living/carbon/human/attacker = allocate(/mob/living/carbon/human)
- var/mob/living/carbon/human/victim = allocate(/mob/living/carbon/human)
+ var/mob/living/carbon/human/attacker = allocate(/mob/living/carbon/human/consistent)
+ var/mob/living/carbon/human/victim = allocate(/mob/living/carbon/human/consistent)
var/obj/item/weldingtool/welding_tool = allocate(/obj/item/weldingtool)
attacker.put_in_active_hand(welding_tool, forced = TRUE)
- attacker.a_intent_change(INTENT_HARM)
+ attacker.set_combat_mode(TRUE)
+
welding_tool.attack_self(attacker) // Turn it on
victim.attackby(welding_tool, attacker)
@@ -40,25 +41,28 @@
var/pre_attack_hit
/datum/unit_test/attack_chain/proc/attack_hit()
+ SIGNAL_HANDLER
attack_hit = TRUE
/datum/unit_test/attack_chain/proc/post_attack_hit()
+ SIGNAL_HANDLER
post_attack_hit = TRUE
/datum/unit_test/attack_chain/proc/pre_attack_hit()
+ SIGNAL_HANDLER
pre_attack_hit = TRUE
/datum/unit_test/attack_chain/Run()
- var/mob/living/carbon/human/attacker = allocate(/mob/living/carbon/human)
- var/mob/living/carbon/human/victim = allocate(/mob/living/carbon/human)
+ var/mob/living/carbon/human/attacker = allocate(/mob/living/carbon/human/consistent)
+ var/mob/living/carbon/human/victim = allocate(/mob/living/carbon/human/consistent)
var/obj/item/storage/toolbox/toolbox = allocate(/obj/item/storage/toolbox)
- RegisterSignal(toolbox, COMSIG_ITEM_PRE_ATTACK, .proc/pre_attack_hit)
- RegisterSignal(toolbox, COMSIG_ITEM_ATTACK, .proc/attack_hit)
- RegisterSignal(toolbox, COMSIG_ITEM_AFTERATTACK, .proc/post_attack_hit)
+ RegisterSignal(toolbox, COMSIG_ITEM_PRE_ATTACK, PROC_REF(pre_attack_hit))
+ RegisterSignal(toolbox, COMSIG_ITEM_ATTACK, PROC_REF(attack_hit))
+ RegisterSignal(toolbox, COMSIG_ITEM_AFTERATTACK, PROC_REF(post_attack_hit))
attacker.put_in_active_hand(toolbox, forced = TRUE)
- attacker.a_intent_change(INTENT_HARM)
+ attacker.set_combat_mode(TRUE)
toolbox.melee_attack_chain(attacker, victim)
TEST_ASSERT(pre_attack_hit, "Pre-attack signal was not fired")
@@ -66,33 +70,32 @@
TEST_ASSERT(post_attack_hit, "Post-attack signal was not fired")
/datum/unit_test/disarm/Run()
- var/mob/living/carbon/human/attacker = allocate(/mob/living/carbon/human)
- var/mob/living/carbon/human/victim = allocate(/mob/living/carbon/human)
+ var/mob/living/carbon/human/attacker = allocate(/mob/living/carbon/human/consistent)
+ var/mob/living/carbon/human/victim = allocate(/mob/living/carbon/human/consistent)
var/obj/item/storage/toolbox/toolbox = allocate(/obj/item/storage/toolbox)
victim.put_in_active_hand(toolbox, forced = TRUE)
- attacker.a_intent_change(INTENT_DISARM)
var/obj/structure/barricade/dense_object = allocate(/obj/structure/barricade)
// Attacker --> Victim --> Empty space --> Wall
- attacker.forceMove(run_loc_bottom_left)
- victim.forceMove(locate(run_loc_bottom_left.x + 1, run_loc_bottom_left.y, run_loc_bottom_left.z))
- dense_object.forceMove(locate(run_loc_bottom_left.x + 3, run_loc_bottom_left.y, run_loc_bottom_left.z))
+ attacker.forceMove(run_loc_floor_bottom_left)
+ victim.forceMove(locate(run_loc_floor_bottom_left.x + 1, run_loc_floor_bottom_left.y, run_loc_floor_bottom_left.z))
+ dense_object.forceMove(locate(run_loc_floor_bottom_left.x + 3, run_loc_floor_bottom_left.y, run_loc_floor_bottom_left.z))
// First disarm, world should now look like:
// Attacker --> Empty space --> Victim --> Wall
- victim.attack_hand(attacker)
+ victim.attack_hand(attacker, list(RIGHT_CLICK = TRUE))
- TEST_ASSERT_EQUAL(victim.loc.x, run_loc_bottom_left.x + 2, "Victim wasn't moved back after being pushed")
- TEST_ASSERT(!victim.has_status_effect(STATUS_EFFECT_KNOCKDOWN), "Victim was knocked down despite not being against a wall")
+ TEST_ASSERT_EQUAL(victim.loc.x, run_loc_floor_bottom_left.x + 2, "Victim wasn't moved back after being pushed")
+ TEST_ASSERT(!victim.has_status_effect(/datum/status_effect/incapacitating/knockdown), "Victim was knocked down despite not being against a wall")
TEST_ASSERT_EQUAL(victim.get_active_held_item(), toolbox, "Victim dropped toolbox despite not being against a wall")
attacker.forceMove(get_step(attacker, EAST))
// Second disarm, victim was against wall and should be down
- victim.attack_hand(attacker)
+ victim.attack_hand(attacker, list(RIGHT_CLICK = TRUE))
- TEST_ASSERT_EQUAL(victim.loc.x, run_loc_bottom_left.x + 2, "Victim was moved after being pushed against a wall")
- TEST_ASSERT(victim.has_status_effect(STATUS_EFFECT_KNOCKDOWN), "Victim was not knocked down after being pushed against a wall")
+ TEST_ASSERT_EQUAL(victim.loc.x, run_loc_floor_bottom_left.x + 2, "Victim was moved after being pushed against a wall")
+ TEST_ASSERT(victim.has_status_effect(/datum/status_effect/incapacitating/knockdown), "Victim was not knocked down after being pushed against a wall")
TEST_ASSERT_EQUAL(victim.get_active_held_item(), null, "Victim didn't drop toolbox after being pushed against a wall")
diff --git a/code/modules/unit_tests/component_tests.dm b/code/modules/unit_tests/component_tests.dm
index 0099d7508c5..f609e73c4b7 100644
--- a/code/modules/unit_tests/component_tests.dm
+++ b/code/modules/unit_tests/component_tests.dm
@@ -8,5 +8,5 @@
var/dupe_type = initial(comp.dupe_type)
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_ASSERT(!length(bad_dms) && !length(bad_dts),
+ "Components with invalid dupe modes: ([bad_dms.Join(",")]) ||| Components with invalid dupe types: ([bad_dts.Join(",")])")
diff --git a/code/modules/unit_tests/focus_only_tests.dm b/code/modules/unit_tests/focus_only_tests.dm
new file mode 100644
index 00000000000..e7a44844255
--- /dev/null
+++ b/code/modules/unit_tests/focus_only_tests.dm
@@ -0,0 +1,21 @@
+/**
+ * These tests perform no behavior of their own, and have their tests offloaded onto other procs.
+ * This is useful in cases like in build_appearance_list where we want to know if any fail,
+ * but is not useful to right a test for.
+ * This file exists so that you can change any of these to TEST_FOCUS and only check for that test.
+ * For example, change /datum/unit_test/focus_only/invalid_overlays to TEST_FOCUS(/datum/unit_test/focus_only/invalid_overlays),
+ * and you will only test the check for invalid overlays in appearance building.
+ */
+/datum/unit_test/focus_only
+
+/// Checks that every overlay passed into build_appearance_list exists in the icon.
+/datum/unit_test/focus_only/invalid_overlays
+
+/// Checks that every icon sent to the research_designs spritesheet is valid.
+// /datum/unit_test/focus_only/invalid_research_designs
+
+/// Checks that every icon sent to vending machines is valid.
+// /datum/unit_test/focus_only/invalid_vending_machine_icon_states
+
+/// Checks that smoothing_groups and canSmoothWith are properly sorted in /atom/Initialize.
+/datum/unit_test/focus_only/sorted_smoothing_groups
diff --git a/code/modules/unit_tests/initialize_sanity.dm b/code/modules/unit_tests/initialize_sanity.dm
index d183f530c85..ccb837b17b1 100644
--- a/code/modules/unit_tests/initialize_sanity.dm
+++ b/code/modules/unit_tests/initialize_sanity.dm
@@ -1,6 +1,6 @@
/datum/unit_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",
diff --git a/code/modules/unit_tests/map_template_paths.dm b/code/modules/unit_tests/map_template_paths.dm
index a10f7a2dfa0..f005bed2299 100644
--- a/code/modules/unit_tests/map_template_paths.dm
+++ b/code/modules/unit_tests/map_template_paths.dm
@@ -6,12 +6,12 @@
if(ispath(path, /datum/map_template/submap))
var/datum/map_template/submap/S = path
if(!fexists(initial(S.prefix) + initial(S.suffix)))
- Fail("Failed to resolve [path]'s prefix+suffix to a file - [initial(S.prefix) + initial(S.suffix)].")
+ TEST_FAIL("Failed to resolve [path]'s prefix+suffix to a file - [initial(S.prefix) + initial(S.suffix)].")
continue
if(ispath(path, /datum/map_template/shuttle))
var/datum/map_template/shuttle/S = path
if(!fexists(initial(S.prefix) + initial(S.suffix)))
- Fail("Failed to resolve [path]'s prefix+suffix to a file - [initial(S.prefix) + initial(S.suffix)].")
+ TEST_FAIL("Failed to resolve [path]'s prefix+suffix to a file - [initial(S.prefix) + initial(S.suffix)].")
continue
if(!fexists("[initial(M.mappath)]"))
- Fail("Failed to resolve [path]'s initial mappath to a file - [initial(M.mappath)].")
+ TEST_FAIL("Failed to resolve [path]'s initial mappath to a file - [initial(M.mappath)].")
diff --git a/code/modules/unit_tests/research_tests.dm b/code/modules/unit_tests/research_tests.dm
index 287e1c71bc2..ad2be69c0b1 100644
--- a/code/modules/unit_tests/research_tests.dm
+++ b/code/modules/unit_tests/research_tests.dm
@@ -3,6 +3,6 @@
var/list/ids_seen = list()
for(var/datum/design/D as anything in designs)
if(ids_seen[D.id])
- Fail("Duplicate on [D.id]: [D] - [D.type]")
+ TEST_FAIL("Duplicate on [D.id]: [D] - [D.type]")
continue
ids_seen[D.id] = TRUE
diff --git a/code/modules/unit_tests/spawn_humans.dm b/code/modules/unit_tests/spawn_humans.dm
index 0500deae0af..c4922ba64f2 100644
--- a/code/modules/unit_tests/spawn_humans.dm
+++ b/code/modules/unit_tests/spawn_humans.dm
@@ -1,7 +1,9 @@
/datum/unit_test/spawn_humans/Run()
- var/locs = block(run_loc_bottom_left, run_loc_top_right)
+ var/locs = block(run_loc_floor_bottom_left, run_loc_floor_top_right)
for(var/I in 1 to 5)
new /mob/living/carbon/human(pick(locs))
+ // TODO-ZANDARIO: Do a full pass of Unit Test upgrades.
+ // new /mob/living/carbon/human/consistent(pick(locs))
- sleep(50)
+ sleep(5 SECONDS)
diff --git a/code/modules/unit_tests/subsystem_init.dm b/code/modules/unit_tests/subsystem_init.dm
index fee0dddbb95..5acf86c05dd 100644
--- a/code/modules/unit_tests/subsystem_init.dm
+++ b/code/modules/unit_tests/subsystem_init.dm
@@ -1,7 +1,14 @@
+/// Tests that all subsystems that need to properly initialize.
+/datum/unit_test/subsystem_init
+
/datum/unit_test/subsystem_init/Run()
- for(var/i in Master.subsystems)
- var/datum/controller/subsystem/ss = i
- if(ss.subsystem_flags & SS_NO_INIT)
+ for(var/datum/controller/subsystem/subsystem as anything in Master.subsystems)
+ if(subsystem.subsystem_flags & SS_NO_INIT)
continue
- if(!ss.initialized)
- Fail("[ss]([ss.type]) is a subsystem meant to initialize but doesn't get set as initialized.")
+ if(!subsystem.initialized)
+ var/message = "[subsystem] ([subsystem.type]) is a subsystem meant to initialize but doesn't get set as initialized."
+
+ if (subsystem.subsystem_flags & SS_OK_TO_FAIL_INIT)
+ TEST_NOTICE(src, "[message]\nThis subsystem is marked as SS_OK_TO_FAIL_INIT. This is still a bug, but it is non-blocking.")
+ else
+ TEST_FAIL(message)
diff --git a/code/modules/unit_tests/teleporters.dm b/code/modules/unit_tests/teleporters.dm
index 7463bc70ee8..2cb047304fb 100644
--- a/code/modules/unit_tests/teleporters.dm
+++ b/code/modules/unit_tests/teleporters.dm
@@ -1,8 +1,8 @@
/datum/unit_test/auto_teleporter_linking/Run()
// Put down the teleporter machinery
- var/obj/machinery/tele_pad/hub = allocate(/obj/machinery/tele_pad)
- var/obj/machinery/tele_projector/station = allocate(/obj/machinery/tele_projector, locate(run_loc_bottom_left.x + 1, run_loc_bottom_left.y, run_loc_bottom_left.z))
- var/obj/machinery/computer/teleporter/computer = allocate(/obj/machinery/computer/teleporter, locate(run_loc_bottom_left.x + 2, run_loc_bottom_left.y, run_loc_bottom_left.z))
+ var/obj/machinery/teleport/hub/hub = allocate(/obj/machinery/teleport/hub)
+ var/obj/machinery/teleport/station/station = allocate(/obj/machinery/teleport/station, locate(run_loc_floor_bottom_left.x + 1, run_loc_floor_bottom_left.y, run_loc_floor_bottom_left.z))
+ var/obj/machinery/computer/teleporter/computer = allocate(/obj/machinery/computer/teleporter, locate(run_loc_floor_bottom_left.x + 2, run_loc_floor_bottom_left.y, run_loc_floor_bottom_left.z))
TEST_ASSERT_EQUAL(hub.power_station, station, "Hub didn't link to the station")
TEST_ASSERT_EQUAL(station.teleporter_console, computer, "Station didn't link to the teleporter console")
diff --git a/code/modules/unit_tests/timer_sanity.dm b/code/modules/unit_tests/timer_sanity.dm
index d92323a5253..dbdf3f6d8e8 100644
--- a/code/modules/unit_tests/timer_sanity.dm
+++ b/code/modules/unit_tests/timer_sanity.dm
@@ -1,3 +1,3 @@
/datum/unit_test/timer_sanity/Run()
- if(SStimer.bucket_count < 0)
- Fail("SStimer is going into negative bucket count from something")
+ TEST_ASSERT(SStimer.bucket_count >= 0,
+ "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
index 9a1757b0064..4c006af5c14 100644
--- a/code/modules/unit_tests/unit_test.dm
+++ b/code/modules/unit_tests/unit_test.dm
@@ -3,17 +3,29 @@
Usage:
Override /Run() to run your test code
-Call Fail() to fail the test (You should specify a reason)
+Call TEST_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
+You can use the run_loc_floor_bottom_left and run_loc_floor_top_right to get turfs for testing
*/
GLOBAL_DATUM(current_test, /datum/unit_test)
GLOBAL_VAR_INIT(failed_any_test, FALSE)
GLOBAL_VAR(test_log)
+/// When unit testing, all logs sent to log_mapping are stored here and retrieved in log_mapping unit test.
+GLOBAL_LIST_EMPTY(unit_test_mapping_logs)
+
+/// The name of the test that is currently focused.
+/// Use the PERFORM_ALL_TESTS macro instead.
+GLOBAL_VAR_INIT(focused_test, focused_test())
+
+/proc/focused_test()
+ for (var/datum/unit_test/unit_test as anything in subtypesof(/datum/unit_test))
+ if (initial(unit_test.focus))
+ return unit_test
+ return null
/datum/unit_test
/// Abstract type of the test
@@ -22,98 +34,183 @@ GLOBAL_VAR(test_log)
//Bit of metadata for the future maybe
var/list/procs_tested
- /// The bottom left turf of the testing zone
- var/turf/run_loc_bottom_left
-
- /// The top right turf of the testing zone
- var/turf/run_loc_top_right
-
- /// The type of turf to allocate for the testing zone
- var/test_turf_type = /turf/simulated/floor/tiled/white
+ /// The bottom left floor turf of the testing zone
+ var/turf/run_loc_floor_bottom_left
+ /// The top right floor turf of the testing zone
+ var/turf/run_loc_floor_top_right
+ ///The priority of the test, the larger it is the later it fires
+ var/priority = TEST_DEFAULT
//internal shit
var/focus = FALSE
var/succeeded = TRUE
var/list/allocated
var/list/fail_reasons
- var/static/datum/turf_reservation/turf_reservation
+ var/static/datum/space_level/reservation
+
+/proc/cmp_unit_test_priority(datum/unit_test/a, datum/unit_test/b)
+ return initial(a.priority) - initial(b.priority)
/datum/unit_test/New()
- if (isnull(turf_reservation))
- turf_reservation = SSmapping.RequestBlockReservation(5, 5)
-
- for (var/turf/reserved_turf in turf_reservation.reserved_turfs)
- reserved_turf.ChangeTurf(test_turf_type)
+ if (isnull(reservation))
+ var/datum/map_template/unit_tests/template = new
+ reservation = template.load_new_z()
allocated = new
+ run_loc_floor_bottom_left = get_turf(locate(/obj/landmark/unit_test_bottom_left) in GLOB.landmarks_list)
+ run_loc_floor_top_right = get_turf(locate(/obj/landmark/unit_test_top_right) in GLOB.landmarks_list)
- run_loc_bottom_left = locate(turf_reservation.bottom_left_coords[1], turf_reservation.bottom_left_coords[2], turf_reservation.bottom_left_coords[3])
- run_loc_top_right = locate(turf_reservation.top_right_coords[1], turf_reservation.top_right_coords[2], turf_reservation.top_right_coords[3])
+ TEST_ASSERT(isfloorturf(run_loc_floor_bottom_left), "run_loc_floor_bottom_left was not a floor ([run_loc_floor_bottom_left])")
+ TEST_ASSERT(isfloorturf(run_loc_floor_top_right), "run_loc_floor_top_right was not a floor ([run_loc_floor_top_right])")
/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)
QDEL_LIST(allocated)
+ // clear the test area
+ for (var/turf/turf in block(locate(1, 1, run_loc_floor_bottom_left.z), locate(world.maxx, world.maxy, run_loc_floor_bottom_left.z)))
+ for (var/content in turf.contents)
+ if (istype(content, /obj/landmark))
+ continue
+ qdel(content)
return ..()
/datum/unit_test/proc/Run()
- Fail("Run() called parent or not implemented")
+ TEST_FAIL("Run() called parent or not implemented")
-/datum/unit_test/proc/Fail(reason = "No reason")
+/datum/unit_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, reason)
+ 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/unit_test/proc/allocate(type, ...)
var/list/arguments = args.Copy(2)
- if (!arguments.len)
- arguments = list(run_loc_bottom_left)
- else if (arguments[1] == null)
- arguments[1] = run_loc_bottom_left
- var/instance = new type(arglist(arguments))
+ if(ispath(type, /atom))
+ if (!arguments.len)
+ arguments = list(run_loc_floor_bottom_left)
+ else if (arguments[1] == null)
+ arguments[1] = run_loc_floor_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()
allocated += instance
return instance
+/datum/unit_test/proc/test_screenshot(name, icon/icon)
+ if (!istype(icon))
+ TEST_FAIL("[icon] is not an icon.")
+ return
+
+ var/path_prefix = replacetext(replacetext("[type]", "/datum/unit_test/", ""), "/", "_")
+ name = replacetext(name, "/", "_")
+
+ var/filename = "code/modules/unit_tests/screenshots/[path_prefix]_[name].png"
+
+ if (fexists(filename))
+ var/data_filename = "data/screenshots/[path_prefix]_[name].png"
+ fcopy(icon, data_filename)
+ log_test("\t[path_prefix]_[name] was found, putting in data/screenshots")
+ else if (fexists("code"))
+ // We are probably running in a local build
+ fcopy(icon, filename)
+ TEST_FAIL("Screenshot for [name] did not exist. One has been created.")
+ else
+ // We are probably running in real CI, so just pretend it worked and move on
+ fcopy(icon, "data/screenshots_new/[path_prefix]_[name].png")
+
+ log_test("\t[path_prefix]_[name] was put in data/screenshots_new")
+
+/// Logs a test message. Will use GitHub action syntax found at https://docs.github.com/en/actions/using-workflows/workflow-commands-for-github-actions
+/datum/unit_test/proc/log_for_test(text, priority, file, line)
+ var/map_name = SSmapping.config.map_name
+
+ // Need to escape the text to properly support newlines.
+ var/annotation_text = replacetext(text, "%", "%25")
+ annotation_text = replacetext(annotation_text, "\n", "%0A")
+
+ log_world("::[priority] file=[file],line=[line],title=[map_name]: [type]::[annotation_text]")
+
+/proc/RunUnitTest(test_path, list/test_results)
+ if (ispath(test_path, /datum/unit_test/focus_only))
+ return
+
+ var/datum/unit_test/test = new test_path
+
+ GLOB.current_test = test
+ var/duration = REALTIMEOFDAY
+
+ log_world("::group::[test_path]")
+ test.Run()
+
+ duration = REALTIMEOFDAY - duration
+ GLOB.current_test = null
+ GLOB.failed_any_test |= !test.succeeded
+
+ var/list/log_entry = list()
+ var/list/fail_reasons = test.fail_reasons
+
+ for(var/reasonID in 1 to LAZYLEN(fail_reasons))
+ var/text = fail_reasons[reasonID][1]
+ var/file = fail_reasons[reasonID][2]
+ var/line = fail_reasons[reasonID][3]
+
+ test.log_for_test(text, "error", file, line)
+
+ // Normal log message
+ log_entry += "\tFAILURE #[reasonID]: [text] at [file]:[line]"
+
+ var/message = log_entry.Join("\n")
+ log_test(message)
+
+ var/test_output_desc = "[test_path] [duration / 10]s"
+ if (test.succeeded)
+ log_world("[TEST_OUTPUT_GREEN("PASS")] [test_output_desc]")
+
+ log_world("::endgroup::")
+
+ if (!test.succeeded)
+ log_world("::error::[TEST_OUTPUT_RED("FAIL")] [test_output_desc]")
+
+ test_results[test_path] = list("status" = test.succeeded ? UNIT_TEST_PASSED : UNIT_TEST_FAILED, "message" = message, "name" = test_path)
+
+ qdel(test)
+
/proc/RunUnitTests()
CHECK_TICK
- var/tests_to_run = subtypesof(/datum/unit_test)
+ var/list/tests_to_run = subtypesof(/datum/unit_test)
+ var/list/focused_tests = list()
for (var/_test_to_run in tests_to_run)
var/datum/unit_test/test_to_run = _test_to_run
- if(initial(test_to_run.abstract_type) == _test_to_run)
- tests_to_run -= _test_to_run
if (initial(test_to_run.focus))
- tests_to_run = list(test_to_run)
- break
+ focused_tests += test_to_run
+ if(length(focused_tests))
+ tests_to_run = focused_tests
- for(var/I in tests_to_run)
- var/datum/unit_test/test = new I
+ tests_to_run = tim_sort(tests_to_run, GLOBAL_PROC_REF(cmp_unit_test_priority))
- GLOB.current_test = test
- var/duration = REALTIMEOFDAY
+ var/list/test_results = list()
- test.Run()
+ for(var/unit_path in tests_to_run)
+ CHECK_TICK //We check tick first because the unit test we run last may be so expensive that checking tick will lock up this loop forever
+ RunUnitTest(unit_path, test_results)
- duration = REALTIMEOFDAY - duration
- GLOB.current_test = null
- GLOB.failed_any_test |= !test.succeeded
-
- var/list/log_entry = list("[test.succeeded ? "PASS" : "FAIL"]: [I] [duration / 10]s")
- var/list/fail_reasons = test.fail_reasons
-
- qdel(test)
-
- for(var/J in 1 to LAZYLEN(fail_reasons))
- log_entry += "\tREASON #[J]: [fail_reasons[J]]"
- log_test(log_entry.Join("\n"))
-
- CHECK_TICK
+ var/file_name = "data/unit_tests.json"
+ fdel(file_name)
+ file(file_name) << json_encode(test_results)
SSticker.force_ending = TRUE
+ sleep(5 SECONDS)
+ //We have to call this manually because del_text can preceed us, and SSticker doesn't fire in the post game
+ SSticker.standard_reboot()
+
+/datum/map_template/unit_tests
+ name = "Unit Tests Zone"
+ mappath = "_maps/templates/unit_tests.dmm"
diff --git a/config/config.txt b/config/config.txt
index 9ca016fd8a2..3f6cc551854 100644
--- a/config/config.txt
+++ b/config/config.txt
@@ -21,6 +21,9 @@ $include entries/hub.txt
# Which explicitly disables LOG_TWITTER, as well as locking it.
# There are various options which are hard-locked for security reasons.
+## Uncomment to set the number of /world/Reboot()s before the DreamDaemon restarts itself. 0 means restart every round. Requires tgstation server tools.
+#ROUNDS_UNTIL_HARD_RESTART 5
+
## Uncomment to enable minimap generation
MINIMAPS_ENABLED
diff --git a/tools/build/build.js b/tools/build/build.js
index 2429752e0f6..c2cde566d85 100755
--- a/tools/build/build.js
+++ b/tools/build/build.js
@@ -44,9 +44,10 @@ export const WarningParameter = new Juke.Parameter({
export const DmMapsIncludeTarget = new Juke.Target({
executes: async () => {
const folders = [
- ...Juke.glob('_maps/RandomRuins/**/*.dmm'),
- ...Juke.glob('_maps/RandomZLevels/**/*.dmm'),
- ...Juke.glob('_maps/shuttles/**/*.dmm'),
+ // Everything but primary station map files.
+ ...Juke.glob('_maps/away_missions/**/*.dmm'),
+ // ...Juke.glob('_maps/map_levels/**/*.dmm'),
+ // ...Juke.glob('_maps/submaps/**/*.dmm'),
...Juke.glob('_maps/templates/**/*.dmm'),
];
const content = folders
@@ -65,7 +66,6 @@ export const DmTarget = new Juke.Target({
inputs: [
'_maps/map_files/generic/**',
'code/**',
- 'goon/**',
'html/**',
'icons/**',
'interface/**',