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