diff --git a/_maps/map_files/MetaStation/MetaStation.v41A.II.dmm b/_maps/map_files/MetaStation/MetaStation.v41A.II.dmm index 29b544685c1c4..ff8836a4906db 100644 --- a/_maps/map_files/MetaStation/MetaStation.v41A.II.dmm +++ b/_maps/map_files/MetaStation/MetaStation.v41A.II.dmm @@ -87001,11 +87001,7 @@ pixel_x = -2; pixel_y = 2 }, -/obj/item/nullrod{ - pixel_x = 4 - }, /obj/item/organ/internal/heart, -/obj/item/soulstone/anybody/chaplain, /turf/simulated/floor/plasteel{ tag = "icon-cult"; icon_state = "cult"; diff --git a/code/__DEFINES/colors.dm b/code/__DEFINES/colors.dm index 2bb53c91ce410..d379817e4b409 100644 --- a/code/__DEFINES/colors.dm +++ b/code/__DEFINES/colors.dm @@ -100,6 +100,11 @@ #define COLOR_ASSEMBLY_BLUE "#38559E" #define COLOR_ASSEMBLY_PURPLE "#6F6192" +//blood colors + +#define COLOR_BLOOD_BASE "#A10808" +#define COLOR_BLOOD_MACHINE "#1F181F" + // Pipe colours #define PIPE_COLOR_GREY "#ffffff" //yes white is grey #define PIPE_COLOR_RED "#ff0000" diff --git a/code/__DEFINES/construction.dm b/code/__DEFINES/construction.dm index 1ef94c8ba656d..2842e70be2130 100644 --- a/code/__DEFINES/construction.dm +++ b/code/__DEFINES/construction.dm @@ -49,7 +49,7 @@ //other construction-related things -//windows affected by nar-sie turn this color. +//windows affected by nar'sie turn this color. #define NARSIE_WINDOW_COLOUR "#7D1919" //let's just pretend fulltile windows being children of border windows is fine diff --git a/code/__DEFINES/cult.dm b/code/__DEFINES/cult.dm new file mode 100644 index 0000000000000..e9965b2c999d2 --- /dev/null +++ b/code/__DEFINES/cult.dm @@ -0,0 +1,66 @@ +// Rune colors, for easy reference +#define RUNE_COLOR_TALISMAN "#0000FF" +#define RUNE_COLOR_TELEPORT "#551A8B" +#define RUNE_COLOR_OFFER "#FFFFFF" +#define RUNE_COLOR_DARKRED "#7D1717" +#define RUNE_COLOR_MEDIUMRED "#C80000" +#define RUNE_COLOR_BURNTORANGE "#CC5500" +#define RUNE_COLOR_RED "#FF0000" +#define RUNE_COLOR_LIGHTRED "#FF726F" +#define RUNE_COLOR_EMP "#4D94FF" +#define RUNE_COLOR_SUMMON "#00FF00" + +#define is_sacrifice_target(A) SSticker.mode?.cult_objs.is_sac_target(A) + +// Blood magic +/// Maximum number of spells with an empowering rune +#define MAX_BLOODCHARGE 4 +/// Maximum number of spells without an empowering rune +#define RUNELESS_MAX_BLOODCHARGE 1 +#define BLOOD_SPEAR_COST 150 +#define BLOOD_BARRAGE_COST 300 +#define BLOOD_BEAM_COST 500 +#define METAL_TO_CONSTRUCT_SHELL_CONVERSION 50 + +// Cult Status +/// At what population does it switch to highpop values +#define CULT_POPULATION_THRESHOLD 100 +/// Percent before rise (Lowpop) +#define CULT_RISEN_LOW 0.2 +/// Percent before ascend (Lowpop) +#define CULT_ASCENDANT_LOW 0.3 +/// Percent before rise (Highpop) +#define CULT_RISEN_HIGH 0.1 +/// Percent before ascend (Highpop) +#define CULT_ASCENDANT_HIGH 0.2 + +// Screen locations +#define DEFAULT_BLOODSPELLS "6:-29,4:-2" +#define DEFAULT_BLOODTIP "14:6,14:27" +#define DEFAULT_TOOLTIP "6:-29,5:-2" + +// Text +#define CULT_GREETING "You catch a glimpse of the Realm of [SSticker.cultdat.entity_name], [SSticker.cultdat.entity_title3]. \ + You now see how flimsy the world is, you see that it should be open to the knowledge of [SSticker.cultdat.entity_name]." + +#define CULT_CURSES list("A fuel technician just slit his own throat and begged for death.", \ + "The shuttle's navigation programming was replaced by a file containing two words, IT COMES.", \ + "The shuttle's custodian tore out his guts and began painting strange shapes on the floor.", \ + "A shuttle engineer began screaming 'DEATH IS NOT THE END' and ripped out wires until an arc flash seared off her flesh.", \ + "A shuttle inspector started laughing madly over the radio and then threw herself into an engine turbine.", \ + "The shuttle dispatcher was found dead with bloody symbols carved into their flesh.", \ + "Steve repeatedly touched a lightbulb until his hands fell off.") + +// Misc +#define SOULS_TO_REVIVE 3 +#define BLOODCULT_EYE "#FF0000" +#define SUMMON_POSSIBILITIES 3 +#define CULT_CLOTHING list(/obj/item/clothing/suit/hooded/cultrobes, /obj/item/clothing/suit/space/cult, /obj/item/clothing/suit/hooded/cultrobes/cult_shield, \ + /obj/item/clothing/suit/hooded/cultrobes/flagellant_robe, /obj/item/clothing/glasses/hud/health/night/cultblind) + +// Cult objective status +#define NARSIE_IS_ASLEEP 0 +#define NARSIE_DEMANDS_SACRIFICE 1 +#define NARSIE_NEEDS_SUMMONING 2 +#define NARSIE_HAS_RISEN 3 +#define NARSIE_HAS_FALLEN -1 diff --git a/code/__DEFINES/misc.dm b/code/__DEFINES/misc.dm index 8eaab195c6285..b23380098e6cd 100644 --- a/code/__DEFINES/misc.dm +++ b/code/__DEFINES/misc.dm @@ -167,45 +167,46 @@ #define MFOAM_IRON 2 //Human Overlays Indexes///////// -#define BODY_LAYER 38 -#define MUTANTRACE_LAYER 37 -#define TAIL_UNDERLIMBS_LAYER 36 //Tail split-rendering. -#define LIMBS_LAYER 35 -#define INTORGAN_LAYER 34 -#define MARKINGS_LAYER 33 -#define UNDERWEAR_LAYER 32 -#define MUTATIONS_LAYER 31 -#define H_DAMAGE_LAYER 30 -#define UNIFORM_LAYER 29 -#define ID_LAYER 28 -#define SHOES_LAYER 27 -#define GLOVES_LAYER 26 -#define EARS_LAYER 25 -#define SUIT_LAYER 24 -#define BELT_LAYER 23 //Possible make this an overlay of somethign required to wear a belt? -#define SUIT_STORE_LAYER 22 -#define BACK_LAYER 21 -#define HEAD_ACCESSORY_LAYER 20 -#define FHAIR_LAYER 19 -#define GLASSES_LAYER 18 -#define HAIR_LAYER 17 //TODO: make part of head layer? -#define HEAD_ACC_OVER_LAYER 16 //Select-layer rendering. -#define FHAIR_OVER_LAYER 15 //Select-layer rendering. -#define GLASSES_OVER_LAYER 14 //Select-layer rendering. -#define TAIL_LAYER 13 //bs12 specific. this hack is probably gonna come back to haunt me -#define FACEMASK_LAYER 12 -#define OVER_MASK_LAYER 11 //Select-layer rendering. -#define HEAD_LAYER 10 -#define COLLAR_LAYER 9 -#define HANDCUFF_LAYER 8 -#define LEGCUFF_LAYER 7 -#define L_HAND_LAYER 6 -#define R_HAND_LAYER 5 -#define TARGETED_LAYER 4 //BS12: Layer for the target overlay from weapon targeting system +#define BODY_LAYER 39 +#define MUTANTRACE_LAYER 38 +#define TAIL_UNDERLIMBS_LAYER 37 //Tail split-rendering. +#define LIMBS_LAYER 36 +#define INTORGAN_LAYER 35 +#define MARKINGS_LAYER 34 +#define UNDERWEAR_LAYER 33 +#define MUTATIONS_LAYER 32 +#define H_DAMAGE_LAYER 31 +#define UNIFORM_LAYER 30 +#define ID_LAYER 29 +#define SHOES_LAYER 28 +#define GLOVES_LAYER 27 +#define EARS_LAYER 26 +#define SUIT_LAYER 25 +#define BELT_LAYER 24 //Possible make this an overlay of somethign required to wear a belt? +#define SUIT_STORE_LAYER 23 +#define BACK_LAYER 22 +#define HEAD_ACCESSORY_LAYER 21 +#define FHAIR_LAYER 20 +#define GLASSES_LAYER 19 +#define HAIR_LAYER 18 //TODO: make part of head layer? +#define HEAD_ACC_OVER_LAYER 17 //Select-layer rendering. +#define FHAIR_OVER_LAYER 16 //Select-layer rendering. +#define GLASSES_OVER_LAYER 15 //Select-layer rendering. +#define TAIL_LAYER 14 //bs12 specific. this hack is probably gonna come back to haunt me +#define FACEMASK_LAYER 13 +#define OVER_MASK_LAYER 12 //Select-layer rendering. +#define HEAD_LAYER 11 +#define COLLAR_LAYER 10 +#define HANDCUFF_LAYER 9 +#define LEGCUFF_LAYER 8 +#define L_HAND_LAYER 7 +#define R_HAND_LAYER 6 +#define TARGETED_LAYER 5 //BS12: Layer for the target overlay from weapon targeting system +#define HALO_LAYER 4 //blood cult ascended halo, because there's currently no better solution for adding/removing #define FIRE_LAYER 3 //If you're on fire #define MISC_LAYER 2 #define FROZEN_LAYER 1 -#define TOTAL_LAYERS 38 +#define TOTAL_LAYERS 39 ///Access Region Codes/// #define REGION_ALL 0 @@ -446,9 +447,6 @@ #define SENSOR_VITALS 2 #define SENSOR_COORDS 3 -// Cult summon possibilities -#define SUMMON_POSSIBILITIES 3 - // Dice rigged options. #define DICE_NOT_RIGGED 1 #define DICE_BASICALLY_RIGGED 2 diff --git a/code/__DEFINES/status_effects.dm b/code/__DEFINES/status_effects.dm index 54b85d80e76be..ce91d2ccf31b9 100644 --- a/code/__DEFINES/status_effects.dm +++ b/code/__DEFINES/status_effects.dm @@ -55,7 +55,7 @@ //#define STATUS_EFFECT_HISWRATH /datum/status_effect/his_wrath //His Wrath. -#define STATUS_EFFECT_SUMMONEDGHOST /datum/status_effect/cultghost //is a cult ghost and can't use manifest runes +#define STATUS_EFFECT_SUMMONEDGHOST /datum/status_effect/cultghost //is a cult ghost: can see dead people, can't manifest more ghosts #define STATUS_EFFECT_CRUSHERMARK /datum/status_effect/crusher_mark //if struck with a proto-kinetic crusher, takes a ton of damage diff --git a/code/__HELPERS/traits.dm b/code/__HELPERS/traits.dm index 352a6fa368094..d91254595c530 100644 --- a/code/__HELPERS/traits.dm +++ b/code/__HELPERS/traits.dm @@ -67,5 +67,10 @@ Remember to update _globalvars/traits.dm if you're adding/removing/renaming trai #define TRAIT_BLOODCRAWL_EAT "bloodcrawl_eat" #define TRAIT_JESTER "jester" +// // common trait sources #define ROUNDSTART_TRAIT "roundstart" //cannot be removed without admin intervention +#define CULT_TRAIT "cult" + +// unique trait sources +#define CULT_EYES "cult_eyes" diff --git a/code/_globalvars/game_modes.dm b/code/_globalvars/game_modes.dm index 3088270579c6b..e88e6a6047c27 100644 --- a/code/_globalvars/game_modes.dm +++ b/code/_globalvars/game_modes.dm @@ -7,5 +7,3 @@ GLOBAL_DATUM(start_state, /datum/station_state) // Used in round-end report. Don GLOBAL_VAR(custom_event_msg) GLOBAL_VAR(custom_event_admin_msg) - -GLOBAL_LIST_EMPTY(summon_spots) diff --git a/code/_onclick/hud/action_button.dm b/code/_onclick/hud/action_button.dm index a041454f81656..8a69c7aa4b3d4 100644 --- a/code/_onclick/hud/action_button.dm +++ b/code/_onclick/hud/action_button.dm @@ -1,6 +1,8 @@ /obj/screen/movable/action_button var/datum/action/linked_action + var/actiontooltipstyle = "" screen_loc = null + var/ordered = TRUE /obj/screen/movable/action_button/MouseDrop(over_object) if((istype(over_object, /obj/screen/movable/action_button) && !istype(over_object, /obj/screen/movable/action_button/hide_toggle))) @@ -12,7 +14,9 @@ var/list/actions = usr.actions actions.Swap(actions.Find(linked_action), actions.Find(B.linked_action)) moved = FALSE + ordered = TRUE B.moved = FALSE + B.ordered = TRUE closeToolTip(usr) usr.update_action_buttons() else if(istype(over_object, /obj/screen/movable/action_button/hide_toggle)) @@ -28,7 +32,7 @@ to_chat(usr, "Action button \"[name]\" is locked, unlock it first.") return TRUE moved = FALSE - usr.update_action_buttons() //redraw buttons that are no longer considered "moved" + usr.update_action_buttons(TRUE) //redraw buttons that are no longer considered "moved" return TRUE if(modifiers["ctrl"]) locked = !locked @@ -120,7 +124,7 @@ /obj/screen/movable/action_button/MouseEntered(location, control, params) if(!QDELETED(src)) - openToolTip(usr, src, params, title = name, content = desc) + openToolTip(usr, src, params, title = name, content = desc, theme = actiontooltipstyle) /obj/screen/movable/action_button/MouseExited() closeToolTip(usr) @@ -147,9 +151,12 @@ client.screen += A.button else for(var/datum/action/A in actions) - button_number++ + A.override_location() // If the action has a location override, call it A.UpdateButtonIcon() + var/obj/screen/movable/action_button/B = A.button + if(B.ordered) + button_number++ if(!B.moved) B.screen_loc = hud_used.ButtonNumberToScreenCoords(button_number) else diff --git a/code/datums/action.dm b/code/datums/action.dm index bca714f9c7351..5e4ceedf7b3fe 100644 --- a/code/datums/action.dm +++ b/code/datums/action.dm @@ -12,7 +12,7 @@ var/obj/screen/movable/action_button/button = null var/button_icon = 'icons/mob/actions/actions.dmi' var/background_icon_state = "bg_default" - + var/buttontooltipstyle = "" var/icon_icon = 'icons/mob/actions/actions.dmi' var/button_icon_state = "default" var/mob/owner @@ -22,6 +22,7 @@ button = new button.linked_action = src button.name = name + button.actiontooltipstyle = buttontooltipstyle if(desc) button.desc = desc @@ -64,6 +65,9 @@ /datum/action/proc/Process() return +/datum/action/proc/override_location() // Override to set coordinates manually + return + /datum/action/proc/IsAvailable()// returns 1 if all checks pass if(!owner) return FALSE diff --git a/code/datums/mind.dm b/code/datums/mind.dm index cac175e1640f3..039dae5969693 100644 --- a/code/datums/mind.dm +++ b/code/datums/mind.dm @@ -228,7 +228,7 @@ . = _memory_edit_header("cult") if(src in SSticker.mode.cult) . += "no|CULTIST" - . += "
Give tome|equip." + . += "
Give dagger|runedmetal." else . += "NO|cultist" @@ -412,8 +412,6 @@ sections["implant"] = memory_edit_implant(H) /** REVOLUTION ***/ sections["revolution"] = memory_edit_revolution(H) - /** CULT ***/ - sections["cult"] = memory_edit_cult(H) /** WIZARD ***/ sections["wizard"] = memory_edit_wizard(H) /** CHANGELING ***/ @@ -433,6 +431,9 @@ sections["eventmisc"] = memory_edit_eventmisc(H) /** TRAITOR ***/ sections["traitor"] = memory_edit_traitor() + if(!issilicon(current)) + /** CULT ***/ + sections["cult"] = memory_edit_cult(H) /** SILICON ***/ if(issilicon(current)) sections["silicon"] = memory_edit_silicon() @@ -725,15 +726,6 @@ special_role = null SSticker.mode.head_revolutionaries -=src to_chat(src, "The nanobots in the mindshield implant remove all thoughts about being a revolutionary. Get back to work!") - if(src in SSticker.mode.cult) - SSticker.mode.cult -= src - SSticker.mode.update_cult_icons_removed(src) - special_role = null - var/datum/game_mode/cult/cult = SSticker.mode - if(istype(cult)) - cult.memorize_cult_objectives(src) - to_chat(current, "The nanobots in the mindshield implant remove all thoughts about being in a cult. Have a productive day!") - memory = "" else if(href_list["revolution"]) @@ -851,41 +843,19 @@ if(!(src in SSticker.mode.cult)) SSticker.mode.add_cultist(src) special_role = SPECIAL_ROLE_CULTIST - to_chat(current, "You catch a glimpse of the Realm of [SSticker.cultdat.entity_name], [SSticker.cultdat.entity_title3]. You now see how flimsy the world is, you see that it should be open to the knowledge of [SSticker.cultdat.entity_name].") + to_chat(current, CULT_GREETING) to_chat(current, "Assist your new compatriots in their dark dealings. Their goal is yours, and yours is theirs. You serve [SSticker.cultdat.entity_title2] above all else. Bring It back.") - log_admin("[key_name(usr)] has culted [key_name(current)]") - message_admins("[key_name_admin(usr)] has culted [key_name_admin(current)]") - if(!GLOB.summon_spots.len) - while(GLOB.summon_spots.len < SUMMON_POSSIBILITIES) - var/area/summon = pick(return_sorted_areas() - GLOB.summon_spots) - if(summon && is_station_level(summon.z) && summon.valid_territory) - GLOB.summon_spots += summon - if("tome") + log_and_message_admins("[key_name(usr)] has culted [key_name(current)]") + if("dagger") var/mob/living/carbon/human/H = current - if(istype(H)) - var/obj/item/tome/T = new(H) - - var/list/slots = list ( - "backpack" = slot_in_backpack, - "left pocket" = slot_l_store, - "right pocket" = slot_r_store, - "left hand" = slot_l_hand, - "right hand" = slot_r_hand, - ) - var/where = H.equip_in_one_of_slots(T, slots) - if(!where) - to_chat(usr, "Spawning tome failed!") - qdel(T) - else - to_chat(H, "A tome, a message from your new master, appears in your [where].") - log_admin("[key_name(usr)] has spawned a tome for [key_name(current)]") - message_admins("[key_name_admin(usr)] has spawned a tome for [key_name_admin(current)]") - - if("equip") - if(!SSticker.mode.equip_cultist(current)) - to_chat(usr, "Spawning equipment failed!") - log_admin("[key_name(usr)] has equipped [key_name(current)] as a cultist") - message_admins("[key_name_admin(usr)] has equipped [key_name_admin(current)] as a cultist") + if(!SSticker.mode.cult_give_item(/obj/item/melee/cultblade/dagger, H)) + to_chat(usr, "Spawning dagger failed!") + log_and_message_admins("[key_name(usr)] has equipped [key_name(current)] with a cult dagger") + if("runedmetal") + var/mob/living/carbon/human/H = current + if(!SSticker.mode.cult_give_item(/obj/item/stack/sheet/runed_metal/ten, H)) + to_chat(usr, "Spawning runed metal failed!") + log_and_message_admins("[key_name(usr)] has equipped [key_name(current)] with 10 runed metal sheets") else if(href_list["wizard"]) diff --git a/code/datums/spells/construct_spells.dm b/code/datums/spells/construct_spells.dm index 10c06e5ce7b66..7c4054f35d680 100644 --- a/code/datums/spells/construct_spells.dm +++ b/code/datums/spells/construct_spells.dm @@ -2,7 +2,7 @@ /obj/effect/proc_holder/spell/aoe_turf/conjure/construct/lesser charge_max = 1800 - action_icon_state = "lesserconstruct" + action_icon_state = "artificer" action_background_icon_state = "bg_cult" /obj/effect/proc_holder/spell/aoe_turf/conjure/floor @@ -12,50 +12,49 @@ action_background_icon_state = "bg_cult" school = "conjuration" charge_max = 20 - clothes_req = 0 + clothes_req = FALSE invocation = "none" invocation_type = "none" range = 0 summon_type = list(/turf/simulated/floor/engine/cult) - centcom_cancast = 0 //Stop crashing the server by spawning turfs on transit tiles + centcom_cancast = FALSE //Stop crashing the server by spawning turfs on transit tiles /obj/effect/proc_holder/spell/aoe_turf/conjure/wall name = "Summon Cult Wall" desc = "This spell constructs a cult wall" - action_icon_state = "lesserconstruct" + action_icon_state = "cultforcewall" action_background_icon_state = "bg_cult" school = "conjuration" charge_max = 100 - clothes_req = 0 + clothes_req = FALSE invocation = "none" invocation_type = "none" range = 0 summon_type = list(/turf/simulated/wall/cult/artificer) //we don't want artificer-based runed metal farms - centcom_cancast = 0 //Stop crashing the server by spawning turfs on transit tiles + centcom_cancast = FALSE //Stop crashing the server by spawning turfs on transit tiles /obj/effect/proc_holder/spell/aoe_turf/conjure/wall/reinforced name = "Greater Construction" desc = "This spell constructs a reinforced metal wall" - school = "conjuration" charge_max = 300 - clothes_req = 0 + clothes_req = FALSE invocation = "none" invocation_type = "none" range = 0 - centcom_cancast = 0 //Stop crashing the server by spawning turfs on transit tiles + centcom_cancast = FALSE //Stop crashing the server by spawning turfs on transit tiles delay = 50 summon_type = list(/turf/simulated/wall/r_wall) /obj/effect/proc_holder/spell/aoe_turf/conjure/soulstone name = "Summon Soulstone" - desc = "This spell reaches into Nar-Sie's realm, summoning one of the legendary fragments across time and space" + desc = "This spell reaches into Nar'Sie's realm, summoning one of the legendary fragments across time and space" action_icon_state = "summonsoulstone" action_background_icon_state = "bg_cult" school = "conjuration" charge_max = 3000 - clothes_req = 0 + clothes_req = FALSE invocation = "none" invocation_type = "none" range = 0 @@ -64,12 +63,12 @@ /obj/effect/proc_holder/spell/aoe_turf/conjure/pylon name = "Cult Pylon" - desc = "This spell conjures a fragile crystal from Nar-Sie's realm. Makes for a convenient light source." - action_icon_state = "summonsoulstone" + desc = "This spell conjures a fragile crystal from Nar'Sie's realm. Makes for a convenient light source." + action_icon_state = "pylon" action_background_icon_state = "bg_cult" school = "conjuration" charge_max = 200 - clothes_req = 0 + clothes_req = FALSE invocation = "none" invocation_type = "none" range = 0 @@ -84,7 +83,7 @@ action_background_icon_state = "bg_cult" school = "transmutation" charge_max = 300 - clothes_req = 0 + clothes_req = FALSE invocation = "none" invocation_type = "none" range = 0 @@ -103,19 +102,19 @@ desc = "This spell allows you to pass through walls" action_icon_state = "phaseshift" action_background_icon_state = "bg_cult" - school = "transmutation" charge_max = 200 - clothes_req = 0 + clothes_req = FALSE invocation = "none" invocation_type = "none" - range = -1 - include_user = 1 - jaunt_duration = 50 //in deciseconds - centcom_cancast = 0 //Stop people from getting to centcom jaunt_in_time = 12 jaunt_in_type = /obj/effect/temp_visual/dir_setting/wraith jaunt_out_type = /obj/effect/temp_visual/dir_setting/wraith/out +/obj/effect/proc_holder/spell/targeted/ethereal_jaunt/shift/do_jaunt(mob/living/target) + target.set_light(0) + ..() + target.set_light(2, 3, l_color = SSticker.cultdat ? SSticker.cultdat.construct_glow : LIGHT_COLOR_BLOOD_MAGIC) + /obj/effect/proc_holder/spell/targeted/ethereal_jaunt/shift/jaunt_steam(mobloc) return @@ -125,7 +124,7 @@ action_background_icon_state = "bg_cult" school = "evocation" charge_max = 400 - clothes_req = 0 + clothes_req = FALSE invocation = "none" invocation_type = "none" proj_lifespan = 10 @@ -138,7 +137,7 @@ action_background_icon_state = "bg_cult" school = "conjuration" charge_max = 200 - clothes_req = 0 + clothes_req = FALSE invocation = "none" invocation_type = "none" range = -1 diff --git a/code/game/area/ss13_areas.dm b/code/game/area/ss13_areas.dm index 21cc4c3a4c091..b917936151d6c 100644 --- a/code/game/area/ss13_areas.dm +++ b/code/game/area/ss13_areas.dm @@ -697,6 +697,9 @@ NOTE: there are two lists of areas in the end of this file: centcom and station //Hallway +/area/hallway + valid_territory = FALSE //too many areas with similar/same names, also not very interesting summon spots + /area/hallway/primary/fore name = "\improper Fore Primary Hallway" icon_state = "hallF" diff --git a/code/game/gamemodes/cult/blood_magic.dm b/code/game/gamemodes/cult/blood_magic.dm new file mode 100644 index 0000000000000..4eb889b38f723 --- /dev/null +++ b/code/game/gamemodes/cult/blood_magic.dm @@ -0,0 +1,827 @@ +/datum/action/innate/cult/blood_magic //Blood magic handles the creation of blood spells (formerly talismans) + name = "Prepare Blood Magic" + button_icon_state = "carve" + desc = "Prepare blood magic by carving runes into your flesh. This is easier with an empowering rune." + var/list/spells = list() + var/channeling = FALSE + +/datum/action/innate/cult/blood_magic/Remove() + for(var/X in spells) + qdel(X) + ..() + +/datum/action/innate/cult/blood_magic/override_location() + button.ordered = FALSE + button.screen_loc = DEFAULT_BLOODSPELLS + button.moved = DEFAULT_BLOODSPELLS + +/datum/action/innate/cult/blood_magic/proc/Positioning() + var/list/screen_loc_split = splittext(button.screen_loc, ",") + var/list/screen_loc_X = splittext(screen_loc_split[1], ":") + var/list/screen_loc_Y = splittext(screen_loc_split[2], ":") + var/pix_X = text2num(screen_loc_X[2]) + for(var/datum/action/innate/cult/blood_spell/B in spells) + if(B.button.locked) + var/order = pix_X + spells.Find(B) * 31 + B.button.screen_loc = "[screen_loc_X[1]]:[order],[screen_loc_Y[1]]:[screen_loc_Y[2]]" + B.button.moved = B.button.screen_loc + +/datum/action/innate/cult/blood_magic/Activate() + var/rune = FALSE + var/limit = RUNELESS_MAX_BLOODCHARGE + for(var/obj/effect/rune/empower/R in range(1, owner)) + rune = TRUE + limit = MAX_BLOODCHARGE + break + if(length(spells) >= limit) + if(rune) + to_chat(owner, "You cannot store more than [MAX_BLOODCHARGE] spell\s. Pick a spell to remove.") + remove_spell("You cannot store more than [MAX_BLOODCHARGE] spell\s, pick a spell to remove.") + else + to_chat(owner, "You cannot store more than [RUNELESS_MAX_BLOODCHARGE] spell\s without an empowering rune! Pick a spell to remove.") + remove_spell("You cannot store more than [RUNELESS_MAX_BLOODCHARGE] spell\s without an empowering rune, pick a spell to remove.") + return + var/entered_spell_name + var/datum/action/innate/cult/blood_spell/BS + var/list/possible_spells = list() + + for(var/I in subtypesof(/datum/action/innate/cult/blood_spell)) + var/datum/action/innate/cult/blood_spell/J = I + var/cult_name = initial(J.name) + possible_spells[cult_name] = J + if(length(spells)) + possible_spells += "(REMOVE SPELL)" + entered_spell_name = input(owner, "Pick a blood spell to prepare...", "Spell Choices") as null|anything in possible_spells + if(entered_spell_name == "(REMOVE SPELL)") + remove_spell() + return + BS = possible_spells[entered_spell_name] + if(QDELETED(src) || owner.incapacitated() || !BS || (rune && !(locate(/obj/effect/rune/empower) in range(1, owner))) || (length(spells) >= limit)) + return + + if(!channeling) + channeling = TRUE + to_chat(owner, "You begin to carve unnatural symbols into your flesh!") + else + to_chat(owner, "You are already invoking blood magic!") + return + + if(do_after(owner, 100 - rune * 60, target = owner)) + if(ishuman(owner)) + var/mob/living/carbon/human/H = owner + if(H.dna && (NO_BLOOD in H.dna.species.species_traits)) + H.cult_self_harm(3 - rune * 2) + else + H.bleed(20 - rune * 12) + var/datum/action/innate/cult/blood_spell/new_spell = new BS(owner) + spells += new_spell + new_spell.Grant(owner, src) + to_chat(owner, "Your wounds glow with power, you have prepared a [new_spell.name] invocation!") + channeling = FALSE + +/datum/action/innate/cult/blood_magic/proc/remove_spell(message = "Pick a spell to remove.") + var/nullify_spell = input(owner, message, "Current Spells") as null|anything in spells + if(nullify_spell) + qdel(nullify_spell) + +/datum/action/innate/cult/blood_spell //The next generation of talismans, handles storage/creation of blood magic + name = "Blood Magic" + button_icon_state = "telerune" + desc = "Fear the Old Blood." + var/charges = 1 + var/magic_path = null + var/obj/item/melee/blood_magic/hand_magic + var/datum/action/innate/cult/blood_magic/all_magic + var/base_desc //To allow for updating tooltips + var/invocation = "Hoi there something's wrong!" + var/health_cost = 0 + +/datum/action/innate/cult/blood_spell/Grant(mob/living/owner, datum/action/innate/cult/blood_magic/BM) + if(health_cost) + desc += "
Deals [health_cost] damage to your arm per use." + base_desc = desc + desc += "
Has [charges] use\s remaining." + all_magic = BM + button.ordered = FALSE + ..() + +/datum/action/innate/cult/blood_spell/override_location() + button.locked = TRUE + all_magic.Positioning() + +/datum/action/innate/cult/blood_spell/Remove() + if(all_magic) + all_magic.spells -= src + if(hand_magic) + qdel(hand_magic) + hand_magic = null + ..() + +/datum/action/innate/cult/blood_spell/IsAvailable() + if(!iscultist(owner) || owner.incapacitated() || !charges) + return FALSE + return ..() + +/datum/action/innate/cult/blood_spell/Activate() + if(magic_path) // If this spell flows from the hand + if(!hand_magic) // If you don't already have the spell active + hand_magic = new magic_path(owner, src) + if(!owner.put_in_hands(hand_magic)) + qdel(hand_magic) + hand_magic = null + to_chat(owner, "You have no empty hand for invoking blood magic!") + return + to_chat(owner, "Your wounds glow as you invoke the [name].") + + else // If the spell is active, and you clicked on the button for it + qdel(hand_magic) + hand_magic = null + +//the spell list + +/datum/action/innate/cult/blood_spell/stun + name = "Stun" + desc = "Empowers your hand to stun and mute a victim on contact." + button_icon_state = "stun" + magic_path = /obj/item/melee/blood_magic/stun + health_cost = 10 + +/datum/action/innate/cult/blood_spell/teleport + name = "Teleport" + desc = "Empowers your hand to teleport yourself or another cultist to a teleport rune on contact." + button_icon_state = "teleport" + magic_path = /obj/item/melee/blood_magic/teleport + health_cost = 7 + +/datum/action/innate/cult/blood_spell/emp + name = "Electromagnetic Pulse" + desc = "Channel an electromagnetic pulse inside your body, then release it, affecting nearby non-cultists. The pulse will still affect you." + button_icon_state = "emp" + health_cost = 10 + invocation = "Ta'gh fara'qha fel d'amar det!" + +/datum/action/innate/cult/blood_spell/emp/Grant(mob/living/owner) + if(ishuman(owner)) + var/mob/living/carbon/human/H = owner + var/oof = FALSE + for(var/obj/item/organ/external/E in H.bodyparts) + if(E.is_robotic()) + oof = TRUE + break + if(!oof) + for(var/obj/item/organ/internal/I in H.internal_organs) + if(I.is_robotic()) + oof = TRUE + break + if(oof) + to_chat(owner, "You get the feeling this is a bad idea.") + ..() + +/datum/action/innate/cult/blood_spell/emp/Activate() + owner.visible_message("[owner]'s body flashes a bright blue!", \ + "You speak the cursed words, channeling an electromagnetic pulse from your body.") + owner.emp_act(2) + empulse(owner, 2, 5, cause = "cult") + owner.whisper(invocation) + charges-- + if(charges <= 0) + qdel(src) + +/datum/action/innate/cult/blood_spell/shackles + name = "Shadow Shackles" + desc = "Empowers your hand to start handcuffing victim on contact, and mute them if successful." + button_icon_state = "shackles" + charges = 4 + magic_path = /obj/item/melee/blood_magic/shackles + +/datum/action/innate/cult/blood_spell/construction + name = "Twisted Construction" + desc = "Empowers your hand to corrupt certain metalic objects.
Converts:
Plasteel into runed metal
50 metal into a construct shell
Cyborg shells into construct shells
Airlocks into brittle runed airlocks after a delay (harm intent)" + button_icon_state = "transmute" + magic_path = "/obj/item/melee/blood_magic/construction" + health_cost = 12 + +/datum/action/innate/cult/blood_spell/dagger + name = "Summon Dagger" + desc = "Summon a ritual dagger, necessary to scribe runes." + button_icon_state = "cult_dagger" + +/datum/action/innate/cult/blood_spell/dagger/New() + if(SSticker.mode) + button_icon_state = SSticker.cultdat.dagger_icon + ..() + +/datum/action/innate/cult/blood_spell/dagger/Activate() + var/turf/T = get_turf(owner) + owner.visible_message("[owner]'s hand glows red for a moment.", \ + "Red light begins to shimmer and take form within your hand!") + var/obj/item/melee/cultblade/dagger/O = new(T) + if(owner.put_in_hands(O)) + to_chat(owner, "A [O.name] appears in your hand!") + else + owner.visible_message("A [O.name] appears at [owner]'s feet!", \ + "A [O.name] materializes at your feet.") + playsound(owner, 'sound/magic/cult_spell.ogg', 25, TRUE) + charges-- + desc = base_desc + desc += "
Has [charges] use\s remaining." + if(charges <= 0) + qdel(src) + +/datum/action/innate/cult/blood_spell/equipment + name = "Summon Equipment" + desc = "Allows you to empower your hand to summon combat gear onto a cultist you touch, including cult armor, a cult bola, and a cult sword." + button_icon_state = "equip" + magic_path = /obj/item/melee/blood_magic/armor + +/datum/action/innate/cult/blood_spell/horror + name = "Hallucinations" + desc = "Gives hallucinations to a target at range. A silent and invisible spell." + button_icon_state = "horror" + var/obj/effect/proc_holder/horror/PH + charges = 4 + +/datum/action/innate/cult/blood_spell/horror/New() + PH = new() + PH.attached_action = src + ..() + +/datum/action/innate/cult/blood_spell/horror/Destroy() + var/obj/effect/proc_holder/horror/destroy = PH + . = ..() + if(destroy && !QDELETED(destroy)) + QDEL_NULL(destroy) + +/datum/action/innate/cult/blood_spell/horror/Activate() + PH.toggle(owner) //the important bit + return TRUE + +/obj/effect/proc_holder/horror + active = FALSE + ranged_mousepointer = 'icons/effects/cult_target.dmi' + var/datum/action/innate/cult/blood_spell/attached_action + +/obj/effect/proc_holder/horror/Destroy() + var/datum/action/innate/cult/blood_spell/AA = attached_action + . = ..() + if(AA && !QDELETED(AA)) + QDEL_NULL(AA) + +/obj/effect/proc_holder/horror/proc/toggle(mob/user) + if(active) + remove_ranged_ability(user, "You dispel the magic...") + else + add_ranged_ability(user, "You prepare to horrify a target...") + +/obj/effect/proc_holder/horror/InterceptClickOn(mob/living/user, params, atom/target) + if(..()) + return + if(ranged_ability_user.incapacitated() || !iscultist(user)) + user.ranged_ability.remove_ranged_ability(user) + return + var/turf/T = get_turf(ranged_ability_user) + if(!isturf(T)) + return FALSE + if(target in view(7, ranged_ability_user)) + if(!ishuman(target) || iscultist(target)) + return + var/mob/living/carbon/human/H = target + H.hallucination = max(H.hallucination, 120) + attached_action.charges-- + attached_action.desc = attached_action.base_desc + attached_action.desc += "
Has [attached_action.charges] use\s remaining." + attached_action.UpdateButtonIcon() + user.ranged_ability.remove_ranged_ability(user, "[H] has been cursed with living nightmares!") + if(attached_action.charges <= 0) + to_chat(ranged_ability_user, "You have exhausted the spell's power!") + qdel(src) + +/datum/action/innate/cult/blood_spell/veiling + name = "Conceal Presence" + desc = "Alternates between hiding and revealing nearby cult structures, cult airlocks and runes." + invocation = "Kla'atu barada nikt'o!" + button_icon_state = "veiling" + charges = 10 + var/revealing = FALSE //if it reveals or not + +/datum/action/innate/cult/blood_spell/veiling/Activate() + if(!revealing) // Hiding stuff + owner.visible_message("Thin grey dust falls from [owner]'s hand!", \ + "You invoke the veiling spell, hiding nearby runes and cult structures.") + charges-- + playsound(owner, 'sound/magic/smoke.ogg', 25, TRUE) + owner.whisper(invocation) + for(var/obj/O in range(4, owner)) + O.cult_conceal() + revealing = TRUE // Switch on use + name = "Reveal Runes" + button_icon_state = "revealing" + + else // Unhiding stuff + owner.visible_message("A flash of light shines from [owner]'s hand!", \ + "You invoke the counterspell, revealing nearby runes and cult structures.") + charges-- + owner.whisper(invocation) + playsound(owner, 'sound/misc/enter_blood.ogg', 25, TRUE) + for(var/obj/O in range(5, owner)) // Slightly higher in case we arent in the exact same spot + O.cult_reveal() + revealing = FALSE // Switch on use + name = "Conceal Runes" + button_icon_state = "veiling" + if(charges <= 0) + qdel(src) + desc = base_desc + desc += "
Has [charges] use\s remaining." + UpdateButtonIcon() + +/datum/action/innate/cult/blood_spell/manipulation + name = "Blood Rites" + desc = "Empowers your hand to manipulate blood. Use on blood or a noncultist to absorb blood to be used later, use on yourself or another cultist to heal them using absorbed blood. \ + \nUse the spell in-hand to cast advanced rites, such as summoning a magical blood spear or firing blood projectiles out of your hands." + invocation = "Fel'th Dol Ab'orod!" + button_icon_state = "manip" + charges = 5 + magic_path = /obj/item/melee/blood_magic/manipulator + +// The "magic hand" items +/obj/item/melee/blood_magic + name = "\improper magical aura" + desc = "A sinister looking aura that distorts the flow of reality around it." + icon = 'icons/obj/items.dmi' + lefthand_file = 'icons/mob/inhands/items_lefthand.dmi' + righthand_file = 'icons/mob/inhands/items_righthand.dmi' + icon_state = "disintegrate" + item_state = "disintegrate" + flags = ABSTRACT | DROPDEL + + w_class = WEIGHT_CLASS_HUGE + throwforce = 0 + throw_range = 0 + throw_speed = 0 + var/invocation + var/uses = 1 + var/health_cost = 0 //The amount of health taken from the user when invoking the spell + var/datum/action/innate/cult/blood_spell/source + +/obj/item/melee/blood_magic/New(loc, spell) + source = spell + uses = source.charges + health_cost = source.health_cost + ..() + +/obj/item/melee/blood_magic/Destroy() + if(!QDELETED(source)) + if(uses <= 0) + source.hand_magic = null + qdel(source) + source = null + else + source.hand_magic = null + source.charges = uses + source.desc = source.base_desc + source.desc += "
Has [uses] use\s remaining." + source.UpdateButtonIcon() + ..() + +/obj/item/melee/blood_magic/attack_self(mob/living/user) + afterattack(user, user, TRUE) + +/obj/item/melee/blood_magic/attack(mob/living/M, mob/living/carbon/user) + if(!iscarbon(user) || !iscultist(user)) + uses = 0 + qdel(src) + return + add_attack_logs(user, M, "used a cult spell ([src]) on") + M.lastattacker = user.real_name + +/obj/item/melee/blood_magic/afterattack(atom/target, mob/living/carbon/user, proximity) + . = ..() + if(invocation) + user.whisper(invocation) + if(health_cost && ishuman(user)) + user.cult_self_harm(health_cost) + if(uses <= 0) + qdel(src) + else if(source) + source.desc = source.base_desc + source.desc += "
Has [uses] use\s remaining." + source.UpdateButtonIcon() + +//The spell effects + +//stun +/obj/item/melee/blood_magic/stun + name = "Stunning Aura" + desc = "Will stun and mute a victim on contact." + color = RUNE_COLOR_RED + invocation = "Fuu ma'jin!" + +/obj/item/melee/blood_magic/stun/afterattack(atom/target, mob/living/carbon/user, proximity) + if(!isliving(target) || !proximity) + return + var/mob/living/L = target + if(iscultist(target)) + return + user.visible_message("[user] holds up [user.p_their()] hand, which explodes in a flash of red light!", \ + "You attempt to stun [L] with the spell!") + + user.mob_light(LIGHT_COLOR_BLOOD_MAGIC, 3, _duration = 2) + + var/obj/item/nullrod/N = locate() in target + if(N) + target.visible_message("[target]'s holy weapon absorbs the red light!", \ + "Your holy weapon absorbs the blinding light!") + else + to_chat(user, "In a brilliant flash of red, [L] falls to the ground!") + // These are in life cycles, so double the time that's stated. + L.Weaken(5) + L.Stun(5) + L.flash_eyes(1, TRUE) + if(issilicon(target)) + var/mob/living/silicon/S = L + S.emp_act(EMP_HEAVY) + else if(iscarbon(target)) + var/mob/living/carbon/C = L + C.Silence(3) + C.Stuttering(8) + C.CultSlur(10) + C.Jitter(8) + uses-- + ..() + + +//Teleportation +/obj/item/melee/blood_magic/teleport + name = "Teleporting Aura" + color = RUNE_COLOR_TELEPORT + desc = "Will teleport a cultist to a teleport rune on contact." + invocation = "Sas'so c'arta forbici!" + +/obj/item/melee/blood_magic/teleport/afterattack(atom/target, mob/living/carbon/user, proximity) + var/list/potential_runes = list() + var/list/teleportnames = list() + var/list/duplicaterunecount = list() + if(!iscultist(target) || !proximity) + to_chat(user, "You can only teleport adjacent cultists with this spell!") + return + for(var/R in GLOB.teleport_runes) + var/obj/effect/rune/teleport/T = R + var/resultkey = T.listkey + if(resultkey in teleportnames) + duplicaterunecount[resultkey]++ + resultkey = "[resultkey] ([duplicaterunecount[resultkey]])" + else + teleportnames.Add(resultkey) + duplicaterunecount[resultkey] = 1 + potential_runes[resultkey] = T + + if(!length(potential_runes)) + to_chat(user, "There are no valid runes to teleport to!") + log_game("Teleport spell failed - no other teleport runes") + return + if(!is_level_reachable(user.z)) + to_chat(user, "You are not in the right dimension!") + log_game("Teleport spell failed - user in away mission") + return + + var/mob/living/L = target + var/input_rune_key = input(user, "Choose a rune to teleport to.", "Rune to Teleport to") as null|anything in potential_runes //we know what key they picked + var/obj/effect/rune/teleport/actual_selected_rune = potential_runes[input_rune_key] //what rune does that key correspond to? + if(!src || QDELETED(src) || !user || user.l_hand != src && user.r_hand != src || user.incapacitated() || !actual_selected_rune) + return + uses-- + + var/turf/origin = get_turf(user) + var/turf/destination = get_turf(actual_selected_rune) + INVOKE_ASYNC(actual_selected_rune, /obj/effect/rune/.proc/teleport_effect, user, origin, destination) + + if(is_mining_level(user.z) && !is_mining_level(destination.z)) //No effect if you stay on lavaland + actual_selected_rune.handle_portal("lava") + else if(!is_station_level(user.z) || istype(get_area(user), /area/space)) + actual_selected_rune.handle_portal("space", origin) + + if(user == target) + target.visible_message("Dust flows from [user]'s hand, and [user.p_they()] disappear[user.p_s()] in a flash of red light!", \ + "You speak the words and find yourself somewhere else!") + else + target.visible_message("Dust flows from [user]'s hand, and [target] disappears in a flash of red light!", \ + "You suddenly find yourself somewhere else!") + destination.visible_message("There is a boom of outrushing air as something appears above the rune!", null, "You hear a boom.") + L.forceMove(destination) + return ..() + +//Shackles +/obj/item/melee/blood_magic/shackles + name = "Shackling Aura" + desc = "Will start handcuffing a victim on contact, and mute them for a short duration if successful." + invocation = "In'totum Lig'abis!" + color = "#000000" // black + +/obj/item/melee/blood_magic/shackles/afterattack(atom/target, mob/living/carbon/user, proximity) + if(iscarbon(target) && proximity) + var/mob/living/carbon/C = target + if(C.canBeHandcuffed() || C.get_arm_ignore()) + CuffAttack(C, user) + else + user.visible_message("This victim doesn't have enough arms to complete the restraint!") + return + ..() + +/obj/item/melee/blood_magic/shackles/proc/CuffAttack(mob/living/carbon/C, mob/living/user) + if(!C.handcuffed) + playsound(loc, 'sound/weapons/cablecuff.ogg', 30, TRUE, -2) + C.visible_message("[user] begins restraining [C] with dark magic!", \ + "[user] begins shaping dark magic shackles around your wrists!") + if(do_mob(user, C, 30)) + if(!C.handcuffed) + C.handcuffed = new /obj/item/restraints/handcuffs/energy/cult/used(C) + C.update_handcuffed() + C.Silence(6) + to_chat(user, "You shackle [C].") + add_attack_logs(user, C, "shackled") + uses-- + else + to_chat(user, "[C] is already bound.") + else + to_chat(user, "You fail to shackle [C].") + else + to_chat(user, "[C] is already bound.") + + +/obj/item/restraints/handcuffs/energy/cult //For the shackling spell + name = "shadow shackles" + desc = "Shackles that bind the wrists with sinister magic." + trashtype = /obj/item/restraints/handcuffs/energy/used + flags = DROPDEL + +/obj/item/restraints/handcuffs/energy/cult/used/dropped(mob/user) + user.visible_message("[user]'s shackles shatter in a discharge of dark magic!", \ + "Your [name] shatter in a discharge of dark magic!") + . = ..() + + +//Construction: Converts 50 metal to a construct shell, plasteel to runed metal, or an airlock to brittle runed airlock +/obj/item/melee/blood_magic/construction + name = "Twisting Aura" + desc = "Corrupts certain metalic objects on contact." + invocation = "Ethra p'ni dedol!" + color = "#000000" // black + var/channeling = FALSE + +/obj/item/melee/blood_magic/construction/examine(mob/user) + . = ..() + . += {"A sinister spell used to convert:\n + Plasteel into runed metal\n + [METAL_TO_CONSTRUCT_SHELL_CONVERSION] metal into a construct shell\n + Airlocks into brittle runed airlocks after a delay (harm intent)"} + +/obj/item/melee/blood_magic/construction/afterattack(atom/target, mob/user, proximity_flag, click_parameters) + if(proximity_flag) + if(channeling) + to_chat(user, "You are already invoking twisted construction!") + return + var/turf/T = get_turf(target) + + //Metal to construct shell + if(istype(target, /obj/item/stack/sheet/metal)) + var/obj/item/stack/sheet/candidate = target + if(candidate.use(METAL_TO_CONSTRUCT_SHELL_CONVERSION)) + uses-- + to_chat(user, "A dark cloud emanates from your hand and swirls around the metal, twisting it into a construct shell!") + new /obj/structure/constructshell(T) + playsound(user, 'sound/magic/cult_spell.ogg', 25, TRUE) + else + to_chat(user, "You need [METAL_TO_CONSTRUCT_SHELL_CONVERSION] metal to produce a construct shell!") + return + + //Plasteel to runed metal + else if(istype(target, /obj/item/stack/sheet/plasteel)) + var/obj/item/stack/sheet/plasteel/candidate = target + var/quantity = candidate.amount + if(candidate.use(quantity)) + uses-- + new /obj/item/stack/sheet/runed_metal(T, quantity) + to_chat(user, "A dark cloud emanates from you hand and swirls around the plasteel, transforming it into runed metal!") + playsound(user, 'sound/magic/cult_spell.ogg', 25, TRUE) + + //Airlock to cult airlock + else if(istype(target, /obj/machinery/door/airlock) && !istype(target, /obj/machinery/door/airlock/cult)) + channeling = TRUE + playsound(T, 'sound/machines/airlockforced.ogg', 50, TRUE) + do_sparks(5, TRUE, target) + if(do_after(user, 50, target = target)) + target.narsie_act(TRUE) + uses-- + user.visible_message("Black ribbons suddenly emanate from [user]'s hand and cling to the airlock - twisting and corrupting it!") + playsound(user, 'sound/magic/cult_spell.ogg', 25, TRUE) + channeling = FALSE + else + channeling = FALSE + return + else + to_chat(user, "The spell will not work on [target]!") + return + ..() + +//Armor: Gives the target a basic cultist combat loadout +/obj/item/melee/blood_magic/armor + name = "Arming Aura" + desc = "Will equipt cult combat gear onto a cultist on contact." + color = "#33cc33" // green + +/obj/item/melee/blood_magic/armor/afterattack(atom/target, mob/living/carbon/user, proximity) + if(iscarbon(target) && proximity) + uses-- + var/mob/living/carbon/C = target + var/armour = C.equip_to_slot_or_del(new /obj/item/clothing/suit/hooded/cultrobes/alt(user), slot_wear_suit) + C.equip_to_slot_or_del(new /obj/item/clothing/under/color/black(user), slot_w_uniform) + C.equip_to_slot_or_del(new /obj/item/storage/backpack/cultpack(user), slot_back) + C.equip_to_slot_or_del(new /obj/item/clothing/shoes/cult(user), slot_shoes) + + if(C == user) + qdel(src) //Clears the hands + C.put_in_hands(new /obj/item/melee/cultblade(user)) + C.put_in_hands(new /obj/item/restraints/legcuffs/bola/cult(user)) + C.visible_message("Otherworldly [armour ? "armour" : "equipment"] suddenly appears on [C]!") + ..() + +//Blood Rite: Absorb blood to heal cult members or summon weapons +/obj/item/melee/blood_magic/manipulator + name = "Blood Rite Aura" + desc = "Absorbs blood from anything you touch. Touching cultists and constructs can heal them. Use in-hand to cast an advanced rite." + color = "#7D1717" + +/obj/item/melee/blood_magic/manipulator/examine(mob/user) + . = ..() + . += "Blood spear and blood barrage cost [BLOOD_SPEAR_COST] and [BLOOD_BARRAGE_COST] charges respectively." + . += "You have collected [uses] charge\s of blood." + +// This should really be split into multiple procs +/obj/item/melee/blood_magic/manipulator/afterattack(atom/target, mob/living/carbon/human/user, proximity) + if(proximity) + if(ishuman(target)) + var/mob/living/carbon/human/H = target + + //Healing a cultist + if(iscultist(H)) + var/charge_loss = uses // Before/after charge difference + if(H.stat == DEAD) + to_chat(user, "Only a revive rune can bring back the dead!") + return + + //Blood restoration + if(H.dna && !(NO_BLOOD in H.dna.species.species_traits) && H.dna.species.exotic_blood == null) + if(H.blood_volume < BLOOD_VOLUME_SAFE) + var/restore_blood = BLOOD_VOLUME_SAFE - H.blood_volume + if(uses * 2 < restore_blood) + H.blood_volume += uses * 2 + to_chat(user, "You use the last of your charges to restore what blood you could, and the spell dissipates!") + uses = 0 + return ..() + else + H.blood_volume = BLOOD_VOLUME_SAFE + uses -= round(restore_blood / 2) + to_chat(user, "Your blood rites have restored [H == user ? "your" : "[H.p_their()]"] blood to safe levels!") + + //Damage healing + var/overall_damage = H.getBruteLoss() + H.getFireLoss() + H.getToxLoss() + H.getOxyLoss() + if(overall_damage == 0) + to_chat(user, "That cultist doesn't require healing!") + return + else + var/ratio = uses / overall_damage + if(H == user) + to_chat(user, "Your blood healing is far less efficient when used on yourself!") + ratio *= 0.35 // Healing is half as effective if you can't perform a full heal + uses -= round(overall_damage) // Healing is 65% more "expensive" even if you can still perform the full heal + if(ratio > 1) + ratio = 1 + uses -= round(overall_damage) + H.visible_message("[H] is fully healed by [H == user ? "[H.p_their()]" : "[H]'s"] blood magic!", + "You are fully healed by [H == user ? "your" : "[user]'s"] blood magic!") + else + H.visible_message("[H] is partially healed by [H == user ? "[H.p_their()]" : "[H]'s"] blood magic.", + "You are partially healed by [H == user ? "your" : "[user]'s"] blood magic.") + uses = 0 + ratio *= -1 + H.adjustOxyLoss((overall_damage * ratio) * (H.getOxyLoss() / overall_damage), FALSE, null, TRUE) + H.adjustToxLoss((overall_damage * ratio) * (H.getToxLoss() / overall_damage), FALSE, null, TRUE) + H.adjustFireLoss((overall_damage * ratio) * (H.getFireLoss() / overall_damage), FALSE, null, TRUE) + H.adjustBruteLoss((overall_damage * ratio) * (H.getBruteLoss() / overall_damage), FALSE, null, TRUE) + H.updatehealth() + playsound(get_turf(H), 'sound/magic/staff_healing.ogg', 25) + new /obj/effect/temp_visual/cult/sparks(get_turf(H)) + user.Beam(H, icon_state="sendbeam", time = 15) + + charge_loss = charge_loss - uses + if(!uses) + to_chat(user, "You use the last of your charges to heal [H == user ? "yourself" : "[H]"], and the spell dissipates!") + else + to_chat(user, "You use [charge_loss] charge\s, and have [uses] remaining.") + + //Draining blood from non-cultists + else + if(H.stat == DEAD) + to_chat(user, "[H.p_their(TRUE)] blood has stopped flowing, you'll have to find another way to extract it.") + return + if(H.cultslurring) + to_chat(user, "[H.p_their(TRUE)] blood has been tainted by an even stronger form of blood magic, it's no use to us like this!") + return + if(H.dna && !(NO_BLOOD in H.dna.species.species_traits) && H.dna.species.exotic_blood == null) + if(H.blood_volume > BLOOD_VOLUME_SAFE) + H.blood_volume -= 100 + uses += 50 + user.Beam(H, icon_state = "drainbeam", time = 10) + playsound(get_turf(H), 'sound/misc/enter_blood.ogg', 50) + H.visible_message("[user] has drained some of [H]'s blood!", + "[user] has drained some of your blood!") + to_chat(user, "Your blood rite gains 50 charges from draining [H]'s blood.") + new /obj/effect/temp_visual/cult/sparks(get_turf(H)) + else + to_chat(user, "[H] is missing too much blood - you cannot drain [H.p_them()] further!") + return + else + to_chat(user, "[H] does not have any usable blood!") + return + + //Healing constructs + if(isconstruct(target)) + var/mob/living/simple_animal/M = target + var/missing = M.maxHealth - M.health + if(missing) + if(uses > missing) + M.adjustHealth(-missing) + M.visible_message("[M] is fully healed by [user]'s blood magic!", + "You are fully healed by [user]'s blood magic!") + uses -= missing + else + M.adjustHealth(-uses) + M.visible_message("[M] is partially healed by [user]'s blood magic!", + "You are partially healed by [user]'s blood magic.") + uses = 0 + playsound(get_turf(M), 'sound/magic/staff_healing.ogg', 25) + user.Beam(M, icon_state = "sendbeam", time = 10) + + //Draining blood on the floor + if(istype(target, /obj/effect/decal/cleanable/blood) || istype(target, /obj/effect/decal/cleanable/trail_holder)) + blood_draw(target, user) + ..() + +/obj/item/melee/blood_magic/manipulator/proc/blood_draw(atom/target, mob/living/carbon/human/user) + var/temp = 0 + var/turf/T = get_turf(target) + if(T) + for(var/obj/effect/decal/cleanable/blood/B in view(T, 2)) + if(B.blood_state == BLOOD_STATE_HUMAN && (B.can_bloodcrawl_in() || istype(B, /obj/effect/decal/cleanable/blood/slime))) + if(B.bloodiness == 100) //Bonus for "pristine" bloodpools, also to prevent cheese with footprint spam + temp += 30 + else + temp += max((B.bloodiness ** 2) / 800, 1) + new /obj/effect/temp_visual/cult/turf/open/floor(get_turf(B)) + qdel(B) + for(var/obj/effect/decal/cleanable/trail_holder/TH in view(T, 2)) + qdel(TH) + if(temp) + user.Beam(T, icon_state = "drainbeam", time = 15) + new /obj/effect/temp_visual/cult/sparks(get_turf(user)) + playsound(T, 'sound/misc/enter_blood.ogg', 50) + temp = round(temp) + to_chat(user, "Your blood rite has gained [temp] charge\s from blood sources around you!") + uses += max(1, temp) + +/obj/item/melee/blood_magic/manipulator/attack_self(mob/living/user) + var/list/options = list("Blood Spear (150)", "Blood Bolt Barrage (300)") + var/choice = input(user, "Choose a greater blood rite...", "Greater Blood Rites") as null|anything in options + switch(choice) + if("Blood Spear (150)") + if(uses < BLOOD_SPEAR_COST) + to_chat(user, "You need [BLOOD_SPEAR_COST] charges to perform this rite.") + else + uses -= BLOOD_SPEAR_COST + var/turf/T = get_turf(user) + qdel(src) + var/datum/action/innate/cult/spear/S = new(user) + var/obj/item/twohanded/cult_spear/rite = new(T) + S.Grant(user, rite) + rite.spear_act = S + if(user.put_in_hands(rite)) + to_chat(user, "A [rite.name] appears in your hand!") + else + user.visible_message("A [rite.name] appears at [user]'s feet!", \ + "A [rite.name] materializes at your feet.") + + if("Blood Bolt Barrage (300)") + if(uses < BLOOD_BARRAGE_COST) + to_chat(user, "You need [BLOOD_BARRAGE_COST] charges to perform this rite.") + else + var/obj/rite = new /obj/item/gun/projectile/shotgun/boltaction/enchanted/arcane_barrage/blood() + uses -= BLOOD_BARRAGE_COST + qdel(src) + user.swap_hand() + user.drop_item() + if(user.put_in_hands(rite)) + to_chat(user, "Both of your hands glow with power!") + else + to_chat(user, "You need a free hand for this rite!") + uses += BLOOD_BARRAGE_COST // Refund the charges + qdel(rite) diff --git a/code/game/gamemodes/cult/cult.dm b/code/game/gamemodes/cult/cult.dm index 433fc5b526a35..a4b5dc06850d3 100644 --- a/code/game/gamemodes/cult/cult.dm +++ b/code/game/gamemodes/cult/cult.dm @@ -1,20 +1,33 @@ GLOBAL_LIST_EMPTY(all_cults) /datum/game_mode + /// A list of all minds currently in the cult var/list/datum/mind/cult = list() - -/proc/iscultist(mob/living/M as mob) + var/datum/cult_objectives/cult_objs = new + /// Does the cult have glowing eyes + var/cult_risen = FALSE + /// Does the cult have halos + var/cult_ascendant = FALSE + /// How many crew need to be converted to rise + var/rise_number + /// How many crew need to be converted to ascend + var/ascend_number + /// Used for the CentComm announcement at ascension + var/ascend_percent + +/proc/iscultist(mob/living/M) return istype(M) && M.mind && SSticker && SSticker.mode && (M.mind in SSticker.mode.cult) - /proc/is_convertable_to_cult(datum/mind/mind) if(!mind) return FALSE if(!mind.current) return FALSE + if(is_sacrifice_target(mind)) + return FALSE if(iscultist(mind.current)) return TRUE //If they're already in the cult, assume they are convertable - if(ishuman(mind.current) && (mind.assigned_role in list("Captain", "Chaplain"))) + if(mind.isholy) return FALSE if(ishuman(mind.current)) var/mob/living/carbon/human/H = mind.current @@ -32,13 +45,6 @@ GLOBAL_LIST_EMPTY(all_cults) return FALSE return TRUE -/proc/is_sacrifice_target(datum/mind/mind) - if(SSticker.mode.name == "cult") - var/datum/game_mode/cult/cult_mode = SSticker.mode - if(mind == cult_mode.sacrifice_target) - return 1 - return 0 - /datum/game_mode/cult name = "cult" config_tag = "cult" @@ -48,185 +54,242 @@ GLOBAL_LIST_EMPTY(all_cults) required_enemies = 3 recommended_enemies = 4 - var/datum/mind/sacrifice_target = null - var/finished = 0 - - - var/list/objectives = list() - - var/eldergod = 1 //for the summon god objective - var/demons_summoned = 0 - - var/acolytes_needed = 4 //for the survive objective - base number of acolytes, increased by 1 for every 10 players var/const/min_cultists_to_start = 3 var/const/max_cultists_to_start = 4 - var/acolytes_survived = 0 - - var/narsie_condition_cleared = 0 //allows Nar-Sie to be summonned during cult rounds. set to 1 once the cult reaches the second phase. - var/current_objective = 1 //equals the number of cleared objectives + 1 - var/prenarsie_objectives = 2 //how many objectives at most before the cult gets to summon narsie - var/list/bloody_floors = list() - var/spilltarget = 100 //how many floor tiles must be covered in blood to complete the bloodspill objective - var/convert_target = 0 //how many members the cult needs to reach to complete the convert objective - var/harvested = 0 - - var/list/sacrificed = list() //contains the mind of the sacrifice target ONCE the sacrifice objective has been completed - var/mass_convert = 0 //set to 1 if the convert objective has been accomplised once that round - var/spilled_blood = 0 //set to 1 if the bloodspill objective has been accomplised once that round - var/max_spilled_blood = 0 //highest quantity of blood covered tiles during the round - var/bonus = 0 //set to 1 if the cult has completed the bonus (third phase) objective (harvest, hijack, massacre) - - var/harvest_target = 10 - var/massacre_target = 5 - - var/escaped_shuttle = 0 - var/escaped_pod = 0 - var/survivors = 0 /datum/game_mode/cult/announce() - to_chat(world, "The current game mode is - Cult!") - to_chat(world, "Some crew members are attempting to start a cult!
\nCultists - complete your objectives. Convert crew members to your cause by using the convert rune. Remember - there is no you, there is only the cult.
\nPersonnel - Do not let the cult succeed in its mission. Brainwashing them with the chaplain's bible reverts them to whatever CentComm-allowed faith they had.
") - + to_chat(world, "The current game mode is - Cult!") + to_chat(world, "Some crewmembers are attempting to start a cult!
\nCultists - complete your objectives. Convert crewmembers to your cause by using the offer rune. Remember - there is no you, there is only the cult.
\nPersonnel - Do not let the cult succeed in its mission. Brainwashing them with holy water reverts them to whatever CentComm-allowed faith they had.
") /datum/game_mode/cult/pre_setup() if(config.protect_roles_from_antagonist) restricted_jobs += protected_jobs - ..() - var/list/cultists_possible = get_players_for_role(ROLE_CULTIST) + var/list/cultists_possible = get_players_for_role(ROLE_CULTIST) for(var/cultists_number = 1 to max_cultists_to_start) - if(!cultists_possible.len) + if(!length(cultists_possible)) break var/datum/mind/cultist = pick(cultists_possible) cultists_possible -= cultist cult += cultist cultist.restricted_roles = restricted_jobs cultist.special_role = SPECIAL_ROLE_CULTIST - ..() - return (cult.len>0) - + return (length(cult) > 0) /datum/game_mode/cult/post_setup() modePlayer += cult - acolytes_needed = acolytes_needed + round((num_players_started() / 10)) - - if(!GLOB.summon_spots.len) - while(GLOB.summon_spots.len < SUMMON_POSSIBILITIES) - var/area/summon = pick(return_sorted_areas() - GLOB.summon_spots) - if(summon && is_station_level(summon.z) && summon.valid_territory) - GLOB.summon_spots += summon + cult_objs.setup() for(var/datum/mind/cult_mind in cult) SEND_SOUND(cult_mind.current, 'sound/ambience/antag/bloodcult.ogg') + to_chat(cult_mind.current, CULT_GREETING) equip_cultist(cult_mind.current) cult_mind.current.faction |= "cult" - var/datum/action/innate/cultcomm/C = new() - C.Grant(cult_mind.current) - update_cult_icons_added(cult_mind) - to_chat(cult_mind.current, "You catch a glimpse of the Realm of [SSticker.cultdat.entity_name], [SSticker.cultdat.entity_title3]. You now see how flimsy the world is, you see that it should be open to the knowledge of [SSticker.cultdat.entity_name].") - first_phase() + if(cult_mind.assigned_role == "Clown") + to_chat(cult_mind.current, "A dark power has allowed you to overcome your clownish nature, letting you wield weapons without harming yourself.") + cult_mind.current.mutations.Remove(CLUMSY) + var/datum/action/innate/toggle_clumsy/A = new + A.Grant(cult_mind.current) + add_cult_actions(cult_mind) + update_cult_icons_added(cult_mind) + cult_objs.study(cult_mind.current) + threshold_check() + addtimer(CALLBACK(src, .proc/threshold_check), 2 MINUTES) // Check again in 2 minutes for latejoiners ..() +/** + * Decides at the start of the round how many conversions are needed to rise/ascend. + * + * The number is decided by (Percentage * (Players - Cultists)), so for example at 110 players it would be 11 conversions for rise. (0.1 * (110 - 4)) + * These values change based on population because 20 cultists are MUCH more powerful if there's only 50 players, compared to 120. + * + * Below 100 players, [CULT_RISEN_LOW] and [CULT_ASCENDANT_LOW] are used. + * Above 100 players, [CULT_RISEN_HIGH] and [CULT_ASCENDANT_HIGH] are used. + */ +/datum/game_mode/cult/proc/threshold_check() + var/players = length(GLOB.player_list) + var/cultists = get_cultists() // Don't count the starting cultists towards the number of needed conversions + if(players >= CULT_POPULATION_THRESHOLD) + // Highpop + ascend_percent = CULT_ASCENDANT_HIGH + rise_number = round(CULT_RISEN_HIGH * (players - cultists)) + ascend_number = round(CULT_ASCENDANT_HIGH * (players - cultists)) + else + // Lowpop + ascend_percent = CULT_ASCENDANT_LOW + rise_number = round(CULT_RISEN_LOW * (players - cultists)) + ascend_number = round(CULT_ASCENDANT_LOW * (players - cultists)) + +/** + * Returns the current number of cultists and constructs. + * + * Returns the number of cultists and constructs in a list ([1] = Cultists, [2] = Constructs), or as one combined number. + * + * * separate - Should the number be returned in two separate values (Humans and Constructs) or as one? + */ +/datum/game_mode/proc/get_cultists(separate = FALSE) + var/cultists = 0 + var/constructs = 0 + for(var/I in cult) + var/datum/mind/M = I + if(ishuman(M.current) && !M.current.has_status_effect(STATUS_EFFECT_SUMMONEDGHOST)) + cultists++ + else if(isconstruct(M.current)) + constructs++ + if(separate) + return list(cultists, constructs) + else + return cultists + constructs -/datum/game_mode/cult/proc/memorize_cult_objectives(datum/mind/cult_mind) - for(var/obj_count in 1 to objectives.len) - var/explanation - switch(objectives[obj_count]) - if("survive") - explanation = "Our knowledge must live on. Make sure at least [acolytes_needed] acolytes escape on the shuttle to spread their work on an another station." - if("convert") - explanation = "We must increase our influence before we can summon [SSticker.cultdat.entity_name], Convert [convert_target] crew members. Take it slowly to avoid raising suspicions." - if("bloodspill") - explanation = "We must prepare this place for [SSticker.cultdat.entity_title1]'s coming. Spill blood and gibs over [spilltarget] floor tiles." - if("sacrifice") - if(sacrifice_target) - explanation = "Sacrifice [sacrifice_target.current.real_name], the [sacrifice_target.assigned_role]. You will need the sacrifice rune and three acolytes to do so." - else - explanation = "Free objective." - if("eldergod") - explanation = "Summon [SSticker.cultdat.entity_name] by invoking the 'Tear Reality' rune.The summoning can only be accomplished in [english_list(GLOB.summon_spots)] - where the veil is weak enough for the ritual to begin." - to_chat(cult_mind.current, "Objective #[obj_count]: [explanation]") - cult_mind.memory += "Objective #[obj_count]: [explanation]
" - - -/datum/game_mode/proc/equip_cultist(mob/living/carbon/human/mob) - if(!istype(mob)) +/datum/game_mode/proc/equip_cultist(mob/living/carbon/human/H, metal = TRUE) + if(!istype(H)) return + . += cult_give_item(/obj/item/melee/cultblade/dagger, H) + if(metal) + . += cult_give_item(/obj/item/stack/sheet/runed_metal/ten, H) + to_chat(H, "These will help you start the cult on this station. Use them well, and remember - you are not the only one.") - if(mob.mind) - if(mob.mind.assigned_role == "Clown") - to_chat(mob, "Your training has allowed you to overcome your clownish nature, allowing you to wield weapons without harming yourself.") - mob.mutations.Remove(CLUMSY) - var/datum/action/innate/toggle_clumsy/A = new - A.Grant(mob) - var/obj/item/paper/talisman/supply/T = new(mob) - var/list/slots = list ( +/datum/game_mode/proc/cult_give_item(obj/item/item_path, mob/living/carbon/human/H) + var/list/slots = list( "backpack" = slot_in_backpack, "left pocket" = slot_l_store, - "right pocket" = slot_r_store, - "left hand" = slot_l_hand, - "right hand" = slot_r_hand, - ) - var/where = mob.equip_in_one_of_slots(T, slots) + "right pocket" = slot_r_store) + 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(mob, "Unfortunately, you weren't able to get a talisman. This is very bad and you should adminhelp immediately.") + 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(mob, "You have a talisman in your [where], one that will help you start the cult on this station. Use it well and remember - there are others.") - mob.update_icons() - return 1 + to_chat(H, "You have a [item_name] in your [where].") + return TRUE -/datum/game_mode/proc/add_cultist(datum/mind/cult_mind) //BASE +/datum/game_mode/proc/add_cultist(datum/mind/cult_mind) if(!istype(cult_mind)) - return 0 - var/datum/game_mode/cult/cult_mode = SSticker.mode + return FALSE + if(!(cult_mind in cult)) cult += cult_mind cult_mind.current.faction |= "cult" - var/datum/action/innate/cultcomm/C = new() - C.Grant(cult_mind.current) + cult_mind.special_role = SPECIAL_ROLE_CULTIST + + if(cult_mind.assigned_role == "Clown") + to_chat(cult_mind.current, "A dark power has allowed you to overcome your clownish nature, letting you wield weapons without harming yourself.") + cult_mind.current.mutations.Remove(CLUMSY) + var/datum/action/innate/toggle_clumsy/A = new + A.Grant(cult_mind.current) SEND_SOUND(cult_mind.current, 'sound/ambience/antag/bloodcult.ogg') cult_mind.current.create_attack_log("Has been converted to the cult!") cult_mind.current.create_log(CONVERSION_LOG, "converted to the cult") + if(jobban_isbanned(cult_mind.current, ROLE_CULTIST) || jobban_isbanned(cult_mind.current, ROLE_SYNDICATE)) replace_jobbanned_player(cult_mind.current, ROLE_CULTIST) + if(!cult_objs.cult_status && ishuman(cult_mind.current)) + cult_objs.setup() update_cult_icons_added(cult_mind) - cult_mode.memorize_cult_objectives(cult_mind) - if(GAMEMODE_IS_CULT) - cult_mode.check_numbers() - return 1 - -/datum/game_mode/proc/remove_cultist(datum/mind/cult_mind, show_message = 1) + add_cult_actions(cult_mind) + var/datum/objective/servecult/obj = new + obj.owner = cult_mind + cult_mind.objectives += obj + + if(cult_risen) + rise(cult_mind.current) + if(cult_ascendant) + ascend(cult_mind.current) + check_cult_size() + cult_objs.study(cult_mind.current) + return TRUE + + +/datum/game_mode/proc/check_cult_size() + if(cult_ascendant) + return + var/cult_players = get_cultists() + + if((cult_players >= rise_number) && !cult_risen) + cult_risen = TRUE + for(var/datum/mind/M in cult) + if(!M.current || !ishuman(M)) + return + SEND_SOUND(M.current, 'sound/hallucinations/i_see_you2.ogg') + to_chat(M.current, "The veil weakens as your cult grows, your eyes begin to glow...") + addtimer(CALLBACK(src, .proc/rise, M.current), 20 SECONDS) + + else if(cult_players >= ascend_number) + cult_ascendant = TRUE + for(var/datum/mind/M in cult) + if(!M.current || !ishuman(M)) + return + SEND_SOUND(M.current, 'sound/hallucinations/im_here1.ogg') + to_chat(M.current, "Your cult is ascendant and the red harvest approaches - you cannot hide your true nature for much longer!") + addtimer(CALLBACK(src, .proc/ascend, M.current), 20 SECONDS) + GLOB.command_announcement.Announce("Picking up extradimensional activity related to the Cult of [SSticker.cultdat ? SSticker.cultdat.entity_name : "Nar'Sie"] from your station. Data suggests that about [ascend_percent * 100]% of the station has been converted. Security staff is authorised lethal force on confirmed cultists to contain the threat. Ensure dead crewmembers are revived and deconverted once the situation is under control.", "Central Command Higher Dimensional Affairs", 'sound/AI/commandreport.ogg') + + +/datum/game_mode/proc/rise(cultist) + if(ishuman(cultist) && iscultist(cultist)) + var/mob/living/carbon/human/H = cultist + H.change_eye_color(BLOODCULT_EYE, FALSE) + H.update_eyes() + ADD_TRAIT(H, CULT_EYES, CULT_TRAIT) + H.update_body() + +/datum/game_mode/proc/ascend(cultist) + if(ishuman(cultist) && iscultist(cultist)) + var/mob/living/carbon/human/H = cultist + new /obj/effect/temp_visual/cult/sparks(get_turf(H), H.dir) + H.update_halo_layer() + + +/datum/game_mode/proc/remove_cultist(datum/mind/cult_mind, show_message = TRUE) if(cult_mind in cult) + var/mob/cultist = cult_mind.current cult -= cult_mind - to_chat(cult_mind.current, "An unfamiliar white light flashes through your mind, cleansing the taint of the dark-one and the memories of your time as his servant with it.") - cult_mind.current.faction -= "cult" - cult_mind.memory = "" + cultist.faction -= "cult" cult_mind.special_role = null - for(var/datum/action/innate/cultcomm/C in cult_mind.current.actions) + for(var/datum/action/innate/cult/C in cultist.actions) qdel(C) update_cult_icons_removed(cult_mind) - if(show_message) - for(var/mob/M in viewers(cult_mind.current)) - to_chat(M, "[cult_mind.current] looks like [cult_mind.current.p_they()] just reverted to [cult_mind.current.p_their()] old faith!") + if(ishuman(cultist)) + var/mob/living/carbon/human/H = cultist + REMOVE_TRAIT(H, CULT_EYES, null) + H.change_eye_color(H.original_eye_color, FALSE) + H.update_eyes() + H.remove_overlay(HALO_LAYER) + H.update_body() + check_cult_size() + if(show_message) + cultist.visible_message("[cultist] looks like [cultist.p_they()] just reverted to [cultist.p_their()] old faith!", + "An unfamiliar white light flashes through your mind, cleansing the taint of [SSticker.cultdat ? SSticker.cultdat.entity_title1 : "Nar'Sie"] and the memories of your time as their servant with it.") /datum/game_mode/proc/update_cult_icons_added(datum/mind/cult_mind) var/datum/atom_hud/antag/culthud = GLOB.huds[ANTAG_HUD_CULT] - culthud.join_hud(cult_mind.current) - set_antag_hud(cult_mind.current, "hudcultist") - + if(cult_mind.current) + culthud.join_hud(cult_mind.current) + set_antag_hud(cult_mind.current, "hudcultist") /datum/game_mode/proc/update_cult_icons_removed(datum/mind/cult_mind) var/datum/atom_hud/antag/culthud = GLOB.huds[ANTAG_HUD_CULT] - culthud.leave_hud(cult_mind.current) - set_antag_hud(cult_mind.current, null) - -/datum/game_mode/proc/update_cult_comms_added(datum/mind/cult_mind) - var/datum/action/innate/cultcomm/C = new() - C.Grant(cult_mind.current) + if(cult_mind.current) + culthud.leave_hud(cult_mind.current) + set_antag_hud(cult_mind.current, null) + +/datum/game_mode/proc/add_cult_actions(datum/mind/cult_mind) + if(cult_mind.current) + var/datum/action/innate/cult/comm/C = new + var/datum/action/innate/cult/check_progress/D = new + C.Grant(cult_mind.current) + D.Grant(cult_mind.current) + if(ishuman(cult_mind.current)) + var/datum/action/innate/cult/blood_magic/magic = new + magic.Grant(cult_mind.current) + var/datum/action/innate/cult/use_dagger/dagger = new + dagger.Grant(cult_mind.current) + cult_mind.current.update_action_buttons(TRUE) /datum/game_mode/cult/proc/get_unconvertables() var/list/ucs = list() @@ -237,163 +300,35 @@ GLOBAL_LIST_EMPTY(all_cults) ucs += player.mind return ucs - -/datum/game_mode/cult/proc/check_cult_victory() - var/cult_fail = 0 - if(objectives.Find("survive")) - cult_fail += check_survive() //the proc returns 1 if there are not enough cultists on the shuttle, 0 otherwise - if(objectives.Find("eldergod")) - cult_fail += eldergod //1 by default, 0 if the elder god has been summoned at least once - if(objectives.Find("slaughter")) - if(!demons_summoned) - cult_fail++ - if(objectives.Find("sacrifice")) - if(sacrifice_target && !(sacrifice_target in sacrificed)) //if the target has been sacrificed, ignore this step. otherwise, add 1 to cult_fail - cult_fail++ - if(objectives.Find("convert")) - if(cult.len < convert_target) - cult_fail++ - if(objectives.Find("bloodspill")) - if(max_spilled_blood < spilltarget) - cult_fail++ - - return cult_fail //if any objectives aren't met, failure - - -/datum/game_mode/cult/proc/check_survive() - acolytes_survived = 0 - for(var/datum/mind/cult_mind in cult) - if(cult_mind.current && cult_mind.current.stat!=2) - var/area/A = get_area(cult_mind.current ) - if( is_type_in_list(A, GLOB.centcom_areas)) - acolytes_survived++ - else if(A == SSshuttle.emergency.areaInstance && SSshuttle.emergency.mode >= SHUTTLE_ESCAPE) //snowflaked into objectives because shitty bay shuttles had areas to auto-determine this - acolytes_survived++ - - if(acolytes_survived>=acolytes_needed) - return 0 - else - return 1 - - -/atom/proc/cult_log(var/message) +/atom/proc/cult_log(message) investigate_log(message, "cult") /datum/game_mode/cult/declare_completion() - bonus_check() - - if(!check_cult_victory()) + if(cult_objs.cult_status == NARSIE_HAS_RISEN) feedback_set_details("round_end_result","cult win - cult win") - feedback_set("round_end_result",acolytes_survived) - to_chat(world, " The cult wins! It has succeeded in serving its dark masters!") + to_chat(world, " The cult wins! It has succeeded in summoning [SSticker.cultdat.entity_name]!") + else if(cult_objs.cult_status == NARSIE_HAS_FALLEN) + feedback_set_details("round_end_result","cult draw - narsie died, nobody wins") + to_chat(world, " Nobody wins! [SSticker.cultdat.entity_name] was summoned, but banished!") else feedback_set_details("round_end_result","cult loss - staff stopped the cult") - feedback_set("round_end_result",acolytes_survived) - to_chat(world, " The staff managed to stop the cult!") - - var/text = "Cultists escaped: [acolytes_survived]" - - if(objectives.len) - text += "
The cultists' objectives were:" - for(var/obj_count=1, obj_count <= objectives.len, obj_count++) - var/explanation - switch(objectives[obj_count]) - if("survive") - if(!check_survive()) - explanation = "Make sure at least [acolytes_needed] acolytes escape on the shuttle. Success!" - feedback_add_details("cult_objective","cult_survive|SUCCESS|[acolytes_needed]") - else - explanation = "Make sure at least [acolytes_needed] acolytes escape on the shuttle. Fail." - feedback_add_details("cult_objective","cult_survive|FAIL|[acolytes_needed]") - if("sacrifice") - if(sacrifice_target) - if(sacrifice_target in sacrificed) - explanation = "Sacrifice [sacrifice_target.name], the [sacrifice_target.assigned_role]. Success!" - feedback_add_details("cult_objective","cult_sacrifice|SUCCESS") - else if(sacrifice_target && sacrifice_target.current) - explanation = "Sacrifice [sacrifice_target.name], the [sacrifice_target.assigned_role]. Fail." - feedback_add_details("cult_objective","cult_sacrifice|FAIL") - else - explanation = "Sacrifice [sacrifice_target.name], the [sacrifice_target.assigned_role]. Fail (Gibbed)." - feedback_add_details("cult_objective","cult_sacrifice|FAIL|GIBBED") - if("eldergod") - if(!eldergod) - explanation = "Summon [SSticker.cultdat.entity_name]. Success!" - feedback_add_details("cult_objective","cult_narsie|SUCCESS") - else - explanation = "Summon [SSticker.cultdat.entity_name]. Fail." - feedback_add_details("cult_objective","cult_narsie|FAIL") - if("slaughter") - if(demons_summoned) - explanation = "Bring the Slaughter. Success!" - feedback_add_details("cult_objective","cult_demons|SUCCESS") - else - explanation = "Bring the Slaughter. Fail." - feedback_add_details("cult_objective","cult_demons|FAIL") - - if("convert")//convert half the crew - if(cult.len >= convert_target) - explanation = "Convert [convert_target] crew members ([cult.len] cultists at round end). Success!" - feedback_add_details("cult_objective","cult_convertion|SUCCESS") - else - explanation = "Convert [convert_target] crew members ([cult.len] total cultists). Fail." - feedback_add_details("cult_objective","cult_convertion|FAIL") - - if("bloodspill")//cover a large portion of the station in blood - if(max_spilled_blood >= spilltarget) - explanation = "Cover [spilltarget] tiles of the station in blood (The peak number of covered tiles was: [max_spilled_blood]). Success!" - feedback_add_details("cult_objective","cult_bloodspill|SUCCESS") - else - explanation = "Cover [spilltarget] tiles of the station in blood (The peak number of covered tiles was: [max_spilled_blood]). Fail." - feedback_add_details("cult_objective","cult_bloodspill|FAIL") - - if("harvest") - if(harvested > harvest_target) - explanation = "Offer [harvest_target] humanoids for [SSticker.cultdat.entity_name]'s first meal of the day. ([harvested] sacrificed) Success!" - feedback_add_details("cult_objective","cult_harvest|SUCCESS") - else - explanation = "Offer [harvest_target] humanoids for [SSticker.cultdat.entity_name]'s first meal of the day. ([harvested] sacrificed) Fail." - feedback_add_details("cult_objective","cult_harvest|FAIL") - - if("hijack") - if(!escaped_shuttle) - explanation = "Do not let a single non-cultist board the Escape Shuttle. ([escaped_shuttle] escaped on the shuttle) ([escaped_pod] escaped on pods) Success!" - feedback_add_details("cult_objective","cult_hijack|SUCCESS") - else - explanation = "Do not let a single non-cultist board the Escape Shuttle. ([escaped_shuttle] escaped on the shuttle) ([escaped_pod] escaped on pods) Fail." - feedback_add_details("cult_objective","cult_hijack|FAIL") - - if("massacre") - if(survivors < massacre_target) - explanation = "Massacre the crew until less than [massacre_target] people are left on the station. ([survivors] humanoids left alive) Success!" - feedback_add_details("cult_objective","cult_massacre|SUCCESS") - else - explanation = "Massacre the crew until less than [massacre_target] people are left on the station. ([survivors] humanoids left alive) Fail." - feedback_add_details("cult_objective","cult_massacre|FAIL") - - text += "
Objective #[obj_count]: [explanation]" - - to_chat(world, text) + to_chat(world, " The staff managed to stop the cult!") + + var/endtext + endtext += "
The cultists' objectives were:" + for(var/datum/objective/obj in cult_objs.presummon_objs) + endtext += "
[obj.explanation_text] - " + if(!obj.check_completion()) + endtext += "Fail." + else + endtext += "Success!" + if(cult_objs.cult_status >= NARSIE_NEEDS_SUMMONING) + endtext += "
[cult_objs.obj_summon.explanation_text] - " + if(!cult_objs.obj_summon.check_completion()) + endtext+= "Fail." + else + endtext += "Success!" + + to_chat(world, endtext) ..() - return 1 - - -/datum/game_mode/proc/auto_declare_completion_cult() - if(cult.len || (SSticker && GAMEMODE_IS_CULT)) - var/text = "The cultists were:" - for(var/datum/mind/cultist in cult) - - text += "
[cultist.key] was [cultist.name] (" - if(cultist.current) - if(cultist.current.stat == DEAD) - text += "died" - else - text += "survived" - if(cultist.current.real_name != cultist.name) - text += " as [cultist.current.real_name]" - else - text += "body destroyed" - text += ")" - - to_chat(world, text) diff --git a/code/game/gamemodes/cult/cult_actions.dm b/code/game/gamemodes/cult/cult_actions.dm new file mode 100644 index 0000000000000..c5dd101c3b4c9 --- /dev/null +++ b/code/game/gamemodes/cult/cult_actions.dm @@ -0,0 +1,124 @@ +/datum/action/innate/cult + icon_icon = 'icons/mob/actions/actions_cult.dmi' + background_icon_state = "bg_cult" + check_flags = AB_CHECK_RESTRAINED|AB_CHECK_STUNNED|AB_CHECK_CONSCIOUS + buttontooltipstyle = "cult" + +/datum/action/innate/cult/IsAvailable() + if(!iscultist(owner)) + return FALSE + return ..() + + +//Comms +/datum/action/innate/cult/comm + name = "Communion" + desc = "Whispered words that all cultists can hear.
Warning:Nearby non-cultists can still hear you." + button_icon_state = "cult_comms" + check_flags = AB_CHECK_CONSCIOUS + +/datum/action/innate/cult/comm/Activate() + var/input = stripped_input(usr, "Please choose a message to tell to the other acolytes.", "Voice of Blood", "") + if(!input || !IsAvailable()) + return + cultist_commune(usr, input) + return + +/datum/action/innate/cult/comm/proc/cultist_commune(mob/living/user, message) + if(!user || !message) + return + if(!user.can_speak()) + to_chat(user, "You can't speak!") + return + + if((MUTE in user.mutations) || user.mind.miming) //Under vow of silence/mute? + user.visible_message("[user] appears to whisper to themselves.", + "You begin to whisper to yourself.") //Make them do *something* abnormal. + sleep(10) + else + user.whisper("O bidai nabora se[pick("'","`")]sma!") // Otherwise book club sayings. + sleep(10) + user.whisper(message) // And whisper the actual message + + var/my_message + if(istype(user, /mob/living/simple_animal/slaughter/cult)) //Harbringers of the Slaughter + my_message = "Harbringer of the Slaughter: [message]" + else + my_message = "[(isconstruct(user) ? "Construct" : isshade(user) ? "" : "Acolyte")] [user.real_name]: [message]" + for(var/mob/M in GLOB.player_list) + if(iscultist(M)) + to_chat(M, my_message) + else if((M in GLOB.dead_mob_list) && !isnewplayer(M)) + to_chat(M, " (F) [my_message] ") + + log_say("(CULT) [message]", user) + +/datum/action/innate/cult/comm/spirit + name = "Spiritual Communion" + desc = "Conveys a message from the spirit realm that all cultists can hear." + +/datum/action/innate/cult/comm/spirit/IsAvailable() + return TRUE + +/datum/action/innate/cult/comm/spirit/cultist_commune(mob/living/user, message) + var/my_message + if(!message) + return + my_message = "The [user.name]: [message]" + for(var/mob/M in GLOB.player_list) + if(iscultist(M)) + to_chat(M, my_message) + else if((M in GLOB.dead_mob_list) && !isnewplayer(M)) + to_chat(M, " (F) [my_message] ") + + +//Objectives +/datum/action/innate/cult/check_progress + name = "Study the Veil" + button_icon_state = "tome" + desc = "Check your cult's current progress and objective." + check_flags = AB_CHECK_CONSCIOUS + +/datum/action/innate/cult/check_progress/New() + if(SSticker.mode) + button_icon_state = SSticker.cultdat.tome_icon + ..() + +/datum/action/innate/cult/check_progress/IsAvailable() + if(iscultist(owner) || isobserver(owner)) + return TRUE + return FALSE + +/datum/action/innate/cult/check_progress/Activate() + if(!IsAvailable()) + return + if(SSticker && SSticker.mode) + SSticker.mode.cult_objs.study(usr, TRUE) + else + to_chat(usr, "You fail to study the Veil. (This should never happen, adminhelp and/or yell at a coder)") + + +//Draw rune +/datum/action/innate/cult/use_dagger + name = "Draw Blood Rune" + desc = "Use the ritual dagger to create a powerful blood rune" + button_icon_state = "blood_dagger" + +/datum/action/innate/cult/use_dagger/Grant() + if(SSticker.mode) + button_icon_state = SSticker.cultdat.dagger_icon + ..() + +/datum/action/innate/cult/use_dagger/override_location() + button.ordered = FALSE + button.screen_loc = "6:157,4:-2" + button.moved = "6:157,4:-2" + +/datum/action/innate/cult/use_dagger/Activate() + var/obj/item/melee/cultblade/dagger/D = owner.find_item(/obj/item/melee/cultblade/dagger) + if(D) + owner.remove_from_mob(D) + owner.put_in_hands(D) + D.attack_self(owner) + else + to_chat(usr, "You do not seem to carry a ritual dagger to draw a rune with. If you need a new one, prepare and use the Summon Dagger spell.") diff --git a/code/game/gamemodes/cult/cult_comms.dm b/code/game/gamemodes/cult/cult_comms.dm deleted file mode 100644 index 0f637153f6cc6..0000000000000 --- a/code/game/gamemodes/cult/cult_comms.dm +++ /dev/null @@ -1,49 +0,0 @@ -/datum/action/innate/cultcomm - name = "Communion" - button_icon_state = "cult_comms" - background_icon_state = "bg_cult" - check_flags = AB_CHECK_RESTRAINED|AB_CHECK_STUNNED|AB_CHECK_CONSCIOUS - -/datum/action/innate/cultcomm/IsAvailable() - if(!iscultist(owner)) - return 0 - return ..() - -/datum/action/innate/cultcomm/Activate() - var/input = stripped_input(usr, "Please choose a message to tell to the other acolytes.", "Voice of Blood", "") - if(!input || !IsAvailable()) - return - - cultist_commune(usr, input) - return - - - -/proc/cultist_commune(mob/living/user, message) - if(!message) - return - - if((MUTE in user.mutations) || user.mind.miming) //Under vow of silence/mute? - user.visible_message("[user] appears to whisper to [user.p_them()]self.","You begin to whisper to yourself.") //Make them do *something* abnormal. - else - user.whisper("O bidai nabora se[pick("'","`")]sma!") // Otherwise book club sayings. - sleep(10) - - if(!user) - return - - if(!((MUTE in user.mutations) || user.mind.miming)) // If they aren't mute/miming, commence the whisperting - user.whisper(message) - var/my_message - if(istype(user, /mob/living/simple_animal/slaughter/cult)) //Harbingers of the Slaughter - my_message = "Harbinger of the Slaughter: [message]" - else - my_message = "[(ishuman(user) ? "Acolyte" : "Construct")] [user.real_name]: [message]" - for(var/mob/M in GLOB.player_list) - if(iscultist(M)) - to_chat(M, my_message) - else if((M in GLOB.dead_mob_list) && !isnewplayer(M)) - to_chat(M, " (F) [my_message] ") - - log_say("(CULT) [message]", user) - user.create_log(SAY_LOG, "(CULT) [message]") diff --git a/code/game/gamemodes/cult/cult_datums.dm b/code/game/gamemodes/cult/cult_datums.dm index 73c7dc2cf03b3..67e9a78d87973 100644 --- a/code/game/gamemodes/cult/cult_datums.dm +++ b/code/game/gamemodes/cult/cult_datums.dm @@ -2,6 +2,9 @@ var/name = "Cult of Nar'Sie" var/theme = "blood" var/tome_icon = "tome" + var/dagger_icon = "blood_dagger" + var/sword_icon = "blood_blade" + var/construct_glow = LIGHT_COLOR_BLOOD_MAGIC //God Entity var/entity_name = "Nar'Sie" @@ -75,6 +78,9 @@ name = "Cult of Kha'Rin" theme = "fire" tome_icon = "helltome" + dagger_icon = "hell_dagger" + sword_icon = "hell_blade" + construct_glow = LIGHT_COLOR_FIRE entity_name = "Kha'Rin" entity_title1 = "The Burning One" @@ -87,24 +93,24 @@ cult_floor_icon_state = "culthell" cult_girder_icon_state = "hell_girder" - artificer_name = "Summoner" + //artificer_name = "Summoner" artificer_icon_state = "summoner" - behemoth_name = "Incarnation of Pain" + //behemoth_name = "Incarnation of Pain" behemoth_icon_state = "incarnation_of_pain" - wraith_name = "Hell Knight" + //wraith_name = "Hell Knight" wraith_icon_state = "hell_knight" wraith_jaunt_out_animation = "infernal_rift_out" wraith_jaunt_in_animation = "infernal_rift_in" - juggernaut_name = "Incarnation of Pain" + //juggernaut_name = "Incarnation of Pain" juggernaut_icon_state = "incarnation_of_pain" - harvester_name = "Lost Soul" + //harvester_name = "Lost Soul" harvester_icon_state = "lost_soul" - shade_name = "Ifrit" + //shade_name = "Ifrit" shade_icon_state = "ifrit" pylon_icon_state = "hell_pylon" @@ -131,6 +137,9 @@ name = "Cult of Mortality" theme = "death" tome_icon = "deathtome" + dagger_icon = "death_dagger" + sword_icon = "death_blade" + construct_glow = LIGHT_COLOR_DARKRED entity_name = "The Reaper" entity_title1 = "The Silent One" @@ -143,24 +152,24 @@ cult_floor_icon_state = "cultdeath" cult_girder_icon_state = "reaper_cultgirder" - artificer_name = "Boneshaper" + //artificer_name = "Boneshaper" artificer_icon_state = "boneshaper" - behemoth_name = "Draugr" + //behemoth_name = "Draugr" behemoth_icon_state = "golem" - wraith_name = "Envoy of Death" + //wraith_name = "Envoy of Death" wraith_icon_state = "envoy_of_death" wraith_jaunt_out_animation = "shadowstep_out" wraith_jaunt_in_animation = "shadowstep_in" - juggernaut_name = "Golem" + //juggernaut_name = "Golem" juggernaut_icon_state = "golem" - harvester_name = "Necrophage" + //harvester_name = "Necrophage" harvester_icon_state = "necrophage" - shade_name = "Banshee" + //shade_name = "Banshee" shade_icon_state = "banshee" pylon_icon_state = "reaper_pylon" diff --git a/code/game/gamemodes/cult/cult_items.dm b/code/game/gamemodes/cult/cult_items.dm index a68e4c1e9ca44..6aa0f896f8f8f 100644 --- a/code/game/gamemodes/cult/cult_items.dm +++ b/code/game/gamemodes/cult/cult_items.dm @@ -1,27 +1,47 @@ +/obj/item/tome + name = "arcane tome" + desc = "An old, dusty tome with frayed edges and a sinister-looking cover." + icon_state = "tome" + throw_speed = 2 + throw_range = 5 + w_class = WEIGHT_CLASS_SMALL + +/obj/item/tome/New() + if(SSticker.mode) + icon_state = SSticker.cultdat.tome_icon + ..() + /obj/item/melee/cultblade - name = "Cult Blade" + name = "cult blade" desc = "An arcane weapon wielded by the followers of a cult." - icon_state = "cultblade" - item_state = "cultblade" + icon = 'icons/obj/cult.dmi' + icon_state = "blood_blade" + item_state = "blood_blade" w_class = WEIGHT_CLASS_BULKY force = 30 throwforce = 10 - sharp = 1 + sharp = TRUE hitsound = 'sound/weapons/bladeslice.ogg' attack_verb = list("attacked", "slashed", "stabbed", "sliced", "torn", "ripped", "diced", "cut") + sprite_sheets_inhand = list("Skrell" = 'icons/mob/species/skrell/held.dmi') // To stop skrell stabbing themselves in the head +/obj/item/melee/cultblade/New() + if(SSticker.mode) + icon_state = SSticker.cultdat.sword_icon + item_state = SSticker.cultdat.sword_icon + ..() -/obj/item/melee/cultblade/attack(mob/living/target as mob, mob/living/carbon/human/user as mob) +/obj/item/melee/cultblade/attack(mob/living/target, mob/living/carbon/human/user) if(!iscultist(user)) user.Weaken(5) user.unEquip(src, 1) - user.visible_message("A powerful force shoves [user] away from [target]!", \ + user.visible_message("A powerful force shoves [user] away from [target]!", "\"You shouldn't play with sharp things. You'll poke someone's eye out.\"") if(ishuman(user)) var/mob/living/carbon/human/H = user H.apply_damage(rand(force/2, force), BRUTE, pick("l_arm", "r_arm")) else - user.adjustBruteLoss(rand(force/2,force)) + user.adjustBruteLoss(rand(force/2, force)) return ..() @@ -30,47 +50,38 @@ if(!iscultist(user)) to_chat(user, "\"I wouldn't advise that.\"") to_chat(user, "An overwhelming sense of nausea overpowers you!") - user.Dizzy(120) + user.Confused(10) + user.Jitter(6) if(HULK in user.mutations) to_chat(user, "You can't seem to hold the blade properly!") return FALSE -/obj/item/melee/cultblade/dagger - name = "sacrificial dagger" - desc = "A strange dagger said to be used by sinister groups for \"preparing\" a corpse before sacrificing it to their dark gods." - icon = 'icons/obj/wizard.dmi' - icon_state = "render" - w_class = WEIGHT_CLASS_SMALL - force = 15 - throwforce = 25 - embed_chance = 75 - -/obj/item/melee/cultblade/dagger/attack(atom/target, mob/living/carbon/human/user) - ..() - if(ishuman(target)) - var/mob/living/carbon/human/H = target - if((H.stat != DEAD) && !(NO_BLOOD in H.dna.species.species_traits)) - H.bleed(50) - /obj/item/restraints/legcuffs/bola/cult name = "runed bola" - desc = "A strong bola, bound with dark magic. Throw it to trip and slow your victim." + desc = "A strong bola, bound with dark magic. Throw it to trip and slow your victim. Will not hit fellow cultists." icon = 'icons/obj/items.dmi' icon_state = "bola_cult" breakouttime = 45 weaken = 1 +/obj/item/restraints/legcuffs/bola/cult/throw_impact(atom/hit_atom, datum/thrownthing/throwingdatum) + if(iscultist(hit_atom)) + hit_atom.visible_message("[src] bounces off of [hit_atom], as if repelled by an unseen force!") + return + . = ..() + /obj/item/clothing/head/hooded/culthood name = "cult hood" icon_state = "culthood" desc = "A hood worn by the followers of a cult." + flags = BLOCKHAIR flags_inv = HIDEFACE flags_cover = HEADCOVERSEYES armor = list(melee = 30, bullet = 10, laser = 5, energy = 5, bomb = 0, bio = 0, rad = 0, fire = 10, acid = 10) cold_protection = HEAD min_cold_protection_temperature = SPACE_HELM_MIN_TEMP_PROTECT - + magical = TRUE /obj/item/clothing/head/hooded/culthood/alt icon_state = "cult_hoodalt" @@ -84,41 +95,23 @@ item_state = "cultrobes" body_parts_covered = UPPER_TORSO|LOWER_TORSO|LEGS|ARMS hoodtype = /obj/item/clothing/head/hooded/culthood - allowed = list(/obj/item/tome,/obj/item/melee/cultblade) + allowed = list(/obj/item/tome, /obj/item/melee/cultblade) armor = list("melee" = 40, "bullet" = 30, "laser" = 40, "energy" = 20, "bomb" = 25, "bio" = 10, "rad" = 0, "fire" = 10, "acid" = 10) flags_inv = HIDEJUMPSUIT + magical = TRUE /obj/item/clothing/suit/hooded/cultrobes/alt icon_state = "cultrobesalt" item_state = "cultrobesalt" hoodtype = /obj/item/clothing/head/hooded/culthood/alt -/obj/item/clothing/head/magus - name = "magus helm" - icon_state = "magus" - item_state = "magus" - desc = "A helm worn by the followers of Nar-Sie." - flags = BLOCKHAIR - flags_inv = HIDEFACE - flags_cover = HEADCOVERSEYES | HEADCOVERSMOUTH - armor = list("melee" = 50, "bullet" = 30, "laser" = 50, "energy" = 20, "bomb" = 25, "bio" = 10, "rad" = 0, "fire" = 10, "acid" = 10) - -/obj/item/clothing/suit/magusred - name = "magus robes" - desc = "A set of armored robes worn by the followers of Nar-Sie." - icon_state = "magusred" - item_state = "magusred" - body_parts_covered = UPPER_TORSO|LOWER_TORSO|LEGS|ARMS - allowed = list(/obj/item/tome,/obj/item/melee/cultblade) - armor = list("melee" = 50, "bullet" = 30, "laser" = 50, "energy" = 20, "bomb" = 25, "bio" = 10, "rad" = 0, "fire" = 10, "acid" = 10) - flags_inv = HIDEGLOVES|HIDESHOES|HIDEJUMPSUIT - /obj/item/clothing/head/helmet/space/cult name = "cult helmet" desc = "A space worthy helmet used by the followers of a cult." icon_state = "cult_helmet" item_state = "cult_helmet" armor = list("melee" = 70, "bullet" = 50, "laser" = 30,"energy" = 15, "bomb" = 30, "bio" = 30, "rad" = 30, "fire" = 40, "acid" = 75) + magical = TRUE /obj/item/clothing/suit/space/cult name = "cult armor" @@ -126,53 +119,57 @@ item_state = "cult_armour" desc = "A bulky suit of armor, bristling with spikes. It looks space proof." w_class = WEIGHT_CLASS_NORMAL - allowed = list(/obj/item/tome,/obj/item/melee/cultblade,/obj/item/tank) + allowed = list(/obj/item/tome, /obj/item/melee/cultblade, /obj/item/tank) slowdown = 1 armor = list("melee" = 70, "bullet" = 50, "laser" = 30,"energy" = 15, "bomb" = 30, "bio" = 30, "rad" = 30, "fire" = 40, "acid" = 75) + magical = TRUE /obj/item/clothing/suit/hooded/cultrobes/cult_shield - name = "empowered cultist robe" + name = "empowered cultist robes" desc = "An empowered garb which creates a powerful shield around the user." icon_state = "cult_armour" item_state = "cult_armour" w_class = WEIGHT_CLASS_BULKY armor = list("melee" = 50, "bullet" = 40, "laser" = 50, "energy" = 30, "bomb" = 50, "bio" = 30, "rad" = 30, "fire" = 50, "acid" = 60) body_parts_covered = UPPER_TORSO|LOWER_TORSO|LEGS|ARMS - allowed = list(/obj/item/tome,/obj/item/melee/cultblade) + allowed = list(/obj/item/tome, /obj/item/melee/cultblade) + hoodtype = /obj/item/clothing/head/hooded/cult_hoodie var/current_charges = 3 var/shield_state = "shield-cult" - hoodtype = /obj/item/clothing/head/hooded/cult_hoodie + var/shield_on = "shield-cult" /obj/item/clothing/head/hooded/cult_hoodie - name = "empowered cultist robe" + name = "empowered cultist hood" desc = "An empowered garb which creates a powerful shield around the user." icon_state = "cult_hoodalt" armor = list("melee" = 40, "bullet" = 30, "laser" = 40,"energy" = 20, "bomb" = 25, "bio" = 10, "rad" = 0, "fire" = 10, "acid" = 10) body_parts_covered = HEAD + flags = BLOCKHAIR flags_inv = HIDEFACE flags_cover = HEADCOVERSEYES + magical = TRUE /obj/item/clothing/suit/hooded/cultrobes/cult_shield/equipped(mob/living/user, slot) ..() - if(!iscultist(user)) + if(!iscultist(user)) // Todo: Make this only happen when actually equipped to the correct slot. (For all cult items) to_chat(user, "\"I wouldn't advise that.\"") to_chat(user, "An overwhelming sense of nausea overpowers you!") user.unEquip(src, 1) - user.Dizzy(30) + user.Confused(10) user.Weaken(5) /obj/item/clothing/suit/hooded/cultrobes/cult_shield/hit_reaction(mob/living/carbon/human/owner, atom/movable/hitby, attack_text = "the attack", final_block_chance = 0, damage = 0, attack_type = MELEE_ATTACK) if(current_charges) - owner.visible_message("\The [attack_text] is deflected in a burst of blood-red sparks!") + owner.visible_message("[attack_text] is deflected in a burst of blood-red sparks!") current_charges-- - playsound(loc, "sparks", 100, 1) + playsound(loc, "sparks", 100, TRUE) new /obj/effect/temp_visual/cult/sparks(get_turf(owner)) if(!current_charges) owner.visible_message("The runed shield around [owner] suddenly disappears!") shield_state = "broken" owner.update_inv_wear_suit() - return 1 - return 0 + return TRUE + return FALSE /obj/item/clothing/suit/hooded/cultrobes/cult_shield/special_overlays() return mutable_appearance('icons/effects/cult_effects.dmi', shield_state, MOB_LAYER + 0.01) @@ -183,23 +180,33 @@ icon_state = "flagellantrobe" item_state = "flagellantrobe" flags_inv = HIDEJUMPSUIT - allowed = list(/obj/item/tome,/obj/item/melee/cultblade) + allowed = list(/obj/item/tome, /obj/item/melee/cultblade) body_parts_covered = UPPER_TORSO|LOWER_TORSO|LEGS|ARMS - armor = list("melee" = -45, "bullet" = -45, "laser" = -45,"energy" = -45, "bomb" = -45, "bio" = -45, "rad" = -45, "fire" = 0, "acid" = 0) + armor = list("melee" = -50, "bullet" = -50, "laser" = -50,"energy" = -50, "bomb" = -50, "bio" = -50, "rad" = -50, "fire" = 0, "acid" = 0) slowdown = -1 sprite_sheets = list( "Vox" = 'icons/mob/species/vox/suit.dmi', "Drask" = 'icons/mob/species/drask/suit.dmi', "Grey" = 'icons/mob/species/grey/suit.dmi' - ) + ) hoodtype = /obj/item/clothing/head/hooded/flagellant_hood +/obj/item/clothing/suit/hooded/cultrobes/flagellant_robe/equipped(mob/living/user, slot) + ..() + if(!iscultist(user)) + to_chat(user, "\"I wouldn't advise that.\"") + to_chat(user, "An overwhelming sense of nausea overpowers you!") + user.unEquip(src, 1) + user.Confused(10) + user.Weaken(5) + /obj/item/clothing/head/hooded/flagellant_hood name = "flagellant's robes" desc = "Blood-soaked garb infused with dark magic; allows the user to move at inhuman speeds, but at the cost of increased damage." icon_state = "flagellanthood" item_state = "flagellanthood" + flags = BLOCKHAIR flags_inv = HIDEFACE flags_cover = HEADCOVERSEYES armor = list("melee" = 0, "bullet" = 0, "laser" = 0, "energy" = 0, "bomb" = 0, "bio" = 0, "rad" = 0, "fire" = 0, "acid" = 0) @@ -207,13 +214,12 @@ "Vox" = 'icons/mob/species/vox/head.dmi', "Drask" = 'icons/mob/species/drask/head.dmi', "Grey" = 'icons/mob/species/grey/head.dmi' - ) + ) /obj/item/whetstone/cult name = "eldritch whetstone" desc = "A block, empowered by dark magic. Sharp weapons will be enhanced when used on the stone." icon_state = "cult_sharpener" - used = 0 increment = 5 max = 40 prefix = "darkened" @@ -222,6 +228,12 @@ /obj/item/whetstone/cult/update_icon() icon_state = "cult_sharpener[used ? "_used" : ""]" +/obj/item/whetstone/cult/attackby(obj/item/I, mob/user, params) + ..() + if(used) + to_chat(user, "[src] crumbles to ashes.") + qdel(src) + /obj/item/reagent_containers/food/drinks/bottle/unholywater name = "flask of unholy water" desc = "Toxic to nonbelievers; this water renews and reinvigorates the faithful of a cult." @@ -229,29 +241,30 @@ color = "#333333" list_reagents = list("unholywater" = 40) -/obj/item/clothing/glasses/night/cultblind +/obj/item/clothing/glasses/hud/health/night/cultblind name = "zealot's blindfold" desc = "May the master guide you through the darkness and shield you from the light." icon_state = "blindfold" item_state = "blindfold" see_in_dark = 8 - flash_protect = 1 + flash_protect = TRUE + prescription = TRUE + origin_tech = null -/obj/item/clothing/glasses/night/cultblind/equipped(mob/user, slot) +/obj/item/clothing/glasses/hud/health/night/cultblind/equipped(mob/user, slot) ..() if(!iscultist(user)) to_chat(user, "\"You want to be blind, do you?\"") user.unEquip(src, 1) - user.Dizzy(30) + user.Confused(30) user.Weaken(5) user.EyeBlind(30) /obj/item/shuttle_curse name = "cursed orb" desc = "You peer within this smokey orb and glimpse terrible fates befalling the escape shuttle." - icon = 'icons/obj/projectiles.dmi' - icon_state ="bluespace" - color = "#ff0000" + icon = 'icons/obj/cult.dmi' + icon_state ="shuttlecurse" var/global/curselimit = 0 /obj/item/shuttle_curse/attack_self(mob/user) @@ -264,29 +277,19 @@ to_chat(user, "We have exhausted our ability to curse the shuttle.") return if(locate(/obj/singularity/narsie) in GLOB.poi_list || locate(/mob/living/simple_animal/slaughter/cult) in GLOB.mob_list) - to_chat(user, "Nar-Sie or her avatars are already on this plane, there is no delaying the end of all things.") + to_chat(user, "Nar'Sie or her avatars are already on this plane, there is no delaying the end of all things.") return if(SSshuttle.emergency.mode == SHUTTLE_CALL) - var/cursetime = 1800 + var/cursetime = 3 MINUTES var/timer = SSshuttle.emergency.timeLeft(1) + cursetime SSshuttle.emergency.setTimer(timer) to_chat(user,"You shatter the orb! A dark essence spirals into the air, then disappears.") - playsound(user.loc, 'sound/effects/glassbr1.ogg', 50, 1) + playsound(user.loc, 'sound/effects/glassbr1.ogg', 50, TRUE) curselimit++ + var/message = pick(CULT_CURSES) + GLOB.command_announcement.Announce("[message] The shuttle will be delayed by [cursetime / 600] minute\s.", "System Failure", 'sound/misc/notice1.ogg') qdel(src) - sleep(20) - var/global/list/curses - if(!curses) - curses = list("A fuel technician just slit his own throat and begged for death. The shuttle will be delayed by two minutes.", - "The shuttle's navigation programming was replaced by a file containing two words, IT COMES. The shuttle will be delayed by two minutes.", - "The shuttle's custodian tore out his guts and began painting strange shapes on the floor. The shuttle will be delayed by two minutes.", - "A shuttle engineer began screaming 'DEATH IS NOT THE END' and ripped out wires until an arc flash seared off her flesh. The shuttle will be delayed by two minutes.", - "A shuttle inspector started laughing madly over the radio and then threw herself into an engine turbine. The shuttle will be delayed by two minutes.", - "The shuttle dispatcher was found dead with bloody symbols carved into their flesh. The shuttle will be delayed by two minutes.", - "Steve repeatedly touched a lightbulb until his hands fell off. The shuttle will be delayed by two minutes.") - var/message = pick(curses) - GLOB.command_announcement.Announce("[message]", "System Failure", 'sound/misc/notice1.ogg') /obj/item/cult_shift name = "veil shifter" @@ -298,7 +301,7 @@ /obj/item/cult_shift/examine(mob/user) . = ..() if(uses) - . += "It has [uses] uses remaining." + . += "It has [uses] use\s remaining." else . += "It seems drained." @@ -311,12 +314,12 @@ /obj/item/cult_shift/attack_self(mob/user) if(!uses || !iscarbon(user)) - to_chat(user, "\The [src] is dull and unmoving in your hands.") + to_chat(user, "[src] is dull and unmoving in your hands.") return if(!iscultist(user)) user.unEquip(src, 1) step(src, pick(GLOB.alldirs)) - to_chat(user, "\The [src] flickers out of your hands, too eager to move!") + to_chat(user, "[src] flickers out of your hands, too eager to move!") return var/outer_tele_radius = 9 @@ -326,7 +329,7 @@ var/list/turfs = new/list() for(var/turf/T in range(user, outer_tele_radius)) if(!is_teleport_allowed(T.z)) - continue + break if(get_dir(C, T) != C.dir) continue if(T == mobloc) @@ -345,7 +348,7 @@ var/turf/destination = pick(turfs) if(uses <= 0) icon_state ="shifter_drained" - playsound(mobloc, "sparks", 50, 1) + playsound(mobloc, "sparks", 50, TRUE) new /obj/effect/temp_visual/dir_setting/cult/phase/out(mobloc, C.dir) var/atom/movable/pulled = handle_teleport_grab(destination, C) @@ -354,8 +357,8 @@ C.start_pulling(pulled) //forcemove resets pulls, so we need to re-pull new /obj/effect/temp_visual/dir_setting/cult/phase(destination, C.dir) - playsound(destination, 'sound/effects/phasein.ogg', 25, 1) - playsound(destination, "sparks", 50, 1) + playsound(destination, 'sound/effects/phasein.ogg', 25, TRUE) + playsound(destination, "sparks", 50, TRUE) else to_chat(C, "The veil cannot be torn here!") @@ -374,21 +377,275 @@ icon_state = "cultrobesalt" item_state = "cultrobesalt" body_parts_covered = UPPER_TORSO|LOWER_TORSO|LEGS|ARMS - allowed = list(/obj/item/tome,/obj/item/melee/cultblade) + allowed = list(/obj/item/tome, /obj/item/melee/cultblade) armor = list(melee = 50, bullet = 30, laser = 50, energy = 20, bomb = 25, bio = 10, rad = 0, fire = 10, acid = 10) flags_inv = HIDEJUMPSUIT - flags = NODROP | DROPDEL /obj/item/clothing/shoes/cult/ghost flags = NODROP | DROPDEL +/obj/item/clothing/under/color/black/ghost + flags = NODROP | DROPDEL + /datum/outfit/ghost_cultist name = "Cultist Ghost" - uniform = /obj/item/clothing/under/color/black + uniform = /obj/item/clothing/under/color/black/ghost suit = /obj/item/clothing/suit/cultrobesghost shoes = /obj/item/clothing/shoes/cult/ghost head = /obj/item/clothing/head/hooded/culthood/alt/ghost r_hand = /obj/item/melee/cultblade/ghost + +/obj/item/shield/mirror + name = "mirror shield" + desc = "An infamous shield used by eldritch sects to confuse and disorient their enemies." + icon = 'icons/obj/cult.dmi' + icon_state = "mirror_shield" + item_state = "mirror_shield" + force = 5 + throwforce = 15 + throw_speed = 1 + throw_range = 3 + attack_verb = list("bumped", "prodded") + hitsound = 'sound/weapons/smash.ogg' + /// Chance that energy projectiles will be reflected + var/reflect_chance = 70 + /// The number of clone illusions remaining + var/illusions = 2 + + // Any damage higher than these values will have a chance to shatter the shield + /// Shatter threshold for Ballistic weapons + var/ballistic_threshold = 10 + /// Shatter threshold for Energy weapons + var/energy_threshold = 20 + +/** + * Reflect/Block/Shatter proc. + * + * Projectiles: + * If you have been hit by a projectile, the 'threshold' will be set depending on the damage type. + * By default, energy weapons have a 70% chance of being reflected, so you're going to want to use ballistics against mirror shields. (Reflection is calculated beforehand in [/mob/living/carbon/human/bullet_act]) + * For every point of damage above the threshold, the shield will have a 3% chance to shatter. (Up to a maximum of 75%) + * If a ballistic projectile doesn't shatter the shield, it will move on to the melee section. + * + * Melee and blocked projectiles: + * Melee attacks and bullets have a 50|50 chance of being blocked by the mirror shield. (Based on the 'block_chance' variable) + * If they are blocked, and the shield has an illusion charge, an illusion will be spawned at src. + * The illusion has a 60% chance to be hostile and attack non-cultists, and a 40% chance to just run away from the user. + */ +/obj/item/shield/mirror/hit_reaction(mob/living/carbon/human/owner, atom/movable/hitby, attack_text = "the attack", final_block_chance = 0, damage = 0, attack_type = MELEE_ATTACK) + if(iscultist(owner)) // Cultist holding the shield + + // Hit by a projectile + if(istype(hitby, /obj/item/projectile)) + var/obj/item/projectile/P = hitby + var/shatter_chance = 0 // Percent chance of the shield shattering on a projectile hit + var/threshold // Depends on the damage Type (Brute or Burn) + if(P.damage_type == BRUTE) + threshold = ballistic_threshold + else if(P.damage_type == BURN) + threshold = energy_threshold + else + return FALSE + // Assuming the projectile damage is 20 (WT-550), 'shatter_chance' will be 10 + // 10 * 3 gives it a 30% chance to shatter per hit. + shatter_chance = min((P.damage - threshold) * 3, 75) // Maximum of 75% chance + + if(prob(shatter_chance)) + var/turf/T = get_turf(owner) + T.visible_message("The sheer force from [P] shatters the mirror shield!") + new /obj/effect/temp_visual/cult/sparks(T) + playsound(T, 'sound/effects/glassbr3.ogg', 100) + owner.Weaken(3) + qdel(src) + return FALSE + + if(P.is_reflectable) + return FALSE //To avoid reflection chance double-dipping with block chance + + // Hit by a melee weapon or blocked a projectile + . = ..() + if(.) // 50|50 chance + playsound(src, 'sound/weapons/parry.ogg', 100, TRUE) + if(illusions > 0) + illusions-- + addtimer(CALLBACK(src, .proc/readd), 45 SECONDS) + if(prob(60)) + spawn_illusion(owner, TRUE) // Hostile illusion + else + spawn_illusion(owner, FALSE) // Running illusion + return TRUE + + else // Non-cultist holding the shield + if(prob(50)) + spawn_illusion(owner, TRUE, TRUE) + return FALSE + +/obj/item/shield/mirror/proc/spawn_illusion(mob/living/carbon/human/user, hostile, betray) + if(hostile) + var/mob/living/simple_animal/hostile/illusion/cult/H = new(user.loc) + H.faction = list("cult") + if(!betray) + H.Copy_Parent(user, 70, 10, 5) + else + H.Copy_Parent(user, 100, 20, 5) + H.GiveTarget(user) + to_chat(user, "[src] betrays you!") + else + var/mob/living/simple_animal/hostile/illusion/escape/cult/E = new(user.loc) + E.Copy_Parent(user, 70, 10) + E.GiveTarget(user) + E.Goto(user, E.move_to_delay, E.minimum_distance) + +/obj/item/shield/mirror/proc/readd() + if(illusions < initial(illusions)) + illusions++ + else if(isliving(loc)) + var/mob/living/holder = loc + if(iscultist(holder)) + to_chat(holder, "The shield's illusions are back at full strength!") + else + to_chat(holder, "[src] vibrates slightly, and starts glowing.") + +/obj/item/shield/mirror/IsReflect() + if(prob(reflect_chance)) + return TRUE + return FALSE + +/obj/item/twohanded/cult_spear + name = "blood halberd" + desc = "A sickening spear composed entirely of crystallized blood." + icon = 'icons/obj/cult.dmi' + icon_state = "bloodspear0" + slot_flags = 0 + force = 17 + force_unwielded = 17 + force_wielded = 24 + throwforce = 40 + throw_speed = 2 + armour_penetration = 30 + block_chance = 30 + attack_verb = list("attacked", "impaled", "stabbed", "torn", "gored") + sharp = TRUE + no_spin_thrown = TRUE + hitsound = 'sound/weapons/bladeslice.ogg' + var/datum/action/innate/cult/spear/spear_act + +/obj/item/twohanded/cult_spear/Destroy() + if(spear_act) + qdel(spear_act) + ..() + +/obj/item/twohanded/cult_spear/update_icon() + icon_state = "bloodspear[wielded]" + +/obj/item/twohanded/cult_spear/throw_impact(atom/hit_atom, datum/thrownthing/throwingdatum) + var/turf/T = get_turf(hit_atom) + if(isliving(hit_atom)) + var/mob/living/L = hit_atom + if(iscultist(L)) + playsound(src, 'sound/weapons/throwtap.ogg', 50) + if(!L.restrained() && L.put_in_active_hand(src)) + L.visible_message("[L] catches [src] out of the air!") + else + L.visible_message("[src] bounces off of [L], as if repelled by an unseen force!") + else if(!..()) + if(!L.null_rod_check()) + L.Weaken(3) + break_spear(T) + else + ..() + +/obj/item/twohanded/cult_spear/proc/break_spear(turf/T) + if(!T) + T = get_turf(src) + if(T) + T.visible_message("[src] shatters and melts back into blood!") + new /obj/effect/temp_visual/cult/sparks(T) + new /obj/effect/decal/cleanable/blood/splatter(T) + playsound(T, 'sound/effects/glassbr3.ogg', 100) + qdel(src) + +/obj/item/twohanded/cult_spear/hit_reaction(mob/living/carbon/human/owner, atom/movable/hitby, attack_text = "the attack", final_block_chance = 0, damage = 0, attack_type = MELEE_ATTACK) + if(wielded) + final_block_chance *= 2 + if(prob(final_block_chance)) + if(attack_type == PROJECTILE_ATTACK) + owner.visible_message("[owner] deflects [attack_text] with [src]!") + playsound(src, pick('sound/weapons/effects/ric1.ogg', 'sound/weapons/effects/ric2.ogg', 'sound/weapons/effects/ric3.ogg', 'sound/weapons/effects/ric4.ogg', 'sound/weapons/effects/ric5.ogg'), 100, TRUE) + return TRUE + else + playsound(src, 'sound/weapons/parry.ogg', 100, TRUE) + owner.visible_message("[owner] parries [attack_text] with [src]!") + return TRUE + return FALSE + +/datum/action/innate/cult/spear + name = "Bloody Bond" + desc = "Call the blood spear back to your hand!" + background_icon_state = "bg_cult" + button_icon_state = "bloodspear" + var/obj/item/twohanded/cult_spear/spear + var/cooldown = 0 + +/datum/action/innate/cult/spear/Grant(mob/user, obj/blood_spear) + . = ..() + spear = blood_spear + +/datum/action/innate/cult/spear/Activate() + if(owner == spear.loc || cooldown > world.time) + return + var/ST = get_turf(spear) + var/OT = get_turf(owner) + if(get_dist(OT, ST) > 10) + to_chat(owner,"The spear is too far away!") + else + cooldown = world.time + 20 + if(isliving(spear.loc)) + var/mob/living/L = spear.loc + L.unEquip(spear) + L.visible_message("An unseen force pulls the blood spear from [L]'s hands!") + spear.throw_at(owner, 10, 2, null) + +/obj/item/gun/projectile/shotgun/boltaction/enchanted/arcane_barrage/blood + name = "blood bolt barrage" + desc = "Blood for blood." + item_state = "disintegrate" + lefthand_file = 'icons/mob/inhands/items_lefthand.dmi' + righthand_file = 'icons/mob/inhands/items_righthand.dmi' + color = "#ff0000" + guns_left = 24 + mag_type = /obj/item/ammo_box/magazine/internal/boltaction/enchanted/arcane_barrage/blood + fire_sound = 'sound/magic/wand_teleport.ogg' + flags = NOBLUDGEON | DROPDEL + +/obj/item/ammo_box/magazine/internal/boltaction/enchanted/arcane_barrage/blood + ammo_type = /obj/item/ammo_casing/magic/arcane_barrage/blood + +/obj/item/ammo_casing/magic/arcane_barrage/blood + projectile_type = /obj/item/projectile/magic/arcane_barrage/blood + muzzle_flash_effect = /obj/effect/temp_visual/emp/cult + +/obj/item/projectile/magic/arcane_barrage/blood + name = "blood bolt" + icon_state = "blood_bolt" + damage_type = BRUTE + impact_effect_type = /obj/effect/temp_visual/dir_setting/bloodsplatter + hitsound = 'sound/effects/splat.ogg' + +/obj/item/projectile/magic/arcane_barrage/blood/prehit(atom/target) + if(iscultist(target)) + damage = 0 + nodamage = TRUE + if(ishuman(target)) + var/mob/living/carbon/human/H = target + if(H.stat != DEAD) + H.reagents.add_reagent("unholywater", 4) + if(isshade(target) || isconstruct(target)) + var/mob/living/simple_animal/M = target + if(M.health + 5 < M.maxHealth) + M.adjustHealth(-5) + new /obj/effect/temp_visual/cult/sparks(target) + ..() diff --git a/code/game/gamemodes/cult/cult_objectives.dm b/code/game/gamemodes/cult/cult_objectives.dm index 38bd1919c627e..4565e66ff2902 100644 --- a/code/game/gamemodes/cult/cult_objectives.dm +++ b/code/game/gamemodes/cult/cult_objectives.dm @@ -1,269 +1,189 @@ -/datum/game_mode/cult/proc/blood_check() - max_spilled_blood = (max(bloody_floors.len,max_spilled_blood)) - if((objectives[current_objective] == "bloodspill") && (bloody_floors.len >= spilltarget) && !spilled_blood) - spilled_blood = 1 - additional_phase() - -/datum/game_mode/cult/proc/check_numbers() - if((objectives[current_objective] == "convert") && (cult.len >= convert_target) && !mass_convert) - mass_convert = 1 - additional_phase() - -/datum/game_mode/cult/proc/first_phase() - - var/new_objective = pick_objective() - - objectives += new_objective - - var/explanation - - switch(new_objective) - if("convert") - explanation = "We must increase our influence before we can summon [SSticker.cultdat.entity_name], Convert [convert_target] crew members. Take it slowly to avoid raising suspicions." - if("bloodspill") - spilltarget = 100 + rand(0,GLOB.player_list.len * 3) - explanation = "We must prepare this place for [SSticker.cultdat.entity_title1]'s coming. Spill blood and gibs over [spilltarget] floor tiles." - if("sacrifice") - explanation = "We need to sacrifice [sacrifice_target.name], the [sacrifice_target.assigned_role], for [sacrifice_target.p_their()] blood is the key that will lead our master to this realm. You will need 3 cultists around a Sacrifice rune to perform the ritual." - - for(var/datum/mind/cult_mind in cult) - to_chat(cult_mind.current, "Objective #[current_objective]: [explanation]") - cult_mind.memory += "Objective #[current_objective]: [explanation]
" - -/datum/game_mode/cult/proc/bypass_phase() - - switch(objectives[current_objective]) - if("convert") - mass_convert = 1 - if("bloodspill") - spilled_blood = 1 - if("sacrifice") - sacrificed += sacrifice_target - additional_phase() - -/datum/game_mode/cult/proc/additional_phase() - if(objectives.Find("eldergod") || objectives.Find("slaughter")) - return - current_objective++ - - message_admins("Picking a new Cult objective.") - var/new_objective = "eldergod" - //the idea here is that if the cult performs well, the should get more objectives before they can summon Nar-Sie. - if(cult.len >= 4) //if there are less than 4 remaining cultists, they get a free pass to the summon objective. - if(current_objective <= prenarsie_objectives) - var/list/unconvertables = get_unconvertables() - if(unconvertables.len <= (cult.len * 2))//if cultists are getting radically outnumbered, they get a free pass to the summon objective. - new_objective = pick_objective() +/datum/cult_objectives //Replace with team antag datum objectives from tg once ported + var/cult_status = NARSIE_IS_ASLEEP + var/list/presummon_objs = list() + var/datum/objective/eldergod/obj_summon = new + var/sacrifices_done = 0 + var/sacrifices_required = 2 + +/datum/cult_objectives/proc/setup() + if(cult_status != NARSIE_IS_ASLEEP) + return FALSE + cult_status = NARSIE_DEMANDS_SACRIFICE + var/datum/objective/sacrifice/obj_sac = new + if(obj_sac.find_target()) + presummon_objs.Add(obj_sac) + else + ready_to_summon() + +/datum/cult_objectives/proc/study(mob/living/M, display_members = FALSE) //Called by cultists/cult constructs checking their objectives + if(!M) + return FALSE + + switch(cult_status) + if(NARSIE_IS_ASLEEP) + to_chat(M, "[SSticker.cultdat ? SSticker.cultdat.entity_name : "The Dark One"] is asleep.") + if(NARSIE_DEMANDS_SACRIFICE) + if(!length(presummon_objs)) + to_chat(M, "Error: No objectives in sacrifice list. Something went wrong. Oof.") else - message_admins("There are over twice more unconvertables than there are cultists ([cult.len] cultists for [unconvertables.len]) unconvertables! Nar-Sie objective unlocked.") - log_admin("There are over twice more unconvertables than there are cultists ([cult.len] cultists for [unconvertables.len]) unconvertables! Nar-Sie objective unlocked.") + var/datum/objective/sacrifice/current_obj = presummon_objs[length(presummon_objs)] //get the last obj in the list, ie the current one + to_chat(M, "The Veil needs to be weakened before we are able to summon [SSticker.cultdat ? SSticker.cultdat.entity_title1 : "The Dark One"].") + to_chat(M, "Current goal: [current_obj.explanation_text]") + if(NARSIE_NEEDS_SUMMONING) + to_chat(M, "The Veil is weak! We can summon [SSticker.cultdat ? SSticker.cultdat.entity_title3 : "The Dark One"]!") + to_chat(M, "Current goal: [obj_summon.explanation_text]") + if(NARSIE_HAS_RISEN) + to_chat(M, "\"I am here.\"") + to_chat(M, "Current goal: \"Feed me.\"") + if(NARSIE_HAS_FALLEN) + to_chat(M, "[SSticker.cultdat ? SSticker.cultdat.entity_name : "The Dark One"] has been banished!") + to_chat(M, "Current goal: Slaughter the unbelievers!") else - message_admins("The Cult has already completed [prenarsie_objectives] objectives! Nar-Sie objective unlocked.") - log_admin("The Cult has already completed [prenarsie_objectives] objectives! Nar-Sie objective unlocked.") - else - message_admins("There are less than 4 cultists! [SSticker.cultdat.entity_name] objective unlocked.") - log_admin("There are less than 4 cultists! [SSticker.cultdat.entity_name] objective unlocked.") - gtfo_phase() - return - - if(!sacrificed.len && (new_objective != "sacrifice")) - sacrifice_target = null - - if(new_objective == "eldergod" || new_objective == "slaughter") - second_phase() - return + to_chat(M, "Error: Cult objective status currently unknown. Something went wrong. Oof.") + + if(display_members) + var/list/cult = SSticker.mode.get_cultists(TRUE) + var/total_cult = cult[1] + cult[2] + var/rise = SSticker.mode.rise_number - total_cult + var/ascend = SSticker.mode.ascend_number - total_cult + + var/overview = "
Current cult members: [total_cult]" + if(!SSticker.mode.cult_ascendant) + if(rise > 0) + overview += " | Conversions until Rise: [rise]" + else if(ascend > 0) + overview += " | Conversions until Ascension: [ascend]" + to_chat(M, "[overview]
") + + if(cult[2]) // If there are any constructs, separate them out + to_chat(M, "Cultists: [cult[1]]") + to_chat(M, "Constructs: [cult[2]]") + + +/datum/cult_objectives/proc/current_sac_objective() //Return the current sacrifice objective datum, if any + if(cult_status == NARSIE_DEMANDS_SACRIFICE && length(presummon_objs)) + var/datum/objective/sacrifice/current_obj = presummon_objs[length(presummon_objs)] + return current_obj + return FALSE + +/datum/cult_objectives/proc/is_sac_target(datum/mind/mind) + if(cult_status != NARSIE_DEMANDS_SACRIFICE || !length(presummon_objs)) + return FALSE + var/datum/objective/sacrifice/current_obj = presummon_objs[length(presummon_objs)] + if(current_obj.target == mind) + return TRUE + return FALSE + +/datum/cult_objectives/proc/find_new_sacrifice_target(datum/mind/mind) + var/datum/objective/sacrifice/current_obj = presummon_objs[length(presummon_objs)] + if(current_obj.find_target()) + for(var/datum/mind/cult_mind in SSticker.mode.cult) + if(cult_mind && cult_mind.current) + to_chat(cult_mind.current, "[SSticker.cultdat.entity_name] murmurs, Our goal is beyond your reach. Sacrifice [current_obj.target] instead...") + return TRUE + return FALSE + +/datum/cult_objectives/proc/succesful_sacrifice() + var/datum/objective/sacrifice/current_obj = presummon_objs[length(presummon_objs)] + current_obj.sacced = TRUE + sacrifices_done++ + if(sacrifices_done >= sacrifices_required) + ready_to_summon() else - objectives += new_objective - - var/explanation - - switch(new_objective) - if("convert") - explanation = "We must increase our influence before we can summon [SSticker.cultdat.entity_name]. Convert [convert_target] crew members. Take it slowly to avoid raising suspicions." - if("bloodspill") - spilltarget = 100 + rand(0,GLOB.player_list.len * 3) - explanation = "We must prepare this place for [SSticker.cultdat.entity_title1]'s coming. Spread blood and gibs over [spilltarget] of the Station's floor tiles." - if("sacrifice") - explanation = "We need to sacrifice [sacrifice_target.name], the [sacrifice_target.assigned_role], for [sacrifice_target.p_their()] blood is the key that will lead our master to this realm. You will need 3 cultists around a Sacrifice rune to perform the ritual." - - for(var/datum/mind/cult_mind in cult) - if(cult_mind) - to_chat(cult_mind.current, "You and your acolytes have completed your task, but this place requires yet more preparation!") - to_chat(cult_mind.current, "Objective #[current_objective]: [explanation]") - cult_mind.memory += "Objective #[current_objective]: [explanation]
" - - message_admins("New Cult Objective: [new_objective]") - log_admin("New Cult Objective: [new_objective]") - - blood_check()//in case there are already enough blood covered tiles when the objective is given. - -/datum/game_mode/cult/proc/gtfo_phase()//YOU HAD ONE JOB - var/explanation - objectives += "survive" - explanation = "Our knowledge must live on. Make sure at least [acolytes_needed] acolytes escape on the shuttle to spread their work on an another station." - for(var/datum/mind/cult_mind in cult) - if(cult_mind) - to_chat(cult_mind.current, "You and your acolytes suddenly feel the urge to do your best, but survive!") - to_chat(cult_mind.current, "Objective Survive: [explanation]") - cult_mind.memory += "Objective Survive: [explanation]
" - - -/datum/game_mode/cult/proc/second_phase() - narsie_condition_cleared = 1 - var/explanation - - if(prob(40))//split the chance of this - objectives += "eldergod" - explanation = "Summon [SSticker.cultdat.entity_name] on the Station via the use of the Tear Reality rune. The veil is weak enough in [english_list(GLOB.summon_spots)] for the ritual to begin." - else - objectives += "slaughter" - explanation = "Bring the Slaughter via the rune 'Call Forth The Slaughter'. The veil is weak enough in [english_list(GLOB.summon_spots)] for the ritual to begin." + var/datum/objective/sacrifice/obj_sac = new + if(obj_sac.find_target()) + presummon_objs += obj_sac + for(var/datum/mind/cult_mind in SSticker.mode.cult) + if(cult_mind && cult_mind.current) + to_chat(cult_mind.current, "You and your acolytes have made progress, but there is more to do still before [SSticker.cultdat ? SSticker.cultdat.entity_title1 : "The Dark One"] can be summoned!") + to_chat(cult_mind.current, "Current goal: [obj_sac.explanation_text]") + else + ready_to_summon() - for(var/datum/mind/cult_mind in cult) - if(cult_mind) +/datum/cult_objectives/proc/ready_to_summon() + cult_status = NARSIE_NEEDS_SUMMONING + for(var/datum/mind/cult_mind in SSticker.mode.cult) + if(cult_mind && cult_mind.current) to_chat(cult_mind.current, "You and your acolytes have succeeded in preparing the station for the ultimate ritual!") - to_chat(cult_mind.current, "Objective #[current_objective]: [explanation]") - cult_mind.memory += "Objective #[current_objective]: [explanation]
" - -/datum/game_mode/cult/proc/third_phase() - current_objective++ - - sleep(10) - - var/last_objective = pick_bonus_objective() - - objectives += last_objective - - var/explanation - - switch(last_objective) - if("harvest") - explanation = "[SSticker.cultdat.entity_title1] hungers for their first meal of this never-ending day. Offer them [harvest_target] humanoids in sacrifice." - if("hijack") - explanation = "[SSticker.cultdat.entity_name] wishes for their troops to start the assault on CentCom immediately. Hijack the escape shuttle and don't let a single non-cultist board it." - if("massacre") - explanation = "[SSticker.cultdat.entity_name] wants to watch you as you massacre the remaining crew on the station (until less than [massacre_target] humans are left alive)." - - for(var/datum/mind/cult_mind in cult) - if(cult_mind) - to_chat(cult_mind.current, "Objective #[current_objective]: [explanation]") - cult_mind.memory += "Objective #[current_objective]: [explanation]
" - - message_admins("Last Cult Objective: [last_objective]") - log_admin("Last Cult Objective: [last_objective]") - -/datum/game_mode/cult/proc/get_possible_sac_targets() - var/list/possible_sac_targets = list() - for(var/mob/living/carbon/human/player in GLOB.player_list) - if(player.mind && !is_convertable_to_cult(player.mind) && (player.stat != DEAD) && (!player.mind.offstation_role) ) - possible_sac_targets += player.mind - if(!possible_sac_targets.len) - //There are no living Unconvertables on the station. Looking for a Sacrifice Target among the ordinary crewmembers - for(var/mob/living/carbon/human/player in GLOB.player_list) - if(is_secure_level(player.z) || player.mind.offstation_role) //We can't sacrifice people that are on the centcom z-level or offstation roles + to_chat(cult_mind.current, "Current goal: [obj_summon.explanation_text]") + +/datum/cult_objectives/proc/succesful_summon() + cult_status = NARSIE_HAS_RISEN + obj_summon.summoned = TRUE + +/datum/cult_objectives/proc/narsie_death() + cult_status = NARSIE_HAS_FALLEN + obj_summon.killed = TRUE + +//Objectives + +/datum/objective/servecult //Given to cultists on conversion/roundstart + explanation_text = "Assist your fellow cultists and Tear the Veil! (Use the Study Veil action to check your progress.)" + completed = TRUE + +/datum/objective/sacrifice + var/sacced = FALSE + explanation_text = "Sacrifice a crewmember in order to prepare the summoning." + +/datum/objective/sacrifice/check_completion() + return sacced || completed + +/datum/objective/sacrifice/find_target() + var/list/target_candidates = list() + for(var/mob/living/carbon/human/H in GLOB.player_list) + if(is_admin_level(H.z)) //We can't sacrifice people that are on the centcom z-level + continue + if(H.mind && !is_convertable_to_cult(H.mind) && (H.stat != DEAD) && (H.mind.offstation_role != TRUE)) + target_candidates += H.mind + if(!length(target_candidates)) //There are no living unconvertables on the station. Looking for a Sacrifice Target among the ordinary crewmembers + for(var/mob/living/carbon/human/H in GLOB.player_list) + if(is_admin_level(H.z)) //We can't sacrifice people that are on the centcom z-level continue - if(player.mind && !(player.mind in cult) && (player.stat != DEAD))//make DAMN sure they are not dead - possible_sac_targets += player.mind - return possible_sac_targets - -// Handles the updating of sacrifice objectives after the sacrifice target goes to cryo and ghosts -/datum/game_mode/cult/proc/update_sac_objective(previous_target, previous_role) - for(var/datum/mind/cult_mind in cult) - if(cult_mind) - var/updated_memory = cult_mind.memory - updated_memory = replacetext("[cult_mind.memory]", "[previous_target]", "[sacrifice_target]") - updated_memory = replacetext("[updated_memory]", "[previous_role]", "[sacrifice_target.assigned_role]") - cult_mind.memory = updated_memory - - -/datum/game_mode/cult/proc/pick_objective() - var/list/possible_objectives = list() - - if(!spilled_blood && (bloody_floors.len < spilltarget)) - possible_objectives |= "bloodspill" - - if(!sacrificed.len) - var/list/possible_targets = get_possible_sac_targets() - if(possible_targets.len > 0) - sacrifice_target = pick(possible_targets) - possible_objectives |= "sacrifice" - else - log_runtime(EXCEPTION("Didn't find a suitable sacrifice target...what the hell? Shout at a coder.")) - - if(!mass_convert) - var/living_crew = 0 - var/living_cultists = 0 - for(var/mob/living/L in GLOB.player_list) - if(L.stat != DEAD) - if(L.mind in cult) - living_cultists++ - else - if(istype(L, /mob/living/carbon/human)) - living_crew++ - - var/total = living_crew + living_cultists - - if((living_cultists * 2) < total) - if(total < 15) - message_admins("There are [total] players, too little for the mass convert objective!") - log_admin("There are [total] players, too little for the mass convert objective!") - else - possible_objectives |= "convert" - convert_target = rand(9,15) - - if(!possible_objectives.len)//No more possible objectives, time to summon Nar-Sie - message_admins("No suitable objectives left! Nar-Sie objective unlocked.") - log_admin("No suitable objectives left! Nar-Sie objective unlocked.") - var/lastbit - if(prob(50)) - lastbit = "eldergod" - else - lastbit = "slaughter" - return lastbit - else - return pick(possible_objectives) - -/datum/game_mode/cult/proc/pick_bonus_objective()//did we summon demons? TIME FOR THE BONUS STAGE - var/list/possible_objectives = list() - - var/living_crew = 0 - for(var/mob/living/carbon/C in GLOB.player_list) - if(C.stat != DEAD) - if(!(C.mind in cult)) - var/turf/T = get_turf(C) - if(is_station_level(T.z)) //we're only interested in the remaining humans on the station - living_crew++ - - if(living_crew > 5) - possible_objectives |= "massacre" - - if(living_crew > 10) - possible_objectives |= "harvest" - - possible_objectives |= "hijack" //we need at least one objective guarranted to fire - - return pick(possible_objectives) - -/datum/game_mode/cult/proc/bonus_check() - switch(objectives[current_objective]) - if("harvest") - if(harvested >= harvest_target) - bonus = 1 - - if("hijack") - for(var/mob/living/L in GLOB.player_list) - if(L.stat != DEAD && !(L.mind in cult)) - var/area/A = get_area(L) - if(is_type_in_list(A.loc, GLOB.centcom_areas)) - escaped_shuttle++ - if(!escaped_shuttle) - bonus = 1 - - if("massacre") - for(var/mob/living/carbon/C in GLOB.player_list) - if(C.stat != DEAD && !(C.mind in cult)) - var/turf/T = get_turf(C) - if(is_station_level(T.z)) //we're only interested in the remaining humans on the station - survivors++ - if(survivors < massacre_target) - bonus = 1 + if(H.mind && !iscultist(H) && (H.stat != DEAD) && (H.mind.offstation_role != TRUE)) + target_candidates += H.mind + if(length(target_candidates)) + target = pick(target_candidates) + explanation_text = "Sacrifice [target], the [target.assigned_role] via invoking an Offer rune with [target.p_their()] body or brain on it and three acolytes around it." + return TRUE + message_admins("Cult Sacrifice: Could not find unconvertible or convertible target. Nar'Sie summoning unlocked!") + return FALSE + + +/datum/objective/eldergod + var/summoned = FALSE + var/killed = FALSE + var/list/summon_spots = list() + +/datum/objective/eldergod/New() + ..() + find_summon_locations() + +/datum/objective/eldergod/proc/find_summon_locations(reroll = FALSE) + if(reroll) + summon_spots = new() + var/sanity = 0 + while(length(summon_spots) < SUMMON_POSSIBILITIES && sanity < 100) + var/area/summon = pick(return_sorted_areas() - summon_spots) + var/valid_spot = FALSE + if(summon && is_station_level(summon.z) && summon.valid_territory) // Check if there's a turf that you can walk on, if not it's not valid + for(var/turf/T in get_area_turfs(summon)) + if(!T.density) + var/clear = TRUE + for(var/obj/O in T) + if(O.density) + clear = FALSE + break + if(clear) + valid_spot = TRUE + break + if(valid_spot) + summon_spots += summon + sanity++ + explanation_text = "Summon [SSticker.cultdat ? SSticker.cultdat.entity_name : "your god"] by invoking the rune 'Tear Veil' with 9 cultists, constructs, or summoned ghosts on it.\ + \nThe summoning can only be accomplished in [english_list(summon_spots)] - where the veil is weak enough for the ritual to begin." + + +/datum/objective/eldergod/check_completion() + if(killed) + return NARSIE_HAS_FALLEN // You failed so hard that even the code went backwards. + return summoned || completed diff --git a/code/game/gamemodes/cult/cult_structures.dm b/code/game/gamemodes/cult/cult_structures.dm index 17ae54591aaaf..2127a89738543 100644 --- a/code/game/gamemodes/cult/cult_structures.dm +++ b/code/game/gamemodes/cult/cult_structures.dm @@ -1,37 +1,42 @@ -//Noncult /obj/structure/cult - density = 1 - anchored = 1 + density = TRUE + anchored = TRUE + layer = BELOW_OBJ_LAYER icon = 'icons/obj/cult.dmi' + light_power = 2 //Noncult As we may have this on maps /obj/structure/cult/altar name = "Altar" - desc = "A bloodstained altar dedicated to Nar-Sie" + desc = "A bloodstained altar." icon_state = "altar" /obj/structure/cult/forge name = "Daemon forge" - desc = "A forge used in crafting the unholy weapons used by the armies of Nar-Sie" + desc = "A forge used in crafting unholy armors and weapons." icon_state = "forge" + light_range = 2 + light_color = LIGHT_COLOR_LAVA /obj/structure/cult/pylon name = "Pylon" - desc = "A floating crystal that hums with an unearthly energy" + desc = "A floating crystal that hums with an unearthly energy." icon_state = "pylon" - light_range = 5 - light_color = "#3e0000" + light_range = 1.5 + light_color = LIGHT_COLOR_RED /obj/structure/cult/archives name = "Desk" - desc = "A desk covered in arcane manuscripts and tomes in unknown languages. Looking at the text makes your skin crawl" + desc = "A desk covered in arcane manuscripts and tomes in unknown languages. Looking at the text makes your skin crawl." icon_state = "archives" + light_range = 1.5 + light_color = LIGHT_COLOR_FIRE //Cult versions cuase fuck map conflicts /obj/structure/cult/functional max_integrity = 100 var/cooldowntime = 0 - var/death_message = "The structure falls apart." //The message shown when the structure is destroyed + var/death_message = "The structure falls apart." //The message shown when the structure is destroyed var/death_sound = 'sound/items/bikehorn.ogg' var/heathen_message = "You're a huge nerd, go away. Also, a coder forgot to put a message here." var/selection_title = "Oops" @@ -42,20 +47,19 @@ /obj/structure/cult/functional/obj_destruction() visible_message(death_message) - playsound(src, death_sound, 50, 1) + playsound(src, death_sound, 50, TRUE) ..() /obj/structure/cult/functional/examine(mob/user) . = ..() if(iscultist(user) && cooldowntime > world.time) - . += "The magic in [src] is weak, it will be ready to use again in [getETA()]." - . += "\The [src] is [anchored ? "":"not "]secured to the floor." + . += "The magic in [src] is weak, it will be ready to use again in [get_ETA()]." + . += "[src] is [anchored ? "":"not "]secured to the floor." -/obj/structure/cult/functional/attackby(obj/I, mob/user, params) - if(istype(I, /obj/item/tome) && iscultist(user)) +/obj/structure/cult/functional/attackby(obj/item/I, mob/user, params) + if(istype(I, /obj/item/melee/cultblade/dagger) && iscultist(user)) anchored = !anchored - to_chat(user, "You [anchored ? "":"un"]secure \the [src] [anchored ? "to":"from"] the floor.") - playsound(loc, 'sound/hallucinations/wail.ogg', 75, 1) + to_chat(user, "You [anchored ? "":"un"]secure [src] [anchored ? "to":"from"] the floor.") if(!anchored) icon_state = SSticker.cultdat?.get_icon("[initial(icon_state)]_off") else @@ -71,39 +75,64 @@ to_chat(user, "You cannot seem to manipulate this structure with your bulky hands!") return if(!anchored) - to_chat(user, "You need to anchor [src] to the floor with a tome first.") + to_chat(user, "You need to anchor [src] to the floor with a dagger first.") return if(cooldowntime > world.time) - to_chat(user, "The magic in [src] is weak, it will be ready to use again in [getETA()].") + to_chat(user, "The magic in [src] is weak, it will be ready to use again in [get_ETA()].") return var/choice = input(user, selection_prompt, selection_title) as null|anything in choosable_items var/pickedtype = choosable_items[choice] if(pickedtype && Adjacent(user) && src && !QDELETED(src) && !user.incapacitated() && cooldowntime <= world.time) cooldowntime = world.time + creation_delay var/obj/item/N = new pickedtype(get_turf(src)) - to_chat(user, replacetext("[creation_message]", "%ITEM%", "[N]")) - -/obj/structure/cult/functional/proc/getETA() - var/time = (cooldowntime - world.time)/600 - var/eta = "[round(time, 1)] minutes" - if(time <= 1) - time = (cooldowntime - world.time)*0.1 - eta = "[round(time, 1)] seconds" - return eta + to_chat(user, replacetext("[creation_message]", "%ITEM%", "[N.name]")) + +/** + * Returns the cooldown time in minutes and seconds + */ +/obj/structure/cult/functional/proc/get_ETA() + var/time = cooldowntime - world.time + var/minutes = round(time / 600) + var/seconds = round(time * 0.1, 1) + var/message + if(minutes) + message = "[minutes] minute\s" + seconds = seconds - (60 * minutes) + if(seconds) // To avoid '2 minutes, 0 seconds.' + message += "[minutes ? ", " : ""][seconds] second\s" + return message + +/obj/structure/cult/functional/cult_conceal() + density = FALSE + visible_message("[src] fades away.") + invisibility = INVISIBILITY_OBSERVER + alpha = 100 //To help ghosts distinguish hidden objs + light_range = 0 + light_power = 0 + update_light() + +/obj/structure/cult/functional/cult_reveal() + density = initial(density) + invisibility = 0 + visible_message("[src] suddenly appears!") + alpha = initial(alpha) + light_range = initial(light_range) + light_power = initial(light_power) + update_light() /obj/structure/cult/functional/altar name = "altar" desc = "A bloodstained altar dedicated to a cult." icon_state = "altar" max_integrity = 150 //Sturdy - death_message = "The altar breaks into splinters, releasing a cascade of spirits into the air!" + death_message = "The altar breaks into splinters, releasing a cascade of spirits into the air!" death_sound = 'sound/effects/altar_break.ogg' heathen_message = "There is a foreboding aura to the altar and you want nothing to do with it." selection_prompt = "You study the rituals on the altar..." selection_title = "Altar" - creation_message = "You kneel before the altar and your faith is rewarded with an %ITEM%!" - choosable_items = list("Eldritch Whetstone"= /obj/item/whetstone/cult, "Zealot's Blindfold" = /obj/item/clothing/glasses/night/cultblind, \ - "Flask of Unholy Water" = /obj/item/reagent_containers/food/drinks/bottle/unholywater, "Cultist Dagger" = /obj/item/melee/cultblade/dagger) + creation_message = "You kneel before the altar and your faith is rewarded with a %ITEM%!" + choosable_items = list("Eldritch Whetstone" = /obj/item/whetstone/cult, "Flask of Unholy Water" = /obj/item/reagent_containers/food/drinks/bottle/unholywater, + "Construct Shell" = /obj/structure/constructshell) /obj/structure/cult/functional/altar/New() . = ..() @@ -113,15 +142,17 @@ name = "daemon forge" desc = "A forge used in crafting the unholy weapons used by the armies of a cult." icon_state = "forge" + light_range = 2 + light_color = LIGHT_COLOR_LAVA max_integrity = 300 //Made of metal - death_message = "The forge falls apart, its lava cooling and winking away!" + death_message = "The forge falls apart, its lava cooling and winking away!" death_sound = 'sound/effects/forge_destroy.ogg' heathen_message = "Your hand feels like it's melting off as you try to touch the forge." selection_prompt = "You study the schematics etched on the forge..." selection_title = "Forge" - creation_message = "You work the forge as dark knowledge guides your hands, creating %ITEM%!" - choosable_items = list("Shielded Robe" = /obj/item/clothing/suit/hooded/cultrobes/cult_shield, "Flagellant's Robe" = /obj/item/clothing/suit/hooded/cultrobes/flagellant_robe, \ - "Cultist Hardsuit" = /obj/item/storage/box/cult) + creation_message = "You work the forge as dark knowledge guides your hands, creating a %ITEM%!" + choosable_items = list("Shielded Robe" = /obj/item/clothing/suit/hooded/cultrobes/cult_shield, "Flagellant's Robe" = /obj/item/clothing/suit/hooded/cultrobes/flagellant_robe, + "Mirror Shield" = /obj/item/shield/mirror) /obj/structure/cult/functional/forge/New() . = ..() @@ -131,45 +162,48 @@ if(istype(I, /obj/item/grab)) var/obj/item/grab/G = I if(!iscarbon(G.affecting)) - to_chat(user, "You may only dunk carbon-based creatures!") - return 0 + return FALSE if(G.affecting == LAVA_PROOF) to_chat(user, "[G.affecting] is immune to lava!") - return 0 + return FALSE if(G.affecting.stat == DEAD) to_chat(user, "[G.affecting] is dead!") - return 0 + return FALSE var/mob/living/carbon/human/C = G.affecting - C.visible_message("[user] dunks [C]'s face into [src]'s lava!", \ + var/obj/item/organ/external/head/head = C.get_organ("head") + if(!head) + to_chat(user, "[C] has no head!") + return FALSE + + C.visible_message("[user] dunks [C]'s face into [src]'s lava!", "[user] dunks your face into [src]'s lava!") - if(!C.stat) - C.emote("scream") + C.emote("scream") + C.apply_damage(30, BURN, "head") // 30 fire damage because it's FUCKING LAVA + head.disfigure() // Your face is unrecognizable because it's FUCKING LAVA + C.UpdateDamageIcon() + add_attack_logs(user, C, "Lava-dunked into [src]") user.changeNext_move(CLICK_CD_MELEE) - var/obj/item/organ/external/head/head = C.get_organ("head") - if(head) - C.apply_damage(30, BURN, "head") //30 fire damage because it's FUCKING LAVA - head.disfigure() //Your face is unrecognizable because it's FUCKING LAVA - return 1 + return TRUE return ..() GLOBAL_LIST_INIT(blacklisted_pylon_turfs, typecacheof(list( - /turf/simulated/floor/engine/cult, - /turf/space, - /turf/simulated/floor/plating/lava, - /turf/simulated/floor/chasm, - /turf/simulated/wall/cult, - /turf/simulated/wall/cult/artificer, - /turf/unsimulated/wall + /turf/simulated/floor/engine/cult, + /turf/space, + /turf/simulated/floor/plating/lava, + /turf/simulated/floor/chasm, + /turf/simulated/wall/cult, + /turf/simulated/wall/cult/artificer, + /turf/unsimulated/wall ))) /obj/structure/cult/functional/pylon name = "pylon" desc = "A floating crystal that slowly heals those faithful to a cult." icon_state = "pylon" - light_range = 5 - light_color = "#3e0000" + light_range = 1.5 + light_color = LIGHT_COLOR_RED max_integrity = 50 //Very fragile - death_message = "The pylon's crystal vibrates and glows fiercely before violently shattering!" + death_message = "The pylon's crystal vibrates and glows fiercely before violently shattering!" death_sound = 'sound/effects/pylon_shatter.ogg' var/heal_delay = 30 @@ -190,24 +224,39 @@ GLOBAL_LIST_INIT(blacklisted_pylon_turfs, typecacheof(list( /obj/structure/cult/functional/pylon/Destroy() STOP_PROCESSING(SSobj, src) - return ..() + ..() + +/obj/structure/cult/functional/pylon/cult_conceal() + STOP_PROCESSING(SSobj, src) + ..() + +/obj/structure/cult/functional/pylon/cult_reveal() + START_PROCESSING(SSobj, src) + ..() /obj/structure/cult/functional/pylon/process() if(!anchored) return + if(last_heal <= world.time) last_heal = world.time + heal_delay for(var/mob/living/L in range(5, src)) - if(iscultist(L) || iswizard(L) || istype(L, /mob/living/simple_animal/shade) || istype(L, /mob/living/simple_animal/hostile/construct)) + if(iscultist(L) || iswizard(L) || isshade(L) || isconstruct(L)) if(L.health != L.maxHealth) new /obj/effect/temp_visual/heal(get_turf(src), "#960000") + if(ishuman(L)) L.heal_overall_damage(1, 1, TRUE, FALSE, TRUE) - if(istype(L, /mob/living/simple_animal/shade) || istype(L, /mob/living/simple_animal/hostile/construct)) + + else if(isshade(L) || isconstruct(L)) var/mob/living/simple_animal/M = L if(M.health < M.maxHealth) M.adjustHealth(-1) - if(last_corrupt <= world.time) + + if(ishuman(L) && L.blood_volume < BLOOD_VOLUME_NORMAL) + L.blood_volume += 1 + + if(!is_station_level(z) && last_corrupt <= world.time) //Pylons only convert tiles on offstation bases to help hide onstation cults from meson users var/list/validturfs = list() var/list/cultturfs = list() for(var/T in circleviewturfs(src, 5)) @@ -234,23 +283,23 @@ GLOBAL_LIST_INIT(blacklisted_pylon_turfs, typecacheof(list( else // Are we in space or something? No cult turfs or // convertable turfs? - last_corrupt = world.time + corrupt_delay*2 - - + last_corrupt = world.time + corrupt_delay * 2 /obj/structure/cult/functional/archives name = "archives" desc = "A desk covered in arcane manuscripts and tomes in unknown languages. Looking at the text makes your skin crawl." icon_state = "archives" + light_range = 1.5 + light_color = LIGHT_COLOR_FIRE max_integrity = 125 //Slightly sturdy - death_message = "The desk breaks apart, its books falling to the floor." + death_message = "The desk breaks apart, its books falling to the floor." death_sound = 'sound/effects/wood_break.ogg' heathen_message = "What do you hope to seek?" selection_prompt = "You flip through the black pages of the archives..." selection_title = "Archives" - creation_message = "You invoke the dark magic of the tomes creating %ITEM%!" - choosable_items = list("Supply Talisman" = /obj/item/paper/talisman/supply/weak, "Shuttle Curse" = /obj/item/shuttle_curse, \ - "Veil Shifter" = /obj/item/cult_shift) + creation_message = "You invoke the dark magic of the tomes creating a %ITEM%!" + choosable_items = list("Shuttle Curse" = /obj/item/shuttle_curse, "Zealot's Blindfold" = /obj/item/clothing/glasses/hud/health/night/cultblind, + "Veil Shifter" = /obj/item/cult_shift) //Add void torch to veil shifter spawn /obj/structure/cult/functional/archives/New() . = ..() @@ -258,11 +307,11 @@ GLOBAL_LIST_INIT(blacklisted_pylon_turfs, typecacheof(list( /obj/effect/gateway name = "gateway" - desc = "You're pretty sure that abyss is staring back" + desc = "You're pretty sure that the abyss is staring back" icon = 'icons/obj/cult.dmi' icon_state = "hole" - density = 1 - anchored = 1.0 + density = TRUE + anchored = TRUE /obj/effect/gateway/singularity_act() return @@ -275,17 +324,3 @@ GLOBAL_LIST_INIT(blacklisted_pylon_turfs, typecacheof(list( /obj/effect/gateway/Crossed(atom/movable/AM, oldloc) return - - -//Armor kit - -/obj/item/storage/box/cult - name = "Dark Forge Cache" - can_hold = list("/obj/item/clothing/suit/space/cult", "/obj/item/clothing/head/helmet/space/cult") - max_w_class = WEIGHT_CLASS_NORMAL - -/obj/item/storage/box/cult/New() - ..() - new /obj/item/clothing/suit/space/cult(src) - new /obj/item/clothing/head/helmet/space/cult(src) - return diff --git a/code/game/gamemodes/cult/ritual.dm b/code/game/gamemodes/cult/ritual.dm index 823a9a8980ebe..254914aabc3a6 100644 --- a/code/game/gamemodes/cult/ritual.dm +++ b/code/game/gamemodes/cult/ritual.dm @@ -1,320 +1,175 @@ #define CULT_ELDERGOD "eldergod" #define CULT_SLAUGHTER "slaughter" -/obj/effect/rune/proc/fizzle() - if(istype(src,/obj/effect/rune)) - usr.say(pick("Hakkrutju gopoenjim.", "Nherasai pivroiashan.", "Firjji prhiv mazenhor.", "Tanah eh wakantahe.", "Obliyae na oraie.", "Miyf hon vnor'c.", "Wakabai hij fen juswix.")) - else - usr.whisper(pick("Hakkrutju gopoenjim.", "Nherasai pivroiashan.", "Firjji prhiv mazenhor.", "Tanah eh wakantahe.", "Obliyae na oraie.", "Miyf hon vnor'c.", "Wakabai hij fen juswix.")) - for (var/mob/V in viewers(src)) - V.show_message("The markings pulse with a small burst of light, then fall dark.", 3, "You hear a faint fizzle.", 2) - return - -/obj/effect/rune/proc/check_icon() - if(!SSticker.mode)//work around for maps with runes and cultdat is not loaded all the way - var/bits = make_bit_triplet() - icon = get_rune(bits) - else - icon = get_rune_cult(invocation) - -/obj/item/tome - name = "arcane tome" - desc = "An old, dusty tome with frayed edges and a sinister-looking cover." - icon_state ="tome" - throw_speed = 2 - throw_range = 5 +/obj/item/melee/cultblade/dagger + name = "ritual dagger" + desc = "A strange dagger said to be used by sinister groups for \"preparing\" a corpse before sacrificing it to their dark gods." + icon_state = "blood_dagger" + item_state = "blood_dagger" w_class = WEIGHT_CLASS_SMALL - var/scribereduct = 0 - var/canbypass = 0 //ADMINBUS - -/obj/item/tome/accursed - name = "accursed tome" - desc = "An arcane tome still empowered with a shadow of its former consecration." - scribereduct = 30 //faster because it's made by corrupting a bible - -/obj/item/tome/imbued //Admin-only tome, allows instant drawing of runes - name = "imbued arcane tome" - desc = "An arcane tome granted by the Geometer itself." - scribereduct = 50 - canbypass = 1 - -/obj/item/tome/New() - if(!SSticker.mode) - icon_state = "tome" - else - icon_state = SSticker.cultdat.tome_icon + force = 15 + throwforce = 25 + armour_penetration = 35 + sprite_sheets_inhand = null // Override parent + var/drawing_rune = FALSE + var/scribe_multiplier = 1 // Lower is faster + +/obj/item/melee/cultblade/dagger/adminbus + name = "ritual dagger of scribing, +1" + desc = "VERY fast culto scribing at incredible hihg speed" + force = 16 + scribe_multiplier = 0.1 + +/obj/item/melee/cultblade/dagger/New() ..() + if(SSticker.mode) + icon_state = SSticker.cultdat.dagger_icon + item_state = SSticker.cultdat.dagger_icon -/obj/item/tome/examine(mob/user) +/obj/item/melee/cultblade/dagger/examine(mob/user) . = ..() if(iscultist(user) || user.stat == DEAD) - . += "The scriptures of [SSticker.cultdat.entity_title3]. Allows the scribing of runes and access to the knowledge archives of the cult of [SSticker.cultdat.entity_name]." - . += "Striking another cultist with it will purge holy water from them." - . += "Striking a non-cultist, however, will sear their flesh." + . += "A dagger gifted by [SSticker.cultdat.entity_title3]. Allows the scribing of runes and access to the knowledge archives of the cult of [SSticker.cultdat.entity_name]." + . += "Striking another cultist with it will purge holy water from them." + . += "Striking a noncultist will tear their flesh." -/obj/item/tome/attack(mob/living/M, mob/living/user) - if(!istype(M)) - return - if(!iscultist(user)) - return ..() +/obj/item/melee/cultblade/dagger/attack(mob/living/M, mob/living/user) if(iscultist(M)) if(M.reagents && M.reagents.has_reagent("holywater")) //allows cultists to be rescued from the clutches of ordained religion to_chat(user, "You remove the taint from [M].") - var/holy2unholy = M.reagents.get_reagent_amount("holywater") + var/amount = M.reagents.get_reagent_amount("holywater") M.reagents.del_reagent("holywater") - M.reagents.add_reagent("unholywater",holy2unholy) + M.reagents.add_reagent("unholywater", amount) add_attack_logs(user, M, "Hit with [src], removing the holy water from them") - return - M.take_organ_damage(0, 15) //Used to be a random between 5 and 20 - playsound(M, 'sound/weapons/sear.ogg', 50, 1) - M.visible_message("[user] strikes [M] with [src]!", \ - "[user] strikes you with [src], searing your flesh!") - flick("tome_attack", src) - user.do_attack_animation(M) - add_attack_logs(user, M, "Hit with [src]") + return FALSE + . = ..() -/obj/item/tome/attack_self(mob/user) +/obj/item/melee/cultblade/dagger/attack_self(mob/user) if(!iscultist(user)) - to_chat(user, "[src] seems full of unintelligible shapes, scribbles, and notes. Is this some sort of joke?") + to_chat(user, "[src] is covered in unintelligible shapes and markings.") return - open_tome(user) - -/obj/item/tome/proc/open_tome(mob/user) - var/choice = alert(user,"You open the tome...",,"Scribe Rune","More Information","Cancel") - switch(choice) - if("More Information") - read_tome(user) - if("Scribe Rune") - scribe_rune(user) - if("Cancel") - return - -/obj/item/tome/proc/read_tome(mob/user) - var/text = list() - text += "
Archives of [SSticker.cultdat.entity_title1]



" - text += "A rune's name and effects can be revealed by examining the rune.<

" - - text += "Rite of Binding
This rune is one of the most important runes the cult has, being the only way to create new talismans. A blank sheet of paper must be on top of the rune. After \ - invoking it and choosing which talisman you desire, the paper will be converted, after some delay into a talisman.

" - - text += "Teleport
This rune is unique in that it requires a keyword before the scribing can begin. When invoked, it will find any other Teleport runes; \ - If any are found, the user can choose which rune to send to. Upon activation, the rune teleports everything above it to the selected rune.

" - - text += "Rite of Enlightenment
This rune is critical to the success of the cult. It will allow you to convert normal crew members into cultists. \ - To do this, simply place the crew member upon the rune and invoke it. This rune requires two invokers to use. If the target to be converted is mindshielded or a certain assignment, they will \ - be unable to be converted. People [SSticker.cultdat.entity_title3] wishes sacrificed will also be ineligible for conversion, and anyone with a shielding presence like the null rod will not be converted.
\ - Successful conversions will produce a tome for the new cultist.

" - - text += "Rite of Tribute
This rune is necessary to achieve your goals. Simply place any dead creature upon the rune and invoke it (this will not \ - target cultists!). If this creature has a mind, a soulstone will be created and the creature's soul transported to it. Sacrificing the dead can be done alone, but sacrificing living crew or your cult's target will require 3 cultists. \ - Soulstones used on construct shells will move that soul into a powerful construct of your choice.

" - - - text += "Rite of Resurrection
This rune requires two corpses. To perform the ritual, place the corpse you wish to revive onto \ - the rune and the offering body adjacent to it. When the rune is invoked, the body to be sacrificed will turn to dust, the life force flowing into the revival target. Assuming the target is not moved \ - within a few seconds, they will be brought back to life, healed of all ailments.

" - - text += "Rite of Disruption
Robotic lifeforms have time and time again been the downfall of fledgling cults. This rune may allow you to gain the upper \ - hand against these pests. By using the rune, a large electromagnetic pulse will be emitted from the rune's location. The size of the EMP will grow significantly for each additional adjacent cultist when the \ - rune is activated.

" - - text += "Astral Communion
This rune is perhaps the most ingenious rune that is usable by a single person. Upon invoking the rune, the \ - user's spirit will be ripped from their body. In this state, the user's physical body will be locked in place to the rune itself - any attempts to move it will result in the rune pulling it back. \ - The body will also take constant damage while in this form, and may even die. The user's spirit will contain their consciousness, and will allow them to freely wander the station as a ghost. This may \ - also be used to commune with the dead.

" - - text += "Rite of the Corporeal Shield
While simple, this rune serves an important purpose in defense and hindering passage. When invoked, the \ - rune will draw a small amount of life force from the user and make the space above the rune completely dense, rendering it impassable to all but the most complex means. The rune may be invoked again to \ - undo this effect and allow passage again.

" - - text += "Rite of Joined Souls
This rune allows the cult to free other cultists with ease. When invoked, it will allow the user to summon a single cultist to the rune from \ - any location. It requires two invokers, and will damage each invoker slightly.

" - - text += "Blood Boil
When invoked, this rune will do a massive amount of damage to all non-cultist viewers, but it will also emit a small explosion upon invocation. \ - It requires three invokers.

" - - text += "Leeching
When invoked, this rune will transfer life force from the victim to the invoker.

" - - text += "Rite of Spectral Manifestation
This rune allows you to summon spirits as humanoid fighters. When invoked, a spirit above the rune will be brought to life as a human, wearing nothing, that seeks only to serve you and [SSticker.cultdat.entity_title3]. \ - However, the spirit's link to reality is fragile - you must remain on top of the rune, and you will slowly take damage. Upon stepping off the rune, all summoned spirits will dissipate, dropping their items to the ground. You may manifest \ - multiple spirits with one rune, but you will rapidly take damage in doing so.

" - - text += "Ritual of Dimensional Rending
This rune is necessary to achieve your goals. On attempting to scribe it, it will produce shields around you and alert everyone you are attempting to scribe it; it takes a very long time to scribe, \ - and does massive damage to the one attempting to scribe it.
Invoking it requires 9 invokers and the sacrifice of a specific crewmember, and once invoked, will summon [SSticker.cultdat.entity_title3], [SSticker.cultdat.entity_name]. \ - This will complete your objectives.


" - - text += "Talisman of Teleportation
The talisman form of the Teleport rune will transport the invoker to a selected Teleport rune once.

" - - text += "Talisman of Fabrication
This talisman is the main way of creating construct shells. To use it, one must strike 30 sheets of metal with the talisman. The sheets will then be twisted into a construct shell, ready to receive a soul to occupy it.

" - - text += "Talisman of Tome Summoning
This talisman will produce a single tome at your feet.

" - - text += "Talisman of Veiling/Revealing
This talisman will hide runes on its first use, and on the second, will reveal runes.

" - - text += "Talisman of Disguising
This talisman will permanently disguise all nearby runes as crayon runes.

" - - text += "Talisman of Electromagnetic Pulse
This talisman will EMP anything else nearby. It disappears after one use.

" - - text += "Talisman of Stunning
Attacking a target will knock them down for a long duration in addition to inhibiting their speech. \ - Robotic lifeforms will suffer the effects of a heavy electromagnetic pulse instead.

" - - text += "Talisman of Armaments
The Talisman of Arming will equip the user with armored robes, a backpack, shoes, an eldritch longsword, and an empowered bola. Any equipment that cannot \ - be equipped will not be summoned, weaponry will be put on the floor below the user. Attacking a fellow cultist with it will instead equip them.

" - - text += "Talisman of Horrors
The Talisman of Horror must be applied directly to the victim, it will shatter your victim's mind with visions of the end-times that may incapacitate them.

" - - text += "Talisman of Shackling
The Talisman of Shackling must be applied directly to the victim, it has 4 uses and cuffs victims with magic shackles that disappear when removed.

" - - text += "In addition to these runes, the cult has a small selection of equipment and constructs.

" - - text += "Equipment:

" - - text += "Cult Blade
Cult blades are sharp weapons that, notably, cannot be used by non-cultists. These blades are produced by the Talisman of Arming.

" - - text += "Cult Bola
Cult bolas are strong bolas, useful for snaring targets. These bolas are produced by the Talisman of Arming.

" - - text += "Cult Robes
Cult robes are heavily armored robes. These robes are produced by the Talisman of Arming.

" - - text += "Soulstone
A soulstone is a simple piece of magic, produced either via the starter talisman or by sacrificing humans. Using it on an unconscious or dead humanoid, or on a Shade, will trap their soul in the stone, allowing its use in construct shells. \ -
The soul within can also be released as a Shade by using it in-hand.

" - - text += "Construct Shell
A construct shell is useless on its own, but placing a filled soulstone within it allows you to produce your choice of a Wraith, a Juggernaut, or an Artificer. \ -
Each construct has uses, detailed below in Constructs. Construct shells can be produced via the starter talisman or the Rite of Fabrication.

" - - text += "Constructs:

" - - text += "Shade
While technically not a construct, the Shade is produced when released from a soulstone. It is quite fragile and has weak melee attacks, but is fully healed when recaptured by a soulstone.

" - - text += "Wraith
The Wraith is a fast, lethal melee attacker which can jaunt through walls. However, it is only slightly more durable than a shade.

" - - text += "Juggernaut
The Juggernaut is a slow, but durable, melee attacker which can produce temporary forcewalls. It will also reflect most lethal energy weapons.

" - - text += "Artificer
The Artificer is a weak and fragile construct, able to heal other constructs, produce more soulstones and construct shells, \ - construct fortifying cult walls and flooring, and finally, it can release a few indiscriminate stunning missiles.

" - - text += "Harvester
If you see one, know that you have done all you can and your life is void.

" - - var/text_string = jointext(text, null) - var/datum/browser/popup = new(user, "tome", "", 800, 600) - popup.set_content(text_string) - popup.open() - return 1 - -/obj/item/tome/proc/finale_runes_ok(mob/living/user, obj/effect/rune/rune_to_scribe) - var/datum/game_mode/cult/cult_mode = SSticker.mode - var/area/A = get_area(src) - if(GAMEMODE_IS_CULT) - if(!canbypass)//not an admin-tome, check things - if(!cult_mode.narsie_condition_cleared) - to_chat(user, "There is still more to do before unleashing [SSticker.cultdat.entity_name]'s' power!") - return 0 - if(!cult_mode.eldergod) - to_chat(user, "\"I am already here. There is no need to try to summon me now.\"") - return 0 - if(cult_mode.demons_summoned) - to_chat(user, "\"We are already here. There is no need to try to summon us now.\"") - return 0 - if(!((CULT_ELDERGOD in cult_mode.objectives) || (CULT_SLAUGHTER in cult_mode.objectives))) - to_chat(user, "[SSticker.cultdat.entity_name]'s power does not wish to be unleashed!") - return 0 - if(!(A in GLOB.summon_spots)) - to_chat(user, "[SSticker.cultdat.entity_name] can only be summoned where the veil is weak - in [english_list(GLOB.summon_spots)]!") - return 0 - var/confirm_final = alert(user, "This is the FINAL step to summon your deity's power. It is a long, painful ritual and the crew will be alerted to your presence.", "Are you prepared for the final battle?", "My life for [SSticker.cultdat.entity_name]!", "No") + scribe_rune(user) + +/obj/item/melee/cultblade/dagger/proc/narsie_rune_check(mob/living/user, area/A) + var/datum/game_mode/gamemode = SSticker.mode + + if(gamemode.cult_objs.cult_status < NARSIE_NEEDS_SUMMONING) + to_chat(user, "[SSticker.cultdat.entity_name] is not ready to be summoned yet!") + return FALSE + if(gamemode.cult_objs.cult_status == NARSIE_HAS_RISEN) + to_chat(user, "\"I am already here. There is no need to try to summon me now.\"") + return FALSE + + var/list/summon_areas = gamemode.cult_objs.obj_summon.summon_spots + if(!(A in summon_areas)) + to_chat(user, "[SSticker.cultdat.entity_name] can only be summoned where the veil is weak - in [english_list(summon_areas)]!") + return FALSE + var/confirm_final = alert(user, "This is the FINAL step to summon your deities power, it is a long, painful ritual and the crew will be alerted to your presence AND your location!", + "Are you prepared for the final battle?", "My life for [SSticker.cultdat.entity_name]!", "No") + if(user) if(confirm_final == "No" || confirm_final == null) - to_chat(user, "You decide to prepare further before scribing the rune.") - return 0 + to_chat(user, "You decide to prepare further before scribing the rune.") + return FALSE else - return 1 - else//the game mode is not cult..but we ARE a cultist...ALL ON THE ADMINBUS - var/confirm_final = alert(user, "This is the FINAL step to summon your deity's power. It is a long, painful ritual and the crew will be alerted to your presence.", "Are you prepared for the final battle?", "My life for [SSticker.cultdat.entity_name]!", "No") - if(confirm_final == "No" || confirm_final == null) - to_chat(user, "You decide to prepare further before scribing the rune.") - return 0 - else - return 1 - -/obj/item/tome/proc/scribe_rune(mob/living/user) - var/turf/runeturf = get_turf(user) - if(isspaceturf(runeturf)) - return - var/chosen_keyword - var/obj/effect/rune/rune_to_scribe - var/entered_rune_name - var/list/possible_runes = list() + if(locate(/obj/effect/rune) in range(1, user)) + to_chat(user, "You need a space cleared of runes before you can summon [SSticker.cultdat.entity_title1]!") + return FALSE + else + return TRUE + +/obj/item/melee/cultblade/dagger/proc/can_scribe(mob/living/user) + if(!src || !user || loc != user || user.incapacitated()) + return FALSE + var/turf/T = get_turf(user) + if(isspaceturf(T)) + return FALSE + if((locate(/obj/effect/rune) in T) || (locate(/obj/effect/rune/narsie) in range(1, T))) + to_chat(user, "There's already a rune here!") + return FALSE + return TRUE + + +/obj/item/melee/cultblade/dagger/proc/scribe_rune(mob/living/user) var/list/shields = list() - var/area/A = get_area(src) - if(locate(/obj/effect/rune) in runeturf) - to_chat(user, "There is already a rune here.") - return - for(var/T in subtypesof(/obj/effect/rune) - /obj/effect/rune/malformed) - var/obj/effect/rune/R = T - if(initial(R.cultist_name)) - possible_runes.Add(initial(R.cultist_name)) //This is to allow the menu to let cultists select runes by name rather than by object path. I don't know a better way to do this - if(!possible_runes.len) - return - entered_rune_name = input(user, "Choose a rite to scribe.", "Sigils of Power") as null|anything in possible_runes - if(!Adjacent(user) || !src || QDELETED(src) || user.incapacitated()) - return - for(var/T in typesof(/obj/effect/rune)) - var/obj/effect/rune/R = T - if(initial(R.cultist_name) == entered_rune_name) - rune_to_scribe = R - if(initial(R.req_keyword)) - var/the_keyword = stripped_input(usr, "Please enter a keyword for the rune.", "Enter Keyword", "") - if(!the_keyword) - return - chosen_keyword = the_keyword - break - if(!rune_to_scribe) + var/list/possible_runes = list() + var/keyword + + if(!can_scribe(user)) // Check this before anything else return - runeturf = get_turf(user) //we may have moved. adjust as needed... - A = get_area(src) - if(locate(/obj/effect/rune) in runeturf) - to_chat(user, "There is already a rune here.") + + // Choosing a rune + for(var/I in (subtypesof(/obj/effect/rune) - /obj/effect/rune/malformed)) + var/obj/effect/rune/R = I + var/rune_name = initial(R.cultist_name) + if(rune_name) + possible_runes[rune_name] = R + if(!length(possible_runes)) return - if(!Adjacent(user) || !src || QDELETED(src) || user.incapacitated()) + + var/chosen_rune = input(user, "Choose a rite to scribe.", "Sigils of Power") as null|anything in possible_runes + if(!chosen_rune) return - if(ispath(rune_to_scribe, /obj/effect/rune/narsie) || ispath(rune_to_scribe, /obj/effect/rune/slaughter))//may need to change this - Fethas - if(finale_runes_ok(user,rune_to_scribe)) - A = get_area(src) - if(!(A in GLOB.summon_spots)) // Check again to make sure they didn't move - to_chat(user, "The ritual can only begin where the veil is weak - in [english_list(GLOB.summon_spots)]!") + var/obj/effect/rune/rune = possible_runes[chosen_rune] + var/narsie_rune = FALSE + if(rune == /obj/effect/rune/narsie) + narsie_rune = TRUE + if(initial(rune.req_keyword)) + keyword = stripped_input(user, "Please enter a keyword for the rune.", "Enter Keyword") + if(!keyword) + return + + // Check if the rune is allowed + var/area/A = get_area(src) + var/turf/runeturf = get_turf(user) + var/datum/game_mode/gamemode = SSticker.mode + if(ispath(rune, /obj/effect/rune/summon)) + if(!is_station_level(runeturf.z) || istype(A, /area/space)) + to_chat(user, "The veil is not weak enough here to summon a cultist, you must be on station!") + return + + if(narsie_rune) + if(!narsie_rune_check(user, A)) + return // don't do shit + else + var/list/summon_areas = gamemode.cult_objs.obj_summon.summon_spots + if(!(A in summon_areas)) // Check again to make sure they didn't move + to_chat(user, "The ritual can only begin where the veil is weak - in [english_list(summon_areas)]!") return - GLOB.command_announcement.Announce("Figments from an eldritch god are being summoned somewhere on the station from an unknown dimension. Disrupt the ritual at all costs!","Central Command Higher Dimensional Affairs", 'sound/AI/spanomalies.ogg') - for(var/B in spiral_range_turfs(1, user, 1)) - var/turf/T = B - var/obj/machinery/shield/N = new(T) - N.name = "Rune-Scriber's Shield" - N.desc = "A potent shield summoned by cultists to protect them while they prepare the final ritual" - N.icon_state = "shield-cult" - N.health = 60 + GLOB.command_announcement.Announce("Figments from an eldritch god are being summoned into the [A.map_name] from an unknown dimension. Disrupt the ritual at all costs!", "Central Command Higher Dimensional Affairs", 'sound/AI/spanomalies.ogg') + for(var/I in spiral_range_turfs(1, user, 1)) + var/turf/T = I + var/obj/machinery/shield/cult/narsie/N = new(T) shields |= N - else - return//don't do shit + // Check everything again, in case they moved + if(!can_scribe(user)) + return + + // Draw the rune var/mob/living/carbon/human/H = user - var/dam_zone = pick("head", "chest", "groin", "l_arm", "l_hand", "r_arm", "r_hand", "l_leg", "l_foot", "r_leg", "r_foot") - var/obj/item/organ/external/affecting = H.get_organ(ran_zone(dam_zone)) - user.visible_message("[user] cuts open [user.p_their()] [affecting] and begins writing in [user.p_their()] own blood!", "You slice open your [affecting] and begin drawing a sigil of [SSticker.cultdat.entity_title3].") - user.apply_damage(initial(rune_to_scribe.scribe_damage), BRUTE , affecting) - if(!do_after(user, initial(rune_to_scribe.scribe_delay)-scribereduct, target = get_turf(user))) - for(var/V in shields) + H.cult_self_harm(initial(rune.scribe_damage), TRUE) + if(!do_after(user, initial(rune.scribe_delay) * scribe_multiplier, target = runeturf)) + for(var/V in shields) // Only used for the 'Tear Veil' rune var/obj/machinery/shield/S = V if(S && !QDELETED(S)) qdel(S) return - if(locate(/obj/effect/rune) in runeturf) - to_chat(user, "There is already a rune here.") - return - user.visible_message("[user] creates a strange circle in [user.p_their()] own blood.", \ - "You finish drawing the arcane markings of [SSticker.cultdat.entity_title3].") - for(var/V in shields) + + user.visible_message("[user] creates a strange circle in [user.p_their()] own blood.", + "You finish drawing the arcane markings of [SSticker.cultdat.entity_title3].") + for(var/V in shields) // Only for the 'Tear Veil' rune var/obj/machinery/shield/S = V if(S && !QDELETED(S)) qdel(S) - var/obj/effect/rune/R = new rune_to_scribe(runeturf, chosen_keyword) + + var/obj/effect/rune/R = new rune(runeturf, keyword) + if(narsie_rune) + for(var/obj/effect/rune/I in orange(1, R)) + qdel(I) + R.blood_DNA = list() R.blood_DNA[H.dna.unique_enzymes] = H.dna.blood_type R.add_hiddenprint(H) - to_chat(user, "The [lowertext(initial(rune_to_scribe.cultist_name))] rune [initial(rune_to_scribe.cultist_desc)]") + R.color = H.dna.species.blood_color + R.rune_blood_color = H.dna.species.blood_color + to_chat(user, "The [lowertext(initial(rune.cultist_name))] rune [initial(rune.cultist_desc)]") diff --git a/code/game/gamemodes/cult/runes.dm b/code/game/gamemodes/cult/runes.dm index 02cd7b6db6570..32b6614c061e6 100644 --- a/code/game/gamemodes/cult/runes.dm +++ b/code/game/gamemodes/cult/runes.dm @@ -1,49 +1,66 @@ -GLOBAL_LIST_EMPTY(sacrificed) -GLOBAL_LIST_INIT(non_revealed_runes, (subtypesof(/obj/effect/rune) - /obj/effect/rune/malformed)) +GLOBAL_LIST_EMPTY(sacrificed) // A mixed list of minds and mobs +GLOBAL_LIST_EMPTY(wall_runes) // A list of all cult shield walls +GLOBAL_LIST_EMPTY(teleport_runes) // I'll give you two guesses /* This file contains runes. Runes are used by the cult to cause many different effects and are paramount to their success. -They are drawn with an arcane tome in blood, and are distinguishable to cultists and normal crew by examining. +They are drawn with a ritual dagger in blood, and are distinguishable to cultists and normal crew by examining. Fake runes can be drawn in crayon to fool people. Runes can either be invoked by one's self or with many different cultists. Each rune has a specific incantation that the cultists will say when invoking it. -To draw a rune, use an arcane tome. +To draw a rune, use a ritual dagger. */ /obj/effect/rune + /// Name non-cultists see name = "rune" + /// Name that cultists see var/cultist_name = "basic rune" + /// Description that non-cultists see desc = "An odd collection of symbols drawn in what seems to be blood." + /// Description that cultists see var/cultist_desc = "a basic rune with no function." //This is shown to cultists who examine the rune in order to determine its true purpose. - anchored = 1 + anchored = TRUE icon = 'icons/obj/rune.dmi' icon_state = "1" resistance_flags = FIRE_PROOF | UNACIDABLE | ACID_PROOF + mouse_opacity = MOUSE_OPACITY_OPAQUE // So that runes aren't so hard to click var/visibility = 0 var/view_range = 7 - layer = TURF_LAYER - - var/invocation = "Aiy ele-mayo!" //This is said by cultists when the rune is invoked. - var/req_cultists = 1 //The amount of cultists required around the rune to invoke it. If only 1, any cultist can invoke it. - var/rune_in_use = 0 // Used for some runes, this is for when you want a rune to not be usable when in use. - - var/scribe_delay = 50 //how long the rune takes to create - var/scribe_damage = 0.1 //how much damage you take doing it - - var/allow_excess_invokers = 0 //if we allow excess invokers when being invoked - var/construct_invoke = 1 //if constructs can invoke it - - var/req_keyword = 0 //If the rune requires a keyword - go figure amirite - var/keyword //The actual keyword for the rune - - var/invoke_damage = 0 //how much damage invokers take when invoking it - + layer = SIGIL_LAYER + color = COLOR_BLOOD_BASE + + /// What is said by cultists when the rune is invoked + var/invocation = "Aiy ele-mayo!" + ///The amount of cultists required around the rune to invoke it. If only 1, any cultist can invoke it. + var/req_cultists = 1 + /// Used for some runes, this is for when you want a rune to not be usable when in use. + var/rune_in_use = FALSE + + /// How long the rune takes to create (Currently only different for the Nar'Sie rune) + var/scribe_delay = 5 SECONDS + /// How much damage you take from drawing the rune + var/scribe_damage = 1 + + /// If nearby cultists will also chant when invoked + var/allow_excess_invokers = 0 + /// If constructs can invoke it + var/construct_invoke = TRUE + + /// If the rune requires a keyword (e.g. Teleport runes) + var/req_keyword = FALSE + /// The actual keyword for the rune + var/keyword + + /// How much damage cultists take when invoking it (This includes constructs) + var/invoke_damage = 0 + /// The color of the rune. (Based on species blood color) + var/rune_blood_color = COLOR_BLOOD_BASE /obj/effect/rune/New(loc, set_keyword) ..() if(set_keyword) keyword = set_keyword - check_icon() var/image/blood = image(loc = src) blood.override = 1 for(var/mob/living/silicon/ai/AI in GLOB.player_list) @@ -56,18 +73,29 @@ To draw a rune, use an arcane tome. . += "Effects: [capitalize(cultist_desc)]" . += "Required Acolytes: [req_cultists]" if(req_keyword && keyword) - . += "Keyword: [keyword]" + . += "Keyword: [keyword]" /obj/effect/rune/attackby(obj/I, mob/user, params) - if(istype(I, /obj/item/tome) && iscultist(user)) - to_chat(user, "You carefully erase the [lowertext(cultist_name)] rune.") - qdel(src) + if(istype(I, /obj/item/melee/cultblade/dagger) && iscultist(user)) + // Telerunes with portals open + if(istype(src, /obj/effect/rune/teleport)) + var/obj/effect/rune/teleport/T = src // Can't erase telerunes if they have a portal open + if(T.inner_portal || T.outer_portal) + to_chat(user, "The portal needs to close first!") + return + + // Everything else + var/obj/item/melee/cultblade/dagger/D = I + user.visible_message("[user] begins to erase [src] with [I].") + if(do_after(user, initial(scribe_delay) * D.scribe_multiplier, target = src)) + to_chat(user, "You carefully erase the [lowertext(cultist_name)] rune.") + qdel(src) return if(istype(I, /obj/item/nullrod)) if(iscultist(user))//cultist..what are doing..cultist..staph... user.drop_item() - user.visible_message("[I] suddenly glows with white light, forcing [user] to drop it in pain!", \ - "[I] suddenly glows with a white light that sears your hand, forcing you to drop it!") + user.visible_message("[I] suddenly glows with a white light, forcing [user] to drop it in pain!", \ + "[I] suddenly glows with a white light that sears your hand, forcing you to drop it!") // TODO: Make this actually burn your hand return to_chat(user,"You disrupt the magic of [src] with [I].") qdel(src) @@ -75,39 +103,33 @@ To draw a rune, use an arcane tome. return ..() /obj/effect/rune/attack_hand(mob/living/user) + user.Move_Pulled(src) // So that you can still drag things onto runes if(!iscultist(user)) to_chat(user, "You aren't able to understand the words of [src].") return var/list/invokers = can_invoke(user) - if(invokers.len >= req_cultists) + if(length(invokers) >= req_cultists) invoke(invokers) else - fail_invoke(user) - + fail_invoke() /obj/effect/rune/attack_animal(mob/living/simple_animal/M) - if(istype(M, /mob/living/simple_animal/shade) || istype(M, /mob/living/simple_animal/hostile/construct)) + if(isshade(M) || isconstruct(M)) if(construct_invoke || !iscultist(M)) //if you're not a cult construct we want the normal fail message attack_hand(M) else to_chat(M, "You are unable to invoke the rune!") -/obj/effect/rune/proc/talismanhide() //for talisman of revealing/hiding +/obj/effect/rune/cult_conceal() //for concealing spell visible_message("[src] fades away.") invisibility = INVISIBILITY_OBSERVER alpha = 100 //To help ghosts distinguish hidden runes -/obj/effect/rune/proc/talismanreveal() //for talisman of revealing/hiding +/obj/effect/rune/cult_reveal() //for revealing spell invisibility = 0 visible_message("[src] suddenly appears!") alpha = initial(alpha) -/obj/effect/rune/proc/talismanfake() //for rune disguising - var/runenum = pick(1,2,3,4,5,6) - visible_message("[src] takes on a waxy apperance!") - icon = 'icons/effects/crayondecal.dmi' - icon_state = "rune[runenum]" - color = rgb(255, 0, 0) /* There are a few different procs each rune runs through when a cultist activates it. @@ -116,31 +138,29 @@ invoke() is the rune's actual effects. fail_invoke() is called when the rune fails, via not enough people around or otherwise. Typically this just has a generic 'fizzle' effect. structure_check() searches for nearby cultist structures required for the invocation. Proper structures are pylons, forges, archives, and altars. */ - -/obj/effect/rune/proc/can_invoke(var/mob/living/user) +/obj/effect/rune/proc/can_invoke(mob/living/user) //This proc determines if the rune can be invoked at the time. If there are multiple required cultists, it will find all nearby cultists. var/list/invokers = list() //people eligible to invoke the rune var/list/chanters = list() //people who will actually chant the rune when passed to invoke() if(invisibility == INVISIBILITY_OBSERVER)//hidden rune return + // Get the user if(user) chanters |= user invokers |= user + // Get anyone nearby if(req_cultists > 1 || allow_excess_invokers) for(var/mob/living/L in range(1, src)) if(iscultist(L)) if(L == user) continue - if(ishuman(L)) - var/mob/living/carbon/human/H = L - if(!H.can_speak()) - continue if(L.stat) continue invokers |= L - if(invokers.len >= req_cultists) + + if(length(invokers) >= req_cultists) // If there's enough invokers if(allow_excess_invokers) - chanters |= invokers + chanters |= invokers // Let the others join in too else invokers -= user shuffle(invokers) @@ -149,10 +169,12 @@ structure_check() searches for nearby cultist structures required for the invoca chanters |= L return chanters -/obj/effect/rune/proc/invoke(var/list/invokers) +/obj/effect/rune/proc/invoke(list/invokers) //This proc contains the effects of the rune as well as things that happen afterwards. If you want it to spawn an object and then delete itself, have both here. for(var/M in invokers) var/mob/living/L = M + if(!L) + return if(invocation) if(!L.IsVocal()) L.emote("gestures ominously.") @@ -164,37 +186,52 @@ structure_check() searches for nearby cultist structures required for the invoca to_chat(L, "[src] saps your strength!") do_invoke_glow() -/obj/effect/rune/proc/burn_invokers(var/list/mobstoburn) - for(var/M in mobstoburn) - var/mob/living/L = M - to_chat(L, "\"YOUR SOUL BURNS WITH YOUR ARROGANCE!!!\"") - if(L.reagents) - L.reagents.add_reagent("hell_water", 10) - L.Weaken(7) - L.Stun(7) - fail_invoke() +/** + * Spawns the phase in/out effects for a cult teleport. + * + * Arguments: + * * user - Mob to teleport + * * location - Location to teleport from + * * target - Location to teleport to + */ +/obj/effect/rune/proc/teleport_effect(mob/living/user, turf/location, target) + new /obj/effect/temp_visual/dir_setting/cult/phase/out(location, user.dir) + new /obj/effect/temp_visual/dir_setting/cult/phase(target, user.dir) + // So that the mob only appears after the effect is finished + user.notransform = TRUE + user.invisibility = INVISIBILITY_MAXIMUM + sleep(12) + user.notransform = FALSE + user.invisibility = 0 /obj/effect/rune/proc/do_invoke_glow() - var/oldtransform = transform - spawn(0) //animate is a delay, we want to avoid being delayed - animate(src, transform = matrix()*2, alpha = 0, time = 5) //fade out - animate(transform = oldtransform, alpha = 255, time = 0) + var/oldtransform = transform + animate(src, transform = matrix() * 2, alpha = 0, time = 5) // Fade out + animate(transform = oldtransform, alpha = 255, time = 0) -/obj/effect/rune/proc/fail_invoke(var/mob/living/user) +/obj/effect/rune/proc/fail_invoke() //This proc contains the effects of a rune if it is not invoked correctly, through either invalid wording or not enough cultists. By default, it's just a basic fizzle. visible_message("The markings pulse with a small flash of red light, then fall dark.") - spawn(0) //animate is a delay, we want to avoid being delayed - animate(src, color = rgb(255, 0, 0), time = 0) - animate(src, color = initial(color), time = 5) + animate(src, color = rgb(255, 0, 0), time = 0) + animate(src, color = rune_blood_color, time = 5) + + +/obj/effect/rune/proc/check_icon() + if(!SSticker.mode)//work around for maps with runes and cultdat is not loaded all the way + var/bits = make_bit_triplet() + icon = get_rune(bits) + else + icon = get_rune_cult(invocation) + //Malformed Rune: This forms if a rune is not drawn correctly. Invoking it does nothing but hurt the user. /obj/effect/rune/malformed - cultist_name = "malformed rune" + cultist_name = "Malformed" cultist_desc = "a senseless rune written in gibberish. No good can come from invoking this." invocation = "Ra'sha yoka!" invoke_damage = 30 -/obj/effect/rune/malformed/invoke(var/list/invokers) +/obj/effect/rune/malformed/invoke(list/invokers) ..() for(var/M in invokers) var/mob/living/L = M @@ -205,204 +242,120 @@ structure_check() searches for nearby cultist structures required for the invoca var/obj/item/nullrod/N = locate() in src if(N) return N - return 0 - -//Rite of Binding: A paper on top of the rune to a talisman. -/obj/effect/rune/imbue - cultist_name = "Rite of Binding" - cultist_desc = "transforms paper into powerful magic talismans." - invocation = "H'drak v'loso, mir'kanas verbot!" - icon_state = "3" + return FALSE -/obj/effect/rune/imbue/invoke(var/list/invokers) - var/mob/living/user = invokers[1] //the first invoker is always the user - var/turf/T = get_turf(src) - var/list/papers_on_rune = list() - var/entered_talisman_name - var/obj/item/paper/talisman/talisman_type - var/list/possible_talismans = list() - for(var/obj/item/paper/P in T) - if(!P.info) - papers_on_rune.Add(P) - if(!papers_on_rune.len) - to_chat(user, "There must be a blank paper on top of [src]!") - fail_invoke() - log_game("Talisman Creation rune failed - no blank papers on rune") - return - if(rune_in_use) - to_chat(user, "[src] can only support one ritual at a time!") - fail_invoke() - log_game("Talisman Creation rune failed - already in use") - return - var/obj/item/paper/paper_to_imbue = pick(papers_on_rune) - for(var/I in subtypesof(/obj/item/paper/talisman) - /obj/item/paper/talisman/malformed - /obj/item/paper/talisman/supply - /obj/item/paper/talisman/supply/weak) - var/obj/item/paper/talisman/J = I - var/talisman_cult_name = initial(J.cultist_name) - if(talisman_cult_name) - possible_talismans[talisman_cult_name] = J //This is to allow the menu to let cultists select talismans by name - entered_talisman_name = input(user, "Choose a talisman to imbue.", "Talisman Choices") as null|anything in possible_talismans - talisman_type = possible_talismans[entered_talisman_name] - if(!Adjacent(user) || !src || QDELETED(src) || user.incapacitated() || rune_in_use || !talisman_type) - return - ..() - visible_message("Dark power begins to channel into the paper!.") - rune_in_use = 1 - if(!do_after(user, 100, target = get_turf(user))) - rune_in_use = 0 - return - new talisman_type(get_turf(src)) - visible_message("[src] glows with power, and bloody images form themselves on [paper_to_imbue].") - qdel(paper_to_imbue) - rune_in_use = 0 - -GLOBAL_LIST_EMPTY(teleport_runes) -/obj/effect/rune/teleport - cultist_name = "Teleport" - cultist_desc = "warps everything above it to another chosen teleport rune." - invocation = "Sas'so c'arta forbici!" - icon_state = "2" - req_keyword = 1 - var/listkey - invoke_damage = 6 //but theres some checks for z-level - -/obj/effect/rune/teleport/New(loc, set_keyword) - ..() - var/area/A = get_area(src) - var/locname = initial(A.name) - listkey = set_keyword ? "[set_keyword] [locname]":"[locname]" - GLOB.teleport_runes += src - -/obj/effect/rune/teleport/Destroy() - GLOB.teleport_runes -= src - return ..() - -/obj/effect/rune/teleport/invoke(var/list/invokers) - var/mob/living/user = invokers[1] //the first invoker is always the user - var/list/potential_runes = list() - var/list/teleportnames = list() - var/list/duplicaterunecount = list() - for(var/R in GLOB.teleport_runes) - var/obj/effect/rune/teleport/T = R - var/resultkey = T.listkey - if(resultkey in teleportnames) - duplicaterunecount[resultkey]++ - resultkey = "[resultkey] ([duplicaterunecount[resultkey]])" - else - teleportnames.Add(resultkey) - duplicaterunecount[resultkey] = 1 - if(T != src && is_level_reachable(T.z)) - potential_runes[resultkey] = T - - if(!potential_runes.len) - to_chat(user, "There are no valid runes to teleport to!") - log_game("Teleport rune failed - no other teleport runes") - fail_invoke() - return - - if(!is_level_reachable(user.z)) - to_chat(user, "You are not in the right dimension!") - log_game("Teleport rune failed - user in away mission") - fail_invoke() - return - - var/input_rune_key = input(user, "Choose a rune to teleport to.", "Rune to Teleport to") as null|anything in potential_runes //we know what key they picked - var/obj/effect/rune/teleport/actual_selected_rune = potential_runes[input_rune_key] //what rune does that key correspond to? - if(!Adjacent(user) || !src || QDELETED(src) || user.incapacitated() || !actual_selected_rune) - fail_invoke() - return - - var/turf/T = get_turf(src) - var/movedsomething = 0 - var/moveuserlater = 0 - for(var/atom/movable/A in T) - if(A.move_resist == INFINITY) - continue //object cant move, shouldnt teleport - if(A == user) - moveuserlater = 1 - movedsomething = 1 - continue - if(!A.anchored) - movedsomething = 1 - A.forceMove(get_turf(actual_selected_rune)) - if(movedsomething) - ..() - visible_message("There is a sharp crack of inrushing air, and everything above the rune disappears!") - to_chat(user, "You[moveuserlater ? "r vision blurs, and you suddenly appear somewhere else":" send everything above the rune away"].") - if(moveuserlater) - user.forceMove(get_turf(actual_selected_rune)) - var/mob/living/carbon/human/H = user - if(user.z != T.z) - if(istype(H)) - H.bleed(5) - user.apply_damage(5, BRUTE) - else - if(istype(H)) - H.bleed(rand(5,10)) - else - fail_invoke() - - -//Rite of Offering: Converts a normal crewmember to the cult or sacrifices mindshielded and sacrifice targets. +//Rite of Enlightenment: Converts a normal crewmember to the cult, or offer them as sacrifice if cant be converted. /obj/effect/rune/convert - cultist_name = "Rite of Offering" - cultist_desc = "Offers a non-cultists on top of it to your deity, either converting or sacrificing them." + cultist_name = "Offer" + cultist_desc = "offers non-cultists on top of it to the Dark One, either converting or sacrificing them. Sacrifices with a soul will result in a captured soulshard. This can be done with brains as well." invocation = "Mah'weyh pleggh at e'ntrath!" - icon_state = "3" + icon_state = "offering" req_cultists = 1 allow_excess_invokers = TRUE rune_in_use = FALSE -/obj/effect/rune/convert/do_invoke_glow() - return - -/obj/effect/rune/convert/invoke(var/list/invokers) +/obj/effect/rune/convert/invoke(list/invokers) if(rune_in_use) return - var/list/myriad_targets = list() + + var/list/offer_targets = list() var/turf/T = get_turf(src) for(var/mob/living/M in T) - if(!iscultist(M)) - myriad_targets |= M - if(!myriad_targets.len) + if(!iscultist(M) || (M.mind && is_sacrifice_target(M.mind))) + offer_targets += M + + // Offering a head/brain + for(var/obj/item/organ/O in T) + var/mob/living/carbon/brain/b_mob + if(istype(O, /obj/item/organ/external/head)) // Offering a head + var/obj/item/organ/external/head/H = O + for(var/obj/item/organ/internal/brain/brain in H.contents) + b_mob = brain.brainmob + brain.forceMove(T) + O = brain // Convoluted way of making the brain disappear + + else if(istype(O, /obj/item/organ/internal/brain)) // Offering a brain + var/obj/item/organ/internal/brain/brain = O + b_mob = brain.brainmob + + if(b_mob && b_mob.mind && (!iscultist(b_mob) || is_sacrifice_target(b_mob.mind))) + offer_targets += b_mob + O.invisibility = INVISIBILITY_MAXIMUM // So that it can't be moved around. This gets qdeleted later + + if(!length(offer_targets)) fail_invoke() log_game("Offer rune failed - no eligible targets") rune_in_use = FALSE return rune_in_use = TRUE - var/mob/living/new_cultist = pick(myriad_targets) - if(is_convertable_to_cult(new_cultist.mind) && !new_cultist.null_rod_check() && !is_sacrifice_target(new_cultist.mind) && new_cultist.stat != DEAD && new_cultist.client != null) - invocation = "Mah'weyh pleggh at e'ntrath!" + var/mob/living/L = pick(offer_targets) + if(L.mind in GLOB.sacrificed) + fail_invoke() + rune_in_use = FALSE + return + + if(L.stat != DEAD && is_convertable_to_cult(L.mind)) ..() - do_convert(new_cultist, invokers) + do_convert(L, invokers) else invocation = "Barhah hra zar'garis!" ..() - do_sacrifice(new_cultist, invokers) + do_sacrifice(L, invokers) + if(isbrain(L)) + qdel(L.loc) // Don't need this anymore! rune_in_use = FALSE /obj/effect/rune/convert/proc/do_convert(mob/living/convertee, list/invokers) - if(invokers.len < 2) + if(length(invokers) < 2) fail_invoke() for(var/I in invokers) to_chat(I, "You need at least two invokers to convert!") return else convertee.visible_message("[convertee] writhes in pain as the markings below them glow a bloody red!", \ - "AAAAAAAAAAAAAA-") - SSticker.mode.add_cultist(convertee.mind, 1) - new /obj/item/tome(get_turf(src)) + "AAAAAAAAAAAAAA-") + SSticker.mode.add_cultist(convertee.mind) convertee.mind.special_role = "Cultist" to_chat(convertee, "Your blood pulses. Your head throbs. The world goes red. All at once you are aware of a horrible, horrible, truth. The veil of reality has been ripped away \ and something evil takes root.") to_chat(convertee, "Assist your new compatriots in their dark dealings. Your goal is theirs, and theirs is yours. You serve [SSticker.cultdat.entity_title3] above all else. Bring it back.\ ") - return + + if(ishuman(convertee)) + var/mob/living/carbon/human/H = convertee + var/brutedamage = convertee.getBruteLoss() + var/burndamage = convertee.getFireLoss() + if(brutedamage || burndamage) // If the convertee is injured + // Heal 90% of all damage, including robotic limbs + H.adjustBruteLoss(-(brutedamage * 0.9), robotic = TRUE) + H.adjustFireLoss(-(burndamage * 0.9), robotic = TRUE) + if(ismachineperson(H)) + H.visible_message("A dark force repairs [convertee]!", + "Your damage has been repaired. Now spread the blood to others.") + else + H.visible_message("[convertee]'s wounds heal and close!", + "Your wounds have been healed. Now spread the blood to others.") + for(var/obj/item/organ/external/E in H.bodyparts) + E.mend_fracture() + E.internal_bleeding = FALSE + for(var/datum/disease/critical/crit in H.viruses) // cure all crit conditions + crit.cure() + + H.uncuff() + H.Silence(3) //Prevent "HALP MAINT CULT" before you realise you're converted + + var/obj/item/melee/cultblade/dagger/D = new(get_turf(src)) + if(H.equip_to_slot_if_possible(D, slot_in_backpack, FALSE, TRUE)) + to_chat(H, "You have a dagger in your backpack. Use it to do [SSticker.cultdat.entity_title1]'s bidding. ") + else + to_chat(H, "There is a dagger on the floor. Use it to do [SSticker.cultdat.entity_title1]'s bidding.") /obj/effect/rune/convert/proc/do_sacrifice(mob/living/offering, list/invokers) var/mob/living/user = invokers[1] //the first invoker is always the user - if(((ishuman(offering) || isrobot(offering)) && offering.stat != DEAD) || is_sacrifice_target(offering.mind)) //Requires three people to sacrifice living targets - if(invokers.len < 3) + + if(offering.stat != DEAD || (offering.mind && is_sacrifice_target(offering.mind))) //Requires three people to sacrifice living targets/sacrifice objective + if(length(invokers) < 3) for(var/M in invokers) to_chat(M, "[offering] is too greatly linked to the world! You need three acolytes!") fail_invoke() @@ -410,624 +363,650 @@ GLOBAL_LIST_EMPTY(teleport_runes) return var/sacrifice_fulfilled - var/datum/game_mode/cult/cult_mode = SSticker.mode + var/datum/game_mode/gamemode = SSticker.mode if(offering.mind) - GLOB.sacrificed.Add(offering.mind) + GLOB.sacrificed += offering.mind if(is_sacrifice_target(offering.mind)) sacrifice_fulfilled = TRUE + else + GLOB.sacrificed += offering + new /obj/effect/temp_visual/cult/sac(loc) - if(SSticker && SSticker.mode && SSticker.mode.name == "cult") - cult_mode.harvested++ for(var/M in invokers) if(sacrifice_fulfilled) to_chat(M, "\"Yes! This is the one I desire! You have done well.\"") - cult_mode.additional_phase() else if(ishuman(offering) || isrobot(offering)) to_chat(M, "\"I accept this sacrifice.\"") else to_chat(M, "\"I accept this meager sacrifice.\"") + playsound(offering, 'sound/misc/demon_consume.ogg', 100, TRUE) - playsound(offering, 'sound/misc/demon_consume.ogg', 100, 1) - if((ishuman(offering) || isrobot(offering)) && offering.client_mobs_in_contents?.len) + if((ishuman(offering) || isrobot(offering) || isbrain(offering)) && offering.mind) var/obj/item/soulstone/stone = new /obj/item/soulstone(get_turf(src)) - stone.invisibility = INVISIBILITY_MAXIMUM //so it's not picked up during transfer_soul() - stone.transfer_soul("FORCE", offering, user) //If it cannot be added + stone.invisibility = INVISIBILITY_MAXIMUM // So it's not picked up during transfer_soul() + stone.transfer_soul("FORCE", offering, user) // If it cannot be added stone.invisibility = 0 else - offering.dust() - -//Ritual of Dimensional Rending: Calls forth the avatar of Nar-Sie upon the station. -/obj/effect/rune/narsie - cultist_name = "Tear Reality (God)" - cultist_desc = "tears apart dimensional barriers, calling forth your god. Requires 9 invokers." - invocation = "TOK-LYR RQA-NAP G'OLT-ULOFT!!" - req_cultists = 9 - icon = 'icons/effects/96x96.dmi' - color = rgb(125,23,23) - icon_state = "rune_large" - pixel_x = -32 //So the big ol' 96x96 sprite shows up right - pixel_y = -32 - mouse_opacity = MOUSE_OPACITY_ICON //we're huge and easy to click - scribe_delay = 450 //how long the rune takes to create - scribe_damage = 40.1 //how much damage you take doing it - var/used - -/obj/effect/rune/narsie/New() - ..() - cultist_name = "Summon [SSticker.cultdat ? SSticker.cultdat.entity_name : "your god"]" - cultist_desc = "tears apart dimensional barriers, calling forth [SSticker.cultdat ? SSticker.cultdat.entity_title3 : "your god"]. Requires 9 invokers." - -/obj/effect/rune/narsie/check_icon() - return + if(isrobot(offering)) + offering.dust() //To prevent the MMI from remaining + else + offering.gib() + playsound(offering, 'sound/magic/disintegrate.ogg', 100, TRUE) + if(sacrifice_fulfilled) + gamemode.cult_objs.succesful_sacrifice() + return TRUE -/obj/effect/rune/narsie/talismanhide() //can't hide this, and you wouldn't want to - return +/obj/effect/rune/teleport + cultist_name = "Teleport" + cultist_desc = "warps everything above it to another chosen teleport rune." + invocation = "Sas'so c'arta forbici!" + icon_state = "teleport" + req_keyword = TRUE + light_power = 4 + var/obj/effect/temp_visual/cult/portal/inner_portal //The portal "hint" for off-station teleportations + var/obj/effect/temp_visual/cult/rune_spawn/rune2/outer_portal + var/listkey -/obj/effect/rune/narsie/invoke(var/list/invokers) - if(used) - return - var/mob/living/user = invokers[1] - var/datum/game_mode/cult/cult_mode = SSticker.mode - if(!(CULT_ELDERGOD in cult_mode.objectives)) - message_admins("[key_name_admin(user)] tried to summonn an eldritch horror when the objective was wrong") - burn_invokers(invokers) - log_game("Summon Nar-Sie rune failed - improper objective") - return - if(!is_station_level(user.z)) - message_admins("[key_name_admin(user)] tried to summon an eldritch horror off station") - burn_invokers(invokers) - log_game("Summon Nar-Sie rune failed - off station Z level") - return - if(!cult_mode.eldergod) - for(var/M in invokers) - to_chat(M, "[SSticker.cultdat.entity_name] is already on this plane!") - log_game("Summon god rune failed - already summoned") - return - //BEGIN THE SUMMONING - used = 1 - color = rgb(255, 0, 0) +/obj/effect/rune/teleport/New(loc, set_keyword) ..() - world << 'sound/effects/dimensional_rend.ogg' - to_chat(world, "The veil... is... TORN!!!--") - icon_state = "rune_large_distorted" - var/turf/T = get_turf(src) - sleep(40) - new /obj/singularity/narsie/large(T) //Causes Nar-Sie to spawn even if the rune has been removed - cult_mode.eldergod = 0 + var/area/A = get_area(src) + var/locname = initial(A.name) + listkey = set_keyword ? "[set_keyword] [locname]":"[locname]" + GLOB.teleport_runes += src -/obj/effect/rune/narsie/attackby(obj/I, mob/user, params) //Since the narsie rune takes a long time to make, add logging to removal. - if((istype(I, /obj/item/tome) && iscultist(user))) - user.visible_message("[user] begins erasing [src]...", "You begin erasing [src]...") - if(do_after(user, 50, target = src)) //Prevents accidental erasures. - log_game("Summon Narsie rune erased by [key_name(user)] with a tome") - message_admins("[key_name_admin(user)] erased a Narsie rune with a tome") - return - if(istype(I, /obj/item/nullrod)) //Begone foul magiks. You cannot hinder me. - log_game("Summon Narsie rune erased by [key_name(user)] using a null rod") - message_admins("[key_name_admin(user)] erased a Narsie rune with a null rod") - return +/obj/effect/rune/teleport/Destroy() + GLOB.teleport_runes -= src return ..() +/obj/effect/rune/teleport/invoke(list/invokers) + var/mob/living/user = invokers[1] //the first invoker is always the user + var/list/potential_runes = list() + var/list/teleportnames = list() + var/list/duplicaterunecount = list() + for(var/I in GLOB.teleport_runes) + var/obj/effect/rune/teleport/R = I + var/resultkey = R.listkey + if(resultkey in teleportnames) + duplicaterunecount[resultkey]++ + resultkey = "[resultkey] ([duplicaterunecount[resultkey]])" + else + teleportnames += resultkey + duplicaterunecount[resultkey] = 1 + if(R != src && is_level_reachable(R.z)) + potential_runes[resultkey] = R -/obj/effect/rune/slaughter - cultist_name = "Call Forth The Slaughter (Demons)" - cultist_desc = "Calls forth the doom of an eldritch being. Three slaughter demons will appear to wreak havoc on the station." - invocation = null - req_cultists = 9 - color = rgb(125,23,23) - scribe_delay = 450 - scribe_damage = 40.1 //how much damage you take doing it - icon = 'icons/effects/96x96.dmi' - icon_state = "rune_large" - pixel_x = -32 - pixel_y = -32 - - var/used = 0 - -/obj/effect/rune/slaughter/check_icon() - return - -/obj/effect/rune/slaughter/talismanhide() //can't hide this, and you wouldn't want to - return + if(!length(potential_runes)) + to_chat(user, "There are no valid runes to teleport to!") + log_game("Teleport rune failed - no other teleport runes") + fail_invoke() + return -/obj/effect/rune/slaughter/attackby(obj/I, mob/user, params) //Since the narsie rune takes a long time to make, add logging to removal. - if((istype(I, /obj/item/tome) && iscultist(user))) - user.visible_message("[user.name] begins erasing [src]...", "You begin erasing [src]...") - if(do_after(user, 50, target = src)) //Prevents accidental erasures. - log_game("Summon demon rune erased by [key_name(user)] with a tome") - message_admins("[key_name_admin(user)] erased a demon rune with a tome") + if(!is_level_reachable(user.z)) + to_chat(user, "You are not in the right dimension!") + log_game("Teleport rune failed - user in away mission") + fail_invoke() return - if(istype(I, /obj/item/nullrod)) //Begone foul magiks. You cannot hinder me. - log_game("Summon demon rune erased by [key_name(user)] using a null rod") - message_admins("[key_name_admin(user)] erased a demon rune with a null rod") + + var/input_rune_key = input(user, "Choose a rune to teleport to.", "Rune to Teleport to") as null|anything in potential_runes //we know what key they picked + var/obj/effect/rune/teleport/actual_selected_rune = potential_runes[input_rune_key] //what rune does that key correspond to? + if(!src || !Adjacent(user) || QDELETED(src) || user.incapacitated() || !actual_selected_rune) + fail_invoke() return - return ..() + var/turf/T = get_turf(src) + var/turf/target = get_turf(actual_selected_rune) + var/movedsomething = FALSE + var/moveuser = FALSE + for(var/atom/movable/A in T) + if(ishuman(A)) + if(A != user) // Teleporting someone else + INVOKE_ASYNC(src, .proc/teleport_effect, A, T, target) + else // Teleporting yourself + INVOKE_ASYNC(src, .proc/teleport_effect, user, T, target) + if(A.move_resist == INFINITY) + continue //object cant move, shouldnt teleport + if(A == user) + moveuser = TRUE + movedsomething = TRUE + continue + if(!A.anchored) + movedsomething = TRUE + A.forceMove(target) -/obj/effect/rune/slaughter/invoke(var/list/invokers) - if(used) - return - var/mob/living/user = invokers[1] - var/datum/game_mode/cult/cult_mode = SSticker.mode - if(!(CULT_SLAUGHTER in cult_mode.objectives)) - message_admins("[key_name_admin(user)] tried to summon demons when the objective was wrong") - burn_invokers(invokers) - log_game("Summon Demons rune failed - improper objective") - return - if(!is_station_level(user.z)) - message_admins("[key_name_admin(user)] tried to summon demons off station") - burn_invokers(invokers) - log_game("Summon demons rune failed - off station Z level") - return - if(cult_mode.demons_summoned) - for(var/M in invokers) - to_chat(M, "Demons are already on this plane!") - log_game("Summon Demons rune failed - already summoned") - return - //BEGIN THE SLAUGHTER - used = 1 - for(var/mob/living/M in range(1,src)) - if(iscultist(M)) - M.say("TOK-LYR RQA-NAP SHA-NEX!!") - world << 'sound/effects/dimensional_rend.ogg' - to_chat(world, "A hellish cacaphony bombards from all around as something awful tears through the world...") - icon_state = "rune_large_distorted" - sleep(55) - to_chat(world, "\"LIBREATE TE EX INFERIS!\"")//Fethas note:I COULDN'T HELP IT OKAY?! - visible_message("[src] melts away into blood, and three horrific figures emerge from within!") + if(movedsomething) + ..() + if(is_mining_level(z) && !is_mining_level(target.z)) //No effect if you stay on lavaland + actual_selected_rune.handle_portal("lava") + else if(!is_station_level(z) || istype(get_area(src), /area/space)) + actual_selected_rune.handle_portal("space", T) + user.visible_message("There is a sharp crack of inrushing air, and everything above the rune disappears!", + "You[moveuser ? "r vision blurs, and you suddenly appear somewhere else":" send everything above the rune away"].") + if(moveuser) + user.forceMove(target) + else + fail_invoke() + +/obj/effect/rune/teleport/proc/handle_portal(portal_type, turf/origin) var/turf/T = get_turf(src) - new /mob/living/simple_animal/slaughter/cult(T) - new /mob/living/simple_animal/slaughter/cult(T, pick(NORTH, EAST, SOUTH, WEST)) - new /mob/living/simple_animal/slaughter/cult(T, pick(NORTHEAST, SOUTHEAST, NORTHWEST, SOUTHWEST)) - cult_mode.demons_summoned = 1 - SSshuttle.emergency.request(null, 0.5,null) - SSshuttle.emergency.canRecall = FALSE - cult_mode.third_phase() - qdel(src) + if(inner_portal || outer_portal) + close_portal() // To avoid stacking descriptions/animations + playsound(T, pick('sound/effects/sparks1.ogg', 'sound/effects/sparks2.ogg', 'sound/effects/sparks3.ogg', 'sound/effects/sparks4.ogg'), 100, TRUE, 14) + inner_portal = new /obj/effect/temp_visual/cult/portal(T) + + if(portal_type == "space") + light_color = color + desc += "
A tear in reality reveals a black void interspersed with dots of light... something recently teleported here from space.
" + + // Space base near the station + if(is_station_level(origin.z)) + desc += "The void feels like it's trying to pull you to the [dir2text(get_dir(T, origin))], near the station!" + // Space base on another Z-level + else + desc += "The void feels like it's trying to pull you to the [dir2text(get_dir(T, origin))], in the direction of space sector [origin.z]!" + + else + inner_portal.icon_state = "lava" + light_color = LIGHT_COLOR_FIRE + desc += "
A tear in reality reveals a coursing river of lava... something recently teleported here from the Lavaland Mines!" + + outer_portal = new(T, 60 SECONDS, color) + light_range = 4 + update_light() + addtimer(CALLBACK(src, .proc/close_portal), 60 SECONDS, TIMER_UNIQUE) + +/obj/effect/rune/teleport/proc/close_portal() + qdel(inner_portal) + qdel(outer_portal) + desc = initial(desc) + light_range = 0 + update_light() + + +//Rune of Empowering : Enables carrying 4 blood spells, greatly reduce blood cost +/obj/effect/rune/empower + cultist_name = "Empower" + cultist_desc = "allows cultists to prepare greater amounts of blood magic at far less of a cost." + invocation = "H'drak v'loso, mir'kanas verbot!" + icon_state = "empower" + construct_invoke = FALSE +/obj/effect/rune/empower/invoke(list/invokers) + . = ..() + var/mob/living/user = invokers[1] //the first invoker is always the user + for(var/datum/action/innate/cult/blood_magic/BM in user.actions) + BM.Activate() -//Rite of Resurrection: Requires two corpses. Revives one and gibs the other. +//Rite of Resurrection: Requires a dead or inactive cultist. When reviving the dead, you can only perform one revival for every three sacrifices your cult has carried out. /obj/effect/rune/raise_dead - cultist_name = "Rite of Resurrection" - cultist_desc = "requires two corpses, one on the rune and one adjacent to the rune. The one on the rune is brought to life, the other is turned to ash." - invocation = null //Depends on the name of the user - see below - icon_state = "1" + cultist_name = "Revive" + cultist_desc = "requires a dead, mindless, or inactive cultist placed upon the rune. For each three bodies sacrificed to the dark patron, one body will be mended and their mind awoken" + invocation = "Pasnar val'keriam usinar. Savrae ines amutan. Yam'toth remium il'tarat!" //Depends on the name of the user - see below + icon_state = "revive" + var/static/sacrifices_used = -SOULS_TO_REVIVE // Cultists get one "free" revive + +/obj/effect/rune/raise_dead/examine(mob/user) + . = ..() + if(iscultist(user) || user.stat == DEAD) + . += "Sacrifices unrewarded: [length(GLOB.sacrificed) - sacrifices_used]" + . += "Sacrifice cost per ressurection:There are no eligible sacrifices nearby!") - log_game("Raise Dead rune failed - no catalyst corpse") - return - mob_to_sacrifice = input(user, "Choose a corpse to sacrifice.", "Corpse to Sacrifice") as null|anything in potential_sacrifice_mobs - if(!Adjacent(user) || !src || QDELETED(src) || user.incapacitated() || !mob_to_sacrifice || rune_in_use) - return + + rune_in_use = TRUE for(var/mob/living/M in T.contents) - if(M.stat == DEAD) - potential_revive_mobs.Add(M) - if(!potential_revive_mobs.len) - to_chat(user, "There is no eligible revival target on the rune!") - log_game("Raise Dead rune failed - no corpse to revive") - return - mob_to_revive = input(user, "Choose a corpse to revive.", "Corpse to Revive") as null|anything in potential_revive_mobs - if(!Adjacent(user) || !src || QDELETED(src) || user.incapacitated() || rune_in_use || !mob_to_revive) - return - if(!in_range(mob_to_sacrifice,src)) - to_chat(user, "The sacrificial target has been moved!") + if(iscultist(M) && (M.stat == DEAD || !M.client || M.client.is_afk())) + potential_revive_mobs |= M + if(!length(potential_revive_mobs)) + to_chat(user, "There are no dead cultists on the rune!") + log_game("Raise Dead rune failed - no cultists to revive") fail_invoke() - log_game("Raise Dead rune failed - catalyst corpse moved") - return - if(!(mob_to_revive in T.contents)) - to_chat(user, "The corpse to revive has been moved!") - fail_invoke() - log_game("Raise Dead rune failed - revival target moved") return - if(mob_to_sacrifice.stat != DEAD) - to_chat(user, "The sacrificial target must be dead!") + if(length(potential_revive_mobs) > 1) + mob_to_revive = input(user, "Choose a cultist to revive.", "Cultist to Revive") as null|anything in potential_revive_mobs + else // If there's only one, no need for a menu + mob_to_revive = potential_revive_mobs[1] + if(!validness_checks(mob_to_revive, user)) fail_invoke() - log_game("Raise Dead rune failed - catalyst corpse is not dead") return - rune_in_use = 1 - if(user.name == "Herbert West") - user.say("To life, to life, I bring them!") - else - user.say("Pasnar val'keriam usinar. Savrae ines amutan. Yam'toth remium il'tarat!") + ..() - mob_to_sacrifice.visible_message("[mob_to_sacrifice]'s body rises into the air, connected to [mob_to_revive] by a glowing tendril!") - mob_to_revive.Beam(mob_to_sacrifice,icon_state="sendbeam",time=20) - sleep(20) - if(!mob_to_sacrifice || !in_range(mob_to_sacrifice, src)) - mob_to_sacrifice.visible_message("[mob_to_sacrifice] disintegrates into a pile of bones.") - return - mob_to_sacrifice.dust() - if(!mob_to_revive || mob_to_revive.stat != DEAD) - visible_message("The glowing tendril snaps against the rune with a shocking crack.") - rune_in_use = 0 - return - mob_to_revive.revive() //This does remove disabilities and such, but the rune might actually see some use because of it! + if(mob_to_revive.stat == DEAD) + var/diff = length(GLOB.sacrificed) - SOULS_TO_REVIVE - sacrifices_used + if(diff < 0) + to_chat(user, "Your cult must carry out [abs(diff)] more sacrifice\s before it can revive another cultist!") + fail_invoke() + return + sacrifices_used += SOULS_TO_REVIVE + mob_to_revive.revive() + mob_to_revive.grab_ghost() + + if(!mob_to_revive.client || mob_to_revive.client.is_afk()) + set waitfor = FALSE + to_chat(user, "[mob_to_revive] was revived, but their mind is lost! Seeking a lost soul to replace it.") + var/list/mob/dead/observer/candidates = SSghost_spawns.poll_candidates("Would you like to play as a revived Cultist?", ROLE_CULTIST, TRUE, poll_time = 20 SECONDS, source = /obj/item/melee/cultblade/dagger) + if(length(candidates)) + var/mob/dead/observer/C = pick(candidates) + to_chat(mob_to_revive.mind, "Your physical form has been taken over by another soul due to your inactivity! Ahelp if you wish to regain your form.") + message_admins("[key_name_admin(C)] has taken control of ([key_name_admin(mob_to_revive)]) to replace an AFK player.") + mob_to_revive.ghostize(FALSE) + mob_to_revive.key = C.key + else + fail_invoke() + return + + SEND_SOUND(mob_to_revive, 'sound/ambience/antag/bloodcult.ogg') to_chat(mob_to_revive, "\"PASNAR SAVRAE YAM'TOTH. Arise.\"") mob_to_revive.visible_message("[mob_to_revive] draws in a huge breath, red light shining from [mob_to_revive.p_their()] eyes.", \ "You awaken suddenly from the void. You're alive!") - rune_in_use = 0 + rune_in_use = FALSE +/obj/effect/rune/raise_dead/proc/validness_checks(mob/living/target_mob, mob/living/user) + if(QDELETED(src)) + return FALSE + if(QDELETED(user)) + return FALSE + if(!Adjacent(user) || user.incapacitated()) + return FALSE + if(QDELETED(target_mob)) + return FALSE + var/turf/T = get_turf(src) + if(target_mob.loc != T) + to_chat(user, "The cultist to revive has been moved!") + log_game("Raise Dead rune failed - revival target moved") + return FALSE + return TRUE /obj/effect/rune/raise_dead/fail_invoke() ..() rune_in_use = FALSE - for(var/mob/living/M in range(1,src)) - if(M.stat == DEAD) + for(var/mob/living/M in range(0, src)) + if(iscultist(M) && M.stat == DEAD) M.visible_message("[M] twitches.") - -//Rite of Disruption: Emits an EMP blast. -/obj/effect/rune/emp - cultist_name = "Rite of Disruption" - cultist_desc = "emits a large electromagnetic pulse, increasing in size for each cultist invoking it, hindering electronics and disabling silicons." - invocation = "Ta'gh fara'qha fel d'amar det!" - icon_state = "5" - allow_excess_invokers = 1 - -/obj/effect/rune/emp/invoke(var/list/invokers) - var/turf/E = get_turf(src) - ..() - visible_message("[src] glows blue for a moment before vanishing.") - switch(invokers.len) - if(1 to 2) - playsound(E, 'sound/items/welder2.ogg', 25, 1) - for(var/M in invokers) - to_chat(M, "You feel a minute vibration pass through you...") - if(3 to 6) - playsound(E, 'sound/effects/EMPulse.ogg', 50, 1) - for(var/M in invokers) - to_chat(M, "Your hair stands on end as a shockwave emanates from the rune!") - if(7 to INFINITY) - playsound(E, 'sound/effects/EMPulse.ogg', 100, 1) - for(var/M in invokers) - var/mob/living/L = M - to_chat(L, "You chant in unison and a colossal burst of energy knocks you backward!") - L.Weaken(2) - qdel(src) //delete before pulsing because it's a delay reee - empulse(E, 9*invokers.len, 12*invokers.len, 1) // Scales now, from a single room to most of the station depending on # of chanters - -//Rite of Astral Communion: Separates one's spirit from their body. They will take damage while it is active. -/obj/effect/rune/astral - cultist_name = "Astral Communion" - cultist_desc = "severs the link between one's spirit and body. This effect is taxing and one's physical body will take damage while this is active." - invocation = "Fwe'sh mah erl nyag r'ya!" - icon_state = "6" - rune_in_use = 0 //One at a time, please! - construct_invoke = 0 - var/mob/living/affecting = null - -/obj/effect/rune/astral/examine(mob/user) - . = ..() - if(affecting) - . += "A translucent field encases [user] above the rune!" - -/obj/effect/rune/astral/can_invoke(mob/living/user) - if(rune_in_use) - to_chat(user, "[src] cannot support more than one body!") - log_game("Astral Communion rune failed - more than one user") - return list() - var/turf/T = get_turf(src) - if(!(user in T.contents)) - to_chat(user, "You must be standing on top of [src]!") - log_game("Astral Communion rune failed - user not standing on rune") - return list() - return ..() - -/obj/effect/rune/astral/invoke(var/list/invokers) - var/mob/living/user = invokers[1] - ..() - var/turf/T = get_turf(src) - rune_in_use = 1 - affecting = user - user.color = "#7e1717" - user.visible_message("[user] freezes statue-still, glowing an unearthly red.", \ - "You see what lies beyond. All is revealed. While this is a wondrous experience, your physical form will waste away in this state. Hurry...") - user.ghostize(1) - while(user) - if(!affecting) - visible_message("[src] pulses gently before falling dark.") - affecting = null //In case it's assigned to a number or something - rune_in_use = 0 - return - affecting.apply_damage(1, BRUTE) - if(!(user in T.contents)) - user.visible_message("A spectral tendril wraps around [user] and pulls [user.p_them()] back to the rune!") - Beam(user,icon_state="drainbeam",time=2) - user.forceMove(get_turf(src)) //NO ESCAPE :^) - if(user.key) - user.visible_message("[user] slowly relaxes, the glow around [user.p_them()] dimming.", \ - "You are re-united with your physical form. [src] releases its hold over you.") - user.color = initial(user.color) - user.Weaken(3) - rune_in_use = 0 - affecting = null - user.update_sight() - return - if(user.stat == UNCONSCIOUS) - if(prob(10)) - var/mob/dead/observer/G = user.get_ghost() - if(G) - to_chat(G, "You feel the link between you and your body weakening... you must hurry!") - if(user.stat == DEAD) - user.color = initial(user.color) - rune_in_use = 0 - affecting = null - var/mob/dead/observer/G = user.get_ghost() - if(G) - to_chat(G, "You suddenly feel your physical form pass on. [src]'s exertion has killed you!") - return - sleep(10) - rune_in_use = 0 - - //Rite of the Corporeal Shield: When invoked, becomes solid and cannot be passed. Invoke again to undo. /obj/effect/rune/wall - cultist_name = "Rite of the Corporeal Shield" - cultist_desc = "when invoked, makes an invisible wall to block passage. Can be invoked again to reverse this." + cultist_name = "Barrier" + cultist_desc = "when invoked, makes a temporary invisible wall to block passage. Can be destroyed by brute force. Can be invoked again to reverse this." invocation = "Khari'd! Eske'te tannin!" - icon_state = "1" - invoke_damage = 2 + icon_state = "barrier" + ///The barrier summoned by the rune when invoked. Tracked as a variable to prevent refreshing the barrier's integrity. shieldgen.dm + var/obj/machinery/shield/cult/barrier/B -/obj/effect/rune/wall/examine(mob/user) +/obj/effect/rune/wall/Initialize(mapload) . = ..() - if(density) - . += "There is a barely perceptible shimmering of the air above [src]." + GLOB.wall_runes += src + +/obj/effect/rune/wall/Destroy() + GLOB.wall_runes -= src + if(B && !QDELETED(B)) + QDEL_NULL(B) + return ..() -/obj/effect/rune/wall/invoke(var/list/invokers) +/obj/effect/rune/wall/invoke(list/invokers) var/mob/living/user = invokers[1] ..() - density = !density - user.visible_message("[user] places [user.p_their()] hands on [src], and [density ? "the air above it begins to shimmer" : "the shimmer above it fades"].", \ - "You channel your life energy into [src], [density ? "preventing" : "allowing"] passage above it.") + if(!B) + B = new /obj/machinery/shield/cult/barrier(loc) + B.parent_rune = src + B.Toggle() if(iscarbon(user)) var/mob/living/carbon/C = user - C.apply_damage(2, BRUTE, pick("l_arm", "r_arm")) - + C.cult_self_harm(2) //Rite of Joined Souls: Summons a single cultist. /obj/effect/rune/summon - cultist_name = "Rite of Joined Souls" - cultist_desc = "summons a single cultist to the rune. Requires 2 invokers." + cultist_name = "Summon Cultist" + cultist_desc = "summons a single cultist to the rune. (Cannot summon restrained cultists!)" invocation = "N'ath reth sh'yro eth d'rekkathnor!" req_cultists = 2 - allow_excess_invokers = 1 - icon_state = "5" - invoke_damage = 5 - var/summoning = FALSE - var/summontime = 0 + invoke_damage = 10 + icon_state = "summon" -/obj/effect/rune/summon/invoke(var/list/invokers) +/obj/effect/rune/summon/invoke(list/invokers) var/mob/living/user = invokers[1] var/list/cultists = list() + for(var/datum/mind/M in SSticker.mode.cult) if(!(M.current in invokers) && M.current && M.current.stat != DEAD) - cultists |= M.current - var/mob/living/cultist_to_summon = input(user, "Who do you wish to call to [src]?", "Followers of [SSticker.cultdat.entity_title3]") as null|anything in cultists - if(!Adjacent(user) || !src || QDELETED(src) || user.incapacitated()) + cultists[M.current.real_name] = M.current + var/input = input(user, "Who do you wish to call to [src]?", "Acolytes") as null|anything in cultists + var/mob/living/cultist_to_summon = cultists[input] + if(!src || QDELETED(src) || !Adjacent(user) || user.incapacitated()) + return + if(!cultist_to_summon) + log_game("Summon Cultist rune failed - no target") return - if(summoning) - to_chat(user, "You are already summoning a target!") + if(cultist_to_summon.stat == DEAD) + to_chat(user, "[cultist_to_summon] has died!") fail_invoke() + log_game("Summon Cultist rune failed - target died") return - - if(!cultist_to_summon) - to_chat(user, "You require a summoning target!") + if(cultist_to_summon.pulledby || cultist_to_summon.buckled) + to_chat(user, "[cultist_to_summon] is being held in place!") + to_chat(cultist_to_summon, "You feel a tugging sensation, but you are being held in place!") fail_invoke() - log_game("Summon Cultist rune failed - no target") + log_game("Summon Cultist rune failed - target restrained") return if(!iscultist(cultist_to_summon)) - to_chat(user, "[cultist_to_summon] is not a follower of [SSticker.cultdat.entity_title3]!") + to_chat(user, "[cultist_to_summon] is not a follower of the [SSticker.cultdat.entity_title3]!") fail_invoke() - log_game("Summon Cultist rune failed - no target") + log_game("Summon Cultist rune failed - target was deconverted") return - if(!is_level_reachable(cultist_to_summon.z)) + if(is_away_level(cultist_to_summon.z)) to_chat(user, "[cultist_to_summon] is not in our dimension!") fail_invoke() log_game("Summon Cultist rune failed - target in away mission") return - var/hard_summon = (cultist_to_summon.reagents && cultist_to_summon.reagents.has_reagent("holywater")) || cultist_to_summon.restrained() - if(hard_summon && invokers.len < 3) - to_chat(user, "The summoning of [cultist_to_summon] is being blocked somehow! You need 3 invokers to counter it!") - fail_invoke() - new /obj/effect/temp_visual/cult/sparks(get_turf(cultist_to_summon)) //observer warning - log_game("Summon Cultist rune failed - holywater in target") - return - summoning = TRUE - ..() - if(hard_summon) - summontime = 20 - - if(do_after(user, summontime, target = src)) - summoning = FALSE // Here incase the proc stops after the qdel - cultist_to_summon.visible_message("[cultist_to_summon] suddenly disappears in a flash of red light!", \ - "Overwhelming vertigo consumes you as you are hurled through the air!") - visible_message("A foggy shape materializes atop [src] and solidifies into [cultist_to_summon]!") - cultist_to_summon.forceMove(get_turf(src)) - qdel(src) - summoning = FALSE + cultist_to_summon.visible_message("[cultist_to_summon] suddenly disappears in a flash of red light!", \ + "Overwhelming vertigo consumes you as you are hurled through the air!") + ..() + INVOKE_ASYNC(src, .proc/teleport_effect, cultist_to_summon, get_turf(cultist_to_summon), src) + visible_message("[src] begins to bubble and rises into the form of [cultist_to_summon]!") + cultist_to_summon.forceMove(get_turf(src)) + qdel(src) -//Rite of Boiling Blood: Deals extremely high amounts of damage to non-cultists nearby +/** + * # Blood Boil Rune + * + * When invoked deals up to 30 burn damage to nearby non-cultists and sets them on fire. + * + * On activation the rune charges for six seconds, changing colour, glowing, and giving out a warning to all nearby mobs. + * After the charging period the rune burns any non-cultists in view and sets them on fire. After another short wait it does the same again with slightly higher damage. + * If the cultists channeling the rune move away or are stunned at any point, the rune is deleted. So it can be countered pretty easily with flashbangs. + */ /obj/effect/rune/blood_boil cultist_name = "Boil Blood" - cultist_desc = "boils the blood of non-believers who can see the rune, dealing extreme amounts of damage. Requires 3 invokers." + cultist_desc = "boils the blood of non-believers who can see the rune, rapidly dealing extreme amounts of damage. Requires 2 invokers channeling the rune." invocation = "Dedo ol'btoh!" - icon_state = "4" - construct_invoke = 0 - req_cultists = 3 + icon_state = "blood_boil" + light_color = LIGHT_COLOR_LAVA + req_cultists = 2 invoke_damage = 15 + construct_invoke = FALSE + var/tick_damage = 10 // 30 burn damage total + damage taken by being on fire/overheating + rune_in_use = FALSE -/obj/effect/rune/blood_boil/do_invoke_glow() - return - -/obj/effect/rune/blood_boil/invoke(var/list/invokers) +/obj/effect/rune/blood_boil/invoke(list/invokers) + if(rune_in_use) + return ..() + rune_in_use = TRUE var/turf/T = get_turf(src) - visible_message("[src] briefly bubbles before exploding!") - for(var/mob/living/carbon/C in viewers(T)) - if(!iscultist(C)) - var/obj/item/nullrod/N = C.null_rod_check() - if(N) - to_chat(C, "\The [N] suddenly burns hotly before returning to normal!") + var/list/targets = list() + for(var/mob/living/L in viewers(T)) + if(!iscultist(L) && L.blood_volume && !ismachineperson(L)) + var/atom/I = L.null_rod_check() + if(I) + if(isitem(I)) + to_chat(L, "[I] suddenly burns hotly before returning to normal!") continue - to_chat(C, "Your blood boils in your veins!") - C.take_overall_damage(45,45) - C.Stun(7) + targets += L + + // Six seconds buildup + visible_message("A haze begins to form above [src]!") + animate(src, color = "#FC9A6D", time = 6 SECONDS) + set_light(6, 1, color) + sleep(6 SECONDS) + visible_message("[src] turns a bright, burning orange!") + if(!burn_check()) + return + + for(var/I in targets) + to_chat(I, "Your blood boils in your veins!") + do_area_burn(T, 1) + animate(src, color = "#FFDF80", time = 5 SECONDS) + sleep(5 SECONDS) + if(!burn_check()) + return + + do_area_burn(T, 2) + animate(src, color = "#FFFFFF", time = 5 SECONDS) + sleep(5 SECONDS) + if(!burn_check()) + return + + do_area_burn(T, 3) qdel(src) - explosion(T, -1, 0, 1, 5) -//Deals brute damage to all targets on the rune and heals the invoker for each target drained. -/obj/effect/rune/leeching - cultist_name = "Drain Life" - cultist_desc = "drains the life of all targets on the rune, restoring life to the user." - invocation = "Yu'gular faras desdae. Umathar uf'kal thenar!" - icon_state = "3" +/obj/effect/rune/blood_boil/proc/do_area_burn(turf/T, iteration) + var/multiplier = iteration / 2 // Iteration 1 = 0.5, Iteration 2 = 1, etc. + set_light(6, 1 * iteration, color) + for(var/mob/living/L in viewers(T)) + if(!iscultist(L) && L.blood_volume && !ismachineperson(L)) + if(L.null_rod_check()) + continue + L.take_overall_damage(0, tick_damage * multiplier) + L.adjust_fire_stacks(2) + L.IgniteMob() + playsound(src, 'sound/effects/bamf.ogg', 100, TRUE) + do_invoke_glow() + sleep(0.6 SECONDS) // Only one 'animate()' can play at once, so this waits for the pulse to finish -/obj/effect/rune/leeching/can_invoke(mob/living/user) - if(world.time <= user.next_move) - return list() - var/turf/T = get_turf(src) - var/list/potential_targets = list() - for(var/mob/living/carbon/M in T.contents - user) - if(M.stat != DEAD) - potential_targets += M - if(!potential_targets.len) - to_chat(user, "There must be at least one valid target on the rune!") - log_game("Leeching rune failed - no valid targets") - return list() - return ..() +/obj/effect/rune/blood_boil/proc/burn_check() + . = TRUE + if(QDELETED(src)) + return FALSE + var/list/cultists = list() + for(var/mob/living/M in range(1, src)) // Get all cultists currently in range + if(iscultist(M) && !M.incapacitated()) + cultists += M + + if(length(cultists) < req_cultists) // Stop the rune there's not enough invokers + visible_message("[src] loses its glow and dissipates!") + qdel(src) -/obj/effect/rune/leeching/invoke(var/list/invokers) - var/mob/living/user = invokers[1] - user.changeNext_move(CLICK_CD_MELEE) - ..() - var/turf/T = get_turf(src) - for(var/mob/living/carbon/M in T.contents - user) - if(M.stat != DEAD) - var/drained_amount = rand(10,20) - M.apply_damage(drained_amount, BRUTE, "chest") - user.adjustBruteLoss(-drained_amount) - to_chat(M, "You feel extremely weak.") - user.Beam(T,icon_state="drainbeam",time=5) - user.visible_message("Blood flows from the rune into [user]!", \ - "Blood flows into you, healing your wounds and revitalizing your spirit.") - - -//Rite of Spectral Manifestation: Summons a ghost on top of the rune as a cultist human with no items. User must stand on the rune at all times, and takes damage for each summoned ghost. /obj/effect/rune/manifest - cultist_name = "Rite of Spectral Manifestation" - cultist_desc = "manifests a spirit as a servant of your god. The invoker must not move from atop the rune, and will take damage for each summoned spirit." + cultist_name = "Spirit Realm" + cultist_desc = "manifests a spirit servant of the Dark One and allows you to ascend as a spirit yourself. The invoker must not move from atop the rune, and will take damage for each summoned spirit." invocation = "Gal'h'rfikk harfrandid mud'gib!" //how the fuck do you pronounce this - icon_state = "6" - construct_invoke = 0 - color = rgb(200, 0, 0) - var/list/summoned_guys = list() - var/ghost_limit = 5 + icon_state = "spirit_realm" + construct_invoke = FALSE + var/mob/dead/observer/ghost = null //The cult ghost of the user + var/default_ghost_limit = 4 //Lowered by the amount of cult objectives done + var/minimum_ghost_limit = 2 //But cant go lower than this var/ghosts = 0 - invoke_damage = 10 - -/obj/effect/rune/manifest/New(loc) - ..() - cultist_desc = "manifests a spirit as a servant of [SSticker.cultdat.entity_title3]. The invoker must not move from atop the rune, and will take damage for each summoned spirit." - notify_ghosts("Manifest rune created in [get_area(src)].", ghost_sound='sound/effects/ghost2.ogg', source = src) +/obj/effect/rune/manifest/examine(mob/user) + . = ..() + if(iscultist(user) || user.stat == DEAD) + . += "Amount of ghosts summoned: [ghosts]" + . += "Maximum amount of ghosts: [clamp(default_ghost_limit - SSticker.mode.cult_objs.sacrifices_done, minimum_ghost_limit, default_ghost_limit)]" + . += "Lowers to a minimum of [minimum_ghost_limit] for each objective accomplished." -/obj/effect/rune/manifest/can_invoke(mob/living/user) - if(ghosts >= ghost_limit) - to_chat(user, "You are sustaining too many ghosts to summon more!") - fail_invoke() - log_game("Manifest rune failed - too many summoned ghosts") - return list() +/obj/effect/rune/manifest/invoke(list/invokers) + . = ..() + var/mob/living/user = invokers[1] + var/turf/T = get_turf(src) if(!(user in get_turf(src))) - to_chat(user,"You must be standing on [src]!") + to_chat(user, "You must be standing on [src]!") fail_invoke() log_game("Manifest rune failed - user not standing on rune") - return list() + return if(user.has_status_effect(STATUS_EFFECT_SUMMONEDGHOST)) - to_chat(user, "Ghosts can't summon more ghosts!") + to_chat(user, "Ghosts can't summon more ghosts!") fail_invoke() log_game("Manifest rune failed - user is a ghost") - return list() + return + + var/choice = alert(user, "You tear open a connection to the spirit realm...", null, "Summon a Cult Ghost", "Ascend as a Dark Spirit", "Cancel") + if(choice == "Summon a Cult Ghost") + if(!is_station_level(z) || istype(get_area(src), /area/space)) + to_chat(user, "The veil is not weak enough here to manifest spirits, you must be on station!") + fail_invoke() + log_game("Manifest rune failed - not on station") + return + if(user.health <= 40) + to_chat(user, "Your body is too weak to manifest spirits, heal yourself first.") + fail_invoke() + log_game("Manifest rune failed - not enough health") + return list() + if(ghosts >= clamp(default_ghost_limit - SSticker.mode.cult_objs.sacrifices_done, minimum_ghost_limit, default_ghost_limit)) + to_chat(user, "You are sustaining too many ghosts to summon more!") + fail_invoke() + log_game("Manifest rune failed - too many summoned ghosts") + return list() + summon_ghosts(user, T) + + else if(choice == "Ascend as a Dark Spirit") + ghostify(user, T) + + +/obj/effect/rune/manifest/proc/summon_ghosts(mob/living/user, turf/T) + notify_ghosts("Manifest rune created in [get_area(src)].", ghost_sound = 'sound/effects/ghost2.ogg', source = src) var/list/ghosts_on_rune = list() - for(var/mob/dead/observer/O in get_turf(src)) - if(O.client && !jobban_isbanned(O, ROLE_CULTIST) && !jobban_isbanned(O, ROLE_SYNDICATE)) - ghosts_on_rune |= O - if(!ghosts_on_rune.len) + for(var/mob/dead/observer/O in T) + if(O.client && !iscultist(O) && !jobban_isbanned(O, ROLE_CULTIST) && !O.has_enabled_antagHUD && !QDELETED(src) && !QDELETED(O)) + ghosts_on_rune += O + if(!length(ghosts_on_rune)) to_chat(user, "There are no spirits near [src]!") fail_invoke() log_game("Manifest rune failed - no nearby ghosts") return list() - return ..() -/obj/effect/rune/manifest/invoke(var/list/invokers) - var/mob/living/user = invokers[1] - var/list/ghosts_on_rune = list() - for(var/mob/dead/observer/O in get_turf(src)) - if(O.client && !jobban_isbanned(O, ROLE_CULTIST) && !jobban_isbanned(O, ROLE_SYNDICATE)) - ghosts_on_rune |= O var/mob/dead/observer/ghost_to_spawn = pick(ghosts_on_rune) - var/mob/living/carbon/human/new_human = new(get_turf(src)) + var/mob/living/carbon/human/new_human = new(T) new_human.real_name = ghost_to_spawn.real_name + new_human.key = ghost_to_spawn.key new_human.alpha = 150 //Makes them translucent new_human.equipOutfit(/datum/outfit/ghost_cultist) //give them armor - new_human.apply_status_effect(STATUS_EFFECT_SUMMONEDGHOST) //ghosts can't summon more ghosts - new_human.color = "grey" //heh..cult greytide...litterly... - ..() - - playsound(src, 'sound/misc/exit_blood.ogg', 50, 1) - visible_message("A cloud of red mist forms above [src], and from within steps... a humanoid shape.") - to_chat(user, "Your blood begins flowing into [src]. You must remain in place and conscious to maintain the forms of those summoned. This will hurt you slowly but surely...") - var/obj/machinery/shield/N = new(get_turf(src)) - N.name = "Invoker's Shield" - N.desc = "A weak shield summoned by cultists to protect them while they carry out delicate rituals" - N.color = "red" - N.health = 20 - N.mouse_opacity = MOUSE_OPACITY_TRANSPARENT - new_human.key = ghost_to_spawn.key - SSticker.mode.add_cultist(new_human.mind, FALSE) - new_human.mind.special_role = SPECIAL_ROLE_CULTIST - summoned_guys |= new_human + new_human.apply_status_effect(STATUS_EFFECT_SUMMONEDGHOST) //ghosts can't summon more ghosts, also lets you see actual ghosts ghosts++ - to_chat(new_human, "You are a servant of [SSticker.cultdat.entity_title3]. You have been made semi-corporeal by the cult of [SSticker.cultdat.entity_name], and you are to serve them at all costs.") - - while(user in get_turf(src)) - if(user.stat) + playsound(src, 'sound/misc/exit_blood.ogg', 50, TRUE) + user.visible_message("A cloud of red mist forms above [src], and from within steps... a [new_human.gender == FEMALE ? "wo" : ""]man.", + "Your blood begins flowing into [src]. You must remain in place and conscious to maintain the forms of those summoned. This will hurt you slowly but surely...") + + var/obj/machinery/shield/cult/weak/shield = new(T) + SSticker.mode.add_cultist(new_human.mind, 0) + to_chat(new_human, "You are a servant of the [SSticker.cultdat.entity_title3]. You have been made semi-corporeal by the cult of [SSticker.cultdat.entity_name], and you are to serve them at all costs.") + + while(!QDELETED(src) && !QDELETED(user) && !QDELETED(new_human) && (user in T)) + if(new_human.InCritical()) + to_chat(user, "You feel your connection to [new_human.real_name] severs as they are destroyed.") + if(ghost) + to_chat(ghost, "You feel your connection to [new_human.real_name] severs as they are destroyed.") break - if(!atoms_share_level(get_turf(new_human), get_turf(user))) + if(user.stat || user.health <= 40) + to_chat(user, "Your body can no longer sustain the connection, and your link to the spirit realm fades.") + if(ghost) + to_chat(ghost, "Your body is damaged and your connection to the spirit realm weakens, any ghost you may have manifested are destroyed.") break user.apply_damage(0.1, BRUTE) - sleep(3) + user.apply_damage(0.1, BURN) + sleep(2) //Takes two pylons to sustain the damage taken by summoning one ghost - qdel(N) + qdel(shield) + ghosts-- if(new_human) - new_human.visible_message("[new_human] suddenly dissolves into bones and ashes.", \ + new_human.visible_message("[new_human] suddenly dissolves into bones and ashes.", "Your link to the world fades. Your form breaks apart.") - for(var/obj/I in new_human) + for(var/obj/item/I in new_human.get_all_slots()) new_human.unEquip(I) - summoned_guys -= new_human - ghosts-- - SSticker.mode.remove_cultist(new_human.mind, FALSE) + SSticker.mode.remove_cultist(new_human, FALSE) new_human.dust() -/obj/effect/rune/manifest/Destroy() - for(var/mob/living/carbon/human/guy in summoned_guys) - for(var/obj/I in guy) - guy.unEquip(I) - guy.dust() - summoned_guys.Cut() +/obj/effect/rune/manifest/proc/ghostify(mob/living/user, turf/T) + user.add_atom_colour(RUNE_COLOR_DARKRED, ADMIN_COLOUR_PRIORITY) + user.visible_message("[user] freezes statue-still, glowing an unearthly red.", + "You see what lies beyond. All is revealed. In this form you find that your voice booms above all others.") + ghost = user.ghostize(TRUE) + var/datum/action/innate/cult/comm/spirit/CM = new + var/datum/action/innate/cult/check_progress/V = new + //var/datum/action/innate/cult/ghostmark/GM = new + ghost.name = "Dark Spirit of [ghost.name]" + ghost.color = "red" + CM.Grant(ghost) + V.Grant(ghost) + //GM.Grant(ghost) + while(!QDELETED(user)) + if(!(user in T)) + user.visible_message("A spectral tendril wraps around [user] and pulls [user.p_them()] back to the rune!") + Beam(user, icon_state = "drainbeam", time = 2) + user.forceMove(get_turf(src)) //NO ESCAPE :^) + if(user.key) + user.visible_message("[user] slowly relaxes, the glow around [user.p_them()] dimming.", + "You are re-united with your physical form. [src] releases its hold over you.") + user.Weaken(3) + break + if(user.health <= 10) + to_chat(ghost, "Your body can no longer sustain the connection!") + break + sleep(5) + CM.Remove(ghost) + V.Remove(ghost) + //GM.Remove(ghost) + user.remove_atom_colour(ADMIN_COLOUR_PRIORITY, RUNE_COLOR_DARKRED) + user.grab_ghost() + user = null + rune_in_use = FALSE + + +//Ritual of Dimensional Rending: Calls forth the avatar of Nar'Sie upon the station. +/obj/effect/rune/narsie + cultist_name = "Tear Veil" + cultist_desc = "tears apart dimensional barriers, calling forth your god." + invocation = "TOK-LYR RQA-NAP G'OLT-ULOFT!!" + req_cultists = 9 + icon = 'icons/effects/96x96.dmi' + icon_state = "rune_large" + pixel_x = -32 //So the big ol' 96x96 sprite shows up right + pixel_y = -32 + mouse_opacity = MOUSE_OPACITY_ICON //we're huge and easy to click + scribe_delay = 45 SECONDS //how long the rune takes to create + scribe_damage = 10 //how much damage you take doing it + var/used = FALSE + +/obj/effect/rune/narsie/New() + ..() + cultist_name = "Summon [SSticker.cultdat ? SSticker.cultdat.entity_name : "your god"]" + cultist_desc = "tears apart dimensional barriers, calling forth [SSticker.cultdat ? SSticker.cultdat.entity_title3 : "your god"]." + +/obj/effect/rune/narsie/check_icon() + return + +/obj/effect/rune/narsie/cult_conceal() //can't hide this, and you wouldn't want to + return + +/obj/effect/rune/narsie/invoke(list/invokers) + if(used) + return + var/mob/living/user = invokers[1] + var/datum/game_mode/gamemode = SSticker.mode + if(!is_station_level(user.z)) + message_admins("[key_name_admin(user)] tried to summon an eldritch horror off station") + log_game("Summon Nar'Sie rune failed - off station Z level") + return + if(gamemode.cult_objs.cult_status == NARSIE_HAS_RISEN) + for(var/M in invokers) + to_chat(M, "\"I am already here. There is no need to try to summon me now.\"") + log_game("Summon god rune failed - already summoned") + return + + //BEGIN THE SUMMONING + gamemode.cult_objs.succesful_summon() + used = TRUE + color = rgb(255, 0, 0) + ..() + SEND_SOUND(world, 'sound/effects/dimensional_rend.ogg') + to_chat(world, "The veil... is... TORN!!!--") + icon_state = "rune_large_distorted" + var/turf/T = get_turf(src) + sleep(40) + new /obj/singularity/narsie/large(T) //Causes Nar'Sie to spawn even if the rune has been removed + +/obj/effect/rune/narsie/attackby(obj/I, mob/user, params) //Since the narsie rune takes a long time to make, add logging to removal. + if((istype(I, /obj/item/melee/cultblade/dagger) && iscultist(user))) + log_game("Summon Narsie rune erased by [key_name(user)] with a cult dagger") + message_admins("[key_name_admin(user)] erased a Narsie rune with a cult dagger") + if(istype(I, /obj/item/nullrod)) //Begone foul magiks. You cannot hinder me. + log_game("Summon Narsie rune erased by [key_name(user)] using a null rod") + message_admins("[key_name_admin(user)] erased a Narsie rune with a null rod") return ..() diff --git a/code/game/gamemodes/cult/talisman.dm b/code/game/gamemodes/cult/talisman.dm deleted file mode 100644 index af0dfb5e6e344..0000000000000 --- a/code/game/gamemodes/cult/talisman.dm +++ /dev/null @@ -1,436 +0,0 @@ -/obj/item/paper/talisman - icon = 'icons/obj/bureaucracy.dmi' - icon_state = "paper_talisman" - var/cultist_name = "talisman" - var/cultist_desc = "A basic talisman. It serves no purpose." - var/invocation = "Naise meam!" - info = "


" - var/uses = 1 - var/health_cost = 0 //The amount of health taken from the user when invoking the talisman - -/obj/item/paper/talisman/update_icon()//overriding this so the update_icon doesn't turn them into normal looking paper - SEND_SIGNAL(src, COMSIG_OBJ_UPDATE_ICON) - -/obj/item/paper/talisman/examine(mob/user) - . = ..() - if(iscultist(user) || user.stat == DEAD) - . += "Name: [cultist_name]" - . += "Effect: [cultist_desc]" - . += "Uses Remaining: [uses]" - else - . += "You see strange symbols on the paper. Are they supposed to mean something?" - -/obj/item/paper/talisman/attack_self(mob/living/user) - if(!iscultist(user)) - to_chat(user, "You see strange symbols on the paper. Are they supposed to mean something?") - return - if(invoke(user)) - uses-- - if(uses <= 0) - user.drop_item() - qdel(src) - -/obj/item/paper/talisman/proc/invoke(mob/living/user, successfuluse = 1) - . = successfuluse - if(successfuluse) //if the calling whatever says we succeed, do the fancy stuff - if(invocation) - user.whisper(invocation) - if(health_cost && iscarbon(user)) - var/mob/living/carbon/C = user - C.apply_damage(health_cost, BRUTE, pick("l_arm", "r_arm")) - -//Malformed Talisman: If something goes wrong. -/obj/item/paper/talisman/malformed - cultist_name = "malformed talisman" - cultist_desc = "A talisman with gibberish scrawlings. No good can come from invoking this." - invocation = "Ra'sha yoka!" - -/obj/item/paper/talisman/malformed/invoke(mob/living/user, successfuluse = 1) - to_chat(user, "You feel a pain in your head. [SSticker.cultdat.entity_title3] is displeased.") - if(iscarbon(user)) - var/mob/living/carbon/C = user - C.apply_damage(10, BRUTE, "head") - -//Supply Talisman: Has a few unique effects. Granted only to starter cultists. -/obj/item/paper/talisman/supply - cultist_name = "Supply Talisman" - icon_state = "supply" - cultist_desc = "A multi-use talisman that can create various objects. Intended to increase the cult's strength early on." - invocation = null - uses = 3 - -/obj/item/paper/talisman/supply/invoke(mob/living/user, successfuluse = 1) - var/dat = list() - dat += "There are [uses] bloody runes on the parchment.
" - dat += "Please choose the chant to be imbued into the fabric of reality.
" - dat += "
" - dat += "N'ath reth sh'yro eth d'raggathnor! - Summons an arcane tome, used to scribe runes and communicate with other cultists.
" - dat += "Bar'tea eas! - Provides 5 runed metal.
" - dat += "Sas'so c'arta forbici! - Allows you to move to a selected teleportation rune.
" - dat += "Ta'gh fara'qha fel d'amar det! - Allows you to destroy technology in a short range.
" - dat += "Fuu ma'jin! - Allows you to stun a person by attacking them with the talisman.
" - dat += "Kla'atu barada nikt'o! - Two use talisman, first use makes all nearby runes invisible, second use reveals nearby hidden runes.
" - dat += "Kal'om neth! - Summons a soul stone, used to capture the spirits of dead or dying humans.
" - dat += "Daa'ig osk! - Summons a construct shell for use with soulstone-captured souls. It is too large to carry on your person.
" - var/datum/browser/popup = new(user, "talisman", "", 400, 400) - popup.set_content(jointext(dat, "")) - popup.open() - return 0 - -/obj/item/paper/talisman/supply/Topic(href, href_list) - if(src) - if(usr.stat || usr.restrained() || !in_range(src, usr)) - return - if(href_list["rune"]) - switch(href_list["rune"]) - if("newtome") - var/obj/item/tome/T = new(usr) - usr.put_in_hands(T) - if("metal") - if(istype(src, /obj/item/paper/talisman/supply/weak)) - usr.visible_message("Lesser supply talismans lack the strength to materialize runed metal!") - return - var/obj/item/stack/sheet/runed_metal/R = new(usr,5) - usr.put_in_hands(R) - if("teleport") - var/obj/item/paper/talisman/teleport/T = new(usr) - usr.put_in_hands(T) - if("emp") - var/obj/item/paper/talisman/emp/T = new(usr) - usr.put_in_hands(T) - if("runestun") - var/obj/item/paper/talisman/stun/T = new(usr) - usr.put_in_hands(T) - if("soulstone") - var/obj/item/soulstone/T = new(usr) - usr.put_in_hands(T) - if("construct") - new /obj/structure/constructshell(get_turf(usr)) - if("veiling") - var/obj/item/paper/talisman/true_sight/T = new(usr) - usr.put_in_hands(T) - uses-- - if(uses <= 0) - if(iscarbon(usr)) - var/mob/living/carbon/C = usr - C.drop_item() - visible_message("[src] crumbles to dust.") - qdel(src) - -/obj/item/paper/talisman/supply/weak - uses = 2 - -//Rite of Translocation: Same as rune -/obj/item/paper/talisman/teleport - cultist_name = "Talisman of Teleportation" - icon_state = "teleport" - cultist_desc = "A single-use talisman that will teleport a user to a random rune of the same keyword." - invocation = "Sas'so c'arta forbici!" - health_cost = 5 - -/obj/item/paper/talisman/teleport/invoke(mob/living/user, successfuluse = 1) - var/list/potential_runes = list() - var/list/teleportnames = list() - var/list/duplicaterunecount = list() - for(var/R in GLOB.teleport_runes) - var/obj/effect/rune/teleport/T = R - var/resultkey = T.listkey - if(resultkey in teleportnames) - duplicaterunecount[resultkey]++ - resultkey = "[resultkey] ([duplicaterunecount[resultkey]])" - else - teleportnames.Add(resultkey) - duplicaterunecount[resultkey] = 1 - potential_runes[resultkey] = T - - if(!potential_runes.len) - to_chat(user, "There are no valid runes to teleport to!") - log_game("Teleport talisman failed - no other teleport runes") - return ..(user, 0) - - if(!is_level_reachable(user.z)) - to_chat(user, "You are not in the right dimension!") - log_game("Teleport talisman failed - user in away mission") - return ..(user, 0) - - var/input_rune_key = input(user, "Choose a rune to teleport to.", "Rune to Teleport to") as null|anything in potential_runes //we know what key they picked - var/obj/effect/rune/teleport/actual_selected_rune = potential_runes[input_rune_key] //what rune does that key correspond to? - if(!src || QDELETED(src) || !user || user.l_hand != src && user.r_hand != src || user.incapacitated() || !actual_selected_rune) - return ..(user, 0) - - user.visible_message("Dust flows from [user]'s hand, and [user.p_they()] disappear[user.p_s()] in a flash of red light!", \ - "You speak the words of the talisman and find yourself somewhere else!") - user.forceMove(get_turf(actual_selected_rune)) - return ..() - - -/obj/item/paper/talisman/summon_tome - cultist_name = "Talisman of Tome Summoning" - icon_state = "tome" - cultist_desc = "A one-use talisman that will call an untranslated tome from the archives of a cult." - invocation = "N'ath reth sh'yro eth d'raggathnor!" - health_cost = 1 - -/obj/item/paper/talisman/summon_tome/invoke(mob/living/user, successfuluse = 1) - . = ..() - user.visible_message("[user]'s hand glows red for a moment.", \ - "You speak the words of the talisman!") - new /obj/item/tome(get_turf(user)) - user.visible_message("A tome appears at [user]'s feet!", \ - "An arcane tome materializes at your feet.") - -/obj/item/paper/talisman/true_sight - cultist_name = "Talisman of Veiling" - icon_state = "veil" - cultist_desc = "A multi-use talisman that hides nearby runes. On its second use, will reveal nearby runes." - invocation = "Kla'atu barada nikt'o!" - health_cost = 1 - uses = 2 - var/revealing = FALSE //if it reveals or not - -/obj/item/paper/talisman/true_sight/invoke(mob/living/user, successfuluse = 1) - . = ..() - if(!revealing) - user.visible_message("Thin grey dust falls from [user]'s hand!", \ - "You speak the words of the talisman, hiding nearby runes.") - invocation = "Nikt'o barada kla'atu!" - revealing = TRUE - for(var/obj/effect/rune/R in range(3,user)) - R.talismanhide() - else - user.visible_message("A flash of light shines from [user]'s hand!", \ - "You speak the words of the talisman, revealing nearby runes.") - for(var/obj/effect/rune/R in range(3,user)) - R.talismanreveal() - -//Rite of False Truths: Same as rune -/obj/item/paper/talisman/make_runes_fake - cultist_name = "Talisman of Disguising" - icon_state = "disguising" - cultist_desc = "A talisman that will make nearby runes appear fake." - invocation = "By'o nar'nar!" - -/obj/item/paper/talisman/make_runes_fake/invoke(mob/living/user, successfuluse = 1) - . = ..() - user.visible_message("Dust flows from [user]s hand.", \ - "You speak the words of the talisman, making nearby runes appear fake.") - for(var/obj/effect/rune/R in orange(6,user)) - R.talismanfake() - R.desc = "A rune drawn in crayon." - - -//Rite of Disruption: Weaker than rune -/obj/item/paper/talisman/emp - cultist_name = "Talisman of Electromagnetic Pulse" - icon_state = "emp" - cultist_desc = "A talisman that will cause a moderately-sized electromagnetic pulse." - invocation = "Ta'gh fara'qha fel d'amar det!" - health_cost = 5 - -/obj/item/paper/talisman/emp/invoke(mob/living/user, successfuluse = 1) - . = ..() - user.visible_message("[user]'s hand flashes a bright blue!", \ - "You speak the words of the talisman, emitting an EMP blast.") - empulse(src, 4, 8, 1) - - -//Rite of Disorientation: Stuns and inhibit speech on a single target for quite some time -/obj/item/paper/talisman/stun - cultist_name = "Talisman of Stunning" - icon_state = "stunning" - cultist_desc = "A talisman that will stun and inhibit speech on a single target. To use, attack target directly." - invocation = "Dream sign:Evil sealing talisman!" - health_cost = 10 - -/obj/item/paper/talisman/stun/invoke(mob/living/user, successfuluse = 0) - if(successfuluse) //if we're forced to be successful(we normally aren't) then do the normal stuff - return ..() - if(iscultist(user)) - to_chat(user, "To use this talisman, attack the target directly.") - else - to_chat(user, "You see strange symbols on the paper. Are they supposed to mean something?") - return 0 - -/obj/item/paper/talisman/stun/attack(mob/living/target, mob/living/user, successfuluse = 1) - if(iscultist(user)) - invoke(user, 1) - user.visible_message("[user] holds up [src], which explodes in a flash of red light!", \ - "You stun [target] with the talisman!") - var/obj/item/nullrod/N = locate() in target - if(N) - target.visible_message("[target]'s holy weapon absorbs the talisman's light!", \ - "Your holy weapon absorbs the blinding light!") - else - add_attack_logs(user, target, "Stunned with a talisman") - target.Weaken(10) - target.Stun(10) - target.flash_eyes(1,1) - if(issilicon(target)) - var/mob/living/silicon/S = target - S.emp_act(1) - if(iscarbon(target)) - var/mob/living/carbon/C = target - C.AdjustSilence(5) - C.AdjustStuttering(15) - C.AdjustCultSlur(20) - C.Jitter(15) - user.drop_item() - qdel(src) - return - ..() - - -//Rite of Arming: Equips cultist armor on the user, where available -/obj/item/paper/talisman/armor - cultist_name = "Talisman of Arming" - icon_state = "arming" - cultist_desc = "A talisman that will equip the invoker with cultist equipment if there is a slot to equip it to." - invocation = "N'ath reth sh'yro eth draggathnor!" - -/obj/item/paper/talisman/armor/invoke(mob/living/user, successfuluse = 1) - . = ..() - var/mob/living/carbon/human/H = user - user.visible_message("Otherworldly equipment suddenly appears on [user]!", \ - "You speak the words of the talisman, arming yourself!") - - H.equip_to_slot_or_del(new /obj/item/clothing/suit/hooded/cultrobes/alt(user), slot_wear_suit) - H.equip_to_slot_or_del(new /obj/item/storage/backpack/cultpack(user), slot_back) - H.equip_to_slot_or_del(new /obj/item/clothing/shoes/cult(user), slot_shoes) - H.put_in_hands(new /obj/item/melee/cultblade(user)) - H.put_in_hands(new /obj/item/restraints/legcuffs/bola/cult(user)) - -/obj/item/paper/talisman/armor/attack(mob/living/target, mob/living/user) - if(iscultist(user) && iscultist(target)) - user.drop_item() - qdel(src) - invoke(target) - return - ..() - - -//Talisman of Horrors: Breaks the mind of the victim with nightmarish hallucinations -/obj/item/paper/talisman/horror - cultist_name = "Talisman of Horrors" - icon_state = "horror" - cultist_desc = "A talisman that will break the mind of the victim with nightmarish hallucinations." - invocation = "Lo'Nab Na'Dm!" - -/obj/item/paper/talisman/horror/attack(mob/living/target, mob/living/user) - if(iscultist(user)) - to_chat(user, "You disturb [target] with visions of the end!") - if(iscarbon(target)) - var/mob/living/carbon/H = target - H.AdjustHallucinate(30) - qdel(src) - - -//Talisman of Fabrication: Creates a construct shell out of 25 metal sheets. -/obj/item/paper/talisman/construction - cultist_name = "Talisman of Construction" - icon_state = "construction" - cultist_desc = "Use this talisman on at least twenty-five metal sheets to create an empty construct shell or on plasteel to make runed metal" - invocation = "Ethra p'ni dedol!" - uses = 25 - -/obj/item/paper/talisman/construction/attack_self(mob/living/user) - if(iscultist(user)) - to_chat(user, "To use this talisman, place it upon a stack of metal sheets or plasteel sheets!") - else - to_chat(user, "You see strange symbols on the paper. Are they supposed to mean something?") - - -/obj/item/paper/talisman/construction/attack(obj/M,mob/living/user) - if(iscultist(user)) - to_chat(user, "This talisman will only work on a stack of metal sheets or plasteel sheets!") - log_game("Construct talisman failed - not a valid target") - -/obj/item/paper/talisman/construction/afterattack(obj/item/stack/sheet/target, mob/user, proximity_flag, click_parameters) - ..() - if(proximity_flag && iscultist(user)) - if(istype(target, /obj/item/stack/sheet/metal)) - var/turf/T = get_turf(target) - if(target.use(25)) - new /obj/structure/constructshell(T) - to_chat(user, "The talisman clings to the metal and twists it into a construct shell!") - user << sound('sound/magic/staff_chaos.ogg',0,1,25) - qdel(src) - return - if(istype(target, /obj/item/stack/sheet/plasteel)) - var/turf/T = get_turf(target) - var/quantity = min(target.amount, uses) - uses -= quantity - new /obj/item/stack/sheet/runed_metal(T,quantity) - target.use(quantity) - to_chat(user, "The talisman clings to the plasteel, transforming it into runed metal!") - user << sound('sound/magic/staff_chaos.ogg',0,1,25) - invoke(user, 1) - if(uses <= 0) - qdel(src) - else - to_chat(user, "The talisman must be used on metal or plasteel!") - -//Talisman of Shackling: Applies special cuffs directly from the talisman -/obj/item/paper/talisman/shackle - cultist_name = "Talisman of Shackling" - icon_state = "shackling" - cultist_desc = "Use this talisman on a victim to handcuff them with dark bindings." - invocation = "In'totum Lig'abis!" - uses = 4 - -/obj/item/paper/talisman/shackle/invoke(mob/living/user, successfuluse = 0) - if(successfuluse) //if we're forced to be successful(we normally aren't) then do the normal stuff - return ..() - if(iscultist(user)) - to_chat(user, "To use this talisman, attack the target directly.") - else - to_chat(user, "You see strange symbols on the paper. Are they supposed to mean something?") - return 0 - -/obj/item/paper/talisman/shackle/attack(mob/living/carbon/target, mob/living/user) - if(iscultist(user) && istype(target)) - if(target.stat == DEAD) - user.visible_message("This talisman's magic does not affect the dead!") - return - CuffAttack(target, user) - return - ..() - -/obj/item/paper/talisman/shackle/proc/CuffAttack(mob/living/carbon/C, mob/living/user) - if(!C.handcuffed) - invoke(user, 1) - playsound(loc, 'sound/weapons/cablecuff.ogg', 30, 1, -2) - C.visible_message("[user] begins restraining [C] with dark magic!", \ - "[user] begins shaping a dark magic around your wrists!") - if(do_mob(user, C, 30)) - if(!C.handcuffed) - C.handcuffed = new /obj/item/restraints/handcuffs/energy/cult/used(C) - C.update_handcuffed() - to_chat(user, "You shackle [C].") - add_attack_logs(user, C, "Handcuffed (shackle talisman)") - uses-- - else - to_chat(user, "[C] is already bound.") - else - to_chat(user, "You fail to shackle [C].") - else - to_chat(user, "[C] is already bound.") - if(uses <= 0) - user.drop_item() - qdel(src) - return - -/obj/item/restraints/handcuffs/energy/cult //For the talisman of shackling - name = "cult shackles" - desc = "Shackles that bind the wrists with sinister magic." - trashtype = /obj/item/restraints/handcuffs/energy/used - origin_tech = "materials=2;magnets=5" - -/obj/item/restraints/handcuffs/energy/used - desc = "energy discharge" - flags = DROPDEL - -/obj/item/restraints/handcuffs/energy/cult/used/dropped(mob/user) - user.visible_message("[user]'s shackles shatter in a discharge of dark magic!", \ - "Your [src] shatters in a discharge of dark magic!") - qdel(src) - . = ..() diff --git a/code/game/gamemodes/miniantags/slaughter/slaughter.dm b/code/game/gamemodes/miniantags/slaughter/slaughter.dm index e58be20152185..be0e649cc2b28 100644 --- a/code/game/gamemodes/miniantags/slaughter/slaughter.dm +++ b/code/game/gamemodes/miniantags/slaughter/slaughter.dm @@ -120,7 +120,7 @@ melee_damage_lower = 60 environment_smash = ENVIRONMENT_SMASH_RWALLS //Smashes through EVERYTHING - r-walls included faction = list("cult") - playstyle_string = "You are a Harbinger of the Slaughter. Brought forth by the servants of Nar-Sie, you have a single purpose: slaughter the heretics \ + playstyle_string = "You are a Harbinger of the Slaughter. Brought forth by the servants of Nar'Sie, you have a single purpose: slaughter the heretics \ who do not worship your master. You may use the ability 'Blood Crawl' near a pool of blood to enter it and become incorporeal. Using the ability again near a blood pool will allow you \ to emerge from it. You are fast, powerful, and almost invincible. By dragging a dead or unconscious body into a blood pool with you, you will consume it after a time and fully regain \ your health. You may use the ability 'Sense Victims' in your Cultist tab to locate a random, living heretic.
" diff --git a/code/game/gamemodes/wizard/soulstone.dm b/code/game/gamemodes/wizard/soulstone.dm index aab4318c77488..d01c68c6ff286 100644 --- a/code/game/gamemodes/wizard/soulstone.dm +++ b/code/game/gamemodes/wizard/soulstone.dm @@ -1,21 +1,32 @@ +// This whole file really needs reorganising at some point, or at the very least the construct stuff should be moved somewhere else. /obj/item/soulstone - name = "Soul Stone Shard" + name = "soul stone shard" icon = 'icons/obj/wizard.dmi' icon_state = "soulstone" item_state = "electronic" + var/icon_state_full = "soulstone2" desc = "A fragment of the legendary treasure known simply as the 'Soul Stone'. The shard still flickers with a fraction of the full artifact's power." w_class = WEIGHT_CLASS_TINY slot_flags = SLOT_BELT origin_tech = "bluespace=4;materials=5" - var/optional = FALSE //does this soulstone ask the victim whether they want to be turned into a shade - var/usability = FALSE // Can this soul stone be used by anyone, or only cultists/wizards? - var/reusable = TRUE // Can this soul stone be used more than once? - var/spent = FALSE // If the soul stone can only be used once, has it been used? + /// Does this soulstone ask the victim whether they want to be turned into a shade + var/optional = FALSE + /// Can this soul stone be used by anyone, or only cultists/wizards? + var/usability = FALSE + /// Can this soul stone be used more than once? + var/reusable = TRUE + /// If the soul stone can only be used once, has it been used? + var/spent = FALSE - var/opt_in = FALSE // for tracking during the 'optional' bit + /// For tracking during the 'optional' bit + var/opt_in = FALSE + var/purified = FALSE /obj/item/soulstone/proc/can_use(mob/living/user) + if(iscultist(user) && purified && !iswizard(user)) + return FALSE + if(iscultist(user) || iswizard(user) || usability) return TRUE @@ -32,21 +43,36 @@ /obj/item/soulstone/anybody usability = TRUE -/obj/item/soulstone/anybody/chaplain +/obj/item/soulstone/anybody/purified + icon_state = "purified_soulstone" + icon_state_full = "purified_soulstone2" + purified = TRUE + optional = TRUE + +/obj/item/soulstone/anybody/purified/chaplain name = "mysterious old shard" reusable = FALSE - optional = TRUE /obj/item/soulstone/pickup(mob/living/user) . = ..() + if(iscultist(user) && purified && !iswizard(user)) + to_chat(user, "[src] reeks of holy magic. You will need to cleanse it with a ritual dagger before anything can be done with it.") if(!can_use(user)) - to_chat(user, "An overwhelming feeling of dread comes over you as you pick up the soulstone. It would be wise to be rid of this quickly.") - user.Dizzy(120) + to_chat(user, "An overwhelming feeling of dread comes over you as you pick up [src].") + +/obj/item/soulstone/Destroy() //Stops the shade from being qdel'd immediately and their ghost being sent back to the arrival shuttle. + for(var/mob/living/simple_animal/shade/A in src) + A.death() + return ..() //////////////////////////////Capturing//////////////////////////////////////////////////////// -/obj/item/soulstone/attack(mob/living/carbon/human/M as mob, mob/user as mob) +/obj/item/soulstone/attack(mob/living/carbon/human/M, mob/user) + if(M == user) + return + if(!can_use(user)) - user.Paralyse(5) + user.Weaken(5) + user.emote("scream") to_chat(user, "Your body is wracked with debilitating pain!") return @@ -61,7 +87,7 @@ to_chat(user, "This being is corrupted by an alien intelligence and cannot be soul trapped.") return ..() - if(jobban_isbanned(M, "cultist") || jobban_isbanned(M, "Syndicate")) + if(jobban_isbanned(M, ROLE_CULTIST) || jobban_isbanned(M, ROLE_SYNDICATE)) to_chat(user, "A mysterious force prevents you from trapping this being's soul.") return ..() @@ -77,8 +103,9 @@ to_chat(user, "You attempt to channel [M]'s soul into [src]. You must give the soul some time to react and stand still...") var/mob/player_mob = M - if(M.get_ghost())//in case our player ghosted and we need to throw the alert at their ghost instead - player_mob = M.get_ghost() + var/ghost = M.get_ghost() + if(ghost) // In case our player ghosted and we need to throw the alert at their ghost instead + player_mob = ghost var/client/player_client = player_mob.client to_chat(player_mob, "[user] is trying to capture your soul into [src]! Click the button in the top right of the game window to respond.") player_client << 'sound/misc/notice2.ogg' @@ -88,11 +115,11 @@ if(player_client.prefs && player_client.prefs.UI_style) A.icon = ui_style2icon(player_client.prefs.UI_style) - //pass the stuff to the alert itself + // Pass the stuff to the alert itself A.stone = src A.stoner = user.real_name - //layer shenanigans to make the alert display the soulstone + // Layer shenanigans to make the alert display the soulstone var/old_layer = layer var/old_plane = plane layer = FLOAT_LAYER @@ -101,7 +128,7 @@ layer = old_layer plane = old_plane - //give the victim 10 seconds to respond + // Give the victim 10 seconds to respond sleep(10 SECONDS) if(!opt_in) @@ -117,57 +144,81 @@ transfer_soul("VICTIM", M, user) return -///////////////////Options for using captured souls/////////////////////////////////////// +/obj/item/soulstone/attackby(obj/item/O, mob/user) + if(istype(O, /obj/item/storage/bible) && !iscultist(user)) + if(purified) + return + to_chat(user, "You begin to exorcise [src].") + playsound(src, 'sound/hallucinations/veryfar_noise.ogg', 40, TRUE) + if(do_after(user, 40, target = src)) + usability = TRUE + purified = TRUE + optional = TRUE + icon_state = "purified_soulstone" + icon_state_full = "purified_soulstone2" + for(var/mob/M in contents) + if(M.mind) + icon_state = "purified_soulstone2" + if(iscultist(M)) + SSticker.mode.remove_cultist(M.mind, FALSE) + to_chat(M, "You feel the cult's influence vanish. Assist [user], your saviour, and get vengeance on those who enslaved you!") + else + to_chat(M, "Your soulstone has been exorcised, and you are now bound to obey [user]. ") + + for(var/mob/living/simple_animal/shade/EX in src) + EX.holy = TRUE + EX.icon_state = "shade_angelic" + user.visible_message("[user] purifies [src]!", "You purify [src]!") + + else if(istype(O, /obj/item/melee/cultblade/dagger) && iscultist(user)) + if(!purified) + return + to_chat(user, "You begin to cleanse [src] of holy magic.") + if(do_after(user, 40, target = src)) + usability = FALSE + purified = FALSE + optional = FALSE + icon_state = "soulstone" + icon_state_full = "soulstone2" + for(var/mob/M in contents) + if(M.mind) + icon_state = "soulstone2" + SSticker.mode.add_cultist(M.mind) + to_chat(M, "Your shard has been cleansed of holy magic, and you are now bound to the cult's will. Obey them and assist in their goals.") + for(var/mob/living/simple_animal/shade/EX in src) + EX.holy = FALSE + EX.icon_state = SSticker.cultdat?.shade_icon_state + to_chat(user, "You have cleansed [src] of holy magic.") + else + ..() + /obj/item/soulstone/attack_self(mob/user) if(!in_range(src, user)) return if(!can_use(user)) - user.Paralyse(5) + user.Weaken(5) + user.emote("scream") to_chat(user, "Your body is wracked with debilitating pain!") return - user.set_machine(src) - var/dat = "Soul Stone
" - for(var/mob/living/simple_animal/shade/A in src) - dat += "Captured Soul: [A.name]
" - dat += {"Summon Shade"} - dat += "
" - dat += {" Close"} - user << browse(dat, "window=aicard") - onclose(user, "aicard") + release_shades(user) return -/obj/item/soulstone/Topic(href, href_list) - var/mob/U = usr - if(!in_range(src, U) || U.machine != src || !can_use(usr)) - U << browse(null, "window=aicard") - U.unset_machine() - return - - add_fingerprint(U) - U.set_machine(src) - - switch(href_list["choice"])//Now we switch based on choice. - if("Close") - U << browse(null, "window=aicard") - U.unset_machine() - return - - if("Summon") - for(var/mob/living/simple_animal/shade/A in src) - A.status_flags &= ~GODMODE - A.canmove = 1 - A.forceMove(get_turf(usr)) - A.cancel_camera() - icon_state = "soulstone" - name = initial(name) - if(iswizard(usr) || usability) - to_chat(A, "You have been released from your prison, but you are still bound to [usr.real_name]'s will. Help [usr.p_them()] succeed in [usr.p_their()] goals at all costs.") - else if(iscultist(usr)) - to_chat(A, "You have been released from your prison, but you are still bound to the cult's will. Help [usr.p_them()] succeed in [usr.p_their()] goals at all costs.") - was_used() - attack_self(U) +/obj/item/soulstone/proc/release_shades(mob/user) + for(var/mob/living/simple_animal/shade/A in src) + A.forceMove(get_turf(user)) + A.cancel_camera() + if(purified) + icon_state = "purified_soulstone" + else + icon_state = "soulstone" + name = initial(name) + if(iscultist(A)) + to_chat(A, "You have been released from your prison, but you are still bound to the cult's will. Help them succeed in their goals at all costs.") + else + to_chat(A, "You have been released from your prison, but you are still bound to your [purified ? "saviour" : "creator"]'s will.") + was_used() ///////////////////////////Transferring to constructs///////////////////////////////////////////////////// /obj/structure/constructshell @@ -175,204 +226,207 @@ icon = 'icons/obj/wizard.dmi' icon_state = "construct-cult" desc = "A wicked machine used by those skilled in magical arts. It is inactive" + /// Is someone currently placing a soulstone into the shell + var/active = FALSE /obj/structure/constructshell/examine(mob/user) . = ..() if(in_range(user, src) && (iscultist(user) || iswizard(user) || user.stat == DEAD)) . += "A construct shell, used to house bound souls from a soulstone." . += "Placing a soulstone with a soul into this shell allows you to produce your choice of the following:" - . += "An Artificer, which can produce more shells and soulstones, as well as fortifications." - . += "A Wraith, which does high damage and can jaunt through walls, though it is quite fragile." - . += "A Juggernaut, which is very hard to kill and can produce temporary walls, but is slow." + . += "An Artificer, which can produce more shells and soulstones, as well as fortifications." + . += "A Wraith, which does high damage and can jaunt through walls, though it is quite fragile." + . += "A Juggernaut, which is very hard to kill and can produce temporary walls, but is slow." -/obj/structure/constructshell/attackby(obj/item/O as obj, mob/user as mob, params) - if(istype(O, /obj/item/soulstone)) - var/obj/item/soulstone/SS = O +/obj/structure/constructshell/attackby(obj/item/I, mob/user, params) + if(istype(I, /obj/item/soulstone)) + var/obj/item/soulstone/SS = I if(!SS.can_use(user)) - to_chat(user, "An overwhelming feeling of dread comes over you as you attempt to place the soulstone into the shell. It would be wise to be rid of this quickly.") - user.Dizzy(120) + to_chat(user, "An overwhelming feeling of dread comes over you as you attempt to place the soulstone into the shell.") + user.Confused(10) return - SS.transfer_soul("CONSTRUCT",src,user) + SS.transfer_soul("CONSTRUCT", src, user) SS.was_used() else return ..() ////////////////////////////Proc for moving soul in and out off stone////////////////////////////////////// -/obj/item/proc/transfer_soul(var/choice as text, var/target, var/mob/U as mob) +/obj/item/soulstone/proc/transfer_soul(choice, target, mob/user) switch(choice) if("FORCE") - var/obj/item/soulstone/SS = src var/mob/living/T = target if(T.client != null) - SS.init_shade(T, U) + init_shade(T, user) else - to_chat(U, "Capture failed!: The soul has already fled its mortal frame. You attempt to bring it back...") + to_chat(user, "Capture failed! The soul has already fled its mortal frame. You attempt to bring it back...") T.Paralyse(20) - if(!SS.getCultGhost(T,U)) + if(!get_cult_ghost(T, user)) T.dust() //If we can't get a ghost, kill the sacrifice anyway. if("VICTIM") var/mob/living/carbon/human/T = target - var/obj/item/soulstone/SS = src if(T.stat == 0) - to_chat(U, "Capture failed!: Kill or maim the victim first!") + to_chat(user, "Capture failed! Kill or maim the victim first!") else - if(!T.client_mobs_in_contents?.len) - to_chat(U, "They have no soul!") + if(!length(T.client_mobs_in_contents)) + to_chat(user, "They have no soul!") else if(T.client == null) - to_chat(U, "Capture failed!: The soul has already fled its mortal frame. You attempt to bring it back...") - SS.getCultGhost(T,U) + to_chat(user, "Capture failed! The soul has already fled its mortal frame. You attempt to bring it back...") + get_cult_ghost(T, user) else - if(SS.contents.len) - to_chat(U, "Capture failed!: The soul stone is full! Use or free an existing soul to make room.") + if(length(contents)) + to_chat(user, "Capture failed! The soul stone is full! Use or free an existing soul to make room.") else - SS.init_shade(T, U, vic = 1) + init_shade(T, user, TRUE) if("SHADE") var/mob/living/simple_animal/shade/T = target - var/obj/item/soulstone/SS = src - if(!SS.can_use(U)) - U.Paralyse(5) - to_chat(U, "Your body is wracked with debilitating pain!") + if(!can_use(user)) + user.Weaken(5) + to_chat(user, "Your body is wracked with debilitating pain!") return if(T.stat == DEAD) - to_chat(U, "Capture failed!: The shade has already been banished!") + to_chat(user, "Capture failed! The shade has already been banished!") + if((iscultist(T) && purified) || (T.holy && !purified)) + to_chat(user, "Capture failed! The shade recoils away from [src]!") else - if(SS.contents.len) - to_chat(U, "Capture failed!: The soul stone is full! Use or free an existing soul to make room.") + if(length(contents)) + to_chat(user, "Capture failed!: The soul stone is full! Use or free an existing soul to make room.") else - T.loc = SS //put shade in stone - T.status_flags |= GODMODE + T.loc = src //put shade in stone T.canmove = 0 T.health = T.maxHealth - T.faction |= "\ref[U]" - SS.icon_state = "soulstone2" - to_chat(T, "Your soul has been recaptured by the soul stone, its arcane energies are reknitting your ethereal form") - to_chat(U, "Capture successful!: [T.name]'s has been recaptured and stored within the soul stone.") + icon_state = icon_state_full + name = "soulstone : [T.name]" + to_chat(T, "Your soul has been recaptured by the soul stone, its arcane energies are reknitting your ethereal form") + to_chat(user, "Capture successful! [T.name]'s has been recaptured and stored within the soul stone.") + if("CONSTRUCT") - var/obj/structure/constructshell/T = target - var/obj/item/soulstone/SS = src - var/mob/living/simple_animal/shade/SH = locate() in SS - if(SH) - var/construct_class = alert(U, "Please choose which type of construct you wish to create.",,"Juggernaut","Wraith","Artificer") + var/obj/structure/constructshell/shell = target + var/mob/living/simple_animal/shade/shade = locate() in src + if(shade) + var/construct_class = alert(user, "Please choose which type of construct you wish to create.", null, "Juggernaut", "Wraith", "Artificer") + if(shell.active) + return // Don't want two people doing it at once + shell.active = TRUE switch(construct_class) if("Juggernaut") - var/mob/living/simple_animal/hostile/construct/armoured/C = new /mob/living/simple_animal/hostile/construct/armoured (get_turf(T.loc)) + var/mob/living/simple_animal/hostile/construct/armoured/C = new(shell.loc) + C.init_construct(shade, src, shell) to_chat(C, "You are a Juggernaut. Though slow, your shell can withstand extreme punishment, create shield walls and even deflect energy weapons, and rip apart enemies and walls alike.") - init_construct(C,SH,SS,T) if("Wraith") - var/mob/living/simple_animal/hostile/construct/wraith/C = new /mob/living/simple_animal/hostile/construct/wraith (get_turf(T.loc)) + var/mob/living/simple_animal/hostile/construct/wraith/C = new(shell.loc) + C.init_construct(shade, src, shell) to_chat(C, "You are a Wraith. Though relatively fragile, you are fast, deadly, and even able to phase through walls.") - init_construct(C,SH,SS,T) if("Artificer") - var/mob/living/simple_animal/hostile/construct/builder/C = new /mob/living/simple_animal/hostile/construct/builder (get_turf(T.loc)) + var/mob/living/simple_animal/hostile/construct/builder/C = new(shell.loc) + C.init_construct(shade, src, shell) to_chat(C, "You are an Artificer. You are incredibly weak and fragile, but you are able to construct fortifications, use magic missile, repair allied constructs (by clicking on them), and most important of all create new constructs (Use your Artificer spell to summon a new construct shell and Summon Soulstone to create a new soulstone).") - init_construct(C,SH,SS,T) else - to_chat(U, "Creation failed!: The soul stone is empty! Go kill someone!") + to_chat(user, "Creation failed!: The soul stone is empty! Go kill someone!") return -/proc/init_construct(mob/living/simple_animal/hostile/construct/C, mob/living/simple_animal/shade/SH, obj/item/soulstone/SS, obj/structure/constructshell/T) - SH.mind.transfer_to(C) - if(iscultist(C)) - var/datum/action/innate/cultcomm/CC = new() - CC.Grant(C) //We have to grant the cult comms again because they're lost during the mind transfer. - qdel(T) - qdel(SH) - to_chat(C, "You are still bound to serve your creator, follow their orders and help them complete their goals at all costs.") - C.cancel_camera() +/mob/living/simple_animal/hostile/construct/proc/init_construct(mob/living/simple_animal/shade/shade, obj/item/soulstone/SS, obj/structure/constructshell/shell) + if(shade.mind) + shade.mind.transfer_to(src) + if(SS.purified) + set_light(3, 5, LIGHT_COLOR_DARK_BLUE) + name = "Holy [name]" + real_name = "Holy [real_name]" + if(iscultist(src) && !SS.purified) // Re-grant cult actions, lost in the transfer + var/datum/action/innate/cult/comm/CC = new + var/datum/action/innate/cult/check_progress/D = new + CC.Grant(src) + D.Grant(src) + SSticker.mode.cult_objs.study(src) // Display objectives again + to_chat(src, "You are still bound to serve the cult, follow their orders and help them complete their goals at all costs.") + else + to_chat(src, "You are still bound to serve your creator, follow their orders and help them complete their goals at all costs.") + cancel_camera() + qdel(shell) + qdel(shade) qdel(SS) -/proc/makeNewConstruct(var/mob/living/simple_animal/hostile/construct/ctype, var/mob/target, var/mob/stoner = null, cultoverride = 0) - if(jobban_isbanned(target, "cultist") || jobban_isbanned(target, "Syndicate")) +/proc/make_new_construct(mob/living/simple_animal/hostile/construct/c_type, mob/target, mob/user, cult_override = FALSE) + if(jobban_isbanned(target, "cultist")) return - var/mob/living/simple_animal/hostile/construct/newstruct = new ctype(get_turf(target)) - newstruct.faction |= "\ref[stoner]" - newstruct.key = target.key - if(stoner && iscultist(stoner) || cultoverride) - if(SSticker.mode.name == "cult") - SSticker.mode:add_cultist(newstruct.mind) - else - SSticker.mode.cult+=newstruct.mind - SSticker.mode.update_cult_icons_added(newstruct.mind) - if(stoner && iswizard(stoner)) - to_chat(newstruct, "You are still bound to serve your creator, follow their orders and help them complete their goals at all costs.") - else if(stoner && iscultist(stoner)) - to_chat(newstruct, "You are still bound to serve the cult, follow their orders and help them complete their goals at all costs.") + var/mob/living/simple_animal/hostile/construct/C = new c_type(get_turf(target)) + C.faction |= "\ref[user]" + C.key = target.key + if(user && iscultist(user) || cult_override) + SSticker.mode.add_cultist(C.mind) + SSticker.mode.update_cult_icons_added(C.mind) + if(user && iscultist(user)) + to_chat(C, "You are still bound to serve the cult, follow their orders and help them complete their goals at all costs.") else - to_chat(newstruct, "You are still bound to serve your creator, follow their orders and help them complete their goals at all costs.") - newstruct.cancel_camera() - -/obj/item/soulstone/proc/init_shade(mob/living/T, mob/U, vic = 0) - new /obj/effect/decal/remains/human(T.loc) //Spawns a skeleton - T.invisibility = 101 - var/atom/movable/overlay/animation = new /atom/movable/overlay( T.loc ) - animation.icon_state = "blank" - animation.icon = 'icons/mob/mob.dmi' - animation.master = T - flick("dust-h", animation) - qdel(animation) - var/path = get_shade_type() - var/mob/living/simple_animal/shade/S = new path(src) - S.status_flags |= GODMODE //So they won't die inside the stone somehow - S.canmove = 0//Can't move out of the soul stone - S.name = "Shade of [T.real_name]" - S.real_name = "Shade of [T.real_name]" - S.key = T.key + to_chat(C, "You are still bound to serve your creator, follow their orders and help them complete their goals at all costs.") + C.cancel_camera() + +/obj/item/soulstone/proc/init_shade(mob/living/M, mob/user, forced = FALSE) + var/type = get_shade_type() + var/mob/living/simple_animal/shade/S = new type(src) + S.canmove = FALSE // Can't move out of the soul stone + S.name = "Shade of [M.real_name]" + S.real_name = "Shade of [M.real_name]" + S.key = M.key S.cancel_camera() - name = "soulstone: Shade of [T.real_name]" - icon_state = "soulstone2" - if(U) - S.faction |= "\ref[U]" //Add the master as a faction, allowing inter-mob cooperation - if(iswizard(U)) + name = "soulstone: [S.name]" + icon_state = icon_state_full + + if(user) + S.faction |= "\ref[user]" //Add the master as a faction, allowing inter-mob cooperation + if(iswizard(user)) SSticker.mode.update_wiz_icons_added(S.mind) S.mind.special_role = SPECIAL_ROLE_WIZARD_APPRENTICE - if(iscultist(U)) - SSticker.mode.add_cultist(S.mind, 0) + if(iscultist(user)) + SSticker.mode.add_cultist(S.mind) S.mind.special_role = SPECIAL_ROLE_CULTIST S.mind.store_memory("Serve the cult's will.") - to_chat(S, "Your soul has been captured! You are now bound to the cult's will. Help them succeed in their goals at all costs.") + to_chat(S, "Your soul has been captured! You are now bound to the cult's will. Help them succeed in their goals at all costs.") else - S.mind.store_memory("Serve [U.real_name], your creator.") - to_chat(S, "Your soul has been captured! You are now bound to [U.real_name]'s will. Help them succeed in their goals at all costs.") - if(vic && U) - to_chat(U, "Capture successful!: [T.real_name]'s soul has been ripped from [U.p_their()] body and stored within the soul stone.") - if(isrobot(T))//Robots have to dust or else they spill out an empty robot brain, and unequiping them spills robot components that shouldn't spawn. - T.dust() + S.mind.store_memory("Serve [user.real_name], your creator.") + to_chat(S, "Your soul has been captured! You are now bound to [user.real_name]'s will. Help them succeed in their goals at all costs.") + if(forced && user) + to_chat(user, "Capture successful!: [M.real_name]'s soul has been ripped from [user.p_their()] body and stored within the soul stone.") + if(isrobot(M))//Robots have to dust or else they spill out an empty robot brain, and unequiping them spills robot components that shouldn't spawn. + M.dust() else - for(var/obj/item/W in T) - T.unEquip(W) - qdel(T) + for(var/obj/item/I in M) + M.unEquip(I) + M.dust() /obj/item/soulstone/proc/get_shade_type() + if(purified) + return /mob/living/simple_animal/shade/holy return /mob/living/simple_animal/shade/cult -/obj/item/soulstone/anybody/get_shade_type() - return /mob/living/simple_animal/shade - -/obj/item/soulstone/proc/getCultGhost(mob/living/T, mob/U) +/obj/item/soulstone/proc/get_cult_ghost(mob/living/M, mob/user) var/mob/dead/observer/chosen_ghost - for(var/mob/dead/observer/ghost in GLOB.player_list) //We put them back in their body - if(ghost.mind && ghost.mind.current == T && ghost.client) + for(var/mob/dead/observer/ghost in GLOB.player_list) // We put them back in their body + if(ghost.mind && ghost.mind.current == M && ghost.client) chosen_ghost = ghost break - if(!chosen_ghost) //Failing that, we grab a ghost - var/list/consenting_candidates = SSghost_spawns.poll_candidates("Would you like to play as a Shade?", ROLE_CULTIST, FALSE, poll_time = 10 SECONDS, source = /mob/living/simple_animal/shade) - if(consenting_candidates.len) + if(!chosen_ghost) // Failing that, we grab a ghost + var/list/consenting_candidates + if(purified) + consenting_candidates = SSghost_spawns.poll_candidates("Would you like to play as a Holy Shade?", ROLE_SENTIENT, FALSE, poll_time = 10 SECONDS, source = /mob/living/simple_animal/shade/holy) + else + consenting_candidates = SSghost_spawns.poll_candidates("Would you like to play as a Shade?", ROLE_SENTIENT, FALSE, poll_time = 10 SECONDS, source = /mob/living/simple_animal/shade) + if(length(consenting_candidates)) chosen_ghost = pick(consenting_candidates) - if(!T) - return 0 + if(!M) + return FALSE if(!chosen_ghost) - to_chat(U, "There were no spirits willing to become a shade.") - return 0 - if(contents.len) //If they used the soulstone on someone else in the meantime - return 0 - T.ckey = chosen_ghost.ckey - init_shade(T, U) - return 1 + to_chat(user, "There were no spirits willing to become a shade.") + return FALSE + if(length(contents)) //If they used the soulstone on someone else in the meantime + return FALSE + M.ckey = chosen_ghost.ckey + init_shade(M, user) + return TRUE diff --git a/code/game/machinery/cloning.dm b/code/game/machinery/cloning.dm index c622b12c94cd7..a7b7bb287c97a 100644 --- a/code/game/machinery/cloning.dm +++ b/code/game/machinery/cloning.dm @@ -451,9 +451,12 @@ GLOBAL_LIST_INIT(cloner_biomass_items, list(\ if(H.mind in SSticker.mode.syndicates) SSticker.mode.update_synd_icons_added() if(H.mind in SSticker.mode.cult) - SSticker.mode.add_cultist(occupant.mind) - SSticker.mode.update_cult_icons_added() //So the icon actually appears - SSticker.mode.update_cult_comms_added(H.mind) //So the comms actually appears + SSticker.mode.update_cult_icons_added(H.mind) // Adds the cult antag hud + SSticker.mode.add_cult_actions(H.mind) // And all the actions + if(SSticker.mode.cult_risen) + SSticker.mode.rise(H) + if(SSticker.mode.cult_ascendant) + SSticker.mode.ascend(H) if(H.mind.vampire) H.mind.vampire.update_owner(H) if((H.mind in SSticker.mode.vampire_thralls) || (H.mind in SSticker.mode.vampire_enthralled)) diff --git a/code/game/machinery/cryopod.dm b/code/game/machinery/cryopod.dm index 46cc06b50a3a7..40e462d2212e6 100644 --- a/code/game/machinery/cryopod.dm +++ b/code/game/machinery/cryopod.dm @@ -372,19 +372,10 @@ else I.forceMove(loc) - // Skip past any cult sacrifice objective using this person - if(GAMEMODE_IS_CULT && is_sacrifice_target(occupant.mind)) - var/datum/game_mode/cult/cult_mode = SSticker.mode - var/list/p_s_t = cult_mode.get_possible_sac_targets() - if(p_s_t.len) - cult_mode.sacrifice_target = pick(p_s_t) - for(var/datum/mind/H in SSticker.mode.cult) - if(H.current) - to_chat(H.current, "[SSticker.cultdat.entity_name] murmurs, [occupant] is beyond your reach. Sacrifice [cult_mode.sacrifice_target.current] instead...
") - H.current << 'sound/ambience/alarm4.ogg' - cult_mode.update_sac_objective(occupant.mind, occupant.mind.assigned_role) - else - cult_mode.bypass_phase() + // Find a new sacrifice target if needed, if unable allow summoning + if(is_sacrifice_target(occupant.mind)) + if(!SSticker.mode.cult_objs.find_new_sacrifice_target()) + SSticker.mode.cult_objs.ready_to_summon() //Update any existing objectives involving this mob. for(var/datum/objective/O in GLOB.all_objectives) diff --git a/code/game/machinery/doors/airlock.dm b/code/game/machinery/doors/airlock.dm index 5189ff1095a22..92a7dc7159ca5 100644 --- a/code/game/machinery/doors/airlock.dm +++ b/code/game/machinery/doors/airlock.dm @@ -939,7 +939,7 @@ About the new airlock wires panel: else if(istype(C, /obj/item/pai_cable)) // -- TLE var/obj/item/pai_cable/cable = C cable.plugin(src, user) - else if((istype(C, /obj/item/paper) && !istype(C, /obj/item/paper/talisman)) || istype(C, /obj/item/photo)) + else if(istype(C, /obj/item/paper) || istype(C, /obj/item/photo)) if(note) to_chat(user, "There's already something pinned to this airlock! Use wirecutters or your hands to remove it.") return @@ -1412,7 +1412,7 @@ About the new airlock wires panel: return "photo" //Removes the current note on the door if any. Returns if a note is removed -/obj/machinery/door/airlock/proc/remove_airlock_note(mob/user, wirecutters_used=TRUE) +/obj/machinery/door/airlock/proc/remove_airlock_note(mob/user, wirecutters_used = TRUE) if(note) if(!wirecutters_used) if (ishuman(user) && user.a_intent == INTENT_GRAB)//grab that note @@ -1431,21 +1431,29 @@ About the new airlock wires panel: return TRUE return FALSE -/obj/machinery/door/airlock/narsie_act() +/obj/machinery/door/airlock/narsie_act(weak = FALSE) var/turf/T = get_turf(src) var/runed = prob(20) var/obj/machinery/door/airlock/cult/A - if(glass) - if(runed) - A = new/obj/machinery/door/airlock/cult/glass(T) - else - A = new/obj/machinery/door/airlock/cult/unruned/glass(T) + if(weak) + A = new/obj/machinery/door/airlock/cult/weak(T) else - if(runed) - A = new/obj/machinery/door/airlock/cult(T) + if(glass) + if(runed) + A = new/obj/machinery/door/airlock/cult/glass(T) + else + A = new/obj/machinery/door/airlock/cult/unruned/glass(T) else - A = new/obj/machinery/door/airlock/cult/unruned(T) + if(runed) + A = new/obj/machinery/door/airlock/cult(T) + else + A = new/obj/machinery/door/airlock/cult/unruned(T) A.name = name + A.stealth_icon = icon + A.stealth_overlays = overlays_file + A.stealth_opacity = opacity + A.stealth_glass = glass + A.stealth_airlock_material = airlock_material qdel(src) /obj/machinery/door/airlock/proc/ai_control_callback() diff --git a/code/game/machinery/doors/airlock_types.dm b/code/game/machinery/doors/airlock_types.dm index 1d19450735715..9f99b45afa423 100644 --- a/code/game/machinery/doors/airlock_types.dm +++ b/code/game/machinery/doors/airlock_types.dm @@ -506,8 +506,22 @@ hackProof = TRUE aiControlDisabled = AICONTROLDISABLED_ON paintable = FALSE + /// Spawns an effect when opening var/openingoverlaytype = /obj/effect/temp_visual/cult/door + /// Will the door let anyone through var/friendly = FALSE + /// Is this door currently concealed + var/stealthy = FALSE + /// Door sprite when concealed + var/stealth_icon = 'icons/obj/doors/airlocks/station/maintenance.dmi' + /// Door overlays when concealed (Bolt lights, maintenance panel, etc.) + var/stealth_overlays = 'icons/obj/doors/airlocks/station/overlays.dmi' + /// Is the concealed airlock glass + var/stealth_glass = FALSE + /// Opacity when concealed (For glass doors) + var/stealth_opacity = TRUE + /// Inner airlock material (Glass, plasteel) + var/stealth_airlock_material = null /obj/machinery/door/airlock/cult/Initialize() . = ..() @@ -521,19 +535,42 @@ /obj/machinery/door/airlock/cult/allowed(mob/living/L) if(!density) - return 1 + return TRUE if(friendly || iscultist(L) || isshade(L)|| isconstruct(L)) - new openingoverlaytype(loc) - return 1 + if(!stealthy) + new openingoverlaytype(loc) + return TRUE else - new /obj/effect/temp_visual/cult/sac(loc) - var/atom/throwtarget - throwtarget = get_edge_target_turf(src, get_dir(src, get_step_away(L, src))) - L << pick(sound('sound/hallucinations/turn_around1.ogg',0,1,50), sound('sound/hallucinations/turn_around2.ogg',0,1,50)) - L.Weaken(2) - spawn(0) + if(!stealthy) + new /obj/effect/temp_visual/cult/sac(loc) + var/atom/throwtarget + throwtarget = get_edge_target_turf(src, get_dir(src, get_step_away(L, src))) + SEND_SOUND(L, pick(sound('sound/hallucinations/turn_around1.ogg', 0, 1, 50), sound('sound/hallucinations/turn_around2.ogg', 0, 1, 50))) + L.Weaken(2) L.throw_at(throwtarget, 5, 1,src) - return 0 + return FALSE + +/obj/machinery/door/airlock/cult/cult_conceal() + icon = stealth_icon + overlays_file = stealth_overlays + opacity = stealth_opacity + glass = stealth_glass + airlock_material = stealth_airlock_material + name = "airlock" + desc = "It opens and closes." + stealthy = TRUE + update_icon() + +/obj/machinery/door/airlock/cult/cult_reveal() + icon = SSticker.cultdat?.airlock_runed_icon_file + overlays_file = SSticker.cultdat?.airlock_runed_overlays_file + opacity = initial(opacity) + glass = initial(glass) + airlock_material = initial(airlock_material) + name = initial(name) + desc = initial(desc) + stealthy = initial(stealthy) + update_icon() /obj/machinery/door/airlock/cult/narsie_act() return @@ -578,6 +615,13 @@ /obj/machinery/door/airlock/cult/unruned/glass/friendly friendly = TRUE +/obj/machinery/door/airlock/cult/weak + name = "brittle cult airlock" + desc = "An airlock hastily corrupted by blood magic, it is unusually brittle in this state." + normal_integrity = 150 + damage_deflection = 5 + armor = list("melee" = 0, "bullet" = 0, "laser" = 0,"energy" = 0, "bomb" = 0, "bio" = 0, "rad" = 0, "fire" = 0, "acid" = 0) + ////////////////////////////////// /* Misc Airlocks diff --git a/code/game/machinery/machinery.dm b/code/game/machinery/machinery.dm index 5a8f401ac29b3..b9eb3f5dd7016 100644 --- a/code/game/machinery/machinery.dm +++ b/code/game/machinery/machinery.dm @@ -196,8 +196,9 @@ Class Procs: /obj/machinery/emp_act(severity) if(use_power && !stat) use_power(7500/severity) - new /obj/effect/temp_visual/emp(loc) + . = TRUE ..() + /obj/machinery/default_welder_repair(mob/user, obj/item/I) . = ..() if(.) diff --git a/code/game/machinery/shieldgen.dm b/code/game/machinery/shieldgen.dm index 75ef71724553a..2f119e6efcfcd 100644 --- a/code/game/machinery/shieldgen.dm +++ b/code/game/machinery/shieldgen.dm @@ -7,8 +7,7 @@ opacity = FALSE anchored = 1 resistance_flags = LAVA_PROOF | FIRE_PROOF | UNACIDABLE | ACID_PROOF - var/const/max_health = 200 - var/health = max_health //The shield can only take so much beating (prevents perma-prisons) + max_integrity = 200 /obj/machinery/shield/New() dir = pick(NORTH, SOUTH, EAST, WEST) @@ -37,42 +36,6 @@ /obj/machinery/shield/CanAtmosPass(turf/T) return !density -/obj/machinery/shield/attackby(obj/item/I, mob/user, params) - if(!istype(I)) - return - - //Calculate damage - var/aforce = I.force - if(I.damtype == BRUTE || I.damtype == BURN) - health -= aforce - - //Play a fitting sound - playsound(loc, 'sound/effects/empulse.ogg', 75, 1) - - if(health <= 0) - visible_message("The [src] dissipates") - qdel(src) - return - - opacity = TRUE - spawn(20) - if(src) - opacity = FALSE - - ..() - -/obj/machinery/shield/bullet_act(obj/item/projectile/Proj) - health -= Proj.damage - ..() - if(health <=0) - visible_message("The [src] dissipates") - qdel(src) - return - opacity = TRUE - spawn(20) - if(src) - opacity = FALSE - /obj/machinery/shield/ex_act(severity) switch(severity) if(1.0) @@ -96,33 +59,61 @@ /obj/machinery/shield/blob_act() qdel(src) +/obj/machinery/shield/cult + name = "cult barrier" + desc = "A shield summoned by cultists to keep heretics away." + max_integrity = 100 + icon_state = "shield-cult" -/obj/machinery/shield/hitby(atom/movable/AM, skipcatch, hitpush, blocked, datum/thrownthing/throwingdatum) - ..() - var/tforce = 0 - if(ismob(AM)) - tforce = 40 - else if(isobj(AM)) - var/obj/O = AM - tforce = O.throwforce - - health -= tforce - - //This seemed to be the best sound for hitting a force field. - playsound(loc, 'sound/effects/empulse.ogg', 100, 1) +/obj/machinery/shield/cult/emp_act(severity) + return - //Handle the destruction of the shield - if(health <= 0) - visible_message("The [src] dissipates") - qdel(src) - return +/obj/machinery/shield/cult/narsie + name = "sanguine barrier" + desc = "A potent shield summoned by cultists to defend their rites." + max_integrity = 60 + +/obj/machinery/shield/cult/weak + name = "Invoker's Shield" + desc = "A weak shield summoned by cultists to protect them while they carry out delicate rituals." + max_integrity = 20 + mouse_opacity = MOUSE_OPACITY_TRANSPARENT + layer = ABOVE_MOB_LAYER + +/obj/machinery/shield/cult/barrier + density = FALSE //toggled on right away by the parent rune + /// The rune that created the shield itself. Used to delete the rune when the shield is destroyed. + var/obj/effect/rune/parent_rune + +/obj/machinery/shield/cult/barrier/attack_hand(mob/living/user) + parent_rune.attack_hand(user) + +/obj/machinery/shield/cult/barrier/attack_animal(mob/living/simple_animal/user) + if(iscultist(user)) + parent_rune.attack_animal(user) + else + ..() - //The shield becomes dense to absorb the blow.. purely asthetic. - opacity = TRUE - spawn(20) - if(src) - opacity = FALSE +/obj/machinery/shield/cult/barrier/Destroy() + if(parent_rune && !QDELETED(parent_rune)) + QDEL_NULL(parent_rune) + return ..() +/** +* Turns the shield on and off. +* +* The shield has 2 states: on and off. When on, it will block movement, projectiles, items, etc. and be clearly visible, and block atmospheric gases. +* When off, the rune no longer blocks anything and turns invisible. +* The barrier itself is not intended to interact with the conceal runes cult spell for balance purposes. +*/ +/obj/machinery/shield/cult/barrier/proc/Toggle() + if(!density) // Currently invisible + density = TRUE // Turn visible + invisibility = initial(invisibility) + else // Currently visible + density = FALSE // Turn invisible + invisibility = INVISIBILITY_MAXIMUM + air_update_turf(1) /obj/machinery/shieldgen name = "Emergency shield projector" diff --git a/code/game/objects/effects/decals/Cleanable/humans.dm b/code/game/objects/effects/decals/Cleanable/humans.dm index b7eea1b367fa3..bf24498e4b249 100644 --- a/code/game/objects/effects/decals/Cleanable/humans.dm +++ b/code/game/objects/effects/decals/Cleanable/humans.dm @@ -37,26 +37,12 @@ GLOBAL_LIST_EMPTY(splatter_cache) /obj/effect/decal/cleanable/blood/Initialize(mapload) . = ..() update_icon() - if(GAMEMODE_IS_CULT) - var/datum/game_mode/cult/mode_ticker = SSticker.mode - var/turf/T = get_turf(src) - if(T && (is_station_level(T.z)))//F I V E T I L E S - if(!(T in mode_ticker.bloody_floors)) - mode_ticker.bloody_floors += T - mode_ticker.bloody_floors[T] = T - mode_ticker.blood_check() if(type == /obj/effect/decal/cleanable/blood/gibs) return if(!.) dry_timer = addtimer(CALLBACK(src, .proc/dry), DRYING_TIME * (amount+1), TIMER_STOPPABLE) /obj/effect/decal/cleanable/blood/Destroy() - if(GAMEMODE_IS_CULT) - var/datum/game_mode/cult/mode_ticker = SSticker.mode - var/turf/T = get_turf(src) - if(T && (is_station_level(T.z))) - mode_ticker.bloody_floors -= T - mode_ticker.blood_check() if(dry_timer) deltimer(dry_timer) return ..() diff --git a/code/game/objects/effects/decals/Cleanable/tracks.dm b/code/game/objects/effects/decals/Cleanable/tracks.dm index 832a66a93bad2..2c6b2678e6392 100644 --- a/code/game/objects/effects/decals/Cleanable/tracks.dm +++ b/code/game/objects/effects/decals/Cleanable/tracks.dm @@ -136,3 +136,8 @@ GLOBAL_LIST_EMPTY(fluidtrack_cache) if(blood_state != C.blood_state) //We only replace footprints of the same type as us return ..() + +/obj/effect/decal/cleanable/blood/footprints/can_bloodcrawl_in() + if(basecolor == COLOR_BLOOD_MACHINE) + return FALSE + return TRUE diff --git a/code/game/objects/effects/decals/crayon.dm b/code/game/objects/effects/decals/crayon.dm index e8666d26d64ae..e336ace5fb54b 100644 --- a/code/game/objects/effects/decals/crayon.dm +++ b/code/game/objects/effects/decals/crayon.dm @@ -9,7 +9,7 @@ mergeable_decal = FALSE // Allows crayon drawings to overlap one another. -/obj/effect/decal/cleanable/crayon/Initialize(mapload, main = "#FFFFFF", var/type = "rune1", var/e_name = "rune") +/obj/effect/decal/cleanable/crayon/Initialize(mapload, main = "#FFFFFF", type = "rune1", e_name = "rune") . = ..() name = e_name diff --git a/code/game/objects/effects/temporary_visuals/cult.dm b/code/game/objects/effects/temporary_visuals/cult.dm index 775b4eafceb7f..a1b2d4320a973 100644 --- a/code/game/objects/effects/temporary_visuals/cult.dm +++ b/code/game/objects/effects/temporary_visuals/cult.dm @@ -11,7 +11,7 @@ /obj/effect/temp_visual/dir_setting/cult/phase name = "phase glow" - duration = 7 + duration = 12 icon = 'icons/effects/cult_effects.dmi' icon_state = "cultin" @@ -19,7 +19,7 @@ icon_state = "cultout" /obj/effect/temp_visual/cult/sac - name = "maw of Nar-Sie" + name = "maw of Nar'Sie" icon_state = "sacconsume" /obj/effect/temp_visual/cult/door @@ -39,3 +39,113 @@ icon_state = "floorglow" duration = 5 plane = FLOOR_PLANE + +/obj/effect/temp_visual/cult/portal + icon_state = "space" + duration = 600 + layer = ABOVE_OBJ_LAYER + +/obj/effect/temp_visual/emp/cult + name = "cult emp sparks" + icon_state = "empdisable_cult" + +/obj/effect/temp_visual/emp/pulse/cult + name = "cult emp pulse" + icon_state = "emppulse_cult" + +//visuals for runes being magically created +/obj/effect/temp_visual/cult/rune_spawn + icon_state = "runeouter" + alpha = 0 + var/turnedness = 179 //179 turns counterclockwise, 181 turns clockwise + +/obj/effect/temp_visual/cult/rune_spawn/Initialize(mapload, set_duration, set_color) + if(isnum(set_duration)) + duration = set_duration + if(set_color) + add_atom_colour(set_color, FIXED_COLOUR_PRIORITY) + . = ..() + var/oldtransform = transform + transform = matrix() * 2 + var/matrix/M = transform + M.Turn(turnedness) + transform = M + animate(src, alpha = 255, time = duration, easing = BOUNCE_EASING, flags = ANIMATION_PARALLEL) + animate(src, transform = oldtransform, time = duration, flags = ANIMATION_PARALLEL) + +/obj/effect/temp_visual/cult/rune_spawn/rune1 + icon_state = "rune1words" + turnedness = 181 + +/obj/effect/temp_visual/cult/rune_spawn/rune1/inner + icon_state = "rune1inner" + turnedness = 179 + +/obj/effect/temp_visual/cult/rune_spawn/rune1/center + icon_state = "rune1center" + +/obj/effect/temp_visual/cult/rune_spawn/rune2 + icon_state = "rune2words" + turnedness = 181 + +/obj/effect/temp_visual/cult/rune_spawn/rune2/inner + icon_state = "rune2inner" + turnedness = 179 + +/obj/effect/temp_visual/cult/rune_spawn/rune2/center + icon_state = "rune2center" + +/obj/effect/temp_visual/cult/rune_spawn/rune3 + icon_state = "rune3words" + turnedness = 181 + +/obj/effect/temp_visual/cult/rune_spawn/rune3/inner + icon_state = "rune3inner" + turnedness = 179 + +/obj/effect/temp_visual/cult/rune_spawn/rune3/center + icon_state = "rune3center" + +/obj/effect/temp_visual/cult/rune_spawn/rune4 + icon_state = "rune4words" + turnedness = 181 + +/obj/effect/temp_visual/cult/rune_spawn/rune4/inner + icon_state = "rune4inner" + turnedness = 179 + +/obj/effect/temp_visual/cult/rune_spawn/rune4/center + icon_state = "rune4center" + +/obj/effect/temp_visual/cult/rune_spawn/rune5 + icon_state = "rune5words" + turnedness = 181 + +/obj/effect/temp_visual/cult/rune_spawn/rune5/inner + icon_state = "rune5inner" + turnedness = 179 + +/obj/effect/temp_visual/cult/rune_spawn/rune5/center + icon_state = "rune5center" + +/obj/effect/temp_visual/cult/rune_spawn/rune6 + icon_state = "rune6words" + turnedness = 181 + +/obj/effect/temp_visual/cult/rune_spawn/rune6/inner + icon_state = "rune6inner" + turnedness = 179 + +/obj/effect/temp_visual/cult/rune_spawn/rune6/center + icon_state = "rune6center" + +/obj/effect/temp_visual/cult/rune_spawn/rune7 + icon_state = "rune7words" + turnedness = 181 + +/obj/effect/temp_visual/cult/rune_spawn/rune7/inner + icon_state = "rune7inner" + turnedness = 179 + +/obj/effect/temp_visual/cult/rune_spawn/rune7/center + icon_state = "rune7center" diff --git a/code/game/objects/empulse.dm b/code/game/objects/empulse.dm index 3e7f476e34678..30c0063ea7849 100644 --- a/code/game/objects/empulse.dm +++ b/code/game/objects/empulse.dm @@ -1,4 +1,4 @@ -/proc/empulse(turf/epicenter, heavy_range, light_range, log=0, cause = null) +/proc/empulse(turf/epicenter, heavy_range, light_range, log = FALSE, cause = null) if(!epicenter) return if(!istype(epicenter, /turf)) @@ -9,7 +9,10 @@ log_game("EMP with size ([heavy_range], [light_range]) in area [epicenter.loc.name] [cause ? "(Cause: [cause])" : ""] [COORD(epicenter)]") if(heavy_range > 1) - new/obj/effect/temp_visual/emp/pulse(epicenter) + if(cause == "cult") + new /obj/effect/temp_visual/emp/pulse/cult(epicenter) + else + new /obj/effect/temp_visual/emp/pulse(epicenter) if(heavy_range > light_range) light_range = heavy_range @@ -17,16 +20,28 @@ for(var/mob/M in range(heavy_range, epicenter)) M << 'sound/effects/empulse.ogg' for(var/atom/T in range(light_range, epicenter)) + if(cause == "cult" && iscultist(T)) + continue var/distance = get_dist(epicenter, T) + var/will_affect = FALSE + if(distance < 0) distance = 0 if(distance < heavy_range) - T.emp_act(1) + will_affect = T.emp_act(1) + else if(distance == heavy_range) if(prob(50)) - T.emp_act(1) + will_affect = T.emp_act(1) else - T.emp_act(2) + will_affect = T.emp_act(2) + else if(distance <= light_range) - T.emp_act(2) - return 1 + will_affect = T.emp_act(2) + + if(will_affect) + if(cause == "cult") + new /obj/effect/temp_visual/emp/cult(T.loc) + else + new /obj/effect/temp_visual/emp(T.loc) + return TRUE diff --git a/code/game/objects/items/crayons.dm b/code/game/objects/items/crayons.dm index ce52e260bca91..4ae316236dfb7 100644 --- a/code/game/objects/items/crayons.dm +++ b/code/game/objects/items/crayons.dm @@ -29,7 +29,7 @@ /obj/item/toy/crayon/New() ..() name = "[colourName] crayon" //Makes crayons identifiable in things like grinders - drawtype = pick(pick(graffiti), pick(letters), "rune[rand(1,10)]") + drawtype = pick(pick(graffiti), pick(letters), "rune[rand(1, 8)]") /obj/item/toy/crayon/attack_self(mob/living/user as mob) update_window(user) @@ -40,8 +40,8 @@ dat += "
" dat += "

Runes:


" dat += "Random rune" - for(var/i = 1; i <= 10; i++) - dat += "Rune[i]" + for(var/i = 1; i <= 8; i++) + dat += "Rune [i]" if(!((i + 1) % 3)) //3 buttons in a row dat += "
" dat += "
" diff --git a/code/game/objects/items/stacks/sheets/sheet_types.dm b/code/game/objects/items/stacks/sheets/sheet_types.dm index 6b70ac015ca2a..d6e21d6d482c2 100644 --- a/code/game/objects/items/stacks/sheets/sheet_types.dm +++ b/code/game/objects/items/stacks/sheets/sheet_types.dm @@ -344,12 +344,12 @@ GLOBAL_LIST_INIT(cardboard_recipes, list ( */ GLOBAL_LIST_INIT(cult_recipes, list ( \ - new/datum/stack_recipe/cult("runed door", /obj/machinery/door/airlock/cult, 1, time = 50, one_per_turf = 1, on_floor = 1), - new/datum/stack_recipe/cult("runed girder", /obj/structure/girder/cult, 1, time = 50, one_per_turf = 1, on_floor = 1), \ - new/datum/stack_recipe/cult("pylon", /obj/structure/cult/functional/pylon, 3, time = 40, one_per_turf = 1, on_floor = 1), \ - new/datum/stack_recipe/cult("forge", /obj/structure/cult/functional/forge, 5, time = 40, one_per_turf = 1, on_floor = 1), \ - new/datum/stack_recipe/cult("archives", /obj/structure/cult/functional/archives, 2, time = 40, one_per_turf = 1, on_floor = 1), \ - new/datum/stack_recipe/cult("altar", /obj/structure/cult/functional/altar, 5, time = 40, one_per_turf = 1, on_floor = 1), \ + new /datum/stack_recipe/cult("runed door (stuns non-cultists)", /obj/machinery/door/airlock/cult, 1, time = 50, one_per_turf = TRUE, on_floor = TRUE, no_cult_structure = TRUE), + new /datum/stack_recipe/cult("runed girder (used to make cult walls)", /obj/structure/girder/cult, 1, time = 10, one_per_turf = TRUE, on_floor = TRUE, no_cult_structure = TRUE), \ + new /datum/stack_recipe/cult("pylon (heals nearby cultists)", /obj/structure/cult/functional/pylon, 4, time = 40, one_per_turf = TRUE, on_floor = TRUE, no_cult_structure = TRUE), \ + new /datum/stack_recipe/cult("forge (crafts shielded robes, flagellant's robes, and mirror shields)", /obj/structure/cult/functional/forge, 3, time = 40, one_per_turf = TRUE, on_floor = TRUE, no_cult_structure = TRUE), \ + new /datum/stack_recipe/cult("archives (crafts zealot's blindfolds, shuttle curse orbs, and veil shifters)", /obj/structure/cult/functional/archives, 3, time = 40, one_per_turf = TRUE, on_floor = TRUE, no_cult_structure = TRUE), \ + new /datum/stack_recipe/cult("altar (crafts eldritch whetstones, construct shells, and flasks of unholy water)", /obj/structure/cult/functional/altar, 3, time = 40, one_per_turf = TRUE, on_floor = TRUE, no_cult_structure = TRUE), )) /obj/item/stack/sheet/runed_metal @@ -360,6 +360,7 @@ GLOBAL_LIST_INIT(cult_recipes, list ( \ item_state = "sheet-metal" sheettype = "runed" merge_type = /obj/item/stack/sheet/runed_metal + recipe_width = 700 /obj/item/stack/sheet/runed_metal/New() . = ..() @@ -380,8 +381,8 @@ GLOBAL_LIST_INIT(cult_recipes, list ( \ return ..() /datum/stack_recipe/cult - one_per_turf = 1 - on_floor = 1 + one_per_turf = TRUE + on_floor = TRUE /datum/stack_recipe/cult/post_build(obj/item/stack/S, obj/result) if(ishuman(S.loc)) @@ -389,6 +390,9 @@ GLOBAL_LIST_INIT(cult_recipes, list ( \ H.bleed(5) ..() +/obj/item/stack/sheet/runed_metal/ten + amount = 10 + /obj/item/stack/sheet/runed_metal/fifty amount = 50 diff --git a/code/game/objects/items/stacks/stack.dm b/code/game/objects/items/stacks/stack.dm index ab592cc040233..b57c3435132ff 100644 --- a/code/game/objects/items/stacks/stack.dm +++ b/code/game/objects/items/stacks/stack.dm @@ -16,6 +16,8 @@ var/to_transfer = 0 var/max_amount = 50 //also see stack recipes initialisation, param "max_res_amount" must be equal to this max_amount var/merge_type = null // This path and its children should merge with this stack, defaults to src.type + var/recipe_width = 400 //Width of the recipe popup + var/recipe_height = 400 //Height of the recipe popup /obj/item/stack/New(loc, new_amount, merge = TRUE) ..() @@ -138,7 +140,7 @@ if(!(max_multiplier in multipliers)) t1 += " [max_multiplier * R.res_amount]x" - var/datum/browser/popup = new(user, "stack", name, 400, 400) + var/datum/browser/popup = new(user, "stack", name, recipe_width, recipe_height) popup.set_content(t1) popup.open(0) onclose(user, "stack") @@ -170,7 +172,7 @@ to_chat(usr, "You haven't got enough [src] to build \the [R.req_amount * multiplier] [R.title]\s!") else to_chat(usr, "You haven't got enough [src] to build \the [R.title]!") - return 0 + return FALSE if(R.window_checks && !valid_window_location(usr.loc, usr.dir)) to_chat(usr, "The [R.title] won't fit here!") @@ -178,11 +180,15 @@ if(R.one_per_turf && (locate(R.result_type) in usr.drop_location())) to_chat(usr, "There is another [R.title] here!") - return 0 + return FALSE if(R.on_floor && !istype(usr.drop_location(), /turf/simulated)) to_chat(usr, "\The [R.title] must be constructed on the floor!") - return 0 + return FALSE + + if(R.no_cult_structure && (locate(/obj/structure/cult) in usr.drop_location())) + to_chat(usr, "There is a structure here!") + return FALSE if(R.time) to_chat(usr, "Building [R.title] ...") diff --git a/code/game/objects/items/stacks/stack_recipe.dm b/code/game/objects/items/stacks/stack_recipe.dm index 5197029728191..20d360059af46 100644 --- a/code/game/objects/items/stacks/stack_recipe.dm +++ b/code/game/objects/items/stacks/stack_recipe.dm @@ -12,8 +12,9 @@ var/one_per_turf = 0 var/on_floor = 0 var/window_checks = FALSE + var/no_cult_structure = FALSE -/datum/stack_recipe/New(title, result_type, req_amount = 1, res_amount = 1, max_res_amount = 1, time = 0, one_per_turf = 0, on_floor = 0, window_checks = FALSE) +/datum/stack_recipe/New(title, result_type, req_amount = 1, res_amount = 1, max_res_amount = 1, time = 0, one_per_turf = 0, on_floor = 0, window_checks = FALSE, no_cult_structure = FALSE) src.title = title src.result_type = result_type src.req_amount = req_amount @@ -23,6 +24,7 @@ src.one_per_turf = one_per_turf src.on_floor = on_floor src.window_checks = window_checks + src.no_cult_structure = no_cult_structure /datum/stack_recipe/proc/post_build(obj/item/stack/S, obj/result) return diff --git a/code/game/objects/items/weapons/holy_weapons.dm b/code/game/objects/items/weapons/holy_weapons.dm index 974bd3b677fbc..39f2c71f77e55 100644 --- a/code/game/objects/items/weapons/holy_weapons.dm +++ b/code/game/objects/items/weapons/holy_weapons.dm @@ -1,6 +1,6 @@ /obj/item/nullrod name = "null rod" - desc = "A rod of pure obsidian, its very presence disrupts and dampens the powers of Nar-Sie's followers." + desc = "A rod of pure obsidian, its very presence disrupts and dampens the powers of Nar'Sie's followers." icon_state = "nullrod" item_state = "nullrod" force = 15 diff --git a/code/game/objects/items/weapons/soap.dm b/code/game/objects/items/weapons/soap.dm index cb220ff9565a0..fbe7e9b370ed3 100644 --- a/code/game/objects/items/weapons/soap.dm +++ b/code/game/objects/items/weapons/soap.dm @@ -28,7 +28,7 @@ to_chat(user, "You take a bite of the [name]. Delicious!") playsound(user.loc, 'sound/items/eatfood.ogg', 50, 0) user.adjust_nutrition(2) - else if(istype(target,/obj/effect/decal/cleanable)) + else if(istype(target, /obj/effect/decal/cleanable) || istype(target, /obj/effect/rune)) user.visible_message("[user] begins to scrub \the [target.name] out with [src].") if(do_after(user, cleanspeed, target = target) && target) to_chat(user, "You scrub \the [target.name] out.") diff --git a/code/game/objects/items/weapons/storage/bible.dm b/code/game/objects/items/weapons/storage/bible.dm index c4afed4b266f7..d6bd46df8e2de 100644 --- a/code/game/objects/items/weapons/storage/bible.dm +++ b/code/game/objects/items/weapons/storage/bible.dm @@ -93,10 +93,14 @@ return if(istype(A, /turf/simulated/floor)) to_chat(user, "You hit the floor with the bible.") - if(user.mind && (user.mind.isholy)) - for(var/obj/effect/rune/R in A) - if(R.invisibility) - R.talismanreveal() + if(user.mind && user.mind.isholy) + for(var/obj/O in A) + O.cult_reveal() + if(istype(A, /obj/machinery/door/airlock)) + to_chat(user, "You hit the airlock with the bible.") + if(user.mind && user.mind.isholy) + var/obj/airlock = A + airlock.cult_reveal() if(user.mind && (user.mind.isholy)) if(A.reagents && A.reagents.has_reagent("water")) //blesses all the water in the holder to_chat(user, "You bless [A].") diff --git a/code/game/objects/objs.dm b/code/game/objects/objs.dm index aa00235828db5..1a4f0691e2113 100644 --- a/code/game/objects/objs.dm +++ b/code/game/objects/objs.dm @@ -353,6 +353,12 @@ a { /obj/proc/check_uplink_validity() return TRUE +/obj/proc/cult_conceal() //Called by cult conceal spell + return + +/obj/proc/cult_reveal() //Called by cult reveal spell and chaplain's bible + return + /obj/proc/force_eject_occupant(mob/target) // This proc handles safely removing occupant mobs from the object if they must be teleported out (due to being SSD/AFK, by admin teleport, etc) or transformed. // In the event that the object doesn't have an overriden version of this proc to do it, log a runtime so one can be added. diff --git a/code/game/objects/structures/crates_lockers/closets/secure/chaplain.dm b/code/game/objects/structures/crates_lockers/closets/secure/chaplain.dm index aec562ac843df..1edebe36cf0c0 100644 --- a/code/game/objects/structures/crates_lockers/closets/secure/chaplain.dm +++ b/code/game/objects/structures/crates_lockers/closets/secure/chaplain.dm @@ -23,7 +23,7 @@ new /obj/item/storage/backpack/cultpack(src) new /obj/item/clothing/head/helmet/riot/knight/templar(src) new /obj/item/clothing/suit/armor/riot/knight/templar(src) - new /obj/item/soulstone/anybody/chaplain(src) + new /obj/item/soulstone/anybody/purified/chaplain(src) new /obj/item/storage/fancy/candle_box/eternal(src) new /obj/item/storage/fancy/candle_box/eternal(src) new /obj/item/storage/fancy/candle_box/eternal(src) diff --git a/code/game/objects/structures/girders.dm b/code/game/objects/structures/girders.dm index 6d27b21e68671..d4b3ea08115b9 100644 --- a/code/game/objects/structures/girders.dm +++ b/code/game/objects/structures/girders.dm @@ -70,7 +70,7 @@ to_chat(user, "There is already a false wall present!") return if(istype(W, /obj/item/stack/sheet/runed_metal)) - to_chat(user, "You can't seem to make the metal bend..") + to_chat(user, "You can't seem to make the metal bend.") return if(istype(W,/obj/item/stack/rods)) @@ -336,8 +336,7 @@ return state = GIRDER_DISASSEMBLED TOOL_DISMANTLE_SUCCESS_MESSAGE - var/obj/item/stack/sheet/metal/M = new(loc, 2) - M.add_fingerprint(user) + refundMetal(metalUsed) qdel(src) else if(!isfloorturf(loc)) @@ -417,13 +416,9 @@ . = ..() icon_state = SSticker.cultdat?.cult_girder_icon_state -/obj/structure/girder/cult/refundMetal(metalAmount) - for(var/i=0;i < metalAmount;i++) - new /obj/item/stack/sheet/runed_metal(get_turf(src)) - /obj/structure/girder/cult/attackby(obj/item/W, mob/user, params) add_fingerprint(user) - if(istype(W, /obj/item/tome) && iscultist(user)) //Cultists can demolish cult girders instantly with their tomes + if(istype(W, /obj/item/melee/cultblade/dagger) && iscultist(user)) //Cultists can demolish cult girders instantly with their dagger user.visible_message("[user] strikes [src] with [W]!", "You demolish [src].") refundMetal(metalUsed) qdel(src) @@ -451,7 +446,7 @@ to_chat(user, "You need at least one sheet of runed metal to construct a runed wall!") return 0 user.visible_message("[user] begins laying runed metal on [src]...", "You begin constructing a runed wall...") - if(do_after(user, 50, target = src)) + if(do_after(user, 10, target = src)) if(R.get_amount() < 1 || !R) return user.visible_message("[user] plates [src] with runed metal.", "You construct a runed wall.") diff --git a/code/modules/admin/player_panel.dm b/code/modules/admin/player_panel.dm index 700e838169558..2986035e15acb 100644 --- a/code/modules/admin/player_panel.dm +++ b/code/modules/admin/player_panel.dm @@ -442,12 +442,33 @@ dat += check_role_table("Ninjas", ticker.mode.ninjas)*/ if(SSticker.mode.cult.len) + var/datum/game_mode/gamemode = SSticker.mode + var/datum/objective/current_sac_obj = gamemode.cult_objs.current_sac_objective() dat += check_role_table("Cultists", SSticker.mode.cult) - dat += "
use Cult Mindspeak" - if(GAMEMODE_IS_CULT) - var/datum/game_mode/cult/cult_round = SSticker.mode - if(!cult_round.narsie_condition_cleared) - dat += "
complete objective (debug)" + if(current_sac_obj) + dat += "
Current cult objective:
[current_sac_obj.explanation_text]" + else if(gamemode.cult_objs.cult_status == NARSIE_NEEDS_SUMMONING) + dat += "
Current cult objective: Summon [SSticker.cultdat ? SSticker.cultdat.entity_name : "Nar'Sie"]" + else if(gamemode.cult_objs.cult_status == NARSIE_HAS_RISEN) + dat += "
Current cult objective: Feed [SSticker.cultdat ? SSticker.cultdat.entity_name : "Nar'Sie"]" + else if(gamemode.cult_objs.cult_status == NARSIE_HAS_FALLEN) + dat += "
Current cult objective: Kill all non-cultists" + else + dat += "
Current cult objective: None! (This is most likely a bug, or var editing gone wrong.)" + dat += "
Sacrifice objectives completed: [gamemode.cult_objs.sacrifices_done]" + dat += "
Sacrifice objectives needed for summoning: [gamemode.cult_objs.sacrifices_required]" + dat += "
Summoning locations: [english_list(gamemode.cult_objs.obj_summon.summon_spots)]" + dat += "
Cult Mindspeak" + + if(gamemode.cult_objs.cult_status == NARSIE_DEMANDS_SACRIFICE) + dat += "
Modify amount of sacrifices required" + dat += "
Reroll sacrifice target" + else + dat += "
Modify amount of sacrifices required (Summon available!)" + dat += "
Reroll sacrifice target (Summon available!)" + + dat += "
Reroll summoning locations" + dat += "
Unlock Nar'Sie summoning" if(SSticker.mode.traitors.len) dat += check_role_table("Traitors", SSticker.mode.traitors) diff --git a/code/modules/admin/topic.dm b/code/modules/admin/topic.dm index 622667a9ac78d..c1b77572a17bb 100644 --- a/code/modules/admin/topic.dm +++ b/code/modules/admin/topic.dm @@ -1676,40 +1676,65 @@ return SStickets.autoRespond(index) - if(href_list["convert_ticket"]) - var/indexNum = text2num(href_list["convert_ticket"]) - if(href_list["is_mhelp"]) - SSmentor_tickets.convert_to_other_ticket(indexNum) - else - SStickets.convert_to_other_ticket(indexNum) - else if(href_list["cult_nextobj"]) - if(alert(usr, "Validate the current Cult objective and unlock the next one?", "Cult Cheat Code", "Yes", "No") != "Yes") - return - - if(!GAMEMODE_IS_CULT) - alert("Couldn't locate cult mode datum! This shouldn't ever happen, tell a coder!") - return - - var/datum/game_mode/cult/cult_round = SSticker.mode - cult_round.bypass_phase() - message_admins("Admin [key_name_admin(usr)] has unlocked the Cult's next objective.") - log_admin("Admin [key_name_admin(usr)] has unlocked the Cult's next objective.") - else if(href_list["cult_mindspeak"]) - var/input = stripped_input(usr, "Communicate to all the cultists with the voice of [SSticker.cultdat.entity_name]", "Voice of [SSticker.cultdat.entity_name]", "") + var/input = stripped_input(usr, "Communicate to all the cultists with the voice of [SSticker.cultdat.entity_name]", "Voice of [SSticker.cultdat.entity_name]") if(!input) return for(var/datum/mind/H in SSticker.mode.cult) - if (H.current) - to_chat(H.current, "[SSticker.cultdat.entity_name] murmurs, [input]") + if(H.current) + to_chat(H.current, "[SSticker.cultdat.entity_name] murmurs, \"[input]\"") for(var/mob/dead/observer/O in GLOB.player_list) - to_chat(O, "[SSticker.cultdat.entity_name] murmurs, [input]") + to_chat(O, "[SSticker.cultdat.entity_name] murmurs, \"[input]\"") message_admins("Admin [key_name_admin(usr)] has talked with the Voice of [SSticker.cultdat.entity_name].") log_admin("[key_name(usr)] Voice of [SSticker.cultdat.entity_name]: [input]") + else if(href_list["cult_adjustsacnumber"]) + var/amount = input("Adjust the amount of sacrifices required before summoning Nar'Sie", "Sacrifice Adjustment", 2) as null | num + if(amount > 0) + var/datum/game_mode/gamemode = SSticker.mode + var/old = gamemode.cult_objs.sacrifices_required + gamemode.cult_objs.sacrifices_required = amount + message_admins("Admin [key_name_admin(usr)] has modified the amount of cult sacrifices required before summoning from [old] to [amount]") + log_admin("Admin [key_name_admin(usr)] has modified the amount of cult sacrifices required before summoning from [old] to [amount]") + + else if(href_list["cult_newtarget"]) + if(alert(usr, "Reroll the cult's sacrifice target?", "Cult Debug", "Yes", "No") != "Yes") + return + + var/datum/game_mode/gamemode = SSticker.mode + if(!gamemode.cult_objs.find_new_sacrifice_target()) + gamemode.cult_objs.ready_to_summon() + + message_admins("Admin [key_name_admin(usr)] has rerolled the Cult's sacrifice target.") + log_admin("Admin [key_name_admin(usr)] has rerolled the Cult's sacrifice target.") + + else if(href_list["cult_newsummonlocations"]) + if(alert(usr, "Reroll the cult's summoning locations?", "Cult Debug", "Yes", "No") != "Yes") + return + + var/datum/game_mode/gamemode = SSticker.mode + gamemode.cult_objs.obj_summon.find_summon_locations(TRUE) + if(gamemode.cult_objs.cult_status == NARSIE_NEEDS_SUMMONING) //Only update cultists if they are already have the summon goal since they arent aware of summon spots till then + for(var/datum/mind/cult_mind in gamemode.cult) + if(cult_mind && cult_mind.current) + to_chat(cult_mind.current, "The veil has shifted! Our summoning will need to take place elsewhere.") + to_chat(cult_mind.current, "Current goal : [gamemode.cult_objs.obj_summon.explanation_text]") + + message_admins("Admin [key_name_admin(usr)] has rerolled the Cult's sacrifice target.") + log_admin("Admin [key_name_admin(usr)] has rerolled the Cult's sacrifice target.") + + else if(href_list["cult_unlocknarsie"]) + if(alert(usr, "Unlock the ability to summon Nar'Sie?", "Cult Debug", "Yes", "No") != "Yes") + return + + var/datum/game_mode/gamemode = SSticker.mode + gamemode.cult_objs.ready_to_summon() + message_admins("Admin [key_name_admin(usr)] has unlocked the Cult's ability to summon Nar'Sie.") + log_admin("Admin [key_name_admin(usr)] has unlocked the Cult's ability to summon Nar'Sie.") + else if(href_list["adminplayerobservecoodjump"]) if(!check_rights(R_ADMIN)) return diff --git a/code/modules/admin/verbs/one_click_antag.dm b/code/modules/admin/verbs/one_click_antag.dm index 779687b25011c..4edc94bbf5aaa 100644 --- a/code/modules/admin/verbs/one_click_antag.dm +++ b/code/modules/admin/verbs/one_click_antag.dm @@ -178,12 +178,6 @@ H = pick(candidates) SSticker.mode.add_cultist(H.mind) candidates.Remove(H) - if(!GLOB.summon_spots.len) - while(GLOB.summon_spots.len < SUMMON_POSSIBILITIES) - var/area/summon = pick(return_sorted_areas() - GLOB.summon_spots) - if(summon && is_station_level(summon.z) && summon.valid_territory) - GLOB.summon_spots += summon - return 1 return 0 diff --git a/code/modules/client/preference/preferences.dm b/code/modules/client/preference/preferences.dm index e216ad6f5d1c6..0bf474aca01b6 100644 --- a/code/modules/client/preference/preferences.dm +++ b/code/modules/client/preference/preferences.dm @@ -2237,6 +2237,7 @@ GLOBAL_LIST_INIT(special_role_times, list( //minimum age (in days) for accounts character.change_gender(MALE) character.change_eye_color(e_colour) + character.original_eye_color = e_colour if(disabilities & DISABILITY_FLAG_FAT) character.dna.SetSEState(GLOB.fatblock, TRUE, TRUE) diff --git a/code/modules/clothing/shoes/miscellaneous.dm b/code/modules/clothing/shoes/miscellaneous.dm index 62f740ad7b03e..240d6181b6647 100644 --- a/code/modules/clothing/shoes/miscellaneous.dm +++ b/code/modules/clothing/shoes/miscellaneous.dm @@ -147,7 +147,7 @@ /obj/item/clothing/shoes/cult name = "boots" - desc = "A pair of boots worn by the followers of Nar-Sie." + desc = "A pair of boots usually worn by cultists." icon_state = "cult" item_state = "cult" item_color = "cult" @@ -156,6 +156,7 @@ min_cold_protection_temperature = SHOES_MIN_TEMP_PROTECT heat_protection = FEET max_heat_protection_temperature = SHOES_MAX_TEMP_PROTECT + magical = TRUE /obj/item/clothing/shoes/cyborg name = "cyborg boots" diff --git a/code/modules/clothing/under/accessories/accessory.dm b/code/modules/clothing/under/accessories/accessory.dm index 76bcd87921dce..bd485a732ceb3 100644 --- a/code/modules/clothing/under/accessories/accessory.dm +++ b/code/modules/clothing/under/accessories/accessory.dm @@ -507,7 +507,7 @@ to_chat(user, "You have to open it first.") return - if(istype(O,/obj/item/paper) || istype(O, /obj/item/photo) && !(istype(O, /obj/item/paper/talisman))) + if(istype(O,/obj/item/paper) || istype(O, /obj/item/photo)) if(held) to_chat(usr, "[src] already has something inside it.") else diff --git a/code/modules/library/computers/checkout.dm b/code/modules/library/computers/checkout.dm index 934234f645ee3..1735f6aa1eb91 100644 --- a/code/modules/library/computers/checkout.dm +++ b/code/modules/library/computers/checkout.dm @@ -47,8 +47,8 @@ dat += "" if(src.arcanecheckout) - new /obj/item/tome(src.loc) - to_chat(user, "Your sanity barely endures the seconds spent in the vault's browsing window. The only thing to remind you of this when you stop browsing is a dusty old tome sitting on the desk. You don't really remember printing it.") + new /obj/item/melee/cultblade/dagger(src.loc) + to_chat(user, "Your sanity barely endures the seconds spent in the vault's browsing window. The only thing to remind you of this when you stop browsing is a strange looking dagger sitting on the desk. You don't really remember where it came from.") user.visible_message("[user] stares at the blank screen for a few moments, [user.p_their()] expression frozen in fear. When [user.p_they()] finally awaken[user.p_s()] from it, [user.p_they()] look[user.p_s()] a lot older.", 2) src.arcanecheckout = 0 if(1) @@ -186,7 +186,7 @@ if(8) dat += {"

Accessing Forbidden Lore Vault v 1.3

- Are you absolutely sure you want to proceed? EldritchTomes Inc. takes no responsibilities for loss of sanity resulting from this action.

+ Are you absolutely sure you want to proceed? EldritchArtifacts Inc. takes no responsibilities for loss of sanity resulting from this action.

Yes.
No.
"} diff --git a/code/modules/mob/dead/observer/orbit.dm b/code/modules/mob/dead/observer/orbit.dm index 77b1546dcb00b..fc5a3e4f9849f 100644 --- a/code/modules/mob/dead/observer/orbit.dm +++ b/code/modules/mob/dead/observer/orbit.dm @@ -58,7 +58,7 @@ var/mob/M = poi if(istype(M)) - if (isobserver(M)) + if(isobserver(M)) ghosts += list(serialized) else if(M.stat == DEAD) dead += list(serialized) @@ -80,16 +80,24 @@ antag_serialized["antag"] = A.name antagonists += list(antag_serialized) + // Changelings if(mind.changeling) var/antag_serialized = serialized.Copy() antag_serialized["antag"] = "Changeling" antagonists += list(antag_serialized) + // Vampires if(mind.vampire) var/antag_serialized = serialized.Copy() antag_serialized["antag"] = "Vampire" antagonists += list(antag_serialized) + // Cultists + if(SSticker.mode.cult && (mind in SSticker.mode.cult)) + var/antag_serialized = serialized.Copy() + antag_serialized["antag"] = "Cultist" + antagonists += list(antag_serialized) + // Other antags are not in the list, mostly because I don't know their code well enough, // and am not sure how to extract the "is this is an antag?" Info easily. // If you are annoyed by this - datumize them and put under `.antag_datums`! diff --git a/code/modules/mob/inventory.dm b/code/modules/mob/inventory.dm index 8f4f4dbb2da50..f61e04ba06404 100644 --- a/code/modules/mob/inventory.dm +++ b/code/modules/mob/inventory.dm @@ -263,3 +263,12 @@ return r_hand return null +//search for a path in inventory and storage items in that inventory (backpack, belt, etc) and return it. Not recursive, so doesnt search storage in storage +/mob/proc/find_item(path) + for(var/obj/item/I in contents) + if(istype(I, /obj/item/storage)) + for(var/obj/item/SI in I.contents) + if(istype(SI, path)) + return SI + else if(istype(I, path)) + return I diff --git a/code/modules/mob/living/carbon/carbon_defense.dm b/code/modules/mob/living/carbon/carbon_defense.dm index e0cba0b028114..73ab48a4312e4 100644 --- a/code/modules/mob/living/carbon/carbon_defense.dm +++ b/code/modules/mob/living/carbon/carbon_defense.dm @@ -72,3 +72,16 @@ /mob/living/carbon/is_mouth_covered(head_only = FALSE, mask_only = FALSE) if((!mask_only && head && (head.flags_cover & HEADCOVERSMOUTH)) || (!head_only && wear_mask && (wear_mask.flags_cover & MASKCOVERSMOUTH))) return TRUE + +//Called when drawing cult runes/using cult spells. Deal damage to a random arm/hand, or chest if not there. +/mob/living/carbon/cult_self_harm(damage, rune_message = FALSE) + var/dam_zone = pick("l_arm", "l_hand", "r_arm", "r_hand") + var/obj/item/organ/external/affecting = get_organ(dam_zone) + if(!affecting) + affecting = get_organ("chest") + if(!affecting) //bruh where's your chest + return FALSE + apply_damage(damage, BRUTE, affecting) + if(rune_message) + visible_message("[src] cuts open [src.p_their()] [affecting.name] and begins writing in [src.p_their()] own blood!", + "You slice open your [affecting.name] and begin drawing a sigil of [SSticker.cultdat.entity_title3].") diff --git a/code/modules/mob/living/carbon/human/examine.dm b/code/modules/mob/living/carbon/human/examine.dm index 7f4b90aef0bb3..fbaaeb124b310 100644 --- a/code/modules/mob/living/carbon/human/examine.dm +++ b/code/modules/mob/living/carbon/human/examine.dm @@ -23,6 +23,8 @@ if(wear_mask) skipface |= wear_mask.flags_inv & HIDEFACE + skipeyes |= wear_mask.flags_inv & HIDEEYES + var/msg = "*---------*\nThis is " if(!(skipjumpsuit && skipface) && icon) //big suits/masks/helmets make it hard to tell their gender @@ -143,11 +145,14 @@ msg += "[p_they(TRUE)] [p_have()] [bicon(wear_mask)] \a [wear_mask] on [p_their()] face.\n" //eyes - if(glasses && !skipeyes && !(glasses.flags & ABSTRACT)) - if(glasses.blood_DNA) - msg += "[p_they(TRUE)] [p_have()] [bicon(glasses)] [glasses.gender==PLURAL?"some":"a"] [glasses.blood_color != "#030303" ? "blood-stained":"oil-stained"] [glasses] covering [p_their()] eyes!\n" - else - msg += "[p_they(TRUE)] [p_have()] [bicon(glasses)] \a [glasses] covering [p_their()] eyes.\n" + if(!skipeyes) + if(glasses && !(glasses.flags & ABSTRACT)) + if(glasses.blood_DNA) + msg += "[p_they(TRUE)] [p_have()] [bicon(glasses)] [glasses.gender==PLURAL?"some":"a"] [glasses.blood_color != "#030303" ? "blood-stained":"oil-stained"] [glasses] covering [p_their()] eyes!\n" + else + msg += "[p_they(TRUE)] [p_have()] [bicon(glasses)] \a [glasses] covering [p_their()] eyes.\n" + else if(iscultist(src) && HAS_TRAIT(src, CULT_EYES)) + msg += "[p_their(TRUE)] eyes are glowing an unnatural red!\n" //left ear if(l_ear && !skipears) diff --git a/code/modules/mob/living/carbon/human/human.dm b/code/modules/mob/living/carbon/human/human.dm index 379a54970d500..2f2c4da0f11ca 100644 --- a/code/modules/mob/living/carbon/human/human.dm +++ b/code/modules/mob/living/carbon/human/human.dm @@ -1608,6 +1608,11 @@ Eyes need to have significantly high darksight to shine unless the mob has the X to_chat(src, "\The [S] pulls \the [hand] from your grip!") apply_effect(current_size * 3, IRRADIATE) +/mob/living/carbon/human/narsie_act() + if(iswizard(src) && iscultist(src)) //Wizard cultists are immune to narsie because it would prematurely end the wiz round that's about to end by the automated shuttle call anyway + return + ..() + /mob/living/carbon/human/proc/do_cpr(mob/living/carbon/human/H) if(H == src) to_chat(src, "You cannot perform CPR on yourself!") diff --git a/code/modules/mob/living/carbon/human/human_defines.dm b/code/modules/mob/living/carbon/human/human_defines.dm index bc5c85e29299a..4cd2d1847d941 100644 --- a/code/modules/mob/living/carbon/human/human_defines.dm +++ b/code/modules/mob/living/carbon/human/human_defines.dm @@ -68,3 +68,4 @@ var/tail // Name of tail image in species effects icon file. var/list/splinted_limbs = list() //limbs we know are splinted + var/original_eye_color = "#000000" diff --git a/code/modules/mob/living/carbon/human/species/_species.dm b/code/modules/mob/living/carbon/human/species/_species.dm index 6716c8f1a0f0d..1b4bc42009631 100644 --- a/code/modules/mob/living/carbon/human/species/_species.dm +++ b/code/modules/mob/living/carbon/human/species/_species.dm @@ -78,7 +78,7 @@ var/bodyflags = 0 var/dietflags = 0 // Make sure you set this, otherwise it won't be able to digest a lot of foods - var/blood_color = "#A10808" //Red. + var/blood_color = COLOR_BLOOD_BASE //Red. var/flesh_color = "#d1aa2e" //Gold. var/single_gib_type = /obj/effect/decal/cleanable/blood/gibs var/remains_type = /obj/effect/decal/remains/human //What sort of remains is left behind when the species dusts @@ -831,6 +831,9 @@ It'll return null if the organ doesn't correspond, so include null checks when u if(XRAY in H.mutations) H.sight |= (SEE_TURFS|SEE_MOBS|SEE_OBJS) + if(H.has_status_effect(STATUS_EFFECT_SUMMONEDGHOST)) + H.see_invisible = SEE_INVISIBLE_OBSERVER + H.sync_lighting_plane_alpha() /datum/species/proc/water_act(mob/living/carbon/human/M, volume, temperature, source, method = REAGENT_TOUCH) diff --git a/code/modules/mob/living/carbon/human/species/machine.dm b/code/modules/mob/living/carbon/human/species/machine.dm index a1cab2ea46645..b54b02929f91f 100644 --- a/code/modules/mob/living/carbon/human/species/machine.dm +++ b/code/modules/mob/living/carbon/human/species/machine.dm @@ -27,7 +27,7 @@ bodyflags = HAS_SKIN_COLOR | HAS_HEAD_MARKINGS | HAS_HEAD_ACCESSORY | ALL_RPARTS dietflags = 0 //IPCs can't eat, so no diet taste_sensitivity = TASTE_SENSITIVITY_NO_TASTE - blood_color = "#1F181F" + blood_color = COLOR_BLOOD_MACHINE flesh_color = "#AAAAAA" //Default styles for created mobs. diff --git a/code/modules/mob/living/carbon/human/update_icons.dm b/code/modules/mob/living/carbon/human/update_icons.dm index 71ee1e00871e5..91f285b28732f 100644 --- a/code/modules/mob/living/carbon/human/update_icons.dm +++ b/code/modules/mob/living/carbon/human/update_icons.dm @@ -525,6 +525,7 @@ GLOBAL_LIST_EMPTY(damage_icon_parts) UpdateDamageIcon() force_update_limbs() update_tail_layer() + update_halo_layer() overlays.Cut() // Force all overlays to regenerate update_fire() update_icons() @@ -1264,6 +1265,17 @@ GLOBAL_LIST_EMPTY(damage_icon_parts) apply_overlay(MISC_LAYER) +/mob/living/carbon/human/proc/update_halo_layer() + remove_overlay(HALO_LAYER) + + if(iscultist(src) && SSticker.mode.cult_ascendant) + var/istate = pick("halo1", "halo2", "halo3", "halo4", "halo5", "halo6") + var/mutable_appearance/new_halo_overlay = mutable_appearance('icons/effects/32x64.dmi', istate, -HALO_LAYER) + overlays_standing[HALO_LAYER] = new_halo_overlay + + apply_overlay(HALO_LAYER) + + /mob/living/carbon/human/admin_Freeze(client/admin, skip_overlays = TRUE, mech = null) if(..()) overlays_standing[FROZEN_LAYER] = mutable_appearance(frozen, layer = -FROZEN_LAYER) diff --git a/code/modules/mob/living/living.dm b/code/modules/mob/living/living.dm index 883b781c43ce7..bb76b436ad0ce 100644 --- a/code/modules/mob/living/living.dm +++ b/code/modules/mob/living/living.dm @@ -422,6 +422,11 @@ C.reagents.clear_reagents() QDEL_LIST(C.reagents.addiction_list) C.reagents.addiction_threshold_accumulated.Cut() + if(iscultist(src)) + if(SSticker.mode.cult_risen) + SSticker.mode.rise(src) + if(SSticker.mode.cult_ascendant) + SSticker.mode.ascend(src) QDEL_LIST(C.processing_patches) @@ -835,7 +840,7 @@ /mob/living/narsie_act() if(client) - makeNewConstruct(/mob/living/simple_animal/hostile/construct/harvester, src, null, 1) + make_new_construct(/mob/living/simple_animal/hostile/construct/harvester, src, cult_override = TRUE) spawn_dust() gib() diff --git a/code/modules/mob/living/living_defense.dm b/code/modules/mob/living/living_defense.dm index 9ecda42de04ac..bc3a7e5a41b1b 100644 --- a/code/modules/mob/living/living_defense.dm +++ b/code/modules/mob/living/living_defense.dm @@ -357,3 +357,6 @@ if(INTENT_DISARM) M.do_attack_animation(src, ATTACK_EFFECT_DISARM) return TRUE + +/mob/living/proc/cult_self_harm(damage, rune_message = FALSE) + return FALSE diff --git a/code/modules/mob/living/simple_animal/constructs.dm b/code/modules/mob/living/simple_animal/constructs.dm index 4c2c2332924cf..96d60cdca2b17 100644 --- a/code/modules/mob/living/simple_animal/constructs.dm +++ b/code/modules/mob/living/simple_animal/constructs.dm @@ -1,4 +1,3 @@ - /mob/living/simple_animal/hostile/construct name = "Construct" real_name = "Construct" @@ -13,6 +12,7 @@ a_intent = INTENT_HARM stop_automated_movement = 1 status_flags = CANPUSH + see_in_dark = 8 attack_sound = 'sound/weapons/punch1.ogg' 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 @@ -44,12 +44,11 @@ for(var/spell in construct_spells) AddSpell(new spell(null)) - if(SSticker.cultdat?.theme == "blood") - updateglow() + set_light(2, 3, l_color = SSticker.cultdat ? SSticker.cultdat.construct_glow : LIGHT_COLOR_BLOOD_MAGIC) /mob/living/simple_animal/hostile/construct/death(gibbed) . = ..() - SSticker.mode.remove_cultist(src.mind, FALSE) + SSticker.mode.remove_cultist(mind, FALSE) /mob/living/simple_animal/hostile/construct/examine(mob/user) . = ..() @@ -85,7 +84,6 @@ else if(src != M) return ..() - /mob/living/simple_animal/hostile/construct/narsie_act() return @@ -122,7 +120,6 @@ playstyle_string = "You are a Juggernaut. Though slow, your shell can withstand extreme punishment, \ create shield walls, rip apart enemies and walls alike, and even deflect energy weapons." - /mob/living/simple_animal/hostile/construct/armoured/hostile //actually hostile, will move around, hit things AIStatus = AI_ON environment_smash = 1 //only token destruction, don't smash the cult wall NO STOP @@ -160,7 +157,6 @@ melee_damage_lower = 25 melee_damage_upper = 25 attacktext = "slashes" - see_in_dark = 8 attack_sound = 'sound/weapons/bladeslice.ogg' const_type = "wraith" construct_spells = list(/obj/effect/proc_holder/spell/targeted/night_vision, /obj/effect/proc_holder/spell/targeted/ethereal_jaunt/shift) @@ -257,14 +253,14 @@ /mob/living/simple_animal/hostile/construct/behemoth name = "Behemoth" real_name = "Behemoth" - desc = "The pinnacle of occult technology, Behemoths are the ultimate weapon in the Cult of Nar-Sie's arsenal." + desc = "The pinnacle of occult technology, Behemoths are the ultimate weapon in the Cult's arsenal." icon = 'icons/mob/mob.dmi' icon_state = "behemoth" icon_living = "behemoth" maxHealth = 750 health = 750 speak_emote = list("rumbles") - response_harm = "harmlessly punches" + response_harm = "harmlessly punches" harm_intent_damage = 0 melee_damage_lower = 50 melee_damage_upper = 50 @@ -291,7 +287,7 @@ /mob/living/simple_animal/hostile/construct/harvester name = "Harvester" real_name = "Harvester" - desc = "A harbinger of Nar-Sie's enlightenment. It'll be all over soon." + desc = "A harbinger of enlightenment. It'll be all over soon." icon = 'icons/mob/mob.dmi' icon_state = "harvester" icon_living = "harvester" @@ -301,7 +297,6 @@ melee_damage_upper = 5 attacktext = "prods" environment_smash = ENVIRONMENT_SMASH_RWALLS - see_in_dark = 8 attack_sound = 'sound/weapons/tap.ogg' const_type = "harvester" construct_spells = list(/obj/effect/proc_holder/spell/targeted/night_vision, @@ -314,23 +309,13 @@ /mob/living/simple_animal/hostile/construct/harvester/Process_Spacemove(var/movement_dir = 0) - return 1 + return TRUE /mob/living/simple_animal/hostile/construct/harvester/hostile //actually hostile, will move around, hit things AIStatus = AI_ON environment_smash = 1 //only token destruction, don't smash the cult wall NO STOP -////////////////Glow//////////////////// -/mob/living/simple_animal/hostile/construct/proc/updateglow() - overlays = 0 - var/overlay_layer = LIGHTING_LAYER + 1 - if(layer != MOB_LAYER) - overlay_layer=TURF_LAYER+0.2 - - overlays += image(icon,"glow-[icon_state]",overlay_layer) - set_light(2, -2, l_color = "#FFFFFF") - ///ui stuff /mob/living/simple_animal/hostile/construct/armoured/update_health_hud() diff --git a/code/modules/mob/living/simple_animal/hostile/illusion.dm b/code/modules/mob/living/simple_animal/hostile/illusion.dm index 3756fabf5229d..79c7e63a341bc 100644 --- a/code/modules/mob/living/simple_animal/hostile/illusion.dm +++ b/code/modules/mob/living/simple_animal/hostile/illusion.dm @@ -71,3 +71,10 @@ /mob/living/simple_animal/hostile/illusion/escape/AttackingTarget() return + +///////Cult Illusions///////// +/mob/living/simple_animal/hostile/illusion/cult + loot = list(/obj/effect/temp_visual/cult/sparks) // So that they SPARKLE on death + +/mob/living/simple_animal/hostile/illusion/escape/cult + loot = list(/obj/effect/temp_visual/cult/sparks) diff --git a/code/modules/mob/living/simple_animal/shade.dm b/code/modules/mob/living/simple_animal/shade.dm index 1e3c723b278b9..2c99640b3ccb5 100644 --- a/code/modules/mob/living/simple_animal/shade.dm +++ b/code/modules/mob/living/simple_animal/shade.dm @@ -20,41 +20,42 @@ maxbodytemp = 4000 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) speed = -1 - stop_automated_movement = 1 + stop_automated_movement = TRUE status_flags = 0 + pull_force = 0 + universal_speak = TRUE faction = list("cult") status_flags = CANPUSH flying = TRUE loot = list(/obj/item/reagent_containers/food/snacks/ectoplasm) - del_on_death = 1 + del_on_death = TRUE deathmessage = "lets out a contented sigh as their form unwinds." + var/holy = FALSE /mob/living/simple_animal/shade/death(gibbed) . = ..() - SSticker.mode.remove_cultist(src.mind, FALSE) + SSticker.mode.remove_cultist(mind, FALSE) -/mob/living/simple_animal/shade/attackby(var/obj/item/O as obj, var/mob/user as mob) //Marker -Agouri +/mob/living/simple_animal/shade/attackby(obj/item/O, mob/user) //Marker -Agouri if(istype(O, /obj/item/soulstone)) - O.transfer_soul("SHADE", src, user) + var/obj/item/soulstone/SS = O + SS.transfer_soul("SHADE", src, user) else - if(O.force) - var/damage = O.force - if(O.damtype == STAMINA) - damage = 0 - health -= damage - user.visible_message("[src] has been attacked with the [O] by [user]. ") - else - user.visible_message("[user] gently taps [src] with the [O]. ", "This weapon is ineffective, it does no damage.") - return + ..() + +/mob/living/simple_animal/shade/Process_Spacemove() + return TRUE + /mob/living/simple_animal/shade/cult/Initialize(mapload) . = ..() - name = SSticker.cultdat?.shade_name - real_name = SSticker.cultdat?.shade_name icon_state = SSticker.cultdat?.shade_icon_state +/mob/living/simple_animal/shade/holy + holy = TRUE + icon_state = "shade_angelic" + /mob/living/simple_animal/shade/sword - universal_speak = 1 faction = list("neutral") /mob/living/simple_animal/shade/sword/Initialize(mapload) diff --git a/code/modules/mob/living/status_procs.dm b/code/modules/mob/living/status_procs.dm index ce266ba5b8fb0..e23baf18489ad 100644 --- a/code/modules/mob/living/status_procs.dm +++ b/code/modules/mob/living/status_procs.dm @@ -371,10 +371,10 @@ // CULTSLURRING /mob/living/CultSlur(amount) - SetSlur(max(slurring, amount)) + SetCultSlur(max(cultslurring, amount)) /mob/living/SetCultSlur(amount) - slurring = max(amount, 0) + cultslurring = max(amount, 0) /mob/living/AdjustCultSlur(amount, bound_lower = 0, bound_upper = INFINITY) var/new_value = directional_bounded_sum(cultslurring, amount, bound_lower, bound_upper) diff --git a/code/modules/power/singularity/narsie.dm b/code/modules/power/singularity/narsie.dm index bead9c935ef90..257d98fffc1e5 100644 --- a/code/modules/power/singularity/narsie.dm +++ b/code/modules/power/singularity/narsie.dm @@ -1,25 +1,28 @@ /obj/singularity/narsie //Moving narsie to a child object of the singularity so it can be made to function differently. --NEO - name = "Nar-sie's Avatar" + name = "Nar'sie's Avatar" desc = "Your mind begins to bubble and ooze as it tries to comprehend what it sees." icon = 'icons/obj/magic_terror.dmi' pixel_x = -89 pixel_y = -85 current_size = 9 //It moves/eats like a max-size singulo, aside from range. --NEO - contained = 0 //Are we going to move around? - dissipate = 0 //Do we lose energy over time? - move_self = 1 //Do we move on our own? - grav_pull = 5 //How many tiles out do we pull? - consume_range = 6 //How many tiles out do we eat + contained = FALSE + dissipate = FALSE + move_self = TRUE + grav_pull = 5 + consume_range = 6 gender = FEMALE +/obj/singularity/narsie/admin_investigate_setup() + return + /obj/singularity/narsie/large - name = "Nar-Sie" + name = "Nar'Sie" icon = 'icons/obj/narsie.dmi' // Pixel stuff centers Narsie. pixel_x = -236 pixel_y = -256 current_size = 12 - move_self = 1 //Do we move on our own? + move_self = TRUE //Do we move on our own? grav_pull = 10 consume_range = 12 //How many tiles out do we eat @@ -27,22 +30,38 @@ ..() icon_state = SSticker.cultdat?.entity_icon_state name = SSticker.cultdat?.entity_name - to_chat(world, " [name] HAS RISEN") - world << pick(sound('sound/hallucinations/im_here1.ogg'), sound('sound/hallucinations/im_here2.ogg')) + to_chat(world, " [uppertext(name)] HAS RISEN") + SEND_SOUND(world, pick('sound/hallucinations/im_here1.ogg', 'sound/hallucinations/im_here2.ogg')) + + var/datum/game_mode/gamemode = SSticker.mode + if(gamemode) + gamemode.cult_objs.succesful_summon() var/area/A = get_area(src) if(A) var/image/alert_overlay = image('icons/effects/cult_effects.dmi', "ghostalertsie") - notify_ghosts("Nar-Sie has risen in \the [A.name]. Reach out to the Geometer to be given a new shell for your soul.", source = src, alert_overlay = alert_overlay, action=NOTIFY_ATTACK) + notify_ghosts("[name] has risen in \the [A.name]. Reach out to the Geometer to be given a new shell for your soul.", source = src, alert_overlay = alert_overlay, action = NOTIFY_ATTACK) narsie_spawn_animation() - sleep(70) - SSshuttle.emergency.request(null, 0.3) // Cannot recall - SSshuttle.emergency.canRecall = FALSE + sleep(7 SECONDS) + SSshuttle.emergency.request(null, 0.3) + SSshuttle.emergency.canRecall = FALSE // Cannot recall + +/obj/singularity/narsie/large/Destroy() + to_chat(world, " [uppertext(name)] HAS FALLEN") + SEND_SOUND(world, 'sound/hallucinations/wail.ogg') + var/datum/game_mode/gamemode = SSticker.mode + if(gamemode) + gamemode.cult_objs.narsie_death() + for(var/datum/mind/cult_mind in SSticker.mode.cult) + if(cult_mind && cult_mind.current) + to_chat(cult_mind.current, "RETRIBUTION!") + to_chat(cult_mind.current, "Current goal: Slaughter the heretics!") + ..() -/obj/singularity/narsie/large/attack_ghost(mob/dead/observer/user as mob) - makeNewConstruct(/mob/living/simple_animal/hostile/construct/harvester, user, null, 1) +/obj/singularity/narsie/large/attack_ghost(mob/dead/observer/user) + make_new_construct(/mob/living/simple_animal/hostile/construct/harvester, user, cult_override = TRUE) new /obj/effect/particle_effect/smoke/sleeping(user.loc) @@ -63,17 +82,16 @@ godsmack(A) return -/obj/singularity/narsie/proc/godsmack(var/atom/A) +/obj/singularity/narsie/proc/godsmack(atom/A) if(istype(A,/obj/)) var/obj/O = A - O.ex_act(1.0) + O.ex_act(1) if(O) qdel(O) else if(isturf(A)) var/turf/T = A T.ChangeTurf(/turf/simulated/floor/engine/cult) - /obj/singularity/narsie/mezzer() for(var/mob/living/carbon/M in oviewers(8, src)) if(M.stat == CONSCIOUS) @@ -82,13 +100,14 @@ M.apply_effect(3, STUN) -/obj/singularity/narsie/consume(var/atom/A) +/obj/singularity/narsie/consume(atom/A) A.narsie_act() - /obj/singularity/narsie/ex_act() //No throwing bombs at it either. --NEO return +/obj/singularity/narsie/singularity_act() //handled in /obj/singularity/proc/consume + return /obj/singularity/narsie/proc/pickcultist() //Narsie rewards his cultists with being devoured first, then picks a ghost to follow. --NEO var/list/cultists = list() @@ -103,11 +122,11 @@ else noncultists += food - if(cultists.len) //cultists get higher priority + if(length(cultists)) //cultists get higher priority acquire(pick(cultists)) return - if(noncultists.len) + if(length(noncultists)) acquire(pick(noncultists)) return @@ -119,12 +138,12 @@ if(pos.z != src.z) continue cultists += ghost - if(cultists.len) + if(length(cultists)) acquire(pick(cultists)) return -/obj/singularity/narsie/proc/acquire(var/mob/food) +/obj/singularity/narsie/proc/acquire(mob/food) if(food == target) return if(!target) @@ -150,8 +169,8 @@ /obj/singularity/narsie/proc/narsie_spawn_animation() icon = 'icons/obj/narsie_spawn_anim.dmi' dir = SOUTH - move_self = 0 + move_self = FALSE flick(SSticker.cultdat?.entity_spawn_animation, src) sleep(11) - move_self = 1 + move_self = TRUE icon = initial(icon) diff --git a/code/modules/power/singularity/singularity.dm b/code/modules/power/singularity/singularity.dm index 78c623adacf70..d5e511af0090d 100644 --- a/code/modules/power/singularity/singularity.dm +++ b/code/modules/power/singularity/singularity.dm @@ -280,6 +280,15 @@ name = "supermatter-charged [initial(name)]" consumedSupermatter = 1 set_light(10) + if(istype(A, /obj/singularity/narsie)) + if(current_size == STAGE_SIX) + visible_message("[SSticker.cultdat?.entity_name] is consumed by [src]!") + qdel(A) + else + visible_message("[SSticker.cultdat?.entity_name] strikes down [src]!") + investigate_log("has been destroyed by Nar'Sie","singulo") + qdel(src) + return diff --git a/code/modules/projectiles/ammunition/special.dm b/code/modules/projectiles/ammunition/special.dm index f37690e278744..3a05a5cbf381d 100644 --- a/code/modules/projectiles/ammunition/special.dm +++ b/code/modules/projectiles/ammunition/special.dm @@ -42,6 +42,9 @@ projectile_type = pick(typesof(/obj/item/projectile/magic)) ..() +/obj/item/ammo_casing/magic/arcane_barrage + projectile_type = /obj/item/projectile/magic/arcane_barrage + /obj/item/ammo_casing/magic/forcebolt projectile_type = /obj/item/projectile/forcebolt diff --git a/code/modules/projectiles/guns/projectile/shotgun.dm b/code/modules/projectiles/guns/projectile/shotgun.dm index cafe9f4cb21f1..55f6a757a5e8e 100644 --- a/code/modules/projectiles/guns/projectile/shotgun.dm +++ b/code/modules/projectiles/guns/projectile/shotgun.dm @@ -258,19 +258,40 @@ ..() guns_left = 0 +/obj/item/gun/projectile/shotgun/boltaction/enchanted/attack_self() + return + /obj/item/gun/projectile/shotgun/boltaction/enchanted/shoot_live_shot(mob/living/user, atom/target, pointblank = FALSE, message = TRUE) ..() if(guns_left) - var/obj/item/gun/projectile/shotgun/boltaction/enchanted/GUN = new + var/obj/item/gun/projectile/shotgun/boltaction/enchanted/GUN = new type GUN.guns_left = guns_left - 1 - user.drop_item() + discard_gun(user) user.swap_hand() + user.drop_item() user.put_in_hands(GUN) else - user.drop_item() - spawn(0) - throw_at(pick(oview(7,get_turf(user))),1,1) + discard_gun(user) + +/obj/item/gun/projectile/shotgun/boltaction/enchanted/proc/discard_gun(mob/living/user) user.visible_message("[user] tosses aside the spent rifle!") + user.throw_item(pick(oview(7, get_turf(user)))) + +/obj/item/gun/projectile/shotgun/boltaction/enchanted/arcane_barrage + name = "arcane barrage" + desc = "Pew Pew Pew." + fire_sound = 'sound/weapons/emitter.ogg' + icon_state = "arcane_barrage" + item_state = "arcane_barrage" + slot_flags = null + flags = NOBLUDGEON | DROPDEL | ABSTRACT + mag_type = /obj/item/ammo_box/magazine/internal/boltaction/enchanted/arcane_barrage + +/obj/item/gun/projectile/shotgun/boltaction/enchanted/arcane_barrage/examine(mob/user) + . = desc // Override since magical hand lasers don't have chambers or bolts + +/obj/item/gun/projectile/shotgun/boltaction/enchanted/arcane_barrage/discard_gun(mob/living/user) + qdel(src) // Automatic Shotguns// diff --git a/code/modules/projectiles/projectile/magic.dm b/code/modules/projectiles/projectile/magic.dm index 080114554bb08..b29b6109d1c73 100644 --- a/code/modules/projectiles/projectile/magic.dm +++ b/code/modules/projectiles/projectile/magic.dm @@ -346,3 +346,13 @@ M.Weaken(slip_weaken) M.Stun(slip_stun) . = ..() + +/obj/item/projectile/magic/arcane_barrage + name = "arcane bolt" + icon_state = "arcane_barrage" + damage = 20 + damage_type = BURN + nodamage = FALSE + armour_penetration = 0 + flag = "magic" + hitsound = 'sound/weapons/barragespellhit.ogg' diff --git a/code/modules/reagents/chemistry/reagents/water.dm b/code/modules/reagents/chemistry/reagents/water.dm index 861c48c7e0e1d..bd6efd5a5d786 100644 --- a/code/modules/reagents/chemistry/reagents/water.dm +++ b/code/modules/reagents/chemistry/reagents/water.dm @@ -241,9 +241,14 @@ if(current_cycle >= 30) // 12 units, 60 seconds @ metabolism 0.4 units & tick rate 2.0 sec M.AdjustStuttering(4, bound_lower = 0, bound_upper = 20) M.Dizzy(5) - if(iscultist(M) && prob(5)) - M.AdjustCultSlur(5)//5 seems like a good number... - M.say(pick("Av'te Nar'sie","Pa'lid Mors","INO INO ORA ANA","SAT ANA!","Daim'niodeis Arc'iai Le'eones","Egkau'haom'nai en Chaous","Ho Diak'nos tou Ap'iron","R'ge Na'sie","Diabo us Vo'iscum","Si gn'um Co'nu")) + if(iscultist(M)) + for(var/datum/action/innate/cult/blood_magic/BM in M.actions) + for(var/datum/action/innate/cult/blood_spell/BS in BM.spells) + to_chat(M, "Your blood rites falter as holy water scours your body!") + qdel(BS) + if(prob(5)) + M.AdjustCultSlur(5)//5 seems like a good number... + M.say(pick("Av'te Nar'sie","Pa'lid Mors","INO INO ORA ANA","SAT ANA!","Daim'niodeis Arc'iai Le'eones","Egkau'haom'nai en Chaous","Ho Diak'nos tou Ap'iron","R'ge Na'sie","Diabo us Vo'iscum","Si gn'um Co'nu")) if(current_cycle >= 75 && prob(33)) // 30 units, 150 seconds M.AdjustConfused(3) if(isvampirethrall(M)) @@ -259,6 +264,11 @@ M.SetJitter(0) M.SetStuttering(0) M.SetConfused(0) + if(ishuman(M)) // Unequip all cult clothing + var/mob/living/carbon/human/H = M + for(var/I in H.contents - (H.bodyparts | H.internal_organs)) // Satanic liver NYI + if(is_type_in_list(I, CULT_CLOTHING)) + H.unEquip(I) return if(ishuman(M) && M.mind && M.mind.vampire && !M.mind.vampire.get_ability(/datum/vampire_passive/full) && prob(80)) var/mob/living/carbon/V = M diff --git a/icons/effects/32x64.dmi b/icons/effects/32x64.dmi new file mode 100644 index 0000000000000..4d9f9911944c2 Binary files /dev/null and b/icons/effects/32x64.dmi differ diff --git a/icons/effects/96x96.dmi b/icons/effects/96x96.dmi index 95e9747f87820..124471265df78 100644 Binary files a/icons/effects/96x96.dmi and b/icons/effects/96x96.dmi differ diff --git a/icons/effects/beam.dmi b/icons/effects/beam.dmi index 00d52a742dad6..a6afd27cc86cf 100644 Binary files a/icons/effects/beam.dmi and b/icons/effects/beam.dmi differ diff --git a/icons/effects/crayondecal.dmi b/icons/effects/crayondecal.dmi index 48388c18fd403..36d68514d44a0 100644 Binary files a/icons/effects/crayondecal.dmi and b/icons/effects/crayondecal.dmi differ diff --git a/icons/effects/cult_effects.dmi b/icons/effects/cult_effects.dmi index 3307e27d24907..cbab2398b3cd1 100644 Binary files a/icons/effects/cult_effects.dmi and b/icons/effects/cult_effects.dmi differ diff --git a/icons/effects/cult_target.dmi b/icons/effects/cult_target.dmi new file mode 100644 index 0000000000000..650feb3361343 Binary files /dev/null and b/icons/effects/cult_target.dmi differ diff --git a/icons/effects/effects.dmi b/icons/effects/effects.dmi index bedd145d30810..6079921620188 100644 Binary files a/icons/effects/effects.dmi and b/icons/effects/effects.dmi differ diff --git a/icons/mob/actions/actions.dmi b/icons/mob/actions/actions.dmi index e270095cbd6fa..e0ef6c5ba1877 100644 Binary files a/icons/mob/actions/actions.dmi and b/icons/mob/actions/actions.dmi differ diff --git a/icons/mob/actions/actions_cult.dmi b/icons/mob/actions/actions_cult.dmi new file mode 100644 index 0000000000000..0e5320a54edcf Binary files /dev/null and b/icons/mob/actions/actions_cult.dmi differ diff --git a/icons/mob/hud.dmi b/icons/mob/hud.dmi index 07593d09c0c60..eb62ce8138097 100644 Binary files a/icons/mob/hud.dmi and b/icons/mob/hud.dmi differ diff --git a/icons/mob/inhands/items_lefthand.dmi b/icons/mob/inhands/items_lefthand.dmi index 240558ebb80f1..f8195652dca2b 100644 Binary files a/icons/mob/inhands/items_lefthand.dmi and b/icons/mob/inhands/items_lefthand.dmi differ diff --git a/icons/mob/inhands/items_righthand.dmi b/icons/mob/inhands/items_righthand.dmi index a3743efa33b4c..5520ac724b327 100644 Binary files a/icons/mob/inhands/items_righthand.dmi and b/icons/mob/inhands/items_righthand.dmi differ diff --git a/icons/mob/mob.dmi b/icons/mob/mob.dmi index ce6a3d96012d2..79a05bd3b8846 100644 Binary files a/icons/mob/mob.dmi and b/icons/mob/mob.dmi differ diff --git a/icons/mob/species/skrell/held.dmi b/icons/mob/species/skrell/held.dmi new file mode 100644 index 0000000000000..10680d5cba925 Binary files /dev/null and b/icons/mob/species/skrell/held.dmi differ diff --git a/icons/obj/cult.dmi b/icons/obj/cult.dmi index f592d142e87e5..7b29b64865d12 100644 Binary files a/icons/obj/cult.dmi and b/icons/obj/cult.dmi differ diff --git a/icons/obj/guns/projectile.dmi b/icons/obj/guns/projectile.dmi index d3566b2a65904..b1efd604cb5d9 100644 Binary files a/icons/obj/guns/projectile.dmi and b/icons/obj/guns/projectile.dmi differ diff --git a/icons/obj/projectiles.dmi b/icons/obj/projectiles.dmi index 0aa0a1a1f009f..104d730143424 100644 Binary files a/icons/obj/projectiles.dmi and b/icons/obj/projectiles.dmi differ diff --git a/icons/obj/rune.dmi b/icons/obj/rune.dmi index 5df6a842ad528..c098b7d8cc26e 100644 Binary files a/icons/obj/rune.dmi and b/icons/obj/rune.dmi differ diff --git a/icons/obj/wizard.dmi b/icons/obj/wizard.dmi index 83a80d2f40805..01fe0b5e82cce 100644 Binary files a/icons/obj/wizard.dmi and b/icons/obj/wizard.dmi differ diff --git a/paradise.dme b/paradise.dme index 537874351b276..f5a81db43629e 100644 --- a/paradise.dme +++ b/paradise.dme @@ -33,6 +33,7 @@ #include "code\__DEFINES\contracts.dm" #include "code\__DEFINES\crafting.dm" #include "code\__DEFINES\criminal_status.dm" +#include "code\__DEFINES\cult.dm" #include "code\__DEFINES\dna.dm" #include "code\__DEFINES\error_handler.dm" #include "code\__DEFINES\flags.dm" @@ -540,15 +541,15 @@ #include "code\game\gamemodes\changeling\powers\swap_form.dm" #include "code\game\gamemodes\changeling\powers\tiny_prick.dm" #include "code\game\gamemodes\changeling\powers\transform.dm" +#include "code\game\gamemodes\cult\blood_magic.dm" #include "code\game\gamemodes\cult\cult.dm" -#include "code\game\gamemodes\cult\cult_comms.dm" +#include "code\game\gamemodes\cult\cult_actions.dm" #include "code\game\gamemodes\cult\cult_datums.dm" #include "code\game\gamemodes\cult\cult_items.dm" #include "code\game\gamemodes\cult\cult_objectives.dm" #include "code\game\gamemodes\cult\cult_structures.dm" #include "code\game\gamemodes\cult\ritual.dm" #include "code\game\gamemodes\cult\runes.dm" -#include "code\game\gamemodes\cult\talisman.dm" #include "code\game\gamemodes\devil\devil.dm" #include "code\game\gamemodes\devil\devil_game_mode.dm" #include "code\game\gamemodes\devil\devilinfo.dm" diff --git a/sound/magic/cult_spell.ogg b/sound/magic/cult_spell.ogg new file mode 100644 index 0000000000000..c107743d805dc Binary files /dev/null and b/sound/magic/cult_spell.ogg differ diff --git a/sound/weapons/barragespellhit.ogg b/sound/weapons/barragespellhit.ogg new file mode 100644 index 0000000000000..a5e859a2aac33 Binary files /dev/null and b/sound/weapons/barragespellhit.ogg differ diff --git a/sound/weapons/parry.ogg b/sound/weapons/parry.ogg new file mode 100644 index 0000000000000..ff60f3c8b8fea Binary files /dev/null and b/sound/weapons/parry.ogg differ diff --git a/strings/tips.txt b/strings/tips.txt index d28943913d3e8..c382968ef0029 100644 --- a/strings/tips.txt +++ b/strings/tips.txt @@ -153,8 +153,7 @@ As a Changeling, the Extract DNA sting counts for your genome absorb objective, As a Changeling, you can absorb someone by strangling them and using the Absorb verb; this gives you the ability to rechoose your powers, the DNA of whoever you absorbed, the memory of the absorbed, and some samples of things the absorbed said. As a Cultist, do not cause too much chaos before your objective is completed. If the shuttle gets called too soon, you may not have enough time to win. As a Cultist, your team starts off very weak, but if necessary can quickly convert everything they have into raw power. Make sure you have the numbers and equipment to support going loud, or the cult will fall flat on its face. -As a Cultist, the Blood Boil rune will deal massive amounts of brute damage to non-cultists, and some damage to fellow cultists nearby, but will create a fire where the rune stands on use. -As a Cultist, you can create an army of manifested goons using a combination of the Manifest rune, which creates homunculi from ghosts, and the Blood Drain rune, which drains life from anyone standing on any blood drain rune. +As a Cultist, the Blood Boil rune will deal massive amounts of burn damage to non-cultists and set them on fire. However, two cultists have to be standing at the rune for its duration. You can deconvert Cultists by feeding them large amounts of holy water. The Chaplain can bless any container with water by hitting it with their bible. Holy water has a myriad of uses against cults and large amounts of it are a great contributor to success against them. As a Wizard, you can turn people to stone, then animate the resulting statue with a staff of animation to create an extremely powerful minion, for all of 5 minutes at least.