mirror of
https://github.com/Aurorastation/Aurora.3.git
synced 2026-07-13 17:07:53 +01:00
f31aeb6595
Stickers can no longer be attached to disposal machinery or conveyors, to prevent further issues with throwing them there. The disposal outlet sprite was set to face the wrong direction when expelling items, for two of its directions. This was fixed. The disposal UI constantly reopening itself was fixed. And a set of disposal systems were added to runtime for easier debugging. <img width="344" height="385" alt="image" src="https://github.com/user-attachments/assets/ff93f3e9-bfa7-480c-9d6b-c501b02ca51a" />
1747 lines
48 KiB
Plaintext
1747 lines
48 KiB
Plaintext
/**
|
|
* Disposal bin
|
|
* Holds items for disposal into pipe system
|
|
* Draws air from turf, gradually charges internal reservoir
|
|
* Once full (~1 atm), uses air resv to flush items into the pipes
|
|
* Automatically recharges air (unless off), will flush when ready if pre-set
|
|
* Can hold items and human size things, no other draggables
|
|
* Toilets are a type of disposal bin for small objects only and work on magic. By magic, I mean torque rotation
|
|
*/
|
|
|
|
/// kPa - assume the inside of a dispoal pipe is 1 atm, so that needs to be added.
|
|
#define SEND_PRESSURE (700 + ONE_ATMOSPHERE)
|
|
/// (Liters L)
|
|
#define PRESSURE_TANK_VOLUME 150
|
|
/// L/s - 4 m/s using a 15 cm by 15 cm inlet
|
|
#define PUMP_MAX_FLOW_RATE 90
|
|
|
|
#define MODE_OFF 0
|
|
#define MODE_PRESSURIZING 1
|
|
#define MODE_READY 2
|
|
#define MODE_FLUSHING 3
|
|
|
|
/obj/structure/machinery/disposal
|
|
name = "disposal unit"
|
|
desc = "A pneumatic waste disposal unit."
|
|
icon = 'icons/obj/disposals.dmi'
|
|
icon_state = "disposal"
|
|
anchored = 1
|
|
density = 1
|
|
/// Unit is powered
|
|
var/is_on = TRUE
|
|
var/datum/wires/disposal/wires
|
|
/// Internal reservoir
|
|
var/datum/gas_mixture/air_contents
|
|
var/mode = MODE_PRESSURIZING
|
|
/// MODE_X define that was set when we last updated overlays.
|
|
var/mode_icon
|
|
/// Whether or not we are currently indicating as occupied.
|
|
var/showing_full = FALSE
|
|
/// Controlled by flush wire status
|
|
var/can_flush = TRUE
|
|
/// TRUE if flush handle is pulled
|
|
var/flush = FALSE
|
|
/// The attached pipe trunk
|
|
var/obj/structure/disposalpipe/trunk/trunk = null
|
|
/// TRUE if flushing in progress. To be replaced with mode entirely
|
|
var/flushing = 0
|
|
/// Every 30 ticks it will look whether it is ready to flush
|
|
var/flush_every_ticks = 30
|
|
/// This var adds 1 once per tick. When it reaches flush_every_ticks it resets and tries to flush
|
|
var/flush_count = 0
|
|
var/last_sound = 0
|
|
/// Most pumps require air to function, but some don't
|
|
var/uses_air = TRUE
|
|
/// The pneumatic pump power. 3 HP ~ 2200W
|
|
active_power_usage = 2200
|
|
idle_power_usage = 100
|
|
|
|
/obj/structure/machinery/disposal/airless
|
|
uses_air = FALSE
|
|
|
|
/obj/structure/machinery/disposal/small
|
|
desc = "A compact pneumatic waste disposal unit."
|
|
icon_state = "disposal_small"
|
|
density = FALSE
|
|
|
|
/obj/structure/machinery/disposal/small/north
|
|
dir = NORTH
|
|
pixel_y = -13
|
|
layer = MOB_LAYER + 0.1
|
|
|
|
/obj/structure/machinery/disposal/small/south
|
|
dir = SOUTH
|
|
pixel_y = 20
|
|
layer = OBJ_LAYER + 0.3
|
|
|
|
/obj/structure/machinery/disposal/small/east
|
|
dir = EAST
|
|
pixel_x = -12
|
|
|
|
/obj/structure/machinery/disposal/small/west
|
|
dir = WEST
|
|
pixel_x = 11
|
|
|
|
/obj/structure/machinery/disposal/small/airless
|
|
uses_air = FALSE
|
|
|
|
/obj/structure/machinery/disposal/small/Initialize()
|
|
. = ..()
|
|
if(pixel_x || pixel_y)
|
|
return
|
|
else
|
|
switch(dir)
|
|
if(NORTH)
|
|
pixel_y = -13
|
|
layer = MOB_LAYER + 0.1
|
|
if(SOUTH)
|
|
pixel_y = 20
|
|
layer = OBJ_LAYER + 0.3
|
|
if(EAST)
|
|
pixel_x = -12
|
|
if(WEST)
|
|
pixel_x = 11
|
|
|
|
/obj/structure/machinery/disposal/small/check_mob_size(mob/target)
|
|
if(target.mob_size > MOB_SMALL)
|
|
return FALSE
|
|
return TRUE
|
|
|
|
/**
|
|
* Create a new disposal:
|
|
* Find the attached trunk (if present), and initialize gas reservoir.
|
|
*/
|
|
/obj/structure/machinery/disposal/Initialize()
|
|
. = ..()
|
|
trunk = locate() in src.loc
|
|
if(!trunk)
|
|
mode = MODE_OFF
|
|
is_on = FALSE
|
|
flush = 0
|
|
else
|
|
trunk.linked = src // link the pipe trunk to self
|
|
|
|
wires = new(src)
|
|
air_contents = new/datum/gas_mixture(PRESSURE_TANK_VOLUME)
|
|
update()
|
|
|
|
/obj/structure/machinery/disposal/Destroy()
|
|
eject()
|
|
if(trunk)
|
|
trunk.linked = null
|
|
return ..()
|
|
|
|
/obj/structure/machinery/disposal/proc/contents_count()
|
|
var/things = 0
|
|
for(var/thing in contents)
|
|
if(istype(thing, /obj/item/assembly/signaler))
|
|
var/obj/item/assembly/signaler/S = thing
|
|
if(S.connected == wires)
|
|
continue
|
|
things++
|
|
return things
|
|
|
|
/**
|
|
* Attack by an item places it into the disposal.
|
|
*/
|
|
/obj/structure/machinery/disposal/attackby(obj/item/attacking_item, mob/user)
|
|
if(stat & BROKEN || !attacking_item || !user)
|
|
return
|
|
|
|
var/has_contents
|
|
|
|
src.add_fingerprint(user)
|
|
if(!is_on)
|
|
has_contents = contents_count()
|
|
if(attacking_item.tool_behaviour == TOOL_SCREWDRIVER)
|
|
if(has_contents)
|
|
to_chat(user, SPAN_WARNING("Eject the items first!"))
|
|
return TRUE
|
|
else if(default_deconstruction_screwdriver(user, attacking_item))
|
|
update()
|
|
return TRUE
|
|
else if(attacking_item.tool_behaviour == TOOL_WELDER && panel_open)
|
|
if(has_contents)
|
|
to_chat(user, SPAN_WARNING("Eject the items first!"))
|
|
return TRUE
|
|
var/obj/item/weldingtool/W = attacking_item
|
|
if(W.use(0,user))
|
|
to_chat(user, SPAN_NOTICE("You start slicing the floorweld off the disposal unit."))
|
|
if(W.use_tool(src, user, 20, volume = 50))
|
|
if(!src || !W.isOn())
|
|
return TRUE
|
|
to_chat(user, SPAN_NOTICE("You sliced the floorweld off the disposal unit."))
|
|
if(!istype(src, /obj/structure/machinery/disposal/small))
|
|
var/obj/structure/disposalconstruct/C = new (src.loc)
|
|
src.transfer_fingerprints_to(C)
|
|
C.ptype = 6 // 6 = disposal unit
|
|
C.anchored = 1
|
|
C.density = 1
|
|
C.update()
|
|
else
|
|
var/obj/structure/disposalconstruct/C = new (src.loc)
|
|
src.transfer_fingerprints_to(C)
|
|
C.ptype = 15 // 15 = small disposal unit
|
|
C.anchored = 1
|
|
C.update()
|
|
qdel(src)
|
|
return TRUE
|
|
else
|
|
to_chat(user, SPAN_WARNING("You need more welding fuel to complete this task."))
|
|
return TRUE
|
|
|
|
if(istype(attacking_item, /obj/item/melee/energy/blade))
|
|
to_chat(user, SPAN_WARNING("You can't place that item inside the disposal unit."))
|
|
return TRUE
|
|
|
|
if(istype(attacking_item, /obj/item/storage) && length(attacking_item.contents) && user.a_intent != I_HURT)
|
|
var/obj/item/storage/S = attacking_item
|
|
|
|
if(istype(S, /obj/item/storage/secure))
|
|
var/obj/item/storage/secure/secured_storage = S
|
|
if(secured_storage.locked)
|
|
to_chat(user, SPAN_WARNING("You can't empty \the [secured_storage] into \the [src]. It is locked."))
|
|
return TRUE
|
|
|
|
user.visible_message("<b>[user]</b> empties \the [S] into \the [src].", SPAN_NOTICE("You empty \the [S] into \the [src]."), range = 3)
|
|
for(var/obj/item/O in S.contents)
|
|
S.remove_from_storage(O, src)
|
|
S.update_icon()
|
|
update()
|
|
return TRUE
|
|
|
|
else if(istype(attacking_item, /obj/item/material/ashtray) && user.a_intent != I_HURT) // If attacked with ashtray on harm intent.
|
|
var/obj/item/material/ashtray/A = attacking_item
|
|
if(A.emptyout(src)) // Ashtray has contents.
|
|
user.visible_message("<b>[user]</b> pours \the [attacking_item] out into \the [src].", SPAN_NOTICE("You pour \the [attacking_item] out into \the [src]."))
|
|
else // Ashtray has no contents.
|
|
to_chat(user, SPAN_NOTICE("\The [attacking_item] is empty."))
|
|
return TRUE
|
|
|
|
else if (istype (attacking_item, /obj/item/lightreplacer))
|
|
var/count = 0
|
|
var/obj/item/lightreplacer/R = attacking_item
|
|
if (R.store_broken)
|
|
for(var/obj/item/light/L in R.contents)
|
|
count++
|
|
L.forceMove(src)
|
|
|
|
if (count)
|
|
to_chat(user, SPAN_NOTICE("You empty [count] broken bulbs into the disposal."))
|
|
else
|
|
to_chat(user, SPAN_NOTICE("There are no broken bulbs to empty out."))
|
|
update()
|
|
return TRUE
|
|
|
|
var/obj/item/grab/G = attacking_item
|
|
// handle grabbed mob
|
|
if(istype(G))
|
|
if(ismob(G.affecting))
|
|
var/mob/GM = G.affecting
|
|
if(!check_mob_size(GM))
|
|
to_chat(user, SPAN_NOTICE("The opening is too narrow for [G.affecting] to fit!"))
|
|
return TRUE
|
|
for (var/mob/V in viewers(usr))
|
|
V.show_message("[usr] starts putting [GM.name] into the disposal.", 3)
|
|
if(do_after(usr, 20))
|
|
if (GM.client)
|
|
GM.client.perspective = EYE_PERSPECTIVE
|
|
GM.client.eye = src
|
|
GM.forceMove(src)
|
|
for (var/mob/C in viewers(src))
|
|
C.show_message(SPAN_WARNING("[GM.name] has been placed in the [src] by [user]."), 3)
|
|
qdel(G)
|
|
usr.attack_log += "\[[time_stamp()]\] <span class='warning'>Has placed [GM.name] ([GM.ckey]) in disposals.</span>"
|
|
GM.attack_log += "\[[time_stamp()]\] <font color='orange'>Has been placed in disposals by [usr.name] ([usr.ckey])</font>"
|
|
msg_admin_attack("[key_name_admin(usr)] placed [key_name_admin(GM)] in a disposals unit. (<A href='byond://?_src_=holder;adminplayerobservecoodjump=1;X=[usr.x];Y=[usr.y];Z=[usr.z]'>JMP</a>)",ckey=key_name(usr),ckey_target=key_name(GM))
|
|
return TRUE
|
|
if(!attacking_item.dropsafety())
|
|
return TRUE
|
|
|
|
if(!attacking_item)
|
|
return TRUE
|
|
|
|
user.drop_from_inventory(attacking_item, src)
|
|
|
|
user.visible_message("<b>[user]</b> places \the [attacking_item] into \the [src].", SPAN_NOTICE("You place \the [attacking_item] into \the [src]."), range = 3)
|
|
update()
|
|
|
|
/**
|
|
* Handles mouse-dropping another mob or self onto disposal.
|
|
*/
|
|
/obj/structure/machinery/disposal/mouse_drop_receive(atom/dropped, mob/user, params)
|
|
var/mob/target = dropped
|
|
if(!istype(target))
|
|
return
|
|
|
|
if(user.stat || !user.canmove || !istype(target))
|
|
return
|
|
if(target.buckled_to || get_dist(user, src) > 1 || get_dist(user, target) > 1)
|
|
return
|
|
|
|
// Animals cannot put mobs other than themselves into disposal
|
|
if(isanimal(user) && target != user)
|
|
return
|
|
|
|
if(!check_mob_size(target))
|
|
to_chat(user, SPAN_NOTICE("The opening is too narrow for [target] to fit!"))
|
|
return
|
|
|
|
// Makes it so synths can't be flushed
|
|
if (isrobot(target) && !isDrone(target))
|
|
to_chat(user, SPAN_NOTICE("[target] is a bit too clunky to fit!"))
|
|
return
|
|
|
|
src.add_fingerprint(user)
|
|
var/target_loc = target.loc
|
|
var/msg
|
|
for (var/mob/V in viewers(usr))
|
|
if(target == user && !user.stat && !user.weakened && !user.stunned && !user.paralysis)
|
|
V.show_message("[usr] starts climbing into the disposal.", 3)
|
|
if(target != user && !user.restrained() && !user.stat && !user.weakened && !user.stunned && !user.paralysis)
|
|
if(target.anchored) return
|
|
V.show_message("[usr] starts stuffing [target.name] into the disposal.", 3)
|
|
if(!do_after(usr, 20))
|
|
return
|
|
if(target_loc != target.loc)
|
|
return
|
|
// If drop self, then climbed in. Must be awake, not stunned or whatever
|
|
if(target == user && !user.stat && !user.weakened && !user.stunned && !user.paralysis)
|
|
msg = "[user.name] climbs into the [src]."
|
|
to_chat(user, "You climb into the [src].")
|
|
else if(target != user && !user.restrained() && !user.stat && !user.weakened && !user.stunned && !user.paralysis)
|
|
msg = "[user.name] stuffs [target.name] into the [src]!"
|
|
to_chat(user, "You stuff [target.name] into the [src]!")
|
|
|
|
user.attack_log += "\[[time_stamp()]\] <span class='warning'>Has placed [target.name] ([target.ckey]) in disposals.</span>"
|
|
target.attack_log += "\[[time_stamp()]\] <font color='orange'>Has been placed in disposals by [user.name] ([user.ckey])</font>"
|
|
msg_admin_attack("[user] ([user.ckey]) placed [target] ([target.ckey]) in a disposals unit. (<A href='byond://?_src_=holder;adminplayerobservecoodjump=1;X=[user.x];Y=[user.y];Z=[user.z]'>JMP</a>)",ckey=key_name(user),ckey_target=key_name(target))
|
|
else
|
|
return
|
|
if (target.client)
|
|
target.client.perspective = EYE_PERSPECTIVE
|
|
target.client.eye = src
|
|
|
|
target.forceMove(src)
|
|
|
|
for (var/mob/C in viewers(src))
|
|
if(C == user)
|
|
continue
|
|
C.show_message(msg, 3)
|
|
|
|
update()
|
|
|
|
/obj/structure/machinery/disposal/proc/check_mob_size(mob/target)
|
|
return 1
|
|
|
|
/**
|
|
* Attempt to move while inside.
|
|
*/
|
|
/obj/structure/machinery/disposal/relaymove(mob/living/user, direction)
|
|
. = ..()
|
|
|
|
if(user.stat || src.mode == MODE_FLUSHING)
|
|
return
|
|
if(user.loc == src)
|
|
src.go_out(user)
|
|
return
|
|
|
|
/**
|
|
* Leave the disposal.
|
|
*/
|
|
/obj/structure/machinery/disposal/proc/go_out(mob/user)
|
|
|
|
if (user.client)
|
|
user.client.eye = user.client.mob
|
|
user.client.perspective = MOB_PERSPECTIVE
|
|
user.forceMove(src.loc)
|
|
update()
|
|
return
|
|
|
|
/**
|
|
* AI: as human but can't flush.
|
|
*/
|
|
/obj/structure/machinery/disposal/attack_ai(mob/user as mob)
|
|
if(!ai_can_interact(user))
|
|
return
|
|
var/inside_bin = (user.loc == src)
|
|
interact(user, !inside_bin)
|
|
|
|
/**
|
|
* Human interacts with machine.
|
|
*/
|
|
/obj/structure/machinery/disposal/attack_hand(mob/user as mob)
|
|
if(stat & BROKEN)
|
|
return
|
|
|
|
if(user.loc == src)
|
|
to_chat(user, SPAN_WARNING("You cannot reach the controls from here."))
|
|
return
|
|
|
|
interact(user)
|
|
|
|
/obj/structure/machinery/disposal/interact(mob/user)
|
|
if(!user)
|
|
return
|
|
|
|
src.add_fingerprint(user)
|
|
user.set_machine(src)
|
|
if(panel_open)
|
|
wires.interact(user)
|
|
|
|
return ui_interact(user)
|
|
|
|
/obj/structure/machinery/disposal/ui_interact(mob/user, datum/tgui/ui)
|
|
ui = SStgui.try_update_ui(user, src, ui)
|
|
if(!ui)
|
|
ui = new(user, src, "DisposalUnit", "Disposal Unit", 320, 200)
|
|
ui.open()
|
|
|
|
/obj/structure/machinery/disposal/ui_data(mob/user)
|
|
var/list/data = list()
|
|
data["is_on"] = is_on
|
|
data["flush"] = flush
|
|
data["mode"] = mode
|
|
data["uses_air"] = uses_air
|
|
data["panel_open"] = panel_open
|
|
data["pressure"] = CLAMP01(XGM_PRESSURE(air_contents) / (SEND_PRESSURE))
|
|
return data
|
|
|
|
/obj/structure/machinery/disposal/ui_act(action, list/params, datum/tgui/ui, datum/ui_state/state)
|
|
. = ..()
|
|
if(.)
|
|
return
|
|
|
|
switch(action)
|
|
if("handle-0")
|
|
flush = FALSE
|
|
update()
|
|
. = TRUE
|
|
if("handle-1")
|
|
flush = TRUE
|
|
update()
|
|
. = TRUE
|
|
if("power")
|
|
is_on = !is_on
|
|
update()
|
|
. = TRUE
|
|
if("eject")
|
|
eject()
|
|
. = TRUE
|
|
|
|
/**
|
|
* Eject the contents of the disposal unit.
|
|
*/
|
|
/obj/structure/machinery/disposal/proc/eject()
|
|
for(var/atom/movable/AM in src)
|
|
AM.forceMove(src.loc)
|
|
AM.pipe_eject(0)
|
|
update()
|
|
|
|
/**
|
|
* Update the icon & overlays to reflect mode & status
|
|
*/
|
|
/obj/structure/machinery/disposal/proc/update()
|
|
if(!isnull(mode_icon) && (stat & BROKEN))
|
|
ClearOverlays()
|
|
icon_state = "[icon_state]-broken"
|
|
mode_icon = null
|
|
mode = MODE_OFF
|
|
flush = 0
|
|
return
|
|
|
|
var/has_contents = !!length(contents)
|
|
|
|
if (mode_icon == mode && showing_full == has_contents)
|
|
return
|
|
|
|
// if we're here, we must rebuild...
|
|
ClearOverlays()
|
|
|
|
// flush handle
|
|
if(flush)
|
|
AddOverlays("[icon_state]-handle")
|
|
|
|
// only handle is shown if no power
|
|
if(stat & NOPOWER || mode == MODE_OFF)
|
|
mode_icon = MODE_OFF
|
|
return
|
|
|
|
// check for items in disposal - occupied light
|
|
if(has_contents)
|
|
AddOverlays("[icon_state]-full")
|
|
showing_full = has_contents
|
|
|
|
// charging and ready light
|
|
if(mode == MODE_PRESSURIZING)
|
|
AddOverlays("[icon_state]-charge")
|
|
mode_icon = MODE_PRESSURIZING
|
|
else if(mode == MODE_READY)
|
|
AddOverlays("[icon_state]-ready")
|
|
mode_icon = MODE_READY
|
|
|
|
/**
|
|
* Timed process. Charge the gas reservoir and perform flush if ready.
|
|
*/
|
|
/obj/structure/machinery/disposal/process()
|
|
if((stat & BROKEN) || !is_on)
|
|
update_use_power(POWER_USE_OFF)
|
|
return
|
|
|
|
flush_count++
|
|
// We've hit the flush counter automatically
|
|
if(flush_count >= flush_every_ticks)
|
|
if(length(contents))
|
|
if(mode == MODE_READY)
|
|
spawn(0)
|
|
feedback_inc("disposal_auto_flush",1)
|
|
flush()
|
|
flush_count = 0
|
|
SStgui.update_uis(src)
|
|
|
|
// If we're ready, don't draw any extra power
|
|
if(mode == MODE_READY || !uses_air)
|
|
update_use_power(POWER_USE_IDLE)
|
|
// We used the manual flush button
|
|
if(flush)
|
|
flush()
|
|
return
|
|
update()
|
|
|
|
// Validate whether we're pressurized or not.
|
|
if(mode == MODE_PRESSURIZING && XGM_PRESSURE(air_contents) >= SEND_PRESSURE)
|
|
mode = MODE_READY
|
|
update()
|
|
return
|
|
|
|
// If you turn this into a bare 'else' statement it just tries to pressurize infinitely and I don't know why.
|
|
else if(mode == MODE_PRESSURIZING && XGM_PRESSURE(air_contents) < SEND_PRESSURE)
|
|
src.pressurize()
|
|
update()
|
|
return
|
|
|
|
/**
|
|
* If powered and working, transfer gas from local env to internal reservoir and use the required power to do so.
|
|
*/
|
|
/obj/structure/machinery/disposal/proc/pressurize()
|
|
// Don't pressurize if there's no power.
|
|
if(stat & NOPOWER)
|
|
update_use_power(POWER_USE_OFF)
|
|
return
|
|
// Recharge from loc turf
|
|
var/atom/L = loc
|
|
if(!loc) return
|
|
var/datum/gas_mixture/env = L.return_air()
|
|
|
|
var/power_draw = -1
|
|
if(env && env.temperature > 0)
|
|
// Group_multiplier is divided out here
|
|
var/transfer_moles = (PUMP_MAX_FLOW_RATE/env.volume)*env.total_moles
|
|
// Using power, pump air from local tile into itself
|
|
power_draw = pump_gas(src, env, air_contents, transfer_moles, active_power_usage)
|
|
|
|
if (power_draw > 0)
|
|
use_power_oneoff(power_draw)
|
|
// If we've reached the target pressure, we're ready to flush
|
|
if(XGM_PRESSURE(air_contents) >= SEND_PRESSURE)
|
|
mode = MODE_READY
|
|
|
|
/**
|
|
* Attempt to flush. If able, create a virtual holder object containing disposal bin & gas reservoir contents to ship through disposals network
|
|
*/
|
|
/obj/structure/machinery/disposal/proc/flush()
|
|
set waitfor = FALSE
|
|
|
|
if(!can_flush)
|
|
shake_animation()
|
|
visible_message(SPAN_WARNING("\The [src] groans violently!"), range = 3)
|
|
flush = FALSE
|
|
return
|
|
|
|
intent_message(MACHINE_SOUND)
|
|
|
|
mode = MODE_FLUSHING
|
|
flick("[icon_state]-flush", src)
|
|
|
|
var/wrapcheck = 0
|
|
var/obj/disposalholder/H = new() // virtual holder object which actually
|
|
// travels through the pipes.
|
|
//Hacky test to get drones to mail themselves through disposals.
|
|
for(var/mob/living/silicon/robot/drone/D in src)
|
|
wrapcheck = 1
|
|
|
|
for(var/obj/item/smallDelivery/O in src)
|
|
wrapcheck = 1
|
|
|
|
if(wrapcheck == 1)
|
|
H.tomail = 1
|
|
|
|
sleep(10)
|
|
if(last_sound < world.time + 1)
|
|
playsound(src, 'sound/machines/disposalflush.ogg', 50, 0, 0)
|
|
last_sound = world.time
|
|
// Wait for animation to finish
|
|
sleep(5)
|
|
|
|
// Copy the contents of disposer to holder
|
|
H.init(src, air_contents)
|
|
// New empty gas reservoir
|
|
air_contents = new(PRESSURE_TANK_VOLUME)
|
|
|
|
// Start the holder processing movement
|
|
H.start(src)
|
|
|
|
// Now reset disposal state
|
|
flush = 0
|
|
if(mode == MODE_FLUSHING)
|
|
if(uses_air)
|
|
mode = MODE_PRESSURIZING
|
|
else
|
|
mode = MODE_READY
|
|
update()
|
|
return
|
|
|
|
/**
|
|
* Called when area power changes.
|
|
*/
|
|
/obj/structure/machinery/disposal/power_change()
|
|
// do default setting/reset of stat NOPOWER bit
|
|
..()
|
|
// update icon
|
|
update()
|
|
return
|
|
|
|
/**
|
|
* Called when holder is expelled from a disposal- should usually only occur if the pipe network is modified
|
|
*/
|
|
/obj/structure/machinery/disposal/proc/expel(var/obj/disposalholder/H)
|
|
|
|
var/turf/target
|
|
playsound(src, 'sound/machines/hiss.ogg', 50, 0, 0)
|
|
// Somehow, someone managed to flush a window which broke mid-transit and caused the disposal to go in an infinite loop trying to expel null, hopefully this fixes it
|
|
if(H)
|
|
for(var/atom/movable/AM in H)
|
|
target = get_offset_target_turf(src.loc, rand(5)-rand(5), rand(5)-rand(5))
|
|
|
|
AM.forceMove(src.loc)
|
|
AM.pipe_eject(0)
|
|
// Poor drones kept smashing windows and taking system damage being fired out of disposals. ~Z
|
|
if(!istype(AM,/mob/living/silicon/robot/drone))
|
|
addtimer(CALLBACK(AM, TYPE_PROC_REF(/atom/movable, throw_at), target, 5, 1), 1)
|
|
|
|
H.vent_gas(loc)
|
|
qdel(H)
|
|
|
|
/obj/structure/machinery/disposal/CanPass(atom/movable/mover, turf/target, height=0, air_group=0)
|
|
if(mover?.movement_type & PHASING)
|
|
return TRUE
|
|
if(istype(mover, /obj/projectile))
|
|
return 1
|
|
if(istype(mover,/obj/item) && mover.throwing)
|
|
return 0
|
|
|
|
else
|
|
return ..(mover, target, height, air_group)
|
|
|
|
/obj/structure/machinery/disposal/hitby(atom/movable/hitting_atom, skipcatch, hitpush, blocked, datum/thrownthing/throwingdatum)
|
|
if(isitem(hitting_atom))
|
|
if(prob(75))
|
|
hitting_atom.forceMove(src)
|
|
visible_message(SPAN_NOTICE("[hitting_atom] lands in [src]."))
|
|
else
|
|
visible_message(SPAN_NOTICE("[hitting_atom] bounces off of [src]'s rim!"))
|
|
return ..()
|
|
else
|
|
return ..()
|
|
|
|
/obj/structure/machinery/disposal/can_attach_sticker(mob/user, obj/item/sticker/S)
|
|
return FALSE
|
|
|
|
/**
|
|
* Virtual disposal object
|
|
* Travels through pipes in lieu of actual items
|
|
* Contents will be items flushed by the disposal
|
|
* This allows the gas flushed to be tracked
|
|
*/
|
|
/obj/disposalholder
|
|
invisibility = 101
|
|
/// Gas used to flush, will appear at exit point
|
|
var/datum/gas_mixture/gas = null
|
|
dir = 0
|
|
/// Can travel 2048 steps before going inactive (in case of loops)
|
|
var/count = 2048
|
|
/// Vhanges if contains a delivery container
|
|
var/destinationTag = ""
|
|
/// Changes if contains wrapped package
|
|
var/tomail = 0
|
|
/// If it contains a mob
|
|
var/hasmob = 0
|
|
|
|
/// Set by a partial tagger the first time round, then put in destinationTag if it goes through again.
|
|
var/partialTag = ""
|
|
|
|
var/tmp/obj/structure/disposalpipe/tick_last
|
|
|
|
/**
|
|
* Initialize a holder from the contents of a disposal unit.
|
|
*/
|
|
/obj/disposalholder/proc/init(var/obj/structure/machinery/disposal/D, var/datum/gas_mixture/flush_gas)
|
|
gas = flush_gas// transfer gas resv. into holder object -- let's be explicit about the data this proc consumes, please.
|
|
|
|
//Check for any living mobs trigger hasmob.
|
|
//hasmob effects whether the package goes to cargo or its tagged destination.
|
|
for(var/mob/living/M in D)
|
|
if(M && M.stat != 2 && !istype(M,/mob/living/silicon/robot/drone))
|
|
hasmob = 1
|
|
|
|
//Checks 1 contents level deep. This means that players can be sent through disposals...
|
|
//...but it should require a second person to open the package. (i.e. person inside a wrapped locker)
|
|
for(var/obj/O in D)
|
|
if(O.contents)
|
|
for(var/mob/living/M in O.contents)
|
|
if(M && M.stat != 2 && !istype(M,/mob/living/silicon/robot/drone))
|
|
hasmob = 1
|
|
|
|
// now everything inside the disposal gets put into the holder
|
|
// note AM since can contain mobs or objs
|
|
for(var/atom/movable/AM in D)
|
|
if(istype(AM, /obj/item/assembly/signaler))
|
|
var/obj/item/assembly/signaler/S = AM
|
|
if(S.connected == D.wires)
|
|
continue
|
|
AM.forceMove(src)
|
|
if(istype(AM, /obj/structure/bigDelivery) && !hasmob)
|
|
var/obj/structure/bigDelivery/T = AM
|
|
src.destinationTag = T.sortTag
|
|
if(istype(AM, /obj/item/smallDelivery) && !hasmob)
|
|
var/obj/item/smallDelivery/T = AM
|
|
src.destinationTag = T.sortTag
|
|
//Drones can mail themselves through maint.
|
|
if(istype(AM, /mob/living/silicon/robot/drone))
|
|
var/mob/living/silicon/robot/drone/drone = AM
|
|
src.destinationTag = drone.mail_destination
|
|
|
|
|
|
/**
|
|
* Start the movement process
|
|
* Argument is the disposal unit the holder started in
|
|
*/
|
|
/obj/disposalholder/proc/start(var/obj/structure/machinery/disposal/D)
|
|
// No trunk connected, so expel immediately
|
|
if(!D.trunk)
|
|
D.expel(src)
|
|
return
|
|
|
|
forceMove(D.trunk)
|
|
set_dir(DOWN)
|
|
START_PROCESSING(SSdisposals, src)
|
|
|
|
/**
|
|
* For the new SSdisposals-based movement.
|
|
*/
|
|
/obj/disposalholder/process()
|
|
if (hasmob && prob(3))
|
|
for(var/mob/living/H in src)
|
|
// Drones use the mailing code to move through the disposal system,
|
|
if(!istype(H,/mob/living/silicon/robot/drone))
|
|
// Horribly maim any living creature jumping down disposals. C'est la vie
|
|
H.take_overall_damage(20, 0, DAMAGE_FLAG_SHARP, "Blunt Trauma")
|
|
|
|
var/obj/structure/disposalpipe/curr = loc
|
|
if (!loc)
|
|
STOP_PROCESSING(SSdisposals, src)
|
|
tick_last = null
|
|
crash_with("disposalholder processing in nullspace!")
|
|
return
|
|
|
|
tick_last = curr
|
|
curr = curr.transfer(src)
|
|
|
|
if (!loc)
|
|
STOP_PROCESSING(SSdisposals, src)
|
|
tick_last = null
|
|
return
|
|
|
|
if (!curr)
|
|
//spawn (0) // expel() can sleep, so we gotta fork to not upset SSdisposals.
|
|
STOP_PROCESSING(SSdisposals, src)
|
|
tick_last.expel(src, get_turf(loc), dir)
|
|
|
|
if (!(count--))
|
|
STOP_PROCESSING(SSdisposals, src)
|
|
tick_last = null
|
|
|
|
/**
|
|
* Find the turf which should contain the next pipe
|
|
*/
|
|
/obj/disposalholder/proc/nextloc()
|
|
return get_step(loc,dir)
|
|
|
|
/**
|
|
* Find a matching pipe on a turf
|
|
*/
|
|
/obj/disposalholder/proc/findpipe(var/turf/T)
|
|
if(!T)
|
|
return null
|
|
|
|
// flip the movement direction
|
|
var/fdir = turn(dir, 180)
|
|
for(var/obj/structure/disposalpipe/P in T)
|
|
// find pipe direction mask that matches flipped dir
|
|
if(fdir & P.dpdir)
|
|
return P
|
|
// if no matching pipe, return null
|
|
return null
|
|
|
|
/**
|
|
* Merge two holder objects. Used when a a holder meets a stuck holder.
|
|
*/
|
|
/obj/disposalholder/proc/merge(obj/disposalholder/other)
|
|
for(var/atom/movable/AM in other)
|
|
// move everything in other holder to this one
|
|
AM.forceMove(src)
|
|
if(ismob(AM))
|
|
var/mob/M = AM
|
|
// if a client mob, update eye to follow this holder
|
|
if(M.client)
|
|
M.client.eye = src
|
|
|
|
qdel(other)
|
|
|
|
/obj/disposalholder/proc/settag(new_tag)
|
|
destinationTag = new_tag
|
|
|
|
/obj/disposalholder/proc/setpartialtag(new_tag)
|
|
if(partialTag == new_tag)
|
|
destinationTag = new_tag
|
|
partialTag = ""
|
|
else
|
|
partialTag = new_tag
|
|
|
|
/**
|
|
* Called when player tries to move while in a pipe.
|
|
*/
|
|
/obj/disposalholder/relaymove(mob/living/user, direction)
|
|
. = ..()
|
|
|
|
if(!istype(user,/mob/living))
|
|
return
|
|
|
|
var/mob/living/U = user
|
|
|
|
if (U.stat || U.last_special <= world.time)
|
|
return
|
|
|
|
U.last_special = world.time+100
|
|
|
|
if (src.loc)
|
|
for (var/mob/M in hearers(src.loc.loc))
|
|
to_chat(M, "<FONT size=[max(0, 5 - get_dist(src, M))]>CLONG, clong!</FONT>")
|
|
|
|
playsound(src.loc, 'sound/effects/clang.ogg', 50, 0, 0)
|
|
|
|
/**
|
|
* Called to vent all gas in holder to a location
|
|
*/
|
|
/obj/disposalholder/proc/vent_gas(atom/location)
|
|
location.assume_air(gas) // vent all gas to turf
|
|
|
|
/obj/disposalholder/Destroy()
|
|
STOP_PROCESSING(SSdisposals, src)
|
|
QDEL_NULL(gas)
|
|
tick_last = null
|
|
return ..()
|
|
|
|
// Disposal pipes
|
|
/obj/structure/disposalpipe
|
|
icon = 'icons/obj/disposals.dmi'
|
|
name = "disposal pipe"
|
|
desc = "An underfloor disposal pipe."
|
|
anchored = 1
|
|
density = 0
|
|
maxhealth = OBJECT_HEALTH_FRAGILE
|
|
|
|
level = 1 // underfloor only
|
|
|
|
var/dpdir = 0 // bitmask of pipe directions
|
|
//dir = 0 // dir will contain dominant direction for junction pipes
|
|
layer = EXPOSED_DISPOSALS_PIPE_LAYER
|
|
var/sortType = ""
|
|
var/subtype = 0
|
|
// new pipe, set the icon_state as on map
|
|
|
|
/obj/structure/disposalpipe/Initialize(mapload)
|
|
. = ..()
|
|
|
|
if(mapload)
|
|
var/turf/T = loc
|
|
var/image/I = image(icon, T, icon_state, dir, pixel_x, pixel_y)
|
|
I.layer = ABOVE_TILE_LAYER
|
|
I.alpha = 125
|
|
LAZYADD(T.blueprints, I)
|
|
|
|
/**
|
|
* Pipe is deleted. Ensure if holder is present, it is expelled.
|
|
*/
|
|
/obj/structure/disposalpipe/Destroy()
|
|
var/obj/disposalholder/H = locate() in src
|
|
if(H)
|
|
// holder was present
|
|
STOP_PROCESSING(SSdisposals, H)
|
|
var/turf/T = src.loc
|
|
if(T.density)
|
|
// deleting pipe is inside a dense turf (wall)
|
|
// this is unlikely, but just dump out everything into the turf in case
|
|
|
|
for(var/atom/movable/AM in H)
|
|
AM.forceMove(T)
|
|
AM.pipe_eject(0)
|
|
qdel(H)
|
|
|
|
return ..()
|
|
|
|
// otherwise, do normal expel from turf
|
|
if(H)
|
|
expel(H, T, 0)
|
|
return ..()
|
|
|
|
/**
|
|
* Returns the direction of the next pipe object, given the entrance dir
|
|
* By default, returns the bitmask of remaining directions
|
|
*/
|
|
/obj/structure/disposalpipe/proc/nextdir(var/fromdir)
|
|
return dpdir & (~turn(fromdir, 180))
|
|
|
|
/**
|
|
* Transfer the holder through this pipe segment
|
|
* Overriden for special behaviour
|
|
*/
|
|
/obj/structure/disposalpipe/proc/transfer(var/obj/disposalholder/H)
|
|
var/nextdir = nextdir(H.dir)
|
|
H.set_dir(nextdir)
|
|
var/turf/T = H.nextloc()
|
|
var/obj/structure/disposalpipe/P = H.findpipe(T)
|
|
|
|
if(P)
|
|
// find other holder in next loc, if inactive merge it with current
|
|
var/obj/disposalholder/H2 = locate() in P
|
|
if(H2 && !(H2.datum_flags & DF_ISPROCESSING))
|
|
H.merge(H2)
|
|
|
|
H.forceMove(P)
|
|
else // if wasn't a pipe, then set loc to turf
|
|
H.forceMove(T)
|
|
return null
|
|
|
|
return P
|
|
|
|
/**
|
|
* Update the icon_state to reflect hidden status
|
|
*/
|
|
/obj/structure/disposalpipe/proc/update()
|
|
var/turf/T = src.loc
|
|
hide(!T.is_plating() && !istype(T,/turf/space)) // space never hides pipes
|
|
|
|
/**
|
|
* Hide called by levelupdate if turf intact status changes
|
|
* Change visibility status and force update of icon
|
|
*/
|
|
/obj/structure/disposalpipe/hide(var/intact)
|
|
set_invisibility(intact ? 101: 0) // hide if floor is intact
|
|
|
|
/**
|
|
* Expel the held objects into a turf
|
|
* Called when there is a break in the pipe
|
|
*/
|
|
/obj/structure/disposalpipe/proc/expel(var/obj/disposalholder/H, var/turf/T, var/direction)
|
|
if(!istype(H) || !istype(T))
|
|
return
|
|
|
|
// Empty the holder if it is expelled into a dense turf.
|
|
// Leaving it intact and sitting in a wall is stupid.
|
|
if(T.density)
|
|
for(var/atom/movable/AM in H)
|
|
AM.forceMove(T)
|
|
AM.pipe_eject(0)
|
|
qdel(H)
|
|
return
|
|
|
|
if(!T.is_plating() && istype(T,/turf/simulated/floor)) //intact floor, pop the tile
|
|
var/turf/simulated/floor/F = T
|
|
F.break_tile()
|
|
new /obj/item/stack/tile(H) // add to holder so it will be thrown with other stuff
|
|
|
|
var/turf/target
|
|
if(direction) // direction is specified
|
|
if(istype(T, /turf/space)) // if ended in space, then range is unlimited
|
|
target = get_edge_target_turf(T, direction)
|
|
else // otherwise limit to 10 tiles
|
|
target = get_ranged_target_turf(T, direction, 10)
|
|
|
|
playsound(src, 'sound/machines/hiss.ogg', 50, 0, 0)
|
|
if(H)
|
|
for(var/atom/movable/AM in H)
|
|
AM.forceMove(T)
|
|
AM.pipe_eject(direction)
|
|
// addtimer will check AM for null.
|
|
addtimer(CALLBACK(AM, TYPE_PROC_REF(/atom/movable, throw_at), target, 100, 1), 1)
|
|
H.vent_gas(T)
|
|
qdel(H)
|
|
|
|
else // no specified direction, so throw in random direction
|
|
playsound(src, 'sound/machines/hiss.ogg', 50, 0, 0)
|
|
if(H)
|
|
for(var/atom/movable/AM in H)
|
|
target = get_offset_target_turf(T, rand(5)-rand(5), rand(5)-rand(5))
|
|
|
|
AM.forceMove(T)
|
|
AM.pipe_eject(0)
|
|
addtimer(CALLBACK(AM, TYPE_PROC_REF(/atom/movable, throw_at), target, 5, 1), 1)
|
|
|
|
H.vent_gas(T) // all gas vent to turf
|
|
qdel(H)
|
|
|
|
|
|
/**
|
|
* Call to break the pipe: will expel any holder inside at the time then delete the pipe
|
|
* remains: Set to leave broken pipe pieces in place.
|
|
*/
|
|
/obj/structure/disposalpipe/proc/broken(var/remains = FALSE)
|
|
if(remains)
|
|
for(var/D in GLOB.cardinals)
|
|
if(D & dpdir)
|
|
var/obj/structure/disposalpipe/broken/P = new(src.loc)
|
|
P.set_dir(D)
|
|
|
|
set_invisibility(101) // make invisible (since we won't delete the pipe immediately)
|
|
var/obj/disposalholder/H = locate() in src
|
|
if(H)
|
|
// holder was present
|
|
STOP_PROCESSING(SSdisposals, H)
|
|
var/turf/T = src.loc
|
|
if(T.density)
|
|
// broken pipe is inside a dense turf (wall)
|
|
// this is unlikely, but just dump out everything into the turf in case
|
|
|
|
for(var/atom/movable/AM in H)
|
|
AM.forceMove(T)
|
|
AM.pipe_eject(0)
|
|
qdel(H)
|
|
return
|
|
|
|
// otherwise, do normal expel from turf
|
|
if(H)
|
|
expel(H, T, 0)
|
|
|
|
QDEL_IN(src, 2) // delete pipe after 2 ticks to ensure expel proc finished
|
|
|
|
|
|
/**
|
|
* Pipe is affected by an explosion
|
|
*/
|
|
/obj/structure/disposalpipe/ex_act(severity)
|
|
switch(severity)
|
|
if(1.0)
|
|
broken(FALSE)
|
|
if(2.0)
|
|
add_damage(rand(5,15))
|
|
if(3.0)
|
|
add_damage(rand(0,15))
|
|
|
|
/obj/structure/disposalpipe/on_death(damage, damage_flags, damage_type, armor_penetration, obj/weapon)
|
|
if(health < -2)
|
|
broken(FALSE)
|
|
else if(health < 1)
|
|
broken(TRUE)
|
|
|
|
/**
|
|
* Attack by item
|
|
* Welding tool: Unfasten and convert to obj/disposalconstruct
|
|
*/
|
|
/obj/structure/disposalpipe/attackby(obj/item/attacking_item, mob/user)
|
|
var/turf/T = src.loc
|
|
if(!T.is_plating())
|
|
return // prevent interaction with T-scanner revealed pipes
|
|
src.add_fingerprint(user)
|
|
if(attacking_item.tool_behaviour == TOOL_WELDER)
|
|
var/obj/item/weldingtool/W = attacking_item
|
|
|
|
if(W.use(0,user))
|
|
playsound(src.loc, 'sound/items/welder_pry.ogg', 100, 1)
|
|
// check if anything changed over 3 seconds
|
|
to_chat(user, "Slicing the disposal pipe...")
|
|
if(W.use_tool(src, user, 30, volume = 50))
|
|
if(!src || !W.isOn()) return
|
|
welded()
|
|
else
|
|
to_chat(user, "You must stay still while welding the pipe.")
|
|
else
|
|
to_chat(user, "You need more welding fuel to cut the pipe.")
|
|
|
|
/**
|
|
* Called when pipe is cut by a welder.
|
|
*/
|
|
/obj/structure/disposalpipe/proc/welded()
|
|
var/obj/structure/disposalconstruct/C = new (src.loc)
|
|
switch(icon_state)
|
|
if("pipe-s")
|
|
C.ptype = 0
|
|
if("pipe-c")
|
|
C.ptype = 1
|
|
if("pipe-j1")
|
|
C.ptype = 2
|
|
if("pipe-j2")
|
|
C.ptype = 3
|
|
if("pipe-y")
|
|
C.ptype = 4
|
|
if("pipe-t")
|
|
C.ptype = 5
|
|
if("pipe-j1s")
|
|
C.ptype = 9
|
|
C.sortType = sortType
|
|
if("pipe-j2s")
|
|
C.ptype = 10
|
|
C.sortType = sortType
|
|
///// Z-Level stuff
|
|
if("pipe-u")
|
|
C.ptype = 11
|
|
if("pipe-d")
|
|
C.ptype = 12
|
|
///// Z-Level stuff
|
|
if("pipe-tagger")
|
|
C.ptype = 13
|
|
if("pipe-tagger-partial")
|
|
C.ptype = 14
|
|
C.subtype = src.subtype
|
|
src.transfer_fingerprints_to(C)
|
|
C.set_dir(dir)
|
|
C.density = 0
|
|
C.anchored = 1
|
|
C.update()
|
|
|
|
qdel(src)
|
|
|
|
/obj/structure/disposalpipe/hides_under_flooring()
|
|
return 1
|
|
|
|
// *** TEST verb
|
|
//client/verb/dispstop()
|
|
// for(var/obj/disposalholder/H in world)
|
|
// H.active = 0
|
|
|
|
/// a straight or bent segment
|
|
/obj/structure/disposalpipe/segment
|
|
icon_state = "pipe-s"
|
|
|
|
/obj/structure/disposalpipe/segment/Initialize()
|
|
. = ..()
|
|
if(icon_state == "pipe-s")
|
|
dpdir = dir | turn(dir, 180)
|
|
else
|
|
dpdir = dir | turn(dir, -90)
|
|
|
|
update()
|
|
|
|
/// Z-Level stuff
|
|
/obj/structure/disposalpipe/up
|
|
icon_state = "pipe-u"
|
|
|
|
/obj/structure/disposalpipe/up/Initialize()
|
|
. = ..()
|
|
dpdir = dir
|
|
update()
|
|
|
|
/obj/structure/disposalpipe/up/nextdir(var/fromdir)
|
|
var/nextdir
|
|
if(fromdir == 11)
|
|
nextdir = dir
|
|
else
|
|
nextdir = 12
|
|
return nextdir
|
|
|
|
/obj/structure/disposalpipe/up/transfer(var/obj/disposalholder/H)
|
|
var/nextdir = nextdir(H.dir)
|
|
H.set_dir(nextdir)
|
|
|
|
var/turf/T
|
|
var/obj/structure/disposalpipe/P
|
|
|
|
if(nextdir == 12)
|
|
var/turf/current_turf = get_turf(src)
|
|
T = GET_TURF_ABOVE(current_turf)
|
|
if(!T)
|
|
H.forceMove(loc)
|
|
return
|
|
else
|
|
for(var/obj/structure/disposalpipe/down/F in T)
|
|
P = F
|
|
|
|
else
|
|
T = get_step(src.loc, H.dir)
|
|
P = H.findpipe(T)
|
|
|
|
if(P)
|
|
// find other holder in next loc, if inactive merge it with current
|
|
var/obj/disposalholder/H2 = locate() in P
|
|
if(H2 && !(H2.datum_flags & DF_ISPROCESSING))
|
|
H.merge(H2)
|
|
|
|
H.forceMove(P)
|
|
else // if wasn't a pipe, then set loc to turf
|
|
H.forceMove(T)
|
|
return null
|
|
|
|
return P
|
|
|
|
/obj/structure/disposalpipe/down
|
|
icon_state = "pipe-d"
|
|
|
|
/obj/structure/disposalpipe/down/Initialize()
|
|
. = ..()
|
|
dpdir = dir
|
|
update()
|
|
|
|
/obj/structure/disposalpipe/down/nextdir(var/fromdir)
|
|
var/nextdir
|
|
if(fromdir == 12)
|
|
nextdir = dir
|
|
else
|
|
nextdir = 11
|
|
return nextdir
|
|
|
|
/obj/structure/disposalpipe/down/transfer(var/obj/disposalholder/H)
|
|
var/nextdir = nextdir(H.dir)
|
|
H.dir = nextdir
|
|
|
|
var/turf/T
|
|
var/obj/structure/disposalpipe/P
|
|
|
|
if(nextdir == 11)
|
|
var/turf/current_turf = get_turf(src)
|
|
T = GET_TURF_BELOW(current_turf)
|
|
if(!T)
|
|
H.forceMove(src.loc)
|
|
return
|
|
else
|
|
for(var/obj/structure/disposalpipe/up/F in T)
|
|
P = F
|
|
|
|
else
|
|
T = get_step(src.loc, H.dir)
|
|
P = H.findpipe(T)
|
|
|
|
if(P)
|
|
// find other holder in next loc, if inactive merge it with current
|
|
var/obj/disposalholder/H2 = locate() in P
|
|
if(H2 && !(H2.datum_flags & DF_ISPROCESSING))
|
|
H.merge(H2)
|
|
|
|
H.forceMove(P)
|
|
else // if wasn't a pipe, then set loc to turf
|
|
H.forceMove(T)
|
|
return null
|
|
|
|
return P
|
|
// Z-Level stuff
|
|
|
|
/obj/structure/disposalpipe/junction/yjunction
|
|
icon_state = "pipe-y"
|
|
|
|
///a three-way junction with dir being the dominant direction
|
|
/obj/structure/disposalpipe/junction
|
|
icon_state = "pipe-j1"
|
|
|
|
/obj/structure/disposalpipe/junction/Initialize()
|
|
. = ..()
|
|
if(icon_state == "pipe-j1")
|
|
dpdir = dir | turn(dir, -90) | turn(dir,180)
|
|
else if(icon_state == "pipe-j2")
|
|
dpdir = dir | turn(dir, 90) | turn(dir,180)
|
|
else // pipe-y
|
|
dpdir = dir | turn(dir,90) | turn(dir, -90)
|
|
update()
|
|
|
|
/*
|
|
* Next direction to move:
|
|
* If coming in from secondary dirs, then next is primary dir.
|
|
* If coming in from primary dir, then next is equal chance of other dirs.
|
|
*/
|
|
/obj/structure/disposalpipe/junction/nextdir(var/fromdir)
|
|
var/flipdir = turn(fromdir, 180)
|
|
if(flipdir != dir) // came from secondary dir
|
|
return dir // so exit through primary
|
|
else // came from primary
|
|
// so need to choose either secondary exit
|
|
var/mask = ..(fromdir)
|
|
|
|
// find a bit which is set
|
|
var/setbit = 0
|
|
if(mask & NORTH)
|
|
setbit = NORTH
|
|
else if(mask & SOUTH)
|
|
setbit = SOUTH
|
|
else if(mask & EAST)
|
|
setbit = EAST
|
|
else
|
|
setbit = WEST
|
|
|
|
if(prob(50)) // 50% chance to choose the found bit or the other one
|
|
return setbit
|
|
else
|
|
return mask & (~setbit)
|
|
|
|
/obj/structure/disposalpipe/tagger
|
|
name = "package tagger"
|
|
icon_state = "pipe-tagger"
|
|
var/sort_tag = ""
|
|
var/partial = 0
|
|
var/no_override = 0
|
|
|
|
/obj/structure/disposalpipe/tagger/proc/updatedesc()
|
|
desc = initial(desc)
|
|
if(sort_tag)
|
|
desc += "\nIt's tagging objects with the '[sort_tag]' tag."
|
|
|
|
/obj/structure/disposalpipe/tagger/proc/updatename()
|
|
if(sort_tag)
|
|
name = "[initial(name)] ([sort_tag])"
|
|
else
|
|
name = initial(name)
|
|
|
|
/obj/structure/disposalpipe/tagger/Initialize()
|
|
. = ..()
|
|
dpdir = dir | turn(dir, 180)
|
|
if(sort_tag)
|
|
SSdisposals.tagger_locations |= sort_tag
|
|
|
|
updatename()
|
|
updatedesc()
|
|
update()
|
|
|
|
/obj/structure/disposalpipe/tagger/attackby(obj/item/attacking_item, mob/user)
|
|
if(..())
|
|
return
|
|
|
|
if(istype(attacking_item, /obj/item/destTagger))
|
|
var/obj/item/destTagger/O = attacking_item
|
|
|
|
if(O.currTag)// Tag set
|
|
sort_tag = O.currTag
|
|
playsound(src.loc, 'sound/machines/twobeep.ogg', 100, 1)
|
|
to_chat(user, SPAN_NOTICE("Changed tag to '[sort_tag]'."))
|
|
updatename()
|
|
updatedesc()
|
|
|
|
/obj/structure/disposalpipe/tagger/transfer(var/obj/disposalholder/H)
|
|
if(sort_tag)
|
|
if(!no_override || H.destinationTag == "")
|
|
if(partial)
|
|
H.setpartialtag(sort_tag)
|
|
else
|
|
H.settag(sort_tag)
|
|
return ..()
|
|
|
|
/obj/structure/disposalpipe/tagger/partial //needs two passes to tag
|
|
name = "partial package tagger"
|
|
desc = "A unique desitnation tagger that requires an object to pass 2 times to tag."
|
|
icon_state = "pipe-tagger-partial"
|
|
partial = 1
|
|
|
|
///a three-way junction that sorts objects
|
|
/obj/structure/disposalpipe/sortjunction
|
|
name = "sorting junction"
|
|
icon_state = "pipe-j1s"
|
|
desc = "An underfloor disposal pipe with a package sorting mechanism."
|
|
|
|
var/posdir = 0
|
|
var/negdir = 0
|
|
var/sortdir = 0
|
|
|
|
/obj/structure/disposalpipe/sortjunction/proc/updatedesc()
|
|
desc = initial(desc)
|
|
if(sortType)
|
|
desc += "\nIt's filtering objects with the '[sortType]' tag."
|
|
|
|
/obj/structure/disposalpipe/sortjunction/proc/updatename()
|
|
if(sortType)
|
|
name = "[initial(name)] ([sortType])"
|
|
else
|
|
name = initial(name)
|
|
|
|
/obj/structure/disposalpipe/sortjunction/proc/updatedir()
|
|
posdir = dir
|
|
negdir = turn(posdir, 180)
|
|
|
|
if(icon_state == "pipe-j1s")
|
|
sortdir = turn(posdir, -90)
|
|
else if(icon_state == "pipe-j2s")
|
|
sortdir = turn(posdir, 90)
|
|
|
|
dpdir = sortdir | posdir | negdir
|
|
|
|
/obj/structure/disposalpipe/sortjunction/Initialize()
|
|
. = ..()
|
|
if(sortType)
|
|
SSdisposals.tagger_locations |= sortType
|
|
|
|
updatedir()
|
|
updatename()
|
|
updatedesc()
|
|
update()
|
|
|
|
/obj/structure/disposalpipe/sortjunction/attackby(obj/item/attacking_item, mob/user)
|
|
if(..())
|
|
return
|
|
|
|
if(istype(attacking_item, /obj/item/destTagger))
|
|
var/obj/item/destTagger/O = attacking_item
|
|
|
|
if(O.currTag)// Tag set
|
|
sortType = O.currTag
|
|
playsound(src.loc, 'sound/machines/twobeep.ogg', 100, 1)
|
|
to_chat(user, SPAN_NOTICE("Changed filter to '[sortType]'."))
|
|
updatename()
|
|
updatedesc()
|
|
|
|
/obj/structure/disposalpipe/sortjunction/proc/divert_check(var/checkTag)
|
|
return sortType == checkTag
|
|
|
|
/**
|
|
* Next direction to move:
|
|
* If coming in from negdir, then next is primary dir or sortdir.
|
|
* If coming in from posdir, then flip around and go back to posdir.
|
|
* If coming in from sortdir, go to posdir.
|
|
*/
|
|
/obj/structure/disposalpipe/sortjunction/nextdir(var/fromdir, var/sortTag)
|
|
if(fromdir != sortdir) // probably came from the negdir
|
|
if(divert_check(sortTag))
|
|
return sortdir
|
|
else
|
|
return posdir
|
|
else // came from sortdir
|
|
// so go with the flow to positive direction
|
|
return posdir
|
|
|
|
/obj/structure/disposalpipe/sortjunction/transfer(var/obj/disposalholder/H)
|
|
var/nextdir = nextdir(H.dir, H.destinationTag)
|
|
H.set_dir(nextdir)
|
|
var/turf/T = H.nextloc()
|
|
var/obj/structure/disposalpipe/P = H.findpipe(T)
|
|
|
|
if(P)
|
|
// find other holder in next loc, if inactive merge it with current
|
|
var/obj/disposalholder/H2 = locate() in P
|
|
if(H2 && !(H2.datum_flags & DF_ISPROCESSING))
|
|
H.merge(H2)
|
|
|
|
H.forceMove(P)
|
|
else // if wasn't a pipe, then set loc to turf
|
|
H.forceMove(T)
|
|
return null
|
|
|
|
return P
|
|
|
|
/// a three-way junction that filters all wrapped and tagged items
|
|
/obj/structure/disposalpipe/sortjunction/wildcard
|
|
name = "tagged sorting junction"
|
|
desc = "An underfloor disposal pipe which filters all wrapped and tagged items."
|
|
subtype = 1
|
|
|
|
/obj/structure/disposalpipe/sortjunction/wildcard/divert_check(var/checkTag)
|
|
return checkTag != ""
|
|
|
|
/// junction that filters all untagged items
|
|
/obj/structure/disposalpipe/sortjunction/untagged
|
|
name = "untagged sorting junction"
|
|
desc = "An underfloor disposal pipe which filters all untagged items."
|
|
subtype = 2
|
|
|
|
/obj/structure/disposalpipe/sortjunction/untagged/divert_check(var/checkTag)
|
|
return checkTag == ""
|
|
|
|
/obj/structure/disposalpipe/sortjunction/flipped //for easier and cleaner mapping
|
|
icon_state = "pipe-j2s"
|
|
|
|
/obj/structure/disposalpipe/sortjunction/wildcard/flipped
|
|
icon_state = "pipe-j2s"
|
|
|
|
/obj/structure/disposalpipe/sortjunction/untagged/flipped
|
|
icon_state = "pipe-j2s"
|
|
|
|
/// a trunk joining to a disposal bin or outlet on the same turf
|
|
/obj/structure/disposalpipe/trunk
|
|
icon_state = "pipe-t"
|
|
var/obj/linked // the linked obj/structure/machinery/disposal or obj/disposaloutlet
|
|
|
|
/obj/structure/disposalpipe/trunk/Initialize()
|
|
. = ..()
|
|
dpdir = dir
|
|
|
|
update()
|
|
|
|
getlinked()
|
|
|
|
/obj/structure/disposalpipe/trunk/proc/getlinked()
|
|
linked = null
|
|
var/obj/structure/machinery/disposal/D = locate() in src.loc
|
|
if(D)
|
|
linked = D
|
|
if (!D.trunk)
|
|
D.trunk = src
|
|
|
|
var/obj/structure/disposaloutlet/O = locate() in src.loc
|
|
if(O)
|
|
linked = O
|
|
|
|
update()
|
|
|
|
/**
|
|
* Override attackby so we disallow trunkremoval when somethings ontop
|
|
*/
|
|
/obj/structure/disposalpipe/trunk/attackby(obj/item/attacking_item, mob/user)
|
|
|
|
//Disposal bins or chutes
|
|
/*
|
|
These shouldn't be required
|
|
var/obj/structure/machinery/disposal/D = locate() in src.loc
|
|
if(D && D.anchored)
|
|
return
|
|
|
|
//Disposal outlet
|
|
var/obj/structure/disposaloutlet/O = locate() in src.loc
|
|
if(O && O.anchored)
|
|
return
|
|
*/
|
|
|
|
//Disposal constructors
|
|
var/obj/structure/disposalconstruct/C = locate() in src.loc
|
|
if(C && C.anchored)
|
|
return
|
|
|
|
var/turf/T = src.loc
|
|
if(!T.is_plating())
|
|
return // prevent interaction with T-scanner revealed pipes
|
|
src.add_fingerprint(user)
|
|
if(attacking_item.tool_behaviour == TOOL_WELDER)
|
|
var/obj/item/weldingtool/W = attacking_item
|
|
|
|
if(W.use(0,user))
|
|
playsound(src.loc, 'sound/items/welder_pry.ogg', 100, 1)
|
|
// check if anything changed over 3 seconds
|
|
to_chat(user, "Slicing the disposal pipe.")
|
|
if(W.use_tool(src, user, 30, volume = 50))
|
|
if(!src || !W.isOn()) return
|
|
welded()
|
|
else
|
|
to_chat(user, "You must stay still while welding the pipe.")
|
|
else
|
|
to_chat(user, "You need more welding fuel to cut the pipe.")
|
|
|
|
/*
|
|
* Would transfer to next pipe segment, but we are in a trunk.
|
|
* If not entering from disposal bin, transfer to linked object (outlet or bin)
|
|
*/
|
|
/obj/structure/disposalpipe/trunk/transfer(var/obj/disposalholder/H)
|
|
|
|
if(H.dir == DOWN) // we just entered from a disposer
|
|
return ..() // so do base transfer proc
|
|
// otherwise, go to the linked object
|
|
if(linked)
|
|
var/obj/structure/disposaloutlet/O = linked
|
|
if(istype(O) && (H))
|
|
O.expel(H) // expel at outlet
|
|
else
|
|
var/obj/structure/machinery/disposal/D = linked
|
|
if(H)
|
|
D.expel(H) // expel at disposal
|
|
else
|
|
if(H)
|
|
src.expel(H, get_turf(src), 0) // expel at turf
|
|
return null
|
|
|
|
// nextdir
|
|
|
|
/obj/structure/disposalpipe/trunk/nextdir(var/fromdir)
|
|
if(fromdir == DOWN)
|
|
return dir
|
|
else
|
|
return 0
|
|
|
|
/obj/structure/disposalpipe/trunk/expel(obj/disposalholder/H)
|
|
if (!linked)
|
|
..(H)
|
|
|
|
/// A broken pipe.
|
|
/obj/structure/disposalpipe/broken
|
|
icon_state = "pipe-b"
|
|
dpdir = 0 // broken pipes have dpdir=0 so they're not found as 'real' pipes
|
|
// i.e. will be treated as an empty turf
|
|
desc = "A broken piece of disposal pipe."
|
|
|
|
/obj/structure/disposalpipe/broken/Initialize()
|
|
. = ..()
|
|
update()
|
|
|
|
/**
|
|
* Called when welded
|
|
* For broken pipe, remove and turn into scrap
|
|
*/
|
|
/obj/structure/disposalpipe/broken/welded()
|
|
// var/obj/item/scrap/S = new(src.loc)
|
|
// S.set_components(200,0,0)
|
|
qdel(src)
|
|
|
|
/// The disposal outlet machine
|
|
/obj/structure/disposaloutlet
|
|
name = "disposal outlet"
|
|
desc = "An outlet for the pneumatic disposal system."
|
|
icon = 'icons/obj/disposals.dmi'
|
|
icon_state = "outlet"
|
|
density = 1
|
|
anchored = 1
|
|
var/active = 0
|
|
var/turf/target // this will be where the output objects are 'thrown' to.
|
|
var/mode = 0
|
|
|
|
var/spread = 0
|
|
var/spread_point = 3
|
|
|
|
/obj/structure/disposaloutlet/Initialize()
|
|
. = ..()
|
|
target = get_ranged_target_turf(src, dir, spread_point)
|
|
|
|
var/obj/structure/disposalpipe/trunk/trunk = locate() in src.loc
|
|
if(trunk)
|
|
trunk.linked = src // link the pipe trunk to self
|
|
|
|
/proc/disposal_log(thing)
|
|
LOG_DEBUG("\[[world.time]] Disposals: [thing]")
|
|
|
|
/obj/structure/disposaloutlet/proc/expel(var/obj/disposalholder/H)
|
|
set waitfor = FALSE
|
|
flick("outlet-open", src)
|
|
playsound(src, 'sound/machines/warning-buzzer.ogg', 50, 0, 0)
|
|
sleep(20) //wait until correct animation frame
|
|
playsound(src, 'sound/machines/hiss.ogg', 50, 0, 0)
|
|
intent_message(THUNK_SOUND)
|
|
|
|
if(H)
|
|
for(var/atom/movable/AM in H)
|
|
AM.forceMove(src.loc)
|
|
AM.pipe_eject(dir)
|
|
if(!istype(AM,/mob/living/silicon/robot/drone)) //Drones keep smashing windows from being fired out of chutes. Bad for the station. ~Z
|
|
spawn(5)
|
|
if(spread)
|
|
var/turf/new_turf_target = get_step(target,turn(src.dir, rand(-spread,spread)))
|
|
AM.throw_at(new_turf_target, spread_point, 1)
|
|
else
|
|
AM.throw_at(target, spread_point, 1)
|
|
|
|
H.vent_gas(src.loc)
|
|
qdel(H)
|
|
|
|
return
|
|
/*
|
|
// expel the contents of the holder object, then delete it
|
|
// called when the holder exits the outlet
|
|
/obj/structure/disposaloutlet/proc/expel(var/obj/structure/disposalholder/H)
|
|
disposal_log("[src] [REF(src)] expel([REF(H)])")
|
|
|
|
flick("outlet-open", src)
|
|
playsound(src, 'sound/machines/warning-buzzer.ogg', 50, 0, 0)
|
|
disposal_log("[src] ([REF(src)]) registering timers.")
|
|
addtimer(CALLBACK(src, PROC_REF(post_expel, H)), 20, TIMER_UNIQUE|TIMER_CLIENT_TIME) // Sound + gas.
|
|
addtimer(CALLBACK(src, PROC_REF(post_post_expel, H)), 20 + 5, TIMER_UNIQUE|TIMER_CLIENT_TIME) // Actually throwing the items.
|
|
|
|
/obj/structure/disposaloutlet/proc/post_expel(obj/structure/disposalholder/H)
|
|
playsound(src, 'sound/machines/hiss.ogg', 50, 0, 0)
|
|
disposal_log("[src] ([REF(src)]) post_expel() timer fired.")
|
|
if(H)
|
|
H.vent_gas(src.loc)
|
|
|
|
/obj/structure/disposaloutlet/proc/throw_object(atom/movable/thing)
|
|
disposal_log("[src] ([REF(src)]) throw_object([thing] [REF(thing)]) at [target] [REF(target)] origin [loc] [REF(loc)]")
|
|
thing.forceMove(loc)
|
|
thing.pipe_eject(dir)
|
|
if (!istype(thing, /mob/living/silicon/robot/drone))
|
|
thing.throw_at(target, 3, 1)
|
|
|
|
/obj/structure/disposaloutlet/proc/post_post_expel(obj/structure/disposalholder/H)
|
|
disposal_log("[src] [REF(src)] post_post_expel([REF(H)]), [H.contents.len] movables")
|
|
for(var/atom/movable/AM in H)
|
|
AM.forceMove(src.loc)
|
|
AM.pipe_eject(dir)
|
|
if(!istype(AM,/mob/living/silicon/robot/drone)) //Drones keep smashing windows from being fired out of chutes. Bad for the station. ~Z
|
|
AM.throw_at(target, 3, 1)
|
|
qdel(H)
|
|
*/
|
|
|
|
/obj/structure/disposaloutlet/attackby(obj/item/attacking_item, mob/user)
|
|
if(!attacking_item || !user)
|
|
return
|
|
src.add_fingerprint(user)
|
|
if(attacking_item.tool_behaviour == TOOL_SCREWDRIVER)
|
|
if(mode==0)
|
|
mode=1
|
|
attacking_item.play_tool_sound(get_turf(src), 50)
|
|
to_chat(user, "You remove the screws around the power connection.")
|
|
return
|
|
else if(mode==1)
|
|
mode=0
|
|
attacking_item.play_tool_sound(get_turf(src), 50)
|
|
to_chat(user, "You attach the screws around the power connection.")
|
|
return
|
|
else if(attacking_item.tool_behaviour == TOOL_WELDER && mode==1)
|
|
var/obj/item/weldingtool/W = attacking_item
|
|
if(W.use(0,user))
|
|
to_chat(user, "You start slicing the floorweld off the disposal outlet.")
|
|
if(W.use_tool(src, user, 20, volume = 50))
|
|
if(!src || !W.isOn()) return
|
|
to_chat(user, "You slice the floorweld off the disposal outlet.")
|
|
var/obj/structure/disposalconstruct/C = new (src.loc)
|
|
src.transfer_fingerprints_to(C)
|
|
C.ptype = 7 // 7 = outlet
|
|
C.update()
|
|
C.anchored = 1
|
|
C.density = 1
|
|
qdel(src)
|
|
return
|
|
else
|
|
to_chat(user, "You need more welding fuel to complete this task.")
|
|
return
|
|
|
|
/**
|
|
* Called when movable is expelled from a disposal pipe or outlet
|
|
* By default does nothing, override for special behaviour
|
|
*/
|
|
/atom/movable/proc/pipe_eject(var/direction)
|
|
return
|
|
|
|
/**
|
|
* Check if mob has client, if so restore client view on eject
|
|
*/
|
|
/mob/pipe_eject(var/direction)
|
|
if (src.client)
|
|
src.client.perspective = MOB_PERSPECTIVE
|
|
src.client.eye = src
|
|
|
|
return
|
|
|
|
/obj/effect/decal/cleanable/blood/gibs/pipe_eject(var/direction)
|
|
var/list/dirs
|
|
if(direction)
|
|
dirs = list( direction, turn(direction, -45), turn(direction, 45))
|
|
else
|
|
dirs = GLOB.alldirs.Copy()
|
|
|
|
src.streak(dirs)
|
|
|
|
/obj/effect/decal/cleanable/blood/gibs/robot/pipe_eject(var/direction)
|
|
var/list/dirs
|
|
if(direction)
|
|
dirs = list( direction, turn(direction, -45), turn(direction, 45))
|
|
else
|
|
dirs = GLOB.alldirs.Copy()
|
|
|
|
src.streak(dirs)
|