diff --git a/_maps/map_files/Delta/delta.dmm b/_maps/map_files/Delta/delta.dmm
index b7bf54b0027..17b427fb28b 100644
--- a/_maps/map_files/Delta/delta.dmm
+++ b/_maps/map_files/Delta/delta.dmm
@@ -16486,7 +16486,7 @@
/turf/simulated/floor/plasteel,
/area/atmos)
"bWf" = (
-/obj/vehicle/janicart{
+/obj/vehicle/ridden/janicart{
dir = 4
},
/turf/simulated/floor/plasteel{
@@ -30963,7 +30963,7 @@
/area/medical/paramedic)
"djT" = (
/obj/effect/decal/warning_stripes/northeastsouth,
-/obj/vehicle/ambulance{
+/obj/vehicle/ridden/ambulance{
dir = 4
},
/obj/machinery/light{
@@ -122046,7 +122046,7 @@
/turf/simulated/floor/plating/airless,
/area/engine/engineering)
"xqE" = (
-/obj/vehicle/secway,
+/obj/vehicle/ridden/secway,
/turf/simulated/floor/plasteel{
dir = 8;
icon_state = "vault"
diff --git a/_maps/map_files/RandomRuins/LavaRuins/lavaland_biodome_winter.dmm b/_maps/map_files/RandomRuins/LavaRuins/lavaland_biodome_winter.dmm
index c9b2160ab0b..874cd053af8 100644
--- a/_maps/map_files/RandomRuins/LavaRuins/lavaland_biodome_winter.dmm
+++ b/_maps/map_files/RandomRuins/LavaRuins/lavaland_biodome_winter.dmm
@@ -160,7 +160,7 @@
/turf/simulated/floor/plating/asteroid/snow/atmosphere,
/area/ruin/powered/snow_biodome)
"aJ" = (
-/obj/vehicle/atv,
+/obj/vehicle/ridden/atv,
/turf/simulated/floor/plating/asteroid/snow/atmosphere,
/area/ruin/powered/snow_biodome)
"aK" = (
diff --git a/_maps/map_files/RandomRuins/SpaceRuins/spacehotelv1.dmm b/_maps/map_files/RandomRuins/SpaceRuins/spacehotelv1.dmm
index 174d22e173b..4cc2d025c5e 100644
--- a/_maps/map_files/RandomRuins/SpaceRuins/spacehotelv1.dmm
+++ b/_maps/map_files/RandomRuins/SpaceRuins/spacehotelv1.dmm
@@ -4392,7 +4392,7 @@
d2 = 8;
icon_state = "0-8"
},
-/obj/vehicle/janicart{
+/obj/vehicle/ridden/janicart{
dir = 4
},
/turf/simulated/floor/plasteel{
diff --git a/_maps/map_files/RandomZLevels/evil_santa.dmm b/_maps/map_files/RandomZLevels/evil_santa.dmm
index a0df9a655b4..fce72ad6497 100644
--- a/_maps/map_files/RandomZLevels/evil_santa.dmm
+++ b/_maps/map_files/RandomZLevels/evil_santa.dmm
@@ -32,7 +32,7 @@
},
/area/vision_change_area/awaymission/evil_santa/hut_e)
"ae" = (
-/obj/vehicle/snowmobile/blue/key{
+/obj/vehicle/ridden/snowmobile/blue/key{
dir = 4
},
/turf/simulated/floor/wood,
@@ -2327,7 +2327,7 @@
/turf/simulated/floor/carpet,
/area/vision_change_area/awaymission/evil_santa/hut_n)
"sE" = (
-/obj/vehicle/snowmobile/blue/key,
+/obj/vehicle/ridden/snowmobile/blue/key,
/turf/simulated/floor/plating/asteroid/snow/atmosphere{
slowdown = 0
},
diff --git a/_maps/map_files/celestation/celestation.dmm b/_maps/map_files/celestation/celestation.dmm
index 4a225106122..78c291b10ba 100644
--- a/_maps/map_files/celestation/celestation.dmm
+++ b/_maps/map_files/celestation/celestation.dmm
@@ -19835,7 +19835,7 @@
/obj/effect/turf_decal/stripes/end{
dir = 4
},
-/obj/vehicle/ambulance{
+/obj/vehicle/ridden/ambulance{
dir = 4
},
/obj/machinery/light,
@@ -25169,7 +25169,7 @@
dir = 8;
pixel_x = -28
},
-/obj/vehicle/janicart,
+/obj/vehicle/ridden/janicart,
/turf/simulated/floor/plasteel,
/area/janitor)
"duG" = (
@@ -78220,7 +78220,7 @@
},
/area/medical/research)
"nHI" = (
-/obj/vehicle/janicart,
+/obj/vehicle/ridden/janicart,
/turf/simulated/floor/plasteel,
/area/janitor)
"nHL" = (
@@ -79485,7 +79485,7 @@
/area/maintenance/fsmaint2)
"nVx" = (
/obj/effect/turf_decal/delivery/red,
-/obj/vehicle/secway,
+/obj/vehicle/ridden/secway,
/obj/structure/sign/poster/official/help_others{
pixel_y = 32
},
@@ -85874,7 +85874,7 @@
/area/crew_quarters/captain)
"pgx" = (
/obj/effect/turf_decal/delivery/red,
-/obj/vehicle/secway,
+/obj/vehicle/ridden/secway,
/turf/simulated/floor/plasteel{
dir = 1;
icon_state = "darkred"
@@ -102947,7 +102947,7 @@
/turf/simulated/floor/grass,
/area/hallway/spacebridge/scidock)
"syE" = (
-/obj/vehicle/ambulance{
+/obj/vehicle/ridden/ambulance{
dir = 4
},
/obj/effect/turf_decal/stripes/end{
diff --git a/_maps/map_files/cerestation/cerestation.dmm b/_maps/map_files/cerestation/cerestation.dmm
index c9d129dfc45..709fbb34e6d 100644
--- a/_maps/map_files/cerestation/cerestation.dmm
+++ b/_maps/map_files/cerestation/cerestation.dmm
@@ -5725,7 +5725,7 @@
/area/security/main)
"aWi" = (
/obj/effect/decal/warning_stripes/northeastsouth,
-/obj/vehicle/ambulance{
+/obj/vehicle/ridden/ambulance{
dir = 4
},
/obj/machinery/light{
@@ -36566,7 +36566,7 @@
/obj/machinery/atmospherics/unary/vent_scrubber/on{
dir = 4
},
-/obj/vehicle/janicart,
+/obj/vehicle/ridden/janicart,
/obj/machinery/alarm{
pixel_y = 24
},
@@ -36601,7 +36601,7 @@
/turf/simulated/floor/plating,
/area/storage/tech)
"gJx" = (
-/obj/vehicle/janicart,
+/obj/vehicle/ridden/janicart,
/obj/machinery/atmospherics/pipe/simple/hidden/scrubbers{
dir = 4
},
@@ -39540,7 +39540,7 @@
/turf/simulated/floor/plating,
/area/hallway/primary/fore/west)
"hzm" = (
-/obj/vehicle/ambulance{
+/obj/vehicle/ridden/ambulance{
dir = 4
},
/obj/effect/decal/warning_stripes/northeastsouth,
@@ -40245,7 +40245,7 @@
/area/janitor)
"hKL" = (
/obj/effect/turf_decal/delivery/red,
-/obj/vehicle/secway,
+/obj/vehicle/ridden/secway,
/turf/simulated/floor/plasteel{
dir = 8;
icon_state = "darkred"
@@ -74900,7 +74900,7 @@
/turf/simulated/floor/plating,
/area/medical/medbreak)
"rzH" = (
-/obj/vehicle/secway,
+/obj/vehicle/ridden/secway,
/obj/effect/turf_decal/delivery/red,
/turf/simulated/floor/plasteel{
dir = 10;
diff --git a/_maps/map_files/cyberiad/cyberiad.dmm b/_maps/map_files/cyberiad/cyberiad.dmm
index 5b11ca44c0b..7b85efd265a 100644
--- a/_maps/map_files/cyberiad/cyberiad.dmm
+++ b/_maps/map_files/cyberiad/cyberiad.dmm
@@ -8724,7 +8724,7 @@
},
/area/security/permabrig)
"aBT" = (
-/obj/vehicle/secway,
+/obj/vehicle/ridden/secway,
/obj/item/key/security,
/turf/simulated/floor/plasteel{
dir = 1;
@@ -30504,7 +30504,7 @@
/turf/simulated/floor/plasteel,
/area/hallway/primary/central/se)
"bTG" = (
-/obj/vehicle/ambulance,
+/obj/vehicle/ridden/ambulance,
/obj/effect/decal/warning_stripes/west,
/obj/effect/decal/warning_stripes/south,
/obj/effect/decal/warning_stripes/east,
@@ -68651,7 +68651,7 @@
name = "north newscaster";
pixel_y = 34
},
-/obj/vehicle/janicart,
+/obj/vehicle/ridden/janicart,
/turf/simulated/floor/plasteel,
/area/janitor)
"lPa" = (
diff --git a/_maps/map_files/event/Station/delta_old.dmm b/_maps/map_files/event/Station/delta_old.dmm
index cfd7cf038dd..1a472c63fcb 100644
--- a/_maps/map_files/event/Station/delta_old.dmm
+++ b/_maps/map_files/event/Station/delta_old.dmm
@@ -15588,7 +15588,7 @@
pixel_x = -28;
pixel_y = 2
},
-/obj/vehicle/janicart{
+/obj/vehicle/ridden/janicart{
dir = 4
},
/turf/simulated/floor/plasteel{
@@ -30829,7 +30829,7 @@
/area/medical/paramedic)
"djT" = (
/obj/effect/decal/warning_stripes/northeastsouth,
-/obj/vehicle/ambulance{
+/obj/vehicle/ridden/ambulance{
dir = 4
},
/obj/machinery/light{
diff --git a/_maps/map_files/event/Station/towerstation.dmm b/_maps/map_files/event/Station/towerstation.dmm
index d554c5d7af0..8f788fa53cd 100644
--- a/_maps/map_files/event/Station/towerstation.dmm
+++ b/_maps/map_files/event/Station/towerstation.dmm
@@ -15172,7 +15172,7 @@
/turf/simulated/floor/plating,
/area/medical/sleeper)
"jMJ" = (
-/obj/vehicle/janicart{
+/obj/vehicle/ridden/janicart{
dir = 4
},
/obj/item/key/janitor,
@@ -37026,7 +37026,7 @@
/turf/simulated/floor/plasteel,
/area/atmos)
"xDI" = (
-/obj/vehicle/secway,
+/obj/vehicle/ridden/secway,
/obj/effect/turf_decal/delivery,
/obj/machinery/camera/autoname,
/obj/machinery/requests_console{
diff --git a/_maps/map_files/generic/CentComm.dmm b/_maps/map_files/generic/CentComm.dmm
index c7f2ad3e703..0975e33a88b 100644
--- a/_maps/map_files/generic/CentComm.dmm
+++ b/_maps/map_files/generic/CentComm.dmm
@@ -1990,7 +1990,7 @@
/turf/simulated/floor/wood,
/area/centcom/jail)
"aQh" = (
-/obj/vehicle/motorcycle{
+/obj/vehicle/ridden/motorcycle{
dir = 8
},
/obj/structure/sign/poster/contraband/random{
@@ -7604,7 +7604,7 @@
/turf/simulated/floor/grass,
/area/centcom/evac)
"dxs" = (
-/obj/vehicle/motorcycle{
+/obj/vehicle/ridden/motorcycle{
dir = 1
},
/turf/simulated/floor/plasteel{
@@ -20407,7 +20407,7 @@
/turf/simulated/floor/carpet/black,
/area/syndicate_mothership/control)
"jGg" = (
-/obj/vehicle/secway{
+/obj/vehicle/ridden/secway{
layer = 3.5
},
/obj/item/key/security,
@@ -21925,7 +21925,7 @@
/turf/simulated/floor/carpet,
/area/centcom/zone1)
"kqc" = (
-/obj/vehicle/janicart{
+/obj/vehicle/ridden/janicart{
dir = 4
},
/obj/item/key/janitor,
@@ -24980,7 +24980,7 @@
/turf/simulated/floor/indestructible/abductor,
/area/abductor_ship)
"lLd" = (
-/obj/vehicle/janicart{
+/obj/vehicle/ridden/janicart{
dir = 4
},
/obj/item/key/janitor,
@@ -46732,7 +46732,7 @@
/area/syndicate_mothership)
"vwk" = (
/obj/effect/decal/cleanable/dirt,
-/obj/vehicle/motorcycle{
+/obj/vehicle/ridden/motorcycle{
dir = 1
},
/turf/simulated/floor/plasteel{
@@ -47220,7 +47220,7 @@
/turf/simulated/floor/plasteel,
/area/shuttle/escape)
"vLc" = (
-/obj/vehicle/motorcycle{
+/obj/vehicle/ridden/motorcycle{
dir = 8
},
/obj/effect/decal/cleanable/dirt,
@@ -49334,7 +49334,7 @@
},
/area/syndicate_mothership/elite_squad)
"wGg" = (
-/obj/vehicle/motorcycle{
+/obj/vehicle/ridden/motorcycle{
dir = 8
},
/turf/simulated/floor/wood{
diff --git a/_maps/map_files/generic/syndicatebase.dmm b/_maps/map_files/generic/syndicatebase.dmm
index dccbb330eb1..7e894a2c950 100644
--- a/_maps/map_files/generic/syndicatebase.dmm
+++ b/_maps/map_files/generic/syndicatebase.dmm
@@ -22755,7 +22755,7 @@
dir = 1;
on = 1
},
-/obj/vehicle/motorcycle,
+/obj/vehicle/ridden/motorcycle,
/turf/simulated/floor/plasteel{
dir = 8;
icon_state = "vault";
@@ -22763,7 +22763,7 @@
},
/area/syndicate/unpowered/syndicate_space_base/main/central_w)
"tAt" = (
-/obj/vehicle/motorcycle,
+/obj/vehicle/ridden/motorcycle,
/turf/simulated/floor/plasteel{
dir = 8;
icon_state = "vault";
@@ -23238,7 +23238,7 @@
/turf/simulated/floor/plating,
/area/syndicate/unpowered/syndicate_space_base/maintenance/east)
"tOd" = (
-/obj/vehicle/snowmobile/blue,
+/obj/vehicle/ridden/snowmobile/blue,
/turf/simulated/floor/plating/ice,
/area/ruin/unpowered)
"tOv" = (
diff --git a/_maps/map_files/generic/z2_old.dmm b/_maps/map_files/generic/z2_old.dmm
index 025fd172c0c..c2da5bc46f2 100644
--- a/_maps/map_files/generic/z2_old.dmm
+++ b/_maps/map_files/generic/z2_old.dmm
@@ -1562,7 +1562,7 @@
},
/area/centcom/specops)
"bpL" = (
-/obj/vehicle/snowmobile/blue/key,
+/obj/vehicle/ridden/snowmobile/blue/key,
/turf/simulated/floor/indestructible/snow,
/area/syndicate_mothership)
"bpU" = (
@@ -22780,7 +22780,7 @@
},
/area/holodeck/source_meetinghall)
"qwX" = (
-/obj/vehicle/snowmobile/key,
+/obj/vehicle/ridden/snowmobile/key,
/turf/simulated/floor/indestructible/snow,
/area/syndicate_mothership)
"qxr" = (
diff --git a/_maps/map_files/nova/nova.dmm b/_maps/map_files/nova/nova.dmm
index 57d5f249494..72021f1f55e 100644
--- a/_maps/map_files/nova/nova.dmm
+++ b/_maps/map_files/nova/nova.dmm
@@ -15965,7 +15965,7 @@
/turf/simulated/floor/plating,
/area/maintenance/tourist)
"con" = (
-/obj/vehicle/janicart{
+/obj/vehicle/ridden/janicart{
dir = 4
},
/turf/simulated/floor/plasteel{
@@ -49491,7 +49491,7 @@
color = "red";
dir = 1
},
-/obj/vehicle/secway,
+/obj/vehicle/ridden/secway,
/turf/simulated/floor/mech_bay_recharge_floor,
/area/security/securearmory)
"hlN" = (
@@ -49583,7 +49583,7 @@
/area/security/securearmory)
"hmk" = (
/obj/effect/decal/warning_stripes/northeastsouth,
-/obj/vehicle/ambulance{
+/obj/vehicle/ridden/ambulance{
dir = 4
},
/obj/machinery/light{
@@ -158333,7 +158333,7 @@
/area/hallway/secondary/entry/commercial)
"xEE" = (
/obj/effect/decal/warning_stripes/south,
-/obj/vehicle/secway,
+/obj/vehicle/ridden/secway,
/turf/simulated/floor/mech_bay_recharge_floor,
/area/security/securearmory)
"xEN" = (
diff --git a/code/__DEFINES/dcs/signals.dm b/code/__DEFINES/dcs/signals.dm
index 4b22ccee110..8ba6bf69e81 100644
--- a/code/__DEFINES/dcs/signals.dm
+++ b/code/__DEFINES/dcs/signals.dm
@@ -312,10 +312,16 @@
#define COMPONENT_MOVABLE_IMPACT_NEVERMIND (1<<1) //return true if you destroyed whatever it was you're impacting and there won't be anything for hitby() to run on
///from base of mob/living/hitby(): (mob/living/target, hit_zone)
#define COMSIG_MOVABLE_IMPACT_ZONE "item_impact_zone"
+///from /atom/movable/proc/buckle_mob(): (mob/living/M, force, check_loc, buckle_mob_flags)
+#define COMSIG_MOVABLE_PREBUCKLE "prebuckle" // this is the last chance to interrupt and block a buckle before it finishes
+ #define COMPONENT_BLOCK_BUCKLE (1<<0)
///from base of atom/movable/buckle_mob(): (mob, force)
#define COMSIG_MOVABLE_BUCKLE "buckle"
///from base of atom/movable/unbuckle_mob(): (mob, force)
#define COMSIG_MOVABLE_UNBUCKLE "unbuckle"
+///from /obj/vehicle/proc/driver_move, caught by the riding component to check and execute the driver trying to drive the vehicle
+#define COMSIG_RIDDEN_DRIVER_MOVE "driver_move"
+ #define COMPONENT_DRIVER_BLOCK_MOVE (1<<0)
///from base of atom/movable/throw_at(): (list/args)
#define COMSIG_MOVABLE_PRE_THROW "movable_pre_throw"
#define COMPONENT_CANCEL_THROW (1<<0)
@@ -1176,6 +1182,12 @@
///From base of datum/controller/subsystem/Initialize
#define COMSIG_SUBSYSTEM_POST_INITIALIZE "subsystem_post_initialize"
+/// Called on a mob when they start riding a vehicle (obj/vehicle)
+#define COMSIG_VEHICLE_RIDDEN "vehicle-ridden"
+ /// Return this to signal that the mob should be removed from the vehicle
+ #define EJECT_FROM_VEHICLE (1<<0)
+
/// Source: /mob/living/simple_animal/borer, listening in datum/antagonist/borer
#define COMSIG_BORER_ENTERED_HOST "borer_on_enter" // when borer entered host
#define COMSIG_BORER_LEFT_HOST "borer_on_leave" // when borer left host
+
diff --git a/code/__DEFINES/dcs/signals_object.dm b/code/__DEFINES/dcs/signals_object.dm
index c4550fb9e2d..ce5845f447c 100644
--- a/code/__DEFINES/dcs/signals_object.dm
+++ b/code/__DEFINES/dcs/signals_object.dm
@@ -16,3 +16,7 @@
/// Return to prevent the default behavior (attack_selfing) from ocurring.
#define COMPONENT_ITEM_ACTION_SLOT_INVALID (1<<0)
+/// from base of /obj/item/slimepotion/speed/interact_with_atom(): (obj/target, /obj/src, mob/user)
+#define COMSIG_SPEED_POTION_APPLIED "speed_potion"
+ #define SPEED_POTION_STOP (1<<0)
+
diff --git a/code/__DEFINES/is_helpers.dm b/code/__DEFINES/is_helpers.dm
index a59894f8230..65a3903b03f 100644
--- a/code/__DEFINES/is_helpers.dm
+++ b/code/__DEFINES/is_helpers.dm
@@ -68,6 +68,8 @@
#define iseffect(A) (istype(A, /obj/effect))
+#define isvehicle(A) (istype(A, /obj/vehicle))
+
#define isprojectile(A) (istype(A, /obj/item/projectile))
#define isgun(A) (istype(A, /obj/item/gun))
@@ -115,6 +117,8 @@ GLOBAL_LIST_INIT(glass_sheet_types, typecacheof(list(
#define isopenspaceturf(A) (istype(A, /turf/simulated/openspace) || istype(A, /turf/space/openspace))
+#define is_space_or_openspace(A) (isopenspaceturf(A) || isspaceturf(A))
+
#define isfloorturf(A) istype(A, /turf/simulated/floor)
#define iswallturf(A) istype(A, /turf/simulated/wall)
diff --git a/code/__DEFINES/layers.dm b/code/__DEFINES/layers.dm
index c546d266a3c..56b86f0524b 100644
--- a/code/__DEFINES/layers.dm
+++ b/code/__DEFINES/layers.dm
@@ -192,7 +192,10 @@
#define BELOW_MOB_LAYER 3.7
#define LYING_MOB_LAYER 3.8
#define BEHIND_MOB_LAYER 3.9
+#define VEHICLE_LAYER 3.91
+#define MOB_BELOW_PIGGYBACK_LAYER 3.94
//#define MOB_LAYER 4 //For easy recordkeeping; this is a byond define
+#define MOB_ABOVE_PIGGYBACK_LAYER 4.06
#define ABOVE_MOB_LAYER 4.1
#define WALL_OBJ_LAYER 4.25
#define EDGED_TURF_LAYER 4.3
diff --git a/code/__DEFINES/mobs.dm b/code/__DEFINES/mobs.dm
index 6f1c3675837..f56d88f9f74 100644
--- a/code/__DEFINES/mobs.dm
+++ b/code/__DEFINES/mobs.dm
@@ -409,6 +409,7 @@
#define PULL_LYING_MOB_SLOWDOWN 1.3
#define PUSH_STANDING_MOB_SLOWDOWN 1.3
+#define HUMAN_CARRY_SLOWDOWN 0.6
#define ACTIVE_HAND_RIGHT 0
#define ACTIVE_HAND_LEFT 1
diff --git a/code/__DEFINES/traits/declarations.dm b/code/__DEFINES/traits/declarations.dm
index 4fad591bac3..41785b8da8e 100644
--- a/code/__DEFINES/traits/declarations.dm
+++ b/code/__DEFINES/traits/declarations.dm
@@ -59,6 +59,15 @@ Remember to update _globalvars/traits.dm if you're adding/removing/renaming trai
#define TRAIT_SHOCKIMMUNE "shock_immunity"
/// Are we immune to specifically tesla / SM shocks?
#define TRAIT_TESLA_SHOCKIMMUNE "tesla_shock_immunity"
+
+/// We place people into a fireman carry quicker than standard
+#define TRAIT_QUICK_CARRY "quick-carry"
+/// We place people into a fireman carry especially quickly compared to quick_carry
+#define TRAIT_QUICKER_CARRY "quicker-carry"
+/// Prevents mob from riding mobs when buckled onto something
+#define TRAIT_CANT_RIDE "cant_ride"
+#define TRAIT_CHUNKYFINGERS "chunkyfingers" //means that you can't use weapons with normal trigger guards.
+
/// Means that you can't use weapons with normal trigger guards.
#define TRAIT_NO_GUNS "no_guns"
#define TRAIT_FORCE_DOORS "force_doors"
@@ -67,6 +76,7 @@ Remember to update _globalvars/traits.dm if you're adding/removing/renaming trai
#define TRAIT_IGNOREDAMAGESLOWDOWN "ignoredamageslowdown"
#define TRAIT_STRONG_GRABBER "strong_grabber"
#define TRAIT_PUSHIMMUNE "push_immunity"
+#define TRAIT_AI_PAUSED "TRAIT_AI_PAUSED"
#define TRAIT_FLATTENED "flattened"
/// Not a genetic obesity but just a mob who overate
diff --git a/code/__DEFINES/traits/sources.dm b/code/__DEFINES/traits/sources.dm
index 817744cbe56..92565ab1354 100644
--- a/code/__DEFINES/traits/sources.dm
+++ b/code/__DEFINES/traits/sources.dm
@@ -150,6 +150,8 @@
#define SCRYING_ORB_TRAIT "scrying_orb"
#define EVIL_FAX_TRAIT "evil_fax"
#define CORGI_HARDSUIT_TRAIT "corgi_hardsuit"
+/// inherited from riding vehicles
+#define VEHICLE_TRAIT "vehicle"
// blob trait sourses
#define BLOB_INFECTED_TRAIT "blob_infected"
diff --git a/code/__DEFINES/vehicles.dm b/code/__DEFINES/vehicles.dm
new file mode 100644
index 00000000000..d210a07dde4
--- /dev/null
+++ b/code/__DEFINES/vehicles.dm
@@ -0,0 +1,47 @@
+//Vehicle control flags. control flags describe access to actions in a vehicle.
+
+///controls the vehicles movement
+#define VEHICLE_CONTROL_DRIVE (1<<0)
+///Can't leave vehicle voluntarily, has to resist.
+#define VEHICLE_CONTROL_KIDNAPPED (1<<1)
+///melee attacks/shoves a vehicle may have
+#define VEHICLE_CONTROL_MELEE (1<<2)
+///using equipment/weapons on the vehicle
+#define VEHICLE_CONTROL_EQUIPMENT (1<<3)
+///changing around settings and the like.
+#define VEHICLE_CONTROL_SETTINGS (1<<4)
+
+///ez define for giving a single pilot mech all the flags it needs.
+#define FULL_MECHA_CONTROL ALL
+
+//Ridden vehicle flags
+
+/// Does our vehicle require arms to operate? Also used for piggybacking on humans to reserve arms on the rider
+#define RIDER_NEEDS_ARMS (1<<0)
+// As above but only used for riding cyborgs, and only reserves 1 arm instead of 2
+#define RIDER_NEEDS_ARM (1<<1)
+/// Do we need legs to ride this (checks against TRAIT_FLOORED)
+#define RIDER_NEEDS_LEGS (1<<2)
+/// If the rider is disabled or loses their needed limbs, do they fall off?
+#define UNBUCKLE_DISABLED_RIDER (1<<3)
+// For fireman carries, the carrying human needs an arm
+#define CARRIER_NEEDS_ARM (1<<4)
+// This rider must be our friend
+#define JUST_FRIEND_RIDERS (1<<5)
+
+//car_traits flags
+///Will this car kidnap people by ramming into them?
+#define CAN_KIDNAP (1<<0)
+
+#define CLOWN_CANNON_INACTIVE 0
+#define CLOWN_CANNON_BUSY 1
+#define CLOWN_CANNON_READY 2
+
+//Vim defines
+///cooldown between uses of the sound maker
+#define VIM_SOUND_COOLDOWN (1 SECONDS)
+///how much vim heals per weld
+#define VIM_HEAL_AMOUNT 20
+
+/// The vehicle being ridden requires pixel offsets for all directions
+#define RIDING_OFFSET_ALL "ALL"
diff --git a/code/_globalvars/traits.dm b/code/_globalvars/traits.dm
index 4fd7d215a46..3c973dff5fb 100644
--- a/code/_globalvars/traits.dm
+++ b/code/_globalvars/traits.dm
@@ -7,6 +7,7 @@
*/
GLOBAL_LIST_INIT(traits_by_type, list(
/atom = list(
+ "TRAIT_AI_PAUSED" = TRAIT_AI_PAUSED,
"TRAIT_BEING_SHOCKED" = TRAIT_BEING_SHOCKED,
"TRAIT_BLOCK_RADIATION" = TRAIT_BLOCK_RADIATION,
"TRAIT_CMAGGED" = TRAIT_CMAGGED,
@@ -38,7 +39,8 @@ GLOBAL_LIST_INIT(traits_by_type, list(
"TRAIT_BLOODCRAWL" = TRAIT_BLOODCRAWL,
"TRAIT_BLOODCRAWL_EAT" = TRAIT_BLOODCRAWL_EAT,
"TRAIT_CAN_STRIP" = TRAIT_CAN_STRIP,
- "TRAIT_NO_GUNS" = TRAIT_NO_GUNS,
+ "TRAIT_CANT_RIDE" = TRAIT_CANT_RIDE,
+ "TRAIT_CHUNKYFINGERS" = TRAIT_CHUNKYFINGERS,
"TRAIT_COLORBLIND" = TRAIT_COLORBLIND,
"TRAIT_COMIC" = TRAIT_COMIC,
"TRAIT_CLUMSY" = TRAIT_CLUMSY,
@@ -95,6 +97,7 @@ GLOBAL_LIST_INIT(traits_by_type, list(
"TRAIT_NO_FINGERPRINTS" = TRAIT_NO_FINGERPRINTS,
"TRAIT_NO_GERMS" = TRAIT_NO_GERMS,
"TRAIT_NO_GLIDE" = TRAIT_NO_GLIDE,
+ "TRAIT_NO_GUNS" = TRAIT_NO_GUNS,
"TRAIT_NO_HUNGER" = TRAIT_NO_HUNGER,
"TRAIT_NO_INTORGANS" = TRAIT_NO_INTORGANS,
"TRAIT_NO_PAIN" = TRAIT_NO_PAIN,
@@ -115,6 +118,8 @@ GLOBAL_LIST_INIT(traits_by_type, list(
"TRAIT_PULL_BLOCKED" = TRAIT_PULL_BLOCKED,
"TRAIT_PUSHIMMUNE" = TRAIT_PUSHIMMUNE,
"TRAIT_PSY_RESIST" = TRAIT_PSY_RESIST,
+ "TRAIT_QUICKER_CARRY" = TRAIT_QUICKER_CARRY,
+ "TRAIT_QUICK_CARRY" = TRAIT_QUICK_CARRY,
"TRAIT_RADIMMUNE" = TRAIT_RADIMMUNE,
"TRAIT_RESIST_COLD" = TRAIT_RESIST_COLD,
"TRAIT_RESIST_HEAT" = TRAIT_RESIST_HEAT,
diff --git a/code/datums/components/_component.dm b/code/datums/components/_component.dm
index a957e9ade7a..6d1810c47ae 100644
--- a/code/datums/components/_component.dm
+++ b/code/datums/components/_component.dm
@@ -206,6 +206,11 @@
else // Many other things have registered here
looked_up += src
+/// Registers multiple signals to the same proc.
+/datum/proc/RegisterSignals(datum/target, list/signal_types, proctype, override = FALSE)
+ for (var/signal_type in signal_types)
+ RegisterSignal(target, signal_type, proctype, override)
+
/**
* Stop listening to a given signal from target
*
diff --git a/code/datums/components/riding/riding.dm b/code/datums/components/riding/riding.dm
new file mode 100644
index 00000000000..aaf73c70d2a
--- /dev/null
+++ b/code/datums/components/riding/riding.dm
@@ -0,0 +1,337 @@
+/**
+ * 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_PASSARGS
+
+ 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
+ /// additional traits to add to anyone riding this vehicle
+ var/list/rider_traits = list(TRAIT_NO_FLOATING_ANIM)
+ /// 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.updating_glide_size = FALSE //i've checked everything in tg code, this stuff is never used anywhere in their code
+ ride_check_flags |= buckle_mob_flags
+
+ if(potion_boost)
+ vehicle_move_delay = round(CONFIG_GET(number/movedelay/run_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))
+ RegisterSignal(parent, COMSIG_BUCKLED_CAN_Z_MOVE, PROC_REF(riding_can_z_move))
+ RegisterSignals(parent, GLOB.movement_type_addtrait_signals, PROC_REF(on_movement_type_trait_gain))
+ RegisterSignals(parent, GLOB.movement_type_removetrait_signals, PROC_REF(on_movement_type_trait_loss))
+ //RegisterSignal(parent, COMSIG_SUPERMATTER_CONSUMED, PROC_REF(on_entered_supermatter))
+ 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
+
+ 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)
+ //rider.updating_glide_size = TRUE
+ UnregisterSignal(rider, COMSIG_LIVING_TRY_PULL)
+ for(var/trait in GLOB.movement_type_trait_to_flag)
+ if(HAS_TRAIT(parent, trait))
+ REMOVE_TRAIT(rider, trait, src)
+ rider.remove_traits(rider_traits, src)
+ 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
+
+ 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))
+
+ for(var/trait in GLOB.movement_type_trait_to_flag)
+ if(HAS_TRAIT(parent, trait))
+ ADD_TRAIT(rider, trait, src)
+ rider.add_traits(rider_traits, src)
+ post_vehicle_mob_buckle(movable_parent, rider)
+
+/// 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
+ if(target == parent)
+ var/mob/living/ridden = parent
+ ridden.balloon_alert(rider_pulling, "нельзя тащить!") //need better option - "not while riding it!""
+ return COMSIG_LIVING_CANCEL_PULL
+
+///any behavior we want to happen after buckling the mob
+/datum/component/riding/proc/post_vehicle_mob_buckle(atom/movable/ridden, atom/movable/rider)
+ return TRUE
+
+/// 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
+
+ var/atom/movable/movable_parent = parent
+ if(isnull(dir))
+ dir = movable_parent.dir
+ for(var/m in movable_parent.buckled_mobs)
+ var/mob/buckled_mob = m
+ 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
+
+ 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
+
+/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/m in AM.buckled_mobs)
+ passindex++
+ var/mob/living/buckled_mob = m
+ 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(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
+
+/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(isvehicle(parent))
+ var/obj/vehicle/vehicle_parent = parent
+ return istype(vehicle_parent.inserted_key, keytype)
+ var/mob/living/carbon/human/H = user
+
+ return H.is_type_in_hands(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 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
+
+ return
+
+/// 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
+ 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 || AM.has_gravity()
+
+/// 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
+
+/// 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
+
+/// Called when our vehicle gains a movement trait, so we can apply it to the riders
+/datum/component/riding/proc/on_movement_type_trait_gain(atom/movable/source, trait)
+ SIGNAL_HANDLER
+ var/atom/movable/movable_parent = parent
+ for(var/mob/rider in movable_parent.buckled_mobs)
+ ADD_TRAIT(rider, trait, src)
+
+/// Called when our vehicle loses a movement trait, so we can remove it from the riders
+/datum/component/riding/proc/on_movement_type_trait_loss(atom/movable/source, trait)
+ SIGNAL_HANDLER
+ var/atom/movable/movable_parent = parent
+ for(var/mob/rider in movable_parent.buckled_mobs)
+ REMOVE_TRAIT(rider, trait, src)
+
+/datum/component/riding/proc/force_unbuckle(atom/movable/source, mob/living/living_hitter)
+ SIGNAL_HANDLER
+
+ if((living_hitter in source.buckled_mobs))
+ return
+ return COMPONENT_CANCEL_ATTACK_CHAIN
+
+/// When we touch a crystal, kill everything inside us. Not implemented yet
+/*
+/datum/component/riding/proc/on_entered_supermatter(atom/movable/ridden, atom/movable/supermatter)
+ SIGNAL_HANDLER
+ for (var/mob/passenger as anything in ridden.buckled_mobs)
+ passenger.Bump(supermatter)
+*/
diff --git a/code/datums/components/riding/riding_mob.dm b/code/datums/components/riding/riding_mob.dm
new file mode 100644
index 00000000000..84cbebce728
--- /dev/null
+++ b/code/datums/components/riding/riding_mob.dm
@@ -0,0 +1,282 @@
+// For any mob that can be ridden
+
+/datum/component/riding/creature
+ /// If TRUE, this creature's movements can be controlled by the rider while mounted (as opposed to riding cyborgs and humans, which is passive)
+ var/can_be_driven = TRUE
+ /// If TRUE, this creature's abilities can be triggered by the rider while mounted
+ var/can_use_abilities = FALSE
+ /// shall we require riders to go through the riding minigame if they arent in our friends list
+ //var/require_minigame = FALSE //not implemented yet
+ /// list of blacklisted abilities that cant be shared
+ var/list/blacklist_abilities = list()
+
+/datum/component/riding/creature/Initialize(mob/living/riding_mob, force = FALSE, ride_check_flags = NONE, potion_boost = FALSE)
+ if(!isliving(parent))
+ return COMPONENT_INCOMPATIBLE
+
+ . = ..()
+
+ var/mob/living/living_parent = parent
+ living_parent.stop_pulling() // was only used on humans previously, may change some other behavior
+ log_riding(living_parent, riding_mob)
+ riding_mob.set_glide_size(living_parent.glide_size)
+ handle_vehicle_offsets(living_parent.dir)
+
+ if(can_use_abilities)
+ setup_abilities(riding_mob)
+
+ if(isanimal(parent))
+ var/mob/living/simple_animal/simple_parent = parent
+ simple_parent.stop_automated_movement = TRUE
+
+/datum/component/riding/creature/Destroy(force)
+ unequip_buckle_inhands(parent)
+ if(isanimal(parent))
+ var/mob/living/simple_animal/simple_parent = parent
+ simple_parent.stop_automated_movement = FALSE
+ REMOVE_TRAIT(parent, TRAIT_AI_PAUSED, src)
+ return ..()
+
+/datum/component/riding/creature/RegisterWithParent()
+ . = ..()
+ RegisterSignal(parent, COMSIG_MOB_EMOTE, PROC_REF(check_emote))
+ if(can_be_driven)
+ RegisterSignal(parent, COMSIG_RIDDEN_DRIVER_MOVE, PROC_REF(driver_move)) // this isn't needed on riding humans or cyborgs since the rider can't control them
+
+/// Creatures need to be logged when being mounted
+/datum/component/riding/creature/proc/log_riding(mob/living/living_parent, mob/living/rider)
+ if(!istype(living_parent) || !istype(rider))
+ return
+
+
+ add_attack_logs(living_parent, rider, "is now being ridden by [rider].")
+ add_attack_logs(rider, living_parent, "started riding [living_parent].")
+
+
+// this applies to humans and most creatures, but is replaced again for cyborgs
+/datum/component/riding/creature/ride_check(mob/living/rider, consequences = TRUE)
+ . = TRUE
+ var/mob/living/living_parent = parent
+ if(living_parent.body_position != STANDING_UP) // if we move while on the ground, the rider falls off
+ . = FALSE
+ // for piggybacks and (redundant?) borg riding, check if the rider is stunned/restrained
+ else if((ride_check_flags & RIDER_NEEDS_ARMS) && (HAS_TRAIT(rider, TRAIT_RESTRAINED) || rider.incapacitated(INC_IGNORE_RESTRAINED|INC_IGNORE_GRABBED)))
+ . = FALSE
+ // for fireman carries, check if the ridden is stunned/restrained
+ else if((ride_check_flags & CARRIER_NEEDS_ARM) && (HAS_TRAIT(living_parent, TRAIT_RESTRAINED) || living_parent.incapacitated(INC_IGNORE_RESTRAINED|INC_IGNORE_GRABBED)))
+ . = FALSE
+
+ else if((ride_check_flags & JUST_FRIEND_RIDERS) && !(living_parent.faction.Find(rider)))
+ . = FALSE
+
+ if(. || !consequences)
+ return
+
+ rider.visible_message(span_warning("[rider] falls off of [living_parent]!"), \
+ span_warning("You fall off of [living_parent]!"))
+ rider.Weaken(1 SECONDS)
+ rider.Knockdown(4 SECONDS)
+ living_parent.unbuckle_mob(rider)
+
+/datum/component/riding/creature/vehicle_mob_buckle(mob/living/ridden, mob/living/rider, force = FALSE)
+ // Ensure that the /mob/post_buckle_mob(mob/living/M) does not mess us up with layers
+ // If we do not do this override we'll be stuck with the above proc (+ 0.1)-ing our rider's layer incorrectly
+ rider.layer = initial(rider.layer)
+ if(can_be_driven)
+ //let the player take over if they should be controlling movement
+ ADD_TRAIT(ridden, TRAIT_AI_PAUSED, src)
+ return ..()
+
+/datum/component/riding/creature/vehicle_mob_unbuckle(mob/living/formerly_ridden, mob/living/former_rider, force = FALSE)
+ /*
+ if(istype(formerly_ridden) && istype(former_rider))
+ formerly_ridden.log_message("is no longer being ridden by [former_rider].", LOG_GAME, color="pink")
+ former_rider.log_message("is no longer riding [formerly_ridden].", LOG_GAME, color="pink")
+ */
+ remove_abilities(former_rider)
+ if(!formerly_ridden.buckled_mobs.len)
+ REMOVE_TRAIT(formerly_ridden, TRAIT_AI_PAUSED, src)
+ // We gotta reset those layers at some point, don't we?
+ former_rider.layer = MOB_LAYER
+ formerly_ridden.layer = MOB_LAYER
+ return ..()
+
+/datum/component/riding/creature/driver_move(atom/movable/movable_parent, mob/living/user, direction)
+ if(!COOLDOWN_FINISHED(src, vehicle_move_cooldown) || !Process_Spacemove())
+ return COMPONENT_DRIVER_BLOCK_MOVE
+ if(!keycheck(user))
+ if(ispath(keytype, /obj/item))
+ var/obj/item/key = keytype
+ to_chat(user, span_warning("You need a [initial(key.name)] to ride [movable_parent]!"))
+ return COMPONENT_DRIVER_BLOCK_MOVE
+ var/mob/living/living_parent = parent
+ var/turf/next = get_step(living_parent, direction)
+ step(living_parent, direction)
+ last_move_diagonal = ((direction & (direction - 1)) && (living_parent.loc == next))
+ var/modified_move_cooldown = vehicle_move_cooldown
+ var/modified_move_delay = vehicle_move_delay
+ //weird sanity code here, we don't got it, at least for now.
+ COOLDOWN_START(src, vehicle_move_cooldown = modified_move_cooldown, (last_move_diagonal ? 2 : 1) * modified_move_delay)
+ return ..()
+
+/// Yeets the rider off, used for animals and cyborgs, redefined for humans who shove their piggyback rider off
+/datum/component/riding/creature/proc/force_dismount(mob/living/rider, throw_range = 8, throw_speed = 3)
+ var/atom/movable/movable_parent = parent
+ movable_parent.unbuckle_mob(rider)
+ rider.Knockdown(3 SECONDS)
+ if(throw_range == 0)
+ return
+ if(!isrobot(movable_parent) && !isanimal(movable_parent))
+ return
+ var/turf/target = get_edge_target_turf(movable_parent, movable_parent.dir)
+ rider.visible_message(span_warning("[rider] is thrown clear of [movable_parent]!"), \
+ span_warning("You're thrown clear of [movable_parent]!"))
+ rider.throw_at(target, throw_range, throw_speed, movable_parent)
+
+/// If we're a cyborg or animal and we spin, we yeet whoever's on us off us
+/datum/component/riding/creature/proc/check_emote(mob/living/user, datum/emote/emote)
+ SIGNAL_HANDLER
+ if((!isrobot(user) && !isanimal(user)) || !istype(emote, /datum/emote/spin))
+ return
+
+ for(var/mob/yeet_mob in user.buckled_mobs)
+ force_dismount(yeet_mob)
+
+/// If the ridden creature has abilities, and some var yet to be made is set to TRUE, the rider will be able to control those abilities
+/datum/component/riding/creature/proc/setup_abilities(mob/living/rider)
+ if(!isliving(parent))
+ return
+
+ var/mob/living/ridden_creature = parent
+
+ for(var/datum/action/action as anything in ridden_creature.actions)
+ if(is_type_in_list(action, blacklist_abilities))
+ continue
+ action.Grant(rider)
+
+/// Takes away the riding parent's abilities from the rider
+/datum/component/riding/creature/proc/remove_abilities(mob/living/rider)
+ if(!isliving(parent))
+ return
+
+ var/mob/living/ridden_creature = parent
+
+ for(var/datum/action/action as anything in ridden_creature.actions)
+ action.Remove(rider)
+
+/datum/component/riding/creature/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
+ return COMPONENT_RIDDEN_ALLOW_Z_MOVE
+
+///////Yes, I said humans. No, this won't end well...//////////
+/datum/component/riding/creature/human
+ can_be_driven = FALSE
+
+/datum/component/riding/creature/human/Initialize(mob/living/riding_mob, force = FALSE, ride_check_flags = NONE, potion_boost = FALSE)
+ . = ..()
+ var/mob/living/carbon/human/human_parent = parent
+ human_parent.add_movespeed_modifier(/datum/movespeed_modifier/human_carry)
+
+ if(ride_check_flags & RIDER_NEEDS_ARMS) // piggyback
+ human_parent.buckle_lying = 0
+ // the riding mob is made nondense so they don't bump into any dense atoms the carrier is pulling,
+ // since pulled movables are moved before buckled movables
+ ADD_TRAIT(riding_mob, TRAIT_UNDENSE, VEHICLE_TRAIT)
+ else if(ride_check_flags & CARRIER_NEEDS_ARM) // fireman
+ human_parent.buckle_lying = 90
+
+/*
+/datum/component/riding/creature/post_vehicle_mob_buckle(mob/living/ridden, mob/living/rider)
+ if(!require_minigame || ridden.faction.Find(REF(rider)))
+ return
+ ridden.Shake(duration = 2 SECONDS)
+ ridden.balloon_alert(rider, "вас пытаются сбросить!")
+ var/datum/riding_minigame/game = new(ridden, rider, FALSE)
+ game.commence_minigame()
+*/
+/datum/component/riding/creature/human/RegisterWithParent()
+ . = ..()
+ RegisterSignal(parent, COMSIG_MOB_ATTACK_HAND, PROC_REF(on_host_unarmed_melee))
+ RegisterSignal(parent, COMSIG_LIVING_SET_BODY_POSITION, PROC_REF(check_carrier_fall_over))
+
+/datum/component/riding/creature/human/log_riding(mob/living/living_parent, mob/living/rider)
+ if(!istype(living_parent) || !istype(rider))
+ return
+
+ /*
+ if(ride_check_flags & RIDER_NEEDS_ARMS) // piggyback
+ living_parent.log_message("started giving [rider] a piggyback ride.", LOG_GAME, color="pink")
+ rider.log_message("started piggyback riding [living_parent].", LOG_GAME, color="pink")
+ else if(ride_check_flags & CARRIER_NEEDS_ARM) // fireman
+ living_parent.log_message("started fireman carrying [rider].", LOG_GAME, color="pink")
+ rider.log_message("was fireman carried by [living_parent].", LOG_GAME, color="pink")
+ */
+
+/datum/component/riding/creature/human/vehicle_mob_unbuckle(datum/source, mob/living/former_rider, force = FALSE)
+ unequip_buckle_inhands(parent)
+ var/mob/living/carbon/human/H = parent
+ H.remove_movespeed_modifier(/datum/movespeed_modifier/human_carry)
+ REMOVE_TRAIT(former_rider, TRAIT_UNDENSE, VEHICLE_TRAIT)
+ return ..()
+
+/// If the carrier shoves the person they're carrying, force the carried mob off
+/datum/component/riding/creature/human/proc/on_host_unarmed_melee(mob/living/source, atom/target, proximity, modifiers)
+ SIGNAL_HANDLER
+
+ if(LAZYACCESS(modifiers, RIGHT_CLICK) && (target in source.buckled_mobs))
+ force_dismount(target)
+ return COMPONENT_CANCEL_ATTACK_CHAIN
+ return NONE
+
+/// If the carrier gets knocked over, force the rider(s) off and see if someone got hurt
+/datum/component/riding/creature/human/proc/check_carrier_fall_over(mob/living/carbon/human/human_parent)
+ SIGNAL_HANDLER
+
+ for(var/i in human_parent.buckled_mobs)
+ var/mob/living/rider = i
+ human_parent.unbuckle_mob(rider)
+ rider.Weaken(1 SECONDS)
+ rider.Knockdown(4 SECONDS)
+ human_parent.visible_message(span_danger("[rider] topples off of [human_parent] as they both fall to the ground!"), \
+ span_warning("You fall to the ground, bringing [rider] with you!"), span_hear("You hear two consecutive thuds."))
+ to_chat(rider, span_danger("[human_parent] falls to the ground, bringing you with [human_parent.p_them()]!"))
+
+/datum/component/riding/creature/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/creature/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/creature/human/force_dismount(mob/living/dismounted_rider)
+ var/atom/movable/AM = parent
+ AM.unbuckle_mob(dismounted_rider)
+ dismounted_rider.Weaken(1 SECONDS)
+ dismounted_rider.Knockdown(4 SECONDS)
+ dismounted_rider.visible_message(span_warning("[AM] pushes [dismounted_rider] off of [AM.p_them()]!"), \
+ span_warning("[AM] pushes you off of [AM.p_them()]!"))
diff --git a/code/datums/components/riding/riding_vehicle.dm b/code/datums/components/riding/riding_vehicle.dm
new file mode 100644
index 00000000000..228c997c50c
--- /dev/null
+++ b/code/datums/components/riding/riding_vehicle.dm
@@ -0,0 +1,416 @@
+// For any /obj/vehicle'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(!isvehicle(parent))
+ return COMPONENT_INCOMPATIBLE
+ return ..()
+
+/datum/component/riding/vehicle/RegisterWithParent()
+ . = ..()
+ RegisterSignal(parent, COMSIG_RIDDEN_DRIVER_MOVE, PROC_REF(driver_move))
+
+/datum/component/riding/vehicle/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(!keycheck(rider))
+ if(z_move_flags & ZMOVE_FEEDBACK)
+ to_chat(rider, "[movable_parent] has no key inserted!")
+ return COMPONENT_RIDDEN_STOP_Z_MOVE
+ if(HAS_TRAIT(rider, TRAIT_INCAPACITATED))
+ if(z_move_flags & ZMOVE_FEEDBACK)
+ to_chat(rider, "You cannot operate [movable_parent] right now!")
+ return COMPONENT_RIDDEN_STOP_Z_MOVE
+ if(ride_check_flags & RIDER_NEEDS_LEGS && HAS_TRAIT(rider, TRAIT_FLOORED))
+ if(z_move_flags & ZMOVE_FEEDBACK)
+ to_chat(rider, "You can't seem to manage that while unable to stand up enough to move [movable_parent]...")
+ return COMPONENT_RIDDEN_STOP_Z_MOVE
+ if(ride_check_flags & RIDER_NEEDS_ARMS && HAS_TRAIT(rider, TRAIT_HANDS_BLOCKED))
+ if(z_move_flags & ZMOVE_FEEDBACK)
+ to_chat(rider, "You can't seem to hold onto [movable_parent] to move it...")
+ return COMPONENT_RIDDEN_STOP_Z_MOVE
+
+ return COMPONENT_RIDDEN_ALLOW_Z_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/vehicle/vehicle_parent = parent
+
+ if(!keycheck(user))
+ if(COOLDOWN_FINISHED(src, message_cooldown))
+ to_chat(user, span_warning("[vehicle_parent] has no key inserted!"))
+ COOLDOWN_START(src, message_cooldown, 5 SECONDS)
+ return COMPONENT_DRIVER_BLOCK_MOVE
+
+ if(!iscarbon(user))
+ if(COOLDOWN_FINISHED(src, message_cooldown))
+ to_chat(user, span_warning("you can't drive, you are not human!"))
+ COOLDOWN_START(src, message_cooldown, 5 SECONDS)
+ return COMPONENT_DRIVER_BLOCK_MOVE
+
+
+ if(HAS_TRAIT(user, TRAIT_INCAPACITATED))
+ if(ride_check_flags & UNBUCKLE_DISABLED_RIDER)
+ vehicle_parent.unbuckle_mob(user, TRUE)
+ user.visible_message(span_danger("[user] falls off \the [vehicle_parent]."),\
+ span_danger("You slip off \the [vehicle_parent] as your body slumps!"))
+ user.Stun(3 SECONDS)
+
+ if(COOLDOWN_FINISHED(src, message_cooldown))
+ to_chat(user, span_warning("You cannot operate \the [vehicle_parent] right now!"))
+ 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)
+ vehicle_parent.unbuckle_mob(user, TRUE)
+ user.visible_message(span_danger("[user] falls off \the [vehicle_parent]."),\
+ span_danger("You fall off \the [vehicle_parent] while trying to operate it while unable to stand!"))
+ user.Stun(3 SECONDS)
+
+ if(COOLDOWN_FINISHED(src, message_cooldown))
+ to_chat(user, span_warning("You can't seem to manage that while unable to stand up enough to move \the [vehicle_parent]..."))
+ 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)
+ vehicle_parent.unbuckle_mob(user, TRUE)
+ user.visible_message(span_danger("[user] falls off \the [vehicle_parent]."),\
+ span_danger("You fall off \the [vehicle_parent] while trying to operate it without being able to hold on!"))
+ user.Stun(3 SECONDS)
+
+ if(COOLDOWN_FINISHED(src, message_cooldown))
+ to_chat(user, span_warning("You can't seem to hold onto \the [vehicle_parent] to move it..."))
+ 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_warning("\The [movable_parent] can not go onto [next]!"))
+ 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/atv
+ keytype = /obj/item/key/atv
+ ride_check_flags = RIDER_NEEDS_LEGS | RIDER_NEEDS_ARMS | UNBUCKLE_DISABLED_RIDER
+ vehicle_move_delay = 1.5
+
+/datum/component/riding/vehicle/atv/handle_specials()
+ . = ..()
+ set_riding_offsets(RIDING_OFFSET_ALL, list(TEXT_NORTH = list(0, 4), TEXT_SOUTH = list(0, 4), TEXT_EAST = list(0, 4), TEXT_WEST = list(0, 4)))
+ 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/ambulance
+ keytype = /obj/item/key/ambulance
+ ride_check_flags = RIDER_NEEDS_LEGS | RIDER_NEEDS_ARMS | UNBUCKLE_DISABLED_RIDER
+ vehicle_move_delay = 2
+
+/datum/component/riding/vehicle/ambulance/handle_specials()
+ . = ..()
+ set_riding_offsets(RIDING_OFFSET_ALL, list(TEXT_NORTH = list(0, 4), TEXT_SOUTH = list(0, 7), TEXT_EAST = list(-13, 7), TEXT_WEST = list(13, 7)))
+
+/datum/component/riding/vehicle/janicart
+ ride_check_flags = RIDER_NEEDS_LEGS | RIDER_NEEDS_ARMS | UNBUCKLE_DISABLED_RIDER
+ keytype = /obj/item/key/janitor
+
+/datum/component/riding/vehicle/janicart/handle_specials()
+ . = ..()
+ set_riding_offsets(RIDING_OFFSET_ALL, list(TEXT_NORTH = list(0, 4), TEXT_SOUTH = list(0, 7), TEXT_EAST = list(-12, 7), TEXT_WEST = list(12, 7)))
+
+/datum/component/riding/vehicle/motorcycle
+ ride_check_flags = RIDER_NEEDS_LEGS | RIDER_NEEDS_ARMS | UNBUCKLE_DISABLED_RIDER
+
+/datum/component/riding/vehicle/motorcycle/handle_specials()
+ . = ..()
+ set_riding_offsets(RIDING_OFFSET_ALL, list(TEXT_NORTH = list(0, 4), TEXT_SOUTH = list(0, 4), TEXT_EAST = list(0, 4), TEXT_WEST = list(0, 4)))
+
+/datum/component/riding/vehicle/secway
+ keytype = /obj/item/key/security
+ vehicle_move_delay = 1.75
+ ride_check_flags = RIDER_NEEDS_LEGS | RIDER_NEEDS_ARMS | UNBUCKLE_DISABLED_RIDER
+
+/datum/component/riding/vehicle/secway/handle_specials()
+ . = ..()
+ set_riding_offsets(RIDING_OFFSET_ALL, list(TEXT_NORTH = list(0, 4), TEXT_SOUTH = list(0, 4), TEXT_EAST = list(0, 4), TEXT_WEST = list( 0, 4)))
+ set_vehicle_dir_layer(SOUTH, ABOVE_MOB_LAYER)
+
+/datum/component/riding/vehicle/snowmobile
+ keytype = /obj/item/key/snowmobile
+ vehicle_move_delay = 1.75
+ ride_check_flags = RIDER_NEEDS_LEGS | RIDER_NEEDS_ARMS | UNBUCKLE_DISABLED_RIDER
+
+/datum/component/riding/vehicle/speedbike
+ vehicle_move_delay = 0.75
+ override_allow_spacemove = TRUE
+ ride_check_flags = RIDER_NEEDS_LEGS | RIDER_NEEDS_ARMS | UNBUCKLE_DISABLED_RIDER
+
+/datum/component/riding/vehicle/speedbike/handle_specials()
+ . = ..()
+ set_riding_offsets(RIDING_OFFSET_ALL, list(TEXT_NORTH = list(0, -8), TEXT_SOUTH = list(0, 4), TEXT_EAST = list(-10, 5), TEXT_WEST = list( 10, 5)))
+ set_vehicle_dir_offsets(NORTH, -16, -16)
+ set_vehicle_dir_offsets(SOUTH, -16, -16)
+ set_vehicle_dir_offsets(EAST, -18, 0)
+ set_vehicle_dir_offsets(WEST, -18, 0)
+
+/datum/component/riding/vehicle/lavaboat
+ ride_check_flags = NONE // not sure
+ keytype = /obj/item/oar
+ var/allowed_turf = /turf/simulated/floor/lava
+
+/datum/component/riding/vehicle/lavaboat/handle_specials()
+ . = ..()
+ allowed_turf_typecache = typecacheof(allowed_turf)
+
+/datum/component/riding/vehicle/lavaboat/dragonboat
+ vehicle_move_delay = 1
+ keytype = null
+
+/datum/component/riding/vehicle/lavaboat/dragonboat/handle_specials()
+ . = ..()
+ set_riding_offsets(RIDING_OFFSET_ALL, list(TEXT_NORTH = list(1, 2), TEXT_SOUTH = list(1, 2), TEXT_EAST = list(1, 2), TEXT_WEST = list( 1, 2)))
+
+/datum/component/riding/vehicle/car
+ vehicle_move_delay = 1.75
+ ride_check_flags = RIDER_NEEDS_LEGS | RIDER_NEEDS_ARMS | UNBUCKLE_DISABLED_RIDER
+
+/datum/component/riding/vehicle/car/handle_specials()
+ . = ..()
+ set_riding_offsets(RIDING_OFFSET_ALL, list(TEXT_NORTH = list(2, 20), TEXT_SOUTH = list(20, 23), TEXT_EAST = list(20, 27), TEXT_WEST = list(34, 10)))
+
+
+/*
+/datum/component/riding/vehicle/bicycle
+ ride_check_flags = RIDER_NEEDS_LEGS | RIDER_NEEDS_ARMS | UNBUCKLE_DISABLED_RIDER
+ vehicle_move_delay = 0
+
+/datum/component/riding/vehicle/bicycle/handle_specials()
+ . = ..()
+ set_riding_offsets(RIDING_OFFSET_ALL, list(TEXT_NORTH = list(0, 4), TEXT_SOUTH = list(0, 4), TEXT_EAST = list(0, 4), TEXT_WEST = list( 0, 4)))
+
+/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/vehicle/ridden/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
+ examine_list += span_notice("Going slow and nice at \"walk\" speed will prevent crashing into things.")
+
+/datum/component/riding/vehicle/scooter/skateboard/vehicle_mob_buckle(datum/source, mob/living/rider, force = FALSE)
+ . = ..()
+ if(can_slow_down)
+ RegisterSignal(rider, COMSIG_MOB_MOVE_INTENT_TOGGLE, 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_MOB_MOVE_INTENT_TOGGLE)
+
+/datum/component/riding/vehicle/scooter/skateboard/proc/toggle_move_delay(mob/living/rider)
+ SIGNAL_HANDLER
+ 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
+ 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
+ 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(!is_space_or_openspace(movable.loc))
+ on_hover_enabled()
+ return
+ var/turf/simulated/our_turf = movable.loc
+ var/turf/below = GET_TURF_BELOW(our_turf)
+
+ if(!check_space_turf(our_turf))
+ on_hover_fail()
+ return
+ //it's open space without support and the turf below is null or space without lattice, or if it'd fall several z-levels.
+ if(isopenspaceturf(our_turf) && our_turf.zPassOut(DOWN) && (isnull(below) || !check_space_turf(below) || (below.zPassOut(DOWN) && below.zPassIn(DOWN))))
+ on_hover_fail(our_turf, below, is_moving)
+ return
+ on_hover_enabled()
+
+///Part of the hover_check proc that returns false if it's a space turf without lattice or such.
+/datum/component/riding/vehicle/scooter/skateboard/hover/proc/check_space_turf(turf/turf)
+ if(!isspaceturf(turf))
+ return TRUE
+ for(var/obj/object in turf.contents)
+ if(object.obj_flags & BLOCK_Z_OUT_DOWN)
+ return TRUE
+ return FALSE
+
+///Called by hover_check() when the hoverboard is on a valid turf.
+/datum/component/riding/vehicle/scooter/skateboard/hover/proc/on_hover_enabled()
+ override_allow_spacemove = TRUE
+
+///Called by hover_check() when the hoverboard is on space or open space turf without a support underneath it.
+/datum/component/riding/vehicle/scooter/skateboard/hover/proc/on_hover_fail(turf/simulated/our_turf, turf/turf_below, is_moving)
+ override_allow_spacemove = FALSE
+ if(turf_below)
+ our_turf.zFall(parent, falling_from_move = is_moving)
+
+/datum/component/riding/vehicle/scooter/skateboard/hover/holy
+ var/is_slown_down = FALSE
+
+/datum/component/riding/vehicle/scooter/skateboard/hover/holy/on_hover_enabled()
+ if(!is_slown_down)
+ return
+ is_slown_down = FALSE
+ vehicle_move_delay -= 1
+
+/datum/component/riding/vehicle/scooter/skateboard/hover/holy/on_hover_fail(turf/simulated/our_turf, turf/turf_below, is_moving)
+ if(is_slown_down)
+ return
+ is_slown_down = TRUE
+ vehicle_move_delay += 1
+
+/datum/component/riding/vehicle/scooter/skateboard/wheelys
+ vehicle_move_delay = 0
+ can_slow_down = FALSE
+
+/datum/component/riding/vehicle/scooter/skateboard/wheelys/handle_specials()
+ . = ..()
+ set_riding_offsets(RIDING_OFFSET_ALL, list(TEXT_NORTH = list(0), TEXT_SOUTH = list(0), TEXT_EAST = list(0), TEXT_WEST = list(0)))
+
+/datum/component/riding/vehicle/scooter/skateboard/wheelys/rollerskates
+ vehicle_move_delay = 1.5
+
+/datum/component/riding/vehicle/secway/driver_move(mob/living/user, direction)
+ var/obj/vehicle/ridden/secway/the_secway = parent
+
+ if(keycheck(user) && the_secway.eddie_murphy)
+ if(COOLDOWN_FINISHED(src, message_cooldown))
+ the_secway.visible_message(span_warning("[the_secway] sputters and refuses to move!"))
+ COOLDOWN_START(src, message_cooldown, 0.75 SECONDS)
+ return COMPONENT_DRIVER_BLOCK_MOVE
+ return ..()
+
+/datum/component/riding/vehicle/speedwagon
+ vehicle_move_delay = 0
+
+/datum/component/riding/vehicle/speedwagon/handle_specials()
+ . = ..()
+ set_riding_offsets(1, list(TEXT_NORTH = list(-10, -4), TEXT_SOUTH = list(16, 3), TEXT_EAST = list(-4, 30), TEXT_WEST = list(4, -3)))
+ set_riding_offsets(2, list(TEXT_NORTH = list(19, -5, 4), TEXT_SOUTH = list(-13, 3, 4), TEXT_EAST = list(-4, -3, 4.1), TEXT_WEST = list(4, 28, 3.9)))
+ set_riding_offsets(3, list(TEXT_NORTH = list(-10, -18, 4.2), TEXT_SOUTH = list(16, 25, 3.9), TEXT_EAST = list(-22, 30), TEXT_WEST = list(22, -3, 4.1)))
+ set_riding_offsets(4, list(TEXT_NORTH = list(19, -18, 4.2), TEXT_SOUTH = list(-13, 25, 3.9), TEXT_EAST = list(-22, 3, 3.9), TEXT_WEST = list(22, 28)))
+ set_vehicle_dir_offsets(NORTH, -48, -48)
+ set_vehicle_dir_offsets(SOUTH, -48, -48)
+ set_vehicle_dir_offsets(EAST, -48, -48)
+ set_vehicle_dir_offsets(WEST, -48, -48)
+ for(var/i in GLOB.cardinal)
+ set_vehicle_dir_layer(i, BELOW_MOB_LAYER)
+
+
+/datum/component/riding/vehicle/wheelchair
+ vehicle_move_delay = 0
+ ride_check_flags = RIDER_NEEDS_ARMS
+
+/datum/component/riding/vehicle/wheelchair/handle_specials()
+ . = ..()
+ set_vehicle_dir_layer(SOUTH, OBJ_LAYER)
+ set_vehicle_dir_layer(NORTH, ABOVE_MOB_LAYER)
+ set_vehicle_dir_layer(EAST, OBJ_LAYER)
+ set_vehicle_dir_layer(WEST, OBJ_LAYER)
+
+// special messaging for those without arms
+/datum/component/riding/vehicle/wheelchair/hand/driver_move(obj/vehicle/vehicle_parent, mob/living/user, direction)
+ var/delay_multiplier = 6.7 // magic number from wheelchair code
+ vehicle_move_delay = round(CONFIG_GET(number/movedelay/run_delay) * delay_multiplier) / clamp(user.usable_hands, 1, 2)
+ return ..()
+
+/datum/component/riding/vehicle/wheelchair/motorized/driver_move(obj/vehicle/vehicle_parent, mob/living/user, direction)
+ var/obj/vehicle/ridden/wheelchair/motorized/our_chair = parent
+ var/speed = our_chair.speed
+ var/delay_multiplier = our_chair.delay_multiplier
+ vehicle_move_delay = round(CONFIG_GET(number/movedelay/run_delay) * delay_multiplier) / speed
+ return ..()
+
+/datum/component/riding/vehicle/wheelchair/motorized/handle_ride(mob/user, direction)
+ . = ..()
+ var/obj/vehicle/ridden/wheelchair/motorized/our_chair = parent
+ if(istype(our_chair) && our_chair.power_cell)
+ our_chair.power_cell.use(our_chair.energy_usage / max(our_chair.power_efficiency, 1) * 0.05)
+
+*/
diff --git a/code/datums/elements/ridable.dm b/code/datums/elements/ridable.dm
new file mode 100644
index 00000000000..97fbd7f1adc
--- /dev/null
+++ b/code/datums/elements/ridable.dm
@@ -0,0 +1,208 @@
+/**
+ * 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|ELEMENT_DETACH_ON_HOST_DESTROY
+ id_arg_index = 2
+
+ /// 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(isvehicle(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
+
+ if(HAS_TRAIT(potential_rider, TRAIT_CANT_RIDE))
+ //Do not prevent buckle, but stop any riding, do not block buckle here
+ //There are things that are supposed to buckle (like slimes) but not ride the creature
+ return 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)) // can be either 1 (cyborg riding) or 2 (human piggybacking) hands
+ potential_rider.visible_message(span_warning("[potential_rider] can't get a grip on [target_movable] because [potential_rider.p_their()] hands are full!"),
+ span_warning("You can't get a grip on [target_movable] because your hands are full!"))
+ return COMPONENT_BLOCK_BUCKLE
+
+ if((ride_check_flags & RIDER_NEEDS_LEGS) && HAS_TRAIT(potential_rider, TRAIT_FLOORED))
+ potential_rider.visible_message(span_warning("[potential_rider] can't get [potential_rider.p_their()] footing on [target_movable]!"),
+ span_warning("You can't get your footing on [target_movable]!"))
+ return COMPONENT_BLOCK_BUCKLE
+
+ var/mob/living/target_living = target_movable
+
+ // need to see if !equip_buckle_inhands() checks are enough to skip any needed incapac/restrain checks
+ // CARRIER_NEEDS_ARM shouldn't apply if the ridden isn't even a living mob
+ if((ride_check_flags & CARRIER_NEEDS_ARM) && !equip_buckle_inhands(target_living, 1, target_living, potential_rider)) // hardcode 1 hand for now
+ target_living.visible_message(span_warning("[target_living] can't get a grip on [potential_rider] because [target_living.p_their()] hands are full!"),
+ span_warning("You can't get a grip on [potential_rider] because your hands are full!"))
+ return COMPONENT_BLOCK_BUCKLE
+
+ 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
+
+ // 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_active_hand(inhand))
+ inserted_successfully = TRUE
+ else
+ var/hand = user.get_item_by_slot(ITEM_SLOT_HAND_LEFT) || user.get_item_by_slot(ITEM_SLOT_HAND_RIGHT)
+ if(hand && user.put_in_hand(inhand, hand))
+ 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_warning("[ridable_atom] has already been coated with red, that's as fast as it'll go!"))
+ 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_warning("It's too dangerous to smear [speed_potion] on [ridable_atom] while it's being ridden!"))
+ return
+ var/speed_limit = round(CONFIG_GET(number/movedelay/run_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_warning("[ridable_atom] can't be made any faster!"))
+ return
+ Detach(ridable_atom)
+ ridable_atom.AddElement(/datum/element/ridable, component_type = riding_component_type, potion_boost = TRUE)
+ to_chat(user, span_notice("You slather the red gunk over [ridable_atom], making it faster."))
+ ridable_atom.remove_atom_colour(WASHABLE_COLOUR_PRIORITY)
+ ridable_atom.add_atom_colour(COLOR_RED, FIXED_COLOUR_PRIORITY)
+ 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
+ source.unbuckle_all_mobs()
+
+ // If we're alive, back to being buckle-able
+ else
+ source.can_buckle = TRUE
+
+/obj/item/riding_offhand
+ name = "offhand"
+ icon = 'icons/obj/items.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)
+ . = ..()
+
+/obj/item/riding_offhand/on_thrown(mob/living/carbon/user, atom/target)
+ if(rider == user)
+ return //Piggyback user.
+ user.unbuckle_mob(rider)
+ if(HAS_TRAIT(user, TRAIT_PACIFISM) || GLOB.pacifism_after_gt)
+ to_chat(user, span_notice("You gently let go of [rider]."))
+ return
+ return rider
+
+/obj/item/riding_offhand/afterattack(atom/movable/interacting_with, mob/living/user, list/modifiers)
+ if(!istype(interacting_with) || !interacting_with.can_buckle)
+ return NONE
+ if(rider == user) // Piggyback user
+ return
+
+ // Handles de-fireman carrying a mob and buckling them onto something (tables, etc)
+ var/mob/living/former_rider = rider
+ user.unbuckle_mob(former_rider)
+ former_rider.forceMove(get_turf(interacting_with))
+ return interacting_with.mouse_buckle_handling(former_rider, user)
diff --git a/code/datums/elements/strippable.dm b/code/datums/elements/strippable.dm
index 3730add4ba8..0dabede699d 100644
--- a/code/datums/elements/strippable.dm
+++ b/code/datums/elements/strippable.dm
@@ -9,10 +9,15 @@
/// An assoc list of keys to /datum/strippable_item
var/list/items
+ /// A proc path that returns TRUE/FALSE if we should show the strip panel for this entity.
+ /// If it does not exist, the strip menu will always show.
+ /// Will be called with (mob/user).
+ var/should_strip_proc_path
+
/// An existing strip menus
var/list/strip_menus
-/datum/element/strippable/Attach(datum/target, list/items = list())
+/datum/element/strippable/Attach(datum/target, list/items = list(), should_strip_proc_path)
. = ..()
if(!isatom(target))
return ELEMENT_INCOMPATIBLE
@@ -20,6 +25,7 @@
RegisterSignal(target, COMSIG_DO_MOB_STRIP, PROC_REF(mouse_drop_onto))
src.items = items
+ src.should_strip_proc_path = should_strip_proc_path
/datum/element/strippable/Detach(datum/source)
. = ..()
@@ -39,6 +45,9 @@
if(over != user)
return
+ if(!isnull(should_strip_proc_path) && !call(source, should_strip_proc_path)(user))
+ return
+
var/datum/strip_menu/strip_menu = LAZYACCESS(strip_menus, source)
if(isnull(strip_menu))
diff --git a/code/game/atoms.dm b/code/game/atoms.dm
index 920746bdfc8..76255eb93fb 100644
--- a/code/game/atoms.dm
+++ b/code/game/atoms.dm
@@ -1700,3 +1700,13 @@ GLOBAL_LIST_EMPTY(blood_splatter_icons)
var/mouseparams = list2params(paramslist)
usr_client.Click(src, loc, null, mouseparams)
return TRUE
+
+/**
+ * A special case of relaymove() in which the person relaying the move may be "driving" this atom
+ *
+ * This is a special case for vehicles and ridden animals where the relayed movement may be handled
+ * by the riding component attached to this atom. Returns TRUE as long as there's nothing blocking
+ * the movement, or FALSE if the signal gets a reply that specifically blocks the movement
+ */
+/atom/proc/relaydrive(mob/living/user, direction)
+ return !(SEND_SIGNAL(src, COMSIG_RIDDEN_DRIVER_MOVE, user, direction) & COMPONENT_DRIVER_BLOCK_MOVE)
diff --git a/code/game/machinery/doors/door.dm b/code/game/machinery/doors/door.dm
index 53286d7bda0..a399d818477 100644
--- a/code/game/machinery/doors/door.dm
+++ b/code/game/machinery/doors/door.dm
@@ -42,6 +42,8 @@
var/sound_cooldown = 1 SECONDS
//Emag vulnerability.
var/hackable = TRUE
+ /// Whether or not the door can be opened by hand (used for blast doors and shutters)
+ var/can_open_with_hands = TRUE
/obj/machinery/door/New()
..()
@@ -97,7 +99,7 @@
/obj/machinery/door/Bumped(atom/movable/moving_atom, skip_effects = FALSE)
. = ..()
- if(skip_effects || operating || emagged)
+ if(skip_effects || operating || emagged || (!can_open_with_hands && density) )
return .
if(ismob(moving_atom))
var/mob/B = moving_atom
@@ -160,7 +162,7 @@
/obj/machinery/door/proc/bumpopen(mob/user)
- if(operating)
+ if(operating || !can_open_with_hands)
return
add_fingerprint(user)
@@ -233,7 +235,7 @@
/obj/machinery/door/proc/try_to_activate_door(mob/user)
add_fingerprint(user)
- if(operating || emagged)
+ if(operating || emagged || !can_open_with_hands)
return
if(requiresID() && (allowed(user) || user.can_advanced_admin_interact()))
if(density)
diff --git a/code/game/machinery/doors/poddoor.dm b/code/game/machinery/doors/poddoor.dm
index 9953735c604..86fe691879b 100644
--- a/code/game/machinery/doors/poddoor.dm
+++ b/code/game/machinery/doors/poddoor.dm
@@ -12,9 +12,11 @@
armor = list("melee" = 50, "bullet" = 100, "laser" = 100, "energy" = 100, "bomb" = 50, "bio" = 100, "rad" = 100, "fire" = 100, "acid" = 70)
resistance_flags = FIRE_PROOF
damage_deflection = 70
+ can_open_with_hands = FALSE
var/id_tag
var/protected = 1
+
/obj/machinery/door/poddoor/preopen
icon_state = "open"
density = FALSE
diff --git a/code/game/objects/buckling.dm b/code/game/objects/buckling.dm
index d5c71a4fbad..60b9191a03f 100644
--- a/code/game/objects/buckling.dm
+++ b/code/game/objects/buckling.dm
@@ -60,14 +60,22 @@
* * target - The mob to be buckled to src
* * force - Set to TRUE to ignore src's can_buckle and target's can_buckle_to
* * check_loc - Set to FALSE to allow buckling from adjacent turfs, or TRUE if buckling is only allowed with src and target on the same turf.
+ * * buckle_mob_flags- Used for riding cyborgs and humans if we need to reserve an arm or two on either the rider or the ridden mob.
*/
-/atom/movable/proc/buckle_mob(mob/living/target, force = FALSE, check_loc = TRUE)
+/atom/movable/proc/buckle_mob(mob/living/target, force = FALSE, check_loc = TRUE, buckle_mob_flags= NONE)
if(!buckled_mobs)
buckled_mobs = list()
if(!is_buckle_possible(target, force, check_loc))
return FALSE
+ // This signal will check if the mob is mounting this atom to ride it. There are 3 possibilities for how this goes
+ // 1. This movable doesn't have a ridable element and can't be ridden, so nothing gets returned, so continue on
+ // 2. There's a ridable element but we failed to mount it for whatever reason (maybe it has no seats left, for example), so we cancel the buckling
+ // 3. There's a ridable element and we were successfully able to mount, so keep it going and continue on with buckling
+ if(SEND_SIGNAL(src, COMSIG_MOVABLE_PREBUCKLE, target, force, buckle_mob_flags) & COMPONENT_BLOCK_BUCKLE)
+ return FALSE
+
// check if we are failed to move from adjacent turf
if(!check_loc && target.loc != loc)
var/old_flags = target.pass_flags
diff --git a/code/game/objects/effects/spawners/lootdrop.dm b/code/game/objects/effects/spawners/lootdrop.dm
index 6ecc671f63e..4bff4d3316b 100644
--- a/code/game/objects/effects/spawners/lootdrop.dm
+++ b/code/game/objects/effects/spawners/lootdrop.dm
@@ -402,11 +402,11 @@
/obj/effect/spawner/lootdrop/trade_sol/vehicle
name = "9. Vehicle"
loot = list(
- /obj/vehicle/motorcycle = 50,
- /obj/vehicle/snowmobile/key = 50,
- /obj/vehicle/snowmobile/blue/key = 50,
- /obj/vehicle/space/speedbike/red = 50,
- /obj/vehicle/space/speedbike = 50)
+ /obj/vehicle/ridden/motorcycle = 50,
+ /obj/vehicle/ridden/snowmobile/key = 50,
+ /obj/vehicle/ridden/snowmobile/blue/key = 50,
+ /obj/vehicle/ridden/speedbike/red = 50,
+ /obj/vehicle/ridden/speedbike = 50)
/obj/effect/spawner/lootdrop/trade_sol/vehicle/Initialize(mapload)
while(lootcount)
diff --git a/code/game/objects/items.dm b/code/game/objects/items.dm
index 32e0d6d9e49..25b478b26a2 100644
--- a/code/game/objects/items.dm
+++ b/code/game/objects/items.dm
@@ -1320,3 +1320,13 @@ GLOBAL_DATUM_INIT(fire_overlay, /mutable_appearance, mutable_appearance('icons/g
SHOULD_CALL_PARENT(TRUE)
SHOULD_BE_PURE(TRUE)
return !HAS_TRAIT(src, TRAIT_NODROP) && !(item_flags & ABSTRACT)
+
+///Called by the carbon throw_item() proc. Returns null if the item negates the throw, or a reference to the thing to suffer the throw else.
+/obj/item/proc/on_thrown(mob/living/carbon/user, atom/target)
+ if((item_flags & ABSTRACT) || HAS_TRAIT(src, TRAIT_NODROP))
+ return
+ user.drop_item_ground(src, silent = TRUE)
+ if(throwforce && HAS_TRAIT(user, TRAIT_PACIFISM))
+ to_chat(src, span_notice("Вы осторожно опускаете [declent_ru(ACCUSATIVE)] на землю."))
+ return
+ return src
diff --git a/code/game/objects/obj_defense.dm b/code/game/objects/obj_defense.dm
index 948dab072cb..a122e3cb4f5 100644
--- a/code/game/objects/obj_defense.dm
+++ b/code/game/objects/obj_defense.dm
@@ -261,6 +261,13 @@ GLOBAL_DATUM_INIT(acid_overlay, /mutable_appearance, mutable_appearance('icons/e
return TRUE
return FALSE
+///Only tesla coils, vehicles, and grounding rods currently call this because mobs are already targeted over all other objects, but this might be useful for more things later.
+/obj/proc/zap_buckle_check(strength)
+ if(has_buckled_mobs())
+ for(var/m in buckled_mobs)
+ var/mob/living/buckled_mob = m
+ buckled_mob.electrocute_act((clamp(round(strength * 1.25e-3), 10, 90) + rand(-5, 5)), src, flags = SHOCK_TESLA)
+
///returns how much the object blocks an explosion. Used by subtypes.
/obj/proc/GetExplosionBlock()
CRASH("Unimplemented GetExplosionBlock()")
diff --git a/code/modules/arcade/prize_datums.dm b/code/modules/arcade/prize_datums.dm
index f58c434cef3..74d4748c45b 100644
--- a/code/modules/arcade/prize_datums.dm
+++ b/code/modules/arcade/prize_datums.dm
@@ -296,11 +296,11 @@ GLOBAL_DATUM_INIT(global_prizes, /datum/prizes, new())
/datum/prize_item/bike
name = "Awesome Bike!"
desc = "Я прикупил огромный байк..."
- typepath = /obj/vehicle/motorcycle
+ typepath = /obj/vehicle/ridden/motorcycle
cost = 2500
/datum/prize_item/speedbike
name = "Awesome Speedbike!"
desc = "Спорим, что вы не сможете его купить? XD"
- typepath =/obj/vehicle/space/speedbike/red
- cost = 10000 //max stack + 1 tickets.
+ typepath =/obj/vehicle/ridden/speedbike/red
+ cost = 10000
diff --git a/code/modules/awaymissions/mission_code/evil_santa.dm b/code/modules/awaymissions/mission_code/evil_santa.dm
index 0e6774e09b7..366d6c41766 100644
--- a/code/modules/awaymissions/mission_code/evil_santa.dm
+++ b/code/modules/awaymissions/mission_code/evil_santa.dm
@@ -234,15 +234,15 @@
/obj/item/clothing/head/helmet/space/eva/pirate/leader,
/obj/item/hardsuit_shield/syndi,
/obj/item/hardsuit_shield/wizard,
- /obj/vehicle/space/speedbike/red,
- /obj/vehicle/space/speedbike/red,
- /obj/vehicle/space/speedbike,
- /obj/vehicle/space/speedbike,
- /obj/vehicle/motorcycle,
- /obj/vehicle/motorcycle,
- /obj/vehicle/snowmobile/blue/key,
- /obj/vehicle/snowmobile/key,
- /obj/vehicle/car,
+ /obj/vehicle/ridden/speedbike/red,
+ /obj/vehicle/ridden/speedbike/red,
+ /obj/vehicle/ridden/speedbike,
+ /obj/vehicle/ridden/speedbike,
+ /obj/vehicle/ridden/motorcycle,
+ /obj/vehicle/ridden/motorcycle,
+ /obj/vehicle/ridden/snowmobile/blue/key,
+ /obj/vehicle/ridden/snowmobile/key,
+ /obj/vehicle/ridden/car,
/obj/item/dnainjector/insulation,
/obj/item/dnainjector/nobreath,
/obj/item/dnainjector/runfast,
diff --git a/code/modules/clothing/gloves/color.dm b/code/modules/clothing/gloves/color.dm
index a611655bafb..4f5fe380e9e 100644
--- a/code/modules/clothing/gloves/color.dm
+++ b/code/modules/clothing/gloves/color.dm
@@ -251,6 +251,7 @@
item_color="white"
transfer_prints = TRUE
resistance_flags = NONE
+ clothing_traits = list(TRAIT_QUICK_CARRY)
/obj/item/clothing/gloves/color/latex/nitrile
name = "nitrile gloves"
@@ -259,6 +260,7 @@
item_state = "nitrilegloves"
transfer_prints = FALSE
item_color = "medical"
+ clothing_traits = list(TRAIT_QUICKER_CARRY)
/obj/item/clothing/gloves/color/latex/modified
name = "modified medical gloves"
diff --git a/code/modules/mining/lavaland/loot/tendril_loot.dm b/code/modules/mining/lavaland/loot/tendril_loot.dm
index cab82e0aba4..ad5d8d55186 100644
--- a/code/modules/mining/lavaland/loot/tendril_loot.dm
+++ b/code/modules/mining/lavaland/loot/tendril_loot.dm
@@ -189,82 +189,6 @@
name = "jacob's ladder"
desc = "An indestructible celestial ladder that violates the laws of physics."
-//Boat
-
-/obj/vehicle/lavaboat
- name = "lava boat"
- desc = "A boat used for traversing lava."
- icon_state = "goliath_boat"
- icon = 'icons/obj/lavaland/dragonboat.dmi'
- layer = ABOVE_MOB_LAYER
- key_type = /obj/item/oar
- key_in_hands = TRUE
- resistance_flags = LAVA_PROOF | FIRE_PROOF
-
-
-/obj/vehicle/lavaboat/relaymove(mob/user, direction)
- if(!COOLDOWN_FINISHED(src, vehicle_move_cooldown))
- return FALSE
- //We can move from land to lava, or lava to land, but not from land to land
- if(!istype(get_step(src, direction), /turf/simulated/floor/lava) && !istype(get_turf(src), /turf/simulated/floor/lava))
- to_chat(user, span_warning("You cannot traverse futher!"))
- COOLDOWN_START(src, vehicle_move_cooldown, 0.5 SECONDS)
- return FALSE
- return ..()
-
-
-/obj/vehicle/lavaboat/handle_vehicle_layer()
- return
-
-
-/obj/item/oar
- name = "oar"
- icon = 'icons/obj/vehicles/vehicles.dmi'
- icon_state = "oar"
- item_state = "rods"
- desc = "Not to be confused with the kind Research hassles you for."
- force = 12
- w_class = WEIGHT_CLASS_NORMAL
- resistance_flags = LAVA_PROOF | FIRE_PROOF
-
-/datum/crafting_recipe/oar
- name = "goliath bone oar"
- result = /obj/item/oar
- reqs = list(/obj/item/stack/sheet/bone = 2)
- time = 15
- category = CAT_PRIMAL
-
-/datum/crafting_recipe/boat
- name = "goliath hide boat"
- result = /obj/vehicle/lavaboat
- reqs = list(/obj/item/stack/sheet/animalhide/goliath_hide = 3)
- time = 50
- category = CAT_PRIMAL
-
-//Dragon Boat
-
-/obj/item/ship_in_a_bottle
- name = "ship in a bottle"
- desc = "A tiny ship inside a bottle."
- icon = 'icons/obj/lavaland/artefacts.dmi'
- icon_state = "ship_bottle"
-
-/obj/item/ship_in_a_bottle/attack_self(mob/user)
- to_chat(user, "You're not sure how they get the ships in these things, but you're pretty sure you know how to get it out.")
- playsound(user.loc, 'sound/effects/glassbr1.ogg', 100, 1)
- new /obj/vehicle/lavaboat/dragon(get_turf(src))
- qdel(src)
-
-/obj/vehicle/lavaboat/dragon
- name = "mysterious boat"
- desc = "This boat moves where you will it, without the need for an oar."
- key_type = null
- key_in_hands = FALSE
- icon_state = "dragon_boat"
- generic_pixel_y = 2
- generic_pixel_x = 1
- vehicle_move_delay = 0.25 SECONDS
-
//Wisp Lantern
/obj/item/wisp_lantern
name = "spooky lantern"
diff --git a/code/modules/mob/living/carbon/carbon.dm b/code/modules/mob/living/carbon/carbon.dm
index fa1d1ead429..152de5ef08d 100644
--- a/code/modules/mob/living/carbon/carbon.dm
+++ b/code/modules/mob/living/carbon/carbon.dm
@@ -535,14 +535,9 @@
to_chat(src, span_notice("Вы осторожно отпускаете [throwable_mob.declent_ru(ACCUSATIVE)]."))
return FALSE
else
- if(held_item.override_throw(src, target) || (held_item.item_flags & ABSTRACT)) //can't throw abstract items
+ if(held_item.override_throw(src, target))
return FALSE
- if(!drop_item_ground(held_item, silent = TRUE))
- return FALSE
- if(held_item.throwforce && (GLOB.pacifism_after_gt || HAS_TRAIT(src, TRAIT_PACIFISM)))
- to_chat(src, span_notice("Вы осторожно опускаете [held_item.declent_ru(ACCUSATIVE)] на землю."))
- return FALSE
- thrown_thing = held_item
+ thrown_thing = held_item.on_thrown(src, target)
if(!thrown_thing)
return FALSE
diff --git a/code/modules/mob/living/carbon/human/human.dm b/code/modules/mob/living/carbon/human/human.dm
index 3752a4114f5..94a84ac7a07 100644
--- a/code/modules/mob/living/carbon/human/human.dm
+++ b/code/modules/mob/living/carbon/human/human.dm
@@ -19,9 +19,9 @@
create_reagents(330)
handcrafting = new()
-
+ AddElement(/datum/element/ridable, /datum/component/riding/creature/human)
AddElement(/datum/element/footstep, FOOTSTEP_MOB_HUMAN, 1, -6)
- AddElement(/datum/element/strippable, GLOB.strippable_human_items)
+ AddElement(/datum/element/strippable, GLOB.strippable_human_items, TYPE_PROC_REF(/mob/living/carbon/human/, should_strip))
UpdateAppearance()
GLOB.human_list += src
@@ -1914,3 +1914,56 @@ Eyes need to have significantly high darksight to shine unless the mob has the X
return FALSE
return ..()
+/mob/living/carbon/human/mouse_buckle_handling(mob/living/M, mob/living/user)
+ if(pulling != M || grab_state != GRAB_AGGRESSIVE || stat != CONSCIOUS)
+ return FALSE
+ //If you dragged them to you and you're aggressively grabbing try to fireman carry them
+ if(can_be_firemanned(M))
+ var/active_hand_available = can_pull(hand, supress_message = TRUE)
+ var/inactive_hand_available = can_pull(!hand, supress_message = TRUE)
+ if(!active_hand_available && !inactive_hand_available)
+ return
+ if(!active_hand_available && !swap_hand())
+ to_chat(user, span_warning("освободи одну из рук!"))
+ return FALSE
+ fireman_carry(M)
+ return TRUE
+
+/mob/living/carbon/human/proc/can_be_firemanned(mob/living/carbon/target)
+ return ishuman(target) && target.body_position == LYING_DOWN
+
+/mob/living/carbon/human/proc/fireman_carry(mob/living/carbon/target)
+ if(!can_be_firemanned(target) || incapacitated(INC_IGNORE_GRABBED))
+ to_chat(src, span_warning("You can't fireman carry [target] while [target.p_they()] [target.p_are()] standing!"))
+ return
+
+ var/carrydelay = 5 SECONDS //if you have latex you are faster at grabbing
+ var/skills_space
+ if(HAS_TRAIT(src, TRAIT_QUICKER_CARRY))
+ carrydelay -= 2 SECONDS
+ else if(HAS_TRAIT(src, TRAIT_QUICK_CARRY))
+ carrydelay -= 1 SECONDS
+
+ /*
+ var/obj/item/organ/internal/cyberimp/chest/spine/potential_spine = get_organ_slot(ORGAN_SLOT_SPINE)
+ if(istype(potential_spine))
+ carrydelay *= potential_spine.athletics_boost_multiplier
+ */
+
+ if(carrydelay <= 3 SECONDS)
+ skills_space = " very quickly"
+ else if(carrydelay <= 4 SECONDS)
+ skills_space = " quickly"
+
+ visible_message(span_notice("[src] starts[skills_space] lifting [target] onto [p_their()] back..."),
+ span_notice("You[skills_space] start to lift [target] onto your back..."))
+ if(!do_after(src, carrydelay, target))
+ visible_message(span_warning("[src] fails to fireman carry [target]!"))
+ return
+
+ //Second check to make sure they're still valid to be carried
+ if(!can_be_firemanned(target) || incapacitated(INC_IGNORE_GRABBED) || target.buckled)
+ visible_message(span_warning("[src] fails to fireman carry [target]!"))
+ return
+
+ return buckle_mob(target, TRUE, FALSE, CARRIER_NEEDS_ARM) //checkloc is false because we usually grab people from nearest tile
diff --git a/code/modules/mob/living/carbon/human/human_defines.dm b/code/modules/mob/living/carbon/human/human_defines.dm
index 02ceb4d307e..b4059f4a60f 100644
--- a/code/modules/mob/living/carbon/human/human_defines.dm
+++ b/code/modules/mob/living/carbon/human/human_defines.dm
@@ -88,3 +88,5 @@
/// Holder for the phisiology datum
var/datum/physiology/physiology
+ /// What types of mobs are allowed to ride/buckle to this mob. Only human for now
+ var/static/list/can_ride_typecache = typecacheof(list(/mob/living/carbon/human))
diff --git a/code/modules/mob/living/carbon/human/human_stripping.dm b/code/modules/mob/living/carbon/human/human_stripping.dm
index 823e299f7e8..4de2cbbb5bb 100644
--- a/code/modules/mob/living/carbon/human/human_stripping.dm
+++ b/code/modules/mob/living/carbon/human/human_stripping.dm
@@ -25,6 +25,16 @@ GLOBAL_LIST_INIT(strippable_human_items, create_strippable_list(list(
/datum/strippable_item/mob_item_slot/legcuffs,
)))
+/mob/living/carbon/human/proc/should_strip(mob/user)
+ if(user.pulling != src || user.grab_state != GRAB_AGGRESSIVE)
+ return TRUE
+
+ if(ishuman(user))
+ var/mob/living/carbon/human/human_user = user
+ return !human_user.can_be_firemanned(src)
+
+ return TRUE
+
/datum/strippable_item/mob_item_slot/eyes
key = STRIPPABLE_ITEM_EYES
item_slot = ITEM_SLOT_EYES
diff --git a/code/modules/mob/living/simple_animal/simple_animal.dm b/code/modules/mob/living/simple_animal/simple_animal.dm
index 72400bc2aaa..86de64b8840 100644
--- a/code/modules/mob/living/simple_animal/simple_animal.dm
+++ b/code/modules/mob/living/simple_animal/simple_animal.dm
@@ -641,6 +641,9 @@
return
if(!can_have_ai && (togglestatus != AI_OFF))
return
+ if(HAS_TRAIT(src, TRAIT_AI_PAUSED))
+ AIStatus = AI_OFF
+ return
var/turf/our_turf = get_turf(src)
if(QDELETED(src) || !our_turf)
return
diff --git a/code/modules/mob/mob.dm b/code/modules/mob/mob.dm
index a6091dd2d67..d1020156e82 100644
--- a/code/modules/mob/mob.dm
+++ b/code/modules/mob/mob.dm
@@ -788,7 +788,7 @@
*
* You can buckle on mobs if you're next to them since most are dense
*/
-/mob/buckle_mob(mob/living/target, force = FALSE, check_loc = TRUE)
+/mob/buckle_mob(mob/living/target, force = FALSE, check_loc = TRUE, buckle_mob_flags= NONE)
if(target.buckled)
return FALSE
return ..()
diff --git a/code/modules/movespeed/modifiers/mobs.dm b/code/modules/movespeed/modifiers/mobs.dm
index 47a522157eb..15b721aa472 100644
--- a/code/modules/movespeed/modifiers/mobs.dm
+++ b/code/modules/movespeed/modifiers/mobs.dm
@@ -225,3 +225,6 @@
*/
+/datum/movespeed_modifier/human_carry
+ multiplicative_slowdown = HUMAN_CARRY_SLOWDOWN
+ blacklisted_movetypes = FLOATING
diff --git a/code/modules/research/xenobiology/xenobiology.dm b/code/modules/research/xenobiology/xenobiology.dm
index 88f99a30564..34c2d21fff4 100644
--- a/code/modules/research/xenobiology/xenobiology.dm
+++ b/code/modules/research/xenobiology/xenobiology.dm
@@ -518,6 +518,8 @@
if(!istype(O))
to_chat(user, "The potion can only be used on items or vehicles!")
return
+ if(SEND_SIGNAL(O, COMSIG_SPEED_POTION_APPLIED, src, user) & SPEED_POTION_STOP)
+ return
if(isitem(O))
var/obj/item/I = O
if(I.slowdown <= 0 || (I.item_flags & IGNORE_SLOWDOWN))
@@ -530,11 +532,6 @@
return
I.item_flags |= IGNORE_SLOWDOWN
I.update_equipped_item()
-
- else if(istype(O, /obj/vehicle))
- var/obj/vehicle/vehicle = O
- if(vehicle.check_potion(src, user))
- return
return ..()
else if (!drop && istype(O, /obj/machinery/smartfridge))
diff --git a/code/modules/station_goals/bluespace_tap.dm b/code/modules/station_goals/bluespace_tap.dm
index 2bd60158441..efa25d1e032 100644
--- a/code/modules/station_goals/bluespace_tap.dm
+++ b/code/modules/station_goals/bluespace_tap.dm
@@ -93,7 +93,7 @@
/obj/effect/spawner/lootdrop/bluespace_tap/cultural
name = "cultural artifacts"
loot = list(
- /obj/vehicle/space/speedbike/red = 10,
+ /obj/vehicle/ridden/speedbike/red = 10,
/obj/item/grenade/clusterbuster/honk = 10,
/obj/item/toy/katana = 10,
/obj/item/stack/sheet/mineral/abductor/fifty = 20,
diff --git a/code/modules/station_goals/brs/brs_reward.dm b/code/modules/station_goals/brs/brs_reward.dm
index 93af7600914..156b30f59af 100644
--- a/code/modules/station_goals/brs/brs_reward.dm
+++ b/code/modules/station_goals/brs/brs_reward.dm
@@ -101,15 +101,15 @@
// Vehicles
/obj/item/fluff/rapid_wheelchair_kit = 50,
- /obj/vehicle/secway = 60,
- /obj/vehicle/atv = 30,
- /obj/vehicle/motorcycle = 20,
- /obj/vehicle/janicart = 15,
- /obj/vehicle/ambulance = 15,
- /obj/vehicle/snowmobile = 15,
- /obj/vehicle/space/speedbike/red = 10,
- /obj/vehicle/space/speedbike = 10,
- /obj/vehicle/car,
+ /obj/vehicle/ridden/secway = 60,
+ /obj/vehicle/ridden/atv = 30,
+ /obj/vehicle/ridden/motorcycle = 20,
+ /obj/vehicle/ridden/janicart = 15,
+ /obj/vehicle/ridden/ambulance = 15,
+ /obj/vehicle/ridden/snowmobile = 15,
+ /obj/vehicle/ridden/speedbike/red = 10,
+ /obj/vehicle/ridden/speedbike = 10,
+ /obj/vehicle/ridden/car,
//Toys
/obj/item/toy/syndicateballoon = 5,
diff --git a/code/modules/vehicle/_vehicle.dm b/code/modules/vehicle/_vehicle.dm
new file mode 100644
index 00000000000..57aada2ad16
--- /dev/null
+++ b/code/modules/vehicle/_vehicle.dm
@@ -0,0 +1,184 @@
+/obj/vehicle
+ name = "generic vehicle"
+ desc = "Yell at coderbus."
+ icon = 'icons/obj/vehicles/vehicles.dmi'
+ icon_state = "error"
+ max_integrity = 300
+ layer = VEHICLE_LAYER
+ density = TRUE
+ anchored = FALSE
+ blocks_emissive = EMISSIVE_BLOCK_GENERIC
+ pass_flags_self = PASSVEHICLE
+ COOLDOWN_DECLARE(cooldown_vehicle_move)
+ var/list/mob/occupants //mob = bitflags of their control level.
+ ///Maximum amount of passengers plus drivers
+ var/max_occupants = 1
+ ////Maximum amount of drivers
+ var/max_drivers = 1
+ var/movedelay = 2
+ var/lastmove = 0
+ /**
+ * If the driver needs a certain item in hand (or inserted, for vehicles) to drive this. For vehicles, this must be duplicated on their riding component subtype
+ * [/datum/component/riding/var/keytype] variable because only a few specific checks are handled here with this var, and the majority of it is on the riding component
+ * Eventually the remaining checks should be moved to the component and this var removed.
+ */
+ var/key_type
+ ///The inserted key, needed on some vehicles to start the engine
+ var/obj/item/key/inserted_key
+ /// Whether the vehicle is currently able to move
+ var/canmove = TRUE
+ var/list/autogrant_actions_passenger //plain list of typepaths
+ var/list/autogrant_actions_controller //assoc list "[bitflag]" = list(typepaths)
+ var/list/list/datum/action/occupant_actions //assoc list mob = list(type = action datum assigned to mob)
+ ///This vehicle will follow us when we move (like atrailer duh)
+ var/obj/vehicle/trailer
+ var/are_legs_exposed = FALSE
+
+/obj/vehicle/Initialize(mapload)
+ . = ..()
+ occupants = list()
+ autogrant_actions_passenger = list()
+ autogrant_actions_controller = list()
+ occupant_actions = list()
+ generate_actions()
+
+/obj/vehicle/Destroy(force)
+ QDEL_NULL(trailer)
+ inserted_key = null
+ return ..()
+
+/obj/vehicle/Exited(atom/movable/gone, direction)
+ if(gone == inserted_key)
+ inserted_key = null
+ return ..()
+
+/obj/vehicle/examine(mob/user)
+ . = ..()
+ . += generate_integrity_message()
+
+/// Returns a readable string of the vehicle's health for examining. Overridden by subtypes who want to be more verbose with their health messages.
+/obj/vehicle/proc/generate_integrity_message()
+ var/examine_text = ""
+ var/integrity = obj_integrity/max_integrity * 100
+ switch(integrity)
+ if(50 to 99)
+ examine_text = "It looks slightly damaged."
+ if(25 to 50)
+ examine_text = "It appears heavily damaged."
+ if(0 to 25)
+ examine_text = span_warning("It's falling apart!")
+
+ return examine_text
+
+/obj/vehicle/proc/is_key(obj/item/I)
+ return istype(I, key_type)
+
+/obj/vehicle/proc/return_occupants()
+ return occupants
+
+/obj/vehicle/proc/occupant_amount()
+ return LAZYLEN(occupants)
+
+/obj/vehicle/proc/return_amount_of_controllers_with_flag(flag)
+ . = 0
+ for(var/i in occupants)
+ if(occupants[i] & flag)
+ .++
+
+/obj/vehicle/proc/return_controllers_with_flag(flag)
+ RETURN_TYPE(/list/mob)
+ . = list()
+ for(var/i in occupants)
+ if(occupants[i] & flag)
+ . += i
+
+/obj/vehicle/proc/return_drivers()
+ return return_controllers_with_flag(VEHICLE_CONTROL_DRIVE)
+
+/obj/vehicle/proc/driver_amount()
+ return return_amount_of_controllers_with_flag(VEHICLE_CONTROL_DRIVE)
+
+/obj/vehicle/proc/is_driver(mob/M)
+ return is_occupant(M) && occupants[M] & VEHICLE_CONTROL_DRIVE
+
+/obj/vehicle/proc/is_occupant(mob/M)
+ return !isnull(LAZYACCESS(occupants, M))
+
+/obj/vehicle/proc/add_occupant(mob/M, control_flags)
+ if(!istype(M) || is_occupant(M))
+ return FALSE
+
+ LAZYSET(occupants, M, NONE)
+ add_control_flags(M, control_flags)
+ after_add_occupant(M)
+ grant_passenger_actions(M)
+ return TRUE
+
+/obj/vehicle/proc/after_add_occupant(mob/M)
+ auto_assign_occupant_flags(M)
+
+/obj/vehicle/proc/auto_assign_occupant_flags(mob/M) //override for each type that needs it. Default is assign driver if drivers is not at max.
+ if(driver_amount() < max_drivers)
+ add_control_flags(M, VEHICLE_CONTROL_DRIVE)
+
+/obj/vehicle/proc/remove_occupant(mob/M)
+ SHOULD_CALL_PARENT(TRUE)
+ if(!istype(M))
+ return FALSE
+ remove_control_flags(M, ALL)
+ remove_passenger_actions(M)
+ LAZYREMOVE(occupants, M)
+ cleanup_actions_for_mob(M)
+ after_remove_occupant(M)
+ return TRUE
+
+/obj/vehicle/proc/after_remove_occupant(mob/M)
+
+/obj/vehicle/relaymove(mob/living/user, direction)
+ if(!canmove)
+ return FALSE
+ if(is_driver(user))
+ return relaydrive(user, direction)
+ return FALSE
+
+/obj/vehicle/proc/after_move(direction)
+ return
+
+/obj/vehicle/proc/add_control_flags(mob/controller, flags)
+ if(!is_occupant(controller) || !flags)
+ return FALSE
+ occupants[controller] |= flags
+ for(var/i in GLOB.bitflags)
+ if(flags & i)
+ grant_controller_actions_by_flag(controller, i)
+ return TRUE
+
+/obj/vehicle/proc/remove_control_flags(mob/controller, flags)
+ if(!is_occupant(controller) || !flags)
+ return FALSE
+ occupants[controller] &= ~flags
+ for(var/i in GLOB.bitflags)
+ if(flags & i)
+ remove_controller_actions_by_flag(controller, i)
+ return TRUE
+
+/// To add a trailer to the vehicle in a manner that allows safe qdels
+/obj/vehicle/proc/add_trailer(obj/vehicle/added_vehicle)
+ trailer = added_vehicle
+ RegisterSignal(trailer, COMSIG_QDELETING, PROC_REF(remove_trailer))
+
+/// To remove a trailer from the vehicle in a manner that allows safe qdels
+/obj/vehicle/proc/remove_trailer()
+ SIGNAL_HANDLER
+ UnregisterSignal(trailer, COMSIG_QDELETING)
+ trailer = null
+
+/obj/vehicle/Move(newloc, dir)
+ // It is unfortunate, but this is the way to make it not mess up
+ var/atom/old_loc = loc
+ // When we do this, it will set the loc to the new loc
+ . = ..()
+ if(trailer && .)
+ var/dir_to_move = get_dir(trailer.loc, old_loc)
+ step(trailer, dir_to_move)
+
diff --git a/code/modules/vehicle/ambulance.dm b/code/modules/vehicle/ambulance.dm
index 8f8b04a8cee..39c66039fc4 100644
--- a/code/modules/vehicle/ambulance.dm
+++ b/code/modules/vehicle/ambulance.dm
@@ -1,107 +1,37 @@
-/obj/vehicle/ambulance
+/obj/vehicle/ridden/ambulance
name = "ambulance"
desc = "This is what the paramedic uses to run over people they need to take to medbay."
icon_state = "docwagon2"
- vehicle_move_delay = 0.3 SECONDS
key_type = /obj/item/key/ambulance
+
var/obj/structure/bed/amb_trolley/bed = null
- var/datum/action/ambulance_alarm/AA
var/datum/looping_sound/ambulance_alarm/soundloop
+
+
+ //Lights on ability activation
light_on = FALSE
light_system = MOVABLE_LIGHT
light_range = 4
light_power = 3
light_color = "#F70027"
+/obj/vehicle/ridden/ambulance/generate_actions()
+ . = ..()
+ initialize_controller_action_type(/datum/action/vehicle/ridden/ambulance/ambulance_alarm, VEHICLE_CONTROL_DRIVE)
+
-/obj/vehicle/ambulance/Initialize(mapload)
+/obj/vehicle/ridden/ambulance/Initialize(mapload)
. = ..()
- AA = new(src)
soundloop = new(list(src), FALSE)
+ AddElement(/datum/element/ridable, /datum/component/riding/vehicle/ambulance)
-/obj/vehicle/ambulance/Destroy()
+/obj/vehicle/ridden/ambulance/Destroy()
QDEL_NULL(soundloop)
- QDEL_NULL(AA)
bed = null
return ..()
-
-/datum/action/ambulance_alarm
- name = "Toggle Sirens"
- icon_icon = 'icons/obj/vehicles/vehicles.dmi'
- button_icon_state = "docwagon2"
- check_flags = AB_CHECK_HANDS_BLOCKED|AB_CHECK_CONSCIOUS|AB_CHECK_INCAPACITATED
- var/toggle_cooldown = 40
- var/cooldown = 0
-
-
-/datum/action/ambulance_alarm/Trigger(left_click = TRUE)
- if(!..())
- return FALSE
-
- var/obj/vehicle/ambulance/A = target
-
- if(!istype(A) || !A.soundloop)
- return FALSE
-
- if(world.time < cooldown + toggle_cooldown)
- return FALSE
-
- cooldown = world.time
-
- if(A.soundloop.muted)
- A.soundloop.start()
- A.set_light_on(TRUE)
- else
- A.soundloop.stop()
- A.set_light_on(FALSE)
-
-
-/datum/looping_sound/ambulance_alarm
- start_length = 0
- mid_sounds = list('sound/items/weeoo1.ogg' = 1)
- mid_length = 14
- volume = 100
-
-
-/obj/vehicle/ambulance/post_buckle_mob(mob/living/target)
- . = ..()
- AA.Grant(target)
-
-
-/obj/vehicle/ambulance/post_unbuckle_mob(mob/living/target)
- . = ..()
- AA.Remove(target)
-
-
-/obj/item/key/ambulance
- name = "ambulance key"
- desc = "A keyring with a small steel key, and tag with a red cross on it."
- icon_state = "keydoc"
-
-
-/obj/vehicle/ambulance/handle_vehicle_offsets()
- if(!has_buckled_mobs())
- return
- for(var/mob/living/buckled_mob as anything in buckled_mobs)
- buckled_mob.setDir(dir)
- switch(dir)
- if(SOUTH)
- buckled_mob.pixel_x = 0
- buckled_mob.pixel_y = 7
- if(WEST)
- buckled_mob.pixel_x = 13
- buckled_mob.pixel_y = 7
- if(NORTH)
- buckled_mob.pixel_x = 0
- buckled_mob.pixel_y = 4
- if(EAST)
- buckled_mob.pixel_x = -13
- buckled_mob.pixel_y = 7
-
-
-/obj/vehicle/ambulance/Move(atom/newloc, direct = NONE, glide_size_override = 0, update_dir = TRUE)
+/obj/vehicle/ridden/ambulance/Move(atom/newloc, direct = NONE, glide_size_override = 0, update_dir = TRUE)
var/oldloc = loc
if(bed && !Adjacent(bed))
bed = null
@@ -119,17 +49,16 @@
icon_state = "ambulance"
anchored = FALSE
-
/obj/structure/bed/amb_trolley/examine(mob/user)
. = ..()
. += "Drag [src]'s sprite over the ambulance to (de)attach it."
/obj/structure/bed/amb_trolley/MouseDrop(atom/over_object, src_location, over_location, src_control, over_control, params)
. = ..()
- if(!istype(over_object, /obj/vehicle/ambulance) || usr.incapacitated() || HAS_TRAIT(usr, TRAIT_HANDS_BLOCKED))
+ if(!istype(over_object, /obj/vehicle/ridden/ambulance) || usr.incapacitated() || HAS_TRAIT(usr, TRAIT_HANDS_BLOCKED))
return FALSE
- var/obj/vehicle/ambulance/amb = over_object
+ var/obj/vehicle/ridden/ambulance/amb = over_object
if(amb.bed)
amb.bed = null
balloon_alert(usr, "отцеплено от машины")
diff --git a/code/modules/vehicle/atv.dm b/code/modules/vehicle/atv.dm
index 01bc376c0af..f8d2d53ccbb 100644
--- a/code/modules/vehicle/atv.dm
+++ b/code/modules/vehicle/atv.dm
@@ -1,116 +1,90 @@
-/obj/vehicle/atv
+/obj/vehicle/ridden/atv
name = "all-terrain vehicle"
desc = "An all-terrain vehicle built for traversing rough terrain with ease. One of the few old-earth technologies that are still relevant on most planet-bound outposts."
icon = 'icons/obj/vehicles/4wheeler.dmi'
icon_state = "atv"
max_integrity = 150
armor = list("melee" = 50, "bullet" = 25, "laser" = 20, "energy" = 0, "bomb" = 50, "bio" = 0, "rad" = 0, "fire" = 60, "acid" = 60)
- key_type = /obj/item/key
- integrity_failure = 70
- generic_pixel_x = 0
- generic_pixel_y = 4
- vehicle_move_delay = 0.25 SECONDS
- var/mutable_appearance/atvcover
+ key_type = /obj/item/key/atv
+ integrity_failure = 0.5
+ var/static/mutable_appearance/atvcover
-/obj/vehicle/atv/Initialize(mapload)
+/obj/vehicle/ridden/atv/Initialize(mapload)
. = ..()
- atvcover = mutable_appearance(icon, "atvcover", ABOVE_MOB_LAYER + 0.1)
+ AddElement(/datum/element/ridable, /datum/component/riding/vehicle/atv)
+ if(!atvcover)
+ atvcover = mutable_appearance(icon, "atvcover", ABOVE_MOB_LAYER + 0.1)
-
-/obj/vehicle/atv/Destroy()
- atvcover = null
+/obj/vehicle/ridden/atv/post_buckle_mob(mob/living/M)
+ add_overlay(atvcover)
return ..()
-
-/obj/vehicle/atv/update_overlays()
- . = ..()
+/obj/vehicle/ridden/atv/post_unbuckle_mob(mob/living/M)
if(!has_buckled_mobs())
- return .
- . += atvcover
-
-
-/obj/vehicle/atv/handle_vehicle_icons()
- update_icon(UPDATE_OVERLAYS)
-
+ cut_overlay(atvcover)
+ return ..()
-/obj/vehicle/atv/handle_vehicle_layer()
- return
+/obj/vehicle/ridden/atv/Destroy()
+ atvcover = null
+ return ..()
//TURRETS!
-/obj/vehicle/atv/turret
+/obj/vehicle/ridden/atv/turret
var/obj/machinery/porta_turret/syndicate/vehicle_turret/turret = /obj/machinery/porta_turret/syndicate/vehicle_turret
-/obj/vehicle/atv/turret/Initialize(mapload)
+/obj/vehicle/ridden/atv/turret/Initialize(mapload)
. = ..()
turret = new turret(loc)
- handle_vehicle_offsets()
- handle_vehicle_icons()
RegisterSignal(src, COMSIG_MOVABLE_UPDATE_GLIDE_SIZE, PROC_REF(on_glide_size_update))
RegisterSignal(turret, COMSIG_QDELETING, PROC_REF(on_turret_deleting))
-/obj/vehicle/atv/turret/Destroy()
+/obj/vehicle/ridden/atv/turret/Destroy()
QDEL_NULL(turret)
return ..()
-/obj/vehicle/atv/turret/proc/on_glide_size_update(datum/source, new_glide_size)
+/obj/vehicle/ridden/atv/turret/proc/on_glide_size_update(datum/source, new_glide_size)
SIGNAL_HANDLER
turret?.set_glide_size(new_glide_size)
-/obj/vehicle/atv/turret/proc/on_turret_deleting(datum/source)
+/obj/vehicle/ridden/atv/turret/proc/on_turret_deleting(datum/source)
SIGNAL_HANDLER
UnregisterSignal(src, COMSIG_MOVABLE_UPDATE_GLIDE_SIZE)
turret = null
-
-/obj/vehicle/atv/turret/Moved(atom/old_loc, movement_dir, forced, list/old_locs, momentum_change = TRUE)
+/obj/vehicle/ridden/atv/turret/Moved(atom/old_loc, movement_dir, forced, list/old_locs, momentum_change = TRUE)
. = ..()
- if(. && turret)
- turret.forceMove(loc)
-
-
-/obj/vehicle/atv/turret/handle_vehicle_layer()
if(!turret)
return
- if(!has_buckled_mobs())
- turret.layer = OBJ_LAYER + 0.01
+ var/turf/our_turf = get_turf(src)
+ if(!our_turf)
return
- if(dir == SOUTH)
- turret.layer = OBJ_LAYER + 0.01
- else
- turret.layer = ABOVE_MOB_LAYER + 0.01
-
-
-/obj/vehicle/atv/turret/update_overlays()
- . = list(atvcover)
-
-
-/obj/vehicle/atv/turret/handle_vehicle_offsets()
- . = ..()
- if(!turret)
- return
-
+ turret.forceMove(our_turf)
switch(dir)
if(NORTH)
- turret.pixel_x = 0
- turret.pixel_y = 4
+ turret.pixel_x = base_pixel_x
+ turret.pixel_y = base_pixel_y + 4
+ turret.layer = ABOVE_MOB_LAYER
if(EAST)
- turret.pixel_x = -12
- turret.pixel_y = 4
+ turret.pixel_x = base_pixel_x - 12
+ turret.pixel_y = base_pixel_y + 4
+ turret.layer = OBJ_LAYER
if(SOUTH)
- turret.pixel_x = 0
- turret.pixel_y = 4
+ turret.pixel_x = base_pixel_x
+ turret.pixel_y = base_pixel_y + 4
+ turret.layer = OBJ_LAYER
if(WEST)
- turret.pixel_x = 12
- turret.pixel_y = 4
+ turret.pixel_x = base_pixel_x + 12
+ turret.pixel_y = base_pixel_y + 4
+ turret.layer = OBJ_LAYER
-/obj/vehicle/atv/turret/fast
+/obj/vehicle/ridden/atv/turret/fast
turret = /obj/machinery/porta_turret/syndicate/vehicle_turret/fast
diff --git a/code/modules/vehicle/janicart.dm b/code/modules/vehicle/janicart.dm
index 6ff68046093..6ca97e9915d 100644
--- a/code/modules/vehicle/janicart.dm
+++ b/code/modules/vehicle/janicart.dm
@@ -1,97 +1,98 @@
-//PIMP-CART
-/obj/vehicle/janicart
+/**
+ * # Janicart
+ */
+/obj/vehicle/ridden/janicart
name = "janicart (pimpin' ride)"
desc = "A brave janitor cyborg gave its life to produce such an amazing combination of speed and utility."
icon_state = "pussywagon"
key_type = /obj/item/key/janitor
+ movedelay = 1
+ /// The attached garbage bag, if present
var/obj/item/storage/bag/trash/trash_bag
- var/floorbuffer = FALSE
-
-/obj/vehicle/janicart/Destroy()
- QDEL_NULL(trash_bag)
- return ..()
+ /// The installed upgrade, if present
+ var/obj/item/janiupgrade/installed_upgrade
+/obj/vehicle/ridden/janicart/Initialize(mapload)
+ . = ..()
+ update_appearance()
+ AddElement(/datum/element/ridable, /datum/component/riding/vehicle/janicart)
-/obj/vehicle/janicart/handle_vehicle_offsets()
- if(!has_buckled_mobs())
- return
- for(var/mob/living/buckled_mob as anything in buckled_mobs)
- buckled_mob.setDir(dir)
- switch(dir)
- if(NORTH)
- buckled_mob.pixel_x = 0
- buckled_mob.pixel_y = 4
- if(EAST)
- buckled_mob.pixel_x = -12
- buckled_mob.pixel_y = 7
- if(SOUTH)
- buckled_mob.pixel_x = 0
- buckled_mob.pixel_y = 7
- if(WEST)
- buckled_mob.pixel_x = 12
- buckled_mob.pixel_y = 7
+/obj/vehicle/ridden/janicart/Destroy()
+ if(trash_bag)
+ QDEL_NULL(trash_bag)
+ if(installed_upgrade)
+ QDEL_NULL(installed_upgrade)
+ return ..()
-/obj/vehicle/janicart/Moved(atom/old_loc, movement_dir, forced, list/old_locs, momentum_change = TRUE)
+/obj/vehicle/ridden/janicart/Moved(atom/old_loc, movement_dir, forced, list/old_locs, momentum_change = TRUE)
. = ..()
- if(. && floorbuffer && isturf(loc))
+ if(. && installed_upgrade && isturf(loc))
loc.clean_blood()
for(var/obj/effect/check in loc)
if(check.is_cleanable())
qdel(check)
-/obj/vehicle/janicart/examine(mob/user)
+/obj/vehicle/ridden/janicart/examine(mob/user)
. = ..()
- if(floorbuffer)
- . += span_notice("It has been upgraded with a floor buffer.")
+ if(installed_upgrade)
+ . += "It has been upgraded with [installed_upgrade], which can be removed with a screwdriver."
-
-/obj/vehicle/janicart/attackby(obj/item/I, mob/user, params)
+/obj/vehicle/ridden/janicart/attackby(obj/item/I, mob/user, params)
if(user.a_intent == INTENT_HARM)
return ..()
-
if(istype(I, /obj/item/storage/bag/trash))
add_fingerprint(user)
if(trash_bag)
balloon_alert(user, "уже прицеплено!")
return ATTACK_CHAIN_PROCEED
- if(!user.drop_transfer_item_to_loc(I, src))
+ if(!user.transfer_item_to_loc(I, src))
return ..()
balloon_alert(user, "прицеплено к машине")
trash_bag = I
- update_icon(UPDATE_OVERLAYS)
+ update_appearance()
return ATTACK_CHAIN_BLOCKED_ALL
- if(istype(I, /obj/item/janiupgrade))
- add_fingerprint(user)
- if(floorbuffer)
+ else if(istype(I, /obj/item/janiupgrade))
+ if(installed_upgrade)
balloon_alert(user, "уже установлено!")
return ATTACK_CHAIN_PROCEED
if(!user.drop_transfer_item_to_loc(I, src))
return ..()
- floorbuffer = TRUE
+ var/obj/item/janiupgrade/new_upgrade = I
+ new_upgrade.forceMove(src)
+ installed_upgrade = new_upgrade
balloon_alert(user, "установлено")
- update_icon(UPDATE_OVERLAYS)
- qdel(I)
+ update_appearance()
return ATTACK_CHAIN_BLOCKED_ALL
- if(trash_bag && (initial(key_type.type) != I.type)) // don't put a key in the trash when we need it
- trash_bag.attackby(I, user, params)
+ else if(istype(I, /obj/item/screwdriver) && installed_upgrade)
+ installed_upgrade.forceMove(get_turf(user))
+ user.put_in_hands(installed_upgrade)
+ balloon_alert(user, "удалено")
+ installed_upgrade = null
+ update_appearance()
return ATTACK_CHAIN_BLOCKED_ALL
- return ..()
+ else if(trash_bag && (!is_key(I) || is_key(inserted_key))) // don't put a key in the trash when we need it
+ trash_bag.attackby(I, user, params)
+ return ATTACK_CHAIN_BLOCKED_ALL
+ else
+ return ..()
-/obj/vehicle/janicart/update_overlays()
+/obj/vehicle/ridden/janicart/update_overlays()
. = ..()
if(trash_bag)
- . += "cart_garbage"
- if(floorbuffer)
+ if(istype(trash_bag, /obj/item/storage/bag/trash/bluespace))
+ . += "cart_bluespace_garbage"
+ else
+ . += "cart_garbage"
+ if(installed_upgrade)
. += "cart_buffer"
-
-/obj/vehicle/janicart/attack_hand(mob/user)
+/obj/vehicle/ridden/janicart/attack_hand(mob/user)
if(..())
return TRUE
else if(trash_bag)
@@ -100,10 +101,6 @@
trash_bag = null
update_icon(UPDATE_OVERLAYS)
-/obj/item/key/janitor
- desc = "A keyring with a small steel key, and a pink fob reading \"Pussy Wagon\"."
- icon_state = "keyjanitor"
-
/obj/item/janiupgrade
name = "floor buffer upgrade"
desc = "An upgrade for mobile janicarts."
diff --git a/code/modules/vehicle/lavaboat.dm b/code/modules/vehicle/lavaboat.dm
new file mode 100644
index 00000000000..d562bb38dba
--- /dev/null
+++ b/code/modules/vehicle/lavaboat.dm
@@ -0,0 +1,63 @@
+/obj/vehicle/ridden/lavaboat
+ name = "lava boat"
+ desc = "A boat used for traversing lava."
+ icon_state = "goliath_boat"
+ icon = 'icons/obj/lavaland/dragonboat.dmi'
+ layer = ABOVE_MOB_LAYER
+ key_type = /obj/item/oar
+ resistance_flags = LAVA_PROOF | FIRE_PROOF
+
+/obj/vehicle/ridden/lavaboat/Initialize(mapload)
+ . = ..()
+ AddElement(/datum/element/ridable, /datum/component/riding/vehicle/lavaboat)
+
+//Dragon Boat
+
+/obj/item/ship_in_a_bottle
+ name = "ship in a bottle"
+ desc = "A tiny ship inside a bottle."
+ icon = 'icons/obj/lavaland/artefacts.dmi'
+ icon_state = "ship_bottle"
+
+/obj/item/ship_in_a_bottle/attack_self(mob/user)
+ to_chat(user, "You're not sure how they get the ships in these things, but you're pretty sure you know how to get it out.")
+ playsound(user.loc, 'sound/effects/glassbr1.ogg', 100, 1)
+ new /obj/vehicle/ridden/lavaboat/dragon(get_turf(src))
+ qdel(src)
+
+/obj/vehicle/ridden/lavaboat/dragon
+ name = "mysterious boat"
+ desc = "This boat moves where you will it, without the need for an oar."
+ key_type = null
+
+/obj/vehicle/ridden/lavaboat/dragon/Initialize(mapload)
+ . = ..()
+ AddElement(/datum/element/ridable, /datum/component/riding/vehicle/lavaboat/dragonboat)
+
+
+//all other stuff
+
+/obj/item/oar
+ name = "oar"
+ icon = 'icons/obj/vehicles/vehicles.dmi'
+ icon_state = "oar"
+ item_state = "rods"
+ desc = "Not to be confused with the kind Research hassles you for."
+ force = 12
+ w_class = WEIGHT_CLASS_NORMAL
+ resistance_flags = LAVA_PROOF | FIRE_PROOF
+
+/datum/crafting_recipe/oar
+ name = "goliath bone oar"
+ result = /obj/item/oar
+ reqs = list(/obj/item/stack/sheet/bone = 2)
+ time = 15
+ category = CAT_PRIMAL
+
+/datum/crafting_recipe/boat
+ name = "goliath hide boat"
+ result = /obj/vehicle/ridden/lavaboat
+ reqs = list(/obj/item/stack/sheet/animalhide/goliath_hide = 3)
+ time = 50
+ category = CAT_PRIMAL
+
diff --git a/code/modules/vehicle/motorcycle.dm b/code/modules/vehicle/motorcycle.dm
index abcc0dc4938..dd3aac566cd 100644
--- a/code/modules/vehicle/motorcycle.dm
+++ b/code/modules/vehicle/motorcycle.dm
@@ -1,35 +1,26 @@
-/obj/vehicle/motorcycle
+/obj/vehicle/ridden/motorcycle
name = "motorcycle"
desc = "A fast and highly maneuverable vehicle."
icon = 'icons/obj/vehicles/motorcycle.dmi'
icon_state = "motorcycle_4dir"
- generic_pixel_x = 0
- generic_pixel_y = 4
- vehicle_move_delay = 0.25 SECONDS
var/mutable_appearance/bikecover
-/obj/vehicle/motorcycle/Initialize(mapload)
+/obj/vehicle/ridden/motorcycle/Initialize(mapload)
. = ..()
- bikecover = mutable_appearance(icon, "motorcycle_4dir_overlay", ABOVE_MOB_LAYER)
+ AddElement(/datum/element/ridable, /datum/component/riding/vehicle/motorcycle)
+ if(!bikecover)
+ bikecover = mutable_appearance(icon, "motorcycle_4dir_overlay", ABOVE_MOB_LAYER)
-/obj/vehicle/motorcycle/Destroy()
+/obj/vehicle/ridden/motorcycle/Destroy()
bikecover = null
return ..()
-/obj/vehicle/motorcycle/update_overlays()
+/obj/vehicle/ridden/motorcycle/update_overlays()
. = ..()
if(!has_buckled_mobs())
return .
. += bikecover
-
-/obj/vehicle/motorcycle/handle_vehicle_icons()
- update_icon(UPDATE_OVERLAYS)
-
-
-/obj/vehicle/motorcycle/handle_vehicle_layer()
- return
-
diff --git a/code/modules/vehicle/ridden.dm b/code/modules/vehicle/ridden.dm
new file mode 100644
index 00000000000..bb29b619179
--- /dev/null
+++ b/code/modules/vehicle/ridden.dm
@@ -0,0 +1,68 @@
+/obj/vehicle/ridden
+ name = "ridden vehicle"
+ can_buckle = TRUE
+ max_buckled_mobs = 1
+ buckle_lying = 0
+ pass_flags_self = PASSTABLE
+ COOLDOWN_DECLARE(message_cooldown)
+
+/obj/vehicle/ridden/examine(mob/user)
+ . = ..()
+ if(key_type)
+ if(!inserted_key)
+ . += span_notice("Put a key inside it by clicking it with the key.")
+ else
+ . += span_notice("Alt-click [src] to remove the key.")
+
+/obj/vehicle/ridden/generate_action_type(actiontype)
+ var/datum/action/vehicle/ridden/A = ..()
+ . = A
+ if(istype(A))
+ A.vehicle_ridden_target = src
+
+/obj/vehicle/ridden/post_unbuckle_mob(mob/living/M)
+ remove_occupant(M)
+ return ..()
+
+/obj/vehicle/ridden/post_buckle_mob(mob/living/M)
+ add_occupant(M)
+ return ..()
+
+/obj/vehicle/ridden/attackby(obj/item/I, mob/user, params)
+ if(!key_type || is_key(inserted_key) || !is_key(I))
+ return ..()
+ if(!user.transfer_item_to_loc(I, src))
+ to_chat(user, span_warning("[I] seems to be stuck to your hand!"))
+ return
+ to_chat(user, span_notice("You insert \the [I] into \the [src]."))
+ if(inserted_key) //just in case there's an invalid key
+ inserted_key.forceMove(drop_location())
+ inserted_key = I
+ return ATTACK_CHAIN_PROCEED
+
+/obj/vehicle/ridden/AltClick(mob/user)
+ if(!inserted_key)
+ return
+ if(!is_occupant(user))
+ to_chat(user, span_warning("You must be riding the [src] to remove [src]'s key!"))
+ return
+ to_chat(user, span_notice("You remove \the [inserted_key] from \the [src]."))
+ inserted_key.forceMove_turf()
+ user.put_in_hands(inserted_key)
+ inserted_key = null
+
+/obj/vehicle/ridden/user_buckle_mob(mob/living/M, mob/user, check_loc = TRUE)
+ if(!in_range(user, src) || !in_range(M, src))
+ return FALSE
+ return ..(M, user, FALSE)
+
+/obj/vehicle/ridden/buckle_mob(mob/living/M, force = FALSE, check_loc = TRUE)
+ if(!force && occupant_amount() >= max_occupants)
+ return FALSE
+
+ var/response = SEND_SIGNAL(M, COMSIG_VEHICLE_RIDDEN, src)
+ if(response & EJECT_FROM_VEHICLE)
+ return FALSE
+
+ return ..()
+
diff --git a/code/modules/vehicle/secway.dm b/code/modules/vehicle/secway.dm
index 87e709a6297..a229bf534b4 100644
--- a/code/modules/vehicle/secway.dm
+++ b/code/modules/vehicle/secway.dm
@@ -1,4 +1,4 @@
-/obj/vehicle/secway
+/obj/vehicle/ridden/secway
name = "secway"
desc = "A brave security cyborg gave its life to help you look like a complete tool."
icon_state = "secway"
@@ -6,11 +6,7 @@
armor = list("melee" = 20, "bullet" = 15, "laser" = 10, "energy" = 0, "bomb" = 30, "bio" = 0, "rad" = 0, "fire" = 60, "acid" = 60)
key_type = /obj/item/key/security
integrity_failure = 50
- generic_pixel_x = 0
- generic_pixel_y = 4
- vehicle_move_delay = 0.25 SECONDS
-
-/obj/item/key/security
- desc = "A keyring with a small steel key, and a rubber stun baton accessory."
- icon_state = "keysec"
+/obj/vehicle/ridden/secway/Initialize(mapload)
+ . = ..()
+ AddElement(/datum/element/ridable, /datum/component/riding/vehicle/secway)
diff --git a/code/modules/vehicle/snowmobile.dm b/code/modules/vehicle/snowmobile.dm
index 830e9b29783..21740a3fea9 100644
--- a/code/modules/vehicle/snowmobile.dm
+++ b/code/modules/vehicle/snowmobile.dm
@@ -1,26 +1,23 @@
-/obj/vehicle/snowmobile
+/obj/vehicle/ridden/snowmobile
name = "red snowmobile"
desc = "Wheeeeeeeeeeee."
icon = 'icons/obj/vehicles/vehicles.dmi'
icon_state = "snowmobile"
- move_speed = 0
key_type = /obj/item/key/snowmobile
- generic_pixel_x = 0
- generic_pixel_y = 4
-/obj/vehicle/snowmobile/blue
+/obj/vehicle/ridden/snowmobile/Initialize(mapload)
+ . = ..()
+ AddElement(/datum/element/ridable, /datum/component/riding/vehicle/snowmobile)
+
+/obj/vehicle/ridden/snowmobile/blue
name = "blue snowmobile"
icon_state = "bluesnowmobile"
-/obj/vehicle/snowmobile/key/Initialize(mapload)
+/obj/vehicle/ridden/snowmobile/key/Initialize(mapload)
. = ..()
inserted_key = new /obj/item/key/snowmobile(src)
-/obj/vehicle/snowmobile/blue/key/Initialize(mapload)
+/obj/vehicle/ridden/snowmobile/blue/key/Initialize(mapload)
. = ..()
inserted_key = new /obj/item/key/snowmobile(src)
-/obj/item/key/snowmobile
- name = "snowmobile key"
- desc = "A keyring with a small steel key, and tag with a red cross on it; clearly it's not implying you're going to the hospital for this..."
- icon_state = "keydoc" //get a better icon, sometime.
diff --git a/code/modules/vehicle/speedbike.dm b/code/modules/vehicle/speedbike.dm
index 72e6e58cec1..6e3943d8989 100644
--- a/code/modules/vehicle/speedbike.dm
+++ b/code/modules/vehicle/speedbike.dm
@@ -1,70 +1,35 @@
-/obj/vehicle/space/speedbike
+/obj/vehicle/ridden/speedbike
name = "Speedbike"
icon = 'icons/obj/vehicles/bike.dmi'
icon_state = "speedbike_blue"
- vehicle_move_delay = 0.15 SECONDS
var/overlay_state = "cover_blue"
var/mutable_appearance/cover_overlay
-/obj/vehicle/space/speedbike/Initialize(mapload)
+/obj/vehicle/ridden/speedbike/Initialize(mapload)
. = ..()
cover_overlay = mutable_appearance(icon, overlay_state, ABOVE_MOB_LAYER)
+ AddElement(/datum/element/ridable, /datum/component/riding/vehicle/speedbike)
-/obj/vehicle/space/speedbike/Destroy()
+/obj/vehicle/ridden/speedbike/Destroy()
cover_overlay = null
return ..()
-/obj/vehicle/space/speedbike/update_overlays()
+/obj/vehicle/ridden/speedbike/update_overlays()
. = ..()
if(!has_buckled_mobs())
return .
. += cover_overlay
-/obj/vehicle/space/speedbike/handle_vehicle_icons()
- update_icon(UPDATE_OVERLAYS)
-
-/obj/vehicle/space/speedbike/Move(atom/newloc, direct = NONE, glide_size_override = 0, update_dir = TRUE)
+/obj/vehicle/ridden/speedbike/Move(atom/newloc, direct = NONE, glide_size_override = 0, update_dir = TRUE)
if(has_buckled_mobs())
new /obj/effect/temp_visual/dir_setting/speedbike_trail(loc, direct)
return ..()
-
-/obj/vehicle/space/speedbike/handle_vehicle_layer()
- return
-
-
-/obj/vehicle/space/speedbike/handle_vehicle_offsets()
- switch(dir)
- if(NORTH, SOUTH)
- pixel_x = -16
- pixel_y = -16
- if(EAST, WEST)
- pixel_x = -18
- pixel_y = 0
- if(!has_buckled_mobs())
- return
- for(var/mob/living/buckled_mob as anything in buckled_mobs)
- buckled_mob.setDir(dir)
- switch(dir)
- if(NORTH)
- buckled_mob.pixel_x = 0
- buckled_mob.pixel_y = -8
- if(SOUTH)
- buckled_mob.pixel_x = 0
- buckled_mob.pixel_y = 4
- if(EAST)
- buckled_mob.pixel_x = -10
- buckled_mob.pixel_y = 5
- if(WEST)
- buckled_mob.pixel_x = 10
- buckled_mob.pixel_y = 5
-
-
-/obj/vehicle/space/speedbike/red
+/obj/vehicle/ridden/speedbike/red
icon_state = "speedbike_red"
overlay_state = "cover_red"
diff --git a/code/modules/vehicle/sportscar.dm b/code/modules/vehicle/sportscar.dm
index a93b4e310a5..97612b154ca 100644
--- a/code/modules/vehicle/sportscar.dm
+++ b/code/modules/vehicle/sportscar.dm
@@ -1,18 +1,21 @@
-/obj/vehicle/car
+/obj/vehicle/ridden/car
name = "sports car"
desc = "A very luxurious vehicle."
icon = 'icons/obj/vehicles/sportscar.dmi'
icon_state = "sportscar"
- vehicle_move_delay = 0.25 SECONDS
pull_push_slowdown = 2
+/obj/vehicle/ridden/car/Initialize(mapload)
+ . = ..()
+ AddElement(/datum/element/ridable, /datum/component/riding/vehicle/car)
+
#define CAR_COVER_NORTH 1
#define CAR_COVER_SOUTH 2
#define CAR_COVER_EAST 3
#define CAR_COVER_WEST 4
-/obj/vehicle/car/update_overlays()
+/obj/vehicle/ridden/car/update_overlays()
. = ..()
if(!has_buckled_mobs())
return .
@@ -37,32 +40,3 @@
#undef CAR_COVER_SOUTH
#undef CAR_COVER_EAST
#undef CAR_COVER_WEST
-
-
-/obj/vehicle/car/handle_vehicle_icons()
- update_icon(UPDATE_OVERLAYS)
-
-
-/obj/vehicle/car/handle_vehicle_offsets()
- if(!has_buckled_mobs())
- return
- for(var/mob/living/buckled_mob as anything in buckled_mobs)
- buckled_mob.setDir(dir)
- switch(dir)
- if(NORTH)
- buckled_mob.pixel_x = 2
- buckled_mob.pixel_y = 20
- if(EAST)
- buckled_mob.pixel_x = 20
- buckled_mob.pixel_y = 23
- if(SOUTH)
- buckled_mob.pixel_x = 20
- buckled_mob.pixel_y = 27
- if(WEST)
- buckled_mob.pixel_x = 34
- buckled_mob.pixel_y = 10
-
-
-/obj/vehicle/car/handle_vehicle_layer()
- return // we got custom layers, dont worry
-
diff --git a/code/modules/vehicle/vehicle.dm b/code/modules/vehicle/vehicle.dm
deleted file mode 100644
index 2acd6e49b35..00000000000
--- a/code/modules/vehicle/vehicle.dm
+++ /dev/null
@@ -1,258 +0,0 @@
-
-/obj/vehicle
- name = "vehicle"
- desc = "A basic vehicle, vroom"
- icon = 'icons/obj/vehicles/vehicles.dmi'
- icon_state = "scooter"
- density = TRUE
- anchored = FALSE
- pass_flags_self = PASSVEHICLE
- can_buckle = TRUE
- pull_push_slowdown = 1
- buckle_lying = 0
- max_integrity = 300
- armor = list("melee" = 30, "bullet" = 30, "laser" = 30, "energy" = 0, "bomb" = 30, "bio" = 0, "rad" = 0, "fire" = 60, "acid" = 60)
- /// Item required for the vehicle to be inserted into the ignition.
- var/obj/item/key_type
- /// Whehter our key should be in mob hands, rather than in the vehicle ignintion.
- var/key_in_hands = FALSE
- /// Currently inserted key.
- var/obj/item/key/inserted_key
- /// To allow non-space vehicles to move in no gravity or not, mostly for adminbus.
- var/needs_gravity = FALSE
- /// All dirs apply this pixel_x for the driver.
- var/generic_pixel_x = 0
- /// All dirs apply this pixel_y for the driver.
- var/generic_pixel_y = 0
- /// If we have a xenobio red potion applied to us.
- var/potion_boosted = FALSE
- /// Delay between movements in deciseconds, lower = faster, higher = slower
- var/vehicle_move_delay = 0.35 SECONDS
- COOLDOWN_DECLARE(vehicle_move_cooldown)
-
-
-/obj/vehicle/Initialize(mapload)
- . = ..()
- handle_vehicle_layer()
- handle_vehicle_icons()
-
-
-/obj/vehicle/Destroy()
- QDEL_NULL(inserted_key)
- return ..()
-
-
-/obj/vehicle/examine(mob/user)
- . = ..()
- if(key_type)
- if(key_in_hands)
- . += span_info("[src] requires the [initial(key_type.name)] to be held in hands to start driving.")
- else
- if(inserted_key)
- . += span_info("Alt-click [src] to remove the key.")
- else
- . += span_info("[src] requires the [initial(key_type.name)] to be inserted into ignintion to start driving.")
-
- if(resistance_flags & ON_FIRE)
- . += span_warning("It's on fire!")
- var/healthpercent = obj_integrity/max_integrity * 100
- switch(healthpercent)
- if(50 to 99)
- . += span_notice("It looks slightly damaged.")
- if(25 to 50)
- . += span_notice("It appears heavily damaged.")
- if(0 to 25)
- . += span_warning("It's falling apart!")
-
-
-/obj/vehicle/attackby(obj/item/I, mob/user, params)
- if(user.a_intent == INTENT_HARM)
- return ..()
-
- if(key_type && I.type == key_type)
- add_fingerprint(user)
- if(inserted_key)
- to_chat(user, span_warning("The [name] already has [inserted_key.name] in the ignition!"))
- return ATTACK_CHAIN_PROCEED
- if(!user.drop_transfer_item_to_loc(I, src))
- return ..()
- inserted_key = I
- to_chat(user, span_notice("You have inserted [I] into [src]'s ignintion."))
- return ATTACK_CHAIN_BLOCKED_ALL
-
- return ..()
-
-
-/obj/vehicle/AltClick(mob/living/user)
- if(!istype(user) || !Adjacent(user))
- return
- if(user.incapacitated() || HAS_TRAIT(user, TRAIT_HANDS_BLOCKED))
- to_chat(user, span_warning("You can't do that right now!"))
- return
- if(!inserted_key)
- to_chat(user, span_warning("[src] has no inserted keys!"))
- return
- if(!(user in buckled_mobs))
- to_chat(user, span_warning("You must be riding [src] to remove [src]'s key!"))
- return
- to_chat(user, span_notice("You remove [inserted_key] from [src]."))
- inserted_key.forceMove_turf()
- user.put_in_hands(inserted_key, ignore_anim = FALSE)
- inserted_key = null
-
-
-/obj/vehicle/proc/keycheck(mob/user, provide_feedback = TRUE)
- if(!key_type)
- return TRUE
- if(key_in_hands)
- if(!(user.l_hand && user.l_hand.type == key_type) && !(user.r_hand && user.r_hand.type == key_type))
- if(provide_feedback)
- to_chat(user, span_warning("You'll need the [initial(key_type.name)] in one of your hands to drive [src]!"))
- return FALSE
- return TRUE
- if(!(inserted_key && inserted_key.type == key_type))
- if(provide_feedback)
- to_chat(user, span_warning("[src] has no inserted keys!"))
- return FALSE
- return TRUE
-
-
-/// Checks to see if we've been hit with a red xenobio potion to make us faster.
-/obj/vehicle/proc/check_potion(obj/item/slimepotion/speed/speed_potion, mob/living/user)
- if(potion_boosted)
- to_chat(user, span_warning("[user] has already been coated with red, that's as fast as it'll go!"))
- return FALSE
- if(has_buckled_mobs()) // effect won't take place till the next time someone mounts it, so just prevent that situation
- to_chat(user, span_warning("It's too dangerous to smear [speed_potion] on [src] while it's being ridden!"))
- return FALSE
- var/speed_limit = round(CONFIG_GET(number/movedelay/run_delay) + get_config_multiplicative_speed_by_path(/mob/living/carbon/human), 0.01)
- if(vehicle_move_delay <= 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_warning("[src] can't be made any faster!"))
- return FALSE
- vehicle_move_delay = speed_limit
- potion_boosted = TRUE
- to_chat(user, span_notice("You slather the red gunk over [src], making it faster."))
- remove_atom_colour(WASHABLE_COLOUR_PRIORITY)
- add_atom_colour(COLOR_RED, FIXED_COLOUR_PRIORITY)
- qdel(speed_potion)
- return TRUE
-
-
-//APPEARANCE
-/obj/vehicle/proc/handle_vehicle_layer()
- if(!has_buckled_mobs())
- layer = OBJ_LAYER
- return
- if(dir == SOUTH)
- layer = ABOVE_MOB_LAYER
- else
- layer = OBJ_LAYER
-
-
-/// Used to update vehicle icons if needed
-/obj/vehicle/proc/handle_vehicle_icons()
- return
-
-
-//Override this to set your vehicle's various pixel offsets
-//if they differ between directions, otherwise use the
-//generic variables
-/obj/vehicle/proc/handle_vehicle_offsets()
- if(!has_buckled_mobs())
- return
- for(var/mob/living/buckled_mob as anything in buckled_mobs)
- buckled_mob.setDir(dir)
- buckled_mob.pixel_x = generic_pixel_x
- buckled_mob.pixel_y = generic_pixel_y
-
-
-/// Used to update dir of buckled mobs on Move().
-/obj/vehicle/proc/handle_buckled_dir()
- for(var/mob/living/buckled_mob as anything in buckled_mobs)
- buckled_mob.setDir(dir)
-
-
-/obj/item/key
- name = "key"
- desc = "A small grey key."
- icon = 'icons/obj/vehicles/vehicles.dmi'
- icon_state = "key"
- w_class = WEIGHT_CLASS_TINY
-
-
-//BUCKLE HOOKS
-/obj/vehicle/post_buckle_mob(mob/living/target)
- handle_vehicle_layer()
- handle_vehicle_offsets()
- handle_vehicle_icons()
-
-
-/obj/vehicle/post_unbuckle_mob(mob/living/target)
- target.pixel_x = target.base_pixel_x + target.body_position_pixel_x_offset
- target.pixel_y = target.base_pixel_y + target.body_position_pixel_y_offset
- handle_vehicle_offsets()
- handle_vehicle_layer()
- handle_vehicle_icons()
-
-
-/obj/vehicle/bullet_act(obj/item/projectile/Proj)
- if(!has_buckled_mobs())
- return
- for(var/mob/living/buckled_mob as anything in buckled_mobs)
- buckled_mob.bullet_act(Proj)
-
-
-//MOVEMENT
-
-/obj/vehicle/relaymove(mob/user, direction)
- if(!COOLDOWN_FINISHED(src, vehicle_move_cooldown) || !has_buckled_mobs() || user != buckled_mobs[1])
- return FALSE
-
- var/turf/next_step = get_step(src, direction)
- if(!next_step || !isturf(loc) || !Process_Spacemove(direction) || !keycheck(user))
- COOLDOWN_START(src, vehicle_move_cooldown, 0.5 SECONDS)
- return FALSE
-
- if(user.incapacitated())
- unbuckle_mob(user)
- return FALSE
-
- var/add_delay = vehicle_move_delay
-
- . = Move(next_step, direction)
- if(ISDIAGONALDIR(direction) && loc == next_step)
- add_delay *= sqrt(2)
-
- set_glide_size(DELAY_TO_GLIDE_SIZE(add_delay))
- COOLDOWN_START(src, vehicle_move_cooldown, add_delay)
-
-
-/obj/vehicle/Move(atom/newloc, direct = NONE, glide_size_override = 0, update_dir = TRUE)
- . = ..()
- handle_vehicle_layer()
- handle_vehicle_offsets()
- handle_vehicle_icons()
- handle_buckled_dir()
-
-
-/obj/vehicle/Bump(atom/bumped_atom)
- . = ..()
- if(. || !has_buckled_mobs() || !istype(bumped_atom, /obj/machinery/door))
- return .
- for(var/mob/living/buckled_mob as anything in buckled_mobs)
- bumped_atom.Bumped(buckled_mob)
-
-
-/obj/vehicle/Process_Spacemove(movement_dir = NONE, continuous_move = FALSE)
- if(needs_gravity)
- return TRUE
- return ..()
-
-
-/obj/vehicle/space
- pressure_resistance = INFINITY
-
-
-/obj/vehicle/space/Process_Spacemove(movement_dir = NONE, continuous_move = FALSE)
- return TRUE
-
diff --git a/code/modules/vehicle/vehicle_actions.dm b/code/modules/vehicle/vehicle_actions.dm
new file mode 100644
index 00000000000..6eead10e5cf
--- /dev/null
+++ b/code/modules/vehicle/vehicle_actions.dm
@@ -0,0 +1,266 @@
+//VEHICLE DEFAULT HANDLING
+
+/**
+ * ## generate_actions
+ *
+ * You override this with initialize_passenger_action_type and initialize_controller_action_type calls
+ * To give passengers actions when they enter the vehicle.
+ * Read the documentation on the aforementioned procs to learn the difference
+ */
+/obj/vehicle/proc/generate_actions()
+ return
+
+/**
+ * ## generate_action_type
+ *
+ * A small proc to properly set up each action path.
+ * args:
+ * * actiontype: typepath of the action the proc sets up.
+ * returns created and set up action instance
+ */
+/obj/vehicle/proc/generate_action_type(actiontype)
+ var/datum/action/vehicle/A = new actiontype
+ if(!istype(A))
+ return
+ A.vehicle_target = src
+ return A
+
+/**
+ * ## initialize_passenger_action_type
+ *
+ * Gives any passenger that enters the mech this action.
+ * They will lose it when they disembark.
+ * args:
+ * * actiontype: typepath of the action you want to give occupants.
+ */
+/obj/vehicle/proc/initialize_passenger_action_type(actiontype)
+ autogrant_actions_passenger += actiontype
+ for(var/i in occupants)
+ grant_passenger_actions(i) //refresh
+
+/**
+ * ## destroy_passenger_action_type
+ *
+ * Removes this action type from all occupants and stops autogranting it
+ * args:
+ * * actiontype: typepath of the action you want to remove from occupants and the autogrant list.
+ */
+/obj/vehicle/proc/destroy_passenger_action_type(actiontype)
+ autogrant_actions_passenger -= actiontype
+ for(var/i in occupants)
+ remove_action_type_from_mob(actiontype, i)
+
+/**
+ * ## initialize_controller_action_type
+ *
+ * Gives any passenger that enters the vehicle this action... IF they have the correct vehicle control flag.
+ * This is used so passengers cannot press buttons only drivers should have, for example.
+ * args:
+ * * actiontype: typepath of the action you want to give occupants.
+ */
+/obj/vehicle/proc/initialize_controller_action_type(actiontype, control_flag)
+ LAZYINITLIST(autogrant_actions_controller["[control_flag]"])
+ autogrant_actions_controller["[control_flag]"] += actiontype
+ for(var/i in occupants)
+ grant_controller_actions(i) //refresh
+
+/**
+ * ## destroy_controller_action_type
+ *
+ * As the name implies, removes the actiontype from autogrant and removes it from all occupants
+ * args:
+ * * actiontype: typepath of the action you want to remove from occupants and autogrant.
+ */
+/obj/vehicle/proc/destroy_controller_action_type(actiontype, control_flag)
+ autogrant_actions_controller["[control_flag]"] -= actiontype
+ UNSETEMPTY(autogrant_actions_controller["[control_flag]"])
+ for(var/i in occupants)
+ remove_action_type_from_mob(actiontype, i)
+
+/**
+ * ## grant_action_type_to_mob
+ *
+ * As on the tin, it does all the annoying small stuff and sanity needed
+ * to GRANT an action to a mob.
+ * args:
+ * * actiontype: typepath of the action you want to give to grant_to.
+ * * grant_to: the mob we're giving actiontype to
+ * returns TRUE if successfully granted
+ */
+/obj/vehicle/proc/grant_action_type_to_mob(actiontype, mob/grant_to)
+ if(isnull(LAZYACCESS(occupants, grant_to)) || !actiontype)
+ return FALSE
+ LAZYINITLIST(occupant_actions[grant_to])
+ if(occupant_actions[grant_to][actiontype])
+ return TRUE
+ var/datum/action/action = generate_action_type(actiontype)
+ action.Grant(grant_to)
+ occupant_actions[grant_to][action.type] = action
+ return TRUE
+
+/**
+ * ## remove_action_type_from_mob
+ *
+ * As on the tin, it does all the annoying small stuff and sanity needed
+ * to REMOVE an action to a mob.
+ * args:
+ * * actiontype: typepath of the action you want to give to grant_to.
+ * * take_from: the mob we're taking actiontype to
+ * returns TRUE if successfully removed
+ */
+/obj/vehicle/proc/remove_action_type_from_mob(actiontype, mob/take_from)
+ if(isnull(LAZYACCESS(occupants, take_from)) || !actiontype)
+ return FALSE
+ LAZYINITLIST(occupant_actions[take_from])
+ if(occupant_actions[take_from][actiontype])
+ var/datum/action/action = occupant_actions[take_from][actiontype]
+ // Actions don't dissipate on removal, they just sit around assuming they'll be reusued
+ // Gotta qdel
+ qdel(action)
+ occupant_actions[take_from] -= actiontype
+ return TRUE
+
+/**
+ * ## grant_passenger_actions
+ *
+ * Called on every passenger that enters the vehicle, goes through the list of actions it needs to give...
+ * and does that.
+ * args:
+ * * grant_to: mob that needs to get every action the vehicle grants
+ */
+/obj/vehicle/proc/grant_passenger_actions(mob/grant_to)
+ for(var/v in autogrant_actions_passenger)
+ grant_action_type_to_mob(v, grant_to)
+
+/**
+ * ## remove_passenger_actions
+ *
+ * Called on every passenger that exits the vehicle, goes through the list of actions it needs to remove...
+ * and does that.
+ * args:
+ * * take_from: mob that needs to get every action the vehicle grants
+ */
+/obj/vehicle/proc/remove_passenger_actions(mob/take_from)
+ for(var/v in autogrant_actions_passenger)
+ remove_action_type_from_mob(v, take_from)
+
+/obj/vehicle/proc/grant_controller_actions(mob/M)
+ if(!istype(M) || isnull(LAZYACCESS(occupants, M)))
+ return FALSE
+ for(var/i in GLOB.bitflags)
+ if(occupants[M] & i)
+ grant_controller_actions_by_flag(M, i)
+ return TRUE
+
+/obj/vehicle/proc/remove_controller_actions(mob/M)
+ if(!istype(M) || isnull(LAZYACCESS(occupants, M)))
+ return FALSE
+ for(var/i in GLOB.bitflags)
+ remove_controller_actions_by_flag(M, i)
+ return TRUE
+
+/obj/vehicle/proc/grant_controller_actions_by_flag(mob/M, flag)
+ if(!istype(M))
+ return FALSE
+ for(var/v in autogrant_actions_controller["[flag]"])
+ grant_action_type_to_mob(v, M)
+ return TRUE
+
+/obj/vehicle/proc/remove_controller_actions_by_flag(mob/M, flag)
+ if(!istype(M))
+ return FALSE
+ for(var/v in autogrant_actions_controller["[flag]"])
+ remove_action_type_from_mob(v, M)
+ return TRUE
+
+/obj/vehicle/proc/cleanup_actions_for_mob(mob/M)
+ if(!istype(M))
+ return FALSE
+ for(var/path in occupant_actions[M])
+ stack_trace("Leftover action type [path] in vehicle type [type] for mob type [M.type] - THIS SHOULD NOT BE HAPPENING!")
+ var/datum/action/action = occupant_actions[M][path]
+ action.Remove(M)
+ occupant_actions[M] -= path
+ occupant_actions -= M
+ return TRUE
+
+/***************** ACTION DATUMS *****************/
+
+/datum/action/vehicle
+ check_flags = AB_CHECK_HANDS_BLOCKED | AB_CHECK_IMMOBILE | AB_CHECK_CONSCIOUS
+ button_icon = 'icons/mob/actions/actions_vehicle.dmi'
+ button_icon_state = "vehicle_eject"
+ var/obj/vehicle/vehicle_target
+
+/datum/action/vehicle/Destroy()
+ vehicle_target = null
+ return ..()
+
+/datum/action/vehicle/ridden
+ var/obj/vehicle/ridden/vehicle_ridden_target
+
+
+/datum/action/vehicle/ridden/ambulance/ambulance_alarm
+ name = "Toggle Sirens"
+ icon_icon = 'icons/obj/vehicles/vehicles.dmi'
+ button_icon_state = "docwagon2"
+ check_flags = AB_CHECK_HANDS_BLOCKED|AB_CHECK_CONSCIOUS|AB_CHECK_INCAPACITATED
+ COOLDOWN_DECLARE(ability_cooldown)
+ var/cooldown_time = 4 SECONDS
+
+/datum/action/vehicle/ridden/ambulance/ambulance_alarm/Trigger(left_click = TRUE)
+ if(!..())
+ return FALSE
+
+ var/obj/vehicle/ridden/ambulance/A = vehicle_ridden_target
+
+ if(!istype(A) || !A.soundloop)
+ return FALSE
+
+ if(!COOLDOWN_FINISHED(src, ability_cooldown))
+ return FALSE
+
+ COOLDOWN_START(src, ability_cooldown, cooldown_time)
+
+ if(A.soundloop.muted)
+ A.soundloop.start()
+ A.set_light_on(TRUE)
+ else
+ A.soundloop.stop()
+ A.set_light_on(FALSE)
+
+/datum/looping_sound/ambulance_alarm
+ start_length = 0
+ mid_sounds = list('sound/items/weeoo1.ogg' = 1)
+ mid_length = 14
+ volume = 100
+
+
+/*
+
+/datum/action/vehicle/sealed
+ check_flags = AB_CHECK_IMMOBILE | AB_CHECK_CONSCIOUS
+ var/obj/vehicle/sealed/vehicle_entered_target
+
+/datum/action/vehicle/sealed/Destroy()
+ vehicle_entered_target = null
+ return ..()
+
+/datum/action/vehicle/sealed/climb_out
+ name = "Climb Out"
+ desc = "Climb out of your vehicle!"
+ button_icon_state = "car_eject"
+
+/datum/action/vehicle/sealed/climb_out/Trigger(trigger_flags)
+ if(..() && istype(vehicle_entered_target))
+ vehicle_entered_target.mob_try_exit(owner, owner)
+
+/datum/action/vehicle/sealed/remove_key
+ name = "Remove key"
+ desc = "Take your key out of the vehicle's ignition."
+ button_icon_state = "car_removekey"
+
+/datum/action/vehicle/sealed/remove_key/Trigger(trigger_flags)
+ vehicle_entered_target.remove_key(owner)
+
+*/
diff --git a/code/modules/vehicle/vehicle_key.dm b/code/modules/vehicle/vehicle_key.dm
new file mode 100644
index 00000000000..471d12f5c8c
--- /dev/null
+++ b/code/modules/vehicle/vehicle_key.dm
@@ -0,0 +1,28 @@
+/obj/item/key
+ name = "key"
+ desc = "A small grey key."
+ icon = 'icons/obj/vehicles/vehicles.dmi'
+ icon_state = "key"
+ w_class = WEIGHT_CLASS_TINY
+
+/obj/item/key/atv
+ name = "ATV key"
+ desc = "A small grey key for starting and operating ATVs."
+
+/obj/item/key/ambulance
+ name = "ambulance key"
+ desc = "A keyring with a small steel key, and tag with a red cross on it."
+ icon_state = "keydoc"
+
+/obj/item/key/janitor
+ desc = "A keyring with a small steel key, and a pink fob reading \"Pussy Wagon\"."
+ icon_state = "keyjanitor"
+
+/obj/item/key/security
+ desc = "A keyring with a small steel key, and a rubber stun baton accessory."
+ icon_state = "keysec"
+
+/obj/item/key/snowmobile
+ name = "snowmobile key"
+ desc = "A keyring with a small steel key, and tag with a red cross on it; clearly it's not implying you're going to the hospital for this..."
+ icon_state = "keydoc" //get a better icon, sometime.
diff --git a/icons/mob/actions/actions_vehicle.dmi b/icons/mob/actions/actions_vehicle.dmi
new file mode 100644
index 00000000000..3ad64040513
Binary files /dev/null and b/icons/mob/actions/actions_vehicle.dmi differ
diff --git a/icons/obj/vehicles/vehicles.dmi b/icons/obj/vehicles/vehicles.dmi
index 79aef59c608..298f220411d 100644
Binary files a/icons/obj/vehicles/vehicles.dmi and b/icons/obj/vehicles/vehicles.dmi differ
diff --git a/paradise.dme b/paradise.dme
index 260612d98e6..e3f6b4b9969 100644
--- a/paradise.dme
+++ b/paradise.dme
@@ -142,6 +142,7 @@
#include "code\__DEFINES\typeids.dm"
#include "code\__DEFINES\uplink_types.dm"
#include "code\__DEFINES\vampire_defines.dm"
+#include "code\__DEFINES\vehicles.dm"
#include "code\__DEFINES\verb_manager.dm"
#include "code\__DEFINES\vv.dm"
#include "code\__DEFINES\wires.dm"
@@ -465,6 +466,9 @@
#include "code\datums\components\transforming.dm"
#include "code\datums\components\twohanded.dm"
#include "code\datums\components\wet_floor.dm"
+#include "code\datums\components\riding\riding.dm"
+#include "code\datums\components\riding\riding_mob.dm"
+#include "code\datums\components\riding\riding_vehicle.dm"
#include "code\datums\diseases\_disease.dm"
#include "code\datums\diseases\_MobProcs.dm"
#include "code\datums\diseases\appendicitis.dm"
@@ -543,6 +547,7 @@
#include "code\datums\elements\light_blocking.dm"
#include "code\datums\elements\movetype_handler.dm"
#include "code\datums\elements\openspace_item_click_handler.dm"
+#include "code\datums\elements\ridable.dm"
#include "code\datums\elements\simple_flying.dm"
#include "code\datums\elements\squish.dm"
#include "code\datums\elements\strippable.dm"
@@ -3156,15 +3161,19 @@
#include "code\modules\tgui_input\say_modal\tgui_say_typing.dm"
#include "code\modules\tooltip\tooltip.dm"
#include "code\modules\unit_tests\_unit_tests.dm"
+#include "code\modules\vehicle\_vehicle.dm"
#include "code\modules\vehicle\ambulance.dm"
#include "code\modules\vehicle\atv.dm"
#include "code\modules\vehicle\janicart.dm"
+#include "code\modules\vehicle\lavaboat.dm"
#include "code\modules\vehicle\motorcycle.dm"
+#include "code\modules\vehicle\ridden.dm"
#include "code\modules\vehicle\secway.dm"
#include "code\modules\vehicle\snowmobile.dm"
#include "code\modules\vehicle\speedbike.dm"
#include "code\modules\vehicle\sportscar.dm"
-#include "code\modules\vehicle\vehicle.dm"
+#include "code\modules\vehicle\vehicle_actions.dm"
+#include "code\modules\vehicle\vehicle_key.dm"
#include "code\modules\vote\vote_datum.dm"
#include "code\modules\vote\vote_presets.dm"
#include "code\modules\vote\vote_verb.dm"