#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/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) 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) . = ..() 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