diff --git a/baystation12.dme b/baystation12.dme index e7a1517e31df6..4746db4d65220 100644 --- a/baystation12.dme +++ b/baystation12.dme @@ -70,7 +70,7 @@ #include "code\__defines\overmap.dm" #include "code\__defines\proc_presets.dm" #include "code\__defines\procs.dm" -#include "code\__defines\psi.dm" +// #include "code\__defines\psi.dm" #include "code\__defines\qdel.dm" #include "code\__defines\research.dm" #include "code\__defines\ruin_tags.dm" @@ -261,7 +261,7 @@ #include "code\controllers\subsystems\processing\nano.dm" #include "code\controllers\subsystems\processing\obj.dm" #include "code\controllers\subsystems\processing\processing.dm" -#include "code\controllers\subsystems\processing\psi.dm" +// #include "code\controllers\subsystems\processing\psi.dm" #include "code\controllers\subsystems\processing\temperature.dm" #include "code\controllers\subsystems\processing\turf.dm" #include "code\controllers\subsystems\processing\vines.dm" @@ -2887,48 +2887,48 @@ #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\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\complexus\complexus_topic.dm" -#include "code\modules\psionics\equipment\cerebro_enhancers.dm" -#include "code\modules\psionics\equipment\foundation_implanter.dm" -#include "code\modules\psionics\equipment\foundation_weapon.dm" -#include "code\modules\psionics\equipment\implant.dm" -#include "code\modules\psionics\equipment\null_ammo.dm" -#include "code\modules\psionics\equipment\psimeter.dm" -#include "code\modules\psionics\equipment\psimonitor.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\interface\ui_toggles.dm" -#include "code\modules\psionics\mob\mob.dm" -#include "code\modules\psionics\mob\mob_assay.dm" -#include "code\modules\psionics\mob\mob_interactions.dm" -#include "code\modules\psionics\null\_null.dm" -#include "code\modules\psionics\null\chemistry.dm" -#include "code\modules\psionics\null\flooring.dm" -#include "code\modules\psionics\null\material.dm" -#include "code\modules\psionics\null\material_sheet.dm" -#include "code\modules\psionics\null\material_weapon.dm" -#include "code\modules\psionics\null\turf_floor.dm" -#include "code\modules\psionics\null\turf_wall.dm" -#include "code\modules\psionics\null\~null.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\complexus\complexus_topic.dm" +// #include "code\modules\psionics\equipment\cerebro_enhancers.dm" +// #include "code\modules\psionics\equipment\foundation_implanter.dm" +// #include "code\modules\psionics\equipment\foundation_weapon.dm" +// #include "code\modules\psionics\equipment\implant.dm" +// #include "code\modules\psionics\equipment\null_ammo.dm" +// #include "code\modules\psionics\equipment\psimeter.dm" +// #include "code\modules\psionics\equipment\psimonitor.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\interface\ui_toggles.dm" +// #include "code\modules\psionics\mob\mob.dm" +// #include "code\modules\psionics\mob\mob_assay.dm" +// #include "code\modules\psionics\mob\mob_interactions.dm" +// #include "code\modules\psionics\null\_null.dm" +// #include "code\modules\psionics\null\chemistry.dm" +// #include "code\modules\psionics\null\flooring.dm" +// #include "code\modules\psionics\null\material.dm" +// #include "code\modules\psionics\null\material_sheet.dm" +// #include "code\modules\psionics\null\material_weapon.dm" +// #include "code\modules\psionics\null\turf_floor.dm" +// #include "code\modules\psionics\null\turf_wall.dm" +// #include "code\modules\psionics\null\~null.dm" #include "code\modules\radiation\radiation.dm" #include "code\modules\random_map\_random_map_setup.dm" #include "code\modules\random_map\random_map.dm" @@ -3455,6 +3455,7 @@ #include "mods\_master_files\code\modules\power\gravitygenerator.dm" #include "mods\_master_files\code\modules\projectiles\projectile\bullets.dm" #include "mods\_master_files\code\modules\psionics\events\mini_spasm.dm" +#include "mods\_master_files\code\modules\psionics\psi.dm" #include "mods\_master_files\code\modules\reagents\reagent_containers\food\snacks.dm" #include "mods\_master_files\code\modules\species\species.dm" #include "mods\_master_files\code\modules\species\station\adherent.dm" diff --git a/code/__defines/mobs.dm b/code/__defines/mobs.dm index d0a0b70ba8d9c..d884653d48472 100644 --- a/code/__defines/mobs.dm +++ b/code/__defines/mobs.dm @@ -500,4 +500,5 @@ ///Flags assigned to carbon mobs trait_flags when they're actively having an allergy. #define MILD_ALLERGY FLAG_01 -#define SEVERE_ALLERGY FLAG_02 \ No newline at end of file +#define SEVERE_ALLERGY FLAG_02 + diff --git a/code/__defines/psi.dm b/code/__defines/psi.dm index c03bbe7d3d357..ac4b43ae82659 100644 --- a/code/__defines/psi.dm +++ b/code/__defines/psi.dm @@ -14,4 +14,4 @@ #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_RANK_PARAMOUNT 5 diff --git a/code/__defines/~mods/~master_defines.dm b/code/__defines/~mods/~master_defines.dm index 6a88127252910..2c123ac0e9222 100644 --- a/code/__defines/~mods/~master_defines.dm +++ b/code/__defines/~mods/~master_defines.dm @@ -106,7 +106,6 @@ #define LANGUAGE_SIMPTAJARAN "Simplified Siik'Maas" //PRIMLANGS - End - //ANOMALIES #define MAX_DEEP 1 #define MIDDLE_DEEP 2 @@ -118,7 +117,6 @@ #define istitanwater(A) istype(A, /turf/simulated/floor/exoplanet/titan_water) //ANOMALIES - //FOV #define FIELD_OF_VISION_BLOCKER_PLANE -199 #define FIELD_OF_VISION_BLOCKER_RENDER_TARGET "*FIELD_OF_VISION_BLOCKER_RENDER_TARGET" @@ -130,3 +128,27 @@ // XENOBIO TRANSFORMATIONS - END +// PSIONICS - Start +#define SPECIES_PSI "Psionics" + +#define PSI_IMPLANT_AUTOMATIC "Security Level Derived" +#define PSI_IMPLANT_SHOCK "Issue Neural Shock" +#define PSI_IMPLANT_WARN "Issue Reprimand" +#define PSI_IMPLANT_LOG "Log Incident" +#define PSI_IMPLANT_DISABLED "Disabled" + +#define PSI_COERCION "coercion" +#define PSI_CONSCIOUSNESS "consciousness" +#define PSI_PSYCHOKINESIS "psychokinesis" +#define PSI_MANIFESTATION "manifestation" +#define PSI_METAKINESIS "metakinesis" +#define PSI_ENERGISTICS "energistics" +#define PSI_REDACTION "redaction" + +#define PSI_RANK_BLUNT 0 +#define PSI_RANK_LATENT 1 +#define PSI_RANK_APPRENTICE 2 +#define PSI_RANK_OPERANT 3 +#define PSI_RANK_MASTER 4 +#define PSI_RANK_GRANDMASTER 5 +// PSIONICS - End diff --git a/code/modules/admin/admin.dm b/code/modules/admin/admin.dm index c31f5826a328a..c0b6e2c21e968 100644 --- a/code/modules/admin/admin.dm +++ b/code/modules/admin/admin.dm @@ -146,7 +146,7 @@ var/global/floorIsLava = 0 body += "Remove psionics.

" body += "Trigger latencies.
" body += "" - for(var/faculty in list(PSI_COERCION, PSI_PSYCHOKINESIS, PSI_REDACTION, PSI_ENERGISTICS)) + for(var/faculty in list(PSI_COERCION, PSI_CONSCIOUSNESS, PSI_PSYCHOKINESIS, PSI_MANIFESTATION, PSI_ENERGISTICS, PSI_REDACTION, PSI_METAKINESIS)) // [SIERRA-ADD] - PSIONICS var/singleton/psionic_faculty/faculty_singleton = SSpsi.get_faculty(faculty) var/faculty_rank = psyker.psi ? psyker.psi.get_rank(faculty) : 0 body += "" diff --git a/code/modules/psionics/equipment/psipower_blade.dm b/code/modules/psionics/equipment/psipower_blade.dm index 6ef449070e75a..867967ee037ba 100644 --- a/code/modules/psionics/equipment/psipower_blade.dm +++ b/code/modules/psionics/equipment/psipower_blade.dm @@ -9,7 +9,7 @@ /obj/item/psychic_power/psiblade/master force = 20 maintain_cost = 2 - + /obj/item/psychic_power/psiblade/master/grand force = 30 maintain_cost = 3 diff --git a/maps/sierra/job/jobs_medical.dm b/maps/sierra/job/jobs_medical.dm index a5738a57d80e1..7930ca589161c 100644 --- a/maps/sierra/job/jobs_medical.dm +++ b/maps/sierra/job/jobs_medical.dm @@ -236,19 +236,20 @@ /datum/computer_file/program/suit_sensors, /datum/computer_file/program/camera_monitor ) - give_psionic_implant_on_join = FALSE + give_psionic_implant_on_join = TRUE /datum/job/psychiatrist/equip(mob/living/carbon/human/H) if(H.mind.role_alt_title == "Counselor") - psi_faculties = list("[PSI_REDACTION]" = PSI_RANK_OPERANT) + psi_faculties = list("[PSI_REDACTION]" = PSI_RANK_MASTER) if(H.mind.role_alt_title == "Mentalist") - psi_faculties = list("[PSI_COERCION]" = PSI_RANK_OPERANT) + psi_faculties = list("[PSI_CONSCIOUSNESS]" = PSI_RANK_MASTER) + return ..() /datum/job/psychiatrist/get_description_blurb() return "Вы - друг, наставник, священник... Или обычный психотерапевт. Помимо своих прямых обязанностей в обеспечении \ - персонала качественной (насколько это возможно) психологической помощью, у вас имеется особенность - вы псионически \ - одарены. Корпорация хорошо платит вам за то, чтобы вы проводили псионическое обследования членов экипажа на \ + персонала качественной (насколько это возможно) психологической помощью, у вас имеется пси-монитор. \ + Корпорация хорошо платит вам за то, чтобы вы проводили псионическое обследования членов экипажа на \ предмет обладания особыми силами, естественно, с отчетом об этом. Ваша зарплата превышает таковую у \ среднестатистческого менталиста из Фонда, и, вероятно, не просто так.
В то время, как Менталист склонен к \ исправлению психологических недугов, поиску псионики и даже чтению мыслей, Советник может проводить медицинскую \ diff --git a/mods/RnD/code/designs_autolathe/disks.dm b/mods/RnD/code/designs_autolathe/disks.dm index 00674436e4165..6e03b7ba23bb1 100644 --- a/mods/RnD/code/designs_autolathe/disks.dm +++ b/mods/RnD/code/designs_autolathe/disks.dm @@ -10,6 +10,7 @@ /datum/design/autolathe/arms_ammo/hidden, /datum/design/autolathe/arms_ammo/hidden/shotgun, /datum/design/autolathe/arms_ammo/shotgun_flash, + /datum/design/autolathe/arms_ammo/psipump, /datum/design/autolathe/arms_ammo/hidden/magazine_smg_rubber, /datum/design/autolathe/arms_ammo/hidden/flamethrower, /datum/design/autolathe/arms_ammo/hidden/speedloader, diff --git a/mods/_master_files/code/modules/psionics/psi.dm b/mods/_master_files/code/modules/psionics/psi.dm new file mode 100644 index 0000000000000..7c6336b178db4 --- /dev/null +++ b/mods/_master_files/code/modules/psionics/psi.dm @@ -0,0 +1,36 @@ +GLOBAL_LIST_AS(psychic_ranks_to_strings, list("Latent", "Apprentice", "Operant", "Masterclass", "Grandmasterclass")) + +PROCESSING_SUBSYSTEM_DEF(psi) + name = "Psychics" + priority = SS_PRIORITY_PSYCHICS + flags = SS_POST_FIRE_TIMING | SS_BACKGROUND + + var/list/faculties_by_id = list() + var/list/faculties_by_name = list() + var/list/faculties_by_name_new = 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/armour_faculty_by_type = list() + var/list/faculties_by_intent = list() + +/datum/controller/subsystem/processing/psi/proc/get_faculty(faculty) + return faculties_by_name[faculty] || faculties_by_id[faculty] + +/datum/controller/subsystem/processing/psi/Initialize(start_uptime) + var/list/faculties = GET_SINGLETON_SUBTYPE_MAP(/singleton/psionic_faculty) + for(var/ftype in faculties) + var/singleton/psionic_faculty/faculty = faculties[ftype] + faculties_by_id[faculty.id] = faculty + faculties_by_name[faculty.name] = faculty + faculties_by_intent[faculty.associated_intent] = faculty.id + faculties_by_name_new[faculty.name] = faculty.id + + var/list/powers = GET_SINGLETON_SUBTYPE_MAP(/singleton/psionic_power) + for(var/ptype in powers) + var/singleton/psionic_power/power = powers[ptype] + if(!is_abstract(power) && power.faculty) + var/singleton/psionic_faculty/faculty = get_faculty(power.faculty) + if(faculty) + faculty.powers |= power diff --git a/mods/ex666_ecosystem/code/xeno_whitelist/xeno_whitelist.dm b/mods/ex666_ecosystem/code/xeno_whitelist/xeno_whitelist.dm index f0b87d11267db..c563d2a4e95d7 100644 --- a/mods/ex666_ecosystem/code/xeno_whitelist/xeno_whitelist.dm +++ b/mods/ex666_ecosystem/code/xeno_whitelist/xeno_whitelist.dm @@ -8,7 +8,7 @@ var/global/list/admin_verbs_xeno = list( if(holder) if(holder.rights & R_XENO) verbs += admin_verbs_xeno -#define HOLDER_LIST list(SPECIES_FBP) +#define HOLDER_LIST list(SPECIES_FBP, SPECIES_PSI) /datum/admins/proc/xeno_whitelist_panel() set name = "Xenos Whitelist Panel" diff --git a/mods/global_modpacks.dm b/mods/global_modpacks.dm index fa8dfef182b9b..c5ac01c0fc659 100644 --- a/mods/global_modpacks.dm +++ b/mods/global_modpacks.dm @@ -59,4 +59,4 @@ #include "TG_signals/_tg_signals_includes.dm" #include "infinity_content/_infinity_includes.dm" #include "petting_zoo/_petting_zoo_includes.dm" - +#include "psionics/psionics_includes.dm" \ No newline at end of file diff --git a/mods/psionics/README.md b/mods/psionics/README.md new file mode 100644 index 0000000000000..2cf3e94815e17 --- /dev/null +++ b/mods/psionics/README.md @@ -0,0 +1,70 @@ + +#### Список PRов: + +- https://github.com/SierraBay/SierraBay12/pull/2780 + + + +## Псионика + +ID мода: PSIONICS + + +### Описание мода + +Порт псионики с Final Destination, добавляет ВЛьный выбор псионики из лодаута и ограничивает псионику под вайтлист + + +### Изменения *кор кода* + +- `test/check-paths.sh` : `+1 к uses of examine()` + + +### Оверрайды + +- `Отсутствуют` + + +### Дефайны + +- `code/__defines/mobs.dm`: `SPECIES_PSI` +- `code/__defines/~mods/~master_defines.dm` : `PSI_COERCION`, `PSI_CONSCIOUSNESS`, `PSI_PSYCHOKINESIS`, `PSI_MANIFESTATION`, `PSI_METAKINESIS`, `PSI_ENERGISTICS`, `PSI_REDACTION`, `PSI_RANK_BLUNT`, `PSI_RANK_LATENT`, `PSI_RANK_APPRENTICE`, `PSI_RANK_OPERANT`, `PSI_RANK_MASTER`, `PSI_RANK_GRANDMASTER`, `PSI_IMPLANT_AUTOMATIC`, `PSI_IMPLANT_SHOCK`, `PSI_IMPLANT_WARN`, `PSI_IMPLANT_LOG`, `PSI_IMPLANT_DISABLED` + + + +### Авторы: + +Roche Hendson, nasend_ +[final-destination.space](https://github.com/RepoStash/FD-NewBay) - источник + diff --git a/mods/psionics/code/complexus/complexus.dm b/mods/psionics/code/complexus/complexus.dm new file mode 100644 index 0000000000000..94c8b177648a2 --- /dev/null +++ b/mods/psionics/code/complexus/complexus.dm @@ -0,0 +1,96 @@ +/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/use_psi_armour = 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/armor_cost = 0 // Amount of power to substract this tick from psi armor blocking damage + + 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/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/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/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 + + var/list/ranks_stat // Assoc list of psi faculties to their state + +/datum/psi_complexus/proc/get_aura_image() + if(_aura_image && !istype(_aura_image)) + var/atom/A = _aura_image + log_debug("Non-image found in psi complexus: \ref[A] - \the [A] - [istype(A) ? A.type : "non-atom"]") + destroy_aura_image(_aura_image) + _aura_image = null + if(!_aura_image) + _aura_image = create_aura_image(owner) + return _aura_image + +/proc/create_aura_image(newloc) + RETURN_TYPE(/image) + var/image/aura_image = image(loc = newloc, icon = 'icons/effects/psi_aura_small.dmi', icon_state = "aura") + aura_image.blend_mode = BLEND_MULTIPLY + aura_image.appearance_flags = DEFAULT_APPEARANCE_FLAGS | NO_CLIENT_COLOR | RESET_COLOR | RESET_ALPHA | RESET_TRANSFORM + aura_image.layer = TURF_LAYER + 0.5 + aura_image.alpha = 0 + aura_image.pixel_x = -64 + aura_image.pixel_y = -64 + aura_image.mouse_opacity = 0 + aura_image.appearance_flags = DEFAULT_APPEARANCE_FLAGS + for(var/thing in SSpsi.processing) + var/datum/psi_complexus/psychic = thing + if(psychic.owner.client && !psychic.suppressed) + psychic.owner.client.images += aura_image + SSpsi.all_aura_images[aura_image] = TRUE + return aura_image + +/proc/destroy_aura_image(image/aura_image) + for(var/thing in SSpsi.processing) + var/datum/psi_complexus/psychic = thing + if(psychic.owner.client) + psychic.owner.client.images -= aura_image + SSpsi.all_aura_images -= aura_image + +/datum/psi_complexus/New(mob/_owner) + owner = _owner + START_PROCESSING(SSpsi, src) + set_extension(src, /datum/extension/armor/psionic) + +/datum/psi_complexus/Destroy() + destroy_aura_image(_aura_image) + STOP_PROCESSING(SSpsi, src) + if(owner) + cancel() + if(owner.client) + owner.client.screen -= list(ui, ui?.components) + for(var/thing in SSpsi.all_aura_images) + owner.client.images -= thing + QDEL_NULL(ui) + owner.psi = null + owner = null + + if(manifested_items) + for(var/thing in manifested_items) + qdel(thing) + manifested_items.Cut() + . = ..() diff --git a/mods/psionics/code/complexus/complexus_helpers.dm b/mods/psionics/code/complexus/complexus_helpers.dm new file mode 100644 index 0000000000000..32229c283062c --- /dev/null +++ b/mods/psionics/code/complexus/complexus_helpers.dm @@ -0,0 +1,113 @@ +/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(amount) + var/old_stun = stun + stun = max(stun, amount) + if(amount && !old_stun) + to_chat(owner, SPAN_DANGER("Your concentration has been shattered! You cannot focus your psi power!")) + ui.update_icon() + cancel() + +/datum/psi_complexus/proc/get_armour(armourtype) + if(use_psi_armour && can_use_passive()) + return round(clamp(clamp(4 * rating, 0, 20) * get_rank(SSpsi.armour_faculty_by_type[armourtype]), 0, 100) * (stamina/max_stamina)) + else + return 0 + +/datum/psi_complexus/proc/get_rank(faculty) + return LAZYACCESS(ranks, faculty) + +/datum/psi_complexus/proc/set_rank(faculty, rank, defer_update, temporary) + if(get_rank(faculty) != rank) + LAZYSET(ranks, faculty, rank) + LAZYSET(ranks_stat, faculty, TRUE) + if(!temporary) + LAZYSET(base_ranks, faculty, rank) + if(!defer_update) + update() + +/datum/psi_complexus/proc/set_cooldown(value) + next_power_use = world.time + value + ui.update_icon() + +/datum/psi_complexus/proc/can_use_passive() + return (owner.stat == CONSCIOUS && !suppressed && !stun) + +/datum/psi_complexus/proc/can_use(incapacitation_flags) + return (owner.stat == CONSCIOUS && (!incapacitation_flags || !owner.incapacitated(incapacitation_flags)) && !suppressed && !stun && world.time >= next_power_use) + +/datum/psi_complexus/proc/spend_power(value = 0, check_incapacitated) + . = FALSE + if(isnull(check_incapacitated)) + check_incapacitated = (INCAPACITATION_STUNNED|INCAPACITATION_KNOCKOUT) + if(can_use(check_incapacitated)) + value = max(1, ceil(value * cost_modifier)) + if(value <= stamina) + stamina -= value + ui.update_icon() + . = TRUE + else + backblast(abs(stamina - value)) + stamina = 0 + . = FALSE + ui.update_icon() + +/datum/psi_complexus/proc/spend_power_armor(value = 0) + armor_cost += value + +/datum/psi_complexus/proc/hide_auras() + if(owner.client) + for(var/thing in SSpsi.all_aura_images) + owner.client.images -= thing + +/datum/psi_complexus/proc/show_auras() + if(owner.client) + for(var/image/I in SSpsi.all_aura_images) + owner.client.images |= I + +/datum/psi_complexus/proc/backblast(value) + + // Can't backblast if you're controlling your power. + if(!owner || suppressed) + return FALSE + + sound_to(owner, sound('sound/effects/psi/power_feedback.ogg')) + to_chat(owner, SPAN_DANGER(FONT_LARGE("Wild energistic feedback blasts across your psyche!"))) + stunned(value * 2) + set_cooldown(value * 100) + + if(prob(value*10)) owner.seizure() + + // Your head asplode. + owner.adjustBrainLoss(value) + if(ishuman(owner)) + var/mob/living/carbon/human/pop = owner +//FD PSIONICS// + if(pop.levitation) + pop.levitation = FALSE + pop.pass_flags &= ~PASS_FLAG_TABLE + pop.pixel_y = 0 + pop.CutOverlays(image('mods/psionics/icons/psi.dmi', "levitation")) + pop.stop_floating() +//FD PSIONICS// + if(pop.should_have_organ(BP_BRAIN)) + var/obj/item/organ/internal/brain/sponge = pop.internal_organs_by_name[BP_BRAIN] + if(sponge && sponge.damage >= sponge.max_damage) + var/obj/item/organ/external/affecting = pop.get_organ(sponge.parent_organ) + if(affecting && !affecting.is_stump()) + affecting.droplimb(0, DROPLIMB_BLUNT) + if(sponge) qdel(sponge) + +/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/mods/psionics/code/complexus/complexus_latency.dm b/mods/psionics/code/complexus/complexus_latency.dm new file mode 100644 index 0000000000000..3aca920462651 --- /dev/null +++ b/mods/psionics/code/complexus/complexus_latency.dm @@ -0,0 +1,18 @@ +/datum/psi_complexus/proc/check_latency_trigger(trigger_strength = 0, source, 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) + var/new_rank = rand(2,5) + owner.set_psi_rank(faculty, new_rank) + var/singleton/psionic_faculty/faculty_singleton = SSpsi.get_faculty(faculty) + to_chat(owner, SPAN_DANGER("Ты кричишь, не издав ни звука, во время того как дисциплина [faculty_singleton.name] пробуждается в тебе из-за [source]!")) + next_latency_trigger = world.time + rand(600, 1800) * new_rank + if(!redactive) owner.adjustBrainLoss(rand(trigger_strength * 2, trigger_strength * 4)) + log_and_message_admins("получил псионику [faculty_singleton.name] с помощью: [source].", owner) + return TRUE diff --git a/mods/psionics/code/complexus/complexus_power_cache.dm b/mods/psionics/code/complexus/complexus_power_cache.dm new file mode 100644 index 0000000000000..0aad4bf1637f2 --- /dev/null +++ b/mods/psionics/code/complexus/complexus_power_cache.dm @@ -0,0 +1,49 @@ +/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/singleton/psionic_faculty/faculty_singleton = SSpsi.get_faculty(faculty) + for(var/thing in faculty_singleton.powers) + var/singleton/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(faculty) + rebuild_power_cache() + return powers_by_faculty[faculty] + +/datum/psi_complexus/proc/get_melee_powers(faculty) + rebuild_power_cache() + return melee_powers[faculty] + +/datum/psi_complexus/proc/get_ranged_powers(faculty) + rebuild_power_cache() + return ranged_powers[faculty] + +/datum/psi_complexus/proc/get_grab_powers(faculty) + rebuild_power_cache() + return grab_powers[faculty] + +/datum/psi_complexus/proc/get_manifestations() + rebuild_power_cache() + return manifestation_powers diff --git a/mods/psionics/code/complexus/complexus_process.dm b/mods/psionics/code/complexus/complexus_process.dm new file mode 100644 index 0000000000000..e0693086af645 --- /dev/null +++ b/mods/psionics/code/complexus/complexus_process.dm @@ -0,0 +1,281 @@ +/datum/psi_complexus/proc/update(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 != ceil(combined_rank/rank_count)) + if(highest_rank <= 1) + if(highest_rank == 0) + qdel(src) + return + else + rebuild_power_cache = TRUE + sound_to(owner, 'sound/effects/psi/power_unlock.ogg') + rating = ceil(combined_rank/rank_count) + cost_modifier = 1 + if(rating > 1) + cost_modifier -= min(1, max(0.1, (rating-1) / 10)) + if(!ui) + ui = new(owner) + +/* if(owner.client) + owner.client.screen += ui.components + owner.client.screen += ui + else + if(owner.client) + owner.client.screen |= ui.components + owner.client.screen |= ui*/ +//FD PSIONICS// + for(var/faculty in ranks) + var/existing_button = FALSE + for(var/obj/screen/psi/toggle_faculty/button in ui.components) + if(button.faculty_id == faculty) + existing_button = button + if(ranks[faculty] <= PSI_RANK_LATENT) + if(existing_button) + ui.components -= existing_button + qdel(existing_button) + continue + if(existing_button) + continue + var/obj/screen/psi/toggle_faculty/faculty_toggle = new(owner, faculty) + ui.components.Insert(2, faculty_toggle) + var/obj/screen/psi/toggle_psi_menu/arrow = ui.components[LAZYLEN(ui.components)] + faculty_toggle.hidden = arrow.hidden + if(owner.client) + owner.client.screen |= ui.components + owner.client.screen |= ui + if(!suppressed && owner.client) + for(var/thing in SSpsi.all_aura_images) + owner.client.images |= thing + ui.update_icon() +//FD PSIONICS// + + var/image/aura_image = get_aura_image() + if(rating >= PSI_RANK_GRANDMASTER) // spooky boosters + aura_color = "#aaffaa" + aura_image.blend_mode = BLEND_SUBTRACT + else + aura_image.blend_mode = BLEND_ADD + switch(highest_faculty) + if(PSI_COERCION) + aura_color = "#3333cc" + if(PSI_PSYCHOKINESIS) + aura_color = "#cc3333" + if(PSI_REDACTION) + aura_color = "#33cc33" + if(PSI_ENERGISTICS) + aura_color = "#cc8221" + if(PSI_CONSCIOUSNESS) + aura_color = "#5233cc" + if(PSI_METAKINESIS) + aura_color = "#cccc33" + if(PSI_MANIFESTATION) + aura_color = "#cc8221" + aura_image.pixel_x = -64 - owner.default_pixel_x + aura_image.pixel_y = -64 - owner.default_pixel_y + + if(!announced && owner && owner.client && !QDELETED(src)) + announced = TRUE + to_chat(owner, "
") + to_chat(owner, SPAN_NOTICE(FONT_LARGE("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, "
") + +/datum/psi_complexus/Process() + var/update_hud + if(armor_cost) + var/value = max(1, ceil(armor_cost * cost_modifier)) + if(value <= stamina) + stamina -= value + else + backblast(abs(stamina - value)) + stamina = 0 + update_hud = TRUE + armor_cost = 0 + + if(stun) + stun-- + if(stun) + if(!suppressed) + suppressed = TRUE + update_hud = TRUE + else + to_chat(owner, SPAN_NOTICE("You have recovered your mental composure.")) + update_hud = TRUE + else + var/psi_leech = owner.do_psionics_check() + if(psi_leech) + if(stamina > 10) + stamina = max(0, stamina - rand(15,20)) + //to_chat(owner, SPAN_DANGER("You feel your psi-power leeched away by \the [psi_leech]...")) + else + stamina++ + else if(stamina < max_stamina) + if(owner.stat == CONSCIOUS) + stamina = min(max_stamina, stamina + rand(1,3)) + 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_APPRENTICE) + attempt_regeneration() + + 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) + + if(next_aura_alpha != last_aura_alpha || next_aura_size != last_aura_size || aura_color != last_aura_color) + last_aura_size = next_aura_size + last_aura_alpha = next_aura_alpha + last_aura_color = aura_color + animate( + get_aura_image(), + alpha = next_aura_alpha, + transform = matrix().Update(scale_x = next_aura_size, scale_y = next_aura_size), + color = aura_color, + time = 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) + switch(use_rank) + if(PSI_RANK_GRANDMASTER) + heal_general = TRUE + heal_poison = TRUE + heal_internal = TRUE + heal_bleeding = TRUE + mend_prob = 50 + heal_rate = 7 + if(PSI_RANK_MASTER) + heal_poison = TRUE + heal_internal = TRUE + heal_bleeding = TRUE + mend_prob = 20 + heal_rate = 5 + if(PSI_RANK_OPERANT) + heal_internal = TRUE + heal_bleeding = TRUE + mend_prob = 10 + heal_rate = 3 + if(PSI_RANK_APPRENTICE) + 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) || BP_IS_CRYSTAL(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(LAZYLEN(H.bad_external_organs)) + 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 * config.organ_health_multiplier)) // 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.status & ORGAN_TENDON_CUT) + to_chat(H, SPAN_NOTICE("Your autoredactive faculty repairs the severed tendon in your [E.name].")) + E.status &= ~ORGAN_TENDON_CUT + 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.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.radiation = max(0, owner.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/mods/psionics/code/complexus/complexus_topic.dm b/mods/psionics/code/complexus/complexus_topic.dm new file mode 100644 index 0000000000000..e785e99983a62 --- /dev/null +++ b/mods/psionics/code/complexus/complexus_topic.dm @@ -0,0 +1,20 @@ +/datum/psi_complexus/CanUseTopic(mob/user, datum/topic_state/state = GLOB.default_state) + return (user.client && check_rights(R_ADMIN, FALSE, user.client)) + +/datum/psi_complexus/Topic(href, list/href_list) + . = ..() + if(!. && check_rights(R_ADMIN)) + if(href_list && href_list["remove_psionics"]) + if(!QDELETED(src) && !QDELETED(owner?.psi) && owner.psi == src) + log_and_message_admins("removed all psionics from [key_name(owner)].") + to_chat(owner, SPAN_NOTICE("Your psionic powers vanish abruptly, leaving you cold and empty.")) + QDEL_NULL(owner.psi) + . = TRUE + if(href_list && href_list["trigger_psi_latencies"]) + log_and_message_admins("triggered psi latencies for [key_name(owner)].") + check_latency_trigger(100, "outside intervention", redactive = TRUE) + . = TRUE + if(.) + var/datum/admins/admin = GLOB.admins[usr.key] + if(istype(admin)) + admin.show_player_panel(owner) diff --git a/mods/psionics/code/equipment/cerebro_enhancers.dm b/mods/psionics/code/equipment/cerebro_enhancers.dm new file mode 100644 index 0000000000000..f6d0b4507e103 --- /dev/null +++ b/mods/psionics/code/equipment/cerebro_enhancers.dm @@ -0,0 +1,167 @@ +//Psi-boosting item (antag only) +/obj/item/clothing/head/helmet/space/psi_amp + name = "cerebro-energetic enhancer" + desc = "A matte-black, eyeless cerebro-energetic enhancement helmet. It uses highly sophisticated, and illegal, techniques to drill into your brain and install psi-infected AIs into the fluid cavities between your lobes." + action_button_name = "Install Boosters" + icon_state = "cerebro" + + item_state_slots = list( + slot_l_hand_str = "helmet", + slot_r_hand_str = "helmet" + ) + + var/operating = FALSE + var/list/boosted_faculties + var/boosted_rank = PSI_RANK_GRANDMASTER + var/unboosted_rank = PSI_RANK_MASTER + var/max_boosted_faculties = 3 + var/boosted_psipower = 120 + +/obj/item/clothing/head/helmet/space/psi_amp/lesser + name = "psionic amplifier" + desc = "A crown-of-thorns cerebro-energetic enhancer that interfaces directly with the brain, isolating and strengthening psionic signals. It kind of looks like a tiara having sex with an industrial robot." + icon_state = "amp" + flags_inv = 0 + body_parts_covered = 0 + + max_boosted_faculties = 1 + boosted_rank = PSI_RANK_MASTER + unboosted_rank = PSI_RANK_APPRENTICE + 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(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/singleton/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 = "Enhance your brainpower." + set category = "Abilities" + set src in usr + + if(operating) + return + + if(canremove) + return + + var/mob/living/carbon/human/H = loc + if(!istype(H) || H.head != src) + canremove = TRUE + return + + to_chat(H, SPAN_WARNING("You feel a strange tugging sensation as \the [src] begins removing the slave-minds from your brain...")) + playsound(H, 'sound/weapons/circsawhit.ogg', 50, 1, -1) + operating = TRUE + + sleep(80) + + if(H.psi) + H.psi.reset() + + to_chat(H, SPAN_NOTICE("\The [src] chimes quietly as it finishes removing the slave-minds from your brain.")) + + canremove = TRUE + operating = FALSE + + verbs -= /obj/item/clothing/head/helmet/space/psi_amp/proc/deintegrate + verbs |= /obj/item/clothing/head/helmet/space/psi_amp/proc/integrate + + action_button_name = "Integrate Psionic Amplifier" + H.update_action_buttons() + + set_light(0) + +/obj/item/clothing/head/helmet/space/psi_amp/Move() + var/lastloc = loc + . = ..() + if(.) + var/mob/living/carbon/human/H = lastloc + if(istype(H) && H.psi) + H.psi.reset() + H = loc + if(!istype(H) || H.head != src) + 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" + set src in usr + + if(operating) + return + + 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.")) + return + + canremove = FALSE + operating = TRUE + to_chat(H, SPAN_WARNING("You feel a series of sharp pinpricks as \the [src] anaesthetises your scalp before drilling down into your brain.")) + playsound(H, 'sound/weapons/circsawhit.ogg', 50, 1, -1) + + sleep(80) + + for(var/faculty in list(PSI_COERCION, PSI_CONSCIOUSNESS, PSI_PSYCHOKINESIS, PSI_MANIFESTATION, PSI_METAKINESIS, PSI_ENERGISTICS, PSI_REDACTION)) + 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) + if(H.psi) + H.psi.max_stamina = boosted_psipower + H.psi.stamina = H.psi.max_stamina + H.psi.update(force = TRUE) + + to_chat(H, SPAN_NOTICE("You experience a brief but powerful wave of deja vu as \the [src] finishes modifying your brain.")) + verbs |= /obj/item/clothing/head/helmet/space/psi_amp/proc/deintegrate + verbs -= /obj/item/clothing/head/helmet/space/psi_amp/proc/integrate + operating = FALSE + action_button_name = "Remove Psionic Amplifier" + H.update_action_buttons() + + set_light(3, 0.5, l_color = "#880000") diff --git a/mods/psionics/code/equipment/foundation_implanter.dm b/mods/psionics/code/equipment/foundation_implanter.dm new file mode 100644 index 0000000000000..0db61b4dbc7fa --- /dev/null +++ b/mods/psionics/code/equipment/foundation_implanter.dm @@ -0,0 +1,40 @@ +#ifndef PSI_IMPLANT_AUTOMATIC +#define PSI_IMPLANT_AUTOMATIC "Security Level Derived" +#endif +#ifndef PSI_IMPLANT_SHOCK +#define PSI_IMPLANT_SHOCK "Issue Neural Shock" +#endif +#ifndef PSI_IMPLANT_WARN +#define PSI_IMPLANT_WARN "Issue Reprimand" +#endif +#ifndef PSI_IMPLANT_LOG +#define PSI_IMPLANT_LOG "Log Incident" +#endif +#ifndef PSI_IMPLANT_DISABLED +#define PSI_IMPLANT_DISABLED "Disabled" +#endif + +/obj/item/implanter/psi + name = "psi-null implanter" + desc = "An implant gun customized to interact with psi dampeners." + var/implanter_mode = PSI_IMPLANT_AUTOMATIC + +/obj/item/implanter/psi/attack_self(mob/user) + var/choice = input("Select a new implant mode.", "Psi Dampener") as null|anything in list(PSI_IMPLANT_AUTOMATIC, PSI_IMPLANT_SHOCK, PSI_IMPLANT_WARN, PSI_IMPLANT_LOG, PSI_IMPLANT_DISABLED) + if(!choice || user != loc) return + var/obj/item/implant/psi_control/implant = imp + if(!istype(implant)) + to_chat(user, SPAN_WARNING("The implanter reports there is no compatible implant loaded.")) + return + implant.psi_mode = choice + to_chat(user, SPAN_NOTICE("You set \the [src] to configure implants with the '[implant.psi_mode]' setting.")) + +/obj/item/implanter/psi/New() + ..() + imp = new /obj/item/implant/psi_control(src) + +#undef PSI_IMPLANT_AUTOMATIC +#undef PSI_IMPLANT_SHOCK +#undef PSI_IMPLANT_WARN +#undef PSI_IMPLANT_LOG +#undef PSI_IMPLANT_DISABLED diff --git a/mods/psionics/code/equipment/foundation_weapon.dm b/mods/psionics/code/equipment/foundation_weapon.dm new file mode 100644 index 0000000000000..f259ca3e58c33 --- /dev/null +++ b/mods/psionics/code/equipment/foundation_weapon.dm @@ -0,0 +1,24 @@ +/obj/item/gun/projectile/revolver/foundation + name = "\improper Foundation revolver" + icon = 'icons/obj/guns/foundation.dmi' + icon_state = "foundation" + desc = "The CF 'Troubleshooter', a compact plastic-composite weapon designed for concealed carry by Cuchulain Foundation field agents. Smells faintly of copper." + ammo_type = /obj/item/ammo_casing/pistol/magnum/nullglass + +/obj/item/gun/projectile/revolver/foundation/disrupts_psionics() + return FALSE + +/obj/item/storage/briefcase/foundation + name = "\improper Foundation briefcase" + desc = "A handsome black leather briefcase embossed with a stylized radio telescope." + icon_state = "fbriefcase" + item_state = "fbriefcase" + +/obj/item/storage/briefcase/foundation/disrupts_psionics() + return FALSE + +/obj/item/storage/briefcase/foundation/New() + ..() + new /obj/item/ammo_magazine/speedloader/magnum/nullglass(src) + new /obj/item/gun/projectile/revolver/foundation(src) + make_exact_fit() diff --git a/mods/psionics/code/equipment/implant.dm b/mods/psionics/code/equipment/implant.dm new file mode 100644 index 0000000000000..d5217fac68da3 --- /dev/null +++ b/mods/psionics/code/equipment/implant.dm @@ -0,0 +1,135 @@ +#ifndef PSI_IMPLANT_AUTOMATIC +#define PSI_IMPLANT_AUTOMATIC "Security Level Derived" +#endif +#ifndef PSI_IMPLANT_SHOCK +#define PSI_IMPLANT_SHOCK "Issue Neural Shock" +#endif +#ifndef PSI_IMPLANT_WARN +#define PSI_IMPLANT_WARN "Issue Reprimand" +#endif +#ifndef PSI_IMPLANT_LOG +#define PSI_IMPLANT_LOG "Log Incident" +#endif +#ifndef PSI_IMPLANT_DISABLED +#define PSI_IMPLANT_DISABLED "Disabled" +#endif + +/obj/item/implant/psi_control + name = "psi dampener implant" + desc = "A safety implant for registered psi-operants." + known = TRUE + + var/overload = 0 + var/max_overload = 100 + var/psi_mode = PSI_IMPLANT_DISABLED + +/obj/item/implant/psi_control/islegal() + return TRUE + +/obj/item/implant/psi_control/Initialize() + . = ..() + SSpsi.psi_dampeners += src + +/obj/item/implant/psi_control/Destroy() + SSpsi.psi_dampeners -= src + . = ..() + +/obj/item/implant/psi_control/emp_act() + ..() + update_functionality() + +/obj/item/implant/psi_control/meltdown() + . = ..() + update_functionality() + +/obj/item/implant/psi_control/disrupts_psionics() + var/use_psi_mode = get_psi_mode() + return (!malfunction && (use_psi_mode == PSI_IMPLANT_SHOCK || use_psi_mode == PSI_IMPLANT_WARN)) ? src : FALSE + +/obj/item/implant/psi_control/removed() + var/mob/living/M = imp_in + if(disrupts_psionics() && istype(M) && M.psi) + to_chat(M, SPAN_NOTICE("You feel the chilly shackles around your psionic faculties fade away.")) + . = ..() + +/obj/item/implant/psi_control/proc/update_functionality(silent) + var/mob/living/M = imp_in + if(get_psi_mode() == PSI_IMPLANT_DISABLED || malfunction) + if(implanted && !silent && istype(M) && M.psi) + to_chat(M, SPAN_NOTICE("You feel the chilly shackles around your psionic faculties fade away.")) + else + if(implanted && !silent && istype(M) && M.psi) + to_chat(M, SPAN_NOTICE("Bands of hollow ice close themselves around your psionic faculties.")) + +/obj/item/implant/psi_control/meltdown() + if(!malfunction) + overload = 100 + if(imp_in) + for(var/thing in SSpsi.psi_monitors) + var/obj/machinery/psi_monitor/monitor = thing + monitor.report_failure(src) + . = ..() + +/obj/item/implant/psi_control/proc/get_psi_mode() + if(psi_mode == PSI_IMPLANT_AUTOMATIC) + var/singleton/security_state/security_state = GET_SINGLETON(GLOB.using_map.security_state) + return security_state.current_security_level.psionic_control_level + return psi_mode + +/obj/item/implant/psi_control/withstand_psi_stress(stress, atom/source) + + var/use_psi_mode = get_psi_mode() + + if(malfunction || use_psi_mode == PSI_IMPLANT_DISABLED) + return stress + + . = 0 + + if(stress > 0) + + // If we're disrupting psionic attempts at the moment, we might overload. + if(disrupts_psionics()) + var/overload_amount = floor(stress/10) + if(overload_amount > 0) + overload += overload_amount + if(overload >= 100) + if(imp_in) + to_chat(imp_in, SPAN_DANGER("Your psi dampener overloads violently!")) + meltdown() + update_functionality() + return + if(imp_in) + if(overload >= 75 && overload < 100) + to_chat(imp_in, SPAN_DANGER("Your psi dampener is searing hot!")) + else if(overload >= 50 && overload < 75) + to_chat(imp_in, SPAN_WARNING("Your psi dampener is uncomfortably hot...")) + else if(overload >= 25 && overload < 50) + to_chat(imp_in, SPAN_WARNING("You feel your psi dampener heating up...")) + + // If all we're doing is logging the incident then just pass back stress without changing it. + if(source && source == imp_in && implanted) + for(var/thing in SSpsi.psi_monitors) + var/obj/machinery/psi_monitor/monitor = thing + monitor.report_violation(src, stress) + if(use_psi_mode == PSI_IMPLANT_LOG) + return stress + else if(use_psi_mode == PSI_IMPLANT_SHOCK) + to_chat(imp_in, SPAN_DANGER("Your psi dampener punishes you with a violent neural shock!")) + imp_in.flash_eyes() + imp_in.Weaken(5) + if(isliving(imp_in)) + var/mob/living/M = imp_in + if(M.psi) M.psi.stunned(5) + else if(use_psi_mode == PSI_IMPLANT_WARN) + to_chat(imp_in, SPAN_WARNING("Your psi dampener primly informs you it has reported this violation.")) + //FD PSIONICS// + //Probably fixing problem with no actual logs coming to computer, dunno why it even have to be on the top of this block// + for(var/report in SSpsi.psi_monitors) + var/obj/machinery/psi_monitor/monitor = report + monitor.report_violation(src, stress) + +#undef PSI_IMPLANT_AUTOMATIC +#undef PSI_IMPLANT_SHOCK +#undef PSI_IMPLANT_WARN +#undef PSI_IMPLANT_LOG +#undef PSI_IMPLANT_DISABLED diff --git a/mods/psionics/code/equipment/null_ammo.dm b/mods/psionics/code/equipment/null_ammo.dm new file mode 100644 index 0000000000000..eb7fa46e428e7 --- /dev/null +++ b/mods/psionics/code/equipment/null_ammo.dm @@ -0,0 +1,17 @@ +/obj/item/projectile/bullet/nullglass + name = "nullglass bullet" + damage = 40 + shrapnel_type = /obj/item/material/shard/nullglass + +/obj/item/projectile/bullet/nullglass/disrupts_psionics() + return src + +/obj/item/ammo_casing/pistol/magnum/nullglass + desc = "A revolver bullet casing with a nullglass coating." + projectile_type = /obj/item/projectile/bullet/nullglass + +/obj/item/ammo_casing/pistol/magnum/nullglass/disrupts_psionics() + return src + +/obj/item/ammo_magazine/speedloader/magnum/nullglass + ammo_type = /obj/item/ammo_casing/pistol/magnum/nullglass diff --git a/mods/psionics/code/equipment/psimeter.dm b/mods/psionics/code/equipment/psimeter.dm new file mode 100644 index 0000000000000..c9b7247ea4537 --- /dev/null +++ b/mods/psionics/code/equipment/psimeter.dm @@ -0,0 +1,71 @@ +/obj/machinery/psi_meter + name = "psi-meter" + desc = "A bulky psi-meter for conducting assays of psi-operants." + icon = 'icons/obj/machines/research/psimeter.dmi' + icon_state = "meter_on" + use_power = POWER_USE_ACTIVE + anchored = TRUE + density = TRUE + opacity = FALSE + + var/list/last_assay + var/mob/living/last_assayed + +/obj/machinery/psi_meter/on_update_icon() + if(use_power && operable()) + icon_state = "meter_on" + else + icon_state = "meter_off" + +/obj/machinery/psi_meter/interface_interact(mob/user) + interact(user) + return TRUE + +/obj/machinery/psi_meter/interact(mob/user) + + if(!use_power) return + + var/list/dat = list() + if(LAZYLEN(last_assay)) + dat = last_assay + else + dat += "

TELESTO Mark I Psi-Meter


[faculty_singleton.name]
" + var/found + for(var/mob/living/H in range(1, src)) + found = TRUE + dat += "" + dat += "
Candidates
[H.name]Conduct Assay" + if(!found) + dat += "
No candidates found.
" + + var/datum/browser/popup = new(user, "psi_assay_\ref[src]", "Psi-Assay") + popup.set_content(jointext(dat,null)) + popup.open() + +/obj/machinery/psi_meter/Topic(href, href_list) + . = ..() + if(!.) + + if(!issilicon(usr) && !Adjacent(usr)) + return FALSE + + var/refresh + if(href_list["print"]) + if(last_assay) + var/obj/item/paper/P = new(loc) + P.name = "paper - Psi-Assay ([last_assayed.name])" + P.info = jointext(last_assay - last_assay[length(last_assay)],null) // Last line is 'print | clear' link line. + return TRUE + + if(href_list["clear"]) + last_assay = null + refresh = TRUE + else if(href_list["assay"]) + last_assayed = locate(href_list["assay"]) + if(istype(last_assayed)) + last_assayed.show_psi_assay(usr, src) + refresh = TRUE + + if(refresh) + interact(usr) + return TRUE diff --git a/mods/psionics/code/equipment/psimonitor.dm b/mods/psionics/code/equipment/psimonitor.dm new file mode 100644 index 0000000000000..a276c213047f0 --- /dev/null +++ b/mods/psionics/code/equipment/psimonitor.dm @@ -0,0 +1,144 @@ +#ifndef PSI_IMPLANT_AUTOMATIC +#define PSI_IMPLANT_AUTOMATIC "Security Level Derived" +#endif +#ifndef PSI_IMPLANT_SHOCK +#define PSI_IMPLANT_SHOCK "Issue Neural Shock" +#endif +#ifndef PSI_IMPLANT_WARN +#define PSI_IMPLANT_WARN "Issue Reprimand" +#endif +#ifndef PSI_IMPLANT_LOG +#define PSI_IMPLANT_LOG "Log Incident" +#endif +#ifndef PSI_IMPLANT_DISABLED +#define PSI_IMPLANT_DISABLED "Disabled" +#endif + +/obj/machinery/psi_monitor + name = "psionic implant monitor" + icon = 'icons/obj/machines/research/psimeter.dmi' + icon_state = "meter_on" + use_power = POWER_USE_ACTIVE + anchored = TRUE + density = TRUE + opacity = FALSE + req_access = list(list(access_psychiatrist, access_captain, access_cmo, access_hos)) + + var/list/psi_violations = list() + var/show_violations = FALSE + var/authorized + +/obj/machinery/psi_monitor/New() + SSpsi.psi_monitors += src + ..() + +/obj/machinery/psi_monitor/emag_act(remaining_charges, mob/user) + if(!emagged) + emagged = TRUE + remaining_charges-- + req_access.Cut() + to_chat(user, SPAN_NOTICE("You short out the access protocols.")) + return TRUE + return FALSE + +/obj/machinery/psi_monitor/Topic(href, href_list) + + . = ..() + if(!.) + + if(href_list["login"]) + + var/obj/item/card/id/ID = usr.GetIdCard() + if(!ID || !allowed(usr)) + to_chat(usr, SPAN_WARNING("Access denied.")) + else + authorized = "[ID.registered_name] ([ID.assignment])" + . = 1 + + else if(href_list["logout"]) + authorized = FALSE + . = 1 + + else if(href_list["show_violations"]) + show_violations = (href_list["show_violations"] == "1") + . = 1 + + else if(href_list["remove_violation"]) + var/remove_ind = text2num(href_list["remove_violation"]) + if(remove_ind > 0 && remove_ind <= length(psi_violations)) + psi_violations.Cut(remove_ind, remove_ind++) + . = 1 + + else if(href_list["change_mode"]) + var/obj/item/implant/psi_control/implant = locate(href_list["change_mode"]) + if(implant.imp_in && !implant.malfunction) + if(!AreConnectedZLevels(z, implant.imp_in.z)) + to_chat(usr, SPAN_WARNING("Signal to implant was lost")) + return 1 + var/choice = input("Select a new implant mode.", "Psi Dampener") as null|anything in list(PSI_IMPLANT_AUTOMATIC, PSI_IMPLANT_SHOCK, PSI_IMPLANT_WARN, PSI_IMPLANT_LOG, PSI_IMPLANT_DISABLED) + if(choice && implant && implant.imp_in && !implant.malfunction) + implant.psi_mode = choice + implant.update_functionality() + . = 1 + + if(. && usr) + interact(usr) + +/obj/machinery/psi_monitor/interface_interact(mob/user) + interact(user) + return TRUE + +/obj/machinery/psi_monitor/interact(mob/user) + + var/list/dat = list() + dat += "

Psi Dampener Monitor

" + if(authorized) + dat += "[authorized]Logout" + else + dat += "Login" + + dat += "

Active Psionic Dampeners


" + dat += "
" + dat += "" + for(var/thing in SSpsi.psi_dampeners) + var/obj/item/implant/psi_control/implant = thing + if(!implant.imp_in) + continue + if(!AreConnectedZLevels(z, implant.imp_in.z)) + continue + dat += "" + if(implant.malfunction) + dat += "" + else + dat += "" + dat += "" + dat += "
OperantSystem loadMode
[implant.imp_in.name]ERRORERROR[implant.overload]%[authorized ? "[implant.psi_mode]" : "[implant.psi_mode]"]

" + + if(show_violations) + dat += "

Psionic Control Violations -


" + if(length(psi_violations)) + for(var/i = 1 to length(psi_violations)) + var/entry = psi_violations[i] + dat += "" + else + dat += "" + dat += "

[entry]
[authorized ? "Remove" : ""]
None reported.

" + else + dat += "

Psionic Control Violations +


" + + var/datum/browser/popup = new(user, "psi_monitor_\ref[src]", "Psi-Monitor") + popup.set_content(jointext(dat,null)) + popup.open() + + +/obj/machinery/psi_monitor/proc/report_failure(obj/item/implant/psi_control/implant) + psi_violations += SPAN_COLOR("#ff0000", "Critical system failure - [implant.imp_in.name].") + +/obj/machinery/psi_monitor/proc/report_violation(obj/item/implant/psi_control/implant, stress) + psi_violations += "Stress [round(stress/10)] event - [implant.imp_in.name]." + +#undef PSI_IMPLANT_AUTOMATIC +#undef PSI_IMPLANT_SHOCK +#undef PSI_IMPLANT_WARN +#undef PSI_IMPLANT_LOG +#undef PSI_IMPLANT_DISABLED diff --git a/mods/psionics/code/equipment/psipower.dm b/mods/psionics/code/equipment/psipower.dm new file mode 100644 index 0000000000000..1104ac894dfff --- /dev/null +++ b/mods/psionics/code/equipment/psipower.dm @@ -0,0 +1,68 @@ +/obj/item/psychic_power + name = "psychic power" + icon = 'mods/psionics/icons/psychic_powers.dmi' + atom_flags = 0 + anchored = TRUE + var/maintain_cost = 3 + var/mob/living/owner +/* +/obj/item/psychic_power/AltClick(mob/user) + if(melee_strikes) + swap_stances(user) + + ..() +*/ +/obj/item/psychic_power/New() + owner = usr + if(!istype(owner)) + qdel(src) + return + START_PROCESSING(SSprocessing, src) + ..() + +/obj/item/psychic_power/Destroy() + if(istype(owner) && owner.psi) + LAZYREMOVE(owner.psi.manifested_items, src) + UNSETEMPTY(owner.psi.manifested_items) + STOP_PROCESSING(SSprocessing, src) + . = ..() + +/obj/item/psychic_power/get_storage_cost() + return ITEM_SIZE_NO_CONTAINER + +/obj/item/psychic_power/attack_self(mob/user) + sound_to(owner, 'sound/effects/psi/power_fail.ogg') + user.drop_from_inventory(src) + +/obj/item/psychic_power/use_before(mob/living/M, mob/living/user) + . = FALSE + if(M.do_psionics_check(max(force, maintain_cost), user)) + to_chat(user, SPAN_DANGER("\The [src] flickers violently out of phase!")) + return TRUE + +/obj/item/psychic_power/afterattack(atom/target, mob/living/user, proximity) + if(target.do_psionics_check(max(force, maintain_cost), user)) + to_chat(user, SPAN_DANGER("\The [src] flickers violently out of phase!")) + return + . = ..(target, user, proximity) + +/obj/item/psychic_power/dropped() + ..() + qdel(src) + +/obj/item/psychic_power/Process() + if(istype(owner)) + owner.psi.spend_power(maintain_cost) + if(!owner || owner.do_psionics_check(maintain_cost, owner) || !owner.IsHolding(src)) + if(istype(loc,/mob/living)) + var/mob/living/carbon/human/host = loc + if(istype(host)) + for(var/obj/item/organ/external/organ in host.organs) + for(var/obj/item/O in organ.implants) + if(O == src) + organ.implants -= src + host.pinned -= src + host.embedded -= src + host.drop_from_inventory(src) + else + qdel(src) diff --git a/mods/psionics/code/equipment/psipower_blade.dm b/mods/psionics/code/equipment/psipower_blade.dm new file mode 100644 index 0000000000000..921d033f24476 --- /dev/null +++ b/mods/psionics/code/equipment/psipower_blade.dm @@ -0,0 +1,183 @@ +/obj/item/carvable/Initialize() + var/list/add_to_tools = list(/obj/item/psychic_power/psiaxe, \ + /obj/item/psychic_power/psiblade) + allow_tool_types += add_to_tools + ..() + +/obj/item/psychic_power/psiblade/IsHatchet() + return TRUE + +/obj/item/psychic_power/psiblade + name = "psychokinetic slash" + force = 15 + sharp = TRUE + edge = TRUE + maintain_cost = 6 + + item_icons = list( + slot_l_hand_str = 'mods/psionics/icons/psi_fd/lefthand.dmi', + slot_r_hand_str = 'mods/psionics/icons/psi_fd/righthand.dmi', + ) + + icon_state = "psiblade_short" + item_state = "psiblade_short" + attack_cooldown = 8 + + base_parry_chance = 20 +/* + fail_chance = 20 + lunge_dist = 6 + melee_strikes = list(/singleton/combo_strike/precise_strike/fast_attacks,/singleton/combo_strike/swipe_strike/mixed_combo) +*/ +/obj/item/psychic_power/psiblade/master + force = 25 + maintain_cost = 5 + +/obj/item/psychic_power/psiblade/master/grand + force = 35 + maintain_cost = 4 + + item_icons = list( + slot_l_hand_str = 'mods/psionics/icons/psi_fd/lefthand.dmi', + slot_r_hand_str = 'mods/psionics/icons/psi_fd/righthand.dmi', + ) + + icon_state = "psiblade_long" + item_state = "psiblade_long" + + base_parry_chance = 50 +/* + lunge_dist = 4 + fail_chance = 40 + melee_strikes = list(/singleton/combo_strike/swipe_strike/sword_slashes,/singleton/combo_strike/swipe_strike/mixed_combo) +*/ +/obj/item/psychic_power/psiblade/master/grand/paramount // Silly typechecks because rewriting old interaction code is outside of scope. + force = 50 + maintain_cost = 3 + icon_state = "psiblade_long" + item_state = "psiblade_long" + +/obj/item/psychic_power/psiaxe/afterattack(atom/A as mob|obj|turf|area, mob/user as mob, proximity) + if(!proximity) return + + if(A) + if(istype(A,/obj/structure/window)) + var/obj/structure/window/W = A + W.shatter() + else if(istype(A,/obj/structure/grille)) + qdel(A) + else if(istype(A,/obj/vine)) + var/obj/vine/P = A + P.kill_health() + + ..() + +/obj/item/psychic_power/psiaxe/IsHatchet() + return TRUE + +/obj/item/psychic_power/psiaxe + name = "psychokinetic axe" + force = 25 + sharp = TRUE + edge = TRUE + maintain_cost = 8 + + item_icons = list( + slot_l_hand_str = 'mods/psionics/icons/psi_fd/lefthand.dmi', + slot_r_hand_str = 'mods/psionics/icons/psi_fd/righthand.dmi', + ) + + icon_state = "psiaxe" + item_state = "psiaxe" + attack_cooldown = 16 + + base_parry_chance = 50 +/* + lunge_delay = 10 SECONDS + lunge_dist = 2 + fail_chance = 40 + melee_strikes = list(/singleton/combo_strike/swipe_strike/polearm_slash, /singleton/combo_strike/swipe_strike/polearm_wide) +*/ +/obj/item/psychic_power/psiaxe/master + force = 35 + maintain_cost = 6 + +/obj/item/psychic_power/psiaxe/master/grand + force = 45 + maintain_cost = 4 + +/obj/item/psychic_power/psiaxe/master/grand/paramount + force = 60 + maintain_cost = 2 + + + +/obj/item/psychic_power/psiclub + name = "psychokinetic club" + force = 10 + edge = TRUE + maintain_cost = 5 + + item_icons = list( + slot_l_hand_str = 'mods/psionics/icons/psi_fd/lefthand.dmi', + slot_r_hand_str = 'mods/psionics/icons/psi_fd/righthand.dmi', + ) + + icon_state = "psiclub" + item_state = "psiclub" + attack_cooldown = 6 + + base_parry_chance = 10 +/* + lunge_dist = 4 + fail_chance = 10 + melee_strikes = list(/singleton/combo_strike/swipe_strike/blunt_swing/mixed_combo, /singleton/combo_strike/circle_strike/blunt) +*/ +/obj/item/psychic_power/psiclub/master + force = 20 + maintain_cost = 4 + +/obj/item/psychic_power/psiclub/master/grand + force = 30 + maintain_cost = 3 + +/obj/item/psychic_power/psiclub/master/grand/paramount + force = 45 + maintain_cost = 2 + + + +/obj/item/psychic_power/psispear + name = "psychokinetic spear" + force = 20 + sharp = TRUE + edge = TRUE + maintain_cost = 6 + + item_icons = list( + slot_l_hand_str = 'mods/psionics/icons/psi_fd/lefthand.dmi', + slot_r_hand_str = 'mods/psionics/icons/psi_fd/righthand.dmi', + ) + + icon_state = "psispear" + item_state = "psispear" + attack_cooldown = 12 + + base_parry_chance = 30 +/* + lunge_delay = 10 SECONDS + lunge_dist = 4 + fail_chance = 60 + melee_strikes = list(/singleton/combo_strike/swipe_strike/polearm_mixed, /singleton/combo_strike/swipe_strike/polearm_slash, /singleton/combo_strike/swipe_strike/polearm_wide) +*/ +/obj/item/psychic_power/psispear/master + force = 30 + maintain_cost = 5 + +/obj/item/psychic_power/psispear/master/grand + force = 40 + maintain_cost = 4 + +/obj/item/psychic_power/psispear/master/grand/paramount + force = 50 + maintain_cost = 3 diff --git a/mods/psionics/code/equipment/psipower_bow.dm b/mods/psionics/code/equipment/psipower_bow.dm new file mode 100644 index 0000000000000..c37728d7415d8 --- /dev/null +++ b/mods/psionics/code/equipment/psipower_bow.dm @@ -0,0 +1,132 @@ +/obj/item/psyarrow + name = "psionic projectile" + desc = "It's got a tip for you - get the point?" + icon = 'icons/obj/psychic_powers.dmi' + icon_state = "tinker" + throwforce = 5 + sharp = TRUE + var/mob/living/owner + +/obj/item/psyarrow/proc/removed(mob/user) + sleep(2) + qdel(src) + +/obj/item/psyarrow/throw_impact(atom/hit_atom) + ..() + if(isliving(hit_atom)) + qdel(src) + sleep(10) + qdel(src) + +/obj/item/psyarrow/master + throwforce = 8 + +/obj/item/psyarrow/master/grand + throwforce = 12 + +/obj/item/psyarrow/master/grand/paramount + throwforce = 16 + +/obj/item/gun/launcher/crossbow/psibow + name = "psychokinetic bow" + icon = 'icons/obj/guns/crossbow.dmi' + icon_state = "crossbow" + item_state = "crossbow-solid" + atom_flags = 0 + color = "#0095ff" + alpha = 110 + anchored = TRUE + release_speed = 6 + var/used_bolt = /obj/item/psyarrow + bolt = new/obj/item/psyarrow + var/maintain_cost = 8 + var/mob/living/owner + draw_time = 40 + +/obj/item/gun/launcher/crossbow/psibow/master + bolt = new/obj/item/psyarrow/master + used_bolt = /obj/item/psyarrow/master + maintain_cost = 6 + draw_time = 30 + +/obj/item/gun/launcher/crossbow/psibow/master/grand + bolt = new/obj/item/psyarrow/master/grand + used_bolt = /obj/item/psyarrow/master/grand + maintain_cost = 4 + draw_time = 20 + +/obj/item/gun/launcher/crossbow/psibow/master/grand/paramount + bolt = new/obj/item/psyarrow/master/grand/paramount + used_bolt = /obj/item/psyarrow/master/grand/paramount + maintain_cost = 2 + draw_time = 10 + +/obj/item/gun/launcher/crossbow/psibow/New() + owner = usr + if(!istype(owner)) + qdel(src) + return + START_PROCESSING(SSprocessing, src) + ..() + +/obj/item/gun/launcher/crossbow/psibow/attack_self(mob/living/user as mob) + if(!bolt) + user.visible_message("[user] starts to reconstruct arrow inside the [src].","You starting to create new arrow for the [src].") + bolt = new used_bolt + update_icon() + if(tension) + if(bolt) + user.visible_message("[user] relaxes the tension on [src]'s string and removes [bolt].","You relax the tension on [src]'s string and remove [bolt].") + bolt.dropInto(loc) + var/obj/item/psyarrow/A = bolt + bolt = null + A.removed(user) + else + user.visible_message("[user] relaxes the tension on [src]'s string.","You relax the tension on [src]'s string.") + tension = 0 + update_icon() + else + draw(user) + +/obj/item/gun/launcher/crossbow/psibow/Destroy() + if(istype(owner) && owner.psi) + LAZYREMOVE(owner.psi.manifested_items, src) + UNSETEMPTY(owner.psi.manifested_items) + STOP_PROCESSING(SSprocessing, src) + . = ..() + +/obj/item/gun/launcher/crossbow/psibow/get_storage_cost() + return ITEM_SIZE_NO_CONTAINER + +/obj/item/gun/launcher/crossbow/psibow/use_before(mob/living/M, mob/living/user, target_zone) + if(M.do_psionics_check(max(force, maintain_cost), user)) + to_chat(user, "\The [src] flickers violently out of phase!") + return 1 + . = ..() + +/obj/item/gun/launcher/crossbow/psibow/afterattack(atom/target, mob/living/user, proximity) + if(target.do_psionics_check(max(force, maintain_cost), user)) + to_chat(user, "\The [src] flickers violently out of phase!") + return + . = ..(target, user, proximity) + +/obj/item/gun/launcher/crossbow/psibow/dropped() + ..() + qdel(src) + +/obj/item/gun/launcher/crossbow/psibow/Process() + if(istype(owner)) + owner.psi.spend_power(maintain_cost) + if(!owner || owner.do_psionics_check(maintain_cost, owner) || loc != owner || (owner.l_hand != src && owner.r_hand != src)) + if(istype(loc,/mob/living)) + var/mob/living/carbon/human/host = loc + if(istype(host)) + for(var/obj/item/organ/external/organ in host.organs) + for(var/obj/item/O in organ.implants) + if(O == src) + organ.implants -= src + host.pinned -= src + host.embedded -= src + host.drop_from_inventory(src) + else + STOP_PROCESSING(SSprocessing, src) diff --git a/mods/psionics/code/equipment/psipower_cryokinesis.dm b/mods/psionics/code/equipment/psipower_cryokinesis.dm new file mode 100644 index 0000000000000..0431de5823a2f --- /dev/null +++ b/mods/psionics/code/equipment/psipower_cryokinesis.dm @@ -0,0 +1,81 @@ +/obj/item/cryokinesis + name = "ice" + icon = 'mods/psionics/icons/psychic_powers.dmi' + + item_icons = list( + slot_l_hand_str = 'mods/psionics/icons/psi_fd/lefthand.dmi', + slot_r_hand_str = 'mods/psionics/icons/psi_fd/righthand.dmi', + ) + + icon_state = "cryo" + + var/delete_on_drop = 0 //should we delete this item, if it isn't in our inventory? + var/uses = 5 //amount of time we can use this item before it shutters, similar to glass spear + +/obj/item/cryokinesis/New() + START_PROCESSING(SSprocessing, src) + ..() + +/obj/item/cryokinesis/Process() + if(uses <= 0) + Destroy() + +/obj/item/cryokinesis/apply_hit_effect(mob/living/target, mob/living/user, hit_zone) + uses -= 1 + ..() + +/obj/item/cryokinesis/afterattack(atom/A as mob|obj|turf|area, mob/living/user as mob) + uses -= 1 + +/obj/item/cryokinesis/Destroy() + playsound(src, "shatter", 70, 1) + src.visible_message(SPAN_DANGER("[src] рассыпается на тысячи мелких льдинок!")) + ..() + +/obj/item/cryokinesis/dropped(mob/living/user as mob) + ..() + if(delete_on_drop) + Destroy() + +/obj/item/cryokinesis/get_storage_cost() + return ITEM_SIZE_NO_CONTAINER + +/obj/item/cryokinesis/fists + name = "ice fists" + icon_state = "icefists" + desc = "A pair of cold, icy, punching gloves" + uses = 10 + delete_on_drop = 1 + force = 15 + attack_cooldown = 8 + + base_parry_chance = 10 +/* + lunge_dist = 5 + fail_chance = 10 + melee_strikes = list(/singleton/combo_strike/swipe_strike/blunt_swing/mixed_combo, /singleton/combo_strike/circle_strike/blunt) +*/ +/obj/item/cryokinesis/fists/apply_hit_effect(mob/living/target, mob/living/user, hit_zone) + if(user.psi) + var/tele_rank = user.psi.get_rank(PSI_PSYCHOKINESIS) + if(tele_rank >= PSI_RANK_OPERANT && !user.psi.suppressed) + force = 35 + ..() + +/obj/item/cryokinesis/rapier + name = "ice sword" + icon_state = "iceblade" + desc = "Sword, made of very fragile and sharp ice" + uses = 5 + force = 25 + attack_cooldown = 4 + + sharp = TRUE + edge = TRUE + + base_parry_chance = 40 +/* + lunge_dist = 3 + fail_chance = 50 + melee_strikes = list(/singleton/combo_strike/swipe_strike/sword_slashes,/singleton/combo_strike/swipe_strike/mixed_combo) +*/ diff --git a/mods/psionics/code/equipment/psipower_electrokinesis.dm b/mods/psionics/code/equipment/psipower_electrokinesis.dm new file mode 100644 index 0000000000000..7ba5dd4f848e8 --- /dev/null +++ b/mods/psionics/code/equipment/psipower_electrokinesis.dm @@ -0,0 +1,66 @@ +/obj/item/psychic_power/electric_whip + name = "electric whip" + force = 10 + maintain_cost = 4 + + item_icons = list( + slot_l_hand_str = 'mods/psionics/icons/psi_fd/lefthand.dmi', + slot_r_hand_str = 'mods/psionics/icons/psi_fd/righthand.dmi', + ) + + icon_state = "electrowhip" + attack_cooldown = 10 + + var/cooldown = 0 + base_parry_chance = 10 + +/obj/item/psychic_power/electric_whip/Process() + if(cooldown > 0) + cooldown-- + + . = ..() + +/obj/item/psychic_power/electric_whip/afterattack(atom/A as mob|obj|turf|area, mob/living/user as mob) + var/el_rank = user.psi.get_rank(PSI_METAKINESIS) + + if(istype(A, /mob/living)) + var/mob/living/target = A + + if(get_dist(user, target) > 4) + return FALSE + + if(cooldown > 0) + to_chat(user, SPAN_WARNING("Ты не можешь использовать плеть настолько часто!")) + return + + if(get_dist(user, target) >= 2) + + if(target == user) + to_chat(user, SPAN_WARNING("Вы не можете зарядить самого себя!")) + return + if(target.psi && !target.psi.suppressed) + var/el_rank_target = target.psi.get_rank(PSI_METAKINESIS) + if(el_rank_target >= el_rank && prob(50)) + user.visible_message(SPAN_DANGER("[target] пропускает ток через себя, возвращая его [user] в виде молнии!")) + user.electrocute_act(rand(el_rank_target * 2, el_rank_target * 5), target, 1, target.zone_sel.selecting) + new /obj/temporary(get_turf(user),3, 'icons/effects/effects.dmi', "electricity_constant") + return TRUE + cooldown += 2 + target.electrocute_act(rand(el_rank * 2, el_rank * 5), user, 1, user.zone_sel.selecting) + new /obj/temporary(get_turf(target),3, 'icons/effects/effects.dmi', "electricity_constant") + return TRUE + +/obj/item/psychic_power/electric_whip/apply_hit_effect(mob/living/target, mob/living/user, hit_zone) + var/el_rank = user.psi.get_rank(PSI_METAKINESIS) + + if(target.psi && !target.psi.suppressed) + var/el_rank_target = target.psi.get_rank(PSI_METAKINESIS) + if(el_rank_target >= el_rank && prob(50)) + user.visible_message(SPAN_DANGER("[target] пропускает ток через себя, возвращая его [user] в виде молнии!")) + user.electrocute_act(rand(el_rank_target * 2, el_rank_target * 5), target, 1, target.zone_sel.selecting) + new /obj/temporary(get_turf(user),3, 'icons/effects/effects.dmi', "electricity_constant") + ..() + cooldown += 2 + target.electrocute_act(rand(el_rank * 2, el_rank * 5), user, 1, user.zone_sel.selecting) + new /obj/temporary(get_turf(target),3, 'icons/effects/effects.dmi', "electricity_constant") + ..() diff --git a/mods/psionics/code/equipment/psipower_engineering.dm b/mods/psionics/code/equipment/psipower_engineering.dm new file mode 100644 index 0000000000000..5d8d1fa6b161d --- /dev/null +++ b/mods/psionics/code/equipment/psipower_engineering.dm @@ -0,0 +1,347 @@ +/obj/item/clothing/gloves/insulated/psi + name = "psychokinetic gloves" + + var/maintain_cost = 2 + var/mob/living/carbon/human/owner + color = "#0095ff" + alpha = 110 + +/obj/item/clothing/gloves/insulated/psi/New() + owner = usr + if(!istype(owner)) + qdel(src) + return + START_PROCESSING(SSprocessing, src) + ..() + +/obj/item/clothing/gloves/insulated/psi/Destroy() + if(istype(owner) && owner.psi) + LAZYREMOVE(owner.psi.manifested_items, src) + UNSETEMPTY(owner.psi.manifested_items) + STOP_PROCESSING(SSprocessing, src) + . = ..() + +/obj/item/clothing/gloves/insulated/psi/get_storage_cost() + return ITEM_SIZE_NO_CONTAINER + +/obj/item/clothing/gloves/insulated/psi/dropped() + ..() + qdel(src) + +/obj/item/clothing/gloves/insulated/psi/Process() + if(istype(owner)) + owner.psi.spend_power(maintain_cost) + if(!owner || owner.do_psionics_check(maintain_cost, owner) || loc != owner || (owner.l_hand != src && owner.r_hand != src && owner.gloves != src)) + if(istype(loc,/mob/living)) + var/mob/living/carbon/human/host = loc + if(istype(host)) + for(var/obj/item/organ/external/organ in host.organs) + for(var/obj/item/O in organ.implants) + if(O == src) + organ.implants -= src + host.pinned -= src + host.embedded -= src + host.drop_from_inventory(src) + else + STOP_PROCESSING(SSprocessing, src) + + + +/obj/item/crowbar/psi + name = "psychokinetic crowbar" + + var/maintain_cost = 2 + var/mob/living/owner + color = "#0095ff" + alpha = 110 + +/obj/item/crowbar/psi/New() + owner = usr + if(!istype(owner)) + qdel(src) + return + START_PROCESSING(SSprocessing, src) + ..() + +/obj/item/crowbar/psi/Destroy() + if(istype(owner) && owner.psi) + LAZYREMOVE(owner.psi.manifested_items, src) + UNSETEMPTY(owner.psi.manifested_items) + STOP_PROCESSING(SSprocessing, src) + . = ..() + +/obj/item/crowbar/psi/get_storage_cost() + return ITEM_SIZE_NO_CONTAINER + +/obj/item/crowbar/psi/dropped() + ..() + qdel(src) + +/obj/item/crowbar/psi/Process() + if(istype(owner)) + owner.psi.spend_power(maintain_cost) + if(!owner || owner.do_psionics_check(maintain_cost, owner) || loc != owner || (owner.l_hand != src && owner.r_hand != src)) + if(isliving(loc)) + var/mob/living/carbon/human/host = loc + if(istype(host)) + for(var/obj/item/organ/external/organ in host.organs) + for(var/obj/item/O in organ.implants) + if(O == src) + organ.implants -= src + host.pinned -= src + host.embedded -= src + host.drop_from_inventory(src) + else + STOP_PROCESSING(SSprocessing, src) + + + +/obj/item/screwdriver/psi + name = "psychokinetic screwdriver" + item_state = "screwdriver_preview" + + var/maintain_cost = 2 + var/mob/living/owner + color = "#0095ff" + alpha = 110 + +/obj/item/screwdriver/psi/New() + owner = usr + if(!istype(owner)) + qdel(src) + return + START_PROCESSING(SSprocessing, src) + ..() + +/obj/item/screwdriver/psi/Destroy() + if(istype(owner) && owner.psi) + LAZYREMOVE(owner.psi.manifested_items, src) + UNSETEMPTY(owner.psi.manifested_items) + STOP_PROCESSING(SSprocessing, src) + . = ..() + +/obj/item/screwdriver/psi/get_storage_cost() + return ITEM_SIZE_NO_CONTAINER + +/obj/item/screwdriver/psi/dropped() + ..() + qdel(src) + +/obj/item/screwdriver/psi/Process() + if(istype(owner)) + owner.psi.spend_power(maintain_cost) + if(!owner || owner.do_psionics_check(maintain_cost, owner) || loc != owner || (owner.l_hand != src && owner.r_hand != src)) + if(isliving(loc)) + var/mob/living/carbon/human/host = loc + if(istype(host)) + for(var/obj/item/organ/external/organ in host.organs) + for(var/obj/item/O in organ.implants) + if(O == src) + organ.implants -= src + host.pinned -= src + host.embedded -= src + host.drop_from_inventory(src) + else + STOP_PROCESSING(SSprocessing, src) + + + +/obj/item/wirecutters/psi + name = "psychokinetic wirecutters" + item_state = "cutters_preview" + + var/maintain_cost = 2 + var/mob/living/owner + color = "#0095ff" + alpha = 110 + +/obj/item/wirecutters/psi/New() + owner = usr + if(!istype(owner)) + qdel(src) + return + START_PROCESSING(SSprocessing, src) + ..() + +/obj/item/wirecutters/psi/Destroy() + if(istype(owner) && owner.psi) + LAZYREMOVE(owner.psi.manifested_items, src) + UNSETEMPTY(owner.psi.manifested_items) + STOP_PROCESSING(SSprocessing, src) + . = ..() + +/obj/item/wirecutters/psi/get_storage_cost() + return ITEM_SIZE_NO_CONTAINER + +/obj/item/wirecutters/psi/dropped() + ..() + qdel(src) + +/obj/item/wirecutters/psi/Process() + if(istype(owner)) + owner.psi.spend_power(maintain_cost) + if(!owner || owner.do_psionics_check(maintain_cost, owner) || loc != owner || (owner.l_hand != src && owner.r_hand != src)) + if(isliving(loc)) + var/mob/living/carbon/human/host = loc + if(istype(host)) + for(var/obj/item/organ/external/organ in host.organs) + for(var/obj/item/O in organ.implants) + if(O == src) + organ.implants -= src + host.pinned -= src + host.embedded -= src + host.drop_from_inventory(src) + else + STOP_PROCESSING(SSprocessing, src) + + + +/obj/item/wrench/psi + name = "psychokinetic wrench" + + var/maintain_cost = 2 + var/mob/living/owner + color = "#0095ff" + alpha = 110 + +/obj/item/wrench/psi/New() + owner = usr + if(!istype(owner)) + qdel(src) + return + START_PROCESSING(SSprocessing, src) + ..() + +/obj/item/wrench/psi/Destroy() + if(istype(owner) && owner.psi) + LAZYREMOVE(owner.psi.manifested_items, src) + UNSETEMPTY(owner.psi.manifested_items) + STOP_PROCESSING(SSprocessing, src) + . = ..() + +/obj/item/wrench/psi/get_storage_cost() + return ITEM_SIZE_NO_CONTAINER + +/obj/item/wrench/psi/dropped() + ..() + qdel(src) + +/obj/item/wrench/psi/Process() + if(istype(owner)) + owner.psi.spend_power(maintain_cost) + if(!owner || owner.do_psionics_check(maintain_cost, owner) || loc != owner || (owner.l_hand != src && owner.r_hand != src)) + if(istype(loc,/mob/living)) + var/mob/living/carbon/human/host = loc + if(istype(host)) + for(var/obj/item/organ/external/organ in host.organs) + for(var/obj/item/O in organ.implants) + if(O == src) + organ.implants -= src + host.pinned -= src + host.embedded -= src + host.drop_from_inventory(src) + else + STOP_PROCESSING(SSprocessing, src) + + + +/obj/item/welder_tank/experimental/special + max_fuel = 100 + +/obj/item/weldingtool/experimental/psi + name = "psychokinetic welding" + tank = /obj/item/welder_tank/experimental/special + + var/maintain_cost = 4 + var/mob/living/owner + color = "#0095ff" + alpha = 110 + +/obj/item/weldingtool/experimental/psi/New() + owner = usr + if(!istype(owner)) + qdel(src) + return + START_PROCESSING(SSprocessing, src) + ..() + +/obj/item/weldingtool/experimental/psi/Destroy() + if(istype(owner) && owner.psi) + LAZYREMOVE(owner.psi.manifested_items, src) + UNSETEMPTY(owner.psi.manifested_items) + STOP_PROCESSING(SSprocessing, src) + . = ..() + +/obj/item/weldingtool/experimental/psi/get_storage_cost() + return ITEM_SIZE_NO_CONTAINER + +/obj/item/weldingtool/experimental/psi/dropped() + ..() + qdel(src) + +/obj/item/weldingtool/experimental/psi/Process() + if(istype(owner)) + owner.psi.spend_power(maintain_cost) + if(!owner || owner.do_psionics_check(maintain_cost, owner) || loc != owner || (owner.l_hand != src && owner.r_hand != src)) + if(isliving(loc)) + var/mob/living/carbon/human/host = loc + if(istype(host)) + for(var/obj/item/organ/external/organ in host.organs) + for(var/obj/item/O in organ.implants) + if(O == src) + organ.implants -= src + host.pinned -= src + host.embedded -= src + host.drop_from_inventory(src) + else + STOP_PROCESSING(SSprocessing, src) + + + +/obj/item/device/multitool/psi + name = "psychokinetic multitool" + item_state = "multitool" + + var/maintain_cost = 4 + var/mob/living/owner + color = "#0095ff" + alpha = 110 + +/obj/item/device/multitool/psi/New() + owner = usr + if(!istype(owner)) + qdel(src) + return + START_PROCESSING(SSprocessing, src) + ..() + +/obj/item/device/multitool/psi/Destroy() + if(istype(owner) && owner.psi) + LAZYREMOVE(owner.psi.manifested_items, src) + UNSETEMPTY(owner.psi.manifested_items) + STOP_PROCESSING(SSprocessing, src) + . = ..() + +/obj/item/device/multitool/psi/get_storage_cost() + return ITEM_SIZE_NO_CONTAINER + +/obj/item/device/multitool/psi/dropped() + ..() + qdel(src) + +/obj/item/device/multitool/psi/Process() + if(istype(owner)) + owner.psi.spend_power(maintain_cost) + if(!owner || owner.do_psionics_check(maintain_cost, owner) || loc != owner || (owner.l_hand != src && owner.r_hand != src)) + if(isliving(loc)) + var/mob/living/carbon/human/host = loc + if(istype(host)) + for(var/obj/item/organ/external/organ in host.organs) + for(var/obj/item/O in organ.implants) + if(O == src) + organ.implants -= src + host.pinned -= src + host.embedded -= src + host.drop_from_inventory(src) + else + STOP_PROCESSING(SSprocessing, src) diff --git a/mods/psionics/code/equipment/psipower_gun.dm b/mods/psionics/code/equipment/psipower_gun.dm new file mode 100644 index 0000000000000..2bc16d7405854 --- /dev/null +++ b/mods/psionics/code/equipment/psipower_gun.dm @@ -0,0 +1,167 @@ +/obj/item/projectile/psi + name = "psionic projectile" + icon = 'icons/obj/projectiles.dmi' + icon_state = "dark_pellet" + damage = 20 + distance_falloff = 1 + penetration_modifier = 1.0 + damage_type = DAMAGE_BRUTE + damage_flags = DAMAGE_FLAG_BULLET | DAMAGE_FLAG_SHARP + miss_sounds = list('sound/weapons/guns/miss1.ogg','sound/weapons/guns/miss2.ogg','sound/weapons/guns/miss3.ogg','sound/weapons/guns/miss4.ogg') + ricochet_sounds = list('sound/weapons/guns/ricochet1.ogg', 'sound/weapons/guns/ricochet2.ogg', + 'sound/weapons/guns/ricochet3.ogg', 'sound/weapons/guns/ricochet4.ogg') + impact_sounds = list(BULLET_IMPACT_MEAT = SOUNDS_BULLET_MEAT, BULLET_IMPACT_METAL = SOUNDS_BULLET_METAL) + +/obj/item/projectile/psi/strong + damage = 25 + icon_state = "plasma_bolt" + color = "#c40eed" + var/explosion_power = EX_ACT_HEAVY + var/explosion_area = 5 + +/obj/item/projectile/psi/strong/on_hit(atom/target, blocked = 0) + explosion(get_turf(target), light_impact_range = explosion_power, flash_range = explosion_area) + ..() + +/obj/item/projectile/psi/strong_piercing + damage = 40 + icon_state = "plasma_bolt" + color = "#c40eed" + var/explosion_power = EX_ACT_DEVASTATING + var/explosion_area = 5 + var/exploded_inwall = FALSE + var/delay = 4 + + armor_penetration = 100 + penetrating = 2 + penetration_modifier = 1.1 + var/proximity_detonation = FALSE //should we explode near our target, and not inside of it? + var/exploded = FALSE + +/obj/item/projectile/psi/strong_piercing/Bump(atom/A as mob|obj|turf|area, forced=0) + ..() + if(exploded) + return + + exploded = TRUE + if(istype(A,/obj/shield)) + explosion(get_turf(A), heavy_impact_range = explosion_power, light_impact_range = explosion_area) + qdel(src) + return + + sleep(delay) + + if(src && !exploded_inwall) + explosion(get_turf(src), heavy_impact_range = explosion_power, light_impact_range = explosion_area) + qdel(src) + +/obj/item/projectile/psi/strong_piercing/Destroy() + if(src && !exploded_inwall && !istype(loc,/atom/movable)) + exploded = TRUE + exploded_inwall = TRUE + explosion(get_turf(src), heavy_impact_range = explosion_power, light_impact_range = explosion_area) + ..() + +/obj/item/gun/energy/psigun + name = "psychokinetic gun" + desc = "Result of Manifestation and Energistics combinated factors." + icon = 'icons/obj/psychic_powers.dmi' + icon_state = "gun" + fire_sound = 'sound/weapons/Taser.ogg' + fire_sound_text = "energy blast" + + var/maintain_cost = 10 + var/mob/living/owner + + max_shots = 20 //Determines the capacity of the weapon's power cell. Specifying a cell_type overrides this value. + projectile_type = /obj/item/projectile/psi + + self_recharge = 1 //if set, the weapon will recharge itself + +/obj/item/gun/energy/psigun/New() + owner = usr + if(!istype(owner)) + qdel(src) + return + START_PROCESSING(SSprocessing, src) + ..() + +/obj/item/gun/energy/psigun/Destroy() + if(istype(owner) && owner.psi) + LAZYREMOVE(owner.psi.manifested_items, src) + UNSETEMPTY(owner.psi.manifested_items) + STOP_PROCESSING(SSprocessing, src) + . = ..() + +/obj/item/gun/energy/psigun/get_storage_cost() + return ITEM_SIZE_NO_CONTAINER + +/obj/item/gun/energy/psigun/use_before(mob/living/M, mob/living/user, target_zone) + if(M.do_psionics_check(max(force, maintain_cost), user)) + to_chat(user, "\The [src] flickers violently out of phase!") + return 1 + . = ..() + +/obj/item/gun/energy/psigun/afterattack(atom/target, mob/living/user, proximity) + if(target.do_psionics_check(max(force, maintain_cost), user)) + to_chat(user, "\The [src] flickers violently out of phase!") + return + . = ..(target, user, proximity) + +/obj/item/gun/energy/psigun/special_check(mob/user) + + if(!isliving(user)) + return 0 + if(!user.IsAdvancedToolUser()) + return 0 + + var/mob/living/M = user + if(!safety() && world.time > last_safety_check + 5 MINUTES && !user.skill_check(SKILL_WEAPONS, SKILL_BASIC)) + if(prob(30)) + toggle_safety() + return 1 + if(MUTATION_FERAL in M.mutations) + to_chat(M, "Твои пальцы слишком большие!") + return 0 + if(M.psi) + var/hilo_rank = M.psi.get_rank(PSI_ENERGISTICS) + if(hilo_rank <= PSI_RANK_LATENT) + to_chat(M, SPAN_DANGER("Не стреляет!")) + return 0 + if((MUTATION_CLUMSY in M.mutations) && prob(40)) //Clumsy handling + var/obj/P = consume_next_projectile() + if(P) + if(process_projectile(P, user, user, pick(BP_L_FOOT, BP_R_FOOT))) + handle_post_fire(user, user) + user.visible_message( + "\The [user] shoots \himself in the foot with \the [src]!", + "You shoot yourself in the foot with \the [src]!" + ) + M.unequip_item() + else + handle_click_empty(user) + return 0 + return 1 + +/obj/item/gun/energy/psigun/dropped() + ..() + qdel(src) + +/obj/item/gun/energy/psigun/Process() + if(istype(owner)) + owner.psi.spend_power(maintain_cost) + if(!owner || owner.do_psionics_check(maintain_cost, owner) || loc != owner || (owner.l_hand != src && owner.r_hand != src)) + if(isliving(loc)) + var/mob/living/carbon/human/host = loc + if(istype(host)) + for(var/obj/item/organ/external/organ in host.organs) + for(var/obj/item/O in organ.implants) + if(O == src) + organ.implants -= src + host.pinned -= src + host.embedded -= src + host.drop_from_inventory(src) + else + STOP_PROCESSING(SSprocessing, src) + + ..() diff --git a/mods/psionics/code/equipment/psipower_medical.dm b/mods/psionics/code/equipment/psipower_medical.dm new file mode 100644 index 0000000000000..083e2f2e670da --- /dev/null +++ b/mods/psionics/code/equipment/psipower_medical.dm @@ -0,0 +1,263 @@ +/obj/item/clothing/gloves/latex/psi + name = "psychokinetic gloves" + + var/maintain_cost = 2 + var/mob/living/owner + color = "#0095ff" + alpha = 110 + +/obj/item/clothing/gloves/latex/psi/New() + owner = usr + if(!istype(owner)) + qdel(src) + return + START_PROCESSING(SSprocessing, src) + ..() + +/obj/item/clothing/gloves/latex/psi/Destroy() + if(istype(owner) && owner.psi) + LAZYREMOVE(owner.psi.manifested_items, src) + UNSETEMPTY(owner.psi.manifested_items) + STOP_PROCESSING(SSprocessing, src) + . = ..() + +/obj/item/clothing/gloves/latex/psi/get_storage_cost() + return ITEM_SIZE_NO_CONTAINER + +/obj/item/clothing/gloves/latex/psi/dropped() + ..() + qdel(src) + +/obj/item/clothing/gloves/latex/psi/Process() + if(istype(owner)) + owner.psi.spend_power(maintain_cost) + var/mob/living/carbon/human/H = owner + if(!H || H.do_psionics_check(maintain_cost, H) || loc != H || (H.l_hand != src && H.r_hand != src && H.gloves != src)) + if(isliving(loc)) + var/mob/living/carbon/human/host = loc + if(istype(host)) + for(var/obj/item/organ/external/organ in host.organs) + for(var/obj/item/O in organ.implants) + if(O == src) + organ.implants -= src + host.pinned -= src + host.embedded -= src + host.drop_from_inventory(src) + else + STOP_PROCESSING(SSprocessing, src) + +//RETRACTOR + +/obj/item/retractor/psi + name = "psychokinetic retractor" + item_state = "retractor" + + var/mob/living/owner + color = "#0095ff" + alpha = 110 + +/obj/item/retractor/psi/New() + owner = usr + if(!istype(owner)) + qdel(src) + return + START_PROCESSING(SSprocessing, src) + ..() + +/obj/item/retractor/psi/Destroy() + if(istype(owner) && owner.psi) + LAZYREMOVE(owner.psi.manifested_items, src) + UNSETEMPTY(owner.psi.manifested_items) + STOP_PROCESSING(SSprocessing, src) + . = ..() + +/obj/item/retractor/psi/get_storage_cost() + return ITEM_SIZE_NO_CONTAINER + +/obj/item/retractor/psi/attack_self(mob/user) + sound_to(owner, 'sound/effects/psi/power_fail.ogg') + user.drop_from_inventory(src) + +/obj/item/retractor/psi/dropped() + ..() + qdel(src) + +//HEMOSTAT + +/obj/item/hemostat/psi + name = "psychokinetic hemostat" + item_state = "hemostat" + + var/mob/living/owner + color = "#0095ff" + alpha = 110 + +/obj/item/hemostat/psi/New() + owner = usr + if(!istype(owner)) + qdel(src) + return + START_PROCESSING(SSprocessing, src) + ..() + +/obj/item/hemostat/psi/Destroy() + if(istype(owner) && owner.psi) + LAZYREMOVE(owner.psi.manifested_items, src) + UNSETEMPTY(owner.psi.manifested_items) + STOP_PROCESSING(SSprocessing, src) + . = ..() + +/obj/item/hemostat/psi/get_storage_cost() + return ITEM_SIZE_NO_CONTAINER + +/obj/item/hemostat/psi/attack_self(mob/user) + sound_to(owner, 'sound/effects/psi/power_fail.ogg') + user.drop_from_inventory(src) + +/obj/item/hemostat/psi/dropped() + ..() + qdel(src) + +//DRILL + +/obj/item/surgicaldrill/psi + name = "psychokinetic drill" + item_state = "drill" + + var/mob/living/owner + color = "#0095ff" + alpha = 110 + +/obj/item/surgicaldrill/psi/New() + owner = usr + if(!istype(owner)) + qdel(src) + return + START_PROCESSING(SSprocessing, src) + ..() + +/obj/item/surgicaldrill/psi/Destroy() + if(istype(owner) && owner.psi) + LAZYREMOVE(owner.psi.manifested_items, src) + UNSETEMPTY(owner.psi.manifested_items) + STOP_PROCESSING(SSprocessing, src) + . = ..() + +/obj/item/surgicaldrill/psi/get_storage_cost() + return ITEM_SIZE_NO_CONTAINER + +/obj/item/surgicaldrill/psi/attack_self(mob/user) + sound_to(owner, 'sound/effects/psi/power_fail.ogg') + user.drop_from_inventory(src) + +/obj/item/surgicaldrill/psi/dropped() + ..() + qdel(src) + +//SCALPEL + +/obj/item/scalpel/psi + name = "psychokinetic scalpel" + item_state = "scalpel" + + var/mob/living/owner + color = "#0095ff" + alpha = 110 + +/obj/item/scalpel/psi/New() + owner = usr + if(!istype(owner)) + qdel(src) + return + START_PROCESSING(SSprocessing, src) + ..() + +/obj/item/scalpel/psi/Destroy() + if(istype(owner) && owner.psi) + LAZYREMOVE(owner.psi.manifested_items, src) + UNSETEMPTY(owner.psi.manifested_items) + STOP_PROCESSING(SSprocessing, src) + . = ..() + +/obj/item/scalpel/psi/get_storage_cost() + return ITEM_SIZE_NO_CONTAINER + +/obj/item/scalpel/psi/attack_self(mob/user) + sound_to(owner, 'sound/effects/psi/power_fail.ogg') + user.drop_from_inventory(src) + +/obj/item/scalpel/psi/dropped() + ..() + qdel(src) + +//SAW + +/obj/item/circular_saw/psi + name = "psychokinetic saw" + item_state = "saw3" + + var/mob/living/owner + color = "#0095ff" + alpha = 110 + +/obj/item/circular_saw/psi/New() + owner = usr + if(!istype(owner)) + qdel(src) + return + START_PROCESSING(SSprocessing, src) + ..() + +/obj/item/circular_saw/psi/Destroy() + if(istype(owner) && owner.psi) + LAZYREMOVE(owner.psi.manifested_items, src) + UNSETEMPTY(owner.psi.manifested_items) + STOP_PROCESSING(SSprocessing, src) + . = ..() + +/obj/item/circular_saw/psi/get_storage_cost() + return ITEM_SIZE_NO_CONTAINER + +/obj/item/circular_saw/psi/attack_self(mob/user) + sound_to(owner, 'sound/effects/psi/power_fail.ogg') + user.drop_from_inventory(src) + +/obj/item/circular_saw/psi/dropped() + ..() + qdel(src) + +//BONE SETTER + +/obj/item/bonesetter/psi + name = "psychokinetic bone setter" + item_state = "bone setter" + + var/mob/living/owner + color = "#0095ff" + alpha = 110 + +/obj/item/bonesetter/psi/New() + owner = usr + if(!istype(owner)) + qdel(src) + return + START_PROCESSING(SSprocessing, src) + ..() + +/obj/item/bonesetter/psi/Destroy() + if(istype(owner) && owner.psi) + LAZYREMOVE(owner.psi.manifested_items, src) + UNSETEMPTY(owner.psi.manifested_items) + STOP_PROCESSING(SSprocessing, src) + . = ..() + +/obj/item/bonesetter/psi/get_storage_cost() + return ITEM_SIZE_NO_CONTAINER + +/obj/item/bonesetter/psi/attack_self(mob/user) + sound_to(owner, 'sound/effects/psi/power_fail.ogg') + user.drop_from_inventory(src) + +/obj/item/bonesetter/psi/dropped() + ..() + qdel(src) diff --git a/mods/psionics/code/equipment/psipower_orbs.dm b/mods/psionics/code/equipment/psipower_orbs.dm new file mode 100644 index 0000000000000..940fe38859e5c --- /dev/null +++ b/mods/psionics/code/equipment/psipower_orbs.dm @@ -0,0 +1,815 @@ +///ELECTRIC ORB/// + +//ATOM related things, that cannot be putted in orb itself +/obj/machinery/vending/use_tool(obj/item/W as obj, mob/living/user as mob) + + if(istype(W, /obj/item/psychic_power/psielectro)) + if(istype(user) && user.psi && !user.psi.suppressed && user.psi.get_rank(PSI_METAKINESIS) >= PSI_RANK_APPRENTICE) + var/option = input(user, "Do something!", "What do you want to do?") in list("Hack", "Electrify") + if (!option) + return + if(option == "Hack") + if(do_after(user, 30)) + to_chat(user, "Вы аккуратно меняете настройки автомата...") + if(!emagged) + emag_act() + if(option == "Electrify") + if(do_after(user, 50)) + to_chat(user, "Вы прикладываете руку к автомату, наполняя его избыточной энергией...") + new /obj/temporary(get_turf(src),3, 'icons/effects/effects.dmi', "electricity_constant") + src.seconds_electrified = 50 + + ..() + +/obj/machinery/smartfridge/use_tool(obj/item/O as obj, mob/living/user as mob) + + if(istype(O, /obj/item/psychic_power/psielectro)) + if(istype(user) && user.psi && !user.psi.suppressed && user.psi.get_rank(PSI_METAKINESIS) >= PSI_RANK_APPRENTICE) + if(do_after(user, 30)) + to_chat(user, "Вы аккуратно меняете настройки автомата...") + if(!emagged) + emag_act() + new /obj/temporary(get_turf(src),3, 'icons/effects/effects.dmi', "electricity_constant") + . = TRUE + + ..() + +/obj/machinery/atm/use_tool(obj/item/I as obj, mob/living/user as mob) + + if(istype(I, /obj/item/psychic_power/psielectro)) + if(istype(user) && user.psi && !user.psi.suppressed && user.psi.get_rank(PSI_METAKINESIS) >= PSI_RANK_OPERANT) + if(do_after(user, 30)) + to_chat(user, "Вы прислоняете руку к терминалу, запуская в него мощный поток тока!") + if(!emagged) + emag_act() + new /obj/temporary(get_turf(src),3, 'icons/effects/effects.dmi', "electricity_constant") + . = TRUE + + ..() + +/mob/living/carbon/electrocute_act(shock_damage, obj/source, siemens_coeff = 1.0, def_zone = null) +//По всем законам логики оно не должно работать, но если ставить тут == вместо != - оно волшебным образом ломается + var/obj/item/psychic_power/psielectro/carried_orb + if(carried_orb != src.get_active_hand()) + if(src.psi && src.psi.get_rank(PSI_METAKINESIS) >= PSI_RANK_MASTER) + if(prob(80)) + src.visible_message("[src] absorbed all pure energy, sent into them!") + src.psi.stamina = min(src.psi.max_stamina, src.psi.stamina + rand(15,20)) + carried_orb.charge += 1 + + var/datum/effect/spark_spread/l = new /datum/effect/spark_spread + l.set_up(5, 1, loc) + l.start() + return 0 + + ..() + +/obj/machinery/psi_monitor/use_tool(obj/item/O as obj, mob/living/user as mob) + + if(istype(O, /obj/item/psychic_power/psielectro)) + if(istype(user) && user.psi && !user.psi.suppressed && user.psi.get_rank(PSI_METAKINESIS) >= PSI_RANK_OPERANT) + if(do_after(user, 30)) + to_chat(user, "Вы прислоняете руку к монитору, пропуская мощный поток тока, ломающий все электронные замки!") + if(!emagged) + emag_act() + new /obj/temporary(get_turf(src),3, 'icons/effects/effects.dmi', "electricity_constant") + . = TRUE + return ..() + +//ATOM related stuff ending here + +//ELECTRIC ORB itself +/obj/item/psychic_power/psielectro + name = "orb of energy" + force = 5 + edge = TRUE + maintain_cost = 10 + + item_icons = list( + slot_l_hand_str = 'mods/psionics/icons/psi_fd/lefthand.dmi', + slot_r_hand_str = 'mods/psionics/icons/psi_fd/righthand.dmi', + ) + + icon_state = "electro" + item_state = "electro" + attack_cooldown = 5 + var/charge = 0 + var/cooldown = 0 + +/obj/item/psychic_power/psielectro/attack_self(mob/living/user as mob) + var/el_rank = user.psi.get_rank(PSI_METAKINESIS) + if(el_rank >= PSI_RANK_MASTER) + user.put_in_hands(new /obj/item/psychic_power/electric_whip(user)) + qdel(src) + +/obj/item/psychic_power/psielectro/Process() + if(cooldown > 0) + cooldown-- + + . = ..() + +/obj/item/psychic_power/psielectro/New(mob/living/user) + user = usr + var/el_rank = user.psi.get_rank(PSI_METAKINESIS) + maintain_cost -= el_rank + + ..() + +/obj/item/psychic_power/psielectro/apply_hit_effect(mob/living/target, mob/living/user, hit_zone) + var/el_rank = user.psi.get_rank(PSI_METAKINESIS) + + if(target.psi && !target.psi.suppressed) + var/el_rank_target = target.psi.get_rank(PSI_METAKINESIS) + if(el_rank_target >= el_rank && prob(50)) + user.visible_message("[target] пропускает ток через себя, возвращая его [user] в виде молнии!") + user.electrocute_act(rand(el_rank_target * 2, el_rank_target * 5), target, 1, target.zone_sel.selecting) + new /obj/temporary(get_turf(user),3, 'icons/effects/effects.dmi', "electricity_constant") + ..() + target.electrocute_act(rand(el_rank * 2, el_rank * 5), user, 1, user.zone_sel.selecting) + new /obj/temporary(get_turf(target),3, 'icons/effects/effects.dmi', "electricity_constant") + ..() + +/obj/item/psychic_power/psielectro/afterattack(atom/A as mob|obj|turf|area, mob/living/user as mob, proximity) + +//TURFS AND ANYTHING ELSE + + if(proximity) + var/datum/effect/spark_spread/sparks = new () + sparks.set_up(3, 0, get_turf(A)) + sparks.start() + + if(!proximity) + return + + var/el_rank = user.psi.get_rank(PSI_METAKINESIS) + +//MOBS + + if(istype(A, /mob/living)) + var/mob/living/target = A + + if(cooldown > 0) + to_chat(user, "Ты не можешь использовать данную способность настолько часто!") + return + if(target == user) + to_chat(user, "Вы не можете зарядить самого себя!") + return + if(user.psi && !user.psi.suppressed && user.psi.get_rank(PSI_METAKINESIS) <= PSI_RANK_OPERANT) + user.visible_message("[user] направляет свору еле-заметных молний в тело [target]!") + if(user.psi && !user.psi.suppressed && user.psi.get_rank(PSI_METAKINESIS) >= PSI_RANK_MASTER) + user.visible_message("[user] поражает [target] мощным электрическим шквалом!") + if(target.psi && !target.psi.suppressed) + var/el_rank_target = target.psi.get_rank(PSI_METAKINESIS) + if(el_rank_target >= el_rank && prob(50)) + user.visible_message("[target] пропускает ток через себя, возвращая его [user] в виде молнии!") + user.electrocute_act(rand(el_rank_target * 2,el_rank_target * 5), target, 1, target.zone_sel.selecting) + cooldown += 1 + new /obj/temporary(get_turf(user),3, 'icons/effects/effects.dmi', "electricity_constant") + return TRUE + target.electrocute_act(rand(el_rank + charge * 2,el_rank + charge * 5), user, 1, user.zone_sel.selecting) + cooldown += 1 + if(charge >= 1) + charge -= 1 + new /obj/temporary(get_turf(target),3, 'icons/effects/effects.dmi', "electricity_constant") + return TRUE + +//LIGHT + + if(istype(A, /obj/machinery/light)) + + if(A.do_psionics_check(maintain_cost, user)) + to_chat(user, SPAN_WARNING("Your power skates across \the [A.name], but cannot get a grip...")) + return FALSE + + var/obj/machinery/light/lighting = A + if(lighting.on) + if(user.psi && !user.psi.suppressed && user.psi.get_rank(PSI_METAKINESIS) >= PSI_RANK_OPERANT) + if(do_after(user, 30)) + if(proximity) + user.visible_message("[user] прислоняет руку к источнику света, и уже через пару секунд он угасает!") + to_chat(user, "Вы прислоняете руку к рабочей лампе, высасывая из неё всё содержимое!") + else + user.visible_message("[user] протягивает руку к [lighting], и затем, резким взмахом вырывает всю энергию, которая в нём хранилась!") + to_chat(user, "Вы с скрипом разбиваете источник света, вытягивая всё электричество, которое в нём было.") + lighting.broken(TRUE) + user.psi.stamina = min(user.psi.max_stamina, user.psi.stamina + rand(5,10)) + charge += 1 + else + return + +//CELLS + + var/obj/item/cell/charging_cell = A.get_cell() + if(istype(charging_cell)) + + if(A.do_psionics_check(maintain_cost, user)) + to_chat(user, SPAN_WARNING("Your power skates across \the [A.name], but cannot get a grip...")) + return FALSE + + if(proximity) + user.visible_message("[user] прикладывает руку к [charging_cell], наполняя её энергией!") + else + user.visible_message("[user] направляет руку к [charging_cell], посылая в неё поток молний!") + charging_cell.give(rand(el_rank * 3,el_rank * 6)) + new /obj/temporary(get_turf(A),3, 'icons/effects/effects.dmi', "electricity_constant") + return TRUE + +//AIRLOCKS + + if(istype(A,/obj/machinery/door/airlock)) + var/obj/machinery/door/airlock/D = A + var/option = input(user, "Do something!", "What do you want to do?") in list("Open/Close", "Bolt/Unbolt", "Electrify") + if (!option) + return + + if(A.do_psionics_check(maintain_cost, user)) + to_chat(user, SPAN_WARNING("Your power skates across \the [A.name], but cannot get a grip...")) + return FALSE + + if(option == "Open/Close") + if(user.psi && !user.psi.suppressed && user.psi.get_rank(PSI_METAKINESIS) <= PSI_RANK_OPERANT) + if(prob(30)) + to_chat("Вы несколько раз щёлкаете пальцами у [D.name], но ничего не происходит!") + user.visible_message("[user] несколько раз щёлкает пальцами у [D.name] в непонимании.") + return + if(D && AIRLOCK_OPEN) + D.open() + user.visible_message("[user] щёлкает пальцами и [D.name] открывается.") + new /obj/temporary(get_turf(A),3, 'icons/effects/effects.dmi', "electricity_constant") + playsound(D.loc, "sparks", 50, 1) + if(D && AIRLOCK_CLOSED) + D.close() + user.visible_message("[user] щёлкает пальцами и [D.name] закрывается.") + new /obj/temporary(get_turf(A),3, 'icons/effects/effects.dmi', "electricity_constant") + playsound(D.loc, "sparks", 50, 1) + + if(option == "Electrify") + D.electrify(50, 0) + if(proximity) + user.visible_message("[user] прикладывает руку к панели [D.name], пропуская через неё поток тока.") + else + user.visible_message("[user] посылает в [D.name] мощный поток электричества.") + new /obj/temporary(get_turf(A),3, 'icons/effects/effects.dmi', "electricity_constant") + playsound(D.loc, "sparks", 50, 1) + + if(option == "Bolt/Unbolt") + if(user.psi && !user.psi.suppressed && user.psi.get_rank(PSI_METAKINESIS) >= PSI_RANK_MASTER) + D.toggle_lock() + if(proximity) + user.visible_message("[user] прислоняет обе руки к [D.name], приводя болты в движение.") + else + user.visible_message("[user] сжимает руку в кулак, приводя болты [D.name] в движение.") + new /obj/temporary(get_turf(A),3, 'icons/effects/effects.dmi', "electricity_constant") + playsound(D.loc, "sparks", 50, 1) + else + user.visible_message("[user] прислоняет обе руки к [D.name], но ничего не происходит.") + to_chat("Вы прислоняете свои руки к [D.name], пытаясь пропустить поток через его внутренние механизмы, но ничего не получается!") + return + + ..() + +///FIRE ORB/// + +//ATOM related things, that cannot be putted in orb itself + +/obj/item/psychic_power/psifire/IsFlameSource() + return TRUE + +/obj/item/psychic_power/psifire/IsWelder() + return TRUE + +/mob/living/fire_act(datum/gas_mixture/air, exposed_temperature, exposed_volume) +//По всем законам логики оно не должно работать, но если ставить тут == вместо != - оно волшебным образом ломается + var/obj/item/psychic_power/psifire/carried_orb + if(carried_orb != src.get_active_hand()) + if(src.psi && src.psi.get_rank(PSI_METAKINESIS) >= PSI_RANK_OPERANT) + return + ..() + +//ATOM related stuff ending here + +//FIRE ORB itself +/obj/item/psychic_power/psifire + name = "orb of eternal flame" + force = 5 + edge = TRUE + maintain_cost = 10 + + item_icons = list( + slot_l_hand_str = 'mods/psionics/icons/psi_fd/lefthand.dmi', + slot_r_hand_str = 'mods/psionics/icons/psi_fd/righthand.dmi', + ) + + icon_state = "pyro" + item_state = "pyro" + attack_cooldown = 5 + + var/combat_mode = TRUE + var/turf/previousturf = null + + var/attack_type = "FIRE WALL" + + var/range = 2 + var/flame_power = 25 + var/flame_color = COLOR_RED + +/obj/item/psychic_power/psifire/New(mob/living/user) + user = usr + var/fire_rank = user.psi.get_rank(PSI_METAKINESIS) + + maintain_cost -= fire_rank + flame_power += fire_rank + range += fire_rank + + if(user.psi && !user.psi.suppressed && user.psi.get_rank(PSI_METAKINESIS) >= PSI_RANK_MASTER) + flame_color = "#33ccc9" + flame_power += 5 + + ..() + +/obj/item/psychic_power/psifire/attack_self(mob/living/user as mob) + var/pyro_rank = user.psi.get_rank(PSI_METAKINESIS) + if(combat_mode) + var/list/options = list( + "FIRE WALL" = image('mods/psionics/icons/psi.dmi', "FIRE PUNCH"), + "FIRE JUMP" = image('mods/psionics/icons/psi.dmi', "FIRE JUMP") + ) + var/chosen_option = show_radial_menu(user, user, options, radius = 25, require_near = TRUE) + if (!chosen_option) + return 0 + if(user.psi.suppressed) + return 0 + if(!combat_mode) + return 0 + switch(chosen_option) + if("FIRE WALL") + attack_type = "FIRE WALL" + to_chat(user, "Теперь вы будете стрелять огнём из рук!") + return 1 + if("FIRE JUMP") + if(pyro_rank < PSI_RANK_OPERANT) + to_chat(user, "Вы ещё недостаточно обучены для подобного приёма!") + return 0 + attack_type = "FIRE JUMP" + to_chat(user, "Теперь вы будете использовать огонь в качестве средства передвижения!") + return 1 + +/obj/item/psychic_power/psifire/AltClick(mob/living/carbon/user) + var/list/options = list( + "HELP" = image('mods/psionics/icons/psi.dmi', "HELP"), + "HARM" = image('mods/psionics/icons/psi.dmi', "HARM") + ) + + var/chosen_option = show_radial_menu(user, user, options, radius = 25, require_near = TRUE) + if (!chosen_option) + return 0 + if(user.psi.suppressed) + return 0 + switch(chosen_option) + if("HARM") + combat_mode = TRUE + to_chat(user, "Вы приготовились к бою. Теперь, ваше касание будет поджигать людей.") + if("HELP") + combat_mode = FALSE + to_chat(user, "Вы вновь можете безопасно прикасаться к вещам вокруг.") + +/obj/item/psychic_power/psifire/apply_hit_effect(mob/living/target, mob/living/user, hit_zone) + if(combat_mode) + user.visible_message("[user] прислоняет руку к [target], зажигая его как спичку!") + target.fire_act(exposed_temperature = 300, exposed_volume = 250) + ..() + +/obj/item/psychic_power/psifire/afterattack(atom/A as mob|obj|turf|area, mob/living/user as mob, proximity) +//TURFS + + if(istype(A, /turf) && !proximity && combat_mode) + if(attack_type == "FIRE WALL") + var/turf/target_turf = get_turf(A) + if(target_turf) + var/turflist = getline(user, target_turf) + flame_turf(turflist) + user.visible_message("[user] взмахивает рукой, создавая стену из огня!") + if(attack_type == "FIRE JUMP") + if(get_dist(user, A) > range) + return 0 + var/turf/target_turf = get_step(get_turf(A), pick(GLOB.alldirs)) + var/list/line_list = getline(user, target_turf) + for(var/i = 1 to length(line_list)) + var/turf/T = line_list[i] + var/obj/temp_visual/decoy/D = new /obj/temp_visual/decoy(T, user.dir, user) + D.alpha = min(150 + i*15, 255) + animate(D, alpha = 0, time = 2 + i*2) + user.throw_at(target_turf, range, 1, user, FALSE) + user.visible_message("[user] делает рывок, используя свои ноги как двигатели!") + flame_turf(line_list) + else if(!proximity) + return + +//EATING FIRE + + if(istype(A, /obj/turf_fire)) + var/obj/turf_fire/fire = A + if(fire.fire_power >= 20) + if(user.psi && !user.psi.suppressed && user.psi.get_rank(PSI_METAKINESIS) >= PSI_RANK_OPERANT) + if(do_after(user, 30)) + user.visible_message("[user] протягивает руку к [fire], постепенно поглащая его!") + to_chat(user, "Вы подводите руку к [fire], вытягивая всю энергии, что в нём скопилась. Огонь приятно обвивает вашу руку.") + qdel(fire) + user.psi.stamina = min(user.psi.max_stamina, user.psi.stamina + rand(5,10)) + else + return + +//OTHER STUFF + + var/obj/OBJ = A + if(istype(OBJ)) + if(istype(A, /obj/item/clothing/mask/smokable/cigarette)) + var/obj/item/clothing/mask/smokable/cigarette/S = A + S.light("[user] щёлкает пальцами как зажигалкой, подпаливая [S.name].") + playsound(S.loc, "light_bic", 100, 1, -4) + else + user.visible_message("[user] прислоняет руку к [OBJ]. Можно заметить, как от места соприкосновения идёт пар.") + OBJ.HandleObjectHeating(src, user, 700) + +//MOBS + + if(istype(A, /mob/living) && combat_mode) + var/mob/living/target = A + + user.visible_message("[user] прислоняет руку к [target], зажигая его как спичку!") + target.fire_act(exposed_temperature = 300, exposed_volume = 250) + else if(istype(A, /mob/living)) + var/mob/living/target = A + if(istype(target.wear_mask, /obj/item/clothing/mask/smokable/cigarette) && user.zone_sel.selecting == BP_MOUTH) + var/obj/item/clothing/mask/smokable/cigarette/cig = target.wear_mask + if(target == user) + cig.use_tool(src, user) + else + cig.light("[user] щёлкает пальцами как зажигалкой, подпаливая [cig.name] во рту [target].") + +/obj/item/psychic_power/psifire/proc/flame_turf(list/turflist) + var/length = LAZYLEN(turflist) + if(length < 1) + return + LIST_RESIZE(turflist, min(length, range)) + + playsound(src, pick('sound/weapons/guns/flamethrower1.ogg','sound/weapons/guns/flamethrower2.ogg','sound/weapons/guns/flamethrower3.ogg' ), 50, TRUE, -3) + + for(var/turf/T in turflist) + if(T.density || istype(T, /turf/space)) + break + if(!previousturf && length(turflist)>1) + previousturf = get_turf(src) + continue //so we don't burn the tile we be standin on + if(previousturf && (!T.CanPass(null, previousturf, 0,0) || !previousturf.CanPass(null, T, 0,0))) + break + previousturf = T + + //Consume part of our fuel to create a fire spot + var/obj/turf_fire/TF = T.IgniteTurf(flame_power, flame_color) + if(istype(TF)) + TF.interact_with_atmos = FALSE + T.hotspot_expose((flame_power * 3) + 300, 50) + sleep(1) + previousturf = null + +///ICE ORB/// + +//ATOM related things, that cannot be putted in orb itself +/obj/item/projectile/bullet/pellet/ice + damage = 20 + pellets = 4 + range_step = 1 + spread_step = 50 + armor_penetration = 20 + icon = 'mods/psionics/icons/psi_fd/projectiles.dmi' + icon_state = "ice_spikes" + color = "#9ee0dd" + +/obj/structure/girder/ice_wall + icon_state = "ice wall" + anchored = TRUE + density = TRUE + layer = ABOVE_HUMAN_LAYER + w_class = ITEM_SIZE_NO_CONTAINER + health_max = 200 + icon = 'mods/psionics/icons/psi_fd/freeze.dmi' + icon_state = "ice_cube" + var/timer = 30 + +/obj/structure/girder/ice_wall/New() + . = ..() + run_timer() + +/obj/structure/girder/ice_wall/use_tool(obj/item/W, mob/user) + if (user.a_intent == I_HURT) + ..() + return + + if(istype(W, /obj/item/gun/energy/plasmacutter) || istype(W, /obj/item/psychic_power/psiblade/master/grand/paramount)) + if(istype(W, /obj/item/gun/energy/plasmacutter)) + var/obj/item/gun/energy/plasmacutter/cutter = W + if(!cutter.slice(user)) + return + playsound(src.loc, 'sound/items/Welder.ogg', 100, 1) + to_chat(user, "Now slicing apart the wall...") + if(do_after(user,reinf_material ? 40: 20,src)) + to_chat(user, "You slice apart the wall!") + if(reinf_material) + reinf_material.place_dismantled_product(get_turf(src)) + dismantle() + return + + if(istype(W, /obj/item/pickaxe/diamonddrill)) + playsound(src.loc, 'sound/weapons/Genhit.ogg', 100, 1) + if(do_after(user,reinf_material ? 60 : 40,src)) + to_chat(user, "You drill through the wall!") + if(reinf_material) + reinf_material.place_dismantled_product(get_turf(src)) + dismantle() + return + +/obj/structure/girder/ice_wall/dismantle() + qdel(src) + +/obj/structure/girder/ice_wall/proc/run_timer() + set waitfor = 0 + var/T = timer + while(T > 0) + sleep(1 SECOND) + T-- + src.visible_message(SPAN_WARNING("[src] тает!")) + src.alpha = 200 + sleep(2) + src.alpha = 150 + sleep(2) + src.alpha = 100 + sleep(2) + src.alpha = 50 + sleep(2) + src.alpha = 20 + sleep(2) + src.alpha = 10 + qdel(src) + +//ATOM related stuff ending here + +//ICE ORB itself +/obj/item/psychic_power/psiice + name = "orb of eternal cold" + force = 5 + edge = TRUE + maintain_cost = 10 + + item_icons = list( + slot_l_hand_str = 'mods/psionics/icons/psi_fd/lefthand.dmi', + slot_r_hand_str = 'mods/psionics/icons/psi_fd/righthand.dmi', + ) + + icon_state = "cryo" + item_state = "cryo" + attack_cooldown = 5 + var/combat_mode = TRUE + var/structure_attack = "ICE WALL" + var/range = 2 + var/cooldown = 0 + var/turf/previousturf = null + var/inner_radius = -1 //for all your ring spell needs + var/outer_radius = 2 + +/obj/item/psychic_power/psiice/New(mob/living/user) + user = usr + var/cryo_rank = user.psi.get_rank(PSI_METAKINESIS) + + maintain_cost -= cryo_rank + range += cryo_rank + + ..() + +/obj/item/psychic_power/psiice/Process() + if(cooldown > 0) + cooldown-- + + . = ..() + +/obj/item/psychic_power/psiice/attack_self(mob/living/user as mob) + var/cryo_rank = user.psi.get_rank(PSI_METAKINESIS) + if(combat_mode) + var/list/options = list( + "ICE WALL" = mutable_appearance('mods/psionics/icons/psi.dmi', "WALL"), + "ICE SPIKES" = mutable_appearance('mods/psionics/icons/psi.dmi', "SPIKES"), + "ICE FISTS" = mutable_appearance('mods/psionics/icons/psi.dmi', "FISTS"), + "ICE SWORD" = mutable_appearance('mods/psionics/icons/psi.dmi', "SWORD") + ) + var/chosen_option = show_radial_menu(user, user, options, radius = 25, require_near = TRUE) + if (!chosen_option) + return 0 + if(user.psi.suppressed) + return 0 + if(!combat_mode) + return 0 + switch(chosen_option) + if("ICE WALL") + structure_attack = "ICE WALL" + to_chat(user, "Теперь вы будете возводить ледяные стены при использовании дальней атаки!") + return 1 + if("ICE SPIKES") + if(cryo_rank < PSI_RANK_OPERANT) + to_chat(user, "Вы ещё недостаточно обучены для подобного приёма!") + return 0 + structure_attack = "ICE SPIKES" + to_chat(user, "Теперь вы будете метать ледяные иглы при использовании дальней атаки!") + return 1 + if("ICE FISTS") + user.put_in_hands(new /obj/item/cryokinesis/fists(user)) + qdel(src) + return 1 + if("ICE SWORD") + user.put_in_hands(new /obj/item/cryokinesis/rapier(user)) + qdel(src) + return 1 + +/obj/item/psychic_power/psiice/AltClick(mob/living/user as mob) + var/list/options = list( + "HELP" = image('mods/psionics/icons/psi.dmi', "HELP"), + "HARM" = image('mods/psionics/icons/psi.dmi', "HARM") + ) + + var/chosen_option = show_radial_menu(user, user, options, radius = 25, require_near = TRUE) + if (!chosen_option) + return 0 + if(user.psi.suppressed) + return 0 + switch(chosen_option) + if("HARM") + combat_mode = TRUE + to_chat(user, "Вы приготовились к бою. Теперь, ваше касание будет замораживать людей, вы сможете создавать ледяные стены и прочие приспособления.") + if("HELP") + combat_mode = FALSE + to_chat(user, "Вы вновь можете безопасно прикасаться к вещам вокруг.") + +/obj/item/psychic_power/psiice/apply_hit_effect(mob/living/target, mob/living/user, hit_zone) + var/cryo_rank = user.psi.get_rank(PSI_METAKINESIS) + + if(target.do_psionics_check(maintain_cost, user)) + to_chat(user, SPAN_WARNING("Your power skates across \the [target.name], but cannot get a grip...")) + return FALSE + cooldown += 2 + + if(target == user && cryo_rank >= PSI_RANK_MASTER) + var/list/targets = list() + for(var/turf/point in oview_or_orange(outer_radius, user, "range")) + if(!(point in oview_or_orange(inner_radius, user, "range"))) + if(point.density) + continue + if(istype(point, /turf/space)) + continue + targets += point + + if(!LAZYLEN(targets)) + return FALSE + + var/turf/user_turf = get_turf(user) + for(var/turf/T in targets) + var/obj/structure/girder/ice_wall/IW = new(T) + if(istype(IW)) + IW.pixel_x = (user_turf.x - T.x) * world.icon_size + IW.pixel_y = (user_turf.y - T.y) * world.icon_size + animate(IW, pixel_x = 0, pixel_y = 0, time = 3, easing = EASE_OUT) + + ..() + + new /obj/structure/girder/ice_wall(get_turf(target)) + new /obj/temporary(get_turf(target),3, 'icons/effects/effects.dmi', "extinguish") + target.Stun(3*cryo_rank) + user.visible_message("[user] прикасается к телу [target] побледневшей рукой, обращая его в лёд!") + target.bodytemperature = 500 / cryo_rank + ..() + +/obj/item/psychic_power/psiice/afterattack(atom/A as mob|obj|turf|area, mob/living/user as mob, proximity) + var/cryo_rank = user.psi.get_rank(PSI_METAKINESIS) + + if(cooldown > 0) + to_chat(user, "Ты не можешь использовать данную способность настолько часто!") + return + + if(!proximity && cryo_rank >= PSI_RANK_OPERANT && structure_attack == "ICE SPIKES") + var/obj/item/projectile/pew + var/pew_sound + cooldown += 2 + user.visible_message("[user] запускает вперёд град ледяных пик!") + pew = new /obj/item/projectile/bullet/pellet/ice(get_turf(user)) + pew.name = "stack of ice spikes" + pew_sound = 'sound/weapons/guns/ricochet4.ogg' + if(istype(pew)) + playsound(pew.loc, pew_sound, 25, 1) + pew.original = A + pew.current = A + pew.starting = get_turf(user) + pew.shot_from = user + pew.launch(A, user.zone_sel.selecting, (A.x-user.x), (A.y-user.y)) + +//TURFS + + if(istype(A, /turf)) + var/turf/target = A + if(combat_mode) + if(!proximity && cryo_rank >= PSI_RANK_MASTER && structure_attack == "ICE WALL") + if(do_after(user, 10)) + cooldown += 2 + user.visible_message("[user] возводит стену из льда!") + var/turf/target_turf = get_turf(target) + if(target_turf) + var/turflist = getline(user, target_turf) + ice_turf(turflist) + if(!proximity) + return + if(do_after(user, 10)) + cooldown += 2 + user.visible_message("[user] возводит стену из льда!") + new /obj/temporary(target,3, 'icons/effects/effects.dmi', "blueshatter") + sleep(1) + new /obj/structure/girder/ice_wall(get_turf(target)) + return TRUE + else + if(!proximity) + return + if(istype(A, /turf/simulated)) + var/turf/simulated/sim = target + cooldown += 2 + user.visible_message("[user] покрывает [sim] ледяной коркой!") + sim.wet_floor(5 * cryo_rank) + new /obj/temporary(sim,3, 'icons/effects/effects.dmi', "blueshatter") + return TRUE + + if(!proximity) + return + +//MOBS + + if(istype(A, /mob/living) && combat_mode == TRUE) + if(A.do_psionics_check(maintain_cost, user)) + to_chat(user, SPAN_WARNING("Your power skates across \the [A.name], but cannot get a grip...")) + return FALSE + cooldown += 2 + var/mob/living/target = A + + if(target == user && cryo_rank >= PSI_RANK_MASTER) + var/list/targets = list() + + for(var/turf/point in oview_or_orange(outer_radius, user, "range")) + if(!(point in oview_or_orange(inner_radius, user, "range"))) + if(point.density) + continue + if(istype(point, /turf/space)) + continue + targets += point + + if(!LAZYLEN(targets)) + return FALSE + + var/turf/user_turf = get_turf(user) + for(var/turf/T in targets) + var/obj/structure/girder/ice_wall/IW = new(T) + if(istype(IW)) + IW.pixel_x = (user_turf.x - T.x) * world.icon_size + IW.pixel_y = (user_turf.y - T.y) * world.icon_size + animate(IW, pixel_x = 0, pixel_y = 0, time = 3, easing = EASE_OUT) + + return TRUE + + new /obj/structure/girder/ice_wall(get_turf(target)) + new /obj/temporary(get_turf(target),3, 'icons/effects/effects.dmi', "extinguish") + target.Stun(3*cryo_rank) + user.visible_message("[user] прикасается к телу [target] побледневшей рукой, обращая его в лёд!") + target.bodytemperature = 500 / cryo_rank + return TRUE + +//ITEMS + + if(istype(A,/obj/item)) + var/obj/item/S = A + S.temperature = T0C - 20 + +/obj/item/psychic_power/psiice/proc/ice_turf(list/turflist) + var/length = LAZYLEN(turflist) + if(length < 1) + return + LIST_RESIZE(turflist, min(length, range)) + + playsound(src, pick('sound/weapons/guns/flamethrower1.ogg','sound/weapons/guns/flamethrower2.ogg','sound/weapons/guns/flamethrower3.ogg' ), 50, TRUE, -3) + + for(var/turf/T in turflist) + if(T.density || istype(T, /turf/space)) + break + if(!previousturf && length(turflist)>1) + previousturf = get_turf(src) + continue //so we don't burn the tile we be standin on + if(previousturf && (!T.CanPass(null, previousturf, 0,0) || !previousturf.CanPass(null, T, 0,0))) + break + previousturf = T + + //Consume part of our fuel to create a fire spot + new /obj/structure/girder/ice_wall(get_turf(T)) + sleep(1) + previousturf = null diff --git a/mods/psionics/code/equipment/psipower_tinker.dm b/mods/psionics/code/equipment/psipower_tinker.dm new file mode 100644 index 0000000000000..9b78389f40b27 --- /dev/null +++ b/mods/psionics/code/equipment/psipower_tinker.dm @@ -0,0 +1,39 @@ +/obj/item/psychic_power/tinker + name = "psychokinetic crowbar" + icon_state = "tinker" + force = 1 + var/emulating = "Crowbar" + +/obj/item/psychic_power/tinker/IsCrowbar() + return emulating == "Crowbar" + +/obj/item/psychic_power/tinker/IsWrench() + return emulating == "Wrench" + +/obj/item/psychic_power/tinker/IsScrewdriver() + return emulating == "Screwdriver" + +/obj/item/psychic_power/tinker/IsWirecutter() + return emulating == "Wirecutters" + +/obj/item/psychic_power/tinker/attack_self() + + if(!owner || loc != owner) + return + + var/choice = input("Select a tool to emulate.","Power") as null|anything in list("Crowbar","Wrench","Screwdriver","Wirecutters","Dismiss") + if(!choice) + return + + if(!owner || loc != owner) + return + + if(choice == "Dismiss") + sound_to(owner, 'sound/effects/psi/power_fail.ogg') + owner.drop_from_inventory(src) + return + + emulating = choice + name = "psychokinetic [lowertext(emulating)]" + to_chat(owner, SPAN_NOTICE("You begin emulating \a [lowertext(emulating)].")) + sound_to(owner, 'sound/effects/psi/power_fabrication.ogg') diff --git a/mods/psionics/code/equipment/psipower_tk.dm b/mods/psionics/code/equipment/psipower_tk.dm new file mode 100644 index 0000000000000..ffc8426637555 --- /dev/null +++ b/mods/psionics/code/equipment/psipower_tk.dm @@ -0,0 +1,105 @@ +/obj/item/psychic_power/telekinesis + name = "telekinetic grip" + maintain_cost = 6 + icon_state = "telekinesis" + var/atom/movable/focus + +/obj/item/psychic_power/telekinesis/Destroy() + focus = null + . = ..() + +/obj/item/psychic_power/telekinesis/Process() + if(!focus || !isturf(focus.loc) || 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(atom/movable/_focus) + + if(!_focus.simulated || !isturf(_focus.loc)) + 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_GRANDMASTER)) + focus = _focus + . = attack_self(owner) + if(!.) + to_chat(owner, SPAN_WARNING("\The [_focus] слишком тяжелый.")) + return FALSE + + focus = _focus + ClearOverlays() + var/image/I = image(icon = focus.icon, icon_state = focus.icon_state) + I.color = focus.color + I.CopyOverlays(focus) + AddOverlays(I) + return TRUE + +/obj/item/psychic_power/telekinesis/attack_self(mob/user) + user.visible_message(SPAN_NOTICE("\The [user] показывает странный жест.")) + sparkle() + return focus.do_simple_ranged_interaction(user) + +/obj/item/psychic_power/telekinesis/afterattack(atom/target, mob/living/user, proximity) + + if(!target || !user || (isobj(target) && !isturf(target.loc)) || !user.psi || !user.psi.can_use() || !user.psi.spend_power(8)) + return + + user.setClickCooldown(DEFAULT_ATTACK_COOLDOWN * 2) + user.psi.set_cooldown(8) + + var/user_psi_leech = user.do_psionics_check(5, user) + if(user_psi_leech) + to_chat(user, SPAN_WARNING("Ты тянешься к \the [target], но твоя хватка размыта с помощью \the [user_psi_leech]...")) + return + + if(target.do_psionics_check(5, user)) + to_chat(user, SPAN_WARNING("Твоя телекинетическая хватка пытается ухватить \the [target], но тщетно...")) + return + + 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("Ты не дотягиваешься.")) + return FALSE + + if(target == focus) + attack_self(user) + else + user.visible_message(SPAN_DANGER("\The [user] резко жестикулирует!")) + sparkle() + if(!isturf(focus.loc) && isitem(focus) && target.Adjacent(focus)) + var/obj/item/I = focus + var/resolved = I.resolve_attackby(target, user) + 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*3, owner) + sleep(1) + sparkle() + owner.drop_from_inventory(src) + +/obj/item/psychic_power/telekinesis/proc/sparkle() + set waitfor = 0 + if(focus) + var/obj/overlay/O = new /obj/overlay(get_turf(focus)) + O.name = "sparkles" + O.anchored = TRUE + O.density = FALSE + O.layer = FLY_LAYER + O.set_dir(pick(GLOB.cardinal)) + O.icon = 'icons/effects/effects.dmi' + O.icon_state = "nothing" + flick("empdisable",O) + sleep(5) + qdel(O) diff --git a/mods/psionics/code/equipment/psipump.dm b/mods/psionics/code/equipment/psipump.dm new file mode 100644 index 0000000000000..9b086f18749ec --- /dev/null +++ b/mods/psionics/code/equipment/psipump.dm @@ -0,0 +1,78 @@ +// WITHOUT CRYSTAL +/obj/item/clothing/head/helmet/psipump + name = "Psi-pump" + desc = "It's a bulky design with lots of sensors and looks more like a huge makeshift helmet than a device. Something's missing" + icon = 'mods/psionics/icons/pump/pump.dmi' + icon_state = "psipump" + item_icons = list(slot_head_str = 'mods/psionics/icons/pump/pump_on_mob.dmi') + matter = list(MATERIAL_STEEL = 20000) + + item_state_slots = list( + slot_l_hand_str = "helmet", + slot_r_hand_str = "helmet" + ) + w_class = ITEM_SIZE_LARGE + +/obj/item/clothing/head/helmet/psipump/attack_hand(mob/living/carbon/human/H) + if(src == H.head) + to_chat(H, SPAN_NOTICE("You need help taking this off!")) + return + ..() + +/obj/item/clothing/head/helmet/psipump/MouseDrop(obj/over_object) + return + +/obj/item/clothing/head/helmet/psipump/use_tool(obj/item/tool, mob/user) + if (istype(tool, /obj/item/device/soulstone)) + qdel(tool) + new /obj/item/clothing/head/helmet/psipump/active(get_turf(src)) + qdel(src) + return ..() + +// RND - PSI-DAMPENER +/datum/design/item/implant/psidamp + name = "Psi dampener implant" + id = "psi_damp" + req_tech = list(TECH_MATERIAL = 2, TECH_BIO = 3, TECH_BLUESPACE = 3) + materials = list(MATERIAL_DIAMOND = 500, MATERIAL_GLASS = 500, MATERIAL_PLASTIC = 500) + build_path = /obj/item/implant/psi_control + sort_string = "PSDMP" + +/datum/technology/bio/psidamp + name = "Psionic dampering" + desc = "Fabrication of Psi-Pump" + id = "psidamp" + + x = 0.2 + y = 0.7 + icon = "implant" + + required_technologies = list(/datum/technology/bio/implants) + required_tech_levels = list() + cost = 1500 + + unlocks_designs = list("psi_damp") + +// RND - PSI-PUMP +/datum/design/autolathe/arms_ammo/psipump + name = "Psi-pump" + build_path = /obj/item/clothing/head/helmet/psipump + +// WITH CRYSTAL +/obj/item/clothing/head/helmet/psipump/active + name = "Psi-pump" + desc = "It's a bulky design with lots of sensors and looks more like a huge makeshift helmet than a device." + icon_state = "psipump" + + item_state_slots = list( + slot_l_hand_str = "helmet", + slot_r_hand_str = "helmet" + ) + w_class = ITEM_SIZE_LARGE + +/obj/item/clothing/head/helmet/psipump/active/disrupts_psionics() + return src + +/datum/codex_entry/psipump + associated_paths = list(/obj/item/clothing/head/helmet/psipump, /obj/item/clothing/head/helmet/psipump/active) + lore_text = "The psi-pump is a device developed by an exonet psychopath, which was subsequently posted online as a template for lathes. The device is securely fixed on the head and without outside help, it is almost impossible to remove. The 'soul' crystal inside disrupts the functioning of psionics." diff --git a/mods/psionics/code/events/_psi.dm b/mods/psionics/code/events/_psi.dm new file mode 100644 index 0000000000000..67671271afde1 --- /dev/null +++ b/mods/psionics/code/events/_psi.dm @@ -0,0 +1,24 @@ +/datum/event/psi + startWhen = 30 + endWhen = 120 + +/datum/event/psi/announce() + priority_announcement.Announce( \ + "Обнаружена локальная абнормальность в соседних псионических континуумах. Всем членам экипажа, обладающим психонетикой \ + рекомендовано приостановить работу и обратится к врачу в случае проявления симптомов.", \ + "Автоматическое сообщение массива датчиков Фонда Кухулин" \ + ) + +/datum/event/psi/end() + priority_announcement.Announce( \ + "Пси-абнормальность прекратилась и нормализовался исходный уровень. \ + Все, с чем вы по-прежнему не можете справиться, является вашей личной проблемой.", \ + "Автоматическое сообщение массива датчиков Фонда Кухулин" \ + ) + +/datum/event/psi/tick() + for(var/thing in SSpsi.processing) + apply_psi_effect(thing) + +/datum/event/psi/proc/apply_psi_effect(datum/psi_complexus/psi) + return diff --git a/mods/psionics/code/events/mini_spasm.dm b/mods/psionics/code/events/mini_spasm.dm new file mode 100644 index 0000000000000..c0a427ea2b9fe --- /dev/null +++ b/mods/psionics/code/events/mini_spasm.dm @@ -0,0 +1,67 @@ +/datum/event/minispasm + startWhen = 60 + endWhen = 90 + var/static/list/psi_operancy_messages = list( + "Что-то ползет в твоем черепе!", + "Что-то разъедает твои мысли!", + "Ты ощущаешь как твой мозг промывается чем-то!", + "Ты испытываешь неприятное ощущение, словно через глаз, кто-то дотрагивается к твоему мозгу!", + "СИГНАЛ СИГНАЛ СИГНАЛ СИГНАЛ СИГНАЛ СИГНАЛ" + ) + +/datum/event/minispasm/announce() + priority_announcement.Announce( \ + "ПРИОРИТЕТНОЕ ОПОВЕЩЕНИЕ: Ф-[rand(50,80)] ОБНАРУЖЕНА ЛОКАЛЬНАЯ ПЕРЕДАЧА ПСИОНИЧЕСКОГО СИГНАЛА ([rand(70,100)]% СОВПАДЕНИЕ) \ + (ИСТОЧНИК СИГНАЛА ТРИАНГУЛИРОВАН — СОСЕДНИЙ МЕСТНЫЙ УЧАСТОК): Всем сотрудникам рекомендуется избегать \ + воздействия активного аудиопередающего оборудования, включая радиогарнитуры и переговорные устройства \ + на время трансляции сигнала.", \ + "Автоматическое сообщение массива датчиков Фонда Кухулин" \ + ) + +/datum/event/minispasm/start() + var/list/victims = list() + for(var/obj/item/device/radio/radio in GLOB.listening_objects) + if(radio.on) + for(var/mob/living/victim in range(radio.canhear_range, radio.loc)) + if(isnull(victims[victim]) && victim.stat == CONSCIOUS && !victim.ear_deaf) + victims[victim] = radio + for(var/thing in victims) + var/mob/living/victim = thing + var/obj/item/device/radio/source = victims[victim] + do_spasm(victim, source) + +/datum/event/minispasm/proc/do_spasm(mob/living/victim, obj/item/device/radio/source) + set waitfor = 0 + + if(iscarbon(victim) && !victim.isSynthetic()) + var/list/disabilities = list(NEARSIGHTED, EPILEPSY, NERVOUS) + for(var/disability in disabilities) + if(victim.disabilities & disability) + disabilities -= disability + if(length(disabilities)) + victim.disabilities |= pick(disabilities) + + if(victim.psi) + to_chat(victim, SPAN_DANGER("Слишком знакомый визг исходит из [icon2html(source, victim)] \the [source]. Твой разум мутнеет!")) + victim.psi.backblast(rand(5,15)) + victim.Paralyse(5) + victim.make_jittery(100) + else + to_chat(victim, SPAN_DANGER("Неописуемый, пронзительный визг исходит из [icon2html(source, victim)] \the [source]. Тебя охватывают судороги!")) + 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(FONT_LARGE(pick(psi_operancy_messages)))) + victim.adjustBrainLoss(rand(10,20)) + victim.set_psi_rank(pick_n_take(faculties), 1) + sleep(30) + victim.psi.update() + sleep(45) + victim.psi.check_latency_trigger(100, "психонетический крик", redactive = TRUE) + +/datum/event/minispasm/end() + priority_announcement.Announce( \ + "ПРИОРИТЕТНОЕ ОПОВЕЩЕНИЕ: ТРАНСЛЯЦИЯ СИГНАЛА ПРЕКРАЩЕНА. Персоналу разрешено возобновить использование незащищенного радиопередающего оборудования. Хорошего дня.", \ + "Автоматическое сообщение массива датчиков Фонда Кухулин" \ + ) diff --git a/mods/psionics/code/events/psi_balm.dm b/mods/psionics/code/events/psi_balm.dm new file mode 100644 index 0000000000000..17fb01d2b4aa0 --- /dev/null +++ b/mods/psionics/code/events/psi_balm.dm @@ -0,0 +1,20 @@ +/datum/event/psi/balm + var/static/list/balm_messages = list( + "Успокаивающие мысли омывают вашу психонетику.", + "На момент, ты слышишь приятный, знакомый голос, что поет песню где-то в дали.", + "Чувство покоя и комфорта окутывает вас, словно теплое одеяло." + ) + +/datum/event/psi/balm/apply_psi_effect(datum/psi_complexus/psi) + var/soothed + if(psi.stun > 1) + psi.stun-- + soothed = TRUE + else if(psi.stamina < psi.max_stamina) + psi.stamina = min(psi.max_stamina, psi.stamina + rand(1,3)) + soothed = TRUE + else if(psi.owner.getBrainLoss() > 0) + psi.owner.adjustBrainLoss(-1) + soothed = TRUE + if(soothed && prob(10)) + to_chat(psi.owner, SPAN_NOTICE("[pick(balm_messages)]")) diff --git a/mods/psionics/code/events/psi_wail.dm b/mods/psionics/code/events/psi_wail.dm new file mode 100644 index 0000000000000..ad93343cca320 --- /dev/null +++ b/mods/psionics/code/events/psi_wail.dm @@ -0,0 +1,17 @@ +/datum/event/psi/wail + var/static/list/whine_messages = list( + "Вой, что угнетает твой мозг, вторгается в твою голову.", + "Ужасный, отвлекающий жужжащий звук прерывает ход твоих мыслей.", + "Ты ощущаешь страшную мигрень, как в твою голову вторгается знакомый вой." + ) + +/datum/event/psi/wail/apply_psi_effect(datum/psi_complexus/psi) + var/annoyed + if(prob(1)) + psi.stunned(1) + annoyed = TRUE + else if(psi.stamina > 0) + psi.stamina = max(0, psi.stamina - rand(1,3)) + annoyed = TRUE + if(annoyed && prob(1)) + to_chat(psi.owner, SPAN_NOTICE("[pick(whine_messages)]")) diff --git a/mods/psionics/code/faculties/_faculty.dm b/mods/psionics/code/faculties/_faculty.dm new file mode 100644 index 0000000000000..5df834831bae5 --- /dev/null +++ b/mods/psionics/code/faculties/_faculty.dm @@ -0,0 +1,11 @@ +/singleton/psionic_faculty + var/id + var/name + var/associated_intent + var/list/armour_types = list() + var/list/powers = list() + +/singleton/psionic_faculty/New() + ..() + for(var/atype in armour_types) + SSpsi.armour_faculty_by_type[atype] = id diff --git a/mods/psionics/code/faculties/_power.dm b/mods/psionics/code/faculties/_power.dm new file mode 100644 index 0000000000000..0143ea04a4dfd --- /dev/null +++ b/mods/psionics/code/faculties/_power.dm @@ -0,0 +1,50 @@ +/singleton/psionic_power + abstract_type = /singleton/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' + +/singleton/psionic_power/proc/invoke(mob/living/user, atom/target) + + if(!user.psi) + return FALSE + + if(!user.psi.ranks_stat[faculty]) + 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 + + var/user_psi_leech = user.do_psionics_check(cost, user) + if(user_psi_leech) + to_chat(user, SPAN_WARNING("Your power is leeched away by \the [user_psi_leech] as fast as you can focus it...")) + return FALSE + + if(target.do_psionics_check(cost, user)) + to_chat(user, SPAN_WARNING("Your power skates across \the [target], but cannot get a grip...")) + return FALSE + + return TRUE + +/singleton/psionic_power/proc/handle_post_power(mob/living/user, 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/mods/psionics/code/faculties/coercion.dm b/mods/psionics/code/faculties/coercion.dm new file mode 100644 index 0000000000000..8d258f397d058 --- /dev/null +++ b/mods/psionics/code/faculties/coercion.dm @@ -0,0 +1,326 @@ +/singleton/psionic_faculty/coercion + id = PSI_COERCION + name = "Coercion" + associated_intent = I_DISARM + armour_types = list(DAMAGE_PSIONIC) + +/singleton/psionic_power/coercion + faculty = PSI_COERCION + abstract_type = /singleton/psionic_power/coercion + +/singleton/psionic_power/coercion/invoke(mob/living/user, mob/living/target) + . = ..() + if (!.) + return FALSE + + if (!istype(target)) + to_chat(user, SPAN_WARNING("Вы не можете пробиться в сознание [target].")) + return FALSE + + if(. && target.deflect_psionic_attack(user)) + return FALSE + +/singleton/psionic_power/coercion/blindstrike + name = "Blindstrike" + cost = 25 + cooldown = 120 + use_ranged = TRUE + use_melee = TRUE + min_rank = PSI_RANK_OPERANT + use_description = "Выберите глаза и переключитесь на синий интент. Затем, нажмите куда угодно чтобы применить круговую атаку, слепящую и оглушающую всех, кто оказался поблизости." + +/singleton/psionic_power/coercion/blindstrike/invoke(mob/living/user, mob/living/target) + if(user.zone_sel.selecting != BP_EYES) + return FALSE + . = ..() + if(.) + user.visible_message(SPAN_DANGER("[user] закидывает голову назад, издавая пронзительный крик!")) + to_chat(user, SPAN_DANGER("Вы издаёте пронзительный крик, оглушая всех вокруг!")) + var/cn_rank = user.psi.get_rank(PSI_COERCION) + for(var/mob/living/M in range(user, user.psi.get_rank(PSI_COERCION))) + if(M == user) + continue + if(prob(cn_rank * 20) && iscarbon(M)) + var/mob/living/carbon/C = M + if(C.can_feel_pain()) + M.emote("scream") + to_chat(M, SPAN_DANGER("Ты ощущаешь, как земля уходит у тебя из под ног!")) + M.flash_eyes() + new /obj/temporary(get_turf(user),6, 'icons/effects/effects.dmi', "summoning") + new /obj/temporary(get_turf(M),3, 'icons/effects/effects.dmi', "purple_electricity_constant") + M.eye_blind = max(M.eye_blind,cn_rank) + M.ear_deaf = max(M.ear_deaf,cn_rank * 2) + M.mod_confused(cn_rank * rand(1,3)) + return TRUE + +/singleton/psionic_power/coercion/emotions + name = "Emotion Amplifier" + cost = 20 + cooldown = 30 + use_melee = TRUE + use_ranged = TRUE + min_rank = PSI_RANK_OPERANT + use_description = "Выберите грудь и переключитесь на синий интент. Затем, нажмите по вашей цели, чтобы многократно усилить одну из её эмоций." + +/singleton/psionic_power/coercion/emotions/invoke(mob/living/user, mob/living/target) + var/list/options = list( + "Joy" = image('mods/psionics/icons/psi.dmi', "JOY"), + "Sadness" = image('mods/psionics/icons/psi.dmi', "SADNESS"), + "Fear" = image('mods/psionics/icons/psi.dmi', "FEAR"), + "Caution" = image('mods/psionics/icons/psi.dmi', "ANXIETY"), + "Anger" = image('mods/psionics/icons/psi.dmi', "ANGER"), + "Stillness" = image('mods/psionics/icons/psi.dmi', "STILLNESS") + ) + + if(user.zone_sel.selecting != BP_CHEST) + return FALSE + if(target == user) + return FALSE + if(isrobot(target)) + return FALSE + . = ..() + if(.) + + var/chosen_option = show_radial_menu(user, user, options, radius = 25, require_near = TRUE) + if (!chosen_option) + return 0 + if(user.psi.suppressed) + return 0 + switch(chosen_option) + if("Joy") + var/funny_option = pick("смеетесь над несмешной шуткой", "стараетесь не обидеть друга", "насмехаетесь над [user]") + to_chat(target, SPAN_WARNING("Внезапно, вы ощущаете принужденный смех. Как будто вы [funny_option].")) + var/mob/living/carbon/C = target + C.Weaken(5) + C.spin(32,2) + C.emote("giggle") + sleep(3 SECONDS) + C.emote("giggle") + sleep(6 SECONDS) + C.emote("giggle") + return 1 + if("Sadness") + var/sad_option = pick("то, как умирают солдаты на войне", "мысль о том, что после смерти ничего нет", "то, что вы никчемны", "воспоминание, как вы опозорились") + to_chat(target, SPAN_WARNING("В голове проскакивает [sad_option].")) + var/mob/living/carbon/C = target + C.eye_blurry = max(C.eye_blurry, 10) + C.emote("whimper") + sleep(3 SECONDS) + C.emote("whimper") + sleep(6 SECONDS) + C.emote("whimper") + return 1 + if("Fear") + to_chat(target, SPAN_OCCULT("Вы цепенеете, завидев [user]. Холодный пот стекает по вашему лбу.")) + var/cn_rank = user.psi.get_rank(PSI_COERCION) + var/mob/living/carbon/C = target + C.make_dizzy(10) + C.Stun(5 + cn_rank) + return 1 + if("Caution") + var/strange_option = pick("ощущаете чьё-то зловещее присутствие", "сильно потеете", "чувствуете, что за вами что-то наблюдает", "ощущаете странный холод", "чувствуете, как что-то ползает по вам") + to_chat(target, SPAN_DANGER("Вы [strange_option].")) + return 1 + if("Anger") + var/anger_option = pick("к самому себе", "к человеку, что стоит рядом", "к месту, в котором вы находитесь", "к своей жизни", "по отношению к ситуации", "к сегодняшнему дню", "к погоде", "к вашей работе", "к тому, что было вчера") + to_chat(target, SPAN_DANGER("Внезапно, вы ощущаете злобу [anger_option].")) + return 1 + if("Stillness") + to_chat(target, SPAN_NOTICE("Вы ощущаете странное умиротворение.")) + if(target.psi) + target.psi.stamina = min(target.psi.max_stamina, target.psi.stamina + rand(15,20)) + return 1 + +/singleton/psionic_power/coercion/agony + name = "Agony" + cost = 8 + cooldown = 60 + use_melee = TRUE + min_rank = PSI_RANK_APPRENTICE + use_description = "Выберите нижнюю часть тела на синем интенте, а затем нажмите по цели вблизи, дабы совершить по ней удар, по силе сравнимый с шоковой дубинкой." + +/singleton/psionic_power/coercion/agony/invoke(mob/living/user, mob/living/target) + if(!istype(target)) + return FALSE + if(user.zone_sel.selecting != BP_GROIN) + return FALSE + . = ..() + if(.) + user.visible_message(SPAN_DANGER("\ [target] дотрагивается к [user]")) + playsound(user.loc, 'sound/weapons/Egloves.ogg', 50, 1, -1) + var/cn_rank = user.psi.get_rank(PSI_COERCION) + new /obj/temporary(get_turf(target),3, 'icons/effects/effects.dmi', "blue_electricity_constant") + target.stun_effect_act(0, cn_rank * 30, user.zone_sel.selecting) + return TRUE + +/singleton/psionic_power/coercion/spasm + name = "Spasm" + cost = 15 + cooldown = 100 + use_ranged = TRUE + min_rank = PSI_RANK_APPRENTICE + use_description = "Выберите кисти или руки на синем интенте. Затем, совершите дистанционную атаку по цели, чтобы попытаться вырвать оружие(или иной предмет) из ранее выбранной конечности." + +/singleton/psionic_power/coercion/spasm/invoke(mob/living/user, 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(.) + var/cn_rank = user.psi.get_rank(PSI_COERCION) + to_chat(user, SPAN_DANGER("Вы ментально протыкаете руку [target] иглой.")) + to_chat(target, SPAN_DANGER("Вы ощущаете, как вашу руку проткнули иглой!")) + if(prob(80)) + target.emote("scream") + if(prob(cn_rank * 20) && target.l_hand && target.l_hand.simulated && target.unEquip(target.l_hand)) + target.visible_message(SPAN_DANGER("[target] из-за спазма роняет предмет, находившийся в левой руке!")) + if(prob(cn_rank * 20) && target.r_hand && target.r_hand.simulated && target.unEquip(target.r_hand)) + target.visible_message(SPAN_DANGER("[target] из-за спазма невольно роняет предмет, находившийся в правой руке!")) + new /obj/temporary(get_turf(target),3, 'icons/effects/effects.dmi', "white_electricity_constant") + return TRUE + + +/singleton/psionic_power/coercion/mindslave + name = "Mindslave" + cost = 28 + cooldown = 200 + use_grab = TRUE + min_rank = PSI_RANK_GRANDMASTER + use_description = "Схватите жертву, цельтесь в глаза, нажмите захватом с синим интентом на цель, чтобы обратить цель в раба. Процесс занимает время, а прерывание может оглушить вас." + +/singleton/psionic_power/coercion/mindslave/invoke(mob/living/user, 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] мертв!")) + return TRUE + if(!target.mind || !target.key) + to_chat(user, SPAN_WARNING("\The [target] не обладает разумом!")) + return TRUE + if(GLOB.thralls.is_antagonist(target.mind)) + to_chat(user, SPAN_WARNING("\The [target] уже кому-то принадлежит!")) + return TRUE + user.visible_message(SPAN_DANGER("\The [user] хватает голову \the [target] руками...")) + to_chat(user, SPAN_WARNING("Ты ввергаешь свою ментальность в \the [target]...")) + to_chat(target, SPAN_DANGER("В твой разум стучится \the [user]! Он пытается поработить тебя!")) + if(!do_after(user, (target.stat == CONSCIOUS ? 8 : 4) SECONDS, target, DO_DEFAULT | DO_USER_UNIQUE_ACT)) + user.psi.backblast(rand(10,25)) + return TRUE + to_chat(user, SPAN_DANGER("Ты прорываешься через мозг \the [target], изменяя его под твое желание, оставляя его подчиненным твоей воле!")) + to_chat(target, SPAN_DANGER("Ты ослаб и \the [user] поработил тебя.")) + GLOB.thralls.add_antagonist(target.mind, new_controller = user) + return TRUE +/* +/singleton/psionic_power/coercion/mind_control + name = "Mind Control" + cost = 28 + cooldown = 10 SECONDS + use_ranged = TRUE + use_melee = TRUE + min_rank = PSI_RANK_OPERANT + use_description = "Переключитесь на синий интент, затем выберите голову и нажмите по цели дабы ВРЕМЕННО обратить её в своего верного подчинённого." + + var/invoking = FALSE + +/singleton/psionic_power/coercion/mind_control/invoke(mob/living/user, mob/living/target) + if(!istype(target)) + return FALSE + + if(user.zone_sel.selecting != BP_HEAD) + return FALSE + + if(!can_use(user, target)) + return FALSE + + if(user == target) + return FALSE + + if(!..()) + return FALSE + + user.visible_message("[user] прижимает палец к веску, старательно концентрируясь над чем-то...") + to_chat(user, "Вы проникаете в хрупкое сознание [target]...") + to_chat(target, "Вы ощущаете присутствие [user] в своей голове! Его настойчивые мысли и желания постепенно заражают ваш разум!") + + invoking = TRUE + + var/rank = user.psi.get_rank(faculty) + var/delay = 30 SECONDS / (rank - 2) // 30, 20, 10 + if(!do_after(user, delay, target, DO_SHOW_PROGRESS|DO_USER_SAME_HAND|DO_BOTH_CAN_TURN|DO_TARGET_CAN_MOVE)) + user.psi.backblast(rand(2,10)) + invoking = FALSE + return TRUE + + invoking = FALSE + + if(get_dist(user, target) > world.view) + to_chat(user, "[target] находится слишком далеко!") + return TRUE + + if(!can_use(user, target)) + return TRUE + + var/flags = rank > PSI_RANK_OPERANT ? null : MIND_CONTROL_ALLOW_SAY + var/duration = 1 MINUTE + 2 MINUTE * (rank - 3) // 1, 3, 5 + + user.set_control_mind(target, duration, flags) + handle_mind_link(user, duration, target) + + to_chat(user, "Вы вторгаетесь в сознание [target], ощущая частичный контроль над его телом!") + to_chat(target, "Ваши стены наконец пали, и вы потеряли контроль над своим телом!") + + return TRUE + +/singleton/psionic_power/coercion/mind_control/proc/can_use(mob/living/user, mob/living/target) + if(invoking) + return FALSE + + if(target.stat == UNCONSCIOUS) + to_chat(user, "[target] находится в бессознательном состоянии!") + return FALSE + + if(target.stat == DEAD || (target.status_flags & FAKEDEATH)) + to_chat(user, "[target] мёртв!") + return FALSE + + var/datum/mind_control/mind_controller = user.mind_controller + if(mind_controller && (target in mind_controller.affected)) + to_chat(user, "[target] уже находится под чьим-то контролем!") + return FALSE + + return TRUE + +/singleton/psionic_power/coercion/mind_control/proc/handle_mind_link(mob/user, duration, target) + set waitfor = FALSE + + if(user.client) + user.client.verbs += /client/proc/order_move + + do_after(user, duration, null, DO_SHOW_PROGRESS|DO_BOTH_CAN_TURN|DO_BOTH_CAN_MOVE) + to_chat(user, "Разум [target] более не находится в вашем подчинении!") + + var/datum/mind_control/mind_controller = user.mind_controller + if(!mind_controller) + mind_controller.affected -= target + return + + if(user.client) + user.client.verbs -= /client/proc/order_move + +/client/proc/order_move(atom/A as mob|obj|turf in view()) + set name = "Mind Control: Move" + set category = "IC" + + var/datum/mind_control/mind_controller = mob.mind_controller + if(!mind_controller) + verbs -= /client/proc/order_move + return + + mind_controller.target = A +*/ diff --git a/mods/psionics/code/faculties/consciousness.dm b/mods/psionics/code/faculties/consciousness.dm new file mode 100644 index 0000000000000..841eb912db8d6 --- /dev/null +++ b/mods/psionics/code/faculties/consciousness.dm @@ -0,0 +1,549 @@ +/singleton/psionic_faculty/consciousness + id = PSI_CONSCIOUSNESS + name = "Consciousness" + associated_intent = I_HELP + armour_types = list(DAMAGE_PSIONIC) + +/singleton/psionic_power/consciousness + faculty = PSI_CONSCIOUSNESS + abstract_type = /singleton/psionic_power/consciousness + +/singleton/psionic_power/consciousness/invoke(mob/living/user, mob/living/target) + . = ..() + if (!.) + return FALSE + + if (!istype(target)) + to_chat(user, SPAN_WARNING("Вы не можете пробиться в сознание [target].")) + return FALSE + + if(. && target.deflect_psionic_attack(user) && target != user) + return FALSE + +/singleton/psionic_power/consciousness/telepathy + name = "Telepathy" + cost = 2 + cooldown = 50 + use_ranged = TRUE + use_melee = TRUE + min_rank = PSI_RANK_APPRENTICE + use_description = "Выберите рот на зелёном интенте, и затем нажмите по цели с любого расстояния, чтобы установить с ней ментальную связь." + +/mob/living + var/space = 0 + var/linked_soul + +/mob/living/proc/ContactSoulmate() + set name = "Contact your friend" + set category = "Psionics" + + if (!linked_soul) + return + + var/phrase = input(usr, "Что вы хотите сказать?", "Связаться", "Ты меня слышишь?") as null|text + if(!phrase || usr.incapacitated()) + return FALSE + + to_chat(usr, SPAN_NOTICE("Вы пытаетесь установить контакт с сознанием [linked_soul], дабы донести до него следующее: [phrase]")) + to_chat(linked_soul, SPAN_OCCULT("Вы слышите отчётливый голос [usr] в своей голове, он говорит вам: [phrase]")) + var/option = alert(linked_soul, "Вы хотите ответить этому зову?", "Обратная связь", "Да", "Нет") + switch(option) + if("Да") + var/answer = input(linked_soul, "Что вы хотите передать в ответ?", "Связаться", "...") as null|text + to_chat(usr, SPAN_OCCULT("[linked_soul] отвечает вам: [answer]")) + else + return + +/singleton/psionic_power/consciousness/telepathy/invoke(mob/living/user, mob/living/target) + if(user.zone_sel.selecting != BP_MOUTH || user.a_intent != I_HELP || target == user) + return FALSE + . = ..() + if(.) + if(target.stat == DEAD || (target.status_flags & FAKEDEATH) || !target.client) + to_chat(user, SPAN_WARNING("[target] не в состоянии ответить вам.")) + return FALSE + + if(user.psi.get_rank(PSI_CONSCIOUSNESS) >= PSI_RANK_MASTER && target != user) + var/option = input(user, "Связь!", "Что вы хотите сделать?") in list("Поговорить", "Привязать", "Отвязать") + if (!option) + return + if(option == "Привязать") + if(user.space >= 1) + to_chat(user, SPAN_NOTICE("Вы не можете поддерживать столь личную связь с более чем одним человеком!")) + return 0 + var/answer = alert(target, "[user] пытается связать ваши разумы воедино. Вы позволите ему сделать это?", "Слияние", "Да", "Нет") + switch(answer) + if("Да") + user.linked_soul = target + user.space = 1 + user.verbs += /mob/living/proc/ContactSoulmate + to_chat(user, SPAN_NOTICE("Вы ощущаете, как ваше сознание становится единым целым с сознанием [target]")) + return 0 + else + to_chat(user, SPAN_NOTICE("[target] отказался от вашего предложения.")) + return 0 + if(option == "Отвязать") + if(user.linked_soul == target) + user.verbs -= /mob/living/proc/ContactSoulmate + user.linked_soul = null + user.space = 0 + to_chat(user, SPAN_NOTICE("Вы рвёте ваши узы с [target]!")) + to_chat(target, SPAN_WARNING("Вы ощущаете странную потерю...")) + return 0 + else + to_chat(user, SPAN_NOTICE("У вас нет никаких уз с [target]!")) + if(option == "Поговорить") + + ///Yes. And no, i don't know how to do it better/// + + var/phrase = input(user, "Что вы хотите сказать?", "Связаться", "Ты меня слышишь?") as null|text + if(!phrase || user.incapacitated() || !do_after(user, 40 / user.psi.get_rank(PSI_CONSCIOUSNESS))) + return 0 + + var/con_rank_user = user.psi.get_rank(PSI_CONSCIOUSNESS) + to_chat(user, SPAN_NOTICE("Вы пытаетесь установить контакт с сознанием [target], дабы донести до него следующее: [phrase]")) + if(target.psi) + var/con_rank_target = target.psi.get_rank(PSI_CONSCIOUSNESS) + if(con_rank_target >= con_rank_user) + to_chat(target, SPAN_OCCULT("Вы слышите отчётливый голос [user] в своей голове, он говорит вам: [phrase]")) + if(con_rank_target > con_rank_user) + var/what = alert(target, "Вы хотите ответить этому зову?", "Обратная связь", "Да", "Нет") + switch(what) + if("Да") + var/answer = input(user, "Что вы хотите передать в ответ?", "Связаться", "...") as null|text + to_chat(user, SPAN_OCCULT("[target] отвечает вам: [answer]")) + else + return 0 + else + to_chat(target, SPAN_OCCULT("Шёпот говорит вам: [phrase]")) + else if(!target.psi) + to_chat(target, SPAN_OCCULT("Шёпот говорит вам: [phrase]")) + return 1 + + /// /// + + var/phrase = input(user, "Что вы хотите сказать?", "Связаться", "Ты меня слышишь?") as null|text + if(!phrase || user.incapacitated() || !do_after(user, 40 / user.psi.get_rank(PSI_CONSCIOUSNESS))) + return FALSE + + var/con_rank_user = user.psi.get_rank(PSI_CONSCIOUSNESS) + to_chat(user, SPAN_NOTICE("Вы пытаетесь установить контакт с сознанием [target], дабы донести до него следующее: [phrase]")) + if(target.psi) + var/con_rank_target = target.psi.get_rank(PSI_CONSCIOUSNESS) + if(con_rank_target >= con_rank_user) + to_chat(target, SPAN_OCCULT("Вы слышите отчётливый голос [user] в своей голове, он говорит вам: [phrase]")) + if(con_rank_target > con_rank_user) + var/option = alert(target, "Вы хотите ответить этому зову?", "Обратная связь", "Да", "Нет") + switch(option) + if("Да") + var/answer = input(target, "Что вы хотите передать в ответ?", "Связаться", "...") as null|text + to_chat(user, SPAN_OCCULT("[target] отвечает вам: [answer]")) + else + return + else + to_chat(target, SPAN_OCCULT("Шёпот говорит вам: [phrase]")) + else if(!target.psi) + to_chat(target, SPAN_OCCULT("Шёпот говорит вам: [phrase]")) + return TRUE + + +/singleton/psionic_power/consciousness/mindread + name = "Read Mind" + cost = 6 + cooldown = 80 + use_ranged = TRUE + use_melee = TRUE + min_rank = PSI_RANK_APPRENTICE + use_description = "Выберите голову на зелёном интенте и затем нажмите по цели находясь на любом расстоянии, чтобы попытаться прочитать его мысли." + +/singleton/psionic_power/consciousness/mindread/invoke(mob/living/user, mob/living/target) + if(user.zone_sel.selecting != BP_HEAD || user.a_intent != I_HELP || target == user) + return FALSE + . = ..() + if(.) + + var/distance = get_dist(get_turf(user), get_turf(target)) + if(distance > user.psi.get_rank(PSI_CONSCIOUSNESS)) + to_chat(user, SPAN_WARNING("Ты не можешь сконцентрироватся настолько далеко.")) + return FALSE + + if(target.stat == DEAD || (target.status_flags & FAKEDEATH) || !target.client) + to_chat(user, SPAN_WARNING("[target] не в состоянии ответить вам.")) + return FALSE + + + to_chat(user, SPAN_NOTICE("Ты концентрируешься на сознании [target]")) + if(!do_after(user, 40 / user.psi.get_rank(PSI_CONSCIOUSNESS), do_flags = DO_USER_UNIQUE_ACT)) + return FALSE + + var/question = input(user, "Что вы хотите сказать?", "Чтение мыслей", "Идеи?") as null|text + if(!question || user.incapacitated()) + return FALSE + + var/con_rank_user = user.psi.get_rank(PSI_CONSCIOUSNESS) + var/started_mindread = world.time + to_chat(user, SPAN_NOTICE("Вы погружаетесь в глубины сознания [target], выискивая ответ на вопрос: [question]")) + var/option = alert(target, "Кто-то пытается проникнуть в ваше сознание! Вы позволите этому случиться?", "Выбирай!", "Да", "Нет") + if (!option) + if(target.psi) + var/con_rank_target = target.psi.get_rank(PSI_CONSCIOUSNESS) + if(con_rank_target > con_rank_user) + to_chat(user, SPAN_NOTICE("[target] без проблем блокирует ваши попытки узнать что-либо!")) + to_chat(target, SPAN_NOTICE("Вы защитили свой разум от вторжения")) + return + else + if (target.getBrainLoss() < 25) + target.adjustBrainLoss(25) + to_chat(user, SPAN_NOTICE("[target] удаётся предотвратить ваше проникновение, но часть его мозга была повреждена в процессе")) + to_chat(target, SPAN_NOTICE("Вам удаётся защитить свои воспоминания. Ваша голова просто раскалывается.")) + return + else if(!target.psi) + if (target.getBrainLoss() < 25) + target.adjustBrainLoss(25) + to_chat(user, SPAN_NOTICE("[target] удаётся предотвратить ваше проникновение, но часть его мозга была повреждена в процессе!")) + to_chat(target, SPAN_NOTICE("Вам удаётся защитить свои воспоминания. Ваша голова просто раскалывается.")) + return + if(option == "Да") + to_chat(target, SPAN_NOTICE("Что-то пытается получить ответ на вопрос: [question]")) + if(option == "Нет") + if(target.psi) + var/con_rank_target = target.psi.get_rank(PSI_CONSCIOUSNESS) + if(con_rank_target > con_rank_user) + to_chat(user, SPAN_NOTICE("[target] без проблем блокирует ваши попытки узнать что-либо!")) + to_chat(target, SPAN_NOTICE("Вы защитили свой разум от вторжения!")) + return + else + if (target.getBrainLoss() < 25) + target.adjustBrainLoss(25) + to_chat(user, SPAN_NOTICE("[target] удаётся предотвратить ваше проникновение, но часть его мозга была повреждена в процессе!")) + to_chat(target, SPAN_NOTICE("Вам удаётся защитить свои воспоминания. Ваша голова просто раскалывается.")) + return + else if(!target.psi) + if (target.getBrainLoss() < 25) + target.adjustBrainLoss(25) + to_chat(user, SPAN_NOTICE("[target] удаётся предотвратить ваше проникновение, но часть его мозга была повреждена в процессе!")) + to_chat(target, SPAN_NOTICE("Вам удаётся защитить свои воспоминания. Ваша голова просто раскалывается.")) + return + + + var/answer = input(target, question, "Чтение мыслей") as null|text + if(!answer || world.time > started_mindread + 60 SECONDS || user.stat != CONSCIOUS || target.stat == DEAD) + to_chat(user, SPAN_NOTICE("Вам не удалось добиться чего-либо полезного от [target].")) + else + to_chat(user, SPAN_NOTICE("В разуме [target], вы находите: [answer]")) + msg_admin_attack("[key_name(user)] использует чтение мыслей на [key_name(target)] с вопросом \"[question]\" и [answer?"следующим ответом \"[answer]\".":"не получил никакого ответа."]") + return TRUE + +/singleton/psionic_power/consciousness/focus + name = "Focus" + cost = 10 + cooldown = 80 + use_grab = TRUE + min_rank = PSI_RANK_APPRENTICE + use_description = "Схватите цель, затем выберите рот на зелёном интенте и нажмите по ней захватом ещё раз, дабы частично очистить её сознание от возможного урона." + +/singleton/psionic_power/consciousness/focus/invoke(mob/living/user, mob/living/target) + if(user.zone_sel.selecting != BP_MOUTH || user.a_intent != I_HELP) + return FALSE + . = ..() + if(.) + user.visible_message(SPAN_WARNING("[user] целует [target] в лоб")) + to_chat(user, SPAN_NOTICE("Вы проверяете разум [target] на наличие повреждений...")) + to_chat(target, SPAN_WARNING("Вы ощущаете, как ваш разум очищается, становясь яснее.")) + if(!do_after(user, (target.stat == CONSCIOUS ? 50 : 25), target)) + user.psi.backblast(rand(5,10)) + return TRUE + to_chat(user, SPAN_WARNING("Вы полностью очистили сознание [target] от негатива.")) + to_chat(target, SPAN_WARNING("Вы ощущаете полную ясность мыслей. Словно стали новым человеком.")) + + var/coercion_rank = user.psi.get_rank(PSI_COERCION) + if(coercion_rank > PSI_RANK_OPERANT) + target.AdjustParalysis(-1) + target.drowsyness = 0 + if(iscarbon(target)) + var/mob/living/carbon/M = target + M.adjust_hallucination(-30) + return TRUE + +/singleton/psionic_power/consciousness/assay + name = "Assay" + cost = 15 + cooldown = 100 + use_grab = TRUE + min_rank = PSI_RANK_APPRENTICE + use_description = "Схватите цель, затем выберите голову и зелёном интент. После этого, нажмите по цели захватом, чтобы погрузится в глубины её разума и отыскать там скрытый потенциал." + +/singleton/psionic_power/consciousness/assay/invoke(mob/living/user, mob/living/target) + if(user.zone_sel.selecting != BP_HEAD || user.a_intent != I_HELP) + return FALSE + . = ..() + if(.) + user.visible_message(SPAN_WARNING("[user] обхватывает голову [target] обеими руками...")) + to_chat(user, SPAN_NOTICE("Вы погружаетесь в глубины сознания [target]...")) + to_chat(target, SPAN_WARNING("Вы ощущаете, как [user] копается в вашем подсознании, что-то выискивая.")) + if(!do_after(user, (target.stat == CONSCIOUS ? 50 : 25), target)) + user.psi.backblast(rand(5,10)) + return TRUE + to_chat(user, SPAN_NOTICE("Вы покидаете разум [target], получив желаемое.")) + to_chat(target, SPAN_DANGER("[user] наконец покидает ваше сознание, узнав желаемое.")) + target.show_psi_assay(user) + return TRUE + +/singleton/psionic_power/consciousness/absorb + name = "Absorption" + cost = 10 + cooldown = 40 + use_ranged = TRUE + use_melee = TRUE + min_rank = PSI_RANK_APPRENTICE + use_description = "Выберите верхнюю часть тела на зелёном интенте, и затем нажмите по цели с любого расстояния, чтобы попытаться поглотить часть псионической силы жертвы." + +/singleton/psionic_power/consciousness/absorb/invoke(mob/living/user, mob/living/target) + var/con_rank_user = user.psi.get_rank(PSI_CONSCIOUSNESS) + if(user.zone_sel.selecting != BP_CHEST || user.a_intent != I_HELP) + return FALSE + . = ..() + if(.) + if(target == user) + to_chat(user, "Вы не можете применить эту способность на себе!") + return 0 + if(target.psi) + var/con_rank_target = target.psi.get_rank(PSI_CONSCIOUSNESS) + if(con_rank_user > con_rank_target) + sound_to(user, 'sound/effects/psi/power_fail.ogg') + if(prob(30)) + to_chat(user, SPAN_DANGER("Вы попытались проникнуть в разум [target], но тот ускользнул из под вашего воздействия.")) + to_chat(target, SPAN_WARNING("Вы рефлекторно избежали губительного воздействия [user] на ваш разум.")) + return 0 + to_chat(user, SPAN_NOTICE("Вы с разбили защиту [target], забрав часть его сил себе.")) + to_chat(target, SPAN_DANGER("Вы ощущаете сильную головную боль, пока [user] пристально сверлит вас взглядом.")) + target.adjustBrainLoss(20) + user.psi.stamina = min(user.psi.max_stamina, user.psi.stamina + rand(25,30)) + target.psi.spend_power(rand(15,25)) + if(con_rank_user == con_rank_target) + sound_to(user, 'sound/effects/psi/power_fail.ogg') + if(prob(50)) + to_chat(user, SPAN_WARNING("Вы попытались проникнуть в разум [target], но в ходе битвы сами получаете значительный урон!")) + to_chat(target, SPAN_DANGER("Вы сопротивлялись [user] повлиять на ваш разум, но в конечном счёте всё равно проиграли.")) + user.psi.stamina = min(user.psi.max_stamina, user.psi.stamina + rand(10,20)) + target.psi.spend_power(rand(10,20)) + user.adjustBrainLoss(20) + target.adjustBrainLoss(20) + user.emote("scream") + target.emote("scream") + return 0 + to_chat(user, SPAN_WARNING("Вы с лёгкостью разбили защиту [target], забрав часть его сил себе.")) + to_chat(target, SPAN_DANGER("Вы ощущаете сильную головную боль, пока [user] пристально сверлит вас взглядом.")) + target.adjustBrainLoss(20) + user.psi.stamina = min(user.psi.max_stamina, user.psi.stamina + rand(25,30)) + target.psi.spend_power(rand(15,25)) + if(con_rank_user < con_rank_target) + sound_to(user, 'sound/effects/psi/power_fail.ogg') + if(prob(30)) + to_chat(user, SPAN_WARNING("Вам удалось пробиться через псионическую завесу [target]!")) + to_chat(target, SPAN_DANGER("[user] пробился в ваш разум чистой, грубой силой, нанеся в процессе значительный урон.")) + target.adjustBrainLoss(20) + user.psi.stamina = min(user.psi.max_stamina, user.psi.stamina + rand(30,45)) + target.psi.spend_power(35) + return 0 + to_chat(user, SPAN_DANGER("Вы пытаетесь пробиться через барьер [target], но встречаете серьёзное сопротивление!")) + to_chat(target, SPAN_NOTICE("[user] попытался пробиться в ваше сознание.")) + user.emote("scream") + user.adjustBrainLoss(30) + user.psi.spend_power(50) + else + to_chat(user, SPAN_NOTICE("У [target] нет пробужденного псионического потенциала.")) + return 0 + +/singleton/psionic_power/consciousness/invis + name = "Invisibility" + cost = 50 + cooldown = 250 + use_ranged = TRUE + use_melee = TRUE + min_rank = PSI_RANK_OPERANT + use_description = "Выберите глаза на зелёном интенте, и затем нажмите по себе или другому человеку(начиная с мастера), чтобы временно сделать его или себя невидимым для остальных." + +/mob/living/proc/run_timer_invisibility() + var/invis_timer = 10 + set waitfor = 0 + var/T = invis_timer + while(T > 0) + sleep(1 SECOND) + T-- + src.visible_message(SPAN_WARNING("[src] внезапно материализуется из воздуха!")) + animate(src, alpha = 255, time = 3 SECONDS) + +/singleton/psionic_power/consciousness/invis/invoke(mob/living/user, mob/living/target) + var/con_rank_user = user.psi.get_rank(PSI_CONSCIOUSNESS) + if(user.zone_sel.selecting != BP_EYES || user.a_intent != I_HELP) + return FALSE + + if (!istype(target)) + to_chat(user, SPAN_WARNING("Вы не можете сделать [target] невидимым.")) + return FALSE + + if(istype(target, /mob/living/carbon) && target != user && con_rank_user >= PSI_RANK_MASTER) + if(do_after(user, 30)) + user.visible_message(SPAN_WARNING("[user] касается [target] и тот исчезает на глазах!")) + animate(target, alpha = 0, time = 3 SECONDS) + target.run_timer_invisibility() + return TRUE + + if(target == user) + user.visible_message(SPAN_WARNING("[user] исчезает у всех на глазах!")) + animate(target, alpha = 0, time = 3 SECONDS) + target.run_timer_invisibility() + return TRUE + +/singleton/psionic_power/consciousness/curse + name = "Hallucinations" + cost = 20 + cooldown = 50 + use_grab = TRUE + use_melee = TRUE + min_rank = PSI_RANK_OPERANT + use_description = "Схватите цель, затем выберите верхнюю часть тела на зелёном интент. После этого, нажмите по цели захватом, чтобы погрузить её в мир галлюцинаций." + +/singleton/psionic_power/consciousness/curse/invoke(mob/living/user, mob/living/carbon/target) + var/con_rank_user = user.psi.get_rank(PSI_CONSCIOUSNESS) + if(user.zone_sel.selecting != BP_CHEST || user.a_intent != I_HELP) + return FALSE + if(target == user) + return FALSE + . = ..() + if(.) + new /obj/temporary(get_turf(target),8, 'icons/effects/effects.dmi', "eye_opening") + playsound(target.loc, 'sound/hallucinations/far_noise.ogg', 15, 1) + target.hallucination(rand(10,20) * con_rank_user, 100) + +/singleton/psionic_power/consciousness/swap + name = "Shadow Swap" + cost = 30 + cooldown = 100 + use_ranged = TRUE + min_rank = PSI_RANK_MASTER + use_description = "Выберите пятки или ноги на зелёном интенте. Затем, нажмите по цели на дистанции, чтобы незаметно обменяться с ней местами." + +/singleton/psionic_power/consciousness/swap/invoke(mob/living/user, mob/living/carbon/human/target) + var/cn_rank_user = user.psi.get_rank(PSI_CONSCIOUSNESS) + + if(!istype(target)) + return FALSE + + if(user.a_intent != I_HELP) + return FALSE + + if(!(user.zone_sel.selecting in list(BP_L_LEG, BP_R_LEG, BP_L_FOOT, BP_R_FOOT))) + return FALSE + + . = ..() + if(.) + var/turf/target_turf = get_turf(target) + var/turf/user_turf = get_turf(user) + + var/list/mobs = GLOB.alive_mobs + GLOB.dead_mobs + for(var/mob/living/M in mobs) + if(M == user) + continue + if(get_dist(user, M) > cn_rank_user) + continue + M.eye_blind = max(M.eye_blind,cn_rank_user) + target.forceMove(user_turf) + user.forceMove(target_turf) + + return TRUE + +//This differs from how TG does it, they have a dedicated turf type for open turf, we have to check the density. Thanks Bay, always be special. +///Returns a list with all the adjacent open turfs. Clears the list of nulls in the end. +/proc/get_adjacent_open_turfs(atom/center) + var/list/hand_back = list() + // Inlined get_open_turf_in_dir, just to be fast + var/turf/new_turf = get_step(center, NORTH) + if(istype(new_turf) && !new_turf.density) + hand_back += new_turf + new_turf = get_step(center, SOUTH) + if(istype(new_turf) && !new_turf.density) + hand_back += new_turf + new_turf = get_step(center, EAST) + if(istype(new_turf) && !new_turf.density) + hand_back += new_turf + new_turf = get_step(center, WEST) + if(istype(new_turf) && !new_turf.density) + hand_back += new_turf + return hand_back + +/singleton/psionic_power/consciousness/copies + name = "Non-Existing Copies" + cost = 50 + cooldown = 100 + use_melee = TRUE + min_rank = PSI_RANK_OPERANT + use_description = "Выберите рот на синем интенте, и затем нажмите по себе, чтобы создать сразу несколько копий самого себя." + var/amount = 1 + +/singleton/psionic_power/consciousness/copies/invoke(mob/living/user, mob/living/carbon/human/target) + var/con_rank_user = user.psi.get_rank(PSI_CONSCIOUSNESS) + switch(con_rank_user) + if(PSI_RANK_OPERANT) + amount = 3 + if(PSI_RANK_MASTER) + amount = 4 + if(PSI_RANK_GRANDMASTER) + amount = 6 + + if(user.zone_sel.selecting != BP_MOUTH) + return FALSE + + if(user.a_intent != I_DISARM) + return FALSE + + if(target != user) + return FALSE + + . = ..() + if(.) + if(do_after(user, 10)) + to_chat(user, SPAN_WARNING("Ты разделяешь своё подсознание на [amount] копий")) + for(var/i = 1 to amount) + var/mob/living/simple_animal/hostile/mirror_shade/MS = new(pick(get_adjacent_open_turfs(user)), user) + MS.CopyOverlays(user, TRUE) + MS.icon = null + return TRUE + +/obj/item/natural_weapon/punch/holo + damtype = DAMAGE_PAIN + +/mob/living/simple_animal/hostile/mirror_shade + + turns_per_move = 2 + response_help = "pokes" + response_disarm = "shoves" + response_harm = "hits" + movement_cooldown = 0 + maxHealth = 20 + health = 20 + harm_intent_damage = 5 + natural_weapon = /obj/item/natural_weapon/punch/holo + a_intent = I_HURT + status_flags = CANPUSH + + var/mob/living/carbon/human/owner + +/mob/living/simple_animal/hostile/mirror_shade/Initialize(mapload, mob/set_owner) + . = ..() + if(set_owner) + owner = set_owner + friends += owner + QDEL_IN(src, 30 SECONDS) + +/mob/living/simple_animal/hostile/mirror_shade/examine(mob/user) + if(!QDELETED(owner)) + /// Technically suspicious, but these have 30 seconds of lifetime so it's probably fine. + return owner.examine(user) + return ..() + +/mob/living/simple_animal/hostile/mirror_shade/Destroy() + owner = null + visible_message(SPAN_WARNING("[src] пропадает!")) + return ..() diff --git a/mods/psionics/code/faculties/energistics.dm b/mods/psionics/code/faculties/energistics.dm new file mode 100644 index 0000000000000..35f1ea828ef50 --- /dev/null +++ b/mods/psionics/code/faculties/energistics.dm @@ -0,0 +1,391 @@ +/singleton/psionic_faculty/energistics + id = PSI_ENERGISTICS + name = "Energistics" + associated_intent = I_HURT + armour_types = list("energy", "melee") + +/singleton/psionic_power/energistics + faculty = PSI_ENERGISTICS + abstract_type = /singleton/psionic_power/energistics + +/singleton/psionic_power/energistics/zorch + name = "Zorch" + cost = 20 + cooldown = 20 + use_ranged = TRUE + min_rank = PSI_RANK_APPRENTICE + use_description = "Выберите красный интент и верхнюю часть тела, чтобы по нажатию запустить в цель луч концентрированной псионической энергии." + +/singleton/psionic_power/energistics/zorch/invoke(mob/living/user, mob/living/target) + if(user.zone_sel.selecting != BP_CHEST) + return FALSE + . = ..() + if(.) + user.visible_message(SPAN_DANGER("Глаза [user] загораются ярким светом!")) + + var/user_rank = user.psi.get_rank(faculty) + var/meta_rank = user.psi.get_rank(PSI_METAKINESIS) + var/obj/item/projectile/pew + var/pew_sound + + switch(user_rank) + if(PSI_RANK_GRANDMASTER) + if(user.a_intent == I_HELP) + if(meta_rank >= PSI_RANK_MASTER) + pew = new /obj/item/projectile/beam/stun/shock/heavy(get_turf(user)) + pew.name = "gigawatt mental beam" + pew_sound = 'sound/weapons/taser2.ogg' + else + pew = new /obj/item/projectile/beam/stun(get_turf(user)) + pew.name = "mental beam" + pew.color = "#3ca7b1" + pew_sound = 'sound/weapons/taser2.ogg' + if(user.a_intent == I_HURT) + pew = new /obj/item/projectile/beam/heavylaser(get_turf(user)) + pew.name = "gigawatt mental laser" + pew.color = "#3ca7b1" + pew_sound = 'sound/weapons/pulse.ogg' + if(PSI_RANK_MASTER) + if(user.a_intent == I_HELP) + if(meta_rank == PSI_RANK_OPERANT) + pew = new /obj/item/projectile/beam/stun/shock(get_turf(user)) + pew.name = "megawatt mental beam" + pew_sound = 'sound/weapons/taser2.ogg' + else + pew = new /obj/item/projectile/beam/stun(get_turf(user)) + pew.name = "mental beam" + pew.color = "#3ca7b1" + pew_sound = 'sound/weapons/taser2.ogg' + if(user.a_intent == I_HURT) + pew = new /obj/item/projectile/beam/megabot(get_turf(user)) + pew.name = "megawatt mental laser" + pew.color = "#3ca7b1" + pew_sound = 'sound/weapons/Laser.ogg' + if(PSI_RANK_OPERANT) + if(user.a_intent == I_HELP) + pew = new /obj/item/projectile/beam/stun(get_turf(user)) + pew.name = "mental beam" + pew.color = "#3ca7b1" + pew_sound = 'sound/weapons/taser2.ogg' + if(user.a_intent == I_HURT) + pew = new /obj/item/projectile/beam/midlaser(get_turf(user)) + pew.name = "mental laser" + pew.color = "#3ca7b1" + pew_sound = 'sound/weapons/scan.ogg' + if(PSI_RANK_APPRENTICE) + pew = new /obj/item/projectile/beam/stun/smalllaser(get_turf(user)) + pew.name = "mental beam" + pew.color = "#3ca7b1" + pew_sound = 'sound/weapons/taser2.ogg' + + if(istype(pew)) + playsound(pew.loc, pew_sound, 25, 1) + pew.original = target + pew.current = target + pew.starting = get_turf(user) + pew.shot_from = user + pew.launch(target, user.zone_sel.selecting, (target.x-user.x), (target.y-user.y)) + return TRUE + +/singleton/psionic_power/energistics/disrupt + name = "Disrupt" + cost = 10 + cooldown = 60 + use_melee = TRUE + min_rank = PSI_RANK_OPERANT + use_description = "Выберите глаза на красном интенте и нажмите на любой объект, чтобы создать мощный электромагнитный импульс, направленный в него." + +/singleton/psionic_power/energistics/disrupt/invoke(mob/living/user, mob/living/target) + var/en_rank = user.psi.get_rank(PSI_ENERGISTICS) + if(user.zone_sel.selecting != BP_EYES) + return FALSE + if(user.a_intent != I_HURT) + return FALSE + if(istype(target, /turf)) + return FALSE + . = ..() + if(.) + if(en_rank == PSI_RANK_GRANDMASTER) + var/option = input(user, "Выбирай!", "Насколько большой должен быть импульс?") in list("Сконцентрированный", "Бесконтрольный") + if (!option) + return + if(option == "Concentrated") + new /obj/temporary(get_turf(target),3, 'icons/effects/effects.dmi', "blue_electricity_constant") + target.visible_message(SPAN_DANGER("[user] переполняет [target] энергией, провоцируя внезапное высвобождение ЭМИ-импульса!")) + empulse(target, 0, 1) + if(option == "Uncontrolled") + new /obj/temporary(get_turf(target),3, 'icons/effects/effects.dmi', "blue_electricity_constant") + target.visible_message(SPAN_DANGER("[user] взмахивает рукой, создавая мощную ЭМИ-волну!")) + empulse(target, 6, 8) + if(en_rank <= PSI_RANK_OPERANT) + new /obj/temporary(get_turf(target),3, 'icons/effects/effects.dmi', "blue_electricity_constant") + target.visible_message(SPAN_DANGER("[user] взмахивает рукой, создавая мощный ЭМИ-импульс!")) + empulse(target, rand(2,4) - en_rank, rand(3,6) - en_rank) + if(en_rank == PSI_RANK_MASTER) + new /obj/temporary(get_turf(target),3, 'icons/effects/effects.dmi', "blue_electricity_constant") + target.visible_message(SPAN_DANGER("[user] взмахивает рукой, создавая мощный ЭМИ-импульс!")) + empulse(target, 1, 2) + return TRUE + +/singleton/psionic_power/energistics/spit + name = "Ballistic Projectile" + cost = 20 + cooldown = 45 + use_ranged = TRUE + use_melee = TRUE + min_rank = PSI_RANK_APPRENTICE + use_description = "Выберите голову на красном интенте и нажмите по чему угодно, чтобы запустить пулю." + + var/psi_shot = "Standard" + +/singleton/psionic_power/energistics/spit/invoke(mob/living/user, mob/living/target) + + var/list/options = list( + "Armor Piercing" = image('mods/psionics/icons/psi.dmi', "AP"), + "Explosive" = image('mods/psionics/icons/psi.dmi', "EXP"), + "Piercing Charges" = image('mods/psionics/icons/psi.dmi', "EXPAP"), + "Standard" = image('mods/psionics/icons/psi.dmi', "DEF") + ) + + if(user.zone_sel.selecting != BP_HEAD) + return FALSE + . = ..() + if(.) + + var/user_rank = user.psi.get_rank(faculty) + var/obj/item/projectile/pew + var/pew_sound + + if(target == user && user.a_intent == I_HELP) + var/chosen_option = show_radial_menu(user, user, options, radius = 20, require_near = TRUE) + if (!chosen_option) + return + psi_shot = chosen_option + to_chat(user, SPAN_WARNING("Теперь, ты будешь выпускать снаряды '[chosen_option]' при использовании данной способности.")) + return TRUE + + if(user.a_intent != I_HURT) + return FALSE + + if(psi_shot == "Standard") + user.visible_message(SPAN_DANGER("[user] изображает пальцами пистолет, делая выстрел!")) + if(user_rank < PSI_RANK_MASTER) + pew = new /obj/item/projectile/psi(get_turf(user)) + pew.name = "small psionic bullet" + pew_sound = 'sound/weapons/guns/ricochet4.ogg' + if(user_rank >= PSI_RANK_MASTER) + pew = new /obj/item/projectile/psi(get_turf(user)) + pew.name = "psionic bullet" + pew.damage = 40 + pew_sound = 'sound/weapons/guns/ricochet4.ogg' + + if(psi_shot == "Armor Piercing") + user.visible_message(SPAN_DANGER("[user] изображает пальцами пистолет, делая выстрел!")) + if(user_rank == PSI_RANK_APPRENTICE) + if(prob(10)) + pew = new /obj/item/projectile/psi(get_turf(user)) + pew.name = "piercing psionic bullet" + pew.color = "#a70909" + pew.armor_penetration = 80 + pew.penetrating = 5 + pew.penetration_modifier = 1.1 + pew_sound = 'sound/weapons/guns/ricochet4.ogg' + else + pew = new /obj/item/projectile/psi(get_turf(user)) + pew.name = "small psionic bullet" + pew_sound = 'sound/weapons/guns/ricochet4.ogg' + to_chat(user, SPAN_WARNING("Ты пытаешься сконцентрировать всю энергию в одном маленьком сгустке, дабы создать пробивной снаряд, но что-то мешает тебе.")) + if(user_rank == PSI_RANK_OPERANT) + pew = new /obj/item/projectile/psi(get_turf(user)) + pew.name = "piercing psionic bullet" + pew.color = "#a70909" + pew.armor_penetration = 80 + pew.penetrating = 5 + pew.penetration_modifier = 1.1 + pew_sound = 'sound/weapons/guns/ricochet4.ogg' + if(user_rank >= PSI_RANK_MASTER) + pew = new /obj/item/projectile/psi(get_turf(user)) + pew.name = "piercing psionic bullet" + pew.color = "#a70909" + pew.armor_penetration = 100 + pew.penetrating = 6 + pew.penetration_modifier = 1.1 + pew.damage = 40 + pew_sound = 'sound/weapons/guns/ricochet4.ogg' + + if(psi_shot == "Explosive") + user.visible_message(SPAN_DANGER("[user] делает резкий выпад рукой, запуская в полёт огромный сгусток энергии!")) + if(user_rank < PSI_RANK_OPERANT) + if(prob(10)) + pew = new /obj/item/projectile/psi/strong(get_turf(user)) + pew.name = "explosive psionic round" + pew.damage = 10 + pew_sound = 'sound/weapons/guns/ricochet4.ogg' + else + to_chat(user, SPAN_DANGER("Огромный ком энергии накапливается внутри тебя, готовясь вырваться наружу, но что-то идёт не так.")) + explosion(get_turf(user), 1, 2) + if(user_rank >= PSI_RANK_OPERANT) + pew = new /obj/item/projectile/psi/strong(get_turf(user)) + pew.name = "explosive psionic round" + pew_sound = 'sound/weapons/guns/ricochet4.ogg' + + if(psi_shot == "Piercing Charges") + user.visible_message(SPAN_WARNING("[user] выставляет перед собой руку и пропускает через неё сжатый сгусток энергии!")) + if(user_rank <= PSI_RANK_OPERANT) + if(prob(30)) + pew = new /obj/item/projectile/psi/strong_piercing(get_turf(user)) + pew.name = "piercing psionic round" + pew.color = "#a70909" + pew.damage = 20 + pew_sound = 'sound/weapons/guns/ricochet4.ogg' + else + pew = new /obj/item/projectile/psi(get_turf(user)) + pew.name = "piercing psionic bullet" + pew.color = "#a70909" + pew.armor_penetration = 80 + pew.penetrating = 5 + pew.penetration_modifier = 1.1 + pew_sound = 'sound/weapons/guns/ricochet4.ogg' + to_chat(user, SPAN_WARNING("Ты пытаешься сконцентрировать всю энергию в одном маленьком сгустке, дабы создать пробивной снаряд, но что-то мешает тебе...")) + explosion(get_turf(user), 2, 3) + if(user_rank == PSI_RANK_MASTER) + if(prob(70)) + pew = new /obj/item/projectile/psi/strong_piercing(get_turf(user)) + pew.name = "piercing psionic round" + pew.color = "#a70909" + pew.damage = 20 + pew_sound = 'sound/weapons/guns/ricochet4.ogg' + else + to_chat(user, SPAN_WARNING("Ты пытаешься сконцентрировать всю энергию в одном маленьком сгустке, дабы создать пробивной снаряд, но что-то мешает тебе.")) + explosion(get_turf(user), 2, 3) + if(user_rank == PSI_RANK_GRANDMASTER) + pew = new /obj/item/projectile/psi/strong_piercing(get_turf(user)) + pew.name = "piercing psionic round" + pew.color = "#a70909" + pew_sound = 'sound/weapons/guns/ricochet4.ogg' + + if(istype(pew)) + playsound(pew.loc, pew_sound, 25, 1) + pew.original = target + pew.current = target + pew.starting = get_turf(user) + pew.shot_from = user + pew.launch(target, user.zone_sel.selecting, (target.x-user.x), (target.y-user.y)) + return TRUE + +/singleton/psionic_power/energistics/storm + name = "Bullet Storm" + cost = 30 + cooldown = 120 + min_rank = PSI_RANK_OPERANT + use_melee = TRUE + use_description = "Выберите рот на красном интенте и нажмите в любое место около себя, чтобы создать рой псионических снарядов, летящих в разные стороны." + var/psi_shot = "Standart" + +/singleton/psionic_power/energistics/storm/invoke(mob/living/user, mob/living/target) + var/list/options = list( + "Explosive" = image('mods/psionics/icons/psi.dmi', "EXP"), + "Standart" = image('mods/psionics/icons/psi.dmi', "DEF") + ) + + if(user.zone_sel.selecting != BP_MOUTH) + return FALSE + . = ..() + if(.) + var/user_rank = user.psi.get_rank(faculty) + + if(target == user && user.a_intent == I_HELP && user_rank == PSI_RANK_GRANDMASTER) + var/chosen_option = show_radial_menu(user, user, options, radius = 20, require_near = TRUE) + if (!chosen_option) + return + psi_shot = chosen_option + to_chat(user, SPAN_DANGER("Теперь, ты будешь выпускать снаряды типа '[chosen_option]' при использовании данной способности.")) + return TRUE + + if(user.a_intent != I_HURT) + return FALSE + + user.visible_message(SPAN_DANGER("[user] создаёт вокруг себя рой из вращающихся пуль, запуская их в полёт!")) + + user.psi.set_cooldown(cooldown) + sleep(4) + user.psi.spend_power(cost) + var/turf/O = get_turf(src) + switch(user_rank) + if(PSI_RANK_GRANDMASTER) + if(psi_shot == "Explosive") + user.fragmentate(O, 10, 4, list(/obj/item/projectile/psi/strong = 1)) + else + user.fragmentate(O, 40, 7, list(/obj/item/projectile/psi = 1)) + if(PSI_RANK_MASTER) + user.fragmentate(O, 30, 6, list(/obj/item/projectile/psi = 1)) + if(PSI_RANK_OPERANT) + user.fragmentate(O, 20, 5, list(/obj/item/projectile/psi = 1)) + return TRUE + +/mob/proc/fragmentate(turf/T=get_turf(src), fragment_number = 30, spreading_range = 5, list/fragtypes=list(/obj/item/projectile)) + set waitfor = 0 + var/list/target_turfs = getcircle(T, spreading_range) + for(var/turf/O in target_turfs) + sleep(0) + var/fragment_type = pickweight(fragtypes) + var/obj/item/projectile/P = new fragment_type(T) + P.shot_from = src.name + P.launch(O) + + +/singleton/psionic_power/energistics/cloud + name = "Cloud" + cost = 20 + cooldown = 50 + use_melee = TRUE + min_rank = PSI_RANK_OPERANT + use_description = "Выберите грудь на зелёном интенте и нажмите по себе, чтобы создать дымовую завесу." + admin_log = FALSE + +/datum/effect/smoke_spread/paramount + smoke_type = /obj/effect/smoke/paramount + +/obj/effect/smoke/paramount + name = "foul gas" + color = "#3b3b3b" + layer = ABOVE_HUMAN_LAYER + +/obj/effect/smoke/paramount/can_affect(mob/living/carbon/M) + . = ..() + if (!.) + return + if (ishuman(M)) + var/mob/living/carbon/human/H = M + if (H.psi.get_rank(PSI_ENERGISTICS) == PSI_RANK_GRANDMASTER) // Если персонаж Грандмастер по Псионике Энергетики, то он не будет получать урон от дыма. + return FALSE + +/obj/effect/smoke/paramount/affect(mob/living/carbon/human/R) + R.apply_damage(10, DAMAGE_PSIONIC) + if (R.coughedtime != 1) + R.coughedtime = 1 + R.emote("gasp") + addtimer(new Callback(R, TYPE_PROC_REF(/mob/living/carbon, clear_coughedtime)), 2 SECONDS) + R.updatehealth() + return + +/singleton/psionic_power/energistics/cloud/invoke(mob/living/user, mob/living/target) + var/en_rank_user = user.psi.get_rank(PSI_ENERGISTICS) + + if(user.zone_sel.selecting != BP_CHEST) + return FALSE + if(target != user) + return FALSE + if(user.a_intent != I_HELP) + return FALSE + + . = ..() + + if(target == user) + var/datum/effect/smoke_spread/smoke = new /datum/effect/smoke_spread() + if(en_rank_user == PSI_RANK_GRANDMASTER) + smoke = new /datum/effect/smoke_spread/paramount() + smoke.set_up(10, 0, user.loc) + smoke.attach(user) + smoke.start() + return TRUE diff --git a/mods/psionics/code/faculties/manifestation.dm b/mods/psionics/code/faculties/manifestation.dm new file mode 100644 index 0000000000000..5563074d1a6b3 --- /dev/null +++ b/mods/psionics/code/faculties/manifestation.dm @@ -0,0 +1,193 @@ +/singleton/psionic_faculty/manifestation + id = PSI_MANIFESTATION + name = "Manifestation" + associated_intent = I_GRAB + armour_types = list("bullet", "melee") + +/singleton/psionic_power/manifestation + faculty = PSI_MANIFESTATION + use_manifest = TRUE + use_sound = null + abstract_type = /singleton/psionic_power/manifestation + +/singleton/psionic_power/manifestation/psiblade + name = "Manifest weapon" + cost = 5 + cooldown = 50 + min_rank = PSI_RANK_APPRENTICE + use_description = "Нажмите по пустой руке на красном интенте, чтобы создать оружие из чистой псионической энергии." + admin_log = FALSE + + var/list/images = list() + + var/list/items_lvl1 = list() + var/list/paths_lvl1 = list( + /obj/item/psychic_power/psiclub, + /obj/item/psychic_power/psiblade) + + var/list/items_lvl2 = list() + var/list/paths_lvl2 = list( + /obj/item/psychic_power/psiclub/master, + /obj/item/psychic_power/psiblade/master, + /obj/item/psychic_power/psiaxe, + /obj/item/psychic_power/psispear) + + var/list/items_lvl3 = list() + var/list/paths_lvl3 = list( + /obj/item/psychic_power/psiclub/master/grand, + /obj/item/psychic_power/psiblade/master/grand, + /obj/item/psychic_power/psiaxe/master, + /obj/item/psychic_power/psispear/master, + /obj/item/gun/launcher/crossbow/psibow/master) + + var/list/items_lvl4 = list() + var/list/paths_lvl4 = list( + /obj/item/psychic_power/psiclub/master/grand/paramount, + /obj/item/psychic_power/psiblade/master/grand/paramount, + /obj/item/psychic_power/psiaxe/master/grand/paramount, + /obj/item/psychic_power/psispear/master/grand/paramount, + /obj/item/gun/launcher/crossbow/psibow/master/grand/paramount) + +/singleton/psionic_power/manifestation/psiblade/invoke(mob/living/user, mob/living/target) + if((target && user != target) || user.a_intent != I_HURT) + return FALSE + + var/demi_rank = user.psi.get_rank(PSI_MANIFESTATION) + + if(user.skill_check(SKILL_WEAPONS, SKILL_TRAINED) && user.skill_check(SKILL_CONSTRUCTION, SKILL_EXPERIENCED)) + paths_lvl4 += /obj/item/gun/energy/psigun + + . = ..() + if(.) + + if(demi_rank <= PSI_RANK_APPRENTICE) + for(var/weapon1 in paths_lvl1) + var/obj/item/I = new weapon1 (src) + items_lvl1 += I + var/image/img = image(icon = I.icon, icon_state = I.item_state) + img.name = I.name + images[I] = img + + if(demi_rank == PSI_RANK_OPERANT) + for(var/weapon2 in paths_lvl2) + var/obj/item/I = new weapon2 (src) + items_lvl2 += I + var/image/img = image(icon = I.icon, icon_state = I.item_state) + img.name = I.name + images[I] = img + + if(demi_rank == PSI_RANK_MASTER) + for(var/weapon3 in paths_lvl3) + var/obj/item/I = new weapon3 (src) + items_lvl3 += I + var/image/img = image(icon = I.icon, icon_state = I.item_state) + img.name = I.name + images[I] = img + + if(demi_rank >= PSI_RANK_GRANDMASTER) + for(var/weapon4 in paths_lvl4) + var/obj/item/I = new weapon4 (src) + items_lvl4 += I + var/image/img = image(icon = I.icon, icon_state = I.item_state) + img.name = I.name + images[I] = img + + var/obj/item = show_radial_menu(user, user, images, radius = 30, require_near = TRUE) + if(item && !user.psi.suppressed) + var/item_type = item.type + . = new item_type(user) + + for(item in items_lvl1 + items_lvl2 + items_lvl3 + items_lvl4) + qdel(item) + + images.Cut() + items_lvl1.Cut() + items_lvl2.Cut() + items_lvl3.Cut() + items_lvl4.Cut() + + paths_lvl4 -= /obj/item/gun/energy/psigun + +/singleton/psionic_power/manifestation/tinker + name = "Manifest tool" + cost = 5 + cooldown = 10 + min_rank = PSI_RANK_APPRENTICE + use_description = "Нажмите по пустой руке на зелёном интенте, чтобы создать ряд полезных инструментов." + admin_log = FALSE + var/list/images = list() + + var/list/items_medical = list() + var/list/paths_medical = list(/obj/item/bonesetter/psi, + /obj/item/circular_saw/psi, + /obj/item/hemostat/psi, + /obj/item/retractor/psi, + /obj/item/scalpel/psi, + /obj/item/surgicaldrill/psi) + + var/list/items_engineering = list() + var/list/paths_engineering = list(/obj/item/crowbar/psi, + /obj/item/screwdriver/psi, + /obj/item/wirecutters/psi, + /obj/item/wrench/psi) + +/singleton/psionic_power/manifestation/tinker/invoke(mob/living/user, mob/living/target) + if((target && user != target) || user.a_intent != I_HELP) + return FALSE + + var/fire_rank = user.psi.get_rank(PSI_METAKINESIS) + var/demi_rank = user.psi.get_rank(PSI_MANIFESTATION) + + if(demi_rank >= PSI_RANK_MASTER) + paths_medical += /obj/item/clothing/gloves/latex/psi + paths_engineering += /obj/item/clothing/gloves/insulated/psi + + if(fire_rank >= PSI_RANK_LATENT && demi_rank >= PSI_RANK_OPERANT && user.skill_check(SKILL_CONSTRUCTION, SKILL_TRAINED)) + paths_engineering += /obj/item/weldingtool/experimental/psi + + if(demi_rank >= PSI_RANK_MASTER && user.skill_check(SKILL_ELECTRICAL, SKILL_BASIC) && user.skill_check(SKILL_DEVICES, SKILL_TRAINED)) + paths_engineering += /obj/item/device/multitool/psi + + . = ..() + if(.) + + var/option = alert(target, "What toolkit you need?", "Choose something!", "Medical", "Engineering") + if (!option) + return + + if(user.psi.suppressed) + return + + if(option == "Engineering") + for(var/engietool in paths_engineering) + var/obj/item/I = new engietool (src) + items_engineering += I + var/image/img = image(icon = I.icon, icon_state = I.item_state) + img.name = I.name + images[I] = img + + if(option == "Medical") + for(var/medtool in paths_medical) + var/obj/item/I = new medtool (src) + items_medical += I + var/image/img = image(icon = I.icon, icon_state = I.item_state) + img.name = I.name + images[I] = img + + var/obj/item = show_radial_menu(user, user, images, radius = 30, require_near = TRUE) + if(item && !user.psi.suppressed) + var/item_type = item.type + . = new item_type(user) + + for(item in items_medical + items_engineering) + qdel(item) + + images.Cut() + items_medical.Cut() + items_engineering.Cut() + + paths_medical -= /obj/item/clothing/gloves/latex/psi + + paths_engineering -= /obj/item/device/multitool/psi + paths_engineering -= /obj/item/weldingtool/experimental/psi + paths_engineering -= /obj/item/clothing/gloves/insulated/psi diff --git a/mods/psionics/code/faculties/metakinesis.dm b/mods/psionics/code/faculties/metakinesis.dm new file mode 100644 index 0000000000000..4ac94f6846b4f --- /dev/null +++ b/mods/psionics/code/faculties/metakinesis.dm @@ -0,0 +1,49 @@ +/singleton/psionic_faculty/metakinesis + id = PSI_METAKINESIS + name = "Metakinesis" + associated_intent = I_GRAB + armour_types = list("laser", "melee") + +/singleton/psionic_power/metakinesis + faculty = PSI_METAKINESIS + use_manifest = TRUE + abstract_type = /singleton/psionic_power/metakinesis + +/singleton/psionic_power/metakinesis/element + name = "Manifest element" + cost = 5 + cooldown = 50 + min_rank = PSI_RANK_APPRENTICE + use_description = "Нажмите по пустой руке на жёлтом интенте, чтобы воспользоваться одним из трёх стихийных элементов." + admin_log = FALSE + + var/list/images = list() + + var/list/items_elements = list() + var/list/paths_elements = list(/obj/item/psychic_power/psielectro, + /obj/item/psychic_power/psifire, + /obj/item/psychic_power/psiice) + +/singleton/psionic_power/metakinesis/element/invoke(mob/living/user, mob/living/target) + if((target && user != target) || user.a_intent != I_GRAB) + return FALSE + . = ..() + if(.) + + for(var/element in paths_elements) + var/obj/item/I = new element (src) + items_elements += I + var/image/img = image(icon = I.icon, icon_state = I.item_state) + img.name = I.name + images[I] = img + + var/obj/item = show_radial_menu(user, user, images, radius = 30, require_near = TRUE) + if(item && !user.psi.suppressed) + var/item_type = item.type + . = new item_type(user) + + for(item in items_elements) + qdel(item) + + images.Cut() + items_elements.Cut() diff --git a/mods/psionics/code/faculties/psychokinesis.dm b/mods/psionics/code/faculties/psychokinesis.dm new file mode 100644 index 0000000000000..a77be6cbd07e0 --- /dev/null +++ b/mods/psionics/code/faculties/psychokinesis.dm @@ -0,0 +1,578 @@ +/singleton/psionic_faculty/psychokinesis + id = PSI_PSYCHOKINESIS + name = "Psychokinesis" + associated_intent = I_GRAB + armour_types = list("melee", "bullet") + +/singleton/psionic_power/psychokinesis + faculty = PSI_PSYCHOKINESIS + use_sound = null + abstract_type = /singleton/psionic_power/psychokinesis + +/singleton/psionic_power/psychokinesis/telekinesis + name = "Telekinesis" + cost = 10 + cooldown = 15 + use_ranged = TRUE + use_manifest = FALSE + min_rank = PSI_RANK_APPRENTICE + use_description = "Нажмите по отдалённом объекту или существу на жёлтом интенте с выбранным телом, чтобы захватить его телекинезом." + admin_log = FALSE + use_sound = 'sound/effects/psi/power_used.ogg' + var/global/list/valid_machine_types = list( + /obj/machinery + ) + +/singleton/psionic_power/psychokinesis/telekinesis/invoke(mob/living/user, mob/living/target) + if((user.zone_sel.selecting in list(BP_L_ARM, BP_R_ARM, BP_L_HAND, BP_R_HAND, BP_HEAD))) + return FALSE + if((user.zone_sel.selecting in list(BP_L_LEG, BP_R_LEG, BP_L_FOOT, BP_R_FOOT))) + return FALSE + if(user.a_intent != I_GRAB) + return FALSE + . = ..() + if(.) + + var/distance = get_dist(user, target) + if(distance > user.psi.get_rank(PSI_PSYCHOKINESIS) + 2) + to_chat(user, "Ваших сил недостаточно, чтобы достать до этого объекта.") + return FALSE + + if(istype(target, /obj/structure)) + user.visible_message("[user] вытягивает руку вперёд, чуть сжимая пальцы.") + 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 + return machine.do_simple_ranged_interaction(user) + else 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("[user] вытягивает руку вперёд, чуть сжимая пальцы.") + return tk + + return FALSE + +/singleton/psionic_power/psychokinesis/gravigeddon + name = "Repulse" + cost = 30 + cooldown = 150 + use_ranged = TRUE + use_melee = TRUE + min_rank = PSI_RANK_OPERANT + use_description = "Выберите руки или кисти на жёлтом интенте, а затем нажмите куда угодно, чтобы разбросать людей от себя мощной волной." + +/singleton/psionic_power/psychokinesis/gravigeddon/invoke(mob/living/user, mob/living/target) + if(!(user.zone_sel.selecting in list(BP_L_ARM, BP_R_ARM, BP_L_HAND, BP_R_HAND))) + return FALSE + . = ..() + if(.) + user.visible_message(SPAN_DANGER("[user] размахивает руками, крича!")) + to_chat(user, SPAN_DANGER("Вы выпускаете мощную псионическую волну, разметая всё вокруг!")) + var/pk_rank = user.psi.get_rank(PSI_PSYCHOKINESIS) + new /obj/temporary(get_turf(user),9, 'icons/effects/effects.dmi', "summoning") + var/list/mobs = GLOB.alive_mobs + GLOB.dead_mobs + for(var/mob/living/M in mobs) + if(M == user) + continue + if(get_dist(user, M) > user.psi.get_rank(PSI_PSYCHOKINESIS)) + continue + if(prob(20) && iscarbon(M)) + var/mob/living/carbon/C = M + if(C.can_feel_pain()) + C.emote("scream") + if(!M.anchored && !M.buckled) + to_chat(M, SPAN_DANGER("Грубая сила ударяет в твоё тело, отправляя тебя в свободный полёт!")) + new /obj/temporary(get_turf(M),4, 'icons/effects/effects.dmi', "smash") + M.throw_at(get_edge_target_turf(M, get_dir(user, M)), pk_rank*2, pk_rank*2, user) + return TRUE + +///WHAT CAN'T BE PUCNED/// + +/obj + var/can_be_telepunched = 1 + +/obj/structure/shuttle + can_be_telepunched = 0 + +/obj/structure/sign + can_be_telepunched = 0 + +/obj/structure/railing + can_be_telepunched = 0 + +/obj/structure/pit + can_be_telepunched = 0 + +/obj/structure/stairs + can_be_telepunched = 0 + +/obj/structure/net + can_be_telepunched = 0 + +/obj/structure/net + can_be_telepunched = 0 + +/obj/structure/m_tray + can_be_telepunched = 0 + +/obj/structure/lattice + can_be_telepunched = 0 + +/obj/structure/ladder + can_be_telepunched = 0 + +/obj/structure/hygiene/drain + can_be_telepunched = 0 + +/obj/structure/holosign + can_be_telepunched = 0 + +/obj/structure/holonet + can_be_telepunched = 0 + +/obj/structure/holohoop + can_be_telepunched = 0 + +/obj/structure/handrail + can_be_telepunched = 0 + +/obj/structure/fuel_port + can_be_telepunched = 0 + +/obj/structure/fountain + can_be_telepunched = 0 + +/obj/structure/flora + can_be_telepunched = 0 +/obj/structure/flora/pottedplant + can_be_telepunched = 1 + +/obj/structure/fireaxecabinet + can_be_telepunched = 0 + +/obj/structure/catwalk + can_be_telepunched = 0 + +/obj/structure/extinguisher_cabinet + can_be_telepunched = 0 + +/obj/structure/disposalpipe + can_be_telepunched = 0 + +/obj/structure/chorus + can_be_telepunched = 0 + +/obj/structure/cable + can_be_telepunched = 0 + +/// + +/obj/machinery/wish_granter + can_be_telepunched = 0 + +/obj/machinery/bluespacedrive + can_be_telepunched = 0 + +/obj/machinery/shield_diffuser + can_be_telepunched = 0 + +/obj/machinery/self_destruct + can_be_telepunched = 0 + +/obj/machinery/requests_console + can_be_telepunched = 0 + +/obj/machinery/readybutton + can_be_telepunched = 0 + +/obj/machinery/power + can_be_telepunched = 0 + +/obj/machinery/pager + can_be_telepunched = 0 + +/obj/machinery/newscaster + can_be_telepunched = 0 + +/obj/machinery/navbeacon + can_be_telepunched = 0 + +/obj/machinery/meter + can_be_telepunched = 0 + +/obj/machinery/mech_recharger + can_be_telepunched = 0 + +/obj/machinery/mass_driver + can_be_telepunched = 0 + +/obj/machinery/magnetic_accelerator + can_be_telepunched = 0 + +/obj/machinery/light_switch + can_be_telepunched = 0 + +/obj/machinery/light_construct + can_be_telepunched = 0 + +/obj/machinery/light + can_be_telepunched = 0 + +/obj/machinery/keycard_auth + can_be_telepunched = 0 + +/obj/machinery/igniter + can_be_telepunched = 0 + +/obj/machinery/holosign + can_be_telepunched = 0 + +/obj/machinery/hologram + can_be_telepunched = 0 + +/obj/machinery/firealarm + can_be_telepunched = 0 +/obj/machinery/alarm + can_be_telepunched = 0 + +/obj/machinery/door_timer + can_be_telepunched = 0 + +/obj/machinery/disposal_switch + can_be_telepunched = 0 + +/obj/machinery/conveyor_switch + can_be_telepunched = 0 + +/obj/machinery/containment_field + can_be_telepunched = 0 + +/obj/machinery/clamp + can_be_telepunched = 0 + +/obj/machinery/button + can_be_telepunched = 0 + +/obj/machinery/body_scanconsole + can_be_telepunched = 0 +/obj/machinery/body_scan_display + can_be_telepunched = 0 + +/obj/machinery/atmospherics + can_be_telepunched = 0 + +/obj/machinery/atm + can_be_telepunched = 0 + +/obj/machinery/airlock_sensor + can_be_telepunched = 0 +/obj/machinery/air_sensor + can_be_telepunched = 0 +/obj/machinery/access_button + can_be_telepunched = 0 + +/obj/machinery/ai_status_display + can_be_telepunched = 0 + +/// /// + +/singleton/psionic_power/psychokinesis/tele_punch + name = "Telekinetic Punch" + cost = 40 + cooldown = 50 + use_ranged = TRUE + use_melee = TRUE + min_rank = PSI_RANK_APPRENTICE + use_description = "Выберите голову на красном интенте, а затем нажмите по цели, чтобы совершить усиленный телекинетический удар." + +/singleton/psionic_power/psychokinesis/tele_punch/invoke(mob/living/carbon/user, mob/living/target) + + var/pk_rank_user = user.psi.get_rank(PSI_PSYCHOKINESIS) + + if(pk_rank_user < PSI_RANK_GRANDMASTER && get_dist(user, target) > 1) + return FALSE + + var/obj/item/organ/external/E = user.organs_by_name[BP_L_HAND] + if(!E || E.is_stump()) + return FALSE + + E = user.organs_by_name[BP_R_HAND] + if(!E || E.is_stump()) + return FALSE + + if(user.zone_sel.selecting != BP_HEAD) + return FALSE + if(user.a_intent != I_HURT) + return FALSE + +//OBJ RELATED CHECKS START// + + if(istype(target, /obj/structure) || istype(target, /obj/machinery)) + var/obj/OBJ = target + if(!OBJ.can_be_telepunched) + return FALSE + +//OBJ RELATED CHECKS END// + + . = ..() + if(.) + if(pk_rank_user <= PSI_RANK_OPERANT) + user.visible_message(SPAN_DANGER("[user] заносит руку назад, совершая резкий удар, буквально разрезающий воздух!")) + + if(istype(target, /obj/structure) || istype(target, /obj/machinery)) + user.visible_message("[user] толкает [target] вперёд!") + var/obj/O = target + if(O.anchored == TRUE) + new /obj/temporary(get_turf(target),3, 'icons/effects/effects.dmi', "smash") + O.throw_at(get_edge_target_turf(O, get_dir(user, O)), 1, 2, user) + return TRUE + new /obj/temporary(get_turf(target),3, 'icons/effects/effects.dmi', "smash") + O.throw_at(get_edge_target_turf(O, get_dir(user, O)), 4, 2, user) + return TRUE + +//ENEMY PSI CHECK START + + if(target.psi) + var/pk_rank_target = target.psi.get_rank(PSI_PSYCHOKINESIS) + if(pk_rank_target >= pk_rank_user && !target.psi.suppressed) + if(prob(20)) + to_chat(target, SPAN_NOTICE("Каким-то чудом, [user] пробивается через ваше силовое поле, нанося сокрушительный урон!")) + target.visible_message(SPAN_DANGER("[target] ловит лицом кулак, улетая назад!")) + if(!user.skill_check(SKILL_HAULING, SKILL_EXPERIENCED)) + + user.apply_damage(rand(10,20),DAMAGE_BRUTE, user.hand ? BP_L_ARM : BP_R_ARM) + to_chat(user, SPAN_WARNING("Ваше неподготовленное тело не выдерживает отдачи от удара, и кожа на вашей руке стирается в кровь!")) + + for(var/zone in list(BP_CHEST, BP_GROIN, BP_HEAD)) + target.apply_damage(rand(10,20),DAMAGE_BRUTE,def_zone=zone) + new /obj/temporary(get_turf(target),3, 'icons/effects/effects.dmi', "smash") + target.throw_at(get_edge_target_turf(target, get_dir(user, target)), 1, 2, user) + return TRUE + + else + to_chat(target, SPAN_NOTICE("Ваше силовое поле успешно сдержало удар, пускай на это и ушло приличное количество концентрации.")) + target.psi.spend_power(10) + for(var/zone in list(BP_CHEST, BP_GROIN, BP_HEAD)) + user.apply_damage(rand(10,20),DAMAGE_BRUTE,def_zone=zone) + new /obj/temporary(get_turf(target),3, 'icons/effects/effects.dmi', "smash") + user.throw_at(get_edge_target_turf(user, get_dir(target, user)), 1, 2, target) + user.visible_message(SPAN_DANGER("Мощное силовое поле [target] отбрасывает [user] назад, создавая мощную обратную волну!")) + return TRUE + +//ENEMY PSI CHECK END + + if(!user.skill_check(SKILL_HAULING, SKILL_EXPERIENCED)) + + user.apply_damage(rand(10,20),DAMAGE_BRUTE, user.hand ? BP_L_ARM : BP_R_ARM) + to_chat(user, SPAN_WARNING("Ваше неподготовленное тело не выдерживает отдачи от удара, и кожа на вашей руке стирается в кровь!")) + + new /obj/temporary(get_turf(target),3, 'icons/effects/effects.dmi', "smash") + target.visible_message(SPAN_DANGER("[target] ловит лицом кулак, улетая назад!")) + for(var/zone in list(BP_CHEST, BP_GROIN, BP_HEAD)) + target.apply_damage(rand(10,20),DAMAGE_BRUTE,def_zone=zone) + + target.throw_at(get_edge_target_turf(target, get_dir(user, target)), 1, 2, user) + + return TRUE + + +///MASTER/// + + + if(pk_rank_user == PSI_RANK_MASTER) + user.visible_message(SPAN_DANGER("[user] заносит руку назад, совершая резкий удар, буквально разрезающий воздух!")) + + if(istype(target, /obj/structure) || istype(target, /obj/machinery)) + user.visible_message("[user] толкает [target] вперёд!") + var/obj/O = target + if(O.anchored == TRUE) + O.anchored = FALSE + new /obj/temporary(get_turf(target),3, 'icons/effects/effects.dmi', "smash") + O.throw_at(get_edge_target_turf(O, get_dir(user, O)), 6, 2, user) + return TRUE + +//ENEMY PSI CHECK START + + if(target.psi) + var/pk_rank_target = target.psi.get_rank(PSI_PSYCHOKINESIS) + if(pk_rank_target >= pk_rank_user && !target.psi.suppressed) + if(prob(40)) + to_chat(target, SPAN_NOTICE("Каким-то чудом, [user] пробивается через ваше силовое поле, нанося сокрушительный урон!")) + target.visible_message(SPAN_DANGER("[target] ловит лицом кулак, улетая назад!")) + if(!user.skill_check(SKILL_HAULING, SKILL_EXPERIENCED)) + + user.apply_damage(rand(30,40),DAMAGE_BRUTE, user.hand ? BP_L_ARM : BP_R_ARM) + to_chat(user, SPAN_WARNING("Ваше неподготовленное тело не выдерживает отдачи от удара, и вашу руку выворачивает наизнанку!")) + + for(var/zone in list(BP_CHEST, BP_GROIN, BP_HEAD)) + target.apply_damage(rand(10,15),DAMAGE_BRUTE,def_zone=zone) + new /obj/temporary(get_turf(target),3, 'icons/effects/effects.dmi', "smash") + new /obj/temporary(get_turf(target),6, 'mods/psionics/icons/effects/heavyimpact.dmi', "heavyimpact") + target.throw_at(get_edge_target_turf(target, get_dir(user, target)), 3, 2, user) + return TRUE + + else + to_chat(target, SPAN_NOTICE("Ваше силовое поле успешно сдержало удар, пускай на это и ушло приличное количество концентрации.")) + target.psi.spend_power(10) + for(var/zone in list(BP_CHEST, BP_GROIN, BP_HEAD)) + user.apply_damage(rand(10,25),DAMAGE_BRUTE,def_zone=zone) + new /obj/temporary(get_turf(target),3, 'icons/effects/effects.dmi', "smash") + new /obj/temporary(get_turf(target),6, 'mods/psionics/icons/effects/heavyimpact.dmi', "heavyimpact") + user.throw_at(get_edge_target_turf(user, get_dir(target, user)), 3, 2, target) + user.visible_message(SPAN_DANGER("Мощное силовое поле [target] отбрасывает [user] назад, создавая мощную обратную волну!")) + return TRUE + +//ENEMY PSI CHECK END + + if(!user.skill_check(SKILL_HAULING, SKILL_EXPERIENCED)) + + user.apply_damage(rand(10,15),DAMAGE_BRUTE, user.hand ? BP_L_ARM : BP_R_ARM) + to_chat(user, SPAN_WARNING("Ваше неподготовленное тело не выдерживает отдачи от удара, и вашу руку выворачивает наизнанку!")) + + new /obj/temporary(get_turf(target),3, 'icons/effects/effects.dmi', "smash") + new /obj/temporary(get_turf(target),6, 'mods/psionics/icons/effects/heavyimpact.dmi', "heavyimpact") + target.visible_message(SPAN_DANGER("[target] ловит лицом кулак, улетая назад!")) + for(var/zone in list(BP_CHEST, BP_GROIN, BP_HEAD)) + target.apply_damage(rand(25,40),DAMAGE_BRUTE,def_zone=zone) + + target.throw_at(get_edge_target_turf(target, get_dir(user, target)), 3, 2, user) + + return TRUE + + +///GRANDMASTER/// + + + if(pk_rank_user == PSI_RANK_GRANDMASTER) + user.visible_message(SPAN_DANGER("[user] заносит руку назад, совершая резкий удар, буквально разрезающий воздух!")) + + if(istype(target, /obj/structure) || istype(target, /obj/machinery)) + user.visible_message("[user] толкает [target] вперёд!") + var/obj/O = target + if(O.anchored == TRUE) + O.anchored = FALSE + new /obj/temporary(get_turf(target),3, 'icons/effects/effects.dmi', "smash") + O.throw_at(get_edge_target_turf(O, get_dir(user, O)), 8, 2, user) + return TRUE + + var/mob/living/M = target + if(get_dist(user, M) <= 6) + var/turf/target_turf = get_step(get_turf(target), pick(GLOB.alldirs)) + var/list/line_list = getline(user, target_turf) + for(var/i = 1 to length(line_list)) + var/turf/T = line_list[i] + var/obj/temp_visual/decoy/D = new /obj/temp_visual/decoy(T, user.dir, user) + D.alpha = min(150 + i*15, 255) + animate(D, alpha = 0, time = 2 + i*2) + user.forceMove(target_turf) + +//ENEMY PSI CHECK START + + if(target.psi) + var/pk_rank_target = target.psi.get_rank(PSI_PSYCHOKINESIS) + if(pk_rank_target >= pk_rank_user && !target.psi.suppressed) + if(prob(60)) + to_chat(target, SPAN_NOTICE("Каким-то чудом, [user] пробивается через ваше силовое поле, нанося сокрушительный урон!")) + target.visible_message(SPAN_DANGER("[target] ловит лицом кулак, улетая назад!")) + if(!user.skill_check(SKILL_HAULING, SKILL_EXPERIENCED)) + + user.apply_damage(60,DAMAGE_BRUTE, user.hand ? BP_L_ARM : BP_R_ARM) + to_chat(user, SPAN_WARNING("Ваше неподготовленное тело не выдерживает отдачи от удара, и вашу руку выворачивает наизнанку!")) + + for(var/zone in list(BP_CHEST, BP_GROIN, BP_HEAD)) + target.apply_damage(rand(20,60),DAMAGE_BRUTE,def_zone=zone) + new /obj/temporary(get_turf(target),3, 'icons/effects/effects.dmi', "smash") + new /obj/temporary(get_turf(target),6, 'mods/psionics/icons/effects/heavyimpact.dmi', "heavyimpact") + target.throw_at(get_edge_target_turf(target, get_dir(user, target)), 6, 2, user) + return TRUE + + else + to_chat(target, SPAN_NOTICE("Ваше силовое поле успешно сдержало удар, пускай на это и ушло приличное количество концентрации.")) + target.psi.spend_power(10) + for(var/zone in list(BP_CHEST, BP_GROIN, BP_HEAD)) + user.apply_damage(rand(20,60),DAMAGE_BRUTE,def_zone=zone) + new /obj/temporary(get_turf(target),3, 'icons/effects/effects.dmi', "smash") + new /obj/temporary(get_turf(target),6, 'mods/psionics/icons/effects/heavyimpact.dmi', "heavyimpact") + user.throw_at(get_edge_target_turf(user, get_dir(target, user)), 6, 2, target) + user.visible_message(SPAN_DANGER("Мощное силовое поле [target] отбрасывает [user] назад, создавая мощную обратную волну!")) + return TRUE + +//ENEMY PSI CHECK END + + if(!user.skill_check(SKILL_HAULING, SKILL_EXPERIENCED)) + + user.apply_damage(60,DAMAGE_BRUTE, user.hand ? BP_L_ARM : BP_R_ARM) + to_chat(user, SPAN_WARNING("Ваше неподготовленное тело не выдерживает отдачи от удара, и вашу руку выворачивает наизнанку!")) + + new /obj/temporary(get_turf(target),3, 'icons/effects/effects.dmi', "smash") + new /obj/temporary(get_turf(target),6, 'mods/psionics/icons/effects/heavyimpact.dmi', "heavyimpact") + target.visible_message(SPAN_DANGER("[target] ловит лицом кулак, улетая назад!")) + for(var/zone in list(BP_CHEST, BP_GROIN, BP_HEAD)) + target.apply_damage(rand(20,60),DAMAGE_BRUTE,def_zone=zone) + + target.throw_at(get_edge_target_turf(target, get_dir(user, target)), 6, 2, user) + + return TRUE + +/obj/structure/girder/rock + icon_state = "ground rock" + anchored = TRUE + density = TRUE + layer = ABOVE_HUMAN_LAYER + w_class = ITEM_SIZE_NO_CONTAINER + health_max = 200 + icon = 'mods/psionics/icons/effects/psi_effects.dmi' + icon_state = "earth_pillar_2" + +/obj/structure/girder/rock/use_tool(obj/item/W, mob/user) + if (user.a_intent == I_HURT) + ..() + return + + if(istype(W, /obj/item/pickaxe/diamonddrill)) + playsound(src.loc, 'sound/weapons/Genhit.ogg', 100, 1) + if(do_after(user,reinf_material ? 60 : 40,src)) + to_chat(user, "You drill through the rock!") + if(reinf_material) + reinf_material.place_dismantled_product(get_turf(src)) + dismantle() + return + +/obj/structure/girder/rock/dismantle() + qdel(src) + +/singleton/psionic_power/psychokinesis/rock_shield + name = "Ground Shield" + cost = 10 + cooldown = 30 + use_melee = TRUE + use_ranged = TRUE + min_rank = PSI_RANK_OPERANT + + use_description = "Выберите любую ногу или пятку на жёлтом интенте, а затем нажмите по ближайшему куску земли, чтобы поднять его вверх." + +/singleton/psionic_power/psychokinesis/rock_shield/invoke(mob/living/carbon/user, turf/simulated/target) + if(!(user.zone_sel.selecting in list(BP_L_LEG, BP_R_LEG, BP_L_FOOT, BP_R_FOOT))) + return FALSE + + if(!target) + to_chat(user, SPAN_NOTICE("Данный материал слабо подойдёт для тех задач, для которых вы хотите использовать его!")) + return FALSE + + . = ..() + if(.) + if(istype(target, /turf/simulated/floor/exoplanet)) + var/turf/A = target + if(do_after(user, 10)) + user.visible_message("[user] возводит каменную стену!") + new /obj/temporary(A, 9, 'mods/psionics/icons/effects/psi_effects.dmi', "earth_pillar_0") + spawn(1 SECONDS) + new /obj/structure/girder/rock(get_turf(A)) + return TRUE + else + return FALSE diff --git a/mods/psionics/code/faculties/redaction.dm b/mods/psionics/code/faculties/redaction.dm new file mode 100644 index 0000000000000..63bb4fdd109b5 --- /dev/null +++ b/mods/psionics/code/faculties/redaction.dm @@ -0,0 +1,312 @@ +/singleton/psionic_faculty/redaction + id = PSI_REDACTION + name = "Redaction" + associated_intent = I_HELP + armour_types = list("bio", "rad") + +/singleton/psionic_power/redaction + faculty = PSI_REDACTION + admin_log = FALSE + abstract_type = /singleton/psionic_power/redaction + +/singleton/psionic_power/redaction/proc/check_dead(mob/living/target) + if(!istype(target)) + return FALSE + if(target.stat == DEAD || (target.status_flags & FAKEDEATH)) + return TRUE + return FALSE + +/singleton/psionic_power/redaction/invoke(mob/living/user, mob/living/target) + if(check_dead(target)) + return FALSE + . = ..() + +/singleton/psionic_power/redaction/skinsight + name = "Skinsight" + cost = 3 + cooldown = 30 + use_grab = TRUE + min_rank = PSI_RANK_APPRENTICE + use_description = "Схватите цель, выберите верхнюю часть тела и переключитесь на зелёный интент, затем нажмите по цели захватом ещё раз, чтобы проверить физическое состояние цели." + +/singleton/psionic_power/redaction/skinsight/invoke(mob/living/user, mob/living/target) + if(user.zone_sel.selecting != BP_CHEST) + return FALSE + . = ..() + if(.) + user.visible_message(SPAN_NOTICE("[user] кладёт руку на [target].")) + to_chat(user, medical_scan_results(target, TRUE, user.get_skill_value(SKILL_MEDICAL))) + return TRUE + +/singleton/psionic_power/redaction/mend + name = "Mend" + cost = 7 + cooldown = 50 + use_melee = TRUE + min_rank = PSI_RANK_APPRENTICE + use_description = "Выберите любую часть тела на зелёном интенте и нажмите по цели, чтобы убрать возможные ранения с указанной зоны." + +//UPDATED + +/singleton/psionic_power/redaction/mend/invoke(mob/living/user, mob/living/carbon/human/target) + if(!isliving(target)) + return + var/red_rank = user.psi.get_rank(PSI_REDACTION) + var/pk_rank = user.psi.get_rank(PSI_PSYCHOKINESIS) + var/obj/item/organ/external/E = target.get_organ(user.zone_sel.selecting) + if(!istype(user) || !istype(target)) + return FALSE + . = ..() + if(.) + var/option = input(user, "Выберите что-нибудь!", "Какую помощь вы хотите оказать [target]?") in list("Кровотечение", "Переломы", "Травмы", "Конечности", "Органы") + user.psi.set_cooldown(cooldown) + if (!option) + return + if(option == "Травмы") + if(red_rank < PSI_RANK_MASTER) + to_chat(user, SPAN_WARNING("Ваших сил недостаточно для проведения этой операции!")) + return 0 + if(do_after(user, 20)) + user.visible_message(SPAN_NOTICE("[user] кладёт руки на плечи [target]...")) + to_chat(target, SPAN_NOTICE("Вы ощущаете приятное тепло...ваши раны заживают.")) + new /obj/temporary(get_turf(target),8, 'icons/effects/effects.dmi', "redaction_healing") + + if(user.skill_check(SKILL_ANATOMY, SKILL_TRAINED) && user.skill_check(SKILL_MEDICAL, SKILL_TRAINED)) + to_chat(user, SPAN_NOTICE("Благодаря имеющимся навыкам, вам удалось эффективно залечить некоторые раны[target].")) + target.adjustBruteLoss(-rand(20,40)) + target.adjustFireLoss(-rand(20,40)) + + target.adjustBruteLoss(-(rand(10,20) * red_rank)) + target.adjustFireLoss(-(rand(10,20) * red_rank)) + if(pk_rank >= PSI_RANK_GRANDMASTER) + 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) + continue + + 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("Вы извлекли [removing] из [E.name] вашего пациента.")) + return 1 + if(option == "Переломы") + +//It's easier to repair severed tendon, than put bones in place or either repair it structure, so no rank check + + if(E.status & ORGAN_TENDON_CUT) + if(do_after(user, 40)) + new /obj/temporary(get_turf(target),8, 'icons/effects/effects.dmi', "redaction_healing") + to_chat(user, SPAN_NOTICE("Вы сплели новое сухожилие на месте повреждённого в [E.name].")) + E.status &= ~ORGAN_TENDON_CUT + return 1 + + if(red_rank < PSI_RANK_OPERANT) + to_chat(user, SPAN_WARNING("Ваших сил недостаточно для проведения этой операции!")) + return 0 + if(!E) + to_chat(user, SPAN_WARNING("Эта конечность отсутствует!")) + return 0 + if(BP_IS_ROBOTIC(E)) + to_chat(user, SPAN_WARNING("Эта конечность заменена протезом.")) + return 0 + if(E.is_stump()) + to_chat(user, SPAN_WARNING("Нет смысла тратить силы на этот обрубок. Здесь вы бессильны.")) + return 0 + if(E.status & ORGAN_BROKEN) + user.visible_message(SPAN_NOTICE("[user] кладёт руку на [target]'s [E.name]...")) + if(do_after(user, 60)) + new /obj/temporary(get_turf(target),8, 'icons/effects/effects.dmi', "redaction_healing") + if(!user.skill_check(SKILL_ANATOMY, SKILL_BASIC)) + if(prob(30)) + to_chat(user, SPAN_WARNING("Вы кое-как попытались вновь соединить кости [target], однако сделали своей неопытностью только хуже.")) + target.apply_damage(20,DAMAGE_BRUTE,E) + return 0 + to_chat(user, SPAN_NOTICE("Вы установили кости на их прежнее место, заделав образовавшиеся на их поверхности трещины.")) + E.status &= ~ORGAN_BROKEN + E.stage = 0 + to_chat(target, SPAN_NOTICE("Вы ощущаете неприятное движение в районе [E.name]...кости начинают вставать на место.")) + return 1 + else + to_chat(user, SPAN_WARNING("[E.name] не имеет никаких внутренних повреждений!")) + return 0 + + if(option == "Кровотечение") + if(red_rank < PSI_RANK_APPRENTICE) + to_chat(user, SPAN_WARNING("Ваших сил недостаточно для проведения этой операции!")) + return 0 + if(!E) + to_chat(user, SPAN_WARNING("Эта конечность отсутствует!")) + return 0 + if(BP_IS_ROBOTIC(E)) + to_chat(user, SPAN_WARNING("Эта конечность не жива.")) + return 0 + if(E.is_stump()) + to_chat(user, SPAN_WARNING("Нет смысла тратить силы на этот обрубок. Здесь вы бессильны.")) + return 0 + if(E.status & ORGAN_ARTERY_CUT && red_rank >= PSI_RANK_OPERANT) + if(do_after(user, 60)) + new /obj/temporary(get_turf(target),8, 'icons/effects/effects.dmi', "redaction_healing") + if(!user.skill_check(SKILL_ANATOMY, SKILL_BASIC)) + if(prob(30)) + to_chat(user, SPAN_WARNING("Ваша попытка связать разорванные артерии в [E.name] закончились ужасным провалом.")) + target.apply_damage(20,DAMAGE_BRUTE,E) + return 0 + to_chat(user, SPAN_NOTICE("Вы вновь связали разованные артерии в [E.name], останавливая внутреннее кровотечение.")) + to_chat(target, SPAN_NOTICE("Вы ощущаете неприятное чувство в районе [E.name]...словно кто-то вновь сплетает ваши вены воедино.")) + E.status &= ~ORGAN_ARTERY_CUT + return 1 + for(var/datum/wound/W in E.wounds) + if(W.bleeding()) + if(W.wound_damage() < 30) + if(do_after(user, 30)) + to_chat(user, SPAN_NOTICE("Вы приказываете крови в [E.name], остановится.")) + to_chat(target, SPAN_NOTICE("Вы ощущаете как ваша кожа смыкается в [E.name]...")) + new /obj/temporary(get_turf(target),8, 'icons/effects/effects.dmi', "redaction_healing") + W.bleed_timer = 0 + W.clamped = TRUE + E.status &= ~ORGAN_BLEEDING + return 1 + else + to_chat(user, SPAN_NOTICE("Эта рваная рана, слишком разтерзана, чтобы ее закрыть.")) + return 0 + else + to_chat(user, SPAN_WARNING("[E.name] не имеет никаких внутренних повреждений!")) + return 0 + + if(option == "Конечности") + if(red_rank < PSI_RANK_MASTER) + to_chat(user, SPAN_WARNING("Ваших сил недостаточно для проведения этой операции!")) + return 0 + if(red_rank >= PSI_RANK_MASTER) + + if(!E) + var/what = alert(user, "Вы уверены, что хотите прибегнуть к трансплантации?", "Обратная связь", "Да", "Нет") + switch(what) + if("Да") + if(do_after(user, 120)) + // Remove all stumps first + for(var/O in target.organs_by_name) + var/obj/item/organ/external/S = target.organs_by_name[O] + if(S.is_stump()) + target.visible_message(SPAN_WARNING("[S.name] рассыпается, а на его месте начинает формироваться нечто новое...")) + qdel(S) + var/list/missing_limbs = target.species.has_limbs - target.organs_by_name + if(do_after(user, 30)) + if(!LAZYLEN(missing_limbs)) + return + var/o_type = pick(missing_limbs) + new /obj/temporary(get_turf(target),8, 'icons/effects/effects.dmi', "redaction_healing") + missing_limbs -= o_type + var/limb_type = target.species.has_limbs[o_type]["path"] + var/obj/new_limb = new limb_type(target) + target.visible_message(SPAN_DANGER("Место на теле [target], где раньше был лишь обрубок - внезапно начинает формировать новую [new_limb.name]!")) + user.visible_message(SPAN_DANGER("[user] выглядит крайне обессиленным.")) + if(!user.skill_check(SKILL_ANATOMY, SKILL_TRAINED) || !user.skill_check(SKILL_MEDICAL, SKILL_BASIC)) + if(prob(60)) + var/limb = pick(BP_L_LEG,BP_R_LEG, BP_L_HAND, BP_R_HAND) + to_chat(user, SPAN_WARNING("Ваша некомпетентность привела к тому, что во время восстановления [new_limb.name] вы повредили свою руку!")) + user.apply_damage(80,DAMAGE_BRUTE,limb) + // limb.mutate() -- придумать + user.adjustBruteLoss(rand(30,40)) + user.psi.spend_power(30) + + target.regenerate_icons() + + else + return 0 + + if(option == "Органы") + if(red_rank < PSI_RANK_MASTER) + to_chat(user, SPAN_WARNING("Ваших сил недостаточно для проведения этой операции!")) + return 0 + if(red_rank >= PSI_RANK_MASTER) + for(var/obj/item/organ/internal/I in E.internal_organs) + if(!BP_IS_ROBOTIC(I) && !BP_IS_CRYSTAL(I) && I.damage > 0) + if(do_after(user, 120)) + to_chat(user, SPAN_NOTICE("Вы вторгаетесь в [target], восстанавливая: [I].")) + new /obj/temporary(get_turf(target),8, 'icons/effects/effects.dmi', "redaction_healing") + var/heal_rate = red_rank + if(!user.skill_check(SKILL_ANATOMY, SKILL_TRAINED) || !user.skill_check(SKILL_MEDICAL, SKILL_BASIC)) + if(prob(60)) + to_chat(user, SPAN_WARNING("Ваша неопытность приводит к тому, что вы лишь усугубили состояние [target]!")) + I.damage = max(0, I.damage + rand(5,10)) + return 0 + I.damage = max(0, I.damage - rand(heal_rate,heal_rate*3)) + return 1 + +/singleton/psionic_power/redaction/cleanse + name = "Cleanse" + cost = 9 + cooldown = 60 + use_melee = TRUE + min_rank = PSI_RANK_OPERANT + use_description = "Нажмите по цели на зелёном интенте, чтобы очистить его от генетических отклонений и воздействий радиации." + +/singleton/psionic_power/redaction/cleanse/invoke(mob/living/user, 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.radiation) + to_chat(user, SPAN_NOTICE("Вы выводите нежелательные частицы из тела [target]...")) + if(target.radiation > removing) + target.radiation -= removing + else + target.radiation = 0 + return TRUE + if(target.getCloneLoss()) + to_chat(user, SPAN_NOTICE("Вы с трудом восстанавливаете прежнюю структуру ДНК [target]...")) + if(target.getCloneLoss() >= removing) + target.adjustCloneLoss(-removing) + else + target.adjustCloneLoss(-(target.getCloneLoss())) + return TRUE + to_chat(user, SPAN_NOTICE("У [target] нет ни радиационного заражения, ни генетических отклонений.")) + return FALSE + +/singleton/psionic_power/revive + name = "Revive" + cost = 25 + cooldown = 80 + use_grab = TRUE + min_rank = PSI_RANK_GRANDMASTER + faculty = PSI_REDACTION + use_description = "Схватите цель, выберите голову и зелёный интент, затем нажмите по нему захватом, чтобы попытаться вернуть его к жизни." + admin_log = FALSE + +/singleton/psionic_power/revive/invoke(mob/living/user, 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("Этот человек ещё жив!")) + return TRUE + + if((world.time - target.timeofdeath) > 6000) + to_chat(user, SPAN_WARNING("[target] пролежал здесь слишком долго. Нет никакой надежды на то, что его выйдет оживить.")) + return TRUE + + user.visible_message(SPAN_NOTICE("[user] кладёт обе руки на тело [target]...")) + new /obj/temporary(get_turf(target),6, 'icons/effects/effects.dmi', "green_sparkles") + if(!do_after(user, 100, target)) + user.psi.backblast(rand(10,25)) + return TRUE + + for(var/mob/observer/G in GLOB.dead_mobs) + if(G.mind && G.mind.current == target && G.client) + to_chat(G, SPAN_NOTICE("Your body has been revived, Re-Enter Corpse to return to it.")) + break + to_chat(target, SPAN_NOTICE("Вы просыпаетесь от вечного сна, вместе с ужасающими воспоминаниями с того света.")) + target.visible_message(SPAN_NOTICE("[target] трясётся в ужасе!")) + new /obj/temporary(get_turf(target),8, 'icons/effects/effects.dmi', "rune_convert") + target.adjustOxyLoss(-rand(30,45)) + target.basic_revival() + return TRUE diff --git a/mods/psionics/code/interface/ui.dm b/mods/psionics/code/interface/ui.dm new file mode 100644 index 0000000000000..d681bdf910656 --- /dev/null +++ b/mods/psionics/code/interface/ui.dm @@ -0,0 +1,21 @@ +/obj/screen/psi + icon = 'mods/psionics/icons/psi.dmi' + var/mob/living/owner + var/hidden = TRUE + +/obj/screen/psi/Initialize(mapload) + . = ..() + owner = loc + loc = null + update_icon() + +/obj/screen/psi/Destroy() + if(owner && owner.client) + owner.client.screen -= src + . = ..() + +/obj/screen/psi/on_update_icon() + if(hidden) + invisibility = INVISIBILITY_ABSTRACT + else + invisibility = 0 diff --git a/mods/psionics/code/interface/ui_hub.dm b/mods/psionics/code/interface/ui_hub.dm new file mode 100644 index 0000000000000..93306a8c1fdb0 --- /dev/null +++ b/mods/psionics/code/interface/ui_hub.dm @@ -0,0 +1,96 @@ +/obj/screen/psi/hub + name = "Psi" + icon_state = "psi_suppressed" + screen_loc = "EAST-1:28,CENTER-3:11" + hidden = FALSE + maptext_x = 6 + maptext_y = -8 + var/image/on_cooldown + var/list/components + +/obj/screen/psi/hub/Initialize(mapload) + . = ..() + on_cooldown = image(icon, "cooldown") + components = list( + new /obj/screen/psi/armour(owner), + new /obj/screen/psi/toggle_psi_menu(owner, src) + ) + START_PROCESSING(SSprocessing, src) + +/obj/screen/psi/hub/on_update_icon() + + if(!owner.psi) + return + + icon_state = owner.psi.suppressed ? "psi_suppressed" : "psi_active" + if(world.time < owner.psi.next_power_use) + AddOverlays(on_cooldown) + else + ClearOverlays() +/* var/offset = 1 + for(var/thing in components) + var/obj/screen/psi/component = thing + component.update_icon() + if(!component.invisibility) component.screen_loc = "EAST-[++offset]:28,CENTER-3:11"*/ + +//FD PSIONICS// + var/length = LAZYLEN(components) + var/x_offset = 1 + var/y_offset = 3 + + for(var/thing in components) + var/obj/screen/psi/component = thing + component.update_icon() + + var/is_menu_toggle = components.Find(component) == length + if(x_offset > 3 && !is_menu_toggle) + x_offset = y_offset > 4 ? 2 : 1 + y_offset++ + + if(!component.invisibility) + component.screen_loc = "EAST-[++x_offset]:28,CENTER-[y_offset]:11" +//FD PSIONICS// + +/obj/screen/psi/hub/Destroy() + STOP_PROCESSING(SSprocessing, src) + owner = null + for(var/thing in components) + qdel(thing) + components.Cut() + . = ..() + +/obj/screen/psi/hub/Process() + if(!istype(owner)) + qdel(src) + return + if(!owner.psi) + return + maptext = "[round((owner.psi.stamina/owner.psi.max_stamina)*100)]%" + update_icon() + +/obj/screen/psi/hub/Click(location, control, click_params) + var/list/params = params2list(click_params) + if(params["shift"]) + owner.show_psi_assay(owner) + return + + if(owner.psi.suppressed && owner.psi.stun) + to_chat(owner, SPAN_WARNING("Ты ошеломлен и не можешь сконцентрироваться на своих психических способностях!")) + return + + owner.psi.suppressed = !owner.psi.suppressed + to_chat(owner, SPAN_NOTICE("Ты [owner.psi.suppressed ? "теперь подавляешь" : "больше не подавляешь"] свои способности.")) + if(owner.psi.suppressed) + var/mob/living/carbon/human/A = owner + if(A.levitation) + A.levitation = FALSE + A.pass_flags &= ~PASS_FLAG_TABLE + A.pixel_y = 0 + A.CutOverlays(image('mods/psionics/icons/psi.dmi', "levitation")) + A.stop_floating() + owner.psi.cancel() + owner.psi.hide_auras() + else + sound_to(owner, sound('sound/effects/psi/power_unlock.ogg')) + owner.psi.show_auras() + update_icon() diff --git a/mods/psionics/code/interface/ui_toggles.dm b/mods/psionics/code/interface/ui_toggles.dm new file mode 100644 index 0000000000000..0fb4ccec9b93d --- /dev/null +++ b/mods/psionics/code/interface/ui_toggles.dm @@ -0,0 +1,106 @@ +// Begin psi armour toggle. +/obj/screen/psi/armour + name = "Psi-Armour" + icon_state = "psiarmour_off" + +/obj/screen/psi/armour/on_update_icon() + ..() + if(invisibility == 0) + icon_state = owner.psi.use_psi_armour ? "psiarmour_on" : "psiarmour_off" + +/mob/living/carbon/human + var/levitation = FALSE + +/mob/living/carbon/human/Life() + if(src.levitation) + src.psi.spend_power(8) + ..() + +/obj/screen/psi/armour/Click() + if(!owner.psi) + return + owner.psi.use_psi_armour = !owner.psi.use_psi_armour + if(owner.psi.use_psi_armour) + to_chat(owner, SPAN_NOTICE("You will now use your psionics to deflect or block incoming attacks.")) + var/mob/living/carbon/human/A = owner + if(A.psi.get_rank(PSI_PSYCHOKINESIS) > PSI_RANK_APPRENTICE && A.psi.ranks_stat[PSI_PSYCHOKINESIS] && !A.psi.suppressed) + A.levitation = TRUE + A.pass_flags |= PASS_FLAG_TABLE + A.pixel_y = 8 + A.AddOverlays(image('mods/psionics/icons/psi.dmi', "levitation")) + A.make_floating(5) + else + to_chat(owner, SPAN_NOTICE("You will no longer use your psionics to deflect or block incoming attacks.")) + var/mob/living/carbon/human/A = owner + if(A.levitation) + A.levitation = FALSE + A.pass_flags &= ~PASS_FLAG_TABLE + A.pixel_y = 0 + A.CutOverlays(image('mods/psionics/icons/psi.dmi', "levitation")) + A.stop_floating() + update_icon() + +// End psi armour toggle. + +// Menu toggle. +/obj/screen/psi/toggle_psi_menu + name = "Show/Hide Psi UI" + icon_state = "arrow_left" + var/obj/screen/psi/hub/controller + +/obj/screen/psi/toggle_psi_menu/Initialize(mapload, obj/screen/psi/hub/_controller) + . = ..() + controller = _controller + +/obj/screen/psi/toggle_psi_menu/Click() + var/set_hidden = !hidden + for(var/thing in controller.components) + var/obj/screen/psi/psi = thing + psi.hidden = set_hidden + controller.update_icon() + +/obj/screen/psi/toggle_psi_menu/on_update_icon() + if(hidden) + icon_state = "arrow_left" + else + icon_state = "arrow_right" +// End menu toggle. + +// Facility toggle. +/obj/screen/psi/toggle_faculty + var/image/disable_overlay + var/faculty_id + +/obj/screen/psi/toggle_faculty/New(mob/living/owner, id) + disable_overlay = image(icon, "cooldown") + faculty_id = id + + name = "Переключить школу [faculty_id]" + icon_state = "[faculty_id]" + ..(owner) + +/obj/screen/psi/toggle_faculty/on_update_icon() + ..() + if(invisibility != 0) + return + + if(owner.psi.ranks_stat[faculty_id]) + ClearOverlays() + else + AddOverlays(disable_overlay) + +/obj/screen/psi/toggle_faculty/Click() + if(!owner.psi) + return + + var/faculty_stat = owner.psi.ranks_stat[faculty_id] + owner.psi.ranks_stat[faculty_id] = !faculty_stat + + if(faculty_stat) + sound_to(owner, sound('sound/effects/psi/power_fail.ogg', volume = 40)) + to_chat(owner, SPAN_NOTICE("Вы более не будете использовать силы школы [faculty_id].")) + else + sound_to(owner, sound('sound/effects/psi/power_unlock.ogg', volume = 40)) + to_chat(owner, SPAN_NOTICE("Вы вновь можете использовать силы школы [faculty_id].")) + update_icon() +// End facility toggle diff --git a/mods/psionics/code/misc/decoyobj.dm b/mods/psionics/code/misc/decoyobj.dm new file mode 100644 index 0000000000000..3a6049eac289e --- /dev/null +++ b/mods/psionics/code/misc/decoyobj.dm @@ -0,0 +1,12 @@ +/obj/temp_visual/decoy + desc = "It's a decoy!" + duration = 15 +/obj/temp_visual/decoy/Initialize(mapload, set_dir, atom/mimiced_atom, modified_duration = 15) + duration = modified_duration + . = ..() + alpha = initial(alpha) + if(mimiced_atom) + name = "The Illusion" + appearance = mimiced_atom.appearance + set_dir(set_dir) + mouse_opacity = 0 diff --git a/mods/psionics/code/mob/mob.dm b/mods/psionics/code/mob/mob.dm new file mode 100644 index 0000000000000..49dd02ff5f6b3 --- /dev/null +++ b/mods/psionics/code/mob/mob.dm @@ -0,0 +1,32 @@ +/mob/living + var/datum/psi_complexus/psi + +/mob/living/Login() + . = ..() + if(psi) + psi.update(TRUE) + if(!psi.suppressed) + psi.show_auras() + +/mob/living/Destroy() + QDEL_NULL(psi) + . = ..() + +/mob/living/proc/set_psi_rank(faculty, rank, take_larger, defer_update, temporary) + if(!src.zone_sel) + to_chat(src, SPAN_NOTICE("Вы чувствуете, как что-то странное касается вашего разума... но ваш разум не способен это осознать.")) + 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) + +/mob/living/proc/deflect_psionic_attack(attacker) + var/blocked = 80 * get_blocked_ratio(null, DAMAGE_PSIONIC) + if(prob(blocked)) + if(attacker) + to_chat(attacker, SPAN_WARNING("Твое ментальное воздействие отражено с помощью защиты [src]!")) + to_chat(src, SPAN_DANGER("[attacker] ментально на тебя воздействует, но ты отражаешь его атаку!")) + return TRUE + return FALSE diff --git a/mods/psionics/code/mob/mob_assay.dm b/mods/psionics/code/mob/mob_assay.dm new file mode 100644 index 0000000000000..bf3f22ed0e5d0 --- /dev/null +++ b/mods/psionics/code/mob/mob_assay.dm @@ -0,0 +1,90 @@ +/mob/living/proc/show_psi_assay(mob/viewer, obj/machinery/psi_meter/machine) + + if(!viewer) viewer = usr + + var/use_He_is = "You are" + var/use_He_has = "You have" + if(istype(machine) || viewer != src) + var/datum/gender/G = GLOB.gender_datums[gender] + use_He_is = "[G.He] [G.is]" + use_He_has = "[G.He] [G.has]" + + var/list/dat = list() + + dat += "

Summary

" + dat += "
" + + if(psi) + 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(mind && !psi.suppressed) + if(GLOB.paramounts.is_antagonist(mind)) + use_rating = SPAN_COLOR("#ff0000", "POTENTIAL DEVIANT PSI-USER") + rating_descriptor = "This indicates a completely deviant psi complexus, either beyond or outside anything currently recorded. Approach with care." + // This space intentionally left blank (for Omega-Minus psi vampires. todo) + if(viewer != usr && GLOB.thralls.is_antagonist(mind) && ishuman(viewer)) + var/mob/living/H = viewer + if(H.psi && H.psi.get_rank(PSI_REDACTION) >= PSI_RANK_GRANDMASTER) + dat += SPAN_COLOR("#ff0000", "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-1]-Omicron" + rating_descriptor = "This indicates the presence of minor latent psi potential with little or no appentice capabilities." + if(2) + use_rating = "[effective_rating]-Omega" + rating_descriptor = "This indicates the presence of minor psi capabilities of the Appentice rank or higher." + if(3) + use_rating = SPAN_COLOR("#f4f441", "[effective_rating]-Lamed") + rating_descriptor = "This indicates the presence of minor psi capabilities of the Operant rank or higher." + if(4) + use_rating = SPAN_COLOR("#f4bc42", "[effective_rating]-Gimmel") + rating_descriptor = "This indicates the presence of significant psi capabilities of the Master rank or higher." + if(5) + use_rating = SPAN_COLOR("#ff0000", "[effective_rating]-Aleph") + rating_descriptor = "This indicates the presence of significant psi capabilities of the Grandmaster rank or higher." + else + use_rating = "[effective_rating]-Omicron" + rating_descriptor = "This indicates the presence of trace latent psi capabilities." + + dat += "[use_He_has] an overall psi rating of [use_rating].
[rating_descriptor]
" + + if(!istype(machine)) + + 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/singleton/psionic_faculty/faculty = SSpsi.get_faculty(faculty_id) + if(psi.ranks[faculty_id] > 0) + dat += "[use_He_is] assayed at the rank of [GLOB.psychic_ranks_to_strings[psi.ranks[faculty.id]]] for the [faculty.name] discipline.
" + else + dat += "[use_He_has] no notable power within the [faculty.name] discipline.
" + 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/singleton/psionic_faculty/faculty = SSpsi.get_faculty(faculty_id) + dat += "" + for(var/singleton/psionic_power/power in check_powers) + dat += "" + dat += "

Psi-power Usage

[use_He_has] access to the following psi-powers within the [faculty.name] discipline:
[power.name][power.use_description]
" + else + dat += "[use_He_has] no notable psychic latency or operancy." + + if(istype(machine)) + dat += "Print Clear Buffer" + machine.last_assay = dat + return + + var/datum/browser/popup = new(viewer, "psi_assay_\ref[src]", "Psi-Assay") + popup.set_content(jointext(dat,null)) + popup.open() diff --git a/mods/psionics/code/mob/mob_interactions.dm b/mods/psionics/code/mob/mob_interactions.dm new file mode 100644 index 0000000000000..786a1e2c84af3 --- /dev/null +++ b/mods/psionics/code/mob/mob_interactions.dm @@ -0,0 +1,75 @@ +#define INVOKE_PSI_POWERS(holder, powers, target, return_on_invocation) \ + if(holder && holder.psi && holder.psi.can_use()) { \ + for(var/thing in powers) { \ + var/singleton/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(atom/A, proximity) + . = ..() + if(. && psi) + INVOKE_PSI_POWERS(src, psi.get_melee_powers(SSpsi.faculties_by_intent[a_intent]), A, FALSE) + if(a_intent == I_HURT) + INVOKE_PSI_POWERS(src, psi.get_melee_powers(SSpsi.faculties_by_name_new["Energistics"]), A, FALSE) + INVOKE_PSI_POWERS(src, psi.get_melee_powers(SSpsi.faculties_by_name_new["Psychokinesis"]), A, FALSE) + if(a_intent == I_GRAB) + INVOKE_PSI_POWERS(src, psi.get_melee_powers(SSpsi.faculties_by_name_new["Psychokinesis"]), A, FALSE) + INVOKE_PSI_POWERS(src, psi.get_melee_powers(SSpsi.faculties_by_name_new["Metakinesis"]), A, FALSE) + INVOKE_PSI_POWERS(src, psi.get_melee_powers(SSpsi.faculties_by_name_new["Manifestation"]), A, FALSE) + if(a_intent == I_DISARM) + INVOKE_PSI_POWERS(src, psi.get_melee_powers(SSpsi.faculties_by_name_new["Coercion"]), A, FALSE) + INVOKE_PSI_POWERS(src, psi.get_melee_powers(SSpsi.faculties_by_name_new["Consciousness"]), A, FALSE) + if(a_intent == I_HELP) + INVOKE_PSI_POWERS(src, psi.get_melee_powers(SSpsi.faculties_by_name_new["Redaction"]), A, FALSE) + INVOKE_PSI_POWERS(src, psi.get_melee_powers(SSpsi.faculties_by_name_new["Energistics"]), A, FALSE) + INVOKE_PSI_POWERS(src, psi.get_melee_powers(SSpsi.faculties_by_name_new["Consciousness"]), A, FALSE) + +/mob/living/RangedAttack(atom/A, params) + if(psi) + INVOKE_PSI_POWERS(src, psi.get_ranged_powers(SSpsi.faculties_by_intent[a_intent]), A, TRUE) + if(a_intent == I_HURT) + INVOKE_PSI_POWERS(src, psi.get_ranged_powers(SSpsi.faculties_by_name_new["Energistics"]), A, TRUE) + INVOKE_PSI_POWERS(src, psi.get_ranged_powers(SSpsi.faculties_by_name_new["Psychokinesis"]), A, TRUE) + if(a_intent == I_GRAB) + INVOKE_PSI_POWERS(src, psi.get_ranged_powers(SSpsi.faculties_by_name_new["Psychokinesis"]), A, TRUE) + INVOKE_PSI_POWERS(src, psi.get_ranged_powers(SSpsi.faculties_by_name_new["Metakinesis"]), A, TRUE) + INVOKE_PSI_POWERS(src, psi.get_ranged_powers(SSpsi.faculties_by_name_new["Manifestation"]), A, TRUE) + if(a_intent == I_DISARM) + INVOKE_PSI_POWERS(src, psi.get_ranged_powers(SSpsi.faculties_by_name_new["Coercion"]), A, TRUE) + if(a_intent == I_HELP) + INVOKE_PSI_POWERS(src, psi.get_ranged_powers(SSpsi.faculties_by_name_new["Energistics"]), A, TRUE) + INVOKE_PSI_POWERS(src, psi.get_ranged_powers(SSpsi.faculties_by_name_new["Redaction"]), A, TRUE) + INVOKE_PSI_POWERS(src, psi.get_ranged_powers(SSpsi.faculties_by_name_new["Consciousness"]), A, TRUE) + . = ..() + +/mob/living/proc/check_psi_grab(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) + if(a_intent == I_HURT) + INVOKE_PSI_POWERS(src, psi.get_grab_powers(SSpsi.faculties_by_name_new["Energistics"]), grab.affecting, FALSE) + if(a_intent == I_GRAB) + INVOKE_PSI_POWERS(src, psi.get_grab_powers(SSpsi.faculties_by_name_new["Psychokinesis"]), grab.affecting, FALSE) + INVOKE_PSI_POWERS(src, psi.get_grab_powers(SSpsi.faculties_by_name_new["Metakinesis"]), grab.affecting, FALSE) + INVOKE_PSI_POWERS(src, psi.get_grab_powers(SSpsi.faculties_by_name_new["Manifestation"]), grab.affecting, FALSE) + if(a_intent == I_DISARM) + INVOKE_PSI_POWERS(src, psi.get_grab_powers(SSpsi.faculties_by_name_new["Coercion"]), grab.affecting, FALSE) + if(a_intent == I_HELP) + INVOKE_PSI_POWERS(src, psi.get_grab_powers(SSpsi.faculties_by_name_new["Redaction"]), grab.affecting, FALSE) + INVOKE_PSI_POWERS(src, psi.get_grab_powers(SSpsi.faculties_by_name_new["Consciousness"]), grab.affecting, FALSE) + +/mob/living/attack_empty_hand(bp_hand) + if(psi) + INVOKE_PSI_POWERS(src, psi.get_manifestations(), src, FALSE) + . = ..() + +#undef INVOKE_PSI_POWERS diff --git a/mods/psionics/code/null/_null.dm b/mods/psionics/code/null/_null.dm new file mode 100644 index 0000000000000..2f22237d7e0c6 --- /dev/null +++ b/mods/psionics/code/null/_null.dm @@ -0,0 +1,30 @@ +/** + * Whether or not this atom or its contents will disrupt psionics. Top-level proc recursively checks all contents. + * + * Returns instance of `/atom/movable` or `FALSE`. Either the atom that can disrupt psionics, or `FALSE` if nothing will + * disrupt. + */ +/atom/proc/disrupts_psionics() + for(var/thing in contents) + var/atom/movable/AM = thing + var/disrupted_by = AM.disrupts_psionics() + if(disrupted_by) + return disrupted_by + return FALSE + +/atom/proc/do_psionics_check(stress, atom/source) + var/turf/T = get_turf(src) + if(istype(T) && T != src) + return T.do_psionics_check(stress, source) + withstand_psi_stress(stress, source) + . = disrupts_psionics() + +/atom/proc/withstand_psi_stress(stress, atom/source) + . = max(stress, 0) + if(.) + for(var/thing in contents) + var/atom/movable/AM = thing + if(istype(AM) && AM != src && AM.disrupts_psionics()) + . = AM.withstand_psi_stress(., source) + if(. <= 0) + break diff --git a/mods/psionics/code/null/chemistry.dm b/mods/psionics/code/null/chemistry.dm new file mode 100644 index 0000000000000..8edf297c2d7ed --- /dev/null +++ b/mods/psionics/code/null/chemistry.dm @@ -0,0 +1,90 @@ + +/singleton/reaction/nullglass + name = "Soulstone" + result = null + required_reagents = list(/datum/reagent/blood = 15, /datum/reagent/crystal = 1) + result_amount = 1 + +/singleton/reaction/nullglass/get_reaction_flags(datum/reagents/holder) + for(var/datum/reagent/blood/blood in holder.reagent_list) + var/weakref/donor_ref = islist(blood.data) && blood.data["donor"] + if(istype(donor_ref)) + var/mob/living/donor = donor_ref.resolve() + if(istype(donor) && (donor.psi || (donor.mind && GLOB.wizards.is_antagonist(donor.mind)))) + return TRUE + +/singleton/reaction/nullglass/on_reaction(datum/reagents/holder, created_volume, reaction_flags) + var/location = get_turf(holder.my_atom) + if(reaction_flags) + for(var/i = 1, i <= created_volume, i++) + new /obj/item/device/soulstone(location) + else + for(var/i = 1, i <= created_volume*2, i++) + new /obj/item/material/shard(location, MATERIAL_CRYSTAL) + +/datum/reagent/crystal + name = "crystallizing agent" + taste_description = "sharpness" + reagent_state = LIQUID + color = "#13bc5e" + should_admin_log = TRUE + +/datum/reagent/crystal/affect_blood(mob/living/carbon/M, removed) + var/result_mat = (M.psi || (M.mind && GLOB.wizards.is_antagonist(M.mind))) ? MATERIAL_NULLGLASS : MATERIAL_CRYSTAL + if(ishuman(M)) + var/mob/living/carbon/human/H = M + for(var/obj/item/organ/external/E in shuffle(H.organs.Copy())) + if(E.is_stump()) + continue + + if(BP_IS_CRYSTAL(E)) + if((E.brute_dam + E.burn_dam) > 0) + if(prob(35)) + to_chat(M, SPAN_NOTICE("You feel a crawling sensation as fresh crystal grows over your [E.name].")) + E.heal_damage(rand(5,8), rand(5,8)) + break + if(BP_IS_BRITTLE(E)) + E.status &= ~ORGAN_BRITTLE + break + else if(prob(15)) + to_chat(H, SPAN_DANGER("Your [E.name] is being lacerated from within!")) + if(E.can_feel_pain()) + H.emote("scream") + if(prob(25)) + for(var/i = 1 to rand(1,3)) + new /obj/item/material/shard(get_turf(E), result_mat) + E.take_external_damage(rand(50,70), 0) + else + E.take_external_damage(rand(20,30), 0) + E.status |= ORGAN_CRYSTAL + E.status |= ORGAN_BRITTLE + break + + for(var/obj/item/organ/internal/I in shuffle(H.internal_organs.Copy())) + if(I.organ_tag == BP_BRAIN) + continue + + if(BP_IS_CRYSTAL(I)) + if(prob(35)) + to_chat(M, SPAN_NOTICE("You feel a deep, sharp tugging sensation as your [I.name] is mended.")) + I.heal_damage(rand(1,3)) + break + if(BP_IS_BRITTLE(I)) + I.status &= ~ORGAN_BRITTLE + break + else if(prob(15)) + to_chat(H, SPAN_DANGER("You feel visceral, sharp twisting within your body!")) + if(I.can_feel_pain()) + H.emote("scream") + if(prob(25)) + I.take_internal_damage(rand(10,15), 0) + else + I.take_internal_damage(rand(5,10), 0) + I.status |= ORGAN_CRYSTAL + I.status |= ORGAN_BRITTLE + break + else + to_chat(M, SPAN_DANGER("Your flesh is being lacerated from within!")) + M.adjustBruteLoss(rand(3,6)) + if(prob(10)) + new /obj/item/material/shard(get_turf(M), result_mat) diff --git a/mods/psionics/code/null/flooring.dm b/mods/psionics/code/null/flooring.dm new file mode 100644 index 0000000000000..b7c06e73c52a7 --- /dev/null +++ b/mods/psionics/code/null/flooring.dm @@ -0,0 +1,20 @@ +/singleton/flooring + var/psi_null + +/singleton/flooring/proc/is_psi_null() + return psi_null + +/singleton/flooring/tiling/nullglass + name = "nullglass plating" + desc = "You can hear the tiles whispering..." + icon_base = "nullglass" + color = COLOR_NULLGLASS + has_damage_range = null + flags = TURF_REMOVE_SCREWDRIVER + build_type = /obj/item/stack/tile/floor_nullglass + psi_null = TRUE + +/obj/item/stack/tile/floor_nullglass + name = "nullglass floor tile" + icon_state = "tile_nullglass" + matter = list(MATERIAL_NULLGLASS = 937.5) diff --git a/mods/psionics/code/null/material.dm b/mods/psionics/code/null/material.dm new file mode 100644 index 0000000000000..983fc94ee9205 --- /dev/null +++ b/mods/psionics/code/null/material.dm @@ -0,0 +1,32 @@ +/material + var/is_psionic_nullifier + +/material/proc/is_psi_null() + return is_psionic_nullifier + +/material/nullglass + is_psionic_nullifier = TRUE + +/material/nullglass + name = MATERIAL_NULLGLASS + icon_colour = COLOR_NULLGLASS + conductive = 1 + stack_type = /obj/item/stack/material/nullglass + flags = MATERIAL_BRITTLE + opacity = 0.5 + integrity = 30 + shard_type = SHARD_SHARD + tableslam_noise = 'sound/effects/Glasshit.ogg' + hardness = 80 + weight = 25 + sheet_icon_base = "diamond" + sheet_singular_name = "gem" + sheet_plural_name = "gems" + door_icon_base = "stone" + destruction_desc = "shatters" + hitsound = 'sound/effects/Glasshit.ogg' + hidden_from_codex = TRUE + +/material/nullglass/generate_recipes() + . = ..() + . += new /datum/stack_recipe/tile/nullglass(src) diff --git a/mods/psionics/code/null/material_sheet.dm b/mods/psionics/code/null/material_sheet.dm new file mode 100644 index 0000000000000..455cbb258e2e5 --- /dev/null +++ b/mods/psionics/code/null/material_sheet.dm @@ -0,0 +1,22 @@ +/obj/item/stack/material/withstand_psi_stress(stress, atom/source) + . = ..(stress, source) + if(amount > 0 && . > 0 && disrupts_psionics()) + if(. > amount) + use(amount) + . -= amount + else + use(stress) + . = 0 + +/obj/item/stack/material/disrupts_psionics() + return (material && material.is_psi_null()) ? src : FALSE + +/obj/item/stack/material/nullglass + name = "nullglass" + icon_state = "diamond" + plural_icon_state = "diamond-mult" + max_icon_state = "diamond-max" + default_type = MATERIAL_NULLGLASS + +/obj/item/stack/material/nullglass/fifty + amount = 50 diff --git a/mods/psionics/code/null/material_weapon.dm b/mods/psionics/code/null/material_weapon.dm new file mode 100644 index 0000000000000..24ef29c6c876b --- /dev/null +++ b/mods/psionics/code/null/material_weapon.dm @@ -0,0 +1,11 @@ +/obj/item/material/disrupts_psionics() + return (material && material.is_psi_null()) ? src : FALSE + +/obj/item/material/withstand_psi_stress(stress, atom/source) + . = ..(stress, source) + if(!health_dead() && . > 0 && disrupts_psionics()) + damage_health(.) + . = max(0, -(get_current_health())) + +/obj/item/material/shard/nullglass/New(newloc) + ..(newloc, MATERIAL_NULLGLASS) diff --git a/mods/psionics/code/null/turf_floor.dm b/mods/psionics/code/null/turf_floor.dm new file mode 100644 index 0000000000000..0a49c172b8184 --- /dev/null +++ b/mods/psionics/code/null/turf_floor.dm @@ -0,0 +1,7 @@ +/turf/simulated/floor/disrupts_psionics() + return (flooring && flooring.is_psi_null()) ? src : ..() + +/turf/simulated/floor/tiled/nullglass + name = "nullglass floor" + icon_state = "nullglass" + initial_flooring = /singleton/flooring/tiling/nullglass diff --git a/mods/psionics/code/null/turf_wall.dm b/mods/psionics/code/null/turf_wall.dm new file mode 100644 index 0000000000000..c047beb0b030b --- /dev/null +++ b/mods/psionics/code/null/turf_wall.dm @@ -0,0 +1,18 @@ +/turf/simulated/wall/disrupts_psionics() + return ((material && material.is_psi_null()) || (reinf_material && reinf_material.is_psi_null())) ? src : ..() + +/turf/simulated/wall/withstand_psi_stress(stress, atom/source) + . = ..(stress, source) + if(. > 0 && disrupts_psionics()) + var/cap = material.integrity + if(reinf_material) cap += reinf_material.integrity + var/stress_total = get_damage_value() + . + damage_health(.) + . = max(0, -(cap-stress_total)) + +/turf/simulated/wall/nullglass + color = "#ff6088" + +/turf/simulated/wall/nullglass/Initialize(ml) + color = null + ..(ml, MATERIAL_NULLGLASS) diff --git a/mods/psionics/code/null/~null.dm b/mods/psionics/code/null/~null.dm new file mode 100644 index 0000000000000..363f29cfa8d7a --- /dev/null +++ b/mods/psionics/code/null/~null.dm @@ -0,0 +1,5 @@ +/* +#undef PSI_IMPLANT_SHOCK +#undef PSI_IMPLANT_WARN +#undef PSI_IMPLANT_DISABLED +*/ diff --git a/mods/psionics/code/override.dm b/mods/psionics/code/override.dm new file mode 100644 index 0000000000000..517d187cc88ee --- /dev/null +++ b/mods/psionics/code/override.dm @@ -0,0 +1,65 @@ +/datum/job/proc/give_psi(mob/living/carbon/human/H) + + if(!(GLOB.species_by_name[SPECIES_HUMAN]) || !(GLOB.species_by_name[SPECIES_VATGROWN]) || !(GLOB.species_by_name[SPECIES_SPACER]) || !(GLOB.species_by_name[SPECIES_GRAVWORLDER]) || !(GLOB.species_by_name[SPECIES_MULE])) + return + + if(psi_latency_chance && prob(psi_latency_chance)) + H.set_psi_rank(pick(PSI_COERCION, PSI_REDACTION, PSI_ENERGISTICS, PSI_PSYCHOKINESIS, PSI_CONSCIOUSNESS, PSI_MANIFESTATION, PSI_METAKINESIS), 1, defer_update = TRUE) + + if(!whitelist_lookup(SPECIES_PSI, H.client.ckey)) + return + + var/list/psi_abilities_by_name = H.client.prefs.psi_abilities + + if(!H.client.prefs.psi_threat_level) + return + + LAZYINITLIST(psi_faculties) + for(var/faculty_name in list("Coercion", "Consciousness", "Energistics", "Manifestation", "Metakinesis", "Psychokinesis", "Redaction")) + var/singleton/psionic_faculty/faculty = SSpsi.faculties_by_name[faculty_name] + var/faculty_id = faculty.id + psi_faculties |= list("[faculty_id]" = psi_abilities_by_name[faculty_name] - 1) + + for(var/psi in psi_faculties) + if(psi_faculties[psi] > 0) + H.set_psi_rank(psi, psi_faculties[psi], take_larger = TRUE, defer_update = TRUE) + + H.psi.update() + + give_psionic_implant_on_join ||= (H.client.prefs.psi_openness && H.client.prefs.psi_threat_level > 0) + + if(!give_psionic_implant_on_join) + return + + var/obj/item/implant/psi_control/imp = new + imp.implanted(H) + imp.forceMove(H) + imp.imp_in = H + imp.implanted = TRUE + var/obj/item/organ/external/affected = H.get_organ(BP_HEAD) + if(affected) + affected.implants += imp + imp.part = affected + to_chat(H, SPAN_DANGER("As a registered psionic, you are fitted with a psi-dampening control implant. Using psi-power while the implant is active will result in neural shocks and your violation being reported.")) + +/datum/job/equip(mob/living/carbon/human/H, alt_title, datum/mil_branch/branch, datum/mil_rank/grade) + + if (required_language) + H.add_language(required_language) + H.set_default_language(all_languages[required_language]) + + if (!length(H.languages)) + H.add_language(LANGUAGE_SPACER) + H.set_default_language(all_languages[LANGUAGE_SPACER]) + + give_psi(H) + + var/singleton/hierarchy/outfit/outfit = get_outfit(H, alt_title, branch, grade) + if(outfit) . = outfit.equip(H, title, alt_title) + if(faction) + H.faction = faction + H.last_faction = faction + +/datum/job + + give_psionic_implant_on_join = FALSE // If psionic, will be implanted for control. diff --git a/mods/psionics/code/preferences/01_basic.dm b/mods/psionics/code/preferences/01_basic.dm new file mode 100644 index 0000000000000..3adb9cbbede67 --- /dev/null +++ b/mods/psionics/code/preferences/01_basic.dm @@ -0,0 +1,43 @@ +/datum/preferences + var/psi_threat_level = 0 + var/psi_openness = TRUE + +/datum/category_item/player_setup_item/psionics/basic + name = "Basic" + sort_order = 1 + +/datum/category_item/player_setup_item/psionics/basic/load_character(datum/pref_record_reader/R) + pref.psi_threat_level = R.read("psi_threat_level") + pref.psi_openness = R.read("psi_openness") + +/datum/category_item/player_setup_item/psionics/basic/save_character(datum/pref_record_writer/W) + W.write("psi_threat_level", pref.psi_threat_level) + W.write("psi_openness", pref.psi_openness) + +/datum/category_item/player_setup_item/psionics/basic/sanitize_character() + pref.psi_threat_level = clamp(pref.psi_threat_level, 0, 4) + +/datum/category_item/player_setup_item/psionics/basic/content(mob/user) + . = list() + if(!whitelist_lookup(SPECIES_PSI, user.ckey)) + . += "You are not permitted to be Psionic
" + else + . += "Power: [pref.psi_threat_level]
" + + if(pref.psi_threat_level) + . += "Implant: [pref.psi_openness ? "Yes" : "No"]
" + + . = jointext(.,null) + +/datum/category_item/player_setup_item/psionics/OnTopic(href, list/href_list, mob/user) + if(href_list["select_psi_threat_level"]) + pref.psi_threat_level = text2num(input("Select threat level", CHARACTER_PREFERENCE_INPUT_TITLE, pref.psi_threat_level) in list("0", "1", "2", "3", "4")) + pref.psi_threat_level = clamp(pref.psi_threat_level, 0, 4) + pref.sanitize_psi_abilities() + return TOPIC_REFRESH + + else if(href_list["toggle_psi_openness"]) + pref.psi_openness = !pref.psi_openness + return TOPIC_REFRESH + + return ..() diff --git a/mods/psionics/code/preferences/02_abilities.dm b/mods/psionics/code/preferences/02_abilities.dm new file mode 100644 index 0000000000000..69b06fb7c2710 --- /dev/null +++ b/mods/psionics/code/preferences/02_abilities.dm @@ -0,0 +1,147 @@ +GLOBAL_LIST_AS(psi_level2cost, list( + "Blunt" = 0, + "Latent" = 2, + "Apprentice" = 6, + "Operant" = 16 +)) + +GLOBAL_LIST_AS(psi_faculty2color, list( + "Coercion" = COLOR_RED, + "Consciousness" = COLOR_SURGERY_BLUE, + "Energistics" = COLOR_MEDICAL_UNKNOWN_IMPLANT, + "Manifestation" = COLOR_LIGHT_CYAN, + "Metakinesis" = COLOR_SEDONA, + "Psychokinesis" = COLOR_YELLOW, + "Redaction" = MANIFEST_COLOR_SERVICE +)) + +GLOBAL_LIST_AS(psi_threat_level2free_points, list(6, 8, 12, 16)) + +/datum/preferences + var/list/psi_abilities + +/datum/preferences/proc/sanitize_psi_abilities() + if(!psi_threat_level) + return + + while(TRUE) + for(var/faculty in psi_abilities) + if(calculate_free_points() >= 0) + return + + if(psi_abilities[faculty] > 1) + --psi_abilities[faculty] + + +/datum/preferences/proc/calculate_free_points() + . = GLOB.psi_threat_level2free_points[psi_threat_level] + + for(var/faculty in psi_abilities) + for(var/level in 1 to LAZYLEN(GLOB.psi_level2cost)) + var/level_name = GLOB.psi_level2cost[level] + var/level_cost = GLOB.psi_level2cost[level_name] + + if(psi_abilities[faculty] == level) + . -= level_cost + +/datum/category_item/player_setup_item/psionics/abilities + name = "Abilities" + sort_order = 3 + + var/static/list/psi_faculties_names + +/datum/category_item/player_setup_item/psionics/abilities/load_character(datum/pref_record_reader/R) + pref.psi_abilities = R.read("psi_abilities") + +/datum/category_item/player_setup_item/psionics/abilities/save_character(datum/pref_record_writer/W) + W.write("psi_abilities", pref.psi_abilities) + +/datum/category_item/player_setup_item/psionics/abilities/sanitize_character() + if(LAZYLEN(pref.psi_abilities)) + return ..() + + if(!psi_faculties_names) + psi_faculties_names = list() + + var/list/faculties = GET_SINGLETON_SUBTYPE_MAP(/singleton/psionic_faculty) + for(var/ftype in faculties) + var/singleton/psionic_faculty/faculty = faculties[ftype] + psi_faculties_names += faculty.name + + pref.psi_abilities = list() + for(var/faculty in psi_faculties_names) + pref.psi_abilities[faculty] = 1 + + return ..() + + +/datum/category_item/player_setup_item/psionics/abilities/proc/can_select_level(faculty, level) + if(level <= 1) + return TRUE // safe measure + + return (pref.calculate_free_points() + GLOB.psi_level2cost[GLOB.psi_level2cost[pref.psi_abilities[faculty]]]) >= GLOB.psi_level2cost[GLOB.psi_level2cost[level]] + +/datum/category_item/player_setup_item/psionics/abilities/content(mob/user) + . = list() + if(!whitelist_lookup(SPECIES_PSI, user.ckey)) + . += "
" + else + + if(!pref.psi_threat_level || !whitelist_lookup(SPECIES_PSI, user.ckey)) + return ..() + + if(!pref.psi_abilities) + sanitize_character() + + . += "" + + . += "
" + + . += FONT_LARGE("Points remaining: [pref.calculate_free_points()]") + + . += "" + + for(var/faculty in list("Coercion", "Consciousness", "Energistics", "Manifestation", "Metakinesis", "Psychokinesis", "Redaction")) + . += "" + . += "" + + for(var/level_index in 1 to LAZYLEN(GLOB.psi_level2cost)) + var/level_name = GLOB.psi_level2cost[level_index] + + if(pref.psi_abilities[faculty] == level_index) + . += "" + else if(can_select_level(faculty, level_index)) + . += "" + else + . += "" + + . += "" + + . += "
[faculty]:
[level_name]
[level_name]
" + + . += "
" + + . = jointext(., null) + +/datum/category_item/player_setup_item/psionics/abilities/OnTopic(href, list/href_list, mob/user) + if(..()) + return TOPIC_HANDLED + + if(href_list["select"]) + var/level = text2num(href_list["select"]) + var/faculty = href_list["faculty"] + + if(!can_select_level(faculty, level)) + return TOPIC_HANDLED + + pref.psi_abilities[faculty] = level + + return TOPIC_REFRESH diff --git a/mods/psionics/icons/effects/heavyimpact.dmi b/mods/psionics/icons/effects/heavyimpact.dmi new file mode 100644 index 0000000000000..dedaca533ac5c Binary files /dev/null and b/mods/psionics/icons/effects/heavyimpact.dmi differ diff --git a/mods/psionics/icons/effects/psi_effects.dmi b/mods/psionics/icons/effects/psi_effects.dmi new file mode 100644 index 0000000000000..77ce19fa8f495 Binary files /dev/null and b/mods/psionics/icons/effects/psi_effects.dmi differ diff --git a/mods/psionics/icons/effects/smoke.dmi b/mods/psionics/icons/effects/smoke.dmi new file mode 100644 index 0000000000000..9c6f5f9669045 Binary files /dev/null and b/mods/psionics/icons/effects/smoke.dmi differ diff --git a/mods/psionics/icons/psi.dmi b/mods/psionics/icons/psi.dmi new file mode 100644 index 0000000000000..544de7bfdc436 Binary files /dev/null and b/mods/psionics/icons/psi.dmi differ diff --git a/mods/psionics/icons/psi_fd/freeze.dmi b/mods/psionics/icons/psi_fd/freeze.dmi new file mode 100644 index 0000000000000..0461619064548 Binary files /dev/null and b/mods/psionics/icons/psi_fd/freeze.dmi differ diff --git a/mods/psionics/icons/psi_fd/lefthand.dmi b/mods/psionics/icons/psi_fd/lefthand.dmi new file mode 100644 index 0000000000000..afc74a556b418 Binary files /dev/null and b/mods/psionics/icons/psi_fd/lefthand.dmi differ diff --git a/mods/psionics/icons/psi_fd/projectiles.dmi b/mods/psionics/icons/psi_fd/projectiles.dmi new file mode 100644 index 0000000000000..79b9744210b32 Binary files /dev/null and b/mods/psionics/icons/psi_fd/projectiles.dmi differ diff --git a/mods/psionics/icons/psi_fd/righthand.dmi b/mods/psionics/icons/psi_fd/righthand.dmi new file mode 100644 index 0000000000000..e971fd181a6b5 Binary files /dev/null and b/mods/psionics/icons/psi_fd/righthand.dmi differ diff --git a/mods/psionics/icons/psychic_powers.dmi b/mods/psionics/icons/psychic_powers.dmi new file mode 100644 index 0000000000000..d5ed857bf86e4 Binary files /dev/null and b/mods/psionics/icons/psychic_powers.dmi differ diff --git a/mods/psionics/icons/pump/pump.dmi b/mods/psionics/icons/pump/pump.dmi new file mode 100644 index 0000000000000..11258510d50b1 Binary files /dev/null and b/mods/psionics/icons/pump/pump.dmi differ diff --git a/mods/psionics/icons/pump/pump_on_mob.dmi b/mods/psionics/icons/pump/pump_on_mob.dmi new file mode 100644 index 0000000000000..60a55f6495bef Binary files /dev/null and b/mods/psionics/icons/pump/pump_on_mob.dmi differ diff --git a/mods/psionics/psionics.dm b/mods/psionics/psionics.dm new file mode 100644 index 0000000000000..85a5f5609dfb9 --- /dev/null +++ b/mods/psionics/psionics.dm @@ -0,0 +1,4 @@ +/singleton/modpack/psionics + name = "Псионика" + desc = "Добавляет псионику с Final Destination, оригинальные авторы Maximum123, DANILCUS, Воид" + author = "Roche Hendson и .nasend" diff --git a/mods/psionics/psionics_includes.dm b/mods/psionics/psionics_includes.dm new file mode 100644 index 0000000000000..558c4fcf64097 --- /dev/null +++ b/mods/psionics/psionics_includes.dm @@ -0,0 +1,65 @@ +#ifndef MODPACK_PSIONICS +#define MODPACK_PSIONICS + +#include "psionics.dm" + + +#include "code/misc/decoyobj.dm" +#include "code/complexus/complexus_helpers.dm" +#include "code/complexus/complexus_latency.dm" +#include "code/complexus/complexus_power_cache.dm" +#include "code/complexus/complexus_process.dm" +#include "code/complexus/complexus_topic.dm" +#include "code/complexus/complexus.dm" +#include "code/equipment/cerebro_enhancers.dm" +#include "code/equipment/foundation_implanter.dm" +#include "code/equipment/foundation_weapon.dm" +#include "code/equipment/implant.dm" +#include "code/equipment/null_ammo.dm" +#include "code/equipment/psimeter.dm" +#include "code/equipment/psimonitor.dm" +#include "code/equipment/psipower_blade.dm" +#include "code/equipment/psipower_bow.dm" +#include "code/equipment/psipower_cryokinesis.dm" +#include "code/equipment/psipower_electrokinesis.dm" +#include "code/equipment/psipower_engineering.dm" +#include "code/equipment/psipower_gun.dm" +#include "code/equipment/psipower_medical.dm" +#include "code/equipment/psipower_orbs.dm" +#include "code/equipment/psipower_tinker.dm" +#include "code/equipment/psipower_tk.dm" +#include "code/equipment/psipower.dm" +#include "code/events/_psi.dm" +#include "code/events/mini_spasm.dm" +#include "code/events/psi_balm.dm" +#include "code/events/psi_wail.dm" +#include "code/faculties/_faculty.dm" +#include "code/faculties/_power.dm" +#include "code/faculties/coercion.dm" +#include "code/faculties/consciousness.dm" +#include "code/faculties/energistics.dm" +#include "code/faculties/manifestation.dm" +#include "code/faculties/metakinesis.dm" +#include "code/faculties/psychokinesis.dm" +#include "code/faculties/redaction.dm" +#include "code/interface/ui_hub.dm" +#include "code/interface/ui_toggles.dm" +#include "code/interface/ui.dm" +#include "code/mob/mob_assay.dm" +#include "code/mob/mob_interactions.dm" +#include "code/mob/mob.dm" +#include "code/null/_null.dm" +#include "code/null/~null.dm" +#include "code/null/chemistry.dm" +#include "code/null/flooring.dm" +#include "code/null/material_sheet.dm" +#include "code/null/material_weapon.dm" +#include "code/null/material.dm" +#include "code/null/turf_floor.dm" +#include "code/null/turf_wall.dm" +#include "code/equipment/psipump.dm" +#include "code/preferences/01_basic.dm" +#include "code/preferences/02_abilities.dm" +#include "code/override.dm" + +#endif diff --git a/mods/ssinput/code/general/preference_setup.dm b/mods/ssinput/code/general/preference_setup.dm index 989ce95195ba2..12d99f2f12035 100644 --- a/mods/ssinput/code/general/preference_setup.dm +++ b/mods/ssinput/code/general/preference_setup.dm @@ -10,7 +10,10 @@ sort_order = 9 category_item_type = /datum/category_item/player_setup_item/controls - +/datum/category_group/player_setup_category/psionics_preferences + name = "Psionics" + sort_order = 10 + category_item_type = /datum/category_item/player_setup_item/psionics /datum/category_item/player_setup_item/controls/keybindings name = "Keybindings" diff --git a/test/check-paths.sh b/test/check-paths.sh index 4fdc31c3c74ef..06677fee1e70d 100755 --- a/test/check-paths.sh +++ b/test/check-paths.sh @@ -57,7 +57,7 @@ exactly 0 "simulated = 0/1" 'simulated\s*=\s*\d' -P exactly 2 "var/ in proc arguments" '(^/[^/].+/.+?\(.*?)var/' -P exactly 0 "tmp/ vars" 'var.*/tmp/' -P exactly 14 "uses of .len" '\.len\b' -P -exactly 15 "uses of examine()" '[.|\s]examine\(' -P # If this fails it's likely because you used '/atom/proc/examine(mob)' instead of '/proc/examinate(mob, atom)' - Exception: An examine()-proc may call other examine()-procs +exactly 16 "uses of examine()" '[.|\s]examine\(' -P # If this fails it's likely because you used '/atom/proc/examine(mob)' instead of '/proc/examinate(mob, atom)' - Exception: An examine()-proc may call other examine()-procs exactly 13 "direct modifications of overlays list" '\boverlays((\s*[|^=+&-])|(\.(Cut)|(Add)|(Copy)|(Remove)|(Remove)))' -P exactly 0 "new/list list instantiations" 'new\s*/list' -P exactly 0 "== null tests" '(==\s*null\b)|(\bnull\s*==)' -P #Use isnull() instead