From 27c40bdaf6974623d32535999908abaf8e822c4d Mon Sep 17 00:00:00 2001 From: Matt Atlas Date: Sun, 30 Jul 2023 23:56:12 +0200 Subject: [PATCH] Psionics Rework: Kyoushiki Murasaki (#16805) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * Your unbeatable smile wrecks the media We wanna know your secret, so mysterious * Even the things she lacks end up in her area Such a perfect liar, you are * Miss ingenious, godly idol! (You're my savior, you're my saving grace) * So, what'd you eat today? What's your favorite book? * And if you wanna go to play, where are you gonna go? I haven't eaten at all * That's a secret No matter what you ask * She's elusive, evasive * So nonchalant Yet still shining bright; * Her secret: out of sight, as if visible, has the taste of honey There is none, none, none of this * And also none, none, none of that What type of guy do you like? * Who is your partner? Come on, answer me * "I don't understand what falling in love with someone else is like" (Hey! Hey!) I cannot tell what is true from what is a lie (Hey! Hey! Hey! Hey!) * And yet again, someone falls for such words (Hey! Hey! Hey! Hey!) Gently lulled into love * Drawing everyone's eyes You are the perfect, ultimate idol * Never to appear again A reincarnation of Venus * With that smile, with that "I love you" You captivate everyone * Even if those eyes, those words, are false They make up a perfect love (Ai) * Yeah, yeah, that girl stands out, exceptional We were extras from the beginning * BS that acted as adornments for the star's leading role Everything we've accomplished is thanks to her — no! * How impudent How could envy and jealousy not exist? * examine * This isn't some made-up drama for appearances So I cannot forgive * You're not perfect, I cannot forgive you I cannot forgive myself * I cannot accept any version of you other than the one stronger than everyone else * Everybody trusts and worships The very strongest, most invincible idol * Weaknesses and whatnot are not to be found Carrying the morning star within * Showing any weakness is no good, no good (Hey! Hey!) Don't let them see anything that they don't want to know (Hey! Hey! Hey! Hey!) * Anything but being one-of-a-kind is a definite no, no (Hey! Hey! Hey! Hey!) That is the real love (Hey!) * can commune fixes * can commune rename * fix is psi blocked * fixes agian * My signature smile that fires up the media The only secret that I shall keep is this: * My career was built upon fake "I love you"s This is love, in my own way * Even the flowing sweats are beautiful Aqua These eyelids that hid the Ruby within * Sing, dance, flutter, I am Maria — Right, lying is the most exquisite form of love * I have never been loved by anyone Nor have I loved anyone else * That such lies of mine will become true someday I trust in it * Someday, surely, I will have it all in hand I am such a greedy idol * Because I wish to love everybody properly in a life-sized way I shall lie today as well (Hey! Hey! Hey! Hey!) * a * b * c * d * And yet, still, (Hey! Hey!) I have been unable to tell you, and you, alone (Hey! Hey! Hey! Hey!) * jesus christ * Update code/controllers/subsystems/processing/psi.dm Co-authored-by: Fluffy <65877598+FluffyGhoster@users.noreply.github.com> * Update code/controllers/subsystems/processing/psi.dm Co-authored-by: Fluffy <65877598+FluffyGhoster@users.noreply.github.com> * Update code/controllers/subsystems/processing/psi.dm Co-authored-by: Fluffy <65877598+FluffyGhoster@users.noreply.github.com> * Update code/game/gamemodes/technomancer/spell_objs.dm Co-authored-by: Fluffy <65877598+FluffyGhoster@users.noreply.github.com> * Update code/modules/mob/living/carbon/human/human.dm Co-authored-by: Fluffy <65877598+FluffyGhoster@users.noreply.github.com> * Update code/modules/mob/living/carbon/human/human_helpers.dm Co-authored-by: Fluffy <65877598+FluffyGhoster@users.noreply.github.com> * Update code/modules/organs/organ_external.dm Co-authored-by: Fluffy <65877598+FluffyGhoster@users.noreply.github.com> * Update code/modules/psionics/abilities/assay.dm Co-authored-by: Fluffy <65877598+FluffyGhoster@users.noreply.github.com> * Update code/modules/psionics/abilities/command.dm Co-authored-by: Fluffy <65877598+FluffyGhoster@users.noreply.github.com> * Update code/modules/psionics/abilities/emotional_suggestion.dm Co-authored-by: Fluffy <65877598+FluffyGhoster@users.noreply.github.com> * Update code/modules/psionics/abilities/grip.dm Co-authored-by: Fluffy <65877598+FluffyGhoster@users.noreply.github.com> * Update code/modules/psionics/abilities/grip.dm Co-authored-by: Fluffy <65877598+FluffyGhoster@users.noreply.github.com> * Update code/modules/psionics/abilities/mend.dm Co-authored-by: Fluffy <65877598+FluffyGhoster@users.noreply.github.com> * Update code/modules/psionics/abilities/mend.dm Co-authored-by: Fluffy <65877598+FluffyGhoster@users.noreply.github.com> * Update code/modules/psionics/abilities/mend.dm Co-authored-by: Fluffy <65877598+FluffyGhoster@users.noreply.github.com> * Update code/modules/psionics/abilities/psi_search.dm Co-authored-by: Fluffy <65877598+FluffyGhoster@users.noreply.github.com> * Update code/modules/psionics/abilities/shockwave.dm Co-authored-by: Fluffy <65877598+FluffyGhoster@users.noreply.github.com> * Update code/modules/psionics/abilities/shockwave.dm Co-authored-by: Fluffy <65877598+FluffyGhoster@users.noreply.github.com> * Update code/modules/psionics/abilities/singularity.dm Co-authored-by: Fluffy <65877598+FluffyGhoster@users.noreply.github.com> * Update code/modules/psionics/abilities/time_stop.dm Co-authored-by: Fluffy <65877598+FluffyGhoster@users.noreply.github.com> * Update code/modules/psionics/events/mini_spasm.dm Co-authored-by: Fluffy <65877598+FluffyGhoster@users.noreply.github.com> * Update code/modules/psionics/interface/ui_hub.dm Co-authored-by: Fluffy <65877598+FluffyGhoster@users.noreply.github.com> * Update code/modules/psionics/interface/ui_hub.dm Co-authored-by: Fluffy <65877598+FluffyGhoster@users.noreply.github.com> * Update code/modules/psionics/interface/ui_hub.dm Co-authored-by: Fluffy <65877598+FluffyGhoster@users.noreply.github.com> * Update code/modules/psionics/interface/ui_hub.dm Co-authored-by: Fluffy <65877598+FluffyGhoster@users.noreply.github.com> * Update code/modules/psionics/interface/ui_hub.dm Co-authored-by: Fluffy <65877598+FluffyGhoster@users.noreply.github.com> * Update code/modules/psionics/interface/ui_hub.dm Co-authored-by: Fluffy <65877598+FluffyGhoster@users.noreply.github.com> * cl update --------- Co-authored-by: Matt Atlas Co-authored-by: Fluffy <65877598+FluffyGhoster@users.noreply.github.com> --- SQL/migrate/V081__psionics.sql | 5 + aurorastation.dme | 55 ++- code/__defines/_macros.dm | 10 +- code/__defines/damage_organs.dm | 1 + code/__defines/psi.dm | 26 +- code/__defines/traits.dm | 21 ++ code/_onclick/hud/ability_screen_objects.dm | 48 ++- code/controllers/subsystems/processing/psi.dm | 116 ++++-- code/datums/mind.dm | 17 +- code/datums/outfits/outfit_antag.dm | 1 - .../highly visible and dangerous weapons.dm | 5 + code/game/antagonist/outsider/loner.dm | 9 +- .../changeling/helpers/_framework.dm | 2 - .../changeling/implements/powers/resources.dm | 8 +- code/game/gamemodes/technomancer/catalog.dm | 4 +- code/game/gamemodes/technomancer/core_obj.dm | 20 +- .../game/gamemodes/technomancer/spell_objs.dm | 117 ++++-- .../technomancer/spells/abjuration.dm | 3 +- .../technomancer/spells/apportation.dm | 1 + .../technomancer/spells/audible_deception.dm | 2 + .../technomancer/spells/aura/biomed_aura.dm | 1 + .../gamemodes/technomancer/spells/chroma.dm | 2 + .../technomancer/spells/condensation.dm | 3 +- .../gamemodes/technomancer/spells/dispel.dm | 3 +- .../technomancer/spells/energy_siphon.dm | 1 + .../technomancer/spells/flame_tongue.dm | 3 +- .../gamemodes/technomancer/spells/gambit.dm | 3 +- .../gamemodes/technomancer/spells/illusion.dm | 2 + .../technomancer/spells/insert/insert.dm | 4 +- .../technomancer/spells/instability_tap.dm | 3 +- .../technomancer/spells/mark_recall.dm | 2 + .../technomancer/spells/mend_organs.dm | 1 + .../technomancer/spells/modifier/modifier.dm | 4 +- .../technomancer/spells/oxygenate.dm | 3 +- .../gamemodes/technomancer/spells/passwall.dm | 1 + .../technomancer/spells/phase_shift.dm | 3 +- .../spells/projectile/projectile.dm | 24 +- .../technomancer/spells/raise_wall.dm | 1 + .../technomancer/spells/resurrect.dm | 1 + .../technomancer/spells/shared_burden.dm | 3 +- .../technomancer/spells/spawner/pulsar.dm | 3 +- .../technomancer/spells/spawner/spawner.dm | 3 +- .../technomancer/spells/summon/summon.dm | 4 +- .../technomancer/spells/targeting_matrix.dm | 1 + .../gamemodes/technomancer/spells/track.dm | 3 +- .../technomancer/spells/warp_strike.dm | 1 + code/game/objects/effects/effect_system.dm | 2 +- .../objects/items/weapons/cigs_lighters.dm | 2 +- .../objects/structures/full_window_frame.dm | 6 + code/modules/admin/admin.dm | 20 +- code/modules/admin/topic.dm | 4 +- code/modules/admin/verbs/buildmode.dm | 5 +- .../preference_setup/general/07_psionics.dm | 135 +++++++ .../loadout/loadout_augments.dm | 3 +- code/modules/client/preferences.dm | 9 + code/modules/mob/living/carbon/breathe.dm | 7 +- code/modules/mob/living/carbon/human/human.dm | 3 + .../mob/living/carbon/human/human_helpers.dm | 18 +- .../mob/living/carbon/human/human_powers.dm | 12 +- .../living/carbon/human/species/species.dm | 16 +- .../human/species/station/diona/diona.dm | 4 +- .../carbon/human/species/station/golem.dm | 5 +- .../carbon/human/species/station/ipc/ipc.dm | 4 +- .../human/species/station/skrell/skrell.dm | 10 +- .../human/species/station/vaurca/vaurca.dm | 5 +- code/modules/mob/living/living_defense.dm | 8 +- .../simple_animal/borer/borer_powers.dm | 40 +-- .../living/simple_animal/hostile/hostile.dm | 2 +- .../mob/living/simple_animal/simple_animal.dm | 5 +- code/modules/mob/mob_grab.dm | 5 - code/modules/organs/organ_external.dm | 18 + code/modules/organs/subtypes/augment.dm | 1 - code/modules/projectiles/pins.dm | 2 +- code/modules/projectiles/projectile/beams.dm | 9 - code/modules/psionics/abilities/_ability.dm | 29 ++ code/modules/psionics/abilities/air_bullet.dm | 58 +++ code/modules/psionics/abilities/assay.dm | 80 +++++ code/modules/psionics/abilities/barrier.dm | 30 ++ code/modules/psionics/abilities/bubble.dm | 33 ++ code/modules/psionics/abilities/charge.dm | 32 ++ code/modules/psionics/abilities/command.dm | 78 ++++ code/modules/psionics/abilities/commune.dm | 71 ++++ .../modules/psionics/abilities/electrocute.dm | 30 ++ .../abilities/emotional_suggestion.dm | 70 ++++ code/modules/psionics/abilities/focus.dm | 28 ++ code/modules/psionics/abilities/grip.dm | 51 +++ .../psionics/abilities/hollow_purple.dm | 106 ++++++ code/modules/psionics/abilities/ion.dm | 24 ++ code/modules/psionics/abilities/jump.dm | 22 ++ code/modules/psionics/abilities/lightning.dm | 59 +++ code/modules/psionics/abilities/mend.dm | 69 ++++ .../modules/psionics/abilities/mind_muddle.dm | 29 ++ .../psionics/abilities/mirror_shade.dm | 53 +++ code/modules/psionics/abilities/nlom_eyes.dm | 76 ++++ .../psionics/abilities/psi_awakening.dm | 43 +++ code/modules/psionics/abilities/psi_drain.dm | 57 +++ code/modules/psionics/abilities/psi_punch.dm | 31 ++ .../psionics/abilities/psi_recovery.dm | 37 ++ code/modules/psionics/abilities/psi_search.dm | 68 ++++ .../modules/psionics/abilities/psi_stamina.dm | 27 ++ code/modules/psionics/abilities/psi_sunder.dm | 44 +++ .../psionics/abilities/psi_suppression.dm | 27 ++ code/modules/psionics/abilities/psi_sword.dm | 32 ++ code/modules/psionics/abilities/psi_tool.dm | 32 ++ code/modules/psionics/abilities/pull.dm | 38 ++ code/modules/psionics/abilities/read_mind.dm | 77 ++++ code/modules/psionics/abilities/rejuvenate.dm | 38 ++ code/modules/psionics/abilities/rend.dm | 64 ++++ code/modules/psionics/abilities/shockwave.dm | 42 +++ .../modules/psionics/abilities/singularity.dm | 59 +++ code/modules/psionics/abilities/skinsight.dm | 67 ++++ code/modules/psionics/abilities/spasm.dm | 30 ++ code/modules/psionics/abilities/stasis.dm | 58 +++ code/modules/psionics/abilities/throw.dm | 93 +++++ code/modules/psionics/abilities/time_stop.dm | 47 +++ code/modules/psionics/abilities/warp.dm | 75 ++++ .../psionics/abilities/zona_absorption.dm | 41 +++ code/modules/psionics/complexus/complexus.dm | 32 +- .../psionics/complexus/complexus_helpers.dm | 32 +- .../psionics/complexus/complexus_latency.dm | 17 +- .../complexus/complexus_power_cache.dm | 49 --- .../psionics/complexus/complexus_process.dm | 214 +++-------- .../psionics/equipment/cerebro_enhancers.dm | 82 ++--- code/modules/psionics/equipment/psipower.dm | 4 + .../psionics/equipment/psipower_blade.dm | 34 +- .../modules/psionics/equipment/psipower_tk.dm | 94 ----- code/modules/psionics/events/mini_spasm.dm | 12 +- code/modules/psionics/faculties/_faculty.dm | 11 - code/modules/psionics/faculties/_power.dm | 37 -- code/modules/psionics/faculties/coercion.dm | 337 ------------------ .../modules/psionics/faculties/energistics.dm | 116 ------ .../psionics/faculties/psychokinesis.dm | 93 ----- code/modules/psionics/faculties/redaction.dm | 186 ---------- code/modules/psionics/interface/ui_hub.dm | 80 ++++- code/modules/psionics/mob/armour.dm | 13 + code/modules/psionics/mob/mob.dm | 13 +- code/modules/psionics/mob/mob_assay.dm | 79 ---- code/modules/psionics/mob/mob_helpers.dm | 19 +- code/modules/psionics/mob/mob_interactions.dm | 37 -- code/modules/shareddream/dream_entry.dm | 6 +- .../mattatlas-kyoushikimurasaki.yml | 51 +++ icons/mob/items/lefthand_spells.dmi | Bin 2480 -> 2559 bytes icons/mob/items/righthand_spells.dmi | Bin 2466 -> 2515 bytes icons/mob/screen_spells.dmi | Bin 74727 -> 72004 bytes icons/obj/projectiles.dmi | Bin 35471 -> 36265 bytes tgui/packages/tgui/interfaces/PsionicShop.tsx | 113 ++++++ 146 files changed, 3213 insertions(+), 1617 deletions(-) create mode 100644 SQL/migrate/V081__psionics.sql create mode 100644 code/modules/client/preference_setup/general/07_psionics.dm create mode 100644 code/modules/psionics/abilities/_ability.dm create mode 100644 code/modules/psionics/abilities/air_bullet.dm create mode 100644 code/modules/psionics/abilities/assay.dm create mode 100644 code/modules/psionics/abilities/barrier.dm create mode 100644 code/modules/psionics/abilities/bubble.dm create mode 100644 code/modules/psionics/abilities/charge.dm create mode 100644 code/modules/psionics/abilities/command.dm create mode 100644 code/modules/psionics/abilities/commune.dm create mode 100644 code/modules/psionics/abilities/electrocute.dm create mode 100644 code/modules/psionics/abilities/emotional_suggestion.dm create mode 100644 code/modules/psionics/abilities/focus.dm create mode 100644 code/modules/psionics/abilities/grip.dm create mode 100644 code/modules/psionics/abilities/hollow_purple.dm create mode 100644 code/modules/psionics/abilities/ion.dm create mode 100644 code/modules/psionics/abilities/jump.dm create mode 100644 code/modules/psionics/abilities/lightning.dm create mode 100644 code/modules/psionics/abilities/mend.dm create mode 100644 code/modules/psionics/abilities/mind_muddle.dm create mode 100644 code/modules/psionics/abilities/mirror_shade.dm create mode 100644 code/modules/psionics/abilities/nlom_eyes.dm create mode 100644 code/modules/psionics/abilities/psi_awakening.dm create mode 100644 code/modules/psionics/abilities/psi_drain.dm create mode 100644 code/modules/psionics/abilities/psi_punch.dm create mode 100644 code/modules/psionics/abilities/psi_recovery.dm create mode 100644 code/modules/psionics/abilities/psi_search.dm create mode 100644 code/modules/psionics/abilities/psi_stamina.dm create mode 100644 code/modules/psionics/abilities/psi_sunder.dm create mode 100644 code/modules/psionics/abilities/psi_suppression.dm create mode 100644 code/modules/psionics/abilities/psi_sword.dm create mode 100644 code/modules/psionics/abilities/psi_tool.dm create mode 100644 code/modules/psionics/abilities/pull.dm create mode 100644 code/modules/psionics/abilities/read_mind.dm create mode 100644 code/modules/psionics/abilities/rejuvenate.dm create mode 100644 code/modules/psionics/abilities/rend.dm create mode 100644 code/modules/psionics/abilities/shockwave.dm create mode 100644 code/modules/psionics/abilities/singularity.dm create mode 100644 code/modules/psionics/abilities/skinsight.dm create mode 100644 code/modules/psionics/abilities/spasm.dm create mode 100644 code/modules/psionics/abilities/stasis.dm create mode 100644 code/modules/psionics/abilities/throw.dm create mode 100644 code/modules/psionics/abilities/time_stop.dm create mode 100644 code/modules/psionics/abilities/warp.dm create mode 100644 code/modules/psionics/abilities/zona_absorption.dm delete mode 100644 code/modules/psionics/complexus/complexus_power_cache.dm delete mode 100644 code/modules/psionics/equipment/psipower_tk.dm delete mode 100644 code/modules/psionics/faculties/_faculty.dm delete mode 100644 code/modules/psionics/faculties/_power.dm delete mode 100644 code/modules/psionics/faculties/coercion.dm delete mode 100644 code/modules/psionics/faculties/energistics.dm delete mode 100644 code/modules/psionics/faculties/psychokinesis.dm delete mode 100644 code/modules/psionics/faculties/redaction.dm create mode 100644 code/modules/psionics/mob/armour.dm delete mode 100644 code/modules/psionics/mob/mob_assay.dm delete mode 100644 code/modules/psionics/mob/mob_interactions.dm create mode 100644 html/changelogs/mattatlas-kyoushikimurasaki.yml create mode 100644 tgui/packages/tgui/interfaces/PsionicShop.tsx diff --git a/SQL/migrate/V081__psionics.sql b/SQL/migrate/V081__psionics.sql new file mode 100644 index 00000000000..a2f318f7ffe --- /dev/null +++ b/SQL/migrate/V081__psionics.sql @@ -0,0 +1,5 @@ +-- +-- Adds psionic prefs +-- + +ALTER TABLE `ss13_characters` ADD COLUMN `psionics` LONGTEXT DEFAULT null AFTER `origin`; diff --git a/aurorastation.dme b/aurorastation.dme index a0dab8d0565..6b799fdac63 100644 --- a/aurorastation.dme +++ b/aurorastation.dme @@ -1585,6 +1585,7 @@ #include "code\modules\client\preference_setup\general\04_equipment.dm" #include "code\modules\client\preference_setup\general\05_background.dm" #include "code\modules\client\preference_setup\general\06_flavor.dm" +#include "code\modules\client\preference_setup\general\07_psionics.dm" #include "code\modules\client\preference_setup\global\01_ui.dm" #include "code\modules\client\preference_setup\global\02_settings.dm" #include "code\modules\client\preference_setup\global\03_language.dm" @@ -2890,32 +2891,66 @@ #include "code\modules\projectiles\targeting\targeting_mob.dm" #include "code\modules\projectiles\targeting\targeting_overlay.dm" #include "code\modules\projectiles\targeting\targeting_triggers.dm" +#include "code\modules\psionics\abilities\_ability.dm" +#include "code\modules\psionics\abilities\air_bullet.dm" +#include "code\modules\psionics\abilities\assay.dm" +#include "code\modules\psionics\abilities\barrier.dm" +#include "code\modules\psionics\abilities\bubble.dm" +#include "code\modules\psionics\abilities\charge.dm" +#include "code\modules\psionics\abilities\command.dm" +#include "code\modules\psionics\abilities\commune.dm" +#include "code\modules\psionics\abilities\electrocute.dm" +#include "code\modules\psionics\abilities\emotional_suggestion.dm" +#include "code\modules\psionics\abilities\focus.dm" +#include "code\modules\psionics\abilities\grip.dm" +#include "code\modules\psionics\abilities\hollow_purple.dm" +#include "code\modules\psionics\abilities\ion.dm" +#include "code\modules\psionics\abilities\jump.dm" +#include "code\modules\psionics\abilities\lightning.dm" +#include "code\modules\psionics\abilities\mend.dm" +#include "code\modules\psionics\abilities\mind_muddle.dm" +#include "code\modules\psionics\abilities\mirror_shade.dm" +#include "code\modules\psionics\abilities\nlom_eyes.dm" +#include "code\modules\psionics\abilities\psi_awakening.dm" +#include "code\modules\psionics\abilities\psi_drain.dm" +#include "code\modules\psionics\abilities\psi_punch.dm" +#include "code\modules\psionics\abilities\psi_recovery.dm" +#include "code\modules\psionics\abilities\psi_search.dm" +#include "code\modules\psionics\abilities\psi_stamina.dm" +#include "code\modules\psionics\abilities\psi_sunder.dm" +#include "code\modules\psionics\abilities\psi_suppression.dm" +#include "code\modules\psionics\abilities\psi_sword.dm" +#include "code\modules\psionics\abilities\psi_tool.dm" +#include "code\modules\psionics\abilities\pull.dm" +#include "code\modules\psionics\abilities\read_mind.dm" +#include "code\modules\psionics\abilities\rejuvenate.dm" +#include "code\modules\psionics\abilities\rend.dm" +#include "code\modules\psionics\abilities\shockwave.dm" +#include "code\modules\psionics\abilities\singularity.dm" +#include "code\modules\psionics\abilities\skinsight.dm" +#include "code\modules\psionics\abilities\spasm.dm" +#include "code\modules\psionics\abilities\stasis.dm" +#include "code\modules\psionics\abilities\throw.dm" +#include "code\modules\psionics\abilities\time_stop.dm" +#include "code\modules\psionics\abilities\warp.dm" +#include "code\modules\psionics\abilities\zona_absorption.dm" #include "code\modules\psionics\complexus\complexus.dm" #include "code\modules\psionics\complexus\complexus_helpers.dm" #include "code\modules\psionics\complexus\complexus_latency.dm" -#include "code\modules\psionics\complexus\complexus_power_cache.dm" #include "code\modules\psionics\complexus\complexus_process.dm" #include "code\modules\psionics\equipment\cerebro_enhancers.dm" #include "code\modules\psionics\equipment\psipower.dm" #include "code\modules\psionics\equipment\psipower_blade.dm" #include "code\modules\psionics\equipment\psipower_tinker.dm" -#include "code\modules\psionics\equipment\psipower_tk.dm" #include "code\modules\psionics\events\_psi.dm" #include "code\modules\psionics\events\mini_spasm.dm" #include "code\modules\psionics\events\psi_balm.dm" #include "code\modules\psionics\events\psi_wail.dm" -#include "code\modules\psionics\faculties\_faculty.dm" -#include "code\modules\psionics\faculties\_power.dm" -#include "code\modules\psionics\faculties\coercion.dm" -#include "code\modules\psionics\faculties\energistics.dm" -#include "code\modules\psionics\faculties\psychokinesis.dm" -#include "code\modules\psionics\faculties\redaction.dm" #include "code\modules\psionics\interface\ui.dm" #include "code\modules\psionics\interface\ui_hub.dm" +#include "code\modules\psionics\mob\armour.dm" #include "code\modules\psionics\mob\mob.dm" -#include "code\modules\psionics\mob\mob_assay.dm" #include "code\modules\psionics\mob\mob_helpers.dm" -#include "code\modules\psionics\mob\mob_interactions.dm" #include "code\modules\radiation\radiation.dm" #include "code\modules\random_map\_random_map_setup.dm" #include "code\modules\random_map\random_map.dm" diff --git a/code/__defines/_macros.dm b/code/__defines/_macros.dm index 8fb3983648c..df8c471e300 100644 --- a/code/__defines/_macros.dm +++ b/code/__defines/_macros.dm @@ -30,11 +30,11 @@ #define FONT_SIZE_HUGE 4 #define FONT_SIZE_GIANT 5 -#define FONT_SMALL(X) ("" + X + "") -#define FONT_NORMAL(X) ("" + X + "") -#define FONT_LARGE(X) ("" + X + "") -#define FONT_HUGE(X) ("" + X + "") -#define FONT_GIANT(X) ("" + X + "") +#define FONT_SMALL(X) ("" + X + "") +#define FONT_NORMAL(X) ("" + X + "") +#define FONT_LARGE(X) ("" + X + "") +#define FONT_HUGE(X) ("" + X + "") +#define FONT_GIANT(X) ("" + X + "") #define MATRIX_DANGER(X) (FONT_LARGE(SPAN_DANGER(X))) #define MATRIX_NOTICE(X) (FONT_LARGE(SPAN_NOTICE(X))) diff --git a/code/__defines/damage_organs.dm b/code/__defines/damage_organs.dm index a8fd161b5ca..703fea6c878 100644 --- a/code/__defines/damage_organs.dm +++ b/code/__defines/damage_organs.dm @@ -20,6 +20,7 @@ #define DAMAGE_FLAG_EXPLODE 16 #define DAMAGE_FLAG_DISPERSED 32 // Makes apply_damage calls without specified zone distribute damage rather than randomly choose organ (for humans) #define DAMAGE_FLAG_BIO 64 +#define DAMAGE_FLAG_PSIONIC 128 #define STUN "stun" #define WEAKEN "weaken" diff --git a/code/__defines/psi.dm b/code/__defines/psi.dm index 70b14bf049a..7d3ffc7728c 100644 --- a/code/__defines/psi.dm +++ b/code/__defines/psi.dm @@ -1,11 +1,17 @@ -#define PSI_COERCION "coercion" -#define PSI_PSYCHOKINESIS "psychokinesis" -#define PSI_REDACTION "redaction" -#define PSI_ENERGISTICS "energistics" +#define PSI_RANK_SENSITIVE 1 /// Psionically sensitive. Skrell. +#define PSI_RANK_HARMONIOUS 2 /// Psionically harmonious. Event Skrell, antagonists. +#define PSI_RANK_APEX 3 /// Psionic apex. Event people. +#define PSI_RANK_LIMITLESS 4 /// Admin-only. Yes, it's a Gojou Satoru reference. -#define PSI_RANK_BLUNT 0 -#define PSI_RANK_LATENT 1 -#define PSI_RANK_OPERANT 2 -#define PSI_RANK_MASTER 3 -#define PSI_RANK_GRANDMASTER 4 -#define PSI_RANK_PARAMOUNT 5 \ No newline at end of file +#define PSI_FLAG_CANON 1 /// An ability that can be selected by a ship character. +#define PSI_FLAG_EVENT 2 /// An ability for event characters, not selectable by ship characters, so technically canon. +#define PSI_FLAG_ANTAG 4 /// An ability for antag characters, not selectable by ship characters, not really canon. +#define PSI_FLAG_FOUNDATIONAL 8 /// An ability automatically given to Psionically Sensitive users. +#define PSI_FLAG_APEX 16 /// An ability automatically given to Psionic Apex users. +#define PSI_FLAG_LIMITLESS 32 /// An ability automatically given to Limitless users. +#define PSI_FLAG_SPECIAL 64 /// Unselectable in the point shop - must be handed out manually. + +/// Psi shop points given by a certain rank. +#define PSI_POINTS_HARMONIOUS 10 +#define PSI_POINTS_APEX 14 +#define PSI_POINTS_LIMITLESS 40 diff --git a/code/__defines/traits.dm b/code/__defines/traits.dm index 9062782c3b5..7b07c65dc7f 100644 --- a/code/__defines/traits.dm +++ b/code/__defines/traits.dm @@ -139,6 +139,24 @@ /// forces the mob to speak gibberish, like highly damaged borgs #define TRAIT_SPEAKING_GIBBERISH "speaking_gibberish" +/// Mob does not need to breathe. +#define TRAIT_NO_BREATHE "no_breathe" + +/// Mob has pressure immunity. +#define TRAIT_PRESSURE_IMMUNITY "pressure_immunity" + +/// Trait sourced from changeling abilities. +#define TRAIT_SOURCE_CHANGELING "changeling" + +/// Mob is psionically deaf. +#define TRAIT_PSIONICALLY_DEAF "psionically_deaf" + +/// Zona bovinae absorbed, used by Loner to track a mob that has had its ZB consumed. Doesn't make them psi-deaf. +#define TRAIT_ZONA_BOVINAE_ABSORBED "bovinae_absorbed" + +/// Hidden from Psi-Search. +#define TRAIT_PSIONIC_SUPPRESSION "psionic_suppression" + /// lets mobs that traditionally don't hallucinate, hallucinate #define TRAIT_BYPASS_HALLUCINATION_RESTRICTION "bypassing_hallucination_restriction" @@ -153,3 +171,6 @@ #define TRAIT_OVERLOADER_OD_INITIAL "overloader_od_initial" #define TRAIT_OVERLOADER_OD_MEDIUM "overloader_od_medium" #define TRAIT_OVERLOADER_OD_EFFECT "overloader_od_effect" + +/// Traits given by psionics. +#define TRAIT_SOURCE_PSIONICS "psionics" diff --git a/code/_onclick/hud/ability_screen_objects.dm b/code/_onclick/hud/ability_screen_objects.dm index c9cbef6601d..256fc7ad1b6 100644 --- a/code/_onclick/hud/ability_screen_objects.dm +++ b/code/_onclick/hud/ability_screen_objects.dm @@ -126,6 +126,10 @@ for(var/obj/screen/ability/A in ability_objects) remove_ability(A) +/obj/screen/movable/ability_master/proc/remove_all_psionic_abilities() + for(var/obj/screen/ability/obj_based/psionic/A in ability_objects) + remove_ability(A) + /obj/screen/movable/ability_master/proc/get_ability_by_name(name_to_search) for(var/obj/screen/ability/A in ability_objects) if(A.name == name_to_search) @@ -147,7 +151,7 @@ /mob/living/LateLogin() . = ..() if(ability_master) - ability_master.toggle_open(1) + ability_master.toggle_open(2) client.screen -= ability_master /mob/living/Initialize() @@ -183,10 +187,15 @@ overlays += ability_icon_state -/obj/screen/ability/Click() +/obj/screen/ability/Click(var/location, var/control, var/params) if(!usr) return + var/list/click_params = params2list(params) + if(click_params["shift"]) + examine(usr) + return + activate() /obj/screen/ability/MouseDrop(var/atom/A) @@ -283,13 +292,44 @@ ////////////////////////////// /obj/screen/ability/obj_based - var/obj/object = null + var/obj/object /obj/screen/ability/obj_based/activate() if(object) object.Click() -// Technomancer +/// Psionics. +/obj/screen/ability/obj_based/psionic + icon_state = "nano_spell_base" + background_base_state = "nano" + var/singleton/psionic_power/connected_power + +/obj/screen/ability/obj_based/psionic/Destroy() + connected_power = null + return ..() + +/obj/screen/movable/ability_master/proc/add_psionic_ability(var/obj/object_given, var/ability_icon_given, var/singleton/psionic_power/P) + if(!object_given) + message_admins("ERROR: add_psionic_ability() was not given an object in its arguments.") + if(!P) + message_admins("Psionic ability added without connected psionic power singleton!") + if(get_ability_by_instance(object_given)) + return // Duplicate + var/obj/screen/ability/obj_based/psionic/A = new /obj/screen/ability/obj_based/psionic() + A.ability_master = src + A.object = object_given + A.ability_icon_state = ability_icon_given + A.name = object_given.name + A.connected_power = P + ability_objects.Add(A) + if(my_mob.client) + toggle_open(2) //forces the icons to refresh on screen + +/obj/screen/ability/obj_based/psionic/examine(mob/user) + to_chat(user, SPAN_NOTICE("This ability is [connected_power.name].")) + to_chat(user, SPAN_NOTICE("[connected_power.desc]")) + +/// Technomancer. /obj/screen/ability/obj_based/technomancer icon_state = "wiz_spell_base" background_base_state = "wiz" diff --git a/code/controllers/subsystems/processing/psi.dm b/code/controllers/subsystems/processing/psi.dm index c6a2b7bee15..2a764990c20 100644 --- a/code/controllers/subsystems/processing/psi.dm +++ b/code/controllers/subsystems/processing/psi.dm @@ -1,40 +1,100 @@ -var/global/list/psychic_ranks_to_strings = list("Latent", "Operant", "Masterclass", "Grandmasterclass", "Paramount") +var/global/list/psychic_ranks_to_strings = list("Psionically Sensitive", "Psionically Harmonious", "Psionic Apex", "Limitless") + /var/datum/controller/subsystem/processing/psi/SSpsi /datum/controller/subsystem/processing/psi - name = "Psychics" + name = "Psionics" priority = SS_PRIORITY_PSYCHICS - flags = SS_POST_FIRE_TIMING | SS_BACKGROUND + flags = SS_BACKGROUND - var/list/faculties_by_id = list() - var/list/faculties_by_name = list() - var/list/all_aura_images = list() - var/list/all_psi_complexes = list() - var/list/psi_dampeners = list() - var/list/psi_monitors = list() - var/list/armor_faculty_by_type = list() - var/list/faculties_by_intent = list() + var/checking_nlom = FALSE + var/last_nlom_awareness_check = 0 + var/completing_nlom = FALSE + var/complete_nlom_time = 0 + var/list/nlom_votes = list() + var/list/nlom_voters = list() + var/list/all_aura_images = list() + var/list/all_psi_complexes = list() /datum/controller/subsystem/processing/psi/New() NEW_SS_GLOBAL(SSpsi) -/datum/controller/subsystem/processing/psi/proc/get_faculty(var/faculty) - return faculties_by_name[faculty] || faculties_by_id[faculty] +/datum/controller/subsystem/processing/psi/fire(resumed) + ..() + if((world.time >= (last_nlom_awareness_check + 30 MINUTES)) && !checking_nlom && !completing_nlom) + checking_nlom = TRUE + nlom_votes = list( + "Calm" = 0, + "Happy" = 0, + "Sad" = 0, + "Fearful" = 0, + "Angry" = 0, + "Stressed" = 0, + "Confused" = 0 + ) + for(var/datum/psi_complexus/PC in all_psi_complexes) + if(PC.psionic_rank >= PSI_RANK_SENSITIVE) + to_chat(PC.owner, SPAN_NOTICE(FONT_HUGE("The Nlom field prompts you for your emotional state."))) + to_chat(PC.owner, + "Calm | Happy | \ + Sad | Fearful | \ + Angry | Stressed | \ + Confused= complete_nlom_time) + var/highest_emotion = "Happy" + for(var/emotion in nlom_votes) + if(nlom_votes[highest_emotion] < nlom_votes[emotion]) + highest_emotion = emotion + for(var/datum/psi_complexus/PC in all_psi_complexes) + if(PC.psionic_rank >= PSI_RANK_SENSITIVE) + var/static/nlom_to_span = list( + "Calm" = "good", + "Happy" = "good", + "Sad" = "notice", + "Fearful" = "alien", + "Angry" = "danger", + "Stressed" = "warning", + "Confused" = "cult" + ) + var/static/nlom_to_chat_text = list( + "Calm" = "calmness", + "Happy" = "happiness", + "Sad" = "sadness", + "Fearful" = "fear", + "Angry" = "anger", + "Stressed" = "stress", + "Confused" = "confusion" + ) + if(nlom_votes[highest_emotion] > 0) + to_chat(PC.owner, SPAN_NOTICE(FONT_HUGE("A sense of [nlom_to_chat_text[highest_emotion]] washes over you."))) + else + to_chat(PC.owner, SPAN_WARNING(FONT_HUGE("The Nlom is silent and still. You feel uneasy."))) + checking_nlom = FALSE + completing_nlom = FALSE + last_nlom_awareness_check = world.time + nlom_votes.Cut() + nlom_voters.Cut() -/datum/controller/subsystem/processing/psi/Initialize() +/datum/controller/subsystem/processing/psi/Topic(href, href_list) . = ..() + if(.) + return - var/list/faculties = subtypesof(/datum/psionic_faculty) - for(var/ftype in faculties) - var/datum/psionic_faculty/faculty = new ftype - faculties_by_id[faculty.id] = faculty - faculties_by_name[faculty.name] = faculty - faculties_by_intent[faculty.associated_intent] = faculty.id - - var/list/powers = subtypesof(/datum/psionic_power) - for(var/ptype in powers) - var/datum/psionic_power/power = new ptype - if(power.faculty) - var/datum/psionic_faculty/faculty = get_faculty(power.faculty) - if(faculty) - faculty.powers |= power \ No newline at end of file + if(href_list["emotion"]) + var/emotion = href_list["emotion"] + var/datum/psi_complexus/PC = locate(href_list["voter"]) + if(PC in nlom_voters) + to_chat(PC.owner, SPAN_WARNING("You have already sent your emotional state to the Nlom.")) + return + if(emotion in nlom_votes) + var/base_vote = 1 + var/psi_rank = PC.get_rank() + if(psi_rank == PSI_RANK_HARMONIOUS) + base_vote = 2 + else if(psi_rank == PSI_RANK_APEX) + base_vote = 4 + nlom_votes[emotion] += base_vote + to_chat(PC.owner, SPAN_NOTICE("You have transmitted your emotional state to the Nlom.")) + nlom_voters |= PC diff --git a/code/datums/mind.dm b/code/datums/mind.dm index 90a6f10da3e..97c506c4d0d 100644 --- a/code/datums/mind.dm +++ b/code/datums/mind.dm @@ -112,7 +112,7 @@ new_character.make_vampire() if(active) new_character.key = key //now transfer the key to link the client to our new body - + /datum/mind/proc/store_memory(new_text) . = length(memory + new_text) @@ -179,11 +179,6 @@ /datum/mind/Topic(href, href_list) if(!check_rights(R_ADMIN)) return - if(current && isliving(current)) - if(href_list["set_psi_faculty"] && href_list["set_psi_faculty_rank"]) - current.set_psi_rank(href_list["set_psi_faculty"], text2num(href_list["set_psi_faculty_rank"])) - return TRUE - if(href_list["add_antagonist"]) var/datum/antagonist/antag = all_antag_types[href_list["add_antagonist"]] if(antag) @@ -441,6 +436,16 @@ for(var/datum/objective/objective in objectives) to_chat(current, "Objective #[obj_count]: [objective.explanation_text]") obj_count++ + + else if(href_list["set_psi_rank"]) + current.set_psi_rank(text2num(href_list["set_psi_rank"])) + return + else if(href_list["set_psi_rank_limitless"]) + var/sure = input(usr, "Limitless is INTENTIONALLY STUPIDLY OVERPOWERED! YOU SHOULD NOT BE USING THIS WITHOUT KNOWING EXACTLY WHAT YOU'RE DOING!", "Don't Get A Staff Complaint") as anything in list("I know what I'm doing!", "I fear no man. But that thing... it scares me!") + if(sure == "I know what I'm doing!") + current.set_psi_rank(PSI_RANK_LIMITLESS) + return + edit_memory() /datum/mind/proc/find_syndicate_uplink() diff --git a/code/datums/outfits/outfit_antag.dm b/code/datums/outfits/outfit_antag.dm index 079f63708fa..db9819455c8 100644 --- a/code/datums/outfits/outfit_antag.dm +++ b/code/datums/outfits/outfit_antag.dm @@ -172,7 +172,6 @@ /datum/outfit/admin/syndicate/mercenary/loner name = "Loner" - head = /obj/item/clothing/head/helmet/space/psi_amp/lesser l_ear = /obj/item/device/radio/headset/syndicate r_pocket = /obj/item/device/special_uplink/burglar diff --git a/code/datums/uplink/highly visible and dangerous weapons.dm b/code/datums/uplink/highly visible and dangerous weapons.dm index d54c2de97da..86a82e4fa89 100644 --- a/code/datums/uplink/highly visible and dangerous weapons.dm +++ b/code/datums/uplink/highly visible and dangerous weapons.dm @@ -251,3 +251,8 @@ name = ".50 Pistol" telecrystal_cost = 5 path = /obj/item/gun/projectile/deagle/adhomai + +/datum/uplink_item/item/visible_weapons/psionic_jumpstarter + name = "Psionic Jumpstarter" + telecrystal_cost = 19 + path = /obj/item/psionic_jumpstarter diff --git a/code/game/antagonist/outsider/loner.dm b/code/game/antagonist/outsider/loner.dm index f45f5f7ed61..5b30620ec47 100644 --- a/code/game/antagonist/outsider/loner.dm +++ b/code/game/antagonist/outsider/loner.dm @@ -7,8 +7,8 @@ var/datum/antagonist/loner/loners bantype = "loner" antag_indicator = "loner" landmark_id = "lonerspawn" - welcome_text = "You are a Loner, someone underequipped to deal with the station. You will probably not survive for the whole round, so don't sweat it if you die!
\ - You are equipped with a lesser cerebro-enhancer, which allows you to unlock your psionic potential. Use it in-hand to choose your boosted faculty, then install it on your head." + welcome_text = "You are a Loner, someone underequipped to deal with the station. You will probably not survive for the whole round, so don't sweat it if you die!
\ + You have a special psionic power that allows you to absorb a psionic energy from a being's Zona Bovinae, granting you an extra point to be used in the Point Shop." flags = ANTAG_OVERRIDE_JOB | ANTAG_CLEAR_EQUIPMENT | ANTAG_CHOOSE_NAME | ANTAG_VOTABLE | ANTAG_SET_APPEARANCE antaghud_indicator = "hudloner" required_age = 7 @@ -39,8 +39,11 @@ var/datum/antagonist/loner/loners player.preEquipOutfit(/datum/outfit/admin/syndicate/mercenary/loner, FALSE) player.equipOutfit(/datum/outfit/admin/syndicate/mercenary/loner, FALSE) + player.set_psi_rank(PSI_RANK_HARMONIOUS) + var/singleton/psionic_power/P = GET_SINGLETON(/singleton/psionic_power/zona_absorption) + P.apply(player) player.force_update_limbs() player.update_eyes() player.regenerate_icons() - return TRUE \ No newline at end of file + return TRUE diff --git a/code/game/gamemodes/changeling/helpers/_framework.dm b/code/game/gamemodes/changeling/helpers/_framework.dm index ce7b30abccc..d0c8160bfb5 100644 --- a/code/game/gamemodes/changeling/helpers/_framework.dm +++ b/code/game/gamemodes/changeling/helpers/_framework.dm @@ -10,9 +10,7 @@ var/global/list/possible_changeling_IDs = list("Alpha","Beta","Gamma","Delta","E var/chem_recharge_rate = 0.5 var/chem_storage = 50 var/sting_range = 1 - var/space_adapted = FALSE var/using_thermals = FALSE - var/no_breathing = FALSE var/changelingID = "Changeling" var/geneticdamage = 0 var/isabsorbing = 0 diff --git a/code/game/gamemodes/changeling/implements/powers/resources.dm b/code/game/gamemodes/changeling/implements/powers/resources.dm index 850922335b5..c928a3da13c 100644 --- a/code/game/gamemodes/changeling/implements/powers/resources.dm +++ b/code/game/gamemodes/changeling/implements/powers/resources.dm @@ -12,8 +12,7 @@ //removes the need to breathe, removes effects of very low pressure /mob/proc/changeling_spaceadaption() - var/datum/changeling/changeling = mind.antag_datums[MODE_CHANGELING] - changeling.space_adapted = TRUE + ADD_TRAIT(src, TRAIT_PRESSURE_IMMUNITY, TRAIT_SOURCE_CHANGELING) return TRUE /mob/proc/changeling_armor() @@ -22,8 +21,7 @@ //removes the need to breathe /mob/proc/changeling_nobreathing() - var/datum/changeling/changeling = mind.antag_datums[MODE_CHANGELING] - changeling.no_breathing = TRUE + ADD_TRAIT(src, TRAIT_NO_BREATHE, TRAIT_SOURCE_CHANGELING) return TRUE // HIVE MIND UPLOAD/DOWNLOAD DNA @@ -97,4 +95,4 @@ var/list/datum/absorbed_dna/hivemind_bank = list() absorbDNA(chosen_dna) to_chat(src, "We absorb the DNA of [S] from the air.") feedback_add_details("changeling_powers", "HD") - return TRUE \ No newline at end of file + return TRUE diff --git a/code/game/gamemodes/technomancer/catalog.dm b/code/game/gamemodes/technomancer/catalog.dm index 7027cf96e4e..cb1a329e116 100644 --- a/code/game/gamemodes/technomancer/catalog.dm +++ b/code/game/gamemodes/technomancer/catalog.dm @@ -356,7 +356,7 @@ var/list/all_technomancer_assistance = typesof(/datum/technomancer/assistance) - return var/obj/item/technomancer_core/core = H.get_technomancer_core() if(core) - for(var/obj/spellbutton/spell in core.spells) + for(var/obj/spellbutton/technomancer/spell in core.spells) for(var/datum/technomancer/spell/spell_datum in spell_instances) if(spell_datum.obj_path == spell.spellpath) budget += spell_datum.cost @@ -381,7 +381,7 @@ var/list/all_technomancer_assistance = typesof(/datum/technomancer/assistance) - // We sadly need to do special stuff here or else people who refund cores with spells will lose points permanently. if(istype(AM, /obj/item/technomancer_core)) var/obj/item/technomancer_core/core = AM - for(var/obj/spellbutton/spell in core.spells) + for(var/obj/spellbutton/technomancer/spell in core.spells) for(var/datum/technomancer/spell/spell_datum in spell_instances) if(spell_datum.obj_path == spell.spellpath) budget += spell_datum.cost diff --git a/code/game/gamemodes/technomancer/core_obj.dm b/code/game/gamemodes/technomancer/core_obj.dm index 39f307d1c1f..dc3b553ac43 100644 --- a/code/game/gamemodes/technomancer/core_obj.dm +++ b/code/game/gamemodes/technomancer/core_obj.dm @@ -178,17 +178,15 @@ /obj/spellbutton name = "generic spellbutton" var/spellpath - var/obj/item/technomancer_core/core var/ability_icon_state -/obj/spellbutton/New(loc, var/path, var/new_name, var/new_icon_state) +/obj/spellbutton/Initialize(mapload, var/path, var/new_name, var/new_icon_state) + . = ..() if(!path || !ispath(path)) - message_admins("ERROR: /obj/spellbutton/New() was not given a proper path!") - qdel(src) + message_admins("ERROR: /obj/spellbutton/Initialize() was not given a proper path!") + return INITIALIZE_HINT_QDEL src.name = new_name src.spellpath = path - src.loc = loc - src.core = loc src.ability_icon_state = new_icon_state /obj/spellbutton/Click() @@ -199,6 +197,14 @@ /obj/spellbutton/DblClick() return Click() +/obj/spellbutton/technomancer + var/obj/item/technomancer_core/core + +/obj/spellbutton/technomancer/Initialize(mapload, var/path, var/new_name, var/new_icon_state) + . = ..() + src.loc = loc + src.core = loc + /mob/living/carbon/human/get_status_tab_items() . = ..() @@ -216,7 +222,7 @@ message_admins("ERROR: /obj/item/technomancer_core/add_spell() was not given a proper path! \ The path supplied was [path].") return - var/obj/spellbutton/spell = new(src, path, new_name, ability_icon_state) + var/obj/spellbutton/technomancer/spell = new(src, path, new_name, ability_icon_state) spells.Add(spell) if(wearer) wearer.ability_master.add_technomancer_ability(spell, ability_icon_state) diff --git a/code/game/gamemodes/technomancer/spell_objs.dm b/code/game/gamemodes/technomancer/spell_objs.dm index 93bd9058620..8958ed6d376 100644 --- a/code/game/gamemodes/technomancer/spell_objs.dm +++ b/code/game/gamemodes/technomancer/spell_objs.dm @@ -20,6 +20,7 @@ #define ASPECT_UNSTABLE "unstable" //Heavily RNG-based, causes instability to the victim. #define ASPECT_CHROMATIC "chromatic" //Used to combine with other spells. #define ASPECT_UNHOLY "unholy" //Involves the dead, blood, and most things against divine beings. +#define ASPECT_PSIONIC "psionics" //Psionic power. Bypasses core checks. /obj/item/spell name = "glowing particles" @@ -32,13 +33,15 @@ ) throwforce = 0 force = 0 - var/mob/living/carbon/human/owner = null - var/obj/item/technomancer_core/core = null + flags = NOBLUDGEON + var/mob/living/carbon/human/owner + var/obj/item/technomancer_core/core var/cast_methods = null // Controls how the spell is casted. var/aspect = null // Used for combining spells. var/toggled = 0 // Mainly used for overlays. var/cooldown = 0 // If set, will add a cooldown overlay and adjust click delay. Must be a multiple of 5 for overlays. var/cast_sound = null // Sound file played when this is used. + var/psi_cost = 0 // Psi complexus cost to use this spell. /obj/item/spell/examine(mob/user, distance) // Nothing on examine. return @@ -46,38 +49,62 @@ // Proc: on_use_cast() // Parameters: 1 (user - the technomancer casting the spell) // Description: Override this for clicking the spell in your hands. -/obj/item/spell/proc/on_use_cast(mob/user) - return +/obj/item/spell/proc/on_use_cast(mob/user, var/bypass_psi_check) + SHOULD_CALL_PARENT(TRUE) + if(aspect == ASPECT_PSIONIC && !bypass_psi_check) + if(!owner.psi.spend_power(psi_cost)) + return FALSE + return TRUE // Proc: on_throw_cast() // Parameters: 1 (hit_atom - the atom hit by the spell object) // Description: Override this for throwing effects. -/obj/item/spell/proc/on_throw_cast(atom/hit_atom) - return +/obj/item/spell/proc/on_throw_cast(atom/hit_atom, var/bypass_psi_check) + SHOULD_CALL_PARENT(TRUE) + if(aspect == ASPECT_PSIONIC && !bypass_psi_check) + if(!owner.psi.spend_power(psi_cost)) + return FALSE + return TRUE // Proc: on_ranged_cast() // Parameters: 2 (hit_atom - the atom clicked on by the user, user - the technomancer that clicked hit_atom) // Description: Override this for ranged effects. -/obj/item/spell/proc/on_ranged_cast(atom/hit_atom, mob/user) - return +/obj/item/spell/proc/on_ranged_cast(atom/hit_atom, mob/user, var/bypass_psi_check) + SHOULD_CALL_PARENT(TRUE) + if(aspect == ASPECT_PSIONIC && !bypass_psi_check) + if(!owner.psi.spend_power(psi_cost)) + return FALSE + return TRUE // Proc: on_melee_cast() // Parameters: 3 (hit_atom - the atom clicked on by the user, user - the technomancer that clicked hit_atom, def_zone - unknown) // Description: Override this for effects that occur at melee range. /obj/item/spell/proc/on_melee_cast(atom/hit_atom, mob/living/user, def_zone) - return + SHOULD_CALL_PARENT(TRUE) + if(aspect == ASPECT_PSIONIC) + if(!owner.psi.spend_power(psi_cost)) + return FALSE + return TRUE // Proc: on_combine_cast() // Parameters: 2 (I - the item trying to merge with the spell, user - the technomancer who initiated the merge) // Description: Override this for combining spells, like Aspect spells. /obj/item/spell/proc/on_combine_cast(obj/item/I, mob/user) - return + SHOULD_CALL_PARENT(TRUE) + if(aspect == ASPECT_PSIONIC) + if(!owner.psi.spend_power(psi_cost)) + return FALSE + return TRUE // Proc: on_innate_cast() // Parameters: 1 (user - the entity who is casting innately (without using hands).) // Description: Override this for casting without using hands (and as a result not using spell objects). /obj/item/spell/proc/on_innate_cast(mob/user) - return + SHOULD_CALL_PARENT(TRUE) + if(aspect == ASPECT_PSIONIC) + if(!owner.psi.spend_power(psi_cost)) + return FALSE + return TRUE // Proc: on_scepter_use_cast() // Parameters: 1 (user - the holder of the Scepter that clicked.) @@ -99,7 +126,9 @@ // if they are able to pay, it is deducted automatically. /obj/item/spell/proc/pay_energy(var/amount) if(!core) - return 0 + if(aspect == ASPECT_PSIONIC) + return owner.psi.spend_power(amount) + return amount return core.pay_energy(amount) // Proc: give_energy() @@ -107,7 +136,7 @@ // Description: Redirects the call to the core's give_energy(). /obj/item/spell/proc/give_energy(var/amount) if(!core) - return 0 + return amount return core.give_energy(amount) // Proc: adjust_instability() @@ -141,11 +170,17 @@ if(isliving(loc)) owner = loc if(owner) - core = owner.get_technomancer_core() - if(!core) - to_chat(owner, "You need a Core to do that.") - qdel(src) - return + if(aspect != ASPECT_PSIONIC) + core = owner.get_technomancer_core() + if(!core) + to_chat(owner, "You need a Core to do that.") + return INITIALIZE_HINT_QDEL + else + if(owner.psi.get_rank() >= PSI_RANK_APEX) + if(force) + force *= 1.1 + armor_penetration *= 1.1 + update_icon() // Proc: Destroy() @@ -180,32 +215,37 @@ // if it still can't find one. It will also check if the core is being worn properly, and finally checks if the owner is a technomancer. /obj/item/spell/proc/run_checks() if(!owner) - return 0 - if(!core) - core = locate(/obj/item/technomancer_core) in owner + return FALSE + if(aspect != ASPECT_PSIONIC) if(!core) + core = locate(/obj/item/technomancer_core) in owner + if(!core) + to_chat(owner, "You need to be wearing a core on your back or your wrists!") + return FALSE + if(core.loc != owner || (owner.back != core && owner.wrists != core)) //Make sure the core's being worn. to_chat(owner, "You need to be wearing a core on your back or your wrists!") - return 0 - if(core.loc != owner || (owner.back != core && owner.wrists != core)) //Make sure the core's being worn. - to_chat(owner, "You need to be wearing a core on your back or your wrists!") - return 0 - if(!core.simple_operation && !technomancers.is_technomancer(owner.mind)) //Now make sure the person using this is the actual antag. - to_chat(owner, "You can't seem to figure out how to make the machine work properly.") - return 0 - return 1 + return FALSE + if(!core.simple_operation && !technomancers.is_technomancer(owner.mind)) //Now make sure the person using this is the actual antag. + to_chat(owner, SPAN_DANGER("You can't seem to figure out how to make the machine work properly.")) + return FALSE + else + if(!owner.psi) + return FALSE + return TRUE // Proc: check_for_scepter() // Parameters: 0 // Description: Terrible code to check if a scepter is in the offhand, returns 1 if yes. /obj/item/spell/proc/check_for_scepter() - if(!src || !owner) return 0 + if(!src || !owner) + return FALSE if(owner.r_hand == src) if(istype(owner.l_hand, /obj/item/scepter)) - return 1 + return TRUE else if(istype(owner.r_hand, /obj/item/scepter)) - return 1 - return 0 + return TRUE + return FALSE // Proc: get_other_hand() // Parameters: 1 (I - item being compared to determine what the offhand is) @@ -259,7 +299,11 @@ if(cast_methods & CAST_RANGED) //Try to use a ranged method if a melee one doesn't exist. on_ranged_cast(target, user) if(cooldown) - var/effective_cooldown = round(cooldown * core.cooldown_modifier, 5) + var/effective_cooldown + if(aspect == ASPECT_PSIONIC) + effective_cooldown = cooldown + else + effective_cooldown = round(cooldown * core.cooldown_modifier, 5) user.setClickCooldown(effective_cooldown) flick("cooldown_[effective_cooldown]",src) @@ -330,3 +374,8 @@ spawn(20) if(src) qdel(src) + +/obj/item/spell/damage_flags() + . = ..() + if(aspect == ASPECT_PSIONIC) + . |= DAMAGE_FLAG_PSIONIC diff --git a/code/game/gamemodes/technomancer/spells/abjuration.dm b/code/game/gamemodes/technomancer/spells/abjuration.dm index d5f6f315dd6..7e967d4e082 100644 --- a/code/game/gamemodes/technomancer/spells/abjuration.dm +++ b/code/game/gamemodes/technomancer/spells/abjuration.dm @@ -15,6 +15,7 @@ aspect = ASPECT_TELE /obj/item/spell/abjuration/on_ranged_cast(atom/hit_atom, mob/user) + . = ..() if(istype(hit_atom, /mob/living) && pay_energy(500) && within_range(hit_atom)) var/mob/living/L = hit_atom var/mob/living/simple_animal/SM = null @@ -38,4 +39,4 @@ // In case NarNar comes back someday. if(istype(hit_atom, /obj/singularity/narsie)) to_chat(user, "One does not simply abjurate Nar'sie away.") - adjust_instability(200) \ No newline at end of file + adjust_instability(200) diff --git a/code/game/gamemodes/technomancer/spells/apportation.dm b/code/game/gamemodes/technomancer/spells/apportation.dm index e8d7a767e22..7f9ba497b04 100644 --- a/code/game/gamemodes/technomancer/spells/apportation.dm +++ b/code/game/gamemodes/technomancer/spells/apportation.dm @@ -16,6 +16,7 @@ aspect = ASPECT_TELE /obj/item/spell/apportation/on_ranged_cast(atom/hit_atom, mob/user) + . = ..() if(istype(hit_atom, /atom/movable)) var/atom/movable/AM = hit_atom diff --git a/code/game/gamemodes/technomancer/spells/audible_deception.dm b/code/game/gamemodes/technomancer/spells/audible_deception.dm index f4245cb329d..af74f41971c 100644 --- a/code/game/gamemodes/technomancer/spells/audible_deception.dm +++ b/code/game/gamemodes/technomancer/spells/audible_deception.dm @@ -65,6 +65,7 @@ var/selected_sound = null /obj/item/spell/audible_deception/on_use_cast(mob/user) + . = ..() var/list/sound_options = available_sounds if(check_for_scepter()) sound_options["!!AIR HORN!!"] = 'sound/items/AirHorn.ogg' @@ -73,6 +74,7 @@ selected_sound = sound_options[new_sound] /obj/item/spell/audible_deception/on_ranged_cast(atom/hit_atom, mob/living/user) + . = ..() var/turf/T = get_turf(hit_atom) if(selected_sound && pay_energy(200)) playsound(src, selected_sound, 80, 1, -1) diff --git a/code/game/gamemodes/technomancer/spells/aura/biomed_aura.dm b/code/game/gamemodes/technomancer/spells/aura/biomed_aura.dm index 29127fc99b1..3d515319b99 100644 --- a/code/game/gamemodes/technomancer/spells/aura/biomed_aura.dm +++ b/code/game/gamemodes/technomancer/spells/aura/biomed_aura.dm @@ -36,5 +36,6 @@ adjust_instability(2) /obj/item/spell/aura/biomed/on_use_cast(mob/living/user) + . = ..() heal_allies_only = !heal_allies_only to_chat(user, "Your aura will now heal [heal_allies_only ? "your allies" : "everyone"] near you.") diff --git a/code/game/gamemodes/technomancer/spells/chroma.dm b/code/game/gamemodes/technomancer/spells/chroma.dm index 44f43121dda..a677d8516ba 100644 --- a/code/game/gamemodes/technomancer/spells/chroma.dm +++ b/code/game/gamemodes/technomancer/spells/chroma.dm @@ -30,6 +30,7 @@ set_light(6, 5, l_color = new_color) /obj/item/spell/chroma/on_ranged_cast(atom/hit_atom, mob/user) + . = ..() var/turf/T = get_turf(hit_atom) if(T) new /obj/effect/temporary_effect/chroma(T, color_to_use) @@ -37,6 +38,7 @@ qdel(src) /obj/item/spell/chroma/on_use_cast(mob/user) + . = ..() var/new_color = input(user, "Choose the color you want your light to be.", "Color selection") as null|color if(new_color) color_to_use = new_color diff --git a/code/game/gamemodes/technomancer/spells/condensation.dm b/code/game/gamemodes/technomancer/spells/condensation.dm index 0f430f4c0c7..f1d5e9b2445 100644 --- a/code/game/gamemodes/technomancer/spells/condensation.dm +++ b/code/game/gamemodes/technomancer/spells/condensation.dm @@ -17,6 +17,7 @@ cooldown = 2 SECONDS /obj/item/spell/condensation/on_ranged_cast(atom/hit_atom, mob/user) + . = ..() if(pay_energy(200)) if(istype(hit_atom, /turf/simulated) && within_range(hit_atom)) var/turf/simulated/T = hit_atom @@ -41,4 +42,4 @@ log_and_message_admins("has wetted the floor with [src] at [T.x],[T.y],[T.z].") else if(hit_atom.reagents && !ismob(hit_atom)) //TODO: Something for the scepter hit_atom.reagents.add_reagent(/singleton/reagent/water, 60, safety = FALSE) - adjust_instability(5) \ No newline at end of file + adjust_instability(5) diff --git a/code/game/gamemodes/technomancer/spells/dispel.dm b/code/game/gamemodes/technomancer/spells/dispel.dm index 17895c6d4bb..d34d3890a21 100644 --- a/code/game/gamemodes/technomancer/spells/dispel.dm +++ b/code/game/gamemodes/technomancer/spells/dispel.dm @@ -16,5 +16,6 @@ aspect = ASPECT_BIOMED /obj/item/spell/dispel/on_ranged_cast(atom/hit_atom, mob/living/user) + . = ..() user.adjust_instability(10) - qdel(src) \ No newline at end of file + qdel(src) diff --git a/code/game/gamemodes/technomancer/spells/energy_siphon.dm b/code/game/gamemodes/technomancer/spells/energy_siphon.dm index 70613fcc86b..423fa489271 100644 --- a/code/game/gamemodes/technomancer/spells/energy_siphon.dm +++ b/code/game/gamemodes/technomancer/spells/energy_siphon.dm @@ -49,6 +49,7 @@ /obj/item/spell/energy_siphon/on_ranged_cast(atom/hit_atom, mob/user) + . = ..() if(istype(hit_atom, /atom/movable) && within_range(hit_atom, 4)) var/atom/movable/AM = hit_atom populate_siphon_list(AM) diff --git a/code/game/gamemodes/technomancer/spells/flame_tongue.dm b/code/game/gamemodes/technomancer/spells/flame_tongue.dm index ec73519aac3..8275abeeec1 100644 --- a/code/game/gamemodes/technomancer/spells/flame_tongue.dm +++ b/code/game/gamemodes/technomancer/spells/flame_tongue.dm @@ -34,9 +34,10 @@ //Needed to make the spell welder have infinite fuel. Don't worry, it uses energy instead. /obj/item/weldingtool/spell/use(var/amount = 1, var/mob/M = null, var/colourChange = TRUE) - return 1 + return TRUE /obj/item/spell/flame_tongue/on_melee_cast(atom/hit_atom, mob/living/user, def_zone) + . = ..() if(isliving(hit_atom) && user.a_intent != I_HELP) var/mob/living/L = hit_atom if(pay_energy(1000)) diff --git a/code/game/gamemodes/technomancer/spells/gambit.dm b/code/game/gamemodes/technomancer/spells/gambit.dm index 16cf330e06c..ea3a71eef10 100644 --- a/code/game/gamemodes/technomancer/spells/gambit.dm +++ b/code/game/gamemodes/technomancer/spells/gambit.dm @@ -26,6 +26,7 @@ aspect = ASPECT_UNSTABLE /obj/item/spell/gambit/on_use_cast(mob/living/carbon/human/user) + . = ..() if(pay_energy(200)) adjust_instability(3) if(check_for_scepter()) @@ -113,4 +114,4 @@ if(!potential_spells.len) potential_spells = all_technomancer_gambit_spells.Copy() - return pick(potential_spells) \ No newline at end of file + return pick(potential_spells) diff --git a/code/game/gamemodes/technomancer/spells/illusion.dm b/code/game/gamemodes/technomancer/spells/illusion.dm index 8572b0bd300..e326b11ef07 100644 --- a/code/game/gamemodes/technomancer/spells/illusion.dm +++ b/code/game/gamemodes/technomancer/spells/illusion.dm @@ -17,6 +17,7 @@ var/mob/living/simple_animal/illusion/illusion = null /obj/item/spell/illusion/on_ranged_cast(atom/hit_atom, mob/user) + . = ..() if(istype(hit_atom, /atom/movable)) var/atom/movable/AM = hit_atom if(pay_energy(100)) @@ -38,6 +39,7 @@ return 1 /obj/item/spell/illusion/on_use_cast(mob/user) + . = ..() if(illusion) var/choice = alert(user, "Would you like to have \the [illusion] speak, or do an emote?", "Illusion", "Speak","Emote","Cancel") switch(choice) diff --git a/code/game/gamemodes/technomancer/spells/insert/insert.dm b/code/game/gamemodes/technomancer/spells/insert/insert.dm index 36eb4aa2c4b..ccfad5eb5a4 100644 --- a/code/game/gamemodes/technomancer/spells/insert/insert.dm +++ b/code/game/gamemodes/technomancer/spells/insert/insert.dm @@ -49,11 +49,13 @@ qdel(src) /obj/item/spell/insert/on_melee_cast(atom/hit_atom, mob/user) + . = ..() if(istype(hit_atom, /mob/living)) var/mob/living/L = hit_atom insert(L,user) /obj/item/spell/insert/on_ranged_cast(atom/hit_atom, mob/user) + . = ..() if(istype(hit_atom, /mob/living)) var/mob/living/L = hit_atom - insert(L,user) \ No newline at end of file + insert(L,user) diff --git a/code/game/gamemodes/technomancer/spells/instability_tap.dm b/code/game/gamemodes/technomancer/spells/instability_tap.dm index 67aac5557fb..e68fdb8e6d1 100644 --- a/code/game/gamemodes/technomancer/spells/instability_tap.dm +++ b/code/game/gamemodes/technomancer/spells/instability_tap.dm @@ -19,6 +19,7 @@ set_light(3, 2, l_color = "#FA58F4") /obj/item/spell/instability_tap/on_use_cast(mob/user) + . = ..() var/amount = calculate_spell_power(5000) if(check_for_scepter()) core.give_energy(amount * 1.5) @@ -27,4 +28,4 @@ core.give_energy(amount) adjust_instability(50) playsound(src, 'sound/effects/supermatter.ogg', 75, 1) - qdel(src) \ No newline at end of file + qdel(src) diff --git a/code/game/gamemodes/technomancer/spells/mark_recall.dm b/code/game/gamemodes/technomancer/spells/mark_recall.dm index 1c78660582d..86a4ceb6f7b 100644 --- a/code/game/gamemodes/technomancer/spells/mark_recall.dm +++ b/code/game/gamemodes/technomancer/spells/mark_recall.dm @@ -27,6 +27,7 @@ aspect = ASPECT_TELE /obj/item/spell/mark/on_use_cast(mob/living/user) + . = ..() if(!allowed_to_teleport()) // Otherwise you could teleport back to the admin Z-level. to_chat(user, "You can't teleport here!") return 0 @@ -63,6 +64,7 @@ aspect = ASPECT_TELE /obj/item/spell/recall/on_use_cast(mob/living/user) + . = ..() if(pay_energy(3000)) if(!mark_spell_ref) to_chat(user, "There's no Mark!") diff --git a/code/game/gamemodes/technomancer/spells/mend_organs.dm b/code/game/gamemodes/technomancer/spells/mend_organs.dm index 1b440605ba3..b6c079b4cad 100644 --- a/code/game/gamemodes/technomancer/spells/mend_organs.dm +++ b/code/game/gamemodes/technomancer/spells/mend_organs.dm @@ -17,6 +17,7 @@ light_color = "#FF5C5C" /obj/item/spell/mend_organs/on_melee_cast(atom/hit_atom, mob/living/user, def_zone) + . = ..() if(isliving(hit_atom)) var/mob/living/L = hit_atom var/heal_power = calculate_spell_power(40) diff --git a/code/game/gamemodes/technomancer/spells/modifier/modifier.dm b/code/game/gamemodes/technomancer/spells/modifier/modifier.dm index b88cefc1ab9..5f7c621f63c 100644 --- a/code/game/gamemodes/technomancer/spells/modifier/modifier.dm +++ b/code/game/gamemodes/technomancer/spells/modifier/modifier.dm @@ -14,11 +14,13 @@ set_light(spell_light_range, spell_light_intensity, l_color = light_color) /obj/item/spell/modifier/on_melee_cast(atom/hit_atom, mob/user) + . = ..() if(isliving(hit_atom)) return on_add_modifier(hit_atom) return FALSE /obj/item/spell/modifier/on_ranged_cast(atom/hit_atom, mob/user) + . = ..() if(isliving(hit_atom)) return on_add_modifier(hit_atom) return FALSE @@ -45,4 +47,4 @@ /datum/modifier/technomancer/stop() ..() - to_chat(target, SPAN_WARNING(on_expired_text)) \ No newline at end of file + to_chat(target, SPAN_WARNING(on_expired_text)) diff --git a/code/game/gamemodes/technomancer/spells/oxygenate.dm b/code/game/gamemodes/technomancer/spells/oxygenate.dm index eafd2cd7bdb..f17955af0d1 100644 --- a/code/game/gamemodes/technomancer/spells/oxygenate.dm +++ b/code/game/gamemodes/technomancer/spells/oxygenate.dm @@ -16,6 +16,7 @@ cooldown = 30 /obj/item/spell/oxygenate/on_ranged_cast(atom/hit_atom, mob/user) + . = ..() if(!within_range(hit_atom)) return if(ishuman(hit_atom)) @@ -30,4 +31,4 @@ T.assume_gas("oxygen", 200) T.assume_gas("nitrogen", 800) playsound(src, 'sound/effects/spray.ogg', 50, 1, -3) - adjust_instability(10) \ No newline at end of file + adjust_instability(10) diff --git a/code/game/gamemodes/technomancer/spells/passwall.dm b/code/game/gamemodes/technomancer/spells/passwall.dm index 4fcb5a206b8..ae42c71184f 100644 --- a/code/game/gamemodes/technomancer/spells/passwall.dm +++ b/code/game/gamemodes/technomancer/spells/passwall.dm @@ -17,6 +17,7 @@ var/busy = 0 /obj/item/spell/passwall/on_melee_cast(atom/hit_atom, mob/user) + . = ..() if(busy) //Prevent someone from trying to get two uses of the spell from one instance. return 0 if(!allowed_to_teleport()) diff --git a/code/game/gamemodes/technomancer/spells/phase_shift.dm b/code/game/gamemodes/technomancer/spells/phase_shift.dm index 2afac756a7a..c05c02c4908 100644 --- a/code/game/gamemodes/technomancer/spells/phase_shift.dm +++ b/code/game/gamemodes/technomancer/spells/phase_shift.dm @@ -46,7 +46,7 @@ L.forceMove(get_turf(src)) L.visible_message(SPAN_WARNING("\The [src] ejects [L]!")) -/obj/effect/phase_shift/relaymove(mob/user as mob) +/obj/effect/phase_shift/relaymove(mob/user) if(user.stat) return @@ -55,6 +55,7 @@ qdel(src) /obj/item/spell/phase_shift/on_use_cast(mob/user) + . = ..() if(isturf(user.loc)) //Check if we're not already in a rift. if(pay_energy(2000)) var/obj/effect/phase_shift/PS = new(get_turf(user)) diff --git a/code/game/gamemodes/technomancer/spells/projectile/projectile.dm b/code/game/gamemodes/technomancer/spells/projectile/projectile.dm index f953b1c538a..35758fe7a92 100644 --- a/code/game/gamemodes/technomancer/spells/projectile/projectile.dm +++ b/code/game/gamemodes/technomancer/spells/projectile/projectile.dm @@ -10,9 +10,11 @@ var/fire_sound = null /obj/item/spell/projectile/on_melee_cast(atom/hit_atom, mob/living/user, def_zone) + . = ..() on_ranged_cast(hit_atom, user, hit_atom) /obj/item/spell/projectile/on_ranged_cast(atom/hit_atom, mob/living/user, atom/pb_target) + . = ..(hit_atom, user, TRUE) if(set_up(hit_atom, user)) var/obj/item/projectile/new_projectile = make_projectile(spell_projectile, user) new_projectile.old_style_target(hit_atom) @@ -20,18 +22,28 @@ log_and_message_admins("has casted [src] at \the [hit_atom].") if(fire_sound) playsound(src, fire_sound, 75, 1) - adjust_instability(instability_per_shot) - return 1 - return 0 + if(aspect != ASPECT_PSIONIC) + adjust_instability(instability_per_shot) + return TRUE + return FALSE /obj/item/spell/projectile/proc/make_projectile(obj/item/projectile/projectile_type, mob/living/user) var/obj/item/projectile/P = new projectile_type(get_turf(user)) - P.damage = calculate_spell_power(P.damage) + P.firer = user + if(aspect != ASPECT_PSIONIC) + P.damage = calculate_spell_power(P.damage) + else + if(owner.psi.get_rank() >= PSI_RANK_APEX) + if(P.damage) + P.damage *= 1.1 + P.armor_penetration *= 1.1 + P.damage_flags |= DAMAGE_FLAG_PSIONIC return P /obj/item/spell/projectile/proc/set_up(atom/hit_atom, mob/living/user) if(spell_projectile) - if(pay_energy(energy_cost_per_shot)) + /// Psionic energy payment is handled at the beginning of on_ranged_cast. + if(pay_energy(aspect == ASPECT_PSIONIC ? psi_cost : energy_cost_per_shot)) if(pre_shot_delay) var/image/target_image = image(icon = 'icons/obj/spells.dmi', loc = get_turf(hit_atom), icon_state = "target") user << target_image @@ -42,4 +54,4 @@ return TRUE return FALSE // We got dropped before the firing occurred. return TRUE // No delay, no need to check. - return FALSE \ No newline at end of file + return FALSE diff --git a/code/game/gamemodes/technomancer/spells/raise_wall.dm b/code/game/gamemodes/technomancer/spells/raise_wall.dm index 0e18ff062e0..c4a9f2b474f 100644 --- a/code/game/gamemodes/technomancer/spells/raise_wall.dm +++ b/code/game/gamemodes/technomancer/spells/raise_wall.dm @@ -16,6 +16,7 @@ aspect = ASPECT_FORCE /obj/item/spell/raise_wall/on_ranged_cast(atom/hit_atom, mob/user) + . = ..() var/turf/target_turf = isturf(hit_atom) ? hit_atom : get_turf(hit_atom) if(target_turf.density) return diff --git a/code/game/gamemodes/technomancer/spells/resurrect.dm b/code/game/gamemodes/technomancer/spells/resurrect.dm index 209b78145fc..1cb2c9ecb76 100644 --- a/code/game/gamemodes/technomancer/spells/resurrect.dm +++ b/code/game/gamemodes/technomancer/spells/resurrect.dm @@ -16,6 +16,7 @@ aspect = ASPECT_BIOMED /obj/item/spell/resurrect/on_melee_cast(atom/hit_atom, mob/living/user, def_zone) + . = ..() if(isliving(hit_atom)) var/mob/living/L = hit_atom if(L == user) diff --git a/code/game/gamemodes/technomancer/spells/shared_burden.dm b/code/game/gamemodes/technomancer/spells/shared_burden.dm index 5fb94651610..f73ae5df0a7 100644 --- a/code/game/gamemodes/technomancer/spells/shared_burden.dm +++ b/code/game/gamemodes/technomancer/spells/shared_burden.dm @@ -15,6 +15,7 @@ aspect = ASPECT_UNSTABLE /obj/item/spell/shared_burden/on_melee_cast(atom/hit_atom, mob/living/user, def_zone) + . = ..() if(ishuman(hit_atom) && within_range(hit_atom)) var/mob/living/carbon/human/H = hit_atom if(H == user) @@ -27,4 +28,4 @@ var/instability_to_drain = min(H.instability, 25) to_chat(user, "You draw instability away from \the [H] and towards you.") adjust_instability(instability_to_drain) - H.adjust_instability(-calculate_spell_power(instability_to_drain)) \ No newline at end of file + H.adjust_instability(-calculate_spell_power(instability_to_drain)) diff --git a/code/game/gamemodes/technomancer/spells/spawner/pulsar.dm b/code/game/gamemodes/technomancer/spells/spawner/pulsar.dm index cb0837fa510..b2c428e420e 100644 --- a/code/game/gamemodes/technomancer/spells/spawner/pulsar.dm +++ b/code/game/gamemodes/technomancer/spells/spawner/pulsar.dm @@ -24,6 +24,7 @@ ..() /obj/item/spell/spawner/pulsar/on_throw_cast(atom/hit_atom, mob/user) + . = ..() empulse(hit_atom, 1, 1, 1, 1, log=1) // Does something every so often. Deletes itself when pulses_remaining hits zero. @@ -40,7 +41,7 @@ /obj/effect/temporary_effect/pulse/proc/pulse_loop() set waitfor = FALSE - + while(pulses_remaining) sleep(pulse_delay) on_pulse() diff --git a/code/game/gamemodes/technomancer/spells/spawner/spawner.dm b/code/game/gamemodes/technomancer/spells/spawner/spawner.dm index 47c2a264ade..8eaf0fb2851 100644 --- a/code/game/gamemodes/technomancer/spells/spawner/spawner.dm +++ b/code/game/gamemodes/technomancer/spells/spawner/spawner.dm @@ -7,9 +7,10 @@ var/obj/effect/spawner_type = null /obj/item/spell/spawner/on_ranged_cast(atom/hit_atom, mob/user) + . = ..() var/turf/T = get_turf(hit_atom) if(T) new spawner_type(T) to_chat(user, "You shift \the [src] onto \the [T].") log_and_message_admins("has casted [src] at [T.x],[T.y],[T.z].") - qdel(src) \ No newline at end of file + qdel(src) diff --git a/code/game/gamemodes/technomancer/spells/summon/summon.dm b/code/game/gamemodes/technomancer/spells/summon/summon.dm index bd86a735a57..6caba308b56 100644 --- a/code/game/gamemodes/technomancer/spells/summon/summon.dm +++ b/code/game/gamemodes/technomancer/spells/summon/summon.dm @@ -12,6 +12,7 @@ var/instability_cost = 0 /obj/item/spell/summon/on_ranged_cast(atom/hit_atom, mob/living/user) + . = ..() var/turf/T = get_turf(hit_atom) if(summoned_mob_type && core.summoned_mobs.len < core.max_summons && within_range(hit_atom) && pay_energy(energy_cost)) var/obj/effect/E = new(T) @@ -30,6 +31,7 @@ user.adjust_instability(instability_cost) /obj/item/spell/summon/on_use_cast(mob/living/user) + . = ..() if(summon_options.len) var/choice = input(user, "Choose a creature to kidnap from somewhere!", "Summon") as null|anything in summon_options if(choice) @@ -37,4 +39,4 @@ // Called when a new mob is summoned, override for special behaviour. /obj/item/spell/summon/proc/on_summon(var/mob/living/summoned) - return \ No newline at end of file + return diff --git a/code/game/gamemodes/technomancer/spells/targeting_matrix.dm b/code/game/gamemodes/technomancer/spells/targeting_matrix.dm index 4ead252ceb1..db39d54e577 100644 --- a/code/game/gamemodes/technomancer/spells/targeting_matrix.dm +++ b/code/game/gamemodes/technomancer/spells/targeting_matrix.dm @@ -15,6 +15,7 @@ aspect = ASPECT_FORCE //idk? /obj/item/spell/targeting_matrix/on_ranged_cast(atom/hit_atom, mob/user) + . = ..() var/turf/T = get_turf(hit_atom) if(T) var/mob/living/chosen_target = targeting_assist(T,5) //The person who's about to get attacked. diff --git a/code/game/gamemodes/technomancer/spells/track.dm b/code/game/gamemodes/technomancer/spells/track.dm index ac284da18bd..d84f1944431 100644 --- a/code/game/gamemodes/technomancer/spells/track.dm +++ b/code/game/gamemodes/technomancer/spells/track.dm @@ -27,6 +27,7 @@ var/list/technomancer_belongings = list() return ..() /obj/item/spell/track/on_use_cast(mob/user) + . = ..() if(tracking) tracking = 0 to_chat(user, "You stop tracking for \the [tracked]'s whereabouts.") @@ -75,4 +76,4 @@ var/list/technomancer_belongings = list() icon_state = "track_far" spawn(5) - .() \ No newline at end of file + .() diff --git a/code/game/gamemodes/technomancer/spells/warp_strike.dm b/code/game/gamemodes/technomancer/spells/warp_strike.dm index a717a4b0a42..88436f4a627 100644 --- a/code/game/gamemodes/technomancer/spells/warp_strike.dm +++ b/code/game/gamemodes/technomancer/spells/warp_strike.dm @@ -14,6 +14,7 @@ aspect = ASPECT_TELE /obj/item/spell/warp_strike/on_ranged_cast(atom/hit_atom, mob/user) + . = ..() var/turf/T = get_turf(hit_atom) if(T) if(!within_range(T)) diff --git a/code/game/objects/effects/effect_system.dm b/code/game/objects/effects/effect_system.dm index c337fe24176..cd17484cea7 100644 --- a/code/game/objects/effects/effect_system.dm +++ b/code/game/objects/effects/effect_system.dm @@ -6,7 +6,7 @@ would spawn and follow the beaker, even if it is carried or thrown. */ -/obj/effect/effect +/obj/effect name = "effect" icon = 'icons/effects/effects.dmi' mouse_opacity = MOUSE_OPACITY_TRANSPARENT diff --git a/code/game/objects/items/weapons/cigs_lighters.dm b/code/game/objects/items/weapons/cigs_lighters.dm index 1b47579cd18..32f703e74cb 100644 --- a/code/game/objects/items/weapons/cigs_lighters.dm +++ b/code/game/objects/items/weapons/cigs_lighters.dm @@ -105,7 +105,7 @@ CIGARETTE PACKETS ARE IN FANCY.DM /obj/item/flame/match/proc/light() lit = TRUE - damtype = "burn" + damtype = "fire" icon_state = "match_lit" item_state = "match_lit" if(ismob(loc)) diff --git a/code/game/objects/structures/full_window_frame.dm b/code/game/objects/structures/full_window_frame.dm index 38446ceb9f8..c460b87fe5f 100644 --- a/code/game/objects/structures/full_window_frame.dm +++ b/code/game/objects/structures/full_window_frame.dm @@ -163,6 +163,12 @@ else to_chat(user, SPAN_WARNING("You need at least [glass_needed] sheets of [MATERIAL_GLASS_REINFORCED_PHORON] to finished the window.")) +/obj/structure/window_frame/hitby(atom/movable/AM, speed) + . = ..() + var/obj/structure/window/W = locate() in get_turf(src) + if(istype(W)) + W.hitby(AM) + /obj/structure/window_frame/unanchored // Used during in-game construction. should_check_mapload = FALSE // No glass. anchored = FALSE diff --git a/code/modules/admin/admin.dm b/code/modules/admin/admin.dm index abe27409bb8..9a13d40176a 100644 --- a/code/modules/admin/admin.dm +++ b/code/modules/admin/admin.dm @@ -106,18 +106,16 @@ var/global/enabled_spooking = 0 var/mob/living/psyker = M if(psyker.psi) body += "
Remove psionics.

" - body += "Trigger latencies.
" body += "" - for(var/faculty in list(PSI_COERCION, PSI_PSYCHOKINESIS, PSI_REDACTION, PSI_ENERGISTICS)) - var/datum/psionic_faculty/faculty_decl = SSpsi.get_faculty(faculty) - var/faculty_rank = psyker.psi ? psyker.psi.get_rank(faculty) : 0 - body += "" - for(var/i = 1 to LAZYLEN(psychic_ranks_to_strings)) - var/psi_title = psychic_ranks_to_strings[i] - if(i == faculty_rank) - psi_title = "[psi_title]" - body += "" - body += "" + for(var/psi_rank in list(PSI_RANK_SENSITIVE, PSI_RANK_HARMONIOUS, PSI_RANK_APEX, PSI_RANK_LIMITLESS)) + var/owner_rank = psyker.psi ? psyker.psi.get_rank() : 0 + var/psi_title = psychic_ranks_to_strings[psi_rank] + if(psi_rank == owner_rank) + psi_title = "[psi_title]" + if(psi_rank != PSI_RANK_LIMITLESS) + body += "[psi_title]" + else + body += "[psi_title]" body += "
[faculty_decl.name][psi_title]
" if (M.client) diff --git a/code/modules/admin/topic.dm b/code/modules/admin/topic.dm index 8516f945ae0..b80f43e5a0a 100644 --- a/code/modules/admin/topic.dm +++ b/code/modules/admin/topic.dm @@ -432,8 +432,8 @@ else if(href_list["trigger_psi_latencies"]) var/datum/psi_complexus/psi = locate(href_list["trigger_psi_latencies"]) - log_and_message_admins("triggered psi latencies for [key_name(psi.owner)].") - psi.check_latency_trigger(100, "outside intervention", redactive = TRUE) + log_and_message_admins("triggered psionics for [key_name(psi.owner)].") + psi.check_psionic_trigger(100, "outside intervention", redactive = TRUE) else if(href_list["monkeyone"]) if(!check_rights(R_SPAWN)) return diff --git a/code/modules/admin/verbs/buildmode.dm b/code/modules/admin/verbs/buildmode.dm index 78fe6034eb9..a0015ebc3e0 100644 --- a/code/modules/admin/verbs/buildmode.dm +++ b/code/modules/admin/verbs/buildmode.dm @@ -42,10 +42,11 @@ /obj/effect/bmode//Cleaning up the tree a bit density = 1 anchored = 1 - layer = SCREEN_LAYER + layer = SCREEN_LAYER + 1 dir = NORTH icon = 'icons/misc/buildmode.dmi' - var/obj/effect/bmode/buildholder/master = null + mouse_opacity = MOUSE_OPACITY_OPAQUE + var/obj/effect/bmode/buildholder/master /obj/effect/bmode/Destroy() if(master && master.cl) diff --git a/code/modules/client/preference_setup/general/07_psionics.dm b/code/modules/client/preference_setup/general/07_psionics.dm new file mode 100644 index 00000000000..161a8f0cc61 --- /dev/null +++ b/code/modules/client/preference_setup/general/07_psionics.dm @@ -0,0 +1,135 @@ +/datum/category_item/player_setup_item/general/psionics + name = "Psionics" + sort_order = 7 + +/datum/category_item/player_setup_item/general/psionics/load_character(var/savefile/S) + S["psionics"] >> pref.psionics + +/datum/category_item/player_setup_item/general/psionics/save_character(var/savefile/S) + var/list/psionics = pref.psionics + for(var/psi in psionics) + var/singleton/psionic_power/P = GET_SINGLETON(text2path(psi)) + if(!istype(P)) + continue + psionics |= P.type + S["psionics"] << json_encode(psionics) + +/datum/category_item/player_setup_item/general/psionics/gather_load_query() + return list( + "ss13_characters" = list( + "vars" = list( + "psionics" = "psionics", + ), + "args" = list("id") + ) + ) + +/datum/category_item/player_setup_item/general/psionics/gather_load_parameters() + return list("id" = pref.current_character) + +/datum/category_item/player_setup_item/general/psionics/gather_save_query() + return list( + "ss13_characters" = list( + "psionics", + "id" = 1 + ) + ) + +/datum/category_item/player_setup_item/general/psionics/gather_save_parameters() + var/list/sanitized_psionics = list() + for(var/S in pref.psionics) + var/singleton/psionic_power/P = GET_SINGLETON(text2path(S)) + if(!istype(P)) + continue + sanitized_psionics |= "[P.type]" + return list( + "psionics" = json_encode(sanitized_psionics), + "char_id" = pref.current_character, + "ckey" = PREF_CLIENT_CKEY + ) + +/datum/category_item/player_setup_item/general/psionics/load_special(savefile/S) + var/before = pref.psionics + try + pref.psionics = json_decode(pref.psionics) + catch (var/exception/e) + log_debug("PSIONICS: Caught [e]. Initial value: [before]") + pref.psionics = list() + +/datum/category_item/player_setup_item/general/psionics/sanitize_character(var/sql_load = 0) + var/datum/species/mob_species = all_species[pref.species] + if(length(pref.psionics) && !mob_species.has_psionics) + to_chat(pref.client, SPAN_WARNING("This species does not have psionics! Resetting...")) + pref.psionics = list() + return + var/list/bought_psionic_powers = list() + for(var/S in pref.psionics) + var/singleton/psionic_power/P = GET_SINGLETON(text2path(S)) + if(istype(P)) + if(!(P.ability_flags & PSI_FLAG_CANON)) + pref.psionics -= S + to_chat(pref.client, SPAN_WARNING("[P.name] is an invalid psionic!")) + continue + bought_psionic_powers |= P + else + to_chat(pref.client, SPAN_WARNING("[S] is an invalid psionic!")) + pref.psionics -= S + continue + if(length(bought_psionic_powers) > mob_species.character_creation_psi_points) + to_chat(pref.client, SPAN_WARNING("You have more psionics than possible! Resetting...")) + pref.psionics = list() + +/datum/category_item/player_setup_item/general/psionics/content(var/mob/user) + var/datum/species/mob_species = all_species[pref.species] + if(!(mob_species.has_psionics)) + return + var/list/bought_psionic_powers = list() + for(var/S in pref.psionics) + var/singleton/psionic_power/P = GET_SINGLETON(text2path(S)) + if(istype(P)) + bought_psionic_powers |= P + + var/list/dat = list( + "Psionics:
" + ) + for(var/singleton/psionic_power/P in bought_psionic_powers) + dat += "- [P.name] -
" + dat += "Add Psionic Power
" + . = dat.Join() + +/datum/category_item/player_setup_item/general/psionics/OnTopic(var/href,var/list/href_list, var/mob/user) + if(href_list["remove_psi_power"]) + var/power_to_remove = href_list["remove_psi_power"] + if(power_to_remove && (power_to_remove in pref.psionics)) + pref.psionics -= power_to_remove + return TOPIC_REFRESH + + else if(href_list["add_psi_power"]) + var/datum/species/mob_species = all_species[pref.species] + var/total_psi_points = mob_species.character_creation_psi_points + var/list/available_psionics = list() + var/list/psionic_map = list() + var/list/bought_psionic_powers = list() + + for(var/S in pref.psionics) + var/singleton/psionic_power/P = GET_SINGLETON(text2path(S)) + if(istype(P)) + bought_psionic_powers |= P + total_psi_points = max(0, total_psi_points - P.point_cost) + + for(var/singleton/psionic_power/P in GET_SINGLETON_SUBTYPE_LIST(/singleton/psionic_power)) + if((P.ability_flags & PSI_FLAG_CANON) && (P.point_cost <= total_psi_points) && !(P in bought_psionic_powers)) + available_psionics |= "[P.name]" + psionic_map[P.name] = P.type + + if(!length(available_psionics)) + to_chat(user, SPAN_WARNING("You ran out of points!")) + return + + var/new_power = input(user, "Choose a psionic power to add.", "Psionics") as null|anything in available_psionics + if(new_power) + var/singleton/psionic_power/P = GET_SINGLETON(psionic_map[new_power]) + if(istype(P)) + pref.psionics += "[P.type]" + return TOPIC_REFRESH + diff --git a/code/modules/client/preference_setup/loadout/loadout_augments.dm b/code/modules/client/preference_setup/loadout/loadout_augments.dm index 42c5c368fe5..0a0e8754d66 100644 --- a/code/modules/client/preference_setup/loadout/loadout_augments.dm +++ b/code/modules/client/preference_setup/loadout/loadout_augments.dm @@ -144,8 +144,7 @@ display_name = "psionic receiver" description = "An augment installed into the head that functions as a surrogate for a missing zona bovinae, also functioning as a filter for the psionically-challenged." path = /obj/item/organ/internal/augment/psi - whitelisted = list(SPECIES_HUMAN, SPECIES_HUMAN_OFFWORLD, SPECIES_TAJARA, SPECIES_TAJARA_ZHAN, SPECIES_TAJARA_MSAI, SPECIES_VAURCA_WORKER, SPECIES_VAURCA_WARRIOR, SPECIES_VAURCA_BULWARK, SPECIES_VAURCA_BREEDER, SPECIES_IPC, SPECIES_IPC_G1, SPECIES_IPC_G2, SPECIES_IPC_XION, SPECIES_IPC_ZENGHU, SPECIES_IPC_BISHOP, SPECIES_IPC_SHELL) - + whitelisted = list(SPECIES_HUMAN, SPECIES_HUMAN_OFFWORLD, SPECIES_TAJARA, SPECIES_TAJARA_ZHAN, SPECIES_TAJARA_MSAI, SPECIES_UNATHI, SPECIES_VAURCA_WORKER, SPECIES_VAURCA_WARRIOR, SPECIES_VAURCA_BULWARK, SPECIES_VAURCA_BREEDER, SPECIES_IPC, SPECIES_IPC_G1, SPECIES_IPC_G2, SPECIES_IPC_XION, SPECIES_IPC_ZENGHU, SPECIES_IPC_BISHOP, SPECIES_IPC_SHELL) /datum/gear/augment/memory_inhibitor display_name = "memory inhibitor" description = "A Zeng Hu implant that allows one to have control over their memories, allowing you to set a timer and remove any memories developed within it." diff --git a/code/modules/client/preferences.dm b/code/modules/client/preferences.dm index dcfcaa18d2b..78b875f81c8 100644 --- a/code/modules/client/preferences.dm +++ b/code/modules/client/preferences.dm @@ -99,6 +99,8 @@ var/list/preferences_datums = list() var/culture var/origin + var/list/psionics = list() + var/list/char_render_holders //Should only be a key-value list of north/south/east/west = obj/screen. var/static/list/preview_screen_locs = list( "1" = "character_preview_map:1,5:-12", @@ -471,6 +473,12 @@ var/list/preferences_datums = list() character.headset_choice = headset_choice + if(length(psionics) && character.client) + for(var/power in psionics) + var/singleton/psionic_power/P = GET_SINGLETON(text2path(power)) + if(istype(P) && (P.ability_flags & PSI_FLAG_CANON)) + P.apply(character) + if(icon_updates) character.force_update_limbs() character.update_mutations(0) @@ -638,6 +646,7 @@ var/list/preferences_datums = list() ccia_actions = list() disabilities = list() + psionics = list() economic_status = ECONOMICALLY_AVERAGE diff --git a/code/modules/mob/living/carbon/breathe.dm b/code/modules/mob/living/carbon/breathe.dm index e47e9ca2a9d..13362a331f4 100644 --- a/code/modules/mob/living/carbon/breathe.dm +++ b/code/modules/mob/living/carbon/breathe.dm @@ -23,12 +23,9 @@ /mob/living/carbon/proc/breathe(var/volume_needed = BREATH_VOLUME) if(species && (species.flags & NO_BREATHE)) return - var/datum/changeling/changeling = get_antag_datum(MODE_CHANGELING) - if(changeling?.space_adapted) + if(HAS_TRAIT(src, TRAIT_PRESSURE_IMMUNITY)) return - if(changeling?.no_breathing) - return - + volume_needed *= (species?.breath_vol_mul || 1) var/datum/gas_mixture/breath = null diff --git a/code/modules/mob/living/carbon/human/human.dm b/code/modules/mob/living/carbon/human/human.dm index b7034b5bf72..96f8d5d100b 100644 --- a/code/modules/mob/living/carbon/human/human.dm +++ b/code/modules/mob/living/carbon/human/human.dm @@ -1485,6 +1485,9 @@ species.set_default_tail(src) + if(species.psi_deaf || HAS_FLAG(species.flags, IS_MECHANICAL) || HAS_FLAG(species.flags, NO_SCAN)) + ADD_TRAIT(src, TRAIT_PSIONICALLY_DEAF, INNATE_TRAIT) + if(client) client.init_verbs() diff --git a/code/modules/mob/living/carbon/human/human_helpers.dm b/code/modules/mob/living/carbon/human/human_helpers.dm index 6460ce4fbb8..e2e6b129fc9 100644 --- a/code/modules/mob/living/carbon/human/human_helpers.dm +++ b/code/modules/mob/living/carbon/human/human_helpers.dm @@ -185,7 +185,7 @@ return O -/mob/living/carbon/human/proc/awaken_psi_basic(var/source, var/allow_latency = TRUE) +/mob/living/carbon/human/proc/awaken_psi_basic(var/source) var/static/list/psi_operancy_messages = list( "There's something in your skull!", "Something is eating your thoughts!", @@ -197,13 +197,10 @@ ) to_chat(src, SPAN_DANGER("An indescribable, brain-tearing sound hisses from [source], and you collapse in a seizure!")) seizure() - var/new_latencies = rand(2,4) - var/list/faculties = list(PSI_COERCION, PSI_REDACTION, PSI_ENERGISTICS, PSI_PSYCHOKINESIS) - for(var/i = 1 to new_latencies) - custom_pain(SPAN_DANGER("[pick(psi_operancy_messages)]"), 25) - set_psi_rank(pick_n_take(faculties), allow_latency ? PSI_RANK_LATENT : PSI_RANK_OPERANT) // if set to latent, it spikes anywhere from OPERANT to PARAMOUNT - sleep(30) - addtimer(CALLBACK(psi, TYPE_PROC_REF(/datum/psi_complexus, check_latency_trigger), 100, source, TRUE), 4.5 SECONDS) + custom_pain(SPAN_DANGER(FONT_LARGE("[pick(psi_operancy_messages)]")), 25) + set_psi_rank(PSI_RANK_HARMONIOUS) + sleep(30) + addtimer(CALLBACK(psi, TYPE_PROC_REF(/datum/psi_complexus, check_psionic_trigger), 100, source, TRUE), 4.5 SECONDS) /mob/living/carbon/human/get_resist_power() return species.resist_mod @@ -349,8 +346,7 @@ /mob/living/carbon/human/proc/pressure_resistant() if(HAS_FLAG(mutations, COLD_RESISTANCE)) return TRUE - var/datum/changeling/changeling = get_antag_datum(MODE_CHANGELING) - if(changeling?.space_adapted) + if(HAS_TRAIT(src, TRAIT_PRESSURE_IMMUNITY)) return TRUE return FALSE @@ -418,7 +414,7 @@ /mob/living/carbon/human/proc/get_tail_accessory() var/obj/item/organ/external/groin/G = organs_by_name[BP_GROIN] - if(!G) + if(!istype(G)) return if(!G.tail_storage) return diff --git a/code/modules/mob/living/carbon/human/human_powers.dm b/code/modules/mob/living/carbon/human/human_powers.dm index 7f2f292ed66..b8578bec788 100644 --- a/code/modules/mob/living/carbon/human/human_powers.dm +++ b/code/modules/mob/living/carbon/human/human_powers.dm @@ -239,14 +239,16 @@ // Simple mobs cannot use Skrellepathy -/mob/proc/can_commune() +/mob/proc/has_psionics() return FALSE -/mob/living/carbon/human/can_commune() +/mob/living/carbon/human/has_psionics() + if(HAS_TRAIT(src, TRAIT_PSIONICALLY_DEAF)) + return FALSE if(psi) return TRUE else - return species ? species.can_commune() : FALSE + return species ? species.has_psionics() : FALSE /mob/living/carbon/human/proc/commune() set category = "Abilities" @@ -322,14 +324,14 @@ to_chat(M,"[src] telepathically says to [target]: [text]") var/mob/living/carbon/human/H = target - if (target.can_commune()) + if (target.has_psionics()) to_chat(H,"You instinctively sense [src] sending their thoughts into your mind, hearing: [text]") else if(prob(25) && (target.mind && target.mind.assigned_role=="Chaplain")) to_chat(H,"You sense [src]'s thoughts enter your mind, whispering quietly: [text]") else to_chat(H,"You feel pressure behind your eyes as alien thoughts enter your mind: [text]") if(istype(H)) - if (target.can_commune()) + if (target.has_psionics()) return if(prob(10) && !(H.species.flags & NO_BLOOD)) to_chat(H,"Your nose begins to bleed...") diff --git a/code/modules/mob/living/carbon/human/species/species.dm b/code/modules/mob/living/carbon/human/species/species.dm index e5337a801f2..300bce9f707 100644 --- a/code/modules/mob/living/carbon/human/species/species.dm +++ b/code/modules/mob/living/carbon/human/species/species.dm @@ -295,6 +295,13 @@ var/use_alt_hair_layer = FALSE + /// Species psionics. FALSE for no psionics. Otherwise, set to the PSI_RANK define you want. + var/has_psionics = FALSE + /// Number of psi points in character creation. + var/character_creation_psi_points = 0 + /// Is this species psionically deaf? + var/psi_deaf = FALSE + /datum/species/proc/get_eyes(var/mob/living/carbon/human/H) return @@ -470,6 +477,8 @@ if(!H.client || !H.client.prefs || !H.client.prefs.gender) H.gender = pick(default_genders) H.pronouns = H.gender + if(has_psionics) + H.set_psi_rank(has_psionics) /datum/species/proc/handle_death(var/mob/living/carbon/human/H, var/gibbed = 0) //Handles any species-specific death events (such as dionaea nymph spawns). return @@ -739,11 +748,8 @@ /datum/species/proc/get_digestion_product() return /singleton/reagent/nutriment -/datum/species/proc/can_commune() - return FALSE - -/datum/species/proc/has_psi_potential() - return TRUE +/datum/species/proc/has_psionics() + return has_psionics /datum/species/proc/handle_despawn() return diff --git a/code/modules/mob/living/carbon/human/species/station/diona/diona.dm b/code/modules/mob/living/carbon/human/species/station/diona/diona.dm index 742abdd0d1a..7060dab22ce 100644 --- a/code/modules/mob/living/carbon/human/species/station/diona/diona.dm +++ b/code/modules/mob/living/carbon/human/species/station/diona/diona.dm @@ -129,6 +129,7 @@ ) alterable_internal_organs = list() + psi_deaf = TRUE /datum/species/diona/can_understand(var/mob/other) var/mob/living/carbon/alien/diona/D = other @@ -184,9 +185,6 @@ if(SB) SB.handle_item_insertion(new /obj/item/device/flashlight/survival(get_turf(H)), TRUE) -/datum/species/diona/has_psi_potential() - return FALSE - /datum/species/diona/is_naturally_insulated() return TRUE diff --git a/code/modules/mob/living/carbon/human/species/station/golem.dm b/code/modules/mob/living/carbon/human/species/station/golem.dm index 02461d5f097..3e71859230f 100644 --- a/code/modules/mob/living/carbon/human/species/station/golem.dm +++ b/code/modules/mob/living/carbon/human/species/station/golem.dm @@ -98,6 +98,8 @@ var/global/list/golem_types = list(SPECIES_GOLEM_COAL, hud_type = /datum/hud_data/construct + psi_deaf = TRUE + var/turn_into_materials = TRUE //the golem will turn into materials upon its death var/golem_designation = "Coal" //used in the creation of the name @@ -124,9 +126,6 @@ var/global/list/golem_types = list(SPECIES_GOLEM_COAL, return TRUE return FALSE -/datum/species/golem/has_psi_potential() - return FALSE - /datum/species/golem/iron name = SPECIES_GOLEM_IRON name_plural = "iron golems" diff --git a/code/modules/mob/living/carbon/human/species/station/ipc/ipc.dm b/code/modules/mob/living/carbon/human/species/station/ipc/ipc.dm index 4ba63ef5beb..405f16c0bed 100644 --- a/code/modules/mob/living/carbon/human/species/station/ipc/ipc.dm +++ b/code/modules/mob/living/carbon/human/species/station/ipc/ipc.dm @@ -143,6 +143,7 @@ var/move_charge_factor = 1 use_alt_hair_layer = TRUE + psi_deaf = TRUE /datum/species/machine/handle_post_spawn(var/mob/living/carbon/human/H) . = ..() @@ -373,9 +374,6 @@ . = ..() check_tag(H, H.client) -/datum/species/machine/has_psi_potential() - return FALSE - /datum/species/machine/handle_death_check(var/mob/living/carbon/human/H) if(H.get_total_health() <= config.health_threshold_dead) return TRUE diff --git a/code/modules/mob/living/carbon/human/species/station/skrell/skrell.dm b/code/modules/mob/living/carbon/human/species/station/skrell/skrell.dm index 6c0ef461c5e..5a7222d60e9 100644 --- a/code/modules/mob/living/carbon/human/species/station/skrell/skrell.dm +++ b/code/modules/mob/living/carbon/human/species/station/skrell/skrell.dm @@ -107,6 +107,9 @@ alterable_internal_organs = list(BP_HEART, BP_EYES, BP_LUNGS, BP_LIVER, BP_KIDNEYS, BP_STOMACH) + has_psionics = PSI_RANK_SENSITIVE + character_creation_psi_points = 4 + /datum/species/skrell/handle_trail(var/mob/living/carbon/human/H, var/turf/T) var/list/trail_info = ..() if(!length(trail_info) && !H.shoes) @@ -117,10 +120,6 @@ return trail_info -/datum/species/skrell/handle_post_spawn(mob/living/carbon/human/H) - ..() - H.set_psi_rank(PSI_COERCION, PSI_RANK_OPERANT) - /datum/species/skrell/handle_strip(var/mob/user, var/mob/living/carbon/human/H, var/action) switch(action) if("headtail") @@ -142,6 +141,3 @@ /datum/species/skrell/can_breathe_water() return TRUE - -/datum/species/skrell/can_commune() - return TRUE diff --git a/code/modules/mob/living/carbon/human/species/station/vaurca/vaurca.dm b/code/modules/mob/living/carbon/human/species/station/vaurca/vaurca.dm index 48bfae9bc7b..4c3a815a5a1 100644 --- a/code/modules/mob/living/carbon/human/species/station/vaurca/vaurca.dm +++ b/code/modules/mob/living/carbon/human/species/station/vaurca/vaurca.dm @@ -141,6 +141,7 @@ alterable_internal_organs = list(BP_HEART, BP_EYES, BP_LUNGS, BP_STOMACH, BP_APPENDIX) + psi_deaf = TRUE /datum/species/bug/before_equip(var/mob/living/carbon/human/H) . = ..() @@ -158,9 +159,6 @@ H.gender = NEUTER return ..() -/datum/species/bug/has_psi_potential() - return FALSE - /datum/species/bug/is_naturally_insulated() return TRUE @@ -168,3 +166,4 @@ if(I.w_class <= ITEMSIZE_SMALL) return TRUE return FALSE + diff --git a/code/modules/mob/living/living_defense.dm b/code/modules/mob/living/living_defense.dm index 535cf791c7e..bcb76210d1d 100644 --- a/code/modules/mob/living/living_defense.dm +++ b/code/modules/mob/living/living_defense.dm @@ -18,6 +18,10 @@ var/natural_armor = GetComponent(/datum/component/armor) if(natural_armor) . += natural_armor + if(psi) + var/armor = psi.GetComponent(/datum/component/armor/psionic) + if(armor) + . += armor /mob/living/bullet_act(var/obj/item/projectile/P, var/def_zone, var/used_weapon = null) @@ -247,7 +251,7 @@ // End BS12 momentum-transfer code. -/mob/living/attack_generic(var/mob/user, var/damage, var/attack_message, var/armor_penetration, var/attack_flags) +/mob/living/attack_generic(var/mob/user, var/damage, var/attack_message, var/armor_penetration, var/attack_flags, var/damage_type = DAMAGE_BRUTE) if(!damage) return @@ -257,7 +261,7 @@ src.visible_message("[user] has [attack_message] [src]!") user.do_attack_animation(src) - apply_damage(damage, DAMAGE_BRUTE, user.zone_sel?.selecting, armor_pen = armor_penetration, damage_flags = attack_flags) + apply_damage(damage, damage_type, user.zone_sel?.selecting, armor_pen = armor_penetration, damage_flags = attack_flags) updatehealth() return TRUE diff --git a/code/modules/mob/living/simple_animal/borer/borer_powers.dm b/code/modules/mob/living/simple_animal/borer/borer_powers.dm index 128b46d1018..d435234cc0a 100644 --- a/code/modules/mob/living/simple_animal/borer/borer_powers.dm +++ b/code/modules/mob/living/simple_animal/borer/borer_powers.dm @@ -453,8 +453,8 @@ if(chemicals < 150) to_chat(src, SPAN_WARNING("You don't have enough chemicals!")) return - if(!host.species.has_psi_potential()) - to_chat(src, SPAN_WARNING("\The [host] lacks a zona bovinidae to psionically enlighten! How disturbing...")) + if(host.species.psi_deaf) + to_chat(src, SPAN_WARNING("\The [host] lacks a zona bovinae to psionically enlighten! How disturbing...")) if(host.psi) to_chat(src, SPAN_WARNING("Your host is already psionically active!")) return @@ -481,13 +481,14 @@ return to_chat(src, SPAN_NOTICE("You succeed in interfacing with the host's zona bovinae, this will be a painful process for them.")) - host.awaken_psi_basic("something in your head", FALSE) + host.awaken_psi_basic("something in your head") + host.psi.psi_points = 3 /// You don't get a lot at the start. host.add_language(LANGUAGE_TCB) // if we don't have TCB, give them TCB | this allows monkey borers to RP -/mob/living/simple_animal/borer/verb/advance_faculty() +/mob/living/simple_animal/borer/verb/advance_psionics() set category = "Abilities" - set name = "Advance Psionic Faculty (75)" - set desc = "Advance one of your host's psionic faculties by one step." + set name = "Advance Psionics (75)" + set desc = "Advance your host's psionic capability by one step." if(!host) to_chat(src, SPAN_NOTICE("You are not inside a host body.")) @@ -507,18 +508,8 @@ to_chat(src, SPAN_WARNING("\The [host]'s mind is shielded against your powers.")) return - var/list/faculties = list(capitalize(PSI_COERCION), capitalize(PSI_REDACTION), capitalize(PSI_ENERGISTICS), capitalize(PSI_PSYCHOKINESIS)) - var/selected_faculty = input(src, "Choose a faculty to upgrade.") as null|anything in faculties - if(!selected_faculty) - return - selected_faculty = lowertext(selected_faculty) - var/max_rank = islesserform(host) ? PSI_RANK_OPERANT : PSI_RANK_MASTER - if(host.psi.get_rank(selected_faculty) >= max_rank) - to_chat(src, SPAN_NOTICE("This faculty has already been pushed to the max potential you can achieve!")) - return - - var/faculty_time = 10 SECONDS - if(!start_ability(host, faculty_time)) + var/upgrade_time = 10 SECONDS + if(!start_ability(host, upgrade_time)) to_chat(src, SPAN_WARNING("You're busy doing something else, complete that task first.")) return @@ -526,19 +517,16 @@ to_chat(src, SPAN_NOTICE("You probe your tendrils deep within your host's zona bovinae, seeking to upgrade their abilities.")) to_chat(host, SPAN_WARNING("You feel a burning, tingling sensation at the back of your head...")) - addtimer(CALLBACK(src, PROC_REF(faculty_upgrade), selected_faculty), faculty_time) + addtimer(CALLBACK(src, PROC_REF(upgrade_rank)), upgrade_time) -/mob/living/simple_animal/borer/proc/faculty_upgrade(var/selected_faculty) +/mob/living/simple_animal/borer/proc/upgrade_rank() if(!host) return - var/host_psi_rank = host.psi.get_rank(selected_faculty) - var/next_rank = host_psi_rank > PSI_RANK_BLUNT ? host_psi_rank + 1 : PSI_RANK_OPERANT - host.psi.set_rank(selected_faculty, next_rank) - host.psi.update(TRUE) - to_chat(src, SPAN_NOTICE("You successfully manage to upgrade your host to [psychic_ranks_to_strings[host.psi.ranks[selected_faculty]]] [selected_faculty].")) + host.psi.psi_points++ + to_chat(src, SPAN_NOTICE("You successfully manage to expand your living host's control over their Zona Bovinae. They know have an extra psionic point to spend.")) to_chat(host, SPAN_GOOD("A breeze of fresh air washes over your mind, you feel powerful!")) - to_chat(host, SPAN_NOTICE("You have been psionically enlightened. You are now a [psychic_ranks_to_strings[host.psi.ranks[selected_faculty]]] in the [selected_faculty] faculty.")) + to_chat(host, SPAN_NOTICE("Your control over the Nlom has expanded. You now have an extra psionic point to spend in the Point Shop.")) /mob/living/simple_animal/borer/verb/host_health_scan() set category = "Abilities" diff --git a/code/modules/mob/living/simple_animal/hostile/hostile.dm b/code/modules/mob/living/simple_animal/hostile/hostile.dm index 5bd4571af3b..0f348947355 100644 --- a/code/modules/mob/living/simple_animal/hostile/hostile.dm +++ b/code/modules/mob/living/simple_animal/hostile/hostile.dm @@ -214,7 +214,7 @@ var/atom/target if(isliving(target_mob)) var/mob/living/L = target_mob - on_attack_mob(L, L.attack_generic(src, rand(melee_damage_lower, melee_damage_upper), attacktext, armor_penetration, attack_flags)) + on_attack_mob(L, L.attack_generic(src, rand(melee_damage_lower, melee_damage_upper), attacktext, armor_penetration, attack_flags, damage_type)) target = L else if(istype(target_mob, /obj/machinery/bot)) var/obj/machinery/bot/B = target_mob diff --git a/code/modules/mob/living/simple_animal/simple_animal.dm b/code/modules/mob/living/simple_animal/simple_animal.dm index 34ef0df031f..98eea889cdf 100644 --- a/code/modules/mob/living/simple_animal/simple_animal.dm +++ b/code/modules/mob/living/simple_animal/simple_animal.dm @@ -14,6 +14,9 @@ mob_swap_flags = MONKEY|SLIME|SIMPLE_ANIMAL mob_push_flags = MONKEY|SLIME|SIMPLE_ANIMAL + /// The type of damage this mob deals. + var/damage_type = DAMAGE_BRUTE + var/show_stat_health = 1 //does the percentage health show in the stat panel for the mob var/icon_living = "" @@ -538,7 +541,7 @@ simple_harm_attack(user) /mob/living/simple_animal/proc/simple_harm_attack(var/mob/living/user) - apply_damage(harm_intent_damage, DAMAGE_BRUTE, used_weapon = "Attack by [user.name]") + apply_damage(harm_intent_damage, damage_type, used_weapon = "Attack by [user.name]") user.visible_message(SPAN_WARNING("\The [user] [response_harm] \the [src]!"), SPAN_WARNING("You [response_harm] \the [src]!")) user.do_attack_animation(src, FIST_ATTACK_ANIMATION) poke(TRUE) diff --git a/code/modules/mob/mob_grab.dm b/code/modules/mob/mob_grab.dm index f0d62fd2f22..13f828fd2de 100644 --- a/code/modules/mob/mob_grab.dm +++ b/code/modules/mob/mob_grab.dm @@ -348,11 +348,6 @@ if(!affecting) return - if(ishuman(user) && affecting == M) - var/mob/living/carbon/human/H = user - if(H.check_psi_grab(src)) - return - if(world.time < (last_action + 20)) return diff --git a/code/modules/organs/organ_external.dm b/code/modules/organs/organ_external.dm index 0fc4102eac7..8c338a4d067 100644 --- a/code/modules/organs/organ_external.dm +++ b/code/modules/organs/organ_external.dm @@ -284,8 +284,26 @@ var/laser = (damage_flags & DAMAGE_FLAG_LASER) var/sharp = (damage_flags & DAMAGE_FLAG_SHARP) var/edge = (damage_flags & DAMAGE_FLAG_EDGE) + var/psionic = HAS_FLAG(damage_flags, DAMAGE_FLAG_PSIONIC) var/blunt = !!(brute && !sharp && !edge) + /// Psionics and psionically deaf species take varying amounts of damage from psionic abilities. + if(psionic) + if(!owner.has_psionics()) + brute *= 0.9 + burn *= 0.9 + else if(owner.psi) + var/psi_rank = owner.psi.get_rank() + if(psi_rank == PSI_RANK_SENSITIVE) + brute *= 1.05 + burn *= 1.05 + else if(psi_rank == PSI_RANK_HARMONIOUS) + brute *= 1.1 + burn *= 1.1 + else if(psi_rank == PSI_RANK_APEX) + brute *= 1.2 + burn *= 1.2 + if(status & ORGAN_BROKEN && prob(40) && brute) if(owner && (owner.can_feel_pain()) && owner.stat == CONSCIOUS) owner.emote(pick("scream", "groan")) //getting hit on broken hand hurts diff --git a/code/modules/organs/subtypes/augment.dm b/code/modules/organs/subtypes/augment.dm index 686bf169609..a1db016ee69 100644 --- a/code/modules/organs/subtypes/augment.dm +++ b/code/modules/organs/subtypes/augment.dm @@ -529,7 +529,6 @@ organ_tag = BP_AUG_COCHLEAR parent_organ = BP_HEAD -// Snakebitten! /obj/item/organ/internal/augment/psi name = "psionic receiver" desc = "An augment installed into the head that functions as a surrogate for a missing zona bovinae, also functioning as a filter for the psionically-challenged." diff --git a/code/modules/projectiles/pins.dm b/code/modules/projectiles/pins.dm index 1201ae24b2e..0124d3af927 100644 --- a/code/modules/projectiles/pins.dm +++ b/code/modules/projectiles/pins.dm @@ -112,7 +112,7 @@ Pins Below. fail_message = "PSIONICS CHECK FAILED." /obj/item/device/firing_pin/psionic/pin_auth(mob/living/user) - if(user.can_commune()) + if(user.has_psionics()) return 1 else return 0 diff --git a/code/modules/projectiles/projectile/beams.dm b/code/modules/projectiles/projectile/beams.dm index fe219b3dbd1..cd9e02e9f93 100644 --- a/code/modules/projectiles/projectile/beams.dm +++ b/code/modules/projectiles/projectile/beams.dm @@ -469,15 +469,6 @@ if(isliving(target)) tesla_zap(target, 5, 5000) -/obj/item/projectile/beam/tesla/master - damage = 15 - -/obj/item/projectile/beam/tesla/grandmaster - damage = 20 - -/obj/item/projectile/beam/tesla/paramount - damage = 25 - /obj/item/projectile/beam/freezer name = "freezing ray" icon_state = "bluelaser" diff --git a/code/modules/psionics/abilities/_ability.dm b/code/modules/psionics/abilities/_ability.dm new file mode 100644 index 00000000000..f04e1ca6e57 --- /dev/null +++ b/code/modules/psionics/abilities/_ability.dm @@ -0,0 +1,29 @@ +/singleton/psionic_power + /// Ability name. + var/name + /// Description of what the ability does. + var/desc + /// Spell object to spawn. Must be a path. + var/spell_path + /// Spell icon state. + var/icon_state + /// Ability flags define who can pick an ability - ship characters, adminspawn characters, antags. + var/ability_flags + /// Minimum required rank to use an ability. + var/minimum_rank = PSI_RANK_SENSITIVE + /// Point shop cost. + var/point_cost = 1 + +/// Called when a power is given to a mob. +/singleton/psionic_power/proc/apply(var/mob/living/carbon/human/H) + if(H.ability_master) + var/obj/spellbutton/spell = new(H, spell_path, name, icon_state) + H.ability_master.add_psionic_ability(spell, icon_state, src) + H.psi.psionic_powers |= type + return TRUE + else + log_debug("Psionic power [src.name] given to mob [H] without ability master!") + return FALSE + +/// A psionic power is made up of two elements: the psionic power singleton (which defines the ability metadata such as its name, description, and cost) +/// and the physical spell object, which defines its behaviour when used. Keep in mind that ALL psionic abilities MUST have the ASPECT_PSIONIC aspect! diff --git a/code/modules/psionics/abilities/air_bullet.dm b/code/modules/psionics/abilities/air_bullet.dm new file mode 100644 index 00000000000..60b5ca13b5e --- /dev/null +++ b/code/modules/psionics/abilities/air_bullet.dm @@ -0,0 +1,58 @@ +/singleton/psionic_power/air_bullet + name = "Air Bullet" + desc = "Wrap air in a psionic bubble, compress it, then send it flying at your enemies. Activate the spell in hand to create up to six air bullets, then \ + fire them by clicking something." + icon_state = "tech_frostaura" + point_cost = 3 + ability_flags = PSI_FLAG_ANTAG + spell_path = /obj/item/spell/projectile/air_bullet + +/obj/item/spell/projectile/air_bullet + name = "air bullet" + desc = "Not really a big bang attack, but it's actually pretty close." + icon_state = "force_missile" + cast_methods = CAST_RANGED|CAST_USE + aspect = ASPECT_PSIONIC + spell_projectile = /obj/item/projectile/air_bullet + fire_sound = 'sound/weapons/wave.ogg' + cooldown = 5 + psi_cost = 0 + var/bullets = 0 + var/max_bullets = 6 + +/obj/item/spell/projectile/air_bullet/on_use_cast(mob/user) + . = ..() + if(!.) + return + if(!isliving(user)) + return + var/mob/living/L = user + if(bullets < max_bullets) + if(do_after(L, 0.5 SECONDS)) + to_chat(L, SPAN_NOTICE("You compress some air bullets between your fingers.")) + bullets = min(bullets + 2, 6) + maptext = SMALL_FONTS(7, bullets) + playsound(L, 'sound/weapons/unjam.ogg', 25) + L.psi.spend_power(3) + if(bullets >= 6) + to_chat(L, SPAN_NOTICE("You finish compressing all the bullets you can hold.")) + return + on_use_cast(L) + +/obj/item/spell/projectile/air_bullet/on_ranged_cast(atom/hit_atom, mob/living/user, atom/pb_target) + if(!bullets) + to_chat(user, SPAN_WARNING("You flick your fingers, but find that you don't have any air bullets left!")) + return + . = ..(hit_atom, user, pb_target) + if(.) + bullets-- + maptext = SMALL_FONTS(7, bullets) + +/obj/item/projectile/air_bullet + name = "air bullet" + icon_state = "plasma_bolt" + damage = 30 + armor_penetration = 10 + impact_sounds = list(BULLET_IMPACT_MEAT = SOUNDS_BULLET_MEAT, BULLET_IMPACT_METAL = SOUNDS_BULLET_METAL) + damage_flags = DAMAGE_FLAG_BULLET + damage_type = DAMAGE_BRUTE diff --git a/code/modules/psionics/abilities/assay.dm b/code/modules/psionics/abilities/assay.dm new file mode 100644 index 00000000000..ebfd4783d52 --- /dev/null +++ b/code/modules/psionics/abilities/assay.dm @@ -0,0 +1,80 @@ +/singleton/psionic_power/assay + name = "Assay" + desc = "Assay a creature's psionic level. Using Assay will also allow you to see psionic auras." + icon_state = "wiz_blind" + point_cost = 0 + ability_flags = PSI_FLAG_FOUNDATIONAL + spell_path = /obj/item/spell/assay + +/obj/item/spell/assay + name = "assay" + desc = "Read someone's psionic potential." + icon_state = "generic" + cast_methods = CAST_MELEE|CAST_INNATE + aspect = ASPECT_PSIONIC + cooldown = 10 + psi_cost = 2 + +/obj/item/spell/assay/on_innate_cast(mob/user) + if(!isliving(user)) + return + + . = ..() + if(!.) + return + + var/mob/living/L = user + + to_chat(user, SPAN_NOTICE("You can now see psionic auras.")) + L.psi.show_auras() + +/obj/item/spell/assay/Destroy() + if(isliving(owner)) + var/mob/living/L = owner + to_chat(L, SPAN_NOTICE("You can no longer see psionic auras.")) + L.psi.hide_auras() + + return ..() + +/obj/item/spell/assay/on_melee_cast(atom/hit_atom, mob/living/user, def_zone) + . = ..() + if(!.) + return + + if(!ishuman(hit_atom)) + return + + if(!isliving(user)) + return + + var/mob/living/carbon/human/target = hit_atom + + if(target.stat == DEAD || HAS_FLAG(target.status_flags, FAKEDEATH)) + to_chat(user, SPAN_WARNING("Psionic power does not flow through a dead person.")) + return + + var/psi_blocked = target.is_psi_blocked() + if(psi_blocked) + to_chat(user, psi_blocked) + return + + user.visible_message(SPAN_NOTICE("[user] lays both [user.get_pronoun("his")] palms on [target]'s temples..."), + SPAN_NOTICE("You lay your palms on [target]'s temples and begin tracing their Nlom signature...")) + if(do_mob(user, target, 4 SECONDS)) + if(!target.psi) + to_chat(user, SPAN_NOTICE("[target] is psionically perceptive, but nothing more: [target.get_pronoun("he")] cannot manipulate the fabric that weaves \ + this universe.")) + if(target.psi) + switch(target.psi.get_rank()) + if(PSI_RANK_SENSITIVE) + to_chat(user, SPAN_NOTICE("[target] is psionically sensitive, just like your typical Skrell. They can manipulate psionics, but not to a very \ + precise degree.")) + if(PSI_RANK_HARMONIOUS) + to_chat(user, SPAN_WARNING("[target] is psionically harmonious. This isn't a feat just about anyone can manage: only those born with a certain \ + psionic attitude among Skrell can reach this level, and with strenuous training. They are capable of using psionics \ + with very fine precision.")) + if(PSI_RANK_APEX) + to_chat(user, SPAN_DANGER("Your psionic power is dwarfed by [target]. Just like a supernova in the night sky, their signature is absolutely brilliant \ + beyond comprehension. This is the apex of psionic power, you are sure.")) + if(PSI_RANK_LIMITLESS) + to_chat(user, SPAN_DANGER("A psionic power like this shouldn't be possible... what in the Stars is going on?")) diff --git a/code/modules/psionics/abilities/barrier.dm b/code/modules/psionics/abilities/barrier.dm new file mode 100644 index 00000000000..72f39057b1b --- /dev/null +++ b/code/modules/psionics/abilities/barrier.dm @@ -0,0 +1,30 @@ +/singleton/psionic_power/barrier + name = "Psionic Barrier" + desc = "Activate in hand to give yourself psionic armour. This armour is slightly worse than generic heavy armour, and has a small passive upkeep." + icon_state = "const_mend" + point_cost = 1 + ability_flags = PSI_FLAG_ANTAG + spell_path = /obj/item/spell/barrier + +/obj/item/spell/barrier + name = "psionic barrier" + icon_state = "generic" + cast_methods = CAST_USE + aspect = ASPECT_PSIONIC + cooldown = 20 + psi_cost = 30 + +/obj/item/spell/barrier/on_use_cast(mob/user, bypass_psi_check) + if(!ishuman(user)) + return + . = ..() + if(!.) + return + + var/mob/living/carbon/human/H = user + var/armor = H.psi.GetComponent(/datum/component/armor/psionic) + if(armor) + to_chat(H, SPAN_WARNING("You are already enveloped by psionic armour!")) + return + to_chat(H, SPAN_NOTICE("You are enveloped by a protective warmth.")) + H.psi.AddComponent(/datum/component/armor/psionic, list(laser = ARMOR_LASER_MEDIUM, bullet = ARMOR_BALLISTIC_CARBINE, energy = ARMOR_ENERGY_RESISTANT, melee = ARMOR_MELEE_RESISTANT)) diff --git a/code/modules/psionics/abilities/bubble.dm b/code/modules/psionics/abilities/bubble.dm new file mode 100644 index 00000000000..af2affa892b --- /dev/null +++ b/code/modules/psionics/abilities/bubble.dm @@ -0,0 +1,33 @@ +/singleton/psionic_power/bubble + name = "Bubble" + desc = "Create a protective bubble around you that removes your need to breathe or wear voidsuits in EVA." + icon_state = "tech_condensation" + point_cost = 1 + ability_flags = PSI_FLAG_ANTAG + spell_path = /obj/item/spell/bubble + +/obj/item/spell/bubble + name = "bubble" + icon_state = "shield" + cast_methods = CAST_INNATE + aspect = ASPECT_PSIONIC + cooldown = 10 + psi_cost = 10 + +/obj/item/spell/bubble/Destroy() + to_chat(owner, SPAN_NOTICE("Your bubble fades away.")) + REMOVE_TRAIT(owner, TRAIT_NO_BREATHE, TRAIT_SOURCE_PSIONICS) + REMOVE_TRAIT(owner, TRAIT_PRESSURE_IMMUNITY, TRAIT_SOURCE_PSIONICS) + return ..() + +/obj/item/spell/bubble/on_innate_cast(mob/user) + if(!ishuman(user)) + return + . = ..() + if(!.) + return + + var/mob/living/carbon/human/H = user + H.visible_message(SPAN_NOTICE("A protective bubble forms around [H]."), SPAN_NOTICE("You form a protective bubble around yourself.")) + ADD_TRAIT(H, TRAIT_NO_BREATHE, TRAIT_SOURCE_PSIONICS) + ADD_TRAIT(H, TRAIT_PRESSURE_IMMUNITY, TRAIT_SOURCE_PSIONICS) diff --git a/code/modules/psionics/abilities/charge.dm b/code/modules/psionics/abilities/charge.dm new file mode 100644 index 00000000000..fab1a32235c --- /dev/null +++ b/code/modules/psionics/abilities/charge.dm @@ -0,0 +1,32 @@ +/singleton/psionic_power/charge + name = "Charge" + desc = "Use this spell on an item with a cell to charge it." + icon_state = "wiz_charge" + point_cost = 1 + ability_flags = PSI_FLAG_ANTAG + spell_path = /obj/item/spell/charge + +/obj/item/spell/charge + name = "charge" + icon_state = "audible_deception" + cast_methods = CAST_USE + aspect = ASPECT_PSIONIC + cooldown = 5 + psi_cost = 5 + +/obj/item/spell/charge/on_melee_cast(atom/hit_atom, mob/living/user, def_zone) + var/obj/item/cell/C = hit_atom.get_cell() + if(!C) + return + if(C.percent() >= 100) + to_chat(user, SPAN_WARNING("That cell is already charged.")) + return + psi_cost = C.maxcharge * 0.001 + . = ..() + if(!.) + return + user.visible_message(SPAN_NOTICE("[user] lays [get_pronoun("his")] hand on \the [C]..."), SPAN_NOTICE("You lay your hand on \the [C]..."),) + if(do_after(user, 1 SECOND)) + to_chat(user, SPAN_WARNING("You restore some power to \the [C].")) + C.give(C.maxcharge * 0.1) + psi_cost = initial(psi_cost) diff --git a/code/modules/psionics/abilities/command.dm b/code/modules/psionics/abilities/command.dm new file mode 100644 index 00000000000..32e30242fa6 --- /dev/null +++ b/code/modules/psionics/abilities/command.dm @@ -0,0 +1,78 @@ +/singleton/psionic_power/command + name = "Command" + desc = "Send a psionic command to a target. It must be one word. The suggestion ends when they finish the task, when a minute passes or \ + when they take notable damage." + icon_state = "wiz_blink" + spell_path = /obj/item/spell/command + ability_flags = PSI_FLAG_EVENT + +/obj/item/spell/command + name = "command" + desc = "Send a psionic command to a target." + icon_state = "apportation" + cast_methods = CAST_RANGED|CAST_MELEE + aspect = ASPECT_PSIONIC + cooldown = 10 + psi_cost = 50 + +/obj/item/spell/command/on_melee_cast(atom/hit_atom, mob/living/user, def_zone) + . = ..() + if(!.) + return + command(hit_atom, user) + +/obj/item/spell/command/on_ranged_cast(atom/hit_atom, mob/user) + . = ..() + if(!.) + return + command(hit_atom, user) + +/obj/item/spell/command/proc/command(atom/hit_atom, mob/user) + if(!isliving(hit_atom)) + return + + if(!isliving(user)) + return + + var/mob/living/L = user + var/multi_word = L.psi.get_rank() > PSI_RANK_HARMONIOUS + + var/mob/living/target = hit_atom + if(target.stat == DEAD) + to_chat(user, SPAN_WARNING("Not even a psion of your level can speak to the dead.")) + return + + var/psi_blocked = target.is_psi_blocked() + if(psi_blocked) + to_chat(user, psi_blocked) + return + + user.visible_message(SPAN_CULT("[user] opens a palm and aims it at [hit_atom]...")) + var/text = input("Enter your command. [multi_word ? "This command may be up to 5 words." : "This command may only be one word."]", "Command", null, null) + if(!text) + return + text = formalize_text(text) + + if(target.stat == DEAD) + to_chat(user, SPAN_WARNING("Not even a psion of your level can speak to the dead.")) + return + + log_say("[key_name(user)] issued a [multi_word ? "multi-word" : "single-word"] command to [key_name(target)]: [text]",ckey=key_name(src)) + + to_chat(user, SPAN_CULT("You psionically command to [target]: [text]")) + + for (var/mob/M in player_list) + if (istype(M, /mob/abstract/new_player)) + continue + else if(M.stat == DEAD && HAS_FLAG(M.client.prefs.toggles, CHAT_GHOSTEARS)) + to_chat(M, "[user] psionically commands to [target]: [text]") + + var/mob/living/carbon/human/H = target + if(H.has_psionics()) + to_chat(H, SPAN_CULT("You are psionically commanded to carry out the following task: [text].")) + else if(target.has_psi_aug()) + to_chat(H, SPAN_CULT("You sense [user]'s psyche link with your psi-receiver, a command sliding into your mind: [text]")) + else + to_chat(H, SPAN_CULT("A thought from outside your consciousness slips into your mind: [text]")) + to_chat(H, SPAN_DANGER("You are required to follow this command. It ends when a minute has passed, when you carry it out, or when you take a significant amount \ + of damage. [multi_word ? "You are required to follow up to five words." : "You are required to only follow the first word of the command."]")) diff --git a/code/modules/psionics/abilities/commune.dm b/code/modules/psionics/abilities/commune.dm new file mode 100644 index 00000000000..bffdb8b6f6b --- /dev/null +++ b/code/modules/psionics/abilities/commune.dm @@ -0,0 +1,71 @@ +/singleton/psionic_power/commune + name = "Commune" + desc = "Psionically commune with the target." + icon_state = "tech_audibledeception" + point_cost = 0 + ability_flags = PSI_FLAG_FOUNDATIONAL + spell_path = /obj/item/spell/commune + +/obj/item/spell/commune + name = "commune" + desc = "Déjà-vu." + icon_state = "overload" + cast_methods = CAST_RANGED|CAST_MELEE + aspect = ASPECT_PSIONIC + cooldown = 10 + psi_cost = 15 + +/obj/item/spell/commune/on_melee_cast(atom/hit_atom, mob/living/user, def_zone) + . = ..() + if(!.) + return + commune(hit_atom, user) + +/obj/item/spell/commune/on_ranged_cast(atom/hit_atom, mob/user) + . = ..() + if(!.) + return + commune(hit_atom, user) + +/obj/item/spell/commune/proc/commune(atom/hit_atom, mob/user) + if(!isliving(hit_atom)) + return + + var/mob/living/target = hit_atom + if(target.stat == DEAD) + to_chat(user, SPAN_WARNING("Not even a psion of your level can speak to the dead.")) + return + + var/psi_blocked = target.is_psi_blocked() + if(psi_blocked) + to_chat(user, psi_blocked) + return + + user.visible_message(SPAN_NOTICE("[user] blinks, their eyes briefly developing an unnatural shine.")) + var/text = input("What would you like to say?", "Speak to creature", null, null) + text = sanitize(text) + if(!text) + return + text = formalize_text(text) + + if(target.stat == DEAD) + to_chat(user, SPAN_WARNING("Not even a psion of your level can speak to the dead.")) + return + + log_say("[key_name(user)] communed to [key_name(target)]: [text]",ckey=key_name(src)) + + to_chat(user, SPAN_CULT("You psionically say to [target]: [text]")) + + for (var/mob/M in player_list) + if (istype(M, /mob/abstract/new_player)) + continue + else if(M.stat == DEAD && M.client.prefs.toggles & CHAT_GHOSTEARS) + to_chat(M, "[user] psionically says to [target]: [text]") + + var/mob/living/carbon/human/H = target + if(H.has_psionics()) + to_chat(H, SPAN_CULT("You instinctively sense [user] passing a thought into your mind: [text]")) + else if(target.has_psi_aug()) + to_chat(H, SPAN_CULT("You sense [user]'s psyche link with your psi-receiver, a thought sliding into your mind: [text]")) + else + to_chat(H, SPAN_ALIEN("A thought from outside your consciousness slips into your mind: [text]")) diff --git a/code/modules/psionics/abilities/electrocute.dm b/code/modules/psionics/abilities/electrocute.dm new file mode 100644 index 00000000000..c8fcbaa7dd6 --- /dev/null +++ b/code/modules/psionics/abilities/electrocute.dm @@ -0,0 +1,30 @@ +/singleton/psionic_power/electrocute + name = "Electrocute" + desc = "Administer a painful amount of psionic shock to the nervous system of a foe in melee range, causing burn and agony damage. " + icon_state = "tech_shockaura" + point_cost = 2 + ability_flags = PSI_FLAG_ANTAG + spell_path = /obj/item/spell/electrocute + +/obj/item/spell/electrocute + name = "electrocute" + icon_state = "chain_lightning" + cast_methods = CAST_MELEE + aspect = ASPECT_PSIONIC + cooldown = 10 + psi_cost = 5 + hitsound = 'sound/effects/psi/power_evoke.ogg' + attack_verb = list("lays a palm on") + +/obj/item/spell/electrocute/on_melee_cast(atom/hit_atom, mob/living/user, def_zone) + if(!ishuman(hit_atom)) + to_chat(user, SPAN_WARNING("That won't work!")) + return + . = ..() + if(!.) + return + var/mob/living/carbon/human/H = hit_atom + H.adjustHalLoss(20) + H.adjustFireLoss(20) + H.flash_pain(20) + to_chat(H, SPAN_DANGER("A sharp, stinging pain enters your mind!")) diff --git a/code/modules/psionics/abilities/emotional_suggestion.dm b/code/modules/psionics/abilities/emotional_suggestion.dm new file mode 100644 index 00000000000..95ec377d7e7 --- /dev/null +++ b/code/modules/psionics/abilities/emotional_suggestion.dm @@ -0,0 +1,70 @@ +/singleton/psionic_power/emotional_suggestion + name = "Emotional Suggestion" + desc = "Allows you to psionically commune with the target." + icon_state = "tech_gambit" + ability_flags = PSI_FLAG_EVENT|PSI_FLAG_CANON + spell_path = /obj/item/spell/emotional_suggestion + +/obj/item/spell/emotional_suggestion + name = "emotional suggestion" + desc = "Suggest an emotion to someone." + icon_state = "generic" + cast_methods = CAST_RANGED|CAST_MELEE + aspect = ASPECT_PSIONIC + cooldown = 10 + psi_cost = 15 + +/obj/item/spell/emotional_suggestion/on_melee_cast(atom/hit_atom, mob/living/user, def_zone) + . = ..() + if(!.) + return + emotional_suggestion(hit_atom, user) + +/obj/item/spell/emotional_suggestion/on_ranged_cast(atom/hit_atom, mob/user) + . = ..() + if(!.) + return + emotional_suggestion(hit_atom, user) + +/obj/item/spell/emotional_suggestion/proc/emotional_suggestion(atom/hit_atom, mob/user) + if(!isliving(hit_atom)) + return + + 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/psi_blocked = target.is_psi_blocked() + if(psi_blocked) + to_chat(user, psi_blocked) + return + + user.visible_message(SPAN_NOTICE("[user] blinks, their eyes briefly developing an unnatural shine.")) + var/text = input("Which emotion would you like to suggest?", "Emotional Suggestion") as null|anything in list("Calm", "Happiness", "Sadness", "Fear", "Anger", "Stress", "Confusion") + if(!text) + return + + text = lowertext(text) + + if(target.stat == DEAD) + to_chat(user, SPAN_WARNING("Not even a psion of your level can suggest to the dead.")) + return + + log_say("[key_name(user)] suggested an emotion to [key_name(target)]: [text]",ckey=key_name(src)) + + to_chat(user, SPAN_CULT("You psionically suggest an emotion to [target]: [text]")) + + for (var/mob/M in player_list) + if (istype(M, /mob/abstract/new_player)) + continue + else if(M.stat == DEAD && HAS_FLAG(M.client.prefs.toggles, CHAT_GHOSTEARS)) + to_chat(M, "[user] psionically suggests an emotion to [target]: [text]") + + var/mob/living/carbon/human/H = target + if(H.has_psionics()) + to_chat(H, SPAN_NOTICE("You feel an emotion of [text] washing through your mind.")) + else if(target.has_psi_aug()) + to_chat(H, SPAN_NOTICE("You sense [user]'s psyche link with your psi-receiver, and an emotion envelops your mind: [text].")) + else + to_chat(H, SPAN_NOTICE("An emotion from outside your consciousness slips into your mind: [text].")) diff --git a/code/modules/psionics/abilities/focus.dm b/code/modules/psionics/abilities/focus.dm new file mode 100644 index 00000000000..3ffb245aef3 --- /dev/null +++ b/code/modules/psionics/abilities/focus.dm @@ -0,0 +1,28 @@ +/singleton/psionic_power/focus + name = "Focus" + desc = "Activate this spell in your hand to generate five units of synaptizine in your bloodstream." + icon_state = "tech_phaseshift" + point_cost = 1 + ability_flags = PSI_FLAG_EVENT + spell_path = /obj/item/spell/focus + +/obj/item/spell/focus + name = "focus" + desc = "Psionic drugs? No way." + icon_state = "blink" + cast_methods = CAST_USE + aspect = ASPECT_PSIONIC + cooldown = 50 + psi_cost = 20 + +/obj/item/spell/focus/on_use_cast(mob/user) + if(!ishuman(user)) + return + . = ..() + if(!.) + return + var/mob/living/carbon/human/H = user + if(do_after(user, 1 SECOND)) + to_chat(H, SPAN_WARNING("A calm rush envelops your mind..")) + H.reagents.add_reagent(/singleton/reagent/synaptizine, 5) + H.update_canmove() diff --git a/code/modules/psionics/abilities/grip.dm b/code/modules/psionics/abilities/grip.dm new file mode 100644 index 00000000000..d85eb280b25 --- /dev/null +++ b/code/modules/psionics/abilities/grip.dm @@ -0,0 +1,51 @@ +/singleton/psionic_power/grip + name = "Grip" + desc = "Grip a victim with psionic energy. You can squeeze your grip to crush them." + icon_state = "ling_bioelectrogenesis" + point_cost = 3 + ability_flags = PSI_FLAG_ANTAG + spell_path = /obj/item/spell/grip + +/obj/item/spell/grip + name = "stasis" + desc = "General Kenobi..." + icon_state = "blink" + cast_methods = CAST_RANGED|CAST_USE + aspect = ASPECT_PSIONIC + cooldown = 15 + psi_cost = 5 + var/mob/living/victim + +/obj/item/spell/grip/Destroy() + victim.captured = FALSE + victim.update_canmove() + victim = null + return ..() + +/obj/item/spell/grip/on_use_cast(mob/user, bypass_psi_check) + if(!victim) + to_chat(user, SPAN_WARNING("You need to grip someone first!")) + return + . = ..() + if(!.) + return + user.visible_message(SPAN_WARNING("[user] squeezes [user.get_pronoun("his")] hand!"), SPAN_WARNING("You squeeze your hand to tighten the psionic force around [victim].")) + to_chat(victim, SPAN_DANGER(FONT_HUGE("You are crushed by an invisible force!"))) + victim.apply_damage(30, DAMAGE_BRUTE, armor_pen = 30, damage_flags = DAMAGE_FLAG_DISPERSED) + +/obj/item/spell/grip/on_ranged_cast(atom/hit_atom, mob/user, bypass_psi_check) + if(!isliving(hit_atom)) + return + if(victim) + to_chat(user, SPAN_WARNING("You can't target more than one person with this!")) + return + . = ..() + if(!.) + return + var/mob/living/M = hit_atom + user.visible_message(SPAN_DANGER("[user] extends [user.get_pronoun("his")] arm and makes a grab motion towards [M]!"), + SPAN_DANGER("You extend your arm and grab [M] with your psionic energy!")) + to_chat(M, SPAN_DANGER(FONT_HUGE("You feel an invisible force tighten around you!"))) + M.captured = TRUE + M.update_canmove() + victim = M diff --git a/code/modules/psionics/abilities/hollow_purple.dm b/code/modules/psionics/abilities/hollow_purple.dm new file mode 100644 index 00000000000..e9f356a8a87 --- /dev/null +++ b/code/modules/psionics/abilities/hollow_purple.dm @@ -0,0 +1,106 @@ +/singleton/psionic_power/hollow_purple + name = "Hollow Technique: Purple" + desc = "This technique brings the concept of motion and reversal into reality. Purple is born from merging both infinites: Blue and Red, \ + to produce an imaginary mass that rushes forth and erases everything in its path. Rather than the attraction of Blue or the repulsion of Red, \ + Purple is an extraordinarily destructive energy wave of annihilation that rips whatever it hits from existence." + icon_state = "hollow_purple" + point_cost = 0 + ability_flags = PSI_FLAG_LIMITLESS + minimum_rank = PSI_FLAG_LIMITLESS + spell_path = /obj/item/spell/projectile/hollow_purple + +/obj/item/spell/projectile/hollow_purple + name = "hollow purple" + desc = "Throughout Heaven and Earth, I alone am the honored one!" + icon_state = "destablize" + cast_methods = CAST_RANGED + aspect = ASPECT_PSIONIC + spell_projectile = /obj/item/projectile/hollow_purple + fire_sound = 'sound/magic/WandODeath.ogg' + cooldown = 10 + psi_cost = 50 + var/stage = 0 + +/obj/item/spell/projectile/hollow_purple/on_ranged_cast(atom/hit_atom, mob/living/user, atom/pb_target) + if(!isliving(user)) + return + + stage = 1 + user.visible_message(SPAN_NOTICE("[user] puts [user.get_pronoun("his")] palms together...")) + START_PROCESSING(SSprocessing, src) + user.say("Cursed Technique Amplification: Blue...") + user.set_light(7, 10, COLOR_BLUE) + color = COLOR_BLUE + if(do_after(user, 4 SECONDS)) + stage = 2 + color = COLOR_RED + user.say("Cursed Technique Reversal: Red...") + user.set_light(0) + user.set_light(7, 10, COLOR_RED) + if(do_after(user, 4 SECONDS)) + user.say("Hollow Technique: Purple!") + user.set_light(0) + user.visible_message(SPAN_CULT("[user] fires a gigantic purple sphere from [user.get_pronoun("his")] hand!")) + . = ..() + + STOP_PROCESSING(SSprocessing, src) + color = initial(color) + user.set_light(0) + stage = 0 + +/obj/item/spell/projectile/hollow_purple/process() + if(stage >= 1) + for(var/mob/living/L in get_hearers_in_view(7, src)) + shake_camera(L, 5, 5) + +/obj/item/projectile/hollow_purple + name = "hollow purple" + icon_state = "hollow_purple" + color = COLOR_PURPLE + damage = 1000 + armor_penetration = 1000 + penetrating = 100 + range = 250 + accuracy = 100 + anti_materiel_potential = 100 + damage_type = DAMAGE_BRUTE + +/obj/item/projectile/hollow_purple/Initialize() + . = ..() + set_light(10, 10, COLOR_PURPLE) + +/obj/item/projectile/hollow_purple/Destroy() + return ..() + +/obj/item/projectile/hollow_purple/on_impact(var/atom/A) + if(ismob(A)) + if(A != firer) + var/mob/M = A + M.gib() + explosion(A, 5, 5, 5) + ..() + +/obj/item/projectile/hollow_purple/on_hit(atom/target, blocked, def_zone) + if(ismob(target)) + if(target != firer) + var/mob/M = target + M.gib() + explosion(target, 5, 5, 5) + ..() + +/obj/item/projectile/hollow_purple/check_penetrate(atom/A) + on_hit(A) + return TRUE + +/obj/item/projectile/hollow_purple/after_move() + for(var/a in range(3, src)) + if(isliving(a) && a != firer) + var/mob/living/M = a + M.gib() + playsound(src, 'sound/magic/LightningShock.ogg', 75, 1) + else if(isturf(a) || isobj(a)) + var/atom/A = a + if(!A.density) + continue + A.ex_act(3) + playsound(src, 'sound/magic/LightningShock.ogg', 75, 1) diff --git a/code/modules/psionics/abilities/ion.dm b/code/modules/psionics/abilities/ion.dm new file mode 100644 index 00000000000..1da9dde5319 --- /dev/null +++ b/code/modules/psionics/abilities/ion.dm @@ -0,0 +1,24 @@ +/singleton/psionic_power/ion + name = "Ion Blast" + desc = "Activate in hand to release a 3x3 ion blast around you. Be careful, this ability is expensive to use." + icon_state = "const_mend" + point_cost = 3 + ability_flags = PSI_FLAG_ANTAG + spell_path = /obj/item/spell/ion + +/obj/item/spell/ion + name = "ion blast" + icon_state = "generic" + cast_methods = CAST_USE + aspect = ASPECT_PSIONIC + cooldown = 20 + psi_cost = 30 + +/obj/item/spell/ion/on_use_cast(mob/user, bypass_psi_check) + . = ..() + if(!.) + return + + user.visible_message(SPAN_DANGER("[user] opens their arms and releases an ion blast around them!"), + SPAN_DANGER("You open your arms and release an ion blast around you!")) + empulse(get_turf(user), 3, 3) diff --git a/code/modules/psionics/abilities/jump.dm b/code/modules/psionics/abilities/jump.dm new file mode 100644 index 00000000000..4eebefb55fc --- /dev/null +++ b/code/modules/psionics/abilities/jump.dm @@ -0,0 +1,22 @@ +/singleton/psionic_power/jump + name = "Jump" + desc = "Teleport to a destination you click on." + icon_state = "tech_dispel" + point_cost = 3 + ability_flags = PSI_FLAG_ANTAG + spell_path = /obj/item/spell/jump + +/obj/item/spell/jump + name = "jump" + icon_state = "warp_strike" + cast_methods = CAST_RANGED + aspect = ASPECT_PSIONIC + cooldown = 50 + psi_cost = 15 + +/obj/item/spell/jump/on_ranged_cast(atom/hit_atom, mob/user, bypass_psi_check) + . = ..() + if(!.) + return + user.visible_message(SPAN_WARNING("[user] warps and disappears!"), SPAN_WARNING("You weave the Nlom to bring you to your destination!")) + do_teleport(user, hit_atom) diff --git a/code/modules/psionics/abilities/lightning.dm b/code/modules/psionics/abilities/lightning.dm new file mode 100644 index 00000000000..d590d22887b --- /dev/null +++ b/code/modules/psionics/abilities/lightning.dm @@ -0,0 +1,59 @@ +/singleton/psionic_power/lightning + name = "Lightning" + desc = "Fire a concentrated lightning bolt. Activate this spell in hand to switch to a second mode that hits living beings in a 4x3 area in front of you." + icon_state = "tech_instabilitytapold" + point_cost = 0 + ability_flags = PSI_FLAG_APEX + minimum_rank = PSI_RANK_APEX + spell_path = /obj/item/spell/projectile/psi_lightning + +/obj/item/spell/projectile/psi_lightning + name = "lightning" + icon_state = "chain_lightning" + cast_methods = CAST_RANGED|CAST_USE + aspect = ASPECT_PSIONIC + spell_projectile = /obj/item/projectile/beam/psi_lightning + fire_sound = 'sound/magic/LightningShock.ogg' + cooldown = 10 + psi_cost = 15 + var/mode = 0 + +/obj/item/spell/projectile/psi_lightning/on_use_cast(mob/user, bypass_psi_check) + . = ..(user, TRUE) + mode = !mode + if(mode) + to_chat(user, SPAN_NOTICE("You will now fire area-of-effect lightning in a 4x3 area in front of you.")) + spell_projectile = /obj/item/projectile/beam/psi_lightning/wide + + else + to_chat(user, SPAN_NOTICE("You will now fire a normal lightning bolt.")) + spell_projectile = /obj/item/projectile/beam/psi_lightning + +/obj/item/projectile/beam/psi_lightning + name = "psionic lightning" + damage = 30 + armor_penetration = 30 + damage_type = DAMAGE_BURN + pass_flags = PASSTABLE | PASSGRILLE | PASSRAILING + range = 40 + accuracy = 100 + + muzzle_type = /obj/effect/projectile/muzzle/tesla + tracer_type = /obj/effect/projectile/tracer/tesla + impact_type = /obj/effect/projectile/impact/tesla + + +/obj/item/projectile/beam/psi_lightning/pellet + damage = 20 + armor_penetration = 20 + +/obj/item/projectile/beam/psi_lightning/wide + damage = 20 + armor_penetration = 20 + +/obj/item/projectile/beam/psi_lightning/wide/Initialize() + for(var/i = 1 to 4) + var/turf/new_turf = get_random_turf_in_range(get_turf(firer), i + rand(0, i), 0, TRUE, FALSE) + var/obj/item/projectile/beam/psi_lightning/pellet/pellet = new type(new_turf) + var/turf/front_turf = get_step(pellet, pellet.dir) + pellet.launch_projectile(front_turf) diff --git a/code/modules/psionics/abilities/mend.dm b/code/modules/psionics/abilities/mend.dm new file mode 100644 index 00000000000..b8c54eb15ee --- /dev/null +++ b/code/modules/psionics/abilities/mend.dm @@ -0,0 +1,69 @@ +/singleton/psionic_power/mend + name = "Mend" + desc = "Mend a creature's wounds. This handles internal wounds as well, such as ruptured organs and broken bones." + icon_state = "tech_biomedaura" + point_cost = 2 + ability_flags = PSI_FLAG_ANTAG + spell_path = /obj/item/spell/mend + +/obj/item/spell/mend + name = "mend" + desc = "Clear!" + icon_state = "mend_wounds" + cast_methods = CAST_MELEE + aspect = ASPECT_PSIONIC + cooldown = 50 + psi_cost = 30 + +/obj/item/spell/mend/on_melee_cast(atom/hit_atom, mob/living/user, def_zone) + if(!ishuman(hit_atom)) + return + + var/mob/living/carbon/human/H = hit_atom + if(!H.has_zona_bovinae()) + to_chat(user, SPAN_WARNING("Psionic power cannot flow through this being.")) + return + + if(H.stat == DEAD || H.status_flags & FAKEDEATH) + to_chat(user, SPAN_WARNING("Psionic power does not flow through a dead person.")) + return + + . = ..() + if(!.) + return + + user.visible_message(SPAN_NOTICE("[user] lays a palm on [H]..."), SPAN_NOTICE("You lay your palm on [H] and get to work.")) + to_chat(user, SPAN_NOTICE("You start by flowing your regenerative psionic energy through their vital organs...")) + for(var/obj/item/organ/O in H.internal_organs) + if(do_mob(user, H, 15 SECONDS)) + if(O.damage > 0) // Fix internal damage + to_chat(SPAN_NOTICE("You mend their [O]'s bruising.")) + O.heal_damage(O.damage) + if(HAS_FLAG(O.status, ORGAN_BROKEN)) + to_chat(SPAN_NOTICE("You restart their [O]'s functionality.")) + O.status &= ~ORGAN_BROKEN + if(O.damage <= 5 && O.organ_tag == BP_EYES) // Fix eyes + H.sdisabilities &= ~BLIND + + to_chat(user, SPAN_NOTICE("You continue by flowing your regenerative psionic energy through their limbs...")) + for(var/obj/item/organ/external/O in H.organs) // Fix limbs + if(!O.robotic < ORGAN_ROBOT) // No robot parts for this. + continue + if(do_mob(user, H, 15 SECONDS)) + to_chat(SPAN_NOTICE("You mend their [O]'s bruising.")) + O.heal_damage(0, O.damage, internal = TRUE, robo_repair = FALSE) + + to_chat(user, SPAN_NOTICE("Finally, you flowing your regenerative psionic energy through their broken limbs...")) + for(var/obj/item/organ/E in H.bad_external_organs) // Fix bones + var/obj/item/organ/external/affected = E + if(do_mob(user, H, 10 SECONDS)) + if((affected.damage < affected.min_broken_damage * config.organ_health_multiplier) && (affected.status & ORGAN_BROKEN)) + to_chat(SPAN_NOTICE("You mend their [affected] together.")) + affected.status &= ~ORGAN_BROKEN + if(HAS_FLAG(affected.status, ORGAN_ARTERY_CUT)) + to_chat(SPAN_NOTICE("You mend a spliced artery in their [affected].")) + affected.status &= ~ORGAN_ARTERY_CUT + if(HAS_FLAG(affected.status, ORGAN_DEAD)) + to_chat(SPAN_NOTICE("You mend some necrosis from their [affected].")) + affected.status &= ~ORGAN_DEAD + user.visible_message(SPAN_NOTICE("[user] raises their palm from [H]."), SPAN_NOTICE("You raise your palm, having finished your work.")) diff --git a/code/modules/psionics/abilities/mind_muddle.dm b/code/modules/psionics/abilities/mind_muddle.dm new file mode 100644 index 00000000000..d78ce624f4e --- /dev/null +++ b/code/modules/psionics/abilities/mind_muddle.dm @@ -0,0 +1,29 @@ +/singleton/psionic_power/mind_muddle + name = "Mind Muddle" + desc = "Use this at range to confuse a target and give them a little bit of pain." + icon_state = "wiz_tele" + point_cost = 1 + ability_flags = PSI_FLAG_EVENT + spell_path = /obj/item/spell/mind_muddle + +/obj/item/spell/mind_muddle + name = "mind muddle" + desc = "Confuse people. That's all it does. Oh, and the pain." + icon_state = "warp_strike" + cast_methods = CAST_RANGED + aspect = ASPECT_PSIONIC + cooldown = 20 + psi_cost = 10 + +/obj/item/spell/mind_muddle/on_ranged_cast(atom/hit_atom, mob/user, bypass_psi_check) + if(!ishuman(hit_atom)) + to_chat(user, SPAN_WARNING("That won't work on them!")) + return + . = ..() + if(!.) + return + var/mob/living/carbon/human/H = hit_atom + H.confused += 20 + to_chat(H, SPAN_DANGER("A stinging sensation fills your mind! You feel nauseated...")) + H.adjustHalLoss(15) + diff --git a/code/modules/psionics/abilities/mirror_shade.dm b/code/modules/psionics/abilities/mirror_shade.dm new file mode 100644 index 00000000000..93b9c424328 --- /dev/null +++ b/code/modules/psionics/abilities/mirror_shade.dm @@ -0,0 +1,53 @@ +/singleton/psionic_power/mirror_shade + name = "Mirror Shade" + desc = "Activate this spell to generate two psionic copies of yourself that will attack nearby mobs. These clones are not dense, will deal pain damage, and disappear \ + after thirty seconds." + icon_state = "wiz_jaunt" + point_cost = 2 + ability_flags = PSI_FLAG_EVENT + spell_path = /obj/item/spell/mirror_shade + +/obj/item/spell/mirror_shade + name = "mirror shade" + desc = "Photocopy yourself." + icon_state = "darkness" + cast_methods = CAST_USE + aspect = ASPECT_PSIONIC + cooldown = 100 + psi_cost = 20 + +/obj/item/spell/mirror_shade/on_use_cast(mob/user) + . = ..() + if(!.) + return + if(do_after(user, 1 SECOND)) + to_chat(user, SPAN_WARNING("You weave your psionic force into two copies of yourself!")) + for(var/i = 1 to 2) + var/mob/living/simple_animal/hostile/mirror_shade/MS = new(pick(get_adjacent_open_turfs(user))) + MS.appearance = user.appearance + MS.name = user.name + MS.owner = user + +/mob/living/simple_animal/hostile/mirror_shade + damage_type = DAMAGE_PAIN + density = FALSE + destroy_surroundings = FALSE + melee_damage_lower = 10 + melee_damage_upper = 15 + attack_emote = "tries shambling towards" + attacktext = "phases its arms through" + var/mob/living/carbon/human/owner + +/mob/living/simple_animal/hostile/mirror_shade/Initialize() + . = ..() + friends += owner + QDEL_IN(src, 30 SECONDS) + +/mob/living/simple_animal/hostile/mirror_shade/examine(mob/user) + /// Technically suspicious, but these have 30 seconds of lifetime so it's probably fine. + return owner.examine(user) + +/mob/living/simple_animal/hostile/mirror_shade/Destroy() + owner = null + visible_message(SPAN_WARNING("[src] dissipates into nothingness, as if it were air all along!")) + return ..() diff --git a/code/modules/psionics/abilities/nlom_eyes.dm b/code/modules/psionics/abilities/nlom_eyes.dm new file mode 100644 index 00000000000..45f426838b7 --- /dev/null +++ b/code/modules/psionics/abilities/nlom_eyes.dm @@ -0,0 +1,76 @@ +/singleton/psionic_power/nlom_eyes + name = "Nlom Eyes" + desc = "Roughly locate a mob on your z-level." + icon_state = "tech_control" + point_cost = 2 + ability_flags = PSI_FLAG_ANTAG + spell_path = /obj/item/spell/nlom_eyes + +/obj/item/spell/nlom_eyes + name = "nlom eyes" + desc = "Psionic drugs? No way." + icon_state = "track" + cast_methods = CAST_USE + aspect = ASPECT_PSIONIC + cooldown = 10 + psi_cost = 5 + /// The mob we are tracking. + var/mob/living/tracked + /// If we are tracking a mob. + var/tracking = FALSE + +/obj/item/spell/nlom_eyes/Destroy() + tracked = null + tracking = 0 + STOP_PROCESSING(SSprocessing, src) + return ..() + +/obj/item/spell/nlom_eyes/on_use_cast(mob/user) + if(tracking) + tracking = FALSE + to_chat(user, SPAN_NOTICE("You stop looking in the Nlom for \the [tracked]'s whereabouts.")) + tracked = null + STOP_PROCESSING(SSprocessing, src) + icon_state = "track" + return + . = ..() + if(!.) + return + + var/list/mob_choices = list() + for(var/mob/living/L in mob_list) + if(L == user) + continue + if(!L.is_psi_blocked()) + continue + if(GET_Z(L) != GET_Z(user)) + continue + mob_choices += L + var/choice = input(user, "Decide who to track.", "Nlom Eyes") as null|anything in mob_choices + if(choice) + tracked = choice + tracking = TRUE + START_PROCESSING(SSprocessing, src) + +/obj/item/spell/track/process() + if(!tracking) + icon_state = "track" + return + + if(!tracked) + icon_state = "track_unknown" + + if(GET_Z(tracked) != GET_Z(owner)) + icon_state = "track_unknown" + + else + set_dir(get_dir(src,get_turf(tracked))) + switch(get_dist(src,get_turf(tracked))) + if(0) + icon_state = "track_direct" + if(1 to 8) + icon_state = "track_close" + if(9 to 16) + icon_state = "track_medium" + if(16 to INFINITY) + icon_state = "track_far" diff --git a/code/modules/psionics/abilities/psi_awakening.dm b/code/modules/psionics/abilities/psi_awakening.dm new file mode 100644 index 00000000000..f2538620e39 --- /dev/null +++ b/code/modules/psionics/abilities/psi_awakening.dm @@ -0,0 +1,43 @@ +/singleton/psionic_power/awaken + name = "Awaken" + desc = "Stimulate a living being's Zona Bovinae and bring them to Psionically Harmonious rank." + icon_state = "tech_corona" + point_cost = 0 + ability_flags = PSI_FLAG_APEX + minimum_rank = PSI_RANK_APEX + spell_path = /obj/item/spell/awaken + +/obj/item/spell/awaken + name = "awaken" + icon_state = "energy_siphon_drain" + cast_methods = CAST_MELEE + aspect = ASPECT_PSIONIC + cooldown = 20 + psi_cost = 100 + +/obj/item/spell/awaken/on_melee_cast(atom/hit_atom, mob/living/user, def_zone) + if(!isliving(hit_atom)) + return + + var/mob/living/L = hit_atom + if(!L.has_zona_bovinae()) + to_chat(user, SPAN_WARNING("This being doesn't have a Zona Bovinae.")) + return + + if(L.psi && L.psi.get_rank() >= PSI_RANK_HARMONIOUS) + to_chat(user, SPAN_WARNING("This wouldn't do anything on them.")) + return + + . = ..() + if(!.) + return + + user.visible_message(SPAN_DANGER("[user] grabs [L]'s head..."), SPAN_DANGER("You grab [L]'s head...")) + if(do_mob(user, L, 5 SECONDS)) + L.set_psi_rank(PSI_RANK_HARMONIOUS) + L.flash_pain(50) + L.adjustHalLoss(20) + playsound(get_turf(L), 'sound/effects/psi/power_feedback.ogg', 100) + user.visible_message(SPAN_DANGER("[user] unleashes a psionic shock through [L]'s head!"), + SPAN_DANGER("You unleash a psionic shock through [L]'s head, targeting their Zona Bovinae and stimulating it it.")) + to_chat(L, SPAN_HIGHDANGER("A large, painful shock courses through your head. You can see the weave of the universe... and manipulate it.")) diff --git a/code/modules/psionics/abilities/psi_drain.dm b/code/modules/psionics/abilities/psi_drain.dm new file mode 100644 index 00000000000..aa47363928b --- /dev/null +++ b/code/modules/psionics/abilities/psi_drain.dm @@ -0,0 +1,57 @@ +/singleton/psionic_power/psi_drain + name = "Psi-Drain" + desc = "Drain psi-stamina from a living being. This can be used both in melee and at range." + icon_state = "gen_project" + point_cost = 0 + ability_flags = PSI_FLAG_APEX + minimum_rank = PSI_RANK_APEX + spell_path = /obj/item/spell/psi_drain + +/obj/item/spell/psi_drain + name = "psi-drain" + icon_state = "chain_lightning" + cast_methods = CAST_MELEE|CAST_RANGED + aspect = ASPECT_PSIONIC + cooldown = 10 + psi_cost = 5 + +/obj/item/spell/psi_drain/on_melee_cast(atom/hit_atom, mob/living/user, def_zone) + if(!isliving(hit_atom)) + return + + var/mob/living/L = hit_atom + + if(!L.has_zona_bovinae()) + to_chat(user, SPAN_WARNING("This being doesn't have a Zona Bovinae.")) + return + + . = ..() + if(!.) + return + + psi_drain(L, user) + +/obj/item/spell/psi_drain/on_ranged_cast(atom/hit_atom, mob/user, bypass_psi_check) + if(!isliving(hit_atom)) + return + + var/mob/living/L = hit_atom + if(!L.has_zona_bovinae()) + to_chat(user, SPAN_WARNING("This being doesn't have a Zona Bovinae.")) + return + + . = ..() + if(!.) + return + psi_drain(L, user) + +/obj/item/spell/psi_drain/proc/psi_drain(mob/living/L, mob/living/user) + user.visible_message(SPAN_DANGER("[user] squeezes [user.get_pronoun("his")] hand at [L]!"), SPAN_DANGER("You squeeze your hand at [L] and siphon [L.get_pronoun("his")] psionic energy!")) + var/psi_taken = min(user.psi.max_stamina, rand(10, 25)) + if(L.psi) + L.psi.spend_power(psi_taken) + else + L.adjustHalLoss(psi_taken * 1.5) + L.flash_pain(psi_taken * 1.5) + playsound(get_turf(user), 'sound/effects/psi/power_evoke.ogg', 100) + to_chat(L, SPAN_DANGER("You feel a headache split apart your mind!")) diff --git a/code/modules/psionics/abilities/psi_punch.dm b/code/modules/psionics/abilities/psi_punch.dm new file mode 100644 index 00000000000..94f16151ff4 --- /dev/null +++ b/code/modules/psionics/abilities/psi_punch.dm @@ -0,0 +1,31 @@ +/singleton/psionic_power/punch + name = "Psi-Punch" + desc = "Imbue your fists with psionic power." + icon_state = "const_fist" + point_cost = 2 + ability_flags = PSI_FLAG_ANTAG + spell_path = /obj/item/spell/punch + +/obj/item/spell/punch + name = "psi-punch" + icon_state = "generic" + cast_methods = CAST_MELEE + aspect = ASPECT_PSIONIC + force = 20 + armor_penetration = 20 + cooldown = 0 + psi_cost = 3 + flags = 0 + hitsound = 'sound/weapons/resonator_blast.ogg' + +/obj/item/spell/punch/on_melee_cast(atom/hit_atom, mob/living/user, def_zone) + if(!isliving(hit_atom)) + return + . = ..() + if(!.) + return + var/mob/living/M = hit_atom + if(prob(15)) + M.throw_at(get_edge_target_turf(loc, loc.dir), 2, 7) + M.visible_message(SPAN_DANGER("[M] is floored by psychic energy!"), SPAN_DANGER("You are floored by psychic energy!")) + M.apply_effect(2, WEAKEN) diff --git a/code/modules/psionics/abilities/psi_recovery.dm b/code/modules/psionics/abilities/psi_recovery.dm new file mode 100644 index 00000000000..d9f4af7d7a0 --- /dev/null +++ b/code/modules/psionics/abilities/psi_recovery.dm @@ -0,0 +1,37 @@ +/singleton/psionic_power/psi_recovery + name = "Psionic Recovery" + desc = "Activate in hand to regenerate psi-stamina." + icon_state = "swarm_zeropoint" + point_cost = 0 + ability_flags = PSI_FLAG_FOUNDATIONAL + spell_path = /obj/item/spell/psi_recovery + +/obj/item/spell/psi_recovery + name = "psionic recovery" + desc = "It's an aura recharge." + icon_state = "generic" + cast_methods = CAST_USE + aspect = ASPECT_PSIONIC + cooldown = 5 + psi_cost = 0 + +/obj/item/spell/psi_recovery/on_use_cast(mob/user) + . = ..() + if(!.) + return + if(!isliving(user)) + return + + var/mob/living/L = user + L.visible_message(SPAN_NOTICE("[user] puts [user.get_pronoun("his")] hands together and focuses..."), + SPAN_NOTICE("You put your hands together and begin focusing on recovering your psionic energy...")) + psi_recovery(user) + +/obj/item/spell/psi_recovery/proc/psi_recovery(mob/user) + var/mob/living/L = user + if(do_after(user, 0.5 SECONDS)) + L.psi.stamina = min(L.psi.max_stamina, L.psi.stamina + rand(1,3)) + if(L.psi.stamina >= L.psi.max_stamina) + to_chat(user, SPAN_NOTICE("You've recovered all your psionic energy.")) + return TRUE + psi_recovery(user) diff --git a/code/modules/psionics/abilities/psi_search.dm b/code/modules/psionics/abilities/psi_search.dm new file mode 100644 index 00000000000..1f00890ee04 --- /dev/null +++ b/code/modules/psionics/abilities/psi_search.dm @@ -0,0 +1,68 @@ +/singleton/psionic_power/psi_search + name = "Psionic Search" + desc = "Scan your Z-level for Nlom signatures. The distance will be slightly inaccurate for weak signatures." + icon_state = "wiz_shield" + point_cost = 0 + ability_flags = PSI_FLAG_FOUNDATIONAL + spell_path = /obj/item/spell/psi_search + +/obj/item/spell/psi_search + name = "psionic search" + desc = "A psionic magnifying glass." + icon_state = "generic" + cast_methods = CAST_USE + aspect = ASPECT_PSIONIC + cooldown = 5 + psi_cost = 10 + +/obj/item/spell/psi_search/on_use_cast(mob/user) + . = ..() + if(!.) + return + if(!isliving(user)) + return + var/mob/living/L = user + L.visible_message(SPAN_NOTICE("[L] puts two fingers to [L.get_pronoun("his")] forehead and focuses..."), + SPAN_NOTICE("You put two fingers to your temple and focus on locating Nlom signatures...")) + if(do_after(L, 3 SECONDS)) + var/list/level_humans = list() + var/found_apex = FALSE + for(var/mob/living/carbon/human/H in human_mob_list) + if(H == L) + continue + if((GET_Z(H) == GET_Z(L)) && !H.is_psi_blocked()) + if(HAS_TRAIT(H, TRAIT_PSIONIC_SUPPRESSION)) + continue + level_humans |= H + if(H.psi) + if(H.psi.get_rank() >= PSI_RANK_APEX) + found_apex = TRUE + if(!length(level_humans)) + to_chat(L, SPAN_WARNING("The Nlom is quiet and empty here.")) + return TRUE + if(found_apex) + to_chat(L, SPAN_DANGER(FONT_HUGE("You reach out into the Nlom and your senses are overwhelmed by a massive signature!"))) + L.flash_pain(20) + L.adjustHalLoss(20) + return + var/list/signatures = list() + var/harmonious_signatures = 0 + var/sensitive_signatures = 0 + var/perceptive_signatures = 0 + for(var/mob/living/carbon/human/H in level_humans) + if(!H.psi) + perceptive_signatures++ + continue + if(H.psi && H.psi.get_rank() == PSI_RANK_SENSITIVE) + sensitive_signatures++ + continue + if(H.psi && H.psi.get_rank() == PSI_RANK_HARMONIOUS) + harmonious_signatures++ + continue + if(perceptive_signatures) + signatures += "[perceptive_signatures] weak signature[perceptive_signatures > 1 ? "s" : ""]" + if(sensitive_signatures) + signatures += "[sensitive_signatures] robust signature[sensitive_signatures > 1 ? "s" : ""]" + if(harmonious_signatures) + signatures += "[harmonious_signatures] very powerful signature[harmonious_signatures > 1 ? "s" : ""]" + to_chat(user, SPAN_NOTICE("Reaching out into the Nlom, you sense [english_list(signatures)].")) diff --git a/code/modules/psionics/abilities/psi_stamina.dm b/code/modules/psionics/abilities/psi_stamina.dm new file mode 100644 index 00000000000..e8278b2fa47 --- /dev/null +++ b/code/modules/psionics/abilities/psi_stamina.dm @@ -0,0 +1,27 @@ +/singleton/psionic_power/psi_stamina + name = "Psi-Stamina Weave" + desc = "Activate this spell in your hand to regenerate your psi-stamina a little bit." + icon_state = "tech_mend_template" + point_cost = 1 + ability_flags = PSI_FLAG_EVENT|PSI_FLAG_CANON + spell_path = /obj/item/spell/psi_stamina + +/obj/item/spell/psi_stamina + name = "psi-stamina weave" + desc = "Kind of like charging up your aura." + icon_state = "generic" + cast_methods = CAST_USE + aspect = ASPECT_PSIONIC + cooldown = 0 + psi_cost = 0 + +/obj/item/spell/psi_stamina/on_use_cast(mob/user) + . = ..() + if(!.) + return + if(do_after(user, 1 SECOND)) + to_chat(user, SPAN_NOTICE("You feel a bit more refreshed.")) + owner.psi.stamina = min(owner.psi.max_stamina, owner.psi.stamina + rand(2,5)) + if(owner.psi.stamina >= owner.psi.max_stamina) + to_chat(user, SPAN_NOTICE("You're ready to go again!")) + return TRUE diff --git a/code/modules/psionics/abilities/psi_sunder.dm b/code/modules/psionics/abilities/psi_sunder.dm new file mode 100644 index 00000000000..ec92daee6bd --- /dev/null +++ b/code/modules/psionics/abilities/psi_sunder.dm @@ -0,0 +1,44 @@ +/singleton/psionic_power/sunder + name = "Sunder" + desc = "Destroy a living being's Zona Bovinae. This will make them psionically deaf." + icon_state = "ling_berserk" + point_cost = 0 + ability_flags = PSI_FLAG_APEX + minimum_rank = PSI_RANK_APEX + spell_path = /obj/item/spell/sunder + +/obj/item/spell/sunder + name = "sunder" + icon_state = "chain_lightning" + cast_methods = CAST_MELEE + aspect = ASPECT_PSIONIC + cooldown = 30 + psi_cost = 20 + +/obj/item/spell/sunder/on_melee_cast(atom/hit_atom, mob/living/user, def_zone) + if(!isliving(hit_atom)) + return + + var/mob/living/L = hit_atom + if(!L.has_zona_bovinae()) + to_chat(user, SPAN_WARNING("This being doesn't have a Zona Bovinae.")) + return + + . = ..() + if(!.) + return + + user.visible_message(SPAN_DANGER("[user] grabs [L]'s head..."), SPAN_DANGER("You grab [L]'s head...")) + if(do_mob(user, L, 5 SECONDS)) + if(L.psi) + QDEL_NULL(L.psi) + ADD_TRAIT(L, TRAIT_PSIONICALLY_DEAF, TRAIT_SOURCE_PSIONICS) + L.flash_pain(100) + L.adjustHalLoss(50) + L.stuttering += 20 + playsound(get_turf(L), 'sound/effects/psi/power_feedback.ogg', 100) + user.visible_message(SPAN_DANGER("[user] unleashes a psionic shock through [L]'s head!"), + SPAN_DANGER("You unleash a psionic shock through [L]'s head, targeting their Zona Bovinae and destroying it.")) + to_chat(L, SPAN_HIGHDANGER("A large, painful shock courses through your head. Part of your brain feels like it's gone.")) + if(isskrell(L)) + to_chat(L, SPAN_CULT("You feel absolutely empty...")) diff --git a/code/modules/psionics/abilities/psi_suppression.dm b/code/modules/psionics/abilities/psi_suppression.dm new file mode 100644 index 00000000000..758fa93811c --- /dev/null +++ b/code/modules/psionics/abilities/psi_suppression.dm @@ -0,0 +1,27 @@ +/singleton/psionic_power/psi_suppression + name = "Psionic Suppression" + desc = "Hold in one of your hands to make yourself invisible to Psi-Search." + icon_state = "tech_shield" + point_cost = 1 + ability_flags = PSI_FLAG_CANON + spell_path = /obj/item/spell/psi_suppression + +/obj/item/spell/psi_suppression + name = "psionic suppression" + icon_state = "generic" + cast_methods = CAST_INNATE + aspect = ASPECT_PSIONIC + psi_cost = 5 + +/obj/item/spell/psi_suppression/Destroy() + to_chat(owner, SPAN_NOTICE("You are no longer hidden from Psi-Search.")) + REMOVE_TRAIT(owner, TRAIT_PSIONIC_SUPPRESSION, TRAIT_SOURCE_PSIONICS) + return ..() + +/obj/item/spell/psi_suppression/on_innate_cast(mob/user) + . = ..() + if(!.) + return + + to_chat(user, SPAN_NOTICE("You are now hidden from Psi-Search.")) + ADD_TRAIT(user, TRAIT_PSIONIC_SUPPRESSION, TRAIT_SOURCE_PSIONICS) diff --git a/code/modules/psionics/abilities/psi_sword.dm b/code/modules/psionics/abilities/psi_sword.dm new file mode 100644 index 00000000000..54ef90614c8 --- /dev/null +++ b/code/modules/psionics/abilities/psi_sword.dm @@ -0,0 +1,32 @@ +/singleton/psionic_power/psi_sword + name = "Psi-Sword" + desc = "Create a psionic sword." + icon_state = "psiblade" + point_cost = 3 + ability_flags = PSI_FLAG_ANTAG + spell_path = /obj/item/spell/psi_sword + +/obj/item/spell/psi_sword + name = "psi-tool" + icon_state = "generic" + cast_methods = CAST_INNATE + aspect = ASPECT_PSIONIC + cooldown = 10 + psi_cost = 5 + +/obj/item/spell/psi_sword/on_innate_cast(mob/user) + if(!ishuman(user)) + return + . = ..() + if(!.) + return + + var/mob/living/carbon/human/H = user + + var/obj/item/psychic_power/psiblade/PT = new(user) + if(H.put_in_any_hand_if_possible(PT)) + qdel(src) + return TRUE + else + to_chat(H, SPAN_WARNING("You need an empty hand for this!")) + return FALSE diff --git a/code/modules/psionics/abilities/psi_tool.dm b/code/modules/psionics/abilities/psi_tool.dm new file mode 100644 index 00000000000..a0f8388a0ff --- /dev/null +++ b/code/modules/psionics/abilities/psi_tool.dm @@ -0,0 +1,32 @@ +/singleton/psionic_power/psi_tool + name = "Psi-Tool" + desc = "Create a psionic tool that can change into a screwdriver, crowbar, wrench or wirecutters." + icon_state = "psitool" + point_cost = 1 + ability_flags = PSI_FLAG_ANTAG + spell_path = /obj/item/spell/psi_tool + +/obj/item/spell/psi_tool + name = "psi-tool" + icon_state = "generic" + cast_methods = CAST_INNATE + aspect = ASPECT_PSIONIC + cooldown = 10 + psi_cost = 5 + +/obj/item/spell/psi_tool/on_innate_cast(mob/user) + if(!ishuman(user)) + return + . = ..() + if(!.) + return + + var/mob/living/carbon/human/H = user + + var/obj/item/psychic_power/tinker/PT = new(user) + if(H.put_in_any_hand_if_possible(PT)) + qdel(src) + return TRUE + else + to_chat(H, SPAN_WARNING("You need an empty hand for this!")) + return FALSE diff --git a/code/modules/psionics/abilities/pull.dm b/code/modules/psionics/abilities/pull.dm new file mode 100644 index 00000000000..2e1931f5d36 --- /dev/null +++ b/code/modules/psionics/abilities/pull.dm @@ -0,0 +1,38 @@ +/singleton/psionic_power/pull + name = "Pull" + desc = "Pulls the target straight towards the user, smashing windows along the way. If it can be held in your hands, \ + it goes straight to your hand. Note that you can catch items you pull to yourself if you toggle throw mode before pulling an item." + icon_state = "tech_passwall" + point_cost = 2 + ability_flags = PSI_FLAG_ANTAG + spell_path = /obj/item/spell/pull + +/obj/item/spell/pull + name = "pull" + desc = "Not as cool as the Mass Effect one." + icon_state = "control" + cast_methods = CAST_RANGED + aspect = ASPECT_PSIONIC + cooldown = 50 + psi_cost = 10 + +/obj/item/spell/pull/on_ranged_cast(atom/hit_atom, mob/user, bypass_psi_check) + if(!ismovable(hit_atom)) + return + if(!ishuman(user)) + return + . = ..() + if(!.) + return + var/atom/movable/AM = hit_atom + var/mob/living/carbon/human/H = user + if(isobj(hit_atom) && w_class < ITEMSIZE_IMMENSE) + if(length(get_line(hit_atom, user))) + if(H.put_in_any_hand_if_possible(hit_atom)) + return + user.visible_message(SPAN_WARNING("[user] extends [user.get_pronoun("his")] hand at [hit_atom]and pulls!"), SPAN_WARNING("You mimic pulling at [hit_atom]!")) + if(ismob(hit_atom)) + to_chat(hit_atom, SPAN_WARNING("A psychic force pulls you!")) + AM.throw_at(user, 10, 7) + playsound(user, 'sound/effects/psi/power_evoke.ogg') + diff --git a/code/modules/psionics/abilities/read_mind.dm b/code/modules/psionics/abilities/read_mind.dm new file mode 100644 index 00000000000..ccc2d6f6025 --- /dev/null +++ b/code/modules/psionics/abilities/read_mind.dm @@ -0,0 +1,77 @@ +/singleton/psionic_power/read_mind + name = "Read Mind" + desc = "Rip thoughts from someone's mind. If your rank is Psionically Sensitive, you may only skim the surface thoughts from a person's mind. \ + If your rank is Psionically Harmonious or above, your target is forced to respond to a five-word question with the truth." + icon_state = "tech_illusion" + spell_path = /obj/item/spell/read_mind + ability_flags = PSI_FLAG_EVENT|PSI_FLAG_CANON + +/obj/item/spell/read_mind + name = "read mind" + desc = "Rip thoughts from someone's mind." + icon_state = "generic" + cast_methods = CAST_MELEE|CAST_USE + aspect = ASPECT_PSIONIC + cooldown = 10 + psi_cost = 50 + +/obj/item/spell/read_mind/on_melee_cast(atom/hit_atom, mob/living/user, def_zone) + . = ..() + if(!.) + return + read_mind(hit_atom, user) + +/obj/item/spell/read_mind/proc/read_mind(atom/hit_atom, mob/user) + if(!isliving(hit_atom)) + return + + if(!isliving(user)) + return + + var/mob/living/target = hit_atom + if(target.stat == DEAD) + to_chat(user, SPAN_WARNING("Not even a psion of your level can speak to the dead.")) + return + + var/psi_blocked = target.is_psi_blocked() + if(psi_blocked) + to_chat(user, psi_blocked) + return + + var/safe_mode = FALSE + var/mob/living/L = user + if(L.psi.get_rank() < PSI_RANK_HARMONIOUS) + safe_mode = TRUE + + user.visible_message(SPAN_WARNING("[user] lays a palm on [hit_atom]'s forehead...")) + var/question + if(!safe_mode) + question = sanitize(input(user, "Ask your question.", "Read Mind") as null|text) + if((!safe_mode && !question) || user.incapacitated()) + return TRUE + + var/started_mindread = world.time + if(target.has_psi_aug()) + to_chat(user, SPAN_NOTICE("Your psyche links with [target]'s psi-receiver, seeking [safe_mode ? "their surface thoughts." : "an answer from their mind's surface: [question]"]")) + to_chat(target, SPAN_NOTICE("[user]'s psyche links with your psi-receiver. [safe_mode ? "What are you thinking about, currently?" : "You cannot avoid the following question, and must answer truthfully: [question]"]")) + else + to_chat(user, SPAN_NOTICE("You dip your mentality into the surface layer of \the [target]'s mind, seeking an answer: [question]")) + to_chat(target, SPAN_NOTICE("Your mind is compelled to answer. [safe_mode ? "What are you thinking about, currently?" : "You cannot avoid the following question, and must answer truthfully: [question]"]")) + var/answer = sanitize(input(target, "[question]\n[safe_mode ? "You must answer with what you are currently thinking about." : "You must answer truthfully."]\nYou have 25 seconds to type a response.", "Read Mind") as null|text) + if(!answer || world.time > started_mindread + 25 SECONDS || user.stat != CONSCIOUS) + to_chat(user, SPAN_NOTICE("You receive nothing useful from \the [target].")) + to_chat(target, SPAN_NOTICE("Your mind blanks out momentarily.")) + else + if(safe_mode) + to_chat(user, SPAN_NOTICE("You skim the first thoughts in [target]'s mind: [answer]")) + else + to_chat(user, SPAN_NOTICE("You pry the answer to your question from [target]'s mind: [answer]")) + msg_admin_attack("[key_name(user)] read mind of [key_name(target)] [safe_mode ? "skimming their surface thoughts" : "forcing them to answer truthfully with question \"[question]\""] and [answer?"got answer \"[answer]\".":"got no answer."]") + if(safe_mode) + target.confused += 15 + target.adjustBrainLoss(10) + to_chat(target, SPAN_WARNING("You feel somewhat nauseated, and a headache's come up too...")) + else + target.adjustBrainLoss(20) + target.confused += 20 + to_chat(target, SPAN_DANGER("Your head feels like it's going to explode, and you feel nauseated...")) diff --git a/code/modules/psionics/abilities/rejuvenate.dm b/code/modules/psionics/abilities/rejuvenate.dm new file mode 100644 index 00000000000..ab92f713ad9 --- /dev/null +++ b/code/modules/psionics/abilities/rejuvenate.dm @@ -0,0 +1,38 @@ +/singleton/psionic_power/rejuvenate + name = "Rejuvenate" + desc = "Restore a creature's blood." + icon_state = "tech_oxygenate" + point_cost = 1 + ability_flags = PSI_FLAG_ANTAG + spell_path = /obj/item/spell/rejuvenate + +/obj/item/spell/rejuvenate + name = "rejuvenate" + desc = "Blood bag who?" + icon_state = "mend_life" + cast_methods = CAST_MELEE + aspect = ASPECT_PSIONIC + cooldown = 50 + psi_cost = 30 + +/obj/item/spell/rejuvenate/on_melee_cast(atom/hit_atom, mob/living/user, def_zone) + if(!ishuman(hit_atom)) + return + + var/mob/living/carbon/human/target = hit_atom + if(!target.has_zona_bovinae()) + to_chat(user, SPAN_WARNING("Psionic power cannot flow through this being.")) + return + + if(target.stat == DEAD || target.status_flags & FAKEDEATH) + to_chat(user, SPAN_WARNING("Psionic power does not flow through a dead person.")) + return + + . = ..() + if(!.) + return + + user.visible_message(SPAN_NOTICE("[user] lays both palms on [target]..."), SPAN_NOTICE("You lay your palms on [target] and get to work.")) + if(do_mob(user, target, 10 SECONDS)) + target.restore_blood() + user.visible_message(SPAN_NOTICE("[user] removes their palms from [target]."), SPAN_NOTICE("You remove your palms from [target], having restored their blood.")) diff --git a/code/modules/psionics/abilities/rend.dm b/code/modules/psionics/abilities/rend.dm new file mode 100644 index 00000000000..58bc090a6e8 --- /dev/null +++ b/code/modules/psionics/abilities/rend.dm @@ -0,0 +1,64 @@ +/singleton/psionic_power/rend + name = "Rend" + desc = "Rend an adjacent target's biomolecular state apart. Very powerful, but with an extremely long cooldown and a huge psi-stamina cost. \ + Activate it in your hand to switch to a structure mode, in which you cannot target living beings (synthetics included) but you can tear apart \ + walls and airlocks much faster." + icon_state = "gen_dissolve" + point_cost = 3 + ability_flags = PSI_FLAG_ANTAG + spell_path = /obj/item/spell/rend + +/obj/item/spell/rend + name = "rend" + icon_state = "chain_lightning" + cast_methods = CAST_USE|CAST_MELEE + aspect = ASPECT_PSIONIC + force = 40 + armor_penetration = 30 + cooldown = 30 + psi_cost = 35 + attack_verb = list("rends apart", "disintegrates") + hitsound = 'sound/weapons/heavysmash.ogg' + flags = 0 + var/structure_mode = FALSE + +/obj/item/spell/rend/on_use_cast(mob/user, bypass_psi_check) + . = ..(user, TRUE) + structure_mode = !structure_mode + if(structure_mode) + to_chat(user, SPAN_WARNING("You reconfigure your rending to damage structures. It can no longer be used on organics or synthetics, but it is much cheaper \ + to damage structures with.")) + maptext = SMALL_FONTS(7, "S") + psi_cost = 15 + else + to_chat(user, SPAN_WARNING("You reconfigure your rending to damage organics again.")) + maptext = SMALL_FONTS(7, "O") + psi_cost = initial(psi_cost) + +/obj/item/spell/rend/on_melee_cast(atom/hit_atom, mob/living/user, def_zone) + if(structure_mode && isliving(hit_atom)) + to_chat(user, SPAN_WARNING("Your rending is not configured for organics!")) + return + . = ..() + if(!.) + return + if(structure_mode) + if(iswall(hit_atom)) + var/turf/simulated/wall/W = hit_atom + var/base_time = 5 SECONDS + if(W.reinf_material) + base_time += (W.reinf_material.hardness / 10) SECONDS + user.visible_message(SPAN_WARNING("[user] lays [user.get_pronoun("his")] palms on \the [W] and begins discharging psionic energy on it..."), + SPAN_WARNING("You lay your palms on \the [W] and begin permeating psionic energy through its structure...")) + if(do_after(user, base_time)) + user.visible_message(SPAN_WARNING("[user] disintegrates \the [W]!"), SPAN_WARNING("You disintegrate \the [W]!")) + W.dismantle_wall(TRUE, FALSE, TRUE) + else if(isairlock(hit_atom)) + var/obj/machinery/door/airlock/A = hit_atom + var/base_time = (round(A.maxhealth / 60)) SECONDS + user.visible_message(SPAN_WARNING("[user] lays [user.get_pronoun("his")] palms on \the [A] and begins discharging psionic energy on it..."), + SPAN_WARNING("You lay your palms on \the [A] and begin permeating psionic energy through its structure...")) + if(do_after(user, base_time)) + user.visible_message(SPAN_WARNING("[user] disintegrates \the [A]!"), SPAN_WARNING("You disintegrate \the [A]!")) + playsound(A, 'sound/effects/meteorimpact.ogg') + qdel(A) diff --git a/code/modules/psionics/abilities/shockwave.dm b/code/modules/psionics/abilities/shockwave.dm new file mode 100644 index 00000000000..15c1c3b099c --- /dev/null +++ b/code/modules/psionics/abilities/shockwave.dm @@ -0,0 +1,42 @@ +/singleton/psionic_power/shockwave + name = "Shockwave" + desc = "Create a wave of telekinetic energy to pummel the ground in a straight line in the direction you're facing. \ + Anyone caught in it falls over unless wearing magboots." + icon_state = "tech_haste" + point_cost = 2 + ability_flags = PSI_FLAG_ANTAG + spell_path = /obj/item/spell/shockwave + +/obj/item/spell/shockwave + name = "shockwave" + icon_state = "flame_tongue" + cast_methods = CAST_USE + aspect = ASPECT_PSIONIC + cooldown = 50 + psi_cost = 20 + +/obj/item/spell/shockwave/on_use_cast(mob/user, bypass_psi_check) + . = ..() + if(!.) + return + + var/user_dir = user.dir + var/turf/T = get_turf(user) + if(isspaceturf(T)) + to_chat(user, SPAN_WARNING("You charge your shockwave, slam your foot down... and then remember that you're in space.")) + return + user.visible_message(SPAN_DANGER(FONT_HUGE("[user] charges [user.get_pronoun("his")] foot with psionic energy and slams it down!")), + SPAN_DANGER(FONT_HUGE("You charge your foot with psionic energy and slam it down!"))) + playsound(T, 'sound/effects/meteorimpact.ogg', 100) + for(var/mob/M in get_hearers_in_view(7, src)) + shake_camera(M, 5, 5) + for(var/i = 1 to 5) + T = get_step(T, user_dir) + if(isspaceturf(T)) + break + for(var/mob/living/M in T) + if(M.Check_Shoegrip()) + continue + M.apply_effect(3, WEAKEN) + M.flash_pain(20) + to_chat(M, SPAN_DANGER("You get caught in the shockwave and fall down!")) diff --git a/code/modules/psionics/abilities/singularity.dm b/code/modules/psionics/abilities/singularity.dm new file mode 100644 index 00000000000..12ca8cc9377 --- /dev/null +++ b/code/modules/psionics/abilities/singularity.dm @@ -0,0 +1,59 @@ +/singleton/psionic_power/singularity + name = "Singularity" + desc = "Creates a psionic illusion. Anyone within four tiles of it is forced to walk towards it. It will dissipate after ten seconds." + icon_state = "tech_energysiphon" + point_cost = 2 + ability_flags = PSI_FLAG_ANTAG + spell_path = /obj/item/spell/singularity + +/obj/item/spell/singularity + name = "singularity" + icon_state = "destablize" + cast_methods = CAST_RANGED + aspect = ASPECT_PSIONIC + cooldown = 200 + psi_cost = 20 + var/obj/effect/singularity/singularity + +/obj/item/spell/singularity/Destroy() + QDEL_NULL(singularity) + return ..() + +/obj/item/spell/singularity/on_ranged_cast(atom/hit_atom, mob/user, bypass_psi_check) + if(!QDELETED(singularity)) + to_chat(user, SPAN_WARNING("You can't make more than one singularity!")) + return + . = ..() + if(!.) + return + + var/obj/effect/singularity/S = new(get_turf(hit_atom)) + S.creator = user + singularity = S + playsound(get_turf(user), 'sound/effects/fingersnap.ogg') + user.visible_message(SPAN_DANGER(FONT_HUGE("[user] snaps [user.get_pronoun("his")] fingers and generates a hole of psionic energy!")), + SPAN_DANGER("You snap your fingers and generate a vortex of psionic energy.")) + +/obj/effect/singularity + name = "psionic singularity" + icon_state = "bluestream_fade" + var/mob/living/creator + +/obj/effect/singularity/process() + for(var/mob/living/M in get_hearers_in_LOS(4, src)) + if(M == creator) + continue + to_chat(M, SPAN_DANGER("Your eyes can't move away from \the [src]... you feel compelled to move towards it!")) + M.AdjustStunned(1) + step_towards(M, src) + +/obj/effect/singularity/Initialize(mapload, ...) + . = ..() + QDEL_IN(src, 10 SECONDS) + START_PROCESSING(SSprocessing, src) + +/obj/effect/singularity/Destroy() + visible_message(SPAN_WARNING("\The [src] dissipates harmlessly with a light 'fwoosh' sound.")) + creator = null + STOP_PROCESSING(SSprocessing, src) + return ..() diff --git a/code/modules/psionics/abilities/skinsight.dm b/code/modules/psionics/abilities/skinsight.dm new file mode 100644 index 00000000000..1966dbe4794 --- /dev/null +++ b/code/modules/psionics/abilities/skinsight.dm @@ -0,0 +1,67 @@ +/singleton/psionic_power/skinsight + name = "Skinsight" + desc = "Get information on a creature's vitals. Activate in your hand to switch between a normal mode or a slower, more detailed mode. Note that the body scan mode \ + is not 100% accurate with the numbers given." + icon_state = "wiz_blind" + point_cost = 1 + ability_flags = PSI_FLAG_EVENT|PSI_FLAG_CANON + spell_path = /obj/item/spell/skinsight + +/obj/item/spell/skinsight + name = "skinsight" + desc = "A health analyzer, but cooler." + icon_state = "blink" + cast_methods = CAST_MELEE|CAST_USE + aspect = ASPECT_PSIONIC + cooldown = 10 + psi_cost = 10 + var/body_scan_mode = FALSE + +/obj/item/spell/skinsight/on_use_cast(mob/user) + . = ..() + body_scan_mode = !body_scan_mode + if(body_scan_mode) + psi_cost = 30 + to_chat(user, SPAN_NOTICE("You will now use more psionic energy for a more detailed readout.")) + else + psi_cost = 10 + to_chat(user, SPAN_NOTICE("You will now use less psionic energy for a more standard readout.")) + +/obj/item/spell/skinsight/on_melee_cast(atom/hit_atom, mob/living/user, def_zone) + if(!ishuman(hit_atom)) + return + + var/mob/living/carbon/human/target = hit_atom + if(!target.has_zona_bovinae()) + to_chat(user, SPAN_WARNING("Psionic power cannot flow through this being.")) + return + + if(!isliving(user)) + return + + if(target.stat == DEAD) + to_chat(user, SPAN_WARNING("The total lack of psionic energy flowing through this person can only mean one thing: they are dead.")) + return + + . = ..() + if(!.) + return + + if(!body_scan_mode) + user.visible_message(SPAN_NOTICE("[user] passes [user.get_pronoun("his")] hand over [target]."), SPAN_NOTICE("You pass your hand over [target].")) + health_scan_mob(target, user, TRUE, TRUE) + else + user.visible_message(SPAN_NOTICE("[user] slowly passes [user.get_pronoun("his")] hand over [target]..."), + SPAN_NOTICE("You slowly pass your hand over [target]...")) + if(do_mob(user, target, 10 SECONDS)) + for(var/obj/item/organ/internal/I in target.internal_organs) + if(I.is_bruised()) + to_chat(user, SPAN_WARNING("You notice some irregularity in the psionic flow through their [I].")) + if(I.is_broken()) + to_chat(user, SPAN_WARNING("Your psionic flow through their [I] is highly irregular.")) + if(I.damage) + to_chat(user, SPAN_WARNING("You are sure there is some damage in their [I].")) + var/pulse = target.get_pulse() + to_chat(user, SPAN_NOTICE("Their pulse is [pulse].")) + var/oxygenation = max(min(target.get_blood_oxygenation() + rand(-10, 10), 100), 0) + to_chat(user, SPAN_NOTICE("Their oxygenation is more or less [oxygenation]%.")) diff --git a/code/modules/psionics/abilities/spasm.dm b/code/modules/psionics/abilities/spasm.dm new file mode 100644 index 00000000000..0cc7e961083 --- /dev/null +++ b/code/modules/psionics/abilities/spasm.dm @@ -0,0 +1,30 @@ +/singleton/psionic_power/spasm + name = "Spasm" + desc = "Force a target to drop the items in its hands. Note that this has a hefty power use and cooldown." + icon_state = "genetics_closed" + point_cost = 1 + ability_flags = PSI_FLAG_EVENT + spell_path = /obj/item/spell/spasm + +/obj/item/spell/spasm + name = "spasm" + desc = "Made you drop your gun." + icon_state = "control" + cast_methods = CAST_RANGED + aspect = ASPECT_PSIONIC + cooldown = 100 + /// This looks like a lot, but this ability is really strong. + psi_cost = 20 + +/obj/item/spell/spasm/on_ranged_cast(atom/hit_atom, mob/user, bypass_psi_check) + if(!ishuman(hit_atom)) + to_chat(user, SPAN_WARNING("That won't work!")) + return + . = ..() + if(!.) + return + var/mob/living/carbon/human/H = hit_atom + H.drop_l_hand() + H.drop_r_hand() + to_chat(H, SPAN_DANGER("Your mind is filled by an acute pain making you drop what you're holding!")) + H.flash_pain(50) diff --git a/code/modules/psionics/abilities/stasis.dm b/code/modules/psionics/abilities/stasis.dm new file mode 100644 index 00000000000..5373d2a9871 --- /dev/null +++ b/code/modules/psionics/abilities/stasis.dm @@ -0,0 +1,58 @@ +/singleton/psionic_power/stasis + name = "Stasis" + desc = "Condenses the Nlom field around one person at a time. This immobilises them like an energy net and also applies stasis to them." + icon_state = "gen_ice" + point_cost = 2 + ability_flags = PSI_FLAG_ANTAG + spell_path = /obj/item/spell/stasis + +/obj/item/spell/stasis + name = "stasis" + desc = "Almost as cool as the Mass Effect one." + icon_state = "overload" + cast_methods = CAST_RANGED + aspect = ASPECT_PSIONIC + cooldown = 100 + psi_cost = 20 + var/obj/effect/energy_net/our_net + +/obj/item/spell/stasis/Destroy() + qdel(our_net) + our_net = null + return ..() + +/obj/item/spell/stasis/on_ranged_cast(atom/hit_atom, mob/user, bypass_psi_check) + if(!isliving(hit_atom)) + return + if(!QDELETED(our_net)) + to_chat(user, SPAN_WARNING("You can't target more than one person with this!")) + return + . = ..() + if(!.) + return + var/mob/living/M = hit_atom + var/obj/effect/energy_net/EN = locate() in get_turf(M) + if(EN) + qdel(EN) + + var/turf/T = get_turf(M) + if(T) + var/obj/effect/energy_net/stasis/net = new(T) + net.layer = M.layer + 1 + M.captured = TRUE + M.update_canmove() + net.affecting = M + our_net = net + M.visible_message(SPAN_DANGER("A field of condensed Nlom appears around [M]!"), SPAN_DANGER("You are immobilised by a field of condensed Nlom! You feel your body and mind slow down...")) + +/obj/effect/energy_net/stasis + name = "condensed nlom field" + desc = "A crystallized field of pure Nlom energy." + icon_state = "shield2" + health = 100 + +/obj/effect/energy_net/stasis/process() + . = ..() + if(iscarbon(affecting)) + var/mob/living/carbon/C = affecting + C.SetStasis(10) diff --git a/code/modules/psionics/abilities/throw.dm b/code/modules/psionics/abilities/throw.dm new file mode 100644 index 00000000000..651b82a2b20 --- /dev/null +++ b/code/modules/psionics/abilities/throw.dm @@ -0,0 +1,93 @@ +/singleton/psionic_power/throw_item + name = "Throw" + desc = "Click this spell with an item to prepare it to be thrown, then use the spell to throw the item with lethal force." + icon_state = "wiz_mm" + point_cost = 3 + ability_flags = PSI_FLAG_ANTAG + spell_path = /obj/item/spell/projectile/throw_item + +/obj/item/spell/projectile/throw_item + name = "throw" + desc = "Almost as cool as the Mass Effect one, but still not as cool." + icon_state = "aspect_bolt" + cast_methods = CAST_RANGED + aspect = ASPECT_PSIONIC + spell_projectile = /obj/item/projectile/psionic_throw + fire_sound = 'sound/weapons/wave.ogg' + cooldown = 20 + psi_cost = 20 + var/obj/item_to_throw + +/obj/item/spell/projectile/throw_item/attackby(obj/item/W, mob/user) + . = ..() + if(!item_to_throw) + owner.drop_item(W) + W.forceMove(src) + to_chat(user, SPAN_NOTICE("You imbue [W] with psionic energy!")) + add_overlay("controlled") + item_to_throw = W + +/obj/item/spell/projectile/throw_item/Destroy() + if(item_to_throw) + item_to_throw.forceMove(loc) + item_to_throw = null + return ..() + +/obj/item/spell/projectile/throw_item/on_use_cast(mob/user) + . = ..(user, TRUE) + if(item_to_throw) + owner.put_in_hands(item_to_throw) + item_to_throw = null + cut_overlays() + +/obj/item/spell/projectile/throw_item/on_ranged_cast(atom/hit_atom, mob/living/user, atom/pb_target) + if(!item_to_throw) + to_chat(user, SPAN_WARNING("You need to imbue an item with psionic energy first!")) + return + . = ..() + item_to_throw = null + cut_overlays() + +/obj/item/spell/projectile/throw_item/make_projectile(obj/item/projectile/projectile_type, mob/living/user) + var/obj/item/projectile/psionic_throw/P = ..() + if(P && item_to_throw) + var/base_damage = item_to_throw.force > 5 ? item_to_throw.force : 10 + /// Why is it called a fucking divisor? It's a multiplier... + var/thrown_force_divisor = 1 + if(istype(item_to_throw, /obj/item/material/twohanded)) + var/obj/item/material/twohanded/MT = item_to_throw + base_damage = MT.force_wielded > MT.force ? MT.force_wielded : force + thrown_force_divisor = MT.thrown_force_divisor + /// There are different throwforce calculations, which means that we have to do this manually. + if(base_damage < (item_to_throw.throwforce * thrown_force_divisor)) + base_damage = item_to_throw.throwforce * thrown_force_divisor + var/base_armor_penetration + if(item_to_throw.armor_penetration > 2) + base_armor_penetration = item_to_throw.armor_penetration + else + base_armor_penetration = 2 + P.name = item_to_throw.name + P.appearance = item_to_throw.appearance + P.damage = base_damage * (item_to_throw.w_class / 2) + P.armor_penetration = base_armor_penetration * item_to_throw.w_class + /// We need to do this again because we're overriding the base make_projectile. + if(owner.psi.get_rank() >= PSI_RANK_APEX) + if(P.damage) + P.damage *= 1.1 + P.armor_penetration *= 1.1 + P.damage_flags |= item_to_throw.damage_flags() + P.damage_type = item_to_throw.damtype + P.source_item = item_to_throw + return P + +/obj/item/projectile/psionic_throw + name = "thrown projectile" + damage_type = DAMAGE_BRUTE + impact_sounds = list(BULLET_IMPACT_MEAT = SOUNDS_BULLET_MEAT, BULLET_IMPACT_METAL = SOUNDS_BULLET_METAL) + var/obj/source_item + +/obj/item/projectile/psionic_throw/Destroy() + if(source_item) + source_item.forceMove(loc) + source_item = null + return ..() diff --git a/code/modules/psionics/abilities/time_stop.dm b/code/modules/psionics/abilities/time_stop.dm new file mode 100644 index 00000000000..b8907f8e906 --- /dev/null +++ b/code/modules/psionics/abilities/time_stop.dm @@ -0,0 +1,47 @@ +/singleton/psionic_power/time_stop + name = "Time Stop" + desc = "Freeze living beings around you in a 5x5 area. This ability is very expensive, so be careful." + icon_state = "tech_control" + point_cost = 2 + ability_flags = PSI_FLAG_ANTAG + spell_path = /obj/item/spell/time_stop + +/obj/item/spell/time_stop + name = "nlom eyes" + desc = "Psionic drugs? No way." + icon_state = "track" + cast_methods = CAST_USE + aspect = ASPECT_PSIONIC + cooldown = 10 + psi_cost = 30 + +/obj/item/spell/time_stop/Destroy() + for(var/mob/living/L in get_hearers_in_view(5, owner)) + if(L == owner) + continue + to_chat(L, SPAN_DANGER("Time around you returns to normal!")) + L.stunned = 0 + L.silent = 0 + return ..() + +/obj/item/spell/time_stop/on_use_cast(mob/user) + . = ..() + if(!.) + return + + if(do_after(user, 1 SECOND)) + user.visible_message(SPAN_DANGER(FONT_HUGE("[user] extends [user.get_pronoun("his")] arms to [user.get_pronoun("his")] sides!")), + SPAN_DANGER("You extend your arms to your side and crystallize the Nlom around you!")) + time_stop(user) + +/obj/item/spell/time_stop/proc/time_stop(mob/living/user) + for(var/mob/living/L in get_hearers_in_view(5, user)) + if(L == user) + continue + to_chat(L, SPAN_DANGER("Time around you slows down to a crawl...")) + L.AdjustStunned(5) + L.silent += 30 + + if(do_after(user, 1 SECOND)) + if(user.psi.spend_power(20)) + time_stop(user) diff --git a/code/modules/psionics/abilities/warp.dm b/code/modules/psionics/abilities/warp.dm new file mode 100644 index 00000000000..2c379bd654c --- /dev/null +++ b/code/modules/psionics/abilities/warp.dm @@ -0,0 +1,75 @@ +/singleton/psionic_power/warp + name = "Warp" + desc = "Warp through objects." + icon_state = "tech_blink" + point_cost = 4 + ability_flags = PSI_FLAG_ANTAG + spell_path = /obj/item/spell/warp + +/obj/item/spell/warp + name = "warp" + icon_state = "warp_strike" + cast_methods = CAST_MELEE + aspect = ASPECT_PSIONIC + cooldown = 50 + psi_cost = 10 + var/maximum_distance = 20 //Measured in tiles. + var/busy = FALSE + +/obj/item/spell/warp/on_melee_cast(atom/hit_atom, mob/living/user, def_zone) + . = ..() + if(!.) + return + if(busy) //Prevent someone from trying to get two uses of the spell from one instance. + return FALSE + if(!allowed_to_teleport()) + to_chat(user, SPAN_WARNING("You can't teleport here.")) + return FALSE + var/turf/T = get_turf(hit_atom) //Turf we touched. + var/turf/our_turf = get_turf(user) //Where we are. + if(!T.density) + if(!T.check_density()) + to_chat(user, SPAN_WARNING("This spell is for solid objects.")) + return FALSE + var/direction = get_dir(our_turf, T) + var/turf/checked_turf = T //Turf we're currently checking for density in the loop below. + var/turf/found_turf = null //Our destination, if one is found. + var/i = maximum_distance + + visible_message(SPAN_NOTICE("[user] rests a hand on \the [hit_atom].")) + busy = TRUE + + spark(our_turf, 3, cardinal) + + while(i) + checked_turf = get_step(checked_turf, direction) //Advance in the given direction + i-- + + if(!checked_turf.density) //If we found a destination (a non-dense turf), then we can stop. + var/dense_objs_on_turf = 0 + for(var/atom/movable/stuff in checked_turf.contents) //Make sure nothing dense is where we want to go, like an airlock or window. + if(stuff.density) + dense_objs_on_turf = 1 + + if(!dense_objs_on_turf) //If we found a non-dense turf with nothing dense on it, then that's our destination. + found_turf = checked_turf + break + sleep(10) + + if(found_turf) + if(user.loc != our_turf) + to_chat(user, SPAN_WARNING("You need to stand still in order to phase through \the [hit_atom].")) + return FALSE + if(!user.incapacitated()) + visible_message(SPAN_WARNING("[user] appears to phase through \the [hit_atom]!")) + to_chat(user, SPAN_NOTICE("You find a destination on the other side of \the [hit_atom], and phase through it.")) + spark(src, 5, 0) + user.forceMove(found_turf) + qdel(src) + return TRUE + else + to_chat(user, SPAN_WARNING("You don't have enough energy to phase through these walls!")) + busy = FALSE + else + to_chat(user, SPAN_NOTICE("You weren't able to find an open space to go to.")) + busy = FALSE diff --git a/code/modules/psionics/abilities/zona_absorption.dm b/code/modules/psionics/abilities/zona_absorption.dm new file mode 100644 index 00000000000..e225ea951d7 --- /dev/null +++ b/code/modules/psionics/abilities/zona_absorption.dm @@ -0,0 +1,41 @@ +/singleton/psionic_power/zona_absorption + 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." + icon_state = "tech_shield_old" + point_cost = 0 + ability_flags = PSI_FLAG_SPECIAL + spell_path = /obj/item/spell/zona_absorption + +/obj/item/spell/zona_absorption + name = "zona bovinae absorption" + icon_state = "chain_lightning" + cast_methods = CAST_MELEE + aspect = ASPECT_PSIONIC + cooldown = 30 + psi_cost = 10 + +/obj/item/spell/zona_absorption/on_melee_cast(atom/hit_atom, mob/living/user, def_zone) + if(!isliving(hit_atom)) + return + var/mob/living/L = hit_atom + if(!L.has_zona_bovinae()) + to_chat(user, SPAN_WARNING("This being doesn't have a Zona Bovinae.")) + return + if(HAS_TRAIT(L, TRAIT_ZONA_BOVINAE_ABSORBED)) + to_chat(user, SPAN_WARNING("You already absorbed points from this creature!")) + return + . = ..() + if(!.) + return + + user.visible_message(SPAN_DANGER("[user] grabs [L]'s head..."), SPAN_DANGER("You grab [L]'s head...")) + if(do_mob(user, L, 5 SECONDS)) + L.flash_pain(20) + L.adjustHalLoss(20) + playsound(get_turf(L), 'sound/effects/psi/power_feedback.ogg', 100) + user.visible_message(SPAN_DANGER("[user] absorbs tendrils of psionic energy from [L]!"), + SPAN_DANGER("You absorb psionic energy from [L], granting you an extra Psionic Point Shop point.")) + var/mob/living/M = user + M.psi.psi_points++ + to_chat(L, SPAN_DANGER("A splitting headache courses through your mind! Your head feels a bit lighter...")) + ADD_TRAIT(L, TRAIT_ZONA_BOVINAE_ABSORBED, TRAIT_SOURCE_PSIONICS) diff --git a/code/modules/psionics/complexus/complexus.dm b/code/modules/psionics/complexus/complexus.dm index 617a4a66c57..9acfe6b1427 100644 --- a/code/modules/psionics/complexus/complexus.dm +++ b/code/modules/psionics/complexus/complexus.dm @@ -1,35 +1,28 @@ /datum/psi_complexus var/announced = FALSE // Whether or not we have been announced to our holder yet. - var/suppressed = TRUE // Whether or not we are suppressing our psi powers. + var/suppressed = FALSE // Whether or not we are suppressing our psi powers. var/use_psi_armor = TRUE // Whether or not we should automatically deflect/block incoming damage. - var/rebuild_power_cache = TRUE // Whether or not we need to rebuild our cache of psi powers. - var/rating = 0 // Overall psi rating. var/cost_modifier = 1 // Multiplier for power use stamina costs. var/stun = 0 // Number of process ticks we are stunned for. var/next_power_use = 0 // world.time minimum before next power use. - var/stamina = 50 // Current psi pool. - var/max_stamina = 50 // Max psi pool. + var/stamina = 100 // Current psi pool. + var/max_stamina = 100 // Max psi pool. + var/psi_points = 0 // Points spendable in the psi pointshop. + var/spent_psi_points = 0 // Need to keep track of if someone has spent psi-points before for things like updating psi-rank. - var/list/latencies // List of all currently latent faculties. - var/list/ranks // Assoc list of psi faculties to current rank. - var/list/base_ranks // Assoc list of psi faculties to base rank, in case reset is needed + var/psionic_rank + var/last_psionic_rank var/list/manifested_items // List of atoms manifested/maintained by psychic power. - var/next_latency_trigger = 0 // world.time minimum before a trigger can be attempted again. + var/list/psionic_powers = list() // List of singleton abilities. var/last_armor_check // world.time of last armor check. var/last_aura_size var/last_aura_alpha var/last_aura_color var/aura_color = "#ff0022" - // Cached powers. - var/list/melee_powers // Powers used in melee range. - var/list/grab_powers // Powers use by using a grab. - var/list/ranged_powers // Powers used at range. - var/list/manifestation_powers // Powers that create an item. - var/list/powers_by_faculty // All powers within a given faculty. - + var/datum/component/armor/psionic/armor_component var/obj/screen/psi/hub/ui // Reference to the master psi UI object. var/mob/living/owner // Reference to our owner. var/image/_aura_image // Client image @@ -70,13 +63,17 @@ /datum/psi_complexus/New(var/mob/_owner) owner = _owner + SSpsi.all_psi_complexes |= src START_PROCESSING(SSpsi, src) /datum/psi_complexus/Destroy() destroy_aura_image(_aura_image) + SSpsi.all_psi_complexes -= src + QDEL_NULL(armor_component) STOP_PROCESSING(SSpsi, src) if(owner) - cancel() + if(owner.ability_master) + owner.ability_master.remove_all_psionic_abilities() if(owner.client) owner.client.screen -= ui for(var/thing in SSpsi.all_aura_images) @@ -89,4 +86,5 @@ for(var/thing in manifested_items) qdel(thing) manifested_items.Cut() + . = ..() diff --git a/code/modules/psionics/complexus/complexus_helpers.dm b/code/modules/psionics/complexus/complexus_helpers.dm index d7c81930cde..f635f483254 100644 --- a/code/modules/psionics/complexus/complexus_helpers.dm +++ b/code/modules/psionics/complexus/complexus_helpers.dm @@ -1,38 +1,28 @@ -/datum/psi_complexus/proc/cancel() - sound_to(owner, sound('sound/effects/psi/power_fail.ogg')) - if(LAZYLEN(manifested_items)) - for(var/thing in manifested_items) - owner.drop_from_inventory(thing) - qdel(thing) - manifested_items = null - /datum/psi_complexus/proc/stunned(var/amount) var/old_stun = stun stun = max(stun, amount) if(amount && !old_stun) to_chat(owner, "Your concentration has been shattered! You cannot focus your psi power!") ui.update_icon() - cancel() /datum/psi_complexus/proc/get_armor(var/armortype) if(can_use_passive()) last_armor_check = world.time - return round(Clamp(Clamp(4 * rating, 0, 20) * get_rank(SSpsi.armor_faculty_by_type[armortype]), 0, 100) * (stamina/max_stamina)) + return round(Clamp(Clamp(4 * get_rank(), 0, 20) * get_rank() / 2, 0, 100) * (stamina/max_stamina)) else last_armor_check = 0 return 0 -/datum/psi_complexus/proc/get_rank(var/faculty) - return LAZYACCESS(ranks, faculty) - -/datum/psi_complexus/proc/set_rank(var/faculty, var/rank, var/defer_update, var/temporary) - if(get_rank(faculty) != rank) - LAZYSET(ranks, faculty, rank) - if(!temporary) - LAZYSET(base_ranks, faculty, rank) +/datum/psi_complexus/proc/set_rank(var/rank, var/defer_update) + if(get_rank() != rank) + last_psionic_rank = psionic_rank + psionic_rank = rank if(!defer_update) update() +/datum/psi_complexus/proc/get_rank() + return psionic_rank + /datum/psi_complexus/proc/set_cooldown(var/value) next_power_use = world.time + value ui.update_icon() @@ -80,11 +70,13 @@ stunned(value * 2) set_cooldown(value * 100) - if(prob(value*10)) owner.emote("scream") + if(prob(value*10)) + owner.emote("scream") // Your head asplode. owner.adjustBrainLoss(value) owner.adjustHalLoss(value * 25) //Ouch. + owner.psi.hide_auras() if(ishuman(owner)) var/mob/living/carbon/human/pop = owner if(pop.should_have_organ(BP_BRAIN)) @@ -97,8 +89,6 @@ /datum/psi_complexus/proc/reset() aura_color = initial(aura_color) - ranks = base_ranks ? base_ranks.Copy() : null max_stamina = initial(max_stamina) stamina = min(stamina, max_stamina) - cancel() update() diff --git a/code/modules/psionics/complexus/complexus_latency.dm b/code/modules/psionics/complexus/complexus_latency.dm index 6d9368f67f9..5637a9befa7 100644 --- a/code/modules/psionics/complexus/complexus_latency.dm +++ b/code/modules/psionics/complexus/complexus_latency.dm @@ -1,17 +1,6 @@ -/datum/psi_complexus/proc/check_latency_trigger(var/trigger_strength = 0, var/source, var/redactive = FALSE) - - if(!LAZYLEN(latencies) || world.time < next_latency_trigger) - return FALSE - - if(!prob(trigger_strength)) - next_latency_trigger = world.time + rand(100, 300) - return FALSE - - var/faculty = pick(latencies) +/datum/psi_complexus/proc/check_psionic_trigger(var/trigger_strength = 0, var/source, var/redactive = FALSE) var/new_rank = rand(2,5) - owner.set_psi_rank(faculty, new_rank) - var/datum/psionic_faculty/faculty_decl = SSpsi.get_faculty(faculty) - to_chat(owner, SPAN_DANGER("You scream internally as your [faculty_decl.name] faculty is forced into operancy by [source]!")) - next_latency_trigger = world.time + rand(600, 1800) * new_rank + owner.set_psi_rank(new_rank) + to_chat(owner, SPAN_DANGER("You scream internally as your psionics are forced into operancy by [source]!")) if(!redactive) owner.adjustBrainLoss(rand(trigger_strength * 2, trigger_strength * 4)) return TRUE diff --git a/code/modules/psionics/complexus/complexus_power_cache.dm b/code/modules/psionics/complexus/complexus_power_cache.dm deleted file mode 100644 index 47cfccd6658..00000000000 --- a/code/modules/psionics/complexus/complexus_power_cache.dm +++ /dev/null @@ -1,49 +0,0 @@ -/datum/psi_complexus/proc/rebuild_power_cache() - if(rebuild_power_cache) - - melee_powers = list() - grab_powers = list() - ranged_powers = list() - manifestation_powers = list() - powers_by_faculty = list() - - for(var/faculty in ranks) - var/relevant_rank = get_rank(faculty) - var/datum/psionic_faculty/faculty_decl = SSpsi.get_faculty(faculty) - for(var/thing in faculty_decl.powers) - var/datum/psionic_power/power = thing - if(relevant_rank >= power.min_rank) - if(!powers_by_faculty[power.faculty]) powers_by_faculty[power.faculty] = list() - powers_by_faculty[power.faculty] += power - if(power.use_ranged) - if(!ranged_powers[faculty]) ranged_powers[faculty] = list() - ranged_powers[faculty] += power - if(power.use_melee) - if(!melee_powers[faculty]) melee_powers[faculty] = list() - melee_powers[faculty] += power - if(power.use_manifest) - manifestation_powers += power - if(power.use_grab) - if(!grab_powers[faculty]) grab_powers[faculty] = list() - grab_powers[faculty] += power - rebuild_power_cache = FALSE - -/datum/psi_complexus/proc/get_powers_by_faculty(var/faculty) - rebuild_power_cache() - return powers_by_faculty[faculty] - -/datum/psi_complexus/proc/get_melee_powers(var/faculty) - rebuild_power_cache() - return melee_powers[faculty] - -/datum/psi_complexus/proc/get_ranged_powers(var/faculty) - rebuild_power_cache() - return ranged_powers[faculty] - -/datum/psi_complexus/proc/get_grab_powers(var/faculty) - rebuild_power_cache() - return grab_powers[faculty] - -/datum/psi_complexus/proc/get_manifestations() - rebuild_power_cache() - return manifestation_powers diff --git a/code/modules/psionics/complexus/complexus_process.dm b/code/modules/psionics/complexus/complexus_process.dm index b0e18682220..3cfa4250d5d 100644 --- a/code/modules/psionics/complexus/complexus_process.dm +++ b/code/modules/psionics/complexus/complexus_process.dm @@ -1,38 +1,15 @@ /datum/psi_complexus/proc/update(var/force) - set waitfor = FALSE - var/last_rating = rating - var/highest_faculty - var/highest_rank = 0 - var/combined_rank = 0 - for(var/faculty in ranks) - var/check_rank = get_rank(faculty) - if(check_rank == 1) - LAZYADD(latencies, faculty) - else - if(check_rank <= 0) - ranks -= faculty - LAZYREMOVE(latencies, faculty) - combined_rank += check_rank - if(!highest_faculty || highest_rank < check_rank) - highest_faculty = faculty - highest_rank = check_rank - - UNSETEMPTY(latencies) - var/rank_count = max(1, LAZYLEN(ranks)) - if(force || last_rating != Ceiling(combined_rank/rank_count)) - if(highest_rank <= 1) - if(highest_rank == 0) - qdel(src) + if(force || last_psionic_rank != psionic_rank) + if(psionic_rank == 0) + qdel(src) return else - rebuild_power_cache = TRUE sound_to(owner, 'sound/effects/psi/power_unlock.ogg') - rating = Ceiling(combined_rank/rank_count) cost_modifier = 1 - if(rating > 1) - cost_modifier -= min(1, max(0.1, (rating-1) / 10)) + if(psionic_rank > 1) + cost_modifier -= min(1, max(0.1, psionic_rank / 10)) if(!ui) ui = new(owner) if(owner.client) @@ -40,35 +17,60 @@ else if(owner.client) owner.client.screen |= ui - if(!suppressed && owner.client) - for(var/thing in SSpsi.all_aura_images) - owner.client.images |= thing var/image/aura_image = get_aura_image() - if(rating >= PSI_RANK_PARAMOUNT) // spooky boosters + if(psionic_rank >= PSI_RANK_APEX) // spooky boosters aura_color = "#aaffaa" aura_image.blend_mode = BLEND_SUBTRACT else aura_image.blend_mode = BLEND_ADD - if(highest_faculty == PSI_COERCION) - aura_color = "#cc3333" - else if(highest_faculty == PSI_PSYCHOKINESIS) - aura_color = "#3333cc" - else if(highest_faculty == PSI_REDACTION) - aura_color = "#33cc33" - else if(highest_faculty == PSI_ENERGISTICS) + if(psionic_rank == PSI_RANK_SENSITIVE) aura_color = "#cccc33" + else if(psionic_rank == PSI_RANK_HARMONIOUS) + aura_color = "#64c464" + + if(psionic_rank > PSI_RANK_SENSITIVE && last_psionic_rank < PSI_RANK_HARMONIOUS) + switch(psionic_rank) + if(PSI_RANK_HARMONIOUS) + psi_points = PSI_POINTS_HARMONIOUS + if(PSI_RANK_APEX) + psi_points = PSI_POINTS_APEX + if(PSI_RANK_LIMITLESS) + psi_points = PSI_POINTS_LIMITLESS + /// We had special abilities unique to our level, so get rid of 'em. + if(last_psionic_rank > PSI_RANK_HARMONIOUS) + wipe_user_abilities() + + if(last_psionic_rank > PSI_RANK_SENSITIVE && psionic_rank < PSI_RANK_HARMONIOUS) + psi_points = 0 + wipe_user_abilities() if(!announced && owner && owner.client && !QDELETED(src)) announced = TRUE to_chat(owner, "
") to_chat(owner, SPAN_NOTICE("You are psionic, touched by powers beyond understanding.")) - to_chat(owner, SPAN_NOTICE("Shift-left-click your Psi icon on the bottom right to view a summary of how to use them, or left click it to suppress or unsuppress your psionics. Beware: overusing your gifts can have deadly consequences.")) + to_chat(owner, SPAN_NOTICE("Left-click your psionic icon to open your Psionic Point Shop, which contains information about your powers. \ + You can also shift-click your ability icons on the top right to know more about them. \ + Beware: overusing your gifts can have deadly consequences.")) to_chat(owner, "
") + if(get_rank() >= PSI_RANK_SENSITIVE) + for(var/singleton/psionic_power/P in GET_SINGLETON_SUBTYPE_LIST(/singleton/psionic_power)) + if((P.ability_flags & PSI_FLAG_FOUNDATIONAL) || (P.ability_flags & PSI_FLAG_APEX && get_rank() >= PSI_RANK_APEX) || (P.ability_flags & PSI_FLAG_LIMITLESS && get_rank() >= PSI_RANK_LIMITLESS)) + if(!(P.type in psionic_powers)) + P.apply(owner) + +/datum/psi_complexus/proc/wipe_user_abilities() + for(var/obj/screen/ability/obj_based/psionic/P in owner.ability_master.ability_objects) + if((P.connected_power.ability_flags & PSI_FLAG_APEX) && get_rank() < PSI_RANK_APEX) + owner.ability_master.remove_ability(P) + if((P.connected_power.ability_flags & PSI_FLAG_LIMITLESS) && get_rank() < PSI_RANK_LIMITLESS) + owner.ability_master.remove_ability(P) + /datum/psi_complexus/process() var/update_hud + if(stun) stun-- if(stun) @@ -77,6 +79,7 @@ update_hud = TRUE else to_chat(owner, SPAN_NOTICE("You have recovered your mental composure.")) + suppressed = FALSE update_hud = TRUE return @@ -86,11 +89,11 @@ else if(owner?.stat == UNCONSCIOUS) stamina = min(max_stamina, stamina + rand(3,5)) - if(!owner.nervous_system_failure() && owner.stat == CONSCIOUS && stamina && !suppressed && get_rank(PSI_REDACTION) >= PSI_RANK_OPERANT) - attempt_regeneration() + if(armor_component) + spend_power(1) - var/next_aura_size = max(0.1,((stamina/max_stamina)*min(3,rating))/5) - var/next_aura_alpha = round(((suppressed ? max(0,rating - 2) : rating)/5)*255) + var/next_aura_size = max(0.1, ((stamina / max_stamina)*min(3, psionic_rank)) / 3) + var/next_aura_alpha = round(((suppressed ? max(0, psionic_rank - 2) : psionic_rank) / 5)*255) if(next_aura_alpha != last_aura_alpha || next_aura_size != last_aura_size || aura_color != last_aura_color) last_aura_size = next_aura_size @@ -103,126 +106,3 @@ if(update_hud) ui.update_icon() - -/datum/psi_complexus/proc/attempt_regeneration() - - var/heal_general = FALSE - var/heal_poison = FALSE - var/heal_internal = FALSE - var/heal_bleeding = FALSE - var/heal_rate = 0 - var/mend_prob = 0 - - var/use_rank = get_rank(PSI_REDACTION) - if(use_rank >= PSI_RANK_PARAMOUNT) - heal_general = TRUE - heal_poison = TRUE - heal_internal = TRUE - heal_bleeding = TRUE - mend_prob = 50 - heal_rate = 7 - else if(use_rank == PSI_RANK_GRANDMASTER) - heal_poison = TRUE - heal_internal = TRUE - heal_bleeding = TRUE - mend_prob = 20 - heal_rate = 5 - else if(use_rank == PSI_RANK_MASTER) - heal_internal = TRUE - heal_bleeding = TRUE - mend_prob = 10 - heal_rate = 3 - else if(use_rank == PSI_RANK_OPERANT) - heal_bleeding = TRUE - mend_prob = 5 - heal_rate = 1 - else - return - - if(!heal_rate || stamina < heal_rate) - return // Don't backblast from trying to heal ourselves thanks. - - if(ishuman(owner)) - - var/mob/living/carbon/human/H = owner - - // Fix some pain. - if(heal_rate > 0) - H.shock_stage = max(0, H.shock_stage - max(1, round(heal_rate/2))) - - // Mend internal damage. - if(prob(mend_prob)) - - // Fix our heart if we're paramount. - if(heal_general && H.is_asystole() && H.should_have_organ(BP_HEART) && spend_power(heal_rate)) - H.resuscitate() - - // Heal organ damage. - if(heal_internal) - for(var/obj/item/organ/I in H.internal_organs) - - if(BP_IS_ROBOTIC(I)) - continue - - if(I.damage > 0 && spend_power(heal_rate)) - I.damage = max(I.damage - heal_rate, 0) - if(prob(25)) - to_chat(H, SPAN_NOTICE("Your innards itch as your autoredactive faculty mends your [I.name].")) - return - - // Heal broken bones. - if(H.bad_external_organs.len) - for(var/obj/item/organ/external/E in H.bad_external_organs) - - if(BP_IS_ROBOTIC(E)) - continue - - if(heal_internal && (E.status & ORGAN_BROKEN) && E.damage < E.min_broken_damage) // So we don't mend and autobreak. - if(spend_power(heal_rate)) - if(E.mend_fracture()) - to_chat(H, SPAN_NOTICE("Your autoredactive faculty coaxes together the shattered bones in your [E.name].")) - return - - if(heal_bleeding) - - if((E.status & ORGAN_ARTERY_CUT) && spend_power(heal_rate)) - to_chat(H, SPAN_NOTICE("Your autoredactive faculty mends the torn artery in your [E.name], stemming the worst of the bleeding.")) - E.status &= ~ORGAN_ARTERY_CUT - return - - if((E.tendon_status() & TENDON_CUT) && E.tendon.can_recover()) - to_chat(H, SPAN_NOTICE("Your autoredactive faculty repairs the severed tendon in your [E.name].")) - E.tendon.rejuvenate() - return TRUE - - for(var/datum/wound/W in E.wounds) - - if(W.bleeding() && spend_power(heal_rate)) - to_chat(H, SPAN_NOTICE("Your autoredactive faculty knits together severed veins, stemming the bleeding from \a [W.desc] on your [E.name].")) - W.bleed_timer = 0 - W.clamped = TRUE - E.status &= ~ORGAN_BLEEDING - return - - // Heal radiation, cloneloss and poisoning. - if(heal_poison) - - if(owner.total_radiation && spend_power(heal_rate)) - if(prob(25)) - to_chat(owner, SPAN_NOTICE("Your autoredactive faculty repairs some of the radiation damage to your body.")) - owner.total_radiation = max(0, owner.total_radiation - heal_rate) - return - - if(owner.getCloneLoss() && spend_power(heal_rate)) - if(prob(25)) - to_chat(owner, SPAN_NOTICE("Your autoredactive faculty stitches together some of your mangled DNA.")) - owner.adjustCloneLoss(-heal_rate) - return - - // Heal everything left. - if(heal_general && prob(mend_prob) && (owner.getBruteLoss() || owner.getFireLoss() || owner.getOxyLoss()) && spend_power(heal_rate)) - owner.adjustBruteLoss(-(heal_rate)) - owner.adjustFireLoss(-(heal_rate)) - owner.adjustOxyLoss(-(heal_rate)) - if(prob(25)) - to_chat(owner, SPAN_NOTICE("Your skin crawls as your autoredactive faculty heals your body.")) diff --git a/code/modules/psionics/equipment/cerebro_enhancers.dm b/code/modules/psionics/equipment/cerebro_enhancers.dm index 36a89e8cfa8..c16405d209d 100644 --- a/code/modules/psionics/equipment/cerebro_enhancers.dm +++ b/code/modules/psionics/equipment/cerebro_enhancers.dm @@ -14,10 +14,7 @@ ) var/operating = FALSE - var/list/boosted_faculties - var/boosted_rank = PSI_RANK_PARAMOUNT - var/unboosted_rank = PSI_RANK_MASTER - var/max_boosted_faculties = 3 + var/boosted_rank = PSI_RANK_HARMONIOUS var/boosted_psipower = 120 /obj/item/clothing/head/helmet/space/psi_amp/lesser @@ -26,51 +23,14 @@ flags_inv = 0 body_parts_covered = 0 - max_boosted_faculties = 1 - boosted_rank = PSI_RANK_MASTER - unboosted_rank = PSI_RANK_OPERANT + boosted_rank = PSI_RANK_HARMONIOUS boosted_psipower = 50 /obj/item/clothing/head/helmet/space/psi_amp/Initialize() . = ..() verbs += /obj/item/clothing/head/helmet/space/psi_amp/proc/integrate -/obj/item/clothing/head/helmet/space/psi_amp/attack_self(var/mob/user) - - if(operating) - return - - if(!canremove) - deintegrate() - return - - var/mob/living/carbon/human/H = loc - if(istype(H) && H.head == src) - integrate() - return - - var/choice = input("Select a brainboard to install or remove.","Psionic Amplifier") as null|anything in SSpsi.faculties_by_name - if(!choice) - return - - var/removed - var/slots_left = max_boosted_faculties - LAZYLEN(boosted_faculties) - var/datum/psionic_faculty/faculty = SSpsi.get_faculty(choice) - if(faculty.id in boosted_faculties) - LAZYREMOVE(boosted_faculties, faculty.id) - removed = TRUE - else - if(slots_left <= 0) - to_chat(user, SPAN_WARNING("There are no slots left to install brainboards into.")) - return - LAZYADD(boosted_faculties, faculty.id) - UNSETEMPTY(boosted_faculties) - - slots_left = max_boosted_faculties - LAZYLEN(boosted_faculties) - to_chat(user, SPAN_NOTICE("You [removed ? "remove" : "install"] the [choice] brainboard [removed ? "from" : "in"] \the [src]. There [slots_left!=1 ? "are" : "is"] [slots_left] slot\s left.")) - /obj/item/clothing/head/helmet/space/psi_amp/proc/deintegrate() - set name = "Remove Psi-Amp" set desc = "Removes your psi-amp." set category = "Abilities" @@ -121,7 +81,6 @@ canremove = TRUE /obj/item/clothing/head/helmet/space/psi_amp/proc/integrate() - set name = "Integrate Psionic Amplifier" set desc = "Enhance your brainpower." set category = "Abilities" @@ -133,10 +92,6 @@ if(!canremove) return - if(LAZYLEN(boosted_faculties) < max_boosted_faculties) - to_chat(usr, SPAN_NOTICE("You still have [max_boosted_faculties - LAZYLEN(boosted_faculties)] facult[LAZYLEN(boosted_faculties) == 1 ? "y" : "ies"] to select. Use \the [src] in-hand to select them.")) - return - var/mob/living/carbon/human/H = loc if(!istype(H) || H.head != src) to_chat(usr, SPAN_WARNING("\The [src] must be worn on your head in order to be activated.")) @@ -149,11 +104,7 @@ sleep(80) - for(var/faculty in list(PSI_COERCION, PSI_PSYCHOKINESIS, PSI_REDACTION, PSI_ENERGISTICS)) - if(faculty in boosted_faculties) - H.set_psi_rank(faculty, boosted_rank, take_larger = TRUE, temporary = TRUE) - else - H.set_psi_rank(faculty, unboosted_rank, take_larger = TRUE, temporary = TRUE) + H.set_psi_rank(boosted_rank, temporary = TRUE) if(H.psi) H.psi.max_stamina = boosted_psipower H.psi.stamina = H.psi.max_stamina @@ -165,5 +116,32 @@ operating = FALSE action_button_name = "Remove Psionic Amplifier" H.update_action_buttons() + H.client.init_verbs() set_light(0.5, 0.1, 3, 2, l_color = "#880000") + +/obj/item/psionic_jumpstarter + name = "psionic jumpstarter" + desc = "Use this to jumpstart your psionic rank to Psionically Harmonious, enabling you to use the Psionic Point Shop and buy offensive psionic abilities. \ + This won't work on species with no Zona Bovinae, like synthetics, vaurcae or dionae! This item is definitely not canon." + icon = 'icons/obj/clothing/hats.dmi' + icon_state = "amp" + contained_sprite = FALSE + +/obj/item/psionic_jumpstarter/attack_self(mob/user) + . = ..() + if(!ishuman(user)) + return + var/mob/living/carbon/human/H = user + if(!H.has_zona_bovinae()) + to_chat(H, SPAN_WARNING("You don't have a Zona Bovinae!")) + return + + if(H.psi && H.psi.get_rank() >= PSI_RANK_HARMONIOUS) + to_chat(H, SPAN_WARNING("You've already awakened your psionic potential!")) + return + + H.set_psi_rank(PSI_RANK_HARMONIOUS) + H.psi.psi_points = 8 + to_chat(H, SPAN_NOTICE("You've awakened your psionic potential. Note that you have a reduced point pool than usual.")) + qdel(src) diff --git a/code/modules/psionics/equipment/psipower.dm b/code/modules/psionics/equipment/psipower.dm index 47d537d9cc8..e5ca9d22151 100644 --- a/code/modules/psionics/equipment/psipower.dm +++ b/code/modules/psionics/equipment/psipower.dm @@ -54,3 +54,7 @@ host.drop_from_inventory(src) else qdel(src) + +/obj/item/psychic_power/damage_flags() + . = ..() + . |= DAMAGE_FLAG_PSIONIC diff --git a/code/modules/psionics/equipment/psipower_blade.dm b/code/modules/psionics/equipment/psipower_blade.dm index d73fa24dfd5..aa748370a09 100644 --- a/code/modules/psionics/equipment/psipower_blade.dm +++ b/code/modules/psionics/equipment/psipower_blade.dm @@ -1,5 +1,5 @@ /obj/item/psychic_power/psiblade - name = "psychokinetic slash" + name = "psionic blade" force = 10 sharp = 1 edge = TRUE @@ -7,21 +7,25 @@ icon_state = "psiblade_short" hitsound = 'sound/weapons/psisword.ogg' +/obj/item/psychic_power/psiblade/Initialize() + . = ..() + switch(owner.psi.get_rank()) + if(PSI_RANK_SENSITIVE) + force = 20 + armor_penetration = 10 + if(PSI_RANK_HARMONIOUS) + force = 25 + armor_penetration = 20 + if(PSI_RANK_APEX) + force = 30 + armor_penetration = 30 + icon_state = "psiblade_long" + if(PSI_RANK_LIMITLESS) + force = 40 + armor_penetration = 40 + icon_state = "psiblade_long" + /obj/item/psychic_power/psiblade/dropped(var/mob/living/user) . = ..() playsound(loc, 'sound/effects/psi/power_fail.ogg', 30, 1) QDEL_IN(src, 1) - -/obj/item/psychic_power/psiblade/master - force = 20 - maintain_cost = 2 - -/obj/item/psychic_power/psiblade/master/grand - force = 30 - maintain_cost = 3 - icon_state = "psiblade_long" - -/obj/item/psychic_power/psiblade/master/grand/paramount // Silly typechecks because rewriting old interaction code is outside of scope. - force = 50 - maintain_cost = 4 - icon_state = "psiblade_long" diff --git a/code/modules/psionics/equipment/psipower_tk.dm b/code/modules/psionics/equipment/psipower_tk.dm deleted file mode 100644 index b321a78b8e7..00000000000 --- a/code/modules/psionics/equipment/psipower_tk.dm +++ /dev/null @@ -1,94 +0,0 @@ -/obj/item/psychic_power/telekinesis - name = "telekinetic grip" - maintain_cost = 3 - icon_state = "telekinesis" - var/atom/movable/focus - -/obj/item/psychic_power/telekinesis/Destroy() - focus = null - . = ..() - -/obj/item/psychic_power/telekinesis/process() - if(!focus || !istype(focus.loc, /turf) || get_dist(get_turf(focus), get_turf(owner)) > owner.psi.get_rank(PSI_PSYCHOKINESIS)) - owner.drop_from_inventory(src) - return - . = ..() - -/obj/item/psychic_power/telekinesis/proc/set_focus(var/atom/movable/_focus) - if(!_focus.simulated || !istype(_focus.loc, /turf)) - return FALSE - - var/check_paramount - if(ismob(_focus)) - var/mob/victim = _focus - check_paramount = (victim.mob_size >= MOB_MEDIUM) - else if(isobj(_focus)) - var/obj/thing = _focus - check_paramount = (thing.w_class >= 5) - else - return FALSE - - if(_focus.anchored || (check_paramount && owner.psi.get_rank(PSI_PSYCHOKINESIS) < PSI_RANK_PARAMOUNT)) - focus = _focus - . = attack_self(owner) - if(!.) - to_chat(owner, SPAN_WARNING("\The [_focus] is too hefty for you to get a mind-grip on.")) - qdel(src) - return FALSE - - focus = _focus - overlays.Cut() - var/image/I = image(icon = focus.icon, icon_state = focus.icon_state) - I.color = focus.color - I.overlays = focus.overlays - overlays += I - return TRUE - -/obj/item/psychic_power/telekinesis/attack_self(var/mob/user) - user.visible_message(SPAN_NOTICE("\The [user] makes a strange gesture.")) - sparkle() - return focus.do_simple_ranged_interaction(user) - -/obj/item/psychic_power/telekinesis/afterattack(var/atom/target, var/mob/living/user, var/proximity) - if(!target || !user || (isobj(target) && !isturf(target.loc)) || !user.psi || !user.psi.can_use() || !user.psi.spend_power(5)) - return - - user.setClickCooldown(DEFAULT_ATTACK_COOLDOWN) - user.psi.set_cooldown(5) - - var/distance = get_dist(get_turf(user), get_turf(focus ? focus : target)) - if(distance > user.psi.get_rank(PSI_PSYCHOKINESIS)) - to_chat(user, SPAN_WARNING("Your telekinetic power won't reach that far.")) - return FALSE - - if(target == focus) - attack_self(user) - else - user.visible_message(SPAN_DANGER("\The [user] gestures sharply!")) - sparkle() - if(!istype(target, /turf) && istype(focus,/obj/item) && target.Adjacent(focus)) - var/obj/item/I = focus - var/resolved = target.attackby(I, user, user:get_organ_target()) - if(!resolved && target && I) - I.afterattack(target,user,1) // for splashing with beakers - else - if(!focus.anchored) - var/user_rank = owner.psi.get_rank(PSI_PSYCHOKINESIS) - focus.throw_at(target, user_rank*2, user_rank*10, owner) - sleep(1) - sparkle() - -/obj/item/psychic_power/telekinesis/proc/sparkle() - set waitfor = 0 - if(focus) - var/obj/effect/overlay/O = new /obj/effect/overlay(get_turf(focus)) - O.name = "sparkles" - O.anchored = 1 - O.density = 0 - O.layer = FLY_LAYER - O.set_dir(pick(cardinal)) - O.icon = 'icons/effects/effects.dmi' - O.icon_state = "nothing" - flick("empdisable",O) - sleep(5) - qdel(O) \ No newline at end of file diff --git a/code/modules/psionics/events/mini_spasm.dm b/code/modules/psionics/events/mini_spasm.dm index a4c664da8b2..9bc4aa04d09 100644 --- a/code/modules/psionics/events/mini_spasm.dm +++ b/code/modules/psionics/events/mini_spasm.dm @@ -48,16 +48,12 @@ else to_chat(victim, SPAN_DANGER("An indescribable, brain-tearing sound hisses from [icon2html(source, victim)] \the [source], and you collapse in a seizure!")) victim.seizure() - var/new_latencies = rand(2,4) - var/list/faculties = list(PSI_COERCION, PSI_REDACTION, PSI_ENERGISTICS, PSI_PSYCHOKINESIS) - for(var/i = 1 to new_latencies) - to_chat(victim, SPAN_DANGER("[pick(psi_operancy_messages)]")) - victim.adjustBrainLoss(rand(10,20)) - victim.set_psi_rank(pick_n_take(faculties), 1) - sleep(30) + to_chat(victim, SPAN_DANGER(FONT_LARGE("[pick(psi_operancy_messages)]"))) + victim.adjustBrainLoss(rand(10,20)) + victim.set_psi_rank(1) victim.psi.update() sleep(45) - victim.psi.check_latency_trigger(100, "a psionic scream", redactive = TRUE) + victim.psi.check_psionic_trigger(100, "a psionic scream", redactive = TRUE) /datum/event/minispasm/end() command_announcement.Announce( \ diff --git a/code/modules/psionics/faculties/_faculty.dm b/code/modules/psionics/faculties/_faculty.dm deleted file mode 100644 index c855ac5abef..00000000000 --- a/code/modules/psionics/faculties/_faculty.dm +++ /dev/null @@ -1,11 +0,0 @@ -/datum/psionic_faculty - var/id - var/name - var/associated_intent - var/list/armor_types = list() - var/list/powers = list() - -/datum/psionic_faculty/New() - ..() - for(var/atype in armor_types) - SSpsi.armor_faculty_by_type[atype] = id diff --git a/code/modules/psionics/faculties/_power.dm b/code/modules/psionics/faculties/_power.dm deleted file mode 100644 index a6df0e44b2f..00000000000 --- a/code/modules/psionics/faculties/_power.dm +++ /dev/null @@ -1,37 +0,0 @@ -/datum/psionic_power - var/name // Name. If null, psipower won't be generated on roundstart. - var/faculty // Associated psi faculty. - var/min_rank // Minimum psi rank to use this power. - var/cost // Base psi stamina cost for using this power. - var/cooldown // Deciseconds cooldown after using this power. - var/admin_log = TRUE // Whether or not using this power prints an admin attack log. - var/use_ranged // This power functions from a distance. - var/use_melee // This power functions at melee range. - var/use_grab // This power has a variant invoked via grab. - var/use_manifest // This power manifests an item in the user's hands. - var/use_description // A short description of how to use this power, shown via assay. - // A sound effect to play when the power is used. - var/use_sound = 'sound/effects/psi/power_used.ogg' - -/datum/psionic_power/proc/invoke(var/mob/living/user, var/atom/target) - - if(!user.psi) - return FALSE - - if(faculty && min_rank) - var/user_rank = user.psi.get_rank(faculty) - if(user_rank < min_rank) - return FALSE - - if(cost && !user.psi.spend_power(cost)) - return FALSE - - return TRUE - -/datum/psionic_power/proc/handle_post_power(var/mob/living/user, var/atom/target) - if(cooldown) - user.psi.set_cooldown(cooldown) - if(admin_log && ismob(user) && ismob(target)) - admin_attack_log(user, target, "Used psipower ([name])", "Was subjected to a psipower ([name])", "used a psipower ([name]) on") - if(use_sound) - playsound(user.loc, use_sound, 75) diff --git a/code/modules/psionics/faculties/coercion.dm b/code/modules/psionics/faculties/coercion.dm deleted file mode 100644 index f23f4887064..00000000000 --- a/code/modules/psionics/faculties/coercion.dm +++ /dev/null @@ -1,337 +0,0 @@ -/datum/psionic_faculty/coercion - id = PSI_COERCION - name = "Coercion" - associated_intent = I_DISARM - -/datum/psionic_power/coercion - faculty = PSI_COERCION - -/datum/psionic_power/coercion/invoke(var/mob/living/user, var/mob/living/target) - if (!istype(target)) - to_chat(user, SPAN_WARNING("You cannot mentally attack \the [target].")) - return FALSE - - . = ..() - -/datum/psionic_power/coercion/blindstrike - name = "Blindstrike" - cost = 8 - cooldown = 120 - use_ranged = TRUE - use_melee = TRUE - min_rank = PSI_RANK_GRANDMASTER - use_description = "Target the eyes on disarm intent and click anywhere to use a radial attack that blinds, deafens and disorients everyone near you." - -/datum/psionic_power/coercion/blindstrike/invoke(var/mob/living/user, var/mob/living/target) - if(user.zone_sel.selecting != BP_EYES) - return FALSE - . = ..() - if(.) - user.visible_message(SPAN_DANGER("\The [user] suddenly throws back their head, as though screaming silently!"), SPAN_NOTICE("You strike at all around you with a deafening psionic scream!")) - for(var/mob/living/M in orange(user.psi.get_rank(PSI_COERCION), user)) - if(M == user) - continue - if(prob(60) && iscarbon(M)) - var/mob/living/carbon/C = M - C.emote("scream") - to_chat(M, SPAN_DANGER("Your senses are blasted into oblivion by a psionic scream!")) - M.eye_blind = max(M.eye_blind,3) - M.ear_deaf = max(M.ear_deaf,6) - if(!M.isSynthetic()) - M.confused = max(M.confused, rand(3,8)) - return TRUE - -/datum/psionic_power/coercion/mindread - name = "Read Mind" - cost = 30 - cooldown = 150 //It should take a good bit to be able to use this again. - use_melee = TRUE - min_rank = PSI_RANK_OPERANT - use_description = "Target the head on disarm intent at melee range to attempt to read a victim's surface thoughts." - -/datum/psionic_power/coercion/mindread/invoke(var/mob/living/user, var/mob/living/target) - if(!isliving(target) || !istype(target) || user.zone_sel.selecting != BP_HEAD) - return FALSE - . = ..() - if(!.) - return - - if(target.stat == DEAD || (target.status_flags & FAKEDEATH) || !target.client) - to_chat(user, SPAN_WARNING("[target] is in no state for a mind-read.")) - return TRUE - - user.visible_message(SPAN_WARNING("\The [user] touches \the [target]'s temple...")) - var/question = sanitize(input(user, "Say something?", "Read Mind", "Penny for your thoughts?") as null|text) - if(!question || user.incapacitated() || !do_mob(user, target, 20)) - return TRUE - var/psi_blocked = target.is_psi_blocked() - if(psi_blocked) - to_chat(user, psi_blocked) - return TRUE - var/started_mindread = world.time - if(target.has_psi_aug()) - to_chat(user, SPAN_NOTICE("Your psyche links with [target]'s psi-receiver, seeking an answer from their mind's surface: [question]")) - to_chat(target, SPAN_NOTICE("[user]'s psyche links with your psi-receiver, your mind is compelled to answer: [question]")) - else - to_chat(user, SPAN_NOTICE("You dip your mentality into the surface layer of \the [target]'s mind, seeking an answer: [question]")) - to_chat(target, SPAN_NOTICE("Your mind is compelled to answer: [question]")) - var/answer = sanitize(input(target, "[question]\nYou have 25 seconds to type a response", "Read Mind") as null|text) - if(!answer || world.time > started_mindread + 25 SECONDS || user.stat != CONSCIOUS) - to_chat(user, SPAN_NOTICE("You receive nothing useful from \the [target].")) - to_chat(target, SPAN_NOTICE("Your mind blanks out momentarily.")) - else - to_chat(user, SPAN_NOTICE("You skim thoughts from the surface of \the [target]'s mind: [answer]")) - msg_admin_attack("[key_name(user)] read mind of [key_name(target)] with question \"[question]\" and [answer?"got answer \"[answer]\".":"got no answer."]") - return TRUE - -/datum/psionic_power/coercion/agony - name = "Agony" - cost = 8 - cooldown = 50 - use_melee = TRUE - min_rank = PSI_RANK_MASTER - use_description = "Target the chest or groin on disarm intent to use a melee attack equivalent to a strike from a stun baton." - -/datum/psionic_power/coercion/agony/invoke(var/mob/living/user, var/mob/living/target) - if(!istype(target)) - return FALSE - if(user.zone_sel.selecting != BP_CHEST && user.zone_sel.selecting != BP_GROIN) - return FALSE - . = ..() - if(.) - user.visible_message(SPAN_DANGER("\The [target] has been struck by \the [user]!")) - playsound(user.loc, 'sound/weapons/Egloves.ogg', 50, 1, -1) - target.stun_effect_act(0, 60, user.zone_sel.selecting) - return TRUE - -/datum/psionic_power/coercion/spasm - name = "Spasm" - cost = 15 - cooldown = 100 - use_melee = TRUE - use_ranged = TRUE - min_rank = PSI_RANK_MASTER - use_description = "Target the arms or hands on disarm intent to use a ranged attack that may rip the weapons away from the target." - -/datum/psionic_power/coercion/spasm/invoke(var/mob/living/user, var/mob/living/carbon/human/target) - if(!istype(target)) - return FALSE - - if(!(user.zone_sel.selecting in list(BP_L_ARM, BP_R_ARM, BP_L_HAND, BP_R_HAND))) - return FALSE - - . = ..() - - if(.) - to_chat(user, SPAN_DANGER("You lash out, stabbing into \the [target] with a lance of psi-power.")) - to_chat(target, SPAN_DANGER("The muscles in your arms cramp horrendously!")) - if(prob(75)) - target.emote("scream") - if(prob(75) && target.l_hand && target.l_hand.simulated && target.unEquip(target.l_hand)) - target.visible_message(SPAN_DANGER("\The [target] drops what they were holding as their left hand spasms!")) - if(prob(75) && target.r_hand && target.r_hand.simulated && target.unEquip(target.r_hand)) - target.visible_message(SPAN_DANGER("\The [target] drops what they were holding as their right hand spasms!")) - return TRUE - -/datum/psionic_power/coercion/mindslave - name = "Mindslave" - cost = 28 - cooldown = 200 - use_grab = TRUE - min_rank = PSI_RANK_PARAMOUNT - use_description = "Grab a victim, target the eyes, then use the grab on them while on disarm intent, in order to convert them into a loyal mind-slave. The process takes some time, and failure is punished harshly." - -/datum/psionic_power/coercion/mindslave/invoke(var/mob/living/user, var/mob/living/target) - if(!istype(target) || user.zone_sel.selecting != BP_EYES) - return FALSE - . = ..() - if(.) - if(target.stat == DEAD || (target.status_flags & FAKEDEATH)) - to_chat(user, SPAN_WARNING("\The [target] is dead!")) - return TRUE - if(!target.mind || !target.key) - to_chat(user, SPAN_WARNING("\The [target] is mindless!")) - return TRUE - for (var/obj/item/implant/mindshield/I in target) - if (I.implanted) - to_chat(user, SPAN_WARNING("\The [target]'s mind is protected from the mindslaving.")) - return TRUE - - user.visible_message(SPAN_DANGER("\The [user] seizes the head of \the [target] in both hands...")) - to_chat(user, SPAN_WARNING("You plunge your mentality into that of \the [target]...")) - to_chat(target, SPAN_DANGER("Your mind is invaded by the presence of \the [user]! They are trying to make you a slave!")) - if(!do_after(user, target.stat == CONSCIOUS ? 80 : 40, target, 0, 1)) - user.psi.backblast(rand(10,25)) - return TRUE - to_chat(user, SPAN_DANGER("You sear through \the [target]'s neurons, reshaping as you see fit and leaving them subservient to your will!")) - to_chat(target, SPAN_DANGER("Your defenses have eroded away and \the [user] has made you their mindslave.")) - thralls.add_antagonist(target.mind, TRUE, TRUE, FALSE, TRUE, TRUE) - return TRUE - -/datum/psionic_power/coercion/assay - name = "Assay" - cost = 15 - cooldown = 100 - use_grab = TRUE - min_rank = PSI_RANK_OPERANT - use_description = "Grab a patient, target the head, then use the grab on them while on disarm intent, in order to perform a deep coercive-redactive probe of their psionic potential." - -/datum/psionic_power/coercion/assay/invoke(var/mob/living/user, var/mob/living/target) - if(user.zone_sel.selecting != BP_HEAD) - return FALSE - . = ..() - if(.) - user.visible_message(SPAN_WARNING("\The [user] holds the head of \the [target] in both hands...")) - to_chat(user, SPAN_NOTICE("You insinuate your mentality into that of \the [target]...")) - to_chat(target, SPAN_WARNING("Your persona is being probed by the psychic lens of \the [user].")) - if(!do_after(user, (target.stat == CONSCIOUS ? 50 : 25), target, 0, 1)) - user.psi.backblast(rand(5,10)) - return TRUE - to_chat(user, SPAN_NOTICE("You retreat from \the [target], holding your new knowledge close.")) - to_chat(target, SPAN_DANGER("Your mental complexus is laid bare to judgement of \the [user].")) - target.show_psi_assay(user) - return TRUE - -/datum/psionic_power/coercion/focus - name = "Focus" - cost = 10 - cooldown = 80 - use_grab = TRUE - min_rank = PSI_RANK_MASTER - use_description = "Grab a patient, target the mouth, then use the grab on them while on disarm intent, in order to cure ailments of the mind." - -/datum/psionic_power/coercion/focus/invoke(var/mob/living/user, var/mob/living/target) - if(user.zone_sel.selecting != "mouth") - return FALSE - . = ..() - if(.) - user.visible_message(SPAN_WARNING("\The [user] holds the head of \the [target] in both hands...")) - to_chat(user, SPAN_NOTICE("You probe \the [target]'s mind for various ailments..")) - to_chat(target, SPAN_WARNING("Your mind is being cleansed of ailments by \the [user].")) - if(!do_after(user, (target.stat == CONSCIOUS ? 50 : 25), target, 0, 1)) - user.psi.backblast(rand(5,10)) - return TRUE - to_chat(user, SPAN_WARNING("You clear \the [target]'s mind of ailments.")) - to_chat(target, SPAN_WARNING("Your mind is cleared of ailments.")) - - var/coercion_rank = user.psi.get_rank(PSI_COERCION) - if(coercion_rank >= PSI_RANK_GRANDMASTER) - target.AdjustParalysis(-1) - target.drowsiness = 0 - if(istype(target, /mob/living/carbon)) - var/mob/living/carbon/M = target - M.hallucination = max(M.hallucination, 10) - return TRUE - -/datum/psionic_power/coercion/commune - name = "Commune" - cost = 15 - cooldown = 10 - use_melee = TRUE - use_ranged = TRUE - min_rank = PSI_RANK_OPERANT - use_sound = null - use_description = "Target the mouth and click on a creature on disarm intent to psionically send them a message." - admin_log = FALSE - -/datum/psionic_power/coercion/commune/invoke(var/mob/living/user, var/mob/living/target) - if((target == user) || user.zone_sel.selecting != BP_MOUTH) - return FALSE - . = ..() - if(.) - user.visible_message(SPAN_NOTICE("[user] blinks, their eyes briefly developing an unnatural shine.")) - if(target.stat == DEAD) - to_chat(user, SPAN_CULT("Not even a psion of your level can speak to the dead.")) - return - - var/text = input("What would you like to say?", "Speak to creature", null, null) - text = sanitize(text) - if(!text) - return - text = formalize_text(text) - - if(target.stat == DEAD) - to_chat(user, SPAN_CULT("Not even a psion of your level can speak to the dead.")) - return - - var/psi_blocked = target.is_psi_blocked() - if(psi_blocked) - to_chat(user, psi_blocked) - return - - log_say("[key_name(user)] communed to [key_name(target)]: [text]",ckey=key_name(src)) - - to_chat(user, SPAN_CULT("You psionically say to [target]: [text]")) - - for (var/mob/M in player_list) - if (istype(M, /mob/abstract/new_player)) - continue - else if(M.stat == DEAD && M.client.prefs.toggles & CHAT_GHOSTEARS) - to_chat(M, "[user] psionically says to [target]: [text]") - - var/mob/living/carbon/human/H = target - if(H.can_commune() || H.psi) - to_chat(H, SPAN_CULT("You instinctively sense [user] passing a thought into your mind: [text]")) - else if(target.has_psi_aug()) - to_chat(H, SPAN_CULT("You sense [user]'s psyche link with your psi-receiver, a thought sliding into your mind: [text]")) - else - to_chat(H, SPAN_ALIEN("A thought from outside your consciousness slips into your mind: [text]")) - -/datum/psionic_power/coercion/psiping - name = "Psi Ping" - cost = 5 - cooldown = 30 - use_melee = TRUE - use_manifest = TRUE - min_rank = PSI_RANK_OPERANT - use_sound = null - use_description = "Activate an empty hand on help intent to detect nearby psionic signatures." - admin_log = FALSE - -/datum/psionic_power/coercion/psiping/invoke(var/mob/living/user, var/mob/living/target) - if((target && user != target) || user.a_intent != I_HELP) - return FALSE - . = ..() - if(.) - to_chat(user, SPAN_NOTICE("You take a moment to tune into the local Nlom...")) - if(!do_after(user, 3 SECONDS)) - return - var/list/dirs = list() - var/turf/our_turf = get_turf(user) - for(var/mob/living/L in range(20)) - var/turf/T = get_turf(L) - if(!T || L == user || L.stat == DEAD || L.invisibility == INVISIBILITY_LEVEL_TWO) - continue - if(!L.is_psi_pingable()) - continue - var/image/ping_image = image(icon = 'icons/effects/effects.dmi', icon_state = "sonar_ping", loc = our_turf, layer = OBFUSCATION_LAYER + 0.1) - pixel_shift_to_turf(ping_image, our_turf, T) - user << ping_image - QDEL_IN(ping_image, 8) - var/direction = num2text(get_dir(user, L)) - var/dist - if(text2num(direction)) - switch(get_dist(user, L) / user.client.view) - if(0 to 0.2) - dist = "very close" - if(0.2 to 0.4) - dist = "close" - if(0.4 to 0.6) - dist = "a little ways away" - if(0.6 to 0.8) - dist = "farther away" - else - dist = "far away" - else - dist = "on top of you" - LAZYINITLIST(dirs[direction]) - dirs[direction][dist] += 1 - for(var/d in dirs) - var/list/feedback = list() - for(var/dst in dirs[d]) - feedback += "[dirs[d][dst]] psionic signature\s [dst]," - if(feedback.len > 1) - feedback[feedback.len - 1] += " and" - to_chat(user, SPAN_NOTICE("You sense " + jointext(feedback, " ") + " towards the [dir2text(text2num(d))].")) - if(!length(dirs)) - to_chat(user, SPAN_NOTICE("You detect no psionic signatures but your own.")) diff --git a/code/modules/psionics/faculties/energistics.dm b/code/modules/psionics/faculties/energistics.dm deleted file mode 100644 index 16b651f98e3..00000000000 --- a/code/modules/psionics/faculties/energistics.dm +++ /dev/null @@ -1,116 +0,0 @@ -/datum/psionic_faculty/energistics - id = PSI_ENERGISTICS - name = "Energistics" - associated_intent = I_HURT - armor_types = list("bomb", "laser", "energy") - -/datum/psionic_power/energistics - faculty = PSI_ENERGISTICS - -/datum/psionic_power/energistics/electropulse - name = "Electropulse" - cost = 40 - cooldown = 100 - use_melee = TRUE - min_rank = PSI_RANK_MASTER - use_description = "Target the right hand while on harm intent and click an object to use a melee attack that causes a localized EMP. This activates the EMP function on things, but it takes a while and applies a long cooldown, in addition to being expensive." - -/datum/psionic_power/energistics/electropulse/invoke(var/mob/living/user, var/mob/living/target) - if(user.zone_sel.selecting != BP_R_HAND) - return FALSE - if(istype(target, /turf) || ismob(target)) - return FALSE - . = ..() - if(.) - user.visible_message(SPAN_WARNING("\The [user] holds their hands over \the [target]...")) - if(do_after(user, 100, TRUE, target)) - user.visible_message(SPAN_DANGER("\The [user] releases a gout of arcing lightning over \the [target]!")) - playsound(target, 'sound/magic/LightningShock.ogg', 75) - var/severity = 1 + user.psi.get_rank(PSI_ENERGISTICS) - target.emp_act(severity) - return TRUE - -/datum/psionic_power/energistics/electrocute - name = "Electrocute" - cost = 15 - cooldown = 25 - use_melee = TRUE - min_rank = PSI_RANK_GRANDMASTER - use_description = "Target the chest or groin while on harm intent to use a melee attack that electrocutes a victim." - -/datum/psionic_power/energistics/electrocute/invoke(var/mob/living/user, var/mob/living/target) - if(user.zone_sel.selecting != BP_CHEST && user.zone_sel.selecting != BP_GROIN) - return FALSE - if(istype(target, /turf)) - return FALSE - . = ..() - if(.) - user.visible_message(SPAN_DANGER("\The [user] sends a jolt of electricity arcing into \the [target]!")) - if(istype(target)) - target.electrocute_act(rand(15,45), user, 1, user.zone_sel.selecting) - return TRUE - else if(istype(target, /atom)) - var/obj/item/cell/charging_cell = target.get_cell() - if(istype(charging_cell)) - charging_cell.give(rand(15,45)) - return TRUE - -/datum/psionic_power/energistics/lightning - name = "Lightning" - cost = 20 - cooldown = 20 - use_ranged = TRUE - min_rank = PSI_RANK_MASTER - use_description = "Use this ranged lightning attack while on harm intent. Your mastery of Energistics will determine how powerful the lightning is. Be wary of overuse, and try not to fry your own brain." - -/datum/psionic_power/energistics/lightning/invoke(var/mob/living/user, var/mob/living/target) - . = ..() - if(.) - user.visible_message(SPAN_DANGER("\The [user] suddenly holds their hand out!")) - - var/user_rank = user.psi.get_rank(faculty) - var/obj/item/projectile/pew - var/pew_sound = 'sound/magic/LightningShock.ogg' - - switch(user_rank) - if(PSI_RANK_PARAMOUNT) - pew = new /obj/item/projectile/beam/tesla/paramount(get_turf(user)) - pew.name = "thunderstrike" - pew_sound = 'sound/effects/psi/thunderstrike.ogg' - if(PSI_RANK_GRANDMASTER) - pew = new /obj/item/projectile/beam/tesla/grandmaster(get_turf(user)) - pew.name = "lightning shock" - if(PSI_RANK_MASTER) - pew = new /obj/item/projectile/beam/tesla/master(get_turf(user)) - pew.name = "lightning beam" - - if(istype(pew)) - playsound(pew.loc, pew_sound, 25, 1) - pew.original = target - pew.starting = get_turf(user) - pew.shot_from = user - pew.launch_projectile(target) - return TRUE - -/datum/psionic_power/energistics/spark - name = "Spark" - cost = 1 - cooldown = 1 - use_melee = TRUE - min_rank = PSI_RANK_OPERANT - use_description = "Target a non-living target in melee range on harm intent to cause some sparks to appear. This can light fires." - -/datum/psionic_power/energistics/spark/invoke(var/mob/living/user, var/mob/living/target) - if(isnull(target) || istype(target)) - return FALSE - . = ..() - if(.) - if(istype(target,/obj/item/clothing/mask/smokable/cigarette)) - var/obj/item/clothing/mask/smokable/cigarette/S = target - S.light(SPAN_NOTICE("\The [user] snaps [user.get_pronoun("his")] fingers and \the [S] lights up.")) - playsound(S.loc, /singleton/sound_category/spark_sound, 50, 1) - return TRUE - if(!isturf(target)) - return FALSE - spark(target, 3) - return TRUE \ No newline at end of file diff --git a/code/modules/psionics/faculties/psychokinesis.dm b/code/modules/psionics/faculties/psychokinesis.dm deleted file mode 100644 index 521fc657456..00000000000 --- a/code/modules/psionics/faculties/psychokinesis.dm +++ /dev/null @@ -1,93 +0,0 @@ -/datum/psionic_faculty/psychokinesis - id = PSI_PSYCHOKINESIS - name = "Psychokinesis" - associated_intent = I_GRAB - armor_types = list("melee", "bullet") - -/datum/psionic_power/psychokinesis - faculty = PSI_PSYCHOKINESIS - use_sound = null - -/datum/psionic_power/psychokinesis/psiblade - name = "Psiblade" - cost = 10 - cooldown = 30 - min_rank = PSI_RANK_OPERANT - use_manifest = TRUE - use_description = "Click on or otherwise activate an empty hand while on harm intent to manifest a psychokinetic cutting blade. The power the blade will vary based on your mastery of the faculty." - admin_log = FALSE - -/datum/psionic_power/psychokinesis/psiblade/invoke(var/mob/living/user, var/mob/living/target) - if((target && user != target) || user.a_intent != I_HURT) - return FALSE - . = ..() - if(.) - switch(user.psi.get_rank(faculty)) - if(PSI_RANK_PARAMOUNT) - return new /obj/item/psychic_power/psiblade/master/grand/paramount(user, user) - if(PSI_RANK_GRANDMASTER) - return new /obj/item/psychic_power/psiblade/master/grand(user, user) - if(PSI_RANK_MASTER) - return new /obj/item/psychic_power/psiblade/master(user, user) - else - return new /obj/item/psychic_power/psiblade(user, user) - -/datum/psionic_power/psychokinesis/tinker - name = "Tinker" - cost = 5 - cooldown = 10 - min_rank = PSI_RANK_MASTER - use_manifest = TRUE - use_description = "Click on or otherwise activate an empty hand while on grab intent to manifest a psychokinetic tool. Use it in-hand to switch between tool types." - admin_log = FALSE - -/datum/psionic_power/psychokinesis/tinker/invoke(var/mob/living/user, var/mob/living/target) - if((target && user != target) || user.a_intent != I_GRAB) - return FALSE - . = ..() - if(.) - return new /obj/item/psychic_power/tinker(user) - -/datum/psionic_power/psychokinesis/telekinesis - name = "Telekinesis" - cost = 5 - cooldown = 10 - use_ranged = TRUE - use_manifest = FALSE - min_rank = PSI_RANK_GRANDMASTER - use_description = "Click on a distant target while on grab intent to manifest a psychokinetic grip. Use it manipulate objects at a distance." - admin_log = FALSE - use_sound = 'sound/effects/psi/power_used.ogg' - var/global/list/valid_machine_types = list( - /obj/machinery/door - ) - -/datum/psionic_power/psychokinesis/telekinesis/invoke(var/mob/living/user, var/mob/living/target) - if(user.a_intent != I_GRAB) - return FALSE - . = ..() - if(.) - - var/distance = get_dist(user, target) - if(distance > user.psi.get_rank(PSI_PSYCHOKINESIS)) - to_chat(user, SPAN_WARNING("Your telekinetic power won't reach that far.")) - return FALSE - - if(istype(target, /mob) || istype(target, /obj)) - var/obj/item/psychic_power/telekinesis/tk = new(user) - if(tk.set_focus(target)) - tk.sparkle() - user.visible_message(SPAN_NOTICE("\The [user] reaches out.")) - return tk - else if(istype(target, /obj/structure)) - user.visible_message(SPAN_NOTICE("\The [user] makes a strange gesture.")) - var/obj/O = target - O.attack_hand(user) - return TRUE - else if(istype(target, /obj/machinery)) - for(var/mtype in valid_machine_types) - if(istype(target, mtype)) - var/obj/machinery/machine = target - machine.attack_hand(user) - return TRUE - return FALSE \ No newline at end of file diff --git a/code/modules/psionics/faculties/redaction.dm b/code/modules/psionics/faculties/redaction.dm deleted file mode 100644 index 83156be0dec..00000000000 --- a/code/modules/psionics/faculties/redaction.dm +++ /dev/null @@ -1,186 +0,0 @@ -/datum/psionic_faculty/redaction - id = PSI_REDACTION - name = "Redaction" - associated_intent = I_HELP - armor_types = list("bio", "rad") - -/datum/psionic_power/redaction - faculty = PSI_REDACTION - admin_log = FALSE - -/datum/psionic_power/redaction/proc/check_dead(var/mob/living/target) - if(!istype(target)) - return FALSE - if(target.stat == DEAD || (target.status_flags & FAKEDEATH)) - return TRUE - return FALSE - -/datum/psionic_power/redaction/invoke(var/mob/living/user, var/mob/living/target) - if(check_dead(target)) - return FALSE - . = ..() - -/datum/psionic_power/redaction/skinsight - name = "Skinsight" - cost = 3 - cooldown = 30 - use_grab = TRUE - min_rank = PSI_RANK_OPERANT - use_description = "Grab a patient, target the chest, then switch to help intent and use the grab on them to perform a check for wounds and damage." - -/datum/psionic_power/redaction/skinsight/invoke(var/mob/living/user, var/mob/living/target) - if(user.zone_sel.selecting != BP_CHEST) - return FALSE - . = ..() - if(.) - user.visible_message(SPAN_NOTICE("\The [user] rests a hand on \the [target].")) - health_scan_mob(target, user, TRUE) - return TRUE - -/datum/psionic_power/redaction/mend - name = "Mend" - cost = 7 - cooldown = 50 - use_melee = TRUE - min_rank = PSI_RANK_OPERANT - use_description = "Target a patient while on help intent at melee range to mend a variety of maladies, such as bleeding, or broken bones. Zone selection will affect which limb will be healed. Higher ranks in this faculty allow you to mend a wider range of problems." - -/datum/psionic_power/redaction/mend/invoke(var/mob/living/user, var/mob/living/carbon/human/target) - if(!istype(user) || !istype(target)) - return FALSE - . = ..() - if(.) - var/obj/item/organ/external/E = target.get_organ(user.zone_sel.selecting) - - if(!E || E.is_stump()) - to_chat(user, SPAN_WARNING("They are missing that limb.")) - return TRUE - - if(BP_IS_ROBOTIC(E)) - to_chat(user, SPAN_WARNING("That limb is prosthetic.")) - return TRUE - - user.visible_message(SPAN_NOTICE("\The [user] rests a hand on \the [target]'s [E.name]...")) - to_chat(target, SPAN_NOTICE("A healing warmth suffuses you.")) - - var/redaction_rank = user.psi.get_rank(PSI_REDACTION) - var/pk_rank = user.psi.get_rank(PSI_PSYCHOKINESIS) - if(pk_rank >= PSI_RANK_LATENT && redaction_rank >= PSI_RANK_MASTER) - var/removal_size = Clamp(5-pk_rank, 0, 5) - var/valid_objects = list() - for(var/thing in E.implants) - var/obj/imp = thing - if(imp.w_class >= removal_size && !istype(imp, /obj/item/implant)) - valid_objects += imp - if(LAZYLEN(valid_objects)) - var/removing = pick(valid_objects) - target.remove_implant(removing, TRUE) - to_chat(user, SPAN_NOTICE("You extend a tendril of psychokinetic-redactive power and carefully tease \the [removing] free of \the [E].")) - return TRUE - - if(redaction_rank >= PSI_RANK_MASTER) - if(E.status & ORGAN_ARTERY_CUT) - to_chat(user, SPAN_NOTICE("You painstakingly mend the torn veins in \the [E], stemming the internal bleeding.")) - E.status &= ~ORGAN_ARTERY_CUT - return TRUE - if((E.tendon_status() & TENDON_CUT) && E.tendon.can_recover()) - to_chat(user, SPAN_NOTICE("You interleave and repair the severed tendon in \the [E].")) - E.tendon.rejuvenate() - return TRUE - if(E.status & ORGAN_BROKEN) - to_chat(user, SPAN_NOTICE("You coax shattered bones to come together and fuse, mending the break.")) - E.status &= ~ORGAN_BROKEN - E.stage = 0 - return TRUE - - for(var/datum/wound/W in E.wounds) - if(W.bleeding()) - if(redaction_rank >= PSI_RANK_MASTER || W.wound_damage() < 30) - to_chat(user, SPAN_NOTICE("You knit together severed veins and broken flesh, stemming the bleeding.")) - W.bleed_timer = 0 - W.clamped = TRUE - E.status &= ~ORGAN_BLEEDING - return TRUE - else - to_chat(user, SPAN_WARNING("This [W.desc] is beyond your power to heal.")) - - if(redaction_rank >= PSI_RANK_GRANDMASTER) - for(var/obj/item/organ/internal/I in E.internal_organs) - if(!BP_IS_ROBOTIC(I) && I.damage > 0) - to_chat(user, SPAN_NOTICE("You encourage the damaged tissue of \the [I] to repair itself.")) - var/heal_rate = redaction_rank - I.damage = max(0, I.damage - rand(heal_rate,heal_rate*2)) - return TRUE - - to_chat(user, SPAN_NOTICE("You can find nothing within \the [target]'s [E.name] to mend.")) - return FALSE - -/datum/psionic_power/redaction/cleanse - name = "Cleanse" - cost = 9 - cooldown = 60 - use_melee = TRUE - min_rank = PSI_RANK_GRANDMASTER - use_description = "Target a patient while on help intent at melee range to cleanse radiation and genetic damage from a patient." - -/datum/psionic_power/redaction/cleanse/invoke(var/mob/living/user, var/mob/living/carbon/human/target) - if(!istype(user) || !istype(target)) - return FALSE - . = ..() - if(.) - // No messages, as Mend procs them even if it fails to heal anything, and Cleanse is always checked after Mend. - var/removing = rand(20,25) - if(target.total_radiation) - to_chat(user, SPAN_NOTICE("You repair some of the radiation-damaged tissue within \the [target]...")) - if(target.total_radiation > removing) - target.total_radiation -= removing - else - target.total_radiation = 0 - return TRUE - if(target.getCloneLoss()) - to_chat(user, SPAN_NOTICE("You stitch together some of the mangled DNA within \the [target]...")) - if(target.getCloneLoss() >= removing) - target.adjustCloneLoss(-removing) - else - target.adjustCloneLoss(-(target.getCloneLoss())) - return TRUE - to_chat(user, SPAN_NOTICE("You can find no genetic damage or radiation to heal within \the [target].")) - return TRUE - -/datum/psionic_power/revive - name = "Revive" - cost = 25 - cooldown = 80 - use_grab = TRUE - min_rank = PSI_RANK_PARAMOUNT - faculty = PSI_REDACTION - use_description = "Obtain a grab on a dead target, target the head, then select help intent and use the grab against them to attempt to bring them back to life. The process is lengthy and failure is punished harshly." - admin_log = FALSE - -/datum/psionic_power/revive/invoke(var/mob/living/user, var/mob/living/target) - if(!isliving(target) || !istype(target) || user.zone_sel.selecting != BP_HEAD) - return FALSE - . = ..() - if(.) - if(target.stat != DEAD && !(target.status_flags & FAKEDEATH)) - to_chat(user, SPAN_WARNING("This person is already alive!")) - return TRUE - - if((world.time - target.timeofdeath) > 6000) - to_chat(user, SPAN_WARNING("\The [target] has been dead for too long to revive.")) - return TRUE - - user.visible_message(SPAN_NOTICE("\The [user] splays out their hands over \the [target]'s body...")) - if(!do_after(user, 100, target, 0, 1)) - user.psi.backblast(rand(10,25)) - return TRUE - - for(var/mob/abstract/observer/G in dead_mob_list) - if(G.mind && G.mind.current == target && G.client) - to_chat(G, FONT_LARGE(SPAN_NOTICE("Your body has been revived, Re-Enter Corpse to return to it."))) - break - to_chat(target, FONT_LARGE(SPAN_NOTICE("Life floods back into your body!"))) - target.visible_message(SPAN_NOTICE("\The [target] shudders violently!")) - target.adjustOxyLoss(-rand(15,20)) - target.basic_revival() - return TRUE diff --git a/code/modules/psionics/interface/ui_hub.dm b/code/modules/psionics/interface/ui_hub.dm index decf4a61f8d..e899336a181 100644 --- a/code/modules/psionics/interface/ui_hub.dm +++ b/code/modules/psionics/interface/ui_hub.dm @@ -1,6 +1,6 @@ /obj/screen/psi/hub name = "Psi" - icon_state = "psi_suppressed" + icon_state = "psi_active" screen_loc = "EAST-1:28,CENTER-3:11" hidden = FALSE maptext_x = 6 @@ -13,7 +13,6 @@ START_PROCESSING(SSprocessing, src) /obj/screen/psi/hub/update_icon() - if(!owner.psi) return @@ -30,25 +29,68 @@ return if(!owner.psi) return - maptext = "[round((owner.psi.stamina/owner.psi.max_stamina)*100)]%" + maptext = SMALL_FONTS(7, "[round((owner.psi.stamina/owner.psi.max_stamina)*100)]%") update_icon() /obj/screen/psi/hub/Click(var/location, var/control, var/params) - var/list/click_params = params2list(params) - if(click_params["shift"]) - owner.show_psi_assay(owner) - return + ui_interact(owner) + update_icon() - if(owner.psi.suppressed && owner.psi.stun) - to_chat(owner, "You are dazed and reeling, and cannot muster enough focus to do that!") - return +/obj/screen/psi/hub/ui_interact(mob/user, datum/tgui/ui) + ui = SStgui.try_update_ui(user, src, ui) + if (!ui) + ui = new(user, src, "PsionicShop", "Psionic Point Shop", 400, 500) + ui.open() - owner.psi.suppressed = !owner.psi.suppressed - to_chat(owner, "You are [owner.psi.suppressed ? "now suppressing" : "no longer suppressing"] your psi-power.") - if(owner.psi.suppressed) - owner.psi.cancel() - owner.psi.hide_auras() - else - sound_to(owner, sound('sound/effects/psi/power_unlock.ogg')) - owner.psi.show_auras() - update_icon() \ No newline at end of file +/obj/screen/psi/hub/ui_state(mob/user) + return conscious_state + +/obj/screen/psi/hub/ui_status(mob/user, datum/ui_state/state) + return UI_INTERACTIVE + +/obj/screen/psi/hub/ui_data(mob/user) + var/list/data = list() + var/owner_rank = owner.psi.get_rank() + data["available_psionics"] = list() + data["psi_rank"] = psychic_ranks_to_strings[owner_rank] + data["psi_points"] = owner.psi.psi_points + data["bought_powers"] = owner.psi.psionic_powers + for(var/singleton/psionic_power/P in GET_SINGLETON_SUBTYPE_LIST(/singleton/psionic_power)) + if(HAS_FLAG(P.ability_flags, PSI_FLAG_SPECIAL) && !(P.type in owner.psi.psionic_powers)) + continue + if(owner_rank < P.minimum_rank) + continue + /// Apex and Limitless abilities are automatically given, but we want them to have said abilities in the point shop so the users know what they do. + if(owner_rank < PSI_RANK_APEX && HAS_FLAG(P.ability_flags, PSI_FLAG_APEX)) + continue + if(owner_rank < PSI_RANK_LIMITLESS && HAS_FLAG(P.ability_flags, PSI_FLAG_LIMITLESS)) + continue + if(NOT_FLAG(P.ability_flags, PSI_FLAG_CANON)) + if(owner_rank < PSI_RANK_HARMONIOUS && HAS_FLAG(P.ability_flags, PSI_FLAG_EVENT)) + continue + if(owner_rank < PSI_RANK_HARMONIOUS && HAS_FLAG(P.ability_flags, PSI_FLAG_ANTAG)) + continue + data["available_psionics"] += list( + list( + "name" = P.name, + "desc" = P.desc, + "point_cost" = P.point_cost, + "minimum_rank" = P.minimum_rank, + "path" = P.type + ) + ) + return data + +/obj/screen/psi/hub/ui_act(action, list/params, datum/tgui/ui, datum/ui_state/state) + . = ..() + if(.) + return TRUE + + switch(action) + if("buy") + var/psionic_path = text2path(params["buy"]) + if(ispath(psionic_path)) + var/singleton/psionic_power/P = GET_SINGLETON(psionic_path) + P.apply(owner) + to_chat(owner, SPAN_NOTICE("You are now capable of using [P.name].")) + return TRUE diff --git a/code/modules/psionics/mob/armour.dm b/code/modules/psionics/mob/armour.dm new file mode 100644 index 00000000000..f8938c8143d --- /dev/null +++ b/code/modules/psionics/mob/armour.dm @@ -0,0 +1,13 @@ +/datum/component/armor/psionic + full_block_message = "You block the blow with your mind!" + partial_block_message = "You soften the blow with your mind!" + +/datum/component/armor/psionic/Initialize(list/armor, armor_type) + . = ..() + var/datum/psi_complexus/PC = parent + PC.armor_component = src + +/datum/component/armor/psionic/Destroy() + var/datum/psi_complexus/PC = parent + PC.armor_component = null + return ..() diff --git a/code/modules/psionics/mob/mob.dm b/code/modules/psionics/mob/mob.dm index 5455c80dec1..0b95e90b064 100644 --- a/code/modules/psionics/mob/mob.dm +++ b/code/modules/psionics/mob/mob.dm @@ -5,16 +5,17 @@ ..() if(psi) psi.update(TRUE) - if(!psi.suppressed) - psi.show_auras() /mob/living/Destroy() QDEL_NULL(psi) . = ..() -/mob/living/proc/set_psi_rank(var/faculty, var/rank, var/take_larger, var/defer_update, var/temporary) +/mob/living/proc/set_psi_rank(var/rank, var/defer_update, var/temporary) + if(HAS_TRAIT(src, TRAIT_PSIONICALLY_DEAF)) + to_chat(src, SPAN_WARNING("Something tingles in your head.")) + return if(!psi) psi = new(src) - var/current_rank = psi.get_rank(faculty) - if(current_rank != rank && (!take_larger || current_rank < rank)) - psi.set_rank(faculty, rank, defer_update, temporary) + var/current_rank = psi.get_rank() + if(current_rank != rank && current_rank < rank) + psi.set_rank(rank, defer_update, temporary) diff --git a/code/modules/psionics/mob/mob_assay.dm b/code/modules/psionics/mob/mob_assay.dm deleted file mode 100644 index 18239838bd6..00000000000 --- a/code/modules/psionics/mob/mob_assay.dm +++ /dev/null @@ -1,79 +0,0 @@ -/mob/living/proc/show_psi_assay(var/mob/viewer) - - if(!viewer) viewer = usr - - var/use_He_is = "You are" - var/use_He_has = "You have" - if(istype(machine) || viewer != src) - use_He_is = "[get_pronoun("He")] [get_pronoun("is")]" - use_He_has = "[get_pronoun("He")] [get_pronoun("has")]" - - var/list/dat = list() - - dat += "

Summary

" - dat += "
" - - if(psi) - - // Hi Warhammer 40k rating system, how are you? - // I hope you get along with the Galactic Milieu metapsychics. - var/use_rating - var/effective_rating = psi.rating - if(effective_rating > 1 && psi.suppressed) - effective_rating = max(0, psi.rating-2) - var/rating_descriptor - - if(viewer != usr && thralls.is_antagonist(mind) && ishuman(viewer)) - var/mob/living/H = viewer - if(H.psi && H.psi.get_rank(PSI_REDACTION) >= PSI_RANK_GRANDMASTER) - dat += "Their mind has been cored like an apple, and enslaved by another operant psychic." - - if(!use_rating) - switch(effective_rating) - if(1) - use_rating = "[effective_rating]-Epsilon" - rating_descriptor = "This indicates the presence of minor latent psi potential with little or no operant capabilities." - if(2) - use_rating = "[effective_rating]-Delta" - rating_descriptor = "This indicates the presence of minor psi capabilities of the Operant rank or higher." - if(3) - use_rating = "[effective_rating]-Gamma" - rating_descriptor = "This indicates the presence of psi capabilities of the Master rank or higher." - if(4) - use_rating = "[effective_rating]-Beta" - rating_descriptor = "This indicates the presence of significant psi capabilities of the Grandmaster rank or higher." - if(5) - use_rating = "[effective_rating]-Alpha" - rating_descriptor = "This indicates the presence of major psi capabilities of the Paramount Grandmaster rank or higher." - else - use_rating = "[effective_rating]-Lambda" - rating_descriptor = "This indicates the presence of trace latent psi capabilities." - - dat += "[use_He_has] an overall psi rating of [use_rating].
[rating_descriptor]
" - - dat += "[use_He_is] currently [psi.suppressed ? "suppressing" : "not suppressing"] your psychic operancy.
" - dat += "[use_He_has] [psi.stamina]/[psi.max_stamina] psi stamina remaining.
" - dat += "
" - - for(var/faculty_id in psi.ranks) - var/datum/psionic_faculty/faculty = SSpsi.get_faculty(faculty_id) - if(psi.ranks[faculty.id] > 0) - dat += "[use_He_is] assayed at the rank of [psychic_ranks_to_strings[psi.ranks[faculty.id]]] for the [faculty.name] faculty.
" - else - dat += "[use_He_has] no notable power within the [faculty.name] faculty.
" - dat += "
" - - if(viewer == usr) - dat += "" - for(var/faculty_id in psi.ranks) - var/list/check_powers = psi.get_powers_by_faculty(faculty_id) - if(LAZYLEN(check_powers)) - var/datum/psionic_faculty/faculty = SSpsi.get_faculty(faculty_id) - dat += "" - for(var/datum/psionic_power/power in check_powers) - dat += "" - dat += "

Psi-power Usage

[use_He_has] access to the following psi-powers within the [faculty.name] faculty:
[power.name][power.use_description]
" - - var/datum/browser/popup = new(viewer, "psi_assay_\ref[src]", "Psi-Assay") - popup.set_content(jointext(dat,null)) - popup.open() diff --git a/code/modules/psionics/mob/mob_helpers.dm b/code/modules/psionics/mob/mob_helpers.dm index 0d7393cc0f2..3b06d4dcc9f 100644 --- a/code/modules/psionics/mob/mob_helpers.dm +++ b/code/modules/psionics/mob/mob_helpers.dm @@ -6,21 +6,24 @@ return psiaug && !psiaug.is_broken() /mob/living/proc/is_psi_blocked() - return !can_commune() + return !has_psionics() /mob/living/carbon/is_psi_blocked() - if(!psi && !has_psi_aug()) - if(isSynthetic()) - return SPAN_ALIEN("Reaching out, your mind grasps at nothing.") - if (isvaurca(src)) - return SPAN_CULT("You reach out into the Nlom; your call sails right through and yields no response.") - if (is_diona()) - return SPAN_ALIEN("[src]'s mind is incompatible, formless.") + if(HAS_TRAIT(src, TRAIT_PSIONICALLY_DEAF)) + return SPAN_WARNING("[src]'s mind is inaccessible, like hitting a brick wall.") for (var/obj/item/implant/mindshield/I in src) if (I.implanted) return SPAN_WARNING("[src]'s mind is inaccessible, like hitting a brick wall.") return FALSE +/mob/living/proc/has_zona_bovinae() + return TRUE + +/mob/living/carbon/has_zona_bovinae() + if(HAS_TRAIT(src, TRAIT_PSIONICALLY_DEAF)) + return FALSE + return TRUE + /mob/living/proc/is_psi_pingable() return !is_psi_blocked() diff --git a/code/modules/psionics/mob/mob_interactions.dm b/code/modules/psionics/mob/mob_interactions.dm deleted file mode 100644 index 2496604bc4d..00000000000 --- a/code/modules/psionics/mob/mob_interactions.dm +++ /dev/null @@ -1,37 +0,0 @@ -#define INVOKE_PSI_POWERS(holder, powers, target, return_on_invocation) \ - if(holder && holder.psi && holder.psi.can_use()) { \ - for(var/thing in powers) { \ - var/datum/psionic_power/power = thing; \ - var/obj/item/result = power.invoke(holder, target); \ - if(result) { \ - power.handle_post_power(holder, target); \ - if(istype(result)) { \ - sound_to(holder, sound('sound/effects/psi/power_evoke.ogg')); \ - LAZYADD(holder.psi.manifested_items, result); \ - holder.put_in_hands(result); \ - } \ - return return_on_invocation; \ - } \ - } \ - } - -/mob/living/UnarmedAttack(var/atom/A, var/proximity) - . = ..() - if(. && psi) - INVOKE_PSI_POWERS(src, psi.get_melee_powers(SSpsi.faculties_by_intent[a_intent]), A, FALSE) - -/mob/living/RangedAttack(var/atom/A, var/params) - if(psi) - INVOKE_PSI_POWERS(src, psi.get_ranged_powers(SSpsi.faculties_by_intent[a_intent]), A, TRUE) - . = ..() - -/mob/living/proc/check_psi_grab(var/obj/item/grab/grab) - if(psi && ismob(grab.affecting)) - INVOKE_PSI_POWERS(src, psi.get_grab_powers(SSpsi.faculties_by_intent[a_intent]), grab.affecting, FALSE) - -/mob/living/attack_empty_hand(var/bp_hand) - if(psi) - INVOKE_PSI_POWERS(src, psi.get_manifestations(), src, FALSE) - . = ..() - -#undef INVOKE_PSI_POWERS \ No newline at end of file diff --git a/code/modules/shareddream/dream_entry.dm b/code/modules/shareddream/dream_entry.dm index ae8807d1f97..6250b748db1 100644 --- a/code/modules/shareddream/dream_entry.dm +++ b/code/modules/shareddream/dream_entry.dm @@ -17,7 +17,7 @@ var/list/dream_entries = list() // If they're an Unconsious person with the abillity to do Skrellepathy. // If either changes, they should be nocked back to the real world. var/mob/living/carbon/human/srom_puller = srom_pulled_by?.resolve() - if((can_commune() || (srom_puller && Adjacent(srom_puller))) && stat == UNCONSCIOUS && sleeping > 1) + if((has_psionics() || (srom_puller && Adjacent(srom_puller))) && stat == UNCONSCIOUS && sleeping > 1) if(!istype(bg) && client) // Don't spawn a brainghost if we're not logged in. bg = new /mob/living/brain_ghost(src) // Generate a new brainghost. if(isnull(bg)) // Prevents you from getting kicked if the brain ghost didn't spawn - geeves @@ -30,7 +30,7 @@ var/list/dream_entries = list() to_chat(bg, "Whilst in shared dreaming, you find it difficult to hide your secrets.") if(willfully_sleeping) to_chat(bg, "To wake up, use the \"Awaken\" verb in the IC tab.") - if(!srom_pulling && can_commune()) + if(!srom_pulling && has_psionics()) var/obj/item/grab/G = r_hand if(!G) G = l_hand @@ -50,7 +50,7 @@ var/list/dream_entries = list() if(istype(bg) || force_wakeup) // If we choose to be asleep, keep sleeping. if(willfully_sleeping && sleeping && stat == UNCONSCIOUS) - if(can_commune() || srom_pulled_by) + if(has_psionics() || srom_pulled_by) sleeping = 5 return for(var/thing in SSpsi.processing) diff --git a/html/changelogs/mattatlas-kyoushikimurasaki.yml b/html/changelogs/mattatlas-kyoushikimurasaki.yml new file mode 100644 index 00000000000..755752b6e53 --- /dev/null +++ b/html/changelogs/mattatlas-kyoushikimurasaki.yml @@ -0,0 +1,51 @@ +################################ +# Example Changelog File +# +# Note: This file, and files beginning with ".", and files that don't end in ".yml" will not be read. If you change this file, you will look really dumb. +# +# Your changelog will be merged with a master changelog. (New stuff added only, and only on the date entry for the day it was merged.) +# When it is, any changes listed below will disappear. +# +# Valid Prefixes: +# bugfix +# wip (For works in progress) +# tweak +# soundadd +# sounddel +# rscadd (general adding of nice things) +# rscdel (general deleting of nice things) +# imageadd +# imagedel +# maptweak +# spellcheck (typo fixes) +# experiment +# balance +# admin +# backend +# security +# refactor +################################# + +# Your name. +author: MattAtlas, Butterrobber202 + +# Optional: Remove this file after generating master changelog. Useful for PR changelogs that won't get used again. +delete-after: True + +# Any changes you've made. See valid prefix list above. +# INDENT WITH TWO SPACES. NOT TABS. SPACES. +# SCREW THIS UP AND IT WON'T WORK. +# Also, all entries are changed into a single [] after a master changelog generation. Just remove the brackets when you add new entries. +# Please surround your changes in double quotes ("), as certain characters otherwise screws up compiling. The quotes will not show up in the changelog. +changes: + - experiment: "Psionics have been reworked to use the Technomancer ability system. This means that you get physical ability objects in your hand to use (in hand, melee, or ranged)." + - rscadd: "You can shift click psionic abilities at the top to obtain info about them." + - rscadd: "Suppressing your psionics is no longer a thing. Your psionics are only suppressed if you are stunned through overusing them, and they will automatically unsuppress once you recover. Psionic suppression has been introduced instead: using it will hide you from Psi-Search." + - rscadd: "Added a psionic point shop, opened by clicking your psionic hub icon. This is also usable by non-antags, but they can't buy anything: it's still useful to use it to know what your powers do and how to use them!" + - rscadd: "Antagonists get points usable in the psionic point shop. This varies per antag: Loners get 8, borer hosts get 3 (increased by borers with Advance Psionics)." + - tweak: "Reworked the Psi ranks. The canon ones are Psionically Sensitive (Skrell), Psionically Harmonious (exceptional Skrell born with better psionic control), Psionic Apex (unknown to everyone, really). There's also an admin-only non-canon Limitless level." + - rscadd: "Every 30 minutes, psionics will be pinged in their chat about their emotional state, which can be one from a selection. A few minutes after, the responses will be tallied together and the winning emotion will be broadcasted. Higher psionic rank means higher vote value." + - rscadd: "Added a psionic jumpstarter to the antagonist uplink for 19 telecrystals. It will boost you to Psionically Harmonious, but with only 8 psi points." + - rscadd: "Added a truckload of new psionic abilities. No, really. They're a lot." + - rscadd: "Skrell may choose additional psionics to spawn with in their loadout." + - rscadd: "Added mechanical support for the lack of a Zona Bovinae, mainly for vaurcae and dionae. In certain cases, a Zona Bovinae may even be removed from someone..." diff --git a/icons/mob/items/lefthand_spells.dmi b/icons/mob/items/lefthand_spells.dmi index 3c7ab3d4af518a629998be0723e3fa831b1e565b..9b7bdd5eda1fd2b447b4dc0c6218038e460d7b09 100644 GIT binary patch literal 2559 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 Qk{=!5>WFrzK?M;10dX<9T>t<8 literal 2480 zcmbVOd05if8va43C~3IgTyhPUK`kpya{)zjOEGh_a!<>q5NFg}GKI7RO)V`<(;}BF zw=u_YO|ZpWazIU7U~*4|k*o|$i5P6|ANS9D?;q!!=X=ldobSBf`M&pjWUTjLc{vR^ z0089O-7rTbYqw-jq0*B1gz>3h$x@qgEHEAu6?f{Fv$64K@i739RGQ0pEca}W%9UB0 zM2^LDo=){<4rzwkteoYziXGdkom>=)QWcJ)yYN$?8)P?CNZIjkUGLM!x9_#Et$xRam<0MKrC z4Ek77DZlL3IF1LrcYL+WL_-}PIrK6hUx6i%L6;w`)x9e7U(0Tvxkp~UNph>MugC>< zv)pF7)|B7mdqgc+yxcfc7Lon)w}ue4B(JKnX0`@>#|)v<*S&MC(-da6^(f>tIS4@( z4ur`=fWU2%aG?Og0g0JAQZ~~L7viROs`rr75}vg%wWs;3&uGoSqSFdfZS(|4>6yF1 zmw(^^+D*V?o_pD$p#_;2GJ5$EAUDxKuBA3O?`D>Q)gV6Tt zMf*~Ab7;%csq}y{n68%i*VV5WjQTjU_1lDw8)w=WVC7gmW0 zq}ThYcG7yEiY)ufkgbWevUowdzn)zQcv3EXcTG6<0yj?kK9XEwIDZ5;#8NTV5<3QM z)`g^Znelp$Q7{#^!H*CGw|@yU7$Xk`lApp*t^1rIsH-zl@E@$C`i>8F^w+N>qhZ~n zA4(5--@~dB2R>)0p;RYX+I3(-)}}}Ppbcxae*xrcmH|#%M&fG>8N(Q0lDqwZy$I9d zSKFZBVgLX^fFwB}W`v)dY6~GkFA|QuSTYf?B(imH9JG0A^wgHI#17viF)k58%rn4K z@f1jY&fJ1-RiTT=VN1 zgNh$r`qIIKDWU=I*c+J!kLgzgn&AWsOYNY;kX(x%`jd{$^##Foh9qAkY2yzw8$N=% zvtn&OH}t6>Rgk+URJKuGxD}_9;j>@T!=lsUz+R&t4U$JR3mTp_uiaW-^Z!u8O`?!q z9qCQ2{}xGZGqj|qm1j8wCe}AVd&K5^d?`3LXD%~y=y&`RG_OlkzVStJ@jH1NCpPWe zxS4&7A7^)@HZGnBR=g(GX7@5H!QUn3B^iiJ>o>@>1jXtTCNHn!E(E@ggc1@CDxx~5 z`7M~RixmiNeftI~8_J}~iMCevk z21XR`E)qCSC&+>Jx?iA2(qoP>g$1Gx#ljaGCu12?&_laams&#RVac-1E6Xz6c`ke9 z-Gp(diDNH)|3US>i~xQ4z|;Lve!UhZ`F}Jm9IY}adaKr38+K({eUDUjrul!DF3&T{hEsBS;VvbF#gASDW&Ax5@T1 z@_&6ryUo-FDCmVbpg+|l+#A*XNcMJtPO>Jrr7hs$5|@%R*tECx4QyC^Z||3 zm6fjh*%T-geYo@ut2L8Rs6}0AesJ<;KtKkG6n(`IKG8!4Hk%wBmL0!b0v02WFLug> zpp~|N*TU!};5g+CeYS9^{bR0zpHtIng#wNLS*jk2#edxtH^}~xHlFYvNB1#?g#|k& zz9nG!iT*Ov>%yxA09#|?_id>h8PPWP#;BldIJh-m0xFq}lW*gNmTh&;(D8BWn+Dys zM)7Y;vHUDlUjj_EDhgW+8LU!H<=`t|oaDPZ_NX>x#Y3*+;sY2TaP-=bN1*L7_w{!J ztgFBCq0{S8uLND&+5Xy_m|7rSbChDRPMiCN|7t6by8${70|w-WU=lKLz~_Ly58u?oyP# g!P(}g+g&0Csr7c?txat}k`@ebck#wFIE7vQ8?Sqbvj6}9 diff --git a/icons/mob/items/righthand_spells.dmi b/icons/mob/items/righthand_spells.dmi index fe934f64850e3b6f73affc88f19954e4e2bf1116..21c50cac9f88d458eed569c4df564532660f78e7 100644 GIT binary patch literal 2515 zcmb7GdoCBbKZ0IyyyMn`+fd+zUMrj&-Xdc=kwflbvdpC z)q(;5pmf5?!Ce;Hz8(lzcDs*XRgi@zF=xGS4uRN!Yax-i5KIIBL{l@enxXA*b;p@0 z6Z@v0yzuy+bB12FBqmQ!D-4zGIp0+$^i+q4Z^`RwX=T!S1mrwPq zC_yCDETiKgV(WmHI6YbQMs?PT&Tgep=s>he3kBZYQe|0s5w|y#uTZa_G^{KgDm)A8*7C1Q2>BU zoNzdLHk!(1nPJBrf$pdVwP@I*-%1vo+;DKjXJfd@XZd8U9r8QWZ)!!KJo#JugZuV= zZoM_CjQCrVRl)`QWe)EBJ;F2dL8*fv6=`R50;vN96rexFSg$>3hgY+1qe9U`72f*8lcg zFL$<;a$V}m%h$dqfGMJ-s~7udADAoE6J^*cSe+r0G#0n~Y16#DnH&*%MQRZoP+>o+ zWKJsz7-{H?`>tzZwt=+1_sh#YJK2Xb!NB_kBZqJE*~>J93}euZ{}b&$(#SfvAFwhl zgoZQZzP#`mv1-avZe=__}q^Dz{&Z^0H3D!3mzjs9^fI`tmo+(Pp>{V$6lU4 z7-^EhqGM+bYgy*0*9fo6XG(jw^8039rG`n$uuOXG3hcu3sv*=VnL3w3Xu`hif z2x&jO5z;zJuhXbQDZ~o9PXD0S+-Wsgeg&g<=b?a}u0AyC%oMpgdUQ)PJJ<@EZkGZm zUNdK9?XXaS{&!mnfQZ=Sej=NQE}__~tOj{lC|SXXj@% z#^Py0a^-Tqoc~ro-UD{`LlVmpX3tAzayGu>EBVH!3s+5$nSxgG0qLX&RnRop@Pf1{ zs1YOR%g1{FJ8ewv%>9`rYL$=TJ?T>H#yd%xw?w_DpJ=y!EbGjMGEqX|uu1pG-vKS! z(r2na654444McVZC=@d@yXoW-e;3m$$rTkp#*mP2DG{ zAM%uV8T0Hec6b+d7Ps!G+TMHC`;%!59U zy149JI91$g)w;||EMjaM@nDQh1f(;hATKihR>L}L4uSzx>65Ge!AQD#GeBf+u}Px!P- zUn>7#epw{}a!Ij$*C^^2Nd|oO+UJiR_K~ZKDxvz8cp#=yY*J^ReF%6SstFmyNUv|1 zr~y?+EoV->_WcDDF~i`n7wb@Zq*z91D>cUS+>CW#p7i@{c6d@zc!m*0km)UXTbz|(cY_RXeWx_l1kL?5 zr9O)~{GU`49t`&!XFZZH%;7J&p}K*ron}BOJyTi9O|ETyZQn7bbD{f4PwF;VMy5Sx zF+iCQI7*aM`juq7K~N_vh+isYtc-0wsxzWgHv2Sh;)OgvOhhYcaN>Y`c6FvM5;@h0 z6RXbMR|P#zZbCj#mFo~Us={SXvs*;3Q?0r+G>I3v@oRO3-P$O{H5^;c00aogfLx4!oK_CQ5{ zBzN4XuZeLq6}FI9xrjX~`p@4>>|_cAN%`sycYA=m*V7{FSiJ+qFHp0&`^%R-mxfRVm zz8T>?uK=7vwgM3 vF{w7~gOJ|;<><+>{epr18`9sK#-=huXE^vU@sHiIEe$wv%*CO~-aql*?*+6P literal 2466 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 LJzHVqcI$rtADIpj diff --git a/icons/obj/projectiles.dmi b/icons/obj/projectiles.dmi index 9ca7b95dbe5d2c78d42c5bcbde2b3b2e7cf4ca56..d36f9006e482405a85ea6fb5d886b9a19dc8d53e 100644 GIT binary patch delta 16588 zcmZv@1ymhP@GpqFyK8Xw;C6A>0KwheZE)A%?v~*0?h@RCI|K;suzdgb-k#k(+h^w7 zJN@gfuIZYos_v?ZPl2e&f=HkQAYi@KwOm1FE+)=aj;>Y?_F!P1*;Sf-_IqS#g6jr> zYiHJr#jA_@zStLwC~<`IPOX*f{z_2h9=R=2y7>vbhrpprP?7{e_V|_mO7%qpV$&BFhX(>g zIgRzdF|iYYD9weVf)K2z1jM<@XSD%Z~!WgZSzx^z6FGp(# zE%Kvr`L4}*+%dp;^b}3ucHpE``qp_2Mtm|@ii?}6xV(}e1wGkvM?zFeYgW~f2q|=+ z$Kl+oO)mcvB!_#JN+weTOxDN~90){Pt2A(V;oiY@h-Yl29B5%6W;vRzvb}l0y_2df zA7OTPL&rc4{dmWY$0IrpaR;cWN4Q#kDLe><#_cF%`(`v>l3<(&L%OkskwLN%#MPY0X(}UZBv!ary?IOQPj67)DwR}Q0b|%^k;O7N#^GEJUbdJ!c1VKFBBOY$vzGkBo zJDhkX3eOk(xb~*Qz7ibvHylE@+c%=rvE8t7ojouJfnR&rq)L-cmu~sl zbL6#nrcRj}HYSR&RaA2XqtaSR{u3H(nFS%09(HGc10#g20VGK7w(U2LdjNLO^TjXRNL8|@9Zv_%cgjjd@!y&=LWZ%F1BOb!Kz z9RxHnh_-27_J>6dKMlbiVQ=rb`4gGLV-xCym%Gzt*KZz+N8cCc^Sn=@?w$TL4oXsoXhQ4)h=hVT#{@l(7utblE*ROvMw6hX%i zQp_g{M?3{<`n$(cEq)yi9?T;_mX-0B9=i_I36M*_nkH!bD1j_^XJHKl#5*d4M`6a{AvsNSrO*OS zm_Ex_W-ZX(#W(z5%z=%NJn~KCm7-Bw1Xpqwbds_>U;q^P6Qf}o?wwoEcj^~+b5|)z z{3|@}$FM42EQ$PO^tPWoD@s7S$j{z%R7vtHZv*jJ|Gu9vcL;xRS0vP5OwqD7kp4Zi zJ_5W(9R`_9TUswYc2B`!cOhLX;rn>dVqt4S+;H2h}Rre$vis*_G}M51;E74sVG`T9ogH}m2`;xFO>mB0^EV(M=bA&3GwIkI*lH@2YbnD<9)i^SBZDo1>4d(#u z1q`-BwJQJ9Tk_|Xd=@;CtK&G1TbA1BCaq-*E`Pbn(?hg07x>X=Lm{NSFTR|*!uaTP+6X$T|@~l6l0ENcVN=H0m*gsDeb82ny;lAq}G?8ZDN(?O@RiLxbh~16!8f%A9)ZttJ9HbOD{9NiGdBpMTG0S`r_WZ2q z$sOBB0H>X2CU$I64hTPjQ|?_)pCa2(?av35$aUZq!}D0;6>DXg*&V#N^2{hEq;rij z^82gR=Fkg(ZfP%zL#C~}h!!y-EnARw=I#Fw@QVYvTx9xQfns`7L_*Pk0zY~_glUr) zxub02g(nx@qT!S(g&Rg8Ed(SRoaNMop`I}_<0l7@r+gn3xkPGEW()y zXdffQdQGGqjAv9g)}aH}6m5{8wym$QW{ z@kVOG_;!ubF=)K)%ULX3ptUgnOSz=FXz&JS^^TDz(CGhEfCsK(8YH!voxvVXrU zO@js<2)hLsTbmX&+AyS6Z%6!@vZX7L{*Vk6c0z~j1a%-C4|x;iX$gqu{7y|ksfczy zoXh2-s3n;np`y6CIx!1b0Ie#)P8;3O>Qi{OVkO~5j*$UXjO>UDSn=wPNE5XS6tdu< zv~LJX6zPMlt{&iJZVN{PyQuG8{bn@ntZnRQE}$i?TrAz!PB<%k1QfRRyNj>8!bO z1>hls7Op%CpkK@;*yE@j&eTXCS<=YJ!2Qo1i#Y!@T5slBw)@k1=D+Ah8WQ+t5zHbQ zSi}R}TD7-EP$kbzKX|1!5tWD_Jdz%s86!1caq?JqJr+xSn^z!Jcm<)X8JJS_7;}Au z#G}p*DA2|mUUQ|^ZEgK=j9h~`Wsj59@=@bA0;^3ZAu_^OAnISJu~FtDU2VM+1^Y-s z28I})c(`r}+7Hgzi`il$<&v2ePRwlqS=xPQ1g#)Z@`gKox`PT4pS>j5t5oJEK6~atDVN@L&)is8UsRR@4dDm1Kt6fQ>Q44XZ%?t`iOWxb?R4@< z?-X~Tz!bUn6PJ-cBGK&7H5u;gu&@6pg#T|wf3gi7aoErW=K!AhDGU-aN!XG`=j=R_ ze*z7weP}&HQ!)a#YNZO3RG>T^fm}&pS=0@IGUB7B2)!=1uhPmPi%hxY}8&N9Hsne|pBkM5p6X{<{apgK z-%cZ?A<&bD#{Irh>w{8+q$Aw#krH9#ax1aX#F8zYQ(uQVO1?3J-^fjJ-&<9^{t7!D z`0=96JfcM&yjvvp-BY^b048>vz}u~eB$54NDNKcD$J`x?C=cFCRNou~ggwfSenfLn zUQZ-&a!?A3c2W*cGD(~jWF@s8(~M#uFVd}KyptsCWxEkxDRZrcN5sKyW(%5b7Muu$Y3Q!RxJqBmYtuR|OWg-_U*wbYF z6hSBu9dC(Y4{EXnBq^l8?p{&77-GaR^Wi_fWIfmGkj8pg(i^%GP#zWiO?h#0&g~ia zpKGwQyL6zu4-SiS&FVoI1Y7kghAPeD;wl*CuS@AF$H31 z9J7>WvXX90nVk$UzbT~}s#LQn{sT^6VaikGTv=JmuIN4r!YB^^F$y8TpOR6vI?gJ= zvP6Cxu-$$TLCt|tQ>ig&jDC;~VmsNzvwKFJ+~c#_fjLib*X{*s;7&L~28Rf`0+M+%EGog{ z|K%ih%slP5cxiZyV#;*wbf+ALBzF2zQ*<@&Qc2<)7BEsp5oXy#CDCfo1`YW#w%0n5 z2mX+pAhkvtKPipykHSL=Az1v75ALIuH#eG>Ox_Sd7o$>Xuf-!vweAg^XdVtwv*7%D zo8L-#37raXQ}b;+#IrZe?*$0J24p?Lna%u0ySj=RS^F@-i$Ee15NGQO9E!Dj*9xAA zjEts%J5e%4x#-QVdV7CE?k5ULo>f9)!4XBeGcs1~yDUGDz+Z6JZG@#KU;-3^<8|Vk zgK|xmQ-+6!>%MbBTCN=R@GQ?>()#rP4VZ6yU%vwKVfYTcD4&HRg|39`H5OSiK5x#M z_A)T{8q6;+j1A+@Cw7GG-(vs{5!gFX|IL&-T?c>7Ej@Hx7m*u_-lgqgGDDG=s6KY< zaybNF-ojj#pV)bXm(mb03GQ=CZ!}Vlo_f==h<1L9MTYr>c3&76Ch249x2=1|C-3kZ zFe7=22r2C-C~0;m92vcQ_=Rbd+mp=&p;(M}T$T<#)xnB3$x59QNq_V1kUGKrO&V z%jtxd)syd8j)V({*7a2N{k>8fC!3)3tK7C8e;q=;NW3F9huVw%<7^jJ;XOYagL#M# z2Q|p18m|638?(BuPlRnHx*oqlK%UDUSXvXy5o=I7Hqz(}@c!C=E{;#T6@5(*Lr&Ta zL8>9{rv=Rg8n^q&*t$ZA7Q5sAFt@c=HovvvDA~QT<(>jNpdo_}6K!ngO3!IzS;tr+ z4HIcymA)pA3HdkCxhnB#p@NFUPAy)GUCO6%qjvv6M?B{_eIg-qj!t8(rUUu}7+Hul z7F2ybY7|!QaZ1S|(GHQucR8nyvq|=+&tN(Wa91}7%nG+kLxH))7~gNQo*x98maYrS z7$QND#V@dBeq@5N?^b;(Um56Af&mo#_qce29yP6i3x;J+kQoDq$aOpob9 z%TxVO5VD`Yq8~K6j$t?K>FFy5;-hBrRhHCL%(2{S8)t~L&^b1U!+4x$#Q*+F(BNcf zW@VocZrMrJ>xHSJp%%5aBb-?2yVQNlm99~7uIuRO!8iD_q@vr>e}1Y|jXTw@^k)oqTTzV(q(t5nHK8O2T{UStr#TpGinZw3w&y}brioXzRBvrF6F9iinE^DGl zPomWSu(IhLlVrnABAl=VFsa!!itr|4>W!}V*t+X(%jR;&K##v;>oSHSz+VUnk@za= zb#+e=Tq;NFAH3X-#a&ORRCK4?aaZHDQvH(3Xf=z4s$2Mrq1B3@4vb-%qMb?VB=q6d z_2YlhUaPc}D4}4b>KTR?PxgPs>ceIZNrxLEQH3oeJoG}czJ*%^8fT~79`{NAoepI0 zQrI4<^`UomLgsZRDqa+3_!P$}6)uopQ}wH(7lyKHk25~8iayPic7O9}l(aTY(k)i1 zQ_HmDX)?k4*Pt`#t1ZJtb7Dewqg^VxO2brHCW~raAwXA{Gq~^F?iScQrs7ohIVG9U zI-~YaC^ViR%2!dqd|sSx5o2k&T#C<>1XUJ1N)|sMYiyoo)Kh_aRrd#QY*Lk1C18Ha zope`b4H_%u-xRQ?z8jhE_C4h{4Vy>*N3YweJxUfGtLT%lN}z2&{iB=#gMps;FH4Ky zE(S}UGj7;}7IRVPUi$>CAv(z$uOCs0to)h1 zJXn_{+?51KsfQr!Lo1fXMiq+`gUd30V?cV(V18n~p^6HfslN0y9a}1Try*cA%V<0k z!yjMZ+_flbYSM7%ypp`=xD+})Jsp0dBw~0bo-T3Y<509PZeuMm-H<fs6FZCKce2eHD8#j)nX}~9WEj=DQlbQmgNTO4fKa}+msC`Y0l4(*Ehp!{~XtU zi|9LJHLXI4%p=$giS|+*WvnubYp476h0%||&8N^jkXQZ4dE_+UgHwTo!92!$2fDD8 zOETU+T_@Fg^pPYy7L}DY`lKiwq9{Vty)KR6*jVrkS~E(9{1+ImpmF}fTq%PaKWQ13 zC50~l21W)Z0}@pq;f`3zD^(Dd4DU>3)~j$!&Mn9?tPsf%kWa_u8h3^%rx}+VFCR?) zrEZ20l@-qn`Qw&(ZKh`!H_PkUduotOaC{RM3y^`ud-YToZ3J<(krju ztsjog_wP*w1)|@-e;>)gc-*-3=Q+Lfl*StYK$m~YIHKiCz}QvVxiao(T>8H*k6G4# zvEMG~NaP`Qh~taNn}_LyX1K?gdd7&pqXn!aD@!i=R=$;lBb*lx|Ets8gCwL(QLmLf zL|@RcI)wPSnh)J-iyZ;qI#{Al-xyP~O8zcue4ht>RxGJ43AZCtX!Tv~7m2azSiqkz zfT&+|qh$yQb72;*XTYlNZ-Koc5Qvroffg4J4KMle_|_;i!Z#)16$JA{b3f(>_4Ivw z{|k8zWTQ-Yjy|EGvd!wyoj}25Laij6k%=XuTw~}2))M+$;;TDNOa;9bsaXEcf$3rn!V z%#{rtSalx2)B`A95;7TbE8+9xgcl>!lvI=2ypCC&6hrhDeSD^~H@qmm5Z0diWa|6H znXDj1PUjZoq-)m5?Th`XH0{DuH$sC@x0{j3PaEp_Mv0YI8HbaAA|{!RQ?XM7uq${_b_+PVz?wPpb2&pQwFWdZ5|w;UB*;ELgY?Z5Hs7`8pKjz|kAy>0_mVip+>~gc7pQ z{4hgYIbQ;Etk~T-vV)KkS-Yz_)3-Eks}@%^o*4xuhu(7s%dbUsYm{9JmN69m@`0VH zs@mdJmHDz*YGglIMOB5ic4;@TekfbRJD5gMZWDql$4YM+(Bh}TyATxAJ zzcCl>%#`=|*YfiPwW%$ITSQq_e2yIH3!Xkxhs~G~S{fHzfIS7SqLgzJ6Xg7bH=}W2 zb!)aGH%Q+r8) zXt}bQs9(mXz`WRU550O(W$q+Sg64D8?=Z{CA8919n zXOs%?(_~cv<@{a2*k%*+XBG(-6YzNbx0SJshYRLbz~1%)KLYRv0s3=kcLp5x}2w3I>`FRJ-? z<@FO8)5eFuH3|~rm1+c=B&q-Kd zxpEmAxcBOmOpX*f*TNY|`{H&$D*FBx21~*-!$%*&C^%IabcYhqK)PxG6SJRu2>7mD zIh3rZo*^iJR;xLGB!DE4K9nz(6tktDjAv#F8>tQm(r2r^iYv)uVOMF$B-3+suhw8DQCF%!I#m z5uKM%NM%SEP{EqTZQc+o8q}C;Cw=_t)Xd%x`93q9nw3pwS_HADdnl06INj#zqVk@` z@S|uey4m)LkMu?B)gkktK|)VL+k%roaCKgIn_WQ-Dp8tqsm0a(e4uBueSq%)M-mg@ z*(M7sQHS%DP4=)74s{RAHe}ONjkLsDkBcw{6AFZwao$bL$6dhuh=vD=GKSz07tSd` zeU*vW+Z~wjMc`E=ch9f8ngne7qQ8ZwPouJyo5knj?uusOyiRP45AFD_XGP8^)G(gU53RM2D#9&WI`~I}ixH+~m4Y zY&>{uz1YG5x#W&qbew_MQ0rJRYP}5s$czWtsmCl@QO-WU;&Fn<8O_@n$>;Y)dD+vkGT7+Bhui9xS13s`4KIa8 z$gN`4GQ(f0!4?)nuK>989K((PW5xmU|?bxj-SDfPSzOV<+|f}(LRy^U@TQEvd!kG`rjU5Jk> z(#~41oABUIWIWVAaKz-7b-dY0g(W9WoK~F>`h6tc&X_HWeed_${xVDIQHCKy$CvBj z>CAKY0wUuy_c13XtSp3HsHR=f9J_!jLxgk~%dfdVVxM~1p(ABqZ|w1Xi~mk9qcs9Q zr7~Egw0_s!@)~xGr~xHi!?JcqZaTI|Y@{G8fqWy;IIs;vK%@obt<$*XKiZTkAg-J$ulrZHYuiDeYSX9E`WY*-0q5*CsV$;SJkn*B4UW18I z=XdtmXSs{--TBk56dPTpT)#X~fA0@5Sm}HkIN6-W(I8OQ2x_)#ku%_4ZbY4|R!<{C)V>U2+M8K^|-;86`Hd&}6*xDr5t67ZXsx{8JN2jQv4EdJ= zEi$VPMQWVh`5G91`_?n!pBs$PXNSZpf=Tj~1CwyaB0Ly{|-Ci_E%c z5rzN~IcSHHBvld{J}?Z54h$)FSxXC|>lvq?LfwX!`rGGjc2tTGY?t3t@Dx~9CSmoL zNufhkvk+RIAT*{}o)sUWN%5;W6Rti#`E9TWWOJqnSS^%zZxw;zpJTZBh~Xt*#! zsA5p+!?e5didC1PQtdn-@pF+-yUN~Mq5fsR9nr!0r=dXG#?{iBVtA5VsSK#2Eyg!V zWii2QohebPlEqw`iDHf*b`|sYhnO?|nr^@92*sbyaJZHgiqNolvmo~fg@|;Va_DrY z26CikTw}mpfYkbBH_Q|XoukkiW4PZKjWlhgq*{p}_}fZc#T&|bu<6fXrAK;Qmp8EvR7Rw+44kwDKHwcF+ZgN-z! z-}h4!DW2>l5R*XTJIv6M3DUp39E%uBW>sR?k^GDy;L~6YFiUa;B_KfFa z(_x$Lqk&emNZRFx?cn#2r$dTs1xXF@<|?My^hNvCdl~eT71j<%)en9ck^DN9jPQ~x zt5hJ^Gehaq9>~8m1?d=j(o)E>i4#aLflr8WVN5^i1W5KG1u}$*LZJ_-mi|00!c58CA2iuer^9w zaV!!996h%uR+uS{T_ zFr$+Xq3cJ+EV>Nwjjoy&Pl;ZjMp?RUa(QtZHZ0<=R$+;?b zcy(Jn5r(c<`pTFV=3D)iXDKoE?TL^si#1q$<-Pz;&}+Ex~| zm`QW!|1P09aJHY5h&jjJqUTHT4gN?++F4Q?JQj^Cz#~)Xg)YSvfN7qMcLe(j7lcYW zj`Fifm1o?Sd7r)`^IMpodhMw^WNJ!XlVs=}8-+8x7?&NHUI3wK^)SEo?Qn-c^fxqU z(R>6}SJ;j0WYw@DGl?`rS2R;JfQOv>SiddD9=`*{LM2$d-8TWH&tZ{Kg5B@@8^fhJ$+@1Xr=pwKl`T4K0qhJrakbV0n9w&dK7a zg#G5iF=VfXg5mAWioV7!R;JA4M2``Oli05Q3 z3)>;M-m6)q1o^7S0;&=$K3}9!xghjUN~D6UQ9SNHa|izaBb31TAb<3N%4Es#1MTj- zs`b_cBe4r7r=Y#(ASPi#66ZXHVCD9RI;0J6AM#(r#GqIO%v3fsB|j;JTBUp8-wg40QQi_VUMbyl}WlreA*J~2nxkGB^cAB+x(!pXTtWl)w@m5$MfpsSZinYvu18R}Dgo+@!` z!?v+Tsi#Bt#%{VY_ywfSS2A!6elBWBO&>fL$mAzLeomAc{x_e2g7~#5aMKv+%9U|Y zXnY=3s)9u}ZRK8xsu(C$B3%^g{ft}A`e#bG#r6`2!UD+q`LT^Fqecm8WJZ3dqRew- zmMq6YDy!u2=szUK?{doBXO*~I4vs&)l=zdqziIxy?KuA{j_Q=WH9x-R8DUfpyKkja zGG$vfKTntX5CPJI48a3KP+`=_1+B=$xXb=?qLZ*Kix!3!`%a@#&90YjNy4_3xMvF6 zo^cG^*IzwFR%bEqPCZ-+lX^WI+@;#BGwoR3wx!#VQj?r2M9m%;zKP(LMl^Ql&O%n- z{|jDXGpTX{ktVagK-)JCIP;+ANTB0jS5j1_$zkFdArvPY>)heTojS=A=YPv9)9`jx z&Wpj{6jlxLRIq&>DG!bPFQlpI?Z}p;PX{bt&R0y!{aP9|!p;<7f#{;wzhqE|vTaGk z#UeK4vU`@y#Ad^y1J&JmQ9QFDpE${9h(}PsG`dU%e+E}|vDAuvDpIIi9l6$5$M#%j zM#CSWVE}gHLC%m8bP7;x#7~tPYFso*SyF188@xivwMI#OL#p`N2(-m)3h5D0H;^sA zaBKb}@-VE7-Mw${yT^UAyy;7ZY@htnD?3-f?wcuPbb-( zoly%FM)kn;l|$MrGrL|&pXNXV23A<{m_1!-kL_aMS#}Gvz^uwj)38sAGJloNdhGqs ze?B0AdBPX}8DbkVV&|!~!$w$08!8g`ru@Jv@M!%!7t<*ZVXC86x!q#{rr2C*^6*wVWzxJOSUP5-3uB~ zD`drIWeQAx&pxa%f%jU#%^4H&Ret>rX6q)U z{3@jWb7&#L?!AAW=o|%CRb3-d~@eG?;t_$jpS(l!u>|4iU=r1zs427k5%JX4y z^E{{4+xU&DS-B1O+3zp$c=S`YG+2%$8Bw&N5hyxOp(F|Rz~Qks(p#(KajK@f@n@ci zwy4%+p{jqhWdNlwE>DCUAQ|eLHKZz)*BxBs`m4{Gu6M?sJF=n!?uMVYM2xp&&|x+w zkXtURwMkaCSTUtd{nUlR8Exe}p0hJg72&S@6^)cM1zSf15~_9wKZ{^lqV&Z!Wxp)-q(B21UYZPd6S&# zueetk-X9~0)eG<3qepsCk$IGM4w$S=1G~|Ht^iGKK(_6oGz7u6LTph2nc?5qL3t2Ag=jJcN+Y+NpHa5Ex=R)Tqg+K;PW zy6NiZeWmiS+Cib=cr?ng;02jzf`PoTM*jtJ&5gE;63BuF9oa&nQIMD^V(Q_JjUuRZ zYPCfHs!)(+fv~cWKV+_b3{Csw0_wYM@c0)NQl$=6VPXA^1-DWHJ*BX*Z1v%&-wju9 zUk`=6ezR^voxn^O5mSpIROF9m#(&3Q%Yv?)Cve{`8RDd)mPHgUqxr{kGLK|2(6hJ5 z44Q~EX8Yy?DMw%jREi!C7UL!1iJ^HIDw^~F01&JkDT9vcU$y%lp6QPfdl+szsg5=Fg;{NuVtLK9blG$U989a%KKy5pfvqiFwr}J*M>FxEmM|`43e)N|^vplm#iSdrzds=Lb zZu6wKH~m?UElTnyGYfTsskn8xaGk~*{3h|BKf2c^>>CzYlf<+}PQau@EJ2Lm&EwNh zUvdwoxhm{WqdY*R{opexp(x32RK9=9QEJVK3o_P&RI%mN*Cg*FZq0S#4AP_Jpq=T- zaEtgIah|EBhrgC)*=~%hnQ)$ah)b&+I#^<|+J@~k^&+j7LAJ3Wl^lvgNIPS|Gd%&k z49~$W^C9pLp3{KP2k^XL_Bj-t9!hpJ)}CI*p&uEKa@eR zoCHG3&7+<@7PYiR*o$`)O)e?I%TvzFk@ZAK%1w{(kKqNgI}g~4iRNlEjL3|+1wFbE zrZl^d&RzPGk)KutG`l;i=~>*^#;f)tPyMaa>N1Dlrz~%PF2~12Plri3UQEuvH#JIZ zTj+1DrP*{pCOxQ%raY$pQC@APqdSMKT|Hyn5YH-EjAu}c`7X1q`SKtMV_e4KlUtM% z0~yBMl+P#fye92sQyyHO{f7SUeoiJ)PjSa(90!v{iM3YQe}B&41`W9m>w@}8!Vtob zf%F>zDW)W5aT;Oq!HHNWe8;G#eye%8cd3bXcpCh_>CdaIJ6JcN^QA4+CnnlFJ70o{ z)r1MDvyHnQ*KWDL_%WVQXPefVoLUTos7>N3W^G0l$D3`0Bq6!Wzu9K}Q}Pff-SRt0 zED%!qTaIj(&&CDc!I-H@Y$hUv9zp!#4s0K=pGmrmZ^3gU>MB@|@BAp13PK!t{F^Le zQu#@h4nt9aS2yuiy4E~qS}}ujn6K17m11WhdEiba9*S`I|J{&L-usId+ptBMJb}q4 zo4AR!yVg8RYZ6q(I#3GHI9X|Cr#qn)mA;CQikM^iqgL}iz`?)0=3+BSi+9Enis7m} z_&HF$jdc1ZpFYp)<2OaZoy>;HEcpKo5Pr4uwh1TAlhfhGNHM3wX0v~jPoVyx*Jm}~ zVf%P>|4B#1DpeJIZ)uu3h?tYnio*^bpL@f2zO#$XBk}S`_`~b%^jptQ;nyA1yFgfy zQMo~YnD%*9t~Y4_pc(R{+}@A4$515PIv79=GoZ;b94_2Wj&Rp~#*Yh1qFiJ65p(HU z*%J3DeJBnb=rT~_-rTe!RyNVB{&ekC)!tjBNipV(j^zKhUuZUWx5od6B5y>HNf=XA zDL@4ora(B8W7dVf`Oy7f1pvacmRRe}ksu0AMZ?3KlR?nL%#Z2gw3>B%px$L80$wO1 zkXymxZGQHE5AW%Gi{sV9B6abmMi@hHIM%^p&SiynF5yN~n73Uw<@O_uwQpqF<;^q< zIh|yn6drFh#gP>cs`uzdMN2DUWUr4ExK?|@DSy3gDXo7`MkrlI9{D=(0n1-nUzJfV zJpQlV!GB%}0J-WhPdYrG#K&DdbZ6g^!u_pv=(05Fu$nbr;Vdf7u@WGJ3Ksoi&jSew z>FVZ2PJx7sd>|`SBtI13@yF%)T6po*StTLY;H@ERtNKO{pn1qwYvk6ZHd$-Z6Vn{_(*NX^|?@cLEs1kWF) zc|UE5hDo0ovN69Qd5n9qojeu&-4`XZ_l3WaW0dRpo&0}e(#?-s;L}66n@P6*(>Z22 zbcu6)#-B31NUq~Q0iM_t4Nfl)8lOt^-!G}fi(PB+()^kE%pw%>(2F#Z)e<_^wm#mr zK2jXNPR`7r`T6;|y1P4%5PM9HCa(O(2j^LWjic7!_)DG}?&ls8AFq=eO5%h$Hw_gy z+<{8-2~d0ju1G#!oNgOPJQiZl8Tmqv9^X~D1~3P2YI^{}y}hDTivp=XA-5c&ZySMv zGK)wrkw-@Hu~%0r6Ad$z_F~VglD06&S==h^dFvRXC%y^5>mI2^@lGjwA{Gd?h=Fh} zV}pbp?}>U|i`0{^q@KLWAE$$|XY_bH%+ zz+GU+N4n~2f~IivMxqBtdp&v1>N!ntq67PX+ElK>R{B?7^e9gtt$3J1hlo-&=QPi- zAl*^haG-P{WIaF0;HvCoy;<)O0W3u%E_uhEXm-^g<98U}YA3QEqtBjb77T0?so;4w zL=j)y#ob4OzkZL@W=4LzHzSPRbnK}Ec1lT|@{g)}BXv)5mEHbhe%Ou`63M`od_=GvkTLLPDMB#!cs24C&Zd1Ag!&Bbo=yl=Mt z8_sj=eA$A~Zo3xT%2;T;{q30id7Py352)mR45(1w6qFE7odGtCUN1^k=g)v~zVOz0 zMx^}sXId^1kx-*)(bb^=VH3}5_Qh`&XlG4WU!fhO?EkDU-TUQ^U9;6J3-_#Vi&^RJ z{}}pWHrp`1?(B7p#vAzm@?43ir%L6BZ*magz&|bf*Zu!hNpu}z@;^ylUj1x_|M?OE zJOA6406v`wqw&YLcYI#rn1OxA?#s#l1O11{KEC-QLqYHoXDvqdUdxf8C7k>t{_~!g zb+Z{(@yWqJtIXSMPP?m>fAime5@2A6O#kH)ltPknzq&rRvBUIi)jX|zK$S7;{e0r= zUwE4~`)>a+I^}&ZEWxoELJa8+;PFI`(p-sm6gcW{uI)a&aA^zAmg<7`EY?2;eKOdK z8thmK@>nnxm!?NjlwH)mF(1?iaS&tdn)$`^y7B3QlbV8 z3`%naxJVebJvy1I?dh)n4&9-05XK$xp-^!XQtX8{_r?;ig6zKe&3~78$puz>He{~9 zvNirmr6LYK@1ywrE5PgdLtBY>eC#3jH=h507rZlycnWK8(}Di=`qs~s^VFWr=bv+R zf`5|do9<|EIY}Di3C!_sXFjucKl$?JdFChO5#3*B+mLhJnPu3n^PaRlnFET@#FCRo zX~8s7Og$6JO9_Z?HhC+~i!888adSPNv!>|K-FNb)^g6ZpPUEV2=hF01@seTl2F24o zyMO6`Y42Nl=fwwd=#6vp$Jq0j^n+9K<=fGZKq*h=!89&X1>v zfE&Npn<_x)Pe3pop5sgRrb-R!rJ(__MCM zSy%&5e;jRRDIUj^zBXH;i-pzs9=7^whje&V3pX)W0PhB;}QPSsmtPaNDZ{pXOx{}Le!(d~&8tnK?=v^HZKUUVoxt-7l4H##8=sAcdQz{u zxyF}T{pG4opZ__|zxk@>wOI$6bcE;`fnzl!hn+KA#wD%hoFT5eC=b9TIUfE51ZHwt zs7)dMM}?b{2=kKgY~>M6d&rgkr?@ex@0>Z7)AHipC%%)C{fpoFvERdi{lD5$b9^Sw z{Lf#a>o7Iwo$c-I1e$EP{}zwrO4+!AJKXkv+@3zA>Av4VjKUEg78d5q)3;+b8N;*v z`K$nXB&S|CCWJiihZ_GWvOjJ%^V&W}{)Zhd7Pop%;c2)Wn*l9$-x~(lwqAExy2T+O zR|)ZGS6x5;t@C_-VCLXC?;t63mR>jDYB5a68gS~Pk37u_lTA}^)(yX>z`eoKdBS6% zM|04C5Qok$Z}1xt(tVUA67FkK;y=9>?h`9S^UF&SK?`b{H@UDn8WZ zz)w^RlRxdKZYK^P{HQ?vdPBVqa#BS{nMXgO{_`Y)LoqYPx|-z}ndMNCc=4UUC&KQH z+VN1KIj^eSW#8*f;a9|?odCE=u{XtPZPKz}PMD+pFz-I81!p6Yvg7uPSFFIt4XOK~ znh@o4Zh+{gfQ!{6`aRX2R)L@Vt5>5++OOw`TAwG_%EL_;(NnJ+=#`tl=bwvV-J^|xox}m!ENgF=*GMs8n(Lz@CnWfg4~)u^+YnMJBvp4 zUj4o`Rn5X}YWp#t@o!plW9;+fHExH;kj&O1xmu^Zq>}%tt3M6sns6C(Z!2V27Y2(< zrq_cztkxgA10P39-To7)Ob-@?qDeErN6*I%%~p@uMC!wlIPf{@6?cbr!}F8xs7r-c z%*V|HSX$=Dbs_NIpnE;f=xJSd8)|F6KPT$1Ee<^d`9}p{J{F zT5YMn^Z4`ezcP5EwIRTa)!nLlK(t_#BVp<9Gip!x^vAm=d1QoH4iQ-!U%To*Ngy!+ z4*K@TpFcGMW=xY=@0m(mM^fc>i{GKy_j#zx54R7o@&H6zNGN?kC_ zNT{_E7tuAmF8sf>mo~9=HFaBJYNwo=L>6_WFMC09i{3ot*G)wmsYjlNa(?EQO zFQefl9P7S%&Z4*PuhQ*1)^!r$+NS_&vff9RF;4p@fBVt0?O%U|9$x2dQ=1&RsRna87cUChJYuYl-K4fwS~MKM?!d(c^!}ueG5=l(WA(v4+2JCHIxH|LJa2 zA0=K$VSpLwazMe@yt#{BuL!EOGzjbuc>dNU!gRv3>_>E?g~Ge;PY7eH k?!Zx&3YevZ!uc2@g#8tAhjial2=-ZIBosl_VunHg7fxI@(EtDd delta 15788 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 U42mXSf_#yTgrazrs8PWG0Tt?28~^|S diff --git a/tgui/packages/tgui/interfaces/PsionicShop.tsx b/tgui/packages/tgui/interfaces/PsionicShop.tsx new file mode 100644 index 00000000000..fa1182e0a2a --- /dev/null +++ b/tgui/packages/tgui/interfaces/PsionicShop.tsx @@ -0,0 +1,113 @@ +import { useBackend, useLocalState } from '../backend'; +import { BlockQuote, Box, Button, Input, NoticeBox, Section } from '../components'; +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}
+
+ ))} +
+ ); +};