diff --git a/code/__DEFINES/combat_defines.dm b/code/__DEFINES/combat_defines.dm index 11b7f38ea6c..2ed4f252cce 100644 --- a/code/__DEFINES/combat_defines.dm +++ b/code/__DEFINES/combat_defines.dm @@ -166,3 +166,6 @@ #define AUTOFIRE_STAT_ALERT (1<<1) /// Gun is shooting. #define AUTOFIRE_STAT_FIRING (1<<2) + +/// Multiplier for wall damage for comparison with object integrity. +#define OBJ_INTEGRITY_TO_WALL_DAMAGE 10 diff --git a/code/__DEFINES/is_helpers.dm b/code/__DEFINES/is_helpers.dm index 2bf287766b3..fe3f1cb3f94 100644 --- a/code/__DEFINES/is_helpers.dm +++ b/code/__DEFINES/is_helpers.dm @@ -155,14 +155,6 @@ GLOBAL_LIST_INIT(glass_sheet_types, typecacheof(list( // Modsuits #define ismodcontrol(A) istype(A, /obj/item/mod/control) -// Meteors -GLOBAL_LIST_INIT(turfs_pass_meteor, typecacheof(list( - /turf/simulated/floor/plating/asteroid, - /turf/space -))) - -#define ispassmeteorturf(A) (is_type_in_typecache(A, GLOB.turfs_pass_meteor)) - #define is_screen_atom(A) istype(A, /atom/movable/screen) #define is_multi_tile_object(atom) (atom?.bound_width > world.icon_size || atom?.bound_height > world.icon_size) diff --git a/code/__DEFINES/movement_defines.dm b/code/__DEFINES/movement_defines.dm index 0742f43f946..c76d019dc80 100644 --- a/code/__DEFINES/movement_defines.dm +++ b/code/__DEFINES/movement_defines.dm @@ -24,6 +24,8 @@ #define MOVEMENT_LOOP_NO_DIR_UPDATE (1<<3) ///Controls how the loop will set momentum_change in its Move call. #define MOVEMENT_LOOP_NO_MOMENTUM_CHANGE (1<<4) +/// Should we forceMove instead of Move? +#define MOVEMENT_LOOP_FORCE_MOVE (1<<5) // Movement loop status flags /// Has the loop been paused, soon to be resumed? diff --git a/code/controllers/subsystem/movement/movement_types.dm b/code/controllers/subsystem/movement/movement_types.dm index 4ca2e82b005..21dfe80ac90 100644 --- a/code/controllers/subsystem/movement/movement_types.dm +++ b/code/controllers/subsystem/movement/movement_types.dm @@ -121,7 +121,7 @@ var/old_loc = moving.loc owner?.processing_move_loop_flags = flags - var/result = move() //Result is an enum value. Enums defined in __DEFINES/movement.dm + var/result = move() // Result is an enum value. Enums defined in __DEFINES/movement_defines.dm if(moving) var/direction = get_dir(old_loc, moving.loc) SEND_SIGNAL(moving, COMSIG_MOVABLE_MOVED_FROM_LOOP, src, old_dir, direction) @@ -183,7 +183,7 @@ * timeout - Time in deci-seconds until the moveloop self expires. Defaults to infinity * subsystem - The movement subsystem to use. Defaults to SSmovement. Only one loop can exist for any one subsystem * priority - Defines how different move loops override each other. Lower numbers beat higher numbers, equal defaults to what currently exists. Defaults to MOVEMENT_DEFAULT_PRIORITY - * flags - Set of bitflags that effect move loop behavior in some way. Check _DEFINES/movement.dm + * flags - Set of bitflags that effect move loop behavior in some way. Check __DEFINES/movement_defines.dm * **/ /datum/move_manager/proc/move(moving, direction, delay, timeout, subsystem, priority, flags, datum/extra_info) @@ -206,38 +206,14 @@ /datum/move_loop/move/move() var/atom/old_loc = moving.loc - moving.Move(get_step(moving, direction), direction, FALSE, !(flags & MOVEMENT_LOOP_NO_DIR_UPDATE), !(flags & MOVEMENT_LOOP_NO_MOMENTUM_CHANGE)) + if(flags & MOVEMENT_LOOP_FORCE_MOVE) + moving.forceMove(get_step(moving, direction)) + else + moving.Move(get_step(moving, direction), direction, FALSE, !(flags & MOVEMENT_LOOP_NO_DIR_UPDATE), !(flags & MOVEMENT_LOOP_NO_MOMENTUM_CHANGE)) // We cannot rely on the return value of Move(), we care about teleports and it doesn't // Moving also can be null on occasion, if the move deleted it and therefor us return old_loc != moving?.loc ? MOVELOOP_SUCCESS : MOVELOOP_FAILURE - -/** - * Like move(), but we don't care about collision at all - * - * Returns TRUE if the loop sucessfully started, or FALSE if it failed - * - * Arguments: - * moving - The atom we want to move - * direction - The direction we want to move in - * delay - How many deci-seconds to wait between fires. Defaults to the lowest value, 0.1 - * timeout - Time in deci-seconds until the moveloop self expires. Defaults to infinity - * subsystem - The movement subsystem to use. Defaults to SSmovement. Only one loop can exist for any one subsystem - * priority - Defines how different move loops override each other. Lower numbers beat higher numbers, equal defaults to what currently exists. Defaults to MOVEMENT_DEFAULT_PRIORITY - * flags - Set of bitflags that effect move loop behavior in some way. Check _DEFINES/movement.dm - * -**/ -/datum/move_manager/proc/force_move_dir(moving, direction, delay, timeout, subsystem, priority, flags, datum/extra_info) - return add_to_loop(moving, subsystem, /datum/move_loop/move/force, priority, flags, extra_info, delay, timeout, direction) - -/datum/move_loop/move/force - -/datum/move_loop/move/force/move() - var/atom/old_loc = moving.loc - moving.forceMove(get_step(moving, direction)) - return old_loc != moving?.loc ? MOVELOOP_SUCCESS : MOVELOOP_FAILURE - - /datum/move_loop/has_target ///The thing we're moving in relation to, either at or away from var/atom/target @@ -268,34 +244,6 @@ SIGNAL_HANDLER qdel(src) - -/** - * Used for force-move loops, similar to move_towards_legacy() but not quite the same - * - * Returns TRUE if the loop sucessfully started, or FALSE if it failed - * - * Arguments: - * moving - The atom we want to move - * chasing - The atom we want to move towards - * delay - How many deci-seconds to wait between fires. Defaults to the lowest value, 0.1 - * timeout - Time in deci-seconds until the moveloop self expires. Defaults to infinity - * subsystem - The movement subsystem to use. Defaults to SSmovement. Only one loop can exist for any one subsystem - * priority - Defines how different move loops override each other. Lower numbers beat higher numbers, equal defaults to what currently exists. Defaults to MOVEMENT_DEFAULT_PRIORITY - * flags - Set of bitflags that effect move loop behavior in some way. Check _DEFINES/movement.dm - * -**/ -/datum/move_manager/proc/force_move(moving, chasing, delay, timeout, subsystem, priority, flags, datum/extra_info) - return add_to_loop(moving, subsystem, /datum/move_loop/has_target/force_move, priority, flags, extra_info, delay, timeout, chasing) - -///Used for force-move loops -/datum/move_loop/has_target/force_move - -/datum/move_loop/has_target/force_move/move() - var/atom/old_loc = moving.loc - moving.forceMove(get_step(moving, get_dir(moving, target))) - return old_loc != moving?.loc ? MOVELOOP_SUCCESS : MOVELOOP_FAILURE - - /** * Used for following jps defined paths. The proc signature here's a bit long, I'm sorry * @@ -315,7 +263,7 @@ * timeout - Time in deci-seconds until the moveloop self expires. Defaults to infinity * subsystem - The movement subsystem to use. Defaults to SSmovement. Only one loop can exist for any one subsystem * priority - Defines how different move loops override each other. Lower numbers beat higher numbers, equal defaults to what currently exists. Defaults to MOVEMENT_DEFAULT_PRIORITY - * flags - Set of bitflags that effect move loop behavior in some way. Check _DEFINES/movement.dm + * flags - Set of bitflags that effect move loop behavior in some way. Check __DEFINES/movement_defines.dm * **/ /datum/move_manager/proc/jps_move(moving, @@ -442,7 +390,11 @@ var/turf/next_step = movement_path[1] var/atom/old_loc = moving.loc - moving.Move(next_step, get_dir(moving, next_step), FALSE, !(flags & MOVEMENT_LOOP_NO_DIR_UPDATE)) + + if(flags & MOVEMENT_LOOP_FORCE_MOVE) + moving.forceMove(next_step) + else + moving.Move(next_step, get_dir(moving, next_step), FALSE, !(flags & MOVEMENT_LOOP_NO_DIR_UPDATE)) . = (old_loc != moving?.loc) ? MOVELOOP_SUCCESS : MOVELOOP_FAILURE // this check if we're on exactly the next tile may be overly brittle for dense objects who may get bumped slightly @@ -493,7 +445,7 @@ * timeout - Time in deci-seconds until the moveloop self expires. Defaults to infinity * subsystem - The movement subsystem to use. Defaults to SSmovement. Only one loop can exist for any one subsystem * priority - Defines how different move loops override each other. Lower numbers beat higher numbers, equal defaults to what currently exists. Defaults to MOVEMENT_DEFAULT_PRIORITY - * flags - Set of bitflags that effect move loop behavior in some way. Check _DEFINES/movement.dm + * flags - Set of bitflags that effect move loop behavior in some way. Check __DEFINES/movement_defines.dm * **/ /datum/move_manager/proc/move_to(moving, chasing, min_dist, delay, timeout, subsystem, priority, flags, datum/extra_info) @@ -511,7 +463,10 @@ return var/atom/old_loc = moving.loc var/turf/next = get_step_to(moving, target) - moving.Move(next, get_dir(moving, next), FALSE, !(flags & MOVEMENT_LOOP_NO_DIR_UPDATE)) + if(flags & MOVEMENT_LOOP_FORCE_MOVE) + moving.forceMove(next) + else + moving.Move(next, get_dir(moving, next), FALSE, !(flags & MOVEMENT_LOOP_NO_DIR_UPDATE)) return old_loc != moving?.loc ? MOVELOOP_SUCCESS : MOVELOOP_FAILURE /** @@ -527,7 +482,7 @@ * timeout - Time in deci-seconds until the moveloop self expires. Defaults to infinity * subsystem - The movement subsystem to use. Defaults to SSmovement. Only one loop can exist for any one subsystem * priority - Defines how different move loops override each other. Lower numbers beat higher numbers, equal defaults to what currently exists. Defaults to MOVEMENT_DEFAULT_PRIORITY - * flags - Set of bitflags that effect move loop behavior in some way. Check _DEFINES/movement.dm + * flags - Set of bitflags that effect move loop behavior in some way. Check __DEFINES/movement_defines.dm * **/ /datum/move_manager/proc/move_away(moving, chasing, max_dist, delay, timeout, subsystem, priority, flags, datum/extra_info) @@ -545,7 +500,10 @@ return var/atom/old_loc = moving.loc var/turf/next = get_step_away(moving, target) - moving.Move(next, get_dir(moving, next), FALSE, !(flags & MOVEMENT_LOOP_NO_DIR_UPDATE)) + if(flags & MOVEMENT_LOOP_FORCE_MOVE) + moving.forceMove(next) + else + moving.Move(next, get_dir(moving, next), FALSE, !(flags & MOVEMENT_LOOP_NO_DIR_UPDATE)) return old_loc != moving?.loc ? MOVELOOP_SUCCESS : MOVELOOP_FAILURE @@ -562,7 +520,7 @@ * timeout - Time in deci-seconds until the moveloop self expires. Defaults to INFINITY * subsystem - The movement subsystem to use. Defaults to SSmovement. Only one loop can exist for any one subsystem * priority - Defines how different move loops override each other. Lower numbers beat higher numbers, equal defaults to what currently exists. Defaults to MOVEMENT_DEFAULT_PRIORITY - * flags - Set of bitflags that effect move loop behavior in some way. Check _DEFINES/movement.dm + * flags - Set of bitflags that effect move loop behavior in some way. Check __DEFINES/movement_defines.dm * **/ /datum/move_manager/proc/move_towards(moving, chasing, delay, home, timeout, subsystem, priority, flags, datum/extra_info) @@ -581,7 +539,7 @@ * timeout - Time in deci-seconds until the moveloop self expires. Defaults to INFINITY * subsystem - The movement subsystem to use. Defaults to SSmovement. Only one loop can exist for any one subsystem * priority - Defines how different move loops override each other. Lower numbers beat higher numbers, equal defaults to what currently exists. Defaults to MOVEMENT_DEFAULT_PRIORITY - * flags - Set of bitflags that effect move loop behavior in some way. Check _DEFINES/movement.dm + * flags - Set of bitflags that effect move loop behavior in some way. Check __DEFINES/movement_defines.dm * **/ /datum/move_manager/proc/home_onto(moving, chasing, delay, timeout, subsystem, priority, flags, datum/extra_info) @@ -589,32 +547,27 @@ ///Used as a alternative to GLOB.move_manager.home_onto /datum/move_loop/has_target/move_towards - ///The turf we want to move into, used for course correction - var/turf/moving_towards - ///Should we try and stay on the path, or is deviation alright + /// Should we track our target, so we won't end up in the wrong place if it moves or if something knocks us off course? var/home = FALSE - ///When this gets larger then 1 we move a turf - var/x_ticker = 0 - var/y_ticker = 0 - ///The rate at which we move, between 0 and 1 - var/x_rate = 1 - var/y_rate = 1 - //We store the signs of x and y seperately, because byond will round negative numbers down - //So doing all our operations with absolute values then multiplying them is easier - var/x_sign = 0 - var/y_sign = 0 + // This tracks our location with sub-tile precision, allowing us to move along a smooth line. We assume that we started at the center of the tile, so that our shifts along the shorter axis are centered in the movement path, rather than biased towards the start or end. It also makes floating point errors less problematic, because neither 0.4999 nor 0.5001 will cross a tile boundary. + var/precise_x + var/precise_y + /// The speed at which we move along each axis, between -1 and 1 + var/x_speed + var/y_speed /datum/move_loop/has_target/move_towards/setup(delay, timeout, atom/chasing, home = FALSE) . = ..() if(!.) return FALSE src.home = home + precise_x = moving.x + 0.5 + precise_y = moving.y + 0.5 - if(home) - if(ismovable(target)) - RegisterSignal(target, COMSIG_MOVABLE_MOVED, PROC_REF(update_slope)) //If it can move, update your slope when it does - RegisterSignal(moving, COMSIG_MOVABLE_MOVED, PROC_REF(handle_move)) - update_slope() + if(home && ismovable(target)) + // If we're following something that can move, make sure we keep moving towards it. + RegisterSignal(target, COMSIG_MOVABLE_MOVED, PROC_REF(update_angle)) + update_angle() /datum/move_loop/has_target/move_towards/compare_loops(datum/move_loop/loop_type, priority, flags, extra_info, delay, timeout, atom/chasing, home = FALSE) if(..() && home == src.home) @@ -625,41 +578,63 @@ if(home) if(ismovable(target)) UnregisterSignal(target, COMSIG_MOVABLE_MOVED) - if(moving) - UnregisterSignal(moving, COMSIG_MOVABLE_MOVED) return ..() /datum/move_loop/has_target/move_towards/move() - //Move our tickers forward a step, we're guaranteed at least one step forward because of how the code is written - if(x_rate) //Did you know that rounding by 0 throws a divide by 0 error? - x_ticker = FLOOR(x_ticker + x_rate, x_rate) - if(y_rate) - y_ticker = FLOOR(y_ticker + y_rate, y_rate) - - var/x = moving.x - var/y = moving.y - var/z = moving.z - - moving_towards = locate(x + round(x_ticker) * x_sign, y + round(y_ticker) * y_sign, z) - //The tickers serve as good methods of tracking remainder - if(x_ticker >= 1) - x_ticker = MODULUS(x_ticker, 1) //I swear to god if you somehow go up by one then one in a tick I'm gonna go mad - if(y_ticker >= 1) - y_ticker = MODULUS(x_ticker, 1) - var/atom/old_loc = moving.loc - moving.Move(moving_towards, get_dir(moving, moving_towards), FALSE, !(flags & MOVEMENT_LOOP_NO_DIR_UPDATE)) - - //YOU FOUND THEM! GOOD JOB - if(home && get_turf(moving) == get_turf(target)) - x_rate = 0 - y_rate = 0 + var/old_turf = get_turf(moving) + if(!old_turf) + // If we aren't anywhere, we should stop moving. + qdel(src) return - return old_loc != moving?.loc ? MOVELOOP_SUCCESS : MOVELOOP_FAILURE -/datum/move_loop/has_target/move_towards/proc/handle_move(source, atom/OldLoc, Dir, Forced = FALSE) - SIGNAL_HANDLER - if(moving.loc == moving_towards && home) //If we didn't go where we should have, update slope to account for the deviation - update_slope() + // If we're not where we expect to be along either axis, we re-center ourselves within the correct tile along that axis. + var/off_course = FALSE + if(floor(precise_x) != moving.x) + precise_x = moving.x + 0.5 + off_course = TRUE + if(floor(precise_y) != moving.y) + precise_y = moving.y + 0.5 + off_course = TRUE + // And if we're homing, get back on course. + if(home && off_course) + update_angle() + + // Update our position based on our current speed. + // We will always move at least one tile, because one of these is always 1 or -1. + precise_x += x_speed + precise_y += y_speed + + var/next_turf = locate(floor(precise_x), floor(precise_y), moving.z) + if(flags & MOVEMENT_LOOP_FORCE_MOVE) + moving.forceMove(next_turf) + else + moving.Move(next_turf, get_dir(old_turf, next_turf), FALSE, !(flags & MOVEMENT_LOOP_NO_DIR_UPDATE)) + + var/turf/here = get_turf(moving) + if(!here) + // If we aren't anywhere, we should stop moving. + qdel(src) + return + + // If we didn't move the way we expected, re-center ourselves along the relevant axis/axes. + off_course = FALSE + if(here.x != floor(precise_x)) + precise_x = here.x + 0.5 + off_course = TRUE + if(here.y != floor(precise_y)) + precise_y = here.y + 0.5 + off_course = TRUE + + // If we're homing, get back on course. + if(home && off_course) + update_angle() + else if(get_turf(moving) == get_turf(target)) + // YOU FOUND IT! GOOD JOB! + x_speed = 0 + y_speed = 0 + + // If we moved at all, that's a win. + return old_turf != here ? MOVELOOP_SUCCESS : MOVELOOP_FAILURE /datum/move_loop/has_target/move_towards/handle_no_target() if(home) @@ -667,46 +642,37 @@ target = null /** - * Recalculates the slope between our object and the target, sets our rates to it + * Recalculates the angle we're moving at, so that we get a smooth movement line, rather than awkwardly bending from orthogonal to diagonal (or vice versa) at some point. * - * The math below is reminiscent of something like y = mx + b - * Except we don't need to care about axis, since we do all our movement in steps of 1 - * Because of that all that matters is we only move one tile at a time - * So we take the smaller delta, divide it by the larger one, and get smaller step per large step - * Then we set the large step to 1, and we're done. This way we're guaranteed to never move more then a tile at once - * And we can have nice lines + * The way we set the angle is by adjusting the speed we move in each direction. + * We always move at full speed along the longer axis towards our target. + * For the other axis, we calculate the right speed to reach our target at the same time we did on the longer axis. + * The net result is that every time we move, we approach the target along the longer axis, but we only move along the shorter axis at regular intervals, creating as smooth a line as possible. **/ -/datum/move_loop/has_target/move_towards/proc/update_slope() - SIGNAL_HANDLER +/datum/move_loop/has_target/move_towards/proc/update_angle() + SIGNAL_HANDLER // COMSIG_MOVABLE_MOVED - //You'll notice this is rise over run, except we flip the formula upside down depending on the larger number - //This is so we never move more then one tile at once - var/delta_y = target.y - moving.y var/delta_x = target.x - moving.x - //It's more convienent to store delta x and y as absolute values - //and modify them right at the end then it is to deal with rounding errors - x_sign = (delta_x > 0) ? 1 : -1 - y_sign = (delta_y > 0) ? 1 : -1 - delta_x = abs(delta_x) - delta_y = abs(delta_y) + var/delta_y = target.y - moving.y + if(delta_x == 0 && delta_y == 0) + // We ain't goin nowhere. + x_speed = 0 + y_speed = 0 + return - if(delta_x >= delta_y) - if(delta_x == 0) //Just go up/down - x_rate = 0 - y_rate = 1 - return - x_rate = 1 - y_rate = delta_y / delta_x //rise over run, you know the deal + if(abs(delta_x) >= abs(delta_y)) + // We need to move farther in the X direction, so always move along X. + x_speed = sign(delta_x) + // Move along Y often enough to smoothly reach the target. + y_speed = delta_y / abs(delta_x) else - if(delta_y == 0) //Just go right/left - x_rate = 1 - y_rate = 0 - return - x_rate = delta_x / delta_y //Keep the larger step size at 1 - y_rate = 1 + // We need to move farther in the Y direction, so always move along Y. + y_speed = sign(delta_y) + // Move along X often enough to smoothly reach the target. + x_speed = delta_x / abs(delta_y) /** - * Wrapper for GLOB.move_manager.home_onto, not reccomended, as its movement ends up being a bit stilted + * Alternative to GLOB.move_manager.home_onto. Not reccomended, as it ends up putting a kink in the movement path if it's not directly along one of the 8 directions. * * Returns TRUE if the loop sucessfully started, or FALSE if it failed * @@ -717,7 +683,7 @@ * timeout - Time in deci-seconds until the moveloop self expires. Defaults to infinity * subsystem - The movement subsystem to use. Defaults to SSmovement. Only one loop can exist for any one subsystem * priority - Defines how different move loops override each other. Lower numbers beat higher numbers, equal defaults to what currently exists. Defaults to MOVEMENT_DEFAULT_PRIORITY - * flags - Set of bitflags that effect move loop behavior in some way. Check _DEFINES/movement.dm + * flags - Set of bitflags that effect move loop behavior in some way. Check __DEFINES/movement_defines.dm * **/ /datum/move_manager/proc/move_towards_legacy(moving, chasing, delay, timeout, subsystem, priority, flags, datum/extra_info) @@ -729,7 +695,10 @@ /datum/move_loop/has_target/move_towards_budget/move() var/turf/target_turf = get_step_towards(moving, target) var/atom/old_loc = moving.loc - moving.Move(target_turf, get_dir(moving, target_turf), FALSE, !(flags & MOVEMENT_LOOP_NO_DIR_UPDATE)) + if(flags & MOVEMENT_LOOP_FORCE_MOVE) + moving.forceMove(target_turf) + else + moving.Move(target_turf, get_dir(moving, target_turf), FALSE, !(flags & MOVEMENT_LOOP_NO_DIR_UPDATE)) return old_loc != moving?.loc ? MOVELOOP_SUCCESS : MOVELOOP_FAILURE /** @@ -744,7 +713,7 @@ * timeout - Time in deci-seconds until the moveloop self expires. This should be considered extremely non-optional as it will completely stun out the movement loop forever if unset. * subsystem - The movement subsystem to use. Defaults to SSmovement. Only one loop can exist for any one subsystem * priority - Defines how different move loops override each other. Lower numbers beat higher numbers, equal defaults to what currently exists. Defaults to MOVEMENT_DEFAULT_PRIORITY - * flags - Set of bitflags that effect move loop behavior in some way. Check _DEFINES/movement.dm + * flags - Set of bitflags that effect move loop behavior in some way. Check __DEFINES/movement_defines.dm */ /datum/move_manager/proc/freeze(moving, halted_turf, delay, timeout, subsystem, priority, flags, datum/extra_info) return add_to_loop(moving, subsystem, /datum/move_loop/freeze, priority, flags, extra_info, delay, timeout, halted_turf) @@ -767,7 +736,7 @@ * timeout - Time in deci-seconds until the moveloop self expires. Defaults to infinity * subsystem - The movement subsystem to use. Defaults to SSmovement. Only one loop can exist for any one subsystem * priority - Defines how different move loops override each other. Lower numbers beat higher numbers, equal defaults to what currently exists. Defaults to MOVEMENT_DEFAULT_PRIORITY - * flags - Set of bitflags that effect move loop behavior in some way. Check _DEFINES/movement.dm + * flags - Set of bitflags that effect move loop behavior in some way. Check __DEFINES/movement_defines.dm * **/ /datum/move_manager/proc/move_rand(moving, directions, delay, timeout, subsystem, priority, flags, datum/extra_info) @@ -802,7 +771,10 @@ var/testdir = pick(potential_dirs) var/turf/moving_towards = get_step(moving, testdir) var/atom/old_loc = moving.loc - moving.Move(moving_towards, testdir, FALSE, !(flags & MOVEMENT_LOOP_NO_DIR_UPDATE)) + if(flags & MOVEMENT_LOOP_FORCE_MOVE) + moving.forceMove(moving_towards) + else + moving.Move(moving_towards, testdir, FALSE, !(flags & MOVEMENT_LOOP_NO_DIR_UPDATE)) if(old_loc != moving?.loc) //If it worked, we're done return MOVELOOP_SUCCESS potential_dirs -= testdir @@ -819,7 +791,7 @@ * timeout - Time in deci-seconds until the moveloop self expires. Defaults to infinity * subsystem - The movement subsystem to use. Defaults to SSmovement. Only one loop can exist for any one subsystem * priority - Defines how different move loops override each other. Lower numbers beat higher numbers, equal defaults to what currently exists. Defaults to MOVEMENT_DEFAULT_PRIORITY - * flags - Set of bitflags that effect move loop behavior in some way. Check _DEFINES/movement.dm + * flags - Set of bitflags that effect move loop behavior in some way. Check __DEFINES/movement_defines.dm * **/ /datum/move_manager/proc/move_to_rand(moving, delay, timeout, subsystem, priority, flags, datum/extra_info) @@ -831,7 +803,10 @@ /datum/move_loop/move_to_rand/move() var/atom/old_loc = moving.loc var/turf/next = get_step_rand(moving) - moving.Move(next, get_dir(moving, next), FALSE, !(flags & MOVEMENT_LOOP_NO_DIR_UPDATE)) + if(flags & MOVEMENT_LOOP_FORCE_MOVE) + moving.forceMove(next) + else + moving.Move(next, get_dir(moving, next), FALSE, !(flags & MOVEMENT_LOOP_NO_DIR_UPDATE)) return old_loc != moving?.loc ? MOVELOOP_SUCCESS : MOVELOOP_FAILURE /** @@ -847,7 +822,7 @@ * timeout - Time in deci-seconds until the moveloop self expires. Defaults to infinity * subsystem - The movement subsystem to use. Defaults to SSmovement. Only one loop can exist for any one subsystem * priority - Defines how different move loops override each other. Lower numbers beat higher numbers, equal defaults to what currently exists. Defaults to MOVEMENT_DEFAULT_PRIORITY - * flags - Set of bitflags that effect move loop behavior in some way. Check _DEFINES/movement.dm + * flags - Set of bitflags that effect move loop behavior in some way. Check __DEFINES/movement_defines.dm * **/ /datum/move_manager/proc/ventcrawl(moving, chasing, delay, timeout, subsystem, priority, flags, skip_first = TRUE, datum/extra_info) diff --git a/code/game/objects/effects/meteors.dm b/code/game/objects/effects/meteors.dm index 6af792175a6..b9971236159 100644 --- a/code/game/objects/effects/meteors.dm +++ b/code/game/objects/effects/meteors.dm @@ -66,8 +66,8 @@ GLOBAL_LIST_INIT(meteors_gore, list(/obj/effect/meteor/meaty = 5, /obj/effect/me icon = 'icons/obj/meteor.dmi' icon_state = "small" density = TRUE - var/hits = 4 - var/hitpwr = EXPLODE_HEAVY //Level of ex_act to be called on hit. + max_integrity = 400 * OBJ_INTEGRITY_TO_WALL_DAMAGE + var/explosion_strength = EXPLODE_HEAVY // Level of ex_act to be called on hit. var/dest pass_flags = PASSTABLE var/heavy = FALSE @@ -78,20 +78,61 @@ GLOBAL_LIST_INIT(meteors_gore, list(/obj/effect/meteor/meaty = 5, /obj/effect/me var/list/meteordrop = list(/obj/item/stack/ore/iron) var/dropamt = 2 -/obj/effect/meteor/Move(atom/newloc, direction, glide_size_override = 0, update_dir = TRUE) - // Delete if we reach our goal or somehow leave the Z level. - if(z != z_original || loc == dest) +/obj/effect/meteor/Move(atom/destination) + // Nullspace is scary. + if(isnull(loc) || isnull(destination)) qdel(src) return FALSE - . = ..() //process movement... + // Quietly delete if we reach our goal or somehow leave the Z level. + if(z != z_original || loc == dest || destination.z != z_original || destination == dest) + qdel(src) + return FALSE - if(.)//.. if did move, ram the turf we get in - var/turf/T = get_turf(loc) - ram_turf(T) + // Dead meteors hit no atoms. + if(obj_integrity <= 0 || QDELETED(src)) + return FALSE - if(prob(10) && !ispassmeteorturf(T))//randomly takes a 'hit' from ramming - get_hit() + if(abs(destination.y - y) == 1 && abs(destination.x - x) == 1) + // Hit one of the turfs beside the diagonal. + crunch(locate(destination.x, y, z)) + + // Dead meteors hit no atoms. + if(obj_integrity <= 0 || QDELETED(src)) + return FALSE + + // Hit the target tile and move to it (even if we made it change). + forceMove(crunch(destination)) + + // Dead meteors don't move, either. + if(obj_integrity <= 0 || QDELETED(src)) + return FALSE + return TRUE + +/obj/effect/meteor/proc/crunch(turf/target) + // Keep track of the target coordinates so we can find the turf again if it changes. + var/target_x = target.x + var/target_y = target.y + var/target_z = target.z + + if(iswallturf(target)) + // Ram into the wall. + ram_wall(target) + + if(!target) + // We broke the turf, find it again. + target = locate(target_x, target_y, target_z) + + if(obj_integrity <= 0 || QDELETED(src)) + return target + + ram_turf_contents(target) + + if(!target) + // We broke the turf, find it again. + target = locate(target_x, target_y, target_z) + + return target /obj/effect/meteor/Destroy() if(timerid) @@ -112,36 +153,70 @@ GLOBAL_LIST_INIT(meteors_gore, list(/obj/effect/meteor/meaty = 5, /obj/effect/me /obj/effect/meteor/Process_Spacemove(movement_dir, continuous_move) return TRUE -/obj/effect/meteor/Bump(atom/A) - if(A) - ram_turf(get_turf(A)) - playsound(loc, meteorsound, 40, TRUE) - if(!istype(A, /obj/structure/railing)) - get_hit() +/obj/effect/meteor/proc/ram_wall(turf/simulated/wall/wall) + var/damage_cap = wall.damage_cap + if(wall.rotting) + damage_cap /= 10 + var/damage_needed = (damage_cap - wall.damage) * OBJ_INTEGRITY_TO_WALL_DAMAGE + if(damage_needed <= 0) + wall.dismantle_wall() + else if(damage_needed < obj_integrity) + obj_integrity -= damage_needed + wall.dismantle_wall() + else + wall.take_damage(obj_integrity / OBJ_INTEGRITY_TO_WALL_DAMAGE) + if(wall) // In case we somehow broke it despite not thinking we could. + wall.ex_act(explosion_strength) + deconstruct(FALSE) -/obj/effect/meteor/proc/ram_turf(turf/T) - //first bust whatever is in the turf - for(var/thing in T) - var/atom/A = thing - if(thing == src) +/obj/effect/meteor/proc/ram_turf_contents(turf/T) + . = FALSE // Tracks whether we hit anything. + + for(var/atom/thing in T) + if(thing == src || QDELETED(thing)) continue + if(isliving(thing)) + . = TRUE // Hit a mob. + var/mob/living/living_thing = thing living_thing.visible_message("[src] slams into [living_thing].", "[src] slams into you!") - A.ex_act(hitpwr) + thing.ex_act(explosion_strength) - //then, ram the turf if it still exists - if(T) - T.ex_act(hitpwr) + if(!isobj(thing)) + continue -//process getting 'hit' by colliding with a dense object -//or randomly when ramming turfs -/obj/effect/meteor/proc/get_hit() - hits-- - if(hits <= 0) - make_debris() - meteor_effect() - qdel(src) + var/obj/obstacle = thing + if(obstacle.obj_integrity <= 0) + // It's already broken. + continue + + if(!obstacle.density) + // Doesn't block our way, but we still damage it on our way past. + obstacle.ex_act(explosion_strength) + continue + + . = TRUE // Hit an object. + + var/damage_needed = obstacle.calculate_oneshot_damage(BRUTE, MELEE) + if(obj_integrity > damage_needed) + obj_integrity -= damage_needed + obstacle.obj_break(MELEE) + obstacle.obj_destruction(MELEE) + else + obstacle.take_damage(obj_integrity, BRUTE, MELEE) + obstacle.ex_act(explosion_strength) + obj_integrity = 0 + deconstruct(FALSE) + return + + // Do some damage to the floor (if any) in passing. + T.ex_act(explosion_strength) + +/obj/effect/meteor/deconstruct(disassembled) + make_debris() + meteor_effect() + return ..() /obj/effect/meteor/ex_act() return @@ -155,9 +230,10 @@ GLOBAL_LIST_INIT(meteors_gore, list(/obj/effect/meteor/meaty = 5, /obj/effect/me /obj/effect/meteor/proc/make_debris() for(var/throws = dropamt, throws > 0, throws--) var/thing_to_spawn = pick(meteordrop) - new thing_to_spawn(get_turf(src)) + if(ispath(thing_to_spawn)) + new thing_to_spawn(get_turf(src)) -/obj/effect/meteor/proc/chase_target(atom/chasing, delay = 1) +/obj/effect/meteor/proc/chase_target(atom/chasing, delay = 0.5) set waitfor = FALSE if(chasing) GLOB.move_manager.home_onto(src, chasing, delay) @@ -204,17 +280,16 @@ GLOBAL_LIST_INIT(meteors_gore, list(/obj/effect/meteor/meaty = 5, /obj/effect/me goal = null return ..() -/obj/effect/meteor/fake/ram_turf(turf/T) - if(!isspaceturf(T)) - fail() - return - for(var/thing in T) - if(isobj(thing) && !iseffect(thing)) - fail() - return +/obj/effect/meteor/fake/ram_wall(turf/simulated/wall/wall) + fail() + return TRUE -/obj/effect/meteor/fake/get_hit() - return +/obj/effect/meteor/fake/ram_turf_contents(turf/T) + for(var/obj/thing in T) + if(!iseffect(thing) && !QDELETED(thing)) + fail() + return TRUE + return FALSE /obj/effect/meteor/fake/proc/succeed() if(istype(goal)) @@ -231,8 +306,8 @@ GLOBAL_LIST_INIT(meteors_gore, list(/obj/effect/meteor/meaty = 5, /obj/effect/me name = "space dust" icon_state = "dust" pass_flags = PASSTABLE | PASSGRILLE - hits = 1 - hitpwr = EXPLODE_LIGHT + max_integrity = 100 * OBJ_INTEGRITY_TO_WALL_DAMAGE + explosion_strength = EXPLODE_LIGHT meteorsound = 'sound/weapons/gunshots/gunshot_smg.ogg' meteordrop = list(/obj/item/stack/ore/glass) @@ -249,7 +324,7 @@ GLOBAL_LIST_INIT(meteors_gore, list(/obj/effect/meteor/meaty = 5, /obj/effect/me /obj/effect/meteor/big name = "big meteor" icon_state = "large" - hits = 6 + max_integrity = 600 * OBJ_INTEGRITY_TO_WALL_DAMAGE heavy = TRUE dropamt = 4 @@ -261,7 +336,7 @@ GLOBAL_LIST_INIT(meteors_gore, list(/obj/effect/meteor/meaty = 5, /obj/effect/me /obj/effect/meteor/flaming name = "flaming meteor" icon_state = "flaming" - hits = 5 + max_integrity = 500 * OBJ_INTEGRITY_TO_WALL_DAMAGE heavy = TRUE meteorsound = 'sound/effects/bamf.ogg' meteordrop = list(/obj/item/stack/ore/plasma) @@ -308,13 +383,13 @@ GLOBAL_LIST_INIT(meteors_gore, list(/obj/effect/meteor/meaty = 5, /obj/effect/me name = "tunguska meteor" icon_state = "flaming" desc = "Your life briefly passes before your eyes the moment you lay them on this monstrosity." - hits = 30 - hitpwr = EXPLODE_DEVASTATE + max_integrity = 3000 * OBJ_INTEGRITY_TO_WALL_DAMAGE + explosion_strength = EXPLODE_DEVASTATE heavy = TRUE meteorsound = 'sound/effects/bamf.ogg' meteordrop = list(/obj/item/stack/ore/plasma) -/obj/effect/meteor/tunguska/Move() +/obj/effect/meteor/tunguska/Move(atom/destination) . = ..() if(.) new /obj/effect/temp_visual/revenant(get_turf(src)) @@ -323,19 +398,25 @@ GLOBAL_LIST_INIT(meteors_gore, list(/obj/effect/meteor/meaty = 5, /obj/effect/me ..() explosion(loc, 5, 10, 15, 20, 0, cause = "[name]: End explosion") -/obj/effect/meteor/tunguska/Bump() - ..() +/obj/effect/meteor/tunguska/ram_wall() + . = ..() if(prob(20)) - explosion(loc, 2, 4, 6, 8, cause = "[name]: Bump explosion") + explosion(loc, 2, 4, 6, 8, cause = "[name]: Wall collision explosion") + +/obj/effect/meteor/tunguska/ram_turf_contents() + . = ..() + if(. && prob(20)) + explosion(loc, 2, 4, 6, 8, cause = "[name]: Object or mob collision explosion") //Meaty Ore /obj/effect/meteor/meaty name = "meaty ore" icon_state = "meateor" desc = "Just... don't think too hard about where this thing came from." - hits = 2 + max_integrity = 200 * OBJ_INTEGRITY_TO_WALL_DAMAGE heavy = TRUE meteorsound = 'sound/effects/blobattack.ogg' + var/bloodtype = /obj/effect/decal/cleanable/blood meteordrop = list(/obj/item/food/meat/human, /obj/item/organ/internal/heart, /obj/item/organ/internal/lungs, /obj/item/organ/internal/appendix) var/meteorgibs = /obj/effect/gibspawner/generic @@ -344,17 +425,16 @@ GLOBAL_LIST_INIT(meteors_gore, list(/obj/effect/meteor/meaty = 5, /obj/effect/me new meteorgibs(get_turf(src)) -/obj/effect/meteor/meaty/ram_turf(turf/T) - if(!isspaceturf(T)) - new /obj/effect/decal/cleanable/blood(T) - -/obj/effect/meteor/meaty/Bump(atom/A) - A.ex_act(hitpwr) - get_hit() +/obj/effect/meteor/meaty/ram_turf_contents() + . = ..() + var/turf/T = get_turf(src) + if(bloodtype && !isspaceturf(T)) + new bloodtype(T) //Meaty Ore Xeno edition /obj/effect/meteor/meaty/xeno color = "#5EFF00" + bloodtype = /obj/effect/decal/cleanable/blood/xeno meteordrop = list(/obj/item/food/monstermeat/xenomeat) meteorgibs = /obj/effect/gibspawner/xeno @@ -362,10 +442,6 @@ GLOBAL_LIST_INIT(meteors_gore, list(/obj/effect/meteor/meaty = 5, /obj/effect/me meteordrop += subtypesof(/obj/item/organ/internal/alien) return ..() -/obj/effect/meteor/meaty/xeno/ram_turf(turf/T) - if(!isspaceturf(T)) - new /obj/effect/decal/cleanable/blood/xeno(T) - ////////////////////////// //Spookoween meteors ///////////////////////// @@ -375,7 +451,7 @@ GLOBAL_LIST_INIT(meteors_gore, list(/obj/effect/meteor/meaty = 5, /obj/effect/me desc = "THE PUMPKING'S COMING!" icon = 'icons/obj/meteor_spooky.dmi' icon_state = "pumpkin" - hits = 10 + max_integrity = 1000 * OBJ_INTEGRITY_TO_WALL_DAMAGE heavy = TRUE dropamt = 1 meteordrop = list(/obj/item/clothing/head/hardhat/pumpkinhead, /obj/item/food/grown/pumpkin) diff --git a/code/game/objects/obj_defense.dm b/code/game/objects/obj_defense.dm index 0b16dd94f30..fd25a8593b5 100644 --- a/code/game/objects/obj_defense.dm +++ b/code/game/objects/obj_defense.dm @@ -30,7 +30,32 @@ armor_protection = armor.getRating(damage_flag) if(armor_protection) //Only apply weak-against-armor/hollowpoint effects if there actually IS armor. armor_protection = clamp((armor_protection * ((100 - armour_penetration_percentage) / 100)) - armour_penetration_flat, min(armor_protection, 0), 100) - return round(damage_amount * (100 - armor_protection)*0.01, DAMAGE_PRECISION) + var/damage_multiplier = (100 - armor_protection) / 100 + return round(damage_amount * damage_multiplier, DAMAGE_PRECISION) + +/// returns the amount of damage required to destroy this object in a single hit. +/obj/proc/calculate_oneshot_damage(damage_type, damage_flag = 0, attack_dir, armour_penetration_flat = 0, armour_penetration_percentage = 0) + if(obj_integrity <= 0) + return 0 + if(resistance_flags & INDESTRUCTIBLE) + return INFINITY + if(damage_type != BRUTE && damage_type != BURN) + return INFINITY + + var/armor_protection = 0 + if(damage_flag) + armor_protection = armor.getRating(damage_flag) + if(armor_protection) // Only apply weak-against-armor/hollowpoint effects if there actually IS armor. + armor_protection = clamp((armor_protection * ((100 - armour_penetration_percentage) / 100)) - armour_penetration_flat, min(armor_protection, 0), 100) + + var/damage_multiplier = (100 - armor_protection) / 100 + if(damage_multiplier <= 0) + return INFINITY + + var/oneshot = obj_integrity / damage_multiplier + if(damage_flag == MELEE) + oneshot = max(oneshot, damage_deflection) + return oneshot ///the sound played when the obj is damaged. /obj/proc/play_attack_sound(damage_amount, damage_type = BRUTE, damage_flag = 0) diff --git a/code/game/objects/structures/grille.dm b/code/game/objects/structures/grille.dm index eb6af458257..fece1842f02 100644 --- a/code/game/objects/structures/grille.dm +++ b/code/game/objects/structures/grille.dm @@ -11,8 +11,8 @@ layer = BELOW_OBJ_LAYER level = 3 armor = list(MELEE = 50, BULLET = 70, LASER = 70, ENERGY = 100, BOMB = 10, RAD = 100, FIRE = 0, ACID = 0) - max_integrity = 50 - integrity_failure = 20 + max_integrity = 300 + integrity_failure = 100 cares_about_temperature = TRUE rad_insulation_beta = RAD_BETA_BLOCKER var/rods_type = /obj/item/stack/rods diff --git a/code/game/objects/structures/railings.dm b/code/game/objects/structures/railings.dm index d738fb8bd92..7b3075310e7 100644 --- a/code/game/objects/structures/railings.dm +++ b/code/game/objects/structures/railings.dm @@ -10,6 +10,7 @@ climbable = TRUE layer = ABOVE_MOB_LAYER flags = ON_BORDER + max_integrity = 50 var/currently_climbed = FALSE var/mover_dir = null diff --git a/code/modules/events/immovable_rod.dm b/code/modules/events/immovable_rod.dm index dd6d39e604b..a3dee1d6532 100644 --- a/code/modules/events/immovable_rod.dm +++ b/code/modules/events/immovable_rod.dm @@ -29,20 +29,24 @@ In my current plan for it, 'solid' will be defined as anything with density == 1 anchored = TRUE var/z_original = 0 var/notify = TRUE - var/move_delay = 1 + var/move_delay = 0.5 var/atom/start var/atom/end /// The minimum amount of damage dealt to walls, relative to their max HP. var/wall_damage_min_fraction = 0.9 /// The maximum amount of damage dealt to walls, relative to their max HP. Values over 1 are useful for adjusting the probability of destroying the wall. var/wall_damage_max_fraction = 1.4 + /// Rods should usually not change their orientation, as they're *immovable*. But if it's a wizard spell, or if an admin spawns one, we should probably let it re-orient to follow its movement. + var/fixed_dir = FALSE -/obj/effect/immovablerod/New(atom/_start, atom/_end, delay) +/obj/effect/immovablerod/New(atom/_start, atom/_end, delay = 0.5) . = ..() ADD_TRAIT(src, TRAIT_NO_EDGE_TRANSITIONS, ROUNDSTART_TRAIT) start = _start end = _end loc = start + if(end && !fixed_dir) + setDir(get_dir(start, end)) z_original = z move_delay = delay if(notify) @@ -54,7 +58,7 @@ In my current plan for it, 'solid' will be defined as anything with density == 1 /obj/effect/immovablerod/proc/head_towards_dest() if(end?.z == z_original) - walk_towards(src, end, move_delay) + GLOB.move_manager.move_towards(src, end, move_delay, home=ismovable(end), timeout=1 MINUTES) /obj/effect/immovablerod/Topic(href, href_list) if(href_list["follow"]) @@ -79,32 +83,44 @@ In my current plan for it, 'solid' will be defined as anything with density == 1 if(!istype(newloc)) return ..() - if(!direction) - direction = get_dir(src, newloc) - forceMove(newloc) - setDir(direction) + if(!fixed_dir) + if(!direction) + direction = get_dir(src, newloc) + setDir(direction) + forceMove(newloc) + + if(z == newloc.z && abs(x - newloc.x) == 1 && abs(y - newloc.y) == 1) + clong(locate(x, newloc.y, z)) + clong(newloc) + + return TRUE + +/obj/effect/immovablerod/Process_Spacemove(movement_dir, continuous_move) + return TRUE + +/obj/effect/immovablerod/proc/clong(turf/victim) if(prob(10)) playsound(src, 'sound/effects/bang.ogg', 50, TRUE) audible_message("CLANG") - clong_turf(newloc) - if(isnull(newloc)) + smash_turf(victim) + if(isnull(victim)) // The turf is dead, long live the turf! - newloc = loc + victim = loc while(TRUE) var/hit_something_dense = FALSE - for(var/atom/victim as anything in newloc) - clong_thing(victim) - if(victim.density) + for(var/atom/obstacle as anything in victim) + clong_thing(obstacle) + if(obstacle.density) hit_something_dense = TRUE // Keep hitting stuff until there's nothing dense or we randomly go through it. if(!hit_something_dense || prob(25)) break -/obj/effect/immovablerod/proc/clong_turf(turf/victim) +/obj/effect/immovablerod/proc/smash_turf(turf/victim) if(!victim.density) return @@ -130,19 +146,25 @@ In my current plan for it, 'solid' will be defined as anything with density == 1 /obj/effect/immovablerod/event wall_damage_min_fraction = 0.33 wall_damage_max_fraction = 1.33 + fixed_dir = TRUE // The base chance to "damage" the floor when passing. This is not guaranteed to cause a full on hull breach. // Chance to expose the tile to space is like 60% of this value. var/floor_rip_chance = 40 // Chance to damage the floor if we didn't rip it. var/floor_graze_chance = 50 +/obj/effect/immovablerod/event/New() + . = ..() + // You might think we should set this to match the direction the rod is moving. But it's not. The rod is immovable. The *station* is moving. The rod don't care which direction the station came from. It's an immovable rod. It can't turn to face the station. + setDir(pick(NORTH, EAST)) + /obj/effect/immovablerod/event/Move() . = ..() if(loc == end) qdel(src) -/obj/effect/immovablerod/event/clong_turf(turf/victim) - if(!isfloorturf(victim)) +/obj/effect/immovablerod/event/smash_turf(turf/simulated/floor/victim) + if(!istype(victim)) return ..() var/turf/simulated/floor/T = victim @@ -155,6 +177,7 @@ In my current plan for it, 'solid' will be defined as anything with density == 1 return AddComponent(/datum/component/deadchat_control/immovable_rod, mode, list(), cooldown) /obj/effect/immovablerod/smite + fixed_dir = TRUE /// The target that we're gonna aim for between start and end var/obj/effect/portal/exit @@ -167,10 +190,10 @@ In my current plan for it, 'solid' will be defined as anything with density == 1 if(get_turf(src) == get_turf(end)) // our exit condition: get outta there kowalski var/target_turf = get_ranged_target_turf(src, dir, rand(1, 10)) - start = loc - walk(src, 0) + setDir(get_dir(src, exit)) exit = new /obj/effect/portal(target_turf, lifespan = 2 SECONDS) - walk_towards(src, exit, move_delay) + GLOB.move_manager.stop_looping(src) + GLOB.move_manager.move_towards(src, exit, move_delay, timeout = 2 SECONDS) else if(locate(exit) in get_turf(src)) QDEL_NULL(exit) qdel(src) @@ -183,4 +206,5 @@ In my current plan for it, 'solid' will be defined as anything with density == 1 */ /obj/effect/immovablerod/proc/walk_in_direction(direction) end = get_edge_target_turf(src, direction) - walk_towards(src, end, move_delay) + GLOB.move_manager.stop_looping(src) + GLOB.move_manager.move_towards(src, end, move_delay, timeout = 1 MINUTES)