Ports a psionic system from Bay. (#7717)

* Ports a psionic system from Bay.

* Rip out this shitcode.

* shitcoden't

* fixes

* it should work fully now

* Admin fixes

* Remove todos

* remove todos part 2

* Removes psi-armour. We don't need this for now.

* Skrell are now operants. Tweaks coercion.

* Adds thralls.

* Temp removal of psiarmour. Fixes psigrabs.

* Thrall assay.

* fixes

* More fixes

* unused define cleanup

* Log and powers

* Skrell powers are done.

* Update code/modules/psionics/events/mini_spasm.dm

Co-Authored-By: Geeves <ggrobler447@gmail.com>

* Update code/modules/psionics/events/mini_spasm.dm

Co-Authored-By: Geeves <ggrobler447@gmail.com>

* Update code/modules/psionics/equipment/cerebro_enhancers.dm

* did this work???

* jargon

* arrow's fixes

Co-authored-by: Geeves <ggrobler447@gmail.com>
This commit is contained in:
Matt Atlas
2019-12-24 11:32:05 +01:00
committed by Werner
parent 6fe1c9c3b4
commit c087a0a0bf
94 changed files with 2284 additions and 413 deletions

View File

@@ -93,6 +93,26 @@ proc/admin_notice(var/message, var/rights)
<A href='?src=\ref[src];subtlemessage=\ref[M]'>Subtle message</A>
"}
body += "<br><br>"
body += "<b>Psionics:</b><br/>"
if(isliving(M))
var/mob/living/psyker = M
if(psyker.psi)
body += "<a href='?src=\ref[src];remove_psionics=\ref[psyker.psi]'>Remove psionics.</a><br/><br/>"
body += "<a href='?src=\ref[src];trigger_psi_latencies\ref[psyker.psi]'>Trigger latencies.</a><br/>"
body += "<table width = '100%'>"
for(var/faculty in list(PSI_COERCION, PSI_PSYCHOKINESIS, PSI_REDACTION, PSI_ENERGISTICS))
var/datum/psionic_faculty/faculty_decl = SSpsi.get_faculty(faculty)
var/faculty_rank = psyker.psi ? psyker.psi.get_rank(faculty) : 0
body += "<tr><td><b>[faculty_decl.name]</b></td>"
for(var/i = 1 to LAZYLEN(psychic_ranks_to_strings))
var/psi_title = psychic_ranks_to_strings[i]
if(i == faculty_rank)
psi_title = "<b>[psi_title]</b>"
body += "<td><a href='?src=\ref[psyker.mind];set_psi_faculty_rank=[i];set_psi_faculty=[faculty]'>[psi_title]</a></td>"
body += "</tr>"
body += "</table>"
if (M.client)
if(!istype(M, /mob/abstract/new_player))
body += "<br><br>"

View File

@@ -573,6 +573,18 @@
Game() // updates the main game menu
.(href, list("f_secret"=1))
else if(href_list["remove_psionics"])
var/datum/psi_complexus/psi = locate(href_list["remove_psionics"])
if(psi && psi.owner && !QDELETED(psi))
to_chat(psi.owner, span("notice", "<b>Your psionic powers vanish abruptly, leaving you cold and empty.</b>"))
log_and_message_admins("removed all psionics from [key_name(psi.owner)].")
QDEL_NULL(psi)
else if(href_list["trigger_psi_latencies"])
var/datum/psi_complexus/psi = locate(href_list["trigger_psi_latencies"])
log_and_message_admins("triggered psi latencies for [key_name(psi.owner)].")
psi.check_latency_trigger(100, "outside intervention", redactive = TRUE)
else if(href_list["monkeyone"])
if(!check_rights(R_SPAWN)) return
@@ -1075,51 +1087,6 @@
return
else if(href_list["SolGovFaxReply"])
//TODO
/*
var/mob/living/carbon/human/H = locate(href_list["SolGovFaxReply"])
var/obj/machinery/photocopier/faxmachine/fax = locate(href_list["originfax"])
var/input = input(src.owner, "Please enter a message to reply to [key_name(H)] via secure connection. NOTE: BBCode does not work, but HTML tags do! Use <br> for line breaks.", "Outgoing message from Centcomm", "") as message|null
if(!input) return
var/customname = input(src.owner, "Pick a title for the report", "Title") as text|null
for(var/obj/machinery/photocopier/faxmachine/F in machines)
if(F == fax)
if(! (F.stat & (BROKEN|NOPOWER) ) )
// animate! it's alive!
flick("faxreceive", F)
// give the sprite some time to flick
spawn(20)
var/obj/item/paper/P = new /obj/item/paper( F.loc )
P.name = "Sol Government- [customname]"
P.info = input
P.update_icon()
playsound(F.loc, "sound/items/polaroid1.ogg", 50, 1)
// Stamps
var/image/stampoverlay = image('icons/obj/bureaucracy.dmi')
stampoverlay.icon_state = "paper_stamp-cap"
if(!P.stamped)
P.stamped = new
P.stamped += /obj/item/stamp
P.overlays += stampoverlay
P.stamps += "<HR><i>This paper has been stamped and encrypted by the Sol Government Quantum Relay.</i>"
to_chat(src.owner, "Message reply to transmitted successfully.")
log_admin("[key_name(src.owner)] replied to a fax message from [key_name(H)]: [input]")
message_admins("[key_name_admin(src.owner)] replied to a fax message from [key_name_admin(H)]", 1)
return
to_chat(src.owner, "/red Unable to locate fax!")
*/
else if(href_list["jumpto"])
if(!check_rights(R_ADMIN)) return

View File

@@ -181,7 +181,7 @@
if(ispath(objholder,/mob) && !check_rights(R_DEBUG,0))
objholder = /obj/structure/closet
if(3)
var/list/locked = list("vars", "key", "ckey", "client", "firemut", "ishulk", "telekinesis", "xray", "virus", "viruses", "cuffed", "ka", "last_eaten", "urine")
var/list/locked = list("vars", "key", "ckey", "client", "firemut", "ishulk", "xray", "virus", "viruses", "cuffed", "ka", "last_eaten", "urine")
master.buildmode.varholder = input(usr,"Enter variable name:" ,"Name", "name")
if(master.buildmode.varholder in locked && !check_rights(R_DEBUG,0))

View File

@@ -588,7 +588,7 @@
check_health()
return
/obj/machinery/portable_atmospherics/hydroponics/attack_tk(mob/user as mob)
/obj/machinery/portable_atmospherics/hydroponics/do_simple_ranged_interaction(var/mob/user)
if(dead)
remove_dead(user)
else if(harvest)

View File

@@ -34,7 +34,6 @@
var/list/language_progress
var/obj/item/clothing/head/hat
var/datum/reagents/vessel
var/list/internal_organs_by_name = list() // so internal organs have less ickiness too
var/energy_duration = 144 // The time in seconds that this diona can exist in total darkness before its energy runs out
var/dark_consciousness = 144 // How long this diona can stay on its feet and keep moving in darkness after energy is gone.
var/dark_survival = 216 // How long this diona can survive in darkness after energy is gone, before it dies

View File

@@ -448,4 +448,7 @@
return FALSE
/mob/living/carbon/proc/get_ingested_reagents()
return reagents
return reagents
/mob/living/carbon/proc/should_have_organ(var/organ_check)
return 0

View File

@@ -32,4 +32,6 @@
var/coughedtime = null // should only be useful for carbons as the only thing using it has a carbon arg.
var/willfully_sleeping = 0
var/consume_nutrition_from_air = FALSE // used by Diona
var/consume_nutrition_from_air = FALSE // used by Diona
var/list/organs_by_name = list() // map organ names to organs
var/list/internal_organs_by_name = list() // so internal organs have less ickiness too

View File

@@ -1821,7 +1821,7 @@
return TRUE
return species.handle_death_check(src)
/mob/living/carbon/human/proc/should_have_organ(var/organ_check)
/mob/living/carbon/human/should_have_organ(var/organ_check)
return (species?.has_organ[organ_check])
/mob/living/carbon/human/proc/resuscitate()
@@ -1845,13 +1845,6 @@
if(stat == CONSCIOUS)
reagents.add_reagent("adrenaline", amount)
/mob/living/carbon/human/proc/seizure()
if(!paralysis && stat == CONSCIOUS)
visible_message("<span class='danger'>\The [src] starts having a seizure!</span>")
Paralyse(rand(8,16))
make_jittery(rand(150,200))
adjustHalLoss(rand(50,60))
/mob/living/carbon/human/proc/gigashatter()
for(var/obj/item/organ/external/E in organs)
E.fracture()

View File

@@ -7,8 +7,6 @@
/mob/living/carbon/var/list/internal_organs = list()
/mob/living/carbon/var/shock_stage = 0
/mob/living/carbon/human/var/list/organs = list()
/mob/living/carbon/human/var/list/organs_by_name = list() // map organ names to organs
/mob/living/carbon/human/var/list/internal_organs_by_name = list() // so internal organs have less ickiness too
/mob/living/carbon/human/proc/recheck_bad_external_organs()
var/damage_this_tick = getToxLoss()

View File

@@ -309,7 +309,7 @@ mob/living/carbon/human/proc/change_monitor()
if(prob(10) && !(H.species.flags & NO_BLOOD))
to_chat(H,"<span class='warning'>Your nose begins to bleed...</span>")
H.drip(3)
else if(prob(25) && (can_feel_pain()))
else if(prob(25) && (H.can_feel_pain()))
to_chat(H,"<span class='warning'>Your head hurts...</span>")
else if(prob(50))
to_chat(H,"<span class='warning'>Your mind buzzes...</span>")

View File

@@ -885,8 +885,8 @@
//Fire and Brute damage overlay (BSSR)
var/hurtdamage = src.getBruteLoss() + src.getFireLoss() + damageoverlaytemp
damageoverlaytemp = 0 // We do this so we can detect if someone hits us or not.
var/ovr
if(hurtdamage)
var/ovr
switch(hurtdamage)
if(10 to 25)
ovr = "brutedamageoverlay1"
@@ -901,10 +901,10 @@
if(85 to INFINITY)
ovr = "brutedamageoverlay6"
if (last_brute_overlay != ovr)
damageoverlay.cut_overlay(last_brute_overlay)
damageoverlay.add_overlay(ovr)
last_brute_overlay = ovr
if(last_brute_overlay != ovr)
damageoverlay.cut_overlay(last_brute_overlay)
damageoverlay.add_overlay(ovr)
last_brute_overlay = ovr
if(healths)
healths.overlays.Cut()

View File

@@ -52,11 +52,6 @@
stamina = 90
sprint_speed_factor = 1.25 //Evolved for rapid escapes from predators
inherent_verbs = list(
/mob/living/carbon/human/proc/commune,
/mob/living/carbon/human/proc/sonar_ping,
)
default_h_style = "Skrell Short Tentacles"
allowed_citizenships = list(CITIZENSHIP_JARGON, CITIZENSHIP_BIESEL, CITIZENSHIP_SOL, CITIZENSHIP_FRONTIER, CITIZENSHIP_ELYRA, CITIZENSHIP_ERIDANI, CITIZENSHIP_DOMINIA)
@@ -65,5 +60,8 @@
zombie_type = "Skrell Zombie"
/datum/species/skrell/handle_post_spawn(mob/living/carbon/human/H)
H.set_psi_rank(PSI_COERCION, PSI_RANK_OPERANT)
/datum/species/skrell/can_breathe_water()
return TRUE

View File

@@ -417,20 +417,6 @@ There are several things that need to be remembered:
add_image = 1
for(var/mut in mutations)
switch(mut)
/*
if(HULK)
if(fat)
standing.underlays += "hulk_[fat]_s"
else
standing.underlays += "hulk_[g]_s"
add_image = 1
if(COLD_RESISTANCE)
standing.underlays += "fire[fat]_s"
add_image = 1
if(TK)
standing.underlays += "telekinesishead[fat]_s"
add_image = 1
*/
if(LASER_EYES)
standing.overlays += "lasereyes_s"
add_image = 1

View File

@@ -489,6 +489,37 @@ default behaviour is:
return
/mob/living/proc/basic_revival(var/repair_brain = TRUE)
if(repair_brain && getBrainLoss() > 50)
repair_brain = FALSE
setBrainLoss(50)
if(stat == DEAD)
switch_from_dead_to_living_mob_list()
timeofdeath = 0
stat = CONSCIOUS
regenerate_icons()
BITSET(hud_updateflag, HEALTH_HUD)
BITSET(hud_updateflag, STATUS_HUD)
BITSET(hud_updateflag, LIFE_HUD)
failed_last_breath = 0 //So mobs that died of oxyloss don't revive and have perpetual out of breath.
/mob/living/carbon/basic_revival(var/repair_brain = TRUE)
if(repair_brain && should_have_organ(BP_BRAIN))
repair_brain = FALSE
var/obj/item/organ/internal/brain/brain = internal_organs_by_name[BP_BRAIN]
if(brain.damage > (brain.max_damage/2))
brain.damage = (brain.max_damage/2)
if(brain.status & ORGAN_DEAD)
brain.status &= ~ORGAN_DEAD
START_PROCESSING(SSprocessing, brain)
brain.update_icon()
..(repair_brain)
/mob/living/proc/UpdateDamageIcon()
return
@@ -844,3 +875,11 @@ default behaviour is:
if (size_reagent > src.composition_reagent_quantity)//We take the larger of the two
src.composition_reagent_quantity = size_reagent
#undef PPM
/mob/living/proc/seizure()
if(!paralysis && stat == CONSCIOUS)
visible_message("<span class='danger'>\The [src] starts having a seizure!</span>")
Paralyse(rand(8,16))
make_jittery(rand(150,200))
adjustHalLoss(rand(50,60))

View File

@@ -97,7 +97,7 @@
//var/jetpack = 0
var/obj/item/tank/jetpack/carbondioxide/synthetic/jetpack = null
var/datum/effect/effect/system/ion_trail_follow/ion_trail = null
var/datum/effect_system/sparks/spark_system//So they can initialize sparks whenever/N
var/datum/effect_system/sparks/spark_system //So they can initialize sparks whenever/N
var/jeton = 0
var/killswitch = 0
var/killswitch_time = 60

View File

@@ -409,7 +409,7 @@
break_stuff_probability = 100//Constantly smashing everything nearby
speak_chance = 15
var/idletime
var/focus_time//How long we've focused on this target
var/focus_time //How long we've focused on this target
var/teleport_delay = 60
var/tactical_delay = 3//Procs between shortrange teleports
var/datum/effect_system/sparks/spark_system

View File

@@ -372,12 +372,15 @@
if (W)
W.attack_self(src)
update_inv_l_hand()
else
attack_empty_hand(BP_L_HAND)
else
var/obj/item/W = r_hand
if (W)
W.attack_self(src)
update_inv_r_hand()
return
else
attack_empty_hand(BP_R_HAND)
/mob/verb/memory()
set name = "Notes"
@@ -996,6 +999,22 @@
/mob/proc/embedded_needs_process()
return (embedded.len > 0)
/mob/proc/remove_implant(var/obj/item/implant, var/surgical_removal = FALSE)
if(!LAZYLEN(get_visible_implants(0))) //Yanking out last object - removing verb.
verbs -= /mob/proc/yank_out_object
for(var/obj/item/O in pinned)
if(O == implant)
pinned -= O
if(!pinned.len)
anchored = 0
implant.dropInto(loc)
implant.add_blood(src)
implant.update_icon()
if(istype(implant,/obj/item/implant))
var/obj/item/implant/imp = implant
imp.removed()
. = TRUE
mob/proc/yank_out_object()
set category = "Object"
set name = "Yank out object"

View File

@@ -314,6 +314,12 @@
/obj/item/grab/attack(mob/M, mob/living/user, var/target_zone)
if(!affecting)
return
if(ishuman(user) && affecting == M)
var/mob/living/carbon/human/H = user
if(H.check_psi_grab(src))
return
if(world.time < (last_action + 20))
return

View File

@@ -90,5 +90,3 @@
. = min(., loc.contents_nano_distance(src_object, src))
else
. = min(., shared_living_nano_distance(src_object))
if(. == STATUS_UPDATE && (TK in mutations)) // If we have telekinesis and remain close enough, allow interaction.
return STATUS_INTERACTIVE

View File

@@ -65,23 +65,6 @@
return
/obj/structure/filingcabinet/attack_tk(mob/user)
if(anchored)
attack_self_tk(user)
else
..()
/obj/structure/filingcabinet/attack_self_tk(mob/user)
if(contents.len)
if(prob(40 + contents.len * 5))
var/obj/item/I = pick(contents)
I.forceMove(loc)
if(prob(25))
step_rand(I)
to_chat(user, "<span class='notice'>You pull \a [I] out of [src] at random.</span>")
return
to_chat(user, "<span class='notice'>You find nothing in [src].</span>")
/obj/structure/filingcabinet/Topic(href, href_list)
if(href_list["retrieve"])
usr << browse("", "window=filingcabinet") // Close the menu)
@@ -138,7 +121,7 @@ Important Notes:<BR>
populate()
..()
/obj/structure/filingcabinet/security/attack_tk()
/obj/structure/filingcabinet/security/do_simple_ranged_interaction(var/mob/user)
populate()
..()
@@ -185,6 +168,6 @@ Important Notes:<BR>
populate()
..()
/obj/structure/filingcabinet/medical/attack_tk()
/obj/structure/filingcabinet/medical/do_simple_ranged_interaction(var/mob/user)
populate()
..()

View File

@@ -118,7 +118,7 @@ var/list/possible_cable_coil_colours = list(
alpha = invisibility ? 127 : 255
//Telekinesis has no effect on a cable
/obj/structure/cable/attack_tk(mob/user)
/obj/structure/cable/do_simple_ranged_interaction(var/mob/user)
return
// Items usable on a cable :

View File

@@ -449,8 +449,6 @@
if(prot || (COLD_RESISTANCE in user.mutations))
to_chat(user, "You remove the light [fitting]")
else if(TK in user.mutations)
to_chat(user, "You telekinetically remove the light [fitting].")
else
to_chat(user, "You try to remove the light [fitting], but it's too hot and you don't want to burn your hand.")
return // if burned, don't remove the light
@@ -481,7 +479,7 @@
stat |= MAINT
update()
/obj/machinery/light/attack_tk(mob/user)
/obj/machinery/light/do_simple_ranged_interaction(var/mob/user)
if(status == LIGHT_EMPTY)
to_chat(user, "There is no [fitting] in this light.")
return

View File

@@ -0,0 +1,92 @@
/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/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_armor_check // world.time of last armour check.
var/last_aura_size
var/last_aura_alpha
var/last_aura_color
var/aura_color = "#ff0022"
// Cached powers.
var/list/melee_powers // Powers used in melee range.
var/list/grab_powers // Powers use by using a grab.
var/list/ranged_powers // Powers used at range.
var/list/manifestation_powers // Powers that create an item.
var/list/powers_by_faculty // All powers within a given faculty.
var/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
/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(var/newloc)
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 = 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 = 0
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(var/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(var/mob/_owner)
owner = _owner
START_PROCESSING(SSpsi, src)
/datum/psi_complexus/Destroy()
destroy_aura_image(_aura_image)
STOP_PROCESSING(SSpsi, src)
if(owner)
cancel()
if(owner.client)
owner.client.screen -= ui
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()
. = ..()

View File

@@ -0,0 +1,104 @@
/datum/psi_complexus/proc/cancel()
sound_to(owner, sound('sound/effects/psi/power_fail.ogg'))
if(LAZYLEN(manifested_items))
for(var/thing in manifested_items)
owner.drop_from_inventory(thing)
qdel(thing)
manifested_items = null
/datum/psi_complexus/proc/stunned(var/amount)
var/old_stun = stun
stun = max(stun, amount)
if(amount && !old_stun)
to_chat(owner, "<span class='danger'>Your concentration has been shattered! You cannot focus your psi power!</span>")
ui.update_icon()
cancel()
/datum/psi_complexus/proc/get_armour(var/armourtype)
if(can_use_passive())
last_armor_check = world.time
return round(Clamp(Clamp(4 * rating, 0, 20) * get_rank(SSpsi.armour_faculty_by_type[armourtype]), 0, 100) * (stamina/max_stamina))
else
last_armor_check = 0
return 0
/datum/psi_complexus/proc/get_rank(var/faculty)
return LAZYACCESS(ranks, faculty)
/datum/psi_complexus/proc/set_rank(var/faculty, var/rank, var/defer_update, var/temporary)
if(get_rank(faculty) != rank)
LAZYSET(ranks, faculty, rank)
if(!temporary)
LAZYSET(base_ranks, faculty, rank)
if(!defer_update)
update()
/datum/psi_complexus/proc/set_cooldown(var/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(var/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(var/value = 0, var/check_incapacitated)
. = FALSE
if(isnull(check_incapacitated))
check_incapacitated = (INCAPACITATION_STUNNED|INCAPACITATION_KNOCKOUT)
if(can_use(check_incapacitated))
value = max(1, Ceiling(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/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(var/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 class='danger'><font size=3>Wild energistic feedback blasts across your psyche!</font></span>")
stunned(value * 2)
set_cooldown(value * 100)
if(prob(value*10)) owner.emote("scream")
// Your head asplode.
owner.adjustBrainLoss(value)
owner.adjustHalLoss(value * 25) //Ouch.
if(ishuman(owner))
var/mob/living/carbon/human/pop = owner
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()

View File

@@ -0,0 +1,17 @@
/datum/psi_complexus/proc/check_latency_trigger(var/trigger_strength = 0, var/source, var/redactive = FALSE)
if(!LAZYLEN(latencies) || world.time < next_latency_trigger)
return FALSE
if(!prob(trigger_strength))
next_latency_trigger = world.time + rand(100, 300)
return FALSE
var/faculty = pick(latencies)
var/new_rank = rand(2,5)
owner.set_psi_rank(faculty, new_rank)
var/datum/psionic_faculty/faculty_decl = SSpsi.get_faculty(faculty)
to_chat(owner, span("danger", "You scream internally as your [faculty_decl.name] faculty is forced into operancy by [source]!"))
next_latency_trigger = world.time + rand(600, 1800) * new_rank
if(!redactive) owner.adjustBrainLoss(rand(trigger_strength * 2, trigger_strength * 4))
return TRUE

View File

@@ -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/datum/psionic_faculty/faculty_decl = SSpsi.get_faculty(faculty)
for(var/thing in faculty_decl.powers)
var/datum/psionic_power/power = thing
if(relevant_rank >= power.min_rank)
if(!powers_by_faculty[power.faculty]) powers_by_faculty[power.faculty] = list()
powers_by_faculty[power.faculty] += power
if(power.use_ranged)
if(!ranged_powers[faculty]) ranged_powers[faculty] = list()
ranged_powers[faculty] += power
if(power.use_melee)
if(!melee_powers[faculty]) melee_powers[faculty] = list()
melee_powers[faculty] += power
if(power.use_manifest)
manifestation_powers += power
if(power.use_grab)
if(!grab_powers[faculty]) grab_powers[faculty] = list()
grab_powers[faculty] += power
rebuild_power_cache = FALSE
/datum/psi_complexus/proc/get_powers_by_faculty(var/faculty)
rebuild_power_cache()
return powers_by_faculty[faculty]
/datum/psi_complexus/proc/get_melee_powers(var/faculty)
rebuild_power_cache()
return melee_powers[faculty]
/datum/psi_complexus/proc/get_ranged_powers(var/faculty)
rebuild_power_cache()
return ranged_powers[faculty]
/datum/psi_complexus/proc/get_grab_powers(var/faculty)
rebuild_power_cache()
return grab_powers[faculty]
/datum/psi_complexus/proc/get_manifestations()
rebuild_power_cache()
return manifestation_powers

View File

@@ -0,0 +1,228 @@
/datum/psi_complexus/proc/update(var/force)
set waitfor = FALSE
var/last_rating = rating
var/highest_faculty
var/highest_rank = 0
var/combined_rank = 0
for(var/faculty in ranks)
var/check_rank = get_rank(faculty)
if(check_rank == 1)
LAZYADD(latencies, faculty)
else
if(check_rank <= 0)
ranks -= faculty
LAZYREMOVE(latencies, faculty)
combined_rank += check_rank
if(!highest_faculty || highest_rank < check_rank)
highest_faculty = faculty
highest_rank = check_rank
UNSETEMPTY(latencies)
var/rank_count = max(1, LAZYLEN(ranks))
if(force || last_rating != Ceiling(combined_rank/rank_count))
if(highest_rank <= 1)
if(highest_rank == 0)
qdel(src)
return
else
rebuild_power_cache = TRUE
sound_to(owner, 'sound/effects/psi/power_unlock.ogg')
rating = Ceiling(combined_rank/rank_count)
cost_modifier = 1
if(rating > 1)
cost_modifier -= min(1, max(0.1, (rating-1) / 10))
if(!ui)
ui = new(owner)
if(owner.client)
owner.client.screen += ui
else
if(owner.client)
owner.client.screen |= ui
if(!suppressed && owner.client)
for(var/thing in SSpsi.all_aura_images)
owner.client.images |= thing
var/image/aura_image = get_aura_image()
if(rating >= PSI_RANK_PARAMOUNT) // spooky boosters
aura_color = "#aaffaa"
aura_image.blend_mode = BLEND_SUBTRACT
else
aura_image.blend_mode = BLEND_ADD
if(highest_faculty == PSI_COERCION)
aura_color = "#cc3333"
else if(highest_faculty == PSI_PSYCHOKINESIS)
aura_color = "#3333cc"
else if(highest_faculty == PSI_REDACTION)
aura_color = "#33cc33"
else if(highest_faculty == PSI_ENERGISTICS)
aura_color = "#cccc33"
if(!announced && owner && owner.client && !QDELETED(src))
announced = TRUE
to_chat(owner, "<hr>")
to_chat(owner, span("notice", "<font size = 3>You are <b>psionic</b>, touched by powers beyond understanding.</font>"))
to_chat(owner, span("notice", "<b>Shift-left-click your Psi icon</b> on the bottom right to <b>view a summary of how to use them</b>, or <b>left click</b> it to <b>suppress or unsuppress</b> your psionics. Beware: overusing your gifts can have <b>deadly consequences</b>."))
to_chat(owner, "<hr>")
/datum/psi_complexus/process()
var/update_hud
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
return
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_OPERANT)
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
var/matrix/M = matrix()
if(next_aura_size != 1)
M.Scale(next_aura_size)
animate(get_aura_image(), alpha = next_aura_alpha, transform = M, 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)
if(use_rank >= PSI_RANK_PARAMOUNT)
heal_general = TRUE
heal_poison = TRUE
heal_internal = TRUE
heal_bleeding = TRUE
mend_prob = 50
heal_rate = 7
else if(use_rank == PSI_RANK_GRANDMASTER)
heal_poison = TRUE
heal_internal = TRUE
heal_bleeding = TRUE
mend_prob = 20
heal_rate = 5
else if(use_rank == PSI_RANK_MASTER)
heal_internal = TRUE
heal_bleeding = TRUE
mend_prob = 10
heal_rate = 3
else if(use_rank == PSI_RANK_OPERANT)
heal_bleeding = TRUE
mend_prob = 5
heal_rate = 1
else
return
if(!heal_rate || stamina < heal_rate)
return // Don't backblast from trying to heal ourselves thanks.
if(ishuman(owner))
var/mob/living/carbon/human/H = owner
// Fix some pain.
if(heal_rate > 0)
H.shock_stage = max(0, H.shock_stage - max(1, round(heal_rate/2)))
// Mend internal damage.
if(prob(mend_prob))
// Fix our heart if we're paramount.
if(heal_general && H.is_asystole() && H.should_have_organ(BP_HEART) && spend_power(heal_rate))
H.resuscitate()
// Heal organ damage.
if(heal_internal)
for(var/obj/item/organ/I in H.internal_organs)
if(BP_IS_ROBOTIC(I))
continue
if(I.damage > 0 && spend_power(heal_rate))
I.damage = max(I.damage - heal_rate, 0)
if(prob(25))
to_chat(H, span("notice", "Your innards itch as your autoredactive faculty mends your [I.name]."))
return
// Heal broken bones.
if(H.bad_external_organs.len)
for(var/obj/item/organ/external/E in H.bad_external_organs)
if(BP_IS_ROBOTIC(E))
continue
if(heal_internal && (E.status & ORGAN_BROKEN) && E.damage < (E.min_broken_damage * 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.total_radiation && spend_power(heal_rate))
if(prob(25))
to_chat(owner, span("notice", "Your autoredactive faculty repairs some of the radiation damage to your body."))
owner.total_radiation = max(0, owner.total_radiation - heal_rate)
return
if(owner.getCloneLoss() && spend_power(heal_rate))
if(prob(25))
to_chat(owner, span("notice", "Your autoredactive faculty stitches together some of your mangled DNA."))
owner.adjustCloneLoss(-heal_rate)
return
// Heal everything left.
if(heal_general && prob(mend_prob) && (owner.getBruteLoss() || owner.getFireLoss() || owner.getOxyLoss()) && spend_power(heal_rate))
owner.adjustBruteLoss(-(heal_rate))
owner.adjustFireLoss(-(heal_rate))
owner.adjustOxyLoss(-(heal_rate))
if(prob(25))
to_chat(owner, span("notice", "Your skin crawls as your autoredactive faculty heals your body."))

View File

@@ -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_PARAMOUNT
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_OPERANT
boosted_psipower = 50
/obj/item/clothing/head/helmet/space/psi_amp/Initialize()
. = ..()
verbs += /obj/item/clothing/head/helmet/space/psi_amp/proc/integrate
/obj/item/clothing/head/helmet/space/psi_amp/attack_self(var/mob/user)
if(operating)
return
if(!canremove)
deintegrate()
return
var/mob/living/carbon/human/H = loc
if(istype(H) && H.head == src)
integrate()
return
var/choice = input("Select a brainboard to install or remove.","Psionic Amplifier") as null|anything in SSpsi.faculties_by_name
if(!choice)
return
var/removed
var/slots_left = max_boosted_faculties - LAZYLEN(boosted_faculties)
var/datum/psionic_faculty/faculty = SSpsi.get_faculty(choice)
if(faculty.id in boosted_faculties)
LAZYREMOVE(boosted_faculties, faculty.id)
removed = TRUE
else
if(slots_left <= 0)
to_chat(user, span("warning", "There are no slots left to install brainboards into."))
return
LAZYADD(boosted_faculties, faculty.id)
UNSETEMPTY(boosted_faculties)
slots_left = max_boosted_faculties - LAZYLEN(boosted_faculties)
to_chat(user, span("notice", "You [removed ? "remove" : "install"] the [choice] brainboard [removed ? "from" : "in"] \the [src]. There [slots_left!=1 ? "are" : "is"] [slots_left] slot\s left."))
/obj/item/clothing/head/helmet/space/psi_amp/proc/deintegrate()
set name = "Remove Psi-Amp"
set desc = "Removes your psi-amp."
set category = "Abilities"
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_PSYCHOKINESIS, PSI_REDACTION, PSI_ENERGISTICS))
if(faculty in boosted_faculties)
H.set_psi_rank(faculty, boosted_rank, take_larger = TRUE, temporary = TRUE)
else
H.set_psi_rank(faculty, unboosted_rank, take_larger = TRUE, temporary = TRUE)
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(0.5, 0.1, 3, 2, l_color = "#880000")

View File

@@ -0,0 +1,51 @@
/obj/item/psychic_power
name = "psychic power"
icon = 'icons/obj/psychic_powers.dmi'
flags = 0
simulated = 1
anchored = 1
var/maintain_cost = 3
var/mob/living/owner
/obj/item/psychic_power/New(var/mob/living/_owner)
owner = _owner
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 5
/obj/item/psychic_power/attack_self(var/mob/user)
sound_to(owner, 'sound/effects/psi/power_fail.ogg')
user.drop_from_inventory(src)
/obj/item/psychic_power/dropped()
..()
qdel(src)
/obj/item/psychic_power/process()
if(istype(owner))
owner.psi.spend_power(maintain_cost)
if(!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
qdel(src)

View File

@@ -0,0 +1,26 @@
/obj/item/psychic_power/psiblade
name = "psychokinetic slash"
force = 10
sharp = 1
edge = 1
maintain_cost = 1
icon_state = "psiblade_short"
hitsound = 'sound/weapons/psisword.ogg'
/obj/item/psychic_power/psiblade/dropped(var/mob/living/user)
playsound(loc, 'sound/effects/psi/power_fail.ogg', 30, 1)
QDEL_IN(src, 1)
/obj/item/psychic_power/psiblade/master
force = 20
maintain_cost = 2
/obj/item/psychic_power/psiblade/master/grand
force = 30
maintain_cost = 3
icon_state = "psiblade_long"
/obj/item/psychic_power/psiblade/master/grand/paramount // Silly typechecks because rewriting old interaction code is outside of scope.
force = 50
maintain_cost = 4
icon_state = "psiblade_long"

View File

@@ -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 class='notice'>You begin emulating \a [lowertext(emulating)].</span>")
sound_to(owner, 'sound/effects/psi/power_fabrication.ogg')

View File

@@ -0,0 +1,96 @@
/obj/item/psychic_power/telekinesis
name = "telekinetic grip"
maintain_cost = 3
icon_state = "telekinesis"
var/atom/movable/focus
/obj/item/psychic_power/telekinesis/Destroy()
focus = null
. = ..()
/obj/item/psychic_power/telekinesis/process()
if(!focus || !istype(focus.loc, /turf) || get_dist(get_turf(focus), get_turf(owner)) > owner.psi.get_rank(PSI_PSYCHOKINESIS))
owner.drop_from_inventory(src)
return
. = ..()
/obj/item/psychic_power/telekinesis/proc/set_focus(var/atom/movable/_focus)
if(!_focus.simulated || !istype(_focus.loc, /turf))
return FALSE
var/check_paramount
if(ismob(_focus))
var/mob/victim = _focus
check_paramount = (victim.mob_size >= MOB_MEDIUM)
else if(isobj(_focus))
var/obj/thing = _focus
check_paramount = (thing.w_class >= 5)
else
return FALSE
if(_focus.anchored || (check_paramount && owner.psi.get_rank(PSI_PSYCHOKINESIS) < PSI_RANK_PARAMOUNT))
focus = _focus
. = attack_self(owner)
if(!.)
to_chat(owner, span("warning", "\The [_focus] is too hefty for you to get a mind-grip on."))
qdel(src)
return FALSE
focus = _focus
overlays.Cut()
var/image/I = image(icon = focus.icon, icon_state = focus.icon_state)
I.color = focus.color
I.overlays = focus.overlays
overlays += I
return TRUE
/obj/item/psychic_power/telekinesis/attack_self(var/mob/user)
user.visible_message(span("notice", "\The [user] makes a strange gesture."))
sparkle()
return focus.do_simple_ranged_interaction(user)
/obj/item/psychic_power/telekinesis/afterattack(var/atom/target, var/mob/living/user, var/proximity)
if(!target || !user || (isobj(target) && !isturf(target.loc)) || !user.psi || !user.psi.can_use() || !user.psi.spend_power(5))
return
user.setClickCooldown(DEFAULT_ATTACK_COOLDOWN)
user.psi.set_cooldown(5)
var/distance = get_dist(get_turf(user), get_turf(focus ? focus : target))
if(distance > user.psi.get_rank(PSI_PSYCHOKINESIS))
to_chat(user, span("warning", "Your telekinetic power won't reach that far."))
return FALSE
if(target == focus)
attack_self(user)
else
user.visible_message(span("danger", "\The [user] gestures sharply!"))
sparkle()
if(!istype(target, /turf) && istype(focus,/obj/item) && target.Adjacent(focus))
var/obj/item/I = focus
var/resolved = target.attackby(I, user, user:get_organ_target())
if(!resolved && target && I)
I.afterattack(target,user,1) // for splashing with beakers
else
if(!focus.anchored)
var/user_rank = owner.psi.get_rank(PSI_PSYCHOKINESIS)
focus.throw_at(target, user_rank*2, user_rank*10, owner)
sleep(1)
sparkle()
/obj/item/psychic_power/telekinesis/proc/sparkle()
set waitfor = 0
if(focus)
var/obj/effect/overlay/O = new /obj/effect/overlay(get_turf(focus))
O.name = "sparkles"
O.anchored = 1
O.density = 0
O.layer = FLY_LAYER
O.set_dir(pick(cardinal))
O.icon = 'icons/effects/effects.dmi'
O.icon_state = "nothing"
flick("empdisable",O)
sleep(5)
qdel(O)

View File

@@ -0,0 +1,22 @@
/datum/event/psi
startWhen = 30
endWhen = 120
/datum/event/psi/announce()
priority_announcement.Announce( \
"A localized disruption within the neighboring psionic continua has been detected. All psi-operant crewmembers \
are advised to cease any sensitive activities and report to medical personnel in case of damage.", \
"Jargon Federation Observation Probe TC-203 Sensor Array", new_sound = 'sound/misc/announcements/nightlight_old.ogg')
/datum/event/psi/end()
priority_announcement.Announce( \
"The psi-disturbance has ended and baseline normality has been re-asserted. \
Anything you still can't cope with is therefore your own problem.", \
"Jargon Federation Observation Probe TC-203 Sensor Array", new_sound = 'sound/misc/announcements/nightlight_old.ogg')
/datum/event/psi/tick()
for(var/thing in SSpsi.processing)
apply_psi_effect(thing)
/datum/event/psi/proc/apply_psi_effect(var/datum/psi_complexus/psi)
return

View File

@@ -0,0 +1,61 @@
/datum/event/minispasm
startWhen = 60
endWhen = 90
var/static/list/psi_operancy_messages = list(
"There's something in your skull!",
"Something is eating your thoughts!",
"You can feel your brain being rewritten!",
"Something is crawling over your frontal lobe!",
"<b>THE SIGNAL THE SIGNAL THE SIGNAL THE SIGNAL THE</b>",
"Something is drilling through your skull!",
"Your head feels like it's going to implode!",
"Thousands of ants are tunneling in your head!"
)
/datum/event/minispasm/announce()
command_announcement.Announce( \
"PRIORITY ALERT: SIGMA-[rand(50,80)] NON-STANDARD PSIONIC SIGNAL-WAVE TRANSMISSION DETECTED - 97% MATCH, NON-VARIANT \
SIGNAL SOURCE TRIANGULATED TO DISTANT SITE: All personnel are advised to avoid \
exposure to active audio transmission equipment including radio headsets and intercoms \
for the duration of the signal broadcast.", \
"Jargon Federation Observation Probe TC-203 Sensor Array", new_sound = 'sound/misc/announcements/security_level_old.ogg')
/datum/event/minispasm/start()
var/list/victims = list()
for(var/obj/item/device/radio/radio in 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(var/mob/living/victim, var/obj/item/device/radio/source)
set waitfor = 0
if(victim.psi)
playsound(source, 'sound/effects/narsie.ogg', 75) //LOUD AS FUCK BOY
to_chat(victim, span("danger", "A hauntingly familiar sound hisses from \icon[source] \the [source], and your vision flickers!"))
victim.psi.backblast(rand(5,15))
victim.Paralyse(5)
victim.make_jittery(100)
else
to_chat(victim, span("danger", "An indescribable, brain-tearing sound hisses from \icon[source] \the [source], and you collapse in a seizure!"))
victim.seizure()
var/new_latencies = rand(2,4)
var/list/faculties = list(PSI_COERCION, PSI_REDACTION, PSI_ENERGISTICS, PSI_PSYCHOKINESIS)
for(var/i = 1 to new_latencies)
to_chat(victim, span("danger", "<font size = 3>[pick(psi_operancy_messages)]</font>"))
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, "a psionic scream", redactive = TRUE)
/datum/event/minispasm/end()
command_announcement.Announce( \
"PRIORITY ALERT: SIGNAL BROADCAST HAS CEASED. Personnel are cleared to resume use of non-hardened radio transmission equipment. Have a nice day.", \
"Jargon Federation Observation Probe TC-203 Sensor Array", new_sound = 'sound/misc/announcements/nightlight_old.ogg')

View File

@@ -0,0 +1,20 @@
/datum/event/psi/balm
var/static/list/balm_messages = list(
"A soothing balm washes over your psyche.",
"For a moment, you can hear a distant, familiar voice singing a quiet lullaby.",
"A sense of peace and comfort falls over you like a warm blanket."
)
/datum/event/psi/balm/apply_psi_effect(var/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", "<i>[pick(balm_messages)]</i>"))

View File

@@ -0,0 +1,17 @@
/datum/event/psi/wail
var/static/list/whine_messages = list(
"A nerve-tearing psychic whine intrudes on your thoughts.",
"A horrible, distracting humming sound breaks your train of thought.",
"Your head aches as a psychic wail intrudes on your psyche."
)
/datum/event/psi/wail/apply_psi_effect(var/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 class='notice'><i>[pick(whine_messages)]</i></span>")

View File

@@ -0,0 +1,11 @@
/datum/psionic_faculty
var/id
var/name
var/associated_intent
var/list/armour_types = list()
var/list/powers = list()
/datum/psionic_faculty/New()
..()
for(var/atype in armour_types)
SSpsi.armour_faculty_by_type[atype] = id

View File

@@ -0,0 +1,37 @@
/datum/psionic_power
var/name // Name. If null, psipower won't be generated on roundstart.
var/faculty // Associated psi faculty.
var/min_rank // Minimum psi rank to use this power.
var/cost // Base psi stamina cost for using this power.
var/cooldown // Deciseconds cooldown after using this power.
var/admin_log = TRUE // Whether or not using this power prints an admin attack log.
var/use_ranged // This power functions from a distance.
var/use_melee // This power functions at melee range.
var/use_grab // This power has a variant invoked via grab.
var/use_manifest // This power manifests an item in the user's hands.
var/use_description // A short description of how to use this power, shown via assay.
// A sound effect to play when the power is used.
var/use_sound = 'sound/effects/psi/power_used.ogg'
/datum/psionic_power/proc/invoke(var/mob/living/user, var/atom/target)
if(!user.psi)
return FALSE
if(faculty && min_rank)
var/user_rank = user.psi.get_rank(faculty)
if(user_rank < min_rank)
return FALSE
if(cost && !user.psi.spend_power(cost))
return FALSE
return TRUE
/datum/psionic_power/proc/handle_post_power(var/mob/living/user, var/atom/target)
if(cooldown)
user.psi.set_cooldown(cooldown)
if(admin_log && ismob(user) && ismob(target))
admin_attack_log(user, target, "Used psipower ([name])", "Was subjected to a psipower ([name])", "used a psipower ([name]) on")
if(use_sound)
playsound(user.loc, use_sound, 75)

View File

@@ -0,0 +1,332 @@
/datum/psionic_faculty/coercion
id = PSI_COERCION
name = "Coercion"
associated_intent = I_DISARM
/datum/psionic_power/coercion
faculty = PSI_COERCION
/datum/psionic_power/coercion/invoke(var/mob/living/user, var/mob/living/target)
if (!istype(target))
to_chat(user, span("warning", "You cannot mentally attack \the [target]."))
return FALSE
. = ..()
/datum/psionic_power/coercion/blindstrike
name = "Blindstrike"
cost = 8
cooldown = 120
use_ranged = TRUE
use_melee = TRUE
min_rank = PSI_RANK_GRANDMASTER
use_description = "Target the eyes or mouth on disarm intent and click anywhere to use a radial attack that blinds, deafens and disorients everyone near you."
/datum/psionic_power/coercion/blindstrike/invoke(var/mob/living/user, var/mob/living/target)
if(user.zone_sel.selecting != "mouth" && user.zone_sel.selecting != BP_EYES)
return FALSE
. = ..()
if(.)
user.visible_message(span("danger", "\The [user] suddenly throws back their head, as though screaming silently!"))
to_chat(user, span("danger", "You strike at all around you with a deafening psionic scream!"))
for(var/mob/living/M in orange(user, user.psi.get_rank(PSI_COERCION)))
if(M == user)
continue
if(prob(60) && iscarbon(M))
var/mob/living/carbon/C = M
if(C.can_feel_pain())
M.emote("scream")
to_chat(M, span("danger", "Your senses are blasted into oblivion by a psionic scream!"))
M.eye_blind = max(M.eye_blind,3)
M.ear_deaf = max(M.ear_deaf,6)
M.confused = rand(3,8)
return TRUE
/datum/psionic_power/coercion/mindread
name = "Read Mind"
cost = 25
cooldown = 250 //It should take a WHILE to be able to use this again.
use_melee = TRUE
min_rank = PSI_RANK_OPERANT
use_description = "Target the head on disarm intent at melee range to attempt to read a victim's surface thoughts."
/datum/psionic_power/coercion/mindread/invoke(var/mob/living/user, var/mob/living/target)
if(!isliving(target) || !istype(target) || user.zone_sel.selecting != BP_HEAD)
return FALSE
. = ..()
if(!.)
return
if(target.stat == DEAD || (target.status_flags & FAKEDEATH) || !target.client)
to_chat(user, span("warning", "\The [target] is in no state for a mind-ream."))
return TRUE
user.visible_message(span("warning", "\The [user] touches \the [target]'s temple..."))
var/question = input(user, "Say something?", "Read Mind", "Penny for your thoughts?") as null|text
if(!question || user.incapacitated() || !do_after(user, 20))
return TRUE
var/started_mindread = world.time
to_chat(user, span("notice", "<b>You dip your mentality into the surface layer of \the [target]'s mind, seeking an answer: <i>[question]</i></b>"))
to_chat(target, span("notice", "<b>Your mind is compelled to answer: <i>[question]</i></b>"))
var/answer = input(target, question, "Read Mind") as null|text
if(!answer || world.time > started_mindread + 25 SECONDS || user.stat != CONSCIOUS || target.stat == DEAD)
to_chat(user, span("notice", "<b>You receive nothing useful from \the [target].</b>"))
else
to_chat(user, span("notice", "<b>You skim thoughts from the surface of \the [target]'s mind: <i>[answer]</i></b>"))
msg_admin_attack("[key_name(user)] read mind of [key_name(target)] with question \"[question]\" and [answer?"got answer \"[answer]\".":"got no answer."]")
return TRUE
/datum/psionic_power/coercion/agony
name = "Agony"
cost = 8
cooldown = 50
use_melee = TRUE
min_rank = PSI_RANK_MASTER
use_description = "Target the chest or groin on disarm intent to use a melee attack equivalent to a strike from a stun baton."
/datum/psionic_power/coercion/agony/invoke(var/mob/living/user, var/mob/living/target)
if(!istype(target))
return FALSE
if(user.zone_sel.selecting != BP_CHEST && user.zone_sel.selecting != BP_GROIN)
return FALSE
. = ..()
if(.)
user.visible_message("<span class='danger'>\The [target] has been struck by \the [user]!</span>")
playsound(user.loc, 'sound/weapons/Egloves.ogg', 50, 1, -1)
target.stun_effect_act(0, 60, user.zone_sel.selecting)
return TRUE
/datum/psionic_power/coercion/spasm
name = "Spasm"
cost = 15
cooldown = 100
use_melee = TRUE
use_ranged = TRUE
min_rank = PSI_RANK_MASTER
use_description = "Target the arms or hands on disarm intent to use a ranged attack that may rip the weapons away from the target."
/datum/psionic_power/coercion/spasm/invoke(var/mob/living/user, var/mob/living/carbon/human/target)
if(!istype(target))
return FALSE
if(!(user.zone_sel.selecting in list(BP_L_ARM, BP_R_ARM, BP_L_HAND, BP_R_HAND)))
return FALSE
. = ..()
if(.)
to_chat(user, "<span class='danger'>You lash out, stabbing into \the [target] with a lance of psi-power.</span>")
to_chat(target, "<span class='danger'>The muscles in your arms cramp horrendously!</span>")
if(prob(75))
target.emote("scream")
if(prob(75) && target.l_hand && target.l_hand.simulated && target.unEquip(target.l_hand))
target.visible_message("<span class='danger'>\The [target] drops what they were holding as their left hand spasms!</span>")
if(prob(75) && target.r_hand && target.r_hand.simulated && target.unEquip(target.r_hand))
target.visible_message("<span class='danger'>\The [target] drops what they were holding as their right hand spasms!</span>")
return TRUE
/datum/psionic_power/coercion/mindslave
name = "Mindslave"
cost = 28
cooldown = 200
use_grab = TRUE
min_rank = PSI_RANK_PARAMOUNT
use_description = "Grab a victim, target the eyes, then use the grab on them while on disarm intent, in order to convert them into a loyal mind-slave. The process takes some time, and failure is punished harshly."
/datum/psionic_power/coercion/mindslave/invoke(var/mob/living/user, var/mob/living/target)
if(!istype(target) || user.zone_sel.selecting != BP_EYES)
return FALSE
. = ..()
if(.)
if(target.stat == DEAD || (target.status_flags & FAKEDEATH))
to_chat(user, "<span class='warning'>\The [target] is dead!</span>")
return TRUE
if(!target.mind || !target.key)
to_chat(user, "<span class='warning'>\The [target] is mindless!</span>")
return TRUE
user.visible_message("<span class='danger'><i>\The [user] seizes the head of \the [target] in both hands...</i></span>")
to_chat(user, "<span class='warning'>You plunge your mentality into that of \the [target]...</span>")
to_chat(target, "<span class='danger'>Your mind is invaded by the presence of \the [user]! They are trying to make you a slave!</span>")
if(!do_after(user, target.stat == CONSCIOUS ? 80 : 40, target, 0, 1))
user.psi.backblast(rand(10,25))
return TRUE
to_chat(user, "<span class='danger'>You sear through \the [target]'s neurons, reshaping as you see fit and leaving them subservient to your will!</span>")
to_chat(target, "<span class='danger'>Your defenses have eroded away and \the [user] has made you their mindslave.</span>")
thralls.add_antagonist(target.mind, TRUE, TRUE, FALSE, TRUE, TRUE)
return TRUE
/datum/psionic_power/coercion/assay
name = "Assay"
cost = 15
cooldown = 100
use_grab = TRUE
min_rank = PSI_RANK_OPERANT
use_description = "Grab a patient, target the head, then use the grab on them while on disarm intent, in order to perform a deep coercive-redactive probe of their psionic potential."
/datum/psionic_power/coercion/assay/invoke(var/mob/living/user, var/mob/living/target)
if(user.zone_sel.selecting != BP_HEAD)
return FALSE
. = ..()
if(.)
user.visible_message(span("warning", "\The [user] holds the head of \the [target] in both hands..."))
to_chat(user, span("notice", "You insinuate your mentality into that of \the [target]..."))
to_chat(target, span("warning", "Your persona is being probed by the psychic lens of \the [user]."))
if(!do_after(user, (target.stat == CONSCIOUS ? 50 : 25), target, 0, 1))
user.psi.backblast(rand(5,10))
return TRUE
to_chat(user, span("notice", "You retreat from \the [target], holding your new knowledge close."))
to_chat(target, span("danger", "Your mental complexus is laid bare to judgement of \the [user]."))
target.show_psi_assay(user)
return TRUE
/datum/psionic_power/coercion/focus
name = "Focus"
cost = 10
cooldown = 80
use_grab = TRUE
min_rank = PSI_RANK_MASTER
use_description = "Grab a patient, target the mouth, then use the grab on them while on disarm intent, in order to cure ailments of the mind."
/datum/psionic_power/coercion/focus/invoke(var/mob/living/user, var/mob/living/target)
if(user.zone_sel.selecting != "mouth")
return FALSE
. = ..()
if(.)
user.visible_message(span("warning", "\The [user] holds the head of \the [target] in both hands..."))
to_chat(user, span("notice", "You probe \the [target]'s mind for various ailments.."))
to_chat(target, span("warning", "Your mind is being cleansed of ailments by \the [user]."))
if(!do_after(user, (target.stat == CONSCIOUS ? 50 : 25), target, 0, 1))
user.psi.backblast(rand(5,10))
return TRUE
to_chat(user, span("warning", "You clear \the [target]'s mind of ailments."))
to_chat(target, span("warning", "Your mind is cleared of ailments."))
var/coercion_rank = user.psi.get_rank(PSI_COERCION)
if(coercion_rank >= PSI_RANK_GRANDMASTER)
target.AdjustParalysis(-1)
target.drowsyness = 0
if(istype(target, /mob/living/carbon))
var/mob/living/carbon/M = target
M.hallucination = max(M.hallucination, 10)
return TRUE
/datum/psionic_power/coercion/commune
name = "Commune"
cost = 10
cooldown = 80
use_melee = TRUE
use_ranged = TRUE
min_rank = PSI_RANK_OPERANT
use_description = "Target the mouth and click on a creature on disarm intent to psionically send them a message."
/datum/psionic_power/coercion/commune/invoke(var/mob/living/user, var/mob/living/target)
if(user.zone_sel.selecting != "mouth")
return FALSE
. = ..()
if(.)
user.visible_message("<i><span class='notice'>[user] touches their fingers to their temple.</span></i>")
var/text = input("What would you like to say?", "Speak to creature", null, null)
text = sanitize(text)
if(!text)
return
if(target.stat == DEAD)
to_chat(user,"<span class='cult'>Not even a psion of your level can speak to the dead.</span>")
return
if (target.isSynthetic())
to_chat(user,"<span class='warning'>This can only be used on living organisms.</span>")
return
if (target.is_diona())
to_chat(user,"<span class='alium'>The creature's mind is incompatible, formless.</span>")
return
if (isvaurca(target))
to_chat (user, "<span class='cult'>You feel your thoughts pass right through a mind empty of psychic energy.</span>")
return
log_say("[key_name(user)] communed to [key_name(target)]: [text]",ckey=key_name(src))
for (var/mob/M in player_list)
if (istype(M, /mob/abstract/new_player))
continue
else if(M.stat == DEAD && M.client.prefs.toggles & CHAT_GHOSTEARS)
to_chat(M,"<span class='notice'>[user] psionically says to [target]:</span> [text]")
var/mob/living/carbon/human/H = target
if (target.can_commune())
to_chat(H,"<b>You instinctively sense [user] sending their thoughts into your mind, hearing:</b> [text]")
else if(prob(25) && (target.mind && target.mind.assigned_role=="Chaplain"))
to_chat(H,"<b>You sense [user]'s psyche enter your mind, whispering quietly:</b> [text]")
else
to_chat(H,"<b>You feel something crawl behind your eyes, hearing:</b> [text]")
if(istype(H))
if (H.can_commune())
return
if(prob(10) && !(H.species.flags & NO_BLOOD))
to_chat(H,"<span class='warning'>Your nose begins to bleed...</span>")
H.drip(3)
else if(prob(25) && (H.can_feel_pain()))
to_chat(H,"<span class='warning'>Your head hurts...</span>")
else if(prob(50))
to_chat(H,"<span class='warning'>Your mind buzzes...</span>")
/datum/psionic_power/coercion/psiping
name = "Psi-ping"
cost = 30
cooldown = 250
use_melee = TRUE
min_rank = PSI_RANK_OPERANT
use_description = "Click on yourself with an empty hand on disarm intent to detect nearby psionic signatures."
/datum/psionic_power/coercion/psiping/invoke(var/mob/living/user, var/mob/living/target)
if((target && user != target))
return FALSE
. = ..()
if(.)
to_chat(user, "<span class='notice'>You take a moment to tune into the local Nlom...</span>")
if(!do_after(user, 3 SECONDS))
return
var/list/dirs = list()
for(var/mob/living/L in range(20))
var/turf/T = get_turf(L)
if(!T || L == user || L.stat == DEAD || L.isSynthetic() || L.is_diona() || isvaurca(L) || L.invisibility == INVISIBILITY_LEVEL_TWO)
continue
var/image/ping_image = image(icon = 'icons/effects/effects.dmi', icon_state = "sonar_ping", loc = user)
ping_image.plane = LIGHTING_LAYER+1
ping_image.layer = LIGHTING_LAYER+1
ping_image.pixel_x = (T.x - user.x) * WORLD_ICON_SIZE
ping_image.pixel_y = (T.y - user.y) * WORLD_ICON_SIZE
user << ping_image
addtimer(CALLBACK(GLOBAL_PROC, /proc/qdel, ping_image), 8)
var/direction = num2text(get_dir(user, L))
var/dist
if(text2num(direction))
switch(get_dist(user, L) / user.client.view)
if(0 to 0.2)
dist = "very close"
if(0.2 to 0.4)
dist = "close"
if(0.4 to 0.6)
dist = "a little ways away"
if(0.6 to 0.8)
dist = "farther away"
else
dist = "far away"
else
dist = "on top of you"
LAZYINITLIST(dirs[direction])
dirs[direction][dist] += 1
for(var/d in dirs)
var/list/feedback = list()
for(var/dst in dirs[d])
feedback += "[dirs[d][dst]] psionic signature\s [dst],"
if(feedback.len > 1)
feedback[feedback.len - 1] += " and"
to_chat(user, span("notice", "You sense " + jointext(feedback, " ") + " towards the [dir2text(text2num(d))]."))
if(!length(dirs))
to_chat(user, span("notice", "You detect no psionic signatures but your own."))

View File

@@ -0,0 +1,113 @@
/datum/psionic_faculty/energistics
id = PSI_ENERGISTICS
name = "Energistics"
associated_intent = I_HURT
armour_types = list("bomb", "laser", "energy")
/datum/psionic_power/energistics
faculty = PSI_ENERGISTICS
/datum/psionic_power/energistics/disrupt
name = "Disrupt"
cost = 10
cooldown = 100
use_melee = TRUE
min_rank = PSI_RANK_MASTER
use_description = "Target the head, eyes or mouth while on harm intent to use a melee attack that causes a localized electromagnetic pulse."
/datum/psionic_power/energistics/disrupt/invoke(var/mob/living/user, var/mob/living/target)
if(user.zone_sel.selecting != BP_HEAD && user.zone_sel.selecting != BP_EYES && user.zone_sel.selecting != "mouth")
return FALSE
if(istype(target, /turf))
return FALSE
. = ..()
if(.)
user.visible_message("<span class='danger'>\The [user] releases a gout of crackling static and arcing lightning over \the [target]!</span>")
empulse(target, 0, 1)
return TRUE
/datum/psionic_power/energistics/electrocute
name = "Electrocute"
cost = 15
cooldown = 25
use_melee = TRUE
min_rank = PSI_RANK_GRANDMASTER
use_description = "Target the chest or groin while on harm intent to use a melee attack that electrocutes a victim."
/datum/psionic_power/energistics/electrocute/invoke(var/mob/living/user, var/mob/living/target)
if(user.zone_sel.selecting != BP_CHEST && user.zone_sel.selecting != BP_GROIN)
return FALSE
if(istype(target, /turf))
return FALSE
. = ..()
if(.)
user.visible_message("<span class='danger'>\The [user] sends a jolt of electricity arcing into \the [target]!</span>")
if(istype(target))
target.electrocute_act(rand(15,45), user, 1, user.zone_sel.selecting)
return TRUE
else if(istype(target, /atom))
var/obj/item/cell/charging_cell = target.get_cell()
if(istype(charging_cell))
charging_cell.give(rand(15,45))
return TRUE
/datum/psionic_power/energistics/zorch
name = "Zorch"
cost = 20
cooldown = 20
use_ranged = TRUE
min_rank = PSI_RANK_MASTER
use_description = "Use this ranged laser attack while on harm intent. Your mastery of Energistics will determine how powerful the laser is. Be wary of overuse, and try not to fry your own brain."
/datum/psionic_power/energistics/zorch/invoke(var/mob/living/user, var/mob/living/target)
. = ..()
if(.)
user.visible_message("<span class='danger'>\The [user]'s eyes flare with light!</span>")
var/user_rank = user.psi.get_rank(faculty)
var/obj/item/projectile/pew
var/pew_sound
switch(user_rank)
if(PSI_RANK_PARAMOUNT)
pew = new /obj/item/projectile/beam/heavylaser(get_turf(user))
pew.name = "gigawatt mental laser"
pew_sound = 'sound/weapons/lasercannonfire.ogg'
if(PSI_RANK_GRANDMASTER)
pew = new /obj/item/projectile/beam/midlaser(get_turf(user))
pew.name = "megawatt mental laser"
pew_sound = 'sound/weapons/Laser.ogg'
if(PSI_RANK_MASTER)
pew = new /obj/item/projectile/beam/stun(get_turf(user))
pew.name = "mental laser"
pew_sound = 'sound/weapons/Taser.ogg'
if(istype(pew))
playsound(pew.loc, pew_sound, 25, 1)
pew.original = target
pew.starting = get_turf(user)
pew.shot_from = user
pew.launch_projectile(target)
return TRUE
/datum/psionic_power/energistics/spark
name = "Spark"
cost = 1
cooldown = 1
use_melee = TRUE
min_rank = PSI_RANK_OPERANT
use_description = "Target a non-living target in melee range on harm intent to cause some sparks to appear. This can light fires."
/datum/psionic_power/energistics/spark/invoke(var/mob/living/user, var/mob/living/target)
if(isnull(target) || istype(target)) return FALSE
. = ..()
if(.)
if(istype(target,/obj/item/clothing/mask/smokable/cigarette))
var/obj/item/clothing/mask/smokable/cigarette/S = target
S.light("[user] snaps \his fingers and \the [S.name] lights up.")
playsound(S.loc, "sparks", 50, 1)
else
var/datum/effect_system/sparks/spark_system
spark_system = bind_spark(src, 3)
spark_system.queue()
return TRUE

View File

@@ -0,0 +1,92 @@
/datum/psionic_faculty/psychokinesis
id = PSI_PSYCHOKINESIS
name = "Psychokinesis"
associated_intent = I_GRAB
armour_types = list("melee", "bullet")
/datum/psionic_power/psychokinesis
faculty = PSI_PSYCHOKINESIS
use_manifest = TRUE
use_sound = null
/datum/psionic_power/psychokinesis/psiblade
name = "Psiblade"
cost = 10
cooldown = 30
min_rank = PSI_RANK_OPERANT
use_description = "Click on or otherwise activate an empty hand while on harm intent to manifest a psychokinetic cutting blade. The power the blade will vary based on your mastery of the faculty."
admin_log = FALSE
/datum/psionic_power/psychokinesis/psiblade/invoke(var/mob/living/user, var/mob/living/target)
if((target && user != target) || user.a_intent != I_HURT)
return FALSE
. = ..()
if(.)
switch(user.psi.get_rank(faculty))
if(PSI_RANK_PARAMOUNT)
return new /obj/item/psychic_power/psiblade/master/grand/paramount(user, user)
if(PSI_RANK_GRANDMASTER)
return new /obj/item/psychic_power/psiblade/master/grand(user, user)
if(PSI_RANK_MASTER)
return new /obj/item/psychic_power/psiblade/master(user, user)
else
return new /obj/item/psychic_power/psiblade(user, user)
/datum/psionic_power/psychokinesis/tinker
name = "Tinker"
cost = 5
cooldown = 10
min_rank = PSI_RANK_MASTER
use_description = "Click on or otherwise activate an empty hand while on help intent to manifest a psychokinetic tool. Use it in-hand to switch between tool types."
admin_log = FALSE
/datum/psionic_power/psychokinesis/tinker/invoke(var/mob/living/user, var/mob/living/target)
if((target && user != target) || user.a_intent != I_HELP)
return FALSE
. = ..()
if(.)
return new /obj/item/psychic_power/tinker(user)
/datum/psionic_power/psychokinesis/telekinesis
name = "Telekinesis"
cost = 5
cooldown = 10
use_ranged = TRUE
use_manifest = FALSE
min_rank = PSI_RANK_GRANDMASTER
use_description = "Click on a distant target while on grab intent to manifest a psychokinetic grip. Use it manipulate objects at a distance."
admin_log = FALSE
use_sound = 'sound/effects/psi/power_used.ogg'
var/global/list/valid_machine_types = list(
/obj/machinery/door
)
/datum/psionic_power/psychokinesis/telekinesis/invoke(var/mob/living/user, var/mob/living/target)
if(user.a_intent != I_GRAB)
return FALSE
. = ..()
if(.)
var/distance = get_dist(user, target)
if(distance > user.psi.get_rank(PSI_PSYCHOKINESIS))
to_chat(user, "<span class='warning'>Your telekinetic power won't reach that far.</span>")
return FALSE
if(istype(target, /mob) || istype(target, /obj))
var/obj/item/psychic_power/telekinesis/tk = new(user)
if(tk.set_focus(target))
tk.sparkle()
user.visible_message("<span class='notice'>\The [user] reaches out.</span>")
return tk
else if(istype(target, /obj/structure))
user.visible_message("<span class='notice'>\The [user] makes a strange gesture.</span>")
var/obj/O = target
O.attack_hand(user)
return TRUE
else if(istype(target, /obj/machinery))
for(var/mtype in valid_machine_types)
if(istype(target, mtype))
var/obj/machinery/machine = target
machine.attack_hand(user)
return TRUE
return FALSE

View File

@@ -0,0 +1,186 @@
/datum/psionic_faculty/redaction
id = PSI_REDACTION
name = "Redaction"
associated_intent = I_HELP
armour_types = list("bio", "rad")
/datum/psionic_power/redaction
faculty = PSI_REDACTION
admin_log = FALSE
/datum/psionic_power/redaction/proc/check_dead(var/mob/living/target)
if(!istype(target))
return FALSE
if(target.stat == DEAD || (target.status_flags & FAKEDEATH))
return TRUE
return FALSE
/datum/psionic_power/redaction/invoke(var/mob/living/user, var/mob/living/target)
if(check_dead(target))
return FALSE
. = ..()
/datum/psionic_power/redaction/skinsight
name = "Skinsight"
cost = 3
cooldown = 30
use_grab = TRUE
min_rank = PSI_RANK_OPERANT
use_description = "Grab a patient, target the chest, then switch to help intent and use the grab on them to perform a check for wounds and damage."
/datum/psionic_power/redaction/skinsight/invoke(var/mob/living/user, var/mob/living/target)
if(user.zone_sel.selecting != BP_CHEST)
return FALSE
. = ..()
if(.)
user.visible_message(span("notice", "\The [user] rests a hand on \the [target]."))
health_scan_mob(target, user, TRUE)
return TRUE
/datum/psionic_power/redaction/mend
name = "Mend"
cost = 7
cooldown = 50
use_melee = TRUE
min_rank = PSI_RANK_OPERANT
use_description = "Target a patient while on help intent at melee range to mend a variety of maladies, such as bleeding or broken bones. Higher ranks in this faculty allow you to mend a wider range of problems."
/datum/psionic_power/redaction/mend/invoke(var/mob/living/user, var/mob/living/carbon/human/target)
if(!istype(user) || !istype(target))
return FALSE
. = ..()
if(.)
var/obj/item/organ/external/E = target.get_organ(user.zone_sel.selecting)
if(!E || E.is_stump())
to_chat(user, span("warning", "They are missing that limb."))
return TRUE
if(BP_IS_ROBOTIC(E))
to_chat(user, span("warning", "That limb is prosthetic."))
return TRUE
user.visible_message(span("notice", "<i>\The [user] rests a hand on \the [target]'s [E.name]...</i>"))
to_chat(target, span("notice", "A healing warmth suffuses you."))
var/redaction_rank = user.psi.get_rank(PSI_REDACTION)
var/pk_rank = user.psi.get_rank(PSI_PSYCHOKINESIS)
if(pk_rank >= PSI_RANK_LATENT && redaction_rank >= PSI_RANK_MASTER)
var/removal_size = Clamp(5-pk_rank, 0, 5)
var/valid_objects = list()
for(var/thing in E.implants)
var/obj/imp = thing
if(imp.w_class >= removal_size && !istype(imp, /obj/item/implant))
valid_objects += imp
if(LAZYLEN(valid_objects))
var/removing = pick(valid_objects)
target.remove_implant(removing, TRUE)
to_chat(user, span("notice", "You extend a tendril of psychokinetic-redactive power and carefully tease \the [removing] free of \the [E]."))
return TRUE
if(redaction_rank >= PSI_RANK_MASTER)
if(E.status & ORGAN_ARTERY_CUT)
to_chat(user, span("notice", "You painstakingly mend the torn veins in \the [E], stemming the internal bleeding."))
E.status &= ~ORGAN_ARTERY_CUT
return TRUE
if(E.status & ORGAN_TENDON_CUT)
to_chat(user, span("notice", "You interleave and repair the severed tendon in \the [E]."))
E.status &= ~ORGAN_TENDON_CUT
return TRUE
if(E.status & ORGAN_BROKEN)
to_chat(user, span("notice", "You coax shattered bones to come together and fuse, mending the break."))
E.status &= ~ORGAN_BROKEN
E.stage = 0
return TRUE
for(var/datum/wound/W in E.wounds)
if(W.bleeding())
if(redaction_rank >= PSI_RANK_MASTER || W.wound_damage() < 30)
to_chat(user, span("notice", "You knit together severed veins and broken flesh, stemming the bleeding."))
W.bleed_timer = 0
W.clamped = TRUE
E.status &= ~ORGAN_BLEEDING
return TRUE
else
to_chat(user, span("notice", "This [W.desc] is beyond your power to heal."))
if(redaction_rank >= PSI_RANK_GRANDMASTER)
for(var/obj/item/organ/internal/I in E.internal_organs)
if(!BP_IS_ROBOTIC(I) && I.damage > 0)
to_chat(user, span("notice", "You encourage the damaged tissue of \the [I] to repair itself."))
var/heal_rate = redaction_rank
I.damage = max(0, I.damage - rand(heal_rate,heal_rate*2))
return TRUE
to_chat(user, span("notice", "You can find nothing within \the [target]'s [E.name] to mend."))
return FALSE
/datum/psionic_power/redaction/cleanse
name = "Cleanse"
cost = 9
cooldown = 60
use_melee = TRUE
min_rank = PSI_RANK_GRANDMASTER
use_description = "Target a patient while on help intent at melee range to cleanse radiation and genetic damage from a patient."
/datum/psionic_power/redaction/cleanse/invoke(var/mob/living/user, var/mob/living/carbon/human/target)
if(!istype(user) || !istype(target))
return FALSE
. = ..()
if(.)
// No messages, as Mend procs them even if it fails to heal anything, and Cleanse is always checked after Mend.
var/removing = rand(20,25)
if(target.total_radiation)
to_chat(user, span("notice", "You repair some of the radiation-damaged tissue within \the [target]..."))
if(target.total_radiation > removing)
target.total_radiation -= removing
else
target.total_radiation = 0
return TRUE
if(target.getCloneLoss())
to_chat(user, span("notice", "You stitch together some of the mangled DNA within \the [target]..."))
if(target.getCloneLoss() >= removing)
target.adjustCloneLoss(-removing)
else
target.adjustCloneLoss(-(target.getCloneLoss()))
return TRUE
to_chat(user, span("notice", "You can find no genetic damage or radiation to heal within \the [target]."))
return TRUE
/datum/psionic_power/revive
name = "Revive"
cost = 25
cooldown = 80
use_grab = TRUE
min_rank = PSI_RANK_PARAMOUNT
faculty = PSI_REDACTION
use_description = "Obtain a grab on a dead target, target the head, then select help intent and use the grab against them to attempt to bring them back to life. The process is lengthy and failure is punished harshly."
admin_log = FALSE
/datum/psionic_power/revive/invoke(var/mob/living/user, var/mob/living/target)
if(!isliving(target) || !istype(target) || user.zone_sel.selecting != BP_HEAD)
return FALSE
. = ..()
if(.)
if(target.stat != DEAD && !(target.status_flags & FAKEDEATH))
to_chat(user, span("warning", "This person is already alive!"))
return TRUE
if((world.time - target.timeofdeath) > 6000)
to_chat(user, span("warning", "\The [target] has been dead for too long to revive."))
return TRUE
user.visible_message(span("notice", "<i>\The [user] splays out their hands over \the [target]'s body...</i>"))
if(!do_after(user, 100, target, 0, 1))
user.psi.backblast(rand(10,25))
return TRUE
for(var/mob/abstract/observer/G in dead_mob_list)
if(G.mind && G.mind.current == target && G.client)
to_chat(G, span("notice", "<font size = 3><b>Your body has been revived, <b>Re-Enter Corpse</b> to return to it.</b></font>"))
break
to_chat(target, span("notice", "<font size = 3><b>Life floods back into your body!</b></font>"))
target.visible_message(span("notice", "\The [target] shudders violently!"))
target.adjustOxyLoss(-rand(15,20))
target.basic_revival()
return TRUE

View File

@@ -0,0 +1,14 @@
/obj/screen/psi
icon = 'icons/mob/screen/psi.dmi'
var/mob/living/owner
var/hidden = TRUE
/obj/screen/psi/New(var/mob/living/_owner)
loc = null
owner = _owner
update_icon()
/obj/screen/psi/Destroy()
if(owner && owner.client)
owner.client.screen -= src
. = ..()

View File

@@ -0,0 +1,54 @@
/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
/obj/screen/psi/hub/New(var/mob/living/_owner)
on_cooldown = image(icon, "cooldown")
..()
START_PROCESSING(SSprocessing, src)
/obj/screen/psi/hub/update_icon()
if(!owner.psi)
return
icon_state = owner.psi.suppressed ? "psi_suppressed" : "psi_active"
/obj/screen/psi/hub/Destroy()
STOP_PROCESSING(SSprocessing, src)
owner = null
. = ..()
/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(var/location, var/control, var/params)
var/list/click_params = params2list(params)
if(click_params["shift"])
owner.show_psi_assay(owner)
return
if(owner.psi.suppressed && owner.psi.stun)
to_chat(owner, "<span class='warning'>You are dazed and reeling, and cannot muster enough focus to do that!</span>")
return
owner.psi.suppressed = !owner.psi.suppressed
to_chat(owner, "<span class='notice'>You are <b>[owner.psi.suppressed ? "now suppressing" : "no longer suppressing"]</b> your psi-power.</span>")
if(owner.psi.suppressed)
owner.psi.cancel()
owner.psi.hide_auras()
else
sound_to(owner, sound('sound/effects/psi/power_unlock.ogg'))
owner.psi.show_auras()
update_icon()

View File

@@ -0,0 +1,20 @@
/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(var/faculty, var/rank, var/take_larger, var/defer_update, var/temporary)
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)

View File

@@ -0,0 +1,80 @@
/mob/living/proc/show_psi_assay(var/mob/viewer)
if(!viewer) viewer = usr
var/use_He_is = "You are"
var/use_He_has = "You have"
if(istype(machine) || viewer != src)
var/datum/gender/G = gender_datums[gender]
use_He_is = "[G.He] [G.is]"
use_He_has = "[G.He] [G.has]"
var/list/dat = list()
dat += "<h2>Summary</h2>"
dat += "<hr>"
if(psi)
// Hi Warhammer 40k rating system, how are you?
// I hope you get along with the Galactic Milieu metapsychics.
var/use_rating
var/effective_rating = psi.rating
if(effective_rating > 1 && psi.suppressed)
effective_rating = max(0, psi.rating-2)
var/rating_descriptor
if(viewer != usr && thralls.is_antagonist(mind) && ishuman(viewer))
var/mob/living/H = viewer
if(H.psi && H.psi.get_rank(PSI_REDACTION) >= PSI_RANK_GRANDMASTER)
dat += "<font color='#FF0000'><b>Their mind has been cored like an apple, and enslaved by another operant psychic.</b></font>"
if(!use_rating)
switch(effective_rating)
if(1)
use_rating = "[effective_rating]-Epsilon"
rating_descriptor = "This indicates the presence of minor latent psi potential with little or no operant capabilities."
if(2)
use_rating = "[effective_rating]-Delta"
rating_descriptor = "This indicates the presence of minor psi capabilities of the Operant rank or higher."
if(3)
use_rating = "<font color = '#F4F441'>[effective_rating]-Gamma</font>"
rating_descriptor = "This indicates the presence of psi capabilities of the Master rank or higher."
if(4)
use_rating = "<font color = '#F4BC42'>[effective_rating]-Beta</font>"
rating_descriptor = "This indicates the presence of significant psi capabilities of the Grandmaster rank or higher."
if(5)
use_rating = "<font color = '#FF0000'>[effective_rating]-Alpha</font>"
rating_descriptor = "This indicates the presence of major psi capabilities of the Paramount Grandmaster rank or higher."
else
use_rating = "[effective_rating]-Lambda"
rating_descriptor = "This indicates the presence of trace latent psi capabilities."
dat += "[use_He_has] an overall psi rating of [use_rating].<br><i>[rating_descriptor]</i><hr>"
dat += "[use_He_is] currently <b>[psi.suppressed ? "suppressing" : "not suppressing"]</b> your psychic operancy.<br>"
dat += "[use_He_has] <b>[psi.stamina]/[psi.max_stamina]</b> psi stamina remaining.<br>"
dat += "<hr>"
for(var/faculty_id in psi.ranks)
var/datum/psionic_faculty/faculty = SSpsi.get_faculty(faculty_id)
if(psi.ranks[faculty.id] > 0)
dat += "[use_He_is] assayed at the rank of <b>[psychic_ranks_to_strings[psi.ranks[faculty.id]]]</b> for the <b>[faculty.name] faculty</b>.<br>"
else
dat += "[use_He_has] no notable power within the <b>[faculty.name] faculty</b>.<br>"
dat += "<hr>"
if(viewer == usr)
dat += "<table width = 100% border = 1><tr><td colspan = 2><h2>Psi-power Usage</h2></td></tr>"
for(var/faculty_id in psi.ranks)
var/list/check_powers = psi.get_powers_by_faculty(faculty_id)
if(LAZYLEN(check_powers))
var/datum/psionic_faculty/faculty = SSpsi.get_faculty(faculty_id)
dat += "<tr><td colspan = 2>[use_He_has] access to the following psi-powers within the <b>[faculty.name] faculty</b>:</td></tr>"
for(var/datum/psionic_power/power in check_powers)
dat += "<tr><td><b>[power.name]</b></td><td>[power.use_description]</td></tr>"
dat += "</table>"
var/datum/browser/popup = new(viewer, "psi_assay_\ref[src]", "Psi-Assay")
popup.set_content(jointext(dat,null))
popup.open()

View File

@@ -0,0 +1,38 @@
#define INVOKE_PSI_POWERS(holder, powers, target, return_on_invocation) \
if(holder && holder.psi && holder.psi.can_use()) { \
for(var/thing in powers) { \
var/datum/psionic_power/power = thing; \
var/obj/item/result = power.invoke(holder, target); \
if(result) { \
power.handle_post_power(holder, target); \
if(istype(result)) { \
sound_to(holder, sound('sound/effects/psi/power_evoke.ogg')); \
LAZYADD(holder.psi.manifested_items, result); \
holder.put_in_hands(result); \
} \
return return_on_invocation; \
} \
} \
}
/mob/living/UnarmedAttack(var/atom/A, var/proximity)
. = ..()
if(. && psi)
INVOKE_PSI_POWERS(src, psi.get_melee_powers(SSpsi.faculties_by_intent[a_intent]), A, FALSE)
/mob/living/RangedAttack(var/atom/A, var/params)
if(psi)
INVOKE_PSI_POWERS(src, psi.get_ranged_powers(SSpsi.faculties_by_intent[a_intent]), A, TRUE)
. = ..()
/mob/living/proc/check_psi_grab(var/obj/item/grab/grab)
if(psi && ismob(grab.affecting))
INVOKE_PSI_POWERS(src, psi.get_grab_powers(SSpsi.faculties_by_intent[a_intent]), grab.affecting, FALSE)
. = ..()
/mob/living/attack_empty_hand(var/bp_hand)
if(psi)
INVOKE_PSI_POWERS(src, psi.get_manifestations(), src, FALSE)
. = ..()
#undef INVOKE_PSI_POWERS

View File

@@ -81,19 +81,6 @@
return 1
return 0
/obj/item/contract/wizard/tk
name = "telekinesis contract"
desc = "This contract makes your mind buzz. It promises to give you the ability to move things with your mind. At a price."
color = "#990033"
/obj/item/contract/wizard/tk/contract_effect(mob/user as mob)
..()
if(!(TK in user.mutations))
user.mutations.Add(TK)
to_chat(user, "<span class='notice'>You feel your mind expanding!</span>")
return 1
return 0
/obj/item/contract/boon
name = "boon contract"
desc = "this contract grants you a boon for signing it."

View File

@@ -6,7 +6,6 @@ var/list/artefact_feedback = list(/obj/structure/closet/wizard/armor = "HS",
/obj/item/monster_manual = "MA",
/obj/item/contract/apprentice = "CP",
/obj/structure/closet/wizard/souls = "SS",
/obj/item/contract/wizard/tk = "TK",
/obj/structure/closet/wizard/scrying = "SO",
/obj/item/teleportation_scroll = "TS",
/obj/item/gun/energy/staff = "ST",

View File

@@ -22,7 +22,6 @@
/spell/targeted/subjugation = 1,
/spell/aoe_turf/smoke = 1,
/spell/aoe_turf/conjure/summon/bats = 3,
/obj/item/contract/wizard/tk = 5,
/obj/structure/closet/wizard/scrying = 2,
/obj/item/storage/belt/wands/full = 4,
/obj/item/teleportation_scroll = 1,

View File

@@ -236,6 +236,6 @@ Note: This proc can be overwritten to allow for different types of auto-alignmen
#undef CELLS
#undef CELLSIZE
/obj/structure/table/attack_tk() // no telehulk sorry
/obj/structure/table/do_simple_ranged_interaction(var/mob/user)
return