mirror of
https://github.com/ParadiseSS13/Paradise.git
synced 2026-07-18 18:44:48 +01:00
Adds basic megafauna: Bluespace Horror, Vox Armalis (#31480)
* Adds basic megafauna: Bluespace Horror, Vox Armalis * Armalis improvements, code review addressed * Lints * Update code/modules/mob/living/basic/hostile/megafauna/basic_megafauna.dm Co-authored-by: DGamerL <108773801+DGamerL@users.noreply.github.com> Signed-off-by: PollardTheDragon <144391971+PollardTheDragon@users.noreply.github.com> * Update code/modules/mob/living/basic/hostile/megafauna/bluespace_horror/bluespace_horror.dm Co-authored-by: DGamerL <108773801+DGamerL@users.noreply.github.com> Signed-off-by: PollardTheDragon <144391971+PollardTheDragon@users.noreply.github.com> * Update code/modules/mob/living/basic/hostile/megafauna/vox_armalis/vox_armalis.dm Co-authored-by: DGamerL <108773801+DGamerL@users.noreply.github.com> Signed-off-by: PollardTheDragon <144391971+PollardTheDragon@users.noreply.github.com> --------- Signed-off-by: PollardTheDragon <144391971+PollardTheDragon@users.noreply.github.com> Co-authored-by: DGamerL <108773801+DGamerL@users.noreply.github.com>
This commit is contained in:
@@ -0,0 +1,210 @@
|
||||
// Base type for basic mob megafauna
|
||||
/mob/living/basic/megafauna
|
||||
name = "megafauna"
|
||||
desc = "Attack the weak point for massive damage."
|
||||
health = 1000
|
||||
maxHealth = 1000
|
||||
a_intent = INTENT_HARM
|
||||
sentience_type = SENTIENCE_BOSS
|
||||
environment_smash = ENVIRONMENT_SMASH_RWALLS
|
||||
mob_biotypes = MOB_ORGANIC | MOB_EPIC
|
||||
obj_damage = 400
|
||||
light_range = 3
|
||||
faction = list("boss")
|
||||
weather_immunities = list("lava","ash")
|
||||
atmos_requirements = list("min_oxy" = 0, "max_oxy" = 0, "min_tox" = 0, "max_tox" = 0, "min_co2" = 0, "max_co2" = 0, "min_n2" = 0, "max_n2" = 0)
|
||||
damage_coeff = list(BRUTE = 1, BURN = 0.5, TOX = 1, CLONE = 1, STAMINA = 0, OXY = 1)
|
||||
minimum_survivable_temperature = 0
|
||||
maximum_survivable_temperature = INFINITY
|
||||
speed = 0
|
||||
move_force = MOVE_FORCE_OVERPOWERING
|
||||
move_resist = MOVE_FORCE_OVERPOWERING
|
||||
pull_force = MOVE_FORCE_OVERPOWERING
|
||||
mob_size = MOB_SIZE_LARGE
|
||||
layer = LARGE_MOB_LAYER // Looks weird with them slipping under mineral walls and cameras and shit otherwise
|
||||
flags_2 = IMMUNE_TO_SHUTTLECRUSH_2
|
||||
initial_traits = list(TRAIT_FLYING)
|
||||
/// The loot the mob should drop if killed with a crusher
|
||||
var/list/crusher_loot = list()
|
||||
/// The type of medal it drops if on hard mode
|
||||
var/medal_type
|
||||
/// The type of boss for the medal it drops
|
||||
var/score_type
|
||||
/// Is the mob truly dead?
|
||||
var/elimination = FALSE
|
||||
/// The mob's internal GPS, if it has one
|
||||
var/obj/item/gps/internal_gps
|
||||
/// If this is a megafauna that should grant achievements, or have a gps signal
|
||||
var/true_spawn = TRUE
|
||||
/// Has someone enabled hard mode?
|
||||
var/enraged = FALSE
|
||||
/// Path of the hardmode loot disk, if applicable.
|
||||
var/enraged_loot
|
||||
/// How much ore should killing this give
|
||||
var/difficulty_ore_modifier = 0
|
||||
/// Actions to grant on Initialize
|
||||
var/list/innate_actions = list()
|
||||
|
||||
/mob/living/basic/megafauna/Initialize(mapload)
|
||||
. = ..()
|
||||
GLOB.alive_megafauna_list |= UID()
|
||||
if(internal_gps && true_spawn)
|
||||
internal_gps = new internal_gps(src)
|
||||
grant_actions_by_list(innate_actions)
|
||||
generate_random_loot()
|
||||
RegisterSignal(src, , PROC_REF(hoverboard_deactivation))
|
||||
|
||||
/mob/living/basic/megafauna/Destroy()
|
||||
QDEL_NULL(internal_gps)
|
||||
UnregisterSignal(src, COMSIG_HOSTILE_FOUND_TARGET)
|
||||
GLOB.alive_megafauna_list -= UID()
|
||||
return ..()
|
||||
|
||||
/mob/living/basic/megafauna/melee_attack(atom/target, list/modifiers, ignore_cooldown)
|
||||
. = ..()
|
||||
if(isliving(target))
|
||||
var/mob/living/L = target
|
||||
if(L.stat == DEAD)
|
||||
devour(L)
|
||||
|
||||
/mob/living/basic/megafauna/can_die()
|
||||
return ..() && health <= 0
|
||||
|
||||
/mob/living/basic/megafauna/death(gibbed)
|
||||
GLOB.alive_megafauna_list -= UID()
|
||||
// lets normalize the icons a bit
|
||||
pixel_x = 0
|
||||
pixel_y = 0
|
||||
// this happens before the parent call because `del_on_death` may be set
|
||||
if(can_die() && !admin_spawned)
|
||||
var/datum/status_effect/crusher_damage/C = has_status_effect(STATUS_EFFECT_CRUSHERDAMAGETRACKING)
|
||||
if(C && length(crusher_loot) && C.total_damage >= maxHealth * 0.6)
|
||||
spawn_crusher_loot()
|
||||
if(enraged && length(loot) && enraged_loot) // Don't drop a disk if the boss drops no loot. Important for legion.
|
||||
for(var/mob/living/M in urange(20, src)) // Yes big range, but for bubblegum arena
|
||||
if(M.client)
|
||||
loot += enraged_loot // Disk for each miner / borg.
|
||||
if(!elimination) // used so the achievment only occurs for the last legion to die.
|
||||
SSblackbox.record_feedback("tally", "megafauna_kills", 1, "[initial(name)]")
|
||||
calc_ore_reward()
|
||||
return ..()
|
||||
|
||||
/// If the megafauna has a pool of random loot items to pick from, override this proc to have it be set on initialization
|
||||
/mob/living/basic/megafauna/proc/generate_random_loot()
|
||||
return list()
|
||||
|
||||
/// Handling the ore part of the mega reward, the higher the difficulty_ore_modifier the more ore will spawn
|
||||
/mob/living/basic/megafauna/proc/calc_ore_reward()
|
||||
var/list/common_ore = list(
|
||||
/obj/item/stack/ore/uranium,
|
||||
/obj/item/stack/ore/silver,
|
||||
/obj/item/stack/ore/gold,
|
||||
/obj/item/stack/ore/plasma,
|
||||
/obj/item/stack/ore/titanium)
|
||||
var/list/rare_ore = list(
|
||||
/obj/item/stack/ore/diamond,
|
||||
/obj/item/stack/ore/bluespace_crystal)
|
||||
|
||||
for(var/ore in common_ore)
|
||||
var/obj/item/stack/O = new ore(loc)
|
||||
O.amount = roll(difficulty_ore_modifier * 2, 4 + difficulty_ore_modifier)
|
||||
for(var/ore in rare_ore)
|
||||
var/obj/item/stack/O = new ore(loc)
|
||||
O.amount = roll(difficulty_ore_modifier, 4 + difficulty_ore_modifier)
|
||||
|
||||
/mob/living/basic/megafauna/proc/spawn_crusher_loot()
|
||||
loot += crusher_loot
|
||||
|
||||
/mob/living/basic/megafauna/on_changed_z_level(turf/old_turf, turf/new_turf)
|
||||
. = ..()
|
||||
if(!istype(get_area(src), /area/shuttle))
|
||||
unrage()
|
||||
|
||||
/mob/living/basic/megafauna/onShuttleMove(turf/oldT, turf/T1, rotation, mob/caller_mob)
|
||||
var/turf/oldloc = loc
|
||||
. = ..()
|
||||
if(!.)
|
||||
return
|
||||
var/turf/newloc = loc
|
||||
message_admins("Megafauna [src] \
|
||||
([ADMIN_FLW(src,"FLW")]) \
|
||||
moved via shuttle from ([oldloc.x], [oldloc.y], [oldloc.z]) to \
|
||||
([newloc.x], [newloc.y], [newloc.z])[caller_mob ? " called by [ADMIN_LOOKUP(caller_mob)]" : ""]")
|
||||
|
||||
/mob/living/basic/megafauna/proc/devour(mob/living/L)
|
||||
if(!L)
|
||||
return FALSE
|
||||
visible_message(
|
||||
"<span class='danger'>[src] devours [L]!</span>",
|
||||
"<span class='userdanger'>You feast on [L], restoring your health!</span>")
|
||||
if(!is_station_level(z) || client) // NPC monsters won't heal while on station
|
||||
adjustBruteLoss(-L.maxHealth/2)
|
||||
L.gib()
|
||||
return TRUE
|
||||
|
||||
/mob/living/basic/megafauna/ex_act(severity, target)
|
||||
switch(severity)
|
||||
if(EXPLODE_DEVASTATE)
|
||||
adjustBruteLoss(250)
|
||||
|
||||
if(EXPLODE_HEAVY)
|
||||
adjustBruteLoss(100)
|
||||
|
||||
if(EXPLODE_LIGHT)
|
||||
adjustBruteLoss(50)
|
||||
|
||||
/// This proc is called by the HRD-MDE grenade to enrage the megafauna. This should increase the megafaunas attack speed if possible, give it new moves, or disable weak moves. This should be reverseable, and reverses on zlvl change.
|
||||
/mob/living/basic/megafauna/proc/enrage()
|
||||
if(enraged || ((health / maxHealth) * 100 <= 80))
|
||||
return
|
||||
difficulty_ore_modifier += 4 // Hardmode only helps the station more and gives you bragging rights, no special items for hardmode
|
||||
enraged = TRUE
|
||||
|
||||
/mob/living/basic/megafauna/proc/unrage()
|
||||
difficulty_ore_modifier -= 4
|
||||
enraged = FALSE
|
||||
|
||||
// MARK: PTL Interaction
|
||||
/mob/living/basic/megafauna/on_ptl_target(obj/machinery/power/transmission_laser/ptl)
|
||||
ptl.RegisterSignal(src, COMSIG_MOB_DEATH, TYPE_PROC_REF(/obj/machinery/power/transmission_laser, untarget), ptl)
|
||||
if(ptl?.firing)
|
||||
on_ptl_fire(ptl)
|
||||
return
|
||||
|
||||
/mob/living/basic/megafauna/on_ptl_tick(obj/machinery/power/transmission_laser/ptl, output_level)
|
||||
loot = list() // disable loot drops form the target to prevent cheese
|
||||
if(10 * output_level * damage_coeff[BURN] / (1 MW) > health) // If we would kill the target dust it.
|
||||
health = 0 // We need this so can_die() won't prevent dusting
|
||||
visible_message("<span class='danger'>\The [src] is reduced to dust by the beam!</span>")
|
||||
dust()
|
||||
else
|
||||
adjustFireLoss(10 * output_level / (1 MW))
|
||||
|
||||
/mob/living/basic/megafauna/on_ptl_untarget(obj/machinery/power/transmission_laser/ptl)
|
||||
on_ptl_stop(ptl)
|
||||
if(ptl)
|
||||
ptl.UnregisterSignal(src, COMSIG_MOB_DEATH)
|
||||
|
||||
/mob/living/basic/megafauna/on_ptl_fire(obj/machinery/power/transmission_laser/ptl)
|
||||
var/orbital_strike = image(icon, src, "orbital_strike", FLY_LAYER, SOUTH)
|
||||
add_overlay(orbital_strike)
|
||||
|
||||
/mob/living/basic/megafauna/on_ptl_stop(obj/machinery/power/transmission_laser/ptl)
|
||||
for(var/image/overlay in overlays)
|
||||
if(overlay.name == "orbital_strike")
|
||||
cut_overlay(overlay)
|
||||
|
||||
/mob/living/basic/megafauna/proc/hoverboard_deactivation(source, target)
|
||||
SIGNAL_HANDLER // COMSIG_HOSTILE_FOUND_TARGET
|
||||
if(!isliving(target))
|
||||
return
|
||||
var/mob/living/L = target
|
||||
if(!L.buckled)
|
||||
return
|
||||
if(!istype(L.buckled, /obj/tgvehicle/scooter/skateboard/hoverboard))
|
||||
return
|
||||
var/obj/tgvehicle/scooter/skateboard/hoverboard/cursed_board = L.buckled
|
||||
// Not a visible message, as walls or such may be in the way
|
||||
to_chat(L, "<span class='userdanger'><b>You hear a loud roar in the distance, and the lights on [cursed_board] begin to spark dangerously, as the board rumbles heavily!</b></span>")
|
||||
playsound(get_turf(src), 'sound/effects/tendril_destroyed.ogg', 200, FALSE, 50, TRUE, TRUE)
|
||||
cursed_board.necropolis_curse()
|
||||
@@ -0,0 +1,182 @@
|
||||
/mob/living/basic/megafauna/bluespace_horror
|
||||
name = "bluespace horror"
|
||||
desc = "An indescribable creature, mutated from extended exposure to bluespace energies. There is no telling what it once was."
|
||||
health = 1500
|
||||
maxHealth = 1500
|
||||
icon = 'icons/mob/lavaland/96x96megafauna.dmi'
|
||||
icon_state = "bluespace_horror"
|
||||
speak_emote = list("emanates")
|
||||
melee_attack_cooldown_min = 1 SECONDS
|
||||
damage_coeff = list(BRUTE = 0.75, BURN = 1.1, TOX = 0, CLONE = 0, STAMINA = 0, OXY = 0)
|
||||
melee_damage_lower = 35
|
||||
melee_damage_upper = 45 // It's a megafauna. Don't get hit nerd.
|
||||
attack_verb_simple = "strike"
|
||||
attack_verb_continuous = "strikes"
|
||||
attack_sound = 'sound/magic/ratvar_attack.ogg'
|
||||
see_in_dark = 20 // I see you
|
||||
pixel_x = -32
|
||||
step_type = FOOTSTEP_MOB_HEAVY
|
||||
is_ranged = TRUE
|
||||
casing_type = /obj/item/ammo_casing/caseless/bluespace_shards
|
||||
projectile_sound = 'sound/magic/wand_teleport.ogg'
|
||||
ranged_burst_count = 2
|
||||
ranged_burst_interval = 0.4
|
||||
ranged_cooldown = 1.5 SECONDS
|
||||
true_spawn = FALSE
|
||||
ai_controller = /datum/ai_controller/basic_controller/bluespace_horror
|
||||
innate_actions = list(
|
||||
/datum/action/cooldown/mob_cooldown/bluespace_horror/summon_mobs = BB_HORROR_SUMMON_MOBS_ACTION,
|
||||
/datum/action/cooldown/mob_cooldown/bluespace_horror/fireball_fan = BB_HORROR_FIREBALL_FAN_ACTION,
|
||||
/datum/action/cooldown/mob_cooldown/bluespace_horror/lifesteal_bolt = BB_HORROR_LIFESTEAL_BOLT_ACTION,
|
||||
/datum/action/cooldown/mob_cooldown/bluespace_horror/magic_missile = BB_HORROR_MAGIC_MISSILE_ACTION,
|
||||
/datum/action/cooldown/mob_cooldown/bluespace_horror/charge = BB_HORROR_CHARGE_ACTION,
|
||||
/datum/action/cooldown/mob_cooldown/bluespace_horror/blink = BB_HORROR_BLINK_ACTION)
|
||||
/// Did we do the big attack?
|
||||
var/final_burst = FALSE
|
||||
|
||||
/mob/living/basic/megafauna/bluespace_horror/Initialize(mapload)
|
||||
. = ..()
|
||||
AddElement(/datum/element/ai_retaliate)
|
||||
|
||||
/mob/living/basic/megafauna/bluespace_horror/Process_Spacemove(movement_dir = 0, continuous_move = FALSE)
|
||||
return TRUE
|
||||
|
||||
/mob/living/basic/megafauna/bluespace_horror/melee_attack(atom/target, list/modifiers, ignore_cooldown)
|
||||
if(!isliving(target))
|
||||
return ..()
|
||||
var/mob/living/L = target
|
||||
var/datum/status_effect/stacking/unstable_bluespace_threads/G = L.has_status_effect(STATUS_EFFECT_BLUESPACE_THREADS)
|
||||
if(!G)
|
||||
L.apply_status_effect(STATUS_EFFECT_BLUESPACE_THREADS, 1, src)
|
||||
return ..()
|
||||
if(G.add_stacks(stacks_added = 1, attacker = src))
|
||||
return ..()
|
||||
|
||||
/mob/living/basic/megafauna/bluespace_horror/adjustHealth(amount, updating_health)
|
||||
if(mind) // For whatever reason, if a player is in control, we want them in charge of their blinks
|
||||
return ..()
|
||||
var/datum/action/cooldown/mob_cooldown/bluespace_horror/blink/blink_action = ai_controller.blackboard[BB_HORROR_BLINK_ACTION]
|
||||
if(blink_action.IsAvailable())
|
||||
ai_controller.queue_behavior(/datum/ai_behavior/horror_blink_dodge, BB_HORROR_BLINK_ACTION)
|
||||
return
|
||||
. = ..()
|
||||
if(health < maxHealth / 3 && !final_burst)
|
||||
ai_controller.queue_behavior(/datum/ai_behavior/omega_fireball_fan)
|
||||
final_burst = TRUE
|
||||
|
||||
/// Proc used for various powers and spells
|
||||
/mob/living/basic/megafauna/bluespace_horror/proc/shoot_projectile(atom/target_atom, obj/projectile/thing_to_shoot, set_angle)
|
||||
var/turf/startloc = get_turf(src)
|
||||
var/turf/endloc = get_turf(target_atom)
|
||||
|
||||
if(!startloc || !endloc || endloc == loc)
|
||||
return
|
||||
|
||||
var/obj/projectile/P = new thing_to_shoot(startloc)
|
||||
P.preparePixelProjectile(endloc, startloc)
|
||||
P.firer = src
|
||||
P.firer_source_atom = src
|
||||
|
||||
if(isnum(set_angle))
|
||||
P.fire(set_angle)
|
||||
else
|
||||
P.fire()
|
||||
|
||||
/// WHen hitting a mob five times, does something random
|
||||
/mob/living/basic/megafauna/bluespace_horror/proc/instability_correction()
|
||||
switch(rand(1,5))
|
||||
// ZAP
|
||||
if(1)
|
||||
var/list/shock_mobs = list()
|
||||
for(var/mob/living/C in view(get_turf(src), 5))
|
||||
if(isliving(C) && !faction_check_mob(C))
|
||||
shock_mobs += C
|
||||
if(length(shock_mobs))
|
||||
for(var/i in 1 to 3)
|
||||
var/mob/living/sucker = pick_n_take(shock_mobs)
|
||||
sucker.electrocute_act(rand(5, 25), "instability correction")
|
||||
playsound(get_turf(sucker), 'sound/effects/eleczap.ogg', 75, TRUE)
|
||||
Beam(sucker, icon_state = "lightning[rand(1, 12)]", icon = 'icons/effects/effects.dmi', time = 5)
|
||||
// Vwoop!
|
||||
if(2)
|
||||
var/list/desto_turfs = list()
|
||||
for(var/turf/possible in view(get_turf(src), 3))
|
||||
if(possible.is_blocked_turf(exclude_mobs = TRUE))
|
||||
continue
|
||||
if(isspaceturf(possible))
|
||||
continue
|
||||
desto_turfs += possible
|
||||
for(var/mob/living/C in oview(get_turf(src), 3))
|
||||
C.forceMove(pick(desto_turfs))
|
||||
to_chat(C, "<span class='danger'>You are teleported a short distance!</span>")
|
||||
do_sparks(3, TRUE, C)
|
||||
// Yoink!
|
||||
if(3)
|
||||
for(var/mob/living/C in oview(get_turf(src), 5))
|
||||
var/dir = get_dir(C.loc, loc)
|
||||
var/turf/step = get_step(C, dir)
|
||||
C.forceMove(step)
|
||||
to_chat(C, "<span class='danger'>You feel a strong force pulling you towards [src]!</span>")
|
||||
|
||||
// Shazam!
|
||||
if(4)
|
||||
for(var/mob/living/C in oview(get_turf(src), 1))
|
||||
shoot_projectile(C, /obj/projectile/magic/lifesteal_bolt)
|
||||
playsound(src, 'sound/magic/invoke_general.ogg', 200, TRUE, 2)
|
||||
// Kablooey!
|
||||
if(5)
|
||||
playsound(src, 'sound/effects/meteorimpact.ogg', 200, TRUE, 2, TRUE)
|
||||
new /obj/effect/temp_visual/stomp(loc)
|
||||
for(var/mob/living/L in range(3, src))
|
||||
if(faction_check(faction, L.faction, FALSE))
|
||||
continue
|
||||
|
||||
L.visible_message("<span class='danger'>[L] was thrown by [src]!</span>",
|
||||
"<span class='userdanger'>You feel a strong force throwing you!</span>",
|
||||
"<span class='danger'>You hear a thud.</span>")
|
||||
var/atom/throw_target = get_edge_target_turf(L, get_dir(src, get_step_away(L, src)))
|
||||
L.throw_at(throw_target, 4, 4)
|
||||
var/limb_to_hit = L.get_organ(pick(BODY_ZONE_HEAD, BODY_ZONE_CHEST, BODY_ZONE_R_ARM, BODY_ZONE_L_ARM, BODY_ZONE_R_LEG, BODY_ZONE_L_LEG))
|
||||
var/armor = L.run_armor_check(def_zone = limb_to_hit, armor_type = MELEE, armor_penetration_percentage = 50)
|
||||
L.apply_damage(20, BRUTE, limb_to_hit, armor)
|
||||
|
||||
|
||||
/obj/item/ammo_casing/caseless/bluespace_shards
|
||||
name = "bluespace shards"
|
||||
projectile_type = /obj/projectile/magic/bluespace_shards
|
||||
fire_sound = 'sound/magic/wand_teleport.ogg'
|
||||
pellets = 4
|
||||
variance = 90
|
||||
|
||||
/obj/projectile/magic/bluespace_shards
|
||||
name = "bluespace shard"
|
||||
icon_state = "u_laser_alt"
|
||||
damage = 20
|
||||
damage_type = BRUTE
|
||||
nodamage = FALSE
|
||||
|
||||
/datum/status_effect/stacking/unstable_bluespace_threads
|
||||
id = "unstable_bluespace_threads"
|
||||
tick_interval = 5 SECONDS
|
||||
stack_threshold = 5
|
||||
max_stacks = 5
|
||||
reset_ticks_on_stack = TRUE
|
||||
var/mob/living/basic/megafauna/bluespace_horror/latest_attacker
|
||||
|
||||
/datum/status_effect/stacking/unstable_bluespace_threads/on_creation(mob/living/new_owner, stacks_to_apply, mob/living/attacker)
|
||||
. = ..()
|
||||
if(.)
|
||||
latest_attacker = attacker
|
||||
|
||||
/datum/status_effect/stacking/unstable_bluespace_threads/add_stacks(stacks_added, mob/living/attacker)
|
||||
. = ..()
|
||||
if(.)
|
||||
latest_attacker = attacker
|
||||
if(stacks != stack_threshold)
|
||||
return TRUE
|
||||
|
||||
/datum/status_effect/stacking/unstable_bluespace_threads/stacks_consumed_effect()
|
||||
addtimer(CALLBACK(latest_attacker, TYPE_PROC_REF(/mob/living/basic/megafauna/bluespace_horror, instability_correction)), 1 SECONDS)
|
||||
|
||||
/datum/status_effect/stacking/unstable_bluespace_threads/on_remove()
|
||||
latest_attacker = null
|
||||
+203
@@ -0,0 +1,203 @@
|
||||
/datum/action/cooldown/mob_cooldown/bluespace_horror/summon_mobs
|
||||
name = "Summon Mobs"
|
||||
button_icon = 'icons/mob/actions/actions_cult.dmi'
|
||||
button_icon_state = "telerune"
|
||||
desc = "Summon a series of servants from bluespace to assist you."
|
||||
click_to_activate = FALSE
|
||||
melee_cooldown_time = CLICK_CD_CLICK_ABILITY
|
||||
cooldown_time = 90 SECONDS
|
||||
shared_cooldown = NONE
|
||||
|
||||
/datum/action/cooldown/mob_cooldown/bluespace_horror/summon_mobs/Activate(atom/target)
|
||||
var/mob/living/basic/megafauna/bluespace_horror/summoner = owner
|
||||
if(!istype(summoner))
|
||||
to_chat(owner, "<span class='warning'>I am unable to summon servants!</span>")
|
||||
return
|
||||
|
||||
var/amount_to_spawn = rand(2, 5)
|
||||
var/list/all_possible_dirs = GLOB.alldirs
|
||||
for(var/i in 1 to amount_to_spawn)
|
||||
var/mob/living/new_mob
|
||||
var/spawn_loc = get_step(summoner, pick_n_take(all_possible_dirs))
|
||||
new_mob = create_random_mob(spawn_loc, HOSTILE_SPAWN)
|
||||
new /obj/effect/temp_visual/emp/pulse/cult(spawn_loc)
|
||||
new_mob.faction = summoner.faction
|
||||
StartCooldown()
|
||||
|
||||
/datum/action/cooldown/mob_cooldown/bluespace_horror/charge
|
||||
name = "Violent Charge"
|
||||
button_icon_state = "OH_YEAAAAH"
|
||||
desc = "Fully materialize in the material plane and charge forward!"
|
||||
melee_cooldown_time = CLICK_CD_CLICK_ABILITY
|
||||
cooldown_time = 20 SECONDS
|
||||
shared_cooldown = NONE
|
||||
|
||||
/datum/action/cooldown/mob_cooldown/bluespace_horror/charge/Activate(atom/target)
|
||||
. = ..()
|
||||
var/dir_to_target = get_dir(get_turf(owner), get_turf(target))
|
||||
var/turf/T = get_step(get_turf(owner), dir_to_target)
|
||||
for(var/i in 1 to 9)
|
||||
new /obj/effect/temp_visual/dragon_swoop/bluespace_horror(T)
|
||||
T = get_step(T, dir_to_target)
|
||||
owner.visible_message("<span class='danger'>[owner] prepares to charge!</span>")
|
||||
addtimer(CALLBACK(src, PROC_REF(charge_to), dir_to_target, 0), 4)
|
||||
StartCooldown()
|
||||
|
||||
/datum/action/cooldown/mob_cooldown/bluespace_horror/charge/proc/charge_to(move_dir, times_ran, list/hit_targets = list())
|
||||
var/mob/living/basic/horror = owner
|
||||
if(times_ran >= 9)
|
||||
return
|
||||
var/turf/T = get_step(get_turf(owner), move_dir)
|
||||
if(ismineralturf(T))
|
||||
var/turf/simulated/mineral/M = T
|
||||
M.gets_drilled()
|
||||
if(iswallturf(T))
|
||||
T.dismantle_wall(TRUE)
|
||||
for(var/obj/structure/window/W in T.contents)
|
||||
W.take_damage(horror.obj_damage, BRUTE)
|
||||
for(var/obj/machinery/door/D in T.contents)
|
||||
D.take_damage(horror.obj_damage, BRUTE)
|
||||
if(T.x > world.maxx - 2 || T.x < 2) // Keep them from runtiming
|
||||
return
|
||||
if(T.y > world.maxy - 2 || T.y < 2)
|
||||
return
|
||||
owner.forceMove(T)
|
||||
playsound(owner, 'sound/effects/bang.ogg', 200, 1)
|
||||
var/throwtarget = get_edge_target_turf(owner, move_dir)
|
||||
for(var/mob/living/L in T.contents - owner)
|
||||
if(owner.faction_check_mob(L))
|
||||
continue
|
||||
owner.visible_message("<span class='danger'>[owner] tramples and kicks [L]!</span>")
|
||||
to_chat(L, "<span class='userdanger'>[owner] tramples you and kicks you away!</span>")
|
||||
L.throw_at(throwtarget, 10, 1, owner)
|
||||
L.Weaken(1 SECONDS) // Pain Train has no breaks.
|
||||
if(L in hit_targets)
|
||||
L.adjustBruteLoss(15)
|
||||
else
|
||||
hit_targets += L
|
||||
L.adjustBruteLoss(25)
|
||||
addtimer(CALLBACK(src, PROC_REF(charge_to), move_dir, (times_ran + 1), hit_targets), 0.7)
|
||||
|
||||
// The visual effect which appears in front of the horror when it goes to charge.
|
||||
/obj/effect/temp_visual/dragon_swoop/bluespace_horror
|
||||
icon_state = "rune_large_distorted"
|
||||
color = "#000099"
|
||||
|
||||
/obj/effect/temp_visual/dragon_swoop/bluespace_horror/Initialize(mapload)
|
||||
. = ..()
|
||||
transform *= 0.33
|
||||
|
||||
/datum/action/cooldown/mob_cooldown/bluespace_horror/magic_missile
|
||||
name = "Bluespace Missile"
|
||||
button_icon_state = "magicm"
|
||||
desc = "Conjure bluespace projectiles to seek nearby victims!"
|
||||
melee_cooldown_time = CLICK_CD_CLICK_ABILITY
|
||||
cooldown_time = 40 SECONDS
|
||||
shared_cooldown = NONE
|
||||
|
||||
/datum/action/cooldown/mob_cooldown/bluespace_horror/magic_missile/Activate(atom/target)
|
||||
. = ..()
|
||||
var/mob/living/basic/megafauna/bluespace_horror/horror = owner
|
||||
if(!istype(horror))
|
||||
return
|
||||
|
||||
for(var/T in RANGE_TURFS(12, horror.loc) - horror.loc)
|
||||
if(prob(10))
|
||||
horror.shoot_projectile(T, /obj/projectile/magic/magic_missile/lesser/horror)
|
||||
playsound(horror, 'sound/magic/magic_missile.ogg', 40, TRUE)
|
||||
sleep(1)
|
||||
horror.shoot_projectile(target, /obj/projectile/magic/magic_missile/lesser/horror)
|
||||
playsound(horror, 'sound/magic/magic_missile.ogg', 40, TRUE)
|
||||
StartCooldown()
|
||||
|
||||
/obj/projectile/magic/magic_missile/lesser/horror
|
||||
damage = 15
|
||||
damage_type = BRUTE
|
||||
nodamage = FALSE
|
||||
|
||||
/datum/action/cooldown/mob_cooldown/bluespace_horror/fireball_fan
|
||||
name = "Fireball Fan"
|
||||
button_icon_state = "fireball"
|
||||
desc = "Concentrate bluespace energy into a volatile volley of flame!"
|
||||
melee_cooldown_time = CLICK_CD_CLICK_ABILITY
|
||||
cooldown_time = 20 SECONDS
|
||||
shared_cooldown = NONE
|
||||
|
||||
/datum/action/cooldown/mob_cooldown/bluespace_horror/fireball_fan/Activate(atom/target)
|
||||
. = ..()
|
||||
var/mob/living/basic/megafauna/bluespace_horror/horror = owner
|
||||
if(!istype(horror))
|
||||
return
|
||||
var/angle_to_target = get_angle(horror, target)
|
||||
|
||||
var/variance = -90
|
||||
for(var/i in 1 to 5)
|
||||
horror.shoot_projectile(target, /obj/projectile/magic/fireball, angle_to_target + variance)
|
||||
variance += 45
|
||||
playsound(horror, 'sound/magic/fireball.ogg', 200, TRUE, 2)
|
||||
StartCooldown()
|
||||
|
||||
/datum/action/cooldown/mob_cooldown/bluespace_horror/lifesteal_bolt
|
||||
name = "Lifesteal Bolt"
|
||||
button_icon = 'icons/mob/actions/actions_cult.dmi'
|
||||
button_icon_state = "revive"
|
||||
desc = "Fire a fast-moving bolt that drains the vitality of those that live on the material plane."
|
||||
melee_cooldown_time = CLICK_CD_CLICK_ABILITY
|
||||
cooldown_time = 10 SECONDS
|
||||
shared_cooldown = NONE
|
||||
|
||||
/datum/action/cooldown/mob_cooldown/bluespace_horror/lifesteal_bolt/Activate(atom/target)
|
||||
. = ..()
|
||||
var/mob/living/basic/megafauna/bluespace_horror/horror = owner
|
||||
if(!istype(horror))
|
||||
return
|
||||
|
||||
horror.shoot_projectile(target, /obj/projectile/magic/lifesteal_bolt)
|
||||
playsound(horror, 'sound/magic/invoke_general.ogg', 200, TRUE, 2)
|
||||
StartCooldown()
|
||||
|
||||
/obj/projectile/magic/lifesteal_bolt
|
||||
name = "lifesteal bolt"
|
||||
icon_state = "arcane_barrage"
|
||||
damage = 25
|
||||
damage_type = BRUTE
|
||||
nodamage = FALSE
|
||||
|
||||
/obj/projectile/magic/lifesteal_bolt/on_hit(mob/living/carbon/target)
|
||||
. = ..()
|
||||
if(!.)
|
||||
return .
|
||||
if(isliving(target) && isliving(firer))
|
||||
var/mob/living/LF = firer
|
||||
target.apply_damage(5, CLONE)
|
||||
LF.adjustBruteLoss(-25)
|
||||
target.Beam(LF, icon_state = "drain_life", icon = 'icons/effects/effects.dmi', time = 2 SECONDS)
|
||||
|
||||
/datum/action/cooldown/mob_cooldown/bluespace_horror/blink
|
||||
name = "Blink"
|
||||
button_icon_state = "blink"
|
||||
desc = "Teleport away from your current location."
|
||||
click_to_activate = FALSE
|
||||
melee_cooldown_time = CLICK_CD_CLICK_ABILITY
|
||||
cooldown_time = 6 SECONDS
|
||||
shared_cooldown = NONE
|
||||
|
||||
/datum/action/cooldown/mob_cooldown/bluespace_horror/blink/Activate(atom/target)
|
||||
. = ..()
|
||||
var/list/turfs = list()
|
||||
for(var/turf/T in range(target, 7))
|
||||
if(T in range(target, 2))
|
||||
continue
|
||||
if(isspaceturf(T))
|
||||
continue
|
||||
if(T.density)
|
||||
continue
|
||||
turfs += T
|
||||
var/turf/picked = pick(turfs)
|
||||
var/datum/effect_system/smoke_spread/smoke
|
||||
smoke = new /datum/effect_system/smoke_spread/bad()
|
||||
smoke.set_up(2, FALSE, owner.loc)
|
||||
smoke.start()
|
||||
owner.forceMove(picked)
|
||||
playsound(owner, 'sound/magic/blink.ogg', 150, TRUE, 2)
|
||||
StartCooldown()
|
||||
+77
@@ -0,0 +1,77 @@
|
||||
/datum/ai_controller/basic_controller/bluespace_horror
|
||||
movement_delay = 1 SECONDS
|
||||
blackboard = list(
|
||||
BB_TARGETING_STRATEGY = /datum/targeting_strategy/basic,
|
||||
BB_TARGET_MINIMUM_STAT = UNCONSCIOUS,
|
||||
BB_AGGRO_RANGE = 7,
|
||||
)
|
||||
ai_movement = /datum/ai_movement/jps
|
||||
idle_behavior = /datum/idle_behavior/idle_random_walk
|
||||
interesting_dist = AI_MEGAFAUNA_INTERESTING_DIST
|
||||
max_target_distance = 15
|
||||
planning_subtrees = list(
|
||||
/datum/ai_planning_subtree/find_and_hunt_target/corpses/human,
|
||||
/datum/ai_planning_subtree/simple_find_target,
|
||||
/datum/ai_planning_subtree/targeted_mob_ability/horror_offensive_spellcasting,
|
||||
/datum/ai_planning_subtree/ranged_skirmish,
|
||||
/datum/ai_planning_subtree/attack_obstacle_in_path/walls,
|
||||
/datum/ai_planning_subtree/basic_melee_attack_subtree,
|
||||
/datum/ai_planning_subtree/find_and_hunt_target/prowl,
|
||||
/datum/ai_planning_subtree/attack_obstacle_in_path/prowl/walls,
|
||||
)
|
||||
|
||||
/datum/ai_planning_subtree/targeted_mob_ability/horror_offensive_spellcasting
|
||||
ability_key = BB_HORROR_FIREBALL_FAN_ACTION
|
||||
/// Minimum time between any spells
|
||||
var/horror_global_spellcasting_delay = 2 SECONDS
|
||||
|
||||
/datum/ai_planning_subtree/targeted_mob_ability/horror_offensive_spellcasting/select_behaviors(datum/ai_controller/controller, seconds_per_tick)
|
||||
if(controller.blackboard[BB_HORROR_SPELL_COOLDOWN] >= world.time)
|
||||
return
|
||||
var/list/ability_keys = list(BB_HORROR_FIREBALL_FAN_ACTION, BB_HORROR_LIFESTEAL_BOLT_ACTION, BB_HORROR_MAGIC_MISSILE_ACTION, BB_HORROR_CHARGE_ACTION, BB_HORROR_SUMMON_MOBS_ACTION)
|
||||
ability_key = pick_n_take(ability_keys)
|
||||
var/datum/action/cooldown/mob_cooldown/selected_action = controller.blackboard[ability_key]
|
||||
while(selected_action && !selected_action.IsAvailable())
|
||||
if(!ability_keys.len) // All spells are on cooldown
|
||||
return
|
||||
ability_key = pick_n_take(ability_keys)
|
||||
selected_action = controller.blackboard[ability_key]
|
||||
controller.set_blackboard_key(BB_HORROR_SPELL_COOLDOWN, world.time + horror_global_spellcasting_delay)
|
||||
return ..()
|
||||
|
||||
/// Dodge!
|
||||
/datum/ai_behavior/horror_blink_dodge
|
||||
action_cooldown = 6 SECONDS
|
||||
required_distance = 0
|
||||
behavior_flags = AI_BEHAVIOR_CAN_PLAN_DURING_EXECUTION
|
||||
|
||||
/datum/ai_behavior/horror_blink_dodge/perform(seconds_per_tick, datum/ai_controller/controller, action_key)
|
||||
. = ..()
|
||||
var/datum/action/cooldown/mob_cooldown/bluespace_horror/blink/blink_action = controller.blackboard[action_key]
|
||||
var/result = blink_action.Trigger()
|
||||
if(result)
|
||||
return AI_BEHAVIOR_INSTANT | AI_BEHAVIOR_SUCCEEDED
|
||||
return AI_BEHAVIOR_INSTANT | AI_BEHAVIOR_FAILED
|
||||
|
||||
/// Bring them down!
|
||||
/datum/ai_behavior/omega_fireball_fan
|
||||
action_cooldown = 10 MINUTES
|
||||
required_distance = 0
|
||||
|
||||
/datum/ai_behavior/omega_fireball_fan/perform(seconds_per_tick, datum/ai_controller/controller, ...)
|
||||
. = ..()
|
||||
var/mob/living/basic/megafauna/bluespace_horror/user = controller.pawn
|
||||
user.say("I WILL NOT BE BANISHED SO EASILY!")
|
||||
spawn(1 SECONDS)
|
||||
var/turf/start_turf = get_step(user, pick(GLOB.alldirs))
|
||||
var/counter = rand(1, 16)
|
||||
playsound(get_turf(user), 'sound/magic/invoke_general.ogg', 200, TRUE)
|
||||
for(var/i in 1 to 80)
|
||||
counter++
|
||||
if(counter > 16)
|
||||
counter = 1
|
||||
var/obj/projectile/proj = pick(/obj/projectile/magic/lifesteal_bolt, /obj/projectile/magic/fireball, /obj/projectile/magic/magic_missile/lesser, /obj/projectile/magic/bluespace_shards)
|
||||
user.shoot_projectile(start_turf, proj, counter * 22.5)
|
||||
playsound(get_turf(user), 'sound/magic/staff_chaos.ogg', 50, TRUE)
|
||||
sleep(1)
|
||||
return AI_BEHAVIOR_INSTANT | AI_BEHAVIOR_SUCCEEDED
|
||||
@@ -0,0 +1,231 @@
|
||||
/mob/living/basic/megafauna/vox_armalis
|
||||
name = "vox armalis"
|
||||
desc = "A massive vox, clad in heavy carapace armor and wielding a massive spikethrower. Something this large and heavy should not be moving with such finesse."
|
||||
health = 300
|
||||
maxHealth = 300
|
||||
gender = PLURAL
|
||||
icon = 'icons/mob/lavaland/32x64fauna.dmi'
|
||||
icon_state = "armalis"
|
||||
icon_dead = "armalis_dead"
|
||||
speak_emote = list("creels")
|
||||
melee_attack_cooldown_min = 1 SECONDS
|
||||
damage_coeff = list(BRUTE = 0.2, BURN = 0.2, TOX = 0.2, CLONE = 0, STAMINA = 0, OXY = 0)
|
||||
melee_damage_lower = 25
|
||||
melee_damage_upper = 30
|
||||
attack_verb_simple = "punch"
|
||||
attack_verb_continuous = "punches"
|
||||
attack_sound = 'sound/weapons/resonator_blast.ogg'
|
||||
response_help_continuous = "gestures at"
|
||||
response_help_simple = "gesture at"
|
||||
move_force = MOVE_FORCE_NORMAL
|
||||
see_in_dark = 20 // I see you
|
||||
step_type = FOOTSTEP_MOB_SHOE
|
||||
is_ranged = TRUE
|
||||
casing_type = /obj/item/ammo_casing/caseless/heavy_spike
|
||||
projectile_sound = 'sound/weapons/bladeslice.ogg'
|
||||
ranged_burst_count = 2
|
||||
ranged_cooldown = 0.5 SECONDS // It's player controlled, it can have player fire rates.
|
||||
initial_traits = list()
|
||||
blood_color = "#2299FC"
|
||||
butcher_time = 40 SECONDS
|
||||
butcher_results = list(
|
||||
/obj/item/food/fried_vox = 8,
|
||||
/obj/item/organ/internal/cyberimp/brain/sensory_enhancer = 1,
|
||||
/obj/item/salvage/loot/vox = 10,
|
||||
/obj/item/organ/internal/brain/vox = 1,
|
||||
)
|
||||
true_spawn = FALSE
|
||||
innate_actions = list(
|
||||
/datum/action/cooldown/mob_cooldown/vox_armalis/swap_ammo,
|
||||
/datum/action/cooldown/mob_cooldown/vox_armalis/activate_qani,
|
||||
/datum/action/cooldown/mob_cooldown/vox_armalis/ignite_claws,
|
||||
)
|
||||
/// Are the claws on
|
||||
var/plasma_claws = FALSE
|
||||
/// Do we explode on death?
|
||||
var/do_death_explosion = TRUE
|
||||
|
||||
/mob/living/basic/megafauna/vox_armalis/Initialize(mapload)
|
||||
. = ..()
|
||||
add_language("Galactic Common")
|
||||
add_language("Vox-pidgin")
|
||||
set_default_language(GLOB.all_languages["Vox-pidgin"])
|
||||
sight |= SEE_MOBS
|
||||
var/datum/language/lang = GLOB.all_languages["Vox-pidgin"]
|
||||
name = lang.get_random_name(gender)
|
||||
|
||||
/mob/living/basic/megafauna/vox_armalis/IsAdvancedToolUser()
|
||||
return TRUE
|
||||
|
||||
/mob/living/basic/megafauna/vox_armalis/Process_Spacemove(movement_dir = 0, continuous_move = FALSE)
|
||||
if(isspaceturf(loc))
|
||||
new /obj/effect/particle_effect/ion_trails(get_turf(src), dir)
|
||||
return TRUE
|
||||
|
||||
/mob/living/basic/megafauna/vox_armalis/update_overlays()
|
||||
. = ..()
|
||||
overlays.Cut()
|
||||
if(plasma_claws)
|
||||
. += "armalis_plasma_claws"
|
||||
|
||||
/mob/living/basic/megafauna/vox_armalis/ex_act(severity)
|
||||
switch(severity)
|
||||
if(EXPLODE_DEVASTATE)
|
||||
adjustBruteLoss(75)
|
||||
|
||||
if(EXPLODE_HEAVY)
|
||||
adjustBruteLoss(25)
|
||||
|
||||
if(EXPLODE_LIGHT)
|
||||
adjustBruteLoss(10)
|
||||
|
||||
/mob/living/basic/megafauna/vox_armalis/devour(mob/living/L)
|
||||
return
|
||||
|
||||
/mob/living/basic/megafauna/vox_armalis/death(gibbed)
|
||||
plasma_claws = FALSE
|
||||
update_icon(UPDATE_OVERLAYS)
|
||||
transform = transform.Turn(90)
|
||||
transform = transform.Translate(0, -16)
|
||||
if(do_death_explosion)
|
||||
death_explosion()
|
||||
return ..()
|
||||
|
||||
/mob/living/basic/megafauna/vox_armalis/gib() // Overwrites to make voxblood
|
||||
if(!death(TRUE) && stat != DEAD)
|
||||
return FALSE
|
||||
// hide and freeze for the GC
|
||||
notransform = TRUE
|
||||
if(gib_nullifies_icon)
|
||||
icon = null
|
||||
invisibility = 101
|
||||
|
||||
playsound(src.loc, 'sound/goonstation/effects/gib.ogg', 50, 1)
|
||||
for(var/i in 1 to 5)
|
||||
new /obj/effect/gibspawner/vox(loc)
|
||||
QDEL_IN(src, 0)
|
||||
return TRUE
|
||||
|
||||
/mob/living/basic/megafauna/vox_armalis/proc/death_explosion()
|
||||
visible_message("<span class='userdanger'>[src] starts beeping ominously!</span>")
|
||||
for(var/i in 1 to 4)
|
||||
playsound(loc, 'sound/items/timer.ogg', 30, 0)
|
||||
sleep(1 SECONDS)
|
||||
explosion(loc, 5, 11, 20, 26, flame_range = 26, cause = name)
|
||||
qdel(src)
|
||||
|
||||
/mob/living/basic/megafauna/vox_armalis/melee_attack(atom/target, list/modifiers, ignore_cooldown)
|
||||
if(a_intent == INTENT_HARM)
|
||||
if(ismob(target) && !plasma_claws)
|
||||
var/mob/dustlung = target
|
||||
var/atom/throw_target = get_edge_target_turf(target, get_dir(src, get_step_away(target, src)))
|
||||
dustlung.throw_at(throw_target, 15, 9) // Like getting hit with a max-spool powerfist
|
||||
return ..()
|
||||
if(istype(target, /obj/machinery/door/airlock))
|
||||
var/obj/machinery/door/airlock/A = target
|
||||
try_open_airlock(A)
|
||||
return
|
||||
if(istype(target, /obj/machinery/door/firedoor))
|
||||
var/obj/machinery/door/firedoor/A = target
|
||||
if(A.density)
|
||||
A.open()
|
||||
else
|
||||
A.close()
|
||||
return
|
||||
if(iswallturf(target))
|
||||
return // We're not on kill intent. Don't smash.
|
||||
if(ismachinery(target)) // We can interface with machines!
|
||||
var/obj/machinery/machine = target
|
||||
machine.attack_hand(src)
|
||||
return
|
||||
|
||||
/mob/living/basic/megafauna/vox_armalis/RangedAttack(atom/A, params)
|
||||
if(a_intent != INTENT_HARM)
|
||||
return // No shoot on friendly mode
|
||||
. = ..()
|
||||
|
||||
/mob/living/basic/megafauna/vox_armalis/proc/try_open_airlock(obj/machinery/door/airlock/D)
|
||||
if(D.operating)
|
||||
return
|
||||
if(D.welded)
|
||||
to_chat(src, "<span class='warning'>The door is welded.</span>")
|
||||
else if(D.locked)
|
||||
to_chat(src, "<span class='warning'>The door is bolted.</span>")
|
||||
else if(D.allowed(src))
|
||||
if(D.density)
|
||||
D.open(TRUE)
|
||||
else
|
||||
D.close(TRUE)
|
||||
return TRUE
|
||||
visible_message("<span class='danger'>[src] forces the door!</span>")
|
||||
playsound(src.loc, "sparks", 100, TRUE, SHORT_RANGE_SOUND_EXTRARANGE)
|
||||
if(D.density)
|
||||
D.open(TRUE)
|
||||
else
|
||||
D.close(TRUE)
|
||||
|
||||
/obj/item/ammo_casing/caseless/heavy_spike
|
||||
name = "heavy alloy spike"
|
||||
desc = "A large broadhead spike made out of a weird silvery metal."
|
||||
projectile_type = /obj/projectile/bullet/hspike
|
||||
muzzle_flash_effect = null
|
||||
fire_sound = 'sound/weapons/bladeslice.ogg'
|
||||
|
||||
/obj/item/ammo_casing/caseless/spike_flechettes
|
||||
name = "spike flechettes"
|
||||
desc = "A cluster of loosely-packed alloy spikes."
|
||||
projectile_type = /obj/projectile/bullet/fspike
|
||||
muzzle_flash_effect = null
|
||||
fire_sound = 'sound/weapons/bladeslice.ogg'
|
||||
pellets = 5
|
||||
variance = 60
|
||||
|
||||
/obj/item/ammo_casing/caseless/spike_penetrator
|
||||
name = "spike penetrator"
|
||||
desc = "A dense alloy spike with a reinforced, sharpened tip."
|
||||
projectile_type = /obj/projectile/bullet/pspike
|
||||
muzzle_flash_effect = null
|
||||
fire_sound = 'sound/weapons/bladeslice.ogg'
|
||||
|
||||
/obj/projectile/bullet/hspike
|
||||
name = "heavy alloy spike"
|
||||
desc = "It's about two feet of weird silvery metal with a wicked point."
|
||||
damage = 35
|
||||
knockdown = 3
|
||||
armor_penetration_flat = 45
|
||||
icon_state = "magspear"
|
||||
|
||||
/obj/projectile/bullet/hspike/on_hit(atom/target, blocked = 0)
|
||||
if((blocked < 100) && ishuman(target))
|
||||
var/mob/living/carbon/human/H = target
|
||||
H.bleed(50)
|
||||
H.Immobilize(1 SECONDS)
|
||||
..()
|
||||
|
||||
/obj/projectile/bullet/fspike
|
||||
name = "alloy spike flechette"
|
||||
desc = "A sharpened dart of silvery metal."
|
||||
damage = 15
|
||||
knockdown = 1
|
||||
armor_penetration_flat = 15
|
||||
icon_state = "magspear"
|
||||
|
||||
/obj/projectile/bullet/fspike/on_hit(atom/target, blocked = 0)
|
||||
if((blocked < 100) && ishuman(target))
|
||||
var/mob/living/carbon/human/H = target
|
||||
H.bleed(25)
|
||||
..()
|
||||
|
||||
/obj/projectile/bullet/pspike
|
||||
name = "alloy spike penetrator"
|
||||
desc = "A 2-foot reinforced spike made of a silvery metal and sharpened to a dangerous point."
|
||||
knockdown = 4
|
||||
armor_penetration_flat = 100
|
||||
icon_state = "magspear"
|
||||
forcedodge = 8
|
||||
|
||||
/obj/projectile/bullet/pspike/on_hit(atom/target, blocked = 0)
|
||||
if((blocked < 100) && ishuman(target))
|
||||
var/mob/living/carbon/human/H = target
|
||||
H.bleed(75)
|
||||
..()
|
||||
@@ -0,0 +1,132 @@
|
||||
/datum/action/cooldown/mob_cooldown/vox_armalis/activate_qani
|
||||
name = "Activate Qani-Laaca"
|
||||
button_icon = 'icons/obj/surgery.dmi'
|
||||
button_icon_state = "sandy"
|
||||
desc = "Activates an advanced Qani-Laaca implant, giving you high speed and the ability to dodge bullets for 20 seconds."
|
||||
click_to_activate = FALSE
|
||||
melee_cooldown_time = CLICK_CD_CLICK_ABILITY
|
||||
cooldown_time = 40 SECONDS
|
||||
shared_cooldown = NONE
|
||||
|
||||
/datum/action/cooldown/mob_cooldown/vox_armalis/activate_qani/Activate(atom/target)
|
||||
var/mob/living/basic/megafauna/vox_armalis/armalis = owner
|
||||
if(!istype(armalis))
|
||||
return
|
||||
RegisterSignal(armalis, COMSIG_MOVABLE_MOVED, PROC_REF(on_movement))
|
||||
RegisterSignal(armalis, COMSIG_ATOM_PREHIT, PROC_REF(dodge_bullets))
|
||||
armalis.next_move_modifier -= 0.5
|
||||
armalis.speed -= 1
|
||||
addtimer(CALLBACK(src, PROC_REF(end_qani)), 20 SECONDS)
|
||||
to_chat(armalis, "<span class='notice'>A flood of suppressants and performance enhancers flood your system, while your augments go into overdrive.</span>")
|
||||
StartCooldown()
|
||||
|
||||
/// Leaves an afterimage behind the mob when they move
|
||||
/datum/action/cooldown/mob_cooldown/vox_armalis/activate_qani/proc/on_movement(mob/living/L, atom/old_loc)
|
||||
SIGNAL_HANDLER
|
||||
if(HAS_TRAIT(L, TRAIT_IMMOBILIZED))
|
||||
return NONE
|
||||
new /obj/effect/temp_visual/decoy/mephedrone_afterimage(old_loc, L, 1.25 SECONDS)
|
||||
|
||||
/// Tries to dodge incoming bullets if we aren't disabled for any reasons
|
||||
/datum/action/cooldown/mob_cooldown/vox_armalis/activate_qani/proc/dodge_bullets(mob/living/basic/megafauna/vox_armalis/armalis, obj/projectile/hitting_projectile)
|
||||
SIGNAL_HANDLER
|
||||
|
||||
if(HAS_TRAIT(armalis, TRAIT_IMMOBILIZED))
|
||||
return NONE
|
||||
armalis.visible_message(
|
||||
"<span class='danger'>[armalis] effortlessly dodges [hitting_projectile]!</span>",
|
||||
"<span class='userdanger'>You effortlessly evade [hitting_projectile]!</span>",
|
||||
)
|
||||
playsound(armalis, pick('sound/weapons/bulletflyby.ogg', 'sound/weapons/bulletflyby2.ogg', 'sound/weapons/bulletflyby3.ogg'), 75, TRUE)
|
||||
armalis.add_filter("mephedrone_overdose_blur", 2, gauss_blur_filter(5))
|
||||
addtimer(CALLBACK(armalis, TYPE_PROC_REF(/atom, remove_filter), "mephedrone_overdose_blur"), 0.5 SECONDS)
|
||||
return ATOM_PREHIT_FAILURE
|
||||
|
||||
/datum/action/cooldown/mob_cooldown/vox_armalis/activate_qani/proc/end_qani()
|
||||
var/mob/living/basic/megafauna/vox_armalis/armalis = owner
|
||||
if(!istype(armalis))
|
||||
return
|
||||
UnregisterSignal(armalis, COMSIG_MOVABLE_MOVED)
|
||||
UnregisterSignal(armalis, COMSIG_ATOM_PREHIT)
|
||||
armalis.next_move_modifier += 0.5
|
||||
armalis.speed += 1
|
||||
to_chat(armalis, "<span class='notice'>Your augments return to standard operation speeds as the suppressants wear off.</span>")
|
||||
|
||||
/datum/action/cooldown/mob_cooldown/vox_armalis/swap_ammo
|
||||
name = "Switch Ammunition"
|
||||
button_icon = 'icons/obj/guns/energy.dmi'
|
||||
button_icon_state = "noisecannon"
|
||||
desc = "Switches the ammunition that your heavy spikethrower fires between spike, flechette, and AP sabot."
|
||||
click_to_activate = FALSE
|
||||
melee_cooldown_time = CLICK_CD_CLICK_ABILITY
|
||||
cooldown_time = 0 SECONDS
|
||||
shared_cooldown = NONE
|
||||
/// The type of ammunition
|
||||
var/ammo_type = 1
|
||||
|
||||
/datum/action/cooldown/mob_cooldown/vox_armalis/swap_ammo/Activate(atom/target)
|
||||
var/mob/living/basic/megafauna/vox_armalis/armalis = owner
|
||||
if(!istype(armalis))
|
||||
return
|
||||
|
||||
var/datum/component/ranged_attacks/comp = armalis.GetComponent(/datum/component/ranged_attacks)
|
||||
if(!comp)
|
||||
return
|
||||
|
||||
playsound(armalis, 'sound/weapons/kenetic_reload.ogg', 60, TRUE)
|
||||
switch(ammo_type)
|
||||
if(1)
|
||||
ammo_type++
|
||||
comp.casing_type = /obj/item/ammo_casing/caseless/spike_flechettes
|
||||
comp.burst_shots = 1
|
||||
comp.cooldown_time = 0.5 SECONDS
|
||||
to_chat(armalis, "<span class='notice'>You switch your heavy spikethrower to flechette shot.</span>")
|
||||
if(2)
|
||||
ammo_type++
|
||||
comp.casing_type = /obj/item/ammo_casing/caseless/spike_penetrator
|
||||
comp.burst_shots = 1
|
||||
comp.cooldown_time = 1 SECONDS
|
||||
to_chat(armalis, "<span class='notice'>You switch your heavy spikethrower to penetrating shot.</span>")
|
||||
if(3)
|
||||
ammo_type = 1
|
||||
comp.casing_type = /obj/item/ammo_casing/caseless/heavy_spike
|
||||
comp.burst_shots = 2
|
||||
comp.cooldown_time = 0.5 SECONDS
|
||||
to_chat(armalis, "<span class='notice'>You switch your heavy spikethrower to heavy spike shot.</span>")
|
||||
StartCooldown()
|
||||
|
||||
/datum/action/cooldown/mob_cooldown/vox_armalis/ignite_claws
|
||||
name = "Toggle Plasma Talons"
|
||||
button_icon = 'icons/obj/weapons/energy_melee.dmi'
|
||||
button_icon_state = "pyro_claws"
|
||||
desc = "Switches your plasma talons on or off, increasing your damage but preventing you from knocking targets back."
|
||||
click_to_activate = FALSE
|
||||
melee_cooldown_time = CLICK_CD_CLICK_ABILITY
|
||||
cooldown_time = 0 SECONDS
|
||||
shared_cooldown = NONE
|
||||
|
||||
/datum/action/cooldown/mob_cooldown/vox_armalis/ignite_claws/Activate(atom/target)
|
||||
var/mob/living/basic/megafauna/vox_armalis/armalis = owner
|
||||
if(!istype(armalis))
|
||||
return
|
||||
|
||||
if(armalis.plasma_claws)
|
||||
armalis.plasma_claws = FALSE
|
||||
armalis.melee_damage_lower = initial(armalis.melee_damage_lower)
|
||||
armalis.melee_damage_upper = initial(armalis.melee_damage_upper)
|
||||
armalis.attack_sound = initial(armalis.attack_sound)
|
||||
armalis.attack_verb_simple = initial(armalis.attack_verb_simple)
|
||||
armalis.attack_verb_continuous = initial(armalis.attack_verb_continuous)
|
||||
playsound(armalis, 'sound/weapons/saberoff.ogg', 80, TRUE)
|
||||
to_chat(armalis, "<span class='notice'>You switch off your plasma talons.</span>")
|
||||
else
|
||||
armalis.plasma_claws = TRUE
|
||||
armalis.melee_damage_lower += 10
|
||||
armalis.melee_damage_upper += 20
|
||||
armalis.attack_sound = 'sound/weapons/blade1.ogg'
|
||||
armalis.attack_verb_simple = "slash"
|
||||
armalis.attack_verb_continuous = "slashes"
|
||||
playsound(armalis, 'sound/weapons/saberon.ogg', 80, TRUE)
|
||||
to_chat(armalis, "<span class='warning'>You switch on your plasma talons.</span>")
|
||||
armalis.update_icon(UPDATE_OVERLAYS)
|
||||
StartCooldown()
|
||||
Reference in New Issue
Block a user