From c125704cb28229e1e6de494276f8358706ec7196 Mon Sep 17 00:00:00 2001 From: SaukyKouko Date: Wed, 20 May 2026 00:27:17 +0300 Subject: [PATCH 01/22] =?UTF-8?q?=D1=82=D1=80=D0=BE=D0=B3=D0=B0=D0=B5?= =?UTF-8?q?=D0=BC=20=D0=BA=D0=BE=D0=B4?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- code/__DEFINES/~ff_defines/psionic.dm | 6 + .../code/_psionic_abilities.dm} | 104 ++++---- .../modules/psionics/code/_psionic_datum.dm | 232 ++++++++++++++++++ .../{psyonics => psionics}/code/coersion.dm | 230 +++++------------ .../code/conjure_item/conjure_item.dm | 99 ++++++++ tff_modular/modules/psionics/code/cyberimp.dm | 40 +++ .../{psyonics => psionics}/code/documents.dm | 39 ++- .../code/energistics.dm | 113 +++------ tff_modular/modules/psionics/code/hud.dm | 88 +++++++ .../modules/psionics/code/pointed/pointed.dm | 1 + .../psionics/code/projectiles/projectile.dm | 62 +++++ .../modules/psionics/code/psi_shop/datum.dm | 97 ++++++++ .../code/psychokinesis.dm | 151 +++--------- .../{psyonics => psionics}/code/redaction.dm | 117 ++++----- .../modules/psionics/code/subsystem.dm | 5 + .../modules/psionics/code/touch/touch.dm | 46 ++++ .../{psyonics => psionics}/icons/actions.dmi | Bin .../{psyonics => psionics}/icons/card.dmi | Bin .../modules/psionics/icons/projectiles.dmi | Bin 0 -> 756 bytes .../modules/psionics/icons/psi_hud.dmi | Bin 0 -> 3543 bytes tff_modular/modules/psionics/icons/spells.dmi | Bin 0 -> 27570 bytes tff_modular/modules/psyonics/code/_quirk.dm | 216 ---------------- tff_modular/modules/psyonics/code/cyberimp.dm | 40 --- .../modules/psyonics/code/subsystem.dm | 5 + tgstation.dme | 24 +- .../tff/psyonic_school.tsx | 15 -- ...{PsyonicLicense.tsx => PsionicLicense.tsx} | 27 +- ...1\203\320\274\320\265\320\275\321\202.txt" | 125 ++++++++++ 28 files changed, 1072 insertions(+), 810 deletions(-) create mode 100644 code/__DEFINES/~ff_defines/psionic.dm rename tff_modular/modules/{psyonics/code/_psyonics.dm => psionics/code/_psionic_abilities.dm} (60%) create mode 100644 tff_modular/modules/psionics/code/_psionic_datum.dm rename tff_modular/modules/{psyonics => psionics}/code/coersion.dm (63%) create mode 100644 tff_modular/modules/psionics/code/conjure_item/conjure_item.dm create mode 100644 tff_modular/modules/psionics/code/cyberimp.dm rename tff_modular/modules/{psyonics => psionics}/code/documents.dm (59%) rename tff_modular/modules/{psyonics => psionics}/code/energistics.dm (51%) create mode 100644 tff_modular/modules/psionics/code/hud.dm create mode 100644 tff_modular/modules/psionics/code/pointed/pointed.dm create mode 100644 tff_modular/modules/psionics/code/projectiles/projectile.dm create mode 100644 tff_modular/modules/psionics/code/psi_shop/datum.dm rename tff_modular/modules/{psyonics => psionics}/code/psychokinesis.dm (56%) rename tff_modular/modules/{psyonics => psionics}/code/redaction.dm (78%) create mode 100644 tff_modular/modules/psionics/code/subsystem.dm create mode 100644 tff_modular/modules/psionics/code/touch/touch.dm rename tff_modular/modules/{psyonics => psionics}/icons/actions.dmi (100%) rename tff_modular/modules/{psyonics => psionics}/icons/card.dmi (100%) create mode 100644 tff_modular/modules/psionics/icons/projectiles.dmi create mode 100644 tff_modular/modules/psionics/icons/psi_hud.dmi create mode 100644 tff_modular/modules/psionics/icons/spells.dmi delete mode 100644 tff_modular/modules/psyonics/code/_quirk.dm delete mode 100644 tff_modular/modules/psyonics/code/cyberimp.dm create mode 100644 tff_modular/modules/psyonics/code/subsystem.dm delete mode 100644 tgui/packages/tgui/interfaces/PreferencesMenu/preferences/features/character_preferences/tff/psyonic_school.tsx rename tgui/packages/tgui/interfaces/{PsyonicLicense.tsx => PsionicLicense.tsx} (85%) create mode 100644 "tgui/packages/tgui/interfaces/\320\235\320\276\320\262\321\213\320\271 \321\202\320\265\320\272\321\201\321\202\320\276\320\262\321\213\320\271 \320\264\320\276\320\272\321\203\320\274\320\265\320\275\321\202.txt" diff --git a/code/__DEFINES/~ff_defines/psionic.dm b/code/__DEFINES/~ff_defines/psionic.dm new file mode 100644 index 00000000000..7547ff74f64 --- /dev/null +++ b/code/__DEFINES/~ff_defines/psionic.dm @@ -0,0 +1,6 @@ +#define TRAIT_PSIONIC_USER "psionicuser" +#define TRAIT_PSIONIC_IMPLANT "psionic_implant" +#define TRAIT_PSI_SUPPRESSED "psi_suppressed" +#define TRAIT_IMMUNE_TO_PSI_SUPPRESSION "immune_to_psi_suppression" +#define HUD_PSI_DISPLAY "hud_psi_display" +#define HUD_PSI_SIGNAL "hud_psi_signal" diff --git a/tff_modular/modules/psyonics/code/_psyonics.dm b/tff_modular/modules/psionics/code/_psionic_abilities.dm similarity index 60% rename from tff_modular/modules/psyonics/code/_psyonics.dm rename to tff_modular/modules/psionics/code/_psionic_abilities.dm index 75feb9a2339..a3badfc0b13 100644 --- a/tff_modular/modules/psyonics/code/_psyonics.dm +++ b/tff_modular/modules/psionics/code/_psionic_abilities.dm @@ -9,11 +9,29 @@ var/target_msg // Сила способности var/cast_power = 0 - // Вторичная школа. Может дать особые эффекты при комбинациях - var/secondary_school = 0 + // Является псионическим спеллом? Нужен чтобы потом удалить их + var/psionic = FALSE + // Уровень способности, определяет может ли ее купить псионик + var/psionic_level = 1 + // Датум псионики что используется при касте + var/datum/psionic/psionic_datum + // Категория + var/category = "utility" + // Цена + var/point_cost = 1 + +/datum/action/cooldown/spell/Grant(mob/grant_to) + . = ..() + if(psionic) + var/mob/living/our_psionic = grant_to + psionic_datum = our_psionic.get_psionic() + cast_power = psionic_datum.psionic_level // Спеллы для призвания предмета -/datum/action/cooldown/spell/conjure_item/psyonic +/datum/action/cooldown/spell/conjure_item/psionic + button_icon = 'tff_modular/modules/psionics/icons/spells.dmi' + background_icon_state = "bg_tech_blue" + overlay_icon_state = "bg_tech_blue_border" delete_old = FALSE delete_on_failure = TRUE requires_hands = TRUE @@ -21,36 +39,33 @@ antimagic_flags = MAGIC_RESISTANCE_MIND spell_requirements = NONE cooldown_reduction_per_rank = 0 SECONDS + psionic = TRUE -/datum/action/cooldown/spell/conjure_item/psyonic/New(Target, power, additional_school) +/datum/action/cooldown/spell/conjure_item/psionic/New(Target, power) . = ..() cast_power = power - secondary_school = additional_school // Проверяем достаточно ли маны /datum/action/cooldown/spell/proc/check_for_mana() var/mob/living/carbon/human/caster = owner - var/datum/quirk/psyonic/quirk_holder = caster.get_quirk(/datum/quirk/psyonic) - if(quirk_holder && (quirk_holder.mana_level - mana_cost) >= 0) + var/datum/psionic/psi_holder = caster.get_psionic() + if(psi_holder) return TRUE else return FALSE // Сосём ману у псионика -/datum/action/cooldown/spell/proc/drain_mana(forced = FALSE) +/datum/action/cooldown/spell/proc/drain_mana() var/mob/living/carbon/human/caster = owner - var/datum/quirk/psyonic/quirk_holder = caster.get_quirk(/datum/quirk/psyonic) + var/datum/psionic/psi_holder = caster.get_psionic() caster.adjust_stamina_loss(stamina_cost, forced = TRUE) - if(quirk_holder && (quirk_holder.mana_level - mana_cost) >= 0) - quirk_holder.mana_level -= mana_cost - return TRUE - else if (forced) - quirk_holder.mana_level = 0 + if(psi_holder) + psi_holder.adjust_psi_energy(mana_cost) return TRUE else return FALSE -/datum/action/cooldown/spell/conjure_item/psyonic/can_cast_spell(feedback) +/datum/action/cooldown/spell/conjure_item/psionic/can_cast_spell(feedback) . = ..() if(!.) return FALSE @@ -60,12 +75,15 @@ else return TRUE -/datum/action/cooldown/spell/conjure_item/psyonic/cast(atom/cast_on) +/datum/action/cooldown/spell/conjure_item/psionic/cast(atom/cast_on) drain_mana() return ..() // Для спеллов которые применяются на себя тыком кнопки a.k.a. выдача генов -/datum/action/cooldown/spell/psyonic +/datum/action/cooldown/spell/psionic + button_icon = 'tff_modular/modules/psionics/icons/spells.dmi' + background_icon_state = "bg_tech_blue" + overlay_icon_state = "bg_tech_blue_border" // Псионические способности (в основном) не блокируются, но выводят особенные сообщения тем, кто это может antimagic_flags = MAGIC_RESISTANCE_MIND @@ -73,13 +91,14 @@ invocation_type = INVOCATION_NONE spell_requirements = NONE cooldown_reduction_per_rank = 0 SECONDS + psionic = TRUE + psionic_level = 1 -/datum/action/cooldown/spell/psyonic/New(Target, power, additional_school) +/datum/action/cooldown/spell/psionic/New(Target, power, additional_school) . = ..() cast_power = power - secondary_school = additional_school -/datum/action/cooldown/spell/psyonic/can_cast_spell(feedback) +/datum/action/cooldown/spell/psionic/can_cast_spell(feedback) . = ..() if(!.) return FALSE @@ -90,7 +109,10 @@ return TRUE // Спеллы для пострелушек -/datum/action/cooldown/spell/pointed/projectile/psyonic +/datum/action/cooldown/spell/pointed/projectile/psionic + button_icon = 'tff_modular/modules/psionics/icons/spells.dmi' + background_icon_state = "bg_tech_blue" + overlay_icon_state = "bg_tech_blue_border" // Псионические способности (в основном) не блокируются, но выводят особенные сообщения тем, кто это может antimagic_flags = MAGIC_RESISTANCE_MIND @@ -98,13 +120,13 @@ invocation_type = INVOCATION_NONE spell_requirements = NONE cooldown_reduction_per_rank = 0 SECONDS + psionic = TRUE -/datum/action/cooldown/spell/pointed/projectile/psyonic/New(Target, power, additional_school) +/datum/action/cooldown/spell/pointed/projectile/psionic/New(Target, power, additional_school) . = ..() cast_power = power - secondary_school = additional_school -/datum/action/cooldown/spell/pointed/projectile/psyonic/can_cast_spell(feedback) +/datum/action/cooldown/spell/pointed/projectile/psionic/can_cast_spell(feedback) . = ..() if(!.) return FALSE @@ -115,20 +137,23 @@ return TRUE // Направленные спеллы a.k.a. псионик выбирают цель на дистанции -/datum/action/cooldown/spell/pointed/psyonic +/datum/action/cooldown/spell/pointed/psionic + button_icon = 'tff_modular/modules/psionics/icons/spells.dmi' + background_icon_state = "bg_tech_blue" + overlay_icon_state = "bg_tech_blue_border" // Псионические способности (в основном) не блокируются, но выводят особенные сообщения тем, кто это может antimagic_flags = MAGIC_RESISTANCE_MIND school = SCHOOL_UNSET invocation_type = INVOCATION_NONE spell_requirements = NONE cooldown_reduction_per_rank = 0 SECONDS + psionic = TRUE -/datum/action/cooldown/spell/pointed/psyonic/New(Target, power, additional_school) +/datum/action/cooldown/spell/pointed/psionic/New(Target, power, additional_school) . = ..() cast_power = power - secondary_school = additional_school -/datum/action/cooldown/spell/pointed/psyonic/can_cast_spell(feedback) +/datum/action/cooldown/spell/pointed/psionic/can_cast_spell(feedback) . = ..() if(!.) return FALSE @@ -139,19 +164,22 @@ return TRUE // Спеллы которыми надо каснуться чего либо -/datum/action/cooldown/spell/touch/psyonic +/datum/action/cooldown/spell/touch/psionic + button_icon = 'tff_modular/modules/psionics/icons/spells.dmi' + background_icon_state = "bg_tech_blue" + overlay_icon_state = "bg_tech_blue_border" // Псионические способности (в основном) не блокируются, но выводят особенные сообщения тем, кто это может antimagic_flags = MAGIC_RESISTANCE_MIND school = SCHOOL_UNSET invocation_type = INVOCATION_NONE spell_requirements = NONE + psionic = TRUE -/datum/action/cooldown/spell/touch/psyonic/New(Target, power, additional_school) +/datum/action/cooldown/spell/touch/psionic/New(Target, power, additional_school) . = ..() cast_power = power - secondary_school = additional_school -/datum/action/cooldown/spell/touch/psyonic/can_cast_spell(feedback) +/datum/action/cooldown/spell/touch/psionic/can_cast_spell(feedback) . = ..() if(!.) return FALSE @@ -161,18 +189,18 @@ else return TRUE -/datum/action/cooldown/spell/touch/psyonic/create_hand(mob/living/carbon/cast_on) +/datum/action/cooldown/spell/touch/psionic/create_hand(mob/living/carbon/cast_on) . = ..() if(!.) return . var/obj/item/bodypart/transfer_limb = cast_on.get_active_hand() if(IS_ROBOTIC_LIMB(transfer_limb)) - to_chat(cast_on, span_notice("You fail to channel your psyonic powers through your inorganic hand.")) + to_chat(cast_on, span_notice("You fail to channel your psionic powers through your inorganic hand.")) return FALSE return TRUE -/particles/droplets/psyonic +/particles/droplets/psionic icon = 'icons/effects/particles/generic.dmi' icon_state = list("dot"=2,"drop"=1) width = 32 @@ -185,9 +213,3 @@ position = generator(GEN_BOX, list(-9,-9,0), list(9,18,0), NORMAL_RAND) scale = generator(GEN_VECTOR, list(0.9,0.9), list(1.1,1.1), NORMAL_RAND) gravity = list(0, 0.95) - -// Проверка на то, есть ли квирк псионики у хумана -/mob/living/carbon/human/proc/ispsyonic() - if(has_quirk(/datum/quirk/psyonic)) - return TRUE - return FALSE diff --git a/tff_modular/modules/psionics/code/_psionic_datum.dm b/tff_modular/modules/psionics/code/_psionic_datum.dm new file mode 100644 index 00000000000..1aa357e23ea --- /dev/null +++ b/tff_modular/modules/psionics/code/_psionic_datum.dm @@ -0,0 +1,232 @@ +#define SENSITIVE_PSIONIC "Sensitive Psionic" +#define HARMONIOUS_PSIONIC "Harmonious Psionic" + +/datum/quirk/psionic + name = "Psionic Abilities" + desc = "Either you were born like this or gained powers from implants/training or other events - you are a psionic. \ + Your mind can access the world that lies beyond our mortal plane. One day voices from within had pierced your skull \ + like a tide wave turns a sailboat over in open sea, but you withstanded it and received abilities your father haven't \ + even dreamed of. From now on a special type of energy is stored in your mind, body and soul and you have control over it." + value = 0 + medical_record_text = "Patient possesses connection to another plain of reality." + quirk_flags = QUIRK_HIDE_FROM_SCAN|QUIRK_HUMAN_ONLY|QUIRK_PROCESSES // Сканеры не видят псиоников. Только псионик школы может точно определить, является ли живое существо псиоником + gain_text = span_cyan("You mind feels uneasy, but... so powerful.") + lose_text = span_warning("You lost something that kept your connection with other realms.") + icon = "fa-star" + mob_trait = TRAIT_PSIONIC_USER + nova_stars_only = TRUE + allow_for_donator = TRUE + +/datum/quirk/psionic/add(client/client_source) + quirk_holder.add_psionic(/datum/psionic/sensitive) + +/datum/quirk/psionic/remove() + . = ..() + quirk_holder.remove_psionic() + +/mob/living + var/datum/psionic/psi_sensivity + +/mob/living/proc/add_psionic(psi_type) + if(!psi_type) + psi_type = new /datum/psionic/sensitive() + if(istype(psi_type, /datum/psionic)) + return FALSE + if(get_psionic()) + return FALSE + var/datum/psionic/new_psi = new psi_type() + new_psi.apply_to(src) + + return FALSE + +/mob/living/proc/remove_psionic() + if(!psi_sensivity) + return FALSE + qdel(psi_sensivity) + +/mob/living/proc/get_psionic() + if(!psi_sensivity) + return FALSE + return psi_sensivity + +/datum/psionic + // Текущий владелец псионики + var/mob/living/psi_owner + // Текущий уровень маны + var/mana_level = 10 + // Максимально возможный уровень маны + var/max_mana = 10 + // Уровень псионических способностей + var/psionic_level = 0 + // Строка для описания уровня + var/psionic_level_string + // Псионические очки, нужные для получения способностей + var/psi_point = 0 + // Требуется ли выдать лицензию + var/license = TRUE + /// Два вара скопированные из item_quirk для правильной выдачи лицензии + var/list/where_items_spawned + var/open_backpack = FALSE + // Магазин + var/datum/psionic_shop/psi_shop_datum + var/datum/action/psionic_shop/psi_shop_action + // Список заклинаний + var/list/datum/action/cooldown/spell/learned_spells = list() + +/datum/psionic/proc/apply_to(mob/living/granted_to) + if(!granted_to) + CRASH("Tried to add psionic without owner") + + psi_owner = granted_to + psi_owner.psi_sensivity = src + + RegisterSignal(psi_owner, COMSIG_MOB_HUD_CREATED, PROC_REF(on_hud_created)) + RegisterSignal(psi_owner, COMSIG_LIVING_LIFE, PROC_REF(psionic_life)) + + if(license) + var/obj/item/card/psionic_license/new_license = new(psi_owner) + give_item_to_holder(new_license, list(LOCATION_BACKPACK = ITEM_SLOT_BACK, LOCATION_HANDS = ITEM_SLOT_HANDS), flavour_text = "Make sure not to lose it. You can not remake this on the station.") + + if(psi_owner.hud_used) + on_hud_created() + + add_shop() + +/datum/psionic/proc/add_shop() + psi_shop_datum = new(psi_owner, src) + psi_shop_action = new(psi_shop_datum) + psi_shop_action.Grant(psi_owner) + +/datum/psionic/proc/on_hud_created(datum/source) + SIGNAL_HANDLER + var/datum/hud/psi_hud = psi_owner.hud_used + psi_hud.add_screen_object(/atom/movable/screen/psionic/psionic_energy, HUD_PSI_DISPLAY, HUD_GROUP_INFO) + psi_hud.add_screen_object(/atom/movable/screen/psionic/psionic_signal, HUD_PSI_SIGNAL, HUD_GROUP_INFO) + psi_hud.show_hud(psi_hud.hud_version) + UnregisterSignal(psi_owner, COMSIG_MOB_HUD_CREATED) + +/datum/psionic/proc/give_item_to_holder(obj/item/license, list/valid_slots, flavour_text = null, default_location = "at your feet", notify_player = TRUE) + if(ispath(license)) + license = new license(get_turf(psi_owner)) + + var/mob/living/carbon/human/human_holder = psi_owner + + var/where = human_holder.equip_in_one_of_slots(license, valid_slots, qdel_on_fail = FALSE, indirect_action = TRUE) || default_location + + if(where == LOCATION_BACKPACK) + open_backpack = TRUE + + if(notify_player) + LAZYADD(where_items_spawned, span_boldnotice("You have \a [license] [where]. [flavour_text]")) + +/datum/psionic/Destroy(force) + . = ..() + UnregisterSignal(psi_owner, COMSIG_MOB_HUD_CREATED) + RegisterSignal(psi_owner, COMSIG_LIVING_LIFE, PROC_REF(psionic_life)) + if(!psi_owner.hud_used) + return + + var/datum/hud/psi_hud = psi_owner.hud_used + psi_hud.remove_screen_object(HUD_PSI_DISPLAY) + psi_hud.remove_screen_object(HUD_PSI_SIGNAL) + for(var/datum/action/cooldown/spell/spells_to_remove in psi_owner.actions) + if(!spells_to_remove.psionic) + continue + spells_to_remove.Remove() + QDEL_NULL(psi_shop_action) + QDEL_NULL(psi_shop_datum) + +/datum/psionic/proc/psionic_life(seconds_per_tick) + SIGNAL_HANDLER + + if(is_suppressed()) // Подавление пси-энергии + mana_level = 1 // Мы не ставим 0, потому что при 0 начинается боль + update_hud() + return FALSE + if(mana_level <= 0) + psi_owner.adjust_stamina_loss(200) + + var/delta_time = DELTA_WORLD_TIME(SSmobs) + var/mob/living/carbon/human/human_holder = psi_owner + var/additional_mana = 1 + + if(psi_owner.has_status_effect(/datum/status_effect/drugginess)) // Наркота даёт бафф к генерации маны + additional_mana *= 1.5 + if(HAS_TRAIT(psi_owner, TRAIT_PSIONIC_IMPLANT)) // Если есть имплант для увеличения регена маны + additional_mana *= 2 + if(human_holder.is_blind()) + additional_mana *= 1.5 + adjust_psi_energy((1 * additional_mana) * delta_time) + update_hud() + +/datum/psionic/proc/adjust_psi_energy(amount) + if(!isnum(amount)) + return + mana_level = clamp(mana_level + amount, 0, max_mana) + +/datum/psionic/sensitive + max_mana = 25 + psionic_level = 1 + psionic_level_string = SENSITIVE_PSIONIC + +/datum/psionic/harmonious + max_mana = 100 + psionic_level = 2 + psionic_level_string = HARMONIOUS_PSIONIC + license = FALSE + +/datum/psionic/proc/is_suppressed() + if(HAS_TRAIT(psi_owner, TRAIT_PSI_SUPPRESSED)) + return TRUE + return FALSE + +/datum/psionic/proc/detect_psionic() + if(psi_owner.psi_sensivity.is_suppressed()) + return FALSE + + var/list/mob/living/psionics = list() + for(var/mob/living/possible_psionic in range(6, psi_owner.loc)) + if(!possible_psionic.psi_sensivity) + continue + if(possible_psionic == psi_owner) + continue + if(possible_psionic.psi_sensivity.is_suppressed()) + continue + psionics += possible_psionic + + if(!length(psionics)) + return FALSE + + return TRUE + +/datum/psionic/proc/research_spell(datum/action/cooldown/spell/spell_path) + if(!ispath(spell_path, /datum/action/cooldown/spell)) + CRASH("Psionic research_spell attempted to purchase an invalid typepath! (got: [spell_path])") + + if(learned_spells[spell_path]) + to_chat(psi_owner, span_warning("We have already researched this spell!")) + return FALSE + + if(psi_point < initial(spell_path.point_cost)) + to_chat(psi_owner, span_warning("We cant research this spell now!")) + return FALSE + + var/success = give_spell(spell_path) + if(success) + psi_point -= initial(spell_path.point_cost) + return success + +/datum/psionic/proc/give_spell(spell_path) + var/datum/action/cooldown/spell/new_action = new spell_path() + + if(!new_action) + to_chat(psi_owner, "This is awkward. Psionic power purchase failed, please report this bug to a coder!") + CRASH("Psionic give_spell was unable to grant a new changeling action for path [spell_path]!") + + learned_spells[spell_path] = new_action + new_action.Grant(psi_owner) + + return TRUE + +#undef SENSITIVE_PSIONIC +#undef HARMONIOUS_PSIONIC diff --git a/tff_modular/modules/psyonics/code/coersion.dm b/tff_modular/modules/psionics/code/coersion.dm similarity index 63% rename from tff_modular/modules/psyonics/code/coersion.dm rename to tff_modular/modules/psionics/code/coersion.dm index 001ed034207..3845b93f4b1 100644 --- a/tff_modular/modules/psyonics/code/coersion.dm +++ b/tff_modular/modules/psionics/code/coersion.dm @@ -2,92 +2,18 @@ #define IS_OBSESSED(mob) (mob?.mind?.has_antag_datum(/datum/antagonist/obsessed)) /// Школа внушения. 7 спеллов -/// Psyonic assay - скан, является ли человек псиоником -/// Psyonic focus - лечение мозга и псих болезней -/// Psyonic mind read - продвинутое чтение разума(Как обычная ген. мутация, но + работа + воспоминания). Выдаётся только и ТОЛЬКО психологу, если он псионик -/// Psyonic agony - работает как стан дубинка, исчезает после одного удара -/// Psyonic spasm - станит на полсекунды, заставляет выронить всё из рук. Работает дистанционно -/// Psyonic hypnosis - гипнотизирует цель фразой, которую выбрал псионик. ERP IS BAD. DO NOT ERP. +/// Psionic assay - скан, является ли человек псиоником +/// Psionic focus - лечение мозга и псих болезней +/// Psionic mind read - продвинутое чтение разума(Как обычная ген. мутация, но + работа + воспоминания). Выдаётся только и ТОЛЬКО психологу, если он псионик +/// Psionic agony - работает как стан дубинка, исчезает после одного удара +/// Psionic spasm - станит на полсекунды, заставляет выронить всё из рук. Работает дистанционно +/// Psionic hypnosis - гипнотизирует цель фразой, которую выбрал псионик. ERP IS BAD. DO NOT ERP. /// P.S. По гипнозу. В оригинале на финиках вообще было порабощение разума. -/// Psyonic blind - временно ослепляет. - -// Прок для проверки носит ли моб шляпку из фольги. Удивительно, но защищает от некоторых спеллов школы внушения) -/mob/living/carbon/human/proc/is_wearing_tinfoil_hat() - if(istype(head, /obj/item/clothing/head/costume/foilhat)) - return TRUE - return FALSE - -// Добавить школу внушения -/mob/living/carbon/human/proc/try_add_coercion_school(tier = 0, additional_school = 0) - if(tier >= 0) - var/datum/action/new_action = new /datum/action/cooldown/spell/touch/psyonic/psyonic_assay(src.mind || src, tier, additional_school) - new_action.Grant(src) - if(tier >= 1) - var/datum/action/new_action = new /datum/action/cooldown/spell/pointed/psyonic/psyonic_focus(src.mind || src, tier, additional_school) - new_action.Grant(src) - if(tier >= 2) - if(HAS_MIND_TRAIT(src, TRAIT_MADNESS_IMMUNE)) // A.K.A. станционный психолог - var/datum/action/new_action = new /datum/action/cooldown/spell/touch/psyonic/psyonic_mind_read(src.mind || src, tier, additional_school) - new_action.Grant(src) - var/datum/action/new_action2 = new /datum/action/cooldown/spell/touch/psyonic/psyonic_hypnosis(src.mind || src, tier, additional_school) - new_action2.Grant(src) - if(tier >= 3) - var/datum/action/new_action = new /datum/action/cooldown/spell/touch/psyonic/psyonic_agony(src.mind || src, tier, additional_school) - new_action.Grant(src) - var/datum/action/new_action2 = new /datum/action/cooldown/spell/pointed/psyonic/psyonic_spasm(src.mind || src, tier, additional_school) - new_action2.Grant(src) - if(tier >= 4) // Способность вызывать слепоту на ~15 секунд втихую на расстоянии это боль. - var/datum/action/new_action = new /datum/action/cooldown/spell/pointed/psyonic/psyonic_blind(src.mind || src, tier, additional_school) - new_action.Grant(src) - -// Спелл для чтения разума другого игрока на наличие псионических способностей -/datum/action/cooldown/spell/touch/psyonic/psyonic_assay - name = "Psyonic Assay" - desc = "Check if the target is a psyonic." - button_icon = 'icons/obj/medical/organs/organs.dmi' - button_icon_state = "brain" - cooldown_time = 3 SECONDS - mana_cost = 5 - stamina_cost = 0 - target_msg = "Your get a headache, but it quickly fades." - hand_path = /obj/item/melee/touch_attack/psyonic_mending - draw_message = span_notice("You ready your hand to cleanse a patient.") - drop_message = span_notice("You lower your hand.") - can_cast_on_self = TRUE - -/datum/action/cooldown/spell/touch/psyonic/psyonic_assay/cast_on_hand_hit(obj/item/melee/touch_attack/hand, atom/victim, mob/living/carbon/mendicant) - if(ishuman(victim)) - var/mob/living/carbon/human/human_victim = victim - if(human_victim.can_block_magic(antimagic_flags)) - to_chat(human_victim, span_notice("Psionic nearby tries to check you for psyonic levels.")) - else - to_chat(human_victim, span_warning(target_msg)) - owner.visible_message(span_warning("[owner] presses his thumb onto [victim]s forehead."), - span_notice("You press your thumb onto [victim]s forehead and begin reading them.")) - to_chat(victim, span_danger("[owner] presses a thumb onto your forehead and holds it there. It burns sligthly!")) - if(do_after(mendicant, 6 SECONDS, human_victim, IGNORE_SLOWDOWNS, TRUE)) - read_psyonic_level(human_victim) - drain_mana() - return TRUE - else - return FALSE - -/datum/action/cooldown/spell/touch/psyonic/psyonic_assay/proc/read_psyonic_level(mob/living/carbon/human/patient) - if(issynthetic(patient) && secondary_school != "Psychokinesis") - to_chat(owner, span_notice("I can see... just numbers. No idea how to work with synths.")) - return FALSE - - if(patient.ispsyonic()) - var/datum/quirk/psyonic/target_quirk = patient.get_quirk(/datum/quirk/psyonic) - owner.visible_message(span_notice("[owner] backs off from [patient]."), - span_cyan("Target is a psyonic from the school of [target_quirk.school]. [patient.p_Their()] class is [target_quirk.psyonic_level_string]")) - else - owner.visible_message(span_notice("[owner] backs off from [patient]."), - span_cyan("Target is not a psyonic.")) +/// Psionic blind - временно ослепляет. // Лечим мозги и брейнтравмы. -/datum/action/cooldown/spell/pointed/psyonic/psyonic_focus - name = "Psyonic Focus" +/datum/action/cooldown/spell/pointed/psionic/psionic_focus + name = "Psionic Focus" desc = "Try to restore patients brain to its natural initial condition, fixing brain damage. Has a chance to heal traumas. Can be cast over distance." button_icon = 'icons/obj/medical/organs/organs.dmi' button_icon_state = "brain-smooth" @@ -95,25 +21,21 @@ mana_cost = 40 target_msg = "You feel like someone is messing with your brains." active_msg = "You prepare to heal someones mind..." + psionic_level = 2 + point_cost = 3 -/datum/action/cooldown/spell/pointed/psyonic/psyonic_focus/New(Target) - . = ..() - if(secondary_school == "Redaction") - cast_power += 1 - -/datum/action/cooldown/spell/pointed/psyonic/psyonic_focus/is_valid_target(atom/cast_on) +/datum/action/cooldown/spell/pointed/psionic/psionic_focus/is_valid_target(atom/cast_on) if(!ishuman(cast_on)) return FALSE - if(issynthetic(cast_on) && secondary_school != "Psychokinesis") + if(issynthetic(cast_on) && cast_power < 2) to_chat(owner, span_notice("I dont know how to work with synths.")) return FALSE - return TRUE -/datum/action/cooldown/spell/pointed/psyonic/psyonic_focus/cast(mob/living/carbon/human/cast_on) +/datum/action/cooldown/spell/pointed/psionic/psionic_focus/cast(mob/living/carbon/human/cast_on) . = ..() if(cast_on.can_block_magic(antimagic_flags)) - to_chat(cast_on, span_notice("Your mind is being healed by a psyonic nearby.")) + to_chat(cast_on, span_notice("Your mind is being healed by a psionic nearby.")) else to_chat(cast_on, span_warning(target_msg)) owner.Beam(cast_on, icon_state = "blood_light", time = 5 SECONDS) @@ -126,7 +48,7 @@ drain_mana() return TRUE -/datum/action/cooldown/spell/pointed/psyonic/psyonic_focus/proc/fix_brainz(mob/living/carbon/human/cast_on) +/datum/action/cooldown/spell/pointed/psionic/psionic_focus/proc/fix_brainz(mob/living/carbon/human/cast_on) var/b_damage = cast_on.get_organ_loss(ORGAN_SLOT_BRAIN) if(b_damage > 0) cast_on.adjust_organ_loss(ORGAN_SLOT_BRAIN, -10 * cast_power) @@ -138,13 +60,13 @@ cast_on.cure_trauma_type(resilience = trauma.resilience) cast_on.apply_status_effect(/datum/status_effect/drugginess, 20 SECONDS) -/datum/action/cooldown/spell/pointed/psyonic/psyonic_focus/proc/accident_harm(mob/living/carbon/human/cast_on) +/datum/action/cooldown/spell/pointed/psionic/psionic_focus/proc/accident_harm(mob/living/carbon/human/cast_on) cast_on.adjust_organ_loss(ORGAN_SLOT_BRAIN, 15 * cast_power, 101) to_chat(cast_on, span_bolddanger("You head hurts!")) // Читаем разум. Выдаёт: последние сейлоги, интент, настоящее имя, воспоминания, намёк на работу, намёк на то, что в антаг_датум что то есть. -/datum/action/cooldown/spell/touch/psyonic/psyonic_mind_read - name = "Psyonic Mind Read" +/datum/action/cooldown/spell/touch/psionic/psionic_mind_read + name = "Psionic Mind Read" desc = "Rudely intrude into targets thoughts." button_icon = 'icons/mob/actions/actions_spells.dmi' button_icon_state = "mindread" @@ -153,19 +75,15 @@ stamina_cost = 40 target_msg = "You feel someone else in your head." - hand_path = /obj/item/melee/touch_attack/psyonic_mending + hand_path = /obj/item/melee/touch_attack/psionic_mending draw_message = span_notice("You ready your hand to read someones mind.") drop_message = span_notice("You lower your hand.") can_cast_on_self = FALSE + psionic_level = 3 -/datum/action/cooldown/spell/touch/psyonic/psyonic_mind_read/cast_on_hand_hit(obj/item/melee/touch_attack/hand, atom/victim, mob/living/carbon/mendicant) +/datum/action/cooldown/spell/touch/psionic/psionic_mind_read/cast_on_hand_hit(obj/item/melee/touch_attack/hand, atom/victim, mob/living/carbon/mendicant) if(ishuman(victim)) var/mob/living/carbon/human/human_victim = victim - if(human_victim.is_wearing_tinfoil_hat()) - to_chat(human_victim, span_clockred("Your tinfoil hat vibrates, protecting your brain from some kind of invisible rays!")) - to_chat(owner, span_clockred("As soon as you touch [human_victim]s head, suddnely pictures of your own mind appear! Looks like the tinfoil hat on their head is interfering.")) - drain_mana() - return TRUE if(human_victim.mind && human_victim.stat != DEAD) if(human_victim.can_block_magic(antimagic_flags)) to_chat(human_victim, span_bolddanger("Psionic nearby tries to read your mind!")) @@ -183,16 +101,14 @@ else return FALSE -/datum/action/cooldown/spell/touch/psyonic/psyonic_mind_read/proc/read_mind(mob/living/carbon/human/patient) +/datum/action/cooldown/spell/touch/psionic/psionic_mind_read/proc/read_mind(mob/living/carbon/human/patient) if(patient.can_block_magic(MAGIC_RESISTANCE_MIND, charge_cost = 0)) to_chat(owner, span_warning("As you reach into [patient]'s mind, \ you are stopped by a mental blockage. It seems you've been foiled.")) return - - if(issynthetic(patient) && secondary_school != "Psychokinesis") + if(issynthetic(patient) && cast_power < 2) to_chat(owner, span_notice("I dont know how to work with synths. It's just zeros and ones. How am I supposed to get info out of this metal bucket?")) return - var/text_to_show = "" var/list/recent_speech = patient.copy_recent_speech(copy_amount = 10) @@ -226,7 +142,7 @@ to_chat(owner, boxed_message(span_infoplain(text_to_show))) // Возвращает размытый текст о профессии -/datum/action/cooldown/spell/touch/psyonic/psyonic_mind_read/proc/get_job_fluff(mob/living/carbon/human/patient) +/datum/action/cooldown/spell/touch/psionic/psionic_mind_read/proc/get_job_fluff(mob/living/carbon/human/patient) var/datum/mind/mind_to_read = patient.mind var/datum/job/patient_job = mind_to_read.assigned_role var/text_to_return = "" @@ -258,7 +174,7 @@ return span_notice(text_to_return) // Возвращает воспоминания разума. Имба против таторов, так как там хранится код от аплинка. А ну и банковский айди. -/datum/action/cooldown/spell/touch/psyonic/psyonic_mind_read/proc/get_memories(mob/living/carbon/human/patient) +/datum/action/cooldown/spell/touch/psionic/psionic_mind_read/proc/get_memories(mob/living/carbon/human/patient) var/datum/mind/mind_to_read = patient.mind if(mind_to_read) var/itogo_text = "" @@ -272,52 +188,46 @@ return "I cant read [patient.p_their()] memories. Maybe there are none?" + "
" // Stun batong на минималках. Исчезает после одного удара -/datum/action/cooldown/spell/touch/psyonic/psyonic_agony - name = "Psyonic Agony" +/datum/action/cooldown/spell/touch/psionic/psionic_agony + name = "Psionic Agony" desc = "Deals pain." button_icon = 'icons/obj/weapons/baton.dmi' button_icon_state = "stunbaton_active" cooldown_time = 0.5 SECONDS mana_cost = 40 stamina_cost = 0 - hand_path = /obj/item/melee/touch_attack/psyonic_mending + hand_path = /obj/item/melee/touch_attack/psionic_mending draw_message = span_notice("You ready your hand to deal pain.") drop_message = span_notice("You lower your hand.") can_cast_on_self = TRUE // Упс :) -/datum/action/cooldown/spell/touch/psyonic/psyonic_agony/cast_on_hand_hit(obj/item/melee/touch_attack/hand, atom/victim, mob/living/carbon/mendicant) +/datum/action/cooldown/spell/touch/psionic/psionic_agony/cast_on_hand_hit(obj/item/melee/touch_attack/hand, atom/victim, mob/living/carbon/mendicant) if(ishuman(victim)) var/mob/living/carbon/human/human_victim = victim - if(human_victim.is_wearing_tinfoil_hat()) - to_chat(human_victim, span_clockred("Your tinfoil hat vibrates, protecting your brain from some kind of invisible rays!")) - to_chat(owner, span_clockred("As soon as you touch [human_victim], your own body hurts as hell! Looks like the tinfoil hat on their head is interfering.")) - psyonic_attack(owner) - drain_mana() - return TRUE if(human_victim.can_block_magic(antimagic_flags)) to_chat(human_victim, span_notice("Psionic nearby tries to attack you, but fails.")) to_chat(owner, span_notice("You can't attack them. They have some kind of protection.")) return FALSE - if(issynthetic(human_victim) && secondary_school != "Psychokinesis") + if(issynthetic(human_victim) && cast_power < 2) human_victim.visible_message(span_danger("[owner] slaps [human_victim] with his hand. Nothing happens. Wow!"), span_warning("You slap [human_victim], but nothing happens. You cannot transfer your energy through metal."), blind_message = span_hear("You hear a slap.")) return FALSE else to_chat(human_victim, span_warning("Pain floods your body as soon as [owner] touches you!.")) - psyonic_attack(human_victim) - log_combat(owner, human_victim, "psyonically stunned") + psionic_attack(human_victim) + log_combat(owner, human_victim, "psionically stunned") drain_mana() return TRUE else return FALSE // Прок удара -/datum/action/cooldown/spell/touch/psyonic/psyonic_agony/proc/psyonic_attack(mob/living/carbon/human/patient) +/datum/action/cooldown/spell/touch/psionic/psionic_agony/proc/psionic_attack(mob/living/carbon/human/patient) patient.apply_damage(35, STAMINA) // Стандартный стан батонг addtimer(CALLBACK(src, PROC_REF(apply_stun_effect), patient), 2 SECONDS) -/datum/action/cooldown/spell/touch/psyonic/psyonic_agony/proc/apply_stun_effect(mob/living/carbon/human/patient) +/datum/action/cooldown/spell/touch/psionic/psionic_agony/proc/apply_stun_effect(mob/living/carbon/human/patient) patient.visible_message(span_danger("[owner] slaps [patient] with his hand, sparks flying out of it!"), span_warning("You slap [patient], stunning him."), blind_message = span_hear("You hear a slap and an electrical crackling afterwards.")) @@ -329,49 +239,39 @@ patient.Knockdown((cast_power/2) SECONDS) // Станит на непродолжительный срок(~0.5 сек) и заставляет выкинуть вещи из рук -/datum/action/cooldown/spell/pointed/psyonic/psyonic_spasm - name = "Psyonic Spasm" +/datum/action/cooldown/spell/pointed/psionic/psionic_spasm + name = "Psionic Spasm" desc = "Activate neurons in victims mucles, briefly stunning them and forcing to drop everything in their hands. Can be cast over distance. Silent." - button_icon = 'tff_modular/modules/psyonics/icons/actions.dmi' + button_icon = 'tff_modular/modules/psionics/icons/actions.dmi' button_icon_state = "spasm" cooldown_time = 1 SECONDS mana_cost = 40 target_msg = "Your muscles spasm!" active_msg = "You prepare to stun a target..." -/datum/action/cooldown/spell/pointed/psyonic/psyonic_spasm/New(Target) - . = ..() - if(secondary_school == "Energistics") - cast_power += 1 - -/datum/action/cooldown/spell/pointed/psyonic/psyonic_spasm/is_valid_target(atom/cast_on) +/datum/action/cooldown/spell/pointed/psionic/psionic_spasm/is_valid_target(atom/cast_on) if(!ishuman(cast_on)) return FALSE - if(issynthetic(cast_on) && secondary_school != "Psychokinesis") + + if(issynthetic(cast_on) && cast_power < 2) to_chat(owner, span_notice("I dont know how to work with synths.")) return FALSE return TRUE -/datum/action/cooldown/spell/pointed/psyonic/psyonic_spasm/cast(mob/living/carbon/human/cast_on) +/datum/action/cooldown/spell/pointed/psionic/psionic_spasm/cast(mob/living/carbon/human/cast_on) . = ..() - if(cast_on.is_wearing_tinfoil_hat()) - to_chat(cast_on, span_clockred("Your tinfoil hat vibrates, protecting your brain from some kind of invisible rays!")) - to_chat(owner, span_clockred("As soon as you try to spasm [cast_on], your own body twitches! Looks like the tinfoil hat on their head is interfering.")) - drain_mana() - stun(owner) - return TRUE if(cast_on.can_block_magic(antimagic_flags)) - to_chat(cast_on, span_warning("Your body is assaulted with psyonic energy!")) + to_chat(cast_on, span_warning("Your body is assaulted with psionic energy!")) else to_chat(cast_on, span_warning(target_msg)) - log_combat(owner, cast_on, "psyonically spasmed") + log_combat(owner, cast_on, "psionically spasmed") stun(cast_on) drain_mana() return TRUE // Сам стан -/datum/action/cooldown/spell/pointed/psyonic/psyonic_spasm/proc/stun(mob/living/carbon/human/cast_on) +/datum/action/cooldown/spell/pointed/psionic/psionic_spasm/proc/stun(mob/living/carbon/human/cast_on) cast_on.Stun(0.2 SECONDS * cast_power) /** @@ -382,10 +282,10 @@ * * в агрограбе * * без движения жертвы или псионика */ -/datum/action/cooldown/spell/touch/psyonic/psyonic_hypnosis - name = "Psyonic Hypnosis" +/datum/action/cooldown/spell/touch/psionic/psionic_hypnosis + name = "Psionic Hypnosis" desc = "Implant a looping pattern into victims head." - button_icon = 'tff_modular/modules/psyonics/icons/actions.dmi' + button_icon = 'tff_modular/modules/psionics/icons/actions.dmi' button_icon_state = "hypno" cooldown_time = 10 SECONDS @@ -393,20 +293,14 @@ stamina_cost = 50 // Но выматывет target_msg = "Your get a headache." - hand_path = /obj/item/melee/touch_attack/psyonic_mending + hand_path = /obj/item/melee/touch_attack/psionic_mending draw_message = span_notice("You ready your hand to hypnotize a victim.") drop_message = span_notice("You lower your hand.") can_cast_on_self = FALSE // No -/datum/action/cooldown/spell/touch/psyonic/psyonic_hypnosis/cast_on_hand_hit(obj/item/melee/touch_attack/hand, atom/victim, mob/living/carbon/mendicant) +/datum/action/cooldown/spell/touch/psionic/psionic_hypnosis/cast_on_hand_hit(obj/item/melee/touch_attack/hand, atom/victim, mob/living/carbon/mendicant) if(ishuman(victim) && mendicant.grab_state == GRAB_AGGRESSIVE && mendicant.pulling == victim) var/mob/living/carbon/human/human_victim = victim - if(human_victim.is_wearing_tinfoil_hat()) - to_chat(human_victim, span_clockred("Your tinfoil hat vibrates, protecting your brain from some kind of invisible rays!")) - to_chat(owner, span_clockred("As soon as you touch [human_victim]s head, you feel incredibly sleepy! Looks like the tinfoil hat on their head is interfering.")) - drain_mana() - addtimer(CALLBACK(owner, TYPE_PROC_REF(/mob/living, Stun), 60, TRUE, TRUE), 15) - return TRUE if(HAS_MIND_TRAIT(human_victim, TRAIT_UNCONVERTABLE)) // Не работает на людей с МЩ to_chat(owner, span_warning("Victims mind is too strong for you to penetrate.")) return FALSE @@ -429,7 +323,7 @@ to_chat(owner, span_notice("You need to grab a human in aggressive grab to hypnotize them.")) return FALSE -/datum/action/cooldown/spell/touch/psyonic/psyonic_hypnosis/proc/hypnotize(mob/living/carbon/human/patient, hypnophrase) +/datum/action/cooldown/spell/touch/psionic/psionic_hypnosis/proc/hypnotize(mob/living/carbon/human/patient, hypnophrase) patient.cure_trauma_type(/datum/brain_trauma/hypnosis, TRAUMA_RESILIENCE_SURGERY) owner.log_message("hypnotised [key_name(patient)] with the phrase '[hypnophrase]'", LOG_ATTACK, color="red") @@ -440,8 +334,8 @@ addtimer(CALLBACK(patient, TYPE_PROC_REF(/mob/living, Stun), 60, TRUE, TRUE), 15) // Ослепляет цель на дистанции на ~15 секунд. Способность максимального уровня -/datum/action/cooldown/spell/pointed/psyonic/psyonic_blind - name = "Psyonic Blind" +/datum/action/cooldown/spell/pointed/psionic/psionic_blind + name = "Psionic Blind" desc = "Interfere with the way neuron signals are transmitted in the victims eyes." button_icon_state = "blind" ranged_mousepointer = 'icons/effects/mouse_pointers/blind_target.dmi' @@ -450,7 +344,7 @@ target_msg = "You eyes hurt!" active_msg = "You prepare to blind a target..." -/datum/action/cooldown/spell/pointed/psyonic/psyonic_blind/is_valid_target(atom/cast_on) +/datum/action/cooldown/spell/pointed/psionic/psionic_blind/is_valid_target(atom/cast_on) if(!ishuman(cast_on)) return FALSE else @@ -458,30 +352,24 @@ if(victim.is_blind()) to_chat(owner, span_notice("[victim] is already blind.")) return FALSE - if(issynthetic(cast_on) && secondary_school != "Psychokinesis") + if(issynthetic(cast_on) && cast_power < 2) to_chat(owner, span_notice("I dont know how to work with synths.")) return FALSE return TRUE -/datum/action/cooldown/spell/pointed/psyonic/psyonic_blind/cast(mob/living/carbon/human/cast_on) +/datum/action/cooldown/spell/pointed/psionic/psionic_blind/cast(mob/living/carbon/human/cast_on) . = ..() - if(cast_on.is_wearing_tinfoil_hat()) - to_chat(cast_on, span_clockred("Your tinfoil hat vibrates, protecting your brain from some kind of invisible rays!")) - to_chat(owner, span_clockred("As soon as you try to blind [cast_on], your own eyes close on its own! Looks like the tinfoil hat on their head is interfering.")) - drain_mana() - blind(owner) - return TRUE if(cast_on.can_block_magic(antimagic_flags)) - to_chat(cast_on, span_warning("Your eyes are burned with psyonic energy!")) + to_chat(cast_on, span_warning("Your eyes are burned with psionic energy!")) else to_chat(cast_on, span_warning(target_msg)) - log_combat(owner, cast_on, "psyonically blinded") + log_combat(owner, cast_on, "psionically blinded") blind(cast_on) drain_mana() return TRUE -/datum/action/cooldown/spell/pointed/psyonic/psyonic_blind/proc/blind(mob/living/carbon/human/cast_on) +/datum/action/cooldown/spell/pointed/psionic/psionic_blind/proc/blind(mob/living/carbon/human/cast_on) cast_on.adjust_temp_blindness( (10 + cast_power * 2) SECONDS) #undef IS_HYPNOTIZED diff --git a/tff_modular/modules/psionics/code/conjure_item/conjure_item.dm b/tff_modular/modules/psionics/code/conjure_item/conjure_item.dm new file mode 100644 index 00000000000..7bc2b9106c0 --- /dev/null +++ b/tff_modular/modules/psionics/code/conjure_item/conjure_item.dm @@ -0,0 +1,99 @@ +// Спавнит пси-клинок в руке. Сила зависит от уровня псионика +/datum/action/cooldown/spell/conjure_item/psionic/psiblade + name = "Psi blade" + desc = "Concentrates psionic energy to create a sharp blade in your hand." + button_icon = 'icons/obj/weapons/transforming_energy.dmi' + button_icon_state = "blade" + cooldown_time = 1.5 SECONDS + item_type = /obj/item/melee/psionic_blade + mana_cost = 40 + stamina_cost = 0 + psionic_level = 2 + point_cost = 3 + +/obj/item/melee/psionic_blade + name = "psionic blade" + desc = "A concentrated collection of particles and energy that looks like a swords blade.." + icon = 'icons/obj/weapons/transforming_energy.dmi' + icon_state = "blade" + inhand_icon_state = "blade" + lefthand_file = 'icons/mob/inhands/weapons/swords_lefthand.dmi' + righthand_file = 'icons/mob/inhands/weapons/swords_righthand.dmi' + w_class = WEIGHT_CLASS_HUGE + force = 30 + throwforce = 10 + hitsound = 'sound/items/weapons/blade1.ogg' + attack_verb_continuous = list("attacks", "slashes", "stabs", "slices", "tears", "lacerates", "rips", "dices", "cuts") + attack_verb_simple = list("attack", "slash", "stab", "slice", "tear", "lacerate", "rip", "dice", "cut") + sharpness = SHARP_EDGED + block_chance = 0 + item_flags = DROPDEL | ABSTRACT | HAND_ITEM + color = COLOR_BRIGHT_BLUE + +/obj/item/melee/psionic_blade/New(loc, power) + . = ..() + ADD_TRAIT(src, TRAIT_EXAMINE_SKIP, INNATE_TRAIT) + +// Спавнит зажигалку в руке. Очень полезно +/datum/action/cooldown/spell/conjure_item/psionic/psilighter + name = "Psi lighter" + desc = "Concentrates psionic energy to create a small flame in your hand." + button_icon = 'icons/obj/cigarettes.dmi' + button_icon_state = "match_lit" + cooldown_time = 1.5 SECONDS + item_type = /obj/item/psionic_fire + mana_cost = 5 + stamina_cost = 0 + point_cost = 1 + +/datum/action/cooldown/spell/conjure_item/psionic/psilighter/post_created(atom/cast_on, atom/created) + . = ..() + var/obj/item/psionic_fire/fire = created + fire.force *= cast_power + +/obj/item/psionic_fire + name = "small psionic fire" + desc = "Small bluish fire, that jumps on your fingers and surprisigly doesn't burn them." + icon = 'icons/obj/weapons/hand.dmi' + icon_state = "greyscale" + color = COLOR_BRIGHT_BLUE + inhand_icon_state = "greyscale" + light_range = 2 + light_power = 2 + light_color = LIGHT_COLOR_LIGHT_CYAN + light_on = TRUE + damtype = BURN + force = 5 + attack_verb_continuous = list("burns", "singes") + attack_verb_simple = list("burn", "singe") + resistance_flags = FIRE_PROOF + w_class = WEIGHT_CLASS_HUGE + light_system = OVERLAY_LIGHT + toolspeed = 2 + tool_behaviour = TOOL_WELDER + item_flags = DROPDEL | ABSTRACT | HAND_ITEM + heat = HIGH_TEMPERATURE_REQUIRED - 100 + +/obj/item/psionic_fire/Initialize(mapload) + . = ..() + + ADD_TRAIT(src, TRAIT_EXAMINE_SKIP, INNATE_TRAIT) + +// Спавнит омни инструмент в руке псионика. Аналог абдукторского +/datum/action/cooldown/spell/conjure_item/psionic/psitool + name = "Psi tool" + desc = "Concentrates psionic energy to create a universal tool." + button_icon = 'icons/obj/antags/abductor.dmi' + button_icon_state = "omnitool" + cooldown_time = 60 SECONDS + item_type = /obj/item/psionic_omnitool + mana_cost = 20 + stamina_cost = 30 + point_cost = 1 + +/datum/action/cooldown/spell/conjure_item/psionic/psiblade/make_item(atom/caster) + var/obj/item/made_item = new item_type(caster.loc, cast_power) + LAZYADD(item_refs, WEAKREF(made_item)) + var/mob/living/carbon/human/caster_pawn = owner + caster_pawn.emote_snap() + return made_item diff --git a/tff_modular/modules/psionics/code/cyberimp.dm b/tff_modular/modules/psionics/code/cyberimp.dm new file mode 100644 index 00000000000..d8bc90d3936 --- /dev/null +++ b/tff_modular/modules/psionics/code/cyberimp.dm @@ -0,0 +1,40 @@ +#define ORGAN_SLOT_BRAIN_PSIONIC "brain_psionic" + +// Не позволяет мане регенерироваться +/obj/item/organ/internal/cyberimp/brain/anti_psionic + name = "Psionic Amplifier Model N" + desc = "This implant will prohibit psionics from regenereting their energy." + icon_state = "brain_implant_rebooter" + slot = ORGAN_SLOT_BRAIN_PSIONIC + +/obj/item/organ/internal/cyberimp/brain/anti_psionic/on_mob_insert(mob/living/carbon/organ_owner, special, movement_flags) + . = ..() + ADD_TRAIT(organ_owner, TRAIT_PSI_SUPPRESSED, IMPLANT_TRAIT) + +/obj/item/organ/internal/cyberimp/brain/anti_psionic/on_mob_remove(mob/living/carbon/organ_owner, special) + . = ..() + REMOVE_TRAIT(organ_owner, TRAIT_PSI_SUPPRESSED, IMPLANT_TRAIT) + +// Увеличивает реген маны в 2 раза +/obj/item/organ/internal/cyberimp/brain/pro_psionic + name = "Psionic Amplifier Model A" + desc = "This implant will boost psionics energy regeneration by two times." + icon_state = "brain_implant_rebooter" + slot = ORGAN_SLOT_BRAIN_PSIONIC + +/obj/item/organ/internal/cyberimp/brain/pro_psionic/on_mob_insert(mob/living/carbon/organ_owner, special, movement_flags) + . = ..() + ADD_TRAIT(organ_owner, TRAIT_PSIONIC_IMPLANT, IMPLANT_TRAIT) + +/obj/item/organ/internal/cyberimp/brain/pro_psionic/on_mob_remove(mob/living/carbon/organ_owner, special) + . = ..() + REMOVE_TRAIT(organ_owner, TRAIT_PSIONIC_IMPLANT, IMPLANT_TRAIT) + +/datum/supply_pack/medical/psionic_implants + name = "Psionic Implants" + desc = "A crate containing two experimental psionic implants, which work ONLY on psionic users. No warranty." + cost = CARGO_CRATE_VALUE * 5 + contains = list(/obj/item/organ/internal/cyberimp/brain/anti_psionic = 1, + /obj/item/organ/internal/cyberimp/brain/pro_psionic = 1) + crate_name = "Psionic implant crate" + discountable = SUPPLY_PACK_RARE_DISCOUNTABLE diff --git a/tff_modular/modules/psyonics/code/documents.dm b/tff_modular/modules/psionics/code/documents.dm similarity index 59% rename from tff_modular/modules/psyonics/code/documents.dm rename to tff_modular/modules/psionics/code/documents.dm index 81a6386c6ac..468590e6082 100644 --- a/tff_modular/modules/psyonics/code/documents.dm +++ b/tff_modular/modules/psionics/code/documents.dm @@ -1,7 +1,7 @@ -/obj/item/card/psyonic_license - name = "psyonic license" - desc = "An official license given to psyonic users by the NanoTrasen Psyonics and Eugenics Division itself." - icon = 'tff_modular/modules/psyonics/icons/card.dmi' +/obj/item/card/psionic_license + name = "psionic license" + desc = "An official license given to psionic users by the NanoTrasen Psionics and Eugenics Division itself." + icon = 'tff_modular/modules/psionics/icons/card.dmi' icon_state = "card_psy" inhand_icon_state = "card-id" lefthand_file = 'icons/mob/inhands/equipment/idcards_lefthand.dmi' @@ -11,29 +11,26 @@ drop_sound = 'sound/items/handling/id_card/id_card_drop1.ogg' sound_vary = TRUE resistance_flags = FIRE_PROOF - var/datum/psyonic_licence_datum/owner_info + var/datum/psionic_licence_datum/owner_info -/obj/item/card/psyonic_license/New(mob/living/carbon/human/owner) +/obj/item/card/psionic_license/New(mob/living/carbon/human/owner) . = ..() owner_info = new(owner) -/obj/item/card/psyonic_license/ui_interact(mob/user, datum/tgui/ui) +/obj/item/card/psionic_license/ui_interact(mob/user, datum/tgui/ui) if(!owner_info) balloon_alert(user, "The card isn't bound to anyone!") return ui = SStgui.try_update_ui(user, src, ui) if(!ui) - ui = new(user, src, "PsyonicLicense") + ui = new(user, src, "PsionicLicense") ui.set_autoupdate(FALSE) ui.open() -/obj/item/card/psyonic_license/ui_static_data(mob/user) +/obj/item/card/psionic_license/ui_static_data(mob/user) var/list/data = list() - - data["primary_school"] = owner_info.primary_school - data["secondary_school"] = owner_info.secondary_school - data["psyonic_level"] = owner_info.psyonic_level + data["psionic_level"] = owner_info.psionic_level data["owner_name"] = owner_info.owner_name data["owner_age"] = owner_info.owner_age data["owner_preview"] = owner_info.owner_preview @@ -41,29 +38,25 @@ return data -/datum/psyonic_licence_datum +/datum/psionic_licence_datum var/datum/weakref/original_owner var/owner_name var/owner_age - var/psyonic_level - var/primary_school - var/secondary_school + var/psionic_level var/owner_species var/icon/owner_preview -/datum/psyonic_licence_datum/New(mob/living/carbon/human/human_owner) +/datum/psionic_licence_datum/New(mob/living/carbon/human/human_owner) . = ..() original_owner = WEAKREF(human_owner) if(original_owner && original_owner.resolve()) var/mob/living/carbon/human/owner = original_owner.resolve() if(!istype(owner, /mob/living/carbon/human)) return - if(!owner.ispsyonic()) + if(!owner.get_psionic()) return - var/datum/quirk/psyonic/quirk_holder = owner.get_quirk(/datum/quirk/psyonic) - psyonic_level = quirk_holder.psyonic_level_string - primary_school = quirk_holder.school - secondary_school = quirk_holder.secondary_school + var/datum/psionic/psi = owner.get_psionic() + psionic_level = psi.psionic_level_string owner_name = owner.real_name owner_age = owner.age diff --git a/tff_modular/modules/psyonics/code/energistics.dm b/tff_modular/modules/psionics/code/energistics.dm similarity index 51% rename from tff_modular/modules/psyonics/code/energistics.dm rename to tff_modular/modules/psionics/code/energistics.dm index 011616ccdfa..ca55d772ac0 100644 --- a/tff_modular/modules/psyonics/code/energistics.dm +++ b/tff_modular/modules/psionics/code/energistics.dm @@ -6,44 +6,24 @@ /// Elecrocute - добавляет мутацию shock touch /// Freeze - заковывает моба в лёд на небольшой промежуток. -// Добавить школу энергетики -/mob/living/carbon/human/proc/try_add_energistics_school(tier = 0, additional_school = 0) - if(tier >= 0) - var/datum/action/new_action = new /datum/action/cooldown/spell/touch/psyonic/psyonic_discharge(src.mind || src, tier, additional_school) - new_action.Grant(src) - var/datum/action/new_action2 = new /datum/action/cooldown/spell/pointed/psyonic/psyonic_spark(src.mind || src, tier, additional_school) - new_action2.Grant(src) - if(tier >= 1) - var/datum/action/new_action = new /datum/action/cooldown/spell/basic_projectile/psyonic_laser(src.mind || src, tier, additional_school) - new_action.Grant(src) - if(tier >= 2) - var/datum/action/new_action = new /datum/action/cooldown/spell/touch/psyonic/psyonic_emp(src.mind || src, tier, additional_school) - new_action.Grant(src) - if(tier >= 3) - var/datum/action/new_action = new /datum/action/cooldown/spell/psyonic/psionic_electrocute(src.mind || src, tier, additional_school) - new_action.Grant(src) - if(tier >= 4) - var/datum/action/new_action = new /datum/action/cooldown/spell/pointed/projectile/psyonic/psyonic_freeze(src.mind || src) - new_action.Grant(src) - // Разрядка АПЦ или батареек в обмен на ману -/datum/action/cooldown/spell/touch/psyonic/psyonic_discharge - name = "Psyonic Discharge" - desc = "Try to discharge battery and convert electricity into raw psyonic energy." +/datum/action/cooldown/spell/touch/psionic/psionic_discharge + name = "Psionic Discharge" + desc = "Try to discharge battery and convert electricity into raw psionic energy." button_icon = 'modular_nova/modules/aesthetics/cells/icons/cell.dmi' button_icon_state = "icell" cooldown_time = 30 SECONDS mana_cost = 0 stamina_cost = 15 - hand_path = /obj/item/melee/touch_attack/psyonic_mending + hand_path = /obj/item/melee/touch_attack/psionic_mending draw_message = span_notice("You ready your hand to discharge an energy source.") drop_message = span_notice("You lower your hand.") can_cast_on_self = FALSE -/datum/action/cooldown/spell/touch/psyonic/psyonic_discharge/is_valid_target(atom/cast_on) +/datum/action/cooldown/spell/touch/psionic/psionic_discharge/is_valid_target(atom/cast_on) return isatom(cast_on) -/datum/action/cooldown/spell/touch/psyonic/psyonic_discharge/cast_on_hand_hit(obj/item/melee/touch_attack/hand, atom/victim, mob/living/carbon/mendicant) +/datum/action/cooldown/spell/touch/psionic/psionic_discharge/cast_on_hand_hit(obj/item/melee/touch_attack/hand, atom/victim, mob/living/carbon/mendicant) if(HAS_TRAIT(mendicant, TRAIT_MINDSHIELD)) // Womp womp to_chat(mendicant, span_warning("As soon as you touch [victim], your energy dissipates without a trace. Mindshield implant messes up your concentration.")) return FALSE @@ -52,14 +32,14 @@ span_notice("You press your hands against [victim]."), blind_message = span_hear("You hear electrical crackling.")) if(do_after(mendicant, 2.5 SECONDS, victim, IGNORE_SLOWDOWNS, TRUE)) - var/datum/quirk/psyonic/quirk_holder = mendicant.get_quirk(/datum/quirk/psyonic) - if(!quirk_holder) + var/datum/psionic/psi_holder = mendicant.get_psionic() + if(!psi_holder) return FALSE if(istype(victim, /obj/item/stock_parts/power_store)) var/obj/item/stock_parts/power_store/batt = victim var/to_charge = (batt.charge / STANDARD_CELL_VALUE) batt.use(batt.charge(), TRUE) - quirk_holder.mana_level = clamp(quirk_holder.mana_level + to_charge, 0, quirk_holder.max_mana) + psi_holder.mana_level = clamp(psi_holder.mana_level + to_charge, 0, psi_holder.max_mana) else if(istype(victim, /obj/machinery/power/apc)) var/obj/machinery/power/apc/target_apc = victim var/obj/item/stock_parts/power_store/batt = target_apc.cell @@ -68,7 +48,7 @@ return FALSE var/to_charge = (batt.charge() / (STANDARD_BATTERY_CHARGE/10)) batt.use(batt.charge(), TRUE) - quirk_holder.mana_level = clamp(quirk_holder.mana_level + to_charge, 0, quirk_holder.max_mana) + psi_holder.mana_level = clamp(psi_holder.mana_level + to_charge, 0, psi_holder.max_mana) else to_chat(owner, span_notice("You've failed to discharge energy.")) return TRUE @@ -76,8 +56,8 @@ return FALSE // Создаёт искры в указанном месте -/datum/action/cooldown/spell/pointed/psyonic/psyonic_spark - name = "Psyonic Spark" +/datum/action/cooldown/spell/pointed/psionic/psionic_spark + name = "Psionic Spark" desc = "Cause some sparks to appear at a place of your choice." button_icon = 'icons/effects/effects.dmi' button_icon_state = "blessed" @@ -85,12 +65,12 @@ mana_cost = 10 active_msg = "You prepare to create sparks..." -/datum/action/cooldown/spell/pointed/psyonic/psyonic_spark/is_valid_target(atom/cast_on) +/datum/action/cooldown/spell/pointed/psionic/psionic_spark/is_valid_target(atom/cast_on) if(!isturf(cast_on)) return FALSE return TRUE -/datum/action/cooldown/spell/pointed/psyonic/psyonic_spark/cast(turf/cast_on) +/datum/action/cooldown/spell/pointed/psionic/psionic_spark/cast(turf/cast_on) . = ..() var/mob/living/carbon/human/caster = owner caster.emote_snap() @@ -101,9 +81,9 @@ return TRUE // Стреляет по направлению куклы псионика фотонной пушкой. Считайте аналог флешки -/datum/action/cooldown/spell/basic_projectile/psyonic_laser +/datum/action/cooldown/spell/basic_projectile/psionic_laser name = "Photon Laser" - desc = "Channels psyonic energy into a weak concentrated photon laser." + desc = "Channels psionic energy into a weak concentrated photon laser." button_icon = 'icons/obj/weapons/guns/projectiles.dmi' button_icon_state = "solarflare" cooldown_time = 0 SECONDS @@ -111,30 +91,30 @@ mana_cost = 10 projectile_type = /obj/projectile/energy/photon -/datum/action/cooldown/spell/basic_projectile/psyonic_laser/cast(atom/cast_on) +/datum/action/cooldown/spell/basic_projectile/psionic_laser/cast(atom/cast_on) var/mob/living/carbon/human/caster = owner - var/datum/quirk/psyonic/quirk_holder = caster.get_quirk(/datum/quirk/psyonic) - if(!(quirk_holder && (quirk_holder.mana_level - mana_cost) >= 0)) + var/datum/psionic/psi_holder = caster.get_psionic() + if(!(psi_holder && (psi_holder.mana_level - mana_cost) >= 0)) return FALSE else - quirk_holder.mana_level -= mana_cost + psi_holder.mana_level -= mana_cost ..() // Создаёт ЕМП в месте удара руки -/datum/action/cooldown/spell/touch/psyonic/psyonic_emp - name = "Psyonic EMP" +/datum/action/cooldown/spell/touch/psionic/psionic_emp + name = "Psionic EMP" desc = "Try to cause a small local EMP." button_icon = 'icons/obj/weapons/grenade.dmi' button_icon_state = "emp" cooldown_time = 15 SECONDS mana_cost = 40 stamina_cost = 40 - hand_path = /obj/item/melee/touch_attack/psyonic_mending + hand_path = /obj/item/melee/touch_attack/psionic_mending draw_message = span_notice("You ready your hand to cause an EMP.") drop_message = span_notice("You lower your hand.") can_cast_on_self = TRUE -/datum/action/cooldown/spell/touch/psyonic/psyonic_emp/cast_on_hand_hit(obj/item/melee/touch_attack/hand, atom/victim, mob/living/carbon/mendicant) +/datum/action/cooldown/spell/touch/psionic/psionic_emp/cast_on_hand_hit(obj/item/melee/touch_attack/hand, atom/victim, mob/living/carbon/mendicant) if(isatom(victim)) empulse(victim, 1, cast_power/2) drain_mana() @@ -143,17 +123,17 @@ return FALSE // Даёт мутацию Shock Touch -/datum/action/cooldown/spell/psyonic/psionic_electrocute - name = "Psyonic Shock Touch" +/datum/action/cooldown/spell/psionic/psionic_electrocute + name = "Psionic Shock Touch" desc = "Force yourself to recieve shock touch mutation." cooldown_time = 60 SECONDS mana_cost = 60 stamina_cost = 60 -/datum/action/cooldown/spell/psyonic/psionic_electrocute/is_valid_target(atom/cast_on) +/datum/action/cooldown/spell/psionic/psionic_electrocute/is_valid_target(atom/cast_on) return !issynthetic(cast_on) -/datum/action/cooldown/spell/psyonic/psionic_electrocute/cast(mob/living/cast_on) +/datum/action/cooldown/spell/psionic/psionic_electrocute/cast(mob/living/cast_on) . = ..() if(!ishuman(cast_on)) return FALSE @@ -163,40 +143,3 @@ to_mutate.dna.add_mutation(/datum/mutation/shock, MUTATION_SOURCE_ACTIVATED) drain_mana() return TRUE - -// Стреляет снарядом вотчера, замораживая жертву. Требует почти максимально возможный запас маны -/datum/action/cooldown/spell/pointed/projectile/psyonic/psyonic_freeze - name = "Psyonic Freeze" - desc = "Fire freezing shark at a target, encasing them in an ice prison." - button_icon = 'icons/effects/freeze.dmi' - button_icon_state = "ice_cube" - cooldown_time = 1 SECONDS - mana_cost = 80 - cast_range = 9 - active_msg = "You prepare to fire ice shard..." - deactive_msg = "You relax." - projectile_type = /obj/projectile/temp/watcher/psyonic_freeze - -/datum/action/cooldown/spell/pointed/projectile/psyonic/psyonic_freeze/is_valid_target(atom/cast_on) - if(!isliving(cast_on)) - return FALSE - return TRUE - -/datum/action/cooldown/spell/pointed/projectile/psyonic/psyonic_freeze/cast(mob/living/cast_on) - drain_mana() - . = ..() - return TRUE - -// Вывел в отдельный тип, потому что в оригинальном ice_wing снаряде видимо баг(?) и он не замораживает, хотя должен. -/obj/projectile/temp/watcher/psyonic_freeze - name = "freezing blast" - damage = 0 // Нет дамага, вместо этого замораживает - -/obj/projectile/temp/watcher/psyonic_freeze/apply_status(mob/living/target) - if(HAS_TRAIT(target, TRAIT_RESISTCOLD)) // Вот тут у ice_wing лишний ! - return - target.apply_status_effect(/datum/status_effect/freon/watcher/psyonic_freeze) - -/datum/status_effect/freon/watcher/psyonic_freeze - duration = 4 // 4 секунды вместо 8 - can_melt = TRUE diff --git a/tff_modular/modules/psionics/code/hud.dm b/tff_modular/modules/psionics/code/hud.dm new file mode 100644 index 00000000000..75fe5050e0a --- /dev/null +++ b/tff_modular/modules/psionics/code/hud.dm @@ -0,0 +1,88 @@ +#define UI_PSI_DISPLAY "EAST:2,CENTER+3:-8" +#define UI_PSI_SIGNAL "EAST:2, CENTER+2:-8" +#define FORMAT_PSI_HUD_TEXT(valuecolor, value) MAPTEXT("
[round(value,1)]
") + +/atom/movable/screen/psionic + icon = 'tff_modular/modules/psionics/icons/psi_hud.dmi' + mouse_over_pointer = MOUSE_HAND_POINTER + +/atom/movable/screen/psionic/psionic_energy + name = "Psionic Status" + icon_state = "psi_active" + screen_loc = UI_PSI_DISPLAY + +/atom/movable/screen/psionic/psionic_energy/Click() + . = ..() + var/list/msg = list() + var/mob/living/owner_mob = hud.mymob + var/datum/psionic/psi_datum = owner_mob.get_psionic() + + if(!psi_datum) + return + + msg += span_horizonblue("This is your Psionic Status.") + msg += span_horizonblue("Here you see your current level of psi-energy. This is used for all of your psi spells.") + msg += span_horizonblue("\nYour psi-energy restores passively, but some conditions can speed it up.") + msg += span_horizonblue("If your psi energy becomes zero, you will begin to suffer from exhaustion.") + + var/psylevel + switch(psi_datum.mana_level) + if(0) + psylevel = "exhaustion" + if(1 to INFINITY) + psylevel = "saturation" + + msg += span_horizonblue("Your current maximum is: [psi_datum.max_mana].") + + msg += span_cult_large("\nRight now, you are feeling [psylevel].") + + to_chat(usr, boxed_message(msg.Join("\n"))) + +/atom/movable/screen/psionic/psionic_signal + name = "Psionic Signal" + icon_state = "psi_broad_inactive" + screen_loc = UI_PSI_SIGNAL + +/atom/movable/screen/psionic/psionic_signal/Click(location, control, params) + . = ..() + var/list/msg = list() + var/mob/living/owner_mob = hud.mymob + var/datum/psionic/psi_datum = owner_mob.get_psionic() + + if(!psi_datum) + return + + msg += span_horizonblue("This is your Psionic Signal.") + msg += span_horizonblue("This signal allows the psionics to sense each other. When there is a psionic nearby, this signal starts to glow blue.") + msg += span_horizonblue("If your psionic being suppressed, you can't sense the psionics nearby, but they can't sense you either.") + msg += span_horizonblue("\nSome extremely powerful psionics are able to partially suppress their signal.") + + to_chat(usr, boxed_message(msg.Join("\n"))) + +/datum/psionic/proc/update_hud() + var/psi_energy_color + var/psi_energy_icon_state + switch(mana_level) + if(-100 to 0) + psi_energy_color = "#480607" + psi_energy_icon_state = "psi_suppressed" + if(1 to INFINITY) + psi_energy_color = "#00BFFF" + psi_energy_icon_state = "psi_active" + + var/atom/movable/screen/psionic/psionic_energy/psi_display = psi_owner?.hud_used?.screen_objects[HUD_PSI_DISPLAY] + psi_display?.maptext = FORMAT_PSI_HUD_TEXT(psi_energy_color, mana_level) + psi_display?.icon_state = psi_energy_icon_state + + var/psi_signal_icon_state + if(detect_psionic()) + psi_signal_icon_state = "psi_signal_active" + else + psi_signal_icon_state = "psi_signal_inactive" + + var/atom/movable/screen/psionic/psionic_signal/psi_signal = psi_owner?.hud_used?.screen_objects[HUD_PSI_SIGNAL] + psi_signal?.icon_state = psi_signal_icon_state + +#undef UI_PSI_DISPLAY +#undef UI_PSI_SIGNAL +#undef FORMAT_PSI_HUD_TEXT diff --git a/tff_modular/modules/psionics/code/pointed/pointed.dm b/tff_modular/modules/psionics/code/pointed/pointed.dm new file mode 100644 index 00000000000..94f074b9b86 --- /dev/null +++ b/tff_modular/modules/psionics/code/pointed/pointed.dm @@ -0,0 +1 @@ +// Тут все заклинания, которые являются направленными, но не прожектайлами. diff --git a/tff_modular/modules/psionics/code/projectiles/projectile.dm b/tff_modular/modules/psionics/code/projectiles/projectile.dm new file mode 100644 index 00000000000..d89807d8e1c --- /dev/null +++ b/tff_modular/modules/psionics/code/projectiles/projectile.dm @@ -0,0 +1,62 @@ +// Тут все заклинания, создающие снаряды. +/datum/action/cooldown/spell/pointed/projectile/psionic/air_bullet + name = "Psionic Air Bullet" + desc = "Wrap air in a psionic bubble, compress it, then send it flying at your enemies." + button_icon_state = "force_missle" + cooldown_time = 1 SECONDS + mana_cost = 0 + cast_range = 9 + active_msg = "You prepare to charge air bullet..." + deactive_msg = "You relax." + projectile_type = /obj/projectile/magic/air_bullet + projectile_amount = INFINITY + psionic_level = 2 + point_cost = 2 + +/datum/action/cooldown/spell/pointed/projectile/psionic/air_bullet/before_cast(atom/cast_on) + . = ..() + if(do_after(owner, 0.5 SECONDS, timed_action_flags = IGNORE_USER_LOC_CHANGE | IGNORE_SLOWDOWNS)) + return FALSE + psionic_datum.adjust_psi_energy(10) + +/obj/projectile/magic/air_bullet + damage = 20 + +// Стреляет снарядом вотчера, замораживая жертву. Требует почти максимально возможный запас маны +/datum/action/cooldown/spell/pointed/projectile/psionic/freeze + name = "Psionic Freeze" + desc = "Fire freezing shark at a target, encasing them in an ice prison." + button_icon = 'icons/effects/freeze.dmi' + button_icon_state = "ice_cube" + cooldown_time = 1 SECONDS + mana_cost = 35 + cast_range = 9 + active_msg = "You prepare to fire ice shard..." + deactive_msg = "You relax." + projectile_type = /obj/projectile/temp/watcher/psionic_freeze + psionic_level = 2 + point_cost = 1 + +/datum/action/cooldown/spell/pointed/projectile/psionic/freeze/is_valid_target(atom/cast_on) + if(!isliving(cast_on)) + return FALSE + return TRUE + +/datum/action/cooldown/spell/pointed/projectile/psionic/freeze/cast(mob/living/cast_on) + drain_mana() + . = ..() + return TRUE + +// Вывел в отдельный тип, потому что в оригинальном ice_wing снаряде видимо баг(?) и он не замораживает, хотя должен. +/obj/projectile/temp/watcher/psionic_freeze + name = "freezing blast" + damage = 0 // Нет дамага, вместо этого замораживает + +/obj/projectile/temp/watcher/psionic_freeze/apply_status(mob/living/target) + if(HAS_TRAIT(target, TRAIT_RESISTCOLD)) // Вот тут у ice_wing лишний ! + return + target.apply_status_effect(/datum/status_effect/freon/watcher/psionic_freeze) + +/datum/status_effect/freon/watcher/psionic_freeze + duration = 4 // 4 секунды вместо 8 + can_melt = TRUE diff --git a/tff_modular/modules/psionics/code/psi_shop/datum.dm b/tff_modular/modules/psionics/code/psi_shop/datum.dm new file mode 100644 index 00000000000..da10dfd7002 --- /dev/null +++ b/tff_modular/modules/psionics/code/psi_shop/datum.dm @@ -0,0 +1,97 @@ +/datum/psionic_shop + var/name = "Psi Mind" + var/datum/psionic/psi_datum + var/mob/living/psi_owner + +/datum/psionic_shop/New(psionic_mob, psionic_datum) + . = ..() + psi_datum = psionic_datum + psi_owner = psionic_mob + +/datum/psionic_shop/Destroy() + psi_datum = null + psi_owner = null + return ..() + +/datum/psionic_shop/ui_state(mob/user) + return GLOB.always_state + +/datum/psionic_shop/ui_status(mob/user, datum/ui_state/state) + if(!psi_datum) + return UI_CLOSE + if(!psi_owner) + return UI_CLOSE + return UI_INTERACTIVE + +/datum/psionic_shop/ui_interact(mob/user, datum/tgui/ui) + ui = SStgui.try_update_ui(user, src, ui) + if(!ui) + ui = new(user, src, "PsionicShop", name) + ui.open() + +/datum/psionic_shop/ui_static_data(mob/user) + var/list/data = list() + + var/static/list/spells + if(isnull(spells)) + spells = list(typecacheof(/datum/action/cooldown/spell)) + for(var/datum/action/cooldown/spell/psi_spells as anything in spells) + if(!psi_spells.psionic) + continue + + var/list/ability_data = list( + "name" = initial(psi_spells.name), + "desc" = initial(psi_spells.desc), + "path" = psi_spells, + "point_required" = initial(psi_spells.point_cost), + "mana_required" = initial(psi_spells.mana_cost), + "cooldown" = initial(psi_spells.cooldown_time), + ) + + spells += list(ability_data) + + sortTim(spells, /proc/cmp_assoc_list_name) + + data["spells"] = spells + return data + +/datum/psionic_shop/ui_data(mob/user) + var/list/data = list() + + data["researched_spells"] = assoc_to_keys(psi_datum.learned_spells) + data["psi_points_count"] = psi_datum.psi_point + + return data + +/datum/psionic_shop/ui_act(action, list/params, datum/tgui/ui, datum/ui_state/state) + . = ..() + if(.) + return + + switch(action) + if("research") + // purchase_power sanity checks stuff like typepath, DNA, and absorbs for us. + psi_datum.research_spell(text2path(params["path"])) + + return TRUE + +/datum/action/psionic_shop + name = "Psionic Shop" + button_icon = 'tff_modular/modules/psionics/icons/spells.dmi' + button_icon_state = "controlled" + background_icon_state = "bg_tech_blue" + overlay_icon_state = "bg_tech_blue_border" + check_flags = NONE + +/datum/action/psionic_shop/New(Target) + . = ..() + if(!istype(Target, /datum/psionic_shop)) + stack_trace("psionic_shop action created with non-emporium.") + qdel(src) + +/datum/action/psionic_shop/Trigger(mob/clicker, trigger_flags) + . = ..() + if(!.) + return + target.ui_interact(owner) + diff --git a/tff_modular/modules/psyonics/code/psychokinesis.dm b/tff_modular/modules/psionics/code/psychokinesis.dm similarity index 56% rename from tff_modular/modules/psyonics/code/psychokinesis.dm rename to tff_modular/modules/psionics/code/psychokinesis.dm index 8476c8caa53..22577b9a29a 100644 --- a/tff_modular/modules/psyonics/code/psychokinesis.dm +++ b/tff_modular/modules/psionics/code/psychokinesis.dm @@ -7,65 +7,29 @@ /// Psyforce - даёт "клешни жизни" для вскрытия дверей /// Telekinesis - даёт мутацию телекинеза. -// Добавляет школу психокинетики -/mob/living/carbon/human/proc/try_add_psychokinesis_school(tier = 0, additional_school = 0) - if(tier >= 0) - var/datum/action/new_action = new /datum/action/cooldown/spell/conjure_item/psyonic/psilighter(src.mind || src, tier, additional_school) - new_action.Grant(src) - if(tier >= 1) - var/datum/action/new_action = new /datum/action/cooldown/spell/conjure_item/psyonic/psiblade(src.mind || src, tier, additional_school) - new_action.Grant(src) - if(tier >= 2) - var/datum/action/new_action = new /datum/action/cooldown/spell/conjure_item/psyonic/psitool(src.mind || src, tier, additional_school) - new_action.Grant(src) - var/datum/action/new_action2 = new /datum/action/cooldown/spell/touch/psyonic/psyonic_tinker(src.mind || src, tier, additional_school) - new_action2.Grant(src) - if(tier >= 3) - var/datum/action/new_action = new /datum/action/cooldown/spell/touch/psyonic/psyonic_force(src.mind || src, tier, additional_school) - new_action.Grant(src) - if(tier >= 4) - var/datum/action/new_action = new /datum/action/cooldown/spell/psyonic/psionic_telekinesis(src.mind || src, tier, additional_school) - new_action.Grant(src) - // Спавнит зажигалку в руке. Очень полезно -/datum/action/cooldown/spell/conjure_item/psyonic/psilighter +/datum/action/cooldown/spell/conjure_item/psionic/psilighter name = "Psi lighter" - desc = "Concentrates psyonic energy to create a small flame in your hand." + desc = "Concentrates psionic energy to create a small flame in your hand." button_icon = 'icons/obj/cigarettes.dmi' button_icon_state = "match_lit" cooldown_time = 1.5 SECONDS - item_type = /obj/item/psyonic_fire + item_type = /obj/item/psionic_fire mana_cost = 5 stamina_cost = 0 -// Спавнит пси-клинок в руке. Сила зависит от уровня псионика -/datum/action/cooldown/spell/conjure_item/psyonic/psiblade - name = "Psi blade" - desc = "Concentrates psyonic energy to create a sharp blade in your hand." - button_icon = 'icons/obj/weapons/transforming_energy.dmi' - button_icon_state = "blade" - cooldown_time = 1.5 SECONDS - item_type = /obj/item/melee/psyonic_blade - mana_cost = 40 - stamina_cost = 0 - // Спавнит омни инструмент в руке псионика. Аналог абдукторского -/datum/action/cooldown/spell/conjure_item/psyonic/psitool +/datum/action/cooldown/spell/conjure_item/psionic/psitool name = "Psi tool" - desc = "Concentrates psyonic energy to create a universal tool." + desc = "Concentrates psionic energy to create a universal tool." button_icon = 'icons/obj/antags/abductor.dmi' button_icon_state = "omnitool" cooldown_time = 1.5 SECONDS - item_type = /obj/item/psyonic_omnitool + item_type = /obj/item/psionic_omnitool mana_cost = 30 stamina_cost = 0 -/datum/action/cooldown/spell/conjure_item/psyonic/psiblade/New(Target) - . = ..() - if(secondary_school == "Psychokinesis") - cast_power += 1 - -/datum/action/cooldown/spell/conjure_item/psyonic/psiblade/make_item(atom/caster) +/datum/action/cooldown/spell/conjure_item/psionic/psiblade/make_item(atom/caster) var/obj/item/made_item = new item_type(caster.loc, cast_power) LAZYADD(item_refs, WEAKREF(made_item)) var/mob/living/carbon/human/caster_pawn = owner @@ -73,28 +37,28 @@ return made_item // Аналог клешней жизни -/datum/action/cooldown/spell/touch/psyonic/psyonic_force - name = "Psyonic Force" - desc = "Concentrates psyonic energy to force a door open." +/datum/action/cooldown/spell/touch/psionic/psionic_force + name = "Psionic Force" + desc = "Concentrates psionic energy to force a door open." button_icon = 'icons/mob/actions/actions_spells.dmi' button_icon_state = "knock" cooldown_time = 3 SECONDS mana_cost = 50 stamina_cost = 50 - hand_path = /obj/item/melee/touch_attack/psyonic_mending + hand_path = /obj/item/melee/touch_attack/psionic_mending draw_message = span_notice("You ready your hand to force a door open.") drop_message = span_notice("You lower your hand.") can_cast_on_self = FALSE -/datum/action/cooldown/spell/touch/psyonic/psyonic_force/is_valid_target(atom/cast_on) +/datum/action/cooldown/spell/touch/psionic/psionic_force/is_valid_target(atom/cast_on) return istype(cast_on, /obj/machinery/door/airlock) -/datum/action/cooldown/spell/touch/psyonic/psyonic_force/cast_on_hand_hit(obj/item/melee/touch_attack/hand, atom/victim, mob/living/carbon/mendicant) +/datum/action/cooldown/spell/touch/psionic/psionic_force/cast_on_hand_hit(obj/item/melee/touch_attack/hand, atom/victim, mob/living/carbon/mendicant) if(isatom(victim)) if(istype(victim, /obj/machinery/door/airlock)) var/obj/machinery/door/airlock/door_to_force = victim owner.visible_message(span_warning("[owner] targets their hands at [victim], like they are some kind of jedi."), - span_notice("You psyonically grab [victim], trying to force it open.")) + span_notice("You psionically grab [victim], trying to force it open.")) if(do_after(mendicant, 5 SECONDS, victim, IGNORE_SLOWDOWNS, TRUE)) force_door_open(door_to_force, mendicant) drain_mana() @@ -104,7 +68,7 @@ else return FALSE -/datum/action/cooldown/spell/touch/psyonic/psyonic_force/proc/force_door_open(obj/machinery/door/airlock/door_to_force, mob/living/carbon/user) +/datum/action/cooldown/spell/touch/psionic/psionic_force/proc/force_door_open(obj/machinery/door/airlock/door_to_force, mob/living/carbon/user) if(door_to_force.seal) to_chat(user, span_warning("Remove the seal first!")) return @@ -128,17 +92,17 @@ return // Даёт мутацию телекинеза -/datum/action/cooldown/spell/psyonic/psionic_telekinesis +/datum/action/cooldown/spell/psionic/psionic_telekinesis name = "Telekinesis" desc = "Force yourself to recieve telekinesis mutation." cooldown_time = 60 SECONDS mana_cost = 80 stamina_cost = 80 -/datum/action/cooldown/spell/psyonic/psionic_telekinesis/is_valid_target(atom/cast_on) +/datum/action/cooldown/spell/psionic/psionic_telekinesis/is_valid_target(atom/cast_on) return !issynthetic(cast_on) -/datum/action/cooldown/spell/psyonic/psionic_telekinesis/cast(mob/living/cast_on) +/datum/action/cooldown/spell/psionic/psionic_telekinesis/cast(mob/living/cast_on) . = ..() if(!ishuman(cast_on)) return FALSE @@ -149,23 +113,23 @@ drain_mana() // Восстанавливает Integrity атома. Позволяет чинить многие нечинимые иными способами вещи -/datum/action/cooldown/spell/touch/psyonic/psyonic_tinker - name = "Psyonic Tinker" +/datum/action/cooldown/spell/touch/psionic/psionic_tinker + name = "Psionic Tinker" desc = "Restore somethings condition to its normal state." button_icon = 'icons/obj/tools.dmi' button_icon_state = "wrench" cooldown_time = 3 SECONDS mana_cost = 40 stamina_cost = 50 - hand_path = /obj/item/melee/touch_attack/psyonic_mending + hand_path = /obj/item/melee/touch_attack/psionic_mending draw_message = span_notice("You ready your hand to tinker.") drop_message = span_notice("You lower your hand.") can_cast_on_self = FALSE -/datum/action/cooldown/spell/touch/psyonic/psyonic_tinker/is_valid_target(atom/cast_on) +/datum/action/cooldown/spell/touch/psionic/psionic_tinker/is_valid_target(atom/cast_on) return cast_on.uses_integrity -/datum/action/cooldown/spell/touch/psyonic/psyonic_tinker/cast_on_hand_hit(obj/item/melee/touch_attack/hand, atom/victim, mob/living/carbon/mendicant) +/datum/action/cooldown/spell/touch/psionic/psionic_tinker/cast_on_hand_hit(obj/item/melee/touch_attack/hand, atom/victim, mob/living/carbon/mendicant) if(isatom(victim)) var/atom/to_fix = victim if((to_fix.get_integrity() >= to_fix.max_integrity) || !to_fix.uses_integrity) @@ -179,63 +143,10 @@ else return FALSE -/obj/item/melee/psyonic_blade - name = "psyonic blade" - desc = "A concentrated collection of particles and energy that looks like a swords blade.." - icon = 'icons/obj/weapons/transforming_energy.dmi' - icon_state = "blade" - inhand_icon_state = "blade" - lefthand_file = 'icons/mob/inhands/weapons/swords_lefthand.dmi' - righthand_file = 'icons/mob/inhands/weapons/swords_righthand.dmi' - w_class = WEIGHT_CLASS_HUGE - force = 10 - throwforce = 10 - hitsound = 'sound/items/weapons/blade1.ogg' - attack_verb_continuous = list("attacks", "slashes", "stabs", "slices", "tears", "lacerates", "rips", "dices", "cuts") - attack_verb_simple = list("attack", "slash", "stab", "slice", "tear", "lacerate", "rip", "dice", "cut") - sharpness = SHARP_EDGED - block_chance = 0 - item_flags = DROPDEL | ABSTRACT | HAND_ITEM - color = COLOR_BRIGHT_BLUE - -/obj/item/melee/psyonic_blade/New(loc, power) - . = ..() - ADD_TRAIT(src, TRAIT_EXAMINE_SKIP, INNATE_TRAIT) - force = 10 + power * 1.5 - block_chance = power * 5 - -/obj/item/psyonic_fire - name = "small psyonic fire" - desc = "Small bluish fire, that jumps on your fingers and surprisigly doesn't burn them." - icon = 'icons/obj/weapons/hand.dmi' - icon_state = "greyscale" - color = COLOR_BRIGHT_BLUE - inhand_icon_state = "greyscale" - light_range = 2 - light_power = 2 - light_color = LIGHT_COLOR_LIGHT_CYAN - light_on = TRUE - damtype = BURN - force = 5 - attack_verb_continuous = list("burns", "singes") - attack_verb_simple = list("burn", "singe") - resistance_flags = FIRE_PROOF - w_class = WEIGHT_CLASS_HUGE - light_system = OVERLAY_LIGHT - toolspeed = 2 - tool_behaviour = TOOL_WELDER - item_flags = DROPDEL | ABSTRACT | HAND_ITEM - heat = HIGH_TEMPERATURE_REQUIRED - 100 - -/obj/item/psyonic_fire/Initialize(mapload) - . = ..() - - ADD_TRAIT(src, TRAIT_EXAMINE_SKIP, INNATE_TRAIT) - // Копирка с абдукторского -/obj/item/psyonic_omnitool - name = "psyonic omnitool" - desc = "Space Swiss Army Knife, able to shapeshift itself to fulfill psyonics needs." +/obj/item/psionic_omnitool + name = "psionic omnitool" + desc = "Space Swiss Army Knife, able to shapeshift itself to fulfill psionics needs." icon = 'icons/obj/antags/abductor.dmi' lefthand_file = 'icons/mob/inhands/antag/abductor_lefthand.dmi' righthand_file = 'icons/mob/inhands/antag/abductor_righthand.dmi' @@ -248,7 +159,7 @@ var/list/tool_list = list() item_flags = DROPDEL | ABSTRACT | HAND_ITEM -/obj/item/psyonic_omnitool/Initialize(mapload) +/obj/item/psionic_omnitool/Initialize(mapload) . = ..() tool_list = list( "Crowbar" = image(icon = 'icons/obj/tools.dmi', icon_state = "crowbar"), @@ -259,7 +170,7 @@ ) ADD_TRAIT(src, TRAIT_EXAMINE_SKIP, INNATE_TRAIT) -/obj/item/psyonic_omnitool/get_all_tool_behaviours() +/obj/item/psionic_omnitool/get_all_tool_behaviours() return list( TOOL_CROWBAR, TOOL_MULTITOOL, @@ -268,11 +179,11 @@ TOOL_WRENCH, ) -/obj/item/psyonic_omnitool/examine() +/obj/item/psionic_omnitool/examine() . = ..() . += " The mode is: [tool_behaviour]" -/obj/item/psyonic_omnitool/attack_self(mob/user) +/obj/item/psionic_omnitool/attack_self(mob/user) if(!user) return @@ -291,7 +202,7 @@ if("Wrench") tool_behaviour = TOOL_WRENCH -/obj/item/psyonic_omnitool/proc/check_menu(mob/user) +/obj/item/psionic_omnitool/proc/check_menu(mob/user) if(!istype(user)) return FALSE if(user.incapacitated || !user.Adjacent(src)) diff --git a/tff_modular/modules/psyonics/code/redaction.dm b/tff_modular/modules/psionics/code/redaction.dm similarity index 78% rename from tff_modular/modules/psyonics/code/redaction.dm rename to tff_modular/modules/psionics/code/redaction.dm index 1311e2a4ea1..f071655f89a 100644 --- a/tff_modular/modules/psyonics/code/redaction.dm +++ b/tff_modular/modules/psionics/code/redaction.dm @@ -8,29 +8,11 @@ /// Cleansing - лечит токс урон /// Revive - пытается оживить труп -// Выдать школу лечения -/mob/living/carbon/human/proc/try_add_redaction_school(tier = 0, additional_school = 0) - if(tier >= 0) - var/datum/action/new_action = new /datum/action/cooldown/spell/pointed/psyonic/psyonic_roentgen(src.mind || src, tier, additional_school) - new_action.Grant(src) - if(tier >= 1) - var/datum/action/new_action = new /datum/action/cooldown/spell/touch/psyonic/psyonic_mending(src.mind || src, tier, additional_school) - new_action.Grant(src) - if(tier >= 2) - var/datum/action/new_action2 = new /datum/action/cooldown/spell/pointed/psyonic/psyonic_drunkness(src.mind || src, tier, additional_school) - new_action2.Grant(src) - if(tier >= 3) - var/datum/action/new_action = new /datum/action/cooldown/spell/touch/psyonic/psyonic_cleansing(src.mind || src, tier, additional_school) - new_action.Grant(src) - if(tier >= 4) - var/datum/action/new_action = new /datum/action/cooldown/spell/touch/psyonic/psyonic_revival(src.mind || src, tier, additional_school) - new_action.Grant(src) - // Мед сканер на расстоянии -/datum/action/cooldown/spell/pointed/psyonic/psyonic_roentgen +/datum/action/cooldown/spell/pointed/psionic/psionic_roentgen name = "Roentgen" desc = "Try to read target's vital energy and determine their state." - button_icon = 'tff_modular/modules/psyonics/icons/actions.dmi' + button_icon = 'tff_modular/modules/psionics/icons/actions.dmi' button_icon_state = "roentgen" cooldown_time = 1 SECONDS @@ -40,21 +22,16 @@ active_msg = "You prepare to scan a target..." -/datum/action/cooldown/spell/pointed/psyonic/psyonic_roentgen/New(Target) - . = ..() - if(secondary_school == "Redaction") - cast_power += 1 - -/datum/action/cooldown/spell/pointed/psyonic/psyonic_roentgen/is_valid_target(atom/cast_on) +/datum/action/cooldown/spell/pointed/psionic/psionic_roentgen/is_valid_target(atom/cast_on) if(!ishuman(cast_on)) return FALSE return TRUE -/datum/action/cooldown/spell/pointed/psyonic/psyonic_roentgen/cast(mob/living/carbon/human/cast_on) +/datum/action/cooldown/spell/pointed/psionic/psionic_roentgen/cast(mob/living/carbon/human/cast_on) . = ..() if(cast_on.can_block_magic(antimagic_flags)) - to_chat(cast_on, span_notice("Your body is being read by a psyonic nearby.")) + to_chat(cast_on, span_notice("Your body is being read by a psionic nearby.")) else to_chat(cast_on, span_warning(target_msg)) if(cast_power > 2) @@ -64,9 +41,9 @@ drain_mana() return TRUE -/obj/item/melee/touch_attack/psyonic_mending - name = "psyonic sparks" - desc = "Concentrated psyonic energy in a hand." +/obj/item/melee/touch_attack/psionic_mending + name = "psionic sparks" + desc = "Concentrated psionic energy in a hand." icon = 'icons/obj/weapons/hand.dmi' icon_state = "greyscale" color = COLOR_VERY_PALE_LIME_GREEN @@ -78,25 +55,25 @@ // Восстанавливает кровь, окси урон, открытые травмы. Не лечит другие типы урона. Если вторичка - психокинетика, то вынимает импланты. // Если уровень Эпсилон - удаляет лярвы ксеноморфов. -/datum/action/cooldown/spell/touch/psyonic/psyonic_mending - name = "Psyonic Mending" +/datum/action/cooldown/spell/touch/psionic/psionic_mending + name = "Psionic Mending" desc = "You can try to restore patients bloodloss, bones, open wounds and partially oxygen level in blood. Does not heal brute, burn, \ and toxic damage. With Psychokinesis as secondary school also can remove small implants. At Epsilon level can remove xenomorph larvae." - button_icon = 'tff_modular/modules/psyonics/icons/actions.dmi' + button_icon = 'tff_modular/modules/psionics/icons/actions.dmi' button_icon_state = "mending_touch" cooldown_time = 3 SECONDS mana_cost = 25 stamina_cost = 25 target_msg = "You body numbs a little." - hand_path = /obj/item/melee/touch_attack/psyonic_mending + hand_path = /obj/item/melee/touch_attack/psionic_mending draw_message = span_notice("You ready your hand to mend a patient.") drop_message = span_notice("You lower your hand.") can_cast_on_self = TRUE -/datum/action/cooldown/spell/touch/psyonic/psyonic_mending/cast_on_hand_hit(obj/item/melee/touch_attack/hand, atom/victim, mob/living/carbon/mendicant) +/datum/action/cooldown/spell/touch/psionic/psionic_mending/cast_on_hand_hit(obj/item/melee/touch_attack/hand, atom/victim, mob/living/carbon/mendicant) if(ishuman(victim)) var/mob/living/carbon/human/human_victim = victim - if(issynthetic(human_victim) && secondary_school != "Psychokinesis") + if(issynthetic(human_victim) && cast_power < 2) to_chat(owner, span_notice("I dont know how to work with synths.")) return FALSE if(human_victim.can_block_magic(antimagic_flags)) @@ -112,10 +89,10 @@ else return FALSE -/datum/action/cooldown/spell/touch/psyonic/psyonic_mending/proc/accident_harm(mob/living/carbon/human/patient) +/datum/action/cooldown/spell/touch/psionic/psionic_mending/proc/accident_harm(mob/living/carbon/human/patient) patient.take_bodypart_damage(5, wound_bonus = 100) -/datum/action/cooldown/spell/touch/psyonic/psyonic_mending/proc/try_heal_all(mob/living/carbon/human/patient) +/datum/action/cooldown/spell/touch/psionic/psionic_mending/proc/try_heal_all(mob/living/carbon/human/patient) if(patient.blood_volume < BLOOD_VOLUME_NORMAL) patient.blood_volume += ((BLOOD_VOLUME_NORMAL - patient.blood_volume) / 5) * cast_power // Эффективнее когда крови мало @@ -127,7 +104,7 @@ if(patient.get_oxy_loss() >= OXYLOSS_PASSOUT_THRESHOLD-10) patient.adjust_oxy_loss(-cast_power*5, forced = TRUE) - if(patient.implants && secondary_school == "Psychokinesis" && cast_power >= 2) // Невольно удаляет импланты, если есть + if(patient.implants && cast_power >= 2) // Невольно удаляет импланты, если есть var/obj/item/implant/imp_2_del = pick(patient.implants) var/atom/drop_loc = imp_2_del.drop_location() imp_2_del.removed(patient) @@ -138,7 +115,7 @@ span_danger("You feel implant inside you starts to move and rips itself out! The resulting wound quickly closes itself though."), ) - if(patient.get_organ_slot("parasite_egg") && cast_power >=4) // Удаляем ксеноморфов + if(patient.get_organ_slot("parasite_egg") && cast_power >= 2) // Удаляем ксеноморфов var/obj/item/organ/body_egg/parasite = patient.get_organ_slot("parasite_egg") parasite.owner.vomit(VOMIT_CATEGORY_BLOOD | MOB_VOMIT_KNOCKDOWN | MOB_VOMIT_HARM) parasite.owner.visible_message( @@ -151,7 +128,7 @@ if(drop_loc) parasite.forceMove(drop_loc) -/datum/action/cooldown/spell/pointed/psyonic/psyonic_drunkness +/datum/action/cooldown/spell/pointed/psionic/psionic_drunkness name = "Ethanol Body Synthesis" desc = "Convert fat masses to ethanol in combat mode, vice versa otherwise. Works with time on distance, but not on synthetics." button_icon = 'icons/obj/drinks/bottles.dmi' @@ -161,7 +138,7 @@ stamina_cost = 30 active_msg = "You prepare to convert fat tissues..." -/datum/action/cooldown/spell/pointed/psyonic/psyonic_drunkness/is_valid_target(atom/cast_on) +/datum/action/cooldown/spell/pointed/psionic/psionic_drunkness/is_valid_target(atom/cast_on) if(!ishuman(cast_on)) return FALSE if(issynthetic(cast_on) ) @@ -169,25 +146,25 @@ return FALSE return TRUE -/datum/action/cooldown/spell/pointed/psyonic/psyonic_drunkness/cast(mob/living/carbon/human/cast_on) +/datum/action/cooldown/spell/pointed/psionic/psionic_drunkness/cast(mob/living/carbon/human/cast_on) . = ..() - cast_on.apply_status_effect(/datum/status_effect/psyonic_fat_conversion, 5 * cast_power SECONDS, !cast_on.combat_mode) + cast_on.apply_status_effect(/datum/status_effect/psionic_fat_conversion, 5 * cast_power SECONDS, !cast_on.combat_mode) drain_mana() return TRUE /// С каждым тиком конвертируем или жир в алкоголь, или алкоголь в жир -/datum/status_effect/psyonic_fat_conversion - id = "psyonic_fat_conversion" +/datum/status_effect/psionic_fat_conversion + id = "psionic_fat_conversion" alert_type = null remove_on_fullheal = TRUE var/eth2fat = TRUE -/datum/status_effect/psyonic_fat_conversion/on_creation(mob/living/new_owner, duration = 10 SECONDS, eth2fat = TRUE) +/datum/status_effect/psionic_fat_conversion/on_creation(mob/living/new_owner, duration = 10 SECONDS, eth2fat = TRUE) src.duration = duration src.eth2fat = eth2fat return ..() -/datum/status_effect/psyonic_fat_conversion/tick(seconds_between_ticks) +/datum/status_effect/psionic_fat_conversion/tick(seconds_between_ticks) var/mob/living/carbon/human/human_owner = owner var/fat = human_owner.nutrition var/drunk = human_owner.get_drunk_amount() @@ -201,25 +178,25 @@ human_owner.adjust_nutrition(-(fat/25)) // Лечит токс урон. -/datum/action/cooldown/spell/touch/psyonic/psyonic_cleansing - name = "Psyonic Cleansing" +/datum/action/cooldown/spell/touch/psionic/psionic_cleansing + name = "Psionic Cleansing" desc = "Filters patient blood out of toxins and removes accumulated radiation." - button_icon = 'tff_modular/modules/psyonics/icons/actions.dmi' + button_icon = 'tff_modular/modules/psionics/icons/actions.dmi' button_icon_state = "cleansing" cooldown_time = 3 SECONDS mana_cost = 35 stamina_cost = 40 target_msg = "Your insides itch." - hand_path = /obj/item/melee/touch_attack/psyonic_mending + hand_path = /obj/item/melee/touch_attack/psionic_mending draw_message = span_notice("You ready your hand to cleanse a patient.") drop_message = span_notice("You lower your hand.") can_cast_on_self = TRUE -/datum/action/cooldown/spell/touch/psyonic/psyonic_cleansing/cast_on_hand_hit(obj/item/melee/touch_attack/hand, atom/victim, mob/living/carbon/mendicant) +/datum/action/cooldown/spell/touch/psionic/psionic_cleansing/cast_on_hand_hit(obj/item/melee/touch_attack/hand, atom/victim, mob/living/carbon/mendicant) if(ishuman(victim)) var/mob/living/carbon/human/human_victim = victim - if(issynthetic(human_victim) && secondary_school != "Psychokinesis") + if(issynthetic(human_victim) && cast_power < 2) to_chat(owner, span_notice("I dont know how to work with synths. Why would I even try to? They dont have toxins.")) return FALSE if(human_victim.can_block_magic(antimagic_flags)) @@ -235,10 +212,10 @@ else return FALSE -/datum/action/cooldown/spell/touch/psyonic/psyonic_cleansing/proc/accident_harm(mob/living/carbon/human/patient) +/datum/action/cooldown/spell/touch/psionic/psionic_cleansing/proc/accident_harm(mob/living/carbon/human/patient) patient.apply_damage(25, TOX, BODY_ZONE_CHEST) -/datum/action/cooldown/spell/touch/psyonic/psyonic_cleansing/proc/try_heal_all(mob/living/carbon/human/patient) +/datum/action/cooldown/spell/touch/psionic/psionic_cleansing/proc/try_heal_all(mob/living/carbon/human/patient) if(patient.get_tox_loss() > 0) patient.adjust_tox_loss(clamp(-(patient.get_tox_loss()/3)*cast_power, -35, 0), forced = TRUE) @@ -250,30 +227,30 @@ * 2. Если не удалось устранить - не оживляет * 3. Если удалось устранить причину - проверяет можно ли дефибнуть снова. Если появилась другая - не оживляет. Всё ок - оживляет. */ -/datum/action/cooldown/spell/touch/psyonic/psyonic_revival - name = "Psyonic Revival" +/datum/action/cooldown/spell/touch/psionic/psionic_revival + name = "Psionic Revival" desc = "Ability to trick death itself. Call for the bodys soul in the other realm in attempt to restore its vessel condition to an... acceptable levels." - button_icon = 'tff_modular/modules/psyonics/icons/actions.dmi' + button_icon = 'tff_modular/modules/psionics/icons/actions.dmi' button_icon_state = "revive" cooldown_time = 3 SECONDS mana_cost = 80 stamina_cost = 160 - hand_path = /obj/item/melee/touch_attack/psyonic_mending + hand_path = /obj/item/melee/touch_attack/psionic_mending draw_message = span_notice("You ready your hand to revive a patient.") drop_message = span_notice("You lower your hand.") can_cast_on_self = FALSE -/datum/action/cooldown/spell/touch/psyonic/psyonic_revival/cast_on_hand_hit(obj/item/melee/touch_attack/hand, atom/victim, mob/living/carbon/mendicant) +/datum/action/cooldown/spell/touch/psionic/psionic_revival/cast_on_hand_hit(obj/item/melee/touch_attack/hand, atom/victim, mob/living/carbon/mendicant) if(ishuman(victim)) var/mob/living/carbon/human/human_victim = victim - var/synth_check = (secondary_school == "Psychokinesis" || !issynthetic(human_victim)) + var/synth_check = (cast_power < 2 || !issynthetic(human_victim)) if(human_victim.stat == DEAD && synth_check) owner.visible_message(span_notice("[owner] kneels before the body of [victim], lowers their hands onto cadavers chest and begins meditating."), span_notice("You kneel before the cadaver, lower your hands onto their chest and start to concentrate energy. You better not \ get disturbed, or else..."), blind_message = span_hear("You hear a low hum.")) - var/obj/effect/abstract/particle_holder/particle_effect = new(human_victim, /particles/droplets/psyonic) + var/obj/effect/abstract/particle_holder/particle_effect = new(human_victim, /particles/droplets/psionic) if(!do_after(mendicant, 25 SECONDS, human_victim, IGNORE_SLOWDOWNS, TRUE)) accident_harm(owner) // Ауч. Больно бьёт по псионику else @@ -283,7 +260,7 @@ drain_mana() return TRUE else if(issynthetic(human_victim) && human_victim.stat == DEAD) - to_chat(owner, span_warning("Your psyonic energy does not work very well with synths.")) + to_chat(owner, span_warning("Your psionic energy does not work very well with synths.")) return FALSE else return FALSE @@ -291,7 +268,7 @@ return FALSE // 25 токса + 50 брута + 1 травма + позор роду псионическому -/datum/action/cooldown/spell/touch/psyonic/psyonic_revival/proc/accident_harm(mob/living/carbon/human/unlucky_guy) +/datum/action/cooldown/spell/touch/psionic/psionic_revival/proc/accident_harm(mob/living/carbon/human/unlucky_guy) unlucky_guy.apply_damage(25, TOX, BODY_ZONE_CHEST) unlucky_guy.take_bodypart_damage(25, wound_bonus = 100) unlucky_guy.take_bodypart_damage(25, wound_bonus = 100, sharpness = SHARP_EDGED) @@ -299,10 +276,10 @@ span_bolddanger("Your revival energy backfired at you, causing severe injuries!"), blind_message = span_hear("You hear bones breaking.")) -/datum/action/cooldown/spell/touch/psyonic/psyonic_revival/proc/can_defib_human(mob/living/carbon/human/patient) +/datum/action/cooldown/spell/touch/psionic/psionic_revival/proc/can_defib_human(mob/living/carbon/human/patient) var/defib_result = patient.can_defib() var/fail_reason - var/synth_check = (secondary_school == "Psychokinesis") + var/synth_check = (cast_power < 2) switch (defib_result) if (DEFIB_FAIL_SUICIDE) fail_reason = "Patient has left this world on his terms. You can not restore him." @@ -342,7 +319,7 @@ fail_reason = "Patient cannot be restored due to star misalignment." return fail_reason -/datum/action/cooldown/spell/touch/psyonic/psyonic_revival/proc/try_heal_all(mob/living/carbon/human/patient) +/datum/action/cooldown/spell/touch/psionic/psionic_revival/proc/try_heal_all(mob/living/carbon/human/patient) var/fail_reason = can_defib_human(patient) // first to possibly cure something fail_reason = can_defib_human(patient) // second to actually try revival if(fail_reason) @@ -377,6 +354,6 @@ patient.set_jitter_if_lower(200 SECONDS) to_chat(patient, "[CONFIG_GET(string/blackoutpolicy)]") SEND_SIGNAL(patient, COMSIG_LIVING_MINOR_SHOCK) - log_combat(owner, patient, "psyonically revived") + log_combat(owner, patient, "psionically revived") #undef HALFWAYCRITDEATH diff --git a/tff_modular/modules/psionics/code/subsystem.dm b/tff_modular/modules/psionics/code/subsystem.dm new file mode 100644 index 00000000000..f576297add2 --- /dev/null +++ b/tff_modular/modules/psionics/code/subsystem.dm @@ -0,0 +1,5 @@ +PROCESSING_SUBSYSTEM_DEF(psionic) + name = "Psionic" + priority = FIRE_PRIORITY_DEFAULT + ss_flags = SS_NO_INIT + wait = 2 SECONDS diff --git a/tff_modular/modules/psionics/code/touch/touch.dm b/tff_modular/modules/psionics/code/touch/touch.dm new file mode 100644 index 00000000000..64e1ffb71b6 --- /dev/null +++ b/tff_modular/modules/psionics/code/touch/touch.dm @@ -0,0 +1,46 @@ +// Тут все заклинания, которые являются прикосновением +// Спелл для чтения разума другого игрока на наличие псионических способностей +/datum/action/cooldown/spell/touch/psionic/psionic_assay + name = "Psionic Assay" + desc = "Check if the target is a psionic." + button_icon = 'icons/obj/medical/organs/organs.dmi' + button_icon_state = "brain" + cooldown_time = 60 SECONDS + mana_cost = 5 + stamina_cost = 0 + target_msg = "Your get a headache, but it quickly fades." + hand_path = /obj/item/melee/touch_attack/psionic_mending + draw_message = span_notice("You ready your hand to cleanse a patient.") + drop_message = span_notice("You lower your hand.") + can_cast_on_self = TRUE + psionic_level = 1 + +/datum/action/cooldown/spell/touch/psionic/psionic_assay/cast_on_hand_hit(obj/item/melee/touch_attack/hand, atom/victim, mob/living/carbon/mendicant) + if(ishuman(victim)) + var/mob/living/carbon/human/human_victim = victim + if(human_victim.can_block_magic(antimagic_flags)) + to_chat(human_victim, span_notice("Psionic nearby tries to check you for psionic levels.")) + else + to_chat(human_victim, span_warning(target_msg)) + owner.visible_message(span_warning("[owner] presses his thumb onto [victim]s forehead."), + span_notice("You press your thumb onto [victim]s forehead and begin reading them.")) + to_chat(victim, span_danger("[owner] presses a thumb onto your forehead and holds it there. It burns sligthly!")) + if(do_after(mendicant, 6 SECONDS, human_victim, IGNORE_SLOWDOWNS, TRUE)) + read_psionic_level(human_victim) + drain_mana() + return TRUE + else + return FALSE + +/datum/action/cooldown/spell/touch/psionic/psionic_assay/proc/read_psionic_level(mob/living/carbon/human/patient) + if(issynthetic(patient) && cast_power < 2) + to_chat(owner, span_notice("I can see... just numbers. No idea how to work with synths.")) + return FALSE + + if(patient.get_psionic()) + var/datum/psionic/target_psi = patient.get_psionic() + owner.visible_message(span_notice("[owner] backs off from [patient]."), + span_cyan("Target is a psionic. [patient.p_Their()] class is [target_psi.psionic_level_string]")) + else + owner.visible_message(span_notice("[owner] backs off from [patient]."), + span_cyan("Target is not a psionic.")) diff --git a/tff_modular/modules/psyonics/icons/actions.dmi b/tff_modular/modules/psionics/icons/actions.dmi similarity index 100% rename from tff_modular/modules/psyonics/icons/actions.dmi rename to tff_modular/modules/psionics/icons/actions.dmi diff --git a/tff_modular/modules/psyonics/icons/card.dmi b/tff_modular/modules/psionics/icons/card.dmi similarity index 100% rename from tff_modular/modules/psyonics/icons/card.dmi rename to tff_modular/modules/psionics/icons/card.dmi diff --git a/tff_modular/modules/psionics/icons/projectiles.dmi b/tff_modular/modules/psionics/icons/projectiles.dmi new file mode 100644 index 0000000000000000000000000000000000000000..928f933abb10354700b17392c9f95a4ea639ab48 GIT binary patch literal 756 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 R%ozXx002ovPDHLkV1mt5-W&h` literal 0 HcmV?d00001 diff --git a/tff_modular/modules/psionics/icons/spells.dmi b/tff_modular/modules/psionics/icons/spells.dmi new file mode 100644 index 0000000000000000000000000000000000000000..4630641ef4aa74516340d7566970f41b152587f8 GIT binary patch literal 27570 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 literal 0 HcmV?d00001 diff --git a/tff_modular/modules/psyonics/code/_quirk.dm b/tff_modular/modules/psyonics/code/_quirk.dm deleted file mode 100644 index c97f18dd012..00000000000 --- a/tff_modular/modules/psyonics/code/_quirk.dm +++ /dev/null @@ -1,216 +0,0 @@ -#define TRAIT_PSYONIC_USER "psyonicuser" -#define TRAIT_NO_PSYONICS "no_psyonics" -#define TRAIT_PRO_PSYONICS "pro_psyonics" - -#define LATENT_PSYONIC 0 -#define OPERANT_PSYONIC 1 -#define MASTER_PSYONIC 2 -#define GRANDMASTER_PSYONIC 3 -#define PARAMOUNT_PSYONIC 4 -#define GREATEST_PSYONIC 5 - -GLOBAL_LIST_INIT(psyonic_schools, list( - "Redaction", - "Coercion", - "Psychokinesis", - "Energistics", -)) - -/datum/quirk/psyonic - name = "Psyonic Abilities" - desc = "Either you were born like this or gained powers from implants/training or other events - you are a psyonic. \ - Your mind can access the world that lies beyond our mortal plane. One day voices from within had pierced your skull \ - like a tide wave turns a sailboat over in open sea, but you withstanded it and received abilities your father haven't \ - even dreamed of. From now on a special type of energy is stored in your mind, body and soul and you have control over it. \ - Every psyonic is a follower of a certain school: \ - Redaction - school of mending and curing bodies and souls; \ - Coercion - school of trickery and controlling others; \ - Psychokinesis - school of object manipulation; \ - Energistics - school of elecricity, fire and light; \ - You can select the school, but it's power will be randomised every round." - value = 12 // Отдадите за псионику жопу, чтобы потом вам Рэнди Рандом всегда слал наименьший уровень силы - medical_record_text = "Patient possesses connection to another plain of reality." - quirk_flags = QUIRK_HIDE_FROM_SCAN|QUIRK_HUMAN_ONLY|QUIRK_PROCESSES // Сканеры не видят псиоников. Только псионик школы принуждения может точно определить, является ли живое существо псиоником - gain_text = span_cyan("You mind feels uneasy, but... so powerful.") - lose_text = span_warning("You lost something that kept your connection with other realms.") - icon = "fa-star" - mob_trait = TRAIT_PSYONIC_USER - nova_stars_only = TRUE - allow_for_donator = TRUE - // Текущий уровень маны - var/mana_level = 0 - // Максимально возможный уровень маны - var/max_mana = 10 - // Уровень псионических способностей - var/psyonic_level = 0 - // Строка для описания уровня - var/psyonic_level_string = "Latent" - // Первичная школа псионики - var/school - // Вторичная школа псионики - var/secondary_school - /// Два вара скопированные из item_quirk для правильной выдачи лицензии - var/list/where_items_spawned - var/open_backpack = FALSE - -/datum/quirk/psyonic/add(client/client_source) - school = client_source?.prefs?.read_preference(/datum/preference/choiced/psyonic_school) - if(!school) - school = pick(GLOB.psyonic_schools) - secondary_school = client_source?.prefs?.read_preference(/datum/preference/choiced/psyonic_school_secondary) - if(!secondary_school) - secondary_school = pick(GLOB.psyonic_schools) - var/mob/living/carbon/human/whom_to_give = quirk_holder - var/fluff_1 = rand(0,1) - var/fluff_2 = rand(0,1) - var/fluff_3 = rand(0,1) - var/fluff_4 = rand(0,1) - psyonic_level = fluff_1 + fluff_2 + fluff_3 + fluff_4 - if(HAS_MIND_TRAIT(whom_to_give, TRAIT_MADNESS_IMMUNE)) // A.K.A. Психолог - psyonic_level += rand(0,1) // _возможное_ доп очко - switch(psyonic_level) - if(LATENT_PSYONIC) - psyonic_level_string = "Pi" - if(OPERANT_PSYONIC) - psyonic_level_string = "Omicron" - if(MASTER_PSYONIC) - psyonic_level_string = "Kappa" - if(GRANDMASTER_PSYONIC) - psyonic_level_string = "Lambda" - if(PARAMOUNT_PSYONIC) - psyonic_level_string = "Theta" - if(GREATEST_PSYONIC) // Дозволен только особо везучим психологам, у которых все предыдущие пять рандомов вышли на 1 - psyonic_level_string = "Epsilon" - if(school == secondary_school) - psyonic_level += 1 // Если вторичка совпадает с первой - добавляем один уровень, но не меняем описание - max_mana = (psyonic_level + 1) * 20 // Минимальный - 20, максимальный - 100 - RegisterSignal(quirk_holder, COMSIG_MOB_GET_STATUS_TAB_ITEMS, PROC_REF(get_status_tab_item)) - switch(school) - if("Redaction") - whom_to_give.try_add_redaction_school(psyonic_level, secondary_school) - if("Coercion") - whom_to_give.try_add_coercion_school(psyonic_level, secondary_school) - if("Psychokinesis") - whom_to_give.try_add_psychokinesis_school(psyonic_level, secondary_school) - if("Energistics") - whom_to_give.try_add_energistics_school(psyonic_level, secondary_school) - - if(secondary_school != school) // Если школы разные, добавить способность нулевого уровня вторичной школы - switch(secondary_school) - if("Redaction") - whom_to_give.try_add_redaction_school(0, 0) - if("Coercion") - whom_to_give.try_add_coercion_school(0, 0) - if("Psychokinesis") - whom_to_give.try_add_psychokinesis_school(0, 0) - if("Energistics") - whom_to_give.try_add_energistics_school(0, 0) - - var/fluff_text = span_cyan("Current psionic factors:") + "
" + \ - "[fluff_1 ? "Current star position is aligned to your soul." : "The stars do not precede luck to you."]" + "
" + \ - "[fluff_2 ? "Other realms are unusually active this shift." : "Other realms are quiet today."]" + "
" + \ - "[fluff_3 ? "Time-bluespace continuum seems to be stable today." : "Time-bluespace continuum is not giving you energy today."]" + "
" + \ - "[fluff_4 ? "Your mind is clearly open to otherwordly energy." : "Something clouds your connection to otherworld energy."]" - to_chat(quirk_holder, boxed_message(span_infoplain(jointext(fluff_text, "\n• ")))) - psyonic_level -= 1 // Обязаловка, иначе выдаст спеллы которые нельзя кастануть - - var/obj/item/card/psyonic_license/new_license = new(whom_to_give) - - give_item_to_holder(new_license, list(LOCATION_BACKPACK = ITEM_SLOT_BACK, LOCATION_HANDS = ITEM_SLOT_HANDS), flavour_text = "Make sure not to lose it. You can not remake this on the station.") - -/datum/quirk/psyonic/proc/give_item_to_holder(obj/item/quirk_item, list/valid_slots, flavour_text = null, default_location = "at your feet", notify_player = TRUE) - if(ispath(quirk_item)) - quirk_item = new quirk_item(get_turf(quirk_holder)) - - var/mob/living/carbon/human/human_holder = quirk_holder - - var/where = human_holder.equip_in_one_of_slots(quirk_item, valid_slots, qdel_on_fail = FALSE, indirect_action = TRUE) || default_location - - if(where == LOCATION_BACKPACK) - open_backpack = TRUE - - if(notify_player) - LAZYADD(where_items_spawned, span_boldnotice("You have \a [quirk_item] [where]. [flavour_text]")) - -/datum/quirk/psyonic/remove() - UnregisterSignal(quirk_holder, COMSIG_MOB_GET_STATUS_TAB_ITEMS) - -// Показывает текущее кол-во псионической энергии -/datum/quirk/psyonic/proc/get_status_tab_item(mob/living/source, list/items) - SIGNAL_HANDLER - - items += "Current psyonic energy: [mana_level]/[max_mana]" - -/datum/quirk/psyonic/process(seconds_per_tick) - if(HAS_TRAIT(quirk_holder, TRAIT_NO_PSYONICS)) // Имплант подавления регена - return - - if(HAS_TRAIT(quirk_holder, TRAIT_MINDSHIELD)) // Womp womp - return - - var/additional_mana = 1 - if(quirk_holder.has_status_effect(/datum/status_effect/drugginess)) // Наркота даёт бафф к генерации маны - additional_mana *= 1.5 - - if(HAS_TRAIT(quirk_holder, TRAIT_PRO_PSYONICS)) // Если есть имплант для увеличения регена маны - additional_mana *= 2 - - var/mob/living/carbon/human/human_holder = quirk_holder - if(human_holder.is_blind()) - additional_mana *= 1.5 - - if(mana_level <= max_mana) - mana_level += seconds_per_tick * 0.5 * additional_mana - mana_level = clamp(mana_level, 0, max_mana) - -/datum/quirk_constant_data/psyonic_school - associated_typepath = /datum/quirk/psyonic - customization_options = list(/datum/preference/choiced/psyonic_school, /datum/preference/choiced/psyonic_school_secondary) - -/datum/preference/choiced/psyonic_school - category = PREFERENCE_CATEGORY_MANUALLY_RENDERED - savefile_key = "psyonic_school" - savefile_identifier = PREFERENCE_CHARACTER - -/datum/preference/choiced/psyonic_school/create_default_value() - return "Redaction" - -/datum/preference/choiced/psyonic_school/init_possible_values() - return GLOB.psyonic_schools - -/datum/preference/choiced/psyonic_school/is_accessible(datum/preferences/preferences) - . = ..() - if (!.) - return FALSE - - return "Psyonic Abilities" in preferences.all_quirks - -/datum/preference/choiced/psyonic_school/apply_to_human(mob/living/carbon/human/target, value) - return - -/datum/preference/choiced/psyonic_school_secondary - category = PREFERENCE_CATEGORY_MANUALLY_RENDERED - savefile_key = "psyonic_school_secondary" - savefile_identifier = PREFERENCE_CHARACTER - -/datum/preference/choiced/psyonic_school_secondary/create_default_value() - return "Redaction" - -/datum/preference/choiced/psyonic_school_secondary/init_possible_values() - return GLOB.psyonic_schools - -/datum/preference/choiced/psyonic_school_secondary/is_accessible(datum/preferences/preferences) - . = ..() - if (!.) - return FALSE - - return "Psyonic Abilities" in preferences.all_quirks - -/datum/preference/choiced/psyonic_school_secondary/apply_to_human(mob/living/carbon/human/target, value) - return - -#undef LATENT_PSYONIC -#undef OPERANT_PSYONIC -#undef MASTER_PSYONIC -#undef GRANDMASTER_PSYONIC -#undef PARAMOUNT_PSYONIC diff --git a/tff_modular/modules/psyonics/code/cyberimp.dm b/tff_modular/modules/psyonics/code/cyberimp.dm deleted file mode 100644 index 356e9c7bcc9..00000000000 --- a/tff_modular/modules/psyonics/code/cyberimp.dm +++ /dev/null @@ -1,40 +0,0 @@ -#define ORGAN_SLOT_BRAIN_PSYONIC "brain_psyonic" - -// Не позволяет мане регенерироваться -/obj/item/organ/internal/cyberimp/brain/anti_psyonic - name = "Psyonic Amplifier Model N" - desc = "This implant will prohibit psyonics from regenereting their energy." - icon_state = "brain_implant_rebooter" - slot = ORGAN_SLOT_BRAIN_PSYONIC - -/obj/item/organ/internal/cyberimp/brain/anti_psyonic/on_mob_insert(mob/living/carbon/organ_owner, special, movement_flags) - . = ..() - ADD_TRAIT(organ_owner, TRAIT_NO_PSYONICS, IMPLANT_TRAIT) - -/obj/item/organ/internal/cyberimp/brain/anti_psyonic/on_mob_remove(mob/living/carbon/organ_owner, special) - . = ..() - REMOVE_TRAIT(organ_owner, TRAIT_NO_PSYONICS, IMPLANT_TRAIT) - -// Увеличивает реген маны в 2 раза -/obj/item/organ/internal/cyberimp/brain/pro_psyonic - name = "Psyonic Amplifier Model A" - desc = "This implant will boost psyonics energy regeneration by two times." - icon_state = "brain_implant_rebooter" - slot = ORGAN_SLOT_BRAIN_PSYONIC - -/obj/item/organ/internal/cyberimp/brain/pro_psyonic/on_mob_insert(mob/living/carbon/organ_owner, special, movement_flags) - . = ..() - ADD_TRAIT(organ_owner, TRAIT_PRO_PSYONICS, IMPLANT_TRAIT) - -/obj/item/organ/internal/cyberimp/brain/pro_psyonic/on_mob_remove(mob/living/carbon/organ_owner, special) - . = ..() - REMOVE_TRAIT(organ_owner, TRAIT_PRO_PSYONICS, IMPLANT_TRAIT) - -/datum/supply_pack/medical/psyonic_implants - name = "Psyonic Implants" - desc = "A crate containing two experimental psyonic implants, which work ONLY on psyonic users. No warranty." - cost = CARGO_CRATE_VALUE * 5 - contains = list(/obj/item/organ/internal/cyberimp/brain/anti_psyonic = 1, - /obj/item/organ/internal/cyberimp/brain/pro_psyonic = 1) - crate_name = "Psyonic implant crate" - discountable = SUPPLY_PACK_RARE_DISCOUNTABLE diff --git a/tff_modular/modules/psyonics/code/subsystem.dm b/tff_modular/modules/psyonics/code/subsystem.dm new file mode 100644 index 00000000000..f576297add2 --- /dev/null +++ b/tff_modular/modules/psyonics/code/subsystem.dm @@ -0,0 +1,5 @@ +PROCESSING_SUBSYSTEM_DEF(psionic) + name = "Psionic" + priority = FIRE_PRIORITY_DEFAULT + ss_flags = SS_NO_INIT + wait = 2 SECONDS diff --git a/tgstation.dme b/tgstation.dme index 8ad815fa118..9d37b553997 100644 --- a/tgstation.dme +++ b/tgstation.dme @@ -429,6 +429,7 @@ #include "code\__DEFINES\~ff_defines\flavor_misc.dm" #include "code\__DEFINES\~ff_defines\food_defines.dm" #include "code\__DEFINES\~ff_defines\nabber_clothes_pathes.dm" +#include "code\__DEFINES\~ff_defines\psionic.dm" #include "code\__DEFINES\~ff_defines\say.dm" #include "code\__DEFINES\~ff_defines\signals.dm" #include "code\__DEFINES\~ff_defines\span.dm" @@ -9895,14 +9896,21 @@ #include "tff_modular\modules\poster_contest\contraband.dm" #include "tff_modular\modules\poster_contest\official.dm" #include "tff_modular\modules\poster_contest\winners_items\code.dm" -#include "tff_modular\modules\psyonics\code\_psyonics.dm" -#include "tff_modular\modules\psyonics\code\_quirk.dm" -#include "tff_modular\modules\psyonics\code\coersion.dm" -#include "tff_modular\modules\psyonics\code\cyberimp.dm" -#include "tff_modular\modules\psyonics\code\documents.dm" -#include "tff_modular\modules\psyonics\code\energistics.dm" -#include "tff_modular\modules\psyonics\code\psychokinesis.dm" -#include "tff_modular\modules\psyonics\code\redaction.dm" +#include "tff_modular\modules\psionics\code\_psionic_abilities.dm" +#include "tff_modular\modules\psionics\code\_psionic_datum.dm" +#include "tff_modular\modules\psionics\code\coersion.dm" +#include "tff_modular\modules\psionics\code\cyberimp.dm" +#include "tff_modular\modules\psionics\code\documents.dm" +#include "tff_modular\modules\psionics\code\energistics.dm" +#include "tff_modular\modules\psionics\code\hud.dm" +#include "tff_modular\modules\psionics\code\psychokinesis.dm" +#include "tff_modular\modules\psionics\code\redaction.dm" +#include "tff_modular\modules\psionics\code\subsystem.dm" +#include "tff_modular\modules\psionics\code\conjure_item\conjure_item.dm" +#include "tff_modular\modules\psionics\code\pointed\pointed.dm" +#include "tff_modular\modules\psionics\code\projectiles\projectile.dm" +#include "tff_modular\modules\psionics\code\psi_shop\datum.dm" +#include "tff_modular\modules\psionics\code\touch\touch.dm" #include "tff_modular\modules\quirks\code\_quirk.dm" #include "tff_modular\modules\quirks\code\entombed.dm" #include "tff_modular\modules\quirks\code\coldbloodedquirk\coldbloodedquirk.dm" diff --git a/tgui/packages/tgui/interfaces/PreferencesMenu/preferences/features/character_preferences/tff/psyonic_school.tsx b/tgui/packages/tgui/interfaces/PreferencesMenu/preferences/features/character_preferences/tff/psyonic_school.tsx deleted file mode 100644 index ec60f99b2ee..00000000000 --- a/tgui/packages/tgui/interfaces/PreferencesMenu/preferences/features/character_preferences/tff/psyonic_school.tsx +++ /dev/null @@ -1,15 +0,0 @@ -import { FeatureChoiced } from '../../base'; -import { FeatureDropdownInput } from '../../dropdowns'; - -export const psyonic_school: FeatureChoiced = { - name: 'School', - description: 'Choose a school, which abilities you shall receive.', - component: FeatureDropdownInput, -}; - -export const psyonic_school_secondary: FeatureChoiced = { - name: 'Secondary School', - description: - 'Choose a secondary school. Abilities in it will be less powerful.', - component: FeatureDropdownInput, -}; diff --git a/tgui/packages/tgui/interfaces/PsyonicLicense.tsx b/tgui/packages/tgui/interfaces/PsionicLicense.tsx similarity index 85% rename from tgui/packages/tgui/interfaces/PsyonicLicense.tsx rename to tgui/packages/tgui/interfaces/PsionicLicense.tsx index 986149ea5d0..23870d7fae4 100644 --- a/tgui/packages/tgui/interfaces/PsyonicLicense.tsx +++ b/tgui/packages/tgui/interfaces/PsionicLicense.tsx @@ -6,28 +6,19 @@ import { Window } from '../layouts'; type Data = { character_preview: string; - primary_school: string; - secondary_school: string; - psyonic_level: string; + psionic_level: string; owner_name: string; owner_age: number; owner_species: string; owner_preview: string; }; -export const PsyonicLicense = (props) => { +export const PsionicLicense = (props) => { const { act, data } = useBackend(); - const { - primary_school, - secondary_school, - psyonic_level, - owner_name, - owner_age, - owner_species, - owner_preview, - } = data; + const { owner_name, owner_age, owner_species, owner_preview, psionic_level } = + data; return ( - + @@ -76,14 +67,8 @@ export const PsyonicLicense = (props) => { {data.owner_species} - - {data.primary_school} - - - {data.secondary_school} - - {data.psyonic_level} + {data.psionic_level} diff --git "a/tgui/packages/tgui/interfaces/\320\235\320\276\320\262\321\213\320\271 \321\202\320\265\320\272\321\201\321\202\320\276\320\262\321\213\320\271 \320\264\320\276\320\272\321\203\320\274\320\265\320\275\321\202.txt" "b/tgui/packages/tgui/interfaces/\320\235\320\276\320\262\321\213\320\271 \321\202\320\265\320\272\321\201\321\202\320\276\320\262\321\213\320\271 \320\264\320\276\320\272\321\203\320\274\320\265\320\275\321\202.txt" new file mode 100644 index 00000000000..2e4196990ea --- /dev/null +++ "b/tgui/packages/tgui/interfaces/\320\235\320\276\320\262\321\213\320\271 \321\202\320\265\320\272\321\201\321\202\320\276\320\262\321\213\320\271 \320\264\320\276\320\272\321\203\320\274\320\265\320\275\321\202.txt" @@ -0,0 +1,125 @@ +// THIS IS A FLUFFY FRONTIER FILE + +import { + BlockQuote, + Box, + Button, + Input, + NoticeBox, + Section, +} from 'tgui-core/components'; +import { useBackend } from '../backend'; +import { Window } from '../layouts'; + +export type PsiData = { + psi_rank: string; + psi_points: number; + available_psionics: Psionic[]; + bought_powers: string[]; +}; + +type Psionic = { + name: string; + desc: string; + point_cost: number; + minimum_rank: string; + path: string; +}; + +export const PsionicShop = (props, context) => { + const { act, data } = useBackend(context); + + const [searchTerm, setSearchTerm] = useLocalState( + context, + `searchTerm`, + ``, + ); + + return ( + + +
{ + setSearchTerm(value); + }} + value={searchTerm} + /> + } + > + + You are{' '} + + {data.psi_rank} + + . + + {data.psi_points ? ( + + You have{' '} + + {data.psi_points} + {' '} + points left. + + ) : ( + '' + )} + + {data.available_psionics && data.available_psionics.length ? ( + + ) : ( + There are no psionics available. + )} +
+
+
+ ); +}; + +export const PsionicsList = (props, context) => { + const { act, data } = useBackend(context); + + const [searchTerm, setSearchTerm] = useLocalState( + context, + `searchTerm`, + ``, + ); + + return ( +
+ {data.available_psionics + .filter( + (psi) => + psi.name.toLowerCase().indexOf(searchTerm.toLowerCase()) > -1, + ) + .map((psi) => ( +
data.psi_points || + data.bought_powers.includes(psi.path) + } + onClick={() => act('buy', { buy: psi.path })} + /> + } + > +
{psi.desc}
+
+ ))} +
+ ); +}; From b7b71bd92c7356fa905c765739503da9ba35d3d5 Mon Sep 17 00:00:00 2001 From: SaukyKouko Date: Wed, 20 May 2026 15:31:32 +0300 Subject: [PATCH 02/22] =?UTF-8?q?=D0=B8=D1=81=D0=BF=D1=80=D0=B0=D0=B2?= =?UTF-8?q?=D0=BB=D1=8F=D0=B5=D0=BC=20=D0=BA=D0=BE=D0=B4=20(=D0=BF=D1=8B?= =?UTF-8?q?=D1=82=D0=B0=D0=B5=D0=BC=D1=81=D1=8F)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../psionics/code/_psionic_abilities.dm | 4 + .../modules/psionics/code/psi_shop/datum.dm | 47 ++- .../modules/psyonics/code/subsystem.dm | 5 - tgui/packages/tgui/interfaces/PsionicShop.tsx | 319 ++++++++++++++++++ 4 files changed, 354 insertions(+), 21 deletions(-) delete mode 100644 tff_modular/modules/psyonics/code/subsystem.dm create mode 100644 tgui/packages/tgui/interfaces/PsionicShop.tsx diff --git a/tff_modular/modules/psionics/code/_psionic_abilities.dm b/tff_modular/modules/psionics/code/_psionic_abilities.dm index a3badfc0b13..72f936d69ab 100644 --- a/tff_modular/modules/psionics/code/_psionic_abilities.dm +++ b/tff_modular/modules/psionics/code/_psionic_abilities.dm @@ -19,6 +19,10 @@ var/category = "utility" // Цена var/point_cost = 1 + // Текст помощи + var/helptext = "" + // Доступ + var/locked = TRUE /datum/action/cooldown/spell/Grant(mob/grant_to) . = ..() diff --git a/tff_modular/modules/psionics/code/psi_shop/datum.dm b/tff_modular/modules/psionics/code/psi_shop/datum.dm index da10dfd7002..1d953f911b3 100644 --- a/tff_modular/modules/psionics/code/psi_shop/datum.dm +++ b/tff_modular/modules/psionics/code/psi_shop/datum.dm @@ -2,15 +2,33 @@ var/name = "Psi Mind" var/datum/psionic/psi_datum var/mob/living/psi_owner + var/list/possible_spells /datum/psionic_shop/New(psionic_mob, psionic_datum) . = ..() psi_datum = psionic_datum psi_owner = psionic_mob + possible_spells = get_possible_spells() + +/proc/get_possible_spells() + var/static/list/filtered_spells = list() + + var/static/list/spell_options + if(!spell_options) + spell_options = subtypesof(/datum/action/cooldown/spell) + for(var/datum/action/cooldown/spell/spell as anything in spell_options) + if(!spell.psionic) + continue + if(spell.locked) + continue + filtered_spells += spell + + return filtered_spells /datum/psionic_shop/Destroy() psi_datum = null psi_owner = null + possible_spells = null return ..() /datum/psionic_shop/ui_state(mob/user) @@ -19,8 +37,6 @@ /datum/psionic_shop/ui_status(mob/user, datum/ui_state/state) if(!psi_datum) return UI_CLOSE - if(!psi_owner) - return UI_CLOSE return UI_INTERACTIVE /datum/psionic_shop/ui_interact(mob/user, datum/tgui/ui) @@ -31,24 +47,24 @@ /datum/psionic_shop/ui_static_data(mob/user) var/list/data = list() - var/static/list/spells if(isnull(spells)) - spells = list(typecacheof(/datum/action/cooldown/spell)) - for(var/datum/action/cooldown/spell/psi_spells as anything in spells) - if(!psi_spells.psionic) + spells = list() + for(var/datum/action/cooldown/spell/spell_path as anything in possible_spells) + if(!spell_path.psionic) continue - - var/list/ability_data = list( - "name" = initial(psi_spells.name), - "desc" = initial(psi_spells.desc), - "path" = psi_spells, - "point_required" = initial(psi_spells.point_cost), - "mana_required" = initial(psi_spells.mana_cost), - "cooldown" = initial(psi_spells.cooldown_time), + if(spell_path.locked) + continue + var/list/spell_data = list( + "name" = spell_path.name, + "desc" = spell_path.desc, + "helptext" = spell_path.helptext, + "path" = spell_path, + "point_required" = spell_path.point_cost, + "category" = spell_path.category, ) - spells += list(ability_data) + spells += list(spell_data) sortTim(spells, /proc/cmp_assoc_list_name) @@ -70,7 +86,6 @@ switch(action) if("research") - // purchase_power sanity checks stuff like typepath, DNA, and absorbs for us. psi_datum.research_spell(text2path(params["path"])) return TRUE diff --git a/tff_modular/modules/psyonics/code/subsystem.dm b/tff_modular/modules/psyonics/code/subsystem.dm deleted file mode 100644 index f576297add2..00000000000 --- a/tff_modular/modules/psyonics/code/subsystem.dm +++ /dev/null @@ -1,5 +0,0 @@ -PROCESSING_SUBSYSTEM_DEF(psionic) - name = "Psionic" - priority = FIRE_PRIORITY_DEFAULT - ss_flags = SS_NO_INIT - wait = 2 SECONDS diff --git a/tgui/packages/tgui/interfaces/PsionicShop.tsx b/tgui/packages/tgui/interfaces/PsionicShop.tsx new file mode 100644 index 00000000000..9d4becdf07e --- /dev/null +++ b/tgui/packages/tgui/interfaces/PsionicShop.tsx @@ -0,0 +1,319 @@ +import { useState } from 'react'; +import { + Box, + Button, + DmIcon, + Icon, + Input, + NoticeBox, + Section, + Stack, + Tabs, + Tooltip, +} from 'tgui-core/components'; +import type { BooleanLike } from 'tgui-core/react'; + +import { useBackend } from '../backend'; +import { Window } from '../layouts'; + +// ========== +// Types +// ========== +type TypePath = string; + +type Spell = { + name: string; + desc: string; + helptext: string; + path: TypePath; + point_required: number; + category: string; +}; + +type PsionicShopContext = { + many_spells: Spell[]; + psi_points_count: number; + researched_spells: TypePath[]; +}; + +const nameToIconState = (name: string): string => { + return name.toLowerCase().replace(/\s+/g, '_'); +}; + +export const PsionicShop = (props) => { + const { act, data } = useBackend(); + const { many_spells, psi_points_count, researched_spells } = data; + + const [searchText, setSearchText] = useState(''); + const [compactMode, setCompactMode] = useState(false); + + const CATEGORY_ORDER = ['utility', 'stealth', 'combat', 'stings']; + const allCategories = Array.from( + new Set(many_spells?.map((a) => a.category)), + ); + const sortedCategories = [ + ...CATEGORY_ORDER.filter((cat) => allCategories.includes(cat)), + ...allCategories.filter((cat) => !CATEGORY_ORDER.includes(cat)), + ]; + const [selectedCategory, setSelectedCategory] = useState( + sortedCategories[0] || 'utility', + ); + + const filteredItems = ( + searchText + ? many_spells.filter((item) => + [item.name, item.desc, item.helptext] + .join(' ') + .toLowerCase() + .includes(searchText.toLowerCase()), + ) + : many_spells.filter((item) => item.category === selectedCategory) + ).sort((a, b) => a.name.localeCompare(b.name)); + + const handleBuy = (spell: Spell) => { + act('research', { path: spell.path }); + }; + + return ( + + +
+ + {psi_points_count} DNA + + + + } + > + + + + + + + +
+
+
+ ); +}; + +// ========== +// ItemList Component +// ========== +type ItemListProps = { + compactMode: BooleanLike; + items: Spell[]; + researched_spells: TypePath[]; + psi_points_count: number; + handleBuy: (item: Spell) => void; +}; + +const ItemList = (props: ItemListProps) => { + const { compactMode, items, researched_spells, psi_points_count, handleBuy } = + props; + + const iconSize = compactMode ? '32px' : '64px'; + + return ( +
+ + {items?.map((spell) => { + const owned = researched_spells.includes(spell.path); + const canAfford = !owned && spell.point_required <= psi_points_count; + + const requirementTooltip = [`${spell.point_required} Psi`].join(', '); + + const costDisplay = `Cost: ${spell.point_required} Psi`; + + const iconState = nameToIconState(spell.name); + + return ( + +
+ + + + + + } + /> + + + + + {compactMode ? ( + + + {owned ? ( + + + {spell.name} + + ) : ( + spell.name + )} + + + + + + + + + + + + + ) : ( +
+ {spell.name} + {owned && ( + + (Owned) + + )} + + } + buttons={ + + + + + + } + > + {spell.desc} + {spell.helptext && ( + + {spell.helptext} + + )} +
+ )} +
+
+
+
+ ); + })} +
+
+ ); +}; From 4c0e6d57626c9a3fd578611e2c1f20990c4b2d09 Mon Sep 17 00:00:00 2001 From: SaukyKouko Date: Wed, 20 May 2026 16:05:18 +0300 Subject: [PATCH 03/22] =?UTF-8?q?=D1=81=D0=BE=D1=85=D1=80=D0=B0=D0=BD?= =?UTF-8?q?=D1=8F=D0=B5=D0=BC=20=D0=BA=D0=BE=D0=B4?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../psionics/code/conjure_item/conjure_item.dm | 1 + .../psionics/code/projectiles/projectile.dm | 1 + .../modules/psionics/code/psi_shop/datum.dm | 7 ++----- tgui/packages/tgui/interfaces/PsionicShop.tsx | 16 ++++++++-------- 4 files changed, 12 insertions(+), 13 deletions(-) diff --git a/tff_modular/modules/psionics/code/conjure_item/conjure_item.dm b/tff_modular/modules/psionics/code/conjure_item/conjure_item.dm index 7bc2b9106c0..28d3cb952f7 100644 --- a/tff_modular/modules/psionics/code/conjure_item/conjure_item.dm +++ b/tff_modular/modules/psionics/code/conjure_item/conjure_item.dm @@ -10,6 +10,7 @@ stamina_cost = 0 psionic_level = 2 point_cost = 3 + category = "Combat" /obj/item/melee/psionic_blade name = "psionic blade" diff --git a/tff_modular/modules/psionics/code/projectiles/projectile.dm b/tff_modular/modules/psionics/code/projectiles/projectile.dm index d89807d8e1c..4ed590b3a69 100644 --- a/tff_modular/modules/psionics/code/projectiles/projectile.dm +++ b/tff_modular/modules/psionics/code/projectiles/projectile.dm @@ -36,6 +36,7 @@ projectile_type = /obj/projectile/temp/watcher/psionic_freeze psionic_level = 2 point_cost = 1 + category = "manipulation" /datum/action/cooldown/spell/pointed/projectile/psionic/freeze/is_valid_target(atom/cast_on) if(!isliving(cast_on)) diff --git a/tff_modular/modules/psionics/code/psi_shop/datum.dm b/tff_modular/modules/psionics/code/psi_shop/datum.dm index 1d953f911b3..15d9f66ccbc 100644 --- a/tff_modular/modules/psionics/code/psi_shop/datum.dm +++ b/tff_modular/modules/psionics/code/psi_shop/datum.dm @@ -47,14 +47,11 @@ /datum/psionic_shop/ui_static_data(mob/user) var/list/data = list() + var/static/list/spells if(isnull(spells)) spells = list() for(var/datum/action/cooldown/spell/spell_path as anything in possible_spells) - if(!spell_path.psionic) - continue - if(spell_path.locked) - continue var/list/spell_data = list( "name" = spell_path.name, "desc" = spell_path.desc, @@ -68,7 +65,7 @@ sortTim(spells, /proc/cmp_assoc_list_name) - data["spells"] = spells + data["many_spells"] = spells return data /datum/psionic_shop/ui_data(mob/user) diff --git a/tgui/packages/tgui/interfaces/PsionicShop.tsx b/tgui/packages/tgui/interfaces/PsionicShop.tsx index 9d4becdf07e..e1083aab9d0 100644 --- a/tgui/packages/tgui/interfaces/PsionicShop.tsx +++ b/tgui/packages/tgui/interfaces/PsionicShop.tsx @@ -19,13 +19,13 @@ import { Window } from '../layouts'; // ========== // Types // ========== -type TypePath = string; +type SpellTypePath = string; type Spell = { name: string; desc: string; helptext: string; - path: TypePath; + path: SpellTypePath; point_required: number; category: string; }; @@ -33,7 +33,7 @@ type Spell = { type PsionicShopContext = { many_spells: Spell[]; psi_points_count: number; - researched_spells: TypePath[]; + researched_spells: SpellTypePath[]; }; const nameToIconState = (name: string): string => { @@ -47,7 +47,7 @@ export const PsionicShop = (props) => { const [searchText, setSearchText] = useState(''); const [compactMode, setCompactMode] = useState(false); - const CATEGORY_ORDER = ['utility', 'stealth', 'combat', 'stings']; + const CATEGORY_ORDER = ['utility', 'combat', 'manipulation']; const allCategories = Array.from( new Set(many_spells?.map((a) => a.category)), ); @@ -92,7 +92,7 @@ export const PsionicShop = (props) => { title={ - {psi_points_count} DNA + {psi_points_count} Psi @@ -169,15 +169,15 @@ export const PsionicShop = (props) => { // ========== // ItemList Component // ========== -type ItemListProps = { +type SpellListProps = { compactMode: BooleanLike; items: Spell[]; - researched_spells: TypePath[]; + researched_spells: SpellTypePath[]; psi_points_count: number; handleBuy: (item: Spell) => void; }; -const ItemList = (props: ItemListProps) => { +const ItemList = (props: SpellListProps) => { const { compactMode, items, researched_spells, psi_points_count, handleBuy } = props; From 54c9cbc55fcb2cb2e63df8bdfa0cecb4652682ec Mon Sep 17 00:00:00 2001 From: SaukyKouko Date: Wed, 20 May 2026 16:39:41 +0300 Subject: [PATCH 04/22] =?UTF-8?q?=D0=B8=D1=81=D0=BF=D1=80=D0=B0=D0=B2?= =?UTF-8?q?=D0=BB=D1=8F=D0=B5=D0=BC=20=D0=BA=D0=BE=D0=B4?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../psionics/code/_psionic_abilities.dm | 7 ++++++- .../psionics/code/conjure_item/conjure_item.dm | 2 +- .../psionics/code/projectiles/projectile.dm | 2 +- .../modules/psionics/code/psi_shop/datum.dm | 11 ++++------- tgui/packages/tgui/interfaces/PsionicShop.tsx | 18 ++++++++---------- 5 files changed, 20 insertions(+), 20 deletions(-) diff --git a/tff_modular/modules/psionics/code/_psionic_abilities.dm b/tff_modular/modules/psionics/code/_psionic_abilities.dm index 72f936d69ab..e527944e237 100644 --- a/tff_modular/modules/psionics/code/_psionic_abilities.dm +++ b/tff_modular/modules/psionics/code/_psionic_abilities.dm @@ -44,6 +44,7 @@ spell_requirements = NONE cooldown_reduction_per_rank = 0 SECONDS psionic = TRUE + locked = FALSE /datum/action/cooldown/spell/conjure_item/psionic/New(Target, power) . = ..() @@ -64,7 +65,7 @@ var/datum/psionic/psi_holder = caster.get_psionic() caster.adjust_stamina_loss(stamina_cost, forced = TRUE) if(psi_holder) - psi_holder.adjust_psi_energy(mana_cost) + psi_holder.adjust_psi_energy(-mana_cost) return TRUE else return FALSE @@ -97,6 +98,7 @@ cooldown_reduction_per_rank = 0 SECONDS psionic = TRUE psionic_level = 1 + locked = FALSE /datum/action/cooldown/spell/psionic/New(Target, power, additional_school) . = ..() @@ -125,6 +127,7 @@ spell_requirements = NONE cooldown_reduction_per_rank = 0 SECONDS psionic = TRUE + locked = FALSE /datum/action/cooldown/spell/pointed/projectile/psionic/New(Target, power, additional_school) . = ..() @@ -152,6 +155,7 @@ spell_requirements = NONE cooldown_reduction_per_rank = 0 SECONDS psionic = TRUE + locked = FALSE /datum/action/cooldown/spell/pointed/psionic/New(Target, power, additional_school) . = ..() @@ -178,6 +182,7 @@ invocation_type = INVOCATION_NONE spell_requirements = NONE psionic = TRUE + locked = FALSE /datum/action/cooldown/spell/touch/psionic/New(Target, power, additional_school) . = ..() diff --git a/tff_modular/modules/psionics/code/conjure_item/conjure_item.dm b/tff_modular/modules/psionics/code/conjure_item/conjure_item.dm index 28d3cb952f7..4e4e0e9d9d5 100644 --- a/tff_modular/modules/psionics/code/conjure_item/conjure_item.dm +++ b/tff_modular/modules/psionics/code/conjure_item/conjure_item.dm @@ -10,7 +10,7 @@ stamina_cost = 0 psionic_level = 2 point_cost = 3 - category = "Combat" + category = "combat" /obj/item/melee/psionic_blade name = "psionic blade" diff --git a/tff_modular/modules/psionics/code/projectiles/projectile.dm b/tff_modular/modules/psionics/code/projectiles/projectile.dm index 4ed590b3a69..9d2671ca151 100644 --- a/tff_modular/modules/psionics/code/projectiles/projectile.dm +++ b/tff_modular/modules/psionics/code/projectiles/projectile.dm @@ -17,7 +17,7 @@ . = ..() if(do_after(owner, 0.5 SECONDS, timed_action_flags = IGNORE_USER_LOC_CHANGE | IGNORE_SLOWDOWNS)) return FALSE - psionic_datum.adjust_psi_energy(10) + psionic_datum.adjust_psi_energy(-10) /obj/projectile/magic/air_bullet damage = 20 diff --git a/tff_modular/modules/psionics/code/psi_shop/datum.dm b/tff_modular/modules/psionics/code/psi_shop/datum.dm index 15d9f66ccbc..ac57d10bb8c 100644 --- a/tff_modular/modules/psionics/code/psi_shop/datum.dm +++ b/tff_modular/modules/psionics/code/psi_shop/datum.dm @@ -11,19 +11,16 @@ possible_spells = get_possible_spells() /proc/get_possible_spells() - var/static/list/filtered_spells = list() - var/static/list/spell_options if(!spell_options) spell_options = subtypesof(/datum/action/cooldown/spell) for(var/datum/action/cooldown/spell/spell as anything in spell_options) if(!spell.psionic) - continue + spell_options -= spell if(spell.locked) - continue - filtered_spells += spell + spell_options -= spell - return filtered_spells + return spell_options /datum/psionic_shop/Destroy() psi_datum = null @@ -98,7 +95,7 @@ /datum/action/psionic_shop/New(Target) . = ..() if(!istype(Target, /datum/psionic_shop)) - stack_trace("psionic_shop action created with non-emporium.") + stack_trace("psionic_shop action created with wrong type.") qdel(src) /datum/action/psionic_shop/Trigger(mob/clicker, trigger_flags) diff --git a/tgui/packages/tgui/interfaces/PsionicShop.tsx b/tgui/packages/tgui/interfaces/PsionicShop.tsx index e1083aab9d0..a3c787d31ba 100644 --- a/tgui/packages/tgui/interfaces/PsionicShop.tsx +++ b/tgui/packages/tgui/interfaces/PsionicShop.tsx @@ -48,15 +48,13 @@ export const PsionicShop = (props) => { const [compactMode, setCompactMode] = useState(false); const CATEGORY_ORDER = ['utility', 'combat', 'manipulation']; - const allCategories = Array.from( - new Set(many_spells?.map((a) => a.category)), - ); + const allCategories = Array.from(new Set(many_spells.map((a) => a.category))); const sortedCategories = [ ...CATEGORY_ORDER.filter((cat) => allCategories.includes(cat)), ...allCategories.filter((cat) => !CATEGORY_ORDER.includes(cat)), ]; const [selectedCategory, setSelectedCategory] = useState( - sortedCategories[0] || 'utility', + sortedCategories[0] || 'combat', ); const filteredItems = ( @@ -91,7 +89,7 @@ export const PsionicShop = (props) => { scrollable={false} title={ - + {psi_points_count} Psi @@ -122,7 +120,7 @@ export const PsionicShop = (props) => { - {sortedCategories?.map((category) => ( + {sortedCategories.map((category) => ( { return (
- {items?.map((spell) => { + {items.map((spell) => { const owned = researched_spells.includes(spell.path); const canAfford = !owned && spell.point_required <= psi_points_count; @@ -245,7 +243,7 @@ const ItemList = (props: SpellListProps) => { }} > {owned ? ( - + {spell.name} @@ -277,7 +275,7 @@ const ItemList = (props: SpellListProps) => { {spell.name} {owned && ( - + (Owned) )} @@ -301,7 +299,7 @@ const ItemList = (props: SpellListProps) => { > {spell.desc} {spell.helptext && ( - + {spell.helptext} )} From 8d6bf8d87e148535ba33147ec441467f3e74456f Mon Sep 17 00:00:00 2001 From: SaukyKouko Date: Wed, 20 May 2026 21:45:54 +0300 Subject: [PATCH 05/22] =?UTF-8?q?=D0=B4=D0=BE=D0=BF=D0=B8=D0=BB=D0=B8?= =?UTF-8?q?=D0=B2=D0=B0=D0=B5=D0=BC=20=D0=B2=D1=81=D0=B5=20=D1=87=D1=82?= =?UTF-8?q?=D0=BE=20=D0=BD=D1=83=D0=B6=D0=BD=D0=BE?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- code/datums/mutations/reach.dm | 4 +- .../psionics/code/_psionic_abilities.dm | 2 + .../modules/psionics/code/_psionic_datum.dm | 1 + tff_modular/modules/psionics/code/coersion.dm | 376 ------------------ .../code/conjure_item/conjure_item.dm | 121 +++--- .../modules/psionics/code/energistics.dm | 145 ------- .../modules/psionics/code/pointed/pointed.dm | 228 +++++++++++ .../psionics/code/projectiles/projectile.dm | 26 +- .../modules/psionics/code/psi_shop/datum.dm | 4 +- .../modules/psionics/code/psychokinesis.dm | 211 ---------- .../modules/psionics/code/redaction.dm | 359 ----------------- .../modules/psionics/code/spell/spell.dm | 144 +++++++ .../modules/psionics/code/subsystem.dm | 5 - .../modules/psionics/code/touch/touch.dm | 219 +++++++++- .../modules/psionics/code/touch/touch_hand.dm | 33 ++ .../modules/psionics/icons/psi_items.dmi | Bin 0 -> 1863 bytes tff_modular/modules/psionics/icons/spells.dmi | Bin 27570 -> 23724 bytes tgstation.dme | 7 +- tgui/packages/tgui/interfaces/PsionicShop.tsx | 2 +- 19 files changed, 721 insertions(+), 1166 deletions(-) delete mode 100644 tff_modular/modules/psionics/code/coersion.dm delete mode 100644 tff_modular/modules/psionics/code/energistics.dm delete mode 100644 tff_modular/modules/psionics/code/psychokinesis.dm delete mode 100644 tff_modular/modules/psionics/code/redaction.dm create mode 100644 tff_modular/modules/psionics/code/spell/spell.dm delete mode 100644 tff_modular/modules/psionics/code/subsystem.dm create mode 100644 tff_modular/modules/psionics/code/touch/touch_hand.dm create mode 100644 tff_modular/modules/psionics/icons/psi_items.dmi diff --git a/code/datums/mutations/reach.dm b/code/datums/mutations/reach.dm index fb434b3c630..27706d81b35 100644 --- a/code/datums/mutations/reach.dm +++ b/code/datums/mutations/reach.dm @@ -10,9 +10,11 @@ ///Typecache of atoms that TK shouldn't interact with var/static/list/blacklisted_atoms = typecacheof(list(/atom/movable/screen)) + var/no_effect = FALSE // TFF ADDITION + /datum/mutation/telekinesis/New(datum/mutation/copymut) ..() - if(!(type in visual_indicators)) + if(!(type in visual_indicators) && !no_effect) // TFF EDIT - ORIGINAL: if(!(type in visual_indicators)) visual_indicators[type] = list(mutable_appearance('modular_nova/master_files/icons/effects/tele_effects.dmi', "telekinesishead", -MUTATIONS_LAYER)) //NOVA EDIT CHANGE - ORIGINAL: visual_indicators[type] = list(mutable_appearance('icons/mob/effects/genetics.dmi', "telekinesishead", -MUTATIONS_LAYER)) /datum/mutation/telekinesis/on_acquiring(mob/living/carbon/human/homan) diff --git a/tff_modular/modules/psionics/code/_psionic_abilities.dm b/tff_modular/modules/psionics/code/_psionic_abilities.dm index e527944e237..7ea0d4ab4d9 100644 --- a/tff_modular/modules/psionics/code/_psionic_abilities.dm +++ b/tff_modular/modules/psionics/code/_psionic_abilities.dm @@ -128,6 +128,7 @@ cooldown_reduction_per_rank = 0 SECONDS psionic = TRUE locked = FALSE + cast_range = 7 /datum/action/cooldown/spell/pointed/projectile/psionic/New(Target, power, additional_school) . = ..() @@ -156,6 +157,7 @@ cooldown_reduction_per_rank = 0 SECONDS psionic = TRUE locked = FALSE + cast_range = 7 /datum/action/cooldown/spell/pointed/psionic/New(Target, power, additional_school) . = ..() diff --git a/tff_modular/modules/psionics/code/_psionic_datum.dm b/tff_modular/modules/psionics/code/_psionic_datum.dm index 1aa357e23ea..37cfe7453b1 100644 --- a/tff_modular/modules/psionics/code/_psionic_datum.dm +++ b/tff_modular/modules/psionics/code/_psionic_datum.dm @@ -145,6 +145,7 @@ return FALSE if(mana_level <= 0) psi_owner.adjust_stamina_loss(200) + psi_owner.SetStun(5 SECONDS) var/delta_time = DELTA_WORLD_TIME(SSmobs) var/mob/living/carbon/human/human_holder = psi_owner diff --git a/tff_modular/modules/psionics/code/coersion.dm b/tff_modular/modules/psionics/code/coersion.dm deleted file mode 100644 index 3845b93f4b1..00000000000 --- a/tff_modular/modules/psionics/code/coersion.dm +++ /dev/null @@ -1,376 +0,0 @@ -#define IS_HYPNOTIZED(mob) (mob?.mind?.has_antag_datum(/datum/antagonist/hypnotized)) -#define IS_OBSESSED(mob) (mob?.mind?.has_antag_datum(/datum/antagonist/obsessed)) - -/// Школа внушения. 7 спеллов -/// Psionic assay - скан, является ли человек псиоником -/// Psionic focus - лечение мозга и псих болезней -/// Psionic mind read - продвинутое чтение разума(Как обычная ген. мутация, но + работа + воспоминания). Выдаётся только и ТОЛЬКО психологу, если он псионик -/// Psionic agony - работает как стан дубинка, исчезает после одного удара -/// Psionic spasm - станит на полсекунды, заставляет выронить всё из рук. Работает дистанционно -/// Psionic hypnosis - гипнотизирует цель фразой, которую выбрал псионик. ERP IS BAD. DO NOT ERP. -/// P.S. По гипнозу. В оригинале на финиках вообще было порабощение разума. -/// Psionic blind - временно ослепляет. - -// Лечим мозги и брейнтравмы. -/datum/action/cooldown/spell/pointed/psionic/psionic_focus - name = "Psionic Focus" - desc = "Try to restore patients brain to its natural initial condition, fixing brain damage. Has a chance to heal traumas. Can be cast over distance." - button_icon = 'icons/obj/medical/organs/organs.dmi' - button_icon_state = "brain-smooth" - cooldown_time = 1 SECONDS - mana_cost = 40 - target_msg = "You feel like someone is messing with your brains." - active_msg = "You prepare to heal someones mind..." - psionic_level = 2 - point_cost = 3 - -/datum/action/cooldown/spell/pointed/psionic/psionic_focus/is_valid_target(atom/cast_on) - if(!ishuman(cast_on)) - return FALSE - if(issynthetic(cast_on) && cast_power < 2) - to_chat(owner, span_notice("I dont know how to work with synths.")) - return FALSE - return TRUE - -/datum/action/cooldown/spell/pointed/psionic/psionic_focus/cast(mob/living/carbon/human/cast_on) - . = ..() - if(cast_on.can_block_magic(antimagic_flags)) - to_chat(cast_on, span_notice("Your mind is being healed by a psionic nearby.")) - else - to_chat(cast_on, span_warning(target_msg)) - owner.Beam(cast_on, icon_state = "blood_light", time = 5 SECONDS) - owner.visible_message(span_warning("[owner] seems to concentrate on something."), - span_notice("You start concentrating your energy to heal [cast_on]s brains.")) - if(!do_after(owner, 5 SECONDS, cast_on, IGNORE_SLOWDOWNS | IGNORE_TARGET_LOC_CHANGE, TRUE)) - accident_harm(cast_on) - else - fix_brainz(cast_on) - drain_mana() - return TRUE - -/datum/action/cooldown/spell/pointed/psionic/psionic_focus/proc/fix_brainz(mob/living/carbon/human/cast_on) - var/b_damage = cast_on.get_organ_loss(ORGAN_SLOT_BRAIN) - if(b_damage > 0) - cast_on.adjust_organ_loss(ORGAN_SLOT_BRAIN, -10 * cast_power) - - var/traumas = cast_on.get_traumas() - if(traumas) - var/datum/brain_trauma/trauma = pick(traumas) - if(trauma.resilience != TRAUMA_RESILIENCE_ABSOLUTE) - cast_on.cure_trauma_type(resilience = trauma.resilience) - cast_on.apply_status_effect(/datum/status_effect/drugginess, 20 SECONDS) - -/datum/action/cooldown/spell/pointed/psionic/psionic_focus/proc/accident_harm(mob/living/carbon/human/cast_on) - cast_on.adjust_organ_loss(ORGAN_SLOT_BRAIN, 15 * cast_power, 101) - to_chat(cast_on, span_bolddanger("You head hurts!")) - -// Читаем разум. Выдаёт: последние сейлоги, интент, настоящее имя, воспоминания, намёк на работу, намёк на то, что в антаг_датум что то есть. -/datum/action/cooldown/spell/touch/psionic/psionic_mind_read - name = "Psionic Mind Read" - desc = "Rudely intrude into targets thoughts." - button_icon = 'icons/mob/actions/actions_spells.dmi' - button_icon_state = "mindread" - cooldown_time = 3 SECONDS - mana_cost = 40 - stamina_cost = 40 - target_msg = "You feel someone else in your head." - - hand_path = /obj/item/melee/touch_attack/psionic_mending - draw_message = span_notice("You ready your hand to read someones mind.") - drop_message = span_notice("You lower your hand.") - can_cast_on_self = FALSE - psionic_level = 3 - -/datum/action/cooldown/spell/touch/psionic/psionic_mind_read/cast_on_hand_hit(obj/item/melee/touch_attack/hand, atom/victim, mob/living/carbon/mendicant) - if(ishuman(victim)) - var/mob/living/carbon/human/human_victim = victim - if(human_victim.mind && human_victim.stat != DEAD) - if(human_victim.can_block_magic(antimagic_flags)) - to_chat(human_victim, span_bolddanger("Psionic nearby tries to read your mind!")) - else - to_chat(human_victim, span_warning(target_msg)) - owner.visible_message(span_warning("[owner] presses his thumb onto [victim]s forehead."), - span_notice("You press your thumb onto [victim]s forehead and begin reading them.")) - to_chat(victim, span_danger("[owner] presses a thumb onto your forehead and holds it there. It burns sligthly!")) - if(do_after(mendicant, 10 SECONDS, human_victim, IGNORE_SLOWDOWNS, TRUE)) - read_mind(human_victim) - drain_mana() - return TRUE - else - return FALSE - else - return FALSE - -/datum/action/cooldown/spell/touch/psionic/psionic_mind_read/proc/read_mind(mob/living/carbon/human/patient) - if(patient.can_block_magic(MAGIC_RESISTANCE_MIND, charge_cost = 0)) - to_chat(owner, span_warning("As you reach into [patient]'s mind, \ - you are stopped by a mental blockage. It seems you've been foiled.")) - return - if(issynthetic(patient) && cast_power < 2) - to_chat(owner, span_notice("I dont know how to work with synths. It's just zeros and ones. How am I supposed to get info out of this metal bucket?")) - return - var/text_to_show = "" - - var/list/recent_speech = patient.copy_recent_speech(copy_amount = 10) - if(length(recent_speech)) - text_to_show += span_boldnotice("You catch some drifting memories of their past conversations...") + "
" - for(var/spoken_memory in recent_speech) - text_to_show += span_notice("[spoken_memory]") + "
" - - text_to_show += span_notice("You find that their intent is to [patient.combat_mode ? "harm" : "help"]...") + "
" - text_to_show += span_notice("You uncover that [patient.p_their()] true identity is [patient.mind.name].") + "
" - if(cast_power >= 3) - text_to_show += span_notice("You can vaguely read their memories: ") + boxed_message(span_italics(get_memories(patient))) - text_to_show += span_notice("You try to read their job: ") + boxed_message(span_italics(get_job_fluff(patient))) - if(patient.mind.enslaved_to || IS_HYPNOTIZED(patient)) - text_to_show += span_boldnotice("[patient.p_Their()] will is not free.") + "
" - if(IS_OBSESSED(patient)) - text_to_show += span_boldnotice("[patient.p_Their()] mind is assaulted by voices within. They should visit a brain surgeon.") + "
" - var/datum/mind/mind_to_read = patient.mind - if(prob(20 * cast_power) && mind_to_read.antag_datums) - if(IS_WIZARD(patient)) - text_to_show += span_notice("You can feel a strong potential pulsating in this individual.") + "
" - else if(IS_HERETIC(patient)) - text_to_show += span_notice("Reality bends around you and goes back to normal, as you try to read [patient.p_their()] mind.") + "
" - var/mob/living/carbon/human/human_owner = owner - human_owner.add_mood_event("gates_of_mansus", /datum/mood_event/gates_of_mansus) - else if(IS_CULTIST(patient)) - text_to_show += span_red("Your mind is assaulted with torrents of blood and gore, as you try to dig deeper.") + "
" - else // Там очень много ролей, в том числе не антажных, а мага, еретика и культиста я думаю и без этой способности найти легко. Тем более мы читаем воспоминания, что более имбово - text_to_show += span_notice("You also can feel something hidden within [patient.p_their()] mind, but it's not readable.") + "
" - - to_chat(owner, boxed_message(span_infoplain(text_to_show))) - -// Возвращает размытый текст о профессии -/datum/action/cooldown/spell/touch/psionic/psionic_mind_read/proc/get_job_fluff(mob/living/carbon/human/patient) - var/datum/mind/mind_to_read = patient.mind - var/datum/job/patient_job = mind_to_read.assigned_role - var/text_to_return = "" - if(patient_job.departments_bitflags & DEPARTMENT_BITFLAG_SECURITY) - text_to_return += "This persons job involves beating up mimes and clowns." + "
" - else if(patient_job.departments_bitflags & DEPARTMENT_BITFLAG_CENTRAL_COMMAND) - text_to_return += "This persons is a greatest authority on this station." + "
" - else if(patient_job.departments_bitflags & DEPARTMENT_BITFLAG_CAPTAIN) - text_to_return += "This persons is likely to have megalomania." + "
" - else if(patient_job.departments_bitflags & DEPARTMENT_BITFLAG_COMMAND) - text_to_return += "This persons calling is commanding others." + "
" - else if(patient_job.departments_bitflags & DEPARTMENT_BITFLAG_SERVICE) - text_to_return += "This persons labor is about servicing others." + "
" - else if(patient_job.departments_bitflags & DEPARTMENT_BITFLAG_CARGO) - text_to_return += "This person works physically a lot." + "
" - else if(patient_job.departments_bitflags & DEPARTMENT_BITFLAG_ENGINEERING) - text_to_return += "This person keeps station alive." + "
" - else if(patient_job.departments_bitflags & DEPARTMENT_BITFLAG_SCIENCE) - text_to_return += "This person is an egghead." + "
" - else if(patient_job.departments_bitflags & DEPARTMENT_BITFLAG_MEDICAL) - text_to_return += "This person is accustomed with wounds, blood and their treatment." + "
" - else if(patient_job.departments_bitflags & DEPARTMENT_BITFLAG_SILICON) - text_to_return += "This is en etenral mankinds servant." + "
" - else if(patient_job.departments_bitflags & DEPARTMENT_BITFLAG_ASSISTANT) - text_to_return += "This persons mind reeks of freedom." + "
" - else - text_to_return += "This person is truly free. They are not obligated with any duties." + "
" - - return span_notice(text_to_return) - -// Возвращает воспоминания разума. Имба против таторов, так как там хранится код от аплинка. А ну и банковский айди. -/datum/action/cooldown/spell/touch/psionic/psionic_mind_read/proc/get_memories(mob/living/carbon/human/patient) - var/datum/mind/mind_to_read = patient.mind - if(mind_to_read) - var/itogo_text = "" - for(var/key in mind_to_read.memories) - var/datum/memory/mem = mind_to_read.memories[key] - itogo_text += mem.name + "
" - if(itogo_text == "") - itogo_text = "[patient.p_Their()] head is empty." - return itogo_text - else - return "I cant read [patient.p_their()] memories. Maybe there are none?" + "
" - -// Stun batong на минималках. Исчезает после одного удара -/datum/action/cooldown/spell/touch/psionic/psionic_agony - name = "Psionic Agony" - desc = "Deals pain." - button_icon = 'icons/obj/weapons/baton.dmi' - button_icon_state = "stunbaton_active" - cooldown_time = 0.5 SECONDS - mana_cost = 40 - stamina_cost = 0 - hand_path = /obj/item/melee/touch_attack/psionic_mending - draw_message = span_notice("You ready your hand to deal pain.") - drop_message = span_notice("You lower your hand.") - can_cast_on_self = TRUE // Упс :) - -/datum/action/cooldown/spell/touch/psionic/psionic_agony/cast_on_hand_hit(obj/item/melee/touch_attack/hand, atom/victim, mob/living/carbon/mendicant) - if(ishuman(victim)) - var/mob/living/carbon/human/human_victim = victim - if(human_victim.can_block_magic(antimagic_flags)) - to_chat(human_victim, span_notice("Psionic nearby tries to attack you, but fails.")) - to_chat(owner, span_notice("You can't attack them. They have some kind of protection.")) - return FALSE - if(issynthetic(human_victim) && cast_power < 2) - human_victim.visible_message(span_danger("[owner] slaps [human_victim] with his hand. Nothing happens. Wow!"), - span_warning("You slap [human_victim], but nothing happens. You cannot transfer your energy through metal."), - blind_message = span_hear("You hear a slap.")) - return FALSE - else - to_chat(human_victim, span_warning("Pain floods your body as soon as [owner] touches you!.")) - psionic_attack(human_victim) - log_combat(owner, human_victim, "psionically stunned") - drain_mana() - return TRUE - else - return FALSE - -// Прок удара -/datum/action/cooldown/spell/touch/psionic/psionic_agony/proc/psionic_attack(mob/living/carbon/human/patient) - patient.apply_damage(35, STAMINA) // Стандартный стан батонг - addtimer(CALLBACK(src, PROC_REF(apply_stun_effect), patient), 2 SECONDS) - -/datum/action/cooldown/spell/touch/psionic/psionic_agony/proc/apply_stun_effect(mob/living/carbon/human/patient) - patient.visible_message(span_danger("[owner] slaps [patient] with his hand, sparks flying out of it!"), - span_warning("You slap [patient], stunning him."), - blind_message = span_hear("You hear a slap and an electrical crackling afterwards.")) - var/trait_check = HAS_TRAIT(patient, TRAIT_BATON_RESISTANCE) //var since we check it in out to_chat as well as determine stun duration - if(!patient.IsKnockdown()) - to_chat(patient, span_warning("Your muscles seize, making you collapse[trait_check ? ", but your body quickly recovers..." : "!"]")) - - if(!trait_check) - patient.Knockdown((cast_power/2) SECONDS) - -// Станит на непродолжительный срок(~0.5 сек) и заставляет выкинуть вещи из рук -/datum/action/cooldown/spell/pointed/psionic/psionic_spasm - name = "Psionic Spasm" - desc = "Activate neurons in victims mucles, briefly stunning them and forcing to drop everything in their hands. Can be cast over distance. Silent." - button_icon = 'tff_modular/modules/psionics/icons/actions.dmi' - button_icon_state = "spasm" - cooldown_time = 1 SECONDS - mana_cost = 40 - target_msg = "Your muscles spasm!" - active_msg = "You prepare to stun a target..." - -/datum/action/cooldown/spell/pointed/psionic/psionic_spasm/is_valid_target(atom/cast_on) - if(!ishuman(cast_on)) - return FALSE - - if(issynthetic(cast_on) && cast_power < 2) - to_chat(owner, span_notice("I dont know how to work with synths.")) - return FALSE - - return TRUE - -/datum/action/cooldown/spell/pointed/psionic/psionic_spasm/cast(mob/living/carbon/human/cast_on) - . = ..() - if(cast_on.can_block_magic(antimagic_flags)) - to_chat(cast_on, span_warning("Your body is assaulted with psionic energy!")) - else - to_chat(cast_on, span_warning(target_msg)) - log_combat(owner, cast_on, "psionically spasmed") - stun(cast_on) - drain_mana() - return TRUE - -// Сам стан -/datum/action/cooldown/spell/pointed/psionic/psionic_spasm/proc/stun(mob/living/carbon/human/cast_on) - cast_on.Stun(0.2 SECONDS * cast_power) - -/** - * Гипнотизирует игрока заданной фразой и даёт брейнтравму с ней - * - * Условия: - * * 30 секунд ожидания - * * в агрограбе - * * без движения жертвы или псионика - */ -/datum/action/cooldown/spell/touch/psionic/psionic_hypnosis - name = "Psionic Hypnosis" - desc = "Implant a looping pattern into victims head." - button_icon = 'tff_modular/modules/psionics/icons/actions.dmi' - button_icon_state = "hypno" - cooldown_time = 10 SECONDS - - mana_cost = 25 // Стоит немного - stamina_cost = 50 // Но выматывет - target_msg = "Your get a headache." - - hand_path = /obj/item/melee/touch_attack/psionic_mending - draw_message = span_notice("You ready your hand to hypnotize a victim.") - drop_message = span_notice("You lower your hand.") - can_cast_on_self = FALSE // No - -/datum/action/cooldown/spell/touch/psionic/psionic_hypnosis/cast_on_hand_hit(obj/item/melee/touch_attack/hand, atom/victim, mob/living/carbon/mendicant) - if(ishuman(victim) && mendicant.grab_state == GRAB_AGGRESSIVE && mendicant.pulling == victim) - var/mob/living/carbon/human/human_victim = victim - if(HAS_MIND_TRAIT(human_victim, TRAIT_UNCONVERTABLE)) // Не работает на людей с МЩ - to_chat(owner, span_warning("Victims mind is too strong for you to penetrate.")) - return FALSE - if(human_victim.can_block_magic(antimagic_flags)) - to_chat(human_victim, span_boldwarning("Psionic nearby tries to hypnotize you!")) - else - to_chat(human_victim, span_warning(target_msg)) - owner.visible_message(span_warning("[owner] firmly grabs [victim]s and begins creepely staring onto them."), - span_notice("You grab [victim]s head and begin implanting a thought into them.")) - var/player_input = tgui_input_text(mendicant, "Hypnophrase", "Input the hypnophrase", max_length = MAX_MESSAGE_LEN) - if(!player_input) - return FALSE - if(do_after(mendicant, (10 - cast_power) SECONDS, human_victim, IGNORE_SLOWDOWNS, TRUE)) - hypnotize(human_victim, player_input) - else - to_chat(owner, span_warning("You failed to hypnotize the victim.")) - drain_mana() - return TRUE - else - to_chat(owner, span_notice("You need to grab a human in aggressive grab to hypnotize them.")) - return FALSE - -/datum/action/cooldown/spell/touch/psionic/psionic_hypnosis/proc/hypnotize(mob/living/carbon/human/patient, hypnophrase) - patient.cure_trauma_type(/datum/brain_trauma/hypnosis, TRAUMA_RESILIENCE_SURGERY) - - owner.log_message("hypnotised [key_name(patient)] with the phrase '[hypnophrase]'", LOG_ATTACK, color="red") - - patient.log_message("has been hypnotised by the phrase '[hypnophrase]' spoken by [key_name(owner)]", LOG_VICTIM, color="orange", log_globally = FALSE) - - addtimer(CALLBACK(patient, TYPE_PROC_REF(/mob/living/carbon, gain_trauma), /datum/brain_trauma/hypnosis, TRAUMA_RESILIENCE_SURGERY, hypnophrase), 1 SECONDS) - addtimer(CALLBACK(patient, TYPE_PROC_REF(/mob/living, Stun), 60, TRUE, TRUE), 15) - -// Ослепляет цель на дистанции на ~15 секунд. Способность максимального уровня -/datum/action/cooldown/spell/pointed/psionic/psionic_blind - name = "Psionic Blind" - desc = "Interfere with the way neuron signals are transmitted in the victims eyes." - button_icon_state = "blind" - ranged_mousepointer = 'icons/effects/mouse_pointers/blind_target.dmi' - cooldown_time = 1 SECONDS - mana_cost = 60 - target_msg = "You eyes hurt!" - active_msg = "You prepare to blind a target..." - -/datum/action/cooldown/spell/pointed/psionic/psionic_blind/is_valid_target(atom/cast_on) - if(!ishuman(cast_on)) - return FALSE - else - var/mob/living/carbon/human/victim = cast_on - if(victim.is_blind()) - to_chat(owner, span_notice("[victim] is already blind.")) - return FALSE - if(issynthetic(cast_on) && cast_power < 2) - to_chat(owner, span_notice("I dont know how to work with synths.")) - return FALSE - - return TRUE - -/datum/action/cooldown/spell/pointed/psionic/psionic_blind/cast(mob/living/carbon/human/cast_on) - . = ..() - if(cast_on.can_block_magic(antimagic_flags)) - to_chat(cast_on, span_warning("Your eyes are burned with psionic energy!")) - else - to_chat(cast_on, span_warning(target_msg)) - log_combat(owner, cast_on, "psionically blinded") - blind(cast_on) - drain_mana() - return TRUE - -/datum/action/cooldown/spell/pointed/psionic/psionic_blind/proc/blind(mob/living/carbon/human/cast_on) - cast_on.adjust_temp_blindness( (10 + cast_power * 2) SECONDS) - -#undef IS_HYPNOTIZED -#undef IS_OBSESSED diff --git a/tff_modular/modules/psionics/code/conjure_item/conjure_item.dm b/tff_modular/modules/psionics/code/conjure_item/conjure_item.dm index 4e4e0e9d9d5..8e7c96b4587 100644 --- a/tff_modular/modules/psionics/code/conjure_item/conjure_item.dm +++ b/tff_modular/modules/psionics/code/conjure_item/conjure_item.dm @@ -7,11 +7,16 @@ cooldown_time = 1.5 SECONDS item_type = /obj/item/melee/psionic_blade mana_cost = 40 - stamina_cost = 0 psionic_level = 2 point_cost = 3 category = "combat" +/datum/action/cooldown/spell/conjure_item/psionic/psiblade/make_item(atom/caster) + var/obj/item/summoning_obj = item_type + summoning_obj.force = 15 * cast_power + summoning_obj.block_chance = 25 * cast_power + return ..() + /obj/item/melee/psionic_blade name = "psionic blade" desc = "A concentrated collection of particles and energy that looks like a swords blade.." @@ -21,13 +26,13 @@ lefthand_file = 'icons/mob/inhands/weapons/swords_lefthand.dmi' righthand_file = 'icons/mob/inhands/weapons/swords_righthand.dmi' w_class = WEIGHT_CLASS_HUGE - force = 30 + force = 15 throwforce = 10 hitsound = 'sound/items/weapons/blade1.ogg' attack_verb_continuous = list("attacks", "slashes", "stabs", "slices", "tears", "lacerates", "rips", "dices", "cuts") attack_verb_simple = list("attack", "slash", "stab", "slice", "tear", "lacerate", "rip", "dice", "cut") sharpness = SHARP_EDGED - block_chance = 0 + block_chance = 25 item_flags = DROPDEL | ABSTRACT | HAND_ITEM color = COLOR_BRIGHT_BLUE @@ -35,51 +40,6 @@ . = ..() ADD_TRAIT(src, TRAIT_EXAMINE_SKIP, INNATE_TRAIT) -// Спавнит зажигалку в руке. Очень полезно -/datum/action/cooldown/spell/conjure_item/psionic/psilighter - name = "Psi lighter" - desc = "Concentrates psionic energy to create a small flame in your hand." - button_icon = 'icons/obj/cigarettes.dmi' - button_icon_state = "match_lit" - cooldown_time = 1.5 SECONDS - item_type = /obj/item/psionic_fire - mana_cost = 5 - stamina_cost = 0 - point_cost = 1 - -/datum/action/cooldown/spell/conjure_item/psionic/psilighter/post_created(atom/cast_on, atom/created) - . = ..() - var/obj/item/psionic_fire/fire = created - fire.force *= cast_power - -/obj/item/psionic_fire - name = "small psionic fire" - desc = "Small bluish fire, that jumps on your fingers and surprisigly doesn't burn them." - icon = 'icons/obj/weapons/hand.dmi' - icon_state = "greyscale" - color = COLOR_BRIGHT_BLUE - inhand_icon_state = "greyscale" - light_range = 2 - light_power = 2 - light_color = LIGHT_COLOR_LIGHT_CYAN - light_on = TRUE - damtype = BURN - force = 5 - attack_verb_continuous = list("burns", "singes") - attack_verb_simple = list("burn", "singe") - resistance_flags = FIRE_PROOF - w_class = WEIGHT_CLASS_HUGE - light_system = OVERLAY_LIGHT - toolspeed = 2 - tool_behaviour = TOOL_WELDER - item_flags = DROPDEL | ABSTRACT | HAND_ITEM - heat = HIGH_TEMPERATURE_REQUIRED - 100 - -/obj/item/psionic_fire/Initialize(mapload) - . = ..() - - ADD_TRAIT(src, TRAIT_EXAMINE_SKIP, INNATE_TRAIT) - // Спавнит омни инструмент в руке псионика. Аналог абдукторского /datum/action/cooldown/spell/conjure_item/psionic/psitool name = "Psi tool" @@ -89,8 +49,7 @@ cooldown_time = 60 SECONDS item_type = /obj/item/psionic_omnitool mana_cost = 20 - stamina_cost = 30 - point_cost = 1 + category = "utility" /datum/action/cooldown/spell/conjure_item/psionic/psiblade/make_item(atom/caster) var/obj/item/made_item = new item_type(caster.loc, cast_power) @@ -98,3 +57,65 @@ var/mob/living/carbon/human/caster_pawn = owner caster_pawn.emote_snap() return made_item + +// Копирка с абдукторского +/obj/item/psionic_omnitool + name = "psionic omnitool" + desc = "Space Swiss Army Knife, able to shapeshift itself to fulfill psionics needs." + icon = 'tff_modular/modules/psionics/icons/psi_items.dmi' + lefthand_file = 'icons/mob/inhands/antag/abductor_lefthand.dmi' + righthand_file = 'icons/mob/inhands/antag/abductor_righthand.dmi' + icon_state = "omnitool" + inhand_icon_state = "silencer" + toolspeed = 1 + tool_behaviour = TOOL_SCREWDRIVER + color = COLOR_BRIGHT_BLUE + usesound = 'sound/items/pshoom/pshoom.ogg' + item_flags = DROPDEL | ABSTRACT | HAND_ITEM + var/list/tool_list = list() + +/obj/item/psionic_omnitool/Initialize(mapload) + . = ..() + tool_list = list( + "Crowbar" = image(icon = 'tff_modular/modules/psionics/icons/psi_items.dmi', icon_state = "crowbar"), + "Screwdriver" = image(icon = 'tff_modular/modules/psionics/icons/psi_items.dmi', icon_state = "screwdriver"), + "Wirecutters" = image(icon = 'tff_modular/modules/psionics/icons/psi_items.dmi', icon_state = "cutters"), + "Wrench" = image(icon = 'tff_modular/modules/psionics/icons/psi_items.dmi', icon_state = "wrench"), + ) + ADD_TRAIT(src, TRAIT_EXAMINE_SKIP, INNATE_TRAIT) + +/obj/item/psionic_omnitool/get_all_tool_behaviours() + return list( + TOOL_CROWBAR, + TOOL_SCREWDRIVER, + TOOL_WIRECUTTER, + TOOL_WRENCH, + ) + +/obj/item/psionic_omnitool/examine() + . = ..() + . += " The mode is: [tool_behaviour]" + +/obj/item/psionic_omnitool/attack_self(mob/user) + if(!user) + return + + var/tool_result = show_radial_menu(user, src, tool_list, custom_check = CALLBACK(src, PROC_REF(check_menu), user), require_near = TRUE, tooltips = TRUE) + if(!check_menu(user)) + return + switch(tool_result) + if("Crowbar") + tool_behaviour = TOOL_CROWBAR + if("Screwdriver") + tool_behaviour = TOOL_SCREWDRIVER + if("Wirecutters") + tool_behaviour = TOOL_WIRECUTTER + if("Wrench") + tool_behaviour = TOOL_WRENCH + +/obj/item/psionic_omnitool/proc/check_menu(mob/user) + if(!istype(user)) + return FALSE + if(user.incapacitated || !user.Adjacent(src)) + return FALSE + return TRUE diff --git a/tff_modular/modules/psionics/code/energistics.dm b/tff_modular/modules/psionics/code/energistics.dm deleted file mode 100644 index ca55d772ac0..00000000000 --- a/tff_modular/modules/psionics/code/energistics.dm +++ /dev/null @@ -1,145 +0,0 @@ -/// Школа энергетики. 6 спеллов -/// Spark - создаёт искры в указанном месте -/// Discharge - разряжает АПЦ/Батарейку. Даёт ману в зависимости от кол-ва энергии -/// Laser - стрелеят концентрированным пучком фотонов, пусть и не самым сильным. -/// Distrupt - создаёт ЭМИ с небольшим радиусом. -/// Elecrocute - добавляет мутацию shock touch -/// Freeze - заковывает моба в лёд на небольшой промежуток. - -// Разрядка АПЦ или батареек в обмен на ману -/datum/action/cooldown/spell/touch/psionic/psionic_discharge - name = "Psionic Discharge" - desc = "Try to discharge battery and convert electricity into raw psionic energy." - button_icon = 'modular_nova/modules/aesthetics/cells/icons/cell.dmi' - button_icon_state = "icell" - cooldown_time = 30 SECONDS - mana_cost = 0 - stamina_cost = 15 - hand_path = /obj/item/melee/touch_attack/psionic_mending - draw_message = span_notice("You ready your hand to discharge an energy source.") - drop_message = span_notice("You lower your hand.") - can_cast_on_self = FALSE - -/datum/action/cooldown/spell/touch/psionic/psionic_discharge/is_valid_target(atom/cast_on) - return isatom(cast_on) - -/datum/action/cooldown/spell/touch/psionic/psionic_discharge/cast_on_hand_hit(obj/item/melee/touch_attack/hand, atom/victim, mob/living/carbon/mendicant) - if(HAS_TRAIT(mendicant, TRAIT_MINDSHIELD)) // Womp womp - to_chat(mendicant, span_warning("As soon as you touch [victim], your energy dissipates without a trace. Mindshield implant messes up your concentration.")) - return FALSE - if(istype(victim, /obj/item/stock_parts/power_store) || istype(victim, /obj/machinery/power/apc)) - owner.visible_message(span_warning("[owner] presses his hands against [victim]."), - span_notice("You press your hands against [victim]."), - blind_message = span_hear("You hear electrical crackling.")) - if(do_after(mendicant, 2.5 SECONDS, victim, IGNORE_SLOWDOWNS, TRUE)) - var/datum/psionic/psi_holder = mendicant.get_psionic() - if(!psi_holder) - return FALSE - if(istype(victim, /obj/item/stock_parts/power_store)) - var/obj/item/stock_parts/power_store/batt = victim - var/to_charge = (batt.charge / STANDARD_CELL_VALUE) - batt.use(batt.charge(), TRUE) - psi_holder.mana_level = clamp(psi_holder.mana_level + to_charge, 0, psi_holder.max_mana) - else if(istype(victim, /obj/machinery/power/apc)) - var/obj/machinery/power/apc/target_apc = victim - var/obj/item/stock_parts/power_store/batt = target_apc.cell - if(!batt) - to_chat(owner, span_notice("There is no battery in this APC.")) - return FALSE - var/to_charge = (batt.charge() / (STANDARD_BATTERY_CHARGE/10)) - batt.use(batt.charge(), TRUE) - psi_holder.mana_level = clamp(psi_holder.mana_level + to_charge, 0, psi_holder.max_mana) - else - to_chat(owner, span_notice("You've failed to discharge energy.")) - return TRUE - else - return FALSE - -// Создаёт искры в указанном месте -/datum/action/cooldown/spell/pointed/psionic/psionic_spark - name = "Psionic Spark" - desc = "Cause some sparks to appear at a place of your choice." - button_icon = 'icons/effects/effects.dmi' - button_icon_state = "blessed" - cooldown_time = 1 SECONDS - mana_cost = 10 - active_msg = "You prepare to create sparks..." - -/datum/action/cooldown/spell/pointed/psionic/psionic_spark/is_valid_target(atom/cast_on) - if(!isturf(cast_on)) - return FALSE - return TRUE - -/datum/action/cooldown/spell/pointed/psionic/psionic_spark/cast(turf/cast_on) - . = ..() - var/mob/living/carbon/human/caster = owner - caster.emote_snap() - var/datum/effect_system/basic/spark_spread/sparks = new (src, 1, FALSE) - sparks.attach(cast_on) - sparks.start() - drain_mana() - return TRUE - -// Стреляет по направлению куклы псионика фотонной пушкой. Считайте аналог флешки -/datum/action/cooldown/spell/basic_projectile/psionic_laser - name = "Photon Laser" - desc = "Channels psionic energy into a weak concentrated photon laser." - button_icon = 'icons/obj/weapons/guns/projectiles.dmi' - button_icon_state = "solarflare" - cooldown_time = 0 SECONDS - spell_requirements = NONE - mana_cost = 10 - projectile_type = /obj/projectile/energy/photon - -/datum/action/cooldown/spell/basic_projectile/psionic_laser/cast(atom/cast_on) - var/mob/living/carbon/human/caster = owner - var/datum/psionic/psi_holder = caster.get_psionic() - if(!(psi_holder && (psi_holder.mana_level - mana_cost) >= 0)) - return FALSE - else - psi_holder.mana_level -= mana_cost - ..() - -// Создаёт ЕМП в месте удара руки -/datum/action/cooldown/spell/touch/psionic/psionic_emp - name = "Psionic EMP" - desc = "Try to cause a small local EMP." - button_icon = 'icons/obj/weapons/grenade.dmi' - button_icon_state = "emp" - cooldown_time = 15 SECONDS - mana_cost = 40 - stamina_cost = 40 - hand_path = /obj/item/melee/touch_attack/psionic_mending - draw_message = span_notice("You ready your hand to cause an EMP.") - drop_message = span_notice("You lower your hand.") - can_cast_on_self = TRUE - -/datum/action/cooldown/spell/touch/psionic/psionic_emp/cast_on_hand_hit(obj/item/melee/touch_attack/hand, atom/victim, mob/living/carbon/mendicant) - if(isatom(victim)) - empulse(victim, 1, cast_power/2) - drain_mana() - return TRUE - else - return FALSE - -// Даёт мутацию Shock Touch -/datum/action/cooldown/spell/psionic/psionic_electrocute - name = "Psionic Shock Touch" - desc = "Force yourself to recieve shock touch mutation." - cooldown_time = 60 SECONDS - mana_cost = 60 - stamina_cost = 60 - -/datum/action/cooldown/spell/psionic/psionic_electrocute/is_valid_target(atom/cast_on) - return !issynthetic(cast_on) - -/datum/action/cooldown/spell/psionic/psionic_electrocute/cast(mob/living/cast_on) - . = ..() - if(!ishuman(cast_on)) - return FALSE - var/mob/living/carbon/human/to_mutate = cast_on - if(!to_mutate.can_mutate()) - return FALSE - to_mutate.dna.add_mutation(/datum/mutation/shock, MUTATION_SOURCE_ACTIVATED) - drain_mana() - return TRUE diff --git a/tff_modular/modules/psionics/code/pointed/pointed.dm b/tff_modular/modules/psionics/code/pointed/pointed.dm index 94f074b9b86..5bdd981aa04 100644 --- a/tff_modular/modules/psionics/code/pointed/pointed.dm +++ b/tff_modular/modules/psionics/code/pointed/pointed.dm @@ -1 +1,229 @@ // Тут все заклинания, которые являются направленными, но не прожектайлами. + +// Станит на непродолжительный срок и заставляет выкинуть вещи из рук +/datum/action/cooldown/spell/pointed/psionic/spasm + name = "Psionic Spasm" + desc = "Force a target to drop the items in its hands. Note that this has a hefty power use and cooldown." + button_icon = 'tff_modular/modules/psionics/icons/actions.dmi' + button_icon_state = "spasm" + cooldown_time = 100 SECONDS + mana_cost = 20 + psionic_level = 2 + target_msg = "Your muscles spasm!" + active_msg = "You prepare to stun a target..." + +/datum/action/cooldown/spell/pointed/psionic/spasm/is_valid_target(atom/cast_on) + if(!ishuman(cast_on)) + return FALSE + + if(issynthetic(cast_on) && cast_power < 2) + to_chat(owner, span_notice("I dont know how to work with synths.")) + return FALSE + + return TRUE + +/datum/action/cooldown/spell/pointed/psionic/spasm/cast(mob/living/carbon/human/cast_on) + . = ..() + if(cast_on.can_block_magic(antimagic_flags)) + to_chat(cast_on, span_warning("Your body is assaulted with psionic energy!")) + else + to_chat(cast_on, span_warning(target_msg)) + log_combat(owner, cast_on, "psionically spasmed") + cast_on.Stun(1 SECONDS * cast_power) + drain_mana() + return TRUE + +// Мед сканер на расстоянии +/datum/action/cooldown/spell/pointed/psionic/skinsight + name = "Skinsight" + desc = "Try to read target's vital energy and determine their state." + button_icon = 'tff_modular/modules/psionics/icons/actions.dmi' + button_icon_state = "roentgen" + cooldown_time = 1 SECONDS + point_cost = 0 + mana_cost = 10 + target_msg = "You feel like someone is looking deep into you." + active_msg = "You prepare to scan a target..." + +/datum/action/cooldown/spell/pointed/psionic/skinsight/is_valid_target(atom/cast_on) + if(!ishuman(cast_on)) + return FALSE + + return TRUE + +/datum/action/cooldown/spell/pointed/psionic/skinsight/cast(mob/living/carbon/human/cast_on) + . = ..() + if(cast_on.can_block_magic(antimagic_flags)) + to_chat(cast_on, span_notice("Your body is being read by a psionic nearby.")) + else + to_chat(cast_on, span_warning(target_msg)) + if(cast_power >= 2) + healthscan(owner, cast_on, SCANNER_VERBOSE, TRUE, tochat = TRUE) + else + healthscan(owner, cast_on, SCANNER_VERBOSE, FALSE, tochat = TRUE) + drain_mana() + return TRUE + +/datum/action/cooldown/spell/pointed/psionic/jump + name = "Psionic Jump" + desc = "Teleport to a destination you click on." + button_icon_state = "warp_strike" + cooldown_time = 15 SECONDS + psionic_level = 2 + mana_cost = 20 + point_cost = 2 + +/datum/action/cooldown/spell/pointed/psionic/jump/cast(atom/cast_on) + . = ..() + if(!isclosedturf(cast_on) && !isobj(cast_on)) + if(isliving(cast_on)) + do_teleport(owner, cast_on.loc, effectin = /obj/effect/temp_visual/wizard, effectout = /obj/effect/temp_visual/wizard/out) + else if(isturf(cast_on)) + do_teleport(owner, cast_on.loc, effectin = /obj/effect/temp_visual/wizard, effectout = /obj/effect/temp_visual/wizard/out) + else + return FALSE + +/datum/action/cooldown/spell/pointed/psionic/emotional_suggestion + name = "Psionic Emotional Suggestion" + desc = "Allows you to psionically commune with the target using emotions." + cooldown_time = 2 SECONDS + mana_cost = 5 + point_cost = 0 + +/datum/action/cooldown/spell/pointed/psionic/emotional_suggestion/cast(atom/cast_on) + . = ..() + if(iscarbon(cast_on)) + emotional_suggestion(cast_on, owner) + else + return FALSE + +/datum/action/cooldown/spell/pointed/psionic/emotional_suggestion/proc/emotional_suggestion(atom/hit_atom, mob/living/user) + if(!isliving(hit_atom)) + return + + var/mob/living/carbon/target = hit_atom + if(target.stat == DEAD) + to_chat(user, span_warning("Not even a psion of your level can suggest to the dead.")) + return + + var/text = tgui_input_list(user, "Which emotion would you like to suggest?", "Emotional Suggestion", list("Calm", "Happiness", "Sadness", "Fear", "Anger", "Stress", "Confusion")) + if(!text) + return + + text = lowertext(text) + + if(target.stat == DEAD) + to_chat(user, span_warning("Not even a psion of your level can suggest to the dead.")) + return + + log_say("[key_name(user)] suggested an emotion to [key_name(target)]: [text]") + + to_chat(user, span_horizonblue("You psionically suggest an emotion to [target]: [text]")) + + var/mob/living/carbon/human/H = target + var/datum/psionic/target_sensitivity = H.get_psionic() + if(target_sensitivity) + to_chat(H, span_notice("[user] blinks, their eyes briefly developing an unnatural shine.")) + to_chat(H, span_notice("You sense [user]'s psyche link with your own, and an emotion of [text] washes through your mind.")) + else + to_chat(H, span_notice("An emotion from outside your consciousness slips into your mind: [text].")) + +/datum/action/cooldown/spell/pointed/psionic/rejuvenate + name = "Rejuvenate" + desc = "Restore a creature's blood and tried to and try to revive it." + button_icon_state = "mend_all" + cast_range = 3 + point_cost = 3 + mana_cost = 80 + psionic_level = 2 + +/datum/action/cooldown/spell/pointed/psionic/rejuvenate/is_valid_target(atom/cast_on) + . = ..() + if(!ismob(cast_on)) + return FALSE + if(!isdead(cast_on)) + return FALSE + var/mob/living/carbon/reviving = cast_on + if(HAS_TRAIT(reviving, TRAIT_DNR)) + owner.balloon_alert(owner, "no soul!") + return FALSE + if(!reviving.get_organ_slot(ORGAN_SLOT_BRAIN)) + owner.balloon_alert(owner, "no brain!") + return FALSE + +/datum/action/cooldown/spell/pointed/psionic/rejuvenate/cast(atom/cast_on) + . = ..() + if(isbasicmob(cast_on)) + var/mob/living/basic/basic_living = cast_on + for (var/i in 1 to 3) + if (!do_after(owner, 3 SECONDS, basic_living, timed_action_flags = IGNORE_INCAPACITATED | IGNORE_USER_LOC_CHANGE | IGNORE_TARGET_LOC_CHANGE)) + return FALSE + + basic_living.heal_overall_damage(30, 30) + + playsound(basic_living, 'sound/effects/singlebeat.ogg', vol = 50, vary = TRUE, ignore_walls = FALSE) + + var/original_transform = basic_living.transform + animate(basic_living, transform = basic_living.transform.Translate(0, 3), time = 0.2 SECONDS, easing = CUBIC_EASING | EASE_OUT, flags = ANIMATION_PARALLEL) + animate(transform = original_transform, time = 0.2 SECONDS, easing = CUBIC_EASING | EASE_IN, flags = ANIMATION_PARALLEL) + + basic_living.visible_message( + message = span_danger("\The [basic_living] shake[basic_living.p_their()] violently!"), + ignored_mobs = owner + ) + + if(!basic_living.revive()) + owner.balloon_alert(owner, "revival failed!") + return FALSE + + to_chat(owner, span_green("You successfully revive \the [owner]!")) + return TRUE + + else if(iscarbon(cast_on)) + var/mob/living/carbon/carbon_living = cast_on + for (var/i in 1 to 3) + if (!do_after(owner, 5 SECONDS, carbon_living, timed_action_flags = IGNORE_INCAPACITATED | IGNORE_USER_LOC_CHANGE | IGNORE_TARGET_LOC_CHANGE)) + return FALSE + + carbon_living.heal_overall_damage(30, 30) + playsound(carbon_living, 'sound/effects/singlebeat.ogg', vol = 50, vary = TRUE, ignore_walls = FALSE) + + var/original_transform = carbon_living.transform + animate(carbon_living, transform = carbon_living.transform.Translate(0, 3), time = 0.2 SECONDS, easing = CUBIC_EASING | EASE_OUT, flags = ANIMATION_PARALLEL) + animate(transform = original_transform, time = 0.2 SECONDS, easing = CUBIC_EASING | EASE_IN, flags = ANIMATION_PARALLEL) + + carbon_living.visible_message( + message = span_danger("\The [carbon_living] shake[carbon_living.p_their()] violently!"), + ignored_mobs = owner + ) + + carbon_living.cure_husk() + carbon_living.regenerate_organs(TRUE) + carbon_living.regenerate_limbs() + carbon_living.adjust_blood_volume(BLOOD_VOLUME_NORMAL, 0, BLOOD_VOLUME_NORMAL) + if(!carbon_living.revive()) + owner.balloon_alert(owner, "revival failed!") + return FALSE + + to_chat(owner, span_green("You successfully revive \the [owner]!")) + return TRUE + +/datum/action/cooldown/spell/pointed/psionic/stasis + name = "Stasis" + desc = "Condenses the Nlom field around one person at a time. This immobilises them and also applies stasis to them." + button_icon_state = "condensation" + point_cost = 1 + psionic_level = 2 + mana_cost = 30 + +/datum/action/cooldown/spell/pointed/psionic/stasis/is_valid_target(atom/cast_on) + . = ..() + if(!isliving(cast_on)) + return FALSE + +/datum/action/cooldown/spell/pointed/psionic/stasis/cast(atom/cast_on) + . = ..() + var/mob/living/freezing = cast_on + freezing.apply_status_effect(/datum/status_effect/freon/watcher) + var/datum/status_effect/freon/watcher/status_effect = freezing.has_status_effect(/datum/status_effect/freon/watcher) + status_effect.duration = 4 * cast_power diff --git a/tff_modular/modules/psionics/code/projectiles/projectile.dm b/tff_modular/modules/psionics/code/projectiles/projectile.dm index 9d2671ca151..384ceb87e65 100644 --- a/tff_modular/modules/psionics/code/projectiles/projectile.dm +++ b/tff_modular/modules/psionics/code/projectiles/projectile.dm @@ -10,17 +10,33 @@ deactive_msg = "You relax." projectile_type = /obj/projectile/magic/air_bullet projectile_amount = INFINITY - psionic_level = 2 + psionic_level = 1 point_cost = 2 + var/casting = FALSE /datum/action/cooldown/spell/pointed/projectile/psionic/air_bullet/before_cast(atom/cast_on) . = ..() - if(do_after(owner, 0.5 SECONDS, timed_action_flags = IGNORE_USER_LOC_CHANGE | IGNORE_SLOWDOWNS)) + if(!casting) + casting = TRUE + if(do_after(owner, 0.5 SECONDS, timed_action_flags = IGNORE_USER_LOC_CHANGE | IGNORE_SLOWDOWNS)) + return FALSE + psionic_datum.adjust_psi_energy(-10) + else return FALSE - psionic_datum.adjust_psi_energy(-10) + +/datum/action/cooldown/spell/pointed/projectile/psionic/air_bullet/cast(atom/cast_on) + . = ..() + casting = FALSE + +/datum/action/cooldown/spell/pointed/projectile/psionic/air_bullet/ready_projectile(obj/projectile/to_fire, atom/target, mob/user, iteration) + . = ..() + to_fire.damage = 10 * cast_power /obj/projectile/magic/air_bullet - damage = 20 + icon = 'tff_modular/modules/psionics/icons/projectiles.dmi' + icon_state = "air_bubble" + damage = 10 + damage_type = BRUTE // Стреляет снарядом вотчера, замораживая жертву. Требует почти максимально возможный запас маны /datum/action/cooldown/spell/pointed/projectile/psionic/freeze @@ -34,7 +50,7 @@ active_msg = "You prepare to fire ice shard..." deactive_msg = "You relax." projectile_type = /obj/projectile/temp/watcher/psionic_freeze - psionic_level = 2 + psionic_level = 1 point_cost = 1 category = "manipulation" diff --git a/tff_modular/modules/psionics/code/psi_shop/datum.dm b/tff_modular/modules/psionics/code/psi_shop/datum.dm index ac57d10bb8c..499c59a5454 100644 --- a/tff_modular/modules/psionics/code/psi_shop/datum.dm +++ b/tff_modular/modules/psionics/code/psi_shop/datum.dm @@ -10,7 +10,7 @@ psi_owner = psionic_mob possible_spells = get_possible_spells() -/proc/get_possible_spells() +/datum/psionic_shop/proc/get_possible_spells() var/static/list/spell_options if(!spell_options) spell_options = subtypesof(/datum/action/cooldown/spell) @@ -19,6 +19,8 @@ spell_options -= spell if(spell.locked) spell_options -= spell + if(spell.psionic_level > psi_datum.psionic_level) + spell_options -= spell return spell_options diff --git a/tff_modular/modules/psionics/code/psychokinesis.dm b/tff_modular/modules/psionics/code/psychokinesis.dm deleted file mode 100644 index 22577b9a29a..00000000000 --- a/tff_modular/modules/psionics/code/psychokinesis.dm +++ /dev/null @@ -1,211 +0,0 @@ -/// Школа психокинетики -/// Имеет 6 спеллов. -/// Psi lighter - создаёт миниатюрный огонёк на кончиках пальцев. Работает как зажигалка. -/// Psi blade - создаёт в руке пси-клинок. Урон увеличивается в зависимости от уровня. -/// Psi tool - создаёт в руке универсальный инструмент. -/// Tinker - чинит integrity чего бы то ни было. -/// Psyforce - даёт "клешни жизни" для вскрытия дверей -/// Telekinesis - даёт мутацию телекинеза. - -// Спавнит зажигалку в руке. Очень полезно -/datum/action/cooldown/spell/conjure_item/psionic/psilighter - name = "Psi lighter" - desc = "Concentrates psionic energy to create a small flame in your hand." - button_icon = 'icons/obj/cigarettes.dmi' - button_icon_state = "match_lit" - cooldown_time = 1.5 SECONDS - item_type = /obj/item/psionic_fire - mana_cost = 5 - stamina_cost = 0 - -// Спавнит омни инструмент в руке псионика. Аналог абдукторского -/datum/action/cooldown/spell/conjure_item/psionic/psitool - name = "Psi tool" - desc = "Concentrates psionic energy to create a universal tool." - button_icon = 'icons/obj/antags/abductor.dmi' - button_icon_state = "omnitool" - cooldown_time = 1.5 SECONDS - item_type = /obj/item/psionic_omnitool - mana_cost = 30 - stamina_cost = 0 - -/datum/action/cooldown/spell/conjure_item/psionic/psiblade/make_item(atom/caster) - var/obj/item/made_item = new item_type(caster.loc, cast_power) - LAZYADD(item_refs, WEAKREF(made_item)) - var/mob/living/carbon/human/caster_pawn = owner - caster_pawn.emote_snap() - return made_item - -// Аналог клешней жизни -/datum/action/cooldown/spell/touch/psionic/psionic_force - name = "Psionic Force" - desc = "Concentrates psionic energy to force a door open." - button_icon = 'icons/mob/actions/actions_spells.dmi' - button_icon_state = "knock" - cooldown_time = 3 SECONDS - mana_cost = 50 - stamina_cost = 50 - hand_path = /obj/item/melee/touch_attack/psionic_mending - draw_message = span_notice("You ready your hand to force a door open.") - drop_message = span_notice("You lower your hand.") - can_cast_on_self = FALSE - -/datum/action/cooldown/spell/touch/psionic/psionic_force/is_valid_target(atom/cast_on) - return istype(cast_on, /obj/machinery/door/airlock) - -/datum/action/cooldown/spell/touch/psionic/psionic_force/cast_on_hand_hit(obj/item/melee/touch_attack/hand, atom/victim, mob/living/carbon/mendicant) - if(isatom(victim)) - if(istype(victim, /obj/machinery/door/airlock)) - var/obj/machinery/door/airlock/door_to_force = victim - owner.visible_message(span_warning("[owner] targets their hands at [victim], like they are some kind of jedi."), - span_notice("You psionically grab [victim], trying to force it open.")) - if(do_after(mendicant, 5 SECONDS, victim, IGNORE_SLOWDOWNS, TRUE)) - force_door_open(door_to_force, mendicant) - drain_mana() - return TRUE - else - return FALSE - else - return FALSE - -/datum/action/cooldown/spell/touch/psionic/psionic_force/proc/force_door_open(obj/machinery/door/airlock/door_to_force, mob/living/carbon/user) - if(door_to_force.seal) - to_chat(user, span_warning("Remove the seal first!")) - return - if(door_to_force.locked) - to_chat(user, span_warning("The airlock's bolts prevent it from being forced!")) - return - if(door_to_force.welded) - to_chat(user, span_warning("It's welded, it won't budge!")) - return - if(door_to_force.hasPower()) - if(!door_to_force.density) - return - if(!door_to_force.prying_so_hard) - playsound(src, 'sound/machines/airlock/airlock_alien_prying.ogg', 100, TRUE) - door_to_force.prying_so_hard = TRUE - door_to_force.open(BYPASS_DOOR_CHECKS) - door_to_force.take_damage(25, BRUTE, 0, 0) - if(door_to_force.density && !door_to_force.open(BYPASS_DOOR_CHECKS)) - to_chat(user, span_warning("Despite your attempts, [src] refuses to open.")) - door_to_force.prying_so_hard = FALSE - return - -// Даёт мутацию телекинеза -/datum/action/cooldown/spell/psionic/psionic_telekinesis - name = "Telekinesis" - desc = "Force yourself to recieve telekinesis mutation." - cooldown_time = 60 SECONDS - mana_cost = 80 - stamina_cost = 80 - -/datum/action/cooldown/spell/psionic/psionic_telekinesis/is_valid_target(atom/cast_on) - return !issynthetic(cast_on) - -/datum/action/cooldown/spell/psionic/psionic_telekinesis/cast(mob/living/cast_on) - . = ..() - if(!ishuman(cast_on)) - return FALSE - var/mob/living/carbon/human/to_mutate = cast_on - if(!to_mutate.can_mutate()) - return FALSE - to_mutate.dna.add_mutation(/datum/mutation/telekinesis, MUTATION_SOURCE_ACTIVATED) - drain_mana() - -// Восстанавливает Integrity атома. Позволяет чинить многие нечинимые иными способами вещи -/datum/action/cooldown/spell/touch/psionic/psionic_tinker - name = "Psionic Tinker" - desc = "Restore somethings condition to its normal state." - button_icon = 'icons/obj/tools.dmi' - button_icon_state = "wrench" - cooldown_time = 3 SECONDS - mana_cost = 40 - stamina_cost = 50 - hand_path = /obj/item/melee/touch_attack/psionic_mending - draw_message = span_notice("You ready your hand to tinker.") - drop_message = span_notice("You lower your hand.") - can_cast_on_self = FALSE - -/datum/action/cooldown/spell/touch/psionic/psionic_tinker/is_valid_target(atom/cast_on) - return cast_on.uses_integrity - -/datum/action/cooldown/spell/touch/psionic/psionic_tinker/cast_on_hand_hit(obj/item/melee/touch_attack/hand, atom/victim, mob/living/carbon/mendicant) - if(isatom(victim)) - var/atom/to_fix = victim - if((to_fix.get_integrity() >= to_fix.max_integrity) || !to_fix.uses_integrity) - return FALSE - owner.visible_message(span_warning("[owner] presses his hands onto [victim]."), - span_notice("You grab [victim], trying to repair it.")) - if(do_after(mendicant, 6 SECONDS, victim, IGNORE_SLOWDOWNS, TRUE)) - to_fix.update_integrity(clamp(to_fix.get_integrity()+(50*cast_power), 1, to_fix.max_integrity)) - drain_mana() - return TRUE - else - return FALSE - -// Копирка с абдукторского -/obj/item/psionic_omnitool - name = "psionic omnitool" - desc = "Space Swiss Army Knife, able to shapeshift itself to fulfill psionics needs." - icon = 'icons/obj/antags/abductor.dmi' - lefthand_file = 'icons/mob/inhands/antag/abductor_lefthand.dmi' - righthand_file = 'icons/mob/inhands/antag/abductor_righthand.dmi' - icon_state = "omnitool" - inhand_icon_state = "silencer" - toolspeed = 1 - tool_behaviour = TOOL_SCREWDRIVER - color = COLOR_BRIGHT_BLUE - usesound = 'sound/items/pshoom/pshoom.ogg' - var/list/tool_list = list() - item_flags = DROPDEL | ABSTRACT | HAND_ITEM - -/obj/item/psionic_omnitool/Initialize(mapload) - . = ..() - tool_list = list( - "Crowbar" = image(icon = 'icons/obj/tools.dmi', icon_state = "crowbar"), - "Multitool" = image(icon = 'icons/obj/devices/tool.dmi', icon_state = "multitool"), - "Screwdriver" = image(icon = 'icons/obj/tools.dmi', icon_state = "screwdriver_brass"), - "Wirecutters" = image(icon = 'icons/obj/tools.dmi', icon_state = "cutters_map"), - "Wrench" = image(icon = 'icons/obj/tools.dmi', icon_state = "wrench"), - ) - ADD_TRAIT(src, TRAIT_EXAMINE_SKIP, INNATE_TRAIT) - -/obj/item/psionic_omnitool/get_all_tool_behaviours() - return list( - TOOL_CROWBAR, - TOOL_MULTITOOL, - TOOL_SCREWDRIVER, - TOOL_WIRECUTTER, - TOOL_WRENCH, - ) - -/obj/item/psionic_omnitool/examine() - . = ..() - . += " The mode is: [tool_behaviour]" - -/obj/item/psionic_omnitool/attack_self(mob/user) - if(!user) - return - - var/tool_result = show_radial_menu(user, src, tool_list, custom_check = CALLBACK(src, PROC_REF(check_menu), user), require_near = TRUE, tooltips = TRUE) - if(!check_menu(user)) - return - switch(tool_result) - if("Crowbar") - tool_behaviour = TOOL_CROWBAR - if("Multitool") - tool_behaviour = TOOL_MULTITOOL - if("Screwdriver") - tool_behaviour = TOOL_SCREWDRIVER - if("Wirecutters") - tool_behaviour = TOOL_WIRECUTTER - if("Wrench") - tool_behaviour = TOOL_WRENCH - -/obj/item/psionic_omnitool/proc/check_menu(mob/user) - if(!istype(user)) - return FALSE - if(user.incapacitated || !user.Adjacent(src)) - return FALSE - return TRUE - diff --git a/tff_modular/modules/psionics/code/redaction.dm b/tff_modular/modules/psionics/code/redaction.dm deleted file mode 100644 index f071655f89a..00000000000 --- a/tff_modular/modules/psionics/code/redaction.dm +++ /dev/null @@ -1,359 +0,0 @@ -#define HALFWAYCRITDEATH ((HEALTH_THRESHOLD_CRIT + HEALTH_THRESHOLD_DEAD) * 0.5) - -/// Школа лечения -/// Имеет 5 спеллов в данный момент -/// Roentgen - обычный мед скан, работающий на дистанции -/// Меnding - лечит кровь, открытые раны и окси урон. Также удаляет импланты/ксеноморфов из тела при определённых условиях. -/// Ethanol Synthesis - если интент на харма, то "превращает" упитанность в алкоголь на дистанции. Любой другой - наоборот. -/// Cleansing - лечит токс урон -/// Revive - пытается оживить труп - -// Мед сканер на расстоянии -/datum/action/cooldown/spell/pointed/psionic/psionic_roentgen - name = "Roentgen" - desc = "Try to read target's vital energy and determine their state." - button_icon = 'tff_modular/modules/psionics/icons/actions.dmi' - button_icon_state = "roentgen" - - cooldown_time = 1 SECONDS - - mana_cost = 10 - target_msg = "You feel like someone is looking deep into you." - - active_msg = "You prepare to scan a target..." - -/datum/action/cooldown/spell/pointed/psionic/psionic_roentgen/is_valid_target(atom/cast_on) - if(!ishuman(cast_on)) - return FALSE - - return TRUE - -/datum/action/cooldown/spell/pointed/psionic/psionic_roentgen/cast(mob/living/carbon/human/cast_on) - . = ..() - if(cast_on.can_block_magic(antimagic_flags)) - to_chat(cast_on, span_notice("Your body is being read by a psionic nearby.")) - else - to_chat(cast_on, span_warning(target_msg)) - if(cast_power > 2) - healthscan(owner, cast_on, SCANNER_VERBOSE, TRUE, tochat = TRUE) - else - healthscan(owner, cast_on, SCANNER_VERBOSE, FALSE, tochat = TRUE) - drain_mana() - return TRUE - -/obj/item/melee/touch_attack/psionic_mending - name = "psionic sparks" - desc = "Concentrated psionic energy in a hand." - icon = 'icons/obj/weapons/hand.dmi' - icon_state = "greyscale" - color = COLOR_VERY_PALE_LIME_GREEN - inhand_icon_state = "greyscale" - light_range = 2 - light_power = 1 - light_color = LIGHT_COLOR_LIGHT_CYAN - light_on = TRUE - -// Восстанавливает кровь, окси урон, открытые травмы. Не лечит другие типы урона. Если вторичка - психокинетика, то вынимает импланты. -// Если уровень Эпсилон - удаляет лярвы ксеноморфов. -/datum/action/cooldown/spell/touch/psionic/psionic_mending - name = "Psionic Mending" - desc = "You can try to restore patients bloodloss, bones, open wounds and partially oxygen level in blood. Does not heal brute, burn, \ - and toxic damage. With Psychokinesis as secondary school also can remove small implants. At Epsilon level can remove xenomorph larvae." - button_icon = 'tff_modular/modules/psionics/icons/actions.dmi' - button_icon_state = "mending_touch" - cooldown_time = 3 SECONDS - mana_cost = 25 - stamina_cost = 25 - target_msg = "You body numbs a little." - hand_path = /obj/item/melee/touch_attack/psionic_mending - draw_message = span_notice("You ready your hand to mend a patient.") - drop_message = span_notice("You lower your hand.") - can_cast_on_self = TRUE - -/datum/action/cooldown/spell/touch/psionic/psionic_mending/cast_on_hand_hit(obj/item/melee/touch_attack/hand, atom/victim, mob/living/carbon/mendicant) - if(ishuman(victim)) - var/mob/living/carbon/human/human_victim = victim - if(issynthetic(human_victim) && cast_power < 2) - to_chat(owner, span_notice("I dont know how to work with synths.")) - return FALSE - if(human_victim.can_block_magic(antimagic_flags)) - to_chat(human_victim, span_notice("Psionic nearby tries to mend you.")) - else - to_chat(human_victim, span_warning(target_msg)) - if(!do_after(mendicant, 5 SECONDS, human_victim, IGNORE_SLOWDOWNS, TRUE)) - accident_harm(human_victim) - else - try_heal_all(human_victim) - drain_mana() - return TRUE - else - return FALSE - -/datum/action/cooldown/spell/touch/psionic/psionic_mending/proc/accident_harm(mob/living/carbon/human/patient) - patient.take_bodypart_damage(5, wound_bonus = 100) - -/datum/action/cooldown/spell/touch/psionic/psionic_mending/proc/try_heal_all(mob/living/carbon/human/patient) - if(patient.blood_volume < BLOOD_VOLUME_NORMAL) - patient.blood_volume += ((BLOOD_VOLUME_NORMAL - patient.blood_volume) / 5) * cast_power // Эффективнее когда крови мало - - if(patient.all_wounds) - var/datum/wound/wound2fix = patient.all_wounds[1] - wound2fix.remove_wound() - playsound(patient, 'sound/effects/wounds/crack2.ogg', 40, TRUE) - - if(patient.get_oxy_loss() >= OXYLOSS_PASSOUT_THRESHOLD-10) - patient.adjust_oxy_loss(-cast_power*5, forced = TRUE) - - if(patient.implants && cast_power >= 2) // Невольно удаляет импланты, если есть - var/obj/item/implant/imp_2_del = pick(patient.implants) - var/atom/drop_loc = imp_2_del.drop_location() - imp_2_del.removed(patient) - if(drop_loc) - imp_2_del.forceMove(drop_loc) - patient.visible_message( - span_warning("[patient]s skin rips open, [imp_2_del] flies out of it and then the wound suddenly heals."), - span_danger("You feel implant inside you starts to move and rips itself out! The resulting wound quickly closes itself though."), - ) - - if(patient.get_organ_slot("parasite_egg") && cast_power >= 2) // Удаляем ксеноморфов - var/obj/item/organ/body_egg/parasite = patient.get_organ_slot("parasite_egg") - parasite.owner.vomit(VOMIT_CATEGORY_BLOOD | MOB_VOMIT_KNOCKDOWN | MOB_VOMIT_HARM) - parasite.owner.visible_message( - span_warning("[patient] twitches, gags and vomits a living creqture with blood! Gross!"), - span_bolddanger("Suddenly you feel sharp pain in your chest, then something starts moving up your throat. \ - Before you can react somethign slips past your lips with a mix of vomit and blood!"), - ) - var/atom/drop_loc = parasite.drop_location() - parasite.Remove(parasite.owner) - if(drop_loc) - parasite.forceMove(drop_loc) - -/datum/action/cooldown/spell/pointed/psionic/psionic_drunkness - name = "Ethanol Body Synthesis" - desc = "Convert fat masses to ethanol in combat mode, vice versa otherwise. Works with time on distance, but not on synthetics." - button_icon = 'icons/obj/drinks/bottles.dmi' - button_icon_state = "beer" - cooldown_time = 1 SECONDS - mana_cost = 30 - stamina_cost = 30 - active_msg = "You prepare to convert fat tissues..." - -/datum/action/cooldown/spell/pointed/psionic/psionic_drunkness/is_valid_target(atom/cast_on) - if(!ishuman(cast_on)) - return FALSE - if(issynthetic(cast_on) ) - to_chat(owner, span_notice("It's a synth. What am I supposed to convert? Oil?")) - return FALSE - return TRUE - -/datum/action/cooldown/spell/pointed/psionic/psionic_drunkness/cast(mob/living/carbon/human/cast_on) - . = ..() - cast_on.apply_status_effect(/datum/status_effect/psionic_fat_conversion, 5 * cast_power SECONDS, !cast_on.combat_mode) - drain_mana() - return TRUE - -/// С каждым тиком конвертируем или жир в алкоголь, или алкоголь в жир -/datum/status_effect/psionic_fat_conversion - id = "psionic_fat_conversion" - alert_type = null - remove_on_fullheal = TRUE - var/eth2fat = TRUE - -/datum/status_effect/psionic_fat_conversion/on_creation(mob/living/new_owner, duration = 10 SECONDS, eth2fat = TRUE) - src.duration = duration - src.eth2fat = eth2fat - return ..() - -/datum/status_effect/psionic_fat_conversion/tick(seconds_between_ticks) - var/mob/living/carbon/human/human_owner = owner - var/fat = human_owner.nutrition - var/drunk = human_owner.get_drunk_amount() - if(eth2fat && !drunk) // если нет алкашки, то и конвертировать нечего - return - if(eth2fat) // алкашку в жир - human_owner.adjust_drunk_effect(-(drunk/6)) - human_owner.adjust_nutrition(drunk) - if(!eth2fat && fat) // жир в алкашку. За 25 тиков полностью обезжирим человека! - human_owner.adjust_drunk_effect(fat/125) - human_owner.adjust_nutrition(-(fat/25)) - -// Лечит токс урон. -/datum/action/cooldown/spell/touch/psionic/psionic_cleansing - name = "Psionic Cleansing" - desc = "Filters patient blood out of toxins and removes accumulated radiation." - button_icon = 'tff_modular/modules/psionics/icons/actions.dmi' - button_icon_state = "cleansing" - cooldown_time = 3 SECONDS - mana_cost = 35 - stamina_cost = 40 - target_msg = "Your insides itch." - - hand_path = /obj/item/melee/touch_attack/psionic_mending - draw_message = span_notice("You ready your hand to cleanse a patient.") - drop_message = span_notice("You lower your hand.") - can_cast_on_self = TRUE - -/datum/action/cooldown/spell/touch/psionic/psionic_cleansing/cast_on_hand_hit(obj/item/melee/touch_attack/hand, atom/victim, mob/living/carbon/mendicant) - if(ishuman(victim)) - var/mob/living/carbon/human/human_victim = victim - if(issynthetic(human_victim) && cast_power < 2) - to_chat(owner, span_notice("I dont know how to work with synths. Why would I even try to? They dont have toxins.")) - return FALSE - if(human_victim.can_block_magic(antimagic_flags)) - to_chat(human_victim, span_notice("Psionic nearby tries to cleanse you.")) - else - to_chat(human_victim, span_warning(target_msg)) - if(!do_after(mendicant, 5 SECONDS, human_victim, IGNORE_SLOWDOWNS, TRUE)) - accident_harm(human_victim) - else - try_heal_all(human_victim) - drain_mana() - return TRUE - else - return FALSE - -/datum/action/cooldown/spell/touch/psionic/psionic_cleansing/proc/accident_harm(mob/living/carbon/human/patient) - patient.apply_damage(25, TOX, BODY_ZONE_CHEST) - -/datum/action/cooldown/spell/touch/psionic/psionic_cleansing/proc/try_heal_all(mob/living/carbon/human/patient) - if(patient.get_tox_loss() > 0) - patient.adjust_tox_loss(clamp(-(patient.get_tox_loss()/3)*cast_power, -35, 0), forced = TRUE) - -/** - * Пытается оживить труп - * - * Логика прока: - * 1. Смотрит есть ли причина по которой нельзя дефибнуть, пытается её устранить - * 2. Если не удалось устранить - не оживляет - * 3. Если удалось устранить причину - проверяет можно ли дефибнуть снова. Если появилась другая - не оживляет. Всё ок - оживляет. - */ -/datum/action/cooldown/spell/touch/psionic/psionic_revival - name = "Psionic Revival" - desc = "Ability to trick death itself. Call for the bodys soul in the other realm in attempt to restore its vessel condition to an... acceptable levels." - button_icon = 'tff_modular/modules/psionics/icons/actions.dmi' - button_icon_state = "revive" - cooldown_time = 3 SECONDS - mana_cost = 80 - stamina_cost = 160 - - hand_path = /obj/item/melee/touch_attack/psionic_mending - draw_message = span_notice("You ready your hand to revive a patient.") - drop_message = span_notice("You lower your hand.") - can_cast_on_self = FALSE - -/datum/action/cooldown/spell/touch/psionic/psionic_revival/cast_on_hand_hit(obj/item/melee/touch_attack/hand, atom/victim, mob/living/carbon/mendicant) - if(ishuman(victim)) - var/mob/living/carbon/human/human_victim = victim - var/synth_check = (cast_power < 2 || !issynthetic(human_victim)) - if(human_victim.stat == DEAD && synth_check) - owner.visible_message(span_notice("[owner] kneels before the body of [victim], lowers their hands onto cadavers chest and begins meditating."), - span_notice("You kneel before the cadaver, lower your hands onto their chest and start to concentrate energy. You better not \ - get disturbed, or else..."), - blind_message = span_hear("You hear a low hum.")) - var/obj/effect/abstract/particle_holder/particle_effect = new(human_victim, /particles/droplets/psionic) - if(!do_after(mendicant, 25 SECONDS, human_victim, IGNORE_SLOWDOWNS, TRUE)) - accident_harm(owner) // Ауч. Больно бьёт по псионику - else - try_heal_all(human_victim) - if(particle_effect) - QDEL_NULL(particle_effect) - drain_mana() - return TRUE - else if(issynthetic(human_victim) && human_victim.stat == DEAD) - to_chat(owner, span_warning("Your psionic energy does not work very well with synths.")) - return FALSE - else - return FALSE - else - return FALSE - -// 25 токса + 50 брута + 1 травма + позор роду псионическому -/datum/action/cooldown/spell/touch/psionic/psionic_revival/proc/accident_harm(mob/living/carbon/human/unlucky_guy) - unlucky_guy.apply_damage(25, TOX, BODY_ZONE_CHEST) - unlucky_guy.take_bodypart_damage(25, wound_bonus = 100) - unlucky_guy.take_bodypart_damage(25, wound_bonus = 100, sharpness = SHARP_EDGED) - unlucky_guy.visible_message(span_warning("Something inside of [unlucky_guy]s body cracks!"), - span_bolddanger("Your revival energy backfired at you, causing severe injuries!"), - blind_message = span_hear("You hear bones breaking.")) - -/datum/action/cooldown/spell/touch/psionic/psionic_revival/proc/can_defib_human(mob/living/carbon/human/patient) - var/defib_result = patient.can_defib() - var/fail_reason - var/synth_check = (cast_power < 2) - switch (defib_result) - if (DEFIB_FAIL_SUICIDE) - fail_reason = "Patient has left this world on his terms. You can not restore him." - if (DEFIB_FAIL_NO_HEART) - fail_reason = "Patients heart is missing and you are not Alpha tier to create it out of air." - if (DEFIB_FAIL_FAILING_HEART) - var/obj/item/organ/heart/target_heart = patient.get_organ_slot(ORGAN_SLOT_HEART) - if(target_heart) - target_heart.operated = TRUE - if((target_heart.organ_flags & ORGAN_ORGANIC) || synth_check) // Only fix organic heart - patient.set_organ_loss(ORGAN_SLOT_HEART, 60) - else - fail_reason = "Patients heart is made out of metals and plastics. You can not work with that." - if (DEFIB_FAIL_TISSUE_DAMAGE) - patient.adjust_brute_loss(-patient.get_brute_loss()/2) - patient.adjust_fire_loss(-patient.get_fire_loss()/2) - if ((patient.get_brute_loss() >= MAX_REVIVE_BRUTE_DAMAGE) || (patient.get_fire_loss() >= MAX_REVIVE_FIRE_DAMAGE)) - fail_reason = "Patients body is too flimsy to support life, but your energy partially healed that. Maybe try again?" - if (DEFIB_FAIL_HUSK) - patient.cure_husk() - if(HAS_TRAIT(patient, TRAIT_HUSK)) - fail_reason = "Patients body is a mere husk, and you can not cure them." - if (DEFIB_FAIL_FAILING_BRAIN) - var/obj/item/organ/brain/target_brain = patient.get_organ_slot(ORGAN_SLOT_BRAIN) - if(target_brain) - if((target_brain.organ_flags & ORGAN_ORGANIC) || synth_check) // Only fix organic heart - patient.set_organ_loss(ORGAN_SLOT_BRAIN, 60) - else - fail_reason = "Patient's brain is made out of metals and plastics. You can not work with that." - if (DEFIB_FAIL_NO_INTELLIGENCE) - fail_reason = "Patient is braindead. Your energy doesnt course through such body." - if (DEFIB_FAIL_NO_BRAIN) - fail_reason = "Patient's brain is missing and even if you were Alpha tier, you could not restore him.." - if (DEFIB_FAIL_BLACKLISTED) - fail_reason = "Patient soul is linked to the dead realm with death grip. You can not restore him." - if (DEFIB_FAIL_DNR) - fail_reason = "Patient cannot be restored due to star misalignment." - return fail_reason - -/datum/action/cooldown/spell/touch/psionic/psionic_revival/proc/try_heal_all(mob/living/carbon/human/patient) - var/fail_reason = can_defib_human(patient) // first to possibly cure something - fail_reason = can_defib_human(patient) // second to actually try revival - if(fail_reason) - owner.visible_message(span_warning(fail_reason)) - else - var/defib_result = patient.can_defib() - if (defib_result == DEFIB_POSSIBLE) - var/total_brute = patient.get_brute_loss() - var/total_burn = patient.get_fire_loss() - - var/need_mob_update = FALSE - if (patient.health > HALFWAYCRITDEATH) - need_mob_update += patient.adjust_oxy_loss(patient.health - HALFWAYCRITDEATH, updating_health = FALSE) - else - var/overall_damage = total_brute + total_burn + patient.get_tox_loss() + patient.get_oxy_loss() - var/mobhealth = patient.health - need_mob_update += patient.adjust_oxy_loss((mobhealth - HALFWAYCRITDEATH) * (patient.get_oxy_loss() / overall_damage), updating_health = FALSE) - need_mob_update += patient.adjust_tox_loss((mobhealth - HALFWAYCRITDEATH) * (patient.get_tox_loss() / overall_damage), updating_health = FALSE, forced = TRUE) // force tox heal for toxin lovers too - need_mob_update += patient.adjust_fire_loss((mobhealth - HALFWAYCRITDEATH) * (total_burn / overall_damage), updating_health = FALSE) - need_mob_update += patient.adjust_brute_loss((mobhealth - HALFWAYCRITDEATH) * (total_brute / overall_damage), updating_health = FALSE) - if(need_mob_update) - patient.updatehealth() - owner.visible_message(span_warning("[owner] suddenly hits [patient]s chest!"), - span_green("Another saved life on your count."), - span_hear("You hear a slap.")) - playsound(src, 'sound/effects/ghost.ogg', 40, FALSE) - patient.set_heartattack(FALSE) - if(defib_result == DEFIB_POSSIBLE) - patient.grab_ghost() - patient.revive() - patient.emote("gasp") - patient.set_jitter_if_lower(200 SECONDS) - to_chat(patient, "[CONFIG_GET(string/blackoutpolicy)]") - SEND_SIGNAL(patient, COMSIG_LIVING_MINOR_SHOCK) - log_combat(owner, patient, "psionically revived") - -#undef HALFWAYCRITDEATH diff --git a/tff_modular/modules/psionics/code/spell/spell.dm b/tff_modular/modules/psionics/code/spell/spell.dm new file mode 100644 index 00000000000..876dfa3f94c --- /dev/null +++ b/tff_modular/modules/psionics/code/spell/spell.dm @@ -0,0 +1,144 @@ +// Даёт мутацию телекинеза +/datum/action/cooldown/spell/psionic/psionic_telekinesis + name = "Telekinesis" + desc = "Force yourself to recieve telekinesis mutation." + cooldown_time = 20 SECONDS + mana_cost = 80 + +/datum/action/cooldown/spell/psionic/psionic_telekinesis/is_valid_target(atom/cast_on) + return !issynthetic(cast_on) + +/datum/action/cooldown/spell/psionic/psionic_telekinesis/cast(mob/living/cast_on) + . = ..() + if(!ishuman(cast_on)) + return FALSE + var/mob/living/carbon/human/to_mutate = cast_on + if(!to_mutate.can_mutate()) + return FALSE + to_mutate.dna.add_mutation(/datum/mutation/telekinesis/psionic, MUTATION_SOURCE_ACTIVATED) + drain_mana() + +/datum/mutation/telekinesis/psionic + no_effect = TRUE + +// Создаёт ЕМП в месте удара руки +/datum/action/cooldown/spell/psionic/emp + name = "Psionic EMP" + desc = "Cause a small, but powerful EMP." + button_icon_state = "overload" + cooldown_time = 15 SECONDS + mana_cost = 30 + psionic_level = 2 + +/datum/action/cooldown/spell/psionic/emp/cast(atom/cast_on) + . = ..() + empulse(cast_on.loc, 3, 3) + drain_mana() + +/datum/action/cooldown/spell/psionic/focus + name = "Psionic Focus" + desc = "Creates a useful reagents inside of you, removing stun." + button_icon_state = "blink" + cooldown_time = 50 SECONDS + mana_cost = 20 + point_cost = 1 + psionic_level = 2 + +/datum/action/cooldown/spell/psionic/focus/cast(atom/cast_on) + . = ..() + var/mob/living/carbon/human/human_living = cast_on + if(do_after(human_living, 1 SECONDS)) + to_chat(human_living, span_warning("A calm rush envelops your mind..")) + human_living.reagents.add_reagent_list(list(/datum/reagent/medicine/ephedrine = 5, /datum/reagent/medicine/synaptizine = 5, /datum/reagent/medicine/epinephrine = 5)) + human_living.SetStun(0) + human_living.SetParalyzed(0) + human_living.SetSleeping(0) + human_living.SetAllImmobility(0) + drain_mana() + +/datum/action/cooldown/spell/psionic/charge + name = "Psionic Charge" + button_icon_state = "audible_deception" + cooldown_time = 60 SECONDS + mana_cost = 10 + point_cost = 1 + psionic_level = 1 + +/datum/action/cooldown/spell/psionic/charge/is_valid_target(atom/cast_on) + return isliving(cast_on) + +/datum/action/cooldown/spell/psionic/charge/cast(mob/living/cast_on) + . = ..() + + // Charge people we're pulling first and foremost + if(isliving(cast_on.pulling) && cast_power >= 2) + var/mob/living/pulled_living = cast_on.pulling + var/pulled_has_spells = FALSE + + for(var/datum/action/cooldown/spell/spell in pulled_living.actions) + spell.reset_spell_cooldown() + pulled_has_spells = TRUE + + if(pulled_has_spells) + to_chat(pulled_living, span_notice("You feel psi flowing through you. It feels good!")) + to_chat(cast_on, span_notice("[pulled_living] suddenly feels very warm!")) + return + + to_chat(pulled_living, span_notice("You feel very strange for a moment, but then it passes.")) + + // Then charge their main hand item, then charge their offhand item + var/obj/item/to_charge = cast_on.get_active_held_item() || cast_on.get_inactive_held_item() + if(!to_charge) + to_chat(cast_on, span_notice("You feel magical power surging through your hands, but the feeling rapidly fades.")) + return + + var/charge_return = SEND_SIGNAL(to_charge, COMSIG_ITEM_MAGICALLY_CHARGED, src, cast_on) + + if(QDELETED(to_charge)) + to_chat(cast_on, span_warning("[src] seems to react adversely with [to_charge]!")) + return + + if(charge_return & COMPONENT_ITEM_BURNT_OUT) + to_chat(cast_on, span_warning("[to_charge] seems to react negatively to [src], becoming uncomfortably warm!")) + + else if(charge_return & COMPONENT_ITEM_CHARGED) + to_chat(cast_on, span_notice("[to_charge] suddenly feels very warm!")) + + else + to_chat(cast_on, span_notice("[to_charge] doesn't seem to be react to [src].")) + + drain_mana() + +/datum/action/cooldown/spell/pointed/psionic/bubble + name = "Psionic Bubble" + desc = "Create a protective bubble around you or target that removes your need to breathe or wear space protection!" + button_icon_state = "shield" + point_cost = 1 + cooldown_time = 30 SECONDS + mana_cost = 10 + +/datum/action/cooldown/spell/pointed/psionic/bubble/cast(atom/cast_on) + . = ..() + var/mob/living/living_living = cast_on + living_living.set_timed_status_effect(15 SECONDS * cast_power, /datum/status_effect/psi_bubble) + +/datum/status_effect/psi_bubble + id = "psi_bubble" + alert_type = /atom/movable/screen/alert/psi_bubble + var/icon/bubbleicon + +/datum/status_effect/psi_bubble/on_apply() + . = ..() + bubbleicon = icon(icon = 'icons/effects/effects.dmi', icon_state = "shield2") + owner.add_overlay(bubbleicon) + owner.add_traits(list(TRAIT_OXYIMMUNE, TRAIT_RESISTLOWPRESSURE, TRAIT_RESISTCOLD)) + +/datum/status_effect/psi_bubble/on_remove() + . = ..() + owner.cut_overlay(bubbleicon) + +/atom/movable/screen/alert/psi_bubble + icon = 'icons/effects/effects.dmi' + icon_state = "shield2" + name = "Air Bubble" + desc = "There is a protective bubble around you that removes your need to breathe or wear space protection!" diff --git a/tff_modular/modules/psionics/code/subsystem.dm b/tff_modular/modules/psionics/code/subsystem.dm deleted file mode 100644 index f576297add2..00000000000 --- a/tff_modular/modules/psionics/code/subsystem.dm +++ /dev/null @@ -1,5 +0,0 @@ -PROCESSING_SUBSYSTEM_DEF(psionic) - name = "Psionic" - priority = FIRE_PRIORITY_DEFAULT - ss_flags = SS_NO_INIT - wait = 2 SECONDS diff --git a/tff_modular/modules/psionics/code/touch/touch.dm b/tff_modular/modules/psionics/code/touch/touch.dm index 64e1ffb71b6..c38e5847c3f 100644 --- a/tff_modular/modules/psionics/code/touch/touch.dm +++ b/tff_modular/modules/psionics/code/touch/touch.dm @@ -1,21 +1,24 @@ +#define IS_HYPNOTIZED(mob) (mob?.mind?.has_antag_datum(/datum/antagonist/hypnotized)) +#define IS_OBSESSED(mob) (mob?.mind?.has_antag_datum(/datum/antagonist/obsessed)) + // Тут все заклинания, которые являются прикосновением // Спелл для чтения разума другого игрока на наличие псионических способностей -/datum/action/cooldown/spell/touch/psionic/psionic_assay + +/datum/action/cooldown/spell/touch/psionic/assay name = "Psionic Assay" desc = "Check if the target is a psionic." button_icon = 'icons/obj/medical/organs/organs.dmi' button_icon_state = "brain" cooldown_time = 60 SECONDS - mana_cost = 5 - stamina_cost = 0 + mana_cost = 10 target_msg = "Your get a headache, but it quickly fades." - hand_path = /obj/item/melee/touch_attack/psionic_mending + hand_path = /obj/item/melee/touch_attack/psionic/assay draw_message = span_notice("You ready your hand to cleanse a patient.") drop_message = span_notice("You lower your hand.") can_cast_on_self = TRUE - psionic_level = 1 + category = "utility" -/datum/action/cooldown/spell/touch/psionic/psionic_assay/cast_on_hand_hit(obj/item/melee/touch_attack/hand, atom/victim, mob/living/carbon/mendicant) +/datum/action/cooldown/spell/touch/psionic/assay/cast_on_hand_hit(obj/item/melee/touch_attack/hand, atom/victim, mob/living/carbon/mendicant) if(ishuman(victim)) var/mob/living/carbon/human/human_victim = victim if(human_victim.can_block_magic(antimagic_flags)) @@ -32,7 +35,7 @@ else return FALSE -/datum/action/cooldown/spell/touch/psionic/psionic_assay/proc/read_psionic_level(mob/living/carbon/human/patient) +/datum/action/cooldown/spell/touch/psionic/assay/proc/read_psionic_level(mob/living/carbon/human/patient) if(issynthetic(patient) && cast_power < 2) to_chat(owner, span_notice("I can see... just numbers. No idea how to work with synths.")) return FALSE @@ -44,3 +47,205 @@ else owner.visible_message(span_notice("[owner] backs off from [patient]."), span_cyan("Target is not a psionic.")) + +// Читаем разум. Выдаёт: последние сейлоги, интент, настоящее имя, воспоминания, намёк на работу, намёк на то, что в антаг_датум что то есть. +/datum/action/cooldown/spell/touch/psionic/mind_read + name = "Psionic Mind Read" + desc = "Rudely intrude into targets thoughts." + button_icon_state = "mindread" + cooldown_time = 3 SECONDS + mana_cost = 20 + target_msg = "You feel someone else in your head." + + hand_path = /obj/item/melee/touch_attack/psionic/read_mind + draw_message = span_notice("You ready your hand to read someones mind.") + drop_message = span_notice("You lower your hand.") + can_cast_on_self = FALSE + psionic_level = 3 + +/datum/action/cooldown/spell/touch/psionic/mind_read/cast_on_hand_hit(obj/item/melee/touch_attack/hand, atom/victim, mob/living/carbon/mendicant) + if(ishuman(victim)) + var/mob/living/carbon/human/human_victim = victim + if(human_victim.mind && human_victim.stat != DEAD) + if(human_victim.can_block_magic(antimagic_flags)) + to_chat(human_victim, span_bolddanger("Psionic nearby tries to read your mind!")) + else + to_chat(human_victim, span_warning(target_msg)) + owner.visible_message(span_warning("[owner] presses his thumb onto [victim]s forehead."), + span_notice("You press your thumb onto [victim]s forehead and begin reading them.")) + to_chat(victim, span_danger("[owner] presses a thumb onto your forehead and holds it there. It burns sligthly!")) + if(do_after(mendicant, 10 SECONDS, human_victim, IGNORE_SLOWDOWNS, TRUE)) + read_mind(human_victim) + drain_mana() + return TRUE + else + return FALSE + else + return FALSE + +/datum/action/cooldown/spell/touch/psionic/mind_read/proc/read_mind(mob/living/carbon/human/patient) + if(patient.can_block_magic(MAGIC_RESISTANCE_MIND, charge_cost = 0)) + to_chat(owner, span_warning("As you reach into [patient]'s mind, \ + you are stopped by a mental blockage. It seems you've been foiled.")) + return + if(issynthetic(patient) && cast_power < 2) + to_chat(owner, span_notice("I dont know how to work with synths. It's just zeros and ones. How am I supposed to get info out of this metal bucket?")) + return + var/text_to_show = "" + + var/list/recent_speech = patient.copy_recent_speech(copy_amount = 10) + if(length(recent_speech)) + text_to_show += span_boldnotice("You catch some drifting memories of their past conversations...") + "
" + for(var/spoken_memory in recent_speech) + text_to_show += span_notice("[spoken_memory]") + "
" + + text_to_show += span_notice("You find that their intent is to [patient.combat_mode ? "harm" : "help"]...") + "
" + text_to_show += span_notice("You uncover that [patient.p_their()] true identity is [patient.mind.name].") + "
" + text_to_show += span_notice("You can vaguely read their memories: ") + boxed_message(span_italics(get_memories(patient))) + text_to_show += span_notice("You try to read their job: ") + boxed_message(span_italics(get_job_fluff(patient))) + if(patient.mind.enslaved_to || IS_HYPNOTIZED(patient)) + text_to_show += span_boldnotice("[patient.p_Their()] will is not free.") + "
" + if(IS_OBSESSED(patient)) + text_to_show += span_boldnotice("[patient.p_Their()] mind is assaulted by voices within. They should visit a brain surgeon.") + "
" + if(cast_power >= 2) + var/datum/mind/mind_to_read = patient.mind + if(prob(20 * cast_power) && mind_to_read.antag_datums) + if(IS_WIZARD(patient)) + text_to_show += span_notice("You can feel a strong potential pulsating in this individual.") + "
" + else if(IS_HERETIC(patient)) + text_to_show += span_notice("Reality bends around you and goes back to normal, as you try to read [patient.p_their()] mind.") + "
" + var/mob/living/carbon/human/human_owner = owner + human_owner.add_mood_event("gates_of_mansus", /datum/mood_event/gates_of_mansus) + else if(IS_CULTIST(patient)) + text_to_show += span_red("Your mind is assaulted with torrents of blood and gore, as you try to dig deeper.") + "
" + else // Там очень много ролей, в том числе не антажных, а мага, еретика и культиста я думаю и без этой способности найти легко. Тем более мы читаем воспоминания, что более имбово + text_to_show += span_notice("You also can feel something hidden within [patient.p_their()] mind, but it's not readable.") + "
" + + to_chat(owner, boxed_message(span_infoplain(text_to_show))) + +// Возвращает размытый текст о профессии +/datum/action/cooldown/spell/touch/psionic/mind_read/proc/get_job_fluff(mob/living/carbon/human/patient) + var/datum/mind/mind_to_read = patient.mind + var/datum/job/patient_job = mind_to_read.assigned_role + var/text_to_return = "" + if(patient_job.departments_bitflags & DEPARTMENT_BITFLAG_SECURITY) + text_to_return += "This persons job involves beating up mimes and clowns." + "
" + else if(patient_job.departments_bitflags & DEPARTMENT_BITFLAG_CENTRAL_COMMAND) + text_to_return += "This persons is a greatest authority on this station." + "
" + else if(patient_job.departments_bitflags & DEPARTMENT_BITFLAG_CAPTAIN) + text_to_return += "This persons is likely to have megalomania." + "
" + else if(patient_job.departments_bitflags & DEPARTMENT_BITFLAG_COMMAND) + text_to_return += "This persons calling is commanding others." + "
" + else if(patient_job.departments_bitflags & DEPARTMENT_BITFLAG_SERVICE) + text_to_return += "This persons labor is about servicing others." + "
" + else if(patient_job.departments_bitflags & DEPARTMENT_BITFLAG_CARGO) + text_to_return += "This person works physically a lot." + "
" + else if(patient_job.departments_bitflags & DEPARTMENT_BITFLAG_ENGINEERING) + text_to_return += "This person keeps station alive." + "
" + else if(patient_job.departments_bitflags & DEPARTMENT_BITFLAG_SCIENCE) + text_to_return += "This person is an egghead." + "
" + else if(patient_job.departments_bitflags & DEPARTMENT_BITFLAG_MEDICAL) + text_to_return += "This person is accustomed with wounds, blood and their treatment." + "
" + else if(patient_job.departments_bitflags & DEPARTMENT_BITFLAG_SILICON) + text_to_return += "This is en etenral mankinds servant." + "
" + else if(patient_job.departments_bitflags & DEPARTMENT_BITFLAG_ASSISTANT) + text_to_return += "This persons mind reeks of freedom." + "
" + else + text_to_return += "This person is truly free. They are not obligated with any duties." + "
" + + return span_notice(text_to_return) + +// Возвращает воспоминания разума. Имба против таторов, так как там хранится код от аплинка. А ну и банковский айди. +/datum/action/cooldown/spell/touch/psionic/mind_read/proc/get_memories(mob/living/carbon/human/patient) + var/datum/mind/mind_to_read = patient.mind + if(mind_to_read) + var/itogo_text = "" + for(var/key in mind_to_read.memories) + var/datum/memory/mem = mind_to_read.memories[key] + itogo_text += mem.name + "
" + if(itogo_text == "") + itogo_text = "[patient.p_Their()] head is empty." + return itogo_text + else + return "I cant read [patient.p_their()] memories. Maybe there are none?" + "
" + +// Восстанавливает кровь, окси урон, открытые травмы. Не лечит другие типы урона. Если вторичка - психокинетика, то вынимает импланты. +// Если уровень Эпсилон - удаляет лярвы ксеноморфов. +/datum/action/cooldown/spell/touch/psionic/mending + name = "Psionic Mending" + desc = "Mend a creature's wounds. This handles internal wounds as well, such as ruptured organs and broken bones." + button_icon = 'tff_modular/modules/psionics/icons/actions.dmi' + button_icon_state = "mending_touch" + cooldown_time = 50 SECONDS + mana_cost = 30 + target_msg = "You body numbs a little." + hand_path = /obj/item/melee/touch_attack/psionic/mending + draw_message = span_notice("You ready your hand to mend a patient.") + drop_message = span_notice("You lower your hand.") + can_cast_on_self = TRUE + +/datum/action/cooldown/spell/touch/psionic/mending/cast_on_hand_hit(obj/item/melee/touch_attack/hand, atom/victim, mob/living/carbon/mendicant) + if(ishuman(victim)) + var/mob/living/carbon/human/human_victim = victim + if(issynthetic(human_victim) && cast_power < 2) + to_chat(owner, span_notice("I dont know how to work with synths.")) + return FALSE + if(human_victim.can_block_magic(antimagic_flags)) + to_chat(human_victim, span_notice("Psionic nearby tries to mend you.")) + else + to_chat(human_victim, span_warning(target_msg)) + if(!do_after(mendicant, 5 SECONDS, human_victim, IGNORE_SLOWDOWNS, TRUE)) + return FALSE + else + try_heal_all(human_victim) + drain_mana() + return TRUE + else + return FALSE + +/datum/action/cooldown/spell/touch/psionic/mending/proc/try_heal_all(mob/living/carbon/human/patient) + if(patient.all_wounds && cast_power >= 2) + var/datum/wound/wound2fix = patient.all_wounds[1] + wound2fix.remove_wound() + playsound(patient, 'sound/effects/wounds/crack2.ogg', 40, TRUE) + + for(var/obj/item/organ/O in patient.organs) + O.apply_organ_damage(-15 * cast_power) + + if(patient.get_oxy_loss() >= OXYLOSS_PASSOUT_THRESHOLD-10) + patient.adjust_oxy_loss(-cast_power * 20, forced = TRUE) + + if(patient.get_organ_slot("parasite_egg") && cast_power >= 2) // Удаляем ксеноморфов + var/obj/item/organ/body_egg/parasite = patient.get_organ_slot("parasite_egg") + parasite.owner.vomit(VOMIT_CATEGORY_BLOOD | MOB_VOMIT_KNOCKDOWN | MOB_VOMIT_HARM) + parasite.owner.visible_message( + span_warning("[patient] twitches, gags and vomits a living creqture with blood! Gross!"), + span_bolddanger("Suddenly you feel sharp pain in your chest, then something starts moving up your throat. \ + Before you can react somethign slips past your lips with a mix of vomit and blood!"), + ) + var/atom/drop_loc = parasite.drop_location() + parasite.Remove(parasite.owner) + if(drop_loc) + parasite.forceMove(drop_loc) + +/datum/action/cooldown/spell/touch/psionic/electrocute + name = "Psionic Electrocute" + desc = "Administer a painful amount of psionic shock to the nervous system of a foe in melee range, causing burn and agony damage." + button_icon_state = "chain_lighting" + cooldown_time = 10 SECONDS + point_cost = 2 + mana_cost = 10 + psionic_level = 2 + hand_path = /obj/item/melee/touch_attack/psionic/chain_lighting + +/datum/action/cooldown/spell/touch/psionic/electrocute/cast_on_hand_hit(obj/item/melee/touch_attack/hand, atom/victim, mob/living/carbon/caster) + . = ..() + if(ishuman(victim)) + var/mob/living/carbon/human/human_living = victim + human_living.adjust_fire_loss(20) + human_living.electrocute_act(10, jitter_time = 2 SECONDS, stutter_time = 2 SECONDS, stun_duration = 2 SECONDS) + else + return FALSE + +#undef IS_HYPNOTIZED +#undef IS_OBSESSED diff --git a/tff_modular/modules/psionics/code/touch/touch_hand.dm b/tff_modular/modules/psionics/code/touch/touch_hand.dm new file mode 100644 index 00000000000..f207ccfc0bf --- /dev/null +++ b/tff_modular/modules/psionics/code/touch/touch_hand.dm @@ -0,0 +1,33 @@ + +/obj/item/melee/touch_attack/psionic + name = "psionic sparks" + desc = "Concentrated psionic energy in a hand." + icon = 'tff_modular/modules/psionics/icons/spells.dmi' + inhand_icon_state = "greyscale" + light_range = 2 + light_power = 1 + light_on = TRUE + +/obj/item/melee/touch_attack/psionic/mending + name = "Mending" + icon_state = "mend_wounds" + color = COLOR_VERY_PALE_LIME_GREEN + light_color = LIGHT_COLOR_LIGHT_CYAN + +/obj/item/melee/touch_attack/psionic/assay + name = "Assay" + icon_state = "generic" + color = COLOR_PURPLE + light_color = COLOR_PURPLE + +/obj/item/melee/touch_attack/psionic/chain_lighting + name = "Psionic Electrocute" + icon_state = "chain_lighting" + color = COLOR_BLUE + light_color = COLOR_BLUE + +/obj/item/melee/touch_attack/psionic/read_mind + name = "Psionic Electrocute" + icon_state = "generic" + color = COLOR_PINK + light_color = COLOR_PINK diff --git a/tff_modular/modules/psionics/icons/psi_items.dmi b/tff_modular/modules/psionics/icons/psi_items.dmi new file mode 100644 index 0000000000000000000000000000000000000000..20ecb976b7ca0ce7b0b42ada0bda27271b2087b5 GIT binary patch literal 1863 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 ZsJPHO-D524E#P&Oy`AH4S)08s{0Gap_kREY literal 27570 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 diff --git a/tgstation.dme b/tgstation.dme index 9d37b553997..b9d83d25848 100644 --- a/tgstation.dme +++ b/tgstation.dme @@ -9898,19 +9898,16 @@ #include "tff_modular\modules\poster_contest\winners_items\code.dm" #include "tff_modular\modules\psionics\code\_psionic_abilities.dm" #include "tff_modular\modules\psionics\code\_psionic_datum.dm" -#include "tff_modular\modules\psionics\code\coersion.dm" #include "tff_modular\modules\psionics\code\cyberimp.dm" #include "tff_modular\modules\psionics\code\documents.dm" -#include "tff_modular\modules\psionics\code\energistics.dm" #include "tff_modular\modules\psionics\code\hud.dm" -#include "tff_modular\modules\psionics\code\psychokinesis.dm" -#include "tff_modular\modules\psionics\code\redaction.dm" -#include "tff_modular\modules\psionics\code\subsystem.dm" #include "tff_modular\modules\psionics\code\conjure_item\conjure_item.dm" #include "tff_modular\modules\psionics\code\pointed\pointed.dm" #include "tff_modular\modules\psionics\code\projectiles\projectile.dm" #include "tff_modular\modules\psionics\code\psi_shop\datum.dm" +#include "tff_modular\modules\psionics\code\spell\spell.dm" #include "tff_modular\modules\psionics\code\touch\touch.dm" +#include "tff_modular\modules\psionics\code\touch\touch_hand.dm" #include "tff_modular\modules\quirks\code\_quirk.dm" #include "tff_modular\modules\quirks\code\entombed.dm" #include "tff_modular\modules\quirks\code\coldbloodedquirk\coldbloodedquirk.dm" diff --git a/tgui/packages/tgui/interfaces/PsionicShop.tsx b/tgui/packages/tgui/interfaces/PsionicShop.tsx index a3c787d31ba..4c4731cf5d0 100644 --- a/tgui/packages/tgui/interfaces/PsionicShop.tsx +++ b/tgui/packages/tgui/interfaces/PsionicShop.tsx @@ -90,7 +90,7 @@ export const PsionicShop = (props) => { title={ - {psi_points_count} Psi + {psi_points_count} Psi From 845be744b09079e944d73757757516e8a2d21ae2 Mon Sep 17 00:00:00 2001 From: SaukyKouko Date: Thu, 21 May 2026 01:30:43 +0300 Subject: [PATCH 06/22] =?UTF-8?q?=D0=98=D1=81=D0=BF=D1=80=D0=B0=D0=B2?= =?UTF-8?q?=D0=BB=D0=B5=D0=BD=D0=B8=D1=8F=20=D0=B1=D0=B0=D0=B3=D0=BE=D0=B2?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- code/__DEFINES/~ff_defines/psionic.dm | 1 + .../psionics/code/_psionic_abilities.dm | 14 ++- .../modules/psionics/code/_psionic_datum.dm | 37 ++++---- .../code/conjure_item/conjure_item.dm | 10 ++- tff_modular/modules/psionics/code/loner.dm | 11 +++ .../modules/psionics/code/pointed/pointed.dm | 90 ++++++++++++++++--- .../psionics/code/projectiles/projectile.dm | 23 ++--- .../modules/psionics/code/psi_shop/datum.dm | 8 +- .../modules/psionics/code/spell/spell.dm | 43 ++------- .../modules/psionics/code/touch/touch.dm | 7 +- tgstation.dme | 1 + tgui/packages/tgui/interfaces/PsionicShop.tsx | 4 +- 12 files changed, 156 insertions(+), 93 deletions(-) create mode 100644 tff_modular/modules/psionics/code/loner.dm diff --git a/code/__DEFINES/~ff_defines/psionic.dm b/code/__DEFINES/~ff_defines/psionic.dm index 7547ff74f64..357c26d49da 100644 --- a/code/__DEFINES/~ff_defines/psionic.dm +++ b/code/__DEFINES/~ff_defines/psionic.dm @@ -2,5 +2,6 @@ #define TRAIT_PSIONIC_IMPLANT "psionic_implant" #define TRAIT_PSI_SUPPRESSED "psi_suppressed" #define TRAIT_IMMUNE_TO_PSI_SUPPRESSION "immune_to_psi_suppression" +#define PSIONIC_TRAIT "psionic_trait" #define HUD_PSI_DISPLAY "hud_psi_display" #define HUD_PSI_SIGNAL "hud_psi_signal" diff --git a/tff_modular/modules/psionics/code/_psionic_abilities.dm b/tff_modular/modules/psionics/code/_psionic_abilities.dm index 7ea0d4ab4d9..779a55c06d9 100644 --- a/tff_modular/modules/psionics/code/_psionic_abilities.dm +++ b/tff_modular/modules/psionics/code/_psionic_abilities.dm @@ -3,8 +3,6 @@ /datum/action/cooldown/spell // Сколько маны стоит кастануть спелл var/mana_cost = 10 - // Некоторые спеллы могут отнимать стамину - var/stamina_cost = 0 // Что написать жертве var/target_msg // Сила способности @@ -16,7 +14,7 @@ // Датум псионики что используется при касте var/datum/psionic/psionic_datum // Категория - var/category = "utility" + var/category = "Tier 1" // Цена var/point_cost = 1 // Текст помощи @@ -31,6 +29,10 @@ psionic_datum = our_psionic.get_psionic() cast_power = psionic_datum.psionic_level +/datum/action/cooldown/spell/update_button_name(atom/movable/screen/movable/action_button/button, force) + . = ..() + button.desc += " Costs [mana_cost] Psi Energy." + // Спеллы для призвания предмета /datum/action/cooldown/spell/conjure_item/psionic button_icon = 'tff_modular/modules/psionics/icons/spells.dmi' @@ -44,7 +46,6 @@ spell_requirements = NONE cooldown_reduction_per_rank = 0 SECONDS psionic = TRUE - locked = FALSE /datum/action/cooldown/spell/conjure_item/psionic/New(Target, power) . = ..() @@ -63,7 +64,6 @@ /datum/action/cooldown/spell/proc/drain_mana() var/mob/living/carbon/human/caster = owner var/datum/psionic/psi_holder = caster.get_psionic() - caster.adjust_stamina_loss(stamina_cost, forced = TRUE) if(psi_holder) psi_holder.adjust_psi_energy(-mana_cost) return TRUE @@ -98,7 +98,6 @@ cooldown_reduction_per_rank = 0 SECONDS psionic = TRUE psionic_level = 1 - locked = FALSE /datum/action/cooldown/spell/psionic/New(Target, power, additional_school) . = ..() @@ -127,7 +126,6 @@ spell_requirements = NONE cooldown_reduction_per_rank = 0 SECONDS psionic = TRUE - locked = FALSE cast_range = 7 /datum/action/cooldown/spell/pointed/projectile/psionic/New(Target, power, additional_school) @@ -156,7 +154,6 @@ spell_requirements = NONE cooldown_reduction_per_rank = 0 SECONDS psionic = TRUE - locked = FALSE cast_range = 7 /datum/action/cooldown/spell/pointed/psionic/New(Target, power, additional_school) @@ -184,7 +181,6 @@ invocation_type = INVOCATION_NONE spell_requirements = NONE psionic = TRUE - locked = FALSE /datum/action/cooldown/spell/touch/psionic/New(Target, power, additional_school) . = ..() diff --git a/tff_modular/modules/psionics/code/_psionic_datum.dm b/tff_modular/modules/psionics/code/_psionic_datum.dm index 37cfe7453b1..73ace652575 100644 --- a/tff_modular/modules/psionics/code/_psionic_datum.dm +++ b/tff_modular/modules/psionics/code/_psionic_datum.dm @@ -42,7 +42,7 @@ /mob/living/proc/remove_psionic() if(!psi_sensivity) return FALSE - qdel(psi_sensivity) + QDEL_NULL(psi_sensivity) /mob/living/proc/get_psionic() if(!psi_sensivity) @@ -63,7 +63,7 @@ // Псионические очки, нужные для получения способностей var/psi_point = 0 // Требуется ли выдать лицензию - var/license = TRUE + var/license = FALSE /// Два вара скопированные из item_quirk для правильной выдачи лицензии var/list/where_items_spawned var/open_backpack = FALSE @@ -93,7 +93,7 @@ add_shop() /datum/psionic/proc/add_shop() - psi_shop_datum = new(psi_owner, src) + psi_shop_datum = new(psi_owner, src, psionic_level) psi_shop_action = new(psi_shop_datum) psi_shop_action.Grant(psi_owner) @@ -117,24 +117,23 @@ open_backpack = TRUE if(notify_player) - LAZYADD(where_items_spawned, span_boldnotice("You have \a [license] [where]. [flavour_text]")) + LAZYADD(where_items_spawned, span_horizonblue("You have \a [license] [where]. [flavour_text]")) /datum/psionic/Destroy(force) . = ..() UnregisterSignal(psi_owner, COMSIG_MOB_HUD_CREATED) RegisterSignal(psi_owner, COMSIG_LIVING_LIFE, PROC_REF(psionic_life)) - if(!psi_owner.hud_used) - return - - var/datum/hud/psi_hud = psi_owner.hud_used - psi_hud.remove_screen_object(HUD_PSI_DISPLAY) - psi_hud.remove_screen_object(HUD_PSI_SIGNAL) + QDEL_NULL(psi_shop_action) + QDEL_NULL(psi_shop_datum) for(var/datum/action/cooldown/spell/spells_to_remove in psi_owner.actions) if(!spells_to_remove.psionic) continue spells_to_remove.Remove() - QDEL_NULL(psi_shop_action) - QDEL_NULL(psi_shop_datum) + + if(psi_owner.hud_used) + var/datum/hud/psi_hud = psi_owner.hud_used + psi_hud.remove_screen_object(HUD_PSI_DISPLAY) + psi_hud.remove_screen_object(HUD_PSI_SIGNAL) /datum/psionic/proc/psionic_life(seconds_per_tick) SIGNAL_HANDLER @@ -169,12 +168,15 @@ max_mana = 25 psionic_level = 1 psionic_level_string = SENSITIVE_PSIONIC + license = TRUE + psi_point = 5 /datum/psionic/harmonious max_mana = 100 psionic_level = 2 psionic_level_string = HARMONIOUS_PSIONIC license = FALSE + psi_point = 10 /datum/psionic/proc/is_suppressed() if(HAS_TRAIT(psi_owner, TRAIT_PSI_SUPPRESSED)) @@ -204,12 +206,14 @@ if(!ispath(spell_path, /datum/action/cooldown/spell)) CRASH("Psionic research_spell attempted to purchase an invalid typepath! (got: [spell_path])") + if(spell_path.psionic_level > get_level()) + to_chat(psi_owner, span_horizonblue("We have not enough psi power to get this spell!")) + return FALSE if(learned_spells[spell_path]) - to_chat(psi_owner, span_warning("We have already researched this spell!")) + to_chat(psi_owner, span_horizonblue("We have already researched this spell!")) return FALSE - if(psi_point < initial(spell_path.point_cost)) - to_chat(psi_owner, span_warning("We cant research this spell now!")) + to_chat(psi_owner, span_horizonblue("We cant research this spell now!")) return FALSE var/success = give_spell(spell_path) @@ -229,5 +233,8 @@ return TRUE +/datum/psionic/proc/get_level() + return psionic_level + #undef SENSITIVE_PSIONIC #undef HARMONIOUS_PSIONIC diff --git a/tff_modular/modules/psionics/code/conjure_item/conjure_item.dm b/tff_modular/modules/psionics/code/conjure_item/conjure_item.dm index 8e7c96b4587..c2039ec6a3f 100644 --- a/tff_modular/modules/psionics/code/conjure_item/conjure_item.dm +++ b/tff_modular/modules/psionics/code/conjure_item/conjure_item.dm @@ -1,6 +1,6 @@ // Спавнит пси-клинок в руке. Сила зависит от уровня псионика /datum/action/cooldown/spell/conjure_item/psionic/psiblade - name = "Psi blade" + name = "Psionic blade" desc = "Concentrates psionic energy to create a sharp blade in your hand." button_icon = 'icons/obj/weapons/transforming_energy.dmi' button_icon_state = "blade" @@ -9,7 +9,8 @@ mana_cost = 40 psionic_level = 2 point_cost = 3 - category = "combat" + category = "Tier 2" + locked = FALSE /datum/action/cooldown/spell/conjure_item/psionic/psiblade/make_item(atom/caster) var/obj/item/summoning_obj = item_type @@ -42,14 +43,15 @@ // Спавнит омни инструмент в руке псионика. Аналог абдукторского /datum/action/cooldown/spell/conjure_item/psionic/psitool - name = "Psi tool" + name = "Psionic tool" desc = "Concentrates psionic energy to create a universal tool." button_icon = 'icons/obj/antags/abductor.dmi' button_icon_state = "omnitool" cooldown_time = 60 SECONDS item_type = /obj/item/psionic_omnitool mana_cost = 20 - category = "utility" + category = "Tier 1" + locked = FALSE /datum/action/cooldown/spell/conjure_item/psionic/psiblade/make_item(atom/caster) var/obj/item/made_item = new item_type(caster.loc, cast_power) diff --git a/tff_modular/modules/psionics/code/loner.dm b/tff_modular/modules/psionics/code/loner.dm new file mode 100644 index 00000000000..b2bbf6e286a --- /dev/null +++ b/tff_modular/modules/psionics/code/loner.dm @@ -0,0 +1,11 @@ +/datum/antagonist/loner + name = "\improper Loner" + antagpanel_category = ANTAG_GROUP_SYNDICATE + show_in_antagpanel = TRUE + var/datum/psionic/psi + +/datum/antagonist/loner/apply_innate_effects(mob/living/mob_override) + . = ..() + var/mob/living/carbon/current_owner = mob_override || owner.current + current_owner.add_psionic(/datum/psionic/harmonious) + ADD_TRAIT(current_owner, TRAIT_IMMUNE_TO_PSI_SUPPRESSION, PSIONIC_TRAIT) diff --git a/tff_modular/modules/psionics/code/pointed/pointed.dm b/tff_modular/modules/psionics/code/pointed/pointed.dm index 5bdd981aa04..aed2d87cbe9 100644 --- a/tff_modular/modules/psionics/code/pointed/pointed.dm +++ b/tff_modular/modules/psionics/code/pointed/pointed.dm @@ -11,6 +11,8 @@ psionic_level = 2 target_msg = "Your muscles spasm!" active_msg = "You prepare to stun a target..." + locked = FALSE + category = "Tier 2" /datum/action/cooldown/spell/pointed/psionic/spasm/is_valid_target(atom/cast_on) if(!ishuman(cast_on)) @@ -44,6 +46,7 @@ mana_cost = 10 target_msg = "You feel like someone is looking deep into you." active_msg = "You prepare to scan a target..." + locked = FALSE /datum/action/cooldown/spell/pointed/psionic/skinsight/is_valid_target(atom/cast_on) if(!ishuman(cast_on)) @@ -72,16 +75,24 @@ psionic_level = 2 mana_cost = 20 point_cost = 2 + locked = FALSE + category = "Tier 2" -/datum/action/cooldown/spell/pointed/psionic/jump/cast(atom/cast_on) +/datum/action/cooldown/spell/pointed/psionic/jump/is_valid_target(atom/cast_on) . = ..() - if(!isclosedturf(cast_on) && !isobj(cast_on)) - if(isliving(cast_on)) - do_teleport(owner, cast_on.loc, effectin = /obj/effect/temp_visual/wizard, effectout = /obj/effect/temp_visual/wizard/out) - else if(isturf(cast_on)) - do_teleport(owner, cast_on.loc, effectin = /obj/effect/temp_visual/wizard, effectout = /obj/effect/temp_visual/wizard/out) - else + if(isclosedturf(cast_on)) return FALSE + if(isobj(cast_on)) + return FALSE + return TRUE + +/datum/action/cooldown/spell/pointed/psionic/jump/cast(atom/cast_on) + . = ..() + if(isliving(cast_on)) + do_teleport(owner, cast_on.loc, effectin = /obj/effect/temp_visual/wizard, effectout = /obj/effect/temp_visual/wizard/out) + else if(isturf(cast_on)) + do_teleport(owner, cast_on.loc, effectin = /obj/effect/temp_visual/wizard, effectout = /obj/effect/temp_visual/wizard/out) + return TRUE /datum/action/cooldown/spell/pointed/psionic/emotional_suggestion name = "Psionic Emotional Suggestion" @@ -89,11 +100,13 @@ cooldown_time = 2 SECONDS mana_cost = 5 point_cost = 0 + locked = FALSE /datum/action/cooldown/spell/pointed/psionic/emotional_suggestion/cast(atom/cast_on) . = ..() if(iscarbon(cast_on)) emotional_suggestion(cast_on, owner) + return TRUE else return FALSE @@ -127,6 +140,7 @@ to_chat(H, span_notice("You sense [user]'s psyche link with your own, and an emotion of [text] washes through your mind.")) else to_chat(H, span_notice("An emotion from outside your consciousness slips into your mind: [text].")) + return TRUE /datum/action/cooldown/spell/pointed/psionic/rejuvenate name = "Rejuvenate" @@ -136,10 +150,12 @@ point_cost = 3 mana_cost = 80 psionic_level = 2 + locked = FALSE + category = "Tier 2" /datum/action/cooldown/spell/pointed/psionic/rejuvenate/is_valid_target(atom/cast_on) . = ..() - if(!ismob(cast_on)) + if(!isliving(cast_on)) return FALSE if(!isdead(cast_on)) return FALSE @@ -150,12 +166,13 @@ if(!reviving.get_organ_slot(ORGAN_SLOT_BRAIN)) owner.balloon_alert(owner, "no brain!") return FALSE + return TRUE /datum/action/cooldown/spell/pointed/psionic/rejuvenate/cast(atom/cast_on) . = ..() if(isbasicmob(cast_on)) var/mob/living/basic/basic_living = cast_on - for (var/i in 1 to 3) + for(var/i in 1 to 3) if (!do_after(owner, 3 SECONDS, basic_living, timed_action_flags = IGNORE_INCAPACITATED | IGNORE_USER_LOC_CHANGE | IGNORE_TARGET_LOC_CHANGE)) return FALSE @@ -176,12 +193,13 @@ owner.balloon_alert(owner, "revival failed!") return FALSE + drain_mana() to_chat(owner, span_green("You successfully revive \the [owner]!")) return TRUE else if(iscarbon(cast_on)) var/mob/living/carbon/carbon_living = cast_on - for (var/i in 1 to 3) + for(var/i in 1 to 3) if (!do_after(owner, 5 SECONDS, carbon_living, timed_action_flags = IGNORE_INCAPACITATED | IGNORE_USER_LOC_CHANGE | IGNORE_TARGET_LOC_CHANGE)) return FALSE @@ -206,6 +224,7 @@ return FALSE to_chat(owner, span_green("You successfully revive \the [owner]!")) + drain_mana() return TRUE /datum/action/cooldown/spell/pointed/psionic/stasis @@ -215,15 +234,60 @@ point_cost = 1 psionic_level = 2 mana_cost = 30 + locked = FALSE + category = "Tier 2" /datum/action/cooldown/spell/pointed/psionic/stasis/is_valid_target(atom/cast_on) . = ..() if(!isliving(cast_on)) return FALSE + return TRUE /datum/action/cooldown/spell/pointed/psionic/stasis/cast(atom/cast_on) . = ..() var/mob/living/freezing = cast_on - freezing.apply_status_effect(/datum/status_effect/freon/watcher) - var/datum/status_effect/freon/watcher/status_effect = freezing.has_status_effect(/datum/status_effect/freon/watcher) - status_effect.duration = 4 * cast_power + var/duration = cast_power * 4 SECONDS + freezing.set_timed_status_effect(duration, /datum/status_effect/freon/watcher) + +/datum/action/cooldown/spell/pointed/psionic/bubble + name = "Psionic Bubble" + desc = "Create a protective bubble around you or target that removes your need to breathe or wear space protection!" + button_icon_state = "shield" + point_cost = 1 + cooldown_time = 30 SECONDS + mana_cost = 10 + locked = FALSE + +/datum/action/cooldown/spell/pointed/psionic/bubble/is_valid_target(atom/cast_on) + . = ..() + if(!isliving(cast_on)) + return FALSE + return TRUE + +/datum/action/cooldown/spell/pointed/psionic/bubble/cast(atom/cast_on) + . = ..() + var/mob/living/living_living = cast_on + var/duration = cast_power * 15 SECONDS + living_living.set_timed_status_effect(duration, /datum/status_effect/psi_bubble) + +/datum/status_effect/psi_bubble + id = "psi_bubble" + alert_type = /atom/movable/screen/alert/psi_bubble + duration = 15 SECONDS + var/icon/bubbleicon + +/datum/status_effect/psi_bubble/on_apply() + . = ..() + bubbleicon = icon(icon = 'icons/effects/effects.dmi', icon_state = "shield2") + owner.add_overlay(bubbleicon) + owner.add_traits(list(TRAIT_OXYIMMUNE, TRAIT_RESISTLOWPRESSURE, TRAIT_RESISTCOLD)) + +/datum/status_effect/psi_bubble/on_remove() + . = ..() + owner.cut_overlay(bubbleicon) + +/atom/movable/screen/alert/psi_bubble + icon = 'icons/effects/effects.dmi' + icon_state = "shield2" + name = "Air Bubble" + desc = "There is a protective bubble around you that removes your need to breathe or wear space protection!" diff --git a/tff_modular/modules/psionics/code/projectiles/projectile.dm b/tff_modular/modules/psionics/code/projectiles/projectile.dm index 384ceb87e65..31790624ac5 100644 --- a/tff_modular/modules/psionics/code/projectiles/projectile.dm +++ b/tff_modular/modules/psionics/code/projectiles/projectile.dm @@ -12,13 +12,14 @@ projectile_amount = INFINITY psionic_level = 1 point_cost = 2 + locked = FALSE var/casting = FALSE /datum/action/cooldown/spell/pointed/projectile/psionic/air_bullet/before_cast(atom/cast_on) . = ..() if(!casting) casting = TRUE - if(do_after(owner, 0.5 SECONDS, timed_action_flags = IGNORE_USER_LOC_CHANGE | IGNORE_SLOWDOWNS)) + if(!do_after(owner, 0.5 SECONDS, timed_action_flags = IGNORE_USER_LOC_CHANGE | IGNORE_SLOWDOWNS)) return FALSE psionic_datum.adjust_psi_energy(-10) else @@ -45,35 +46,37 @@ button_icon = 'icons/effects/freeze.dmi' button_icon_state = "ice_cube" cooldown_time = 1 SECONDS - mana_cost = 35 + mana_cost = 20 cast_range = 9 active_msg = "You prepare to fire ice shard..." deactive_msg = "You relax." projectile_type = /obj/projectile/temp/watcher/psionic_freeze psionic_level = 1 point_cost = 1 - category = "manipulation" - -/datum/action/cooldown/spell/pointed/projectile/psionic/freeze/is_valid_target(atom/cast_on) - if(!isliving(cast_on)) - return FALSE - return TRUE + category = "Tier 1" + locked = FALSE /datum/action/cooldown/spell/pointed/projectile/psionic/freeze/cast(mob/living/cast_on) drain_mana() . = ..() return TRUE +/datum/action/cooldown/spell/pointed/projectile/psionic/freeze/ready_projectile(obj/projectile/to_fire, atom/target, mob/user, iteration) + . = ..() + var/obj/projectile/temp/watcher/psionic_freeze/psi_freeze = to_fire + psi_freeze.power = cast_power + // Вывел в отдельный тип, потому что в оригинальном ice_wing снаряде видимо баг(?) и он не замораживает, хотя должен. /obj/projectile/temp/watcher/psionic_freeze name = "freezing blast" damage = 0 // Нет дамага, вместо этого замораживает + var/power = 1 /obj/projectile/temp/watcher/psionic_freeze/apply_status(mob/living/target) if(HAS_TRAIT(target, TRAIT_RESISTCOLD)) // Вот тут у ice_wing лишний ! return - target.apply_status_effect(/datum/status_effect/freon/watcher/psionic_freeze) + target.set_timed_status_effect(power * 4, /datum/status_effect/freon/watcher/psionic_freeze) /datum/status_effect/freon/watcher/psionic_freeze - duration = 4 // 4 секунды вместо 8 + duration = 4 SECONDS can_melt = TRUE diff --git a/tff_modular/modules/psionics/code/psi_shop/datum.dm b/tff_modular/modules/psionics/code/psi_shop/datum.dm index 499c59a5454..ba83a9d2cb5 100644 --- a/tff_modular/modules/psionics/code/psi_shop/datum.dm +++ b/tff_modular/modules/psionics/code/psi_shop/datum.dm @@ -1,5 +1,5 @@ /datum/psionic_shop - var/name = "Psi Mind" + var/name = "Psionic Mind" var/datum/psionic/psi_datum var/mob/living/psi_owner var/list/possible_spells @@ -8,10 +8,9 @@ . = ..() psi_datum = psionic_datum psi_owner = psionic_mob - possible_spells = get_possible_spells() /datum/psionic_shop/proc/get_possible_spells() - var/static/list/spell_options + var/list/spell_options if(!spell_options) spell_options = subtypesof(/datum/action/cooldown/spell) for(var/datum/action/cooldown/spell/spell as anything in spell_options) @@ -19,8 +18,6 @@ spell_options -= spell if(spell.locked) spell_options -= spell - if(spell.psionic_level > psi_datum.psionic_level) - spell_options -= spell return spell_options @@ -39,6 +36,7 @@ return UI_INTERACTIVE /datum/psionic_shop/ui_interact(mob/user, datum/tgui/ui) + possible_spells = get_possible_spells() ui = SStgui.try_update_ui(user, src, ui) if(!ui) ui = new(user, src, "PsionicShop", name) diff --git a/tff_modular/modules/psionics/code/spell/spell.dm b/tff_modular/modules/psionics/code/spell/spell.dm index 876dfa3f94c..bbe1ec7a443 100644 --- a/tff_modular/modules/psionics/code/spell/spell.dm +++ b/tff_modular/modules/psionics/code/spell/spell.dm @@ -4,6 +4,7 @@ desc = "Force yourself to recieve telekinesis mutation." cooldown_time = 20 SECONDS mana_cost = 80 + locked = FALSE /datum/action/cooldown/spell/psionic/psionic_telekinesis/is_valid_target(atom/cast_on) return !issynthetic(cast_on) @@ -29,6 +30,8 @@ cooldown_time = 15 SECONDS mana_cost = 30 psionic_level = 2 + locked = FALSE + category = "Tier 2" /datum/action/cooldown/spell/psionic/emp/cast(atom/cast_on) . = ..() @@ -43,6 +46,8 @@ mana_cost = 20 point_cost = 1 psionic_level = 2 + locked = FALSE + category = "Tier 2" /datum/action/cooldown/spell/psionic/focus/cast(atom/cast_on) . = ..() @@ -55,14 +60,18 @@ human_living.SetSleeping(0) human_living.SetAllImmobility(0) drain_mana() + else + return FALSE /datum/action/cooldown/spell/psionic/charge name = "Psionic Charge" + desc = "Use this spell on an item with a cell to charge it." button_icon_state = "audible_deception" cooldown_time = 60 SECONDS mana_cost = 10 point_cost = 1 psionic_level = 1 + locked = FALSE /datum/action/cooldown/spell/psionic/charge/is_valid_target(atom/cast_on) return isliving(cast_on) @@ -108,37 +117,3 @@ to_chat(cast_on, span_notice("[to_charge] doesn't seem to be react to [src].")) drain_mana() - -/datum/action/cooldown/spell/pointed/psionic/bubble - name = "Psionic Bubble" - desc = "Create a protective bubble around you or target that removes your need to breathe or wear space protection!" - button_icon_state = "shield" - point_cost = 1 - cooldown_time = 30 SECONDS - mana_cost = 10 - -/datum/action/cooldown/spell/pointed/psionic/bubble/cast(atom/cast_on) - . = ..() - var/mob/living/living_living = cast_on - living_living.set_timed_status_effect(15 SECONDS * cast_power, /datum/status_effect/psi_bubble) - -/datum/status_effect/psi_bubble - id = "psi_bubble" - alert_type = /atom/movable/screen/alert/psi_bubble - var/icon/bubbleicon - -/datum/status_effect/psi_bubble/on_apply() - . = ..() - bubbleicon = icon(icon = 'icons/effects/effects.dmi', icon_state = "shield2") - owner.add_overlay(bubbleicon) - owner.add_traits(list(TRAIT_OXYIMMUNE, TRAIT_RESISTLOWPRESSURE, TRAIT_RESISTCOLD)) - -/datum/status_effect/psi_bubble/on_remove() - . = ..() - owner.cut_overlay(bubbleicon) - -/atom/movable/screen/alert/psi_bubble - icon = 'icons/effects/effects.dmi' - icon_state = "shield2" - name = "Air Bubble" - desc = "There is a protective bubble around you that removes your need to breathe or wear space protection!" diff --git a/tff_modular/modules/psionics/code/touch/touch.dm b/tff_modular/modules/psionics/code/touch/touch.dm index c38e5847c3f..4fcfc53b117 100644 --- a/tff_modular/modules/psionics/code/touch/touch.dm +++ b/tff_modular/modules/psionics/code/touch/touch.dm @@ -16,7 +16,8 @@ draw_message = span_notice("You ready your hand to cleanse a patient.") drop_message = span_notice("You lower your hand.") can_cast_on_self = TRUE - category = "utility" + category = "Tier 1" + locked = FALSE /datum/action/cooldown/spell/touch/psionic/assay/cast_on_hand_hit(obj/item/melee/touch_attack/hand, atom/victim, mob/living/carbon/mendicant) if(ishuman(victim)) @@ -62,6 +63,7 @@ drop_message = span_notice("You lower your hand.") can_cast_on_self = FALSE psionic_level = 3 + locked = FALSE /datum/action/cooldown/spell/touch/psionic/mind_read/cast_on_hand_hit(obj/item/melee/touch_attack/hand, atom/victim, mob/living/carbon/mendicant) if(ishuman(victim)) @@ -183,6 +185,7 @@ draw_message = span_notice("You ready your hand to mend a patient.") drop_message = span_notice("You lower your hand.") can_cast_on_self = TRUE + locked = FALSE /datum/action/cooldown/spell/touch/psionic/mending/cast_on_hand_hit(obj/item/melee/touch_attack/hand, atom/victim, mob/living/carbon/mendicant) if(ishuman(victim)) @@ -237,6 +240,8 @@ mana_cost = 10 psionic_level = 2 hand_path = /obj/item/melee/touch_attack/psionic/chain_lighting + locked = FALSE + category = "Tier 2" /datum/action/cooldown/spell/touch/psionic/electrocute/cast_on_hand_hit(obj/item/melee/touch_attack/hand, atom/victim, mob/living/carbon/caster) . = ..() diff --git a/tgstation.dme b/tgstation.dme index b9d83d25848..8e8a3a6356d 100644 --- a/tgstation.dme +++ b/tgstation.dme @@ -9901,6 +9901,7 @@ #include "tff_modular\modules\psionics\code\cyberimp.dm" #include "tff_modular\modules\psionics\code\documents.dm" #include "tff_modular\modules\psionics\code\hud.dm" +#include "tff_modular\modules\psionics\code\loner.dm" #include "tff_modular\modules\psionics\code\conjure_item\conjure_item.dm" #include "tff_modular\modules\psionics\code\pointed\pointed.dm" #include "tff_modular\modules\psionics\code\projectiles\projectile.dm" diff --git a/tgui/packages/tgui/interfaces/PsionicShop.tsx b/tgui/packages/tgui/interfaces/PsionicShop.tsx index 4c4731cf5d0..a1814362032 100644 --- a/tgui/packages/tgui/interfaces/PsionicShop.tsx +++ b/tgui/packages/tgui/interfaces/PsionicShop.tsx @@ -47,14 +47,14 @@ export const PsionicShop = (props) => { const [searchText, setSearchText] = useState(''); const [compactMode, setCompactMode] = useState(false); - const CATEGORY_ORDER = ['utility', 'combat', 'manipulation']; + const CATEGORY_ORDER = ['Tier 1', 'Tier 2']; const allCategories = Array.from(new Set(many_spells.map((a) => a.category))); const sortedCategories = [ ...CATEGORY_ORDER.filter((cat) => allCategories.includes(cat)), ...allCategories.filter((cat) => !CATEGORY_ORDER.includes(cat)), ]; const [selectedCategory, setSelectedCategory] = useState( - sortedCategories[0] || 'combat', + sortedCategories[0] || 'Tier 1', ); const filteredItems = ( From c362d8d3823bbe157d8a73f257927fe754c7f833 Mon Sep 17 00:00:00 2001 From: SaukyKouko Date: Fri, 22 May 2026 16:25:57 +0300 Subject: [PATCH 07/22] =?UTF-8?q?=D0=94=D0=BE=D0=B1=D0=B0=D0=B2=D0=BB?= =?UTF-8?q?=D1=8F=D0=B5=D0=BC=20=D0=BD=D0=BE=D0=B2=D1=8B=D0=B5=20=D1=81?= =?UTF-8?q?=D0=BF=D0=B5=D0=BB=D0=BB=D1=8B,=20=D0=B8=D1=81=D0=BF=D1=80?= =?UTF-8?q?=D0=B0=D0=B2=D0=BB=D1=8F=D0=B5=D0=BC=20=D0=B1=D0=B0=D0=B3=D0=B8?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- code/__DEFINES/~ff_defines/psionic.dm | 7 +- .../psionics/code/_psionic_abilities.dm | 153 +++-- .../modules/psionics/code/_psionic_datum.dm | 42 +- .../code/conjure_item/conjure_item.dm | 13 +- tff_modular/modules/psionics/code/cyberimp.dm | 4 +- tff_modular/modules/psionics/code/hud.dm | 17 +- tff_modular/modules/psionics/code/loner.dm | 13 +- .../modules/psionics/code/pointed/pointed.dm | 536 +++++++++++++++--- .../psionics/code/projectiles/projectile.dm | 104 ++-- .../modules/psionics/code/psi_shop/datum.dm | 5 +- .../modules/psionics/code/spell/spell.dm | 396 ++++++++++++- .../modules/psionics/code/touch/touch.dm | 79 ++- .../modules/psionics/code/touch/touch_hand.dm | 44 +- .../modules/psionics/icons/actions.dmi | Bin 4932 -> 0 bytes tff_modular/modules/psionics/icons/spells.dmi | Bin 23724 -> 36751 bytes .../modules/psionics/icons/touch_spell.dmi | Bin 0 -> 24218 bytes .../modules/psionics/sounds/power_config.ogg | Bin 0 -> 20039 bytes .../modules/psionics/sounds/power_evoke.ogg | Bin 0 -> 17986 bytes .../psionics/sounds/power_fabrication.ogg | Bin 0 -> 50996 bytes .../modules/psionics/sounds/power_fail.ogg | Bin 0 -> 24938 bytes .../psionics/sounds/power_feedback.ogg | Bin 0 -> 77856 bytes .../modules/psionics/sounds/power_unlock.ogg | Bin 0 -> 30877 bytes .../modules/psionics/sounds/power_used.ogg | Bin 0 -> 22450 bytes tgui/packages/tgui/interfaces/PsionicShop.tsx | 7 +- 24 files changed, 1129 insertions(+), 291 deletions(-) delete mode 100644 tff_modular/modules/psionics/icons/actions.dmi create mode 100644 tff_modular/modules/psionics/icons/touch_spell.dmi create mode 100644 tff_modular/modules/psionics/sounds/power_config.ogg create mode 100644 tff_modular/modules/psionics/sounds/power_evoke.ogg create mode 100644 tff_modular/modules/psionics/sounds/power_fabrication.ogg create mode 100644 tff_modular/modules/psionics/sounds/power_fail.ogg create mode 100644 tff_modular/modules/psionics/sounds/power_feedback.ogg create mode 100644 tff_modular/modules/psionics/sounds/power_unlock.ogg create mode 100644 tff_modular/modules/psionics/sounds/power_used.ogg diff --git a/code/__DEFINES/~ff_defines/psionic.dm b/code/__DEFINES/~ff_defines/psionic.dm index 357c26d49da..fab60f9dfc1 100644 --- a/code/__DEFINES/~ff_defines/psionic.dm +++ b/code/__DEFINES/~ff_defines/psionic.dm @@ -1,7 +1,10 @@ #define TRAIT_PSIONIC_USER "psionicuser" #define TRAIT_PSIONIC_IMPLANT "psionic_implant" -#define TRAIT_PSI_SUPPRESSED "psi_suppressed" -#define TRAIT_IMMUNE_TO_PSI_SUPPRESSION "immune_to_psi_suppression" +#define TRAIT_PSIONIC_SUPPRESSED "psionic_suppressed" +#define TRAIT_PSIONIC_EXHAUSTION "psionic_exhaustion" +#define SUNDER_TRAIT "sunder_trait" #define PSIONIC_TRAIT "psionic_trait" #define HUD_PSI_DISPLAY "hud_psi_display" #define HUD_PSI_SIGNAL "hud_psi_signal" +#define HUD_PSIONIC_ARROW "psionic_arrow" +#define FACTION_PSIONIC "faction_psionic" diff --git a/tff_modular/modules/psionics/code/_psionic_abilities.dm b/tff_modular/modules/psionics/code/_psionic_abilities.dm index 779a55c06d9..9c578087bbc 100644 --- a/tff_modular/modules/psionics/code/_psionic_abilities.dm +++ b/tff_modular/modules/psionics/code/_psionic_abilities.dm @@ -21,6 +21,8 @@ var/helptext = "" // Доступ var/locked = TRUE + // Игнорируем ли мы подавление/отсутствие маны? + var/ignore_suppression = FALSE /datum/action/cooldown/spell/Grant(mob/grant_to) . = ..() @@ -31,7 +33,8 @@ /datum/action/cooldown/spell/update_button_name(atom/movable/screen/movable/action_button/button, force) . = ..() - button.desc += " Costs [mana_cost] Psi Energy." + if(mana_cost) + button.desc += " Costs [mana_cost] Psi Energy." // Спеллы для призвания предмета /datum/action/cooldown/spell/conjure_item/psionic @@ -55,10 +58,15 @@ /datum/action/cooldown/spell/proc/check_for_mana() var/mob/living/carbon/human/caster = owner var/datum/psionic/psi_holder = caster.get_psionic() - if(psi_holder) - return TRUE - else + if(!psi_holder) + return FALSE + if(HAS_TRAIT(caster, TRAIT_PSIONIC_EXHAUSTION)) + return FALSE + if(HAS_TRAIT(caster, TRAIT_PSIONIC_SUPPRESSED)) + if(ignore_suppression) + return TRUE return FALSE + return TRUE // Сосём ману у псионика /datum/action/cooldown/spell/proc/drain_mana() @@ -70,15 +78,10 @@ else return FALSE -/datum/action/cooldown/spell/conjure_item/psionic/can_cast_spell(feedback) +/datum/action/cooldown/spell/conjure_item/psionic/before_cast(atom/cast_on) . = ..() - if(!.) - return FALSE - if(!check_for_mana()) - return FALSE - else - return TRUE + return SPELL_CANCEL_CAST /datum/action/cooldown/spell/conjure_item/psionic/cast(atom/cast_on) drain_mana() @@ -99,19 +102,10 @@ psionic = TRUE psionic_level = 1 -/datum/action/cooldown/spell/psionic/New(Target, power, additional_school) - . = ..() - cast_power = power - -/datum/action/cooldown/spell/psionic/can_cast_spell(feedback) +/datum/action/cooldown/spell/psionic/before_cast(atom/cast_on) . = ..() - if(!.) - return FALSE - if(!check_for_mana()) - return FALSE - else - return TRUE + return SPELL_CANCEL_CAST // Спеллы для пострелушек /datum/action/cooldown/spell/pointed/projectile/psionic @@ -128,19 +122,10 @@ psionic = TRUE cast_range = 7 -/datum/action/cooldown/spell/pointed/projectile/psionic/New(Target, power, additional_school) +/datum/action/cooldown/spell/pointed/projectile/psionic/before_cast(atom/cast_on) . = ..() - cast_power = power - -/datum/action/cooldown/spell/pointed/projectile/psionic/can_cast_spell(feedback) - . = ..() - if(!.) - return FALSE - if(!check_for_mana()) - return FALSE - else - return TRUE + return SPELL_CANCEL_CAST // Направленные спеллы a.k.a. псионик выбирают цель на дистанции /datum/action/cooldown/spell/pointed/psionic @@ -156,21 +141,12 @@ psionic = TRUE cast_range = 7 -/datum/action/cooldown/spell/pointed/psionic/New(Target, power, additional_school) +/datum/action/cooldown/spell/pointed/psionic/before_cast(atom/cast_on) . = ..() - cast_power = power - -/datum/action/cooldown/spell/pointed/psionic/can_cast_spell(feedback) - . = ..() - if(!.) - return FALSE - if(!check_for_mana()) - return FALSE - else - return TRUE + return SPELL_CANCEL_CAST -// Спеллы которыми надо каснуться чего либо +// Спеллы которыми надо коснуться чего либо. Перед активацией имеется "этап активации" заклинания. /datum/action/cooldown/spell/touch/psionic button_icon = 'tff_modular/modules/psionics/icons/spells.dmi' background_icon_state = "bg_tech_blue" @@ -181,30 +157,95 @@ invocation_type = INVOCATION_NONE spell_requirements = NONE psionic = TRUE + var/channel_message + var/currently_channeling = FALSE + var/channel_time = 1 SECONDS + var/channel_flags = IGNORE_USER_LOC_CHANGE|IGNORE_HELD_ITEM + var/charge_overlay_icon = 'icons/effects/effects.dmi' + var/charge_overlay_state = "lighting" + var/mutable_appearance/charge_overlay_instance + var/charge_sound = 'tff_modular/modules/psionics/sounds/power_evoke.ogg' + var/sound/charge_sound_instance -/datum/action/cooldown/spell/touch/psionic/New(Target, power, additional_school) +/datum/action/cooldown/spell/touch/psionic/New(Target, original) . = ..() - cast_power = power + if(!channel_message) + channel_message = span_notice("You start charging [src]...") + + if(charge_sound) + charge_sound_instance = sound(charge_sound, channel = CHANNEL_CHARGED_SPELL) + + if(charge_overlay_icon && charge_overlay_state) + charge_overlay_instance = mutable_appearance(charge_overlay_icon, charge_overlay_state, EFFECTS_LAYER) + -/datum/action/cooldown/spell/touch/psionic/can_cast_spell(feedback) +/datum/action/cooldown/spell/touch/psionic/Destroy() + if(owner) + stop_channel_effect(owner) + + charge_overlay_instance = null + charge_sound_instance = null + return ..() + +/datum/action/cooldown/spell/touch/psionic/Remove(mob/living/remove_from) + stop_channel_effect(remove_from) + return ..() + +/datum/action/cooldown/spell/touch/psionic/is_action_active(atom/movable/screen/movable/action_button/current_button) + return currently_channeling + +/datum/action/cooldown/spell/touch/psionic/can_cast_spell(feedback = TRUE) . = ..() if(!.) return FALSE - + if(currently_channeling) + if(feedback) + to_chat(owner, span_warning("You're already channeling [src]!")) + return FALSE if(!check_for_mana()) return FALSE - else - return TRUE + return TRUE + + +/datum/action/cooldown/spell/touch/psionic/before_cast(atom/cast_on) + . = ..() + if(. & SPELL_CANCEL_CAST) + return + if(!check_for_mana()) + return SPELL_CANCEL_CAST + to_chat(owner, channel_message) + + if(charge_sound_instance) + playsound(owner, charge_sound_instance, 50, FALSE) + + if(charge_overlay_instance) + owner.add_overlay(charge_overlay_instance) + + currently_channeling = TRUE + build_all_button_icons(UPDATE_BUTTON_STATUS) + if(!do_after(owner, channel_time, timed_action_flags = channel_flags)) + stop_channel_effect(owner) + return . | SPELL_CANCEL_CAST + +/datum/action/cooldown/spell/touch/psionic/cast(atom/cast_on) + . = ..() + stop_channel_effect(owner) + +/datum/action/cooldown/spell/touch/psionic/proc/stop_channel_effect(mob/for_who) + if(charge_overlay_instance) + for_who.cut_overlay(charge_overlay_instance) + + if(charge_sound_instance) + for_who.stop_sound_channel(CHANNEL_CHARGED_SPELL) + playsound(for_who, sound(null, repeat = 0, channel = CHANNEL_CHARGED_SPELL), 50, FALSE) + + currently_channeling = FALSE + build_all_button_icons(UPDATE_BUTTON_STATUS) /datum/action/cooldown/spell/touch/psionic/create_hand(mob/living/carbon/cast_on) . = ..() if(!.) return . - var/obj/item/bodypart/transfer_limb = cast_on.get_active_hand() - if(IS_ROBOTIC_LIMB(transfer_limb)) - to_chat(cast_on, span_notice("You fail to channel your psionic powers through your inorganic hand.")) - return FALSE - return TRUE /particles/droplets/psionic diff --git a/tff_modular/modules/psionics/code/_psionic_datum.dm b/tff_modular/modules/psionics/code/_psionic_datum.dm index 73ace652575..cc4cb1659f2 100644 --- a/tff_modular/modules/psionics/code/_psionic_datum.dm +++ b/tff_modular/modules/psionics/code/_psionic_datum.dm @@ -47,7 +47,7 @@ /mob/living/proc/get_psionic() if(!psi_sensivity) return FALSE - return psi_sensivity + return psi_sensivity || TRUE /datum/psionic // Текущий владелец псионики @@ -69,9 +69,12 @@ var/open_backpack = FALSE // Магазин var/datum/psionic_shop/psi_shop_datum + // Включалка магазина var/datum/action/psionic_shop/psi_shop_action // Список заклинаний var/list/datum/action/cooldown/spell/learned_spells = list() + // Эта переменная, если равна FALSE, не позволяет как-либо усилить псионика. + var/zona_bovinae = TRUE /datum/psionic/proc/apply_to(mob/living/granted_to) if(!granted_to) @@ -128,7 +131,7 @@ for(var/datum/action/cooldown/spell/spells_to_remove in psi_owner.actions) if(!spells_to_remove.psionic) continue - spells_to_remove.Remove() + spells_to_remove.Remove(psi_owner) if(psi_owner.hud_used) var/datum/hud/psi_hud = psi_owner.hud_used @@ -145,7 +148,9 @@ if(mana_level <= 0) psi_owner.adjust_stamina_loss(200) psi_owner.SetStun(5 SECONDS) - + psi_owner.apply_status_effect(/datum/status_effect/psionic_exhaustion) + update_hud() + return FALSE var/delta_time = DELTA_WORLD_TIME(SSmobs) var/mob/living/carbon/human/human_holder = psi_owner var/additional_mana = 1 @@ -153,7 +158,7 @@ if(psi_owner.has_status_effect(/datum/status_effect/drugginess)) // Наркота даёт бафф к генерации маны additional_mana *= 1.5 if(HAS_TRAIT(psi_owner, TRAIT_PSIONIC_IMPLANT)) // Если есть имплант для увеличения регена маны - additional_mana *= 2 + additional_mana *= 1.5 if(human_holder.is_blind()) additional_mana *= 1.5 adjust_psi_energy((1 * additional_mana) * delta_time) @@ -179,7 +184,9 @@ psi_point = 10 /datum/psionic/proc/is_suppressed() - if(HAS_TRAIT(psi_owner, TRAIT_PSI_SUPPRESSED)) + if(HAS_TRAIT(psi_owner, TRAIT_PSIONIC_EXHAUSTION)) + return TRUE + if(HAS_TRAIT(psi_owner, TRAIT_PSIONIC_SUPPRESSED)) return TRUE return FALSE @@ -205,9 +212,8 @@ /datum/psionic/proc/research_spell(datum/action/cooldown/spell/spell_path) if(!ispath(spell_path, /datum/action/cooldown/spell)) CRASH("Psionic research_spell attempted to purchase an invalid typepath! (got: [spell_path])") - if(spell_path.psionic_level > get_level()) - to_chat(psi_owner, span_horizonblue("We have not enough psi power to get this spell!")) + to_chat(psi_owner, span_horizonblue("We have not enough psi rank to get this spell!")) return FALSE if(learned_spells[spell_path]) to_chat(psi_owner, span_horizonblue("We have already researched this spell!")) @@ -215,6 +221,7 @@ if(psi_point < initial(spell_path.point_cost)) to_chat(psi_owner, span_horizonblue("We cant research this spell now!")) return FALSE + psi_owner.playsound_local(psi_owner.loc, 'tff_modular/modules/psionics/sounds/power_evoke.ogg', 50, TRUE) var/success = give_spell(spell_path) if(success) @@ -225,8 +232,8 @@ var/datum/action/cooldown/spell/new_action = new spell_path() if(!new_action) - to_chat(psi_owner, "This is awkward. Psionic power purchase failed, please report this bug to a coder!") - CRASH("Psionic give_spell was unable to grant a new changeling action for path [spell_path]!") + to_chat(psi_owner, "This is awkward. Psionic spell research failed, please report this bug to a coder!") + CRASH("Psionic give_spell was unable to grant a new psionic action for path [spell_path]!") learned_spells[spell_path] = new_action new_action.Grant(psi_owner) @@ -236,5 +243,22 @@ /datum/psionic/proc/get_level() return psionic_level +/datum/status_effect/psionic_exhaustion + id = "psionic_exhaustion" + duration = 15 SECONDS + alert_type = null + var/icon/instabilityicon + +/datum/status_effect/psionic_exhaustion/on_apply() + . = ..() + instabilityicon = icon('tff_modular/modules/psionics/icons/spells.dmi', "instability") + owner.add_overlay(instabilityicon) + ADD_TRAIT(owner, TRAIT_PSIONIC_EXHAUSTION, PSIONIC_TRAIT) + +/datum/status_effect/psionic_exhaustion/on_remove() + . = ..() + REMOVE_TRAIT(owner, TRAIT_PSIONIC_EXHAUSTION, PSIONIC_TRAIT) + owner.cut_overlay(instabilityicon) + #undef SENSITIVE_PSIONIC #undef HARMONIOUS_PSIONIC diff --git a/tff_modular/modules/psionics/code/conjure_item/conjure_item.dm b/tff_modular/modules/psionics/code/conjure_item/conjure_item.dm index c2039ec6a3f..b74a5ee2b8d 100644 --- a/tff_modular/modules/psionics/code/conjure_item/conjure_item.dm +++ b/tff_modular/modules/psionics/code/conjure_item/conjure_item.dm @@ -12,12 +12,6 @@ category = "Tier 2" locked = FALSE -/datum/action/cooldown/spell/conjure_item/psionic/psiblade/make_item(atom/caster) - var/obj/item/summoning_obj = item_type - summoning_obj.force = 15 * cast_power - summoning_obj.block_chance = 25 * cast_power - return ..() - /obj/item/melee/psionic_blade name = "psionic blade" desc = "A concentrated collection of particles and energy that looks like a swords blade.." @@ -27,13 +21,14 @@ lefthand_file = 'icons/mob/inhands/weapons/swords_lefthand.dmi' righthand_file = 'icons/mob/inhands/weapons/swords_righthand.dmi' w_class = WEIGHT_CLASS_HUGE - force = 15 + force = 30 + armour_penetration = 30 throwforce = 10 hitsound = 'sound/items/weapons/blade1.ogg' attack_verb_continuous = list("attacks", "slashes", "stabs", "slices", "tears", "lacerates", "rips", "dices", "cuts") attack_verb_simple = list("attack", "slash", "stab", "slice", "tear", "lacerate", "rip", "dice", "cut") sharpness = SHARP_EDGED - block_chance = 25 + block_chance = 50 item_flags = DROPDEL | ABSTRACT | HAND_ITEM color = COLOR_BRIGHT_BLUE @@ -45,7 +40,7 @@ /datum/action/cooldown/spell/conjure_item/psionic/psitool name = "Psionic tool" desc = "Concentrates psionic energy to create a universal tool." - button_icon = 'icons/obj/antags/abductor.dmi' + button_icon = 'tff_modular/modules/psionics/icons/psi_items.dmi' button_icon_state = "omnitool" cooldown_time = 60 SECONDS item_type = /obj/item/psionic_omnitool diff --git a/tff_modular/modules/psionics/code/cyberimp.dm b/tff_modular/modules/psionics/code/cyberimp.dm index d8bc90d3936..79f173b2c6f 100644 --- a/tff_modular/modules/psionics/code/cyberimp.dm +++ b/tff_modular/modules/psionics/code/cyberimp.dm @@ -9,11 +9,11 @@ /obj/item/organ/internal/cyberimp/brain/anti_psionic/on_mob_insert(mob/living/carbon/organ_owner, special, movement_flags) . = ..() - ADD_TRAIT(organ_owner, TRAIT_PSI_SUPPRESSED, IMPLANT_TRAIT) + ADD_TRAIT(organ_owner, TRAIT_PSIONIC_SUPPRESSED, IMPLANT_TRAIT) /obj/item/organ/internal/cyberimp/brain/anti_psionic/on_mob_remove(mob/living/carbon/organ_owner, special) . = ..() - REMOVE_TRAIT(organ_owner, TRAIT_PSI_SUPPRESSED, IMPLANT_TRAIT) + REMOVE_TRAIT(organ_owner, TRAIT_PSIONIC_SUPPRESSED, IMPLANT_TRAIT) // Увеличивает реген маны в 2 раза /obj/item/organ/internal/cyberimp/brain/pro_psionic diff --git a/tff_modular/modules/psionics/code/hud.dm b/tff_modular/modules/psionics/code/hud.dm index 75fe5050e0a..6006f23a493 100644 --- a/tff_modular/modules/psionics/code/hud.dm +++ b/tff_modular/modules/psionics/code/hud.dm @@ -34,7 +34,7 @@ msg += span_horizonblue("Your current maximum is: [psi_datum.max_mana].") - msg += span_cult_large("\nRight now, you are feeling [psylevel].") + msg += span_big(span_horizonblue("\nRight now, you are feeling [psylevel].")) to_chat(usr, boxed_message(msg.Join("\n"))) @@ -55,20 +55,19 @@ msg += span_horizonblue("This is your Psionic Signal.") msg += span_horizonblue("This signal allows the psionics to sense each other. When there is a psionic nearby, this signal starts to glow blue.") msg += span_horizonblue("If your psionic being suppressed, you can't sense the psionics nearby, but they can't sense you either.") - msg += span_horizonblue("\nSome extremely powerful psionics are able to partially suppress their signal.") + msg += span_horizonblue("\nSome psionics are able to suppress their signal.") to_chat(usr, boxed_message(msg.Join("\n"))) /datum/psionic/proc/update_hud() var/psi_energy_color var/psi_energy_icon_state - switch(mana_level) - if(-100 to 0) - psi_energy_color = "#480607" - psi_energy_icon_state = "psi_suppressed" - if(1 to INFINITY) - psi_energy_color = "#00BFFF" - psi_energy_icon_state = "psi_active" + if(HAS_TRAIT(psi_owner, TRAIT_PSIONIC_EXHAUSTION) || HAS_TRAIT(psi_owner, TRAIT_PSIONIC_SUPPRESSED)) + psi_energy_color = "#480607" + psi_energy_icon_state = "psi_suppressed" + else + psi_energy_color = "#00BFFF" + psi_energy_icon_state = "psi_active" var/atom/movable/screen/psionic/psionic_energy/psi_display = psi_owner?.hud_used?.screen_objects[HUD_PSI_DISPLAY] psi_display?.maptext = FORMAT_PSI_HUD_TEXT(psi_energy_color, mana_level) diff --git a/tff_modular/modules/psionics/code/loner.dm b/tff_modular/modules/psionics/code/loner.dm index b2bbf6e286a..3c128ad3e6c 100644 --- a/tff_modular/modules/psionics/code/loner.dm +++ b/tff_modular/modules/psionics/code/loner.dm @@ -2,10 +2,21 @@ name = "\improper Loner" antagpanel_category = ANTAG_GROUP_SYNDICATE show_in_antagpanel = TRUE + stinger_sound = 'tff_modular/modules/psionics/sounds/power_unlock.ogg' var/datum/psionic/psi + var/previosly_psionic = FALSE /datum/antagonist/loner/apply_innate_effects(mob/living/mob_override) . = ..() var/mob/living/carbon/current_owner = mob_override || owner.current + if(current_owner.get_psionic()) + current_owner.remove_psionic() + previosly_psionic = TRUE current_owner.add_psionic(/datum/psionic/harmonious) - ADD_TRAIT(current_owner, TRAIT_IMMUNE_TO_PSI_SUPPRESSION, PSIONIC_TRAIT) + +/datum/antagonist/loner/remove_innate_effects(mob/living/mob_override) + . = ..() + var/mob/living/carbon/current_owner = mob_override || owner.current + current_owner.remove_psionic() + if(previosly_psionic) + current_owner.add_psionic(/datum/psionic/sensitive) diff --git a/tff_modular/modules/psionics/code/pointed/pointed.dm b/tff_modular/modules/psionics/code/pointed/pointed.dm index aed2d87cbe9..dc9a24b0f2f 100644 --- a/tff_modular/modules/psionics/code/pointed/pointed.dm +++ b/tff_modular/modules/psionics/code/pointed/pointed.dm @@ -4,8 +4,7 @@ /datum/action/cooldown/spell/pointed/psionic/spasm name = "Psionic Spasm" desc = "Force a target to drop the items in its hands. Note that this has a hefty power use and cooldown." - button_icon = 'tff_modular/modules/psionics/icons/actions.dmi' - button_icon_state = "spasm" + button_icon_state = "genetics_closed" cooldown_time = 100 SECONDS mana_cost = 20 psionic_level = 2 @@ -33,17 +32,17 @@ log_combat(owner, cast_on, "psionically spasmed") cast_on.Stun(1 SECONDS * cast_power) drain_mana() + playsound(cast_on, 'tff_modular/modules/psionics/sounds/power_evoke.ogg', 50, TRUE) return TRUE // Мед сканер на расстоянии /datum/action/cooldown/spell/pointed/psionic/skinsight name = "Skinsight" desc = "Try to read target's vital energy and determine their state." - button_icon = 'tff_modular/modules/psionics/icons/actions.dmi' - button_icon_state = "roentgen" + button_icon_state = "wiz_blind" cooldown_time = 1 SECONDS point_cost = 0 - mana_cost = 10 + mana_cost = 5 target_msg = "You feel like someone is looking deep into you." active_msg = "You prepare to scan a target..." locked = FALSE @@ -65,56 +64,64 @@ else healthscan(owner, cast_on, SCANNER_VERBOSE, FALSE, tochat = TRUE) drain_mana() + playsound(cast_on, 'tff_modular/modules/psionics/sounds/power_used.ogg', 50, TRUE) return TRUE /datum/action/cooldown/spell/pointed/psionic/jump name = "Psionic Jump" desc = "Teleport to a destination you click on." - button_icon_state = "warp_strike" - cooldown_time = 15 SECONDS + button_icon_state = "tech_dispel" + cooldown_time = 30 SECONDS psionic_level = 2 mana_cost = 20 point_cost = 2 locked = FALSE category = "Tier 2" -/datum/action/cooldown/spell/pointed/psionic/jump/is_valid_target(atom/cast_on) +/datum/action/cooldown/spell/pointed/psionic/jump/can_cast_spell(feedback) . = ..() + if(HAS_TRAIT(owner, TRAIT_NO_TRANSFORM)) + return FALSE + return TRUE + +/datum/action/cooldown/spell/pointed/psionic/jump/is_valid_target(atom/cast_on) if(isclosedturf(cast_on)) return FALSE if(isobj(cast_on)) return FALSE + if(!(cast_on in view(owner.client.view, owner))) + owner.balloon_alert(owner, "out of view!") + return FALSE return TRUE /datum/action/cooldown/spell/pointed/psionic/jump/cast(atom/cast_on) . = ..() - if(isliving(cast_on)) - do_teleport(owner, cast_on.loc, effectin = /obj/effect/temp_visual/wizard, effectout = /obj/effect/temp_visual/wizard/out) - else if(isturf(cast_on)) - do_teleport(owner, cast_on.loc, effectin = /obj/effect/temp_visual/wizard, effectout = /obj/effect/temp_visual/wizard/out) + do_teleport(owner, get_turf(cast_on), 1, /obj/effect/temp_visual/dir_setting/ninja, /obj/effect/temp_visual/dir_setting/ninja, 'tff_modular/modules/psionics/sounds/power_fabrication.ogg', channel = TELEPORT_CHANNEL_MAGIC) + drain_mana() return TRUE /datum/action/cooldown/spell/pointed/psionic/emotional_suggestion name = "Psionic Emotional Suggestion" desc = "Allows you to psionically commune with the target using emotions." + button_icon_state = "tech_gambit" cooldown_time = 2 SECONDS mana_cost = 5 point_cost = 0 locked = FALSE +/datum/action/cooldown/spell/pointed/psionic/is_valid_target(atom/cast_on) + if(!iscarbon(cast_on)) + return FALSE + return TRUE + /datum/action/cooldown/spell/pointed/psionic/emotional_suggestion/cast(atom/cast_on) . = ..() - if(iscarbon(cast_on)) - emotional_suggestion(cast_on, owner) - return TRUE - else - return FALSE + emotional_suggestion(cast_on, owner) + drain_mana() + return TRUE /datum/action/cooldown/spell/pointed/psionic/emotional_suggestion/proc/emotional_suggestion(atom/hit_atom, mob/living/user) - if(!isliving(hit_atom)) - return - - var/mob/living/carbon/target = hit_atom + var/mob/living/target = hit_atom if(target.stat == DEAD) to_chat(user, span_warning("Not even a psion of your level can suggest to the dead.")) return @@ -125,10 +132,6 @@ text = lowertext(text) - if(target.stat == DEAD) - to_chat(user, span_warning("Not even a psion of your level can suggest to the dead.")) - return - log_say("[key_name(user)] suggested an emotion to [key_name(target)]: [text]") to_chat(user, span_horizonblue("You psionically suggest an emotion to [target]: [text]")) @@ -140,12 +143,14 @@ to_chat(H, span_notice("You sense [user]'s psyche link with your own, and an emotion of [text] washes through your mind.")) else to_chat(H, span_notice("An emotion from outside your consciousness slips into your mind: [text].")) + + playsound(H, 'tff_modular/modules/psionics/sounds/power_used.ogg', 50, TRUE) return TRUE /datum/action/cooldown/spell/pointed/psionic/rejuvenate - name = "Rejuvenate" + name = "Psionic Rejuvenate" desc = "Restore a creature's blood and tried to and try to revive it." - button_icon_state = "mend_all" + button_icon_state = "tech_resurrect" cast_range = 3 point_cost = 3 mana_cost = 80 @@ -154,58 +159,24 @@ category = "Tier 2" /datum/action/cooldown/spell/pointed/psionic/rejuvenate/is_valid_target(atom/cast_on) - . = ..() - if(!isliving(cast_on)) - return FALSE - if(!isdead(cast_on)) - return FALSE - var/mob/living/carbon/reviving = cast_on - if(HAS_TRAIT(reviving, TRAIT_DNR)) - owner.balloon_alert(owner, "no soul!") - return FALSE - if(!reviving.get_organ_slot(ORGAN_SLOT_BRAIN)) - owner.balloon_alert(owner, "no brain!") + if(iscarbon(cast_on)) + var/mob/living/carbon/human = cast_on + if(human.stat == DEAD) + return TRUE return FALSE - return TRUE + return FALSE /datum/action/cooldown/spell/pointed/psionic/rejuvenate/cast(atom/cast_on) . = ..() - if(isbasicmob(cast_on)) - var/mob/living/basic/basic_living = cast_on - for(var/i in 1 to 3) - if (!do_after(owner, 3 SECONDS, basic_living, timed_action_flags = IGNORE_INCAPACITATED | IGNORE_USER_LOC_CHANGE | IGNORE_TARGET_LOC_CHANGE)) - return FALSE - - basic_living.heal_overall_damage(30, 30) - - playsound(basic_living, 'sound/effects/singlebeat.ogg', vol = 50, vary = TRUE, ignore_walls = FALSE) - - var/original_transform = basic_living.transform - animate(basic_living, transform = basic_living.transform.Translate(0, 3), time = 0.2 SECONDS, easing = CUBIC_EASING | EASE_OUT, flags = ANIMATION_PARALLEL) - animate(transform = original_transform, time = 0.2 SECONDS, easing = CUBIC_EASING | EASE_IN, flags = ANIMATION_PARALLEL) - - basic_living.visible_message( - message = span_danger("\The [basic_living] shake[basic_living.p_their()] violently!"), - ignored_mobs = owner - ) - - if(!basic_living.revive()) - owner.balloon_alert(owner, "revival failed!") - return FALSE - - drain_mana() - to_chat(owner, span_green("You successfully revive \the [owner]!")) - return TRUE - - else if(iscarbon(cast_on)) + if(iscarbon(cast_on)) var/mob/living/carbon/carbon_living = cast_on for(var/i in 1 to 3) - if (!do_after(owner, 5 SECONDS, carbon_living, timed_action_flags = IGNORE_INCAPACITATED | IGNORE_USER_LOC_CHANGE | IGNORE_TARGET_LOC_CHANGE)) + if(!do_after(owner, 5 SECONDS, carbon_living, timed_action_flags = IGNORE_USER_LOC_CHANGE | IGNORE_TARGET_LOC_CHANGE)) return FALSE carbon_living.heal_overall_damage(30, 30) playsound(carbon_living, 'sound/effects/singlebeat.ogg', vol = 50, vary = TRUE, ignore_walls = FALSE) - + playsound(carbon_living, 'tff_modular/modules/psionics/sounds/power_used.ogg', 50, TRUE) var/original_transform = carbon_living.transform animate(carbon_living, transform = carbon_living.transform.Translate(0, 3), time = 0.2 SECONDS, easing = CUBIC_EASING | EASE_OUT, flags = ANIMATION_PARALLEL) animate(transform = original_transform, time = 0.2 SECONDS, easing = CUBIC_EASING | EASE_IN, flags = ANIMATION_PARALLEL) @@ -223,14 +194,17 @@ owner.balloon_alert(owner, "revival failed!") return FALSE - to_chat(owner, span_green("You successfully revive \the [owner]!")) + to_chat(owner, span_horizonblue("You successfully revive \the [owner]!")) drain_mana() - return TRUE + else + return FALSE + return TRUE /datum/action/cooldown/spell/pointed/psionic/stasis - name = "Stasis" + name = "Psionic Stasis" desc = "Condenses the Nlom field around one person at a time. This immobilises them and also applies stasis to them." - button_icon_state = "condensation" + button_icon_state = "gen_ice" + cooldown_time = 60 SECONDS point_cost = 1 psionic_level = 2 mana_cost = 30 @@ -238,7 +212,6 @@ category = "Tier 2" /datum/action/cooldown/spell/pointed/psionic/stasis/is_valid_target(atom/cast_on) - . = ..() if(!isliving(cast_on)) return FALSE return TRUE @@ -246,20 +219,26 @@ /datum/action/cooldown/spell/pointed/psionic/stasis/cast(atom/cast_on) . = ..() var/mob/living/freezing = cast_on + if(!do_after(owner, 2 SECONDS, freezing, IGNORE_TARGET_LOC_CHANGE | IGNORE_USER_LOC_CHANGE | IGNORE_SLOWDOWNS | IGNORE_HELD_ITEM)) + return FALSE var/duration = cast_power * 4 SECONDS - freezing.set_timed_status_effect(duration, /datum/status_effect/freon/watcher) + freezing.apply_status_effect(/datum/status_effect/freon/watcher/psionic, duration) + playsound(freezing, 'tff_modular/modules/psionics/sounds/power_evoke.ogg', 50, TRUE) + +/datum/status_effect/freon/watcher/psionic/on_creation(mob/living/new_owner, new_duration) + . = ..() + duration = new_duration /datum/action/cooldown/spell/pointed/psionic/bubble name = "Psionic Bubble" desc = "Create a protective bubble around you or target that removes your need to breathe or wear space protection!" - button_icon_state = "shield" + button_icon_state = "tech_condensation" point_cost = 1 cooldown_time = 30 SECONDS mana_cost = 10 locked = FALSE /datum/action/cooldown/spell/pointed/psionic/bubble/is_valid_target(atom/cast_on) - . = ..() if(!isliving(cast_on)) return FALSE return TRUE @@ -268,26 +247,415 @@ . = ..() var/mob/living/living_living = cast_on var/duration = cast_power * 15 SECONDS - living_living.set_timed_status_effect(duration, /datum/status_effect/psi_bubble) + living_living.apply_status_effect(/datum/status_effect/psi_bubble, duration) + playsound(cast_on, 'tff_modular/modules/psionics/sounds/power_used.ogg', 50, TRUE) + return TRUE /datum/status_effect/psi_bubble id = "psi_bubble" - alert_type = /atom/movable/screen/alert/psi_bubble + alert_type = /atom/movable/screen/alert/status_effect/psi_bubble + tick_interval = STATUS_EFFECT_AUTO_TICK + processing_speed = STATUS_EFFECT_NORMAL_PROCESS duration = 15 SECONDS + show_duration = TRUE var/icon/bubbleicon +/datum/status_effect/psi_bubble/on_creation(mob/living/new_owner, set_duration) + if(isnum(set_duration)) + duration = set_duration + return ..() + /datum/status_effect/psi_bubble/on_apply() . = ..() - bubbleicon = icon(icon = 'icons/effects/effects.dmi', icon_state = "shield2") + bubbleicon = icon(icon = 'icons/effects/effects.dmi', icon_state = "bubbles") owner.add_overlay(bubbleicon) - owner.add_traits(list(TRAIT_OXYIMMUNE, TRAIT_RESISTLOWPRESSURE, TRAIT_RESISTCOLD)) + owner.add_traits(list(TRAIT_OXYIMMUNE, TRAIT_RESISTLOWPRESSURE, TRAIT_RESISTCOLD), PSIONIC_TRAIT) + RegisterSignal(owner, COMSIG_ATOM_EXAMINE, PROC_REF(on_examine)) + return TRUE /datum/status_effect/psi_bubble/on_remove() . = ..() owner.cut_overlay(bubbleicon) + owner.remove_traits(list(TRAIT_OXYIMMUNE, TRAIT_RESISTLOWPRESSURE, TRAIT_RESISTCOLD), PSIONIC_TRAIT) + UnregisterSignal(owner, COMSIG_ATOM_EXAMINE) + return TRUE + +/datum/status_effect/psi_bubble/proc/on_examine(atom/source, mob/user, list/examine_list) + SIGNAL_HANDLER + + examine_list += span_horizonblue("[source.p_Theyre()] covered with strange bubbles!") -/atom/movable/screen/alert/psi_bubble - icon = 'icons/effects/effects.dmi' - icon_state = "shield2" +/atom/movable/screen/alert/status_effect/psi_bubble name = "Air Bubble" desc = "There is a protective bubble around you that removes your need to breathe or wear space protection!" + overlay_icon = 'icons/effects/effects.dmi' + overlay_state = "shield2" + +/datum/action/cooldown/spell/pointed/psionic/barrier + name = "Barrier" + desc = "Give yourself or a target psionic armour." + button_icon_state = "tech_frostaura" + category = "Tier 2" + cooldown_time = 60 SECONDS + psionic_level = 2 + point_cost = 1 + mana_cost = 30 + locked = FALSE + +/datum/action/cooldown/spell/pointed/psionic/barrier/is_valid_target(atom/cast_on) + . = ..() + if(!ishuman(cast_on)) + return FALSE + +/datum/action/cooldown/spell/pointed/psionic/barrier/cast(atom/cast_on) + . = ..() + var/mob/living/carbon/human/artificer = cast_on + var/duration = 20 SECONDS * cast_power + artificer.apply_status_effect(/datum/status_effect/psionic_armour, duration) + playsound(artificer, 'tff_modular/modules/psionics/sounds/power_used.ogg', 50, TRUE) + return TRUE + +/datum/status_effect/psionic_armour + id = "psionic_armour" + duration = 20 SECONDS + alert_type = /atom/movable/screen/alert/status_effect/psionic_armour + show_duration = TRUE + +/datum/status_effect/psionic_armour/on_creation(mob/living/new_owner, new_duration) + . = ..() + duration = new_duration + +/datum/status_effect/psionic_armour/on_apply() + . = ..() + var/mob/living/carbon/human/affected = owner + ADD_TRAIT(affected, TRAIT_HARDLY_WOUNDED, PSIONIC_TRAIT) + affected.physiology.brute_mod *= 0.75 + affected.physiology.burn_mod *= 0.75 + affected.physiology.stamina_mod *= 0.25 + return TRUE + +/datum/status_effect/psionic_armour/on_remove() + . = ..() + var/mob/living/carbon/human/affected = owner + REMOVE_TRAIT(affected, TRAIT_HARDLY_WOUNDED, PSIONIC_TRAIT) + affected.physiology.brute_mod /= 0.75 + affected.physiology.burn_mod /= 0.75 + affected.physiology.stamina_mod /= 0.25 + return TRUE + +/atom/movable/screen/alert/status_effect/psionic_armour + name = "Psionic Armour" + desc = "You covered with Psi Armour, and any damage you receive is reduced!" + overlay_icon = 'tff_modular/modules/psionics/icons/spells.dmi' + overlay_state = "tech_frostaura" + +/datum/action/cooldown/spell/pointed/psionic/psi_throw + name = "Psionic Throw" + desc = "Throw an object in the opposite direction from yourself. Works on living beings." + button_icon_state = "wiz_mm" + category = "Tier 2" + cooldown_time = 30 SECONDS + psionic_level = 2 + point_cost = 2 + mana_cost = 20 + locked = FALSE + +/datum/action/cooldown/spell/pointed/psionic/psi_throw/is_valid_target(atom/cast_on) + if(cast_on == owner) + return FALSE + if(isobj(cast_on) || isliving(cast_on)) + var/atom/movable/AM = cast_on + if(AM.anchored) + return FALSE + return TRUE + return FALSE + +/datum/action/cooldown/spell/pointed/psionic/psi_throw/cast(atom/cast_on) + . = ..() + var/turf/throwtarget = get_edge_target_turf(owner, get_dir(owner, get_step_away(cast_on, owner))) + var/dist_from_caster = get_dist(cast_on, owner) + if(dist_from_caster == 0) + if(isliving(cast_on)) + var/mob/living/victim = cast_on + victim.Paralyze(10 SECONDS) + victim.adjust_brute_loss(5) + to_chat(victim, span_userdanger("You're psionically slammed into the floor by [owner]!")) + else + if(isliving(cast_on)) + var/mob/living/victim = cast_on + victim.Paralyze(2 SECONDS) + to_chat(victim, span_userdanger("You're psionically thrown back by [owner]!")) + + var/atom/movable/to_throw = cast_on + to_throw.safe_throw_at( + target = throwtarget, + range = 4 * cast_power, + speed = 2, + thrower = owner, + force = MOVE_FORCE_STRONG, + ) + + playsound(owner, 'tff_modular/modules/psionics/sounds/power_used.ogg', 50, TRUE) + drain_mana() + return TRUE + + +/datum/action/cooldown/spell/pointed/psionic/mind_muddle + name = "Psionic Mind Muddle" + desc = "Use this at range to confuse a target and give them a little bit of pain." + button_icon_state = "wiz_tele" + cooldown_time = 20 SECONDS + psionic_level = 1 + point_cost = 2 + mana_cost = 10 + locked = FALSE + +/datum/action/cooldown/spell/pointed/psionic/mind_muddle/is_valid_target(atom/cast_on) + if(!ishuman(cast_on)) + return FALSE + return TRUE + +/datum/action/cooldown/spell/pointed/psionic/mind_muddle/cast(atom/cast_on) + . = ..() + var/mob/living/carbon/human/victim = cast_on + if(!do_after(owner, 2 SECONDS, victim, IGNORE_TARGET_LOC_CHANGE | IGNORE_USER_LOC_CHANGE | IGNORE_SLOWDOWNS | IGNORE_HELD_ITEM)) + return FALSE + victim.adjust_stamina_loss(20 * cast_power) + victim.adjust_confusion(3 SECONDS * cast_power) + playsound(victim, 'tff_modular/modules/psionics/sounds/power_used.ogg', 50, TRUE) + return TRUE + +/datum/action/cooldown/spell/pointed/psionic/drain + name = "Psionic Drain" + desc = "Drain psi-stamina from a living being." + button_icon_state = "gen_project" + cooldown_time = 10 SECONDS + psionic_level = 1 + point_cost = 2 + mana_cost = 0 + locked = FALSE + cast_range = 3 + +/datum/action/cooldown/spell/pointed/psionic/drain/is_valid_target(atom/cast_on) + if(!ishuman(cast_on)) + return FALSE + return TRUE + +/datum/action/cooldown/spell/pointed/psionic/drain/cast(atom/cast_on) + . = ..() + drain_psi_stamina(cast_on) + to_chat(cast_on, span_horizonblue("You begin to feel weak...")) + +/datum/action/cooldown/spell/pointed/psionic/drain/proc/drain_psi_stamina(atom/cast_on) + var/mob/living/carbon/human/victim = cast_on + if(!do_after(owner, 1 SECONDS, victim, IGNORE_TARGET_LOC_CHANGE)) + return FALSE + victim.adjust_stamina_loss(10) + psionic_datum.adjust_psi_energy(10) + to_chat(victim, span_horizonblue("You're getting worse...")) + playsound(victim, 'tff_modular/modules/psionics/sounds/power_used.ogg', 50, TRUE) + drain_psi_stamina(victim) + +/datum/action/cooldown/spell/pointed/psionic/warp + name = "Psionic Warp" + desc = "Warp through objects." + button_icon_state = "tech_blink" + category = "Tier 2" + cooldown_time = 60 SECONDS + psionic_level = 2 + point_cost = 2 + mana_cost = 30 + locked = FALSE + cast_range = 2 + var/turf/target_turf + +/datum/action/cooldown/spell/pointed/psionic/warp/is_valid_target(atom/cast_on) + . = ..() + var/turf/closed/wall/turf_we_check = cast_on + if(!iswallturf(turf_we_check)) + to_chat(owner, span_horizonblue("Target must be a wall!")) + return FALSE + if(!turf_we_check.density) + return FALSE + return TRUE + +/datum/action/cooldown/spell/pointed/psionic/warp/cast(atom/cast_on) + . = ..() + var/turf/closed/wall/density_object = cast_on + density_object.warp() + drain_mana() + +/turf/closed/wall + var/warped = FALSE + +/turf/closed/wall/proc/warp() + density = 0 + animate(src, alpha = 75, time = 2 SECONDS) + warped = TRUE + RegisterSignal(src, COMSIG_ATOM_EXAMINE, PROC_REF(on_examine)) + +/turf/closed/wall/proc/unwarp() + density = initial(density) + animate(src, alpha = initial(alpha), time = 2 SECONDS) + warped = FALSE + UnregisterSignal(src, COMSIG_ATOM_EXAMINE) + +/turf/closed/wall/attack_hand(mob/user, list/modifiers) + . = ..() + if(warped) + to_chat(user, span_horizonblue("The wall begins to return to its condition... For some reason.")) + unwarp() + +/turf/closed/wall/proc/on_examine(datum/source, mob/user, text) + SIGNAL_HANDLER + text += span_horizonblue("This wall... Doesn't exist?") + +/datum/action/cooldown/spell/pointed/psionic/pull + name = "Psionic Pull" + desc = "Pulls the target straight towards the user. Even if the item is big, it's cant harm you on impact. Note that you can catch items you pull to yourself if you toggle throw mode before pulling an item." + button_icon_state = "tech_passwall" + category = "Tier 2" + cooldown_time = 60 SECONDS + psionic_level = 2 + point_cost = 1 + mana_cost = 30 + locked = FALSE + cast_range = 5 + +/datum/action/cooldown/spell/pointed/psionic/pull/is_valid_target(atom/cast_on) + if(cast_on == owner) + return FALSE + if(isobj(cast_on) || isliving(cast_on)) + var/atom/movable/AM = cast_on + if(AM.anchored) + return FALSE + return TRUE + return FALSE + +/datum/action/cooldown/spell/pointed/psionic/pull/cast(atom/cast_on) + . = ..() + var/atom/movable/AM = cast_on + var/mob/living/carbon/human/user = owner + if(isobj(cast_on)) + var/obj/object = cast_on + if(object.anchored) + to_chat(user, span_warning("That object cant be moved!")) + return + user.visible_message(span_warning("[user] extends [user.p_their()] hand at [cast_on] and pulls!"), span_warning("You mimic pulling at [cast_on]!")) + if(ismob(cast_on)) + to_chat(cast_on, span_warning("A psychic force pulls you!")) + AM.safe_throw_at(user, 10, 1, user, gentle = TRUE) + playsound(user, 'tff_modular/modules/psionics/sounds/power_evoke.ogg', 40) + drain_mana() + +/datum/action/cooldown/spell/pointed/psionic/expansion + name = "Psionic Expansion" + desc = "Allows the selected target to see living creatures through walls." + button_icon_state = "gen_rmind" + category = "Tier 2" + cooldown_time = 40 SECONDS + psionic_level = 2 + point_cost = 1 + mana_cost = 10 + locked = FALSE + cast_range = 5 + +/datum/action/cooldown/spell/pointed/psionic/expansion/is_valid_target(atom/cast_on) + if(!isliving(cast_on)) + return FALSE + if(HAS_TRAIT(cast_on, TRAIT_THERMAL_VISION)) + to_chat(cast_on, span_warning("The target doesn't need it!")) + return FALSE + return TRUE + +/datum/action/cooldown/spell/pointed/psionic/expansion/cast(atom/cast_on) + . = ..() + var/mob/living/getting_vision = cast_on + var/new_duration = 15 SECONDS * cast_power + getting_vision.apply_status_effect(/datum/status_effect/thermal_vision, new_duration) + drain_mana() + +/datum/status_effect/thermal_vision + id = "thermal_vision" + duration = 15 SECONDS + show_duration = TRUE + alert_type = null + +/datum/status_effect/thermal_vision/on_creation(mob/living/new_owner, new_duration) + . = ..() + duration = new_duration + +/datum/status_effect/thermal_vision/on_apply() + . = ..() + ADD_TRAIT(owner, TRAIT_THERMAL_VISION, PSIONIC_TRAIT) + owner.update_sight() + +/datum/status_effect/thermal_vision/on_remove() + . = ..() + ADD_TRAIT(owner, TRAIT_THERMAL_VISION, PSIONIC_TRAIT) + owner.update_sight() + +/datum/action/cooldown/spell/pointed/psionic/singularity + name = "Psionic Singularity" + desc = "Creates a psionic illusion. Anyone within four tiles of it is forced to walk towards it. It will dissipate after ten seconds." + button_icon_state = "tech_energysiphon" + category = "Tier 2" + point_cost = 2 + cooldown_time = 40 SECONDS + psionic_level = 2 + mana_cost = 10 + locked = FALSE + cast_range = 3 + +/datum/action/cooldown/spell/pointed/psionic/singularity/is_valid_target(atom/cast_on) + if(isturf(cast_on)) + return TRUE + return FALSE + +/datum/action/cooldown/spell/pointed/psionic/singularity/cast(atom/cast_on) + . = ..() + owner.visible_message(span_horizonblue("[owner] raises his hands up, directing a strange energy to the floor near him..."), span_horizonblue("You're now directing a strange energy, creating Singularity.")) + new /obj/effect/singularity(get_turf(cast_on)) + drain_mana() + return TRUE + +/obj/effect/singularity + anchored = TRUE + name = "Singularity" + desc = "It's so......" + icon = 'icons/obj/machines/engine/singularity.dmi' + icon_state = "dark_matter_s1" + layer = FLY_LAYER + plane = ABOVE_GAME_PLANE + mouse_opacity = MOUSE_OPACITY_TRANSPARENT + var/list/immune = list() + var/hypnorange = 2 + var/duration = 100 + +/obj/effect/singularity/Initialize(mapload, radius, time, list/immune_atoms) + . = ..() + if(!isnull(time)) + duration = time + if(!isnull(radius)) + hypnorange = radius + if(!isnull(immune_atoms)) + for(var/mob/living/A in immune_atoms) + immune += A + START_PROCESSING(SSobj, src) + +/obj/effect/singularity/process(seconds_per_tick) + . = ..() + for(var/mob/living/carbon/victims as anything in view(hypnorange, get_turf(src))) + if(victims.can_block_magic(MAGIC_RESISTANCE_MIND, charge_cost = 0)) + continue + if(victims.Adjacent(src)) + continue + step_towards(victims, src, 1) + + duration -= seconds_per_tick + if(duration <= 0) + qdel(src) + +/obj/effect/singularity/Destroy(force) + . = ..() + STOP_PROCESSING(SSobj, src) diff --git a/tff_modular/modules/psionics/code/projectiles/projectile.dm b/tff_modular/modules/psionics/code/projectiles/projectile.dm index 31790624ac5..bd94a2967d1 100644 --- a/tff_modular/modules/psionics/code/projectiles/projectile.dm +++ b/tff_modular/modules/psionics/code/projectiles/projectile.dm @@ -2,9 +2,9 @@ /datum/action/cooldown/spell/pointed/projectile/psionic/air_bullet name = "Psionic Air Bullet" desc = "Wrap air in a psionic bubble, compress it, then send it flying at your enemies." - button_icon_state = "force_missle" + button_icon_state = "tech_repelmissiles" cooldown_time = 1 SECONDS - mana_cost = 0 + mana_cost = 10 cast_range = 9 active_msg = "You prepare to charge air bullet..." deactive_msg = "You relax." @@ -13,70 +13,70 @@ psionic_level = 1 point_cost = 2 locked = FALSE - var/casting = FALSE - -/datum/action/cooldown/spell/pointed/projectile/psionic/air_bullet/before_cast(atom/cast_on) - . = ..() - if(!casting) - casting = TRUE - if(!do_after(owner, 0.5 SECONDS, timed_action_flags = IGNORE_USER_LOC_CHANGE | IGNORE_SLOWDOWNS)) - return FALSE - psionic_datum.adjust_psi_energy(-10) - else - return FALSE - -/datum/action/cooldown/spell/pointed/projectile/psionic/air_bullet/cast(atom/cast_on) - . = ..() - casting = FALSE /datum/action/cooldown/spell/pointed/projectile/psionic/air_bullet/ready_projectile(obj/projectile/to_fire, atom/target, mob/user, iteration) . = ..() to_fire.damage = 10 * cast_power +/datum/action/cooldown/spell/pointed/projectile/psionic/air_bullet/fire_projectile(atom/target) + . = ..() + drain_mana() + playsound(owner, 'tff_modular/modules/psionics/sounds/power_feedback.ogg', 50, TRUE) + /obj/projectile/magic/air_bullet icon = 'tff_modular/modules/psionics/icons/projectiles.dmi' icon_state = "air_bubble" damage = 10 damage_type = BRUTE -// Стреляет снарядом вотчера, замораживая жертву. Требует почти максимально возможный запас маны -/datum/action/cooldown/spell/pointed/projectile/psionic/freeze - name = "Psionic Freeze" - desc = "Fire freezing shark at a target, encasing them in an ice prison." - button_icon = 'icons/effects/freeze.dmi' - button_icon_state = "ice_cube" - cooldown_time = 1 SECONDS - mana_cost = 20 - cast_range = 9 - active_msg = "You prepare to fire ice shard..." - deactive_msg = "You relax." - projectile_type = /obj/projectile/temp/watcher/psionic_freeze - psionic_level = 1 - point_cost = 1 - category = "Tier 1" - locked = FALSE +/datum/action/cooldown/spell/pointed/projectile/psionic/lighting + name = "Spell Cards" + desc = "Hits living beings in a 4x3 area in front of you with thunders." + button_icon_state = "spellcard" + category = "Tier 2" + click_cd_override = 1 + cooldown_time = 60 SECONDS + psionic_level = 2 + mana_cost = 30 + point_cost = 3 + cast_range = 40 + projectile_type = /obj/projectile/beam/psi_lightning + projectile_amount = 1 + projectiles_per_fire = 5 + /// The turn rate of the spell cards in flight. (They track onto locked on targets) + var/projectile_turnrate = 10 + /// The homing spread of the spell cards in flight. + var/projectile_pixel_homing_spread = 32 + /// The initial spread of the spell cards when fired. + var/projectile_initial_spread_amount = 30 + /// The location spread of the spell cards when fired. + var/projectile_location_spread_amount = 12 -/datum/action/cooldown/spell/pointed/projectile/psionic/freeze/cast(mob/living/cast_on) - drain_mana() - . = ..() - return TRUE -/datum/action/cooldown/spell/pointed/projectile/psionic/freeze/ready_projectile(obj/projectile/to_fire, atom/target, mob/user, iteration) +/datum/action/cooldown/spell/pointed/projectile/psionic/lighting/ready_projectile(obj/projectile/to_fire, atom/target, mob/user, iteration) . = ..() - var/obj/projectile/temp/watcher/psionic_freeze/psi_freeze = to_fire - psi_freeze.power = cast_power + var/rand_spr = rand() + var/total_angle = projectile_initial_spread_amount * 2 + var/adjusted_angle = total_angle - ((projectile_initial_spread_amount / projectiles_per_fire) * 0.5) + var/one_fire_angle = adjusted_angle / projectiles_per_fire + var/current_angle = iteration * one_fire_angle * rand_spr - (projectile_initial_spread_amount / 2) + + to_fire.pixel_x = rand(-projectile_location_spread_amount, projectile_location_spread_amount) + to_fire.pixel_y = rand(-projectile_location_spread_amount, projectile_location_spread_amount) + to_fire.aim_projectile(target, user, null, current_angle) -// Вывел в отдельный тип, потому что в оригинальном ice_wing снаряде видимо баг(?) и он не замораживает, хотя должен. -/obj/projectile/temp/watcher/psionic_freeze - name = "freezing blast" - damage = 0 // Нет дамага, вместо этого замораживает - var/power = 1 +/datum/action/cooldown/spell/pointed/projectile/psionic/lighting/cast(atom/cast_on) + . = ..() + drain_mana() -/obj/projectile/temp/watcher/psionic_freeze/apply_status(mob/living/target) - if(HAS_TRAIT(target, TRAIT_RESISTCOLD)) // Вот тут у ice_wing лишний ! - return - target.set_timed_status_effect(power * 4, /datum/status_effect/freon/watcher/psionic_freeze) +/obj/projectile/beam/psi_lightning + name = "psionic lightning" + damage = 20 + armour_penetration = 30 + damage_type = BURN + pass_flags = PASSTABLE | PASSGRILLE | PASSGLASS + range = 40 -/datum/status_effect/freon/watcher/psionic_freeze - duration = 4 SECONDS - can_melt = TRUE + muzzle_type = /obj/effect/projectile/muzzle/solar + tracer_type = /obj/effect/projectile/muzzle/solar + impact_type = /obj/effect/projectile/muzzle/solar diff --git a/tff_modular/modules/psionics/code/psi_shop/datum.dm b/tff_modular/modules/psionics/code/psi_shop/datum.dm index ba83a9d2cb5..d73aebda6dd 100644 --- a/tff_modular/modules/psionics/code/psi_shop/datum.dm +++ b/tff_modular/modules/psionics/code/psi_shop/datum.dm @@ -56,6 +56,7 @@ "path" = spell_path, "point_required" = spell_path.point_cost, "category" = spell_path.category, + "icon" = spell_path.button_icon_state ) spells += list(spell_data) @@ -87,7 +88,7 @@ /datum/action/psionic_shop name = "Psionic Shop" button_icon = 'tff_modular/modules/psionics/icons/spells.dmi' - button_icon_state = "controlled" + button_icon_state = "swarm_zeropoint" background_icon_state = "bg_tech_blue" overlay_icon_state = "bg_tech_blue_border" check_flags = NONE @@ -103,4 +104,4 @@ if(!.) return target.ui_interact(owner) - + clicker.playsound_local(clicker.loc, 'tff_modular/modules/psionics/sounds/power_fabrication.ogg', 50, TRUE) diff --git a/tff_modular/modules/psionics/code/spell/spell.dm b/tff_modular/modules/psionics/code/spell/spell.dm index bbe1ec7a443..20c00b5826e 100644 --- a/tff_modular/modules/psionics/code/spell/spell.dm +++ b/tff_modular/modules/psionics/code/spell/spell.dm @@ -1,32 +1,8 @@ -// Даёт мутацию телекинеза -/datum/action/cooldown/spell/psionic/psionic_telekinesis - name = "Telekinesis" - desc = "Force yourself to recieve telekinesis mutation." - cooldown_time = 20 SECONDS - mana_cost = 80 - locked = FALSE - -/datum/action/cooldown/spell/psionic/psionic_telekinesis/is_valid_target(atom/cast_on) - return !issynthetic(cast_on) - -/datum/action/cooldown/spell/psionic/psionic_telekinesis/cast(mob/living/cast_on) - . = ..() - if(!ishuman(cast_on)) - return FALSE - var/mob/living/carbon/human/to_mutate = cast_on - if(!to_mutate.can_mutate()) - return FALSE - to_mutate.dna.add_mutation(/datum/mutation/telekinesis/psionic, MUTATION_SOURCE_ACTIVATED) - drain_mana() - -/datum/mutation/telekinesis/psionic - no_effect = TRUE - // Создаёт ЕМП в месте удара руки /datum/action/cooldown/spell/psionic/emp - name = "Psionic EMP" + name = "Ion Blast" desc = "Cause a small, but powerful EMP." - button_icon_state = "overload" + button_icon_state = "tech_overload" cooldown_time = 15 SECONDS mana_cost = 30 psionic_level = 2 @@ -36,23 +12,24 @@ /datum/action/cooldown/spell/psionic/emp/cast(atom/cast_on) . = ..() empulse(cast_on.loc, 3, 3) + playsound(cast_on, 'tff_modular/modules/psionics/sounds/power_fail.ogg', 50, TRUE) drain_mana() /datum/action/cooldown/spell/psionic/focus name = "Psionic Focus" desc = "Creates a useful reagents inside of you, removing stun." - button_icon_state = "blink" + button_icon_state = "tech_haste" + category = "Tier 2" cooldown_time = 50 SECONDS mana_cost = 20 point_cost = 1 psionic_level = 2 locked = FALSE - category = "Tier 2" /datum/action/cooldown/spell/psionic/focus/cast(atom/cast_on) . = ..() var/mob/living/carbon/human/human_living = cast_on - if(do_after(human_living, 1 SECONDS)) + if(do_after(human_living, 1 SECONDS, timed_action_flags = IGNORE_SLOWDOWNS | IGNORE_USER_LOC_CHANGE | IGNORE_TARGET_LOC_CHANGE | IGNORE_HELD_ITEM)) to_chat(human_living, span_warning("A calm rush envelops your mind..")) human_living.reagents.add_reagent_list(list(/datum/reagent/medicine/ephedrine = 5, /datum/reagent/medicine/synaptizine = 5, /datum/reagent/medicine/epinephrine = 5)) human_living.SetStun(0) @@ -60,13 +37,14 @@ human_living.SetSleeping(0) human_living.SetAllImmobility(0) drain_mana() + playsound(human_living, 'tff_modular/modules/psionics/sounds/power_used.ogg', 50, TRUE) else return FALSE /datum/action/cooldown/spell/psionic/charge name = "Psionic Charge" desc = "Use this spell on an item with a cell to charge it." - button_icon_state = "audible_deception" + button_icon_state = "wiz_charge" cooldown_time = 60 SECONDS mana_cost = 10 point_cost = 1 @@ -117,3 +95,361 @@ to_chat(cast_on, span_notice("[to_charge] doesn't seem to be react to [src].")) drain_mana() + playsound(cast_on, 'tff_modular/modules/psionics/sounds/power_fabrication.ogg', 50, TRUE) + +/datum/action/cooldown/spell/psionic/suppression + name = "Psionic Suppression" + desc = "Suppress your psionic energy, making you invisible to other psionics, but you can't use psionic abilities." + button_icon_state = "tech_shield" + category = "Tier 2" + cooldown_time = 30 SECONDS + psionic_level = 2 + mana_cost = 0 + point_cost = 0 + ignore_suppression = TRUE + locked = FALSE + var/suppressing = FALSE + +/datum/action/cooldown/spell/psionic/suppression/cast(atom/cast_on) + . = ..() + if(suppressing || HAS_TRAIT_FROM(cast_on, TRAIT_PSIONIC_SUPPRESSED, ACTION_TRAIT)) + REMOVE_TRAIT(cast_on, TRAIT_PSIONIC_SUPPRESSED, ACTION_TRAIT) + else + ADD_TRAIT(cast_on, TRAIT_PSIONIC_SUPPRESSED, ACTION_TRAIT) + +/datum/action/cooldown/spell/psionic/sunder + name = "Psionic Sunder" + desc = "Destroy a Zona Bovinae of psionic creature you pulling. This will make them force-suppressed." + button_icon_state = "ling_berserk" + category = "Tier 2" + cooldown_time = 10 SECONDS + psionic_level = 2 + mana_cost = 30 + point_cost = 1 + locked = FALSE + +/datum/action/cooldown/spell/psionic/sunder/before_cast(atom/cast_on) + . = ..() + var/mob/living/carbon/human/human_living = cast_on + var/mob/living/carbon/human/victim = human_living.pulling + var/datum/psionic/victim_psionic = victim.get_psionic() + if(!victim) + to_chat(human_living, span_horizonblue("You must grab victim to use this ability!")) + return FALSE + if(!victim_psionic) + to_chat(human_living, span_horizonblue("Not a Psionic!")) + return FALSE + if(victim_psionic.get_level() > 1) + to_chat(human_living, span_horizonblue("Their psi mind is too strong!")) + return FALSE + +/datum/action/cooldown/spell/psionic/sunder/cast(atom/cast_on) + . = ..() + var/mob/living/carbon/human/human_living = cast_on + var/mob/living/carbon/human/victim = human_living.pulling + to_chat(victim, span_big(span_horizonblue("You feel your psionic energy leaving your mind..."))) + if(!do_after(human_living, 10 SECONDS, victim)) + return FALSE + ADD_TRAIT(victim, TRAIT_PSIONIC_SUPPRESSED, SUNDER_TRAIT) + playsound(human_living, 'tff_modular/modules/psionics/sounds/power_fabrication.ogg', 50, TRUE) + +/datum/action/cooldown/spell/psionic/stamina + name = "Psionic Stamina Weave" + desc = "Activate this spell to regenerate your psi-mana a little bit." + button_icon_state = "tech_mend_template" + point_cost = 1 + cooldown_time = 20 SECONDS + mana_cost = 0 + locked = FALSE + var/charging = FALSE + +/datum/action/cooldown/spell/psionic/stamina/cast(atom/cast_on) + . = ..() + regenerate_stamina(cast_on) + return TRUE + +/datum/action/cooldown/spell/psionic/stamina/is_action_active(atom/movable/screen/movable/action_button/current_button) + return charging + +/datum/action/cooldown/spell/psionic/stamina/proc/regenerate_stamina(mob/living/carbon/human/human_living) + if(!do_after(human_living, 1 SECONDS)) + charging = FALSE + return FALSE + charging = TRUE + psionic_datum.adjust_psi_energy(5) + playsound(human_living, 'tff_modular/modules/psionics/sounds/power_used.ogg', 50, TRUE) + regenerate_stamina(human_living) + +/datum/action/cooldown/spell/psionic/search + name = "Psionic Search" + desc = "Scan your Z-level for Nlom signatures." + button_icon_state = "wiz_shield" + mana_cost = 5 + cooldown_time = 10 SECONDS + point_cost = 1 + locked = FALSE + +/datum/action/cooldown/spell/psionic/search/cast(atom/cast_on) + . = ..() + var/list/alive_list = list() + var/mob/living/carbon/human/searcher = cast_on + for(var/mob/living/carbon/human/alive in world) + var/datum/psionic/psi_datum = alive.get_psionic() + if(!is_valid_z_level(alive.loc, searcher.loc)) + continue + if(alive.stat == DEAD) + continue + if(!psi_datum) + continue + if(psi_datum.is_suppressed()) + continue + alive_list += alive + + var/mob/living/carbon/who_to_find = tgui_input_list(searcher, "Who you want to find?", "Psionic Search", alive_list) + if(!who_to_find || who_to_find.stat == DEAD || QDELETED(searcher)) + return FALSE + + var/area/place = get_area(who_to_find) + to_chat(searcher, span_horizonblue("The one who you looking for at... [place.name]")) + playsound(searcher, 'tff_modular/modules/psionics/sounds/power_used.ogg', 50, TRUE, SILENCED_SOUND_EXTRARANGE) + +/datum/action/cooldown/spell/psionic/nlom_eyes + name = "Nlom Eyes" + desc = "Roughly locate a mob on your z-level." + button_icon_state = "tech_control" + mana_cost = 5 + cooldown_time = 30 SECONDS + point_cost = 1 + locked = FALSE + psionic_level = 2 + category = "Tier 2" + var/datum/status_effect/agent_pinpointer/scan/navigator + +/datum/action/cooldown/spell/psionic/nlom_eyes/cast(atom/cast_on) + . = ..() + var/list/signatures_list = list() + for(var/mob/living/carbon/human/mob_signatures as anything in world) + if(!iscarbon(mob_signatures)) + continue + if(!is_valid_z_level(cast_on.loc, mob_signatures)) + continue + if(mob_signatures.stat == DEAD) + continue + signatures_list += mob_signatures + + var/mob/living/carbon/who_to_find = tgui_input_list(cast_on, "Choose who you want to find?", "Nlom Eyes", signatures_list) + if(!who_to_find || who_to_find.stat == DEAD || QDELETED(cast_on)) + return FALSE + + playsound(owner, 'tff_modular/modules/psionics/sounds/power_fabrication.ogg', 50, TRUE, SILENCED_SOUND_EXTRARANGE) + owner.balloon_alert(owner, get_balloon_message(who_to_find)) + drain_mana() + +/// Gets the balloon message for who we're tracking. +/datum/action/cooldown/spell/psionic/nlom_eyes/proc/get_balloon_message(atom/tracked_thing) + var/balloon_message = "error text!" + var/turf/their_turf = get_turf(tracked_thing) + var/turf/our_turf = get_turf(owner) + var/their_z = their_turf?.z + var/our_z = our_turf?.z + + // One of us is in somewhere we shouldn't be + if(!our_z || !their_z) + // "Hell if I know" + balloon_message = "on another plane!" + + // They're not on the same z-level as us + else if(our_z != their_z) + // They're on the station + if(is_station_level(their_z)) + // We're on a multi-z station + if(is_station_level(our_z)) + if(our_z > their_z) + balloon_message = "below you!" + else + balloon_message = "above you!" + // We're off station, they're not + else + balloon_message = "on station!" + + // Mining + else if(is_mining_level(their_z)) + balloon_message = "on lavaland!" + + // In the gateway + else if(is_away_level(their_z) || is_secret_level(their_z)) + balloon_message = "beyond the gateway!" + + // They're somewhere we probably can't get too - sacrifice z-level, centcom, etc + else + balloon_message = "on another plane!" + + // They're on the same z-level as us! + else + var/dist = get_dist(our_turf, their_turf) + var/dir = get_dir(our_turf, their_turf) + + var/arrow_color + + switch(dist) + if(0 to 15) + balloon_message = "very near, [dir2text(dir)]!" + arrow_color = COLOR_CARP_LIGHT_BLUE + if(16 to 31) + balloon_message = "near, [dir2text(dir)]!" + arrow_color = COLOR_BLUE + if(32 to 127) + balloon_message = "far, [dir2text(dir)]!" + arrow_color = COLOR_CARP_DARK_BLUE + else + balloon_message = "very far!" + arrow_color = COLOR_DARK + + if(owner.hud_used) + var/atom/movable/screen/navigate_arrow/arrow = owner.hud_used.add_screen_object(/atom/movable/screen/navigate_arrow, HUD_PSIONIC_ARROW, HUD_GROUP_INFO, update_screen = TRUE) + arrow.start_effect(their_turf, arrow_color) + + return balloon_message + +/datum/action/cooldown/spell/psionic/zona_bovinae + name = "Zona Bovinae Absorption" + desc = "Absorb a psionic energy from a being's Zona Bovinae, granting you an extra point to be used in the Point Shop." + button_icon_state = "tech_illusion" + mana_cost = 0 + cooldown_time = 10 SECONDS + point_cost = 1 + locked = FALSE + psionic_level = 2 + category = "Tier 2" + +/datum/action/cooldown/spell/psionic/zona_bovinae/is_valid_target(atom/cast_on) + . = ..() + var/mob/living/carbon/human/absorber = cast_on + var/mob/living/carbon/human/victim = absorber.pulling + if(!victim) + to_chat(absorber, span_horizonblue("You need to grab someone to do it!")) + return FALSE + if(!iscarbon(victim)) + to_chat(absorber, span_horizonblue("Victim need to be a humanoid!")) + return FALSE + if(absorber.grab_state < GRAB_AGGRESSIVE) + to_chat(absorber, span_horizonblue("Must have agressive grab!")) + return FALSE + if(!victim.mind) + to_chat(absorber, span_horizonblue("Victim need to have mind!")) + return FALSE + if(victim.stat == DEAD) + return FALSE + return TRUE + +/datum/action/cooldown/spell/psionic/zona_bovinae/cast(atom/cast_on) + . = ..() + var/mob/living/carbon/human/absorber = cast_on + var/mob/living/carbon/human/victim = absorber.pulling + to_chat(absorber, span_horizonblue("You're trying to get into the [victim]'s mind...")) + to_chat(victim, span_horizonblue("You feel like [absorber.p_their()] entering your mind...")) + if(!do_after(absorber, 10 SECONDS, victim, extra_checks=CALLBACK(src, PROC_REF(still_near)))) + return FALSE + victim.adjust_organ_loss(ORGAN_SLOT_BRAIN, 10, 80) + victim.Paralyze(8 SECONDS) + to_chat(absorber, span_horizonblue("You're trying to get [victim]'s memories...")) + to_chat(victim, span_horizonblue("You feel like [absorber.p_their()] touching your memories...")) + victim.adjust_organ_loss(ORGAN_SLOT_BRAIN, 20, 80) + victim.Paralyze(8 SECONDS) + if(!do_after(absorber, 10 SECONDS, victim, extra_checks=CALLBACK(src, PROC_REF(still_near), absorber, victim))) + return FALSE + to_chat(absorber, span_horizonblue("You trying to absorb [victim]'s Zona Bovinae...")) + to_chat(victim, span_horizonblue("You feel like [absorber.p_their()] empties your mind...")) + if(!do_after(absorber, 10 SECONDS, victim, extra_checks=CALLBACK(src, PROC_REF(still_near), absorber, victim))) + return FALSE + victim.adjust_organ_loss(ORGAN_SLOT_BRAIN, 50, 80) + victim.Paralyze(8 SECONDS) + psionic_datum.psi_point += 1 + to_chat(absorber, span_horizonblue("You absorbed [victim]'s Zona Bovinae!")) + to_chat(victim, span_horizonblue("You feel like your mind shattered.")) + +/datum/action/cooldown/spell/psionic/zona_bovinae/proc/still_near(mob/living/carbon/human/absorber, mob/living/carbon/human/victim) + if(QDELETED(absorber)) + return FALSE + if(QDELETED(victim)) + return FALSE + if(!absorber.Adjacent(victim)) + return FALSE + if(victim.stat == DEAD) + return FALSE + if(absorber.stat == DEAD) + return FALSE + if(absorber.grab_state < GRAB_AGGRESSIVE) + to_chat(absorber, span_horizonblue("Must have agressive grab!")) + return FALSE + if(!absorber.pulling == victim) + return FALSE + return TRUE + +/datum/action/cooldown/spell/psionic/shockwave + name = "Psionic Shockwave" + desc = "Create a wave of telekinetic energy to pummel the ground around you." + button_icon_state = "tech_corona" + category = "Tier 2" + mana_cost = 20 + cooldown_time = 50 SECONDS + point_cost = 1 + locked = FALSE + psionic_level = 2 + +/datum/action/cooldown/spell/psionic/shockwave/before_cast(atom/cast_on) + . = ..() + if(isspaceturf(get_turf(cast_on))) + to_chat(cast_on, span_horizonblue("You charge your shockwave, slam your foot down... and then remember that you're in space.")) + return SPELL_CANCEL_CAST + +/datum/action/cooldown/spell/psionic/shockwave/cast(atom/cast_on) + . = ..() + for(var/mob/living/victims as anything in get_hearers_in_view(7, cast_on)) + if(!isliving(victims)) + continue + if(victims == cast_on) + continue + shake_camera(victims, 2 SECONDS, 2) + victims.Paralyze(2 SECONDS) + cast_on.visible_message(span_horizonblue("[cast_on]'s foot starts to cover in blue energy, and then he stomps on the floor"), span_horizonblue("You channel psionic energy into your foot, and then stomp on the floor.")) + +/datum/action/cooldown/spell/psionic/time_stop + name = "Time Stop" + desc = "Create a wave of telekinetic energy to pummel the ground around you." + button_icon_state = "tech_control" + category = "Tier 2" + mana_cost = 80 + cooldown_time = 120 SECONDS + point_cost = 3 + locked = FALSE + psionic_level = 2 + +/datum/action/cooldown/spell/psionic/time_stop/cast(atom/cast_on) + . = ..() + var/list/default_immune_atoms = list() + default_immune_atoms += cast_on + new /obj/effect/timestop/magic(get_turf(cast_on), 1 * cast_power, 2 SECONDS * cast_power, default_immune_atoms) + +/datum/action/cooldown/spell/psionic/mirror_shade + name = "Mirror Shade" + desc = "Activate this spell to generate two psionic copies of yourself that will attack nearby mobs. These clones are not dense, will deal pain damage, and disappear \ + after thirty seconds." + button_icon_state = "wiz_jaunt" + point_cost = 2 + mana_cost = 30 + cooldown_time = 100 SECONDS + locked = FALSE + +/datum/action/cooldown/spell/psionic/mirror_shade/cast(atom/cast_on) + . = ..() + for(var/i in 1 to 2) + var/mob/living/basic/illusion/bizarro = new(owner.loc) + bizarro.full_setup(owner, faction_override = list(FACTION_PSIONIC), life = 10 SECONDS, damage = 20, replicate = 5) + bizarro.melee_damage_type = STAMINA + + owner.add_faction(FACTION_PSIONIC) + addtimer(CALLBACK(src, PROC_REF(stop_cast)), 10 SECONDS) + +/datum/action/cooldown/spell/psionic/mirror_shade/proc/stop_cast() + owner.remove_faction(FACTION_PSIONIC) diff --git a/tff_modular/modules/psionics/code/touch/touch.dm b/tff_modular/modules/psionics/code/touch/touch.dm index 4fcfc53b117..6ed23c67d59 100644 --- a/tff_modular/modules/psionics/code/touch/touch.dm +++ b/tff_modular/modules/psionics/code/touch/touch.dm @@ -7,8 +7,7 @@ /datum/action/cooldown/spell/touch/psionic/assay name = "Psionic Assay" desc = "Check if the target is a psionic." - button_icon = 'icons/obj/medical/organs/organs.dmi' - button_icon_state = "brain" + button_icon_state = "tech_audibledeception" cooldown_time = 60 SECONDS mana_cost = 10 target_msg = "Your get a headache, but it quickly fades." @@ -51,10 +50,10 @@ // Читаем разум. Выдаёт: последние сейлоги, интент, настоящее имя, воспоминания, намёк на работу, намёк на то, что в антаг_датум что то есть. /datum/action/cooldown/spell/touch/psionic/mind_read - name = "Psionic Mind Read" + name = "Psionic Read Mind" desc = "Rudely intrude into targets thoughts." - button_icon_state = "mindread" - cooldown_time = 3 SECONDS + button_icon_state = "tech_illusion" + cooldown_time = 5 SECONDS mana_cost = 20 target_msg = "You feel someone else in your head." @@ -62,8 +61,9 @@ draw_message = span_notice("You ready your hand to read someones mind.") drop_message = span_notice("You lower your hand.") can_cast_on_self = FALSE - psionic_level = 3 + psionic_level = 1 locked = FALSE + channel_time = 2 SECONDS /datum/action/cooldown/spell/touch/psionic/mind_read/cast_on_hand_hit(obj/item/melee/touch_attack/hand, atom/victim, mob/living/carbon/mendicant) if(ishuman(victim)) @@ -175,9 +175,8 @@ // Если уровень Эпсилон - удаляет лярвы ксеноморфов. /datum/action/cooldown/spell/touch/psionic/mending name = "Psionic Mending" - desc = "Mend a creature's wounds. This handles internal wounds as well, such as ruptured organs and broken bones." - button_icon = 'tff_modular/modules/psionics/icons/actions.dmi' - button_icon_state = "mending_touch" + desc = "Mend a creature's wounds. This handles internal wounds as well." + button_icon_state = "tech_biomedaura" cooldown_time = 50 SECONDS mana_cost = 30 target_msg = "You body numbs a little." @@ -186,6 +185,7 @@ drop_message = span_notice("You lower your hand.") can_cast_on_self = TRUE locked = FALSE + channel_time = 2 SECONDS /datum/action/cooldown/spell/touch/psionic/mending/cast_on_hand_hit(obj/item/melee/touch_attack/hand, atom/victim, mob/living/carbon/mendicant) if(ishuman(victim)) @@ -197,7 +197,7 @@ to_chat(human_victim, span_notice("Psionic nearby tries to mend you.")) else to_chat(human_victim, span_warning(target_msg)) - if(!do_after(mendicant, 5 SECONDS, human_victim, IGNORE_SLOWDOWNS, TRUE)) + if(!do_after(mendicant, 5 SECONDS / cast_power, human_victim, IGNORE_SLOWDOWNS, TRUE)) return FALSE else try_heal_all(human_victim) @@ -231,26 +231,79 @@ if(drop_loc) parasite.forceMove(drop_loc) + var/damage_to_heal = 30 * cast_power + patient.heal_overall_damage(damage_to_heal, damage_to_heal, damage_to_heal) + /datum/action/cooldown/spell/touch/psionic/electrocute name = "Psionic Electrocute" desc = "Administer a painful amount of psionic shock to the nervous system of a foe in melee range, causing burn and agony damage." - button_icon_state = "chain_lighting" - cooldown_time = 10 SECONDS + button_icon_state = "tech_shockaura" + cooldown_time = 20 SECONDS point_cost = 2 mana_cost = 10 psionic_level = 2 hand_path = /obj/item/melee/touch_attack/psionic/chain_lighting locked = FALSE category = "Tier 2" + channel_time = 1 SECONDS /datum/action/cooldown/spell/touch/psionic/electrocute/cast_on_hand_hit(obj/item/melee/touch_attack/hand, atom/victim, mob/living/carbon/caster) . = ..() if(ishuman(victim)) var/mob/living/carbon/human/human_living = victim human_living.adjust_fire_loss(20) - human_living.electrocute_act(10, jitter_time = 2 SECONDS, stutter_time = 2 SECONDS, stun_duration = 2 SECONDS) + human_living.electrocute_act(10, owner, jitter_time = 2 SECONDS, stutter_time = 2 SECONDS, stun_duration = 2 SECONDS) + return TRUE else return FALSE +/datum/action/cooldown/spell/touch/psionic/rend + name = "Psionic Rend" + desc = "Rend an adjacent target's biomolecular state apart. Very powerful, but with an extremely long cooldown and a huge psi-stamina cost. \ + Activate it in your hand to switch to a structure mode, in which you cannot target living beings but you can tear apart \ + walls and airlocks." + button_icon_state = "gen_dissolve" + category = "Tier 2" + cooldown_time = 120 SECONDS + point_cost = 4 + mana_cost = 80 + psionic_level = 2 + hand_path = /obj/item/melee/touch_attack/psionic/rend + locked = FALSE + channel_time = 1 SECONDS + var/structure_mode = FALSE + +/datum/action/cooldown/spell/touch/psionic/rend/is_valid_target(atom/cast_on) + . = ..() + if(structure_mode) + if(!iswallturf(cast_on) && !istype(cast_on, /obj/machinery/door/airlock)) + return FALSE + return TRUE + +/datum/action/cooldown/spell/touch/psionic/rend/cast(atom/cast_on) + . = ..() + if(structure_mode) + if(iswallturf(cast_on)) + var/turf/closed/wall/wall = cast_on + var/base_time = 3 SECONDS + if(istype(wall, /turf/closed/wall/r_wall)) + base_time += 7 SECONDS + owner.visible_message(span_warning("[owner] lays [owner.p_their()] palms on \the [wall] and begins discharging psionic energy on it..."), + span_warning("You lay your palms on \the [wall] and begin permeating psionic energy through its structure...")) + if(do_after(owner, base_time, wall)) + owner.visible_message(span_warning("[owner] disintegrates \the [wall]!"), span_warning("You disintegrate \the [wall]!")) + wall.dismantle_wall(devastated = FALSE) + else if(istype(cast_on, /obj/machinery/door/airlock)) + var/obj/machinery/door/airlock/A = cast_on + var/base_time = 5 SECONDS + owner.visible_message(span_warning("[owner] lays [owner.p_their()] palms on \the [A] and begins discharging psionic energy on it..."), + span_warning("You lay your palms on \the [A] and begin permeating psionic energy through its structure...")) + if(do_after(owner, base_time)) + owner.visible_message(span_warning("[owner] disintegrates \the [A]!"), span_warning("You disintegrate \the [A]!")) + playsound(A, 'sound/effects/meteorimpact.ogg', 40) + qdel(A) + + return TRUE + #undef IS_HYPNOTIZED #undef IS_OBSESSED diff --git a/tff_modular/modules/psionics/code/touch/touch_hand.dm b/tff_modular/modules/psionics/code/touch/touch_hand.dm index f207ccfc0bf..0f39d8739b0 100644 --- a/tff_modular/modules/psionics/code/touch/touch_hand.dm +++ b/tff_modular/modules/psionics/code/touch/touch_hand.dm @@ -1,33 +1,43 @@ /obj/item/melee/touch_attack/psionic - name = "psionic sparks" + name = "Sparks" desc = "Concentrated psionic energy in a hand." - icon = 'tff_modular/modules/psionics/icons/spells.dmi' + icon = 'tff_modular/modules/psionics/icons/touch_spell.dmi' inhand_icon_state = "greyscale" - light_range = 2 - light_power = 1 - light_on = TRUE + color = null /obj/item/melee/touch_attack/psionic/mending name = "Mending" icon_state = "mend_wounds" - color = COLOR_VERY_PALE_LIME_GREEN - light_color = LIGHT_COLOR_LIGHT_CYAN /obj/item/melee/touch_attack/psionic/assay name = "Assay" - icon_state = "generic" - color = COLOR_PURPLE - light_color = COLOR_PURPLE + icon_state = "track" /obj/item/melee/touch_attack/psionic/chain_lighting - name = "Psionic Electrocute" + name = "Electrocute" icon_state = "chain_lighting" - color = COLOR_BLUE - light_color = COLOR_BLUE /obj/item/melee/touch_attack/psionic/read_mind - name = "Psionic Electrocute" - icon_state = "generic" - color = COLOR_PINK - light_color = COLOR_PINK + name = "Electrocute" + icon_state = "apportation" + +/obj/item/melee/touch_attack/psionic/rend + name = "Rend" + icon_state = "energy_siphon_drain" + force = 40 + +/obj/item/melee/touch_attack/psionic/rend/attack_self(mob/user, modifiers) + . = ..() + var/datum/action/cooldown/spell/touch/psionic/rend/hand_spell = spell_which_made_us?.resolve() + if(hand_spell.structure_mode) + hand_spell.structure_mode = FALSE + else if(!hand_spell.structure_mode) + hand_spell.structure_mode = TRUE + to_chat(user, span_horizonblue("structure mode turned on!")) + +/obj/item/melee/touch_attack/psionic/rend/attack(mob/living/target_mob, mob/living/user, list/modifiers, list/attack_modifiers) + . = ..() + var/datum/action/cooldown/spell/touch/psionic/rend/hand_spell = spell_which_made_us?.resolve() + if(hand_spell.structure_mode) + return FALSE diff --git a/tff_modular/modules/psionics/icons/actions.dmi b/tff_modular/modules/psionics/icons/actions.dmi deleted file mode 100644 index beb7a648626421f29010b3a1cae3bffcf330de76..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 4932 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 R_upawWqEbEG8yyW{{hr-OP~M% diff --git a/tff_modular/modules/psionics/icons/spells.dmi b/tff_modular/modules/psionics/icons/spells.dmi index 0696751f4990438a9fdc618a7fb2ed19b58a781e..931b4c756c526891b0291968013a3757cb525d72 100644 GIT binary patch literal 36751 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 literal 23724 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 ZsJPHO-D524E#P&Oy`AH4S)08s{0Gap_kREY diff --git a/tff_modular/modules/psionics/icons/touch_spell.dmi b/tff_modular/modules/psionics/icons/touch_spell.dmi new file mode 100644 index 0000000000000000000000000000000000000000..da2c38d59bd7e702c18b0d75e1aa8f5d5ca0ee62 GIT binary patch literal 24218 zcma&OXIN8R&^Eg1MWl#`N=F3+1w@K~5~NsZDn&$^fPe@AY0{DiND*lwpcD}c9ip_* zLs4l0(mSC_?}Pb(Q`_Fd_NkMbrxV;2f}W+$CU!=r$Z`MdoII01CjD~L zp?Kv~l2o2l*qG^8=1X;_e_oxoaoSz*zus3Tp|3f7SNc0$L^|=(R=b06A0}ySM7h*= zOk%D1dG%fA9fh=0@zJl09v37=+T9ULNDvebLVdWO$FgDX)a3|o*OEW~{YpXKrFVxp z6q!p+B6Z}?JKCW8A1ejwJM{d()f)7snik#PN%+-++!pRip%#kI1ZiANv=eb(`X(A# z;*dY8W&{5;?swd60d=lR>@GrhIihNhULneXPHe3(*DAB^#9OncRqo+*<0>3BfY+KiUY(YDvOc4W@>X8))vidqgJMyMAFxqS+jTUnJl9>VO=^$v6B9oo!p zMADYS>ngJH`juDw-@muHLFYnPi2*0)7S{5T0{Y9ujti1woetU`DXkYw*|>f8O6*dU z>IrV4rxgRwlNXs2f-$8wA6Sb?+g(@uj(Sc;U*wxO^x{E|Gk2!Qbo6VEjM_?=P4kI} zK&Rg!VmK$QSH9mR&l|JIW7M}R@jH*cUe$65nDU0L8Bt{v^l@vOG_m zeC(yV_xs%`{y1d=;l8$!t1gz976a`UI=Yq-hPDtS3f<7YV)QI^rN%1!WV zuc}cQE4v?l1{seSMVRpY^4`_|!jd1Zv#j{q>Xkp!qTgF5tqNwY%lRhLY5Nlnu>2=Z zF1;mh?*vyZiJ4ntDr6G}Yu>iM*?3}PHG9wS%g?mIF9&Szzk1YXo$7L)`R=}zadp6wE)%I#;B|y%4o0ohfkV5w2{+u*6`; z&)nto5~eRy3L)Tfx>zK|<@AAnyN1rXHs16*K!jM~Od{ilhZ4#Cbkcq6#X6 zlkS!NMLWU>gMz>S^{Rj=UC`_c=&GckJZU9kfk1k-ck$XGl;}}bYtII;MqDj1EC$gr($hUTuR0mK;VVgxOXtB+uWE)sjF7%4N)d*- zP4^9d%xMp&uFa|eaWh78*FEuiZQt;th$Z&7SqSuJ4{iX_Ll=KMlDsWJ8e|u>Z+OuT zO{qP&qVj|}R)VquX8e8hXwWx*ML&W4b4!m{i;m`6?Kba?@S-4V4!RKi<|DK*JE&l= zBgYvM*jn_>?R?~j;&;=6hVl92FI>G})K-*NWQq-DF_SV&m|2-=WjgS<`}9wX@7mBE z`4rQ}?FtS~+7O^E%KYRBhz_XeS+%GX#)II(THs%m^#Cf2@ZFrbF}b`K$uE0f0Do>8 zGZb2cFn7tG!gPh^B6?i1rdy>+K zVLv?YpfO2l*s#w)04V>xXeDlx^{dFQ!6L|b62GkFgN1P{vf7JWiJ3NaSr;EzC*!jY zmehL*XIwWE~HmE1llLn1g-1QK_ksUd<^ub!#HlgtVtQO~?b9a~yTBSXambavjG#j@5_^>wwPuDtK+^gHf1!X*RITpaH zI;!d-7uXq>JK6NJ=6CWs#!GIVCtb+p&F#oF?UWxIaZSx}m6iW&*2GJ@!{BReZ(%H5UwT;v)1R9rh(eM`&T1>9+;Ai16H zt=_v{SLf3omX(Lk{2}*21WNwLDR>RZ zUnY@}V*d{z!Ap7A51g!^(BMu9zST^3-6lT-!#B7_!=>=R7)k$m^09d$_hh}9TMvq zAjXh2{p+8PX<;BnQ3!HlrsGJar(=bnpyv=XJMDm@POw_Hg98ge+^mdWFWh710_t<+ zgVoC*c5oMBd~QiMt*Qa8xCLn=K&+hqm<1NvC{R!^ttV7}g{Hu$3DN(dx4=Zwx;bmo$j(L)H#|1!^R#NE@q;C4+?#U+STC@>mxi#t} z!L%q1F_cC#?cSv;5ZyX$?&@i2#(BE$rmVN=zB{ls#K5$XIZIG`&sX1p-F?sf-LQGi zjkkeSa~mm7;TBjKV2Pwl8+*O9jG&w;<-qpWQcdt_zccpavckSQKjB-d=XDSx1KICp zecS_6FJbeXx9o@(<+rR@zv;g#@uwnYaZbr27?`$Cc++xE0ev{!Cqd~g310v}`Csdb zS&-Qr@$THLa+@PsR1}kB#+Q(wD>6aLGwj)~a&kFX(VqAHCirHGyD@eF zKC3Q;@FJDv^3+Npz-sd3z~{?5bj^&lHzRdi+yaR%R;PYedlNPpAdNx7YFO&{fnPom z=f;>t5H$yDNqeaWtO%3|1}H?Ix1%vDuxq0mcOc)NT05X`S}JC@*nz)S2E6RL8_-4# z{V!92RHhHWh8Jds%t?n3cBM7(`#0>KTf9!Pb=C1p|rZ8!z3qAimae+%bdX1_7aI)KZ4f=Z*eg}u4J728r z7tx{AvM(vJ{DFK??1RUQG$2uhqx6>=9jvq=_Sj4&IeJzIrMB%+*a=Nl$+ACX<>**H z&-mFtdGj12cr<`9MnYH3(2#CZm$x-e%)~lbo{<%@;hn0;KBI-IXAz~p{)`|~^)kd4 zh(lbAi|)nCMUNV;=ZYDEKcxiSZt#Xe#A*hDNDv)1Ae^05QdW1UdIpDS0Opb6T*x3V=9gCnG`?AOZjQsmYmXVp+& zIryOmT_GO&*>q>enxYwEE04sYBgGdH1FmCh2XF{0AWU#7*)!5Tc4~LE79<0_zh{I( zaq-%#OAHG=0Qo1~Hl}{?S@C6;_uDGrIPrDrxwy1s zMj^jz%hIs~_8=XtXyB(lYVLU?(8^gc{8%eN%u2u|TXp71WrC%3j z{^EZ20P{}roOVa(`+$Tm+ZnPVF4wtgDqOYtf2X%8%^bX3`M7as$-JU-42#=Y^yvx} z$F3;7*`RsM*a9V^jS)vJV;If-t=APeUdl0!|K&+SkYyS;5hT4$tLkU&vlQ~`1{h&H z+N!iZ@HRlnTE6-H5jYXBj+QS>h$;5di@YbwiK2lfqEQ`Xl@1DNUxHfso?fSI15z!MYv^xPUK@Pw!* zu-X%Xz@H%e^o2G9yX#{_fSBlXxvbR34+M$S9kXu-_wjDT$IF zUm%Sfj~LgPQ_hS3wj#SY@no;fv#^Y0ujUP4I75@ThFeqhk*1RO$832qVz~mD0hM|L zciFI!ThL}i=j=fF*tADBlk#e2rC#U<)uxr9%jhHc*X5>uHRFQqIr^$UDyok?HVN{a z?YxDSAr-$ac0t~Lj9HM~imFe#ZXVyW0-eEtS|D!=j7-M(6Ux<1CFW+V)%EpH(*C{1_A-ps;`siZf0{z zk;~Tw*&gN*%15R0_0GqQ)LB>|QKMeAq1n-FdGaQ1V%b#sb zy+p5n+NVLe#X;l@G4MIoj50xkauh7UCrF$^e^lWB4T2dNfQ|VJ$=A5+k{M_aD$31j z?eLYI1L#ALh85clCeQ}1(5P=5!nCXZA7K4=!2eH-pRfdq15||k^RxQu9Ru1Nq8jfn z`9963Z1sf#)e6V~{0f59S;4^y1ttx;q6xA6B@@q{L-aJbfEd~77&U44>i-WE$Aw|H zDE1$^0qyfiZ+5vWhAL=450R?FT!;7EcU=(xW~kVFSx>JOQ}6J zu#rMCvFw9aicfr+GHrtA9h%CUGVO;~(Hp5k?ZN1xhwqfitM@*mH>dKI2<3a3epfEZZIjJ0mL(s+0T#0*E^*{IxXSoA3kgJ2)iubst|fO%0eFK@W4wd(HWu zH3tENiJ1cxXg)`M`rkBPuNNtf&_uzpkAn@c;+|M|fUG|=#69_7hNP-d8z#6OwgP8Y zmp05u--ePkc`4^Y9~`7<7o8>q;9Tq&%RLUf`}G%xYz zsE4t~I=p2R5Z24q*uXP8GOJV1DcM|!HVER<0B?4X{;0+UntuM==RT?>2ibI$+*s#U z0^*oekrIWt4;j@ZGihl|L~=zK($PU~Iz3X>fCsMO=^IWFfFlm6y9;R~b1P*77YO*g z8ovpJsAuv1AK998AqE~Gwr8Oby^K=>E*0wM00#BB7DVU9l1}`i@h@;Z&}3Q+i2%-4 zt?VJnS90yTkj6bG2B^@nK_%-9-4j7n@118>{1UqR7L`5!Cvxkl&gmPW3axO*sdEhJ zuCrVTKxM{5wE`;XAW=aX%j)uvQijrFuF9CH$&Yti^UBK?xq)Je^k3fswQEmXSGwb$A*@iUhW z5rI6kW6Vd2q;W^7)jdVRSG)C1$L}wcNy+HSCU*tuw@E8qWHxOKF#GOm-e%O;^YA?b?^%I`#aB+~WqnnPXOg5-R{S+x zs^dc8+VGNSm(up{2$&J+5X$W>pGhwBJ5^(TUYi>$*YE>uur2SeB+5goy6%O-I4CdC0 zWFg|p)lx|Y0M9S;D-^bj?&uPByyr)r+{p}%G9Q~BcXZZq>0J7-k+Ksbt;WFPlwLFb zS)`*mx9;=s|5IRwLL=J5b5H1tybK}d)!e7kCm4qzbi5(=8U{Q3pDh? zTgd>Uf>ArNtEKI{V2!OoP=z^VLG< z5m>}&2_Ziq!7Yxa!(BivhJ5oB&*OjC@3hR_EY3f*BQXrTl{FtNyhM6^ex2c|fbvJe znByi3JDs7Pk7_BN1c!d-?YSMYY$fvHQ+Hgo)i4n)fUTX`Xu6HRxAy~aI?R&%uI?e> z%;PY$%|)TS0dmf_&$YV#_n7*j0G8Oy(RTyNk1L+>4rk;l`K2WPR=SumfV&?@XCr=i z&-s&D(Ftq;OZ0~Pwh+<9bW##6B)%(c_~PijgoHEl{K?>@1l-@?S45KOc zQ?#s=aLDFIi<%+t>96%O@dRS*AH)^?j?QifnUh*^`p*4!jXhjhE2 zczL(NTXkpNm*(cY%yaDP&BsYkn@Ycb%JA9-Y5Zp=PfU(?4SSqkdOB?AmE;L$m~w_t z?a0H&#wId+5|>P!Iz7aJd-_6H{tugwF%t7@X)!e2H*uqrUHn}iS0i6oj_AkssCVrA zK!(gjm%Js@eZ8=-(P zZ*``4Oktdy#p~81a?oa!s-H8=UGNRH=fxACE-l?@9s_l21E z)KC6Trn zYCJT45Bu$m_|x^pF?WZQaT)ocE7R~Y*dPrAH7*xPFqkfc%vFe5p7^=UW?_*KRRg<})o|6Xs@Y8AJfCLu{4nie4+$h}p^aEmkQ+qq-;H&?{`l=Nlwm7xw9OzkTwx9@m#Yh&qrfOMU z%Tt}KT*;qVOV@);uz3cIWs{~IAE(jgFE+`PBpWX;uPKMi0jE{dqMuyaZ}gxEEvG`| z*3y6~j*Yq(r3b_%ZBfF7<%iLoHe|K_@r(Ci_W*tFlsw;i-A>|}3n~@5lA}ulRd*{^ zJChICji7P9m*aOAoX-*iYp7)E9{m1-%fk9M3`R0dKWkY=dU6aWpto{yj5uYb3j>_# z_jf4K_?-LgmK!VyzSX{1)5Bl4^}DVrW_;bU-bKGYmMYo1 z;5jp!WYw`8@cQJp`!CFnV!ueSB8n08+>AF=Q3*AAI>&m+Rk;uF4*it8@*V&1ckIUE zvucLsTIcmP9hij7Ckq_sT^4sdIDgo?Z~bYvC6AP<&&UaKW~qwwOf%+n%^obRT27Y;hY`h z)e9B-)Jt!D#5WuExYkzN%%3`p1Kzo^7i_`A{NWQz+BUf;mywwlllRug%FO7Xx(?<+ z_U69V?sM4e8b4QY#0>W5uDoyH#i@zWHT5Ox0J7&bzSi;X&;#n^7&+fpnvgOl3DKoK z+4|A9qPSb>8`GWsGR_?_ z1=dBZZ;rXX+o|-G<|2^ybo5mdQk3Dsk-7%zdavzB$-b8f8~f}2IS}yy{Ynp7)e1zY zrhQfn!Z-mxtc#yBz{zXkOcHYITejWaS?;hY);&MAt$#i4WJF&y7YxBqNw7pKuc=FI z(P*ugH2VN1$k3I?eRW{0knDuHg-jetfOP^7-;1G3{?`&m#bc)3-)dr}X(9-c5*fsc znunA4d2cRR*mBKbCypa$Y$ys;2sb}2s$7Jf{V2H`k#}2 zqcuVw%{NXX>dgfLDl^P}d32tISmP@g3qTJ z;4UH1_}L0|WGOvZR8x&5TnAm3?XsninF|{`!cTFZomI1lGoa4<{4_{)Ef&&zV^zC# zz-pkTkd(VAzJYEA3qCG_9e$)1a+-=wup(BjmM#pajonoKq2PHSD09#D-58zfWy-5t zP*o*UNP~{3sOJpt-acHMTcLjP^>AKYccVdJLyQ4cdsYW{JmNUw3$$7RWKZbRGGF;E zE$c_%;UQu+rLBC-=vp`Pnk0s8rqpk4rJgM&*}H~1^;r3e99V8Dy%$2ERB=eO9S)K* zMs;V{1_oMaYjbjOrJ16NNQ=G#E6p52>7*C&RO(7hht&CpvZz-KZy#T4g(1>j+%zyJ^Ibd}c@gn%<8!o9A={*q7tH=TL z;j9rC#@Js_;-3*INO6>6}80F z=v_uQ?Jv1)mnjs`|L*mEmzmSFkZaM|1dd1Qi`Y!i#8=OK?_j{eD~k549Y45@-kVg2oeu{!zzS%r={%vtiqZXX=Y{o*vWo1w`NDkf!QG|LY7eb!90h|e5fTRaHkNF1=gAnp zy3AoCaSoXSW_+c;V{poAJGIqQ(yz)ToauKjHNggI>K^C^xZW4oz8RaBktI=j1*`n;id?aDzN;`u@_^Vty*Oy!NyMG1S%bW@B7_6#^{y5KK$bH=5~N_XL(Veq@V zz)`S75NFk7YY9_n`77e$iz@Mi-E{qnwc>wIgHP}8yM9&SX*OqYlxrIYFjz^h!tn5F z(Sh;XS_U-tBJ@iDfN$lv-32a&xN*n4_)?3RWfc-(V_La>Ya^vaazr*fycx$?D+$Cl z+A=I7K0UY2e^(V6tM1cCOwv)i! zSYQBJzkA0;po-2?TuJzrktM3jUG14Kjnry%!%zH7Jzro|vz><75u~73{7w``8Fb~c z92UVP)SQ$5W??tQ7y&|kq^(0WU3yCD7dHe0X1)aW4IzujLGvRz5)AZ|p*QRrQURX- zxF-NE;(ETueY2QR`0$optd=YlFfYtUbFez-U*>R6ji4LCR&Ue%qnWe+Jg_*E<=@*P z0~(eYofc1qi@+#u_6Ye!MI$dSZ$Zzt;$zQ3c=(gmm6c=Z zNj(Z!VRcXH&pE2=U3Ky&>CCxVg?4lp*BEto4>>}5f2sVTpQU~eX|KfqkA_#*oLTAF zyodlIHeX*#!>-~86D#coWmeohTTc_atfZi^N-t^Y8+y%PdyNQk(QqWSMpl32qL6yx z5VmjsOO8-hW0U;8KY0QEbRiyK3}IvB12w@3S-I6@)oHb)s*$FDjiwa3zOPT3CTB{B%jV*GwXf5jM+Z=p6?lAy$eryy z7lmpBPl@lVYl5|Bl1ooBSvd^Wy z*-dGwes_+I;J^?&o3Z=im+;X*Y^du0? zSMuu0!7V&(&A1p`Fd~Ee!ULsU)HE5|n33#kTQmJPPE1>*@`5ZMYWwQx*C*Zgq6BCY z|Lyb!>j~Y7GK>l2P@eev-k?2Y(BTU$KnQ*1I10QX+6C*sId4Eo|3}KPSHjgRCen(@ zI_%l3uR3xf>U8wA+7W2WMgh7kf^{N?%rxBQ=YNj+9cM7VhK1kQS?D#d zsmqL~T%`2@!T=rSO~>JDcLWzaF`E$2|4=RCN{IIO+oLnzUj)#sb{_4ZL%-E zye1Pm5x@(U@_0(_OCFq-mJyM3XQit?5{;~FrraB+bi~|BU zRO3s?y{_R=mQ6Rod#M4^5$SNfeGl5V*VzKNNZ}@Y_lWuJfHil88X*`Z zbN`k>5JDYsfoQkS1l*00di+eyc#@jgh>jg8^~V2hUZaTV@KY=4FrI@HyoTRel@>G>D%e{3ml=Vbk@{oLaCvSykE<~#&L@K$f+oF-1-;7?wL z2h1Ram-9Lb$y+X6m`GjkSfKi%ekX)o0=^<>L~^Imp*xX9@?fm9fPmx(7Z+!G%fz|3 zvHIZg#36ld7(#$@hwl1|82zjhz%CYHFv^?)f6e6&Bi4--)3o%}+i4awn#kgm%lloV z%*qQu(bn6Kt05%l(f>7$rtVp}pBo5siAd#e5ndoM7#wv$C(=m1W? zIRzbng_TZw&V`9!OSjNEmK_#_7%>A4}wwd zPSaW4gcNVo~dx6nKlvXL*hslwcS7dEU<<|)0Yxgq<#HJ z=i1#a3c~bXe;?GI(b-8!W{ng<3So01z7jSDHgJ~%$Y$mRO^SN zPn`eQ(zJ7TtE^5_Y1?nMb#;B(XOh}Xy)@XFtQ;hQJwUCWT5oZJO0jh<4rcE;U96=nXVULv4DoXZ0jA+#y)d|5F}eF zeoPq{@!2(h0t-q6X@Q&+6pdSUFQ0TfedG139|`$<|BLQ#a^^0v8Y57rMW^!w@g3suuaFpv)*+bDeEI*nNvE z+ErwDb5E9m&iAp6tQz(aN}efN!fad$&~8vYb~s7K;$|(e9o`x#TYIsug^%e3Kc!lK zD3dxre_dNY==m+|=3=K=Lb|FeuJ$s>%hIChr^1CXz{R3P-)cv?jUH5AvzLO=26|#F zn!Ie9kVTuKOv=y^j*`&k-N%HxWh;OCJ=)a<<}WUW|VLgy59w$h!B zp1O_3c|6T=uIU*Qy8eap-bY8$S67EuIJ5|)VOO-(X!UAA>}&2{0knwPEs@cJC_l1g z+auQ2B~SlPkMS5#_+)k{|M#(J^lFUx?bnFfLXjgZ1AU<-O<%BBrOJ5W@eMPVmaVEM zw}#Vqgx^_rHC$^(2zbQg67rbQ_qD90NY#V|E7%bpUqTvI?&#s zwp49Mvt+Xj2TRyl&fKlLF&1GEAlP@gHV7bl#c^3zQQOQF>Oui zyWf7@_^OmAK}c$XnL;#JTWeS5cQlzp`+Uu8h^bvmD;woedAIa>j{k=L%31IMu}Vvg?j-tZaLn*HIsSKIg=&}LLP@*642lG*Cf+aI2Ct;1;HWI~ab=4TNw3lGAz$VOJJgdc6hc zKD@ND$rYN8oo<1-Uh5F#HT5u9XIaIMQYWm`kqgGwT>=}Io!S(6;^y5ixoUfgW0kn$ zt{iS#wZ7)}>D)B@`*-7)r3V(iU?KR>d%6 ziTwsbv_<}S>yO3hgE9|iy$=|Zr9|JdYZ{Zl`c4EEx2@IGQnS)XKcnJ1hw8@~ojt|Akx- zPt7kZYz5dSIC?oiLcGY`_GuMZ*)24T_t+)59f|J+r_I2sa{M{hgqI`3KVu@p1;;NB zD*MJ==B4;b-j10XafQ^GgEaKKA^{l8+L8Mfvs%vyy33kIZf(U)k-A@uGBWl)%#5=W_!;J#7DodSW}? zSS^txNK48}v&KUv9$M?un5n;N5rE8r-gs3-mIT1z2C8Yrh=OxIpIzoTQu}N&)o>-a zlYq(Ze_Bk3k(kNHOzHG}5WxG`+FwDg%c8OU1#;avw~{1phy%nwL>#V2;>TGmdIqvXZOaxY7ieM1C{)i1JhzbF(JNnKy9U8)M3ZbJUSj{qGJM1C>0)=6#t7yA<*$aN8?xVmAh zq>cRz5UX9EhW#Uz#Q*XLZsKMKC>+>V>tV}looW-Wkm}UY6BNQm=QKWmQjayHG^l8Z z4^6u@P8z;Gc`Q=eOgf0h=xHwu?*^22AZ(X%~@o5Ax~&0KEC-N12~*UVM6;WV25PY5w_y6vXY%Q5A4M^tCd* zp-R7<$D2t=V$x#3(_uP~K{tuyTCHGP z643u|uFJ~Gn!&69l2;0MTe((}zeBi+6DC5Q=0)~Ktwn^FZ&SCBi!~ps#kYiz3(#jD zgnAwD%S*3(N6zL%Aa=e?EcWHyP<=bNy<{w!)P$svh&fWRF0ceRNu6BmCKN;&d*MW- z;wd*Sy7w{98dKNcJC-^3pG)zf0(=loSk;Bg2+}>vfDN6wzphzVS6qHUMa76+&p0xj zkb6mSO!k`+*#Qq$+;^)Q&Owl?slF2)6* zEo-EZTgph=%LlW_eu{r48^c<@+)B!?NLLS(qB#=rKDa>B{{H?$aq*Aegg4Xf?RTcb zWRc_nWLkbffnE(&h9pizB25vFM;K-I%L@at+%1Tqf|+W_oh=4ZU$zixJP>a|Bs9Tf zMD_=pGH-6A6Ms46qeI@!QXBYdt!o}* zC#tAqO1v7L(^^6&Mi#H~383fW~Z z(DePf@(6Ng(O4ECg)^$n36K>wPO8VLsjB+fo3J=+jSQT{M#2Nl5J@;CFKi6FqIFGt z;?HE&W(al&k)Gf{>tqo?Tt)9#Mlf&8$opS$D9GK)9LGQwr zi1t|MIOWe?-~;Hcug<7ZtrxOiN6vz=V4_{8)5}Ln8KN)g9<%C4g{)7*In&7}wD5-}B}@{g25INw%uc(aw}1Bn`Ws zU^L#4)-APNZ0%$wi%5-yD^K~0Kh@JNT!@ePIB&PFF>_z}<=T$|*GJ;1{QpFJU?*v3 zA*%5jVA&L(tA74!Sg8vJ0=Wc9MCai|l5t|a)yOk{a%%Otr|Wx@rP+Kj7~J4Jn56jA zXKv5*KA&^PNY@mc>p64y9}C9Sh8%8zkAkq|=;cdQiyPbCJaH_UtKEfR=~9Thm>*wP zWEB~Y{Mc}`4J1Fv>WS;?Gaecli4U#{>(tg(a@BR^=TQk**l`dKX311%{D&7I9Vzqf z0R~>1_GA{oi(4knGXCNXN*uHDW7XBR`c_g< zl1FscYXb)%;47un!zIpnjPPyahSR^Cjq(KBBK1pt|CU}*t?$;KZ|@7YU-!6OOnoig z`+52HF}8n}5rYfI9d28;I+M&;Ftf0MQD~fWBVD|4sKTGHS6tQ{jDWioMp*yHkedia zo@e9!p69Bt-D49D8Gy!eS|R(_3QG4fy`%rd3fWyidjEkfZ1{Z-)aFIvw8*6;e1E3I zxr)D$#EogLezYo*^hNFt-JPX1_*i9WH-AzE|G!v+Tdl4x=5i6d_0tb>sJeSL`C>0^%y>PSXf(vF1?e zR9yIVWUq6Mi_8em@!N;TR`L7j_WfkvF|+88b1q#`A6NDa`ffv z@oe}E=a;?OBeR4^QKH-FG#h14*S= z7IE?JMCZa0$o2MfaX$G&JaGu>j9tRcadS|Vn4|O=JND(aWaT!7>TA$YTy3^rx*%ID z(nHC0DF&sm7&l*%U)2-h&O{u)$s(bR@xvvbJ)bAS$YZ+ubCvvW)fweG5sxcGn(&_k z4{3b7vaGXbR!BZ9 zMS;W6iBklr{d-rbXNzjxN4EJ_I(Igz*7IUN3a4!I8zXbemk4^ct?-#g*}#8@ULs2P z8gmOEk_ycb>5YzhAF}Bc)(~x#Z4`YTuV~x%ww;fwh7&=hgafqcHR2x^Z-aK^juBDw z0%#ZEZM$)vF|(>~qv|;Q5@?8vn^E2SWO=LXNf~sQjKIb#GM4I6%+fYZu*hqGs~4Zk zs$I)zIzsYY{WZt2muHgxfLN@~phTtEgM#GaMXg&Ytortv_lBm?^7zwaH9zy#y&jj& zfauyfX0*ao1OIma%_G|VtNff)&|y|9`f{~FABfjb#i8V?bltsGs$5J&gNZ+ z#j=ci?5iZGr0y$+`q_KTu&A{RUXsn8iwr~OVtKK|34WqWNctt}szzC{CH$j!f?|mM zB7sfAP#~R8&Ml@BV&-Pg{6ih&&?rVY^rxoyj@S5;Rn73Mv|m4?c02%2^Wj2Aq>hD2m0D=eJns^VOGbPWbF7cHVrCBea%6oCW_&6+ ziIzh9n_vP#Sp%JqYpF(ofKKVZFXj#^eDdT;>N8HxpTj5NYGtxCoZpcYffceck(~pwC|FfYtySW@2FKzR0WF9?=DiRR249j|`;#pT7&jWPYZN7HD34Q#c0JpM_fAn49ZBo{luJRvf z^dtggOcY@LZ|zFy3-E7;F~z?rE56dux}x7rQ=F^9>Ps1~m zH(uX^x&0@U3Q)S~t}AL@_V!)>7wHis33380APd#WTYK_>Uxz|<;?~RHhkS+MAl;R} zQqz?fUvw5hdQZL)jceJoB@vQ-7(mGkUKq0;ysFvBN?5onJg{N8=KUicQ2FUA)Sf>F z#C8bz+UH~X4#=PovTRuM#ih2x!@}iP+7WG#&E+4t6U#PWCF~D zUQt}Cm51-Q?0dVmp@VTXu;`#YPzqR&ziOUV4R2B+x#y+Ks}E2V2pgt5yeO&PQ6D4f zdO%Qe&sfrjW$I+?=`i8C2h17yb+&4Zv!Ur_DcnZA#4}m4*oGI#eu~TfPThr_sK zP>9iv{jKk2(C{LD?R5I8kNcL2@oZOPO!&QnFrD(`XZL$UpIIX**LY<9d{CIWccH=b z(pTke#-7g%G4QGElzE{Kik(igc}`6t@--9_NulPp*8-?qqk#A7f%ls%lB=OiYX0zt zpg5=Q1pb|S+S=&a!Tj&1*YwR-?TCFICZkyiB5BT4`?;6l&B3t2KnFa=TZdvdR~CNp zfac?i+SEU5hsM^PGIf_9|3qwRe9qZ$BN3Nra9)^lT$eH$g2hTApDyCNk12N`H;EV} z9-m9gNpn6NlnWqtI>Rhloq5c86!AopNJ5K^R658eWB@3ICY{%e#4IK-rVo=C_BoM3>&Jb**CA+oZ#}t-R@i=wR8}? z7DN>`s+c!++xJV@r-#!yRh+O(>$c7vdP2zl&O&nG#$}G4i`ZGQub_m3m#ZUJLr)bU zqJ`2^rBuGDOi;gkR$>_ z$#3Hx+|05t9#KW(ww*!#Q-Vb#$@Q|MF2|KG?>+jC@W4zz_Df%}r;lRS+A=~FX~ssb z-?hgP#)v(1&865?=RfNPVWQte5V^|&AXK)=&-}q`@}us$oRI!DxqYO}myK+4v=AOF zCSMioe*R>r2-bmmXY3^)7U_s-Eo);d2YOti>qKI;v4%|2$LXfEF06v`r|&ViEM%eB z*XoEaV}aXE>2u;7pp;L8CqYW1Ya!MErZ^Li8B>4}TMSks!b#f()NN_|vJ?q;b6cO` zTOVY{C1Ktz|JGQ=Ih%g(fB9)lH{I!%A6Gzt!pB^DW?F+* zuI+zHUfQUnl9+OeLg{Fcbt)9fAj>2)TBITlS+dQO>^hn@DN9uLrLknm_E?Hg#uAPu zCXJmjmSKk3e)lu=p6&PN`;X;$KF_`1*LB_B@AtYlTg zWP!f*R!}y=DiaV^S&#LWe50B(RYAG#c7IO?L62Y^; zoszg*;j+jM;>?3=I5ZS+X|9nt7){t=AWPuYlQ=yZ{r!)9_}77(Mf@&oh0@Z}iZ?kk zHeG}$jPE6Bnq%fIF3{u-pkJ&TAPUuR%`dQeCj2ojO@{iWwQ z3X;mQg+GMK9#Q3q()qnBesn7W)b=y(_<1Bx_($Ne;mzLzkKav0UrzurPx)&gnVafCeK^i*jy^(4EUN1I-sDf~l^KQn;9z5Mrc zvRSCCc~+@g-#HK4`KDRzxD4Je5l(T6OAb$x3b|AB!l8tg{Y~1l)7Vq7L|j+{C<8VI z;C`ycaUj|4j9&fc(>(~px%GuSXNz0i4_q0jfC|VaNJ=8gQD%R+FeA}%eP;10RmN;b z`V>DJfg1eY=xuObI*Dc?+&p|RR>T}A@%)s^ZOPA&@NQtq4juKVQmPLLJV0b+nzkeV zrIfw{UP)c<$$!=8vzG z25o7fk^GMz;+Y<7A=Q~J6{rYQrz3{kq3g2;u2Pa0(ri;%@Iv1Hz&?fusz!t7`}H(i z^_15xY)9;F+015K=RM5jB?eQgT{=MD%nuu^L3U3#y!U~eE!K;0@N)cX*Afl<-Uwit#1xbd0 z*~Qqy4E4ZHZb?MV2{cnHgpq6=9Ph=|3r8h*aoH2U;sAa!eayc#gz>1rJ#bC%LD8N$ z{Nn>m@>g~9>*BfN*%F;Cj$j|5Ut?UX8^3XSiH0B5>hsF8MPWO^H_oHvWfcA!HPfpM zfdWURZ%y&Xs87RPKR=i_r=zeyszqGD3=Pm4Llw20>Wv`oXV=fGQPPk##USG~0dEP* zCx*=S)wpQgb<7_@Yq{!13 zvAAatQ6EST$L;7Qu^s|jo3RmD-9vM6l1D8xY^k>bfar1hTUwBJ;ZHe^RO%kEf)f<+(>kZmSF`BVm2$W>G-dIBCBQu~#i4z#4vTA^~V_d^~%SPLDAYN@$dfER~Ld zFVl=N(RRw0P7_N{^B}9+LR^S9V$$4RHELx7F5K` z;&v$@L!owN!K6)w#WhamE+8;rLD4D+;bJdx)4Z>@i0o9z`g-5m>c0@ecjKi;h0A@G zva|{89RASEU-Z^_&#e*>mYyJ8;pff+I1KimgjPWm&>PVj@m+w-QUqfc*CY6<6Vut= zVi6&xMhCVeUx+2AstDLR2kLOme0rwPIJ?v82viS;Q2Nd*YyjpKa2 zwy@}X|0JGruH^*(1QY)^;^}CfwWD8L++lAP4vYYKx>1ua4|)K${;46<=Ov@W!K6`V zUFTI?cF01b$3vE6Q1?xwK}6`GiN2$u5*uurdoc;gU|3-`nw{A*F&Ly>IN<)?<0S`V zt|Nm6#RffjzQSRMk{9D%$DR?Rlcwnn4H^Zx)17)jI`^Aqi&n!~J>T(xkd1ihkXc1` ziT8PUtS5X596q!Ck8AKuw_x}wJZZDt>2Q?OB1Mzvu;?6&QHAUjF(Db0mAzwxgh(({kgO6xayy?bF`)_<|QxB9&kkL-AX#I5iaAKXz1^~d9l>* zkUcW~&h~dtzT+Rdg@k=zjyAu?FSUm|LydzyQ5)6^G(z4H zF_$5%<{t}9V#pV9O?VYyeuk=gtb2I;JYB=R0_e6Rzo5?wKUIR=%l5a8Z@3Uk?}zGY|J^<@>BnXMI3u|WLE8mJdS{O zo7skkfpCl^wrL+WT-fGs|3X6^{yivx(ysnHDp~O!tcJDli>OFp<8%2yfn+;+$wSN? zJ>~o>v{KcWBE^Om<%G(8p65L=|88YImvoW_k@+R512y zv>U)=dh8bux_8LVCxut(WDrXZ7OX{?C;c!C7Nnj%H&-u}e`_Rh^NwJd0((wTK0W!O zFDv>$&z`YXJvYOtd!;+~Q4HA2&f1%Zr4&-#Zg?+e&rA#`P59yXwq@eSJXFf%N?%fz zF3lJ1!cWaG0dU~2H{qRL7dh3=$}5>5J5_aGwu}BvrUpS%d?81MJ6M^jBnB-x?o3Y2 z_~Qjr2{F(B#wkf{TMT7^vXo?ddPQPoGr6^ut@U|MB4)_izoSb+H+0bYcyr~I?g?*J zZjkl0Q#0D;TxorD$d0&`uy~Jx!S4l53Zqcqxhfw99$17(cae3qivcnVK@$!*X z;5eq?j@Q7{1HOAFWY}GpQb&~cnl*}ek-cNL!;PkZYHZ_qW`N<;OI|9Z(bw&_+}I+& znUkco@4;*mJdP^Am`=+oBsww43OU~|bmHG&dK#$*Vb7dKjIZtWzOV{NiGy>MrmWvC}$|#yZsUY~J3Va3;3o;7Xn> zd99<{ktKTsyA2u!) zDPs*81FBcNo9&WE;cn@1&i zzg$%0?j8?Msl0yDrt!Shivo-J{W4{RnderjXydX>?Cxr9;`v$5sbx>rhr_F?xy`gX z{Kn-sI7NxgLvuU^e|veJ7&_-sN>@jJq1?SO>HX{(XZitmiH<@!*d;o9uha0{V?T%9 z!>Oec_tf>z%&kae4l)X*3LORlMpr<$2+~J8DE5n{OD4G6%|6RZkUbnR$%@8Wl_T!XHd=MLp8bd^jach%NxtlC0`8OHEM;5e=C@xrS4{5z0oxJro!UQ zUWLLg27u$Lu0l=-5kZyv;M1;zhPXzrH}+Y}WB6^nwB^7ZCjaRUt#}{`>18D3O6c@0 zj>y4Lt<%`7GxcATy5ed!QhH^w=Z#6B#XRh__n4*LcT}@x#Qx)th>)Ztglwj;J9* z&v;c9g5q1wBR-WCc1J3$-&NM*Z69kA-U7wlaP!mWl2%>r{(7wn+n`x`jOig<9W!tZ zf`#L=mwO>8!MYA(vp^WeBT~Vhix>vlE>cr#t9BZR{wO$WTIxFo@pMD1{~vQ_B3n!? zeqEBr$}e8*EPt-8)PtFdxrF%0XtY3`_q<99R}Voo&l#PegqK9pdAvNkQk!jrv05T#3oL(?xqoLx1&~n0b!Ru0Ug=NHq{`YlJdxFPyFq4>Vt(%rL@13XcY$0x+AgYc zmv7+N!%9e{!KwqAPij6RC14%^+ zu_{Lan(|0m-QEf^!0Ka9e0aHp$O@b;0Gk9ZR{Ha5!-Hb5-~fEfo~sNVU)|b;3r=b+ zZi>H2yC{Dz-sRw-kVs07T}Fmxhg$Fi=4gAuK6~(8%kiOA z_v5+91t{$Tsr5~E&)qf@3&6bo{12n1+;-VIu!BMnI1^^ddKjd47K5o;UITSug70;p zUHPZC;Qxbh54p0WYmfTX;nN~7dGS4s%E^GvT7X0%W^xxTmwM>aP)i&%R~NQgvL6!g zT!`|zOLJNrM;RS(+lx`!j{THn8Ew?t6C7?ohF4#Xe)BGNYRaeJ)4UKel(%qBXEMvEa#YDFu&9XEg&_T-t$EXCPSHFSUN(}~KsZdbM z6`)ei(mY~`3vTUo+luFBxAg55O;+1%dP^@)oNi#TlK~6(=zJ}3snzxbKy&qlS{dy0 z&p9Maf$_N$X$EPvfnIoWkq%X+0iWs1?;qcm(z$;w-DcSrV|bXyqEe-mkAk|w9+Oa2 z$`qiMMbakES7{3i0ST2`N0wR|ajF z1O*8ow5~sM{CB)4$d<4KlAqmfyO83vf2e#L`eRK0N`V}tkPZ~A(JNk3san&&DI=$f zPXeO{dxJ4ZVo@Q8)&A-vsSp9jQntm~1m^)#mYW+PX$aIyh_-k(+clwD9o)E3{V-?u zx6xCBH_nR~t=1Gc2rvU;$Tx_+oQFCFE+I-=IqkTWvvk~!-ExG&x;6%{6Ei50@a%-B z{2g$i=g~vg_lB6^9GKG&b~YJHpTLG^xvnu>oW`v@_Qw|uGZJ{bpb z;^d}~JiZ+lCr#Rx>=Q%kD>$zivcnYQQqj91?E%4y66k86QbXl$*^-5#)9F!>9}v^D zq^@4j5(e(LMAclh*mBzqD2hB0^ZQ_|sSzZ&;4kF_|M)EMaW-mfhXFt!i@psAi)}F+ zP~I=68_E_3CAWy@)PRLzpe(xmtR5l~TBjfolW}Fs%a$r8f@)F5hFF8w#~ttdAKmE} AHUIzs literal 0 HcmV?d00001 diff --git a/tff_modular/modules/psionics/sounds/power_config.ogg b/tff_modular/modules/psionics/sounds/power_config.ogg new file mode 100644 index 0000000000000000000000000000000000000000..157663343209f9828cb85876966ccd5ec0d07aa4 GIT binary patch literal 20039 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 literal 0 HcmV?d00001 diff --git a/tff_modular/modules/psionics/sounds/power_feedback.ogg b/tff_modular/modules/psionics/sounds/power_feedback.ogg new file mode 100644 index 0000000000000000000000000000000000000000..139dfba0bd46f08a965336a900a1ba0f9c23c8d6 GIT binary patch literal 77856 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 =OOI`8Z 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 literal 0 HcmV?d00001 diff --git a/tff_modular/modules/psionics/sounds/power_unlock.ogg b/tff_modular/modules/psionics/sounds/power_unlock.ogg new file mode 100644 index 0000000000000000000000000000000000000000..3ba24f81b38d3c513c863d82470bcf4a4b11ca62 GIT binary patch literal 30877 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�ZG_85Kb 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 { - return name.toLowerCase().replace(/\s+/g, '_'); -}; - export const PsionicShop = (props) => { const { act, data } = useBackend(); const { many_spells, psi_points_count, researched_spells } = data; @@ -192,7 +189,7 @@ const ItemList = (props: SpellListProps) => { const costDisplay = `Cost: ${spell.point_required} Psi`; - const iconState = nameToIconState(spell.name); + const iconState = spell.icon; return ( From f422eba2010109c2c430e0a7d7c189f8cc78d541 Mon Sep 17 00:00:00 2001 From: SaukyKouko Date: Fri, 22 May 2026 17:04:05 +0300 Subject: [PATCH 08/22] =?UTF-8?q?=D0=B8=D1=81=D0=BF=D1=80=D0=B0=D0=B2?= =?UTF-8?q?=D0=BB=D1=8F=D0=B5=D0=BC=20=D0=B1=D0=B0=D0=B3=D0=B8?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../modules/psionics/code/_psionic_datum.dm | 4 +- .../modules/psionics/code/pointed/pointed.dm | 65 ---------------- .../psionics/code/projectiles/projectile.dm | 12 ++- .../modules/psionics/code/spell/spell.dm | 75 +++++++++---------- .../modules/psionics/code/touch/touch.dm | 48 ------------ .../modules/psionics/code/touch/touch_hand.dm | 20 ----- 6 files changed, 47 insertions(+), 177 deletions(-) diff --git a/tff_modular/modules/psionics/code/_psionic_datum.dm b/tff_modular/modules/psionics/code/_psionic_datum.dm index cc4cb1659f2..c74339ded79 100644 --- a/tff_modular/modules/psionics/code/_psionic_datum.dm +++ b/tff_modular/modules/psionics/code/_psionic_datum.dm @@ -174,14 +174,14 @@ psionic_level = 1 psionic_level_string = SENSITIVE_PSIONIC license = TRUE - psi_point = 5 + psi_point = 7 /datum/psionic/harmonious max_mana = 100 psionic_level = 2 psionic_level_string = HARMONIOUS_PSIONIC license = FALSE - psi_point = 10 + psi_point = 14 /datum/psionic/proc/is_suppressed() if(HAS_TRAIT(psi_owner, TRAIT_PSIONIC_EXHAUSTION)) diff --git a/tff_modular/modules/psionics/code/pointed/pointed.dm b/tff_modular/modules/psionics/code/pointed/pointed.dm index dc9a24b0f2f..368b1317850 100644 --- a/tff_modular/modules/psionics/code/pointed/pointed.dm +++ b/tff_modular/modules/psionics/code/pointed/pointed.dm @@ -594,68 +594,3 @@ . = ..() ADD_TRAIT(owner, TRAIT_THERMAL_VISION, PSIONIC_TRAIT) owner.update_sight() - -/datum/action/cooldown/spell/pointed/psionic/singularity - name = "Psionic Singularity" - desc = "Creates a psionic illusion. Anyone within four tiles of it is forced to walk towards it. It will dissipate after ten seconds." - button_icon_state = "tech_energysiphon" - category = "Tier 2" - point_cost = 2 - cooldown_time = 40 SECONDS - psionic_level = 2 - mana_cost = 10 - locked = FALSE - cast_range = 3 - -/datum/action/cooldown/spell/pointed/psionic/singularity/is_valid_target(atom/cast_on) - if(isturf(cast_on)) - return TRUE - return FALSE - -/datum/action/cooldown/spell/pointed/psionic/singularity/cast(atom/cast_on) - . = ..() - owner.visible_message(span_horizonblue("[owner] raises his hands up, directing a strange energy to the floor near him..."), span_horizonblue("You're now directing a strange energy, creating Singularity.")) - new /obj/effect/singularity(get_turf(cast_on)) - drain_mana() - return TRUE - -/obj/effect/singularity - anchored = TRUE - name = "Singularity" - desc = "It's so......" - icon = 'icons/obj/machines/engine/singularity.dmi' - icon_state = "dark_matter_s1" - layer = FLY_LAYER - plane = ABOVE_GAME_PLANE - mouse_opacity = MOUSE_OPACITY_TRANSPARENT - var/list/immune = list() - var/hypnorange = 2 - var/duration = 100 - -/obj/effect/singularity/Initialize(mapload, radius, time, list/immune_atoms) - . = ..() - if(!isnull(time)) - duration = time - if(!isnull(radius)) - hypnorange = radius - if(!isnull(immune_atoms)) - for(var/mob/living/A in immune_atoms) - immune += A - START_PROCESSING(SSobj, src) - -/obj/effect/singularity/process(seconds_per_tick) - . = ..() - for(var/mob/living/carbon/victims as anything in view(hypnorange, get_turf(src))) - if(victims.can_block_magic(MAGIC_RESISTANCE_MIND, charge_cost = 0)) - continue - if(victims.Adjacent(src)) - continue - step_towards(victims, src, 1) - - duration -= seconds_per_tick - if(duration <= 0) - qdel(src) - -/obj/effect/singularity/Destroy(force) - . = ..() - STOP_PROCESSING(SSobj, src) diff --git a/tff_modular/modules/psionics/code/projectiles/projectile.dm b/tff_modular/modules/psionics/code/projectiles/projectile.dm index bd94a2967d1..5aeeb503848 100644 --- a/tff_modular/modules/psionics/code/projectiles/projectile.dm +++ b/tff_modular/modules/psionics/code/projectiles/projectile.dm @@ -30,17 +30,18 @@ damage_type = BRUTE /datum/action/cooldown/spell/pointed/projectile/psionic/lighting - name = "Spell Cards" + name = "Psionic Lighting" desc = "Hits living beings in a 4x3 area in front of you with thunders." button_icon_state = "spellcard" category = "Tier 2" click_cd_override = 1 - cooldown_time = 60 SECONDS + cooldown_time = 20 SECONDS psionic_level = 2 mana_cost = 30 point_cost = 3 cast_range = 40 - projectile_type = /obj/projectile/beam/psi_lightning + locked = FALSE + projectile_type = /obj/projectile/beam/emitter/hitscan/lighting projectile_amount = 1 projectiles_per_fire = 5 /// The turn rate of the spell cards in flight. (They track onto locked on targets) @@ -69,7 +70,7 @@ . = ..() drain_mana() -/obj/projectile/beam/psi_lightning +/obj/projectile/beam/emitter/hitscan/lighting name = "psionic lightning" damage = 20 armour_penetration = 30 @@ -80,3 +81,6 @@ muzzle_type = /obj/effect/projectile/muzzle/solar tracer_type = /obj/effect/projectile/muzzle/solar impact_type = /obj/effect/projectile/muzzle/solar + hitscan_light_color_override = COLOR_LIGHT_YELLOW + muzzle_flash_color_override = COLOR_LIGHT_YELLOW + impact_light_color_override = COLOR_LIGHT_YELLOW diff --git a/tff_modular/modules/psionics/code/spell/spell.dm b/tff_modular/modules/psionics/code/spell/spell.dm index 20c00b5826e..22ed3f7decd 100644 --- a/tff_modular/modules/psionics/code/spell/spell.dm +++ b/tff_modular/modules/psionics/code/spell/spell.dm @@ -311,7 +311,7 @@ return balloon_message -/datum/action/cooldown/spell/psionic/zona_bovinae +/datum/action/cooldown/spell/pointed/psionic/zona_bovinae name = "Zona Bovinae Absorption" desc = "Absorb a psionic energy from a being's Zona Bovinae, granting you an extra point to be used in the Point Shop." button_icon_state = "tech_illusion" @@ -321,46 +321,41 @@ locked = FALSE psionic_level = 2 category = "Tier 2" + cast_range = 2 -/datum/action/cooldown/spell/psionic/zona_bovinae/is_valid_target(atom/cast_on) +/datum/action/cooldown/spell/pointed/psionic/zona_bovinae/is_valid_target(atom/cast_on) . = ..() - var/mob/living/carbon/human/absorber = cast_on - var/mob/living/carbon/human/victim = absorber.pulling - if(!victim) - to_chat(absorber, span_horizonblue("You need to grab someone to do it!")) - return FALSE + var/mob/living/carbon/human/victim = cast_on if(!iscarbon(victim)) - to_chat(absorber, span_horizonblue("Victim need to be a humanoid!")) - return FALSE - if(absorber.grab_state < GRAB_AGGRESSIVE) - to_chat(absorber, span_horizonblue("Must have agressive grab!")) + to_chat(owner, span_horizonblue("Victim need to be a humanoid!")) return FALSE if(!victim.mind) - to_chat(absorber, span_horizonblue("Victim need to have mind!")) + to_chat(owner, span_horizonblue("Victim need to have mind!")) return FALSE if(victim.stat == DEAD) + to_chat(owner, span_horizonblue("There is nothing interesting...")) return FALSE return TRUE -/datum/action/cooldown/spell/psionic/zona_bovinae/cast(atom/cast_on) +/datum/action/cooldown/spell/pointed/psionic/zona_bovinae/cast(atom/cast_on) . = ..() - var/mob/living/carbon/human/absorber = cast_on - var/mob/living/carbon/human/victim = absorber.pulling + var/mob/living/carbon/human/absorber = owner + var/mob/living/carbon/human/victim = cast_on to_chat(absorber, span_horizonblue("You're trying to get into the [victim]'s mind...")) - to_chat(victim, span_horizonblue("You feel like [absorber.p_their()] entering your mind...")) + to_chat(victim, span_horizonblue("You feel like [absorber] entering your mind...")) if(!do_after(absorber, 10 SECONDS, victim, extra_checks=CALLBACK(src, PROC_REF(still_near)))) return FALSE victim.adjust_organ_loss(ORGAN_SLOT_BRAIN, 10, 80) victim.Paralyze(8 SECONDS) to_chat(absorber, span_horizonblue("You're trying to get [victim]'s memories...")) - to_chat(victim, span_horizonblue("You feel like [absorber.p_their()] touching your memories...")) + to_chat(victim, span_horizonblue("You feel like [absorber] touching your memories...")) victim.adjust_organ_loss(ORGAN_SLOT_BRAIN, 20, 80) victim.Paralyze(8 SECONDS) - if(!do_after(absorber, 10 SECONDS, victim, extra_checks=CALLBACK(src, PROC_REF(still_near), absorber, victim))) + if(!do_after(absorber, 10 SECONDS, victim, extra_checks=CALLBACK(src, PROC_REF(still_near)))) return FALSE to_chat(absorber, span_horizonblue("You trying to absorb [victim]'s Zona Bovinae...")) - to_chat(victim, span_horizonblue("You feel like [absorber.p_their()] empties your mind...")) - if(!do_after(absorber, 10 SECONDS, victim, extra_checks=CALLBACK(src, PROC_REF(still_near), absorber, victim))) + to_chat(victim, span_horizonblue("You feel like [absorber] empties your mind...")) + if(!do_after(absorber, 10 SECONDS, victim, extra_checks=CALLBACK(src, PROC_REF(still_near)))) return FALSE victim.adjust_organ_loss(ORGAN_SLOT_BRAIN, 50, 80) victim.Paralyze(8 SECONDS) @@ -368,21 +363,9 @@ to_chat(absorber, span_horizonblue("You absorbed [victim]'s Zona Bovinae!")) to_chat(victim, span_horizonblue("You feel like your mind shattered.")) -/datum/action/cooldown/spell/psionic/zona_bovinae/proc/still_near(mob/living/carbon/human/absorber, mob/living/carbon/human/victim) - if(QDELETED(absorber)) - return FALSE - if(QDELETED(victim)) - return FALSE - if(!absorber.Adjacent(victim)) - return FALSE - if(victim.stat == DEAD) - return FALSE - if(absorber.stat == DEAD) - return FALSE - if(absorber.grab_state < GRAB_AGGRESSIVE) - to_chat(absorber, span_horizonblue("Must have agressive grab!")) - return FALSE - if(!absorber.pulling == victim) +/datum/action/cooldown/spell/pointed/psionic/zona_bovinae/proc/still_near(mob/living/carbon/human/absorber, mob/living/carbon/human/victim) + var/distance = get_dist(absorber, victim) + if(distance > cast_range) return FALSE return TRUE @@ -434,22 +417,38 @@ /datum/action/cooldown/spell/psionic/mirror_shade name = "Mirror Shade" desc = "Activate this spell to generate two psionic copies of yourself that will attack nearby mobs. These clones are not dense, will deal pain damage, and disappear \ - after thirty seconds." + after 15 seconds." button_icon_state = "wiz_jaunt" point_cost = 2 mana_cost = 30 cooldown_time = 100 SECONDS locked = FALSE + var/list/mob/mobs_to_attack = list() /datum/action/cooldown/spell/psionic/mirror_shade/cast(atom/cast_on) . = ..() + for(var/mob/living/carbon/human as anything in view(7, cast_on)) + if(!iscarbon(human)) + continue + if(human.stat == DEAD) + continue + if(human == owner) + continue + + mobs_to_attack += human + + if(isnull(mobs_to_attack)) + return FALSE + for(var/i in 1 to 2) var/mob/living/basic/illusion/bizarro = new(owner.loc) - bizarro.full_setup(owner, faction_override = list(FACTION_PSIONIC), life = 10 SECONDS, damage = 20, replicate = 5) + var/mob/who_to_attack = pick(mobs_to_attack) + bizarro.full_setup(owner, who_to_attack, faction_override = list(FACTION_PSIONIC), life = 15 SECONDS, damage = 20, replicate = 5) bizarro.melee_damage_type = STAMINA owner.add_faction(FACTION_PSIONIC) - addtimer(CALLBACK(src, PROC_REF(stop_cast)), 10 SECONDS) + addtimer(CALLBACK(src, PROC_REF(stop_cast)), 15 SECONDS) /datum/action/cooldown/spell/psionic/mirror_shade/proc/stop_cast() owner.remove_faction(FACTION_PSIONIC) + mobs_to_attack = null diff --git a/tff_modular/modules/psionics/code/touch/touch.dm b/tff_modular/modules/psionics/code/touch/touch.dm index 6ed23c67d59..5b4d47c362c 100644 --- a/tff_modular/modules/psionics/code/touch/touch.dm +++ b/tff_modular/modules/psionics/code/touch/touch.dm @@ -257,53 +257,5 @@ else return FALSE -/datum/action/cooldown/spell/touch/psionic/rend - name = "Psionic Rend" - desc = "Rend an adjacent target's biomolecular state apart. Very powerful, but with an extremely long cooldown and a huge psi-stamina cost. \ - Activate it in your hand to switch to a structure mode, in which you cannot target living beings but you can tear apart \ - walls and airlocks." - button_icon_state = "gen_dissolve" - category = "Tier 2" - cooldown_time = 120 SECONDS - point_cost = 4 - mana_cost = 80 - psionic_level = 2 - hand_path = /obj/item/melee/touch_attack/psionic/rend - locked = FALSE - channel_time = 1 SECONDS - var/structure_mode = FALSE - -/datum/action/cooldown/spell/touch/psionic/rend/is_valid_target(atom/cast_on) - . = ..() - if(structure_mode) - if(!iswallturf(cast_on) && !istype(cast_on, /obj/machinery/door/airlock)) - return FALSE - return TRUE - -/datum/action/cooldown/spell/touch/psionic/rend/cast(atom/cast_on) - . = ..() - if(structure_mode) - if(iswallturf(cast_on)) - var/turf/closed/wall/wall = cast_on - var/base_time = 3 SECONDS - if(istype(wall, /turf/closed/wall/r_wall)) - base_time += 7 SECONDS - owner.visible_message(span_warning("[owner] lays [owner.p_their()] palms on \the [wall] and begins discharging psionic energy on it..."), - span_warning("You lay your palms on \the [wall] and begin permeating psionic energy through its structure...")) - if(do_after(owner, base_time, wall)) - owner.visible_message(span_warning("[owner] disintegrates \the [wall]!"), span_warning("You disintegrate \the [wall]!")) - wall.dismantle_wall(devastated = FALSE) - else if(istype(cast_on, /obj/machinery/door/airlock)) - var/obj/machinery/door/airlock/A = cast_on - var/base_time = 5 SECONDS - owner.visible_message(span_warning("[owner] lays [owner.p_their()] palms on \the [A] and begins discharging psionic energy on it..."), - span_warning("You lay your palms on \the [A] and begin permeating psionic energy through its structure...")) - if(do_after(owner, base_time)) - owner.visible_message(span_warning("[owner] disintegrates \the [A]!"), span_warning("You disintegrate \the [A]!")) - playsound(A, 'sound/effects/meteorimpact.ogg', 40) - qdel(A) - - return TRUE - #undef IS_HYPNOTIZED #undef IS_OBSESSED diff --git a/tff_modular/modules/psionics/code/touch/touch_hand.dm b/tff_modular/modules/psionics/code/touch/touch_hand.dm index 0f39d8739b0..a1aeeb00b1f 100644 --- a/tff_modular/modules/psionics/code/touch/touch_hand.dm +++ b/tff_modular/modules/psionics/code/touch/touch_hand.dm @@ -21,23 +21,3 @@ /obj/item/melee/touch_attack/psionic/read_mind name = "Electrocute" icon_state = "apportation" - -/obj/item/melee/touch_attack/psionic/rend - name = "Rend" - icon_state = "energy_siphon_drain" - force = 40 - -/obj/item/melee/touch_attack/psionic/rend/attack_self(mob/user, modifiers) - . = ..() - var/datum/action/cooldown/spell/touch/psionic/rend/hand_spell = spell_which_made_us?.resolve() - if(hand_spell.structure_mode) - hand_spell.structure_mode = FALSE - else if(!hand_spell.structure_mode) - hand_spell.structure_mode = TRUE - to_chat(user, span_horizonblue("structure mode turned on!")) - -/obj/item/melee/touch_attack/psionic/rend/attack(mob/living/target_mob, mob/living/user, list/modifiers, list/attack_modifiers) - . = ..() - var/datum/action/cooldown/spell/touch/psionic/rend/hand_spell = spell_which_made_us?.resolve() - if(hand_spell.structure_mode) - return FALSE From 9293ae909714ba92d6eadb14078e481cf4da2247 Mon Sep 17 00:00:00 2001 From: SaukyKouko Date: Fri, 22 May 2026 17:31:11 +0300 Subject: [PATCH 09/22] =?UTF-8?q?=D0=BD=D0=BE=D0=B2=D0=B0=D1=8F=20=D0=B0?= =?UTF-8?q?=D0=B1=D0=B8=D0=BB=D0=BA=D0=B0,=20=D0=B8=D1=81=D0=BF=D1=80?= =?UTF-8?q?=D0=B0=D0=B2=D0=BB=D0=B5=D0=BD=D0=B8=D0=B5=20=D0=B1=D0=B0=D0=B3?= =?UTF-8?q?=D0=BE=D0=B2?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- code/__DEFINES/~ff_defines/psionic.dm | 1 + tff_modular/modules/psionics/code/loner.dm | 10 +- .../modules/psionics/code/pointed/pointed.dm | 105 ++++++++++++++++++ .../modules/psionics/code/spell/spell.dm | 97 ---------------- 4 files changed, 111 insertions(+), 102 deletions(-) diff --git a/code/__DEFINES/~ff_defines/psionic.dm b/code/__DEFINES/~ff_defines/psionic.dm index fab60f9dfc1..5146011f187 100644 --- a/code/__DEFINES/~ff_defines/psionic.dm +++ b/code/__DEFINES/~ff_defines/psionic.dm @@ -2,6 +2,7 @@ #define TRAIT_PSIONIC_IMPLANT "psionic_implant" #define TRAIT_PSIONIC_SUPPRESSED "psionic_suppressed" #define TRAIT_PSIONIC_EXHAUSTION "psionic_exhaustion" +#define TRAIT_ZONA_BOVINAE_ABSORBED "zona_bovinae_absorbed" #define SUNDER_TRAIT "sunder_trait" #define PSIONIC_TRAIT "psionic_trait" #define HUD_PSI_DISPLAY "hud_psi_display" diff --git a/tff_modular/modules/psionics/code/loner.dm b/tff_modular/modules/psionics/code/loner.dm index 3c128ad3e6c..98b4cad9690 100644 --- a/tff_modular/modules/psionics/code/loner.dm +++ b/tff_modular/modules/psionics/code/loner.dm @@ -1,12 +1,12 @@ -/datum/antagonist/loner +/datum/antagonist/traitor/loner name = "\improper Loner" - antagpanel_category = ANTAG_GROUP_SYNDICATE - show_in_antagpanel = TRUE stinger_sound = 'tff_modular/modules/psionics/sounds/power_unlock.ogg' + give_uplink = FALSE + uplink_flag_given = NONE var/datum/psionic/psi var/previosly_psionic = FALSE -/datum/antagonist/loner/apply_innate_effects(mob/living/mob_override) +/datum/antagonist/traitor/loner/apply_innate_effects(mob/living/mob_override) . = ..() var/mob/living/carbon/current_owner = mob_override || owner.current if(current_owner.get_psionic()) @@ -14,7 +14,7 @@ previosly_psionic = TRUE current_owner.add_psionic(/datum/psionic/harmonious) -/datum/antagonist/loner/remove_innate_effects(mob/living/mob_override) +/datum/antagonist/traitor/loner/remove_innate_effects(mob/living/mob_override) . = ..() var/mob/living/carbon/current_owner = mob_override || owner.current current_owner.remove_psionic() diff --git a/tff_modular/modules/psionics/code/pointed/pointed.dm b/tff_modular/modules/psionics/code/pointed/pointed.dm index 368b1317850..c7b84a83035 100644 --- a/tff_modular/modules/psionics/code/pointed/pointed.dm +++ b/tff_modular/modules/psionics/code/pointed/pointed.dm @@ -594,3 +594,108 @@ . = ..() ADD_TRAIT(owner, TRAIT_THERMAL_VISION, PSIONIC_TRAIT) owner.update_sight() + + +/datum/action/cooldown/spell/pointed/psionic/zona_bovinae + name = "Zona Bovinae Absorption" + desc = "Absorb a psionic energy from a being's Zona Bovinae, granting you an extra point to be used in the Point Shop. The victim will not be able to make it psionic energy stronger in future." + button_icon_state = "tech_illusion" + mana_cost = 0 + cooldown_time = 10 SECONDS + point_cost = 1 + locked = FALSE + psionic_level = 2 + category = "Tier 2" + cast_range = 2 + +/datum/action/cooldown/spell/pointed/psionic/zona_bovinae/is_valid_target(atom/cast_on) + . = ..() + var/mob/living/carbon/human/victim = cast_on + if(!iscarbon(victim)) + to_chat(owner, span_horizonblue("Victim need to be a humanoid!")) + return FALSE + if(!victim.mind) + to_chat(owner, span_horizonblue("Victim need to have mind!")) + return FALSE + if(victim.stat == DEAD) + to_chat(owner, span_horizonblue("There is nothing interesting...")) + return FALSE + return TRUE + +/datum/action/cooldown/spell/pointed/psionic/zona_bovinae/cast(atom/cast_on) + . = ..() + var/mob/living/carbon/human/absorber = owner + var/mob/living/carbon/human/victim = cast_on + to_chat(absorber, span_horizonblue("You're trying to get into the [victim]'s mind...")) + to_chat(victim, span_horizonblue("You feel like [absorber] entering your mind...")) + if(!do_after(absorber, 10 SECONDS, victim, extra_checks=CALLBACK(src, PROC_REF(still_near)))) + return FALSE + victim.adjust_organ_loss(ORGAN_SLOT_BRAIN, 10, 80) + victim.Paralyze(8 SECONDS) + to_chat(absorber, span_horizonblue("You're trying to get [victim]'s memories...")) + to_chat(victim, span_horizonblue("You feel like [absorber] touching your memories...")) + victim.adjust_organ_loss(ORGAN_SLOT_BRAIN, 20, 80) + victim.Paralyze(8 SECONDS) + if(!do_after(absorber, 10 SECONDS, victim, extra_checks=CALLBACK(src, PROC_REF(still_near)))) + return FALSE + to_chat(absorber, span_horizonblue("You're trying to absorb [victim]'s Zona Bovinae...")) + to_chat(victim, span_horizonblue("You feel like [absorber] empties your mind...")) + if(!do_after(absorber, 10 SECONDS, victim, extra_checks=CALLBACK(src, PROC_REF(still_near)))) + return FALSE + victim.adjust_organ_loss(ORGAN_SLOT_BRAIN, 50, 80) + victim.Paralyze(8 SECONDS) + ADD_TRAIT(victim, TRAIT_ZONA_BOVINAE_ABSORBED, PSIONIC_TRAIT) + psionic_datum.psi_point += 1 + to_chat(absorber, span_horizonblue("You absorbed [victim]'s Zona Bovinae!")) + to_chat(victim, span_horizonblue("You feel like your mind shattered.")) + +/datum/action/cooldown/spell/pointed/psionic/zona_bovinae/proc/still_near(mob/living/carbon/human/absorber, mob/living/carbon/human/victim) + var/distance = get_dist(absorber, victim) + if(distance > cast_range) + return FALSE + return TRUE + +/datum/action/cooldown/spell/pointed/psionic/awakening + name = "Psionic Awakening" + desc = "Stimulate a living being's Zona Bovinae and bring them to Psionically Harmonious rank." + button_icon_state = "const_repairaura" + mana_cost = 0 + cooldown_time = 10 SECONDS + point_cost = 2 + locked = FALSE + psionic_level = 2 + category = "Tier 2" + cast_range = 2 + var/is_psionic = FALSE + +/datum/action/cooldown/spell/pointed/psionic/awakening/is_valid_target(atom/cast_on) + var/mob/living/victim = cast_on + var/datum/psionic/victim_psionic = victim.get_psionic() + if(!isliving(victim)) + return FALSE + if(istype(victim_psionic, /datum/psionic/harmonious)) + to_chat(owner, span_horizonblue("Their psi sensivity is strong enough!")) + return FALSE + if(HAS_TRAIT(victim, TRAIT_ZONA_BOVINAE_ABSORBED)) + to_chat(owner, span_horizonblue("Their psi sensivity is shattered!")) + return FALSE + return TRUE + +/datum/action/cooldown/spell/pointed/psionic/awakening/before_cast(atom/cast_on) + . = ..() + var/mob/living/victim = cast_on + if(victim.get_psionic()) + is_psionic = TRUE + +/datum/action/cooldown/spell/pointed/psionic/awakening/cast(atom/cast_on) + . = ..() + var/mob/living/victim = cast_on + to_chat(victim, span_horizonblue("You can feel your psionic energy getting stronger...")) + if(!do_after(owner, 10 SECONDS, victim)) + return FALSE + if(is_psionic) + victim.remove_psionic() + victim.add_psionic(/datum/psionic/harmonious) + victim.psi_sensivity.psi_point = 7 + else + victim.add_psionic(/datum/psionic/sensitive) diff --git a/tff_modular/modules/psionics/code/spell/spell.dm b/tff_modular/modules/psionics/code/spell/spell.dm index 22ed3f7decd..1b01e7befe4 100644 --- a/tff_modular/modules/psionics/code/spell/spell.dm +++ b/tff_modular/modules/psionics/code/spell/spell.dm @@ -311,64 +311,6 @@ return balloon_message -/datum/action/cooldown/spell/pointed/psionic/zona_bovinae - name = "Zona Bovinae Absorption" - desc = "Absorb a psionic energy from a being's Zona Bovinae, granting you an extra point to be used in the Point Shop." - button_icon_state = "tech_illusion" - mana_cost = 0 - cooldown_time = 10 SECONDS - point_cost = 1 - locked = FALSE - psionic_level = 2 - category = "Tier 2" - cast_range = 2 - -/datum/action/cooldown/spell/pointed/psionic/zona_bovinae/is_valid_target(atom/cast_on) - . = ..() - var/mob/living/carbon/human/victim = cast_on - if(!iscarbon(victim)) - to_chat(owner, span_horizonblue("Victim need to be a humanoid!")) - return FALSE - if(!victim.mind) - to_chat(owner, span_horizonblue("Victim need to have mind!")) - return FALSE - if(victim.stat == DEAD) - to_chat(owner, span_horizonblue("There is nothing interesting...")) - return FALSE - return TRUE - -/datum/action/cooldown/spell/pointed/psionic/zona_bovinae/cast(atom/cast_on) - . = ..() - var/mob/living/carbon/human/absorber = owner - var/mob/living/carbon/human/victim = cast_on - to_chat(absorber, span_horizonblue("You're trying to get into the [victim]'s mind...")) - to_chat(victim, span_horizonblue("You feel like [absorber] entering your mind...")) - if(!do_after(absorber, 10 SECONDS, victim, extra_checks=CALLBACK(src, PROC_REF(still_near)))) - return FALSE - victim.adjust_organ_loss(ORGAN_SLOT_BRAIN, 10, 80) - victim.Paralyze(8 SECONDS) - to_chat(absorber, span_horizonblue("You're trying to get [victim]'s memories...")) - to_chat(victim, span_horizonblue("You feel like [absorber] touching your memories...")) - victim.adjust_organ_loss(ORGAN_SLOT_BRAIN, 20, 80) - victim.Paralyze(8 SECONDS) - if(!do_after(absorber, 10 SECONDS, victim, extra_checks=CALLBACK(src, PROC_REF(still_near)))) - return FALSE - to_chat(absorber, span_horizonblue("You trying to absorb [victim]'s Zona Bovinae...")) - to_chat(victim, span_horizonblue("You feel like [absorber] empties your mind...")) - if(!do_after(absorber, 10 SECONDS, victim, extra_checks=CALLBACK(src, PROC_REF(still_near)))) - return FALSE - victim.adjust_organ_loss(ORGAN_SLOT_BRAIN, 50, 80) - victim.Paralyze(8 SECONDS) - psionic_datum.psi_point += 1 - to_chat(absorber, span_horizonblue("You absorbed [victim]'s Zona Bovinae!")) - to_chat(victim, span_horizonblue("You feel like your mind shattered.")) - -/datum/action/cooldown/spell/pointed/psionic/zona_bovinae/proc/still_near(mob/living/carbon/human/absorber, mob/living/carbon/human/victim) - var/distance = get_dist(absorber, victim) - if(distance > cast_range) - return FALSE - return TRUE - /datum/action/cooldown/spell/psionic/shockwave name = "Psionic Shockwave" desc = "Create a wave of telekinetic energy to pummel the ground around you." @@ -413,42 +355,3 @@ var/list/default_immune_atoms = list() default_immune_atoms += cast_on new /obj/effect/timestop/magic(get_turf(cast_on), 1 * cast_power, 2 SECONDS * cast_power, default_immune_atoms) - -/datum/action/cooldown/spell/psionic/mirror_shade - name = "Mirror Shade" - desc = "Activate this spell to generate two psionic copies of yourself that will attack nearby mobs. These clones are not dense, will deal pain damage, and disappear \ - after 15 seconds." - button_icon_state = "wiz_jaunt" - point_cost = 2 - mana_cost = 30 - cooldown_time = 100 SECONDS - locked = FALSE - var/list/mob/mobs_to_attack = list() - -/datum/action/cooldown/spell/psionic/mirror_shade/cast(atom/cast_on) - . = ..() - for(var/mob/living/carbon/human as anything in view(7, cast_on)) - if(!iscarbon(human)) - continue - if(human.stat == DEAD) - continue - if(human == owner) - continue - - mobs_to_attack += human - - if(isnull(mobs_to_attack)) - return FALSE - - for(var/i in 1 to 2) - var/mob/living/basic/illusion/bizarro = new(owner.loc) - var/mob/who_to_attack = pick(mobs_to_attack) - bizarro.full_setup(owner, who_to_attack, faction_override = list(FACTION_PSIONIC), life = 15 SECONDS, damage = 20, replicate = 5) - bizarro.melee_damage_type = STAMINA - - owner.add_faction(FACTION_PSIONIC) - addtimer(CALLBACK(src, PROC_REF(stop_cast)), 15 SECONDS) - -/datum/action/cooldown/spell/psionic/mirror_shade/proc/stop_cast() - owner.remove_faction(FACTION_PSIONIC) - mobs_to_attack = null From 001ac45ab23eb76ddde15ac924db9a046a015ae9 Mon Sep 17 00:00:00 2001 From: SaukyKouko Date: Fri, 22 May 2026 17:31:55 +0300 Subject: [PATCH 10/22] =?UTF-8?q?=D0=9F=D0=B5=D1=80=D0=B5=D0=B8=D0=BC?= =?UTF-8?q?=D0=B5=D0=BD=D0=BE=D0=B2=D1=8B=D0=B2=D0=B0=D0=B5=D0=BC=20=D0=BF?= =?UTF-8?q?=D0=BE=D0=B4=20=D0=B1=D0=BE=D0=BB=D0=B5=D0=B5=20=D0=BF=D0=BE?= =?UTF-8?q?=D0=BD=D1=8F=D1=82=D0=BD=D0=BE=D0=B5=20=D0=BD=D0=B0=D0=B7=D0=B2?= =?UTF-8?q?=D0=B0=D0=BD=D0=B8=D0=B5?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- tff_modular/modules/psionics/code/loner.dm | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tff_modular/modules/psionics/code/loner.dm b/tff_modular/modules/psionics/code/loner.dm index 98b4cad9690..47dbe4ed378 100644 --- a/tff_modular/modules/psionics/code/loner.dm +++ b/tff_modular/modules/psionics/code/loner.dm @@ -1,5 +1,5 @@ /datum/antagonist/traitor/loner - name = "\improper Loner" + name = "\improper Psionic Agent" stinger_sound = 'tff_modular/modules/psionics/sounds/power_unlock.ogg' give_uplink = FALSE uplink_flag_given = NONE From 5619a65ef62185338cf480feb02fe41de8971127 Mon Sep 17 00:00:00 2001 From: SaukyKouko Date: Fri, 22 May 2026 17:40:59 +0300 Subject: [PATCH 11/22] =?UTF-8?q?=D0=AF=20=D0=BF=D0=B5=D1=80=D0=B5=D0=B4?= =?UTF-8?q?=D1=83=D0=BC=D0=B0=D0=BB?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- tff_modular/modules/psionics/code/loner.dm | 32 +++++++++++++++---- .../modules/psionics/code/pointed/pointed.dm | 2 +- 2 files changed, 27 insertions(+), 7 deletions(-) diff --git a/tff_modular/modules/psionics/code/loner.dm b/tff_modular/modules/psionics/code/loner.dm index 47dbe4ed378..4f6d4b28f2d 100644 --- a/tff_modular/modules/psionics/code/loner.dm +++ b/tff_modular/modules/psionics/code/loner.dm @@ -1,12 +1,14 @@ -/datum/antagonist/traitor/loner - name = "\improper Psionic Agent" +/datum/antagonist/loner + name = "\improper Loner" + antagpanel_category = ANTAG_GROUP_SYNDICATE + show_in_roundend = TRUE + roundend_category = "traitors" + show_in_antagpanel = TRUE stinger_sound = 'tff_modular/modules/psionics/sounds/power_unlock.ogg' - give_uplink = FALSE - uplink_flag_given = NONE var/datum/psionic/psi var/previosly_psionic = FALSE -/datum/antagonist/traitor/loner/apply_innate_effects(mob/living/mob_override) +/datum/antagonist/loner/apply_innate_effects(mob/living/mob_override) . = ..() var/mob/living/carbon/current_owner = mob_override || owner.current if(current_owner.get_psionic()) @@ -14,9 +16,27 @@ previosly_psionic = TRUE current_owner.add_psionic(/datum/psionic/harmonious) -/datum/antagonist/traitor/loner/remove_innate_effects(mob/living/mob_override) +/datum/antagonist/loner/remove_innate_effects(mob/living/mob_override) . = ..() var/mob/living/carbon/current_owner = mob_override || owner.current current_owner.remove_psionic() if(previosly_psionic) current_owner.add_psionic(/datum/psionic/sensitive) + +/datum/antagonist/loner/forge_objectives() + . = ..() + var/datum/objective/assassination = new /datum/objective/assassinate() + assassination.owner = owner + objectives += assassination + + var/datum/objective/thief = new /datum/objective/steal() + thief.owner = owner + objectives += thief + + var/datum/objective/protect = new /datum/objective/protect() + protect.owner = owner + objectives += protect + + var/datum/objective/escape = new /datum/objective/escape() + escape.owner = owner + objectives += escape diff --git a/tff_modular/modules/psionics/code/pointed/pointed.dm b/tff_modular/modules/psionics/code/pointed/pointed.dm index c7b84a83035..552e8f6bd5a 100644 --- a/tff_modular/modules/psionics/code/pointed/pointed.dm +++ b/tff_modular/modules/psionics/code/pointed/pointed.dm @@ -656,7 +656,7 @@ return TRUE /datum/action/cooldown/spell/pointed/psionic/awakening - name = "Psionic Awakening" + name = "Psionic Awaken" desc = "Stimulate a living being's Zona Bovinae and bring them to Psionically Harmonious rank." button_icon_state = "const_repairaura" mana_cost = 0 From 8302ec528e3b3de54d96039d4634c2e28c8c122e Mon Sep 17 00:00:00 2001 From: SaukyKouko Date: Fri, 22 May 2026 18:47:07 +0300 Subject: [PATCH 12/22] =?UTF-8?q?=D0=BE=D0=B1=D0=BD=D0=BE=D0=B2=D0=BB?= =?UTF-8?q?=D0=B5=D0=BD=D0=B8=D0=B5?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../modules/psionics/code/_psionic_datum.dm | 20 +++++++++---------- .../modules/psionics/code/pointed/pointed.dm | 2 +- .../psionics/code/projectiles/projectile.dm | 8 ++++++-- .../modules/psionics/code/spell/spell.dm | 14 ++++++++++++- 4 files changed, 30 insertions(+), 14 deletions(-) diff --git a/tff_modular/modules/psionics/code/_psionic_datum.dm b/tff_modular/modules/psionics/code/_psionic_datum.dm index c74339ded79..0f143c80781 100644 --- a/tff_modular/modules/psionics/code/_psionic_datum.dm +++ b/tff_modular/modules/psionics/code/_psionic_datum.dm @@ -12,8 +12,6 @@ quirk_flags = QUIRK_HIDE_FROM_SCAN|QUIRK_HUMAN_ONLY|QUIRK_PROCESSES // Сканеры не видят псиоников. Только псионик школы может точно определить, является ли живое существо псиоником gain_text = span_cyan("You mind feels uneasy, but... so powerful.") lose_text = span_warning("You lost something that kept your connection with other realms.") - icon = "fa-star" - mob_trait = TRAIT_PSIONIC_USER nova_stars_only = TRUE allow_for_donator = TRUE @@ -47,7 +45,7 @@ /mob/living/proc/get_psionic() if(!psi_sensivity) return FALSE - return psi_sensivity || TRUE + return psi_sensivity /datum/psionic // Текущий владелец псионики @@ -81,16 +79,16 @@ CRASH("Tried to add psionic without owner") psi_owner = granted_to - psi_owner.psi_sensivity = src - - RegisterSignal(psi_owner, COMSIG_MOB_HUD_CREATED, PROC_REF(on_hud_created)) - RegisterSignal(psi_owner, COMSIG_LIVING_LIFE, PROC_REF(psionic_life)) + granted_to.psi_sensivity = src + ADD_TRAIT(granted_to, TRAIT_PSIONIC_USER, PSIONIC_TRAIT) + RegisterSignal(granted_to, COMSIG_MOB_HUD_CREATED, PROC_REF(on_hud_created)) + RegisterSignal(granted_to, COMSIG_LIVING_LIFE, PROC_REF(psionic_life)) if(license) - var/obj/item/card/psionic_license/new_license = new(psi_owner) + var/obj/item/card/psionic_license/new_license = new(granted_to) give_item_to_holder(new_license, list(LOCATION_BACKPACK = ITEM_SLOT_BACK, LOCATION_HANDS = ITEM_SLOT_HANDS), flavour_text = "Make sure not to lose it. You can not remake this on the station.") - if(psi_owner.hud_used) + if(granted_to.hud_used) on_hud_created() add_shop() @@ -125,7 +123,7 @@ /datum/psionic/Destroy(force) . = ..() UnregisterSignal(psi_owner, COMSIG_MOB_HUD_CREATED) - RegisterSignal(psi_owner, COMSIG_LIVING_LIFE, PROC_REF(psionic_life)) + UnregisterSignal(psi_owner, COMSIG_LIVING_LIFE, PROC_REF(psionic_life)) QDEL_NULL(psi_shop_action) QDEL_NULL(psi_shop_datum) for(var/datum/action/cooldown/spell/spells_to_remove in psi_owner.actions) @@ -138,6 +136,8 @@ psi_hud.remove_screen_object(HUD_PSI_DISPLAY) psi_hud.remove_screen_object(HUD_PSI_SIGNAL) + REMOVE_TRAIT(psi_owner, TRAIT_PSIONIC_USER, PSIONIC_TRAIT) + /datum/psionic/proc/psionic_life(seconds_per_tick) SIGNAL_HANDLER diff --git a/tff_modular/modules/psionics/code/pointed/pointed.dm b/tff_modular/modules/psionics/code/pointed/pointed.dm index 552e8f6bd5a..0904e708dd4 100644 --- a/tff_modular/modules/psionics/code/pointed/pointed.dm +++ b/tff_modular/modules/psionics/code/pointed/pointed.dm @@ -684,7 +684,7 @@ /datum/action/cooldown/spell/pointed/psionic/awakening/before_cast(atom/cast_on) . = ..() var/mob/living/victim = cast_on - if(victim.get_psionic()) + if(HAS_TRAIT(cast_on, TRAIT_PSIONIC_USER)) is_psionic = TRUE /datum/action/cooldown/spell/pointed/psionic/awakening/cast(atom/cast_on) diff --git a/tff_modular/modules/psionics/code/projectiles/projectile.dm b/tff_modular/modules/psionics/code/projectiles/projectile.dm index 5aeeb503848..58f6858247f 100644 --- a/tff_modular/modules/psionics/code/projectiles/projectile.dm +++ b/tff_modular/modules/psionics/code/projectiles/projectile.dm @@ -37,7 +37,7 @@ click_cd_override = 1 cooldown_time = 20 SECONDS psionic_level = 2 - mana_cost = 30 + mana_cost = 50 point_cost = 3 cast_range = 40 locked = FALSE @@ -53,6 +53,10 @@ /// The location spread of the spell cards when fired. var/projectile_location_spread_amount = 12 +/datum/action/cooldown/spell/pointed/projectile/psionic/lighting/can_cast_spell(feedback) + . = ..() + if(HAS_TRAIT(owner, TRAIT_INCAPACITATED)) + return FALSE /datum/action/cooldown/spell/pointed/projectile/psionic/lighting/ready_projectile(obj/projectile/to_fire, atom/target, mob/user, iteration) . = ..() @@ -72,7 +76,7 @@ /obj/projectile/beam/emitter/hitscan/lighting name = "psionic lightning" - damage = 20 + damage = 10 armour_penetration = 30 damage_type = BURN pass_flags = PASSTABLE | PASSGRILLE | PASSGLASS diff --git a/tff_modular/modules/psionics/code/spell/spell.dm b/tff_modular/modules/psionics/code/spell/spell.dm index 1b01e7befe4..e0942a5b60c 100644 --- a/tff_modular/modules/psionics/code/spell/spell.dm +++ b/tff_modular/modules/psionics/code/spell/spell.dm @@ -322,6 +322,12 @@ locked = FALSE psionic_level = 2 +/datum/action/cooldown/spell/psionic/shockwave/can_cast_spell(feedback) + . = ..() + if(HAS_TRAIT(owner, TRAIT_INCAPACITATED)) + return FALSE + return TRUE + /datum/action/cooldown/spell/psionic/shockwave/before_cast(atom/cast_on) . = ..() if(isspaceturf(get_turf(cast_on))) @@ -354,4 +360,10 @@ . = ..() var/list/default_immune_atoms = list() default_immune_atoms += cast_on - new /obj/effect/timestop/magic(get_turf(cast_on), 1 * cast_power, 2 SECONDS * cast_power, default_immune_atoms) + new /obj/effect/timestop/magic(get_turf(cast_on), 1, 2 SECONDS * cast_power, default_immune_atoms) + +/datum/action/cooldown/spell/psionic/time_stop/can_cast_spell(feedback) + . = ..() + if(HAS_TRAIT(owner, TRAIT_INCAPACITATED)) + return FALSE + return TRUE From c02fa4d6d8eed78132f4b15f984cad41cc1f50d7 Mon Sep 17 00:00:00 2001 From: SaukyKouko Date: Fri, 22 May 2026 18:52:03 +0300 Subject: [PATCH 13/22] =?UTF-8?q?=D0=B8=D1=81=D0=BF=D1=80=D0=B0=D0=B2?= =?UTF-8?q?=D0=BB=D1=8F=D0=B5=D0=BC=20=D0=BE=D1=88=D0=B8=D0=B1=D0=BA=D1=83?= =?UTF-8?q?=20=D1=82=D0=B5=D1=81=D1=82=D0=BE=D0=B2?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- tff_modular/modules/psionics/code/pointed/pointed.dm | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tff_modular/modules/psionics/code/pointed/pointed.dm b/tff_modular/modules/psionics/code/pointed/pointed.dm index 0904e708dd4..2d22051f4f3 100644 --- a/tff_modular/modules/psionics/code/pointed/pointed.dm +++ b/tff_modular/modules/psionics/code/pointed/pointed.dm @@ -684,7 +684,7 @@ /datum/action/cooldown/spell/pointed/psionic/awakening/before_cast(atom/cast_on) . = ..() var/mob/living/victim = cast_on - if(HAS_TRAIT(cast_on, TRAIT_PSIONIC_USER)) + if(HAS_TRAIT(victim, TRAIT_PSIONIC_USER)) is_psionic = TRUE /datum/action/cooldown/spell/pointed/psionic/awakening/cast(atom/cast_on) From 57f06715ed286839109fd2623a7cb8bc3331b73f Mon Sep 17 00:00:00 2001 From: SaukyKouko Date: Fri, 22 May 2026 19:38:41 +0300 Subject: [PATCH 14/22] =?UTF-8?q?=D0=B8=D1=81=D0=BF=D1=80=D0=B0=D0=B2?= =?UTF-8?q?=D0=BB=D0=B5=D0=BD=D0=B8=D0=B5=20=D0=B1=D0=B0=D0=B3=D0=B0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- tff_modular/modules/psionics/code/_psionic_datum.dm | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/tff_modular/modules/psionics/code/_psionic_datum.dm b/tff_modular/modules/psionics/code/_psionic_datum.dm index 0f143c80781..edd3145bb86 100644 --- a/tff_modular/modules/psionics/code/_psionic_datum.dm +++ b/tff_modular/modules/psionics/code/_psionic_datum.dm @@ -7,13 +7,14 @@ Your mind can access the world that lies beyond our mortal plane. One day voices from within had pierced your skull \ like a tide wave turns a sailboat over in open sea, but you withstanded it and received abilities your father haven't \ even dreamed of. From now on a special type of energy is stored in your mind, body and soul and you have control over it." - value = 0 + value = 8 medical_record_text = "Patient possesses connection to another plain of reality." quirk_flags = QUIRK_HIDE_FROM_SCAN|QUIRK_HUMAN_ONLY|QUIRK_PROCESSES // Сканеры не видят псиоников. Только псионик школы может точно определить, является ли живое существо псиоником gain_text = span_cyan("You mind feels uneasy, but... so powerful.") lose_text = span_warning("You lost something that kept your connection with other realms.") nova_stars_only = TRUE allow_for_donator = TRUE + icon = "fa-star" /datum/quirk/psionic/add(client/client_source) quirk_holder.add_psionic(/datum/psionic/sensitive) From 225f497f679940d8fab14cb585e0c69e29b8550b Mon Sep 17 00:00:00 2001 From: SaukyKouko Date: Sat, 23 May 2026 04:05:10 +0300 Subject: [PATCH 15/22] =?UTF-8?q?=D1=84=D0=B8=D0=BB=D1=8C=D1=82=D1=80?= =?UTF-8?q?=D1=83=D0=B5=D0=BC=20=D1=81=D0=BF=D0=B5=D0=BB=D0=BB=D1=8B,=20?= =?UTF-8?q?=D0=BE=D0=B1=D0=BD=D0=BE=D0=B2=D0=BB=D1=8F=D0=B5=D0=BC=20=D1=84?= =?UTF-8?q?=D0=BE=D0=BA=D1=83=D1=81=20=D0=B8=20=D0=B4=D0=BE=D0=B1=D0=B0?= =?UTF-8?q?=D0=B2=D0=BB=D1=8F=D0=B5=D0=BC=20=D0=BF=D0=B0=D1=80=D1=83=20?= =?UTF-8?q?=D0=BD=D0=BE=D0=B2=D1=8B=D1=85?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- code/__DEFINES/~ff_defines/psionic.dm | 2 + .../psionics/code/_psionic_abilities.dm | 4 - .../modules/psionics/code/_psionic_datum.dm | 33 +- tff_modular/modules/psionics/code/admin.dm | 26 + .../psionics/code/conjure_item/blade.dm | 37 + .../{conjure_item.dm => omnitool.dm} | 37 - tff_modular/modules/psionics/code/loner.dm | 16 +- .../modules/psionics/code/pointed/armour.dm | 57 ++ .../modules/psionics/code/pointed/awaken.dm | 48 ++ .../modules/psionics/code/pointed/bubble.dm | 61 ++ .../modules/psionics/code/pointed/drain.dm | 32 + .../code/pointed/emotional_suggestion.dm | 46 ++ .../psionics/code/pointed/expansion.dm | 48 ++ .../modules/psionics/code/pointed/jump.dm | 32 + .../psionics/code/pointed/mind_muddle.dm | 24 + .../modules/psionics/code/pointed/pointed.dm | 701 ------------------ .../modules/psionics/code/pointed/pull.dm | 37 + .../psionics/code/pointed/rejuvenate.dm | 52 ++ .../psionics/code/pointed/skinsight.dm | 31 + .../modules/psionics/code/pointed/spasm.dm | 34 + .../modules/psionics/code/pointed/stasis.dm | 28 + .../modules/psionics/code/pointed/throw.dm | 49 ++ .../modules/psionics/code/pointed/warp.dm | 55 ++ .../psionics/code/pointed/zona_bovinae.dm | 58 ++ .../psionics/code/projectiles/air_bullet.dm | 30 + .../{projectile.dm => lighting.dm} | 31 - tff_modular/modules/psionics/code/quirk.dm | 21 + .../modules/psionics/code/spell/charge.dm | 54 ++ .../modules/psionics/code/spell/focus.dm | 55 ++ .../modules/psionics/code/spell/ion_blast.dm | 16 + .../modules/psionics/code/spell/nlom_eyes.dm | 97 +++ .../modules/psionics/code/spell/search.dm | 32 + .../modules/psionics/code/spell/shockwave.dm | 33 + .../modules/psionics/code/spell/signal.dm | 37 + .../modules/psionics/code/spell/spell.dm | 369 --------- .../modules/psionics/code/spell/stamina.dm | 27 + .../modules/psionics/code/spell/sunder.dm | 34 + .../psionics/code/spell/suppression.dm | 19 + .../modules/psionics/code/spell/time_stop.dm | 22 + .../psionics/code/spell/transparency.dm | 68 ++ .../modules/psionics/code/touch/assay.dm | 45 ++ .../psionics/code/touch/electrocute.dm | 22 + .../modules/psionics/code/touch/mending.dm | 62 ++ .../code/touch/{touch.dm => mind_read.dm} | 133 ---- tgstation.dme | 43 +- .../tgui/interfaces/AntagInfoLoner.tsx | 77 ++ 46 files changed, 1565 insertions(+), 1310 deletions(-) create mode 100644 tff_modular/modules/psionics/code/admin.dm create mode 100644 tff_modular/modules/psionics/code/conjure_item/blade.dm rename tff_modular/modules/psionics/code/conjure_item/{conjure_item.dm => omnitool.dm} (65%) create mode 100644 tff_modular/modules/psionics/code/pointed/armour.dm create mode 100644 tff_modular/modules/psionics/code/pointed/awaken.dm create mode 100644 tff_modular/modules/psionics/code/pointed/bubble.dm create mode 100644 tff_modular/modules/psionics/code/pointed/drain.dm create mode 100644 tff_modular/modules/psionics/code/pointed/emotional_suggestion.dm create mode 100644 tff_modular/modules/psionics/code/pointed/expansion.dm create mode 100644 tff_modular/modules/psionics/code/pointed/jump.dm create mode 100644 tff_modular/modules/psionics/code/pointed/mind_muddle.dm delete mode 100644 tff_modular/modules/psionics/code/pointed/pointed.dm create mode 100644 tff_modular/modules/psionics/code/pointed/pull.dm create mode 100644 tff_modular/modules/psionics/code/pointed/rejuvenate.dm create mode 100644 tff_modular/modules/psionics/code/pointed/skinsight.dm create mode 100644 tff_modular/modules/psionics/code/pointed/spasm.dm create mode 100644 tff_modular/modules/psionics/code/pointed/stasis.dm create mode 100644 tff_modular/modules/psionics/code/pointed/throw.dm create mode 100644 tff_modular/modules/psionics/code/pointed/warp.dm create mode 100644 tff_modular/modules/psionics/code/pointed/zona_bovinae.dm create mode 100644 tff_modular/modules/psionics/code/projectiles/air_bullet.dm rename tff_modular/modules/psionics/code/projectiles/{projectile.dm => lighting.dm} (67%) create mode 100644 tff_modular/modules/psionics/code/quirk.dm create mode 100644 tff_modular/modules/psionics/code/spell/charge.dm create mode 100644 tff_modular/modules/psionics/code/spell/focus.dm create mode 100644 tff_modular/modules/psionics/code/spell/ion_blast.dm create mode 100644 tff_modular/modules/psionics/code/spell/nlom_eyes.dm create mode 100644 tff_modular/modules/psionics/code/spell/search.dm create mode 100644 tff_modular/modules/psionics/code/spell/shockwave.dm create mode 100644 tff_modular/modules/psionics/code/spell/signal.dm delete mode 100644 tff_modular/modules/psionics/code/spell/spell.dm create mode 100644 tff_modular/modules/psionics/code/spell/stamina.dm create mode 100644 tff_modular/modules/psionics/code/spell/sunder.dm create mode 100644 tff_modular/modules/psionics/code/spell/suppression.dm create mode 100644 tff_modular/modules/psionics/code/spell/time_stop.dm create mode 100644 tff_modular/modules/psionics/code/spell/transparency.dm create mode 100644 tff_modular/modules/psionics/code/touch/assay.dm create mode 100644 tff_modular/modules/psionics/code/touch/electrocute.dm create mode 100644 tff_modular/modules/psionics/code/touch/mending.dm rename tff_modular/modules/psionics/code/touch/{touch.dm => mind_read.dm} (55%) create mode 100644 tgui/packages/tgui/interfaces/AntagInfoLoner.tsx diff --git a/code/__DEFINES/~ff_defines/psionic.dm b/code/__DEFINES/~ff_defines/psionic.dm index 5146011f187..dbcbf1b977e 100644 --- a/code/__DEFINES/~ff_defines/psionic.dm +++ b/code/__DEFINES/~ff_defines/psionic.dm @@ -3,9 +3,11 @@ #define TRAIT_PSIONIC_SUPPRESSED "psionic_suppressed" #define TRAIT_PSIONIC_EXHAUSTION "psionic_exhaustion" #define TRAIT_ZONA_BOVINAE_ABSORBED "zona_bovinae_absorbed" +#define TRAIT_PSIONIC_INFLUENCED "psionic_influenced" #define SUNDER_TRAIT "sunder_trait" #define PSIONIC_TRAIT "psionic_trait" #define HUD_PSI_DISPLAY "hud_psi_display" #define HUD_PSI_SIGNAL "hud_psi_signal" #define HUD_PSIONIC_ARROW "psionic_arrow" #define FACTION_PSIONIC "faction_psionic" +#define VV_HK_GIVE_PSIONIC "give_psionic" diff --git a/tff_modular/modules/psionics/code/_psionic_abilities.dm b/tff_modular/modules/psionics/code/_psionic_abilities.dm index 9c578087bbc..708b311ad56 100644 --- a/tff_modular/modules/psionics/code/_psionic_abilities.dm +++ b/tff_modular/modules/psionics/code/_psionic_abilities.dm @@ -50,10 +50,6 @@ cooldown_reduction_per_rank = 0 SECONDS psionic = TRUE -/datum/action/cooldown/spell/conjure_item/psionic/New(Target, power) - . = ..() - cast_power = power - // Проверяем достаточно ли маны /datum/action/cooldown/spell/proc/check_for_mana() var/mob/living/carbon/human/caster = owner diff --git a/tff_modular/modules/psionics/code/_psionic_datum.dm b/tff_modular/modules/psionics/code/_psionic_datum.dm index edd3145bb86..d6a4c7a1096 100644 --- a/tff_modular/modules/psionics/code/_psionic_datum.dm +++ b/tff_modular/modules/psionics/code/_psionic_datum.dm @@ -1,38 +1,16 @@ #define SENSITIVE_PSIONIC "Sensitive Psionic" #define HARMONIOUS_PSIONIC "Harmonious Psionic" -/datum/quirk/psionic - name = "Psionic Abilities" - desc = "Either you were born like this or gained powers from implants/training or other events - you are a psionic. \ - Your mind can access the world that lies beyond our mortal plane. One day voices from within had pierced your skull \ - like a tide wave turns a sailboat over in open sea, but you withstanded it and received abilities your father haven't \ - even dreamed of. From now on a special type of energy is stored in your mind, body and soul and you have control over it." - value = 8 - medical_record_text = "Patient possesses connection to another plain of reality." - quirk_flags = QUIRK_HIDE_FROM_SCAN|QUIRK_HUMAN_ONLY|QUIRK_PROCESSES // Сканеры не видят псиоников. Только псионик школы может точно определить, является ли живое существо псиоником - gain_text = span_cyan("You mind feels uneasy, but... so powerful.") - lose_text = span_warning("You lost something that kept your connection with other realms.") - nova_stars_only = TRUE - allow_for_donator = TRUE - icon = "fa-star" - -/datum/quirk/psionic/add(client/client_source) - quirk_holder.add_psionic(/datum/psionic/sensitive) - -/datum/quirk/psionic/remove() - . = ..() - quirk_holder.remove_psionic() - /mob/living var/datum/psionic/psi_sensivity -/mob/living/proc/add_psionic(psi_type) +/mob/living/proc/add_psionic(psi_type, remove_old) if(!psi_type) psi_type = new /datum/psionic/sensitive() - if(istype(psi_type, /datum/psionic)) + if(!istype(psi_type, /datum/psionic)) return FALSE if(get_psionic()) - return FALSE + remove_psionic() var/datum/psionic/new_psi = new psi_type() new_psi.apply_to(src) @@ -171,12 +149,15 @@ mana_level = clamp(mana_level + amount, 0, max_mana) /datum/psionic/sensitive - max_mana = 25 + max_mana = 50 psionic_level = 1 psionic_level_string = SENSITIVE_PSIONIC license = TRUE psi_point = 7 +/datum/psionic/sensitive/no_license + license = FALSE + /datum/psionic/harmonious max_mana = 100 psionic_level = 2 diff --git a/tff_modular/modules/psionics/code/admin.dm b/tff_modular/modules/psionics/code/admin.dm new file mode 100644 index 00000000000..10d4bd1d788 --- /dev/null +++ b/tff_modular/modules/psionics/code/admin.dm @@ -0,0 +1,26 @@ + +/mob/living/vv_get_dropdown() + . = ..() + VV_DROPDOWN_OPTION(VV_HK_GIVE_PSIONIC, "Give Psionic") + +/mob/living/vv_do_topic(list/href_list) + . = ..() + + if(!.) + return + + if(href_list[VV_HK_GIVE_PSIONIC]) + admin_give_psionic(usr) + +/mob/living/proc/admin_give_psionic(mob/admin) + if(!admin || !check_rights(NONE)) + return + var/picked_type = tgui_input_list(admin, "Pick the psionic type.", "Psionic Controller", subtypesof(/datum/psionic)) + if(tgui_alert(admin, "Confirm creation.", "Psionic Controller", list("Yes", "No")) != "Yes") + return + var/datum/psionic/new_psionic = picked_type + add_psionic(new_psionic) + message_admins(span_adminnotice("[key_name_admin(admin)] gave a psionic powers of tier [new_psionic.get_level()] to [src].")) + log_admin("[key_name(admin)] gave a psionic powers of tier [new_psionic.get_level()] to [src].") + BLACKBOX_LOG_ADMIN_VERB("Give Psionic") + diff --git a/tff_modular/modules/psionics/code/conjure_item/blade.dm b/tff_modular/modules/psionics/code/conjure_item/blade.dm new file mode 100644 index 00000000000..941712f2010 --- /dev/null +++ b/tff_modular/modules/psionics/code/conjure_item/blade.dm @@ -0,0 +1,37 @@ +// Спавнит пси-клинок в руке. Сила зависит от уровня псионика +/datum/action/cooldown/spell/conjure_item/psionic/psiblade + name = "Psionic blade" + desc = "Concentrates psionic energy to create a sharp blade in your hand." + button_icon = 'icons/obj/weapons/transforming_energy.dmi' + button_icon_state = "blade" + cooldown_time = 1.5 SECONDS + item_type = /obj/item/melee/psionic_blade + mana_cost = 40 + psionic_level = 2 + point_cost = 3 + category = "Tier 2" + locked = FALSE + +/obj/item/melee/psionic_blade + name = "psionic blade" + desc = "A concentrated collection of particles and energy that looks like a swords blade.." + icon = 'icons/obj/weapons/transforming_energy.dmi' + icon_state = "blade" + inhand_icon_state = "blade" + lefthand_file = 'icons/mob/inhands/weapons/swords_lefthand.dmi' + righthand_file = 'icons/mob/inhands/weapons/swords_righthand.dmi' + w_class = WEIGHT_CLASS_HUGE + force = 25 + armour_penetration = 30 + throwforce = 10 + hitsound = 'sound/items/weapons/blade1.ogg' + attack_verb_continuous = list("attacks", "slashes", "stabs", "slices", "tears", "lacerates", "rips", "dices", "cuts") + attack_verb_simple = list("attack", "slash", "stab", "slice", "tear", "lacerate", "rip", "dice", "cut") + sharpness = SHARP_EDGED + block_chance = 50 + item_flags = DROPDEL | ABSTRACT | HAND_ITEM + color = COLOR_BRIGHT_BLUE + +/obj/item/melee/psionic_blade/New(loc, power) + . = ..() + ADD_TRAIT(src, TRAIT_EXAMINE_SKIP, INNATE_TRAIT) diff --git a/tff_modular/modules/psionics/code/conjure_item/conjure_item.dm b/tff_modular/modules/psionics/code/conjure_item/omnitool.dm similarity index 65% rename from tff_modular/modules/psionics/code/conjure_item/conjure_item.dm rename to tff_modular/modules/psionics/code/conjure_item/omnitool.dm index b74a5ee2b8d..ac5b68698fe 100644 --- a/tff_modular/modules/psionics/code/conjure_item/conjure_item.dm +++ b/tff_modular/modules/psionics/code/conjure_item/omnitool.dm @@ -1,40 +1,3 @@ -// Спавнит пси-клинок в руке. Сила зависит от уровня псионика -/datum/action/cooldown/spell/conjure_item/psionic/psiblade - name = "Psionic blade" - desc = "Concentrates psionic energy to create a sharp blade in your hand." - button_icon = 'icons/obj/weapons/transforming_energy.dmi' - button_icon_state = "blade" - cooldown_time = 1.5 SECONDS - item_type = /obj/item/melee/psionic_blade - mana_cost = 40 - psionic_level = 2 - point_cost = 3 - category = "Tier 2" - locked = FALSE - -/obj/item/melee/psionic_blade - name = "psionic blade" - desc = "A concentrated collection of particles and energy that looks like a swords blade.." - icon = 'icons/obj/weapons/transforming_energy.dmi' - icon_state = "blade" - inhand_icon_state = "blade" - lefthand_file = 'icons/mob/inhands/weapons/swords_lefthand.dmi' - righthand_file = 'icons/mob/inhands/weapons/swords_righthand.dmi' - w_class = WEIGHT_CLASS_HUGE - force = 30 - armour_penetration = 30 - throwforce = 10 - hitsound = 'sound/items/weapons/blade1.ogg' - attack_verb_continuous = list("attacks", "slashes", "stabs", "slices", "tears", "lacerates", "rips", "dices", "cuts") - attack_verb_simple = list("attack", "slash", "stab", "slice", "tear", "lacerate", "rip", "dice", "cut") - sharpness = SHARP_EDGED - block_chance = 50 - item_flags = DROPDEL | ABSTRACT | HAND_ITEM - color = COLOR_BRIGHT_BLUE - -/obj/item/melee/psionic_blade/New(loc, power) - . = ..() - ADD_TRAIT(src, TRAIT_EXAMINE_SKIP, INNATE_TRAIT) // Спавнит омни инструмент в руке псионика. Аналог абдукторского /datum/action/cooldown/spell/conjure_item/psionic/psitool diff --git a/tff_modular/modules/psionics/code/loner.dm b/tff_modular/modules/psionics/code/loner.dm index 4f6d4b28f2d..e3c56658aca 100644 --- a/tff_modular/modules/psionics/code/loner.dm +++ b/tff_modular/modules/psionics/code/loner.dm @@ -5,6 +5,7 @@ roundend_category = "traitors" show_in_antagpanel = TRUE stinger_sound = 'tff_modular/modules/psionics/sounds/power_unlock.ogg' + ui_name = "AntagInfoLoner" var/datum/psionic/psi var/previosly_psionic = FALSE @@ -25,18 +26,25 @@ /datum/antagonist/loner/forge_objectives() . = ..() - var/datum/objective/assassination = new /datum/objective/assassinate() + var/datum/objective/assassinate/assassination = new /datum/objective/assassinate() assassination.owner = owner objectives += assassination - var/datum/objective/thief = new /datum/objective/steal() + var/datum/objective/steal/thief = new /datum/objective/steal() thief.owner = owner objectives += thief - var/datum/objective/protect = new /datum/objective/protect() + var/datum/objective/protect/protect = new /datum/objective/protect() protect.owner = owner objectives += protect - var/datum/objective/escape = new /datum/objective/escape() + var/datum/objective/escape/escape = new /datum/objective/escape() escape.owner = owner objectives += escape + +/datum/antagonist/loner/greet() + . = ..() + to_chat(owner, "You Are a Psi Agent, Loner. You possess high-power psionic abilities that can strongly influence the space around you. \n\ + You were trained by the syndicate as part of an experiment and must show the best results in completing the tasks assigned to you. \n\ + Glory to the Syndicate!") + owner.announce_objectives() diff --git a/tff_modular/modules/psionics/code/pointed/armour.dm b/tff_modular/modules/psionics/code/pointed/armour.dm new file mode 100644 index 00000000000..7b7d9ba50d4 --- /dev/null +++ b/tff_modular/modules/psionics/code/pointed/armour.dm @@ -0,0 +1,57 @@ +/datum/action/cooldown/spell/pointed/psionic/barrier + name = "Barrier" + desc = "Give yourself or a target psionic armour." + button_icon_state = "tech_frostaura" + category = "Tier 2" + cooldown_time = 60 SECONDS + psionic_level = 2 + point_cost = 1 + mana_cost = 30 + locked = FALSE + +/datum/action/cooldown/spell/pointed/psionic/barrier/is_valid_target(atom/cast_on) + . = ..() + if(!ishuman(cast_on)) + return FALSE + +/datum/action/cooldown/spell/pointed/psionic/barrier/cast(atom/cast_on) + . = ..() + var/mob/living/carbon/human/artificer = cast_on + var/duration = 20 SECONDS * cast_power + artificer.apply_status_effect(/datum/status_effect/psionic_armour, duration) + playsound(artificer, 'tff_modular/modules/psionics/sounds/power_used.ogg', 50, TRUE) + return TRUE + +/datum/status_effect/psionic_armour + id = "psionic_armour" + duration = 20 SECONDS + alert_type = /atom/movable/screen/alert/status_effect/psionic_armour + show_duration = TRUE + +/datum/status_effect/psionic_armour/on_creation(mob/living/new_owner, new_duration) + . = ..() + duration = new_duration + +/datum/status_effect/psionic_armour/on_apply() + . = ..() + var/mob/living/carbon/human/affected = owner + ADD_TRAIT(affected, TRAIT_HARDLY_WOUNDED, PSIONIC_TRAIT) + affected.physiology.brute_mod *= 0.75 + affected.physiology.burn_mod *= 0.75 + affected.physiology.stamina_mod *= 0.25 + return TRUE + +/datum/status_effect/psionic_armour/on_remove() + . = ..() + var/mob/living/carbon/human/affected = owner + REMOVE_TRAIT(affected, TRAIT_HARDLY_WOUNDED, PSIONIC_TRAIT) + affected.physiology.brute_mod /= 0.75 + affected.physiology.burn_mod /= 0.75 + affected.physiology.stamina_mod /= 0.25 + return TRUE + +/atom/movable/screen/alert/status_effect/psionic_armour + name = "Psionic Armour" + desc = "You covered with Psi Armour, and any damage you receive is reduced!" + overlay_icon = 'tff_modular/modules/psionics/icons/spells.dmi' + overlay_state = "tech_frostaura" diff --git a/tff_modular/modules/psionics/code/pointed/awaken.dm b/tff_modular/modules/psionics/code/pointed/awaken.dm new file mode 100644 index 00000000000..9cc37d9c8ec --- /dev/null +++ b/tff_modular/modules/psionics/code/pointed/awaken.dm @@ -0,0 +1,48 @@ +/datum/action/cooldown/spell/pointed/psionic/awakening + name = "Psionic Awaken" + desc = "Stimulate a living being's Zona Bovinae and bring them to Psionically Harmonious rank." + button_icon_state = "const_repairaura" + mana_cost = 0 + cooldown_time = 10 SECONDS + point_cost = 3 + locked = FALSE + psionic_level = 2 + category = "Tier 2" + cast_range = 2 + var/is_psionic = FALSE + +/datum/action/cooldown/spell/pointed/psionic/awakening/is_valid_target(atom/cast_on) + var/mob/living/victim = cast_on + var/datum/psionic/victim_psionic = victim.get_psionic() + if(!isliving(victim)) + return FALSE + if(istype(victim_psionic, /datum/psionic/harmonious)) + to_chat(owner, span_horizonblue("Their psi sensivity is strong enough!")) + return FALSE + if(HAS_TRAIT(victim, TRAIT_ZONA_BOVINAE_ABSORBED)) + to_chat(owner, span_horizonblue("Their psi sensivity is shattered!")) + return FALSE + if(HAS_TRAIT(victim, TRAIT_PSIONIC_INFLUENCED)) + to_chat(owner, span_horizonblue("Their psi sensivity is already influenced!")) + return TRUE + +/datum/action/cooldown/spell/pointed/psionic/awakening/before_cast(atom/cast_on) + . = ..() + var/mob/living/victim = cast_on + if(HAS_TRAIT(victim, TRAIT_PSIONIC_USER)) + is_psionic = TRUE + +/datum/action/cooldown/spell/pointed/psionic/awakening/cast(atom/cast_on) + . = ..() + var/mob/living/victim = cast_on + to_chat(victim, span_horizonblue("You can feel your psionic energy getting stronger...")) + if(!do_after(owner, 10 SECONDS, victim)) + return FALSE + if(is_psionic) + victim.remove_psionic() + victim.add_psionic(/datum/psionic/harmonious) + victim.psi_sensivity.psi_point = 7 + else + victim.add_psionic(/datum/psionic/sensitive) + + ADD_TRAIT(victim, TRAIT_PSIONIC_INFLUENCED, PSIONIC_TRAIT) diff --git a/tff_modular/modules/psionics/code/pointed/bubble.dm b/tff_modular/modules/psionics/code/pointed/bubble.dm new file mode 100644 index 00000000000..b7ce002057e --- /dev/null +++ b/tff_modular/modules/psionics/code/pointed/bubble.dm @@ -0,0 +1,61 @@ +/datum/action/cooldown/spell/pointed/psionic/bubble + name = "Psionic Bubble" + desc = "Create a protective bubble around you or target that removes your need to breathe or wear space protection!" + button_icon_state = "tech_condensation" + point_cost = 1 + cooldown_time = 30 SECONDS + mana_cost = 10 + locked = FALSE + +/datum/action/cooldown/spell/pointed/psionic/bubble/is_valid_target(atom/cast_on) + if(!isliving(cast_on)) + return FALSE + return TRUE + +/datum/action/cooldown/spell/pointed/psionic/bubble/cast(atom/cast_on) + . = ..() + var/mob/living/living_living = cast_on + var/duration = cast_power * 15 SECONDS + living_living.apply_status_effect(/datum/status_effect/psi_bubble, duration) + playsound(cast_on, 'tff_modular/modules/psionics/sounds/power_used.ogg', 50, TRUE) + return TRUE + +/datum/status_effect/psi_bubble + id = "psi_bubble" + alert_type = /atom/movable/screen/alert/status_effect/psi_bubble + tick_interval = STATUS_EFFECT_AUTO_TICK + processing_speed = STATUS_EFFECT_NORMAL_PROCESS + duration = 15 SECONDS + show_duration = TRUE + var/icon/bubbleicon + +/datum/status_effect/psi_bubble/on_creation(mob/living/new_owner, set_duration) + if(isnum(set_duration)) + duration = set_duration + return ..() + +/datum/status_effect/psi_bubble/on_apply() + . = ..() + bubbleicon = icon(icon = 'icons/effects/effects.dmi', icon_state = "bubbles") + owner.add_overlay(bubbleicon) + owner.add_traits(list(TRAIT_OXYIMMUNE, TRAIT_RESISTLOWPRESSURE, TRAIT_RESISTCOLD), PSIONIC_TRAIT) + RegisterSignal(owner, COMSIG_ATOM_EXAMINE, PROC_REF(on_examine)) + return TRUE + +/datum/status_effect/psi_bubble/on_remove() + . = ..() + owner.cut_overlay(bubbleicon) + owner.remove_traits(list(TRAIT_OXYIMMUNE, TRAIT_RESISTLOWPRESSURE, TRAIT_RESISTCOLD), PSIONIC_TRAIT) + UnregisterSignal(owner, COMSIG_ATOM_EXAMINE) + return TRUE + +/datum/status_effect/psi_bubble/proc/on_examine(atom/source, mob/user, list/examine_list) + SIGNAL_HANDLER + + examine_list += span_horizonblue("[source.p_Theyre()] covered with strange bubbles!") + +/atom/movable/screen/alert/status_effect/psi_bubble + name = "Air Bubble" + desc = "There is a protective bubble around you that removes your need to breathe or wear space protection!" + overlay_icon = 'icons/effects/effects.dmi' + overlay_state = "shield2" diff --git a/tff_modular/modules/psionics/code/pointed/drain.dm b/tff_modular/modules/psionics/code/pointed/drain.dm new file mode 100644 index 00000000000..59f69ffe960 --- /dev/null +++ b/tff_modular/modules/psionics/code/pointed/drain.dm @@ -0,0 +1,32 @@ +/datum/action/cooldown/spell/pointed/psionic/drain + name = "Psionic Drain" + desc = "Drain psi-stamina from a living being, will harm it!" + button_icon_state = "gen_project" + cooldown_time = 10 SECONDS + psionic_level = 1 + point_cost = 2 + mana_cost = 0 + locked = FALSE + cast_range = 3 + +/datum/action/cooldown/spell/pointed/psionic/drain/is_valid_target(atom/cast_on) + if(!ishuman(cast_on)) + return FALSE + if(cast_on == owner) + return FALSE + return TRUE + +/datum/action/cooldown/spell/pointed/psionic/drain/cast(atom/cast_on) + . = ..() + drain_psi_stamina(cast_on) + to_chat(cast_on, span_horizonblue("You begin to feel weak...")) + +/datum/action/cooldown/spell/pointed/psionic/drain/proc/drain_psi_stamina(atom/cast_on) + var/mob/living/carbon/human/victim = cast_on + if(!do_after(owner, 1 SECONDS, victim, IGNORE_TARGET_LOC_CHANGE)) + return FALSE + victim.adjust_stamina_loss(10) + psionic_datum.adjust_psi_energy(10) + to_chat(victim, span_horizonblue("You're getting worse...")) + playsound(victim, 'tff_modular/modules/psionics/sounds/power_used.ogg', 50, TRUE) + drain_psi_stamina(victim) diff --git a/tff_modular/modules/psionics/code/pointed/emotional_suggestion.dm b/tff_modular/modules/psionics/code/pointed/emotional_suggestion.dm new file mode 100644 index 00000000000..18da557d9a4 --- /dev/null +++ b/tff_modular/modules/psionics/code/pointed/emotional_suggestion.dm @@ -0,0 +1,46 @@ +/datum/action/cooldown/spell/pointed/psionic/emotional_suggestion + name = "Psionic Emotional Suggestion" + desc = "Allows you to psionically commune with the target using emotions." + button_icon_state = "tech_gambit" + cooldown_time = 2 SECONDS + mana_cost = 5 + point_cost = 0 + locked = FALSE + +/datum/action/cooldown/spell/pointed/psionic/is_valid_target(atom/cast_on) + if(!iscarbon(cast_on)) + return FALSE + return TRUE + +/datum/action/cooldown/spell/pointed/psionic/emotional_suggestion/cast(atom/cast_on) + . = ..() + emotional_suggestion(cast_on, owner) + drain_mana() + return TRUE + +/datum/action/cooldown/spell/pointed/psionic/emotional_suggestion/proc/emotional_suggestion(atom/hit_atom, mob/living/user) + var/mob/living/target = hit_atom + if(target.stat == DEAD) + to_chat(user, span_warning("Not even a psion of your level can suggest to the dead.")) + return + + var/text = tgui_input_list(user, "Which emotion would you like to suggest?", "Emotional Suggestion", list("Calm", "Happiness", "Sadness", "Fear", "Anger", "Stress", "Confusion")) + if(!text) + return + + text = lowertext(text) + + log_say("[key_name(user)] suggested an emotion to [key_name(target)]: [text]") + + to_chat(user, span_horizonblue("You psionically suggest an emotion to [target]: [text]")) + + var/mob/living/carbon/human/H = target + var/datum/psionic/target_sensitivity = H.get_psionic() + if(target_sensitivity) + to_chat(H, span_horizonblue("[user] blinks, their eyes briefly developing an unnatural shine.")) + to_chat(H, span_horizonblue("You sense [user]'s psyche link with your own, and an emotion of [text] washes through your mind.")) + else + to_chat(H, span_horizonblue("An emotion from outside your consciousness slips into your mind: [text].")) + + playsound(H, 'tff_modular/modules/psionics/sounds/power_used.ogg', 50, TRUE) + return TRUE diff --git a/tff_modular/modules/psionics/code/pointed/expansion.dm b/tff_modular/modules/psionics/code/pointed/expansion.dm new file mode 100644 index 00000000000..1c44bcb139e --- /dev/null +++ b/tff_modular/modules/psionics/code/pointed/expansion.dm @@ -0,0 +1,48 @@ +/datum/action/cooldown/spell/pointed/psionic/expansion + name = "Psionic Expansion" + desc = "Allows the selected target to see living creatures through walls." + button_icon_state = "gen_rmind" + category = "Tier 2" + cooldown_time = 40 SECONDS + psionic_level = 2 + point_cost = 1 + mana_cost = 10 + locked = FALSE + cast_range = 5 + +/datum/action/cooldown/spell/pointed/psionic/expansion/is_valid_target(atom/cast_on) + if(!isliving(cast_on)) + return FALSE + if(HAS_TRAIT(cast_on, TRAIT_THERMAL_VISION)) + to_chat(cast_on, span_warning("The target doesn't need it!")) + return FALSE + return TRUE + +/datum/action/cooldown/spell/pointed/psionic/expansion/cast(atom/cast_on) + . = ..() + var/mob/living/getting_vision = cast_on + var/new_duration = 15 SECONDS * cast_power + getting_vision.apply_status_effect(/datum/status_effect/thermal_vision, new_duration) + drain_mana() + +/datum/status_effect/thermal_vision + id = "thermal_vision" + duration = 15 SECONDS + show_duration = TRUE + alert_type = null + +/datum/status_effect/thermal_vision/on_creation(mob/living/new_owner, new_duration) + . = ..() + duration = new_duration + +/datum/status_effect/thermal_vision/on_apply() + . = ..() + ADD_TRAIT(owner, TRAIT_THERMAL_VISION, PSIONIC_TRAIT) + owner.update_sight() + return TRUE + +/datum/status_effect/thermal_vision/on_remove() + . = ..() + ADD_TRAIT(owner, TRAIT_THERMAL_VISION, PSIONIC_TRAIT) + owner.update_sight() + return TRUE diff --git a/tff_modular/modules/psionics/code/pointed/jump.dm b/tff_modular/modules/psionics/code/pointed/jump.dm new file mode 100644 index 00000000000..6ff0e5ea45d --- /dev/null +++ b/tff_modular/modules/psionics/code/pointed/jump.dm @@ -0,0 +1,32 @@ +/datum/action/cooldown/spell/pointed/psionic/jump + name = "Psionic Jump" + desc = "Teleport to a destination you click on." + button_icon_state = "tech_dispel" + cooldown_time = 30 SECONDS + psionic_level = 2 + mana_cost = 20 + point_cost = 2 + locked = FALSE + category = "Tier 2" + +/datum/action/cooldown/spell/pointed/psionic/jump/can_cast_spell(feedback) + . = ..() + if(HAS_TRAIT(owner, TRAIT_NO_TRANSFORM)) + return FALSE + return TRUE + +/datum/action/cooldown/spell/pointed/psionic/jump/is_valid_target(atom/cast_on) + if(isclosedturf(cast_on)) + return FALSE + if(isobj(cast_on)) + return FALSE + if(!(cast_on in view(owner.client.view, owner))) + owner.balloon_alert(owner, "out of view!") + return FALSE + return TRUE + +/datum/action/cooldown/spell/pointed/psionic/jump/cast(atom/cast_on) + . = ..() + do_teleport(owner, get_turf(cast_on), 1, /obj/effect/temp_visual/dir_setting/ninja, /obj/effect/temp_visual/dir_setting/ninja, 'tff_modular/modules/psionics/sounds/power_fabrication.ogg', channel = TELEPORT_CHANNEL_MAGIC) + drain_mana() + return TRUE diff --git a/tff_modular/modules/psionics/code/pointed/mind_muddle.dm b/tff_modular/modules/psionics/code/pointed/mind_muddle.dm new file mode 100644 index 00000000000..a14c8432a63 --- /dev/null +++ b/tff_modular/modules/psionics/code/pointed/mind_muddle.dm @@ -0,0 +1,24 @@ +/datum/action/cooldown/spell/pointed/psionic/mind_muddle + name = "Psionic Mind Muddle" + desc = "Use this at range to confuse a target and give them a little bit of pain." + button_icon_state = "wiz_tele" + cooldown_time = 20 SECONDS + psionic_level = 1 + point_cost = 2 + mana_cost = 10 + locked = FALSE + +/datum/action/cooldown/spell/pointed/psionic/mind_muddle/is_valid_target(atom/cast_on) + if(!ishuman(cast_on)) + return FALSE + return TRUE + +/datum/action/cooldown/spell/pointed/psionic/mind_muddle/cast(atom/cast_on) + . = ..() + var/mob/living/carbon/human/victim = cast_on + if(!do_after(owner, 2 SECONDS, victim, IGNORE_TARGET_LOC_CHANGE | IGNORE_USER_LOC_CHANGE | IGNORE_SLOWDOWNS | IGNORE_HELD_ITEM)) + return FALSE + victim.adjust_stamina_loss(20 * cast_power) + victim.adjust_confusion(3 SECONDS * cast_power) + playsound(victim, 'tff_modular/modules/psionics/sounds/power_used.ogg', 50, TRUE) + return TRUE diff --git a/tff_modular/modules/psionics/code/pointed/pointed.dm b/tff_modular/modules/psionics/code/pointed/pointed.dm deleted file mode 100644 index 2d22051f4f3..00000000000 --- a/tff_modular/modules/psionics/code/pointed/pointed.dm +++ /dev/null @@ -1,701 +0,0 @@ -// Тут все заклинания, которые являются направленными, но не прожектайлами. - -// Станит на непродолжительный срок и заставляет выкинуть вещи из рук -/datum/action/cooldown/spell/pointed/psionic/spasm - name = "Psionic Spasm" - desc = "Force a target to drop the items in its hands. Note that this has a hefty power use and cooldown." - button_icon_state = "genetics_closed" - cooldown_time = 100 SECONDS - mana_cost = 20 - psionic_level = 2 - target_msg = "Your muscles spasm!" - active_msg = "You prepare to stun a target..." - locked = FALSE - category = "Tier 2" - -/datum/action/cooldown/spell/pointed/psionic/spasm/is_valid_target(atom/cast_on) - if(!ishuman(cast_on)) - return FALSE - - if(issynthetic(cast_on) && cast_power < 2) - to_chat(owner, span_notice("I dont know how to work with synths.")) - return FALSE - - return TRUE - -/datum/action/cooldown/spell/pointed/psionic/spasm/cast(mob/living/carbon/human/cast_on) - . = ..() - if(cast_on.can_block_magic(antimagic_flags)) - to_chat(cast_on, span_warning("Your body is assaulted with psionic energy!")) - else - to_chat(cast_on, span_warning(target_msg)) - log_combat(owner, cast_on, "psionically spasmed") - cast_on.Stun(1 SECONDS * cast_power) - drain_mana() - playsound(cast_on, 'tff_modular/modules/psionics/sounds/power_evoke.ogg', 50, TRUE) - return TRUE - -// Мед сканер на расстоянии -/datum/action/cooldown/spell/pointed/psionic/skinsight - name = "Skinsight" - desc = "Try to read target's vital energy and determine their state." - button_icon_state = "wiz_blind" - cooldown_time = 1 SECONDS - point_cost = 0 - mana_cost = 5 - target_msg = "You feel like someone is looking deep into you." - active_msg = "You prepare to scan a target..." - locked = FALSE - -/datum/action/cooldown/spell/pointed/psionic/skinsight/is_valid_target(atom/cast_on) - if(!ishuman(cast_on)) - return FALSE - - return TRUE - -/datum/action/cooldown/spell/pointed/psionic/skinsight/cast(mob/living/carbon/human/cast_on) - . = ..() - if(cast_on.can_block_magic(antimagic_flags)) - to_chat(cast_on, span_notice("Your body is being read by a psionic nearby.")) - else - to_chat(cast_on, span_warning(target_msg)) - if(cast_power >= 2) - healthscan(owner, cast_on, SCANNER_VERBOSE, TRUE, tochat = TRUE) - else - healthscan(owner, cast_on, SCANNER_VERBOSE, FALSE, tochat = TRUE) - drain_mana() - playsound(cast_on, 'tff_modular/modules/psionics/sounds/power_used.ogg', 50, TRUE) - return TRUE - -/datum/action/cooldown/spell/pointed/psionic/jump - name = "Psionic Jump" - desc = "Teleport to a destination you click on." - button_icon_state = "tech_dispel" - cooldown_time = 30 SECONDS - psionic_level = 2 - mana_cost = 20 - point_cost = 2 - locked = FALSE - category = "Tier 2" - -/datum/action/cooldown/spell/pointed/psionic/jump/can_cast_spell(feedback) - . = ..() - if(HAS_TRAIT(owner, TRAIT_NO_TRANSFORM)) - return FALSE - return TRUE - -/datum/action/cooldown/spell/pointed/psionic/jump/is_valid_target(atom/cast_on) - if(isclosedturf(cast_on)) - return FALSE - if(isobj(cast_on)) - return FALSE - if(!(cast_on in view(owner.client.view, owner))) - owner.balloon_alert(owner, "out of view!") - return FALSE - return TRUE - -/datum/action/cooldown/spell/pointed/psionic/jump/cast(atom/cast_on) - . = ..() - do_teleport(owner, get_turf(cast_on), 1, /obj/effect/temp_visual/dir_setting/ninja, /obj/effect/temp_visual/dir_setting/ninja, 'tff_modular/modules/psionics/sounds/power_fabrication.ogg', channel = TELEPORT_CHANNEL_MAGIC) - drain_mana() - return TRUE - -/datum/action/cooldown/spell/pointed/psionic/emotional_suggestion - name = "Psionic Emotional Suggestion" - desc = "Allows you to psionically commune with the target using emotions." - button_icon_state = "tech_gambit" - cooldown_time = 2 SECONDS - mana_cost = 5 - point_cost = 0 - locked = FALSE - -/datum/action/cooldown/spell/pointed/psionic/is_valid_target(atom/cast_on) - if(!iscarbon(cast_on)) - return FALSE - return TRUE - -/datum/action/cooldown/spell/pointed/psionic/emotional_suggestion/cast(atom/cast_on) - . = ..() - emotional_suggestion(cast_on, owner) - drain_mana() - return TRUE - -/datum/action/cooldown/spell/pointed/psionic/emotional_suggestion/proc/emotional_suggestion(atom/hit_atom, mob/living/user) - var/mob/living/target = hit_atom - if(target.stat == DEAD) - to_chat(user, span_warning("Not even a psion of your level can suggest to the dead.")) - return - - var/text = tgui_input_list(user, "Which emotion would you like to suggest?", "Emotional Suggestion", list("Calm", "Happiness", "Sadness", "Fear", "Anger", "Stress", "Confusion")) - if(!text) - return - - text = lowertext(text) - - log_say("[key_name(user)] suggested an emotion to [key_name(target)]: [text]") - - to_chat(user, span_horizonblue("You psionically suggest an emotion to [target]: [text]")) - - var/mob/living/carbon/human/H = target - var/datum/psionic/target_sensitivity = H.get_psionic() - if(target_sensitivity) - to_chat(H, span_notice("[user] blinks, their eyes briefly developing an unnatural shine.")) - to_chat(H, span_notice("You sense [user]'s psyche link with your own, and an emotion of [text] washes through your mind.")) - else - to_chat(H, span_notice("An emotion from outside your consciousness slips into your mind: [text].")) - - playsound(H, 'tff_modular/modules/psionics/sounds/power_used.ogg', 50, TRUE) - return TRUE - -/datum/action/cooldown/spell/pointed/psionic/rejuvenate - name = "Psionic Rejuvenate" - desc = "Restore a creature's blood and tried to and try to revive it." - button_icon_state = "tech_resurrect" - cast_range = 3 - point_cost = 3 - mana_cost = 80 - psionic_level = 2 - locked = FALSE - category = "Tier 2" - -/datum/action/cooldown/spell/pointed/psionic/rejuvenate/is_valid_target(atom/cast_on) - if(iscarbon(cast_on)) - var/mob/living/carbon/human = cast_on - if(human.stat == DEAD) - return TRUE - return FALSE - return FALSE - -/datum/action/cooldown/spell/pointed/psionic/rejuvenate/cast(atom/cast_on) - . = ..() - if(iscarbon(cast_on)) - var/mob/living/carbon/carbon_living = cast_on - for(var/i in 1 to 3) - if(!do_after(owner, 5 SECONDS, carbon_living, timed_action_flags = IGNORE_USER_LOC_CHANGE | IGNORE_TARGET_LOC_CHANGE)) - return FALSE - - carbon_living.heal_overall_damage(30, 30) - playsound(carbon_living, 'sound/effects/singlebeat.ogg', vol = 50, vary = TRUE, ignore_walls = FALSE) - playsound(carbon_living, 'tff_modular/modules/psionics/sounds/power_used.ogg', 50, TRUE) - var/original_transform = carbon_living.transform - animate(carbon_living, transform = carbon_living.transform.Translate(0, 3), time = 0.2 SECONDS, easing = CUBIC_EASING | EASE_OUT, flags = ANIMATION_PARALLEL) - animate(transform = original_transform, time = 0.2 SECONDS, easing = CUBIC_EASING | EASE_IN, flags = ANIMATION_PARALLEL) - - carbon_living.visible_message( - message = span_danger("\The [carbon_living] shake[carbon_living.p_their()] violently!"), - ignored_mobs = owner - ) - - carbon_living.cure_husk() - carbon_living.regenerate_organs(TRUE) - carbon_living.regenerate_limbs() - carbon_living.adjust_blood_volume(BLOOD_VOLUME_NORMAL, 0, BLOOD_VOLUME_NORMAL) - if(!carbon_living.revive()) - owner.balloon_alert(owner, "revival failed!") - return FALSE - - to_chat(owner, span_horizonblue("You successfully revive \the [owner]!")) - drain_mana() - else - return FALSE - return TRUE - -/datum/action/cooldown/spell/pointed/psionic/stasis - name = "Psionic Stasis" - desc = "Condenses the Nlom field around one person at a time. This immobilises them and also applies stasis to them." - button_icon_state = "gen_ice" - cooldown_time = 60 SECONDS - point_cost = 1 - psionic_level = 2 - mana_cost = 30 - locked = FALSE - category = "Tier 2" - -/datum/action/cooldown/spell/pointed/psionic/stasis/is_valid_target(atom/cast_on) - if(!isliving(cast_on)) - return FALSE - return TRUE - -/datum/action/cooldown/spell/pointed/psionic/stasis/cast(atom/cast_on) - . = ..() - var/mob/living/freezing = cast_on - if(!do_after(owner, 2 SECONDS, freezing, IGNORE_TARGET_LOC_CHANGE | IGNORE_USER_LOC_CHANGE | IGNORE_SLOWDOWNS | IGNORE_HELD_ITEM)) - return FALSE - var/duration = cast_power * 4 SECONDS - freezing.apply_status_effect(/datum/status_effect/freon/watcher/psionic, duration) - playsound(freezing, 'tff_modular/modules/psionics/sounds/power_evoke.ogg', 50, TRUE) - -/datum/status_effect/freon/watcher/psionic/on_creation(mob/living/new_owner, new_duration) - . = ..() - duration = new_duration - -/datum/action/cooldown/spell/pointed/psionic/bubble - name = "Psionic Bubble" - desc = "Create a protective bubble around you or target that removes your need to breathe or wear space protection!" - button_icon_state = "tech_condensation" - point_cost = 1 - cooldown_time = 30 SECONDS - mana_cost = 10 - locked = FALSE - -/datum/action/cooldown/spell/pointed/psionic/bubble/is_valid_target(atom/cast_on) - if(!isliving(cast_on)) - return FALSE - return TRUE - -/datum/action/cooldown/spell/pointed/psionic/bubble/cast(atom/cast_on) - . = ..() - var/mob/living/living_living = cast_on - var/duration = cast_power * 15 SECONDS - living_living.apply_status_effect(/datum/status_effect/psi_bubble, duration) - playsound(cast_on, 'tff_modular/modules/psionics/sounds/power_used.ogg', 50, TRUE) - return TRUE - -/datum/status_effect/psi_bubble - id = "psi_bubble" - alert_type = /atom/movable/screen/alert/status_effect/psi_bubble - tick_interval = STATUS_EFFECT_AUTO_TICK - processing_speed = STATUS_EFFECT_NORMAL_PROCESS - duration = 15 SECONDS - show_duration = TRUE - var/icon/bubbleicon - -/datum/status_effect/psi_bubble/on_creation(mob/living/new_owner, set_duration) - if(isnum(set_duration)) - duration = set_duration - return ..() - -/datum/status_effect/psi_bubble/on_apply() - . = ..() - bubbleicon = icon(icon = 'icons/effects/effects.dmi', icon_state = "bubbles") - owner.add_overlay(bubbleicon) - owner.add_traits(list(TRAIT_OXYIMMUNE, TRAIT_RESISTLOWPRESSURE, TRAIT_RESISTCOLD), PSIONIC_TRAIT) - RegisterSignal(owner, COMSIG_ATOM_EXAMINE, PROC_REF(on_examine)) - return TRUE - -/datum/status_effect/psi_bubble/on_remove() - . = ..() - owner.cut_overlay(bubbleicon) - owner.remove_traits(list(TRAIT_OXYIMMUNE, TRAIT_RESISTLOWPRESSURE, TRAIT_RESISTCOLD), PSIONIC_TRAIT) - UnregisterSignal(owner, COMSIG_ATOM_EXAMINE) - return TRUE - -/datum/status_effect/psi_bubble/proc/on_examine(atom/source, mob/user, list/examine_list) - SIGNAL_HANDLER - - examine_list += span_horizonblue("[source.p_Theyre()] covered with strange bubbles!") - -/atom/movable/screen/alert/status_effect/psi_bubble - name = "Air Bubble" - desc = "There is a protective bubble around you that removes your need to breathe or wear space protection!" - overlay_icon = 'icons/effects/effects.dmi' - overlay_state = "shield2" - -/datum/action/cooldown/spell/pointed/psionic/barrier - name = "Barrier" - desc = "Give yourself or a target psionic armour." - button_icon_state = "tech_frostaura" - category = "Tier 2" - cooldown_time = 60 SECONDS - psionic_level = 2 - point_cost = 1 - mana_cost = 30 - locked = FALSE - -/datum/action/cooldown/spell/pointed/psionic/barrier/is_valid_target(atom/cast_on) - . = ..() - if(!ishuman(cast_on)) - return FALSE - -/datum/action/cooldown/spell/pointed/psionic/barrier/cast(atom/cast_on) - . = ..() - var/mob/living/carbon/human/artificer = cast_on - var/duration = 20 SECONDS * cast_power - artificer.apply_status_effect(/datum/status_effect/psionic_armour, duration) - playsound(artificer, 'tff_modular/modules/psionics/sounds/power_used.ogg', 50, TRUE) - return TRUE - -/datum/status_effect/psionic_armour - id = "psionic_armour" - duration = 20 SECONDS - alert_type = /atom/movable/screen/alert/status_effect/psionic_armour - show_duration = TRUE - -/datum/status_effect/psionic_armour/on_creation(mob/living/new_owner, new_duration) - . = ..() - duration = new_duration - -/datum/status_effect/psionic_armour/on_apply() - . = ..() - var/mob/living/carbon/human/affected = owner - ADD_TRAIT(affected, TRAIT_HARDLY_WOUNDED, PSIONIC_TRAIT) - affected.physiology.brute_mod *= 0.75 - affected.physiology.burn_mod *= 0.75 - affected.physiology.stamina_mod *= 0.25 - return TRUE - -/datum/status_effect/psionic_armour/on_remove() - . = ..() - var/mob/living/carbon/human/affected = owner - REMOVE_TRAIT(affected, TRAIT_HARDLY_WOUNDED, PSIONIC_TRAIT) - affected.physiology.brute_mod /= 0.75 - affected.physiology.burn_mod /= 0.75 - affected.physiology.stamina_mod /= 0.25 - return TRUE - -/atom/movable/screen/alert/status_effect/psionic_armour - name = "Psionic Armour" - desc = "You covered with Psi Armour, and any damage you receive is reduced!" - overlay_icon = 'tff_modular/modules/psionics/icons/spells.dmi' - overlay_state = "tech_frostaura" - -/datum/action/cooldown/spell/pointed/psionic/psi_throw - name = "Psionic Throw" - desc = "Throw an object in the opposite direction from yourself. Works on living beings." - button_icon_state = "wiz_mm" - category = "Tier 2" - cooldown_time = 30 SECONDS - psionic_level = 2 - point_cost = 2 - mana_cost = 20 - locked = FALSE - -/datum/action/cooldown/spell/pointed/psionic/psi_throw/is_valid_target(atom/cast_on) - if(cast_on == owner) - return FALSE - if(isobj(cast_on) || isliving(cast_on)) - var/atom/movable/AM = cast_on - if(AM.anchored) - return FALSE - return TRUE - return FALSE - -/datum/action/cooldown/spell/pointed/psionic/psi_throw/cast(atom/cast_on) - . = ..() - var/turf/throwtarget = get_edge_target_turf(owner, get_dir(owner, get_step_away(cast_on, owner))) - var/dist_from_caster = get_dist(cast_on, owner) - if(dist_from_caster == 0) - if(isliving(cast_on)) - var/mob/living/victim = cast_on - victim.Paralyze(10 SECONDS) - victim.adjust_brute_loss(5) - to_chat(victim, span_userdanger("You're psionically slammed into the floor by [owner]!")) - else - if(isliving(cast_on)) - var/mob/living/victim = cast_on - victim.Paralyze(2 SECONDS) - to_chat(victim, span_userdanger("You're psionically thrown back by [owner]!")) - - var/atom/movable/to_throw = cast_on - to_throw.safe_throw_at( - target = throwtarget, - range = 4 * cast_power, - speed = 2, - thrower = owner, - force = MOVE_FORCE_STRONG, - ) - - playsound(owner, 'tff_modular/modules/psionics/sounds/power_used.ogg', 50, TRUE) - drain_mana() - return TRUE - - -/datum/action/cooldown/spell/pointed/psionic/mind_muddle - name = "Psionic Mind Muddle" - desc = "Use this at range to confuse a target and give them a little bit of pain." - button_icon_state = "wiz_tele" - cooldown_time = 20 SECONDS - psionic_level = 1 - point_cost = 2 - mana_cost = 10 - locked = FALSE - -/datum/action/cooldown/spell/pointed/psionic/mind_muddle/is_valid_target(atom/cast_on) - if(!ishuman(cast_on)) - return FALSE - return TRUE - -/datum/action/cooldown/spell/pointed/psionic/mind_muddle/cast(atom/cast_on) - . = ..() - var/mob/living/carbon/human/victim = cast_on - if(!do_after(owner, 2 SECONDS, victim, IGNORE_TARGET_LOC_CHANGE | IGNORE_USER_LOC_CHANGE | IGNORE_SLOWDOWNS | IGNORE_HELD_ITEM)) - return FALSE - victim.adjust_stamina_loss(20 * cast_power) - victim.adjust_confusion(3 SECONDS * cast_power) - playsound(victim, 'tff_modular/modules/psionics/sounds/power_used.ogg', 50, TRUE) - return TRUE - -/datum/action/cooldown/spell/pointed/psionic/drain - name = "Psionic Drain" - desc = "Drain psi-stamina from a living being." - button_icon_state = "gen_project" - cooldown_time = 10 SECONDS - psionic_level = 1 - point_cost = 2 - mana_cost = 0 - locked = FALSE - cast_range = 3 - -/datum/action/cooldown/spell/pointed/psionic/drain/is_valid_target(atom/cast_on) - if(!ishuman(cast_on)) - return FALSE - return TRUE - -/datum/action/cooldown/spell/pointed/psionic/drain/cast(atom/cast_on) - . = ..() - drain_psi_stamina(cast_on) - to_chat(cast_on, span_horizonblue("You begin to feel weak...")) - -/datum/action/cooldown/spell/pointed/psionic/drain/proc/drain_psi_stamina(atom/cast_on) - var/mob/living/carbon/human/victim = cast_on - if(!do_after(owner, 1 SECONDS, victim, IGNORE_TARGET_LOC_CHANGE)) - return FALSE - victim.adjust_stamina_loss(10) - psionic_datum.adjust_psi_energy(10) - to_chat(victim, span_horizonblue("You're getting worse...")) - playsound(victim, 'tff_modular/modules/psionics/sounds/power_used.ogg', 50, TRUE) - drain_psi_stamina(victim) - -/datum/action/cooldown/spell/pointed/psionic/warp - name = "Psionic Warp" - desc = "Warp through objects." - button_icon_state = "tech_blink" - category = "Tier 2" - cooldown_time = 60 SECONDS - psionic_level = 2 - point_cost = 2 - mana_cost = 30 - locked = FALSE - cast_range = 2 - var/turf/target_turf - -/datum/action/cooldown/spell/pointed/psionic/warp/is_valid_target(atom/cast_on) - . = ..() - var/turf/closed/wall/turf_we_check = cast_on - if(!iswallturf(turf_we_check)) - to_chat(owner, span_horizonblue("Target must be a wall!")) - return FALSE - if(!turf_we_check.density) - return FALSE - return TRUE - -/datum/action/cooldown/spell/pointed/psionic/warp/cast(atom/cast_on) - . = ..() - var/turf/closed/wall/density_object = cast_on - density_object.warp() - drain_mana() - -/turf/closed/wall - var/warped = FALSE - -/turf/closed/wall/proc/warp() - density = 0 - animate(src, alpha = 75, time = 2 SECONDS) - warped = TRUE - RegisterSignal(src, COMSIG_ATOM_EXAMINE, PROC_REF(on_examine)) - -/turf/closed/wall/proc/unwarp() - density = initial(density) - animate(src, alpha = initial(alpha), time = 2 SECONDS) - warped = FALSE - UnregisterSignal(src, COMSIG_ATOM_EXAMINE) - -/turf/closed/wall/attack_hand(mob/user, list/modifiers) - . = ..() - if(warped) - to_chat(user, span_horizonblue("The wall begins to return to its condition... For some reason.")) - unwarp() - -/turf/closed/wall/proc/on_examine(datum/source, mob/user, text) - SIGNAL_HANDLER - text += span_horizonblue("This wall... Doesn't exist?") - -/datum/action/cooldown/spell/pointed/psionic/pull - name = "Psionic Pull" - desc = "Pulls the target straight towards the user. Even if the item is big, it's cant harm you on impact. Note that you can catch items you pull to yourself if you toggle throw mode before pulling an item." - button_icon_state = "tech_passwall" - category = "Tier 2" - cooldown_time = 60 SECONDS - psionic_level = 2 - point_cost = 1 - mana_cost = 30 - locked = FALSE - cast_range = 5 - -/datum/action/cooldown/spell/pointed/psionic/pull/is_valid_target(atom/cast_on) - if(cast_on == owner) - return FALSE - if(isobj(cast_on) || isliving(cast_on)) - var/atom/movable/AM = cast_on - if(AM.anchored) - return FALSE - return TRUE - return FALSE - -/datum/action/cooldown/spell/pointed/psionic/pull/cast(atom/cast_on) - . = ..() - var/atom/movable/AM = cast_on - var/mob/living/carbon/human/user = owner - if(isobj(cast_on)) - var/obj/object = cast_on - if(object.anchored) - to_chat(user, span_warning("That object cant be moved!")) - return - user.visible_message(span_warning("[user] extends [user.p_their()] hand at [cast_on] and pulls!"), span_warning("You mimic pulling at [cast_on]!")) - if(ismob(cast_on)) - to_chat(cast_on, span_warning("A psychic force pulls you!")) - AM.safe_throw_at(user, 10, 1, user, gentle = TRUE) - playsound(user, 'tff_modular/modules/psionics/sounds/power_evoke.ogg', 40) - drain_mana() - -/datum/action/cooldown/spell/pointed/psionic/expansion - name = "Psionic Expansion" - desc = "Allows the selected target to see living creatures through walls." - button_icon_state = "gen_rmind" - category = "Tier 2" - cooldown_time = 40 SECONDS - psionic_level = 2 - point_cost = 1 - mana_cost = 10 - locked = FALSE - cast_range = 5 - -/datum/action/cooldown/spell/pointed/psionic/expansion/is_valid_target(atom/cast_on) - if(!isliving(cast_on)) - return FALSE - if(HAS_TRAIT(cast_on, TRAIT_THERMAL_VISION)) - to_chat(cast_on, span_warning("The target doesn't need it!")) - return FALSE - return TRUE - -/datum/action/cooldown/spell/pointed/psionic/expansion/cast(atom/cast_on) - . = ..() - var/mob/living/getting_vision = cast_on - var/new_duration = 15 SECONDS * cast_power - getting_vision.apply_status_effect(/datum/status_effect/thermal_vision, new_duration) - drain_mana() - -/datum/status_effect/thermal_vision - id = "thermal_vision" - duration = 15 SECONDS - show_duration = TRUE - alert_type = null - -/datum/status_effect/thermal_vision/on_creation(mob/living/new_owner, new_duration) - . = ..() - duration = new_duration - -/datum/status_effect/thermal_vision/on_apply() - . = ..() - ADD_TRAIT(owner, TRAIT_THERMAL_VISION, PSIONIC_TRAIT) - owner.update_sight() - -/datum/status_effect/thermal_vision/on_remove() - . = ..() - ADD_TRAIT(owner, TRAIT_THERMAL_VISION, PSIONIC_TRAIT) - owner.update_sight() - - -/datum/action/cooldown/spell/pointed/psionic/zona_bovinae - name = "Zona Bovinae Absorption" - desc = "Absorb a psionic energy from a being's Zona Bovinae, granting you an extra point to be used in the Point Shop. The victim will not be able to make it psionic energy stronger in future." - button_icon_state = "tech_illusion" - mana_cost = 0 - cooldown_time = 10 SECONDS - point_cost = 1 - locked = FALSE - psionic_level = 2 - category = "Tier 2" - cast_range = 2 - -/datum/action/cooldown/spell/pointed/psionic/zona_bovinae/is_valid_target(atom/cast_on) - . = ..() - var/mob/living/carbon/human/victim = cast_on - if(!iscarbon(victim)) - to_chat(owner, span_horizonblue("Victim need to be a humanoid!")) - return FALSE - if(!victim.mind) - to_chat(owner, span_horizonblue("Victim need to have mind!")) - return FALSE - if(victim.stat == DEAD) - to_chat(owner, span_horizonblue("There is nothing interesting...")) - return FALSE - return TRUE - -/datum/action/cooldown/spell/pointed/psionic/zona_bovinae/cast(atom/cast_on) - . = ..() - var/mob/living/carbon/human/absorber = owner - var/mob/living/carbon/human/victim = cast_on - to_chat(absorber, span_horizonblue("You're trying to get into the [victim]'s mind...")) - to_chat(victim, span_horizonblue("You feel like [absorber] entering your mind...")) - if(!do_after(absorber, 10 SECONDS, victim, extra_checks=CALLBACK(src, PROC_REF(still_near)))) - return FALSE - victim.adjust_organ_loss(ORGAN_SLOT_BRAIN, 10, 80) - victim.Paralyze(8 SECONDS) - to_chat(absorber, span_horizonblue("You're trying to get [victim]'s memories...")) - to_chat(victim, span_horizonblue("You feel like [absorber] touching your memories...")) - victim.adjust_organ_loss(ORGAN_SLOT_BRAIN, 20, 80) - victim.Paralyze(8 SECONDS) - if(!do_after(absorber, 10 SECONDS, victim, extra_checks=CALLBACK(src, PROC_REF(still_near)))) - return FALSE - to_chat(absorber, span_horizonblue("You're trying to absorb [victim]'s Zona Bovinae...")) - to_chat(victim, span_horizonblue("You feel like [absorber] empties your mind...")) - if(!do_after(absorber, 10 SECONDS, victim, extra_checks=CALLBACK(src, PROC_REF(still_near)))) - return FALSE - victim.adjust_organ_loss(ORGAN_SLOT_BRAIN, 50, 80) - victim.Paralyze(8 SECONDS) - ADD_TRAIT(victim, TRAIT_ZONA_BOVINAE_ABSORBED, PSIONIC_TRAIT) - psionic_datum.psi_point += 1 - to_chat(absorber, span_horizonblue("You absorbed [victim]'s Zona Bovinae!")) - to_chat(victim, span_horizonblue("You feel like your mind shattered.")) - -/datum/action/cooldown/spell/pointed/psionic/zona_bovinae/proc/still_near(mob/living/carbon/human/absorber, mob/living/carbon/human/victim) - var/distance = get_dist(absorber, victim) - if(distance > cast_range) - return FALSE - return TRUE - -/datum/action/cooldown/spell/pointed/psionic/awakening - name = "Psionic Awaken" - desc = "Stimulate a living being's Zona Bovinae and bring them to Psionically Harmonious rank." - button_icon_state = "const_repairaura" - mana_cost = 0 - cooldown_time = 10 SECONDS - point_cost = 2 - locked = FALSE - psionic_level = 2 - category = "Tier 2" - cast_range = 2 - var/is_psionic = FALSE - -/datum/action/cooldown/spell/pointed/psionic/awakening/is_valid_target(atom/cast_on) - var/mob/living/victim = cast_on - var/datum/psionic/victim_psionic = victim.get_psionic() - if(!isliving(victim)) - return FALSE - if(istype(victim_psionic, /datum/psionic/harmonious)) - to_chat(owner, span_horizonblue("Their psi sensivity is strong enough!")) - return FALSE - if(HAS_TRAIT(victim, TRAIT_ZONA_BOVINAE_ABSORBED)) - to_chat(owner, span_horizonblue("Their psi sensivity is shattered!")) - return FALSE - return TRUE - -/datum/action/cooldown/spell/pointed/psionic/awakening/before_cast(atom/cast_on) - . = ..() - var/mob/living/victim = cast_on - if(HAS_TRAIT(victim, TRAIT_PSIONIC_USER)) - is_psionic = TRUE - -/datum/action/cooldown/spell/pointed/psionic/awakening/cast(atom/cast_on) - . = ..() - var/mob/living/victim = cast_on - to_chat(victim, span_horizonblue("You can feel your psionic energy getting stronger...")) - if(!do_after(owner, 10 SECONDS, victim)) - return FALSE - if(is_psionic) - victim.remove_psionic() - victim.add_psionic(/datum/psionic/harmonious) - victim.psi_sensivity.psi_point = 7 - else - victim.add_psionic(/datum/psionic/sensitive) diff --git a/tff_modular/modules/psionics/code/pointed/pull.dm b/tff_modular/modules/psionics/code/pointed/pull.dm new file mode 100644 index 00000000000..01d58f149ad --- /dev/null +++ b/tff_modular/modules/psionics/code/pointed/pull.dm @@ -0,0 +1,37 @@ +/datum/action/cooldown/spell/pointed/psionic/pull + name = "Psionic Pull" + desc = "Pulls the target straight towards the user. Even if the item is big, it's cant harm you on impact. Note that you can catch items you pull to yourself if you toggle throw mode before pulling an item." + button_icon_state = "tech_passwall" + category = "Tier 2" + cooldown_time = 20 SECONDS + psionic_level = 2 + point_cost = 1 + mana_cost = 30 + locked = FALSE + cast_range = 5 + +/datum/action/cooldown/spell/pointed/psionic/pull/is_valid_target(atom/cast_on) + if(cast_on == owner) + return FALSE + if(isobj(cast_on) || (isliving(cast_on) && cast_power >= 2)) + var/atom/movable/AM = cast_on + if(AM.anchored) + return FALSE + return TRUE + return FALSE + +/datum/action/cooldown/spell/pointed/psionic/pull/cast(atom/cast_on) + . = ..() + var/atom/movable/AM = cast_on + var/mob/living/carbon/human/user = owner + if(isobj(cast_on)) + var/obj/object = cast_on + if(object.anchored) + to_chat(user, span_warning("That object cant be moved!")) + return + user.visible_message(span_warning("[user] extends [user.p_their()] hand at [cast_on] and pulls!"), span_warning("You mimic pulling at [cast_on]!")) + if(ismob(cast_on)) + to_chat(cast_on, span_warning("A psychic force pulls you!")) + AM.safe_throw_at(user, 10, 1, user, gentle = TRUE) + playsound(user, 'tff_modular/modules/psionics/sounds/power_evoke.ogg', 40) + drain_mana() diff --git a/tff_modular/modules/psionics/code/pointed/rejuvenate.dm b/tff_modular/modules/psionics/code/pointed/rejuvenate.dm new file mode 100644 index 00000000000..58f24f7508f --- /dev/null +++ b/tff_modular/modules/psionics/code/pointed/rejuvenate.dm @@ -0,0 +1,52 @@ +/datum/action/cooldown/spell/pointed/psionic/rejuvenate + name = "Psionic Rejuvenate" + desc = "Restore a creature's blood and tried to and try to revive it." + button_icon_state = "tech_resurrect" + cast_range = 3 + point_cost = 3 + mana_cost = 80 + psionic_level = 2 + locked = FALSE + category = "Tier 2" + +/datum/action/cooldown/spell/pointed/psionic/rejuvenate/is_valid_target(atom/cast_on) + if(iscarbon(cast_on)) + var/mob/living/carbon/human = cast_on + if(human.stat == DEAD) + return TRUE + return FALSE + return FALSE + +/datum/action/cooldown/spell/pointed/psionic/rejuvenate/cast(atom/cast_on) + . = ..() + if(iscarbon(cast_on)) + var/mob/living/carbon/carbon_living = cast_on + for(var/i in 1 to 3) + if(!do_after(owner, 5 SECONDS, carbon_living, timed_action_flags = IGNORE_USER_LOC_CHANGE | IGNORE_TARGET_LOC_CHANGE)) + return FALSE + + carbon_living.heal_overall_damage(30, 30) + playsound(carbon_living, 'sound/effects/singlebeat.ogg', vol = 50, vary = TRUE, ignore_walls = FALSE) + playsound(carbon_living, 'tff_modular/modules/psionics/sounds/power_used.ogg', 50, TRUE) + var/original_transform = carbon_living.transform + animate(carbon_living, transform = carbon_living.transform.Translate(0, 3), time = 0.2 SECONDS, easing = CUBIC_EASING | EASE_OUT, flags = ANIMATION_PARALLEL) + animate(transform = original_transform, time = 0.2 SECONDS, easing = CUBIC_EASING | EASE_IN, flags = ANIMATION_PARALLEL) + + carbon_living.visible_message( + message = span_danger("\The [carbon_living] shake[carbon_living.p_their()] violently!"), + ignored_mobs = owner + ) + + carbon_living.cure_husk() + carbon_living.regenerate_organs(TRUE) + carbon_living.regenerate_limbs() + carbon_living.adjust_blood_volume(BLOOD_VOLUME_NORMAL, 0, BLOOD_VOLUME_NORMAL) + if(!carbon_living.revive()) + owner.balloon_alert(owner, "revival failed!") + return FALSE + + to_chat(owner, span_horizonblue("You successfully revive \the [owner]!")) + drain_mana() + else + return FALSE + return TRUE diff --git a/tff_modular/modules/psionics/code/pointed/skinsight.dm b/tff_modular/modules/psionics/code/pointed/skinsight.dm new file mode 100644 index 00000000000..fb768ff7b15 --- /dev/null +++ b/tff_modular/modules/psionics/code/pointed/skinsight.dm @@ -0,0 +1,31 @@ +// Мед сканер на расстоянии +/datum/action/cooldown/spell/pointed/psionic/skinsight + name = "Skinsight" + desc = "Try to read target's vital energy and determine their state." + button_icon_state = "wiz_blind" + cooldown_time = 1 SECONDS + point_cost = 0 + mana_cost = 5 + target_msg = "You feel like someone is looking deep into you." + active_msg = "You prepare to scan a target..." + locked = FALSE + +/datum/action/cooldown/spell/pointed/psionic/skinsight/is_valid_target(atom/cast_on) + if(!ishuman(cast_on)) + return FALSE + + return TRUE + +/datum/action/cooldown/spell/pointed/psionic/skinsight/cast(mob/living/carbon/human/cast_on) + . = ..() + if(cast_on.can_block_magic(antimagic_flags)) + to_chat(cast_on, span_notice("Your body is being read by a psionic nearby.")) + else + to_chat(cast_on, span_warning(target_msg)) + if(cast_power >= 2) + healthscan(owner, cast_on, SCANNER_VERBOSE, TRUE, tochat = TRUE) + else + healthscan(owner, cast_on, SCANNER_VERBOSE, FALSE, tochat = TRUE) + drain_mana() + playsound(cast_on, 'tff_modular/modules/psionics/sounds/power_used.ogg', 50, TRUE) + return TRUE diff --git a/tff_modular/modules/psionics/code/pointed/spasm.dm b/tff_modular/modules/psionics/code/pointed/spasm.dm new file mode 100644 index 00000000000..e1d578f9ad5 --- /dev/null +++ b/tff_modular/modules/psionics/code/pointed/spasm.dm @@ -0,0 +1,34 @@ +// Станит на непродолжительный срок и заставляет выкинуть вещи из рук +/datum/action/cooldown/spell/pointed/psionic/spasm + name = "Psionic Spasm" + desc = "Force a target to drop the items in its hands. Note that this has a hefty power use and cooldown." + button_icon_state = "genetics_closed" + cooldown_time = 100 SECONDS + mana_cost = 20 + psionic_level = 2 + target_msg = "Your muscles spasm!" + active_msg = "You prepare to stun a target..." + locked = FALSE + category = "Tier 2" + +/datum/action/cooldown/spell/pointed/psionic/spasm/is_valid_target(atom/cast_on) + if(!ishuman(cast_on)) + return FALSE + + if(issynthetic(cast_on) && cast_power < 2) + to_chat(owner, span_notice("I dont know how to work with synths.")) + return FALSE + + return TRUE + +/datum/action/cooldown/spell/pointed/psionic/spasm/cast(mob/living/carbon/human/cast_on) + . = ..() + if(cast_on.can_block_magic(antimagic_flags)) + to_chat(cast_on, span_warning("Your body is assaulted with psionic energy!")) + else + to_chat(cast_on, span_warning(target_msg)) + log_combat(owner, cast_on, "psionically spasmed") + cast_on.Stun(1 SECONDS * cast_power) + drain_mana() + playsound(cast_on, 'tff_modular/modules/psionics/sounds/power_evoke.ogg', 50, TRUE) + return TRUE diff --git a/tff_modular/modules/psionics/code/pointed/stasis.dm b/tff_modular/modules/psionics/code/pointed/stasis.dm new file mode 100644 index 00000000000..635a10d8486 --- /dev/null +++ b/tff_modular/modules/psionics/code/pointed/stasis.dm @@ -0,0 +1,28 @@ +/datum/action/cooldown/spell/pointed/psionic/stasis + name = "Psionic Stasis" + desc = "Condenses the Nlom field around one person at a time. This immobilises them and also applies stasis to them." + button_icon_state = "gen_ice" + cooldown_time = 60 SECONDS + point_cost = 1 + psionic_level = 2 + mana_cost = 30 + locked = FALSE + category = "Tier 2" + +/datum/action/cooldown/spell/pointed/psionic/stasis/is_valid_target(atom/cast_on) + if(!isliving(cast_on)) + return FALSE + return TRUE + +/datum/action/cooldown/spell/pointed/psionic/stasis/cast(atom/cast_on) + . = ..() + var/mob/living/freezing = cast_on + if(!do_after(owner, 2 SECONDS, freezing, IGNORE_TARGET_LOC_CHANGE | IGNORE_USER_LOC_CHANGE | IGNORE_SLOWDOWNS | IGNORE_HELD_ITEM)) + return FALSE + var/duration = cast_power * 4 SECONDS + freezing.apply_status_effect(/datum/status_effect/freon/watcher/psionic, duration) + playsound(freezing, 'tff_modular/modules/psionics/sounds/power_evoke.ogg', 50, TRUE) + +/datum/status_effect/freon/watcher/psionic/on_creation(mob/living/new_owner, new_duration) + . = ..() + duration = new_duration diff --git a/tff_modular/modules/psionics/code/pointed/throw.dm b/tff_modular/modules/psionics/code/pointed/throw.dm new file mode 100644 index 00000000000..e403cded0ca --- /dev/null +++ b/tff_modular/modules/psionics/code/pointed/throw.dm @@ -0,0 +1,49 @@ +/datum/action/cooldown/spell/pointed/psionic/psi_throw + name = "Psionic Throw" + desc = "Throw an object in the opposite direction from yourself. Works on living beings." + button_icon_state = "wiz_mm" + category = "Tier 2" + cooldown_time = 25 SECONDS + psionic_level = 2 + point_cost = 2 + mana_cost = 20 + locked = FALSE + +/datum/action/cooldown/spell/pointed/psionic/psi_throw/is_valid_target(atom/cast_on) + if(cast_on == owner) + return FALSE + if(isobj(cast_on) || isliving(cast_on)) + var/atom/movable/AM = cast_on + if(AM.anchored) + return FALSE + return TRUE + return FALSE + +/datum/action/cooldown/spell/pointed/psionic/psi_throw/cast(atom/cast_on) + . = ..() + var/turf/throwtarget = get_edge_target_turf(owner, get_dir(owner, get_step_away(cast_on, owner))) + var/dist_from_caster = get_dist(cast_on, owner) + if(dist_from_caster == 0) + if(isliving(cast_on)) + var/mob/living/victim = cast_on + victim.Paralyze(10 SECONDS) + victim.adjust_brute_loss(5) + to_chat(victim, span_userdanger("You're psionically slammed into the floor by [owner]!")) + else + if(isliving(cast_on)) + var/mob/living/victim = cast_on + victim.Paralyze(2 SECONDS) + to_chat(victim, span_userdanger("You're psionically thrown back by [owner]!")) + + var/atom/movable/to_throw = cast_on + to_throw.safe_throw_at( + target = throwtarget, + range = 4 * cast_power, + speed = 2, + thrower = owner, + force = MOVE_FORCE_STRONG, + ) + + playsound(owner, 'tff_modular/modules/psionics/sounds/power_used.ogg', 50, TRUE) + drain_mana() + return TRUE diff --git a/tff_modular/modules/psionics/code/pointed/warp.dm b/tff_modular/modules/psionics/code/pointed/warp.dm new file mode 100644 index 00000000000..1369b7cf68e --- /dev/null +++ b/tff_modular/modules/psionics/code/pointed/warp.dm @@ -0,0 +1,55 @@ +/datum/action/cooldown/spell/pointed/psionic/warp + name = "Psionic Warp" + desc = "Warp through objects." + button_icon_state = "tech_blink" + category = "Tier 2" + cooldown_time = 60 SECONDS + psionic_level = 2 + point_cost = 2 + mana_cost = 30 + locked = FALSE + cast_range = 2 + var/turf/target_turf + +/datum/action/cooldown/spell/pointed/psionic/warp/is_valid_target(atom/cast_on) + . = ..() + var/turf/closed/wall/turf_we_check = cast_on + if(!iswallturf(turf_we_check)) + to_chat(owner, span_horizonblue("Target must be a wall!")) + return FALSE + if(!turf_we_check.density) + return FALSE + return TRUE + +/datum/action/cooldown/spell/pointed/psionic/warp/cast(atom/cast_on) + . = ..() + var/turf/closed/wall/density_object = cast_on + density_object.warp() + drain_mana() + +/turf/closed/wall + var/warped = FALSE + +/turf/closed/wall/proc/warp() + density = 0 + animate(src, alpha = 75, time = 2 SECONDS) + warped = TRUE + apply_wibbly_filters(src) + RegisterSignal(src, COMSIG_ATOM_EXAMINE, PROC_REF(on_examine)) + +/turf/closed/wall/proc/unwarp() + density = initial(density) + animate(src, alpha = initial(alpha), time = 2 SECONDS) + warped = FALSE + remove_wibbly_filters(src) + UnregisterSignal(src, COMSIG_ATOM_EXAMINE) + +/turf/closed/wall/attack_hand(mob/user, list/modifiers) + . = ..() + if(warped) + to_chat(user, span_horizonblue("The wall begins to return to its condition... For some reason.")) + unwarp() + +/turf/closed/wall/proc/on_examine(datum/source, mob/user, text) + SIGNAL_HANDLER + text += span_horizonblue("There's something wrong with this wall. It looks like it's... An illusion?") diff --git a/tff_modular/modules/psionics/code/pointed/zona_bovinae.dm b/tff_modular/modules/psionics/code/pointed/zona_bovinae.dm new file mode 100644 index 00000000000..324cf04062a --- /dev/null +++ b/tff_modular/modules/psionics/code/pointed/zona_bovinae.dm @@ -0,0 +1,58 @@ +/datum/action/cooldown/spell/pointed/psionic/zona_bovinae + name = "Zona Bovinae Absorption" + desc = "Absorb a psionic energy from a being's Zona Bovinae, granting you an extra point to be used in the Point Shop. The victim will not be able to make it psionic energy stronger in future." + button_icon_state = "tech_illusion" + mana_cost = 0 + cooldown_time = 10 SECONDS + point_cost = 0 + locked = FALSE + psionic_level = 2 + category = "Tier 2" + cast_range = 2 + +/datum/action/cooldown/spell/pointed/psionic/zona_bovinae/is_valid_target(atom/cast_on) + . = ..() + var/mob/living/carbon/human/victim = cast_on + if(!iscarbon(victim)) + to_chat(owner, span_horizonblue("Victim need to be a humanoid!")) + return FALSE + if(!victim.mind) + to_chat(owner, span_horizonblue("Victim need to have mind!")) + return FALSE + if(victim.stat == DEAD) + to_chat(owner, span_horizonblue("There is nothing interesting...")) + return FALSE + return TRUE + +/datum/action/cooldown/spell/pointed/psionic/zona_bovinae/cast(atom/cast_on) + . = ..() + var/mob/living/carbon/human/absorber = owner + var/mob/living/carbon/human/victim = cast_on + to_chat(absorber, span_horizonblue("You're trying to get into the [victim]'s mind...")) + to_chat(victim, span_horizonblue("You feel like [absorber] entering your mind...")) + if(!do_after(absorber, 10 SECONDS, victim, extra_checks=CALLBACK(src, PROC_REF(still_near)))) + return FALSE + victim.adjust_organ_loss(ORGAN_SLOT_BRAIN, 10, 80) + victim.Paralyze(8 SECONDS) + to_chat(absorber, span_horizonblue("You're trying to get [victim]'s memories...")) + to_chat(victim, span_horizonblue("You feel like [absorber] touching your memories...")) + victim.adjust_organ_loss(ORGAN_SLOT_BRAIN, 20, 80) + victim.Paralyze(8 SECONDS) + if(!do_after(absorber, 10 SECONDS, victim, extra_checks=CALLBACK(src, PROC_REF(still_near)))) + return FALSE + to_chat(absorber, span_horizonblue("You're trying to absorb [victim]'s Zona Bovinae...")) + to_chat(victim, span_horizonblue("You feel like [absorber] empties your mind...")) + if(!do_after(absorber, 10 SECONDS, victim, extra_checks=CALLBACK(src, PROC_REF(still_near)))) + return FALSE + victim.adjust_organ_loss(ORGAN_SLOT_BRAIN, 50, 80) + victim.Paralyze(8 SECONDS) + ADD_TRAIT(victim, TRAIT_ZONA_BOVINAE_ABSORBED, PSIONIC_TRAIT) + psionic_datum.psi_point += 1 + to_chat(absorber, span_horizonblue("You absorbed [victim]'s Zona Bovinae!")) + to_chat(victim, span_horizonblue("You feel like your mind shattered.")) + +/datum/action/cooldown/spell/pointed/psionic/zona_bovinae/proc/still_near(mob/living/carbon/human/absorber, mob/living/carbon/human/victim) + var/distance = get_dist(absorber, victim) + if(distance > cast_range) + return FALSE + return TRUE diff --git a/tff_modular/modules/psionics/code/projectiles/air_bullet.dm b/tff_modular/modules/psionics/code/projectiles/air_bullet.dm new file mode 100644 index 00000000000..549e8654fc9 --- /dev/null +++ b/tff_modular/modules/psionics/code/projectiles/air_bullet.dm @@ -0,0 +1,30 @@ +// Тут все заклинания, создающие снаряды. +/datum/action/cooldown/spell/pointed/projectile/psionic/air_bullet + name = "Psionic Air Bullet" + desc = "Wrap air in a psionic bubble, compress it, then send it flying at your enemies." + button_icon_state = "tech_repelmissiles" + cooldown_time = 1 SECONDS + mana_cost = 10 + cast_range = 9 + active_msg = "You prepare to charge air bullet..." + deactive_msg = "You relax." + projectile_type = /obj/projectile/magic/air_bullet + projectile_amount = INFINITY + psionic_level = 1 + point_cost = 2 + locked = FALSE + +/datum/action/cooldown/spell/pointed/projectile/psionic/air_bullet/ready_projectile(obj/projectile/to_fire, atom/target, mob/user, iteration) + . = ..() + to_fire.damage = 10 * cast_power + +/datum/action/cooldown/spell/pointed/projectile/psionic/air_bullet/fire_projectile(atom/target) + . = ..() + drain_mana() + playsound(owner, 'tff_modular/modules/psionics/sounds/power_feedback.ogg', 50, TRUE) + +/obj/projectile/magic/air_bullet + icon = 'tff_modular/modules/psionics/icons/projectiles.dmi' + icon_state = "air_bubble" + damage = 10 + damage_type = BRUTE diff --git a/tff_modular/modules/psionics/code/projectiles/projectile.dm b/tff_modular/modules/psionics/code/projectiles/lighting.dm similarity index 67% rename from tff_modular/modules/psionics/code/projectiles/projectile.dm rename to tff_modular/modules/psionics/code/projectiles/lighting.dm index 58f6858247f..7520b1ca20e 100644 --- a/tff_modular/modules/psionics/code/projectiles/projectile.dm +++ b/tff_modular/modules/psionics/code/projectiles/lighting.dm @@ -1,34 +1,3 @@ -// Тут все заклинания, создающие снаряды. -/datum/action/cooldown/spell/pointed/projectile/psionic/air_bullet - name = "Psionic Air Bullet" - desc = "Wrap air in a psionic bubble, compress it, then send it flying at your enemies." - button_icon_state = "tech_repelmissiles" - cooldown_time = 1 SECONDS - mana_cost = 10 - cast_range = 9 - active_msg = "You prepare to charge air bullet..." - deactive_msg = "You relax." - projectile_type = /obj/projectile/magic/air_bullet - projectile_amount = INFINITY - psionic_level = 1 - point_cost = 2 - locked = FALSE - -/datum/action/cooldown/spell/pointed/projectile/psionic/air_bullet/ready_projectile(obj/projectile/to_fire, atom/target, mob/user, iteration) - . = ..() - to_fire.damage = 10 * cast_power - -/datum/action/cooldown/spell/pointed/projectile/psionic/air_bullet/fire_projectile(atom/target) - . = ..() - drain_mana() - playsound(owner, 'tff_modular/modules/psionics/sounds/power_feedback.ogg', 50, TRUE) - -/obj/projectile/magic/air_bullet - icon = 'tff_modular/modules/psionics/icons/projectiles.dmi' - icon_state = "air_bubble" - damage = 10 - damage_type = BRUTE - /datum/action/cooldown/spell/pointed/projectile/psionic/lighting name = "Psionic Lighting" desc = "Hits living beings in a 4x3 area in front of you with thunders." diff --git a/tff_modular/modules/psionics/code/quirk.dm b/tff_modular/modules/psionics/code/quirk.dm new file mode 100644 index 00000000000..44168818f47 --- /dev/null +++ b/tff_modular/modules/psionics/code/quirk.dm @@ -0,0 +1,21 @@ +/datum/quirk/psionic + name = "Psionic Abilities" + desc = "Either you were born like this or gained powers from implants/training or other events - you are a psionic. \ + Your mind can access the world that lies beyond our mortal plane. One day voices from within had pierced your skull \ + like a tide wave turns a sailboat over in open sea, but you withstanded it and received abilities your father haven't \ + even dreamed of. From now on a special type of energy is stored in your mind, body and soul and you have control over it." + value = 8 + medical_record_text = "Patient possesses connection to another plain of reality." + quirk_flags = QUIRK_HIDE_FROM_SCAN|QUIRK_HUMAN_ONLY|QUIRK_PROCESSES // Сканеры не видят псиоников. Только псионик школы может точно определить, является ли живое существо псиоником + gain_text = span_cyan("You mind feels uneasy, but... so powerful.") + lose_text = span_warning("You lost something that kept your connection with other realms.") + nova_stars_only = TRUE + allow_for_donator = TRUE + icon = "fa-star" + +/datum/quirk/psionic/add(client/client_source) + quirk_holder.add_psionic(/datum/psionic/sensitive) + +/datum/quirk/psionic/remove() + . = ..() + quirk_holder.remove_psionic() diff --git a/tff_modular/modules/psionics/code/spell/charge.dm b/tff_modular/modules/psionics/code/spell/charge.dm new file mode 100644 index 00000000000..c28dfde8dee --- /dev/null +++ b/tff_modular/modules/psionics/code/spell/charge.dm @@ -0,0 +1,54 @@ +/datum/action/cooldown/spell/psionic/charge + name = "Psionic Charge" + desc = "Use this spell on an item with a cell to charge it." + button_icon_state = "wiz_charge" + cooldown_time = 60 SECONDS + mana_cost = 10 + psionic_level = 1 + locked = FALSE + +/datum/action/cooldown/spell/psionic/charge/is_valid_target(atom/cast_on) + return isliving(cast_on) + +/datum/action/cooldown/spell/psionic/charge/cast(mob/living/cast_on) + . = ..() + + // Charge people we're pulling first and foremost + if(isliving(cast_on.pulling) && cast_power >= 2) + var/mob/living/pulled_living = cast_on.pulling + var/pulled_has_spells = FALSE + + for(var/datum/action/cooldown/spell/spell in pulled_living.actions) + spell.reset_spell_cooldown() + pulled_has_spells = TRUE + + if(pulled_has_spells) + to_chat(pulled_living, span_notice("You feel psi flowing through you. It feels good!")) + to_chat(cast_on, span_notice("[pulled_living] suddenly feels very warm!")) + return + + to_chat(pulled_living, span_notice("You feel very strange for a moment, but then it passes.")) + + // Then charge their main hand item, then charge their offhand item + var/obj/item/to_charge = cast_on.get_active_held_item() || cast_on.get_inactive_held_item() + if(!to_charge) + to_chat(cast_on, span_notice("You feel magical power surging through your hands, but the feeling rapidly fades.")) + return + + var/charge_return = SEND_SIGNAL(to_charge, COMSIG_ITEM_MAGICALLY_CHARGED, src, cast_on) + + if(QDELETED(to_charge)) + to_chat(cast_on, span_warning("[src] seems to react adversely with [to_charge]!")) + return + + if(charge_return & COMPONENT_ITEM_BURNT_OUT) + to_chat(cast_on, span_warning("[to_charge] seems to react negatively to [src], becoming uncomfortably warm!")) + + else if(charge_return & COMPONENT_ITEM_CHARGED) + to_chat(cast_on, span_notice("[to_charge] suddenly feels very warm!")) + + else + to_chat(cast_on, span_notice("[to_charge] doesn't seem to be react to [src].")) + + drain_mana() + playsound(cast_on, 'tff_modular/modules/psionics/sounds/power_fabrication.ogg', 50, TRUE) diff --git a/tff_modular/modules/psionics/code/spell/focus.dm b/tff_modular/modules/psionics/code/spell/focus.dm new file mode 100644 index 00000000000..be8004a10fc --- /dev/null +++ b/tff_modular/modules/psionics/code/spell/focus.dm @@ -0,0 +1,55 @@ +/datum/action/cooldown/spell/psionic/focus + name = "Psionic Focus" + desc = "Creates a useful reagents inside of you, removing stun." + button_icon_state = "tech_haste" + category = "Tier 2" + cooldown_time = 50 SECONDS + mana_cost = 20 + psionic_level = 2 + locked = FALSE + +/datum/action/cooldown/spell/psionic/focus/cast(atom/cast_on) + . = ..() + var/mob/living/carbon/human/human_living = cast_on + if(do_after(human_living, 1 SECONDS, timed_action_flags = IGNORE_SLOWDOWNS | IGNORE_USER_LOC_CHANGE | IGNORE_TARGET_LOC_CHANGE | IGNORE_HELD_ITEM)) + to_chat(human_living, span_warning("A calm rush envelops your mind..")) + human_living.reagents.add_reagent(/datum/reagent/medicine/psimulant, 10) + drain_mana() + playsound(human_living, 'tff_modular/modules/psionics/sounds/power_used.ogg', 50, TRUE) + else + return FALSE + +/datum/reagent/medicine/psimulant + name = "Psi-Stimulant" + description = "This strange substance that cannot be artificially created causes vivacity, stimulation and a surge of strength." + taste_description = "Brain" + color = COLOR_TOOL_BLUE + self_consuming = TRUE + +/datum/reagent/medicine/psimulant/on_mob_add(mob/living/affected_mob, amount) + . = ..() + var/mob/living/carbon/human/psimulator = affected_mob + psimulator.add_movespeed_modifier(/datum/movespeed_modifier/psimulant) + psimulator.add_actionspeed_modifier(/datum/actionspeed_modifier/psimulant) + +/datum/reagent/medicine/psimulant/metabolize_reagent(mob/living/carbon/affected_mob, seconds_per_tick, metabolized_volume) + . = ..() + affected_mob.SetParalyzed(0) + affected_mob.SetStun(0) + affected_mob.SetAllImmobility(0) + affected_mob.adjust_stamina_loss(-5) + affected_mob.remove_status_effect(/datum/status_effect/speech/stutter) + affected_mob.set_resting(FALSE) + affected_mob.SetSleeping(0) + +/datum/reagent/medicine/psimulant/on_mob_delete(mob/living/affected_mob) + . = ..() + var/mob/living/carbon/human/psimulator = affected_mob + psimulator.remove_movespeed_modifier(/datum/movespeed_modifier/psimulant) + psimulator.remove_actionspeed_modifier(/datum/actionspeed_modifier/psimulant) + +/datum/movespeed_modifier/psimulant + multiplicative_slowdown = -0.2 + +/datum/actionspeed_modifier/psimulant + multiplicative_slowdown = -0.3 diff --git a/tff_modular/modules/psionics/code/spell/ion_blast.dm b/tff_modular/modules/psionics/code/spell/ion_blast.dm new file mode 100644 index 00000000000..c10bd95ead2 --- /dev/null +++ b/tff_modular/modules/psionics/code/spell/ion_blast.dm @@ -0,0 +1,16 @@ +// Создаёт ЕМП в месте удара руки +/datum/action/cooldown/spell/psionic/emp + name = "Ion Blast" + desc = "Cause a small, but powerful EMP." + button_icon_state = "tech_overload" + cooldown_time = 30 SECONDS + mana_cost = 50 + psionic_level = 2 + locked = FALSE + category = "Tier 2" + +/datum/action/cooldown/spell/psionic/emp/cast(atom/cast_on) + . = ..() + empulse(cast_on.loc, 3, 3) + playsound(cast_on, 'tff_modular/modules/psionics/sounds/power_fail.ogg', 50, TRUE) + drain_mana() diff --git a/tff_modular/modules/psionics/code/spell/nlom_eyes.dm b/tff_modular/modules/psionics/code/spell/nlom_eyes.dm new file mode 100644 index 00000000000..6938c73342c --- /dev/null +++ b/tff_modular/modules/psionics/code/spell/nlom_eyes.dm @@ -0,0 +1,97 @@ +/datum/action/cooldown/spell/psionic/nlom_eyes + name = "Nlom Eyes" + desc = "Roughly locate a mob on your z-level." + button_icon_state = "tech_control" + mana_cost = 5 + cooldown_time = 30 SECONDS + point_cost = 1 + locked = FALSE + psionic_level = 2 + category = "Tier 2" + var/datum/status_effect/agent_pinpointer/scan/navigator + +/datum/action/cooldown/spell/psionic/nlom_eyes/cast(atom/cast_on) + . = ..() + var/list/signatures_list = list() + for(var/mob/living/carbon/human/mob_signatures as anything in world) + if(!iscarbon(mob_signatures)) + continue + if(!is_valid_z_level(cast_on.loc, mob_signatures)) + continue + if(mob_signatures.stat == DEAD) + continue + signatures_list += mob_signatures + + var/mob/living/carbon/who_to_find = tgui_input_list(cast_on, "Choose who you want to find?", "Nlom Eyes", signatures_list) + if(!who_to_find || who_to_find.stat == DEAD || QDELETED(cast_on)) + return FALSE + + playsound(owner, 'tff_modular/modules/psionics/sounds/power_fabrication.ogg', 50, TRUE, SILENCED_SOUND_EXTRARANGE) + owner.balloon_alert(owner, get_balloon_message(who_to_find)) + drain_mana() + +/// Gets the balloon message for who we're tracking. +/datum/action/cooldown/spell/psionic/nlom_eyes/proc/get_balloon_message(atom/tracked_thing) + var/balloon_message = "error text!" + var/turf/their_turf = get_turf(tracked_thing) + var/turf/our_turf = get_turf(owner) + var/their_z = their_turf?.z + var/our_z = our_turf?.z + + // One of us is in somewhere we shouldn't be + if(!our_z || !their_z) + // "Hell if I know" + balloon_message = "on another plane!" + + // They're not on the same z-level as us + else if(our_z != their_z) + // They're on the station + if(is_station_level(their_z)) + // We're on a multi-z station + if(is_station_level(our_z)) + if(our_z > their_z) + balloon_message = "below you!" + else + balloon_message = "above you!" + // We're off station, they're not + else + balloon_message = "on station!" + + // Mining + else if(is_mining_level(their_z)) + balloon_message = "on lavaland!" + + // In the gateway + else if(is_away_level(their_z) || is_secret_level(their_z)) + balloon_message = "beyond the gateway!" + + // They're somewhere we probably can't get too - sacrifice z-level, centcom, etc + else + balloon_message = "on another plane!" + + // They're on the same z-level as us! + else + var/dist = get_dist(our_turf, their_turf) + var/dir = get_dir(our_turf, their_turf) + + var/arrow_color + + switch(dist) + if(0 to 15) + balloon_message = "very near, [dir2text(dir)]!" + arrow_color = COLOR_CARP_LIGHT_BLUE + if(16 to 31) + balloon_message = "near, [dir2text(dir)]!" + arrow_color = COLOR_BLUE + if(32 to 127) + balloon_message = "far, [dir2text(dir)]!" + arrow_color = COLOR_CARP_DARK_BLUE + else + balloon_message = "very far!" + arrow_color = COLOR_DARK + + if(owner.hud_used) + var/atom/movable/screen/navigate_arrow/arrow = owner.hud_used.add_screen_object(/atom/movable/screen/navigate_arrow, HUD_PSIONIC_ARROW, HUD_GROUP_INFO, update_screen = TRUE) + arrow.start_effect(their_turf, arrow_color) + + return balloon_message diff --git a/tff_modular/modules/psionics/code/spell/search.dm b/tff_modular/modules/psionics/code/spell/search.dm new file mode 100644 index 00000000000..12d30b07cf5 --- /dev/null +++ b/tff_modular/modules/psionics/code/spell/search.dm @@ -0,0 +1,32 @@ +/datum/action/cooldown/spell/psionic/search + name = "Psionic Search" + desc = "Scan your Z-level for Nlom signatures." + button_icon_state = "wiz_shield" + mana_cost = 5 + cooldown_time = 10 SECONDS + point_cost = 1 + locked = FALSE + +/datum/action/cooldown/spell/psionic/search/cast(atom/cast_on) + . = ..() + var/list/alive_list = list() + var/mob/living/carbon/human/searcher = cast_on + for(var/mob/living/carbon/human/alive in world) + var/datum/psionic/psi_datum = alive.get_psionic() + if(!is_valid_z_level(alive.loc, searcher.loc)) + continue + if(alive.stat == DEAD) + continue + if(!psi_datum) + continue + if(psi_datum.is_suppressed()) + continue + alive_list += alive + + var/mob/living/carbon/who_to_find = tgui_input_list(searcher, "Who you want to find?", "Psionic Search", alive_list) + if(!who_to_find || who_to_find.stat == DEAD || QDELETED(searcher)) + return FALSE + + var/area/place = get_area(who_to_find) + to_chat(searcher, span_horizonblue("The one who you looking for at... [place.name]")) + playsound(searcher, 'tff_modular/modules/psionics/sounds/power_used.ogg', 50, TRUE, SILENCED_SOUND_EXTRARANGE) diff --git a/tff_modular/modules/psionics/code/spell/shockwave.dm b/tff_modular/modules/psionics/code/spell/shockwave.dm new file mode 100644 index 00000000000..f800747f9ee --- /dev/null +++ b/tff_modular/modules/psionics/code/spell/shockwave.dm @@ -0,0 +1,33 @@ +/datum/action/cooldown/spell/psionic/shockwave + name = "Psionic Shockwave" + desc = "Create a wave of telekinetic energy to pummel the ground around you." + button_icon_state = "tech_corona" + category = "Tier 2" + mana_cost = 20 + cooldown_time = 50 SECONDS + point_cost = 1 + locked = FALSE + psionic_level = 2 + +/datum/action/cooldown/spell/psionic/shockwave/can_cast_spell(feedback) + . = ..() + if(HAS_TRAIT(owner, TRAIT_INCAPACITATED)) + return FALSE + return TRUE + +/datum/action/cooldown/spell/psionic/shockwave/before_cast(atom/cast_on) + . = ..() + if(isspaceturf(get_turf(cast_on))) + to_chat(cast_on, span_horizonblue("You charge your shockwave, slam your foot down... and then remember that you're in space.")) + return SPELL_CANCEL_CAST + +/datum/action/cooldown/spell/psionic/shockwave/cast(atom/cast_on) + . = ..() + for(var/mob/living/victims as anything in get_hearers_in_view(7, cast_on)) + if(!isliving(victims)) + continue + if(victims == cast_on) + continue + shake_camera(victims, 2 SECONDS, 2) + victims.Paralyze(2 SECONDS) + cast_on.visible_message(span_horizonblue("[cast_on]'s foot starts to cover in blue energy, and then he stomps on the floor"), span_horizonblue("You channel psionic energy into your foot, and then stomp on the floor.")) diff --git a/tff_modular/modules/psionics/code/spell/signal.dm b/tff_modular/modules/psionics/code/spell/signal.dm new file mode 100644 index 00000000000..e3ff4c0dc47 --- /dev/null +++ b/tff_modular/modules/psionics/code/spell/signal.dm @@ -0,0 +1,37 @@ +/datum/action/cooldown/spell/psionic/signal + name = "Psionic Signal" + desc = "Sends a signal to all psionics in the sector." + mana_cost = 5 + +/datum/action/cooldown/spell/psionic/signal/can_cast_spell(feedback) + . = ..() + if(HAS_TRAIT(owner, TRAIT_INCAPACITATED)) + return SPELL_CANCEL_CAST + +/datum/action/cooldown/spell/psionic/signal/cast(atom/cast_on) + . = ..() + var/mob/living/caster = cast_on + var/list/mob/living/psionic_list = list() + if(isnull(psionic_list)) + for(var/mob/living/psionics as anything in world) + if(!psionics.get_psionic()) + continue + if(psionics.stat == DEAD) + continue + if(psionics.psi_sensivity.is_suppressed()) + continue + if(!is_valid_z_level(caster.z, psionics.z)) + continue + psionic_list += psionics + + if(isnull(psionic_list)) + to_chat(caster, span_horizonblue("There is no one you can send signal.")) + return FALSE + + var/text = tgui_input_text(caster, "Psionic Signal", "What kind of signal do you want to send to other psionics?", "Meow", 400) + if(!text || caster.stat == DEAD || QDELETED(caster)) + return FALSE + + text = lowertext(text) + + to_chat(psionic_list, span_horizonblue("[text]")) diff --git a/tff_modular/modules/psionics/code/spell/spell.dm b/tff_modular/modules/psionics/code/spell/spell.dm deleted file mode 100644 index e0942a5b60c..00000000000 --- a/tff_modular/modules/psionics/code/spell/spell.dm +++ /dev/null @@ -1,369 +0,0 @@ -// Создаёт ЕМП в месте удара руки -/datum/action/cooldown/spell/psionic/emp - name = "Ion Blast" - desc = "Cause a small, but powerful EMP." - button_icon_state = "tech_overload" - cooldown_time = 15 SECONDS - mana_cost = 30 - psionic_level = 2 - locked = FALSE - category = "Tier 2" - -/datum/action/cooldown/spell/psionic/emp/cast(atom/cast_on) - . = ..() - empulse(cast_on.loc, 3, 3) - playsound(cast_on, 'tff_modular/modules/psionics/sounds/power_fail.ogg', 50, TRUE) - drain_mana() - -/datum/action/cooldown/spell/psionic/focus - name = "Psionic Focus" - desc = "Creates a useful reagents inside of you, removing stun." - button_icon_state = "tech_haste" - category = "Tier 2" - cooldown_time = 50 SECONDS - mana_cost = 20 - point_cost = 1 - psionic_level = 2 - locked = FALSE - -/datum/action/cooldown/spell/psionic/focus/cast(atom/cast_on) - . = ..() - var/mob/living/carbon/human/human_living = cast_on - if(do_after(human_living, 1 SECONDS, timed_action_flags = IGNORE_SLOWDOWNS | IGNORE_USER_LOC_CHANGE | IGNORE_TARGET_LOC_CHANGE | IGNORE_HELD_ITEM)) - to_chat(human_living, span_warning("A calm rush envelops your mind..")) - human_living.reagents.add_reagent_list(list(/datum/reagent/medicine/ephedrine = 5, /datum/reagent/medicine/synaptizine = 5, /datum/reagent/medicine/epinephrine = 5)) - human_living.SetStun(0) - human_living.SetParalyzed(0) - human_living.SetSleeping(0) - human_living.SetAllImmobility(0) - drain_mana() - playsound(human_living, 'tff_modular/modules/psionics/sounds/power_used.ogg', 50, TRUE) - else - return FALSE - -/datum/action/cooldown/spell/psionic/charge - name = "Psionic Charge" - desc = "Use this spell on an item with a cell to charge it." - button_icon_state = "wiz_charge" - cooldown_time = 60 SECONDS - mana_cost = 10 - point_cost = 1 - psionic_level = 1 - locked = FALSE - -/datum/action/cooldown/spell/psionic/charge/is_valid_target(atom/cast_on) - return isliving(cast_on) - -/datum/action/cooldown/spell/psionic/charge/cast(mob/living/cast_on) - . = ..() - - // Charge people we're pulling first and foremost - if(isliving(cast_on.pulling) && cast_power >= 2) - var/mob/living/pulled_living = cast_on.pulling - var/pulled_has_spells = FALSE - - for(var/datum/action/cooldown/spell/spell in pulled_living.actions) - spell.reset_spell_cooldown() - pulled_has_spells = TRUE - - if(pulled_has_spells) - to_chat(pulled_living, span_notice("You feel psi flowing through you. It feels good!")) - to_chat(cast_on, span_notice("[pulled_living] suddenly feels very warm!")) - return - - to_chat(pulled_living, span_notice("You feel very strange for a moment, but then it passes.")) - - // Then charge their main hand item, then charge their offhand item - var/obj/item/to_charge = cast_on.get_active_held_item() || cast_on.get_inactive_held_item() - if(!to_charge) - to_chat(cast_on, span_notice("You feel magical power surging through your hands, but the feeling rapidly fades.")) - return - - var/charge_return = SEND_SIGNAL(to_charge, COMSIG_ITEM_MAGICALLY_CHARGED, src, cast_on) - - if(QDELETED(to_charge)) - to_chat(cast_on, span_warning("[src] seems to react adversely with [to_charge]!")) - return - - if(charge_return & COMPONENT_ITEM_BURNT_OUT) - to_chat(cast_on, span_warning("[to_charge] seems to react negatively to [src], becoming uncomfortably warm!")) - - else if(charge_return & COMPONENT_ITEM_CHARGED) - to_chat(cast_on, span_notice("[to_charge] suddenly feels very warm!")) - - else - to_chat(cast_on, span_notice("[to_charge] doesn't seem to be react to [src].")) - - drain_mana() - playsound(cast_on, 'tff_modular/modules/psionics/sounds/power_fabrication.ogg', 50, TRUE) - -/datum/action/cooldown/spell/psionic/suppression - name = "Psionic Suppression" - desc = "Suppress your psionic energy, making you invisible to other psionics, but you can't use psionic abilities." - button_icon_state = "tech_shield" - category = "Tier 2" - cooldown_time = 30 SECONDS - psionic_level = 2 - mana_cost = 0 - point_cost = 0 - ignore_suppression = TRUE - locked = FALSE - var/suppressing = FALSE - -/datum/action/cooldown/spell/psionic/suppression/cast(atom/cast_on) - . = ..() - if(suppressing || HAS_TRAIT_FROM(cast_on, TRAIT_PSIONIC_SUPPRESSED, ACTION_TRAIT)) - REMOVE_TRAIT(cast_on, TRAIT_PSIONIC_SUPPRESSED, ACTION_TRAIT) - else - ADD_TRAIT(cast_on, TRAIT_PSIONIC_SUPPRESSED, ACTION_TRAIT) - -/datum/action/cooldown/spell/psionic/sunder - name = "Psionic Sunder" - desc = "Destroy a Zona Bovinae of psionic creature you pulling. This will make them force-suppressed." - button_icon_state = "ling_berserk" - category = "Tier 2" - cooldown_time = 10 SECONDS - psionic_level = 2 - mana_cost = 30 - point_cost = 1 - locked = FALSE - -/datum/action/cooldown/spell/psionic/sunder/before_cast(atom/cast_on) - . = ..() - var/mob/living/carbon/human/human_living = cast_on - var/mob/living/carbon/human/victim = human_living.pulling - var/datum/psionic/victim_psionic = victim.get_psionic() - if(!victim) - to_chat(human_living, span_horizonblue("You must grab victim to use this ability!")) - return FALSE - if(!victim_psionic) - to_chat(human_living, span_horizonblue("Not a Psionic!")) - return FALSE - if(victim_psionic.get_level() > 1) - to_chat(human_living, span_horizonblue("Their psi mind is too strong!")) - return FALSE - -/datum/action/cooldown/spell/psionic/sunder/cast(atom/cast_on) - . = ..() - var/mob/living/carbon/human/human_living = cast_on - var/mob/living/carbon/human/victim = human_living.pulling - to_chat(victim, span_big(span_horizonblue("You feel your psionic energy leaving your mind..."))) - if(!do_after(human_living, 10 SECONDS, victim)) - return FALSE - ADD_TRAIT(victim, TRAIT_PSIONIC_SUPPRESSED, SUNDER_TRAIT) - playsound(human_living, 'tff_modular/modules/psionics/sounds/power_fabrication.ogg', 50, TRUE) - -/datum/action/cooldown/spell/psionic/stamina - name = "Psionic Stamina Weave" - desc = "Activate this spell to regenerate your psi-mana a little bit." - button_icon_state = "tech_mend_template" - point_cost = 1 - cooldown_time = 20 SECONDS - mana_cost = 0 - locked = FALSE - var/charging = FALSE - -/datum/action/cooldown/spell/psionic/stamina/cast(atom/cast_on) - . = ..() - regenerate_stamina(cast_on) - return TRUE - -/datum/action/cooldown/spell/psionic/stamina/is_action_active(atom/movable/screen/movable/action_button/current_button) - return charging - -/datum/action/cooldown/spell/psionic/stamina/proc/regenerate_stamina(mob/living/carbon/human/human_living) - if(!do_after(human_living, 1 SECONDS)) - charging = FALSE - return FALSE - charging = TRUE - psionic_datum.adjust_psi_energy(5) - playsound(human_living, 'tff_modular/modules/psionics/sounds/power_used.ogg', 50, TRUE) - regenerate_stamina(human_living) - -/datum/action/cooldown/spell/psionic/search - name = "Psionic Search" - desc = "Scan your Z-level for Nlom signatures." - button_icon_state = "wiz_shield" - mana_cost = 5 - cooldown_time = 10 SECONDS - point_cost = 1 - locked = FALSE - -/datum/action/cooldown/spell/psionic/search/cast(atom/cast_on) - . = ..() - var/list/alive_list = list() - var/mob/living/carbon/human/searcher = cast_on - for(var/mob/living/carbon/human/alive in world) - var/datum/psionic/psi_datum = alive.get_psionic() - if(!is_valid_z_level(alive.loc, searcher.loc)) - continue - if(alive.stat == DEAD) - continue - if(!psi_datum) - continue - if(psi_datum.is_suppressed()) - continue - alive_list += alive - - var/mob/living/carbon/who_to_find = tgui_input_list(searcher, "Who you want to find?", "Psionic Search", alive_list) - if(!who_to_find || who_to_find.stat == DEAD || QDELETED(searcher)) - return FALSE - - var/area/place = get_area(who_to_find) - to_chat(searcher, span_horizonblue("The one who you looking for at... [place.name]")) - playsound(searcher, 'tff_modular/modules/psionics/sounds/power_used.ogg', 50, TRUE, SILENCED_SOUND_EXTRARANGE) - -/datum/action/cooldown/spell/psionic/nlom_eyes - name = "Nlom Eyes" - desc = "Roughly locate a mob on your z-level." - button_icon_state = "tech_control" - mana_cost = 5 - cooldown_time = 30 SECONDS - point_cost = 1 - locked = FALSE - psionic_level = 2 - category = "Tier 2" - var/datum/status_effect/agent_pinpointer/scan/navigator - -/datum/action/cooldown/spell/psionic/nlom_eyes/cast(atom/cast_on) - . = ..() - var/list/signatures_list = list() - for(var/mob/living/carbon/human/mob_signatures as anything in world) - if(!iscarbon(mob_signatures)) - continue - if(!is_valid_z_level(cast_on.loc, mob_signatures)) - continue - if(mob_signatures.stat == DEAD) - continue - signatures_list += mob_signatures - - var/mob/living/carbon/who_to_find = tgui_input_list(cast_on, "Choose who you want to find?", "Nlom Eyes", signatures_list) - if(!who_to_find || who_to_find.stat == DEAD || QDELETED(cast_on)) - return FALSE - - playsound(owner, 'tff_modular/modules/psionics/sounds/power_fabrication.ogg', 50, TRUE, SILENCED_SOUND_EXTRARANGE) - owner.balloon_alert(owner, get_balloon_message(who_to_find)) - drain_mana() - -/// Gets the balloon message for who we're tracking. -/datum/action/cooldown/spell/psionic/nlom_eyes/proc/get_balloon_message(atom/tracked_thing) - var/balloon_message = "error text!" - var/turf/their_turf = get_turf(tracked_thing) - var/turf/our_turf = get_turf(owner) - var/their_z = their_turf?.z - var/our_z = our_turf?.z - - // One of us is in somewhere we shouldn't be - if(!our_z || !their_z) - // "Hell if I know" - balloon_message = "on another plane!" - - // They're not on the same z-level as us - else if(our_z != their_z) - // They're on the station - if(is_station_level(their_z)) - // We're on a multi-z station - if(is_station_level(our_z)) - if(our_z > their_z) - balloon_message = "below you!" - else - balloon_message = "above you!" - // We're off station, they're not - else - balloon_message = "on station!" - - // Mining - else if(is_mining_level(their_z)) - balloon_message = "on lavaland!" - - // In the gateway - else if(is_away_level(their_z) || is_secret_level(their_z)) - balloon_message = "beyond the gateway!" - - // They're somewhere we probably can't get too - sacrifice z-level, centcom, etc - else - balloon_message = "on another plane!" - - // They're on the same z-level as us! - else - var/dist = get_dist(our_turf, their_turf) - var/dir = get_dir(our_turf, their_turf) - - var/arrow_color - - switch(dist) - if(0 to 15) - balloon_message = "very near, [dir2text(dir)]!" - arrow_color = COLOR_CARP_LIGHT_BLUE - if(16 to 31) - balloon_message = "near, [dir2text(dir)]!" - arrow_color = COLOR_BLUE - if(32 to 127) - balloon_message = "far, [dir2text(dir)]!" - arrow_color = COLOR_CARP_DARK_BLUE - else - balloon_message = "very far!" - arrow_color = COLOR_DARK - - if(owner.hud_used) - var/atom/movable/screen/navigate_arrow/arrow = owner.hud_used.add_screen_object(/atom/movable/screen/navigate_arrow, HUD_PSIONIC_ARROW, HUD_GROUP_INFO, update_screen = TRUE) - arrow.start_effect(their_turf, arrow_color) - - return balloon_message - -/datum/action/cooldown/spell/psionic/shockwave - name = "Psionic Shockwave" - desc = "Create a wave of telekinetic energy to pummel the ground around you." - button_icon_state = "tech_corona" - category = "Tier 2" - mana_cost = 20 - cooldown_time = 50 SECONDS - point_cost = 1 - locked = FALSE - psionic_level = 2 - -/datum/action/cooldown/spell/psionic/shockwave/can_cast_spell(feedback) - . = ..() - if(HAS_TRAIT(owner, TRAIT_INCAPACITATED)) - return FALSE - return TRUE - -/datum/action/cooldown/spell/psionic/shockwave/before_cast(atom/cast_on) - . = ..() - if(isspaceturf(get_turf(cast_on))) - to_chat(cast_on, span_horizonblue("You charge your shockwave, slam your foot down... and then remember that you're in space.")) - return SPELL_CANCEL_CAST - -/datum/action/cooldown/spell/psionic/shockwave/cast(atom/cast_on) - . = ..() - for(var/mob/living/victims as anything in get_hearers_in_view(7, cast_on)) - if(!isliving(victims)) - continue - if(victims == cast_on) - continue - shake_camera(victims, 2 SECONDS, 2) - victims.Paralyze(2 SECONDS) - cast_on.visible_message(span_horizonblue("[cast_on]'s foot starts to cover in blue energy, and then he stomps on the floor"), span_horizonblue("You channel psionic energy into your foot, and then stomp on the floor.")) - -/datum/action/cooldown/spell/psionic/time_stop - name = "Time Stop" - desc = "Create a wave of telekinetic energy to pummel the ground around you." - button_icon_state = "tech_control" - category = "Tier 2" - mana_cost = 80 - cooldown_time = 120 SECONDS - point_cost = 3 - locked = FALSE - psionic_level = 2 - -/datum/action/cooldown/spell/psionic/time_stop/cast(atom/cast_on) - . = ..() - var/list/default_immune_atoms = list() - default_immune_atoms += cast_on - new /obj/effect/timestop/magic(get_turf(cast_on), 1, 2 SECONDS * cast_power, default_immune_atoms) - -/datum/action/cooldown/spell/psionic/time_stop/can_cast_spell(feedback) - . = ..() - if(HAS_TRAIT(owner, TRAIT_INCAPACITATED)) - return FALSE - return TRUE diff --git a/tff_modular/modules/psionics/code/spell/stamina.dm b/tff_modular/modules/psionics/code/spell/stamina.dm new file mode 100644 index 00000000000..7841c29988b --- /dev/null +++ b/tff_modular/modules/psionics/code/spell/stamina.dm @@ -0,0 +1,27 @@ +/datum/action/cooldown/spell/psionic/stamina + name = "Psionic Stamina Weave" + desc = "Activate this spell to regenerate your psi-mana and nutrients a little bit." + button_icon_state = "tech_mend_template" + point_cost = 1 + cooldown_time = 20 SECONDS + mana_cost = 0 + locked = FALSE + var/charging = FALSE + +/datum/action/cooldown/spell/psionic/stamina/cast(atom/cast_on) + . = ..() + regenerate_stamina(cast_on) + return TRUE + +/datum/action/cooldown/spell/psionic/stamina/is_action_active(atom/movable/screen/movable/action_button/current_button) + return charging + +/datum/action/cooldown/spell/psionic/stamina/proc/regenerate_stamina(mob/living/carbon/human/human_living) + if(!do_after(human_living, 1 SECONDS)) + charging = FALSE + return FALSE + charging = TRUE + psionic_datum.adjust_psi_energy(5) + human_living.adjust_nutrition(5) + playsound(human_living, 'tff_modular/modules/psionics/sounds/power_used.ogg', 50, TRUE) + regenerate_stamina(human_living) diff --git a/tff_modular/modules/psionics/code/spell/sunder.dm b/tff_modular/modules/psionics/code/spell/sunder.dm new file mode 100644 index 00000000000..3ec6c08979f --- /dev/null +++ b/tff_modular/modules/psionics/code/spell/sunder.dm @@ -0,0 +1,34 @@ +/datum/action/cooldown/spell/psionic/sunder + name = "Psionic Sunder" + desc = "Destroy a Zona Bovinae of psionic creature you pulling. This will make them force-suppressed." + button_icon_state = "ling_berserk" + category = "Tier 2" + cooldown_time = 10 SECONDS + psionic_level = 2 + mana_cost = 30 + locked = FALSE + +/datum/action/cooldown/spell/psionic/sunder/before_cast(atom/cast_on) + . = ..() + var/mob/living/carbon/human/human_living = cast_on + var/mob/living/carbon/human/victim = human_living.pulling + var/datum/psionic/victim_psionic = victim.get_psionic() + if(!victim) + to_chat(human_living, span_horizonblue("You must grab victim to use this ability!")) + return FALSE + if(!victim_psionic) + to_chat(human_living, span_horizonblue("Not a Psionic!")) + return FALSE + if(victim_psionic.get_level() > 1) + to_chat(human_living, span_horizonblue("Their psi mind is too strong!")) + return FALSE + +/datum/action/cooldown/spell/psionic/sunder/cast(atom/cast_on) + . = ..() + var/mob/living/carbon/human/human_living = cast_on + var/mob/living/carbon/human/victim = human_living.pulling + to_chat(victim, span_big(span_horizonblue("You feel your psionic energy leaving your mind..."))) + if(!do_after(human_living, 10 SECONDS, victim)) + return FALSE + ADD_TRAIT(victim, TRAIT_PSIONIC_SUPPRESSED, SUNDER_TRAIT) + playsound(human_living, 'tff_modular/modules/psionics/sounds/power_fabrication.ogg', 50, TRUE) diff --git a/tff_modular/modules/psionics/code/spell/suppression.dm b/tff_modular/modules/psionics/code/spell/suppression.dm new file mode 100644 index 00000000000..dae5d494293 --- /dev/null +++ b/tff_modular/modules/psionics/code/spell/suppression.dm @@ -0,0 +1,19 @@ +/datum/action/cooldown/spell/psionic/suppression + name = "Psionic Suppression" + desc = "Suppress your psionic energy, making your signal invisible to other psionics, but you can't use psionic abilities." + button_icon_state = "tech_shield" + category = "Tier 2" + cooldown_time = 30 SECONDS + psionic_level = 2 + mana_cost = 0 + point_cost = 0 + ignore_suppression = TRUE + locked = FALSE + var/suppressing = FALSE + +/datum/action/cooldown/spell/psionic/suppression/cast(atom/cast_on) + . = ..() + if(suppressing || HAS_TRAIT_FROM(cast_on, TRAIT_PSIONIC_SUPPRESSED, ACTION_TRAIT)) + REMOVE_TRAIT(cast_on, TRAIT_PSIONIC_SUPPRESSED, ACTION_TRAIT) + else + ADD_TRAIT(cast_on, TRAIT_PSIONIC_SUPPRESSED, ACTION_TRAIT) diff --git a/tff_modular/modules/psionics/code/spell/time_stop.dm b/tff_modular/modules/psionics/code/spell/time_stop.dm new file mode 100644 index 00000000000..b50650d670d --- /dev/null +++ b/tff_modular/modules/psionics/code/spell/time_stop.dm @@ -0,0 +1,22 @@ +/datum/action/cooldown/spell/psionic/time_stop + name = "Time Stop" + desc = "Create a wave of telekinetic energy to pummel the ground around you." + button_icon_state = "tech_control" + category = "Tier 2" + mana_cost = 80 + cooldown_time = 120 SECONDS + point_cost = 3 + locked = FALSE + psionic_level = 2 + +/datum/action/cooldown/spell/psionic/time_stop/cast(atom/cast_on) + . = ..() + var/list/default_immune_atoms = list() + default_immune_atoms += cast_on + new /obj/effect/timestop/magic(get_turf(cast_on), 1, 2 SECONDS * cast_power, default_immune_atoms) + +/datum/action/cooldown/spell/psionic/time_stop/can_cast_spell(feedback) + . = ..() + if(HAS_TRAIT(owner, TRAIT_INCAPACITATED)) + return FALSE + return TRUE diff --git a/tff_modular/modules/psionics/code/spell/transparency.dm b/tff_modular/modules/psionics/code/spell/transparency.dm new file mode 100644 index 00000000000..d1d64267bde --- /dev/null +++ b/tff_modular/modules/psionics/code/spell/transparency.dm @@ -0,0 +1,68 @@ +/datum/action/cooldown/spell/psionic/transparency + name = "Psionic Transparency" + desc = "You become invisible for 10 seconds. You can't take damage and interact with the world." + button_icon_state = "tech_illusion" + category = "Tier 2" + cooldown_time = 120 SECONDS + psionic_level = 2 + mana_cost = 70 + point_cost = 2 + locked = FALSE + +/datum/action/cooldown/spell/psionic/transparency/is_valid_target(atom/cast_on) + if(!iscarbon(cast_on)) + return FALSE + return TRUE + +/datum/action/cooldown/spell/psionic/transparency/cast(atom/cast_on) + . = ..() + var/mob/living/carbon/human/living_human = cast_on + living_human.apply_status_effect(/datum/status_effect/transparency) + +/datum/status_effect/transparency + id = "Transparency" + status_type = STATUS_EFFECT_REFRESH + duration = 10 SECONDS + + var/static/list/transparency_traits = list(TRAIT_GODMODE, TRAIT_HANDS_BLOCKED, TRAIT_SECLUDED_LOCATION) + +/datum/status_effect/transparency/on_apply() + animate(owner, alpha = 0, time = 1 SECONDS) + owner.set_density(FALSE) + RegisterSignal(owner, COMSIG_MOB_BEFORE_SPELL_CAST, PROC_REF(prevent_spell_usage)) + RegisterSignal(owner, COMSIG_CARBON_CUFF_ATTEMPTED, PROC_REF(prevent_cuff)) + RegisterSignal(owner, COMSIG_BEING_STRIPPED, PROC_REF(no_strip)) + owner.add_traits(transparency_traits, TRAIT_STATUS_EFFECT(id)) + return TRUE + +/datum/status_effect/transparency/on_remove() + owner.remove_traits(transparency_traits, TRAIT_STATUS_EFFECT(id)) + owner.alpha = initial(owner.alpha) + owner.density = initial(owner.density) + UnregisterSignal(owner, SIGNAL_REMOVETRAIT(TRAIT_ALLOW_HERETIC_CASTING)) + UnregisterSignal(owner, COMSIG_MOB_BEFORE_SPELL_CAST) + UnregisterSignal(owner, COMSIG_ATOM_HOLYATTACK) + UnregisterSignal(owner, COMSIG_CARBON_CUFF_ATTEMPTED) + UnregisterSignal(owner, COMSIG_BEING_STRIPPED) + owner.visible_message( + span_warning("The haze around [owner] disappears, leaving them materialized!"), + span_notice("You exit the transparency."), + ) + return TRUE + +/datum/status_effect/transparency/get_examine_text() + return span_horizonblue("How do you see [owner]?") + +/datum/status_effect/transparency/proc/no_strip(atom/source, mob/user, obj/item/equipping) + SIGNAL_HANDLER + to_chat(user, span_warning("You fail to put anything on [source] as they are incorporeal!")) + return COMPONENT_CANT_STRIP + +/datum/status_effect/transparency/proc/prevent_spell_usage(datum/source, datum/spell) + SIGNAL_HANDLER + owner.balloon_alert(owner, "may not cast spells in transparency!") + return SPELL_CANCEL_CAST + +/datum/status_effect/transparency/proc/prevent_cuff(datum/source, mob/attemptee) + SIGNAL_HANDLER + return COMSIG_CARBON_CUFF_PREVENT diff --git a/tff_modular/modules/psionics/code/touch/assay.dm b/tff_modular/modules/psionics/code/touch/assay.dm new file mode 100644 index 00000000000..0561d191b24 --- /dev/null +++ b/tff_modular/modules/psionics/code/touch/assay.dm @@ -0,0 +1,45 @@ +// Спелл для чтения разума другого игрока на наличие псионических способностей + +/datum/action/cooldown/spell/touch/psionic/assay + name = "Psionic Assay" + desc = "Check if the target is a psionic." + button_icon_state = "tech_audibledeception" + cooldown_time = 60 SECONDS + mana_cost = 10 + target_msg = "Your get a headache, but it quickly fades." + hand_path = /obj/item/melee/touch_attack/psionic/assay + draw_message = span_notice("You ready your hand to cleanse a patient.") + drop_message = span_notice("You lower your hand.") + can_cast_on_self = TRUE + category = "Tier 1" + locked = FALSE + +/datum/action/cooldown/spell/touch/psionic/assay/cast_on_hand_hit(obj/item/melee/touch_attack/hand, atom/victim, mob/living/carbon/mendicant) + if(ishuman(victim)) + var/mob/living/carbon/human/human_victim = victim + if(human_victim.can_block_magic(antimagic_flags)) + to_chat(human_victim, span_notice("Psionic nearby tries to check you for psionic levels.")) + else + to_chat(human_victim, span_warning(target_msg)) + owner.visible_message(span_warning("[owner] presses his thumb onto [victim]s forehead."), + span_notice("You press your thumb onto [victim]s forehead and begin reading them.")) + to_chat(victim, span_danger("[owner] presses a thumb onto your forehead and holds it there. It burns sligthly!")) + if(do_after(mendicant, 6 SECONDS, human_victim, IGNORE_SLOWDOWNS, TRUE)) + read_psionic_level(human_victim) + drain_mana() + return TRUE + else + return FALSE + +/datum/action/cooldown/spell/touch/psionic/assay/proc/read_psionic_level(mob/living/carbon/human/patient) + if(issynthetic(patient) && cast_power < 2) + to_chat(owner, span_notice("I can see... just numbers. No idea how to work with synths.")) + return FALSE + + if(patient.get_psionic()) + var/datum/psionic/target_psi = patient.get_psionic() + owner.visible_message(span_notice("[owner] backs off from [patient]."), + span_cyan("Target is a psionic. [patient.p_Their()] rank is [target_psi.psionic_level_string]")) + else + owner.visible_message(span_notice("[owner] backs off from [patient]."), + span_cyan("Target is not a psionic.")) diff --git a/tff_modular/modules/psionics/code/touch/electrocute.dm b/tff_modular/modules/psionics/code/touch/electrocute.dm new file mode 100644 index 00000000000..b358abeac7c --- /dev/null +++ b/tff_modular/modules/psionics/code/touch/electrocute.dm @@ -0,0 +1,22 @@ +/datum/action/cooldown/spell/touch/psionic/electrocute + name = "Psionic Electrocute" + desc = "Administer a painful amount of psionic shock to the nervous system of a foe in melee range, causing burn and agony damage." + button_icon_state = "tech_shockaura" + cooldown_time = 20 SECONDS + point_cost = 2 + mana_cost = 10 + psionic_level = 2 + hand_path = /obj/item/melee/touch_attack/psionic/chain_lighting + locked = FALSE + category = "Tier 2" + channel_time = 1 SECONDS + +/datum/action/cooldown/spell/touch/psionic/electrocute/cast_on_hand_hit(obj/item/melee/touch_attack/hand, atom/victim, mob/living/carbon/caster) + . = ..() + if(ishuman(victim)) + var/mob/living/carbon/human/human_living = victim + human_living.adjust_fire_loss(20) + human_living.electrocute_act(10, owner, jitter_time = 2 SECONDS, stutter_time = 2 SECONDS, stun_duration = 2 SECONDS) + return TRUE + else + return FALSE diff --git a/tff_modular/modules/psionics/code/touch/mending.dm b/tff_modular/modules/psionics/code/touch/mending.dm new file mode 100644 index 00000000000..9b14517a3dc --- /dev/null +++ b/tff_modular/modules/psionics/code/touch/mending.dm @@ -0,0 +1,62 @@ +// Восстанавливает кровь, окси урон, открытые травмы. Не лечит другие типы урона. +// Если уровень Эпсилон - удаляет лярвы ксеноморфов. +/datum/action/cooldown/spell/touch/psionic/mending + name = "Psionic Mending" + desc = "Mend a creature's wounds. This handles internal wounds as well." + button_icon_state = "tech_biomedaura" + cooldown_time = 50 SECONDS + mana_cost = 30 + target_msg = "You body numbs a little." + hand_path = /obj/item/melee/touch_attack/psionic/mending + draw_message = span_notice("You ready your hand to mend a patient.") + drop_message = span_notice("You lower your hand.") + can_cast_on_self = TRUE + locked = FALSE + channel_time = 2 SECONDS + +/datum/action/cooldown/spell/touch/psionic/mending/cast_on_hand_hit(obj/item/melee/touch_attack/hand, atom/victim, mob/living/carbon/mendicant) + if(ishuman(victim)) + var/mob/living/carbon/human/human_victim = victim + if(issynthetic(human_victim) && cast_power < 2) + to_chat(owner, span_notice("I dont know how to work with synths.")) + return FALSE + if(human_victim.can_block_magic(antimagic_flags)) + to_chat(human_victim, span_notice("Psionic nearby tries to mend you.")) + else + to_chat(human_victim, span_warning(target_msg)) + if(!do_after(mendicant, 5 SECONDS / cast_power, human_victim, IGNORE_SLOWDOWNS, TRUE)) + return FALSE + else + try_heal_all(human_victim) + drain_mana() + return TRUE + else + return FALSE + +/datum/action/cooldown/spell/touch/psionic/mending/proc/try_heal_all(mob/living/carbon/human/patient) + if(patient.all_wounds && cast_power >= 2) + var/datum/wound/wound2fix = patient.all_wounds[1] + wound2fix.remove_wound() + playsound(patient, 'sound/effects/wounds/crack2.ogg', 40, TRUE) + + for(var/obj/item/organ/O in patient.organs) + O.apply_organ_damage(-15 * cast_power) + + if(patient.get_oxy_loss() >= OXYLOSS_PASSOUT_THRESHOLD-10) + patient.adjust_oxy_loss(-cast_power * 20, forced = TRUE) + + if(patient.get_organ_slot("parasite_egg") && cast_power >= 2) // Удаляем ксеноморфов + var/obj/item/organ/body_egg/parasite = patient.get_organ_slot("parasite_egg") + parasite.owner.vomit(VOMIT_CATEGORY_BLOOD | MOB_VOMIT_KNOCKDOWN | MOB_VOMIT_HARM) + parasite.owner.visible_message( + span_warning("[patient] twitches, gags and vomits a living creqture with blood! Gross!"), + span_bolddanger("Suddenly you feel sharp pain in your chest, then something starts moving up your throat. \ + Before you can react somethign slips past your lips with a mix of vomit and blood!"), + ) + var/atom/drop_loc = parasite.drop_location() + parasite.Remove(parasite.owner) + if(drop_loc) + parasite.forceMove(drop_loc) + + var/damage_to_heal = 30 * cast_power + patient.heal_overall_damage(damage_to_heal, damage_to_heal, damage_to_heal) diff --git a/tff_modular/modules/psionics/code/touch/touch.dm b/tff_modular/modules/psionics/code/touch/mind_read.dm similarity index 55% rename from tff_modular/modules/psionics/code/touch/touch.dm rename to tff_modular/modules/psionics/code/touch/mind_read.dm index 5b4d47c362c..cf01f0089d5 100644 --- a/tff_modular/modules/psionics/code/touch/touch.dm +++ b/tff_modular/modules/psionics/code/touch/mind_read.dm @@ -1,53 +1,6 @@ #define IS_HYPNOTIZED(mob) (mob?.mind?.has_antag_datum(/datum/antagonist/hypnotized)) #define IS_OBSESSED(mob) (mob?.mind?.has_antag_datum(/datum/antagonist/obsessed)) -// Тут все заклинания, которые являются прикосновением -// Спелл для чтения разума другого игрока на наличие псионических способностей - -/datum/action/cooldown/spell/touch/psionic/assay - name = "Psionic Assay" - desc = "Check if the target is a psionic." - button_icon_state = "tech_audibledeception" - cooldown_time = 60 SECONDS - mana_cost = 10 - target_msg = "Your get a headache, but it quickly fades." - hand_path = /obj/item/melee/touch_attack/psionic/assay - draw_message = span_notice("You ready your hand to cleanse a patient.") - drop_message = span_notice("You lower your hand.") - can_cast_on_self = TRUE - category = "Tier 1" - locked = FALSE - -/datum/action/cooldown/spell/touch/psionic/assay/cast_on_hand_hit(obj/item/melee/touch_attack/hand, atom/victim, mob/living/carbon/mendicant) - if(ishuman(victim)) - var/mob/living/carbon/human/human_victim = victim - if(human_victim.can_block_magic(antimagic_flags)) - to_chat(human_victim, span_notice("Psionic nearby tries to check you for psionic levels.")) - else - to_chat(human_victim, span_warning(target_msg)) - owner.visible_message(span_warning("[owner] presses his thumb onto [victim]s forehead."), - span_notice("You press your thumb onto [victim]s forehead and begin reading them.")) - to_chat(victim, span_danger("[owner] presses a thumb onto your forehead and holds it there. It burns sligthly!")) - if(do_after(mendicant, 6 SECONDS, human_victim, IGNORE_SLOWDOWNS, TRUE)) - read_psionic_level(human_victim) - drain_mana() - return TRUE - else - return FALSE - -/datum/action/cooldown/spell/touch/psionic/assay/proc/read_psionic_level(mob/living/carbon/human/patient) - if(issynthetic(patient) && cast_power < 2) - to_chat(owner, span_notice("I can see... just numbers. No idea how to work with synths.")) - return FALSE - - if(patient.get_psionic()) - var/datum/psionic/target_psi = patient.get_psionic() - owner.visible_message(span_notice("[owner] backs off from [patient]."), - span_cyan("Target is a psionic. [patient.p_Their()] class is [target_psi.psionic_level_string]")) - else - owner.visible_message(span_notice("[owner] backs off from [patient]."), - span_cyan("Target is not a psionic.")) - // Читаем разум. Выдаёт: последние сейлоги, интент, настоящее имя, воспоминания, намёк на работу, намёк на то, что в антаг_датум что то есть. /datum/action/cooldown/spell/touch/psionic/mind_read name = "Psionic Read Mind" @@ -171,91 +124,5 @@ else return "I cant read [patient.p_their()] memories. Maybe there are none?" + "
" -// Восстанавливает кровь, окси урон, открытые травмы. Не лечит другие типы урона. Если вторичка - психокинетика, то вынимает импланты. -// Если уровень Эпсилон - удаляет лярвы ксеноморфов. -/datum/action/cooldown/spell/touch/psionic/mending - name = "Psionic Mending" - desc = "Mend a creature's wounds. This handles internal wounds as well." - button_icon_state = "tech_biomedaura" - cooldown_time = 50 SECONDS - mana_cost = 30 - target_msg = "You body numbs a little." - hand_path = /obj/item/melee/touch_attack/psionic/mending - draw_message = span_notice("You ready your hand to mend a patient.") - drop_message = span_notice("You lower your hand.") - can_cast_on_self = TRUE - locked = FALSE - channel_time = 2 SECONDS - -/datum/action/cooldown/spell/touch/psionic/mending/cast_on_hand_hit(obj/item/melee/touch_attack/hand, atom/victim, mob/living/carbon/mendicant) - if(ishuman(victim)) - var/mob/living/carbon/human/human_victim = victim - if(issynthetic(human_victim) && cast_power < 2) - to_chat(owner, span_notice("I dont know how to work with synths.")) - return FALSE - if(human_victim.can_block_magic(antimagic_flags)) - to_chat(human_victim, span_notice("Psionic nearby tries to mend you.")) - else - to_chat(human_victim, span_warning(target_msg)) - if(!do_after(mendicant, 5 SECONDS / cast_power, human_victim, IGNORE_SLOWDOWNS, TRUE)) - return FALSE - else - try_heal_all(human_victim) - drain_mana() - return TRUE - else - return FALSE - -/datum/action/cooldown/spell/touch/psionic/mending/proc/try_heal_all(mob/living/carbon/human/patient) - if(patient.all_wounds && cast_power >= 2) - var/datum/wound/wound2fix = patient.all_wounds[1] - wound2fix.remove_wound() - playsound(patient, 'sound/effects/wounds/crack2.ogg', 40, TRUE) - - for(var/obj/item/organ/O in patient.organs) - O.apply_organ_damage(-15 * cast_power) - - if(patient.get_oxy_loss() >= OXYLOSS_PASSOUT_THRESHOLD-10) - patient.adjust_oxy_loss(-cast_power * 20, forced = TRUE) - - if(patient.get_organ_slot("parasite_egg") && cast_power >= 2) // Удаляем ксеноморфов - var/obj/item/organ/body_egg/parasite = patient.get_organ_slot("parasite_egg") - parasite.owner.vomit(VOMIT_CATEGORY_BLOOD | MOB_VOMIT_KNOCKDOWN | MOB_VOMIT_HARM) - parasite.owner.visible_message( - span_warning("[patient] twitches, gags and vomits a living creqture with blood! Gross!"), - span_bolddanger("Suddenly you feel sharp pain in your chest, then something starts moving up your throat. \ - Before you can react somethign slips past your lips with a mix of vomit and blood!"), - ) - var/atom/drop_loc = parasite.drop_location() - parasite.Remove(parasite.owner) - if(drop_loc) - parasite.forceMove(drop_loc) - - var/damage_to_heal = 30 * cast_power - patient.heal_overall_damage(damage_to_heal, damage_to_heal, damage_to_heal) - -/datum/action/cooldown/spell/touch/psionic/electrocute - name = "Psionic Electrocute" - desc = "Administer a painful amount of psionic shock to the nervous system of a foe in melee range, causing burn and agony damage." - button_icon_state = "tech_shockaura" - cooldown_time = 20 SECONDS - point_cost = 2 - mana_cost = 10 - psionic_level = 2 - hand_path = /obj/item/melee/touch_attack/psionic/chain_lighting - locked = FALSE - category = "Tier 2" - channel_time = 1 SECONDS - -/datum/action/cooldown/spell/touch/psionic/electrocute/cast_on_hand_hit(obj/item/melee/touch_attack/hand, atom/victim, mob/living/carbon/caster) - . = ..() - if(ishuman(victim)) - var/mob/living/carbon/human/human_living = victim - human_living.adjust_fire_loss(20) - human_living.electrocute_act(10, owner, jitter_time = 2 SECONDS, stutter_time = 2 SECONDS, stun_duration = 2 SECONDS) - return TRUE - else - return FALSE - #undef IS_HYPNOTIZED #undef IS_OBSESSED diff --git a/tgstation.dme b/tgstation.dme index 8e8a3a6356d..9cadcce1753 100644 --- a/tgstation.dme +++ b/tgstation.dme @@ -9898,16 +9898,49 @@ #include "tff_modular\modules\poster_contest\winners_items\code.dm" #include "tff_modular\modules\psionics\code\_psionic_abilities.dm" #include "tff_modular\modules\psionics\code\_psionic_datum.dm" +#include "tff_modular\modules\psionics\code\admin.dm" #include "tff_modular\modules\psionics\code\cyberimp.dm" #include "tff_modular\modules\psionics\code\documents.dm" #include "tff_modular\modules\psionics\code\hud.dm" #include "tff_modular\modules\psionics\code\loner.dm" -#include "tff_modular\modules\psionics\code\conjure_item\conjure_item.dm" -#include "tff_modular\modules\psionics\code\pointed\pointed.dm" -#include "tff_modular\modules\psionics\code\projectiles\projectile.dm" +#include "tff_modular\modules\psionics\code\quirk.dm" +#include "tff_modular\modules\psionics\code\conjure_item\blade.dm" +#include "tff_modular\modules\psionics\code\conjure_item\omnitool.dm" +#include "tff_modular\modules\psionics\code\pointed\armour.dm" +#include "tff_modular\modules\psionics\code\pointed\awaken.dm" +#include "tff_modular\modules\psionics\code\pointed\bubble.dm" +#include "tff_modular\modules\psionics\code\pointed\drain.dm" +#include "tff_modular\modules\psionics\code\pointed\emotional_suggestion.dm" +#include "tff_modular\modules\psionics\code\pointed\expansion.dm" +#include "tff_modular\modules\psionics\code\pointed\jump.dm" +#include "tff_modular\modules\psionics\code\pointed\mind_muddle.dm" +#include "tff_modular\modules\psionics\code\pointed\pull.dm" +#include "tff_modular\modules\psionics\code\pointed\rejuvenate.dm" +#include "tff_modular\modules\psionics\code\pointed\skinsight.dm" +#include "tff_modular\modules\psionics\code\pointed\spasm.dm" +#include "tff_modular\modules\psionics\code\pointed\stasis.dm" +#include "tff_modular\modules\psionics\code\pointed\throw.dm" +#include "tff_modular\modules\psionics\code\pointed\warp.dm" +#include "tff_modular\modules\psionics\code\pointed\zona_bovinae.dm" +#include "tff_modular\modules\psionics\code\projectiles\air_bullet.dm" +#include "tff_modular\modules\psionics\code\projectiles\lighting.dm" #include "tff_modular\modules\psionics\code\psi_shop\datum.dm" -#include "tff_modular\modules\psionics\code\spell\spell.dm" -#include "tff_modular\modules\psionics\code\touch\touch.dm" +#include "tff_modular\modules\psionics\code\spell\charge.dm" +#include "tff_modular\modules\psionics\code\spell\focus.dm" +#include "tff_modular\modules\psionics\code\spell\ion_blast.dm" +#include "tff_modular\modules\psionics\code\spell\nlom_eyes.dm" +#include "tff_modular\modules\psionics\code\spell\search.dm" +#include "tff_modular\modules\psionics\code\spell\shockwave.dm" +#include "tff_modular\modules\psionics\code\spell\signal.dm" +#include "tff_modular\modules\psionics\code\spell\stamina.dm" +#include "tff_modular\modules\psionics\code\spell\sunder.dm" +#include "tff_modular\modules\psionics\code\spell\suppression.dm" +#include "tff_modular\modules\psionics\code\spell\time_stop.dm" +#include "tff_modular\modules\psionics\code\spell\transparency.dm" +#include "tff_modular\modules\psionics\code\touch\assay.dm" +#include "tff_modular\modules\psionics\code\touch\electrocute.dm" +#include "tff_modular\modules\psionics\code\touch\mending.dm" +#include "tff_modular\modules\psionics\code\touch\mind_read.dm" #include "tff_modular\modules\psionics\code\touch\touch_hand.dm" #include "tff_modular\modules\quirks\code\_quirk.dm" #include "tff_modular\modules\quirks\code\entombed.dm" diff --git a/tgui/packages/tgui/interfaces/AntagInfoLoner.tsx b/tgui/packages/tgui/interfaces/AntagInfoLoner.tsx new file mode 100644 index 00000000000..2120eda8019 --- /dev/null +++ b/tgui/packages/tgui/interfaces/AntagInfoLoner.tsx @@ -0,0 +1,77 @@ +// THIS IS A FLUFFY FRONTIER FILE! +import { BlockQuote, LabeledList, Section, Stack } from 'tgui-core/components'; + +import { Window } from '../layouts'; + +const tipstyle = { + color: 'lightblue', +}; + +const noticestyle = { + color: 'goodstyle', +}; + +export const AntagInfoLoner = (props) => { + return ( + + + + +
+ + You are a Loner. + +
+ You Are a Psi Agent, Loner. You possess high-power psionic + abilities that can strongly influence the space around you. + You were trained by the syndicate as part of an experiment + and must show the best results in completing the tasks + assigned to you. Gloty to the syndicate! +
+
+ + + Tip #1:  + You have quite a few defensive and healing spells, be careful + and fight cleverly! +
+ Tip #2:  + Try to be secretive, you have many abilities that allow you to + easily and quietly enter any room! +
+ Tip #3:  + Your abilities allow you to be a good support for syndicate + agent, cooperation is also the key to success! +
+
+
+
+ +
+ + + Your abilities are wasting psionic energy, if your psionic + energy drops to 0, you will face unpleasant consequences! + + + All psionics are able to see each other's signals, but you can + suppress your signal and hide from other psionics. + + + There's a box of psionic implants in the cargo. This box + contains an implant that accelerates the production of psi + energy, as well as an implant that completely suppresses your + psionics. + + + You have a psionic store. Buy psionic abilities wisely! You + cannot change the abilities you have acquired. + + +
+
+
+
+
+ ); +}; From f564ecf8a079b94be76af8268ef3c7ca4bd2a21b Mon Sep 17 00:00:00 2001 From: SaukyKouko Date: Sat, 23 May 2026 04:10:01 +0300 Subject: [PATCH 16/22] =?UTF-8?q?=D0=B8=D1=81=D0=BF=D1=80=D0=B0=D0=B2?= =?UTF-8?q?=D0=BB=D0=B5=D0=BD=D0=B8=D0=B5=20=D0=BE=D1=88=D0=B8=D0=B1=D0=BA?= =?UTF-8?q?=D0=B8?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- tff_modular/modules/psionics/code/_psionic_datum.dm | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tff_modular/modules/psionics/code/_psionic_datum.dm b/tff_modular/modules/psionics/code/_psionic_datum.dm index d6a4c7a1096..ddefb2cf99c 100644 --- a/tff_modular/modules/psionics/code/_psionic_datum.dm +++ b/tff_modular/modules/psionics/code/_psionic_datum.dm @@ -7,7 +7,7 @@ /mob/living/proc/add_psionic(psi_type, remove_old) if(!psi_type) psi_type = new /datum/psionic/sensitive() - if(!istype(psi_type, /datum/psionic)) + if(istype(psi_type, /datum/psionic)) return FALSE if(get_psionic()) remove_psionic() From d9ae987c67e52a93ff23103fabe8b920dc958819 Mon Sep 17 00:00:00 2001 From: SaukyKouko Date: Sat, 23 May 2026 13:57:40 +0300 Subject: [PATCH 17/22] =?UTF-8?q?=D0=BE=D0=B1=D0=BD=D0=BE=D0=B2=D0=BB?= =?UTF-8?q?=D0=B5=D0=BD=D0=B8=D1=8F?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../modules/psionics/code/_psionic_datum.dm | 4 +- tff_modular/modules/psionics/code/hud.dm | 4 +- tff_modular/modules/psionics/code/opfor.dm | 31 ++++++++++++++++ .../modules/psionics/code/pointed/pull.dm | 5 +-- .../modules/psionics/code/pointed/warp.dm | 2 +- .../modules/psionics/code/spell/focus.dm | 18 +++++---- .../modules/psionics/code/spell/shockwave.dm | 1 + .../modules/psionics/code/spell/signal.dm | 37 ------------------- .../modules/psionics/code/spell/stamina.dm | 19 +++++----- .../modules/psionics/code/touch/assay.dm | 1 + tgstation.dme | 2 +- 11 files changed, 60 insertions(+), 64 deletions(-) create mode 100644 tff_modular/modules/psionics/code/opfor.dm delete mode 100644 tff_modular/modules/psionics/code/spell/signal.dm diff --git a/tff_modular/modules/psionics/code/_psionic_datum.dm b/tff_modular/modules/psionics/code/_psionic_datum.dm index ddefb2cf99c..61d0a2f0791 100644 --- a/tff_modular/modules/psionics/code/_psionic_datum.dm +++ b/tff_modular/modules/psionics/code/_psionic_datum.dm @@ -149,7 +149,7 @@ mana_level = clamp(mana_level + amount, 0, max_mana) /datum/psionic/sensitive - max_mana = 50 + max_mana = 35 psionic_level = 1 psionic_level_string = SENSITIVE_PSIONIC license = TRUE @@ -163,7 +163,7 @@ psionic_level = 2 psionic_level_string = HARMONIOUS_PSIONIC license = FALSE - psi_point = 14 + psi_point = 10 /datum/psionic/proc/is_suppressed() if(HAS_TRAIT(psi_owner, TRAIT_PSIONIC_EXHAUSTION)) diff --git a/tff_modular/modules/psionics/code/hud.dm b/tff_modular/modules/psionics/code/hud.dm index 6006f23a493..59c602d1abb 100644 --- a/tff_modular/modules/psionics/code/hud.dm +++ b/tff_modular/modules/psionics/code/hud.dm @@ -1,5 +1,5 @@ -#define UI_PSI_DISPLAY "EAST:2,CENTER+3:-8" -#define UI_PSI_SIGNAL "EAST:2, CENTER+2:-8" +#define UI_PSI_DISPLAY "WEST:2,CENTER+3:-8" +#define UI_PSI_SIGNAL "WEST:2, CENTER+2:-8" #define FORMAT_PSI_HUD_TEXT(valuecolor, value) MAPTEXT("
[round(value,1)]
") /atom/movable/screen/psionic diff --git a/tff_modular/modules/psionics/code/opfor.dm b/tff_modular/modules/psionics/code/opfor.dm new file mode 100644 index 00000000000..5658536ffa9 --- /dev/null +++ b/tff_modular/modules/psionics/code/opfor.dm @@ -0,0 +1,31 @@ +/datum/opposing_force_equipment/uplink/psionic + item_type = /obj/effect/gibspawner/generic + name = "Psionic Tier 1" + description = "A set of useful psionic abilities." + admin_note = "Gives the owner the psionics of the 1 tier. The 1 tier psionic is available to all veterans." + var/datum/psionic/psionic_type = /datum/psionic/sensitive + +/datum/opposing_force_equipment/uplink/psionic/on_issue(mob/living/target) + . = ..() + target.add_psionic(psionic_type) + +/datum/opposing_force_equipment/uplink/psionic/tier_2 + name = "Psionic Tier 2" + description = "A set of useful and dangerous psionic abilities." + admin_note = "Gives the owner the psionics of the 2 tier. The 2 tier psionic is dangerous and available only to antagonists." + +/datum/uplink_item/bundles_tc/psionic + name = "Psionic Awaken" + desc = "Upon purchase, it grants you 2 tier psionic." + item = ABSTRACT_UPLINK_ITEM + surplus = 0 + progression_minimum = 15 MINUTES + limited_stock = 1 + cost = 35 + restricted = FALSE + purchasable_from = UPLINK_TRAITORS + +/datum/uplink_item/bundles_tc/psionic/spawn_item(spawn_path, mob/user, datum/uplink_handler/uplink_handler, atom/movable/source) + var/mob/living/buyer = user + buyer.add_psionic(/datum/psionic/harmonious) + return source diff --git a/tff_modular/modules/psionics/code/pointed/pull.dm b/tff_modular/modules/psionics/code/pointed/pull.dm index 01d58f149ad..2237ed59ff5 100644 --- a/tff_modular/modules/psionics/code/pointed/pull.dm +++ b/tff_modular/modules/psionics/code/pointed/pull.dm @@ -2,10 +2,7 @@ name = "Psionic Pull" desc = "Pulls the target straight towards the user. Even if the item is big, it's cant harm you on impact. Note that you can catch items you pull to yourself if you toggle throw mode before pulling an item." button_icon_state = "tech_passwall" - category = "Tier 2" - cooldown_time = 20 SECONDS - psionic_level = 2 - point_cost = 1 + cooldown_time = 15 SECONDS mana_cost = 30 locked = FALSE cast_range = 5 diff --git a/tff_modular/modules/psionics/code/pointed/warp.dm b/tff_modular/modules/psionics/code/pointed/warp.dm index 1369b7cf68e..a963f3eed20 100644 --- a/tff_modular/modules/psionics/code/pointed/warp.dm +++ b/tff_modular/modules/psionics/code/pointed/warp.dm @@ -47,7 +47,7 @@ /turf/closed/wall/attack_hand(mob/user, list/modifiers) . = ..() if(warped) - to_chat(user, span_horizonblue("The wall begins to return to its condition... For some reason.")) + to_chat(user, span_horizonblue("The wall begins to return to its condition...")) unwarp() /turf/closed/wall/proc/on_examine(datum/source, mob/user, text) diff --git a/tff_modular/modules/psionics/code/spell/focus.dm b/tff_modular/modules/psionics/code/spell/focus.dm index be8004a10fc..92eb9c62f83 100644 --- a/tff_modular/modules/psionics/code/spell/focus.dm +++ b/tff_modular/modules/psionics/code/spell/focus.dm @@ -13,7 +13,7 @@ var/mob/living/carbon/human/human_living = cast_on if(do_after(human_living, 1 SECONDS, timed_action_flags = IGNORE_SLOWDOWNS | IGNORE_USER_LOC_CHANGE | IGNORE_TARGET_LOC_CHANGE | IGNORE_HELD_ITEM)) to_chat(human_living, span_warning("A calm rush envelops your mind..")) - human_living.reagents.add_reagent(/datum/reagent/medicine/psimulant, 10) + human_living.reagents.add_reagent(/datum/reagent/medicine/psimulant, 5) drain_mana() playsound(human_living, 'tff_modular/modules/psionics/sounds/power_used.ogg', 50, TRUE) else @@ -31,16 +31,18 @@ var/mob/living/carbon/human/psimulator = affected_mob psimulator.add_movespeed_modifier(/datum/movespeed_modifier/psimulant) psimulator.add_actionspeed_modifier(/datum/actionspeed_modifier/psimulant) + psimulator.SetParalyzed(0) + psimulator.SetStun(0) + psimulator.SetAllImmobility(0) + psimulator.remove_status_effect(/datum/status_effect/speech/stutter) + psimulator.set_resting(FALSE) + psimulator.SetSleeping(0) /datum/reagent/medicine/psimulant/metabolize_reagent(mob/living/carbon/affected_mob, seconds_per_tick, metabolized_volume) . = ..() - affected_mob.SetParalyzed(0) - affected_mob.SetStun(0) - affected_mob.SetAllImmobility(0) - affected_mob.adjust_stamina_loss(-5) - affected_mob.remove_status_effect(/datum/status_effect/speech/stutter) - affected_mob.set_resting(FALSE) - affected_mob.SetSleeping(0) + affected_mob.adjust_stamina_loss(-2) + affected_mob.adjust_brute_loss(-0.3) + affected_mob.adjust_fire_loss(-0.3) /datum/reagent/medicine/psimulant/on_mob_delete(mob/living/affected_mob) . = ..() diff --git a/tff_modular/modules/psionics/code/spell/shockwave.dm b/tff_modular/modules/psionics/code/spell/shockwave.dm index f800747f9ee..d2d7166a0d0 100644 --- a/tff_modular/modules/psionics/code/spell/shockwave.dm +++ b/tff_modular/modules/psionics/code/spell/shockwave.dm @@ -31,3 +31,4 @@ shake_camera(victims, 2 SECONDS, 2) victims.Paralyze(2 SECONDS) cast_on.visible_message(span_horizonblue("[cast_on]'s foot starts to cover in blue energy, and then he stomps on the floor"), span_horizonblue("You channel psionic energy into your foot, and then stomp on the floor.")) + playsound(cast_on, 'sound/effects/meteorimpact.ogg', 100, TRUE) diff --git a/tff_modular/modules/psionics/code/spell/signal.dm b/tff_modular/modules/psionics/code/spell/signal.dm deleted file mode 100644 index e3ff4c0dc47..00000000000 --- a/tff_modular/modules/psionics/code/spell/signal.dm +++ /dev/null @@ -1,37 +0,0 @@ -/datum/action/cooldown/spell/psionic/signal - name = "Psionic Signal" - desc = "Sends a signal to all psionics in the sector." - mana_cost = 5 - -/datum/action/cooldown/spell/psionic/signal/can_cast_spell(feedback) - . = ..() - if(HAS_TRAIT(owner, TRAIT_INCAPACITATED)) - return SPELL_CANCEL_CAST - -/datum/action/cooldown/spell/psionic/signal/cast(atom/cast_on) - . = ..() - var/mob/living/caster = cast_on - var/list/mob/living/psionic_list = list() - if(isnull(psionic_list)) - for(var/mob/living/psionics as anything in world) - if(!psionics.get_psionic()) - continue - if(psionics.stat == DEAD) - continue - if(psionics.psi_sensivity.is_suppressed()) - continue - if(!is_valid_z_level(caster.z, psionics.z)) - continue - psionic_list += psionics - - if(isnull(psionic_list)) - to_chat(caster, span_horizonblue("There is no one you can send signal.")) - return FALSE - - var/text = tgui_input_text(caster, "Psionic Signal", "What kind of signal do you want to send to other psionics?", "Meow", 400) - if(!text || caster.stat == DEAD || QDELETED(caster)) - return FALSE - - text = lowertext(text) - - to_chat(psionic_list, span_horizonblue("[text]")) diff --git a/tff_modular/modules/psionics/code/spell/stamina.dm b/tff_modular/modules/psionics/code/spell/stamina.dm index 7841c29988b..46d9963225d 100644 --- a/tff_modular/modules/psionics/code/spell/stamina.dm +++ b/tff_modular/modules/psionics/code/spell/stamina.dm @@ -1,27 +1,28 @@ /datum/action/cooldown/spell/psionic/stamina - name = "Psionic Stamina Weave" - desc = "Activate this spell to regenerate your psi-mana and nutrients a little bit." + name = "Psionic Nutrients Weave" + desc = "Activate this spell to regenerate your nutrients a little bit." button_icon_state = "tech_mend_template" point_cost = 1 cooldown_time = 20 SECONDS - mana_cost = 0 + mana_cost = 5 locked = FALSE var/charging = FALSE /datum/action/cooldown/spell/psionic/stamina/cast(atom/cast_on) . = ..() - regenerate_stamina(cast_on) + regenerate_nutrients(cast_on) return TRUE -/datum/action/cooldown/spell/psionic/stamina/is_action_active(atom/movable/screen/movable/action_button/current_button) - return charging +/datum/action/cooldown/spell/psionic/stamina/before_cast(atom/cast_on) + if(charging) + return SPELL_CANCEL_CAST -/datum/action/cooldown/spell/psionic/stamina/proc/regenerate_stamina(mob/living/carbon/human/human_living) +/datum/action/cooldown/spell/psionic/stamina/proc/regenerate_nutrients(mob/living/carbon/human/human_living) if(!do_after(human_living, 1 SECONDS)) charging = FALSE return FALSE charging = TRUE - psionic_datum.adjust_psi_energy(5) human_living.adjust_nutrition(5) playsound(human_living, 'tff_modular/modules/psionics/sounds/power_used.ogg', 50, TRUE) - regenerate_stamina(human_living) + regenerate_nutrients(human_living) + drain_mana() diff --git a/tff_modular/modules/psionics/code/touch/assay.dm b/tff_modular/modules/psionics/code/touch/assay.dm index 0561d191b24..f33f656f4d4 100644 --- a/tff_modular/modules/psionics/code/touch/assay.dm +++ b/tff_modular/modules/psionics/code/touch/assay.dm @@ -13,6 +13,7 @@ can_cast_on_self = TRUE category = "Tier 1" locked = FALSE + point_cost = 0 /datum/action/cooldown/spell/touch/psionic/assay/cast_on_hand_hit(obj/item/melee/touch_attack/hand, atom/victim, mob/living/carbon/mendicant) if(ishuman(victim)) diff --git a/tgstation.dme b/tgstation.dme index 9cadcce1753..989d12c62a0 100644 --- a/tgstation.dme +++ b/tgstation.dme @@ -9903,6 +9903,7 @@ #include "tff_modular\modules\psionics\code\documents.dm" #include "tff_modular\modules\psionics\code\hud.dm" #include "tff_modular\modules\psionics\code\loner.dm" +#include "tff_modular\modules\psionics\code\opfor.dm" #include "tff_modular\modules\psionics\code\quirk.dm" #include "tff_modular\modules\psionics\code\conjure_item\blade.dm" #include "tff_modular\modules\psionics\code\conjure_item\omnitool.dm" @@ -9931,7 +9932,6 @@ #include "tff_modular\modules\psionics\code\spell\nlom_eyes.dm" #include "tff_modular\modules\psionics\code\spell\search.dm" #include "tff_modular\modules\psionics\code\spell\shockwave.dm" -#include "tff_modular\modules\psionics\code\spell\signal.dm" #include "tff_modular\modules\psionics\code\spell\stamina.dm" #include "tff_modular\modules\psionics\code\spell\sunder.dm" #include "tff_modular\modules\psionics\code\spell\suppression.dm" From 59aa8aba452b84e9390a14b0f8024e8f489c27c2 Mon Sep 17 00:00:00 2001 From: SaukyKouko Date: Sat, 23 May 2026 16:07:42 +0300 Subject: [PATCH 18/22] =?UTF-8?q?=D0=B8=D1=81=D0=BF=D1=80=D0=B0=D0=B2?= =?UTF-8?q?=D0=BB=D0=B5=D0=BD=D0=B8=D1=8F=20=D0=B1=D0=B0=D0=BB=D0=B0=D0=BD?= =?UTF-8?q?=D1=81=D0=B0=20=D0=B8=20=D0=B1=D0=B0=D0=B3=D0=B0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- tff_modular/modules/psionics/code/conjure_item/omnitool.dm | 2 +- tff_modular/modules/psionics/code/spell/stamina.dm | 2 +- tff_modular/modules/psionics/code/touch/mending.dm | 4 ++-- 3 files changed, 4 insertions(+), 4 deletions(-) diff --git a/tff_modular/modules/psionics/code/conjure_item/omnitool.dm b/tff_modular/modules/psionics/code/conjure_item/omnitool.dm index ac5b68698fe..cade5d805ad 100644 --- a/tff_modular/modules/psionics/code/conjure_item/omnitool.dm +++ b/tff_modular/modules/psionics/code/conjure_item/omnitool.dm @@ -5,7 +5,7 @@ desc = "Concentrates psionic energy to create a universal tool." button_icon = 'tff_modular/modules/psionics/icons/psi_items.dmi' button_icon_state = "omnitool" - cooldown_time = 60 SECONDS + cooldown_time = 10 SECONDS item_type = /obj/item/psionic_omnitool mana_cost = 20 category = "Tier 1" diff --git a/tff_modular/modules/psionics/code/spell/stamina.dm b/tff_modular/modules/psionics/code/spell/stamina.dm index 46d9963225d..f22576577a0 100644 --- a/tff_modular/modules/psionics/code/spell/stamina.dm +++ b/tff_modular/modules/psionics/code/spell/stamina.dm @@ -22,7 +22,7 @@ charging = FALSE return FALSE charging = TRUE + drain_mana() human_living.adjust_nutrition(5) playsound(human_living, 'tff_modular/modules/psionics/sounds/power_used.ogg', 50, TRUE) regenerate_nutrients(human_living) - drain_mana() diff --git a/tff_modular/modules/psionics/code/touch/mending.dm b/tff_modular/modules/psionics/code/touch/mending.dm index 9b14517a3dc..9ef52bb9cc1 100644 --- a/tff_modular/modules/psionics/code/touch/mending.dm +++ b/tff_modular/modules/psionics/code/touch/mending.dm @@ -4,7 +4,7 @@ name = "Psionic Mending" desc = "Mend a creature's wounds. This handles internal wounds as well." button_icon_state = "tech_biomedaura" - cooldown_time = 50 SECONDS + cooldown_time = 60 SECONDS mana_cost = 30 target_msg = "You body numbs a little." hand_path = /obj/item/melee/touch_attack/psionic/mending @@ -43,7 +43,7 @@ O.apply_organ_damage(-15 * cast_power) if(patient.get_oxy_loss() >= OXYLOSS_PASSOUT_THRESHOLD-10) - patient.adjust_oxy_loss(-cast_power * 20, forced = TRUE) + patient.adjust_oxy_loss(-20 * cast_power, forced = TRUE) if(patient.get_organ_slot("parasite_egg") && cast_power >= 2) // Удаляем ксеноморфов var/obj/item/organ/body_egg/parasite = patient.get_organ_slot("parasite_egg") From a3f0b3f129de58ba2ecc6473dd90354db7ffd8ca Mon Sep 17 00:00:00 2001 From: SaukyKouko Date: Sat, 23 May 2026 16:17:48 +0300 Subject: [PATCH 19/22] Update mending.dm --- tff_modular/modules/psionics/code/touch/mending.dm | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/tff_modular/modules/psionics/code/touch/mending.dm b/tff_modular/modules/psionics/code/touch/mending.dm index 9ef52bb9cc1..23bf16d659b 100644 --- a/tff_modular/modules/psionics/code/touch/mending.dm +++ b/tff_modular/modules/psionics/code/touch/mending.dm @@ -43,7 +43,9 @@ O.apply_organ_damage(-15 * cast_power) if(patient.get_oxy_loss() >= OXYLOSS_PASSOUT_THRESHOLD-10) - patient.adjust_oxy_loss(-20 * cast_power, forced = TRUE) + patient.adjust_oxy_loss(-30 * cast_power, forced = TRUE) + + patient.adjust_tox_loss(-20 * cast_power, forced = TRUE) if(patient.get_organ_slot("parasite_egg") && cast_power >= 2) // Удаляем ксеноморфов var/obj/item/organ/body_egg/parasite = patient.get_organ_slot("parasite_egg") @@ -58,5 +60,5 @@ if(drop_loc) parasite.forceMove(drop_loc) - var/damage_to_heal = 30 * cast_power + var/damage_to_heal = 25 * cast_power patient.heal_overall_damage(damage_to_heal, damage_to_heal, damage_to_heal) From b0e3f29fbc375e62ab4fd78b027acfe83c7a4f87 Mon Sep 17 00:00:00 2001 From: SaukyKouko Date: Sat, 23 May 2026 16:21:53 +0300 Subject: [PATCH 20/22] =?UTF-8?q?=D0=B8=D1=81=D0=BF=D1=80=D0=B0=D0=B2?= =?UTF-8?q?=D0=BB=D1=8F=D0=B5=D0=BC=20=D0=90=D0=9F=20=D0=BF=D1=83=D0=BB?= =?UTF-8?q?=D0=B8?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- tff_modular/modules/psionics/code/projectiles/air_bullet.dm | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/tff_modular/modules/psionics/code/projectiles/air_bullet.dm b/tff_modular/modules/psionics/code/projectiles/air_bullet.dm index 549e8654fc9..2c26dc223c3 100644 --- a/tff_modular/modules/psionics/code/projectiles/air_bullet.dm +++ b/tff_modular/modules/psionics/code/projectiles/air_bullet.dm @@ -4,7 +4,7 @@ desc = "Wrap air in a psionic bubble, compress it, then send it flying at your enemies." button_icon_state = "tech_repelmissiles" cooldown_time = 1 SECONDS - mana_cost = 10 + mana_cost = 15 cast_range = 9 active_msg = "You prepare to charge air bullet..." deactive_msg = "You relax." @@ -28,3 +28,4 @@ icon_state = "air_bubble" damage = 10 damage_type = BRUTE + armour_penetration = 50 From 2a4b58205a9a546b7052a27bc67af1df7d5f140b Mon Sep 17 00:00:00 2001 From: SaukyKouko Date: Sat, 23 May 2026 16:45:16 +0300 Subject: [PATCH 21/22] Update stamina.dm --- tff_modular/modules/psionics/code/spell/stamina.dm | 1 + 1 file changed, 1 insertion(+) diff --git a/tff_modular/modules/psionics/code/spell/stamina.dm b/tff_modular/modules/psionics/code/spell/stamina.dm index f22576577a0..a0d42f15391 100644 --- a/tff_modular/modules/psionics/code/spell/stamina.dm +++ b/tff_modular/modules/psionics/code/spell/stamina.dm @@ -14,6 +14,7 @@ return TRUE /datum/action/cooldown/spell/psionic/stamina/before_cast(atom/cast_on) + . = ..() if(charging) return SPELL_CANCEL_CAST From f315d27d4739f3dbed7bebc7dd8fd890014efe42 Mon Sep 17 00:00:00 2001 From: SaukyKouko Date: Sat, 23 May 2026 17:36:00 +0300 Subject: [PATCH 22/22] Update transparency.dm --- tff_modular/modules/psionics/code/spell/transparency.dm | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tff_modular/modules/psionics/code/spell/transparency.dm b/tff_modular/modules/psionics/code/spell/transparency.dm index d1d64267bde..c30c62c181a 100644 --- a/tff_modular/modules/psionics/code/spell/transparency.dm +++ b/tff_modular/modules/psionics/code/spell/transparency.dm @@ -23,7 +23,7 @@ id = "Transparency" status_type = STATUS_EFFECT_REFRESH duration = 10 SECONDS - + alert_type = null var/static/list/transparency_traits = list(TRAIT_GODMODE, TRAIT_HANDS_BLOCKED, TRAIT_SECLUDED_LOCATION) /datum/status_effect/transparency/on_apply()