mirror of
https://github.com/Bubberstation/Bubberstation.git
synced 2026-08-30 16:47:13 +01:00
[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:
co-authored by
Jacquerel
Giz
parent
ca0e216440
commit
58c20a99fb
@@ -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)
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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>")
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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)
|
||||
|
||||
|
||||
@@ -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)
|
||||
@@ -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))
|
||||
|
||||
@@ -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
|
||||
@@ -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,
|
||||
|
||||
@@ -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()
|
||||
..()
|
||||
|
||||
@@ -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
|
||||
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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)
|
||||
. = ..()
|
||||
|
||||
@@ -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
@@ -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%;
|
||||
|
||||
Reference in New Issue
Block a user