Files
CHOMPStation2/code/modules/vehicles/vehicle.dm
CHOMPStation2StaffMirrorBot 667e08ee0a [MIRROR] Kitchen Sink PR (#10745)
Co-authored-by: Cameron Lennox <killer65311@gmail.com>
2025-04-26 19:16:57 +02:00

453 lines
12 KiB
Plaintext

//Dummy object for holding items in vehicles.
//Prevents items from being interacted with.
/datum/vehicle_dummy_load
var/name = "dummy load"
var/actual_load
/obj/vehicle
name = "vehicle"
icon = 'icons/obj/vehicles.dmi'
layer = MOB_LAYER + 0.1 //so it sits above objects including mobs
density = TRUE
anchored = TRUE
animate_movement=1
light_range = 3
can_buckle = TRUE
buckle_movable = 1
buckle_lying = 0
var/mechanical = TRUE // If false, doesn't care for things like cells, engines, EMP, keys, etc.
var/attack_log = null
var/on = 0
var/health = 0 //do not forget to set health for your vehicle!
var/maxhealth = 0
var/fire_dam_coeff = 1.0
var/brute_dam_coeff = 1.0
var/open = 0 //Maint panel
var/locked = 1
var/stat = 0
var/emagged = 0
var/powered = 0 //set if vehicle is powered and should use fuel when moving
var/move_delay = 1 //set this to limit the speed of the vehicle
var/obj/item/cell/cell
var/charge_use = 5 //set this to adjust the amount of power the vehicle uses per move
var/paint_color = "#666666" //For vehicles with special paint overlays.
var/atom/movable/load //all vehicles can take a load, since they should all be a least drivable
var/load_item_visible = 1 //set if the loaded item should be overlayed on the vehicle sprite
var/load_offset_x = 0 //pixel_x offset for item overlay
var/load_offset_y = 0 //pixel_y offset for item overlay
var/mob_offset_y = 0 //pixel_y offset for mob overlay
var/datum/looping_sound/idle_carengine/soundloop //CHOMPedit: Looping engine audio.
//-------------------------------------------
// Standard procs
//-------------------------------------------
//ChompADD START
/obj/vehicle/Initialize(mapload)
. = ..()
soundloop = new(list(src), FALSE)
//ChompADD END
///obj/vehicle/New()
// ..()
// //spawn the cell you want in each vehicle // CHOMPedit: Commented out in favour of initialize.
/obj/vehicle/Destroy()
QDEL_NULL(riding_datum)
QDEL_NULL(soundloop) //ChompADD
return ..()
//BUCKLE HOOKS
/obj/vehicle/buckle_mob(mob/living/M, forced = FALSE, check_loc = TRUE)
. = ..()
M.update_water()
if(riding_datum)
riding_datum.ridden = src
riding_datum.handle_vehicle_offsets()
/obj/vehicle/unbuckle_mob(mob/living/buckled_mob, force = FALSE)
. = ..(buckled_mob, force)
buckled_mob?.update_water()
if(riding_datum)
riding_datum.restore_position(buckled_mob)
riding_datum.handle_vehicle_offsets() // So the person in back goes to the front.
/obj/vehicle/Move(var/newloc, var/direction, var/movetime)
// VOREstation edit - Zmoving for falling
var/turf/newturf = newloc
var/zmove = (newturf && z != newturf.z)
if(!zmove && world.time < l_move_time + move_delay) //This AND the riding datum move speed limit? // VOREstation edit end
return
if(!zmove && mechanical && on && powered && cell.charge < charge_use) // VOREstation edit - zmove doesn't run this
turn_off()
return
. = ..()
if(!zmove && mechanical && on && powered) // VOREstation edit - zmove doesn't run this
cell.use(charge_use)
//Dummy loads do not have to be moved as they are just an overlay
//See load_object() proc in cargo_trains.dm for an example
//Also mobs are buckled to the vehicle and get moved in atom/movable/Move's call to take care of that
if(load && !(load in buckled_mobs) && !istype(load, /datum/vehicle_dummy_load))
load.forceMove(loc)
/obj/vehicle/attackby(obj/item/W as obj, mob/user as mob)
if(istype(W, /obj/item/hand_labeler))
return
if(mechanical)
if(W.has_tool_quality(TOOL_SCREWDRIVER))
if(!locked)
open = !open
update_icon()
to_chat(user, span_notice("Maintenance panel is now [open ? "opened" : "closed"]."))
playsound(src, W.usesound, 50, 1)
else if(W.has_tool_quality(TOOL_CROWBAR) && cell && open)
remove_cell(user)
else if(istype(W, /obj/item/cell) && !cell && open)
insert_cell(W, user)
else if(W.has_tool_quality(TOOL_WELDER))
var/obj/item/weldingtool/T = W.get_welder()
if(T.welding)
if(health < maxhealth)
if(open)
health = min(maxhealth, health+10)
user.setClickCooldown(user.get_attack_speed(W))
playsound(src, T.usesound, 50, 1)
user.visible_message(span_red("[user] repairs [src]!"),span_blue("You repair [src]!"))
else
to_chat(user, span_notice("Unable to repair with the maintenance panel closed."))
else
to_chat(user, span_notice("[src] does not need a repair."))
else
to_chat(user, span_notice("Unable to repair while [src] is off."))
else if(hasvar(W,"force") && hasvar(W,"damtype"))
user.setClickCooldown(user.get_attack_speed(W))
switch(W.damtype)
if("fire")
health -= W.force * fire_dam_coeff
if("brute")
health -= W.force * brute_dam_coeff
..()
healthcheck()
else
..()
/obj/vehicle/bullet_act(var/obj/item/projectile/Proj)
health -= Proj.get_structure_damage()
..()
healthcheck()
/obj/vehicle/proc/adjust_health(amount)
health = between(0, health + amount, maxhealth)
healthcheck()
/obj/vehicle/ex_act(severity)
switch(severity)
if(1.0)
explode()
return
if(2.0)
health -= rand(5,10)*fire_dam_coeff
health -= rand(10,20)*brute_dam_coeff
healthcheck()
return
if(3.0)
if (prob(50))
health -= rand(1,5)*fire_dam_coeff
health -= rand(1,5)*brute_dam_coeff
healthcheck()
return
return
/obj/vehicle/emp_act(severity)
if(!mechanical)
return
var/was_on = on
stat |= EMPED
var/obj/effect/overlay/pulse2 = new /obj/effect/overlay(src.loc)
pulse2.icon = 'icons/effects/effects.dmi'
pulse2.icon_state = "empdisable"
pulse2.name = "emp sparks"
pulse2.anchored = TRUE
pulse2.set_dir(pick(GLOB.cardinal))
spawn(10)
qdel(pulse2)
if(on)
turn_off()
spawn(severity*300)
stat &= ~EMPED
if(was_on)
turn_on()
/obj/vehicle/attack_ai(mob/user as mob)
return
// For downstream compatibility (in particular Paradise)
/obj/vehicle/proc/handle_rotation()
return
//-------------------------------------------
// Vehicle procs
//-------------------------------------------
/obj/vehicle/proc/turn_on()
if(!mechanical || stat)
return FALSE
if(!cell)
return FALSE
if(powered && cell.charge < charge_use)
return FALSE
if(on)
return FALSE
on = 1
playsound(src, 'sound/effects/vehicle/ignition_car.ogg', 60, 2, -2) //CHOMPedit: New sound effects.
soundloop.start()
set_light(initial(light_range))
update_icon()
return TRUE
/obj/vehicle/proc/turn_off()
if(!on)
return FALSE
if(!mechanical)
return FALSE
on = 0
playsound(src, 'sound/effects/vehicle/engine_off.ogg', 60, 2, -2) //CHOMPedit: New sound effects.
soundloop.stop()
set_light(0)
update_icon()
/obj/vehicle/emag_act(var/remaining_charges, mob/user as mob)
if(!mechanical)
return FALSE
if(!emagged)
emagged = 1
if(locked)
locked = 0
to_chat(user, span_warning("You bypass [src]'s controls."))
return TRUE
/obj/vehicle/proc/explode()
src.visible_message(span_bolddanger("[src] blows apart!"), 1)
playsound(src, 'sound/effects/explosions/vehicleexplosion.ogg', 100, 8, 3) //CHOMPedit: New sound effects.
var/turf/Tsec = get_turf(src)
//stuns people who are thrown off a train that has been blown up
if(isliving(load))
var/mob/living/M = load
M.apply_effects(5, 5)
unload()
if(mechanical)
new /obj/item/stack/rods(Tsec)
new /obj/item/stack/rods(Tsec)
new /obj/item/stack/cable_coil/cut(Tsec)
new /obj/effect/gibspawner/robot(Tsec)
new /obj/effect/decal/cleanable/blood/oil(src.loc)
if(cell)
cell.forceMove(Tsec)
cell.update_icon()
cell = null
qdel(src)
/obj/vehicle/proc/healthcheck()
if(health <= 0)
explode()
/obj/vehicle/proc/powercheck()
if(!mechanical)
return
if(!cell && !powered)
return
if(!cell && powered)
turn_off()
return
if(cell.charge < charge_use)
turn_off()
return
if(cell && powered)
turn_on()
return
/obj/vehicle/proc/insert_cell(var/obj/item/cell/C, var/mob/living/carbon/human/H)
if(!mechanical)
return
if(cell)
return
if(!istype(C))
return
H.drop_from_inventory(C)
C.forceMove(src)
cell = C
powercheck()
to_chat(H, span_notice("You install [C] in [src]."))
/obj/vehicle/proc/remove_cell(var/mob/living/carbon/human/H)
if(!mechanical)
return
if(!cell)
return
to_chat(H, span_notice("You remove [cell] from [src]."))
cell.forceMove(get_turf(H))
H.put_in_hands(cell)
cell = null
powercheck()
/obj/vehicle/proc/RunOver(var/mob/living/M)
return //write specifics for different vehicles
//-------------------------------------------
// Loading/unloading procs
//
// Set specific item restriction checks in
// the vehicle load() definition before
// calling this parent proc.
//-------------------------------------------
/obj/vehicle/proc/load(var/atom/movable/C, var/mob/living/user)
//This loads objects onto the vehicle so they can still be interacted with.
//Define allowed items for loading in specific vehicle definitions.
if(!isturf(C.loc)) //To prevent loading things from someone's inventory, which wouldn't get handled properly.
return 0
if(load || C.anchored)
return 0
// if a create/closet, close before loading
var/obj/structure/closet/crate = C
if(istype(crate))
crate.close()
C.forceMove(loc)
C.set_dir(dir)
C.anchored = TRUE
load = C
if(load_item_visible)
C.pixel_x += load_offset_x
if(ismob(C))
C.pixel_y += mob_offset_y
else
C.pixel_y += load_offset_y
C.layer = layer + 0.1
if(ismob(C) && user)
user_buckle_mob(C, user)
return 1
/obj/vehicle/proc/unload(var/mob/user, var/direction)
if(!load)
return
var/turf/dest = null
//find a turf to unload to
if(direction) //if direction specified, unload in that direction
dest = get_step(src, direction)
else if(user) //if a user has unloaded the vehicle, unload at their feet
dest = get_turf(user)
if(!dest)
dest = get_step_to(src, get_step(src, turn(dir, 90))) //try unloading to the side of the vehicle first if neither of the above are present
//if these all result in the same turf as the vehicle or nullspace, pick a new turf with open space
if(!dest || dest == get_turf(src))
var/list/options = new()
for(var/test_dir in GLOB.alldirs)
var/new_dir = get_step_to(src, get_step(src, test_dir))
if(new_dir && load.Adjacent(new_dir))
options += new_dir
if(options.len)
dest = pick(options)
else
dest = get_turf(src) //otherwise just dump it on the same turf as the vehicle
if(!isturf(dest)) //if there still is nowhere to unload, cancel out since the vehicle is probably in nullspace
return 0
load.forceMove(dest)
load.set_dir(get_dir(loc, dest))
load.anchored = FALSE //we can only load non-anchored items, so it makes sense to set this to false
if(ismob(load))
var/mob/L = load
L.pixel_x = L.default_pixel_x
L.pixel_y = L.default_pixel_y
else
load.pixel_x = initial(load.pixel_x)
load.pixel_y = initial(load.pixel_y)
load.layer = initial(load.layer)
if(ismob(load))
unbuckle_mob(load)
load = null
return 1
//-------------------------------------------------------
// Stat update procs
//-------------------------------------------------------
/obj/vehicle/proc/update_stats()
return
/obj/vehicle/attack_generic(var/mob/user, var/damage, var/attack_message)
if(!damage)
return
visible_message(span_danger("[user] [attack_message] the [src]!"))
user.attack_log += text("\[[time_stamp()]\] [span_red("attacked [src.name]")]")
user.do_attack_animation(src)
src.health -= damage
if(mechanical && prob(10))
new /obj/effect/decal/cleanable/blood/oil(src.loc)
spawn(1) healthcheck()
return 1
/obj/vehicle/take_damage(var/damage)
if(!damage)
return
src.health -= damage
if(mechanical && prob(10))
new /obj/effect/decal/cleanable/blood/oil(src.loc)
spawn(1) healthcheck()
return 1
//ChompADD START
//----------------------------
// Engine sounds datum
//----------------------------
/datum/looping_sound/idle_carengine
mid_sounds = 'sound/effects/vehicle/engine_loop.ogg'
mid_length = 2.60 SECONDS
chance = 100
volume = 10
exclusive = TRUE
volume_chan = VOLUME_CHANNEL_AMBIENCE
//ChompADD END