From 198949757669c5225d6efac28bf0a2ca9e51ec3f Mon Sep 17 00:00:00 2001 From: Timberpoes Date: Fri, 22 Jan 2021 13:18:05 +0000 Subject: [PATCH] Frame stacking fix and unit test addition. (#56287) * Reverts #56205 * Allow things without density to bypass checks * The rest of the owl * The rest of the owl * Doc and tweak * More feex * RCD machine frame unit test * I suck * AAAAA * Bad at unit tests * Revert unit tests (for including in another PR) * Fix windoor_assembly return logic * Comment /mob/living/proc/PushAM logic * Windoor assembley logic tweak * Fix frame stacking * Unit test * Better wording from macros? --- code/__DEFINES/construction.dm | 3 - code/__HELPERS/unsorted.dm | 42 ++++--- code/game/atoms_movable.dm | 13 +- code/game/machinery/_machinery.dm | 40 ++++-- code/game/machinery/cell_charger.dm | 1 - code/game/machinery/doors/windowdoor.dm | 26 ++-- code/game/objects/items/stacks/stack.dm | 114 +++++++++--------- code/game/objects/objs.dm | 5 - code/game/objects/structures/grille.dm | 9 +- code/game/objects/structures/railings.dm | 9 +- .../objects/structures/windoor_assembly.dm | 25 ++-- code/game/objects/structures/window.dm | 57 +++------ code/game/turfs/turf.dm | 22 ++-- code/modules/mob/living/living.dm | 22 +++- .../chemistry/machinery/reagentgrinder.dm | 1 - code/modules/unit_tests/_unit_tests.dm | 1 + code/modules/unit_tests/rcd.dm | 46 +++++++ 17 files changed, 242 insertions(+), 194 deletions(-) create mode 100644 code/modules/unit_tests/rcd.dm diff --git a/code/__DEFINES/construction.dm b/code/__DEFINES/construction.dm index 35398032451..1efd21f379a 100644 --- a/code/__DEFINES/construction.dm +++ b/code/__DEFINES/construction.dm @@ -63,9 +63,6 @@ //windows affected by Nar'Sie turn this color. #define NARSIE_WINDOW_COLOUR "#7D1919" -//let's just pretend fulltile windows being children of border windows is fine -#define FULLTILE_WINDOW_DIR NORTHEAST - //The amount of materials you get from a sheet of mineral like iron/diamond/glass etc #define MINERAL_MATERIAL_AMOUNT 2000 //The maximum size of a stack object. diff --git a/code/__HELPERS/unsorted.dm b/code/__HELPERS/unsorted.dm index 487c28851ac..072821d4d0d 100644 --- a/code/__HELPERS/unsorted.dm +++ b/code/__HELPERS/unsorted.dm @@ -1196,24 +1196,36 @@ GLOBAL_DATUM_INIT(dview_mob, /mob/dview, new) #define FOR_DVIEW_END GLOB.dview_mob.loc = null -//can a window be here, or is there a window blocking it? -/proc/valid_window_location(turf/T, dir_to_check) - if(!T) +/** + * Checks whether the target turf is in a valid state to accept a directional window + * or other directional pseudo-dense object such as railings. + * + * Returns FALSE if the target turf cannot accept a directional window or railing. + * Returns TRUE otherwise. + * + * Arguments: + * * dest_turf - The destination turf to check for existing windows and railings + * * test_dir - The prospective dir of some atom you'd like to put on this turf. + * * is_fulltile - Whether the thing you're attempting to move to this turf takes up the entire tile or whether it supports multiple movable atoms on its tile. + */ +/proc/valid_window_location(turf/dest_turf, test_dir, is_fulltile = FALSE) + if(!dest_turf) return FALSE - for(var/obj/O in T) - if(istype(O, /obj/machinery/door/window) && (O.dir == dir_to_check || dir_to_check == FULLTILE_WINDOW_DIR)) - return FALSE - if(istype(O, /obj/structure/windoor_assembly)) - var/obj/structure/windoor_assembly/W = O - if(W.ini_dir == dir_to_check || dir_to_check == FULLTILE_WINDOW_DIR) + for(var/obj/turf_content in dest_turf) + if(istype(turf_content, /obj/machinery/door/window)) + if((turf_content.dir == test_dir) || is_fulltile) return FALSE - if(istype(O, /obj/structure/window)) - var/obj/structure/window/W = O - if(W.ini_dir == dir_to_check || W.ini_dir == FULLTILE_WINDOW_DIR || dir_to_check == FULLTILE_WINDOW_DIR) + if(istype(turf_content, /obj/structure/windoor_assembly)) + var/obj/structure/windoor_assembly/windoor_assembly = turf_content + if(windoor_assembly.dir == test_dir || is_fulltile) return FALSE - if(istype(O, /obj/structure/railing)) - var/obj/structure/railing/rail = O - if(rail.ini_dir == dir_to_check || rail.ini_dir == FULLTILE_WINDOW_DIR || dir_to_check == FULLTILE_WINDOW_DIR) + if(istype(turf_content, /obj/structure/window)) + var/obj/structure/window/window_structure = turf_content + if(window_structure.dir == test_dir || window_structure.fulltile || is_fulltile) + return FALSE + if(istype(turf_content, /obj/structure/railing)) + var/obj/structure/railing/rail = turf_content + if(rail.dir == test_dir || is_fulltile) return FALSE return TRUE diff --git a/code/game/atoms_movable.dm b/code/game/atoms_movable.dm index 2ae1633262a..fe10ef7aa3c 100644 --- a/code/game/atoms_movable.dm +++ b/code/game/atoms_movable.dm @@ -69,6 +69,9 @@ ///Highest-intensity light affecting us, which determines our visibility. var/affecting_dynamic_lumi = 0 + /// Whether this atom should have its dir automatically changed when it moves. Setting this to FALSE allows for things such as directional windows to retain dir on moving without snowflake code all of the place. + var/set_dir_on_move = TRUE + /atom/movable/Initialize(mapload) . = ..() @@ -343,7 +346,9 @@ if(!direct) direct = get_dir(src, newloc) - setDir(direct) + + if(set_dir_on_move) + setDir(direct) if(!loc.Exit(src, newloc)) return @@ -443,7 +448,7 @@ moving_diagonally = SECOND_DIAG_STEP . = step(src, SOUTH) if(moving_diagonally == SECOND_DIAG_STEP) - if(!.) + if(!. && set_dir_on_move) setDir(first_step_dir) else if (!inertia_moving) inertia_next_move = world.time + inertia_move_delay @@ -476,7 +481,9 @@ set_glide_size(glide_size_override) last_move = direct - setDir(direct) + + if(set_dir_on_move) + setDir(direct) if(. && has_buckled_mobs() && !handle_buckled_mob_movement(loc, direct, glide_size_override)) //movement failed due to buckled mob(s) return FALSE diff --git a/code/game/machinery/_machinery.dm b/code/game/machinery/_machinery.dm index ccfabb950a8..fe9167a20ec 100644 --- a/code/game/machinery/_machinery.dm +++ b/code/game/machinery/_machinery.dm @@ -495,15 +495,33 @@ LAZYCLEARLIST(component_parts) return ..() +/** + * Spawns a frame where this machine is. If the machine was not disassmbled, the + * frame is spawned damaged. If the frame couldn't exist on this turf, it's smashed + * down to metal sheets. + * + * Arguments: + * * disassembled - If FALSE, the machine was destroyed instead of disassembled and the frame spawns at reduced integrity. + */ /obj/machinery/proc/spawn_frame(disassembled) - var/obj/structure/frame/machine/M = new /obj/structure/frame/machine(loc) - . = M - M.set_anchored(anchored) + var/obj/structure/frame/machine/new_frame = new /obj/structure/frame/machine(loc) + + new_frame.state = 2 + + // If the new frame shouldn't be able to fit here due to the turf being blocked, spawn the frame deconstructed. + if(isturf(loc)) + var/turf/machine_turf = loc + if(machine_turf.is_blocked_turf(TRUE, source_atom = new_frame)) + new_frame.deconstruct(disassembled) + return + + new_frame.icon_state = "box_1" + . = new_frame + new_frame.set_anchored(anchored) if(!disassembled) - M.obj_integrity = M.max_integrity * 0.5 //the frame is already half broken - transfer_fingerprints_to(M) - M.state = 2 - M.icon_state = "box_1" + new_frame.obj_integrity = new_frame.max_integrity * 0.5 //the frame is already half broken + transfer_fingerprints_to(new_frame) + /obj/machinery/obj_break(damage_flag) SHOULD_CALL_PARENT(TRUE) @@ -564,10 +582,7 @@ /obj/proc/default_unfasten_wrench(mob/user, obj/item/I, time = 20) //try to unwrench an object in a WONDERFUL DYNAMIC WAY if(!(flags_1 & NODECONSTRUCT_1) && I.tool_behaviour == TOOL_WRENCH) var/turf/ground = get_turf(src) - var/list/excluded_objects = list(type) - if(anchorables) - excluded_objects += anchorables - if(!anchored && ground.is_blocked_turf(exclude_mobs = TRUE, excluded_objects = excluded_objects)) + if(!anchored && ground.is_blocked_turf(exclude_mobs = TRUE, source_atom = src)) to_chat(user, "You fail to secure [src].") return CANT_UNFASTEN var/can_be_unfasten = can_be_unfasten_wrench(user) @@ -579,7 +594,7 @@ var/prev_anchored = anchored //as long as we're the same anchored state and we're either on a floor or are anchored, toggle our anchored state if(I.use_tool(src, user, time, extra_checks = CALLBACK(src, .proc/unfasten_wrench_check, prev_anchored, user))) - if(!anchored && ground.is_blocked_turf(exclude_mobs = TRUE, excluded_objects = excluded_objects))//i know what you tryin to sneak in + if(!anchored && ground.is_blocked_turf(exclude_mobs = TRUE, source_atom = src))//i know what you tryin to sneak in to_chat(user, "You fail to secure [src].") return CANT_UNFASTEN to_chat(user, "You [anchored ? "un" : ""]secure [src].") @@ -733,4 +748,3 @@ var/alertstr = "Network Alert: Hacking attempt detected[get_area(src)?" in [get_area_name(src, TRUE)]":". Unable to pinpoint location"]." for(var/mob/living/silicon/ai/AI in GLOB.player_list) to_chat(AI, alertstr) - diff --git a/code/game/machinery/cell_charger.dm b/code/game/machinery/cell_charger.dm index ddc1a0fe3b9..2b82ad34a03 100644 --- a/code/game/machinery/cell_charger.dm +++ b/code/game/machinery/cell_charger.dm @@ -9,7 +9,6 @@ power_channel = AREA_USAGE_EQUIP circuit = /obj/item/circuitboard/machine/cell_charger pass_flags = PASSTABLE - anchorables = list(/obj/structure/table) var/obj/item/stock_parts/cell/charging = null var/charge_rate = 250 diff --git a/code/game/machinery/doors/windowdoor.dm b/code/game/machinery/doors/windowdoor.dm index 5f79bf97b25..82e8bad72ce 100644 --- a/code/game/machinery/doors/windowdoor.dm +++ b/code/game/machinery/doors/windowdoor.dm @@ -17,6 +17,7 @@ CanAtmosPass = ATMOS_PASS_PROC interaction_flags_machine = INTERACT_MACHINE_WIRES_IF_OPEN | INTERACT_MACHINE_ALLOW_SILICON | INTERACT_MACHINE_OPEN_SILICON | INTERACT_MACHINE_REQUIRES_SILICON | INTERACT_MACHINE_OPEN network_id = NETWORK_DOOR_AIRLOCKS + set_dir_on_move = FALSE var/obj/item/electronics/airlock/electronics = null var/reinf = 0 var/shards = 2 @@ -110,20 +111,18 @@ . = ..() if(.) return - if(get_dir(loc, target) == dir) //Make sure looking at appropriate border - return - if(istype(mover, /obj/structure/window)) - var/obj/structure/window/W = mover - if(!valid_window_location(loc, W.ini_dir)) - return FALSE - else if(istype(mover, /obj/structure/windoor_assembly)) - var/obj/structure/windoor_assembly/W = mover - if(!valid_window_location(loc, W.ini_dir)) - return FALSE - else if(istype(mover, /obj/machinery/door/window) && !valid_window_location(loc, mover.dir)) + + if(get_dir(loc, target) == dir) return FALSE - else - return TRUE + + if(istype(mover, /obj/structure/window)) + var/obj/structure/window/moved_window = mover + return valid_window_location(loc, moved_window.dir, is_fulltile = moved_window.fulltile) + + if(istype(mover, /obj/structure/windoor_assembly) || istype(mover, /obj/machinery/door/window)) + return valid_window_location(loc, mover.dir, is_fulltile = FALSE) + + return TRUE /obj/machinery/door/window/CanAtmosPass(turf/T) if(get_dir(loc, T) == dir) @@ -261,7 +260,6 @@ WA.set_anchored(TRUE) WA.state= "02" WA.setDir(dir) - WA.ini_dir = dir WA.update_icon() WA.created_name = name diff --git a/code/game/objects/items/stacks/stack.dm b/code/game/objects/items/stacks/stack.dm index 0745ab26a7b..72d761b3a23 100644 --- a/code/game/objects/items/stacks/stack.dm +++ b/code/game/objects/items/stacks/stack.dm @@ -233,53 +233,46 @@ if(get_amount() < 1 && !is_cyborg) qdel(src) return - var/datum/stack_recipe/R = locate(params["ref"]) - if(!is_valid_recipe(R, recipes)) //href exploit protection + var/datum/stack_recipe/recipe = locate(params["ref"]) + if(!is_valid_recipe(recipe, recipes)) //href exploit protection return var/multiplier = text2num(params["multiplier"]) if(!multiplier || (multiplier <= 0)) //href exploit protection return - if(!building_checks(R, multiplier)) + if(!building_checks(recipe, multiplier)) return - if(R.time) + if(recipe.time) var/adjusted_time = 0 - usr.visible_message("[usr] starts building \a [R.title].", "You start building \a [R.title]...") - if(HAS_TRAIT(usr, R.trait_booster)) - adjusted_time = (R.time * R.trait_modifier) + usr.visible_message("[usr] starts building \a [recipe.title].", "You start building \a [recipe.title]...") + if(HAS_TRAIT(usr, recipe.trait_booster)) + adjusted_time = (recipe.time * recipe.trait_modifier) else - adjusted_time = R.time + adjusted_time = recipe.time if(!do_after(usr, adjusted_time, target = usr)) return - if(!building_checks(R, multiplier)) + if(!building_checks(recipe, multiplier)) return var/obj/O - if(R.max_res_amount > 1) //Is it a stack? - O = new R.result_type(usr.drop_location(), R.res_amount * multiplier) - else if(ispath(R.result_type, /turf)) + if(recipe.max_res_amount > 1) //Is it a stack? + O = new recipe.result_type(usr.drop_location(), recipe.res_amount * multiplier) + else if(ispath(recipe.result_type, /turf)) var/turf/T = usr.drop_location() if(!isturf(T)) return - T.PlaceOnTop(R.result_type, flags = CHANGETURF_INHERIT_AIR) + T.PlaceOnTop(recipe.result_type, flags = CHANGETURF_INHERIT_AIR) else - O = new R.result_type(usr.drop_location()) + O = new recipe.result_type(usr.drop_location()) if(O) O.setDir(usr.dir) - use(R.req_amount * multiplier) + use(recipe.req_amount * multiplier) - if(R.applies_mats && LAZYLEN(mats_per_unit)) + if(recipe.applies_mats && LAZYLEN(mats_per_unit)) if(isstack(O)) var/obj/item/stack/crafted_stack = O - crafted_stack.set_mats_per_unit(mats_per_unit, R.req_amount / R.res_amount) + crafted_stack.set_mats_per_unit(mats_per_unit, recipe.req_amount / recipe.res_amount) else - O.set_custom_materials(mats_per_unit, R.req_amount / R.res_amount) - - if(istype(O, /obj/structure/windoor_assembly)) - var/obj/structure/windoor_assembly/W = O - W.ini_dir = W.dir - else if(istype(O, /obj/structure/window)) - var/obj/structure/window/W = O - W.ini_dir = W.dir + O.set_custom_materials(mats_per_unit, recipe.req_amount / recipe.res_amount) if(QDELETED(O)) return //It's a stack and has already been merged @@ -305,50 +298,55 @@ return TRUE return ..() -/obj/item/stack/proc/building_checks(datum/stack_recipe/R, multiplier) - if (get_amount() < R.req_amount*multiplier) - if (R.req_amount*multiplier>1) - to_chat(usr, "You haven't got enough [src] to build \the [R.req_amount*multiplier] [R.title]\s!") +/obj/item/stack/proc/building_checks(datum/stack_recipe/recipe, multiplier) + if (get_amount() < recipe.req_amount*multiplier) + if (recipe.req_amount*multiplier>1) + to_chat(usr, "You haven't got enough [src] to build \the [recipe.req_amount*multiplier] [recipe.title]\s!") else - to_chat(usr, "You haven't got enough [src] to build \the [R.title]!") + to_chat(usr, "You haven't got enough [src] to build \the [recipe.title]!") return FALSE - var/turf/T = get_turf(usr) + var/turf/dest_turf = get_turf(usr) - var/obj/D = R.result_type - if(R.window_checks && !valid_window_location(T, initial(D.dir) == FULLTILE_WINDOW_DIR ? FULLTILE_WINDOW_DIR : usr.dir)) - to_chat(usr, "The [R.title] won't fit here!") - return FALSE - if(R.one_per_turf && (locate(R.result_type) in T)) - to_chat(usr, "There is another [R.title] here!") - return FALSE - if(R.on_floor) - if(!isfloorturf(T)) - to_chat(usr, "\The [R.title] must be constructed on the floor!") + // If we're making a window, we have some special snowflake window checks to do. + if(ispath(recipe.result_type, /obj/structure/window)) + var/obj/structure/window/result_path = recipe.result_type + if(!valid_window_location(dest_turf, usr.dir, is_fulltile = initial(result_path.fulltile))) + to_chat(usr, "The [recipe.title] won't fit here!") return FALSE - for(var/obj/AM in T) - if(istype(AM,/obj/structure/grille)) + + if(recipe.one_per_turf && (locate(recipe.result_type) in dest_turf)) + to_chat(usr, "There is another [recipe.title] here!") + return FALSE + + if(recipe.on_floor) + if(!isfloorturf(dest_turf)) + to_chat(usr, "\The [recipe.title] must be constructed on the floor!") + return FALSE + + for(var/obj/object in dest_turf) + if(istype(object, /obj/structure/grille)) continue - if(istype(AM,/obj/structure/table)) + if(istype(object, /obj/structure/table)) continue - if(istype(AM,/obj/structure/window)) - var/obj/structure/window/W = AM - if(!W.fulltile) + if(istype(object, /obj/structure/window)) + var/obj/structure/window/window_structure = object + if(!window_structure.fulltile) continue - if(AM.density) - to_chat(usr, "Theres a [AM.name] here. You cant make a [R.title] here!") + if(object.density) + to_chat(usr, "There is \a [object.name] here. You cant make \a [recipe.title] here!") return FALSE - if(R.placement_checks) - switch(R.placement_checks) + if(recipe.placement_checks) + switch(recipe.placement_checks) if(STACK_CHECK_CARDINALS) var/turf/step for(var/direction in GLOB.cardinals) - step = get_step(T, direction) - if(locate(R.result_type) in step) - to_chat(usr, "\The [R.title] must not be built directly adjacent to another!") + step = get_step(dest_turf, direction) + if(locate(recipe.result_type) in step) + to_chat(usr, "\The [recipe.title] must not be built directly adjacent to another!") return FALSE if(STACK_CHECK_ADJACENT) - if(locate(R.result_type) in range(1, T)) - to_chat(usr, "\The [R.title] must be constructed at least one tile away from others of its type!") + if(locate(recipe.result_type) in range(1, dest_turf)) + to_chat(usr, "\The [recipe.title] must be constructed at least one tile away from others of its type!") return FALSE return TRUE @@ -520,15 +518,12 @@ var/time = 0 var/one_per_turf = FALSE var/on_floor = FALSE - var/window_checks = FALSE var/placement_checks = FALSE var/applies_mats = FALSE var/trait_booster = null var/trait_modifier = 1 /datum/stack_recipe/New(title, result_type, req_amount = 1, res_amount = 1, max_res_amount = 1,time = 0, one_per_turf = FALSE, on_floor = FALSE, window_checks = FALSE, placement_checks = FALSE, applies_mats = FALSE, trait_booster = null, trait_modifier = 1) - - src.title = title src.result_type = result_type src.req_amount = req_amount @@ -537,7 +532,6 @@ src.time = time src.one_per_turf = one_per_turf src.on_floor = on_floor - src.window_checks = window_checks src.placement_checks = placement_checks src.applies_mats = applies_mats src.trait_booster = trait_booster diff --git a/code/game/objects/objs.dm b/code/game/objects/objs.dm index 0c6dd002999..6051f942b12 100644 --- a/code/game/objects/objs.dm +++ b/code/game/objects/objs.dm @@ -46,9 +46,6 @@ /// broadcasted to as long as the other guys network is on the same branch or above. var/network_id = null - ///List of object types that the item can be anchored on top of, such as a table - var/list/anchorables = null - /obj/vv_edit_var(vname, vval) if(vname == NAMEOF(src, obj_flags)) if ((obj_flags & DANGEROUS_POSSESSION) && !(vval & DANGEROUS_POSSESSION)) @@ -64,8 +61,6 @@ stack_trace("Invalid type [armor.type] found in .armor during /obj Initialize()") if(obj_integrity == null) obj_integrity = max_integrity - if(anchorables) - anchorables = string_list(anchorables) . = ..() //Do this after, else mat datums is mad. diff --git a/code/game/objects/structures/grille.dm b/code/game/objects/structures/grille.dm index 63b49b0988e..1a8e110107c 100644 --- a/code/game/objects/structures/grille.dm +++ b/code/game/objects/structures/grille.dm @@ -70,11 +70,13 @@ if(!isturf(loc)) return FALSE var/turf/T = loc - var/window_dir = the_rcd.window_size == RCD_WINDOW_FULLTILE ? FULLTILE_WINDOW_DIR : user.dir - if(!valid_window_location(T, window_dir)) + if(!ispath(the_rcd.window_type, /obj/structure/window)) + CRASH("Invalid window path type in RCD: [the_rcd.window_type]") + var/obj/structure/window/window_path = the_rcd.window_type + if(!valid_window_location(T, user.dir, is_fulltile = initial(window_path.fulltile))) return FALSE to_chat(user, "You construct the window.") - var/obj/structure/window/WD = new the_rcd.window_type(T, window_dir) + var/obj/structure/window/WD = new the_rcd.window_type(T, user.dir) WD.set_anchored(TRUE) return TRUE return FALSE @@ -190,7 +192,6 @@ else WD = new/obj/structure/window/fulltile(drop_location()) //normal window WD.setDir(dir_to_set) - WD.ini_dir = dir_to_set WD.set_anchored(FALSE) WD.state = 0 ST.use(2) diff --git a/code/game/objects/structures/railings.dm b/code/game/objects/structures/railings.dm index 15b0bd3ac96..00ed93933aa 100644 --- a/code/game/objects/structures/railings.dm +++ b/code/game/objects/structures/railings.dm @@ -6,8 +6,6 @@ density = TRUE anchored = TRUE climbable = TRUE - ///Initial direction of the railing. - var/ini_dir /obj/structure/railing/corner //aesthetic corner sharp edges hurt oof ouch icon_state = "railing_corner" @@ -18,10 +16,6 @@ . = ..() AddComponent(/datum/component/simple_rotation,ROTATION_ALTCLICK | ROTATION_CLOCKWISE | ROTATION_COUNTERCLOCKWISE | ROTATION_VERBS ,null,CALLBACK(src, .proc/can_be_rotated),CALLBACK(src,.proc/after_rotation)) -/obj/structure/railing/Initialize() - . = ..() - ini_dir = dir - /obj/structure/railing/attackby(obj/item/I, mob/living/user, params) ..() add_fingerprint(user) @@ -94,7 +88,7 @@ var/target_dir = turn(dir, rotation_type == ROTATION_CLOCKWISE ? -90 : 90) - if(!valid_window_location(loc, target_dir)) //Expanded to include rails, as well! + if(!valid_window_location(loc, target_dir, is_fulltile = FALSE)) //Expanded to include rails, as well! to_chat(user, "[src] cannot be rotated in that direction!") return FALSE return TRUE @@ -104,5 +98,4 @@ return TRUE /obj/structure/railing/proc/after_rotation(mob/user,rotation_type) - ini_dir = dir add_fingerprint(user) diff --git a/code/game/objects/structures/windoor_assembly.dm b/code/game/objects/structures/windoor_assembly.dm index 4888386d0ae..35552638275 100644 --- a/code/game/objects/structures/windoor_assembly.dm +++ b/code/game/objects/structures/windoor_assembly.dm @@ -18,8 +18,8 @@ anchored = FALSE density = FALSE dir = NORTH + set_dir_on_move = FALSE - var/ini_dir var/obj/item/electronics/airlock/electronics = null var/created_name = null @@ -33,7 +33,6 @@ ..() if(set_dir) setDir(set_dir) - ini_dir = dir air_update_turf(TRUE, TRUE) /obj/structure/windoor_assembly/Destroy() @@ -44,7 +43,6 @@ /obj/structure/windoor_assembly/Move() var/turf/T = loc . = ..() - setDir(ini_dir) move_update_air(T) /obj/structure/windoor_assembly/update_icon_state() @@ -52,18 +50,16 @@ /obj/structure/windoor_assembly/CanAllowThrough(atom/movable/mover, turf/target) . = ..() - if(get_dir(loc, target) == dir) //Make sure looking at appropriate border + + if(get_dir(loc, target) == dir) return + if(istype(mover, /obj/structure/window)) - var/obj/structure/window/W = mover - if(!valid_window_location(loc, W.ini_dir)) - return FALSE - else if(istype(mover, /obj/structure/windoor_assembly)) - var/obj/structure/windoor_assembly/W = mover - if(!valid_window_location(loc, W.ini_dir)) - return FALSE - else if(istype(mover, /obj/machinery/door/window) && !valid_window_location(loc, mover.dir)) - return FALSE + var/obj/structure/window/moved_window = mover + return valid_window_location(loc, moved_window.dir, is_fulltile = moved_window.fulltile) + + if(istype(mover, /obj/structure/windoor_assembly) || istype(mover, /obj/machinery/door/window)) + return valid_window_location(loc, mover.dir, is_fulltile = FALSE) /obj/structure/windoor_assembly/CanAtmosPass(turf/T) if(get_dir(loc, T) == dir) @@ -321,13 +317,12 @@ return FALSE var/target_dir = turn(dir, rotation_type == ROTATION_CLOCKWISE ? -90 : 90) - if(!valid_window_location(loc, target_dir)) + if(!valid_window_location(loc, target_dir, is_fulltile = FALSE)) to_chat(user, "[src] cannot be rotated in that direction!") return FALSE return TRUE /obj/structure/windoor_assembly/proc/after_rotation(mob/user) - ini_dir = dir update_icon() //Flips the windoor assembly, determines whather the door opens to the left or the right diff --git a/code/game/objects/structures/window.dm b/code/game/objects/structures/window.dm index fedfa08291d..c7fc3edf3bb 100644 --- a/code/game/objects/structures/window.dm +++ b/code/game/objects/structures/window.dm @@ -14,7 +14,7 @@ CanAtmosPass = ATMOS_PASS_PROC rad_insulation = RAD_VERY_LIGHT_INSULATION pass_flags_self = PASSGLASS - var/ini_dir = null + set_dir_on_move = FALSE var/state = WINDOW_OUT_OF_FRAME var/reinf = FALSE var/heat_resistance = 800 @@ -32,7 +32,6 @@ flags_ricochet = RICOCHET_HARD receive_ricochet_chance_mod = 0.5 - /obj/structure/window/examine(mob/user) . = ..() if(reinf) @@ -57,7 +56,6 @@ if(reinf && anchored) state = RWINDOW_SECURE - ini_dir = dir air_update_turf(TRUE, TRUE) if(fulltile) @@ -99,33 +97,25 @@ if(current_size >= STAGE_FIVE) deconstruct(FALSE) -/obj/structure/window/setDir(direct) - if(!fulltile) - ..() - else - ..(FULLTILE_WINDOW_DIR) - /obj/structure/window/CanAllowThrough(atom/movable/mover, turf/target) . = ..() if(.) return - if(dir == FULLTILE_WINDOW_DIR) - return FALSE //full tile window, you can't move into it! - var/attempted_dir = get_dir(loc, target) - if(attempted_dir == dir) - return - if(istype(mover, /obj/structure/window)) - var/obj/structure/window/W = mover - if(!valid_window_location(loc, W.ini_dir)) - return FALSE - else if(istype(mover, /obj/structure/windoor_assembly)) - var/obj/structure/windoor_assembly/W = mover - if(!valid_window_location(loc, W.ini_dir)) - return FALSE - else if(istype(mover, /obj/machinery/door/window) && !valid_window_location(loc, mover.dir)) + + if(fulltile) return FALSE - else if(attempted_dir != dir) - return TRUE + + if(get_dir(loc, target) == dir) + return FALSE + + if(istype(mover, /obj/structure/window)) + var/obj/structure/window/moved_window = mover + return valid_window_location(loc, moved_window.dir, is_fulltile = moved_window.fulltile) + + if(istype(mover, /obj/structure/windoor_assembly) || istype(mover, /obj/machinery/door/window)) + return valid_window_location(loc, mover.dir, is_fulltile = FALSE) + + return TRUE /obj/structure/window/CheckExit(atom/movable/O, turf/target) if(istype(O) && (O.pass_flags & PASSGLASS)) @@ -285,14 +275,13 @@ var/target_dir = turn(dir, rotation_type == ROTATION_CLOCKWISE ? -90 : 90) - if(!valid_window_location(loc, target_dir)) + if(!valid_window_location(loc, target_dir, is_fulltile = fulltile)) to_chat(user, "[src] cannot be rotated in that direction!") return FALSE return TRUE /obj/structure/window/proc/after_rotation(mob/user,rotation_type) air_update_turf(TRUE, FALSE) - ini_dir = dir add_fingerprint(user) /obj/structure/window/proc/on_painted(is_dark_color) @@ -313,14 +302,13 @@ /obj/structure/window/Move() var/turf/T = loc . = ..() - setDir(ini_dir) if(anchored) move_update_air(T) /obj/structure/window/CanAtmosPass(turf/T) if(!anchored || !density) return TRUE - return !(FULLTILE_WINDOW_DIR == dir || dir == get_dir(loc, T)) + return !(fulltile || dir == get_dir(loc, T)) //This proc is used to update the icons of nearby windows. /obj/structure/window/proc/update_nearby_icons() @@ -359,7 +347,7 @@ /obj/structure/window/CanAStarPass(ID, to_dir) if(!density) return TRUE - if((dir == FULLTILE_WINDOW_DIR) || (dir == to_dir)) + if(fulltile || (dir == to_dir)) return FALSE return TRUE @@ -612,7 +600,6 @@ icon = 'icons/obj/smooth_structures/window.dmi' icon_state = "window-0" base_icon_state = "window" - dir = FULLTILE_WINDOW_DIR max_integrity = 50 fulltile = TRUE flags_1 = PREVENT_CLICK_UNDER_1 @@ -628,7 +615,6 @@ icon = 'icons/obj/smooth_structures/plasma_window.dmi' icon_state = "plasma_window-0" base_icon_state = "plasma_window" - dir = FULLTILE_WINDOW_DIR max_integrity = 300 fulltile = TRUE flags_1 = PREVENT_CLICK_UNDER_1 @@ -644,7 +630,6 @@ icon = 'icons/obj/smooth_structures/rplasma_window.dmi' icon_state = "rplasma_window-0" base_icon_state = "rplasma_window" - dir = FULLTILE_WINDOW_DIR state = RWINDOW_SECURE max_integrity = 1000 fulltile = TRUE @@ -662,7 +647,6 @@ icon = 'icons/obj/smooth_structures/reinforced_window.dmi' icon_state = "reinforced_window-0" base_icon_state = "reinforced_window" - dir = FULLTILE_WINDOW_DIR max_integrity = 150 fulltile = TRUE flags_1 = PREVENT_CLICK_UNDER_1 @@ -680,7 +664,6 @@ icon = 'icons/obj/smooth_structures/tinted_window.dmi' icon_state = "tinted_window-0" base_icon_state = "tinted_window" - dir = FULLTILE_WINDOW_DIR fulltile = TRUE flags_1 = PREVENT_CLICK_UNDER_1 smoothing_flags = SMOOTH_BITMASK @@ -701,7 +684,6 @@ icon = 'icons/obj/smooth_structures/shuttle_window.dmi' icon_state = "shuttle_window-0" base_icon_state = "shuttle_window" - dir = FULLTILE_WINDOW_DIR max_integrity = 150 wtype = "shuttle" fulltile = TRUE @@ -732,7 +714,6 @@ icon = 'icons/obj/smooth_structures/plastitanium_window.dmi' icon_state = "plastitanium_window-0" base_icon_state = "plastitanium_window" - dir = FULLTILE_WINDOW_DIR max_integrity = 1200 wtype = "shuttle" fulltile = TRUE @@ -758,7 +739,6 @@ icon = 'icons/obj/smooth_structures/paperframes.dmi' icon_state = "paperframes-0" base_icon_state = "paperframes" - dir = FULLTILE_WINDOW_DIR opacity = TRUE max_integrity = 15 fulltile = TRUE @@ -852,7 +832,6 @@ canSmoothWith = list(SMOOTH_GROUP_WINDOW_FULLTILE_BRONZE) fulltile = TRUE flags_1 = PREVENT_CLICK_UNDER_1 - dir = FULLTILE_WINDOW_DIR max_integrity = 50 glass_amount = 2 diff --git a/code/game/turfs/turf.dm b/code/game/turfs/turf.dm index d9a07f2ace6..319f8237e47 100755 --- a/code/game/turfs/turf.dm +++ b/code/game/turfs/turf.dm @@ -166,17 +166,25 @@ GLOBAL_LIST_EMPTY(station_turfs) /turf/proc/multiz_turf_new(turf/T, dir) SEND_SIGNAL(src, COMSIG_TURF_MULTIZ_NEW, T, dir) -///returns if the turf has something dense inside it. if exclude_mobs is true, skips dense mobs like fat yoshi. if exclude_object is true, it will exclude the excluded_object you sent through -/turf/proc/is_blocked_turf(exclude_mobs, list/excluded_objects = list()) +/** + * Check whether the specified turf is blocked by something dense inside it. + * + * Returns TRUE if the turf is blocked, FALSE otherwise. + * + * Arguments: + * * exclude_mobs - If TRUE, ignores dense mobs on the turf. + * * source_atom - If you're checking if the turf an atom is on is blocked, this will let you exclude the atom from this check so it doesn't block itself with its own density. + */ +/turf/proc/is_blocked_turf(exclude_mobs, source_atom) if(density) return TRUE for(var/i in contents) var/atom/movable/thing = i - var/excuded = FALSE - for(var/excluded in excluded_objects) - if(istype(thing, excluded)) - excuded = TRUE - if(!excuded && thing.density && (!exclude_mobs || !ismob(thing))) + if(thing == source_atom) + continue + // If the thing is dense AND we're including mobs or the thing isn't a mob AND if there's a source atom and + // it cannot pass through the thing on the turf, we consider the turf blocked. + if(thing.density && (!exclude_mobs || !ismob(thing)) && (source_atom && !thing.CanPass(source_atom, src))) return TRUE return FALSE diff --git a/code/modules/mob/living/living.dm b/code/modules/mob/living/living.dm index 9a73238bf10..fe6905d8aee 100644 --- a/code/modules/mob/living/living.dm +++ b/code/modules/mob/living/living.dm @@ -205,13 +205,23 @@ if(!client && (mob_size < MOB_SIZE_SMALL)) return now_pushing = TRUE - var/t = get_dir(src, AM) + var/dir_to_target = get_dir(src, AM) + + // If there's no dir_to_target then the player is on the same turf as the atom they're trying to push. + // This can happen when a player is stood on the same turf as a directional window. All attempts to push + // the window will fail as get_dir will return 0 and the player will be unable to move the window when + // it should be pushable. + // In this scenario, we will use the facing direction of the /mob/living attempting to push the atom as + // a fallback. + if(!dir_to_target) + dir_to_target = dir + var/push_anchored = FALSE if((AM.move_resist * MOVE_FORCE_CRUSH_RATIO) <= force) - if(move_crush(AM, move_force, t)) + if(move_crush(AM, move_force, dir_to_target)) push_anchored = TRUE if((AM.move_resist * MOVE_FORCE_FORCEPUSH_RATIO) <= force) //trigger move_crush and/or force_push regardless of if we can push it normally - if(force_push(AM, move_force, t, push_anchored)) + if(force_push(AM, move_force, dir_to_target, push_anchored)) push_anchored = TRUE if((AM.anchored && !push_anchored) || (force < (AM.move_resist * MOVE_FORCE_PUSH_RATIO))) now_pushing = FALSE @@ -219,7 +229,7 @@ if (istype(AM, /obj/structure/window)) var/obj/structure/window/W = AM if(W.fulltile) - for(var/obj/structure/window/win in get_step(W,t)) + for(var/obj/structure/window/win in get_step(W, dir_to_target)) now_pushing = FALSE return if(pulling == AM) @@ -227,8 +237,8 @@ var/current_dir if(isliving(AM)) current_dir = AM.dir - if(AM.Move(get_step(AM.loc, t), t, glide_size)) - Move(get_step(loc, t), t) + if(AM.Move(get_step(AM.loc, dir_to_target), dir_to_target, glide_size)) + Move(get_step(loc, dir_to_target), dir_to_target) if(current_dir) AM.setDir(current_dir) now_pushing = FALSE diff --git a/code/modules/reagents/chemistry/machinery/reagentgrinder.dm b/code/modules/reagents/chemistry/machinery/reagentgrinder.dm index e0ac343b565..0ba45939874 100644 --- a/code/modules/reagents/chemistry/machinery/reagentgrinder.dm +++ b/code/modules/reagents/chemistry/machinery/reagentgrinder.dm @@ -12,7 +12,6 @@ circuit = /obj/item/circuitboard/machine/reagentgrinder pass_flags = PASSTABLE resistance_flags = ACID_PROOF - anchorables = list(/obj/structure/table) var/operating = FALSE var/obj/item/reagent_containers/beaker = null var/limit = 10 diff --git a/code/modules/unit_tests/_unit_tests.dm b/code/modules/unit_tests/_unit_tests.dm index 737fd3b6325..fc7ebbb707c 100644 --- a/code/modules/unit_tests/_unit_tests.dm +++ b/code/modules/unit_tests/_unit_tests.dm @@ -60,6 +60,7 @@ #include "pills.dm" #include "plantgrowth_tests.dm" #include "projectiles.dm" +#include "rcd.dm" #include "reagent_id_typos.dm" #include "reagent_mod_expose.dm" #include "reagent_mod_procs.dm" diff --git a/code/modules/unit_tests/rcd.dm b/code/modules/unit_tests/rcd.dm new file mode 100644 index 00000000000..989ac8c3b9c --- /dev/null +++ b/code/modules/unit_tests/rcd.dm @@ -0,0 +1,46 @@ +/** + * Simple unit test to ensure there's no regression in behaviour where machine frames should not be stacked. + * + * We attempt to use the RCD to build multiple stacked machine frames on a turf. If we end up with any number that + * is not equal to 1, this means we've either built no machine frames (bad) or built more than one (regression). + * + * If this is successful, we attempt to spawn in some no-density machines that result in machine frames and we run + * the test again on our turf containing our single frame, deconstructing the machines! This should also not spawn + * any stacked machine frames. + */ +/datum/unit_test/frame_stacking/Run() + // First test - RCDs stacking frames. + var/obj/item/construction/rcd/rcd = allocate(/obj/item/construction/rcd/combat/admin) + var/mob/living/carbon/human/engineer = allocate(/mob/living/carbon/human) + + engineer.put_in_hands(rcd, forced = TRUE) + + rcd.mode = RCD_MACHINE + + var/list/adjacent_turfs = get_adjacent_open_turfs(engineer) + + if(!length(adjacent_turfs)) + Fail("RCD Test failed - Lack of adjacent open turfs. This may be an issue with the unit test.") + + var/turf/adjacent_turf = adjacent_turfs[1] + + for(var/i in 1 to 10) + adjacent_turf.rcd_act(engineer, rcd, rcd.mode) + + var/frame_count = 0 + for(var/obj/structure/frame/machine_frame in adjacent_turf.contents) + frame_count++ + + TEST_ASSERT_EQUAL(frame_count, 1, "Expected RCD machine frame stacking test to end up with exactly 1 machine frame.") + + // Second test - Deconstructing stacked machines to stack frames. We'll recycle our old turf to accomplish this. + for(var/i in 1 to 10) + // This should be a type path to a machine with no density, that can be wrenched on a turf with another machine of the same type. + var/obj/machinery/new_machine = new /obj/machinery/recharger(adjacent_turf) + new_machine.deconstruct(TRUE) + + frame_count = 0 + for(var/obj/structure/frame/machine_frame in adjacent_turf.contents) + frame_count++ + + TEST_ASSERT_EQUAL(frame_count, 1, "Expected no density machine deconstruction frame stacking test to end up with exactly 1 machine frame.")