Files
Bubberstation/code/modules/research/anomaly/anomaly_refinery.dm
MrMelbert 5261efb67f Re-refactors batons / Refactors attack chain force modifiers (#90809)
## About The Pull Request

Melee attack chain now has a list passed along with it,
`attack_modifiers`, which you can stick force modifiers to change the
resulting attack

This is basically a soft implementation of damage packets until a more
definitive pr, but one that only applies to item attack chain, and not
unarmed attacks.

This change was done to facilitate a baton refactor - batons no longer
hack together their own attack chain, and are now integrated straight
into the real attack chain. This refactor itself was done because batons
don't send any attack signals, which has been annoying in the past (for
swing combat).

## Changelog

🆑 Melbert
refactor: Batons have been refactored again. Baton stuns now properly
count as an attack, when before it was a nothing. Report any oddities,
particularly in regards to harmbatonning vs normal batonning.
refactor: The method of adjusting item damage mid-attack has been
refactored - some affected items include the Nullblade and knives.
Report any strange happenings with damage numbers.
refactor: A few objects have been moved to the new interaction chain -
records consoles, mawed crucible, alien weeds and space vines, hedges,
restaurant portals, and some mobs - to name a few.
fix: Spears only deal bonus damage against secure lockers, not all
closet types (including crates)
/🆑
2025-05-19 13:32:12 +10:00

362 lines
13 KiB
Plaintext

#define MAX_RADIUS_REQUIRED 20 //maxcap
#define MIN_RADIUS_REQUIRED 4 //1, 2, 4
/// How long the compression test can last before the machine just gives up and ejects the items.
#define COMPRESSION_TEST_TIME (SSOBJ_DT SECONDS * 5)
/**
* # Explosive compressor machines
*
* The explosive compressor machine used in anomaly core production.
*
* Uses the standard ordnance/tank explosion scaling to compress raw anomaly cores into completed ones. The required explosion radius increases as more cores of that type are created.
*/
/obj/machinery/research/anomaly_refinery
name = "anomaly refinery"
desc = "An advanced machine capable of implosion-compressing raw anomaly cores into finished artifacts. Also equipped with state of the art bomb prediction software."
circuit = /obj/item/circuitboard/machine/anomaly_refinery
icon = 'icons/obj/machines/research.dmi'
base_icon_state = "explosive_compressor"
icon_state = "explosive_compressor"
density = TRUE
/// The raw core inserted in the machine.
var/obj/item/raw_anomaly_core/inserted_core
/// The TTV inserted in the machine.
var/obj/item/transfer_valve/inserted_bomb
/// The timer that lets us timeout the test.
var/datum/timedevent/timeout_timer
/// Whether we are currently active a bomb and core.
var/active = FALSE
/// The message produced by the explosive compressor at the end of the compression test.
var/test_status = null
/// Determines which tank will be the merge_gases target (destroyed upon testing).
var/obj/item/tank/tank_to_target
// These vars are used for the explosion simulation and doesn't affect the core detonation.
/// Combined result of the first two tanks. Exists only in our machine.
var/datum/gas_mixture/combined_gasmix
/// Here for the UI, tracks the amounts of reaction that has occured. 1 means valve opened but not reacted.
var/reaction_increment = 0
/obj/machinery/research/anomaly_refinery/Initialize(mapload)
. = ..()
RegisterSignal(src, COMSIG_ATOM_INTERNAL_EXPLOSION, PROC_REF(check_test))
/obj/machinery/research/anomaly_refinery/examine_more(mob/user)
. = ..()
if (obj_flags & EMAGGED)
. += span_notice("A small panel on [p_their()] side is dislaying a notice. Something about firmware?")
/obj/machinery/research/anomaly_refinery/assume_air(datum/gas_mixture/giver)
return null // Required to make the TTV not vent directly into the air.
/**
* Determines how much explosive power (last value, so light impact theoretical radius) is required to make a certain anomaly type.
*
* Returns null if the max amount has already been reached.
*
* Arguments:
* * anomaly_type - anomaly type define
*/
/obj/machinery/research/anomaly_refinery/proc/get_required_radius(anomaly_type)
if(!SSresearch.is_core_available(anomaly_type))
return //return null
var/already_made = SSresearch.created_anomaly_types[anomaly_type]
var/hard_limit = SSresearch.anomaly_hard_limit_by_type[anomaly_type]
// my crappy autoscale formula
// linear scaling.
var/radius_span = MAX_RADIUS_REQUIRED - MIN_RADIUS_REQUIRED
var/radius_increase_per_core = radius_span / hard_limit
var/radius = clamp(round(MIN_RADIUS_REQUIRED + radius_increase_per_core * already_made, 1), MIN_RADIUS_REQUIRED, MAX_RADIUS_REQUIRED)
return radius
/obj/machinery/research/anomaly_refinery/attackby(obj/item/tool, mob/living/user, list/modifiers, list/attack_modifiers)
if(active)
to_chat(user, span_warning("You can't insert [tool] into [src] while [p_theyre()] currently active."))
return
if(istype(tool, /obj/item/raw_anomaly_core))
if(inserted_core)
to_chat(user, span_warning("There is already a core in [src]."))
return
if(!user.transferItemToLoc(tool, src))
to_chat(user, span_warning("[tool] is stuck to your hand."))
return
var/obj/item/raw_anomaly_core/raw_core = tool
if(!get_required_radius(raw_core.anomaly_type))
say("Unfortunately, due to diminishing supplies of condensed anomalous matter, [raw_core] and any cores of its type are no longer of a sufficient quality level to be compressed into a working core.")
return
inserted_core = raw_core
to_chat(user, span_notice("You insert [raw_core] into [src]."))
return
if(istype(tool, /obj/item/transfer_valve))
if(inserted_bomb)
to_chat(user, span_warning("There is already a bomb in [src]."))
return
var/obj/item/transfer_valve/valve = tool
if(!valve.ready())
to_chat(user, span_warning("[valve] is incomplete."))
return
if(!user.transferItemToLoc(tool, src))
to_chat(user, span_warning("[tool] is stuck to your hand."))
return
inserted_bomb = tool
tank_to_target = inserted_bomb.tank_two
to_chat(user, span_notice("You insert [tool] into [src]"))
return
update_appearance()
return ..()
/obj/machinery/research/anomaly_refinery/wrench_act(mob/living/user, obj/item/tool)
. = ..()
default_unfasten_wrench(user, tool)
return ITEM_INTERACT_SUCCESS
/obj/machinery/research/anomaly_refinery/screwdriver_act(mob/living/user, obj/item/tool)
if(!default_deconstruction_screwdriver(user, "[base_icon_state]-off", "[base_icon_state]", tool))
return FALSE
update_appearance()
return TRUE
/obj/machinery/research/anomaly_refinery/crowbar_act(mob/living/user, obj/item/tool)
if(!default_deconstruction_crowbar(tool))
return FALSE
return TRUE
/obj/machinery/research/anomaly_refinery/emag_act(mob/user, obj/item/card/emag/emag_card)
. = ..()
if (obj_flags & EMAGGED)
balloon_alert(user, "already hacked!")
return
obj_flags |= EMAGGED
playsound(src, 'sound/machines/buzz/buzz-sigh.ogg', 50, vary = FALSE)
say("ERROR: Unauthorized firmware access.")
return TRUE
/**
* Starts a compression test.
*/
/obj/machinery/research/anomaly_refinery/proc/start_test()
if (active)
say("ERROR: Already running a compression test.")
return
if(!istype(inserted_core) || !istype(inserted_bomb))
end_test("ERROR: Missing equpment. Items ejected.")
return
if(!inserted_bomb?.tank_one || !inserted_bomb?.tank_two || !(tank_to_target == inserted_bomb?.tank_one || tank_to_target == inserted_bomb?.tank_two))
end_test("ERROR: Transfer valve malfunctioning. Items ejected.")
return
say("Beginning compression test. Opening transfer valve.")
active = TRUE
test_status = null
if (obj_flags & EMAGGED)
say("ERROR: An firmware issue was detected while starting a process. Running autopatcher.")
playsound(src, 'sound/machines/ding.ogg', 50, vary = TRUE)
addtimer(CALLBACK(src, PROC_REF(error_test)), 2 SECONDS, TIMER_STOPPABLE | TIMER_UNIQUE | TIMER_NO_HASH_WAIT) // Synced with the sound.
return
inserted_bomb.toggle_valve(tank_to_target)
timeout_timer = addtimer(CALLBACK(src, PROC_REF(timeout_test)), COMPRESSION_TEST_TIME, TIMER_STOPPABLE | TIMER_UNIQUE | TIMER_NO_HASH_WAIT)
return
/**
* Ejects a live TTV.
* Triggered by attempting to operate an emagged anomaly refinery.
*/
/obj/machinery/research/anomaly_refinery/proc/error_test()
message_admins("[src] was emagged and ejected a TTV.")
investigate_log("was emagged and ejected a TTV.", INVESTIGATE_RESEARCH)
obj_flags &= ~EMAGGED
say("Issue resolved. Have a nice day!")
inserted_bomb.toggle_valve(tank_to_target)
eject_bomb(force = TRUE)
timeout_timer = addtimer(CALLBACK(src, PROC_REF(timeout_test)), COMPRESSION_TEST_TIME, TIMER_STOPPABLE | TIMER_UNIQUE | TIMER_NO_HASH_WAIT) // Actually start the test so they can't just put the bomb back in.
/**
* Ends a compression test.
*
* Arguments:
* - message: A message for the compressor to say when the test ends.
*/
/obj/machinery/research/anomaly_refinery/proc/end_test(message)
active = FALSE
tank_to_target = null
test_status = null
if(inserted_core)
eject_core()
if(inserted_bomb)
eject_bomb()
if(timeout_timer)
QDEL_NULL(timeout_timer)
if(message)
say(message)
return
/**
* Checks whether an internal explosion was sufficient to compress the core.
*/
/obj/machinery/research/anomaly_refinery/proc/check_test(atom/source, list/arguments)
SIGNAL_HANDLER
if(!inserted_core)
test_status = "ERROR: No core present during detonation."
return COMSIG_CANCEL_EXPLOSION
var/heavy = arguments[EXARG_KEY_DEV_RANGE]
var/medium = arguments[EXARG_KEY_HEAVY_RANGE]
var/light = arguments[EXARG_KEY_LIGHT_RANGE]
var/explosion_range = max(heavy, medium, light, 0)
var/required_range = get_required_radius(inserted_core.anomaly_type)
var/turf/location = get_turf(src)
var/cap_multiplier = SSmapping.level_trait(location.z, ZTRAIT_BOMBCAP_MULTIPLIER)
if(isnull(cap_multiplier))
cap_multiplier = 1
var/capped_heavy = min(GLOB.MAX_EX_DEVESTATION_RANGE * cap_multiplier, heavy)
var/capped_medium = min(GLOB.MAX_EX_HEAVY_RANGE * cap_multiplier, medium)
SSexplosions.shake_the_room(location, explosion_range, (capped_heavy * 15) + (capped_medium * 20), capped_heavy, capped_medium)
if(explosion_range < required_range)
test_status = "Resultant detonation failed to produce enough implosive power to compress [inserted_core]. Items ejected."
return COMSIG_CANCEL_EXPLOSION
if(test_status)
return COMSIG_CANCEL_EXPLOSION
inserted_core = inserted_core.create_core(src, TRUE, TRUE)
test_status = "Success. Resultant detonation has theoretical range of [explosion_range]. Required radius was [required_range]. Core production complete."
return COMSIG_CANCEL_EXPLOSION
/**
* Handles timing out the test after a while.
*/
/obj/machinery/research/anomaly_refinery/proc/timeout_test()
timeout_timer = null
if(!test_status)
test_status = "Transfer valve resulted in negligible explosive power. Items ejected."
end_test(test_status)
/// This is not the real valve opening process. This is the simulated one used for displaying reactions.
/obj/machinery/research/anomaly_refinery/proc/simulate_valve()
if(!inserted_bomb?.tank_one || !inserted_bomb?.tank_two)
eject_bomb()
return FALSE
if(reaction_increment == 0)
var/datum/gas_mixture/first_gasmix = inserted_bomb.tank_one.return_air()
var/datum/gas_mixture/second_gasmix = inserted_bomb.tank_two.return_air()
combined_gasmix = new(70)
combined_gasmix.volume = first_gasmix.volume + second_gasmix.volume
combined_gasmix.merge(first_gasmix.copy())
combined_gasmix.merge(second_gasmix.copy())
else
combined_gasmix.react()
reaction_increment += 1
/// We dont allow incomplete valves to go in but do code in checks for incomplete valves. Just in case.
/obj/machinery/research/anomaly_refinery/proc/eject_bomb(mob/user, force = FALSE)
if(!inserted_bomb || (active && !force))
return
if(user)
user.put_in_hands(inserted_bomb)
to_chat(user, span_notice("You remove [inserted_bomb] from [src]."))
else
inserted_bomb.forceMove(drop_location())
combined_gasmix = null
reaction_increment = 0
/obj/machinery/research/anomaly_refinery/proc/eject_core(mob/user)
if(!inserted_core || active)
return
if(user)
user.put_in_hands(inserted_core)
to_chat(user, span_notice("You remove [inserted_core] from [src]."))
else
inserted_core.forceMove(drop_location())
/// We rely on exited to clear references.
/obj/machinery/research/anomaly_refinery/Exited(atom/movable/gone, direction)
if(gone == inserted_bomb)
inserted_bomb = null
tank_to_target = null
if(gone == inserted_core)
inserted_core = null
. = ..()
/obj/machinery/research/anomaly_refinery/proc/swap_target()
if(!inserted_bomb?.tank_one || !inserted_bomb?.tank_two)
eject_bomb()
return FALSE
tank_to_target = (tank_to_target == inserted_bomb.tank_one) ? inserted_bomb.tank_two : inserted_bomb.tank_one
/obj/machinery/research/anomaly_refinery/on_deconstruction(disassembled)
eject_bomb()
eject_core()
return ..()
/obj/machinery/research/anomaly_refinery/Destroy()
inserted_bomb = null
inserted_core = null
combined_gasmix = null
return ..()
/obj/machinery/research/anomaly_refinery/ui_interact(mob/user, datum/tgui/ui)
. = ..()
ui = SStgui.try_update_ui(user, src, ui)
if(!ui)
ui = new(user, src, "AnomalyRefinery")
ui.open()
/obj/machinery/research/anomaly_refinery/ui_act(action, list/params, datum/tgui/ui, datum/ui_state/state)
. = ..()
if (.)
return
switch(action)
if("react")
simulate_valve()
if("eject_bomb")
eject_bomb(usr)
if("eject_core")
eject_core(usr)
if("start_implosion")
start_test()
if("swap")
swap_target()
/obj/machinery/research/anomaly_refinery/ui_data(mob/user)
var/list/data = list()
var/list/parsed_gasmixes = list()
var/obj/item/tank/other_tank
if(inserted_bomb?.tank_one && inserted_bomb?.tank_two)
other_tank = inserted_bomb.tank_one == tank_to_target ? inserted_bomb.tank_two : inserted_bomb.tank_one
parsed_gasmixes += list(gas_mixture_parser(tank_to_target?.return_air(), tank_to_target?.name))
parsed_gasmixes += list(gas_mixture_parser(other_tank?.return_air(), other_tank?.name))
parsed_gasmixes += list(gas_mixture_parser(combined_gasmix, "Combined Gasmix"))
data["gasList"] = parsed_gasmixes
data["valvePresent"] = inserted_bomb ? TRUE : FALSE
data["valveReady"] = (inserted_bomb?.tank_one && inserted_bomb?.tank_two) ? TRUE : FALSE
data["reactionIncrement"] = reaction_increment
data["core"] = inserted_core ? inserted_core.name : FALSE
data["requiredRadius"] = inserted_core ? get_required_radius(inserted_core.anomaly_type) : null
data["active"] = active
return data
#undef MAX_RADIUS_REQUIRED
#undef MIN_RADIUS_REQUIRED
#undef COMPRESSION_TEST_TIME