//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 = MECH_BASE_LAYER density = 1 anchored = 1 animate_movement=1 light_range = 3 buckle_movable = 1 buckle_lying = 0 var/buckling_sound = 'sound/effects/metal_close.ogg' 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/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/flying = FALSE var/organic = FALSE var/corpse = null //------------------------------------------- // Standard procs //------------------------------------------- /obj/vehicle/Initialize() . = ..() setup_vehicle() /obj/vehicle/Destroy() QDEL_NULL(cell) return ..() /obj/vehicle/proc/setup_vehicle() LAZYADD(can_buckle, /mob/living) /obj/vehicle/Move() if(world.time > l_move_time + move_delay) var/old_loc = get_turf(src) if(on && powered && cell.charge < charge_use && !organic) turn_off() var/init_anc = anchored anchored = 0 if(!..()) anchored = init_anc return 0 set_dir(get_dir(old_loc, loc)) anchored = init_anc if(on && powered && !organic) 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 if(load && !istype(load, /datum/vehicle_dummy_load)) load.forceMove(loc) load.set_dir(dir) return 1 else return 0 /obj/vehicle/proc/create_vehicle_overlay() return /obj/vehicle/attackby(obj/item/attacking_item, mob/user) if(istype(attacking_item, /obj/item/hand_labeler)) return if(attacking_item.tool_behaviour == TOOL_SCREWDRIVER && !organic) if(!locked) open = !open update_icon() to_chat(user, SPAN_NOTICE("Maintenance panel is now [open ? "opened" : "closed"].")) else if(attacking_item.tool_behaviour == TOOL_CROWBAR && cell && open && !organic) remove_cell(user) else if(istype(attacking_item, /obj/item/cell) && !cell && open && !organic) insert_cell(attacking_item, user) else if(attacking_item.tool_behaviour == TOOL_WELDER && !organic) var/obj/item/weldingtool/T = attacking_item if(T.welding) if(health < maxhealth) if(open) health = min(maxhealth, health+10) user.setClickCooldown(DEFAULT_ATTACK_COOLDOWN) user.visible_message(SPAN_WARNING("[user] repairs [src]!"), SPAN_NOTICE("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(attacking_item,"force") && hasvar(attacking_item,"damtype")) user.setClickCooldown(DEFAULT_ATTACK_COOLDOWN) switch(attacking_item.damtype) if("fire") health -= attacking_item.force * fire_dam_coeff if("brute") health -= attacking_item.force * brute_dam_coeff ..() healthcheck() else ..() /obj/vehicle/bullet_act(obj/projectile/hitting_projectile, def_zone, piercing_hit) . = ..() if(. != BULLET_ACT_HIT) return . health -= hitting_projectile.get_structure_damage() if (prob(20) && !organic) spark(src, 5, GLOB.alldirs) 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(organic) 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 = 1 pulse2.set_dir(pick(GLOB.cardinals)) QDEL_IN(pulse2, 10) if(on) turn_off() addtimer(CALLBACK(src, PROC_REF(post_emp), was_on), severity * 300) /obj/vehicle/proc/post_emp(was_on) if(organic) return 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(stat) return FALSE if(powered && cell?.charge < charge_use && !organic) return FALSE on = TRUE set_light(initial(light_range)) update_icon() return TRUE /obj/vehicle/proc/turn_off() on = FALSE set_light(0) update_icon() /obj/vehicle/emag_act(var/remaining_charges, mob/user as mob) if(organic) return FALSE if(!emagged) emagged = 1 if(locked) locked = 0 to_chat(user, SPAN_WARNING("You bypass \the [src]'s controls.")) return 1 /obj/vehicle/proc/explode() var/turf/Tsec = get_turf(src) if(organic) visible_message(SPAN_WARNING("\The [src] dies!")) var/body = new corpse(Tsec) if(isliving(body)) var/mob/living/M = body M.death() else visible_message(SPAN_WARNING("\The [src] blows apart!")) new /obj/item/stack/rods(Tsec) new /obj/item/stack/rods(Tsec) new /obj/item/stack/cable_coil/cut(Tsec) if(cell) cell.forceMove(Tsec) cell.update_icon() cell = null //stuns people who are thrown off a train that has been blown up new /obj/effect/gibspawner/robot(Tsec) new /obj/effect/decal/cleanable/blood/oil(src.loc) if(istype(load, /mob/living)) var/mob/living/M = load M.apply_effects(5, 5) unload() qdel(src) /obj/vehicle/proc/healthcheck() if(health <= 0) explode() /obj/vehicle/proc/powercheck() 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(cell) return if(!istype(C)) return H.drop_from_inventory(C,src) cell = C powercheck() to_chat(usr, SPAN_NOTICE("You install [C] in [src].")) /obj/vehicle/proc/remove_cell(var/mob/living/carbon/human/H) if(!cell) return to_chat(usr, 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/carbon/human/H) return //write specifics for different vehicles //------------------------------------------- // Loading/unloading procs // // Set specific item restriction checks in // the vehicle load() definition before // calling this parent proc. //------------------------------------------- /** * Loads an /atom onto the vehicle * * * thing_to_load - The thing to load, an `/obj` or `/mob` * * Returns `TRUE` if the load was successful, `FALSE` otherwise */ /obj/vehicle/proc/load(var/atom/movable/thing_to_load) //This loads objects onto the vehicle so they can still be interacted with. //Define allowed items for loading in specific vehicle definitions. if(!isturf(thing_to_load.loc)) //To prevent loading things from someone's inventory, which wouldn't get handled properly. return FALSE if(load || thing_to_load.anchored) return FALSE // if a crate/closet, close before loading var/obj/structure/closet/closet = thing_to_load if(istype(closet)) closet.close() thing_to_load.forceMove(loc) thing_to_load.set_dir(dir) thing_to_load.anchored = TRUE load = thing_to_load thing_to_load.layer = VEHICLE_LOAD_LAYER if(load_item_visible) thing_to_load.pixel_x += load_offset_x if(ismob(thing_to_load)) thing_to_load.pixel_y += mob_offset_y else if(istype(thing_to_load, /obj/structure/closet/crate)) thing_to_load.pixel_y += load_offset_y else thing_to_load.pixel_y += 10 if(ismob(thing_to_load)) buckle(thing_to_load, thing_to_load) return TRUE /obj/vehicle/buckle(atom/movable/buckling_atom, mob/user) . = ..() if(. && buckling_sound) playsound(src, buckling_sound, 20) /obj/vehicle/user_unbuckle(mob/user) ..() unload(user) return /obj/vehicle/proc/unload(var/mob/user, var/direction) if(!load) return var/turf/dest = null var/turf/v_turf = get_turf(src) //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 == v_turf || dest.is_hole) var/list/options = new() var/list/safe_options = new() for(var/test_dir in GLOB.alldirs) var/turf/T = get_step_to(src, get_step(src, test_dir)) if(istype(T) && load.Adjacent(T)) options += T if(!T.is_hole) safe_options += T if(safe_options.len) dest = pick(safe_options) else if(!v_turf.is_hole) dest = v_turf else if(options.len) // No safe tiles to unload -- you're going to the shadow realm, jimbo dest = pick(options) if(!isturf(dest)) dest = v_turf //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 = 0 //we can only load non-anchored items, so it makes sense to set this to false load.pixel_x = initial(load.pixel_x) load.pixel_y = initial(load.pixel_y) load.layer = initial(load.layer) if(ismob(load)) unbuckle(user) if(ishuman(load)) var/mob/living/carbon/human/H = load H.update_body() load = null return 1 // This exists to stop a weird jumping motion when you disembark. // It essentially makes disembarkation count as a movement. // Yes, it's not the full calculation. But it's relatively close, and will make it seamless. /obj/vehicle/post_buckle(var/mob/M) if (M.client) M.client.move_delay = M.movement_delay() + GLOB.config.walk_speed //------------------------------------------------------- // Stat update procs //------------------------------------------------------- /obj/vehicle/proc/update_stats() return /obj/vehicle/attack_generic(mob/user, damage, attack_message, environment_smash, armor_penetration, attack_flags, damage_type) if(!damage) return visible_message(SPAN_DANGER("[user] [attack_message] the [src]!")) user.attack_log += "\[[time_stamp()]\] attacked [src.name]" user.do_attack_animation(src) src.health -= damage if(prob(10)) new /obj/effect/decal/cleanable/blood/oil(src.loc) spawn(1) healthcheck() return 1 /obj/vehicle/can_fall(turf/below, turf/simulated/open/dest = src.loc) if (flying) return FALSE if (LAZYLEN(dest.climbers) && (src in dest.climbers)) return FALSE if (!dest.is_hole) return FALSE // See if something prevents us from falling. for(var/atom/A in below) if(!A.CanPass(src, dest)) return FALSE return TRUE