Files
Bubberstation/code/modules/recycling/disposal/bin.dm
san7890 bae1aef3b4 Refactors Regal Rats into Basic Mobs (more titles edition) (#77681)
## About The Pull Request

I literally can't focus on anything nowadays, so I just did this to
break a never-ending chain of distress. Anyways, regal rats! These
fellas are mostly player controlled, but did have _some_ AI capabilities
(mainly tied to their actions), so that was incorporated too. Everything
should work as-expected (as well as look a shitload cleaner).

Instead of doing weird and awful conditional signals being sent out, I
made the `COMSIG_REGAL_RAT_INTERACT` (not the actual name) have a return
value so we can always rely on that working whenever we have that signal
registered on something we attack. I also cleaned up pretty much every
proc related to regal rats, gave them AIs to reflect their kingly nature
(and action capabilities (as well as move the action to
`mob_cooldown`)).

Since I thought they needed it, Regal Rats now get a special moniker!
This is stuff like "the Big Cheese" and what-not, like actual regents in
history. That's nice.
## Why It's Good For The Game

Two more off the list. Much better code to read. Way smarter rats with
spawning their army as part of a retaliatory assault (war). More sovl
with better regal rat names. The list goes on.
## Changelog
🆑
refactor: Regal Rats have been refactored into basic mobs. They should
be a bit smarter and retain their docility (until attacked, in which
case you should prepare to get rekt by summoned rats), and properly flee
when they can instead of just sit there as you beat them to death. The
framework for them interacting with stuff (i.e. opening doors while
slobbering on food) is a bit more unified too, now. They also have
cooler names too!
/🆑

FYI: Beyond a few code touchups, I haven't touched the actions at all. I
do not believe myself to be enthusiastic about fixing anything involving
the actions code as of this moment so that this PR is more overbloated
unless it's unbelievably stupid or easy to fix.
2023-08-18 23:09:07 +01:00

587 lines
18 KiB
Plaintext

// Disposal bin and Delivery chute.
#define SEND_PRESSURE (0.05*ONE_ATMOSPHERE)
/obj/machinery/disposal
icon = 'icons/obj/pipes_n_cables/disposal.dmi'
density = TRUE
armor_type = /datum/armor/machinery_disposal
max_integrity = 200
resistance_flags = FIRE_PROOF
interaction_flags_machine = INTERACT_MACHINE_OPEN | INTERACT_MACHINE_WIRES_IF_OPEN | INTERACT_MACHINE_ALLOW_SILICON | INTERACT_MACHINE_OPEN_SILICON
obj_flags = CAN_BE_HIT
/// The internal air reservoir of the disposal
var/datum/gas_mixture/air_contents
/// Is the disposal at full pressure
var/full_pressure = FALSE
/// Is the pressure charging
var/pressure_charging = 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
var/flushing = FALSE
/// 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
/// The last time a sound was played
var/last_sound = 0
/// The stored disposal construction pipe
var/obj/structure/disposalconstruct/stored
/datum/armor/machinery_disposal
melee = 25
bullet = 10
laser = 10
energy = 100
fire = 90
acid = 30
// create a new disposal
// find the attached trunk (if present) and init gas resvr.
/obj/machinery/disposal/Initialize(mapload, obj/structure/disposalconstruct/make_from)
. = ..()
if(make_from)
setDir(make_from.dir)
make_from.moveToNullspace()
stored = make_from
pressure_charging = FALSE // newly built disposal bins start with pump off
else
stored = new /obj/structure/disposalconstruct(null, null , SOUTH , FALSE , src)
trunk_check()
air_contents = new /datum/gas_mixture()
//gas.volume = 1.05 * CELLSTANDARD
update_appearance()
RegisterSignal(src, COMSIG_RAT_INTERACT, PROC_REF(on_rat_rummage))
RegisterSignal(src, COMSIG_STORAGE_DUMP_CONTENT, PROC_REF(on_storage_dump))
var/static/list/loc_connections = list(
COMSIG_CARBON_DISARM_COLLIDE = PROC_REF(trash_carbon),
COMSIG_TURF_RECEIVE_SWEEPED_ITEMS = PROC_REF(ready_for_trash),
)
AddElement(/datum/element/connect_loc, loc_connections)
return INITIALIZE_HINT_LATELOAD //we need turfs to have air
/// Checks if there a connecting trunk diposal pipe under the disposal
/obj/machinery/disposal/proc/trunk_check()
var/obj/structure/disposalpipe/trunk/found_trunk = locate() in loc
if(!found_trunk)
pressure_charging = FALSE
flush = FALSE
else
if(initial(pressure_charging))
pressure_charging = TRUE
flush = initial(flush)
found_trunk.set_linked(src) // link the pipe trunk to self
trunk = found_trunk
/obj/machinery/disposal/Destroy()
eject()
if(trunk)
trunk.linked = null
trunk = null
return ..()
/obj/machinery/disposal/Exited(atom/movable/gone, direction)
. = ..()
if(gone == stored && !QDELETED(src))
stored = null
deconstruct(FALSE)
/obj/machinery/disposal/singularity_pull(S, current_size)
..()
if(current_size >= STAGE_FIVE)
deconstruct()
/obj/machinery/disposal/LateInitialize()
//this will get a copy of the air turf and take a SEND PRESSURE amount of air from it
var/atom/L = loc
var/datum/gas_mixture/env = new
env.copy_from(L.return_air())
var/datum/gas_mixture/removed = env.remove(SEND_PRESSURE + 1)
air_contents.merge(removed)
trunk_check()
/obj/machinery/disposal/attackby(obj/item/I, mob/living/user, params)
add_fingerprint(user)
if(!pressure_charging && !full_pressure && !flush)
if(I.tool_behaviour == TOOL_SCREWDRIVER)
toggle_panel_open()
I.play_tool_sound(src)
to_chat(user, span_notice("You [panel_open ? "remove":"attach"] the screws around the power connection."))
return
else if(I.tool_behaviour == TOOL_WELDER && panel_open)
if(!I.tool_start_check(user, amount=1))
return
to_chat(user, span_notice("You start slicing the floorweld off \the [src]..."))
if(I.use_tool(src, user, 20, volume=SMALL_MATERIAL_AMOUNT) && panel_open)
to_chat(user, span_notice("You slice the floorweld off \the [src]."))
deconstruct()
return
if(!user.combat_mode)
if((I.item_flags & ABSTRACT) || !user.temporarilyRemoveItemFromInventory(I))
return
place_item_in_disposal(I, user)
update_appearance()
return 1 //no afterattack
else
return ..()
/// The regal rat spawns ratty treasures from the disposal
/obj/machinery/disposal/proc/rat_rummage(mob/living/basic/regal_rat/king)
king.visible_message(span_warning("[king] starts rummaging through [src]."),span_notice("You rummage through [src]..."))
if (!do_after(king, 2 SECONDS, src, interaction_key = "regalrat"))
return
var/loot = rand(1,100)
switch(loot)
if(1 to 5)
to_chat(king, span_notice("You find some leftover coins. More for the royal treasury!"))
var/pickedcoin = pick(GLOB.ratking_coins)
for(var/i = 1 to rand(1,3))
new pickedcoin(get_turf(king))
if(6 to 33)
king.say(pick("Treasure!","Our precious!","Cheese!"), ignore_spam = TRUE, forced = "regal rat rummaging")
to_chat(king, span_notice("Score! You find some cheese!"))
new /obj/item/food/cheese/wedge(get_turf(king))
else
var/pickedtrash = pick(GLOB.ratking_trash)
to_chat(king, span_notice("You just find more garbage and dirt. Lovely, but beneath you now."))
new pickedtrash(get_turf(king))
/// Moves an item into the diposal bin
/obj/machinery/disposal/proc/place_item_in_disposal(obj/item/I, mob/user)
I.forceMove(src)
user.visible_message(span_notice("[user.name] places \the [I] into \the [src]."), span_notice("You place \the [I] into \the [src]."))
/// Mouse drop another mob or self
/obj/machinery/disposal/MouseDrop_T(mob/living/target, mob/living/user)
if(istype(target))
stuff_mob_in(target, user)
/// Handles stuffing a grabbed mob into the disposal
/obj/machinery/disposal/proc/stuff_mob_in(mob/living/target, mob/living/user)
var/ventcrawler = HAS_TRAIT(user, TRAIT_VENTCRAWLER_ALWAYS) || HAS_TRAIT(user, TRAIT_VENTCRAWLER_NUDE)
if(!iscarbon(user) && !ventcrawler) //only carbon and ventcrawlers can climb into disposal by themselves.
if (iscyborg(user))
var/mob/living/silicon/robot/borg = user
if (!borg.model || !borg.model.canDispose)
return
else
return
if(!isturf(user.loc)) //No magically doing it from inside closets
return
if(target.buckled || target.has_buckled_mobs())
return
if(target.mob_size > MOB_SIZE_HUMAN)
to_chat(user, span_warning("[target] doesn't fit inside [src]!"))
return
add_fingerprint(user)
if(user == target)
user.visible_message(span_warning("[user] starts climbing into [src]."), span_notice("You start climbing into [src]..."))
else
target.visible_message(span_danger("[user] starts putting [target] into [src]."), span_userdanger("[user] starts putting you into [src]!"))
if(do_after(user, 2 SECONDS, target))
if (!loc)
return
target.forceMove(src)
if(user == target)
user.visible_message(span_warning("[user] climbs into [src]."), span_notice("You climb into [src]."))
. = TRUE
else
target.visible_message(span_danger("[user] places [target] in [src]."), span_userdanger("[user] places you in [src]."))
log_combat(user, target, "stuffed", addition="into [src]")
target.LAssailant = WEAKREF(user)
. = TRUE
update_appearance()
/obj/machinery/disposal/relaymove(mob/living/user, direction)
attempt_escape(user)
// resist to escape the bin
/obj/machinery/disposal/container_resist_act(mob/living/user)
attempt_escape(user)
/// Checks if a mob can climb out of the disposal, and lets them if they can
/obj/machinery/disposal/proc/attempt_escape(mob/user)
if(flushing)
return
go_out(user)
/// Makes a mob in the disposal climb out
/obj/machinery/disposal/proc/go_out(mob/user)
user.forceMove(loc)
update_appearance()
// clumsy monkeys and xenos can only pull the flush lever
/obj/machinery/disposal/attack_paw(mob/user, list/modifiers)
if(ISADVANCEDTOOLUSER(user))
return ..()
if(machine_stat & BROKEN)
return
flush = !flush
update_appearance()
/// Ejects the contents of the disposal unit
/obj/machinery/disposal/proc/eject()
pipe_eject(src, FALSE, FALSE)
update_appearance()
/// Plays the animations and sounds for flushing, and initializes the diposal holder object
/obj/machinery/disposal/proc/flush()
flushing = TRUE
flushAnimation()
sleep(1 SECONDS)
if(last_sound < world.time + 1)
playsound(src, 'sound/machines/disposalflush.ogg', 50, FALSE, FALSE)
last_sound = world.time
sleep(0.5 SECONDS)
if(QDELETED(src))
return
var/obj/structure/disposalholder/H = new(src)
newHolderDestination(H)
H.init(src)
air_contents = new()
H.start(src)
flushing = FALSE
flush = FALSE
/// Sets the default destinationTag of the disposal holder object
/obj/machinery/disposal/proc/newHolderDestination(obj/structure/disposalholder/H)
H.destinationTag = SORT_TYPE_DISPOSALS
for(var/obj/item/delivery/O in src)
H.tomail = TRUE
return
/// Plays the flushing animation of the disposal
/obj/machinery/disposal/proc/flushAnimation()
flick("[icon_state]-flush", src)
/// Called when holder is expelled from a disposal
/obj/machinery/disposal/proc/expel(obj/structure/disposalholder/H)
H.active = FALSE
playsound(src, 'sound/machines/hiss.ogg', 50, FALSE, FALSE)
pipe_eject(H)
H.vent_gas(loc)
qdel(H)
/obj/machinery/disposal/deconstruct(disassembled = TRUE)
var/turf/T = loc
if(!(flags_1 & NODECONSTRUCT_1))
if(stored)
var/obj/structure/disposalconstruct/construct = stored
stored = null
construct.forceMove(T)
transfer_fingerprints_to(construct)
construct.set_anchored(FALSE)
construct.set_density(TRUE)
construct.update_appearance()
for(var/atom/movable/AM in src) //out, out, darned crowbar!
AM.forceMove(T)
..()
///How disposal handles getting a storage dump from a storage object
/obj/machinery/disposal/proc/on_storage_dump(datum/source, obj/item/storage_source, mob/user)
SIGNAL_HANDLER
. = STORAGE_DUMP_HANDLED
to_chat(user, span_notice("You dump out [storage_source] into [src]."))
for(var/obj/item/to_dump in storage_source)
if(to_dump.loc != storage_source)
continue
if(user.active_storage != storage_source && to_dump.on_found(user))
return
if(!storage_source.atom_storage.attempt_remove(to_dump, src, silent = TRUE))
continue
to_dump.pixel_x = to_dump.base_pixel_x + rand(-5, 5)
to_dump.pixel_y = to_dump.base_pixel_y + rand(-5, 5)
// 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
/obj/machinery/disposal/bin
name = "disposal unit"
desc = "A pneumatic waste disposal unit."
icon_state = "disposal"
// attack by item places it in to disposal
/obj/machinery/disposal/bin/attackby(obj/item/I, mob/user, params)
if(istype(I, /obj/item/storage/bag/trash)) //Not doing component overrides because this is a specific type.
var/obj/item/storage/bag/trash/bag = I
to_chat(user, span_warning("You empty the bag."))
bag.atom_storage.remove_all(src)
update_appearance()
else
return ..()
// handle machine interaction
/obj/machinery/disposal/bin/ui_state(mob/user)
return GLOB.notcontained_state
/obj/machinery/disposal/bin/ui_interact(mob/user, datum/tgui/ui)
if(machine_stat & BROKEN)
return
ui = SStgui.try_update_ui(user, src, ui)
if(!ui)
ui = new(user, src, "DisposalUnit", name)
ui.open()
/obj/machinery/disposal/bin/ui_data(mob/user)
var/list/data = list()
data["flush"] = flush
data["full_pressure"] = full_pressure
data["pressure_charging"] = pressure_charging
data["panel_open"] = panel_open
data["per"] = CLAMP01(air_contents.return_pressure() / (SEND_PRESSURE))
data["isai"] = isAI(user)
return data
/obj/machinery/disposal/bin/ui_act(action, params)
. = ..()
if(.)
return
switch(action)
if("handle-0")
flush = FALSE
update_appearance()
. = TRUE
if("handle-1")
if(!panel_open)
flush = TRUE
update_appearance()
. = TRUE
if("pump-0")
if(pressure_charging)
pressure_charging = FALSE
update_appearance()
. = TRUE
if("pump-1")
if(!pressure_charging)
pressure_charging = TRUE
update_appearance()
. = TRUE
if("eject")
eject()
. = TRUE
/obj/machinery/disposal/bin/hitby(atom/movable/AM, skipcatch, hitpush, blocked, datum/thrownthing/throwingdatum)
if(isitem(AM) && AM.CanEnterDisposals())
if((throwingdatum.thrower && HAS_TRAIT(throwingdatum.thrower, TRAIT_THROWINGARM)) || prob(75))
AM.forceMove(src)
visible_message(span_notice("[AM] lands in [src]."))
update_appearance()
else
visible_message(span_notice("[AM] bounces off of [src]'s rim!"))
return ..()
else
return ..()
/obj/machinery/disposal/bin/flush()
..()
full_pressure = FALSE
pressure_charging = TRUE
update_appearance()
/obj/machinery/disposal/bin/update_appearance(updates)
. = ..()
if((machine_stat & (BROKEN|NOPOWER)) || panel_open)
luminosity = 0
return
luminosity = 1
/obj/machinery/disposal/bin/update_overlays()
. = ..()
if(machine_stat & BROKEN)
return
//flush handle
if(flush)
. += "dispover-handle"
//only handle is shown if no power
if(machine_stat & NOPOWER || panel_open)
return
//check for items in disposal - occupied light
if(contents.len > 0)
. += "dispover-full"
. += emissive_appearance(icon, "dispover-full", src, alpha = src.alpha)
//charging and ready light
if(pressure_charging)
. += "dispover-charge"
. += emissive_appearance(icon, "dispover-charge-glow", src, alpha = src.alpha)
else if(full_pressure)
. += "dispover-ready"
. += emissive_appearance(icon, "dispover-ready-glow", src, alpha = src.alpha)
/// Initiates flushing
/obj/machinery/disposal/bin/proc/do_flush()
set waitfor = FALSE
flush()
//timed process
//charge the gas reservoir and perform flush if ready
/obj/machinery/disposal/bin/process(seconds_per_tick)
if(machine_stat & BROKEN) //nothing can happen if broken
return
flush_count++
if(flush_count >= flush_every_ticks)
if(contents.len)
if(full_pressure)
do_flush()
flush_count = 0
if(flush && air_contents.return_pressure() >= SEND_PRESSURE) // flush can happen even without power
do_flush()
if(machine_stat & NOPOWER) // won't charge if no power
return
use_power(idle_power_usage) // base power usage
if(!pressure_charging) // if off or ready, no need to charge
return
// otherwise charge
use_power(idle_power_usage) // charging power usage
var/atom/L = loc //recharging from loc turf
var/datum/gas_mixture/env = L.return_air()
if(!env.temperature)
return
var/pressure_delta = (SEND_PRESSURE*1.01) - air_contents.return_pressure()
var/transfer_moles = 0.05 * seconds_per_tick * (pressure_delta*air_contents.volume)/(env.temperature * R_IDEAL_GAS_EQUATION)
//Actually transfer the gas
var/datum/gas_mixture/removed = env.remove(transfer_moles)
air_contents.merge(removed)
air_update_turf(FALSE, FALSE)
//if full enough, switch to ready mode
if(air_contents.return_pressure() >= SEND_PRESSURE)
full_pressure = TRUE
pressure_charging = FALSE
update_appearance()
return
/obj/machinery/disposal/bin/get_remote_view_fullscreens(mob/user)
if(user.stat == DEAD || !(user.sight & (SEEOBJS|SEEMOBS)))
user.overlay_fullscreen("remote_view", /atom/movable/screen/fullscreen/impaired, 2)
//Delivery Chute
/obj/machinery/disposal/delivery_chute
name = "delivery chute"
desc = "A chute for big and small packages alike!"
density = TRUE
icon_state = "intake"
pressure_charging = FALSE // the chute doesn't need charging and always works
/obj/machinery/disposal/delivery_chute/place_item_in_disposal(obj/item/I, mob/user)
if(I.CanEnterDisposals())
..()
flush()
/obj/machinery/disposal/delivery_chute/Bumped(atom/movable/AM) //Go straight into the chute
if(QDELETED(AM) || !AM.CanEnterDisposals())
return
switch(dir)
if(NORTH)
if(AM.loc.y != loc.y+1)
return
if(EAST)
if(AM.loc.x != loc.x+1)
return
if(SOUTH)
if(AM.loc.y != loc.y-1)
return
if(WEST)
if(AM.loc.x != loc.x-1)
return
if(isobj(AM))
var/obj/O = AM
O.forceMove(src)
else if(ismob(AM))
var/mob/M = AM
if(prob(2)) // to prevent mobs being stuck in infinite loops
to_chat(M, span_warning("You hit the edge of the chute."))
return
M.forceMove(src)
flush()
/// Called to check if an atom can fit inside the diposal
/atom/movable/proc/CanEnterDisposals()
return TRUE
/obj/projectile/CanEnterDisposals()
return
/obj/effect/CanEnterDisposals()
return
/obj/vehicle/sealed/mecha/CanEnterDisposals()
return
/// Handles the signal for the rat king looking inside the disposal
/obj/machinery/disposal/proc/on_rat_rummage(datum/source, mob/living/basic/regal_rat/king)
SIGNAL_HANDLER
if(king.combat_mode)
return
INVOKE_ASYNC(src, TYPE_PROC_REF(/obj/machinery/disposal/, rat_rummage), king)
return COMPONENT_RAT_INTERACTED
/// Handles a carbon mob getting shoved into the disposal bin
/obj/machinery/disposal/proc/trash_carbon(datum/source, mob/living/carbon/shover, mob/living/carbon/target, shove_blocked)
SIGNAL_HANDLER
if(!shove_blocked)
return
target.Knockdown(SHOVE_KNOCKDOWN_SOLID)
target.forceMove(src)
target.visible_message(span_danger("[shover.name] shoves [target.name] into \the [src]!"),
span_userdanger("You're shoved into \the [src] by [target.name]!"), span_hear("You hear aggressive shuffling followed by a loud thud!"), COMBAT_MESSAGE_RANGE, src)
to_chat(src, span_danger("You shove [target.name] into \the [src]!"))
log_combat(shover, target, "shoved", "into [src] (disposal bin)")
return COMSIG_CARBON_SHOVE_HANDLED
///Called when a push broom is trying to sweep items onto the turf this object is standing on. Garbage will be moved inside.
/obj/machinery/disposal/proc/ready_for_trash(datum/source, obj/item/pushbroom/broom, mob/user, list/items_to_sweep)
SIGNAL_HANDLER
if(!items_to_sweep)
return
for (var/obj/item/garbage in items_to_sweep)
garbage.forceMove(src)
items_to_sweep.Cut()
update_appearance()
to_chat(user, span_notice("You sweep the pile of garbage into [src]."))
playsound(broom.loc, 'sound/weapons/thudswoosh.ogg', 30, TRUE, -1)
#undef SEND_PRESSURE