Files
Bubberstation/code/datums/components/slippery.dm
MrMelbert c1ed62915b Adds UPSIDE_DOWN movetype for negative gravity / makes Atrocinator affected by less things (#79785)
## About The Pull Request

Fixes #79764

I was going to tackle this issue by slamming `TRAIT_NO_SLIP_ALL` on
Atrocinator users and calling it a day, but like, that didn't feel
proper.

So I thought hey, we could just give them the flying movetype, even
though they technically aren't flying it means they're unaffected by
things that flying would make you unaffected by.

Nope, this means the mob technically "negates gravity", so no falling
and no feetsteps.

Let's try floating - this give us feetsteps but no falling upwards. 

So instead of going back to square one, with `TRAIT_NO_SLIP_ALL`, I
decided to go for the more complex route of just adding a movetype.

Hence, move type `UPSIDE_DOWN`. This covers situations where a mob would
be "floating" above the ground, but still walking. ...Negative gravity.

This means overall the Atrociator acts more as you'd expect - you don't
slip on ice, you don't trigger bear traps or mouse traps, you can walk
over railings, unaffected by conveyor belts, etc.

## Why It's Good For The Game

Makes the Atrocinator a lot more consistent with how you'd expect for it
to work.

Admittedly it is a bit niche use of movetypes, but it can possibly be
expanded to more things in the future, who knows? I applied it to mobs
on meat spikes (even though they don't move), just for proof of concept.

## Changelog

🆑 Melbert
fix: Atrocinating mobs will now behave more as you'd expect. Meaning
they don't slip on wet patches, can't trigger bear traps / landmines /
mouse traps, ignore conveyors, and can walk over tables and railings.
fix: Floating mobs are unaffected by conveyor belts, acid (on the
ground), glass tables
fix: Floating mobs won't squish stuff like roaches anymore 
fix: Fixes bear traps triggering on floating / flying mobs 
/🆑

---------

Co-authored-by: Ghom <42542238+Ghommie@users.noreply.github.com>
2023-11-20 21:19:13 +00:00

239 lines
9.2 KiB
Plaintext

/**
* # Slip behaviour component
*
* Add this component to an object to make it a slippery object, slippery objects make mobs that cross them fall over.
* Items with this component that get picked up may give their parent mob the slip behaviour.
*
* Here is a simple example of adding the component behaviour to an object.area
*
* AddComponent(/datum/component/slippery, 80, (NO_SLIP_WHEN_WALKING | SLIDE))
*
* This adds slippery behaviour to the parent atom, with a 80 decisecond (~8 seconds) knockdown
* The lube flags control how the slip behaves, in this case, the mob wont slip if it's in walking mode (NO_SLIP_WHEN_WALKING)
* and if they do slip, they will slide a few tiles (SLIDE)
*
*
* This component has configurable behaviours, see the [Initialize proc for the argument listing][/datum/component/slippery/proc/Initialize].
*/
/datum/component/slippery
dupe_mode = COMPONENT_DUPE_UNIQUE_PASSARGS
/// If the slip forces the crossing mob to drop held items.
var/force_drop_items = FALSE
/// How long the slip keeps the crossing mob knocked over (they can still crawl and use weapons) for.
var/knockdown_time = 0
/// How long the slip paralyzes (prevents the crossing mob doing anything) for.
var/paralyze_time = 0
/// Flags for how slippery the parent is. See [__DEFINES/mobs.dm]
var/lube_flags
/// Optional callback allowing you to define custom conditions for slipping
var/datum/callback/can_slip_callback
/// Optional call back that is called when a mob slips on this component
var/datum/callback/on_slip_callback
/// If parent is an item, this is the person currently holding/wearing the parent (or the parent if no one is holding it)
var/mob/living/holder
/// Whitelist of item slots the parent can be equipped in that make the holder slippery. If null or empty, it will always make the holder slippery.
var/list/slot_whitelist = list(ITEM_SLOT_OCLOTHING, ITEM_SLOT_ICLOTHING, ITEM_SLOT_GLOVES, ITEM_SLOT_FEET, ITEM_SLOT_HEAD, ITEM_SLOT_MASK, ITEM_SLOT_BELT, ITEM_SLOT_NECK)
///what we give to connect_loc by default, makes slippable mobs moving over us slip
var/static/list/default_connections = list(
COMSIG_ATOM_ENTERED = PROC_REF(Slip),
)
///what we give to connect_loc if we're an item and get equipped by a mob. makes slippable mobs moving over our holder slip
var/static/list/holder_connections = list(
COMSIG_ATOM_ENTERED = PROC_REF(Slip_on_wearer),
)
/// The connect_loc_behalf component for the holder_connections list.
var/datum/weakref/holder_connect_loc_behalf
/**
* Initialize the slippery component behaviour
*
* When applied to any atom in the game this will apply slipping behaviours to that atom
*
* Arguments:
* * knockdown - Length of time the knockdown applies (Deciseconds)
* * lube_flags - Controls the slip behaviour, they are listed starting [here][SLIDE]
* * datum/callback/on_slip_callback - Callback to define further custom controls on when slipping is applied
* * paralyze - length of time to paralyze the crossing mob for (Deciseconds)
* * force_drop - should the crossing mob drop items in it's hands or not
* * slot_whitelist - flags controlling where on a mob this item can be equipped to make the parent mob slippery full list [here][ITEM_SLOT_OCLOTHING]
* * datum/callback/on_slip_callback - Callback to add custom behaviours as the crossing mob is slipped
*/
/datum/component/slippery/Initialize(
knockdown,
lube_flags = NONE,
datum/callback/on_slip_callback,
paralyze,
force_drop = FALSE,
slot_whitelist,
datum/callback/can_slip_callback,
)
src.knockdown_time = max(knockdown, 0)
src.paralyze_time = max(paralyze, 0)
src.force_drop_items = force_drop
src.lube_flags = lube_flags
src.can_slip_callback = can_slip_callback
src.on_slip_callback = on_slip_callback
if(slot_whitelist)
src.slot_whitelist = slot_whitelist
add_connect_loc_behalf_to_parent()
if(ismovable(parent))
if(isitem(parent))
RegisterSignal(parent, COMSIG_ITEM_EQUIPPED, PROC_REF(on_equip))
RegisterSignal(parent, COMSIG_ITEM_DROPPED, PROC_REF(on_drop))
RegisterSignal(parent, COMSIG_ITEM_APPLY_FANTASY_BONUSES, PROC_REF(apply_fantasy_bonuses))
RegisterSignal(parent, COMSIG_ITEM_REMOVE_FANTASY_BONUSES, PROC_REF(remove_fantasy_bonuses))
else
RegisterSignal(parent, COMSIG_ATOM_ENTERED, PROC_REF(Slip))
/datum/component/slippery/Destroy(force, silent)
can_slip_callback = null
on_slip_callback = null
holder = null
return ..()
/datum/component/slippery/proc/apply_fantasy_bonuses(obj/item/source, bonus)
SIGNAL_HANDLER
knockdown_time = source.modify_fantasy_variable("knockdown_time", knockdown_time, bonus)
if(bonus >= 5)
paralyze_time = source.modify_fantasy_variable("paralyze_time", paralyze_time, bonus)
LAZYSET(source.fantasy_modifications, "lube_flags", lube_flags)
lube_flags |= SLIDE
if(bonus >= 10)
lube_flags |= GALOSHES_DONT_HELP|SLIP_WHEN_CRAWLING
/datum/component/slippery/proc/remove_fantasy_bonuses(obj/item/source, bonus)
SIGNAL_HANDLER
knockdown_time = source.reset_fantasy_variable("knockdown_time", knockdown_time)
paralyze_time = source.reset_fantasy_variable("paralyze_time", paralyze_time)
var/previous_lube_flags = LAZYACCESS(source.fantasy_modifications, "lube_flags")
LAZYREMOVE(source.fantasy_modifications, "lube_flags")
if(!isnull(previous_lube_flags))
lube_flags = previous_lube_flags
/datum/component/slippery/proc/add_connect_loc_behalf_to_parent()
if(ismovable(parent))
AddComponent(/datum/component/connect_loc_behalf, parent, default_connections)
/datum/component/slippery/InheritComponent(
datum/component/slippery/component,
i_am_original,
knockdown,
lube_flags = NONE,
datum/callback/on_slip_callback,
paralyze,
force_drop = FALSE,
slot_whitelist,
datum/callback/can_slip_callback,
)
if(component)
knockdown = component.knockdown_time
lube_flags = component.lube_flags
on_slip_callback = component.on_slip_callback
can_slip_callback = component.on_slip_callback
paralyze = component.paralyze_time
force_drop = component.force_drop_items
slot_whitelist = component.slot_whitelist
src.knockdown_time = max(knockdown, 0)
src.paralyze_time = max(paralyze, 0)
src.force_drop_items = force_drop
src.lube_flags = lube_flags
src.on_slip_callback = on_slip_callback
src.can_slip_callback = can_slip_callback
if(slot_whitelist)
src.slot_whitelist = slot_whitelist
/**
* The proc that does the sliping. Invokes the slip callback we have set.
*
* Arguments
* * source - the source of the signal
* * arrived - the atom/movable that is being slipped.
*/
/datum/component/slippery/proc/Slip(datum/source, atom/movable/arrived, atom/old_loc, list/atom/old_locs)
SIGNAL_HANDLER
if(!isliving(arrived))
return
if(lube_flags & SLIPPERY_TURF)
var/turf/turf = get_turf(source)
if(HAS_TRAIT(turf, TRAIT_TURF_IGNORE_SLIPPERY))
return
var/mob/living/victim = arrived
if(victim.movement_type & MOVETYPES_NOT_TOUCHING_GROUND)
return
if(can_slip_callback && !can_slip_callback.Invoke(holder, victim))
return
if(victim.slip(knockdown_time, parent, lube_flags, paralyze_time, force_drop_items))
on_slip_callback?.Invoke(victim)
/**
* Gets called when COMSIG_ITEM_EQUIPPED is sent to parent.
* This proc register slip signals to the equipper.
* If we have a slot whitelist, we only register the signals if the slot is valid (ex: clown PDA only slips in ID or belt slot).
*
* Arguments
* * source - the source of the signal
* * equipper - the mob we're equipping the slippery thing to
* * slot - the slot we're equipping the slippery thing to on the equipper.
*/
/datum/component/slippery/proc/on_equip(datum/source, mob/equipper, slot)
SIGNAL_HANDLER
if((!LAZYLEN(slot_whitelist) || (slot in slot_whitelist)) && isliving(equipper))
holder = equipper
qdel(GetComponent(/datum/component/connect_loc_behalf))
AddComponent(/datum/component/connect_loc_behalf, holder, holder_connections)
RegisterSignal(holder, COMSIG_QDELETING, PROC_REF(holder_deleted))
/**
* Detects if the holder mob is deleted.
* If our holder mob is the holder set in this component, we null it.
*
* Arguments:
* * source - the source of the signal
* * possible_holder - the mob being deleted.
*/
/datum/component/slippery/proc/holder_deleted(datum/source, datum/possible_holder)
SIGNAL_HANDLER
if(possible_holder == holder)
holder = null
/**
* Gets called when COMSIG_ITEM_DROPPED is sent to parent.
* Makes our holder mob un-slippery.
*
* Arguments:
* * source - the source of the signal
* * user - the mob that was formerly wearing our slippery item.
*/
/datum/component/slippery/proc/on_drop(datum/source, mob/user)
SIGNAL_HANDLER
UnregisterSignal(user, COMSIG_QDELETING)
qdel(GetComponent(/datum/component/connect_loc_behalf))
add_connect_loc_behalf_to_parent()
holder = null
/**
* The slip proc, but for equipped items.
* Slips the person who crossed us if we're lying down and unbuckled.
*
* Arguments:
* * source - the source of the signal
* * arrived - the atom/movable that slipped on us.
*/
/datum/component/slippery/proc/Slip_on_wearer(datum/source, atom/movable/arrived, atom/old_loc, list/atom/old_locs)
SIGNAL_HANDLER
if(holder.body_position == LYING_DOWN && !holder.buckled)
Slip(source, arrived)
/datum/component/slippery/UnregisterFromParent()
. = ..()
qdel(GetComponent(/datum/component/connect_loc_behalf))