Files
Bubberstation/code/modules/research/experimentor.dm
Pickle-Coding c1f11f26ce Converts arbitrary energy units to the joule. Fixes conservation of energy issues relating to charging cells. (#81579)
## About The Pull Request
Removes all arbitrary energy and power units in the codebase. Everything
is replaced with the joule and watt, with 1 = 1 joule, or 1 watt if you
are going to multiply by time. This is a visible change, where all
arbitrary energy units you see in the game will get proper prefixed
units of energy.

With power cells being converted to the joule, charging one joule of a
power cell will require one joule of energy.

The grid will now store energy, instead of power. When an energy usage
is described as using the watt, a power to energy conversion based on
the relevant subsystem's timing (usually multiplying by seconds_per_tick
or applying power_to_energy()) is needed before adding or removing from
the grid. Power usages that are described as the watt is really anything
you would scale by time before applying the load. If it's described as a
joule, no time conversion is needed. Players will still read the grid as
power, having no visible change.

Machines that dynamically use power with the use_power() proc will
directly drain from the grid (and apc cell if there isn't enough)
instead of just tallying it up on the dynamic power usages for the area.
This should be more robust at conserving energy as the surplus is
updated on the go, preventing charging cells from nothing.

APCs no longer consume power for the dynamic power usage channels. APCs
will consume power for static power usages. Because static power usages
are added up without checking surplus, static power consumption will be
applied before any machine processes. This will give a more truthful
surplus for dynamic power consumers.

APCs will display how much power it is using for charging the cell. APC
cell charging applies power in its own channel, which gets added up to
the total. This will prevent invisible power usage you see when looking
at the power monitoring console.

After testing in MetaStation, I found roundstart power consumption to be
around 406kW after all APCs get fully charged. During the roundstart APC
charge rush, the power consumption can get as high as over 2MW (up to
25kW per roundstart APC charging) as long as there's that much
available.

Because of the absurd potential power consumption of charging APCs near
roundstart, I have changed how APCs decide to charge. APCs will now
charge only after all other machines have processed in the machines
processing subsystem. This will make sure APC charging won't disrupt
machines taking from the grid, and should stop APCs getting their power
drained due to others demanding too much power while charging. I have
removed the delays for APC charging too, so they start charging
immediately whenever there's excess power. It also stops them turning
red when a small amount of cell gets drained (airlocks opening and shit
during APC charge rush), as they immediately become fully charged
(unless too much energy got drained somehow) before changing icon.

Engineering SMES now start at 100% charge instead of 75%. I noticed
cells were draining earlier than usual after these changes, so I am
making them start maxed to try and combat that.

These changes will fix all conservation of energy issues relating to
charging powercells.
## Why It's Good For The Game
Closes #73438
Closes #75789
Closes #80634
Closes #82031

Makes it much easier to interface with the power system in the codebase.
It's more intuitive. Removes a bunch of conservation of energy issues,
making energy and power much more meaningful. It will help the
simulation remain immersive as players won't encounter energy
duplication so easily. Arbitrary energy units getting replaced with the
joule will also tell people more meaningful information when reading it.
APC charging will feel more snappy.
## Changelog
🆑
fix: Fixes conservation of energy issues relating to charging
powercells.
qol: APCs will display how much power they are using to charge their
cell. This is accounted for in the power monitoring console.
qol: All arbitrary power cell energy units you see are replaced with
prefixed joules.
balance: As a consequence of the conservation of energy issues getting
fixed, the power consumption for charging cells is now very significant.
balance: APCs only use surplus power from the grid after every machine
processes when charging, preventing APCs from causing others to
discharge while charging.
balance: Engineering SMES start at max charge to combat the increased
energy loss due to conservation of energy fixes.
/🆑

---------

Co-authored-by: SyncIt21 <110812394+SyncIt21@users.noreply.github.com>
Co-authored-by: Ghom <42542238+Ghommie@users.noreply.github.com>
2024-03-23 16:58:56 +01:00

696 lines
30 KiB
Plaintext

//this is designed to replace the destructive analyzer
//NEEDS MAJOR CODE CLEANUP
#define SCANTYPE_POKE 1
#define SCANTYPE_IRRADIATE 2
#define SCANTYPE_GAS 3
#define SCANTYPE_HEAT 4
#define SCANTYPE_COLD 5
#define SCANTYPE_OBLITERATE 6
#define SCANTYPE_DISCOVER 7
#define EFFECT_PROB_VERYLOW 20
#define EFFECT_PROB_LOW 35
#define EFFECT_PROB_MEDIUM 50
#define EFFECT_PROB_HIGH 75
#define EFFECT_PROB_VERYHIGH 95
#define FAIL 8
/obj/machinery/rnd/experimentor
name = "\improper E.X.P.E.R.I-MENTOR"
desc = "A \"replacement\" for the destructive analyzer with a slight tendency to catastrophically fail."
icon = 'icons/obj/machines/experimentator.dmi'
icon_state = "h_lathe"
base_icon_state = "h_lathe"
density = TRUE
use_power = IDLE_POWER_USE
circuit = /obj/item/circuitboard/machine/experimentor
interaction_flags_machine = INTERACT_MACHINE_WIRES_IF_OPEN|INTERACT_MACHINE_ALLOW_SILICON|INTERACT_MACHINE_OPEN_SILICON
var/recentlyExperimented = 0
/// Weakref to the first ian we can find at init
var/datum/weakref/tracked_ian_ref
/// Weakref to the first runtime we can find at init
var/datum/weakref/tracked_runtime_ref
///Determines the probability of a malfunction.
var/malfunction_probability_coeff = 0
///Keeps track of how many times we've had a critical reaction
var/malfunction_probability_coeff_modifier = 0
var/resetTime = 15
var/cloneMode = FALSE
var/list/item_reactions
var/static/list/valid_items //valid items for special reactions like transforming
var/list/critical_items_typecache //items that can cause critical reactions
/obj/machinery/rnd/experimentor/proc/valid_items()
RETURN_TYPE(/list)
if (isnull(valid_items))
generate_valid_items_and_item_reactions()
return valid_items
/obj/machinery/rnd/experimentor/proc/item_reactions()
RETURN_TYPE(/list)
if (isnull(item_reactions))
generate_valid_items_and_item_reactions()
return item_reactions
/obj/machinery/rnd/experimentor/proc/generate_valid_items_and_item_reactions()
var/static/list/banned_typecache = typecacheof(list(
/obj/item/stock_parts/cell/infinite,
/obj/item/grenade/chem_grenade/tuberculosis
))
item_reactions = list()
valid_items = list()
for(var/I in typesof(/obj/item))
if(ispath(I, /obj/item/relic))
item_reactions["[I]"] = SCANTYPE_DISCOVER
else
item_reactions["[I]"] = pick(SCANTYPE_POKE,SCANTYPE_IRRADIATE,SCANTYPE_GAS,SCANTYPE_HEAT,SCANTYPE_COLD,SCANTYPE_OBLITERATE)
if(is_type_in_typecache(I, banned_typecache))
continue
if(ispath(I, /obj/item/stock_parts) || ispath(I, /obj/item/grenade/chem_grenade) || ispath(I, /obj/item/knife))
var/obj/item/tempCheck = I
if(initial(tempCheck.icon_state) != null) //check it's an actual usable item, in a hacky way
valid_items["[I]"] += 15
if(ispath(I, /obj/item/food))
var/obj/item/tempCheck = I
if(initial(tempCheck.icon_state) != null) //check it's an actual usable item, in a hacky way
valid_items["[I]"] += rand(1,4)
/obj/machinery/rnd/experimentor/Initialize(mapload)
. = ..()
tracked_ian_ref = WEAKREF(locate(/mob/living/basic/pet/dog/corgi/ian) in GLOB.mob_living_list)
tracked_runtime_ref = WEAKREF(locate(/mob/living/basic/pet/cat/runtime) in GLOB.mob_living_list)
critical_items_typecache = typecacheof(list(
/obj/item/construction/rcd,
/obj/item/grenade,
/obj/item/aicard,
/obj/item/storage/backpack/holding,
/obj/item/slime_extract,
/obj/item/transfer_valve))
/obj/machinery/rnd/experimentor/RefreshParts()
. = ..()
malfunction_probability_coeff = malfunction_probability_coeff_modifier
resetTime = initial(resetTime)
for(var/datum/stock_part/servo/servo in component_parts)
resetTime = max(1, resetTime - servo.tier)
for(var/datum/stock_part/scanning_module/scanning_module in component_parts)
malfunction_probability_coeff += scanning_module.tier * 2
for(var/datum/stock_part/micro_laser/micro_laser in component_parts)
malfunction_probability_coeff += micro_laser.tier
/obj/machinery/rnd/experimentor/examine(mob/user)
. = ..()
if(in_range(user, src) || isobserver(user))
. += span_notice("The status display reads: Malfunction probability reduced by <b>[malfunction_probability_coeff]%</b>.<br>Cooldown interval between experiments at <b>[resetTime*0.1]</b> seconds.")
/obj/machinery/rnd/experimentor/attackby(obj/item/weapon, mob/living/user, params)
if(user.combat_mode)
return ..()
if(!is_insertion_ready(user))
return ..()
if(!user.transferItemToLoc(weapon, src))
to_chat(user, span_warning("\The [weapon] is stuck to your hand, you cannot put it in the [name]!"))
return TRUE
loaded_item = weapon
to_chat(user, span_notice("You add [weapon] to the machine."))
flick("h_lathe_load", src)
return TRUE
/obj/machinery/rnd/experimentor/default_deconstruction_crowbar(obj/item/O)
ejectItem()
return ..(O)
/obj/machinery/rnd/experimentor/ui_interact(mob/user, datum/tgui/ui)
ui = SStgui.try_update_ui(user, src, ui)
if(!ui)
ui = new (user, src, "Experimentator")
ui.open()
/obj/machinery/rnd/experimentor/ui_data(mob/user)
var/list/data = list()
data["hasItem"] = !!loaded_item
data["isOnCooldown"] = recentlyExperimented
data["isServerConnected"] = !!stored_research
if(!isnull(loaded_item))
var/list/item_data = list()
item_data["name"] = loaded_item.name
item_data["icon"] = icon2base64(getFlatIcon(loaded_item, no_anim = TRUE))
item_data["isRelic"] = istype(loaded_item, /obj/item/relic)
item_data["associatedNodes"] = list()
var/list/unlockable_nodes = techweb_item_unlock_check(loaded_item)
for(var/node_id in unlockable_nodes)
var/datum/techweb_node/node = SSresearch.techweb_node_by_id(node_id)
item_data["associatedNodes"] += list(list(
"name" = node.display_name,
"isUnlocked" = !(node_id in stored_research.hidden_nodes),
))
data["loadedItem"] = item_data
return data
/obj/machinery/rnd/experimentor/ui_act(action, list/params)
. = ..()
if(.)
return
switch(action)
if("eject")
ejectItem()
return TRUE
if("experiment")
var/reaction = text2num(params["id"])
if(isnull(reaction))
return
try_perform_experiment(reaction)
return TRUE
/obj/machinery/rnd/experimentor/proc/ejectItem(delete = FALSE)
if(isnull(loaded_item))
return
if(delete)
QDEL_NULL(loaded_item)
return
var/atom/drop_atom = get_step(src, EAST) || drop_location()
if(cloneMode)
visible_message(span_notice("A duplicate of \the [loaded_item] pops out!"))
new loaded_item.type(drop_atom)
cloneMode = FALSE
return
loaded_item.forceMove(drop_atom)
loaded_item = null
/obj/machinery/rnd/experimentor/proc/match_reaction(obj/item/matching, target_reaction)
PRIVATE_PROC(TRUE)
if(isnull(matching) || isnull(target_reaction))
return FAIL
var/list/item_reactions = item_reactions()
if("[matching.type]" in item_reactions)
var/associated_reaction = item_reactions["[matching.type]"]
if(associated_reaction == target_reaction)
return associated_reaction
return FAIL
/obj/machinery/rnd/experimentor/proc/try_perform_experiment(reaction)
PRIVATE_PROC(TRUE)
if(isnull(stored_research))
return
if(recentlyExperimented)
return
if(isnull(loaded_item))
return
if(reaction != SCANTYPE_DISCOVER)
reaction = match_reaction(loaded_item, reaction)
if(reaction != FAIL)
var/picked_node_id = pick(techweb_item_unlock_check(loaded_item))
stored_research.unhide_node(SSresearch.techweb_node_by_id(picked_node_id))
experiment(reaction, loaded_item)
use_energy(750 JOULES)
/obj/machinery/rnd/experimentor/proc/throwSmoke(turf/where)
var/datum/effect_system/fluid_spread/smoke/smoke = new
smoke.set_up(0, holder = src, location = where)
smoke.start()
/obj/machinery/rnd/experimentor/proc/experiment(exp,obj/item/exp_on)
recentlyExperimented = 1
icon_state = "[base_icon_state]_wloop"
var/chosenchem
var/criticalReaction = is_type_in_typecache(exp_on, critical_items_typecache)
////////////////////////////////////////////////////////////////////////////////////////////////
if(exp == SCANTYPE_POKE)
visible_message(span_notice("[src] prods at [exp_on] with mechanical arms."))
if(prob(EFFECT_PROB_LOW) && criticalReaction)
visible_message(span_notice("[exp_on] is gripped in just the right way, enhancing its focus."))
malfunction_probability_coeff_modifier++
RefreshParts() //recalculate malfunction_probability_coeff
else if(prob(EFFECT_PROB_VERYLOW-malfunction_probability_coeff))
visible_message(span_danger("[src] malfunctions and destroys [exp_on], lashing its arms out at nearby people!"))
for(var/mob/living/m in oview(1, src))
m.apply_damage(15, BRUTE, pick(BODY_ZONE_HEAD,BODY_ZONE_CHEST,BODY_ZONE_CHEST))
investigate_log("Experimentor dealt minor brute to [m].", INVESTIGATE_EXPERIMENTOR)
ejectItem(TRUE)
else if(prob(EFFECT_PROB_LOW-malfunction_probability_coeff))
visible_message(span_warning("[src] malfunctions!"))
exp = SCANTYPE_OBLITERATE
else if(prob(EFFECT_PROB_MEDIUM-malfunction_probability_coeff))
visible_message(span_danger("[src] malfunctions, throwing the [exp_on]!"))
var/mob/living/target = locate(/mob/living) in oview(7,src)
if(target)
var/obj/item/throwing = loaded_item
investigate_log("Experimentor has thrown [loaded_item] at [key_name(target)]", INVESTIGATE_EXPERIMENTOR)
ejectItem()
if(throwing)
throwing.throw_at(target, 10, 1)
////////////////////////////////////////////////////////////////////////////////////////////////
if(exp == SCANTYPE_IRRADIATE)
visible_message(span_danger("[src] reflects radioactive rays at [exp_on]!"))
if(prob(EFFECT_PROB_LOW) && criticalReaction)
visible_message(span_notice("[exp_on] has activated an unknown subroutine!"))
cloneMode = TRUE
investigate_log("Experimentor has made a clone of [exp_on]", INVESTIGATE_EXPERIMENTOR)
ejectItem()
else if(prob(EFFECT_PROB_VERYLOW-malfunction_probability_coeff))
visible_message(span_danger("[src] malfunctions, melting [exp_on] and leaking radiation!"))
radiation_pulse(src, max_range = 6, threshold = 0.3)
ejectItem(TRUE)
else if(prob(EFFECT_PROB_LOW-malfunction_probability_coeff))
visible_message(span_warning("[src] malfunctions, spewing toxic waste!"))
for(var/turf/T in oview(1, src))
if(!T.density)
if(prob(EFFECT_PROB_VERYHIGH) && !(locate(/obj/effect/decal/cleanable/greenglow) in T))
var/obj/effect/decal/cleanable/reagentdecal = new/obj/effect/decal/cleanable/greenglow(T)
reagentdecal.reagents.add_reagent(/datum/reagent/uranium/radium, 7)
else if(prob(EFFECT_PROB_MEDIUM-malfunction_probability_coeff))
var/savedName = "[exp_on]"
ejectItem(TRUE)
var/newPath = text2path(pick_weight(valid_items()))
loaded_item = new newPath(src)
visible_message(span_warning("[src] malfunctions, transforming [savedName] into [loaded_item]!"))
investigate_log("Experimentor has transformed [savedName] into [loaded_item]", INVESTIGATE_EXPERIMENTOR)
if(istype(loaded_item, /obj/item/grenade/chem_grenade))
var/obj/item/grenade/chem_grenade/CG = loaded_item
CG.detonate()
ejectItem()
////////////////////////////////////////////////////////////////////////////////////////////////
if(exp == SCANTYPE_GAS)
visible_message(span_warning("[src] fills its chamber with gas, [exp_on] included."))
if(prob(EFFECT_PROB_LOW) && criticalReaction)
visible_message(span_notice("[exp_on] achieves the perfect mix!"))
new /obj/item/stack/sheet/mineral/plasma(get_turf(pick(oview(1,src))))
else if(prob(EFFECT_PROB_VERYLOW-malfunction_probability_coeff))
visible_message(span_danger("[src] destroys [exp_on], leaking dangerous gas!"))
chosenchem = pick(/datum/reagent/carbon,/datum/reagent/uranium/radium,/datum/reagent/toxin,/datum/reagent/consumable/condensedcapsaicin,/datum/reagent/drug/mushroomhallucinogen,/datum/reagent/drug/space_drugs,/datum/reagent/consumable/ethanol,/datum/reagent/consumable/ethanol/beepsky_smash)
var/datum/reagents/tmp_holder = new/datum/reagents(50)
tmp_holder.my_atom = src
tmp_holder.add_reagent(chosenchem , 50)
investigate_log("Experimentor has released [chosenchem] smoke.", INVESTIGATE_EXPERIMENTOR)
var/datum/effect_system/fluid_spread/smoke/chem/smoke = new
smoke.set_up(0, holder = src, location = src, carry = tmp_holder, silent = TRUE)
playsound(src, 'sound/effects/smoke.ogg', 50, TRUE, -3)
smoke.start()
qdel(tmp_holder)
ejectItem(TRUE)
else if(prob(EFFECT_PROB_VERYLOW-malfunction_probability_coeff))
visible_message(span_danger("[src]'s chemical chamber has sprung a leak!"))
chosenchem = pick(/datum/reagent/mutationtoxin/classic,/datum/reagent/cyborg_mutation_nanomachines,/datum/reagent/toxin/acid)
var/datum/reagents/tmp_holder = new/datum/reagents(50)
tmp_holder.my_atom = src
tmp_holder.add_reagent(chosenchem , 50)
var/datum/effect_system/fluid_spread/smoke/chem/smoke = new
smoke.set_up(0, holder = src, location = src, carry = tmp_holder, silent = TRUE)
playsound(src, 'sound/effects/smoke.ogg', 50, TRUE, -3)
smoke.start()
qdel(tmp_holder)
ejectItem(TRUE)
warn_admins(usr, "[chosenchem] smoke")
investigate_log("Experimentor has released <font color='red'>[chosenchem]</font> smoke!", INVESTIGATE_EXPERIMENTOR)
else if(prob(EFFECT_PROB_LOW-malfunction_probability_coeff))
visible_message(span_warning("[src] malfunctions, spewing harmless gas."))
throwSmoke(loc)
else if(prob(EFFECT_PROB_MEDIUM-malfunction_probability_coeff))
visible_message(span_warning("[src] melts [exp_on], ionizing the air around it!"))
empulse(loc, 4, 6)
investigate_log("Experimentor has generated an Electromagnetic Pulse.", INVESTIGATE_EXPERIMENTOR)
ejectItem(TRUE)
////////////////////////////////////////////////////////////////////////////////////////////////
if(exp == SCANTYPE_HEAT)
visible_message(span_notice("[src] raises [exp_on]'s temperature."))
if(prob(EFFECT_PROB_LOW) && criticalReaction)
visible_message(span_warning("[src]'s emergency coolant system gives off a small ding!"))
playsound(src, 'sound/machines/ding.ogg', 50, TRUE)
var/obj/item/reagent_containers/cup/glass/coffee/C = new /obj/item/reagent_containers/cup/glass/coffee(get_turf(pick(oview(1,src))))
chosenchem = pick(/datum/reagent/toxin/plasma,/datum/reagent/consumable/capsaicin,/datum/reagent/consumable/ethanol)
C.reagents.remove_all(25)
C.reagents.add_reagent(chosenchem , 50)
C.name = "Cup of Suspicious Liquid"
C.desc = "It has a large hazard symbol printed on the side in fading ink."
investigate_log("Experimentor has made a cup of [chosenchem] coffee.", INVESTIGATE_EXPERIMENTOR)
else if(prob(EFFECT_PROB_VERYLOW-malfunction_probability_coeff))
var/turf/start = get_turf(src)
var/mob/M = locate(/mob/living) in view(src, 3)
var/turf/MT = get_turf(M)
if(MT)
visible_message(span_danger("[src] dangerously overheats, launching a flaming fuel orb!"))
investigate_log("Experimentor has launched a <font color='red'>fireball</font> at [M]!", INVESTIGATE_EXPERIMENTOR)
var/obj/projectile/magic/fireball/FB = new /obj/projectile/magic/fireball(start)
FB.preparePixelProjectile(MT, start)
FB.fire()
else if(prob(EFFECT_PROB_LOW-malfunction_probability_coeff))
visible_message(span_danger("[src] malfunctions, melting [exp_on] and releasing a burst of flame!"))
explosion(src, devastation_range = -1, flame_range = 2, adminlog = FALSE)
investigate_log("Experimentor started a fire.", INVESTIGATE_EXPERIMENTOR)
ejectItem(TRUE)
else if(prob(EFFECT_PROB_MEDIUM-malfunction_probability_coeff))
visible_message(span_warning("[src] malfunctions, melting [exp_on] and leaking hot air!"))
var/datum/gas_mixture/env = loc.return_air()
if(env)
var/heat_capacity = max(env.heat_capacity(), 1)
env.temperature = min((env.temperature * heat_capacity + 100000) / heat_capacity, 1000)
air_update_turf(FALSE, FALSE)
investigate_log("Experimentor has released hot air.", INVESTIGATE_EXPERIMENTOR)
ejectItem(TRUE)
else if(prob(EFFECT_PROB_MEDIUM-malfunction_probability_coeff))
visible_message(span_warning("[src] malfunctions, activating its emergency coolant systems!"))
throwSmoke(loc)
for(var/mob/living/m in oview(1, src))
m.apply_damage(5, BURN, pick(BODY_ZONE_HEAD,BODY_ZONE_CHEST,BODY_ZONE_CHEST))
investigate_log("Experimentor has dealt minor burn damage to [key_name(m)]", INVESTIGATE_EXPERIMENTOR)
ejectItem()
////////////////////////////////////////////////////////////////////////////////////////////////
if(exp == SCANTYPE_COLD)
visible_message(span_notice("[src] lowers [exp_on]'s temperature."))
if(prob(EFFECT_PROB_LOW) && criticalReaction)
visible_message(span_warning("[src]'s emergency coolant system gives off a small ding!"))
var/obj/item/reagent_containers/cup/glass/coffee/C = new /obj/item/reagent_containers/cup/glass/coffee(get_turf(pick(oview(1,src))))
playsound(src, 'sound/machines/ding.ogg', 50, TRUE) //Ding! Your death coffee is ready!
chosenchem = pick(/datum/reagent/uranium,/datum/reagent/consumable/frostoil,/datum/reagent/medicine/ephedrine)
C.reagents.remove_all(25)
C.reagents.add_reagent(chosenchem , 50)
C.name = "Cup of Suspicious Liquid"
C.desc = "It has a large hazard symbol printed on the side in fading ink."
investigate_log("Experimentor has made a cup of [chosenchem] coffee.", INVESTIGATE_EXPERIMENTOR)
else if(prob(EFFECT_PROB_VERYLOW-malfunction_probability_coeff))
visible_message(span_danger("[src] malfunctions, shattering [exp_on] and releasing a dangerous cloud of coolant!"))
var/datum/reagents/tmp_holder = new/datum/reagents(50)
tmp_holder.my_atom = src
tmp_holder.add_reagent(/datum/reagent/consumable/frostoil, 50)
investigate_log("Experimentor has released frostoil gas.", INVESTIGATE_EXPERIMENTOR)
var/datum/effect_system/fluid_spread/smoke/chem/smoke = new
smoke.set_up(0, holder = src, location = src, carry = tmp_holder, silent = TRUE)
playsound(src, 'sound/effects/smoke.ogg', 50, TRUE, -3)
smoke.start()
qdel(tmp_holder)
ejectItem(TRUE)
else if(prob(EFFECT_PROB_LOW-malfunction_probability_coeff))
visible_message(span_warning("[src] malfunctions, shattering [exp_on] and leaking cold air!"))
var/datum/gas_mixture/env = loc.return_air()
if(env)
var/heat_capacity = max(env.heat_capacity(), 1)
env.temperature = max((env.temperature * heat_capacity - 75000) / heat_capacity, TCMB)
air_update_turf(FALSE, FALSE)
investigate_log("Experimentor has released cold air.", INVESTIGATE_EXPERIMENTOR)
ejectItem(TRUE)
else if(prob(EFFECT_PROB_MEDIUM-malfunction_probability_coeff))
visible_message(span_warning("[src] malfunctions, releasing a flurry of chilly air as [exp_on] pops out!"))
var/datum/effect_system/fluid_spread/smoke/smoke = new
smoke.set_up(0, holder = src, location = loc)
smoke.start()
ejectItem()
////////////////////////////////////////////////////////////////////////////////////////////////
if(exp == SCANTYPE_OBLITERATE)
visible_message(span_warning("[exp_on] activates the crushing mechanism, [exp_on] is destroyed!"))
if(prob(EFFECT_PROB_LOW) && criticalReaction)
visible_message(span_warning("[src]'s crushing mechanism slowly and smoothly descends, flattening the [exp_on]!"))
new /obj/item/stack/sheet/plasteel(get_turf(pick(oview(1,src))))
else if(prob(EFFECT_PROB_VERYLOW-malfunction_probability_coeff))
visible_message(span_danger("[src]'s crusher goes way too many levels too high, crushing right through space-time!"))
playsound(src, 'sound/effects/supermatter.ogg', 50, TRUE, -3)
investigate_log("Experimentor has triggered the 'throw things' reaction.", INVESTIGATE_EXPERIMENTOR)
for(var/atom/movable/AM in oview(7,src))
if(!AM.anchored)
AM.throw_at(src,10,1)
else if(prob(EFFECT_PROB_LOW-malfunction_probability_coeff))
visible_message(span_danger("[src]'s crusher goes one level too high, crushing right into space-time!"))
playsound(src, 'sound/effects/supermatter.ogg', 50, TRUE, -3)
investigate_log("Experimentor has triggered the 'minor throw things' reaction.", INVESTIGATE_EXPERIMENTOR)
var/list/throwAt = list()
for(var/atom/movable/AM in oview(7,src))
if(!AM.anchored)
throwAt.Add(AM)
for(var/counter in 1 to throwAt.len)
var/atom/movable/cast = throwAt[counter]
cast.throw_at(pick(throwAt),10,1)
ejectItem(TRUE)
////////////////////////////////////////////////////////////////////////////////////////////////
if(exp == FAIL)
var/a = pick("rumbles","shakes","vibrates","shudders","honks")
var/b = pick("crushes","spins","viscerates","smashes","insults")
visible_message(span_warning("[exp_on] [a], and [b], the experiment was a failure."))
if(exp == SCANTYPE_DISCOVER)
visible_message(span_notice("[src] scans the [exp_on], revealing its true nature!"))
playsound(src, 'sound/effects/supermatter.ogg', 50, 3, -1)
var/obj/item/relic/R = loaded_item
R.reveal()
investigate_log("Experimentor has revealed a relic with [span_danger("[R.realProc]")] effect.", INVESTIGATE_EXPERIMENTOR)
ejectItem()
//Global reactions
if(prob(EFFECT_PROB_VERYLOW-malfunction_probability_coeff) && loaded_item)
var/globalMalf = rand(1,100)
if(globalMalf < 15)
visible_message(span_warning("[src]'s onboard detection system has malfunctioned!"))
item_reactions()["[exp_on.type]"] = pick(SCANTYPE_POKE,SCANTYPE_IRRADIATE,SCANTYPE_GAS,SCANTYPE_HEAT,SCANTYPE_COLD,SCANTYPE_OBLITERATE)
ejectItem()
if(globalMalf > 16 && globalMalf < 35)
visible_message(span_warning("[src] melts [exp_on], ian-izing the air around it!"))
throwSmoke(loc)
var/mob/living/tracked_ian = tracked_ian_ref?.resolve()
if(tracked_ian)
throwSmoke(tracked_ian.loc)
tracked_ian.forceMove(loc)
investigate_log("Experimentor has stolen Ian!", INVESTIGATE_EXPERIMENTOR) //...if anyone ever fixes it...
else
new /mob/living/basic/pet/dog/corgi(loc)
investigate_log("Experimentor has spawned a new corgi.", INVESTIGATE_EXPERIMENTOR)
ejectItem(TRUE)
if(globalMalf > 36 && globalMalf < 50)
visible_message(span_warning("Experimentor draws the life essence of those nearby!"))
for(var/mob/living/m in view(4,src))
to_chat(m, span_danger("You feel your flesh being torn from you, mists of blood drifting to [src]!"))
m.apply_damage(50, BRUTE, BODY_ZONE_CHEST)
investigate_log("Experimentor has taken 50 brute a blood sacrifice from [m]", INVESTIGATE_EXPERIMENTOR)
if(globalMalf > 51 && globalMalf < 75)
visible_message(span_warning("[src] encounters a run-time error!"))
throwSmoke(loc)
var/mob/living/tracked_runtime = tracked_runtime_ref?.resolve()
if(tracked_runtime)
throwSmoke(tracked_runtime.loc)
tracked_runtime.forceMove(drop_location())
investigate_log("Experimentor has stolen Runtime!", INVESTIGATE_EXPERIMENTOR)
else
new /mob/living/basic/pet/cat(loc)
investigate_log("Experimentor failed to steal runtime, and instead spawned a new cat.", INVESTIGATE_EXPERIMENTOR)
ejectItem(TRUE)
if(globalMalf > 76 && globalMalf < 98)
visible_message(span_warning("[src] begins to smoke and hiss, shaking violently!"))
use_energy(500 KILO JOULES)
investigate_log("Experimentor has drained power from its APC", INVESTIGATE_EXPERIMENTOR)
if(globalMalf == 99)
visible_message(span_warning("[src] begins to glow and vibrate. It's going to blow!"))
addtimer(CALLBACK(src, PROC_REF(boom)), 50)
if(globalMalf == 100)
visible_message(span_warning("[src] begins to glow and vibrate. It's going to blow!"))
addtimer(CALLBACK(src, PROC_REF(honk)), 50)
addtimer(CALLBACK(src, PROC_REF(reset_exp)), resetTime)
/obj/machinery/rnd/experimentor/proc/boom()
explosion(src, devastation_range = 1, heavy_impact_range = 5, light_impact_range = 10, flash_range = 5, adminlog = TRUE)
/obj/machinery/rnd/experimentor/proc/honk()
playsound(src, 'sound/items/bikehorn.ogg', 500)
new /obj/item/grown/bananapeel(loc)
/obj/machinery/rnd/experimentor/proc/reset_exp()
update_appearance()
recentlyExperimented = FALSE
/obj/machinery/rnd/experimentor/update_icon_state()
icon_state = base_icon_state
return ..()
/obj/machinery/rnd/experimentor/proc/warn_admins(user, ReactionName)
var/turf/T = get_turf(user)
message_admins("Experimentor reaction: [ReactionName] generated by [ADMIN_LOOKUPFLW(user)] at [ADMIN_VERBOSEJMP(T)]")
log_game("Experimentor reaction: [ReactionName] generated by [key_name(user)] in [AREACOORD(T)]")
#undef SCANTYPE_POKE
#undef SCANTYPE_IRRADIATE
#undef SCANTYPE_GAS
#undef SCANTYPE_HEAT
#undef SCANTYPE_COLD
#undef SCANTYPE_OBLITERATE
#undef SCANTYPE_DISCOVER
#undef EFFECT_PROB_VERYLOW
#undef EFFECT_PROB_LOW
#undef EFFECT_PROB_MEDIUM
#undef EFFECT_PROB_HIGH
#undef EFFECT_PROB_VERYHIGH
#undef FAIL
//////////////////////////////////SPECIAL ITEMS////////////////////////////////////////
/obj/item/relic
name = "strange object"
desc = "What mysteries could this hold? Maybe Research & Development could find out."
icon = 'icons/obj/devices/assemblies.dmi'
var/realName = "defined object"
var/revealed = FALSE
var/realProc
var/reset_timer = 60
COOLDOWN_DECLARE(cooldown)
/obj/item/relic/Initialize(mapload)
. = ..()
icon_state = pick("shock_kit","armor-igniter-analyzer","infra-igniter0","infra-igniter1","radio-multitool","prox-radio1","radio-radio","timer-multitool0","radio-igniter-tank")
realName = "[pick("broken","twisted","spun","improved","silly","regular","badly made")] [pick("device","object","toy","illegal tech","weapon")]"
/obj/item/relic/proc/reveal()
if(revealed) //Re-rolling your relics seems a bit overpowered, yes?
return
revealed = TRUE
name = realName
reset_timer = rand(reset_timer, reset_timer * 5)
realProc = pick(PROC_REF(teleport), PROC_REF(explode), PROC_REF(rapidDupe), PROC_REF(petSpray), PROC_REF(flash), PROC_REF(clean), PROC_REF(corgicannon))
/obj/item/relic/attack_self(mob/user)
if(!revealed)
to_chat(user, span_notice("You aren't quite sure what this is. Maybe R&D knows what to do with it?"))
return
if(!COOLDOWN_FINISHED(src, cooldown))
to_chat(user, span_warning("[src] does not react!"))
return
if(loc != user)
return
COOLDOWN_START(src, cooldown, reset_timer)
call(src,realProc)(user)
//////////////// RELIC PROCS /////////////////////////////
/obj/item/relic/proc/throwSmoke(turf/where)
var/datum/effect_system/fluid_spread/smoke/smoke = new
smoke.set_up(0, holder = src, location = get_turf(where))
smoke.start()
/obj/item/relic/proc/corgicannon(mob/user)
playsound(src, SFX_SPARKS, rand(25,50), TRUE, SHORT_RANGE_SOUND_EXTRARANGE)
var/mob/living/basic/pet/dog/corgi/sad_corgi = new(get_turf(user))
sad_corgi.throw_at(pick(oview(10,user)), 10, rand(3,8), callback = CALLBACK(src, PROC_REF(throwSmoke), sad_corgi))
warn_admins(user, "Corgi Cannon", 0)
/obj/item/relic/proc/clean(mob/user)
playsound(src, SFX_SPARKS, rand(25,50), TRUE, SHORT_RANGE_SOUND_EXTRARANGE)
var/obj/item/grenade/chem_grenade/cleaner/CL = new/obj/item/grenade/chem_grenade/cleaner(get_turf(user))
CL.detonate()
qdel(CL)
warn_admins(user, "Foam", 0)
/obj/item/relic/proc/flash(mob/user)
playsound(src, SFX_SPARKS, rand(25,50), TRUE, SHORT_RANGE_SOUND_EXTRARANGE)
var/obj/item/grenade/flashbang/CB = new/obj/item/grenade/flashbang(user.loc)
CB.detonate()
warn_admins(user, "Flash")
/obj/item/relic/proc/petSpray(mob/user)
var/message = span_danger("[src] begins to shake, and in the distance the sound of rampaging animals arises!")
visible_message(message)
to_chat(user, message)
var/static/list/valid_animals = list(
/mob/living/basic/bear,
/mob/living/basic/bee,
/mob/living/basic/butterfly,
/mob/living/basic/carp,
/mob/living/basic/crab,
/mob/living/basic/lizard,
/mob/living/basic/mouse,
/mob/living/basic/parrot,
/mob/living/basic/pet/cat,
/mob/living/basic/pet/dog/corgi,
/mob/living/basic/pet/dog/pug,
/mob/living/basic/pet/fox,
)
for(var/counter in 1 to rand(1, 25))
var/mobType = pick(valid_animals)
new mobType(get_turf(src))
warn_admins(user, "Mass Mob Spawn")
if(prob(60))
to_chat(user, span_warning("[src] falls apart!"))
qdel(src)
/obj/item/relic/proc/rapidDupe(mob/user)
audible_message("[src] emits a loud pop!")
var/list/dupes = list()
for(var/counter in 1 to rand(5,10))
var/obj/item/relic/R = new type(get_turf(src))
R.name = name
R.desc = desc
R.realName = realName
R.realProc = realProc
R.revealed = TRUE
dupes += R
R.throw_at(pick(oview(7,get_turf(src))),10,1)
QDEL_LIST_IN(dupes, rand(10, 100))
warn_admins(user, "Rapid duplicator", 0)
/obj/item/relic/proc/explode(mob/user)
to_chat(user, span_danger("[src] begins to heat up!"))
addtimer(CALLBACK(src, PROC_REF(do_explode), user), rand(35, 100))
/obj/item/relic/proc/do_explode(mob/user)
if(loc == user)
visible_message(span_notice("\The [src]'s top opens, releasing a powerful blast!"))
explosion(src, heavy_impact_range = rand(1,5), light_impact_range = rand(1,5), flame_range = 2, flash_range = rand(1,5), adminlog = TRUE)
warn_admins(user, "Explosion")
qdel(src) //Comment this line to produce a light grenade (the bomb that keeps on exploding when used)!!
/obj/item/relic/proc/teleport(mob/user)
to_chat(user, span_notice("[src] begins to vibrate!"))
addtimer(CALLBACK(src, PROC_REF(do_the_teleport), user), rand(10, 30))
/obj/item/relic/proc/do_the_teleport(mob/user)
var/turf/userturf = get_turf(user)
if(loc == user && !is_centcom_level(userturf.z)) //Because Nuke Ops bringing this back on their shuttle, then looting the ERT area is 2fun4you!
visible_message(span_notice("[src] twists and bends, relocating itself!"))
throwSmoke(userturf)
do_teleport(user, userturf, 8, asoundin = 'sound/effects/phasein.ogg', channel = TELEPORT_CHANNEL_BLUESPACE)
throwSmoke(get_turf(user))
warn_admins(user, "Teleport", 0)
//Admin Warning proc for relics
/obj/item/relic/proc/warn_admins(mob/user, RelicType, priority = 1)
var/turf/T = get_turf(src)
var/log_msg = "[RelicType] relic used by [key_name(user)] in [AREACOORD(T)]"
if(priority) //For truly dangerous relics that may need an admin's attention. BWOINK!
message_admins("[RelicType] relic activated by [ADMIN_LOOKUPFLW(user)] in [ADMIN_VERBOSEJMP(T)]")
log_game(log_msg)
investigate_log(log_msg, "experimentor")