diff --git a/check_regex.yaml b/check_regex.yaml
index 620226f6207e..c051d974de12 100644
--- a/check_regex.yaml
+++ b/check_regex.yaml
@@ -38,7 +38,7 @@ standards:
- exactly:
[
- 298,
+ 295,
"non-bitwise << uses",
'(? 32)
+ starting_coord = i
+ break
+
+ for (var/i = text_len, i >= starting_coord, i--)
+ if (text2ascii(text, i) > 32)
+ return copytext(text, starting_coord, i + 1)
+
+ if(starting_coord > 1)
+ return copytext(text, starting_coord)
+ return ""
+
/**
* Truncate a string to the given length
*
@@ -255,7 +273,7 @@
/proc/trim(text, max_length)
if(max_length)
text = copytext_char(text, 1, max_length)
- return trim_left(trim_right(text))
+ return trim_reduced(text)
//Returns a string with the first element of the string capitalized.
/proc/capitalize(t)
@@ -735,6 +753,9 @@ GLOBAL_LIST_INIT(binary, list("0","1"))
base = text("[]\herself", rest)
if("hers")
base = text("[]\hers", rest)
+ else // Someone fucked up, if you're not a macro just go home yeah?
+ // This does technically break parsing, but at least it's better then what it used to do
+ return base
. = base
if(rest)
diff --git a/code/__HELPERS/unsorted.dm b/code/__HELPERS/unsorted.dm
index 714cbf4849e2..efd551badefb 100644
--- a/code/__HELPERS/unsorted.dm
+++ b/code/__HELPERS/unsorted.dm
@@ -1364,11 +1364,13 @@ GLOBAL_DATUM_INIT(dview_mob, /mob/dview, new)
return "{[time_high]-[time_mid]-[GUID_VERSION][time_low]-[GUID_VARIANT][time_clock]-[node_id]}"
-// \ref behaviour got changed in 512 so this is necesary to replicate old behaviour.
-// If it ever becomes necesary to get a more performant REF(), this lies here in wait
-// #define REF(thing) (thing && istype(thing, /datum) && (thing:datum_flags & DF_USE_TAG) && thing:tag ? "[thing:tag]" : "\ref[thing]")
+/**
+ * \ref behaviour got changed in 512 so this is necesary to replicate old behaviour.
+ * If it ever becomes necesary to get a more performant REF(), this lies here in wait
+ * #define REF(thing) (thing && isdatum(thing) && (thing:datum_flags & DF_USE_TAG) && thing:tag ? "[thing:tag]" : text_ref(thing))
+**/
/proc/REF(input)
- if(istype(input, /datum))
+ if(isdatum(input))
var/datum/thing = input
if(thing.datum_flags & DF_USE_TAG)
if(!thing.tag)
@@ -1376,7 +1378,7 @@ GLOBAL_DATUM_INIT(dview_mob, /mob/dview, new)
thing.datum_flags &= ~DF_USE_TAG
else
return "\[[url_encode(thing.tag)]\]"
- return "\ref[input]"
+ return text_ref(input)
// Makes a call in the context of a different usr
// Use sparingly
diff --git a/code/controllers/subsystem/garbage.dm b/code/controllers/subsystem/garbage.dm
index 895a8c1685fc..b362a7ed4e62 100644
--- a/code/controllers/subsystem/garbage.dm
+++ b/code/controllers/subsystem/garbage.dm
@@ -221,7 +221,7 @@ SUBSYSTEM_DEF(garbage)
var/type = D.type
var/datum/qdel_item/I = items[type]
- log_world("## TESTING: GC: -- \ref[D] | [type] was unable to be GC'd --")
+ log_world("## TESTING: GC: -- [text_ref(D)] | [type] was unable to be GC'd --")
#ifdef TESTING
for(var/c in GLOB.admins) //Using testing() here would fill the logs with ADMIN_VV garbage
var/client/admin = c
@@ -258,7 +258,7 @@ SUBSYSTEM_DEF(garbage)
return
var/queue_time = world.time
- var/refid = "\ref[D]"
+ var/refid = text_ref(D)
if (D.gc_destroyed <= 0)
D.gc_destroyed = queue_time
@@ -271,7 +271,7 @@ SUBSYSTEM_DEF(garbage)
++delslasttick
++totaldels
var/type = D.type
- var/refID = "\ref[D]"
+ var/refID = text_ref(D)
var/datum/qdel_item/I = items[type]
if (!force && I.qdel_flags & QDEL_ITEM_SUSPENDED_FOR_LAG)
@@ -389,7 +389,7 @@ SUBSYSTEM_DEF(garbage)
D.find_references()
if (QDEL_HINT_IFFAIL_FINDREFERENCE) //qdel will, if REFERENCE_TRACKING is enabled and the object fails to collect, display all references to this object.
SSgarbage.Queue(D)
- SSgarbage.reference_find_on_fail["\ref[D]"] = TRUE
+ SSgarbage.reference_find_on_fail[text_ref(D)] = TRUE
#endif
else
#ifdef TESTING
diff --git a/code/controllers/subsystem/statpanel.dm b/code/controllers/subsystem/statpanel.dm
index ad13009760a0..1b844f168cf9 100644
--- a/code/controllers/subsystem/statpanel.dm
+++ b/code/controllers/subsystem/statpanel.dm
@@ -153,17 +153,16 @@ SUBSYSTEM_DEF(statpanels)
list("CPU:", world.cpu),
list("Instances:", "[num2text(world.contents.len, 10)]"),
list("World Time:", "[world.time]"),
- list("Globals:", GLOB.stat_entry(), "\ref[GLOB]"),
- list("[config]:", config.stat_entry(), "\ref[config]"),
+ list("Globals:", GLOB.stat_entry(), text_ref(GLOB)),
+ list("[config]:", config.stat_entry(), text_ref(config)),
list("Byond:", "(FPS:[world.fps]) (TickCount:[world.time/world.tick_lag]) (TickDrift:[round(Master.tickdrift,1)]([round((Master.tickdrift/(world.time/world.tick_lag))*100,0.1)]%)) (Internal Tick Usage: [round(MAPTICK_LAST_INTERNAL_TICK_USAGE,0.1)]%)"),
- list("Master Controller:", Master.stat_entry(), "\ref[Master]"),
- list("Failsafe Controller:", Failsafe.stat_entry(), "\ref[Failsafe]"),
+ list("Master Controller:", Master.stat_entry(), text_ref(Master)),
+ list("Failsafe Controller:", Failsafe.stat_entry(), text_ref(Failsafe)),
list("","")
)
- for(var/ss in Master.subsystems)
- var/datum/controller/subsystem/sub_system = ss
- mc_data[++mc_data.len] = list("\[[sub_system.state_letter()]][sub_system.name]", sub_system.stat_entry(), "\ref[sub_system]")
- mc_data[++mc_data.len] = list("Camera Net", "Cameras: [GLOB.cameranet.cameras.len] | Chunks: [GLOB.cameranet.chunks.len]", "\ref[GLOB.cameranet]")
+ for(var/datum/controller/subsystem/sub_system as anything in Master.subsystems)
+ mc_data[++mc_data.len] = list("\[[sub_system.state_letter()]][sub_system.name]", sub_system.stat_entry(), text_ref(sub_system))
+ mc_data[++mc_data.len] = list("Camera Net", "Cameras: [GLOB.cameranet.cameras.len] | Chunks: [GLOB.cameranet.chunks.len]", text_ref(GLOB.cameranet))
mc_data_encoded = url_encode(json_encode(mc_data))
/atom/proc/remove_from_cache()
diff --git a/code/controllers/subsystem/timer.dm b/code/controllers/subsystem/timer.dm
index a37f7e1c0896..176f91cf5808 100644
--- a/code/controllers/subsystem/timer.dm
+++ b/code/controllers/subsystem/timer.dm
@@ -511,8 +511,8 @@ SUBSYSTEM_DEF(timer)
/datum/timedevent/proc/bucketJoin()
// Generate debug-friendly name for timer
var/static/list/bitfield_flags = list("TIMER_UNIQUE", "TIMER_OVERRIDE", "TIMER_CLIENT_TIME", "TIMER_STOPPABLE", "TIMER_NO_HASH_WAIT", "TIMER_LOOP")
- name = "Timer: [id] (\ref[src]), TTR: [timeToRun], wait:[wait] Flags: [jointext(bitfield_to_list(flags, bitfield_flags), ", ")], \
- callBack: \ref[callBack], callBack.object: [callBack.object]\ref[callBack.object]([getcallingtype()]), \
+ name = "Timer: [id] ([text_ref(src)]), TTR: [timeToRun], wait:[wait] Flags: [jointext(bitfield_to_list(flags, bitfield_flags), ", ")], \
+ callBack: [text_ref(callBack)], callBack.object: [callBack.object][text_ref(callBack.object)]([getcallingtype()]), \
callBack.delegate:[callBack.delegate]([callBack.arguments ? callBack.arguments.Join(", ") : ""]), source: [source]"
if (bucket_joined)
diff --git a/code/controllers/subsystem/vis_overlays.dm b/code/controllers/subsystem/vis_overlays.dm
index a4b0fccae437..6d134610f9f3 100644
--- a/code/controllers/subsystem/vis_overlays.dm
+++ b/code/controllers/subsystem/vis_overlays.dm
@@ -42,7 +42,7 @@ SUBSYSTEM_DEF(vis_overlays)
else
overlay = _create_new_vis_overlay(icon, iconstate, layer, plane, dir, alpha, add_appearance_flags)
overlay.cache_expiration = -1
- var/cache_id = "\ref[overlay]@{[world.time]}"
+ var/cache_id = "[text_ref(overlay)]@{[world.time]}"
vis_overlay_cache[cache_id] = overlay
. = overlay
if(overlay == null)
diff --git a/code/datums/datum.dm b/code/datums/datum.dm
index e2f478ba7834..73aab2fb8ca8 100644
--- a/code/datums/datum.dm
+++ b/code/datums/datum.dm
@@ -40,6 +40,11 @@
/// Datum level flags
var/datum_flags = NONE
+ /// A cached version of our \ref
+ /// The brunt of \ref costs are in creating entries in the string tree (a tree of immutable strings)
+ /// This avoids doing that more then once per datum by ensuring ref strings always have a reference to them after they're first pulled
+ var/cached_ref
+
/// A weak reference to another datum
var/datum/weakref/weak_reference
diff --git a/code/datums/mapgen/_biome.dm b/code/datums/mapgen/_biome.dm
index b5a35d953d3a..02c527b41042 100644
--- a/code/datums/mapgen/_biome.dm
+++ b/code/datums/mapgen/_biome.dm
@@ -1,17 +1,26 @@
/datum/biome
/// WEIGHTED list of open turfs that this biome can place
- var/open_turf_types = list(/turf/open/floor/plating/asteroid = 1)
+ var/list/open_turf_types = list(/turf/open/floor/plating/asteroid = 1)
+ /// EXPANDED (no values) list of open turfs that this biome can place
+ var/list/open_turf_types_expanded
/// WEIGHTED list of flora that this biome can spawn.
/// Flora do not have any local keep-away logic; all spawns are independent.
var/list/flora_spawn_list
+ /// EXPANDED (no values) list of flora that this biome can spawn.
+ var/list/flora_spawn_list_expanded
/// WEIGHTED list of features that this biome can spawn.
/// Features will not spawn within 7 tiles of other features of the same type.
var/list/feature_spawn_list
+ /// EXPANDED (no values) list of features that this biome can spawn.
+ var/list/feature_spawn_list_expanded
/// WEIGHTED list of mobs that this biome can spawn.
/// Mobs have multi-layered logic for determining if they can be spawned on a given tile.
/// Necropolis spawners etc. should go HERE, not in features, despite them not being mobs.
var/list/mob_spawn_list
+ /// EXPANDED (no values) list of mobs that this biome can spawn.
+ var/list/mob_spawn_list_expanded
+
/// Percentage chance that an open turf will attempt a flora spawn.
var/flora_spawn_chance = 2
@@ -20,6 +29,15 @@
/// Base percentage chance that an open turf will attempt a flora spawn.
var/mob_spawn_chance = 6
+/datum/biome/New()
+ open_turf_types_expanded = expand_weights(open_turf_types)
+ if(length(flora_spawn_list))
+ flora_spawn_list_expanded = expand_weights(flora_spawn_list)
+ if(length(feature_spawn_list))
+ feature_spawn_list_expanded = expand_weights(feature_spawn_list)
+ if(length(mob_spawn_list))
+ mob_spawn_list_expanded = expand_weights(mob_spawn_list)
+
/// Changes the passed turf according to the biome's internal logic, optionally using string_gen,
/// and adds it to the passed area.
/// The call to ChangeTurf respects changeturf_flags.
@@ -41,7 +59,7 @@
return TRUE
/datum/biome/proc/get_turf_type(turf/gen_turf, string_gen)
- return pickweight(open_turf_types)
+ return pick(open_turf_types_expanded)
/// Fills a turf with flora, features, and creatures based on the biome's variables.
/// The features and creatures compare against and add to the lists passed to determine
@@ -63,14 +81,14 @@
var/atom/spawned_mob
//FLORA SPAWNING HERE
- if(length(flora_spawn_list) && prob(flora_spawn_chance) && (a_flags & FLORA_ALLOWED))
- spawned_flora = pickweight(flora_spawn_list)
+ if(length(flora_spawn_list_expanded) && prob(flora_spawn_chance) && (a_flags & FLORA_ALLOWED))
+ spawned_flora = pick(flora_spawn_list_expanded)
spawned_flora = new spawned_flora(open_turf)
open_turf.flags_1 |= NO_LAVA_GEN_1
//FEATURE SPAWNING HERE
- if(length(feature_spawn_list) && prob(feature_spawn_chance) && (a_flags & FLORA_ALLOWED)) //checks the same flag because lol dunno
- var/atom/feature_type = pickweight(feature_spawn_list)
+ if(length(feature_spawn_list_expanded) && prob(feature_spawn_chance) && (a_flags & FLORA_ALLOWED)) //checks the same flag because lol dunno
+ var/atom/feature_type = pick(feature_spawn_list_expanded)
var/can_spawn = TRUE
for(var/other_feature in feature_list)
@@ -85,8 +103,8 @@
open_turf.flags_1 |= NO_LAVA_GEN_1
//MOB SPAWNING HERE
- if(length(mob_spawn_list) && !spawned_flora && !spawned_feature && prob(mob_spawn_chance) && (a_flags & MOB_SPAWN_ALLOWED))
- var/atom/picked_mob = pickweight(mob_spawn_list)
+ if(length(mob_spawn_list_expanded) && !spawned_flora && !spawned_feature && prob(mob_spawn_chance) && (a_flags & MOB_SPAWN_ALLOWED))
+ var/atom/picked_mob = pick(mob_spawn_list_expanded)
var/can_spawn = TRUE
for(var/thing in mob_list)
@@ -109,9 +127,15 @@
/datum/biome/cave
/// WEIGHTED list of closed turfs that this biome can place
- var/closed_turf_types = list(/turf/closed/mineral/random/volcanic = 1)
+ var/list/closed_turf_types = list(/turf/closed/mineral/random/volcanic = 1)
+ /// EXPANDED (no values) list of closed turfs that this biome can place
+ var/list/closed_turf_types_expanded
+
+/datum/biome/cave/New()
+ closed_turf_types_expanded = expand_weights(closed_turf_types)
+ return ..()
/datum/biome/cave/get_turf_type(turf/gen_turf, string_gen)
// gets the character in string_gen corresponding to gen_turf's coords. if it is nonzero,
// place a closed turf; otherwise place an open turf
- return pickweight(text2num(string_gen[world.maxx * (gen_turf.y - 1) + gen_turf.x]) ? closed_turf_types : open_turf_types)
+ return pick(text2num(string_gen[world.maxx * (gen_turf.y - 1) + gen_turf.x]) ? closed_turf_types_expanded : open_turf_types_expanded)
diff --git a/code/datums/mapgen/planetary/WasteGenerator.dm b/code/datums/mapgen/planetary/WasteGenerator.dm
index 8fdada12822e..d0243740bc3b 100644
--- a/code/datums/mapgen/planetary/WasteGenerator.dm
+++ b/code/datums/mapgen/planetary/WasteGenerator.dm
@@ -213,7 +213,7 @@
/datum/biome/waste/tar_bed/total
open_turf_types = list(
- /turf/open/water/tar/waste/lit
+ /turf/open/water/tar/waste/lit = 1
)
flora_spawn_chance = 0
@@ -340,7 +340,7 @@
/datum/biome/cave/waste/tar_bed/full
open_turf_types = list(
- /turf/open/water/tar/waste
+ /turf/open/water/tar/waste = 1
)
flora_spawn_chance = 0
diff --git a/code/datums/position_point_vector.dm b/code/datums/position_point_vector.dm
index 07fe0ed7652f..9675c337fcc8 100644
--- a/code/datums/position_point_vector.dm
+++ b/code/datums/position_point_vector.dm
@@ -22,7 +22,8 @@
/proc/angle_between_points(datum/point/a, datum/point/b)
return ATAN2((b.y - a.y), (b.x - a.x))
-/datum/position //For positions with map x/y/z and pixel x/y so you don't have to return lists. Could use addition/subtraction in the future I guess.
+/// For positions with map x/y/z and pixel x/y so you don't have to return lists. Could use addition/subtraction in the future I guess.
+/datum/position
var/x = 0
var/y = 0
var/z = 0
@@ -66,7 +67,8 @@
/datum/position/proc/return_point()
return new /datum/point(src)
-/datum/point //A precise point on the map in absolute pixel locations based on world.icon_size. Pixels are FROM THE EDGE OF THE MAP!
+/// A precise point on the map in absolute pixel locations based on world.icon_size. Pixels are FROM THE EDGE OF THE MAP!
+/datum/point
var/x = 0
var/y = 0
var/z = 0
@@ -80,7 +82,8 @@
p.z = z
return p
-/datum/point/New(_x, _y, _z, _pixel_x = 0, _pixel_y = 0) //first argument can also be a /datum/position or /atom.
+/// First argument can also be a /datum/position or /atom.
+/datum/point/New(_x, _y, _z, _pixel_x = 0, _pixel_y = 0)
if(istype(_x, /datum/position))
var/datum/position/P = _x
_x = P.x
@@ -107,7 +110,7 @@
/datum/point/proc/debug_out()
var/turf/T = return_turf()
- return "\ref[src] aX [x] aY [y] aZ [z] pX [return_px()] pY [return_py()] mX [T.x] mY [T.y] mZ [T.z]"
+ return "[text_ref(src)] aX [x] aY [y] aZ [z] pX [return_px()] pY [return_py()] mX [T.x] mY [T.y] mZ [T.z]"
/datum/point/proc/move_atom_to_src(atom/movable/AM)
AM.forceMove(return_turf())
@@ -130,10 +133,13 @@
return MODULUS(y, world.icon_size) - 16 - 1
/datum/point/vector
- var/speed = 32 //pixels per iteration
+ /// Pixels per iteration
+ var/speed = 32
var/iteration = 0
var/angle = 0
- var/mpx = 0 //calculated x/y movement amounts to prevent having to do trig every step.
+ /// Calculated x movement amounts to prevent having to do trig every step.
+ var/mpx = 0
+ /// Calculated y movement amounts to prevent having to do trig every step.
var/mpy = 0
var/starting_x = 0 //just like before, pixels from EDGE of map! This is set in initialize_location().
var/starting_y = 0
@@ -151,6 +157,15 @@
starting_y = y
starting_z = z
+/// Same effect as initiliaze_location, but without setting the starting_x/y/z
+/datum/point/vector/proc/set_location(tile_x, tile_y, tile_z, p_x = 0, p_y = 0)
+ if(!isnull(tile_x))
+ x = ((tile_x - 1) * world.icon_size) + world.icon_size * 0.5 + p_x + 1
+ if(!isnull(tile_y))
+ y = ((tile_y - 1) * world.icon_size) + world.icon_size * 0.5 + p_y + 1
+ if(!isnull(tile_z))
+ z = tile_z
+
/datum/point/vector/copy_to(datum/point/vector/v = new)
..(v)
v.speed = speed
diff --git a/code/datums/shuttles.dm b/code/datums/shuttles.dm
index 525ed09c33c9..48bedf89aeb9 100644
--- a/code/datums/shuttles.dm
+++ b/code/datums/shuttles.dm
@@ -66,7 +66,7 @@
// Finding the dir of the mobile port
var/dpos = cached_map.find_next_delimiter_position(model_text, start_pos, ",","{","}")
- var/cache_text = cached_map.trim_text(copytext(model_text, start_pos, dpos))
+ var/cache_text = trim_reduced(copytext(model_text, start_pos, dpos))
var/variables_start = findtext(cache_text, "{")
port_dir = NORTH // Incase something went wrong with variables from the cache
if(variables_start)
diff --git a/code/game/atoms.dm b/code/game/atoms.dm
index 4628bb22f92f..084736fb7069 100644
--- a/code/game/atoms.dm
+++ b/code/game/atoms.dm
@@ -168,7 +168,7 @@
*/
/atom/New(loc, ...)
//atom creation method that preloads variables at creation
- if(GLOB.use_preloader && (src.type == GLOB._preloader.target_path))//in case the instanciated atom is creating other atoms in New()
+ if(GLOB.use_preloader && src.type == GLOB._preloader_path)//in case the instanciated atom is creating other atoms in New()
world.preloader_load(src)
if(datum_flags & DF_USE_TAG)
diff --git a/code/game/gamemodes/dynamic/dynamic.dm b/code/game/gamemodes/dynamic/dynamic.dm
index 3b139bba78e9..81cd538a7e45 100644
--- a/code/game/gamemodes/dynamic/dynamic.dm
+++ b/code/game/gamemodes/dynamic/dynamic.dm
@@ -111,22 +111,22 @@ GLOBAL_VAR_INIT(dynamic_forced_threat_level, -1)
/datum/game_mode/dynamic/admin_panel()
var/list/dat = list("
Game Mode PanelGame Mode Panel
")
- dat += "Dynamic Mode \[VV\]\[Refresh\]
"
+ dat += "Dynamic Mode \[VV\]\[Refresh\]
"
dat += "Threat Level: [threat_level]
"
- dat += "Threat to Spend: [threat] \[Adjust\] \[View Log\]
"
+ dat += "Threat to Spend: [threat] \[Adjust\] \[View Log\]
"
dat += "
"
dat += "Parameters: centre = [GLOB.dynamic_curve_centre] ; width = [GLOB.dynamic_curve_width].
"
dat += "On average, [peaceful_percentage]% of the rounds are more peaceful.
"
- dat += "Forced extended: [GLOB.dynamic_forced_extended ? "On" : "Off"]
"
- dat += "Classic secret (only autotraitor): [GLOB.dynamic_classic_secret ? "On" : "Off"]
"
- dat += "No stacking (only one round-ender): [GLOB.dynamic_no_stacking ? "On" : "Off"]
"
- dat += "Stacking limit: [GLOB.dynamic_stacking_limit] \[Adjust\]"
+ dat += "Forced extended: [GLOB.dynamic_forced_extended ? "On" : "Off"]
"
+ dat += "Classic secret (only autotraitor): [GLOB.dynamic_classic_secret ? "On" : "Off"]
"
+ dat += "No stacking (only one round-ender): [GLOB.dynamic_no_stacking ? "On" : "Off"]
"
+ dat += "Stacking limit: [GLOB.dynamic_stacking_limit] \[Adjust\]"
dat += "
"
- dat += "\[Force Next Latejoin Ruleset\]
"
+ dat += "\[Force Next Latejoin Ruleset\]
"
if (forced_latejoin_rule)
- dat += {"-> [forced_latejoin_rule.name] <-
"}
- dat += "\[Execute Midround Ruleset\]
"
+ dat += {"-> [forced_latejoin_rule.name] <-
"}
+ dat += "\[Execute Midround Ruleset\]
"
dat += "
"
dat += "Executed rulesets: "
if (executed_rules.len > 0)
@@ -136,8 +136,8 @@ GLOBAL_VAR_INIT(dynamic_forced_threat_level, -1)
else
dat += "none.
"
dat += "
Injection Timers: ([get_injection_chance(TRUE)]% chance)
"
- dat += "Latejoin: [(latejoin_injection_cooldown-world.time)>60*10 ? "[round((latejoin_injection_cooldown-world.time)/60/10,0.1)] minutes" : "[(latejoin_injection_cooldown-world.time)] seconds"] \[Now!\]
"
- dat += "Midround: [(midround_injection_cooldown-world.time)>60*10 ? "[round((midround_injection_cooldown-world.time)/60/10,0.1)] minutes" : "[(midround_injection_cooldown-world.time)] seconds"] \[Now!\]
"
+ dat += "Latejoin: [(latejoin_injection_cooldown-world.time)>60*10 ? "[round((latejoin_injection_cooldown-world.time)/60/10,0.1)] minutes" : "[(latejoin_injection_cooldown-world.time)] seconds"] \[Now!\]
"
+ dat += "Midround: [(midround_injection_cooldown-world.time)>60*10 ? "[round((midround_injection_cooldown-world.time)/60/10,0.1)] minutes" : "[(midround_injection_cooldown-world.time)] seconds"] \[Now!\]
"
usr << browse(dat.Join(), "window=gamemode_panel;size=500x500")
/datum/game_mode/dynamic/Topic(href, href_list)
diff --git a/code/game/machinery/airlock_cycle_control.dm b/code/game/machinery/airlock_cycle_control.dm
index 0b35bedebd05..76094e803cd3 100644
--- a/code/game/machinery/airlock_cycle_control.dm
+++ b/code/game/machinery/airlock_cycle_control.dm
@@ -167,7 +167,7 @@
var/maxpressure = (exterior_pressure && (cyclestate == AIRLOCK_CYCLESTATE_OUTCLOSING || cyclestate == AIRLOCK_CYCLESTATE_OUTOPENING || cyclestate == AIRLOCK_CYCLESTATE_OUTOPEN)) ? exterior_pressure : interior_pressure
var/pressure_bars = round(pressure / maxpressure * 5 + 0.01)
- var/new_overlays_hash = "[pressure_bars]-[cyclestate]-[buildstage]-[panel_open]-[machine_stat]-[shorted]-[locked]-\ref[vis_target]"
+ var/new_overlays_hash = "[pressure_bars]-[cyclestate]-[buildstage]-[panel_open]-[machine_stat]-[shorted]-[locked]-[text_ref(vis_target)]"
if(use_hash && new_overlays_hash == overlays_hash)
return ..()
overlays_hash = new_overlays_hash
@@ -645,7 +645,7 @@
"airlocks" = list(),
"skip_timer" = (world.time - skip_timer),
"skip_delay" = skip_delay,
- "vis_target" = "\ref[vis_target]"
+ "vis_target" = "[text_ref(vis_target)]"
)
if((locked && !user.has_unlimited_silicon_privilege) || (user.has_unlimited_silicon_privilege && aidisabled))
@@ -661,7 +661,7 @@
var/obj/machinery/atmospherics/components/unary/vent_pump/vent = V
data["vents"] += list(list(
"role" = vents[vent],
- "vent_id" = "\ref[vent]",
+ "vent_id" = "[text_ref(vent)]",
"name" = vent.name
))
for(var/A in airlocks)
@@ -683,7 +683,7 @@
data["airlocks"] += list(list(
"role" = airlocks[airlock],
- "airlock_id" = "\ref[airlock]",
+ "airlock_id" = "[text_ref(airlock)]",
"name" = airlock.name,
"access" = access_str
))
diff --git a/code/game/objects/effects/decals/decal.dm b/code/game/objects/effects/decals/decal.dm
index e375cfd1117e..a3ad1f1af13d 100644
--- a/code/game/objects/effects/decals/decal.dm
+++ b/code/game/objects/effects/decals/decal.dm
@@ -46,15 +46,24 @@
var/detail_overlay
var/detail_color
-/obj/effect/turf_decal/Initialize()
- ..()
- return INITIALIZE_HINT_QDEL
+// This is with the intent of optimizing mapload
+// See spawners for more details since we use the same pattern
+// Basically rather then creating and deleting ourselves, why not just do the bare minimum?
+/obj/effect/turf_decal/Initialize(mapload)
+ SHOULD_CALL_PARENT(FALSE)
+ if(flags_1 & INITIALIZED_1)
+ stack_trace("Warning: [src]([type]) initialized multiple times!")
+ flags_1 |= INITIALIZED_1
-/obj/effect/turf_decal/ComponentInitialize()
- . = ..()
var/turf/T = loc
if(!istype(T)) //you know this will happen somehow
CRASH("Turf decal initialized in an object/nullspace")
T.AddElement(/datum/element/decal, icon, icon_state, dir, FALSE, color, null, null, alpha, FALSE)
if(detail_overlay)
T.AddElement(/datum/element/decal, icon, detail_overlay, dir, FALSE, detail_color, null, null, alpha, appearance_flags)
+ return INITIALIZE_HINT_QDEL
+
+/obj/effect/turf_decal/Destroy(force)
+ SHOULD_CALL_PARENT(FALSE)
+ moveToNullspace()
+ return QDEL_HINT_QUEUE
diff --git a/code/game/objects/effects/misc.dm b/code/game/objects/effects/misc.dm
index 6d5f840fcc68..b21c0b7073d5 100644
--- a/code/game/objects/effects/misc.dm
+++ b/code/game/objects/effects/misc.dm
@@ -21,6 +21,23 @@
/obj/effect/spawner
name = "object spawner"
+// Brief explanation:
+// Rather then setting up and then deleting spawners, we block all atomlike setup
+// and do the absolute bare minimum
+// This is with the intent of optimizing mapload
+/obj/effect/spawner/Initialize(mapload)
+ SHOULD_CALL_PARENT(FALSE)
+ if(flags_1 & INITIALIZED_1)
+ stack_trace("Warning: [src]([type]) initialized multiple times!")
+ flags_1 |= INITIALIZED_1
+
+ return INITIALIZE_HINT_QDEL
+
+/obj/effect/spawner/Destroy(force)
+ SHOULD_CALL_PARENT(FALSE)
+ moveToNullspace()
+ return QDEL_HINT_QUEUE
+
/obj/effect/list_container
name = "list container"
diff --git a/code/game/objects/effects/spawners/bombspawner.dm b/code/game/objects/effects/spawners/bombspawner.dm
index 914b910d9830..e1df4ff4ad18 100644
--- a/code/game/objects/effects/spawners/bombspawner.dm
+++ b/code/game/objects/effects/spawners/bombspawner.dm
@@ -37,8 +37,6 @@
V.update_appearance()
- return INITIALIZE_HINT_QDEL
-
/obj/effect/spawner/newbomb/timer/syndicate/Initialize()
temp_p = (OPTIMAL_TEMP_K_PLA_BURN_SCALE(pressure_p, pressure_o, temp_o)/2 + OPTIMAL_TEMP_K_PLA_BURN_RATIO(pressure_p, pressure_o, temp_o)/2) - T0C
. = ..()
diff --git a/code/game/objects/effects/spawners/bundle.dm b/code/game/objects/effects/spawners/bundle.dm
index 9efafeabf86a..19e7b1c957fa 100644
--- a/code/game/objects/effects/spawners/bundle.dm
+++ b/code/game/objects/effects/spawners/bundle.dm
@@ -7,11 +7,10 @@
var/list/items
/obj/effect/spawner/bundle/Initialize(mapload)
- ..()
+ . = ..()
if(items && items.len)
for(var/path in items)
new path(loc)
- return INITIALIZE_HINT_QDEL
/obj/effect/spawner/bundle/costume/chicken
name = "chicken costume spawner"
diff --git a/code/game/objects/effects/spawners/lootdrop.dm b/code/game/objects/effects/spawners/lootdrop.dm
index e43ffd7cb182..1e349fd17abb 100644
--- a/code/game/objects/effects/spawners/lootdrop.dm
+++ b/code/game/objects/effects/spawners/lootdrop.dm
@@ -8,7 +8,7 @@
var/fan_out_items = FALSE //Whether the items should be distributed to offsets 0,1,-1,2,-2,3,-3.. This overrides pixel_x/y on the spawner itself
/obj/effect/spawner/lootdrop/Initialize(mapload)
- ..()
+ . = ..()
if(loot && loot.len)
var/loot_spawned = 0
while((lootcount-loot_spawned) && loot.len)
@@ -31,7 +31,6 @@
else
break // WS edit - Support spawn weights of 0 in loot tables and ruins
loot_spawned++
- return INITIALIZE_HINT_QDEL
/obj/effect/spawner/lootdrop/donkpockets
name = "donk pocket box spawner"
diff --git a/code/game/objects/effects/spawners/structure.dm b/code/game/objects/effects/spawners/structure.dm
index 9ce3411cc93a..ec893399630b 100644
--- a/code/game/objects/effects/spawners/structure.dm
+++ b/code/game/objects/effects/spawners/structure.dm
@@ -15,11 +15,8 @@ INITIALIZE_IMMEDIATE(/obj/effect/spawner/structure)
/obj/effect/spawner/structure/Initialize()
. = ..()
- if(spawn_list && spawn_list.len)
- for(var/I in spawn_list)
- new I(get_turf(src))
- return INITIALIZE_HINT_QDEL
-
+ for(var/spawn_type in spawn_list)
+ new spawn_type(loc)
//normal windows
diff --git a/code/game/objects/effects/spawners/traps.dm b/code/game/objects/effects/spawners/traps.dm
index 731b4efc1d98..0409d9944b9b 100644
--- a/code/game/objects/effects/spawners/traps.dm
+++ b/code/game/objects/effects/spawners/traps.dm
@@ -4,7 +4,6 @@
icon_state = "trap_rand"
/obj/effect/spawner/trap/Initialize(mapload)
- ..()
+ . = ..()
var/new_type = pick(subtypesof(/obj/structure/trap) - typesof(/obj/structure/trap/ctf))
new new_type(get_turf(src))
- return INITIALIZE_HINT_QDEL
diff --git a/code/game/objects/effects/spawners/xeno_egg_delivery.dm b/code/game/objects/effects/spawners/xeno_egg_delivery.dm
index d0e99d0f9036..99eac4828932 100644
--- a/code/game/objects/effects/spawners/xeno_egg_delivery.dm
+++ b/code/game/objects/effects/spawners/xeno_egg_delivery.dm
@@ -5,7 +5,7 @@
var/announcement_time = 1200
/obj/effect/spawner/xeno_egg_delivery/Initialize(mapload)
- ..()
+ . = ..()
var/turf/T = get_turf(src)
new /obj/structure/alien/egg(T)
@@ -16,4 +16,3 @@
log_game("An alien egg has been delivered to [AREACOORD(T)]")
var/message = "Attention [station_name()], we have entrusted you with a research specimen in [get_area_name(T, TRUE)]. Remember to follow all safety precautions when dealing with the specimen."
SSticker.OnRoundstart(CALLBACK(GLOBAL_PROC, /proc/_addtimer, CALLBACK(GLOBAL_PROC, /proc/print_command_report, message), announcement_time))
- return INITIALIZE_HINT_QDEL
diff --git a/code/game/objects/items/stacks/stack.dm b/code/game/objects/items/stacks/stack.dm
index ec750eb66aca..0811e39fbe67 100644
--- a/code/game/objects/items/stacks/stack.dm
+++ b/code/game/objects/items/stacks/stack.dm
@@ -156,7 +156,7 @@
"res_amount" = R.res_amount,
"max_res_amount" = R.max_res_amount,
"req_amount" = R.req_amount,
- "ref" = "\ref[R]",
+ "ref" = text_ref(R),
)
/**
diff --git a/code/modules/admin/admin.dm b/code/modules/admin/admin.dm
index a530012270ae..82cb857576c1 100644
--- a/code/modules/admin/admin.dm
+++ b/code/modules/admin/admin.dm
@@ -437,7 +437,7 @@
dat += "(Force Roundstart Rulesets)
"
if (GLOB.dynamic_forced_roundstart_ruleset.len > 0)
for(var/datum/dynamic_ruleset/roundstart/rule in GLOB.dynamic_forced_roundstart_ruleset)
- dat += {"-> [rule.name] <-
"}
+ dat += {"-> [rule.name] <-
"}
dat += "(Clear Rulesets)
"
dat += "(Dynamic mode options)
"
dat += "
"
diff --git a/code/modules/admin/sound_emitter.dm b/code/modules/admin/sound_emitter.dm
index 2c930034967f..bdf2a4e6aa34 100644
--- a/code/modules/admin/sound_emitter.dm
+++ b/code/modules/admin/sound_emitter.dm
@@ -58,16 +58,16 @@
/obj/effect/sound_emitter/proc/edit_emitter(mob/user)
var/dat = ""
- dat += "Label: [maptext ? maptext : "No label set!"]
"
+ dat += "Label: [maptext ? maptext : "No label set!"]
"
dat += "
"
- dat += "Sound File: [sound_file ? sound_file : "No file chosen!"]
"
- dat += "Volume: [sound_volume]%
"
+ dat += "Sound File: [sound_file ? sound_file : "No file chosen!"]
"
+ dat += "Volume: [sound_volume]%
"
dat += "
"
- dat += "Mode: [motus_operandi]
"
+ dat += "Mode: [motus_operandi]
"
if(motus_operandi != SOUND_EMITTER_LOCAL)
- dat += "Range: [emitter_range][emitter_range == SOUND_EMITTER_RADIUS ? "[play_radius]-tile radius" : ""]
"
+ dat += "Range: [emitter_range][emitter_range == SOUND_EMITTER_RADIUS ? "[play_radius]-tile radius" : ""]
"
dat += "
"
- dat += "Play Sound (interrupts other sound emitter sounds)"
+ dat += "Play Sound (interrupts other sound emitter sounds)"
var/datum/browser/popup = new(user, "emitter", "", 500, 600)
popup.set_content(dat)
popup.open()
diff --git a/code/modules/admin/view_variables/reference_tracking.dm b/code/modules/admin/view_variables/reference_tracking.dm
index 69d3a5d1541b..a9a84986416d 100644
--- a/code/modules/admin/view_variables/reference_tracking.dm
+++ b/code/modules/admin/view_variables/reference_tracking.dm
@@ -106,11 +106,11 @@
found_refs[varname] = TRUE
continue //End early, don't want these logging
#endif
- log_reftracker("Found [type] \ref[src] in [datum_container.type]'s \ref[datum_container] [varname] var. [container_name]")
+ log_reftracker("Found [type] [text_ref(src)] in [datum_container.type]'s [text_ref(datum_container)] [varname] var. [container_name]")
continue
if(islist(variable))
- DoSearchVar(variable, "[container_name] \ref[datum_container] -> [varname] (list)", recursive_limit - 1, search_time)
+ DoSearchVar(variable, "[container_name] [text_ref(datum_container)] -> [varname] (list)", recursive_limit - 1, search_time)
else if(islist(potential_container))
var/normal = IS_NORMAL_LIST(potential_container)
@@ -126,7 +126,7 @@
found_refs[potential_cache] = TRUE
continue //End early, don't want these logging
#endif
- log_reftracker("Found [type] \ref[src] in list [container_name].")
+ log_reftracker("Found [type] [text_ref(src)] in list [container_name].")
continue
var/assoc_val = null
@@ -139,7 +139,7 @@
found_refs[potential_cache] = TRUE
continue //End early, don't want these logging
#endif
- log_reftracker("Found [type] \ref[src] in list [container_name]\[[element_in_list]\]")
+ log_reftracker("Found [type] [text_ref(src)] in list [container_name]\[[element_in_list]\]")
continue
//We need to run both of these checks, since our object could be hiding in either of them
//Check normal sublists
@@ -153,7 +153,7 @@
thing_to_del.qdel_and_find_ref_if_fail(force)
/datum/proc/qdel_and_find_ref_if_fail(force = FALSE)
- SSgarbage.reference_find_on_fail["\ref[src]"] = TRUE
+ SSgarbage.reference_find_on_fail[text_ref(src)] = TRUE
qdel(src, force)
#endif
diff --git a/code/modules/antagonists/borer/borer.dm b/code/modules/antagonists/borer/borer.dm
index 4fc5a6aab8f1..ca9b6af1b106 100644
--- a/code/modules/antagonists/borer/borer.dm
+++ b/code/modules/antagonists/borer/borer.dm
@@ -136,7 +136,7 @@ GLOBAL_VAR_INIT(total_borer_hosts_needed, 3)
. = ..()
generation = gen
if(is_team_borer)
- notify_ghosts("A cortical borer has been created in [get_area(src)]!", enter_link = "(Click to enter)", source = src, action = NOTIFY_ATTACK)
+ notify_ghosts("A cortical borer has been created in [get_area(src)]!", enter_link = "(Click to enter)", source = src, action = NOTIFY_ATTACK)
var/numeral = rand(1000, 9999)
real_name = "Cortical Borer [numeral]"
truename = "[borer_names[min(generation, borer_names.len)]] [numeral]"
@@ -200,7 +200,7 @@ GLOBAL_VAR_INIT(total_borer_hosts_needed, 3)
chemicals -= C.chemuse
log_game("[src]/([src.ckey]) has injected [C.chemname] ([C.chem]) into their host [victim]/([victim.ckey])")
- src << output(chemicals, "ViewBorer\ref[src]Chems.browser:update_chemicals")
+ src << output(chemicals, "ViewBorer[text_ref(src)]Chems.browser:update_chemicals")
..()
@@ -235,7 +235,7 @@ GLOBAL_VAR_INIT(total_borer_hosts_needed, 3)
if(statpanel("Status"))
stat(null, "Chemicals: [chemicals]")
- src << output(chemicals, "ViewBorer\ref[src]Chems.browser:update_chemicals")
+ src << output(chemicals, "ViewBorer[text_ref(src)]Chems.browser:update_chemicals")
/mob/living/simple_animal/borer/verb/Communicate()
set category = "Borer"
@@ -484,13 +484,13 @@ GLOBAL_VAR_INIT(total_borer_hosts_needed, 3)
for(var/datum in typesof(/datum/borer_chem))
var/datum/borer_chem/C = new datum()
if(C.chem)
- content += "[C.chemname] ([C.quantity]u, takes [C.chemuse] chemical) [C.chem_desc] |
"
+ content += "[C.chemname] ([C.quantity]u, takes [C.chemuse] chemical) [C.chem_desc] |
"
content += ""
var/html = get_html_template(content)
- usr << browse(null, "window=ViewBorer\ref[src]Chems;size=600x800")
- usr << browse(html, "window=ViewBorer\ref[src]Chems;size=600x800")
+ usr << browse(null, "window=ViewBorer[text_ref(src)]Chems;size=600x800")
+ usr << browse(html, "window=ViewBorer[text_ref(src)]Chems;size=600x800")
return
diff --git a/code/modules/events/holiday/xmas.dm b/code/modules/events/holiday/xmas.dm
index f1a36affcd3b..f38d21b868c4 100644
--- a/code/modules/events/holiday/xmas.dm
+++ b/code/modules/events/holiday/xmas.dm
@@ -49,15 +49,13 @@
var/festive_tree = /obj/structure/flora/tree/pine/xmas
var/christmas_tree = /obj/structure/flora/tree/pine/xmas/presents
-/obj/effect/spawner/xmastree/Initialize()
- ..()
+/obj/effect/spawner/xmastree/Initialize(mapload)
+ . = ..()
if((CHRISTMAS in SSevents.holidays) && christmas_tree)
new christmas_tree(get_turf(src))
else if((FESTIVE_SEASON in SSevents.holidays) && festive_tree)
new festive_tree(get_turf(src))
- return INITIALIZE_HINT_QDEL
-
/obj/effect/spawner/xmastree/rdrod
name = "festivus pole spawner"
festive_tree = /obj/structure/festivus
diff --git a/code/modules/holodeck/computer.dm b/code/modules/holodeck/computer.dm
index ea07e2f70b27..c7a911b6cf3f 100644
--- a/code/modules/holodeck/computer.dm
+++ b/code/modules/holodeck/computer.dm
@@ -183,7 +183,7 @@ and clear when youre done! if you dont i will use :newspaper2: on you
var/turf/possible_blacklist = _turf
if (possible_blacklist.holodeck_compatible)
continue
- input_blacklist += possible_blacklist
+ input_blacklist[possible_blacklist] = TRUE
///loads the template whose id string it was given ("offline_program" loads datum/map_template/holodeck/offline)
/obj/machinery/computer/holodeck/proc/load_program(map_id, force = FALSE, add_delay = TRUE)
diff --git a/code/modules/language/language_holder.dm b/code/modules/language/language_holder.dm
index 1f42ec424706..61570535cbbf 100644
--- a/code/modules/language/language_holder.dm
+++ b/code/modules/language/language_holder.dm
@@ -55,7 +55,7 @@ Key procs
/// Initializes, and copies in the languages from the current atom if available.
/datum/language_holder/New(atom/_owner)
if(_owner && QDELETED(_owner))
- CRASH("Langauge holder added to a qdeleting thing, what the fuck \ref[_owner]")
+ CRASH("Langauge holder added to a qdeleting thing, what the fuck [text_ref(_owner)]")
owner = _owner
if(istype(owner, /datum/mind))
var/datum/mind/M = owner
diff --git a/code/modules/mapping/map_template.dm b/code/modules/mapping/map_template.dm
index 39d443929e21..b3f5b1078b20 100644
--- a/code/modules/mapping/map_template.dm
+++ b/code/modules/mapping/map_template.dm
@@ -159,6 +159,7 @@
var/list/turf_blacklist = list()
update_blacklist(T, turf_blacklist)
+ UNSETEMPTY(turf_blacklist)
parsed.turf_blacklist = turf_blacklist
if(!parsed.load(T.x, T.y, T.z, cropMap=TRUE, no_changeturf=(SSatoms.initialized == INITIALIZATION_INSSATOMS), placeOnTop=should_place_on_top))
return
diff --git a/code/modules/mapping/preloader.dm b/code/modules/mapping/preloader.dm
index 4b61663f668e..59480159f1c8 100644
--- a/code/modules/mapping/preloader.dm
+++ b/code/modules/mapping/preloader.dm
@@ -1,6 +1,7 @@
// global datum that will preload variables on atoms instanciation
GLOBAL_VAR_INIT(use_preloader, FALSE)
-GLOBAL_DATUM_INIT(_preloader, /datum/map_preloader, new)
+GLOBAL_LIST_INIT(_preloader_attributes, null)
+GLOBAL_LIST_INIT(_preloader_path, null)
/// Preloader datum
/datum/map_preloader
@@ -11,15 +12,14 @@ GLOBAL_DATUM_INIT(_preloader, /datum/map_preloader, new)
/world/proc/preloader_setup(list/the_attributes, path)
if(the_attributes.len)
GLOB.use_preloader = TRUE
- var/datum/map_preloader/preloader_local = GLOB._preloader
- preloader_local.attributes = the_attributes
- preloader_local.target_path = path
+ GLOB._preloader_attributes = the_attributes
+ GLOB._preloader_path = path
/world/proc/preloader_load(atom/what)
GLOB.use_preloader = FALSE
- var/datum/map_preloader/preloader_local = GLOB._preloader
- for(var/attribute in preloader_local.attributes)
- var/value = preloader_local.attributes[attribute]
+ var/list/attributes = GLOB._preloader_attributes
+ for(var/attribute in attributes)
+ var/value = attributes[attribute]
if(islist(value))
value = deepCopyList(value)
#ifdef TESTING
diff --git a/code/modules/mapping/reader.dm b/code/modules/mapping/reader.dm
index 59c0bb14dbc4..9e12fb7d4ce7 100644
--- a/code/modules/mapping/reader.dm
+++ b/code/modules/mapping/reader.dm
@@ -11,9 +11,13 @@
/datum/parsed_map
var/original_path
+ /// The length of a key in this file. This is promised by the standard to be static
var/key_len = 0
var/list/grid_models = list()
var/list/gridSets = list()
+ /// List of area types we've loaded AS A PART OF THIS MAP
+ /// We do this to allow non unique areas, so we'll only load one per map
+ var/list/area/loaded_areas = list()
var/list/modelCache
@@ -23,19 +27,21 @@
var/list/bounds
var/did_expand = FALSE
- ///any turf in this list is skipped inside of build_coordinate
- var/list/turf_blacklist = list()
+ ///any turf in this list is skipped inside of build_coordinate. Lazy assoc list
+ var/list/turf_blacklist
// raw strings used to represent regexes more accurately
// '' used to avoid confusing syntax highlighting
var/static/regex/dmmRegex = new(@'"([a-zA-Z]+)" = \(((?:.|\n)*?)\)\n(?!\t)|\((\d+),(\d+),(\d+)\) = \{"([a-zA-Z\n]*)"\}', "g")
- var/static/regex/trimQuotesRegex = new(@'^[\s\n]+"?|"?[\s\n]+$|^"|"$', "g")
var/static/regex/trimRegex = new(@'^[\s\n]+|[\s\n]+$', "g")
#ifdef TESTING
var/turfsSkipped = 0
#endif
+//text trimming (both directions) helper macro
+#define TRIM_TEXT(text) (trim_reduced(text))
+
/// Shortcut function to parse a map and apply it to the world.
///
/// - `dmm_file`: A .dmm file to load (Required).
@@ -53,6 +59,9 @@
/// Parse a map, possibly cropping it.
/datum/parsed_map/New(tfile, x_lower = -INFINITY, x_upper = INFINITY, y_lower = -INFINITY, y_upper=INFINITY, measureOnly=FALSE)
+ // This proc sleeps for like 6 seconds. why?
+ // Is it file accesses? if so, can those be done ahead of time, async to save on time here? I wonder.
+ // Love ya :)
if(isfile(tfile))
original_path = "[tfile]"
tfile = file2text(tfile)
@@ -60,16 +69,23 @@
// create a new datum without loading a map
return
- bounds = parsed_bounds = list(1.#INF, 1.#INF, 1.#INF, -1.#INF, -1.#INF, -1.#INF)
- var/stored_index = 1
+ src.bounds = parsed_bounds = list(1.#INF, 1.#INF, 1.#INF, -1.#INF, -1.#INF, -1.#INF)
+ // lists are structs don't you know :)
+ var/list/bounds = src.bounds
+ var/list/grid_models = src.grid_models
+ var/key_len = src.key_len
+ var/stored_index = 1
+ var/list/regexOutput
//multiz lool
while(dmmRegex.Find(tfile, stored_index))
stored_index = dmmRegex.next
+ // Datum var lookup is expensive, this isn't
+ regexOutput = dmmRegex.group
// "aa" = (/type{vars=blah})
- if(dmmRegex.group[1]) // Model
- var/key = dmmRegex.group[1]
+ if(regexOutput[1]) // Model
+ var/key = regexOutput[1]
if(grid_models[key]) // Duplicate model keys are ignored in DMMs
continue
if(key_len != length(key))
@@ -78,14 +94,14 @@
else
CRASH("Inconsistent key length in DMM")
if(!measureOnly)
- grid_models[key] = dmmRegex.group[2]
+ grid_models[key] = regexOutput[2]
// (1,1,1) = {"aaaaaaaaaaaaaaaaaaaaaaaaaaaaaa"}
- else if(dmmRegex.group[3]) // Coords
+ else if(regexOutput[3]) // Coords
if(!key_len)
CRASH("Coords before model definition in DMM")
- var/curr_x = text2num(dmmRegex.group[3])
+ var/curr_x = text2num(regexOutput[3])
if(curr_x < x_lower || curr_x > x_upper)
continue
@@ -94,69 +110,96 @@
gridSet.xcrd = curr_x
//position of the currently processed square
- gridSet.ycrd = text2num(dmmRegex.group[4])
- gridSet.zcrd = text2num(dmmRegex.group[5])
+ gridSet.ycrd = text2num(regexOutput[4])
+ gridSet.zcrd = text2num(regexOutput[5])
- bounds[MAP_MINX] = min(bounds[MAP_MINX], clamp(gridSet.xcrd, x_lower, x_upper))
+ bounds[MAP_MINX] = min(bounds[MAP_MINX], curr_x)
bounds[MAP_MINZ] = min(bounds[MAP_MINZ], gridSet.zcrd)
bounds[MAP_MAXZ] = max(bounds[MAP_MAXZ], gridSet.zcrd)
- var/list/gridLines = splittext(dmmRegex.group[6], "\n")
+ var/list/gridLines = splittext(regexOutput[6], "\n")
gridSet.gridLines = gridLines
var/leadingBlanks = 0
- while(leadingBlanks < gridLines.len && gridLines[++leadingBlanks] == "")
+ while(leadingBlanks < length(gridLines) && gridLines[++leadingBlanks] == "")
if(leadingBlanks > 1)
gridLines.Cut(1, leadingBlanks) // Remove all leading blank lines.
- if(!gridLines.len) // Skip it if only blank lines exist.
+ if(!length(gridLines)) // Skip it if only blank lines exist.
continue
gridSets += gridSet
- if(gridLines.len && gridLines[gridLines.len] == "")
- gridLines.Cut(gridLines.len) // Remove only one blank line at the end.
+ if(gridLines[length(gridLines)] == "")
+ gridLines.Cut(length(gridLines)) // Remove only one blank line at the end.
- bounds[MAP_MINY] = min(bounds[MAP_MINY], clamp(gridSet.ycrd, y_lower, y_upper))
- gridSet.ycrd += gridLines.len - 1 // Start at the top and work down
- bounds[MAP_MAXY] = max(bounds[MAP_MAXY], clamp(gridSet.ycrd, y_lower, y_upper))
+ bounds[MAP_MINY] = min(bounds[MAP_MINY], gridSet.ycrd)
+ gridSet.ycrd += length(gridLines) - 1 // Start at the top and work down
+ bounds[MAP_MAXY] = max(bounds[MAP_MAXY], gridSet.ycrd)
- var/maxx = gridSet.xcrd
- if(gridLines.len) //Not an empty map
- maxx = max(maxx, gridSet.xcrd + length(gridLines[1]) / key_len - 1)
+ var/maxx = curr_x
+ if(length(gridLines)) //Not an empty map
+ maxx = max(maxx, curr_x + length(gridLines[1]) / key_len - 1)
- bounds[MAP_MAXX] = clamp(max(bounds[MAP_MAXX], maxx), x_lower, x_upper)
+ bounds[MAP_MAXX] = max(bounds[MAP_MAXX], maxx)
CHECK_TICK
// Indicate failure to parse any coordinates by nulling bounds
if(bounds[1] == 1.#INF)
- bounds = null
- parsed_bounds = bounds
+ src.bounds = null
+ else
+ // Clamp all our mins and maxes down to the proscribed limits
+ bounds[MAP_MINX] = clamp(bounds[MAP_MINX], x_lower, x_upper)
+ bounds[MAP_MAXX] = clamp(bounds[MAP_MAXX], x_lower, x_upper)
+ bounds[MAP_MINY] = clamp(bounds[MAP_MINY], y_lower, y_upper)
+ bounds[MAP_MAXY] = clamp(bounds[MAP_MAXY], y_lower, y_upper)
+
+ parsed_bounds = src.bounds
+ src.key_len = key_len
/// Load the parsed map into the world. See [/proc/load_map] for arguments.
-/datum/parsed_map/proc/load(x_offset, y_offset, z_offset, cropMap, no_changeturf, x_lower, x_upper, y_lower, y_upper, placeOnTop)
+/datum/parsed_map/proc/load(x_offset, y_offset, z_offset, cropMap, no_changeturf, x_lower, x_upper, y_lower, y_upper, placeOnTop, whitelist = FALSE)
//How I wish for RAII
Master.StartLoadingMap()
. = _load_impl(x_offset, y_offset, z_offset, cropMap, no_changeturf, x_lower, x_upper, y_lower, y_upper, placeOnTop)
Master.StopLoadingMap()
+
+#define MAPLOADING_CHECK_TICK \
+ if(TICK_CHECK) { \
+ SSatoms.map_loader_stop(); \
+ stoplag(); \
+ SSatoms.map_loader_begin(); \
+ }
// Do not call except via load() above.
/datum/parsed_map/proc/_load_impl(x_offset = 1, y_offset = 1, z_offset = world.maxz + 1, cropMap = FALSE, no_changeturf = FALSE, x_lower = -INFINITY, x_upper = INFINITY, y_lower = -INFINITY, y_upper = INFINITY, placeOnTop = FALSE)
PRIVATE_PROC(TRUE)
- var/list/areaCache = list()
var/list/modelCache = build_cache(no_changeturf)
var/space_key = modelCache[SPACE_KEY]
var/list/bounds
+ var/key_len = src.key_len
src.bounds = bounds = list(1.#INF, 1.#INF, 1.#INF, -1.#INF, -1.#INF, -1.#INF)
- for(var/I in gridSets)
- var/datum/grid_set/gset = I
- var/ycrd = gset.ycrd + y_offset - 1
+ // Tell ss atoms that we're doing maploading
+ // We'll have to account for this in the following tick_checks so it doesn't overflow
+ SSatoms.map_loader_begin()
+
+ //used for sending the maxx and maxy expanded global signals at the end of this proc
+ var/has_expanded_world_maxx = FALSE
+ var/has_expanded_world_maxy = FALSE
+ var/y_relative_to_absolute = y_offset - 1
+ var/x_relative_to_absolute = x_offset - 1
+ for(var/datum/grid_set/gset as anything in gridSets)
+ var/relative_x = gset.xcrd
+ var/relative_y = gset.ycrd
+ var/true_xcrd = relative_x + x_relative_to_absolute
+ var/ycrd = relative_y + y_relative_to_absolute
var/zcrd = gset.zcrd + z_offset - 1
if(!cropMap && ycrd > world.maxy)
world.maxy = ycrd // Expand Y here. X is expanded in the loop below
did_expand = TRUE
var/zexpansion = zcrd > world.maxz
+ var/no_afterchange = no_changeturf
if(zexpansion)
if(cropMap)
continue
@@ -166,50 +209,141 @@
did_expand = FALSE
if(!no_changeturf)
WARNING("Z-level expansion occurred without no_changeturf set, this may cause problems when /turf/AfterChange is called")
-
- for(var/line in gset.gridLines)
- if((ycrd - y_offset + 1) < y_lower || (ycrd - y_offset + 1) > y_upper) //Reverse operation and check if it is out of bounds of cropping.
- --ycrd
+ no_afterchange = TRUE
+ // Ok so like. something important
+ // We talk in "relative" coords here, so the coordinate system of the map datum
+ // This is so we can do offsets, but it is NOT the same as positions in game
+ // That's why there's some uses of - y_relative_to_absolute here, to turn absolute positions into relative ones
+
+ // Skip Y coords that are above the smallest of the three params
+ // So maxy and y_upper get to act as thresholds, and relative_y can play
+ var/y_skip_above = min(world.maxy - y_relative_to_absolute, y_upper, relative_y)
+ // How many lines to skip because they'd be above the y cuttoff line
+ var/y_starting_skip = relative_y - y_skip_above
+ ycrd += y_starting_skip
+
+ // Y is the LOWEST it will ever be here, so we can easily set a threshold for how low to go
+ var/line_count = length(gset.gridLines)
+ var/lowest_y = relative_y - (line_count - 1) // -1 because we decrement at the end of the loop, not the start
+ var/y_ending_skip = max(max(y_lower, 1 - y_relative_to_absolute) - lowest_y, 0)
+
+ // Now we're gonna precompute the x thresholds
+ // We skip all the entries below the lower x, or 1
+ var/starting_x_delta = max(max(x_lower, 1 - x_relative_to_absolute) - relative_x, 0)
+ // The x loop counts by key length, so we gotta multiply here
+ var/x_starting_skip = starting_x_delta * key_len
+ true_xcrd += starting_x_delta
+
+ var/line_length = 0
+ if(line_count)
+ // This is promised as static, so we will treat it as such
+ line_length = length(gset.gridLines[1])
+ // We're gonna skip all the entries above the upper x, or maxx if cropMap is set
+ var/x_target = line_length - key_len + 1
+ var/x_step_count = ROUND_UP(x_target / key_len)
+ var/final_x = relative_x + (x_step_count - 1)
+ var/x_delta_with = x_upper
+ if(cropMap)
+ // Take our smaller crop threshold yes?
+ x_delta_with = min(x_delta_with, world.maxx)
+ if(final_x > x_delta_with)
+ // If our relative x is greater then X upper, well then we've gotta limit our expansion
+ var/delta = max(final_x - x_delta_with, 0)
+ x_step_count -= delta
+ final_x -= delta
+ x_target = x_step_count * key_len
+ if(final_x > world.maxx && !cropMap)
+ world.maxx = final_x
+ has_expanded_world_maxx = TRUE
+
+ // We're gonna track the first and last pairs of coords we find
+ // The first x is guarenteed to be the lowest, the first y the highest, and vis versa
+ // This is faster then doing mins and maxes inside the hot loop below
+ var/first_found = FALSE
+ var/first_x = 0
+ var/first_y = 0
+ var/last_x = 0
+ var/last_y = 0
+
+ // Everything following this line is VERY hot. How hot depends on the map format
+ // (Yes this does mean dmm is technically faster to parse. shut up)
+
+ // This is the "is this map tgm" check
+ if(key_len == line_length)
+ // Wanna clear something up about maps, talking in 255x255 here
+ // In the tgm format, each gridset contains 255 lines, each line representing one tile, with 255 total gridsets
+ // In the dmm format, each gridset contains 255 lines, each line representing one row of tiles, containing 255 * line length characters, with one gridset per z
+ // since this is the tgm branch any cutoff of x means we just shouldn't iterate this gridset
+ if(!x_step_count || x_starting_skip)
continue
- if(ycrd <= world.maxy && ycrd >= 1)
- var/xcrd = gset.xcrd + x_offset - 1
- for(var/tpos = 1 to length(line) - key_len + 1 step key_len)
- if((xcrd - x_offset + 1) < x_lower || (xcrd - x_offset + 1) > x_upper) //Same as above.
- ++xcrd
- continue //X cropping.
- if(xcrd > world.maxx)
- if(cropMap)
- break
- else
- world.maxx = xcrd
- did_expand = TRUE
-
- 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))
- var/list/cache = modelCache[model_key]
- if(!cache)
- CRASH("Undefined model key in DMM: [model_key]")
- build_coordinate(areaCache, cache, locate(xcrd, ycrd, zcrd), no_afterchange, placeOnTop)
-
- // only bother with bounds that actually exist
- bounds[MAP_MINX] = min(bounds[MAP_MINX], xcrd)
- bounds[MAP_MINY] = min(bounds[MAP_MINY], ycrd)
- bounds[MAP_MINZ] = min(bounds[MAP_MINZ], zcrd)
- bounds[MAP_MAXX] = max(bounds[MAP_MAXX], xcrd)
- bounds[MAP_MAXY] = max(bounds[MAP_MAXY], ycrd)
- bounds[MAP_MAXZ] = max(bounds[MAP_MAXZ], zcrd)
+ for(var/i in 1 + y_starting_skip to line_count - y_ending_skip)
+ var/line = gset.gridLines[i]
+ if(line == space_key && no_afterchange)
+ #ifdef TESTING
+ ++turfsSkipped
+ #endif
+ ycrd--
+ MAPLOADING_CHECK_TICK
+ continue
+
+ var/list/cache = modelCache[line]
+ if(!cache)
+ SSatoms.map_loader_stop()
+ CRASH("Undefined model key in DMM: [line]")
+ build_coordinate(cache, locate(true_xcrd, ycrd, zcrd), no_afterchange, placeOnTop)
+
+ // only bother with bounds that actually exist
+ if(!first_found)
+ first_found = TRUE
+ first_y = ycrd
+ last_y = ycrd
+ ycrd--
+ MAPLOADING_CHECK_TICK
+ // The x coord never changes, so this is safe
+ if(first_found)
+ first_x = true_xcrd
+ last_x = true_xcrd
+ else
+ // This is the dmm parser, note the double loop
+ for(var/i in 1 + y_starting_skip to line_count - y_ending_skip)
+ var/line = gset.gridLines[i]
+
+ var/xcrd = true_xcrd
+ for(var/tpos in 1 + x_starting_skip to x_target step key_len)
+ var/model_key = copytext(line, tpos, tpos + key_len)
+ if(model_key == space_key && no_afterchange)
#ifdef TESTING
- else
- ++turfsSkipped
+ ++turfsSkipped
#endif
- CHECK_TICK
+ MAPLOADING_CHECK_TICK
+ ++xcrd
+ continue
+ var/list/cache = modelCache[model_key]
+ if(!cache)
+ SSatoms.map_loader_stop()
+ CRASH("Undefined model key in DMM: [model_key]")
+ build_coordinate(cache, locate(xcrd, ycrd, zcrd), no_afterchange, placeOnTop)
+
+ // only bother with bounds that actually exist
+ if(!first_found)
+ first_found = TRUE
+ first_x = xcrd
+ first_y = ycrd
+ last_x = xcrd
+ last_y = ycrd
+ MAPLOADING_CHECK_TICK
++xcrd
- --ycrd
-
- CHECK_TICK
-
+ ycrd--
+ MAPLOADING_CHECK_TICK
+ bounds[MAP_MINX] = min(bounds[MAP_MINX], first_x)
+ bounds[MAP_MINY] = min(bounds[MAP_MINY], last_y)
+ bounds[MAP_MINZ] = min(bounds[MAP_MINZ], zcrd)
+ bounds[MAP_MAXX] = max(bounds[MAP_MAXX], last_x)
+ bounds[MAP_MAXY] = max(bounds[MAP_MAXY], first_y)
+ bounds[MAP_MAXZ] = max(bounds[MAP_MAXZ], zcrd)
+
+ // And we are done lads, call it off
+ SSatoms.map_loader_stop()
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
@@ -221,180 +355,176 @@
testing("Skipped loading [turfsSkipped] default turfs")
#endif
+ if(has_expanded_world_maxx || has_expanded_world_maxy)
+ SEND_GLOBAL_SIGNAL(COMSIG_GLOB_EXPANDED_WORLD_BOUNDS, has_expanded_world_maxx, has_expanded_world_maxy)
+
if(did_expand)
world.refresh_atmos_grid()
return TRUE
+GLOBAL_LIST_EMPTY(map_model_default)
+
/datum/parsed_map/proc/build_cache(no_changeturf, bad_paths=null)
if(modelCache && !bad_paths)
return modelCache
. = modelCache = list()
var/list/grid_models = src.grid_models
+ var/set_space = FALSE
+ // Use where a list is needed, but where it will not be modified
+ // Used here to remove the cost of needing to make a new list for each fields entry when it's set manually later
+ var/static/list/default_list = GLOB.map_model_default
for(var/model_key in grid_models)
var/model = grid_models[model_key]
- var/list/members = list() //will contain all members (paths) in model (in our example : /turf/unsimulated/wall and /area/mine/explored)
- var/list/members_attributes = list() //will contain lists filled with corresponding variables, if any (in our example : list(icon_state = "rock") and list())
+ // This is safe because dmm strings will never actually newline
+ // So we can parse things just fine
+ var/list/entries = splittext(model, ",\n")
+ //will contain all members (paths) in model (in our example : /turf/unsimulated/wall and /area/mine/explored)
+ var/list/members = new /list(length(entries))
+ //will contain lists filled with corresponding variables, if any (in our example : list(icon_state = "rock") and list())
+ //member attributes are rarish, so we could lazyinit this
+ var/list/members_attributes = new /list(length(entries))
/////////////////////////////////////////////////////////
//Constructing members and corresponding variables lists
////////////////////////////////////////////////////////
var/index = 1
- var/old_position = 1
- var/dpos
-
- while(dpos != 0)
- //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/path_text = trim_text(copytext(full_def, 1, variables_start))
+ for(var/member_string in entries)
+ var/variables_start = 0
+ //findtext is a bit expensive, lets only do this if the last char of our string is a } (IE: we know we have vars)
+ //this saves about 25 miliseconds on my machine. Not a major optimization
+ if(member_string[length(member_string)] == "}")
+ variables_start = findtext(member_string, "{")
+
+ var/path_text = TRIM_TEXT(copytext(member_string, 1, variables_start))
var/atom_def = text2path(path_text) //path definition, e.g /obj/foo/bar
- if(dpos)
- old_position = dpos + length(model[dpos])
if(!ispath(atom_def, /atom)) // Skip the item if the path does not exist. Fix your crap, mappers!
if(bad_paths)
LAZYOR(bad_paths[path_text], model_key)
continue
- members.Add(atom_def)
+ members[index] = 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()
-
+ var/list/fields = default_list
if(variables_start)//if there's any variable
- full_def = copytext(full_def, variables_start + length(full_def[variables_start]), -length(copytext_char(full_def, -1))) //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)
+ member_string = copytext(member_string, variables_start + length(member_string[variables_start]), -length(copytext_char(member_string, -1))) //removing the last '}'
+ fields = readlist(member_string, ";")
+ 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
//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
+ // 1. the space_key isn't set yet
+ // 2. no_changeturf is set
// 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]) \
+ if(!set_space \
+ && no_changeturf \
&& 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))
-
+ set_space = TRUE
.[SPACE_KEY] = model_key
continue
-
.[model_key] = list(members, members_attributes)
-/datum/parsed_map/proc/build_coordinate(list/areaCache, list/model, turf/crds, no_changeturf as num, placeOnTop as num)
+/datum/parsed_map/proc/build_coordinate(list/model, turf/crds, no_changeturf as num, placeOnTop as num)
+ // If we don't have a turf, nothing we will do next will actually acomplish anything, so just go back
+ // Note, this would actually drop area vvs in the tile, but like, why tho
+ if(!crds)
+ return
var/index
var/list/members = model[1]
var/list/members_attributes = model[2]
+ // We use static lists here because it's cheaper then passing them around
+ var/static/list/default_list = GLOB.map_model_default
////////////////
//Instanciation
////////////////
- for (var/turf_in_blacklist in turf_blacklist)
- if (crds == turf_in_blacklist) //if the given turf is blacklisted, dont do anything with it
- return
+ if(turf_blacklist?[crds])
+ return
//The next part of the code assumes there's ALWAYS an /area AND a /turf on a given tile
//first instance the /area and remove it from the members list
index = members.len
+ var/atom/instance
if(members[index] != /area/template_noop)
- var/atype = members[index]
- world.preloader_setup(members_attributes[index], atype)//preloader for assigning set variables on atom creation
- var/atom/instance = areaCache[atype]
- if (!instance)
- instance = GLOB.areas_by_type[atype]
+ if(members_attributes[index] != default_list)
+ world.preloader_setup(members_attributes[index], members[index])//preloader for assigning set variables on atom creation
+ instance = loaded_areas[members[index]]
+ if(!instance)
+ var/area_type = members[index]
+ // If this parsed map doesn't have that area already, we check the global cache
+ instance = GLOB.areas_by_type[area_type]
+ // If the global list DOESN'T have this area it's either not a unique area, or it just hasn't been created yet
if (!instance)
- instance = new atype(null)
- areaCache[atype] = instance
- if(crds)
- instance.contents.Add(crds)
+ instance = new area_type(null)
+ if(!instance)
+ CRASH("[area_type] failed to be new'd, what'd you do?")
+ loaded_areas[area_type] = instance
- if(GLOB.use_preloader && instance)
- world.preloader_load(instance)
+ instance.contents.Add(crds)
- //then instance the /turf and, if multiple tiles are presents, simulates the DMM underlays piling effect
+ if(GLOB.use_preloader)
+ world.preloader_load(instance)
- var/first_turf_index = 1
- while(!ispath(members[first_turf_index], /turf)) //find first /turf object in members
- first_turf_index++
+ // Index right before /area is /turf
+ index--
+ //then instance the /turf
+ //NOTE: this used to place any turfs before the last "underneath" it using .appearance and underlays
+ //We don't actually use this, and all it did was cost cpu, so we don't do this anymore
+ if(members[index] != /turf/template_noop)
+ if(members_attributes[index] != default_list)
+ world.preloader_setup(members_attributes[index], members[index])
+
+ // Note: we make the assertion that the last path WILL be a turf. if it isn't, this will fail.
+ var/old_virtual_z = crds.virtual_z
+ if(placeOnTop)
+ instance = crds.PlaceOnTop(null, members[index], CHANGETURF_DEFER_CHANGE | (no_changeturf ? CHANGETURF_SKIP : NONE))
+ else if(!no_changeturf)
+ instance = crds.ChangeTurf(members[index], null, CHANGETURF_DEFER_CHANGE)
+ else
+ instance = create_turf(members[index], crds , old_virtual_z)//first preloader pass
+ var/turf/new_turf = instance
+ new_turf.virtual_z = old_virtual_z //UNDER NO CIRCUMSTANCES LOOSE THIS VARIABLE
- //turn off base new Initialization until the whole thing is loaded
- SSatoms.map_loader_begin()
- //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,placeOnTop)
-
- 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,placeOnTop)//instance new turf
- T.underlays += underlay
- index++
+ if(GLOB.use_preloader && instance)//second preloader pass, for those atoms that don't ..() in New()
+ world.preloader_load(instance)
+ MAPLOADING_CHECK_TICK
//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,placeOnTop)
- //Restore initialization to the previous value
- SSatoms.map_loader_stop()
+ for(var/atom_index in 1 to index-1)
+ if(members_attributes[atom_index] != default_list)
+ world.preloader_setup(members_attributes[atom_index], members[atom_index])
+
+ // We make the assertion that only /atom s will be in this portion of the code. if that isn't true, this will fail
+ instance = create_atom(members[atom_index], crds)//first preloader pass
+
+ if(GLOB.use_preloader && instance)//second preloader pass, for those atoms that don't ..() in New()
+ world.preloader_load(instance)
+ MAPLOADING_CHECK_TICK
////////////////
//Helpers procs
////////////////
-//Instance an atom at (x,y,z) and gives it the variables in attributes
-/datum/parsed_map/proc/instance_atom(path,list/attributes, turf/crds, no_changeturf, placeOnTop)
- world.preloader_setup(attributes, path)
-
- if(crds)
- if(ispath(path, /turf))
- var/old_virtual_z = crds.virtual_z
- if(placeOnTop)
- . = crds.PlaceOnTop(null, path, CHANGETURF_DEFER_CHANGE | (no_changeturf ? CHANGETURF_SKIP : NONE))
- else if(!no_changeturf)
- . = crds.ChangeTurf(path, null, CHANGETURF_DEFER_CHANGE)
- else
- . = create_turf(path, crds , old_virtual_z)//first preloader pass
- var/turf/new_turf = .
- new_turf.virtual_z = old_virtual_z //UNDER NO CIRCUMSTANCES LOOSE THIS VARIABLE
- else
- . = create_atom(path, crds)//first preloader pass
-
- if(GLOB.use_preloader && .)//second preloader pass, for those atoms that don't ..() in New()
- world.preloader_load(.)
-
- //custom CHECK_TICK here because we don't want things created while we're sleeping to not initialize
- if(TICK_CHECK)
- SSatoms.map_loader_stop()
- stoplag()
- SSatoms.map_loader_begin()
-
/datum/parsed_map/proc/create_turf(path, crds, virtual_z)
set waitfor = FALSE
. = new path (crds, virtual_z)
@@ -403,15 +533,6 @@
set waitfor = FALSE
. = new path (crds)
-//text trimming (both directions) helper proc
-//optionally removes quotes before and after the text (for variable name)
-/datum/parsed_map/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
/datum/parsed_map/proc/find_next_delimiter_position(text as text,initial_position as num, delimiter=",",opening_escape="\"",closing_escape="\"")
@@ -426,7 +547,6 @@
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
/datum/parsed_map/proc/readlist(text as text, delimiter=",")
@@ -434,30 +554,49 @@
if (!text)
return
- var/position
- var/old_position = 1
-
- while(position != 0)
- // 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)))
- var/left_constant = delimiter == ";" ? trim_left : parse_constant(trim_left)
- if(position)
- old_position = position + length(text[position])
+ // If we're using a semi colon, we can do this as splittext rather then constant calls to find_next_delimiter_position
+ // This does make the code a bit harder to read, but saves a good bit of time so suck it up
+ var/using_semicolon = delimiter == ";"
+ if(using_semicolon)
+ var/list/line_entries = splittext(text, ";\n")
+ for(var/entry in line_entries)
+ // 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(entry,"=")
+ // This could in theory happen if someone inserts an improper newline
+ // Let's be nice and kill it here rather then later, it'll save like 0.02 seconds if we don't need to run trims in build_cache
+ if(!equal_position)
+ continue
+ var/trim_left = TRIM_TEXT(copytext(entry,1,equal_position))
- if(equal_position && !isnum(left_constant))
// Associative var, so do the association.
// Note that numbers cannot be keys - the RHS is dropped if so.
- var/trim_right = trim_text(copytext(text, equal_position + length(text[equal_position]), position))
+ var/trim_right = TRIM_TEXT(copytext(entry, equal_position + length(entry[equal_position])))
var/right_constant = parse_constant(trim_right)
- .[left_constant] = right_constant
+ .[trim_left] = right_constant
+ else
+ var/position
+ var/old_position = 1
+ while(position != 0)
+ // 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)))
+ var/left_constant = parse_constant(trim_left)
+ if(position)
+ old_position = position + length(text[position])
+ if(!left_constant) // damn newlines man. Exists to provide behavior consistency with the above loop. not a major cost becuase this path is cold
+ continue
- else // simple var
- . += list(left_constant)
+ if(equal_position && !isnum(left_constant))
+ // Associative var, so do the association.
+ // Note that numbers cannot be keys - the RHS is dropped if so.
+ var/trim_right = TRIM_TEXT(copytext(text, equal_position + length(text[equal_position]), position))
+ var/right_constant = parse_constant(trim_right)
+ .[left_constant] = right_constant
+ else // simple var
+ . += list(left_constant)
/datum/parsed_map/proc/parse_constant(text)
// number
@@ -467,7 +606,10 @@
// string
if(text[1] == "\"")
- return copytext(text, length(text[1]) + 1, findtext(text, "\"", length(text[1]) + 1))
+ // insert implied locate \" and length("\"") here
+ // It's a minimal timesave but it is a timesave
+ // Safe becuase we're guarenteed trimmed constants
+ return copytext(text, 2, -1)
// list
if(copytext(text, 1, 6) == "list(")//6 == length("list(") + 1
@@ -495,7 +637,8 @@
/datum/parsed_map/Destroy()
..()
- turf_blacklist.Cut()
+ if(turf_blacklist)
+ turf_blacklist.Cut()
parsed_bounds.Cut()
bounds.Cut()
grid_models.Cut()
diff --git a/code/modules/mob/say_vr.dm b/code/modules/mob/say_vr.dm
index 83bb19a33882..d2e6a4f0dda2 100644
--- a/code/modules/mob/say_vr.dm
+++ b/code/modules/mob/say_vr.dm
@@ -10,28 +10,22 @@
set src in usr
if(usr != src)
- usr << "No."
+ to_chat(usr, span_warning("You can't set someone else's flavour text!"))
var/msg = sanitize(input(usr,"Set the flavor text in your 'examine' verb. Can also be used for OOC notes about your character.","Flavor Text",html_decode(flavor_text)) as message|null)
- if(msg) //WS edit - "Cancel" does not clear flavor text
+ if(msg)
msg = copytext(msg, 1, MAX_MESSAGE_LEN)
msg = html_encode(msg)
flavor_text = msg
-/mob/proc/warn_flavor_changed()
- if(flavor_text && flavor_text != "") // don't spam people that don't use it!
- src << "OOC Warning:
"
- src << "Your flavor text is likely out of date! Change"
-
/mob/proc/print_flavor_text()
if(flavor_text && flavor_text != "")
var/msg = replacetext(flavor_text, "\n", " ")
if(length(msg) <= 100)
return "[msg]"
else
- return "[copytext(msg, 1, 97)]... More..."
-
+ return "[copytext(msg, 1, 97)]... More..."
/mob/proc/get_top_level_mob()
if(istype(src.loc,/mob)&&src.loc!=src)
diff --git a/code/modules/unit_tests/biome_lists.dm b/code/modules/unit_tests/biome_lists.dm
index 7c7500155235..18334cc8a15d 100644
--- a/code/modules/unit_tests/biome_lists.dm
+++ b/code/modules/unit_tests/biome_lists.dm
@@ -2,9 +2,14 @@
for(var/biome_type as anything in SSmapping.biomes)
var/datum/biome/biome = SSmapping.biomes[biome_type]
+ validate_chance(biome.open_turf_types, "open turf", biome_type)
validate_chance(biome.mob_spawn_list, "mob spawn", biome_type)
validate_chance(biome.flora_spawn_list, "flora spawn", biome_type)
validate_chance(biome.feature_spawn_list, "feature spawn", biome_type)
+ if(!istype(biome, /datum/biome/cave))
+ continue
+ var/datum/biome/cave/cave = biome
+ validate_chance(cave.closed_turf_types, "closed turf", biome_type)
/datum/unit_test/biome_lists/proc/validate_chance(list/to_check, name, biome)
if(to_check && !islist(to_check))
@@ -14,5 +19,5 @@
if(!value)
TEST_FAIL("Biome [biome] has no [name] weight for [type]")
return
- if(!isnum(value) || value < 1 || value != round(value))
+ if(!isnum(value) || value < 1 || value != round(value)) //ensures natural numbers
TEST_FAIL("Biome [biome] has invalid [name] chance for [type] ([value])")