mirror of
https://github.com/ParadiseSS13/Paradise.git
synced 2026-07-21 03:55:05 +01:00
Adds sscooters and skateboards [serious pr do not close after AFD] (#24934)
* WORLD WIDE NOISE * Makes it pass CI, tweaks values, glass table hoverboard, and railing grinding * grind on pipes * https://youtu.be/cTiM1MJ1o7c?feature=shared * https://youtu.be/Jc6CHI9f6cM?feature=shared * temporary change for icon conflicts (hopefully) * Apply suggestions from code review Co-authored-by: DGamerL <108773801+DGamerL@users.noreply.github.com> Co-authored-by: Henri215 <77684085+Henri215@users.noreply.github.com> Signed-off-by: Qwertytoforty <52090703+Qwertytoforty@users.noreply.github.com> * Apply suggestions from code review Co-authored-by: Luc <89928798+lewcc@users.noreply.github.com> Co-authored-by: Matt <116982774+Burzah@users.noreply.github.com> Signed-off-by: Qwertytoforty <52090703+Qwertytoforty@users.noreply.github.com> * The rest of the fun loving owl * missed this one * Apply suggestions from code review Co-authored-by: Luc <89928798+lewcc@users.noreply.github.com> Signed-off-by: Qwertytoforty <52090703+Qwertytoforty@users.noreply.github.com> * changes * Apply suggestions from code review Co-authored-by: Ryan <80364400+Sirryan2002@users.noreply.github.com> Signed-off-by: Qwertytoforty <52090703+Qwertytoforty@users.noreply.github.com> * Update code/modules/vehicle/tg_vehicles/tg_vehicles.dm Co-authored-by: Luc <89928798+lewcc@users.noreply.github.com> Signed-off-by: Qwertytoforty <52090703+Qwertytoforty@users.noreply.github.com> * I love stealth conflicts! * purge lines * Apply suggestions from code review Co-authored-by: DGamerL <108773801+DGamerL@users.noreply.github.com> Signed-off-by: Qwertytoforty <52090703+Qwertytoforty@users.noreply.github.com> * Update tg_vehicles.dm * works works works * Update code/modules/vehicle/tg_vehicles/tg_vehicles.dm Co-authored-by: DGamerL <108773801+DGamerL@users.noreply.github.com> Signed-off-by: Qwertytoforty <52090703+Qwertytoforty@users.noreply.github.com> --------- Signed-off-by: Qwertytoforty <52090703+Qwertytoforty@users.noreply.github.com> Co-authored-by: DGamerL <108773801+DGamerL@users.noreply.github.com> Co-authored-by: Henri215 <77684085+Henri215@users.noreply.github.com> Co-authored-by: Luc <89928798+lewcc@users.noreply.github.com> Co-authored-by: Matt <116982774+Burzah@users.noreply.github.com> Co-authored-by: Ryan <80364400+Sirryan2002@users.noreply.github.com>
This commit is contained in:
@@ -0,0 +1,277 @@
|
||||
/**
|
||||
* 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
|
||||
|
||||
/**
|
||||
* 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
|
||||
|
||||
/// 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)
|
||||
|
||||
/datum/component/riding/Initialize(mob/living/riding_mob, force = FALSE, buckle_mob_flags= NONE, potion_boost = FALSE)
|
||||
if(!ismovable(parent))
|
||||
return COMPONENT_INCOMPATIBLE
|
||||
|
||||
handle_specials(riding_mob)
|
||||
ride_check_flags |= buckle_mob_flags
|
||||
|
||||
if(potion_boost)
|
||||
vehicle_move_delay = round(GLOB.configuration.movement.human_delay * 0.85, 0.01)
|
||||
|
||||
/datum/component/riding/RegisterWithParent()
|
||||
. = ..()
|
||||
RegisterSignal(parent, COMSIG_ATOM_DIR_CHANGE, PROC_REF(vehicle_turned))
|
||||
RegisterSignal(parent, COMSIG_MOVABLE_UNBUCKLE, PROC_REF(vehicle_mob_unbuckle))
|
||||
RegisterSignal(parent, COMSIG_MOVABLE_BUCKLE, PROC_REF(vehicle_mob_buckle))
|
||||
RegisterSignal(parent, COMSIG_MOVABLE_MOVED, PROC_REF(vehicle_moved))
|
||||
RegisterSignal(parent, COMSIG_MOVABLE_BUMP, PROC_REF(vehicle_bump))
|
||||
if(!can_force_unbuckle)
|
||||
RegisterSignal(parent, COMSIG_ATOM_ATTACK_HAND, PROC_REF(force_unbuckle))
|
||||
|
||||
/**
|
||||
* This proc handles all of the proc calls to things like set_vehicle_dir_layer() that a type of riding datum needs to call on creation
|
||||
*
|
||||
* The original riding component had these procs all called from the ridden object itself through the use of GetComponent() and LoadComponent()
|
||||
* This was obviously problematic for componentization, but while lots of the variables being set were able to be moved to component variables,
|
||||
* the proc calls couldn't be. Thus, anything that has to do an initial proc call should be handled here.
|
||||
*/
|
||||
/datum/component/riding/proc/handle_specials()
|
||||
return
|
||||
|
||||
/// This proc is called when a rider unbuckles, whether they chose to or not. If there's no more riders, this will be the riding component's death knell.
|
||||
/datum/component/riding/proc/vehicle_mob_unbuckle(datum/source, mob/living/rider, force = FALSE)
|
||||
SIGNAL_HANDLER //COMSIG_MOVABLE_UNBUCKLE
|
||||
|
||||
handle_unbuckle(rider)
|
||||
|
||||
/datum/component/riding/proc/handle_unbuckle(mob/living/rider)
|
||||
var/atom/movable/movable_parent = parent
|
||||
restore_position(rider)
|
||||
unequip_buckle_inhands(rider)
|
||||
UnregisterSignal(rider, COMSIG_LIVING_TRY_PULL)
|
||||
if(!movable_parent.has_buckled_mobs())
|
||||
qdel(src)
|
||||
|
||||
/// This proc is called when a rider buckles, allowing for offsets to be set properly
|
||||
/datum/component/riding/proc/vehicle_mob_buckle(datum/source, mob/living/rider, force = FALSE)
|
||||
SIGNAL_HANDLER //COMSIG_MOVABLE_BUCKLE
|
||||
|
||||
var/atom/movable/movable_parent = parent
|
||||
handle_vehicle_layer(movable_parent.dir)
|
||||
handle_vehicle_offsets(movable_parent.dir)
|
||||
|
||||
if(rider.pulling == source)
|
||||
rider.stop_pulling()
|
||||
RegisterSignal(rider, COMSIG_LIVING_TRY_PULL, PROC_REF(on_rider_try_pull))
|
||||
|
||||
/// This proc is called when the rider attempts to grab the thing they're riding, preventing them from doing so.
|
||||
/datum/component/riding/proc/on_rider_try_pull(mob/living/rider_pulling, atom/movable/target, force)
|
||||
SIGNAL_HANDLER // COMSIG_LIVING_TRY_PULL
|
||||
if(target == parent)
|
||||
var/mob/living/ridden = parent
|
||||
to_chat(ridden, "<span class='warning'>You can't pull [target]!</span>")
|
||||
return COMSIG_LIVING_CANCEL_PULL
|
||||
|
||||
/// 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
|
||||
|
||||
/// This is called after the ridden atom is successfully moved and is used to handle icon stuff
|
||||
/datum/component/riding/proc/vehicle_moved(datum/source, oldloc, dir, forced)
|
||||
SIGNAL_HANDLER //COMSIG_MOVABLE_MOVED
|
||||
|
||||
var/atom/movable/movable_parent = parent
|
||||
if(isnull(dir))
|
||||
dir = movable_parent.dir
|
||||
for(var/mob/buckled_mob in movable_parent.buckled_mobs)
|
||||
ride_check(buckled_mob)
|
||||
if(QDELETED(src))
|
||||
return // runtimed with piggy's without this, look into this more
|
||||
handle_vehicle_offsets(dir)
|
||||
handle_vehicle_layer(dir)
|
||||
|
||||
/// Turning is like moving
|
||||
/datum/component/riding/proc/vehicle_turned(datum/source, _old_dir, new_dir)
|
||||
SIGNAL_HANDLER //COMSIG_ATOM_DIR_CHANGE
|
||||
|
||||
vehicle_moved(source, null, new_dir)
|
||||
|
||||
/**
|
||||
* Check to see if we have all of the necessary bodyparts and not-falling-over statuses we need to stay onboard.
|
||||
* If not and if consequences is TRUE, well, there'll be consequences.
|
||||
*/
|
||||
/datum/component/riding/proc/ride_check(mob/living/rider, consequences = TRUE)
|
||||
return TRUE
|
||||
|
||||
/datum/component/riding/proc/handle_vehicle_offsets(dir)
|
||||
var/atom/movable/AM = parent
|
||||
var/AM_dir = "[dir]"
|
||||
var/passindex = 0
|
||||
if(!AM.has_buckled_mobs())
|
||||
return
|
||||
|
||||
for(var/mob/living/buckled_mob in AM.buckled_mobs)
|
||||
passindex++
|
||||
var/list/offsets = get_offsets(passindex)
|
||||
buckled_mob.setDir(dir)
|
||||
dir_loop:
|
||||
for(var/offsetdir in offsets)
|
||||
if(offsetdir == AM_dir)
|
||||
var/list/diroffsets = offsets[offsetdir]
|
||||
buckled_mob.pixel_x = diroffsets[1]
|
||||
if(length(diroffsets) >= 2)
|
||||
buckled_mob.pixel_y = diroffsets[2]
|
||||
if(length(diroffsets) == 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
|
||||
|
||||
/datum/component/riding/proc/set_vehicle_offsets(list/offsets)
|
||||
if(!islist(offsets))
|
||||
return FALSE
|
||||
directional_vehicle_offsets = offsets
|
||||
|
||||
/**
|
||||
* This proc is used to see if we have the appropriate key to drive this atom, if such a key is needed. Returns FALSE if we don't have what we need to drive.
|
||||
*
|
||||
* Still needs to be neatened up and spruced up with proper OOP, as a result of vehicles having their own key handling from other ridable atoms
|
||||
*/
|
||||
/datum/component/riding/proc/keycheck(mob/user)
|
||||
if(!keytype)
|
||||
return TRUE
|
||||
|
||||
if(istgvehicle(parent))
|
||||
var/obj/vehicle/vehicle_parent = parent
|
||||
return istype(vehicle_parent.inserted_key, keytype)
|
||||
|
||||
return 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 = initial(buckled_mob.pixel_x)
|
||||
buckled_mob.pixel_y = initial(buckled_mob.pixel_y)
|
||||
buckled_mob.plane = initial(buckled_mob.plane)
|
||||
|
||||
//MOVEMENT
|
||||
/datum/component/riding/proc/turf_check(turf/next, turf/current)
|
||||
if(allowed_turf_typecache && !allowed_turf_typecache[next.type])
|
||||
return allowed_turf_typecache[current.type]
|
||||
else if(forbid_turf_typecache && forbid_turf_typecache[next.type])
|
||||
return !forbid_turf_typecache[current.type]
|
||||
return TRUE
|
||||
|
||||
/// Every time the driver tries to move, this is called to see if they can actually drive and move the vehicle (via relaymove)
|
||||
/datum/component/riding/proc/driver_move(atom/movable/movable_parent, mob/living/user, direction)
|
||||
SIGNAL_HANDLER //COMSIG_RIDDEN_DRIVER_MOVE
|
||||
SHOULD_CALL_PARENT(TRUE)
|
||||
movable_parent.set_glide_size(DELAY_TO_GLIDE_SIZE(vehicle_move_delay))
|
||||
|
||||
/// So we can check all occupants when we bump a door to see if anyone has access
|
||||
/datum/component/riding/proc/vehicle_bump(atom/movable/movable_parent, obj/machinery/door/possible_bumped_door)
|
||||
SIGNAL_HANDLER //COMSIG_MOVABLE_BUMP
|
||||
if(!istype(possible_bumped_door))
|
||||
return
|
||||
for(var/occupant in movable_parent.buckled_mobs)
|
||||
INVOKE_ASYNC(possible_bumped_door, TYPE_PROC_REF(/obj/machinery/door/, bumpopen), occupant)
|
||||
|
||||
/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, continuous_move)
|
||||
var/atom/movable/AM = parent
|
||||
return override_allow_spacemove || has_gravity(AM)
|
||||
|
||||
/// currently replicated from ridable because we need this behavior here too, see if we can deal with that
|
||||
/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
|
||||
|
||||
/datum/component/riding/proc/force_unbuckle(atom/movable/source, mob/living/living_hitter)
|
||||
SIGNAL_HANDLER //COMSIG_ATOM_ATTACK_HAND
|
||||
|
||||
if(living_hitter in source.buckled_mobs)
|
||||
return
|
||||
return COMPONENT_CANCEL_ATTACK_CHAIN
|
||||
|
||||
@@ -0,0 +1,175 @@
|
||||
// For any /obj/tgvehicle's that can be ridden
|
||||
|
||||
/datum/component/riding/vehicle/Initialize(mob/living/riding_mob, force = FALSE, ride_check_flags = (RIDER_NEEDS_LEGS | RIDER_NEEDS_ARMS), potion_boost = FALSE)
|
||||
if(!istgvehicle(parent))
|
||||
return COMPONENT_INCOMPATIBLE
|
||||
return ..()
|
||||
|
||||
/datum/component/riding/vehicle/RegisterWithParent()
|
||||
. = ..()
|
||||
RegisterSignal(parent, COMSIG_RIDDEN_DRIVER_MOVE, PROC_REF(driver_move))
|
||||
|
||||
/datum/component/riding/vehicle/driver_move(atom/movable/movable_parent, mob/living/user, direction)
|
||||
if(!COOLDOWN_FINISHED(src, vehicle_move_cooldown))
|
||||
return COMPONENT_DRIVER_BLOCK_MOVE
|
||||
var/obj/tgvehicle/vehicle_parent = parent
|
||||
|
||||
if(!keycheck(user))
|
||||
if(COOLDOWN_FINISHED(src, message_cooldown))
|
||||
to_chat(user, "<span class='warning'>[vehicle_parent] has no key inserted!</span>")
|
||||
COOLDOWN_START(src, message_cooldown, 5 SECONDS)
|
||||
return COMPONENT_DRIVER_BLOCK_MOVE
|
||||
|
||||
if(user.incapacitated())
|
||||
if(ride_check_flags & UNBUCKLE_DISABLED_RIDER)
|
||||
INVOKE_ASYNC(vehicle_parent, TYPE_PROC_REF(/atom/movable, unbuckle_mob), user, TRUE)
|
||||
user.visible_message("<span class='danger'>[user] falls off [vehicle_parent].</span>",\
|
||||
"<span class='danger'>You slip off [vehicle_parent] as your body slumps!</span>")
|
||||
user.Stun(3 SECONDS)
|
||||
|
||||
if(COOLDOWN_FINISHED(src, message_cooldown))
|
||||
to_chat(user, "<span class='warning'>You cannot operate [vehicle_parent] right now!</span>")
|
||||
COOLDOWN_START(src, message_cooldown, 5 SECONDS)
|
||||
return COMPONENT_DRIVER_BLOCK_MOVE
|
||||
|
||||
if(ride_check_flags & RIDER_NEEDS_LEGS && HAS_TRAIT(user, TRAIT_FLOORED))
|
||||
if(ride_check_flags & UNBUCKLE_DISABLED_RIDER)
|
||||
INVOKE_ASYNC(vehicle_parent, TYPE_PROC_REF(/atom/movable, unbuckle_mob), user, TRUE)
|
||||
user.visible_message("<span class='danger'>[user] falls off [vehicle_parent].</span>",\
|
||||
"<span class='danger'>You fall off [vehicle_parent] while trying to operate it while unable to stand!</span>")
|
||||
user.Stun(3 SECONDS)
|
||||
|
||||
if(COOLDOWN_FINISHED(src, message_cooldown))
|
||||
to_chat(user, "<span class='warning'>You can't seem to manage that while unable to stand up enough to move [vehicle_parent]...</span>")
|
||||
COOLDOWN_START(src, message_cooldown, 5 SECONDS)
|
||||
return COMPONENT_DRIVER_BLOCK_MOVE
|
||||
|
||||
if(ride_check_flags & RIDER_NEEDS_ARMS && HAS_TRAIT(user, TRAIT_HANDS_BLOCKED))
|
||||
if(ride_check_flags & UNBUCKLE_DISABLED_RIDER)
|
||||
INVOKE_ASYNC(vehicle_parent, TYPE_PROC_REF(/atom/movable, unbuckle_mob), user, TRUE)
|
||||
user.visible_message("<span class='danger'>[user] falls off [vehicle_parent].</span>",\
|
||||
"<span class='danger'>You fall off [vehicle_parent] while trying to operate it without being able to hold on!</span>")
|
||||
user.Stun(3 SECONDS)
|
||||
|
||||
if(COOLDOWN_FINISHED(src, message_cooldown))
|
||||
to_chat(user, "<span class='warning'>You can't seem to hold onto [vehicle_parent] to move it...</span>")
|
||||
COOLDOWN_START(src, message_cooldown, 5 SECONDS)
|
||||
return COMPONENT_DRIVER_BLOCK_MOVE
|
||||
|
||||
handle_ride(user, direction)
|
||||
return ..()
|
||||
|
||||
/// This handles the actual movement for vehicles once [/datum/component/riding/vehicle/proc/driver_move] has given us the green light
|
||||
/datum/component/riding/vehicle/proc/handle_ride(mob/user, direction)
|
||||
var/atom/movable/movable_parent = parent
|
||||
|
||||
var/turf/next = get_step(movable_parent, direction)
|
||||
var/turf/current = get_turf(movable_parent)
|
||||
if(!istype(next) || !istype(current))
|
||||
return //not happening.
|
||||
if(!turf_check(next, current))
|
||||
to_chat(user, "<span class='warning'>[movable_parent] cannot go onto [next]!</span>")
|
||||
return
|
||||
if(!Process_Spacemove(direction) || !isturf(movable_parent.loc))
|
||||
return
|
||||
|
||||
step(movable_parent, direction)
|
||||
last_move_diagonal = ((direction & (direction - 1)) && (movable_parent.loc == next))
|
||||
COOLDOWN_START(src, vehicle_move_cooldown, (last_move_diagonal ? 2 : 1) * vehicle_move_delay)
|
||||
|
||||
if(QDELETED(src))
|
||||
return
|
||||
handle_vehicle_layer(movable_parent.dir)
|
||||
handle_vehicle_offsets(movable_parent.dir)
|
||||
return TRUE
|
||||
|
||||
/datum/component/riding/vehicle/scooter
|
||||
ride_check_flags = RIDER_NEEDS_LEGS | RIDER_NEEDS_ARMS | UNBUCKLE_DISABLED_RIDER
|
||||
|
||||
/datum/component/riding/vehicle/scooter/handle_specials(mob/living/riding_mob)
|
||||
. = ..()
|
||||
if(isrobot(riding_mob))
|
||||
set_riding_offsets(RIDING_OFFSET_ALL, list(TEXT_NORTH = list(0), TEXT_SOUTH = list(0), TEXT_EAST = list(0), TEXT_WEST = list(2)))
|
||||
else
|
||||
set_riding_offsets(RIDING_OFFSET_ALL, list(TEXT_NORTH = list(2), TEXT_SOUTH = list(-2), TEXT_EAST = list(0), TEXT_WEST = list(2)))
|
||||
|
||||
/datum/component/riding/vehicle/scooter/skateboard
|
||||
vehicle_move_delay = 1.5
|
||||
ride_check_flags = RIDER_NEEDS_LEGS | UNBUCKLE_DISABLED_RIDER
|
||||
///If TRUE, the vehicle will be slower (but safer) to ride on walk intent.
|
||||
var/can_slow_down = TRUE
|
||||
|
||||
/datum/component/riding/vehicle/scooter/skateboard/handle_specials()
|
||||
. = ..()
|
||||
set_vehicle_dir_layer(SOUTH, ABOVE_MOB_LAYER)
|
||||
set_vehicle_dir_layer(NORTH, OBJ_LAYER)
|
||||
set_vehicle_dir_layer(EAST, OBJ_LAYER)
|
||||
set_vehicle_dir_layer(WEST, OBJ_LAYER)
|
||||
|
||||
/datum/component/riding/vehicle/scooter/skateboard/RegisterWithParent()
|
||||
. = ..()
|
||||
if(can_slow_down)
|
||||
RegisterSignal(parent, COMSIG_PARENT_EXAMINE, PROC_REF(on_examine))
|
||||
var/obj/tgvehicle/scooter/skateboard/board = parent
|
||||
if(istype(board))
|
||||
board.can_slow_down = can_slow_down
|
||||
|
||||
/datum/component/riding/vehicle/scooter/skateboard/proc/on_examine(datum/source, mob/user, list/examine_list)
|
||||
SIGNAL_HANDLER //COMSIG_PARENT_EXAMINE
|
||||
examine_list += "<span class='notice>Going nice and slow at walk speed will prevent crashing into things.</span>"
|
||||
|
||||
/datum/component/riding/vehicle/scooter/skateboard/vehicle_mob_buckle(datum/source, mob/living/rider, force = FALSE)
|
||||
. = ..()
|
||||
if(can_slow_down)
|
||||
RegisterSignal(rider, COMSIG_MOVE_INTENT_TOGGLED, PROC_REF(toggle_move_delay))
|
||||
toggle_move_delay(rider)
|
||||
|
||||
/datum/component/riding/vehicle/scooter/skateboard/handle_unbuckle(mob/living/rider)
|
||||
. = ..()
|
||||
if(can_slow_down)
|
||||
toggle_move_delay(rider)
|
||||
UnregisterSignal(rider, COMSIG_MOVE_INTENT_TOGGLED)
|
||||
|
||||
/datum/component/riding/vehicle/scooter/skateboard/proc/toggle_move_delay(mob/living/rider)
|
||||
SIGNAL_HANDLER //COMSIG_MOVE_INTENT_TOGGLED
|
||||
vehicle_move_delay = initial(vehicle_move_delay)
|
||||
if(rider.m_intent == MOVE_INTENT_WALK)
|
||||
vehicle_move_delay += 0.6
|
||||
|
||||
/datum/component/riding/vehicle/scooter/skateboard/pro
|
||||
vehicle_move_delay = 1
|
||||
|
||||
///This one lets the rider ignore gravity, move in zero g and son on, but only on ground turfs or at most one z-level above them.
|
||||
/datum/component/riding/vehicle/scooter/skateboard/hover
|
||||
vehicle_move_delay = 1
|
||||
override_allow_spacemove = TRUE
|
||||
|
||||
/datum/component/riding/vehicle/scooter/skateboard/hover/RegisterWithParent()
|
||||
. = ..()
|
||||
RegisterSignal(parent, COMSIG_ATOM_HAS_GRAVITY, PROC_REF(check_grav))
|
||||
RegisterSignal(parent, COMSIG_MOVABLE_SPACEMOVE, PROC_REF(check_drifting))
|
||||
hover_check()
|
||||
|
||||
///Makes sure that the vehicle is grav-less if capable of zero-g movement. Forced gravity will honestly screw this.
|
||||
/datum/component/riding/vehicle/scooter/skateboard/hover/proc/check_grav(datum/source, turf/gravity_turf, list/gravs)
|
||||
SIGNAL_HANDLER //COMSIG_ATOM_HAS_GRAVITY
|
||||
if(override_allow_spacemove)
|
||||
gravs += 0
|
||||
|
||||
///Makes sure the vehicle isn't drifting while it can be maneuvered.
|
||||
/datum/component/riding/vehicle/scooter/skateboard/hover/proc/check_drifting(datum/source, movement_dir, continuous_move)
|
||||
SIGNAL_HANDLER //COMSIG_MOVABLE_SPACEMOVE
|
||||
if(override_allow_spacemove)
|
||||
return COMSIG_MOVABLE_STOP_SPACEMOVE
|
||||
|
||||
/datum/component/riding/vehicle/scooter/skateboard/hover/vehicle_moved(atom/movable/source, oldloc, dir, forced)
|
||||
. = ..()
|
||||
hover_check(TRUE)
|
||||
|
||||
///Makes sure that the hoverboard can move in zero-g in (open) space but only there's a ground turf on the z-level below.
|
||||
/datum/component/riding/vehicle/scooter/skateboard/hover/proc/hover_check(is_moving = FALSE)
|
||||
var/atom/movable/movable = parent
|
||||
if(!isspaceturf(movable.loc) || locate(/obj/structure/railing) in range(1, movable.loc)) //If you can't grind the faragus rails, why live?
|
||||
override_allow_spacemove = TRUE
|
||||
return
|
||||
override_allow_spacemove = FALSE
|
||||
@@ -0,0 +1,181 @@
|
||||
/**
|
||||
* This element is used to indicate that a movable atom can be mounted by mobs in order to ride it. The movable is considered mounted when a mob is buckled to it,
|
||||
* at which point a [riding component][/datum/component/riding] is created on the movable, and that component handles the actual riding behavior.
|
||||
*
|
||||
* Besides the target, the ridable element has one argument: the component subtype. This is not really ideal since there's ~20-30 component subtypes rather than
|
||||
* having the behavior defined on the ridable atoms themselves or some such, but because the old riding behavior was so horrifyingly spread out and redundant,
|
||||
* just having the variables, behavior, and procs be standardized is still a big improvement.
|
||||
*/
|
||||
/datum/element/ridable
|
||||
element_flags = ELEMENT_BESPOKE
|
||||
|
||||
/// The specific riding component subtype we're loading our instructions from, don't leave this as default please!
|
||||
var/riding_component_type = /datum/component/riding
|
||||
/// If we have a xenobio red potion applied to us, we get split off so we can pass our special status onto new riding components
|
||||
var/potion_boosted = FALSE
|
||||
|
||||
/datum/element/ridable/Attach(atom/movable/target, component_type = /datum/component/riding, potion_boost = FALSE)
|
||||
. = ..()
|
||||
if(!ismovable(target))
|
||||
return COMPONENT_INCOMPATIBLE
|
||||
|
||||
if(component_type == /datum/component/riding)
|
||||
stack_trace("Tried attaching a ridable element to [target] with basic/abstract /datum/component/riding component type. Please designate a specific riding component subtype when adding the ridable element.")
|
||||
return COMPONENT_INCOMPATIBLE
|
||||
|
||||
target.can_buckle = TRUE
|
||||
riding_component_type = component_type
|
||||
potion_boosted = potion_boost
|
||||
|
||||
RegisterSignal(target, COMSIG_MOVABLE_PREBUCKLE, PROC_REF(check_mounting))
|
||||
if(istgvehicle(target))
|
||||
RegisterSignal(target, COMSIG_SPEED_POTION_APPLIED, PROC_REF(check_potion))
|
||||
if(ismob(target))
|
||||
RegisterSignal(target, COMSIG_MOB_STATCHANGE, PROC_REF(on_stat_change))
|
||||
|
||||
/datum/element/ridable/Detach(atom/movable/target)
|
||||
target.can_buckle = initial(target.can_buckle)
|
||||
UnregisterSignal(target, list(COMSIG_MOVABLE_PREBUCKLE, COMSIG_SPEED_POTION_APPLIED, COMSIG_MOB_STATCHANGE))
|
||||
return ..()
|
||||
|
||||
/// Someone is buckling to this movable, which is literally the only thing we care about (other than speed potions)
|
||||
/datum/element/ridable/proc/check_mounting(atom/movable/target_movable, mob/living/potential_rider, force = FALSE, ride_check_flags = NONE)
|
||||
SIGNAL_HANDLER //COMSIG_MOVABLE_PREBUCKLE
|
||||
INVOKE_ASYNC(src, PROC_REF(check_mounting_async_edition), target_movable, potential_rider, force, ride_check_flags)
|
||||
|
||||
/datum/element/ridable/proc/check_mounting_async_edition(atom/movable/target_movable, mob/living/potential_rider, force = FALSE, ride_check_flags = NONE)
|
||||
var/arms_needed = 0
|
||||
if(ride_check_flags & RIDER_NEEDS_ARMS)
|
||||
arms_needed = 2
|
||||
else if(ride_check_flags & RIDER_NEEDS_ARM)
|
||||
arms_needed = 1
|
||||
ride_check_flags &= ~RIDER_NEEDS_ARM
|
||||
ride_check_flags |= RIDER_NEEDS_ARMS
|
||||
|
||||
if(arms_needed && !equip_buckle_inhands(potential_rider, arms_needed, target_movable))
|
||||
potential_rider.visible_message("<span class='warning'>[potential_rider] can't get a grip on [target_movable] because [potential_rider.p_their()] hands are full!</span>", "<span class='warning'>You can't get a grip on [target_movable] because your hands are full!</span>")
|
||||
return COMPONENT_BLOCK_BUCKLE
|
||||
|
||||
if((ride_check_flags & RIDER_NEEDS_LEGS) && HAS_TRAIT(potential_rider, TRAIT_FLOORED))
|
||||
potential_rider.visible_message("<span class='warning'>[potential_rider] can't get [potential_rider.p_their()] footing on [target_movable]!</span>",
|
||||
"<span class='warning'>You can't get your footing on [target_movable]!</span>")
|
||||
return COMPONENT_BLOCK_BUCKLE
|
||||
|
||||
var/mob/living/target_living = target_movable
|
||||
|
||||
target_living.AddComponent(riding_component_type, potential_rider, force, ride_check_flags, potion_boost = potion_boosted)
|
||||
|
||||
/// Try putting the appropriate number of [riding offhand items][/obj/item/riding_offhand] into the target's hands, return FALSE if we can't
|
||||
/datum/element/ridable/proc/equip_buckle_inhands(mob/living/carbon/human/user, amount_required = 1, atom/movable/target_movable, riding_target_override = null)
|
||||
var/atom/movable/AM = target_movable
|
||||
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
|
||||
var/list/held_items = list()
|
||||
held_items += user.l_hand
|
||||
held_items += user.r_hand
|
||||
for(var/obj/item/I in held_items) // delete any hand items like slappers that could still totally be used to grab on
|
||||
if(I.flags & DROPDEL & ABSTRACT)
|
||||
qdel(I)
|
||||
|
||||
// this would be put_in_hands() if it didn't have the chance to sleep, since this proc gets called from a signal handler that relies on what this returns
|
||||
var/inserted_successfully = FALSE
|
||||
if(user.put_in_inactive_hand(inhand))
|
||||
inserted_successfully = TRUE
|
||||
else
|
||||
if(user.put_in_active_hand(inhand))
|
||||
inserted_successfully = TRUE
|
||||
|
||||
if(inserted_successfully)
|
||||
amount_equipped++
|
||||
else
|
||||
qdel(inhand)
|
||||
return FALSE
|
||||
|
||||
if(amount_equipped >= amount_required)
|
||||
return TRUE
|
||||
else
|
||||
unequip_buckle_inhands(user, target_movable)
|
||||
return FALSE
|
||||
|
||||
/// Checks to see if we've been hit with a red xenobio potion to make us faster. This is only registered if we're a vehicle
|
||||
/datum/element/ridable/proc/check_potion(atom/movable/ridable_atom, obj/item/slimepotion/speed/speed_potion, mob/living/user)
|
||||
SIGNAL_HANDLER
|
||||
|
||||
if(potion_boosted)
|
||||
to_chat(user, "<span class='warning'>[ridable_atom] has already been coated with red, that's as fast as it'll go!</span>")
|
||||
return
|
||||
if(ridable_atom.has_buckled_mobs()) // effect won't take place til the next time someone mounts it, so just prevent that situation
|
||||
to_chat(user, "<span class='warning'>It's too dangerous to smear [speed_potion] on [ridable_atom] while it's being ridden!</span>")
|
||||
return
|
||||
var/speed_limit = round(GLOB.configuration.movement.human_delay * 0.85, 0.01)
|
||||
var/datum/component/riding/theoretical_riding_component = riding_component_type
|
||||
var/theoretical_speed = initial(theoretical_riding_component.vehicle_move_delay)
|
||||
if(theoretical_speed <= speed_limit) // i say speed but this is actually move delay, so you have to be ABOVE the speed limit to pass
|
||||
to_chat(user, "<span class='warning'>[ridable_atom] can't be made any faster!</span>")
|
||||
return
|
||||
Detach(ridable_atom)
|
||||
ridable_atom.AddElement(/datum/element/ridable, component_type = riding_component_type, potion_boost = TRUE)
|
||||
to_chat(user, "<span class='notice'>You slather the red gunk over [ridable_atom], making it faster.</span>")
|
||||
ridable_atom.remove_atom_colour(WASHABLE_COLOUR_PRIORITY)
|
||||
ridable_atom.add_atom_colour("#6e6e86", FIXED_COLOUR_PRIORITY)
|
||||
ADD_TRAIT(ridable_atom, TRAIT_OIL_SLICKED, "potion")
|
||||
qdel(speed_potion)
|
||||
return SPEED_POTION_STOP
|
||||
|
||||
/// Remove all of the relevant [riding offhand items][/obj/item/riding_offhand] from the target
|
||||
/datum/element/ridable/proc/unequip_buckle_inhands(mob/living/carbon/user, atom/movable/target_movable)
|
||||
var/atom/movable/AM = target_movable
|
||||
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
|
||||
|
||||
/datum/element/ridable/proc/on_stat_change(mob/source)
|
||||
SIGNAL_HANDLER
|
||||
|
||||
// If we're dead, don't let anyone buckle onto us
|
||||
if(source.stat == DEAD)
|
||||
source.can_buckle = FALSE
|
||||
INVOKE_ASYNC(src, TYPE_PROC_REF(/atom/movable, unbuckle_all_mobs), source)
|
||||
|
||||
// If we're alive, back to being buckle-able
|
||||
else
|
||||
source.can_buckle = TRUE
|
||||
|
||||
/obj/item/riding_offhand
|
||||
name = "offhand"
|
||||
icon_state = "offhand"
|
||||
w_class = WEIGHT_CLASS_HUGE
|
||||
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
|
||||
return ..()
|
||||
|
||||
/obj/item/riding_offhand/equipped()
|
||||
if(loc != rider && loc != parent)
|
||||
selfdeleting = TRUE
|
||||
qdel(src)
|
||||
return ..()
|
||||
|
||||
/obj/item/riding_offhand/Destroy()
|
||||
var/atom/movable/AM = parent
|
||||
if(selfdeleting)
|
||||
if(rider in AM.buckled_mobs)
|
||||
AM.unbuckle_mob(rider)
|
||||
return ..()
|
||||
|
||||
Reference in New Issue
Block a user