diff --git a/code/__DEFINES/dcs/obj_signals.dm b/code/__DEFINES/dcs/obj_signals.dm
index 4b138f7146a..ef15e2dec39 100644
--- a/code/__DEFINES/dcs/obj_signals.dm
+++ b/code/__DEFINES/dcs/obj_signals.dm
@@ -41,3 +41,7 @@
/// from /datum/component/shelver/shelf_items()
#define COMSIG_SHELF_ATTEMPT_PICKUP "shelf_attempt_pickup"
#define SHELF_PICKUP_FAILURE (1 << 0)
+
+/// from /datum/component/supermatter_crystal/proc/consume()
+/// called on the thing consumed, passes the thing which consumed it
+#define COMSIG_SUPERMATTER_CONSUMED "sm_consumed_this"
diff --git a/code/__DEFINES/is_helpers.dm b/code/__DEFINES/is_helpers.dm
index fe3f1cb3f94..7826847a04c 100644
--- a/code/__DEFINES/is_helpers.dm
+++ b/code/__DEFINES/is_helpers.dm
@@ -68,6 +68,8 @@
#define ismecha(A) (istype(A, /obj/mecha))
+#define isclowncar(A) (istype(A, /obj/tgvehicle/sealed/car/clowncar))
+
#define iseffect(A) (istype(A, /obj/effect))
#define isclothing(A) (istype(A, /obj/item/clothing))
diff --git a/code/__DEFINES/tg_cooldowns.dm b/code/__DEFINES/tg_cooldowns.dm
index 874210929a7..7c337ff159d 100644
--- a/code/__DEFINES/tg_cooldowns.dm
+++ b/code/__DEFINES/tg_cooldowns.dm
@@ -43,6 +43,14 @@
#define TIMER_COOLDOWN_END(cd_source, cd_index) LAZYREMOVE(cd_source.cooldowns, cd_index)
+
+/// Checks if a timer based cooldown is NOT finished.
+#define TIMER_COOLDOWN_RUNNING(cd_source, cd_index) LAZYACCESS(cd_source.cooldowns, cd_index)
+
+/// Checks if a timer based cooldown is finished.
+#define TIMER_COOLDOWN_FINISHED(cd_source, cd_index) (!TIMER_COOLDOWN_RUNNING(cd_source, cd_index))
+
+
/*
* Stoppable timer cooldowns.
* Use indexes the same as the regular tiemr cooldowns.
diff --git a/code/__DEFINES/vehicle_defines.dm b/code/__DEFINES/vehicle_defines.dm
index be45aea5fac..70256698fc7 100644
--- a/code/__DEFINES/vehicle_defines.dm
+++ b/code/__DEFINES/vehicle_defines.dm
@@ -27,6 +27,14 @@
/// Do we need a carbon, a silicon, or a small mob, to ride the vehicle?
#define RIDER_CARBON_OR_SILICON_NO_LARGE_MOBS (1<<4)
+///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
+
+
/// The vehicle being ridden requires pixel offsets for all directions
#define RIDING_OFFSET_ALL "ALL"
diff --git a/code/__HELPERS/trait_helpers.dm b/code/__HELPERS/trait_helpers.dm
index 771d639c3fc..11fd577aa94 100644
--- a/code/__HELPERS/trait_helpers.dm
+++ b/code/__HELPERS/trait_helpers.dm
@@ -455,6 +455,7 @@ Remember to update _globalvars/traits.dm if you're adding/removing/renaming trai
#define DOGGO_SPACESUIT "doggo_spacesuit"
#define FLOORCLUWNE "floorcluwne"
#define LOCKDOWN_TRAIT "lockdown"
+#define VEHICLE_TRAIT "vehicle"
#define STAT_TRAIT "stat_trait"
#define TRANSFORMING_TRAIT "transforming"
#define BUCKLING_TRAIT "buckled"
@@ -466,6 +467,7 @@ Remember to update _globalvars/traits.dm if you're adding/removing/renaming trai
#define HOLO_CIGAR "holo_cigar"
#define GLADIATOR "gladiator"
#define PULSEDEMON_TRAIT "pulse_demon"
+#define TRAIT_CLOWN_CAR_SQUISHED "squished_by_car"
/// Mentor observing
#define MENTOR_OBSERVING "mobserving"
#define TIPPED_OVER "tipped_over"
diff --git a/code/_onclick/click.dm b/code/_onclick/click.dm
index a3c60e28e53..3230540eabb 100644
--- a/code/_onclick/click.dm
+++ b/code/_onclick/click.dm
@@ -108,6 +108,10 @@
var/obj/mecha/M = loc
return M.click_action(A, src, params)
+ if(isclowncar(loc) && !modifiers["shift"])
+ var/obj/tgvehicle/sealed/car/clowncar/cc = loc
+ return cc.fire_cannon_at(A, src, params)
+
if(restrained())
RestrainedClickOn(A)
return
diff --git a/code/_onclick/cyborg.dm b/code/_onclick/cyborg.dm
index b81ffe438df..bb88d40eeb7 100644
--- a/code/_onclick/cyborg.dm
+++ b/code/_onclick/cyborg.dm
@@ -49,6 +49,9 @@
if(incapacitated())
return
+ if(HAS_TRAIT(src, TRAIT_HANDS_BLOCKED))
+ return
+
if(next_move >= world.time)
return
diff --git a/code/datums/components/riding/riding_vehicle.dm b/code/datums/components/riding/riding_vehicle.dm
index 7f35a266bf3..c0eebfbb88e 100644
--- a/code/datums/components/riding/riding_vehicle.dm
+++ b/code/datums/components/riding/riding_vehicle.dm
@@ -198,4 +198,8 @@
return
override_allow_spacemove = FALSE
+/datum/component/riding/vehicle/clowncar
+ vehicle_move_delay = 0.6
+ ride_check_flags = RIDER_NEEDS_LEGS | RIDER_NEEDS_ARMS
+
#undef EPSILON
diff --git a/code/datums/uplink_items/uplink_traitor.dm b/code/datums/uplink_items/uplink_traitor.dm
index 709554f2ae2..55651388d52 100644
--- a/code/datums/uplink_items/uplink_traitor.dm
+++ b/code/datums/uplink_items/uplink_traitor.dm
@@ -49,6 +49,21 @@
cost = 5
job = list("Clown")
+/datum/uplink_item/jobspecific/clown_car
+ name = "Clown Car"
+ desc = "The Clown Car is the ultimate transportation method for any worthy clown! \
+ Simply insert your bikehorn and get in, and get ready to have the funniest ride of your life! \
+ You can ram any crew you come across and stuff them into your car, kidnapping them and locking them inside until \
+ someone saves them or they manage to crawl out. Be sure not to ram into any walls or vending machines, as the springloaded seats \
+ are very sensitive. Now with our included lube defense mechanism which will protect you against any angry shitcurity! \
+ Premium features can be unlocked with a cryptographic sequencer!"
+ reference = "CCR"
+ item = /obj/tgvehicle/sealed/car/clowncar
+ cost = 50
+ job = list("Clown")
+ surplus = 0
+ hijack_only = TRUE
+
//mime
/datum/uplink_item/jobspecific/caneshotgun
name = "Cane Shotgun and Assassination Shells"
diff --git a/code/modules/mob/living/carbon/human/human_mob.dm b/code/modules/mob/living/carbon/human/human_mob.dm
index d9f646a0211..6ee44c345eb 100644
--- a/code/modules/mob/living/carbon/human/human_mob.dm
+++ b/code/modules/mob/living/carbon/human/human_mob.dm
@@ -2032,6 +2032,8 @@ Eyes need to have significantly high darksight to shine unless the mob has the X
/mob/living/carbon/human/update_runechat_msg_location()
if(ismecha(loc))
runechat_msg_location = loc.UID()
+ else if(istgvehicle(loc))
+ runechat_msg_location = loc.UID()
else
return ..()
diff --git a/code/modules/mob/living/silicon/robot/robot_mob.dm b/code/modules/mob/living/silicon/robot/robot_mob.dm
index 567a927759c..ab42f7b27ce 100644
--- a/code/modules/mob/living/silicon/robot/robot_mob.dm
+++ b/code/modules/mob/living/silicon/robot/robot_mob.dm
@@ -376,6 +376,12 @@ GLOBAL_LIST_INIT(robot_verbs_default, list(
module_actions.Cut()
return ..()
+/mob/living/silicon/robot/update_runechat_msg_location()
+ if(istgvehicle(loc))
+ runechat_msg_location = loc.UID()
+ else
+ return ..()
+
/mob/living/silicon/robot/proc/pick_module()
if(module)
return
diff --git a/code/modules/power/engines/supermatter/supermatter.dm b/code/modules/power/engines/supermatter/supermatter.dm
index 3b3b6d90ecf..0bebfeb9917 100644
--- a/code/modules/power/engines/supermatter/supermatter.dm
+++ b/code/modules/power/engines/supermatter/supermatter.dm
@@ -857,6 +857,7 @@
Bumped(A)
/obj/machinery/atmospherics/supermatter_crystal/proc/Consume(atom/movable/AM)
+ SEND_SIGNAL(AM, COMSIG_SUPERMATTER_CONSUMED, src)
if(isliving(AM))
var/mob/living/user = AM
if(user.status_flags & GODMODE)
diff --git a/code/modules/vehicle/tg_vehicles/cars/car.dm b/code/modules/vehicle/tg_vehicles/cars/car.dm
new file mode 100644
index 00000000000..dc1374fa288
--- /dev/null
+++ b/code/modules/vehicle/tg_vehicles/cars/car.dm
@@ -0,0 +1,114 @@
+/obj/tgvehicle/sealed/car
+ layer = ABOVE_MOB_LAYER
+ move_resist = MOVE_FORCE_VERY_STRONG
+
+ ///Bitflags for special behavior such as kidnapping
+ var/car_traits = NONE
+ ///Sound file(s) to play when we drive around
+ var/engine_sound = 'sound/vehicles/carrev.ogg'
+ ///Set this to the length of the engine sound.
+ var/engine_sound_length = 2 SECONDS
+ ///Time it takes to break out of the car.
+ var/escape_time = 6 SECONDS
+ /// How long it takes to move, cars don't use the riding component similar to mechs so we handle it ourselves
+ var/vehicle_move_delay = 1
+ /// What sound to play if someone was forced in.
+ var/forced_enter_sound
+ /// How long it takes to rev (vrrm vrrm!)
+ COOLDOWN_DECLARE(enginesound_cooldown)
+
+/obj/tgvehicle/sealed/car/generate_actions()
+ . = ..()
+ if(!isnull(key_type))
+ initialize_controller_action_type(/datum/action/vehicle/sealed/remove_key, VEHICLE_CONTROL_DRIVE)
+ if(car_traits & CAN_KIDNAP)
+ initialize_controller_action_type(/datum/action/vehicle/sealed/dump_kidnapped_mobs, VEHICLE_CONTROL_DRIVE)
+
+/obj/tgvehicle/sealed/car/MouseDrop_T(mob/living/M, mob/living/user)
+ if(!istype(M))
+ return FALSE
+ if(HAS_TRAIT(M, TRAIT_HANDS_BLOCKED) && !is_driver(M))
+ return FALSE
+ if((car_traits & CAN_KIDNAP) && isliving(user) && M != user)
+ var/mob/living/kidnapped = M
+ kidnapped.visible_message("[user] starts forcing [kidnapped] into [src]!")
+ mob_try_forced_enter(user, kidnapped)
+ return ..()
+
+/obj/tgvehicle/sealed/car/mob_try_exit(mob/future_pedestrian, mob/user, silent = FALSE)
+ if(future_pedestrian != user || !(LAZYACCESS(occupants, future_pedestrian) & VEHICLE_CONTROL_KIDNAPPED))
+ mob_exit(future_pedestrian, silent)
+ return TRUE
+ if(escape_time > 0)
+ to_chat(user, "You push against the back of \the [src]'s trunk to try and get out.")
+ if(!do_after(user, escape_time, target = src))
+ return FALSE
+ to_chat(user, "[user] gets out of [src].")
+ mob_exit(future_pedestrian, silent)
+ return TRUE
+
+/obj/tgvehicle/sealed/car/attack_hand(mob/living/user, list/modifiers)
+ . = ..()
+ if(!(car_traits & CAN_KIDNAP))
+ return
+ to_chat(user, "You start opening [src]'s trunk.")
+ if(!do_after(user, 30, target = src))
+ return
+ if(return_amount_of_controllers_with_flag(VEHICLE_CONTROL_KIDNAPPED))
+ to_chat(user, "The people stuck in [src]'s trunk all come tumbling out.?")
+ dump_specific_mobs(VEHICLE_CONTROL_KIDNAPPED)
+ return
+ to_chat(user, "It seems [src]'s trunk was empty.")
+
+/// Attempts to force a mob into the car
+/obj/tgvehicle/sealed/car/proc/mob_try_forced_enter(mob/forcer, mob/kidnapped, silent = FALSE)
+ if(occupant_amount() >= max_occupants)
+ return FALSE
+ var/atom/old_loc = loc
+ var/enter_delay = get_enter_delay(kidnapped)
+ if(enter_delay == 0 || do_after(forcer, enter_delay, target = kidnapped, extra_checks=CALLBACK(src, TYPE_PROC_REF(/obj/tgvehicle/sealed/car, is_car_stationary), old_loc)))
+ mob_forced_enter(kidnapped, silent)
+ return TRUE
+ return FALSE
+
+/// Callback proc to check for
+/obj/tgvehicle/sealed/car/proc/is_car_stationary(atom/old_loc)
+ return (old_loc == loc)
+
+/// Proc called when someone is forcefully stuffedd into a car
+/obj/tgvehicle/sealed/car/proc/mob_forced_enter(mob/kidnapped, silent = FALSE)
+ if(!silent)
+ kidnapped.visible_message("[kidnapped] is forced into \the [src]!")
+ if(forced_enter_sound)
+ playsound(src, forced_enter_sound, 70, TRUE)
+ kidnapped.forceMove(src)
+ add_occupant(kidnapped, VEHICLE_CONTROL_KIDNAPPED, TRUE)
+
+/obj/tgvehicle/sealed/car/Destroy()
+ explosion(src, heavy_impact_range = 1, light_impact_range = 2, flash_range = 3, adminlog = FALSE)
+ log_attack("[src] exploded due to destruction")
+ return ..()
+
+/obj/tgvehicle/sealed/car/relaymove(mob/living/user, direction)
+ if(is_driver(user) && canmove && (!key_type || istype(inserted_key, key_type)))
+ vehicle_move(direction)
+ return TRUE
+
+/obj/tgvehicle/sealed/car/vehicle_move(direction)
+ if(!COOLDOWN_FINISHED(src, cooldown_vehicle_move))
+ return FALSE
+ COOLDOWN_START(src, cooldown_vehicle_move, vehicle_move_delay)
+
+ if(COOLDOWN_FINISHED(src, enginesound_cooldown))
+ COOLDOWN_START(src, enginesound_cooldown, engine_sound_length)
+ playsound(get_turf(src), engine_sound, 100, TRUE)
+
+ ..()
+ if(trailer)
+ var/dir_to_move = get_dir(trailer.loc, loc)
+ var/did_move = step(trailer, direction)
+ if(did_move)
+ step(trailer, dir_to_move)
+ return did_move
+ after_move(direction)
+ return step(src, direction)
diff --git a/code/modules/vehicle/tg_vehicles/cars/clowncar.dm b/code/modules/vehicle/tg_vehicles/cars/clowncar.dm
new file mode 100644
index 00000000000..19b977dfa16
--- /dev/null
+++ b/code/modules/vehicle/tg_vehicles/cars/clowncar.dm
@@ -0,0 +1,297 @@
+/obj/tgvehicle/sealed/car/clowncar
+ name = "clown car"
+ desc = "How someone could even fit in there is beyond me."
+ icon_state = "clowncar"
+ max_integrity = 150
+ max_occupants = 50
+ key_type = /obj/item/bikehorn
+ enter_sound = 'sound/effects/clowncar/door_close.ogg'
+ exit_sound = 'sound/effects/clowncar/door_open.ogg'
+ car_traits = CAN_KIDNAP
+ armor = list(melee = 70, bullet = 40, laser = 40, energy = 40, bomb = 30, rad = 0, fire = 80, acid = 80)
+ enter_delay = 4 SECONDS
+ access_provider_flags = VEHICLE_CONTROL_DRIVE|VEHICLE_CONTROL_KIDNAPPED
+ /// list of headlight colors we use to pick through when we have party mode due to emag
+ var/headlight_colors = list(COLOR_RED, COLOR_ORANGE, COLOR_YELLOW, COLOR_LIME, COLOR_BLUE_LIGHT, COLOR_CYAN, COLOR_PURPLE)
+ /// Cooldown time inbetween [/obj/tgvehicle/sealed/car/clowncar/proc/roll_the_dice()] usages
+ var/dice_cooldown_time = 15 SECONDS
+ /// Current status of the cannon, alternates between CLOWN_CANNON_INACTIVE, CLOWN_CANNON_BUSY and CLOWN_CANNON_READY
+ var/cannonmode = CLOWN_CANNON_INACTIVE
+ /// Does the driver require the clown role to drive it
+ var/enforce_clown_role = TRUE
+ /// Emag Button Cooldown
+ var/last_emag_button_use = 0
+ /// How fast can you fire the cannon
+ var/cannon_fire_delay = 0.5 SECONDS
+ COOLDOWN_DECLARE(cannon_cooldown)
+
+/obj/tgvehicle/sealed/car/clowncar/Initialize(mapload)
+ . = ..()
+ START_PROCESSING(SSobj,src)
+
+/obj/tgvehicle/sealed/car/clowncar/process()
+ if(headlights_toggle && emagged)
+ set_light(headlight_range, headlight_power, pick(headlight_colors))
+
+/obj/tgvehicle/sealed/car/clowncar/generate_actions()
+ . = ..()
+ initialize_controller_action_type(/datum/action/vehicle/sealed/horn, VEHICLE_CONTROL_DRIVE)
+ initialize_controller_action_type(/datum/action/vehicle/sealed/headlights, VEHICLE_CONTROL_DRIVE)
+ initialize_controller_action_type(/datum/action/vehicle/sealed/thank, VEHICLE_CONTROL_KIDNAPPED)
+
+/obj/tgvehicle/sealed/car/clowncar/auto_assign_occupant_flags(mob/M)
+ if(ishuman(M))
+ var/mob/living/carbon/human/H = M
+ // Only clowns can drive the car, unless an admin varedits the car to allow someone else to drive.
+ if(H.mind?.assigned_role == "Clown" || (!enforce_clown_role && length(return_controllers_with_flag(VEHICLE_CONTROL_DRIVE)) < 1))
+ add_control_flags(H, VEHICLE_CONTROL_DRIVE)
+ playsound(src, 'sound/effects/clowncar/door_close.ogg', 70, TRUE)
+ log_game("[M] has entered [src] as a possible driver")
+ return
+ add_control_flags(M, VEHICLE_CONTROL_KIDNAPPED)
+
+/obj/tgvehicle/sealed/car/clowncar/mob_forced_enter(mob/M)
+ . = ..()
+ playsound(src, pick('sound/effects/clowncar/clowncar_load1.ogg', 'sound/effects/clowncar/clowncar_load2.ogg'), 75)
+ if(iscarbon(M))
+ var/mob/living/carbon/forced_mob = M
+ if(forced_mob.reagents.has_reagent("dublindrop"))
+ var/reagent_amount = forced_mob.reagents.get_reagent_amount("dublindrop")
+ forced_mob.reagents.del_reagent("dublindrop")
+ if(reagent_amount >= 30)
+ message_admins("[ADMIN_LOOKUPFLW(forced_mob)] was forced into a clown car with [reagent_amount] unit(s) of Irish Car Bomb, causing an explosion.")
+ audible_message("You hear a rattling sound coming from the engine. That can't be good...", null, 1)
+ addtimer(CALLBACK(src, PROC_REF(irish_car_bomb)), 5 SECONDS)
+
+// We don't want everything in the clown car to get dusted when it touches the supermatter, so dump the mobs.
+/obj/tgvehicle/sealed/car/clowncar/on_entered_supermatter(atom/movable/vehicle, atom/movable/supermatter)
+ dump_mobs(TRUE)
+
+/obj/tgvehicle/sealed/car/clowncar/proc/irish_car_bomb()
+ dump_mobs()
+ explosion(src, light_impact_range = 1)
+
+/obj/tgvehicle/sealed/car/clowncar/attack_animal(mob/living/simple_animal/user, list/modifiers)
+ if((user.loc != src) || user.environment_smash >= ENVIRONMENT_SMASH_WALLS)
+ return ..()
+
+/obj/tgvehicle/sealed/car/clowncar/take_damage(damage_amount, damage_type = BRUTE, damage_flag = 0, sound_effect = 1, attack_dir)
+ . = ..()
+ if(prob(33))
+ visible_message("[src] spews out a ton of space lube!")
+ var/datum/effect_system/foam_spread/L = new()
+ var/datum/reagents/foamreagent = new /datum/reagents(200)
+ foamreagent.add_reagent("lube", 200)
+ L.set_up(40, get_turf(src), foamreagent)
+ L.start()
+
+/obj/tgvehicle/sealed/car/clowncar/attacked_by(obj/item/I, mob/living/user)
+ . = ..()
+ if(!istype(I, /obj/item/food/grown/banana))
+ return
+ var/obj/item/food/grown/banana/banana = I
+ obj_integrity += min(banana.seed.potency, max_integrity-obj_integrity)
+ to_chat(user, "You use the [banana] to repair [src]!")
+ qdel(banana)
+
+/obj/tgvehicle/sealed/car/clowncar/Bump(atom/bumped)
+ . = ..()
+ if(isliving(bumped))
+ if(ismegafauna(bumped)) // No, you cannot kidnap megafauna with the clown car
+ return
+ var/mob/living/hittarget_living = bumped
+
+ if(istype(hittarget_living, /mob/living/basic/deer))
+ visible_message("[src] careens into [hittarget_living]! Oh the humanity!")
+ for(var/mob/living/carbon/carbon_occupant in occupants)
+ if(prob(35)) // Note: The randomstep on dump_mobs throws occupants into each other and often causes wounds regardless.
+ continue
+ hittarget_living.adjustBruteLoss(200)
+ new /obj/effect/decal/cleanable/blood/splatter(get_turf(hittarget_living))
+
+ dump_mobs(TRUE)
+ playsound(src, 'sound/effects/clowncar/car_crash.ogg', 100)
+ return
+
+ if(iscarbon(hittarget_living))
+ var/mob/living/carbon/C = hittarget_living
+ C.Stun(4 SECONDS) // I play to make sprites go horizontal
+ hittarget_living.visible_message("[src] rams into [hittarget_living] and sucks [hittarget_living.p_them()] up!")
+ mob_forced_enter(hittarget_living)
+ playsound(src, pick('sound/effects/clowncar/clowncar_ram1.ogg', 'sound/effects/clowncar/clowncar_ram2.ogg', 'sound/effects/clowncar/clowncar_ram3.ogg'), 75)
+ log_attack(src, hittarget_living, "sucked up")
+ return
+ if(isturf(bumped))
+ var/turf/bumped_turf = bumped
+ if(!bumped_turf.is_blocked_turf())
+ return
+ if(istype(bumped, /obj/machinery/door))
+ var/obj/machinery/door/conditionalwall = bumped
+ for(var/mob/occupant as anything in return_controllers_with_flag(access_provider_flags))
+ if(conditionalwall.allowed(occupant))
+ return
+ if(conditionalwall.operating)
+ return
+ visible_message("[src] rams into [bumped] and crashes!")
+ playsound(src, pick('sound/effects/clowncar/clowncar_crash1.ogg', 'sound/effects/clowncar/clowncar_crash2.ogg'), 75)
+ playsound(src, 'sound/effects/clowncar/clowncar_crashpins.ogg', 75)
+ dump_mobs(TRUE)
+ log_attack(src, bumped, "crashed into", null)
+
+/obj/tgvehicle/sealed/car/clowncar/after_move(direction)
+ ..()
+ if(!has_gravity(src))
+ return
+ var/mob/possible_pancake = locate(/mob/living/carbon) in loc
+ if(!possible_pancake)
+ return
+ if(possible_pancake.loc == src)
+ return
+ if(!iscarbon(possible_pancake))
+ return
+ var/mob/living/carbon/target_pancake = possible_pancake
+ if(target_pancake.body_position != LYING_DOWN)
+ return
+ if(HAS_TRAIT(target_pancake, TRAIT_CLOWN_CAR_SQUISHED))
+ return
+ target_pancake.visible_message("[src] runs over [target_pancake], flattening [target_pancake.p_them()] like a pancake!")
+ handle_squish_carbon(target_pancake, 30)
+ ADD_TRAIT(target_pancake, TRAIT_CLOWN_CAR_SQUISHED, "clown_car")
+ addtimer(CALLBACK(src, PROC_REF(allow_resquish), target_pancake), 5 SECONDS)
+ target_pancake.Stun(2 SECONDS)
+ playsound(target_pancake, 'sound/effects/clowncar/cartoon_splat.ogg', 75)
+ log_attack(src, target_pancake, "ran over")
+
+/obj/tgvehicle/sealed/car/clowncar/emag_act(mob/user, obj/item/card/emag/emag_card)
+ if(emagged)
+ return FALSE
+ emagged = TRUE
+ to_chat(user, "You scramble [src]'s child safety lock, and a panel with six colorful buttons appears!")
+ initialize_controller_action_type(/datum/action/vehicle/sealed/roll_the_dice, VEHICLE_CONTROL_DRIVE)
+ initialize_controller_action_type(/datum/action/vehicle/sealed/cannon, VEHICLE_CONTROL_DRIVE)
+ return TRUE
+
+/obj/tgvehicle/sealed/car/clowncar/obj_destruction(damage_flag)
+ playsound(src, 'sound/misc/sadtrombone.ogg', 100)
+ STOP_PROCESSING(SSobj,src)
+ return ..()
+
+/obj/tgvehicle/sealed/car/clowncar/proc/allow_resquish(mob/living/carbon/pancake)
+ REMOVE_TRAIT(pancake, TRAIT_CLOWN_CAR_SQUISHED, "clown_car")
+
+/**
+ * Plays a random funky effect
+ * Only available while car is emagged
+ * Possible effects:
+ * * Spawn bananapeel
+ * * Spawn random reagent foam
+ * * Make the clown car look like a singulo temporarily
+ * * Spawn Laughing chem gas
+ * * Drop oil
+ * * Fart and make everyone nearby laugh
+ */
+
+/obj/tgvehicle/sealed/car/clowncar/proc/roll_the_dice(mob/user)
+ if(last_emag_button_use + dice_cooldown_time > world.time)
+ to_chat(user, "The button panel is currently recharging.")
+ return
+ last_emag_button_use = world.time
+ switch(rand(1,6))
+ if(1)
+ visible_message("[user] presses one of the colorful buttons on [src], and a special banana peel drops out of it.")
+ new /obj/item/grown/bananapeel/specialpeel(loc)
+ if(2)
+ visible_message("[user] presses one of the colorful buttons on [src], and unknown chemicals flood out of it.")
+ var/datum/reagents/randomchems = new/datum/reagents(300)
+ randomchems.my_atom = src
+ randomchems.add_reagent(get_random_reagent_id(), 100)
+ var/datum/effect_system/foam_spread/foam = new()
+ foam.set_up(200, src, randomchems)
+ foam.start()
+ if(3)
+ visible_message("[user] presses one of the colorful buttons on [src], and the clown car turns on its singularity disguise system.")
+ icon = 'icons/obj/singularity.dmi'
+ icon_state = "singularity_s1"
+ addtimer(CALLBACK(src, PROC_REF(reset_icon)), 10 SECONDS)
+ if(4)
+ visible_message("[user] presses one of the colorful buttons on [src], and the clown car spews out a cloud of confetti all over the place.")
+ confettisize(src, 50, 8)
+ if(5)
+ visible_message("[user] presses one of the colorful buttons on [src], and the clown car starts dropping a lubricant trail.")
+ RegisterSignal(src, COMSIG_MOVABLE_MOVED, PROC_REF(cover_in_oil))
+ addtimer(CALLBACK(src, PROC_REF(stop_dropping_oil)), 3 SECONDS)
+ if(6)
+ visible_message("[user] presses one of the colorful buttons on [src], and the clown car lets out a comedic toot.")
+ playsound(src, 'sound/effects/clowncar/clowncar_fart.ogg', 100)
+ for(var/mob/living/L in orange(loc, 6))
+ L.emote("laugh")
+ for(var/mob/living/L as anything in occupants)
+ L.emote("laugh")
+
+/// resets the icon and iconstate of the clowncar after it was set to singulo states
+/obj/tgvehicle/sealed/car/clowncar/proc/reset_icon()
+ icon = initial(icon)
+ icon_state = initial(icon_state)
+
+/// Deploys oil when the clowncar moves in oil deploy mode
+/obj/tgvehicle/sealed/car/clowncar/proc/cover_in_oil()
+ SIGNAL_HANDLER
+ var/turf/simulated/T = get_turf(src)
+ T.MakeSlippery(TURF_WET_LUBE)
+
+/// Stops dropping oil after the time has run up
+/obj/tgvehicle/sealed/car/clowncar/proc/stop_dropping_oil()
+ UnregisterSignal(src, COMSIG_MOVABLE_MOVED)
+
+/// Toggles the on and off state of the clown cannon that shoots random kidnapped people
+/obj/tgvehicle/sealed/car/clowncar/proc/toggle_cannon(mob/user)
+ if(cannonmode == CLOWN_CANNON_BUSY)
+ to_chat(user, "Please wait for the vehicle to finish its current action first.")
+ return
+ if(cannonmode) // cannon active, deactivate
+ flick("clowncar_fromfire", src)
+ icon_state = "clowncar"
+ addtimer(CALLBACK(src, PROC_REF(deactivate_cannon)), 2 SECONDS)
+ playsound(src, 'sound/effects/clowncar/clowncar_cannonmode2.ogg', 75)
+ visible_message("[src] starts going back into mobile mode.")
+ else
+ canmove = FALSE // anchor and activate cannon
+ flick("clowncar_tofire", src)
+ icon_state = "clowncar_fire"
+ visible_message("[src] opens up and reveals a large cannon.")
+ addtimer(CALLBACK(src, PROC_REF(activate_cannon)), 2 SECONDS)
+ playsound(src, 'sound/effects/clowncar/clowncar_cannonmode1.ogg', 75)
+ cannonmode = CLOWN_CANNON_BUSY
+
+/// Finalizes cannon activation
+/obj/tgvehicle/sealed/car/clowncar/proc/activate_cannon()
+ var/mouse_pointer = 'icons/effects/mouse_pointers/weapon_pointer.dmi'
+ cannonmode = CLOWN_CANNON_READY
+ for(var/mob/living/driver as anything in return_controllers_with_flag(VEHICLE_CONTROL_DRIVE))
+ REMOVE_TRAIT(driver, TRAIT_HANDS_BLOCKED, VEHICLE_TRAIT)
+ if(driver.client.mouse_pointer_icon == initial(driver.client.mouse_pointer_icon))
+ driver.client.mouse_pointer_icon = mouse_pointer
+
+/// Finalizes cannon deactivation
+/obj/tgvehicle/sealed/car/clowncar/proc/deactivate_cannon()
+ canmove = TRUE
+ cannonmode = CLOWN_CANNON_INACTIVE
+ for(var/mob/living/driver as anything in return_controllers_with_flag(VEHICLE_CONTROL_DRIVE))
+ driver.client.mouse_pointer_icon = initial(driver.client.mouse_pointer_icon)
+ ADD_TRAIT(driver, TRAIT_HANDS_BLOCKED, VEHICLE_TRAIT)
+
+/// Fires the cannon where the user clicks
+/obj/tgvehicle/sealed/car/clowncar/proc/fire_cannon_at(atom/target, mob/user, list/modifiers)
+ if(cannonmode != CLOWN_CANNON_READY || !length(return_controllers_with_flag(VEHICLE_CONTROL_KIDNAPPED)))
+ return
+ if(!COOLDOWN_FINISHED(src, cannon_cooldown))
+ return
+ var/mob/living/unlucky_sod = pick(return_controllers_with_flag(VEHICLE_CONTROL_KIDNAPPED))
+ mob_exit(unlucky_sod, silent = TRUE)
+ flick("clowncar_recoil", src)
+ playsound(src, pick('sound/effects/clowncar/carcannon1.ogg', 'sound/effects/clowncar/carcannon2.ogg', 'sound/effects/clowncar/carcannon3.ogg'), 75)
+ unlucky_sod.throw_at(target, 10, 2)
+ COOLDOWN_START(src, cannon_cooldown, cannon_fire_delay)
+ log_attack(user, unlucky_sod, "fired towards [target]") // this doesn't catch if the mob hits something between the car and the target
+ return COMSIG_MOB_CANCEL_CLICKON
+
diff --git a/code/modules/vehicle/tg_vehicles/scooter.dm b/code/modules/vehicle/tg_vehicles/scooter.dm
index e0a5f8c1563..5872ff69beb 100644
--- a/code/modules/vehicle/tg_vehicles/scooter.dm
+++ b/code/modules/vehicle/tg_vehicles/scooter.dm
@@ -300,16 +300,16 @@
/obj/tgvehicle/scooter/skateboard/wrench_act(mob/living/user, obj/item/I)
return
-/obj/tgvehicle/scooter/skateboard/improvised/attackby__legacy__attackchain(obj/item/I, mob/user, params)
+/obj/tgvehicle/scooter/skateboard/improvised/item_interaction(mob/living/user, obj/item/I, list/modifiers)
if(!istype(I, /obj/item/stack/rods))
return ..()
var/obj/item/stack/S = I
if(S.get_amount() < 2)
- return
+ return ITEM_INTERACT_COMPLETE
to_chat(user, "You begin making handlebars for [src].")
if(do_after(user, 2.5 SECONDS, target = src))
if(!loc || !S || S.get_amount() < 2 || !S.use(2))
- return
+ return ITEM_INTERACT_COMPLETE
to_chat(user, "You add the rods to [src], creating handlebars.")
var/obj/tgvehicle/scooter/skaterskoot = new(loc)
if(has_buckled_mobs())
diff --git a/code/modules/vehicle/tg_vehicles/sealed.dm b/code/modules/vehicle/tg_vehicles/sealed.dm
new file mode 100644
index 00000000000..1f323781cd9
--- /dev/null
+++ b/code/modules/vehicle/tg_vehicles/sealed.dm
@@ -0,0 +1,175 @@
+/obj/tgvehicle/sealed
+ can_buckle = FALSE
+ /// How long it takes to enter the vehice
+ var/enter_delay = 2 SECONDS
+ /// A special curser you may get while driving
+ var/mouse_pointer
+ /// Are the headlights on?
+ var/headlights_toggle = FALSE
+ /// How far do the headlights reach?
+ var/headlight_range = 6
+ /// How powerful are the headlights?
+ var/headlight_power = 2
+ /// Determines which occupants provide access when bumping into doors
+ var/access_provider_flags = VEHICLE_CONTROL_DRIVE
+
+/obj/tgvehicle/sealed/Initialize(mapload)
+ . = ..()
+ RegisterSignal(src, COMSIG_SUPERMATTER_CONSUMED, PROC_REF(on_entered_supermatter))
+
+/obj/tgvehicle/sealed/generate_actions()
+ . = ..()
+ initialize_passenger_action_type(/datum/action/vehicle/sealed/climb_out)
+
+/obj/tgvehicle/sealed/generate_action_type()
+ var/datum/action/vehicle/sealed/E = ..()
+ . = E
+ if(istype(E))
+ E.vehicle_entered_target = src
+
+/obj/tgvehicle/sealed/MouseDrop_T(mob/living/M, mob/living/user)
+ if(!istype(M))
+ return ..()
+ mob_try_enter(M)
+ return ..()
+
+/obj/tgvehicle/sealed/Exited(atom/movable/gone, direction)
+ . = ..()
+ if(ismob(gone))
+ remove_occupant(gone)
+
+// so that we can check the access of the vehicle's occupants. Ridden vehicles do this in the riding component, but these don't have that
+/obj/tgvehicle/sealed/Bump(atom/A)
+ . = ..()
+ if(istype(A, /obj/machinery/door))
+ var/obj/machinery/door/conditionalwall = A
+ for(var/mob/occupant as anything in return_controllers_with_flag(access_provider_flags))
+ if(!conditionalwall.allowed(occupant))
+ continue
+ if(conditionalwall.operating || conditionalwall.emagged || conditionalwall.foam_level)
+ return
+ conditionalwall.bumpopen(occupant)
+
+/obj/tgvehicle/sealed/after_add_occupant(mob/M)
+ . = ..()
+ ADD_TRAIT(M, TRAIT_HANDS_BLOCKED, VEHICLE_TRAIT)
+
+/obj/tgvehicle/sealed/after_remove_occupant(mob/M)
+ . = ..()
+ REMOVE_TRAIT(M, TRAIT_HANDS_BLOCKED, VEHICLE_TRAIT)
+
+/obj/tgvehicle/sealed/proc/mob_try_enter(mob/rider)
+ if(!istype(rider))
+ return FALSE
+ var/enter_delay = get_enter_delay(rider)
+ if(enter_delay == 0)
+ if(enter_checks(rider))
+ mob_enter(rider)
+ return TRUE
+ return FALSE
+ if(do_after(rider, enter_delay, target = src))
+ if(!enter_checks(rider))
+ return FALSE
+ mob_enter(rider)
+ return TRUE
+ return FALSE
+
+/// returns enter do_after delay for the given mob in ticks
+/obj/tgvehicle/sealed/proc/get_enter_delay(mob/M)
+ return enter_delay
+
+/// Extra checks to perform during the do_after to enter the vehicle
+/obj/tgvehicle/sealed/proc/enter_checks(mob/M)
+ return occupant_amount() < max_occupants
+
+/obj/tgvehicle/sealed/proc/mob_enter(mob/M, silent = FALSE)
+ if(!istype(M))
+ return FALSE
+ if(!silent)
+ M.visible_message("[M] climbs into \the [src]!")
+ M.forceMove(src)
+ add_occupant(M)
+ return TRUE
+
+/obj/tgvehicle/sealed/proc/mob_try_exit(mob/M, mob/user, silent = FALSE, randomstep = FALSE)
+ mob_exit(M, silent, randomstep)
+
+/obj/tgvehicle/sealed/proc/mob_exit(mob/M, silent = FALSE, randomstep = FALSE)
+ if(!istype(M))
+ return FALSE
+ remove_occupant(M)
+ M.forceMove(exit_location(M))
+ if(randomstep)
+ var/turf/target_turf = get_step(exit_location(M), pick(GLOB.cardinal))
+ M.throw_at(target_turf, 5, 10)
+
+ if(!silent)
+ M.visible_message("[M] drops out of \the [src]!")
+ return TRUE
+
+/obj/tgvehicle/sealed/proc/exit_location(M)
+ return drop_location()
+
+/obj/tgvehicle/sealed/item_interaction(mob/living/user, obj/item/I, list/modifiers)
+ if(key_type && !is_key(inserted_key) && is_key(I))
+ if(I.flags & NODROP || !user.drop_item() || !I.forceMove(src))
+ to_chat(user, "[I] is stuck to your hand!")
+ return ITEM_INTERACT_COMPLETE
+ to_chat(user, "You insert [I] into [src].")
+ if(inserted_key) // just in case there's an invalid key
+ inserted_key.forceMove(drop_location())
+ inserted_key = I
+ inserted_key.forceMove(src)
+ return ITEM_INTERACT_COMPLETE
+ return ..()
+
+/obj/tgvehicle/sealed/proc/remove_key(mob/user)
+ if(!inserted_key)
+ to_chat(user, "There is no key in [src]!")
+ return
+ if(!is_occupant(user) || !(occupants[user] & VEHICLE_CONTROL_DRIVE))
+ to_chat(user, "You must be driving [src] to remove [src]'s key!")
+ return
+ to_chat(user, "You remove [inserted_key] from [src].")
+ user.put_in_hands(inserted_key)
+ inserted_key = null
+
+/obj/tgvehicle/sealed/Destroy()
+ dump_mobs()
+ return ..()
+
+/obj/tgvehicle/sealed/proc/dump_mobs(randomstep = TRUE)
+ for(var/i in occupants)
+ mob_exit(i, randomstep = randomstep)
+ if(iscarbon(i))
+ var/mob/living/carbon/Carbon = i
+ Carbon.Stun(4 SECONDS)
+
+/obj/tgvehicle/sealed/proc/dump_specific_mobs(flag, randomstep = TRUE)
+ for(var/i in occupants)
+ if(!(occupants[i] & flag))
+ continue
+ mob_exit(i, randomstep = randomstep)
+ if(iscarbon(i))
+ var/mob/living/carbon/C = i
+ C.Stun(4 SECONDS)
+
+
+/obj/tgvehicle/sealed/AllowDrop()
+ return FALSE
+
+/obj/tgvehicle/sealed/relaymove(mob/living/user, direction)
+ if(canmove)
+ vehicle_move(direction)
+ return TRUE
+
+/// Sinced sealed vehicles (cars and mechs) don't have riding components, the actual movement is handled here from [/obj/tgvehicle/sealed/proc/relaymove]
+/obj/tgvehicle/sealed/proc/vehicle_move(direction)
+ return FALSE
+
+/// When we touch a crystal, kill everything inside us
+/obj/tgvehicle/sealed/proc/on_entered_supermatter(atom/movable/vehicle, atom/movable/supermatter)
+ SIGNAL_HANDLER // COMSIG_SUPERMATTER_CONSUMED
+ for(var/mob/passenger as anything in occupants)
+ if(!is_ai(passenger))
+ passenger.Bump(supermatter)
diff --git a/code/modules/vehicle/tg_vehicles/tg_vehicle_actions.dm b/code/modules/vehicle/tg_vehicles/tg_vehicle_actions.dm
index 44e59182199..e8172a2bb67 100644
--- a/code/modules/vehicle/tg_vehicles/tg_vehicle_actions.dm
+++ b/code/modules/vehicle/tg_vehicles/tg_vehicle_actions.dm
@@ -287,3 +287,116 @@
animate(board, pixel_y = -6, time = 0.3 SECONDS)
board.unbuckle_mob(rider)
addtimer(CALLBACK(board, TYPE_PROC_REF(/obj/tgvehicle/scooter/skateboard, pick_up_board), rider), 0.5 SECONDS) // so the board can still handle "picking it up"
+
+/datum/action/vehicle/sealed
+ check_flags = AB_CHECK_IMMOBILE | AB_CHECK_CONSCIOUS
+ var/obj/tgvehicle/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)
+
+// Clown car stuff
+/datum/action/vehicle/sealed/horn
+ name = "Honk Horn"
+ desc = "Honk your classy horn."
+ button_icon_state = "car_horn"
+ var/horn_sound = 'sound/items/airhorn.ogg'
+ var/horn_cooldown = 60
+ var/last_horn = 0
+
+/datum/action/vehicle/sealed/horn/Trigger(trigger_flags)
+ if(last_horn + horn_cooldown > world.time)
+ return
+ last_horn = world.time
+ vehicle_entered_target.visible_message("[vehicle_entered_target] loudly honks!")
+ to_chat(owner, "You press [vehicle_entered_target]'s horn.")
+ if(istype(vehicle_entered_target.inserted_key, /obj/item/bikehorn))
+ playsound(vehicle_entered_target, 'sound/items/bikehorn.ogg', 75) // The bikehorn plays a sound instead
+ return
+ playsound(vehicle_entered_target, horn_sound, 75)
+
+/datum/action/vehicle/sealed/headlights
+ name = "Toggle Headlights"
+ desc = "Turn on your brights!"
+ button_icon_state = "car_headlights"
+
+/datum/action/vehicle/sealed/headlights/Trigger(trigger_flags)
+ to_chat(owner, "You flip the switch for the vehicle's headlights.")
+ vehicle_entered_target.headlights_toggle = !vehicle_entered_target.headlights_toggle
+ if(vehicle_entered_target.headlights_toggle)
+ vehicle_entered_target.set_light(vehicle_entered_target.headlight_range, vehicle_entered_target.headlight_power)
+ else
+ vehicle_entered_target.remove_light()
+ vehicle_entered_target.update_appearance()
+ playsound(owner, vehicle_entered_target.headlights_toggle ? 'sound/weapons/gun_interactions/pistol_magin.ogg' : 'sound/weapons/gun_interactions/pistol_magout.ogg', 40, TRUE)
+
+/datum/action/vehicle/sealed/dump_kidnapped_mobs
+ name = "Dump Kidnapped Mobs"
+ desc = "Dump all objects and people in your car on the floor."
+ button_icon_state = "car_dump"
+
+/datum/action/vehicle/sealed/dump_kidnapped_mobs/Trigger(trigger_flags)
+ vehicle_entered_target.visible_message("[vehicle_target] starts dumping the people inside of it.")
+ vehicle_entered_target.dump_specific_mobs(VEHICLE_CONTROL_KIDNAPPED)
+
+
+/datum/action/vehicle/sealed/roll_the_dice
+ name = "Press Colorful Button"
+ desc = "Press one of those colorful buttons on your display panel!"
+ button_icon_state = "car_rtd"
+
+/datum/action/vehicle/sealed/roll_the_dice/Trigger(trigger_flags)
+ if(!istype(vehicle_entered_target, /obj/tgvehicle/sealed/car/clowncar))
+ return
+ var/obj/tgvehicle/sealed/car/clowncar/C = vehicle_entered_target
+ C.roll_the_dice(owner)
+
+/datum/action/vehicle/sealed/cannon
+ name = "Toggle Siege Mode"
+ desc = "Destroy them with their own fodder!"
+ button_icon_state = "car_cannon"
+
+/datum/action/vehicle/sealed/cannon/Trigger(trigger_flags)
+ if(!istype(vehicle_entered_target, /obj/tgvehicle/sealed/car/clowncar))
+ return
+ var/obj/tgvehicle/sealed/car/clowncar/C = vehicle_entered_target
+ C.toggle_cannon(owner)
+
+
+/datum/action/vehicle/sealed/thank
+ name = "Thank the Clown Car Driver"
+ desc = "They're just doing their job."
+ button_icon_state = "car_thanktheclown"
+ COOLDOWN_DECLARE(thank_time_cooldown)
+
+/datum/action/vehicle/sealed/thank/Trigger(trigger_flags)
+ if(!istype(vehicle_entered_target, /obj/tgvehicle/sealed/car/clowncar))
+ return
+ if(!COOLDOWN_FINISHED(src, thank_time_cooldown))
+ return
+ COOLDOWN_START(src, thank_time_cooldown, 6 SECONDS)
+ var/obj/tgvehicle/sealed/car/clowncar/clown_car = vehicle_entered_target
+ var/list/mob/drivers = clown_car.return_drivers()
+ if(!length(drivers))
+ to_chat(owner, "You prepare to thank the driver, only to realize that they don't exist.")
+ return
+ var/mob/clown = pick(drivers)
+ owner.say("Thank you for the fun ride, [clown.name]!")
diff --git a/code/modules/vehicle/tg_vehicles/tg_vehicles.dm b/code/modules/vehicle/tg_vehicles/tg_vehicles.dm
index 945caf2d545..15782edb5c6 100644
--- a/code/modules/vehicle/tg_vehicles/tg_vehicles.dm
+++ b/code/modules/vehicle/tg_vehicles/tg_vehicles.dm
@@ -18,6 +18,8 @@
var/max_occupants = 1
////Maximum amount of drivers
var/max_drivers = 1
+ /// Whether the vehicle is currently able to move
+ var/canmove = TRUE
/**
* 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
@@ -26,14 +28,18 @@
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/can_move = TRUE
var/list/autogrant_actions_passenger //plain list of typepaths
var/list/autogrant_actions_controller //assoc list "[bitflag]" = list(typepaths)
var/list/mob/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/tgvehicle/trailer
var/are_legs_exposed = FALSE
+ /// Sound played when entering a vehicle
+ var/enter_sound
+ /// Sound played when exiting a vehicle
+ var/exit_sound
+
+ new_attack_chain = TRUE
/obj/tgvehicle/Initialize(mapload)
. = ..()
@@ -51,6 +57,8 @@
/obj/tgvehicle/Exited(atom/movable/gone, direction)
if(gone == inserted_key)
inserted_key = null
+ if(exit_sound)
+ playsound(src, exit_sound, 70, TRUE)
return ..()
/obj/tgvehicle/examine(mob/user)
@@ -137,7 +145,7 @@
return
/obj/tgvehicle/relaymove(mob/living/user, direction)
- if(!can_move)
+ if(!canmove)
return FALSE
if(is_driver(user))
return relaydrive(user, direction)
@@ -192,7 +200,7 @@
add_occupant(M)
return ..()
-/obj/tgvehicle/attackby__legacy__attackchain(obj/item/I, mob/user, params)
+/obj/tgvehicle/item_interaction(mob/living/user, obj/item/I, list/modifiers)
if(!key_type || is_key(inserted_key) || !is_key(I))
return ..()
if(user.drop_item())
@@ -203,15 +211,12 @@
inserted_key = I
else
to_chat(user, "[I] seems to be stuck to your hand!")
- if(inserted_key) //just in case there's an invalid key
- inserted_key.forceMove(drop_location())
- inserted_key = I
/obj/tgvehicle/AltClick(mob/user)
if(!inserted_key)
return ..()
- if(!is_occupant(user))
- to_chat(user, "You must be riding the [src] to remove [src]'s key!")
+ if(!is_occupant(user) || !(occupants[user] & VEHICLE_CONTROL_DRIVE))
+ to_chat(user, "You must be driving the [src] to remove [src]'s key!")
return
to_chat(user, "You remove [inserted_key] from [src].")
inserted_key.forceMove(drop_location())
diff --git a/icons/obj/tgvehicles.dmi b/icons/obj/tgvehicles.dmi
index 369176ef01c..5be2c5a8912 100644
Binary files a/icons/obj/tgvehicles.dmi and b/icons/obj/tgvehicles.dmi differ
diff --git a/paradise.dme b/paradise.dme
index dc13031e6b3..f9b96e7602c 100644
--- a/paradise.dme
+++ b/paradise.dme
@@ -3240,8 +3240,11 @@
#include "code\modules\vehicle\sportscar.dm"
#include "code\modules\vehicle\vehicle.dm"
#include "code\modules\vehicle\tg_vehicles\scooter.dm"
+#include "code\modules\vehicle\tg_vehicles\sealed.dm"
#include "code\modules\vehicle\tg_vehicles\tg_vehicle_actions.dm"
#include "code\modules\vehicle\tg_vehicles\tg_vehicles.dm"
+#include "code\modules\vehicle\tg_vehicles\cars\car.dm"
+#include "code\modules\vehicle\tg_vehicles\cars\clowncar.dm"
#include "code\modules\virology\virology_goals.dm"
#include "code\modules\vote\vote_datum.dm"
#include "code\modules\vote\vote_presets.dm"
diff --git a/sound/effects/clowncar/car_crash.ogg b/sound/effects/clowncar/car_crash.ogg
new file mode 100644
index 00000000000..a614a78d2f7
Binary files /dev/null and b/sound/effects/clowncar/car_crash.ogg differ
diff --git a/sound/effects/clowncar/carcannon1.ogg b/sound/effects/clowncar/carcannon1.ogg
new file mode 100644
index 00000000000..751fa6f7546
Binary files /dev/null and b/sound/effects/clowncar/carcannon1.ogg differ
diff --git a/sound/effects/clowncar/carcannon2.ogg b/sound/effects/clowncar/carcannon2.ogg
new file mode 100644
index 00000000000..7bc86d7cbca
Binary files /dev/null and b/sound/effects/clowncar/carcannon2.ogg differ
diff --git a/sound/effects/clowncar/carcannon3.ogg b/sound/effects/clowncar/carcannon3.ogg
new file mode 100644
index 00000000000..80407e553fd
Binary files /dev/null and b/sound/effects/clowncar/carcannon3.ogg differ
diff --git a/sound/effects/clowncar/cartoon_splat.ogg b/sound/effects/clowncar/cartoon_splat.ogg
new file mode 100644
index 00000000000..c99c7f1b25f
Binary files /dev/null and b/sound/effects/clowncar/cartoon_splat.ogg differ
diff --git a/sound/effects/clowncar/clowncar_cannonmode1.ogg b/sound/effects/clowncar/clowncar_cannonmode1.ogg
new file mode 100644
index 00000000000..aa21c8f9906
Binary files /dev/null and b/sound/effects/clowncar/clowncar_cannonmode1.ogg differ
diff --git a/sound/effects/clowncar/clowncar_cannonmode2.ogg b/sound/effects/clowncar/clowncar_cannonmode2.ogg
new file mode 100644
index 00000000000..931e1464229
Binary files /dev/null and b/sound/effects/clowncar/clowncar_cannonmode2.ogg differ
diff --git a/sound/effects/clowncar/clowncar_crash1.ogg b/sound/effects/clowncar/clowncar_crash1.ogg
new file mode 100644
index 00000000000..681a483f4ca
Binary files /dev/null and b/sound/effects/clowncar/clowncar_crash1.ogg differ
diff --git a/sound/effects/clowncar/clowncar_crash2.ogg b/sound/effects/clowncar/clowncar_crash2.ogg
new file mode 100644
index 00000000000..fcd1164770e
Binary files /dev/null and b/sound/effects/clowncar/clowncar_crash2.ogg differ
diff --git a/sound/effects/clowncar/clowncar_crashpins.ogg b/sound/effects/clowncar/clowncar_crashpins.ogg
new file mode 100644
index 00000000000..e2419ef2bbb
Binary files /dev/null and b/sound/effects/clowncar/clowncar_crashpins.ogg differ
diff --git a/sound/effects/clowncar/clowncar_fart.ogg b/sound/effects/clowncar/clowncar_fart.ogg
new file mode 100644
index 00000000000..ba14ff9abf8
Binary files /dev/null and b/sound/effects/clowncar/clowncar_fart.ogg differ
diff --git a/sound/effects/clowncar/clowncar_load1.ogg b/sound/effects/clowncar/clowncar_load1.ogg
new file mode 100644
index 00000000000..d4b0c605331
Binary files /dev/null and b/sound/effects/clowncar/clowncar_load1.ogg differ
diff --git a/sound/effects/clowncar/clowncar_load2.ogg b/sound/effects/clowncar/clowncar_load2.ogg
new file mode 100644
index 00000000000..19ac5cf4d9c
Binary files /dev/null and b/sound/effects/clowncar/clowncar_load2.ogg differ
diff --git a/sound/effects/clowncar/clowncar_ram1.ogg b/sound/effects/clowncar/clowncar_ram1.ogg
new file mode 100644
index 00000000000..4f5fea8971f
Binary files /dev/null and b/sound/effects/clowncar/clowncar_ram1.ogg differ
diff --git a/sound/effects/clowncar/clowncar_ram2.ogg b/sound/effects/clowncar/clowncar_ram2.ogg
new file mode 100644
index 00000000000..171e0cbb285
Binary files /dev/null and b/sound/effects/clowncar/clowncar_ram2.ogg differ
diff --git a/sound/effects/clowncar/clowncar_ram3.ogg b/sound/effects/clowncar/clowncar_ram3.ogg
new file mode 100644
index 00000000000..f4c48a47d7e
Binary files /dev/null and b/sound/effects/clowncar/clowncar_ram3.ogg differ
diff --git a/sound/effects/clowncar/door_close.ogg b/sound/effects/clowncar/door_close.ogg
new file mode 100644
index 00000000000..c19bb932894
Binary files /dev/null and b/sound/effects/clowncar/door_close.ogg differ
diff --git a/sound/effects/clowncar/door_open.ogg b/sound/effects/clowncar/door_open.ogg
new file mode 100644
index 00000000000..ae79ecc7177
Binary files /dev/null and b/sound/effects/clowncar/door_open.ogg differ
diff --git a/sound/vehicles/carrev.ogg b/sound/vehicles/carrev.ogg
new file mode 100644
index 00000000000..f27b2ea2af4
Binary files /dev/null and b/sound/vehicles/carrev.ogg differ