mirror of
https://github.com/Bubberstation/Bubberstation.git
synced 2026-08-31 09:08:30 +01:00
## About The Pull Request Ok so like, side map right? It makes things higher up in the world render above things lower down in the world. Most of the time this is what we want, but it is NOT what we want for floors. Floors are allowed to be larger then 32x32, and if they are we want them to render based off JUST their layer. If we don't allow this grass turfs and others get cut off on their bottom edge, which looks WEIRD. In order to make this happen, we can add TOPDOWN_LAYER to every layer on the floor plane and disable sidemap. I've added documentation for this to VISUALS.md, and have also implemented unit test errors to prevent mixing TOPDOWN layers with non topdown planes (or vis versa). This new test adds ~1 second to tests, which is I think a perfectly scrumpulent number. EDIT: I nerd sniped myself and implemented sidemap layering and lighting for cameras (also larger then 32x32 icon support for getflat) The lighting isn't perfect, we don't handle things displaying in the void all that well (I am convinced getflat blending is broken but I have no debugger so I can't fix it properly), but it'll do. This came up cause I had to fix another layering issue in cameras and thought I might as well go all in.  ## Why It's Good For The Game Old:  New:  ## Changelog 🆑 fix: Grass turfs will render properly now. Reworked how floors render, please report any bugs! fix: Cameras now properly capture lighting fix: The layering seen in photos should better match the actual game /🆑
966 lines
39 KiB
Plaintext
966 lines
39 KiB
Plaintext
/**
|
|
* Base transport structure. A single tile that can form a modular set with neighbouring tiles
|
|
* This type holds elevators and trams
|
|
*/
|
|
/obj/structure/transport/linear
|
|
name = "linear transport module"
|
|
desc = "A lightweight lift platform. It moves."
|
|
icon = 'icons/obj/smooth_structures/catwalk.dmi'
|
|
icon_state = "catwalk-0"
|
|
base_icon_state = "catwalk"
|
|
density = FALSE
|
|
anchored = TRUE
|
|
resistance_flags = INDESTRUCTIBLE | LAVA_PROOF | FIRE_PROOF | UNACIDABLE | ACID_PROOF
|
|
armor_type = /datum/armor/transport_module
|
|
max_integrity = 50
|
|
layer = TRAM_FLOOR_LAYER
|
|
plane = GAME_PLANE
|
|
smoothing_flags = SMOOTH_BITMASK
|
|
smoothing_groups = SMOOTH_GROUP_INDUSTRIAL_LIFT
|
|
canSmoothWith = SMOOTH_GROUP_INDUSTRIAL_LIFT
|
|
obj_flags = BLOCK_Z_OUT_DOWN
|
|
appearance_flags = PIXEL_SCALE|KEEP_TOGETHER //no TILE_BOUND since we're potentially multitile
|
|
// If we don't do this, we'll build our overlays early, and fuck up how we're rendered
|
|
blocks_emissive = EMISSIVE_BLOCK_NONE
|
|
|
|
///ID used to determine what transport types we can merge with
|
|
var/transport_id = TRANSPORT_TYPE_ELEVATOR
|
|
|
|
///if true, the elevator works through floors
|
|
var/pass_through_floors = FALSE
|
|
|
|
///what movables on our platform that we are moving
|
|
var/list/atom/movable/transport_contents = list()
|
|
///weakrefs to the contents we have when we're first created. stored so that admins can clear the tram to its initial state
|
|
///if someone put a bunch of stuff onto it.
|
|
var/list/datum/weakref/initial_contents = list()
|
|
|
|
///what glide_size we set our moving contents to.
|
|
var/glide_size_override = 8
|
|
///movables inside transport_contents who had their glide_size changed since our last movement.
|
|
///used so that we dont have to change the glide_size of every object every movement, which scales to cost more than you'd think
|
|
var/list/atom/movable/changed_gliders = list()
|
|
|
|
///decisecond delay between horizontal movements. cannot make the tram move faster than 1 movement per world.tick_lag. only used to give to the transport_controller
|
|
var/speed_limiter = 0.5
|
|
|
|
///master datum that controls our movement. in general /transport/linear subtypes control moving themselves, and
|
|
/// /datum/transport_controller instances control moving the entire tram and any behavior associated with that.
|
|
var/datum/transport_controller/linear/transport_controller_datum
|
|
///what subtype of /datum/transport_controller to create for itself if no other platform on this tram has created one yet.
|
|
///very important for some behaviors since
|
|
var/transport_controller_type = /datum/transport_controller/linear
|
|
|
|
///how many tiles this platform extends on the x axis
|
|
var/width = 1
|
|
///how many tiles this platform extends on the y axis (north-south not up-down, that would be the z axis)
|
|
var/height = 1
|
|
|
|
///if TRUE, this platform will late initialize and then expand to become a multitile object across all other linked platforms on this z level
|
|
var/create_modular_set = FALSE
|
|
|
|
/// Does our elevator warn people (with visual effects) when moving down?
|
|
var/warns_on_down_movement = FALSE
|
|
/// if TRUE, we will gib anyone we land on top of. if FALSE, we will just apply damage with a serious wound penalty.
|
|
var/violent_landing = TRUE
|
|
/// damage multiplier if a mob is hit by the lift while it is moving horizontally
|
|
var/collision_lethality = 1
|
|
/// How long does it take for the elevator to move vertically?
|
|
var/elevator_vertical_speed = 2 SECONDS
|
|
|
|
/// We use a radial to travel primarily, instead of a button / ui
|
|
var/radial_travel = TRUE
|
|
/// A lazylist of REFs to all mobs which have a radial open currently
|
|
var/list/current_operators
|
|
|
|
/datum/armor/transport_module
|
|
melee = 80
|
|
bullet = 90
|
|
bomb = 70
|
|
fire = 100
|
|
acid = 100
|
|
|
|
/obj/structure/transport/linear/Initialize(mapload)
|
|
. = ..()
|
|
// Yes if it's VV'd it won't be accurate but it probably shouldn't ever be
|
|
if(radial_travel)
|
|
AddElement(/datum/element/contextual_screentip_bare_hands, lmb_text = "Send Transport")
|
|
|
|
ADD_TRAIT(src, TRAIT_CHASM_STOPPER, INNATE_TRAIT)
|
|
set_movement_registrations()
|
|
|
|
//since transport_controller datums find all connected platforms when a transport structure first creates it and then
|
|
//sets those platforms' transport_controller_datum to itself, this check will only evaluate to true once per tram platform
|
|
if(!transport_controller_datum && transport_controller_type)
|
|
transport_controller_datum = new transport_controller_type(src)
|
|
return INITIALIZE_HINT_LATELOAD
|
|
|
|
/obj/structure/transport/linear/LateInitialize()
|
|
//after everything is initialized the transport controller can order everything
|
|
transport_controller_datum.order_platforms_by_z_level()
|
|
|
|
/obj/structure/transport/linear/Destroy()
|
|
transport_controller_datum = null
|
|
return ..()
|
|
|
|
|
|
///set the movement registrations to our current turf(s) so contents moving out of our tile(s) are removed from our movement lists
|
|
/obj/structure/transport/linear/proc/set_movement_registrations(list/turfs_to_set)
|
|
for(var/turf/turf_loc as anything in turfs_to_set || locs)
|
|
RegisterSignal(turf_loc, COMSIG_ATOM_EXITED, PROC_REF(uncrossed_remove_item_from_transport))
|
|
RegisterSignals(turf_loc, list(COMSIG_ATOM_ENTERED,COMSIG_ATOM_AFTER_SUCCESSFUL_INITIALIZED_ON), PROC_REF(add_item_on_transport))
|
|
|
|
///unset our movement registrations from turfs that no longer contain us (or every loc if turfs_to_unset is unspecified)
|
|
/obj/structure/transport/linear/proc/unset_movement_registrations(list/turfs_to_unset)
|
|
var/static/list/registrations = list(COMSIG_ATOM_ENTERED, COMSIG_ATOM_EXITED, COMSIG_ATOM_AFTER_SUCCESSFUL_INITIALIZED_ON)
|
|
for(var/turf/turf_loc as anything in turfs_to_unset || locs)
|
|
UnregisterSignal(turf_loc, registrations)
|
|
|
|
|
|
/obj/structure/transport/linear/proc/uncrossed_remove_item_from_transport(datum/source, atom/movable/gone, direction)
|
|
SIGNAL_HANDLER
|
|
if(!(gone.loc in locs))
|
|
remove_item_from_transport(gone)
|
|
|
|
/obj/structure/transport/linear/proc/remove_item_from_transport(atom/movable/potential_rider)
|
|
SIGNAL_HANDLER
|
|
if(!(potential_rider in transport_contents))
|
|
return
|
|
if(isliving(potential_rider) && HAS_TRAIT(potential_rider, TRAIT_CANNOT_BE_UNBUCKLED))
|
|
REMOVE_TRAIT(potential_rider, TRAIT_CANNOT_BE_UNBUCKLED, BUCKLED_TRAIT)
|
|
|
|
transport_contents -= potential_rider
|
|
changed_gliders -= potential_rider
|
|
|
|
UnregisterSignal(potential_rider, list(COMSIG_QDELETING, COMSIG_MOVABLE_UPDATE_GLIDE_SIZE))
|
|
|
|
/obj/structure/transport/linear/proc/add_item_on_transport(datum/source, atom/movable/new_transport_contents)
|
|
SIGNAL_HANDLER
|
|
var/static/list/blacklisted_types = typecacheof(list(/obj/structure/fluff/tram_rail, /obj/effect/decal/cleanable, /obj/structure/transport/linear, /mob/camera))
|
|
if(is_type_in_typecache(new_transport_contents, blacklisted_types) || new_transport_contents.invisibility == INVISIBILITY_ABSTRACT || HAS_TRAIT(new_transport_contents, TRAIT_UNDERFLOOR)) //prevents the tram from stealing things like landmarks
|
|
return FALSE
|
|
if(new_transport_contents in transport_contents)
|
|
return FALSE
|
|
|
|
if(isliving(new_transport_contents) && !HAS_TRAIT(new_transport_contents, TRAIT_CANNOT_BE_UNBUCKLED))
|
|
ADD_TRAIT(new_transport_contents, TRAIT_CANNOT_BE_UNBUCKLED, BUCKLED_TRAIT)
|
|
|
|
transport_contents += new_transport_contents
|
|
RegisterSignal(new_transport_contents, COMSIG_QDELETING, PROC_REF(remove_item_from_transport))
|
|
|
|
return TRUE
|
|
|
|
///adds everything on our tile that can be added to our transport_contents and initial_contents lists when we're created
|
|
/obj/structure/transport/linear/proc/add_initial_contents()
|
|
for(var/turf/turf_loc in locs)
|
|
for(var/atom/movable/movable_contents as anything in turf_loc)
|
|
if(movable_contents == src)
|
|
continue
|
|
|
|
if(add_item_on_transport(src, movable_contents))
|
|
|
|
var/datum/weakref/new_initial_contents = WEAKREF(movable_contents)
|
|
if(!new_initial_contents)
|
|
continue
|
|
|
|
initial_contents += new_initial_contents
|
|
|
|
///verify the movables in our list of contents are actually on our loc
|
|
/obj/structure/transport/linear/proc/verify_transport_contents()
|
|
for(var/atom/movable/movable_contents as anything in transport_contents)
|
|
if(!(movable_contents.loc in locs))
|
|
remove_item_from_transport(movable_contents)
|
|
|
|
///signal handler for COMSIG_MOVABLE_UPDATE_GLIDE_SIZE: when a movable in transport_contents changes its glide_size independently.
|
|
///adds that movable to a lazy list, movables in that list have their glide_size updated when the tram next moves
|
|
/obj/structure/transport/linear/proc/on_changed_glide_size(atom/movable/moving_contents, new_glide_size)
|
|
SIGNAL_HANDLER
|
|
if(new_glide_size != glide_size_override)
|
|
changed_gliders += moving_contents
|
|
|
|
|
|
///make this tram platform multitile, expanding to cover all the tram platforms adjacent to us and deleting them. makes movement more efficient.
|
|
///the platform becoming multitile should be in the lower left corner since thats assumed to be the loc of multitile objects
|
|
/obj/structure/transport/linear/proc/create_modular_set(min_x, min_y, max_x, max_y, z)
|
|
|
|
if(!(min_x && min_y && max_x && max_y && z))
|
|
for(var/obj/structure/transport/linear/other_transport as anything in transport_controller_datum.transport_modules)
|
|
if(other_transport.z != z)
|
|
continue
|
|
|
|
min_x = min(min_x, other_transport.x)
|
|
max_x = max(max_x, other_transport.x)
|
|
|
|
min_y = min(min_y, other_transport.y)
|
|
max_y = max(max_y, other_transport.y)
|
|
|
|
var/turf/lower_left_corner = locate(min_x, min_y, z)
|
|
var/obj/structure/transport/linear/primary_module = locate() in lower_left_corner
|
|
|
|
if(!primary_module)
|
|
stack_trace("no lift in the lower left corner of a lift level!")
|
|
return FALSE
|
|
|
|
if(primary_module != src)
|
|
//the loc of a multitile object must always be the lower left corner
|
|
return primary_module.create_modular_set()
|
|
|
|
width = (max_x - min_x) + 1
|
|
height = (max_y - min_y) + 1
|
|
|
|
///list of turfs we dont go over. if for whatever reason we encounter an already multitile lift platform
|
|
///we add all of its locs to this list so we dont add that lift platform multiple times as we iterate through its locs
|
|
var/list/locs_to_skip = locs.Copy()
|
|
|
|
bound_width = bound_width * width
|
|
bound_height = bound_height * height
|
|
|
|
//multitile movement code assumes our loc is on the lower left corner of our bounding box
|
|
|
|
var/first_x = 0
|
|
var/first_y = 0
|
|
|
|
var/last_x = max(max_x - min_x, 0)
|
|
var/last_y = max(max_y - min_y, 0)
|
|
|
|
for(var/y in first_y to last_y)
|
|
|
|
var/y_pixel_offset = world.icon_size * y
|
|
|
|
for(var/x in first_x to last_x)
|
|
|
|
var/x_pixel_offset = world.icon_size * x
|
|
|
|
var/turf/set_turf = locate(x + min_x, y + min_y, z)
|
|
|
|
if(!set_turf)
|
|
continue
|
|
|
|
if(set_turf in locs_to_skip)
|
|
continue
|
|
|
|
var/obj/structure/transport/linear/other_transport = locate() in set_turf
|
|
|
|
if(!other_transport)
|
|
continue
|
|
|
|
locs_to_skip += other_transport.locs.Copy()//make sure we never go over multitile platforms multiple times
|
|
|
|
other_transport.pixel_x = x_pixel_offset
|
|
other_transport.pixel_y = y_pixel_offset
|
|
|
|
overlays += other_transport
|
|
|
|
//now we vore all the other lifts connected to us on our z level
|
|
for(var/obj/structure/transport/linear/other_transport in transport_controller_datum.transport_modules)
|
|
if(other_transport == src || other_transport.z != z)
|
|
continue
|
|
|
|
transport_controller_datum.transport_modules -= other_transport
|
|
if(other_transport.transport_contents)
|
|
transport_contents |= other_transport.transport_contents
|
|
if(other_transport.initial_contents)
|
|
initial_contents |= other_transport.initial_contents
|
|
|
|
qdel(other_transport)
|
|
|
|
transport_controller_datum.create_modular_set = TRUE
|
|
|
|
var/turf/old_loc = loc
|
|
|
|
forceMove(locate(min_x, min_y, z))//move to the lower left corner
|
|
set_movement_registrations(locs - old_loc)
|
|
blocks_emissive = EMISSIVE_BLOCK_GENERIC
|
|
update_appearance()
|
|
return TRUE
|
|
|
|
///returns an unordered list of all lift platforms adjacent to us. used so our transport_controller_datum can control all connected platforms.
|
|
///includes platforms directly above or below us as well. only includes platforms with an identical transport_id to our own.
|
|
/obj/structure/transport/linear/proc/module_adjacency(datum/transport_controller/transport_controller_datum)
|
|
. = list()
|
|
for(var/direction in GLOB.cardinals_multiz)
|
|
var/obj/structure/transport/linear/neighbor = locate() in get_step_multiz(src, direction)
|
|
if(!neighbor || neighbor.transport_id != transport_id)
|
|
continue
|
|
. += neighbor
|
|
|
|
///main proc for moving the lift in the direction [travel_direction]. handles horizontal and/or vertical movement for multi platformed lifts and multitile lifts.
|
|
/obj/structure/transport/linear/proc/travel(travel_direction)
|
|
var/list/things_to_move = transport_contents
|
|
var/turf/destination
|
|
if(!isturf(travel_direction))
|
|
destination = get_step_multiz(src, travel_direction)
|
|
else
|
|
destination = travel_direction
|
|
travel_direction = get_dir_multiz(loc, travel_direction)
|
|
|
|
var/x_offset = ROUND_UP(bound_width / 32) - 1 //how many tiles our horizontally farthest edge is from us
|
|
var/y_offset = ROUND_UP(bound_height / 32) - 1 //how many tiles our vertically farthest edge is from us
|
|
|
|
//the x coordinate of the edge furthest from our future destination, which would be our right hand side
|
|
var/back_edge_x = destination.x + x_offset//if we arent multitile this should just be destination.x
|
|
var/upper_edge_y = destination.y + y_offset
|
|
|
|
var/turf/upper_right_corner = locate(min(world.maxx, back_edge_x), min(world.maxy, upper_edge_y), destination.z)
|
|
|
|
var/list/dest_locs = block(
|
|
destination,
|
|
upper_right_corner
|
|
)
|
|
|
|
var/list/entering_locs = dest_locs - locs
|
|
var/list/exited_locs = locs - dest_locs
|
|
|
|
if(travel_direction == DOWN)
|
|
for(var/turf/dest_turf as anything in entering_locs)
|
|
SEND_SIGNAL(dest_turf, COMSIG_TURF_INDUSTRIAL_LIFT_ENTER, things_to_move)
|
|
|
|
if(iswallturf(dest_turf))
|
|
var/turf/closed/wall/hit_wall = dest_turf
|
|
do_sparks(2, FALSE, hit_wall)
|
|
hit_wall.dismantle_wall(devastated = TRUE)
|
|
for(var/mob/nearby_witness in urange(8, src))
|
|
shake_camera(nearby_witness, 2, 3)
|
|
playsound(hit_wall, 'sound/effects/meteorimpact.ogg', 100, TRUE)
|
|
|
|
for(var/mob/living/crushed in dest_turf.contents)
|
|
to_chat(crushed, span_userdanger("You are crushed by [src]!"))
|
|
if(violent_landing)
|
|
// Violent landing = gibbed. But the nicest kind of gibbing, keeping everything intact.
|
|
crushed.investigate_log("has been gibbed by [src].", INVESTIGATE_DEATHS)
|
|
crushed.gib(DROP_ALL_REMAINS)
|
|
else
|
|
// Less violent landing simply crushes every bone in your body.
|
|
crushed.Paralyze(30 SECONDS, ignore_canstun = TRUE)
|
|
crushed.apply_damage(30, BRUTE, BODY_ZONE_CHEST, wound_bonus = 30)
|
|
crushed.apply_damage(20, BRUTE, BODY_ZONE_HEAD, wound_bonus = 25)
|
|
crushed.apply_damage(15, BRUTE, BODY_ZONE_L_LEG, wound_bonus = 15)
|
|
crushed.apply_damage(15, BRUTE, BODY_ZONE_R_LEG, wound_bonus = 15)
|
|
crushed.apply_damage(15, BRUTE, BODY_ZONE_L_ARM, wound_bonus = 15)
|
|
crushed.apply_damage(15, BRUTE, BODY_ZONE_R_ARM, wound_bonus = 15)
|
|
|
|
else if(travel_direction == UP)
|
|
for(var/turf/dest_turf as anything in entering_locs)
|
|
///handles any special interactions objects could have with the lift/tram, handled on the item itself
|
|
SEND_SIGNAL(dest_turf, COMSIG_TURF_INDUSTRIAL_LIFT_ENTER, things_to_move)
|
|
|
|
if(iswallturf(dest_turf))
|
|
var/turf/closed/wall/hit_wall = dest_turf
|
|
do_sparks(2, FALSE, hit_wall)
|
|
hit_wall.dismantle_wall(devastated = TRUE)
|
|
for(var/mob/client_mob in SSspatial_grid.orthogonal_range_search(src, SPATIAL_GRID_CONTENTS_TYPE_CLIENTS, 8))
|
|
shake_camera(client_mob, 2, 3)
|
|
playsound(hit_wall, 'sound/effects/meteorimpact.ogg', 100, TRUE)
|
|
|
|
else
|
|
///potentially finds a spot to throw the victim at for daring to be hit by a tram. is null if we havent found anything to throw
|
|
var/atom/throw_target
|
|
|
|
for(var/turf/dest_turf as anything in entering_locs)
|
|
///handles any special interactions objects could have with the lift/tram, handled on the item itself
|
|
SEND_SIGNAL(dest_turf, COMSIG_TURF_INDUSTRIAL_LIFT_ENTER, things_to_move)
|
|
|
|
if(iswallturf(dest_turf))
|
|
var/turf/closed/wall/collided_wall = dest_turf
|
|
do_sparks(2, FALSE, collided_wall)
|
|
collided_wall.dismantle_wall(devastated = TRUE)
|
|
for(var/mob/client_mob in SSspatial_grid.orthogonal_range_search(collided_wall, SPATIAL_GRID_CONTENTS_TYPE_CLIENTS, 8))
|
|
shake_camera(client_mob, duration = 2, strength = 3)
|
|
|
|
playsound(collided_wall, 'sound/effects/meteorimpact.ogg', 100, TRUE)
|
|
|
|
if(ismineralturf(dest_turf))
|
|
var/turf/closed/mineral/dest_mineral_turf = dest_turf
|
|
for(var/mob/client_mob in SSspatial_grid.orthogonal_range_search(dest_mineral_turf, SPATIAL_GRID_CONTENTS_TYPE_CLIENTS, 8))
|
|
shake_camera(client_mob, duration = 2, strength = 3)
|
|
dest_mineral_turf.gets_drilled(give_exp = FALSE)
|
|
|
|
for(var/obj/structure/victim_structure in dest_turf.contents)
|
|
if(QDELING(victim_structure))
|
|
continue
|
|
if(!is_type_in_typecache(victim_structure, transport_controller_datum.ignored_smashthroughs) && victim_structure.layer >= LOW_OBJ_LAYER)
|
|
|
|
if(victim_structure.anchored && initial(victim_structure.anchored) == TRUE)
|
|
visible_message(span_danger("[src] smashes through [victim_structure]!"))
|
|
victim_structure.deconstruct(FALSE)
|
|
|
|
else
|
|
if(!throw_target)
|
|
throw_target = get_edge_target_turf(src, turn(travel_direction, pick(45, -45)))
|
|
visible_message(span_danger("[src] violently rams [victim_structure] out of the way!"))
|
|
victim_structure.anchored = FALSE
|
|
victim_structure.take_damage(rand(20, 25) * collision_lethality)
|
|
victim_structure.throw_at(throw_target, 200 * collision_lethality, 4 * collision_lethality)
|
|
|
|
for(var/obj/machinery/victim_machine in dest_turf.contents)
|
|
if(QDELING(victim_machine))
|
|
continue
|
|
if(is_type_in_typecache(victim_machine, transport_controller_datum.ignored_smashthroughs))
|
|
continue
|
|
if(istype(victim_machine, /obj/machinery/field)) //graceful break handles this scenario
|
|
continue
|
|
if(victim_machine.layer >= LOW_OBJ_LAYER) //avoids stuff that is probably flush with the ground
|
|
playsound(src, 'sound/effects/bang.ogg', 50, TRUE)
|
|
visible_message(span_danger("[src] smashes through [victim_machine]!"))
|
|
qdel(victim_machine)
|
|
|
|
for(var/mob/living/victim_living in dest_turf.contents)
|
|
var/damage_multiplier = victim_living.maxHealth * 0.01
|
|
var/extra_ouch = FALSE // if emagged you're gonna have a really bad time
|
|
if(speed_limiter == 0.5) // slow trams don't cause extra damage
|
|
for(var/obj/structure/tram/spoiler/my_spoiler in transport_contents)
|
|
if(get_dist(my_spoiler, victim_living) != 1)
|
|
continue
|
|
|
|
if(my_spoiler.deployed)
|
|
extra_ouch = TRUE
|
|
break
|
|
|
|
if(transport_controller_datum.ignored_smashthroughs[victim_living.type])
|
|
continue
|
|
to_chat(victim_living, span_userdanger("[src] collides into you!"))
|
|
playsound(src, 'sound/effects/splat.ogg', 50, TRUE)
|
|
var/damage = 0
|
|
switch(extra_ouch)
|
|
if(TRUE)
|
|
playsound(src, 'sound/effects/grillehit.ogg', 50, TRUE)
|
|
var/obj/item/bodypart/head/head = victim_living.get_bodypart("head")
|
|
if(head)
|
|
log_combat(src, victim_living, "beheaded")
|
|
head.dismember()
|
|
victim_living.regenerate_icons()
|
|
add_overlay(mutable_appearance(icon, "blood_overlay"))
|
|
register_collision(points = 3)
|
|
|
|
if(FALSE)
|
|
log_combat(src, victim_living, "collided with")
|
|
if(prob(15)) //sorry buddy, luck wasn't on your side
|
|
damage = 29 * collision_lethality * damage_multiplier
|
|
else
|
|
damage = rand(7, 21) * collision_lethality * damage_multiplier
|
|
victim_living.apply_damage(2 * damage, BRUTE, BODY_ZONE_HEAD, wound_bonus = 7)
|
|
victim_living.apply_damage(3 * damage, BRUTE, BODY_ZONE_CHEST, wound_bonus = 21)
|
|
victim_living.apply_damage(0.5 * damage, BRUTE, BODY_ZONE_L_LEG, wound_bonus = 14)
|
|
victim_living.apply_damage(0.5 * damage, BRUTE, BODY_ZONE_R_LEG, wound_bonus = 14)
|
|
victim_living.apply_damage(0.5 * damage, BRUTE, BODY_ZONE_L_ARM, wound_bonus = 14)
|
|
victim_living.apply_damage(0.5 * damage, BRUTE, BODY_ZONE_R_ARM, wound_bonus = 14)
|
|
|
|
if(QDELETED(victim_living)) //in case it was a mob that dels on death
|
|
continue
|
|
if(!throw_target)
|
|
throw_target = get_edge_target_turf(src, turn(travel_direction, pick(45, -45)))
|
|
|
|
var/turf/turf_to_bloody = get_turf(victim_living)
|
|
turf_to_bloody.add_mob_blood(victim_living)
|
|
|
|
victim_living.throw_at()
|
|
//if travel_direction EAST, will turn to the NORTHEAST or SOUTHEAST and throw the ran over guy away
|
|
var/datum/callback/land_slam = new(victim_living, TYPE_PROC_REF(/mob/living/, tram_slam_land))
|
|
victim_living.throw_at(throw_target, 200 * collision_lethality, 4 * collision_lethality, callback = land_slam)
|
|
|
|
//increment the hit counters
|
|
if(ismob(victim_living) && victim_living.client && istype(transport_controller_datum, /datum/transport_controller/linear/tram))
|
|
register_collision(points = 1)
|
|
|
|
unset_movement_registrations(exited_locs)
|
|
group_move(things_to_move, travel_direction)
|
|
set_movement_registrations(entering_locs)
|
|
|
|
/obj/structure/transport/linear/proc/register_collision(points = 1)
|
|
SSpersistence.tram_hits_this_round += points
|
|
SSblackbox.record_feedback("amount", "tram_collision", points)
|
|
var/datum/transport_controller/linear/tram/tram_controller = transport_controller_datum
|
|
ASSERT(istype(tram_controller))
|
|
tram_controller.register_collision(points)
|
|
|
|
///move the movers list of movables on our tile to destination if we successfully move there first.
|
|
///this is like calling forceMove() on everything in movers and ourselves, except nothing in movers
|
|
///has destination.Entered() and origin.Exited() called on them, as only our movement can be perceived.
|
|
///none of the movers are able to react to the movement of any other mover, saving a lot of needless processing cost
|
|
///and is more sensible. without this, if you and a banana are on the same platform, when that platform moves you will slip
|
|
///on the banana even if youre not moving relative to it.
|
|
/obj/structure/transport/linear/proc/group_move(list/atom/movable/movers, movement_direction)
|
|
if(movement_direction == NONE)
|
|
stack_trace("a transport was told to move to somewhere it already is!")
|
|
return FALSE
|
|
|
|
var/turf/our_dest = get_step(src, movement_direction)
|
|
|
|
//vars updated per mover
|
|
var/turf/mover_old_loc
|
|
var/turf/mover_old_area
|
|
|
|
var/turf/mover_new_loc
|
|
var/turf/mover_new_area
|
|
|
|
if(glide_size != glide_size_override)
|
|
set_glide_size(glide_size_override)
|
|
|
|
forceMove(our_dest)
|
|
if(loc != our_dest || QDELETED(src))//check if our movement succeeded, if it didnt then the movers cant be moved
|
|
return FALSE
|
|
|
|
for(var/atom/movable/mover as anything in changed_gliders)
|
|
if(QDELETED(mover))
|
|
movers -= mover
|
|
continue
|
|
|
|
if(mover.glide_size != glide_size_override)
|
|
mover.set_glide_size(glide_size_override)
|
|
|
|
changed_gliders.Cut()
|
|
|
|
for(var/atom/movable/mover as anything in movers)
|
|
if(QDELETED(mover))
|
|
movers -= mover
|
|
continue
|
|
|
|
//another O(n) set of read operations, not ideal given datum var read times.
|
|
//ideally we would only need to do this check once per tile with contents (which is constant per tram, while contents can scale infinitely)
|
|
//and then the only O(n) process is calling these procs for each contents that actually changes areas
|
|
//but that approach is probably a lot buggier. itd be nice to have it figured out though
|
|
mover_old_loc = mover.loc
|
|
mover_old_area = mover_old_loc.loc
|
|
|
|
mover.loc = (mover_new_loc = get_step(mover, movement_direction))
|
|
mover_new_area = mover_new_loc.loc
|
|
|
|
|
|
if(mover_old_area != mover_new_area)
|
|
mover_old_area.Exited(mover, movement_direction)
|
|
mover_new_area.Entered(mover, mover_new_area)
|
|
|
|
mover.Moved(mover_old_loc, movement_direction, TRUE, null, FALSE)
|
|
|
|
|
|
return TRUE
|
|
|
|
/**
|
|
* reset the contents of this lift platform to its original state in case someone put too much shit on it.
|
|
* everything that is considered foreign is deleted, you can configure what is considered foreign.
|
|
*
|
|
* used by an admin via calling reset_lift_contents() on our transport_controller_datum.
|
|
*
|
|
* Arguments:
|
|
* * consider_anything_past - number. if > 0 this platform will only handle foreign contents that exceed this number on each of our locs
|
|
* * foreign_objects - bool. if true this platform will consider /atom/movable's that arent mobs as part of foreign contents
|
|
* * foreign_non_player_mobs - bool. if true we consider mobs that dont have a mind to be foreign
|
|
* * consider_player_mobs - bool. if true we consider player mobs to be foreign. only works if foreign_non_player_mobs is true as well
|
|
*/
|
|
/obj/structure/transport/linear/proc/reset_contents(consider_anything_past = 0, foreign_objects = TRUE, foreign_non_player_mobs = TRUE, consider_player_mobs = FALSE)
|
|
if(!foreign_objects && !foreign_non_player_mobs && !consider_player_mobs)
|
|
return FALSE
|
|
|
|
consider_anything_past = isnum(consider_anything_past) ? max(consider_anything_past, 0) : 0
|
|
//just in case someone fucks up the arguments
|
|
|
|
if(consider_anything_past && length(transport_contents) <= consider_anything_past)
|
|
return FALSE
|
|
|
|
///list of resolve()'d initial_contents that are still in transport_contents
|
|
var/list/atom/movable/original_contents = list(src)
|
|
|
|
///list of objects we consider foreign according to the given arguments
|
|
var/list/atom/movable/foreign_contents = list()
|
|
|
|
|
|
for(var/datum/weakref/initial_contents_ref as anything in initial_contents)
|
|
if(!initial_contents_ref)
|
|
continue
|
|
|
|
var/atom/movable/resolved_contents = initial_contents_ref.resolve()
|
|
|
|
if(!resolved_contents)
|
|
continue
|
|
|
|
if(!(resolved_contents in transport_contents))
|
|
continue
|
|
|
|
original_contents += resolved_contents
|
|
|
|
for(var/turf/turf_loc as anything in locs)
|
|
var/list/atom/movable/foreign_contents_in_loc = list()
|
|
|
|
for(var/atom/movable/foreign_movable as anything in (turf_loc.contents - original_contents))
|
|
if(foreign_objects && ismovable(foreign_movable) && !ismob(foreign_movable) && !istype(foreign_movable, /obj/effect/landmark/transport/nav_beacon))
|
|
foreign_contents_in_loc += foreign_movable
|
|
continue
|
|
|
|
if(foreign_non_player_mobs && ismob(foreign_movable))
|
|
var/mob/foreign_mob = foreign_movable
|
|
if(consider_player_mobs || !foreign_mob.mind)
|
|
foreign_contents_in_loc += foreign_mob
|
|
continue
|
|
|
|
if(consider_anything_past)
|
|
foreign_contents_in_loc.len -= consider_anything_past
|
|
//hey cool this works, neat. this takes from the opposite side of the list that youd expect but its easy so idc
|
|
//also this means that if you use consider_anything_past then foreign mobs are less likely to be deleted than foreign objects
|
|
//because internally the contents list is 2 linked lists of obj contents - mob contents, thus mobs are always last in the order
|
|
//when you iterate it.
|
|
|
|
foreign_contents += foreign_contents_in_loc
|
|
|
|
for(var/atom/movable/contents_to_delete as anything in foreign_contents)
|
|
qdel(contents_to_delete)
|
|
|
|
return TRUE
|
|
|
|
/// Callback / general proc to check if the lift is usable by the passed mob.
|
|
/obj/structure/transport/linear/proc/can_open_lift_radial(mob/living/user, starting_position)
|
|
// Gotta be a living mob
|
|
if(!isliving(user))
|
|
return FALSE
|
|
// Gotta be awake and aware
|
|
if(user.incapacitated())
|
|
return FALSE
|
|
// Maintain the god given right to fight an elevator
|
|
if(user.combat_mode)
|
|
return FALSE
|
|
// Gotta be by the lift
|
|
if(!user.Adjacent(src))
|
|
return FALSE
|
|
// If the lift moves while the radial is open close that shit
|
|
if(starting_position != loc)
|
|
return FALSE
|
|
|
|
return TRUE
|
|
|
|
/// Opens the radial for the lift, allowing the user to move it around.
|
|
/obj/structure/transport/linear/proc/open_lift_radial(mob/living/user)
|
|
var/starting_position = loc
|
|
if(!can_open_lift_radial(user, starting_position))
|
|
return
|
|
// One radial per person
|
|
for(var/obj/structure/transport/linear/other_platform as anything in transport_controller_datum.transport_modules)
|
|
if(REF(user) in other_platform.current_operators)
|
|
return
|
|
|
|
|
|
var/list/possible_directions = list()
|
|
if(transport_controller_datum.Check_lift_move(UP))
|
|
var/static/image/up_arrow
|
|
if(!up_arrow)
|
|
up_arrow = image(icon = 'icons/testing/turf_analysis.dmi', icon_state = "red_arrow", dir = NORTH)
|
|
|
|
possible_directions["Up"] = up_arrow
|
|
|
|
if(transport_controller_datum.Check_lift_move(DOWN))
|
|
var/static/image/down_arrow
|
|
if(!down_arrow)
|
|
down_arrow = image(icon = 'icons/testing/turf_analysis.dmi', icon_state = "red_arrow", dir = SOUTH)
|
|
|
|
possible_directions["Down"] = down_arrow
|
|
|
|
add_fingerprint(user)
|
|
if(!length(possible_directions))
|
|
balloon_alert(user, "elevator out of service!")
|
|
return
|
|
|
|
LAZYADD(current_operators, REF(user))
|
|
var/result = show_radial_menu(
|
|
user = user,
|
|
anchor = src,
|
|
choices = possible_directions,
|
|
custom_check = CALLBACK(src, PROC_REF(can_open_lift_radial), user, starting_position),
|
|
require_near = TRUE,
|
|
tooltips = TRUE,
|
|
)
|
|
|
|
LAZYREMOVE(current_operators, REF(user))
|
|
if(!can_open_lift_radial(user, starting_position))
|
|
return //nice try
|
|
if(!isnull(result) && result != "Cancel" && transport_controller_datum.controller_status & CONTROLS_LOCKED)
|
|
// Only show this message if they actually wanted to move
|
|
balloon_alert(user, "elevator controls locked!")
|
|
return
|
|
switch(result)
|
|
if("Up")
|
|
// We have to make sure that they don't do illegal actions
|
|
// by not having their radial menu refresh from someone else moving the lift.
|
|
if(!transport_controller_datum.simple_move_wrapper(UP, elevator_vertical_speed, user))
|
|
return
|
|
|
|
show_fluff_message(UP, user)
|
|
open_lift_radial(user)
|
|
|
|
if("Down")
|
|
if(!transport_controller_datum.simple_move_wrapper(DOWN, elevator_vertical_speed, user))
|
|
return
|
|
|
|
show_fluff_message(DOWN, user)
|
|
open_lift_radial(user)
|
|
|
|
if("Cancel")
|
|
return
|
|
|
|
/**
|
|
* Proc to ensure that the radial menu closes when it should.
|
|
* Arguments:
|
|
* * user - The person that opened the menu.
|
|
* * starting_loc - The location of the lift when the menu was opened, used to prevent the menu from being interacted with after the lift was moved by someone else.
|
|
*
|
|
* Returns:
|
|
* * boolean, FALSE if the menu should be closed, TRUE if the menu is clear to stay opened.
|
|
*/
|
|
/obj/structure/transport/linear/proc/check_menu(mob/user, starting_loc)
|
|
if(user.incapacitated() || !user.Adjacent(src) || starting_loc != src.loc)
|
|
return FALSE
|
|
return TRUE
|
|
|
|
/obj/structure/transport/linear/attack_hand(mob/user, list/modifiers)
|
|
. = ..()
|
|
if(.)
|
|
return
|
|
if(!radial_travel)
|
|
return ..()
|
|
|
|
return open_lift_radial(user)
|
|
|
|
//ai probably shouldn't get to use lifts but they sure are great for admins to crush people with
|
|
/obj/structure/transport/linear/attack_ghost(mob/user)
|
|
. = ..()
|
|
if(.)
|
|
return
|
|
if(!radial_travel)
|
|
return
|
|
if(!isAdminGhostAI(user))
|
|
return
|
|
|
|
return open_lift_radial(user)
|
|
|
|
/obj/structure/transport/linear/attack_paw(mob/user, list/modifiers)
|
|
if(!radial_travel)
|
|
return ..()
|
|
|
|
return open_lift_radial(user)
|
|
|
|
/obj/structure/transport/linear/attackby(obj/item/attacking_item, mob/user, params)
|
|
if(!radial_travel)
|
|
return ..()
|
|
|
|
return open_lift_radial(user)
|
|
|
|
/obj/structure/transport/linear/attack_robot(mob/living/user)
|
|
if(!radial_travel)
|
|
return ..()
|
|
|
|
return open_lift_radial(user)
|
|
|
|
/**
|
|
* Shows a message indicating that the lift has moved up or down.
|
|
* Arguments:
|
|
* * direction - What direction are we going
|
|
* * user - The mob that caused the lift to move, for the visible message.
|
|
*/
|
|
/obj/structure/transport/linear/proc/show_fluff_message(direction, mob/user)
|
|
if(direction == UP)
|
|
user.visible_message(span_notice("[user] moves the lift upwards."), span_notice("You move the lift upwards."))
|
|
|
|
if(direction == DOWN)
|
|
user.visible_message(span_notice("[user] moves the lift downwards."), span_notice("You move the lift downwards."))
|
|
|
|
/obj/machinery/door/poddoor/lift
|
|
name = "elevator door"
|
|
desc = "Keeps idiots like you from walking into an open elevator shaft."
|
|
icon = 'icons/obj/doors/liftdoor.dmi'
|
|
opacity = FALSE
|
|
glass = TRUE
|
|
|
|
/obj/machinery/door/poddoor/lift/Initialize(mapload)
|
|
if(!isnull(transport_linked_id)) //linter and stuff
|
|
elevator_mode = TRUE
|
|
return ..()
|
|
|
|
/obj/machinery/door/poddoor/lift/preopen
|
|
icon_state = "open"
|
|
density = FALSE
|
|
opacity = FALSE
|
|
|
|
// A subtype intended for "public use"
|
|
/obj/structure/transport/linear/public
|
|
icon = 'icons/turf/floors.dmi'
|
|
icon_state = "rockvault"
|
|
base_icon_state = null
|
|
smoothing_flags = NONE
|
|
smoothing_groups = null
|
|
canSmoothWith = null
|
|
warns_on_down_movement = TRUE
|
|
violent_landing = FALSE
|
|
elevator_vertical_speed = 3 SECONDS
|
|
radial_travel = FALSE
|
|
|
|
/obj/structure/transport/linear/debug
|
|
name = "transport platform"
|
|
desc = "A lightweight platform. It moves in any direction, except up and down."
|
|
color = "#5286b9ff"
|
|
transport_id = TRANSPORT_TYPE_DEBUG
|
|
radial_travel = TRUE
|
|
|
|
/obj/structure/transport/linear/debug/open_lift_radial(mob/living/user)
|
|
var/starting_position = loc
|
|
if (!can_open_lift_radial(user,starting_position))
|
|
return
|
|
//NORTH, SOUTH, EAST, WEST, NORTHEAST, NORTHWEST, SOUTHEAST, SOUTHWEST
|
|
var/static/list/tool_list = list(
|
|
"NORTH" = image(icon = 'icons/testing/turf_analysis.dmi', icon_state = "red_arrow", dir = NORTH),
|
|
"NORTHEAST" = image(icon = 'icons/testing/turf_analysis.dmi', icon_state = "red_arrow", dir = NORTH),
|
|
"EAST" = image(icon = 'icons/testing/turf_analysis.dmi', icon_state = "red_arrow", dir = EAST),
|
|
"SOUTHEAST" = image(icon = 'icons/testing/turf_analysis.dmi', icon_state = "red_arrow", dir = EAST),
|
|
"SOUTH" = image(icon = 'icons/testing/turf_analysis.dmi', icon_state = "red_arrow", dir = SOUTH),
|
|
"SOUTHWEST" = image(icon = 'icons/testing/turf_analysis.dmi', icon_state = "red_arrow", dir = SOUTH),
|
|
"WEST" = image(icon = 'icons/testing/turf_analysis.dmi', icon_state = "red_arrow", dir = WEST),
|
|
"NORTHWEST" = image(icon = 'icons/testing/turf_analysis.dmi', icon_state = "red_arrow", dir = WEST)
|
|
)
|
|
|
|
var/result = show_radial_menu(user, src, tool_list, custom_check = CALLBACK(src, PROC_REF(can_open_lift_radial), user, starting_position), require_near = TRUE, tooltips = FALSE)
|
|
if (!can_open_lift_radial(user,starting_position))
|
|
return // nice try
|
|
if(!isnull(result) && result != "Cancel" && transport_controller_datum.controller_status & CONTROLS_LOCKED)
|
|
// Only show this message if they actually wanted to move
|
|
balloon_alert(user, "elevator controls locked!")
|
|
return
|
|
|
|
switch(result)
|
|
if("NORTH")
|
|
transport_controller_datum.move_transport_horizontally(NORTH)
|
|
open_lift_radial(user)
|
|
if("NORTHEAST")
|
|
transport_controller_datum.move_transport_horizontally(NORTHEAST)
|
|
open_lift_radial(user)
|
|
if("EAST")
|
|
transport_controller_datum.move_transport_horizontally(EAST)
|
|
open_lift_radial(user)
|
|
if("SOUTHEAST")
|
|
transport_controller_datum.move_transport_horizontally(SOUTHEAST)
|
|
open_lift_radial(user)
|
|
if("SOUTH")
|
|
transport_controller_datum.move_transport_horizontally(SOUTH)
|
|
open_lift_radial(user)
|
|
if("SOUTHWEST")
|
|
transport_controller_datum.move_transport_horizontally(SOUTHWEST)
|
|
open_lift_radial(user)
|
|
if("WEST")
|
|
transport_controller_datum.move_transport_horizontally(WEST)
|
|
open_lift_radial(user)
|
|
if("NORTHWEST")
|
|
transport_controller_datum.move_transport_horizontally(NORTHWEST)
|
|
open_lift_radial(user)
|
|
if("Cancel")
|
|
return
|
|
|
|
add_fingerprint(user)
|
|
|
|
/obj/structure/transport/linear/tram
|
|
name = "tram subfloor"
|
|
desc = "The subfloor lattice of the tram. You can build a tram wall frame by using <b>titanium sheets,</b> or place down <b>thermoplastic tram floor tiles.</b>"
|
|
icon = 'icons/obj/tram/tram_structure.dmi'
|
|
icon_state = "subfloor"
|
|
base_icon_state = null
|
|
density = FALSE
|
|
layer = TRAM_STRUCTURE_LAYER
|
|
smoothing_flags = NONE
|
|
smoothing_groups = null
|
|
canSmoothWith = null
|
|
//the modular structure is pain to work with, damage is done to the floor on top
|
|
transport_id = TRANSPORT_TYPE_TRAM
|
|
transport_controller_type = /datum/transport_controller/linear/tram
|
|
radial_travel = FALSE
|
|
/// Set by the tram control console in late initialize
|
|
var/travelling = FALSE
|
|
|
|
/// Do we want this transport to link with nearby modules to make a multi-tile platform
|
|
create_modular_set = TRUE
|
|
|
|
/obj/structure/transport/linear/tram/corner/northwest
|
|
icon_state = "subfloor-corner-nw"
|
|
|
|
/obj/structure/transport/linear/tram/corner/southwest
|
|
icon_state = "subfloor-corner-sw"
|
|
|
|
/obj/structure/transport/linear/tram/corner/northeast
|
|
icon_state = "subfloor-corner-ne"
|
|
|
|
/obj/structure/transport/linear/tram/corner/southeast
|
|
icon_state = "subfloor-corner-se"
|
|
|
|
/obj/structure/transport/linear/tram/add_item_on_transport(datum/source, atom/movable/item)
|
|
. = ..()
|
|
if(travelling)
|
|
on_changed_glide_size(item, item.glide_size)
|
|
|
|
/obj/structure/transport/linear/tram/proc/set_travelling(travelling)
|
|
if (src.travelling == travelling)
|
|
return
|
|
|
|
for(var/atom/movable/glider as anything in transport_contents)
|
|
if(travelling)
|
|
glider.set_glide_size(glide_size_override)
|
|
RegisterSignal(glider, COMSIG_MOVABLE_UPDATE_GLIDE_SIZE, PROC_REF(on_changed_glide_size))
|
|
else
|
|
changed_gliders -= glider
|
|
UnregisterSignal(glider, COMSIG_MOVABLE_UPDATE_GLIDE_SIZE)
|
|
|
|
src.travelling = travelling
|
|
SEND_SIGNAL(src, COMSIG_TRANSPORT_ACTIVE, travelling)
|
|
|
|
/obj/structure/transport/linear/tram/set_currently_z_moving()
|
|
return FALSE //trams can never z fall and shouldnt waste any processing time trying to do so
|
|
|
|
/**
|
|
* Handles unlocking the tram controls for use after moving
|
|
*
|
|
* More safety checks to make sure the tram has actually docked properly
|
|
* at a location before users are allowed to interact with the tram console again.
|
|
* Tram finds its location at this point before fully unlocking controls to the user.
|
|
*/
|
|
/obj/structure/transport/linear/tram/proc/unlock_controls()
|
|
for(var/obj/structure/transport/linear/tram/tram_part as anything in transport_controller_datum.transport_modules) //only thing everyone needs to know is the new location.
|
|
tram_part.set_travelling(FALSE)
|
|
transport_controller_datum.controls_lock(FALSE)
|
|
|
|
///debug proc to highlight the locs of the tram platform
|
|
/obj/structure/transport/linear/tram/proc/find_dimensions(iterations = 100)
|
|
message_admins("num turfs: [length(locs)]")
|
|
|
|
var/overlay = /obj/effect/overlay/ai_detect_hud
|
|
var/list/turfs = list()
|
|
|
|
for(var/turf/our_turf as anything in locs)
|
|
new overlay(our_turf)
|
|
turfs += our_turf
|
|
|
|
addtimer(CALLBACK(src, PROC_REF(clear_turfs), turfs, iterations), 0.1 SECONDS)
|
|
|
|
/obj/structure/transport/linear/tram/proc/clear_turfs(list/turfs_to_clear, iterations)
|
|
for(var/turf/our_old_turf as anything in turfs_to_clear)
|
|
var/obj/effect/overlay/ai_detect_hud/hud = locate() in our_old_turf
|
|
if(hud)
|
|
qdel(hud)
|
|
|
|
var/overlay = /obj/effect/overlay/ai_detect_hud
|
|
|
|
for(var/turf/our_turf as anything in locs)
|
|
new overlay(our_turf)
|
|
|
|
iterations--
|
|
|
|
var/list/turfs = list()
|
|
for(var/turf/our_turf as anything in locs)
|
|
turfs += our_turf
|
|
|
|
if(iterations)
|
|
addtimer(CALLBACK(src, PROC_REF(clear_turfs), turfs, iterations), 0.1 SECONDS)
|
|
|
|
/obj/structure/transport/linear/tram/proc/estop_throw(throw_direction)
|
|
for(var/mob/living/passenger in transport_contents)
|
|
var/mob_throw_chance = transport_controller_datum.throw_chance
|
|
if(prob(mob_throw_chance || 17.5) || HAS_TRAIT(passenger, TRAIT_CURSED)) // sometimes you go through a window, especially with bad luck
|
|
passenger.AddElement(/datum/element/window_smashing, duration = 1.5 SECONDS)
|
|
var/throw_target = get_edge_target_turf(src, throw_direction)
|
|
passenger.throw_at(throw_target, 30, 7, force = MOVE_FORCE_OVERPOWERING)
|
|
|
|
/obj/structure/transport/linear/tram/slow
|
|
transport_controller_type = /datum/transport_controller/linear/tram/slow
|
|
speed_limiter = /datum/transport_controller/linear/tram/slow::speed_limiter
|