\ + The old ones gave you these tasks to fulfill:") + owner.announce_objectives() + to_chat(owner, "The book whispers, the forbidden knowledge walks once again!
\ + Your book allows you to research abilities, read it very carefully! you cannot undo what has been done!
\ + You gain charges by either collecitng influences or sacrifcing people tracked by the living heart
\ + You can find a basic guide at : https://tgstation13.org/wiki/Heresy_101 ") + +/datum/antagonist/heretic/on_gain() + var/mob/living/current = owner.current + if(ishuman(current)) + forge_primary_objectives() + gain_knowledge(/datum/eldritch_knowledge/spell/basic) + gain_knowledge(/datum/eldritch_knowledge/living_heart) + current.log_message("has been converted to the cult of the forgotten ones!", LOG_ATTACK, color="#960000") + GLOB.reality_smash_track.AddMind(owner) + START_PROCESSING(SSprocessing,src) + if(give_equipment) + equip_cultist() + return ..() + +/datum/antagonist/heretic/on_removal() + + for(var/X in researched_knowledge) + var/datum/eldritch_knowledge/EK = researched_knowledge[X] + EK.on_lose(owner.current) + + if(!silent) + owner.current.visible_message("", null, null, null, owner.current) + to_chat(owner.current, "Your mind begins to flare as the otherwordly knowledge escapes your grasp!") + owner.current.log_message("has renounced the cult of the old ones!", LOG_ATTACK, color="#960000") + GLOB.reality_smash_track.RemoveMind(owner) + STOP_PROCESSING(SSprocessing,src) + + return ..() + + +/datum/antagonist/heretic/proc/equip_cultist() + var/mob/living/carbon/H = owner.current + if(!istype(H)) + return + . += ecult_give_item(/obj/item/forbidden_book, H) + . += ecult_give_item(/obj/item/living_heart, H) + +/datum/antagonist/heretic/proc/ecult_give_item(obj/item/item_path, mob/living/carbon/human/H) + var/list/slots = list( + "backpack" = ITEM_SLOT_BACKPACK, + "left pocket" = ITEM_SLOT_LPOCKET, + "right pocket" = ITEM_SLOT_RPOCKET + ) + + var/T = new item_path(H) + var/item_name = initial(item_path.name) + var/where = H.equip_in_one_of_slots(T, slots) + if(!where) + to_chat(H, "Unfortunately, you weren't able to get a [item_name]. This is very bad and you should adminhelp immediately (press F1).") + return FALSE + else + to_chat(H, "You have a [item_name] in your [where].") + if(where == "backpack") + SEND_SIGNAL(H.back, COMSIG_TRY_STORAGE_SHOW, H) + return TRUE + +/datum/antagonist/heretic/process() + + for(var/X in researched_knowledge) + var/datum/eldritch_knowledge/EK = researched_knowledge[X] + EK.on_life(owner.current) + +/datum/antagonist/heretic/proc/forge_primary_objectives() + var/list/assasination = list() + var/list/protection = list() + for(var/i in 1 to 2) + var/pck = pick("assasinate","stalk","protect") + switch(pck) + if("assasinate") + var/datum/objective/assassinate/A = new + A.owner = owner + var/list/owners = A.get_owners() + A.find_target(owners,protection) + assasination += A.target + objectives += A + if("stalk") + var/datum/objective/stalk/S = new + S.owner = owner + S.find_target() + objectives += S + if("protect") + var/datum/objective/protect/P = new + P.owner = owner + var/list/owners = P.get_owners() + P.find_target(owners,assasination) + protection += P.target + objectives += P + + var/datum/objective/sacrifice_ecult/SE = new + SE.owner = owner + SE.update_explanation_text() + objectives += SE + +/datum/antagonist/heretic/apply_innate_effects(mob/living/mob_override) + . = ..() + var/mob/living/current = owner.current + if(mob_override) + current = mob_override + add_antag_hud(antag_hud_type, antag_hud_name, current) + handle_clown_mutation(current, mob_override ? null : "Knowledge described in the book allowed you to overcome your clownish nature, allowing you to use complex items effectively.") + current.faction |= "heretics" + +/datum/antagonist/heretic/remove_innate_effects(mob/living/mob_override) + . = ..() + var/mob/living/current = owner.current + if(mob_override) + current = mob_override + remove_antag_hud(antag_hud_type, antag_hud_name, current) + handle_clown_mutation(current, removing = FALSE) + current.faction -= "heretics" + +/datum/antagonist/heretic/get_admin_commands() + . = ..() + .["Equip"] = CALLBACK(src,.proc/equip_cultist) + +/datum/antagonist/heretic/roundend_report() + var/list/parts = list() + + var/cultiewin = TRUE + + parts += printplayer(owner) + parts += "Sacrifices Made: [total_sacrifices]" + + if(length(objectives)) + var/count = 1 + for(var/o in objectives) + var/datum/objective/objective = o + if(objective.check_completion()) + parts += "Objective #[count]: [objective.explanation_text] Success!" + else + parts += "Objective #[count]: [objective.explanation_text] Fail." + cultiewin = FALSE + count++ + + if(cultiewin) + parts += "The heretic was successful!" + else + parts += "The heretic has failed." + + parts += "Knowledge Researched: " + + var/list/knowledge_message = list() + var/list/knowledge = get_all_knowledge() + for(var/X in knowledge) + var/datum/eldritch_knowledge/EK = knowledge[X] + knowledge_message += "[EK.name]" + parts += knowledge_message.Join(", ") + + return parts.Join("
") +//////////////// +// Knowledge // +//////////////// + +/datum/antagonist/heretic/proc/gain_knowledge(datum/eldritch_knowledge/EK) + if(get_knowledge(EK)) + return FALSE + var/datum/eldritch_knowledge/initialized_knowledge = new EK + researched_knowledge[initialized_knowledge.type] = initialized_knowledge + initialized_knowledge.on_gain(owner.current) + return TRUE + +/datum/antagonist/heretic/proc/get_researchable_knowledge() + var/list/researchable_knowledge = list() + var/list/banned_knowledge = list() + for(var/X in researched_knowledge) + var/datum/eldritch_knowledge/EK = researched_knowledge[X] + researchable_knowledge |= EK.next_knowledge + banned_knowledge |= EK.banned_knowledge + banned_knowledge |= EK.type + researchable_knowledge -= banned_knowledge + return researchable_knowledge + +/datum/antagonist/heretic/proc/get_knowledge(wanted) + return researched_knowledge[wanted] + +/datum/antagonist/heretic/proc/get_all_knowledge() + return researched_knowledge + +//////////////// +// Objectives // +//////////////// + +/datum/objective/stalk + name = "spendtime" + var/timer = 5 MINUTES + +/datum/objective/stalk/process() + if(owner?.current.stat != DEAD && target?.current.stat != DEAD && (target in view(5,owner.current))) + timer -= 1 SECONDS + ///we don't want to process after the counter reaches 0, otherwise it is wasted processing + if(timer <= 0) + STOP_PROCESSING(SSprocessing,src) + +/datum/objective/stalk/Destroy(force, ...) + STOP_PROCESSING(SSprocessing,src) + return ..() + +/datum/objective/stalk/update_explanation_text() + //we want to start processing after we set the timer + timer += rand(-3 MINUTES, 3 MINUTES) + START_PROCESSING(SSprocessing,src) + if(target?.current) + explanation_text = "Stalk [target.name] for at least [DisplayTimeText(timer)] while they're alive." + else + explanation_text = "Free Objective" + +/datum/objective/stalk/check_completion() + return timer <= 0 || explanation_text == "Free Objective" + +/datum/objective/sacrifice_ecult + name = "sacrifice" + +/datum/objective/sacrifice_ecult/update_explanation_text() + . = ..() + target_amount = rand(2,6) + explanation_text = "Sacrifice at least [target_amount] people." + +/datum/objective/sacrifice_ecult/check_completion() + if(!owner) + return FALSE + var/datum/antagonist/heretic/cultie = owner.has_antag_datum(/datum/antagonist/heretic) + if(!cultie) + return FALSE + return cultie.total_sacrifices >= target_amount diff --git a/code/modules/antagonists/eldritch_cult/eldritch_book.dm b/code/modules/antagonists/eldritch_cult/eldritch_book.dm new file mode 100644 index 00000000000..b9d8e11750c --- /dev/null +++ b/code/modules/antagonists/eldritch_cult/eldritch_book.dm @@ -0,0 +1,137 @@ +/obj/item/forbidden_book + name = "Codex Cicatrix" + desc = "Book describing the secrets of the veil." + icon = 'icons/obj/eldritch.dmi' + icon_state = "book" + w_class = WEIGHT_CLASS_SMALL + ///Last person that touched this + var/mob/living/last_user + ///how many charges do we have? + var/charge = 1 + ///Where we cannot create the rune? + var/static/list/blacklisted_turfs = typecacheof(list(/turf/closed,/turf/open/space,/turf/open/lava)) + +/obj/item/forbidden_book/Destroy() + last_user = null + . = ..() + + +/obj/item/forbidden_book/examine(mob/user) + . = ..() + if(!IS_HERETIC(user)) + return + . += "The Tome holds [charge] charges." + . += "Use it on the floor to create a transmutation rune, used to perform rituals." + . += "Hit an influence in the black part with it to gain a charge." + . += "Hit a transmutation rune to destroy it." + +/obj/item/forbidden_book/afterattack(atom/target, mob/user, proximity_flag, click_parameters) + . = ..() + if(!proximity_flag || !IS_HERETIC(user)) + return + if(istype(target,/obj/effect/eldritch)) + remove_rune(target,user) + if(istype(target,/obj/effect/reality_smash)) + get_power_from_influence(target,user) + if(istype(target,/turf/open)) + draw_rune(target,user) + +///Gives you a charge and destroys a corresponding influence +/obj/item/forbidden_book/proc/get_power_from_influence(atom/target, mob/user) + var/obj/effect/reality_smash/RS = target + to_chat(target, "You start drawing power from influence...") + if(do_after(user,10 SECONDS,FALSE,RS)) + qdel(RS) + charge += 1 + +///Draws a rune on a selected turf +/obj/item/forbidden_book/proc/draw_rune(atom/target,mob/user) + + for(var/turf/T in range(1,target)) + if(is_type_in_typecache(T, blacklisted_turfs)) + to_chat(target, "The terrain doesn't support runes!") + return + var/A = get_turf(target) + to_chat(target, "You start drawing a rune...") + + if(do_after(user,30 SECONDS,A)) + + new /obj/effect/eldritch/big(A) + +///Removes runes from the selected turf +/obj/item/forbidden_book/proc/remove_rune(atom/target,mob/user) + + to_chat(target, "You start removing a rune...") + if(do_after(user,2 SECONDS,user)) + qdel(target) + +/obj/item/forbidden_book/ui_interact(mob/user, ui_key = "main", datum/tgui/ui = null, force_open = 0, datum/tgui/master_ui = null, datum/ui_state/state = GLOB.default_state) // Remember to use the appropriate state. + if(!IS_HERETIC(user)) + return FALSE + last_user = user + ui = SStgui.try_update_ui(user, src, ui_key, ui, force_open) + if(!ui) + ui = new(user, src, ui_key, "ForbiddenLore", name, 500, 900, master_ui, state) + ui.open() + +/obj/item/forbidden_book/ui_data(mob/user) + var/datum/antagonist/heretic/cultie = user.mind.has_antag_datum(/datum/antagonist/heretic) + var/list/to_know = list() + for(var/Y in cultie.get_researchable_knowledge()) + to_know += new Y + var/list/known = cultie.get_all_knowledge() + var/list/data = list() + var/list/lore = list() + + data["charges"] = charge + + for(var/X in to_know) + lore = list() + var/datum/eldritch_knowledge/EK = X + lore["type"] = EK.type + lore["name"] = EK.name + lore["cost"] = EK.cost + lore["disabled"] = EK.cost <= charge ? FALSE : TRUE + lore["path"] = EK.route + lore["state"] = "Research" + lore["flavour"] = EK.gain_text + lore["desc"] = EK.desc + data["to_know"] += list(lore) + + for(var/X in known) + lore = list() + var/datum/eldritch_knowledge/EK = known[X] + lore["name"] = EK.name + lore["cost"] = EK.cost + lore["disabled"] = TRUE + lore["path"] = EK.route + lore["state"] = "Researched" + lore["flavour"] = EK.gain_text + lore["desc"] = EK.desc + data["to_know"] += list(lore) + + if(!length(data["to_know"])) + data["to_know"] = null + + return data + +/obj/item/forbidden_book/ui_act(action, params) + . = ..() + if(.) + return + switch(action) + if("research") + var/datum/antagonist/heretic/cultie = last_user.mind.has_antag_datum(/datum/antagonist/heretic) + var/ekname = params["name"] + for(var/X in cultie.get_researchable_knowledge()) + var/datum/eldritch_knowledge/EK = X + if(initial(EK.name) != ekname) + continue + if(cultie.gain_knowledge(EK)) + charge -= text2num(params["cost"]) + return TRUE + + update_icon() // Not applicable to all objects. + +/obj/item/forbidden_book/debug + charge = 100 diff --git a/code/modules/antagonists/eldritch_cult/eldritch_effects.dm b/code/modules/antagonists/eldritch_cult/eldritch_effects.dm new file mode 100644 index 00000000000..a04ac8f615f --- /dev/null +++ b/code/modules/antagonists/eldritch_cult/eldritch_effects.dm @@ -0,0 +1,225 @@ +/obj/effect/eldritch + name = "Generic rune" + desc = "Weird combination of shapes and symbols etched into the floor itself. The indentation is filled with thick black tar-like fluid." + anchored = TRUE + icon_state = "" + resistance_flags = FIRE_PROOF | UNACIDABLE | ACID_PROOF + layer = SIGIL_LAYER + +/obj/effect/eldritch/attack_hand(mob/living/user) + . = ..() + if(.) + return + try_activate(user) + +/obj/effect/eldritch/proc/try_activate(mob/living/user) + if(!IS_HERETIC(user)) + return + activate(user) + +/obj/effect/eldritch/attacked_by(obj/item/I, mob/living/user) + . = ..() + if(istype(I,/obj/item/nullrod)) + qdel(src) + +/obj/effect/eldritch/proc/activate(mob/living/user) + // Have fun trying to read this proc. + var/datum/antagonist/heretic/cultie = user.mind.has_antag_datum(/datum/antagonist/heretic) + var/list/knowledge = cultie.get_all_knowledge() + var/list/atoms_in_range = list() + + for(var/A in range(1, src)) + var/atom/atom_in_range = A + if(istype(atom_in_range,/area)) + continue + if(istype(atom_in_range,/turf)) // we dont want turfs + continue + if(istype(atom_in_range,/mob/living)) + var/mob/living/living_in_range = atom_in_range + if(living_in_range.stat != DEAD || living_in_range == user) // we only accept corpses, no living beings allowed. + continue + atoms_in_range += atom_in_range + + for(var/X in knowledge) + var/datum/eldritch_knowledge/current_eldritch_knowledge = knowledge[X] + + //has to be done so that we can freely edit the local_required_atoms without fucking up the eldritch knowledge + var/list/local_required_atoms = list() + + if(!current_eldritch_knowledge.required_atoms || current_eldritch_knowledge.required_atoms.len == 0) + continue + + local_required_atoms += current_eldritch_knowledge.required_atoms + + var/list/selected_atoms = list() + + if(!current_eldritch_knowledge.recipe_snowflake_check(atoms_in_range,drop_location(),selected_atoms)) + continue + + for(var/LR in local_required_atoms) + var/list/local_required_atom_list = LR + + for(var/LAIR in atoms_in_range) + var/atom/local_atom_in_range = LAIR + if(is_type_in_list(local_atom_in_range,local_required_atom_list)) + selected_atoms |= local_atom_in_range + local_required_atoms -= list(local_required_atom_list) + + if(length(local_required_atoms) > 0) + continue + + flick("[icon_state]_active",src) + playsound(user, 'sound/magic/castsummon.ogg', 75, TRUE) + if(current_eldritch_knowledge.on_finished_recipe(user,selected_atoms,loc)) + current_eldritch_knowledge.cleanup_atoms(selected_atoms) + return + to_chat(user,"Your ritual failed! You used either wrong components or are missing something important!") + +/obj/effect/eldritch/big + name = "Transmutation rune" + icon = 'icons/effects/96x96.dmi' + icon_state = "eldritch_rune1" + pixel_x = -32 //So the big ol' 96x96 sprite shows up right + pixel_y = -32 + +/** + * #Reality smash tracker + * + * Stupid fucking list holder, DONT create new ones, it will break the game, this is automnatically created whenever eldritch cultists are created. + * + * Tracks relevant data, generates relevant data, useful tool + */ +/datum/reality_smash_tracker + ///list of tracked reality smashes + var/list/smashes = list() + ///List of mobs with ability to see the smashes + var/list/targets = list() + +/datum/reality_smash_tracker/Destroy(force, ...) + if(GLOB.reality_smash_track == src) + stack_trace("/datum/reality_smash_tracker was deleted. Heretics may no longer access any influences. Fix it or call coder support") + QDEL_LIST(smashes) + targets.Cut() + return ..() + +/** + * Automatically fixes the target and smash network + * + * Fixes any bugs that are caused by late Generate() or exchanging clients + */ +/datum/reality_smash_tracker/proc/ReworkNetwork() + listclearnulls(targets) + listclearnulls(smashes) + for(var/mind in targets) + for(var/X in smashes) + var/obj/effect/reality_smash/reality_smash = X + reality_smash.AddMind(mind) + +/** + * Generates a set amount of reality smashes based on the N value + * + * Automatically creates more reality smashes + */ +/datum/reality_smash_tracker/proc/_Generate() + var/targ_len = length(targets) + var/smash_len = length(smashes) + var/number = targ_len * 6 - smash_len + + for(var/i in 0 to number) + + var/turf/chosen_location = get_safe_random_station_turf() + //we also dont want them close to each other, at least 1 tile of seperation + var/obj/effect/reality_smash/what_if_i_have_one = locate() in range(1, chosen_location) + var/obj/effect/broken_illusion/what_if_i_had_one_but_got_used = locate() in range(1, chosen_location) + if(what_if_i_have_one || what_if_i_had_one_but_got_used) //we dont want to spawn + continue + var/obj/effect/reality_smash/RS = new/obj/effect/reality_smash(chosen_location.loc) + smashes += RS + + ReworkNetwork() + +/** + * Adds a mind to the list of people that can see the reality smashes + * + * Use this whenever you want to add someone to the list + */ +/datum/reality_smash_tracker/proc/AddMind(var/datum/mind/M) + RegisterSignal(M.current,COMSIG_MOB_LOGIN,.proc/ReworkNetwork) + targets |= M + _Generate() + for(var/X in smashes) + var/obj/effect/reality_smash/reality_smash = X + reality_smash.AddMind(M) + + +/** + * Removes a mind from the list of people that can see the reality smashes + * + * Use this whenever you want to remove someone from the list + */ +/datum/reality_smash_tracker/proc/RemoveMind(var/datum/mind/M) + UnregisterSignal(M.current,COMSIG_MOB_LOGIN) + targets -= M + for(var/obj/effect/reality_smash/RS in smashes) + RS.RemoveMind(M) + +/obj/effect/broken_illusion + name = "Pierced reality" + icon = 'icons/effects/eldritch.dmi' + icon_state = "pierced_illusion" + +/obj/effect/broken_illusion/examine(mob/user) + if(!IS_HERETIC(user) && ishuman(user)) + var/mob/living/carbon/human/human_user = user + to_chat(human_user,"Your brain hurts when you look at this!") + human_user.adjustOrganLoss(ORGAN_SLOT_BRAIN,10) + . = ..() + +/obj/effect/reality_smash + name = "/improper reality smash" + icon = 'icons/effects/eldritch.dmi' + ///We cannot use icon_state since this is invisible, functions the same way but with custom behaviour. + var/image_state = "reality_smash" + ///Who can see us? + var/list/minds = list() + ///Tracked image + var/image/img + +/obj/effect/reality_smash/Initialize() + . = ..() + img = image(icon, src, image_state, OBJ_LAYER) + generate_name() + +/obj/effect/reality_smash/Destroy() + on_destroy() + return ..() + +///Custom effect that happens on destruction +/obj/effect/reality_smash/proc/on_destroy() + for(var/cm in minds) + var/datum/mind/cultie = cm + if(cultie.current?.client) + cultie.current.client.images -= img + //clear the list + minds -= cultie + img = null + new /obj/effect/broken_illusion(drop_location()) + +///Makes the mind able to see this effect +/obj/effect/reality_smash/proc/AddMind(var/datum/mind/cultie) + if(cultie.current.client) + minds |= cultie + cultie.current.client.images |= img + +///Makes the mind not able to see this effect +/obj/effect/reality_smash/proc/RemoveMind(var/datum/mind/cultie) + if(cultie.current.client) + minds -= cultie + cultie.current.client.images -= img + +///Generates random name +/obj/effect/reality_smash/proc/generate_name() + var/static/list/prefix = list("Omniscient","Thundering","Enlightening","Intrusive","Rejectful","Atomized","Subtle","Rising","Lowering","Fleeting","Towering","Blissful") + var/static/list/postfix = list("Flaw","Presence","Crack","Heat","Cold","Memory","Reminder","Breeze") + + name = pick(prefix) + " " + pick(postfix) diff --git a/code/modules/antagonists/eldritch_cult/eldritch_items.dm b/code/modules/antagonists/eldritch_cult/eldritch_items.dm new file mode 100644 index 00000000000..74aab436ed8 --- /dev/null +++ b/code/modules/antagonists/eldritch_cult/eldritch_items.dm @@ -0,0 +1,125 @@ +/obj/item/living_heart + name = "Living Heart" + desc = "Link to the worlds beyond." + icon = 'icons/obj/eldritch.dmi' + icon_state = "living_heart" + w_class = WEIGHT_CLASS_SMALL + ///Target + var/mob/living/carbon/human/target + +/obj/item/living_heart/attack_self(mob/user) + . = ..() + if(!IS_HERETIC(user)) + return + if(!target) + to_chat(user,"No target could be found. Put the living heart on the rune and use the rune to recieve a target.") + return + var/dist = get_dist(user.loc,target.loc) + var/dir = get_dir(user.loc,target.loc) + + switch(dist) + if(0 to 15) + to_chat(user,"[target.real_name] is near you. They are to the [dir2text(dir)] of you!") + if(16 to 31) + to_chat(user,"[target.real_name] is somewhere in your vicinty. They are to the [dir2text(dir)] of you!") + if(32 to 127) + to_chat(user,"[target.real_name] is far away from you. They are to the [dir2text(dir)] of you!") + else + to_chat(user,"[target.real_name] is beyond our reach.") + + if(target.stat == DEAD) + to_chat(user,"[target.real_name] is dead. Bring them onto a transmutation rune!") + +/obj/item/melee/sickly_blade + name = "Sickly blade" + desc = "Twisted sickle with an ornamental eye. The eyes looks at you." + icon = 'icons/obj/eldritch.dmi' + icon_state = "eldritch_blade" + inhand_icon_state = "eldritch_blade" + lefthand_file = 'icons/mob/inhands/weapons/swords_lefthand.dmi' + righthand_file = 'icons/mob/inhands/weapons/swords_righthand.dmi' + flags_1 = CONDUCT_1 + sharpness = IS_SHARP + w_class = WEIGHT_CLASS_NORMAL + force = 17 + throwforce = 10 + hitsound = 'sound/weapons/bladeslice.ogg' + attack_verb = list("attacked", "slashed", "stabbed", "sliced", "torn", "ripped", "diced", "rended") + +/obj/item/melee/sickly_blade/attack(mob/living/M, mob/living/user) + if(!IS_HERETIC(user)) + to_chat(user,"Echoes of the breat beyond break your mind!") + var/mob/living/carbon/human/human_user = user + human_user.AdjustParalyzed(5 SECONDS) + return FALSE + return ..() + +/obj/item/melee/sickly_blade/afterattack(atom/target, mob/user, proximity_flag, click_parameters) + . = ..() + var/datum/antagonist/heretic/cultie = user.mind.has_antag_datum(/datum/antagonist/heretic) + if(!cultie || !proximity_flag) + return + var/list/knowledge = cultie.get_all_knowledge() + for(var/X in knowledge) + var/datum/eldritch_knowledge/eldritch_knowledge_datum = knowledge[X] + eldritch_knowledge_datum.on_eldritch_blade(target,user,proximity_flag,click_parameters) + +/obj/item/melee/sickly_blade/rust + name = "Rusted Blade" + +/obj/item/melee/sickly_blade/ash + name = "Ashen Blade" + +/obj/item/melee/sickly_blade/flesh + name = "Flesh Blade" + +/obj/item/clothing/neck/eldritch_amulet + name = "Warm Eldritch Medallion" + desc = "Eldritch medallion that let's you see." + icon = 'icons/obj/eldritch.dmi' + icon_state = "eye_medalion" + w_class = WEIGHT_CLASS_SMALL + ///What trait do we want to add upon equipiing + var/trait = TRAIT_THERMAL_VISION + +/obj/item/clothing/neck/eldritch_amulet/equipped(mob/user, slot) + . = ..() + if(ishuman(user) && user.mind && slot == ITEM_SLOT_NECK && IS_HERETIC(user) ) + ADD_TRAIT(user, trait, CLOTHING_TRAIT) + user.update_sight() + +/obj/item/clothing/neck/eldritch_amulet/dropped(mob/user) + . = ..() + REMOVE_TRAIT(user, trait, CLOTHING_TRAIT) + user.update_sight() + +/obj/item/clothing/neck/eldritch_amulet/piercing + name = "Piercing Eldritch Medallion" + trait = TRAIT_XRAY_VISION + +/obj/item/clothing/head/hooded/cult_hoodie/eldritch + name = "ominous hood" + icon_state = "eldritch" + desc = "A torn, dust-caked hood. Strange eyes line the inside." + flags_inv = HIDEMASK|HIDEEARS|HIDEEYES|HIDEFACE|HIDEHAIR|HIDEFACIALHAIR + flags_cover = HEADCOVERSEYES | HEADCOVERSMOUTH | PEPPERPROOF + flash_protect = FLASH_PROTECTION_WELDER + +/obj/item/clothing/suit/hooded/cultrobes/eldritch + name = "ominous armor" + desc = "A ragged, dusty set of robes. Strange eyes line the inside." + icon_state = "eldritch_armor" + inhand_icon_state = "eldritch_armor" + flags_inv = HIDESHOES|HIDEJUMPSUIT + body_parts_covered = CHEST|GROIN|LEGS|FEET|ARMS + allowed = list(/obj/item/melee/sickly_blade, /obj/item/forbidden_book) + hoodtype = /obj/item/clothing/head/hooded/cult_hoodie/eldritch + // slightly better than normal cult robes + armor = list("melee" = 50, "bullet" = 50, "laser" = 50,"energy" = 50, "bomb" = 35, "bio" = 20, "rad" = 0, "fire" = 20, "acid" = 20) + +/obj/item/reagent_containers/glass/beaker/eldritch + name = "flask of eldritch essence" + desc = "Toxic to the close minded. Healing to those with knowledge of the beyond." + icon = 'icons/obj/eldritch.dmi' + icon_state = "eldrich_flask" + list_reagents = list(/datum/reagent/eldritch = 50) diff --git a/code/modules/antagonists/eldritch_cult/eldritch_knowledge.dm b/code/modules/antagonists/eldritch_cult/eldritch_knowledge.dm new file mode 100644 index 00000000000..fe736619458 --- /dev/null +++ b/code/modules/antagonists/eldritch_cult/eldritch_knowledge.dm @@ -0,0 +1,281 @@ + +/** + * #Eldritch Knwoledge + * + * Datum that makes eldritch cultist interesting. + * + * Eldritch knowledge aren't instantiated anywhere roundstart, and are initalized and destroyed as the round goes on. + */ +/datum/eldritch_knowledge + ///Name of the knowledge + var/name = "Basic knowledge" + ///Description of the knowledge + var/desc = "Basic knowledge of forbidden arts." + ///What shows up + var/gain_text = "" + ///Cost of knowledge in souls + var/cost = 0 + ///Next knowledge in the research tree + var/list/next_knowledge = list() + ///What knowledge is incompatible with this. This will simply make it impossible to research knowledges that are in banned_knowledge once this gets researched. + var/list/banned_knowledge = list() + ///Used with rituals, how many items this needs + var/list/required_atoms = list() + ///What do we get out of this + var/list/result_atoms = list() + ///What path is this on defaults to "Side" + var/route = PATH_SIDE + +/datum/eldritch_knowledge/New() + . = ..() + var/list/temp_list + for(var/X in required_atoms) + var/atom/A = X + temp_list += list(typesof(A)) + required_atoms = temp_list + +/** + * What happens when this is assigned to an antag datum + * + * This proc is called whenever a new eldritch knowledge is added to an antag datum + */ +/datum/eldritch_knowledge/proc/on_gain(mob/user) + to_chat(user, "[gain_text]") + return +/** + * What happens when you loose this + * + * This proc is called whenever antagonist looses his antag datum, put cleanup code in here + */ +/datum/eldritch_knowledge/proc/on_lose(mob/user) + return +/** + * What happens every tick + * + * This proc is called on SSprocess in eldritch cultist antag datum. SSprocess happens roughly every second + */ +/datum/eldritch_knowledge/proc/on_life(mob/user) + return + +/** + * Special check for recipes + * + * If you are adding a more complex summoning or something that requires a special check that parses through all the atoms in an area override this. + */ +/datum/eldritch_knowledge/proc/recipe_snowflake_check(list/atoms,loc) + return TRUE + +/** + * What happens once the recipe is succesfully finished + * + * By default this proc creates atoms from result_atoms list. Override this is you want something else to happen. + */ +/datum/eldritch_knowledge/proc/on_finished_recipe(mob/living/user,list/atoms,loc) + if(result_atoms.len == 0) + return FALSE + + for(var/A in result_atoms) + new A(loc) + + return TRUE + +/** + * Used atom cleanup + * + * Overide this proc if you dont want ALL ATOMS to be destroyed. useful in many situations. + */ +/datum/eldritch_knowledge/proc/cleanup_atoms(list/atoms) + for(var/X in atoms) + var/atom/A = X + if(!isliving(A)) + qdel(A) + return + +/** + * Mansus grasp act + * + * Gives addtional effects to mansus grasp spell + */ +/datum/eldritch_knowledge/proc/on_mansus_grasp(atom/target, mob/user, proximity_flag, click_parameters) + return + + +/** + * Sickly blade act + * + * Gives addtional effects to sickly blade weapon + */ +/datum/eldritch_knowledge/proc/on_eldritch_blade(target,user,proximity_flag,click_parameters) + return + +////////////// +///Subtypes/// +////////////// + +/datum/eldritch_knowledge/spell + var/obj/effect/proc_holder/spell/spell_to_add + +/datum/eldritch_knowledge/spell/on_gain(mob/user) + var/obj/effect/proc_holder/S = new spell_to_add + user.mind.AddSpell(S) + return ..() + +/datum/eldritch_knowledge/spell/on_lose(mob/user) + user.mind.RemoveSpell(spell_to_add) + return ..() + +/datum/eldritch_knowledge/curse + var/timer = 5 MINUTES + var/list/fingerprints = list() + +/datum/eldritch_knowledge/curse/recipe_snowflake_check(list/atoms, loc) + fingerprints = list() + for(var/X in atoms) + var/atom/A = X + fingerprints |= A.return_fingerprints() + listclearnulls(fingerprints) + if(fingerprints.len == 0) + return FALSE + return TRUE + +/datum/eldritch_knowledge/curse/on_finished_recipe(mob/living/user,list/atoms,loc) + + var/list/compiled_list = list() + + for(var/H in GLOB.human_list) + var/mob/living/carbon/human/human_to_check = H + if(fingerprints[md5(human_to_check.dna.uni_identity)]) + compiled_list |= human_to_check.real_name + compiled_list[human_to_check.real_name] = human_to_check + + if(compiled_list.len == 0) + to_chat(user, "The items don't posses required fingerprints.") + return FALSE + + var/chosen_mob = input("Select the person you wish to curse","Your target") as null|anything in sortList(compiled_list, /proc/cmp_mob_realname_dsc) + if(!chosen_mob) + return FALSE + curse(compiled_list[chosen_mob]) + addtimer(CALLBACK(src, .proc/uncurse, compiled_list[chosen_mob]),timer) + return TRUE + +/datum/eldritch_knowledge/curse/proc/curse(mob/living/chosen_mob) + return + +/datum/eldritch_knowledge/curse/proc/uncurse(mob/living/chosen_mob) + return + +/datum/eldritch_knowledge/summon + //Mob to summon + var/mob/living/mob_to_summon + + +/datum/eldritch_knowledge/summon/on_finished_recipe(mob/living/user,list/atoms,loc) + //we need to spawn the mob first so that we can use it in pollCandidatesForMob, we will move it from nullspace down the code + var/mob/living/summoned = new mob_to_summon(loc) + message_admins("[summoned.name] is being summoned by [user.real_name] in [loc]") + var/list/mob/dead/observer/candidates = pollCandidatesForMob("Do you want to play as [summoned.name]", ROLE_HERETIC, null, FALSE, 100, summoned) + if(!LAZYLEN(candidates)) + to_chat(user,"No ghost could be found...") + qdel(summoned) + return FALSE + var/mob/dead/observer/C = pick(candidates) + log_game("[key_name_admin(C)] has taken control of ([key_name_admin(summoned)]), their master is [user.real_name]") + summoned.ghostize(FALSE) + summoned.key = C.key + var/datum/antagonist/heretic_monster/heretic_monster = summoned.mind.has_antag_datum(/datum/antagonist/heretic_monster) + var/datum/antagonist/heretic/master = user.mind.has_antag_datum(/datum/antagonist/heretic) + heretic_monster.set_owner(master) + return TRUE + +//Ascension knowledge +/datum/eldritch_knowledge/final + var/finished = FALSE + +/datum/eldritch_knowledge/final/recipe_snowflake_check(list/atoms, loc,selected_atoms) + if(finished) + return FALSE + var/counter = 0 + for(var/mob/living/carbon/human/H in atoms) + selected_atoms |= H + counter++ + if(counter == 3) + return TRUE + return FALSE + +/datum/eldritch_knowledge/final/on_finished_recipe(mob/living/user, list/atoms, loc) + finished = TRUE + return TRUE + +/datum/eldritch_knowledge/final/cleanup_atoms(list/atoms) + . = ..() + for(var/mob/living/carbon/human/H in atoms) + H.gib() + + +/////////////// +///Base lore/// +/////////////// + +/datum/eldritch_knowledge/spell/basic + name = "Break of dawn" + desc = "Starts your journey in the mansus. Allows you to select a target using a living heart on a transmutation rune." + gain_text = "Gates of mansus open up to your mind." + next_knowledge = list(/datum/eldritch_knowledge/base_rust,/datum/eldritch_knowledge/base_ash,/datum/eldritch_knowledge/base_flesh) + cost = 0 + spell_to_add = /obj/effect/proc_holder/spell/targeted/touch/mansus_grasp + required_atoms = list(/obj/item/living_heart) + route = "Start" + +/datum/eldritch_knowledge/spell/basic/recipe_snowflake_check(list/atoms, loc) + . = ..() + for(var/obj/item/living_heart/LH in atoms) + if(!LH.target) + return TRUE + if(LH.target in atoms) + return TRUE + return FALSE + +/datum/eldritch_knowledge/spell/basic/on_finished_recipe(mob/living/user, list/atoms, loc) + . = TRUE + var/mob/living/carbon/carbon_user = user + for(var/obj/item/living_heart/LH in atoms) + + if(LH.target && LH.target.stat == DEAD) + to_chat(carbon_user,"Your patrons accepts your offer..") + var/mob/living/carbon/human/H = LH.target + H.gib() + LH.target = null + var/datum/antagonist/heretic/EC = carbon_user.mind.has_antag_datum(/datum/antagonist/heretic) + + EC.total_sacrifices++ + for(var/X in carbon_user.get_all_gear()) + if(!istype(X,/obj/item/forbidden_book)) + continue + var/obj/item/forbidden_book/FB = X + FB.charge++ + break + + if(!LH.target) + var/datum/objective/A = new + A.owner = user.mind + var/datum/mind/targeted = A.find_target()//easy way, i dont feel like copy pasting that entire block of code + LH.target = targeted.current + qdel(A) + if(LH.target) + to_chat(user,"Your new target has been selected, go and sacrifice [LH.target.real_name]!") + + else + to_chat(user,"target could not be found for living heart.") + +/datum/eldritch_knowledge/spell/basic/cleanup_atoms(list/atoms) + return + +/datum/eldritch_knowledge/living_heart + name = "Living Heart" + desc = "Allows you to create additional living hearts, using a heart, a pool of blood and a poppy. Living hearts when used on a transmutation rune will grant you a person to hunt and sacrifice on the rune. Every sacrifice gives you an additional charge in the book." + gain_text = "Gates of mansus open up to your mind." + cost = 0 + required_atoms = list(/obj/item/organ/heart,/obj/effect/decal/cleanable/blood,/obj/item/reagent_containers/food/snacks/grown/poppy) + result_atoms = list(/obj/item/living_heart) + route = "Start" diff --git a/code/modules/antagonists/eldritch_cult/eldritch_magic.dm b/code/modules/antagonists/eldritch_cult/eldritch_magic.dm new file mode 100644 index 00000000000..b64754cc094 --- /dev/null +++ b/code/modules/antagonists/eldritch_cult/eldritch_magic.dm @@ -0,0 +1,473 @@ +/obj/effect/proc_holder/spell/targeted/ethereal_jaunt/shift/ash + name = "Ashen passage" + desc = "Low range spell allowing you to pass through a few walls." + school = "transmutation" + invocation = "ASH'N P'SSG'" + invocation_type = "whisper" + charge_max = 150 + range = -1 + action_icon = 'icons/mob/actions/actions_ecult.dmi' + action_icon_state = "ash_shift" + action_background_icon_state = "bg_ecult" + jaunt_in_time = 13 + jaunt_duration = 10 + jaunt_in_type = /obj/effect/temp_visual/dir_setting/ash_shift + jaunt_out_type = /obj/effect/temp_visual/dir_setting/ash_shift/out + +/obj/effect/proc_holder/spell/targeted/ethereal_jaunt/shift/ash/long + jaunt_duration = 50 + +/obj/effect/proc_holder/spell/targeted/ethereal_jaunt/shift/ash/play_sound() + return + +/obj/effect/temp_visual/dir_setting/ash_shift + name = "ash_shift" + icon = 'icons/mob/mob.dmi' + icon_state = "ash_shift2" + duration = 13 + +/obj/effect/temp_visual/dir_setting/ash_shift/out + icon_state = "ash_shift" + +/obj/effect/proc_holder/spell/targeted/touch/mansus_grasp + name = "Mansus Grasp" + desc = "Touch spell that let's you channel the power of the old gods through you." + hand_path = /obj/item/melee/touch_attack/mansus_fist + school = "evocation" + charge_max = 150 + clothes_req = FALSE + action_icon = 'icons/mob/actions/actions_ecult.dmi' + action_icon_state = "mansus_grasp" + action_background_icon_state = "bg_ecult" + +/obj/item/melee/touch_attack/mansus_fist + name = "Mansus Grasp" + desc = "A sinister looking aura that distorts the flow of reality around it. Causes knockdown, major stamina damage aswell as some Brute. It gains additional beneficial effects with certain knowledges you can research." + icon_state = "disintegrate" + inhand_icon_state = "disintegrate" + catchphrase = "R'CH T'H TR'TH" + +/obj/item/melee/touch_attack/mansus_fist/afterattack(atom/target, mob/user, proximity_flag, click_parameters) + . = ..() + playsound(user, 'sound/items/welder.ogg', 75, TRUE) + if(ishuman(target)) + var/mob/living/carbon/human/tar = target + if(tar.anti_magic_check()) + tar.visible_message("Spell bounces off of [target]!","The spell bounces off of you!") + return + var/datum/mind/M = user.mind + var/datum/antagonist/heretic/cultie = M.has_antag_datum(/datum/antagonist/heretic) + + if(iscarbon(target)) + var/mob/living/carbon/C = target + C.adjustBruteLoss(10) + C.AdjustKnockdown(5 SECONDS) + C.adjustStaminaLoss(80) + var/list/knowledge = cultie.get_all_knowledge() + for(var/X in knowledge) + var/datum/eldritch_knowledge/EK = knowledge[X] + EK.on_mansus_grasp(target, user, proximity_flag, click_parameters) + +/obj/effect/proc_holder/spell/aoe_turf/rust_conversion + name = "Aggressive Spread" + desc = "Spreads rust onto nearby turfs." + school = "transmutation" + charge_max = 300 //twice as long as mansus grasp + clothes_req = FALSE + invocation = "A'GRSV SPR'D" + invocation_type = "whisper" + range = 3 + action_icon = 'icons/mob/actions/actions_ecult.dmi' + action_icon_state = "corrode" + action_background_icon_state = "bg_ecult" + +/obj/effect/proc_holder/spell/aoe_turf/rust_conversion/cast(list/targets, mob/user = usr) + playsound(user, 'sound/items/welder.ogg', 75, TRUE) + for(var/turf/T in targets) + ///What we want is the 3 tiles around the user and the tile under him to be rusted, so min(dist,1)-1 causes us to get 0 for these tiles, rest of the tiles are based on chance + var/chance = 100 - (max(get_dist(T,user),1)-1)*100/(range+1) + if(!prob(chance)) + continue + T.rust_heretic_act() + +/obj/effect/proc_holder/spell/aoe_turf/rust_conversion/small + name = "Rust Conversion" + desc = "Spreads rust onto nearby turfs." + range = 2 + +/obj/effect/proc_holder/spell/targeted/touch/ash_leech + name = "Blood Siphon" + desc = "Touch spell that heals you while damaging the enemy, has a chance to transfer wounds between you and your enemy." + hand_path = /obj/item/melee/touch_attack/ash_leech + school = "evocation" + charge_max = 150 + clothes_req = FALSE + invocation = "FL'MS O'ET'RN'ITY" + invocation_type = "whisper" + action_icon = 'icons/mob/actions/actions_ecult.dmi' + action_icon_state = "blood_siphon" + action_background_icon_state = "bg_ecult" + +/obj/item/melee/touch_attack/ash_leech + name = "Blood Siphon" + desc = "A sinister looking aura that distorts the flow of reality around it." + icon_state = "disintegrate" + inhand_icon_state = "disintegrate" + catchphrase = "R'BRTH" + +/obj/item/melee/touch_attack/ash_leech/afterattack(atom/target, mob/user, proximity_flag, click_parameters) + . = ..() + playsound(user, 'sound/magic/demon_attack1.ogg', 75, TRUE) + if(ishuman(target)) + var/mob/living/carbon/human/tar = target + if(tar.anti_magic_check()) + tar.visible_message("Spell bounces off of [target]!","The spell bounces off of you!") + return + var/mob/living/carbon/C2 = user + if(isliving(target)) + var/mob/living/L = target + L.adjustBruteLoss(20) + C2.adjustBruteLoss(-20) + if(iscarbon(target)) + var/mob/living/carbon/C1 = target + for(var/obj/item/bodypart/bodypart in C2.bodyparts) + for(var/datum/wound/wound in bodypart.wounds) + if(prob(50)) + continue + var/obj/item/bodypart/target_bodypart = locate(bodypart.type) in C1.bodyparts + if(!target_bodypart) + continue + wound.remove_wound() + wound.apply_wound(target_bodypart) + + C1.blood_volume -= 20 + if(C2.blood_volume < BLOOD_VOLUME_MAXIMUM) //we dont want to explode after all + C2.blood_volume += 20 + return + +/obj/effect/proc_holder/spell/targeted/projectile/dumbfire/rust_wave + name = "Patron's Reach" + desc = "Channels energy into your gauntlet - firing it results in a wave of rust being created in it's wake." + proj_type = /obj/projectile/magic/spell/rust_wave + charge_max = 350 + clothes_req = FALSE + action_icon = 'icons/mob/actions/actions_ecult.dmi' + action_icon_state = "rust_wave" + action_background_icon_state = "bg_ecult" + invocation = "SPR'D TH' WO'D" + invocation_type = "whisper" + +/obj/projectile/magic/spell/rust_wave + name = "Patron's Reach" + icon_state = "eldritch_projectile" + alpha = 180 + damage = 30 + damage_type = TOX + hitsound = 'sound/weapons/punch3.ogg' + trigger_range = 0 + ignored_factions = list("heretics") + range = 15 + speed = 1 + +/obj/projectile/magic/spell/rust_wave/Moved(atom/OldLoc, Dir) + . = ..() + playsound(src, 'sound/items/welder.ogg', 75, TRUE) + var/list/turflist = list() + var/turf/T1 + turflist += get_turf(src) + T1 = get_step(src,turn(dir,90)) + turflist += T1 + turflist += get_step(T1,turn(dir,90)) + T1 = get_step(src,turn(dir,-90)) + turflist += T1 + turflist += get_step(T1,turn(dir,-90)) + for(var/X in turflist) + if(!X || prob(25)) + continue + var/turf/T = X + T.rust_heretic_act() + +/obj/effect/proc_holder/spell/targeted/projectile/dumbfire/rust_wave/short + name = "Small Patron's Reach" + proj_type = /obj/projectile/magic/spell/rust_wave/short + +/obj/projectile/magic/spell/rust_wave/short + range = 7 + speed = 2 + +/obj/effect/proc_holder/spell/pointed/ash_cleave + name = "Cleave" + desc = "Causes severe bleeding on a target and people around them" + school = "transmutation" + charge_max = 350 + clothes_req = FALSE + invocation = "CL'VE" + invocation_type = "whisper" + range = 9 + action_icon = 'icons/mob/actions/actions_ecult.dmi' + action_icon_state = "cleave" + action_background_icon_state = "bg_ecult" + +/obj/effect/proc_holder/spell/pointed/ash_cleave/cast(list/targets, mob/user) + if(!targets.len) + to_chat(user, "No target found in range!") + return FALSE + if(!can_target(targets[1], user)) + return FALSE + + for(var/mob/living/carbon/human/C in range(1,targets[1])) + targets |= C + + + for(var/X in targets) + var/mob/living/carbon/human/target = X + if(target == user) + continue + if(target.anti_magic_check()) + to_chat(user, "The spell had no effect!") + target.visible_message("[target]'s veins flash with fire, but their magic protection repulses the blaze!", \ + "Your veins flash with fire, but your magic protection repels the blaze!") + continue + + target.visible_message("[target]'s veins are shredded from within as an unholy blaze erupts from their blood!", \ + "Your veins burst from within and unholy flame erupts from your blood!") + for(var/repetition in 0 to 2) + var/obj/item/bodypart/bodypart = pick(target.bodyparts) + var/datum/wound/brute/cut/critical/crit_wound = new + crit_wound.apply_wound(bodypart) + target.adjustFireLoss(20) + new /obj/effect/temp_visual/cleave(target.drop_location()) + +/obj/effect/proc_holder/spell/pointed/ash_cleave/can_target(atom/target, mob/user, silent) + . = ..() + if(!.) + return FALSE + if(!istype(target,/mob/living/carbon/human)) + if(!silent) + to_chat(user, "You are unable to cleave [target]!") + return FALSE + return TRUE + +/obj/effect/proc_holder/spell/pointed/ash_cleave/long + charge_max = 650 + +/obj/effect/proc_holder/spell/pointed/touch/mad_touch + name = "Touch of madness" + desc = "Touch spell that drains your enemies sanity." + school = "transmutation" + charge_max = 150 + clothes_req = FALSE + invocation_type = "none" + range = 2 + action_icon = 'icons/mob/actions/actions_ecult.dmi' + action_icon_state = "mad_touch" + action_background_icon_state = "bg_ecult" + +/obj/effect/proc_holder/spell/pointed/touch/mad_touch/can_target(atom/target, mob/user, silent) + . = ..() + if(!.) + return FALSE + if(!istype(target,/mob/living/carbon/human)) + if(!silent) + to_chat(user, "You are unable to touch [target]!") + return FALSE + return TRUE + +/obj/effect/proc_holder/spell/pointed/touch/mad_touch/cast(list/targets, mob/user) + . = ..() + for(var/mob/living/carbon/target in targets) + if(ishuman(targets)) + var/mob/living/carbon/human/tar = target + if(tar.anti_magic_check()) + tar.visible_message("Spell bounces off of [target]!","The spell bounces off of you!") + return + if(target.mind && !target.mind.has_antag_datum(/datum/antagonist/heretic)) + to_chat(user,"[target.name] has been cursed!") + SEND_SIGNAL(target, COMSIG_ADD_MOOD_EVENT, "gates_of_mansus", /datum/mood_event/gates_of_mansus) + +/obj/effect/proc_holder/spell/pointed/ash_final + name = "Nightwatcher's Rite" + desc = "Powerful spell that releases 5 streams of fire away from you." + school = "transmutation" + invocation = "F'RE" + invocation_type = "whisper" + charge_max = 300 + range = 15 + clothes_req = FALSE + action_icon = 'icons/mob/actions/actions_ecult.dmi' + action_icon_state = "flames" + action_background_icon_state = "bg_ecult" + +/obj/effect/proc_holder/spell/pointed/ash_final/cast(list/targets, mob/user) + for(var/X in targets) + var/T + T = line_target(-25, range, X, user) + INVOKE_ASYNC(src, .proc/fire_line, user,T) + T = line_target(10, range, X, user) + INVOKE_ASYNC(src, .proc/fire_line, user,T) + T = line_target(0, range, X, user) + INVOKE_ASYNC(src, .proc/fire_line, user,T) + T = line_target(-10, range, X, user) + INVOKE_ASYNC(src, .proc/fire_line, user,T) + T = line_target(25, range, X, user) + INVOKE_ASYNC(src, .proc/fire_line, user,T) + return ..() + +/obj/effect/proc_holder/spell/pointed/ash_final/proc/line_target(offset, range, atom/at , atom/user) + if(!at) + return + var/angle = ATAN2(at.x - user.x, at.y - user.y) + offset + var/turf/T = get_turf(user) + for(var/i in 1 to range) + var/turf/check = locate(user.x + cos(angle) * i, user.y + sin(angle) * i, user.z) + if(!check) + break + T = check + return (getline(user, T) - get_turf(user)) + +/obj/effect/proc_holder/spell/pointed/ash_final/proc/fire_line(atom/source, list/turfs) + var/list/hit_list = list() + for(var/turf/T in turfs) + if(istype(T, /turf/closed)) + break + + for(var/mob/living/L in T.contents) + if(L.anti_magic_check()) + L.visible_message("Spell bounces off of [L]!","The spell bounces off of you!") + continue + if(L in hit_list || L == source) + continue + hit_list += L + L.adjustFireLoss(20) + to_chat(L, "You're hit by [source]'s fire breath!") + + new /obj/effect/hotspot(T) + T.hotspot_expose(700,50,1) + // deals damage to mechs + for(var/obj/mecha/M in T.contents) + if(M in hit_list) + continue + hit_list += M + M.take_damage(45, BURN, "melee", 1) + sleep(1.5) + +/obj/effect/proc_holder/spell/targeted/shapeshift/eldritch + invocation = "SH'PE" + invocation_type = "whisper" + clothes_req = FALSE + action_background_icon_state = "bg_ecult" + possible_shapes = list(/mob/living/simple_animal/mouse,\ + /mob/living/simple_animal/pet/dog/corgi,\ + /mob/living/simple_animal/hostile/carp,\ + /mob/living/simple_animal/bot/secbot, \ + /mob/living/simple_animal/pet/fox,\ + /mob/living/simple_animal/pet/cat ) + +/obj/effect/proc_holder/spell/targeted/emplosion/eldritch + name = "Energetic Pulse" + invocation = "E'P" + invocation_type = "whisper" + clothes_req = FALSE + action_background_icon_state = "bg_ecult" + range = -1 + include_user = TRUE + charge_max = 300 + emp_heavy = 6 + emp_light = 10 + +/obj/effect/proc_holder/spell/aoe_turf/fire_cascade + name = "Fire Cascade" + desc = "creates hot turfs around you." + school = "transmutation" + charge_max = 300 //twice as long as mansus grasp + clothes_req = FALSE + invocation = "C'SC'DE" + invocation_type = "whisper" + range = 4 + action_icon = 'icons/mob/actions/actions_ecult.dmi' + action_icon_state = "fire_ring" + action_background_icon_state = "bg_ecult" + +/obj/effect/proc_holder/spell/aoe_turf/fire_cascade/cast(list/targets, mob/user = usr) + INVOKE_ASYNC(src, .proc/fire_cascade, user,range) + +/obj/effect/proc_holder/spell/aoe_turf/fire_cascade/proc/fire_cascade(atom/centre,max_range) + playsound(get_turf(centre), 'sound/items/welder.ogg', 75, TRUE) + var/_range = 1 + for(var/i = 0, i <= max_range,i++) + for(var/turf/T in spiral_range_turfs(_range,centre)) + new /obj/effect/hotspot(T) + T.hotspot_expose(700,50,1) + _range++ + sleep(3) + +/obj/effect/proc_holder/spell/aoe_turf/fire_cascade/big + range = 6 + +/obj/effect/proc_holder/spell/targeted/telepathy/eldritch + invocation = "" + invocation_type = "whisper" + clothes_req = FALSE + action_background_icon_state = "bg_ecult" + +/obj/effect/proc_holder/spell/targeted/fire_sworn + name = "Oath of Fire" + desc = "For a minute you will passively create a ring of fire around you." + invocation = "FL'MS" + invocation_type = "whisper" + clothes_req = FALSE + action_background_icon_state = "bg_ecult" + range = -1 + include_user = TRUE + charge_max = 700 + action_icon = 'icons/mob/actions/actions_ecult.dmi' + action_icon_state = "fire_ring" + ///how long it lasts + var/duration = 1 MINUTES + ///who casted it right now + var/mob/current_user + ///Determines if you get the fire ring effect + var/has_fire_ring = FALSE + +/obj/effect/proc_holder/spell/targeted/fire_sworn/cast(list/targets, mob/user) + . = ..() + current_user = user + has_fire_ring = TRUE + addtimer(CALLBACK(src, .proc/remove, user), duration, TIMER_OVERRIDE|TIMER_UNIQUE) + +/obj/effect/proc_holder/spell/targeted/fire_sworn/proc/remove() + has_fire_ring = FALSE + +/obj/effect/proc_holder/spell/targeted/fire_sworn/process() + . = ..() + if(!has_fire_ring) + return + for(var/turf/T in range(1,current_user)) + new /obj/effect/hotspot(T) + T.hotspot_expose(700,50,1) + + +/obj/effect/proc_holder/spell/targeted/worm_contract + name = "Force Contract" + desc = "Forces all the worm parts to collapse onto a single turf" + invocation_type = "none" + clothes_req = FALSE + action_background_icon_state = "bg_ecult" + range = -1 + include_user = TRUE + charge_max = 300 + action_icon = 'icons/mob/actions/actions_ecult.dmi' + action_icon_state = "worm_contract" + +/obj/effect/proc_holder/spell/targeted/worm_contract/cast(list/targets, mob/user) + . = ..() + if(!istype(user,/mob/living/simple_animal/hostile/eldritch/armsy)) + to_chat(user, "You try to contract your muscles but nothing happens...") + var/mob/living/simple_animal/hostile/eldritch/armsy/armsy = user + armsy.contract_next_chain_into_single_tile() + +/obj/effect/temp_visual/cleave + icon = 'icons/effects/eldritch.dmi' + icon_state = "cleave" + duration = 6 + diff --git a/code/modules/antagonists/eldritch_cult/eldritch_monster_antag.dm b/code/modules/antagonists/eldritch_cult/eldritch_monster_antag.dm new file mode 100644 index 00000000000..7520ff64809 --- /dev/null +++ b/code/modules/antagonists/eldritch_cult/eldritch_monster_antag.dm @@ -0,0 +1,43 @@ +///Tracking reasons +/datum/antagonist/heretic_monster + name = "Eldritch Horror" + roundend_category = "Heretics" + antagpanel_category = "Heretic Beast" + antag_moodlet = /datum/mood_event/heretics + job_rank = ROLE_HERETIC + antag_hud_type = ANTAG_HUD_HERETIC + antag_hud_name = "heretic_beast" + var/datum/antagonist/heretic/master + +/datum/antagonist/heretic_monster/admin_add(datum/mind/new_owner,mob/admin) + new_owner.add_antag_datum(src) + message_admins("[key_name_admin(admin)] has heresized [key_name_admin(new_owner)].") + log_admin("[key_name(admin)] has heresized [key_name(new_owner)].") + +/datum/antagonist/heretic_monster/greet() + owner.current.playsound_local(get_turf(owner.current), 'sound/ambience/antag/ecult_op.ogg', 100, FALSE, pressure_affected = FALSE)//subject to change + to_chat(owner, "You became an Eldritch Horror!") + +/datum/antagonist/heretic_monster/on_removal() + if(owner) + to_chat(owner, "Your master is no longer [master.owner.current.real_name]") + owner = null + return ..() + +/datum/antagonist/heretic_monster/proc/set_owner(datum/antagonist/heretic/_master) + master = _master + var/datum/objective/master_obj = new + master_obj.owner = src + master_obj.explanation_text = "Assist your master in any way you can!" + objectives += master_obj + owner.announce_objectives() + to_chat(owner, "Your master is [master.owner.current.real_name]") + return + +/datum/antagonist/heretic_monster/apply_innate_effects(mob/living/mob_override) + . = ..() + add_antag_hud(antag_hud_type, antag_hud_name, owner.current) + +/datum/antagonist/heretic_monster/remove_innate_effects(mob/living/mob_override) + . = ..() + remove_antag_hud(antag_hud_type, antag_hud_name, owner.current) diff --git a/code/modules/antagonists/eldritch_cult/knowledge/ash_lore.dm b/code/modules/antagonists/eldritch_cult/knowledge/ash_lore.dm new file mode 100644 index 00000000000..42ef879c6c0 --- /dev/null +++ b/code/modules/antagonists/eldritch_cult/knowledge/ash_lore.dm @@ -0,0 +1,191 @@ +/datum/eldritch_knowledge/base_ash + name = "Nightwatcher's secret" + desc = "Opens up the path of ash to you. Allows you to transmute a match with a kitchen knife or it's derivatives into an ashen blade." + gain_text = "City guard knows their watch. If you ask them at night they may tell you about the ashy lantern." + banned_knowledge = list(/datum/eldritch_knowledge/base_rust,/datum/eldritch_knowledge/base_flesh,/datum/eldritch_knowledge/final/rust_final,/datum/eldritch_knowledge/final/flesh_final) + next_knowledge = list(/datum/eldritch_knowledge/ashen_grasp) + required_atoms = list(/obj/item/kitchen/knife,/obj/item/match) + result_atoms = list(/obj/item/melee/sickly_blade/ash) + cost = 1 + route = PATH_ASH + +/datum/eldritch_knowledge/spell/ashen_shift + name = "Ashen Shift" + gain_text = "Ash is all the same, how can one man master it all?" + desc = "Short range jaunt that can help you escape from bad situations." + cost = 1 + spell_to_add = /obj/effect/proc_holder/spell/targeted/ethereal_jaunt/shift/ash + next_knowledge = list(/datum/eldritch_knowledge/ash_mark,/datum/eldritch_knowledge/essence,/datum/eldritch_knowledge/ashen_eyes) + route = PATH_ASH + +/datum/eldritch_knowledge/ashen_grasp + name = "Grasp of Ash" + gain_text = "Gates have opened, minds have flooded, I remain." + desc = "Empowers your mansus grasp to throw away enemies." + cost = 1 + next_knowledge = list(/datum/eldritch_knowledge/spell/ashen_shift) + route = PATH_ASH + +/datum/eldritch_knowledge/ashen_grasp/on_mansus_grasp(atom/target, mob/user, proximity_flag, click_parameters) + . = ..() + if(!iscarbon(target)) + return + + var/mob/living/carbon/C = target + var/datum/status_effect/eldritch/E = C.has_status_effect(/datum/status_effect/eldritch/rust) || C.has_status_effect(/datum/status_effect/eldritch/ash) || C.has_status_effect(/datum/status_effect/eldritch/flesh) + if(E) + E.on_effect() + for(var/X in user.mind.spell_list) + if(!istype(X,/obj/effect/proc_holder/spell/targeted/touch/mansus_grasp)) + continue + var/obj/effect/proc_holder/spell/targeted/touch/mansus_grasp/MG = X + MG.charge_counter = min(round(MG.charge_counter + MG.charge_max * 0.75),MG.charge_max) // refunds 75% of charge. + var/atom/throw_target = get_edge_target_turf(C, user.dir) + if(!C.anchored) + C.throw_at(throw_target, rand(4,8), 14, user) + return + +/datum/eldritch_knowledge/ashen_eyes + name = "Ashen Eyes" + gain_text = "Piercing eyes may guide me through the mundane." + desc = "Allows you to craft thermal vision amulet by transmutating eyes with a glass shard." + cost = 1 + next_knowledge = list(/datum/eldritch_knowledge/spell/ashen_shift,/datum/eldritch_knowledge/flesh_ghoul) + required_atoms = list(/obj/item/organ/eyes,/obj/item/shard) + result_atoms = list(/obj/item/clothing/neck/eldritch_amulet) + +/datum/eldritch_knowledge/ash_mark + name = "Mark of ash" + gain_text = "Spread the famine." + desc = "Your sickly blade now applies ash mark on hit. Use your mansus grasp to proc the mark. Mark of Ash causes stamina damage, and fire loss, and spreads to a nearby carbon. Damage decreases with how many times the mark has spread." + cost = 2 + next_knowledge = list(/datum/eldritch_knowledge/curse/blindness) + banned_knowledge = list(/datum/eldritch_knowledge/rust_mark,/datum/eldritch_knowledge/flesh_mark) + route = PATH_ASH + +/datum/eldritch_knowledge/ash_mark/on_eldritch_blade(target,user,proximity_flag,click_parameters) + . = ..() + if(isliving(target)) + var/mob/living/living_target = target + living_target.apply_status_effect(/datum/status_effect/eldritch/ash,5) + +/datum/eldritch_knowledge/curse/blindness + name = "Curse of blindness" + gain_text = "Blind man walks through the world, unnoticed by the masses." + desc = "Curse someone with 2 minutes of complete blindness by sacrificing a pair of eyes, a screwdriver and a pool of blood, with an object that the victim has touched with their bare hands." + cost = 1 + required_atoms = list(/obj/item/organ/eyes,/obj/item/screwdriver,/obj/effect/decal/cleanable/blood) + next_knowledge = list(/datum/eldritch_knowledge/curse/corrosion,/datum/eldritch_knowledge/ash_blade_upgrade,/datum/eldritch_knowledge/curse/paralysis) + timer = 2 MINUTES + route = PATH_ASH + +/datum/eldritch_knowledge/curse/blindness/curse(mob/living/chosen_mob) + . = ..() + chosen_mob.become_blind(MAGIC_TRAIT) + +/datum/eldritch_knowledge/curse/blindness/uncurse(mob/living/chosen_mob) + . = ..() + chosen_mob.cure_blind(MAGIC_TRAIT) + +/datum/eldritch_knowledge/spell/mad_touch + name = "Touch of Madness" + gain_text = "Take what's yours." + desc = "Short range spell that allows you to curse someone with massive sanity loss." + cost = 1 + spell_to_add = /obj/effect/proc_holder/spell/pointed/touch/mad_touch + next_knowledge = list(/datum/eldritch_knowledge/spell/blood_siphon,/datum/eldritch_knowledge/summon/ashy,/datum/eldritch_knowledge/final/ash_final) + route = PATH_ASH + +/datum/eldritch_knowledge/ash_blade_upgrade + name = "Fiery blade" + gain_text = "May the sun burn the heretics." + desc = "Your blade of choice will now add firestacks." + cost = 2 + next_knowledge = list(/datum/eldritch_knowledge/spell/mad_touch) + banned_knowledge = list(/datum/eldritch_knowledge/rust_blade_upgrade,/datum/eldritch_knowledge/flesh_blade_upgrade) + route = PATH_ASH + +/datum/eldritch_knowledge/ash_blade_upgrade/on_eldritch_blade(target,user,proximity_flag,click_parameters) + . = ..() + if(iscarbon(target)) + var/mob/living/carbon/C = target + C.adjust_fire_stacks(1) + C.IgniteMob() + +/datum/eldritch_knowledge/curse/corrosion + name = "Curse of Corrosion" + gain_text = "Cursed land, cursed man, cursed mind." + desc = "Curse someone for 2 minutes of vomiting and major organ damage. Using a wirecutter, a spill of blood, a heart, left arm and a right arm, and an item that the victim touched with their bare hands." + cost = 1 + required_atoms = list(/obj/item/wirecutters,/obj/effect/decal/cleanable/blood,/obj/item/organ/heart,/obj/item/bodypart/l_arm,/obj/item/bodypart/r_arm) + next_knowledge = list(/datum/eldritch_knowledge/curse/blindness,/datum/eldritch_knowledge/spell/area_conversion) + timer = 2 MINUTES + +/datum/eldritch_knowledge/curse/corrosion/curse(mob/living/chosen_mob) + . = ..() + chosen_mob.apply_status_effect(/datum/status_effect/corrosion_curse) + +/datum/eldritch_knowledge/curse/corrosion/uncurse(mob/living/chosen_mob) + . = ..() + chosen_mob.remove_status_effect(/datum/status_effect/corrosion_curse) + +/datum/eldritch_knowledge/curse/paralysis + name = "Curse of Paralysis" + gain_text = "Corrupt their flesh, make them bleed." + desc = "Curse someone for 5 minutes of inability to walk. Using a knife, pool of blood, left leg, right leg, a hatchet and an item that the victim touched with their bare hands. " + cost = 1 + required_atoms = list(/obj/item/kitchen/knife,/obj/effect/decal/cleanable/blood,/obj/item/bodypart/l_leg,/obj/item/bodypart/r_leg,/obj/item/hatchet) + next_knowledge = list(/datum/eldritch_knowledge/curse/blindness,/datum/eldritch_knowledge/summon/raw_prophet) + timer = 5 MINUTES + +/datum/eldritch_knowledge/curse/paralysis/curse(mob/living/chosen_mob) + . = ..() + ADD_TRAIT(chosen_mob,TRAIT_PARALYSIS_L_LEG,MAGIC_TRAIT) + ADD_TRAIT(chosen_mob,TRAIT_PARALYSIS_R_LEG,MAGIC_TRAIT) + chosen_mob.update_mobility() + +/datum/eldritch_knowledge/curse/paralysis/uncurse(mob/living/chosen_mob) + . = ..() + REMOVE_TRAIT(chosen_mob,TRAIT_PARALYSIS_L_LEG,MAGIC_TRAIT) + REMOVE_TRAIT(chosen_mob,TRAIT_PARALYSIS_R_LEG,MAGIC_TRAIT) + chosen_mob.update_mobility() + +/datum/eldritch_knowledge/spell/cleave + name = "Blood Cleave" + gain_text = "At first i didn't know these instruments of war, but the priest told me to use them." + desc = "Gives AOE spell that causes heavy bleeding and blood loss." + cost = 1 + spell_to_add = /obj/effect/proc_holder/spell/pointed/ash_cleave + next_knowledge = list(/datum/eldritch_knowledge/summon/raw_prophet,/datum/eldritch_knowledge/spell/area_conversion) + +/datum/eldritch_knowledge/final/ash_final + name = "Ashlord's rite" + gain_text = "The forgotten lords have spoken! The lord of ash have come! Fear the fire!" + desc = "Bring 3 corpses onto a transmutation rune, you will become immune to fire ,space ,cold and other enviromental hazards and become overall sturdier to all other damages. You will gain a spell that passively creates ring of fire around you as well ,as you will gain a powerful abiltiy that let's you create a wave of flames all around you." + required_atoms = list(/mob/living/carbon/human) + cost = 3 + route = PATH_ASH + var/list/trait_list = list(TRAIT_RESISTHEAT,TRAIT_NOBREATH,TRAIT_RESISTCOLD,TRAIT_RESISTHIGHPRESSURE,TRAIT_RESISTLOWPRESSURE,TRAIT_NOFIRE,TRAIT_RADIMMUNE,TRAIT_PIERCEIMMUNE,TRAIT_NODISMEMBER,TRAIT_BOMBIMMUNE) + +/datum/eldritch_knowledge/final/ash_final/on_finished_recipe(mob/living/user, list/atoms, loc) + priority_announce("$^@*$^@(#&$(@^$^@# Fear the blaze, for Ashbringer [user.real_name] has come! $^@*$^@(#&$(@^$^@#","#$^@*$^@(#&$(@^$^@#", 'sound/ai/spanomalies.ogg') + user.mind.AddSpell(new /obj/effect/proc_holder/spell/aoe_turf/fire_cascade/big) + user.mind.AddSpell(new /obj/effect/proc_holder/spell/targeted/fire_sworn) + var/mob/living/carbon/human/H = user + H.physiology.brute_mod *= 0.5 + H.physiology.burn_mod *= 0.5 + for(var/X in trait_list) + ADD_TRAIT(user,X,MAGIC_TRAIT) + return ..() + +/datum/eldritch_knowledge/final/ash_final/on_life(mob/user) + . = ..() + if(!finished) + return + var/turf/L = get_turf(user) + var/datum/gas_mixture/env = L.return_air() + for(var/turf/T in range(1,user)) + env = T.return_air() + env.temperature += 25 + T.air_update_turf() + L.air_update_turf() diff --git a/code/modules/antagonists/eldritch_cult/knowledge/flesh_lore.dm b/code/modules/antagonists/eldritch_cult/knowledge/flesh_lore.dm new file mode 100644 index 00000000000..389554ad51d --- /dev/null +++ b/code/modules/antagonists/eldritch_cult/knowledge/flesh_lore.dm @@ -0,0 +1,256 @@ +/datum/eldritch_knowledge/base_flesh + name = "Principle of Hunger" + desc = "Opens up the path of flesh to you. Allows you to transmute a pool of blood with a kitchen knife into a Flesh Blade" + gain_text = "Hundred's of us starved, but I.. I found the strength in my greed." + banned_knowledge = list(/datum/eldritch_knowledge/base_ash,/datum/eldritch_knowledge/base_rust,/datum/eldritch_knowledge/final/ash_final,/datum/eldritch_knowledge/final/rust_final) + next_knowledge = list(/datum/eldritch_knowledge/flesh_grasp) + required_atoms = list(/obj/item/kitchen/knife,/obj/effect/decal/cleanable/blood) + result_atoms = list(/obj/item/melee/sickly_blade/flesh) + cost = 1 + route = PATH_FLESH + +/datum/eldritch_knowledge/flesh_ghoul + name = "Imperfect Ritual" + desc = "Allows you to resurrect the dead as voiceless dead by sacrificing them on the transmutation rune with a poppy. Voiceless dead are mute and have 50 HP. You can only have 2 at a time." + gain_text = "I found notes.. notes of a ritual, it was unfinished and yet i still did it." + cost = 1 + required_atoms = list(/mob/living/carbon/human,/obj/item/reagent_containers/food/snacks/grown/poppy) + next_knowledge = list(/datum/eldritch_knowledge/flesh_mark,/datum/eldritch_knowledge/armor,/datum/eldritch_knowledge/ashen_eyes) + route = PATH_FLESH + var/max_amt = 2 + var/current_amt = 0 + var/list/ghouls = list() + +/datum/eldritch_knowledge/flesh_ghoul/on_finished_recipe(mob/living/user,list/atoms,loc) + var/mob/living/carbon/human/humie = locate() in atoms + if(!humie) + return + + humie?.grab_ghost() + + if(!humie.mind || !humie.client) + var/list/mob/dead/observer/candidates = pollCandidatesForMob("Do you want to play as a [humie.real_name], a voiceless dead", ROLE_HERETIC, null, ROLE_HERETIC, 50,humie) + if(!LAZYLEN(candidates)) + return + var/mob/dead/observer/C = pick(candidates) + message_admins("[key_name_admin(C)] has taken control of ([key_name_admin(humie)]) to replace an AFK player.") + humie.ghostize(0) + humie.key = C.key + + if(!check_ghouls(user) || HAS_TRAIT(humie,TRAIT_HUSK)) + return + + ADD_TRAIT(humie,TRAIT_MUTE,MAGIC_TRAIT) + log_game("[key_name_admin(humie)] has become a voiceless dead, their master is [user.real_name]") + humie.revive(full_heal = TRUE, admin_revive = TRUE) + humie.setMaxHealth(50) + humie.health = 50 // Voiceless dead are much tougher than ghouls + humie.become_husk() + humie.faction |= "heretics" + + var/datum/antagonist/heretic_monster/heretic_monster = humie.mind.add_antag_datum(/datum/antagonist/heretic_monster) + var/datum/antagonist/heretic/master = user.mind.has_antag_datum(/datum/antagonist/heretic) + heretic_monster.set_owner(master) + atoms -= humie + ghouls += humie + +/datum/eldritch_knowledge/flesh_ghoul/proc/check_ghouls(mob/living/user) + + listclearnulls(ghouls) + + for(var/mob/living/carbon/human/ghoul in ghouls) + if(ghoul.stat == DEAD) + ghoul?.mind.remove_antag_datum(/datum/antagonist/heretic_monster) + ghouls -= ghoul + current_amt-- + + if(current_amt >= max_amt) + return FALSE + return TRUE + +/datum/eldritch_knowledge/flesh_grasp + name = "Grasp of Flesh" + gain_text = "My new found desire, it drove me to do great things! The Priest said." + desc = "Empowers your mansus grasp to be able to create a single ghoul out of a dead person. Ghouls have only 25 HP and look like husks." + cost = 1 + next_knowledge = list(/datum/eldritch_knowledge/flesh_ghoul) + var/ghoul_amt = 1 + var/list/spooky_scaries + route = PATH_FLESH + +/datum/eldritch_knowledge/flesh_grasp/on_mansus_grasp(atom/target, mob/user, proximity_flag, click_parameters) + . = ..() + if(!ishuman(target)) + return + var/mob/living/carbon/human/human_target = target + var/datum/status_effect/eldritch/eldritch_effect = human_target.has_status_effect(/datum/status_effect/eldritch/rust) || human_target.has_status_effect(/datum/status_effect/eldritch/ash) || human_target.has_status_effect(/datum/status_effect/eldritch/flesh) + if(eldritch_effect) + eldritch_effect.on_effect() + if(iscarbon(target)) + var/mob/living/carbon/carbon_target = target + var/obj/item/bodypart/bodypart = pick(carbon_target.bodyparts) + var/datum/wound/brute/cut/severe/crit_wound = new + crit_wound.apply_wound(bodypart) + + human_target.grab_ghost() + + if(!human_target.mind || !human_target.client) + to_chat(user, "There is no soul connected to this body...") + return + + check_ghouls(user) + if(HAS_TRAIT(human_target, TRAIT_HUSK)) + to_chat(user, "You cannot revive a dead ghoul!") + return + + if(LAZYLEN(spooky_scaries) >= ghoul_amt) + to_chat(user, "Your patron cannot support more ghouls on this plane!") + return + + LAZYADD(spooky_scaries, human_target) + log_game("[key_name_admin(human_target)] has become a ghoul, their master is [user.real_name]") + + + human_target.revive(full_heal = TRUE, admin_revive = TRUE) + human_target.setMaxHealth(25) + human_target.health = 25 + human_target.become_husk() + human_target.faction |= "heretics" + var/datum/antagonist/heretic_monster/heretic_monster = human_target.mind.add_antag_datum(/datum/antagonist/heretic_monster) + var/datum/antagonist/heretic/master = user.mind.has_antag_datum(/datum/antagonist/heretic) + heretic_monster.set_owner(master) + return + +/datum/eldritch_knowledge/flesh_grasp/proc/check_ghouls(mob/user) + if(LAZYLEN(spooky_scaries) == 0) + return + + for(var/spook in spooky_scaries) + if(!ishuman(spook)) + LAZYREMOVE(spooky_scaries, spook) + continue + var/mob/living/carbon/human/ghoul = spook + if(ghoul.stat == DEAD) + ghoul?.mind.remove_antag_datum(/datum/antagonist/heretic_monster) + LAZYREMOVE(spooky_scaries, spook) + continue + + listclearnulls(spooky_scaries) + +/datum/eldritch_knowledge/flesh_mark + name = "Mark of flesh" + gain_text = "I saw them, the marked ones. The screams.. the silence." + desc = "Your sickly blade now applies mark of flesh status effect. To proc the mark, use your mansus grasp on the marked. Mark of flesh when procced causeds additional bleeding." + cost = 2 + next_knowledge = list(/datum/eldritch_knowledge/summon/raw_prophet) + banned_knowledge = list(/datum/eldritch_knowledge/rust_mark,/datum/eldritch_knowledge/ash_mark) + route = PATH_FLESH + +/datum/eldritch_knowledge/flesh_mark/on_eldritch_blade(target,user,proximity_flag,click_parameters) + . = ..() + if(isliving(target)) + var/mob/living/living_target = target + living_target.apply_status_effect(/datum/status_effect/eldritch/flesh) + +/datum/eldritch_knowledge/flesh_blade_upgrade + name = "Bleeding Steel" + gain_text = "It rained blood, that's when i understood the gravekeeper's advice." + desc = "Your blade will now cause additional bleeding." + cost = 2 + next_knowledge = list(/datum/eldritch_knowledge/summon/stalker) + banned_knowledge = list(/datum/eldritch_knowledge/ash_blade_upgrade,/datum/eldritch_knowledge/rust_blade_upgrade) + route = PATH_FLESH + +/datum/eldritch_knowledge/flesh_blade_upgrade/on_eldritch_blade(target,user,proximity_flag,click_parameters) + . = ..() + if(iscarbon(target)) + var/mob/living/carbon/carbon_target = target + var/obj/item/bodypart/bodypart = pick(carbon_target.bodyparts) + var/datum/wound/brute/cut/severe/crit_wound = new + crit_wound.apply_wound(bodypart) + +/datum/eldritch_knowledge/summon/raw_prophet + name = "Raw Ritual" + gain_text = "Uncanny man, walks alone in the valley, I was able to call his aid." + desc = "You can now summon a Raw Prophet using eyes, a left arm, right arm and a pool of blood. Raw prophets have increased seeing range, as well as Xray. But are very fragile and weak." + cost = 1 + required_atoms = list(/obj/item/organ/eyes,/obj/item/bodypart/l_arm,/obj/item/bodypart/r_arm,/obj/effect/decal/cleanable/blood) + mob_to_summon = /mob/living/simple_animal/hostile/eldritch/raw_prophet + next_knowledge = list(/datum/eldritch_knowledge/flesh_blade_upgrade,/datum/eldritch_knowledge/spell/cleave,/datum/eldritch_knowledge/curse/paralysis) + route = PATH_FLESH + +/datum/eldritch_knowledge/summon/stalker + name = "Lonely Ritual" + gain_text = "I was able to combine my greed and desires to summon an eldritch beast i have not seen before." + desc = "You can now summon a Stalker using a knife, a flower, a pen and a piece of paper. Stalkers can shapeshift into harmeless animals and get close to the victim." + cost = 1 + required_atoms = list(/obj/item/kitchen/knife,/obj/item/reagent_containers/food/snacks/grown/poppy,/obj/item/pen,/obj/item/paper) + mob_to_summon = /mob/living/simple_animal/hostile/eldritch/stalker + next_knowledge = list(/datum/eldritch_knowledge/summon/ashy,/datum/eldritch_knowledge/summon/rusty,/datum/eldritch_knowledge/final/flesh_final) + route = PATH_FLESH + +/datum/eldritch_knowledge/summon/ashy + name = "Ashen Ritual" + gain_text = "I combined principle of hunger with desire of destruction. The eyeful lords have noticed me." + desc = "You can now summon an Ash Man by transmutating a pile of ash , a head and a book." + cost = 1 + required_atoms = list(/obj/effect/decal/cleanable/ash,/obj/item/bodypart/head,/obj/item/book) + mob_to_summon = /mob/living/simple_animal/hostile/eldritch/ash_spirit + next_knowledge = list(/datum/eldritch_knowledge/summon/stalker,/datum/eldritch_knowledge/spell/rust_wave) + +/datum/eldritch_knowledge/summon/rusty + name = "Rusted Ritual" + gain_text = "I combined principle of hunger with desire of corruption. The rusted hills call my name." + desc = "You can now summon a Rust Walker transmutating vomit pool, a head and a book." + cost = 1 + required_atoms = list(/obj/effect/decal/cleanable/vomit,/obj/item/bodypart/head,/obj/item/book) + mob_to_summon = /mob/living/simple_animal/hostile/eldritch/rust_spirit + next_knowledge = list(/datum/eldritch_knowledge/summon/stalker,/datum/eldritch_knowledge/spell/mad_touch) + +/datum/eldritch_knowledge/spell/blood_siphon + name = "Blood Siphon" + gain_text = "Our blood is all the same after all, the owl told me." + desc = "You gain a spell that drains enemies health and restores yours." + cost = 1 + spell_to_add = /obj/effect/proc_holder/spell/targeted/touch/ash_leech + next_knowledge = list(/datum/eldritch_knowledge/spell/rust_wave,/datum/eldritch_knowledge/spell/mad_touch) + +/datum/eldritch_knowledge/final/flesh_final + name = "Priest's Final Hymn" + gain_text = "Man of this world. Hear me! For the time of the lord of arms has come!" + desc = "Bring 3 bodies onto a transmutation rune to either ascend as a terror of the night prime or you can summon a regular terror of the night." + required_atoms = list(/mob/living/carbon/human) + cost = 3 + route = PATH_FLESH + +/datum/eldritch_knowledge/final/flesh_final/on_finished_recipe(mob/living/user, list/atoms, loc) + var/alert_ = alert(user,"Do you want to ascend as the lord of the night or just summon a terror of the night?","...","Yes","No") + user.SetImmobilized(10 HOURS) // no way someone will stand 10 hours in a spot, just so he can move while the alert is still showing. + switch(alert_) + if("No") + var/mob/living/summoned = new /mob/living/simple_animal/hostile/eldritch/armsy(loc) + message_admins("[summoned.name] is being summoned by [user.real_name] in [loc]") + var/list/mob/dead/observer/candidates = pollCandidatesForMob("Do you want to play as a [summoned.real_name]", ROLE_HERETIC, null, ROLE_HERETIC, 100,summoned) + user.SetImmobilized(0) + if(LAZYLEN(candidates) == 0) + to_chat(user,"No ghost could be found...") + qdel(summoned) + return FALSE + var/mob/dead/observer/ghost_candidate = pick(candidates) + priority_announce("$^@*$^@(#&$(@^$^@# Fear the dark, for vassal of arms has ascended! Terror of the night has come! $^@*$^@(#&$(@^$^@#","#$^@*$^@(#&$(@^$^@#", 'sound/ai/spanomalies.ogg') + log_game("[key_name_admin(ghost_candidate)] has taken control of ([key_name_admin(summoned)]).") + summoned.ghostize(FALSE) + summoned.key = ghost_candidate.key + var/datum/antagonist/heretic_monster/monster = summoned.mind.has_antag_datum(/datum/antagonist/heretic_monster) + var/datum/antagonist/heretic/master = user.mind.has_antag_datum(/datum/antagonist/heretic) + monster.set_owner(master) + if("Yes") + var/mob/living/summoned = new /mob/living/simple_animal/hostile/eldritch/armsy/prime(loc,TRUE,10) + summoned.ghostize(0) + user.SetImmobilized(0) + priority_announce("$^@*$^@(#&$(@^$^@# Fear the dark, for king of arms has ascended! Lord of the night has come! $^@*$^@(#&$(@^$^@#","#$^@*$^@(#&$(@^$^@#", 'sound/ai/spanomalies.ogg') + log_game("[user.real_name] ascended as [summoned.real_name]") + var/mob/living/carbon/carbon_user = user + carbon_user.mind.transfer_to(summoned, TRUE) + carbon_user.gib() + return ..() diff --git a/code/modules/antagonists/eldritch_cult/knowledge/rust_lore.dm b/code/modules/antagonists/eldritch_cult/knowledge/rust_lore.dm new file mode 100644 index 00000000000..89838d28bf3 --- /dev/null +++ b/code/modules/antagonists/eldritch_cult/knowledge/rust_lore.dm @@ -0,0 +1,198 @@ +/datum/eldritch_knowledge/base_rust + name = "Blacksmith's Tale" + desc = "Opens up the path of rust to you. Allows you to transmute a knife with any trash item into a Rusty Blade." + gain_text = "Let me tell you a story, blacksmith said as he glazed into his rusty blade." + banned_knowledge = list(/datum/eldritch_knowledge/base_ash,/datum/eldritch_knowledge/base_flesh,/datum/eldritch_knowledge/final/ash_final,/datum/eldritch_knowledge/final/flesh_final) + next_knowledge = list(/datum/eldritch_knowledge/rust_fist) + required_atoms = list(/obj/item/kitchen/knife,/obj/item/trash) + result_atoms = list(/obj/item/melee/sickly_blade/rust) + cost = 1 + route = PATH_RUST + +/datum/eldritch_knowledge/rust_fist + name = "Grasp of rust" + desc = "Empowers your mansus grasp to deal 500 damage to non-living matter and rust any turf it touches. Destroys already rusted turfs." + gain_text = "Rust grows on the ceiling of the mansus." + cost = 1 + next_knowledge = list(/datum/eldritch_knowledge/rust_regen) + var/rust_force = 500 + var/static/list/blacklisted_turfs = typecacheof(list(/turf/closed,/turf/open/space,/turf/open/lava,/turf/open/chasm,/turf/open/floor/plating/rust)) + route = PATH_RUST + +/datum/eldritch_knowledge/rust_fist/on_mansus_grasp(atom/target, mob/user, proximity_flag, click_parameters) + . = ..() + if(ishuman(target)) + var/mob/living/carbon/human/H = target + var/datum/status_effect/eldritch/E = H.has_status_effect(/datum/status_effect/eldritch/rust) || H.has_status_effect(/datum/status_effect/eldritch/ash) || H.has_status_effect(/datum/status_effect/eldritch/flesh) + if(E) + E.on_effect() + H.adjustOrganLoss(pick(ORGAN_SLOT_BRAIN,ORGAN_SLOT_EARS,ORGAN_SLOT_EYES,ORGAN_SLOT_LIVER,ORGAN_SLOT_LUNGS,ORGAN_SLOT_STOMACH,ORGAN_SLOT_HEART),25) + target.rust_heretic_act() + return + +/datum/eldritch_knowledge/spell/area_conversion + name = "Agressive Spread" + desc = "Spreads rust to nearby turfs. Destroys already rusted walls." + gain_text = "All men wise know not to touch the bound king." + cost = 1 + spell_to_add = /obj/effect/proc_holder/spell/aoe_turf/rust_conversion + next_knowledge = list(/datum/eldritch_knowledge/rust_blade_upgrade,/datum/eldritch_knowledge/curse/corrosion,/datum/eldritch_knowledge/spell/cleave) + route = PATH_RUST + +/datum/eldritch_knowledge/rust_regen + name = "Leeching Walk" + desc = "Passively heals you when you are on rusted tiles." + gain_text = "The strength was unparallel, it was unnatural. Blacksmith was smiling." + cost = 1 + next_knowledge = list(/datum/eldritch_knowledge/rust_mark,/datum/eldritch_knowledge/armor,/datum/eldritch_knowledge/essence) + route = PATH_RUST + +/datum/eldritch_knowledge/rust_regen/on_life(mob/user) + . = ..() + var/turf/user_loc_turf = get_turf(user) + if(!istype(user_loc_turf, /turf/open/floor/plating/rust) || !isliving(user)) + return + var/mob/living/living_user = user + living_user.adjustBruteLoss(-2, FALSE) + living_user.adjustFireLoss(-2, FALSE) + living_user.adjustToxLoss(-2, FALSE) + living_user.adjustOxyLoss(-0.5, FALSE) + living_user.adjustStaminaLoss(-2) + +/datum/eldritch_knowledge/rust_mark + name = "Mark of Rust" + desc = "Your eldritch blade now applies a rust mark. Rust mark has a chance to deal between 0 to 200 damage to 75% of enemies items. To Detonate the mark use your mansus grasp on it." + gain_text = "Lords of the depths help those in dire need at a cost." + cost = 2 + next_knowledge = list(/datum/eldritch_knowledge/spell/area_conversion) + banned_knowledge = list(/datum/eldritch_knowledge/ash_mark,/datum/eldritch_knowledge/flesh_mark) + route = PATH_RUST + +/datum/eldritch_knowledge/rust_mark/on_eldritch_blade(target,user,proximity_flag,click_parameters) + . = ..() + if(isliving(target)) + var/mob/living/living_target = target + living_target.apply_status_effect(/datum/status_effect/eldritch/rust) + +/datum/eldritch_knowledge/rust_blade_upgrade + name = "Toxic blade" + gain_text = "Let the blade guide you through the flesh." + desc = "Your blade of choice will now add toxin to enemies bloodstream." + cost = 2 + next_knowledge = list(/datum/eldritch_knowledge/spell/rust_wave) + banned_knowledge = list(/datum/eldritch_knowledge/ash_blade_upgrade,/datum/eldritch_knowledge/flesh_blade_upgrade) + route = PATH_RUST + +/datum/eldritch_knowledge/rust_blade_upgrade/on_eldritch_blade(target,user,proximity_flag,click_parameters) + . = ..() + if(iscarbon(target)) + var/mob/living/carbon/carbon_target = target + carbon_target.reagents.add_reagent(/datum/reagent/eldritch, 5) + +/datum/eldritch_knowledge/spell/rust_wave + name = "Wave of Rust" + desc = "You can now send a projectile that converts an area into rust." + gain_text = "Messenger's of hope fear the rustbringer!" + cost = 1 + spell_to_add = /obj/effect/proc_holder/spell/targeted/projectile/dumbfire/rust_wave + next_knowledge = list(/datum/eldritch_knowledge/final/rust_final,/datum/eldritch_knowledge/spell/blood_siphon,/datum/eldritch_knowledge/summon/rusty) + route = PATH_RUST + +/datum/eldritch_knowledge/armor + name = "Armorer's ritual" + desc = "You can now create eldritch armor using a table and a gas mask." + gain_text = "For I am the heir to the throne of doom." + cost = 1 + next_knowledge = list(/datum/eldritch_knowledge/rust_regen,/datum/eldritch_knowledge/flesh_ghoul) + required_atoms = list(/obj/structure/table,/obj/item/clothing/mask/gas) + result_atoms = list(/obj/item/clothing/suit/hooded/cultrobes/eldritch) + +/datum/eldritch_knowledge/essence + name = "Priest's ritual" + desc = "You can now transmute a tank of water into a bottle of eldritch water." + gain_text = "This is an old recipe, i got it from an owl." + cost = 1 + next_knowledge = list(/datum/eldritch_knowledge/rust_regen,/datum/eldritch_knowledge/spell/ashen_shift) + required_atoms = list(/obj/structure/reagent_dispensers/watertank) + result_atoms = list(/obj/item/reagent_containers/glass/beaker/eldritch) + +/datum/eldritch_knowledge/final/rust_final + name = "Rustbringer's Oath" + desc = "Bring 3 corpses onto the transmutation rune. After you finish the ritual rust will now automatically spread from the rune. Your healing on rust is also tripled, while you become more resillient overall." + gain_text = "Champion of rust. Corruptor of steel. Fear the dark for Rustbringer has come!" + cost = 3 + required_atoms = list(/mob/living/carbon/human) + route = PATH_RUST + +/datum/eldritch_knowledge/final/rust_final/on_finished_recipe(mob/living/user, list/atoms, loc) + var/mob/living/carbon/human/H = user + H.physiology.brute_mod *= 0.5 + H.physiology.burn_mod *= 0.5 + priority_announce("$^@*$^@(#&$(@^$^@# Fear the decay, for Rustbringer [user.real_name] has come! $^@*$^@(#&$(@^$^@#","#$^@*$^@(#&$(@^$^@#", 'sound/ai/spanomalies.ogg') + new /datum/rust_spread(loc) + return ..() + + +/datum/eldritch_knowledge/final/rust_final/on_life(mob/user) + . = ..() + if(!finished) + return + var/mob/living/carbon/human/human_user = user + human_user.adjustBruteLoss(-3, FALSE) + human_user.adjustFireLoss(-3, FALSE) + human_user.adjustToxLoss(-3, FALSE) + human_user.adjustOxyLoss(-1, FALSE) + human_user.adjustStaminaLoss(-10) + + +/** + * #Rust spread datum + * + * Simple datum that automatically spreads rust around it + * + * Simple implementation of automatically growing entity + */ +/datum/rust_spread + var/list/edge_turfs = list() + var/list/turfs = list() + var/static/list/blacklisted_turfs = typecacheof(list(/turf/open/indestructible,/turf/closed/indestructible,/turf/open/space,/turf/open/lava,/turf/open/chasm)) + var/spread_per_tick = 6 + + +/datum/rust_spread/New(loc) + . = ..() + var/turf/turf_loc = get_turf(loc) + turf_loc.rust_heretic_act() + turfs += turf_loc + START_PROCESSING(SSprocessing,src) + + +/datum/rust_spread/Destroy(force, ...) + STOP_PROCESSING(SSprocessing,src) + return ..() + +/datum/rust_spread/process() + compile_turfs() + var/turf/T + for(var/i in 0 to spread_per_tick) + T = pick(edge_turfs) + T.rust_heretic_act() + turfs += get_turf(T) + +/** + * Compile turfs + * + * Recreates all edge_turfs as well as normal turfs. + */ +/datum/rust_spread/proc/compile_turfs() + edge_turfs = list() + for(var/X in turfs) + if(!istype(X,/turf/closed/wall/rust) && !istype(X,/turf/closed/wall/r_wall/rust) && !istype(X,/turf/open/floor/plating/rust)) + turfs -=X + continue + for(var/turf/T in range(1,X)) + if(T in turfs) + continue + if(is_type_in_typecache(T,blacklisted_turfs)) + continue + edge_turfs += T diff --git a/code/modules/mob/inventory.dm b/code/modules/mob/inventory.dm index b64cc63c672..81d7952db6d 100644 --- a/code/modules/mob/inventory.dm +++ b/code/modules/mob/inventory.dm @@ -492,3 +492,16 @@ bodyparts += BP hand_bodyparts[i] = BP ..() //Don't redraw hands until we have organs for them + +//GetAllContenst that is reasonable and not stupid +/mob/living/carbon/proc/get_all_gear() + var/list/processing_list = get_equipped_items() + held_items + listclearnulls(processing_list) // handles empty hands + var/i = 0 + while(i < length(processing_list) ) + var/atom/A = processing_list[++i] + if(SEND_SIGNAL(A, COMSIG_CONTAINS_STORAGE)) + var/list/item_stuff = list() + SEND_SIGNAL(A, COMSIG_TRY_STORAGE_RETURN_INVENTORY, item_stuff) + processing_list += item_stuff + return processing_list diff --git a/code/modules/mob/living/living.dm b/code/modules/mob/living/living.dm index 90c64d1cb0b..ec41adc4716 100644 --- a/code/modules/mob/living/living.dm +++ b/code/modules/mob/living/living.dm @@ -61,7 +61,7 @@ /mob/living/Bump(atom/A) if(..()) //we are thrown onto something return - if (buckled || now_pushing) + if(buckled || now_pushing) return if(ismob(A)) var/mob/M = A diff --git a/code/modules/mob/living/silicon/silicon.dm b/code/modules/mob/living/silicon/silicon.dm index 4055defbefb..40e533c76b0 100644 --- a/code/modules/mob/living/silicon/silicon.dm +++ b/code/modules/mob/living/silicon/silicon.dm @@ -431,3 +431,6 @@ /mob/living/silicon/handle_high_gravity(gravity) return + +/mob/living/silicon/rust_heretic_act() + adjustBruteLoss(500) diff --git a/code/modules/mob/living/simple_animal/bot/bot.dm b/code/modules/mob/living/simple_animal/bot/bot.dm index 429f678c0ec..99db7dea0af 100644 --- a/code/modules/mob/living/simple_animal/bot/bot.dm +++ b/code/modules/mob/living/simple_animal/bot/bot.dm @@ -1056,3 +1056,6 @@ Pass a positive integer as an argument to override a bot's default speed. if(I) I.icon_state = null path.Cut(1, 2) + +/mob/living/simple_animal/bot/rust_heretic_act() + adjustBruteLoss(400) diff --git a/code/modules/mob/living/simple_animal/eldritch_demons.dm b/code/modules/mob/living/simple_animal/eldritch_demons.dm new file mode 100644 index 00000000000..a5b0722d81a --- /dev/null +++ b/code/modules/mob/living/simple_animal/eldritch_demons.dm @@ -0,0 +1,302 @@ +/mob/living/simple_animal/hostile/eldritch + name = "Demon" + real_name = "Demon" + desc = "" + gender = NEUTER + mob_biotypes = NONE + speak_emote = list("screams") + response_help_continuous = "thinks better of touching" + response_help_simple = "think better of touching" + response_disarm_continuous = "flails at" + response_disarm_simple = "flail at" + response_harm_continuous = "reaps" + response_harm_simple = "tears" + speak_chance = 1 + icon = 'icons/mob/eldritch_mobs.dmi' + speed = 0 + a_intent = INTENT_HARM + stop_automated_movement = 1 + AIStatus = AI_ON + attack_sound = 'sound/weapons/punch1.ogg' + see_in_dark = 7 + lighting_alpha = LIGHTING_PLANE_ALPHA_MOSTLY_INVISIBLE + damage_coeff = list(BRUTE = 1, BURN = 1, TOX = 0, CLONE = 0, STAMINA = 0, OXY = 0) + atmos_requirements = list("min_oxy" = 0, "max_oxy" = 0, "min_tox" = 0, "max_tox" = 0, "min_co2" = 0, "max_co2" = 0, "min_n2" = 0, "max_n2" = 0) + minbodytemp = 0 + maxbodytemp = INFINITY + healable = 0 + movement_type = GROUND + pressure_resistance = 100 + del_on_death = TRUE + deathmessage = "implodes into itself" + faction = list("heretics") + ///Innate spells that are supposed to be added when a beast is created + var/list/spells_to_add + +/mob/living/simple_animal/hostile/eldritch/Initialize() + . = ..() + add_spells() + //by default + mind.add_antag_datum(/datum/antagonist/heretic_monster) + +/** + * Add_spells + * + * Goes through spells_to_add and adds each spell to the mind. + */ +/mob/living/simple_animal/hostile/eldritch/proc/add_spells() + for(var/spell in spells_to_add) + AddSpell(new spell()) + +/mob/living/simple_animal/hostile/eldritch/raw_prophet + name = "Raw Prophet" + real_name = "Raw Prophet" + desc = "Abomination made from severed limbs." + icon_state = "raw_prophet" + status_flags = CANPUSH + icon_living = "raw_prophet" + melee_damage_lower = 5 + melee_damage_upper = 10 + maxHealth = 50 + health = 50 + sight = SEE_MOBS|SEE_OBJS|SEE_TURFS + spells_to_add = list(/obj/effect/proc_holder/spell/targeted/ethereal_jaunt/shift/ash/long,/obj/effect/proc_holder/spell/targeted/telepathy/eldritch) + +/mob/living/simple_animal/hostile/eldritch/raw_prophet/Login() + . = ..() + client?.view_size.setTo(11) + +/mob/living/simple_animal/hostile/eldritch/armsy + name = "Terror of the night" + real_name = "Armsy" + desc = "Abomination made from severed limbs." + icon_state = "armsy_start" + icon_living = "armsy_start" + maxHealth = 200 + health = 200 + melee_damage_lower = 10 + melee_damage_upper = 15 + move_resist = MOVE_FORCE_OVERPOWERING+1 + spells_to_add = list(/obj/effect/proc_holder/spell/targeted/worm_contract) + ///Previous segment in the chain + var/mob/living/simple_animal/hostile/eldritch/armsy/back + ///Next segment in the chain + var/mob/living/simple_animal/hostile/eldritch/armsy/front + ///Your old location + var/oldloc + ///Allow / disallow pulling + var/allow_pulling = FALSE + ///How many arms do we have to eat to expand? + var/stacks_to_grow = 5 + ///Currently eaten arms + var/current_stacks = 0 + +//I tried Initalize but it didnt work, like at all. This proc just wouldnt fire if it was Initalize instead of New +/mob/living/simple_animal/hostile/eldritch/armsy/Initialize(mapload,spawn_more = TRUE,len = 6) + . = ..() + if(len < 3) + stack_trace("Eldritch Armsy created with invalid len ([len]). Reverting to 3.") + len = 3 //code breaks below 3, let's just not allow it. + oldloc = loc + RegisterSignal(src,COMSIG_MOVABLE_MOVED,.proc/update_chain_links) + if(!spawn_more) + return + allow_pulling = TRUE + ///next link + var/mob/living/simple_animal/hostile/eldritch/armsy/next + ///previous link + var/mob/living/simple_animal/hostile/eldritch/armsy/prev + ///current link + var/mob/living/simple_animal/hostile/eldritch/armsy/current + for(var/i in 0 to len) + prev = current + //i tried using switch, but byond is really fucky and it didnt work as intended. Im sorry + if(i == 0) + current = new type(drop_location(),FALSE) + current.icon_state = "armsy_mid" + current.icon_living = "armsy_mid" + current.front = src + current.AIStatus = AI_OFF + back = current + else if(i < len) + current = new type(drop_location(),FALSE) + prev.back = current + prev.icon_state = "armsy_mid" + prev.icon_living = "armsy_mid" + prev.front = next + prev.AIStatus = AI_OFF + else + prev.icon_state = "armsy_end" + prev.icon_living = "armsy_end" + prev.front = next + prev.AIStatus = AI_OFF + next = prev + +/mob/living/simple_animal/hostile/eldritch/armsy/can_be_pulled() + return FALSE + +///Updates chain links to force move onto a single tile +/mob/living/simple_animal/hostile/eldritch/armsy/proc/contract_next_chain_into_single_tile() + if(back) + back.forceMove(loc) + back.contract_next_chain_into_single_tile() + return + +///Updates the next mob in the chain to move to our last location, fixed the worm if somehow broken. +/mob/living/simple_animal/hostile/eldritch/armsy/proc/update_chain_links() + gib_trail() + if(back && back.loc != oldloc) + back.Move(oldloc) + // self fixing properties if somehow broken + if(front && loc != front.oldloc) + forceMove(front.oldloc) + oldloc = loc + +/mob/living/simple_animal/hostile/eldritch/armsy/proc/gib_trail() + if(front) // head makes gibs + return + var/chosen_decal = pick(typesof(/obj/effect/decal/cleanable/blood/tracks)) + var/obj/effect/decal/cleanable/blood/gibs/decal = new chosen_decal(drop_location()) + decal.setDir(dir) + +/mob/living/simple_animal/hostile/eldritch/armsy/Destroy() + if(front) + front.icon_state = "armsy_end" + front.icon_living = "armsy_end" + front.back = null + if(back) + QDEL_NULL(back) // chain destruction baby + return ..() + + +/mob/living/simple_animal/hostile/eldritch/armsy/proc/heal() + if(health == maxHealth) + if(back) + back.heal() + return + else + current_stacks++ + if(current_stacks >= stacks_to_grow) + var/mob/living/simple_animal/hostile/eldritch/armsy/prev = new type(drop_location(),spawn_more = FALSE) + icon_state = "armsy_mid" + icon_living = "armsy_mid" + back = prev + prev.icon_state = "armsy_end" + prev.icon_living = "armsy_end" + prev.front = src + prev.AIStatus = AI_OFF + current_stacks = 0 + + adjustBruteLoss(-maxHealth * 0.5, FALSE) + adjustFireLoss(-maxHealth * 0.5 ,FALSE) + +/mob/living/simple_animal/hostile/eldritch/armsy/AttackingTarget() + if(istype(target,/obj/item/bodypart/r_arm) || istype(target,/obj/item/bodypart/l_arm)) + qdel(target) + heal() + return + if(target == back || target == front) + return + if(back) + back.target = target + back.AttackingTarget() + if(!Adjacent(target)) + return + do_attack_animation(target) + //have fun + if(istype(target,/turf/closed/wall)) + var/turf/closed/wall = target + wall.ScrapeAway() + + if(iscarbon(target)) + var/mob/living/carbon/C = target + if(HAS_TRAIT(C, TRAIT_NODISMEMBER)) + return + var/list/parts = list() + for(var/X in C.bodyparts) + var/obj/item/bodypart/bodypart = X + if(bodypart.body_part != HEAD && bodypart.body_part != CHEST && bodypart.body_part != LEG_LEFT && bodypart.body_part != LEG_RIGHT) + if(bodypart.dismemberable) + parts += bodypart + if(length(parts) && prob(10)) + var/obj/item/bodypart/bodypart = pick(parts) + bodypart.dismember() + + return ..() + +/mob/living/simple_animal/hostile/eldritch/armsy/prime + name = "Lord of the Night" + real_name = "Master of Decay" + maxHealth = 400 + health = 400 + melee_damage_lower = 20 + melee_damage_upper = 25 + +/mob/living/simple_animal/hostile/eldritch/armsy/prime/Initialize(mapload,spawn_more = TRUE,len = 9) + . = ..() + var/matrix/matrix_transformation = matrix() + matrix_transformation.Scale(1.4,1.4) + transform = matrix_transformation + +/mob/living/simple_animal/hostile/eldritch/rust_spirit + name = "Rust Walker" + real_name = "Rusty" + desc = "Incomprehensible abomination actively seeping life out of it's surrounding." + icon_state = "rust_walker_s" + status_flags = CANPUSH + icon_living = "rust_walker_s" + maxHealth = 75 + health = 75 + melee_damage_lower = 15 + melee_damage_upper = 20 + sight = SEE_TURFS + spells_to_add = list(/obj/effect/proc_holder/spell/aoe_turf/rust_conversion/small,/obj/effect/proc_holder/spell/targeted/projectile/dumbfire/rust_wave/short) + +/mob/living/simple_animal/hostile/eldritch/rust_spirit/setDir(newdir) + . = ..() + if(newdir == NORTH) + icon_state = "rust_walker_n" + else if(newdir == SOUTH) + icon_state = "rust_walker_s" + update_icon() + +/mob/living/simple_animal/hostile/eldritch/rust_spirit/Moved() + . = ..() + playsound(src, 'sound/effects/footstep/rustystep1.ogg', 100, TRUE) + +/mob/living/simple_animal/hostile/eldritch/rust_spirit/Life() + if(stat == DEAD) + return ..() + var/turf/T = get_turf(src) + if(istype(T,/turf/open/floor/plating/rust)) + adjustBruteLoss(-3, FALSE) + adjustFireLoss(-3, FALSE) + return ..() + +/mob/living/simple_animal/hostile/eldritch/ash_spirit + name = "Ash Man" + real_name = "Ashy" + desc = "Incomprehensible abomination actively seeping life out of it's surrounding." + icon_state = "ash_walker" + status_flags = CANPUSH + icon_living = "ash_walker" + maxHealth = 75 + health = 75 + melee_damage_lower = 15 + melee_damage_upper = 20 + sight = SEE_TURFS + spells_to_add = list(/obj/effect/proc_holder/spell/targeted/ethereal_jaunt/shift/ash,/obj/effect/proc_holder/spell/pointed/ash_cleave/long,/obj/effect/proc_holder/spell/aoe_turf/fire_cascade) + +/mob/living/simple_animal/hostile/eldritch/stalker + name = "Flesh Stalker" + real_name = "Flesh Stalker" + desc = "Abomination made from severed limbs." + icon_state = "stalker" + status_flags = CANPUSH + icon_living = "stalker" + maxHealth = 150 + health = 150 + melee_damage_lower = 15 + melee_damage_upper = 20 + sight = SEE_MOBS + spells_to_add = list(/obj/effect/proc_holder/spell/targeted/ethereal_jaunt/shift/ash,/obj/effect/proc_holder/spell/targeted/shapeshift/eldritch,/obj/effect/proc_holder/spell/targeted/emplosion/eldritch) diff --git a/code/modules/reagents/chemistry/reagents/other_reagents.dm b/code/modules/reagents/chemistry/reagents/other_reagents.dm index 1f2dc2b62b9..022ca8d8e95 100644 --- a/code/modules/reagents/chemistry/reagents/other_reagents.dm +++ b/code/modules/reagents/chemistry/reagents/other_reagents.dm @@ -2318,3 +2318,29 @@ wounded_part.heal_damage(0.25, 0.25) M.adjustStaminaLoss(-0.25*REM) // the more wounds, the more stamina regen ..() + +/datum/reagent/eldritch + name = "Eldritch Essence" + description = "Strange liquid that defies the laws of physics" + taste_description = "Ag'hsj'saje'sh" + color = "#1f8016" + +/datum/reagent/eldritch/on_mob_life(mob/living/carbon/M) + if(IS_HERETIC(M)) + M.drowsyness = max(M.drowsyness-5, 0) + M.AdjustAllImmobility(-40, FALSE) + M.adjustStaminaLoss(-10, FALSE) + M.adjustToxLoss(-2, FALSE) + M.adjustOxyLoss(-2, FALSE) + M.adjustBruteLoss(-2, FALSE) + M.adjustFireLoss(-2, FALSE) + if(ishuman(M) && M.blood_volume < BLOOD_VOLUME_NORMAL) + M.blood_volume += 3 + else + M.adjustOrganLoss(ORGAN_SLOT_BRAIN, 3, 150) + M.adjustToxLoss(2, FALSE) + M.adjustFireLoss(2, FALSE) + M.adjustOxyLoss(2, FALSE) + M.adjustBruteLoss(2, FALSE) + holder.remove_reagent(type, 1) + return TRUE diff --git a/code/modules/spells/spell_types/ethereal_jaunt.dm b/code/modules/spells/spell_types/ethereal_jaunt.dm index 6987e115156..5d36d677b71 100644 --- a/code/modules/spells/spell_types/ethereal_jaunt.dm +++ b/code/modules/spells/spell_types/ethereal_jaunt.dm @@ -18,7 +18,7 @@ action_icon_state = "jaunt" /obj/effect/proc_holder/spell/targeted/ethereal_jaunt/cast(list/targets,mob/user = usr) //magnets, so mostly hardcoded - playsound(get_turf(user), 'sound/magic/ethereal_enter.ogg', 50, TRUE, -1) + play_sound("enter",user) for(var/mob/living/target in targets) INVOKE_ASYNC(src, .proc/do_jaunt, target) @@ -42,7 +42,7 @@ jaunt_steam(mobloc) target.mobility_flags &= ~MOBILITY_MOVE holder.reappearing = 1 - playsound(get_turf(target), 'sound/magic/ethereal_exit.ogg', 50, TRUE, -1) + play_sound("exit",target) sleep(25 - jaunt_in_time) new jaunt_in_type(mobloc, holder.dir) target.setDir(holder.dir) @@ -62,6 +62,13 @@ steam.set_up(10, 0, mobloc) steam.start() +/obj/effect/proc_holder/spell/targeted/ethereal_jaunt/proc/play_sound(type,mob/living/target) + switch(type) + if("enter") + playsound(get_turf(target), 'sound/magic/ethereal_enter.ogg', 50, TRUE, -1) + if("exit") + playsound(get_turf(target), 'sound/magic/ethereal_exit.ogg', 50, TRUE, -1) + /obj/effect/dummy/phased_mob/spell_jaunt name = "water" icon = 'icons/effects/effects.dmi' diff --git a/icons/effects/96x96.dmi b/icons/effects/96x96.dmi index bebd7179983..d69458decfb 100644 Binary files a/icons/effects/96x96.dmi and b/icons/effects/96x96.dmi differ diff --git a/icons/effects/effects.dmi b/icons/effects/effects.dmi index 46a79a666db..b751323e2d3 100644 Binary files a/icons/effects/effects.dmi and b/icons/effects/effects.dmi differ diff --git a/icons/effects/eldritch.dmi b/icons/effects/eldritch.dmi new file mode 100644 index 00000000000..d4b08ef874e Binary files /dev/null and b/icons/effects/eldritch.dmi differ diff --git a/icons/mob/actions/actions_ecult.dmi b/icons/mob/actions/actions_ecult.dmi new file mode 100644 index 00000000000..f89253531df Binary files /dev/null and b/icons/mob/actions/actions_ecult.dmi differ diff --git a/icons/mob/actions/backgrounds.dmi b/icons/mob/actions/backgrounds.dmi index 64722511694..7628c646b9f 100644 Binary files a/icons/mob/actions/backgrounds.dmi and b/icons/mob/actions/backgrounds.dmi differ diff --git a/icons/mob/clothing/head.dmi b/icons/mob/clothing/head.dmi index 04eff038e70..77fa73c2262 100644 Binary files a/icons/mob/clothing/head.dmi and b/icons/mob/clothing/head.dmi differ diff --git a/icons/mob/clothing/neck.dmi b/icons/mob/clothing/neck.dmi index f13673a5e50..7149d0d8e62 100644 Binary files a/icons/mob/clothing/neck.dmi and b/icons/mob/clothing/neck.dmi differ diff --git a/icons/mob/clothing/suit.dmi b/icons/mob/clothing/suit.dmi index 896be1e41c0..c84472aadc3 100644 Binary files a/icons/mob/clothing/suit.dmi and b/icons/mob/clothing/suit.dmi differ diff --git a/icons/mob/eldritch_mobs.dmi b/icons/mob/eldritch_mobs.dmi new file mode 100644 index 00000000000..8a16d53f885 Binary files /dev/null and b/icons/mob/eldritch_mobs.dmi differ diff --git a/icons/mob/hud.dmi b/icons/mob/hud.dmi index 98169efa8c4..1c43f5141e6 100644 Binary files a/icons/mob/hud.dmi and b/icons/mob/hud.dmi differ diff --git a/icons/mob/inhands/weapons/swords_lefthand.dmi b/icons/mob/inhands/weapons/swords_lefthand.dmi index b29a8b04f7d..0e52d6a57b7 100644 Binary files a/icons/mob/inhands/weapons/swords_lefthand.dmi and b/icons/mob/inhands/weapons/swords_lefthand.dmi differ diff --git a/icons/mob/inhands/weapons/swords_righthand.dmi b/icons/mob/inhands/weapons/swords_righthand.dmi index ee52a1156cd..cd38fb04cf2 100644 Binary files a/icons/mob/inhands/weapons/swords_righthand.dmi and b/icons/mob/inhands/weapons/swords_righthand.dmi differ diff --git a/icons/mob/mob.dmi b/icons/mob/mob.dmi index 64d0cc4ef8c..5d69e466a7f 100644 Binary files a/icons/mob/mob.dmi and b/icons/mob/mob.dmi differ diff --git a/icons/obj/clothing/hats.dmi b/icons/obj/clothing/hats.dmi index 8e6497ee786..501d97fe614 100644 Binary files a/icons/obj/clothing/hats.dmi and b/icons/obj/clothing/hats.dmi differ diff --git a/icons/obj/clothing/suits.dmi b/icons/obj/clothing/suits.dmi index b240bf5945c..fda468aa1b7 100644 Binary files a/icons/obj/clothing/suits.dmi and b/icons/obj/clothing/suits.dmi differ diff --git a/icons/obj/eldritch.dmi b/icons/obj/eldritch.dmi new file mode 100644 index 00000000000..d7f7a3f52d1 Binary files /dev/null and b/icons/obj/eldritch.dmi differ diff --git a/icons/obj/projectiles.dmi b/icons/obj/projectiles.dmi index 37f1a216c16..dc0ec7de632 100644 Binary files a/icons/obj/projectiles.dmi and b/icons/obj/projectiles.dmi differ diff --git a/icons/turf/floors.dmi b/icons/turf/floors.dmi index a9f7abbf1b3..d38d1f24a22 100644 Binary files a/icons/turf/floors.dmi and b/icons/turf/floors.dmi differ diff --git a/sound/ambience/antag/ecult_op.ogg b/sound/ambience/antag/ecult_op.ogg new file mode 100644 index 00000000000..9944e833a66 Binary files /dev/null and b/sound/ambience/antag/ecult_op.ogg differ diff --git a/sound/effects/footstep/rustystep1.ogg b/sound/effects/footstep/rustystep1.ogg new file mode 100644 index 00000000000..bf90d52779c Binary files /dev/null and b/sound/effects/footstep/rustystep1.ogg differ diff --git a/tgstation.dme b/tgstation.dme index 7f32fde99a3..4a3442336b2 100644 --- a/tgstation.dme +++ b/tgstation.dme @@ -692,6 +692,7 @@ #include "code\game\gamemodes\dynamic\dynamic_rulesets_latejoin.dm" #include "code\game\gamemodes\dynamic\dynamic_rulesets_midround.dm" #include "code\game\gamemodes\dynamic\dynamic_rulesets_roundstart.dm" +#include "code\game\gamemodes\eldritch_cult\eldritch_cult.dm" #include "code\game\gamemodes\extended\extended.dm" #include "code\game\gamemodes\gang\gang.dm" #include "code\game\gamemodes\gang\gang_handler.dm" @@ -1468,6 +1469,16 @@ #include "code\modules\antagonists\disease\disease_disease.dm" #include "code\modules\antagonists\disease\disease_event.dm" #include "code\modules\antagonists\disease\disease_mob.dm" +#include "code\modules\antagonists\eldritch_cult\eldritch_antag.dm" +#include "code\modules\antagonists\eldritch_cult\eldritch_book.dm" +#include "code\modules\antagonists\eldritch_cult\eldritch_effects.dm" +#include "code\modules\antagonists\eldritch_cult\eldritch_items.dm" +#include "code\modules\antagonists\eldritch_cult\eldritch_knowledge.dm" +#include "code\modules\antagonists\eldritch_cult\eldritch_magic.dm" +#include "code\modules\antagonists\eldritch_cult\eldritch_monster_antag.dm" +#include "code\modules\antagonists\eldritch_cult\knowledge\ash_lore.dm" +#include "code\modules\antagonists\eldritch_cult\knowledge\flesh_lore.dm" +#include "code\modules\antagonists\eldritch_cult\knowledge\rust_lore.dm" #include "code\modules\antagonists\ert\ert.dm" #include "code\modules\antagonists\fugitive\fugitive.dm" #include "code\modules\antagonists\fugitive\fugitive_outfits.dm" @@ -2305,6 +2316,7 @@ #include "code\modules\mob\living\simple_animal\constructs.dm" #include "code\modules\mob\living\simple_animal\corpse.dm" #include "code\modules\mob\living\simple_animal\damage_procs.dm" +#include "code\modules\mob\living\simple_animal\eldritch_demons.dm" #include "code\modules\mob\living\simple_animal\parrot.dm" #include "code\modules\mob\living\simple_animal\shade.dm" #include "code\modules\mob\living\simple_animal\simple_animal.dm" diff --git a/tgui/packages/tgui/interfaces/ForbiddenLore.js b/tgui/packages/tgui/interfaces/ForbiddenLore.js new file mode 100644 index 00000000000..3dc3a74f633 --- /dev/null +++ b/tgui/packages/tgui/interfaces/ForbiddenLore.js @@ -0,0 +1,62 @@ +import { useBackend } from '../backend'; +import { flow } from 'common/fp'; +import { sortBy } from 'common/collections'; +import { map } from 'common/collections'; +import { Button, Section, Box, Tabs } from '../components'; +import { Window } from '../layouts'; + +export const ForbiddenLore = (props, context) => { + const { act, data } = useBackend(context); + // Extract `health` and `color` variables from the `data` object. + const { + charges, + to_know = {}, + } = data; + const SortByPath = flow([ + sortBy(to_know => to_know.state !== "Research", + to_know => to_know.path === "Side"), + ])(data.to_know || []); + + return ( +
/gi,"\n").replace(/<\/?[a-z0-9-_]+[^>]*>/gi,"").replace(/&(nbsp|amp|quot|lt|gt|apos);/g,(function(e,n){return t[n]})).replace(/?([0-9]+);/gi,(function(e,t){var n=parseInt(t,10);return String.fromCharCode(n)})).replace(/?([0-9a-f]+);/gi,(function(e,t){var n=parseInt(t,16);return String.fromCharCode(n)}))};t.buildQueryString=function(e){return Object.keys(e).map((function(t){return encodeURIComponent(t)+"="+encodeURIComponent(e[t])})).join("&")}},function(e,t,n){"use strict";var o=n(51),r=n(61),a=n(16),i=n(12),c=n(67),l=[].push,d=function(e){var t=1==e,n=2==e,d=3==e,u=4==e,s=6==e,p=5==e||s;return function(m,f,h,C){for(var N,b,g=a(m),v=r(g),V=o(f,h,3),y=i(v.length),_=0,k=C||c,x=t?k(m,y):n?k(m,0):undefined;y>_;_++)if((p||_ in v)&&(b=V(N=v[_],_,g),e))if(t)x[_]=b;else if(b)switch(e){case 3:return!0;case 5:return N;case 6:return _;case 2:l.call(x,N)}else if(u)return!1;return s?-1:d||u?u:x}};e.exports={forEach:d(0),map:d(1),filter:d(2),some:d(3),every:d(4),find:d(5),findIndex:d(6)}},function(e,t,n){"use strict";var o=n(9),r=n(74),a=n(49),i=n(27),c=n(35),l=n(18),d=n(125),u=Object.getOwnPropertyDescriptor;t.f=o?u:function(e,t){if(e=i(e),t=c(t,!0),d)try{return u(e,t)}catch(n){}if(l(e,t))return a(!r.f.call(e,t),e[t])}},function(e,t,n){"use strict";e.exports=function(e){if(e==undefined)throw TypeError("Can't call method on "+e);return e}},function(e,t,n){"use strict";var o=n(7),r=n(31),a=n(18),i=n(91),c=n(92),l=n(36),d=l.get,u=l.enforce,s=String(String).split("String");(e.exports=function(e,t,n,c){var l=!!c&&!!c.unsafe,d=!!c&&!!c.enumerable,p=!!c&&!!c.noTargetGet;"function"==typeof n&&("string"!=typeof t||a(n,"name")||r(n,"name",t),u(n).source=s.join("string"==typeof t?t:"")),e!==o?(l?!p&&e[t]&&(d=!0):delete e[t],d?e[t]=n:r(e,t,n)):d?e[t]=n:i(t,n)})(Function.prototype,"toString",(function(){return"function"==typeof this&&d(this).source||c(this)}))},function(e,t,n){"use strict";var o=n(9),r=n(5),a=n(18),i=Object.defineProperty,c={},l=function(e){throw e};e.exports=function(e,t){if(a(c,e))return c[e];t||(t={});var n=[][e],d=!!a(t,"ACCESSORS")&&t.ACCESSORS,u=a(t,0)?t[0]:l,s=a(t,1)?t[1]:undefined;return c[e]=!!n&&!r((function(){if(d&&!o)return!0;var e={length:-1};d?i(e,1,{enumerable:!0,get:l}):e[1]=1,n.call(e,u,s)}))}},function(e,t,n){"use strict";function o(e,t,n,o,r,a,i){try{var c=e[a](i),l=c.value}catch(d){return void n(d)}c.done?t(l):Promise.resolve(l).then(o,r)}t.__esModule=!0,t.winset=t.winget=t.runCommand=t.callByondAsync=t.callByond=t.IS_IE8=void 0;var r=window.Byond,a=function(){var e=navigator.userAgent.match(/Trident\/(\d+).+?;/i);if(!e)return null;var t=e[1];return t?parseInt(t,10):null}(),i=null!==a&&a<=6;t.IS_IE8=i;var c=function(e,t){void 0===t&&(t={}),r.call(e,t)};t.callByond=c;var l=function(e,t){void 0===t&&(t={}),window.__callbacks__=window.__callbacks__||[];var n=window.__callbacks__.length,o=new Promise((function(e){window.__callbacks__.push(e)}));return r.call(e,Object.assign(Object.assign({},t),{},{callback:"__callbacks__["+n+"]"})),o};t.callByondAsync=l;t.runCommand=function(e){return c("winset",{command:e})};var d=function(){var e,t=(e=regeneratorRuntime.mark((function n(e,t){var o;return regeneratorRuntime.wrap((function(n){for(;;)switch(n.prev=n.next){case 0:return n.next=2,l("winget",{id:e,property:t});case 2:return o=n.sent,n.abrupt("return",o[t]);case 4:case"end":return n.stop()}}),n)})),function(){var t=this,n=arguments;return new Promise((function(r,a){var i=e.apply(t,n);function c(e){o(i,r,a,c,l,"next",e)}function l(e){o(i,r,a,c,l,"throw",e)}c(undefined)}))});return function(e,n){return t.apply(this,arguments)}}();t.winget=d;t.winset=function(e,t,n){var o;return c("winset",((o={})[e+"."+t]=n,o))}},function(e,t,n){"use strict";var o=n(61),r=n(23);e.exports=function(e){return o(r(e))}},function(e,t,n){"use strict";var o=n(129),r=n(18),a=n(135),i=n(14).f;e.exports=function(e){var t=o.Symbol||(o.Symbol={});r(t,e)||i(t,e,{value:a.f(e)})}},function(e,t,n){"use strict";var o=n(23),r=/"/g;e.exports=function(e,t,n,a){var i=String(o(e)),c="<"+t;return""!==n&&(c+=" "+n+'="'+String(a).replace(r,""")+'"'),c+">"+i+""+t+">"}},function(e,t,n){"use strict";var o=n(5);e.exports=function(e){return o((function(){var t=""[e]('"');return t!==t.toLowerCase()||t.split('"').length>3}))}},function(e,t,n){"use strict";var o=n(9),r=n(14),a=n(49);e.exports=o?function(e,t,n){return r.f(e,t,a(1,n))}:function(e,t,n){return e[t]=n,e}},function(e,t,n){"use strict";var o=Math.ceil,r=Math.floor;e.exports=function(e){return isNaN(e=+e)?0:(e>0?r:o)(e)}},function(e,t,n){"use strict";e.exports=function(e){if("function"!=typeof e)throw TypeError(String(e)+" is not a function");return e}},function(e,t,n){"use strict";var o={}.toString;e.exports=function(e){return o.call(e).slice(8,-1)}},function(e,t,n){"use strict";var o=n(8);e.exports=function(e,t){if(!o(e))return e;var n,r;if(t&&"function"==typeof(n=e.toString)&&!o(r=n.call(e)))return r;if("function"==typeof(n=e.valueOf)&&!o(r=n.call(e)))return r;if(!t&&"function"==typeof(n=e.toString)&&!o(r=n.call(e)))return r;throw TypeError("Can't convert object to primitive value")}},function(e,t,n){"use strict";var o,r,a,i=n(127),c=n(7),l=n(8),d=n(31),u=n(18),s=n(75),p=n(63),m=c.WeakMap;if(i){var f=new m,h=f.get,C=f.has,N=f.set;o=function(e,t){return N.call(f,e,t),t},r=function(e){return h.call(f,e)||{}},a=function(e){return C.call(f,e)}}else{var b=s("state");p[b]=!0,o=function(e,t){return d(e,b,t),t},r=function(e){return u(e,b)?e[b]:{}},a=function(e){return u(e,b)}}e.exports={set:o,get:r,has:a,enforce:function(e){return a(e)?r(e):o(e,{})},getterFor:function(e){return function(t){var n;if(!l(t)||(n=r(t)).type!==e)throw TypeError("Incompatible receiver, "+e+" required");return n}}}},function(e,t,n){"use strict";var o=n(18),r=n(16),a=n(75),i=n(104),c=a("IE_PROTO"),l=Object.prototype;e.exports=i?Object.getPrototypeOf:function(e){return e=r(e),o(e,c)?e[c]:"function"==typeof e.constructor&&e instanceof e.constructor?e.constructor.prototype:e instanceof Object?l:null}},function(e,t,n){"use strict";var o=n(129),r=n(7),a=function(e){return"function"==typeof e?e:undefined};e.exports=function(e,t){return arguments.length<2?a(o[e])||a(r[e]):o[e]&&o[e][t]||r[e]&&r[e][t]}},function(e,t,n){"use strict";t.__esModule=!0,t.getGasColor=t.getGasLabel=t.RADIO_CHANNELS=t.CSS_COLORS=t.COLORS=t.UI_CLOSE=t.UI_DISABLED=t.UI_UPDATE=t.UI_INTERACTIVE=void 0;t.UI_INTERACTIVE=2;t.UI_UPDATE=1;t.UI_DISABLED=0;t.UI_CLOSE=-1;t.COLORS={department:{captain:"#c06616",security:"#e74c3c",medbay:"#3498db",science:"#9b59b6",engineering:"#f1c40f",cargo:"#f39c12",centcom:"#00c100",other:"#c38312"},damageType:{oxy:"#3498db",toxin:"#2ecc71",burn:"#e67e22",brute:"#e74c3c"}};t.CSS_COLORS=["black","white","red","orange","yellow","olive","green","teal","blue","violet","purple","pink","brown","grey","good","average","bad","label"];t.RADIO_CHANNELS=[{name:"Syndicate",freq:1213,color:"#a52a2a"},{name:"Red Team",freq:1215,color:"#ff4444"},{name:"Blue Team",freq:1217,color:"#3434fd"},{name:"CentCom",freq:1337,color:"#2681a5"},{name:"Supply",freq:1347,color:"#b88646"},{name:"Service",freq:1349,color:"#6ca729"},{name:"Science",freq:1351,color:"#c68cfa"},{name:"Command",freq:1353,color:"#5177ff"},{name:"Medical",freq:1355,color:"#57b8f0"},{name:"Engineering",freq:1357,color:"#f37746"},{name:"Security",freq:1359,color:"#dd3535"},{name:"AI Private",freq:1447,color:"#d65d95"},{name:"Common",freq:1459,color:"#1ecc43"}];var o=[{id:"o2",name:"Oxygen",label:"O\u2082",color:"blue"},{id:"n2",name:"Nitrogen",label:"N\u2082",color:"red"},{id:"co2",name:"Carbon Dioxide",label:"CO\u2082",color:"grey"},{id:"plasma",name:"Plasma",label:"Plasma",color:"pink"},{id:"water_vapor",name:"Water Vapor",label:"H\u2082O",color:"grey"},{id:"nob",name:"Hyper-noblium",label:"Hyper-nob",color:"teal"},{id:"n2o",name:"Nitrous Oxide",label:"N\u2082O",color:"red"},{id:"no2",name:"Nitryl",label:"NO\u2082",color:"brown"},{id:"tritium",name:"Tritium",label:"Tritium",color:"green"},{id:"bz",name:"BZ",label:"BZ",color:"purple"},{id:"stim",name:"Stimulum",label:"Stimulum",color:"purple"},{id:"pluox",name:"Pluoxium",label:"Pluoxium",color:"blue"},{id:"miasma",name:"Miasma",label:"Miasma",color:"olive"},{id:"hydrogen",name:"Hydrogen",label:"H\u2082",color:"white"}];t.getGasLabel=function(e,t){var n=String(e).toLowerCase(),r=o.find((function(e){return e.id===n||e.name.toLowerCase()===n}));return r&&r.label||t||e};t.getGasColor=function(e){var t=String(e).toLowerCase(),n=o.find((function(e){return e.id===t||e.name.toLowerCase()===t}));return n&&n.color}},function(e,t,n){"use strict";e.exports=!1},function(e,t,n){"use strict";var o=n(5);e.exports=function(e,t){var n=[][e];return!!n&&o((function(){n.call(null,t||function(){throw 1},1)}))}},function(e,t,n){"use strict";var o=n(4),r=n(7),a=n(9),i=n(117),c=n(11),l=n(80),d=n(58),u=n(49),s=n(31),p=n(12),m=n(143),f=n(158),h=n(35),C=n(18),N=n(77),b=n(8),g=n(45),v=n(53),V=n(50).f,y=n(159),_=n(21).forEach,k=n(57),x=n(14),w=n(22),B=n(36),L=n(82),S=B.get,I=B.set,T=x.f,A=w.f,E=Math.round,P=r.RangeError,M=l.ArrayBuffer,R=l.DataView,O=c.NATIVE_ARRAY_BUFFER_VIEWS,j=c.TYPED_ARRAY_TAG,D=c.TypedArray,F=c.TypedArrayPrototype,W=c.aTypedArrayConstructor,z=c.isTypedArray,G=function(e,t){for(var n=0,o=t.length,r=new(W(e))(o);o>n;)r[n]=t[n++];return r},H=function(e,t){T(e,t,{get:function(){return S(this)[t]}})},U=function(e){var t;return e instanceof M||"ArrayBuffer"==(t=N(e))||"SharedArrayBuffer"==t},K=function(e,t){return z(e)&&"symbol"!=typeof t&&t in e&&String(+t)==String(t)},q=function(e,t){return K(e,t=h(t,!0))?u(2,e[t]):A(e,t)},Y=function(e,t,n){return!(K(e,t=h(t,!0))&&b(n)&&C(n,"value"))||C(n,"get")||C(n,"set")||n.configurable||C(n,"writable")&&!n.writable||C(n,"enumerable")&&!n.enumerable?T(e,t,n):(e[t]=n.value,e)};a?(O||(w.f=q,x.f=Y,H(F,"buffer"),H(F,"byteOffset"),H(F,"byteLength"),H(F,"length")),o({target:"Object",stat:!0,forced:!O},{getOwnPropertyDescriptor:q,defineProperty:Y}),e.exports=function(e,t,n){var a=e.match(/\d+$/)[0]/8,c=e+(n?"Clamped":"")+"Array",l="get"+e,u="set"+e,h=r[c],C=h,N=C&&C.prototype,x={},w=function(e,t){T(e,t,{get:function(){return function(e,t){var n=S(e);return n.view[l](t*a+n.byteOffset,!0)}(this,t)},set:function(e){return function(e,t,o){var r=S(e);n&&(o=(o=E(o))<0?0:o>255?255:255&o),r.view[u](t*a+r.byteOffset,o,!0)}(this,t,e)},enumerable:!0})};O?i&&(C=t((function(e,t,n,o){return d(e,C,c),L(b(t)?U(t)?o!==undefined?new h(t,f(n,a),o):n!==undefined?new h(t,f(n,a)):new h(t):z(t)?G(C,t):y.call(C,t):new h(m(t)),e,C)})),v&&v(C,D),_(V(h),(function(e){e in C||s(C,e,h[e])})),C.prototype=N):(C=t((function(e,t,n,o){d(e,C,c);var r,i,l,u=0,s=0;if(b(t)){if(!U(t))return z(t)?G(C,t):y.call(C,t);r=t,s=f(n,a);var h=t.byteLength;if(o===undefined){if(h%a)throw P("Wrong length");if((i=h-s)<0)throw P("Wrong length")}else if((i=p(o)*a)+s>h)throw P("Wrong length");l=i/a}else l=m(t),r=new M(i=l*a);for(I(e,{buffer:r,byteOffset:s,byteLength:i,length:l,view:new R(r)});u
/gi,"\n").replace(/<\/?[a-z0-9-_]+[^>]*>/gi,"").replace(/&(nbsp|amp|quot|lt|gt|apos);/g,(function(e,n){return t[n]})).replace(/?([0-9]+);/gi,(function(e,t){var n=parseInt(t,10);return String.fromCharCode(n)})).replace(/?([0-9a-f]+);/gi,(function(e,t){var n=parseInt(t,16);return String.fromCharCode(n)}))};t.buildQueryString=function(e){return Object.keys(e).map((function(t){return encodeURIComponent(t)+"="+encodeURIComponent(e[t])})).join("&")}},function(e,t,n){"use strict";var o=n(51),r=n(61),a=n(16),i=n(12),c=n(67),l=[].push,d=function(e){var t=1==e,n=2==e,d=3==e,u=4==e,s=6==e,p=5==e||s;return function(m,f,h,C){for(var N,b,g=a(m),v=r(g),V=o(f,h,3),y=i(v.length),_=0,k=C||c,x=t?k(m,y):n?k(m,0):undefined;y>_;_++)if((p||_ in v)&&(b=V(N=v[_],_,g),e))if(t)x[_]=b;else if(b)switch(e){case 3:return!0;case 5:return N;case 6:return _;case 2:l.call(x,N)}else if(u)return!1;return s?-1:d||u?u:x}};e.exports={forEach:d(0),map:d(1),filter:d(2),some:d(3),every:d(4),find:d(5),findIndex:d(6)}},function(e,t,n){"use strict";var o=n(9),r=n(74),a=n(49),i=n(27),c=n(35),l=n(18),d=n(125),u=Object.getOwnPropertyDescriptor;t.f=o?u:function(e,t){if(e=i(e),t=c(t,!0),d)try{return u(e,t)}catch(n){}if(l(e,t))return a(!r.f.call(e,t),e[t])}},function(e,t,n){"use strict";e.exports=function(e){if(e==undefined)throw TypeError("Can't call method on "+e);return e}},function(e,t,n){"use strict";var o=n(7),r=n(31),a=n(18),i=n(91),c=n(92),l=n(36),d=l.get,u=l.enforce,s=String(String).split("String");(e.exports=function(e,t,n,c){var l=!!c&&!!c.unsafe,d=!!c&&!!c.enumerable,p=!!c&&!!c.noTargetGet;"function"==typeof n&&("string"!=typeof t||a(n,"name")||r(n,"name",t),u(n).source=s.join("string"==typeof t?t:"")),e!==o?(l?!p&&e[t]&&(d=!0):delete e[t],d?e[t]=n:r(e,t,n)):d?e[t]=n:i(t,n)})(Function.prototype,"toString",(function(){return"function"==typeof this&&d(this).source||c(this)}))},function(e,t,n){"use strict";var o=n(9),r=n(5),a=n(18),i=Object.defineProperty,c={},l=function(e){throw e};e.exports=function(e,t){if(a(c,e))return c[e];t||(t={});var n=[][e],d=!!a(t,"ACCESSORS")&&t.ACCESSORS,u=a(t,0)?t[0]:l,s=a(t,1)?t[1]:undefined;return c[e]=!!n&&!r((function(){if(d&&!o)return!0;var e={length:-1};d?i(e,1,{enumerable:!0,get:l}):e[1]=1,n.call(e,u,s)}))}},function(e,t,n){"use strict";function o(e,t,n,o,r,a,i){try{var c=e[a](i),l=c.value}catch(d){return void n(d)}c.done?t(l):Promise.resolve(l).then(o,r)}t.__esModule=!0,t.winset=t.winget=t.runCommand=t.callByondAsync=t.callByond=t.IS_IE8=void 0;var r=window.Byond,a=function(){var e=navigator.userAgent.match(/Trident\/(\d+).+?;/i);if(!e)return null;var t=e[1];return t?parseInt(t,10):null}(),i=null!==a&&a<=6;t.IS_IE8=i;var c=function(e,t){void 0===t&&(t={}),r.call(e,t)};t.callByond=c;var l=function(e,t){void 0===t&&(t={}),window.__callbacks__=window.__callbacks__||[];var n=window.__callbacks__.length,o=new Promise((function(e){window.__callbacks__.push(e)}));return r.call(e,Object.assign(Object.assign({},t),{},{callback:"__callbacks__["+n+"]"})),o};t.callByondAsync=l;t.runCommand=function(e){return c("winset",{command:e})};var d=function(){var e,t=(e=regeneratorRuntime.mark((function n(e,t){var o;return regeneratorRuntime.wrap((function(n){for(;;)switch(n.prev=n.next){case 0:return n.next=2,l("winget",{id:e,property:t});case 2:return o=n.sent,n.abrupt("return",o[t]);case 4:case"end":return n.stop()}}),n)})),function(){var t=this,n=arguments;return new Promise((function(r,a){var i=e.apply(t,n);function c(e){o(i,r,a,c,l,"next",e)}function l(e){o(i,r,a,c,l,"throw",e)}c(undefined)}))});return function(e,n){return t.apply(this,arguments)}}();t.winget=d;t.winset=function(e,t,n){var o;return c("winset",((o={})[e+"."+t]=n,o))}},function(e,t,n){"use strict";var o=n(61),r=n(23);e.exports=function(e){return o(r(e))}},function(e,t,n){"use strict";var o=n(129),r=n(18),a=n(135),i=n(14).f;e.exports=function(e){var t=o.Symbol||(o.Symbol={});r(t,e)||i(t,e,{value:a.f(e)})}},function(e,t,n){"use strict";var o=n(23),r=/"/g;e.exports=function(e,t,n,a){var i=String(o(e)),c="<"+t;return""!==n&&(c+=" "+n+'="'+String(a).replace(r,""")+'"'),c+">"+i+""+t+">"}},function(e,t,n){"use strict";var o=n(5);e.exports=function(e){return o((function(){var t=""[e]('"');return t!==t.toLowerCase()||t.split('"').length>3}))}},function(e,t,n){"use strict";var o=n(9),r=n(14),a=n(49);e.exports=o?function(e,t,n){return r.f(e,t,a(1,n))}:function(e,t,n){return e[t]=n,e}},function(e,t,n){"use strict";var o=Math.ceil,r=Math.floor;e.exports=function(e){return isNaN(e=+e)?0:(e>0?r:o)(e)}},function(e,t,n){"use strict";e.exports=function(e){if("function"!=typeof e)throw TypeError(String(e)+" is not a function");return e}},function(e,t,n){"use strict";var o={}.toString;e.exports=function(e){return o.call(e).slice(8,-1)}},function(e,t,n){"use strict";var o=n(8);e.exports=function(e,t){if(!o(e))return e;var n,r;if(t&&"function"==typeof(n=e.toString)&&!o(r=n.call(e)))return r;if("function"==typeof(n=e.valueOf)&&!o(r=n.call(e)))return r;if(!t&&"function"==typeof(n=e.toString)&&!o(r=n.call(e)))return r;throw TypeError("Can't convert object to primitive value")}},function(e,t,n){"use strict";var o,r,a,i=n(127),c=n(7),l=n(8),d=n(31),u=n(18),s=n(75),p=n(63),m=c.WeakMap;if(i){var f=new m,h=f.get,C=f.has,N=f.set;o=function(e,t){return N.call(f,e,t),t},r=function(e){return h.call(f,e)||{}},a=function(e){return C.call(f,e)}}else{var b=s("state");p[b]=!0,o=function(e,t){return d(e,b,t),t},r=function(e){return u(e,b)?e[b]:{}},a=function(e){return u(e,b)}}e.exports={set:o,get:r,has:a,enforce:function(e){return a(e)?r(e):o(e,{})},getterFor:function(e){return function(t){var n;if(!l(t)||(n=r(t)).type!==e)throw TypeError("Incompatible receiver, "+e+" required");return n}}}},function(e,t,n){"use strict";var o=n(18),r=n(16),a=n(75),i=n(104),c=a("IE_PROTO"),l=Object.prototype;e.exports=i?Object.getPrototypeOf:function(e){return e=r(e),o(e,c)?e[c]:"function"==typeof e.constructor&&e instanceof e.constructor?e.constructor.prototype:e instanceof Object?l:null}},function(e,t,n){"use strict";var o=n(129),r=n(7),a=function(e){return"function"==typeof e?e:undefined};e.exports=function(e,t){return arguments.length<2?a(o[e])||a(r[e]):o[e]&&o[e][t]||r[e]&&r[e][t]}},function(e,t,n){"use strict";t.__esModule=!0,t.getGasColor=t.getGasLabel=t.RADIO_CHANNELS=t.CSS_COLORS=t.COLORS=t.UI_CLOSE=t.UI_DISABLED=t.UI_UPDATE=t.UI_INTERACTIVE=void 0;t.UI_INTERACTIVE=2;t.UI_UPDATE=1;t.UI_DISABLED=0;t.UI_CLOSE=-1;t.COLORS={department:{captain:"#c06616",security:"#e74c3c",medbay:"#3498db",science:"#9b59b6",engineering:"#f1c40f",cargo:"#f39c12",centcom:"#00c100",other:"#c38312"},damageType:{oxy:"#3498db",toxin:"#2ecc71",burn:"#e67e22",brute:"#e74c3c"}};t.CSS_COLORS=["black","white","red","orange","yellow","olive","green","teal","blue","violet","purple","pink","brown","grey","good","average","bad","label"];t.RADIO_CHANNELS=[{name:"Syndicate",freq:1213,color:"#a52a2a"},{name:"Red Team",freq:1215,color:"#ff4444"},{name:"Blue Team",freq:1217,color:"#3434fd"},{name:"CentCom",freq:1337,color:"#2681a5"},{name:"Supply",freq:1347,color:"#b88646"},{name:"Service",freq:1349,color:"#6ca729"},{name:"Science",freq:1351,color:"#c68cfa"},{name:"Command",freq:1353,color:"#5177ff"},{name:"Medical",freq:1355,color:"#57b8f0"},{name:"Engineering",freq:1357,color:"#f37746"},{name:"Security",freq:1359,color:"#dd3535"},{name:"AI Private",freq:1447,color:"#d65d95"},{name:"Common",freq:1459,color:"#1ecc43"}];var o=[{id:"o2",name:"Oxygen",label:"O\u2082",color:"blue"},{id:"n2",name:"Nitrogen",label:"N\u2082",color:"red"},{id:"co2",name:"Carbon Dioxide",label:"CO\u2082",color:"grey"},{id:"plasma",name:"Plasma",label:"Plasma",color:"pink"},{id:"water_vapor",name:"Water Vapor",label:"H\u2082O",color:"grey"},{id:"nob",name:"Hyper-noblium",label:"Hyper-nob",color:"teal"},{id:"n2o",name:"Nitrous Oxide",label:"N\u2082O",color:"red"},{id:"no2",name:"Nitryl",label:"NO\u2082",color:"brown"},{id:"tritium",name:"Tritium",label:"Tritium",color:"green"},{id:"bz",name:"BZ",label:"BZ",color:"purple"},{id:"stim",name:"Stimulum",label:"Stimulum",color:"purple"},{id:"pluox",name:"Pluoxium",label:"Pluoxium",color:"blue"},{id:"miasma",name:"Miasma",label:"Miasma",color:"olive"},{id:"hydrogen",name:"Hydrogen",label:"H\u2082",color:"white"}];t.getGasLabel=function(e,t){var n=String(e).toLowerCase(),r=o.find((function(e){return e.id===n||e.name.toLowerCase()===n}));return r&&r.label||t||e};t.getGasColor=function(e){var t=String(e).toLowerCase(),n=o.find((function(e){return e.id===t||e.name.toLowerCase()===t}));return n&&n.color}},function(e,t,n){"use strict";function o(e){var t=0;if("undefined"==typeof Symbol||null==e[Symbol.iterator]){if(Array.isArray(e)||(e=function(e,t){if(!e)return;if("string"==typeof e)return r(e,t);var n=Object.prototype.toString.call(e).slice(8,-1);"Object"===n&&e.constructor&&(n=e.constructor.name);if("Map"===n||"Set"===n)return Array.from(e);if("Arguments"===n||/^(?:Ui|I)nt(?:8|16|32)(?:Clamped)?Array$/.test(n))return r(e,t)}(e)))return function(){return t>=e.length?{done:!0}:{done:!1,value:e[t++]}};throw new TypeError("Invalid attempt to iterate non-iterable instance.\nIn order to be iterable, non-array objects must have a [Symbol.iterator]() method.")}return(t=e[Symbol.iterator]()).next.bind(t)}function r(e,t){(null==t||t>e.length)&&(t=e.length);for(var n=0,o=new Array(t);n