From fce35d18a77bcff3e7a1b6eca79a1bd9cbc00d8d Mon Sep 17 00:00:00 2001 From: XeonMations <62395746+XeonMations@users.noreply.github.com> Date: Sat, 7 Dec 2024 20:18:08 +0200 Subject: [PATCH] [Port] Makes gravity event based, QOL additions as well as bugfixes regarding gravity (#11406) * floored mobs * rahhh * Update gravitygenerator.dm * rah * to do: gravity generator shenanigens * rahhh * Update fullscreen.dm * rahhhhh * asfas * Update movetype_handler.dm * aaa * fffhg * dassad * now it works * Update screen_alert.dmi * sadasd * CI/CD * Update screen_alert.dmi * 16h icon diff bot moment * Update gravity.dm --- code/__DEFINES/dcs/signals/signals_movable.dm | 4 +- code/__DEFINES/flags.dm | 4 + code/__DEFINES/gravity.dm | 17 +++- code/__DEFINES/traits/declarations.dm | 7 ++ code/__DEFINES/traits/sources.dm | 3 + code/_globalvars/bitfields.dm | 3 +- code/_globalvars/traits/_traits.dm | 6 +- code/_onclick/hud/alert.dm | 5 + code/controllers/subsystem/mapping.dm | 25 +++++ code/datums/components/caltrop.dm | 2 +- code/datums/components/chasm.dm | 2 +- code/datums/components/conveyor_movement.dm | 2 +- code/datums/components/slippery.dm | 2 +- code/datums/components/spikes.dm | 2 +- code/datums/components/squashable.dm | 2 +- code/datums/components/squeak.dm | 2 +- code/datums/elements/forced_gravity.dm | 17 ++-- code/datums/elements/movetype_handler.dm | 12 ++- .../proximity_monitor/fields/gravity.dm | 2 +- .../weather/weather_types/floor_is_lava.dm | 2 +- code/game/atoms.dm | 39 ++++---- code/game/objects/effects/mines.dm | 2 +- code/game/objects/items/handcuffs.dm | 86 ++++++++++-------- .../items/stacks/sheets/mineral/glass.dm | 2 +- code/game/objects/structures/kitchen_spike.dm | 2 + code/game/objects/structures/railings.dm | 4 +- code/game/objects/structures/tables_racks.dm | 2 +- code/game/turfs/open/_open.dm | 2 +- code/game/turfs/open/lava.dm | 2 +- code/game/turfs/open/openspace.dm | 7 ++ code/game/turfs/open/space/space.dm | 2 + code/game/turfs/turf.dm | 3 + .../clock_cult/traps/receivers/skewer.dm | 2 +- .../traitor/equipment/Malf_Modules.dm | 1 + code/modules/assembly/mousetrap.dm | 2 +- code/modules/clothing/shoes/magboots.dm | 26 +++++- .../space_management/zlevel_manager.dm | 1 + .../modules/mob/dead/new_player/new_player.dm | 3 + .../mob/living/carbon/carbon_movement.dm | 6 +- code/modules/mob/living/carbon/human/dummy.dm | 5 + .../mob/living/carbon/human/species.dm | 1 + code/modules/mob/living/init_signals.dm | 37 +++++++- code/modules/mob/living/life.dm | 28 +++--- code/modules/mob/living/living.dm | 63 +++++++++---- code/modules/mob/living/living_defines.dm | 3 + code/modules/mob/living/living_movement.dm | 54 ++++++++++- code/modules/mob/living/silicon/ai/life.dm | 2 - .../hostile/jungle/mega_arachnid.dm | 2 +- code/modules/mob/mob_movement.dm | 11 +-- code/modules/movespeed/modifiers/mobs.dm | 1 + code/modules/power/gravitygenerator.dm | 23 ++++- .../projectile/energy/net_snare.dm | 4 +- code/modules/shuttle/shuttle.dm | 4 +- icons/hud/screen_alert.dmi | Bin 136024 -> 136445 bytes 54 files changed, 403 insertions(+), 150 deletions(-) diff --git a/code/__DEFINES/dcs/signals/signals_movable.dm b/code/__DEFINES/dcs/signals/signals_movable.dm index 5b3ae553ddadc..6bcfbde38a980 100644 --- a/code/__DEFINES/dcs/signals/signals_movable.dm +++ b/code/__DEFINES/dcs/signals/signals_movable.dm @@ -69,8 +69,8 @@ // /datum/element/movetype_handler signals /// Called when the floating anim has to be temporarily stopped and restarted later: (timer) #define COMSIG_PAUSE_FLOATING_ANIM "pause_floating_anim" -/// From base of datum/element/movetype_handler/on_movement_type_trait_gain: (flag) +/// From base of datum/element/movetype_handler/on_movement_type_trait_gain: (flag, old_movement_type) #define COMSIG_MOVETYPE_FLAG_ENABLED "movetype_flag_enabled" -/// From base of datum/element/movetype_handler/on_movement_type_trait_loss: (flag) +/// From base of datum/element/movetype_handler/on_movement_type_trait_loss: (flag, old_movement_type) #define COMSIG_MOVETYPE_FLAG_DISABLED "movetype_flag_disabled" diff --git a/code/__DEFINES/flags.dm b/code/__DEFINES/flags.dm index 342b99743fd63..04e7d943ddb6e 100644 --- a/code/__DEFINES/flags.dm +++ b/code/__DEFINES/flags.dm @@ -141,6 +141,10 @@ GLOBAL_LIST_INIT(bitflags, list(1, 2, 4, 8, 16, 32, 64, 128, 256, 512, 1024, 204 #define FLOATING (1<<3) #define PHASING (1<<4) //! When moving, will Bump()/Cross() everything, but won't be stopped. #define THROWN (1<<5) //! while an atom is being thrown +#define UPSIDE_DOWN (1<<6) /// The mob is walking on the ceiling. Or is generally just, upside down. + +/// Combination flag for movetypes which, for all intents and purposes, mean the mob is not touching the ground +#define MOVETYPES_NOT_TOUCHING_GROUND (FLYING|FLOATING|UPSIDE_DOWN) //! ## Fire and Acid stuff, for resistance_flags #define LAVA_PROOF (1<<0) diff --git a/code/__DEFINES/gravity.dm b/code/__DEFINES/gravity.dm index eba2af77e122a..a638b22be3dd6 100644 --- a/code/__DEFINES/gravity.dm +++ b/code/__DEFINES/gravity.dm @@ -12,7 +12,18 @@ /// Singularity is stage 6 (11x11) #define STAGE_SIX 11 //! From supermatter shard -/// Anything above this is high gravity, anything below no grav until negative gravity -#define STANDARD_GRAVITY 1 +/** + * The point where gravity is negative enough to pull you upwards. + * That means walking checks for a ceiling instead of a floor, and you can fall "upwards" + * + * This should only be possible on multi-z maps because it works like shit on maps that aren't. + */ +#define NEGATIVE_GRAVITY -1 + +#define STANDARD_GRAVITY 1 //Anything above this is high gravity, anything below no grav until negative gravity /// The gravity strength threshold for high gravity damage. -#define GRAVITY_DAMAGE_TRESHOLD 3 +#define GRAVITY_DAMAGE_THRESHOLD 3 +/// The scaling factor for high gravity damage. +#define GRAVITY_DAMAGE_SCALING 0.5 +/// The maximum [BRUTE] damage a mob can take from high gravity per second. +#define GRAVITY_DAMAGE_MAXIMUM 1.5 diff --git a/code/__DEFINES/traits/declarations.dm b/code/__DEFINES/traits/declarations.dm index 7385f71578e02..dd6121db10c3d 100644 --- a/code/__DEFINES/traits/declarations.dm +++ b/code/__DEFINES/traits/declarations.dm @@ -317,4 +317,11 @@ Remember to update _globalvars/traits.dm if you're adding/removing/renaming trai /// The person with this trait always appears as 'unknown'. #define TRAIT_UNKNOWN "unknown" +/// We are ignoring gravity +#define TRAIT_IGNORING_GRAVITY "ignores_gravity" +/// We have some form of forced gravity acting on us +#define TRAIT_FORCED_GRAVITY "forced_gravity" +#define TRAIT_MOVE_UPSIDE_DOWN "move_upside_down" +#define TRAIT_NEGATES_GRAVITY "negates_gravity" + // END TRAIT DEFINES diff --git a/code/__DEFINES/traits/sources.dm b/code/__DEFINES/traits/sources.dm index fea059205dc72..d52b459750be9 100644 --- a/code/__DEFINES/traits/sources.dm +++ b/code/__DEFINES/traits/sources.dm @@ -133,3 +133,6 @@ #define NO_GRAVITY_TRAIT "no-gravity" #define LIFECANDLE_TRAIT "lifecandle" #define LEAPER_BUBBLE_TRAIT "leaper-bubble" +#define NEGATIVE_GRAVITY_TRAIT "negative-gravity" +/// Sources for TRAIT_IGNORING_GRAVITY +#define IGNORING_GRAVITY_NEGATION "ignoring_gravity_negation" diff --git a/code/_globalvars/bitfields.dm b/code/_globalvars/bitfields.dm index 2d121bb1829d5..8d75b40d3dd9d 100644 --- a/code/_globalvars/bitfields.dm +++ b/code/_globalvars/bitfields.dm @@ -153,7 +153,8 @@ DEFINE_BITFIELD(movement_type, list( "FLYING" = FLYING, "VENTCRAWLING" = VENTCRAWLING, "FLOATING" = FLOATING, - "PHASING" = PHASING + "PHASING" = PHASING, + "UPSIDE_DOWN" = UPSIDE_DOWN, )) DEFINE_BITFIELD(mat_container_flags, list( diff --git a/code/_globalvars/traits/_traits.dm b/code/_globalvars/traits/_traits.dm index bb40b3836fbf8..aab4c39b3d4b4 100644 --- a/code/_globalvars/traits/_traits.dm +++ b/code/_globalvars/traits/_traits.dm @@ -178,7 +178,11 @@ GLOBAL_LIST_INIT(traits_by_type, list( "TRAIT_NORADDAMAGE" = TRAIT_NORADDAMAGE, "TRAIT_MOBILE" = TRAIT_MOBILE, "INSTANT_DO_AFTER" = INSTANT_DO_AFTER, - "TRAIT_UNKNOWN" = TRAIT_UNKNOWN + "TRAIT_UNKNOWN" = TRAIT_UNKNOWN, + "TRAIT_IGNORING_GRAVITY" = TRAIT_IGNORING_GRAVITY, + "TRAIT_FORCED_GRAVITY" = TRAIT_FORCED_GRAVITY, + "TRAIT_MOVE_UPSIDE_DOWN" = TRAIT_MOVE_UPSIDE_DOWN, + "TRAIT_NEGATES_GRAVITY" = TRAIT_NEGATES_GRAVITY ), /obj/item/integrated_circuit = list( "TRAIT_COMPONENT_MMI" = TRAIT_COMPONENT_MMI, diff --git a/code/_onclick/hud/alert.dm b/code/_onclick/hud/alert.dm index dd5f5e8722411..51325b3bab772 100644 --- a/code/_onclick/hud/alert.dm +++ b/code/_onclick/hud/alert.dm @@ -248,6 +248,11 @@ If you're feeling frisky, examine yourself and click the underlined item to pull var/mob/living/carbon/M = usr return M.help_shake_act(M) +/atom/movable/screen/alert/negative + name = "Negative Gravity" + desc = "You're getting pulled upwards. While you won't have to worry about falling down anymore, you may accidentally fall upwards!" + icon_state = "negative" + /atom/movable/screen/alert/weightless name = "Weightless" desc = "Gravity has ceased affecting you, and you're floating around aimlessly. You'll need something large and heavy, like a \ diff --git a/code/controllers/subsystem/mapping.dm b/code/controllers/subsystem/mapping.dm index add3bf18afaa8..d2d8fba411651 100644 --- a/code/controllers/subsystem/mapping.dm +++ b/code/controllers/subsystem/mapping.dm @@ -55,6 +55,10 @@ SUBSYSTEM_DEF(mapping) var/datum/space_level/empty_space var/num_of_res_levels = 1 + ///shows the default gravity value for each z level. recalculated when gravity generators change. + ///List in the form: list(z level num = max generator gravity in that z level OR the gravity level trait) + var/list/gravity_by_z_level = list() + /datum/controller/subsystem/mapping/PreInit() ..() #ifdef FORCE_MAP @@ -126,6 +130,7 @@ SUBSYSTEM_DEF(mapping) generate_station_area_list() transit = add_new_zlevel("Transit/Reserved", list(ZTRAIT_RESERVED = TRUE)) initialize_reserved_level(transit.z_value) + calculate_default_z_level_gravities() return SS_INIT_SUCCESS /datum/controller/subsystem/mapping/fire(resumed) @@ -617,6 +622,9 @@ GLOBAL_LIST_EMPTY(the_station_areas) /// - Adds to z_list, and builds its area turfs /datum/controller/subsystem/mapping/proc/manage_z_level(datum/space_level/new_z, filled_with_space, contain_turfs = TRUE) z_list += new_z + + gravity_by_z_level.len += 1 + if(contain_turfs) build_area_turfs(new_z.z_value, filled_with_space) @@ -632,6 +640,10 @@ GLOBAL_LIST_EMPTY(the_station_areas) var/area/our_area = to_contain.loc our_area.contained_turfs += to_contain +/datum/controller/subsystem/mapping/proc/calculate_default_z_level_gravities() + for(var/z_level in 1 to length(z_list)) + calculate_z_level_gravity(z_level) + /datum/controller/subsystem/mapping/proc/generate_z_level_linkages() for(var/z_level in 1 to length(z_list)) generate_linkages_for_z_level(z_level) @@ -650,3 +662,16 @@ GLOBAL_LIST_EMPTY(the_station_areas) multiz_levels[z_level] = new /list(LARGEST_Z_LEVEL_INDEX) multiz_levels[z_level][Z_LEVEL_UP] = !!z_above multiz_levels[z_level][Z_LEVEL_DOWN] = !!z_below + +/datum/controller/subsystem/mapping/proc/calculate_z_level_gravity(z_level_number) + if(!isnum(z_level_number) || z_level_number < 1) + return FALSE + + var/max_gravity = 0 + + for(var/obj/machinery/gravity_generator/main/grav_gen as anything in GLOB.gravity_generators["[z_level_number]"]) + max_gravity = max(grav_gen.setting, max_gravity) + + max_gravity = max_gravity || level_trait(z_level_number, ZTRAIT_GRAVITY) || 0 //just to make sure no nulls + gravity_by_z_level[z_level_number] = max_gravity + return max_gravity diff --git a/code/datums/components/caltrop.dm b/code/datums/components/caltrop.dm index 67e76e0592e17..ff2c7611bc549 100644 --- a/code/datums/components/caltrop.dm +++ b/code/datums/components/caltrop.dm @@ -55,7 +55,7 @@ if(!(flags & CALTROP_BYPASS_SHOES) && (H.shoes || feetCover)) return - if((H.movement_type & (FLYING|FLOATING)) || (H.body_position == LYING_DOWN)|| H.buckled) + if((H.movement_type & MOVETYPES_NOT_TOUCHING_GROUND) || (H.body_position == LYING_DOWN)|| H.buckled) return var/damage = rand(min_damage, max_damage) diff --git a/code/datums/components/chasm.dm b/code/datums/components/chasm.dm index c439bcee66ce0..3db67bab6209a 100644 --- a/code/datums/components/chasm.dm +++ b/code/datums/components/chasm.dm @@ -69,7 +69,7 @@ return FALSE if(!isliving(AM) && !isobj(AM)) return FALSE - if(is_type_in_typecache(AM, forbidden_types) || AM.throwing || (AM.movement_type & (FLOATING|FLYING))) + if(is_type_in_typecache(AM, forbidden_types) || AM.throwing || (AM.movement_type & MOVETYPES_NOT_TOUCHING_GROUND)) return FALSE //Flies right over the chasm if(ismob(AM)) diff --git a/code/datums/components/conveyor_movement.dm b/code/datums/components/conveyor_movement.dm index 43fb9979a2cc9..6ec3f30830731 100644 --- a/code/datums/components/conveyor_movement.dm +++ b/code/datums/components/conveyor_movement.dm @@ -24,7 +24,7 @@ source.delay = speed //We use the default delay if(living_parent) var/mob/living/moving_mob = parent - if((moving_mob.movement_type & (FLOATING|FLYING)) && !moving_mob.stat) + if((moving_mob.movement_type & MOVETYPES_NOT_TOUCHING_GROUND) && !moving_mob.stat) return MOVELOOP_SKIP_STEP var/atom/movable/moving_parent = parent if(moving_parent.anchored || !moving_parent.has_gravity()) diff --git a/code/datums/components/slippery.dm b/code/datums/components/slippery.dm index 3b244d5e16f8a..39907f144d6d0 100644 --- a/code/datums/components/slippery.dm +++ b/code/datums/components/slippery.dm @@ -34,7 +34,7 @@ if(!isliving(arrived)) return var/mob/living/victim = arrived - if(!(victim.movement_type & (FLOATING|FLYING)) && victim.slip(knockdown_time, parent, lube_flags, paralyze_time, force_drop_items) && callback) + if(!(victim.movement_type & MOVETYPES_NOT_TOUCHING_GROUND) && victim.slip(knockdown_time, parent, lube_flags, paralyze_time, force_drop_items) && callback) callback.Invoke(victim) /datum/component/slippery/UnregisterFromParent() diff --git a/code/datums/components/spikes.dm b/code/datums/components/spikes.dm index be078a73721c7..a2a2daac40a68 100644 --- a/code/datums/components/spikes.dm +++ b/code/datums/components/spikes.dm @@ -58,7 +58,7 @@ if(ishuman(C)) var/mob/living/carbon/human/H = C var/feetCover = (H.wear_suit && (H.wear_suit.body_parts_covered & FEET)) || (H.w_uniform && (H.w_uniform.body_parts_covered & FEET)) - if((H.movement_type & (FLOATING|FLYING)) || H.body_position == LYING_DOWN || H.buckled || H.shoes || feetCover) + if((H.movement_type & MOVETYPES_NOT_TOUCHING_GROUND) || H.body_position == LYING_DOWN || H.buckled || H.shoes || feetCover) prick(H, 0.5) else prick(H, 2) diff --git a/code/datums/components/squashable.dm b/code/datums/components/squashable.dm index f9159614909fd..9091ef34e981d 100644 --- a/code/datums/components/squashable.dm +++ b/code/datums/components/squashable.dm @@ -53,7 +53,7 @@ if(isliving(crossing_movable)) var/mob/living/crossing_mob = crossing_movable - if(crossing_mob.mob_size > MOB_SIZE_SMALL && !(crossing_mob.movement_type & (FLOATING|FLYING))) + if(crossing_mob.mob_size > MOB_SIZE_SMALL && !(crossing_mob.movement_type & MOVETYPES_NOT_TOUCHING_GROUND)) if(HAS_TRAIT(crossing_mob, TRAIT_PACIFISM)) crossing_mob.visible_message("[crossing_mob] carefully steps over [parent_as_living].", "You carefully step over [parent_as_living] to avoid hurting it.") return diff --git a/code/datums/components/squeak.dm b/code/datums/components/squeak.dm index 8757402c7b2e9..9c1d694be9bc9 100644 --- a/code/datums/components/squeak.dm +++ b/code/datums/components/squeak.dm @@ -94,7 +94,7 @@ return if(istype(arrived, /obj/effect/dummy/phased_mob)) //don't squeek if they're in a phased/jaunting container. return - if(arrived.movement_type & (FLYING|FLOATING) || !arrived.has_gravity()) + if(arrived.movement_type & MOVETYPES_NOT_TOUCHING_GROUND || !arrived.has_gravity()) return var/atom/current_parent = parent if(isturf(current_parent?.loc)) diff --git a/code/datums/elements/forced_gravity.dm b/code/datums/elements/forced_gravity.dm index 17f2651dc485c..51a0c270e77fa 100644 --- a/code/datums/elements/forced_gravity.dm +++ b/code/datums/elements/forced_gravity.dm @@ -4,7 +4,7 @@ var/gravity var/ignore_space -/datum/element/forced_gravity/Attach(datum/target, gravity=1, ignore_space=FALSE) +/datum/element/forced_gravity/Attach(datum/target, gravity=1, ignore_space=FALSE, can_override = FALSE) . = ..() if(!isatom(target)) return ELEMENT_INCOMPATIBLE @@ -12,21 +12,26 @@ src.gravity = gravity src.ignore_space = ignore_space - RegisterSignal(target, COMSIG_ATOM_HAS_GRAVITY, PROC_REF(gravity_check)) + RegisterSignal(target, COMSIG_ATOM_HAS_GRAVITY, PROC_REF(gravity_check), override = can_override) if(isturf(target)) - RegisterSignal(target, COMSIG_TURF_HAS_GRAVITY, PROC_REF(turf_gravity_check)) + RegisterSignal(target, COMSIG_TURF_HAS_GRAVITY, PROC_REF(turf_gravity_check), override = can_override) + + ADD_TRAIT(target, TRAIT_FORCED_GRAVITY, REF(src)) /datum/element/forced_gravity/Detach(datum/source, force) . = ..() var/static/list/signals_b_gone = list(COMSIG_ATOM_HAS_GRAVITY, COMSIG_TURF_HAS_GRAVITY) UnregisterSignal(source, signals_b_gone) + REMOVE_TRAIT(source, TRAIT_FORCED_GRAVITY, REF(src)) /datum/element/forced_gravity/proc/gravity_check(datum/source, turf/location, list/gravs) SIGNAL_HANDLER - if(!ignore_space && isspaceturf(location)) - return + if(!ignore_space && location.force_no_gravity) + return FALSE gravs += gravity + return TRUE + /datum/element/forced_gravity/proc/turf_gravity_check(datum/source, atom/checker, list/gravs) SIGNAL_HANDLER - return gravity_check(null, source, gravs) + gravity_check(null, source, gravs) diff --git a/code/datums/elements/movetype_handler.dm b/code/datums/elements/movetype_handler.dm index 3e648d01c4f5a..b33800738307e 100644 --- a/code/datums/elements/movetype_handler.dm +++ b/code/datums/elements/movetype_handler.dm @@ -52,7 +52,9 @@ if(!(source.movement_type & (FLOATING|FLYING)) && (trait == TRAIT_MOVE_FLYING || trait == TRAIT_MOVE_FLOATING) && !paused_floating_anim_atoms[source] && !HAS_TRAIT(source, TRAIT_NO_FLOATING_ANIM)) DO_FLOATING_ANIM(source) source.movement_type |= flag - SEND_SIGNAL(source, COMSIG_MOVETYPE_FLAG_ENABLED, flag) + if((trait == TRAIT_MOVE_FLYING || trait == TRAIT_MOVE_FLOATING) && !(source.movement_type & (FLOATING|FLYING))) + stop_floating(source) + SEND_SIGNAL(source, COMSIG_MOVETYPE_FLAG_DISABLED, flag) /// Called when a movement type trait is removed from the movable. Disables the relative bitflag if it wasn't there in the compile-time bitfield. /datum/element/movetype_handler/proc/on_movement_type_trait_loss(atom/movable/source, trait) @@ -60,10 +62,14 @@ var/flag = GLOB.movement_type_trait_to_flag[trait] if(initial(source.movement_type) & flag) return + var/old_state = source.movement_type source.movement_type &= ~flag - if((trait == TRAIT_MOVE_FLYING || trait == TRAIT_MOVE_FLOATING) && !(source.movement_type & (FLOATING|FLYING))) + if((old_state & (FLOATING|FLYING)) && !(source.movement_type & (FLOATING|FLYING))) stop_floating(source) - SEND_SIGNAL(source, COMSIG_MOVETYPE_FLAG_DISABLED, flag) + var/turf/pitfall = source.loc //Things that don't fly fall in open space. + if(istype(pitfall)) + pitfall.zFall(source) + SEND_SIGNAL(source, COMSIG_MOVETYPE_FLAG_DISABLED, flag, old_state) /// Called when the TRAIT_NO_FLOATING_ANIM trait is added to the movable. Stops it from bobbing up and down. /datum/element/movetype_handler/proc/on_no_floating_anim_trait_gain(atom/movable/source, trait) diff --git a/code/datums/proximity_monitor/fields/gravity.dm b/code/datums/proximity_monitor/fields/gravity.dm index ccac71a6d8508..b55a6521c32ed 100644 --- a/code/datums/proximity_monitor/fields/gravity.dm +++ b/code/datums/proximity_monitor/fields/gravity.dm @@ -9,7 +9,7 @@ /datum/proximity_monitor/advanced/gravity/setup_field_turf(turf/T) . = ..() - T.AddElement(/datum/element/forced_gravity, gravity_value) + T.AddElement(/datum/element/forced_gravity, gravity_value, can_override = TRUE) modified_turfs[T] = gravity_value /datum/proximity_monitor/advanced/gravity/cleanup_field_turf(turf/T) diff --git a/code/datums/weather/weather_types/floor_is_lava.dm b/code/datums/weather/weather_types/floor_is_lava.dm index 9762499693560..61bf86a2c3c12 100644 --- a/code/datums/weather/weather_types/floor_is_lava.dm +++ b/code/datums/weather/weather_types/floor_is_lava.dm @@ -35,6 +35,6 @@ return if(!L.client) //Only sentient people are going along with it! return - if(L.movement_type & (FLOATING|FLYING)) + if(L.movement_type & MOVETYPES_NOT_TOUCHING_GROUND) return L.adjustFireLoss(3) diff --git a/code/game/atoms.dm b/code/game/atoms.dm index 9aa198d1ae78c..674d8de01175e 100644 --- a/code/game/atoms.dm +++ b/code/game/atoms.dm @@ -1897,6 +1897,12 @@ CREATION_TEST_IGNORE_SUBTYPES(/atom) * Sends signals [COMSIG_ATOM_HAS_GRAVITY] and [COMSIG_TURF_HAS_GRAVITY], both can force gravity with * the forced gravity var. * + * HEY JACKASS, LISTEN + * IF YOU ADD SOMETHING TO THIS PROC, MAKE SURE /mob/living ACCOUNTS FOR IT + * + * Living mobs treat gravity in an event based manner. We've decomposed this proc into different checks + * for them to use. If you add more to it, make sure you do that, or things will behave strangely + * * Gravity situations: * * No gravity if you're not in a turf * * No gravity if this atom is in is a space turf @@ -1909,32 +1915,25 @@ CREATION_TEST_IGNORE_SUBTYPES(/atom) if(!isturf(gravity_turf)) gravity_turf = get_turf(src) - if(!gravity_turf) + if(!gravity_turf)//no gravity in nullspace return FALSE var/list/forced_gravity = list() SEND_SIGNAL(src, COMSIG_ATOM_HAS_GRAVITY, gravity_turf, forced_gravity) - if(!length(forced_gravity)) - SEND_SIGNAL(gravity_turf, COMSIG_TURF_HAS_GRAVITY, src, forced_gravity) + SEND_SIGNAL(gravity_turf, COMSIG_TURF_HAS_GRAVITY, src, forced_gravity) if(length(forced_gravity)) - var/max_grav - for(var/i in forced_gravity) - max_grav = max(max_grav, i) - return max_grav + var/positive_grav = max(forced_gravity) + var/negative_grav = min(min(forced_gravity), 0) //negative grav needs to be below or equal to 0 - if(!gravity_turf.check_gravity()) // Turf never has gravity - return FALSE - var/area/A = get_area(gravity_turf) - if(A.has_gravity) // Areas which always has gravity - return TRUE - else if(SSmapping.level_trait(gravity_turf.z, ZTRAIT_GRAVITY)) // If the z-level always has gravity - return TRUE - else if(GLOB.gravity_generators["[gravity_turf.get_virtual_z_level()]"]) // If there's a gravity generator on our z level - var/max_grav = 0 - for(var/obj/machinery/gravity_generator/main/G in GLOB.gravity_generators["[gravity_turf.get_virtual_z_level()]"]) - max_grav = max(G.setting,max_grav) - return max_grav - return FALSE + //our gravity is sum of the most massive positive and negative numbers returned by the signal + //so that adding two forced_gravity elements with an effect size of 1 each doesnt add to 2 gravity + //but negative force gravity effects can cancel out positive ones + + return (positive_grav + negative_grav) + + var/area/turf_area = gravity_turf.loc + + return !gravity_turf.force_no_gravity && (SSmapping.gravity_by_z_level[gravity_turf.z] || turf_area.has_gravity) /* * Called when something made out of plasma is exposed to high temperatures. diff --git a/code/game/objects/effects/mines.dm b/code/game/objects/effects/mines.dm index b860bda0a83f1..7940d16e74694 100644 --- a/code/game/objects/effects/mines.dm +++ b/code/game/objects/effects/mines.dm @@ -125,7 +125,7 @@ /obj/effect/mine/proc/on_entered(datum/source, atom/movable/AM) SIGNAL_HANDLER - if(!isturf(loc) || AM.throwing || (AM.movement_type & (FLYING | FLOATING)) || !AM.has_gravity() || triggered) + if(!isturf(loc) || AM.throwing || (AM.movement_type & MOVETYPES_NOT_TOUCHING_GROUND) || !AM.has_gravity() || triggered) return if(ismob(AM)) checksmartmine(AM) diff --git a/code/game/objects/items/handcuffs.dm b/code/game/objects/items/handcuffs.dm index 1c16677c2f466..bb2e99f70d127 100644 --- a/code/game/objects/items/handcuffs.dm +++ b/code/game/objects/items/handcuffs.dm @@ -215,7 +215,7 @@ . = ..() update_icon() var/static/list/loc_connections = list( - COMSIG_ATOM_ENTERED = PROC_REF(spring_trap), + COMSIG_ATOM_ENTERED = PROC_REF(trap_stepped_on), ) AddElement(/datum/element/connect_loc, loc_connections) @@ -241,41 +241,53 @@ update_appearance() playsound(src, 'sound/effects/snap.ogg', 50, TRUE) -/obj/item/restraints/legcuffs/beartrap/proc/spring_trap(datum/source, AM as mob|obj) +/obj/item/restraints/legcuffs/beartrap/proc/trap_stepped_on(datum/source, atom/movable/entering, ...) SIGNAL_HANDLER - if(armed && isturf(loc)) - if(isliving(AM)) - var/mob/living/L = AM - var/snap = TRUE - if(istype(L.buckled, /obj/vehicle)) - var/obj/vehicle/ridden_vehicle = L.buckled - if(!ridden_vehicle.are_legs_exposed) //close the trap without injuring/trapping the rider if their legs are inside the vehicle at all times. - close_trap() - ridden_vehicle.visible_message("[ridden_vehicle] triggers \the [src].") - - if(L.movement_type & (FLYING|FLOATING)) //don't close the trap if they're flying/floating over it. - snap = FALSE - - var/def_zone = BODY_ZONE_CHEST - if(snap && iscarbon(L)) - var/mob/living/carbon/C = L - if(C.body_position == STANDING_UP) - def_zone = pick(BODY_ZONE_L_LEG, BODY_ZONE_R_LEG) - if(!C.legcuffed && C.num_legs >= 2) //beartrap can't cuff your leg if there's already a beartrap or legcuffs, or you don't have two legs. - C.legcuffed = src - forceMove(C) - C.update_equipment_speed_mods() - C.update_inv_legcuffed() - SSblackbox.record_feedback("tally", "handcuffs", 1, type) - else if(snap && isanimal(L)) - var/mob/living/simple_animal/SA = L - if(SA.mob_size <= MOB_SIZE_TINY) //don't close the trap if they're as small as a mouse. - snap = FALSE - if(snap) - close_trap() - L.visible_message("[L] triggers \the [src].", \ - "You trigger \the [src]!") - L.apply_damage(trap_damage, BRUTE, def_zone) + + spring_trap(entering) + +/** + * Tries to spring the trap on the target movable. + * + * This proc is safe to call without knowing if the target is valid or if the trap is armed. + * + * Does not trigger on tiny mobs. + * If ignore_movetypes is FALSE, does not trigger on floating / flying / etc. mobs. + */ +/obj/item/restraints/legcuffs/beartrap/proc/spring_trap(atom/movable/target, ignore_movetypes = FALSE, hit_prone = FALSE) + if(!armed || !isturf(loc) || !isliving(target)) + return + + var/mob/living/victim = target + if(istype(victim.buckled, /obj/vehicle)) + var/obj/vehicle/ridden_vehicle = victim.buckled + if(!ridden_vehicle.are_legs_exposed) //close the trap without injuring/trapping the rider if their legs are inside the vehicle at all times. + close_trap() + ridden_vehicle.visible_message("[ridden_vehicle] triggers \the [src].") + return + + //don't close the trap if they're as small as a mouse + if(victim.mob_size <= MOB_SIZE_TINY) + return + if(!ignore_movetypes && (victim.movement_type & MOVETYPES_NOT_TOUCHING_GROUND)) + return + + close_trap() + if(ignore_movetypes) + victim.visible_message("\The [src] ensnares [victim]!", \ + "\The [src] ensnares you!") + else + victim.visible_message("[victim] triggers \the [src].", \ + "You trigger \the [src]!") + var/def_zone = BODY_ZONE_CHEST + if(iscarbon(victim) && (victim.body_position == STANDING_UP || hit_prone)) + var/mob/living/carbon/carbon_victim = victim + def_zone = pick(BODY_ZONE_L_LEG, BODY_ZONE_R_LEG) + if(!carbon_victim.legcuffed && carbon_victim.num_legs >= 2) //beartrap can't cuff your leg if there's already a beartrap or legcuffs, or you don't have two legs. + INVOKE_ASYNC(carbon_victim, TYPE_PROC_REF(/mob/living/carbon, equip_to_slot), src, ITEM_SLOT_LEGCUFFED) + SSblackbox.record_feedback("tally", "handcuffs", 1, type) + + victim.apply_damage(trap_damage, BRUTE, def_zone) /obj/item/restraints/legcuffs/beartrap/energy name = "energy snare" @@ -296,7 +308,7 @@ qdel(src) /obj/item/restraints/legcuffs/beartrap/energy/attack_hand(mob/user, list/modifiers) - spring_trap(null, user) + spring_trap(user) return ..() /obj/item/restraints/legcuffs/beartrap/energy/cyborg @@ -371,7 +383,7 @@ /obj/item/restraints/legcuffs/bola/energy/ensnare(mob/living/carbon/C) var/obj/item/restraints/legcuffs/beartrap/B = new /obj/item/restraints/legcuffs/beartrap/energy/cyborg(get_turf(C)) - B.spring_trap(null, C) + B.spring_trap(C, ignore_movetypes = TRUE) qdel(src) /obj/item/restraints/legcuffs/bola/energy/emp_act(severity) diff --git a/code/game/objects/items/stacks/sheets/mineral/glass.dm b/code/game/objects/items/stacks/sheets/mineral/glass.dm index 80f392130647d..3602fc2b74d28 100644 --- a/code/game/objects/items/stacks/sheets/mineral/glass.dm +++ b/code/game/objects/items/stacks/sheets/mineral/glass.dm @@ -295,7 +295,7 @@ SIGNAL_HANDLER if(isliving(AM)) var/mob/living/L = AM - if(!(L.movement_type & (FLYING|FLOATING)) || L.buckled) + if(!(L.movement_type & MOVETYPES_NOT_TOUCHING_GROUND) || L.buckled) playsound(src, 'sound/effects/glass_step.ogg', HAS_TRAIT(L, TRAIT_LIGHT_STEP) ? 30 : 50, TRUE) /obj/item/shard/plasma diff --git a/code/game/objects/structures/kitchen_spike.dm b/code/game/objects/structures/kitchen_spike.dm index f48bcdf352da5..37987a431deb6 100644 --- a/code/game/objects/structures/kitchen_spike.dm +++ b/code/game/objects/structures/kitchen_spike.dm @@ -84,6 +84,7 @@ m180.Turn(180) animate(L, transform = m180, time = 3) L.pixel_y = L.base_pixel_y + PIXEL_Y_OFFSET_LYING + ADD_TRAIT(user, TRAIT_MOVE_UPSIDE_DOWN, REF(src)) else if (has_buckled_mobs()) for(var/mob/living/L in buckled_mobs) user_unbuckle_mob(L, user) @@ -129,6 +130,7 @@ m180.Turn(180) animate(M, transform = m180, time = 3) M.pixel_y = M.base_pixel_y + PIXEL_Y_OFFSET_LYING + REMOVE_TRAIT(M, TRAIT_MOVE_UPSIDE_DOWN, REF(src)) M.adjustBruteLoss(30) src.visible_message("[M] falls free of [src]!") unbuckle_mob(M,force=1) diff --git a/code/game/objects/structures/railings.dm b/code/game/objects/structures/railings.dm index cd6f467120bcc..ca961d47a5197 100644 --- a/code/game/objects/structures/railings.dm +++ b/code/game/objects/structures/railings.dm @@ -84,7 +84,7 @@ /obj/structure/railing/CanPass(atom/movable/mover, border_dir) . = ..() if(border_dir & dir) - return . || mover.throwing || mover.movement_type & (FLYING | FLOATING) + return . || mover.throwing || mover.movement_type & MOVETYPES_NOT_TOUCHING_GROUND return TRUE /obj/structure/railing/proc/on_exit(datum/source, atom/movable/leaving, direction) @@ -102,7 +102,7 @@ if (leaving.throwing) return - if (leaving.movement_type & (PHASING | FLYING | FLOATING)) + if (leaving.movement_type & (PHASING | MOVETYPES_NOT_TOUCHING_GROUND)) return if (leaving.move_force >= MOVE_FORCE_EXTREMELY_STRONG) diff --git a/code/game/objects/structures/tables_racks.dm b/code/game/objects/structures/tables_racks.dm index 544cb9177e98b..1a657d3bc2c98 100644 --- a/code/game/objects/structures/tables_racks.dm +++ b/code/game/objects/structures/tables_racks.dm @@ -317,7 +317,7 @@ CREATION_TEST_IGNORE_SUBTYPES(/obj/structure/table) check_break(M) /obj/structure/table/glass/proc/check_break(mob/living/M) - if(M.has_gravity() && M.mob_size > MOB_SIZE_SMALL && !(M.movement_type & (FLOATING|FLYING))) + if(M.has_gravity() && M.mob_size > MOB_SIZE_SMALL && !(M.movement_type & MOVETYPES_NOT_TOUCHING_GROUND)) table_shatter(M) /obj/structure/table/glass/proc/table_shatter(mob/living/victim) diff --git a/code/game/turfs/open/_open.dm b/code/game/turfs/open/_open.dm index 6b0f5a24a69ef..d6a6ae2dd1d22 100644 --- a/code/game/turfs/open/_open.dm +++ b/code/game/turfs/open/_open.dm @@ -99,7 +99,7 @@ CREATION_TEST_IGNORE_SELF(/turf/open) /turf/open/indestructible/sound/Entered(atom/movable/arrived, atom/old_loc, list/atom/old_locs) . = ..() - if(istype(arrived) && !(arrived.movement_type & (FLYING|FLOATING))) + if(istype(arrived) && !(arrived.movement_type & MOVETYPES_NOT_TOUCHING_GROUND)) playsound(src,sound,50,1) /turf/open/indestructible/necropolis diff --git a/code/game/turfs/open/lava.dm b/code/game/turfs/open/lava.dm index a31ddf285f808..104b7d6dadd28 100644 --- a/code/game/turfs/open/lava.dm +++ b/code/game/turfs/open/lava.dm @@ -125,7 +125,7 @@ else if (isliving(thing)) . = 1 var/mob/living/L = thing - if(L.movement_type & (FLOATING|FLYING)) + if(L.movement_type & MOVETYPES_NOT_TOUCHING_GROUND) continue //YOU'RE FLYING OVER IT var/buckle_check = L.buckled if(isobj(buckle_check)) diff --git a/code/game/turfs/open/openspace.dm b/code/game/turfs/open/openspace.dm index 4cef4cfdf32af..005628458fbd8 100644 --- a/code/game/turfs/open/openspace.dm +++ b/code/game/turfs/open/openspace.dm @@ -31,6 +31,13 @@ CREATION_TEST_IGNORE_SUBTYPES(/turf/open/openspace) /turf/open/openspace/airless initial_gas_mix = AIRLESS_ATMOS +/turf/open/openspace/Initialize(mapload) + . = ..() + var/area/our_area = loc + if(istype(our_area, /area/space)) + force_no_gravity = TRUE + return INITIALIZE_HINT_LATELOAD + /turf/open/openspace/can_have_cabling() if(locate(/obj/structure/lattice/catwalk, src)) return TRUE diff --git a/code/game/turfs/open/space/space.dm b/code/game/turfs/open/space/space.dm index d4e29778c1bab..54672809445a6 100644 --- a/code/game/turfs/open/space/space.dm +++ b/code/game/turfs/open/space/space.dm @@ -38,6 +38,8 @@ z_eventually_space = TRUE vis_flags = VIS_INHERIT_ID //when this be added to vis_contents of something it be associated with something on clicking, important for visualisation of turf in openspace and interraction with openspace that show you turf. + force_no_gravity = TRUE + /turf/open/space/basic/New() //Do not convert to Initialize //This is used to optimize the map loader return diff --git a/code/game/turfs/turf.dm b/code/game/turfs/turf.dm index 94342afcd56a3..a1992909487a4 100644 --- a/code/game/turfs/turf.dm +++ b/code/game/turfs/turf.dm @@ -72,6 +72,9 @@ CREATION_TEST_IGNORE_SELF(/turf) /// See __DEFINES/construction.dm for RCD_MEMORY_*. var/rcd_memory + ///whether or not this turf forces movables on it to have no gravity (unless they themselves have forced gravity) + var/force_no_gravity = FALSE + ///Icon-smoothing variable to map a diagonal wall corner with a fixed underlay. var/list/fixed_underlay = null diff --git a/code/modules/antagonists/clock_cult/traps/receivers/skewer.dm b/code/modules/antagonists/clock_cult/traps/receivers/skewer.dm index d9d66ea55c9f4..b66d84885dc28 100644 --- a/code/modules/antagonists/clock_cult/traps/receivers/skewer.dm +++ b/code/modules/antagonists/clock_cult/traps/receivers/skewer.dm @@ -27,7 +27,7 @@ var/target_stabbed = FALSE density = TRUE for(var/mob/living/M in get_turf(src)) - if(M.incorporeal_move || M.movement_type & (FLOATING|FLYING)) + if(M.incorporeal_move || M.movement_type & MOVETYPES_NOT_TOUCHING_GROUND) continue if(buckle_mob(M, TRUE)) target_stabbed = TRUE diff --git a/code/modules/antagonists/traitor/equipment/Malf_Modules.dm b/code/modules/antagonists/traitor/equipment/Malf_Modules.dm index 39399d9e8064a..94f2af29bb758 100644 --- a/code/modules/antagonists/traitor/equipment/Malf_Modules.dm +++ b/code/modules/antagonists/traitor/equipment/Malf_Modules.dm @@ -4,6 +4,7 @@ GLOBAL_LIST_INIT(blacklisted_malf_machines, typecacheof(list( /obj/machinery/field/containment, /obj/machinery/power/supermatter_crystal, + /obj/machinery/gravity_generator, /obj/machinery/doomsday_device, /obj/machinery/nuclearbomb, /obj/machinery/nuclearbomb/selfdestruct, diff --git a/code/modules/assembly/mousetrap.dm b/code/modules/assembly/mousetrap.dm index 4769f01272687..db98df285b98e 100644 --- a/code/modules/assembly/mousetrap.dm +++ b/code/modules/assembly/mousetrap.dm @@ -175,7 +175,7 @@ if(armed) if(ismob(AM)) var/mob/MM = AM - if(!(MM.movement_type & (FLOATING|FLYING))) + if(!(MM.movement_type & MOVETYPES_NOT_TOUCHING_GROUND)) if(ishuman(AM)) var/mob/living/carbon/H = AM if(H.m_intent == MOVE_INTENT_RUN) diff --git a/code/modules/clothing/shoes/magboots.dm b/code/modules/clothing/shoes/magboots.dm index 6e3a364ab8ff3..a1cecec5fe1f1 100644 --- a/code/modules/clothing/shoes/magboots.dm +++ b/code/modules/clothing/shoes/magboots.dm @@ -12,6 +12,16 @@ equip_delay_other = 70 resistance_flags = FIRE_PROOF +/obj/item/clothing/shoes/magboots/equipped(mob/user, slot) + . = ..() + if(slot & ITEM_SLOT_FEET) + update_gravity_trait(user) + else + REMOVE_TRAIT(user, TRAIT_NEGATES_GRAVITY, type) + +/obj/item/clothing/shoes/magboots/dropped(mob/user) + . = ..() + REMOVE_TRAIT(user, TRAIT_NEGATES_GRAVITY, type) /obj/item/clothing/shoes/magboots/verb/toggle() set name = "Toggle Magboots" @@ -22,7 +32,7 @@ attack_self(usr) -/obj/item/clothing/shoes/magboots/attack_self(mob/user) +/obj/item/clothing/shoes/magboots/attack_self(mob/living/user) if(magpulse) clothing_flags &= ~NOSLIP slowdown = SHOES_SLOWDOWN @@ -31,9 +41,8 @@ slowdown = slowdown_active magpulse = !magpulse icon_state = "[magboot_state][magpulse]" - to_chat(user, "You [magpulse ? "enable" : "disable"] the mag-pulse traction system.") - user.update_inv_shoes() //so our mob-overlays update - user.update_gravity(user.has_gravity()) + update_gravity_trait(user) + user.refresh_gravity() update_action_buttons() /obj/item/clothing/shoes/magboots/negates_gravity() @@ -43,6 +52,13 @@ . = ..() . += "Its mag-pulse traction system appears to be [magpulse ? "enabled" : "disabled"]." +///Adds/removes the gravity negation trait from the wearer depending on if the magpulse system is turned on. +/obj/item/clothing/shoes/magboots/proc/update_gravity_trait(mob/user) + if(magpulse) + ADD_TRAIT(user, TRAIT_NEGATES_GRAVITY, type) + else + REMOVE_TRAIT(user, TRAIT_NEGATES_GRAVITY, type) + /obj/item/clothing/shoes/magboots/advance desc = "Advanced magnetic boots that have a lighter magnetic pull, placing less burden on the wearer." @@ -69,6 +85,7 @@ clothing_flags = NOSLIP /obj/item/clothing/shoes/magboots/commando/attack_self(mob/user) //Code for the passive no-slip of the commando magboots to always apply, kind of a shit code solution though. + . = ..() if(magpulse) slowdown = SHOES_SLOWDOWN else @@ -77,7 +94,6 @@ icon_state = "[magboot_state][magpulse]" to_chat(user, "You [magpulse ? "enable" : "disable"] the mag-pulse traction system.") user.update_inv_shoes() - user.update_gravity(user.has_gravity()) update_action_buttons() /obj/item/clothing/shoes/magboots/crushing diff --git a/code/modules/mapping/space_management/zlevel_manager.dm b/code/modules/mapping/space_management/zlevel_manager.dm index d43aedc2a225d..7e7c8e188e887 100644 --- a/code/modules/mapping/space_management/zlevel_manager.dm +++ b/code/modules/mapping/space_management/zlevel_manager.dm @@ -27,6 +27,7 @@ var/datum/space_level/S = new z_type(new_z, name, traits, orbital_body_type) manage_z_level(S, filled_with_space = TRUE, contain_turfs = contain_turfs) generate_linkages_for_z_level(new_z) + calculate_z_level_gravity(new_z) SEND_GLOBAL_SIGNAL(COMSIG_GLOB_NEW_Z, S) return S diff --git a/code/modules/mob/dead/new_player/new_player.dm b/code/modules/mob/dead/new_player/new_player.dm index 5ce3b199f2e90..04313e3f0c07c 100644 --- a/code/modules/mob/dead/new_player/new_player.dm +++ b/code/modules/mob/dead/new_player/new_player.dm @@ -32,6 +32,9 @@ GLOB.new_player_list -= src return ..() +/mob/dead/new_player/mob_negates_gravity() + return TRUE //no need to calculate if they have gravity. + /mob/dead/new_player/prepare_huds() return diff --git a/code/modules/mob/living/carbon/carbon_movement.dm b/code/modules/mob/living/carbon/carbon_movement.dm index b776e3bf4bc43..a4e01f2cd0d6f 100644 --- a/code/modules/mob/living/carbon/carbon_movement.dm +++ b/code/modules/mob/living/carbon/carbon_movement.dm @@ -1,6 +1,6 @@ /mob/living/carbon/slip(knockdown_amount, obj/O, lube, paralyze, force_drop) - if(movement_type & (FLYING|FLOATING)) + if(movement_type & MOVETYPES_NOT_TOUCHING_GROUND) return FALSE if((lube & NO_SLIP_ON_CATWALK) && (locate(/obj/structure/lattice/catwalk) in get_turf(src))) return FALSE @@ -63,9 +63,9 @@ REMOVE_TRAIT(src, TRAIT_FLOORED, LACKING_LOCOMOTION_APPENDAGES_TRAIT) REMOVE_TRAIT(src, TRAIT_IMMOBILIZED, LACKING_LOCOMOTION_APPENDAGES_TRAIT) -/mob/living/carbon/on_movement_type_flag_disabled(datum/source, flag) +/mob/living/carbon/on_movement_type_flag_disabled(datum/source, flag, old_movement_type) . = ..() - if(flag & (FLYING | FLOATING) && !(movement_type & (FLYING | FLOATING))) + if(old_movement_type & (FLYING | FLOATING) && !(movement_type & (FLYING | FLOATING))) var/limbless_slowdown = 0 if(usable_legs < default_num_legs) limbless_slowdown += (default_num_legs - usable_legs) * 3 diff --git a/code/modules/mob/living/carbon/human/dummy.dm b/code/modules/mob/living/carbon/human/dummy.dm index 12961b8d82daa..d3050b5df6689 100644 --- a/code/modules/mob/living/carbon/human/dummy.dm +++ b/code/modules/mob/living/carbon/human/dummy.dm @@ -12,6 +12,11 @@ INITIALIZE_IMMEDIATE(/mob/living/carbon/human/dummy) . = ..() remove_from_all_data_huds() + +// We don't want your dummy floating up and down in the preference menu. +/mob/living/carbon/human/dummy/mob_negates_gravity() + return TRUE + /mob/living/carbon/human/dummy/prepare_data_huds() return diff --git a/code/modules/mob/living/carbon/human/species.dm b/code/modules/mob/living/carbon/human/species.dm index a4f6a35ff6439..06d5bf93df7b0 100644 --- a/code/modules/mob/living/carbon/human/species.dm +++ b/code/modules/mob/living/carbon/human/species.dm @@ -2391,6 +2391,7 @@ GLOBAL_LIST_EMPTY(features_by_species) if(isturf(H.loc)) var/turf/T = H.loc T.Entered(H) + H.refresh_gravity() ///Calls the DMI data for a custom icon for a given bodypart from the Species Datum. /datum/species/proc/get_custom_icons(var/part) diff --git a/code/modules/mob/living/init_signals.dm b/code/modules/mob/living/init_signals.dm index b3608f641cd33..1c1b9752b350b 100644 --- a/code/modules/mob/living/init_signals.dm +++ b/code/modules/mob/living/init_signals.dm @@ -41,6 +41,16 @@ RegisterSignal(src, COMSIG_MOVETYPE_FLAG_ENABLED, PROC_REF(on_movement_type_flag_enabled)) RegisterSignal(src, COMSIG_MOVETYPE_FLAG_DISABLED, PROC_REF(on_movement_type_flag_disabled)) + RegisterSignals(src, list(SIGNAL_ADDTRAIT(TRAIT_NEGATES_GRAVITY), SIGNAL_REMOVETRAIT(TRAIT_NEGATES_GRAVITY)), PROC_REF(on_negate_gravity)) + RegisterSignals(src, list(SIGNAL_ADDTRAIT(TRAIT_IGNORING_GRAVITY), SIGNAL_REMOVETRAIT(TRAIT_IGNORING_GRAVITY)), PROC_REF(on_ignore_gravity)) + RegisterSignals(src, list(SIGNAL_ADDTRAIT(TRAIT_FORCED_GRAVITY), SIGNAL_REMOVETRAIT(TRAIT_FORCED_GRAVITY)), PROC_REF(on_force_gravity)) + // We hook for forced grav changes from our turf and ourselves + var/static/list/loc_connections = list( + SIGNAL_ADDTRAIT(TRAIT_FORCED_GRAVITY) = PROC_REF(on_loc_force_gravity), + SIGNAL_REMOVETRAIT(TRAIT_FORCED_GRAVITY) = PROC_REF(on_loc_force_gravity), + ) + AddElement(/datum/element/connect_loc, loc_connections) + ///Called when TRAIT_KNOCKEDOUT is added to the mob. /mob/living/proc/on_knockedout_trait_gain(datum/source) SIGNAL_HANDLER @@ -63,6 +73,29 @@ SIGNAL_HANDLER REMOVE_TRAIT(src, TRAIT_KNOCKEDOUT, TRAIT_DEATHCOMA) +/// Called when [TRAIT_NEGATES_GRAVITY] is gained or lost +/mob/living/proc/on_negate_gravity(datum/source) + SIGNAL_HANDLER + if(!isgroundlessturf(loc)) + if(HAS_TRAIT(src, TRAIT_NEGATES_GRAVITY)) + ADD_TRAIT(src, TRAIT_IGNORING_GRAVITY, IGNORING_GRAVITY_NEGATION) + else + REMOVE_TRAIT(src, TRAIT_IGNORING_GRAVITY, IGNORING_GRAVITY_NEGATION) + +/// Called when [TRAIT_IGNORING_GRAVITY] is gained or lost +/mob/living/proc/on_ignore_gravity(datum/source) + SIGNAL_HANDLER + refresh_gravity() + +/// Called when [TRAIT_FORCED_GRAVITY] is gained or lost +/mob/living/proc/on_force_gravity(datum/source) + SIGNAL_HANDLER + refresh_gravity() + +/// Called when our loc's [TRAIT_FORCED_GRAVITY] is gained or lost +/mob/living/proc/on_loc_force_gravity(datum/source) + SIGNAL_HANDLER + refresh_gravity() ///Called when TRAIT_IMMOBILIZED is added to the mob. /mob/living/proc/on_immobilized_trait_gain(datum/source) @@ -178,11 +211,11 @@ clear_alert("succumb") ///From [element/movetype_handler/on_movement_type_trait_gain()] -/mob/living/proc/on_movement_type_flag_enabled(datum/source, trait) +/mob/living/proc/on_movement_type_flag_enabled(datum/source, trait, flag, old_movement_type) SIGNAL_HANDLER update_movespeed(FALSE) ///From [element/movetype_handler/on_movement_type_trait_loss()] -/mob/living/proc/on_movement_type_flag_disabled(datum/source, trait) +/mob/living/proc/on_movement_type_flag_disabled(datum/source, trait, flag, old_movement_type) SIGNAL_HANDLER update_movespeed(FALSE) diff --git a/code/modules/mob/living/life.dm b/code/modules/mob/living/life.dm index b98d460e29a1f..0be85eaa01ca3 100644 --- a/code/modules/mob/living/life.dm +++ b/code/modules/mob/living/life.dm @@ -36,14 +36,7 @@ if(environment) handle_environment(environment) - //Handle gravity - var/gravity = has_gravity() - update_gravity(gravity) - - if(gravity > STANDARD_GRAVITY) - if(!get_filter("gravity")) - add_filter("gravity",1,list("type"="motion_blur", "x"=0, "y"=0)) - handle_high_gravity(gravity) + handle_gravity(delta_time, times_fired) if(stat != DEAD) handle_traits(delta_time) // eye, ear, brain damages @@ -117,9 +110,20 @@ /mob/living/proc/update_damage_hud() return -/mob/living/proc/handle_high_gravity(gravity) - if(gravity >= GRAVITY_DAMAGE_TRESHOLD) //Aka gravity values of 3 or more - var/grav_stregth = gravity - GRAVITY_DAMAGE_TRESHOLD - adjustBruteLoss(min(grav_stregth,3)) +/mob/living/proc/handle_gravity(seconds_per_tick, times_fired) + if(gravity_state > STANDARD_GRAVITY) + handle_high_gravity(gravity_state, seconds_per_tick, times_fired) + +/mob/living/proc/gravity_animate() + if(!get_filter("gravity")) + add_filter("gravity",1,list("type"="motion_blur", "x"=0, "y"=0)) + animate(get_filter("gravity"), y = 1, time = 10, loop = -1) + animate(y = 0, time = 10) + +/mob/living/proc/handle_high_gravity(gravity, seconds_per_tick, times_fired) + if(gravity < GRAVITY_DAMAGE_THRESHOLD) //Aka gravity values of 3 or more + return + var/grav_strength = gravity - GRAVITY_DAMAGE_THRESHOLD + adjustBruteLoss(min(GRAVITY_DAMAGE_SCALING * grav_strength, GRAVITY_DAMAGE_MAXIMUM) * seconds_per_tick) #undef BODYTEMP_DIVISOR diff --git a/code/modules/mob/living/living.dm b/code/modules/mob/living/living.dm index 58f66906a0bb1..00771145a2264 100644 --- a/code/modules/mob/living/living.dm +++ b/code/modules/mob/living/living.dm @@ -24,6 +24,7 @@ // it prevents 'GLOB.poi_list' being glitched. without this, it will show xeno(or some mobs) twice in orbit panel. //color correction RegisterSignal(src, COMSIG_MOVABLE_ENTERED_AREA, PROC_REF(apply_color_correction)) + gravity_setup() /mob/living/ComponentInitialize() . = ..() @@ -955,25 +956,51 @@ /mob/living/proc/get_visible_name() return name -/mob/living/update_gravity(has_gravity) - . = ..() - if(!SSticker.HasRoundStarted()) - return - var/was_weightless = alerts["gravity"] && istype(alerts["gravity"], /atom/movable/screen/alert/weightless) - if(has_gravity) - if(has_gravity == 1) - clear_alert("gravity") - else - if(has_gravity >= GRAVITY_DAMAGE_TRESHOLD) - throw_alert("gravity", /atom/movable/screen/alert/veryhighgravity) - else - throw_alert("gravity", /atom/movable/screen/alert/highgravity) - if(was_weightless) - REMOVE_TRAIT(src, TRAIT_MOVE_FLOATING, NO_GRAVITY_TRAIT) +/mob/living/proc/update_gravity(gravity) + // Handle movespeed stuff + var/speed_change = max(0, gravity - STANDARD_GRAVITY) + if(speed_change) + add_or_update_variable_movespeed_modifier(/datum/movespeed_modifier/gravity, multiplicative_slowdown=speed_change) else - throw_alert("gravity", /atom/movable/screen/alert/weightless) - if(!was_weightless) - ADD_TRAIT(src, TRAIT_MOVE_FLOATING, NO_GRAVITY_TRAIT) + remove_movespeed_modifier(/datum/movespeed_modifier/gravity) + + // Time to add/remove gravity alerts. sorry for the mess it's gotta be fast + var/atom/movable/screen/alert/gravity_alert = alerts["gravity"] + switch(gravity) + if(-INFINITY to NEGATIVE_GRAVITY) + if(!istype(gravity_alert, /atom/movable/screen/alert/negative)) + throw_alert("gravity", /atom/movable/screen/alert/negative) + ADD_TRAIT(src, TRAIT_MOVE_UPSIDE_DOWN, NEGATIVE_GRAVITY_TRAIT) + var/matrix/flipped_matrix = transform + flipped_matrix.b = -flipped_matrix.b + flipped_matrix.e = -flipped_matrix.e + animate(src, transform = flipped_matrix, pixel_y = pixel_y+4, time = 0.5 SECONDS, easing = EASE_OUT, flags = ANIMATION_PARALLEL) + base_pixel_y += 4 + if(NEGATIVE_GRAVITY + 0.01 to 0) + if(!istype(gravity_alert, /atom/movable/screen/alert/weightless)) + throw_alert("gravity", /atom/movable/screen/alert/weightless) + ADD_TRAIT(src, TRAIT_MOVE_FLOATING, NO_GRAVITY_TRAIT) + if(0.01 to STANDARD_GRAVITY) + if(gravity_alert) + clear_alert("gravity") + if(STANDARD_GRAVITY + 0.01 to GRAVITY_DAMAGE_THRESHOLD - 0.01) + throw_alert("gravity", /atom/movable/screen/alert/highgravity) + if(GRAVITY_DAMAGE_THRESHOLD to INFINITY) + throw_alert("gravity", /atom/movable/screen/alert/veryhighgravity) + + // If we had no gravity alert, or the same alert as before, go home + if(!gravity_alert || alerts["gravity"] == gravity_alert) + return + // By this point we know that we do not have the same alert as we used to + if(istype(gravity_alert, /atom/movable/screen/alert/weightless)) + REMOVE_TRAIT(src, TRAIT_MOVE_FLOATING, NO_GRAVITY_TRAIT) + if(istype(gravity_alert, /atom/movable/screen/alert/negative)) + REMOVE_TRAIT(src, TRAIT_MOVE_UPSIDE_DOWN, NEGATIVE_GRAVITY_TRAIT) + var/matrix/flipped_matrix = transform + flipped_matrix.b = -flipped_matrix.b + flipped_matrix.e = -flipped_matrix.e + animate(src, transform = flipped_matrix, pixel_y = pixel_y-4, time = 0.5 SECONDS, easing = EASE_OUT, flags = ANIMATION_PARALLEL) + base_pixel_y -= 4 // The src mob is trying to strip an item from someone // Override if a certain type of mob should be behave differently when stripping items (can't, for example) diff --git a/code/modules/mob/living/living_defines.dm b/code/modules/mob/living/living_defines.dm index 7e5917bde22ba..c7b849c0c4b69 100644 --- a/code/modules/mob/living/living_defines.dm +++ b/code/modules/mob/living/living_defines.dm @@ -169,3 +169,6 @@ ///The x amount a mob's sprite should be offset due to the current position they're in var/body_position_pixel_y_offset = 0 + /// What our current gravity state is. Used to avoid duplicate animates and such + var/gravity_state = null + diff --git a/code/modules/mob/living/living_movement.dm b/code/modules/mob/living/living_movement.dm index eaf9b84962334..25b8815ef6b45 100644 --- a/code/modules/mob/living/living_movement.dm +++ b/code/modules/mob/living/living_movement.dm @@ -1,8 +1,60 @@ -/mob/living/Moved() +/mob/living/Moved(atom/old_loc, movement_dir, forced, list/old_locs, momentum_change = TRUE) . = ..() update_turf_movespeed(loc) update_looking_move() + if(HAS_TRAIT(src, TRAIT_NEGATES_GRAVITY)) + if(!isgroundlessturf(loc)) + ADD_TRAIT(src, TRAIT_IGNORING_GRAVITY, IGNORING_GRAVITY_NEGATION) + else + REMOVE_TRAIT(src, TRAIT_IGNORING_GRAVITY, IGNORING_GRAVITY_NEGATION) + var/turf/old_turf = get_turf(old_loc) + var/turf/new_turf = get_turf(src) + // If we're moving to/from nullspace, refresh + // Easier then adding nullchecks to all this shit, and technically right since a null turf means nograv + if(isnull(old_turf) || isnull(new_turf)) + if(!QDELING(src)) + refresh_gravity() + return + // If the turf gravity has changed, then it's possible that our state has changed, so update + if(HAS_TRAIT(old_turf, TRAIT_FORCED_GRAVITY) != HAS_TRAIT(new_turf, TRAIT_FORCED_GRAVITY) || new_turf.force_no_gravity != old_turf.force_no_gravity) + refresh_gravity() + + // Going to do area gravity checking here + var/area/old_area = get_area(old_turf) + var/area/new_area = get_area(new_turf) + // If the area gravity has changed, then it's possible that our state has changed, so update + if(old_area.has_gravity != new_area.has_gravity) + refresh_gravity() + +/mob/living/onTransitZ(old_z, new_z, same_z_layer, notify_contents) + . = ..() + + if(!old_z || !new_z || SSmapping.gravity_by_z_level[old_z] != SSmapping.gravity_by_z_level[new_z]) + refresh_gravity() + +/// Living Mob use event based gravity +/// We check here to ensure we haven't dropped any gravity changes +/mob/living/proc/gravity_setup() + on_negate_gravity(src) + refresh_gravity() + +/// Handles gravity effects. Call if something about our gravity has potentially changed! +/mob/living/proc/refresh_gravity() + var/old_grav_state = gravity_state + gravity_state = has_gravity() + if(gravity_state == old_grav_state) + return + + update_gravity(gravity_state) + + if(gravity_state > STANDARD_GRAVITY) + gravity_animate() + else if(old_grav_state > STANDARD_GRAVITY) + remove_filter("gravity") + +/mob/living/mob_negates_gravity() + return HAS_TRAIT_FROM(src, TRAIT_IGNORING_GRAVITY, IGNORING_GRAVITY_NEGATION) /mob/living/CanAllowThrough(atom/movable/mover, border_dir) . = ..() diff --git a/code/modules/mob/living/silicon/ai/life.dm b/code/modules/mob/living/silicon/ai/life.dm index 81e5ff91e12a8..aacc8ed8cb00d 100644 --- a/code/modules/mob/living/silicon/ai/life.dm +++ b/code/modules/mob/living/silicon/ai/life.dm @@ -3,8 +3,6 @@ return //Being dead doesn't mean your temperature never changes - update_gravity(has_gravity()) - handle_status_effects(delta_time) handle_traits(delta_time) diff --git a/code/modules/mob/living/simple_animal/hostile/jungle/mega_arachnid.dm b/code/modules/mob/living/simple_animal/hostile/jungle/mega_arachnid.dm index c9bee4c254854..6ded2491d4a6e 100644 --- a/code/modules/mob/living/simple_animal/hostile/jungle/mega_arachnid.dm +++ b/code/modules/mob/living/simple_animal/hostile/jungle/mega_arachnid.dm @@ -58,7 +58,7 @@ /obj/projectile/mega_arachnid/on_hit(atom/target, blocked = FALSE) if(iscarbon(target) && blocked < 100) var/obj/item/restraints/legcuffs/beartrap/mega_arachnid/B = new /obj/item/restraints/legcuffs/beartrap/mega_arachnid(get_turf(target)) - B.spring_trap(null, target) + B.spring_trap(target) return ..() /obj/item/restraints/legcuffs/beartrap/mega_arachnid diff --git a/code/modules/mob/mob_movement.dm b/code/modules/mob/mob_movement.dm index b089c1bde7df3..289f149f9561e 100644 --- a/code/modules/mob/mob_movement.dm +++ b/code/modules/mob/mob_movement.dm @@ -354,6 +354,9 @@ continue return rebound +/mob/has_gravity(turf/gravity_turf) + return mob_negates_gravity() || ..() + /** * Does this mob ignore gravity */ @@ -372,14 +375,6 @@ /mob/proc/slip(knockdown, paralyze, forcedrop, w_amount, obj/O, lube) return -/// Update the gravity status of this mob -/mob/proc/update_gravity(has_gravity, override=FALSE) - var/speed_change = max(0, has_gravity - STANDARD_GRAVITY) - if(!speed_change) - remove_movespeed_modifier(/datum/movespeed_modifier/gravity) - else - add_or_update_variable_movespeed_modifier(/datum/movespeed_modifier/gravity, multiplicative_slowdown=speed_change) - //bodypart selection verbs - Cyberboss //8:repeated presses toggles through head - eyes - mouth //4: r-arm 5: chest 6: l-arm diff --git a/code/modules/movespeed/modifiers/mobs.dm b/code/modules/movespeed/modifiers/mobs.dm index 7a4e3ba20d20b..c6f604069e801 100644 --- a/code/modules/movespeed/modifiers/mobs.dm +++ b/code/modules/movespeed/modifiers/mobs.dm @@ -83,6 +83,7 @@ /datum/movespeed_modifier/limbless variable = TRUE movetypes = GROUND + blacklisted_movetypes = FLOATING|FLYING flags = IGNORE_NOSLOW /datum/movespeed_modifier/simplemob_varspeed diff --git a/code/modules/power/gravitygenerator.dm b/code/modules/power/gravitygenerator.dm index 4f91bdd2f00c7..1106020078cd1 100644 --- a/code/modules/power/gravitygenerator.dm +++ b/code/modules/power/gravitygenerator.dm @@ -187,6 +187,20 @@ GLOBAL_LIST_EMPTY(gravity_generators) // We will keep track of this by adding ne // Interaction +/obj/machinery/gravity_generator/main/examine(mob/user) + . = ..() + if(!(machine_stat & BROKEN)) + return + switch(broken_state) + if(GRAV_NEEDS_SCREWDRIVER) + . += "The entire frame is barely holding together, the screws need to be refastened." + if(GRAV_NEEDS_WELDING) + . += "There's lots of broken seals on the framework, it could use some welding." + if(GRAV_NEEDS_PLASTEEL) + . += "Some of this damaged plating needs full replacement. 10 plasteel should be enough." + if(GRAV_NEEDS_WRENCH) + . += "The new plating just needs to be bolted into place now." + // Fixing the gravity generator. /obj/machinery/gravity_generator/main/attackby(obj/item/I, mob/user, params) switch(broken_state) @@ -365,11 +379,13 @@ GLOBAL_LIST_EMPTY(gravity_generators) // We will keep track of this by adding ne /obj/machinery/gravity_generator/main/proc/shake_everyone() var/turf/T = get_turf(src) var/sound/alert_sound = sound('sound/effects/alert.ogg') - for(var/i in GLOB.mob_list) - var/mob/M = i + for(var/mobs in GLOB.mob_list) + var/mob/M = mobs if(M.get_virtual_z_level() != get_virtual_z_level() && !(ztrait && SSmapping.level_trait(z, ztrait) && SSmapping.level_trait(M.z, ztrait))) continue - M.update_gravity(M.has_gravity()) + if(isliving(M)) + var/mob/living/grav_update = M + grav_update.refresh_gravity() if(M.client) shake_camera(M, 15, 1) M.playsound_local(T, null, 100, 1, 0.5, S = alert_sound) @@ -399,6 +415,7 @@ GLOBAL_LIST_EMPTY(gravity_generators) // We will keep track of this by adding ne GLOB.gravity_generators["[theZ]"] |= src else GLOB.gravity_generators["[theZ]"] -= src + SSmapping.calculate_z_level_gravity(z) /obj/machinery/gravity_generator/main/proc/change_setting(value) if(value != setting) diff --git a/code/modules/projectiles/projectile/energy/net_snare.dm b/code/modules/projectiles/projectile/energy/net_snare.dm index 04e496aa21786..5a3bdb9df2b5a 100644 --- a/code/modules/projectiles/projectile/energy/net_snare.dm +++ b/code/modules/projectiles/projectile/energy/net_snare.dm @@ -83,7 +83,7 @@ new/obj/item/restraints/legcuffs/beartrap/energy(get_turf(loc)) else if(iscarbon(target)) var/obj/item/restraints/legcuffs/beartrap/B = new /obj/item/restraints/legcuffs/beartrap/energy(get_turf(target)) - B.spring_trap(null, target) + B.spring_trap(target) . = ..() /obj/projectile/energy/trap/on_range() @@ -104,7 +104,7 @@ qdel(src) if(iscarbon(target)) var/obj/item/restraints/legcuffs/beartrap/B = new /obj/item/restraints/legcuffs/beartrap/energy/cyborg(get_turf(target)) - B.spring_trap(null, target) + B.spring_trap(target) QDEL_IN(src, 10) . = ..() diff --git a/code/modules/shuttle/shuttle.dm b/code/modules/shuttle/shuttle.dm index 0b6df82332fec..b9c133c5fbdb3 100644 --- a/code/modules/shuttle/shuttle.dm +++ b/code/modules/shuttle/shuttle.dm @@ -61,8 +61,8 @@ CREATION_TEST_IGNORE_SUBTYPES(/obj/docking_port) else return QDEL_HINT_LETMELIVE -/obj/docking_port/has_gravity(turf/T) - return FALSE +/obj/docking_port/has_gravity(turf/current_turf) + return TRUE /obj/docking_port/take_damage(damage_amount, damage_type = BRUTE, damage_flag = 0, sound_effect = 1, attack_dir, armour_penetration = 0) return diff --git a/icons/hud/screen_alert.dmi b/icons/hud/screen_alert.dmi index dc9f39b99ec45a20a94b869f0c0b814fed700291..13d032eeee32e0d380ffcc10a1415f883c526bb5 100644 GIT binary patch delta 19614 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 LNkvXXu0mjf9;KJp delta 19185 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%0D(Qrj7e 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