diff --git a/code/_macros.dm b/code/_macros.dm index e8a97cbada..6c1f37b4bc 100644 --- a/code/_macros.dm +++ b/code/_macros.dm @@ -90,6 +90,9 @@ #define GENERATE_DEBUG_ID "[rand(0, 9)][rand(0, 9)][rand(0, 9)][rand(0, 9)][pick(alphabet_lowercase)][pick(alphabet_lowercase)][pick(alphabet_lowercase)][pick(alphabet_lowercase)]" #define RECT new /datum/shape/rectangle +#define SQUARE new /datum/shape/rectangle/square +#define ELLIPSE new /datum/shape/ellipse +#define CIRCLE new /datum/shape/ellipse/circle #define QTREE new /datum/quadtree #define SEARCH_QTREE(qtree, shape_range, flags) qtree.query_range(shape_range, null, flags) diff --git a/code/controllers/subsystem/sound.dm b/code/controllers/subsystem/sound.dm index 4fdfd79353..010850dda2 100644 --- a/code/controllers/subsystem/sound.dm +++ b/code/controllers/subsystem/sound.dm @@ -19,8 +19,7 @@ SUBSYSTEM_DEF(sound) if(!run_hearers) // Initialize for handling next template run_hearers = run_queue[run_template] // get base hearers if(run_template.range) // ranging - var/datum/shape/rectangle/zone = RECT(run_template.x, run_template.y, run_template.range * 2, run_template.range * 2) - run_hearers |= SSquadtree.players_in_range(zone, run_template.z) + run_hearers |= SSquadtree.players_in_range(SQUARE(run_template.x, run_template.y, run_template.range * 2), run_template.z) if(MC_TICK_CHECK) return while(length(run_hearers)) // Output sound to hearers diff --git a/code/controllers/subsystem/techtree.dm b/code/controllers/subsystem/techtree.dm index 04ac2591bc..5f22373228 100644 --- a/code/controllers/subsystem/techtree.dm +++ b/code/controllers/subsystem/techtree.dm @@ -34,17 +34,6 @@ SUBSYSTEM_DEF(techtree) var/datum/space_level/zpos = SSmapping.add_new_zlevel(tree.name, list(ZTRAIT_TECHTREE)) tree.zlevel = zpos - var/zlevel = zpos.z_value - var/turf/z_min = locate(1, 1, zlevel) - var/turf/z_max = locate(world.maxx, world.maxy, zlevel) - - - - for(var/t in block(z_min, z_max)) - var/turf/Tu = t - Tu.ChangeTurf(/turf/closed/void, list(/turf/closed/void)) - new /area/techtree(Tu) - for(var/tier in tree.tree_tiers) tree.unlocked_techs += tier tree.all_techs += tier diff --git a/code/controllers/subsystem/ticker.dm b/code/controllers/subsystem/ticker.dm index e06e1ac458..ef796bc439 100644 --- a/code/controllers/subsystem/ticker.dm +++ b/code/controllers/subsystem/ticker.dm @@ -109,14 +109,6 @@ SUBSYSTEM_DEF(ticker) mode.declare_completion(force_ending) REDIS_PUBLISH("byond.round", "type" = "round-complete") flash_clients() - addtimer(CALLBACK( - SSvote, - /datum/controller/subsystem/vote/proc/initiate_vote, - "gamemode", - "SERVER", - CALLBACK(src, PROC_REF(handle_map_reboot)), - TRUE - ), 3 SECONDS) Master.SetRunLevel(RUNLEVEL_POSTGAME) /// Attempt to start game asynchronously if applicable @@ -161,16 +153,6 @@ SUBSYSTEM_DEF(ticker) return FALSE return TRUE -/datum/controller/subsystem/ticker/proc/handle_map_reboot() - addtimer(CALLBACK( - SSvote, - /datum/controller/subsystem/vote/proc/initiate_vote, - "groundmap", - "SERVER", - CALLBACK(src, PROC_REF(Reboot)), - TRUE - ), 3 SECONDS) - /datum/controller/subsystem/ticker/proc/setup() to_chat(world, SPAN_BOLDNOTICE("Enjoy the game!")) var/init_start = world.timeofday @@ -349,40 +331,6 @@ SUBSYSTEM_DEF(ticker) fdel("data/mode.txt") WRITE_FILE(file("data/mode.txt"), the_mode) - -/datum/controller/subsystem/ticker/proc/Reboot(reason, delay) - set waitfor = FALSE - - if(usr && !check_rights(R_SERVER)) - return - - if(graceful) - to_chat_forced(world, "

[SPAN_BOLDNOTICE("Shutting down...")]

") - world.Reboot(FALSE) - return - - if(!delay) - delay = CONFIG_GET(number/round_end_countdown) * 10 - - var/skip_delay = check_rights() - if(delay_end && !skip_delay) - to_chat(world, SPAN_BOLDNOTICE("An admin has delayed the round end.")) - return - - to_chat(world, SPAN_BOLDNOTICE("Rebooting World in [DisplayTimeText(delay)]. [reason]")) - - var/start_wait = world.time - sleep(delay - (world.time - start_wait)) - - if(delay_end && !skip_delay) - to_chat(world, SPAN_BOLDNOTICE("Reboot was cancelled by an admin.")) - return - - log_game("Rebooting World. [reason]") - to_chat_forced(world, "

[SPAN_BOLDNOTICE("Rebooting...")]

") - - world.Reboot(TRUE) - /datum/controller/subsystem/ticker/proc/create_characters() if(!RoleAuthority) return diff --git a/code/datums/quadtree.dm b/code/datums/quadtree.dm index bf5cd8000b..ec3e577a24 100644 --- a/code/datums/quadtree.dm +++ b/code/datums/quadtree.dm @@ -49,43 +49,124 @@ ..() return QDEL_HINT_IWILLGC -/datum/shape //Leaving rectangles as a subtype if anyone decides to add circles later +/// A simple geometric shape for testing collisions and intersections. This one is a single point. +/datum/shape + /// Horizontal position of the shape's center point. var/center_x = 0 + /// Vertical position of the shape's center point. var/center_y = 0 + /// Distance from the shape's leftmost to rightmost extent. + var/bounds_x = 0 + /// Distance from the shape's topmost to bottommost extent. + var/bounds_y = 0 -/datum/shape/proc/intersects() - return -/datum/shape/proc/contains() - return +/datum/shape/New(center_x, center_y) + set_shape(center_x, center_y) +/// Assign shape variables. +/datum/shape/proc/set_shape(center_x, center_y) + src.center_x = center_x + src.center_y = center_y + +/// Returns TRUE if the coordinates x, y are in or on the shape, otherwise FALSE. +/datum/shape/proc/contains_xy(x, y) + return center_x == x && center_y == y + +/// Returns TRUE if the coord datum is in or on the shape, otherwise FALSE. +/datum/shape/proc/contains_coords(datum/coords/coords) + return contains_xy(coords.x_pos, coords.y_pos) + +/// Returns TRUE if the atom is in or on the shape, otherwise FALSE. +/datum/shape/proc/contains_atom(atom/atom) + return contains_xy(atom.x, atom.y) + +/// Returns TRUE if this shape's bounding box intersects the provided shape's bounding box, otherwise FALSE. Generally faster than a full intersection test. +/datum/shape/proc/intersects_aabb(datum/shape/aabb) + return (abs(src.center_x - aabb.center_x) <= (src.bounds_x + aabb.bounds_x) * 0.5) && (abs(src.center_y - aabb.center_y) <= (src.bounds_y + aabb.bounds_y) * 0.5) + +/// Returns TRUE if this shape intersects the provided rectangle shape, otherwise FALSE. +/datum/shape/proc/intersects_rect(datum/shape/rectangle/rect) + return rect.contains_xy(src.center_x, src.center_y) + +/// A simple geometric shape for testing collisions and intersections. This one is an axis-aligned rectangle. /datum/shape/rectangle + /// Distance from the shape's leftmost to rightmost extent. + var/width = 0 + /// Distance from the shape's topmost to bottommost extent. + var/height = 0 + +/datum/shape/rectangle/New(center_x, center_y, width, height) + set_shape(center_x, center_y, width, height) + +/datum/shape/rectangle/set_shape(center_x, center_y, width, height) + ..() + src.bounds_x = width + src.bounds_y = height + src.width = width + src.height = height + +/datum/shape/rectangle/contains_xy(x, y) + return (abs(center_x - x) <= width * 0.5) && (abs(center_y - y) <= height * 0.5) + +/datum/shape/rectangle/intersects_rect(datum/shape/rectangle/rect) + return intersects_aabb(rect) + +/// A simple geometric shape for testing collisions and intersections. This one is an axis-aligned square. +/datum/shape/rectangle/square + /// Distance between the shape's opposing extents. + var/length = 0 + +/datum/shape/rectangle/square/New(center_x, center_y, length) + set_shape(center_x, center_y, length) + +/datum/shape/rectangle/square/set_shape(center_x, center_y, length) + ..(center_x, center_y, length, length) + src.length = length + +/// A simple geometric shape for testing collisions and intersections. This one is an axis-aligned ellipse. +/datum/shape/ellipse + /// Distance from the shape's leftmost to rightmost extent. var/width = 0 + /// Distance from the shape's topmost to bottommost extent. var/height = 0 + VAR_PROTECTED/_axis_x_sq = 0 + VAR_PROTECTED/_axis_y_sq = 0 + +/datum/shape/ellipse/New(center_x, center_y, width, height) + set_shape(center_x, center_y, width, height) -/datum/shape/rectangle/New(x, y, w, h) +/datum/shape/ellipse/set_shape(center_x, center_y, width, height) ..() - center_x = x - center_y = y - width = w - height = h - -/datum/shape/rectangle/intersects(datum/shape/rectangle/range) - return !(range.center_x + range.width/2 < center_x - width / 2|| \ - range.center_x - range.width/2 > center_x + width / 2|| \ - range.center_y + range.height/2 < center_y - height / 2|| \ - range.center_y - range.height/2 > center_y + height / 2) - -/datum/shape/rectangle/contains(datum/coords/coords) - return (coords.x_pos >= center_x - width / 2 \ - && coords.x_pos <= center_x + width / 2 \ - && coords.y_pos >= center_y - height /2 \ - && coords.y_pos <= center_y + height / 2) - -/datum/shape/rectangle/proc/contains_atom(atom/A) - return (A.x >= center_x - width / 2 \ - && A.x <= center_x + width / 2 \ - && A.y >= center_y - height /2 \ - && A.y <= center_y + height / 2) + src.bounds_x = width + src.bounds_y = height + src.width = width + src.height = height + src._axis_x_sq = (width * 0.5)**2 + src._axis_y_sq = (height * 0.5)**2 + +/datum/shape/ellipse/contains_xy(x, y) + return ((center_x - x)**2 / _axis_x_sq + (center_y - y)**2 / _axis_y_sq <= 1) + +/datum/shape/ellipse/intersects_rect(datum/shape/rectangle/rect) + if(..()) + return TRUE + + var/nearest_x = clamp(src.center_x, rect.center_x - rect.width * 0.5, rect.center_x + rect.width * 0.5) + var/nearest_y = clamp(src.center_y, rect.center_y - rect.height * 0.5, rect.center_y + rect.height * 0.5) + + return src.contains_xy(nearest_x, nearest_y) + +/// A simple geometric shape for testing collisions and intersections. This one is a circle. +/datum/shape/ellipse/circle + /// Distance from the shape's center to edge. + var/radius = 0 + +/datum/shape/ellipse/circle/New(center_x, center_y, radius) + set_shape(center_x, center_y, radius) + +/datum/shape/ellipse/circle/set_shape(center_x, center_y, radius) + ..(center_x, center_y, radius * 2, radius * 2) + src.radius = radius /datum/quadtree/proc/subdivide() //Warning: this might give you eye cancer @@ -96,7 +177,7 @@ is_divided = TRUE /datum/quadtree/proc/insert_player(datum/coords/qtplayer/p_coords) - if(!boundary.contains(p_coords)) + if(!boundary.contains_coords(p_coords)) return FALSE if(!player_coords) @@ -118,11 +199,11 @@ player_coords.Add(p_coords) return TRUE -/datum/quadtree/proc/query_range(datum/shape/rectangle/range, list/found_players, flags = 0) +/datum/quadtree/proc/query_range(datum/shape/range, list/found_players, flags = 0) if(!found_players) found_players = list() . = found_players - if(!range?.intersects(boundary)) + if(!range?.intersects_rect(boundary)) return if(is_divided) nw_branch.query_range(range, found_players, flags) @@ -136,7 +217,7 @@ continue if((flags & QTREE_EXCLUDE_OBSERVER) && P.is_observer) continue - if(range.contains(P)) + if(range.contains_coords(P)) if(flags & QTREE_SCAN_MOBS) found_players.Add(P.player) continue diff --git a/code/game/objects/items/devices/motion_detector.dm b/code/game/objects/items/devices/motion_detector.dm index 99b11d1ff6..60cf62b4e2 100644 --- a/code/game/objects/items/devices/motion_detector.dm +++ b/code/game/objects/items/devices/motion_detector.dm @@ -37,7 +37,7 @@ var/iff_signal = FACTION_MARINE actions_types = list(/datum/action/item_action) var/scanning = FALSE // controls if MD is in process of scan - var/datum/shape/rectangle/range_bounds + var/datum/shape/rectangle/square/range_bounds var/long_range_locked = FALSE //only long-range MD var/ping_overlay @@ -53,7 +53,7 @@ /obj/item/device/motiondetector/Initialize() . = ..() - range_bounds = new //Just creating a rectangle datum + range_bounds = new //Just creating a square datum update_icon() /obj/item/device/motiondetector/Destroy() @@ -215,12 +215,7 @@ if(!istype(cur_turf)) return - if(!range_bounds) - range_bounds = new/datum/shape/rectangle - range_bounds.center_x = cur_turf.x - range_bounds.center_y = cur_turf.y - range_bounds.width = detector_range * 2 - range_bounds.height = detector_range * 2 + range_bounds.set_shape(cur_turf.x, cur_turf.y, detector_range * 2) var/list/ping_candidates = SSquadtree.players_in_range(range_bounds, cur_turf.z, QTREE_EXCLUDE_OBSERVER | QTREE_SCAN_MOBS) diff --git a/code/game/objects/structures/fence.dm b/code/game/objects/structures/fence.dm index b29c69e8af..6a4b479929 100644 --- a/code/game/objects/structures/fence.dm +++ b/code/game/objects/structures/fence.dm @@ -3,6 +3,7 @@ desc = "A large metal mesh strewn between two poles. Intended as a cheap way to separate areas, while allowing one to see through it." icon = 'icons/obj/structures/props/fence.dmi' icon_state = "fence0" + throwpass = TRUE density = TRUE anchored = TRUE layer = WINDOW_LAYER diff --git a/code/modules/admin/banjob.dm b/code/modules/admin/banjob.dm index 18f06e79a6..230590fdf9 100644 --- a/code/modules/admin/banjob.dm +++ b/code/modules/admin/banjob.dm @@ -146,6 +146,12 @@ WARNING!*/ else jobs += "Emergency Response Team" + //Freed Mobs + if(jobban_isbanned(M, "Freed Mob", P) || isbanned_dept) + jobs += "Freed Mob" + else + jobs += "Freed Mob" + //Survivor if(jobban_isbanned(M, "Survivor", P) || isbanned_dept) jobs += "Survivor" diff --git a/code/modules/cm_aliens/structures/special/egg_morpher.dm b/code/modules/cm_aliens/structures/special/egg_morpher.dm index 1fd154eb35..e8040f81ba 100644 --- a/code/modules/cm_aliens/structures/special/egg_morpher.dm +++ b/code/modules/cm_aliens/structures/special/egg_morpher.dm @@ -14,14 +14,14 @@ var/huggers_to_grow_max = 12 var/huggers_reserved = 0 var/mob/captured_mob - var/datum/shape/rectangle/range_bounds + var/datum/shape/range_bounds appearance_flags = KEEP_TOGETHER layer = FACEHUGGER_LAYER /obj/effect/alien/resin/special/eggmorph/Initialize(mapload, hive_ref) . = ..() - range_bounds = RECT(x, y, EGGMORPG_RANGE, EGGMORPG_RANGE) + range_bounds = SQUARE(x, y, EGGMORPG_RANGE) /obj/effect/alien/resin/special/eggmorph/Destroy() if (stored_huggers && linked_hive) @@ -155,7 +155,7 @@ /obj/effect/alien/resin/special/eggmorph/proc/check_facehugger_target() if(!range_bounds) - range_bounds = RECT(x, y, EGGMORPG_RANGE, EGGMORPG_RANGE) + range_bounds = SQUARE(x, y, EGGMORPG_RANGE) var/list/targets = SSquadtree.players_in_range(range_bounds, z, QTREE_SCAN_MOBS | QTREE_EXCLUDE_OBSERVER) if(isnull(targets) || !length(targets)) diff --git a/code/modules/cm_tech/techtree.dm b/code/modules/cm_tech/techtree.dm index 6c39d8ab9c..a027789185 100644 --- a/code/modules/cm_tech/techtree.dm +++ b/code/modules/cm_tech/techtree.dm @@ -58,16 +58,24 @@ if(longest_tier < tier_length) longest_tier = tier_length - // Clear out the area - for(var/t in block(locate(1, 1, zlevel.z_value), locate(longest_tier * 2 + 1, length(all_techs) * 3 + 1, zlevel.z_value))) + // Clear out and create the area + // (The `+ 2` on both of these is 1 for a buffer tile, and 1 for the outer `/turf/closed/void`.) + var/area_max_x = longest_tier * 2 + 2 + var/area_max_y = length(all_techs) * 3 + 2 + for(var/t in block(locate(1, 1, zlevel.z_value), locate(area_max_x, area_max_y, zlevel.z_value))) var/turf/pos = t for(var/A in pos) qdel(A) - pos.ChangeTurf(/turf/open/blank) - pos.color = "#000000" - + if(pos.x == area_max_x || pos.y == area_max_y) + // The turfs around the edge are closed. + pos.ChangeTurf(/turf/closed/void) + else + pos.ChangeTurf(/turf/open/blank) + pos.color = "#000000" + new /area/techtree(pos) + // Create the tech nodes var/y_offset = 1 for(var/tier in all_techs) var/tier_length = length(all_techs[tier]) diff --git a/code/modules/defenses/bell_tower.dm b/code/modules/defenses/bell_tower.dm index 52207298c4..a9d6c08bc7 100644 --- a/code/modules/defenses/bell_tower.dm +++ b/code/modules/defenses/bell_tower.dm @@ -257,7 +257,7 @@ STOP_PROCESSING(SSobj, src) return - var/list/targets = SSquadtree.players_in_range(RECT(M.x, M.y, area_range, area_range), M.z, QTREE_SCAN_MOBS | QTREE_EXCLUDE_OBSERVER) + var/list/targets = SSquadtree.players_in_range(SQUARE(M.x, M.y, area_range), M.z, QTREE_SCAN_MOBS | QTREE_EXCLUDE_OBSERVER) if(!targets) return diff --git a/code/modules/defenses/planted_flag.dm b/code/modules/defenses/planted_flag.dm index fac725047f..f0f6b05565 100644 --- a/code/modules/defenses/planted_flag.dm +++ b/code/modules/defenses/planted_flag.dm @@ -7,7 +7,7 @@ desc = "A planted flag with the iconic USCM flag plastered all over it, you feel a burst of energy by its mere sight." handheld_type = /obj/item/defenses/handheld/planted_flag disassemble_time = 10 - var/datum/shape/rectangle/range_bounds + var/datum/shape/range_bounds var/area_range = PLANTED_FLAG_RANGE var/buff_intensity = PLANTED_FLAG_BUFF health = 200 @@ -33,7 +33,7 @@ apply_area_effect() start_processing() - range_bounds = RECT(x, y, PLANTED_FLAG_RANGE, PLANTED_FLAG_RANGE) + range_bounds = SQUARE(x, y, PLANTED_FLAG_RANGE) update_icon() /obj/structure/machinery/defenses/planted_flag/Destroy() @@ -70,9 +70,9 @@ /obj/structure/machinery/defenses/planted_flag/proc/apply_area_effect() if(!range_bounds) - range_bounds = RECT(x, y, area_range, area_range) + range_bounds = SQUARE(x, y, area_range) - var/list/targets = SSquadtree.players_in_range(RECT(x, y, area_range, area_range), z, QTREE_SCAN_MOBS | QTREE_EXCLUDE_OBSERVER) + var/list/targets = SSquadtree.players_in_range(SQUARE(x, y, area_range), z, QTREE_SCAN_MOBS | QTREE_EXCLUDE_OBSERVER) if(!targets) return @@ -146,7 +146,7 @@ if(!M.x && !M.y && !M.z) return - var/list/targets = SSquadtree.players_in_range(RECT(M.x, M.y, area_range, area_range), M.z, QTREE_SCAN_MOBS | QTREE_EXCLUDE_OBSERVER) + var/list/targets = SSquadtree.players_in_range(SQUARE(M.x, M.y, area_range), M.z, QTREE_SCAN_MOBS | QTREE_EXCLUDE_OBSERVER) targets |= M for(var/mob/living/carbon/human/H in targets) diff --git a/code/modules/mob/dead/observer/observer.dm b/code/modules/mob/dead/observer/observer.dm index 44a3592329..409c88388f 100644 --- a/code/modules/mob/dead/observer/observer.dm +++ b/code/modules/mob/dead/observer/observer.dm @@ -1034,6 +1034,10 @@ This is the proc mobs get to turn into a ghost. Forked from ghostize due to comp to_chat(src, SPAN_WARNING("The game hasn't started yet!")) return + if(jobban_isbanned(src, "Freed Mob")) + to_chat(src, SPAN_WARNING("You are banned from being able to join as a freed mob.")) + return + var/list/mobs_by_role = list() // the list the mobs are assigned to first, for sorting purposes for(var/mob/freed_mob as anything in GLOB.freed_mob_list) var/role_name = freed_mob.get_role_name() @@ -1059,6 +1063,10 @@ This is the proc mobs get to turn into a ghost. Forked from ghostize due to comp if(!istype(freed_mob) || !(freed_mob in GLOB.freed_mob_list)) return + if(jobban_isbanned(src, "Freed Mob")) + to_chat(src, SPAN_WARNING("You are banned from being able to join as a freed mob.")) + return + if(QDELETED(freed_mob) || freed_mob.client) GLOB.freed_mob_list -= freed_mob to_chat(src, SPAN_WARNING("Something went wrong.")) diff --git a/html/changelogs/archive/2024-07.yml b/html/changelogs/archive/2024-07.yml index 4042cde8ee..f171a09f14 100644 --- a/html/changelogs/archive/2024-07.yml +++ b/html/changelogs/archive/2024-07.yml @@ -5,3 +5,12 @@ Doubleumc: - admin: Radio Clarity slider now shows an example message - qol: ARC turret shots start from the center of the ARC +2024-07-24: + Doubleumc: + - bugfix: sounds & motion detectors should be more reliable + - rscdel: Removed automatic end of round vote and reboot +2024-07-25: + Doubleumc: + - code_imp: Made the Tech Tree subsystem initialise faster. + Zonespace27: + - admin: Added freed mob bans to the jobban panel.