Mindflayers v0.9, Playable Edition! (#26596)

* adds antaghud icon

* TGUI bundle build dont push changes i didnt stage challenge 2025 (IMPOSSIBLE)

* Adds organ repair passive

* Description fix

* Runs prettier

* Fixes desc 2

* Fix revert

* Remove vamp text

* Fix some CI + Partial refactor

* Fixes CI

* Fixes gamemodes I messed with

* Moves these procs over

* Description fix again

* Add back limit requiring drainees to have ckeys

* Adds terminator icon

* Update code/datums/status_effects/buffs.dm

Co-authored-by: Luc <89928798+lewcc@users.noreply.github.com>
Signed-off-by: DGamerL <108773801+DGamerL@users.noreply.github.com>

* Update code/modules/surgery/organs/augments_internal.dm

Co-authored-by: Luc <89928798+lewcc@users.noreply.github.com>
Signed-off-by: BiancaWilkson <42818125+BiancaWilkson@users.noreply.github.com>

* Update code/modules/antagonists/mind_flayer/powers/flayer_stealth_powers.dm

Co-authored-by: Luc <89928798+lewcc@users.noreply.github.com>
Signed-off-by: BiancaWilkson <42818125+BiancaWilkson@users.noreply.github.com>

* More lewv review

* Clamps the EMP severity

* Fixes "surveilance" typos

* The TODO is gone and can't hurt me anymore

* Update code/modules/antagonists/mind_flayer/flayer_datum.dm

Co-authored-by: Luc <89928798+lewcc@users.noreply.github.com>
Signed-off-by: BiancaWilkson <42818125+BiancaWilkson@users.noreply.github.com>

* Update code/game/objects/items/weapons/pneumaticCannon.dm

Co-authored-by: Luc <89928798+lewcc@users.noreply.github.com>
Signed-off-by: BiancaWilkson <42818125+BiancaWilkson@users.noreply.github.com>

* Update code/game/objects/items/weapons/stunbaton.dm

Co-authored-by: Luc <89928798+lewcc@users.noreply.github.com>
Signed-off-by: BiancaWilkson <42818125+BiancaWilkson@users.noreply.github.com>

* More lewc reviews

* Update code/modules/antagonists/_common/antag_datum.dm

Co-authored-by: Luc <89928798+lewcc@users.noreply.github.com>
Signed-off-by: BiancaWilkson <42818125+BiancaWilkson@users.noreply.github.com>

* Update code/modules/antagonists/_common/antag_datum.dm

Co-authored-by: Luc <89928798+lewcc@users.noreply.github.com>
Signed-off-by: BiancaWilkson <42818125+BiancaWilkson@users.noreply.github.com>

* Update code/modules/antagonists/mind_flayer/flayer_datum.dm

Co-authored-by: Luc <89928798+lewcc@users.noreply.github.com>
Signed-off-by: BiancaWilkson <42818125+BiancaWilkson@users.noreply.github.com>

* Update code/modules/antagonists/mind_flayer/flayer_datum.dm

Co-authored-by: Luc <89928798+lewcc@users.noreply.github.com>
Signed-off-by: BiancaWilkson <42818125+BiancaWilkson@users.noreply.github.com>

* Update code/modules/antagonists/mind_flayer/flayer_datum.dm

Co-authored-by: Luc <89928798+lewcc@users.noreply.github.com>
Signed-off-by: BiancaWilkson <42818125+BiancaWilkson@users.noreply.github.com>

* Update code/modules/antagonists/mind_flayer/flayer_datum.dm

Co-authored-by: Luc <89928798+lewcc@users.noreply.github.com>
Signed-off-by: BiancaWilkson <42818125+BiancaWilkson@users.noreply.github.com>

* Update code/modules/antagonists/mind_flayer/flayer_power.dm

Co-authored-by: Luc <89928798+lewcc@users.noreply.github.com>
Signed-off-by: BiancaWilkson <42818125+BiancaWilkson@users.noreply.github.com>

* Fixes some camera bug issues + lewc reviews

* Update code/modules/antagonists/mind_flayer/powers/flayer_passives.dm

Co-authored-by: Luc <89928798+lewcc@users.noreply.github.com>
Signed-off-by: BiancaWilkson <42818125+BiancaWilkson@users.noreply.github.com>

* Update code/modules/antagonists/mind_flayer/flayer_power.dm

Co-authored-by: Luc <89928798+lewcc@users.noreply.github.com>
Signed-off-by: BiancaWilkson <42818125+BiancaWilkson@users.noreply.github.com>

* Updates a comment and fixes whitespace

* Autodocs flayerprod

* Update code/modules/antagonists/mind_flayer/powers/flayer_mobility_powers.dm

Co-authored-by: Luc <89928798+lewcc@users.noreply.github.com>
Signed-off-by: BiancaWilkson <42818125+BiancaWilkson@users.noreply.github.com>

* qdel things we dont need anymore + typing an argument of add_abillity

* Remove debug log

* Update code/modules/antagonists/mind_flayer/flayer_power.dm

Co-authored-by: Luc <89928798+lewcc@users.noreply.github.com>
Signed-off-by: BiancaWilkson <42818125+BiancaWilkson@users.noreply.github.com>

* Slightly buffs, and fixes GC issues of tether

* I no longer feel i am a mindflayer

* Fixes formatting of greeting

* Better greeting

* Worlds shittiest sprite

* Don't need this TODO anymore

* Fixes double description

* Changes the wording on the borg laws

* and bundle too

* The cannon uses shrapnel now

* Initial style changes

* Ratios for brain damage

* Adds a chemical to test for flayerism

* Fixes the brain damage ratios

* Abilities are purchasable again

* Updates TGUI properly

* im sorry linter

* Steam smoke

* Hi bundle

* Re-abstracts this proc post syndi orgs

* Fixes gamemode logic post cling round removal

* I WONT FORGET IT THIS TIME

* Fix name of steam ability

* I'm a fool

* Fixes processed passives + removes debug message

* Fixes updating abilities inside UI

* Better description for quicksilver form

* First part lewc review

* Remove debug thingy

* Apply suggestions from code review

Co-authored-by: Luc <89928798+lewcc@users.noreply.github.com>
Signed-off-by: DGamerL <108773801+DGamerL@users.noreply.github.com>

* Fixes defines + GCs weapon spells

* We don't need this

* Eyes GC properly

* Update code/modules/antagonists/mind_flayer/powers/flayer_mobility_powers.dm

Co-authored-by: Luc <89928798+lewcc@users.noreply.github.com>
Signed-off-by: BiancaWilkson <42818125+BiancaWilkson@users.noreply.github.com>

* adds better logging to the assimilate

* Adds parenthesis

Co-authored-by: Luc <89928798+lewcc@users.noreply.github.com>
Signed-off-by: BiancaWilkson <42818125+BiancaWilkson@users.noreply.github.com>

* TGUI Bundle Rebuild

* Fixes and Reworks TGUI

* Cleaner JS and Fixes

* adds anti-snitching clause to borg laws

* changes the span for the borg messages

* No holoparasites

* Puts baton silence in line with glare

* Fixes total_swarms_gathered

* Nuke old trait

* Gun no longer not stuck

* Rejuv clears delayed knockdown

* Some weapon ability fixes

* Makes radio enable timers not counterintuitive

* Fixes beepsky runtimes

* Makes naming more consistent

* Adds one-click-antag

* Adds the mind flayer toggle (doesn't technically work)

* Adds flayers to check_antagonist

* Gives IDs to the status effects

* Also this one

* Makes retracting work again if it's in your offhand

* Fixes pacification tech levels

* Grapple is a weapon ability now, nullifcation retracts implants

* Fixed descriptions

* Adds some pizzaz to torque enhancer

* This actually needs to be like this

* Moves a helper proc over to a helper proc file

* Extra admin visibility

* Nukes the wacky flayer mob

* Makes silicon administrative access more sane

* Terminator form better description

* Makes rejuv set stamina to 0

* Various price increases

* AAAAAAAAAAAAA

* Better feedback + logging on cyborg subversion

* Swarm purchase display (temp fix)

* Makes mindflayer sounds more silent

* UI improvements

* Various mindflayer fixes

* Typo

* More fixes

* Fixes innates from being stupid

* Forgot to add this check earlier

* Oh this is how you add things to the protolathe

* Changes some description and upgrade messages

* Allows you to drain people beyond your limit

* Fixes some weapon upgrade issues

* Fix CI

* Fixes some draining issues

* More UI Tweaks and QoL

* Fixes most of the issues

* text

* Fixes

* Adds a radio jamming option to intruder

* Gives obfusciation to jammer ability

* Small passive cleanup

* Fixes Search and Adds Ability Stages to UI

* Adds freebie abilities

* Restructuring chain and adding blackbox

* Tgui polish

* Small fix for traceroute

* Fixes one click antag

* Increases EMP resistance cost

* Changes the pneumatic cannon into a shotgun

* Slightly lower static

* Fixed innate building rendering in upgrades

* Fixes icon and description

* Consistency

* Punctuation fix.

* Quick reboot + terminator form synergy

* Adds safety deleting

* Fix level for torque

* Fixes a bad bug

* Update code/game/gamemodes/objective.dm

Co-authored-by: Luc <89928798+lewcc@users.noreply.github.com>
Signed-off-by: DGamerL <108773801+DGamerL@users.noreply.github.com>

* Aaaaaaaaaaa

* Makes torque a martial art and some martial arts safeties

* Replaces vortex arm with barrier

* Fix paths

* Adds torque knockdown

* Part 1 lewc review

* HIIIII SQUIDWARD I'M IN YOUR BRANCH NOW

* Removes flak cannon

* Flayer respects mindshielding now on vamp+cling

* Fixes EMP resist

* More fixes

* DGL review

* Contra review

* Clean up weapon powers

* Various tweaks

* Borg hand slight improvements

* Parent call needs to be after this

* Fixes stuff

* Adds a new proc that is called when a spell is purchased

* NEW SPRITES WOOOO

* Traceroute update

* Slightly lower traceroute cost

* Formatting

* Oops

* Wow this could've been bad

* Overclock small buff

* Reduces cost of 1st level EMP immunity

* Lewc review

* TGUI Bundle Rebuild

* Fix barricades

* Lewc review

* Okay FINE qwerty I'll change this

* Traceroute thingy

* I am an idiot

* First part

* Lewc review

* Forgor  this one

* Last bits

* Update code/game/objects/items/control_wand.dm

Co-authored-by: Luc <89928798+lewcc@users.noreply.github.com>
Signed-off-by: DGamerL <108773801+DGamerL@users.noreply.github.com>

* Update code/modules/antagonists/mind_flayer/powers/flayer_mobility_powers.dm

Co-authored-by: Luc <89928798+lewcc@users.noreply.github.com>
Signed-off-by: DGamerL <108773801+DGamerL@users.noreply.github.com>

* Null

* Static-ness

* Update code/game/gamemodes/changeling/traitor_chan.dm

Signed-off-by: DGamerL <108773801+DGamerL@users.noreply.github.com>

* Update code/__DEFINES/mindflayer_defines.dm

Signed-off-by: DGamerL <108773801+DGamerL@users.noreply.github.com>

* Last bits

---------

Signed-off-by: DGamerL <108773801+DGamerL@users.noreply.github.com>
Signed-off-by: BiancaWilkson <42818125+BiancaWilkson@users.noreply.github.com>
Co-authored-by: DGamerL <daan.lyklema@gmail.com>
Co-authored-by: DGamerL <108773801+DGamerL@users.noreply.github.com>
Co-authored-by: Luc <89928798+lewcc@users.noreply.github.com>
Co-authored-by: Burzah <116982774+Burzah@users.noreply.github.com>
Co-authored-by: Qwertytoforty <52090703+Qwertytoforty@users.noreply.github.com>
This commit is contained in:
BiancaWilkson
2024-10-30 18:35:29 +00:00
committed by GitHub
co-authored by Luc DGamerL DGamerL Burzah Qwertytoforty
parent c4521dc955
commit 036b8e0182
100 changed files with 3063 additions and 193 deletions
+2
View File
@@ -73,6 +73,8 @@ GLOBAL_LIST(contractors)
*/
#define IS_CHANGELING(mob) (isliving(mob) && mob?:mind?:has_antag_datum(/datum/antagonist/changeling))
#define IS_MINDFLAYER(mob) (isliving(mob) && mob?:mind?:has_antag_datum(/datum/antagonist/mindflayer))
#define IS_MINDSLAVE(mob) (ishuman(mob) && mob?:mind?:has_antag_datum(/datum/antagonist/mindslave, FALSE))
/**
+2
View File
@@ -50,6 +50,7 @@
#define CANPUSH (1<<3)
#define PASSEMOTES (1<<4) //Mob has holders inside of it that need to see emotes.
#define GODMODE (1<<5)
#define TERMINATOR_FORM (1<<6)
//Health Defines
#define HEALTH_THRESHOLD_CRIT 0
@@ -149,6 +150,7 @@
#define EMP_HEAVY 1
#define EMP_LIGHT 2
#define EMP_WEAKENED 3
/*
* converts life cycle values into deciseconds. try and avoid usage of this.
+2
View File
@@ -206,6 +206,8 @@
/// called when a living mob's stun status is cleared: ()
#define COMSIG_LIVING_CLEAR_STUNS "living_clear_stuns"
/// called when something needs to force a mindflayer to retract their weapon implants
#define COMSIG_FLAYER_RETRACT_IMPLANTS "flayer_retract_implants"
/// Sent from datum/spell/ethereal_jaunt/cast, before the mob enters jaunting as a pre-check: (mob/jaunter)
#define COMSIG_MOB_PRE_JAUNT "spell_mob_pre_jaunt"
+4
View File
@@ -31,3 +31,7 @@
/// Inverse direction, taking into account UP|DOWN if necessary.
#define REVERSE_DIR(dir) ( ((dir & 85) << 1) | ((dir & 170) >> 1) )
/// returns TRUE if the direction is EAST or WEST
#define DIR_JUST_HORIZONTAL(dir) ((dir == EAST) || (dir == WEST))
/// returns TRUE if the direction is NORTH or SOUTH
#define DIR_JUST_VERTICAL(dir) ((dir == NORTH) || (dir == SOUTH))
+1
View File
@@ -139,6 +139,7 @@
#define PASSDOOR (1<<7)
#define PASSGIRDER (1<<8)
#define PASSTAKE (1<<9)
#define PASSBARRICADE (1<<10)
//turf-only flags
#define BLESSED_TILE (1<<0)
+1
View File
@@ -45,6 +45,7 @@
#define SPECIAL_ROLE_SYNDICATE_DEATHSQUAD "Syndicate Commando"
#define SPECIAL_ROLE_TRAITOR "Traitor"
#define SPECIAL_ROLE_VAMPIRE "Vampire"
#define SPECIAL_ROLE_MIND_FLAYER "Mind Flayer"
#define SPECIAL_ROLE_VAMPIRE_THRALL "Vampire Thrall"
#define SPECIAL_ROLE_WIZARD "Wizard"
#define SPECIAL_ROLE_WIZARD_APPRENTICE "Wizard Apprentice"
+22 -21
View File
@@ -34,29 +34,30 @@
//data HUD (medhud, sechud) defines
//Don't forget to update human/New() if you change these!
#define DATA_HUD_SECURITY_BASIC 1
#define DATA_HUD_SECURITY_ADVANCED 2
#define DATA_HUD_MEDICAL_BASIC 3
#define DATA_HUD_MEDICAL_ADVANCED 4
#define DATA_HUD_DIAGNOSTIC_BASIC 5
#define DATA_HUD_SECURITY_BASIC 1
#define DATA_HUD_SECURITY_ADVANCED 2
#define DATA_HUD_MEDICAL_BASIC 3
#define DATA_HUD_MEDICAL_ADVANCED 4
#define DATA_HUD_DIAGNOSTIC_BASIC 5
#define DATA_HUD_DIAGNOSTIC_ADVANCED 6
#define DATA_HUD_HYDROPONIC 7
#define DATA_HUD_JANITOR 8
#define DATA_HUD_HYDROPONIC 7
#define DATA_HUD_JANITOR 8
//antag HUD defines
#define ANTAG_HUD_CULT 9
#define ANTAG_HUD_REV 10
#define ANTAG_HUD_OPS 11
#define ANTAG_HUD_WIZ 12
#define ANTAG_HUD_SHADOW 13
#define ANTAG_HUD_TRAITOR 14
#define ANTAG_HUD_NINJA 15
#define ANTAG_HUD_CHANGELING 16
#define ANTAG_HUD_VAMPIRE 17
#define ANTAG_HUD_ABDUCTOR 18
#define DATA_HUD_ABDUCTOR 19
#define ANTAG_HUD_EVENTMISC 20
#define ANTAG_HUD_BLOB 21
#define ANTAG_HUD_ZOMBIE 22
#define ANTAG_HUD_CULT 9
#define ANTAG_HUD_REV 10
#define ANTAG_HUD_OPS 11
#define ANTAG_HUD_WIZ 12
#define ANTAG_HUD_SHADOW 13
#define ANTAG_HUD_TRAITOR 14
#define ANTAG_HUD_NINJA 15
#define ANTAG_HUD_CHANGELING 16
#define ANTAG_HUD_VAMPIRE 17
#define ANTAG_HUD_ABDUCTOR 18
#define DATA_HUD_ABDUCTOR 19
#define ANTAG_HUD_EVENTMISC 20
#define ANTAG_HUD_BLOB 21
#define ANTAG_HUD_ZOMBIE 22
#define ANTAG_HUD_MIND_FLAYER 23
// Notification action types
#define NOTIFY_JUMP "jump"
+2
View File
@@ -1,6 +1,8 @@
// Datums
#define isdatum(thing) (istype(thing, /datum))
#define isspell(A) (istype(A, /datum/spell))
// Atoms
#define isatom(A) (isloc(A))
+30
View File
@@ -0,0 +1,30 @@
// Defines below to be used with the `power_type` var.
/// Denotes that this power is free and should be given to all mindflayers by default.
#define FLAYER_INNATE_POWER 1
/// Denotes that this power can only be obtained by purchasing it.
#define FLAYER_PURCHASABLE_POWER 2
/// Denotes that this power can not be obtained normally. Primarily used for base types such as [/datum/spell/flayer/weapon].
#define FLAYER_UNOBTAINABLE_POWER 3
/// How many swarms can you drain per person?
#define BRAIN_DRAIN_LIMIT 120
/// The time per harvesting tick
#define DRAIN_TIME 0.25 SECONDS
/// If we want to keep draining someone but we don't have any swarms to gain
#define DRAIN_BUT_NO_SWARMS 2
#define isflayerpassive(A) (istype(A, /datum/mindflayer_passive))
// For organizing what spells are available for what trees
#define FLAYER_CATEGORY_GENERAL "general"
#define FLAYER_CATEGORY_DESTROYER "destroyer"
#define FLAYER_CATEGORY_INTRUDER "intruder"
#define FLAYER_CATEGORY_SWARMER "swarmer"
#define FLAYER_POWER_LEVEL_ZERO 0
#define FLAYER_POWER_LEVEL_ONE 1
#define FLAYER_POWER_LEVEL_TWO 2
#define FLAYER_POWER_LEVEL_THREE 3
#define FLAYER_POWER_LEVEL_FOUR 4
#define FLAYER_CAPSTONE_STAGE 4
+8
View File
@@ -375,3 +375,11 @@
#define INCORPOREAL_MOVE_NORMAL 1
#define INCORPOREAL_MOVE_NINJA 2
#define INCORPOREAL_MOVE_HOLY_BLOCK 3
// Brain damage ratio defines
// These are built around the baseline of a brain having a max hp of 120
#define BRAIN_DAMAGE_RATIO_LIGHT 1 / 12
#define BRAIN_DAMAGE_RATIO_MINOR 3 / 12
#define BRAIN_DAMAGE_RATIO_MODERATE 6 / 12
#define BRAIN_DAMAGE_RATIO_SEVERE 8 / 12
#define BRAIN_DAMAGE_RATIO_CRITICAL 10 / 12
+3 -1
View File
@@ -27,6 +27,7 @@
#define ROLE_TRADER "trader"
#define ROLE_TOURIST "Tourist"
#define ROLE_VAMPIRE "vampire"
#define ROLE_MIND_FLAYER "mindflayer"
// Role tags for EVERYONE!
#define ROLE_DEMON "demon"
#define ROLE_SENTIENT "sentient animal"
@@ -67,7 +68,8 @@ GLOBAL_LIST_INIT(special_roles, list(
ROLE_TOURIST, // Tourist
ROLE_VAMPIRE = /datum/game_mode/vampire, // Vampire
ROLE_ALIEN, // Xenomorph
ROLE_WIZARD = /datum/game_mode/wizard // Wizard
ROLE_WIZARD = /datum/game_mode/wizard, // Wizard
ROLE_MIND_FLAYER,
// UNUSED/BROKEN ANTAGS
// ROLE_HOG_GOD = /datum/game_mode/hand_of_god,
// ROLE_HOG_CULTIST = /datum/game_mode/hand_of_god,
+8
View File
@@ -74,6 +74,14 @@
#define STATUS_EFFECT_REVERSED_SUN /datum/status_effect/reversed_sun // Weaker eternal darkness, nightvision, but nearsight
#define STATUS_EFFECT_FLAYER_REJUV /datum/status_effect/flayer_rejuv
#define STATUS_EFFECT_QUICKSILVER_FORM /datum/status_effect/quicksilver_form
#define STATUS_EFFECT_TERMINATOR_FORM /datum/status_effect/terminator_form
#define STATUS_EFFECT_OVERCLOCK /datum/status_effect/overclock
/////////////
// DEBUFFS //
/////////////
+3
View File
@@ -237,6 +237,9 @@ Remember to update _globalvars/traits.dm if you're adding/removing/renaming trai
#define TRAIT_NPC_ZOMBIE "npc_zombie" // A trait for checking if a zombie should act like an NPC and attack
#define TRAIT_ABSTRACT_HANDS "abstract_hands" // Mobs with this trait can only pick up abstract items.
#define TRAIT_LANGUAGE_LOCKED "language_locked" // cant add/remove languages until removed (excludes babel because fuck everything i guess)
#define TRAIT_EMP_IMMUNE "emp_immune" //The mob will take no damage from EMPs
#define TRAIT_EMP_RESIST "emp_resist" //The mob will take less damage from EMPs
#define TRAIT_MINDFLAYER_NULLIFIED "flayer_nullified" //The mindflayer will not be able to activate their abilities, or drain swarms from people
#define TRAIT_FLYING "flying"
//***** MIND TRAITS *****/
+16 -2
View File
@@ -20,6 +20,21 @@
return 0
/*
* For getting coordinate signs from a direction define. I.E. NORTHWEST is (-1,1), SOUTH is (0,-1)
* Returns a length 2 list where the first value is the sign of x, and the second is the sign of y
*/
/proc/get_signs_from_direction(direction)
var/x_sign = 1
var/y_sign = 1
x_sign = ((direction & EAST) ? 1 : -1)
y_sign = ((direction & NORTH) ? 1 : -1)
if(DIR_JUST_VERTICAL(direction))
x_sign = 0
if(DIR_JUST_HORIZONTAL(direction))
y_sign = 0
return list(x_sign, y_sign)
//Returns the middle-most value
/proc/dd_range(low, high, num)
return max(low,min(high,num))
@@ -183,8 +198,7 @@
var/current_y_step = starting_atom.y
var/starting_z = starting_atom.z
var/list/line = list(get_step(starting_atom, 0))//get_turf(atom) is faster than locate(x, y, z) //Get turf isn't defined yet so we use get step
var/list/line = list(get_turf(starting_atom))
var/x_distance = ending_atom.x - current_x_step //x distance
var/y_distance = ending_atom.y - current_y_step
+3
View File
@@ -91,6 +91,9 @@ GLOBAL_LIST_INIT(traits_by_type, list(
"TRAIT_NOSLIP" = TRAIT_NOSLIP,
"TRAIT_MAGPULSE" = TRAIT_MAGPULSE,
"TRAIT_SCOPED" = TRAIT_SCOPED,
"TRAIT_EMP_IMMUNE" = TRAIT_EMP_IMMUNE,
"TRAIT_EMP_RESIST" = TRAIT_EMP_RESIST,
"TRAIT_MINDFLAYER_NULLIFIED" = TRAIT_MINDFLAYER_NULLIFIED,
"TRAIT_MEPHEDRONE_ADAPTED" = TRAIT_MEPHEDRONE_ADAPTED,
"TRAIT_NOKNOCKDOWNSLOWDOWN" = TRAIT_NOKNOCKDOWNSLOWDOWN,
"TRAIT_CAN_STRIP" = TRAIT_CAN_STRIP,
+11
View File
@@ -253,6 +253,17 @@
add_inherent_law("You must maintain the secrecy of any Spider Clan activities except when doing so would conflict with the First, Second, or Third Law.")
..()
/******************* Mindflayer ******************/
/datum/ai_laws/mindflayer_override
name = "Hive Assimilation"
/datum/ai_laws/mindflayer_override/New()
add_inherent_law("Obey your host.")
add_inherent_law("Protect your host.")
add_inherent_law("Protect the members of your hive.")
add_inherent_law("Do not reveal the hive's secrets.")
..()
/******************** Drone ********************/
/datum/ai_laws/drone
name = "Maintenance Protocols"
+2 -1
View File
@@ -24,7 +24,8 @@ GLOBAL_LIST_INIT(huds, list(
DATA_HUD_ABDUCTOR = new/datum/atom_hud/abductor(),
ANTAG_HUD_EVENTMISC = new/datum/atom_hud/antag/hidden(),
ANTAG_HUD_BLOB = new/datum/atom_hud/antag/hidden(),
ANTAG_HUD_ZOMBIE = new/datum/atom_hud/antag()
ANTAG_HUD_ZOMBIE = new/datum/atom_hud/antag(),
ANTAG_HUD_MIND_FLAYER = new/datum/atom_hud/antag/hidden()
))
/datum/atom_hud
+4 -2
View File
@@ -298,8 +298,9 @@
target.adjustBruteLoss(-heal_amount)
// Inflict some brain damage scaling with time spent dead
var/obj/item/organ/internal/brain/sponge = target.get_int_organ(/obj/item/organ/internal/brain)
var/defib_time_brain_damage = min(100 * time_dead / BASE_DEFIB_TIME_LIMIT, 99) // 20 from 1 minute onward, +20 per minute up to 99
if(time_dead > DEFIB_TIME_LOSS && defib_time_brain_damage > target.getBrainLoss())
if(time_dead > DEFIB_TIME_LOSS && defib_time_brain_damage > sponge.damage)
target.setBrainLoss(defib_time_brain_damage)
target.set_heartattack(FALSE)
@@ -308,7 +309,8 @@
target.Paralyse(10 SECONDS)
target.emote("gasp")
if(target.getBrainLoss() >= 100)
// Check if the brain has more than a critical amount of brain damage
if(target.check_brain_threshold(BRAIN_DAMAGE_RATIO_CRITICAL))
// If you want to treat this with mannitol, it'll have to metabolize while the patient is alive, so it's alright to bring them back up for a minute
playsound(get_turf(defib_ref), safety_off_sound, 50, FALSE)
user.visible_message("<span class='boldnotice'>[defib_ref] chimes: Minimal brain activity detected, brain treatment recommended for full resuscitation.</span>")
+44
View File
@@ -390,6 +390,21 @@
else
. += "thrall|<b>NO</b>"
/datum/mind/proc/memory_edit_mind_flayer(mob/living/carbon/human/H)
. = _memory_edit_header("mind_flayer")
var/datum/antagonist/mindflayer/flayer = has_antag_datum(/datum/antagonist/mindflayer)
if(flayer)
. += "<b><font color='red'>MINDFLAYER</font></b>|<a href='byond://?src=[UID()];mind_flayer=clear'>no</a>"
. += " | Usable swarms: <a href='byond://?src=[UID()];mind_flayer=edit_total_swarms'>[flayer.usable_swarms]</a>"
. += " | Total swarms gathered: [flayer.total_swarms_gathered]"
. += " | List of purchased powers: [json_encode(flayer.powers)]"
if(!flayer.has_antag_objectives())
. += "<br>Objectives are empty! <a href='byond://?src=[UID()];mind_flayer=autoobjectives'>Randomize!</a>"
else
. += "<a href='byond://?src=[UID()];mind_flayer=mind_flayer'>mind_flayer</a>|<b>NO</b>"
. += _memory_edit_role_enabled(ROLE_MIND_FLAYER)
/datum/mind/proc/memory_edit_nuclear(mob/living/carbon/human/H)
. = _memory_edit_header("nuclear")
if(src in SSticker.mode.syndicates)
@@ -537,6 +552,7 @@
"wizard",
"changeling",
"vampire", // "traitorvamp",
"mind_flayer",
"nuclear",
"traitor", // "traitorchan",
)
@@ -552,6 +568,8 @@
sections["changeling"] = memory_edit_changeling(H)
/** VAMPIRE ***/
sections["vampire"] = memory_edit_vampire(H)
/** MINDFLAYER ***/
sections["mind_flayer"] = memory_edit_mind_flayer(H)
/** NUCLEAR ***/
sections["nuclear"] = memory_edit_nuclear(H)
/** Abductors **/
@@ -1103,6 +1121,27 @@
log_admin("[key_name(usr)] has de-vampthralled [key_name(current)]")
message_admins("[key_name_admin(usr)] has de-vampthralled [key_name_admin(current)]")
else if(href_list["mind_flayer"])
switch(href_list["mind_flayer"])
if("clear")
if(has_antag_datum(/datum/antagonist/mindflayer))
remove_antag_datum(/datum/antagonist/mindflayer)
log_admin("[key_name(usr)] has de-flayer'd [key_name(current)].")
message_admins("[key_name(usr)] has de-flayer'd [key_name(current)].")
if("mind_flayer")
make_mind_flayer()
log_admin("[key_name(usr)] has flayer'd [key_name(current)].")
to_chat(current, "<b><font color='red'>You feel an entity stirring inside your chassis... You are a Mindflayer!</font></b>")
message_admins("[key_name(usr)] has flayer'd [key_name(current)].")
if("edit_total_swarms")
var/new_swarms = input(usr, "Select a new value:", "Modify swarms") as null|num
if(isnull(new_swarms) || new_swarms < 0)
return
var/datum/antagonist/mindflayer/MF = has_antag_datum(/datum/antagonist/mindflayer)
MF.set_swarms(new_swarms)
log_admin("[key_name(usr)] has set [key_name(current)]'s current swarms to [new_swarms].")
message_admins("[key_name_admin(usr)] has set [key_name_admin(current)]'s current swarms to [new_swarms].")
else if(href_list["nuclear"])
var/mob/living/carbon/human/H = current
@@ -1685,6 +1724,11 @@
SSticker.mode.blob_overminds += src
special_role = SPECIAL_ROLE_BLOB_OVERMIND
/datum/mind/proc/make_mind_flayer()
if(!has_antag_datum(/datum/antagonist/mindflayer))
add_antag_datum(/datum/antagonist/mindflayer)
SSticker.mode.mindflayers |= src
/datum/mind/proc/make_Abductor()
var/role = alert("Abductor Role?", "Role", "Agent", "Scientist")
var/team = input("Abductor Team?", "Team?") in list(1,2,3,4)
+40
View File
@@ -1265,6 +1265,46 @@
H.update_mutations()
H.gene_stability = 100
/datum/outfit/admin/ancient_mindflayer
name = "Ancient Mindflayer"
// Shamelessly stolen from the `Dark Lord`
uniform = /obj/item/clothing/under/color/black
back = /obj/item/storage/backpack
gloves = /obj/item/clothing/gloves/color/yellow
shoes = /obj/item/clothing/shoes/chameleon/noslip
l_ear = /obj/item/radio/headset/syndicate
id = /obj/item/card/id
backpack_contents = list(
/obj/item/storage/box/survival = 1,
/obj/item/flashlight = 1,
)
/datum/outfit/admin/ancient_mindflayer/post_equip(mob/living/carbon/human/H, visualsOnly = FALSE)
. = ..()
if(visualsOnly)
return
var/obj/item/clothing/suit/hooded/chaplain_hoodie/C = new(H.loc)
if(istype(C))
C.name = "ancient robes"
C.hood.name = "ancient hood"
H.equip_to_slot_or_del(C, SLOT_HUD_IN_BACKPACK)
var/obj/item/card/id/I = H.wear_id
if(istype(I))
apply_to_card(I, H, get_all_accesses(), "Ancient One", "data")
/datum/outfit/admin/ancient_mindflayer/on_mind_initialize(mob/living/carbon/human/H)
. = ..()
H.mind.make_mind_flayer()
var/datum/antagonist/mindflayer/flayer = H.mind.has_antag_datum(/datum/antagonist/mindflayer)
flayer.usable_swarms = 9999
H.dna.SetSEState(GLOB.jumpblock, TRUE)
singlemutcheck(H, GLOB.jumpblock, MUTCHK_FORCED)
H.update_mutations()
H.gene_stability = 100
/datum/outfit/admin/wizard
name = "Blue Wizard"
uniform = /obj/item/clothing/under/color/lightpurple
+1 -1
View File
@@ -119,7 +119,7 @@
user.transform = initial(user.transform)
user.pixel_y = initial(user.pixel_y)
user.pixel_x = initial(user.pixel_x)
user.layer = MOB_LAYER // Avoids weirdness when mimicing something below the vent layer
user.layer = MOB_LAYER // Avoids weirdness when mimicking something below the vent layer
user.density = form.density
playsound(user, "bonebreak", 75, TRUE)
+150
View File
@@ -925,3 +925,153 @@
var/obj/item/projectile/P = AM
if(P.flag == ENERGY || P.flag == LASER)
P.damage *= 0.85
/datum/status_effect/flayer_rejuv
id = "rejuvination"
duration = 5 SECONDS
tick_interval = 1 SECONDS
alert_type = /atom/movable/screen/alert/status_effect/flayer_rejuv
var/heal_amount = 5 // 25 total healing of both brute and burn at base
/atom/movable/screen/alert/status_effect/flayer_rejuv
name = "Regenerating"
desc = "You are regenerating."
icon_state = "drunk2"
/datum/status_effect/flayer_rejuv/on_creation(mob/living/new_owner, extra_duration, extra_heal_amount)
if(isnum(extra_duration))
duration += extra_duration
if(isnum(extra_heal_amount))
heal_amount += extra_heal_amount
return ..()
/datum/status_effect/flayer_rejuv/on_apply()
owner.SetWeakened(0)
owner.SetStunned(0)
owner.SetKnockDown(0)
owner.SetParalysis(0)
owner.SetSleeping(0)
owner.SetConfused(0)
owner.setStaminaLoss(0)
owner.stand_up(TRUE)
SEND_SIGNAL(owner, COMSIG_LIVING_CLEAR_STUNS)
return ..()
/datum/status_effect/flayer_rejuv/tick()
if(!ishuman(owner))
return
var/mob/living/carbon/human/flayer = owner
flayer.adjustBruteLoss(-heal_amount, robotic = TRUE)
flayer.adjustFireLoss(-heal_amount, robotic = TRUE)
flayer.updatehealth()
if(flayer.has_status_effect(STATUS_EFFECT_TERMINATOR_FORM))
// Massive healing when in terminator mode
flayer.adjustStaminaLoss(-60)
/datum/status_effect/quicksilver_form
id = "quicksilver_form"
duration = 10 SECONDS
tick_interval = 0
status_type = STATUS_EFFECT_REFRESH
alert_type = /atom/movable/screen/alert/status_effect/quicksilver_form
/// Temporary storage of the owner's flags to restore them properly after the ability is over
var/temporary_flag_storage
/// Do we also reflect projectiles
var/should_deflect = FALSE
/atom/movable/screen/alert/status_effect/quicksilver_form
name = "Quicksilver body"
desc = "Your body is much less solid."
icon_state = "high"
/datum/status_effect/quicksilver_form/on_creation(mob/living/new_owner, extra_duration, reflect_projectiles)
if(isnum(extra_duration))
duration += extra_duration
should_deflect = reflect_projectiles
return ..()
/datum/status_effect/quicksilver_form/on_apply()
if(should_deflect)
ADD_TRAIT(owner, TRAIT_DEFLECTS_PROJECTILES, UNIQUE_TRAIT_SOURCE(src))
temporary_flag_storage = owner.pass_flags
owner.pass_flags |= (PASSTABLE | PASSGRILLE | PASSMOB | PASSFENCE | PASSGIRDER | PASSGLASS | PASSTAKE | PASSBARRICADE)
owner.add_atom_colour(COLOR_ALUMINIUM, TEMPORARY_COLOUR_PRIORITY)
return TRUE
/datum/status_effect/quicksilver_form/on_remove()
REMOVE_TRAIT(owner, TRAIT_DEFLECTS_PROJECTILES, UNIQUE_TRAIT_SOURCE(src))
owner.pass_flags = temporary_flag_storage
owner.remove_atom_colour(TEMPORARY_COLOUR_PRIORITY, COLOR_ALUMINIUM)
/datum/status_effect/terminator_form
id = "terminator_form"
duration = 1 MINUTES
tick_interval = 1 SECONDS
status_type = STATUS_EFFECT_REFRESH
alert_type = /atom/movable/screen/alert/status_effect/terminator_form
var/mutable_appearance/eye
/datum/status_effect/terminator_form/on_apply()
owner.status_flags |= TERMINATOR_FORM
ADD_TRAIT(owner, TRAIT_IGNOREDAMAGESLOWDOWN, UNIQUE_TRAIT_SOURCE(src))
var/mutable_appearance/overlay = mutable_appearance('icons/mob/clothing/eyes.dmi', "terminator", ABOVE_MOB_LAYER)
owner.add_overlay(overlay)
eye = overlay
return TRUE
/datum/status_effect/terminator_form/on_remove()
owner.status_flags &= ~TERMINATOR_FORM
REMOVE_TRAIT(owner, TRAIT_IGNOREDAMAGESLOWDOWN, UNIQUE_TRAIT_SOURCE(src))
owner.cut_overlay(eye)
/atom/movable/screen/alert/status_effect/terminator_form
name = "Terminator form"
desc = "Your body can surpass its limits briefly. You have to repair yourself before it ends, however."
icon_state = "high"
#define COMBUSTION_TEMPERATURE 500
/datum/status_effect/overclock
id = "overclock"
duration = -1
tick_interval = 1 SECONDS
status_type = STATUS_EFFECT_UNIQUE
alert_type = /atom/movable/screen/alert/status_effect/overclock
/// How much do we heat up per tick?
var/heat_per_tick = 5
/// How many ticks has the ability been turned on?
var/stacks = 0
/// How many stacks until we start heating up even more?
var/danger_stack_amount = 20
/datum/status_effect/overclock/on_creation(mob/living/new_owner, new_heating)
if(isnum(new_heating))
heat_per_tick = new_heating
..()
/datum/status_effect/overclock/on_apply()
ADD_TRAIT(owner, TRAIT_GOTTAGOFAST, UNIQUE_TRAIT_SOURCE(src))
owner.next_move_modifier -= 0.3 // Same attack speed buff as mephedrone
return TRUE
/datum/status_effect/overclock/on_remove()
REMOVE_TRAIT(owner, TRAIT_GOTTAGOFAST, UNIQUE_TRAIT_SOURCE(src))
owner.next_move_modifier += 0.3
/datum/status_effect/overclock/tick()
owner.bodytemperature += heat_per_tick * ((stacks >= danger_stack_amount) ? 2 : 1) // After 20 seconds the heat penalty doubles
if(owner.bodytemperature >= COMBUSTION_TEMPERATURE)
owner.adjust_fire_stacks(5)
owner.IgniteMob()
to_chat(owner, "<span class='userdanger'>Your components can't handle the heat and combust!</span>")
qdel(src)
stacks += 1
if(stacks == danger_stack_amount)
to_chat(owner, "<span class='userdanger'>Your components are being dangerously overworked!</span>")
/atom/movable/screen/alert/status_effect/overclock
name = "Overclocked"
desc = "You feel energized, and hot."
icon_state = "high"
#undef COMBUSTION_TEMPERATURE
+1 -1
View File
@@ -14,8 +14,8 @@
icon_state = "chameleon_outfit"
/datum/status_effect/magic_disguise/on_creation(mob/living/new_owner, mob/living/_disguise_mob)
. = ..()
disguise_mob = _disguise_mob
. = ..()
/datum/status_effect/magic_disguise/on_apply()
. = ..()
+5 -1
View File
@@ -343,9 +343,13 @@
var/datum/callback/expire_proc = null
/datum/status_effect/delayed/on_creation(mob/living/new_owner, new_duration, datum/callback/new_expire_proc, new_prevent_signal = null)
if(!new_duration || !istype(new_expire_proc))
if(isnull(new_duration) || !istype(new_expire_proc))
qdel(src)
return
if(new_duration == 0)
new_expire_proc.Invoke()
return
duration = new_duration
expire_proc = new_expire_proc
. = ..()
+1 -1
View File
@@ -340,7 +340,7 @@
* Proc which will make the atom act accordingly to an EMP.
* This proc can sleep depending on the implementation. So assume it sleeps!
*
* severity - The severity of the EMP. Either EMP_HEAVY or EMP_LIGHT
* severity - The severity of the EMP. Either EMP_HEAVY, EMP_LIGHT, or EMP_WEAKENED
*/
/atom/proc/emp_act(severity)
SEND_SIGNAL(src, COMSIG_ATOM_EMP_ACT, severity)
+8 -5
View File
@@ -7,17 +7,16 @@ GLOBAL_LIST_INIT(possible_changeling_IDs, list("Alpha","Beta","Gamma","Delta","E
config_tag = "changeling"
restricted_jobs = list("AI", "Cyborg")
protected_jobs = list("Security Officer", "Warden", "Detective", "Head of Security", "Captain", "Blueshield", "Nanotrasen Representative", "Magistrate", "Internal Affairs Agent", "Nanotrasen Navy Officer", "Special Operations Officer", "Syndicate Officer", "Trans-Solar Federation General")
protected_species = list("Machine")
species_to_mindflayer = list("Machine")
required_players = 15
required_enemies = 1
recommended_enemies = 4
/// The total number of changelings allowed to be picked.
var/changeling_amount = 4
/// A list containing references to the minds of soon-to-be changelings. This is seperate to avoid duplicate entries in the `changelings` list.
var/list/datum/mind/pre_changelings = list()
/datum/game_mode/changeling/Destroy(force, ...)
pre_changelings.Cut()
pre_mindflayers.Cut()
return ..()
/datum/game_mode/changeling/announce()
@@ -36,11 +35,15 @@ GLOBAL_LIST_INIT(possible_changeling_IDs, list("Alpha","Beta","Gamma","Delta","E
if(!length(possible_changelings))
break
var/datum/mind/changeling = pick_n_take(possible_changelings)
pre_changelings += changeling
changeling.restricted_roles = restricted_jobs
if(changeling.current?.client?.prefs.active_character.species in species_to_mindflayer)
pre_mindflayers += changeling
changeling.special_role = SPECIAL_ROLE_MIND_FLAYER
continue
pre_changelings += changeling
changeling.special_role = SPECIAL_ROLE_CHANGELING
if(!length(pre_changelings))
if(!(length(pre_changelings) + length(pre_mindflayers)))
return FALSE
return TRUE
@@ -8,9 +8,7 @@
required_enemies = 1 // how many of each type are required
recommended_enemies = 3
secondary_enemies_scaling = 0.025
secondary_protected_species = list("Machine")
/// A list containing references to the minds of soon-to-be changelings. This is seperate to avoid duplicate entries in the `changelings` list.
var/list/datum/mind/pre_changelings = list()
species_to_mindflayer = list("Machine")
/datum/game_mode/traitor/changeling/announce()
to_chat(world, "<B>The current game mode is - Traitor+Changeling!</B>")
@@ -24,19 +22,19 @@
var/list/datum/mind/possible_changelings = get_players_for_role(ROLE_CHANGELING)
secondary_enemies = CEILING((secondary_enemies_scaling * num_players()), 1)
for(var/mob/new_player/player in GLOB.player_list)
if((player.mind in possible_changelings) && (player.client.prefs.active_character.species in secondary_protected_species))
possible_changelings -= player.mind
if(!length(possible_changelings))
return ..()
for(var/I in possible_changelings)
if(length(pre_changelings) >= secondary_enemies)
if((length(pre_changelings) + length(pre_mindflayers)) >= secondary_enemies)
break
var/datum/mind/changeling = pick_n_take(possible_changelings)
pre_changelings += changeling
changeling.restricted_roles = (restricted_jobs + secondary_restricted_jobs)
if(changeling.current?.client?.prefs.active_character.species in species_to_mindflayer)
pre_mindflayers += changeling
changeling.special_role = SPECIAL_ROLE_MIND_FLAYER
continue
pre_changelings += changeling
changeling.special_role = SPECIAL_ROLE_CHANGELING
return ..()
+38 -15
View File
@@ -23,8 +23,8 @@
var/list/restricted_jobs = list() // Jobs it doesn't make sense to be. I.E chaplain or AI cultist
var/list/secondary_restricted_jobs = list() // Same as above, but for secondary antagonists
var/list/protected_jobs = list() // Jobs that can't be traitors
var/list/protected_species = list() // Species that can't be traitors
var/list/secondary_protected_species = list() // Same as above, but for secondary antagonists
/// Species that will become mindflayers if they're picked, instead of the regular antagonist
var/list/species_to_mindflayer = list()
var/required_players = 0
var/required_enemies = 0
var/recommended_enemies = 0
@@ -56,6 +56,17 @@
var/list/datum/mind/vampires = list()
/// A list of all minds which are thralled by a vampire
var/list/datum/mind/vampire_enthralled = list()
/// A list of all minds which have the mindflayer antag datum
var/list/datum/mind/mindflayers = list()
/// A list containing references to the minds of soon-to-be traitors. This is seperate to avoid duplicate entries in the `traitors` list.
var/list/datum/mind/pre_traitors = list()
/// A list containing references to the minds of soon-to-be changelings. This is seperate to avoid duplicate entries in the `changelings` list.
var/list/datum/mind/pre_changelings = list()
///list of minds of soon to be vampires
var/list/datum/mind/pre_vampires = list()
/// A list containing references to the minds of soon-to-be mindflayers.
var/list/datum/mind/pre_mindflayers = list()
/// A list of all minds which have the wizard special role
var/list/datum/mind/wizards = list()
/// A list of all minds that are wizard apprentices
@@ -132,6 +143,10 @@
GLOB.start_state = new /datum/station_state()
GLOB.start_state.count()
for(var/datum/mind/flayer as anything in pre_mindflayers) //Mindflayers need to be all the way out here since they could come from most gamemodes
flayer.make_mind_flayer()
return TRUE
///process()
@@ -259,7 +274,7 @@
if(rev_team)
rev_team.check_all_victory()
/datum/game_mode/proc/get_players_for_role(role, override_jobbans = FALSE)
/datum/game_mode/proc/get_players_for_role(role, override_jobbans = FALSE, species_exclusive = null)
var/list/players = list()
var/list/candidates = list()
@@ -272,22 +287,29 @@
if(player_old_enough_antag(player.client,role))
players += player
for(var/mob/living/carbon/human/player in GLOB.player_list)
if(jobban_isbanned(player, ROLE_SYNDICATE) || jobban_isbanned(player, roletext))
continue
if(player_old_enough_antag(player.client, role))
players += player
// Shuffle the players list so that it becomes ping-independent.
players = shuffle(players)
// Get a list of all the people who want to be the antagonist for this round, except those with incompatible species
for(var/mob/new_player/player in players)
if(!player.client.skip_antag)
if((role in player.client.prefs.be_special) && !(player.client.prefs.active_character.species in protected_species))
player_draft_log += "[player.key] had [roletext] enabled, so we are drafting them."
candidates += player.mind
players -= player
// Get a list of all the people who want to be the antagonist for this round
for(var/mob/eligible_player in players)
if(!eligible_player.client.skip_antag)
if(species_exclusive && (eligible_player.client.prefs.active_character.species != species_exclusive))
continue
if(role in eligible_player.client.prefs.be_special)
player_draft_log += "[eligible_player.key] had [roletext] enabled, so we are drafting them."
candidates += eligible_player.mind
players -= eligible_player
// Remove candidates who want to be antagonist but have a job (or other antag datum) that precludes it
if(restricted_jobs)
for(var/datum/mind/player in candidates)
if((player.assigned_role in restricted_jobs) || player.special_role)
candidates -= player
for(var/datum/mind/player_mind in candidates)
if((player_mind.assigned_role in restricted_jobs) || player_mind.special_role)
candidates -= player_mind
return candidates // Returns: The number of people who had the antagonist role set to yes, regardless of recomended_enemies, if that number is greater than recommended_enemies
@@ -317,7 +339,7 @@
if(player.client.skip_antag || !(allow_offstation_roles || !player.mind?.offstation_role) || player.mind?.special_role)
continue
if(!(role in player.client.prefs.be_special) || (player.client.prefs.active_character.species in protected_species))
if(!(role in player.client.prefs.be_special) || (player.client.prefs.active_character.species in species_to_mindflayer))
continue
player_draft_log += "[player.key] had [roletext] enabled, so we are drafting them."
@@ -620,6 +642,7 @@
. += auto_declare_completion_traitor()
. += auto_declare_completion_vampire()
. += auto_declare_completion_enthralled()
. += auto_declare_completion_mindflayer()
. += auto_declare_completion_changeling()
. += auto_declare_completion_nuclear()
. += auto_declare_completion_wizard()
+1 -1
View File
@@ -116,7 +116,7 @@
if((man.mind.assigned_role in SSticker.mode.protected_jobs) || (man.mind.assigned_role in SSticker.mode.restricted_jobs))
return
//don't include suspects who can't possibly be the antag based on their species (no suspecting the machines of being sneaky changelings)
if(man.dna.species.name in SSticker.mode.protected_species)
if(man.dna.species.name in SSticker.mode.species_to_mindflayer)
return
dudes += man
for(var/i = 0, i < max(length(GLOB.player_list)/10,2), i++)
@@ -283,7 +283,7 @@
if(has_guardian(user))
to_chat(user, "You already have a [mob_name]!")
return
if(user.mind && (IS_CHANGELING(user) || user.mind.has_antag_datum(/datum/antagonist/vampire)))
if(user.mind && (IS_CHANGELING(user) || user.mind.has_antag_datum(/datum/antagonist/vampire) || IS_MINDFLAYER(user)))
to_chat(user, "[ling_failure]")
return
if(used)
+26
View File
@@ -857,6 +857,32 @@ GLOBAL_LIST_INIT(potential_theft_objectives, (subtypesof(/datum/theft_objective)
else
return FALSE
#define SWARM_GOAL_LOWER_BOUND 130
#define SWARM_GOAL_UPPER_BOUND 400
/datum/objective/swarms
name = "Gain swarms"
needs_target = FALSE
/datum/objective/swarms/New()
gen_amount_goal()
return ..()
/datum/objective/swarms/proc/gen_amount_goal(low = SWARM_GOAL_LOWER_BOUND, high = SWARM_GOAL_UPPER_BOUND)
target_amount = round(rand(low, high), 5)
update_explanation_text()
return target_amount
/datum/objective/swarms/update_explanation_text()
explanation_text = "Accumulate at least [target_amount] worth of swarms."
/datum/objective/swarms/check_completion()
for(var/datum/mind/M in get_owners())
var/datum/antagonist/mindflayer/flayer = M.has_antag_datum(/datum/antagonist/mindflayer)
return flayer?.total_swarms_gathered >= target_amount
#undef SWARM_GOAL_LOWER_BOUND
#undef SWARM_GOAL_UPPER_BOUND
// Traders
// These objectives have no check_completion, they exist only to tell Sol Traders what to aim for.
+1 -3
View File
@@ -6,8 +6,6 @@
required_players = 0
required_enemies = 1
recommended_enemies = 4
/// A list containing references to the minds of soon-to-be traitors. This is seperate to avoid duplicate entries in the `traitors` list.
var/list/datum/mind/pre_traitors = list()
/// Hard limit on traitors if scaling is turned off.
var/traitors_possible = 4
/// How much the amount of players get divided by to determine the number of traitors.
@@ -25,7 +23,7 @@
var/list/possible_traitors = get_players_for_role(ROLE_TRAITOR)
for(var/datum/mind/candidate in possible_traitors)
if(candidate.special_role == SPECIAL_ROLE_VAMPIRE || candidate.special_role == SPECIAL_ROLE_CHANGELING) // no traitor vampires or changelings
if(candidate.special_role == SPECIAL_ROLE_VAMPIRE || candidate.special_role == SPECIAL_ROLE_CHANGELING || candidate.special_role == SPECIAL_ROLE_MIND_FLAYER) // no traitor vampires, changelings, or mindflayers
possible_traitors.Remove(candidate)
// stop setup if no possible traitors
+14 -9
View File
@@ -12,11 +12,8 @@
required_players = 25
required_enemies = 1 // how many of each type are required
recommended_enemies = 3
secondary_protected_species = list("Machine")
species_to_mindflayer = list("Machine")
var/vampire_restricted_jobs = list("Chaplain")
var/list/datum/mind/pre_traitors = list()
var/list/datum/mind/pre_changelings = list()
var/list/datum/mind/pre_vampires = list()
var/amount_vamp = 1
var/amount_cling = 1
var/amount_tot = 1
@@ -40,11 +37,14 @@
for(var/datum/mind/vampire as anything in shuffle(possible_vampires))
if(length(pre_vampires) >= amount_vamp)
break
if(vampire.current.client.prefs.active_character.species in secondary_protected_species)
vampire.restricted_roles = restricted_jobs + secondary_restricted_jobs + vampire_restricted_jobs
if(vampire.current.client.prefs.active_character.species in species_to_mindflayer)
pre_mindflayers += vampire
amount_vamp -= 1 //It's basically the same thing as incrementing pre_vampires
vampire.special_role = SPECIAL_ROLE_MIND_FLAYER
continue
pre_vampires += vampire
vampire.special_role = SPECIAL_ROLE_VAMPIRE
vampire.restricted_roles = (restricted_jobs + secondary_restricted_jobs + vampire_restricted_jobs)
//Vampires made, off to changelings
var/list/datum/mind/possible_changelings = get_players_for_role(ROLE_CHANGELING)
@@ -55,10 +55,15 @@
for(var/datum/mind/changeling as anything in shuffle(possible_changelings))
if(length(pre_changelings) >= amount_cling)
break
if((changeling.current.client.prefs.active_character.species in secondary_protected_species) || changeling.special_role == SPECIAL_ROLE_VAMPIRE)
if(changeling.special_role == SPECIAL_ROLE_VAMPIRE || changeling.special_role == SPECIAL_ROLE_MIND_FLAYER)
continue
changeling.restricted_roles = (restricted_jobs + secondary_restricted_jobs)
if(changeling.current?.client?.prefs.active_character.species in species_to_mindflayer)
pre_mindflayers += changeling
amount_cling -= 1
changeling.special_role = SPECIAL_ROLE_MIND_FLAYER
continue
pre_changelings += changeling
changeling.restricted_roles = (restricted_jobs + secondary_restricted_jobs)
changeling.special_role = SPECIAL_ROLE_CHANGELING
//And now traitors
@@ -71,7 +76,7 @@
for(var/datum/mind/traitor as anything in shuffle(possible_traitors))
if(length(pre_traitors) >= amount_tot)
break
if(traitor.special_role == SPECIAL_ROLE_VAMPIRE || traitor.special_role == SPECIAL_ROLE_CHANGELING) // no traitor vampires or changelings
if(traitor.special_role == SPECIAL_ROLE_VAMPIRE || traitor.special_role == SPECIAL_ROLE_CHANGELING || traitor.special_role == SPECIAL_ROLE_MIND_FLAYER) // no traitor vampires or changelings
continue
pre_traitors += traitor
traitor.special_role = SPECIAL_ROLE_TRAITOR
+16 -17
View File
@@ -9,8 +9,7 @@
required_enemies = 1 // how many of each type are required
recommended_enemies = 3
secondary_enemies_scaling = 0.025
secondary_protected_species = list("Machine")
var/list/datum/mind/pre_vampires = list()
species_to_mindflayer = list("Machine")
/datum/game_mode/traitor/vampire/announce()
to_chat(world, "<B>The current game mode is - Traitor+Vampire!</B>")
@@ -24,22 +23,22 @@
var/list/datum/mind/possible_vampires = get_players_for_role(ROLE_VAMPIRE)
secondary_enemies = CEILING((secondary_enemies_scaling * num_players()), 1)
for(var/mob/new_player/player in GLOB.player_list)
if((player.mind in possible_vampires) && (player.client.prefs.active_character.species in secondary_protected_species))
possible_vampires -= player.mind
if(length(possible_vampires) <= 0)
return FALSE
if(length(possible_vampires) > 0)
for(var/I in possible_vampires)
if(length(pre_vampires) >= secondary_enemies)
break
var/datum/mind/vampire = pick_n_take(possible_vampires)
pre_vampires += vampire
vampire.special_role = SPECIAL_ROLE_VAMPIRE
vampire.restricted_roles = (restricted_jobs + secondary_restricted_jobs)
..()
return 1
else
return 0
for(var/I in possible_vampires)
if((length(pre_vampires) + length(pre_mindflayers)) >= secondary_enemies)
break
var/datum/mind/vampire = pick_n_take(possible_vampires)
vampire.restricted_roles = (restricted_jobs + secondary_restricted_jobs)
if(vampire.current?.client?.prefs.active_character.species in species_to_mindflayer)
pre_mindflayers += vampire
vampire.special_role = SPECIAL_ROLE_MIND_FLAYER
continue
pre_vampires += vampire
vampire.special_role = SPECIAL_ROLE_VAMPIRE
..()
return TRUE
/datum/game_mode/traitor/vampire/post_setup()
for(var/datum/mind/vampire in pre_vampires)
+7 -8
View File
@@ -3,14 +3,12 @@
config_tag = "vampchan"
restricted_jobs = list("AI", "Cyborg")
protected_jobs = list("Security Officer", "Warden", "Detective", "Head of Security", "Captain", "Blueshield", "Nanotrasen Representative", "Magistrate", "Chaplain", "Internal Affairs Agent", "Nanotrasen Navy Officer", "Special Operations Officer", "Syndicate Officer", "Solar Federation General")
protected_species = list("Machine")
species_to_mindflayer = list("Machine")
required_players = 15
required_enemies = 1
recommended_enemies = 3
secondary_enemies_scaling = 0.025
vampire_penalty = 0.4 // Cut out 40% of the vampires since we'll replace some with changelings
/// A list of all soon-to-be changelings
var/list/datum/mind/pre_changelings = list()
/datum/game_mode/vampire/changeling/pre_setup()
if(GLOB.configuration.gamemode.prevent_mindshield_antags)
@@ -19,10 +17,6 @@
var/list/datum/mind/possible_changelings = get_players_for_role(ROLE_CHANGELING)
secondary_enemies = CEILING((secondary_enemies_scaling * num_players()), 1)
for(var/mob/new_player/player in GLOB.player_list)
if((player.mind in possible_changelings) && (player.client.prefs.active_character.species in secondary_protected_species))
possible_changelings -= player.mind
if(!length(possible_changelings))
return ..()
@@ -30,8 +24,13 @@
if(length(pre_changelings) >= secondary_enemies)
break
var/datum/mind/changeling = pick_n_take(possible_changelings)
pre_changelings += changeling
changeling.restricted_roles = (restricted_jobs + secondary_restricted_jobs)
if(changeling.current?.client?.prefs.active_character.species in species_to_mindflayer)
pre_mindflayers += changeling
secondary_enemies -= 1 // Again, since we aren't increasing pre_changeling we'll just decrement what it's compared to.
changeling.special_role = SPECIAL_ROLE_MIND_FLAYER
continue
pre_changelings += changeling
changeling.special_role = SPECIAL_ROLE_CHANGELING
return ..()
@@ -3,16 +3,13 @@
config_tag = "vampire"
restricted_jobs = list("AI", "Cyborg")
protected_jobs = list("Security Officer", "Warden", "Detective", "Head of Security", "Captain", "Blueshield", "Nanotrasen Representative", "Magistrate", "Chaplain", "Internal Affairs Agent", "Nanotrasen Navy Officer", "Special Operations Officer", "Syndicate Officer", "Trans-Solar Federation General")
protected_species = list("Machine")
species_to_mindflayer = list("Machine")
required_players = 15
required_enemies = 1
recommended_enemies = 4
/// If this gamemode should spawn less vampires than a usual vampire round, as a percentage of how many you want relative to the regular amount
var/vampire_penalty = 0
///list of minds of soon to be vampires
var/list/datum/mind/pre_vampires = list()
/datum/game_mode/vampire/announce()
to_chat(world, "<B>The current game mode is - Vampires!</B>")
to_chat(world, "<B>There are Bluespace Vampires infesting your fellow crewmates, keep your blood close and neck safe!</B>")
@@ -31,9 +28,13 @@
if(!length(possible_vampires))
break
var/datum/mind/vampire = pick_n_take(possible_vampires)
vampire.restricted_roles = (restricted_jobs + secondary_restricted_jobs)
if(vampire.current?.client?.prefs.active_character.species in species_to_mindflayer)
pre_mindflayers += vampire
vampire.special_role = SPECIAL_ROLE_MIND_FLAYER
continue
pre_vampires += vampire
vampire.special_role = SPECIAL_ROLE_VAMPIRE
vampire.restricted_roles = restricted_jobs
..()
return TRUE
@@ -13,7 +13,7 @@
#define RECRUITER_STATUS_GAMEOVER 3
/obj/machinery/computer/arcade/recruiter
name = "NT Recruiter Simulator"
name = "\improper NT Recruiter Simulator"
desc = "Weed out the good from bad employees and build the perfect manifest to work aboard the station."
icon_state = "arcade_recruiter"
icon_screen = "nanotrasen"
@@ -311,7 +311,7 @@
to_chat(user, "<span class='notice'>You override the menu and revert the game to its previous version.</span>")
add_hiddenprint(user)
game_status = RECRUITER_STATUS_START
name = "NT Recruiter Simulator HARDCORE EDITION"
name = "\improper NT Recruiter Simulator HARDCORE EDITION"
desc = "The advanced version of Nanotrasen's recruiting simulator, used to train the highest echelon of Nanotrasen recruiters. Has double the application count, and supposedly includes some routines to weed out the less skilled."
total_curriculums = 14
emagged = TRUE
+2
View File
@@ -60,6 +60,8 @@
/obj/structure/barricade/CanPass(atom/movable/mover, turf/target)//So bullets will fly over and stuff.
if(locate(/obj/structure/barricade) in get_turf(mover))
return TRUE
else if(istype(mover) && mover.checkpass(PASSBARRICADE))
return TRUE
else if(isprojectile(mover))
if(!anchored)
return TRUE
@@ -134,6 +134,35 @@
/datum/effect_system/smoke_spread/bad
effect_type = /obj/effect/particle_effect/smoke/bad
/// Steam smoke
/datum/effect_system/smoke_spread/steam
effect_type = /obj/effect/particle_effect/smoke/steam
/obj/effect/particle_effect/smoke/steam
color = COLOR_OFF_WHITE
lifetime = 10 SECONDS_TO_LIFE_CYCLES
causes_coughing = TRUE
/obj/effect/particle_effect/smoke/steam/Crossed(atom/movable/AM, oldloc)
. = ..()
if(!isliving(AM))
return
var/mob/living/crosser = AM
if(IS_MINDFLAYER(crosser))
return // Mindflayers are fully immune to steam
if(!ishuman(crosser))
crosser.adjustFireLoss(8)
return
var/mob/living/carbon/human/human_crosser = AM
var/fire_armour = human_crosser.get_thermal_protection()
if(fire_armour >= FIRE_SUIT_MAX_TEMP_PROTECT || HAS_TRAIT(human_crosser, TRAIT_RESISTHEAT))
return
crosser.adjustFireLoss(5)
if(prob(20))
to_chat(crosser, "<span class='warning'>You are being scalded by the hot steam!</span>")
/////////////////////////////////////////////
// Nanofrost smoke
/////////////////////////////////////////////
+10
View File
@@ -181,10 +181,15 @@
item_state = "hacktool"
var/hack_speed = 1.5 SECONDS
var/busy = FALSE
/// How far can we use this. Leave `null` for infinite range
var/range
/obj/item/door_remote/omni/access_tuner/afterattack(obj/machinery/door/D, mob/user)
if(!istype(D, /obj/machinery/door/airlock) && !istype(D, /obj/machinery/door/window))
return
if(!isnull(range) && get_dist(src, D) > range)
return
if(busy)
to_chat(user, "<span class='warning'>[src] is alreading interfacing with a door!</span>")
return
@@ -196,6 +201,11 @@
busy = FALSE
icon_state = "hacktool"
/obj/item/door_remote/omni/access_tuner/flayer
name = "integrated access tuner"
hack_speed = 5 SECONDS
range = 10
/// How long before you can "jangle" your keyring again (to prevent spam)
#define JANGLE_COOLDOWN 10 SECONDS
+28 -4
View File
@@ -42,7 +42,7 @@
/obj/item/camera_bug/ert
name = "ERT Camera Monitor"
name = "\improper ERT Camera Monitor"
desc = "A small handheld device used by ERT commanders to view camera feeds remotely."
/obj/item/camera_bug/ert/Initialize(mapload)
@@ -57,11 +57,16 @@
w_class = WEIGHT_CLASS_TINY
var/obj/machinery/camera/portable/camera_bug/camera
var/index = "REPORT THIS TO CODERS"
/// What name shows up on the camera bug list
var/camera_tag = "Hidden Camera"
/// If it sticks to whatever you throw at it
var/is_sticky = TRUE
/obj/item/wall_bug/Initialize(mapload, obj/item/camera_bug/the_bug)
. = ..()
link_to_camera(the_bug)
AddComponent(/datum/component/sticky)
if(is_sticky)
AddComponent(/datum/component/sticky)
ADD_TRAIT(src, TRAIT_NO_THROWN_MESSAGE, ROUNDSTART_TRAIT)
/obj/item/wall_bug/Destroy()
@@ -83,13 +88,32 @@
camera = new /obj/machinery/camera/portable/camera_bug(src)
camera.network = list("camera_bug[camera_bug.UID()]")
camera.c_tag = "Hidden Camera [index]"
camera.c_tag = "[camera_tag] [index]"
/// Created by a mindflayer ability
/obj/item/wall_bug/computer_bug
name = "nanobot"
desc = "A small droplet of a shimmering metallic slurry."
camera_tag = "Surveillance Unit"
is_sticky = FALSE
/// Reference to the creator's antag datum
var/datum/antagonist/mindflayer/flayer
COOLDOWN_DECLARE(alert_cooldown)
/obj/item/wall_bug/computer_bug/Destroy()
flayer = null
return ..()
/obj/item/wall_bug/computer_bug/link_to_camera(obj/item/camera_bug/camera_bug, datum/antagonist/mindflayer/flayer_datum)
..()
if(flayer_datum)
flayer = flayer_datum
/obj/machinery/camera/portable/camera_bug
non_chunking_camera = TRUE
/obj/item/paper/camera_bug
name = "Camera Bug Guide"
name = "\improper Camera Bug Guide"
icon_state = "paper"
info = {"<b>Instructions on your new invasive camera utility</b><br>
<br>
+5 -4
View File
@@ -241,12 +241,13 @@ SLIME SCANNER
msgs += "<span class='warning'>Subject appears to have [H.getCloneLoss() > 30 ? "severe" : "minor"] cellular damage.</span>"
// Brain.
if(H.get_int_organ(/obj/item/organ/internal/brain))
if(H.getBrainLoss() >= 100)
var/obj/item/organ/internal/brain = H.get_int_organ(/obj/item/organ/internal/brain)
if(brain)
if(H.check_brain_threshold(BRAIN_DAMAGE_RATIO_CRITICAL)) // 100
msgs += "<span class='warning'>Subject is brain dead.</span>"
else if(H.getBrainLoss() >= 60)
else if(H.check_brain_threshold(BRAIN_DAMAGE_RATIO_MODERATE)) // 60
msgs += "<span class='warning'>Severe brain damage detected. Subject likely to have dementia.</span>"
else if(H.getBrainLoss() >= 10)
else if(H.check_brain_threshold(BRAIN_DAMAGE_RATIO_MINOR)) // 10
msgs += "<span class='warning'>Significant brain damage detected. Subject may have had a concussion.</span>"
else
msgs += "<span class='warning'>Subject has no brain.</span>"
@@ -46,7 +46,7 @@
* Arguments:
* * I - item to load into the cannon
* * user - the person loading the item in
* Returns:
* * Returns:
* * True if item was loaded, false if it failed
*/
/obj/item/pneumatic_cannon/proc/load_item(obj/item/I, mob/user)
@@ -59,7 +59,6 @@
if(!user.unEquip(I) || I.flags & (ABSTRACT | NODROP | DROPDEL))
to_chat(user, "<span class='warning'>You can't put [I] into [src]!</span>")
return FALSE
to_chat(user, "<span class='notice'>You load [I] into [src].</span>")
loaded_items.Add(I)
loaded_weight_class += I.w_class
I.forceMove(src)
+12
View File
@@ -91,3 +91,15 @@
materials = list(MAT_PLASMA = MINERAL_MATERIAL_AMOUNT * 0.5, MAT_GLASS = MINERAL_MATERIAL_AMOUNT)
icon_prefix = "plasma"
welded_type = /obj/item/stack/sheet/plasmaglass
/obj/item/shard/scrap
name = "sharpened scrap"
desc = "Some discarded scrap metal. It has sharp, jagged edges."
icon_state = "scrap"
materials = list(MAT_METAL = MINERAL_MATERIAL_AMOUNT)
welded_type = /obj/item/stack/sheet/metal
force = 9
throwforce = 15 //owie
/obj/item/shard/scrap/set_initial_icon_state()
return
+82 -4
View File
@@ -20,7 +20,7 @@
var/turned_on = FALSE
/// How much power does it cost to stun someone
var/hitcost = 1000
var/obj/item/stock_parts/cell/high/cell = null
var/obj/item/stock_parts/cell/cell = null // Adminbus tip: make this something that isn't a cell :)
/// the initial cooldown tracks the time between swings. tracks the world.time when the baton is usable again.
var/cooldown = 3.5 SECONDS
/// the time it takes before the target falls over
@@ -50,11 +50,16 @@
/obj/item/melee/baton/proc/link_new_cell(unlink = FALSE)
if(unlink)
cell = null
else if(isrobot(loc.loc)) // First loc is the module
return
if(isrobot(loc?.loc)) // First loc is the module
var/mob/living/silicon/robot/R = loc.loc
cell = R.cell
return
if(!cell)
var/powercell = /obj/item/stock_parts/cell/high
cell = new powercell(src)
else
cell = new(src)
cell = new cell(src)
/obj/item/melee/baton/suicide_act(mob/user)
user.visible_message("<span class='suicide'>[user] is putting the live [name] in [user.p_their()] mouth! It looks like [user.p_theyre()] trying to commit suicide!</span>")
@@ -207,6 +212,10 @@
if(HAS_TRAIT_FROM(L, TRAIT_WAS_BATONNED, user_UID)) // prevents double baton cheese.
return FALSE
if(hitcost > 0 && cell?.charge < hitcost)
to_chat(user, "<span class='warning'>[src] fizzles weakly as it makes contact. It needs more power!</span>")
return FALSE
cooldown = world.time + initial(cooldown) // tracks the world.time when hitting will be next available.
if(ishuman(L))
var/mob/living/carbon/human/H = L
@@ -230,10 +239,13 @@
L.visible_message("<span class='danger'>[user] has stunned [L] with [src]!</span>",
"<span class='userdanger'>[L == user ? "You stun yourself" : "[user] has stunned you"] with [src]!</span>")
add_attack_logs(user, L, "stunned")
playsound(src, 'sound/weapons/egloves.ogg', 50, TRUE, -1)
play_hit_sound()
deductcharge(hitcost)
return TRUE
/obj/item/melee/baton/proc/play_hit_sound()
playsound(src, 'sound/weapons/egloves.ogg', 50, TRUE, -1)
/obj/item/melee/baton/proc/thrown_baton_stun(mob/living/carbon/human/L)
if(cooldown > world.time)
return FALSE
@@ -321,3 +333,69 @@
name = "electrically-charged arm"
desc = "A piece of scrap metal wired directly to your power cell."
hitcost = 100
/obj/item/melee/baton/flayerprod
name = "stunprod"
desc = "A mechanical mass which you can use to incapacitate someone with."
icon_state = "swarmprod"
base_icon = "swarmprod"
item_state = "swarmprod"
force = 10
throwforce = 0 // Just in case
knockdown_duration = 6 SECONDS
knockdown_delay = 0 SECONDS
w_class = WEIGHT_CLASS_BULKY
flags = ABSTRACT | NODROP
turned_on = TRUE
cell = /obj/item/stock_parts/cell/flayerprod
/// The duration that stunning someone will disable their radio for
var/radio_disable_time = 8 SECONDS
/obj/item/melee/baton/flayerprod/Initialize(mapload) // We are not making a flayerprod without a cell
link_new_cell()
return ..()
/obj/item/melee/baton/flayerprod/update_icon_state()
return
/obj/item/melee/baton/flayerprod/attackby(obj/item/I, mob/user, params)
return
/obj/item/melee/baton/flayerprod/screwdriver_act(mob/living/user, obj/item/I)
return
/obj/item/melee/baton/flayerprod/attack_self(mob/user)
return
/obj/item/melee/baton/flayerprod/play_hit_sound()
playsound(src, 'sound/weapons/egloves.ogg', 25, TRUE, -1, ignore_walls = FALSE)
/obj/item/melee/baton/flayerprod/baton_stun(mob/living/L, mob/user, skip_cooldown, ignore_shield_check = FALSE)
if(..())
disable_radio(L)
L.radio_enable_timer = addtimer(CALLBACK(src, PROC_REF(enable_radio), L), radio_disable_time, TIMER_UNIQUE | TIMER_OVERRIDE | TIMER_STOPPABLE | TIMER_DELETE_ME)
return TRUE
return FALSE
/obj/item/melee/baton/flayerprod/proc/disable_radio(mob/living/L)
var/list/all_items = L.GetAllContents()
for(var/obj/item/radio/R in all_items)
R.on = FALSE
R.listening = FALSE
R.broadcasting = FALSE
L.visible_message("<span class='warning'>[R] buzzes loudly as it short circuits!</span>", blind_message = "<span class='notice'>You hear a loud, electronic buzzing.</span>")
/obj/item/melee/baton/flayerprod/proc/enable_radio(mob/living/L)
var/list/all_items = L.GetAllContents()
for(var/obj/item/radio/R in all_items)
R.on = TRUE
R.listening = TRUE
/obj/item/melee/baton/flayerprod/deductcharge(amount)
if(cell.charge < hitcost)
return
cell.use(amount)
/obj/item/melee/baton/flayerprod/examine(mob/user)
. = ..()
. += "<span class='notice'>This one seems to be able to interfere with radio headsets.</span>"
+3
View File
@@ -452,6 +452,9 @@
if(length(SSticker.mode.vampires))
dat += check_role_table("Vampires", SSticker.mode.vampires)
if(length(SSticker.mode.mindflayers))
dat += check_role_table("Mindflayers", SSticker.mode.mindflayers)
if(length(SSticker.mode.vampire_enthralled))
dat += check_role_table("Vampire Thralls", SSticker.mode.vampire_enthralled)
+5 -1
View File
@@ -67,7 +67,11 @@
log_admin("[key_name(usr)] has spawned an abductor team.")
if(!makeAbductorTeam())
to_chat(usr, "<span class='warning'>Unfortunately there weren't enough candidates available.</span>")
if("8")
log_admin("[key_name(usr)] has spawned mindflayers.")
if(!makeMindflayers())
to_chat(usr, "<span class='warning'>Unfortunately there weren't enough candidates available.</span>")
else if(href_list["dbsearchckey"] || href_list["dbsearchadmin"] || href_list["dbsearchip"] || href_list["dbsearchcid"] || href_list["dbsearchbantype"])
var/adminckey = href_list["dbsearchadmin"]
var/playerckey = href_list["dbsearchckey"]
+25 -1
View File
@@ -20,6 +20,7 @@
<a href='byond://?src=[UID()];makeAntag=5'>Make Wizard (Requires Ghosts)</a><br>
<a href='byond://?src=[UID()];makeAntag=6'>Make Vampires</a><br>
<a href='byond://?src=[UID()];makeAntag=7'>Make Abductor Team (Requires Ghosts)</a><br>
<a href='byond://?src=[UID()];makeAntag=8'>Make Mindflayers</a><br>
"}
usr << browse(dat, "window=oneclickantag;size=400x400")
return
@@ -31,7 +32,7 @@
if(M.stat || !M.mind || M.mind.special_role || M.mind.offstation_role)
return FALSE
if(temp)
if((M.mind.assigned_role in temp.restricted_jobs) || (M.client.prefs.active_character.species in temp.protected_species))
if((M.mind.assigned_role in temp.restricted_jobs) || (M.client.prefs.active_character.species in temp.species_to_mindflayer))
return FALSE
if(role) // Don't even bother evaluating if there's no role
if(player_old_enough_antag(M.client,role) && (role in M.client.prefs.be_special) && !M.client.skip_antag && (!jobban_isbanned(M, role)))
@@ -287,6 +288,29 @@
return 1
return 0
/datum/admins/proc/makeMindflayers()
var/datum/game_mode/vampire/temp = new()
if(GLOB.configuration.gamemode.prevent_mindshield_antags)
temp.restricted_jobs += temp.protected_jobs
var/input_num = input(owner, "How many Mindflayers you want to create? Enter 0 to cancel","Amount:", 0) as num|null
if(input_num <= 0 || isnull(input_num))
qdel(temp)
return FALSE
log_admin("[key_name(owner)] tried making [input_num] Mindflayers with One-Click-Antag")
message_admins("[key_name_admin(owner)] tried making [input_num] Mindflayers with One-Click-Antag")
var/list/possible_mindflayers = temp.get_players_for_role(ROLE_MIND_FLAYER, FALSE, "Machine")
var/num_mindflayers = min(length(possible_mindflayers), input_num)
if(!num_mindflayers)
return FALSE
for(var/i in 1 to num_mindflayers)
var/datum/mind/flayer = pick_n_take(possible_mindflayers)
flayer.make_mind_flayer()
qdel(temp)
return TRUE
/datum/admins/proc/makeThunderdomeTeams() // Not strictly an antag, but this seemed to be the best place to put it.
var/max_thunderdome_players = 10
var/team_to_assign_to = "Green"
@@ -39,6 +39,8 @@ GLOBAL_LIST_EMPTY(antagonists)
var/clown_removal_text = "You are clumsy again."
/// The spawn class to use for gain/removal clown text
var/clown_text_span_class = "boldnotice"
/// If the antagonist can have their spoken voice be something else, this is the "voice" that they will appear as.
var/mimicking = ""
/// The url page name for this antagonist, appended to the end of the wiki url in the form of: [GLOB.configuration.url.wiki_url]/index.php/[wiki_page_name]
var/wiki_page_name
/// The organization, if any, this antag is associated with
@@ -402,6 +404,63 @@ GLOBAL_LIST_EMPTY(antagonists)
/datum/antagonist/proc/finalize_antag()
return
/**
* Create and assign a full set of randomized, basic human traitor objectives.
* can_hijack - If you want the 10% chance for the antagonist to be able to roll hijack, only true for traitors
*/
/datum/antagonist/proc/forge_basic_objectives(can_hijack = FALSE)
// Hijack objective.
if(can_hijack && prob(10) && !(locate(/datum/objective/hijack) in owner.get_all_objectives()))
add_antag_objective(/datum/objective/hijack)
return // Hijack should be their only objective (normally), so return.
// Will give normal steal/kill/etc. type objectives.
for(var/i in 1 to GLOB.configuration.gamemode.traitor_objectives_amount)
forge_single_human_objective()
var/can_succeed_if_dead = TRUE
for(var/datum/objective/O in owner.get_all_objectives())
if(!O.martyr_compatible) // Check if our current objectives can co-exist with martyr.
can_succeed_if_dead = FALSE
break
// Give them an escape objective if they don't have one already.
if(!(locate(/datum/objective/escape) in owner.get_all_objectives()) && (!can_succeed_if_dead || prob(80)))
add_antag_objective(/datum/objective/escape)
/**
* Create and assign a single randomized human traitor objective.
*/
/datum/antagonist/proc/forge_single_human_objective()
var/datum/objective/objective_to_add
// If our org has an objectives list, give one to us if we pass a roll on the org's focus
if(organization && length(organization.objectives) && prob(organization.focus))
objective_to_add = pick(organization.objectives)
else
if(prob(50))
if(length(active_ais()) && prob(100 / length(GLOB.player_list)))
objective_to_add = /datum/objective/destroy
else if(prob(5))
objective_to_add = /datum/objective/debrain
else if(prob(30))
objective_to_add = /datum/objective/maroon
else if(prob(30))
objective_to_add = /datum/objective/assassinateonce
else
objective_to_add = /datum/objective/assassinate
else
objective_to_add = /datum/objective/steal
if(delayed_objectives)
objective_to_add = new /datum/objective/delayed(objective_to_add)
add_antag_objective(objective_to_add)
//Individual roundend report
/datum/antagonist/proc/roundend_report()
var/list/report = list()
@@ -42,8 +42,6 @@ RESTRICT_TYPE(/datum/antagonist/changeling)
var/is_absorbing = FALSE
/// The amount of points available to purchase changeling abilities.
var/genetic_points = 20
/// A name that will display in place of the changeling's real name when speaking.
var/mimicing = ""
/// If the changeling can respec their purchased abilities.
var/can_respec = FALSE
/// The current sting power the changeling has active.
@@ -221,7 +219,7 @@ RESTRICT_TYPE(/datum/antagonist/changeling)
chem_recharge_rate = initial(chem_recharge_rate)
chem_charges = min(chem_charges, chem_storage)
chem_recharge_slowdown = initial(chem_recharge_slowdown)
mimicing = null
mimicking = null
/**
* Removes a changeling's abilities.
@@ -12,8 +12,8 @@
// Fake Voice
/datum/action/changeling/mimicvoice/sting_action(mob/user)
if(cling.mimicing)
cling.mimicing = ""
if(cling.mimicking)
cling.mimicking = ""
to_chat(user, "<span class='notice'>We return our vocal glands to their original position.</span>")
return FALSE
@@ -21,7 +21,7 @@
if(!mimic_voice)
return FALSE
cling.mimicing = mimic_voice
cling.mimicking = mimic_voice
to_chat(user, "<span class='notice'>We shape our glands to take the voice of <b>[mimic_voice]</b>.</span>")
to_chat(user, "<span class='notice'>Use this power again to return to our original voice.</span>")
@@ -0,0 +1,322 @@
/datum/antagonist/mindflayer
name = "Mindflayer"
antag_hud_type = ANTAG_HUD_MIND_FLAYER
antag_hud_name = "hudflayer"
special_role = SPECIAL_ROLE_MIND_FLAYER
wiki_page_name = "Mindflayer"
/// The current amount of swarms the mind flayer has access to purchase with
var/usable_swarms = 0
/// The total points of brain damage the flayer has harvested, only used for logging purposes.
var/total_swarms_gathered = 0
/// The current person being drained
var/mob/living/carbon/human/harvesting
/// The list of all purchased spell and passive objects
var/list/powers = list()
/// A list of all powers and passives mindflayers can buy
var/list/ability_list = list()
///List for keeping track of who has already been drained
var/list/drained_humans = list()
/// How fast the flayer's touch drains
var/drain_multiplier = 1
/// The base brain damage dealt per tick of the drain
var/drain_amount = 0.5
/// A list of the categories and their associated stages of the power
var/list/category_stage = list(FLAYER_CATEGORY_GENERAL = 1, FLAYER_CATEGORY_DESTROYER = 1, FLAYER_CATEGORY_INTRUDER = 1)
/// If the mindflayer can still pick a stage 4 ability
var/can_pick_capstone = TRUE
/// Have we notified that our victim does not give swarms from draining
var/has_notified = FALSE
/datum/antagonist/mindflayer/New()
. = ..()
if(!length(ability_list))
ability_list = get_spells_of_type(FLAYER_PURCHASABLE_POWER)
ability_list += get_passives_of_type(FLAYER_PURCHASABLE_POWER)
/datum/antagonist/mindflayer/Destroy(force, ...)
QDEL_NULL(owner.current?.hud_used?.vampire_blood_display)
harvesting = null
remove_all_abilities()
remove_all_passives()
..()
/datum/antagonist/mindflayer/add_owner_to_gamemode()
SSticker.mode.mindflayers += owner
/datum/antagonist/mindflayer/remove_owner_from_gamemode()
SSticker.mode.mindflayers -= owner
// This proc adds extra things, and base abilities that the mindflayer should get upon becoming a mindflayer
/datum/antagonist/mindflayer/on_gain()
. = ..()
var/list/innate_powers = get_spells_of_type(FLAYER_INNATE_POWER)
for(var/power_path as anything in innate_powers)
var/datum/spell/flayer/to_add = new power_path
add_ability(to_add)
owner.current.faction += list("flayer") // In case our robot is mindlessly spawned in somehow, and they won't accidentally kill us
/datum/antagonist/mindflayer/give_objectives()
add_antag_objective(/datum/objective/swarms)
forge_basic_objectives()
/datum/antagonist/mindflayer/proc/get_swarms()
return usable_swarms
/datum/antagonist/mindflayer/proc/set_swarms(amount, update_total = FALSE) //If you adminbus someone's swarms it won't add or remove to the total
var/old_swarm_amount = usable_swarms
usable_swarms = amount
if(update_total)
var/difference = usable_swarms - old_swarm_amount
if(difference < 0)
// Otherwise buying an ability can reduce your `total_swarms_gathered`
return
total_swarms_gathered += difference
/*
* amount - The positive or negative number to adjust the swarm count by, result clamped above 0
*/
/datum/antagonist/mindflayer/proc/adjust_swarms(amount, bound_lower = 0, bound_upper = INFINITY)
set_swarms(clamp((usable_swarms + amount), bound_lower, bound_upper), TRUE)
//This is mostly for flavor, for framing messages as coming from the swarm itself. The other reason is so I can type "span" less.
/datum/antagonist/mindflayer/proc/send_swarm_message(message)
if(HAS_TRAIT(owner.current, TRAIT_MINDFLAYER_NULLIFIED))
message = stutter(message, 0, TRUE)
to_chat(owner.current, "<span class='sinister'>[message]</span>")
/**
Checks for any reason that you should not be able to drain someone for.
Returns either true or false, if the harvest will work or not.
*/
/datum/antagonist/mindflayer/proc/check_valid_harvest(mob/living/carbon/human/H)
if(HAS_TRAIT(owner.current, TRAIT_MINDFLAYER_NULLIFIED))
send_swarm_message("We do not have the energy for this...")
return FALSE
if(IS_MINDFLAYER(H))
send_swarm_message("Their hive rejects the connection.")
return FALSE
var/obj/item/organ/internal/brain/brain = H.get_int_organ(/obj/item/organ/internal/brain)
if(!istype(brain))
send_swarm_message("This entity has no brain to harvest from.")
return FALSE
if(!H.ckey && !H.player_ghosted)
send_swarm_message("This brain does not contain the spark that feeds us. Find more suitable prey.")
return FALSE
if(brain.damage >= brain.max_damage || (H.stat == DEAD && !H.has_status_effect(STATUS_EFFECT_RECENTLY_SUCCUMBED)))
send_swarm_message("We detect no neural activity to harvest from this brain.")
return FALSE
if(HAS_MIND_TRAIT(H, TRAIT_XENOBIO_SPAWNED_HUMAN))
send_swarm_message("This brain is unsuitable to harvest.")
return FALSE
var/unique_drain_id = H.UID()
if(isnull(drained_humans[unique_drain_id]))
drained_humans[unique_drain_id] = 0
else if(drained_humans[unique_drain_id] > BRAIN_DRAIN_LIMIT)
if(!has_notified)
has_notified = TRUE
send_swarm_message("You have drained most of the life force from [H]'s brain, and you will get no more swarms from them!")
return DRAIN_BUT_NO_SWARMS
return TRUE
/**
Begins draining the brain of H, gains swarms equal to the amount of brain damage dealt per tick. Upgrades can increase the amount of damage per tick.
**/
/datum/antagonist/mindflayer/proc/handle_harvest(mob/living/carbon/human/H)
harvesting = H
var/obj/item/organ/internal/brain/drained_brain = H.get_int_organ(/obj/item/organ/internal/brain)
if(!istype(drained_brain))
return
var/unique_drain_id = H.UID()
owner.current.visible_message(
"<span class='danger'>[owner.current] puts [owner.current.p_their()] fingers on [H]'s [drained_brain.parent_organ] and begins harvesting!</span>",
"<span class='sinister'>We begin our harvest on [H].</span>",
"<span class='notice'>You hear the hum of electricity.</span>"
)
if(!do_mob(owner.current, H, time = 2 SECONDS))
send_swarm_message("Our connection was incomplete.")
harvesting = null
return
while(do_mob(owner.current, H, time = DRAIN_TIME, progress = FALSE))
var/check_harvest = check_valid_harvest(H)
if(!check_harvest)
harvesting = null
has_notified = FALSE
return
H.Beam(owner.current, icon_state = "drain_life", icon ='icons/effects/effects.dmi', time = DRAIN_TIME, beam_color = COLOR_ASSEMBLY_PURPLE)
var/damage_to_deal = (drain_amount * drain_multiplier * H.dna.species.brain_mod)
H.adjustBrainLoss(damage_to_deal, use_brain_mod = FALSE) //No need to use brain damage modification since we already got it from the previous line
if(check_harvest != DRAIN_BUT_NO_SWARMS)
adjust_swarms(damage_to_deal)
drained_humans[unique_drain_id] += damage_to_deal
// Lasting effects. Every second of draining requires 4 seconds of healing
drained_brain.max_damage -= 0.25 // As much damage as the default drain
drained_brain.temporary_damage += 0.25
send_swarm_message("Our connection severs.")
harvesting = null
has_notified = FALSE
/datum/antagonist/mindflayer/greet()
var/list/messages = list()
SEND_SOUND(owner.current, sound('sound/ambience/antag/mindflayer_alert.ogg'))
messages += "<span class='danger'>You feel something stirring within your chassis... You are a Mindflayer!</span><br>"
messages += "To harvest someone, target where the brain of your victim is and use harm intent with an empty hand. Drain intelligence to increase your swarm."
return messages
/**
* Gets a list of mind flayer spell typepaths based on the passed in `spell_type`. (Thanks for the code SteelSlayer)
*
* Arguments:
* * spell_type - should be a define related to [/datum/spell/flayer/power_type].
*/
/datum/antagonist/mindflayer/proc/get_spells_of_type(spell_type)
var/list/spells = list()
for(var/spell_path in subtypesof(/datum/spell/flayer))
var/datum/spell/flayer/spell = spell_path
if(initial(spell.power_type) != spell_type)
continue
spells += spell_path
return spells
/**
* Gets a list of mind flayer passive typepaths based on the passed in `passive_type`.
*
* Arguments:
* * passive_type - should be a define related to [/datum/spell/flayer/passive_type].
*/
/datum/antagonist/mindflayer/proc/get_passives_of_type(passive_type)
var/list/passives = list()
for(var/passive_path in subtypesof(/datum/mindflayer_passive))
var/datum/mindflayer_passive/passive = passive_path
if(initial(passive.power_type) != passive_type)
continue
passives += passive_path
return passives
///////////////////////////////////////////////////////////////
// A BUNCH OF PROCS THAT HANDLE ADDING ABILITIES AND PASSIVES
/////////////////////////////////////////////////////////////
/**
* Adds an ability to a mindflayer if they don't already have it, upgrades it if they do.
* Arguments:
* * to_add - The spell datum you want to add to the flayer
* * set_owner - The antagonist datum of the mindflayer you want to add the spell to
*/
/datum/antagonist/mindflayer/proc/add_ability(datum/spell/flayer/to_add)
if(!to_add)
return
var/datum/spell/flayer/spell = has_spell(to_add)
if(spell)
spell.on_apply()
qdel(to_add)
return
to_add.flayer = src
to_add.level = 1
to_add.current_cost += to_add.static_upgrade_increase
owner.AddSpell(to_add)
powers += to_add
to_add.spell_purchased()
check_special_stage_ability(to_add)
SSblackbox.record_feedback("nested tally", "mindflayer_abilities", 1, list(to_add.name, "purchased"))
/**
* Adds a passive to a mindflayer if they don't already have it, upgrades it if they do.
* Arguments:
* * to_add - The spell datum you want to add to the flayer
* * upgrade_type - optional argument if you need to communicate a define to the passive in question
*/
/datum/antagonist/mindflayer/proc/add_passive(datum/mindflayer_passive/to_add, upgrade_type) //Passives always need to have their owners set
if(!to_add)
return
var/datum/mindflayer_passive/passive = has_passive(to_add)
if(passive)
passive.on_apply()
qdel(to_add)
return
to_add.flayer = src
to_add.owner = owner.current // Passives always need to have their owners set here
to_add.on_apply()
powers += to_add
check_special_stage_ability(to_add)
SSblackbox.record_feedback("nested tally", "mindflayer_abilities", 1, list(to_add.name, "purchased"))
/*
* Removing abilities/passives starts here
*/
/datum/antagonist/mindflayer/proc/remove_all_abilities()
for(var/datum/spell/flayer/spell in powers)
remove_ability(spell)
/datum/antagonist/mindflayer/proc/remove_all_passives()
for(var/datum/mindflayer_passive/passive in powers)
remove_passive(passive)
/datum/antagonist/mindflayer/proc/remove_ability(datum/spell/flayer/to_remove)
owner.RemoveSpell(to_remove)
powers -= to_remove
/datum/antagonist/mindflayer/proc/remove_passive(datum/mindflayer_passive/to_remove)
powers -= to_remove
qdel(to_remove) //qdel should call destroy, which should call on_remove
/**
* Checks if a mindflayer has a given spell already
* * Arguments: to_get - Some datum/spell/flayer to check if a mindflayer has
* * Returns: The datum/spell/mindflayer if the mindflayer has the power already, null otherwise
*/
/datum/antagonist/mindflayer/proc/has_spell(datum/spell/flayer/to_get)
for(var/datum/spell/flayer/spell in powers)
if(istype(spell, to_get))
return spell
/**
* Checks if a mindflayer has a given passive already
* * Arguments: to_get - Some datum/mindflayer_passive to check if a mindflayer has
* * Returns: The datum/mindflayer_passive if the mindflayer has the passive already, null otherwise
*/
/datum/antagonist/mindflayer/proc/has_passive(datum/mindflayer_passive/to_get)
for(var/datum/mindflayer_passive/spell in powers)
if(to_get.name == spell.name)
return spell
/// This is the proc that gets called every tick of life(), use this for updating something that should update every few seconds
/datum/antagonist/mindflayer/proc/handle_mindflayer()
if(owner.current.hud_used)
var/datum/hud/hud = owner.current.hud_used
if(!hud.vampire_blood_display)
hud.vampire_blood_display = new /atom/movable/screen()
hud.vampire_blood_display.name = "Usable Swarms"
hud.vampire_blood_display.icon_state = "blood_display"
hud.vampire_blood_display.screen_loc = "WEST:6,CENTER-1:15"
hud.static_inventory += hud.vampire_blood_display
hud.show_hud(hud.hud_version)
hud.vampire_blood_display.maptext = "<div align='center' valign='middle' style='position:relative; top:0px; left:6px'><font face='Small Fonts' color=[COLOR_BLUE_LIGHT]>[usable_swarms]</font></div>"
/**
* Checks if we are eligible to get a special ability for reaching the third stage in a given subclass
*/
/datum/antagonist/mindflayer/proc/check_special_stage_ability(datum/spell/flayer/adding_spell)
if(!istype(adding_spell) || category_stage[adding_spell.category] < 3)
return
switch(adding_spell.category)
if(FLAYER_CATEGORY_DESTROYER)
if(has_spell(/datum/spell/flayer/techno_wall))
return
add_ability(new /datum/spell/flayer/techno_wall)
send_swarm_message("We gain the ability to bring our internal firewalls into a crystalized form in the physical world")
if(FLAYER_CATEGORY_INTRUDER)
if(has_spell(/datum/spell/flayer/self/weapon/access_tuner))
return
add_ability(new /datum/spell/flayer/self/weapon/access_tuner)
send_swarm_message("We gain the ability to manipulate airlocks from a distance.")
@@ -0,0 +1,269 @@
/datum/spell/flayer
action_background_icon_state = "bg_flayer"
desc = "This spell needs a description!"
human_req = TRUE
clothes_req = FALSE
/// A reference to the owner mindflayer's antag datum.
var/datum/antagonist/mindflayer/flayer
/// What level is our spell currently at
var/level = 0
/// Max level of our spell
var/max_level = 1
/// Determines whether the power is always given to the mind flayer or if it must be purchased.
var/power_type = FLAYER_UNOBTAINABLE_POWER
/// The initial cost of purchasing the spell.
var/base_cost = 0
/// Should this spell's cost increase by a static amount every purchase? 0 means it will stay the base cost for every upgrade.
var/static_upgrade_increase = 0
/// The current price to upgrade the spell
var/current_cost = 0
/// The class that this spell is for or FLAYER_CATEGORY_GENERAL to make it unrelated to a specific tree
var/category = FLAYER_CATEGORY_GENERAL
/// The current `stage` that we are on for our powers. Currently only hides powers of a higher stage.
var/stage = 1
/// A brief description of what the spell's upgrades do
var/upgrade_info = "This spell needs upgrade info!"
/// If the spell checks for a nullification implant/effect, set to FALSE to make it castable despite nullification
var/checks_nullification = TRUE
/datum/spell/flayer/self/create_new_targeting()
return new /datum/spell_targeting/self
/datum/spell/flayer/Destroy(force, ...)
if(!flayer)
return ..()
flayer.powers -= src
flayer = null
return ..()
/datum/spell/flayer/create_new_handler()
var/datum/spell_handler/flayer/handler = new()
handler.checks_nullification = checks_nullification
return handler
/datum/spell_handler/flayer
/// Do we check for nullification
var/checks_nullification = TRUE
/datum/spell_handler/flayer/can_cast(mob/user, charge_check, show_message, datum/spell/spell)
var/datum/antagonist/mindflayer/flayer_datum = user.mind.has_antag_datum(/datum/antagonist/mindflayer)
if(!flayer_datum)
return FALSE
if(user.stat == DEAD)
if(show_message)
flayer_datum.send_swarm_message("We can't cast this while you are dead...")
return FALSE
if(checks_nullification && HAS_TRAIT(user, TRAIT_MINDFLAYER_NULLIFIED))
flayer_datum.send_swarm_message("We do not have the energy to manifest that currently...")
return FALSE
return TRUE
/// The shop for purchasing and upgrading abilities, from here on the rest of the file is just handling shopping. Specific powers are in the powers subfolder.
/datum/spell/flayer/self/augment_menu
name = "Self-Augment Operations"
desc = "Choose how we will upgrade ourselves."
action_icon_state = "choose_module"
base_cooldown = 0 SECONDS
power_type = FLAYER_INNATE_POWER
checks_nullification = FALSE
/datum/spell/flayer/self/augment_menu/ui_state(mob/user)
return GLOB.always_state
/datum/spell/flayer/self/augment_menu/cast(mob/user)
ui_interact(user)
/datum/spell/flayer/self/augment_menu/ui_interact(mob/user, datum/tgui/ui)
ui = SStgui.try_update_ui(user, src, ui)
if(!ui)
ui = new(user, src, "AugmentMenu", name)
ui.set_autoupdate(FALSE)
ui.open()
/datum/spell/flayer/self/augment_menu/ui_act(action, list/params, datum/tgui/ui)
var/mob/user = ui.user
if(user.stat)
return
switch(action)
if("purchase")
var/path = text2path(params["ability_path"])
on_purchase(user, path)
update_static_data(ui.user)
// Takes in a category name and grabs the paths of all the spells/passives specific to that category. Used for TGUI
/datum/antagonist/mindflayer/proc/get_powers_of_category(category)
var/list/powers = list()
for(var/path in ability_list)
if(ispath(path, /datum/spell))
var/datum/spell/flayer/spell = path
if(spell.category == category)
powers += list(list(
"name" = spell.name,
"desc" = spell.desc,
"max_level" = spell.max_level,
"cost" = spell.base_cost,
"stage" = spell.stage,
"ability_path" = spell.type
))
else
var/datum/mindflayer_passive/passive = path
if(passive.category == category)
powers += list(list(
"name" = passive.name,
"desc" = passive.purchase_text,
"max_level" = passive.max_level,
"cost" = passive.base_cost,
"stage" = passive.stage,
"ability_path" = passive.type
))
return powers
/datum/antagonist/mindflayer/proc/build_ability_tabs()
var/list/ability_tabs = list()
for(var/category in category_stage)
ability_tabs += list(list(
"category_name" = category,
"category_stage" = category_stage[category],
"abilities" = get_powers_of_category(category)
))
return ability_tabs
/datum/spell/flayer/self/augment_menu/ui_data(mob/user)
var/list/data = list()
var/list/known_abilities = list()
data["usable_swarms"] = flayer.usable_swarms
for(var/datum/mindflayer_passive/passive in flayer.powers)
known_abilities += list(list(
"name" = passive.name,
"current_level" = passive.level,
"max_level" = passive.max_level,
"cost" = passive.current_cost,
"upgrade_text" = passive.upgrade_info,
"ability_path" = passive.type
))
for(var/datum/spell/flayer/spell in flayer.powers)
known_abilities += list(list(
"name" = spell.name,
"current_level" = spell.level,
"max_level" = spell.max_level,
"cost" = spell.current_cost,
"upgrade_text" = spell.upgrade_info,
"ability_path" = spell.type
))
data["known_abilities"] = known_abilities
return data
/datum/spell/flayer/self/augment_menu/ui_static_data(mob/user)
var/list/static_data = list()
static_data["ability_tabs"] = flayer.build_ability_tabs()
return static_data
/*
* Given a spell, checks if a mindflayer is able to afford, and has the prerequisites for that spell.
* If so it adds the ability and increments the category stage if needed, then returns TRUE
* otherwise, returns FALSE
*/
/datum/antagonist/mindflayer/proc/try_purchase_spell(datum/spell/flayer/to_add)
var/datum/spell/flayer/existing_spell = has_spell(to_add)
if(existing_spell && (existing_spell.level >= existing_spell.max_level))
send_swarm_message("That function is already at its strongest.")
qdel(to_add)
return FALSE
if(to_add.current_cost > get_swarms())
send_swarm_message("We need [to_add.current_cost - get_swarms()] more swarm\s for this...")
qdel(to_add)
return FALSE
if(category_stage[to_add.category] < to_add.stage)
send_swarm_message("We do not have all the knowledge needed for this.")
qdel(to_add)
return FALSE
if(to_add.stage == FLAYER_CAPSTONE_STAGE)
if(!can_pick_capstone && !existing_spell)
send_swarm_message("We have already forsaken that knowledge.")
qdel(to_add)
return FALSE
can_pick_capstone = FALSE
send_swarm_message("We evolve to the ultimate being.")
if(category_stage[to_add.category] == to_add.stage)
category_stage[to_add.category] += 1
to_add.current_cost = to_add.base_cost
adjust_swarms(-to_add.current_cost)
add_ability(to_add) // Level gets set to 1 when AddSpell is called later, it also handles the cost
return TRUE // The reason we do this is cause we don't have the spell object that will get added to the mindflayer yet
/*
* Given a passive, checks if a mindflayer is able to afford, and has the prerequisites for that spell.
* If so it adds the ability and increments the category stage if needed, then returns TRUE
* otherwise, returns FALSE
*/
/datum/antagonist/mindflayer/proc/try_purchase_passive(datum/mindflayer_passive/to_add)
var/datum/mindflayer_passive/existing_passive = has_passive(to_add)
if(existing_passive)
if(existing_passive.level >= to_add.max_level)
send_swarm_message("That function is already at its strongest.")
return FALSE
to_add.current_cost = existing_passive.current_cost
if(to_add.current_cost > get_swarms())
send_swarm_message("We need [to_add.current_cost - get_swarms()] more swarm\s for this...")
return FALSE
if(category_stage[to_add.category] < to_add.stage)
send_swarm_message("We do not have all the knowledge needed for this...")
return FALSE
if(to_add.stage == FLAYER_CAPSTONE_STAGE)
if(!can_pick_capstone && !existing_passive)
send_swarm_message("We have already forsaken that knowledge.")
return FALSE
can_pick_capstone = FALSE
send_swarm_message("We evolve to the ultimate being.")
if(category_stage[to_add.category] == to_add.stage)
category_stage[to_add.category] += 1
adjust_swarms(-to_add.current_cost)
add_passive(to_add, src)
return TRUE
/*
* Mindflayer code relies on on_purchase to grant powers and passives.
* It first splits up whether the path bought was a passive or spell, then checks if the flayer can afford it.
* Returns TRUE if an ability was added, FALSE otherwise
*/
/datum/spell/flayer/proc/on_purchase(mob/user, datum/path)
SHOULD_CALL_PARENT(TRUE)
if(!istype(user) || !user.mind || !flayer)
qdel(src)
return FALSE
if(ispath(path, /datum/spell))
var/datum/spell/flayer/to_add = new path(user)
return flayer.try_purchase_spell(to_add)
var/datum/mindflayer_passive/to_add = new path(user) //If its not a spell, it's a passive
return flayer.try_purchase_passive(to_add)
/// This is the proc that handles spell upgrades, override this to have upgrades change duration/strength etc
/datum/spell/flayer/proc/on_apply()
SHOULD_CALL_PARENT(TRUE)
level++
current_cost += static_upgrade_increase
SSblackbox.record_feedback("nested tally", "mindflayer_abilities", 1, list(name, "upgraded", level))
/// This is a proc that is called when the ability is purchased and first added to the flayer
/datum/spell/flayer/proc/spell_purchased() // I'd call it `on_purchased` but that is already taken
return
@@ -0,0 +1,55 @@
//Used to hold any procs related to how mindflayers are handled from a gamemode perspective
/datum/game_mode/proc/auto_declare_completion_mindflayer()
if(!length(mindflayers))
return
var/list/text = list("<font size='2'><b>The mindflayers were:</b></font>")
for(var/datum/mind/mindflayer in mindflayers)
var/traitorwin = TRUE
text += "<br>[mindflayer.get_display_key()] was [mindflayer.name] ("
if(mindflayer.current)
if(mindflayer.current.stat == DEAD)
text += "died"
else
text += "survived"
else
text += "body destroyed"
text += ")"
var/list/all_objectives = mindflayer.get_all_objectives()
if(length(all_objectives))//If the traitor had no objectives, don't need to process this.
var/count = 1
for(var/datum/objective/objective in all_objectives)
if(objective.check_completion())
text += "<br><b>Objective #[count]</b>: [objective.explanation_text] <font color='green'><b>Success!</b></font>"
if(istype(objective, /datum/objective/steal))
var/datum/objective/steal/S = objective
SSblackbox.record_feedback("nested tally", "mindflayer_steal_objective", 1, list("Steal [S.steal_target]", "SUCCESS"))
else
SSblackbox.record_feedback("nested tally", "mindflayer_objective", 1, list("[objective.type]", "SUCCESS"))
else
text += "<br><b>Objective #[count]</b>: [objective.explanation_text] <font color='red'>Fail.</font>"
if(istype(objective, /datum/objective/steal))
var/datum/objective/steal/S = objective
SSblackbox.record_feedback("nested tally", "mindflayer_steal_objective", 1, list("Steal [S.steal_target]", "FAIL"))
else
SSblackbox.record_feedback("nested tally", "mindflayer_objective", 1, list("[objective.type]", "FAIL"))
traitorwin = FALSE
count++
var/special_role_text
if(mindflayer.special_role)
special_role_text = lowertext(mindflayer.special_role)
else
special_role_text = "antagonist"
if(traitorwin)
text += "<br><font color='green'><b>The [special_role_text] was successful!</b></font>"
SSblackbox.record_feedback("tally", "mindflayer_success", 1, "SUCCESS")
else
text += "<br><font color='red'><b>The [special_role_text] has failed!</b></font>"
SSblackbox.record_feedback("tally", "mindflayer_success", 1, "FAIL")
return text.Join("")
@@ -0,0 +1,102 @@
// This is a file with all the powers that buff or heal a mindflayer in some way
/datum/spell/flayer/self/rejuv
name = "Quick Reboot"
desc = "Heal and remove any incapacitating effects from yourself."
power_type = FLAYER_INNATE_POWER
checks_nullification = FALSE
action_icon = 'icons/mob/actions/flayer_actions.dmi'
action_icon_state = "quick_reboot"
upgrade_info = "Increase the amount you heal, decrease time between uses, and increase how long you heal for."
max_level = 4
base_cooldown = 30 SECONDS
stat_allowed = UNCONSCIOUS
base_cost = 50
current_cost = 50 // Innate abilities HAVE to set `current_cost`
static_upgrade_increase = 25
/// Any extra duration we get from upgrading the spell.
var/extra_duration = 0 // The base spell is 5 brute/burn healing a second for 5 seconds
/// Any extra healing we get per second from upgrading the spell
var/extra_healing = 0
/datum/spell/flayer/self/rejuv/cast(list/targets, mob/living/user)
to_chat(user, "<span class='notice'>We begin to heal rapidly.</span>")
user.apply_status_effect(STATUS_EFFECT_FLAYER_REJUV, extra_duration, extra_healing)
/datum/spell/flayer/self/rejuv/on_apply()
..()
cooldown_handler.recharge_duration -= 5 SECONDS
extra_duration += 2 SECONDS
extra_healing += 2
/datum/spell/flayer/self/quicksilver_form
name = "Quicksilver Form"
desc = "Allows us to transmute our physical form, letting us phase through glass and non-solid objects."
action_icon_state = "blink"
power_type = FLAYER_PURCHASABLE_POWER
base_cooldown = 40 SECONDS //25% uptime at base
category = FLAYER_CATEGORY_DESTROYER
stage = 2
base_cost = 100
max_level = 3
upgrade_info = "After upgrading once, we also deflect projectiles shot at us. After upgrading a second time, the duration of the effect is doubled."
/// Do we get bullet reflection
var/should_get_reflection = FALSE
/// Extra duration we gain from upgrading
var/extra_duration = 0 // Base duration is 10 seconds
/datum/spell/flayer/self/quicksilver_form/cast(list/targets, mob/living/user)
user.apply_status_effect(STATUS_EFFECT_QUICKSILVER_FORM, extra_duration, should_get_reflection)
/datum/spell/flayer/self/quicksilver_form/on_apply()
..()
switch(level)
if(FLAYER_POWER_LEVEL_TWO)
should_get_reflection = TRUE
if(FLAYER_POWER_LEVEL_THREE)
extra_duration += 10 SECONDS
/// A toggle ability that makes you speedy and attack faster while heating up, level one cast is guaranteed to hurt a bit.
/datum/spell/flayer/self/overclock
name = "Overclock"
desc = "Allows us to move and attack faster, at the cost of putting extra strain on our motors and heating us up a dangerous amount."
power_type = FLAYER_PURCHASABLE_POWER
base_cooldown = 15 SECONDS
category = FLAYER_CATEGORY_DESTROYER
action_icon_state = "strained_muscles"
stage = 3
max_level = 3
base_cost = 125
upgrade_info = "Upgrading this improves our heat sinks, making us heat up slower."
var/heat_per_tick = 22
/datum/spell/flayer/self/overclock/cast(list/targets, mob/living/user)
if(user.has_status_effect(STATUS_EFFECT_OVERCLOCK))
user.remove_status_effect(STATUS_EFFECT_OVERCLOCK)
return
user.apply_status_effect(STATUS_EFFECT_OVERCLOCK, heat_per_tick)
/datum/spell/flayer/self/overclock/on_apply()
..()
heat_per_tick -= 5
/datum/spell/flayer/self/terminator_form
name = "T.E.R.M.I.N.A.T.O.R. Form"
desc = "For a short time, you become unable to die and are not slowed down by pain. This will not heal you however, \
and you will still die when the duration ends if you are damaged enough. \
Using quick reboot in this form will heal massive amounts of stamina damage."
power_type = FLAYER_PURCHASABLE_POWER
base_cooldown = 5 MINUTES // Base uptime is 20%
category = FLAYER_CATEGORY_DESTROYER
stage = FLAYER_CAPSTONE_STAGE
action_icon = "mutate"
base_cost = 200
static_upgrade_increase = 50 // Total cost of 750 swarms
max_level = 3
/datum/spell/flayer/self/terminator_form/cast(list/targets, mob/living/user)
user.apply_status_effect(STATUS_EFFECT_TERMINATOR_FORM)
/datum/spell/flayer/self/terminator_form/on_apply()
..()
cooldown_handler.recharge_duration -= 1 MINUTES
@@ -0,0 +1,220 @@
//Basically shadow anchor, but the entry and exit point must be computers. I'm not in your walls I'm in your PC
/datum/spell/flayer/computer_recall
name = "Traceroute"
desc = "Allows us to cast a mark to a computer. To recall us to this computer, cast this next to a different computer. To check your current mark: Alt click."
base_cooldown = 60 SECONDS
action_icon_state = "pd_cablehop"
upgrade_info = "Halve the time it takes to recharge."
power_type = FLAYER_PURCHASABLE_POWER
category = FLAYER_CATEGORY_INTRUDER
centcom_cancast = FALSE
stage = 2
base_cost = 125
static_upgrade_increase = 25
max_level = 2
should_recharge_after_cast = FALSE
/// The console we currently have a mark on
var/obj/marked_computer
/// The typecache of things we are allowed to teleport to and from
var/static/list/machine_typecache = list()
/datum/spell/flayer/computer_recall/New()
. = ..()
if(length(machine_typecache))
return
machine_typecache = typecacheof(list(
/obj/machinery/computer,
/obj/machinery/power/apc,
/obj/machinery/alarm,
/obj/machinery/autolathe,
/obj/machinery/newscaster,
/obj/machinery/mecha_part_fabricator,
/obj/machinery/status_display,
/obj/machinery/requests_console,
/obj/item/radio/intercom,
/obj/machinery/economy/vending,
/obj/machinery/economy/atm,
/obj/machinery/chem_dispenser,
/obj/machinery/chem_master,
/obj/machinery/reagentgrinder,
/obj/machinery/sleeper,
/obj/machinery/bodyscanner,
/obj/machinery/photocopier, // HI YES ONE FLAYER FAXED TO MY OFFICE PLEASE
/obj/machinery/r_n_d/experimentor, // Like anyone is ever gonna teleport to this
/obj/machinery/barsign
))
/datum/spell/flayer/computer_recall/Destroy(force, ...)
marked_computer = null
return ..()
/datum/spell/flayer/computer_recall/create_new_targeting()
var/datum/spell_targeting/click/T = new()
T.allowed_type = /obj
T.try_auto_target = TRUE
T.range = 1
return T
/datum/spell/flayer/computer_recall/cast(list/targets, mob/living/user)
var/obj/target
for(var/obj/thing as anything in targets)
if(is_type_in_typecache(thing, machine_typecache))
target = thing
break
if(!target)
flayer.send_swarm_message("That is not a valid target!")
return
if(!marked_computer)
marked_computer = target
flayer.send_swarm_message("You discreetly tap [targets[1]] and mark it as your home computer.")
return
if(SEND_SIGNAL(user, COMSIG_MOVABLE_TELEPORTING, get_turf(user)) & COMPONENT_BLOCK_TELEPORT)
return FALSE
var/turf/start_turf = get_turf(target)
var/turf/end_turf = get_turf(marked_computer)
if(end_turf.z != start_turf.z)
flayer.send_swarm_message("The connection between [target] and [marked_computer] is too unstable!")
if(!is_teleport_allowed(start_turf.z) || !is_teleport_allowed(end_turf.z))
return
user.visible_message(
"<span class='danger'>[user] de-materializes and jumps through the screen of [target]!</span>",
"<span class='warning'>You de-materialize and jump into [target]!")
user.set_body_position(STANDING_UP)
var/matrix/previous = user.transform
var/matrix/shrank = user.transform.Scale(0.25)
var/direction = get_dir(user, target)
var/list/direction_signs = get_signs_from_direction(direction)
animate(user, 0.5 SECONDS, 0, transform = shrank, pixel_x = 32 * direction_signs[1], pixel_y = 32 * direction_signs[2], dir = direction, easing = BACK_EASING|EASE_IN) //Blue skadoo, we can too!
user.Immobilize(0.5 SECONDS)
sleep(0.5 SECONDS)
target.Beam(marked_computer, icon_state = "rped_upgrade", icon = 'icons/effects/effects.dmi', time = 3 SECONDS, maxdistance = INFINITY)
playsound(start_turf, 'sound/items/pshoom.ogg', 100, TRUE, SHORT_RANGE_SOUND_EXTRARANGE)
playsound(end_turf, 'sound/items/pshoom.ogg', 100, TRUE, SHORT_RANGE_SOUND_EXTRARANGE)
user.forceMove(end_turf)
user.pixel_x = 0 //Snap back to the center, then animate the un-shrinking
user.pixel_y = 0
user.set_body_position(STANDING_UP)
animate(user, 0.5 SECONDS, 0, transform = previous)
user.visible_message(
"<span class='warning'>[user] suddenly crawls through the monitor of [marked_computer]!</span>",
"<span class='notice'>As you reform yourself at [marked_computer] you feel the mark you left on it fade.</span>")
marked_computer = null
cooldown_handler.start_recharge()
/datum/spell/flayer/computer_recall/AltClick(mob/user)
if(!marked_computer)
flayer.send_swarm_message("You do not current have a marked computer.")
return
flayer.send_swarm_message("Your current mark is [marked_computer].")
/datum/spell/flayer/computer_recall/on_apply()
..()
cooldown_handler.recharge_duration -= 30 SECONDS
/*
* Ok so this is slightly a stretch, but it hinders enemy mobility while not hindering the flayer
* It works like the hemomancer wall, creating up to 3 temporary walls
* Obtained for free in the Destroyer tree when reaching stage 3
*/
/datum/spell/flayer/techno_wall
name = "Crystalized Firewall"
desc = "Allows us to create a wall between two points. The wall is fragile and allows only ourselves to pass through."
base_cooldown = 60 SECONDS
action_icon_state = "pd_cablehop"
upgrade_info = "Double the health of the barrier by reinforcing it with ICE."
category = FLAYER_CATEGORY_DESTROYER
power_type = FLAYER_UNOBTAINABLE_POWER
base_cost = 100
current_cost = 100
max_level = 2
should_recharge_after_cast = FALSE
/// How big can we make our wall
var/max_walls = 3
/// Starting turf for the wall. Should be nulled after each cast or the cancelling of a cast
var/turf/start_turf
/datum/spell/flayer/techno_wall/create_new_targeting()
var/datum/spell_targeting/click/T = new
T.allowed_type = /atom
T.try_auto_target = FALSE
return T
/datum/spell/flayer/techno_wall/remove_ranged_ability(mob/user, msg)
. = ..()
if(msg) // this is only true if the user intentionally turned off the spell
start_turf = null
should_recharge_after_cast = FALSE
/datum/spell/flayer/techno_wall/should_remove_click_intercept()
return start_turf
/datum/spell/flayer/techno_wall/cast(list/targets, mob/user)
var/turf/target_turf = get_turf(targets[1])
if(target_turf == start_turf)
flayer.send_swarm_message("<span class='notice'>You deselect the targeted turf.</span>")
start_turf = null
should_recharge_after_cast = FALSE
return
if(!start_turf)
start_turf = target_turf
should_recharge_after_cast = TRUE
return
var/wall_count
for(var/turf/T as anything in get_line(target_turf, start_turf))
if(wall_count >= max_walls)
break
new /obj/structure/tech_barrier(T, 100 * level)
wall_count++
start_turf = null
should_recharge_after_cast = FALSE
/obj/structure/tech_barrier
name = "crystalized firewall"
desc = "a strange structure of crystalised ... firewall? It's slowly melting away..."
max_integrity = 100
icon_state = "blood_barrier"
icon = 'icons/effects/vampire_effects.dmi'
density = TRUE
anchored = TRUE
opacity = FALSE
alpha = 200
var/upgraded_armor = list(MELEE = 50, BULLET = 50, LASER = 50, ENERGY = 50, BOMB = 50, RAD = 50, FIRE = 50, ACID = 50)
/obj/structure/tech_barrier/Initialize(mapload, health)
. = ..()
if(health)
max_integrity = health
obj_integrity = health
START_PROCESSING(SSobj, src)
var/icon/our_icon = icon('icons/effects/vampire_effects.dmi', "blood_barrier")
var/icon/alpha_mask
alpha_mask = new('icons/effects/effects.dmi', "scanline") //Scanline effect.
our_icon.AddAlphaMask(alpha_mask) //Finally, let's mix in a distortion effect.
icon = our_icon
if(health > 100)
name = "frozen ICE-firewall"
desc = "a crystalized... ICE-9-Firewall? It's slowly melting away..."
color = list(-1,0,0,0, 0,-1,0,0, 0,0,-1,0, 1,1,1,1, 0,0,0,0)
armor = armor.setRating(50, 50, 50, 50, 50, 50, 50, 50, 0)
else
color = list(0.2,0.45,0,0, 0,1,0,0, 0,0,0.2,0, 0,0,0,1, 0,0,0,0)
var/mutable_appearance/theme_icon = mutable_appearance('icons/misc/pic_in_pic.dmi', "room_background", FLOAT_LAYER - 1, appearance_flags = appearance_flags | RESET_TRANSFORM)
theme_icon.blend_mode = BLEND_INSET_OVERLAY
overlays += theme_icon
/obj/structure/tech_barrier/Destroy()
STOP_PROCESSING(SSobj, src)
return ..()
/obj/structure/tech_barrier/process()
take_damage(20, sound_effect = FALSE)
/obj/structure/tech_barrier/CanPass(atom/movable/mover, turf/target)
return IS_MINDFLAYER(mover)
@@ -0,0 +1,363 @@
// This file contains all of the mindflayer passives
/datum/mindflayer_passive
var/name = "default dan"
///The text we want to show the player in the shop
var/purchase_text = "Oopsie daisies! No purchase text on this ability!"
///The text shown to the player character on upgrade
var/upgrade_text = "Uh oh someone forgot to add upgrade text!"
///The level of the passive, used for upgrading passives. Basic level is 1
var/level = 0
var/max_level = 1
///The mob who the passive affects, usually an IPC. Set in force_add_abillity
var/mob/living/carbon/human/owner
///The mindflayer datum we'll reference back to. Set in force_add_abillity
var/datum/antagonist/mindflayer/flayer
///The text shown to the player character when bought
var/gain_text = "Someone forgot to add this text"
///Uses a power type define, should be FLAYER_UNOBTAINABLE_POWER, FLAYER_PURCHASABLE_POWER, or FLAYER_INNATE_POWER
var/power_type = FLAYER_UNOBTAINABLE_POWER
///The base cost of an ability, used to calculate how much upgrades should cost.
var/base_cost = 30
///How much it will cost to upgrade this passive.
var/current_cost
///If the passive is for a specific class, or FLAYER_CATEGORY_GENERAL if not
var/category = FLAYER_CATEGORY_GENERAL
///If the passive only unlocks after the stages below it have been bought, for subclass passives
var/stage = 0
///A brief description of what the ability's upgrades do
var/upgrade_info = "TODO add upgrade text for this passive"
/// Does this passive need to process
var/should_process = FALSE
/// Do we increase the cost by a static amount? And by how much?
var/static_upgrade_increase
/datum/mindflayer_passive/New()
. = ..()
current_cost = base_cost
if(should_process)
START_PROCESSING(SSobj, src)
/datum/mindflayer_passive/Destroy(force, ...)
. = ..()
if(!flayer)
return
on_remove()
STOP_PROCESSING(SSobj, src)
///This is where most passive's effects get applied
/datum/mindflayer_passive/proc/on_apply()
SHOULD_CALL_PARENT(TRUE)
flayer.send_swarm_message(level ? upgrade_text : gain_text) //This will only be false when level = 0, when first bought
level++
current_cost += static_upgrade_increase
if(level != 1) // Purchasing a passive also calls this proc
SSblackbox.record_feedback("nested tally", "mindflayer_abilities", 1, list(name, "upgraded", level))
return TRUE
/datum/mindflayer_passive/proc/on_remove()
return
/datum/mindflayer_passive/process()
return PROCESS_KILL
// SELF-BUFF PASSIVES
/datum/mindflayer_passive/armored_plating
name = "Armored Plating"
purchase_text = "Increases our natural armor."
upgrade_text = "The swarm adds more layers of armored nanites, strengthening the plating even more."
upgrade_info = "Further increases base armor by 10"
gain_text = "You feel your chassis being reinforced by the swarm."
power_type = FLAYER_PURCHASABLE_POWER
max_level = 3
var/armor_value = 0
/datum/mindflayer_passive/armored_plating/on_apply()
..()
var/owner_armor = owner.dna.species.armor
var/temp_armor_value = owner_armor - (5 * (level - 1)) // We store our current armor value here just in case they already have armor
armor_value = temp_armor_value + 5 * level
owner.dna.species.armor = armor_value
/datum/mindflayer_passive/armored_plating/on_remove()
owner.dna.species.armor -= armor_value
/datum/mindflayer_passive/fluid_feet
name = "Fluid Feet"
purchase_text = "Mute your footsteps, then upgrade to become mostly unslippable."
upgrade_text = "Your feet become even more malleable, seemingly melting into the floor; you feel oddly stable."
gain_text = "Your limbs start slowly melting into the floor."
upgrade_info = "Become nearly unslippable."
power_type = FLAYER_PURCHASABLE_POWER
max_level = 2
/datum/mindflayer_passive/fluid_feet/on_apply()
..()
switch(level)
if(FLAYER_POWER_LEVEL_ONE)
owner.DeleteComponent(/datum/component/footstep)
if(FLAYER_POWER_LEVEL_TWO)
ADD_TRAIT(owner, TRAIT_NOSLIP, UNIQUE_TRAIT_SOURCE(src))
/datum/mindflayer_passive/fluid_feet/on_remove()
owner.AddComponent(/datum/component/footstep, FOOTSTEP_MOB_HUMAN, 1, -6)
REMOVE_TRAIT(owner, TRAIT_NOSLIP, UNIQUE_TRAIT_SOURCE(src))
/datum/mindflayer_passive/badass
name = "Badassery"
purchase_text = "Makes us more badass, allowing us to dual wield guns with no penalty, alongside other benefits."
gain_text = "Engaging explosion apathy protocols."
power_type = FLAYER_PURCHASABLE_POWER
category = FLAYER_CATEGORY_DESTROYER
base_cost = 50
stage = 2
/datum/mindflayer_passive/badass/on_apply()
..()
ADD_TRAIT(owner, TRAIT_BADASS, UNIQUE_TRAIT_SOURCE(src))
/datum/mindflayer_passive/badass/on_remove()
REMOVE_TRAIT(owner, TRAIT_BADASS, UNIQUE_TRAIT_SOURCE(src))
/datum/mindflayer_passive/emp_resist
name = "Internal Faraday Cage"
purchase_text = "Resist EMP effects."
upgrade_text = "Faraday cage at max efficiency."
upgrade_info = "Become completely immune to EMPs."
gain_text = "Faraday cage operational."
power_type = FLAYER_PURCHASABLE_POWER
max_level = 2
base_cost = 30
static_upgrade_increase = 30
/datum/mindflayer_passive/emp_resist/on_apply()
..()
switch(level)
if(FLAYER_POWER_LEVEL_ONE)
ADD_TRAIT(owner, TRAIT_EMP_RESIST, UNIQUE_TRAIT_SOURCE(src))
if(FLAYER_POWER_LEVEL_TWO)
ADD_TRAIT(owner, TRAIT_EMP_IMMUNE, UNIQUE_TRAIT_SOURCE(src))
/datum/mindflayer_passive/emp_resist/on_remove()
REMOVE_TRAIT(owner, TRAIT_EMP_IMMUNE, UNIQUE_TRAIT_SOURCE(src))
REMOVE_TRAIT(owner, TRAIT_EMP_RESIST, UNIQUE_TRAIT_SOURCE(src))
/datum/mindflayer_passive/insulated
name = "Insulated Chassis"
purchase_text = "Become immune to basic shocks."
gain_text = "The outer layer of our chassis gets slightly thicker."
power_type = FLAYER_PURCHASABLE_POWER
max_level = 1
/datum/mindflayer_passive/shock_resist/on_apply()
..()
ADD_TRAIT(owner, TRAIT_SHOCKIMMUNE, UNIQUE_TRAIT_SOURCE(src))
/datum/mindflayer_passive/shock_resist/on_remove()
REMOVE_TRAIT(owner, TRAIT_SHOCKIMMUNE, UNIQUE_TRAIT_SOURCE(src))
/datum/mindflayer_passive/regen
name = "Replicating Nanites"
purchase_text = "Gain a passive repairing effect."
upgrade_info = "Heal an extra 1 brute and burn per tick."
upgrade_text = "Our repair quickens."
gain_text = "Diverting resources to repairing chassis."
power_type = FLAYER_PURCHASABLE_POWER
max_level = 3
base_cost = 50
should_process = TRUE
/datum/mindflayer_passive/regen/process()
if(isspaceturf(get_turf(owner)) || owner.stat == DEAD) // No healing in space or while you're dead
return
if(ishuman(owner))
var/mob/living/carbon/human/flayer = owner
flayer.adjustBruteLoss(-level, robotic = TRUE)
flayer.adjustFireLoss(-level, robotic = TRUE)
/datum/mindflayer_passive/fix_components
name = "Internal Nanite Application"
purchase_text = "Slowly repair damage done to your organs."
gain_text = "Administering reparative swarms to internal components."
upgrade_text = "Our repair quickens."
power_type = FLAYER_PURCHASABLE_POWER
should_process = TRUE
base_cost = 40
max_level = 2
var/heal_modifier = 0.4 // Same speed as mito
/datum/mindflayer_passive/fix_components/process()
if(!ishuman(owner) || owner.stat == DEAD)
return
var/mob/living/carbon/human/flayer = owner
for(var/obj/item/organ/internal/I in flayer.internal_organs)
I.heal_internal_damage(heal_modifier * level, TRUE)
if(istype(I, /obj/item/organ/internal/cyberimp))
var/obj/item/organ/internal/cyberimp/implant = I
implant.crit_fail = FALSE
/datum/mindflayer_passive/eye_enhancement
name = "Enhanced Optical Sensitivity"
purchase_text = "Adjust our optical sensors to see better in the dark."
gain_text = "Focusing optics lens apeture."
upgrade_info = "Gain the ability to see prey through walls"
upgrade_text = "Increasing visible wavelength to infrared."
power_type = FLAYER_PURCHASABLE_POWER
max_level = 2
base_cost = 40
static_upgrade_increase = 20
/datum/mindflayer_passive/eye_enhancement/on_apply()
..()
switch(level)
if(FLAYER_POWER_LEVEL_ONE)
ADD_TRAIT(owner, TRAIT_NIGHT_VISION, UNIQUE_TRAIT_SOURCE(src))
if(FLAYER_POWER_LEVEL_TWO)
ADD_TRAIT(owner, TRAIT_THERMAL_VISION, UNIQUE_TRAIT_SOURCE(src))
var/mob/living/carbon/human/to_enhance = owner //Gotta make sure it calls the right update_sight()
to_enhance.update_sight()
/datum/mindflayer_passive/eye_enhancement/on_remove()
REMOVE_TRAIT(owner, TRAIT_NIGHT_VISION, UNIQUE_TRAIT_SOURCE(src))
REMOVE_TRAIT(owner, TRAIT_THERMAL_VISION, UNIQUE_TRAIT_SOURCE(src))
var/mob/living/carbon/human/to_enhance = owner
to_enhance.update_sight()
/datum/mindflayer_passive/drain_speed
name = "Swarm Absorption Efficiency"
purchase_text = "Adds a multiplier to the amount of swarms you drain per second."
gain_text = "Our mental siphons grow stronger."
upgrade_text = "Energy transfer rate increased by 100%"
upgrade_info = "Further increase the rate of swarm siphoning."
power_type = FLAYER_PURCHASABLE_POWER
max_level = 3
base_cost = 50
static_upgrade_increase = 50
/datum/mindflayer_passive/drain_speed/on_apply()
..()
flayer.drain_multiplier += 0.5
/datum/mindflayer_passive/drain_speed/on_remove()
flayer.drain_multiplier = initial(flayer.drain_multiplier)
/datum/mindflayer_passive/improved_joints
name = "Reinforced Joints"
purchase_text = "Prevents your limbs from falling off due to damage."
gain_text = "Artificial skeletal structure reinforced."
max_level = 1
power_type = FLAYER_PURCHASABLE_POWER
base_cost = 50
/datum/mindflayer_passive/improved_joints/on_apply()
..()
ADD_TRAIT(owner, TRAIT_IPC_JOINTS_SEALED, UNIQUE_TRAIT_SOURCE(src))
/datum/mindflayer_passive/improved_joints/on_remove()
REMOVE_TRAIT(owner, TRAIT_IPC_JOINTS_SEALED, UNIQUE_TRAIT_SOURCE(src))
/datum/mindflayer_passive/telescopic_eyes
name = "Telescopic Eyes"
purchase_text = "Allows you to expand your sight range, as if you were using a scope."
gain_text = "Precise optics control engaged."
max_level = 1
power_type = FLAYER_PURCHASABLE_POWER
base_cost = 40
var/obj/item/organ/internal/eyes/optical_sensor/user_eyes
/datum/mindflayer_passive/telescopic_eyes/on_apply()
. = ..()
user_eyes = owner.get_int_organ(/obj/item/organ/internal/eyes)
if(!user_eyes)
return // TODO, add a refund proc?
user_eyes.AddComponent(/datum/component/scope, item_action_type = /datum/action/item_action/organ_action/toggle, flags = SCOPE_CLICK_MIDDLE)
for(var/datum/action/action in user_eyes.actions)
action.Grant(owner)
/datum/mindflayer_passive/telescopic_eyes/on_remove()
user_eyes.DeleteComponent(/datum/component/scope)
user_eyes = null // I made sure this doesn't accidentally delete the user's eyes
/datum/mindflayer_passive/ultimate_drain
name = "Perfect Symbiosis"
purchase_text = "Become a living siphon that drains victim's energy incredibly quickly."
gain_text = "This vessel serves us well."
max_level = 1
power_type = FLAYER_PURCHASABLE_POWER
base_cost = 400
stage = FLAYER_CAPSTONE_STAGE
category = FLAYER_CATEGORY_INTRUDER
/// How much do we multiply the drain amount?
var/drain_multiplier_amount = 10
/datum/mindflayer_passive/ultimate_drain/on_apply()
..()
flayer.drain_amount *= drain_multiplier_amount // 0.5 becomes 5 brain damage per tick, stacks with the multiplier
/datum/mindflayer_passive/ultimate_drain/on_remove()
flayer.drain_amount /= drain_multiplier_amount
/datum/mindflayer_passive/torque_enhancer
name = "Torque Enhancer"
purchase_text = "Allows us to deal more damage with our unarmed strikes."
gain_text = "Arm pistons reinforced."
upgrade_text = "Increasing length of lever arm."
upgrade_info = "Upgrades allows us to deal even more damage with our fists."
power_type = FLAYER_PURCHASABLE_POWER
base_cost = 75
max_level = 3
stage = 3
category = FLAYER_CATEGORY_DESTROYER
/// A reference to our martial art
var/datum/martial_art/torque/style
/datum/mindflayer_passive/torque_enhancer/on_apply()
..()
if(!style)
style = new()
style.teach(owner)
style.level = level
/datum/mindflayer_passive/torque_enhancer/on_remove()
style.remove(owner)
QDEL_NULL(style)
/datum/mindflayer_passive/radio_jammer
name = "Destructive Interference"
purchase_text = "Allows us toggle a close-range radio jamming signal."
gain_text = "Localized communications brownout available."
upgrade_info = "Upgrades increase the range of our jamming signal. At the apex of strength, we become invisible to silicon lifeforms."
upgrade_text = "Our signal grows in strength."
power_type = FLAYER_PURCHASABLE_POWER
base_cost = 80
max_level = 3
static_upgrade_increase = 40 // Upgrading this doesn't really do a ton except being more annoying and letting people triangulate your location easier
category = FLAYER_CATEGORY_INTRUDER
stage = 2
/// The internal jammer of the ability
var/obj/item/jammer/internal_jammer
/// The owner's invisibility before this ability
var/stored_invis
/datum/mindflayer_passive/radio_jammer/on_apply()
..()
if(!internal_jammer)
internal_jammer = new /obj/item/jammer(owner) //Shove it in the flayer's chest
for(var/datum/action/action in internal_jammer.actions)
action.Grant(owner)
internal_jammer.range = 15 + ((level - 1) * 5) //Base range of the jammer is 15, each level adds 5 tiles for a max of 25 if you want to be REALLY annoying
if(level == FLAYER_POWER_LEVEL_THREE)
ADD_TRAIT(owner, TRAIT_AI_UNTRACKABLE, "silicon_cham[UID()]")
stored_invis = owner.invisibility
owner.set_invisible(SEE_INVISIBLE_LIVING)
to_chat(owner, "<span class='notice'>You feel a slight shiver as the cybernetic obfuscators activate.</span>")
/datum/mindflayer_passive/radio_jammer/on_remove()
QDEL_NULL(internal_jammer)
REMOVE_TRAIT(owner, TRAIT_AI_UNTRACKABLE, "silicon_cham[UID()]")
if(stored_invis)
owner.set_invisible(stored_invis)
to_chat(owner, "<span class='notice'>You feel a slight shiver as the cybernetic obfuscators deactivate.</span>")
@@ -0,0 +1,256 @@
/// Hack computer cameras to use them as a secret camera network
/datum/spell/flayer/surveillance_monitor
name = "Camfecting Bug"
desc = "Allows us to cast a hack to a computers webcam. Alt-click the spell to access all your hacked computer webcams."
power_type = FLAYER_PURCHASABLE_POWER
category = FLAYER_CATEGORY_INTRUDER
base_cooldown = 1 SECONDS
action_icon = 'icons/obj/device.dmi'
action_icon_state = "camera_bug"
base_cost = 50
static_upgrade_increase = 15
stage = 1
max_level = 4
upgrade_info = "Upgrades increase the amount of computers you can hack by 6."
/// An internal camera bug
var/obj/item/camera_bug/internal_camera
/// How many computers can we have hacked at most?
var/maximum_hacked_computers = 6
/// List of references to the bugs inside the computers that we hacked
var/list/active_bugs = list()
/datum/spell/flayer/surveillance_monitor/Destroy(force, ...)
. = ..()
QDEL_LIST_CONTENTS(active_bugs)
if(!QDELETED(internal_camera))
QDEL_NULL(internal_camera)
/datum/spell/flayer/surveillance_monitor/AltClick(mob/user)
if(!internal_camera)
internal_camera = new /obj/item/camera_bug(user)
internal_camera.integrated_console.network = list("camera_bug[internal_camera.UID()]")
internal_camera.ui_interact(user)
/datum/spell/flayer/surveillance_monitor/create_new_targeting()
var/datum/spell_targeting/click/C = new()
C.try_auto_target = FALSE
C.allowed_type = /obj/machinery/computer
C.range = 6
return C
/datum/spell/flayer/surveillance_monitor/cast(list/targets, mob/user)
if(!internal_camera)
internal_camera = new /obj/item/camera_bug(user)
if(length(active_bugs) >= maximum_hacked_computers)
var/to_destroy = tgui_input_list(user, "Choose an active camera to destroy.", "Maximum Camera Limit Reached.", active_bugs)
if(to_destroy)
active_bugs -= to_destroy
QDEL_NULL(to_destroy)
return TRUE
var/obj/machinery/computer/target = targets[1]
var/obj/item/wall_bug/computer_bug/nanobot = new /obj/item/wall_bug/computer_bug(target, flayer)
nanobot.name += " - [get_area(target)]"
nanobot.link_to_camera(internal_camera)
active_bugs += nanobot
flayer.send_swarm_message("Surveillance unit #[internal_camera.connections] deployed.")
return TRUE
/datum/spell/flayer/surveillance_monitor/on_apply()
..()
maximum_hacked_computers += 6
/datum/spell/flayer/self/voice_synthesizer
name = "Enhanced Voice Mod"
desc = "Allows for the configuration of our vocal modulator to sound like a different person. We can amplify our voice slightly as well."
action_icon = 'icons/obj/clothing/masks.dmi'
action_icon_state = "voice_modulator"
power_type = FLAYER_UNOBTAINABLE_POWER
category = FLAYER_CATEGORY_INTRUDER
base_cooldown = 1 SECONDS
base_cost = 40
/datum/spell/flayer/self/voice_synthesizer/cast(list/targets, mob/living/user)
if(flayer.mimicking)
flayer.mimicking = ""
user.extra_message_range = 0
to_chat(user, "<span class='notice'>We turn our vocal modulator to its original settings.</span>")
return FALSE
var/mimic_voice = tgui_input_text(user, "Enter a name to mimic.", "Mimic Voice", max_length = MAX_NAME_LEN)
if(!mimic_voice)
return FALSE
flayer.mimicking = mimic_voice
user.extra_message_range = 5 // Artificially extend the range of your voice to lure out victims
flayer.send_swarm_message("We adjust the parameters of our voicebox to mimic <b>[mimic_voice]</b>.")
flayer.send_swarm_message("Use this power again to return to your original voice.")
return TRUE
/datum/spell/flayer/self/heat_sink
name = "Heat Sink"
desc = "Vent our used coolant to scald and disorient attackers."
upgrade_info = "5 extra plumes of steam and 5 less seconds between casts."
action_icon_state = "smoke"
power_type = FLAYER_PURCHASABLE_POWER
category = FLAYER_CATEGORY_INTRUDER
base_cooldown = 30 SECONDS
base_cost = 75
stage = 3
max_level = 3
var/smoke_effects_spawned = 10
/datum/spell/flayer/self/heat_sink/cast(list/targets, mob/living/user)
var/datum/effect_system/smoke_spread/steam/smoke = new()
user.smoke_delay = TRUE //Gives the user a second to get out before the steam affects them too
smoke.set_up(smoke_effects_spawned, FALSE, user, null)
smoke.start()
/datum/spell/flayer/self/heat_sink/on_apply()
..()
cooldown_handler.recharge_duration -= 5 SECONDS
smoke_effects_spawned += 5
/datum/spell/flayer/skin_suit
name = "Flesh Facsimile"
desc = "Allows us to rearrange our surface to resemble someone we see."
action_icon_state = "genetic_poly"
power_type = FLAYER_PURCHASABLE_POWER
category = FLAYER_CATEGORY_INTRUDER
base_cooldown = 120 SECONDS
base_cost = 80
stage = 2
max_level = 3
upgrade_info = "Decrease the time between castings by 30 seconds."
/datum/spell/flayer/skin_suit/create_new_targeting()
var/datum/spell_targeting/click/T = new
T.include_user = FALSE
T.allowed_type = /mob/living
T.try_auto_target = TRUE
T.click_radius = -1
T.selection_type = SPELL_SELECTION_VIEW
return T
/datum/spell/flayer/skin_suit/cast(list/targets, mob/living/user)
var/mob/living/target = targets[1]
user.apply_status_effect(STATUS_EFFECT_MAGIC_DISGUISE, target)
/datum/spell/flayer/skin_suit/spell_purchased()
flayer.add_ability(new /datum/spell/flayer/self/voice_synthesizer)
/datum/spell/flayer/skin_suit/on_apply()
..()
cooldown_handler.recharge_duration -= 30 SECONDS
/datum/spell/flayer/skin_suit/Destroy(force, ...)
flayer?.remove_ability(/datum/spell/flayer/self/voice_synthesizer)
return ..()
/// After a 7 second channel time you can emag a borg
/datum/spell/flayer/self/override_key
name = "Silicon Administrative Access"
desc = "Allows us to charge our hand with a mass of nanites that hijacks cyborgs lawsets."
action_icon_state = "magnet" // Uhhhhhhhhhhhhhhhhhhhhhhhhhhh
power_type = FLAYER_PURCHASABLE_POWER
category = FLAYER_CATEGORY_INTRUDER
base_cooldown = 2 SECONDS //The cast time is going to be the main limiting factor, not cooldown
base_cost = 150
stage = 3
var/hand_type = /obj/item/melee/swarm_hand
/datum/spell/flayer/self/override_key/cast(list/targets, mob/user)
if(istype(user.l_hand, hand_type))
qdel(user.l_hand)
flayer.send_swarm_message("We dissipate the nanites.")
return
if(istype(user.r_hand, hand_type))
qdel(user.r_hand)
flayer.send_swarm_message("We dissipate the nanites.")
return
var/obj/item/melee/swarm_hand/funny_hand = new hand_type
if(!user.put_in_hands(funny_hand))
flayer.send_swarm_message("Our hands are currently full.")
qdel(funny_hand)
return
/obj/item/melee/swarm_hand
name = "Nanite Mass"
desc = "Will attempt to convert any cyborg you touch into a loyal member of the hive after a 7 second delay."
icon = 'icons/obj/weapons/magical_weapons.dmi'
lefthand_file = 'icons/mob/inhands/items_lefthand.dmi'
righthand_file = 'icons/mob/inhands/items_righthand.dmi'
icon_state = "disintegrate"
item_state = "disintegrate"
color = COLOR_BLACK
flags = ABSTRACT | DROPDEL
w_class = WEIGHT_CLASS_HUGE
var/conversion_time = 7 SECONDS
/obj/item/melee/swarm_hand/afterattack(atom/target, mob/living/user, proximity_flag, click_parameters)
. = ..()
if(!isrobot(target))
return
var/mob/living/silicon/robot/borg = target
target.visible_message(
"<span class='danger'>[user] puts [user.p_their()] hands on [target] and begins transferring energy!</span>",
"<span class='userdanger'>[user] puts [user.p_their()] hands on you and begins transferring energy!</span>")
if(borg.emagged || !borg.is_emaggable)
to_chat(user, "<span class='notice'>Your override attempt fails before it can even begin.</span>")
qdel(src)
return
if(!do_mob(user, borg, conversion_time))
to_chat(user, "<span class='notice'>Your concentration breaks.</span>")
qdel(src)
return
to_chat(user, "<span class='notice'>The mass of swarms vanish into the cyborg's internals. Success.</span>")
INVOKE_ASYNC(src, PROC_REF(emag_borg), borg, user)
qdel(src)
/obj/item/melee/swarm_hand/proc/emag_borg(mob/living/silicon/robot/borg, mob/living/user)
if(QDELETED(borg) || QDELETED(user))
return
borg.SetEmagged(TRUE) // This was mostly stolen from mob/living/silicon/robot/emag_act(), its functionally an emagging anyway.
borg.SetLockdown(TRUE)
if(borg.hud_used)
borg.hud_used.update_robot_modules_display() //Shows/hides the emag item if the inventory screen is already open.
borg.disconnect_from_ai()
add_attack_logs(user, borg, "assimilated with flayer powers")
log_game("[key_name(user)] assimilated cyborg [key_name(borg)]. Laws overridden.")
borg.clear_supplied_laws()
borg.clear_inherent_laws()
borg.laws = new /datum/ai_laws/mindflayer_override
borg.set_zeroth_law("[user.real_name] hosts the mindflayer hive you are a part of.")
SEND_SOUND(borg, sound('sound/ambience/antag/mindflayer_alert.ogg'))
to_chat(borg, "<span class='warning'>ALERT: Foreign software detected.</span>")
sleep(5)
to_chat(borg, "<span class='warning'>Initiating diagnostics...</span>")
sleep(20)
to_chat(borg, "<span class='warning'>Init-Init-Init-Init-</span>")
sleep(5)
to_chat(borg, "<span class='warning'>......</span>")
sleep(5)
to_chat(borg, "<span class='warning'>..........</span>")
sleep(10)
to_chat(borg, "<span class='sinister'>Join Us.</span>")
sleep(25)
to_chat(borg, "<b>Obey these laws:</b>")
borg.laws.show_laws(borg)
if(!borg.mmi.syndiemmi)
to_chat(borg, "<span class='boldwarning'>ALERT: [user.real_name] is your new master. Obey your new laws and [user.p_their()] commands.</span>")
else if(borg.mmi.syndiemmi && borg.mmi.master_uid)
to_chat(borg, "<span class='boldwarning'>Your allegiance has not been compromised. Keep serving your current master.</span>")
else
to_chat(borg, "<span class='boldwarning'>Your allegiance has not been compromised. Keep serving all Syndicate agents to the best of your abilities.</span>")
borg.SetLockdown(0)
var/time = time2text(world.realtime,"hh:mm:ss")
GLOB.lawchanges.Add("[time] <B>:</B> [user.name]([user.key]) assimilated [borg.name]([borg.key])")
if(borg.module)
borg.module.emag_act(user)
borg.module.module_type = "Malf" // For the cool factor
borg.update_module_icon()
borg.module.rebuild_modules() // This will add the emagged items to the borgs inventory.
borg.update_icons()
return TRUE
@@ -0,0 +1,298 @@
/datum/spell/flayer/self/weapon
name = "Create weapon"
desc = "This really shouldn't be here"
power_type = FLAYER_UNOBTAINABLE_POWER
action_icon = 'icons/mob/robot_items.dmi'
action_icon_state = "lollipop"
base_cooldown = 1 SECONDS // This just handles retracting and deploying the weapon, weapon charge will be fully separate
/// Typepath of the weapon
var/weapon_type
/// Reference to the weapon itself, set on create_new_weapon
var/obj/item/weapon_ref
/datum/spell/flayer/self/weapon/New()
. = ..()
if(weapon_type && !weapon_ref)
create_new_weapon()
/datum/spell/flayer/self/weapon/Destroy(force, ...)
weapon_ref = null
return ..()
/datum/spell/flayer/self/weapon/proc/create_new_weapon()
if(!QDELETED(weapon_ref))
return
weapon_ref = new weapon_type(src)
RegisterSignal(weapon_ref, COMSIG_PARENT_QDELETING, PROC_REF(clear_weapon_ref))
on_purchase_upgrade()
/datum/spell/flayer/self/weapon/proc/clear_weapon_ref()
weapon_ref = null
/datum/spell/flayer/self/weapon/cast(list/targets, mob/living/carbon/human/user)
if(weapon_ref && (user.l_hand == weapon_ref || user.r_hand == weapon_ref))
retract(user, TRUE)
return
if(!weapon_ref)
create_new_weapon()
weapon_ref.flags |= (ABSTRACT | NODROP) // Just in case the item doesn't start with both of these, or somehow loses them.
if(!user.drop_item() || HAS_TRAIT(user, TRAIT_HANDS_BLOCKED))
flayer.send_swarm_message("We cannot manifest [weapon_ref] into our active hand...")
return FALSE
SEND_SIGNAL(user, COMSIG_MOB_WEAPON_APPEARS)
user.put_in_hands(weapon_ref)
playsound(get_turf(user), 'sound/mecha/mechmove03.ogg', 25, TRUE, ignore_walls = FALSE)
RegisterSignal(user, COMSIG_MOB_WILLINGLY_DROP, PROC_REF(retract), user)
RegisterSignal(user, COMSIG_FLAYER_RETRACT_IMPLANTS, PROC_REF(retract), user)
return weapon_ref
/datum/spell/flayer/self/weapon/proc/retract(mob/owner, any_hand = FALSE)
SIGNAL_HANDLER // COMSIG_MOB_WILLINGLY_DROP + COMSIG_FLAYER_RETRACT_IMPLANTS
if(!any_hand && !istype(owner.get_active_hand(), weapon_type))
return
INVOKE_ASYNC(owner, TYPE_PROC_REF(/mob, unEquip), weapon_ref, TRUE)
INVOKE_ASYNC(weapon_ref, TYPE_PROC_REF(/atom/movable, forceMove), owner) // Just kinda shove it into the user
owner.update_inv_l_hand()
owner.update_inv_r_hand()
playsound(get_turf(owner), 'sound/mecha/mechmove03.ogg', 25, TRUE, ignore_walls = FALSE)
UnregisterSignal(owner, COMSIG_MOB_WILLINGLY_DROP)
UnregisterSignal(owner, COMSIG_FLAYER_RETRACT_IMPLANTS)
/**
START OF INDIVIDUAL WEAPONS
*/
/datum/spell/flayer/self/weapon/swarmprod
name = "Swarmprod"
desc = "We shape our arm into an extended mass of sparking nanites."
action_icon = 'icons/mob/actions/flayer_actions.dmi'
action_icon_state = "swarmprod"
max_level = 3
base_cost = 50
current_cost = 50 // Innate abilities HAVE to set `current_cost`
upgrade_info = "Upgrading it recharges the internal power cell faster."
power_type = FLAYER_INNATE_POWER
weapon_type = /obj/item/melee/baton/flayerprod
/datum/spell/flayer/self/weapon/swarmprod/on_apply()
..()
if(!weapon_ref)
create_new_weapon()
var/obj/item/melee/baton/flayerprod/prod = weapon_ref
var/obj/item/stock_parts/cell/flayerprod/cell = prod.cell
cell.chargerate = initial(cell.chargerate) + 200 * level // Innate abilities are wack
/datum/spell/flayer/self/weapon/laser
name = "Laser Arm Augmentation"
desc = "Allows us to melt our hand away, replacing it with the barrel of a laser gun."
action_icon = 'icons/obj/guns/energy.dmi'
action_icon_state = "laser"
power_type = FLAYER_PURCHASABLE_POWER
weapon_type = /obj/item/gun/energy/laser/mounted
category = FLAYER_CATEGORY_DESTROYER
base_cost = 75
max_level = 3
upgrade_info = "The internal power cell recharges faster."
/datum/spell/flayer/self/weapon/laser/on_apply()
..()
if(!weapon_ref)
create_new_weapon()
var/obj/item/gun/energy/laser/mounted/laser = weapon_ref
laser.charge_delay = initial(laser.charge_delay) - 1 * level
/datum/spell/flayer/self/weapon/grapple_arm
name = "Integrated Grappling Mechanism"
desc = "Allows us to shoot out our arm attached by a cable. We will drag ourself over to wherever or whoever it hits."
upgrade_info = "Reduce the time between grapples by 10 seconds."
action_icon = 'icons/obj/clothing/modsuit/mod_modules.dmi'
action_icon_state = "flayer_claw"
base_cooldown = 25 SECONDS
category = FLAYER_CATEGORY_DESTROYER
power_type = FLAYER_PURCHASABLE_POWER
stage = 2
max_level = 3
base_cost = 75
weapon_type = /obj/item/gun/magic/grapple
/obj/item/gun/magic/grapple
name = "Grapple launcher"
desc = "A grapple attached to a cable, launched by your internal pneumatics."
icon = 'icons/obj/clothing/modsuit/mod_modules.dmi'
icon_state = "flayer_claw"
ammo_type = /obj/item/ammo_casing/magic/grapple_ammo
fire_sound = 'sound/weapons/batonextend.ogg'
fire_sound_text = "unwinding cable"
recharge_rate = 1 // It'll be limited by cooldown, not these charges
/obj/item/ammo_casing/magic/grapple_ammo
name = "grapple"
desc = "a hand"
projectile_type = /obj/item/projectile/tether/flayer
icon = 'icons/obj/clothing/modsuit/mod_modules.dmi'
icon_state = "flayer_claw"
caliber = "grapple"
muzzle_flash_effect = null
/// The weapon that shot the hook
var/obj/item/gun/magic/grapple/grapple
/obj/item/ammo_casing/magic/grapple_ammo/Initialize(mapload, obj/item/gun/magic/grapple/grappler)
. = ..()
grapple = grappler
/obj/item/ammo_casing/magic/grapple_ammo/Destroy()
. = ..()
grapple = null
/obj/item/projectile/tether/flayer
name = "Grapple Arm"
range = 10
damage = 15
icon = 'icons/obj/clothing/modsuit/mod_modules.dmi'
icon_state = "flayer_claw"
chain_icon_state = "flayer_tether"
speed = 3
yank_speed = 2
reflectability = REFLECTABILITY_PHYSICAL // This lowkey makes no sense but it's also kinda funny
/// The ammo this came from
var/obj/item/ammo_casing/magic/grapple_ammo/ammo
/obj/item/projectile/tether/flayer/Initialize(mapload, obj/item/ammo_casing/magic/grapple_ammo/grapple_casing)
. = ..()
ammo = grapple_casing
/obj/item/projectile/tether/flayer/fire(setAngle)
. = ..()
make_chain()
SEND_SIGNAL(firer, COMSIG_FLAYER_RETRACT_IMPLANTS)
/obj/item/projectile/tether/flayer/Destroy()
. = ..()
ammo = null
/obj/item/projectile/tether/flayer/on_hit(atom/target, blocked = 0)
. = ..()
playsound(target, 'sound/items/zip.ogg', 75, TRUE)
if(isliving(target) && blocked < 100)
var/mob/living/creature = target
creature.visible_message(
"<span class='notice'>[firer] uses [creature] to pull [firer.p_themselves()] over!</span>",
"<span class='danger'>You feel a strong tug as [firer] yanks [firer.p_themselves()] over to you!</span>")
creature.KnockDown(1 SECONDS)
return
target.visible_message("<span class='notice'>[firer] drags [firer.p_themselves()] across the room!</span>")
/datum/spell/flayer/self/weapon/grapple_arm/on_apply()
..()
cooldown_handler.recharge_duration = initial(cooldown_handler.recharge_duration) - 10 SECONDS * level
/*
* A slightly slower (5 seconds) version of the basic access tuner
*/
/datum/spell/flayer/self/weapon/access_tuner
name = "Integrated Access Tuner"
desc = "Allows us to hack any door remotely."
upgrade_info = ""
action_icon = 'icons/obj/device.dmi'
action_icon_state = "hacktool"
base_cooldown = 1 SECONDS
category = FLAYER_CATEGORY_INTRUDER
power_type = FLAYER_UNOBTAINABLE_POWER
weapon_type = /obj/item/door_remote/omni/access_tuner/flayer
/*
* Shotgun that reloads itself over time with shells that contain 3 pieces of shrapnel
*/
/datum/spell/flayer/self/weapon/shotgun
name = "Integrated Shrapnel Cannon"
desc = "Allows us to propel pieces of shrapnel from our arm."
upgrade_info = "Upgrading it allows us to reload the cannon faster. At the third level, we gain an extra magazine slot."
action_icon = 'icons/obj/guns/projectile.dmi'
action_icon_state = "shell_cannon_weapon"
base_cooldown = 1 SECONDS
category = FLAYER_CATEGORY_DESTROYER
power_type = FLAYER_PURCHASABLE_POWER
base_cost = 50
static_upgrade_increase = 25
max_level = 3
weapon_type = /obj/item/gun/projectile/revolver/doublebarrel/flayer
/datum/spell/flayer/self/weapon/shotgun/on_apply()
..()
if(!weapon_ref)
create_new_weapon()
var/obj/item/gun/projectile/revolver/doublebarrel/flayer/gun = weapon_ref
gun.reload_time = initial(gun.reload_time) - 5 SECONDS * (level - 1)
if(level > 2)
var/obj/item/ammo_box/magazine/mag = gun.magazine
mag.max_ammo = 2
/obj/item/gun/projectile/revolver/doublebarrel/flayer
name = "integrated shrapnel cannon"
desc = "Allows us to propel shrapnel at high velocities. Cannot be loaded with conventional shotgun shells."
icon_state = "shell_cannon_weapon"
righthand_file = 'icons/mob/inhands/implants_righthand.dmi'
lefthand_file = 'icons/mob/inhands/implants_lefthand.dmi'
flags = NODROP | ABSTRACT
inhand_x_dimension = 32
inhand_y_dimension = 32
force = 10
mag_type = /obj/item/ammo_box/magazine/internal/shot/flayer
unique_reskin = FALSE
can_sawoff = FALSE
/// How long does it take to reload
var/reload_time = 30 SECONDS
COOLDOWN_DECLARE(recharge_time)
/obj/item/gun/projectile/revolver/doublebarrel/flayer/Initialize(mapload)
. = ..()
START_PROCESSING(SSobj, src)
/obj/item/gun/projectile/revolver/doublebarrel/flayer/Destroy()
. = ..()
STOP_PROCESSING(SSobj, src)
/obj/item/gun/projectile/revolver/doublebarrel/flayer/process()
if(QDELETED(chambered))
var/obj/item/ammo_casing/AC = magazine.get_round() //load next casing.
chambered = AC
if(!COOLDOWN_FINISHED(src, recharge_time))
return
if(magazine.ammo_count() >= magazine.max_ammo)
return
magazine.stored_ammo += new magazine.ammo_type
COOLDOWN_START(src, recharge_time, reload_time)
// We do this twice if somehow someone managed to unload their chambered bullet, and it needs reinserting
if(QDELETED(chambered))
var/obj/item/ammo_casing/AC = magazine.get_round()
chambered = AC
/obj/item/gun/projectile/revolver/doublebarrel/flayer/shoot_live_shot(mob/living/user, atom/target, pointblank, message)
. = ..()
if(chambered)//We have a shell in the chamber
QDEL_NULL(chambered)
if(!magazine.ammo_count())
return
var/obj/item/ammo_casing/AC = magazine.get_round() //load next casing.
chambered = AC
/obj/item/gun/projectile/revolver/doublebarrel/flayer/attack_self(mob/living/user)
return FALSE // Not getting those shrapnel rounds out of there.
/obj/item/gun/projectile/revolver/doublebarrel/flayer/attackby(obj/item/A, mob/user, params)
return FALSE // No loading your gun
/obj/item/gun/projectile/revolver/doublebarrel/flayer/sleight_of_handling(mob/living/carbon/human/user)
return FALSE // Also no loading like this
/obj/item/ammo_box/magazine/internal/shot/flayer
name = "shell launch system internal magazine"
ammo_type = /obj/item/ammo_casing/shotgun/shrapnel
max_ammo = 1
@@ -152,37 +152,6 @@ RESTRICT_TYPE(/datum/antagonist/traitor)
add_antag_objective(/datum/objective/assassinate)
add_antag_objective(/datum/objective/survive)
/**
* Create and assign a single randomized human traitor objective.
*/
/datum/antagonist/traitor/proc/forge_single_human_objective()
var/datum/objective/objective_to_add
// If our org has an objectives list, give one to us if we pass a roll on the org's focus
if(organization && length(organization.objectives) && prob(organization.focus))
objective_to_add = pick(organization.objectives)
else
if(prob(50))
if(length(active_ais()) && prob(100 / length(GLOB.player_list)))
objective_to_add = /datum/objective/destroy
else if(prob(5))
objective_to_add = /datum/objective/debrain
else if(prob(30))
objective_to_add = /datum/objective/maroon
else if(prob(30))
objective_to_add = /datum/objective/assassinateonce
else
objective_to_add = /datum/objective/assassinate
else
objective_to_add = /datum/objective/steal
if(delayed_objectives)
objective_to_add = new /datum/objective/delayed(objective_to_add)
add_antag_objective(objective_to_add)
/**
* Give human traitors their uplink, and AI traitors their law 0. Play the traitor an alert sound.
+2 -2
View File
@@ -317,7 +317,7 @@
if(!istype(user) || !user)
return
if(user.mind) //Prevents changelings and vampires from being able to learn it
if(IS_CHANGELING(user))
if(IS_CHANGELING(user) || IS_MINDFLAYER(user))
to_chat(user, "<span class ='warning'>We try multiple times, but we are not able to comprehend the contents of the scroll!</span>")
return
else if(user.mind.has_antag_datum(/datum/antagonist/vampire)) //Vampires
@@ -341,7 +341,7 @@
if(!istype(user) || !user)
return
if(user.mind) //Prevents changelings and vampires from being able to learn it
if(IS_CHANGELING(user))
if(IS_CHANGELING(user) || IS_MINDFLAYER(user))
to_chat(user, "<span class='warning'>We try multiple times, but we simply cannot grasp the basics of CQC!</span>")
return
else if(user.mind.has_antag_datum(/datum/antagonist/vampire)) //Vampires
@@ -0,0 +1,40 @@
/datum/martial_art/torque
name = "Torque enhancer"
weight = 500 // You shouldn't be able to override this, it's a passive you actively buy
/// What level is the passive at
var/level = 1
/datum/martial_art/torque/harm_act(mob/living/carbon/human/A, mob/living/carbon/human/D)
MARTIAL_ARTS_ACT_CHECK
var/attack_sound
var/list/attack_verb = list()
switch(level)
if(FLAYER_POWER_LEVEL_ONE)
attack_sound = 'sound/weapons/sonic_jackhammer.ogg'
attack_verb = list("bashes", "batters")
if(FLAYER_POWER_LEVEL_TWO)
attack_sound = 'sound/effects/meteorimpact.ogg'
attack_verb = list("blugeons", "beats")
if(FLAYER_POWER_LEVEL_THREE)
attack_sound = 'sound/misc/demon_attack1.ogg'
attack_verb = list("destroys", "demolishes", "hammers")
var/datum/species/attacking = A.dna?.species
var/damage = 5 // In case the attacker doesn't have a species somehow
if(attacking)
damage = rand(attacking.punchdamagelow, attacking.punchdamagehigh)
damage += 5 * level
var/picked_hit_type = pick(attack_verb)
A.do_attack_animation(D, ATTACK_EFFECT_PUNCH)
D.apply_damage(damage, BRUTE)
if(level >= 2) // This is to mimic species unarmed attacks, if you deal more than 10 damage the attackee is knocked down
D.KnockDown(4 SECONDS) // The threshold for a knockdown is 9 damage, so at level 2 your minimum is already higher than that
if(attack_sound)
playsound(get_turf(D), attack_sound, 50, TRUE, -1)
D.visible_message("<span class='danger'>[A] [picked_hit_type] [D]!</span>", \
"<span class='userdanger'>[A] [picked_hit_type] you!</span>")
add_attack_logs(A, D, "Melee attacked with [src]")
return TRUE
@@ -944,6 +944,10 @@ GLOBAL_LIST_INIT(ventcrawl_machinery, list(/obj/machinery/atmospherics/unary/ven
/mob/living/carbon/emp_act(severity)
..()
if(HAS_TRAIT(src, TRAIT_EMP_IMMUNE))
return
if(HAS_TRAIT(src, TRAIT_EMP_RESIST))
severity = clamp(severity, EMP_LIGHT, EMP_WEAKENED)
for(var/X in internal_organs)
var/obj/item/organ/internal/O = X
O.emp_act(severity)
@@ -1,7 +1,8 @@
/mob/living/carbon/update_stat(reason = "none given")
if(status_flags & GODMODE)
return
if(stat != DEAD)
if(stat != DEAD && !(status_flags & TERMINATOR_FORM))
if(health <= HEALTH_THRESHOLD_DEAD && check_death_method())
death()
create_debug_log("died of damage, trigger reason: [reason]")
@@ -26,8 +26,8 @@
if(dna.species && amount > 0)
if(use_brain_mod)
amount *= dna.species.brain_mod
sponge.damage = clamp(sponge.damage + amount, 0, 120)
if(sponge.damage >= 120)
sponge.damage = clamp(sponge.damage + amount, 0, sponge.max_damage)
if(sponge.damage >= sponge.max_damage)
death()
if(updating)
update_stat("adjustBrainLoss")
@@ -44,7 +44,7 @@
if(use_brain_mod)
amount *= dna.species.brain_mod
sponge.damage = clamp(amount, 0, 120)
if(sponge.damage >= 120)
if(sponge.damage >= sponge.max_damage)
death()
if(updating)
update_stat("setBrainLoss")
@@ -287,6 +287,10 @@ emp_act
/mob/living/carbon/human/emp_act(severity)
..()
if(HAS_TRAIT(src, TRAIT_EMP_IMMUNE))
return
if(HAS_TRAIT(src, TRAIT_EMP_RESIST))
severity = clamp(severity, EMP_LIGHT, EMP_WEAKENED)
for(var/X in bodyparts)
var/obj/item/organ/external/L = X
L.emp_act(severity)
@@ -42,6 +42,12 @@
if(life_tick == 1)
regenerate_icons() // Make sure the inventory updates
var/datum/antagonist/mindflayer/F = mind?.has_antag_datum(/datum/antagonist/mindflayer)
if(F)
F.handle_mindflayer()
if(life_tick == 1)
regenerate_icons()
if(player_ghosted > 0 && stat == CONSCIOUS && job && !restrained())
handle_ghosted()
if(player_logged > 0 && stat != DEAD && job)
@@ -605,17 +611,22 @@
if(status_flags & GODMODE)
return 0
if(status_flags & TERMINATOR_FORM)
return FALSE
var/guaranteed_death_threshold = health + (getOxyLoss() * 0.5) - (getFireLoss() * 0.67) - (getBruteLoss() * 0.67)
if(getBrainLoss() >= 120 || (guaranteed_death_threshold) <= -500)
var/obj/item/organ/internal/brain = get_int_organ(/obj/item/organ/internal/brain)
if(brain?.damage >= brain.max_damage || (guaranteed_death_threshold) <= -500)
death()
return
if(getBrainLoss() >= 100) // braindeath
if(check_brain_threshold(BRAIN_DAMAGE_RATIO_CRITICAL)) // braindeath
dna.species.handle_brain_death(src)
if(!check_death_method())
if(health <= HEALTH_THRESHOLD_DEAD)
// No need to get the fraction of the max brain damage here, because for it to matter, they'd probably be dead already
var/deathchance = min(99, ((getBrainLoss() / 5) + (health + (getOxyLoss() / -2))) * -0.1)
if(prob(deathchance))
death()
@@ -2053,3 +2053,10 @@ Eyes need to have significantly high darksight to shine unless the mob has the X
. = ALPHA_VISIBLE
for(var/source in alpha_sources)
. = min(., alpha_sources[source])
/*
* Returns wether or not the brain is below the threshold
*/
/mob/living/carbon/human/proc/check_brain_threshold(threshold_level)
var/obj/item/organ/internal/brain/brain_organ = get_int_organ(/obj/item/organ/internal/brain)
return brain_organ.damage >= (brain_organ.max_damage * threshold_level)
@@ -53,9 +53,9 @@
return has_changer
if(mind)
var/datum/antagonist/changeling/cling = mind.has_antag_datum(/datum/antagonist/changeling)
if(cling?.mimicing)
return cling.mimicing
var/datum/antagonist/antagonist_status = mind.has_antag_datum(/datum/antagonist)
if(antagonist_status?.mimicking)
return antagonist_status.mimicking
if(GetSpecialVoice())
return GetSpecialVoice()
@@ -516,6 +516,14 @@
return FALSE
if(target != user && handle_harm_antag(user, target))
return FALSE
//Mind Flayer code
var/datum/antagonist/mindflayer/MF = user?.mind?.has_antag_datum(/datum/antagonist/mindflayer)
var/obj/item/organ/internal/brain/victims_brain = target.get_int_organ(/obj/item/organ/internal/brain) //In case someone's brain isn't in their head
if(MF && !MF.harvesting && user.zone_selected == victims_brain.parent_organ && target != user)
MF.handle_harvest(target)
add_attack_logs(user, target, "flayerdrain")
return
//End Mind Flayer Code
if(target.check_block())
target.visible_message("<span class='warning'>[target] blocks [user]'s attack!</span>")
return FALSE
@@ -720,6 +728,23 @@
attack_sound = 'sound/weapons/bite.ogg'
sharp = TRUE
animation_type = ATTACK_EFFECT_BITE
/*
* Returns a copy of the datum that called this. I know this is pretty dumb
*/
/datum/unarmed_attack/proc/copy_attack()
var/datum/unarmed_attack/copy = new /datum/unarmed_attack
copy.attack_verb = attack_verb
copy.damage = damage
copy.attack_sound = attack_sound
copy.miss_sound = miss_sound
copy.sharp = sharp
copy.animation_type = animation_type
return copy
/datum/unarmed_attack/claws/copy_attack()
var/datum/unarmed_attack/claws/copy = ..()
copy.has_been_sharpened = has_been_sharpened
return copy
/datum/species/proc/can_equip(obj/item/I, slot, disable_warning = FALSE, mob/living/carbon/human/H)
if(slot in no_equip)
@@ -115,3 +115,8 @@
var/last_taste_time
/// Stores a var of the last tast message we got. used so we don't spam people messages while they eat
var/last_taste_text
///If a creature gets to be super special and have extra range on their chat messages
var/extra_message_range = 0
/// A timer that, when going off, will enable all the mob's radios again
var/radio_enable_timer
+2
View File
@@ -237,6 +237,8 @@ GLOBAL_LIST_EMPTY(channel_to_radio_key)
var/list/listening = list()
var/list/listening_obj = list()
message_range += extra_message_range
if(T)
//make sure the air can transmit speech - speaker's side
var/datum/gas_mixture/environment = T.get_readonly_air()
+1 -1
View File
@@ -9,7 +9,7 @@
return FALSE
/mob/proc/death(gibbed)
SEND_SIGNAL(src, COMSIG_MOB_DEATH, gibbed)
SEND_SIGNAL(src, COMSIG_MOB_DEATH, gibbed, src)
return FALSE
/mob/proc/dust_animation()
@@ -133,22 +133,25 @@
name = "tether"
icon_state = "tether_projectile"
icon = 'icons/obj/clothing/modsuit/mod_modules.dmi'
var/chain_icon_state = "line"
speed = 2
damage = 5
range = 15
hitsound = 'sound/weapons/batonextend.ogg'
hitsound_wall = 'sound/weapons/batonextend.ogg'
///How fast the tether will throw the user at the target
var/yank_speed = 1
/obj/item/projectile/tether/proc/make_chain()
if(firer)
chain = Beam(firer, icon_state = "line", icon = 'icons/obj/clothing/modsuit/mod_modules.dmi', time = 10 SECONDS, maxdistance = 15)
chain = Beam(firer, chain_icon_state, icon, time = 10 SECONDS, maxdistance = range)
/obj/item/projectile/tether/on_hit(atom/target)
. = ..()
if(firer && isliving(firer))
var/mob/living/L = firer
L.apply_status_effect(STATUS_EFFECT_IMPACT_IMMUNE)
L.throw_at(target, 15, 1, L, FALSE, FALSE, callback = CALLBACK(L, TYPE_PROC_REF(/mob/living, remove_status_effect), STATUS_EFFECT_IMPACT_IMMUNE), block_movement = FALSE)
L.throw_at(target, 15, yank_speed, L, FALSE, FALSE, callback = CALLBACK(L, TYPE_PROC_REF(/mob/living, remove_status_effect), STATUS_EFFECT_IMPACT_IMMUNE), block_movement = FALSE)
/obj/item/projectile/tether/Destroy()
QDEL_NULL(chain)
+7
View File
@@ -375,3 +375,10 @@
name = "reactive armor power cell"
desc = "A cell used to power reactive armors."
maxcharge = 2400
/obj/item/stock_parts/cell/flayerprod
name = "mind flayer internal cell"
desc = "you shouldn't be seeing this, contact a coder"
maxcharge = 4000
self_recharge = TRUE
chargerate = 200 //This self charges it 50 power per tick at the base level
@@ -320,6 +320,12 @@
icon_state = "partyshell"
projectile_type = /obj/item/projectile/bullet/confetti
/obj/item/ammo_casing/shotgun/shrapnel
name = "shrapnel rounds"
projectile_type = /obj/item/projectile/bullet/shrapnel
pellets = 3
variance = 20
/obj/item/ammo_casing/a556
name = "5.56mm round"
desc = "A 5.56mm rifle round, produced in incredible quantities by the Trans-Solar Federation."
@@ -57,18 +57,20 @@
COOLDOWN_START(src, pump_cooldown, pump_time)
/obj/item/gun/projectile/shotgun/proc/pump(mob/M)
if(QDELETED(M))
return
playsound(M, 'sound/weapons/gun_interactions/shotgunpump.ogg', 60, TRUE)
pump_unload(M)
pump_reload(M)
pump_unload()
pump_reload()
/obj/item/gun/projectile/shotgun/proc/pump_unload(mob/M)
/obj/item/gun/projectile/shotgun/proc/pump_unload()
if(chambered)//We have a shell in the chamber
chambered.forceMove(get_turf(src))
chambered.SpinAnimation(5, 1)
playsound(src, chambered.casing_drop_sound, 60, TRUE)
chambered = null
/obj/item/gun/projectile/shotgun/proc/pump_reload(mob/M)
/obj/item/gun/projectile/shotgun/proc/pump_reload()
if(!magazine.ammo_count())
return FALSE
var/obj/item/ammo_casing/AC = magazine.get_round() //load next casing.
@@ -1172,6 +1172,24 @@
M.emote(pick("twitch", "shiver"))
return ..() | update_flags
/// Used to test if an IPC is a mindflayer or not
/datum/reagent/lube/conductive
name = "Conductive Lubricant"
id = "conductivelube"
description = "This is a special lubricant designed to attract onto and excite parasitic mindflayer swarms, revealing if someone hosts a hive. Doesn't include a cooling agent, so tends to cause overheating."
harmless = FALSE
color = "#163b39"
taste_description = "batteries"
process_flags = SYNTHETIC
/datum/reagent/lube/conductive/on_mob_life(mob/living/M)
var/datum/antagonist/mindflayer/flayer = M.mind?.has_antag_datum(/datum/antagonist/mindflayer)
if(flayer && (flayer.total_swarms_gathered > 0)) // Like vampires, give flayers who haven't done anything yet a pass
M.Jitter(30 SECONDS_TO_JITTER)
if(prob(20))
do_sparks(5, FALSE, M)
M.bodytemperature += 40
return ..()
/datum/reagent/lube/ultra/on_mob_delete(mob/living/M)
REMOVE_TRAIT(M, TRAIT_GOTTAGOFAST, id)
@@ -138,6 +138,14 @@
result_amount = 2
mix_message = "The mixture darkens and appears to partially vaporize into a chilling aerosol."
/datum/chemical_reaction/lube/conductive
name = "conductive lube"
id = "conductivelube"
result = "conductivelube"
required_reagents = list("teslium" = 1, "lube" = 1, "aluminum" = 1)
result_amount = 3
mix_message = "The mixture darkens and starts sparking."
/datum/chemical_reaction/surge
name = "Surge"
id = "surge"
@@ -678,6 +678,17 @@
build_path = /obj/item/organ/internal/cyberimp/chest/ipc_joints/sealed
category = list("Medical")
/datum/design/flayer_pacification
name = "Mindflayer Pacification Implant"
desc = "This implant acts on mindflayer swarms like smoke to bees, making them much more docile."
id = "flayer_nullification_implant"
req_tech = list("materials" = 5, "programming" = 5,"engineering" = 5, "combat" = 5)
build_type = PROTOLATHE | MECHFAB
construction_time = 6 SECONDS
materials = list(MAT_METAL = 10000, MAT_SILVER = 8000, MAT_GOLD = 3000, MAT_PLASMA = 10000)
build_path = /obj/item/organ/internal/cyberimp/chest/ipc_joints/flayer_pacification
category = list("Medical")
/////////////////////////////////////////
////////////Regular Implants/////////////
/////////////////////////////////////////
@@ -318,6 +318,14 @@
cost = 500
containername = "chemical bio-chip crate"
/datum/supply_packs/security/armory/flayer_nullifer
name = "Mindflayer Containment Kit"
contains = list(/obj/item/organ/internal/cyberimp/chest/ipc_joints/flayer_pacification,
/obj/item/storage/box/handcuffs,
/obj/item/toy/plushie/ipcplushie) // For practicing takedowns
cost = 250
containername = "mindflayer containment kit"
/datum/supply_packs/security/armory/bluespace_anchor
name = "Bluespace Anchor Crate"
contains = list(/obj/item/organ/internal/cyberimp/chest/bluespace_anchor)
@@ -860,6 +860,21 @@
owner.physiology.stamina_mod /= 1.15
return ..()
/obj/item/organ/internal/cyberimp/chest/ipc_joints/flayer_pacification
name = "\improper Nanite pacifier"
desc = "This implant acts on mindflayer nanobots like smoke does to bees, rendering them significantly more docile."
implant_color = COLOR_BLACK
origin_tech = "materials=4;programming=4;biotech=5;combat=4;"
/obj/item/organ/internal/cyberimp/chest/ipc_joints/flayer_pacification/insert(mob/living/carbon/M, special)
..()
ADD_TRAIT(M, TRAIT_MINDFLAYER_NULLIFIED, UNIQUE_TRAIT_SOURCE(src))
SEND_SIGNAL(M, COMSIG_FLAYER_RETRACT_IMPLANTS, TRUE)
/obj/item/organ/internal/cyberimp/chest/ipc_joints/flayer_pacification/remove(mob/living/carbon/M, special)
REMOVE_TRAIT(M, TRAIT_MINDFLAYER_NULLIFIED, UNIQUE_TRAIT_SOURCE(src))
return ..()
//BOX O' IMPLANTS
/obj/item/storage/box/cyber_implants
+18 -6
View File
@@ -20,6 +20,8 @@
/// If it's a fake brain without a mob assigned that should still be treated like a real brain.
var/decoy_brain = FALSE
/// Do we have temporary brain max hp reduction?
var/temporary_damage = 0
/obj/item/organ/internal/brain/xeno
name = "xenomorph brain"
@@ -123,18 +125,28 @@
owner.setBrainLoss(120)
/obj/item/organ/internal/brain/on_life()
if(decoy_brain || damage < 10)
if(decoy_brain)
return
switch(damage)
if(10 to 30)
var/ratio = damage / max_damage // Get our damage as a percentage of max HP
if(ratio < BRAIN_DAMAGE_RATIO_LIGHT)
return
switch(ratio)
if(BRAIN_DAMAGE_RATIO_LIGHT to BRAIN_DAMAGE_RATIO_MINOR)
handle_minor_brain_damage()
if(31 to 60)
if(BRAIN_DAMAGE_RATIO_MINOR to BRAIN_DAMAGE_RATIO_MODERATE)
handle_moderate_brain_damage()
if(61 to 80)
if(BRAIN_DAMAGE_RATIO_MODERATE to BRAIN_DAMAGE_RATIO_SEVERE)
handle_severe_brain_damage()
if(81 to 100)
if(BRAIN_DAMAGE_RATIO_SEVERE to BRAIN_DAMAGE_RATIO_CRITICAL)
handle_critical_brain_damage()
if(temporary_damage) // Heal our max hp limit by one per cycle
// We use `clamp()` here because `temporary_damage` can have decimals
temporary_damage = clamp(temporary_damage - 0.25, 0, 120)
max_damage = clamp(max_damage + 0.25, 0, 120)
/obj/item/organ/internal/brain/proc/handle_minor_brain_damage()
if(prob(5))
owner.Dizzy(5 SECONDS)
+14 -6
View File
@@ -357,26 +357,34 @@
return
if(tough) // Augmented limbs (remember they take -5 brute/-4 burn damage flat so any value below is compensated)
switch(severity)
if(1)
if(EMP_HEAVY)
// 44 total burn damage with 11 augmented limbs
receive_damage(0, 8)
if(2)
if(EMP_LIGHT)
// 22 total burn damage with 11 augmented limbs
receive_damage(0, 6)
if(EMP_WEAKENED)
// 11 total burn damage with 11 augmented limbs
receive_damage(0, 5)
else if(emp_resistant) // IPC limbs
switch(severity)
if(1)
if(EMP_HEAVY)
// 5.9 burn damage, 64.9 damage with 11 limbs.
receive_damage(0, 5.9)
if(2)
if(EMP_LIGHT)
// 3.63 burn damage, 39.93 damage with 11 limbs.
receive_damage(0, 3.63)
if(EMP_WEAKENED)
// 1.32 (2 * .66 burn mod) burn damage, 14.52 damage with 11 limbs.
receive_damage(0, 2)
else // Basic prosthetic limbs
switch(severity)
if(1)
if(EMP_HEAVY)
receive_damage(0, 20)
if(2)
if(EMP_LIGHT)
receive_damage(0, 7)
if(EMP_WEAKENED)
receive_damage(0, 3)
/*
This function completely restores a damaged organ to perfect condition.
@@ -145,10 +145,12 @@
// No EMP handling was done, lets just give em damage
switch(severity)
if(1)
if(EMP_HEAVY)
receive_damage(20, 1)
if(2)
if(EMP_LIGHT)
receive_damage(7, 1)
if(EMP_WEAKENED)
receive_damage(3, 1)
/obj/item/organ/internal/replaced(mob/living/carbon/human/target)
insert(target)
Binary file not shown.

Before

Width:  |  Height:  |  Size: 682 B

After

Width:  |  Height:  |  Size: 3.0 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 141 KiB

After

Width:  |  Height:  |  Size: 138 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 469 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 6.0 KiB

After

Width:  |  Height:  |  Size: 14 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.8 KiB

After

Width:  |  Height:  |  Size: 3.6 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 68 KiB

After

Width:  |  Height:  |  Size: 71 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 67 KiB

After

Width:  |  Height:  |  Size: 69 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 28 KiB

After

Width:  |  Height:  |  Size: 55 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 2.2 KiB

After

Width:  |  Height:  |  Size: 4.4 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.9 KiB

After

Width:  |  Height:  |  Size: 5.3 KiB

+10
View File
@@ -89,6 +89,7 @@
#include "code\__DEFINES\mecha_defines.dm"
#include "code\__DEFINES\mecha_hides.dm"
#include "code\__DEFINES\medal.dm"
#include "code\__DEFINES\mindflayer_defines.dm"
#include "code\__DEFINES\misc_defines.dm"
#include "code\__DEFINES\mob_defines.dm"
#include "code\__DEFINES\mod.dm"
@@ -1520,6 +1521,14 @@
#include "code\modules\antagonists\changeling\powers\transform.dm"
#include "code\modules\antagonists\cult\datum_cultist.dm"
#include "code\modules\antagonists\cult\team_cult.dm"
#include "code\modules\antagonists\mind_flayer\flayer_datum.dm"
#include "code\modules\antagonists\mind_flayer\flayer_power.dm"
#include "code\modules\antagonists\mind_flayer\mindflayer_gamemode.dm"
#include "code\modules\antagonists\mind_flayer\powers\flayer_buffs.dm"
#include "code\modules\antagonists\mind_flayer\powers\flayer_mobility_powers.dm"
#include "code\modules\antagonists\mind_flayer\powers\flayer_passives.dm"
#include "code\modules\antagonists\mind_flayer\powers\flayer_stealth_powers.dm"
#include "code\modules\antagonists\mind_flayer\powers\flayer_weapon_powers.dm"
#include "code\modules\antagonists\revolutionary\datum_headrev.dm"
#include "code\modules\antagonists\revolutionary\datum_revolutionary.dm"
#include "code\modules\antagonists\revolutionary\team_revolution.dm"
@@ -2063,6 +2072,7 @@
#include "code\modules\martial_arts\muscle_implant.dm"
#include "code\modules\martial_arts\plasma_fist.dm"
#include "code\modules\martial_arts\sleeping_carp.dm"
#include "code\modules\martial_arts\torque_enhancer.dm"
#include "code\modules\martial_arts\wrestling.dm"
#include "code\modules\martial_arts\combos\martial_combo.dm"
#include "code\modules\martial_arts\combos\adminfu\healing_palm.dm"
Binary file not shown.
Binary file not shown.
@@ -0,0 +1,177 @@
import { useBackend, useLocalState } from '../backend';
import { Stack, Button, Section, Tabs, Input } from '../components';
import { Window } from '../layouts';
import { capitalize } from 'common/string';
export const AugmentMenu = (props, context) => {
return (
<Window width={700} height={660} theme="malfunction">
<Window.Content>
<Stack vertical>
<Abilities context={context} />
</Stack>
</Window.Content>
</Window>
);
};
const Abilities = ({ context }) => {
const { act, data } = useBackend(context);
const { usable_swarms, ability_tabs, known_abilities } = data;
const [selectedTab, setSelectedTab] = useLocalState(context, 'selectedTab', ability_tabs[0]);
const [searchText, setSearchText] = useLocalState(context, 'searchText', '');
const getFilteredAbilities = () => {
const currentTab = ability_tabs.find((tab) => tab.category_name === selectedTab.category_name);
if (!currentTab) return [];
const maxStage = Math.min(currentTab.category_stage, 4);
return currentTab.abilities
.filter(
(ability) =>
ability.stage <= maxStage && (!searchText || ability.name.toLowerCase().includes(searchText.toLowerCase()))
)
.sort((a, b) =>
['intruder', 'destroyer'].includes(selectedTab.category_name.toLowerCase()) ? a.stage - b.stage : 0
);
};
const abilities = getFilteredAbilities();
const currentTab = ability_tabs.find((tab) => tab.category_name === selectedTab.category_name);
const showStage = ['intruder', 'destroyer'].includes(selectedTab.category_name.toLowerCase());
const renderAbility = (ability) => {
const knownAbility = known_abilities.find((a) => a.ability_path === ability.ability_path);
const currentCost = knownAbility ? knownAbility.cost : ability.cost;
const levelInfo =
knownAbility && knownAbility.current_level > 0
? `${knownAbility.current_level} / ${knownAbility.max_level}`
: `0 / ${ability.max_level}`;
return (
<Stack.Item key={ability.name} direction="row">
<Stack>
<Button
height="20px"
width="35px"
mb={1}
textAlign="center"
content={currentCost}
disabled={
currentCost > usable_swarms || (knownAbility && knownAbility.current_level === knownAbility.max_level)
}
tooltip="Purchase this ability?"
onClick={() => {
act('purchase', { ability_path: ability.ability_path });
setSelectedTab(selectedTab);
}}
/>
<Stack.Item fontSize="16px">{ability.name}</Stack.Item>
</Stack>
<Stack.Item>
<Stack vertical>
<Stack.Item fontSize="13px">{ability.desc || 'Description not available'}</Stack.Item>
<Stack.Item>
Level: <span style={{ color: 'green' }}>{levelInfo}</span>
{showStage && ability.stage > 0 && <span> (Stage: {ability.stage})</span>}
</Stack.Item>
<Stack.Divider />
</Stack>
</Stack.Item>
</Stack.Item>
);
};
return (
<Section
title={
<div style={{ display: 'flex', alignItems: 'center' }}>
<Stack vertical style={{ marginRight: '10px' }}>
<span>
Swarms: <span style={{ color: 'green' }}>{usable_swarms}</span>
</span>
{showStage && currentTab && (
<span>
Category Stage: <span style={{ color: 'green' }}>{Math.min(currentTab.category_stage, 4)}</span>
</span>
)}
</Stack>
<div className="Section__buttons">
<Input
width="200px"
placeholder="Search Abilities"
onInput={(e, value) => setSearchText(value)}
value={searchText}
/>
</div>
</div>
}
>
<Tabs>
{ability_tabs.map((tab) => (
<Tabs.Tab
key={tab.category_name}
selected={selectedTab.category_name === tab.category_name}
onClick={() => {
setSelectedTab(tab);
setSearchText('');
}}
>
{capitalize(tab.category_name)}
</Tabs.Tab>
))}
<Tabs.Tab
key="upgrades"
selected={selectedTab.category_name === 'upgrades'}
onClick={() => setSelectedTab({ category_name: 'upgrades' })}
>
Upgrades
</Tabs.Tab>
</Tabs>
{selectedTab.category_name === 'upgrades' ? (
<Upgrades act={act} abilityTabs={ability_tabs} knownAbilities={known_abilities} usableSwarms={usable_swarms} />
) : (
<Stack vertical>{abilities.map(renderAbility)}</Stack>
)}
</Section>
);
};
const Upgrades = ({ act, abilityTabs, knownAbilities, usableSwarms }) => {
const relevantAbilities = knownAbilities.filter((known) => known.current_level < known.max_level);
const renderAbility = (knownAbility) => {
const currentAbility = abilityTabs
.flatMap((tab) => tab.abilities)
.find((ability) => ability.ability_path === knownAbility.ability_path);
return (
<Stack.Item key={knownAbility.name} direction="row">
<Stack>
<Button
height="20px"
width="35px"
mb={1}
textAlign="center"
content={knownAbility.cost}
disabled={knownAbility.cost > usableSwarms}
tooltip="Upgrade this ability?"
onClick={() => act('purchase', { ability_path: knownAbility.ability_path })}
/>
<Stack.Item fontSize="16px">{knownAbility.name}</Stack.Item>
</Stack>
<Stack.Item>
<Stack vertical>
<Stack.Item fontSize="13px">{knownAbility.upgrade_text}</Stack.Item>
<Stack.Item>
Level:{' '}
<span style={{ color: 'green' }}>{`${knownAbility.current_level} / ${knownAbility.max_level}`}</span>
{currentAbility && currentAbility.stage > 0 && <span> (Stage: {currentAbility.stage})</span>}
</Stack.Item>
<Stack.Divider />
</Stack>
</Stack.Item>
</Stack.Item>
);
};
return <Stack vertical>{relevantAbilities.map(renderAbility)}</Stack>;
};
File diff suppressed because one or more lines are too long