Files
e0aa218843 Shell May Cry: A comprehensive rework of synthetics. (#20721)
https://www.youtube.com/watch?v=9mPvZ96pHJI

A pull request commissioned by the Synthetic Lore Team to
comprehensively rework synthetics (read: IPCs) and how they work in
Aurora. The objective is to make IPCs as unique as possible from humans,
upgrading the robotic feel and atmosphere, while also preserving a good
sense of balance in-game.

Key features:

- A comprehensive expansion of synthetic organs, all of which now
fulfill a purpose: hydraulics, cooling units, power systems, actuators,
diagnostics units.
- Customizable organs with benefits and drawbacks, such as with cooling
units and power systems.
- Unique ways to repair the organs and more involved steps.
- Unique damage mechanics - every organ has wiring and electronics which
affect its functioning, and they are defended by plating which provides
natural armour.
- Improved and immersive diagnostics.
- Unique features, benefits, and drawbacks for every IPC frame.
- A rework of the positronic brain, which can be either destroyed or
shut down, alongside effects caused by low integrity.
- A rework of how EMPs affect IPC organs.
- Non-binary damage states for each organ.

To-do:

- [x] Finish the unique features for each frame.
- [x] Look into if mechanical synthskin is possible.
- [x] Power system.
- [x] Posibrain mechanics.
- [ ] Passive cooling expansion.
- [x] EMP mechanics.
- [x] Repair mechanics.
- [x] Mob weight mechanics.
- [ ] Gurney for heavy mobs.
- [x] New augments.
- [ ] IPC tag scanning and flashing.

---------

Signed-off-by: Werner <1331699+Arrow768@users.noreply.github.com>
Co-authored-by: Matt Atlas <liermattia@gmail.com>
Co-authored-by: Geeves <22774890+Geevies@users.noreply.github.com>
Co-authored-by: Werner <1331699+Arrow768@users.noreply.github.com>
2025-12-21 16:26:23 +00:00

213 lines
8.3 KiB
Plaintext

/datum/technomancer/spell/energy_siphon
name = "Energy Siphon"
desc = "This creates a link to a target that drains electricity, converts it to energy that the Core can use, then absorbs it. \
Every second, electricity is stolen until the link is broken by the target moving too far away, or having no more energy left. \
Can drain from powercells, microbatteries, and other Cores. The beam created by the siphoning is harmful to touch."
enhancement_desc = "Rate of siphoning is doubled."
spell_power_desc = "Rate of siphoning is scaled up based on spell power."
cost = 100
obj_path = /obj/item/spell/energy_siphon
ability_icon_state = "tech_energysiphon"
category = UTILITY_SPELLS
/obj/item/spell/energy_siphon
name = "energy siphon"
desc = "Now you are an energy vampire."
icon_state = "energy_siphon"
cast_methods = CAST_RANGED
aspect = ASPECT_SHOCK
var/atom/movable/siphoning = null // What the spell is currently draining. Does nothing if null.
var/list/things_to_siphon = list() //Things which are actually drained as a result of the above not being null.
var/flow_rate = 1000 // Limits how much electricity can be drained per second. Measured by default in god knows what.
/obj/item/spell/energy_siphon/New()
..()
START_PROCESSING(SSprocessing, src)
/obj/item/spell/energy_siphon/Destroy()
stop_siphoning()
STOP_PROCESSING(SSprocessing, src)
return ..()
/obj/item/spell/energy_siphon/process()
if(!siphoning)
return
if(!pay_energy(100))
to_chat(owner, SPAN_WARNING("You can't afford to maintain the siphon link!"))
stop_siphoning()
return
if(get_dist(siphoning, get_turf(src)) > 4)
to_chat(owner, SPAN_WARNING("\The [siphoning] is too far to drain from!"))
stop_siphoning()
return
if(!(siphoning in view(owner)))
to_chat(owner, SPAN_WARNING("\The [siphoning] cannot be seen!"))
stop_siphoning()
return
siphon(siphoning, owner)
/obj/item/spell/energy_siphon/on_ranged_cast(atom/hit_atom, mob/user)
. = ..()
if(istype(hit_atom, /atom/movable) && within_range(hit_atom, 4))
var/atom/movable/AM = hit_atom
populate_siphon_list(AM)
if(!things_to_siphon.len)
to_chat(user, SPAN_WARNING("You cannot steal energy from \a [AM]."))
return 0
siphoning = AM
update_icon()
log_and_message_admins("is siphoning energy from \a [AM].")
else
stop_siphoning()
/obj/item/spell/energy_siphon/proc/populate_siphon_list(atom/movable/target)
things_to_siphon.Cut()
things_to_siphon |= target.GetAllContents()
for(var/atom/movable/AM in things_to_siphon)
if(ishuman(AM)) // We can drain FBPs, so we can skip the test below.
var/mob/living/carbon/human/H = AM
if(H.isSynthetic())
continue
if(AM.drain_power(1) <= 0) // This checks if whatever's in the list can be drained from.
things_to_siphon.Remove(AM)
/obj/item/spell/energy_siphon/proc/stop_siphoning()
siphoning = null
things_to_siphon.Cut()
update_icon()
#define SIPHON_CELL_TO_ENERGY 0.5
#define SIPHON_FBP_TO_ENERGY 5.0
#define SIPHON_CORE_TO_ENERGY 0.5
// This is called every tick, so long as a link exists between the target and the Technomancer.
/obj/item/spell/energy_siphon/proc/siphon(atom/movable/siphoning, mob/living/user)
var/list/things_to_drain = things_to_siphon // Temporary list copy of what we're gonna steal from.
var/charge_to_give = 0 // How much energy to give to the Technomancer at the end.
var/flow_remaining = calculate_spell_power(flow_rate)
if(!siphoning)
return 0
update_icon()
//playsound(source = src, soundin = 'TODO', vol = 30, vary = 0, extrarange = 0, falloff = 0, is_global = 0)
if(check_for_scepter())
flow_remaining = flow_remaining * 2
// First, we drain normal batteries.
if(things_to_drain.len)
// Don't bother with empty stuff.
for(var/atom/movable/AM in things_to_drain)
if(AM.drain_power(1) <= 0)
things_to_drain.Remove(AM)
if(!things_to_drain.len)
return
var/charge_to_steal = round(flow_remaining / things_to_drain.len) // This is to drain all the cells evenly.
for(var/atom/movable/AM in things_to_drain)
var/big_number = AM.drain_power(0,0,charge_to_steal / CELLRATE) // This drains the cell, and leaves us with a big number.
flow_remaining = flow_remaining - (big_number * CELLRATE) // Which we reduce to our needed number by multiplying.
AM.update_icon() // So guns and batteries will display correctly.
charge_to_give = charge_to_give + (flow_rate - flow_remaining) * SIPHON_CELL_TO_ENERGY
// If we have 'leftover' flow, let's try to do more.
if(round(flow_remaining))
if(ishuman(siphoning))
var/mob/living/carbon/human/H = siphoning
// Let's drain from FBPs. Note that it is possible for the caster to drain themselves if they are an FBP and desperate.
if(H.isSynthetic())
var/nutrition_to_steal = flow_remaining * 0.025 // Should steal about 25 nutrition per second by default.
var/old_nutrition = H.nutrition
H.nutrition = max(H.nutrition - nutrition_to_steal, 0)
var/nutrition_delta = old_nutrition - H.nutrition
charge_to_give += nutrition_delta * SIPHON_FBP_TO_ENERGY
flow_remaining = flow_remaining - nutrition_to_steal / 0.025
// Let's steal some energy from another Technomancer.
if(H != user)
var/obj/item/technomancer_core/their_core = H.get_technomancer_core()
if(their_core && their_core.pay_energy(flow_remaining / 2)) // Don't give energy from nothing.
charge_to_give += flow_remaining * SIPHON_CORE_TO_ENERGY
flow_remaining = 0
if(charge_to_give) // Shock anyone standing in the beam.
create_lightning(user, siphoning)
// Now we can actually fill up the core.
if(core.energy < core.max_energy)
give_energy(charge_to_give)
to_chat(user, SPAN_NOTICE("Stolen [charge_to_give * CELLRATE] kJ and converted to [charge_to_give] Core energy."))
if((core.max_energy - core.energy) < charge_to_give ) // We have some overflow, if this is true.
if(user.isSynthetic() && ishuman(user)) // Let's do something with it, if we're a robot.
var/mob/living/carbon/human/H = user
charge_to_give = charge_to_give - (core.max_energy - core.energy)
var/obj/item/organ/internal/machine/power_core/C = H.internal_organs_by_name[BP_CELL]
var/obj/item/cell/potato = C.get_cell()
potato.give(charge_to_give)
to_chat(user, SPAN_NOTICE("Redirected energy to internal microcell."))
else
to_chat(user, SPAN_NOTICE("Stolen [charge_to_give * CELLRATE] kJ."))
adjust_instability(2)
if(flow_remaining == flow_rate) // We didn't drain anything.
to_chat(user, SPAN_WARNING("\The [siphoning] cannot be drained any further."))
stop_siphoning()
/obj/item/spell/energy_siphon/update_icon()
..()
if(siphoning)
icon_state = "energy_siphon_drain"
else
icon_state = "energy_siphon"
/obj/item/spell/energy_siphon/proc/create_lightning(mob/user, atom/source)
if(user && source && user != source)
var/i = 7 // process() takes two seconds to tick, this ensures the appearance of a ongoing beam.
while(i)
var/obj/projectile/beam/lightning/energy_siphon/lightning = new(get_turf(source))
lightning.firer = user
lightning.old_style_target(user)
lightning.fire()
i--
sleep(3)
/obj/projectile/beam/lightning/energy_siphon
name = "energy stream"
icon_state = "lightning"
range = 6 // Backup plan in-case the effect somehow misses the Technomancer.
power = 5 // This fires really fast, so this may add up if someone keeps standing in the beam.
penetrating = 5
/obj/projectile/beam/lightning/energy_siphon/Bump(atom/A as mob|obj|turf|area, forced=0)
if(A == firer) // For this, you CAN shoot yourself.
on_hit(A)
density = 0
set_invisibility(101)
qdel(src)
return 1
..()
/obj/projectile/beam/lightning/energy_siphon/on_hit(atom/target, blocked, def_zone)
if(!isliving(target))
return ..()
var/mob/living/target_mob = target
if(target_mob == firer) // This shouldn't actually occur due to Bump(), but just in-case.
return 1
if(ishuman(target_mob)) // Otherwise someone else stood in the beam and is going to pay for it.
var/mob/living/carbon/human/H = target_mob
var/obj/item/organ/external/affected = H.get_organ(check_zone(BP_CHEST))
H.electrocute_act(power, src, H.get_siemens_coefficient_organ(affected), BP_CHEST, 0)
else
target_mob.electrocute_act(power, src, 0.75, BP_CHEST)
return 0 // Since this is a continous beam, it needs to keep flying until it hits the Technomancer.
#undef SIPHON_CELL_TO_ENERGY
#undef SIPHON_FBP_TO_ENERGY
#undef SIPHON_CORE_TO_ENERGY