[MIRROR] Basic blob mobs [MDB IGNORE] (#23938)

* Basic blob mobs

* Update blackmesa.dmm

* Modular update

---------

Co-authored-by: Jacquerel <hnevard@gmail.com>
Co-authored-by: Giz <13398309+vinylspiders@users.noreply.github.com>
This commit is contained in:
SkyratBot
2023-09-30 00:15:55 -04:00
committed by GitHub
co-authored by Jacquerel Giz
parent ca0e216440
commit 58c20a99fb
51 changed files with 868 additions and 568 deletions
+2 -2
View File
@@ -5008,7 +5008,7 @@
/turf/open/floor/iron/smooth_large,
/area/awaymission/black_mesa/entrance_internal_hall)
"cPC" = (
/mob/living/simple_animal/hostile/blob/blobspore/independent,
/mob/living/basic/blob_minion/spore,
/obj/structure/blob/normal,
/turf/open/floor/engine,
/area/awaymission/black_mesa/hecu_zone_test_chambers)
@@ -9388,7 +9388,7 @@
/turf/open/floor/iron/white,
/area/awaymission/black_mesa/hecu_zone_camp)
"ldC" = (
/mob/living/simple_animal/hostile/blob/blobbernaut/independent,
/mob/living/basic/blob_minion/blobbernaut,
/obj/structure/blob/normal,
/turf/open/floor/engine,
/area/awaymission/black_mesa/hecu_zone_test_chambers)
+3
View File
@@ -11,6 +11,9 @@
///How close a mob must be for us to select it as a target, if that is less than how far we can maintain it as a target
#define BB_AGGRO_RANGE "BB_aggro_range"
///Turf we want a mob to move to
#define BB_TRAVEL_DESTINATION "BB_travel_destination"
///song instrument blackboard, set by instrument subtrees
#define BB_SONG_INSTRUMENT "BB_SONG_INSTRUMENT"
///song lines blackboard, set by default on controllers
@@ -17,6 +17,9 @@
#define COMSIG_ATOM_AFTER_SHUTTLE_MOVE "movable_after_shuttle_move"
///called on a movable (NOT living) when it starts pulling (atom/movable/pulled, state, force)
#define COMSIG_ATOM_START_PULL "movable_start_pull"
/// called on /atom when something attempts to pass through it (atom/movable/source, atom/movable/passing, dir)
#define COMSIG_ATOM_TRIED_PASS "atom_tried_pass"
#define COMSIG_COMPONENT_PERMIT_PASSAGE (1 << 0)
///called on /living when someone starts pulling (atom/movable/pulled, state, force)
#define COMSIG_LIVING_START_PULL "living_start_pull"
///called on /living when someone is pulled (mob/living/puller)
@@ -0,0 +1,4 @@
/// Signal sent when a blob overmind picked a new strain (/mob/camera/blob/overmind, /datum/blobstrain/new_strain)
#define COMSIG_BLOB_SELECTED_STRAIN "blob_selected_strain"
/// Signal sent by a blob spore when it creates a zombie (/mob/living/basic/blob_minion/spore/spore, //mob/living/basic/blob_minion/zombie/zombie)
#define COMSIG_BLOB_ZOMBIFIED "blob_zombified"
@@ -7,6 +7,8 @@
#define COMSIG_MOB_LOGIN "mob_login"
///from base of /mob/Logout(): ()
#define COMSIG_MOB_LOGOUT "mob_logout"
///from base of /mob/mind_initialize
#define COMSIG_MOB_MIND_INITIALIZED "mob_mind_inited"
///from base of mob/set_stat(): (new_stat, old_stat)
#define COMSIG_MOB_STATCHANGE "mob_statchange"
///from base of mob/reagent_check(): (datum/reagent/chem, seconds_per_tick, times_fired)
-2
View File
@@ -278,8 +278,6 @@ GLOBAL_LIST_INIT(glass_sheet_types, typecacheof(list(
#define isholoeffect(O) (istype(O, /obj/effect/holodeck_effect))
#define isblobmonster(O) (istype(O, /mob/living/simple_animal/hostile/blob))
#define isshuttleturf(T) (!isnull(T.depth_to_find_baseturf(/turf/baseturf_skipover/shuttle)))
#define isProbablyWallMounted(O) (O.pixel_x > 20 || O.pixel_x < -20 || O.pixel_y > 20 || O.pixel_y < -20)
+1
View File
@@ -17,6 +17,7 @@
#define span_bigicon(str) ("<span class='bigicon'>" + str + "</span>")
#define span_binarysay(str) ("<span class='binarysay'>" + str + "</span>")
#define span_blob(str) ("<span class='blob'>" + str + "</span>")
#define span_blobannounce(str) ("<span class='blobannounce'>" + str + "</span>")
#define span_blue(str) ("<span class='blue'>" + str + "</span>")
#define span_blueteamradio(str) ("<span class='blueteamradio'>" + str + "</span>")
#define span_bold(str) ("<span class='bold'>" + str + "</span>")
+2
View File
@@ -1144,6 +1144,8 @@ Remember to update _globalvars/traits.dm if you're adding/removing/renaming trai
#define AUTOPSY_TRAIT "autopsy_trait"
/// Trait given by [/datum/status_effect/blessing_of_insanity]
#define MAD_WIZARD_TRAIT "mad_wizard_trait"
/// Isn't attacked harmfully by blob structures
#define TRAIT_BLOB_ALLY "blob_ally"
/**
* Trait granted by [/mob/living/carbon/Initialize] and
+16
View File
@@ -81,3 +81,19 @@ it will be sent to all connected chats.
/// Handles text formatting for item use hints in examine text
#define EXAMINE_HINT(text) ("<b>" + text + "</b>")
/// Sends a message to all dead and observing players, if a source is provided a follow link will be attached.
/proc/send_to_observers(message, source)
var/list/all_observers = GLOB.dead_player_list + GLOB.current_observers_list
for(var/mob/observer as anything in all_observers)
if (isnull(source))
to_chat(observer, "[message]")
continue
var/link = FOLLOW_LINK(observer, source)
to_chat(observer, "[link] [message]")
/// Sends a message to everyone with blob telepathy, and all observers
/proc/blob_telepathy(message, source)
for(var/mob/creature as anything in GLOB.blob_telepathy_mobs)
to_chat(creature, message)
send_to_observers(message, source)
+3
View File
@@ -52,6 +52,9 @@ GLOBAL_LIST_EMPTY(current_living_antags)
/// All observers with clients that joined as observers.
GLOBAL_LIST_EMPTY(current_observers_list)
/// All living mobs which can hear blob telepathy
GLOBAL_LIST_EMPTY(blob_telepathy_mobs)
///underages who have been reported to security for trying to buy things they shouldn't, so they can't spam
GLOBAL_LIST_EMPTY(narcd_underages)
+2
View File
@@ -5,6 +5,7 @@
#define POLL_IGNORE_ALIEN_LARVA "alien_larva"
#define POLL_IGNORE_ASH_SPIRIT "ash_spirit"
#define POLL_IGNORE_ASHWALKER "ashwalker"
#define POLL_IGNORE_BLOB "blob"
#define POLL_IGNORE_BOTS "bots"
#define POLL_IGNORE_CARGORILLA "cargorilla"
#define POLL_IGNORE_CONTRACTOR_SUPPORT "contractor_support"
@@ -43,6 +44,7 @@ GLOBAL_LIST_INIT(poll_ignore_desc, list(
POLL_IGNORE_ALIEN_LARVA = "Xenomorph larva",
POLL_IGNORE_ASH_SPIRIT = "Ash Spirit",
POLL_IGNORE_ASHWALKER = "Ashwalker eggs",
POLL_IGNORE_BLOB = "Blob spores",
POLL_IGNORE_BOTS = "Bots",
POLL_IGNORE_CARGORILLA = "Cargorilla",
POLL_IGNORE_CONTRACTOR_SUPPORT = "Contractor Support Unit",
@@ -384,7 +384,7 @@
src.simulated_only = simulated_only
src.avoid = avoid
src.skip_first = skip_first
movement_path = initial_path.Copy()
movement_path = initial_path?.Copy()
if(isidcard(id))
RegisterSignal(id, COMSIG_QDELETING, PROC_REF(handle_no_id)) //I prefer erroring to harddels. If this breaks anything consider making id info into a datum or something
@@ -6,6 +6,8 @@
/datum/ai_behavior/travel_towards
required_distance = 0
behavior_flags = AI_BEHAVIOR_REQUIRE_MOVEMENT | AI_BEHAVIOR_CAN_PLAN_DURING_EXECUTION
/// If true we will get rid of our target on completion
var/clear_target = FALSE
/datum/ai_behavior/travel_towards/setup(datum/ai_controller/controller, target_key)
. = ..()
@@ -16,7 +18,15 @@
/datum/ai_behavior/travel_towards/perform(seconds_per_tick, datum/ai_controller/controller, target_key)
. = ..()
finish_action(controller, TRUE)
finish_action(controller, TRUE, target_key)
/datum/ai_behavior/travel_towards/finish_action(datum/ai_controller/controller, succeeded, target_key)
. = ..()
if (clear_target)
controller.clear_blackboard_key(target_key)
/datum/ai_behavior/travel_towards/stop_on_arrival
clear_target = TRUE
/**
* # Travel Towards Atom
@@ -0,0 +1,18 @@
/// Simply walk to a location
/datum/ai_planning_subtree/travel_to_point
/// Blackboard key where we travel a place we walk to
var/location_key = BB_TRAVEL_DESTINATION
/// What do we do in order to travel
var/travel_behaviour = /datum/ai_behavior/travel_towards
/datum/ai_planning_subtree/travel_to_point/SelectBehaviors(datum/ai_controller/controller, seconds_per_tick)
. = ..()
var/atom/target = controller.blackboard[location_key]
if (QDELETED(target))
return
controller.queue_behavior(travel_behaviour, location_key)
return SUBTREE_RETURN_FINISH_PLANNING
/datum/ai_planning_subtree/travel_to_point/and_clear_target
travel_behaviour = /datum/ai_behavior/travel_towards/stop_on_arrival
@@ -93,7 +93,7 @@
var/mob/living/hunter = controller.pawn
var/atom/hunted = controller.blackboard[hunting_target_key]
if(isnull(hunted))
if(QDELETED(hunted))
//Target is gone for some reason. forget about this task!
controller[hunting_target_key] = null
finish_action(controller, FALSE, hunting_target_key)
@@ -0,0 +1,17 @@
/// Find and attack corpses
/datum/ai_planning_subtree/find_and_hunt_target/corpses
finding_behavior = /datum/ai_behavior/find_hunt_target/corpses
hunting_behavior = /datum/ai_behavior/hunt_target/unarmed_attack_target
hunt_targets = list(/mob/living)
/// Find nearby dead mobs
/datum/ai_behavior/find_hunt_target/corpses
/datum/ai_behavior/find_hunt_target/corpses/valid_dinner(mob/living/source, mob/living/dinner, radius)
if (!isliving(dinner) || dinner.stat != DEAD)
return FALSE
return can_see(source, dinner, radius)
/// Find and attack specifically human corpses
/datum/ai_planning_subtree/find_and_hunt_target/corpses/human
hunt_targets = list(/mob/living/carbon/human)
+3 -3
View File
@@ -2,7 +2,7 @@
* This movement datum represents smart-pathing
*/
/datum/ai_movement/jps
max_pathing_attempts = 4
max_pathing_attempts = 20
/datum/ai_movement/jps/start_moving_towards(datum/ai_controller/controller, atom/current_movement_target, min_distance)
. = ..()
@@ -12,13 +12,13 @@
var/datum/move_loop/loop = SSmove_manager.jps_move(moving,
current_movement_target,
delay,
repath_delay = 2 SECONDS,
repath_delay = 0.5 SECONDS,
max_path_length = AI_MAX_PATH_LENGTH,
minimum_distance = controller.get_minimum_distance(),
id = controller.get_access(),
subsystem = SSai_movement,
extra_info = controller,
initial_path = controller.blackboard[BB_PATH_TO_USE])
)
RegisterSignal(loop, COMSIG_MOVELOOP_PREPROCESS_CHECK, PROC_REF(pre_move))
RegisterSignal(loop, COMSIG_MOVELOOP_POSTPROCESS, PROC_REF(post_move))
+154
View File
@@ -0,0 +1,154 @@
/**
* Common behaviour shared by things which are minions to a blob
*/
/datum/component/blob_minion
dupe_mode = COMPONENT_DUPE_UNIQUE_PASSARGS
/// Overmind who is our boss
var/mob/camera/blob/overmind
/// Callback to run if overmind strain changes
var/datum/callback/on_strain_changed
/datum/component/blob_minion/Initialize(mob/camera/blob/overmind, datum/callback/on_strain_changed)
. = ..()
if (!isliving(parent))
return COMPONENT_INCOMPATIBLE
src.on_strain_changed = on_strain_changed
register_overlord(overmind)
/datum/component/blob_minion/InheritComponent(datum/component/new_comp, i_am_original, mob/camera/blob/overmind, datum/callback/on_strain_changed)
if (!isnull(on_strain_changed))
src.on_strain_changed = on_strain_changed
register_overlord(overmind)
/datum/component/blob_minion/proc/register_overlord(mob/camera/blob/overmind)
if (isnull(overmind))
return
src.overmind = overmind
overmind.register_new_minion(parent)
RegisterSignal(overmind, COMSIG_QDELETING, PROC_REF(overmind_deleted))
RegisterSignal(overmind, COMSIG_BLOB_SELECTED_STRAIN, PROC_REF(overmind_properties_changed))
overmind_properties_changed(overmind, overmind.blobstrain)
/// Our overmind is gone, uh oh!
/datum/component/blob_minion/proc/overmind_deleted()
SIGNAL_HANDLER
overmind = null
overmind_properties_changed()
/// Our overmind has changed colour and properties
/datum/component/blob_minion/proc/overmind_properties_changed(mob/camera/blob/overmind, datum/blobstrain/new_strain)
SIGNAL_HANDLER
var/mob/living/living_parent = parent
living_parent.update_appearance(UPDATE_ICON)
on_strain_changed?.Invoke(overmind, new_strain)
/datum/component/blob_minion/RegisterWithParent()
var/mob/living/living_parent = parent
living_parent.pass_flags |= PASSBLOB
living_parent.faction |= ROLE_BLOB
ADD_TRAIT(parent, TRAIT_BLOB_ALLY, REF(src))
remove_verb(parent, /mob/living/verb/pulled) // No dragging people into the blob
RegisterSignal(parent, COMSIG_MOB_MIND_INITIALIZED, PROC_REF(on_mind_init))
RegisterSignal(parent, COMSIG_ATOM_UPDATE_ICON, PROC_REF(on_update_appearance))
RegisterSignal(parent, COMSIG_MOB_GET_STATUS_TAB_ITEMS, PROC_REF(on_update_status_tab))
RegisterSignal(parent, COMSIG_ATOM_BLOB_ACT, PROC_REF(on_blob_touched))
RegisterSignal(parent, COMSIG_ATOM_FIRE_ACT, PROC_REF(on_burned))
RegisterSignal(parent, COMSIG_ATOM_TRIED_PASS, PROC_REF(on_attempted_pass))
RegisterSignal(parent, COMSIG_MOVABLE_SPACEMOVE, PROC_REF(on_space_move))
RegisterSignal(parent, COMSIG_LIVING_TRY_SPEECH, PROC_REF(on_try_speech))
RegisterSignal(parent, COMSIG_MOB_CHANGED_TYPE, PROC_REF(on_transformed))
living_parent.update_appearance(UPDATE_ICON)
GLOB.blob_telepathy_mobs |= parent
/datum/component/blob_minion/UnregisterFromParent()
if (!isnull(overmind))
overmind.blob_mobs -= parent
var/mob/living/living_parent = parent
living_parent.pass_flags &= ~PASSBLOB
living_parent.faction -= ROLE_BLOB
REMOVE_TRAIT(parent, TRAIT_BLOB_ALLY, REF(src))
add_verb(parent, /mob/living/verb/pulled)
UnregisterSignal(parent, list(
COMSIG_ATOM_BLOB_ACT,
COMSIG_ATOM_FIRE_ACT,
COMSIG_ATOM_TRIED_PASS,
COMSIG_ATOM_UPDATE_ICON,
COMSIG_LIVING_TRY_SPEECH,
COMSIG_MOB_CHANGED_TYPE,
COMSIG_MOB_GET_STATUS_TAB_ITEMS,
COMSIG_MOB_MIND_INITIALIZED,
COMSIG_MOVABLE_SPACEMOVE,
))
GLOB.blob_telepathy_mobs -= parent
/// Become blobpilled when we gain a mind
/datum/component/blob_minion/proc/on_mind_init(mob/living/minion, datum/mind/new_mind)
SIGNAL_HANDLER
if (isnull(overmind))
return
var/datum/antagonist/blob_minion/minion_motive = new(overmind)
new_mind.add_antag_datum(minion_motive)
/// When our icon is updated, update our colour too
/datum/component/blob_minion/proc/on_update_appearance(mob/living/minion)
SIGNAL_HANDLER
if(isnull(overmind))
minion.remove_atom_colour(FIXED_COLOUR_PRIORITY)
return
minion.add_atom_colour(overmind.blobstrain.color, FIXED_COLOUR_PRIORITY)
/// When our icon is updated, update our colour too
/datum/component/blob_minion/proc/on_update_status_tab(mob/living/minion, list/status_items)
SIGNAL_HANDLER
if (isnull(overmind))
return
status_items += "Blobs to Win: [length(overmind.blobs_legit)]/[overmind.blobwincount]"
/// If we feel the gentle caress of a blob, we feel better
/datum/component/blob_minion/proc/on_blob_touched(mob/living/minion)
SIGNAL_HANDLER
if(minion.stat == DEAD || minion.health >= minion.maxHealth)
return COMPONENT_CANCEL_BLOB_ACT // Don't hurt us in order to heal us
for(var/i in 1 to 2)
var/obj/effect/temp_visual/heal/heal_effect = new /obj/effect/temp_visual/heal(get_turf(parent)) // hello yes you are being healed
heal_effect.color = isnull(overmind) ? COLOR_BLACK : overmind.blobstrain.complementary_color
minion.heal_overall_damage(minion.maxHealth * BLOBMOB_HEALING_MULTIPLIER)
return COMPONENT_CANCEL_BLOB_ACT
/// If we feel the fearsome bite of open flame, we feel worse
/datum/component/blob_minion/proc/on_burned(mob/living/minion, exposed_temperature, exposed_volume)
SIGNAL_HANDLER
if(isnull(exposed_temperature))
minion.adjustFireLoss(5)
return
minion.adjustFireLoss(clamp(0.01 * exposed_temperature, 1, 5))
/// Someone is attempting to move through us, allow it if it is a blob tile
/datum/component/blob_minion/proc/on_attempted_pass(mob/living/minion, atom/movable/incoming)
SIGNAL_HANDLER
if(istype(incoming, /obj/structure/blob))
return COMSIG_COMPONENT_PERMIT_PASSAGE
/// If we're near a blob, stop drifting
/datum/component/blob_minion/proc/on_space_move(mob/living/minion)
SIGNAL_HANDLER
var/obj/structure/blob/blob_handhold = locate() in range(1, parent)
if (!isnull(blob_handhold))
return COMSIG_MOVABLE_STOP_SPACEMOVE
/// We only speak telepathically to blobs
/datum/component/blob_minion/proc/on_try_speech(mob/living/minion, message, ignore_spam, forced)
SIGNAL_HANDLER
var/spanned_message = minion.say_quote(message)
var/rendered = span_blob("<b>\[Blob Telepathy\] [minion.real_name]</b> [spanned_message]")
blob_telepathy(rendered, minion)
return COMPONENT_CANNOT_SPEAK
/// Called when a blob minion is transformed into something else, hopefully a spore into a zombie
/datum/component/blob_minion/proc/on_transformed(mob/living/minion, mob/living/replacement)
SIGNAL_HANDLER
overmind?.assume_direct_control(replacement)
/datum/component/blob_minion/PostTransfer()
if(!isliving(parent))
return COMPONENT_INCOMPATIBLE
+1 -1
View File
@@ -246,6 +246,7 @@
var/static/list/something_pool = list(
/mob/living/basic/bat,
/mob/living/basic/bear,
/mob/living/basic/blob_minion/blobbernaut,
/mob/living/basic/butterfly,
/mob/living/basic/carp,
/mob/living/basic/carp/magic,
@@ -272,7 +273,6 @@
/mob/living/basic/statue,
/mob/living/basic/stickman,
/mob/living/basic/stickman/dog,
/mob/living/simple_animal/hostile/blob/blobbernaut/independent,
/mob/living/simple_animal/hostile/gorilla,
/mob/living/simple_animal/hostile/megafauna/dragon/lesser,
/mob/living/simple_animal/hostile/retaliate/goat,
+1
View File
@@ -11,6 +11,7 @@
mind.set_current(src)
// There's nowhere else to set this up, mind code makes me depressed
mind.antag_hud = add_alt_appearance(/datum/atom_hud/alternate_appearance/basic/antagonist_hud, "combo_hud", mind)
SEND_SIGNAL(src, COMSIG_MOB_MIND_INITIALIZED, mind)
/mob/living/carbon/mind_initialize()
..()
+2
View File
@@ -318,6 +318,8 @@
/atom/proc/CanPass(atom/movable/mover, border_dir)
SHOULD_CALL_PARENT(TRUE)
SHOULD_BE_PURE(TRUE)
if(SEND_SIGNAL(src, COMSIG_ATOM_TRIED_PASS, mover, border_dir) & COMSIG_COMPONENT_PERMIT_PASSAGE)
return TRUE
if(mover.movement_type & PHASING)
return TRUE
. = CanAllowThrough(mover, border_dir)
@@ -394,6 +394,15 @@
reagents.expose(smoker, INGEST, fraction)
return TRUE
/// Helper to quickly create a cloud of reagent smoke
/proc/do_chem_smoke(range = 0, amount = DIAMOND_AREA(range), atom/holder = null, location = null, reagent_type = /datum/reagent/water, reagent_volume = 10, log = FALSE)
var/datum/reagents/smoke_reagents = new/datum/reagents(reagent_volume)
smoke_reagents.add_reagent(reagent_type, reagent_volume)
var/datum/effect_system/fluid_spread/smoke/chem/smoke = new
smoke.attach(location)
smoke.set_up(amount = amount, holder = holder, location = location, carry = smoke_reagents, silent = TRUE)
smoke.start(log = log)
/// A factory which produces clouds of chemical bearing smoke.
/datum/effect_system/fluid_spread/smoke/chem
@@ -34,9 +34,3 @@
objective.owner = owner
objective.overmind = overmind
objectives += objective
/datum/antagonist/blob_minion/blobbernaut
name = "\improper Blobbernaut"
/datum/antagonist/blob_minion/blob_zombie
name = "\improper Blob Zombie"
@@ -96,12 +96,12 @@ GLOBAL_LIST_INIT(valid_blobstrains, subtypesof(/datum/blobstrain) - list(/datum/
B.modify_max_integrity(B.max_integrity * max_structure_health_multiplier)
B.update_appearance()
for(var/mob/living/simple_animal/hostile/blob/BM as anything in overmind.blob_mobs)
BM.maxHealth *= max_mob_health_multiplier
BM.health *= max_mob_health_multiplier
BM.update_icons() //If it's getting a new strain, tell it what it does!
to_chat(BM, "Your overmind's blob strain is now: <b><font color=\"[color]\">[name]</b></font>!")
to_chat(BM, "The <b><font color=\"[color]\">[name]</b></font> strain [shortdesc ? "[shortdesc]" : "[description]"]")
for(var/mob/living/blob_mob as anything in overmind.blob_mobs)
blob_mob.maxHealth *= max_mob_health_multiplier
blob_mob.health *= max_mob_health_multiplier
blob_mob.update_icons() //If it's getting a new strain, tell it what it does!
to_chat(blob_mob, "Your overmind's blob strain is now: <b><font color=\"[color]\">[name]</b></font>!")
to_chat(blob_mob, "The <b><font color=\"[color]\">[name]</b></font> strain [shortdesc ? "[shortdesc]" : "[description]"]")
/datum/blobstrain/proc/on_lose()
if(overmind.blob_core)
@@ -122,9 +122,9 @@ GLOBAL_LIST_INIT(valid_blobstrains, subtypesof(/datum/blobstrain) - list(/datum/
for(var/obj/structure/blob/B as anything in overmind.all_blobs)
B.modify_max_integrity(B.max_integrity / max_structure_health_multiplier)
for(var/mob/living/simple_animal/hostile/blob/BM as anything in overmind.blob_mobs)
BM.maxHealth /= max_mob_health_multiplier
BM.health /= max_mob_health_multiplier
for(var/mob/living/blob_mob as anything in overmind.blob_mobs)
blob_mob.maxHealth /= max_mob_health_multiplier
blob_mob.health /= max_mob_health_multiplier
/datum/blobstrain/proc/on_sporedeath(mob/living/spore)
@@ -15,8 +15,9 @@
var/mob_protection = L.getarmor(null, BIO) * 0.01
reagent.expose_mob(L, VAPOR, BLOBMOB_BLOBBERNAUT_REAGENTATK_VOL+blobbernaut_reagentatk_bonus, FALSE, mob_protection, overmind)//this will do between 10 and 20 damage(reduced by mob protection), depending on chemical, plus 4 from base brute damage.
/datum/blobstrain/reagent/on_sporedeath(mob/living/spore)
spore.reagents.add_reagent(reagent.type, 10)
/datum/blobstrain/reagent/on_sporedeath(mob/living/basic/spore)
var/burst_range = (spore.type == /mob/living/basic/blob_minion/spore) ? 1 : 0
do_chem_smoke(range = burst_range, holder = spore, location = get_turf(spore), reagent_type = reagent.type)
// These can only be applied by blobs. They are what (reagent) blobs are made out of.
/datum/reagent/blob
@@ -29,7 +30,7 @@
/// Used by blob reagents to calculate the reaction volume they should use when exposing mobs.
/datum/reagent/blob/proc/return_mob_expose_reac_volume(mob/living/exposed_mob, methods=TOUCH, reac_volume, show_message, touch_protection, mob/camera/blob/overmind)
if(exposed_mob.stat == DEAD || isblobmonster(exposed_mob))
if(exposed_mob.stat == DEAD || HAS_TRAIT(exposed_mob, TRAIT_BLOB_ALLY))
return 0 //the dead, and blob mobs, don't cause reactions
return round(reac_volume * min(1.5 - touch_protection, 1), 0.1) //full touch protection means 50% volume, any prot below 0.5 means 100% volume.
@@ -11,18 +11,16 @@
message_living = ", and you feel tired"
reagent = /datum/reagent/blob/distributed_neurons
/datum/blobstrain/reagent/distributed_neurons/damage_reaction(obj/structure/blob/B, damage, damage_type, damage_flag)
if((damage_flag == MELEE || damage_flag == BULLET || damage_flag == LASER) && damage <= 20 && B.get_integrity() - damage <= 0 && prob(15)) //if the cause isn't fire or a bomb, the damage is less than 21, we're going to die from that damage, 15% chance of a shitty spore.
B.visible_message(span_warning("<b>A spore floats free of the blob!</b>"))
var/mob/living/simple_animal/hostile/blob/blobspore/weak/BS = new/mob/living/simple_animal/hostile/blob/blobspore/weak(B.loc)
BS.overmind = B.overmind
BS.update_icons()
B.overmind.blob_mobs.Add(BS)
/datum/blobstrain/reagent/distributed_neurons/damage_reaction(obj/structure/blob/blob_tile, damage, damage_type, damage_flag)
if((damage_flag == MELEE || damage_flag == BULLET || damage_flag == LASER) && damage <= 20 && blob_tile.get_integrity() - damage <= 0 && prob(15)) //if the cause isn't fire or a bomb, the damage is less than 21, we're going to die from that damage, 15% chance of a shitty spore.
blob_tile.visible_message(span_boldwarning("A spore floats free of the blob!"))
blob_tile.overmind.create_spore(blob_tile.loc, /mob/living/basic/blob_minion/spore/minion/weak)
return ..()
/datum/reagent/blob/distributed_neurons
name = "Distributed Neurons"
color = "#E88D5D"
taste_description = "fizzing"
/datum/reagent/blob/distributed_neurons/expose_mob(mob/living/exposed_mob, methods=TOUCH, reac_volume, show_message, touch_protection, mob/camera/blob/overmind)
. = ..()
@@ -33,10 +31,7 @@
exposed_mob.investigate_log("has been killed by distributed neurons (blob).", INVESTIGATE_DEATHS)
exposed_mob.death() //sleeping in a fight? bad plan.
if(exposed_mob.stat == DEAD && overmind.can_buy(5))
var/mob/living/simple_animal/hostile/blob/blobspore/spore = new/mob/living/simple_animal/hostile/blob/blobspore(get_turf(exposed_mob))
spore.overmind = overmind
spore.update_icons()
overmind.blob_mobs.Add(spore)
var/mob/living/basic/blob_minion/spore/minion/spore = overmind.create_spore(get_turf(exposed_mob))
spore.zombify(exposed_mob)
overmind.add_points(-5)
to_chat(overmind, span_notice("Spent 5 resources for the zombification of [exposed_mob]."))
@@ -16,15 +16,15 @@
if(damage && ((damage_type == BRUTE) || (damage_type == BURN)) && B.get_integrity() - damage > 0 && COOLDOWN_FINISHED(src, retaliate_cooldown)) // Is there any damage, is it burn or brute, will we be alive, and has the cooldown finished?
COOLDOWN_START(src, retaliate_cooldown, 2.5 SECONDS) // 2.5 seconds before auto-retaliate can whack everything within 1 tile again
B.visible_message(span_boldwarning("The blob retaliates, lashing out!"))
for(var/atom/A in range(1, B))
if(!A.can_blob_attack())
for(var/atom/thing in range(1, B))
if(!thing.can_blob_attack())
continue
var/attacked_turf = get_turf(A)
if(isliving(A) && !isblobmonster(A)) // Make sure to inject strain-reagents with automatic attacks when needed.
var/attacked_turf = get_turf(thing)
if(isliving(thing) && !HAS_TRAIT(thing, TRAIT_BLOB_ALLY)) // Make sure to inject strain-reagents with automatic attacks when needed.
B.blob_attack_animation(attacked_turf, overmind)
attack_living(A)
attack_living(thing)
else if(A.blob_act(B)) // After checking for mobs, whack everything else with the standard attack
else if(thing.blob_act(B)) // After checking for mobs, whack everything else with the standard attack
B.blob_attack_animation(attacked_turf, overmind) // Only play the animation if the attack did something meaningful
return ..()
@@ -35,7 +35,7 @@
color = "#9ACD32"
/datum/reagent/blob/reactive_spines/return_mob_expose_reac_volume(mob/living/exposed_mob, methods=TOUCH, reac_volume, show_message, touch_protection, mob/camera/blob/overmind)
if(exposed_mob.stat == DEAD || isblobmonster(exposed_mob))
if(exposed_mob.stat == DEAD || HAS_TRAIT(exposed_mob, TRAIT_BLOB_ALLY))
return 0 //the dead, and blob mobs, don't cause reactions
return reac_volume
+60 -45
View File
@@ -54,6 +54,7 @@ GLOBAL_LIST_EMPTY(blob_nodes)
var/list/strain_choices
/mob/camera/blob/Initialize(mapload, starting_points = OVERMIND_STARTING_POINTS)
ADD_TRAIT(src, TRAIT_BLOB_ALLY, INNATE_TRAIT)
validate_location()
blob_points = starting_points
manualplace_min_time += world.time
@@ -71,6 +72,7 @@ GLOBAL_LIST_EMPTY(blob_nodes)
SSshuttle.registerHostileEnvironment(src)
. = ..()
START_PROCESSING(SSobj, src)
GLOB.blob_telepathy_mobs |= src
/mob/camera/blob/proc/validate_location()
var/turf/T = get_turf(src)
@@ -112,6 +114,7 @@ GLOBAL_LIST_EMPTY(blob_nodes)
to_chat(src, span_notice("The <b><font color=\"[blobstrain.color]\">[blobstrain.name]</b></font> strain [blobstrain.description]"))
if(blobstrain.effectdesc)
to_chat(src, span_notice("The <b><font color=\"[blobstrain.color]\">[blobstrain.name]</b></font> strain [blobstrain.effectdesc]"))
SEND_SIGNAL(src, COMSIG_BLOB_SELECTED_STRAIN, blobstrain)
/mob/camera/blob/can_z_move(direction, turf/start, turf/destination, z_move_flags = NONE, mob/living/rider)
if(placed) // The blob can't expand vertically (yet)
@@ -162,50 +165,70 @@ GLOBAL_LIST_EMPTY(blob_nodes)
priority_announce("Confirmed outbreak of level 5 biohazard aboard [station_name()]. All personnel must contain the outbreak.", "Biohazard Alert", ANNOUNCER_OUTBREAK5)
has_announced = TRUE
/// Create a blob spore and link it to us
/mob/camera/blob/proc/create_spore(turf/spore_turf, spore_type = /mob/living/basic/blob_minion/spore/minion)
var/mob/living/basic/blob_minion/spore/spore = new spore_type(spore_turf)
assume_direct_control(spore)
return spore
/// Give our new minion the properties of a minion
/mob/camera/blob/proc/assume_direct_control(mob/living/minion)
minion.AddComponent(/datum/component/blob_minion, src)
/// Add something to our list of mobs and wait for it to die
/mob/camera/blob/proc/register_new_minion(mob/living/minion)
blob_mobs |= minion
if (!istype(minion, /mob/living/basic/blob_minion/blobbernaut))
RegisterSignal(minion, COMSIG_LIVING_DEATH, PROC_REF(on_minion_death))
/// When a spore (or zombie) dies then we do this
/mob/camera/blob/proc/on_minion_death(mob/living/spore)
SIGNAL_HANDLER
blobstrain.on_sporedeath(spore)
/mob/camera/blob/proc/victory()
sound_to_playing_players('sound/machines/alarm.ogg')
sleep(10 SECONDS)
for(var/i in GLOB.mob_living_list)
var/mob/living/L = i
var/turf/T = get_turf(L)
if(!T || !is_station_level(T.z))
for(var/mob/living/live_guy as anything in GLOB.mob_living_list)
var/turf/guy_turf = get_turf(live_guy)
if(isnull(guy_turf) || !is_station_level(guy_turf.z))
continue
if(L in GLOB.overminds || (L.pass_flags & PASSBLOB))
if(live_guy in GLOB.overminds || (live_guy.pass_flags & PASSBLOB))
continue
var/area/Ablob = get_area(T)
if(!(Ablob.area_flags & BLOBS_ALLOWED))
var/area/blob_area = get_area(guy_turf)
if(!(blob_area.area_flags & BLOBS_ALLOWED))
continue
if(!(ROLE_BLOB in L.faction))
playsound(L, 'sound/effects/splat.ogg', 50, TRUE)
if(L.stat != DEAD)
L.investigate_log("has died from blob takeover.", INVESTIGATE_DEATHS)
L.death()
new/mob/living/simple_animal/hostile/blob/blobspore(T)
if(!(ROLE_BLOB in live_guy.faction))
playsound(live_guy, 'sound/effects/splat.ogg', 50, TRUE)
if(live_guy.stat != DEAD)
live_guy.investigate_log("has died from blob takeover.", INVESTIGATE_DEATHS)
live_guy.death()
create_spore(guy_turf)
else
L.fully_heal()
live_guy.fully_heal()
for(var/area/A in GLOB.areas)
if(!(A.type in GLOB.the_station_areas))
for(var/area/check_area in GLOB.areas)
if(!is_type_in_list(check_area, GLOB.the_station_areas))
continue
if(!(A.area_flags & BLOBS_ALLOWED))
if(!(check_area.area_flags & BLOBS_ALLOWED))
continue
A.color = blobstrain.color
A.name = "blob"
A.icon = 'icons/mob/nonhuman-player/blob.dmi'
A.icon_state = "blob_shield"
A.layer = BELOW_MOB_LAYER
A.invisibility = 0
A.blend_mode = 0
check_area.color = blobstrain.color
check_area.name = "blob"
check_area.icon = 'icons/mob/nonhuman-player/blob.dmi'
check_area.icon_state = "blob_shield"
check_area.layer = BELOW_MOB_LAYER
check_area.invisibility = 0
check_area.blend_mode = 0
var/datum/antagonist/blob/B = mind.has_antag_datum(/datum/antagonist/blob)
if(B)
var/datum/objective/blob_takeover/main_objective = locate() in B.objectives
if(main_objective)
main_objective.completed = TRUE
to_chat(world, span_blob("[real_name] consumed the station in an unstoppable tide!"))
to_chat(world, span_blobannounce("[real_name] consumed the station in an unstoppable tide!"))
SSticker.news_report = BLOB_WIN
SSticker.force_ending = FORCE_END_ROUND
@@ -216,11 +239,6 @@ GLOBAL_LIST_EMPTY(blob_nodes)
if(B && B.overmind == src)
B.overmind = null
B.update_appearance() //reset anything that was ours
for(var/BLO in blob_mobs)
var/mob/living/simple_animal/hostile/blob/BM = BLO
if(BM)
BM.overmind = null
BM.update_icons()
for(var/obj/structure/blob/blob_structure as anything in all_blobs)
blob_structure.overmind = null
all_blobs = null
@@ -233,6 +251,7 @@ GLOBAL_LIST_EMPTY(blob_nodes)
SSshuttle.clearHostileEnvironment(src)
STOP_PROCESSING(SSobj, src)
GLOB.blob_telepathy_mobs -= src
return ..()
@@ -240,7 +259,7 @@ GLOBAL_LIST_EMPTY(blob_nodes)
. = ..()
if(!. || !client)
return FALSE
to_chat(src, span_blob("You are the overmind!"))
to_chat(src, span_blobannounce("You are the overmind!"))
if(!placed && autoplace_max_time <= world.time)
to_chat(src, span_boldannounce("You will automatically place your blob core in [DisplayTimeText(autoplace_max_time - world.time)]."))
to_chat(src, span_boldannounce("You [manualplace_min_time ? "will be able to":"can"] manually place your blob core by pressing the Place Blob Core button in the bottom right corner of the screen."))
@@ -257,9 +276,11 @@ GLOBAL_LIST_EMPTY(blob_nodes)
return FALSE
var/current_health = round((blob_core.get_integrity() / blob_core.max_integrity) * 100)
hud_used.healths.maptext = MAPTEXT("<div align='center' valign='middle' style='position:relative; top:0px; left:6px'><font color='#82ed00'>[current_health]%</font></div>")
for(var/mob/living/simple_animal/hostile/blob/blobbernaut/blobbernaut in blob_mobs)
if(blobbernaut.hud_used && blobbernaut.hud_used.blobpwrdisplay)
blobbernaut.hud_used.blobpwrdisplay.maptext = MAPTEXT("<div align='center' valign='middle' style='position:relative; top:0px; left:6px'><font color='#82ed00'>[current_health]%</font></div>")
for(var/mob/living/basic/blob_minion/blobbernaut/blobbernaut in blob_mobs)
var/datum/hud/using_hud = blobbernaut.hud_used
if(!using_hud?.blobpwrdisplay)
continue
using_hud.blobpwrdisplay.maptext = MAPTEXT("<div align='center' valign='middle' style='position:relative; top:0px; left:6px'><font color='#82ed00'>[current_health]%</font></div>")
/mob/camera/blob/proc/add_points(points)
blob_points = clamp(blob_points + points, 0, max_blob_points)
@@ -291,14 +312,8 @@ GLOBAL_LIST_EMPTY(blob_nodes)
src.log_talk(message, LOG_SAY)
var/message_a = say_quote(message)
var/rendered = span_big("<font color=\"#EE4000\"><b>\[Blob Telepathy\] [name](<font color=\"[blobstrain.color]\">[blobstrain.name]</font>)</b> [message_a]</font>")
for(var/mob/M in GLOB.mob_list)
if(isovermind(M) || isblobmonster(M))
to_chat(M, rendered)
if(isobserver(M))
var/link = FOLLOW_LINK(M, src)
to_chat(M, "[link] [rendered]")
var/rendered = span_big(span_blob("<b>\[Blob Telepathy\] [name](<font color=\"[blobstrain.color]\">[blobstrain.name]</font>)</b> [message_a]"))
blob_telepathy(rendered, src)
/mob/camera/blob/blob_act(obj/structure/blob/B)
return
@@ -324,8 +339,8 @@ GLOBAL_LIST_EMPTY(blob_nodes)
else
return FALSE
else
var/area/A = get_area(NewLoc)
if(isgroundlessturf(NewLoc) || istype(A, /area/shuttle)) //if unplaced, can't go on shuttles or goundless tiles
var/area/check_area = get_area(NewLoc)
if(isgroundlessturf(NewLoc) || istype(check_area, /area/shuttle)) //if unplaced, can't go on shuttles or groundless tiles
return FALSE
forceMove(NewLoc)
return TRUE
+17 -29
View File
@@ -196,38 +196,25 @@
var/list/mob/dead/observer/candidates = poll_ghost_candidates("Do you want to play as a [blobstrain.name] blobbernaut?", ROLE_BLOB, ROLE_BLOB, 50)
factory.is_creating_blobbernaut = FALSE
if(!length(candidates))
to_chat(src, span_warning("You could not conjure a sentience for your blobbernaut. Your points have been refunded. Try again later."))
add_points(BLOBMOB_BLOBBERNAUT_RESOURCE_COST)
factory.blobbernaut = null //players must answer rapidly
factory.assign_blobbernaut(null)
return FALSE
factory.modify_max_integrity(initial(factory.max_integrity) * 0.25) //factories that produced a blobbernaut have much lower health
factory.update_appearance()
factory.visible_message(span_warning("<b>The blobbernaut [pick("rips", "tears", "shreds")] its way out of the factory blob!</b>"))
playsound(factory.loc, 'sound/effects/splat.ogg', 50, TRUE)
var/mob/living/simple_animal/hostile/blob/blobbernaut/blobber = new /mob/living/simple_animal/hostile/blob/blobbernaut(get_turf(factory))
flick("blobbernaut_produce", blobber)
factory.blobbernaut = blobber
blobber.factory = factory
blobber.overmind = src
blobber.update_icons()
blobber.adjustHealth(blobber.maxHealth * 0.5)
blob_mobs += blobber
var/mob/living/basic/blob_minion/blobbernaut/minion/blobber = new(get_turf(factory))
assume_direct_control(blobber)
factory.assign_blobbernaut(blobber)
var/mob/dead/observer/player = pick(candidates)
blobber.key = player.key
blobber.assign_key(player.key, blobstrain)
RegisterSignal(blobber, COMSIG_HOSTILE_POST_ATTACKINGTARGET, PROC_REF(on_blobbernaut_attacked))
SEND_SOUND(blobber, sound('sound/effects/blobattack.ogg'))
SEND_SOUND(blobber, sound('sound/effects/attackblob.ogg'))
to_chat(blobber, span_infoplain("You are powerful, hard to kill, and slowly regenerate near nodes and cores, [span_cultlarge("but will slowly die if not near the blob")] or if the factory that made you is killed."))
to_chat(blobber, span_infoplain("You can communicate with other blobbernauts and overminds <b>telepathically</b> by attempting to speak normally"))
to_chat(blobber, span_infoplain("Your overmind's blob reagent is: <b><font color=\"[blobstrain.color]\">[blobstrain.name]</b></font>!"))
to_chat(blobber, span_infoplain("The <b><font color=\"[blobstrain.color]\">[blobstrain.name]</b></font> reagent [blobstrain.shortdesc ? "[blobstrain.shortdesc]" : "[blobstrain.description]"]"))
/// When one of our boys attacked something, we sometimes want to perform extra effects
/mob/camera/blob/proc/on_blobbernaut_attacked(mob/living/basic/blobbynaut, atom/target, success)
SIGNAL_HANDLER
if (!success)
return
blobstrain.blobbernaut_attack(target, blobbynaut)
/** Moves the core */
/mob/camera/blob/proc/relocate_core()
@@ -358,10 +345,11 @@
var/list/surrounding_turfs = TURF_NEIGHBORS(tile)
if(!length(surrounding_turfs))
return FALSE
for(var/mob/living/simple_animal/hostile/blob/blobspore/spore as anything in blob_mobs)
if(isturf(spore.loc) && get_dist(spore, tile) <= 35 && !spore.key)
spore.LoseTarget()
spore.Goto(pick(surrounding_turfs), spore.move_to_delay)
for(var/mob/living/basic/blob_mob as anything in blob_mobs)
if(!isturf(blob_mob.loc) || get_dist(blob_mob, tile) > 35 || blob_mob.key)
continue
blob_mob.ai_controller.clear_blackboard_key(BB_BASIC_MOB_CURRENT_TARGET)
blob_mob.ai_controller.set_blackboard_key(BB_TRAVEL_DESTINATION, pick(surrounding_turfs))
/** Opens the reroll menu to change strains */
/mob/camera/blob/proc/strain_reroll()
@@ -129,13 +129,13 @@
return FALSE //oh no we failed
/obj/structure/blob/proc/ConsumeTile()
for(var/atom/A in loc)
if(!A.can_blob_attack())
for(var/atom/thing in loc)
if(!thing.can_blob_attack())
continue
if(isliving(A) && overmind && !isblobmonster(A)) // Make sure to inject strain-reagents with automatic attacks when needed.
overmind.blobstrain.attack_living(A)
if(isliving(thing) && overmind && !HAS_TRAIT(thing, TRAIT_BLOB_ALLY)) // Make sure to inject strain-reagents with automatic attacks when needed.
overmind.blobstrain.attack_living(thing)
continue // Don't smack them twice though
A.blob_act(src)
thing.blob_act(src)
if(iswallturf(loc))
loc.blob_act(src) //don't ask how a wall got on top of the core, just eat it
@@ -10,12 +10,12 @@
armor_type = /datum/armor/structure_blob/factory
///How many spores this factory can have.
var/max_spores = BLOB_FACTORY_MAX_SPORES
///The list of spores
var/list/spores = list()
///The list of spores and zombies
var/list/spores_and_zombies = list()
COOLDOWN_DECLARE(spore_delay)
var/spore_cooldown = BLOBMOB_SPORE_SPAWN_COOLDOWN
///Its Blobbernaut, if it has spawned any.
var/mob/living/simple_animal/hostile/blob/blobbernaut/blobbernaut
var/mob/living/basic/blob_minion/blobbernaut/minion/blobbernaut
///Used in blob/powers.dm, checks if it's already trying to spawn a blobbernaut to prevent issues.
var/is_creating_blobbernaut = FALSE
@@ -32,15 +32,8 @@
overmind.factory_blobs += src
/obj/structure/blob/special/factory/Destroy()
for(var/mob/living/simple_animal/hostile/blob/blobspore/spore in spores)
to_chat(spore, span_userdanger("Your factory was destroyed! You can no longer sustain yourself."))
spore.death()
spores = null
if(blobbernaut)
blobbernaut.factory = null
to_chat(blobbernaut, span_userdanger("Your factory was destroyed! You feel yourself dying!"))
blobbernaut.throw_alert("nofactory", /atom/movable/screen/alert/nofactory)
blobbernaut = null
spores_and_zombies = null
blobbernaut = null
if(overmind)
overmind.factory_blobs -= src
return ..()
@@ -49,13 +42,57 @@
. = ..()
if(blobbernaut)
return
if(spores.len >= max_spores)
if(length(spores_and_zombies) >= max_spores)
return
if(!COOLDOWN_FINISHED(src, spore_delay))
return
COOLDOWN_START(src, spore_delay, spore_cooldown)
var/mob/living/simple_animal/hostile/blob/blobspore/BS = new (loc, src)
if(overmind) //if we don't have an overmind, we don't need to do anything but make a spore
BS.overmind = overmind
BS.update_icons()
overmind.blob_mobs.Add(BS)
var/mob/living/basic/blob_minion/created_spore = (overmind) ? overmind.create_spore(loc) : new(loc)
register_mob(created_spore)
RegisterSignal(created_spore, COMSIG_BLOB_ZOMBIFIED, PROC_REF(on_zombie_created))
/// Tracks the existence of a mob in our mobs list
/obj/structure/blob/special/factory/proc/register_mob(mob/living/basic/blob_minion/blob_mob)
spores_and_zombies |= blob_mob
blob_mob.link_to_factory(src)
RegisterSignal(blob_mob, COMSIG_LIVING_DEATH, PROC_REF(on_spore_died))
RegisterSignal(blob_mob, COMSIG_QDELETING, PROC_REF(on_spore_lost))
/// When a spore or zombie dies reset our spawn cooldown so we don't instantly replace it
/obj/structure/blob/special/factory/proc/on_spore_died(mob/living/dead_spore)
SIGNAL_HANDLER
COOLDOWN_START(src, spore_delay, spore_cooldown)
/// When a spore is deleted remove it from our list
/obj/structure/blob/special/factory/proc/on_spore_lost(mob/living/dead_spore)
SIGNAL_HANDLER
spores_and_zombies -= dead_spore
/// When a spore makes a zombie add it to our mobs list
/obj/structure/blob/special/factory/proc/on_zombie_created(mob/living/spore, mob/living/zombie)
SIGNAL_HANDLER
register_mob(zombie)
/// Produce a blobbernaut
/obj/structure/blob/special/factory/proc/assign_blobbernaut(mob/living/new_naut)
is_creating_blobbernaut = FALSE
if (isnull(new_naut))
return
modify_max_integrity(initial(max_integrity) * 0.25) //factories that produced a blobbernaut have much lower health
visible_message(span_boldwarning("The blobbernaut [pick("rips", "tears", "shreds")] its way out of the factory blob!"))
playsound(loc, 'sound/effects/splat.ogg', 50, TRUE)
blobbernaut = new_naut
blobbernaut.link_to_factory(src)
RegisterSignals(new_naut, list(COMSIG_QDELETING, COMSIG_LIVING_DEATH), PROC_REF(on_blobbernaut_death))
update_appearance(UPDATE_ICON)
/// When our brave soldier dies, reset our max integrity
/obj/structure/blob/special/factory/proc/on_blobbernaut_death(mob/living/death_naut)
SIGNAL_HANDLER
if (isnull(blobbernaut) || blobbernaut != death_naut)
return
blobbernaut = null
max_integrity = initial(max_integrity)
update_appearance(UPDATE_ICON)
+1 -1
View File
@@ -10,4 +10,4 @@
/datum/round_event/wizard/blobies/start()
for(var/mob/living/carbon/human/H in GLOB.dead_mob_list)
new /mob/living/simple_animal/hostile/blob/blobspore(H.loc)
new /mob/living/basic/blob_minion/spore/minion(H.loc) // Creates zombies which ghosts can control
@@ -165,6 +165,9 @@
return TRUE
/mob/living/basic/blob_act(obj/structure/blob/attacking_blob)
. = ..()
if (!.)
return
apply_damage(20, damagetype = BRUTE)
/mob/living/basic/do_attack_animation(atom/attacked_atom, visual_effect_icon, used_item, no_effect)
@@ -0,0 +1,51 @@
/**
* Extremely simple AI, this isn't a very smart boy
* Only notable quirk is that it uses JPS movement, simple avoidance would fail to realise it can path through blobs
*/
/datum/ai_controller/basic_controller/blobbernaut
blackboard = list(
BB_TARGETTING_DATUM = new /datum/targetting_datum/basic/attack_until_dead,
)
ai_movement = /datum/ai_movement/jps
idle_behavior = /datum/idle_behavior/idle_random_walk
planning_subtrees = list(
/datum/ai_planning_subtree/simple_find_target,
/datum/ai_planning_subtree/attack_obstacle_in_path,
/datum/ai_planning_subtree/basic_melee_attack_subtree,
)
/**
* Move to a point designated by the overmind, otherwise just slap people nearby
*/
/datum/ai_controller/basic_controller/blob_zombie
blackboard = list(
BB_TARGETTING_DATUM = new /datum/targetting_datum/basic/attack_until_dead,
)
ai_movement = /datum/ai_movement/jps
idle_behavior = /datum/idle_behavior/idle_random_walk
planning_subtrees = list(
/datum/ai_planning_subtree/travel_to_point/and_clear_target,
/datum/ai_planning_subtree/simple_find_target,
/datum/ai_planning_subtree/attack_obstacle_in_path,
/datum/ai_planning_subtree/basic_melee_attack_subtree,
)
/**
* As blob zombie but will prioritise attacking corpses to zombify them
*/
/datum/ai_controller/basic_controller/blob_spore
blackboard = list(
BB_TARGETTING_DATUM = new /datum/targetting_datum/basic/attack_until_dead,
)
ai_movement = /datum/ai_movement/jps
idle_behavior = /datum/idle_behavior/idle_random_walk
planning_subtrees = list(
/datum/ai_planning_subtree/find_and_hunt_target/corpses,
/datum/ai_planning_subtree/travel_to_point/and_clear_target,
/datum/ai_planning_subtree/simple_find_target,
/datum/ai_planning_subtree/attack_obstacle_in_path,
/datum/ai_planning_subtree/basic_melee_attack_subtree,
)
@@ -0,0 +1,37 @@
/// Root of shared behaviour for mobs spawned by blobs, is abstract and should not be spawned
/mob/living/basic/blob_minion
name = "Blob Error"
desc = "A nonfunctional fungal creature created by bad code or celestial mistake. Point and laugh."
icon = 'icons/mob/nonhuman-player/blob.dmi'
icon_state = "blob_head"
unique_name = TRUE
pass_flags = PASSBLOB
faction = list(ROLE_BLOB)
combat_mode = TRUE
bubble_icon = "blob"
speak_emote = null
habitable_atmos = list("min_oxy" = 0, "max_oxy" = 0, "min_plas" = 0, "max_plas" = 0, "min_co2" = 0, "max_co2" = 0, "min_n2" = 0, "max_n2" = 0)
minimum_survivable_temperature = 0
maximum_survivable_temperature = INFINITY
lighting_cutoff_red = 20
lighting_cutoff_green = 40
lighting_cutoff_blue = 30
initial_language_holder = /datum/language_holder/empty
/mob/living/basic/blob_minion/Initialize(mapload)
. = ..()
add_traits(list(TRAIT_BLOB_ALLY, TRAIT_MUTE), INNATE_TRAIT)
AddComponent(/datum/component/blob_minion, on_strain_changed = CALLBACK(src, PROC_REF(on_strain_updated)))
/// Called when our blob overmind changes their variant, update some of our mob properties
/mob/living/basic/blob_minion/proc/on_strain_updated(mob/camera/blob/overmind, datum/blobstrain/new_strain)
return
/// Associates this mob with a specific blob factory node
/mob/living/basic/blob_minion/proc/link_to_factory(obj/structure/blob/special/factory/factory)
RegisterSignal(factory, COMSIG_QDELETING, PROC_REF(on_factory_destroyed))
/// Called when our factory is destroyed
/mob/living/basic/blob_minion/proc/on_factory_destroyed()
SIGNAL_HANDLER
to_chat(src, span_userdanger("Your factory was destroyed! You feel yourself dying!"))
@@ -0,0 +1,118 @@
/**
* A floating fungus which turns people into zombies and explodes into reagent clouds upon death.
*/
/mob/living/basic/blob_minion/spore
name = "blob spore"
desc = "A floating, fragile spore."
icon = 'icons/mob/nonhuman-player/blob.dmi'
icon_state = "blobpod"
icon_living = "blobpod"
health_doll_icon = "blobpod"
health = BLOBMOB_SPORE_HEALTH
maxHealth = BLOBMOB_SPORE_HEALTH
verb_say = "psychically pulses"
verb_ask = "psychically probes"
verb_exclaim = "psychically yells"
verb_yell = "psychically screams"
melee_damage_lower = BLOBMOB_SPORE_DMG_LOWER
melee_damage_upper = BLOBMOB_SPORE_DMG_UPPER
obj_damage = 0
attack_verb_continuous = "batters"
attack_verb_simple = "batter"
attack_sound = 'sound/weapons/genhit1.ogg'
death_message = "explodes into a cloud of gas!"
gold_core_spawnable = HOSTILE_SPAWN
basic_mob_flags = DEL_ON_DEATH
ai_controller = /datum/ai_controller/basic_controller/blob_spore
/// Size of cloud produced from a dying spore
var/death_cloud_size = 1
/// Type of mob to create
var/mob/living/zombie_type = /mob/living/basic/blob_minion/zombie
/mob/living/basic/blob_minion/spore/Initialize(mapload)
. = ..()
AddElement(/datum/element/simple_flying)
AddElement(/datum/element/swabable, CELL_LINE_TABLE_BLOBSPORE, CELL_VIRUS_TABLE_GENERIC_MOB, 1, 5)
/mob/living/basic/blob_minion/spore/death(gibbed)
. = ..()
death_burst()
/mob/living/basic/blob_minion/spore/on_factory_destroyed()
death()
/// Create an explosion of spores on death
/mob/living/basic/blob_minion/spore/proc/death_burst()
do_chem_smoke(range = death_cloud_size, holder = src, location = get_turf(src), reagent_type = /datum/reagent/toxin/spore)
/mob/living/basic/blob_minion/spore/melee_attack(mob/living/carbon/human/target, list/modifiers, ignore_cooldown)
. = ..()
if (!ishuman(target) || target.stat != DEAD)
return
zombify(target)
/// Become a zombie
/mob/living/basic/blob_minion/spore/proc/zombify(mob/living/carbon/human/target)
visible_message(span_warning("The corpse of [target.name] suddenly rises!"))
var/mob/living/basic/blob_minion/zombie/blombie = change_mob_type(zombie_type, loc, new_name = initial(zombie_type.name))
blombie.set_name()
if (istype(blombie)) // In case of badmin
blombie.consume_corpse(target)
SEND_SIGNAL(src, COMSIG_BLOB_ZOMBIFIED, blombie)
qdel(src)
/// Variant of the blob spore which is actually spawned by blob factories
/mob/living/basic/blob_minion/spore/minion
gold_core_spawnable = NO_SPAWN
zombie_type = /mob/living/basic/blob_minion/zombie/controlled
/// We die if we leave the same turf as this z level
var/turf/z_turf
/mob/living/basic/blob_minion/spore/minion/Initialize(mapload)
. = ..()
RegisterSignal(src, COMSIG_MOVABLE_Z_CHANGED, PROC_REF(on_z_changed))
/// When we z-move check that we're on the same z level as our factory was
/mob/living/basic/blob_minion/spore/minion/proc/on_z_changed()
SIGNAL_HANDLER
if (isnull(z_turf))
return
if (!is_valid_z_level(get_turf(src), z_turf))
death()
/// Mark the turf we need to track from our factory
/mob/living/basic/blob_minion/spore/minion/link_to_factory(obj/structure/blob/special/factory/factory)
. = ..()
z_turf = get_turf(factory)
/// If the blob changes to distributed neurons then you can control the spores
/mob/living/basic/blob_minion/spore/minion/on_strain_updated(mob/camera/blob/overmind, datum/blobstrain/new_strain)
if (istype(new_strain, /datum/blobstrain/reagent/distributed_neurons))
AddComponent(\
/datum/component/ghost_direct_control,\
ban_type = ROLE_BLOB_INFECTION,\
poll_candidates = TRUE,\
poll_ignore_key = POLL_IGNORE_BLOB,\
)
else
qdel(GetComponent(/datum/component/ghost_direct_control))
/mob/living/basic/blob_minion/spore/minion/death_burst()
return // This behaviour is superceded by the overmind's intervention
/// Weakened spore spawned by distributed neurons, can't zombify people and makes a teeny explosion
/mob/living/basic/blob_minion/spore/minion/weak
name = "fragile blob spore"
health = 15
maxHealth = 15
melee_damage_lower = 1
melee_damage_upper = 2
death_cloud_size = 0
/mob/living/basic/blob_minion/spore/minion/weak/zombify()
return
/mob/living/basic/blob_minion/spore/minion/weak/on_strain_updated()
return
@@ -0,0 +1,97 @@
/// A shambling mob made out of a crew member
/mob/living/basic/blob_minion/zombie
name = "blob zombie"
desc = "A shambling corpse animated by the blob."
icon_state = "zombie"
icon_living = "zombie"
health_doll_icon = "blobpod"
mob_biotypes = MOB_ORGANIC | MOB_HUMANOID
health = 70
maxHealth = 70
verb_say = "gurgles"
verb_ask = "demands"
verb_exclaim = "roars"
verb_yell = "bellows"
melee_damage_lower = 10
melee_damage_upper = 15
obj_damage = 20
attack_verb_continuous = "punches"
attack_verb_simple = "punch"
attack_sound = 'sound/weapons/genhit1.ogg'
death_message = "collapses to the ground!"
gold_core_spawnable = NO_SPAWN
basic_mob_flags = DEL_ON_DEATH
ai_controller = /datum/ai_controller/basic_controller/blob_zombie
/// The dead body we have inside
var/mob/living/carbon/human/corpse
/mob/living/basic/blob_minion/zombie/Initialize(mapload)
. = ..()
AddElement(/datum/element/swabable, CELL_LINE_TABLE_BLOBSPORE, CELL_VIRUS_TABLE_GENERIC_MOB, 1, 5)
/mob/living/basic/blob_minion/zombie/death(gibbed)
corpse?.forceMove(loc)
death_burst()
return ..()
/mob/living/basic/blob_minion/zombie/Exited(atom/movable/gone, direction)
. = ..()
if (gone != corpse)
return
corpse = null
death()
/mob/living/basic/blob_minion/zombie/Destroy()
QDEL_NULL(corpse)
return ..()
/mob/living/basic/blob_minion/zombie/on_factory_destroyed()
. = ..()
death()
/mob/living/basic/blob_minion/zombie/update_overlays()
. = ..()
copy_overlays(corpse, TRUE)
var/mutable_appearance/blob_head_overlay = mutable_appearance('icons/mob/nonhuman-player/blob.dmi', "blob_head")
blob_head_overlay.color = LAZYACCESS(atom_colours, FIXED_COLOUR_PRIORITY) || COLOR_WHITE
color = initial(color) // reversing what our component did lol, but we needed the value for the overlay
. += blob_head_overlay
/// Create an explosion of spores on death
/mob/living/basic/blob_minion/zombie/proc/death_burst()
do_chem_smoke(range = 0, holder = src, location = get_turf(src), reagent_type = /datum/reagent/toxin/spore)
/// Store a body so that we can drop it on death
/mob/living/basic/blob_minion/zombie/proc/consume_corpse(mob/living/carbon/human/new_corpse)
if(new_corpse.wear_suit)
maxHealth += new_corpse.get_armor_rating(MELEE)
health = maxHealth
new_corpse.set_facial_hairstyle("Shaved", update = FALSE)
new_corpse.set_hairstyle("Bald", update = TRUE)
new_corpse.forceMove(src)
corpse = new_corpse
update_appearance(UPDATE_ICON)
RegisterSignal(corpse, COMSIG_LIVING_REVIVE, PROC_REF(on_corpse_revived))
/// Dynamic changeling reentry
/mob/living/basic/blob_minion/zombie/proc/on_corpse_revived()
SIGNAL_HANDLER
visible_message(span_boldwarning("[src] bursts from the inside!"))
death()
/// Blob-created zombies will ping for player control when they make a zombie
/mob/living/basic/blob_minion/zombie/controlled
/mob/living/basic/blob_minion/zombie/controlled/consume_corpse(mob/living/carbon/human/new_corpse)
. = ..()
if (!isnull(client))
return
AddComponent(\
/datum/component/ghost_direct_control,\
ban_type = ROLE_BLOB_INFECTION,\
poll_candidates = TRUE,\
poll_ignore_key = POLL_IGNORE_BLOB,\
)
/mob/living/basic/blob_minion/zombie/controlled/death_burst()
return
@@ -0,0 +1,108 @@
/**
* Player-piloted brute mob. Mostly just a "move and click" kind of guy.
* Has a variant which takes damage when away from blob tiles
*/
/mob/living/basic/blob_minion/blobbernaut
name = "blobbernaut"
desc = "A hulking, mobile chunk of blobmass."
icon_state = "blobbernaut"
icon_living = "blobbernaut"
icon_dead = "blobbernaut_dead"
health = BLOBMOB_BLOBBERNAUT_HEALTH
maxHealth = BLOBMOB_BLOBBERNAUT_HEALTH
damage_coeff = list(BRUTE = 0.5, BURN = 1, TOX = 1, CLONE = 1, STAMINA = 0, OXY = 1)
melee_damage_lower = BLOBMOB_BLOBBERNAUT_DMG_SOLO_LOWER
melee_damage_upper = BLOBMOB_BLOBBERNAUT_DMG_SOLO_UPPER
obj_damage = BLOBMOB_BLOBBERNAUT_DMG_OBJ
attack_verb_continuous = "slams"
attack_verb_simple = "slam"
attack_sound = 'sound/effects/blobattack.ogg'
verb_say = "gurgles"
verb_ask = "demands"
verb_exclaim = "roars"
verb_yell = "bellows"
force_threshold = 10
pressure_resistance = 50
mob_size = MOB_SIZE_LARGE
hud_type = /datum/hud/living/blobbernaut
gold_core_spawnable = HOSTILE_SPAWN
ai_controller = /datum/ai_controller/basic_controller/blobbernaut
/mob/living/basic/blob_minion/blobbernaut/Initialize(mapload)
. = ..()
AddElement(/datum/element/swabable, CELL_LINE_TABLE_BLOBBERNAUT, CELL_VIRUS_TABLE_GENERIC_MOB, 1, 5)
/mob/living/basic/blob_minion/blobbernaut/death(gibbed)
flick("blobbernaut_death", src)
return ..()
/// This variant is the one actually spawned by blob factories, takes damage when away from blob tiles
/mob/living/basic/blob_minion/blobbernaut/minion
gold_core_spawnable = NO_SPAWN
/// Is our factory dead?
var/orphaned = FALSE
/mob/living/basic/blob_minion/blobbernaut/minion/Life(seconds_per_tick, times_fired)
. = ..()
if (!.)
return FALSE
var/damage_sources = 0
var/list/blobs_in_area = range(2, src)
if (!(locate(/obj/structure/blob) in blobs_in_area))
damage_sources++
if (orphaned)
damage_sources++
else
var/particle_colour = atom_colours[FIXED_COLOUR_PRIORITY] || COLOR_BLACK
if (locate(/obj/structure/blob/special/core) in blobs_in_area)
heal_overall_damage(maxHealth * BLOBMOB_BLOBBERNAUT_HEALING_CORE * seconds_per_tick)
var/obj/effect/temp_visual/heal/heal_effect = new /obj/effect/temp_visual/heal(get_turf(src))
heal_effect.color = particle_colour
if (locate(/obj/structure/blob/special/node) in blobs_in_area)
heal_overall_damage(maxHealth * BLOBMOB_BLOBBERNAUT_HEALING_NODE * seconds_per_tick)
var/obj/effect/temp_visual/heal/heal_effect = new /obj/effect/temp_visual/heal(get_turf(src))
heal_effect.color = particle_colour
if (damage_sources == 0)
return FALSE
// take 2.5% of max health as damage when not near the blob or if the naut has no factory, 5% if both
apply_damage(maxHealth * BLOBMOB_BLOBBERNAUT_HEALTH_DECAY * damage_sources * seconds_per_tick, damagetype = TOX) // We reduce brute damage
var/mutable_appearance/harming = mutable_appearance('icons/mob/nonhuman-player/blob.dmi', "nautdamage", MOB_LAYER + 0.01)
harming.appearance_flags = RESET_COLOR
harming.color = atom_colours[FIXED_COLOUR_PRIORITY] || COLOR_WHITE
harming.dir = dir
flick_overlay_view(harming, 0.8 SECONDS)
return TRUE
/// Called by the blob creation power to give us a mind and a basic task orientation
/mob/living/basic/blob_minion/blobbernaut/minion/proc/assign_key(ckey, datum/blobstrain/blobstrain)
key = ckey
flick("blobbernaut_produce", src)
health = maxHealth / 2 // Start out injured to encourage not beelining away from the blob
SEND_SOUND(src, sound('sound/effects/blobattack.ogg'))
SEND_SOUND(src, sound('sound/effects/attackblob.ogg'))
to_chat(src, span_infoplain("You are powerful, hard to kill, and slowly regenerate near nodes and cores, [span_cultlarge("but will slowly die if not near the blob")] or if the factory that made you is killed."))
to_chat(src, span_infoplain("You can communicate with other blobbernauts and overminds <b>telepathically</b> by attempting to speak normally"))
to_chat(src, span_infoplain("Your overmind's blob reagent is: <b><font color=\"[blobstrain.color]\">[blobstrain.name]</b></font>!"))
to_chat(src, span_infoplain("The <b><font color=\"[blobstrain.color]\">[blobstrain.name]</b></font> reagent [blobstrain.shortdesc ? "[blobstrain.shortdesc]" : "[blobstrain.description]"]"))
/// Set our attack damage based on blob's properties
/mob/living/basic/blob_minion/blobbernaut/minion/on_strain_updated(mob/camera/blob/overmind, datum/blobstrain/new_strain)
if (isnull(overmind))
melee_damage_lower = initial(melee_damage_lower)
melee_damage_upper = initial(melee_damage_upper)
attack_verb_continuous = initial(attack_verb_continuous)
return
melee_damage_lower = BLOBMOB_BLOBBERNAUT_DMG_LOWER
melee_damage_upper = BLOBMOB_BLOBBERNAUT_DMG_UPPER
attack_verb_continuous = new_strain.blobbernaut_message
/// Called by our factory to inform us that it's not going to support us financially any more
/mob/living/basic/blob_minion/blobbernaut/minion/on_factory_destroyed()
. = ..()
orphaned = TRUE
throw_alert("nofactory", /atom/movable/screen/alert/nofactory)
@@ -13,53 +13,33 @@
/datum/ai_planning_subtree/attack_obstacle_in_path,
/datum/ai_planning_subtree/basic_melee_attack_subtree,
/datum/ai_planning_subtree/sculpt_statues,
/datum/ai_planning_subtree/find_and_hunt_target/cannibalize,
/datum/ai_planning_subtree/find_and_hunt_target/corpses/ice_whelp,
/datum/ai_planning_subtree/burn_trees,
)
/datum/ai_planning_subtree/find_and_hunt_target/cannibalize
/datum/ai_planning_subtree/find_and_hunt_target/corpses/ice_whelp
target_key = BB_TARGET_CANNIBAL
hunting_behavior = /datum/ai_behavior/cannibalize
finding_behavior = /datum/ai_behavior/find_hunt_target/dragon_corpse
finding_behavior = /datum/ai_behavior/find_hunt_target/corpses/dragon_corpse
hunting_behavior = /datum/ai_behavior/hunt_target/unarmed_attack_target/dragon_cannibalise
hunt_targets = list(/mob/living/basic/mining/ice_whelp)
hunt_range = 10
/datum/ai_behavior/find_hunt_target/dragon_corpse
/datum/ai_behavior/find_hunt_target/corpses/dragon_corpse
/datum/ai_behavior/find_hunt_target/dragon_corpse/valid_dinner(mob/living/source, mob/living/dinner, radius)
if(dinner.stat != DEAD)
return FALSE
/datum/ai_behavior/find_hunt_target/corpses/dragon_corpse/valid_dinner(mob/living/source, mob/living/dinner, radius)
if(dinner.pulledby) //someone already got him before us
return FALSE
return ..()
return can_see(source, dinner, radius)
/datum/ai_behavior/cannibalize
/datum/ai_behavior/hunt_target/unarmed_attack_target/dragon_cannibalise
behavior_flags = AI_BEHAVIOR_REQUIRE_MOVEMENT | AI_BEHAVIOR_REQUIRE_REACH | AI_BEHAVIOR_CAN_PLAN_DURING_EXECUTION
/datum/ai_behavior/cannibalize/setup(datum/ai_controller/controller, target_key)
. = ..()
var/atom/target = controller.blackboard[target_key]
if(QDELETED(target))
return FALSE
set_movement_target(controller, target)
/datum/ai_behavior/cannibalize/perform(seconds_per_tick, datum/ai_controller/controller, target_key, attack_key)
. = ..()
var/mob/living/basic/living_pawn = controller.pawn
/datum/ai_behavior/hunt_target/unarmed_attack_target/dragon_cannibalise/perform(seconds_per_tick, datum/ai_controller/controller, target_key, attack_key)
var/mob/living/target = controller.blackboard[target_key]
if(QDELETED(target))
if(QDELETED(target) || target.stat != DEAD || target.pulledby) //we were too slow
finish_action(controller, FALSE)
return
if(target.stat != DEAD || target.pulledby) //we were too slow
finish_action(controller, FALSE)
return
living_pawn.melee_attack(target)
finish_action(controller, TRUE)
return ..()
/datum/ai_behavior/cannibalize/finish_action(datum/ai_controller/controller, succeeded, target_key)
. = ..()
+1 -1
View File
@@ -1479,6 +1479,7 @@
var/picked_animal = pick(
/mob/living/basic/bat,
/mob/living/basic/bear,
/mob/living/basic/blob_minion/blobbernaut,
/mob/living/basic/butterfly,
/mob/living/basic/carp,
/mob/living/basic/carp/magic,
@@ -1504,7 +1505,6 @@
/mob/living/basic/statue,
/mob/living/basic/stickman,
/mob/living/basic/stickman/dog,
/mob/living/simple_animal/hostile/blob/blobbernaut/independent,
/mob/living/simple_animal/hostile/gorilla,
/mob/living/simple_animal/hostile/megafauna/dragon/lesser,
/mob/living/simple_animal/hostile/retaliate/goat,
@@ -181,6 +181,7 @@
return ..()
/mob/living/fire_act()
. = ..()
adjust_fire_stacks(3)
ignite_mob()
@@ -1,101 +0,0 @@
//Do not spawn
/mob/living/simple_animal/hostile/blob
icon = 'icons/mob/nonhuman-player/blob.dmi'
pass_flags = PASSBLOB
faction = list(ROLE_BLOB)
bubble_icon = "blob"
speak_emote = null //so we use verb_yell/verb_say/etc
atmos_requirements = list("min_oxy" = 0, "max_oxy" = 0, "min_plas" = 0, "max_plas" = 0, "min_co2" = 0, "max_co2" = 0, "min_n2" = 0, "max_n2" = 0)
minbodytemp = 0
maxbodytemp = INFINITY
unique_name = 1
combat_mode = TRUE
// ... Blob colored lighting
lighting_cutoff_red = 20
lighting_cutoff_green = 40
lighting_cutoff_blue = 30
initial_language_holder = /datum/language_holder/empty
retreat_distance = null //! retreat doesn't obey pass_flags, so won't work on blob mobs.
/// Blob camera that controls the blob
var/mob/camera/blob/overmind = null
/// If this is related to anything else
var/independent = FALSE
/// The factory blob tile that generated this blob minion
var/obj/structure/blob/special/factory/factory
/mob/living/simple_animal/hostile/blob/update_icons()
if(overmind)
add_atom_colour(overmind.blobstrain.color, FIXED_COLOUR_PRIORITY)
else
remove_atom_colour(FIXED_COLOUR_PRIORITY)
/mob/living/simple_animal/hostile/blob/Initialize(mapload)
. = ..()
if(!independent) //no pulling people deep into the blob
remove_verb(src, /mob/living/verb/pulled)
else
pass_flags &= ~PASSBLOB
/mob/living/simple_animal/hostile/blob/death()
factory = null
if(overmind)
overmind.blob_mobs -= src
overmind = null
return ..()
/mob/living/simple_animal/hostile/blob/get_status_tab_items()
. = ..()
if(overmind)
. += "Blobs to Win: [overmind.blobs_legit.len]/[overmind.blobwincount]"
/mob/living/simple_animal/hostile/blob/blob_act(obj/structure/blob/B)
if(stat != DEAD && health < maxHealth)
for(var/unused in 1 to 2)
var/obj/effect/temp_visual/heal/heal_effect = new /obj/effect/temp_visual/heal(get_turf(src)) //hello yes you are being healed
if(overmind)
heal_effect.color = overmind.blobstrain.complementary_color
else
heal_effect.color = "#000000"
adjustHealth(-maxHealth*BLOBMOB_HEALING_MULTIPLIER)
/mob/living/simple_animal/hostile/blob/fire_act(exposed_temperature, exposed_volume)
..()
if(exposed_temperature)
adjustFireLoss(clamp(0.01 * exposed_temperature, 1, 5))
else
adjustFireLoss(5)
/mob/living/simple_animal/hostile/blob/CanAllowThrough(atom/movable/mover, border_dir)
. = ..()
if(istype(mover, /obj/structure/blob))
return TRUE
///override to use astar/JPS instead of walk_to so we can take our blob pass_flags into account.
/mob/living/simple_animal/hostile/blob/Goto(target, delay, minimum_distance)
if(prevent_goto_movement)
return FALSE
if(target == src.target)
approaching_target = TRUE
else
approaching_target = FALSE
SSmove_manager.jps_move(moving = src, chasing = target, delay = delay, repath_delay = 2 SECONDS, minimum_distance = minimum_distance, simulated_only = FALSE, skip_first = TRUE, timeout = 5 SECONDS, flags = MOVEMENT_LOOP_IGNORE_GLIDE)
return TRUE
/mob/living/simple_animal/hostile/blob/Process_Spacemove(movement_dir = 0, continuous_move = FALSE)
for(var/obj/structure/blob/blob in range(1, src))
return 1
return ..()
/mob/living/simple_animal/hostile/blob/say(message, bubble_type, list/spans = list(), sanitize = TRUE, datum/language/language = null, ignore_spam = FALSE, forced = null, filterproof = null, message_range = 7, datum/saymode/saymode = null)
if(sanitize)
message = trim(copytext_char(sanitize(message), 1, MAX_MESSAGE_LEN))
var/spanned_message = say_quote(message)
var/rendered = "<font color=\"#EE4000\"><b>\[Blob Telepathy\] [real_name]</b> [spanned_message]</font>"
for(var/creature in GLOB.mob_list)
if(isovermind(creature) || isblobmonster(creature))
to_chat(creature, rendered)
if(isobserver(creature))
var/link = FOLLOW_LINK(creature, src)
to_chat(creature, "[link] [rendered]")
@@ -1,109 +0,0 @@
/mob/living/simple_animal/hostile/blob/blobbernaut
name = "blobbernaut"
desc = "A hulking, mobile chunk of blobmass."
icon_state = "blobbernaut"
icon_living = "blobbernaut"
icon_dead = "blobbernaut_dead"
health = BLOBMOB_BLOBBERNAUT_HEALTH
maxHealth = BLOBMOB_BLOBBERNAUT_HEALTH
damage_coeff = list(BRUTE = 0.5, BURN = 1, TOX = 1, CLONE = 1, STAMINA = 0, OXY = 1)
melee_damage_lower = BLOBMOB_BLOBBERNAUT_DMG_SOLO_LOWER
melee_damage_upper = BLOBMOB_BLOBBERNAUT_DMG_SOLO_UPPER
obj_damage = BLOBMOB_BLOBBERNAUT_DMG_OBJ
attack_verb_continuous = "slams"
attack_verb_simple = "slam"
attack_sound = 'sound/effects/blobattack.ogg'
verb_say = "gurgles"
verb_ask = "demands"
verb_exclaim = "roars"
verb_yell = "bellows"
force_threshold = 10
pressure_resistance = 50
mob_size = MOB_SIZE_LARGE
hud_type = /datum/hud/living/blobbernaut
/mob/living/simple_animal/hostile/blob/blobbernaut/Initialize(mapload)
. = ..()
add_cell_sample()
/mob/living/simple_animal/hostile/blob/blobbernaut/mind_initialize()
. = ..()
if(independent | !overmind)
return
var/datum/antagonist/blob_minion/blobbernaut/naut = new(overmind)
mind.add_antag_datum(naut)
/mob/living/simple_animal/hostile/blob/blobbernaut/add_cell_sample()
AddElement(/datum/element/swabable, CELL_LINE_TABLE_BLOBBERNAUT, CELL_VIRUS_TABLE_GENERIC_MOB, 1, 5)
/mob/living/simple_animal/hostile/blob/blobbernaut/Life(seconds_per_tick = SSMOBS_DT, times_fired)
if(!..())
return FALSE
var/list/blobs_in_area = range(2, src)
if(independent)
return FALSE // strong independent blobbernaut that don't need no blob
var/damagesources = 0
if(!(locate(/obj/structure/blob) in blobs_in_area))
damagesources++
if(!factory)
damagesources++
else
if(locate(/obj/structure/blob/special/core) in blobs_in_area)
adjustHealth(-maxHealth*BLOBMOB_BLOBBERNAUT_HEALING_CORE * seconds_per_tick)
var/obj/effect/temp_visual/heal/heal_effect = new /obj/effect/temp_visual/heal(get_turf(src)) //hello yes you are being healed
if(overmind)
heal_effect.color = overmind.blobstrain.complementary_color
else
heal_effect.color = "#000000"
if(locate(/obj/structure/blob/special/node) in blobs_in_area)
adjustHealth(-maxHealth*BLOBMOB_BLOBBERNAUT_HEALING_NODE * seconds_per_tick)
var/obj/effect/temp_visual/heal/heal_effect = new /obj/effect/temp_visual/heal(get_turf(src))
if(overmind)
heal_effect.color = overmind.blobstrain.complementary_color
else
heal_effect.color = "#000000"
if(!damagesources)
return FALSE
adjustHealth(maxHealth * BLOBMOB_BLOBBERNAUT_HEALTH_DECAY * damagesources * seconds_per_tick) //take 2.5% of max health as damage when not near the blob or if the naut has no factory, 5% if both
var/mutable_appearance/healing = mutable_appearance('icons/mob/nonhuman-player/blob.dmi', "nautdamage", MOB_LAYER+0.01)
healing.appearance_flags = RESET_COLOR
if(overmind)
healing.color = overmind.blobstrain.complementary_color
flick_overlay_view(healing, 0.8 SECONDS)
/mob/living/simple_animal/hostile/blob/blobbernaut/AttackingTarget()
. = ..()
if(. && isliving(target) && overmind)
overmind.blobstrain.blobbernaut_attack(target, src)
/mob/living/simple_animal/hostile/blob/blobbernaut/update_icons()
..()
if(overmind) //if we have an overmind, we're doing chemical reactions instead of pure damage
melee_damage_lower = BLOBMOB_BLOBBERNAUT_DMG_LOWER
melee_damage_upper = BLOBMOB_BLOBBERNAUT_DMG_UPPER
attack_verb_continuous = overmind.blobstrain.blobbernaut_message
else
melee_damage_lower = initial(melee_damage_lower)
melee_damage_upper = initial(melee_damage_upper)
attack_verb_continuous = initial(attack_verb_continuous)
/mob/living/simple_animal/hostile/blob/blobbernaut/death(gibbed)
if(factory)
factory.blobbernaut = null //remove this blobbernaut from its factory
factory.max_integrity = initial(factory.max_integrity)
flick("blobbernaut_death", src)
return ..()
/mob/living/simple_animal/hostile/blob/blobbernaut/independent
independent = TRUE
gold_core_spawnable = HOSTILE_SPAWN
@@ -1,168 +0,0 @@
/mob/living/simple_animal/hostile/blob/blobspore
name = "blob spore"
desc = "A floating, fragile spore."
icon_state = "blobpod"
icon_living = "blobpod"
health_doll_icon = "blobpod"
health = BLOBMOB_SPORE_HEALTH
maxHealth = BLOBMOB_SPORE_HEALTH
verb_say = "psychically pulses"
verb_ask = "psychically probes"
verb_exclaim = "psychically yells"
verb_yell = "psychically screams"
melee_damage_lower = BLOBMOB_SPORE_DMG_LOWER
melee_damage_upper = BLOBMOB_SPORE_DMG_UPPER
environment_smash = ENVIRONMENT_SMASH_NONE
obj_damage = 0
attack_verb_continuous = "hits"
attack_verb_simple = "hit"
attack_sound = 'sound/weapons/genhit1.ogg'
del_on_death = TRUE
death_message = "explodes into a cloud of gas!"
gold_core_spawnable = NO_SPAWN //gold slime cores should only spawn the independent subtype
/// Size of cloud produced from a dying spore
var/death_cloud_size = 1
/// The attached person
var/mob/living/carbon/human/corpse
/// If this is attached to a person
var/is_zombie = FALSE
/// Whether or not this is a fragile spore from Distributed Neurons
var/is_weak = FALSE
/mob/living/simple_animal/hostile/blob/blobspore/Initialize(mapload, obj/structure/blob/special/linked_node)
. = ..()
AddElement(/datum/element/simple_flying)
if(!istype(linked_node))
return
factory = linked_node
factory.spores += src
if(linked_node.overmind && istype(linked_node.overmind.blobstrain, /datum/blobstrain/reagent/distributed_neurons) && !istype(src, /mob/living/simple_animal/hostile/blob/blobspore/weak))
notify_ghosts("A controllable spore has been created in \the [get_area(src)].", source = src, action = NOTIFY_ORBIT, flashwindow = FALSE, header = "Sentient Spore Created")
add_cell_sample()
/mob/living/simple_animal/hostile/blob/blobspore/mind_initialize()
. = ..()
if(independent || !overmind)
return FALSE
var/datum/antagonist/blob_minion/blob_zombie/zombie = new(overmind)
mind.add_antag_datum(zombie)
/mob/living/simple_animal/hostile/blob/blobspore/Life(seconds_per_tick = SSMOBS_DT, times_fired)
if(!is_zombie && isturf(loc))
for(var/mob/living/carbon/human/target in view(src,1)) //Only for corpse right next to/on same tile
if(!is_weak && target.stat == DEAD)
zombify(target)
break
if(factory && !is_valid_z_level(get_turf(src), get_turf(factory)))
death()
return ..()
/mob/living/simple_animal/hostile/blob/blobspore/attack_ghost(mob/user)
. = ..()
if(.)
return
humanize_pod(user)
/mob/living/simple_animal/hostile/blob/blobspore/death(gibbed)
// On death, create a small smoke of harmful gas (s-Acid)
var/datum/effect_system/fluid_spread/smoke/chem/spores = new
var/turf/location = get_turf(src)
// Create the reagents to put into the air
create_reagents(10)
if(overmind?.blobstrain)
overmind.blobstrain.on_sporedeath(src)
else
reagents.add_reagent(/datum/reagent/toxin/spore, 10)
// Attach the smoke spreader and setup/start it.
spores.attach(location)
spores.set_up(death_cloud_size, holder = src, location = location, carry = reagents, silent = TRUE)
spores.start()
if(factory)
factory.spore_delay = world.time + factory.spore_cooldown //put the factory on cooldown
return ..()
/mob/living/simple_animal/hostile/blob/blobspore/death()
if(factory)
factory.spores -= src
corpse?.forceMove(loc)
corpse = null
return ..()
/mob/living/simple_animal/hostile/blob/blobspore/update_icons()
if(overmind)
add_atom_colour(overmind.blobstrain.complementary_color, FIXED_COLOUR_PRIORITY)
else
remove_atom_colour(FIXED_COLOUR_PRIORITY)
if(!is_zombie)
return FALSE
copy_overlays(corpse, TRUE)
var/mutable_appearance/blob_head_overlay = mutable_appearance('icons/mob/nonhuman-player/blob.dmi', "blob_head")
if(overmind)
blob_head_overlay.color = overmind.blobstrain.complementary_color
color = initial(color) // looks better.
add_overlay(blob_head_overlay)
/mob/living/simple_animal/hostile/blob/blobspore/add_cell_sample()
AddElement(/datum/element/swabable, CELL_LINE_TABLE_BLOBSPORE, CELL_VIRUS_TABLE_GENERIC_MOB, 1, 5)
/mob/living/simple_animal/hostile/blob/blobspore/independent
gold_core_spawnable = HOSTILE_SPAWN
independent = TRUE
/mob/living/simple_animal/hostile/blob/blobspore/weak
name = "fragile blob spore"
health = 15
maxHealth = 15
melee_damage_lower = 1
melee_damage_upper = 2
death_cloud_size = 0
is_weak = TRUE
/** Ghost control a blob zombie */
/mob/living/simple_animal/hostile/blob/blobspore/proc/humanize_pod(mob/user)
if((!overmind || istype(src, /mob/living/simple_animal/hostile/blob/blobspore/weak) || !istype(overmind.blobstrain, /datum/blobstrain/reagent/distributed_neurons)) && !is_zombie)
return FALSE
if(key || stat)
return FALSE
var/pod_ask = tgui_alert(usr, "Are you bulbous enough?", "Blob Spore", list("Yes", "No"))
if(pod_ask != "Yes" || QDELETED(src))
return FALSE
if(key)
to_chat(user, span_warning("Someone else already took this spore!"))
return FALSE
key = user.key
log_message("took control of [name].", LOG_GAME)
/** Zombifies a dead mob, turning it into a blob zombie */
/mob/living/simple_animal/hostile/blob/blobspore/proc/zombify(mob/living/carbon/human/target)
is_zombie = 1
if(target.wear_suit)
maxHealth += target.get_armor_rating(MELEE) // That zombie's got armor, I want armor!
maxHealth += 40
health = maxHealth
name = "blob zombie"
desc = "A shambling corpse animated by the blob."
mob_biotypes |= MOB_HUMANOID
melee_damage_lower += 8
melee_damage_upper += 11
obj_damage = 20 // now that it has a corpse to puppet, it can properly attack structures
environment_smash = ENVIRONMENT_SMASH_STRUCTURES
movement_type = GROUND
death_cloud_size = 0
icon = target.icon
icon_state = "zombie"
target.set_facial_hairstyle("Shaved", update = FALSE)
target.set_hairstyle("Bald", update = TRUE)
target.forceMove(src)
corpse = target
update_icons()
visible_message(span_warning("The corpse of [target.name] suddenly rises!"))
if(!key)
notify_ghosts("\A [src] has been created in \the [get_area(src)].", source = src, action = NOTIFY_ORBIT, flashwindow = FALSE, header = "Blob Zombie Created")
@@ -66,7 +66,7 @@
else
desired_mob.key = key
SEND_SIGNAL(src, COMSIG_MOB_CHANGED_TYPE)
SEND_SIGNAL(src, COMSIG_MOB_CHANGED_TYPE, desired_mob)
if(delete_old_mob)
QDEL_IN(src, 1)
return desired_mob
@@ -263,7 +263,7 @@
/datum/reagent/medicine/psicodine = -2) //Blob zombies likely wouldn't appreciate psicodine so why this is here
virus_suspectibility = 0
resulting_atoms = list(/mob/living/simple_animal/hostile/blob/blobspore/independent = 2) //These are useless so we might as well spawn 2.
resulting_atoms = list(/mob/living/basic/blob_minion/spore = 2) //These are useless so we might as well spawn 2.
/datum/micro_organism/cell_line/blobbernaut
desc = "Blobular myocytes"
@@ -282,7 +282,7 @@
suppressive_reagents = list(/datum/reagent/consumable/tinlux = -6)
virus_suspectibility = 0
resulting_atoms = list(/mob/living/simple_animal/hostile/blob/blobbernaut/independent = 1)
resulting_atoms = list(/mob/living/basic/blob_minion/blobbernaut = 1)
/datum/micro_organism/cell_line/gelatinous_cube
desc = "Cubic ooze particles"
@@ -68,12 +68,6 @@
/mob/living/simple_animal/hostile/asteroid/polarbear,
/mob/living/simple_animal/hostile/asteroid/polarbear/lesser,
/mob/living/simple_animal/hostile/asteroid/wolf,
/mob/living/simple_animal/hostile/blob,
/mob/living/simple_animal/hostile/blob/blobbernaut,
/mob/living/simple_animal/hostile/blob/blobbernaut/independent,
/mob/living/simple_animal/hostile/blob/blobspore,
/mob/living/simple_animal/hostile/blob/blobspore/independent,
/mob/living/simple_animal/hostile/blob/blobspore/weak,
/mob/living/simple_animal/hostile/construct,
/mob/living/simple_animal/hostile/construct/artificer,
/mob/living/simple_animal/hostile/construct/artificer/angelic,
@@ -59,7 +59,7 @@
weak_against_armour = TRUE
/obj/projectile/beam/laser/plasma_glob/on_hit(atom/target, blocked)
if(istype(target, /obj/structure/blob) || istype(target, /mob/living/simple_animal/hostile/blob))
if(istype(target, /obj/structure/blob) || HAS_TRAIT(target, TRAIT_BLOB_ALLY))
damage = damage * 0.75
return ..()
+9 -3
View File
@@ -280,6 +280,7 @@
#include "code\__DEFINES\dcs\signals\signals_backpack.dm"
#include "code\__DEFINES\dcs\signals\signals_beam.dm"
#include "code\__DEFINES\dcs\signals\signals_bitrunning.dm"
#include "code\__DEFINES\dcs\signals\signals_blob.dm"
#include "code\__DEFINES\dcs\signals\signals_bot.dm"
#include "code\__DEFINES\dcs\signals\signals_camera.dm"
#include "code\__DEFINES\dcs\signals\signals_changeling.dm"
@@ -921,6 +922,7 @@
#include "code\datums\ai\basic_mobs\basic_subtrees\target_retaliate.dm"
#include "code\datums\ai\basic_mobs\basic_subtrees\targeted_mob_ability.dm"
#include "code\datums\ai\basic_mobs\basic_subtrees\tipped_subtree.dm"
#include "code\datums\ai\basic_mobs\basic_subtrees\travel_to_point.dm"
#include "code\datums\ai\basic_mobs\basic_subtrees\use_mob_ability.dm"
#include "code\datums\ai\basic_mobs\pet_commands\fetch.dm"
#include "code\datums\ai\basic_mobs\pet_commands\pet_command_planning.dm"
@@ -942,6 +944,7 @@
#include "code\datums\ai\hauntium\hauntium_subtrees.dm"
#include "code\datums\ai\hunting_behavior\hunting_behaviors.dm"
#include "code\datums\ai\hunting_behavior\hunting_cockroach.dm"
#include "code\datums\ai\hunting_behavior\hunting_corpses.dm"
#include "code\datums\ai\hunting_behavior\hunting_lights.dm"
#include "code\datums\ai\hunting_behavior\hunting_mouse.dm"
#include "code\datums\ai\idle_behaviors\_idle_behavior.dm"
@@ -1018,6 +1021,7 @@
#include "code\datums\components\basic_mob_attack_telegraph.dm"
#include "code\datums\components\basic_ranged_ready_overlay.dm"
#include "code\datums\components\beetlejuice.dm"
#include "code\datums\components\blob_minion.dm"
#include "code\datums\components\blood_walk.dm"
#include "code\datums\components\bloodysoles.dm"
#include "code\datums\components\boomerang.dm"
@@ -4412,6 +4416,11 @@
#include "code\modules\mob\living\basic\festivus_pole.dm"
#include "code\modules\mob\living\basic\health_adjustment.dm"
#include "code\modules\mob\living\basic\tree.dm"
#include "code\modules\mob\living\basic\blob_minions\blob_ai.dm"
#include "code\modules\mob\living\basic\blob_minions\blob_mob.dm"
#include "code\modules\mob\living\basic\blob_minions\blob_spore.dm"
#include "code\modules\mob\living\basic\blob_minions\blob_zombie.dm"
#include "code\modules\mob\living\basic\blob_minions\blobbernaut.dm"
#include "code\modules\mob\living\basic\clown\clown.dm"
#include "code\modules\mob\living\basic\clown\clown_ai.dm"
#include "code\modules\mob\living\basic\farm_animals\deer.dm"
@@ -4758,9 +4767,6 @@
#include "code\modules\mob\living\simple_animal\guardian\types\standard.dm"
#include "code\modules\mob\living\simple_animal\guardian\types\support.dm"
#include "code\modules\mob\living\simple_animal\hostile\alien.dm"
#include "code\modules\mob\living\simple_animal\hostile\blob.dm"
#include "code\modules\mob\living\simple_animal\hostile\blobbernaut.dm"
#include "code\modules\mob\living\simple_animal\hostile\blobspore.dm"
#include "code\modules\mob\living\simple_animal\hostile\dark_wizard.dm"
#include "code\modules\mob\living\simple_animal\hostile\heretic_monsters.dm"
#include "code\modules\mob\living\simple_animal\hostile\hostile.dm"
@@ -535,6 +535,10 @@ em {
}
.blob {
color: #ee4000;
}
.blobannounce {
color: #556b2f;
font-weight: bold;
font-size: 185%;
@@ -594,6 +594,10 @@ h2.alert {
}
.blob {
color: #ee4000;
}
.blobannounce {
color: #323f1c;
font-weight: bold;
font-size: 185%;