diff --git a/code/__DEFINES/dcs/signals/signals_atom/signals_atom_movable.dm b/code/__DEFINES/dcs/signals/signals_atom/signals_atom_movable.dm index ead6717bcbe..bfbaf57d9c6 100644 --- a/code/__DEFINES/dcs/signals/signals_atom/signals_atom_movable.dm +++ b/code/__DEFINES/dcs/signals/signals_atom/signals_atom_movable.dm @@ -53,6 +53,10 @@ #define COMSIG_MOVABLE_THROW_LANDED "movable_throw_landed" ///from base of atom/movable/on_changed_z_level(): (turf/old_turf, turf/new_turf, same_z_layer) #define COMSIG_MOVABLE_Z_CHANGED "movable_ztransit" +/// from /atom/movable/can_z_move(): (turf/start, turf/destination) +#define COMSIG_CAN_Z_MOVE "movable_can_z_move" + /// Return to block z movement + #define COMPONENT_CANT_Z_MOVE (1<<0) ///called before hearing a message from atom/movable/Hear(): #define COMSIG_MOVABLE_PRE_HEAR "movable_pre_hear" ///cancel hearing the message because we're doing something else presumably diff --git a/code/__DEFINES/status_effects.dm b/code/__DEFINES/status_effects.dm index 936fd0b170f..8ea7128f5f1 100644 --- a/code/__DEFINES/status_effects.dm +++ b/code/__DEFINES/status_effects.dm @@ -60,6 +60,7 @@ #define STASIS_ADMIN "stasis_admin" #define STASIS_LEGION_EATEN "stasis_eaten" #define STASIS_SLIME_BZ "stasis_slime_bz" +#define STASIS_ELDRITCH_ETHER "stasis_eldritch_ether" #define STASIS_NETPOD_EFFECT "stasis_netpod" diff --git a/code/__DEFINES/traits/declarations.dm b/code/__DEFINES/traits/declarations.dm index e48756840f2..461aef1c703 100644 --- a/code/__DEFINES/traits/declarations.dm +++ b/code/__DEFINES/traits/declarations.dm @@ -1332,6 +1332,8 @@ Remember to update _globalvars/traits.dm if you're adding/removing/renaming trai /// Trait given to anything linked to, not necessarily allied to, the mansus #define TRAIT_MANSUS_TOUCHED "mansus_touched" +/// Trait given to all participants in a heretic arena +#define TRAIT_ELDRITCH_ARENA_PARTICIPANT "eldritch_arena_participant" // These traits are used in IS_X() as an OR, and is utilized for pseudoantags (such as deathmatch or domains) so they don't need to actually get antag status. // To specifically and only get the antag datum, GET_X() exists now. diff --git a/code/__DEFINES/traits/sources.dm b/code/__DEFINES/traits/sources.dm index 723100637b7..762126c3da7 100644 --- a/code/__DEFINES/traits/sources.dm +++ b/code/__DEFINES/traits/sources.dm @@ -56,6 +56,8 @@ #define SHOES_TRAIT "shoes" /// Trait inherited by implants #define IMPLANT_TRAIT "implant" +/// Traits given by the heretic arena spell +#define HERETIC_ARENA_TRAIT "heretic_arena" #define GLASSES_TRAIT "glasses" /// inherited from riding vehicles #define VEHICLE_TRAIT "vehicle" diff --git a/code/_globalvars/traits/_traits.dm b/code/_globalvars/traits/_traits.dm index 6b2bfabb64d..94e5b668294 100644 --- a/code/_globalvars/traits/_traits.dm +++ b/code/_globalvars/traits/_traits.dm @@ -249,6 +249,7 @@ GLOBAL_LIST_INIT(traits_by_type, list( "TRAIT_EASYDISMEMBER" = TRAIT_EASYDISMEMBER, "TRAIT_ECHOLOCATION_EXTRA_RANGE" = TRAIT_ECHOLOCATION_EXTRA_RANGE, "TRAIT_ECHOLOCATION_RECEIVER" = TRAIT_ECHOLOCATION_RECEIVER, + "TRAIT_ELDRITCH_ARENA_PARTICIPANT" = TRAIT_ELDRITCH_ARENA_PARTICIPANT, "TRAIT_ELDRITCH_PAINTING_EXAMINE" = TRAIT_ELDRITCH_PAINTING_EXAMINE, "TRAIT_ELITE_CHALLENGER" = TRAIT_ELITE_CHALLENGER, "TRAIT_EMOTEMUTE" = TRAIT_EMOTEMUTE, diff --git a/code/controllers/subsystem/throwing.dm b/code/controllers/subsystem/throwing.dm index da403db9e45..7425dc65efe 100644 --- a/code/controllers/subsystem/throwing.dm +++ b/code/controllers/subsystem/throwing.dm @@ -89,6 +89,8 @@ SUBSYSTEM_DEF(throwing) var/delayed_time = 0 ///The last world.time value stored when the thrownthing was moving. var/last_move = 0 + /// If our thrownthing has been blocked + var/blocked = FALSE /datum/thrownthing/New(thrownthing, target, init_dir, maxrange, speed, thrower, diagonals_first, force, gentle, callback, target_zone) . = ..() diff --git a/code/datums/proximity_monitor/fields/heretic_arena.dm b/code/datums/proximity_monitor/fields/heretic_arena.dm new file mode 100644 index 00000000000..af949963a7c --- /dev/null +++ b/code/datums/proximity_monitor/fields/heretic_arena.dm @@ -0,0 +1,287 @@ +GLOBAL_LIST_EMPTY(heretic_arenas) + +// Invisible effect that doesnt exist outside of containing the prox monitor +/obj/effect/abstract/heretic_arena + icon = null + icon_state = null + alpha = 0 + invisibility = INVISIBILITY_ABSTRACT + mouse_opacity = MOUSE_OPACITY_TRANSPARENT + anchored = TRUE + resistance_flags = INDESTRUCTIBLE + /// Proximity monitor that handles the effects we are looking for + var/datum/proximity_monitor/advanced/heretic_arena/arena + +/obj/effect/abstract/heretic_arena/Initialize(mapload, range, duration, caster) + . = ..() + arena = new(src, range) + QDEL_IN(src, duration) + arena.set_caster(caster) + GLOB.heretic_arenas += src + +/obj/effect/abstract/heretic_arena/Destroy(force) + QDEL_NULL(arena) + GLOB.heretic_arenas -= src + . = ..() + +/datum/proximity_monitor/advanced/heretic_arena + /// Reference to the caster, the spell collapses if they leave the arena + var/arena_caster + /// List of mobs inside our arena + var/list/contained_mobs = list() + /// List of border walls we have placed on the edges of the monitor + var/list/border_walls = list() + /// List of blades we've so generously handed out to the participants + var/list/welfare_blades = list() + /// List of immunities given to our combatants + var/static/list/given_immunities = list( + TRAIT_BOMBIMMUNE, + TRAIT_IGNORESLOWDOWN, + TRAIT_NO_SLIP_ALL, + TRAIT_NOBREATH, + TRAIT_PIERCEIMMUNE, + TRAIT_PUSHIMMUNE, + TRAIT_RADIMMUNE, + TRAIT_RESISTCOLD, + TRAIT_RESISTHEAT, + TRAIT_RESISTHIGHPRESSURE, + TRAIT_RESISTLOWPRESSURE, + TRAIT_SHOCKIMMUNE, + TRAIT_SLEEPIMMUNE, + TRAIT_STUNIMMUNE, + TRAIT_FORCED_GRAVITY, + ) + +/datum/proximity_monitor/advanced/heretic_arena/New(atom/_host, range, _ignore_if_not_on_turf) + . = ..() + recalculate_field(full_recalc = TRUE) + var/list/things_in_range = range(range) + for(var/mob/living/carbon/human/human_in_range in things_in_range) + human_in_range.add_traits(given_immunities, HERETIC_ARENA_TRAIT) + contained_mobs += human_in_range + if(!IS_HERETIC(human_in_range)) + var/obj/item/melee/sickly_blade/training/new_blade = new(get_turf(human_in_range)) + welfare_blades += new_blade + INVOKE_ASYNC(human_in_range, TYPE_PROC_REF(/mob, put_in_hands), new_blade) + human_in_range.mind?.add_antag_datum(/datum/antagonist/heretic_arena_participant) + human_in_range.apply_status_effect(/datum/status_effect/arena_tracker) + RegisterSignal(human_in_range, COMSIG_CAN_Z_MOVE, PROC_REF(on_try_z_move)) + RegisterSignal(human_in_range, COMSIG_LADDER_TRAVEL, PROC_REF(on_try_ladder)) + RegisterSignal(human_in_range, COMSIG_MOVABLE_PRE_MOVE, PROC_REF(on_pre_move)) + RegisterSignal(human_in_range, COMSIG_MOVABLE_POST_TELEPORT, PROC_REF(on_teleport)) + +/datum/proximity_monitor/advanced/heretic_arena/Destroy() + for(var/mob/living/carbon/human/mob in contained_mobs) + mob.remove_traits(given_immunities, HERETIC_ARENA_TRAIT) + mob.remove_status_effect(/datum/status_effect/arena_tracker) + UnregisterSignal(mob, list(COMSIG_CAN_Z_MOVE, COMSIG_LADDER_TRAVEL, COMSIG_MOVABLE_PRE_MOVE, COMSIG_MOVABLE_POST_TELEPORT)) + if(mob.mind?.has_antag_datum(/datum/antagonist/heretic_arena_participant)) + mob.mind.remove_antag_datum(/datum/antagonist/heretic_arena_participant) + for(var/turf/to_restore in border_walls) + to_restore.ChangeTurf(border_walls[to_restore]) + for(var/obj/to_refund as anything in welfare_blades) + qdel(to_refund) + arena_caster = null + return ..() + +/datum/proximity_monitor/advanced/heretic_arena/setup_edge_turf(turf/target) + . = ..() + var/old_turf = target.type + target.ChangeTurf(/turf/closed/indestructible/heretic_wall) + border_walls += target + border_walls[target] += old_turf + +/datum/proximity_monitor/advanced/heretic_arena/field_edge_uncrossed(atom/movable/movable, turf/old_location, turf/new_location) + if(!isliving(movable)) + return + var/mob/living/living_mob = movable + addtimer(CALLBACK(living_mob, TYPE_PROC_REF(/mob/living, remove_status_effect), /datum/status_effect/arena_tracker), 10 SECONDS) + living_mob.remove_traits(given_immunities, HERETIC_ARENA_TRAIT) + if(living_mob == arena_caster) + QDEL_IN(host, 3 SECONDS) + +/// Prevents using ladders +/datum/proximity_monitor/advanced/heretic_arena/proc/on_try_ladder(mob/climber) + SIGNAL_HANDLER + return LADDER_TRAVEL_BLOCK + +/// If we try to enter a space turf that has a mirage, we will block the movement +/datum/proximity_monitor/advanced/heretic_arena/proc/on_pre_move(atom/movable/mover, atom/newloc) + if(locate(/atom/movable/mirage_holder) in newloc.contents) + return COMPONENT_MOVABLE_BLOCK_PRE_MOVE + +/// Blocks Z movement to new z levels +/datum/proximity_monitor/advanced/heretic_arena/proc/on_try_z_move(atom/movable/source, turf/start, turf/destination) + SIGNAL_HANDLER + if(start.z == destination.z) + return + return COMPONENT_CANT_Z_MOVE + +/// If our caster teleports away (after winning presumably) we'll collapse the arena so that it doens't needlessly linger +/datum/proximity_monitor/advanced/heretic_arena/proc/on_teleport(atom/teleportee, atom/destination, channel) + if(teleportee == arena_caster) + qdel(host) + +/datum/proximity_monitor/advanced/heretic_arena/proc/set_caster(atom/caster) + arena_caster = caster + +/turf/closed/indestructible/heretic_wall + name = "eldritch wall" + desc = "A wall penning in the sheep amongst the wolves. It glows with malevolent energy - prodding it is likely unwise." + icon = 'icons/turf/walls.dmi' + icon_state = "eldritch_forcewall" + opacity = FALSE + pass_flags_self = NONE // No PASSCLOSEDTURF because only arena victors are allowed to go in or out + +/turf/closed/indestructible/heretic_wall/CanAllowThrough(atom/movable/mover, border_dir) + if(isliving(mover)) + var/mob/living/living_mover = mover + var/datum/status_effect/arena_tracker/tracker = living_mover.has_status_effect(/datum/status_effect/arena_tracker) + if(tracker?.arena_victor) + return TRUE + return ..() + +/turf/closed/indestructible/heretic_wall/Bumped(atom/movable/bumped_atom) + . = ..() + if(!isliving(bumped_atom)) + return + var/mob/living/living_mob = bumped_atom + var/atom/target = get_edge_target_turf(living_mob, get_dir(src, get_step_away(living_mob, src))) + living_mob.throw_at(target, 4, 5) + to_chat(living_mob, span_userdanger("The wall repels you with tremendous force!")) + +/// Called when you crit somebody to update your crown +/datum/status_effect/arena_tracker/proc/on_crit_somebody() + owner.cut_overlay(crown_overlay) + crown_overlay = mutable_appearance('icons/mob/effects/crown.dmi', "arena_victor", -HALO_LAYER) + crown_overlay.pixel_y = 24 + owner.add_overlay(crown_overlay) + owner.remove_traits(list(TRAIT_ELDRITCH_ARENA_PARTICIPANT, TRAIT_NO_TELEPORT), TRAIT_STATUS_EFFECT(id)) + + // The mansus celebrates your efforts + if(IS_HERETIC(owner)) + owner.heal_overall_damage(60, 60, 60) + owner.adjustToxLoss(-60, forced = TRUE) // Slime heretics everywhere... + owner.adjustOxyLoss(-60) + if(iscarbon(owner)) + var/mob/living/carbon/carbon_owner = owner + for(var/datum/wound/wound as anything in carbon_owner.all_wounds) + wound.remove_wound() + + if(arena_victor) // No need to spam if we've already killed at least 1 person + return + if(IS_HERETIC(owner)) + to_chat(owner, span_big(span_hypnophrase("The mansus is pleased with your performance, you may leave now."))) + else + to_chat(owner, span_big(span_hypnophrase("You have done well, you may leave now."))) + arena_victor = TRUE + +/** + * Status applied to every mob in the heretic arena. + * Tracks the last person to damage owner. + * When owner enters crit, we send a signal to last_attacker status so they can leave the arena + */ + +/datum/status_effect/arena_tracker + id = "arena_tracker" + duration = STATUS_EFFECT_PERMANENT + tick_interval = STATUS_EFFECT_NO_TICK + status_type = STATUS_EFFECT_UNIQUE + alert_type = null + /// Tracks the last person who dealt damage to this mob + var/datum/weakref/last_attacker + /// If our mob is free to leave, set to true + var/arena_victor = FALSE + /// The overlay for our mob, changes color to indicate that they are a victor and are free to leave + var/mutable_appearance/crown_overlay + +/datum/status_effect/arena_tracker/on_apply() + RegisterSignal(owner, SIGNAL_ADDTRAIT(TRAIT_CRITICAL_CONDITION), PROC_REF(on_enter_crit)) + RegisterSignal(owner, COMSIG_MOB_APPLY_DAMAGE, PROC_REF(damage_taken)) + owner.add_traits(list(TRAIT_ELDRITCH_ARENA_PARTICIPANT, TRAIT_NO_TELEPORT), TRAIT_STATUS_EFFECT(id)) + crown_overlay = mutable_appearance('icons/mob/effects/crown.dmi', "arena_fighter", -HALO_LAYER) + crown_overlay.pixel_y = 24 + owner.add_overlay(crown_overlay) + return TRUE + +/datum/status_effect/arena_tracker/on_remove() + UnregisterSignal(owner, list(SIGNAL_ADDTRAIT(TRAIT_CRITICAL_CONDITION), COMSIG_MOB_APPLY_DAMAGE)) + owner.remove_traits(list(TRAIT_ELDRITCH_ARENA_PARTICIPANT, TRAIT_NO_TELEPORT), TRAIT_STATUS_EFFECT(id)) + owner.cut_overlay(crown_overlay) + crown_overlay = null + +// If our last attacker is an arena participant, we let them know they've scored a critical hit +/datum/status_effect/arena_tracker/proc/on_enter_crit(mob/owner) + SIGNAL_HANDLER + if(!last_attacker) + return // Safety check in case they somehow enter crit with *nobody* attacking them + var/mob/living/our_attacker = last_attacker.resolve() + if(!isliving(our_attacker) || our_attacker == owner) // We don't allow people to crit themselves as a valid way to escape + return + var/datum/status_effect/arena_tracker/their_tracker = our_attacker.has_status_effect(/datum/status_effect/arena_tracker) + if(!their_tracker) + return // Somebody killed us who isn't an arena participant + their_tracker.on_crit_somebody() + +/datum/status_effect/arena_tracker/proc/damage_taken( + datum/source, + damage_amount, + damagetype, + def_zone, + blocked, + wound_bonus, + bare_wound_bonus, + sharpness, + attack_direction, + attacking_item, + wound_clothing, +) + SIGNAL_HANDLER + if(isnull(attacking_item)) + return + if(!isobj(attacking_item)) + return + var/obj/attacking_object = attacking_item + + // Track being hit by a mob holding a stick + if(ismob(attacking_object.loc)) + last_attacker = WEAKREF(attacking_object.loc) + return + + // Track being hit by a mob throwing a stick + if(isitem(attacking_object)) + var/obj/item/thrown_item = attacking_item + var/thrown_by = thrown_item.thrownby?.resolve() + if(ismob(thrown_by)) + last_attacker = WEAKREF(thrown_by) + return + + // Edge case. If our attacking_item is a gun which the owner has dropped we need to find out who shot us + // Track being hit by a mob shooting a stick + if(isprojectile(attacking_object)) + var/obj/projectile/attacking_projectile = attacking_object + if(ismob(attacking_projectile.firer)) + last_attacker = WEAKREF(attacking_projectile.firer) + +/datum/antagonist/heretic_arena_participant + name = "Arena Participant" + show_in_roundend = FALSE + replace_banned = FALSE + objectives = list() + antag_hud_name = "brainwashed" + block_midrounds = FALSE + +/datum/antagonist/heretic_arena_participant/on_gain() + forge_objectives() + return ..() + +/datum/antagonist/heretic_arena_participant/forge_objectives() + var/datum/objective/survive = new /datum/objective + survive.owner = owner + survive.explanation_text = "You have been trapped in an arena. The only way out is to slaughter someone else. Kill your captor, or betray your friends - the choice is yours." + objectives += survive + var/datum/objective/fight_to_escape = new /datum/objective + fight_to_escape.owner = owner + fight_to_escape.explanation_text = "Escape is impossible. The only way out is to defeat another participant in this battle to the death. \ + A weapon has been bestowed unto you, granting you a fighting chance, it would be quite a shame were you to attempt to break it." + objectives += fight_to_escape diff --git a/code/game/atoms_movable.dm b/code/game/atoms_movable.dm index 0056f2f2216..5959d2c4d0d 100644 --- a/code/game/atoms_movable.dm +++ b/code/game/atoms_movable.dm @@ -449,6 +449,8 @@ if(z_move_flags & ZMOVE_FEEDBACK) to_chat(rider || src, span_warning("There's nowhere to go in that direction!")) return FALSE + if(SEND_SIGNAL(src, COMSIG_CAN_Z_MOVE, start, destination) & COMPONENT_CANT_Z_MOVE) + return FALSE if(z_move_flags & ZMOVE_FALL_CHECKS && (throwing || (movement_type & (FLYING|FLOATING)) || !has_gravity(start))) return FALSE if(z_move_flags & ZMOVE_CAN_FLY_CHECKS && !(movement_type & (FLYING|FLOATING)) && has_gravity(start)) diff --git a/code/game/objects/items/grenades/_grenade.dm b/code/game/objects/items/grenades/_grenade.dm index 802dd26f5cb..262a493cc75 100644 --- a/code/game/objects/items/grenades/_grenade.dm +++ b/code/game/objects/items/grenades/_grenade.dm @@ -25,6 +25,10 @@ var/dud_flags = NONE ///Is this grenade currently armed? var/active = FALSE + /// Sound played when the grenade is armed + var/grenade_arm_sound = 'sound/items/weapons/armbomb.ogg' + /// If the sound of the grenade should be varied + var/grenade_sound_vary = TRUE ///Is it a cluster grenade? We don't wanna spam admin logs with these. var/type_cluster = FALSE ///How long it takes for a grenade to explode after being armed @@ -156,7 +160,7 @@ if(shrapnel_type && shrapnel_radius) shrapnel_initialized = TRUE AddComponent(/datum/component/pellet_cloud, projectile_type = shrapnel_type, magnitude = shrapnel_radius) - playsound(src, 'sound/items/weapons/armbomb.ogg', volume, TRUE) + playsound(src, grenade_arm_sound, volume, grenade_sound_vary) if(istype(user)) user.add_mob_memory(/datum/memory/bomb_planted, antagonist = src) active = TRUE diff --git a/code/game/objects/items/grenades/chem_grenade.dm b/code/game/objects/items/grenades/chem_grenade.dm index 086d183d5b1..93af52924c2 100644 --- a/code/game/objects/items/grenades/chem_grenade.dm +++ b/code/game/objects/items/grenades/chem_grenade.dm @@ -234,7 +234,7 @@ active = TRUE update_icon_state() - playsound(src, 'sound/items/weapons/armbomb.ogg', volume, TRUE) + playsound(src, grenade_arm_sound, volume, grenade_sound_vary) if(landminemode) landminemode.activate() return diff --git a/code/game/objects/items/grenades/clusterbuster.dm b/code/game/objects/items/grenades/clusterbuster.dm index fe5666267e0..33f0c877483 100644 --- a/code/game/objects/items/grenades/clusterbuster.dm +++ b/code/game/objects/items/grenades/clusterbuster.dm @@ -13,7 +13,6 @@ var/base_state = "clusterbang" var/payload = /obj/item/grenade/flashbang/cluster var/payload_spawner = /obj/effect/payload_spawner - var/prime_sound = 'sound/items/weapons/armbomb.ogg' var/min_spawned = 4 var/max_spawned = 8 var/segment_chance = 35 @@ -44,7 +43,7 @@ new /obj/item/grenade/clusterbuster/segment(drop_location(), src)//Creates 'segments' that launches a few more payloads new payload_spawner(drop_location(), payload, numspawned)//Launches payload - playsound(src, prime_sound, 75, TRUE, -3) + playsound(src, grenade_arm_sound, 75, TRUE, -3) qdel(src) ////////////////////// @@ -66,7 +65,7 @@ icon_state = base_state payload_spawner = base.payload_spawner payload = base.payload - prime_sound = base.prime_sound + grenade_arm_sound = base.grenade_arm_sound min_spawned = base.min_spawned max_spawned = base.max_spawned icon_state = "[base_state]_active" @@ -78,7 +77,7 @@ /obj/item/grenade/clusterbuster/segment/detonate(mob/living/lanced_by) new payload_spawner(drop_location(), payload, rand(min_spawned, max_spawned)) - playsound(src, prime_sound, 75, TRUE, -3) + playsound(src, grenade_arm_sound, 75, TRUE, -3) qdel(src) ////////////////////////////////// @@ -207,7 +206,7 @@ icon_state = "slimebang" base_state = "slimebang" payload_spawner = /obj/effect/payload_spawner/random_slime - prime_sound = 'sound/effects/bubbles/bubbles.ogg' + grenade_arm_sound = 'sound/effects/bubbles/bubbles.ogg' /obj/item/grenade/clusterbuster/slime/volatile payload_spawner = /obj/effect/payload_spawner/random_slime/volatile diff --git a/code/modules/antagonists/heretic/heretic_curses.dm b/code/modules/antagonists/heretic/heretic_curses.dm new file mode 100644 index 00000000000..23d88e91f35 --- /dev/null +++ b/code/modules/antagonists/heretic/heretic_curses.dm @@ -0,0 +1,283 @@ +/*! + * Contains all the curses a heretic can cast using their upgraded codex + */ + +/datum/heretic_knowledge/curse + abstract_parent_type = /datum/heretic_knowledge/curse + /// How far can we curse people? + var/max_range = 64 + /// The duration of the curse + var/duration = 1 MINUTES + /// What color do we outline cursed folk with? + var/curse_color = "#dadada" + /// A list of all the fingerprints that were found on our atoms, in our last go at the ritual + var/list/fingerprints + /// A list of all the blood samples that were found on our atoms, in our last go at the ritual + var/list/blood_samples + +/datum/heretic_knowledge/curse/recipe_snowflake_check(mob/living/user, list/atoms, list/selected_atoms, turf/loc) + fingerprints = list() + blood_samples = list() + for(var/atom/requirement as anything in atoms) + for(var/print in GET_ATOM_FINGERPRINTS(requirement)) + fingerprints[print] = TRUE + + for(var/blood in GET_ATOM_BLOOD_DNA(requirement)) + blood_samples[blood] = TRUE + + for(var/datum/reagent/blood/usable_reagent as anything in requirement.reagents?.reagent_list) + if(!istype(usable_reagent, /datum/reagent/blood)) + continue + blood_samples[usable_reagent.data["blood_DNA"]] = TRUE + + return TRUE + +/datum/heretic_knowledge/curse/on_finished_recipe(mob/living/user, list/selected_atoms, turf/loc) + // Potential targets is an assoc list of [names] to [human mob ref]. + var/list/potential_targets = list() + + for(var/datum/mind/crewmember as anything in get_crewmember_minds()) + var/mob/living/carbon/human/human_to_check = crewmember.current + if(!istype(human_to_check) || human_to_check.stat == DEAD || !human_to_check.dna) + continue + var/their_prints = md5(human_to_check.dna.unique_identity) + var/their_blood = human_to_check.dna.unique_enzymes + if(!fingerprints[their_prints] && !blood_samples[their_blood]) + continue + potential_targets["[human_to_check.real_name]"] = human_to_check + + var/chosen_mob = tgui_input_list(user, "Select the victim you wish to curse.", name, sort_list(potential_targets, GLOBAL_PROC_REF(cmp_text_asc))) + if(isnull(chosen_mob)) + return FALSE + + var/mob/living/carbon/human/to_curse = potential_targets[chosen_mob] + if(QDELETED(to_curse)) + loc.balloon_alert(user, "ritual failed, invalid choice!") + return FALSE + + // Yes, you COULD curse yourself, not sure why but you could + if(to_curse == user) + var/are_you_sure = tgui_alert(user, "Are you sure you want to curse yourself?", name, list("Yes", "No")) + if(are_you_sure != "Yes") + return FALSE + + if(!ask_for_input(user)) + return FALSE + + var/turf/curse_turf = get_turf(to_curse) + if(!is_valid_z_level(curse_turf, loc) || get_dist(curse_turf, loc) > max_range * 1.5) // Give a bit of leeway on max range for people moving around + loc.balloon_alert(user, "ritual failed, too far!") + return FALSE + + if(IS_HERETIC(to_curse) && to_curse != user) + to_chat(user, span_warning("[to_curse.p_their()] ties to the Mansus are too strong. You are unable to curse [to_curse].")) + return TRUE + + if(to_curse.can_block_magic(MAGIC_RESISTANCE|MAGIC_RESISTANCE_HOLY, charge_cost = 0)) + to_chat(to_curse, span_warning("A ghastly chill envelops you for a moment, but then it passes.")) + return TRUE + + log_combat(user, to_curse, "cursed via heretic ritual", addition = "([name])") + var/obj/item/codex_cicatrix/morbus/cursed_book = locate() in selected_atoms + curse(to_curse, cursed_book) + to_chat(user, span_hierophant("You cast a [name] upon [to_curse.real_name].")) + + fingerprints = null + blood_samples = null + for(var/atom/to_wash in selected_atoms) + to_wash.wash(CLEAN_SCRUB) + for(var/atom/to_drain in selected_atoms) + if(!to_drain.reagents?.reagent_list) + continue + for(var/datum/reagent/to_match in to_drain.reagents.reagent_list) + if(to_match.data["blood_DNA"] != to_curse.dna.unique_enzymes) + continue + to_drain.reagents.remove_reagent(to_match.type, 5) + return TRUE + +/** + * Calls a curse onto [chosen_mob]. + */ +/datum/heretic_knowledge/curse/proc/curse(mob/living/carbon/human/chosen_mob, obj/item/codex_cicatrix/morbus/cursing_book) + SHOULD_CALL_PARENT(TRUE) + + if(duration > 0) + addtimer(CALLBACK(src, PROC_REF(uncurse), chosen_mob), duration) + + if(!curse_color) + return + + chosen_mob.add_filter(name, 2, list("type" = "outline", "color" = curse_color, "size" = 1)) + +/** + * Removes a curse from [chosen_mob]. Used in timers / callbacks. + */ +/datum/heretic_knowledge/curse/proc/uncurse(mob/living/carbon/human/chosen_mob) + SHOULD_CALL_PARENT(TRUE) + + if(QDELETED(chosen_mob)) + return + + if(!curse_color) + return + + chosen_mob.remove_filter(name) + +/** + * Asks the user for input (Optional) + * Return TRUE to finish the curse + * Return FALSE to cancel the curse + */ +/datum/heretic_knowledge/curse/proc/ask_for_input(mob/living/user) + return TRUE + +//---- Curse of Paralysis + +/datum/heretic_knowledge/curse/paralysis + abstract_parent_type = /datum/heretic_knowledge/curse/paralysis + name = "Curse of Paralysis" + desc = "Allows you to transmute a hatchet and both a left and right leg to cast a curse of immobility on a crew member. \ + While cursed, the victim will be unable to walk. You can additionally supply an item that a victim has touched \ + or is covered in the victim's blood to make the curse last longer." + gain_text = "The flesh of humanity is weak. Make them bleed. Show them their fragility." + + duration = 5 MINUTES + curse_color = "#f19a9a" + + research_tree_icon_path = 'icons/ui_icons/antags/heretic/knowledge.dmi' + research_tree_icon_state = "curse_paralysis" + + +/datum/heretic_knowledge/curse/paralysis/curse(mob/living/carbon/human/chosen_mob) + if(chosen_mob.usable_legs <= 0) // What're you gonna do, curse someone who already can't walk? + to_chat(chosen_mob, span_notice("You feel a slight pain for a moment, but it passes shortly. Odd.")) + return + + to_chat(chosen_mob, span_danger("You suddenly lose feeling in your leg[chosen_mob.usable_legs == 1 ? "":"s"]!")) + chosen_mob.add_traits(list(TRAIT_PARALYSIS_L_LEG, TRAIT_PARALYSIS_R_LEG), type) + return ..() + +/datum/heretic_knowledge/curse/paralysis/uncurse(mob/living/carbon/human/chosen_mob) + if(QDELETED(chosen_mob)) + return + + chosen_mob.remove_traits(list(TRAIT_PARALYSIS_L_LEG, TRAIT_PARALYSIS_R_LEG), type) + if(chosen_mob.usable_legs > 1) + to_chat(chosen_mob, span_green("You regain feeling in your leg[chosen_mob.usable_legs == 1 ? "":"s"]!")) + return ..() + +//---- Curse of Corrosion + +/datum/heretic_knowledge/curse/corrosion + abstract_parent_type = /datum/heretic_knowledge/curse/corrosion + name = "Curse of Corrosion" + desc = "Allows you to transmute wirecutters, a pool of vomit, and a heart to cast a curse of sickness on a crew member. \ + While cursed, the victim will repeatedly vomit while their organs will take constant damage. You can additionally supply an item \ + that a victim has touched or is covered in the victim's blood to make the curse last longer." + gain_text = "The body of humanity is temporary. Their weaknesses cannot be stopped, like iron falling to rust. Show them all." + + duration = 3 MINUTES + curse_color = "#c1ffc9" + + research_tree_icon_path = 'icons/ui_icons/antags/heretic/knowledge.dmi' + research_tree_icon_state = "curse_corrosion" + +/datum/heretic_knowledge/curse/corrosion/curse(mob/living/carbon/human/chosen_mob) + to_chat(chosen_mob, span_danger("You feel very ill...")) + chosen_mob.apply_status_effect(/datum/status_effect/corrosion_curse) + return ..() + +/datum/heretic_knowledge/curse/corrosion/uncurse(mob/living/carbon/human/chosen_mob) + if(QDELETED(chosen_mob)) + return + + chosen_mob.remove_status_effect(/datum/status_effect/corrosion_curse) + to_chat(chosen_mob, span_green("You start to feel better.")) + return ..() + +//---- Curse of Transmutation + +/datum/heretic_knowledge/curse/transmutation + abstract_parent_type = /datum/heretic_knowledge/curse/transmutation + name = "Curse of Transmutation" + duration = 0 // Infinite curse, it breaks when our codex is destroyed + curse_color = NONE + /// What species we are going to turn our victim in to + var/chosen_species + +/datum/heretic_knowledge/curse/transmutation/ask_for_input(mob/living/user) + var/list/chooseable_races = list() + for(var/datum/species/species_type as anything in subtypesof(/datum/species)) + if(initial(species_type.changesource_flags) & RACE_SWAP) + chooseable_races[species_type.name] = species_type + + var/species_name = tgui_input_list(user, "Choose a race", "Choose a race to turn your victim into", chooseable_races) + if(!species_name) + return FALSE + chosen_species = chooseable_races[species_name] + return ..() + +/datum/heretic_knowledge/curse/transmutation/curse(mob/living/carbon/human/chosen_mob, obj/item/codex_cicatrix/morbus/cursing_book) + if(chosen_mob.dna.species == chosen_species) + to_chat(chosen_mob, span_warning("You feel your body morph into... itself?")) + return + chosen_mob.apply_status_effect(/datum/status_effect/race_swap, chosen_species) + cursing_book.transmuted_victims += WEAKREF(chosen_mob) + to_chat(chosen_mob, span_danger("You feel your body morph into a new shape")) + return ..() + +/datum/heretic_knowledge/curse/transmutation/uncurse(mob/living/carbon/human/chosen_mob) + if(QDELETED(chosen_mob)) + return + + chosen_mob.remove_status_effect(/datum/status_effect/race_swap) + + return ..() + +/datum/status_effect/race_swap + id = "race_swap" + status_type = STATUS_EFFECT_REPLACE + alert_type = null + duration = STATUS_EFFECT_PERMANENT + tick_interval = STATUS_EFFECT_NO_TICK + /// What species were we before this effect was ever applied on us + var/old_species + +/datum/status_effect/race_swap/on_creation(mob/living/new_owner, datum/species/new_species) + . = ..() + owner.set_species(new_species) + +/datum/status_effect/race_swap/on_apply() + if(!iscarbon(owner)) + return FALSE + var/mob/living/carbon/carbon_owner = owner + if(!old_species) + old_species = carbon_owner.dna.species + return ..() + +/datum/status_effect/race_swap/be_replaced() + owner.set_species(old_species) + return ..() + +/datum/status_effect/race_swap/on_remove() + . = ..() + owner.set_species(old_species) + +//---- Curse of Indulgence + +/datum/heretic_knowledge/curse/indulgence + abstract_parent_type = /datum/heretic_knowledge/curse/indulgence + name = "Curse of Indulgence" + duration = 8 MINUTES + curse_color = COLOR_MAROON + +/datum/heretic_knowledge/curse/indulgence/curse(mob/living/carbon/human/chosen_mob) + chosen_mob.gain_trauma(/datum/brain_trauma/severe/flesh_desire, TRAUMA_RESILIENCE_MAGIC) + chosen_mob.nutrition = NUTRITION_LEVEL_STARVING + return ..() + +/datum/heretic_knowledge/curse/indulgence/uncurse(mob/living/carbon/human/chosen_mob) + if(QDELETED(chosen_mob)) + return + chosen_mob.cure_trauma_type(/datum/brain_trauma/severe/flesh_desire, TRAUMA_RESILIENCE_MAGIC) + return ..() diff --git a/code/modules/antagonists/heretic/heretic_knowledge.dm b/code/modules/antagonists/heretic/heretic_knowledge.dm index bc9ca00779a..30d12425e11 100644 --- a/code/modules/antagonists/heretic/heretic_knowledge.dm +++ b/code/modules/antagonists/heretic/heretic_knowledge.dm @@ -378,129 +378,6 @@ /datum/heretic_knowledge/blade_upgrade/proc/do_ranged_effects(mob/living/source, mob/living/target, obj/item/melee/sickly_blade/blade) return -/** - * A knowledge subtype lets the heretic curse someone with a ritual. - */ -/datum/heretic_knowledge/curse - abstract_parent_type = /datum/heretic_knowledge/curse - /// How far can we curse people? - var/max_range = 64 - /// The duration of the curse - var/duration = 1 MINUTES - /// The duration of the curse on people which have a fingerprint or blood sample present - var/duration_modifier = 2 - /// What color do we outline cursed folk with? - var/curse_color = "#dadada" - /// A list of all the fingerprints that were found on our atoms, in our last go at the ritual - var/list/fingerprints - /// A list of all the blood samples that were found on our atoms, in our last go at the ritual - var/list/blood_samples - -/datum/heretic_knowledge/curse/recipe_snowflake_check(mob/living/user, list/atoms, list/selected_atoms, turf/loc) - fingerprints = list() - blood_samples = list() - for(var/atom/requirement as anything in atoms) - for(var/print in GET_ATOM_FINGERPRINTS(requirement)) - fingerprints[print] = 1 - - for(var/blood in GET_ATOM_BLOOD_DNA(requirement)) - blood_samples[blood] = 1 - - return TRUE - -/datum/heretic_knowledge/curse/on_finished_recipe(mob/living/user, list/selected_atoms, turf/loc) - - // Potential targets is an assoc list of [names] to [human mob ref]. - var/list/potential_targets = list() - // Boosted targets is a list of human mob references. - var/list/boosted_targets = list() - - for(var/datum/mind/crewmember as anything in get_crewmember_minds()) - var/mob/living/carbon/human/human_to_check = crewmember.current - if(!istype(human_to_check) || human_to_check.stat == DEAD || !human_to_check.dna) - continue - var/their_prints = md5(human_to_check.dna.unique_identity) - var/their_blood = human_to_check.dna.unique_enzymes - // Having their fingerprints or blood present will boost the curse - // and also not run any z or dist checks, as a bonus for those going beyond - if(fingerprints[their_prints] || blood_samples[their_blood]) - boosted_targets += human_to_check - potential_targets["[human_to_check.real_name] (Boosted)"] = human_to_check - continue - - // No boost present, so we should be a little stricter moving forward - var/turf/check_turf = get_turf(human_to_check) - // We have to match z-levels. - // Otherwise, you could probably hard own miners, which is funny but mean. - // Multi-z stations technically work though. - if(!is_valid_z_level(check_turf, loc)) - continue - // Also has to abide by our max range. - if(get_dist(check_turf, loc) > max_range) - continue - - potential_targets[human_to_check.real_name] = human_to_check - - var/chosen_mob = tgui_input_list(user, "Select the victim you wish to curse.", name, sort_list(potential_targets, GLOBAL_PROC_REF(cmp_text_asc))) - if(isnull(chosen_mob)) - return FALSE - - var/mob/living/carbon/human/to_curse = potential_targets[chosen_mob] - if(QDELETED(to_curse)) - loc.balloon_alert(user, "ritual failed, invalid choice!") - return FALSE - - // Yes, you COULD curse yourself, not sure why but you could - if(to_curse == user) - var/are_you_sure = tgui_alert(user, "Are you sure you want to curse yourself?", name, list("Yes", "No")) - if(are_you_sure != "Yes") - return FALSE - - var/boosted = (to_curse in boosted_targets) - var/turf/curse_turf = get_turf(to_curse) - if(!boosted && (!is_valid_z_level(curse_turf, loc) || get_dist(curse_turf, loc) > max_range * 1.5)) // Give a bit of leeway on max range for people moving around - loc.balloon_alert(user, "ritual failed, too far!") - return FALSE - - if(to_curse.can_block_magic(MAGIC_RESISTANCE|MAGIC_RESISTANCE_HOLY, charge_cost = 0)) - to_chat(to_curse, span_warning("You feel a ghastly chill, but the feeling passes shortly.")) - return TRUE - - log_combat(user, to_curse, "cursed via heretic ritual", addition = "([boosted ? "Boosted" : ""] [name])") - curse(to_curse, boosted) - to_chat(user, span_hierophant("You cast a[boosted ? "n empowered":""] [name] upon [to_curse.real_name].")) - - fingerprints = null - blood_samples = null - return TRUE - -/** - * Calls a curse onto [chosen_mob]. - */ -/datum/heretic_knowledge/curse/proc/curse(mob/living/carbon/human/chosen_mob, boosted = FALSE) - SHOULD_CALL_PARENT(TRUE) - - addtimer(CALLBACK(src, PROC_REF(uncurse), chosen_mob, boosted), duration * (boosted ? duration_modifier : 1)) - - if(!curse_color) - return - - chosen_mob.add_filter(name, 2, list("type" = "outline", "color" = curse_color, "size" = 1)) - -/** - * Removes a curse from [chosen_mob]. Used in timers / callbacks. - */ -/datum/heretic_knowledge/curse/proc/uncurse(mob/living/carbon/human/chosen_mob, boosted = FALSE) - SHOULD_CALL_PARENT(TRUE) - - if(QDELETED(chosen_mob)) - return - - if(!curse_color) - return - - chosen_mob.remove_filter(name) - /** * A knowledge subtype lets the heretic summon a monster with the ritual. */ diff --git a/code/modules/antagonists/heretic/influences.dm b/code/modules/antagonists/heretic/influences.dm index 0010457635a..9dad4f20acf 100644 --- a/code/modules/antagonists/heretic/influences.dm +++ b/code/modules/antagonists/heretic/influences.dm @@ -220,7 +220,7 @@ return FALSE if(!codex.book_open) codex.attack_self(user) // open booke - INVOKE_ASYNC(src, PROC_REF(drain_influence), user, 2) + INVOKE_ASYNC(src, PROC_REF(drain_influence), user, 2, codex.drain_speed) return TRUE /** @@ -229,12 +229,12 @@ * * If successful, the influence is drained and deleted. */ -/obj/effect/heretic_influence/proc/drain_influence(mob/living/user, knowledge_to_gain) +/obj/effect/heretic_influence/proc/drain_influence(mob/living/user, knowledge_to_gain, drain_speed = 10 SECONDS) being_drained = TRUE loc.balloon_alert(user, "draining influence...") - if(!do_after(user, 10 SECONDS, src, hidden = TRUE)) + if(!do_after(user, drain_speed, src, hidden = TRUE)) being_drained = FALSE loc.balloon_alert(user, "interrupted!") return diff --git a/code/modules/antagonists/heretic/items/eldritch_flask.dm b/code/modules/antagonists/heretic/items/eldritch_flask.dm index a3ec676f64d..fc0dd054497 100644 --- a/code/modules/antagonists/heretic/items/eldritch_flask.dm +++ b/code/modules/antagonists/heretic/items/eldritch_flask.dm @@ -6,3 +6,101 @@ icon = 'icons/obj/antags/eldritch.dmi' icon_state = "eldritch_flask" list_reagents = list(/datum/reagent/eldritch = 50) + +// Unique bottle that lets you instantly draw blood from a victim +/obj/item/reagent_containers/cup/phylactery + name = "phylactery of damnation" + desc = "Used to steal blood from soon-to-be victims." + icon = 'icons/obj/antags/eldritch.dmi' + icon_state = "phylactery" + base_icon_state = "phylactery" + has_variable_transfer_amount = FALSE + reagent_flags = OPENCONTAINER | DUNKABLE | TRANSPARENT + volume = 10 + /// Cooldown before you can steal blood again + COOLDOWN_DECLARE(drain_cooldown) + +/obj/item/reagent_containers/cup/phylactery/interact_with_atom_secondary(atom/target, mob/living/user, list/modifiers) + if(!COOLDOWN_FINISHED(src, drain_cooldown)) + user.balloon_alert(user, "can't steal so fast!") + return NONE + if(!isliving(target)) + return NONE + var/mob/living/living_target = target + if(reagents.total_volume >= reagents.maximum_volume) + to_chat(user, span_notice("[src] is full.")) + return ITEM_INTERACT_BLOCKING + if(living_target == user) + return ITEM_INTERACT_BLOCKING + if(living_target.can_block_magic(MAGIC_RESISTANCE_HOLY)) + to_chat(user, span_warning("You are unable to draw any blood from [living_target]!")) + COOLDOWN_START(src, drain_cooldown, 5 SECONDS) + to_chat(living_target, span_warning("You feel a force attempt to steal your blood, but it is repelled!")) + return ITEM_INTERACT_BLOCKING + var/drawn_amount = min(reagents.maximum_volume - reagents.total_volume, 5) + if(living_target.transfer_blood_to(src, drawn_amount)) + to_chat(user, span_notice("You take a blood sample from [living_target].")) + to_chat(living_target, span_warning("You feel a tiny prick!")) + COOLDOWN_START(src, drain_cooldown, 5 SECONDS) + playsound(src, 'sound/effects/chemistry/catalyst.ogg', 20, TRUE, extrarange = SILENCED_SOUND_EXTRARANGE, falloff_exponent = 10) + else + to_chat(user, span_warning("You are unable to draw any blood from [living_target]!")) + return ITEM_INTERACT_SUCCESS + +/obj/item/reagent_containers/cup/phylactery/ranged_interact_with_atom_secondary(atom/interacting_with, mob/living/user, list/modifiers) + if(get_dist(user, interacting_with) <= 30) + return interact_with_atom_secondary(interacting_with, user, modifiers) + return ..() + +/obj/item/reagent_containers/cup/phylactery/update_icon_state() + . = ..() + switch(reagents.total_volume) + if(0) + icon_state = base_icon_state + if(0.1 to 5) + icon_state = base_icon_state + "_1" + if(5.1 to 10) + icon_state = base_icon_state + "_2" + +// Funny potion that is basically an aheal. The downside is that it puts you to sleep for a minute. +/obj/item/ether + name = "ether of the newborn" + desc = "A flask of nausea-inducing, thick green liquid. Restores your body completely, then places you into an enhanced sleep for a full minute." + icon = 'icons/obj/antags/eldritch.dmi' + icon_state = "poison_flask" + +/obj/item/ether/attack_self(mob/living/user, modifiers) + . = ..() + user.revive(HEAL_ALL) + for(var/obj/item/implant/to_remove in user.implants) + to_remove.removed(user) + + user.apply_status_effect(/datum/status_effect/eldritch_sleep) + user.SetSleeping(60 SECONDS) + qdel(src) + +/datum/status_effect/eldritch_sleep + id = "eldritch_sleep" + duration = 60 SECONDS + status_type = STATUS_EFFECT_REFRESH + alert_type = /atom/movable/screen/alert/status_effect/eldritch_sleep + show_duration = TRUE + remove_on_fullheal = TRUE + /// List of traits our drinker gets while they are asleep + var/list/sleeping_traits = list(TRAIT_NOBREATH, TRAIT_RESISTLOWPRESSURE, TRAIT_RESISTLOWPRESSURE, TRAIT_RESISTCOLD, TRAIT_RESISTHEAT) + +/datum/status_effect/eldritch_sleep/on_apply() + . = ..() + owner.add_traits(sleeping_traits, TRAIT_STATUS_EFFECT(id)) + owner.apply_status_effect(/datum/status_effect/grouped/stasis, STASIS_ELDRITCH_ETHER) + +/datum/status_effect/eldritch_sleep/on_remove() + owner.SetSleeping(0) // Wake up bookworm, we have some heathens to burn + owner.remove_traits(sleeping_traits, TRAIT_STATUS_EFFECT(id)) + owner.reagents?.remove_all(100) // If someone gives you over 100 units of poison while you sleep then you deserve this L + owner.remove_status_effect(/datum/status_effect/grouped/stasis, STASIS_ELDRITCH_ETHER) + +/atom/movable/screen/alert/status_effect/eldritch_sleep + name = "Eldritch Slumber" + desc = "You feel an indescribable warmth keeping you safe..." + icon_state = "eldritch_slumber" diff --git a/code/modules/antagonists/heretic/items/forbidden_book.dm b/code/modules/antagonists/heretic/items/forbidden_book.dm index 2591a1fd752..d3c22d7b687 100644 --- a/code/modules/antagonists/heretic/items/forbidden_book.dm +++ b/code/modules/antagonists/heretic/items/forbidden_book.dm @@ -10,6 +10,10 @@ w_class = WEIGHT_CLASS_SMALL /// Helps determine the icon state of this item when it's used on self. var/book_open = FALSE + /// How fast we can drain influences + var/drain_speed = 10 SECONDS + /// How fast we can draw runes + var/draw_speed = 8 SECONDS /obj/item/codex_cicatrix/Initialize(mapload) . = ..() @@ -53,7 +57,7 @@ if(isopenturf(interacting_with)) var/obj/effect/heretic_influence/influence = locate(/obj/effect/heretic_influence) in interacting_with if(!influence?.drain_influence_with_codex(user, src)) - heretic_datum.try_draw_rune(user, interacting_with, drawing_time = 8 SECONDS) + heretic_datum.try_draw_rune(user, interacting_with, drawing_time = draw_speed) return ITEM_INTERACT_BLOCKING return NONE @@ -68,3 +72,75 @@ icon_state = base_icon_state flick("[base_icon_state]_closing", src) book_open = FALSE + +// Upgraded version of the codex cicatrix that allows us to cast curses +/obj/item/codex_cicatrix/morbus // I'm morbing all over + name = "Codex Morbus" + desc = "A hideous, ragged book covered in separately-blinking eyes, all of them staring at you. You have no idea how to hold this thing, and to be honest you're not sure if you want to." + base_icon_state = "book_morbus" + icon_state = "book_morbus" + drain_speed = 7 SECONDS + draw_speed = 5 SECONDS + /// List of mobs we've cursed with transmutation. When the codex is destroyed all those curses become undone + var/list/transmuted_victims = list() + +/obj/item/codex_cicatrix/morbus/examine(mob/user) + . = ..() + if(IS_HERETIC(user)) + . += span_info("Can be used to cast a curse with blood in your offhand by right clicking a rune.") + return + . += span_danger("The eyes stop blinking. They stare at you. Their gaze burns...") + if(!ishuman(user)) + return + var/mob/living/carbon/human/human_user = user + to_chat(human_user, span_userdanger("Your mind burns as you stare at the pages!")) + human_user.adjustOrganLoss(ORGAN_SLOT_BRAIN, 10, 190) + human_user.add_mood_event("gates_of_mansus", /datum/mood_event/gates_of_mansus) + +/obj/item/codex_cicatrix/morbus/examine_more(mob/user) + . = ..() // XANTODO - Add a summary of each curse to the description so that the curser knows what will happen the cursee + +/obj/item/codex_cicatrix/morbus/interact_with_atom_secondary(atom/interacting_with, mob/living/user, list/modifiers) + if(!istype(interacting_with, /obj/effect/heretic_rune/big)) + return NONE + + var/list/curse_list = list() + for(var/datum/heretic_knowledge/curse/curses as anything in subtypesof(/datum/heretic_knowledge/curse)) + curse_list[curses.name] = curses + var/selected_curse = tgui_input_list(user, "Cast any curse", "Select a curse!", curse_list, timeout = 0) + if(!selected_curse) + return NONE + + if(!user.Adjacent(interacting_with)) + return NONE + + var/atom/held_offhand = user.get_inactive_held_item() + if(!held_offhand) + user.balloon_alert(user, "no catalyst!") + return + var/blood_samples = list() + for(var/blood in GET_ATOM_BLOOD_DNA(held_offhand)) + blood_samples[blood] = 1 + for(var/datum/reagent/blood/usable_reagent as anything in held_offhand.reagents?.reagent_list) + if(!istype(usable_reagent, /datum/reagent/blood)) + continue + blood_samples += usable_reagent.data["blood_DNA"] + if(isnull(blood_samples)) + user.balloon_alert(user, "no blood!") + return ITEM_INTERACT_BLOCKING + + var/curse_type = curse_list[selected_curse] + var/datum/heretic_knowledge/curse/to_cast = new curse_type + to_cast.recipe_snowflake_check(user, list(held_offhand), loc = get_turf(user)) + to_cast.on_finished_recipe(user, list(src, held_offhand), loc = get_turf(user)) + return ITEM_INTERACT_SUCCESS + +/obj/item/codex_cicatrix/morbus/atom_destruction(damage_flag) + for(var/datum/weakref/to_uncurse_ref as anything in transmuted_victims) + var/mob/to_uncurse = to_uncurse_ref.resolve() + if(!to_uncurse || !ismob(to_uncurse)) + continue + var/datum/heretic_knowledge/curse/transmutation/to_undo = new() + to_undo.uncurse(to_uncurse) + transmuted_victims -= to_uncurse_ref + return ..() diff --git a/code/modules/antagonists/heretic/items/heretic_blades.dm b/code/modules/antagonists/heretic/items/heretic_blades.dm index 1cee767dc96..545ee85f301 100644 --- a/code/modules/antagonists/heretic/items/heretic_blades.dm +++ b/code/modules/antagonists/heretic/items/heretic_blades.dm @@ -25,6 +25,10 @@ attack_verb_continuous = list("attacks", "slashes", "slices", "tears", "lacerates", "rips", "dices", "rends") attack_verb_simple = list("attack", "slash", "slice", "tear", "lacerate", "rip", "dice", "rend") var/after_use_message = "" + /// Tracks how many times attack_self() is called so that breaking a blade while in an arena has to be intentional + var/escape_attempts = 0 + /// Timer that resets your escape_attempts back to 0 + var/escape_timer /obj/item/melee/sickly_blade/examine(mob/user) . = ..() @@ -50,8 +54,27 @@ return . /obj/item/melee/sickly_blade/attack_self(mob/user) + if(HAS_TRAIT(user, TRAIT_ELDRITCH_ARENA_PARTICIPANT)) + user.balloon_alert(user, "can't escape!") + if(escape_attempts > 2) + to_chat(user, span_hypnophrase(span_big("Cowardly sheep will be slaughtered!"))) + playsound(src, SFX_SHATTER, 70, TRUE) + var/obj/item/bodypart/to_remove = user.get_active_hand() + to_remove.dismember() + deltimer(escape_timer) + qdel(src) + return + escape_attempts++ + escape_timer = addtimer(CALLBACK(src, PROC_REF(reset_attempts)), 2 SECONDS, TIMER_STOPPABLE) + return + if(HAS_TRAIT(user, TRAIT_NO_TELEPORT)) + user.balloon_alert(user, "can't break!") + return seek_safety(user) - return ..() + +/obj/item/melee/sickly_blade/proc/reset_attempts() + escape_attempts = 0 + deltimer(escape_timer) /// Attempts to teleport the passed mob to somewhere safe on the station, if they can use the blade. /obj/item/melee/sickly_blade/proc/seek_safety(mob/user) @@ -282,3 +305,14 @@ heretic_datum.try_draw_rune(user, target, drawing_time = 14 SECONDS) // Faster than pen, slower than cicatrix return ITEM_INTERACT_BLOCKING return NONE + +// Weaker blade variant given to people so they can participate in the heretic arena spell +/obj/item/melee/sickly_blade/training + name = "\improper imperfect blade" + desc = "A blade given to those who cannot accept the truth, out of pity. \ + May it act as a blessing in the short time it remains alongside you." + force = 17 + armour_penetration = 0 + +/obj/item/melee/sickly_blade/training/check_usability(mob/living/user) + return TRUE // If you can hold this, you can use it diff --git a/code/modules/antagonists/heretic/items/heretic_grenade.dm b/code/modules/antagonists/heretic/items/heretic_grenade.dm new file mode 100644 index 00000000000..eb6f4ecf077 --- /dev/null +++ b/code/modules/antagonists/heretic/items/heretic_grenade.dm @@ -0,0 +1,120 @@ +/*! + * Contains Heretic grenades + * They spread rust and obliterate borgs/mechs + */ + +/obj/item/grenade/chem_grenade/rust_sower + name = "\improper Rust sower" + desc = "A nifty little thing that explodes into rust. Causes borgs and mechs to get utterly obliterated" + possible_fuse_time = list("5") + stage = GRENADE_READY + base_icon_state = "rustgrenade" + inhand_icon_state = "rustgrenade" + grenade_arm_sound = 'sound/items/weapons/rust_sower_armbomb.ogg' + grenade_sound_vary = FALSE + +/obj/item/grenade/chem_grenade/rust_sower/update_icon_state() + . = ..() + if(active) + icon_state = "[base_icon_state]_active" + else + icon_state = base_icon_state + +/obj/item/grenade/chem_grenade/rust_sower/Initialize(mapload) + . = ..() + RegisterSignal(src, COMSIG_ITEM_ON_GRIND, PROC_REF(on_try_grind)) + var/obj/item/reagent_containers/cup/beaker/large/beaker_one = new(src) + var/obj/item/reagent_containers/cup/beaker/large/beaker_two = new(src) + + beaker_one.reagents.add_reagent(/datum/reagent/heretic_rust, 50) + beaker_one.reagents.add_reagent(/datum/reagent/potassium, 50) + beaker_two.reagents.add_reagent(/datum/reagent/phosphorus, 50) + beaker_two.reagents.add_reagent(/datum/reagent/consumable/sugar, 50) + + beakers += beaker_one + beakers += beaker_two + +/obj/item/grenade/chem_grenade/rust_sower/detonate(mob/living/lanced_by) + . = ..() + playsound(src, 'sound/items/weapons/rust_sower_explode.ogg', 70, FALSE) + qdel(src) + +/obj/item/grenade/chem_grenade/rust_sower/screwdriver_act(mob/living/user, obj/item/tool) + return NONE + +/obj/item/grenade/chem_grenade/rust_sower/wrench_act(mob/living/user, obj/item/tool) + return NONE + +/obj/item/grenade/chem_grenade/rust_sower/multitool_act(mob/living/user, obj/item/tool) + return NONE + +/// Returns -1 so that you cant extract the chems +/obj/item/grenade/chem_grenade/rust_sower/proc/on_try_grind() + SIGNAL_HANDLER + return -1 + +/datum/reagent/heretic_rust + name = "Eldritch Rust" + description = "A slurry of viscous, chunky brown liquid." + color = COLOR_CARGO_BROWN // Rust color + taste_description = "rotten copper" + penetrates_skin = NONE + ph = 7.4 + default_container = /obj/item/reagent_containers/cup/bottle/capsaicin + +/datum/reagent/heretic_rust/expose_atom(atom/exposed_atom, reac_volume) + . = ..() + if(ismecha(exposed_atom)) + var/obj/vehicle/sealed/mecha/to_wreck = exposed_atom + to_wreck.take_damage(300, BURN) + +/datum/reagent/heretic_rust/expose_mob(mob/living/exposed_mob, methods=TOUCH, reac_volume) + . = ..() + if(!ishuman(exposed_mob)) + if(issilicon(exposed_mob) || ismecha(exposed_mob) || isbot(exposed_mob)) + exposed_mob.adjustBruteLoss(500) + return + if(IS_HERETIC(exposed_mob)) + return + if(exposed_mob.can_block_magic(MAGIC_RESISTANCE_HOLY)) + return + + var/mob/living/carbon/victim = exposed_mob + if(methods & (TOUCH|VAPOR|INHALE)) + //check for protection + //actually handle the pepperspray effects + if(!victim.is_pepper_proof()) // you need both eye and mouth protection + if(prob(5)) + victim.emote("scream") + victim.emote("cry") + victim.set_eye_blur_if_lower(10 SECONDS) + victim.adjust_temp_blindness(6 SECONDS) + victim.set_confusion_if_lower(5 SECONDS) + victim.Knockdown(3 SECONDS) + victim.add_movespeed_modifier(/datum/movespeed_modifier/reagent/pepperspray) + addtimer(CALLBACK(victim, TYPE_PROC_REF(/mob, remove_movespeed_modifier), /datum/movespeed_modifier/reagent/pepperspray), 10 SECONDS) + victim.update_damage_hud() + victim.adjust_disgust(5) + for(var/obj/item/bodypart/robotic_limb in victim.bodyparts) + if(robotic_limb.biological_state & BIO_ROBOTIC) + robotic_limb.receive_damage(5, 5) + if(methods & INGEST) + if(!holder.has_reagent(/datum/reagent/consumable/milk)) + if(prob(15)) + to_chat(exposed_mob, span_danger("[pick("Your head pounds.", "Your mouth feels like it's on fire.", "You feel dizzy.")]")) + if(prob(10)) + victim.set_eye_blur_if_lower(2 SECONDS) + if(prob(10)) + victim.set_dizzy_if_lower(2 SECONDS) + if(prob(5)) + victim.vomit(VOMIT_CATEGORY_DEFAULT) + +/datum/reagent/heretic_rust/expose_turf(turf/exposed_turf, reac_volume) + . = ..() + exposed_turf.rust_turf() + +/datum/reagent/heretic_rust/on_mob_life(mob/living/carbon/affected_mob, seconds_per_tick, times_fired) + . = ..() + if(!holder.has_reagent(/datum/reagent/consumable/milk)) + if(SPT_PROB(5, seconds_per_tick)) + affected_mob.visible_message(span_warning("[affected_mob] [pick("dry heaves!","coughs!","splutters!")]")) diff --git a/code/modules/antagonists/heretic/items/heretic_shoes.dm b/code/modules/antagonists/heretic/items/heretic_shoes.dm new file mode 100644 index 00000000000..3c7a94d829c --- /dev/null +++ b/code/modules/antagonists/heretic/items/heretic_shoes.dm @@ -0,0 +1,9 @@ +/obj/item/clothing/shoes/greaves_of_the_prophet + name = "\improper Joint-snap sabatons" + desc = "Sabatons made out of rugged, worn iron. Feels more stable than the ground they tread on. They're caked in a thin layer of rust - and yet, the sight of it fills you with odd relief." + icon_state = "hereticgreaves" + resistance_flags = ACID_PROOF | FIRE_PROOF | LAVA_PROOF + +/obj/item/clothing/shoes/greaves_of_the_prophet/Initialize(mapload) + . = ..() + attach_clothing_traits(list(TRAIT_NO_SLIP_WATER, TRAIT_NO_SLIP_ICE, TRAIT_NO_SLIP_SLIDE, TRAIT_NO_SLIP_ALL)) diff --git a/code/modules/antagonists/heretic/knowledge/blade_lore.dm b/code/modules/antagonists/heretic/knowledge/blade_lore.dm index 993aa20c428..2b85a5b7091 100644 --- a/code/modules/antagonists/heretic/knowledge/blade_lore.dm +++ b/code/modules/antagonists/heretic/knowledge/blade_lore.dm @@ -12,9 +12,9 @@ mark = /datum/heretic_knowledge/mark/blade_mark ritual_of_knowledge = /datum/heretic_knowledge/knowledge_ritual/blade unique_ability = /datum/heretic_knowledge/spell/realignment - tier2 = /datum/heretic_knowledge/duel_stance + tier2 = /datum/heretic_knowledge/spell/furious_steel blade = /datum/heretic_knowledge/blade_upgrade/blade - tier3 = /datum/heretic_knowledge/spell/furious_steel + tier3 = /datum/heretic_knowledge/spell/wolves_among_sheep ascension = /datum/heretic_knowledge/ultimate/blade_final /datum/heretic_knowledge/limited_amount/starting/base_blade @@ -188,70 +188,19 @@ action_to_add = /datum/action/cooldown/spell/realignment cost = 1 - -/// The amount of blood flow reduced per level of severity of gained bleeding wounds for Stance of the Torn Champion. -#define BLOOD_FLOW_PER_SEVEIRTY -1 - -/datum/heretic_knowledge/duel_stance - name = "Stance of the Torn Champion" - desc = "Grants resilience to blood loss from wounds and immunity to having your limbs dismembered. \ - Additionally, when damaged below 50% of your maximum health, \ - you gain increased resistance to gaining wounds and resistance to batons." - gain_text = "In time, it was he who stood alone among the bodies of his former comrades, awash in blood, none of it his own. \ - He was without rival, equal, or purpose." +/datum/heretic_knowledge/spell/wolves_among_sheep + name = "Wolves Among Sheep" + desc = "Alters the fabric of reality, conjuring a magical arena unpassable to outsiders, \ + all participants are trapped and immune to any form of crowd control or enviromental hazards; \ + trapped participants are granted a Blade and are unable to leave or jaunt until they score a critical hit. \ + Critical hits partially restore the Heretic's health." + gain_text = "Shadows crawl across the room, casting every chair, table \ + and console into the looming shape of another traitorous hand. \ + I have made an enemy of all, and peace will never be known to me \ + again. I have shattered bonds and severed all alliances. In this truth, \ + I know now the fragility of comradery. My enemies will be all, divided." cost = 1 - research_tree_icon_path = 'icons/effects/blood.dmi' - research_tree_icon_state = "suitblood" - research_tree_icon_dir = SOUTH - /// Whether we're currently in duelist stance, gaining certain buffs (low health) - var/in_duelist_stance = FALSE - -/datum/heretic_knowledge/duel_stance/on_gain(mob/user, datum/antagonist/heretic/our_heretic) - ADD_TRAIT(user, TRAIT_NODISMEMBER, type) - RegisterSignal(user, COMSIG_ATOM_EXAMINE, PROC_REF(on_examine)) - RegisterSignal(user, COMSIG_CARBON_GAIN_WOUND, PROC_REF(on_wound_gain)) - RegisterSignal(user, COMSIG_LIVING_HEALTH_UPDATE, PROC_REF(on_health_update)) - - on_health_update(user) // Run this once, so if the knowledge is learned while hurt it activates properly - -/datum/heretic_knowledge/duel_stance/on_lose(mob/user, datum/antagonist/heretic/our_heretic) - REMOVE_TRAIT(user, TRAIT_NODISMEMBER, type) - if(in_duelist_stance) - user.remove_traits(list(TRAIT_HARDLY_WOUNDED, TRAIT_BATON_RESISTANCE), type) - - UnregisterSignal(user, list(COMSIG_ATOM_EXAMINE, COMSIG_CARBON_GAIN_WOUND, COMSIG_LIVING_HEALTH_UPDATE)) - -/datum/heretic_knowledge/duel_stance/proc/on_examine(mob/living/source, mob/user, list/examine_list) - SIGNAL_HANDLER - - var/obj/item/held_item = source.get_active_held_item() - if(in_duelist_stance) - examine_list += span_warning("[source] looks unnaturally poised[held_item?.force >= 15 ? " and ready to strike out":""].") - -/datum/heretic_knowledge/duel_stance/proc/on_wound_gain(mob/living/source, datum/wound/gained_wound, obj/item/bodypart/limb) - SIGNAL_HANDLER - - if(gained_wound.blood_flow <= 0) - return - - gained_wound.adjust_blood_flow(gained_wound.severity * BLOOD_FLOW_PER_SEVEIRTY) - -/datum/heretic_knowledge/duel_stance/proc/on_health_update(mob/living/source) - SIGNAL_HANDLER - - if(in_duelist_stance && source.health > source.maxHealth * 0.5) - source.balloon_alert(source, "exited duelist stance") - in_duelist_stance = FALSE - source.remove_traits(list(TRAIT_HARDLY_WOUNDED, TRAIT_BATON_RESISTANCE), type) - return - - if(!in_duelist_stance && source.health <= source.maxHealth * 0.5) - source.balloon_alert(source, "entered duelist stance") - in_duelist_stance = TRUE - source.add_traits(list(TRAIT_HARDLY_WOUNDED, TRAIT_BATON_RESISTANCE), type) - return - -#undef BLOOD_FLOW_PER_SEVEIRTY + action_to_add = /datum/action/cooldown/spell/wolves_among_sheep /datum/heretic_knowledge/blade_upgrade/blade name = "Empowered Blades" diff --git a/code/modules/antagonists/heretic/knowledge/lock_lore.dm b/code/modules/antagonists/heretic/knowledge/lock_lore.dm index 573a151f991..bde397a8048 100644 --- a/code/modules/antagonists/heretic/knowledge/lock_lore.dm +++ b/code/modules/antagonists/heretic/knowledge/lock_lore.dm @@ -103,18 +103,6 @@ research_tree_icon_path = 'icons/obj/card.dmi' research_tree_icon_state = "card_gold" -/datum/heretic_knowledge/key_ring/on_finished_recipe(mob/living/user, list/selected_atoms, turf/loc) - var/obj/item/card/id = locate(/obj/item/card/id/advanced) in selected_atoms - if(isnull(id)) - return FALSE - var/obj/item/card/id/advanced/heretic/result_item = new(loc) - if(!istype(result_item)) - return FALSE - selected_atoms -= id - result_item.eat_card(id) - result_item.shapeshift(id) - return TRUE - /datum/heretic_knowledge/mark/lock_mark name = "Mark of Lock" desc = "Your Mansus Grasp now applies the Mark of Lock. \ diff --git a/code/modules/antagonists/heretic/knowledge/side_ash_moon.dm b/code/modules/antagonists/heretic/knowledge/side_ash_moon.dm index b4470f9c7fb..c5bc72fd621 100644 --- a/code/modules/antagonists/heretic/knowledge/side_ash_moon.dm +++ b/code/modules/antagonists/heretic/knowledge/side_ash_moon.dm @@ -5,7 +5,7 @@ route = PATH_SIDE tier1 = /datum/heretic_knowledge/medallion - tier2 = /datum/heretic_knowledge/curse/paralysis + tier2 = /datum/heretic_knowledge/ether tier3 = /datum/heretic_knowledge/summon/ashy // Sidepaths for knowledge between Ash and Flesh. @@ -25,44 +25,21 @@ research_tree_icon_path = 'icons/obj/antags/eldritch.dmi' research_tree_icon_state = "eye_medalion" -/datum/heretic_knowledge/curse/paralysis - name = "Curse of Paralysis" - desc = "Allows you to transmute a hatchet and both a left and right leg to cast a curse of immobility on a crew member. \ - While cursed, the victim will be unable to walk. You can additionally supply an item that a victim has touched \ - or is covered in the victim's blood to make the curse last longer." - gain_text = "The flesh of humanity is weak. Make them bleed. Show them their fragility." - +/datum/heretic_knowledge/ether + name = "Ether Of The Newborn" + desc = "Conjures a single use potion, drinking it will remove any sort of abnormality from your body including diseases, traumas and implants \ + on top of restoring it to full health, at the cost of losing consciousness for an entire minute." + gain_text = "Vision and thought grow hazy as the fumes of this ichor swirl up to meet me. \ + Through the haze, I find myself staring back in relief, or something grossly resembling my visage. \ + It is this wretched thing that I consign to my fate, and whose own that I snatch through the haze of dreams. Fools that we are." required_atoms = list( - /obj/item/bodypart/leg/left = 1, - /obj/item/bodypart/leg/right = 1, - /obj/item/hatchet = 1, + /obj/item/shard = 1, + /obj/effect/decal/cleanable/vomit = 1, ) - duration = 3 MINUTES - duration_modifier = 2 - curse_color = "#f19a9a" + result_atoms = list(/obj/item/ether) cost = 1 - - research_tree_icon_path = 'icons/ui_icons/antags/heretic/knowledge.dmi' - research_tree_icon_state = "curse_paralysis" - - -/datum/heretic_knowledge/curse/paralysis/curse(mob/living/carbon/human/chosen_mob, boosted = FALSE) - if(chosen_mob.usable_legs <= 0) // What're you gonna do, curse someone who already can't walk? - to_chat(chosen_mob, span_notice("You feel a slight pain for a moment, but it passes shortly. Odd.")) - return - - to_chat(chosen_mob, span_danger("You suddenly lose feeling in your leg[chosen_mob.usable_legs == 1 ? "":"s"]!")) - chosen_mob.add_traits(list(TRAIT_PARALYSIS_L_LEG, TRAIT_PARALYSIS_R_LEG), type) - return ..() - -/datum/heretic_knowledge/curse/paralysis/uncurse(mob/living/carbon/human/chosen_mob, boosted = FALSE) - if(QDELETED(chosen_mob)) - return - - chosen_mob.remove_traits(list(TRAIT_PARALYSIS_L_LEG, TRAIT_PARALYSIS_R_LEG), type) - if(chosen_mob.usable_legs > 1) - to_chat(chosen_mob, span_green("You regain feeling in your leg[chosen_mob.usable_legs == 1 ? "":"s"]!")) - return ..() + research_tree_icon_path = 'icons/obj/antags/eldritch.dmi' + research_tree_icon_state = "poison_flask" /datum/heretic_knowledge/summon/ashy name = "Ashen Ritual" diff --git a/code/modules/antagonists/heretic/knowledge/side_blade_rust.dm b/code/modules/antagonists/heretic/knowledge/side_blade_rust.dm index a09c9cd8797..1666eb282cf 100644 --- a/code/modules/antagonists/heretic/knowledge/side_blade_rust.dm +++ b/code/modules/antagonists/heretic/knowledge/side_blade_rust.dm @@ -6,7 +6,7 @@ tier1 = /datum/heretic_knowledge/armor tier2 = list(/datum/heretic_knowledge/crucible, /datum/heretic_knowledge/rifle) - tier3 = /datum/heretic_knowledge/spell/rust_charge + tier3 = list(/datum/heretic_knowledge/spell/rust_charge, /datum/heretic_knowledge/greaves_of_the_prophet) // Sidepaths for knowledge between Rust and Blade. /datum/heretic_knowledge/armor @@ -115,4 +115,22 @@ action_to_add = /datum/action/cooldown/mob_cooldown/charge/rust cost = 1 - +/datum/heretic_knowledge/greaves_of_the_prophet + name = "Greaves Of The Prophet" + desc = "Conjures a pair of Armored Greaves, they confer to the user fully immunity to slips and the ability resist gravity at will." + gain_text = " \ + Gristle churns into joint, a pop, and the fool twists a blackened foot from the \ + jaws of another. At their game for centuries, this mangled tree of limbs twists, \ + thrashing snares buried into snarling gums, seeking to shred the weight of grafted \ + neighbors. Weighed down by lacerated feet, this canopy of rancid idiots ever seeks \ + the undoing of its own bonds. I dread the thought of walking in their wake, but \ + I must press on all the same. Their rhythms keep the feud fresh with indifference \ + to barrier or border. Pulling more into their turmoil as they waltz." + cost = 1 + required_atoms = list( + /obj/item/clothing/shoes/jackboots = 1, + /obj/item/stack/sheet/mineral/titanium = 2, + ) + result_atoms = list(/obj/item/clothing/shoes/greaves_of_the_prophet) + research_tree_icon_path = 'icons/obj/clothing/shoes.dmi' + research_tree_icon_state = "hereticgreaves" diff --git a/code/modules/antagonists/heretic/knowledge/side_lock_flesh.dm b/code/modules/antagonists/heretic/knowledge/side_lock_flesh.dm index 0f7c9d9fc70..11d31a64ebe 100644 --- a/code/modules/antagonists/heretic/knowledge/side_lock_flesh.dm +++ b/code/modules/antagonists/heretic/knowledge/side_lock_flesh.dm @@ -4,15 +4,27 @@ route = PATH_SIDE - tier1 = /datum/heretic_knowledge/dummy_lock_to_flesh + tier1 = /datum/heretic_knowledge/phylactery tier2 = /datum/heretic_knowledge/spell/opening_blast tier3 = /datum/heretic_knowledge/spell/apetra_vulnera -/datum/heretic_knowledge/dummy_lock_to_flesh - name = "Flesh and Lock ways" - desc = "Research this to gain access to the other path" - gain_text = "There are ways from feasting to wounding, the power of birth is close to the power of opening." +/** + * Phylactery of Damnation + */ +/datum/heretic_knowledge/phylactery + name = "Phylactery of Damnation" + desc = "Allows you to transmute a sheet of glass and a poppy into a Phylactery that can instantly draw blood, even from long distances. \ + Be warned, your target may still feel a prick." + gain_text = "A tincture twisted into the shape of a bloodsucker vermin. \ + Whether it chose the shape for itself, or this is the humor of the sickened mind that conjured this vile implement into being is something best not pondered." + required_atoms = list( + /obj/item/stack/sheet/glass = 1, + /obj/item/food/grown/poppy = 1, + ) + result_atoms = list(/obj/item/reagent_containers/cup/phylactery) cost = 1 + research_tree_icon_path = 'icons/obj/antags/eldritch.dmi' + research_tree_icon_state = "phylactery_2" // Sidepaths for knowledge between Knock and Flesh. /datum/heretic_knowledge/spell/opening_blast diff --git a/code/modules/antagonists/heretic/knowledge/side_lock_moon.dm b/code/modules/antagonists/heretic/knowledge/side_lock_moon.dm index 5d3795b0ce9..17d8e34e9da 100644 --- a/code/modules/antagonists/heretic/knowledge/side_lock_moon.dm +++ b/code/modules/antagonists/heretic/knowledge/side_lock_moon.dm @@ -6,18 +6,10 @@ tier1 = /datum/heretic_knowledge/spell/mind_gate tier2 = list(/datum/heretic_knowledge/unfathomable_curio, /datum/heretic_knowledge/painting) - tier3 = /datum/heretic_knowledge/dummy_moon_to_lock + tier3 = /datum/heretic_knowledge/codex_morbus // Sidepaths for knowledge between Knock and Moon. -/datum/heretic_knowledge/dummy_moon_to_lock - name = "Lock and Moon ways" - desc = "Research this to gain access to the other path" - gain_text = "The powers of Madness are like a wound in one's soul, and every wound can be opened and closed." - cost = 1 - - - /datum/heretic_knowledge/spell/mind_gate name = "Mind Gate" desc = "Grants you Mind Gate, a spell which inflicts hallucinations, \ @@ -111,3 +103,36 @@ user.balloon_alert(user, "no additional atom present!") return FALSE + +/** + * Codex Morbus, an upgrade to the base codex + * Functionally an upgraded version of the codex, but it also has the ability to cast curses by right clicking at a rune. + * Requires you to have the blood of your victim in your off-hand + */ +/datum/heretic_knowledge/codex_morbus + name = "Codex Morbus" + desc = "Allows you to use a codex cicatrix, and a body upgrades your Codex Cicactrix into a Codex Morbus. \ + It draws runes and siphons essences a bit faster. \ + Right Click on a rune to curse crewmembers, the target's blood is required for a curse to take effect." + gain_text = "The spine of this leather-bound tome creaks with an eerily pained sigh. \ + To ply page from place takes considerable effort, and I dare not linger on the suggestions the book makes for longer than necessary. \ + It speaks of coming plagues, of waiting supplicants of dead and forgotten gods, and the undoing of mortal kind. \ + It speaks of needles to peel the skin of the world back and leaving it to fester. And it speaks to me by name." + required_atoms = list( + /obj/item/codex_cicatrix = 1, + /mob/living/carbon/human = 1, + ) + result_atoms = list(/obj/item/codex_cicatrix/morbus) + cost = 1 + research_tree_icon_path = 'icons/obj/antags/eldritch.dmi' + research_tree_icon_state = "book_morbus" + +/datum/heretic_knowledge/codex_morbus/on_finished_recipe(mob/living/user, list/selected_atoms, turf/loc) + . = ..() + var/mob/living/carbon/human/to_fuck_up = locate() in selected_atoms + for(var/_limb in to_fuck_up.bodyparts) + var/obj/item/bodypart/limb = _limb + limb.force_wound_upwards(/datum/wound/slash/flesh/critical) + for(var/obj/item/bodypart/limb as anything in to_fuck_up.bodyparts) + to_fuck_up.cause_wound_of_type_and_severity(WOUND_BLUNT, limb, WOUND_SEVERITY_CRITICAL) + return TRUE diff --git a/code/modules/antagonists/heretic/knowledge/side_rust_cosmos.dm b/code/modules/antagonists/heretic/knowledge/side_rust_cosmos.dm index 0272f10b021..8a727b91ee4 100644 --- a/code/modules/antagonists/heretic/knowledge/side_rust_cosmos.dm +++ b/code/modules/antagonists/heretic/knowledge/side_rust_cosmos.dm @@ -5,7 +5,7 @@ route = PATH_SIDE tier1 = /datum/heretic_knowledge/essence - tier2 = list(/datum/heretic_knowledge/curse/corrosion, /datum/heretic_knowledge/entropy_pulse) + tier2 = list(/datum/heretic_knowledge/entropy_pulse, /datum/heretic_knowledge/rust_sower) tier3 = /datum/heretic_knowledge/summon/rusty @@ -29,6 +29,19 @@ research_tree_icon_path = 'icons/obj/antags/eldritch.dmi' research_tree_icon_state = "eldritch_flask" +/datum/heretic_knowledge/rust_sower + name = "Rust Sower Grenade" + desc = "Conjures a cursed grenade filled with Eldritch Rust, upon detonating it releases a huge cloud that blinds organics, rusts affected turfs and obliterates Silicons and Mechs." + gain_text = "The choked vines of the Rusted Hills are burdened with such overripe fruits. It undoes the markers of progress, leaving a clean slate to work into new shapes." + required_atoms = list( + /obj/item/grenade/chem_grenade = 1, + /obj/item/organ/liver = 1, + ) + result_atoms = list(/obj/item/grenade/chem_grenade/rust_sower) + cost = 1 + research_tree_icon_path = 'icons/obj/weapons/grenade.dmi' + research_tree_icon_state = "rustgrenade" + /datum/heretic_knowledge/entropy_pulse name = "Pulse of Entropy" desc = "Allows you to transmute 10 iron sheets and a garbage item to fill the surrounding vicinity of the rune with rust." @@ -55,40 +68,6 @@ nearby_turf.rust_heretic_act() return TRUE -/datum/heretic_knowledge/curse/corrosion - name = "Curse of Corrosion" - desc = "Allows you to transmute wirecutters, a pool of vomit, and a heart to cast a curse of sickness on a crew member. \ - While cursed, the victim will repeatedly vomit while their organs will take constant damage. You can additionally supply an item \ - that a victim has touched or is covered in the victim's blood to make the curse last longer." - gain_text = "The body of humanity is temporary. Their weaknesses cannot be stopped, like iron falling to rust. Show them all." - - required_atoms = list( - /obj/item/wirecutters = 1, - /obj/effect/decal/cleanable/vomit = 1, - /obj/item/organ/heart = 1, - ) - duration = 0.5 MINUTES - duration_modifier = 4 - curse_color = "#c1ffc9" - cost = 1 - - research_tree_icon_path = 'icons/ui_icons/antags/heretic/knowledge.dmi' - research_tree_icon_state = "curse_corrosion" - - -/datum/heretic_knowledge/curse/corrosion/curse(mob/living/carbon/human/chosen_mob, boosted = FALSE) - to_chat(chosen_mob, span_danger("You feel very ill...")) - chosen_mob.apply_status_effect(/datum/status_effect/corrosion_curse) - return ..() - -/datum/heretic_knowledge/curse/corrosion/uncurse(mob/living/carbon/human/chosen_mob, boosted = FALSE) - if(QDELETED(chosen_mob)) - return - - chosen_mob.remove_status_effect(/datum/status_effect/corrosion_curse) - to_chat(chosen_mob, span_green("You start to feel better.")) - return ..() - /datum/heretic_knowledge/summon/rusty name = "Rusted Ritual" desc = "Allows you to transmute a pool of vomit, some cable coil, and 10 sheets of iron into a Rust Walker. \ diff --git a/code/modules/antagonists/heretic/knowledge/starting_lore.dm b/code/modules/antagonists/heretic/knowledge/starting_lore.dm index 1b7d0c84ca5..875146567a4 100644 --- a/code/modules/antagonists/heretic/knowledge/starting_lore.dm +++ b/code/modules/antagonists/heretic/knowledge/starting_lore.dm @@ -271,7 +271,7 @@ GLOBAL_LIST_INIT(heretic_start_knowledge, initialize_starting_knowledge()) result_atoms = list(/obj/item/codex_cicatrix) cost = 1 is_starting_knowledge = TRUE - priority = MAX_KNOWLEDGE_PRIORITY - 3 // Least priority out of the starting knowledges, as it's an optional boon. + priority = MAX_KNOWLEDGE_PRIORITY - 4 // Least priority out of the starting knowledges, as it's an optional boon. var/static/list/non_mob_bindings = typecacheof(list(/obj/item/stack/sheet/leather, /obj/item/stack/sheet/animalhide, /obj/item/food/deadmouse)) research_tree_icon_path = 'icons/obj/antags/eldritch.dmi' research_tree_icon_state = "book" @@ -374,3 +374,42 @@ GLOBAL_LIST_INIT(heretic_start_knowledge, initialize_starting_knowledge()) var/drain_message = pick_list(HERETIC_INFLUENCE_FILE, "drain_message") to_chat(user, span_hypnophrase(span_big("[drain_message]"))) return . + +/** + * Warren King's Welcome + * Ritual available at the start. So that heretics can easily gain access to maintenance airlocks without having to rely on a HoP or having to off some poor assistant. + * Gives access to solars since those doors are especially useful to get in or out of space. + */ +/datum/heretic_knowledge/bookworm + name = "Warren King's Welcome" + desc = "Allows you to transmute 5 wires and a piece of paper to infuse any ID with maintenace and external airlock access." + gain_text = "Gnawed into vicious-stained fingerbones, my grim invitation snaps my nauseous and clouded mind towards the heavy-set door. \ + Slowly, the light dances between a crawling darkness, blanketing the fetid promenade with infinite machinations. \ + But the King will soon take his pound of flesh. Even here, the taxman takes their cut. For there are a thousands mouths to feed." + required_atoms = list( + /obj/item/stack/cable_coil = 5, + /obj/item/paper = 1, + ) + cost = 1 + is_starting_knowledge = TRUE + priority = MAX_KNOWLEDGE_PRIORITY - 3 + research_tree_icon_path = 'icons/obj/card.dmi' + research_tree_icon_state = "eldritch" + +/datum/heretic_knowledge/bookworm/recipe_snowflake_check(mob/living/user, list/atoms, list/selected_atoms, turf/loc) + . = ..() + for(var/obj/item/card/id/used_id in atoms) + if((ACCESS_MAINT_TUNNELS in used_id.access) && (ACCESS_EXTERNAL_AIRLOCKS in used_id.access)) // If we can't give any access we aren't elligible + continue + selected_atoms += used_id + return TRUE + + user.balloon_alert(user, "ritual failed, no ID lacking access!") + return FALSE + +/datum/heretic_knowledge/bookworm/on_finished_recipe(mob/living/user, list/selected_atoms, turf/loc) + . = ..() + var/obj/item/card/id/improved_id = locate() in selected_atoms + improved_id.add_access(list(ACCESS_MAINT_TUNNELS, ACCESS_EXTERNAL_AIRLOCKS), mode = FORCE_ADD_ALL) + selected_atoms -= improved_id + return TRUE diff --git a/code/modules/antagonists/heretic/magic/wolves_among_sheep.dm b/code/modules/antagonists/heretic/magic/wolves_among_sheep.dm new file mode 100644 index 00000000000..7f4f5eeebb6 --- /dev/null +++ b/code/modules/antagonists/heretic/magic/wolves_among_sheep.dm @@ -0,0 +1,137 @@ +/*! + * Contains the spell "Wolves among Sheep" + * Handles the creation of the "arena", in terms of visuals. Banishes windows/airlocks and puts down the floors + * For the functionality of the spell itself see [/obj/effect/abstract/heretic_arena] which is created during [/proc/create_arena()] + */ +/datum/action/cooldown/spell/wolves_among_sheep + name = "Wolves among Sheep" + desc = "Alters the fabric of reality, conjuring a magical arena unpassable to outsiders, \ + all participants are trapped and immune to any form of crowd control or enviromental hazards; \ + trapped participants are granted a Blade and are unable to leave or jaunt until they score a critical hit." + background_icon_state = "bg_heretic" + overlay_icon_state = null + button_icon = 'icons/mob/actions/actions_ecult.dmi' + button_icon_state = "among_sheep" + + school = SCHOOL_FORBIDDEN + cooldown_time = 2 MINUTES + + invocation = "D`M``N `XP`NS``N!" + invocation_type = INVOCATION_SHOUT + spell_requirements = NONE + /// Max distance our effect is expected to reach + var/max_range = 9 + /// Max distance our effect has *actually* reached + var/greatest_dist = 0 + /// Central turf where the spell was initially casted + var/turf/center_turf + /// List of all the turfs we've affected, built during /cast(). We use this to make things appear/disappear and revert once the spell expires + var/list/to_transform = list() + /// List of airlocks we've removed, so we can re-place them once the effect expires + var/list/banished_airlocks = list() + /// Timer before the effects of the spell ends. It's a variable here so we can end it prematurely + var/revert_timer + /// Reference to the arena so we can clear it if we need to + var/ongoing_arena + +/datum/action/cooldown/spell/wolves_among_sheep/cast(atom/cast_on) + . = ..() + center_turf = get_turf(owner) + playsound(center_turf,'sound/machines/airlock/airlockopen.ogg', 750, TRUE) + to_transform = list() + new /obj/effect/heretic_rune/big(center_turf) + addtimer(CALLBACK(src, PROC_REF(create_arena), center_turf), 1 SECONDS) + revert_timer = addtimer(CALLBACK(src, PROC_REF(revert_effects)), 61 SECONDS, TIMER_STOPPABLE) // 1 second to spread out, 60 seconds to fight + + // Loop to make the spreading floor effect before finalizing our arena + for(var/turf/transform_turf as anything in RANGE_TURFS(max_range, center_turf)) + var/turf_distance = get_dist(center_turf, transform_turf) + if(turf_distance > greatest_dist) + greatest_dist = turf_distance + if(greatest_dist > max_range) + stack_trace("greatest_dist ([greatest_dist]) has somehow exceeded the expected maximum range ([max_range])") + if(!to_transform["[turf_distance]"]) + to_transform["[turf_distance]"] = list() + to_transform["[turf_distance]"] += transform_turf + for(var/iterator in 1 to greatest_dist) + if(!to_transform["[iterator]"]) + continue + addtimer(CALLBACK(src, PROC_REF(apply_visual), to_transform["[iterator]"]), 1 * iterator) // 0.9 SECONDS to convert our area + + // Loop doesnt catch src.loc so we have to handle it manually + apply_visual(list(center_turf)) + +/datum/action/cooldown/spell/wolves_among_sheep/can_cast_spell(feedback) + . = ..() + for(var/obj/nearby_arena in GLOB.heretic_arenas) + // We can't allow arenas to overlap because they break each other during cleanup. + // If any future coder wants to allow arenas to merge or fight like domains, feel free to implement it. + if(get_dist(owner, nearby_arena) <= 25) + if(feedback) + owner.balloon_alert(owner, "another arena nearby!") + return FALSE + +/// Applies a visual to each turf +/datum/action/cooldown/spell/wolves_among_sheep/proc/apply_visual(list/turfs) + for(var/turf/target as anything in turfs) + if(isopenturf(target)) + var/turf_icon = "rose_stone_" + "[pick(1, 2, 3, 4, 5, 6, 7, 8)]" + target.add_alt_appearance(/datum/atom_hud/alternate_appearance/basic/everyone, "heretic_arena", image('icons/turf/floors/rose_stone_turf.dmi', target, turf_icon, layer = ABOVE_OPEN_TURF_LAYER)) + else if(isclosedturf(target)) + var/wall_icon = "rose_stone_" + "[pick(1, 2, 3, 4, 5, 6, 7, 8)]" + target.add_alt_appearance(/datum/atom_hud/alternate_appearance/basic/everyone, "heretic_arena", image('icons/turf/walls/rose_stone_wall.dmi', target, wall_icon, layer = ABOVE_OPEN_TURF_LAYER)) + + target.turf_flags |= NOJAUNT // We make the arena a NOJAUNT area so that stinky people cannot teleport in + + // Phase out the doors (restore them afterwards) + for(var/obj/machinery/door/airlock/to_banish in target) + banished_airlocks += to_banish + banished_airlocks[to_banish] = to_banish.loc + to_banish.moveToNullspace() + // Windows will also get an alt appearance + for(var/obj/structure/window/to_change in target) + to_change.add_alt_appearance(/datum/atom_hud/alternate_appearance/basic/everyone, "heretic_arena", image('icons/obj/structures.dmi', to_change, "stone_window_pane", layer = ABOVE_OPEN_TURF_LAYER)) + +/// Sets up the proximity monitor which handles things that are within the area and leave once they get someone to crit +/datum/action/cooldown/spell/wolves_among_sheep/proc/create_arena(turf/target) + RegisterSignals(owner, list(SIGNAL_ADDTRAIT(TRAIT_CRITICAL_CONDITION)), PROC_REF(on_caster_crit)) + + // This is where most of the funcionality of the spell is + ongoing_arena = new /obj/effect/abstract/heretic_arena(target, max_range, 60 SECONDS, owner) + RegisterSignal(ongoing_arena, COMSIG_QDELETING, PROC_REF(on_arena_delete)) + +/// Clears the timer if the arena is deleted +/datum/action/cooldown/spell/wolves_among_sheep/proc/on_arena_delete() + SIGNAL_HANDLER + deltimer(revert_timer) + ongoing_arena = null + revert_effects() + +/// If the caster goes into crit, the arena falls apart right away +/datum/action/cooldown/spell/wolves_among_sheep/proc/on_caster_crit() + SIGNAL_HANDLER + deltimer(revert_timer) + revert_effects() + +/// Undoes our changes +/datum/action/cooldown/spell/wolves_among_sheep/proc/revert_effects() + UnregisterSignal(owner, list(SIGNAL_ADDTRAIT(TRAIT_CRITICAL_CONDITION))) + for(var/iterator in 1 to greatest_dist) + var/backwards_iterator = greatest_dist - iterator + 1 //We go backwards + if(!to_transform["[backwards_iterator]"]) + continue + addtimer(CALLBACK(src, PROC_REF(revert_terrain), to_transform["[backwards_iterator]"]), 1 * iterator) + addtimer(CALLBACK(src, PROC_REF(revert_terrain), list(center_turf)), 1 SECONDS) + if(ongoing_arena) + QDEL_NULL(ongoing_arena) + +/// Transforms all the turfs and restores the airlocks +/datum/action/cooldown/spell/wolves_among_sheep/proc/revert_terrain(list/turfs) + for(var/turf/target as anything in turfs) + target.remove_alt_appearance("heretic_arena") + target.turf_flags = initial(target.turf_flags) // Restore flags to what they were + for(var/obj/structure/window/to_revert in target) + to_revert.remove_alt_appearance("heretic_arena") + for(var/obj/machinery/door/airlock/to_restore in banished_airlocks) + to_restore.forceMove(banished_airlocks[to_restore]) + banished_airlocks -= to_restore diff --git a/code/modules/deathmatch/deathmatch_loadouts.dm b/code/modules/deathmatch/deathmatch_loadouts.dm index 5275533d6f2..a63dfe1ed11 100644 --- a/code/modules/deathmatch/deathmatch_loadouts.dm +++ b/code/modules/deathmatch/deathmatch_loadouts.dm @@ -1024,7 +1024,6 @@ // I mean is it really that bad if they don't even know half this stuff is added to them. // It's like, forbidden knowledge. It fits with the mansus theme - great excuse for poor design! knowledge_to_grant = list( - /datum/heretic_knowledge/duel_stance, /datum/heretic_knowledge/blade_grasp, /datum/heretic_knowledge/blade_dance, /datum/heretic_knowledge/blade_upgrade/blade, diff --git a/code/modules/mob/living/damage_procs.dm b/code/modules/mob/living/damage_procs.dm index 09ab3a73fbf..7f87af20d32 100644 --- a/code/modules/mob/living/damage_procs.dm +++ b/code/modules/mob/living/damage_procs.dm @@ -95,7 +95,7 @@ if(BRAIN) damage_dealt = -1 * adjustOrganLoss(ORGAN_SLOT_BRAIN, damage_amount) - SEND_SIGNAL(src, COMSIG_MOB_AFTER_APPLY_DAMAGE, damage_dealt, damagetype, def_zone, blocked, wound_bonus, bare_wound_bonus,sharpness, attack_direction, attacking_item, wound_clothing) + SEND_SIGNAL(src, COMSIG_MOB_AFTER_APPLY_DAMAGE, damage_dealt, damagetype, def_zone, blocked, wound_bonus, bare_wound_bonus, sharpness, attack_direction, attacking_item, wound_clothing) return damage_dealt /** diff --git a/code/modules/mob/living/living_defense.dm b/code/modules/mob/living/living_defense.dm index 467c31ee06f..50724d3ef82 100644 --- a/code/modules/mob/living/living_defense.dm +++ b/code/modules/mob/living/living_defense.dm @@ -255,7 +255,7 @@ if(!thrown_item.throwforce) return var/armor = run_armor_check(zone, MELEE, "Your armor has protected your [parse_zone_with_bodypart(zone)].", "Your armor has softened hit to your [parse_zone_with_bodypart(zone)].", thrown_item.armour_penetration, "", FALSE, thrown_item.weak_against_armour) - apply_damage(thrown_item.throwforce, thrown_item.damtype, zone, armor, sharpness = thrown_item.get_sharpness(), wound_bonus = (nosell_hit * CANT_WOUND)) + apply_damage(thrown_item.throwforce, thrown_item.damtype, zone, armor, sharpness = thrown_item.get_sharpness(), wound_bonus = (nosell_hit * CANT_WOUND), attacking_item = thrown_item) if(QDELETED(src)) //Damage can delete the mob. return if(body_position == LYING_DOWN) // physics says it's significantly harder to push someone by constantly chucking random furniture at them if they are down on the floor. diff --git a/code/modules/unit_tests/heretic_rituals.dm b/code/modules/unit_tests/heretic_rituals.dm index b55136cface..63c4c76ba8b 100644 --- a/code/modules/unit_tests/heretic_rituals.dm +++ b/code/modules/unit_tests/heretic_rituals.dm @@ -7,6 +7,7 @@ * - Summon rituals sleep after completing as they expect a ghost candidate to fill the summon, so they're skipped. * - Final rituals results in a bunch of side-effects and vary a good deal so they're skipped explicitly. * - Sacrifice ritual (Hunt and Sacrifice) requires sacrifice targets, as well as spawning a new z-level, so it's better not to test. + * - Codex Morbus doesn't consume the body that is required in it's ritual. */ /datum/unit_test/heretic_rituals @@ -29,6 +30,7 @@ /datum/heretic_knowledge/summon, /datum/heretic_knowledge/ultimate, /datum/heretic_knowledge/hunt_and_sacrifice, + /datum/heretic_knowledge/codex_morbus, )) var/list/all_ritual_knowledge = list() diff --git a/icons/hud/screen_alert.dmi b/icons/hud/screen_alert.dmi index 7ee26d58ae4..2d12bdd0151 100755 Binary files a/icons/hud/screen_alert.dmi and b/icons/hud/screen_alert.dmi differ diff --git a/icons/mob/actions/actions_ecult.dmi b/icons/mob/actions/actions_ecult.dmi index d287622f898..9710dfd3f50 100644 Binary files a/icons/mob/actions/actions_ecult.dmi and b/icons/mob/actions/actions_ecult.dmi differ diff --git a/icons/mob/actions/backgrounds.dmi b/icons/mob/actions/backgrounds.dmi index c8f8b723f9e..0c82afeb05c 100644 Binary files a/icons/mob/actions/backgrounds.dmi and b/icons/mob/actions/backgrounds.dmi differ diff --git a/icons/mob/clothing/feet.dmi b/icons/mob/clothing/feet.dmi index 95a3790ac73..b316830f19a 100644 Binary files a/icons/mob/clothing/feet.dmi and b/icons/mob/clothing/feet.dmi differ diff --git a/icons/mob/effects/crown.dmi b/icons/mob/effects/crown.dmi new file mode 100644 index 00000000000..ed346627c23 Binary files /dev/null and b/icons/mob/effects/crown.dmi differ diff --git a/icons/mob/inhands/equipment/security_lefthand.dmi b/icons/mob/inhands/equipment/security_lefthand.dmi index 91306c0a093..e87eebd154e 100644 Binary files a/icons/mob/inhands/equipment/security_lefthand.dmi and b/icons/mob/inhands/equipment/security_lefthand.dmi differ diff --git a/icons/mob/inhands/equipment/security_righthand.dmi b/icons/mob/inhands/equipment/security_righthand.dmi index c6d26854eb6..e153e6395ff 100644 Binary files a/icons/mob/inhands/equipment/security_righthand.dmi and b/icons/mob/inhands/equipment/security_righthand.dmi differ diff --git a/icons/obj/antags/eldritch.dmi b/icons/obj/antags/eldritch.dmi index 0a7b097127c..8eb5fb8a394 100644 Binary files a/icons/obj/antags/eldritch.dmi and b/icons/obj/antags/eldritch.dmi differ diff --git a/icons/obj/card.dmi b/icons/obj/card.dmi index 66bf7a51912..7b7a18dc221 100644 Binary files a/icons/obj/card.dmi and b/icons/obj/card.dmi differ diff --git a/icons/obj/clothing/shoes.dmi b/icons/obj/clothing/shoes.dmi index 8c91be43d4a..32bd4d5c076 100644 Binary files a/icons/obj/clothing/shoes.dmi and b/icons/obj/clothing/shoes.dmi differ diff --git a/icons/obj/structures.dmi b/icons/obj/structures.dmi index a77b88944cb..f3e7b554253 100644 Binary files a/icons/obj/structures.dmi and b/icons/obj/structures.dmi differ diff --git a/icons/obj/weapons/grenade.dmi b/icons/obj/weapons/grenade.dmi index c2eba7adcc1..415166f9c40 100644 Binary files a/icons/obj/weapons/grenade.dmi and b/icons/obj/weapons/grenade.dmi differ diff --git a/icons/turf/floors/rose_stone_turf.dmi b/icons/turf/floors/rose_stone_turf.dmi new file mode 100644 index 00000000000..9fba7a3d8b5 Binary files /dev/null and b/icons/turf/floors/rose_stone_turf.dmi differ diff --git a/icons/turf/walls.dmi b/icons/turf/walls.dmi index e12843bc69b..4c655cf4b5b 100644 Binary files a/icons/turf/walls.dmi and b/icons/turf/walls.dmi differ diff --git a/icons/turf/walls/rose_stone_wall.dmi b/icons/turf/walls/rose_stone_wall.dmi new file mode 100644 index 00000000000..2bbda788d05 Binary files /dev/null and b/icons/turf/walls/rose_stone_wall.dmi differ diff --git a/sound/items/weapons/attributions.txt b/sound/items/weapons/attributions.txt new file mode 100644 index 00000000000..6a7d58874db --- /dev/null +++ b/sound/items/weapons/attributions.txt @@ -0,0 +1,4 @@ +{ + rust_sower_armbomb.ogg - EnterTheJake + rust_sower_explode.ogg - EnterTheJake +} diff --git a/sound/items/weapons/rust_sower_armbomb.ogg b/sound/items/weapons/rust_sower_armbomb.ogg new file mode 100644 index 00000000000..0cc796ba2a8 Binary files /dev/null and b/sound/items/weapons/rust_sower_armbomb.ogg differ diff --git a/sound/items/weapons/rust_sower_explode.ogg b/sound/items/weapons/rust_sower_explode.ogg new file mode 100644 index 00000000000..52b1cd8ec4c Binary files /dev/null and b/sound/items/weapons/rust_sower_explode.ogg differ diff --git a/tgstation.dme b/tgstation.dme index 53038fe6a88..e82a5382c9f 100644 --- a/tgstation.dme +++ b/tgstation.dme @@ -1895,6 +1895,7 @@ #include "code\datums\proximity_monitor\proximity_monitor.dm" #include "code\datums\proximity_monitor\fields\ai_target_tracking.dm" #include "code\datums\proximity_monitor\fields\gravity.dm" +#include "code\datums\proximity_monitor\fields\heretic_arena.dm" #include "code\datums\proximity_monitor\fields\timestop.dm" #include "code\datums\proximity_monitor\fields\void_storm.dm" #include "code\datums\proximity_monitor\fields\projectile_dampener\projectile_dampener.dm" @@ -3338,6 +3339,7 @@ #include "code\modules\antagonists\greentext\greentext.dm" #include "code\modules\antagonists\heretic\cosmic_effect.dm" #include "code\modules\antagonists\heretic\heretic_antag.dm" +#include "code\modules\antagonists\heretic\heretic_curses.dm" #include "code\modules\antagonists\heretic\heretic_focus.dm" #include "code\modules\antagonists\heretic\heretic_knowledge.dm" #include "code\modules\antagonists\heretic\heretic_living_heart.dm" @@ -3354,7 +3356,9 @@ #include "code\modules\antagonists\heretic\items\forbidden_book.dm" #include "code\modules\antagonists\heretic\items\heretic_armor.dm" #include "code\modules\antagonists\heretic\items\heretic_blades.dm" +#include "code\modules\antagonists\heretic\items\heretic_grenade.dm" #include "code\modules\antagonists\heretic\items\heretic_necks.dm" +#include "code\modules\antagonists\heretic\items\heretic_shoes.dm" #include "code\modules\antagonists\heretic\items\hunter_rifle.dm" #include "code\modules\antagonists\heretic\items\keyring.dm" #include "code\modules\antagonists\heretic\items\labyrinth_handbook.dm" @@ -3428,6 +3432,7 @@ #include "code\modules\antagonists\heretic\magic\void_prison.dm" #include "code\modules\antagonists\heretic\magic\void_pull.dm" #include "code\modules\antagonists\heretic\magic\wave_of_desperation.dm" +#include "code\modules\antagonists\heretic\magic\wolves_among_sheep.dm" #include "code\modules\antagonists\heretic\status_effects\buffs.dm" #include "code\modules\antagonists\heretic\status_effects\debuffs.dm" #include "code\modules\antagonists\heretic\status_effects\ghoul.dm"