GLOBAL_LIST_EMPTY(clock_scriptures) GLOBAL_LIST_EMPTY(clock_scriptures_by_type) // Scriptures are the "spells" of clock cultists // They're usable through their clockwork slab, and cannot be invoked otherwise /datum/scripture /// Name of the scripture var/name = "" /// Shown decription of the scripture in the UI var/desc = "" /// Tooltip shown on-hover. Keep to a few words var/tip = "" /// How much power this scripture costs to use var/power_cost = 0 /// How much vitality this scripture costs to use var/vitality_cost = 0 /// How many cogs are required to be used to use this var/cogs_required = 0 /// Time required to invoke this while standing still var/invocation_time = 0 /// Text said during invocation, automatically translates to Ratvarian var/list/invocation_text = list() /// Icon state of the action button var/button_icon_state = "telerune" /// How many people need to invoke this in sight of each other to use var/invokers_required = 1 /// What category of scripture is this var/category = SPELLTYPE_ABSTRACT /// Only set to false if you call end_invoke somewhere in your scripture var/end_on_invocation = TRUE /// Ref to the mob invoking this var/mob/living/invoker /// Ref to the slab invoking this var/obj/item/clockwork/clockwork_slab/invoking_slab /// Timer object for the distance between invoking chants var/invocation_chant_timer = null /// Sound to play on finish var/sound/recital_sound = null /// If this scripture requires research to be unlocked var/research_required = FALSE /datum/scripture/New() . = ..() if(invokers_required > 1) desc += " Requires [invokers_required] invokers, should you be in a group." if(research_required) desc += " Requires research to unlock." /datum/scripture/Destroy(force, ...) invoker = null invoking_slab = null return ..() /// Invoke this scripture, checking if there's valid power and vitality /datum/scripture/proc/invoke() if(GLOB.clock_power < power_cost || GLOB.clock_vitality < vitality_cost) invoke_fail() if(invocation_chant_timer) deltimer(invocation_chant_timer) invocation_chant_timer = null end_invoke() return GLOB.clock_power -= power_cost GLOB.clock_vitality -= vitality_cost invoke_success() /// On success of invoking the scripture /datum/scripture/proc/invoke_success() return TRUE /// On failure of invoking the scripture /datum/scripture/proc/invoke_fail() return TRUE /// The overall reciting proc for saying every single line for a scripture /datum/scripture/proc/recital() if(!length(invocation_text)) return var/steps = length(invocation_text) var/time_between_say = invocation_time / (steps + 1) if(invocation_chant_timer) deltimer(invocation_chant_timer) invocation_chant_timer = null recite(1, time_between_say, steps) /// For reciting an individual line of a scripture /datum/scripture/proc/recite(text_point, wait_time, stop_at = 0) if(QDELETED(src)) return invocation_chant_timer = null if(!invoking_slab || !invoking_slab.invoking_scripture) return var/invokers_left = invokers_required if(invokers_left > 1) for(var/mob/living/potential_invoker in viewers(invoker)) if(!invokers_left) break if(potential_invoker?.mind.has_antag_datum(/datum/antagonist/clock_cultist/solo)) // Solo cultists can use all scriptures alone, while group clock cult doesn't get so lucky invokers_left = 0 clockwork_say(potential_invoker, text2ratvar(invocation_text[text_point]), TRUE) break if(potential_invoker.stat) continue if(IS_CLOCK(potential_invoker)) clockwork_say(potential_invoker, text2ratvar(invocation_text[text_point]), TRUE) invokers_left-- else clockwork_say(invoker, text2ratvar(invocation_text[text_point]), TRUE) if(recital_sound) SEND_SOUND(invoker, recital_sound) if(text_point < stop_at) invocation_chant_timer = addtimer(CALLBACK(src, PROC_REF(recite), text_point+1, wait_time, stop_at), wait_time, TIMER_STOPPABLE) /// Check for any special requriements such as not having enough invokers, or not holding the slab /datum/scripture/proc/check_special_requirements(mob/user) if(!invoker || !invoking_slab) message_admins("No invoker for [name]") return FALSE if(invoker.get_active_held_item() != invoking_slab && !iscyborg(invoker)) to_chat(invoker, span_brass("You fail to invoke [name].")) return FALSE var/invokers = 0 for(var/mob/living/potential_invoker in viewers(invoker)) if(potential_invoker.stat) continue if(IS_CLOCK(potential_invoker)) invokers++ if(potential_invoker.mind?.has_antag_datum(/datum/antagonist/clock_cultist/solo)) // They count for infinite so they can do all scriptures solo invokers = INFINITY break if(invokers < invokers_required) to_chat(invoker, span_brass("You need [invokers_required] servants to channel [name]!")) return FALSE return TRUE /// Start invoking a scripture, calling end_invoke() if it doesn't finish /datum/scripture/proc/begin_invoke(mob/living/invoking_mob, obj/item/clockwork/clockwork_slab/slab, bypass_unlock_checks = FALSE) if(invoking_mob.get_active_held_item() != slab && !iscyborg(invoking_mob)) to_chat(invoking_mob, span_brass("You need to have the [slab.name] in your active hand to recite scriptures.")) return slab.invoking_scripture = src invoker = invoking_mob invoking_slab = slab if(!(type in slab.purchased_scriptures) && !bypass_unlock_checks) log_runtime("CLOCKCULT: Attempting to invoke a scripture that has not been unlocked. Either there is a bug, or [ADMIN_LOOKUP(invoker)] is using some wacky exploits.") end_invoke() return if(!check_special_requirements(invoking_mob)) end_invoke() return recital() if(do_after(invoking_mob, invocation_time, target = invoking_mob, extra_checks = CALLBACK(src, PROC_REF(check_special_requirements), invoking_mob))) invoke() to_chat(invoking_mob, span_brass("You invoke [name].")) if(end_on_invocation) end_invoke() else invoke_fail() if(invocation_chant_timer) deltimer(invocation_chant_timer) invocation_chant_timer = null end_invoke() /// End the invoking, nulling things out /datum/scripture/proc/end_invoke() invoking_slab.invoking_scripture = null // Base create structure scripture /datum/scripture/create_structure /// Typepath for the structure to create var/summoned_structure /datum/scripture/create_structure/check_special_requirements(mob/user) . = ..() if(!.) return FALSE for(var/obj/structure/destructible/clockwork/clockwork_struct in get_turf(invoker)) if(istype(clockwork_struct, /obj/structure/destructible/clockwork/trap)) continue invoker.balloon_alert(invoker, "structure already on tile!") return FALSE return TRUE /datum/scripture/create_structure/invoke_success() var/created_structure = new summoned_structure(get_turf(invoker)) var/obj/structure/destructible/clockwork/clockwork_structure = created_structure if(istype(clockwork_structure)) clockwork_structure.owner = invoker.mind //For scriptures that charge the slab, and the slab will affect something //(stunning etc.) /datum/scripture/slab name = "Charge Slab" end_on_invocation = FALSE /// Time to perform this var/use_time = 1 SECONDS /// Overlay for the item/inhand state while this is invoked var/slab_overlay = "volt" /// Progressbar datum for count_down() var/datum/progressbar/progress /// How many times this scripture can be used overall, is unchanging var/uses = 1 /// Text displayed after use var/after_use_text = "" /// Internal spell for pointed/aimed spells var/datum/action/cooldown/spell/pointed/slab/pointed_spell /// How many times this can be used this particular invocation, can go down var/uses_left = 0 /// How much time left to use this var/time_left = 0 /// ID of the loop timer var/loop_timer_id /datum/scripture/slab/New() . = ..() pointed_spell = new(src) pointed_spell.name = src.name pointed_spell.deactive_msg = "" pointed_spell.parent_scripture = src /datum/scripture/slab/Destroy() if(progress) QDEL_NULL(progress) if(!QDELETED(pointed_spell)) QDEL_NULL(pointed_spell) return ..() /datum/scripture/slab/invoke() progress = new(invoker, use_time) uses_left = uses time_left = use_time invoking_slab.charge_overlay = slab_overlay invoking_slab.update_icon() invoking_slab.active_scripture = src pointed_spell.set_click_ability(invoker) count_down() GLOB.clock_power -= power_cost GLOB.clock_vitality -= vitality_cost invoke_success() /// Count down the progress bar /datum/scripture/slab/proc/count_down() if(QDELETED(src)) return progress.update(time_left) time_left-- loop_timer_id = null if(time_left > 0) loop_timer_id = addtimer(CALLBACK(src, PROC_REF(count_down)), 0.1 SECONDS, TIMER_STOPPABLE) else end_invocation() /// What occurs when an atom is clicked on. /datum/scripture/slab/proc/click_on(atom/clicked_atom) SHOULD_CALL_PARENT(TRUE) if(!invoker.can_interact_with(clicked_atom)) return if(!apply_effects(clicked_atom)) return uses_left-- if(uses_left) return if(after_use_text) clockwork_say(invoker, text2ratvar(after_use_text), TRUE) end_invocation(TRUE) /// What occurs when the invocation ends /datum/scripture/slab/proc/end_invocation(silent = FALSE) SHOULD_CALL_PARENT(TRUE) //Remove the timer if there is one currently active if(loop_timer_id) deltimer(loop_timer_id) loop_timer_id = null if(!silent) to_chat(invoker, span_brass("You are no longer invoking [name].")) qdel(progress) pointed_spell.unset_click_ability(invoker) invoking_slab.charge_overlay = null invoking_slab.update_icon() invoking_slab.active_scripture = null end_invoke() /// Apply the effects of a scripture to an atom /datum/scripture/slab/proc/apply_effects(atom/applied_atom) return TRUE /datum/action/cooldown/spell/pointed/slab /// The scripture datum that this spell is referring to var/datum/scripture/slab/parent_scripture /datum/action/cooldown/spell/pointed/slab/Destroy() parent_scripture = null return ..() /datum/action/cooldown/spell/pointed/slab/InterceptClickOn(mob/living/clicker, params, atom/target) parent_scripture?.click_on(target) /// Generate all scriptures in a global assoc of name:ref. Only needs to be done once /proc/generate_clockcult_scriptures() for(var/categorypath in subtypesof(/datum/scripture)) var/datum/scripture/clock_script = new categorypath GLOB.clock_scriptures += clock_script GLOB.clock_scriptures_by_type[clock_script.type] = clock_script