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>
@@ -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))
|
||||
|
||||
/**
|
||||
|
||||
@@ -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.
|
||||
|
||||
@@ -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"
|
||||
|
||||
@@ -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))
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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"
|
||||
|
||||
@@ -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"
|
||||
|
||||
@@ -1,6 +1,8 @@
|
||||
// Datums
|
||||
#define isdatum(thing) (istype(thing, /datum))
|
||||
|
||||
#define isspell(A) (istype(A, /datum/spell))
|
||||
|
||||
// Atoms
|
||||
#define isatom(A) (isloc(A))
|
||||
|
||||
|
||||
@@ -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
|
||||
@@ -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
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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 //
|
||||
/////////////
|
||||
|
||||
@@ -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 *****/
|
||||
|
||||
@@ -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
|
||||
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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"
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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>")
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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()
|
||||
. = ..()
|
||||
|
||||
@@ -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
|
||||
. = ..()
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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 ..()
|
||||
|
||||
@@ -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()
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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.
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
/////////////////////////////////////////////
|
||||
|
||||
@@ -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
|
||||
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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>"
|
||||
|
||||
@@ -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)
|
||||
|
||||
|
||||
@@ -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"]
|
||||
|
||||
@@ -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.
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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()
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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)
|
||||
|
||||
|
Before Width: | Height: | Size: 682 B After Width: | Height: | Size: 3.0 KiB |
|
Before Width: | Height: | Size: 141 KiB After Width: | Height: | Size: 138 KiB |
|
After Width: | Height: | Size: 469 B |
|
Before Width: | Height: | Size: 6.0 KiB After Width: | Height: | Size: 14 KiB |
|
Before Width: | Height: | Size: 1.8 KiB After Width: | Height: | Size: 3.6 KiB |
|
Before Width: | Height: | Size: 68 KiB After Width: | Height: | Size: 71 KiB |
|
Before Width: | Height: | Size: 67 KiB After Width: | Height: | Size: 69 KiB |
|
Before Width: | Height: | Size: 28 KiB After Width: | Height: | Size: 55 KiB |
|
Before Width: | Height: | Size: 2.2 KiB After Width: | Height: | Size: 4.4 KiB |
|
Before Width: | Height: | Size: 1.9 KiB After Width: | Height: | Size: 5.3 KiB |
@@ -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"
|
||||
|
||||
@@ -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>;
|
||||
};
|
||||