diff --git a/code/__DEFINES/~ff_defines/psionic.dm b/code/__DEFINES/~ff_defines/psionic.dm new file mode 100644 index 00000000000..dbcbf1b977e --- /dev/null +++ b/code/__DEFINES/~ff_defines/psionic.dm @@ -0,0 +1,13 @@ +#define TRAIT_PSIONIC_USER "psionicuser" +#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 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/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 new file mode 100644 index 00000000000..708b311ad56 --- /dev/null +++ b/tff_modular/modules/psionics/code/_psionic_abilities.dm @@ -0,0 +1,259 @@ +// Тут хранятся некрасивые базовые классы и прочее. Не смотрите сюда. + +/datum/action/cooldown/spell + // Сколько маны стоит кастануть спелл + var/mana_cost = 10 + // Что написать жертве + var/target_msg + // Сила способности + var/cast_power = 0 + // Является псионическим спеллом? Нужен чтобы потом удалить их + var/psionic = FALSE + // Уровень способности, определяет может ли ее купить псионик + var/psionic_level = 1 + // Датум псионики что используется при касте + var/datum/psionic/psionic_datum + // Категория + var/category = "Tier 1" + // Цена + var/point_cost = 1 + // Текст помощи + var/helptext = "" + // Доступ + var/locked = TRUE + // Игнорируем ли мы подавление/отсутствие маны? + var/ignore_suppression = FALSE + +/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/update_button_name(atom/movable/screen/movable/action_button/button, force) + . = ..() + if(mana_cost) + button.desc += " Costs [mana_cost] Psi Energy." + +// Спеллы для призвания предмета +/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 + // Псионические способности (в основном) не блокируются, но выводят особенные сообщения тем, кто это может + antimagic_flags = MAGIC_RESISTANCE_MIND + spell_requirements = NONE + cooldown_reduction_per_rank = 0 SECONDS + psionic = TRUE + +// Проверяем достаточно ли маны +/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 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() + var/mob/living/carbon/human/caster = owner + var/datum/psionic/psi_holder = caster.get_psionic() + if(psi_holder) + psi_holder.adjust_psi_energy(-mana_cost) + return TRUE + else + return FALSE + +/datum/action/cooldown/spell/conjure_item/psionic/before_cast(atom/cast_on) + . = ..() + if(!check_for_mana()) + return SPELL_CANCEL_CAST + +/datum/action/cooldown/spell/conjure_item/psionic/cast(atom/cast_on) + drain_mana() + return ..() + +// Для спеллов которые применяются на себя тыком кнопки a.k.a. выдача генов +/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 + + school = SCHOOL_UNSET + invocation_type = INVOCATION_NONE + spell_requirements = NONE + cooldown_reduction_per_rank = 0 SECONDS + psionic = TRUE + psionic_level = 1 + +/datum/action/cooldown/spell/psionic/before_cast(atom/cast_on) + . = ..() + if(!check_for_mana()) + return SPELL_CANCEL_CAST + +// Спеллы для пострелушек +/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 + + school = SCHOOL_UNSET + invocation_type = INVOCATION_NONE + spell_requirements = NONE + cooldown_reduction_per_rank = 0 SECONDS + psionic = TRUE + cast_range = 7 + +/datum/action/cooldown/spell/pointed/projectile/psionic/before_cast(atom/cast_on) + . = ..() + if(!check_for_mana()) + return SPELL_CANCEL_CAST + +// Направленные спеллы a.k.a. псионик выбирают цель на дистанции +/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 + cast_range = 7 + +/datum/action/cooldown/spell/pointed/psionic/before_cast(atom/cast_on) + . = ..() + if(!check_for_mana()) + 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" + overlay_icon_state = "bg_tech_blue_border" + // Псионические способности (в основном) не блокируются, но выводят особенные сообщения тем, кто это может + antimagic_flags = MAGIC_RESISTANCE_MIND + school = SCHOOL_UNSET + 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, original) + . = ..() + 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/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 + 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 . + return TRUE + +/particles/droplets/psionic + icon = 'icons/effects/particles/generic.dmi' + icon_state = list("dot"=2,"drop"=1) + width = 32 + height = 36 + count = 20 + spawning = 0.2 + lifespan = 1.5 SECONDS + fade = 0.5 SECONDS + color = "#00a2ff" + 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) 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..61d0a2f0791 --- /dev/null +++ b/tff_modular/modules/psionics/code/_psionic_datum.dm @@ -0,0 +1,246 @@ +#define SENSITIVE_PSIONIC "Sensitive Psionic" +#define HARMONIOUS_PSIONIC "Harmonious Psionic" + +/mob/living + var/datum/psionic/psi_sensivity + +/mob/living/proc/add_psionic(psi_type, remove_old) + if(!psi_type) + psi_type = new /datum/psionic/sensitive() + if(istype(psi_type, /datum/psionic)) + return FALSE + if(get_psionic()) + remove_psionic() + 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_NULL(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 = FALSE + /// Два вара скопированные из 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() + // Эта переменная, если равна FALSE, не позволяет как-либо усилить псионика. + var/zona_bovinae = TRUE + +/datum/psionic/proc/apply_to(mob/living/granted_to) + if(!granted_to) + CRASH("Tried to add psionic without owner") + + psi_owner = granted_to + 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(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(granted_to.hud_used) + on_hud_created() + + add_shop() + +/datum/psionic/proc/add_shop() + psi_shop_datum = new(psi_owner, src, psionic_level) + 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_horizonblue("You have \a [license] [where]. [flavour_text]")) + +/datum/psionic/Destroy(force) + . = ..() + UnregisterSignal(psi_owner, COMSIG_MOB_HUD_CREATED) + 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) + if(!spells_to_remove.psionic) + continue + spells_to_remove.Remove(psi_owner) + + 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) + + REMOVE_TRAIT(psi_owner, TRAIT_PSIONIC_USER, PSIONIC_TRAIT) + +/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) + 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 + + if(psi_owner.has_status_effect(/datum/status_effect/drugginess)) // Наркота даёт бафф к генерации маны + additional_mana *= 1.5 + if(HAS_TRAIT(psi_owner, TRAIT_PSIONIC_IMPLANT)) // Если есть имплант для увеличения регена маны + additional_mana *= 1.5 + 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 = 35 + 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 + psionic_level_string = HARMONIOUS_PSIONIC + license = FALSE + psi_point = 10 + +/datum/psionic/proc/is_suppressed() + if(HAS_TRAIT(psi_owner, TRAIT_PSIONIC_EXHAUSTION)) + return TRUE + if(HAS_TRAIT(psi_owner, TRAIT_PSIONIC_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(spell_path.psionic_level > get_level()) + 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!")) + return FALSE + 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) + 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 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) + + return TRUE + +/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/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/omnitool.dm b/tff_modular/modules/psionics/code/conjure_item/omnitool.dm new file mode 100644 index 00000000000..cade5d805ad --- /dev/null +++ b/tff_modular/modules/psionics/code/conjure_item/omnitool.dm @@ -0,0 +1,81 @@ + +// Спавнит омни инструмент в руке псионика. Аналог абдукторского +/datum/action/cooldown/spell/conjure_item/psionic/psitool + name = "Psionic tool" + 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 = 10 SECONDS + item_type = /obj/item/psionic_omnitool + mana_cost = 20 + 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) + LAZYADD(item_refs, WEAKREF(made_item)) + 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/cyberimp.dm b/tff_modular/modules/psionics/code/cyberimp.dm new file mode 100644 index 00000000000..79f173b2c6f --- /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_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_PSIONIC_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/psionics/code/hud.dm b/tff_modular/modules/psionics/code/hud.dm new file mode 100644 index 00000000000..59c602d1abb --- /dev/null +++ b/tff_modular/modules/psionics/code/hud.dm @@ -0,0 +1,87 @@ +#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 + 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_big(span_horizonblue("\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 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 + 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) + 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/loner.dm b/tff_modular/modules/psionics/code/loner.dm new file mode 100644 index 00000000000..e3c56658aca --- /dev/null +++ b/tff_modular/modules/psionics/code/loner.dm @@ -0,0 +1,50 @@ +/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' + ui_name = "AntagInfoLoner" + 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) + +/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/assassinate/assassination = new /datum/objective/assassinate() + assassination.owner = owner + objectives += assassination + + var/datum/objective/steal/thief = new /datum/objective/steal() + thief.owner = owner + objectives += thief + + var/datum/objective/protect/protect = new /datum/objective/protect() + protect.owner = owner + objectives += protect + + 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/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/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/pull.dm b/tff_modular/modules/psionics/code/pointed/pull.dm new file mode 100644 index 00000000000..2237ed59ff5 --- /dev/null +++ b/tff_modular/modules/psionics/code/pointed/pull.dm @@ -0,0 +1,34 @@ +/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" + cooldown_time = 15 SECONDS + 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..a963f3eed20 --- /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...")) + 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..2c26dc223c3 --- /dev/null +++ b/tff_modular/modules/psionics/code/projectiles/air_bullet.dm @@ -0,0 +1,31 @@ +// Тут все заклинания, создающие снаряды. +/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 = 15 + 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 + armour_penetration = 50 diff --git a/tff_modular/modules/psionics/code/projectiles/lighting.dm b/tff_modular/modules/psionics/code/projectiles/lighting.dm new file mode 100644 index 00000000000..7520b1ca20e --- /dev/null +++ b/tff_modular/modules/psionics/code/projectiles/lighting.dm @@ -0,0 +1,59 @@ +/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." + button_icon_state = "spellcard" + category = "Tier 2" + click_cd_override = 1 + cooldown_time = 20 SECONDS + psionic_level = 2 + mana_cost = 50 + point_cost = 3 + cast_range = 40 + 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) + 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/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) + . = ..() + 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) + +/datum/action/cooldown/spell/pointed/projectile/psionic/lighting/cast(atom/cast_on) + . = ..() + drain_mana() + +/obj/projectile/beam/emitter/hitscan/lighting + name = "psionic lightning" + damage = 10 + armour_penetration = 30 + damage_type = BURN + pass_flags = PASSTABLE | PASSGRILLE | PASSGLASS + range = 40 + + 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/psi_shop/datum.dm b/tff_modular/modules/psionics/code/psi_shop/datum.dm new file mode 100644 index 00000000000..d73aebda6dd --- /dev/null +++ b/tff_modular/modules/psionics/code/psi_shop/datum.dm @@ -0,0 +1,107 @@ +/datum/psionic_shop + var/name = "Psionic 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 + +/datum/psionic_shop/proc/get_possible_spells() + 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) + if(!spell.psionic) + spell_options -= spell + if(spell.locked) + spell_options -= spell + + return spell_options + +/datum/psionic_shop/Destroy() + psi_datum = null + psi_owner = null + possible_spells = 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 + 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) + ui.open() + +/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) + 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, + "icon" = spell_path.button_icon_state + ) + + spells += list(spell_data) + + sortTim(spells, /proc/cmp_assoc_list_name) + + data["many_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") + 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 = "swarm_zeropoint" + 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 wrong type.") + qdel(src) + +/datum/action/psionic_shop/Trigger(mob/clicker, trigger_flags) + . = ..() + 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/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..92eb9c62f83 --- /dev/null +++ b/tff_modular/modules/psionics/code/spell/focus.dm @@ -0,0 +1,57 @@ +/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, 5) + 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) + 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.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) + . = ..() + 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..d2d7166a0d0 --- /dev/null +++ b/tff_modular/modules/psionics/code/spell/shockwave.dm @@ -0,0 +1,34 @@ +/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.")) + playsound(cast_on, 'sound/effects/meteorimpact.ogg', 100, 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..a0d42f15391 --- /dev/null +++ b/tff_modular/modules/psionics/code/spell/stamina.dm @@ -0,0 +1,29 @@ +/datum/action/cooldown/spell/psionic/stamina + 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 = 5 + locked = FALSE + var/charging = FALSE + +/datum/action/cooldown/spell/psionic/stamina/cast(atom/cast_on) + . = ..() + regenerate_nutrients(cast_on) + return TRUE + +/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_nutrients(mob/living/carbon/human/human_living) + if(!do_after(human_living, 1 SECONDS)) + 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) 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..c30c62c181a --- /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 + alert_type = null + 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..f33f656f4d4 --- /dev/null +++ b/tff_modular/modules/psionics/code/touch/assay.dm @@ -0,0 +1,46 @@ +// Спелл для чтения разума другого игрока на наличие псионических способностей + +/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 + 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)) + 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..23bf16d659b --- /dev/null +++ b/tff_modular/modules/psionics/code/touch/mending.dm @@ -0,0 +1,64 @@ +// Восстанавливает кровь, окси урон, открытые травмы. Не лечит другие типы урона. +// Если уровень Эпсилон - удаляет лярвы ксеноморфов. +/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 = 60 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(-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") + 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 = 25 * cast_power + patient.heal_overall_damage(damage_to_heal, damage_to_heal, damage_to_heal) diff --git a/tff_modular/modules/psionics/code/touch/mind_read.dm b/tff_modular/modules/psionics/code/touch/mind_read.dm new file mode 100644 index 00000000000..cf01f0089d5 --- /dev/null +++ b/tff_modular/modules/psionics/code/touch/mind_read.dm @@ -0,0 +1,128 @@ +#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/mind_read + name = "Psionic Read Mind" + desc = "Rudely intrude into targets thoughts." + button_icon_state = "tech_illusion" + cooldown_time = 5 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 = 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)) + 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?" + "
" + +#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..a1aeeb00b1f --- /dev/null +++ b/tff_modular/modules/psionics/code/touch/touch_hand.dm @@ -0,0 +1,23 @@ + +/obj/item/melee/touch_attack/psionic + name = "Sparks" + desc = "Concentrated psionic energy in a hand." + icon = 'tff_modular/modules/psionics/icons/touch_spell.dmi' + inhand_icon_state = "greyscale" + color = null + +/obj/item/melee/touch_attack/psionic/mending + name = "Mending" + icon_state = "mend_wounds" + +/obj/item/melee/touch_attack/psionic/assay + name = "Assay" + icon_state = "track" + +/obj/item/melee/touch_attack/psionic/chain_lighting + name = "Electrocute" + icon_state = "chain_lighting" + +/obj/item/melee/touch_attack/psionic/read_mind + name = "Electrocute" + icon_state = "apportation" 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 00000000000..928f933abb1 Binary files /dev/null and b/tff_modular/modules/psionics/icons/projectiles.dmi differ diff --git a/tff_modular/modules/psionics/icons/psi_hud.dmi b/tff_modular/modules/psionics/icons/psi_hud.dmi new file mode 100644 index 00000000000..3f5c1dca180 Binary files /dev/null and b/tff_modular/modules/psionics/icons/psi_hud.dmi differ 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 00000000000..20ecb976b7c Binary files /dev/null and b/tff_modular/modules/psionics/icons/psi_items.dmi differ diff --git a/tff_modular/modules/psionics/icons/spells.dmi b/tff_modular/modules/psionics/icons/spells.dmi new file mode 100644 index 00000000000..931b4c756c5 Binary files /dev/null and b/tff_modular/modules/psionics/icons/spells.dmi differ 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 00000000000..da2c38d59bd Binary files /dev/null and b/tff_modular/modules/psionics/icons/touch_spell.dmi differ 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 00000000000..15766334320 Binary files /dev/null and b/tff_modular/modules/psionics/sounds/power_config.ogg differ diff --git a/tff_modular/modules/psionics/sounds/power_evoke.ogg b/tff_modular/modules/psionics/sounds/power_evoke.ogg new file mode 100644 index 00000000000..37d9c5a5401 Binary files /dev/null and b/tff_modular/modules/psionics/sounds/power_evoke.ogg differ diff --git a/tff_modular/modules/psionics/sounds/power_fabrication.ogg b/tff_modular/modules/psionics/sounds/power_fabrication.ogg new file mode 100644 index 00000000000..8720d196a80 Binary files /dev/null and b/tff_modular/modules/psionics/sounds/power_fabrication.ogg differ diff --git a/tff_modular/modules/psionics/sounds/power_fail.ogg b/tff_modular/modules/psionics/sounds/power_fail.ogg new file mode 100644 index 00000000000..75364171dd3 Binary files /dev/null and b/tff_modular/modules/psionics/sounds/power_fail.ogg differ 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 00000000000..139dfba0bd4 Binary files /dev/null and b/tff_modular/modules/psionics/sounds/power_feedback.ogg differ 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 00000000000..3ba24f81b38 Binary files /dev/null and b/tff_modular/modules/psionics/sounds/power_unlock.ogg differ diff --git a/tff_modular/modules/psionics/sounds/power_used.ogg b/tff_modular/modules/psionics/sounds/power_used.ogg new file mode 100644 index 00000000000..aa0978f14bc Binary files /dev/null and b/tff_modular/modules/psionics/sounds/power_used.ogg differ diff --git a/tff_modular/modules/psyonics/code/_psyonics.dm b/tff_modular/modules/psyonics/code/_psyonics.dm deleted file mode 100644 index 75feb9a2339..00000000000 --- a/tff_modular/modules/psyonics/code/_psyonics.dm +++ /dev/null @@ -1,193 +0,0 @@ -// Тут хранятся некрасивые базовые классы и прочее. Не смотрите сюда. - -/datum/action/cooldown/spell - // Сколько маны стоит кастануть спелл - var/mana_cost = 10 - // Некоторые спеллы могут отнимать стамину - var/stamina_cost = 0 - // Что написать жертве - var/target_msg - // Сила способности - var/cast_power = 0 - // Вторичная школа. Может дать особые эффекты при комбинациях - var/secondary_school = 0 - -// Спеллы для призвания предмета -/datum/action/cooldown/spell/conjure_item/psyonic - delete_old = FALSE - delete_on_failure = TRUE - requires_hands = TRUE - // Псионические способности (в основном) не блокируются, но выводят особенные сообщения тем, кто это может - antimagic_flags = MAGIC_RESISTANCE_MIND - spell_requirements = NONE - cooldown_reduction_per_rank = 0 SECONDS - -/datum/action/cooldown/spell/conjure_item/psyonic/New(Target, power, additional_school) - . = ..() - 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) - return TRUE - else - return FALSE - -// Сосём ману у псионика -/datum/action/cooldown/spell/proc/drain_mana(forced = FALSE) - var/mob/living/carbon/human/caster = owner - var/datum/quirk/psyonic/quirk_holder = caster.get_quirk(/datum/quirk/psyonic) - 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 - return TRUE - else - return FALSE - -/datum/action/cooldown/spell/conjure_item/psyonic/can_cast_spell(feedback) - . = ..() - if(!.) - return FALSE - - if(!check_for_mana()) - return FALSE - else - return TRUE - -/datum/action/cooldown/spell/conjure_item/psyonic/cast(atom/cast_on) - drain_mana() - return ..() - -// Для спеллов которые применяются на себя тыком кнопки a.k.a. выдача генов -/datum/action/cooldown/spell/psyonic - // Псионические способности (в основном) не блокируются, но выводят особенные сообщения тем, кто это может - antimagic_flags = MAGIC_RESISTANCE_MIND - - school = SCHOOL_UNSET - invocation_type = INVOCATION_NONE - spell_requirements = NONE - cooldown_reduction_per_rank = 0 SECONDS - -/datum/action/cooldown/spell/psyonic/New(Target, power, additional_school) - . = ..() - cast_power = power - secondary_school = additional_school - -/datum/action/cooldown/spell/psyonic/can_cast_spell(feedback) - . = ..() - if(!.) - return FALSE - - if(!check_for_mana()) - return FALSE - else - return TRUE - -// Спеллы для пострелушек -/datum/action/cooldown/spell/pointed/projectile/psyonic - // Псионические способности (в основном) не блокируются, но выводят особенные сообщения тем, кто это может - antimagic_flags = MAGIC_RESISTANCE_MIND - - school = SCHOOL_UNSET - invocation_type = INVOCATION_NONE - spell_requirements = NONE - cooldown_reduction_per_rank = 0 SECONDS - -/datum/action/cooldown/spell/pointed/projectile/psyonic/New(Target, power, additional_school) - . = ..() - cast_power = power - secondary_school = additional_school - -/datum/action/cooldown/spell/pointed/projectile/psyonic/can_cast_spell(feedback) - . = ..() - if(!.) - return FALSE - - if(!check_for_mana()) - return FALSE - else - return TRUE - -// Направленные спеллы a.k.a. псионик выбирают цель на дистанции -/datum/action/cooldown/spell/pointed/psyonic - // Псионические способности (в основном) не блокируются, но выводят особенные сообщения тем, кто это может - antimagic_flags = MAGIC_RESISTANCE_MIND - school = SCHOOL_UNSET - invocation_type = INVOCATION_NONE - spell_requirements = NONE - cooldown_reduction_per_rank = 0 SECONDS - -/datum/action/cooldown/spell/pointed/psyonic/New(Target, power, additional_school) - . = ..() - cast_power = power - secondary_school = additional_school - -/datum/action/cooldown/spell/pointed/psyonic/can_cast_spell(feedback) - . = ..() - if(!.) - return FALSE - - if(!check_for_mana()) - return FALSE - else - return TRUE - -// Спеллы которыми надо каснуться чего либо -/datum/action/cooldown/spell/touch/psyonic - // Псионические способности (в основном) не блокируются, но выводят особенные сообщения тем, кто это может - antimagic_flags = MAGIC_RESISTANCE_MIND - school = SCHOOL_UNSET - invocation_type = INVOCATION_NONE - spell_requirements = NONE - -/datum/action/cooldown/spell/touch/psyonic/New(Target, power, additional_school) - . = ..() - cast_power = power - secondary_school = additional_school - -/datum/action/cooldown/spell/touch/psyonic/can_cast_spell(feedback) - . = ..() - if(!.) - return FALSE - - if(!check_for_mana()) - return FALSE - else - return TRUE - -/datum/action/cooldown/spell/touch/psyonic/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.")) - return FALSE - - return TRUE - -/particles/droplets/psyonic - icon = 'icons/effects/particles/generic.dmi' - icon_state = list("dot"=2,"drop"=1) - width = 32 - height = 36 - count = 20 - spawning = 0.2 - lifespan = 1.5 SECONDS - fade = 0.5 SECONDS - color = "#00a2ff" - 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/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/coersion.dm b/tff_modular/modules/psyonics/code/coersion.dm deleted file mode 100644 index 001ed034207..00000000000 --- a/tff_modular/modules/psyonics/code/coersion.dm +++ /dev/null @@ -1,488 +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 спеллов -/// Psyonic assay - скан, является ли человек псиоником -/// Psyonic focus - лечение мозга и псих болезней -/// Psyonic mind read - продвинутое чтение разума(Как обычная ген. мутация, но + работа + воспоминания). Выдаётся только и ТОЛЬКО психологу, если он псионик -/// Psyonic agony - работает как стан дубинка, исчезает после одного удара -/// Psyonic spasm - станит на полсекунды, заставляет выронить всё из рук. Работает дистанционно -/// Psyonic 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.")) - -// Лечим мозги и брейнтравмы. -/datum/action/cooldown/spell/pointed/psyonic/psyonic_focus - name = "Psyonic 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..." - -/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) - if(!ishuman(cast_on)) - return FALSE - if(issynthetic(cast_on) && secondary_school != "Psychokinesis") - 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) - . = ..() - if(cast_on.can_block_magic(antimagic_flags)) - to_chat(cast_on, span_notice("Your mind is being healed by a psyonic 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/psyonic/psyonic_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/psyonic/psyonic_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" - 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/psyonic_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 - -/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) - 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!")) - 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/psyonic/psyonic_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") - 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/psyonic/psyonic_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/psyonic/psyonic_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/psyonic/psyonic_agony - name = "Psyonic 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 - 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) - 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") - 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") - drain_mana() - return TRUE - else - return FALSE - -// Прок удара -/datum/action/cooldown/spell/touch/psyonic/psyonic_agony/proc/psyonic_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) - 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/psyonic/psyonic_spasm - name = "Psyonic 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_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) - if(!ishuman(cast_on)) - return FALSE - if(issynthetic(cast_on) && secondary_school != "Psychokinesis") - 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) - . = ..() - 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!")) - else - to_chat(cast_on, span_warning(target_msg)) - log_combat(owner, cast_on, "psyonically spasmed") - stun(cast_on) - drain_mana() - return TRUE - -// Сам стан -/datum/action/cooldown/spell/pointed/psyonic/psyonic_spasm/proc/stun(mob/living/carbon/human/cast_on) - cast_on.Stun(0.2 SECONDS * cast_power) - -/** - * Гипнотизирует игрока заданной фразой и даёт брейнтравму с ней - * - * Условия: - * * 30 секунд ожидания - * * в агрограбе - * * без движения жертвы или псионика - */ -/datum/action/cooldown/spell/touch/psyonic/psyonic_hypnosis - name = "Psyonic Hypnosis" - desc = "Implant a looping pattern into victims head." - button_icon = 'tff_modular/modules/psyonics/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/psyonic_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) - 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 - 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/psyonic/psyonic_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/psyonic/psyonic_blind - name = "Psyonic 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/psyonic/psyonic_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) && secondary_school != "Psychokinesis") - 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) - . = ..() - 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!")) - else - to_chat(cast_on, span_warning(target_msg)) - log_combat(owner, cast_on, "psyonically blinded") - blind(cast_on) - drain_mana() - return TRUE - -/datum/action/cooldown/spell/pointed/psyonic/psyonic_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/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/energistics.dm b/tff_modular/modules/psyonics/code/energistics.dm deleted file mode 100644 index 011616ccdfa..00000000000 --- a/tff_modular/modules/psyonics/code/energistics.dm +++ /dev/null @@ -1,202 +0,0 @@ -/// Школа энергетики. 6 спеллов -/// Spark - создаёт искры в указанном месте -/// Discharge - разряжает АПЦ/Батарейку. Даёт ману в зависимости от кол-ва энергии -/// Laser - стрелеят концентрированным пучком фотонов, пусть и не самым сильным. -/// Distrupt - создаёт ЭМИ с небольшим радиусом. -/// 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." - 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 - 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) - 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) - 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/quirk/psyonic/quirk_holder = mendicant.get_quirk(/datum/quirk/psyonic) - if(!quirk_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) - 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) - quirk_holder.mana_level = clamp(quirk_holder.mana_level + to_charge, 0, quirk_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/psyonic/psyonic_spark - name = "Psyonic 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/psyonic/psyonic_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) - . = ..() - 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/psyonic_laser - name = "Photon Laser" - desc = "Channels psyonic 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/psyonic_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)) - return FALSE - else - quirk_holder.mana_level -= mana_cost - ..() - -// Создаёт ЕМП в месте удара руки -/datum/action/cooldown/spell/touch/psyonic/psyonic_emp - name = "Psyonic 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 - 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) - if(isatom(victim)) - empulse(victim, 1, cast_power/2) - drain_mana() - return TRUE - else - return FALSE - -// Даёт мутацию Shock Touch -/datum/action/cooldown/spell/psyonic/psionic_electrocute - name = "Psyonic 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) - return !issynthetic(cast_on) - -/datum/action/cooldown/spell/psyonic/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 - -// Стреляет снарядом вотчера, замораживая жертву. Требует почти максимально возможный запас маны -/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/psyonics/code/psychokinesis.dm b/tff_modular/modules/psyonics/code/psychokinesis.dm deleted file mode 100644 index 8476c8caa53..00000000000 --- a/tff_modular/modules/psyonics/code/psychokinesis.dm +++ /dev/null @@ -1,300 +0,0 @@ -/// Школа психокинетики -/// Имеет 6 спеллов. -/// Psi lighter - создаёт миниатюрный огонёк на кончиках пальцев. Работает как зажигалка. -/// Psi blade - создаёт в руке пси-клинок. Урон увеличивается в зависимости от уровня. -/// Psi tool - создаёт в руке универсальный инструмент. -/// Tinker - чинит integrity чего бы то ни было. -/// 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 - name = "Psi lighter" - desc = "Concentrates psyonic 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 - 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 - name = "Psi tool" - desc = "Concentrates psyonic 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 - 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) - 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/psyonic/psyonic_force - name = "Psyonic Force" - desc = "Concentrates psyonic 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 - 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) - 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) - 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.")) - 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/psyonic/psyonic_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/psyonic/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) - return !issynthetic(cast_on) - -/datum/action/cooldown/spell/psyonic/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/psyonic/psyonic_tinker - name = "Psyonic 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 - 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) - 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) - 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/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." - 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/psyonic_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/psyonic_omnitool/get_all_tool_behaviours() - return list( - TOOL_CROWBAR, - TOOL_MULTITOOL, - TOOL_SCREWDRIVER, - TOOL_WIRECUTTER, - TOOL_WRENCH, - ) - -/obj/item/psyonic_omnitool/examine() - . = ..() - . += " The mode is: [tool_behaviour]" - -/obj/item/psyonic_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/psyonic_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/psyonics/code/redaction.dm b/tff_modular/modules/psyonics/code/redaction.dm deleted file mode 100644 index 1311e2a4ea1..00000000000 --- a/tff_modular/modules/psyonics/code/redaction.dm +++ /dev/null @@ -1,382 +0,0 @@ -#define HALFWAYCRITDEATH ((HEALTH_THRESHOLD_CRIT + HEALTH_THRESHOLD_DEAD) * 0.5) - -/// Школа лечения -/// Имеет 5 спеллов в данный момент -/// Roentgen - обычный мед скан, работающий на дистанции -/// Меnding - лечит кровь, открытые раны и окси урон. Также удаляет импланты/ксеноморфов из тела при определённых условиях. -/// Ethanol Synthesis - если интент на харма, то "превращает" упитанность в алкоголь на дистанции. Любой другой - наоборот. -/// 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 - 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_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/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) - if(!ishuman(cast_on)) - return FALSE - - return TRUE - -/datum/action/cooldown/spell/pointed/psyonic/psyonic_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.")) - 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/psyonic_mending - name = "psyonic sparks" - desc = "Concentrated psyonic 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/psyonic/psyonic_mending - name = "Psyonic 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_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 - 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) - if(ishuman(victim)) - var/mob/living/carbon/human/human_victim = victim - if(issynthetic(human_victim) && secondary_school != "Psychokinesis") - 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/psyonic/psyonic_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) - 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 && secondary_school == "Psychokinesis" && 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 >=4) // Удаляем ксеноморфов - 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/psyonic/psyonic_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/psyonic/psyonic_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/psyonic/psyonic_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) - drain_mana() - return TRUE - -/// С каждым тиком конвертируем или жир в алкоголь, или алкоголь в жир -/datum/status_effect/psyonic_fat_conversion - id = "psyonic_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) - src.duration = duration - src.eth2fat = eth2fat - return ..() - -/datum/status_effect/psyonic_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/psyonic/psyonic_cleansing - name = "Psyonic Cleansing" - desc = "Filters patient blood out of toxins and removes accumulated radiation." - button_icon = 'tff_modular/modules/psyonics/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 - 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) - if(ishuman(victim)) - var/mob/living/carbon/human/human_victim = victim - if(issynthetic(human_victim) && secondary_school != "Psychokinesis") - 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/psyonic/psyonic_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) - 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/psyonic/psyonic_revival - name = "Psyonic 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_state = "revive" - cooldown_time = 3 SECONDS - mana_cost = 80 - stamina_cost = 160 - - hand_path = /obj/item/melee/touch_attack/psyonic_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) - if(ishuman(victim)) - var/mob/living/carbon/human/human_victim = victim - var/synth_check = (secondary_school == "Psychokinesis" || !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) - 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 psyonic energy does not work very well with synths.")) - return FALSE - else - return FALSE - else - return FALSE - -// 25 токса + 50 брута + 1 травма + позор роду псионическому -/datum/action/cooldown/spell/touch/psyonic/psyonic_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/psyonic/psyonic_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") - 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/psyonic/psyonic_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, "psyonically revived") - -#undef HALFWAYCRITDEATH diff --git a/tff_modular/modules/psyonics/icons/actions.dmi b/tff_modular/modules/psyonics/icons/actions.dmi deleted file mode 100644 index beb7a648626..00000000000 Binary files a/tff_modular/modules/psyonics/icons/actions.dmi and /dev/null differ diff --git a/tgstation.dme b/tgstation.dme index 8ad815fa118..989d12c62a0 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,52 @@ #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\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\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" +#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\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\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" #include "tff_modular\modules\quirks\code\coldbloodedquirk\coldbloodedquirk.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. + + +
+
+
+
+
+ ); +}; 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/PsionicShop.tsx b/tgui/packages/tgui/interfaces/PsionicShop.tsx new file mode 100644 index 00000000000..92e9eed3df9 --- /dev/null +++ b/tgui/packages/tgui/interfaces/PsionicShop.tsx @@ -0,0 +1,314 @@ +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 SpellTypePath = string; + +type Spell = { + name: string; + desc: string; + helptext: string; + path: SpellTypePath; + point_required: number; + category: string; + icon: string; +}; + +type PsionicShopContext = { + many_spells: Spell[]; + psi_points_count: number; + researched_spells: SpellTypePath[]; +}; + +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 = ['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] || 'Tier 1', + ); + + 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} Psi + + + + } + > + + + + + + + +
+
+
+ ); +}; + +// ========== +// ItemList Component +// ========== +type SpellListProps = { + compactMode: BooleanLike; + items: Spell[]; + researched_spells: SpellTypePath[]; + psi_points_count: number; + handleBuy: (item: Spell) => void; +}; + +const ItemList = (props: SpellListProps) => { + 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 = spell.icon; + + return ( + +
+ + + + + + } + /> + + + + + {compactMode ? ( + + + {owned ? ( + + + {spell.name} + + ) : ( + spell.name + )} + + + + + + + + + + + + + ) : ( +
+ {spell.name} + {owned && ( + + (Owned) + + )} + + } + buttons={ + + + + + + } + > + {spell.desc} + {spell.helptext && ( + + {spell.helptext} + + )} +
+ )} +
+
+
+
+ ); + })} +
+
+ ); +}; 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}
+
+ ))} +
+ ); +};