From 1bc28c07c09828f70c986fe67e658a065367efdf Mon Sep 17 00:00:00 2001 From: Neerti Date: Fri, 8 Sep 2017 08:14:43 -0400 Subject: [PATCH] Port's TG's Maploader --- .../binary_devices/binary_atmos_base.dm | 222 +++++---- .../components/omni_devices/omni_base.dm | 1 + .../components/portables_connector.dm | 10 +- .../trinary_devices/trinary_base.dm | 280 ++++++----- code/ATMOSPHERICS/components/tvalve.dm | 11 +- .../components/unary/unary_base.dm | 143 +++--- code/ATMOSPHERICS/components/valve.dm | 6 +- code/ATMOSPHERICS/he_pipes.dm | 205 ++++---- code/ATMOSPHERICS/pipes.dm | 68 ++- code/__defines/misc.dm | 8 + code/_helpers/lists.dm | 10 + code/_helpers/text.dm | 57 +++ code/controllers/master.dm | 12 +- code/controllers/subsystems/creation.dm | 29 ++ code/game/atoms_movable.dm | 5 +- code/game/machinery/alarm.dm | 2 +- code/game/machinery/status_display.dm | 8 + code/game/objects/effects/landmarks.dm | 8 +- code/modules/admin/admin_verbs.dm | 5 +- .../admin/verbs/map_template_loadverb.dm | 69 +++ code/modules/awaymissions/zlevel.dm | 3 +- code/modules/maps/dmm_suite.dm | 2 +- code/modules/maps/tg/dmm_suite.dm | 63 +++ code/modules/maps/tg/map_template.dm | 165 +++++++ code/modules/maps/tg/reader.dm | 466 ++++++++++++++++++ code/world.dm | 3 + icons/turf/floors.dmi | Bin 50521 -> 50556 bytes maps/submaps/_readme.dm | 27 + maps/submaps/cave_submaps/cave.dm | 5 + maps/submaps/space_submaps/space.dm | 5 + maps/submaps/surface_submaps/forest.dm | 5 + polaris.dme | 13 +- 32 files changed, 1472 insertions(+), 444 deletions(-) create mode 100644 code/controllers/subsystems/creation.dm create mode 100644 code/modules/admin/verbs/map_template_loadverb.dm create mode 100644 code/modules/maps/tg/dmm_suite.dm create mode 100644 code/modules/maps/tg/map_template.dm create mode 100644 code/modules/maps/tg/reader.dm create mode 100644 maps/submaps/_readme.dm create mode 100644 maps/submaps/cave_submaps/cave.dm create mode 100644 maps/submaps/space_submaps/space.dm create mode 100644 maps/submaps/surface_submaps/forest.dm diff --git a/code/ATMOSPHERICS/components/binary_devices/binary_atmos_base.dm b/code/ATMOSPHERICS/components/binary_devices/binary_atmos_base.dm index ea604bc996..4bbbf02b16 100644 --- a/code/ATMOSPHERICS/components/binary_devices/binary_atmos_base.dm +++ b/code/ATMOSPHERICS/components/binary_devices/binary_atmos_base.dm @@ -1,4 +1,4 @@ -obj/machinery/atmospherics/binary +/obj/machinery/atmospherics/binary dir = SOUTH initialize_directions = SOUTH|NORTH use_power = 1 @@ -9,123 +9,131 @@ obj/machinery/atmospherics/binary var/datum/pipe_network/network1 var/datum/pipe_network/network2 - New() - ..() - switch(dir) - if(NORTH) - initialize_directions = NORTH|SOUTH - if(SOUTH) - initialize_directions = NORTH|SOUTH - if(EAST) - initialize_directions = EAST|WEST - if(WEST) - initialize_directions = EAST|WEST - air1 = new - air2 = new +/obj/machinery/atmospherics/binary/New() + ..() - air1.volume = 200 - air2.volume = 200 + air1 = new + air2 = new + + air1.volume = 200 + air2.volume = 200 + +/obj/machinery/atmospherics/binary/init_dir() + switch(dir) + if(NORTH) + initialize_directions = NORTH|SOUTH + if(SOUTH) + initialize_directions = NORTH|SOUTH + if(EAST) + initialize_directions = EAST|WEST + if(WEST) + initialize_directions = EAST|WEST // Housekeeping and pipe network stuff below - network_expand(datum/pipe_network/new_network, obj/machinery/atmospherics/pipe/reference) - if(reference == node1) - network1 = new_network +/obj/machinery/atmospherics/binary/network_expand(datum/pipe_network/new_network, obj/machinery/atmospherics/pipe/reference) + if(reference == node1) + network1 = new_network - else if(reference == node2) - network2 = new_network + else if(reference == node2) + network2 = new_network - if(new_network.normal_members.Find(src)) - return 0 + if(new_network.normal_members.Find(src)) + return 0 - new_network.normal_members += src + new_network.normal_members += src - return null + return null - Destroy() - . = ..() +/obj/machinery/atmospherics/binary/Destroy() + . = ..() - if(node1) - node1.disconnect(src) - qdel(network1) - if(node2) - node2.disconnect(src) - qdel(network2) + if(node1) + node1.disconnect(src) + qdel(network1) + if(node2) + node2.disconnect(src) + qdel(network2) + node1 = null + node2 = null + +/obj/machinery/atmospherics/binary/initialize() + if(node1 && node2) + return + + init_dir() + + var/node2_connect = dir + var/node1_connect = turn(dir, 180) + + for(var/obj/machinery/atmospherics/target in get_step(src,node1_connect)) + target.init_dir() + if(target.initialize_directions & get_dir(target,src)) + if (check_connect_types(target,src)) + node1 = target + break + + for(var/obj/machinery/atmospherics/target in get_step(src,node2_connect)) + target.init_dir() + if(target.initialize_directions & get_dir(target,src)) + if (check_connect_types(target,src)) + node2 = target + break + + update_icon() + update_underlays() + +/obj/machinery/atmospherics/binary/build_network() + if(!network1 && node1) + network1 = new /datum/pipe_network() + network1.normal_members += src + network1.build_network(node1, src) + + if(!network2 && node2) + network2 = new /datum/pipe_network() + network2.normal_members += src + network2.build_network(node2, src) + + +/obj/machinery/atmospherics/binary/return_network(obj/machinery/atmospherics/reference) + build_network() + + if(reference==node1) + return network1 + + if(reference==node2) + return network2 + + return null + +/obj/machinery/atmospherics/binary/reassign_network(datum/pipe_network/old_network, datum/pipe_network/new_network) + if(network1 == old_network) + network1 = new_network + if(network2 == old_network) + network2 = new_network + + return 1 + +/obj/machinery/atmospherics/binary/return_network_air(datum/pipe_network/reference) + var/list/results = list() + + if(network1 == reference) + results += air1 + if(network2 == reference) + results += air2 + + return results + +/obj/machinery/atmospherics/binary/disconnect(obj/machinery/atmospherics/reference) + if(reference==node1) + qdel(network1) node1 = null + + else if(reference==node2) + qdel(network2) node2 = null - initialize() - if(node1 && node2) return + update_icon() + update_underlays() - var/node2_connect = dir - var/node1_connect = turn(dir, 180) - - for(var/obj/machinery/atmospherics/target in get_step(src,node1_connect)) - if(target.initialize_directions & get_dir(target,src)) - if (check_connect_types(target,src)) - node1 = target - break - - for(var/obj/machinery/atmospherics/target in get_step(src,node2_connect)) - if(target.initialize_directions & get_dir(target,src)) - if (check_connect_types(target,src)) - node2 = target - break - - update_icon() - update_underlays() - - build_network() - if(!network1 && node1) - network1 = new /datum/pipe_network() - network1.normal_members += src - network1.build_network(node1, src) - - if(!network2 && node2) - network2 = new /datum/pipe_network() - network2.normal_members += src - network2.build_network(node2, src) - - - return_network(obj/machinery/atmospherics/reference) - build_network() - - if(reference==node1) - return network1 - - if(reference==node2) - return network2 - - return null - - reassign_network(datum/pipe_network/old_network, datum/pipe_network/new_network) - if(network1 == old_network) - network1 = new_network - if(network2 == old_network) - network2 = new_network - - return 1 - - return_network_air(datum/pipe_network/reference) - var/list/results = list() - - if(network1 == reference) - results += air1 - if(network2 == reference) - results += air2 - - return results - - disconnect(obj/machinery/atmospherics/reference) - if(reference==node1) - qdel(network1) - node1 = null - - else if(reference==node2) - qdel(network2) - node2 = null - - update_icon() - update_underlays() - - return null \ No newline at end of file + return null \ No newline at end of file diff --git a/code/ATMOSPHERICS/components/omni_devices/omni_base.dm b/code/ATMOSPHERICS/components/omni_devices/omni_base.dm index 6690c7c257..8b174b2845 100644 --- a/code/ATMOSPHERICS/components/omni_devices/omni_base.dm +++ b/code/ATMOSPHERICS/components/omni_devices/omni_base.dm @@ -247,6 +247,7 @@ if(P.node || P.mode == 0) continue for(var/obj/machinery/atmospherics/target in get_step(src, P.dir)) + target.init_dir() if(target.initialize_directions & get_dir(target,src)) if (check_connect_types(target,src)) P.node = target diff --git a/code/ATMOSPHERICS/components/portables_connector.dm b/code/ATMOSPHERICS/components/portables_connector.dm index c39ca8469b..69fc0561c9 100644 --- a/code/ATMOSPHERICS/components/portables_connector.dm +++ b/code/ATMOSPHERICS/components/portables_connector.dm @@ -18,9 +18,11 @@ use_power = 0 level = 1 +/obj/machinery/atmospherics/portables_connector/init_dir() + initialize_directions = dir /obj/machinery/atmospherics/portables_connector/New() - initialize_directions = dir + init_dir() ..() /obj/machinery/atmospherics/portables_connector/update_icon() @@ -73,11 +75,15 @@ node = null /obj/machinery/atmospherics/portables_connector/initialize() - if(node) return + if(node) + return + + init_dir() var/node_connect = dir for(var/obj/machinery/atmospherics/target in get_step(src,node_connect)) + target.init_dir() if(target.initialize_directions & get_dir(target,src)) if (check_connect_types(target,src)) node = target diff --git a/code/ATMOSPHERICS/components/trinary_devices/trinary_base.dm b/code/ATMOSPHERICS/components/trinary_devices/trinary_base.dm index 717540db69..34e6d480a8 100644 --- a/code/ATMOSPHERICS/components/trinary_devices/trinary_base.dm +++ b/code/ATMOSPHERICS/components/trinary_devices/trinary_base.dm @@ -1,4 +1,4 @@ -obj/machinery/atmospherics/trinary +/obj/machinery/atmospherics/trinary dir = SOUTH initialize_directions = SOUTH|NORTH|WEST use_power = 0 @@ -13,153 +13,163 @@ obj/machinery/atmospherics/trinary var/datum/pipe_network/network2 var/datum/pipe_network/network3 - New() - ..() - switch(dir) - if(NORTH) - initialize_directions = EAST|NORTH|SOUTH - if(SOUTH) - initialize_directions = SOUTH|WEST|NORTH - if(EAST) - initialize_directions = EAST|WEST|SOUTH - if(WEST) - initialize_directions = WEST|NORTH|EAST - air1 = new - air2 = new - air3 = new +/obj/machinery/atmospherics/trinary/New() + ..() + init_dir() - air1.volume = 200 - air2.volume = 200 - air3.volume = 200 + air1 = new + air2 = new + air3 = new + + air1.volume = 200 + air2.volume = 200 + air3.volume = 200 + +/obj/machinery/atmospherics/trinary/init_dir() + switch(dir) + if(NORTH) + initialize_directions = EAST|NORTH|SOUTH + if(SOUTH) + initialize_directions = SOUTH|WEST|NORTH + if(EAST) + initialize_directions = EAST|WEST|SOUTH + if(WEST) + initialize_directions = WEST|NORTH|EAST // Housekeeping and pipe network stuff below - network_expand(datum/pipe_network/new_network, obj/machinery/atmospherics/pipe/reference) - if(reference == node1) - network1 = new_network +/obj/machinery/atmospherics/trinary/network_expand(datum/pipe_network/new_network, obj/machinery/atmospherics/pipe/reference) + if(reference == node1) + network1 = new_network - else if(reference == node2) - network2 = new_network + else if(reference == node2) + network2 = new_network - else if (reference == node3) - network3 = new_network + else if (reference == node3) + network3 = new_network - if(new_network.normal_members.Find(src)) - return 0 + if(new_network.normal_members.Find(src)) + return 0 - new_network.normal_members += src + new_network.normal_members += src - return null + return null - Destroy() - . = ..() +/obj/machinery/atmospherics/trinary/Destroy() + . = ..() - if(node1) - node1.disconnect(src) - qdel(network1) - if(node2) - node2.disconnect(src) - qdel(network2) - if(node3) - node3.disconnect(src) - qdel(network3) + if(node1) + node1.disconnect(src) + qdel(network1) + if(node2) + node2.disconnect(src) + qdel(network2) + if(node3) + node3.disconnect(src) + qdel(network3) + node1 = null + node2 = null + node3 = null + +/obj/machinery/atmospherics/trinary/initialize() + if(node1 && node2 && node3) + return + + init_dir() + + var/node1_connect = turn(dir, -180) + var/node2_connect = turn(dir, -90) + var/node3_connect = dir + + for(var/obj/machinery/atmospherics/target in get_step(src,node1_connect)) + target.init_dir() + if(target.initialize_directions & get_dir(target,src)) + if (check_connect_types(target,src)) + node1 = target + break + + for(var/obj/machinery/atmospherics/target in get_step(src,node2_connect)) + target.init_dir() + if(target.initialize_directions & get_dir(target,src)) + if (check_connect_types(target,src)) + node2 = target + break + for(var/obj/machinery/atmospherics/target in get_step(src,node3_connect)) + target.init_dir() + if(target.initialize_directions & get_dir(target,src)) + if (check_connect_types(target,src)) + node3 = target + break + + update_icon() + update_underlays() + +/obj/machinery/atmospherics/trinary/build_network() + if(!network1 && node1) + network1 = new /datum/pipe_network() + network1.normal_members += src + network1.build_network(node1, src) + + if(!network2 && node2) + network2 = new /datum/pipe_network() + network2.normal_members += src + network2.build_network(node2, src) + + if(!network3 && node3) + network3 = new /datum/pipe_network() + network3.normal_members += src + network3.build_network(node3, src) + + +/obj/machinery/atmospherics/trinary/return_network(obj/machinery/atmospherics/reference) + build_network() + + if(reference==node1) + return network1 + + if(reference==node2) + return network2 + + if(reference==node3) + return network3 + + return null + +/obj/machinery/atmospherics/trinary/reassign_network(datum/pipe_network/old_network, datum/pipe_network/new_network) + if(network1 == old_network) + network1 = new_network + if(network2 == old_network) + network2 = new_network + if(network3 == old_network) + network3 = new_network + + return 1 + +/obj/machinery/atmospherics/trinary/return_network_air(datum/pipe_network/reference) + var/list/results = list() + + if(network1 == reference) + results += air1 + if(network2 == reference) + results += air2 + if(network3 == reference) + results += air3 + + return results + +/obj/machinery/atmospherics/trinary/disconnect(obj/machinery/atmospherics/reference) + if(reference==node1) + qdel(network1) node1 = null + + else if(reference==node2) + qdel(network2) node2 = null + + else if(reference==node3) + qdel(network3) node3 = null - initialize() - if(node1 && node2 && node3) return + update_underlays() - var/node1_connect = turn(dir, -180) - var/node2_connect = turn(dir, -90) - var/node3_connect = dir - - for(var/obj/machinery/atmospherics/target in get_step(src,node1_connect)) - if(target.initialize_directions & get_dir(target,src)) - if (check_connect_types(target,src)) - node1 = target - break - - for(var/obj/machinery/atmospherics/target in get_step(src,node2_connect)) - if(target.initialize_directions & get_dir(target,src)) - if (check_connect_types(target,src)) - node2 = target - break - for(var/obj/machinery/atmospherics/target in get_step(src,node3_connect)) - if(target.initialize_directions & get_dir(target,src)) - if (check_connect_types(target,src)) - node3 = target - break - - update_icon() - update_underlays() - - build_network() - if(!network1 && node1) - network1 = new /datum/pipe_network() - network1.normal_members += src - network1.build_network(node1, src) - - if(!network2 && node2) - network2 = new /datum/pipe_network() - network2.normal_members += src - network2.build_network(node2, src) - - if(!network3 && node3) - network3 = new /datum/pipe_network() - network3.normal_members += src - network3.build_network(node3, src) - - - return_network(obj/machinery/atmospherics/reference) - build_network() - - if(reference==node1) - return network1 - - if(reference==node2) - return network2 - - if(reference==node3) - return network3 - - return null - - reassign_network(datum/pipe_network/old_network, datum/pipe_network/new_network) - if(network1 == old_network) - network1 = new_network - if(network2 == old_network) - network2 = new_network - if(network3 == old_network) - network3 = new_network - - return 1 - - return_network_air(datum/pipe_network/reference) - var/list/results = list() - - if(network1 == reference) - results += air1 - if(network2 == reference) - results += air2 - if(network3 == reference) - results += air3 - - return results - - disconnect(obj/machinery/atmospherics/reference) - if(reference==node1) - qdel(network1) - node1 = null - - else if(reference==node2) - qdel(network2) - node2 = null - - else if(reference==node3) - qdel(network3) - node3 = null - - update_underlays() - - return null \ No newline at end of file + return null \ No newline at end of file diff --git a/code/ATMOSPHERICS/components/tvalve.dm b/code/ATMOSPHERICS/components/tvalve.dm index 81a870743e..9a0a92c75c 100644 --- a/code/ATMOSPHERICS/components/tvalve.dm +++ b/code/ATMOSPHERICS/components/tvalve.dm @@ -47,10 +47,10 @@ update_underlays() /obj/machinery/atmospherics/tvalve/New() - initialize_directions() + init_dir() ..() -/obj/machinery/atmospherics/tvalve/proc/initialize_directions() +/obj/machinery/atmospherics/tvalve/init_dir() switch(dir) if(NORTH) initialize_directions = SOUTH|NORTH|EAST @@ -189,21 +189,26 @@ var/node2_dir var/node3_dir + init_dir() + node1_dir = turn(dir, 180) node2_dir = turn(dir, -90) node3_dir = dir for(var/obj/machinery/atmospherics/target in get_step(src,node1_dir)) + target.init_dir() if(target.initialize_directions & get_dir(target,src)) if (check_connect_types(target,src)) node1 = target break for(var/obj/machinery/atmospherics/target in get_step(src,node2_dir)) + target.init_dir() if(target.initialize_directions & get_dir(target,src)) if (check_connect_types(target,src)) node2 = target break for(var/obj/machinery/atmospherics/target in get_step(src,node3_dir)) + target.init_dir() if(target.initialize_directions & get_dir(target,src)) if (check_connect_types(target,src)) node3 = target @@ -371,7 +376,7 @@ icon_state = "map_tvalvem1" state = 1 -/obj/machinery/atmospherics/tvalve/mirrored/initialize_directions() +/obj/machinery/atmospherics/tvalve/mirrored/init_dir() switch(dir) if(NORTH) initialize_directions = SOUTH|NORTH|WEST diff --git a/code/ATMOSPHERICS/components/unary/unary_base.dm b/code/ATMOSPHERICS/components/unary/unary_base.dm index 0372483c79..327112d918 100644 --- a/code/ATMOSPHERICS/components/unary/unary_base.dm +++ b/code/ATMOSPHERICS/components/unary/unary_base.dm @@ -11,83 +11,90 @@ var/welded = 0 //defining this here for ventcrawl stuff - New() - ..() - initialize_directions = dir - air_contents = new +/obj/machinery/atmospherics/unary/New() + ..() + init_dir() + air_contents = new - air_contents.volume = 200 + air_contents.volume = 200 + +/obj/machinery/atmospherics/unary/init_dir() + initialize_directions = dir // Housekeeping and pipe network stuff below - network_expand(datum/pipe_network/new_network, obj/machinery/atmospherics/pipe/reference) - if(reference == node) - network = new_network +/obj/machinery/atmospherics/unary/network_expand(datum/pipe_network/new_network, obj/machinery/atmospherics/pipe/reference) + if(reference == node) + network = new_network - if(new_network.normal_members.Find(src)) - return 0 + if(new_network.normal_members.Find(src)) + return 0 - new_network.normal_members += src + new_network.normal_members += src - return null + return null - Destroy() - . = ..() +/obj/machinery/atmospherics/unary/Destroy() + . = ..() - if(node) - node.disconnect(src) - qdel(network) + if(node) + node.disconnect(src) + qdel(network) + node = null + +/obj/machinery/atmospherics/unary/initialize() + if(node) + return + + init_dir() + + var/node_connect = dir + + for(var/obj/machinery/atmospherics/target in get_step(src,node_connect)) + target.init_dir() + if(target.initialize_directions & get_dir(target,src)) + if (check_connect_types(target,src)) + node = target + break + + update_icon() + update_underlays() + +/obj/machinery/atmospherics/unary/build_network() + if(!network && node) + network = new /datum/pipe_network() + network.normal_members += src + network.build_network(node, src) + + +/obj/machinery/atmospherics/unary/return_network(obj/machinery/atmospherics/reference) + build_network() + + if(reference==node) + return network + + return null + +/obj/machinery/atmospherics/unary/reassign_network(datum/pipe_network/old_network, datum/pipe_network/new_network) + if(network == old_network) + network = new_network + + return 1 + +/obj/machinery/atmospherics/unary/return_network_air(datum/pipe_network/reference) + var/list/results = list() + + if(network == reference) + results += air_contents + + return results + +/obj/machinery/atmospherics/unary/disconnect(obj/machinery/atmospherics/reference) + if(reference==node) + qdel(network) node = null - initialize() - if(node) return + update_icon() + update_underlays() - var/node_connect = dir - - for(var/obj/machinery/atmospherics/target in get_step(src,node_connect)) - if(target.initialize_directions & get_dir(target,src)) - if (check_connect_types(target,src)) - node = target - break - - update_icon() - update_underlays() - - build_network() - if(!network && node) - network = new /datum/pipe_network() - network.normal_members += src - network.build_network(node, src) - - - return_network(obj/machinery/atmospherics/reference) - build_network() - - if(reference==node) - return network - - return null - - reassign_network(datum/pipe_network/old_network, datum/pipe_network/new_network) - if(network == old_network) - network = new_network - - return 1 - - return_network_air(datum/pipe_network/reference) - var/list/results = list() - - if(network == reference) - results += air_contents - - return results - - disconnect(obj/machinery/atmospherics/reference) - if(reference==node) - qdel(network) - node = null - - update_icon() - update_underlays() - - return null \ No newline at end of file + return null \ No newline at end of file diff --git a/code/ATMOSPHERICS/components/valve.dm b/code/ATMOSPHERICS/components/valve.dm index 7fa8d60a77..209c4730a0 100644 --- a/code/ATMOSPHERICS/components/valve.dm +++ b/code/ATMOSPHERICS/components/valve.dm @@ -38,13 +38,12 @@ /obj/machinery/atmospherics/valve/hide(var/i) update_underlays() -/obj/machinery/atmospherics/valve/New() +/obj/machinery/atmospherics/valve/init_dir() switch(dir) if(NORTH || SOUTH) initialize_directions = NORTH|SOUTH if(EAST || WEST) initialize_directions = EAST|WEST - ..() /obj/machinery/atmospherics/valve/network_expand(datum/pipe_network/new_network, obj/machinery/atmospherics/pipe/reference) if(reference == node1) @@ -142,6 +141,7 @@ return /obj/machinery/atmospherics/valve/initialize() + init_dir() normalize_dir() var/node1_dir @@ -155,11 +155,13 @@ node2_dir = direction for(var/obj/machinery/atmospherics/target in get_step(src,node1_dir)) + target.init_dir() if(target.initialize_directions & get_dir(target,src)) if (check_connect_types(target,src)) node1 = target break for(var/obj/machinery/atmospherics/target in get_step(src,node2_dir)) + target.init_dir() if(target.initialize_directions & get_dir(target,src)) if (check_connect_types(target,src)) node2 = target diff --git a/code/ATMOSPHERICS/he_pipes.dm b/code/ATMOSPHERICS/he_pipes.dm index 52dedfb089..bdad6a62e5 100644 --- a/code/ATMOSPHERICS/he_pipes.dm +++ b/code/ATMOSPHERICS/he_pipes.dm @@ -1,5 +1,5 @@ -obj/machinery/atmospherics/pipe/simple/heat_exchanging +/obj/machinery/atmospherics/pipe/simple/heat_exchanging icon = 'icons/atmos/heat.dmi' icon_state = "intact" pipe_icon = "hepipe" @@ -17,93 +17,99 @@ obj/machinery/atmospherics/pipe/simple/heat_exchanging buckle_lying = 1 // BubbleWrap - New() - ..() - initialize_directions_he = initialize_directions // The auto-detection from /pipe is good enough for a simple HE pipe - // BubbleWrap END - color = "#404040" //we don't make use of the fancy overlay system for colours, use this to set the default. +/obj/machinery/atmospherics/pipe/simple/heat_exchanging/New() + ..() + init_dir() +// BubbleWrap END + color = "#404040" //we don't make use of the fancy overlay system for colours, use this to set the default. - initialize() - normalize_dir() - var/node1_dir - var/node2_dir +/obj/machinery/atmospherics/pipe/simple/heat_exchanging/init_dir() + initialize_directions_he = initialize_directions // The auto-detection from /pipe is good enough for a simple HE pipe - for(var/direction in cardinal) - if(direction&initialize_directions_he) - if (!node1_dir) - node1_dir = direction - else if (!node2_dir) - node2_dir = direction +/obj/machinery/atmospherics/pipe/simple/heat_exchanging/initialize() + init_dir() + normalize_dir() + var/node1_dir + var/node2_dir - for(var/obj/machinery/atmospherics/pipe/simple/heat_exchanging/target in get_step(src,node1_dir)) - if(target.initialize_directions_he & get_dir(target,src)) - node1 = target - break - for(var/obj/machinery/atmospherics/pipe/simple/heat_exchanging/target in get_step(src,node2_dir)) - if(target.initialize_directions_he & get_dir(target,src)) - node2 = target - break - if(!node1 && !node2) - qdel(src) - return + for(var/direction in cardinal) + if(direction&initialize_directions_he) + if (!node1_dir) + node1_dir = direction + else if (!node2_dir) + node2_dir = direction - update_icon() + for(var/obj/machinery/atmospherics/pipe/simple/heat_exchanging/target in get_step(src,node1_dir)) + target.init_dir() + if(target.initialize_directions_he & get_dir(target,src)) + node1 = target + break + for(var/obj/machinery/atmospherics/pipe/simple/heat_exchanging/target in get_step(src,node2_dir)) + target.init_dir() + if(target.initialize_directions_he & get_dir(target,src)) + node2 = target + break + if(!node1 && !node2) + qdel(src) return + update_icon() + return - process() - if(!parent) - ..() - else - var/datum/gas_mixture/pipe_air = return_air() - if(istype(loc, /turf/simulated/)) - var/environment_temperature = 0 - if(loc:blocks_air) - environment_temperature = loc:temperature - else - var/datum/gas_mixture/environment = loc.return_air() - environment_temperature = environment.temperature - if(abs(environment_temperature-pipe_air.temperature) > minimum_temperature_difference) - parent.temperature_interact(loc, volume, thermal_conductivity) - else if(istype(loc, /turf/space/)) - parent.radiate_heat_to_space(surface, 1) - if(buckled_mob) - var/hc = pipe_air.heat_capacity() - var/avg_temp = (pipe_air.temperature * hc + buckled_mob.bodytemperature * 3500) / (hc + 3500) - pipe_air.temperature = avg_temp - buckled_mob.bodytemperature = avg_temp +/obj/machinery/atmospherics/pipe/simple/heat_exchanging/process() + if(!parent) + ..() + else + var/datum/gas_mixture/pipe_air = return_air() + if(istype(loc, /turf/simulated/)) + var/environment_temperature = 0 + if(loc:blocks_air) + environment_temperature = loc:temperature + else + var/datum/gas_mixture/environment = loc.return_air() + environment_temperature = environment.temperature + if(abs(environment_temperature-pipe_air.temperature) > minimum_temperature_difference) + parent.temperature_interact(loc, volume, thermal_conductivity) + else if(istype(loc, /turf/space/)) + parent.radiate_heat_to_space(surface, 1) - var/heat_limit = 1000 + if(buckled_mob) + var/hc = pipe_air.heat_capacity() + var/avg_temp = (pipe_air.temperature * hc + buckled_mob.bodytemperature * 3500) / (hc + 3500) + pipe_air.temperature = avg_temp + buckled_mob.bodytemperature = avg_temp - var/mob/living/carbon/human/H = buckled_mob - if(istype(H) && H.species) - heat_limit = H.species.heat_level_3 + var/heat_limit = 1000 - if(pipe_air.temperature > heat_limit + 1) - buckled_mob.apply_damage(4 * log(pipe_air.temperature - heat_limit), BURN, BP_TORSO, used_weapon = "Excessive Heat") + var/mob/living/carbon/human/H = buckled_mob + if(istype(H) && H.species) + heat_limit = H.species.heat_level_3 - //fancy radiation glowing - if(pipe_air.temperature && (icon_temperature > 500 || pipe_air.temperature > 500)) //start glowing at 500K - if(abs(pipe_air.temperature - icon_temperature) > 10) - icon_temperature = pipe_air.temperature + if(pipe_air.temperature > heat_limit + 1) + buckled_mob.apply_damage(4 * log(pipe_air.temperature - heat_limit), BURN, BP_TORSO, used_weapon = "Excessive Heat") - var/h_r = heat2color_r(icon_temperature) - var/h_g = heat2color_g(icon_temperature) - var/h_b = heat2color_b(icon_temperature) + //fancy radiation glowing + if(pipe_air.temperature && (icon_temperature > 500 || pipe_air.temperature > 500)) //start glowing at 500K + if(abs(pipe_air.temperature - icon_temperature) > 10) + icon_temperature = pipe_air.temperature - if(icon_temperature < 2000) //scale up overlay until 2000K - var/scale = (icon_temperature - 500) / 1500 - h_r = 64 + (h_r - 64)*scale - h_g = 64 + (h_g - 64)*scale - h_b = 64 + (h_b - 64)*scale + var/h_r = heat2color_r(icon_temperature) + var/h_g = heat2color_g(icon_temperature) + var/h_b = heat2color_b(icon_temperature) - animate(src, color = rgb(h_r, h_g, h_b), time = 20, easing = SINE_EASING) + if(icon_temperature < 2000) //scale up overlay until 2000K + var/scale = (icon_temperature - 500) / 1500 + h_r = 64 + (h_r - 64)*scale + h_g = 64 + (h_g - 64)*scale + h_b = 64 + (h_b - 64)*scale + + animate(src, color = rgb(h_r, h_g, h_b), time = 20, easing = SINE_EASING) -obj/machinery/atmospherics/pipe/simple/heat_exchanging/junction +/obj/machinery/atmospherics/pipe/simple/heat_exchanging/junction icon = 'icons/atmos/junction.dmi' icon_state = "intact" pipe_icon = "hejunction" @@ -112,37 +118,38 @@ obj/machinery/atmospherics/pipe/simple/heat_exchanging/junction minimum_temperature_difference = 300 thermal_conductivity = WALL_HEAT_TRANSFER_COEFFICIENT - // BubbleWrap - New() - .. () - switch ( dir ) - if ( SOUTH ) - initialize_directions = NORTH - initialize_directions_he = SOUTH - if ( NORTH ) - initialize_directions = SOUTH - initialize_directions_he = NORTH - if ( EAST ) - initialize_directions = WEST - initialize_directions_he = EAST - if ( WEST ) - initialize_directions = EAST - initialize_directions_he = WEST - // BubbleWrap END +/obj/machinery/atmospherics/pipe/simple/heat_exchanging/init_dir() + switch ( dir ) + if ( SOUTH ) + initialize_directions = NORTH + initialize_directions_he = SOUTH + if ( NORTH ) + initialize_directions = SOUTH + initialize_directions_he = NORTH + if ( EAST ) + initialize_directions = WEST + initialize_directions_he = EAST + if ( WEST ) + initialize_directions = EAST + initialize_directions_he = WEST - initialize() - for(var/obj/machinery/atmospherics/target in get_step(src,initialize_directions)) - if(target.initialize_directions & get_dir(target,src)) - node1 = target - break - for(var/obj/machinery/atmospherics/pipe/simple/heat_exchanging/target in get_step(src,initialize_directions_he)) - if(target.initialize_directions_he & get_dir(target,src)) - node2 = target - break - if(!node1&&!node2) - qdel(src) - return +/obj/machinery/atmospherics/pipe/simple/heat_exchanging/initialize() + init_dir() + for(var/obj/machinery/atmospherics/target in get_step(src,initialize_directions)) + target.init_dir() + if(target.initialize_directions & get_dir(target,src)) + node1 = target + break + for(var/obj/machinery/atmospherics/pipe/simple/heat_exchanging/target in get_step(src,initialize_directions_he)) + target.init_dir() + if(target.initialize_directions_he & get_dir(target,src)) + node2 = target + break - update_icon() + if(!node1&&!node2) + qdel(src) return + + update_icon() + return diff --git a/code/ATMOSPHERICS/pipes.dm b/code/ATMOSPHERICS/pipes.dm index 9566be9947..c363d85575 100644 --- a/code/ATMOSPHERICS/pipes.dm +++ b/code/ATMOSPHERICS/pipes.dm @@ -34,6 +34,10 @@ return 1 +// This is used to set up what directions pipes will connect to. Called inside New(), initialize(), and when pipes look at another pipe, incase they didn't get to initialize() yet. +/obj/machinery/atmospherics/proc/init_dir() + return + /obj/machinery/atmospherics/pipe/return_air() if(!parent) parent = new /datum/pipeline() @@ -170,19 +174,9 @@ icon = null alpha = 255 - switch(dir) - if(SOUTH || NORTH) - initialize_directions = SOUTH|NORTH - if(EAST || WEST) - initialize_directions = EAST|WEST - if(NORTHEAST) - initialize_directions = NORTH|EAST - if(NORTHWEST) - initialize_directions = NORTH|WEST - if(SOUTHEAST) - initialize_directions = SOUTH|EAST - if(SOUTHWEST) - initialize_directions = SOUTH|WEST + init_dir() + + /obj/machinery/atmospherics/pipe/simple/hide(var/i) if(istype(loc, /turf/simulated)) @@ -210,6 +204,21 @@ else return 1 +/obj/machinery/atmospherics/pipe/simple/init_dir() + switch(dir) + if(SOUTH || NORTH) + initialize_directions = SOUTH|NORTH + if(EAST || WEST) + initialize_directions = EAST|WEST + if(NORTHEAST) + initialize_directions = NORTH|EAST + if(NORTHWEST) + initialize_directions = NORTH|WEST + if(SOUTHEAST) + initialize_directions = SOUTH|EAST + if(SOUTHWEST) + initialize_directions = SOUTH|WEST + /obj/machinery/atmospherics/pipe/simple/proc/burst() src.visible_message("\The [src] bursts!"); playsound(src.loc, 'sound/effects/bang.ogg', 25, 1) @@ -270,6 +279,7 @@ return /obj/machinery/atmospherics/pipe/simple/initialize() + init_dir() normalize_dir() var/node1_dir var/node2_dir @@ -282,11 +292,13 @@ node2_dir = direction for(var/obj/machinery/atmospherics/target in get_step(src,node1_dir)) + target.init_dir() if(target.initialize_directions & get_dir(target,src)) if (check_connect_types(target,src)) node1 = target break for(var/obj/machinery/atmospherics/target in get_step(src,node2_dir)) + target.init_dir() if(target.initialize_directions & get_dir(target,src)) if (check_connect_types(target,src)) node2 = target @@ -436,6 +448,9 @@ alpha = 255 icon = null + init_dir() + +/obj/machinery/atmospherics/pipe/manifold/init_dir() switch(dir) if(NORTH) initialize_directions = EAST|SOUTH|WEST @@ -544,11 +559,13 @@ update_icon() /obj/machinery/atmospherics/pipe/manifold/initialize() + init_dir() var/connect_directions = (NORTH|SOUTH|EAST|WEST)&(~dir) for(var/direction in cardinal) if(direction&connect_directions) for(var/obj/machinery/atmospherics/target in get_step(src,direction)) + target.init_dir() if(target.initialize_directions & get_dir(target,src)) if (check_connect_types(target,src)) node1 = target @@ -561,6 +578,7 @@ for(var/direction in cardinal) if(direction&connect_directions) for(var/obj/machinery/atmospherics/target in get_step(src,direction)) + target.init_dir() if(target.initialize_directions & get_dir(target,src)) if (check_connect_types(target,src)) node2 = target @@ -573,6 +591,7 @@ for(var/direction in cardinal) if(direction&connect_directions) for(var/obj/machinery/atmospherics/target in get_step(src,direction)) + target.init_dir() if(target.initialize_directions & get_dir(target,src)) if (check_connect_types(target,src)) node3 = target @@ -823,24 +842,28 @@ /obj/machinery/atmospherics/pipe/manifold4w/initialize() for(var/obj/machinery/atmospherics/target in get_step(src,1)) + target.init_dir() if(target.initialize_directions & 2) if (check_connect_types(target,src)) node1 = target break for(var/obj/machinery/atmospherics/target in get_step(src,2)) + target.init_dir() if(target.initialize_directions & 1) if (check_connect_types(target,src)) node2 = target break for(var/obj/machinery/atmospherics/target in get_step(src,4)) + target.init_dir() if(target.initialize_directions & 8) if (check_connect_types(target,src)) node3 = target break for(var/obj/machinery/atmospherics/target in get_step(src,8)) + target.init_dir() if(target.initialize_directions & 4) if (check_connect_types(target,src)) node4 = target @@ -958,6 +981,9 @@ /obj/machinery/atmospherics/pipe/cap/New() ..() + init_dir() + +/obj/machinery/atmospherics/pipe/cap/init_dir() initialize_directions = dir /obj/machinery/atmospherics/pipe/cap/hide(var/i) @@ -1006,7 +1032,9 @@ overlays += icon_manager.get_atmos_icon("pipe", , pipe_color, "cap") /obj/machinery/atmospherics/pipe/cap/initialize() + init_dir() for(var/obj/machinery/atmospherics/target in get_step(src, dir)) + target.init_dir() if(target.initialize_directions & get_dir(target,src)) if (check_connect_types(target,src)) node = target @@ -1079,9 +1107,12 @@ /obj/machinery/atmospherics/pipe/tank/New() icon_state = "air" - initialize_directions = dir + init_dir() ..() +/obj/machinery/atmospherics/pipe/tank/init_dir() + initialize_directions = dir + /obj/machinery/atmospherics/pipe/tank/process() if(!parent) ..() @@ -1110,9 +1141,11 @@ update_underlays() /obj/machinery/atmospherics/pipe/tank/initialize() + init_dir() var/connect_direction = dir for(var/obj/machinery/atmospherics/target in get_step(src,connect_direction)) + target.init_dir() if(target.initialize_directions & get_dir(target,src)) if (check_connect_types(target,src)) node1 = target @@ -1241,9 +1274,12 @@ var/build_killswitch = 1 /obj/machinery/atmospherics/pipe/vent/New() - initialize_directions = dir + init_dir() ..() +/obj/machinery/atmospherics/pipe/vent/init_dir() + initialize_directions = dir + /obj/machinery/atmospherics/pipe/vent/high_volume name = "Larger vent" volume = 1000 @@ -1279,9 +1315,11 @@ icon_state = "exposed" /obj/machinery/atmospherics/pipe/vent/initialize() + init_dir() var/connect_direction = dir for(var/obj/machinery/atmospherics/target in get_step(src,connect_direction)) + target.init_dir() if(target.initialize_directions & get_dir(target,src)) if (check_connect_types(target,src)) node1 = target diff --git a/code/__defines/misc.dm b/code/__defines/misc.dm index 5b0de767eb..3bb7333a02 100644 --- a/code/__defines/misc.dm +++ b/code/__defines/misc.dm @@ -214,3 +214,11 @@ #define WORLD_ICON_SIZE 32 //Needed for the R-UST port #define PIXEL_MULTIPLIER WORLD_ICON_SIZE/32 //Needed for the R-UST port + +// Maploader bounds indices +#define MAP_MINX 1 +#define MAP_MINY 2 +#define MAP_MINZ 3 +#define MAP_MAXX 4 +#define MAP_MAXY 5 +#define MAP_MAXZ 6 \ No newline at end of file diff --git a/code/_helpers/lists.dm b/code/_helpers/lists.dm index e647cd1df7..8b9040820f 100644 --- a/code/_helpers/lists.dm +++ b/code/_helpers/lists.dm @@ -662,3 +662,13 @@ proc/dd_sortedTextList(list/incoming) L.Swap(start++,end--) return L + +//Copies a list, and all lists inside it recusively +//Does not copy any other reference type +/proc/deepCopyList(list/l) + if(!islist(l)) + return l + . = l.Copy() + for(var/i = 1 to l.len) + if(islist(.[i])) + .[i] = .(.[i]) diff --git a/code/_helpers/text.dm b/code/_helpers/text.dm index bd175e4fcc..d4fa4a74bb 100644 --- a/code/_helpers/text.dm +++ b/code/_helpers/text.dm @@ -331,4 +331,61 @@ proc/TextPreview(var/string,var/len=40) /proc/strip_improper(var/text) return replacetext(replacetext(text, "\proper", ""), "\improper", "") +//Used for applying byonds text macros to strings that are loaded at runtime +/proc/apply_text_macros(string) + var/next_backslash = findtext(string, "\\") + if(!next_backslash) + return string + + var/leng = length(string) + + var/next_space = findtext(string, " ", next_backslash + 1) + if(!next_space) + next_space = leng - next_backslash + + if(!next_space) //trailing bs + return string + + var/base = next_backslash == 1 ? "" : copytext(string, 1, next_backslash) + var/macro = lowertext(copytext(string, next_backslash + 1, next_space)) + var/rest = next_backslash > leng ? "" : copytext(string, next_space + 1) + + //See http://www.byond.com/docs/ref/info.html#/DM/text/macros + switch(macro) + //prefixes/agnostic + if("the") + rest = text("\the []", rest) + if("a") + rest = text("\a []", rest) + if("an") + rest = text("\an []", rest) + if("proper") + rest = text("\proper []", rest) + if("improper") + rest = text("\improper []", rest) + if("roman") + rest = text("\roman []", rest) + //postfixes + if("th") + base = text("[]\th", rest) + if("s") + base = text("[]\s", rest) + if("he") + base = text("[]\he", rest) + if("she") + base = text("[]\she", rest) + if("his") + base = text("[]\his", rest) + if("himself") + base = text("[]\himself", rest) + if("herself") + base = text("[]\herself", rest) + if("hers") + base = text("[]\hers", rest) + + . = base + if(rest) + . += .(rest) + + #define gender2text(gender) capitalize(gender) \ No newline at end of file diff --git a/code/controllers/master.dm b/code/controllers/master.dm index 7c2bc86a65..5056e82e7b 100644 --- a/code/controllers/master.dm +++ b/code/controllers/master.dm @@ -47,7 +47,7 @@ var/datum/controller/master/Master = new() var/static/restart_clear = 0 var/static/restart_timeout = 0 var/static/restart_count = 0 - + //current tick limit, assigned before running a subsystem. //used by CHECK_TICK as well so that the procs subsystems call can obey that SS's tick limits var/static/current_ticklimit = TICK_LIMIT_RUNNING @@ -541,15 +541,25 @@ var/datum/controller/master/Master = new() stat("Master Controller:", statclick.update("(TickRate:[Master.processing]) (Iteration:[Master.iteration])")) /datum/controller/master/StartLoadingMap() + if(map_loading) + admin_notice("Another map is attempting to be loaded before first map released lock. Delaying.", R_DEBUG) + else + admin_notice("Map is now being built. Locking.", R_DEBUG) + //disallow more than one map to load at once, multithreading it will just cause race conditions while(map_loading) stoplag() for(var/S in subsystems) var/datum/controller/subsystem/SS = S SS.StartLoadingMap() + + // ZAS might displace objects as the map loads if an air tick is processed mid-load. + air_processing_killed = TRUE map_loading = TRUE /datum/controller/master/StopLoadingMap(bounds = null) + admin_notice("Map is finished. Unlocking.", R_DEBUG) + air_processing_killed = FALSE map_loading = FALSE for(var/S in subsystems) var/datum/controller/subsystem/SS = S diff --git a/code/controllers/subsystems/creation.dm b/code/controllers/subsystems/creation.dm new file mode 100644 index 0000000000..e92a0447c8 --- /dev/null +++ b/code/controllers/subsystems/creation.dm @@ -0,0 +1,29 @@ +// +// Creation subsystem, which is responsible for initializing newly created objects. +// +SUBSYSTEM_DEF(creation) + name = "Creation" + priority = 14 + wait = 5 +// flags = SS_POST_FIRE_TIMING|SS_BACKGROUND|SS_NO_INIT + flags = SS_NO_FIRE|SS_NO_INIT + runlevels = RUNLEVELS_DEFAULT | RUNLEVEL_LOBBY + + var/list/atoms_needing_initialize = list() + + var/map_loading = FALSE + +/datum/controller/subsystem/creation/StartLoadingMap() + map_loading = TRUE + +/datum/controller/subsystem/creation/StopLoadingMap() + map_loading = FALSE + +/datum/controller/subsystem/creation/proc/initialize_late_atoms() + admin_notice("Initializing atoms in submap.", R_DEBUG) + var/total_atoms = atoms_needing_initialize.len + for(var/atom/movable/A in atoms_needing_initialize) + if(!QDELETED(A)) + A.initialize() + atoms_needing_initialize -= A + admin_notice("Initalized [total_atoms] atoms in submap.", R_DEBUG) \ No newline at end of file diff --git a/code/game/atoms_movable.dm b/code/game/atoms_movable.dm index 64ae0722f8..81501979fd 100644 --- a/code/game/atoms_movable.dm +++ b/code/game/atoms_movable.dm @@ -21,7 +21,10 @@ /atom/movable/New() ..() if(auto_init && ticker && ticker.current_state == GAME_STATE_PLAYING) - initialize() + if(SScreation && SScreation.map_loading) // If a map is being loaded, newly created objects need to wait for it to finish. + SScreation.atoms_needing_initialize += src + else + initialize() /atom/movable/Destroy() . = ..() diff --git a/code/game/machinery/alarm.dm b/code/game/machinery/alarm.dm index 5e8620fd10..7b330d10f9 100644 --- a/code/game/machinery/alarm.dm +++ b/code/game/machinery/alarm.dm @@ -267,7 +267,7 @@ return 0 /obj/machinery/alarm/proc/master_is_operating() - return alarm_area.master_air_alarm && !(alarm_area.master_air_alarm.stat & (NOPOWER | BROKEN)) + return alarm_area && alarm_area.master_air_alarm && !(alarm_area.master_air_alarm.stat & (NOPOWER | BROKEN)) /obj/machinery/alarm/proc/elect_master() for(var/obj/machinery/alarm/AA in alarm_area) diff --git a/code/game/machinery/status_display.dm b/code/game/machinery/status_display.dm index 98582f3ccc..527b5f1283 100644 --- a/code/game/machinery/status_display.dm +++ b/code/game/machinery/status_display.dm @@ -90,6 +90,10 @@ if(STATUS_DISPLAY_BLANK) //blank return 1 if(STATUS_DISPLAY_TRANSFER_SHUTTLE_TIME) //emergency shuttle timer + if(!emergency_shuttle) + message1 = "-ETA-" + message2 = "Never" // You're here forever. + return 1 if(emergency_shuttle.waiting_to_leave()) message1 = "-ETD-" if(emergency_shuttle.shuttle.is_launching()) @@ -172,12 +176,16 @@ maptext = new_text /obj/machinery/status_display/proc/get_shuttle_timer_arrival() + if(!emergency_shuttle) + return "Error" var/timeleft = emergency_shuttle.estimate_arrival_time() if(timeleft < 0) return "" return "[add_zero(num2text((timeleft / 60) % 60),2)]:[add_zero(num2text(timeleft % 60), 2)]" /obj/machinery/status_display/proc/get_shuttle_timer_departure() + if(!emergency_shuttle) + return "Error" var/timeleft = emergency_shuttle.estimate_launch_time() if(timeleft < 0) return "" diff --git a/code/game/objects/effects/landmarks.dm b/code/game/objects/effects/landmarks.dm index 788e65827b..e031b09f5e 100644 --- a/code/game/objects/effects/landmarks.dm +++ b/code/game/objects/effects/landmarks.dm @@ -87,9 +87,11 @@ if(delete_me) qdel(src) -/obj/effect/landmark/Destroy() - landmarks_list -= src - return ..() +/obj/effect/landmark/Destroy(var/force = FALSE) + if(delete_me || force) + landmarks_list -= src + return ..() + return QDEL_HINT_LETMELIVE /obj/effect/landmark/start name = "start" diff --git a/code/modules/admin/admin_verbs.dm b/code/modules/admin/admin_verbs.dm index ab3fa05d21..f527de5de6 100644 --- a/code/modules/admin/admin_verbs.dm +++ b/code/modules/admin/admin_verbs.dm @@ -136,7 +136,10 @@ var/list/admin_verbs_spawn = list( /datum/admins/proc/spawn_atom, //allows us to spawn instances, /client/proc/respawn_character, /client/proc/virus2_editor, - /client/proc/spawn_chemdisp_cartridge + /client/proc/spawn_chemdisp_cartridge, + /client/proc/map_template_load, + /client/proc/map_template_upload, + /client/proc/map_template_load_on_new_z ) var/list/admin_verbs_server = list( /datum/admins/proc/capture_map, diff --git a/code/modules/admin/verbs/map_template_loadverb.dm b/code/modules/admin/verbs/map_template_loadverb.dm new file mode 100644 index 0000000000..25fda4bfb3 --- /dev/null +++ b/code/modules/admin/verbs/map_template_loadverb.dm @@ -0,0 +1,69 @@ +/client/proc/map_template_load() + set category = "Debug" + set name = "Map template - Place At Loc" + + var/datum/map_template/template + + + var/map = input(usr, "Choose a Map Template to place at your CURRENT LOCATION","Place Map Template") as null|anything in map_templates + if(!map) + return + template = map_templates[map] + + var/turf/T = get_turf(mob) + if(!T) + return + + var/list/preview = list() + template.preload_size(template.mappath) + for(var/S in template.get_affected_turfs(T,centered = TRUE)) + preview += image('icons/misc/debug_group.dmi',S ,"red") + usr.client.images += preview + if(alert(usr,"Confirm location.", "Template Confirm","No","Yes") == "Yes") + if(template.annihilate && alert(usr,"This template is set to annihilate everything in the red square. \ + \nEVERYTHING IN THE RED SQUARE WILL BE DELETED, ARE YOU ABSOLUTELY SURE?", "Template Confirm","No","Yes") == "No") + usr.client.images -= preview + return + + if(template.load(T, centered = TRUE)) + message_admins("[key_name_admin(usr)] has placed a map template ([template.name]).") + else + to_chat(usr, "Failed to place map") + usr.client.images -= preview + +/client/proc/map_template_load_on_new_z() + set category = "Debug" + set name = "Map template - New Z" + + var/datum/map_template/template + + var/map = input(usr, "Choose a Map Template to place on a new Z-level.","Place Map Template") as null|anything in map_templates + if(!map) + return + template = map_templates[map] + + if(alert(usr,"Confirm map load.", "Template Confirm","No","Yes") == "Yes") + if(template.load_new_z()) + message_admins("[key_name_admin(usr)] has placed a map template ([template.name]) on Z level [world.maxz].") + else + to_chat(usr, "Failed to place map") + + +/client/proc/map_template_upload() + set category = "Debug" + set name = "Map Template - Upload" + + var/map = input(usr, "Choose a Map Template to upload to template storage","Upload Map Template") as null|file + if(!map) + return + if(copytext("[map]",-4) != ".dmm") + to_chat(usr, "Bad map file: [map]") + return + + var/datum/map_template/M = new(map, "[map]") + if(M.preload_size(map)) + to_chat(usr, "Map template '[map]' ready to place ([M.width]x[M.height])") + map_templates[M.name] = M + message_admins("[key_name_admin(usr)] has uploaded a map template ([map])") + else + to_chat(usr, "Map template '[map]' failed to load properly") diff --git a/code/modules/awaymissions/zlevel.dm b/code/modules/awaymissions/zlevel.dm index 43f11c7521..740e408062 100644 --- a/code/modules/awaymissions/zlevel.dm +++ b/code/modules/awaymissions/zlevel.dm @@ -40,7 +40,8 @@ proc/createRandomZlevel() var/map = pick(potentialRandomZlevels) var/file = file(map) if(isfile(file)) - maploader.load_map(file) + var/datum/map_template/template = new(file, "away mission") + template.load_new_z() world.log << "away mission loaded: [map]" for(var/obj/effect/landmark/L in landmarks_list) diff --git a/code/modules/maps/dmm_suite.dm b/code/modules/maps/dmm_suite.dm index c263072f99..a9f9825211 100644 --- a/code/modules/maps/dmm_suite.dm +++ b/code/modules/maps/dmm_suite.dm @@ -1,4 +1,4 @@ -var/global/dmm_suite/maploader = new +var/global/dmm_suite/maploader = null dmm_suite{ /* diff --git a/code/modules/maps/tg/dmm_suite.dm b/code/modules/maps/tg/dmm_suite.dm new file mode 100644 index 0000000000..c4ceec33ee --- /dev/null +++ b/code/modules/maps/tg/dmm_suite.dm @@ -0,0 +1,63 @@ +dmm_suite{ + /* + + dmm_suite version 1.0 + Released January 30th, 2011. + + NOTE: Map saving functionality removed + + defines the object /dmm_suite + - Provides the proc load_map() + - Loads the specified map file onto the specified z-level. + - provides the proc write_map() + - Returns a text string of the map in dmm format + ready for output to a file. + - provides the proc save_map() + - Returns a .dmm file if map is saved + - Returns FALSE if map fails to save + + The dmm_suite provides saving and loading of map files in BYOND's native DMM map + format. It approximates the map saving and loading processes of the Dream Maker + and Dream Seeker programs so as to allow editing, saving, and loading of maps at + runtime. + + ------------------------ + + To save a map at runtime, create an instance of /dmm_suite, and then call + write_map(), which accepts three arguments: + - A turf representing one corner of a three dimensional grid (Required). + - Another turf representing the other corner of the same grid (Required). + - Any, or a combination, of several bit flags (Optional, see documentation). + + The order in which the turfs are supplied does not matter, the /dmm_writer will + determine the grid containing both, in much the same way as DM's block() function. + write_map() will then return a string representing the saved map in dmm format; + this string can then be saved to a file, or used for any other purose. + + ------------------------ + + To load a map at runtime, create an instance of /dmm_suite, and then call load_map(), + which accepts two arguments: + - A .dmm file to load (Required). + - A number representing the z-level on which to start loading the map (Optional). + + The /dmm_suite will load the map file starting on the specified z-level. If no + z-level was specified, world.maxz will be increased so as to fit the map. Note + that if you wish to load a map onto a z-level that already has objects on it, + you will have to handle the removal of those objects. Otherwise the new map will + simply load the new objects on top of the old ones. + + Also note that all type paths specified in the .dmm file must exist in the world's + code, and that the /dmm_reader trusts that files to be loaded are in fact valid + .dmm files. Errors in the .dmm format will cause runtime errors. + + */ + + verb/load_map(var/dmm_file as file, var/x_offset as num, var/y_offset as num, var/z_offset as num, var/cropMap as num, var/measureOnly as num, no_changeturf as num){ + // dmm_file: A .dmm file to load (Required). + // z_offset: A number representing the z-level on which to start loading the map (Optional). + // cropMap: When true, the map will be cropped to fit the existing world dimensions (Optional). + // measureOnly: When true, no changes will be made to the world (Optional). + // no_changeturf: When true, turf/AfterChange won't be called on loaded turfs + } +} \ No newline at end of file diff --git a/code/modules/maps/tg/map_template.dm b/code/modules/maps/tg/map_template.dm new file mode 100644 index 0000000000..ae8031638f --- /dev/null +++ b/code/modules/maps/tg/map_template.dm @@ -0,0 +1,165 @@ +var/list/global/map_templates = list() + +// Called when the world starts, in world.dm +/proc/load_map_templates() + for(var/T in subtypesof(/datum/map_template)) + var/datum/map_template/template = T + if(!(initial(template.mappath))) // If it's missing the actual path its probably a base type or being used for inheritence. + continue + template = new T() + map_templates[template.name] = template + return TRUE + +/datum/map_template + var/name = "Default Template Name" + var/desc = "Some text should go here. Maybe." + var/width = 0 + var/height = 0 + var/mappath = null + var/loaded = 0 // Times loaded this round + var/annihilate = FALSE // If true, all (movable) atoms at the location where the map is loaded will be deleted before the map is loaded in. + var/static/dmm_suite/maploader = new + +/datum/map_template/New(path = null, rename = null) + if(path) + mappath = path + if(mappath) + preload_size(mappath) + if(rename) + name = rename + +/datum/map_template/proc/preload_size(path) + var/bounds = maploader.load_map(file(path), 1, 1, 1, cropMap=FALSE, measureOnly=TRUE) + if(bounds) + width = bounds[MAP_MAXX] // Assumes all templates are rectangular, have a single Z level, and begin at 1,1,1 + height = bounds[MAP_MAXY] + return bounds + +/datum/map_template/proc/initTemplateBounds(var/list/bounds) + var/list/obj/machinery/atmospherics/atmos_machines = list() + var/list/atom/atoms = list() + var/list/area/areas = list() +// var/list/turf/turfs = list() + + for(var/L in block(locate(bounds[MAP_MINX], bounds[MAP_MINY], bounds[MAP_MINZ]), + locate(bounds[MAP_MAXX], bounds[MAP_MAXY], bounds[MAP_MAXZ]))) + var/turf/B = L + atoms += B + for(var/A in B) + atoms += A +// turfs += B + areas |= get_area(B) + if(istype(A, /obj/machinery/atmospherics)) + atmos_machines += A + + var/i = 0 + +// Apparently when areas get initialize()'d they initialize their turfs as well. +// If this is ever changed, uncomment the block of code below. + +// admin_notice("Initializing newly created simulated turfs in submap.", R_DEBUG) +// for(var/turf/simulated/T in turfs) +// T.initialize() +// i++ +// admin_notice("[i] turf\s initialized.", R_DEBUG) +// i = 0 + + SScreation.initialize_late_atoms() + + admin_notice("Initializing newly created area(s) in submap.", R_DEBUG) + for(var/area/A in areas) + A.initialize() + i++ + admin_notice("[i] area\s initialized.", R_DEBUG) + i = 0 + + admin_notice("Initializing atmos pipenets and machinery in submap.", R_DEBUG) + for(var/obj/machinery/atmospherics/machine in atmos_machines) + machine.initialize() + i++ + + for(var/obj/machinery/atmospherics/machine in atmos_machines) + machine.build_network() + + for(var/obj/machinery/atmospherics/unary/U in machines) + if(istype(U, /obj/machinery/atmospherics/unary/vent_pump)) + var/obj/machinery/atmospherics/unary/vent_pump/T = U + T.broadcast_status() + else if(istype(U, /obj/machinery/atmospherics/unary/vent_scrubber)) + var/obj/machinery/atmospherics/unary/vent_scrubber/T = U + T.broadcast_status() + admin_notice("[i] pipe\s initialized.", R_DEBUG) + + admin_notice("Rebuilding powernets due to submap creation.", R_DEBUG) + makepowernets() + + admin_notice("Submap initializations finished.", R_DEBUG) + +/datum/map_template/proc/load_new_z() + var/x = round(world.maxx/2) + var/y = round(world.maxy/2) + + var/list/bounds = maploader.load_map(file(mappath), x, y) + if(!bounds) + return FALSE + +// repopulate_sorted_areas() + + //initialize things that are normally initialized after map load + initTemplateBounds(bounds) + log_game("Z-level [name] loaded at at [x],[y],[world.maxz]") + return TRUE + +/datum/map_template/proc/load(turf/T, centered = FALSE) + var/old_T = T + if(centered) + T = locate(T.x - round(width/2) , T.y - round(height/2) , T.z) + if(!T) + return + if(T.x+width > world.maxx) + return + if(T.y+height > world.maxy) + return + + if(annihilate) + annihilate_bounds(old_T, centered) + + var/list/bounds = maploader.load_map(file(mappath), T.x, T.y, T.z, cropMap=TRUE) + if(!bounds) + return + +// if(!SSmapping.loading_ruins) //Will be done manually during mapping ss init +// repopulate_sorted_areas() + + //initialize things that are normally initialized after map load + initTemplateBounds(bounds) + + log_game("[name] loaded at at [T.x],[T.y],[T.z]") + loaded++ + return TRUE + +/datum/map_template/proc/get_affected_turfs(turf/T, centered = FALSE) + var/turf/placement = T + if(centered) + var/turf/corner = locate(placement.x - round(width/2), placement.y - round(height/2), placement.z) + if(corner) + placement = corner + return block(placement, locate(placement.x+width-1, placement.y+height-1, placement.z)) + +/datum/map_template/proc/annihilate_bounds(turf/origin, centered = FALSE) + var/deleted_atoms = 0 + admin_notice("Annihilating objects in submap loading locatation.", R_DEBUG) + var/list/turfs_to_clean = get_affected_turfs(origin, centered) + if(turfs_to_clean.len) + for(var/turf/T in turfs_to_clean) + for(var/atom/movable/AM in T) + ++deleted_atoms + qdel(AM) + admin_notice("Annihilated [deleted_atoms] objects.", R_DEBUG) + + +//for your ever biggening badminnery kevinz000 +//❤ - Cyberboss +/proc/load_new_z_level(var/file, var/name) + var/datum/map_template/template = new(file, name) + template.load_new_z() \ No newline at end of file diff --git a/code/modules/maps/tg/reader.dm b/code/modules/maps/tg/reader.dm new file mode 100644 index 0000000000..e74977d0e5 --- /dev/null +++ b/code/modules/maps/tg/reader.dm @@ -0,0 +1,466 @@ +/////////////////////////////////////////////////////////////// +//SS13 Optimized Map loader +////////////////////////////////////////////////////////////// + +/* +//global datum that will preload variables on atoms instanciation +GLOBAL_VAR_INIT(use_preloader, FALSE) +GLOBAL_DATUM_INIT(_preloader, /dmm_suite/preloader, new) +*/ + +//global datum that will preload variables on atoms instanciation +var/global/dmm_suite/preloader/_preloader = new() +var/global/use_preloader = FALSE + +/dmm_suite + // /"([a-zA-Z]+)" = \(((?:.|\n)*?)\)\n(?!\t)|\((\d+),(\d+),(\d+)\) = \{"([a-zA-Z\n]*)"\}/g + var/static/regex/dmmRegex = new/regex({""(\[a-zA-Z]+)" = \\(((?:.|\n)*?)\\)\n(?!\t)|\\((\\d+),(\\d+),(\\d+)\\) = \\{"(\[a-zA-Z\n]*)"\\}"}, "g") + // /^[\s\n]+"?|"?[\s\n]+$|^"|"$/g + var/static/regex/trimQuotesRegex = new/regex({"^\[\\s\n]+"?|"?\[\\s\n]+$|^"|"$"}, "g") + // /^[\s\n]+|[\s\n]+$/ + var/static/regex/trimRegex = new/regex("^\[\\s\n]+|\[\\s\n]+$", "g") + var/static/list/modelCache = list() + var/static/space_key + #ifdef TESTING + var/static/turfsSkipped + #endif + +/** + * Construct the model map and control the loading process + * + * WORKING : + * + * 1) Makes an associative mapping of model_keys with model + * e.g aa = /turf/unsimulated/wall{icon_state = "rock"} + * 2) Read the map line by line, parsing the result (using parse_grid) + * + */ +/dmm_suite/load_map(dmm_file as file, x_offset as num, y_offset as num, z_offset as num, cropMap as num, measureOnly as num, no_changeturf as num) + //How I wish for RAII + if(!measureOnly) + Master.StartLoadingMap() + space_key = null + #ifdef TESTING + turfsSkipped = 0 + #endif + . = load_map_impl(dmm_file, x_offset, y_offset, z_offset, cropMap, measureOnly, no_changeturf) + #ifdef TESTING + if(turfsSkipped) + testing("Skipped loading [turfsSkipped] default turfs") + #endif + if(!measureOnly) + Master.StopLoadingMap() + +/dmm_suite/proc/load_map_impl(dmm_file, x_offset, y_offset, z_offset, cropMap, measureOnly, no_changeturf) + var/tfile = dmm_file//the map file we're creating + if(isfile(tfile)) + tfile = file2text(tfile) + + if(!x_offset) + x_offset = 1 + if(!y_offset) + y_offset = 1 + if(!z_offset) + z_offset = world.maxz + 1 + + var/list/bounds = list(1.#INF, 1.#INF, 1.#INF, -1.#INF, -1.#INF, -1.#INF) + var/list/grid_models = list() + var/key_len = 0 + + var/stored_index = 1 + while(dmmRegex.Find(tfile, stored_index)) + stored_index = dmmRegex.next + + // "aa" = (/type{vars=blah}) + if(dmmRegex.group[1]) // Model + var/key = dmmRegex.group[1] + if(grid_models[key]) // Duplicate model keys are ignored in DMMs + continue + if(key_len != length(key)) + if(!key_len) + key_len = length(key) + else + throw EXCEPTION("Inconsistant key length in DMM") + if(!measureOnly) + grid_models[key] = dmmRegex.group[2] + + // (1,1,1) = {"aaaaaaaaaaaaaaaaaaaaaaaaaaaaaa"} + else if(dmmRegex.group[3]) // Coords + if(!key_len) + throw EXCEPTION("Coords before model definition in DMM") + + var/xcrdStart = text2num(dmmRegex.group[3]) + x_offset - 1 + //position of the currently processed square + var/xcrd + var/ycrd = text2num(dmmRegex.group[4]) + y_offset - 1 + var/zcrd = text2num(dmmRegex.group[5]) + z_offset - 1 + + var/zexpansion = zcrd > world.maxz + if(zexpansion) + if(cropMap) + continue + else + world.maxz = zcrd //create a new z_level if needed + if(!no_changeturf) + WARNING("Z-level expansion occurred without no_changeturf set, this may cause problems") + + bounds[MAP_MINX] = min(bounds[MAP_MINX], xcrdStart) + bounds[MAP_MINZ] = min(bounds[MAP_MINZ], zcrd) + bounds[MAP_MAXZ] = max(bounds[MAP_MAXZ], zcrd) + + var/list/gridLines = splittext(dmmRegex.group[6], "\n") + + var/leadingBlanks = 0 + while(leadingBlanks < gridLines.len && gridLines[++leadingBlanks] == "") + if(leadingBlanks > 1) + gridLines.Cut(1, leadingBlanks) // Remove all leading blank lines. + + if(!gridLines.len) // Skip it if only blank lines exist. + continue + + if(gridLines.len && gridLines[gridLines.len] == "") + gridLines.Cut(gridLines.len) // Remove only one blank line at the end. + + bounds[MAP_MINY] = min(bounds[MAP_MINY], ycrd) + ycrd += gridLines.len - 1 // Start at the top and work down + + if(!cropMap && ycrd > world.maxy) + if(!measureOnly) + world.maxy = ycrd // Expand Y here. X is expanded in the loop below + bounds[MAP_MAXY] = max(bounds[MAP_MAXY], ycrd) + else + bounds[MAP_MAXY] = max(bounds[MAP_MAXY], min(ycrd, world.maxy)) + + var/maxx = xcrdStart + if(measureOnly) + for(var/line in gridLines) + maxx = max(maxx, xcrdStart + length(line) / key_len - 1) + else + for(var/line in gridLines) + if(ycrd <= world.maxy && ycrd >= 1) + xcrd = xcrdStart + for(var/tpos = 1 to length(line) - key_len + 1 step key_len) + if(xcrd > world.maxx) + if(cropMap) + break + else + world.maxx = xcrd + + if(xcrd >= 1) + var/model_key = copytext(line, tpos, tpos + key_len) + var/no_afterchange = no_changeturf || zexpansion + if(!no_afterchange || (model_key != space_key)) + if(!grid_models[model_key]) + throw EXCEPTION("Undefined model key in DMM.") + parse_grid(grid_models[model_key], model_key, xcrd, ycrd, zcrd, no_changeturf || zexpansion) + #ifdef TESTING + else + ++turfsSkipped + #endif + CHECK_TICK + maxx = max(maxx, xcrd) + ++xcrd + --ycrd + + bounds[MAP_MAXX] = max(bounds[MAP_MAXX], cropMap ? min(maxx, world.maxx) : maxx) + + CHECK_TICK + + if(bounds[1] == 1.#INF) // Shouldn't need to check every item + return null + else + // if(!measureOnly) + // if(!no_changeturf) + // for(var/t in block(locate(bounds[MAP_MINX], bounds[MAP_MINY], bounds[MAP_MINZ]), locate(bounds[MAP_MAXX], bounds[MAP_MAXY], bounds[MAP_MAXZ]))) + // var/turf/T = t + // //we do this after we load everything in. if we don't; we'll have weird atmos bugs regarding atmos adjacent turfs + // T.post_change() + return bounds + +/** + * Fill a given tile with its area/turf/objects/mobs + * Variable model is one full map line (e.g /turf/unsimulated/wall{icon_state = "rock"}, /area/mine/explored) + * + * WORKING : + * + * 1) Read the model string, member by member (delimiter is ',') + * + * 2) Get the path of the atom and store it into a list + * + * 3) a) Check if the member has variables (text within '{' and '}') + * + * 3) b) Construct an associative list with found variables, if any (the atom index in members is the same as its variables in members_attributes) + * + * 4) Instanciates the atom with its variables + * + */ +/dmm_suite/proc/parse_grid(model as text, model_key as text, xcrd as num,ycrd as num,zcrd as num, no_changeturf as num) + /*Method parse_grid() + - Accepts a text string containing a comma separated list of type paths of the + same construction as those contained in a .dmm file, and instantiates them. + */ + + var/list/members //will contain all members (paths) in model (in our example : /turf/unsimulated/wall and /area/mine/explored) + var/list/members_attributes //will contain lists filled with corresponding variables, if any (in our example : list(icon_state = "rock") and list()) + var/list/cached = modelCache[model] + var/index + + if(cached) + members = cached[1] + members_attributes = cached[2] + else + + ///////////////////////////////////////////////////////// + //Constructing members and corresponding variables lists + //////////////////////////////////////////////////////// + + members = list() + members_attributes = list() + index = 1 + + var/old_position = 1 + var/dpos + + do + //finding next member (e.g /turf/unsimulated/wall{icon_state = "rock"} or /area/mine/explored) + dpos = find_next_delimiter_position(model, old_position, ",", "{", "}") //find next delimiter (comma here) that's not within {...} + + var/full_def = trim_text(copytext(model, old_position, dpos)) //full definition, e.g : /obj/foo/bar{variables=derp} + var/variables_start = findtext(full_def, "{") + var/atom_def = text2path(trim_text(copytext(full_def, 1, variables_start))) //path definition, e.g /obj/foo/bar + old_position = dpos + 1 + + if(!atom_def) // Skip the item if the path does not exist. Fix your crap, mappers! + continue + members.Add(atom_def) + + //transform the variables in text format into a list (e.g {var1="derp"; var2; var3=7} => list(var1="derp", var2, var3=7)) + var/list/fields = list() + + if(variables_start)//if there's any variable + full_def = copytext(full_def,variables_start+1,length(full_def))//removing the last '}' + fields = readlist(full_def, ";") + if(fields.len) + if(!trim(fields[fields.len])) + --fields.len + for(var/I in fields) + var/value = fields[I] + if(istext(value)) + fields[I] = apply_text_macros(value) + + //then fill the members_attributes list with the corresponding variables + members_attributes.len++ + members_attributes[index++] = fields + + CHECK_TICK + while(dpos != 0) + + //check and see if we can just skip this turf + //So you don't have to understand this horrid statement, we can do this if + // 1. no_changeturf is set + // 2. the space_key isn't set yet + // 3. there are exactly 2 members + // 4. with no attributes + // 5. and the members are world.turf and world.area + // Basically, if we find an entry like this: "XXX" = (/turf/default, /area/default) + // We can skip calling this proc every time we see XXX + if(no_changeturf && !space_key && members.len == 2 && members_attributes.len == 2 && length(members_attributes[1]) == 0 && length(members_attributes[2]) == 0 && (world.area in members) && (world.turf in members)) + space_key = model_key + return + + + modelCache[model] = list(members, members_attributes) + + + //////////////// + //Instanciation + //////////////// + + //The next part of the code assumes there's ALWAYS an /area AND a /turf on a given tile + var/turf/crds = locate(xcrd,ycrd,zcrd) + + //first instance the /area and remove it from the members list + index = members.len + if(members[index] != /area/template_noop) + var/atom/instance + _preloader.setup(members_attributes[index])//preloader for assigning set variables on atom creation + var/atype = members[index] + for(var/area/A in world) + if(A.type == atype) + instance = A + break + if(!instance) + instance = new atype(null) + if(crds) + instance.contents.Add(crds) + + if(use_preloader && instance) + _preloader.load(instance) + + //then instance the /turf and, if multiple tiles are presents, simulates the DMM underlays piling effect + + var/first_turf_index = 1 + while(!ispath(members[first_turf_index], /turf)) //find first /turf object in members + first_turf_index++ + + //turn off base new Initialization until the whole thing is loaded + SScreation.StartLoadingMap() + //instanciate the first /turf + var/turf/T + if(members[first_turf_index] != /turf/template_noop) + T = instance_atom(members[first_turf_index],members_attributes[first_turf_index],crds,no_changeturf) + + if(T) + //if others /turf are presents, simulates the underlays piling effect + index = first_turf_index + 1 + while(index <= members.len - 1) // Last item is an /area + var/underlay = T.appearance + T = instance_atom(members[index],members_attributes[index],crds,no_changeturf)//instance new turf + T.underlays += underlay + index++ + + //finally instance all remainings objects/mobs + for(index in 1 to first_turf_index-1) + instance_atom(members[index],members_attributes[index],crds,no_changeturf) + //Restore initialization to the previous value + SScreation.StopLoadingMap() + +//////////////// +//Helpers procs +//////////////// + +//Instance an atom at (x,y,z) and gives it the variables in attributes +/dmm_suite/proc/instance_atom(path,list/attributes, turf/crds, no_changeturf) + _preloader.setup(attributes, path) + + if(crds) + if(!no_changeturf && ispath(path, /turf)) + . = crds.ChangeTurf(path, FALSE, TRUE) + else + . = create_atom(path, crds)//first preloader pass + + if(use_preloader && .)//second preloader pass, for those atoms that don't ..() in New() + _preloader.load(.) + + //custom CHECK_TICK here because we don't want things created while we're sleeping to not initialize + if(TICK_CHECK) + SScreation.StopLoadingMap() + stoplag() + SScreation.StartLoadingMap() + +/dmm_suite/proc/create_atom(path, crds) + set waitfor = FALSE + . = new path (crds) + +//text trimming (both directions) helper proc +//optionally removes quotes before and after the text (for variable name) +/dmm_suite/proc/trim_text(what as text,trim_quotes=0) + if(trim_quotes) + return trimQuotesRegex.Replace(what, "") + else + return trimRegex.Replace(what, "") + + +//find the position of the next delimiter,skipping whatever is comprised between opening_escape and closing_escape +//returns 0 if reached the last delimiter +/dmm_suite/proc/find_next_delimiter_position(text as text,initial_position as num, delimiter=",",opening_escape="\"",closing_escape="\"") + var/position = initial_position + var/next_delimiter = findtext(text,delimiter,position,0) + var/next_opening = findtext(text,opening_escape,position,0) + + while((next_opening != 0) && (next_opening < next_delimiter)) + position = findtext(text,closing_escape,next_opening + 1,0)+1 + next_delimiter = findtext(text,delimiter,position,0) + next_opening = findtext(text,opening_escape,position,0) + + return next_delimiter + + +//build a list from variables in text form (e.g {var1="derp"; var2; var3=7} => list(var1="derp", var2, var3=7)) +//return the filled list +/dmm_suite/proc/readlist(text as text, delimiter=",") + + var/list/to_return = list() + + var/position + var/old_position = 1 + + do + //find next delimiter that is not within "..." + position = find_next_delimiter_position(text,old_position,delimiter) + + //check if this is a simple variable (as in list(var1, var2)) or an associative one (as in list(var1="foo",var2=7)) + var/equal_position = findtext(text,"=",old_position, position) + + var/trim_left = trim_text(copytext(text,old_position,(equal_position ? equal_position : position)),1)//the name of the variable, must trim quotes to build a BYOND compliant associatives list + old_position = position + 1 + + if(equal_position)//associative var, so do the association + var/trim_right = trim_text(copytext(text,equal_position+1,position))//the content of the variable + + //Check for string + if(findtext(trim_right,"\"",1,2)) + trim_right = copytext(trim_right,2,findtext(trim_right,"\"",3,0)) + + //Check for number + else if(isnum(text2num(trim_right))) + trim_right = text2num(trim_right) + + //Check for null + else if(trim_right == "null") + trim_right = null + + //Check for list + else if(copytext(trim_right,1,5) == "list") + trim_right = readlist(copytext(trim_right,6,length(trim_right))) + + //Check for file + else if(copytext(trim_right,1,2) == "'") + trim_right = file(copytext(trim_right,2,length(trim_right))) + + //Check for path + else if(ispath(text2path(trim_right))) + trim_right = text2path(trim_right) + + to_return[trim_left] = trim_right + + else//simple var + to_return[trim_left] = null + + while(position != 0) + + return to_return + +/dmm_suite/Destroy() + ..() + return QDEL_HINT_HARDDEL_NOW + +////////////////// +//Preloader datum +////////////////// + +/dmm_suite/preloader + parent_type = /datum + var/list/attributes + var/target_path + +/dmm_suite/preloader/proc/setup(list/the_attributes, path) + if(the_attributes.len) + use_preloader = TRUE + attributes = the_attributes + target_path = path + +/dmm_suite/preloader/proc/load(atom/what) + for(var/attribute in attributes) + var/value = attributes[attribute] + if(islist(value)) + value = deepCopyList(value) + what.vars[attribute] = value + use_preloader = FALSE + +/area/template_noop + name = "Area Passthrough" + +/turf/template_noop + name = "Turf Passthrough" + icon_state = "template_void" diff --git a/code/world.dm b/code/world.dm index 9a031dda0f..3d89f0c043 100644 --- a/code/world.dm +++ b/code/world.dm @@ -82,6 +82,9 @@ var/global/datum/global_init/init = new () // This is kinda important. Set up details of what the hell things are made of. populate_material_list() + // Loads all the pre-made submap templates. + load_map_templates() + if(config.generate_map) if(using_map.perform_map_generation()) using_map.refresh_mining_turfs() diff --git a/icons/turf/floors.dmi b/icons/turf/floors.dmi index 7f474fa1fe14baceb899e6812c19c651033083ac..7c2f111e05f7b38b79bd888db618161d2b1d399e 100644 GIT binary patch delta 12272 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 KMNUMnLSTaT`zFNz delta 12257 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 v(DHOb;kusYs>FgM