diff --git a/code/__DEFINES/status_effects.dm b/code/__DEFINES/status_effects.dm index 4c1ac003864..a270bd5a302 100644 --- a/code/__DEFINES/status_effects.dm +++ b/code/__DEFINES/status_effects.dm @@ -71,6 +71,8 @@ #define STATUS_EFFECT_SAWBLEED /datum/status_effect/saw_bleed //if the bleed builds up enough, takes a ton of damage +#define STATUS_EFFECT_BLOODLETTING /datum/status_effect/saw_bleed/bloodletting //nerfed version + #define STATUS_EFFECT_GROUNDPOUND /datum/status_effect/stacking/ground_pound //if hit twice, the third attack will fling the target #define STATUS_EFFECT_TELEPORTSICK /datum/status_effect/teleport_sickness //increasing debuffs as you rapidly teleport. diff --git a/code/__HELPERS/lists.dm b/code/__HELPERS/lists.dm index 3eaf5356dd5..bff94ef67f0 100644 --- a/code/__HELPERS/lists.dm +++ b/code/__HELPERS/lists.dm @@ -900,3 +900,17 @@ proc/dd_sortedObjectList(list/incoming) . = list() for(var/thing in flat_list) .[thing] = TRUE + +///compare two lists, returns TRUE if they are the same +/proc/compare_list(list/l, list/d) + if(!islist(l) || !islist(d)) + return FALSE + + if(length(l) != length(d)) + return FALSE + + for(var/i in 1 to length(l)) + if(l[i] != d[i]) + return FALSE + + return TRUE diff --git a/code/datums/status_effects/buffs.dm b/code/datums/status_effects/buffs.dm index 12628c3addb..214852e1799 100644 --- a/code/datums/status_effects/buffs.dm +++ b/code/datums/status_effects/buffs.dm @@ -55,7 +55,7 @@ /datum/status_effect/shadow_mend id = "shadow_mend" - duration = 30 + duration = 3 SECONDS alert_type = /obj/screen/alert/status_effect/shadow_mend /obj/screen/alert/status_effect/shadow_mend @@ -80,9 +80,11 @@ /datum/status_effect/void_price id = "void_price" - duration = 300 - tick_interval = 30 + duration = 30 SECONDS + tick_interval = 3 SECONDS + status_type = STATUS_EFFECT_REFRESH alert_type = /obj/screen/alert/status_effect/void_price + var/price = 3 //This is how much hp you lose per tick. Each time the buff is refreshed, it increased by 1. Healing too much in a short period of time will cause your swift demise /obj/screen/alert/status_effect/void_price name = "Void Price" @@ -91,8 +93,12 @@ /datum/status_effect/void_price/tick() playsound(owner, 'sound/weapons/bite.ogg', 50, 1) - owner.adjustBruteLoss(3) + owner.adjustBruteLoss(price) +/datum/status_effect/void_price/refresh() + price++ + return ..() + /datum/status_effect/blooddrunk id = "blooddrunk" duration = 10 diff --git a/code/datums/status_effects/debuffs.dm b/code/datums/status_effects/debuffs.dm index 75e8fd87a1e..900c1a62eb4 100644 --- a/code/datums/status_effects/debuffs.dm +++ b/code/datums/status_effects/debuffs.dm @@ -77,6 +77,7 @@ var/delay_before_decay = 5 var/bleed_damage = 200 var/needs_to_bleed = FALSE + var/bleed_cap = 10 /datum/status_effect/saw_bleed/Destroy() if(owner) @@ -113,7 +114,7 @@ owner.underlays -= bleed_underlay bleed_amount += amount if(bleed_amount) - if(bleed_amount >= 10) + if(bleed_amount >= bleed_cap) needs_to_bleed = TRUE qdel(src) else @@ -137,6 +138,12 @@ else new /obj/effect/temp_visual/bleed(get_turf(owner)) +/datum/status_effect/saw_bleed/bloodletting + id = "bloodletting" + bleed_cap = 7 + bleed_damage = 25 //Seems weak (it is) but it also works on humans and bypasses armor SOOOO + bleed_amount = 6 + /datum/status_effect/stacking/ground_pound id = "ground_pound" tick_interval = 5 SECONDS diff --git a/code/game/gamemodes/wizard/spellbook.dm b/code/game/gamemodes/wizard/spellbook.dm index 8f03ced04f9..eef025e45d0 100644 --- a/code/game/gamemodes/wizard/spellbook.dm +++ b/code/game/gamemodes/wizard/spellbook.dm @@ -496,6 +496,14 @@ log_name = "SI" category = "Weapons and Armors" +/datum/spellbook_entry/item/cursed_katana + name = "Cursed Katana" + desc = "A cursed artefact, used to seal a horrible being inside the katana, which has now reformed. Can be used to make multiple powerful combos, examine it to see them. Can not be dropped. On death, you will dust." + item_path = /obj/item/organ/internal/cyberimp/arm/katana + cost = 1 + log_name = "CK" + category = "Weapons and Armors" + /datum/spellbook_entry/item/spell_blade name = "Spellblade" desc = "A magical sword that can be enchanted by using it in hand to have a unique on-hit effect. Lighting: arcs electricity between nearby targets, stunning and damaging them. Fire: creates a massive ball of fire on hit, and makes the wielder immune to fire. Bluespace: allows you to strike people from a range, teleporting you to them. Forceshield: on hit, makes you stun immune for 3 seconds and reduces damage by half." diff --git a/code/game/objects/items/weapons/weaponry.dm b/code/game/objects/items/weapons/weaponry.dm index be234ebaa07..2f91305ed3f 100644 --- a/code/game/objects/items/weapons/weaponry.dm +++ b/code/game/objects/items/weapons/weaponry.dm @@ -88,8 +88,6 @@ resistance_flags = FIRE_PROOF needs_permit = TRUE -/obj/item/katana/cursed - slot_flags = null /obj/item/katana/suicide_act(mob/user) user.visible_message("[user] is slitting [user.p_their()] stomach open with [src]! It looks like [user.p_theyre()] trying to commit seppuku.") diff --git a/code/modules/mining/abandonedcrates.dm b/code/modules/mining/abandonedcrates.dm index ad83f635888..b88771835e5 100644 --- a/code/modules/mining/abandonedcrates.dm +++ b/code/modules/mining/abandonedcrates.dm @@ -143,7 +143,7 @@ new /obj/item/gun/projectile/automatic/pistol(src) new /obj/item/ammo_box/magazine/m10mm(src) if(98) - new /obj/item/katana/cursed(src) + new /obj/item/organ/internal/cyberimp/arm/katana(src) if(99) new /obj/item/storage/belt/champion(src) new /obj/item/clothing/mask/luchador(src) diff --git a/code/modules/mining/lavaland/necropolis_chests.dm b/code/modules/mining/lavaland/necropolis_chests.dm index 4dfb3f4eaf9..7647d23f33f 100644 --- a/code/modules/mining/lavaland/necropolis_chests.dm +++ b/code/modules/mining/lavaland/necropolis_chests.dm @@ -22,7 +22,7 @@ if(3) new /obj/item/soulstone/anybody(src) if(4) - new /obj/item/katana/cursed(src) + new /obj/item/organ/internal/cyberimp/arm/katana(src) if(5) new /obj/item/clothing/glasses/godeye(src) if(6) @@ -417,3 +417,283 @@ #undef RIGHT_HAND #undef LEFT_HAND + +/obj/item/organ/internal/cyberimp/arm/katana + name = "dark shard" + desc = "An eerie metal shard surrounded by dark energies." + icon = 'icons/obj/lavaland/artefacts.dmi' + icon_state = "cursed_katana_organ" + status = 0 + contents = newlist(/obj/item/cursed_katana) + + +/obj/item/organ/internal/cyberimp/arm/katana/attack_self(mob/living/carbon/user, modifiers) + . = ..() + to_chat(user,"The mass goes up your arm and inside it!") + playsound(user, 'sound/misc/demon_consume.ogg', 50, TRUE) + RegisterSignal(user, COMSIG_MOB_DEATH, .proc/user_death) + + user.drop_item() + insert(user) + +/obj/item/organ/internal/cyberimp/arm/katana/emp_act() //Organic, no emp stuff + return + +/obj/item/organ/internal/cyberimp/arm/katana/Retract() + var/obj/item/cursed_katana/katana = holder + if(!katana || katana.shattered) + return FALSE + if(!katana.drew_blood) + to_chat(owner, "[katana] lashes out at you in hunger!") + playsound(owner, 'sound/misc/demon_attack1.ogg', 50, TRUE) + if(parent_organ) + owner.apply_damage(25, BRUTE, parent_organ, TRUE) + katana.drew_blood = FALSE + katana.clean_blood() + return ..() + +/obj/item/organ/internal/cyberimp/arm/katana/Extend() + for(var/obj/item/cursed_katana/katana in contents) + if(katana.shattered) + to_chat(owner, " Your cursed katana has not reformed yet!") + return FALSE + return ..() + +/obj/item/organ/internal/cyberimp/arm/katana/proc/user_death(mob/user) + SIGNAL_HANDLER + INVOKE_ASYNC(src, .proc/user_death_async, user) + +/obj/item/organ/internal/cyberimp/arm/katana/proc/user_death_async(mob/user) + Retract() + user.visible_message("[user] begins to turn to dust, his soul being contained within [src]!", + "You feel your body begin to turn to dust, your soul being drawn into [src]!") + forceMove(get_turf(owner)) + remove(user) + addtimer(CALLBACK(user, /mob/.proc/dust), 1 SECONDS) + + +/obj/item/organ/internal/cyberimp/arm/katana/remove(mob/living/carbon/M, special) + UnregisterSignal(M, COMSIG_MOB_DEATH) + . = ..() + + +#define LEFT_SLASH "Harm" +#define RIGHT_SLASH "Disarm" +#define COMBO_STEPS "steps" +#define COMBO_PROC "proc" +#define ATTACK_STRIKE "Hilt Strike" +#define ATTACK_SLICE "Wide Slice" +#define ATTACK_DASH "Dash Attack" +#define ATTACK_CUT "Tendon Cut" +#define ATTACK_HEAL "Dark Heal" +#define ATTACK_SHATTER "Shatter" + + +/obj/item/cursed_katana + name = "cursed katana" + desc = "A katana used to seal something vile away long ago. \ + Even with the weapon destroyed, all the pieces containing the creature have coagulated back together to find a new host." + icon = 'icons/obj/lavaland/artefacts.dmi' + icon_state = "cursed_katana" + lefthand_file = 'icons/mob/inhands/items_lefthand.dmi' + righthand_file = 'icons/mob/inhands/items_righthand.dmi' + force = 15 + armour_penetration_percentage = 40 + armour_penetration_flat = 10 + block_chance = 50 + sharp = TRUE + w_class = WEIGHT_CLASS_HUGE + attack_verb = list("attack", "slash", "stab", "slice", "tear", "lacerate", "rip", "dice", "cut") + hitsound = 'sound/weapons/bladeslice.ogg' + var/shattered = FALSE + var/drew_blood = FALSE + var/timerid + var/list/input_list = list() + var/list/combo_strings = list() + var/static/list/combo_list = list( + ATTACK_STRIKE = list(COMBO_STEPS = list(LEFT_SLASH, LEFT_SLASH, RIGHT_SLASH), COMBO_PROC = .proc/strike), + ATTACK_SLICE = list(COMBO_STEPS = list(RIGHT_SLASH, LEFT_SLASH, LEFT_SLASH), COMBO_PROC = .proc/slice), + ATTACK_DASH = list(COMBO_STEPS = list(LEFT_SLASH, RIGHT_SLASH, RIGHT_SLASH), COMBO_PROC = .proc/dash), + ATTACK_CUT = list(COMBO_STEPS = list(RIGHT_SLASH, RIGHT_SLASH, LEFT_SLASH), COMBO_PROC = .proc/cut), + ATTACK_HEAL = list(COMBO_STEPS = list(LEFT_SLASH, RIGHT_SLASH, LEFT_SLASH, RIGHT_SLASH), COMBO_PROC = .proc/heal), + ATTACK_SHATTER = list(COMBO_STEPS = list(RIGHT_SLASH, LEFT_SLASH, RIGHT_SLASH, LEFT_SLASH), COMBO_PROC = .proc/shatter), + ) + +/obj/item/cursed_katana/Initialize(mapload) + . = ..() + for(var/combo in combo_list) + var/list/combo_specifics = combo_list[combo] + var/step_string = english_list(combo_specifics[COMBO_STEPS]) + combo_strings += ("[combo] - [step_string]") + +/obj/item/cursed_katana/examine(mob/user) + . = ..() + . += drew_blood ? ("It's sated... for now.") : ("It will not be sated until it tastes blood.") + . += combo_strings + +/obj/item/cursed_katana/dropped(mob/user) + . = ..() + reset_inputs(null, TRUE) + +/obj/item/cursed_katana/attack_self(mob/user) + . = ..() + reset_inputs(user, TRUE) + +/obj/item/cursed_katana/attack(mob/living/target, mob/user, click_parameters) + if(target.stat == DEAD || target == user) //No, you can not stab yourself to cloak / not take the penalty for not drawing blood + return ..() + if(HAS_TRAIT(user, TRAIT_PACIFISM)) + to_chat(user, "You don't want to harm [target]!") + return TRUE + drew_blood = TRUE + if(user.a_intent == INTENT_DISARM) + input_list += RIGHT_SLASH + if(user.a_intent == INTENT_HARM) + input_list += LEFT_SLASH + if(ishostile(target)) + user.changeNext_move(CLICK_CD_RAPID) + if(length(input_list) > 4) + reset_inputs(user, TRUE) + if(check_input(target, user)) + reset_inputs(null, TRUE) + return TRUE + else + timerid = addtimer(CALLBACK(src, .proc/reset_inputs, user, FALSE), 5 SECONDS, TIMER_UNIQUE|TIMER_OVERRIDE|TIMER_STOPPABLE) + return ..() + +/obj/item/cursed_katana/hit_reaction(mob/living/carbon/human/owner, atom/movable/hitby, attack_text = "the attack", final_block_chance = 0, damage = 0, attack_type = MELEE_ATTACK) + if(attack_type == PROJECTILE_ATTACK) + final_block_chance = 0 //Don't bring a sword to a gunfight + return ..() + +/obj/item/cursed_katana/proc/check_input(mob/living/target, mob/user) + for(var/combo in combo_list) + var/list/combo_specifics = combo_list[combo] + if(compare_list(input_list,combo_specifics[COMBO_STEPS])) + INVOKE_ASYNC(src, combo_specifics[COMBO_PROC], target, user) + return TRUE + return FALSE + +/obj/item/cursed_katana/proc/reset_inputs(mob/user, deltimer) + input_list.Cut() + if(user) + to_chat(user, "You return to neutral stance.") + if(deltimer && timerid) + deltimer(timerid) + +/obj/item/cursed_katana/proc/strike(mob/living/target, mob/user) + user.visible_message("[user] strikes [target] with [src]'s hilt!", + "You hilt strike [target]!") + to_chat(target, "You've been struck by [user]!") + playsound(src, 'sound/weapons/genhit3.ogg', 50, TRUE) + RegisterSignal(target, COMSIG_MOVABLE_IMPACT, .proc/strike_throw_impact) + var/atom/throw_target = get_edge_target_turf(target, user.dir) + target.throw_at(throw_target, 5, 3, user, FALSE, callback = CALLBACK(/datum/.proc/UnregisterSignal, target, COMSIG_MOVABLE_IMPACT)) + target.apply_damage(17, BRUTE, BODY_ZONE_CHEST) + to_chat(target, "You've been struck by [user]!") + user.do_attack_animation(target, ATTACK_EFFECT_PUNCH) + +/obj/item/cursed_katana/proc/strike_throw_impact(mob/living/source, atom/hit_atom, datum/thrownthing/thrownthing) + SIGNAL_HANDLER + + UnregisterSignal(source, COMSIG_MOVABLE_IMPACT) + source.apply_damage(5, BRUTE, BODY_ZONE_CHEST) + if(ishostile(source)) + var/mob/living/simple_animal/hostile/target = source + target.ranged_cooldown += 2 SECONDS + else if(iscarbon(source)) + var/mob/living/carbon/target = source + target.AdjustConfused(8 SECONDS) + return NONE + +/obj/item/cursed_katana/proc/slice(mob/living/target, mob/user) + user.visible_message("[user] does a wide slice!", + "You do a wide slice!") + playsound(src, 'sound/weapons/bladeslice.ogg', 50, TRUE) + var/turf/user_turf = get_turf(user) + var/dir_to_target = get_dir(user_turf, get_turf(target)) + var/static/list/cursed_katana_slice_angles = list(0, -45, 45, -90, 90) //so that the animation animates towards the target clicked and not towards a side target + for(var/iteration in cursed_katana_slice_angles) + var/turf/T = get_step(user_turf, turn(dir_to_target, iteration)) + user.do_attack_animation(T, ATTACK_EFFECT_CLAW) + for(var/mob/living/additional_target in T) + if(user.Adjacent(additional_target) && additional_target.density) + additional_target.apply_damage(15, BRUTE, BODY_ZONE_CHEST, TRUE) + to_chat(additional_target, "You've been sliced by [user]!") + target.apply_damage(5, BRUTE, BODY_ZONE_CHEST, TRUE) + +/obj/item/cursed_katana/proc/heal(mob/living/target, mob/living/user) + user.visible_message("[user] lets [src] feast on [target]'s blood!", + "You let [src] feast on [target], and it heals you, at a price!") + target.apply_damage(15, BRUTE, BODY_ZONE_CHEST, TRUE) + user.apply_status_effect(STATUS_EFFECT_SHADOW_MEND) + +/obj/item/cursed_katana/proc/cut(mob/living/target, mob/user) + user.visible_message("[user] cuts [target]'s tendons!", + "You tendon cut [target]!") + to_chat(target, "Your tendons have been cut by [user]!") + target.apply_damage(15, BRUTE, BODY_ZONE_CHEST, TRUE) + user.do_attack_animation(target, ATTACK_EFFECT_DISARM) + playsound(src, 'sound/weapons/rapierhit.ogg', 50, TRUE) + var/datum/status_effect/saw_bleed/bloodletting/A = target.has_status_effect(STATUS_EFFECT_BLOODLETTING) + if(!A) + target.apply_status_effect(STATUS_EFFECT_BLOODLETTING) + else + A.add_bleed(6) + +/obj/item/cursed_katana/proc/dash(mob/living/target, mob/user) + var/turf/dash_target = get_turf(target) + var/turf/user_turf = get_turf(user) + if(!is_teleport_allowed(dash_target.z)) //No teleporting at CC + to_chat(user, ("You can not dash here!")) + return + user.visible_message("[user] dashes through [target]!", + "You dash through [target]!") + to_chat(target, ("[user] dashes through you!")) + playsound(src, 'sound/magic/blink.ogg', 50, TRUE) + target.apply_damage(17, BRUTE, BODY_ZONE_CHEST, TRUE) + for(var/distance in 0 to 8) + var/turf/current_dash_target = dash_target + current_dash_target = get_step(current_dash_target, user.dir) + if(!is_blocked_turf(current_dash_target, TRUE)) + dash_target = current_dash_target + else + break + for(var/mob/living/additional_target in dash_target) //Slash through every mob you cut through + additional_target.apply_damage(15, BRUTE, BODY_ZONE_CHEST, TRUE) + to_chat(additional_target, "You've been sliced by [user]!") + user_turf.Beam(dash_target, icon_state = "warp_beam", time = 0.3 SECONDS, maxdistance = INFINITY) + user.forceMove(dash_target) + +/obj/item/cursed_katana/proc/shatter(mob/living/target, mob/user) + user.visible_message("[user] shatters [src] over [target]!", + "You shatter [src] over [target]!") + to_chat(target, "[user] shatters [src] over you!") + target.apply_damage((ishostile(target) ? 75 : 35), BRUTE, BODY_ZONE_CHEST, TRUE) + target.KnockDown(5 SECONDS) + target.adjustStaminaLoss(60) //Takes 4 hits to do, breaks your weapon. Perfectly fine. + user.do_attack_animation(target, ATTACK_EFFECT_SMASH) + playsound(src, 'sound/effects/glassbr3.ogg', 100, TRUE) + if(ishuman(user)) + var/mob/living/carbon/human/H = user + for(var/obj/item/organ/internal/cyberimp/arm/katana/O in H.internal_organs) + if(O.holder == src) + O.Retract() + shattered = TRUE + addtimer(CALLBACK(src, .proc/coagulate, user), 45 SECONDS) + +/obj/item/cursed_katana/proc/coagulate(mob/user) + to_chat(user, "[src] reforms!") + shattered = FALSE + playsound(src, 'sound/misc/demon_consume.ogg', 50, TRUE) + +#undef LEFT_SLASH +#undef RIGHT_SLASH +#undef COMBO_STEPS +#undef COMBO_PROC +#undef ATTACK_STRIKE +#undef ATTACK_SLICE +#undef ATTACK_DASH +#undef ATTACK_CUT +#undef ATTACK_HEAL +#undef ATTACK_SHATTER diff --git a/icons/mob/inhands/items_lefthand.dmi b/icons/mob/inhands/items_lefthand.dmi index 196b266990c..621b769c085 100644 Binary files a/icons/mob/inhands/items_lefthand.dmi and b/icons/mob/inhands/items_lefthand.dmi differ diff --git a/icons/mob/inhands/items_righthand.dmi b/icons/mob/inhands/items_righthand.dmi index 372a5dbfd18..a8c554abec2 100644 Binary files a/icons/mob/inhands/items_righthand.dmi and b/icons/mob/inhands/items_righthand.dmi differ diff --git a/icons/obj/lavaland/artefacts.dmi b/icons/obj/lavaland/artefacts.dmi index 32eeb480a37..5244f3667a8 100644 Binary files a/icons/obj/lavaland/artefacts.dmi and b/icons/obj/lavaland/artefacts.dmi differ