mirror of
https://github.com/Bubberstation/Bubberstation.git
synced 2025-12-10 01:34:01 +00:00
## About The Pull Request Adds portable wind turbines that can be put in your back slot or anchored to the ground. They accept a cell-powered item and charge it while you walk, or when space wind passes over them. Can be purchased for 400 credits or crafted with 3 kitchen knives, plastic, and servos. Requires a capacitor to charge things, and higher tiers charge faster, faster walkspeed also charges faster. <img width="592" height="644" alt="im222age" src="https://github.com/user-attachments/assets/e9997536-5ee0-4417-a31c-cb58666d4d07" /> https://github.com/user-attachments/assets/1cf7fce5-d385-4e3e-be97-fb15e253c308 ## Why It's Good For The Game Sometimes you don't have a cell charger. And you need to charge something. Now you can charge something by running laps around the station. During a blob, rechargers are brought to the front lines to charge energy guns and such but what if the blob turns off the power? And what are bar-rp'ers to do? Kill two birds with one stone by having them run laps instead of sitting around doing nothing. Also its funny. ## Changelog 🆑 add: Added a portable wind turbine which can charge things when you walk around add: Added a signal that procs when an object resists space wind (from being anchored / pulled) sound: added woosh.ogg as a low "wooshing" noise image: added a wind turbine sprite /🆑 --------- Co-authored-by: tattle <66640614+dragomagol@users.noreply.github.com>
377 lines
13 KiB
Plaintext
377 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
|
|
///What item is being charged currently?
|
|
var/obj/item/charging = null
|
|
///Did we put power into "charging" last process()?
|
|
var/using_power = FALSE
|
|
///Did we finish recharging the currently inserted item?
|
|
var/finished_recharging = 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)
|
|
finished_recharging = FALSE
|
|
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/attackby(obj/item/attacking_item, mob/user, params)
|
|
if(istype(attacking_item, /obj/item/stock_parts/capacitor))
|
|
if (cap)
|
|
balloon_alert(user, "already has a capacitor!")
|
|
return TRUE
|
|
user.transferItemToLoc(attacking_item, src)
|
|
cap = attacking_item
|
|
balloon_alert(user, "inserted the [attacking_item]")
|
|
return TRUE
|
|
if(!is_type_in_typecache(attacking_item, allowed_devices))
|
|
return ..()
|
|
if(isnull(cap))
|
|
balloon_alert(user, "no capacitor inserted!")
|
|
return TRUE
|
|
if(charging)
|
|
balloon_alert(user, "already charging something!")
|
|
return TRUE
|
|
if(istype(attacking_item, /obj/item/gun/energy))
|
|
var/obj/item/gun/energy/energy_gun = attacking_item
|
|
if(!energy_gun.can_charge)
|
|
balloon_alert(user, "not rechargable!")
|
|
return TRUE
|
|
user.transferItemToLoc(attacking_item, src)
|
|
charging = attacking_item
|
|
return TRUE
|
|
|
|
/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)
|
|
using_power = TRUE
|
|
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
|
|
update_appearance()
|
|
|
|
if(!using_power && !finished_recharging) //Inserted thing is at max charge/ammo, notify those around us
|
|
finished_recharging = TRUE
|
|
playsound(src, 'sound/machines/ping.ogg', 30, TRUE)
|
|
say("[charging] has finished recharging!")
|
|
|
|
/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)
|
|
. = ..()
|
|
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
|