Files
Yogstation/code/datums/components/riding.dm
SapphicOverload b13ed70f05 Replaces intents with combat mode (#21883)
* it begins

* Update gun.dm

* misc fixes

* Update gun.dm

* more fixes

* Update lightning_flow.dm

* i may be stupid

* Update suicide.dm

* fix mech strafing

* Update mecha.dm

* let there be qol

* ghost stuff

* Update screen_clockwork.dmi

* does the stuff

* stuff

* Update worldbreaker.dm

* moltial arts

* Update worldbreaker.dm

* CRITICAL FIX

* mech stuff

* Update tables_racks.dm

* stuff

* fix seismic arm

* buster/seismic arm fix 2

* saber + lockers

* stuff

* hand tele and pre_attack_secondary

* more right click acts

* Update closets.dm

* who did this

* heck

* Update mob.dm

* Update items.dm

* darkspawn fix

* fixes wound healing

* Update item_attack.dm

* minor qol stuff

* Update kinetic_crusher.dm

* Update kinetic_crusher.dm

* runtime fix

* Update kinetic_crusher.dm

* Update screen_plasmafire.dmi

* stuff

* syringes

* i am very silly

* death to /obj/item/toolset_handler

* Update assembly.dm

* surgery fix + hypo stuff

* mantis fix

* gas harpoon

* atmos machines

---------

Co-authored-by: Molti <gamingjoelouis@gmail.com>
2024-05-09 13:50:24 -05:00

486 lines
18 KiB
Plaintext

/**
* This is the riding component, which is applied to a movable atom by the [ridable element][/datum/element/ridable] when a mob is successfully buckled to said movable.
*
* This component lives for as long as at least one mob is buckled to the parent. Once all mobs are unbuckled, the component is deleted, until another mob is buckled in
* and we make a new riding component, so on and so forth until the sun explodes.
*/
/datum/component/riding
dupe_mode = COMPONENT_DUPE_UNIQUE
var/last_move_diagonal = FALSE
///tick delay between movements, lower = faster, higher = slower
var/vehicle_move_delay = 2
var/last_vehicle_move = 0 //used for move delays
/**
* If the driver needs a certain item in hand (or inserted, for vehicles) to drive this. For vehicles, this must be duplicated on the actual vehicle object in their
* [/obj/vehicle/var/key_type] variable because the vehicle objects still have a few special checks/functions of their own I'm not porting over to the riding component
* quite yet. Make sure if you define it on the vehicle, you define it here too.
*/
var/keytype
var/slowed = FALSE
var/slowvalue = 1
/// position_of_user = list(dir = list(px, py)), or RIDING_OFFSET_ALL for a generic one.
var/list/riding_offsets = list()
/// ["[DIRECTION]"] = layer. Don't set it for a direction for default, set a direction to null for no change.
var/list/directional_vehicle_layers = list()
/// same as above but instead of layer you have a list(px, py)
var/list/directional_vehicle_offsets = list()
/// allow typecache for only certain turfs, forbid to allow all but those. allow only certain turfs will take precedence.
var/list/allowed_turf_typecache
/// allow typecache for only certain turfs, forbid to allow all but those. allow only certain turfs will take precedence.
var/list/forbid_turf_typecache
/// We don't need roads where we're going if this is TRUE, allow normal movement in space tiles
var/override_allow_spacemove = FALSE
/// can anyone other than the rider unbuckle the rider?
var/can_force_unbuckle = TRUE
/**
* Ride check flags defined for the specific riding component types, so we know if we need arms, legs, or whatever.
* Takes additional flags from the ridable element and the buckle proc (buckle_mob_flags) for riding cyborgs/humans in case we need to reserve arms
*/
var/ride_check_flags = NONE
/// For telling someone they can't drive
COOLDOWN_DECLARE(message_cooldown)
/// For telling someone they can't drive
COOLDOWN_DECLARE(vehicle_move_cooldown)
var/allow_one_away_from_valid_turf = TRUE //allow moving one tile away from a valid turf but not more.
var/drive_verb = "drive"
var/ride_check_rider_incapacitated = FALSE
var/ride_check_rider_restrained = FALSE
var/ride_check_ridden_incapacitated = FALSE
var/del_on_unbuckle_all = FALSE
/datum/component/riding/Initialize()
if(!ismovable(parent))
return COMPONENT_INCOMPATIBLE
RegisterSignal(parent, COMSIG_MOVABLE_BUCKLE, PROC_REF(vehicle_mob_buckle))
RegisterSignal(parent, COMSIG_MOVABLE_UNBUCKLE, PROC_REF(vehicle_mob_unbuckle))
RegisterSignal(parent, COMSIG_MOVABLE_MOVED, PROC_REF(vehicle_moved))
RegisterSignal(parent, COMSIG_BUCKLED_CAN_Z_MOVE, PROC_REF(riding_can_z_move))
/datum/component/riding/proc/vehicle_mob_unbuckle(datum/source, mob/living/M, force = FALSE)
var/atom/movable/AM = parent
restore_position(M)
unequip_buckle_inhands(M)
M.updating_glide_size = TRUE
if(AM.movement_type & FLYING)
M.movement_type &= ~FLYING
if(del_on_unbuckle_all && !AM.has_buckled_mobs())
qdel(src)
/datum/component/riding/proc/vehicle_mob_buckle(datum/source, mob/living/M, force = FALSE)
var/atom/movable/AM = parent
M.set_glide_size(AM.glide_size)
M.updating_glide_size = FALSE
handle_vehicle_offsets()
if(AM.movement_type & FLYING)
M.movement_type |= FLYING
/// Some ridable atoms may want to only show on top of the rider in certain directions, like wheelchairs
/datum/component/riding/proc/handle_vehicle_layer(dir)
var/atom/movable/AM = parent
var/static/list/defaults = list(TEXT_NORTH = OBJ_LAYER, TEXT_SOUTH = ABOVE_MOB_LAYER, TEXT_EAST = ABOVE_MOB_LAYER, TEXT_WEST = ABOVE_MOB_LAYER)
. = defaults["[dir]"]
if(directional_vehicle_layers["[dir]"])
. = directional_vehicle_layers["[dir]"]
if(isnull(.)) //you can set it to null to not change it.
. = AM.layer
AM.layer = .
/datum/component/riding/proc/set_vehicle_dir_layer(dir, layer)
directional_vehicle_layers["[dir]"] = layer
/datum/component/riding/proc/vehicle_moved(datum/source, oldloc, dir, forced)
var/atom/movable/AM = parent
for(var/mob/M in AM.buckled_mobs)
ride_check(M)
handle_vehicle_offsets()
handle_vehicle_layer(dir)
/datum/component/riding/proc/ride_check(mob/living/M)
var/atom/movable/AM = parent
var/mob/AMM = AM
if(AM.movement_type & FLYING)
AMM.movement_type |= FLYING
else
AMM.movement_type &= ~FLYING
if((ride_check_rider_restrained && M.restrained(TRUE)) || (ride_check_rider_incapacitated && M.incapacitated(FALSE, TRUE)) || (ride_check_ridden_incapacitated && istype(AMM) && AMM.incapacitated(FALSE, TRUE)))
AM.visible_message(span_warning("[M] falls off of [AM]!"))
AM.unbuckle_mob(M)
return TRUE
/datum/component/riding/proc/force_dismount(mob/living/M)
var/atom/movable/AM = parent
AM.unbuckle_mob(M)
/datum/component/riding/proc/handle_vehicle_offsets()
var/atom/movable/AM = parent
var/AM_dir = "[AM.dir]"
var/passindex = 0
if(AM.has_buckled_mobs())
for(var/m in AM.buckled_mobs)
passindex++
var/mob/living/buckled_mob = m
var/list/offsets = get_offsets(passindex)
var/rider_dir = get_rider_dir(passindex)
buckled_mob.setDir(rider_dir)
dir_loop:
for(var/offsetdir in offsets)
if(offsetdir == AM_dir)
var/list/diroffsets = offsets[offsetdir]
buckled_mob.pixel_x = diroffsets[1]
if(diroffsets.len >= 2)
buckled_mob.pixel_y = diroffsets[2]
if(diroffsets.len == 3)
buckled_mob.layer = diroffsets[3]
break dir_loop
var/list/static/default_vehicle_pixel_offsets = list(TEXT_NORTH = list(0, 0), TEXT_SOUTH = list(0, 0), TEXT_EAST = list(0, 0), TEXT_WEST = list(0, 0))
var/px = default_vehicle_pixel_offsets[AM_dir]
var/py = default_vehicle_pixel_offsets[AM_dir]
if(directional_vehicle_offsets[AM_dir])
if(isnull(directional_vehicle_offsets[AM_dir]))
px = AM.pixel_x
py = AM.pixel_y
else
px = directional_vehicle_offsets[AM_dir][1]
py = directional_vehicle_offsets[AM_dir][2]
AM.pixel_x = px
AM.pixel_y = py
/datum/component/riding/proc/set_vehicle_dir_offsets(dir, x, y)
directional_vehicle_offsets["[dir]"] = list(x, y)
//Override this to set your vehicle's various pixel offsets
/datum/component/riding/proc/get_offsets(pass_index) // list(dir = x, y, layer)
. = list(TEXT_NORTH = list(0, 0), TEXT_SOUTH = list(0, 0), TEXT_EAST = list(0, 0), TEXT_WEST = list(0, 0))
if(riding_offsets["[pass_index]"])
. = riding_offsets["[pass_index]"]
else if(riding_offsets["[RIDING_OFFSET_ALL]"])
. = riding_offsets["[RIDING_OFFSET_ALL]"]
/datum/component/riding/proc/set_riding_offsets(index, list/offsets)
if(!islist(offsets))
return FALSE
riding_offsets["[index]"] = offsets
//Override this to set the passengers/riders dir based on which passenger they are.
//ie: rider facing the vehicle's dir, but passenger 2 facing backwards, etc.
/datum/component/riding/proc/get_rider_dir(pass_index)
var/atom/movable/AM = parent
return AM.dir
//KEYS
/datum/component/riding/proc/keycheck(mob/user)
return !keytype || user.is_holding_item_of_type(keytype)
//BUCKLE HOOKS
/datum/component/riding/proc/restore_position(mob/living/buckled_mob)
if(isnull(buckled_mob))
return
buckled_mob.pixel_x = buckled_mob.base_pixel_x
buckled_mob.pixel_y = buckled_mob.base_pixel_y
var/atom/source = parent
SET_PLANE_EXPLICIT(buckled_mob, initial(buckled_mob.plane), source)
if(buckled_mob.client)
buckled_mob.client.view_size.resetToDefault()
//MOVEMENT
/datum/component/riding/proc/turf_check(turf/next, turf/current)
if(allowed_turf_typecache && !allowed_turf_typecache[next.type])
return (allow_one_away_from_valid_turf && allowed_turf_typecache[current.type])
else if(forbid_turf_typecache && forbid_turf_typecache[next.type])
return (allow_one_away_from_valid_turf && !forbid_turf_typecache[current.type])
return TRUE
/datum/component/riding/proc/handle_ride(mob/user, direction)
var/atom/movable/AM = parent
if(user.incapacitated())
Unbuckle(user)
return
if(world.time < last_vehicle_move + ((last_move_diagonal? 2 : 1) * vehicle_move_delay * CONFIG_GET(number/movedelay/run_delay))) //yogs - fixed this to work with movespeed
return
AM.set_glide_size((last_move_diagonal? 2 : 1) * DELAY_TO_GLIDE_SIZE(vehicle_move_delay) * CONFIG_GET(number/movedelay/run_delay))
for(var/mob/M in AM.buckled_mobs)
ride_check(M)
M.set_glide_size(AM.glide_size)
last_vehicle_move = world.time
if(keycheck(user))
var/turf/next = get_step(AM, direction)
var/turf/current = get_turf(AM)
if(!istype(next) || !istype(current))
return //not happening.
if(!turf_check(next, current))
to_chat(user, "Your \the [AM] can not go onto [next]!")
return
if(!Process_Spacemove(direction) || !isturf(AM.loc))
return
step(AM, direction)
if((direction & (direction - 1)) && (AM.loc == next)) //moved diagonally
last_move_diagonal = TRUE
else
last_move_diagonal = FALSE
AM.set_glide_size((last_move_diagonal? 2 : 1) * DELAY_TO_GLIDE_SIZE(vehicle_move_delay) * CONFIG_GET(number/movedelay/run_delay))
for(var/mob/M in AM.buckled_mobs)
ride_check(M)
M.set_glide_size(AM.glide_size)
handle_vehicle_layer(direction)
handle_vehicle_offsets()
else
to_chat(user, span_notice("You'll need the keys in one of your hands to [drive_verb] [AM]."))
/datum/component/riding/proc/Unbuckle(atom/movable/M)
addtimer(CALLBACK(parent, TYPE_PROC_REF(/atom/movable, unbuckle_mob), M), 0, TIMER_UNIQUE)
/datum/component/riding/proc/Process_Spacemove(direction)
var/atom/movable/AM = parent
return override_allow_spacemove || AM.has_gravity()
/datum/component/riding/proc/account_limbs(mob/living/M)
if(M.get_num_legs() < 2 && !slowed)
vehicle_move_delay = vehicle_move_delay + slowvalue
slowed = TRUE
else if(slowed)
vehicle_move_delay = vehicle_move_delay - slowvalue
slowed = FALSE
///////Yes, I said humans. No, this won't end well...//////////
/datum/component/riding/human
del_on_unbuckle_all = TRUE
/datum/component/riding/human/Initialize()
. = ..()
RegisterSignal(parent, COMSIG_HUMAN_MELEE_UNARMED_ATTACK, PROC_REF(on_host_unarmed_melee))
/datum/component/riding/human/vehicle_mob_unbuckle(datum/source, mob/living/M, force = FALSE)
unequip_buckle_inhands(parent)
var/mob/living/carbon/human/AM = parent
AM.remove_movespeed_modifier(MOVESPEED_ID_HUMAN_CARRYING)
. = ..()
/datum/component/riding/human/vehicle_mob_buckle(datum/source, mob/living/M, force = FALSE)
. = ..()
var/mob/living/carbon/human/AM = parent
var/slowdown = AM.dna.check_mutation(STRONG) ? 0 : HUMAN_CARRY_SLOWDOWN
AM.add_movespeed_modifier(MOVESPEED_ID_HUMAN_CARRYING, multiplicative_slowdown = slowdown)
/datum/component/riding/human/proc/on_host_unarmed_melee(atom/target, modifiers)
var/mob/living/carbon/human/AM = parent
if(modifiers && modifiers[RIGHT_CLICK] && (target in AM.buckled_mobs))
force_dismount(target)
/datum/component/riding/human/handle_vehicle_layer(dir)
var/atom/movable/AM = parent
if(!AM.buckled_mobs || !AM.buckled_mobs.len)
AM.layer = MOB_LAYER
return
for(var/mob/M in AM.buckled_mobs) //ensure proper layering of piggyback and carry, sometimes weird offsets get applied
M.layer = MOB_LAYER
if(!AM.buckle_lying) // rider is vertical, must be piggybacking
if(dir == SOUTH)
AM.layer = MOB_ABOVE_PIGGYBACK_LAYER
else
AM.layer = MOB_BELOW_PIGGYBACK_LAYER
else // laying flat, we must be firemanning the rider
if(dir == NORTH)
AM.layer = MOB_BELOW_PIGGYBACK_LAYER
else
AM.layer = MOB_ABOVE_PIGGYBACK_LAYER
/datum/component/riding/human/get_offsets(pass_index)
var/mob/living/carbon/human/H = parent
if(H.buckle_lying)
return list(TEXT_NORTH = list(0, 6), TEXT_SOUTH = list(0, 6), TEXT_EAST = list(0, 6), TEXT_WEST = list(0, 6))
else
return list(TEXT_NORTH = list(0, 6), TEXT_SOUTH = list(0, 6), TEXT_EAST = list(-6, 4), TEXT_WEST = list( 6, 4))
/datum/component/riding/human/force_dismount(mob/living/user)
var/atom/movable/AM = parent
AM.unbuckle_mob(user)
user.Knockdown(60)
user.visible_message(span_warning("[AM] pushes [user] off of [AM.p_them()]!"))
/datum/component/riding/human/riding_can_z_move(atom/movable/movable_parent, direction, turf/start, turf/destination, z_move_flags, mob/living/rider)
if(!(z_move_flags & ZMOVE_CAN_FLY_CHECKS))
return COMPONENT_RIDDEN_ALLOW_Z_MOVE
// if(!can_be_driven)
// if(z_move_flags & ZMOVE_FEEDBACK)
// to_chat(rider, span_warning("[movable_parent] cannot be driven around. Unbuckle from [movable_parent.p_them()] first."))
// return COMPONENT_RIDDEN_STOP_Z_MOVE
if(!ride_check(rider, FALSE))
if(z_move_flags & ZMOVE_FEEDBACK)
to_chat(rider, span_warning("You're unable to ride [movable_parent] right now!"))
return COMPONENT_RIDDEN_STOP_Z_MOVE
return COMPONENT_RIDDEN_ALLOW_Z_MOVE
/datum/component/riding/cyborg
del_on_unbuckle_all = TRUE
/datum/component/riding/cyborg/ride_check(mob/user)
var/atom/movable/AM = parent
if(user.incapacitated())
var/kick = TRUE
if(iscyborg(AM))
var/mob/living/silicon/robot/R = AM
if(R.module && R.module.ride_allow_incapacitated)
kick = FALSE
if(kick)
to_chat(user, span_userdanger("You fall off of [AM]!"))
Unbuckle(user)
return
if(iscarbon(user))
var/mob/living/carbon/carbonuser = user
if(!carbonuser.get_num_arms())
Unbuckle(user)
to_chat(user, span_userdanger("You can't grab onto [AM] with no hands!"))
return
/datum/component/riding/cyborg/handle_vehicle_layer(dir)
var/atom/movable/robot_parent = parent
if(dir == SOUTH)
robot_parent.layer = MOB_ABOVE_PIGGYBACK_LAYER
else
robot_parent.layer = MOB_BELOW_PIGGYBACK_LAYER
/datum/component/riding/cyborg/get_offsets(pass_index) // list(dir = x, y, layer)
return list(TEXT_NORTH = list(0, 4), TEXT_SOUTH = list(0, 4), TEXT_EAST = list(-6, 3), TEXT_WEST = list( 6, 3))
/datum/component/riding/cyborg/handle_vehicle_offsets()
var/atom/movable/AM = parent
if(AM.has_buckled_mobs())
for(var/mob/living/M in AM.buckled_mobs)
M.setDir(AM.dir)
if(iscyborg(AM))
var/mob/living/silicon/robot/R = AM
if(istype(R.module))
M.pixel_x = R.module.ride_offset_x[dir2text(AM.dir)]
M.pixel_y = R.module.ride_offset_y[dir2text(AM.dir)]
else
..()
/datum/component/riding/cyborg/force_dismount(mob/living/M)
var/atom/movable/AM = parent
var/mob/living/silicon/robot/S = AM
if(S.throwcooldown)
to_chat(S, "You have to wait for your motors to recharge")
return
M.visible_message(span_warning("[AM] queues their servos to fling [M]!"))
playsound(AM,'sound/misc/borg/fling_start.ogg',80,1,-1)
if(!do_after(AM, 1 SECONDS, M))
M.visible_message(span_boldwarning("[AM]'s servos disengage!"))
playsound(AM,'sound/misc/borg/fling_cancel.ogg',80,1,-1)
return
//sanity check after the timer to make sure they're still buckled
if(!S.has_buckled_mobs())
M.visible_message(span_boldwarning("[AM]'s servos pop!"))
playsound(AM,'sound/misc/borg/fling_pop.ogg',80,1,-1)
//consider adding some damage as a borg if you mess up your timing
return;
//if we're a borg with a person we're gonna wait until here to spin so it waits until after we charge up the servos
S.spin(20, 1)
playsound(AM,'sound/misc/borg/fling_throw.ogg',80,1,-1)
AM.unbuckle_mob(M)
var/turf/target = get_edge_target_turf(AM, AM.dir)
var/turf/targetm = get_step(get_turf(AM), AM.dir)
M.Move(targetm)
M.visible_message(span_warning("[M] is thrown clear of [AM]!"))
M.throw_at(target, 14, 5, AM)
M.Paralyze(60)
S.throwcooldown = TRUE
addtimer(VARSET_CALLBACK(S, throwcooldown, FALSE), 10 SECONDS)
/datum/component/riding/cyborg/riding_can_z_move(atom/movable/movable_parent, direction, turf/start, turf/destination, z_move_flags, mob/living/rider)
if(!(z_move_flags & ZMOVE_CAN_FLY_CHECKS))
return COMPONENT_RIDDEN_ALLOW_Z_MOVE
// if(!can_be_driven)
// if(z_move_flags & ZMOVE_FEEDBACK)
// to_chat(rider, span_warning("[movable_parent] cannot be driven around. Unbuckle from [movable_parent.p_them()] first."))
// return COMPONENT_RIDDEN_STOP_Z_MOVE
if(!ride_check(rider, FALSE))
if(z_move_flags & ZMOVE_FEEDBACK)
to_chat(rider, span_warning("You're unable to ride [movable_parent] right now!"))
return COMPONENT_RIDDEN_STOP_Z_MOVE
return COMPONENT_RIDDEN_ALLOW_Z_MOVE
/datum/component/riding/proc/equip_buckle_inhands(mob/living/carbon/human/user, amount_required = 1, riding_target_override = null)
var/atom/movable/AM = parent
var/amount_equipped = 0
for(var/amount_needed = amount_required, amount_needed > 0, amount_needed--)
var/obj/item/riding_offhand/inhand = new /obj/item/riding_offhand(user)
if(!riding_target_override)
inhand.rider = user
else
inhand.rider = riding_target_override
inhand.parent = AM
for(var/obj/item/I in user.held_items) // delete any hand items like slappers that could still totally be used to grab on
if((I.obj_flags & HAND_ITEM))
qdel(I)
if(user.put_in_hands(inhand, TRUE))
amount_equipped++
else
break
if(amount_equipped >= amount_required)
return TRUE
else
unequip_buckle_inhands(user)
return FALSE
/datum/component/riding/proc/unequip_buckle_inhands(mob/living/carbon/user)
var/atom/movable/AM = parent
for(var/obj/item/riding_offhand/O in user.contents)
if(O.parent != AM)
CRASH("RIDING OFFHAND ON WRONG MOB")
if(O.selfdeleting)
continue
else
qdel(O)
return TRUE
/// Extra checks before buckled.can_z_move can be called in mob/living/can_z_move()
/datum/component/riding/proc/riding_can_z_move(atom/movable/movable_parent, direction, turf/start, turf/destination, z_move_flags, mob/living/rider)
SIGNAL_HANDLER
return COMPONENT_RIDDEN_ALLOW_Z_MOVE
/obj/item/riding_offhand
name = "offhand"
icon = 'icons/obj/misc.dmi'
icon_state = "offhand"
w_class = WEIGHT_CLASS_HUGE
item_flags = ABSTRACT | DROPDEL | NOBLUDGEON
resistance_flags = INDESTRUCTIBLE | LAVA_PROOF | FIRE_PROOF | UNACIDABLE | ACID_PROOF
var/mob/living/carbon/rider
var/mob/living/parent
var/selfdeleting = FALSE
/obj/item/riding_offhand/dropped()
selfdeleting = TRUE
. = ..()
/obj/item/riding_offhand/equipped()
if(loc != rider && loc != parent)
selfdeleting = TRUE
qdel(src)
. = ..()
/obj/item/riding_offhand/Destroy()
var/atom/movable/AM = parent
if(selfdeleting)
if(rider in AM.buckled_mobs)
AM.unbuckle_mob(rider)
. = ..()