From c9563247216ac60e8b3c5c8a1e05c420eb06ffef Mon Sep 17 00:00:00 2001 From: Mark Suckerberg Date: Wed, 18 Oct 2023 13:08:27 -0500 Subject: [PATCH] Maploader Optimisations (#2365) ## About The Pull Request Ports: tgstation/tgstation#69632 tgstation/tgstation#70118 tgstation/tgstation#70729 Aiming to also port: tgstation/tgstation#70966 tgstation/tgstation#71074 tgstation/tgstation#77495 ## Why It's Good For The Game Anything we can do to speedup and/or reduce planetgen and maploading lag is great for the game ## Changelog :cl: /:cl: --------- Co-authored-by: LemonInTheDark <58055496+LemonInTheDark@users.noreply.github.com> --- check_regex.yaml | 2 +- code/__DEFINES/dcs/signals.dm | 2 + code/__DEFINES/is_helpers.dm | 2 + code/__DEFINES/maths.dm | 2 + code/__DEFINES/misc.dm | 7 + code/__DEFINES/typeids.dm | 2 +- code/__HELPERS/_lists.dm | 51 +- code/__HELPERS/icons.dm | 6 +- code/__HELPERS/text.dm | 23 +- code/__HELPERS/unsorted.dm | 12 +- code/controllers/subsystem/garbage.dm | 8 +- code/controllers/subsystem/statpanel.dm | 15 +- code/controllers/subsystem/timer.dm | 4 +- code/controllers/subsystem/vis_overlays.dm | 2 +- code/datums/datum.dm | 5 + code/datums/mapgen/_biome.dm | 44 +- .../datums/mapgen/planetary/WasteGenerator.dm | 4 +- code/datums/position_point_vector.dm | 27 +- code/datums/shuttles.dm | 2 +- code/game/atoms.dm | 2 +- code/game/gamemodes/dynamic/dynamic.dm | 22 +- code/game/machinery/airlock_cycle_control.dm | 8 +- code/game/objects/effects/decals/decal.dm | 19 +- code/game/objects/effects/misc.dm | 17 + .../objects/effects/spawners/bombspawner.dm | 2 - code/game/objects/effects/spawners/bundle.dm | 3 +- .../game/objects/effects/spawners/lootdrop.dm | 3 +- .../objects/effects/spawners/structure.dm | 7 +- code/game/objects/effects/spawners/traps.dm | 3 +- .../effects/spawners/xeno_egg_delivery.dm | 3 +- code/game/objects/items/stacks/stack.dm | 2 +- code/modules/admin/admin.dm | 2 +- code/modules/admin/sound_emitter.dm | 12 +- .../view_variables/reference_tracking.dm | 10 +- code/modules/antagonists/borer/borer.dm | 12 +- code/modules/events/holiday/xmas.dm | 6 +- code/modules/holodeck/computer.dm | 2 +- code/modules/language/language_holder.dm | 2 +- code/modules/mapping/map_template.dm | 1 + code/modules/mapping/preloader.dm | 14 +- code/modules/mapping/reader.dm | 547 +++++++++++------- code/modules/mob/say_vr.dm | 12 +- code/modules/unit_tests/biome_lists.dm | 7 +- 43 files changed, 605 insertions(+), 333 deletions(-) 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 Panel

Game 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])")