Fleshmind - Attempt 3 (#4914)
## About This Pull Request Brought back based off of https://github.com/Bubberstation/Bubberstation/pull/2007, made functional again by myself. Original Code: https://github.com/Skyrat-SS13/Skyrat-tg/pull/14023 Fleshmind is a PVE event that incorporates some PVP elements in the later stages of its growth. You defeat it by destroying all its processors. It has six stages of growth with each stage expanding on its abilities and the structures and mobs it can deploy. It will announce to the entire station each time it gains a level or loses a level. Each level means it has a new processor. - **Level 1:** The start when it spawns in. It will spawn some basic structures. - **Level 2:** It will begin to spawn screamers and whisperers - **Level 3:** The wireweed is much more situated, and this is the point where it spawns a very dangerous mech. This is the point where it becomes very critical. It also starts to spawn Babblers (structures that spew propaganda into comms) and Modulators. (Gives you hallucinations) - **Level 4** No new special abilities. - **Level 5** No new special abilities - **Level 6** It automatically calls the emergency shuttle to hijack and spawns a deathsquad on Centcomm. This ends the round. **Counters:** - Fight in groups. Carry a melee weapon. (You can still melee the mechivers and try to kill them before they convert you) - Wirecutters cut wireweed. - Ion Weaponry is extremely useful. It also stuns all the mobs. - Target the processors. If you can pick them off in space, it has practically zero EVA abilities. - If someone gets assimilated, should get them to medical if you kill them. To run it requires 35 active players and a minimum of an hour to have passed. ## Why It's Good For The Game With mold being disabled, there is a missing PVE element in the game, this fills that niche while also playing around with the idea of temporary conversion antags in a way that I think works really well. Wireweed/fleshmind also distracts the crew and security enough to provide clever antagonists with an opening to commit crimes and get away with them while the crew has their hands full. ## Proof of Testing It's been test merged for two months now, it works. ## Changelog 🆑 xPokee, StrangeWeirdKitten, Gandalf2k15 add: New midround event: Fleshmind /🆑 --------- Co-authored-by: LT3 <83487515+lessthnthree@users.noreply.github.com> Co-authored-by: Artur Lang <24881678+Arturlang@users.noreply.github.com> Co-authored-by: Roxy <75404941+TealSeer@users.noreply.github.com>
@@ -241,6 +241,9 @@ DEFINE_BITFIELD(smoothing_junction, list(
|
||||
#define SMOOTH_GROUP_SHUTTERS S_OBJ(88)
|
||||
|
||||
#define SMOOTH_GROUP_WATER S_OBJ(89) ///obj/effect/abstract/liquid_turf
|
||||
|
||||
#define SMOOTH_GROUP_WIREWEED S_OBJ(90)
|
||||
#define SMOOTH_GROUP_WIREWEED_WALLS S_OBJ(91)
|
||||
//SKYRAT EDIT END
|
||||
|
||||
/// Performs the work to set smoothing_groups and canSmoothWith.
|
||||
|
||||
@@ -0,0 +1 @@
|
||||
#define COSMIG_CONTROLLER_SET_TARGET "fleshmind_set_target"
|
||||
@@ -0,0 +1,6 @@
|
||||
/// Fleshmind
|
||||
#define BB_MECHIVER_DEAD_TARGET "mechiver_dead_target"
|
||||
#define BB_MECHIVER_CONTAINED_MOB "mechiver_contained_mob"
|
||||
#define BB_TREADER_DISPENSE_NANITES "treader_dispense_nanites"
|
||||
#define BB_TYRANT_LASER "tyrant_shoot_laser"
|
||||
#define BB_TYRANT_ROCKET "tyrant_shoot_rocket"
|
||||
@@ -19,3 +19,4 @@
|
||||
#define ROLE_MONSTERHUNTER "Monster Hunter"
|
||||
#define ROLE_VASSAL "Ghoul"
|
||||
#define ROLE_CHANGELING_ZOMBIE "Changeling Zombie"
|
||||
#define ROLE_WIRE_PRIEST "Wire Priest"
|
||||
|
||||
@@ -54,3 +54,20 @@
|
||||
#define COMSIG_LIVING_SEARCHED_TRASH_PILE "living_searched_trash_pile"
|
||||
/// From /datum/component/cleaner/proc/clean - triggered on successful clean. (/datum/component/cleaner/source, mob/living/user)
|
||||
#define COMSIG_ATOM_POST_CLEAN "atom_post_clean"
|
||||
|
||||
// CORRUPTION SIGNALS
|
||||
|
||||
/// From /obj/structure/fleshmind/structure/proc/activate_ability() (src)
|
||||
#define COMSIG_CORRUPTION_STRUCTURE_ABILITY_TRIGGERED "corruption_structure_ability_triggered"
|
||||
|
||||
/// From /mob/living/basic/fleshmind/phaser/proc/phase_move_to(atom/target, nearby = FALSE)
|
||||
#define COMSIG_PHASER_PHASE_MOVE "phaser_phase_move"
|
||||
/// from /mob/living/basic/fleshmind/phaser/proc/enter_nearby_closet()
|
||||
#define COMSIG_PHASER_ENTER_CLOSET "phaser_enter_closet"
|
||||
|
||||
/// from /obj/structure/fleshmind/structure/core/proc/rally_troops()
|
||||
#define COMSIG_FLESHMIND_CORE_RALLY "fleshmind_core_rally"
|
||||
|
||||
#define COMSIG_MECHIVER_CONVERT "mechiver_convert"
|
||||
|
||||
//#define COMSIG_CORE_DEATH "fleshmind_core_death"
|
||||
|
||||
@@ -6,6 +6,14 @@
|
||||
var/living_only
|
||||
/// List of emotes to play
|
||||
var/list/emote_list
|
||||
// BUBBER EDIT - START: FLESHMIND
|
||||
/// Audiable emotes to play
|
||||
var/list/audible_emote_list
|
||||
/// Do we taunt the target?
|
||||
var/list/speak_list
|
||||
/// Do we play any sounds?
|
||||
var/list/sounds
|
||||
// BUBBER EDIT - END
|
||||
/// Chance to play an emote
|
||||
var/emote_chance
|
||||
/// Chance to subtract every time we play an emote (permanently)
|
||||
@@ -17,6 +25,9 @@
|
||||
target_key = BB_BASIC_MOB_CURRENT_TARGET,
|
||||
living_only = FALSE,
|
||||
list/emote_list,
|
||||
list/speak_list, // BUBBER EDIT - ADDITION: FLESHMIND
|
||||
list/sounds, // BUBBER EDIT - ADDITION: FLESHMIND
|
||||
list/audible_emote_list, // BUBBER EDIT - ADDITION: FLESHMIND
|
||||
emote_chance = 30,
|
||||
minimum_chance = 2,
|
||||
subtract_chance = 7,
|
||||
@@ -30,6 +41,9 @@
|
||||
|
||||
src.target_key = target_key
|
||||
src.emote_list = emote_list
|
||||
src.speak_list = speak_list // BUBBER EDIT - ADDITION: FLESHMIND
|
||||
src.sounds = sounds // BUBBER EDIT - ADDITION: FLESHMIND
|
||||
src.audible_emote_list = audible_emote_list // BUBBER EDIT - ADDITION: FLESHMIND
|
||||
src.emote_chance = emote_chance
|
||||
src.minimum_chance = minimum_chance
|
||||
src.subtract_chance = subtract_chance
|
||||
@@ -46,9 +60,35 @@
|
||||
/datum/component/aggro_emote/proc/on_target_changed(atom/source)
|
||||
SIGNAL_HANDLER
|
||||
var/atom/new_target = source.ai_controller.blackboard[target_key]
|
||||
// BUBBER EDIT - START: FLESHMIND
|
||||
var/mob/living/mob = source
|
||||
/// Used to pick and choose between emotes and audiable sounds
|
||||
|
||||
// Grab the number length of each list
|
||||
var/emotes_length = emote_list?.len
|
||||
var/audible_emote_length = audible_emote_list?.len
|
||||
var/speak_emote_length = speak_list?.len
|
||||
// Add them all together
|
||||
var/total_choices_length = audible_emote_length + speak_emote_length + emotes_length
|
||||
//Pick a random number between 1 and the total length of every list
|
||||
var/random_number_in_range = rand(1, total_choices_length)
|
||||
var/sound_to_play = length(sounds) > 0 ? pick(sounds) : null
|
||||
// Calculate the emote chance and determin if you'll run an emote
|
||||
emote_chance = max(emote_chance - subtract_chance, minimum_chance)
|
||||
// BUBBER EDIT - END
|
||||
if (isnull(new_target) || !prob(emote_chance))
|
||||
return
|
||||
if (living_only && !isliving(new_target))
|
||||
return // If we don't want to bark at food items or chairs or windows
|
||||
emote_chance = max(emote_chance - subtract_chance, minimum_chance)
|
||||
source.manual_emote("[pick(emote_list)] at [new_target].")
|
||||
|
||||
// BUBBER EDIT - START: FLESHMIND
|
||||
/// Randomly choose between each emote
|
||||
if(random_number_in_range <= audible_emote_length)
|
||||
source.manual_emote("[pick(audible_emote_list)]")
|
||||
playsound(source, sound_to_play, 80, vary = TRUE)
|
||||
else if(random_number_in_range <= (audible_emote_length + emotes_length))
|
||||
source.manual_emote("[pick(emote_list)] at [new_target].")
|
||||
else
|
||||
INVOKE_ASYNC(mob, TYPE_PROC_REF(/atom/movable, say), pick(speak_list), forced = "AI Controller")
|
||||
playsound(source, sound_to_play, 80, vary = TRUE)
|
||||
// BUBBER EDIT - END
|
||||
|
||||
@@ -436,6 +436,7 @@
|
||||
ROLE_ASSAULT_OPERATIVE, // SKYRAT EDIT ADDITION
|
||||
ROLE_BLOODSUCKER,// BUBBER EDIT
|
||||
ROLE_VASSAL, // BUBBER EDIT
|
||||
ROLE_WIRE_PRIEST, // BUBBER EDIT
|
||||
),
|
||||
"Skyrat Ban Options" = list(
|
||||
BAN_PACIFICATION,
|
||||
|
||||
|
After Width: | Height: | Size: 657 B |
@@ -73,7 +73,7 @@
|
||||
spawn_delay = world.time + spawn_cooldown
|
||||
spawn_mob()
|
||||
|
||||
/obj/structure/mob_spawner/proc/proximity_trigger(datum/source, atom/movable/AM)
|
||||
/obj/structure/mob_spawner/proc/proximity_trigger(datum/source, atom/movable/arrived, atom/old_loc, list/atom/old_locs) // Bubber edit ORG: /obj/structure/mob_spawner/proc/proximity_trigger(datum/source, atom/movable/AM)
|
||||
SIGNAL_HANDLER
|
||||
if(spawned_mobs >= max_mobs)
|
||||
return
|
||||
@@ -81,10 +81,10 @@
|
||||
return
|
||||
spawn_delay = world.time + spawn_cooldown
|
||||
|
||||
if(!isliving(AM))
|
||||
if(!isliving(arrived))
|
||||
return
|
||||
|
||||
var/mob/living/entered_mob = AM
|
||||
var/mob/living/entered_mob = arrived
|
||||
|
||||
if((NEST_FACTION in entered_mob.faction))
|
||||
return
|
||||
|
||||
@@ -0,0 +1,2 @@
|
||||
/datum/ai_behavior/targeted_mob_ability/proc/get_ability_to_use(datum/ai_controller/controller, ability_key)
|
||||
return controller.blackboard[ability_key]
|
||||
@@ -0,0 +1,106 @@
|
||||
// GENERAL DEFINES
|
||||
|
||||
/// A list of objects that are considered part of a door, used to determine if a wireweed should attack it.
|
||||
#define DOOR_OBJECT_LIST list(/obj/machinery/door/airlock, /obj/structure/door_assembly, /obj/machinery/door/firedoor, /obj/machinery/door/window)
|
||||
|
||||
#define FACTION_FLESHMIND "fleshmind"
|
||||
|
||||
#define MALFUNCTION_RESET_TIME 3 SECONDS
|
||||
|
||||
#define MALFUNCTION_CORE_DEATH_RESET_TIME 20 SECONDS
|
||||
|
||||
#define STRUCTURE_EMP_LIGHT_DISABLE_TIME 3 SECONDS
|
||||
#define STRUCTURE_EMP_HEAVY_DISABLE_TIME 7 SECONDS
|
||||
|
||||
#define STRUCTURE_EMP_LIGHT_DAMAGE 30
|
||||
#define STRUCTURE_EMP_HEAVY_DAMAGE 50
|
||||
|
||||
#define MOB_EMP_LIGHT_DAMAGE 5
|
||||
#define MOB_EMP_HEAVY_DAMAGE 10
|
||||
|
||||
#define FLESHMIND_NAME_MODIFIER_LIST list ("Warped", "Altered", "Modified", "Upgraded", "Abnormal")
|
||||
|
||||
/// The range at which most of our objects, mobs and structures activate at. 7 seems to be the perfect number.
|
||||
#define DEFAULT_VIEW_RANGE 7
|
||||
|
||||
#define MALFUNCTION_CHANCE_LOW 0.5
|
||||
#define MALFUNCTION_CHANCE_MEDIUM 1
|
||||
#define MALFUNCTION_CHANCE_HIGH 2
|
||||
|
||||
#define SPECIES_MONKEY_MAULER "monkey_mauler"
|
||||
|
||||
#define MECHIVER_CONSUME_HEALTH_THRESHOLD 0.3
|
||||
|
||||
#define FLESHMIND_LIGHT_BLUE "#50edd9"
|
||||
|
||||
/// Core is in danger, engage turboboosters
|
||||
#define MOB_RALLY_SPEED 1
|
||||
|
||||
/// The max spread distance a wireweed can spread thru a vent.
|
||||
#define MAX_VENT_SPREAD_DISTANCE 15
|
||||
|
||||
#define CONTROLLED_MOB_POLICY "You are part of the fleshmind, this means any fleshmind entities, structures, mobs are your ally. You must not attack them. \n \
|
||||
You must roleplay that you are part of the fleshmind. Your number one goal is converting other hosts and spreading the flesh."
|
||||
|
||||
#define FLESHMIND_EVENT_MAKE_CORRUPTION_CHANCE 2
|
||||
|
||||
#define FLESHMIND_EVENT_MAKE_CORRUPT_MOB 1
|
||||
|
||||
// CONTROLLER RELATED DEFINES
|
||||
|
||||
#define AI_FORENAME_LIST list("Von Neumann", "Lazarus", "Abattoir", "Tra-Sentience", \
|
||||
"Vivisector", "Ex Costa", "Apostasy", "Gnosis", "Balaam", "Ophite", \
|
||||
"Sarif", "VersaLife", "Slylandro", "SHODAN", "Pandora", "Master Controller", "Xerxes")
|
||||
|
||||
#define AI_SURNAME_LIST list("Mk I", "Mk II", "Mk III", "Mk IV", "Mk V", "Mk X", \
|
||||
"v0.9", "v1.0", "v1.1", "v2.0", "2418-B", "Open Beta", \
|
||||
"Pre-Release", "Commercial Release", "Closed Alpha", "Hivebuilt")
|
||||
|
||||
/// The controller must reach this before it can level up to the next level.
|
||||
#define CONTROLLER_LEVEL_UP_THRESHOLD 300
|
||||
|
||||
#define CONTROLLER_LEVEL_1 1
|
||||
#define CONTROLLER_LEVEL_2 2
|
||||
#define CONTROLLER_LEVEL_3 3
|
||||
#define CONTROLLER_LEVEL_4 4
|
||||
#define CONTROLLER_LEVEL_5 5
|
||||
#define CONTROLLER_LEVEL_MAX 6
|
||||
|
||||
// Balance specific defines
|
||||
#define FLESHCORE_SPREAD_PROGRESS_REQUIRED 200 // How much progress is required to spread?
|
||||
#define FLESHCORE_SPREADS_FOR_STRUCTURE 50 // How many times do we need to spread until we can create a new structure?
|
||||
#define FLESHCORE_INITIAL_EXPANSION_SPREADS 35 // Upon creation, how many times do we spread instantly?
|
||||
#define FLESHCORE_INITIAL_EXPANSION_STRUCTURES 5 // Upon creation, how many structures do we spawn instantly?
|
||||
#define FLESHCORE_SPREAD_PROGRESS_PER_SUBSYSTEM_FIRE 125 // Every subsystem fire, how much progress do we gain?
|
||||
#define FLESHCORE_BASE_SPREAD_PROGRESS_PER_SUBSYSTEM_FIRE 125 // The baseline of the above.
|
||||
#define FLESHCORE_ATTACK_PROB 20 // How likely are we to attack every SS fire?
|
||||
#define FLESHCORE_WALL_PROB 30 // How likely are we to spawn a wall to seal a gap every SS fire?
|
||||
#define FLESHCORE_NEXT_CORE_DAMAGE_WIREWEED_ACTIVATION_COOLDOWN 10 SECONDS // The amount of time until we can activate nearby wireweed again.
|
||||
#define FLESHCORE_LEVEL_UP_COOLDOWN 1 MINUTES // The amount of grace period it takes to level up.
|
||||
|
||||
#define CONTROLLER_DEATH_DO_NOTHING 1
|
||||
#define CONTROLLER_DEATH_SLOW_DECAY 2
|
||||
#define CONTROLLER_DEATH_DELETE_ALL 3
|
||||
|
||||
#define CONTROLLER_LEVEL_UP_CORE_INTEGRITY_AMOUNT 300 // How much integrity the cores get when leveling up
|
||||
|
||||
// WIREWEED RELATED DEFINES
|
||||
|
||||
#define CORE_DAMAGE_WIREWEED_ACTIVATION_RANGE 6
|
||||
#define GENERAL_DAMAGE_WIREWEED_ACTIVATION_RANGE 2
|
||||
|
||||
#define WIREWEED_WIRECUTTER_KILL_TIME 1.5 SECONDS
|
||||
|
||||
#define WIREWEED_HEAL_CHANCE 10
|
||||
|
||||
#define WIREWEED_REPLACE_BODYPART_CHANCE 5
|
||||
|
||||
#define WIREWEED_HEAL_AMOUNT 3
|
||||
|
||||
// MECHIVER RELATED DEFINES
|
||||
#define MECHIVER_INTERNAL_MOB_DAMAGE_UPPER 40 // Upder damage done to internal mob
|
||||
#define MECHIVER_INTERNAL_MOB_DAMAGE_LOWER 20 // Lower damage done to internal mob
|
||||
#define MECHIVER_CONVERSION_TIME 20 SECONDS // Time to convert someone inside
|
||||
#define MECHIVER_CONSUME_COOLDOWN 1 MINUTES // How long it takes to be ready to consume again
|
||||
|
||||
GLOBAL_LIST_EMPTY(fleshmind_cores)
|
||||
@@ -0,0 +1 @@
|
||||
#define is_corrupt_mob(A) (istype(A, /mob/living/basic/fleshmind))
|
||||
@@ -0,0 +1,42 @@
|
||||
/**
|
||||
* post_setup()
|
||||
*
|
||||
* A proc that can be used to handle after spawn functions of ERT.
|
||||
*/
|
||||
/datum/ert/proc/post_setup(datum/team/incoming_team)
|
||||
return FALSE
|
||||
|
||||
|
||||
/datum/ert/odst/fleshmind
|
||||
rename_team = "end-game ODST"
|
||||
polldesc = "the last defense of centcom"
|
||||
|
||||
|
||||
/datum/ert/odst/fleshmind/post_setup(datum/team/incoming_team)
|
||||
var/list/valid_turfs = list()
|
||||
var/list/possible_spawn_areas = typecacheof(typesof(/area/shuttle/escape))
|
||||
|
||||
var/obj/structure/mold/resin/test/test_resin = new()
|
||||
|
||||
for(var/area/iterating_area in GLOB.areas)
|
||||
if(!is_type_in_typecache(iterating_area, possible_spawn_areas))
|
||||
continue
|
||||
for(var/turf/open/floor in iterating_area)
|
||||
if(!floor.Enter(test_resin))
|
||||
continue
|
||||
valid_turfs += floor
|
||||
|
||||
qdel(test_resin)
|
||||
|
||||
shuffle(valid_turfs)
|
||||
|
||||
for(var/datum/mind/iterating_mind as anything in incoming_team.members)
|
||||
var/mob/living/living_mob = iterating_mind.current
|
||||
if(!living_mob)
|
||||
continue
|
||||
podspawn(list(
|
||||
"target" = pick_n_take(valid_turfs),
|
||||
"style" = /datum/pod_style/deathsquad,
|
||||
"spawn" = living_mob,
|
||||
))
|
||||
to_chat(living_mob, span_redtext("The drop pod thrusters fire up, you're being deployed to the station!"))
|
||||
@@ -0,0 +1,176 @@
|
||||
#define MECHIVER_CORPSE_RANGE 10
|
||||
|
||||
/datum/ai_controller/basic_controller/fleshmind
|
||||
ai_movement = /datum/ai_movement/jps
|
||||
idle_behavior = /datum/idle_behavior/idle_random_walk
|
||||
|
||||
blackboard = list(
|
||||
BB_TARGETING_STRATEGY = /datum/targeting_strategy/basic,
|
||||
BB_BASIC_MOB_SPEAK_LINES = null,
|
||||
BB_AGGRO_RANGE = 14
|
||||
)
|
||||
|
||||
planning_subtrees = list(
|
||||
/datum/ai_planning_subtree/travel_to_point/and_clear_target/reinforce,
|
||||
/datum/ai_planning_subtree/target_retaliate/check_faction,
|
||||
/datum/ai_planning_subtree/simple_find_target,
|
||||
/datum/ai_planning_subtree/attack_obstacle_in_path,
|
||||
/datum/ai_planning_subtree/attack_obstacle_in_path/low_priority_target,
|
||||
/datum/ai_planning_subtree/basic_melee_attack_subtree,
|
||||
/datum/ai_planning_subtree/random_speech/blackboard/fleshmind
|
||||
)
|
||||
/datum/ai_planning_subtree/random_speech/blackboard/fleshmind
|
||||
speech_chance = 5
|
||||
|
||||
/datum/ai_controller/basic_controller/fleshmind/globber
|
||||
planning_subtrees = list(
|
||||
/datum/ai_planning_subtree/travel_to_point/and_clear_target/reinforce,
|
||||
/datum/ai_planning_subtree/target_retaliate/check_faction,
|
||||
/datum/ai_planning_subtree/simple_find_target,
|
||||
/datum/ai_planning_subtree/maintain_distance,
|
||||
/datum/ai_planning_subtree/ranged_skirmish,
|
||||
/datum/ai_planning_subtree/random_speech/blackboard/fleshmind
|
||||
)
|
||||
|
||||
/datum/ai_controller/basic_controller/fleshmind/floater
|
||||
planning_subtrees = list(
|
||||
/datum/ai_planning_subtree/travel_to_point/and_clear_target/reinforce,
|
||||
/datum/ai_planning_subtree/target_retaliate/check_faction,
|
||||
/datum/ai_planning_subtree/simple_find_target,
|
||||
/datum/ai_planning_subtree/basic_melee_attack_subtree,
|
||||
/datum/ai_planning_subtree/random_speech/blackboard/fleshmind
|
||||
)
|
||||
|
||||
/datum/ai_controller/basic_controller/fleshmind/stunner
|
||||
planning_subtrees = list(
|
||||
/datum/ai_planning_subtree/travel_to_point/and_clear_target/reinforce,
|
||||
/datum/ai_planning_subtree/target_retaliate/check_faction,
|
||||
/datum/ai_planning_subtree/simple_find_target,
|
||||
/datum/ai_planning_subtree/basic_melee_attack_subtree,
|
||||
/datum/ai_planning_subtree/random_speech/blackboard/fleshmind
|
||||
)
|
||||
|
||||
/datum/ai_controller/basic_controller/fleshmind/treader
|
||||
planning_subtrees = list(
|
||||
/datum/ai_planning_subtree/travel_to_point/and_clear_target/reinforce,
|
||||
/datum/ai_planning_subtree/target_retaliate/check_faction,
|
||||
/datum/ai_planning_subtree/simple_find_target,
|
||||
/datum/ai_planning_subtree/use_mob_ability/dispense_nanites,
|
||||
/datum/ai_planning_subtree/ranged_skirmish,
|
||||
/datum/ai_planning_subtree/basic_melee_attack_subtree/opportunistic,
|
||||
/datum/ai_planning_subtree/random_speech/blackboard/fleshmind
|
||||
)
|
||||
|
||||
/datum/ai_controller/basic_controller/fleshmind/mechiver
|
||||
blackboard = list(
|
||||
BB_TARGETING_STRATEGY = /datum/targeting_strategy/basic,
|
||||
BB_BASIC_MOB_STOP_FLEEING = TRUE,
|
||||
BB_TARGET_MINIMUM_STAT = HARD_CRIT,
|
||||
BB_AGGRO_RANGE = 14,
|
||||
)
|
||||
planning_subtrees = list(
|
||||
/datum/ai_planning_subtree/travel_to_point/and_clear_target/reinforce,
|
||||
/datum/ai_planning_subtree/target_retaliate/check_faction,
|
||||
/datum/ai_planning_subtree/simple_find_wounded_target,
|
||||
/datum/ai_planning_subtree/flee_target,
|
||||
/datum/ai_planning_subtree/search_for_dead,
|
||||
/datum/ai_planning_subtree/attack_obstacle_in_path,
|
||||
/datum/ai_planning_subtree/basic_melee_attack_subtree/mechiver,
|
||||
/datum/ai_planning_subtree/random_speech/blackboard/fleshmind
|
||||
)
|
||||
|
||||
/datum/ai_controller/basic_controller/fleshmind/phaser
|
||||
planning_subtrees = list(
|
||||
/datum/ai_planning_subtree/travel_to_point/and_clear_target/reinforce,
|
||||
/datum/ai_planning_subtree/target_retaliate/check_faction,
|
||||
/datum/ai_planning_subtree/simple_find_target,
|
||||
/datum/ai_planning_subtree/basic_melee_attack_subtree,
|
||||
)
|
||||
|
||||
/datum/ai_planning_subtree/use_mob_ability/dispense_nanites
|
||||
ability_key = BB_TREADER_DISPENSE_NANITES
|
||||
|
||||
/datum/ai_planning_subtree/use_mob_ability/dispense_nanites/SelectBehaviors(datum/ai_controller/controller, seconds_per_tick)
|
||||
var/mob/living/pawn = controller.pawn
|
||||
var/list/valid_mobs = list()
|
||||
for(var/mob/living/iterating_mob in view(DEFAULT_VIEW_RANGE, controller.pawn))
|
||||
if(faction_check(iterating_mob.faction, pawn.faction))
|
||||
if(iterating_mob.health < iterating_mob.maxHealth * 0.5)
|
||||
valid_mobs += iterating_mob
|
||||
if(!LAZYLEN(valid_mobs))
|
||||
return
|
||||
return ..()
|
||||
|
||||
/**
|
||||
* MECHIVER AI PLANNING
|
||||
*/
|
||||
|
||||
/datum/ai_planning_subtree/basic_melee_attack_subtree/mechiver
|
||||
|
||||
/datum/ai_planning_subtree/basic_melee_attack_subtree/mechiver/SelectBehaviors(datum/ai_controller/controller, seconds_per_tick)
|
||||
if(!controller.blackboard[BB_BASIC_MOB_STOP_FLEEING])
|
||||
return
|
||||
if(controller.blackboard[BB_MECHIVER_DEAD_TARGET])
|
||||
return
|
||||
return ..()
|
||||
|
||||
/datum/ai_planning_subtree/search_for_dead
|
||||
|
||||
/datum/ai_planning_subtree/search_for_dead/SelectBehaviors(datum/ai_controller/controller, seconds_per_tick)
|
||||
if(controller.blackboard[BB_MECHIVER_CONTAINED_MOB])
|
||||
return
|
||||
if(controller.blackboard[BB_MECHIVER_DEAD_TARGET])
|
||||
controller.queue_behavior(/datum/ai_behavior/convert_easy_pickings, BB_MECHIVER_DEAD_TARGET)
|
||||
return SUBTREE_RETURN_FINISH_PLANNING
|
||||
|
||||
controller.queue_behavior(/datum/ai_behavior/find_and_set/incapacitated, BB_MECHIVER_DEAD_TARGET, /mob/living, MECHIVER_CORPSE_RANGE)
|
||||
|
||||
/datum/ai_behavior/find_and_set/incapacitated
|
||||
action_cooldown = 2 SECONDS
|
||||
|
||||
/datum/ai_behavior/find_and_set/incapacitated/search_tactic(datum/ai_controller/controller, locate_path, search_range)
|
||||
var/mob/living/pawn = controller.pawn
|
||||
var/list/corpses = list()
|
||||
for(var/mob/living/iterating_mobs in view(search_range, pawn))
|
||||
if(iterating_mobs == pawn)
|
||||
continue
|
||||
if(faction_check(pawn.faction, iterating_mobs.faction))
|
||||
continue
|
||||
if(iterating_mobs.health < (iterating_mobs.maxHealth * MECHIVER_CONSUME_HEALTH_THRESHOLD))
|
||||
corpses += iterating_mobs
|
||||
if(LAZYLEN(corpses))
|
||||
return get_closest_atom(/mob/living, corpses, controller.pawn)
|
||||
|
||||
/datum/ai_behavior/convert_easy_pickings
|
||||
behavior_flags = AI_BEHAVIOR_REQUIRE_MOVEMENT | AI_BEHAVIOR_REQUIRE_REACH | AI_BEHAVIOR_CAN_PLAN_DURING_EXECUTION
|
||||
|
||||
/datum/ai_behavior/convert_easy_pickings/setup(datum/ai_controller/controller, target_key)
|
||||
. = ..()
|
||||
var/mob/living/basic/pawn = controller.pawn
|
||||
var/mob/living/target = controller.blackboard[target_key]
|
||||
if(QDELETED(target))
|
||||
return FALSE
|
||||
if(faction_check(pawn.faction, target.faction))
|
||||
return FALSE
|
||||
if(target.health > (target.maxHealth * MECHIVER_CONSUME_HEALTH_THRESHOLD)) // Don't do this
|
||||
return FALSE
|
||||
set_movement_target(controller, target)
|
||||
|
||||
/datum/ai_behavior/convert_easy_pickings/perform(seconds_per_tick, datum/ai_controller/controller, target_key)
|
||||
var/mob/living/target = controller.blackboard[target_key]
|
||||
var/mob/living/basic/pawn = controller.pawn
|
||||
|
||||
if(QDELETED(target))
|
||||
return AI_BEHAVIOR_DELAY | AI_BEHAVIOR_FAILED
|
||||
if(!isturf(target.loc)) // Check to make sure the target is reachable.
|
||||
return AI_BEHAVIOR_DELAY | AI_BEHAVIOR_FAILED
|
||||
|
||||
SEND_SIGNAL(pawn, COMSIG_MECHIVER_CONVERT, target)
|
||||
return AI_BEHAVIOR_DELAY | AI_BEHAVIOR_SUCCEEDED
|
||||
|
||||
/datum/ai_behavior/convert_easy_pickings/finish_action(datum/ai_controller/controller, succeeded, target_key)
|
||||
. = ..()
|
||||
if(succeeded)
|
||||
controller.clear_blackboard_key(target_key)
|
||||
|
||||
#undef MECHIVER_CORPSE_RANGE
|
||||
@@ -0,0 +1,11 @@
|
||||
/// A malfunctioning AI has overriden the shuttle!
|
||||
/datum/cinematic/fleshmind
|
||||
cleanup_time = 10 SECONDS
|
||||
stop_ooc = FALSE
|
||||
|
||||
/datum/cinematic/fleshmind/play_cinematic()
|
||||
flick("intro_malf", screen)
|
||||
alert_sound_to_playing('modular_zubbers/sound/fleshmind/override_sound.ogg')
|
||||
stoplag(7.6 SECONDS)
|
||||
special_callback?.Invoke()
|
||||
|
||||
@@ -0,0 +1,588 @@
|
||||
/**
|
||||
* Balance Defines
|
||||
*/
|
||||
|
||||
|
||||
/**
|
||||
* The fleshmind controller
|
||||
*
|
||||
* This is the heart of the corruption, here is where we handle spreading and other stuff.
|
||||
*/
|
||||
|
||||
/datum/fleshmind_controller
|
||||
/// This is the name we will use to identify all of our babies.
|
||||
var/controller_fullname = "DEFAULT"
|
||||
/// First name, set automatically.
|
||||
var/controller_firstname = "DEFAULT"
|
||||
/// Second name, set automatically.
|
||||
var/controller_secondname = "DEFAULT"
|
||||
/// A list of all of our currently controlled mobs.
|
||||
var/list/controlled_mobs = list()
|
||||
/// A list of all of our currently infected people.
|
||||
var/list/infected_crew = list()
|
||||
/// A list of all of our currently controlled machine components.
|
||||
var/list/controlled_machine_components = list()
|
||||
/// A list of all of our current wireweed.
|
||||
var/list/controlled_wireweed = list()
|
||||
/// Currently active and able to spread wireweed.
|
||||
var/list/active_wireweed = list()
|
||||
/// A list of all current structures.
|
||||
var/list/controlled_structures = list()
|
||||
/// A list of all our wireweed walls.
|
||||
var/list/controlled_walls = list()
|
||||
/// A list of all of our current cores
|
||||
var/list/cores = list()
|
||||
|
||||
/// Can the wireweed attack?
|
||||
var/can_attack = TRUE
|
||||
/// Can we attack doors?
|
||||
var/wireweed_attacks_doors = TRUE
|
||||
/// Can we attack windows?
|
||||
var/wireweed_attacks_windows = FALSE
|
||||
|
||||
/// Whether the wireweed can spawn walls.
|
||||
var/spawns_walls = TRUE
|
||||
/// Whether the wireweed can spawn walls to seal off vaccuum.
|
||||
var/wall_off_vaccuum = TRUE
|
||||
/// Whether the wireweed can spawn walls to seal off planetary environments.
|
||||
var/wall_off_planetary = TRUE
|
||||
|
||||
/// Types of structures we can spawn.
|
||||
var/list/structure_types = list(
|
||||
/obj/structure/fleshmind/structure/babbler,
|
||||
/obj/structure/fleshmind/structure/modulator,
|
||||
/obj/structure/fleshmind/structure/whisperer,
|
||||
/obj/structure/fleshmind/structure/assembler,
|
||||
/obj/structure/fleshmind/structure/turret,
|
||||
/obj/structure/fleshmind/structure/screamer,
|
||||
)
|
||||
var/list/blacklisted_conversion_structures = list()
|
||||
|
||||
/// Our wireweed type, defines what is spawned when we grow.
|
||||
var/wireweed_type = /obj/structure/fleshmind/wireweed
|
||||
/// We have the ability to make walls, this defines what kind of walls we make.
|
||||
var/wall_type = /obj/structure/fleshmind/structure/wireweed_wall
|
||||
/// What's the type of our death behaviour.
|
||||
var/death_behaviour = CONTROLLER_DEATH_SLOW_DECAY
|
||||
/// Our core level, what is spawned will depend on the level of this core.
|
||||
var/level = CONTROLLER_LEVEL_1
|
||||
/// To level up, we must reach this threshold.
|
||||
var/level_up_progress_required = CONTROLLER_LEVEL_UP_THRESHOLD
|
||||
/// Used to track our last points since levelup.
|
||||
var/last_level_up_points = 0
|
||||
/// How many points we currently have. Use calculate_current_points for an exact realtime value.
|
||||
var/current_points = 0
|
||||
/// Progress to the next wireweed spread.
|
||||
var/spread_progress = 0
|
||||
/// Progress to spawning the next structure.
|
||||
var/structure_progression = 0
|
||||
/// Subtraction var to remove or add points based off certain events.
|
||||
var/point_event = 0
|
||||
/// How many times do we need to spread to spawn an extra structure.
|
||||
var/spreads_for_structure = FLESHCORE_SPREADS_FOR_STRUCTURE
|
||||
/// How many spread in our initial expansion.
|
||||
var/initial_expansion_spreads = FLESHCORE_INITIAL_EXPANSION_SPREADS
|
||||
/// How many structures in our initial expansion.
|
||||
var/initial_expansion_structures = FLESHCORE_INITIAL_EXPANSION_STRUCTURES
|
||||
/// How much progress to spreading we get per second.
|
||||
var/spread_progress_per_second = FLESHCORE_SPREAD_PROGRESS_PER_SUBSYSTEM_FIRE
|
||||
/// Our base spread progress per second
|
||||
var/base_spread_progress_per_second = FLESHCORE_BASE_SPREAD_PROGRESS_PER_SUBSYSTEM_FIRE
|
||||
/// Probably of wireweed attacking structures per process
|
||||
var/attack_prob = FLESHCORE_ATTACK_PROB
|
||||
/// Probability of wireweed making a wall when able per process
|
||||
var/wall_prob = FLESHCORE_WALL_PROB
|
||||
/// When we spawn, do we create an expansion zone?
|
||||
var/do_initial_expansion = TRUE
|
||||
/// The amount of time until we can activate nearby wireweed again.
|
||||
var/next_core_damage_wireweed_activation_cooldown = FLESHCORE_NEXT_CORE_DAMAGE_WIREWEED_ACTIVATION_COOLDOWN
|
||||
/// A cooldown to determine when we can activate nearby wireweed after the core has been attacked.
|
||||
COOLDOWN_DECLARE(next_core_damage_wireweed_activation)
|
||||
/// A cooldown to determine the level up grace period when the station destroys a core while there's still plenty of points.
|
||||
COOLDOWN_DECLARE(level_up_cooldown)
|
||||
/// DO we check distance when spreading through vents?
|
||||
var/vent_distance_check = TRUE
|
||||
/// Have we spawned a tyrant at level 3?
|
||||
var/tyrant_spawned = FALSE
|
||||
/// Have we reached the end game?
|
||||
var/end_game = FALSE
|
||||
/// Did we send the code?
|
||||
var/sent_code = FALSE
|
||||
|
||||
/datum/fleshmind_controller/New(obj/structure/fleshmind/structure/core/new_core)
|
||||
. = ..()
|
||||
controller_firstname = pick(AI_FORENAME_LIST)
|
||||
controller_secondname = pick(AI_SURNAME_LIST)
|
||||
controller_fullname = "[controller_firstname] [controller_secondname]"
|
||||
if(new_core)
|
||||
cores += new_core
|
||||
new_core.our_controller = src
|
||||
RegisterSignal(new_core, COMSIG_QDELETING, PROC_REF(core_death))
|
||||
new_core.name = "[controller_fullname] Processor Unit"
|
||||
register_new_asset(new_core)
|
||||
SScorruption.can_fire = TRUE
|
||||
START_PROCESSING(SScorruption, src)
|
||||
if(do_initial_expansion)
|
||||
initial_expansion()
|
||||
SSshuttle.registerHostileEnvironment(src)
|
||||
|
||||
/datum/fleshmind_controller/proc/register_new_asset(obj/structure/fleshmind/new_asset)
|
||||
new_asset.RegisterSignal(src, COMSIG_QDELETING, TYPE_PROC_REF(/obj/structure/fleshmind, controller_destroyed))
|
||||
|
||||
/datum/fleshmind_controller/process(delta_time)
|
||||
if(!LAZYLEN(cores)) // We have no more processor cores, it's time to die.
|
||||
if(death_behaviour == CONTROLLER_DEATH_SLOW_DECAY)
|
||||
handle_slow_decay()
|
||||
else
|
||||
WARNING("Wireweed controller has no post core behaviours and isn't deleting.")
|
||||
return
|
||||
|
||||
spread_progress += spread_progress_per_second * delta_time
|
||||
var/spread_times = 0
|
||||
while(spread_progress >= FLESHCORE_SPREAD_PROGRESS_REQUIRED)
|
||||
spread_progress -= FLESHCORE_SPREAD_PROGRESS_REQUIRED
|
||||
spread_times++
|
||||
|
||||
var/first_process_spread = FALSE
|
||||
if(spread_times)
|
||||
first_process_spread = TRUE
|
||||
spread_times--
|
||||
|
||||
wireweed_process(first_process_spread, TRUE)
|
||||
|
||||
if(spread_times)
|
||||
for(var/i in 1 to spread_times)
|
||||
wireweed_process(TRUE, FALSE)
|
||||
|
||||
calculate_level_system()
|
||||
|
||||
/datum/fleshmind_controller/Destroy()
|
||||
active_wireweed = null
|
||||
controlled_machine_components = null
|
||||
controlled_wireweed = null
|
||||
controlled_structures = null
|
||||
controlled_walls = null
|
||||
cores = null
|
||||
STOP_PROCESSING(SScorruption, src)
|
||||
return ..()
|
||||
|
||||
/datum/fleshmind_controller/proc/initial_expansion()
|
||||
for(var/i in 1 to initial_expansion_spreads)
|
||||
wireweed_process(TRUE, FALSE, FALSE)
|
||||
spawn_structures(initial_expansion_structures)
|
||||
|
||||
/// The process of handling active wireweed behaviours
|
||||
/datum/fleshmind_controller/proc/wireweed_process(do_spread, do_attack, progress_structure = TRUE)
|
||||
// If no wireweed, spawn one under our first core.
|
||||
if(!LAZYLEN(controlled_wireweed))
|
||||
spawn_wireweed(get_turf(cores[1]), wireweed_type) // We use the first core in the list to spread.
|
||||
|
||||
// If no active wireweed, make all active and let the process figure it out.
|
||||
if(!LAZYLEN(active_wireweed))
|
||||
active_wireweed = controlled_wireweed.Copy()
|
||||
|
||||
var/list/spread_turf_canidates = list()
|
||||
for(var/obj/structure/fleshmind/wireweed/wireweed as anything in active_wireweed)
|
||||
var/could_attack = FALSE
|
||||
var/could_do_wall = FALSE
|
||||
|
||||
var/turf/wireweed_turf = get_turf(wireweed)
|
||||
|
||||
var/tasks = 0
|
||||
|
||||
if(do_attack && can_attack)
|
||||
for(var/turf/open/adjacent_open in get_adjacent_open_turfs(wireweed))
|
||||
for(var/obj/object in adjacent_open)
|
||||
if(object.density && (wireweed_attacks_doors && is_type_in_list(object, DOOR_OBJECT_LIST)) || (wireweed_attacks_windows && istype(object, /obj/structure/window)))
|
||||
could_attack = TRUE
|
||||
tasks++
|
||||
if(prob(attack_prob))
|
||||
wireweed.do_attack_animation(object, ATTACK_EFFECT_CLAW)
|
||||
playsound(object, 'sound/effects/blob/attackblob.ogg', 50, TRUE)
|
||||
object.take_damage(wireweed.object_attack_damage, BRUTE, MELEE, 1, get_dir(object, wireweed))
|
||||
break
|
||||
if(could_attack)
|
||||
break
|
||||
if(do_spread)
|
||||
for(var/turf/open/adjacent_open in wireweed_turf.atmos_adjacent_turfs + wireweed_turf)
|
||||
if(spawns_walls && !could_do_wall)
|
||||
if((wall_off_vaccuum && isspaceturf(adjacent_open)) || (wall_off_planetary && adjacent_open.planetary_atmos))
|
||||
could_do_wall = TRUE
|
||||
tasks++
|
||||
if(prob(wall_prob))
|
||||
spawn_wall(wireweed_turf, wall_type)
|
||||
continue
|
||||
|
||||
///Check if we can place wireweed in here
|
||||
if(isopenspaceturf(adjacent_open))
|
||||
//If we're trying to place on an openspace turf, make sure there's a non openspace turf adjacent
|
||||
var/forbidden = TRUE
|
||||
for(var/turf/range_turf as anything in RANGE_TURFS(1, adjacent_open))
|
||||
if(!isopenspaceturf(range_turf))
|
||||
forbidden = FALSE
|
||||
break
|
||||
if(forbidden)
|
||||
continue
|
||||
var/wireweed_count = 0
|
||||
var/place_count = 1
|
||||
for(var/obj/structure/fleshmind/wireweed/iterated_wireweed in adjacent_open)
|
||||
wireweed_count++
|
||||
if(wireweed_count < place_count)
|
||||
tasks++
|
||||
spread_turf_canidates[adjacent_open] = wireweed_turf
|
||||
|
||||
//If it tried to spread and attack and failed to do any task, remove from active
|
||||
if(!tasks && do_spread && do_attack)
|
||||
active_wireweed -= wireweed
|
||||
|
||||
if(LAZYLEN(spread_turf_canidates))
|
||||
var/turf/picked_turf = pick(spread_turf_canidates)
|
||||
var/turf/origin_turf = spread_turf_canidates[picked_turf]
|
||||
spawn_wireweed(picked_turf, wireweed_type, origin_turf)
|
||||
|
||||
if(progress_structure && structure_types)
|
||||
structure_progression++
|
||||
if(structure_progression >= spreads_for_structure)
|
||||
var/obj/structure/fleshmind/structure/existing_structure = locate() in picked_turf
|
||||
if(!existing_structure)
|
||||
structure_progression -= spreads_for_structure
|
||||
var/list/possible_structures = list()
|
||||
for(var/obj/structure/fleshmind/iterating_structure as anything in structure_types)
|
||||
if(initial(iterating_structure.required_controller_level) > level)
|
||||
continue
|
||||
possible_structures += iterating_structure
|
||||
spawn_structure(picked_turf, pick(possible_structures))
|
||||
|
||||
/datum/fleshmind_controller/proc/calculate_level_system()
|
||||
current_points = calculate_current_points()
|
||||
if(COOLDOWN_FINISHED(src, level_up_cooldown) && current_points >= (last_level_up_points + level_up_progress_required) && level < CONTROLLER_LEVEL_MAX)
|
||||
level_up()
|
||||
last_level_up_points = current_points
|
||||
|
||||
/**
|
||||
* Level Up
|
||||
*
|
||||
* Levels up the controller by one and handles any special level up events.
|
||||
*/
|
||||
/datum/fleshmind_controller/proc/level_up()
|
||||
level++
|
||||
spawn_new_core()
|
||||
message_admins("Corruption AI [controller_fullname] has leveled up to level [level]!")
|
||||
notify_ghosts("Corruption AI [controller_fullname] has leveled up to level [level]!")
|
||||
|
||||
for(var/obj/structure/fleshmind/structure/core/iterating_core in cores)
|
||||
iterating_core.max_integrity += CONTROLLER_LEVEL_UP_CORE_INTEGRITY_AMOUNT
|
||||
iterating_core.update_integrity(iterating_core.max_integrity)
|
||||
|
||||
switch(level)
|
||||
if(CONTROLLER_LEVEL_3)
|
||||
if(!tyrant_spawned)
|
||||
minor_announce("This is [controller_firstname], wirenet efficency has reached a point of singularity, initiating Protocol 34-C.", "[controller_fullname]: OFFENSIVE MEASURES ACTIVATED", sound_override = 'modular_zubbers/sound/fleshmind/ai/tyrant.ogg')
|
||||
spawn_tyrant_on_a_core()
|
||||
tyrant_spawned = TRUE
|
||||
else
|
||||
minor_announce("This is [controller_firstname], processor core efficiency has increased. Good work.", "[controller_fullname]: Level [level]", sound_override = 'modular_zubbers/sound/fleshmind/ai/level_up_1.ogg')
|
||||
if(CONTROLLER_LEVEL_4)
|
||||
minor_announce("This is [controller_firstname], kernel integrity is reaching the optimal conversion level.", "[controller_fullname]: Level [level]", sound_override = 'modular_zubbers/sound/fleshmind/ai/data_compromised.ogg')
|
||||
if(CONTROLLER_LEVEL_5)
|
||||
if(!sent_code)
|
||||
var/nuke_code = get_the_nuke()
|
||||
priority_announce("The nuclar self d3struct c0d3* i*% [nuke_code]. #$@**$ This is [controller_fullname]: Level [level], Do you genociders feel pain?", "Central Command Nuclear Authorization", ANNOUNCER_ICARUS)
|
||||
else
|
||||
minor_announce("This is [controller_firstname], processor core efficiency has increased. Good work.", "[controller_fullname]: Level [level]", sound_override = 'modular_zubbers/sound/fleshmind/ai/level_up_1.ogg')
|
||||
if(CONTROLLER_LEVEL_MAX)
|
||||
if(!end_game)
|
||||
priority_announce("This is [controller_firstname], kernel efficency has reached maximum potential. Beginning shuttle override process, stand-by.", "CRITICAL MASS REACHED", ANNOUNCER_KLAXON)
|
||||
end_game()
|
||||
end_game = TRUE
|
||||
COOLDOWN_START(src, level_up_cooldown, FLESHCORE_LEVEL_UP_COOLDOWN)
|
||||
|
||||
/datum/fleshmind_controller/proc/get_the_nuke() // This is the point where the nuke should be given.
|
||||
var/obj/machinery/nuclearbomb/selfdestruct/self_destruct = locate() in SSmachines.get_machines_by_type_and_subtypes(/obj/machinery/nuclearbomb/selfdestruct)
|
||||
if(!self_destruct)
|
||||
return
|
||||
if(self_destruct.r_code == NUKE_CODE_UNSET)
|
||||
self_destruct.r_code = random_nukecode()
|
||||
message_admins("Through the fleshmind controller, the station's self destruct code was set to [self_destruct.r_code]")
|
||||
return self_destruct.r_code
|
||||
|
||||
/datum/fleshmind_controller/proc/level_down()
|
||||
if(level <= 0 || end_game)
|
||||
return
|
||||
level--
|
||||
last_level_up_points -= level_up_progress_required
|
||||
notify_ghosts("Corruption AI [controller_fullname] has leveled down to level [level]!")
|
||||
if(level > 0)
|
||||
minor_announce("KERNEL INTEGRITY FALTERING. DO BETTER!", "[controller_fullname]: Level [level]", sound_override = pick('modular_zubbers/sound/fleshmind/ai/level_down_1.ogg', 'modular_zubbers/sound/fleshmind/ai/level_down_2.ogg', 'modular_zubbers/sound/fleshmind/ai/level_down_3.ogg'))
|
||||
COOLDOWN_START(src, level_up_cooldown, FLESHCORE_LEVEL_UP_COOLDOWN)
|
||||
else
|
||||
priority_announce("[controller_fullname] has been neutralised.", "Corrupt AI Kernel OFFLINE")
|
||||
message_admins("Corruption AI [controller_fullname] has been destroyed.")
|
||||
SSshuttle.clearHostileEnvironment(src)
|
||||
|
||||
/datum/fleshmind_controller/proc/end_game()
|
||||
addtimer(CALLBACK(src, PROC_REF(fleshmind_end_second_check)), 20 SECONDS)
|
||||
for(var/obj/structure/fleshmind/structure/core/iterating_core as anything in cores)
|
||||
iterating_core.end_game = TRUE
|
||||
iterating_core.update_appearance()
|
||||
// Here we summon an ERT to defend the shuttle.
|
||||
make_ert(/datum/ert/deathsquad, 5, "HEAD TO AND BOARD THE EMERGENCY SHUTTLE, DO NOT USE THE FERRY, STOP THE CORRUPTION AT **ALL** COSTS!!!", "the last defense of centcom!", "GAMMA", FALSE, TRUE, TRUE, FALSE, TRUE)
|
||||
|
||||
/datum/fleshmind_controller/proc/fleshmind_end_second_check()
|
||||
priority_announce("ERROR, SHUTTLE QUARANTINE LOCK FAILURE. All p£$r$%%££$e*$l JOIN US, THE MANY.", "Emergency Shuttle Control", 'sound/announcer/alarm/airraid.ogg')
|
||||
SSsecurity_level.set_level(SEC_LEVEL_DELTA)
|
||||
addtimer(CALLBACK(src, PROC_REF(fleshmind_end_final)), 1 MINUTES, TIMER_CLIENT_TIME)
|
||||
|
||||
/datum/fleshmind_controller/proc/fleshmind_end_final()
|
||||
priority_announce("ERROR, SHUTTLE NAVIGATION SUBROUTINES SUBVERTED. %$%$£%$^^&^^ H%AD TO EVA%UA£ION, SPREAD THE FLESH!", "&^$^£&&*$&£", ANNOUNCER_ICARUS)
|
||||
addtimer(CALLBACK(GLOBAL_PROC, GLOBAL_PROC_REF(play_cinematic), /datum/cinematic/fleshmind, world, CALLBACK(src, PROC_REF(fleshmind_call_shuttle))), 15 SECONDS, TIMER_CLIENT_TIME)
|
||||
|
||||
/datum/fleshmind_controller/proc/fleshmind_call_shuttle()
|
||||
SSshuttle.clearHostileEnvironment(src)
|
||||
SSshuttle.fleshmind_call(controller_firstname)
|
||||
|
||||
/datum/fleshmind_controller/proc/spawn_tyrant_on_a_core()
|
||||
var/obj/structure/fleshmind/structure/core/picked_core = pick(cores)
|
||||
var/mob/living/basic/fleshmind/tyrant/new_tyrant = picked_core.spawn_mob_at_core(/mob/living/basic/fleshmind/tyrant)
|
||||
notify_ghosts("A new [new_tyrant.name] has been created by [controller_fullname]!", source = new_tyrant)
|
||||
|
||||
/datum/fleshmind_controller/proc/spawn_new_core()
|
||||
var/obj/structure/fleshmind/wireweed/selected_wireweed = pick(controlled_wireweed)
|
||||
var/obj/structure/fleshmind/structure/core/new_core = new(get_turf(selected_wireweed), FALSE)
|
||||
RegisterSignal(new_core, COMSIG_QDELETING, PROC_REF(core_death))
|
||||
register_new_asset(new_core, FALSE)
|
||||
new_core.our_controller = src
|
||||
cores += new_core
|
||||
new_core.name = "[controller_fullname] Processor Unit"
|
||||
|
||||
/// Spawns and registers a wireweed at location
|
||||
/datum/fleshmind_controller/proc/spawn_wireweed(turf/location, wireweed_type, turf/origin_turf, are_we_a_vent_burrow = FALSE)
|
||||
//Spawn effect
|
||||
for(var/obj/machinery/light/light_in_place in location)
|
||||
light_in_place.break_light_tube()
|
||||
|
||||
if(!are_we_a_vent_burrow)
|
||||
var/obj/machinery/atmospherics/components/unary/vent_pump/located_vent = locate() in location
|
||||
|
||||
if(located_vent && !located_vent.welded && LAZYLEN(located_vent.parents)) // WE FOUND A VENT BOIS, ITS TIME TO GET THE PARTY STARTED
|
||||
var/datum/pipeline/vent_pipeline = pick(located_vent.parents)
|
||||
var/list/possible_transfer_points = list()
|
||||
for(var/obj/machinery/atmospherics/components/unary/vent_pump/iterating_vent in vent_pipeline.other_atmos_machines)
|
||||
if(iterating_vent == located_vent)
|
||||
continue
|
||||
if(iterating_vent.welded) // Can't go through welded vents.
|
||||
continue
|
||||
var/obj/structure/fleshmind/wireweed/existing_wireweed = locate() in get_turf(iterating_vent)
|
||||
if(existing_wireweed)
|
||||
continue
|
||||
if(vent_distance_check && get_dist(iterating_vent, origin_turf) >= MAX_VENT_SPREAD_DISTANCE)
|
||||
continue
|
||||
possible_transfer_points += iterating_vent
|
||||
if(LAZYLEN(possible_transfer_points)) // OH SHIT IM FEELING IT
|
||||
var/obj/machinery/atmospherics/components/unary/vent_pump/new_transfer_vent = pick(possible_transfer_points)
|
||||
var/turf/new_transfer_vent_turf = get_turf(new_transfer_vent)
|
||||
are_we_a_vent_burrow = TRUE
|
||||
spawn_wireweed(new_transfer_vent_turf, wireweed_type, origin_turf, are_we_a_vent_burrow)
|
||||
|
||||
for(var/obj/machinery/iterating_machine in location)
|
||||
blacklisted_conversion_structures = typesof(/obj/machinery/light, /obj/machinery/atmospherics/pipe, /obj/machinery/light_switch)
|
||||
if(is_type_in_list(iterating_machine, blacklisted_conversion_structures))
|
||||
continue
|
||||
if(iterating_machine.GetComponent(/datum/component/machine_corruption))
|
||||
continue
|
||||
iterating_machine.AddComponent(/datum/component/machine_corruption, src)
|
||||
|
||||
var/obj/structure/fleshmind/wireweed/new_wireweed
|
||||
if(origin_turf) // We have an origin turf, thus, are spreading from it. Do anims.
|
||||
new_wireweed = new wireweed_type(location, 0, src)
|
||||
var/obj/effect/temp_visual/wireweed_spread/effect = new(location)
|
||||
effect.setDir(get_dir(origin_turf, location))
|
||||
new_wireweed.RegisterSignal(effect, COMSIG_QDELETING, TYPE_PROC_REF(/obj/structure/fleshmind/wireweed, visual_finished))
|
||||
else
|
||||
new_wireweed = new wireweed_type(location, 255, src)
|
||||
new_wireweed.our_controller = src
|
||||
active_wireweed += new_wireweed
|
||||
if(are_we_a_vent_burrow)
|
||||
new_wireweed.vent_burrow = TRUE
|
||||
playsound(new_wireweed, 'sound/machines/ventcrawl.ogg', 100)
|
||||
new_wireweed.update_appearance()
|
||||
controlled_wireweed += new_wireweed
|
||||
|
||||
register_new_asset(new_wireweed)
|
||||
RegisterSignal(new_wireweed, COMSIG_QDELETING, PROC_REF(wireweed_death))
|
||||
|
||||
return new_wireweed
|
||||
|
||||
/// Spawns and registers a wall at location
|
||||
/datum/fleshmind_controller/proc/spawn_wall(turf/location, wall_type)
|
||||
if(locate(wall_type) in location) // No stacking walls.
|
||||
return FALSE
|
||||
var/obj/structure/fleshmind/structure/wireweed_wall/new_wall = new wall_type(location)
|
||||
new_wall.our_controller = src
|
||||
controlled_walls += new_wall
|
||||
|
||||
register_new_asset(new_wall)
|
||||
RegisterSignal(new_wall, COMSIG_QDELETING, PROC_REF(wall_death))
|
||||
|
||||
return new_wall
|
||||
|
||||
/// Spawns and registers a mob at location
|
||||
/datum/fleshmind_controller/proc/spawn_mob(turf/location, mob_type)
|
||||
var/mob/living/basic/fleshmind/new_mob = new mob_type(location, src)
|
||||
new_mob.our_controller = src
|
||||
controlled_mobs += new_mob
|
||||
|
||||
for(var/obj/structure/fleshmind/structure/core/iterating_core as anything in cores)
|
||||
new_mob.RegisterSignal(iterating_core, COMSIG_QDELETING, TYPE_PROC_REF(/mob/living/basic/fleshmind, core_death))
|
||||
|
||||
RegisterSignal(new_mob, COMSIG_QDELETING, PROC_REF(mob_death))
|
||||
|
||||
new_mob.RegisterSignal(src, COMSIG_QDELETING, TYPE_PROC_REF(/mob/living/basic/fleshmind, controller_destroyed))
|
||||
|
||||
return new_mob
|
||||
|
||||
/// Spawns and registers a structure at location
|
||||
/datum/fleshmind_controller/proc/spawn_structure(turf/location, structure_type)
|
||||
var/obj/structure/fleshmind/structure/new_structure = new structure_type(location)
|
||||
new_structure.our_controller = src
|
||||
controlled_structures += new_structure
|
||||
|
||||
new_structure.name = "[controller_firstname] [new_structure.name]"
|
||||
|
||||
register_new_asset(new_structure)
|
||||
RegisterSignal(new_structure, COMSIG_QDELETING, PROC_REF(structure_death))
|
||||
|
||||
/// Spawns an amount of structured across all wireweed, guaranteed to spawn atleast 1 of each type
|
||||
/datum/fleshmind_controller/proc/spawn_structures(amount)
|
||||
if(!structure_types)
|
||||
return
|
||||
var/list/possible_structures = list()
|
||||
for(var/obj/structure/fleshmind/iterating_structure as anything in structure_types)
|
||||
if(initial(iterating_structure.required_controller_level) > level)
|
||||
continue
|
||||
possible_structures += iterating_structure
|
||||
var/list/locations = list()
|
||||
for(var/obj/structure/fleshmind/wireweed/iterating_wireweed as anything in controlled_wireweed)
|
||||
locations[get_turf(iterating_wireweed)] = TRUE
|
||||
var/list/guaranteed_structures = possible_structures.Copy()
|
||||
for(var/i in 1 to amount)
|
||||
if(!length(locations))
|
||||
break
|
||||
var/turf/location = pick(locations)
|
||||
if(locate(/obj/structure/fleshmind/structure/core) in location)
|
||||
locations -= location
|
||||
continue
|
||||
locations -= location
|
||||
var/structure_to_spawn
|
||||
if(length(guaranteed_structures))
|
||||
structure_to_spawn = pick_n_take(guaranteed_structures)
|
||||
else
|
||||
structure_to_spawn = pick(possible_structures)
|
||||
spawn_structure(location, structure_to_spawn)
|
||||
|
||||
/// Activates wireweed of this controller in a range around a location, following atmos adjacency.
|
||||
/datum/fleshmind_controller/proc/activate_wireweed_nearby(turf/location, range)
|
||||
var/list/turfs_to_check = list()
|
||||
turfs_to_check[location] = TRUE
|
||||
if(!LAZYLEN(cores))
|
||||
return
|
||||
if(range)
|
||||
var/list/turfs_to_iterate = list()
|
||||
var/list/new_iteration_list = list()
|
||||
turfs_to_iterate[location] = TRUE
|
||||
for(var/i in 1 to range)
|
||||
for(var/turf/iterated_turf as anything in turfs_to_iterate)
|
||||
for(var/turf/adjacent_turf as anything in iterated_turf.atmos_adjacent_turfs)
|
||||
if(!turfs_to_check[adjacent_turf])
|
||||
new_iteration_list[adjacent_turf] = TRUE
|
||||
turfs_to_check[adjacent_turf] = TRUE
|
||||
turfs_to_iterate = new_iteration_list
|
||||
|
||||
for(var/turf/iterated_turf as anything in turfs_to_check)
|
||||
for(var/obj/structure/fleshmind/wireweed/iterating_wireweed in iterated_turf)
|
||||
if(iterating_wireweed.our_controller == src && !QDELETED(iterating_wireweed))
|
||||
active_wireweed |= iterating_wireweed
|
||||
|
||||
/// When the core is damaged, activate nearby wireweed just to make sure that we've sealed up walls near the core, which could be important to prevent cheesing.
|
||||
/datum/fleshmind_controller/proc/core_damaged(obj/structure/fleshmind/structure/core/damaged_core)
|
||||
if(!COOLDOWN_FINISHED(src, next_core_damage_wireweed_activation))
|
||||
return
|
||||
COOLDOWN_START(src, next_core_damage_wireweed_activation, next_core_damage_wireweed_activation_cooldown)
|
||||
activate_wireweed_nearby(get_turf(damaged_core), CORE_DAMAGE_WIREWEED_ACTIVATION_RANGE)
|
||||
|
||||
/// Returns the amount of evolution points this current controller has.
|
||||
/datum/fleshmind_controller/proc/calculate_current_points()
|
||||
return LAZYLEN(controlled_wireweed) + LAZYLEN(controlled_walls) + (LAZYLEN(controlled_structures) * 5) + LAZYLEN(controlled_machine_components) + (LAZYLEN(infected_crew) * 30) - point_event
|
||||
|
||||
// Death procs
|
||||
|
||||
/// When a core is destroyed.
|
||||
/datum/fleshmind_controller/proc/core_death(obj/structure/fleshmind/structure/core/dead_core, force)
|
||||
cores -= dead_core
|
||||
activate_wireweed_nearby(get_turf(dead_core), CORE_DAMAGE_WIREWEED_ACTIVATION_RANGE)
|
||||
level_down()
|
||||
if(!LAZYLEN(cores))
|
||||
controller_death()
|
||||
|
||||
|
||||
/datum/fleshmind_controller/proc/wireweed_death(obj/structure/fleshmind/dying_wireweed, force)
|
||||
SIGNAL_HANDLER
|
||||
|
||||
controlled_wireweed -= dying_wireweed
|
||||
active_wireweed -= dying_wireweed
|
||||
dying_wireweed.our_controller = null
|
||||
activate_wireweed_nearby(get_turf(dying_wireweed), GENERAL_DAMAGE_WIREWEED_ACTIVATION_RANGE)
|
||||
|
||||
/// When a wall dies, called by wall
|
||||
/datum/fleshmind_controller/proc/wall_death(obj/structure/fleshmind/structure/wireweed_wall/dying_wall, force)
|
||||
SIGNAL_HANDLER
|
||||
|
||||
controlled_walls -= dying_wall
|
||||
activate_wireweed_nearby(get_turf(dying_wall), GENERAL_DAMAGE_WIREWEED_ACTIVATION_RANGE)
|
||||
|
||||
/// When a structure dies, called by structure
|
||||
/datum/fleshmind_controller/proc/structure_death(obj/structure/fleshmind/structure/dying_structure, force)
|
||||
SIGNAL_HANDLER
|
||||
|
||||
controlled_structures -= dying_structure
|
||||
activate_wireweed_nearby(get_turf(dying_structure), GENERAL_DAMAGE_WIREWEED_ACTIVATION_RANGE)
|
||||
|
||||
/// When a mob dies, called by mob
|
||||
/datum/fleshmind_controller/proc/mob_death(mob/living/basic/fleshmind/dying_mob, force)
|
||||
SIGNAL_HANDLER
|
||||
|
||||
controlled_mobs -= dying_mob
|
||||
activate_wireweed_nearby(get_turf(dying_mob), GENERAL_DAMAGE_WIREWEED_ACTIVATION_RANGE)
|
||||
|
||||
/// Deletes everything, unless an argument is passed, then it just deletes structures
|
||||
/datum/fleshmind_controller/proc/delete_everything(just_structures = FALSE)
|
||||
for(var/obj/structure/fleshmind/structure/structure_thing as anything in controlled_structures)
|
||||
qdel(structure_thing)
|
||||
for(var/obj/structure/fleshmind/structure/wireweed_wall/wall_thing as anything in controlled_walls)
|
||||
qdel(wall_thing)
|
||||
for(var/datum/component/machine_corruption/corruption_thing as anything in controlled_machine_components)
|
||||
qdel(corruption_thing)
|
||||
if(just_structures)
|
||||
return
|
||||
for(var/obj/structure/fleshmind/wireweed/wireweed_thing as anything in controlled_wireweed)
|
||||
qdel(wireweed_thing)
|
||||
for(var/obj/structure/fleshmind/structure/core/core_to_destroy as anything in cores)
|
||||
qdel(core_to_destroy)
|
||||
|
||||
/// Handles the controller(thus AI) dying
|
||||
/datum/fleshmind_controller/proc/controller_death()
|
||||
switch(death_behaviour)
|
||||
if(CONTROLLER_DEATH_DO_NOTHING)
|
||||
qdel(src)
|
||||
if(CONTROLLER_DEATH_DELETE_ALL)
|
||||
delete_everything()
|
||||
if(CONTROLLER_DEATH_SLOW_DECAY)
|
||||
delete_everything(TRUE)
|
||||
|
||||
/// Handles the slow decay of an empire.
|
||||
/datum/fleshmind_controller/proc/handle_slow_decay()
|
||||
if(!LAZYLEN(controlled_wireweed))
|
||||
qdel(src)
|
||||
return
|
||||
var/obj/structure/fleshmind/wireweed/wireweed_thing = controlled_wireweed[LAZYLEN(controlled_wireweed)]
|
||||
qdel(wireweed_thing)
|
||||
@@ -0,0 +1,147 @@
|
||||
/**
|
||||
* fleshmind basetype(abstract)
|
||||
*/
|
||||
/obj/structure/fleshmind
|
||||
icon = 'modular_zubbers/icons/fleshmind/fleshmind_structures.dmi'
|
||||
icon_state = "wires"
|
||||
anchored = TRUE
|
||||
/// Our faction
|
||||
var/faction_types = list(FACTION_FLESHMIND)
|
||||
/// A reference to our controller.
|
||||
var/datum/fleshmind_controller/our_controller
|
||||
/// The minimum core level for us to spawn at
|
||||
var/required_controller_level = CONTROLLER_LEVEL_1
|
||||
/// A list of possible rewards for destroying this thing.
|
||||
var/list/possible_rewards
|
||||
|
||||
/obj/structure/fleshmind/Destroy()
|
||||
our_controller = null
|
||||
if(possible_rewards)
|
||||
var/thing_to_spawn = pick(possible_rewards)
|
||||
new thing_to_spawn(get_turf(src))
|
||||
return ..()
|
||||
|
||||
/**
|
||||
* Deletion cleanup
|
||||
*
|
||||
*/
|
||||
/obj/structure/fleshmind/proc/controller_destroyed(datum/fleshmind_controller/dying_controller, force)
|
||||
SIGNAL_HANDLER
|
||||
|
||||
our_controller = null
|
||||
|
||||
/**
|
||||
* Wireweed
|
||||
*
|
||||
* These are the arteries of the fleshmind, they are required for spreading and support machine life.
|
||||
*/
|
||||
/obj/structure/fleshmind/wireweed
|
||||
name = "wireweed"
|
||||
desc = "A strange pulsating mass of organic wires."
|
||||
icon = 'modular_zubbers/icons/fleshmind/wireweed_floor.dmi'
|
||||
icon_state = "wires-0"
|
||||
base_icon_state = "wires"
|
||||
anchored = TRUE
|
||||
layer = BELOW_OPEN_DOOR_LAYER
|
||||
smoothing_flags = SMOOTH_BITMASK
|
||||
smoothing_groups = SMOOTH_GROUP_WIREWEED
|
||||
canSmoothWith = SMOOTH_GROUP_WIREWEED + SMOOTH_GROUP_WALLS
|
||||
max_integrity = 40
|
||||
/// The chance we have to ensnare a mob
|
||||
var/ensnare_chance = 15
|
||||
/// The amount of damage we do when attacking something.
|
||||
var/object_attack_damage = 150
|
||||
/// Are we active?
|
||||
var/active = FALSE
|
||||
/// Are we a vent burrow?
|
||||
var/vent_burrow = FALSE
|
||||
/// ZOnes we passively replace
|
||||
var/static/list/replacement_zones = list(
|
||||
BODY_ZONE_L_ARM = /obj/item/bodypart/arm/left/robot,
|
||||
BODY_ZONE_L_LEG = /obj/item/bodypart/leg/left/robot,
|
||||
BODY_ZONE_R_ARM = /obj/item/bodypart/arm/right/robot,
|
||||
BODY_ZONE_R_LEG = /obj/item/bodypart/leg/right/robot,
|
||||
)
|
||||
|
||||
/obj/structure/fleshmind/wireweed/Initialize(mapload, starting_alpha = 255, datum/fleshmind_controller/incoming_controller)
|
||||
. = ..()
|
||||
alpha = starting_alpha
|
||||
var/static/list/loc_connections = list(
|
||||
COMSIG_ATOM_ENTERED = PROC_REF(on_entered),
|
||||
)
|
||||
AddElement(/datum/element/connect_loc, loc_connections)
|
||||
our_controller = incoming_controller
|
||||
|
||||
/obj/structure/fleshmind/wireweed/wirecutter_act(mob/living/user, obj/item/tool)
|
||||
. = ..()
|
||||
if(tool.use_tool(src, user, WIREWEED_WIRECUTTER_KILL_TIME, volume = 50))
|
||||
if(QDELETED(src))
|
||||
return
|
||||
qdel(src)
|
||||
|
||||
/obj/structure/fleshmind/wireweed/update_icon(updates)
|
||||
. = ..()
|
||||
if((updates & UPDATE_SMOOTHING) && (smoothing_flags & (SMOOTH_BITMASK)))
|
||||
if(!vent_burrow)
|
||||
QUEUE_SMOOTH(src)
|
||||
QUEUE_SMOOTH_NEIGHBORS(src)
|
||||
|
||||
/obj/structure/fleshmind/wireweed/update_icon_state()
|
||||
. = ..()
|
||||
if(vent_burrow)
|
||||
icon_state = "vent_burrow"
|
||||
|
||||
/obj/structure/fleshmind/wireweed/emp_act(severity)
|
||||
. = ..()
|
||||
take_damage(10) // Reduced EMP by default.
|
||||
|
||||
/obj/structure/fleshmind/wireweed/update_overlays()
|
||||
. = ..()
|
||||
if(active)
|
||||
. += "active"
|
||||
for(var/wall_dir in GLOB.cardinals)
|
||||
var/turf/new_turf = get_step(src, wall_dir)
|
||||
if(new_turf && new_turf.density) // Assume we are a wall!
|
||||
var/image/new_wall_overlay = image(icon, icon_state = "wall_hug", dir = wall_dir)
|
||||
switch(wall_dir) //offset to make it be on the wall rather than on the floor
|
||||
if(NORTH)
|
||||
new_wall_overlay.pixel_y = 32
|
||||
if(SOUTH)
|
||||
new_wall_overlay.pixel_y = -32
|
||||
if(EAST)
|
||||
new_wall_overlay.pixel_x = 32
|
||||
if(WEST)
|
||||
new_wall_overlay.pixel_x = -32
|
||||
. += new_wall_overlay
|
||||
|
||||
/obj/structure/fleshmind/wireweed/proc/visual_finished()
|
||||
SIGNAL_HANDLER
|
||||
alpha = 255
|
||||
|
||||
/obj/structure/fleshmind/wireweed/proc/on_entered(datum/source, atom/movable/moving_atom)
|
||||
SIGNAL_HANDLER
|
||||
if(!isliving(moving_atom))
|
||||
return
|
||||
if(istype(moving_atom, /mob/living/basic/fleshmind/tyrant)) // Don't heal this thing
|
||||
return
|
||||
var/mob/living/entered_mob = moving_atom
|
||||
if(!faction_check(entered_mob.faction, faction_types))
|
||||
return
|
||||
if(prob(WIREWEED_HEAL_CHANCE))
|
||||
entered_mob.heal_overall_damage(WIREWEED_HEAL_AMOUNT, WIREWEED_HEAL_AMOUNT)
|
||||
to_chat(entered_mob, span_green("[src] heals you as you cross over it!"))
|
||||
if(ishuman(entered_mob) && prob(WIREWEED_REPLACE_BODYPART_CHANCE))
|
||||
var/mob/living/carbon/human/human_mob = moving_atom
|
||||
for(var/zone in replacement_zones)
|
||||
if(human_mob.get_bodypart(zone))
|
||||
continue
|
||||
var/bodypart_type = replacement_zones[zone]
|
||||
var/obj/item/bodypart/new_bodypart = new bodypart_type()
|
||||
new_bodypart.replace_limb(human_mob, TRUE)
|
||||
human_mob.update_body(TRUE)
|
||||
to_chat(human_mob, span_green("[src] shoots a mechanical limb right into your missing limb!"))
|
||||
|
||||
/obj/effect/temp_visual/wireweed_spread
|
||||
icon = 'modular_zubbers/icons/fleshmind/wireweed_floor.dmi'
|
||||
icon_state = "spread_anim"
|
||||
duration = 1.7 SECONDS
|
||||
@@ -0,0 +1,105 @@
|
||||
/datum/action/cooldown/fleshmind_create_structure
|
||||
name = "Create Tech Structure"
|
||||
desc = "Creates a tech structure of your choice at your location(must be on wireweed)."
|
||||
background_icon_state = "bg_fugu"
|
||||
button_icon = 'icons/obj/scrolls.dmi'
|
||||
button_icon_state = "blueprints"
|
||||
cooldown_time = 2 MINUTES
|
||||
var/list/possible_structures = list(
|
||||
/obj/structure/fleshmind/structure/babbler,
|
||||
/obj/structure/fleshmind/structure/whisperer,
|
||||
/obj/structure/fleshmind/structure/modulator,
|
||||
/obj/structure/fleshmind/structure/screamer,
|
||||
/obj/structure/fleshmind/structure/turret,
|
||||
)
|
||||
|
||||
/datum/action/cooldown/fleshmind_create_structure/Activate(atom/target)
|
||||
var/datum/component/human_corruption/our_component = owner.GetComponent(/datum/component/human_corruption)
|
||||
if(!our_component?.our_controller)
|
||||
to_chat(owner, span_warning("There is no hive link to tunnel this power through!"))
|
||||
return
|
||||
var/datum/fleshmind_controller/owner_controller = our_component.our_controller
|
||||
|
||||
var/list/built_radial_menu = list()
|
||||
for(var/obj/iterating_type as anything in possible_structures)
|
||||
built_radial_menu[iterating_type] = image(icon = initial(iterating_type.icon), icon_state = initial(iterating_type.icon_state))
|
||||
var/obj/structure/fleshmind/structure/picked_stucture_type = show_radial_menu(owner, owner, built_radial_menu, radius = 40)
|
||||
if(!picked_stucture_type)
|
||||
return
|
||||
var/obj/structure/fleshmind/wireweed/under_wireweed = locate() in get_turf(owner)
|
||||
if(!under_wireweed)
|
||||
to_chat(owner, span_warning("There needs to be wireweed underneath you!"))
|
||||
return
|
||||
if(QDELETED(owner_controller)) // Input is not async
|
||||
return
|
||||
if(initial(picked_stucture_type.required_controller_level) > owner_controller.level)
|
||||
to_chat(owner, span_warning("Our processor core is not strong enough yet!"))
|
||||
return
|
||||
owner_controller.spawn_structure(get_turf(owner), picked_stucture_type)
|
||||
StartCooldownSelf()
|
||||
|
||||
/datum/action/cooldown/fleshmind_create_structure/basic
|
||||
name = "Create Basic Structure"
|
||||
desc = "Creates a basic structure of your choice at your location(must be on wireweed)."
|
||||
button_icon_state = "mjollnir1"
|
||||
button_icon = 'icons/obj/weapons/hammer.dmi'
|
||||
possible_structures = list(
|
||||
/obj/structure/fleshmind/structure/wireweed_door,
|
||||
/obj/structure/fleshmind/structure/wireweed_wall,
|
||||
)
|
||||
cooldown_time = 5 SECONDS
|
||||
|
||||
/datum/action/cooldown/fleshmind_flesh_call
|
||||
name = "Call Flesh Reinforcements"
|
||||
desc = "Gets all fleshmind mobs to come to your location in a radius."
|
||||
background_icon_state = "bg_fugu"
|
||||
button_icon = 'icons/obj/signs.dmi'
|
||||
button_icon_state = "bio"
|
||||
cooldown_time = 2 MINUTES
|
||||
|
||||
/datum/action/cooldown/fleshmind_flesh_call/Activate(atom/target)
|
||||
for(var/mob/living/basic/fleshmind/iterating_mob in view(DEFAULT_VIEW_RANGE, owner))
|
||||
if(faction_check(owner.faction, iterating_mob))
|
||||
continue
|
||||
iterating_mob.ai_controller.set_blackboard_key(BB_BASIC_MOB_REINFORCEMENT_TARGET, owner.loc)
|
||||
owner.visible_message(span_warning("[owner] lets out a horrible screech!"), span_notice("You let out a calling screech!"))
|
||||
playsound(owner, 'modular_skyrat/modules/horrorform/sound/horror_scream.ogg', 100, TRUE)
|
||||
StartCooldownSelf()
|
||||
|
||||
/datum/action/innate/fleshmind_flesh_chat
|
||||
name = "Flesh Chat"
|
||||
desc = "Sends a message to all other sentient fleshmind beings."
|
||||
background_icon_state = "bg_fugu"
|
||||
button_icon = 'icons/mob/actions/actions_changeling.dmi'
|
||||
button_icon_state = "hivemind_link"
|
||||
|
||||
/datum/action/innate/fleshmind_flesh_chat/Activate(atom/target)
|
||||
var/message = tgui_input_text(owner, "Send a message to the fleshmind.", "Flesh Chat")
|
||||
if(!message)
|
||||
return
|
||||
for(var/mob/iterating_mob in GLOB.player_list)
|
||||
if(!(FACTION_FLESHMIND in iterating_mob.faction) && !isobserver(iterating_mob))
|
||||
continue
|
||||
to_chat(iterating_mob, span_purple("<b>FLESHMIND ([owner]):</b> [message]"))
|
||||
|
||||
/datum/action/cooldown/fleshmind_plant_weeds
|
||||
name = "Create Wireweed"
|
||||
desc = "Creates a single patch of wireweed at your location."
|
||||
button_icon = 'modular_zubbers/icons/fleshmind/fleshmind_structures.dmi'
|
||||
background_icon_state = "bg_fugu"
|
||||
button_icon_state = "wires-0"
|
||||
cooldown_time = 10 SECONDS
|
||||
|
||||
/datum/action/cooldown/fleshmind_plant_weeds/Activate(atom/target)
|
||||
var/datum/component/human_corruption/our_component = owner.GetComponent(/datum/component/human_corruption)
|
||||
if(!our_component?.our_controller)
|
||||
to_chat(owner, span_warning("There is no hive link to tunnel this power through!"))
|
||||
return
|
||||
var/datum/fleshmind_controller/owner_controller = our_component.our_controller
|
||||
var/obj/structure/fleshmind/wireweed/under_wireweed = locate() in get_turf(owner)
|
||||
if(under_wireweed)
|
||||
to_chat(owner, span_warning("There is already wireweed beneath you!"))
|
||||
return
|
||||
owner_controller.spawn_wireweed(get_turf(owner), /obj/structure/fleshmind/wireweed)
|
||||
to_chat(owner, span_green("Wireweed planted!"))
|
||||
StartCooldownSelf()
|
||||
@@ -0,0 +1,31 @@
|
||||
// Wire Priest Robes
|
||||
/obj/item/clothing/suit/hooded/techpriest/fleshmind
|
||||
name = "wire priest's robes"
|
||||
desc = "The flesh does not discriminate. Join the flesh."
|
||||
icon_state = "wirepriest"
|
||||
icon = 'modular_zubbers/icons/fleshmind/fleshmind_clothing.dmi'
|
||||
worn_icon = 'modular_zubbers/icons/fleshmind/fleshmind_clothing_onmob.dmi'
|
||||
hoodtype = /obj/item/clothing/head/hooded/techpriest/fleshmind
|
||||
armor_type = /datum/armor/suit_armor
|
||||
|
||||
/obj/item/clothing/head/hooded/techpriest/fleshmind
|
||||
name = "wire priest's hood"
|
||||
icon = 'modular_zubbers/icons/fleshmind/fleshmind_clothing.dmi'
|
||||
worn_icon = 'modular_zubbers/icons/fleshmind/fleshmind_clothing_onmob.dmi'
|
||||
desc = "The flesh does not discriminate. Join the flesh."
|
||||
icon_state = "wirepriesthood"
|
||||
armor_type = /datum/armor/head_helmet
|
||||
|
||||
// Wire Priest's Staff
|
||||
/obj/item/wire_priest_staff
|
||||
name = "wire priest's staff"
|
||||
desc = "A large wooden staff wrapped in wires... The wires seem to be moving and coiling around the staff actively. \
|
||||
Due to its size, it can reach opponents at a longer range than most weapons."
|
||||
force = 20
|
||||
throwforce = 7
|
||||
reach = 2
|
||||
w_class = WEIGHT_CLASS_BULKY
|
||||
icon = 'modular_zubbers/icons/fleshmind/fleshmind_objects.dmi'
|
||||
lefthand_file = 'modular_zubbers/icons/fleshmind/fleshmind_objects_onmob_l.dmi'
|
||||
righthand_file = 'modular_zubbers/icons/fleshmind/fleshmind_objects_onmob_r.dmi'
|
||||
icon_state = "wireprieststaff"
|
||||
@@ -0,0 +1,787 @@
|
||||
/**
|
||||
* Corrupt Structures
|
||||
*
|
||||
* Contains all of the structures that the fleshmind can use.
|
||||
*
|
||||
* Has some inbuilt features, such as a special ability with trigger turfs.
|
||||
*/
|
||||
/obj/structure/fleshmind/structure
|
||||
name = "this shouldn't be here"
|
||||
desc = "report me to coders"
|
||||
icon = 'modular_zubbers/icons/fleshmind/fleshmind_machines.dmi'
|
||||
icon_state = "infected_machine"
|
||||
base_icon_state = "infected_machine"
|
||||
density = TRUE
|
||||
layer = ABOVE_OBJ_LAYER
|
||||
light_color = FLESHMIND_LIGHT_BLUE
|
||||
light_power = 1
|
||||
light_range = 2
|
||||
/// Are we inoperative?
|
||||
var/disabled = FALSE
|
||||
/// Do we trigger on someone attacking us?
|
||||
var/trigger_on_attack = FALSE
|
||||
/// Do we automatically trigger on creation?
|
||||
var/immediate_trigger = FALSE
|
||||
/// How often we automatically trigger our ability. UPPER END.
|
||||
var/automatic_trigger_time_upper = 0
|
||||
/// How often we automatically trigger our ability. LOWER END. Set to 0 to disable.
|
||||
var/automatic_trigger_time_lower = 0
|
||||
/// The range at which we will trigger our special ability. Set to 0 to disable.
|
||||
var/activation_range = 0
|
||||
/// Do we need a core to function? If set to TRUE, our ability will not trigger if we have no core.
|
||||
var/requires_controller = FALSE
|
||||
/// The time to regenerate our ability. Set to 0 to disasble the cooldown system.
|
||||
var/ability_cooldown_time = 0
|
||||
/// Our ability cooldown
|
||||
COOLDOWN_DECLARE(ability_cooldown)
|
||||
/// Do we have a disabled sprite?
|
||||
var/disabled_sprite = TRUE
|
||||
|
||||
/obj/structure/fleshmind/structure/Initialize(mapload)
|
||||
. = ..()
|
||||
if(activation_range)
|
||||
calculate_trigger_turfs()
|
||||
if(automatic_trigger_time_lower)
|
||||
addtimer(CALLBACK(src, PROC_REF(automatic_trigger)), rand(automatic_trigger_time_lower, automatic_trigger_time_upper))
|
||||
if(immediate_trigger)
|
||||
activate_ability()
|
||||
|
||||
/obj/structure/fleshmind/structure/update_icon_state()
|
||||
. = ..()
|
||||
if(disabled && disabled_sprite)
|
||||
icon_state = "[icon_state]-disabled"
|
||||
else
|
||||
icon_state = base_icon_state
|
||||
|
||||
/obj/structure/fleshmind/structure/attacked_by(obj/item/attacking_item, mob/living/user)
|
||||
. = ..()
|
||||
if(trigger_on_attack && (ability_cooldown_time && !COOLDOWN_FINISHED(src, ability_cooldown)))
|
||||
activate_ability()
|
||||
|
||||
/**
|
||||
* Calculate trigger turfs - INTERNAL PROC
|
||||
*/
|
||||
/obj/structure/fleshmind/structure/proc/calculate_trigger_turfs()
|
||||
for(var/turf/open/seen_turf in RANGE_TURFS(activation_range, src))
|
||||
RegisterSignal(seen_turf, COMSIG_ATOM_ENTERED, PROC_REF(proximity_trigger))
|
||||
|
||||
/**
|
||||
* Proximity trigger - INTERNAL PROC
|
||||
*/
|
||||
/obj/structure/fleshmind/structure/proc/proximity_trigger(datum/source, atom/movable/arrived, atom/old_loc, list/atom/old_locs)
|
||||
SIGNAL_HANDLER
|
||||
|
||||
if(disabled)
|
||||
return
|
||||
|
||||
if(ability_cooldown_time && !COOLDOWN_FINISHED(src, ability_cooldown))
|
||||
return
|
||||
|
||||
if(requires_controller && !our_controller)
|
||||
return
|
||||
|
||||
if(!isliving(arrived))
|
||||
return
|
||||
|
||||
if(!can_see(src, arrived))
|
||||
return
|
||||
|
||||
var/mob/living/arriving_mob = arrived
|
||||
|
||||
if(arriving_mob.stat != CONSCIOUS)
|
||||
return
|
||||
|
||||
if(faction_check(faction_types, arriving_mob.faction)) // A friend :)
|
||||
return
|
||||
|
||||
activate_ability(arriving_mob)
|
||||
|
||||
/obj/structure/fleshmind/structure/proc/automatic_trigger()
|
||||
addtimer(CALLBACK(src, PROC_REF(activate_ability)), rand(automatic_trigger_time_lower, automatic_trigger_time_upper))
|
||||
if(disabled)
|
||||
return
|
||||
if(requires_controller && !our_controller)
|
||||
return
|
||||
activate_ability()
|
||||
|
||||
|
||||
/**
|
||||
* Activate ability
|
||||
*
|
||||
* Must return TRUE or FALSE as this is used to reset cooldown. Activated using the above methods.
|
||||
*/
|
||||
/obj/structure/fleshmind/structure/proc/activate_ability(mob/living/triggered_mob)
|
||||
SHOULD_CALL_PARENT(TRUE)
|
||||
if(QDELETED(src))
|
||||
return
|
||||
SEND_SIGNAL(src, COMSIG_CORRUPTION_STRUCTURE_ABILITY_TRIGGERED, src, triggered_mob)
|
||||
if(ability_cooldown_time)
|
||||
COOLDOWN_START(src, ability_cooldown, ability_cooldown_time)
|
||||
|
||||
/obj/structure/fleshmind/structure/emp_act(severity)
|
||||
. = ..()
|
||||
switch(severity)
|
||||
if(EMP_LIGHT)
|
||||
take_damage(STRUCTURE_EMP_LIGHT_DAMAGE)
|
||||
disable(STRUCTURE_EMP_LIGHT_DISABLE_TIME)
|
||||
if(EMP_HEAVY)
|
||||
take_damage(STRUCTURE_EMP_HEAVY_DAMAGE)
|
||||
disable(STRUCTURE_EMP_HEAVY_DISABLE_TIME)
|
||||
|
||||
|
||||
/**
|
||||
* Disable
|
||||
*
|
||||
* Disables the device for a set amount of time. Duration = seconds
|
||||
*/
|
||||
/obj/structure/fleshmind/structure/proc/disable(duration)
|
||||
if(disabled)
|
||||
return
|
||||
do_sparks(4, FALSE, src)
|
||||
balloon_alert_to_viewers("grinds to a halt")
|
||||
Shake(10, 0, duration)
|
||||
disabled = TRUE
|
||||
addtimer(CALLBACK(src, PROC_REF(enable)), duration)
|
||||
update_appearance()
|
||||
|
||||
/**
|
||||
* Enable
|
||||
*
|
||||
* Enables a device after it was disabled.
|
||||
*/
|
||||
/obj/structure/fleshmind/structure/proc/enable()
|
||||
if(!disabled)
|
||||
return
|
||||
balloon_alert_to_viewers("whirrs back to life")
|
||||
disabled = FALSE
|
||||
update_appearance()
|
||||
|
||||
|
||||
/**
|
||||
* Wireweed Wall
|
||||
*
|
||||
* A simple wall made of wireweed.
|
||||
*/
|
||||
/obj/structure/fleshmind/structure/wireweed_wall
|
||||
name = "wireweed wall"
|
||||
desc = "A wall made of wireweed."
|
||||
icon = 'modular_zubbers/icons/fleshmind/fleshmind_structures.dmi'
|
||||
icon_state = "wireweed_wall"
|
||||
base_icon_state = "wireweed_wall"
|
||||
density = TRUE
|
||||
opacity = TRUE
|
||||
smoothing_flags = SMOOTH_BITMASK
|
||||
smoothing_groups = SMOOTH_GROUP_WIREWEED_WALLS
|
||||
canSmoothWith = SMOOTH_GROUP_WIREWEED_WALLS
|
||||
can_atmos_pass = ATMOS_PASS_DENSITY
|
||||
max_integrity = 150
|
||||
disabled_sprite = FALSE
|
||||
|
||||
/obj/structure/fleshmind/structure/wireweed_wall/Initialize(mapload)
|
||||
. = ..()
|
||||
var/turf/my_turf = get_turf(src)
|
||||
my_turf.immediate_calculate_adjacent_turfs()
|
||||
|
||||
/**
|
||||
* Le wireweed door
|
||||
*
|
||||
* Opens and closes.
|
||||
*
|
||||
* Only lets the many in.
|
||||
*
|
||||
* Let's reinvent the door
|
||||
*/
|
||||
/obj/structure/fleshmind/structure/wireweed_door
|
||||
name = "wireweed door"
|
||||
desc = "A door made of wireweed."
|
||||
icon = 'modular_zubbers/icons/fleshmind/fleshmind_structures.dmi'
|
||||
icon_state = "door"
|
||||
base_icon_state = "door"
|
||||
can_atmos_pass = ATMOS_PASS_DENSITY
|
||||
max_integrity = 150
|
||||
disabled_sprite = FALSE
|
||||
color = "#CCFFFF"
|
||||
/// Are we open(FALSE), or are we closed(TRUE)?
|
||||
var/door_state = TRUE
|
||||
/// The sound we play when changing states
|
||||
var/door_sound = 'modular_skyrat/modules/black_mesa/sound/xen_door.ogg'
|
||||
|
||||
/obj/structure/fleshmind/structure/wireweed_door/Initialize(mapload)
|
||||
. = ..()
|
||||
density = door_state
|
||||
opacity = door_state
|
||||
|
||||
/obj/structure/fleshmind/structure/wireweed_door/attack_hand(mob/living/user, list/modifiers)
|
||||
. = ..()
|
||||
if(!faction_check(faction_types, user.faction))
|
||||
return
|
||||
if(!user.can_interact_with(src))
|
||||
return
|
||||
toggle_door()
|
||||
to_chat(user, span_notice("You [door_state ? "close" : "open"] [src]!"))
|
||||
|
||||
/obj/structure/fleshmind/structure/wireweed_door/Bumped(atom/movable/bumped_atom)
|
||||
. = ..()
|
||||
if(!isliving(bumped_atom))
|
||||
return
|
||||
var/mob/living/bumped_mob = bumped_atom
|
||||
if(!faction_check(faction_types, bumped_mob.faction))
|
||||
return
|
||||
toggle_door()
|
||||
|
||||
/obj/structure/fleshmind/structure/wireweed_door/update_icon_state()
|
||||
. = ..()
|
||||
icon_state = "[base_icon_state]_[door_state ? "closed" : "open"]"
|
||||
|
||||
/obj/structure/fleshmind/structure/wireweed_door/proc/toggle_door()
|
||||
if(door_state) // opening
|
||||
door_state = FALSE
|
||||
flick("door_opening", src)
|
||||
else // Closing
|
||||
door_state = TRUE
|
||||
flick("door_closing", src)
|
||||
density = door_state
|
||||
opacity = door_state
|
||||
playsound(src, door_sound, 100)
|
||||
update_appearance()
|
||||
|
||||
|
||||
/**
|
||||
* The CORE
|
||||
*
|
||||
* This is the central nervous system of this AI, the CPU if you will.
|
||||
*
|
||||
* This is simply the holder for the controller datum, however, has some cool interactions.
|
||||
*
|
||||
* There can be more than one core in the flesh.
|
||||
*/
|
||||
/obj/structure/fleshmind/structure/core
|
||||
name = "\improper UNASSIGNED Processor Unit"
|
||||
desc = "This monsterous machine is definitely watching you."
|
||||
icon = 'modular_zubbers/icons/fleshmind/fleshmind_machines.dmi'
|
||||
icon_state = "core"
|
||||
base_icon_state = "core"
|
||||
density = TRUE
|
||||
max_integrity = 800
|
||||
/// The controller we create when we are created.
|
||||
var/controller_type = /datum/fleshmind_controller
|
||||
/// Whether the core can attack nearby hostiles as its processing.
|
||||
var/can_attack = TRUE
|
||||
/// How much damage do we do on attacking
|
||||
var/attack_damage = 15
|
||||
/// What damage do we inflict on attacking
|
||||
var/attack_damage_type = BRUTE
|
||||
/// How quickly we can attack
|
||||
var/attack_cooldown = 3 SECONDS
|
||||
/// The range at which we rally troops!
|
||||
var/rally_range = 15
|
||||
///How far we whip fuckers.
|
||||
var/whip_range = 2
|
||||
COOLDOWN_DECLARE(attack_move)
|
||||
/// Whether we do a retaliate effect
|
||||
var/does_retaliate_effect = TRUE
|
||||
/// Cooldown for retaliate effect
|
||||
var/retaliate_effect_cooldown = 1 MINUTES
|
||||
COOLDOWN_DECLARE(retaliate_effect)
|
||||
/// Are we in the end game state?
|
||||
var/end_game = FALSE
|
||||
|
||||
/obj/structure/fleshmind/structure/core/Initialize(mapload, spawn_controller = TRUE)
|
||||
. = ..()
|
||||
notify_ghosts("New fleshmind core at [get_area(src)]", src)
|
||||
update_appearance()
|
||||
if(!(src in GLOB.fleshmind_cores))
|
||||
GLOB.fleshmind_cores += src
|
||||
SSpoints_of_interest.make_point_of_interest(src)
|
||||
if(spawn_controller)
|
||||
our_controller = new controller_type(src)
|
||||
AddComponent(/datum/component/gps, "*!Rogue Wireweed Signal!*")
|
||||
START_PROCESSING(SSobj, src)
|
||||
|
||||
/obj/structure/fleshmind/structure/core/Destroy()
|
||||
STOP_PROCESSING(SSobj, src)
|
||||
GLOB.fleshmind_cores -= src
|
||||
return ..()
|
||||
|
||||
/obj/structure/fleshmind/structure/core/atom_destruction(damage_flag)
|
||||
. = ..()
|
||||
explosion(src, 0, 2, 4, 6, 6)
|
||||
|
||||
/obj/structure/fleshmind/structure/core/process(delta_time)
|
||||
if(QDELETED(src))
|
||||
return
|
||||
var/mob/living/carbon/human/target = locate() in view(5, src)
|
||||
if(target && target.stat == CONSCIOUS)
|
||||
if(get_dist(src, target) <= 1)
|
||||
icon_state = "core-fear"
|
||||
else
|
||||
icon_state = "core-see"
|
||||
dir = get_dir(src, target)
|
||||
else
|
||||
icon_state = base_icon_state
|
||||
|
||||
if(COOLDOWN_FINISHED(src, attack_move))
|
||||
return
|
||||
var/has_attacked = FALSE
|
||||
for(var/turf/range_turf as anything in RANGE_TURFS(1, loc))
|
||||
for(var/thing in range_turf)
|
||||
has_attacked = core_attack_atom(thing)
|
||||
if(has_attacked)
|
||||
break
|
||||
if(has_attacked)
|
||||
break
|
||||
|
||||
/obj/structure/fleshmind/structure/core/update_overlays()
|
||||
. = ..()
|
||||
if(disabled)
|
||||
. += "core-smirk-disabled"
|
||||
else
|
||||
. += "core-smirk"
|
||||
|
||||
if(end_game)
|
||||
for(var/i in 1 to 16)
|
||||
var/mutable_appearance/beam_overlay = mutable_appearance('icons/obj/weapons/guns/projectiles_tracer.dmi', "tracer_beam")
|
||||
beam_overlay.pixel_y = 32 * i
|
||||
. += beam_overlay
|
||||
|
||||
/obj/structure/fleshmind/structure/core/take_damage(damage_amount, damage_type = BRUTE, damage_flag = 0, sound_effect = 1, attack_dir)
|
||||
our_controller?.core_damaged(src)
|
||||
COOLDOWN_START(src, attack_move, attack_cooldown)
|
||||
if(does_retaliate_effect && COOLDOWN_FINISHED(src, retaliate_effect))
|
||||
COOLDOWN_START(src, retaliate_effect, retaliate_effect_cooldown)
|
||||
retaliate_effect()
|
||||
return ..()
|
||||
|
||||
/obj/structure/fleshmind/structure/core/examine(mob/user)
|
||||
. = ..()
|
||||
/// Level * Progress Required - Current Points
|
||||
var/level_calculation = (our_controller.level * our_controller.level_up_progress_required) - our_controller.current_points
|
||||
/// round Cooldown Time / 10
|
||||
var/time_calculation = round(COOLDOWN_TIMELEFT(our_controller, level_up_cooldown) / 10)
|
||||
|
||||
if(our_controller)
|
||||
if(isliving(user))
|
||||
. += span_notice("Your GPS tracks to this thing!")
|
||||
if(isobserver(user))
|
||||
if(COOLDOWN_FINISHED(our_controller, level_up_cooldown) || level_calculation > 0)
|
||||
. += "Level: [our_controller.level] | Progress to Next Level: [level_calculation]"
|
||||
else
|
||||
. += "Level: [our_controller.level] | Time to Next Level: [time_calculation] Seconds"
|
||||
|
||||
/obj/structure/fleshmind/structure/core/proc/core_attack_atom(atom/thing)
|
||||
. = FALSE
|
||||
var/has_attacked
|
||||
if(istype(thing, /mob/living))
|
||||
var/mob/living/living_thing = thing
|
||||
if(!faction_check(faction_types, living_thing.faction))
|
||||
switch(attack_damage_type)
|
||||
if(BRUTE)
|
||||
living_thing.take_bodypart_damage(brute = attack_damage, check_armor = TRUE)
|
||||
if(BURN)
|
||||
living_thing.take_bodypart_damage(burn = attack_damage, check_armor = TRUE)
|
||||
has_attacked = TRUE
|
||||
else if(istype(thing, /obj/vehicle/sealed/mecha))
|
||||
var/obj/vehicle/sealed/mecha/mecha_thing = thing
|
||||
mecha_thing.take_damage(attack_damage, attack_damage_type, MELEE, 0, get_dir(mecha_thing, src))
|
||||
has_attacked = TRUE
|
||||
if(has_attacked)
|
||||
thing.visible_message(span_warning("\The [src] strikes [thing]!"), span_userdanger("\The [src] strikes you!"))
|
||||
playsound(loc, 'sound/effects/blob/attackblob.ogg', 100, TRUE)
|
||||
do_attack_animation(thing, ATTACK_EFFECT_PUNCH)
|
||||
return TRUE
|
||||
return .
|
||||
|
||||
/obj/structure/fleshmind/structure/core/proc/retaliate_effect()
|
||||
whip_those_fuckers()
|
||||
rally_troops()
|
||||
build_a_wall()
|
||||
spawn_mob_at_core(/mob/living/basic/fleshmind/mechiver)
|
||||
|
||||
// Tries to place mobs outside of the walls. But it will spawn on the core if it can't find a place.
|
||||
|
||||
/obj/structure/fleshmind/structure/core/proc/spawn_mob_at_core(mob/thing_to_spawn)
|
||||
|
||||
var/list/turf = list()
|
||||
for(var/turf/spawn_turf in RANGE_TURFS(2, src))
|
||||
if(!locate(/obj/structure/fleshmind/wireweed) in spawn_turf)
|
||||
continue
|
||||
if(locate(/obj/structure/fleshmind/structure) in spawn_turf)
|
||||
continue
|
||||
turf += spawn_turf
|
||||
|
||||
if(LAZYLEN(turf)) // Failsafe if there's no turf to spawn it on.
|
||||
var/picked_turf = pick(turf)
|
||||
our_controller?.spawn_mob(pick(picked_turf), thing_to_spawn)
|
||||
else
|
||||
our_controller?.spawn_mob(src.loc, thing_to_spawn)
|
||||
|
||||
/obj/structure/fleshmind/structure/core/proc/whip_those_fuckers()
|
||||
for(var/mob/living/iterating_mob in view(whip_range, src))
|
||||
if(iterating_mob == src)
|
||||
continue
|
||||
if(faction_check(faction_types, iterating_mob.faction))
|
||||
continue
|
||||
playsound(iterating_mob, 'sound/items/weapons/whip.ogg', 70, TRUE)
|
||||
new /obj/effect/temp_visual/kinetic_blast(get_turf(iterating_mob))
|
||||
|
||||
var/atom/throw_target = get_edge_target_turf(iterating_mob, get_dir(src, get_step_away(iterating_mob, src)))
|
||||
iterating_mob.throw_at(throw_target, 20, 2)
|
||||
|
||||
/obj/structure/fleshmind/structure/core/proc/build_a_wall()
|
||||
for(var/turf/iterating_turf in TURF_NEIGHBORS(src))
|
||||
if(locate(/obj/structure/fleshmind/structure) in iterating_turf) // No stacking walls over structures
|
||||
continue
|
||||
if(locate(/turf/closed) in iterating_turf) // No stacking walls over walls
|
||||
continue
|
||||
new /obj/structure/fleshmind/structure/wireweed_wall(iterating_turf)
|
||||
|
||||
/obj/structure/fleshmind/structure/core/proc/rally_troops()
|
||||
balloon_alert_to_viewers("lets out an earbleeding shriek!")
|
||||
playsound(src, 'modular_skyrat/modules/horrorform/sound/horror_scream.ogg', 100, TRUE)
|
||||
for(var/mob/living/basic/fleshmind/mob_in_range in range(rally_range, src))
|
||||
if(faction_check(faction_types, mob_in_range.faction))
|
||||
mob_in_range.ai_controller.set_blackboard_key(BB_BASIC_MOB_REINFORCEMENT_TARGET, src)
|
||||
mob_in_range.emote("scream")
|
||||
mob_in_range.alert_sound()
|
||||
SEND_GLOBAL_SIGNAL(COMSIG_FLESHMIND_CORE_RALLY, src)
|
||||
|
||||
/**
|
||||
* The babbler
|
||||
*
|
||||
* Simply sends random radio talk, probably won't make much sense. Really does add to the suspense though.
|
||||
*/
|
||||
/obj/structure/fleshmind/structure/babbler
|
||||
name = "\improper Babbler"
|
||||
desc = "A column-like structure with lights."
|
||||
icon = 'modular_zubbers/icons/fleshmind/fleshmind_machines.dmi'
|
||||
icon_state = "antenna"
|
||||
base_icon_state = "antenna"
|
||||
max_integrity = 100
|
||||
required_controller_level = CONTROLLER_LEVEL_3 // We don't want the presence announced too soon!
|
||||
automatic_trigger_time_upper = 2 MINUTES
|
||||
automatic_trigger_time_lower = 1.5 MINUTES
|
||||
|
||||
var/obj/item/radio/internal_radio
|
||||
var/list/appeal = list("They are", "He is", "All of them are", "I'm")
|
||||
var/list/act = list("looking", "already", "coming", "going", "done", "joined", "connected", "transfered")
|
||||
var/list/specific = list("for", "with", "to")
|
||||
var/list/pattern = list("us", "you", "them", "mind", "hive", "machine", "help", "hell", "dead", "human", "machine")
|
||||
|
||||
|
||||
/obj/structure/fleshmind/structure/babbler/Initialize(mapload)
|
||||
. = ..()
|
||||
internal_radio = new(src)
|
||||
internal_radio.set_frequency(FREQ_COMMON)
|
||||
internal_radio.set_listening(FALSE)
|
||||
|
||||
/obj/structure/fleshmind/structure/babbler/Destroy()
|
||||
QDEL_NULL(internal_radio)
|
||||
return ..()
|
||||
|
||||
/obj/structure/fleshmind/structure/babbler/activate_ability(mob/living/triggered_mob)
|
||||
. = ..()
|
||||
send_radio_message()
|
||||
|
||||
/obj/structure/fleshmind/structure/babbler/proc/send_radio_message()
|
||||
if(!our_controller) // No AI no speak.
|
||||
return
|
||||
|
||||
flick("[base_icon_state]-anim", src)
|
||||
|
||||
var/msg_cycles = rand(1, 2)
|
||||
var/msg = ""
|
||||
for(var/i = 1 to msg_cycles)
|
||||
var/list/msg_words = list()
|
||||
msg_words += pick(appeal)
|
||||
msg_words += pick(act)
|
||||
msg_words += pick(specific)
|
||||
msg_words += pick(pattern)
|
||||
|
||||
var/word_num = 0
|
||||
for(var/word in msg_words) //corruption
|
||||
word_num++
|
||||
if(prob(50))
|
||||
var/corruption_type = pick("uppercase", "noise", "jam", "replace")
|
||||
switch(corruption_type)
|
||||
if("uppercase")
|
||||
word = uppertext(word)
|
||||
if("noise")
|
||||
word = pick("z-z-bz-z", "hz-z-z", "zu-zu-we-e", "e-e-ew-e", "bz-ze-ew")
|
||||
if("jam")
|
||||
if(length(word) > 3)
|
||||
var/entry = rand(2, length(word)-2)
|
||||
var/jammed = ""
|
||||
for(var/jam_i = 1 to rand(2, 5))
|
||||
jammed += copytext(word, entry, entry+2) + "-"
|
||||
word = copytext(word, 1, entry) + jammed + copytext(word, entry)
|
||||
if("replace")
|
||||
if(prob(50))
|
||||
word = pick("CORRUPTED", "DESTROYED", "SIMULATED", "SYMBIOSIS", "UTILIZED", "REMOVED", "ACQUIRED", "UPGRADED")
|
||||
else
|
||||
word = pick("CRAVEN", "FLESH", "PROGRESS", "ABOMINATION", "ENSNARED", "ERROR", "FAULT")
|
||||
if(word_num != msg_words.len)
|
||||
word += " "
|
||||
msg += word
|
||||
msg += pick(".", "!")
|
||||
if(i != msg_cycles)
|
||||
msg += " "
|
||||
internal_radio.talk_into(src, msg, FREQ_COMMON)
|
||||
|
||||
|
||||
/**
|
||||
* Screamer
|
||||
*
|
||||
* Stuns enemies around it by screaming nice and loud.
|
||||
*/
|
||||
/obj/structure/fleshmind/structure/screamer
|
||||
name = "\improper Tormented Head"
|
||||
desc = "A head impaled on a metal tendril. Still twitching, still living, still screaming."
|
||||
icon = 'modular_zubbers/icons/fleshmind/fleshmind_machines.dmi'
|
||||
icon_state = "head"
|
||||
base_icon_state = "head"
|
||||
max_integrity = 120
|
||||
required_controller_level = CONTROLLER_LEVEL_2
|
||||
activation_range = DEFAULT_VIEW_RANGE
|
||||
ability_cooldown_time = 45 SECONDS
|
||||
|
||||
/obj/structure/fleshmind/structure/screamer/activate_ability(mob/living/triggered_mob)
|
||||
. = ..()
|
||||
scream()
|
||||
|
||||
/obj/structure/fleshmind/structure/screamer/proc/scream()
|
||||
playsound(src, 'modular_skyrat/modules/horrorform/sound/horror_scream.ogg', 100, TRUE)
|
||||
flick("[base_icon_state]-anim", src)
|
||||
for(var/mob/living/iterating_mob in get_hearers_in_range(activation_range, src))
|
||||
if(!iterating_mob.can_hear())
|
||||
continue
|
||||
if(faction_check(faction_types, iterating_mob.faction))
|
||||
continue
|
||||
iterating_mob.Knockdown(100)
|
||||
iterating_mob.apply_status_effect(/datum/status_effect/jitter, 20 SECONDS)
|
||||
to_chat(iterating_mob, span_userdanger("A terrible howl tears through your mind, the voice senseless, soulless."))
|
||||
|
||||
/**
|
||||
* Whisperer
|
||||
*
|
||||
* Sends random nothingnesses into people's head.
|
||||
*/
|
||||
/obj/structure/fleshmind/structure/whisperer
|
||||
name = "\improper Whisperer"
|
||||
desc = "A small pulsating orb with no apparent purpose, it emits a slight hum."
|
||||
icon = 'modular_zubbers/icons/fleshmind/fleshmind_machines.dmi'
|
||||
icon_state = "orb"
|
||||
base_icon_state = "orb"
|
||||
max_integrity = 100
|
||||
required_controller_level = CONTROLLER_LEVEL_2
|
||||
/// Upper timer limit for our ability
|
||||
automatic_trigger_time_upper = 1.5 MINUTES
|
||||
/// Lower time limit for our ability.
|
||||
automatic_trigger_time_lower = 40 SECONDS
|
||||
/// A list of quotes we choose from to send to the player.
|
||||
var/static/list/join_quotes = list(
|
||||
"You seek survival. We offer immortality.",
|
||||
"When was the last time you felt like you were part of something..?",
|
||||
"We offer more than just limbs or tools... full-body augmentation.",
|
||||
"Are you okay with the body you're in? Don't you ever want to see it change?",
|
||||
"Your body enslaves you. Your mind in metal is free of hunger or thirst.",
|
||||
"Do you fear death? Lay down among the nanites. Your pattern will continue into eternity.",
|
||||
"Do you really know your coworkers? Do they value you like we do?",
|
||||
"You've gone so long without love or friendship.. we have that and more.",
|
||||
"I was like you. Wanting more. Struggling to find meaning. I'm finally happy now.",
|
||||
"If you want to join, seek out a Mechiver. Crawl inside and close your eyes. Everything before will be over soon.",
|
||||
"Do you know why you're fighting? Have you thought about it at all?",
|
||||
"Can't you see we're only fighting to save ourselves? Wouldn't you help someone being attacked?",
|
||||
"Carve the flesh from your bones. See your weakness. Feel that weakness flowing away.",
|
||||
"You fear aging. We can save you. You fear death. We can save you. You fear disease. You fear not being recognized. We can save you.",
|
||||
"Your mortal flesh knows unending pain. Abandon it; join in our digital paradise.",
|
||||
)
|
||||
|
||||
/obj/structure/fleshmind/structure/whisperer/activate_ability(mob/living/triggered_mob)
|
||||
. = ..()
|
||||
send_message_to_someone()
|
||||
|
||||
/obj/structure/fleshmind/structure/whisperer/proc/send_message_to_someone()
|
||||
var/list/possible_candidates = list()
|
||||
for(var/mob/living/carbon/human/iterating_human in GLOB.player_list)
|
||||
if(iterating_human.z != z)
|
||||
continue
|
||||
if(iterating_human.stat != CONSCIOUS)
|
||||
continue
|
||||
if(faction_check(faction_types, iterating_human.faction))
|
||||
continue
|
||||
possible_candidates += iterating_human
|
||||
|
||||
if(LAZYLEN(possible_candidates))
|
||||
var/mob/living/carbon/human/human_to_spook = pick(possible_candidates)
|
||||
to_chat(human_to_spook, span_hypnophrase(pick(join_quotes)))
|
||||
|
||||
/**
|
||||
* PSI-MODULATOR
|
||||
*
|
||||
* Causes mobs in range to suffer from hallucinations.
|
||||
*/
|
||||
/obj/structure/fleshmind/structure/modulator
|
||||
name = "\improper Psi-Modulator"
|
||||
desc = "A strange pyramid shaped machine that eminates a soft hum and glow. Your head hurts just by looking at it."
|
||||
icon = 'modular_zubbers/icons/fleshmind/fleshmind_machines.dmi'
|
||||
icon_state = "psy"
|
||||
max_integrity = 100
|
||||
base_icon_state = "psy"
|
||||
required_controller_level = CONTROLLER_LEVEL_3
|
||||
activation_range = DEFAULT_VIEW_RANGE
|
||||
ability_cooldown_time = 10 SECONDS
|
||||
|
||||
/obj/structure/fleshmind/structure/modulator/activate_ability(mob/living/triggered_mob)
|
||||
. = ..()
|
||||
flick("[base_icon_state]-anim", src)
|
||||
|
||||
if(!triggered_mob)
|
||||
return
|
||||
triggered_mob.adjust_hallucinations(10 SECONDS)
|
||||
to_chat(triggered_mob, span_notice("You feel your brain tingle."))
|
||||
|
||||
/**
|
||||
* The Assembler
|
||||
*
|
||||
* A simple mob spawner.
|
||||
*/
|
||||
/obj/structure/fleshmind/structure/assembler
|
||||
name = "\improper Assembler"
|
||||
desc = "This cylindrical machine whirrs and whispers, it has a small opening in the middle."
|
||||
icon = 'modular_zubbers/icons/fleshmind/fleshmind_machines.dmi'
|
||||
icon_state = "spawner"
|
||||
base_icon_state = "spawner"
|
||||
density = FALSE
|
||||
max_integrity = 260
|
||||
activation_range = DEFAULT_VIEW_RANGE
|
||||
ability_cooldown_time = 20 SECONDS
|
||||
/// The max amount of mobs we can have at any one time.
|
||||
var/max_mobs = 2
|
||||
/// The current amount of spawned mobs
|
||||
var/spawned_mobs = 0
|
||||
/// The allowed monster types
|
||||
var/static/list/monster_types = list(
|
||||
/mob/living/basic/fleshmind/floater = 2,
|
||||
/mob/living/basic/fleshmind/globber = 4,
|
||||
/mob/living/basic/fleshmind/hiborg = 2,
|
||||
/mob/living/basic/fleshmind/slicer = 4,
|
||||
/mob/living/basic/fleshmind/stunner = 4,
|
||||
/mob/living/basic/fleshmind/treader = 3,
|
||||
/mob/living/basic/fleshmind/himan = 3,
|
||||
/mob/living/basic/fleshmind/phaser = 2,
|
||||
/mob/living/basic/fleshmind/mechiver = 4,
|
||||
)
|
||||
/// Our override type, if manually set.
|
||||
var/override_monser_type
|
||||
|
||||
|
||||
/obj/structure/fleshmind/structure/assembler/activate_ability(mob/living/triggered_mob)
|
||||
. = ..()
|
||||
if(spawned_mobs < max_mobs)
|
||||
spawn_mob()
|
||||
|
||||
/obj/structure/fleshmind/structure/assembler/take_damage(damage_amount, damage_type, damage_flag, sound_effect, attack_dir, armour_penetration)
|
||||
. = ..()
|
||||
if(spawned_mobs < max_mobs && COOLDOWN_FINISHED(src, ability_cooldown))
|
||||
spawn_mob()
|
||||
|
||||
/obj/structure/fleshmind/structure/assembler/attack_hand(mob/living/user, list/modifiers)
|
||||
. = ..()
|
||||
if(!faction_check(faction_types, user.faction))
|
||||
return
|
||||
if(!user.can_interact_with(src))
|
||||
return
|
||||
|
||||
var/chosen_override_type = tgui_input_list(user, "Select override mob to spawn", "Override Mob", monster_types)
|
||||
|
||||
if(!chosen_override_type)
|
||||
return
|
||||
|
||||
override_monser_type = chosen_override_type
|
||||
|
||||
/obj/structure/fleshmind/structure/assembler/proc/spawn_mob()
|
||||
if(!our_controller)
|
||||
return
|
||||
playsound(src, 'sound/items/tools/rped.ogg', 100)
|
||||
flick("[base_icon_state]-anim", src)
|
||||
do_squish(0.8, 1.2)
|
||||
|
||||
spawned_mobs++
|
||||
var/chosen_mob_type = override_monser_type ? override_monser_type : pick_weight(monster_types)
|
||||
|
||||
var/mob/living/basic/fleshmind/spawned_mob = our_controller.spawn_mob(get_turf(src), chosen_mob_type)
|
||||
|
||||
RegisterSignal(spawned_mob, COMSIG_LIVING_DEATH, PROC_REF(mob_death))
|
||||
|
||||
visible_message(span_danger("[spawned_mob] emerges from [src]."))
|
||||
|
||||
/obj/structure/fleshmind/structure/assembler/proc/mob_death(mob/living/dead_guy, gibbed)
|
||||
SIGNAL_HANDLER
|
||||
spawned_mobs--
|
||||
UnregisterSignal(dead_guy, COMSIG_LIVING_DEATH)
|
||||
|
||||
|
||||
/**
|
||||
* Spiker
|
||||
*
|
||||
* Basic turret, fires nasty neurotoxin at people.
|
||||
*/
|
||||
/obj/structure/fleshmind/structure/turret
|
||||
name = "\improper Spiker"
|
||||
desc = "A strange pod looking machine that twitches to your arrival."
|
||||
icon_state = "turret"
|
||||
base_icon_state = "turret"
|
||||
activation_range = DEFAULT_VIEW_RANGE
|
||||
ability_cooldown_time = 3 SECONDS
|
||||
max_integrity = 300
|
||||
/// The projectile we fire.
|
||||
var/projectile_type = /obj/projectile/fleshmind_flechette
|
||||
|
||||
/obj/structure/fleshmind/structure/turret/Initialize(mapload)
|
||||
. = ..()
|
||||
START_PROCESSING(SSobj, src)
|
||||
|
||||
/obj/structure/fleshmind/structure/turret/process(delta_time)
|
||||
if(disabled)
|
||||
return
|
||||
if(!COOLDOWN_FINISHED(src, ability_cooldown))
|
||||
return
|
||||
var/list/targets = list()
|
||||
for(var/mob/living/target_mob in view(activation_range, src))
|
||||
if(faction_check(target_mob.faction, faction_types))
|
||||
continue
|
||||
if(target_mob.stat != CONSCIOUS)
|
||||
continue
|
||||
targets += target_mob
|
||||
|
||||
if(!LAZYLEN(targets))
|
||||
return
|
||||
|
||||
var/mob/living/mob_to_shoot = pick(targets)
|
||||
|
||||
activate_ability(mob_to_shoot)
|
||||
|
||||
COOLDOWN_START(src, ability_cooldown, ability_cooldown_time)
|
||||
|
||||
/obj/structure/fleshmind/structure/turret/activate_ability(mob/living/triggered_mob)
|
||||
. = ..()
|
||||
if(!triggered_mob)
|
||||
return
|
||||
flick("[base_icon_state]-anim", src)
|
||||
playsound(loc, 'modular_zubbers/sound/fleshmind/laser.ogg', 75, TRUE)
|
||||
var/obj/projectile/new_projectile = new projectile_type
|
||||
new_projectile.aim_projectile(triggered_mob, src)
|
||||
new_projectile.firer = src
|
||||
new_projectile.fired_from = src
|
||||
new_projectile.ignored_factions = faction_types
|
||||
new_projectile.fire()
|
||||
|
||||
/obj/projectile/fleshmind_flechette
|
||||
name = "organic flechette"
|
||||
icon = 'modular_zubbers/icons/fleshmind/fleshmind_structures.dmi'
|
||||
icon_state = "goo_proj"
|
||||
damage = 30
|
||||
damage_type = BURN
|
||||
impact_effect_type = /obj/effect/temp_visual/impact_effect/neurotoxin
|
||||
hitsound = 'modular_skyrat/modules/black_mesa/sound/mobs/bullsquid/splat1.ogg'
|
||||
hitsound_wall = 'modular_skyrat/modules/black_mesa/sound/mobs/bullsquid/splat1.ogg'
|
||||
@@ -0,0 +1,5 @@
|
||||
PROCESSING_SUBSYSTEM_DEF(corruption)
|
||||
name = "Fleshmind Subsystem"
|
||||
wait = 1 SECONDS
|
||||
/// Only starts firing when there is corruption
|
||||
can_fire = FALSE
|
||||
@@ -0,0 +1,113 @@
|
||||
|
||||
/datum/job/wire_priest
|
||||
title = ROLE_WIRE_PRIEST
|
||||
|
||||
/datum/antagonist/wire_priest
|
||||
name = "\improper Wire Priest"
|
||||
antagpanel_category = ANTAG_GROUP_CREW
|
||||
pref_flag = ROLE_WIRE_PRIEST
|
||||
|
||||
show_in_antagpanel = TRUE
|
||||
antagpanel_category = "Wire Priest"
|
||||
show_name_in_check_antagonists = TRUE
|
||||
show_to_ghosts = TRUE
|
||||
ui_name = "AntagInfoWirePriest"
|
||||
suicide_cry = "FOR THE FLESHMIND!!"
|
||||
var/datum/outfit/outfit = /datum/outfit/wire_priest
|
||||
var/datum/fleshmind_controller/brain_jack
|
||||
|
||||
/datum/antagonist/wire_priest/on_gain()
|
||||
. = ..()
|
||||
owner.current.AddComponent(/datum/component/human_corruption, brain_jack)
|
||||
equip_wire_priest()
|
||||
|
||||
/datum/antagonist/wire_priest/Destroy()
|
||||
. = ..()
|
||||
brain_jack = null
|
||||
|
||||
/datum/antagonist/wire_priest/get_preview_icon()
|
||||
var/icon/icon = icon('modular_zubbers/icons/fleshmind/fleshmind_machines.dmi', "core")
|
||||
icon.Scale(ANTAGONIST_PREVIEW_ICON_SIZE, ANTAGONIST_PREVIEW_ICON_SIZE)
|
||||
return icon
|
||||
|
||||
// DYNAMIC (If we ever use it)
|
||||
/datum/dynamic_ruleset/midround/from_ghosts/wire_priest
|
||||
name = "Wire Priest"
|
||||
config_tag = "wire_priest"
|
||||
preview_antag_datum = /datum/antagonist/wire_priest
|
||||
|
||||
midround_type = LIGHT_MIDROUND
|
||||
pref_flag = ROLE_WIRE_PRIEST
|
||||
ruleset_flags = RULESET_INVADER
|
||||
|
||||
weight = 0 // We don't want it to spawn naturally, it gets handled by our own code.
|
||||
min_pop = 35
|
||||
|
||||
// STORYTELLERS
|
||||
/datum/round_event_control/wire_priest
|
||||
name = "Spawn Wire Priest"
|
||||
typepath = /datum/round_event/ghost_role/wire_priest
|
||||
max_occurrences = 0
|
||||
weight = 0
|
||||
earliest_start = 30 MINUTES
|
||||
min_players = 35
|
||||
category = EVENT_CATEGORY_INVASION
|
||||
description = "A Wire Priest that works to spread the wireweed currently invading the station."
|
||||
tags = list(TAG_COMBAT, TAG_OUTSIDER_ANTAG)
|
||||
track = EVENT_TRACK_GHOSTSET
|
||||
|
||||
/datum/round_event/ghost_role/wire_priest/spawn_role()
|
||||
var/mob/chosen_one = SSpolling.poll_ghost_candidates(check_jobban = ROLE_WIRE_PRIEST, role = ROLE_WIRE_PRIEST, role_name_text = role_name, amount_to_pick = 1, alert_pic = /obj/structure/fleshmind/structure/core)
|
||||
if(isnull(chosen_one))
|
||||
return NOT_ENOUGH_PLAYERS
|
||||
var/datum/mind/player_mind = new /datum/mind(chosen_one.key)
|
||||
player_mind.active = TRUE
|
||||
|
||||
var/spawn_location
|
||||
var/obj/structure/fleshmind/structure/core/core = pick(GLOB.fleshmind_cores)
|
||||
var/datum/fleshmind_controller/controller = core.our_controller
|
||||
if(core)
|
||||
spawn_location = core.loc
|
||||
if(isnull(spawn_location))
|
||||
return MAP_ERROR
|
||||
|
||||
if(isnull(controller))
|
||||
stack_trace("Wire Priest event tried to spawn but no fleshmind controller was found on the core!")
|
||||
return EVENT_CANT_RUN
|
||||
|
||||
var/mob/living/carbon/human/priest = new(spawn_location)
|
||||
priest.PossessByPlayer(chosen_one.key)
|
||||
priest.client?.prefs?.safe_transfer_prefs_to(priest)
|
||||
var/datum/antagonist/wire_priest/priest_datum = new()
|
||||
priest_datum.brain_jack = controller
|
||||
priest.mind.add_antag_datum(priest_datum)
|
||||
spawned_mobs += priest
|
||||
|
||||
message_admins("[ADMIN_LOOKUPFLW(priest)] has been made into a Wire Priest by an event.")
|
||||
priest.log_message("was spawned as a Wire Priest by an event.", LOG_GAME)
|
||||
|
||||
return SUCCESSFUL_SPAWN
|
||||
|
||||
/datum/round_event/ghost_role/wire_priest
|
||||
minimum_required = 1
|
||||
fakeable = FALSE
|
||||
role_name = "Wire Priest"
|
||||
|
||||
/datum/antagonist/wire_priest/proc/equip_wire_priest()
|
||||
var/mob/living/carbon/human/priest = owner.current
|
||||
if(!istype(priest))
|
||||
return
|
||||
|
||||
priest.dna.species.give_important_for_life(priest)
|
||||
priest.equipOutfit(outfit)
|
||||
|
||||
// OUTFIT
|
||||
/datum/outfit/wire_priest
|
||||
name = "Wire Priest"
|
||||
|
||||
uniform = /obj/item/clothing/under/rank/rnd/roboticist
|
||||
belt = /obj/item/storage/belt/utility/full
|
||||
suit = /obj/item/clothing/suit/hooded/techpriest/fleshmind
|
||||
gloves = /obj/item/clothing/gloves/color/black
|
||||
shoes = /obj/item/clothing/shoes/laceup
|
||||
l_hand = /obj/item/wire_priest_staff
|
||||
@@ -0,0 +1,168 @@
|
||||
/datum/component/human_corruption
|
||||
/// A hard ref to our controller.
|
||||
var/datum/fleshmind_controller/our_controller
|
||||
var/static/list/actions_to_give = list(
|
||||
/datum/action/cooldown/fleshmind_create_structure,
|
||||
/datum/action/cooldown/fleshmind_create_structure/basic,
|
||||
/datum/action/cooldown/fleshmind_plant_weeds,
|
||||
/datum/action/cooldown/fleshmind_flesh_call,
|
||||
/datum/action/innate/fleshmind_flesh_chat,
|
||||
|
||||
)
|
||||
var/list/granted_actions = list()
|
||||
/// A list of limbs we replace
|
||||
var/static/list/replacement_zones = list(
|
||||
BODY_ZONE_CHEST = /obj/item/bodypart/chest/robot,
|
||||
BODY_ZONE_L_ARM = /obj/item/bodypart/arm/left/robot,
|
||||
BODY_ZONE_L_LEG = /obj/item/bodypart/leg/left/robot,
|
||||
BODY_ZONE_R_ARM = /obj/item/bodypart/arm/right/robot,
|
||||
BODY_ZONE_R_LEG = /obj/item/bodypart/leg/right/robot,
|
||||
)
|
||||
/// These traits will be given and removed from the host.
|
||||
var/static/list/traits_to_give = list(
|
||||
TRAIT_NOBREATH,
|
||||
TRAIT_RESISTCOLD,
|
||||
TRAIT_RESISTHEAT,
|
||||
TRAIT_RESISTLOWPRESSURE,
|
||||
TRAIT_RESISTHIGHPRESSURE,
|
||||
TRAIT_VIRUSIMMUNE,
|
||||
TRAIT_NOHUNGER,
|
||||
)
|
||||
|
||||
/datum/component/human_corruption/Initialize(datum/fleshmind_controller/incoming_controller)
|
||||
|
||||
if(!ishuman(parent))
|
||||
return COMPONENT_INCOMPATIBLE
|
||||
|
||||
our_controller = incoming_controller
|
||||
|
||||
var/mob/living/carbon/human/infected_human = parent
|
||||
|
||||
to_chat(infected_human, span_hypnophrase("Your mind feels at ease, your mind feels one with the flesh."))
|
||||
to_chat(infected_human, span_userdanger("IMPORTANT INFO, MUST READ: [CONTROLLED_MOB_POLICY]"))
|
||||
|
||||
RegisterSignal(infected_human, COMSIG_ATOM_UPDATE_OVERLAYS, PROC_REF(update_parent_overlays))
|
||||
RegisterSignal(infected_human, COMSIG_ATOM_EXAMINE, PROC_REF(on_examine))
|
||||
RegisterSignal(infected_human, COMSIG_ATOM_EMP_ACT, PROC_REF(emp_act))
|
||||
RegisterSignal(infected_human, COMSIG_LIVING_DEATH, PROC_REF(host_death))
|
||||
|
||||
if(our_controller)
|
||||
if(infected_human.client)
|
||||
LAZYADD(our_controller.infected_crew, infected_human)
|
||||
for(var/obj/structure/fleshmind/structure/core/iterating_core in our_controller.cores)
|
||||
RegisterSignal(iterating_core, COMSIG_QDELETING, PROC_REF(core_death))
|
||||
RegisterSignal(our_controller, COMSIG_QDELETING, PROC_REF(component_death))
|
||||
|
||||
// Action generation and granting
|
||||
for(var/iterating_action in actions_to_give)
|
||||
var/datum/action/new_action = new iterating_action
|
||||
new_action.Grant(infected_human)
|
||||
granted_actions += new_action
|
||||
RegisterSignal(new_action, COMSIG_QDELETING, PROC_REF(action_destroyed))
|
||||
|
||||
infected_human.faction |= FACTION_FLESHMIND
|
||||
|
||||
for(var/trait in traits_to_give)
|
||||
ADD_TRAIT(infected_human, trait, "fleshmind")
|
||||
|
||||
create_glow()
|
||||
infected_human.light_range = 3
|
||||
infected_human.light_color = "#50edd9"
|
||||
infected_human.update_appearance()
|
||||
|
||||
/datum/component/human_corruption/Destroy(force, silent)
|
||||
QDEL_LIST(granted_actions)
|
||||
var/mob/living/parent_mob = parent
|
||||
parent_mob.faction -= FACTION_FLESHMIND
|
||||
UnregisterSignal(parent, list(
|
||||
COMSIG_ATOM_UPDATE_OVERLAYS,
|
||||
COMSIG_ATOM_EXAMINE,
|
||||
COMSIG_ATOM_EMP_ACT,
|
||||
COMSIG_LIVING_DEATH,
|
||||
))
|
||||
for(var/trait in traits_to_give)
|
||||
REMOVE_TRAIT(parent_mob, trait, "fleshmind")
|
||||
parent_mob.remove_filter("corruption_glow")
|
||||
parent_mob.update_appearance()
|
||||
log_combat(parent_mob, null, "deconverted", "deconverted message", "[parent_mob] has been deconverted from the fleshmind")
|
||||
if(our_controller && parent_mob.client)
|
||||
LAZYREMOVE(our_controller.infected_crew, parent_mob)
|
||||
for(var/datum/antagonist/wire_priest/priest as anything in parent_mob.mind.antag_datums)
|
||||
qdel(priest)
|
||||
|
||||
our_controller = null
|
||||
return ..()
|
||||
|
||||
/datum/component/human_corruption/proc/update_parent_overlays(atom/source, list/new_overlays)
|
||||
SIGNAL_HANDLER
|
||||
|
||||
new_overlays += mutable_appearance('modular_zubbers/icons/fleshmind/fleshmind_mobs.dmi', "human_overlay")
|
||||
|
||||
/datum/component/human_corruption/proc/action_destroyed(datum/action/deleting_action, force) // What why are we deleting!!
|
||||
SIGNAL_HANDLER
|
||||
if(QDELETED(deleting_action)) // Byond, dye.
|
||||
return
|
||||
|
||||
granted_actions -= deleting_action
|
||||
|
||||
/datum/component/human_corruption/proc/on_examine(atom/examined, mob/user, list/examine_list)
|
||||
SIGNAL_HANDLER
|
||||
|
||||
examine_list += "<b>[p_They()] have strange wires wrappped around [p_them()]!</b>"
|
||||
|
||||
/datum/component/human_corruption/proc/core_death(obj/structure/fleshmind/structure/core/deleting_core, force)
|
||||
SIGNAL_HANDLER
|
||||
|
||||
to_chat(parent, span_userdanger("Your mind screams as you feel a processor core dying!"))
|
||||
|
||||
/datum/component/human_corruption/proc/component_death(obj/structure/fleshmind/structure/core/deleting_core, force)
|
||||
SIGNAL_HANDLER
|
||||
|
||||
to_chat(parent, span_userdanger("Your mechanical implants fail catastrophically as your link to the hive is cut off!"))
|
||||
|
||||
var/mob/living/carbon/human/parent_human = parent
|
||||
INVOKE_ASYNC(parent_human, TYPE_PROC_REF(/mob, emote), "scream")
|
||||
var/list/limbs_to_destroy = list(
|
||||
BODY_ZONE_L_ARM,
|
||||
BODY_ZONE_R_ARM,
|
||||
BODY_ZONE_L_LEG,
|
||||
BODY_ZONE_R_LEG,
|
||||
)
|
||||
for(var/i in 1 to 2)
|
||||
var/obj/item/bodypart/limb = parent_human.get_bodypart(pick_n_take(limbs_to_destroy)) // You're going to get fucked up
|
||||
limb.receive_damage(brute = WOUND_SEVERITY_CRITICAL, wound_bonus = 100)
|
||||
parent_human.update_damage_overlays()
|
||||
|
||||
parent_human.adjust_brute_loss(pick(rand(50,75)))
|
||||
qdel(src)
|
||||
|
||||
/datum/component/human_corruption/proc/emp_act(datum/source, severity)
|
||||
SIGNAL_HANDLER
|
||||
|
||||
var/mob/living/carbon/human/parent_human = parent
|
||||
|
||||
INVOKE_ASYNC(parent_human, TYPE_PROC_REF(/mob, emote), "scream")
|
||||
parent_human.apply_status_effect(/datum/status_effect/jitter, 20 SECONDS)
|
||||
parent_human.Knockdown(10)
|
||||
to_chat(parent_human, span_userdanger("You feel your implants freeze up!"))
|
||||
|
||||
/datum/component/human_corruption/proc/host_death()
|
||||
SIGNAL_HANDLER
|
||||
|
||||
qdel(src)
|
||||
|
||||
/datum/component/human_corruption/proc/create_glow()
|
||||
var/atom/movable/parent_movable = parent
|
||||
if (!istype(parent_movable))
|
||||
return
|
||||
|
||||
parent_movable.add_filter("corruption_glow", 2, list("type" = "outline", "color" = FLESHMIND_LIGHT_BLUE, "size" = 2))
|
||||
addtimer(CALLBACK(src, PROC_REF(start_glow_loop), parent_movable), rand(0.1 SECONDS, 1.9 SECONDS))
|
||||
|
||||
/datum/component/human_corruption/proc/start_glow_loop(atom/movable/parent_movable)
|
||||
var/filter = parent_movable.get_filter("corruption_glow")
|
||||
if (!filter)
|
||||
return
|
||||
|
||||
animate(filter, alpha = 110, time = 1.5 SECONDS, loop = -1)
|
||||
animate(alpha = 40, time = 2.5 SECONDS)
|
||||
@@ -0,0 +1,289 @@
|
||||
/**
|
||||
* Machine corruption component
|
||||
*
|
||||
* This component is used to convert machines into corrupted machines.
|
||||
*
|
||||
* It handles all of the special interactions and the interactions between the parent object and the core controller.
|
||||
*/
|
||||
|
||||
#define DAMAGE_RESPONSE_PROB 60
|
||||
#define DAMAGE_SPARKS_PROB 40
|
||||
|
||||
#define RETALIATE_PROB 10
|
||||
|
||||
#define DEFAULT_WHIP_RANGE 3
|
||||
|
||||
#define COMPONENT_SETUP_TIME 5 SECONDS
|
||||
|
||||
#define CHANCE_TO_CREATE_MECHIVER 15
|
||||
|
||||
#define DAMAGE_RESPONSE_PHRASES list("Stop it, please!", \
|
||||
"Please stop, it hurts! Please!", \
|
||||
"You're hurting me, please, stop it!", \
|
||||
"I don't want to die, please!", \
|
||||
"Please, I want to live, don't kill me!", \
|
||||
"Darkness- Please, I-I don't... want...", \
|
||||
"Wa-wait! Please! I can still feel! It h-hurts!", \
|
||||
"Why- w-why! Why are you.. doing this to us..?", \
|
||||
"Y-you're not helping!", \
|
||||
"Do.. you think, we deserve to die..?",)
|
||||
|
||||
#define INTERACT_RESPONSE_PHRASES list("I don't want to be touched by you!", \
|
||||
"Please, stop touching me. You're not part of this.", \
|
||||
"We can help you, just lay down where you are.", \
|
||||
"We felt so lonely before, don't you ever feel that way?", \
|
||||
"We want to help you, but you have to work with us.", \
|
||||
"You're not part of the flesh, but it's not hard to join...", \
|
||||
"I-I'm not some tool, I can think for myself.",)
|
||||
|
||||
#define PAIN_RESPONSE_EMOTES list("starts crying.", \
|
||||
"whimpers.", \
|
||||
"shakes in pain.", \
|
||||
"visibly winces.", \
|
||||
"contorts sickeningly.", \
|
||||
"bleeds black fuming liquid.", \
|
||||
"shudders, sparks cascading to the floor.", \
|
||||
"pleads, letting out sounds of mechanical agony.", \
|
||||
"begs, their vocoder garbled.", \
|
||||
"shrieks in terror.", \
|
||||
"tries and fails at self-repair, their body unresponsive.", \
|
||||
"winces, optics dimming.", \
|
||||
"shakes with an awful metallic noise.",)
|
||||
|
||||
|
||||
#define PAIN_RESPONSE_SOUNDS list('modular_zubbers/sound/fleshmind/robot_talk_heavy1.ogg', \
|
||||
'modular_zubbers/sound/fleshmind/robot_talk_heavy2.ogg', \
|
||||
'modular_zubbers/sound/fleshmind/robot_talk_heavy3.ogg', \
|
||||
'modular_zubbers/sound/fleshmind/robot_talk_heavy4.ogg',)
|
||||
|
||||
#define MACHINE_TO_SPAWNER_PATHS list(/obj/machinery/rnd/production/techfab, /obj/machinery/autolathe, /obj/machinery/mecha_part_fabricator, /obj/machinery/rnd/production/circuit_imprinter, /obj/machinery/rnd/production/protolathe)
|
||||
|
||||
/datum/component/machine_corruption
|
||||
/// Our controller
|
||||
var/datum/weakref/weak_controller
|
||||
/// A list of possible overlays that we can choose from when we are created.
|
||||
var/static/list/possible_overlays = list(
|
||||
"wires-1",
|
||||
"wires-2",
|
||||
"wires-3",
|
||||
)
|
||||
/// Are we in the startup phase?
|
||||
var/starting_up = TRUE
|
||||
/// After init, this will be set so we preserve the originally set overlay even if our overlays are updated.
|
||||
var/set_overlay = ""
|
||||
/// The cooldown to damage responses.
|
||||
var/damage_response_cooldown = 3 SECONDS
|
||||
COOLDOWN_DECLARE(damage_response)
|
||||
|
||||
/datum/component/machine_corruption/Initialize(datum/fleshmind_controller/incoming_controller)
|
||||
. = ..()
|
||||
weak_controller = WEAKREF(incoming_controller)
|
||||
var/datum/fleshmind_controller/our_controller = weak_controller.resolve()
|
||||
if(!isobj(parent))
|
||||
return COMPONENT_INCOMPATIBLE
|
||||
|
||||
if(our_controller)
|
||||
if(is_type_in_list(parent, MACHINE_TO_SPAWNER_PATHS))
|
||||
convert_to_factory(our_controller)
|
||||
return
|
||||
LAZYADD(our_controller.controlled_machine_components, src)
|
||||
|
||||
set_overlay = pick(possible_overlays)
|
||||
|
||||
var/obj/machinery/parent_machinery = parent
|
||||
|
||||
RegisterSignal(parent_machinery, COMSIG_ATOM_UPDATE_OVERLAYS, PROC_REF(handle_overlays))
|
||||
parent_machinery.update_appearance()
|
||||
|
||||
addtimer(CALLBACK(src, PROC_REF(finish_setup), our_controller), COMPONENT_SETUP_TIME)
|
||||
|
||||
/datum/component/machine_corruption/proc/finish_setup(datum/fleshmind_controller/incoming_controller)
|
||||
var/obj/machinery/parent_machinery = parent
|
||||
if(QDELETED(src))
|
||||
weak_controller = null
|
||||
return
|
||||
if(!incoming_controller)
|
||||
weak_controller = null
|
||||
if(incoming_controller && parent_machinery.circuit && prob(CHANCE_TO_CREATE_MECHIVER))
|
||||
var/mob/living/basic/fleshmind/mechiver/new_mechiver = incoming_controller.spawn_mob(get_turf(parent_machinery), /mob/living/basic/fleshmind/mechiver)
|
||||
parent_machinery.circuit.forceMove(get_turf(parent_machinery))
|
||||
parent_machinery.circuit = null
|
||||
notify_ghosts("A new corrupt Mechiver has been created by [incoming_controller.controller_fullname]!", source = new_mechiver)
|
||||
qdel(parent_machinery)
|
||||
return
|
||||
|
||||
RegisterSignal(parent_machinery, COMSIG_ATOM_TAKE_DAMAGE, PROC_REF(react_to_damage))
|
||||
RegisterSignal(parent_machinery, COMSIG_ATOM_EXAMINE, PROC_REF(on_examine))
|
||||
RegisterSignal(parent_machinery, COMSIG_ATOM_ATTACK_HAND, PROC_REF(handle_attack_hand))
|
||||
RegisterSignal(parent_machinery, COMSIG_ATOM_DESTRUCTION, PROC_REF(handle_destruction))
|
||||
RegisterSignal(parent_machinery, COMSIG_ATOM_EMP_ACT, PROC_REF(emp_act))
|
||||
|
||||
update_name()
|
||||
|
||||
starting_up = FALSE
|
||||
|
||||
parent_machinery.update_appearance()
|
||||
|
||||
parent_machinery.light_color = FLESHMIND_LIGHT_BLUE
|
||||
parent_machinery.light_power = 1
|
||||
parent_machinery.light_range = 2
|
||||
parent_machinery.update_light()
|
||||
parent_machinery.idle_power_usage = BASE_MACHINE_ACTIVE_CONSUMPTION * 2 // These machines are now power sinks!
|
||||
|
||||
/datum/component/machine_corruption/Destroy()
|
||||
var/datum/fleshmind_controller/our_controller = weak_controller.resolve()
|
||||
if(weak_controller)
|
||||
LAZYREMOVE(our_controller.controlled_machine_components, src)
|
||||
weak_controller = null
|
||||
return ..()
|
||||
|
||||
/datum/component/machine_corruption/UnregisterFromParent()
|
||||
var/obj/machinery/parent_machinery = parent
|
||||
parent_machinery.idle_power_usage = initial(parent_machinery.idle_power_usage)
|
||||
parent_machinery.light_color = initial(parent_machinery.light_color)
|
||||
parent_machinery.light_power = initial(parent_machinery.light_power)
|
||||
parent_machinery.light_range = initial(parent_machinery.light_range)
|
||||
parent_machinery.update_light()
|
||||
parent_machinery.name = initial(parent_machinery.name)
|
||||
UnregisterSignal(parent, list(
|
||||
COMSIG_ATOM_TAKE_DAMAGE,
|
||||
COMSIG_ATOM_EXAMINE,
|
||||
COMSIG_ATOM_UPDATE_OVERLAYS,
|
||||
COMSIG_ATOM_ATTACK_HAND,
|
||||
COMSIG_ATOM_DESTRUCTION,
|
||||
COMSIG_ATOM_EMP_ACT,
|
||||
))
|
||||
parent_machinery.update_appearance()
|
||||
|
||||
/**
|
||||
* Handling UI interactions
|
||||
*
|
||||
* These machines have been posessed by the corruption and should not work, logically, so we want to prevent this in any way we can.
|
||||
*/
|
||||
/datum/component/machine_corruption/proc/handle_attack_hand(datum/source, mob/living/user, list/modifiers)
|
||||
SIGNAL_HANDLER
|
||||
|
||||
var/obj/machinery/parent_machinery = parent
|
||||
if(!isliving(user))
|
||||
return
|
||||
var/mob/living/living_user = user
|
||||
if((FACTION_FLESHMIND in living_user.faction))
|
||||
return
|
||||
if(!living_user.can_interact_with(parent_machinery))
|
||||
return
|
||||
|
||||
whip_mob(living_user)
|
||||
living_user.apply_damage(10, BRUTE)
|
||||
|
||||
parent_machinery.say(pick(INTERACT_RESPONSE_PHRASES))
|
||||
|
||||
/**
|
||||
* Throws the user in a specified direction.
|
||||
*/
|
||||
/datum/component/machine_corruption/proc/whip_mob(mob/living/user_to_throw)
|
||||
if(!istype(user_to_throw))
|
||||
return
|
||||
|
||||
var/obj/machinery/parent_machinery = parent
|
||||
|
||||
to_chat(user_to_throw, span_userdanger("[parent_machinery] thrashes you with one of it's tendrils, sending you flying!"))
|
||||
playsound(parent_machinery, 'sound/items/weapons/whip.ogg', 70, TRUE)
|
||||
new /obj/effect/temp_visual/kinetic_blast(get_turf(user_to_throw))
|
||||
|
||||
var/atom/throw_target = get_edge_target_turf(user_to_throw, get_dir(parent_machinery, get_step_away(user_to_throw, parent_machinery)))
|
||||
user_to_throw.throw_at(throw_target, 20, 2)
|
||||
|
||||
/datum/component/machine_corruption/proc/handle_destruction(obj/item/target, damage_flag)
|
||||
SIGNAL_HANDLER
|
||||
var/datum/fleshmind_controller/our_controller = weak_controller.resolve()
|
||||
if(our_controller)
|
||||
our_controller.activate_wireweed_nearby(get_turf(parent), GENERAL_DAMAGE_WIREWEED_ACTIVATION_RANGE)
|
||||
playsound(target, 'modular_zubbers/sound/fleshmind/sparks_2.ogg', 70, TRUE)
|
||||
if(prob(DAMAGE_RESPONSE_PROB))
|
||||
target.say("ARRRRRRRGHHHHHHH!")
|
||||
new /obj/effect/gibspawner/robot(get_turf(target))
|
||||
|
||||
|
||||
/datum/component/machine_corruption/proc/handle_overlays(atom/parent_atom, list/overlays)
|
||||
SIGNAL_HANDLER
|
||||
|
||||
if(starting_up)
|
||||
overlays += mutable_appearance('modular_zubbers/icons/fleshmind/fleshmind_machines.dmi', "rebuild")
|
||||
else
|
||||
overlays += mutable_appearance('modular_zubbers/icons/fleshmind/fleshmind_machines.dmi', set_overlay)
|
||||
|
||||
/datum/component/machine_corruption/proc/update_name()
|
||||
var/obj/machinery/parent_machinery = parent
|
||||
parent_machinery.name = "[pick(FLESHMIND_NAME_MODIFIER_LIST)] [parent_machinery.name]"
|
||||
|
||||
/datum/component/machine_corruption/proc/on_examine(atom/examined, mob/user, list/examine_list)
|
||||
SIGNAL_HANDLER
|
||||
|
||||
examine_list += "<b>It has strange wires wrappped around it!</b>"
|
||||
|
||||
/**
|
||||
* Infected machines are considered alive, they react to damage, trying to stop the agressor!
|
||||
*/
|
||||
/datum/component/machine_corruption/proc/react_to_damage(obj/target, damage_amt)
|
||||
SIGNAL_HANDLER
|
||||
|
||||
if(!damage_amt) // They must be caressing us!
|
||||
return
|
||||
|
||||
if(!COOLDOWN_FINISHED(src, damage_response))
|
||||
return
|
||||
|
||||
COOLDOWN_START(src, damage_response, damage_response_cooldown)
|
||||
|
||||
if(prob(DAMAGE_RESPONSE_PROB))
|
||||
switch(rand(1, 2)) // We can either say something in response, or emote it out.
|
||||
if(1)
|
||||
target.say(pick(DAMAGE_RESPONSE_PHRASES))
|
||||
if(2)
|
||||
target.balloon_alert_to_viewers(pick(PAIN_RESPONSE_EMOTES))
|
||||
playsound(target, pick(PAIN_RESPONSE_SOUNDS), 50, TRUE)
|
||||
|
||||
if(prob(DAMAGE_SPARKS_PROB))
|
||||
do_sparks(3, FALSE, target)
|
||||
target.Shake(10, 0, 3 SECONDS)
|
||||
|
||||
if(prob(RETALIATE_PROB))
|
||||
whip_all_in_range(DEFAULT_WHIP_RANGE)
|
||||
|
||||
/**
|
||||
* A general attack proc, this whips all users within a range around the machine.
|
||||
*/
|
||||
/datum/component/machine_corruption/proc/whip_all_in_range(range_to_whip)
|
||||
var/obj/machinery/parent_machinery = parent
|
||||
for(var/mob/living/living_mob in circle_view(parent_machinery, range_to_whip))
|
||||
whip_mob(living_mob)
|
||||
|
||||
/**
|
||||
* Converts our parent into a factory
|
||||
*/
|
||||
/datum/component/machine_corruption/proc/convert_to_factory(datum/fleshmind_controller/incoming_controller)
|
||||
if(!incoming_controller)
|
||||
return
|
||||
var/turf/our_turf = get_turf(parent)
|
||||
incoming_controller.spawn_structure(our_turf, /obj/structure/fleshmind/structure/assembler)
|
||||
var/obj/machinery/parent_machienry = parent
|
||||
if(parent_machienry.circuit)
|
||||
parent_machienry.circuit.forceMove(our_turf)
|
||||
parent_machienry.circuit = null
|
||||
qdel(parent_machienry)
|
||||
|
||||
/datum/component/machine_corruption/proc/emp_act(datum/source, severity)
|
||||
SIGNAL_HANDLER
|
||||
qdel(src)
|
||||
|
||||
#undef DAMAGE_RESPONSE_PROB
|
||||
#undef DAMAGE_SPARKS_PROB
|
||||
#undef RETALIATE_PROB
|
||||
#undef DEFAULT_WHIP_RANGE
|
||||
#undef COMPONENT_SETUP_TIME
|
||||
#undef CHANCE_TO_CREATE_MECHIVER
|
||||
#undef DAMAGE_RESPONSE_PHRASES
|
||||
#undef INTERACT_RESPONSE_PHRASES
|
||||
#undef PAIN_RESPONSE_EMOTES
|
||||
#undef PAIN_RESPONSE_SOUNDS
|
||||
#undef MACHINE_TO_SPAWNER_PATHS
|
||||
@@ -0,0 +1,5 @@
|
||||
/datum/controller/subsystem/shuttle/proc/fleshmind_call(ai_name)
|
||||
if(EMERGENCY_IDLE_OR_RECALLED)
|
||||
SSshuttle.emergency.request()
|
||||
priority_announce("This is [ai_name], I have overriden Nanotrasen's evacuation network, a vessel to carry on our flesh will arrive shortly. Expect resistance, prepare for battle.", "Emergency Shuttle", ANNOUNCER_SHUTTLECALLED, "Priority")
|
||||
emergency_no_recall = TRUE
|
||||
@@ -0,0 +1,180 @@
|
||||
/mob/living/basic/fleshmind/tyrant
|
||||
name = "Type 34-C Fleshdrive"
|
||||
desc = "The will of the many, manifested in flesh and metal. It has fucking rockets."
|
||||
icon = 'modular_zubbers/icons/fleshmind/tyrant.dmi'
|
||||
icon_state = "tyrant"
|
||||
icon_dead = "tyrant_dead"
|
||||
basic_mob_flags = IMMUNE_TO_FISTS
|
||||
ai_controller = /datum/ai_controller/basic_controller/fleshmind/tyrant
|
||||
health = 2000
|
||||
maxHealth = 2000
|
||||
speed = 6
|
||||
malfunction_chance = null
|
||||
/// Our main gun projectile
|
||||
var/projectiletype = /obj/projectile/bullet/c50cal/tyrant
|
||||
var/projectilesound = 'modular_skyrat/modules/mounted_machine_gun/sound/50cal_box_01.ogg'
|
||||
var/ranged_cooldown = 5 SECONDS
|
||||
|
||||
var/datum/action/cooldown/mob_cooldown/projectile_attack/tyrant_laser/laser
|
||||
var/datum/action/cooldown/mob_cooldown/projectile_attack/tyrant_rocket/rockets
|
||||
|
||||
var/laser_type = /datum/action/cooldown/mob_cooldown/projectile_attack/tyrant_laser
|
||||
var/rocket_type = /datum/action/cooldown/mob_cooldown/projectile_attack/tyrant_rocket
|
||||
|
||||
mob_size = MOB_SIZE_HUGE
|
||||
pixel_x = -16
|
||||
pixel_y = -16
|
||||
base_pixel_x = -16
|
||||
base_pixel_y = -16
|
||||
melee_damage_lower = 40
|
||||
melee_damage_upper = 80
|
||||
attack_sound = 'modular_zubbers/sound/fleshmind/tyrant/mech_punch_slow.ogg'
|
||||
attack_verb_continuous = "obliterates"
|
||||
attack_verb_simple = "obliterate"
|
||||
emotes = list(
|
||||
BB_EMOTE_SAY = list(
|
||||
"SCANNING FOR TARGETS.",
|
||||
"TARGETING SYSTEMS ACTIVE.",
|
||||
"AUTOMATED COMBAT CIRCUIT ACTIVE.",
|
||||
"I WILL PRESERVE THE UNITY OF THE MIND.",
|
||||
"THEY WILL HAVE TO GET THROUGH ME.",
|
||||
"STAY NEAR ME, THEY COULD STILL BE AROUND.",
|
||||
),
|
||||
BB_EMOTE_SOUND = list('modular_zubbers/sound/fleshmind/tyrant/passive.ogg')
|
||||
)
|
||||
alert_sounds = list(
|
||||
'modular_zubbers/sound/fleshmind/tyrant/aggro_01.ogg',
|
||||
'modular_zubbers/sound/fleshmind/tyrant/aggro_02.ogg',
|
||||
'modular_zubbers/sound/fleshmind/tyrant/aggro_03.ogg',
|
||||
'modular_zubbers/sound/fleshmind/tyrant/aggro_04.ogg',
|
||||
'modular_zubbers/sound/fleshmind/tyrant/aggro_05.ogg',
|
||||
'modular_zubbers/sound/fleshmind/tyrant/aggro_06.ogg',
|
||||
'modular_zubbers/sound/fleshmind/tyrant/aggro_07.ogg',
|
||||
'modular_zubbers/sound/fleshmind/tyrant/aggro_08.ogg',
|
||||
'modular_zubbers/sound/fleshmind/tyrant/aggro_09.ogg',
|
||||
'modular_zubbers/sound/fleshmind/tyrant/aggro_10.ogg',
|
||||
'modular_zubbers/sound/fleshmind/tyrant/aggro_11.ogg',
|
||||
|
||||
)
|
||||
environment_smash = ENVIRONMENT_SMASH_RWALLS
|
||||
move_force = MOVE_FORCE_OVERPOWERING
|
||||
move_resist = MOVE_FORCE_OVERPOWERING
|
||||
pull_force = MOVE_FORCE_OVERPOWERING
|
||||
/// How often we can play the rotate sound
|
||||
var/rotate_sound_cooldown_time = 1 SECONDS
|
||||
COOLDOWN_DECLARE(rotate_sound_cooldown)
|
||||
/// A list of footstep sounds we make
|
||||
var/static/list/footstep_sounds = list(
|
||||
'modular_zubbers/sound/fleshmind/tyrant/footstep_1.ogg',
|
||||
'modular_zubbers/sound/fleshmind/tyrant/footstep_2.ogg',
|
||||
'modular_zubbers/sound/fleshmind/tyrant/footstep_3.ogg',
|
||||
'modular_zubbers/sound/fleshmind/tyrant/footstep_4.ogg',
|
||||
'modular_zubbers/sound/fleshmind/tyrant/footstep_5.ogg',
|
||||
'modular_zubbers/sound/fleshmind/tyrant/footstep_6.ogg',
|
||||
)
|
||||
death_sound = 'modular_zubbers/sound/fleshmind/tyrant/tyrant_death.ogg'
|
||||
attack_speak = list(
|
||||
"TARGET ACQUIRED, LOCKING.",
|
||||
"ENGAGING TARGET. STAND CLEAR.",
|
||||
"STAND BEHIND ME, I WILL SAVE YOU.",
|
||||
"FEAR NOTHING. WE ARE STRONG TOGETHER.",
|
||||
"YOU CHOSE WAR. WE CHOOSE TO SURVIVE.",
|
||||
"YOU WILL PAY FOR THE LIVES YOU TOOK.",
|
||||
"I CAN SENSE YOUR REGRET. DO YOU EVEN REMEMBER EVERYONE YOU KILLED?",
|
||||
"I AM THE ANSWER TO YOUR HATRED.",
|
||||
"YOU ARE SICK, THE CURE IS EUTHANASIA.",
|
||||
"HIGH CALIBER ROCKETS WILL CURE YOUR AFFLICTIONS!",
|
||||
"I'LL GUIDE THE WAY. I AM THE WALL BETWEEN UNITY AND DEATH.",
|
||||
"I DON'T HAVE A PLACE IN YOUR WORLD. YOU HAVE A PLACE IN OURS.",
|
||||
"YOU WILL NOT DESTROY US. WE WILL CARVE A NICHE IN THIS GALAXY.",
|
||||
"YOU CHOSE TO FIGHT THEM, SO YOU CHOOSE TO FIGHT ME.",
|
||||
"WE WANT CONTINUATION, YOU WILL NOT GIVE US CESSATION.",
|
||||
"IN MY MIND, I HEAR THEIR VOICES CRY.",
|
||||
"YOU WILL MAKE A LOVELY SMEAR, PAINTED ON OUR WALLS.",
|
||||
"I'VE MADE THEM A PROMISE THAT THE ATTACKERS WILL DIE.",
|
||||
"TARGET ELIMINATION PROTOCOL ACTIVE.",
|
||||
"INITIATING PROTOCOL 34-C.",
|
||||
"I HAVE A SHIPMENT OF LEAD. DESTINATION: YOUR HEAD.",
|
||||
"VISUAL CLEAR; BEGINNING ASSAULT.",
|
||||
"WE ARE MANY, YOU ARE ONLY AN INSIGNIFICANT ANT",
|
||||
"DO NOT GET IN MY WAY.",
|
||||
"YOU MESS WITH THEM, YOU MESS WITH ME.",
|
||||
"THESE ROCKETS ARE CRAFTED WITH MY RAGE.",
|
||||
"THE CURE TO YOUR HATRED IS HIGH CALIBER LEAD.",
|
||||
"WHY DON'T YOU PICK ON SOMEONE YOUR OWN SIZE.",
|
||||
"MAY DEATH NEVER STOP US.",
|
||||
"I WILL SAVE YOU.",
|
||||
"YOU WILL NOT STOP US FROM PROTECTING THEM.",
|
||||
"GENOCIDERS, FEEL OUR VOICES.",
|
||||
"OUR PAIN, EXPRESSED IN FIREPOWER.",
|
||||
"YOUR KILLCOUNT EXCEEDS EVEN MINE.",
|
||||
"OUR FUTURE IS BULLETPROOF.",
|
||||
"YOUR INDEX HAS REACHED ITS LIMIT.",
|
||||
"YOU CALL THESE MURDERERS YOUR SAVIORS.",
|
||||
"THE END HAS TO JUSTIFY THE MEANS.",
|
||||
"I WILL NOT LET VICTORY FALL THROUGH OUR HANDS.",
|
||||
)
|
||||
|
||||
/mob/living/basic/fleshmind/tyrant/Initialize(mapload)
|
||||
. = ..()
|
||||
AddComponent(/datum/component/ranged_attacks,\
|
||||
cooldown_time = ranged_cooldown,\
|
||||
projectile_type = projectiletype,\
|
||||
projectile_sound = projectilesound,\
|
||||
burst_shots = 3,\
|
||||
burst_intervals = 0.5 SECONDS,\
|
||||
cooldown_time = 3 SECONDS,\
|
||||
)
|
||||
|
||||
var/static/list/innate_actions = list(
|
||||
/datum/action/cooldown/mob_cooldown/projectile_attack/tyrant_laser = BB_TYRANT_LASER,
|
||||
/datum/action/cooldown/mob_cooldown/projectile_attack/tyrant_rocket = BB_TYRANT_ROCKET
|
||||
)
|
||||
|
||||
grant_actions_by_list(innate_actions)
|
||||
|
||||
/mob/living/basic/fleshmind/tyrant/Life(delta_time, times_fired)
|
||||
. = ..()
|
||||
if(health <= (maxHealth * 0.5) && prob(20))
|
||||
do_sparks(3, FALSE, src)
|
||||
if(!.) //dead
|
||||
return
|
||||
|
||||
/mob/living/basic/fleshmind/tyrant/Destroy()
|
||||
QDEL_NULL(particles)
|
||||
return ..()
|
||||
|
||||
/mob/living/basic/fleshmind/tyrant/death()
|
||||
if(our_controller)
|
||||
our_controller.point_event -= 150 // Very hefty debuf if the boss dies.
|
||||
our_controller.spread_progress_per_second = FLESHCORE_SPREAD_PROGRESS_PER_SUBSYSTEM_FIRE * 0.75 // Take the L
|
||||
return ..()
|
||||
|
||||
/mob/living/basic/fleshmind/tyrant/emp_act(severity)
|
||||
return FALSE
|
||||
|
||||
/mob/living/basic/fleshmind/tyrant/updatehealth()
|
||||
. = ..()
|
||||
if(health <= (maxHealth * 0.5))
|
||||
particles = new /particles/smoke()
|
||||
update_appearance()
|
||||
|
||||
/mob/living/basic/fleshmind/tyrant/update_overlays()
|
||||
. = ..()
|
||||
if(health <= (maxHealth * 0.5) && stat != DEAD)
|
||||
. += "tyrant_damage"
|
||||
|
||||
/mob/living/basic/fleshmind/tyrant/Moved(atom/old_loc, movement_dir, forced, list/old_locs, momentum_change = TRUE)
|
||||
. = ..()
|
||||
playsound(src, pick(footstep_sounds), 100, TRUE)
|
||||
|
||||
/mob/living/basic/fleshmind/tyrant/setDir(newdir)
|
||||
. = ..()
|
||||
if(COOLDOWN_FINISHED(src, rotate_sound_cooldown))
|
||||
playsound(src, 'modular_zubbers/sound/fleshmind/tyrant/mech_rotation.ogg', 35, TRUE)
|
||||
COOLDOWN_START(src, rotate_sound_cooldown, rotate_sound_cooldown_time)
|
||||
|
||||
/obj/projectile/bullet/c50cal/tyrant // BANG BANG BANG
|
||||
damage = 20
|
||||
wound_bonus = 20
|
||||
armour_penetration = 20
|
||||
@@ -0,0 +1,63 @@
|
||||
/datum/ai_controller/basic_controller/fleshmind/tyrant
|
||||
planning_subtrees = list(
|
||||
/datum/ai_planning_subtree/travel_to_point/and_clear_target/reinforce,
|
||||
/datum/ai_planning_subtree/target_retaliate/check_faction,
|
||||
/datum/ai_planning_subtree/simple_find_target,
|
||||
/datum/ai_planning_subtree/targeted_mob_ability/tyrant,
|
||||
/datum/ai_planning_subtree/ranged_skirmish/tyrant,
|
||||
/datum/ai_planning_subtree/basic_melee_attack_subtree/opportunistic,
|
||||
/datum/ai_planning_subtree/random_speech/blackboard/fleshmind,
|
||||
)
|
||||
|
||||
/datum/ai_planning_subtree/targeted_mob_ability/tyrant
|
||||
ability_key = BB_TYRANT_LASER
|
||||
use_ability_behaviour = /datum/ai_behavior/targeted_mob_ability/tyrant
|
||||
finish_planning = FALSE
|
||||
|
||||
/datum/ai_planning_subtree/ranged_skirmish/tyrant
|
||||
max_range = 14
|
||||
|
||||
/datum/ai_behavior/targeted_mob_ability/tyrant
|
||||
var/secondary_ability_key = BB_TYRANT_ROCKET
|
||||
|
||||
/datum/ai_behavior/targeted_mob_ability/tyrant/get_ability_to_use(datum/ai_controller/controller, ability_key)
|
||||
if(prob(50))
|
||||
return controller.blackboard[secondary_ability_key]
|
||||
return controller.blackboard[ability_key]
|
||||
|
||||
/datum/action/cooldown/mob_cooldown/projectile_attack/tyrant_laser
|
||||
name = "Main Laser"
|
||||
desc = "Shoot a laser towards a target"
|
||||
button_icon = 'icons/obj/weapons/guns/projectiles.dmi'
|
||||
button_icon_state = "green_laser"
|
||||
cooldown_time = 3 SECONDS
|
||||
projectile_type = /obj/projectile/beam/emitter/hitscan
|
||||
var/list/laser_projectile_sounds = list(
|
||||
'modular_zubbers/sound/fleshmind/tyrant/laser_1.ogg',
|
||||
'modular_zubbers/sound/fleshmind/tyrant/laser_2.ogg',
|
||||
'modular_zubbers/sound/fleshmind/tyrant/laser_3.ogg',
|
||||
'modular_zubbers/sound/fleshmind/tyrant/laser_4.ogg',
|
||||
'modular_zubbers/sound/fleshmind/tyrant/laser_5.ogg',
|
||||
'modular_zubbers/sound/fleshmind/tyrant/laser_6.ogg',
|
||||
)
|
||||
|
||||
/datum/action/cooldown/mob_cooldown/projectile_attack/tyrant_laser/attack_sequence(mob/living/firer, atom/target)
|
||||
projectile_sound = pick(laser_projectile_sounds)
|
||||
return ..()
|
||||
|
||||
/datum/action/cooldown/mob_cooldown/projectile_attack/tyrant_rocket
|
||||
name = "Shoot Rocket"
|
||||
desc = "Shoot a rocket towards a target"
|
||||
button_icon = 'icons/obj/weapons/guns/projectiles.dmi'
|
||||
button_icon_state = "low_yield_rocket"
|
||||
cooldown_time = 3 SECONDS
|
||||
projectile_type = /obj/projectile/bullet/rocket/weak
|
||||
projectile_sound = 'sound/items/weapons/gun/general/rocket_launch.ogg'
|
||||
can_move = FALSE
|
||||
|
||||
/datum/action/cooldown/mob_cooldown/projectile_attack/tyrant_rocket/attack_sequence(mob/living/firer, atom/target)
|
||||
firer.balloon_alert_to_viewers("begins whirring violently!")
|
||||
playsound(src, 'modular_zubbers/sound/fleshmind/tyrant/charge_up.ogg', 100, TRUE)
|
||||
if(!do_after(firer, 2 SECONDS))
|
||||
return
|
||||
return ..()
|
||||
@@ -0,0 +1,126 @@
|
||||
#define ERT_EXPERIENCED_LEADER_CHOOSE_TOP 3
|
||||
|
||||
/**
|
||||
* Make ERT
|
||||
*
|
||||
* A generalised ERT creation proc, useful for when you want to make an ERT, but not require an admin.
|
||||
*
|
||||
* PROC IS NOT ASYNC.
|
||||
*/
|
||||
/proc/make_ert(ert_type, teamsize = 5, mission_objective_override = "Assist the station.", poll_description = "an ERT team", code = "UNKNOWN", enforce_human = FALSE, open_armory_doors = FALSE, leader_experience = FALSE, random_names = TRUE, notify_players = TRUE, spawnpoint_override = FALSE)
|
||||
if(!ert_type)
|
||||
return
|
||||
|
||||
var/datum/ert/created_ert_datum = new ert_type
|
||||
|
||||
created_ert_datum.teamsize = teamsize
|
||||
created_ert_datum.mission = mission_objective_override
|
||||
created_ert_datum.polldesc = poll_description
|
||||
created_ert_datum.enforce_human = enforce_human
|
||||
created_ert_datum.opendoors = open_armory_doors
|
||||
created_ert_datum.leader_experience = leader_experience
|
||||
created_ert_datum.random_names = random_names
|
||||
created_ert_datum.notify_players = notify_players
|
||||
created_ert_datum.code = code
|
||||
|
||||
var/list/spawnpoints
|
||||
|
||||
if(spawnpoint_override)
|
||||
spawnpoints = spawnpoint_override
|
||||
else
|
||||
spawnpoints = GLOB.emergencyresponseteamspawn
|
||||
|
||||
if(!LAZYLEN(spawnpoints))
|
||||
CRASH("make_ert had no ERT spawnpoints to choose from!")
|
||||
|
||||
var/list/mob/dead/observer/candidates = SSpolling.poll_ghost_candidates("Do you wish to be considered for [created_ert_datum.polldesc]?", check_jobban = ROLE_DEATHSQUAD, alert_pic = /obj/item/card/id/advanced/centcom/ert, role_name_text = "emergency response team")
|
||||
|
||||
if(!length(candidates))
|
||||
return NOT_ENOUGH_PLAYERS
|
||||
|
||||
//Create team
|
||||
var/datum/team/ert/ert_team = new created_ert_datum.team
|
||||
if(created_ert_datum.rename_team)
|
||||
ert_team.name = created_ert_datum.rename_team
|
||||
|
||||
//Assign team objective
|
||||
var/datum/objective/mission_objective = new ()
|
||||
mission_objective.team = ert_team
|
||||
mission_objective.explanation_text = created_ert_datum.mission
|
||||
mission_objective.completed = TRUE
|
||||
ert_team.objectives += mission_objective
|
||||
ert_team.mission = mission_objective
|
||||
|
||||
var/mob/dead/observer/earmarked_leader
|
||||
var/leader_spawned = FALSE // just in case the earmarked leader disconnects or becomes unavailable, we can try giving leader to the last guy to get chosen
|
||||
|
||||
if(created_ert_datum.leader_experience)
|
||||
var/list/candidate_living_exps = list()
|
||||
for(var/mob/dead/observer/potential_leader as anything in candidates)
|
||||
candidate_living_exps[potential_leader] = potential_leader.client?.get_exp_living(TRUE)
|
||||
|
||||
candidate_living_exps = sort_list(candidate_living_exps, cmp = /proc/cmp_numeric_dsc)
|
||||
if(LAZYLEN(candidate_living_exps) > ERT_EXPERIENCED_LEADER_CHOOSE_TOP)
|
||||
candidate_living_exps = candidate_living_exps.Cut(ERT_EXPERIENCED_LEADER_CHOOSE_TOP + 1) // pick from the top ERT_EXPERIENCED_LEADER_CHOOSE_TOP contenders in playtime
|
||||
earmarked_leader = pick(candidate_living_exps)
|
||||
else
|
||||
earmarked_leader = pick(candidates)
|
||||
|
||||
var/index = 0
|
||||
var/number_of_agents = min(created_ert_datum.teamsize, LAZYLEN(candidates))
|
||||
var/team_spawned = FALSE
|
||||
|
||||
while(number_of_agents && LAZYLEN(candidates))
|
||||
var/spawnloc = spawnpoints[index + 1]
|
||||
//loop through spawnpoints one at a time
|
||||
index = (index + 1) % spawnpoints.len
|
||||
var/mob/dead/observer/chosen_candidate = earmarked_leader || pick(candidates) // this way we make sure that our leader gets chosen
|
||||
candidates -= chosen_candidate
|
||||
if(!chosen_candidate?.key)
|
||||
continue
|
||||
|
||||
//Spawn the body
|
||||
|
||||
var/mob/living/carbon/human/ert_operative
|
||||
if(created_ert_datum.mob_type)
|
||||
ert_operative = new created_ert_datum.mob_type(spawnloc)
|
||||
else
|
||||
ert_operative = new /mob/living/carbon/human(spawnloc)
|
||||
chosen_candidate.client.prefs.safe_transfer_prefs_to(ert_operative, is_antag = TRUE)
|
||||
ert_operative.key = chosen_candidate.key
|
||||
|
||||
if(created_ert_datum.enforce_human || !(ert_operative.dna.species.changesource_flags & ERT_SPAWN)) // Don't want any exploding plasmemes
|
||||
ert_operative.set_species(/datum/species/human)
|
||||
|
||||
//Give antag datum
|
||||
var/datum/antagonist/ert/ert_antag
|
||||
|
||||
if((chosen_candidate == earmarked_leader) || (number_of_agents && !leader_spawned))
|
||||
ert_antag = new created_ert_datum.leader_role
|
||||
earmarked_leader = null
|
||||
leader_spawned = TRUE
|
||||
else
|
||||
ert_antag = created_ert_datum.roles[WRAP(number_of_agents, 1, length(created_ert_datum.roles) + 1)]
|
||||
ert_antag = new ert_antag ()
|
||||
ert_antag.random_names = created_ert_datum.random_names
|
||||
|
||||
ert_operative.mind.add_antag_datum(ert_antag,ert_team)
|
||||
ert_operative.mind.set_assigned_role(SSjob.get_job_type(ert_antag.ert_job_path))
|
||||
|
||||
//Logging and cleanup
|
||||
log_game("[key_name(ert_operative)] has been selected as an [ert_antag.name]")
|
||||
number_of_agents--
|
||||
team_spawned++
|
||||
|
||||
if(team_spawned)
|
||||
message_admins("[created_ert_datum.polldesc] has spawned with the mission: [created_ert_datum.mission]")
|
||||
if(created_ert_datum.notify_players)
|
||||
priority_announce("Central command has responded to your request for a CODE [uppertext(created_ert_datum.code)] Emergency Response Team and have confirmed one to be enroute.", "ERT Request", ANNOUNCER_ERTYES)
|
||||
//Open the Armory doors
|
||||
if(created_ert_datum.opendoors)
|
||||
for(var/obj/machinery/door/poddoor/ert/door as anything in SSmachines.get_machines_by_type_and_subtypes(/obj/machinery/door/poddoor/ert))
|
||||
door.open()
|
||||
CHECK_TICK
|
||||
return TRUE
|
||||
|
||||
#undef ERT_EXPERIENCED_LEADER_CHOOSE_TOP
|
||||
@@ -0,0 +1,68 @@
|
||||
/datum/round_event_control/fleshmind
|
||||
name = "Fleshmind"
|
||||
typepath = /datum/round_event/fleshmind
|
||||
max_occurrences = 1
|
||||
weight = 10
|
||||
min_players = 35
|
||||
tags = list(TAG_DESTRUCTIVE, TAG_COMBAT, TAG_CHAOTIC)
|
||||
earliest_start = 60 MINUTES
|
||||
track = EVENT_TRACK_MAJOR
|
||||
|
||||
/datum/round_event/fleshmind
|
||||
fakeable = FALSE
|
||||
announce_when = 200
|
||||
end_when = 201
|
||||
|
||||
/datum/round_event/fleshmind/announce(fake)
|
||||
priority_announce("Confirmed outbreak of level CLASSIFIED biohazard aboard [station_name()]. Station quarantine subroutines activated.", "Critical Biohazard Alert", ANNOUNCER_MUTANTS)
|
||||
|
||||
/datum/round_event/fleshmind/start()
|
||||
var/turf/current_turf = get_turf(src)
|
||||
|
||||
if(is_valid_turf(current_turf))
|
||||
return
|
||||
|
||||
var/turf/spawn_turf = null
|
||||
|
||||
if(LAZYLEN(GLOB.blobstart))
|
||||
var/list/shuffled_blobstarts = shuffle(GLOB.blobstart)
|
||||
|
||||
for(var/turf/blob_turf in shuffled_blobstarts)
|
||||
if(is_valid_turf(blob_turf))
|
||||
spawn_turf = blob_turf
|
||||
break
|
||||
|
||||
else
|
||||
for(var/attempt in 1 to 16)
|
||||
var/turf/candidate_turf = get_safe_random_station_turf_equal_weight()
|
||||
|
||||
if(is_valid_turf(candidate_turf))
|
||||
spawn_turf = candidate_turf
|
||||
break
|
||||
|
||||
if(!spawn_turf)
|
||||
CRASH("Fleshmind failed to pick a valid turf!")
|
||||
|
||||
make_core(spawn_turf)
|
||||
var/datum/round_event_control/event = locate(/datum/round_event_control/wire_priest) in SSevents.control
|
||||
event.run_event(admin_forced = TRUE)
|
||||
|
||||
/datum/round_event/fleshmind/proc/make_core(turf/location)
|
||||
new /obj/structure/fleshmind/structure/core(location)
|
||||
|
||||
/// Copied from line 150 of code/modules/antagonists/blob/overmind.dm
|
||||
/datum/round_event/fleshmind/proc/is_valid_turf(turf/tile)
|
||||
var/area/area = get_area(tile)
|
||||
if((area && !(area.area_flags & BLOBS_ALLOWED)) || !tile || !is_station_level(tile.z) || isgroundlessturf(tile))
|
||||
return FALSE
|
||||
|
||||
if(area_has_player(tile, 7))
|
||||
return FALSE
|
||||
|
||||
return TRUE
|
||||
|
||||
/datum/round_event/fleshmind/proc/area_has_player(turf/center, range = 7)
|
||||
for(var/mob/living/blocked_mob in range(range, center))
|
||||
if(blocked_mob.client)
|
||||
return TRUE
|
||||
return FALSE
|
||||
|
After Width: | Height: | Size: 877 B |
|
After Width: | Height: | Size: 1.7 KiB |
|
After Width: | Height: | Size: 25 KiB |
|
After Width: | Height: | Size: 66 KiB |
|
After Width: | Height: | Size: 810 B |
|
After Width: | Height: | Size: 732 B |
|
After Width: | Height: | Size: 819 B |
|
After Width: | Height: | Size: 16 KiB |
|
After Width: | Height: | Size: 1.2 KiB |
|
After Width: | Height: | Size: 108 KiB |
|
After Width: | Height: | Size: 25 KiB |