//use this define to highlight docking port bounding boxes (ONLY FOR DEBUG USE) // #define DOCKING_PORT_HIGHLIGHT //NORTH default dir /obj/docking_port invisibility = INVISIBILITY_ABSTRACT icon = 'icons/obj/device.dmi' //icon = 'icons/dirsquare.dmi' icon_state = "pinonfar" resistance_flags = INDESTRUCTIBLE | LAVA_PROOF | FIRE_PROOF | UNACIDABLE | ACID_PROOF anchored = 1 var/id // this should point -away- from the dockingport door, ie towards the ship dir = NORTH var/width = 0 //size of covered area, perpendicular to dir var/height = 0 //size of covered area, parallel to dir var/dwidth = 0 //position relative to covered area, perpendicular to dir var/dheight = 0 //position relative to covered area, parallel to dir //these objects are indestructible /obj/docking_port/Destroy(force) // unless you assert that you know what you're doing. Horrible things // may result. if(force) ..() . = QDEL_HINT_HARDDEL_NOW else return QDEL_HINT_LETMELIVE /obj/docking_port/take_damage() return /obj/docking_port/singularity_pull() return /obj/docking_port/singularity_act() return 0 /obj/docking_port/shuttleRotate() return //we don't rotate with shuttles via this code. //returns a list(x0,y0, x1,y1) where points 0 and 1 are bounding corners of the projected rectangle /obj/docking_port/proc/return_coords(_x, _y, _dir) if(_dir == null) _dir = dir if(_x == null) _x = x if(_y == null) _y = y //byond's sin and cos functions are inaccurate. This is faster and perfectly accurate var/cos = 1 var/sin = 0 switch(_dir) if(WEST) cos = 0 sin = 1 if(SOUTH) cos = -1 sin = 0 if(EAST) cos = 0 sin = -1 return list( _x + (-dwidth*cos) - (-dheight*sin), _y + (-dwidth*sin) + (-dheight*cos), _x + (-dwidth+width-1)*cos - (-dheight+height-1)*sin, _y + (-dwidth+width-1)*sin + (-dheight+height-1)*cos ) //returns turfs within our projected rectangle in a specific order. //this ensures that turfs are copied over in the same order, regardless of any rotation /obj/docking_port/proc/return_ordered_turfs(_x, _y, _z, _dir, area/A) if(!_dir) _dir = dir if(!_x) _x = x if(!_y) _y = y if(!_z) _z = z var/cos = 1 var/sin = 0 switch(_dir) if(WEST) cos = 0 sin = 1 if(SOUTH) cos = -1 sin = 0 if(EAST) cos = 0 sin = -1 . = list() var/xi var/yi for(var/dx=0, dx S.dwidth) return SHUTTLE_DWIDTH_TOO_LARGE if(width-dwidth > S.width-S.dwidth) return SHUTTLE_WIDTH_TOO_LARGE if(dheight > S.dheight) return SHUTTLE_DHEIGHT_TOO_LARGE if(height-dheight > S.height-S.dheight) return SHUTTLE_HEIGHT_TOO_LARGE //check the dock isn't occupied var/currently_docked = S.get_docked() if(currently_docked) // by someone other than us if(currently_docked != src) return SHUTTLE_SOMEONE_ELSE_DOCKED else // This isn't an error, per se, but we can't let the shuttle code // attempt to move us where we currently are, it will get weird. return SHUTTLE_ALREADY_DOCKED return SHUTTLE_CAN_DOCK /obj/docking_port/mobile/proc/check_dock(obj/docking_port/stationary/S) var/status = canDock(S) if(status == SHUTTLE_CAN_DOCK) return TRUE else if(status == SHUTTLE_ALREADY_DOCKED) // We're already docked there, don't need to do anything. // Triggering shuttle movement code in place is weird return FALSE else var/msg = "Shuttle [src] cannot dock at [S], error: [status]" message_admins(msg) return FALSE //call the shuttle to destination S /obj/docking_port/mobile/proc/request(obj/docking_port/stationary/S) if(!check_dock(S)) return switch(mode) if(SHUTTLE_CALL) if(S == destination) if(timeLeft(1) < callTime) setTimer(callTime) else destination = S setTimer(callTime) if(SHUTTLE_RECALL) if(S == destination) setTimer(callTime - timeLeft(1)) else destination = S setTimer(callTime) mode = SHUTTLE_CALL if(SHUTTLE_IDLE, SHUTTLE_IGNITING) destination = S mode = SHUTTLE_IGNITING setTimer(ignitionTime) //recall the shuttle to where it was previously /obj/docking_port/mobile/proc/cancel() if(mode != SHUTTLE_CALL) return remove_ripples() invertTimer() mode = SHUTTLE_RECALL /obj/docking_port/mobile/proc/enterTransit() previous = null // if(!destination) // return var/obj/docking_port/stationary/S0 = get_docked() var/obj/docking_port/stationary/S1 = assigned_transit if(S1) if(dock(S1)) WARNING("shuttle \"[id]\" could not enter transit space. Docked at [S0 ? S0.id : "null"]. Transit dock [S1 ? S1.id : "null"].") else previous = S0 else WARNING("shuttle \"[id]\" could not enter transit space. S0=[S0 ? S0.id : "null"] S1=[S1 ? S1.id : "null"]") /obj/docking_port/mobile/proc/jumpToNullSpace() // Destroys the docking port and the shuttle contents. // Not in a fancy way, it just ceases. var/obj/docking_port/stationary/S0 = get_docked() var/turf_type = /turf/open/space var/area_type = /area/space // If the shuttle is docked to a stationary port, restore its normal // "empty" area and turf if(S0) if(S0.turf_type) turf_type = S0.turf_type if(S0.area_type) area_type = S0.area_type var/list/L0 = return_ordered_turfs(x, y, z, dir, areaInstance) //remove area surrounding docking port if(areaInstance.contents.len) var/area/A0 = locate("[area_type]") if(!A0) A0 = new area_type(null) for(var/turf/T0 in L0) A0.contents += T0 for(var/i in L0) var/turf/T0 =i if(!T0) continue T0.empty(turf_type) qdel(src, force=TRUE) /obj/docking_port/mobile/proc/create_ripples(obj/docking_port/stationary/S1) var/list/turfs = ripple_area(S1) for(var/t in turfs) ripples += PoolOrNew(/obj/effect/overlay/temp/ripple, t) /obj/docking_port/mobile/proc/remove_ripples() for(var/R in ripples) qdel(R) ripples.Cut() /obj/docking_port/mobile/proc/ripple_area(obj/docking_port/stationary/S1) var/list/L0 = return_ordered_turfs(x, y, z, dir, areaInstance) var/list/L1 = return_ordered_turfs(S1.x, S1.y, S1.z, S1.dir) var/list/ripple_turfs = list() for(var/i in 1 to L0.len) var/turf/T0 = L0[i] if(!T0) continue var/turf/T1 = L1[i] if(!T1) continue if(T0.type != T0.baseturf) ripple_turfs += T1 return ripple_turfs /obj/docking_port/mobile/proc/check_poddoors() for(var/obj/machinery/door/poddoor/shuttledock/pod in airlocks) pod.check() //this is the main proc. It instantly moves our mobile port to stationary port S1 //it handles all the generic behaviour, such as sanity checks, closing doors on the shuttle, stunning mobs, etc /obj/docking_port/mobile/proc/dock(obj/docking_port/stationary/S1, force=FALSE) // Crashing this ship with NO SURVIVORS if(!force) if(!check_dock(S1)) return -1 if(!canMove()) return -1 var/obj/docking_port/stationary/S0 = get_docked() var/turf_type = /turf/open/space var/area_type = /area/space if(S0) if(S0.turf_type) turf_type = S0.turf_type if(S0.area_type) area_type = S0.area_type var/destination_turf_type = S1.turf_type var/list/L0 = return_ordered_turfs(x, y, z, dir, areaInstance) var/list/L1 = return_ordered_turfs(S1.x, S1.y, S1.z, S1.dir) var/rotation = dir2angle(S1.dir)-dir2angle(dir) if ((rotation % 90) != 0) rotation += (rotation % 90) //diagonal rotations not allowed, round up rotation = SimplifyDegrees(rotation) //remove area surrounding docking port if(areaInstance.contents.len) var/area/A0 = locate("[area_type]") if(!A0) A0 = new area_type(null) for(var/turf/T0 in L0) A0.contents += T0 remove_ripples() //move or squish anything in the way ship at destination roadkill(L0, L1, S1.dir) for(var/i in 1 to L0.len) var/turf/T0 = L0[i] if(!T0) continue var/turf/T1 = L1[i] if(!T1) continue if(T0.type != T0.baseturf) //So if there is a hole in the shuttle we don't drag along the space/asteroid/etc to wherever we are going next T0.copyTurf(T1) T1.baseturf = destination_turf_type areaInstance.contents += T1 //copy over air if(isopenturf(T1)) var/turf/open/Ts1 = T1 Ts1.copy_air_with_tile(T0) //move mobile to new location for(var/atom/movable/AM in T0) AM.onShuttleMove(T1, rotation) if(rotation) T1.shuttleRotate(rotation) //lighting stuff T1.redraw_lighting() SSair.remove_from_active(T1) T1.CalculateAdjacentTurfs() SSair.add_to_active(T1,1) T0.ChangeTurf(turf_type) T0.redraw_lighting() SSair.remove_from_active(T0) T0.CalculateAdjacentTurfs() SSair.add_to_active(T0,1) check_poddoors() S1.last_dock_time = world.time loc = S1.loc setDir(S1.dir) /obj/docking_port/mobile/proc/findRoundstartDock() return SSshuttle.getDock(roundstart_move) /obj/docking_port/mobile/proc/dockRoundstart() var/port = findRoundstartDock() if(port) return dock(port) /obj/effect/landmark/shuttle_import name = "Shuttle Import" /obj/docking_port/mobile/proc/roadkill(list/L0, list/L1, dir) var/list/hurt_mobs = list() for(var/i in 1 to L0.len) var/turf/T0 = L0[i] var/turf/T1 = L1[i] if(!T0 || !T1) continue if(T0.type == T0.baseturf) continue // The corresponding tile will not be changed, so no roadkill for(var/atom/movable/AM in T1) if(ismob(AM)) if(isliving(AM) && !(AM in hurt_mobs)) hurt_mobs |= AM var/mob/living/M = AM if(M.buckled) M.buckled.unbuckle_mob(M, 1) if(M.pulledby) M.pulledby.stop_pulling() M.stop_pulling() M.visible_message("[M] is hit by \ a hyperspace ripple[M.anchored ? "":" and is thrown clear"]!", "You feel an immense \ crushing pressure as the space around you ripples.") if(M.anchored) M.gib() else step(M, dir) M.Paralyse(10) M.ex_act(2) else //non-living mobs shouldn't be affected by shuttles, which is why this is an else if(istype(AM, /obj/singularity) && !istype(AM, /obj/singularity/narsie)) //it's a singularity but not a god, ignore it. continue if(!AM.anchored) step(AM, dir) else qdel(AM) //used by shuttle subsystem to check timers /obj/docking_port/mobile/proc/check() check_ripples() if(mode == SHUTTLE_IGNITING) check_transit_zone() if(timeLeft(1) > 0) return // If we can't dock or we don't have a transit slot, wait for 20 ds, // then try again switch(mode) if(SHUTTLE_CALL) if(dock(destination)) setTimer(20) return if(SHUTTLE_RECALL) if(dock(previous)) setTimer(20) return if(SHUTTLE_IGNITING) if(check_transit_zone() != TRANSIT_READY) setTimer(20) return else mode = SHUTTLE_CALL setTimer(callTime) enterTransit() return mode = SHUTTLE_IDLE timer = 0 destination = null /obj/docking_port/mobile/proc/check_ripples() if(!ripples.len) if((mode == SHUTTLE_CALL) || (mode == SHUTTLE_RECALL)) if(timeLeft(1) <= SHUTTLE_RIPPLE_TIME) create_ripples(destination) /obj/docking_port/mobile/proc/check_transit_zone() if(assigned_transit) return TRANSIT_READY else SSshuttle.request_transit_dock(src) /obj/docking_port/mobile/proc/setTimer(wait) timer = world.time + wait last_timer_length = wait /obj/docking_port/mobile/proc/invertTimer() if(!last_timer_length) return var/time_remaining = timer - world.time if(time_remaining > 0) var/time_passed = last_timer_length - time_remaining setTimer(time_passed) //returns timeLeft /obj/docking_port/mobile/proc/timeLeft(divisor) if(divisor <= 0) divisor = 10 var/ds_remaining if(!timer) ds_remaining = callTime else ds_remaining = max(0, timer - world.time) . = round(ds_remaining / divisor, 1) // returns 3-letter mode string, used by status screens and mob status panel /obj/docking_port/mobile/proc/getModeStr() switch(mode) if(SHUTTLE_IGNITING) return "IGN" if(SHUTTLE_RECALL) return "RCL" if(SHUTTLE_CALL) return "ETA" if(SHUTTLE_DOCKED) return "ETD" if(SHUTTLE_ESCAPE) return "ESC" if(SHUTTLE_STRANDED) return "ERR" return "" // returns 5-letter timer string, used by status screens and mob status panel /obj/docking_port/mobile/proc/getTimerStr() if(mode == SHUTTLE_STRANDED) return "--:--" var/timeleft = timeLeft() if(timeleft > 0) return "[add_zero(num2text((timeleft / 60) % 60),2)]:[add_zero(num2text(timeleft % 60), 2)]" else return "00:00" /obj/docking_port/mobile/proc/getStatusText() var/obj/docking_port/stationary/dockedAt = get_docked() . = (dockedAt && dockedAt.name) ? dockedAt.name : "unknown" if(istype(dockedAt, /obj/docking_port/stationary/transit)) var/obj/docking_port/stationary/dst if(mode == SHUTTLE_RECALL) dst = previous else dst = destination . += " towards [dst ? dst.name : "unknown location"] ([timeLeft(600)] minutes)" #undef DOCKING_PORT_HIGHLIGHT