diff --git a/code/modules/antagonists/clockcult/clock_mobs/clockwork_marauder.dm b/code/modules/antagonists/clockcult/clock_mobs/clockwork_marauder.dm index 3bc237fc56..0112553ac5 100644 --- a/code/modules/antagonists/clockcult/clock_mobs/clockwork_marauder.dm +++ b/code/modules/antagonists/clockcult/clock_mobs/clockwork_marauder.dm @@ -124,3 +124,434 @@ #undef MARAUDER_SLOWDOWN_PERCENTAGE #undef MARAUDER_SHIELD_REGEN_TIME + +/*//Clockwork guardian: Slow but with high damage, resides inside of a servant. Created via the Memory Allocation scripture. +/mob/living/simple_animal/hostile/clockwork/marauder/guardian + name = "clockwork guardian" + desc = "A stalwart apparition of a soldier, blazing with crimson flames. It's armed with a gladius and shield and stands ready by its master." + icon_state = "clockwork_marauder" + health = 300 + maxHealth = 300 + speed = 1 + obj_damage = 40 + melee_damage_lower = 12 + melee_damage_upper = 12 + attacktext = "slashes" + attack_sound = 'sound/weapons/bladeslice.ogg' + weather_immunities = list("lava") + movement_type = FLYING + loot = list(/obj/item/clockwork/component/geis_capacitor/fallen_armor) + var/true_name = "Meme Master 69" //Required to call forth the guardian + var/global/list/possible_true_names = list("Servant", "Warden", "Serf", "Page", "Usher", "Knave", "Vassal", "Escort") + var/mob/living/host //The mob that the guardian is living inside of + var/recovering = FALSE //If the guardian is recovering from recalling + var/blockchance = 17 //chance to block attacks entirely + var/counterchance = 30 //chance to counterattack after blocking + var/static/list/damage_heal_order = list(OXY, BURN, BRUTE, TOX) //we heal our host's damage in this order + light_range = 2 + light_power = 1.1 + playstyle_string = "You are a clockwork guardian, a living extension of Sevtug's will. As a guardian, you are somewhat slow, but may block attacks, \ + and have a chance to also counter blocked melee attacks for extra damage, in addition to being immune to extreme temperatures and pressures. \ + Your primary goal is to serve the creature that you are now a part of. You can use :b to communicate silently with your master, \ + but can only exit if your master calls your true name or if they are exceptionally damaged. \ + \n\n\ + Stay near your host to protect and heal them; being too far from your host will rapidly cause you massive damage. Recall to your host if you are too weak and believe you cannot continue \ + fighting safely. As a final note, you should probably avoid harming any fellow servants of Ratvar." + +/mob/living/simple_animal/hostile/clockwork/marauder/guardian/Initialize() + . = ..() + true_name = pick(possible_true_names) + +/mob/living/simple_animal/hostile/clockwork/marauder/guardianLife() + ..() + if(is_in_host()) + if(!is_servant_of_ratvar(host)) + emerge_from_host(FALSE, TRUE) + unbind_from_host() + return + if(!GLOB.ratvar_awakens && host.stat == DEAD) + death() + return + if(GLOB.ratvar_awakens) + adjustHealth(-50) + else + adjustHealth(-10) + if(!recovering) + heal_host() //also heal our host if inside of them and we aren't recovering + else if(health == maxHealth) + to_chat(src, "Your strength has returned. You can once again come forward!") + to_chat(host, "Your guardian is now strong enough to come forward again!") + recovering = FALSE + else + if(GLOB.ratvar_awakens) //If Ratvar is alive, guardians don't need a host and are downright impossible to kill + adjustHealth(-5) + heal_host() + else if(host) + if(!is_servant_of_ratvar(host)) + unbind_from_host() + return + if(host.stat == DEAD) + adjustHealth(50) + to_chat(src, "Your host is dead!") + return + if(z && host.z && z == host.z) + switch(get_dist(get_turf(src), get_turf(host))) + if(2) + adjustHealth(-1) + if(3) + //EQUILIBRIUM + if(4) + adjustHealth(1) + if(5) + adjustHealth(3) + if(6) + adjustHealth(6) + if(7) + adjustHealth(9) + if(8 to INFINITY) + adjustHealth(15) + to_chat(src, "You're too far from your host and rapidly taking damage!") + else //right next to or on top of host + adjustHealth(-2) + heal_host() //gradually heal host if nearby and host is very weak + else //well then, you're not even in the same zlevel + adjustHealth(15) + to_chat(src, "You're too far from your host and rapidly taking damage!") + +/mob/living/simple_animal/hostile/clockwork/marauder/guardian/death(gibbed) + emerge_from_host(FALSE, TRUE) + unbind_from_host() + visible_message("[src]'s equipment clatters lifelessly to the ground as the red flames within dissipate.", \ + "Your equipment falls away. You feel a moment of confusion before your fragile form is annihilated.") + . = ..() + +/mob/living/simple_animal/hostile/clockwork/marauder/guardian/Stat() + ..() + if(statpanel("Status")) + stat(null, "Current True Name: [true_name]") + stat(null, "Host: [host ? host : "NONE"]") + if(host) + var/resulthealth = round((host.health / host.maxHealth) * 100, 0.5) + if(iscarbon(host)) + resulthealth = round((abs(HEALTH_THRESHOLD_DEAD - host.health) / abs(HEALTH_THRESHOLD_DEAD - host.maxHealth)) * 100) + stat(null, "Host Health: [resulthealth]%") + if(GLOB.ratvar_awakens) + stat(null, "You are [recovering ? "un" : ""]able to deploy!") + else + if(resulthealth > GUARDIAN_EMERGE_THRESHOLD) + stat(null, "You are [recovering ? "unable to deploy" : "able to deploy on hearing your True Name"]!") + else + stat(null, "You are [recovering ? "unable to deploy" : "able to deploy to protect your host"]!") + stat(null, "You do [melee_damage_upper] damage on melee attacks.") + +/mob/living/simple_animal/hostile/clockwork/marauder/guardian/Process_Spacemove(movement_dir = 0) + return 1 + +/mob/living/simple_animal/hostile/clockwork/marauder/guardian/proc/bind_to_host(mob/living/new_host) + if(!new_host) + return FALSE + host = new_host + var/datum/action/innate/summon_marauder/guardian/SM = new() + SM.linked_guardian = src + SM.Grant(host) + var/datum/action/innate/linked_minds/LM = new() + LM.linked_guardian = src + LM.Grant(host) + return TRUE + +/mob/living/simple_animal/hostile/clockwork/marauder/guardian/proc/unbind_from_host() + if(host) + for(var/datum/action/innate/summon_marauder/guardian/SM in host.actions) + qdel(SM) + for(var/datum/action/innate/linked_minds/LM in host.actions) + qdel(LM) + host = null + return TRUE + return FALSE + +//DAMAGE and FATIGUE +/mob/living/simple_animal/hostile/clockwork/marauder/guardian/proc/heal_host() + if(!host) + return + var/resulthealth = round((host.health / host.maxHealth) * 100, 0.5) + if(iscarbon(host)) + resulthealth = round((abs(HEALTH_THRESHOLD_DEAD - host.health) / abs(HEALTH_THRESHOLD_DEAD - host.maxHealth)) * 100) + if(GLOB.ratvar_awakens || resulthealth <= GUARDIAN_EMERGE_THRESHOLD) + new /obj/effect/temp_visual/heal(host.loc, "#AF0AAF") + host.heal_ordered_damage(4, damage_heal_order) + +/mob/living/simple_animal/hostile/clockwork/marauder/guardian/adjustHealth(amount, updating_health = TRUE, forced = FALSE) + if(amount > 0) + for(var/mob/living/L in view(2, src)) + if(L.is_holding_item_of_type(/obj/item/nullrod)) + to_chat(src, "The presence of a brandished holy artifact weakens your armor!") + amount *= 4 //if a wielded null rod is nearby, it takes four times the health damage + break + . = ..() + if(src && updating_health) + update_health_hud() + update_stats() + +/mob/living/simple_animal/hostile/clockwork/marauder/guardian/update_health_hud() + if(hud_used && hud_used.healths) + if(istype(hud_used, /datum/hud/guardian)) + var/datum/hud/guardian/M = hud_used + var/resulthealth + if(host) + if(iscarbon(host)) + resulthealth = "[round((abs(HEALTH_THRESHOLD_DEAD - host.health) / abs(HEALTH_THRESHOLD_DEAD - host.maxHealth)) * 100)]%" + else + resulthealth = "[round((host.health / host.maxHealth) * 100, 0.5)]%" + else + resulthealth = "NONE" + M.hosthealth.maptext = "
HOST
[resulthealth]
" + hud_used.healths.maptext = "
[round((health / maxHealth) * 100, 0.5)]%" + +/mob/living/simple_animal/hostile/clockwork/marauder/guardian/proc/update_stats() + if(GLOB.ratvar_awakens) + speed = 0 + melee_damage_lower = 20 + melee_damage_upper = 20 + attacktext = "devastates" + else + var/healthpercent = (health/maxHealth) * 100 + switch(healthpercent) + if(100 to 70) //Bonuses to speed and damage at high health + speed = 0 + melee_damage_lower = 16 + melee_damage_upper = 16 + attacktext = "viciously slashes" + if(70 to 40) + speed = initial(speed) + melee_damage_lower = initial(melee_damage_lower) + melee_damage_upper = initial(melee_damage_upper) + attacktext = initial(attacktext) + if(40 to 30) //Damage decrease, but not speed + speed = initial(speed) + melee_damage_lower = 10 + melee_damage_upper = 10 + attacktext = "lightly slashes" + if(30 to 20) //Speed decrease + speed = 2 + melee_damage_lower = 8 + melee_damage_upper = 8 + attacktext = "lightly slashes" + if(20 to 10) //Massive speed decrease and weak melee attacks + speed = 3 + melee_damage_lower = 6 + melee_damage_upper = 6 + attacktext = "weakly slashes" + if(10 to 0) //We are super weak and going to die + speed = 4 + melee_damage_lower = 4 + melee_damage_upper = 4 + attacktext = "taps" + +//ATTACKING, BLOCKING, and COUNTERING + +/mob/living/simple_animal/hostile/clockwork/marauder/guardian/AttackingTarget() + if(is_in_host()) + return FALSE + return ..() + +/mob/living/simple_animal/hostile/clockwork/marauder/guardian/bullet_act(obj/item/projectile/Proj) + if(blockOrCounter(null, Proj)) + return + return ..() + +/mob/living/simple_animal/hostile/clockwork/marauder/guardian/hitby(atom/movable/AM, skipcatch, hitpush, blocked) + if(blockOrCounter(null, AM)) + return + return ..() + +/mob/living/simple_animal/hostile/clockwork/marauder/guardian/attack_animal(mob/living/simple_animal/M) + if(istype(M, /mob/living/simple_animal/hostile/clockwork/marauder/guardian) || !blockOrCounter(M, M)) //we don't want infinite blockcounter loops if fighting another guardian + return ..() + +/mob/living/simple_animal/hostile/clockwork/marauder/guardian/attack_paw(mob/living/carbon/monkey/M) + if(!blockOrCounter(M, M)) + return ..() + +/mob/living/simple_animal/hostile/clockwork/marauder/guardian/attack_alien(mob/living/carbon/alien/humanoid/M) + if(!blockOrCounter(M, M)) + return ..() + +/mob/living/simple_animal/hostile/clockwork/marauder/guardian/attack_slime(mob/living/simple_animal/slime/M) + if(!blockOrCounter(M, M)) + return ..() + +/mob/living/simple_animal/hostile/clockwork/marauder/guardian/attack_hand(mob/living/carbon/human/M) + if(!blockOrCounter(M, M)) + return ..() + +/mob/living/simple_animal/hostile/clockwork/marauder/guardian/attackby(obj/item/I, mob/user, params) + if(istype(I, /obj/item/nullrod) || !blockOrCounter(user, I)) + return ..() + +/mob/living/simple_animal/hostile/clockwork/marauder/guardian/proc/blockOrCounter(mob/target, atom/textobject) + if(GLOB.ratvar_awakens) //if ratvar has woken, we block nearly everything at a very high chance + blockchance = 90 + counterchance = 90 + if(prob(blockchance)) + . = TRUE + if(target) + target.do_attack_animation(src) + target.changeNext_move(CLICK_CD_MELEE) + blockchance = initial(blockchance) + playsound(src, 'sound/magic/clockwork/fellowship_armory.ogg', 30, 1, 0, 1) //clang + visible_message("[src] blocks [target && isitem(textobject) ? "[target]'s [textobject.name]":"\the [textobject]"]!", \ + "You block [target && isitem(textobject) ? "[target]'s [textobject.name]":"\the [textobject]"]!") + if(target && Adjacent(target)) + if(prob(counterchance)) + counterchance = initial(counterchance) + var/previousattacktext = attacktext + attacktext = "counters" + UnarmedAttack(target) + attacktext = previousattacktext + else + counterchance = min(counterchance + initial(counterchance), 100) + else + blockchance = min(blockchance + initial(blockchance), 100) + if(GLOB.ratvar_awakens) + blockchance = 90 + counterchance = 90 + +//COMMUNICATION and EMERGENCE + +/mob/living/simple_animal/hostile/clockwork/marauder/guardian/handle_inherent_channels(message, message_mode) + if(host && (is_in_host() || message_mode == MODE_BINARY)) + guardian_comms(message) + return TRUE + return ..() + +/mob/living/simple_animal/hostile/clockwork/marauder/guardian/proc/guardian_comms(message) + var/name_part = "[src] ([true_name])" + message = "\"[message]\"" //Processed output + to_chat(src, "[name_part]: [message]") + to_chat(host, "[name_part]: [message]") + for(var/M in GLOB.mob_list) + if(isobserver(M)) + var/link = FOLLOW_LINK(M, src) + to_chat(M, "[link] [name_part] (to [findtextEx(host.name, host.real_name) ? "[host.name]" : "[host.real_name] (as [host.name])"]): [message] ") + return TRUE + +/mob/living/simple_animal/hostile/clockwork/marauder/guardian/proc/return_to_host() + if(is_in_host()) + return FALSE + if(!host) + to_chat(src, "You don't have a host!") + return FALSE + var/resulthealth = round((host.health / host.maxHealth) * 100, 0.5) + if(iscarbon(host)) + resulthealth = round((abs(HEALTH_THRESHOLD_DEAD - host.health) / abs(HEALTH_THRESHOLD_DEAD - host.maxHealth)) * 100) + host.visible_message("[host]'s skin flashes crimson!", "You feel [true_name]'s consciousness settle in your mind.") + visible_message("[src] suddenly disappears!", "You return to [host].") + forceMove(host) + if(resulthealth > GUARDIAN_EMERGE_THRESHOLD && health != maxHealth) + recovering = TRUE + to_chat(src, "You have weakened and will need to recover before manifesting again!") + to_chat(host, "[true_name] has weakened and will need to recover before manifesting again!") + return TRUE + +/mob/living/simple_animal/hostile/clockwork/marauder/guardian/proc/try_emerge() + if(!host) + to_chat(src, "You don't have a host!") + return FALSE + if(!GLOB.ratvar_awakens) + var/resulthealth = round((host.health / host.maxHealth) * 100, 0.5) + if(iscarbon(host)) + resulthealth = round((abs(HEALTH_THRESHOLD_DEAD - host.health) / abs(HEALTH_THRESHOLD_DEAD - host.maxHealth)) * 100) + if(host.stat != DEAD && resulthealth > GUARDIAN_EMERGE_THRESHOLD) //if above 20 health, fails + to_chat(src, "Your host must be at [GUARDIAN_EMERGE_THRESHOLD]% or less health to emerge like this!") + return FALSE + return emerge_from_host(FALSE) + +/mob/living/simple_animal/hostile/clockwork/marauder/guardian/proc/emerge_from_host(hostchosen, force) //Notice that this is a proc rather than a verb - guardians can NOT exit at will, but they CAN return + if(!is_in_host()) + return FALSE + if(!force && recovering) + if(hostchosen) + to_chat(host, "[true_name] is too weak to come forth!") + else + to_chat(host, "[true_name] tries to emerge to protect you, but it's too weak!") + to_chat(src, "You try to come forth, but you're too weak!") + return FALSE + if(!force) + if(hostchosen) //guardian approved + to_chat(host, "Your words echo with power as [true_name] emerges from your body!") + else + to_chat(host, "[true_name] emerges from your body to protect you!") + forceMove(host.loc) + visible_message("[host]'s skin glows red as [name] emerges from their body!", "You exit the safety of [host]'s body!") + return TRUE + +/mob/living/simple_animal/hostile/clockwork/marauder/guardian/get_alt_name() + return " ([text2ratvar(true_name)])" + +/mob/living/simple_animal/hostile/clockwork/marauder/guardian/proc/is_in_host() //Checks if the guardian is inside of their host + return host && loc == host + +//HOST ACTIONS + +//Summon guardian action: Calls forth or recalls your guardian +/datum/action/innate/summon_marauder + name = "Force Guardian to Emerge/Recall" + desc = "Allows you to force your clockwork guardian to emerge or recall as required." + button_icon_state = "clockwork_marauder" + background_icon_state = "bg_clock" + check_flags = AB_CHECK_CONSCIOUS + buttontooltipstyle = "clockcult" + var/mob/living/simple_animal/hostile/clockwork/marauder/guardian/linked_guardian + var/static/list/defend_phrases = list("Defend me", "Come forth", "Assist me", "Protect me", "Give aid", "Help me") + var/static/list/return_phrases = list("Return", "Return to me", "Your job is done", "You have served", "Come back", "Retreat") + +/datum/action/innate/summon_marauder/IsAvailable() + if(!linked_guardian) + return FALSE + if(isliving(owner)) + var/mob/living/L = owner + if(!L.can_speak_vocal() || L.stat) + return FALSE + return ..() + +/datum/action/innate/summon_guardian/Activate() + if(linked_guardian.is_in_host()) + clockwork_say(owner, text2ratvar("[pick(defend_phrases)], [linked_guardian.true_name]!")) + linked_guardian.emerge_from_host(TRUE) + else + clockwork_say(owner, text2ratvar("[pick(return_phrases)], [linked_guardian.true_name]!")) + linked_guardian.return_to_host() + return TRUE + +//Linked Minds action: talks to your guardian +/datum/action/innate/linked_minds + name = "Linked Minds" + desc = "Allows you to silently communicate with your guardian." + button_icon_state = "linked_minds" + background_icon_state = "bg_clock" + check_flags = AB_CHECK_CONSCIOUS + buttontooltipstyle = "clockcult" + var/mob/living/simple_animal/hostile/clockwork/marauder/guardian/linked_guardian + +/datum/action/innate/linked_minds/IsAvailable() + if(!linked_guardian) + return FALSE + return ..() + +/datum/action/innate/linked_minds/Activate() + var/message = stripped_input(owner, "Enter a message to tell your guardian.", "Telepathy") + if(!owner || !message) + return FALSE + if(!linked_guardian) + to_chat(owner, "Your guardian seems to have been destroyed!") + return FALSE + var/name_part = "Servant [findtextEx(owner.name, owner.real_name) ? "[owner.name]" : "[owner.real_name] (as [owner.name])"]" + message = "\"[message]\"" //Processed output + to_chat(owner, "[name_part]: [message]") + to_chat(linked_guardian, "[name_part]: [message]") + for(var/M in GLOB.mob_list) + if(isobserver(M)) + var/link = FOLLOW_LINK(M, src) + to_chat(M, "[link] [name_part] (to [linked_guardian] ([linked_guardian.true_name])): [message]") + return TRUE +*/ \ No newline at end of file diff --git a/code/modules/antagonists/clockcult/clock_scriptures/scripture_applications.dm b/code/modules/antagonists/clockcult/clock_scriptures/scripture_applications.dm index ffe9ecfa80..a284261045 100644 --- a/code/modules/antagonists/clockcult/clock_scriptures/scripture_applications.dm +++ b/code/modules/antagonists/clockcult/clock_scriptures/scripture_applications.dm @@ -23,6 +23,61 @@ quickbind = TRUE quickbind_desc = "Creates a Sigil of Transmission, which can drain and will store power for clockwork structures." +/*//Prolonging Prism: Creates a prism that will delay the shuttle at a power cost +/datum/clockwork_scripture/create_object/prolonging_prism + descname = "Powered Structure, Delay Emergency Shuttles" + name = "Prolonging Prism" + desc = "Creates a mechanized prism which will delay the arrival of an emergency shuttle by 2 minutes at a massive power cost." + invocations = list("May this prism...", "...grant us time to enact his will!") + channel_time = 80 + consumed_components = list(VANGUARD_COGWHEEL = 5, GEIS_CAPACITOR = 2, REPLICANT_ALLOY = 2) + object_path = /obj/structure/destructible/clockwork/powered/prolonging_prism + creator_message = "You form a prolonging prism, which will delay the arrival of an emergency shuttle at a massive power cost." + observer_message = "An onyx prism forms in midair and sprouts tendrils to support itself!" + invokers_required = 2 + multiple_invokers_used = TRUE + usage_tip = "The power cost to delay a shuttle increases based on the number of times activated." + tier = SCRIPTURE_APPLICATION + one_per_tile = TRUE + primary_component = VANGUARD_COGWHEEL + sort_priority = 7 + quickbind = TRUE + quickbind_desc = "Creates a Prolonging Prism, which will delay the arrival of an emergency shuttle by 2 minutes at a massive power cost." + +/datum/clockwork_scripture/create_object/prolonging_prism/check_special_requirements() + if(SSshuttle.emergency.mode == SHUTTLE_DOCKED || SSshuttle.emergency.mode == SHUTTLE_IGNITING || SSshuttle.emergency.mode == SHUTTLE_STRANDED || SSshuttle.emergency.mode == SHUTTLE_ESCAPE) + to_chat(invoker, "\"It is too late to construct one of these, champion.\"") + return FALSE + var/turf/T = get_turf(invoker) + if(!T || !(T.z in GLOB.station_z_levels)) + to_chat(invoker, "\"You must be on the station to construct one of these, champion.\"") + return FALSE + return ..() + + + +//Tinkerer's Daemon: Creates an efficient machine that rapidly produces components at a power cost. +/datum/clockwork_scripture/create_object/tinkerers_daemon + descname = "Powered Structure, Component Generator" + name = "Tinkerer's Daemon" + desc = "Creates a tinkerer's daemon which can rapidly collect components. It will only function if it has sufficient power, active daemons are outnumbered by Servants by a ratio of 5:1, \ + and there is at least one existing cache." + invocations = list("May this generator...", "...collect Engine parts that yet hold greatness!") + channel_time = 80 + consumed_components = list(BELLIGERENT_EYE = 2, GEIS_CAPACITOR = 2, REPLICANT_ALLOY = 5) + object_path = /obj/structure/destructible/clockwork/powered/tinkerers_daemon + creator_message = "You form a tinkerer's daemon which can rapidly collect components at a power cost." + invokers_required = 2 + multiple_invokers_used = TRUE + usage_tip = "Vital to your success!" + tier = SCRIPTURE_APPLICATION + one_per_tile = TRUE + primary_component = REPLICANT_ALLOY + sort_priority = 9 + quickbind = TRUE + quickbind_desc = "Creates a Tinkerer's Daemon, which can rapidly collect components for power." + +*/ //Mania Motor: Creates a malevolent transmitter that will broadcast the whispers of Sevtug into the minds of nearby nonservants, causing a variety of mental effects at a power cost. /datum/clockwork_scripture/create_object/mania_motor @@ -67,7 +122,67 @@ quickbind = TRUE quickbind_desc = "Creates a Clockwork Obelisk, which can send messages or open Spatial Gateways with power." +/* +//Memory Allocation: Finds a willing ghost and makes them into a clockwork marauders for the invoker. +/datum/clockwork_scripture/memory_allocation + descname = "Guardian" + name = "Memory Allocation" + desc = "Allocates part of your consciousness to a Clockwork Marauder, a vigilant fighter that lives within you, able to be \ + called forth by Speaking its True Name or if you become exceptionally low on health.
\ + If it remains close to you, you will gradually regain health up to a low amount, but it will die if it goes too far from you." + invocations = list("Fright's will...", "...call forth...") + channel_time = 100 + consumed_components = list(BELLIGERENT_EYE = 2, VANGUARD_COGWHEEL = 2, GEIS_CAPACITOR = 4) + usage_tip = "Marauders are useful as personal bodyguards and frontline warriors." + tier = SCRIPTURE_APPLICATION + primary_component = GEIS_CAPACITOR + sort_priority = 3 +/datum/clockwork_scripture/memory_allocation/check_special_requirements() + for(var/mob/living/simple_animal/hostile/clockwork/marauder/M in GLOB.all_clockwork_mobs) + if(M.host == invoker) + to_chat(invoker, "You can only house one marauder at a time!") + return FALSE + return TRUE + +/datum/clockwork_scripture/memory_allocation/scripture_effects() + return create_marauder() + +/datum/clockwork_scripture/memory_allocation/proc/create_marauder() + invoker.visible_message("A purple tendril appears from [invoker]'s [slab.name] and impales itself in [invoker.p_their()] forehead!", \ + "A tendril flies from [slab] into your forehead. You begin waiting while it painfully rearranges your thought pattern...") + invoker.notransform = TRUE //Vulnerable during the process + slab.busy = "Thought Modification in progress" + if(!do_after(invoker, 50, target = invoker)) + invoker.visible_message("The tendril, covered in blood, retracts from [invoker]'s head and back into the [slab.name]!", \ + "Total agony overcomes you as the tendril is forced out early!") + invoker.notransform = FALSE + invoker.Knockdown(100) + invoker.apply_damage(10, BRUTE, "head") + slab.busy = null + return FALSE + clockwork_say(invoker, text2ratvar("...the mind made...")) + invoker.notransform = FALSE + slab.busy = "Marauder Selection in progress" + if(!check_special_requirements()) + return FALSE + to_chat(invoker, "The tendril shivers slightly as it selects a marauder...") + var/list/marauder_candidates = pollGhostCandidates("Do you want to play as the clockwork marauder of [invoker.real_name]?", ROLE_SERVANT_OF_RATVAR, null, FALSE, 50, POLL_IGNORE_CLOCKWORK_MARAUDER) + if(!check_special_requirements()) + return FALSE + if(!marauder_candidates.len) + invoker.visible_message("The tendril retracts from [invoker]'s head, sealing the entry wound as it does so!", \ + "The tendril was unsuccessful! Perhaps you should try again another time.") + return FALSE + clockwork_say(invoker, text2ratvar("...sword and shield!")) + var/mob/dead/observer/theghost = pick(marauder_candidates) + var/mob/living/simple_animal/hostile/clockwork/marauder/M = new(invoker) + M.key = theghost.key + M.bind_to_host(invoker) + invoker.visible_message("The tendril retracts from [invoker]'s head, sealing the entry wound as it does so!", \ + "[M.true_name], a clockwork marauder, has taken up residence in your mind. Communicate with it via the \"Linked Minds\" action button.") + return TRUE +*/ //Clockwork Marauder: Creates a construct shell for a clockwork marauder, a well-rounded frontline fighter. /datum/clockwork_scripture/create_object/construct/clockwork_marauder descname = "Well-Rounded Combat Construct" diff --git a/code/modules/antagonists/clockcult/clock_scriptures/scripture_drivers.dm b/code/modules/antagonists/clockcult/clock_scriptures/scripture_drivers.dm index 6349ecb581..864187b903 100644 --- a/code/modules/antagonists/clockcult/clock_scriptures/scripture_drivers.dm +++ b/code/modules/antagonists/clockcult/clock_scriptures/scripture_drivers.dm @@ -113,6 +113,40 @@ quickbind = TRUE quickbind_desc = "Stuns and mutes a target from a short range." +/* +//Geis: Grants a short-range binding attack that allows you to mute and drag around a target in a very obvious manner. +/datum/clockwork_scripture/ranged_ability/geis + descname = "Melee Mute & Stun" + name = "Geis" + desc = "Charges your slab with divine energy, allowing you to bind and pull a struck heretic." + invocations = list("Divinity, grant me strength...", "...to bind the heathen!") + whispered = TRUE + channel_time = 20 + usage_tip = "You CANNOT TAKE ANY NON-PULL ACTIONS while the target is bound, so Sigils of Submission should be placed before use." + tier = SCRIPTURE_DRIVER + primary_component = GEIS_CAPACITOR + sort_priority = 5 + quickbind = TRUE + quickbind_desc = "Allows you to bind and mute an adjacent target non-Servant.
Click your slab to disable." + slab_overlay = "geis" + ranged_type = /obj/effect/proc_holder/slab/geis + ranged_message = "You charge the clockwork slab with divine energy.\n\ + Left-click a target within melee range to bind!\n\ + Click your slab to cancel." + timeout_time = 100 + +/datum/clockwork_scripture/ranged_ability/geis/run_scripture() + var/servants = 0 + if(!GLOB.ratvar_awakens) + for(var/mob/living/M in GLOB.living_mob_list) + if(can_recite_scripture(M, TRUE)) + servants++ + if(servants > SCRIPT_SERVANT_REQ) + whispered = FALSE + servants -= SCRIPT_SERVANT_REQ + channel_time = min(channel_time + servants*3, 50) + return ..() +*/ //Hateful Manacles: Applies restraints from melee over several seconds. The restraints function like handcuffs and break on removal. /datum/clockwork_scripture/ranged_ability/hateful_manacles @@ -293,3 +327,25 @@ sort_priority = 11 quickbind = TRUE quickbind_desc = "Creates a pair of Wraith Spectacles, which grant true sight but cause gradual vision loss." +/* +//Tinkerer's Cache: Creates a tinkerer's cache, allowing global component storage. +/datum/clockwork_scripture/create_object/tinkerers_cache + descname = "Necessary Structure, Shares Components" + name = "Tinkerer's Cache" + desc = "Forms a cache that can store an infinite amount of components. All caches are linked and will provide components to slabs. \ + Striking a cache with a slab will transfer that slab's components to the global cache." + invocations = list("Constructing...", "...a cache!") + channel_time = 50 + consumed_components = list(BELLIGERENT_EYE = 0, VANGUARD_COGWHEEL = 0, GEIS_CAPACITOR = 0, REPLICANT_ALLOY = 1, HIEROPHANT_ANSIBLE = 0) + object_path = /obj/structure/destructible/clockwork/cache + creator_message = "You form a tinkerer's cache, which is capable of storing components, which will automatically be used by slabs." + observer_message = "A hollow brass spire rises and begins to blaze!" + usage_tip = "Slabs will draw components from the global cache after the slab's own repositories, making caches extremely useful." + tier = SCRIPTURE_DRIVER + one_per_tile = TRUE + primary_component = REPLICANT_ALLOY + sort_priority = 8 + quickbind = TRUE + quickbind_desc = "Creates a Tinkerer's Cache, which stores components globally for slab access." + var/static/prev_cost = 0 + */ diff --git a/code/modules/antagonists/clockcult/clock_scriptures/scripture_judgement.dm b/code/modules/antagonists/clockcult/clock_scriptures/scripture_judgement.dm new file mode 100644 index 0000000000..0a8e717b5d --- /dev/null +++ b/code/modules/antagonists/clockcult/clock_scriptures/scripture_judgement.dm @@ -0,0 +1,43 @@ +/////////////// +// JUDGEMENT // +/////////////// + +//Ark of the Clockwork Justiciar: Creates a Gateway to the Celestial Derelict, summoning ratvar. +/datum/clockwork_scripture/create_object/ark_of_the_clockwork_justiciar + descname = "Structure, Win Condition" + name = "Ark of the Clockwork Justiciar" + desc = "Tears apart a rift in spacetime to Reebe, the Celestial Derelict, using a massive amount of components.\n\ + This gateway will, after some time, call forth Ratvar from his exile and massively empower all scriptures and tools." + invocations = list("ARMORER! FRIGHT! AMPERAGE! VANGUARD! I CALL UPON YOU!!", \ + "THE TIME HAS COME FOR OUR MASTER TO BREAK THE CHAINS OF EXILE!!", \ + "LEND US YOUR AID! ENGINE COMES!!") + channel_time = 150 + consumed_components = list(BELLIGERENT_EYE = ARK_SUMMON_COST, VANGUARD_COGWHEEL = ARK_SUMMON_COST, GEIS_CAPACITOR = ARK_SUMMON_COST, REPLICANT_ALLOY = ARK_SUMMON_COST, HIEROPHANT_ANSIBLE = ARK_SUMMON_COST) + invokers_required = 6 + multiple_invokers_used = TRUE + object_path = /obj/structure/destructible/clockwork/massive/celestial_gateway + creator_message = null + usage_tip = "The gateway is completely vulnerable to attack during its five-minute duration. It will periodically give indication of its general position to everyone on the station \ + as well as being loud enough to be heard throughout the entire sector. Defend it with your life!" + tier = SCRIPTURE_JUDGEMENT + sort_priority = 1 + +/datum/clockwork_scripture/create_object/ark_of_the_clockwork_justiciar/check_special_requirements() + if(!slab.no_cost) + if(GLOB.ratvar_awakens) + to_chat(invoker, "\"I am already here, idiot.\"") + return FALSE + for(var/obj/structure/destructible/clockwork/massive/celestial_gateway/G in GLOB.all_clockwork_objects) + var/area/gate_area = get_area(G) + to_chat(invoker, "There is already an Ark at [gate_area.map_name]!") + return FALSE + var/area/A = get_area(invoker) + var/turf/T = get_turf(invoker) + if(!T || !(T.z in GLOB.station_z_levels) || istype(A, /area/shuttle) || !A.blob_allowed) + to_chat(invoker, "You must be on the station to activate the Ark!") + return FALSE + if(GLOB.clockwork_gateway_activated) + to_chat(invoker, "Ratvar's recent banishment renders him too weak to be wrung forth from Reebe!") + return FALSE + return ..() + diff --git a/code/modules/antagonists/clockcult/clock_scriptures/scripture_scripts.dm b/code/modules/antagonists/clockcult/clock_scriptures/scripture_scripts.dm index d22a2f69b7..6ac3151c49 100644 --- a/code/modules/antagonists/clockcult/clock_scriptures/scripture_scripts.dm +++ b/code/modules/antagonists/clockcult/clock_scriptures/scripture_scripts.dm @@ -487,3 +487,27 @@ invoker.light_range = 0 invoker.update_light() return ..() + +/*//Belligerent: Channeled for up to fifteen times over thirty seconds. Forces non-servants that can hear the chant to walk, doing minor damage. Nar-Sian cultists are burned. +/datum/clockwork_scripture/channeled/belligerent + descname = "Channeled, Area Slowdown" + name = "Belligerent" + desc = "Forces all nearby non-servants to walk rather than run, doing minor damage. Chanted every two seconds for up to thirty seconds." + chant_invocations = list("Punish their blindness!", "Take time, make slow!") + chant_amount = 15 + chant_interval = 20 + channel_time = 20 + power_cost = 300 + usage_tip = "Useful for crowd control in a populated area and disrupting mass movement." + tier = SCRIPTURE_DRIVER + primary_component = BELLIGERENT_EYE + sort_priority = 1 + quickbind = TRUE + quickbind_desc = "Forces nearby non-Servants to walk, doing minor damage with each chant.
Maximum 15 chants." + +/datum/clockwork_scripture/channeled/belligerent/chant_effects(chant_number) + for(var/mob/living/carbon/C in hearers(7, invoker)) + C.apply_status_effect(STATUS_EFFECT_BELLIGERENT) + new /obj/effect/temp_visual/ratvar/belligerent(get_turf(invoker)) + return TRUE + */ \ No newline at end of file diff --git a/code/modules/antagonists/clockcult/clock_structures/prolonging_prism.dm b/code/modules/antagonists/clockcult/clock_structures/prolonging_prism.dm new file mode 100644 index 0000000000..155413a612 --- /dev/null +++ b/code/modules/antagonists/clockcult/clock_structures/prolonging_prism.dm @@ -0,0 +1,135 @@ +//Prolonging Prism: A prism that consumes power to delay the shuttle +/obj/structure/destructible/clockwork/powered/prolonging_prism + name = "prolonging prism" + desc = "A dark onyx prism, held in midair by spiraling tendrils of stone." + clockwork_desc = "A powerful prism that will delay the arrival of an emergency shuttle." + icon_state = "prolonging_prism_inactive" + active_icon = "prolonging_prism" + inactive_icon = "prolonging_prism_inactive" + unanchored_icon = "prolonging_prism_unwrenched" + construction_value = 20 + max_integrity = 125 + break_message = "The prism falls to the ground with a heavy thud!" + debris = list(/obj/item/clockwork/alloy_shards/small = 3, \ + /obj/item/clockwork/alloy_shards/medium = 1, \ + /obj/item/clockwork/alloy_shards/large = 1, \ + /obj/item/clockwork/component/vanguard_cogwheel/onyx_prism = 1) + var/static/list/component_refund = list(VANGUARD_COGWHEEL = 2, GEIS_CAPACITOR = 1, REPLICANT_ALLOY = 1) + var/static/delay_cost = 3000 + var/static/delay_cost_increase = 1250 + var/static/delay_remaining = 0 + +/obj/structure/destructible/clockwork/powered/prolonging_prism/examine(mob/user) + ..() + if(is_servant_of_ratvar(user) || isobserver(user)) + if(SSshuttle.emergency.mode == SHUTTLE_DOCKED || SSshuttle.emergency.mode == SHUTTLE_IGNITING || SSshuttle.emergency.mode == SHUTTLE_STRANDED || SSshuttle.emergency.mode == SHUTTLE_ESCAPE) + to_chat(user, "An emergency shuttle has arrived and this prism is no longer useful; attempt to activate it to gain a partial refund of components used.") + else + var/efficiency = get_efficiency_mod(TRUE) + to_chat(user, "It requires at least [DisplayPower(get_delay_cost())] of power to attempt to delay the arrival of an emergency shuttle by [2 * efficiency] minutes.") + to_chat(user, "This cost increases by [DisplayPower(delay_cost_increase)] for every previous activation.") + +/obj/structure/destructible/clockwork/powered/prolonging_prism/forced_disable(bad_effects) + if(active) + if(bad_effects) + try_use_power(MIN_CLOCKCULT_POWER*4) + visible_message("[src] emits an airy chuckling sound and falls dark!") + toggle() + return TRUE + +/obj/structure/destructible/clockwork/powered/prolonging_prism/attack_hand(mob/living/user) + if(user.canUseTopic(src, !issilicon(user), NO_DEXTERY) && is_servant_of_ratvar(user)) + if(SSshuttle.emergency.mode == SHUTTLE_DOCKED || SSshuttle.emergency.mode == SHUTTLE_IGNITING || SSshuttle.emergency.mode == SHUTTLE_STRANDED || SSshuttle.emergency.mode == SHUTTLE_ESCAPE) + to_chat(user, "You break [src] apart, refunding some of the components used.") + for(var/i in component_refund) + generate_cache_component(i, src) + take_damage(max_integrity) + return 0 + if(active) + return 0 + var/turf/T = get_turf(src) + if(!T || !(T.z in GLOB.station_z_levels)) + to_chat(user, "[src] must be on the station to function!") + return 0 + if(SSshuttle.emergency.mode != SHUTTLE_CALL) + to_chat(user, "No emergency shuttles are attempting to arrive at the station!") + return 0 + if(!try_use_power(get_delay_cost())) + to_chat(user, "[src] needs more power to function!") + return 0 + delay_cost += delay_cost_increase + delay_remaining += PRISM_DELAY_DURATION + toggle(0, user) + +/obj/structure/destructible/clockwork/powered/prolonging_prism/process() + var/turf/own_turf = get_turf(src) + if(SSshuttle.emergency.mode != SHUTTLE_CALL || delay_remaining <= 0 || !own_turf || !(own_turf.z in GLOB.station_z_levels)) + forced_disable(FALSE) + return + . = ..() + var/delay_amount = 40 + delay_remaining -= delay_amount + var/efficiency = get_efficiency_mod() + SSshuttle.emergency.setTimer(SSshuttle.emergency.timeLeft(1) + (delay_amount * efficiency)) + var/highest_y + var/highest_x + var/lowest_y + var/lowest_x + var/list/prism_turfs = list() + for(var/t in SSshuttle.emergency.ripple_area(SSshuttle.getDock("emergency_home"))) + prism_turfs[t] = TRUE + var/turf/T = t + if(!highest_y || T.y > highest_y) + highest_y = T.y + if(!highest_x || T.x > highest_x) + highest_x = T.x + if(!lowest_y || T.y < lowest_y) + lowest_y = T.y + if(!lowest_x || T.x < lowest_x) + lowest_x = T.x + var/mean_y = Lerp(lowest_y, highest_y) + var/mean_x = Lerp(lowest_x, highest_x) + if(prob(50)) + mean_y = Ceiling(mean_y) + else + mean_y = Floor(mean_y) + if(prob(50)) + mean_x = Ceiling(mean_x) + else + mean_x = Floor(mean_x) + var/turf/semi_random_center_turf = locate(mean_x, mean_y, ZLEVEL_STATION_PRIMARY) + for(var/t in getline(src, semi_random_center_turf)) + prism_turfs[t] = TRUE + var/placement_style = prob(50) + for(var/t in prism_turfs) + var/turf/T = t + if(placement_style) + if(IsOdd(T.x + T.y)) + seven_random_hexes(T, efficiency) + else if(prob(50 * efficiency)) + new /obj/effect/temp_visual/ratvar/prolonging_prism(T) + else + if(IsEven(T.x + T.y)) + seven_random_hexes(T, efficiency) + else if(prob(50 * efficiency)) + new /obj/effect/temp_visual/ratvar/prolonging_prism(T) + CHECK_TICK //we may be going over a hell of a lot of turfs + +/obj/structure/destructible/clockwork/powered/prolonging_prism/proc/get_delay_cost() + return Floor(delay_cost, MIN_CLOCKCULT_POWER) + +/obj/structure/destructible/clockwork/powered/prolonging_prism/proc/seven_random_hexes(turf/T, efficiency) + var/static/list/hex_states = list("prismhex1", "prismhex2", "prismhex3", "prismhex4", "prismhex5", "prismhex6", "prismhex7") + var/mutable_appearance/hex_combo + for(var/n in hex_states) //BUILD ME A HEXAGON + if(prob(50 * efficiency)) + if(!hex_combo) + hex_combo = mutable_appearance('icons/effects/64x64.dmi', n, RIPPLE_LAYER) + else + hex_combo.add_overlay(mutable_appearance('icons/effects/64x64.dmi', n, RIPPLE_LAYER)) + if(hex_combo) //YOU BUILT A HEXAGON + hex_combo.pixel_x = -16 + hex_combo.pixel_y = -16 + hex_combo.mouse_opacity = MOUSE_OPACITY_TRANSPARENT + hex_combo.plane = GAME_PLANE + new /obj/effect/temp_visual/ratvar/prolonging_prism(T, hex_combo) diff --git a/code/modules/antagonists/clockcult/clock_structures/tinkerers_cache.dm b/code/modules/antagonists/clockcult/clock_structures/tinkerers_cache.dm new file mode 100644 index 0000000000..cd33cf7adc --- /dev/null +++ b/code/modules/antagonists/clockcult/clock_structures/tinkerers_cache.dm @@ -0,0 +1,116 @@ +//Tinkerer's cache: Stores components for later use. +/obj/structure/destructible/clockwork/cache + name = "tinkerer's cache" + desc = "A large brass spire with a flaming hole in its center." + clockwork_desc = "A brass container capable of storing a large amount of components.\n\ + Shares components with all other caches and will gradually generate components if near a Clockwork Wall." + icon_state = "tinkerers_cache" + unanchored_icon = "tinkerers_cache_unwrenched" + construction_value = 10 + break_message = "The cache's fire winks out before it falls in on itself!" + max_integrity = 80 + light_color = "#C2852F" + var/wall_generation_cooldown + var/turf/closed/wall/clockwork/linkedwall //if we've got a linked wall and are producing + var/static/linked_caches = 0 //how many caches are linked to walls; affects how fast components are produced + +/obj/structure/destructible/clockwork/cache/Initialize() + . = ..() + START_PROCESSING(SSobj, src) + GLOB.clockwork_caches++ + update_slab_info() + set_light(2, 0.7) + +/obj/structure/destructible/clockwork/cache/Destroy() + GLOB.clockwork_caches-- + update_slab_info() + STOP_PROCESSING(SSobj, src) + if(linkedwall) + linked_caches-- + linkedwall.linkedcache = null + linkedwall = null + return ..() + +/obj/structure/destructible/clockwork/cache/process() + if(!anchored) + if(linkedwall) + linked_caches-- + linkedwall.linkedcache = null + linkedwall = null + return + for(var/turf/closed/wall/clockwork/C in range(4, src)) + if(!C.linkedcache && !linkedwall) + linked_caches++ + C.linkedcache = src + linkedwall = C + wall_generation_cooldown = world.time + get_production_time() + visible_message("[src] starts to whirr in the presence of [C]...") + break + if(linkedwall && wall_generation_cooldown <= world.time) + wall_generation_cooldown = world.time + get_production_time() + var/component_id = generate_cache_component(null, src) + playsound(linkedwall, 'sound/magic/clockwork/fellowship_armory.ogg', rand(15, 20), 1, -3, 1, 1) + visible_message("Something cl[pick("ank", "ink", "unk", "ang")]s around inside of [src]...") + +/obj/structure/destructible/clockwork/cache/attackby(obj/item/I, mob/living/user, params) + if(!is_servant_of_ratvar(user)) + return ..() + if(istype(I, /obj/item/clockwork/component)) + var/obj/item/clockwork/component/C = I + if(!anchored) + to_chat(user, "[src] needs to be secured to place [C] into it!") + else + GLOB.clockwork_component_cache[C.component_id]++ + update_slab_info() + to_chat(user, "You add [C] to [src].") + user.drop_item() + qdel(C) + return 1 + else if(istype(I, /obj/item/clockwork/slab)) + var/obj/item/clockwork/slab/S = I + if(!anchored) + to_chat(user, "[src] needs to be secured to offload your slab's components into it!") + else + for(var/i in S.stored_components) + GLOB.clockwork_component_cache[i] += S.stored_components[i] + S.stored_components[i] = 0 + update_slab_info() + user.visible_message("[user] empties [S] into [src].", "You offload your slab's components into [src].") + return 1 + else + return ..() + +/obj/structure/destructible/clockwork/cache/update_anchored(mob/user, do_damage) + ..() + if(anchored) + set_light(2, 0.7) + else + set_light(0) + +/obj/structure/destructible/clockwork/cache/attack_hand(mob/living/user) + ..() + if(is_servant_of_ratvar(user)) + if(linkedwall) + if(wall_generation_cooldown > world.time) + var/temp_time = (wall_generation_cooldown - world.time) * 0.1 + to_chat(user, "[src] will produce a component in [temp_time] second[temp_time == 1 ? "":"s"].") + else + to_chat(user, "[src] is about to produce a component!") + else if(anchored) + to_chat(user, "[src] is unlinked! Construct a Clockwork Wall nearby to generate components!") + else + to_chat(user, "[src] needs to be secured to generate components!") + +/obj/structure/destructible/clockwork/cache/examine(mob/user) + ..() + if(is_servant_of_ratvar(user) || isobserver(user)) + if(linkedwall) + to_chat(user, "It is linked to a Clockwork Wall and will generate a component every [DisplayTimeText(get_production_time())]!") + else + to_chat(user, "It is unlinked! Construct a Clockwork Wall nearby to generate components!") + to_chat(user, "Stored components:") + for(var/i in GLOB.clockwork_component_cache) + to_chat(user, "[get_component_icon(i)] [get_component_name(i)][i != REPLICANT_ALLOY ? "s":""]: [GLOB.clockwork_component_cache[i]]") + +/obj/structure/destructible/clockwork/cache/proc/get_production_time() + return (CACHE_PRODUCTION_TIME + (ACTIVE_CACHE_SLOWDOWN * linked_caches)) * get_efficiency_mod(TRUE) diff --git a/code/modules/antagonists/clockcult/clock_structures/tinkerers_daemon.dm b/code/modules/antagonists/clockcult/clock_structures/tinkerers_daemon.dm new file mode 100644 index 0000000000..4ceb7c3e26 --- /dev/null +++ b/code/modules/antagonists/clockcult/clock_structures/tinkerers_daemon.dm @@ -0,0 +1,171 @@ +//Tinkerer's Daemon: A machine that rapidly produces components at a power cost. +/obj/structure/destructible/clockwork/powered/tinkerers_daemon + name = "tinkerer's daemon" + desc = "A strange machine with three small brass obelisks attached to it." + clockwork_desc = "An efficient machine that can rapidly produce components at a small power cost. It will only function if active daemons are outnumbered by servants at a rate to 5:1." + icon_state = "tinkerers_daemon" + active_icon = "tinkerers_daemon" + inactive_icon = "tinkerers_daemon" + unanchored_icon = "tinkerers_daemon_unwrenched" + max_integrity = 100 + construction_value = 20 + break_message = "The daemon shatters into millions of pieces, leaving only a disc of metal!" + debris = list(/obj/item/clockwork/alloy_shards/medium = 1, \ + /obj/item/clockwork/alloy_shards/small = 6, \ + /obj/item/clockwork/component/replicant_alloy/replication_plate = 1) + var/static/mutable_appearance/daemon_glow = mutable_appearance('icons/obj/clockwork_objects.dmi', "tinkerglow") + var/static/mutable_appearance/component_glow = mutable_appearance('icons/obj/clockwork_objects.dmi', "t_random_component") + var/component_id_to_produce + var/production_time = 0 //last time we produced a component + var/production_cooldown = 70 + +/obj/structure/destructible/clockwork/powered/tinkerers_daemon/Destroy() + GLOB.active_daemons -= src + return ..() + +/obj/structure/destructible/clockwork/powered/tinkerers_daemon/examine(mob/user) + ..() + if(is_servant_of_ratvar(user) || isobserver(user)) + if(active) + if(component_id_to_produce) + to_chat(user, "It is currently producing [get_component_name(component_id_to_produce)][component_id_to_produce != REPLICANT_ALLOY ? "s":""].") + else + to_chat(user, "It is currently producing random components.") + to_chat(user, "It will produce a component every [round((production_cooldown*0.1) * get_efficiency_mod(TRUE), 0.1)] seconds and requires at least the following power for each component type:") + for(var/i in GLOB.clockwork_component_cache) + to_chat(user, "[get_component_icon(i)] [get_component_name(i)]: [DisplayPower(get_component_cost(i))] ([GLOB.clockwork_component_cache[i]] exist[GLOB.clockwork_component_cache[i] == 1 ? "s" : ""])") + +/obj/structure/destructible/clockwork/powered/tinkerers_daemon/forced_disable(bad_effects) + if(active) + if(bad_effects) + try_use_power(MIN_CLOCKCULT_POWER*4) + visible_message("[src] shuts down with a horrible grinding noise!") + playsound(src, 'sound/magic/clockwork/anima_fragment_attack.ogg', 50, 1) + else + visible_message("[src] shuts down!") + toggle() + return TRUE + +/obj/structure/destructible/clockwork/powered/tinkerers_daemon/attack_hand(mob/living/user) + if(!is_servant_of_ratvar(user)) + to_chat(user, "You place your hand on the daemon, but nothing happens.") + return + if(active) + toggle(0, user) + else + if(!anchored) + to_chat(user, "[src] needs to be secured to the floor before it can be activated!") + return FALSE + if(!GLOB.clockwork_caches) + to_chat(user, "\"You require a cache for this daemon to operate. Get to it.\"") + return + var/min_power_usable = 0 + for(var/i in GLOB.clockwork_component_cache) + if(!min_power_usable) + min_power_usable = get_component_cost(i) + else + min_power_usable = min(min_power_usable, get_component_cost(i)) + if(total_accessable_power() < min_power_usable) + to_chat(user, "\"You need more power to activate this daemon, friend.\"") + return + var/servants = 0 + for(var/mob/living/L in GLOB.living_mob_list) + if(is_servant_of_ratvar(L)) + servants++ + if(servants * 0.2 < 1) + to_chat(user, "\"There are too few servants for daemons to work.\"") + return + var/choice = alert(user,"Activate Daemon...",,"Specific Component","Random Component","Cancel") + switch(choice) + if("Specific Component") + var/list/components = list() + for(var/i in GLOB.clockwork_component_cache) + components["[get_component_name(i)] ([DisplayPower(get_component_cost(i))])"] = i + var/input_component = input(user, "Choose a component type.", name) as null|anything in components + component_id_to_produce = components[input_component] + servants = 0 + for(var/mob/living/L in GLOB.living_mob_list) + if(is_servant_of_ratvar(L)) + servants++ + if(!is_servant_of_ratvar(user) || !user.canUseTopic(src, !issilicon(user), NO_DEXTERY) || active || !GLOB.clockwork_caches || servants * 0.2 < 1) + return + if(!component_id_to_produce) + to_chat(user, "You decide not to select a component and activate the daemon.") + return + if(total_accessable_power() < get_component_cost(component_id_to_produce)) + to_chat(user, "There is too little power to produce this type of component!") + return + toggle(0, user) + if("Random Component") + component_id_to_produce = null + servants = 0 + for(var/mob/living/L in GLOB.living_mob_list) + if(is_servant_of_ratvar(L)) + servants++ + if(!is_servant_of_ratvar(user) || !user.canUseTopic(src, !issilicon(user), NO_DEXTERY) || active || !GLOB.clockwork_caches || servants * 0.2 < 1) + return + toggle(0, user) + +/obj/structure/destructible/clockwork/powered/tinkerers_daemon/toggle(fast_process, mob/living/user) + . = ..() + if(active) + GLOB.active_daemons += src + var/component_color = get_component_color(component_id_to_produce) + daemon_glow.color = component_color + add_overlay(daemon_glow) + component_glow.icon_state = "t_[component_id_to_produce ? component_id_to_produce :"random_component"]" + component_glow.color = component_color + add_overlay(component_glow) + production_time = world.time + production_cooldown //don't immediately produce when turned on after being off + set_light(2, 0.9, get_component_color_bright(component_id_to_produce)) + else + GLOB.active_daemons -= src + cut_overlays() + set_light(0) + +/obj/structure/destructible/clockwork/powered/tinkerers_daemon/proc/get_component_cost(id) + return max(MIN_CLOCKCULT_POWER*2, (MIN_CLOCKCULT_POWER*2) * (1 + round(GLOB.clockwork_component_cache[id] * 0.2))) + +/obj/structure/destructible/clockwork/powered/tinkerers_daemon/process() + var/servants = 0 + for(var/mob/living/L in GLOB.living_mob_list) + if(is_servant_of_ratvar(L)) + servants++ + while(servants * 0.2 < LAZYLEN(GLOB.active_daemons)) + var/obj/structure/destructible/clockwork/powered/tinkerers_daemon/D = GLOB.active_daemons[1] + if(!istype(D)) + break + if(D.active) + D.forced_disable(FALSE) + if(D == src) + return + . = ..() + var/min_power_usable = 0 + if(!component_id_to_produce) + for(var/i in GLOB.clockwork_component_cache) + if(!min_power_usable) + min_power_usable = get_component_cost(i) + else + min_power_usable = min(min_power_usable, get_component_cost(i)) + else + min_power_usable = get_component_cost(component_id_to_produce) + if(!GLOB.clockwork_caches || . < min_power_usable) //if we don't have enough to produce the lowest or what we chose to produce, cancel out + forced_disable(FALSE) + return + if(production_time <= world.time) + var/component_to_generate = component_id_to_produce + if(!component_to_generate) + component_to_generate = get_weighted_component_id() //more likely to generate components that we have less of + if(!try_use_power(get_component_cost(component_to_generate))) + component_to_generate = null + if(!component_id_to_produce) + for(var/i in GLOB.clockwork_component_cache) + if(try_use_power(get_component_cost(i))) //if we fail but are producing random, try and get a different component to produce + component_to_generate = i + break + if(component_to_generate) + generate_cache_component(component_to_generate, src) + production_time = world.time + (production_cooldown * get_efficiency_mod(TRUE)) //go on cooldown + visible_message("[src] hums as it produces a component.") + else + forced_disable(FALSE) //we shouldn't actually ever get here, as we should cancel out way before this