//This file contains all Lavaland megafauna loot. Does not include crusher trophies. //Hierophant: Hierophant Club #define HIEROPHANT_BLINK_RANGE 5 #define HIEROPHANT_BLINK_COOLDOWN (15 SECONDS) /datum/action/innate/dash/hierophant current_charges = 1 max_charges = 1 charge_rate = HIEROPHANT_BLINK_COOLDOWN recharge_sound = null phasein = /obj/effect/temp_visual/hierophant/blast/visual phaseout = /obj/effect/temp_visual/hierophant/blast/visual // It's a simple purple beam, works well enough for the purple hiero effects. beam_effect = "plasmabeam" /datum/action/innate/dash/hierophant/teleport(mob/user, atom/target) var/dist = get_dist(user, target) if(dist > HIEROPHANT_BLINK_RANGE) user.balloon_alert(user, "destination out of range!") return FALSE var/turf/target_turf = get_turf(target) if(target_turf.is_blocked_turf_ignore_climbable()) user.balloon_alert(user, "destination blocked!") return FALSE . = ..() var/obj/item/hierophant_club/club = target if(!istype(club)) return club.update_appearance(UPDATE_ICON_STATE) /datum/action/innate/dash/hierophant/charge() . = ..() var/obj/item/hierophant_club/club = target if(!istype(club)) return club.update_appearance(UPDATE_ICON_STATE) /obj/item/hierophant_club name = "hierophant club" desc = "The strange technology of this large club allows various nigh-magical teleportation feats. It used to beat you, but now you can set the beat." icon_state = "hierophant_club_ready_beacon" inhand_icon_state = "hierophant_club_ready_beacon" icon_angle = -135 icon = 'icons/obj/mining_zones/artefacts.dmi' 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_BACK w_class = WEIGHT_CLASS_NORMAL force = 15 attack_verb_continuous = list("clubs", "beats", "pummels") attack_verb_simple = list("club", "beat", "pummel") hitsound = 'sound/items/weapons/sonic_jackhammer.ogg' resistance_flags = LAVA_PROOF | FIRE_PROOF | ACID_PROOF actions_types = list(/datum/action/item_action/vortex_recall) action_slots = ALL /// Linked teleport beacon for the group teleport functionality. var/obj/effect/hierophant/beacon /// TRUE if currently doing a teleport to the beacon, FALSE otherwise. var/teleporting = FALSE //if we ARE teleporting /// Action enabling the blink-dash functionality. var/datum/action/innate/dash/hierophant/blink /// Whether the blink ability is activated. IF TRUE, left clicking a location will blink to it. If FALSE, this is disabled. var/blink_activated = TRUE /obj/item/hierophant_club/Initialize(mapload) . = ..() AddElement(/datum/element/update_icon_updates_onmob) blink = new(src) /obj/item/hierophant_club/Destroy() QDEL_NULL(blink) return ..() /obj/item/hierophant_club/examine(mob/user) . = ..() . += span_hierophant_warning("The[beacon ? " beacon is not currently":"re is a beacon"] attached.") /obj/item/hierophant_club/suicide_act(mob/living/user) say("Xverwpsgexmrk...", forced = "hierophant club suicide") user.visible_message(span_suicide("[user] holds [src] into the air! It looks like [user.p_theyre()] trying to commit suicide!")) new/obj/effect/temp_visual/hierophant/telegraph(get_turf(user)) playsound(user,'sound/machines/airlock/airlockopen.ogg', 75, TRUE) user.visible_message(span_hierophant_warning("[user] fades out, leaving [user.p_their()] belongings behind!")) for(var/obj/item/user_item in user) if(user_item != src) user.dropItemToGround(user_item) for(var/turf/blast_turf in RANGE_TURFS(1, user)) new /obj/effect/temp_visual/hierophant/blast/visual(blast_turf, user, TRUE) user.dropItemToGround(src) //Drop us last, so it goes on top of their stuff qdel(user) /obj/item/hierophant_club/attack_self(mob/user) blink_activated = !blink_activated to_chat(user, span_notice("You [blink_activated ? "enable" : "disable"] the blink function on [src].")) /obj/item/hierophant_club/interact_with_atom(atom/interacting_with, mob/living/user, list/modifiers) // If our target is the beacon and the hierostaff is next to the beacon, we're trying to pick it up. if(interacting_with == beacon) return NONE if(blink_activated) blink.teleport(user, interacting_with) return ITEM_INTERACT_SUCCESS return NONE /obj/item/hierophant_club/ranged_interact_with_atom(atom/interacting_with, mob/living/user, list/modifiers) if(blink_activated) blink.teleport(user, interacting_with) return ITEM_INTERACT_SUCCESS return NONE /obj/item/hierophant_club/update_icon_state() icon_state = inhand_icon_state = "hierophant_club[blink?.current_charges > 0 ? "_ready":""][(!QDELETED(beacon)) ? "":"_beacon"]" return ..() /obj/item/hierophant_club/ui_action_click(mob/user, action) if(!user.is_holding(src)) //you need to hold the staff to teleport to_chat(user, span_warning("You need to hold the club in your hands to [beacon ? "teleport with it":"detach the beacon"]!")) return if(!beacon || QDELETED(beacon)) if(isturf(user.loc)) user.visible_message(span_hierophant_warning("[user] starts fiddling with [src]'s pommel..."), \ span_notice("You start detaching the hierophant beacon...")) if(do_after(user, 5 SECONDS, target = user) && !beacon) var/turf/user_turf = get_turf(user) playsound(user_turf,'sound/effects/magic/blind.ogg', 200, TRUE, -4) new /obj/effect/temp_visual/hierophant/telegraph/teleport(user_turf, user) beacon = new/obj/effect/hierophant(user_turf) user.update_mob_action_buttons() user.visible_message(span_hierophant_warning("[user] places a strange machine beneath [user.p_their()] feet!"), \ "[span_hierophant("You detach the hierophant beacon, allowing you to teleport yourself and any allies to it at any time!")]\n\ [span_notice("You can remove the beacon to place it again by striking it with the club.")]") update_appearance(UPDATE_ICON_STATE) else to_chat(user, span_warning("You need to be on solid ground to detach the beacon!")) return if(get_dist(user, beacon) <= 2) //beacon too close abort to_chat(user, span_warning("You are too close to the beacon to teleport to it!")) return var/turf/beacon_turf = get_turf(beacon) if(beacon_turf?.is_blocked_turf(TRUE)) to_chat(user, span_warning("The beacon is blocked by something, preventing teleportation!")) return if(!isturf(user.loc)) to_chat(user, span_warning("You don't have enough space to teleport from here!")) return teleporting = TRUE //start channel user.update_mob_action_buttons() user.visible_message(span_hierophant_warning("[user] starts to glow faintly...")) beacon.icon_state = "hierophant_tele_on" var/obj/effect/temp_visual/hierophant/telegraph/edge/user_telegraph = new /obj/effect/temp_visual/hierophant/telegraph/edge(user.loc) var/obj/effect/temp_visual/hierophant/telegraph/edge/beacon_telegraph = new /obj/effect/temp_visual/hierophant/telegraph/edge(beacon.loc) if(do_after(user, 4 SECONDS, target = user) && user && beacon) var/turf/destination = get_turf(beacon) var/turf/source = get_turf(user) if(destination.is_blocked_turf(TRUE)) teleporting = FALSE to_chat(user, span_warning("The beacon is blocked by something, preventing teleportation!")) user.update_mob_action_buttons() beacon.icon_state = "hierophant_tele_off" return new /obj/effect/temp_visual/hierophant/telegraph(destination, user) new /obj/effect/temp_visual/hierophant/telegraph(source, user) playsound(destination,'sound/effects/magic/wand_teleport.ogg', 200, TRUE) playsound(source,'sound/machines/airlock/airlockopen.ogg', 200, TRUE) if(!do_after(user, 0.3 SECONDS, target = user) || !user || !beacon || QDELETED(beacon)) //no walking away shitlord teleporting = FALSE if(user) user.update_mob_action_buttons() if(beacon) beacon.icon_state = "hierophant_tele_off" return if(destination.is_blocked_turf(TRUE)) teleporting = FALSE to_chat(user, span_warning("The beacon is blocked by something, preventing teleportation!")) user.update_mob_action_buttons() beacon.icon_state = "hierophant_tele_off" return user.log_message("teleported self from [AREACOORD(source)] to [beacon].", LOG_GAME) new /obj/effect/temp_visual/hierophant/telegraph/teleport(destination, user) new /obj/effect/temp_visual/hierophant/telegraph/teleport(source, user) for(var/turf_near_beacon in RANGE_TURFS(1, destination)) new /obj/effect/temp_visual/hierophant/blast/visual(turf_near_beacon, user, TRUE) for(var/turf_near_source in RANGE_TURFS(1, source)) new /obj/effect/temp_visual/hierophant/blast/visual(turf_near_source, user, TRUE) for(var/mob/living/mob_to_teleport in range(1, source)) INVOKE_ASYNC(src, PROC_REF(teleport_mob), source, mob_to_teleport, destination, user) sleep(0.6 SECONDS) //at this point the blasts detonate if(beacon) beacon.icon_state = "hierophant_tele_off" else qdel(user_telegraph) qdel(beacon_telegraph) if(beacon) beacon.icon_state = "hierophant_tele_off" teleporting = FALSE if(user) user.update_mob_action_buttons() /obj/item/hierophant_club/proc/teleport_mob(turf/source, mob/teleporting, turf/target, mob/user) var/turf/turf_to_teleport_to = get_step(target, get_dir(source, teleporting)) //get position relative to caster if(!turf_to_teleport_to || turf_to_teleport_to.is_blocked_turf(TRUE)) return animate(teleporting, alpha = 0, time = 2, easing = EASE_OUT) //fade out sleep(0.1 SECONDS) if(!teleporting) return teleporting.visible_message(span_hierophant_warning("[teleporting] fades out!")) sleep(0.2 SECONDS) if(!teleporting) return var/success = do_teleport(teleporting, turf_to_teleport_to, no_effects = TRUE, channel = TELEPORT_CHANNEL_MAGIC) sleep(0.1 SECONDS) if(!teleporting) return animate(teleporting, alpha = 255, time = 2, easing = EASE_IN) //fade IN sleep(0.1 SECONDS) if(!teleporting) return teleporting.visible_message(span_hierophant_warning("[teleporting] fades in!")) if(user != teleporting && success) log_combat(user, teleporting, "teleported", null, "from [AREACOORD(source)]") /obj/item/hierophant_club/equipped(mob/user) . = ..() blink.Grant(user, src) user.update_icons() /obj/item/hierophant_club/dropped(mob/user) . = ..() blink.Remove(user) user.update_icons() #undef HIEROPHANT_BLINK_RANGE #undef HIEROPHANT_BLINK_COOLDOWN //Bubblegum: Mayhem in a Bottle, H.E.C.K. Suit, Soulscythe /obj/item/mayhem name = "mayhem in a bottle" desc = "A magically infused bottle of blood, the scent of which will drive anyone nearby into a murderous frenzy." icon = 'icons/obj/mining_zones/artefacts.dmi' icon_state = "vial" /obj/item/mayhem/attack_self(mob/user) if(tgui_alert(user, "Breaking the bottle will cause nearby crewmembers to go into a murderous frenzy. Be sure you know what you are doing...","Break the bottle?",list("Break it!","DON'T")) != "Break it!") return if(QDELETED(src) || !user.is_holding(src) || user.incapacitated) return for(var/mob/living/carbon/human/target in range(7,user)) target.apply_status_effect(/datum/status_effect/mayhem) to_chat(user, span_notice("You shatter the bottle!")) playsound(user.loc, 'sound/effects/glass/glassbr1.ogg', 100, TRUE) message_admins(span_adminnotice("[ADMIN_LOOKUPFLW(user)] has activated a bottle of mayhem!")) user.log_message("activated a bottle of mayhem", LOG_ATTACK) qdel(src) /obj/item/clothing/suit/hooded/hostile_environment name = "H.E.C.K. suit" desc = "Hostile Environment Cross-Kinetic Suit: A suit designed to withstand the wide variety of hazards from Lavaland. It wasn't enough for its last owner." icon_state = "hostile_env" hoodtype = /obj/item/clothing/head/hooded/hostile_environment armor_type = /datum/armor/hooded_hostile_environment cold_protection = CHEST|GROIN|LEGS|FEET|ARMS|HANDS min_cold_protection_temperature = FIRE_SUIT_MIN_TEMP_PROTECT heat_protection = CHEST|GROIN|LEGS|FEET|ARMS|HANDS max_heat_protection_temperature = FIRE_IMMUNITY_MAX_TEMP_PROTECT body_parts_covered = CHEST|GROIN|LEGS|FEET|ARMS|HANDS clothing_flags = THICKMATERIAL|HEADINTERNALS resistance_flags = FIRE_PROOF|LAVA_PROOF|ACID_PROOF transparent_protection = HIDESUITSTORAGE|HIDEJUMPSUIT allowed = null greyscale_colors = "#4d4d4d#808080" greyscale_config = /datum/greyscale_config/heck_suit greyscale_config_worn = /datum/greyscale_config/heck_suit/worn greyscale_config_worn_digi = /datum/greyscale_config/heck_suit/worn/digi //SKYRAT EDIT ADDITION - DigiGreyscale flags_1 = IS_PLAYER_COLORABLE_1 /datum/armor/hooded_hostile_environment melee = 70 bullet = 40 laser = 10 energy = 20 bomb = 50 fire = 100 acid = 100 /obj/item/clothing/suit/hooded/hostile_environment/Initialize(mapload) . = ..() AddElement(/datum/element/radiation_protected_clothing) AddElement(/datum/element/gags_recolorable) allowed = GLOB.mining_suit_allowed /obj/item/clothing/suit/hooded/hostile_environment/process(seconds_per_tick) var/mob/living/carbon/wearer = loc if(istype(wearer) && SPT_PROB(1, seconds_per_tick)) //cursed by bubblegum if(prob(7.5)) wearer.cause_hallucination(/datum/hallucination/oh_yeah, "H.E.C.K suit", haunt_them = TRUE) else if(HAS_TRAIT(wearer, TRAIT_ANOSMIA)) //Anosmia quirk holder cannot fell any smell to_chat(wearer, span_warning("[pick("You hear faint whispers.","You feel hot.","You hear a roar in the distance.")]")) else to_chat(wearer, span_warning("[pick("You hear faint whispers.","You smell ash.","You feel hot.","You hear a roar in the distance.")]")) /obj/item/clothing/head/hooded/hostile_environment name = "H.E.C.K. helmet" icon = 'icons/obj/clothing/head/helmet.dmi' worn_icon = 'icons/mob/clothing/head/helmet.dmi' desc = "Hostile Environment Cross-Kinetic Helmet: A helmet designed to withstand the wide variety of hazards from Lavaland. It wasn't enough for its last owner." icon_state = "hostile_env" w_class = WEIGHT_CLASS_NORMAL armor_type = /datum/armor/hooded_hostile_environment cold_protection = HEAD min_cold_protection_temperature = FIRE_SUIT_MIN_TEMP_PROTECT heat_protection = HEAD max_heat_protection_temperature = FIRE_IMMUNITY_MAX_TEMP_PROTECT clothing_flags = SNUG_FIT|THICKMATERIAL resistance_flags = FIRE_PROOF|LAVA_PROOF|ACID_PROOF flags_inv = HIDEMASK|HIDEEARS|HIDEFACE|HIDEEYES|HIDEHAIR|HIDEFACIALHAIR|HIDESNOUT flags_cover = HEADCOVERSMOUTH actions_types = list() greyscale_colors = "#4d4d4d#808080#ff3300" greyscale_config = /datum/greyscale_config/heck_helmet greyscale_config_worn = /datum/greyscale_config/heck_helmet/worn greyscale_config_worn_digi = /datum/greyscale_config/heck_helmet/worn/snouted //SKYRAT EDIT ADDITION - MuzzledGreyscale (Why does this use worn_digi) flags_1 = IS_PLAYER_COLORABLE_1 /obj/item/clothing/head/hooded/hostile_environment/Initialize(mapload) . = ..() update_appearance() AddComponent(/datum/component/butchering/wearable, \ speed = 0.5 SECONDS, \ effectiveness = 150, \ bonus_modifier = 0, \ butcher_sound = null, \ disabled = null, \ can_be_blunt = TRUE, \ butcher_callback = CALLBACK(src, PROC_REF(consume)), \ ) AddElement(/datum/element/radiation_protected_clothing) AddElement(/datum/element/gags_recolorable) /obj/item/clothing/head/hooded/hostile_environment/equipped(mob/user, slot, initial = FALSE) . = ..() to_chat(user, span_notice("You feel a bloodlust. You can now butcher corpses with your bare arms.")) /obj/item/clothing/head/hooded/hostile_environment/dropped(mob/user, silent = FALSE) . = ..() to_chat(user, span_notice("You lose your bloodlust.")) /obj/item/clothing/head/hooded/hostile_environment/proc/consume(mob/living/user, mob/living/butchered) if(butchered.mob_biotypes & (MOB_ROBOTIC | MOB_SPIRIT)) return var/health_consumed = butchered.maxHealth * 0.1 user.heal_ordered_damage(health_consumed, list(BRUTE, BURN, TOX)) to_chat(user, span_notice("You heal from the corpse of [butchered].")) var/datum/client_colour/color = user.add_client_colour(/datum/client_colour/bloodlust) QDEL_IN(color, 1 SECONDS) #define MAX_BLOOD_LEVEL 100 /obj/item/soulscythe name = "soulscythe" desc = "An old relic of hell created by devils to establish themselves as the leadership of hell over the demons. It grows stronger while it possesses a powerful soul." icon = 'icons/obj/mining_zones/artefacts.dmi' icon_state = "soulscythe" inhand_icon_state = "soulscythe" lefthand_file = 'icons/mob/inhands/64x64_lefthand.dmi' righthand_file = 'icons/mob/inhands/64x64_righthand.dmi' attack_verb_continuous = list("chops", "slices", "cuts", "reaps") attack_verb_simple = list("chop", "slice", "cut", "reap") hitsound = 'sound/items/weapons/bladeslice.ogg' inhand_x_dimension = 64 inhand_y_dimension = 64 force = 20 throwforce = 17 armour_penetration = 50 sharpness = SHARP_EDGED bare_wound_bonus = 10 layer = MOB_LAYER resistance_flags = LAVA_PROOF | FIRE_PROOF | ACID_PROOF /// Soulscythe mob in the scythe var/mob/living/simple_animal/soulscythe/soul /// Are we grabbing a spirit? var/using = FALSE /// Currently charging? var/charging = FALSE /// Cooldown between moves COOLDOWN_DECLARE(move_cooldown) /// Cooldown between attacks COOLDOWN_DECLARE(attack_cooldown) /obj/item/soulscythe/Initialize(mapload) . = ..() soul = new(src) RegisterSignal(soul, COMSIG_LIVING_RESIST, PROC_REF(on_resist)) RegisterSignal(soul, COMSIG_MOB_ATTACK_RANGED, PROC_REF(on_attack)) RegisterSignal(soul, COMSIG_MOB_ATTACK_RANGED_SECONDARY, PROC_REF(on_secondary_attack)) RegisterSignal(src, COMSIG_ATOM_INTEGRITY_CHANGED, PROC_REF(on_integrity_change)) AddElement(/datum/element/bane, mob_biotypes = MOB_PLANT, damage_multiplier = 0.5, requires_combat_mode = FALSE) /obj/item/soulscythe/examine(mob/user) . = ..() . += soul.ckey ? span_nicegreen("There is a soul inhabiting it.") : span_danger("It's dormant.") /obj/item/soulscythe/attack(mob/living/attacked, mob/living/user, params) . = ..() if(attacked.stat != DEAD) give_blood(10) /obj/item/soulscythe/attack_hand(mob/user, list/modifiers) if(soul.ckey && !soul.faction_check_atom(user)) to_chat(user, span_warning("You can't pick up [src]!")) return return ..() /obj/item/soulscythe/pickup(mob/user) . = ..() if(soul.ckey) animate(src) //stop spinnage /obj/item/soulscythe/dropped(mob/user, silent) . = ..() if(soul.ckey) reset_spin() //resume spinnage /obj/item/soulscythe/attack_self(mob/user, modifiers) if(using || soul.ckey || soul.stat) return if(!(GLOB.ghost_role_flags & GHOSTROLE_STATION_SENTIENCE)) balloon_alert(user, "you can't awaken the scythe!") return using = TRUE balloon_alert(user, "you hold the scythe up...") ADD_TRAIT(src, TRAIT_NODROP, type) var/mob/chosen_one = SSpolling.poll_ghosts_for_target( check_jobban = ROLE_PAI, poll_time = 20 SECONDS, checked_target = src, ignore_category = POLL_IGNORE_POSSESSED_BLADE, alert_pic = src, role_name_text = "soulscythe soul", chat_text_border_icon = src, ) on_poll_concluded(user, chosen_one) /// Ghost poll has concluded and a candidate has been chosen. /obj/item/soulscythe/proc/on_poll_concluded(mob/living/master, mob/dead/observer/ghost) if(isnull(ghost)) balloon_alert(master, "the scythe is dormant!") REMOVE_TRAIT(src, TRAIT_NODROP, type) using = FALSE return soul.PossessByPlayer(ghost.ckey) soul.copy_languages(master, LANGUAGE_MASTER) //Make sure the sword can understand and communicate with the master. soul.faction = list("[REF(master)]") balloon_alert(master, "the scythe glows") add_overlay("soulscythe_gem") density = TRUE if(!ismob(loc)) reset_spin() REMOVE_TRAIT(src, TRAIT_NODROP, type) using = FALSE /obj/item/soulscythe/relaymove(mob/living/user, direction) if(!COOLDOWN_FINISHED(src, move_cooldown) || charging) return if(!isturf(loc)) balloon_alert(user, "resist out!") COOLDOWN_START(src, move_cooldown, 1 SECONDS) return if(!use_blood(1, FALSE)) return if(pixel_x != base_pixel_x || pixel_y != base_pixel_y) animate(src, 0.2 SECONDS, pixel_x = base_pixel_y, pixel_y = base_pixel_y, flags = ANIMATION_PARALLEL) try_step_multiz(direction) COOLDOWN_START(src, move_cooldown, (direction in GLOB.cardinals) ? 0.1 SECONDS : 0.2 SECONDS) /obj/item/soulscythe/throw_impact(atom/hit_atom, datum/thrownthing/throwingdatum) . = ..() if(!charging) return charging = FALSE throwforce *= 0.5 reset_spin() if(ismineralturf(hit_atom)) var/turf/closed/mineral/hit_rock = hit_atom hit_rock.gets_drilled() if(isliving(hit_atom)) var/mob/living/hit_mob = hit_atom if(hit_mob.stat != DEAD) give_blood(15) /obj/item/soulscythe/AllowClick() return TRUE /obj/item/soulscythe/proc/use_blood(amount = 0, message = TRUE) if(amount > soul.blood_level) if(message) to_chat(soul, span_warning("Not enough blood!")) return FALSE soul.blood_level -= amount return TRUE /obj/item/soulscythe/proc/give_blood(amount) soul.blood_level = min(MAX_BLOOD_LEVEL, soul.blood_level + amount) /obj/item/soulscythe/proc/on_resist(mob/living/user) SIGNAL_HANDLER if(isturf(loc)) return INVOKE_ASYNC(src, PROC_REF(break_out)) /obj/item/soulscythe/proc/break_out() if(!use_blood(10)) return balloon_alert(soul, "you resist...") if(!do_after(soul, 5 SECONDS, target = src, timed_action_flags = IGNORE_TARGET_LOC_CHANGE)) balloon_alert(soul, "interrupted!") return balloon_alert(soul, "you break out") if(ismob(loc)) var/mob/holder = loc holder.temporarilyRemoveItemFromInventory(src) forceMove(drop_location()) /obj/item/soulscythe/proc/on_integrity_change(datum/source, old_value, new_value) SIGNAL_HANDLER soul.set_health(new_value) /obj/item/soulscythe/proc/on_attack(mob/living/source, atom/attacked_atom, modifiers) SIGNAL_HANDLER if(!COOLDOWN_FINISHED(src, attack_cooldown) || !isturf(loc)) return if(get_dist(source, attacked_atom) > 1) INVOKE_ASYNC(src, PROC_REF(shoot_target), attacked_atom) else INVOKE_ASYNC(src, PROC_REF(slash_target), attacked_atom) /obj/item/soulscythe/proc/on_secondary_attack(mob/living/source, atom/attacked_atom, modifiers) SIGNAL_HANDLER if(!COOLDOWN_FINISHED(src, attack_cooldown) || !isturf(loc)) return INVOKE_ASYNC(src, PROC_REF(charge_target), attacked_atom) /obj/item/soulscythe/proc/shoot_target(atom/attacked_atom) if(!use_blood(15)) return COOLDOWN_START(src, attack_cooldown, 3 SECONDS) var/obj/projectile/projectile = new /obj/projectile/soulscythe(get_turf(src)) projectile.aim_projectile(attacked_atom, src) projectile.firer = src projectile.fire(null, attacked_atom) visible_message(span_danger("[src] fires at [attacked_atom]!"), span_notice("You fire at [attacked_atom]!")) playsound(src, 'sound/effects/magic/fireball.ogg', 50, TRUE) /obj/item/soulscythe/proc/slash_target(atom/attacked_atom) if(isliving(attacked_atom) && use_blood(10)) var/mob/living/attacked_mob = attacked_atom if(attacked_mob.stat != DEAD) give_blood(15) attacked_mob.apply_damage(damage = force * (ishostile(attacked_mob) ? 2 : 1), sharpness = SHARP_EDGED, bare_wound_bonus = 5) to_chat(attacked_mob, span_userdanger("You're slashed by [src]!")) else if((ismachinery(attacked_atom) || isstructure(attacked_atom)) && use_blood(5)) var/obj/attacked_obj = attacked_atom attacked_obj.take_damage(force, BRUTE, MELEE, FALSE) else return COOLDOWN_START(src, attack_cooldown, 1 SECONDS) animate(src) SpinAnimation(5) addtimer(CALLBACK(src, PROC_REF(reset_spin)), 1 SECONDS) visible_message(span_danger("[src] slashes [attacked_atom]!"), span_notice("You slash [attacked_atom]!")) playsound(src, 'sound/items/weapons/bladeslice.ogg', 50, TRUE) do_attack_animation(attacked_atom, ATTACK_EFFECT_SLASH) /obj/item/soulscythe/proc/charge_target(atom/attacked_atom) if(charging || !use_blood(30)) return COOLDOWN_START(src, attack_cooldown, 5 SECONDS) animate(src) charging = TRUE visible_message(span_danger("[src] starts charging...")) balloon_alert(soul, "you start charging...") if(!do_after(soul, 2 SECONDS, target = src, timed_action_flags = IGNORE_TARGET_LOC_CHANGE)) balloon_alert(soul, "interrupted!") return visible_message(span_danger("[src] charges at [attacked_atom]!"), span_notice("You charge at [attacked_atom]!")) new /obj/effect/temp_visual/mook_dust(get_turf(src)) playsound(src, 'sound/items/weapons/thudswoosh.ogg', 50, TRUE) SpinAnimation(1) throwforce *= 2 throw_at(attacked_atom, 10, 3, soul, FALSE) /obj/item/soulscythe/proc/reset_spin() animate(src) SpinAnimation(15) /obj/item/soulscythe/Destroy(force) soul.ghostize() QDEL_NULL(soul) . = ..() /mob/living/simple_animal/soulscythe name = "mysterious spirit" maxHealth = 200 health = 200 gender = NEUTER mob_biotypes = MOB_SPIRIT faction = list() weather_immunities = list(TRAIT_ASHSTORM_IMMUNE, TRAIT_SNOWSTORM_IMMUNE) /// Blood level, used for movement and abilities in a soulscythe var/blood_level = MAX_BLOOD_LEVEL /mob/living/simple_animal/soulscythe/get_status_tab_items() . = ..() . += "Blood: [blood_level]/[MAX_BLOOD_LEVEL]" /mob/living/simple_animal/soulscythe/Life(seconds_per_tick, times_fired) . = ..() if(!stat) blood_level = min(MAX_BLOOD_LEVEL, blood_level + round(1 * seconds_per_tick)) /obj/projectile/soulscythe name = "soulslash" icon_state = "soulslash" armor_flag = MELEE //jokair damage = 15 light_range = 1 light_power = 1 light_color = LIGHT_COLOR_BLOOD_MAGIC /obj/projectile/soulscythe/on_hit(atom/target, blocked = 0, pierce_hit) if(ishostile(target)) damage *= 2 return ..() #undef MAX_BLOOD_LEVEL //Ash Drake: Spectral Blade, Lava Staff, Dragon's Blood /obj/item/melee/ghost_sword name = "\improper spectral blade" desc = "A rusted and dulled blade. It doesn't look like it'd do much damage. It glows weakly." icon = 'icons/obj/weapons/sword.dmi' icon_state = "spectral" inhand_icon_state = "spectral" icon_angle = -45 lefthand_file = 'icons/mob/inhands/weapons/swords_lefthand.dmi' righthand_file = 'icons/mob/inhands/weapons/swords_righthand.dmi' obj_flags = CONDUCTS_ELECTRICITY sharpness = SHARP_EDGED w_class = WEIGHT_CLASS_BULKY force = 1 throwforce = 1 hitsound = 'sound/effects/ghost2.ogg' block_sound = 'sound/items/weapons/parry.ogg' attack_verb_continuous = list("attacks", "slashes", "slices", "tears", "lacerates", "rips", "dices", "rends") attack_verb_simple = list("attack", "slash", "slice", "tear", "lacerate", "rip", "dice", "rend") resistance_flags = LAVA_PROOF | FIRE_PROOF | ACID_PROOF var/summon_cooldown = 0 var/list/mob/dead/observer/spirits var/list/alt_continuous = list("stabs", "pierces", "impales") var/list/alt_simple = list("stab", "pierce", "impale") /obj/item/melee/ghost_sword/Initialize(mapload) . = ..() spirits = list() START_PROCESSING(SSobj, src) SSpoints_of_interest.make_point_of_interest(src) alt_continuous = string_list(alt_continuous) alt_simple = string_list(alt_simple) AddComponent(/datum/component/alternative_sharpness, SHARP_POINTY, alt_continuous, alt_simple) AddComponent(\ /datum/component/butchering, \ speed = 15 SECONDS, \ effectiveness = 90, \ ) /obj/item/melee/ghost_sword/Destroy() for(var/mob/dead/observer/G in spirits) G.RemoveInvisibility(type) spirits.Cut() STOP_PROCESSING(SSobj, src) . = ..() /obj/item/melee/ghost_sword/attack_self(mob/user) if(summon_cooldown > world.time) to_chat(user, span_warning("You just recently called out for aid. You don't want to annoy the spirits!")) return to_chat(user, span_notice("You call out for aid, attempting to summon spirits to your side.")) notify_ghosts( "[user] is raising [user.p_their()] [name], calling for your help!", source = user, ignore_key = POLL_IGNORE_SPECTRAL_BLADE, header = "Spectral blade", ) summon_cooldown = world.time + 600 /obj/item/melee/ghost_sword/process() ghost_check() /obj/item/melee/ghost_sword/proc/ghost_check() var/ghost_counter = 0 var/turf/T = get_turf(src) var/list/contents = T.get_all_contents() var/mob/dead/observer/current_spirits = list() for(var/thing in contents) var/atom/A = thing A.transfer_observers_to(src) for(var/i in orbiters?.orbiter_list) if(!isobserver(i)) continue var/mob/dead/observer/G = i ghost_counter++ G.SetInvisibility(INVISIBILITY_NONE, id=type, priority=INVISIBILITY_PRIORITY_BASIC_ANTI_INVISIBILITY) current_spirits |= G for(var/mob/dead/observer/G in spirits - current_spirits) G.RemoveInvisibility(type) spirits = current_spirits return ghost_counter /obj/item/melee/ghost_sword/attack(mob/living/target, mob/living/carbon/human/user) force = 0 var/ghost_counter = ghost_check() force = clamp((ghost_counter * 4), 0, 75) user.visible_message(span_danger("[user] strikes with the force of [ghost_counter] vengeful spirits!")) ..() /obj/item/melee/ghost_sword/hit_reaction(mob/living/carbon/human/owner, atom/movable/hitby, attack_text = "the attack", final_block_chance = 0, damage = 0, attack_type = MELEE_ATTACK, damage_type = BRUTE) var/ghost_counter = ghost_check() final_block_chance += clamp((ghost_counter * 5), 0, 75) owner.visible_message(span_danger("[owner] is protected by a ring of [ghost_counter] ghosts!")) return ..() /obj/item/dragons_blood name = "bottle of dragons blood" desc = "You're not actually going to drink this, are you?" icon = 'icons/obj/mining_zones/artefacts.dmi' icon_state = "vial" /obj/item/dragons_blood/attack_self(mob/living/carbon/human/user) if(!istype(user)) return var/mob/living/carbon/human/consumer = user var/random = rand(2,4) //SKYRAT EDIT - Commenting out #1 because it makes people invisible. switch(random) if(1) to_chat(user, span_danger("Your appearance morphs to that of a very small humanoid ash dragon! You get to look like a freak without the cool abilities.")) consumer.dna.features = list( "mcolor" = "#A02720", "tail_lizard" = "Dark Tiger", "tail_human" = "None", "snout" = "Sharp", "horns" = "Curled", "ears" = "None", "wings" = "None", "frills" = "None", "spines" = "Long", "lizard_markings" = "Dark Tiger Body", "legs" = DIGITIGRADE_LEGS, ) consumer.eye_color_left = "#FEE5A3" consumer.eye_color_right = "#FEE5A3" consumer.set_species(/datum/species/lizard) if(2) to_chat(user, span_danger("Your flesh begins to melt! Miraculously, you seem fine otherwise.")) consumer.set_species(/datum/species/skeleton) if(3) to_chat(user, span_danger("Power courses through you! You can now shift your form at will.")) var/datum/action/cooldown/spell/shapeshift/dragon/dragon_shapeshift = new(user.mind || user) dragon_shapeshift.Grant(user) if(4) to_chat(user, span_danger("You feel like you could walk straight through lava now.")) ADD_TRAIT(user, TRAIT_LAVA_IMMUNE, type) playsound(user,'sound/items/drink.ogg', 30, TRUE) qdel(src) /obj/item/lava_staff name = "staff of lava" desc = "The ability to fill the emergency shuttle with lava. What more could you want out of life?" icon_state = "lavastaff" inhand_icon_state = "lavastaff" icon_angle = -45 lefthand_file = 'icons/mob/inhands/weapons/staves_lefthand.dmi' righthand_file = 'icons/mob/inhands/weapons/staves_righthand.dmi' icon = 'icons/obj/weapons/guns/magic.dmi' slot_flags = ITEM_SLOT_BACK w_class = WEIGHT_CLASS_NORMAL force = 18 damtype = BURN resistance_flags = LAVA_PROOF | FIRE_PROOF | ACID_PROOF attack_verb_continuous = list("sears", "clubs", "burn") attack_verb_simple = list("sear", "club", "burn") hitsound = 'sound/items/weapons/sear.ogg' var/turf_type = /turf/open/lava/smooth/weak var/transform_string = "lava" var/reset_turf_type = /turf/open/misc/asteroid/basalt var/reset_string = "basalt" var/create_cooldown = 10 SECONDS var/create_delay = 3 SECONDS var/reset_cooldown = 5 SECONDS var/timer = 0 var/static/list/banned_turfs = typecacheof(list(/turf/open/space, /turf/closed)) /obj/item/lava_staff/interact_with_atom(atom/interacting_with, mob/living/user, list/modifiers) if(interacting_with.atom_storage || SHOULD_SKIP_INTERACTION(interacting_with, src, user)) return NONE return ranged_interact_with_atom(interacting_with, user, modifiers) /obj/item/lava_staff/ranged_interact_with_atom(atom/interacting_with, mob/living/user, list/modifiers) if(timer > world.time) return NONE if(is_type_in_typecache(interacting_with, banned_turfs)) return NONE if(!(interacting_with in view(user.client.view, get_turf(user)))) return NONE var/turf/open/T = get_turf(interacting_with) if(!istype(T)) return NONE if(!islava(T)) var/obj/effect/temp_visual/lavastaff/L = new /obj/effect/temp_visual/lavastaff(T) L.alpha = 0 animate(L, alpha = 255, time = create_delay) user.visible_message(span_danger("[user] points [src] at [T]!")) timer = world.time + create_delay + 1 if(do_after(user, create_delay, target = T)) var/old_name = T.name if(T.TerraformTurf(turf_type, flags = CHANGETURF_INHERIT_AIR)) user.visible_message(span_danger("[user] turns \the [old_name] into [transform_string]!")) message_admins("[ADMIN_LOOKUPFLW(user)] fired the lava staff at [ADMIN_VERBOSEJMP(T)]") user.log_message("fired the lava staff at [AREACOORD(T)].", LOG_ATTACK) timer = world.time + create_cooldown playsound(T,'sound/effects/magic/fireball.ogg', 200, TRUE) else timer = world.time qdel(L) else var/old_name = T.name if(T.TerraformTurf(reset_turf_type, flags = CHANGETURF_INHERIT_AIR)) user.visible_message(span_danger("[user] turns \the [old_name] into [reset_string]!")) timer = world.time + reset_cooldown playsound(T,'sound/effects/magic/fireball.ogg', 200, TRUE) return ITEM_INTERACT_SUCCESS /obj/effect/temp_visual/lavastaff icon_state = "lavastaff_warn" duration = 50 /turf/open/lava/smooth/weak lava_damage = 10 lava_firestacks = 10 temperature_damage = 2500 //Blood-Drunk Miner: Cleaving Saw /obj/item/melee/cleaving_saw name = "cleaving saw" desc = "This saw, effective at drawing the blood of beasts, transforms into a long cleaver that makes use of centrifugal force." icon = 'icons/obj/mining_zones/artefacts.dmi' lefthand_file = 'icons/mob/inhands/64x64_lefthand.dmi' righthand_file = 'icons/mob/inhands/64x64_righthand.dmi' icon_state = "cleaving_saw" inhand_icon_state = "cleaving_saw" worn_icon_state = "cleaving_saw" attack_verb_continuous = list("attacks", "saws", "slices", "tears", "lacerates", "rips", "dices", "cuts") attack_verb_simple = list("attack", "saw", "slice", "tear", "lacerate", "rip", "dice", "cut") force = 12 throwforce = 20 inhand_x_dimension = 64 inhand_y_dimension = 64 slot_flags = ITEM_SLOT_BELT hitsound = 'sound/items/weapons/bladeslice.ogg' w_class = WEIGHT_CLASS_BULKY sharpness = SHARP_EDGED /// List of factions we deal bonus damage to var/list/nemesis_factions = list(FACTION_MINING, FACTION_BOSS) /// Amount of damage we deal to the above factions var/faction_bonus_force = 30 /// Whether the cleaver is actively AoE swiping something. var/swiping = FALSE /// Amount of bleed stacks gained per hit var/bleed_stacks_per_hit = 3 /// Force when the saw is opened. var/open_force = 20 /// Throwforce when the saw is opened. var/open_throwforce = 20 /obj/item/melee/cleaving_saw/Initialize(mapload) . = ..() AddComponent( \ /datum/component/transforming, \ transform_cooldown_time = (CLICK_CD_MELEE * 0.25), \ force_on = open_force, \ throwforce_on = open_throwforce, \ sharpness_on = sharpness, \ hitsound_on = hitsound, \ w_class_on = w_class, \ attack_verb_continuous_on = list("cleaves", "swipes", "slashes", "chops"), \ attack_verb_simple_on = list("cleave", "swipe", "slash", "chop"), \ ) RegisterSignal(src, COMSIG_TRANSFORMING_ON_TRANSFORM, PROC_REF(on_transform)) /obj/item/melee/cleaving_saw/examine(mob/user) . = ..() . += span_notice("It is [HAS_TRAIT(src, TRAIT_TRANSFORM_ACTIVE) ? "open, will cleave enemies in a wide arc and deal additional damage to fauna":"closed, and can be used for rapid consecutive attacks that cause fauna to bleed"].") . += span_notice("Both modes will build up existing bleed effects, doing a burst of high damage if the bleed is built up high enough.") . += span_notice("Transforming it immediately after an attack causes the next attack to come out faster.") /obj/item/melee/cleaving_saw/suicide_act(mob/living/user) user.visible_message(span_suicide("[user] is [HAS_TRAIT(src, TRAIT_TRANSFORM_ACTIVE) ? "closing [src] on [user.p_their()] neck" : "opening [src] into [user.p_their()] chest"]! It looks like [user.p_theyre()] trying to commit suicide!")) attack_self(user) return BRUTELOSS /obj/item/melee/cleaving_saw/melee_attack_chain(mob/user, atom/target, params) . = ..() if(!HAS_TRAIT(src, TRAIT_TRANSFORM_ACTIVE)) user.changeNext_move(CLICK_CD_MELEE * 0.5) //when closed, it attacks very rapidly /obj/item/melee/cleaving_saw/attack(mob/living/target, mob/living/carbon/human/user) var/is_open = HAS_TRAIT(src, TRAIT_TRANSFORM_ACTIVE) if(!is_open || swiping || !target.density || get_turf(target) == get_turf(user)) if(!is_open) faction_bonus_force = 0 var/is_nemesis_faction = FALSE for(var/found_faction in target.faction) if(found_faction in nemesis_factions) is_nemesis_faction = TRUE force += faction_bonus_force nemesis_effects(user, target) break . = ..() if(is_nemesis_faction) force -= faction_bonus_force if(!is_open) faction_bonus_force = initial(faction_bonus_force) else var/turf/user_turf = get_turf(user) var/dir_to_target = get_dir(user_turf, get_turf(target)) swiping = TRUE var/static/list/cleaving_saw_cleave_angles = list(0, -45, 45) //so that the animation animates towards the target clicked and not towards a side target for(var/i in cleaving_saw_cleave_angles) var/turf/turf = get_step(user_turf, turn(dir_to_target, i)) for(var/mob/living/living_target in turf) if(user.Adjacent(living_target) && living_target.body_position != LYING_DOWN) melee_attack_chain(user, living_target) swiping = FALSE /* * If we're attacking [target]s in our nemesis list, apply unique effects. * * user - the mob attacking with the saw * target - the mob being attacked */ /obj/item/melee/cleaving_saw/proc/nemesis_effects(mob/living/user, mob/living/target) if(istype(target, /mob/living/simple_animal/hostile/asteroid/elite)) return var/datum/status_effect/stacking/saw_bleed/existing_bleed = target.has_status_effect(/datum/status_effect/stacking/saw_bleed) if(existing_bleed) existing_bleed.add_stacks(bleed_stacks_per_hit) else target.apply_status_effect(/datum/status_effect/stacking/saw_bleed, bleed_stacks_per_hit) /* * Signal proc for [COMSIG_TRANSFORMING_ON_TRANSFORM]. * * Gives feedback and makes the nextmove after transforming much quicker. */ /obj/item/melee/cleaving_saw/proc/on_transform(obj/item/source, mob/user, active) SIGNAL_HANDLER user.changeNext_move(CLICK_CD_MELEE * 0.25) if(user) balloon_alert(user, "[active ? "opened" : "closed"] [src]") playsound(src, 'sound/effects/magic/clockwork/fellowship_armory.ogg', 35, TRUE, frequency = 90000 - (active * 30000)) return COMPONENT_NO_DEFAULT_MESSAGE //Legion: Staff of Storms /obj/item/storm_staff name = "staff of storms" desc = "An ancient staff retrieved from the remains of Legion. The wind stirs as you move it." icon_state = "staffofstorms" inhand_icon_state = "staffofstorms" icon_angle = -45 icon = 'icons/obj/weapons/guns/magic.dmi' lefthand_file = 'icons/mob/inhands/weapons/staves_lefthand.dmi' righthand_file = 'icons/mob/inhands/weapons/staves_righthand.dmi' slot_flags = ITEM_SLOT_BACK w_class = WEIGHT_CLASS_BULKY force = 20 damtype = BURN hitsound = 'sound/items/weapons/taserhit.ogg' wound_bonus = -30 bare_wound_bonus = 20 resistance_flags = LAVA_PROOF | FIRE_PROOF | ACID_PROOF var/max_thunder_charges = 3 var/thunder_charges = 3 var/thunder_charge_time = 15 SECONDS var/static/list/excluded_areas = list(/area/space) var/list/targeted_turfs = list() /obj/item/storm_staff/examine(mob/user) . = ..() . += span_notice("It has [thunder_charges] charges remaining.") . += span_notice("Use it in hand to dispel storms.") . += span_notice("Use it on targets to summon thunderbolts from the sky.") . += span_notice("The thunderbolts are boosted if in an area with weather effects.") /obj/item/storm_staff/attack_self(mob/user) var/area/user_area = get_area(user) var/turf/user_turf = get_turf(user) if(!user_area || !user_turf || (is_type_in_list(user_area, excluded_areas))) to_chat(user, span_warning("Something is preventing you from using the staff here.")) return var/datum/weather/affected_weather for(var/datum/weather/weather as anything in SSweather.processing) if((user_turf.z in weather.impacted_z_levels) && ispath(user_area.type, weather.area_type)) affected_weather = weather break if(!affected_weather) return if(affected_weather.stage == END_STAGE) balloon_alert(user, "already ended!") return if(affected_weather.stage == WIND_DOWN_STAGE) balloon_alert(user, "already ending!") return balloon_alert(user, "you hold the staff up...") if(!do_after(user, 3 SECONDS, target = src)) balloon_alert(user, "interrupted!") return user.visible_message(span_warning("[user] holds [src] skywards as an orange beam travels into the sky!"), \ span_notice("You hold [src] skyward, dispelling the storm!")) playsound(user, 'sound/effects/magic/staff_change.ogg', 200, FALSE) var/old_color = user.color user.color = list(340/255, 240/255, 0,0, 0,0,0,0, 0,0,0,0, 0,0,0,1, 0,0,0,0) var/old_transform = user.transform user.transform *= 1.2 animate(user, color = old_color, transform = old_transform, time = 1 SECONDS) affected_weather.wind_down() user.log_message("has dispelled a storm at [AREACOORD(user_turf)].", LOG_GAME) /obj/item/storm_staff/ranged_interact_with_atom(atom/interacting_with, mob/living/user, list/modifiers) return thunder_blast(interacting_with, user) ? ITEM_INTERACT_SUCCESS : ITEM_INTERACT_BLOCKING /obj/item/storm_staff/afterattack(atom/target, mob/user, click_parameters) thunder_blast(target, user) /obj/item/storm_staff/proc/thunder_blast(atom/target, mob/user) if(!thunder_charges) balloon_alert(user, "needs to charge!") return FALSE var/turf/target_turf = get_turf(target) var/area/target_area = get_area(target) if(!target_turf || !target_area || (is_type_in_list(target_area, excluded_areas))) balloon_alert(user, "can't bolt here!") return FALSE if(target_turf in targeted_turfs) balloon_alert(user, "already targeted!") return FALSE if(HAS_TRAIT(user, TRAIT_PACIFISM)) balloon_alert(user, "you don't want to harm!") return FALSE var/power_boosted = FALSE for(var/datum/weather/weather as anything in SSweather.processing) if(weather.stage != MAIN_STAGE) continue if((target_turf.z in weather.impacted_z_levels) && ispath(target_area.type, weather.area_type)) power_boosted = TRUE break playsound(src, 'sound/effects/magic/lightningshock.ogg', 10, TRUE, extrarange = SILENCED_SOUND_EXTRARANGE, falloff_distance = 0) targeted_turfs += target_turf balloon_alert(user, "you aim at [target_turf]...") new /obj/effect/temp_visual/telegraphing/thunderbolt(target_turf) addtimer(CALLBACK(src, PROC_REF(throw_thunderbolt), target_turf, power_boosted), 1.5 SECONDS) thunder_charges-- addtimer(CALLBACK(src, PROC_REF(recharge)), thunder_charge_time) user.log_message("fired the staff of storms at [AREACOORD(target_turf)].", LOG_ATTACK) return TRUE /obj/item/storm_staff/proc/recharge(mob/user) thunder_charges = min(thunder_charges + 1, max_thunder_charges) playsound(src, 'sound/effects/magic/charge.ogg', 10, TRUE, extrarange = SILENCED_SOUND_EXTRARANGE, falloff_distance = 0) /obj/item/storm_staff/proc/throw_thunderbolt(turf/target, boosted) targeted_turfs -= target new /obj/effect/temp_visual/thunderbolt(target) var/list/affected_turfs = list(target) if(boosted) for(var/direction in GLOB.alldirs) var/turf_to_add = get_step(target, direction) if(!turf_to_add) continue affected_turfs += turf_to_add for(var/turf/turf as anything in affected_turfs) new /obj/effect/temp_visual/electricity(turf) for(var/mob/living/hit_mob in turf) to_chat(hit_mob, span_userdanger("You've been struck by lightning!")) hit_mob.electrocute_act(15 * (isanimal_or_basicmob(hit_mob) ? 3 : 1) * (turf == target ? 2 : 1) * (boosted ? 2 : 1), src, flags = SHOCK_TESLA|SHOCK_NOSTUN) for(var/obj/hit_thing in turf) hit_thing.take_damage(20, BURN, ENERGY, FALSE) playsound(target, 'sound/effects/magic/lightningbolt.ogg', 100, TRUE) target.visible_message(span_danger("A thunderbolt strikes [target]!")) explosion(target, light_impact_range = (boosted ? 1 : 0), flame_range = (boosted ? 2 : 1), silent = TRUE) /datum/action/innate/brain_undeployment name = "Disconnect from shell" desc = "Stop controlling your shell and resume normal core operations." button_icon = 'icons/mob/actions/actions_AI.dmi' button_icon_state = "ai_core" /datum/action/innate/brain_undeployment/Trigger(trigger_flags) if(!..()) return FALSE var/obj/item/organ/brain/cybernetic/ai/shell_to_disconnect = owner.get_organ_by_type(/obj/item/organ/brain/cybernetic/ai) shell_to_disconnect.undeploy() return TRUE /obj/item/organ/brain/cybernetic/ai name = "AI-uplink brain" desc = "Can be inserted into a body with NO ORGANIC INTERNAL ORGANS (robotic organs only) to allow AIs to control it. Comes with its own health sensors beacon. MUST be a humanoid or bad things happen to the consciousness." can_smoothen_out = FALSE /// if connected, our AI var/mob/living/silicon/ai/mainframe /// action for undeployment var/datum/action/innate/brain_undeployment/undeployment_action = new /obj/item/organ/brain/cybernetic/ai/Initialize(mapload) . = ..() AddElement(/datum/element/noticable_organ, "%PRONOUN_Their eyes move with machine precision, their expression completely blank.") /obj/item/organ/brain/cybernetic/ai/Destroy() . = ..() undeploy() mainframe = null QDEL_NULL(undeployment_action) /obj/item/organ/brain/cybernetic/ai/on_mob_insert(mob/living/carbon/brain_owner, special, movement_flags) . = ..() brain_owner.add_traits(list(HUMAN_SENSORS_VISIBLE_WITHOUT_SUIT, TRAIT_NO_MINDSWAP, TRAIT_CORPSELOCKED), REF(src)) update_med_hud_status(brain_owner) RegisterSignal(brain_owner, COMSIG_LIVING_HEALTH_UPDATE, PROC_REF(update_med_hud_status)) RegisterSignal(brain_owner, COMSIG_CLICK, PROC_REF(owner_clicked)) RegisterSignal(brain_owner, COMSIG_MOB_GET_STATUS_TAB_ITEMS, PROC_REF(get_status_tab_item)) RegisterSignal(brain_owner, COMSIG_MOB_MIND_BEFORE_MIDROUND_ROLL, PROC_REF(cancel_rolls)) RegisterSignals(brain_owner, list(COMSIG_QDELETING, COMSIG_LIVING_PRE_WABBAJACKED), PROC_REF(undeploy)) RegisterSignal(brain_owner, COMSIG_CARBON_GAIN_ORGAN, PROC_REF(on_organ_gain)) /obj/item/organ/brain/cybernetic/ai/on_mob_remove(mob/living/carbon/organ_owner, special, movement_flags) undeploy() . = ..() organ_owner.remove_traits(list(HUMAN_SENSORS_VISIBLE_WITHOUT_SUIT, TRAIT_NO_MINDSWAP, TRAIT_CORPSELOCKED), REF(src)) UnregisterSignal(organ_owner, list(COMSIG_LIVING_HEALTH_UPDATE, COMSIG_CLICK, COMSIG_MOB_GET_STATUS_TAB_ITEMS, COMSIG_MOB_MIND_BEFORE_MIDROUND_ROLL, COMSIG_QDELETING, COMSIG_LIVING_PRE_WABBAJACKED)) /obj/item/organ/brain/cybernetic/ai/proc/cancel_rolls(mob/living/source, datum/mind/mind, datum/antagonist/antagonist) SIGNAL_HANDLER if(ispath(antagonist, /datum/antagonist/malf_ai)) return return CANCEL_ROLL /obj/item/organ/brain/cybernetic/ai/proc/get_status_tab_item(mob/living/source, list/items) SIGNAL_HANDLER if(!mainframe) return items += mainframe.get_status_tab_items() /obj/item/organ/brain/cybernetic/ai/proc/update_med_hud_status(mob/living/mob_parent) SIGNAL_HANDLER var/image/holder = mob_parent.hud_list?[STATUS_HUD] if(isnull(holder)) return var/icon/size_check = icon(mob_parent.icon, mob_parent.icon_state, mob_parent.dir) holder.pixel_y = size_check.Height() - ICON_SIZE_Y if(mob_parent.stat == DEAD || HAS_TRAIT(mob_parent, TRAIT_FAKEDEATH) || isnull(mainframe)) holder.icon_state = "huddead2" holder.pixel_x = -8 // new icon states? nuh uh else holder.icon_state = "hudtrackingai" holder.pixel_x = -16 // no thoughts only wifi /obj/item/organ/brain/cybernetic/ai/can_gain_trauma(datum/brain_trauma/trauma, resilience, natural_gain = FALSE) return FALSE /obj/item/organ/brain/cybernetic/ai/proc/owner_clicked(datum/source, atom/location, control, params, mob/user) SIGNAL_HANDLER if(!isAI(user)) return var/list/lines = list() lines += span_bold("[owner]") lines += "Target is currently [!HAS_TRAIT(owner, TRAIT_INCAPACITATED) ? "functional" : "incapacitated"]" lines += "Estimated organic/inorganic integrity: [owner.health]" if(mainframe) lines += span_warning("Already occupied by another digital entity.") else if(!is_sufficiently_augmented()) lines += span_warning("Organic organs detected. Robotic organs only, cannot take over.") else lines += "[span_boldnotice("Take control?")]
" to_chat(user, boxed_message(jointext(lines, "\n")), type = MESSAGE_TYPE_INFO) /obj/item/organ/brain/cybernetic/ai/Topic(href, href_list) ..() if(!href_list["ai_take_control"] || !is_sufficiently_augmented() || mainframe) return var/mob/living/silicon/ai/AI = locate(href_list["ai_take_control"]) in GLOB.silicon_mobs if(isnull(AI)) return if(AI.controlled_equipment) to_chat(AI, span_warning("You are already loaded into an onboard computer!")) return if(!GLOB.cameranet.checkCameraVis(owner)) to_chat(AI, span_warning("Target is no longer near active cameras.")) return if(!isturf(AI.loc)) to_chat(AI, span_warning("You aren't in your core!")) return RegisterSignal(owner, COMSIG_LIVING_DEATH, PROC_REF(undeploy)) AI.deployed_shell = owner deploy_init(AI) ADD_TRAIT(AI.mind, TRAIT_UNCONVERTABLE, REF(src)) ADD_TRAIT(AI, TRAIT_MIND_TEMPORARILY_GONE, REF(src)) AI.mind.transfer_to(owner) /obj/item/organ/brain/cybernetic/ai/proc/deploy_init(mob/living/silicon/ai/AI) //todo camera maybe mainframe = AI RegisterSignal(AI, COMSIG_QDELETING, PROC_REF(ai_deleted)) undeployment_action.Grant(owner) update_med_hud_status(owner) to_chat(owner, span_boldbig("You are still considered a silicon/cyborg/AI. Follow your laws.")) /obj/item/organ/brain/cybernetic/ai/proc/undeploy(datum/source) SIGNAL_HANDLER if(!owner?.mind || !mainframe) return UnregisterSignal(owner, list(COMSIG_LIVING_DEATH, COMSIG_QDELETING)) UnregisterSignal(mainframe, COMSIG_QDELETING) mainframe.redeploy_action.Remove(mainframe) mainframe.redeploy_action.last_used_shell = null owner.mind.transfer_to(mainframe) mainframe.deployed_shell = null undeployment_action.Remove(owner) if(mainframe.laws) mainframe.laws.show_laws(mainframe) if(mainframe.eyeobj) mainframe.eyeobj.setLoc(loc) REMOVE_TRAIT(mainframe.mind, TRAIT_UNCONVERTABLE, REF(src)) REMOVE_TRAIT(mainframe, TRAIT_MIND_TEMPORARILY_GONE, REF(src)) mainframe = null update_med_hud_status(owner) /obj/item/organ/brain/cybernetic/ai/proc/is_sufficiently_augmented() var/mob/living/carbon/carb_owner = owner . = TRUE if(!istype(carb_owner)) return for(var/obj/item/organ/organ as anything in carb_owner.organs) if(organ.organ_flags & ORGAN_EXTERNAL) continue if(!IS_ROBOTIC_ORGAN(organ) && !istype(organ, /obj/item/organ/tongue)) //tongues are not in the exosuit fab and nobody is going to bother to find them so return FALSE /obj/item/organ/brain/cybernetic/ai/proc/on_organ_gain(datum/source, obj/item/organ/new_organ, special) SIGNAL_HANDLER if(!is_sufficiently_augmented()) to_chat(owner, span_danger("Connection failure. Organics detected.")) undeploy() /obj/item/organ/brain/cybernetic/ai/proc/ai_deleted(datum/source) SIGNAL_HANDLER to_chat(owner, span_danger("Your core has been rendered inoperable...")) undeploy()