diff --git a/code/__DEFINES/status_effects.dm b/code/__DEFINES/status_effects.dm index 35a85794fe5..e5d5bb723c1 100644 --- a/code/__DEFINES/status_effects.dm +++ b/code/__DEFINES/status_effects.dm @@ -54,7 +54,7 @@ //#define STATUS_EFFECT_CRUSHERMARK /datum/status_effect/crusher_mark //if struck with a proto-kinetic crusher, takes a ton of damage -//#define STATUS_EFFECT_SAWBLEED /datum/status_effect/saw_bleed //if the bleed builds up enough, takes a ton of damage +#define STATUS_EFFECT_SAWBLEED /datum/status_effect/saw_bleed //if the bleed builds up enough, takes a ton of damage //#define STATUS_EFFECT_NECROPOLIS_CURSE /datum/status_effect/necropolis_curse //#define CURSE_BLINDING 1 //makes the edges of the target's screen obscured diff --git a/code/__HELPERS/unsorted.dm b/code/__HELPERS/unsorted.dm index f05ac4ce96d..9b26bba67af 100644 --- a/code/__HELPERS/unsorted.dm +++ b/code/__HELPERS/unsorted.dm @@ -587,13 +587,14 @@ proc/GaussRandRound(var/sigma,var/roundto) return 1 -/proc/is_blocked_turf(var/turf/T) - var/cant_pass = 0 - if(T.density) cant_pass = 1 - for(var/atom/A in T) - if(A.density)//&&A.anchored - cant_pass = 1 - return cant_pass +proc/is_blocked_turf(turf/T, exclude_mobs) + if(T.density) + return 1 + for(var/i in T) + var/atom/A = i + if(A.density && (!exclude_mobs || !ismob(A))) + return 1 + return 0 /proc/get_step_towards2(var/atom/ref , var/atom/trg) var/base_dir = get_dir(ref, get_step_towards(ref,trg)) diff --git a/code/datums/status_effects/debuffs.dm b/code/datums/status_effects/debuffs.dm index cfafa912b64..6e809b32606 100644 --- a/code/datums/status_effects/debuffs.dm +++ b/code/datums/status_effects/debuffs.dm @@ -7,4 +7,76 @@ /datum/status_effect/cultghost/tick() if(owner.reagents) - owner.reagents.del_reagent("holywater") //can't be deconverted \ No newline at end of file + owner.reagents.del_reagent("holywater") //can't be deconverted + +/datum/status_effect/saw_bleed + id = "saw_bleed" + duration = -1 //removed under specific conditions + tick_interval = 6 + alert_type = null + var/mutable_appearance/bleed_overlay + var/mutable_appearance/bleed_underlay + var/bleed_amount = 3 + var/bleed_buildup = 3 + var/delay_before_decay = 5 + var/bleed_damage = 200 + var/needs_to_bleed = FALSE + +/datum/status_effect/saw_bleed/Destroy() + if(owner) + owner.cut_overlay(bleed_overlay) + owner.underlays -= bleed_underlay + QDEL_NULL(bleed_overlay) + return ..() + +/datum/status_effect/saw_bleed/on_apply() + if(owner.stat == DEAD) + return FALSE + bleed_overlay = mutable_appearance('icons/effects/bleed.dmi', "bleed[bleed_amount]") + bleed_underlay = mutable_appearance('icons/effects/bleed.dmi', "bleed[bleed_amount]") + var/icon/I = icon(owner.icon, owner.icon_state, owner.dir) + var/icon_height = I.Height() + bleed_overlay.pixel_x = -owner.pixel_x + bleed_overlay.pixel_y = FLOOR(icon_height * 0.25, 1) + bleed_overlay.transform = matrix() * (icon_height/world.icon_size) //scale the bleed overlay's size based on the target's icon size + bleed_underlay.pixel_x = -owner.pixel_x + bleed_underlay.transform = matrix() * (icon_height/world.icon_size) * 3 + bleed_underlay.alpha = 40 + owner.add_overlay(bleed_overlay) + owner.underlays += bleed_underlay + return ..() + +/datum/status_effect/saw_bleed/tick() + if(owner.stat == DEAD) + qdel(src) + else + add_bleed(-1) + +/datum/status_effect/saw_bleed/proc/add_bleed(amount) + owner.cut_overlay(bleed_overlay) + owner.underlays -= bleed_underlay + bleed_amount += amount + if(bleed_amount) + if(bleed_amount >= 10) + needs_to_bleed = TRUE + qdel(src) + else + if(amount > 0) + tick_interval += delay_before_decay + bleed_overlay.icon_state = "bleed[bleed_amount]" + bleed_underlay.icon_state = "bleed[bleed_amount]" + owner.add_overlay(bleed_overlay) + owner.underlays += bleed_underlay + else + qdel(src) + +/datum/status_effect/saw_bleed/on_remove() + if(needs_to_bleed) + var/turf/T = get_turf(owner) + new /obj/effect/temp_visual/bleed/explode(T) + for(var/d in alldirs) + new /obj/effect/temp_visual/dir_setting/bloodsplatter(T, d) + playsound(T, "desceration", 200, 1, -1) + owner.adjustBruteLoss(bleed_damage) + else + new /obj/effect/temp_visual/bleed(get_turf(owner)) \ No newline at end of file diff --git a/code/game/objects/effects/temporary_visuals/miscellaneous.dm b/code/game/objects/effects/temporary_visuals/miscellaneous.dm index 880043c8be6..dda9d18c77f 100644 --- a/code/game/objects/effects/temporary_visuals/miscellaneous.dm +++ b/code/game/objects/effects/temporary_visuals/miscellaneous.dm @@ -228,4 +228,65 @@ randomdir = FALSE pixel_y = -16 pixel_x = -16 - duration = 20 \ No newline at end of file + duration = 20 + +/obj/effect/temp_visual/bleed + name = "bleed" + icon = 'icons/effects/bleed.dmi' + icon_state = "bleed0" + duration = 10 + var/shrink = TRUE + +/obj/effect/temp_visual/bleed/Initialize(mapload, atom/size_calc_target) + . = ..() + var/size_matrix = matrix() + if(size_calc_target) + layer = size_calc_target.layer + 0.01 + var/icon/I = icon(size_calc_target.icon, size_calc_target.icon_state, size_calc_target.dir) + size_matrix = matrix() * (I.Height()/world.icon_size) + transform = size_matrix //scale the bleed overlay's size based on the target's icon size + var/matrix/M = transform + if(shrink) + M = size_matrix*0.1 + else + M = size_matrix*2 + animate(src, alpha = 20, transform = M, time = duration, flags = ANIMATION_PARALLEL) + +/obj/effect/temp_visual/bleed/explode + icon_state = "bleed10" + duration = 12 + shrink = FALSE + +/obj/effect/temp_visual/small_smoke + icon_state = "smoke" + duration = 50 + +/obj/effect/temp_visual/small_smoke/halfsecond + duration = 5 + +/obj/effect/temp_visual/dir_setting/firing_effect + icon = 'icons/effects/effects.dmi' + icon_state = "firing_effect" + duration = 2 + +/obj/effect/temp_visual/dir_setting/firing_effect/setDir(newdir) + switch(newdir) + if(NORTH) + layer = BELOW_MOB_LAYER + pixel_x = rand(-3,3) + pixel_y = rand(4,6) + if(SOUTH) + pixel_x = rand(-3,3) + pixel_y = rand(-1,1) + else + pixel_x = rand(-1,1) + pixel_y = rand(-1,1) + ..() + +/obj/effect/temp_visual/dir_setting/firing_effect/energy + icon_state = "firing_effect_energy" + duration = 3 + +/obj/effect/temp_visual/dir_setting/firing_effect/magic + icon_state = "shieldsparkles" + duration = 3 \ No newline at end of file diff --git a/code/game/objects/items/weapons/melee/energy.dm b/code/game/objects/items/weapons/melee/energy.dm index 0d85245d58f..12d4f95d964 100644 --- a/code/game/objects/items/weapons/melee/energy.dm +++ b/code/game/objects/items/weapons/melee/energy.dm @@ -2,6 +2,8 @@ var/active = 0 var/force_on = 30 //force when active var/throwforce_on = 20 + var/faction_bonus_force = 0 //Bonus force dealt against certain factions + var/list/nemesis_factions //Any mob with a faction that exists in this list will take bonus damage/effects w_class = WEIGHT_CLASS_SMALL var/w_class_on = WEIGHT_CLASS_BULKY var/icon_state_on = "axe1" @@ -13,6 +15,19 @@ var/brightness_on = 2 var/colormap = list(red=LIGHT_COLOR_RED, blue=LIGHT_COLOR_LIGHTBLUE, green=LIGHT_COLOR_GREEN, purple=LIGHT_COLOR_PURPLE, rainbow=LIGHT_COLOR_WHITE) +/obj/item/melee/energy/attack(mob/living/target, mob/living/carbon/human/user) + var/nemesis_faction = FALSE + if(LAZYLEN(nemesis_factions)) + for(var/F in target.faction) + if(F in nemesis_factions) + nemesis_faction = TRUE + force += faction_bonus_force + nemesis_effects(user, target) + break + . = ..() + if(nemesis_faction) + force -= faction_bonus_force + /obj/item/melee/energy/suicide_act(mob/user) user.visible_message(pick("[user] is slitting [user.p_their()] stomach open with the [name]! It looks like [user.p_theyre()] trying to commit seppuku.", \ "[user] is falling on the [name]! It looks like [user.p_theyre()] trying to commit suicide.")) @@ -225,3 +240,125 @@ desc = "An extremely sharp blade made out of hard light. Packs quite a punch." icon_state = "lightblade" item_state = "lightblade" + +/obj/item/melee/energy/proc/nemesis_effects(mob/living/user, mob/living/target) + return + +/obj/item/melee/energy/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." + force = 12 + force_on = 20 //force when active + throwforce = 20 + throwforce_on = 20 + icon = 'icons/obj/lavaland/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 + icon_state = "cleaving_saw" + icon_state_on = "cleaving_saw_open" + slot_flags = SLOT_BELT + var/attack_verb_off = list("attacked", "sawed", "sliced", "torn", "ripped", "diced", "cut") + attack_verb_on = list("cleaved", "swiped", "slashed", "chopped") + hitsound = 'sound/weapons/bladeslice.ogg' + w_class = WEIGHT_CLASS_BULKY + sharp = 1 + faction_bonus_force = 30 + nemesis_factions = list("mining", "boss") + var/transform_cooldown + var/swiping = FALSE + +/obj/item/melee/energy/cleaving_saw/nemesis_effects(mob/living/user, mob/living/target) + var/datum/status_effect/saw_bleed/B = target.has_status_effect(STATUS_EFFECT_SAWBLEED) + if(!B) + if(!active) //This isn't in the above if-check so that the else doesn't care about active + target.apply_status_effect(STATUS_EFFECT_SAWBLEED) + else + B.add_bleed(B.bleed_buildup) + +/obj/item/melee/energy/cleaving_saw/attack_self(mob/living/carbon/user) + transform_weapon(user) + +/obj/item/melee/energy/cleaving_saw/proc/transform_weapon(mob/living/user, supress_message_text) + if(transform_cooldown > world.time) + return FALSE + + transform_cooldown = world.time + (CLICK_CD_MELEE * 0.5) + user.changeNext_move(CLICK_CD_MELEE * 0.25) + + if(ishuman(user)) + var/mob/living/carbon/human/H = user + if(H.disabilities & CLUMSY && prob(50)) + to_chat(H, "You accidentally cut yourself with [src], like a doofus!") + H.take_organ_damage(10,10) + active = !active + if(active) + force = force_on + throwforce = throwforce_on + hitsound = 'sound/weapons/bladeslice.ogg' + throw_speed = 4 + if(attack_verb_on.len) + attack_verb = attack_verb_on + if(!item_color) + icon_state = icon_state_on + set_light(brightness_on) + else + icon_state = "sword[item_color]" + set_light(brightness_on, l_color=colormap[item_color]) + w_class = w_class_on + playsound(user, 'sound/magic/fellowship_armory.ogg', 35, TRUE, frequency = 90000 - (active * 30000)) + to_chat(user, "You open [src]. It will now cleave enemies in a wide arc and deal additional damage to fauna.") + else + force = initial(force) + throwforce = initial(throwforce) + hitsound = initial(hitsound) + throw_speed = initial(throw_speed) + if(attack_verb_on.len) + attack_verb = list() + icon_state = initial(icon_state) + w_class = initial(w_class) + playsound(user, 'sound/magic/fellowship_armory.ogg', 35, 1) //changed it from 50% volume to 35% because deafness + set_light(0) + to_chat(user, "You close [src]. It will now attack rapidly and cause fauna to bleed.") + + if(ishuman(user)) + var/mob/living/carbon/human/H = user + H.update_inv_l_hand() + H.update_inv_r_hand() + + add_fingerprint(user) + +/obj/item/melee/energy/cleaving_saw/examine(mob/user) + ..() + to_chat(user, "It is [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"].
\ + Both modes will build up existing bleed effects, doing a burst of high damage if the bleed is built up high enough.
\ + Transforming it immediately after an attack causes the next attack to come out faster.
") + +/obj/item/melee/energy/cleaving_saw/suicide_act(mob/user) + user.visible_message("[user] is [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!") + return BRUTELOSS + +/obj/item/melee/energy/cleaving_saw/melee_attack_chain(mob/user, atom/target, params) + ..() + if(!active) + user.changeNext_move(CLICK_CD_MELEE * 0.5) //when closed, it attacks very rapidly + +/obj/item/melee/energy/cleaving_saw/attack(mob/living/target, mob/living/carbon/human/user) + if(!active || swiping || !target.density || get_turf(target) == get_turf(user)) + if(!active) + faction_bonus_force = 0 + ..() + if(!active) + 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/T = get_step(user_turf, turn(dir_to_target, i)) + for(var/mob/living/L in T) + if(user.Adjacent(L) && L.density) + melee_attack_chain(user, L) + swiping = FALSE \ No newline at end of file diff --git a/code/modules/mob/living/living_defines.dm b/code/modules/mob/living/living_defines.dm index 6e12748911b..873e47ae1a5 100644 --- a/code/modules/mob/living/living_defines.dm +++ b/code/modules/mob/living/living_defines.dm @@ -68,3 +68,5 @@ var/list/status_effects //a list of all status effects the mob has var/deathgasp_on_death = FALSE + + var/stun_absorption = null //converted to a list of stun absorption sources this mob has when one is added diff --git a/code/modules/mob/living/simple_animal/hostile/megafauna/blood_drunk_miner.dm b/code/modules/mob/living/simple_animal/hostile/megafauna/blood_drunk_miner.dm new file mode 100644 index 00000000000..5fd5b4e9231 --- /dev/null +++ b/code/modules/mob/living/simple_animal/hostile/megafauna/blood_drunk_miner.dm @@ -0,0 +1,265 @@ +#define MINER_DASH_RANGE 4 + +/* + +BLOOD-DRUNK MINER + +Effectively a highly aggressive miner, the blood-drunk miner has very few attacks but compensates by being highly aggressive. + +The blood-drunk miner's attacks are as follows +- If not in KA range, it will rapidly dash at its target +- If in KA range, it will fire its kinetic accelerator +- If in melee range, will rapidly attack, akin to an actual player +- After any of these attacks, may transform its cleaving saw: + Untransformed, it attacks very rapidly for smaller amounts of damage + Transformed, it attacks at normal speed for higher damage and cleaves enemies hit + +When the blood-drunk miner dies, it leaves behind the cleaving saw it was using and its kinetic accelerator. + +Difficulty: Medium + +*/ + +/mob/living/simple_animal/hostile/megafauna/blood_drunk_miner + name = "blood-drunk miner" + desc = "A miner destined to wander forever, engaged in an endless hunt." + health = 900 + maxHealth = 900 + icon_state = "miner" + icon_living = "miner" + icon = 'icons/mob/alienqueen.dmi' + light_color = "#E4C7C5" + speak_emote = list("roars") + speed = 1 + move_to_delay = 3 + projectiletype = /obj/item/projectile/kinetic/miner + projectilesound = 'sound/weapons/kenetic_accel.ogg' + ranged = 1 + ranged_cooldown_time = 16 + pixel_x = -16 + loot = list(/obj/item/melee/energy/cleaving_saw, /obj/item/gun/energy/kinetic_accelerator) + wander = FALSE + del_on_death = TRUE + blood_volume = BLOOD_VOLUME_NORMAL + var/obj/item/melee/energy/cleaving_saw/miner/miner_saw + var/time_until_next_transform = 0 + var/dashing = FALSE + var/dash_cooldown = 15 + var/guidance = FALSE + deathmessage = "falls to the ground, decaying into glowing particles." + death_sound = "bodyfall" + +/obj/item/gps/internal/miner + icon_state = null + gpstag = "Resonant Signal" + desc = "The sweet blood, oh, it sings to me." + invisibility = 100 + +/mob/living/simple_animal/hostile/megafauna/blood_drunk_miner/guidance + guidance = TRUE + +/mob/living/simple_animal/hostile/megafauna/blood_drunk_miner/hunter/AttackingTarget() + . = ..() + if(. && prob(12)) + INVOKE_ASYNC(src, .proc/dash) + +/obj/item/melee/energy/cleaving_saw/miner //nerfed saw because it is very murdery + force = 6 + force_on = 10 + +/obj/item/melee/energy/cleaving_saw/miner/attack(mob/living/target, mob/living/carbon/human/user) + target.add_stun_absorption("miner", 10, INFINITY) + ..() + target.stun_absorption -= "miner" + +/obj/item/projectile/kinetic/miner + damage = 20 + speed = 0.9 + icon_state = "ka_tracer" + range = MINER_DASH_RANGE + +/mob/living/simple_animal/hostile/megafauna/blood_drunk_miner/Initialize() + . = ..() + miner_saw = new(src) + internal_gps = new/obj/item/gps/internal/miner(src) + + // Add a zone selection UI; otherwise the mob can't melee attack properly. + zone_sel = new /obj/screen/zone_sel() + +/mob/living/simple_animal/hostile/megafauna/blood_drunk_miner/adjustHealth(amount, updating_health = TRUE, forced = FALSE) + var/adjustment_amount = amount * 0.1 + if(world.time + adjustment_amount > next_move) + changeNext_move(adjustment_amount) //attacking it interrupts it attacking, but only briefly + . = ..() + +/mob/living/simple_animal/hostile/megafauna/blood_drunk_miner/death() + if(health > 0) + return + new /obj/effect/temp_visual/dir_setting/miner_death(loc, dir) + return ..() + +/mob/living/simple_animal/hostile/megafauna/blood_drunk_miner/Move(atom/newloc) + if(dashing || (newloc && newloc.z == z && (islava(newloc) || ischasm(newloc)))) //we're not stupid! + return FALSE + return ..() + +/mob/living/simple_animal/hostile/megafauna/blood_drunk_miner/ex_act(severity, target) + if(dash()) + return + return ..() + +/mob/living/simple_animal/hostile/megafauna/blood_drunk_miner/AttackingTarget() + if(QDELETED(target)) + return + if(next_move > world.time || !Adjacent(target)) //some cheating + INVOKE_ASYNC(src, .proc/quick_attack_loop) + return + face_atom(target) + if(isliving(target)) + var/mob/living/L = target + if(L.stat == DEAD) + visible_message("[src] butchers [L]!", + "You butcher [L], restoring your health!") + if(!is_station_level(z) || client) //NPC monsters won't heal while on station + if(guidance) + adjustHealth(-L.maxHealth) + else + adjustHealth(-(L.maxHealth * 0.5)) + L.gib() + return TRUE + changeNext_move(CLICK_CD_MELEE) + miner_saw.melee_attack_chain(src, target) + if(guidance) + adjustHealth(-2) + transform_weapon() + INVOKE_ASYNC(src, .proc/quick_attack_loop) + return TRUE + +/mob/living/simple_animal/hostile/megafauna/blood_drunk_miner/do_attack_animation(atom/A, visual_effect_icon, obj/item/used_item, no_effect) + if(!used_item && !isturf(A)) + used_item = miner_saw + ..() + +/mob/living/simple_animal/hostile/megafauna/blood_drunk_miner/GiveTarget(new_target) + var/targets_the_same = (new_target == target) + . = ..() + if(. && target && !targets_the_same) + wander = TRUE + transform_weapon() + INVOKE_ASYNC(src, .proc/quick_attack_loop) + +/mob/living/simple_animal/hostile/megafauna/blood_drunk_miner/OpenFire() + Goto(target, move_to_delay, minimum_distance) + if(get_dist(src, target) > MINER_DASH_RANGE && dash_cooldown <= world.time) + INVOKE_ASYNC(src, .proc/dash, target) + else + shoot_ka() + transform_weapon() + +/mob/living/simple_animal/hostile/megafauna/blood_drunk_miner/proc/shoot_ka() + if(ranged_cooldown <= world.time && get_dist(src, target) <= MINER_DASH_RANGE && !Adjacent(target)) + ranged_cooldown = world.time + ranged_cooldown_time + visible_message("[src] fires the proto-kinetic accelerator!") + face_atom(target) + new /obj/effect/temp_visual/dir_setting/firing_effect(loc, dir) + Shoot(target) + changeNext_move(CLICK_CD_RANGE) + +//I'm still of the belief that this entire proc needs to be wiped from existence. +// do not take my touching of it to be endorsement of it. ~mso +/mob/living/simple_animal/hostile/megafauna/blood_drunk_miner/proc/quick_attack_loop() + while(!QDELETED(target) && next_move <= world.time) //this is done this way because next_move can change to be sooner while we sleep. + stoplag(1) + sleep((next_move - world.time) * 1.5) //but don't ask me what the fuck this is about + if(QDELETED(target)) + return + if(dashing || next_move > world.time || !Adjacent(target)) + if(dashing && next_move <= world.time) + next_move = world.time + 1 + INVOKE_ASYNC(src, .proc/quick_attack_loop) //lets try that again. + return + AttackingTarget() + +/mob/living/simple_animal/hostile/megafauna/blood_drunk_miner/proc/dash(atom/dash_target) + if(world.time < dash_cooldown) + return + var/list/accessable_turfs = list() + var/self_dist_to_target = 0 + var/turf/own_turf = get_turf(src) + if(!QDELETED(dash_target)) + self_dist_to_target += get_dist(dash_target, own_turf) + for(var/turf/simulated/O in RANGE_TURFS(MINER_DASH_RANGE, own_turf)) + var/turf_dist_to_target = 0 + if(!QDELETED(dash_target)) + turf_dist_to_target += get_dist(dash_target, O) + if(get_dist(src, O) >= MINER_DASH_RANGE && turf_dist_to_target <= self_dist_to_target && !islava(O) && !ischasm(O)) + var/valid = TRUE + for(var/turf/T in getline(own_turf, O)) + if(is_blocked_turf(T, TRUE)) + valid = FALSE + continue + if(valid) + accessable_turfs[O] = turf_dist_to_target + var/turf/target_turf + if(!QDELETED(dash_target)) + var/closest_dist = MINER_DASH_RANGE + for(var/t in accessable_turfs) + if(accessable_turfs[t] < closest_dist) + closest_dist = accessable_turfs[t] + for(var/t in accessable_turfs) + if(accessable_turfs[t] != closest_dist) + accessable_turfs -= t + if(!LAZYLEN(accessable_turfs)) + return + dash_cooldown = world.time + initial(dash_cooldown) + target_turf = pick(accessable_turfs) + var/turf/step_back_turf = get_step(target_turf, get_cardinal_dir(target_turf, own_turf)) + var/turf/step_forward_turf = get_step(own_turf, get_cardinal_dir(own_turf, target_turf)) + new /obj/effect/temp_visual/small_smoke/halfsecond(step_back_turf) + new /obj/effect/temp_visual/small_smoke/halfsecond(step_forward_turf) + var/obj/effect/temp_visual/decoy/D = new /obj/effect/temp_visual/decoy(loc, src) + animate(D, alpha = 0, time = 5) + forceMove(step_back_turf) + playsound(own_turf, 'sound/weapons/punchmiss.ogg', 40, 1, -1) + dashing = TRUE + alpha = 0 + animate(src, alpha = 255, time = 5) + sleep(2) + D.forceMove(step_forward_turf) + forceMove(target_turf) + playsound(target_turf, 'sound/weapons/punchmiss.ogg', 40, 1, -1) + sleep(1) + dashing = FALSE + shoot_ka() + return TRUE + +/mob/living/simple_animal/hostile/megafauna/blood_drunk_miner/proc/transform_weapon() + if(time_until_next_transform <= world.time) + miner_saw.transform_cooldown = 0 + miner_saw.transform_weapon(src, TRUE) + icon_state = "miner[miner_saw.active ? "_transformed":""]" + icon_living = "miner[miner_saw.active ? "_transformed":""]" + time_until_next_transform = world.time + rand(50, 100) + +/obj/effect/temp_visual/dir_setting/miner_death + icon_state = "miner_death" + duration = 15 + +/obj/effect/temp_visual/dir_setting/miner_death/Initialize(mapload, set_dir) + . = ..() + INVOKE_ASYNC(src, .proc/fade_out) + +/obj/effect/temp_visual/dir_setting/miner_death/proc/fade_out() + var/matrix/M = new + M.Turn(pick(90, 270)) + var/final_dir = dir + if(dir & (EAST|WEST)) //Facing east or west + final_dir = pick(NORTH, SOUTH) //So you fall on your side rather than your face or ass + + animate(src, transform = M, pixel_y = -6, dir = final_dir, time = 2, easing = EASE_IN|EASE_OUT) + sleep(5) + animate(src, color = list("#A7A19E", "#A7A19E", "#A7A19E", list(0, 0, 0)), time = 10, easing = EASE_IN, flags = ANIMATION_PARALLEL) + sleep(4) + animate(src, alpha = 0, time = 6, easing = EASE_OUT, flags = ANIMATION_PARALLEL) + +#undef MINER_DASH_RANGE \ No newline at end of file diff --git a/code/modules/mob/living/status_procs.dm b/code/modules/mob/living/status_procs.dm index 2eea5755293..d4406a386b5 100644 --- a/code/modules/mob/living/status_procs.dm +++ b/code/modules/mob/living/status_procs.dm @@ -548,3 +548,37 @@ dna.SetSEState(block, 0, 1) //Fix the gene genemutcheck(src, block,null, MUTCHK_FORCED) dna.UpdateSE() + +///////////////////////////////////// STUN ABSORPTION ///////////////////////////////////// + +/mob/living/proc/add_stun_absorption(key, duration, priority, message, self_message, examine_message) +//adds a stun absorption with a key, a duration in deciseconds, its priority, and the messages it makes when you're stun/examined, if any + if(!islist(stun_absorption)) + stun_absorption = list() + if(stun_absorption[key]) + stun_absorption[key]["end_time"] = world.time + duration + stun_absorption[key]["priority"] = priority + stun_absorption[key]["stuns_absorbed"] = 0 + else + stun_absorption[key] = list("end_time" = world.time + duration, "priority" = priority, "stuns_absorbed" = 0, \ + "visible_message" = message, "self_message" = self_message, "examine_message" = examine_message) + +/mob/living/proc/absorb_stun(amount, ignoring_flag_presence) + if(!amount || amount <= 0 || stat || ignoring_flag_presence || !islist(stun_absorption)) + return FALSE + var/priority_absorb_key + var/highest_priority + for(var/i in stun_absorption) + if(stun_absorption[i]["end_time"] > world.time && (!priority_absorb_key || stun_absorption[i]["priority"] > highest_priority)) + priority_absorb_key = stun_absorption[i] + highest_priority = priority_absorb_key["priority"] + if(priority_absorb_key) + if(priority_absorb_key["visible_message"] || priority_absorb_key["self_message"]) + if(priority_absorb_key["visible_message"] && priority_absorb_key["self_message"]) + visible_message("[src][priority_absorb_key["visible_message"]]", "[priority_absorb_key["self_message"]]") + else if(priority_absorb_key["visible_message"]) + visible_message("[src][priority_absorb_key["visible_message"]]") + else if(priority_absorb_key["self_message"]) + to_chat(src, "[priority_absorb_key["self_message"]]") + priority_absorb_key["stuns_absorbed"] += amount + return TRUE \ No newline at end of file diff --git a/icons/effects/bleed.dmi b/icons/effects/bleed.dmi new file mode 100644 index 00000000000..d563334f632 Binary files /dev/null and b/icons/effects/bleed.dmi differ diff --git a/icons/mob/alienqueen.dmi b/icons/mob/alienqueen.dmi index b01af7e71b4..5d09aad2480 100644 Binary files a/icons/mob/alienqueen.dmi and b/icons/mob/alienqueen.dmi differ diff --git a/icons/mob/inhands/64x64_lefthand.dmi b/icons/mob/inhands/64x64_lefthand.dmi new file mode 100644 index 00000000000..69b9922f140 Binary files /dev/null and b/icons/mob/inhands/64x64_lefthand.dmi differ diff --git a/icons/mob/inhands/64x64_righthand.dmi b/icons/mob/inhands/64x64_righthand.dmi new file mode 100644 index 00000000000..09cef64e2b0 Binary files /dev/null and b/icons/mob/inhands/64x64_righthand.dmi differ diff --git a/icons/obj/lavaland/artefacts.dmi b/icons/obj/lavaland/artefacts.dmi index 45927256e35..a0486389b74 100644 Binary files a/icons/obj/lavaland/artefacts.dmi and b/icons/obj/lavaland/artefacts.dmi differ diff --git a/paradise.dme b/paradise.dme index 2cbae757457..09323624471 100644 --- a/paradise.dme +++ b/paradise.dme @@ -1926,6 +1926,7 @@ #include "code\modules\mob\living\simple_animal\hostile\tree.dm" #include "code\modules\mob\living\simple_animal\hostile\venus_human_trap.dm" #include "code\modules\mob\living\simple_animal\hostile\winter_mobs.dm" +#include "code\modules\mob\living\simple_animal\hostile\megafauna\blood_drunk_miner.dm" #include "code\modules\mob\living\simple_animal\hostile\megafauna\bubblegum.dm" #include "code\modules\mob\living\simple_animal\hostile\megafauna\colossus.dm" #include "code\modules\mob\living\simple_animal\hostile\megafauna\drake.dm" diff --git a/sound/magic/fellowship_armory.ogg b/sound/magic/fellowship_armory.ogg new file mode 100644 index 00000000000..c108d4621c6 Binary files /dev/null and b/sound/magic/fellowship_armory.ogg differ