Files
Leland KembleandGitHub b3c74dca2d Moves a plethora of things from attackby() to item_interaction() (#96726)
## About The Pull Request

50 files. Sorry about that, I was faster today. Just merge without
looking, it'll be fine.

This is the end of `/machinery/` & `/stack/` `attackby()`s(that aren't
actually meant for being attacked) when these are all merged.
Like, 200 left? I think? 

Also, this one will make light replacer reloading slightly more
difficult until whichever one converted the light replacer is merged.
If, for some ungodly reason, this one gets merged first.

Changes beyond conversion:

if a `/porta_turret_construct` somehow became anchored out of sync with
its construction steps, it's no longer softlocked
you can no longer place an infinite amount of blackboxes into the
blackbox recorder
a TTV will no longer keep its ghost on your back when you cut its wires
while wearing it
RCLs will properly update their appearance when initially given their
first coil
The creator of `robot_suit/attackby()` has been cursed to spend a
thousand years in the lake of fire

## Why It's Good For The Game

Now that all of these are remade, you can find bugs in them and then get
GBP for fixing them(not that my code would ever have bugs).

## Changelog
🆑

fix: It's no longer possible to softlock building a turret(if it ever
was)
fix: you can no longer stuff the blackbox recorder full with as many
blackboxes as you've somehow collected
fix: TTVs will no longer remain on your back as a ghost when you cut
their wire straps
fix: RCLs will now properly update their appearance when given their
first coil of cleaners
code: 50 files have been converted from attackby() to item_interaction()
/🆑
2026-07-02 06:10:46 +02:00

376 lines
13 KiB
Plaintext

#define TURBINE_MAX_STORED_POWER (0.2 * STANDARD_CELL_CHARGE)
#define TURBINE_ANIMATION_TICKS_PER_TILE (1)
#define TURBINE_ANIMATION_TICKS (4)
#define TURBINE_CHARGE_PER_TILE (0.002 * STANDARD_CELL_CHARGE)
#define TURBINE_ANCHORED_POWER_PER_KPA (TURBINE_CHARGE_PER_TILE * 2)
#define TURBINE_MIN_SECONDS_BETWEEN_SOUNDS (0.33 SECONDS)
/obj/item/portable_wind_turbine
name = "portable wind turbine"
icon = 'icons/obj/wind_turbine.dmi'
icon_state = "icon"
base_icon_state = "icon"
desc = "A portable wind turbine that can charge attached energy based weaponry, PDAs, and other devices. As a safety mechanism after an unprecedented amount of amputations, only charges when attached to your back / the floor."
worn_icon = 'icons/obj/wind_turbine.dmi'
worn_icon_state = "base_turbine"
inhand_icon_state = "wind_turbine"
lefthand_file = 'icons/mob/inhands/64x64_lefthand.dmi'
righthand_file = 'icons/mob/inhands/64x64_righthand.dmi'
inhand_x_dimension = 64
inhand_y_dimension = 64
slot_flags = ITEM_SLOT_SUITSTORE | ITEM_SLOT_BACK
force = 10
throwforce = 6
w_class = WEIGHT_CLASS_BULKY
custom_materials = list(/datum/material/iron = SHEET_MATERIAL_AMOUNT * 22, /datum/material/plastic = SHEET_MATERIAL_AMOUNT * 5, /datum/material/glass = SMALL_MATERIAL_AMOUNT * 0.5)
///What item is being charged currently?
var/obj/item/charging = null
///Did we put power into "charging" last process()?
var/using_power = FALSE
///Current rotor animation frame. (floating point value).
var/rotor_tick = 0
///Power that has been generated by moving about.
var/available_power = 0
///The last time (world.time) a sound was played at.
var/last_sound_time = 0
///Capacitor currently inserted.
var/obj/item/stock_parts/capacitor/cap = null
var/static/list/allowed_devices = typecacheof(list(
/obj/item/gun/energy,
/obj/item/compact_remote,
/obj/item/controller,
/obj/item/organ/cyberimp/bci,
/obj/item/integrated_circuit,
/obj/item/melee/baton/security,
/obj/item/modular_computer,
/obj/item/stock_parts/power_store/cell,
))
/obj/item/portable_wind_turbine/Initialize(mapload)
. = ..()
AddElement(/datum/element/drag_pickup)
RegisterSignal(src, COMSIG_MOVABLE_SET_ANCHORED, PROC_REF(on_anchor))
RegisterSignal(src, COMSIG_ATOM_PRE_DIR_CHANGE, PROC_REF(block_dir_changes_unanchored))
update_appearance()
/obj/item/portable_wind_turbine/loaded/Initialize(mapload)
. = ..()
cap = new /obj/item/stock_parts/capacitor(src)
/obj/item/portable_wind_turbine/Destroy()
return ..()
///Called when src is anchored
/obj/item/portable_wind_turbine/proc/on_anchor(atom/source, is_anchored)
SIGNAL_HANDLER
if (is_anchored)
RegisterSignal(src, COMSIG_MOVABLE_RESISTED_SPACEWIND, PROC_REF(on_space_wind))
UnregisterSignal(src, COMSIG_ATOM_PRE_DIR_CHANGE)
forceMove(get_turf(src))
pixel_x = 0
pixel_y = 0
setDir(SOUTH)
else
UnregisterSignal(src, COMSIG_MOVABLE_RESISTED_SPACEWIND)
RegisterSignal(src, COMSIG_ATOM_PRE_DIR_CHANGE, PROC_REF(block_dir_changes_unanchored))
///Called when src changes direction when not equipped
/obj/item/portable_wind_turbine/proc/block_dir_changes_unanchored(atom/source, old_dir, new_dir)
SIGNAL_HANDLER
return COMPONENT_ATOM_BLOCK_DIR_CHANGE
///Called when this resists space wind
/obj/item/portable_wind_turbine/proc/on_space_wind(atom/source, pressure_difference, pressure_direction)
SIGNAL_HANDLER
var/obj/item/portable_wind_turbine/turbine = source
if (!turbine)
return
if (isnull(cap))
return
turbine.add_power(TURBINE_ANCHORED_POWER_PER_KPA * pressure_difference * cap.rating, ignore_cap = TRUE)
set_rotor_tick(rotor_tick + 1)
/obj/item/portable_wind_turbine/update_appearance(updates)
. = ..()
update_back()
/obj/item/portable_wind_turbine/equipped(mob/user, slot, initial)
. = ..()
update_appearance()
if(slot & slot_flags)
RegisterSignal(user, COMSIG_MOVABLE_MOVED, PROC_REF(on_move))
RegisterSignal(user, COMSIG_ATOM_POST_DIR_CHANGE, PROC_REF(on_dir_change))
else
setDir(SOUTH)
UnregisterSignal(user, COMSIG_MOVABLE_MOVED, PROC_REF(on_move))
UnregisterSignal(user, COMSIG_ATOM_POST_DIR_CHANGE, PROC_REF(on_dir_change))
///Called when the thing HOLDING the turbine changes direction
/obj/item/portable_wind_turbine/proc/on_dir_change(datum/source, old_dir, new_dir)
SIGNAL_HANDLER
update_appearance()
///Updates the worn back icon for the current loc
/obj/item/portable_wind_turbine/proc/update_back()
if (ishuman(loc))
var/mob/living/carbon/human/human = loc
human.update_worn_back()
///Tries to play the woosh sound effect. May not play it if it's been too soon since the last call.
/obj/item/portable_wind_turbine/proc/try_playsound()
if ((world.time - last_sound_time) < TURBINE_MIN_SECONDS_BETWEEN_SOUNDS)
return
playsound(src, 'sound/machines/woosh.ogg', 20, FALSE)
last_sound_time = world.time
///Sets the rotor animation tick to a new value. Returns TRUE if the rotor made a full rotation.
/obj/item/portable_wind_turbine/proc/set_rotor_tick(new_tick)
var/last_rotor_tick = floor(rotor_tick)
rotor_tick = new_tick
var/made_full_rotation = FALSE
if (rotor_tick >= TURBINE_ANIMATION_TICKS)
rotor_tick -= floor(rotor_tick / TURBINE_ANIMATION_TICKS) * TURBINE_ANIMATION_TICKS
try_playsound()
made_full_rotation = TRUE
var/rounded_rotor_tick = floor(rotor_tick)
if (rounded_rotor_tick != last_rotor_tick)
update_appearance()
return made_full_rotation
///Adds a certain amount of power to the internal buffer
/obj/item/portable_wind_turbine/proc/add_power(power, ignore_cap = FALSE)
if (ignore_cap)
available_power += power
else
available_power = min(available_power + power, TURBINE_MAX_STORED_POWER * (isnull(cap?.rating) ? 1 : cap.rating))
///Called when the thing HOLDING the turbine moves
/obj/item/portable_wind_turbine/proc/on_move(atom/thing, atom/old_loc, dir)
SIGNAL_HANDLER
var/mob/living/user = thing
if (!user)
return
if (isnull(cap))
return
var/distance = get_dist(old_loc, user.loc)
var/turf/open/open_turf = get_turf(user)
if (!istype(open_turf))
return
var/pressure_factor = open_turf.air.return_pressure() / 101.0
if (pressure_factor <= 0)
return
var/made_full_rotation = set_rotor_tick(rotor_tick + distance * TURBINE_ANIMATION_TICKS_PER_TILE * pressure_factor)
add_power(distance * TURBINE_CHARGE_PER_TILE * pressure_factor * cap.rating)
if (!made_full_rotation)
return
if (!HAS_TRAIT(user, TRAIT_CLUMSY))
return
var/obj/item/bodypart/head/head_to_bash = user.get_bodypart(BODY_ZONE_HEAD)
if (!head_to_bash)
return
user.Paralyze(5)
user.Knockdown(10)
user.visible_message(span_danger("[user] gets whacked in the head by the [src]'s spinning blades!"), span_userdanger("You get hit in the head by the [src] and fall over!"))
// imaginary friends call sleep in their emotes
// even though imaginary friends can't put on a wind turbine I still have to do this
INVOKE_ASYNC(user, TYPE_PROC_REF(/mob/living/, emote), "scream")
user.apply_damage(5, BRUTE, head_to_bash, attacking_item=src)
playsound(source = src, soundin = 'sound/items/weapons/smash.ogg', vol = src.get_clamped_volume(), vary = TRUE)
/obj/item/portable_wind_turbine/wrench_act(mob/living/user, obj/item/tool)
. = NONE
switch(default_unfasten_wrench(user, tool, 4 SECONDS))
if(SUCCESSFUL_UNFASTEN)
return ITEM_INTERACT_SUCCESS
if(FAILED_UNFASTEN)
return ITEM_INTERACT_BLOCKING
return .
/obj/item/portable_wind_turbine/examine(mob/user)
. = ..()
if(!in_range(user, src) && !issilicon(user) && !isobserver(user))
. += span_warning("You're too far away to examine [src]'s contents! You can still watch it spin so wonderfully though...")
if (charging && istype(charging,/obj/item/melee/baton/security/))
. += span_info("You can see the [charging] hanging precariously off the charging port...")
return
if(cap)
. += span_info("Click it with a screwdriver to eject the [cap].")
. += span_info("Wrench it on a tile to anchor it and harness space wind.")
. += span_info("The wind turbine is currently storing [floor(available_power / 100) / 10]kJ.")
if(charging)
. += {"[span_notice("\The [src] contains:")]
[span_notice("- \A [charging].")]"}
/obj/item/portable_wind_turbine/screwdriver_act(mob/living/user, obj/item/tool)
. = ..()
if(charging)
user.balloon_alert(user, "remove the [charging] first!")
return FALSE
if(cap)
tool.play_tool_sound(src, 50)
user.balloon_alert(user, "capacitor removed")
cap.forceMove(drop_location())
available_power = 0
cap = null
return TRUE
else
user.balloon_alert(user, "no capacitor!")
return FALSE
/obj/item/portable_wind_turbine/Entered(atom/movable/arrived, atom/old_loc, list/atom/old_locs)
if(is_type_in_typecache(arrived, allowed_devices))
charging = arrived
START_PROCESSING(SSmachines, src)
using_power = TRUE
update_appearance()
return ..()
/obj/item/portable_wind_turbine/Exited(atom/movable/gone, direction)
if(gone == charging)
if(!QDELING(charging))
charging.update_appearance()
charging = null
using_power = FALSE
update_appearance()
return ..()
/obj/item/portable_wind_turbine/item_interaction(mob/living/user, obj/item/tool, list/modifiers)
if(istype(tool, /obj/item/stock_parts/capacitor))
if (cap)
balloon_alert(user, "already has a capacitor!")
return ITEM_INTERACT_BLOCKING
user.transferItemToLoc(tool, src)
cap = tool
balloon_alert(user, "inserted the [tool]")
return ITEM_INTERACT_SUCCESS
if(!is_type_in_typecache(tool, allowed_devices))
return NONE
if(isnull(cap))
balloon_alert(user, "no capacitor inserted!")
return ITEM_INTERACT_BLOCKING
if(charging)
balloon_alert(user, "already charging something!")
return ITEM_INTERACT_BLOCKING
if(istype(tool, /obj/item/gun/energy))
var/obj/item/gun/energy/energy_gun = tool
if(!energy_gun.can_charge)
balloon_alert(user, "not rechargable!")
return ITEM_INTERACT_BLOCKING
user.transferItemToLoc(tool, src)
charging = tool
return ITEM_INTERACT_SUCCESS
/obj/item/portable_wind_turbine/attack_hand(mob/user, list/modifiers)
if(loc == user || (istype(loc, /turf) && !isnull(charging)))
take_charging_out(user)
return TRUE
add_fingerprint(user)
return ..()
/obj/item/portable_wind_turbine/handle_deconstruct(dissassembled)
charging?.forceMove(drop_location())
cap?.forceMove(drop_location())
return ..()
///Takes charging item out if there is one
/obj/item/portable_wind_turbine/proc/take_charging_out(mob/user)
if(isnull(charging) || user.put_in_hands(charging))
return
charging.forceMove(drop_location())
update_appearance()
/obj/item/portable_wind_turbine/attack_tk(mob/user)
if(isnull(charging))
return
charging.forceMove(drop_location())
return COMPONENT_CANCEL_ATTACK_CHAIN
/obj/item/portable_wind_turbine/process(seconds_per_tick)
using_power = FALSE
if(isnull(charging))
return PROCESS_KILL
if(isnull(cap))
// this is easier than starting/stopping the process based on capacitor state
// this code only charges the attached item, so stopping it early should not be a problem
return
var/obj/item/stock_parts/power_store/cell/charging_cell = charging.get_cell()
if (!charging_cell && istype(charging, /obj/item/stock_parts/power_store/cell))
charging_cell = charging
if (!charging_cell)
var/datum/component/shell/shell = charging.GetComponent(/datum/component/shell)
charging_cell = shell?.attached_circuit?.get_cell()
if(charging_cell)
var/wanted_power = min(charging_cell.maxcharge - charging_cell.charge, charging_cell.chargerate)
if(wanted_power > 0)
var/power_to_give = min(available_power, wanted_power) * seconds_per_tick / 2
if (power_to_give > 0)
charging_cell.give(power_to_give)
available_power -= power_to_give
if(charging_cell.charge == charging_cell.maxcharge)
playsound(src, 'sound/machines/ping.ogg', 30, TRUE)
say("[charging] has finished recharging!")
else
using_power = TRUE
update_appearance()
/obj/item/portable_wind_turbine/emp_act(severity)
. = ..()
if (. & EMP_PROTECT_CONTENTS)
return
if(istype(charging, /obj/item/gun/energy))
var/obj/item/gun/energy/energy_gun = charging
energy_gun?.cell.emp_act(severity)
else if(istype(charging, /obj/item/melee/baton/security))
var/obj/item/melee/baton/security/batong = charging
batong?.cell.charge = 0
/obj/item/portable_wind_turbine/update_overlays()
. = ..()
var/mutable_appearance/rotor = mutable_appearance(worn_icon, "rotor_[floor(rotor_tick)]")
rotor.pixel_y -= 8
. += rotor
if (isnull(charging))
return
if (istype(charging, /obj/item/melee/baton/security/))
. += mutable_appearance(icon, "baton")
/obj/item/portable_wind_turbine/worn_overlays(mutable_appearance/standing, isinhands, icon_file, bodyshape = NONE)
. = ..()
if (isinhands)
return
var/mutable_appearance/rotor = mutable_appearance(worn_icon, "rotor_[floor(rotor_tick)]")
rotor.layer = BELOW_MOB_LAYER
if (ishuman(loc))
var/mob/living/carbon/human/user = loc
if (user.dir & NORTH)
var/mutable_appearance/just_render_above_other_things = mutable_appearance(worn_icon, worn_icon_state)
just_render_above_other_things.layer = ABOVE_MOB_LAYER
rotor.layer = ABOVE_MOB_LAYER
. += just_render_above_other_things
. += rotor
if (isnull(charging))
return
if (istype(charging, /obj/item/melee/baton/security/))
var/mutable_appearance/baton_overlay = mutable_appearance(icon, "baton")
. += baton_overlay
#undef TURBINE_MAX_STORED_POWER
#undef TURBINE_ANIMATION_TICKS_PER_TILE
#undef TURBINE_ANIMATION_TICKS
#undef TURBINE_CHARGE_PER_TILE
#undef TURBINE_ANCHORED_POWER_PER_KPA
#undef TURBINE_MIN_SECONDS_BETWEEN_SOUNDS