diff --git a/code/__DEFINES/planes.dm b/code/__DEFINES/planes.dm new file mode 100644 index 0000000000..31165e0258 --- /dev/null +++ b/code/__DEFINES/planes.dm @@ -0,0 +1,18 @@ +//List of all preclaimed planes + //Generally 'arbitrary' planes should be given a constant number + //Planes that are dependent upon another plane value should be defined with that plane + #define PLANE_SPACE_BACKGROUND -10 + #define PLANE_SPACE_PARALLAX (PLANE_SPACE_BACKGROUND + 1) + #define PLANE_SPACE_DUST (PLANE_SPACE_PARALLAX + 1) + + #define PLANE_TURF -6 + #define PLANE_NOIR_BLOOD -5 + #define PLANE_OBJ -4 + #define PLANE_MOB -3 + #define PLANE_EFFECTS -2 + #define PLANE_LIGHTING -1 + + #define PLANE_BASE 0 + + #define PLANE_STATIC 1 + #define PLANE_HUD 2 diff --git a/code/__HELPERS/unsorted.dm b/code/__HELPERS/unsorted.dm index 19ff525dc8..ce5fc3f223 100644 --- a/code/__HELPERS/unsorted.dm +++ b/code/__HELPERS/unsorted.dm @@ -1437,4 +1437,12 @@ proc/pick_closest_path(value) var/str = "[val]" while(length(str) < 5) str = "0" + str - . = str \ No newline at end of file + . = str + +/proc/trange(var/Dist = 0, var/turf/Center = null) + if (isnull(Center)) + return + + var/turf/x1y1 = locate(((Center.x - Dist) < 1 ? 1 : Center.x - Dist), ((Center.y - Dist) < 1 ? 1 : Center.y - Dist), Center.z) + var/turf/x2y2 = locate(((Center.x + Dist) > world.maxx ? world.maxx : Center.x + Dist), ((Center.y + Dist) > world.maxy ? world.maxy : Center.y + Dist), Center.z) + return block(x1y1, x2y2) diff --git a/code/_globalvars/misc.dm b/code/_globalvars/misc.dm index baa881732a..7070009029 100644 --- a/code/_globalvars/misc.dm +++ b/code/_globalvars/misc.dm @@ -19,4 +19,9 @@ var/map_ready = 0 Therefore, we'd need to use spawn() inside New() to wait for the surrounding turf contents to be instanced However, using lots of spawn() has a severe performance impact, and often results in spaghetti-code map_ready will be set to 1 when world/New() is called (which happens just after the map is instanced) -*/ \ No newline at end of file +*/ + +//SPACE PARALLAX +var/parallax_initialized = 0 +var/space_color = "#050505" +var/list/parallax_icon[27] diff --git a/code/_onclick/hud/hud.dm b/code/_onclick/hud/hud.dm index 68dc004363..591e47f83d 100644 --- a/code/_onclick/hud/hud.dm +++ b/code/_onclick/hud/hud.dm @@ -179,6 +179,7 @@ mymob.update_action_buttons(1) reorganize_alerts() mymob.reload_fullscreen() + update_parallax_existence() /datum/hud/human/show_hud(version = 0) @@ -205,4 +206,3 @@ usr << "Switched HUD mode. Press F12 to toggle." else usr << "This mob type does not use a HUD." - diff --git a/code/_onclick/hud/parallax.dm b/code/_onclick/hud/parallax.dm new file mode 100644 index 0000000000..7f0e283dde --- /dev/null +++ b/code/_onclick/hud/parallax.dm @@ -0,0 +1,254 @@ +/* + * This file handles all parallax-related business once the parallax itself is initialized with the rest of the HUD + */ +#define PARALLAX_IMAGE_WIDTH 15 +#define PARALLAX_IMAGE_TILES (PARALLAX_IMAGE_WIDTH**2) + +var/list/parallax_on_clients = list() + +/obj/screen/parallax + var/base_offset_x = 0 + var/base_offset_y = 0 + mouse_opacity = 0 + icon = 'icons/turf/space.dmi' + icon_state = "blank" + name = "space parallax" + screen_loc = "CENTER,CENTER" + blend_mode = BLEND_ADD + layer = AREA_LAYER + plane = PLANE_SPACE_PARALLAX + var/parallax_speed = 0 + +/obj/screen/plane_master + appearance_flags = PLANE_MASTER + screen_loc = "CENTER,CENTER" + +/obj/screen/plane_master/parallax_master + plane = PLANE_SPACE_PARALLAX + blend_mode = BLEND_MULTIPLY + color = list( + 1,0,0,0, + 0,1,0,0, + 0,0,1,0, + 0,0,0,0, + 0,0,0,1) + +/obj/screen/plane_master/parallax_spacemaster //Turns space white, causing the parallax to only show in areas with opacity. Somehow + plane = PLANE_SPACE_BACKGROUND + color = list( + 0,0,0,0, + 0,0,0,0, + 0,0,0,0, + 1,1,1,1, + 0,0,0,0) + +/obj/screen/plane_master/parallax_spacemaster/New() + ..() + overlays += image(icon = 'icons/mob/screen1.dmi', icon_state = "blank") + +/obj/screen/plane_master/parallax_dustmaster + plane = PLANE_SPACE_DUST + color = list(0,0,0,0) + +/datum/hud/proc/update_parallax_existence() + if(!parallax_initialized) + return + initialize_parallax() + update_parallax() + update_parallax_values() + +/datum/hud/proc/initialize_parallax() + var/client/C = mymob.client + + if(!C.parallax_master) + C.parallax_master = PoolOrNew(/obj/screen/plane_master/parallax_master) + if(!C.parallax_spacemaster) + C.parallax_spacemaster = PoolOrNew(/obj/screen/plane_master/parallax_spacemaster) + if(!C.parallax_dustmaster) + C.parallax_dustmaster = PoolOrNew(/obj/screen/plane_master/parallax_dustmaster) + + if(!C.parallax.len) + for(var/obj/screen/parallax/bgobj in parallax_icon) + var/obj/screen/parallax/parallax_layer = PoolOrNew(/obj/screen/parallax) + parallax_layer.appearance = bgobj.appearance + parallax_layer.base_offset_x = bgobj.base_offset_x + parallax_layer.base_offset_y = bgobj.base_offset_y + parallax_layer.parallax_speed = bgobj.parallax_speed + parallax_layer.screen_loc = bgobj.screen_loc + C.parallax += parallax_layer + if(bgobj.parallax_speed) + C.parallax_movable += parallax_layer + + if(!C.parallax_offset.len) + C.parallax_offset["horizontal"] = 0 + C.parallax_offset["vertical"] = 0 + + C.screen |= C.parallax_dustmaster + +/datum/hud/proc/update_parallax() + var/client/C = mymob.client + if(C.prefs.space_parallax) + parallax_on_clients |= C + for(var/obj/screen/parallax/bgobj in C.parallax) + C.screen |= bgobj + C.screen |= C.parallax_master + C.screen |= C.parallax_spacemaster + if(C.prefs.space_dust) + C.parallax_dustmaster.color = list( + 1,0,0,0, + 0,1,0,0, + 0,0,1,0, + 0,0,0,1) + else + C.parallax_dustmaster.color = list(0,0,0,0) + else + for(var/obj/screen/parallax/bgobj in C.parallax) + C.screen -= bgobj + parallax_on_clients -= C + C.screen -= C.parallax_master + C.screen -= C.parallax_spacemaster + C.parallax_dustmaster.color = list(0,0,0,0) + +/datum/hud/proc/update_parallax_values() + var/client/C = mymob.client + if(!parallax_initialized) + return + + if(!(locate(/turf/open/space) in trange(C.view,get_turf(C.eye)))) + return + + //ACTUALLY MOVING THE PARALLAX + var/turf/posobj = get_turf(C.eye) + + if(!C.previous_turf || (C.previous_turf.z != posobj.z)) + C.previous_turf = posobj + + //Doing it this way prevents parallax layers from "jumping" when you change Z-Levels. + var/offsetx = C.parallax_offset["horizontal"] + posobj.x - C.previous_turf.x + var/offsety = C.parallax_offset["vertical"] + posobj.y - C.previous_turf.y + C.parallax_offset["horizontal"] = offsetx + C.parallax_offset["vertical"] = offsety + + C.previous_turf = posobj + + var/maxoffset = 480 //480 = (15 tiles * 32 icon_size * 3 grid size / 2) - (15 tiles * 32 icon size / 2) for centering + var/minoffset = -960 //960 = (15 tiles * 32 icon_size * 3 grid size / 2) + (15 tiles * 32 icon size / 2) for centering + + for(var/obj/screen/parallax/bgobj in C.parallax_movable) + var/accumulated_offset_x = bgobj.base_offset_x - round(offsetx * bgobj.parallax_speed * C.prefs.parallax_speed) + var/accumulated_offset_y = bgobj.base_offset_y - round(offsety * bgobj.parallax_speed * C.prefs.parallax_speed) + + if(accumulated_offset_x > maxoffset) + accumulated_offset_x -= 1440 //3x3 grid, 15 tiles * 32 icon_size * 3 grid size + if(accumulated_offset_x < minoffset) + accumulated_offset_x += 1440 + + if(accumulated_offset_y > maxoffset) + accumulated_offset_y -= 1440 + if(accumulated_offset_y < minoffset) + accumulated_offset_y += 1440 + + bgobj.screen_loc = "CENTER:[accumulated_offset_x],CENTER:[accumulated_offset_y]" + +//Parallax generation code below + +#define PARALLAX4_ICON_NUMBER 20 +#define PARALLAX3_ICON_NUMBER 14 +#define PARALLAX2_ICON_NUMBER 10 + +/datum/subsystem/parallax/proc/create_global_parallax_icons() + var/list/plane1 = list() + var/list/plane2 = list() + var/list/plane3 = list() + var/list/pixel_x = list() + var/list/pixel_y = list() + var/index = 1 + for(var/i = 0 to (PARALLAX_IMAGE_TILES-1)) + for(var/j = 1 to 9) + plane1 += rand(1,26) + plane2 += rand(1,26) + plane3 += rand(1,26) + pixel_x += world.icon_size * (i%PARALLAX_IMAGE_WIDTH) + pixel_y += world.icon_size * round(i/PARALLAX_IMAGE_WIDTH) + + for(var/i in 0 to 8) + var/obj/screen/parallax/parallax_layer = PoolOrNew(/obj/screen/parallax) + + var/list/L = list() + for(var/j in 1 to PARALLAX_IMAGE_TILES) + if(plane1[j+i*PARALLAX_IMAGE_TILES] <= PARALLAX4_ICON_NUMBER) + var/image/I = image('icons/turf/space_parallax4.dmi',"[plane1[j+i*PARALLAX_IMAGE_TILES]]") + I.pixel_x = pixel_x[j] + I.pixel_y = pixel_y[j] + L += I + + parallax_layer.overlays = L + parallax_layer.parallax_speed = 0 + parallax_layer.calibrate_parallax(i+1) + parallax_icon[index] = parallax_layer + index++ + + for(var/i in 0 to 8) + var/obj/screen/parallax/parallax_layer = PoolOrNew(/obj/screen/parallax) + + var/list/L = list() + for(var/j in 1 to PARALLAX_IMAGE_TILES) + if(plane2[j+i*PARALLAX_IMAGE_TILES] <= PARALLAX3_ICON_NUMBER) + var/image/I = image('icons/turf/space_parallax3.dmi',"[plane2[j+i*PARALLAX_IMAGE_TILES]]") + I.pixel_x = pixel_x[j] + I.pixel_y = pixel_y[j] + L += I + + parallax_layer.overlays = L + parallax_layer.parallax_speed = 0.5 + parallax_layer.calibrate_parallax(i+1) + parallax_icon[index] = parallax_layer + index++ + + for(var/i in 0 to 8) + var/obj/screen/parallax/parallax_layer = PoolOrNew(/obj/screen/parallax) + var/list/L = list() + for(var/j in 1 to PARALLAX_IMAGE_TILES) + if(plane3[j+i*PARALLAX_IMAGE_TILES] <= PARALLAX2_ICON_NUMBER) + var/image/I = image('icons/turf/space_parallax2.dmi',"[plane3[j+i*PARALLAX_IMAGE_TILES]]") + I.pixel_x = pixel_x[j] + I.pixel_y = pixel_y[j] + L += I + + parallax_layer.overlays = L + parallax_layer.parallax_speed = 1 + parallax_layer.calibrate_parallax(i+1) + parallax_icon[index] = parallax_layer + index++ + + parallax_initialized = 1 + +/obj/screen/parallax/proc/calibrate_parallax(var/i) + if(!i) return + + /* Placement of screen objects + 1 2 3 + 4 5 6 + 7 8 9 + */ + base_offset_x = -PARALLAX_IMAGE_WIDTH*world.icon_size/2 + base_offset_y = -PARALLAX_IMAGE_WIDTH*world.icon_size/2 + + switch(i) + if(1,4,7) + base_offset_x -= world.icon_size*PARALLAX_IMAGE_WIDTH + if(3,6,9) + base_offset_x += world.icon_size*PARALLAX_IMAGE_WIDTH + switch(i) + if(1,2,3) + base_offset_y += world.icon_size*PARALLAX_IMAGE_WIDTH + if(7,8,9) + base_offset_y -= world.icon_size*PARALLAX_IMAGE_WIDTH + + screen_loc = "CENTER:[base_offset_x],CENTER:[base_offset_y]" + +#undef PARALLAX4_ICON_NUMBER +#undef PARALLAX3_ICON_NUMBER +#undef PARALLAX2_ICON_NUMBER +#undef PARALLAX_IMAGE_WIDTH +#undef PARALLAX_IMAGE_TILES diff --git a/code/controllers/subsystem/parallax.dm b/code/controllers/subsystem/parallax.dm new file mode 100644 index 0000000000..5f8f373212 --- /dev/null +++ b/code/controllers/subsystem/parallax.dm @@ -0,0 +1,12 @@ +var/datum/subsystem/parallax/SSparallax + +/datum/subsystem/parallax + name = "Space Parallax" + init_order = 18 + flags = SS_NO_FIRE + +/datum/subsystem/parallax/New() + NEW_SS_GLOBAL(SSparallax) + +/datum/subsystem/parallax/Initialize() + create_global_parallax_icons() diff --git a/code/game/atoms_movable.dm b/code/game/atoms_movable.dm index 4a5dd97ac3..1db488d119 100644 --- a/code/game/atoms_movable.dm +++ b/code/game/atoms_movable.dm @@ -69,6 +69,8 @@ last_move = 0 return + update_client_hook(loc) + if(.) Moved(oldloc, direct) @@ -132,6 +134,9 @@ continue AM.Crossed(src) Moved(oldloc, 0) + + update_client_hook(destination) + return 1 return 0 @@ -152,13 +157,26 @@ else //something went very wrong. CRASH("Brainmob without container.") - /mob/living/silicon/pai/forceMove(atom/destination) if(card) card.forceMove(destination) else //something went very wrong. CRASH("pAI without card") +/atom/movable/proc/update_client_hook(atom/destination) + if(locate(/mob) in src) + for(var/client/C in parallax_on_clients) + if((get_turf(C.eye) == destination) && (C.mob.hud_used)) + C.mob.hud_used.update_parallax_values() + +/mob/update_client_hook(atom/destination) + if(locate(/mob) in src) + for(var/client/C in parallax_on_clients) + if((get_turf(C.eye) == destination) && (C.mob.hud_used)) + C.mob.hud_used.update_parallax_values() + else if(client && hud_used) + hud_used.update_parallax_values() + //Called whenever an object moves and by mobs when they attempt to move themselves through space //And when an object or action applies a force on src, see newtonian_move() below diff --git a/code/game/turfs/space/space.dm b/code/game/turfs/space/space.dm index 22f510ca1d..f1d2b9db03 100644 --- a/code/game/turfs/space/space.dm +++ b/code/game/turfs/space/space.dm @@ -5,6 +5,8 @@ desc = "A vast, cold, and lonely place." intact = 0 + plane = PLANE_SPACE_BACKGROUND + temperature = TCMB thermal_conductivity = OPEN_HEAT_TRANSFER_COEFFICIENT heat_capacity = 700000 @@ -21,6 +23,11 @@ /turf/open/space/New() update_icon() air = space_gas + var/image/I = image('icons/turf/space_parallax1.dmi',"[icon_state]") + I.plane = PLANE_SPACE_DUST + I.alpha = 80 + I.blend_mode = BLEND_ADD + overlays += I /turf/open/space/Destroy(force) if(force) diff --git a/code/game/turfs/space/transit.dm b/code/game/turfs/space/transit.dm index 13e75be663..85d3fc6357 100644 --- a/code/game/turfs/space/transit.dm +++ b/code/game/turfs/space/transit.dm @@ -2,6 +2,7 @@ icon_state = "black" dir = SOUTH baseturf = /turf/open/space/transit + plane = PLANE_TURF /turf/open/space/transit/horizontal dir = WEST diff --git a/code/modules/client/client_defines.dm b/code/modules/client/client_defines.dm index 5aff1278e1..f5283695ff 100644 --- a/code/modules/client/client_defines.dm +++ b/code/modules/client/client_defines.dm @@ -51,4 +51,15 @@ var/datum/tooltip/tooltips //Used for var edit flagging, also defined in datums (clients are not a child of datums for some reason) - var/var_edited = 0 \ No newline at end of file + var/var_edited = 0 + + //////////// + //PARALLAX// + //////////// + var/list/parallax = list() + var/list/parallax_movable = list() + var/list/parallax_offset = list() + var/turf/previous_turf = null + var/obj/screen/plane_master/parallax_master/parallax_master = null + var/obj/screen/plane_master/parallax_dustmaster/parallax_dustmaster = null + var/obj/screen/plane_master/parallax_spacemaster/parallax_spacemaster = null diff --git a/code/modules/client/preferences.dm b/code/modules/client/preferences.dm index 7a22e62d31..31b761de66 100644 --- a/code/modules/client/preferences.dm +++ b/code/modules/client/preferences.dm @@ -117,6 +117,11 @@ var/list/preferences_datums = list() var/list/ignoring = list() + //Parallax prefs + var/space_parallax = 1 + var/space_dust = 1 + var/parallax_speed = 2 + /datum/preferences/New(client/C) custom_names["ai"] = pick(ai_names) custom_names["cyborg"] = pick(ai_names) @@ -448,6 +453,10 @@ var/list/preferences_datums = list() dat += "Ghost pda: [(chat_toggles & CHAT_GHOSTPDA) ? "All Messages" : "Nearest Creatures"]
" dat += "Pull requests: [(chat_toggles & CHAT_PULLR) ? "Yes" : "No"]
" dat += "Midround Antagonist: [(toggles & MIDROUND_ANTAG) ? "Yes" : "No"]
" + dat += "Space Parallax: [space_parallax ? "Enabled" : "Disabled"]
" + if(space_parallax) + dat += "Parallax Speed: [parallax_speed]
" + dat += "Space Dust: [space_dust ? "Yes" : "No"]
" if(config.allow_Metadata) dat += "OOC Notes: Edit
" @@ -1275,6 +1284,15 @@ var/list/preferences_datums = list() else be_special += be_special_type + if("parallax") + space_parallax = !space_parallax + + if("dust") + space_dust = !space_dust + + if("p_speed") + parallax_speed = min(max(input(user, "Enter a number between 0 and 5 included (default=2)","Parallax Speed Preferences",parallax_speed),0),5) + if("name") be_random_name = !be_random_name @@ -1399,4 +1417,4 @@ var/list/preferences_datums = list() if(icon_updates) character.update_body() character.update_hair() - character.update_body_parts() \ No newline at end of file + character.update_body_parts() diff --git a/code/modules/client/preferences_savefile.dm b/code/modules/client/preferences_savefile.dm index d79b57ff93..7288c42f23 100644 --- a/code/modules/client/preferences_savefile.dm +++ b/code/modules/client/preferences_savefile.dm @@ -237,6 +237,9 @@ SAVEFILE UPDATING/VERSIONING - 'Simplified', or rather, more coder-friendly ~Car S["hotkeys"] >> hotkeys S["tgui_fancy"] >> tgui_fancy S["tgui_lock"] >> tgui_lock + S["space_parallax"] >> space_parallax + S["space_dust"] >> space_dust + S["parallax_speed"] >> parallax_speed if(islist(S["be_special"])) S["be_special"] >> be_special @@ -270,6 +273,9 @@ SAVEFILE UPDATING/VERSIONING - 'Simplified', or rather, more coder-friendly ~Car tgui_lock = sanitize_integer(tgui_lock, 0, 1, initial(tgui_lock)) default_slot = sanitize_integer(default_slot, 1, max_save_slots, initial(default_slot)) toggles = sanitize_integer(toggles, 0, 65535, initial(toggles)) + space_parallax = sanitize_integer(space_parallax, 0, 1, initial(space_parallax)) + space_dust = sanitize_integer(space_dust, 0, 1, initial(space_dust)) + parallax_speed = sanitize_integer(parallax_speed, 0, 5, initial(parallax_speed)) ghost_form = sanitize_inlist(ghost_form, ghost_forms, initial(ghost_form)) ghost_orbit = sanitize_inlist(ghost_orbit, ghost_orbits, initial(ghost_orbit)) ghost_accs = sanitize_inlist(ghost_accs, ghost_accs_options, GHOST_ACCS_DEFAULT_OPTION) @@ -306,6 +312,9 @@ SAVEFILE UPDATING/VERSIONING - 'Simplified', or rather, more coder-friendly ~Car S["ignoring"] << ignoring S["ghost_hud"] << ghost_hud S["inquisitive_ghost"] << inquisitive_ghost + S["space_parallax"] << space_parallax + S["space_dust"] << space_dust + S["parallax_speed"] << parallax_speed return 1 @@ -556,4 +565,4 @@ SAVEFILE UPDATING/VERSIONING - 'Simplified', or rather, more coder-friendly ~Car /client/verb/savefile_import(path as text) var/savefile/S = new /savefile(path) S.ImportText("/",file("[path].txt")) -*/ \ No newline at end of file +*/ diff --git a/icons/mob/screen1.dmi b/icons/mob/screen1.dmi new file mode 100644 index 0000000000..51e5df832b Binary files /dev/null and b/icons/mob/screen1.dmi differ diff --git a/icons/turf/screen1_parallax.dmi b/icons/turf/screen1_parallax.dmi new file mode 100644 index 0000000000..cd0f232bbc Binary files /dev/null and b/icons/turf/screen1_parallax.dmi differ diff --git a/icons/turf/shuttle.dmi b/icons/turf/shuttle.dmi index 1a7fa3c9bd..a49c19226f 100644 Binary files a/icons/turf/shuttle.dmi and b/icons/turf/shuttle.dmi differ diff --git a/icons/turf/space.dmi b/icons/turf/space.dmi index d32279e704..dc6a28d285 100644 Binary files a/icons/turf/space.dmi and b/icons/turf/space.dmi differ diff --git a/icons/turf/space_parallax1.dmi b/icons/turf/space_parallax1.dmi new file mode 100644 index 0000000000..a538bf0f0d Binary files /dev/null and b/icons/turf/space_parallax1.dmi differ diff --git a/icons/turf/space_parallax2.dmi b/icons/turf/space_parallax2.dmi new file mode 100644 index 0000000000..e93cc831d4 Binary files /dev/null and b/icons/turf/space_parallax2.dmi differ diff --git a/icons/turf/space_parallax3.dmi b/icons/turf/space_parallax3.dmi new file mode 100644 index 0000000000..2261ab17f7 Binary files /dev/null and b/icons/turf/space_parallax3.dmi differ diff --git a/icons/turf/space_parallax4.dmi b/icons/turf/space_parallax4.dmi new file mode 100644 index 0000000000..560bacd009 Binary files /dev/null and b/icons/turf/space_parallax4.dmi differ diff --git a/tgstation.dme b/tgstation.dme index 9f0a602fe0..8b2b7e3725 100644 --- a/tgstation.dme +++ b/tgstation.dme @@ -40,6 +40,7 @@ #include "code\__DEFINES\math.dm" #include "code\__DEFINES\misc.dm" #include "code\__DEFINES\pipe_construction.dm" +#include "code\__DEFINES\planes.dm" #include "code\__DEFINES\preferences.dm" #include "code\__DEFINES\qdel.dm" #include "code\__DEFINES\radio.dm" @@ -125,6 +126,7 @@ #include "code\_onclick\hud\monkey.dm" #include "code\_onclick\hud\movable_screen_objects.dm" #include "code\_onclick\hud\other_mobs.dm" +#include "code\_onclick\hud\parallax.dm" #include "code\_onclick\hud\revenanthud.dm" #include "code\_onclick\hud\robot.dm" #include "code\_onclick\hud\screen_objects.dm" @@ -168,6 +170,7 @@ #include "code\controllers\subsystem\npcpool.dm" #include "code\controllers\subsystem\objects.dm" #include "code\controllers\subsystem\pai.dm" +#include "code\controllers\subsystem\parallax.dm" #include "code\controllers\subsystem\radio.dm" #include "code\controllers\subsystem\server_maintenance.dm" #include "code\controllers\subsystem\shuttles.dm"