diff --git a/_maps/RandomRuins/LavaRuins/lavaland_surface_pizzaparty.dmm b/_maps/RandomRuins/LavaRuins/lavaland_surface_pizzaparty.dmm index 72e83365717..00ddfa74d05 100644 --- a/_maps/RandomRuins/LavaRuins/lavaland_surface_pizzaparty.dmm +++ b/_maps/RandomRuins/LavaRuins/lavaland_surface_pizzaparty.dmm @@ -496,7 +496,7 @@ /turf/open/floor/carpet/blue/lavaland, /area/ruin/unpowered) "Fg" = ( -/mob/living/simple_animal/hostile/asteroid/goliath/beast, +/mob/living/basic/mining/goliath, /obj/effect/decal/cleanable/blood/old, /turf/open/misc/asteroid/basalt/lava_land_surface, /area/lavaland/surface/outdoors) diff --git a/_maps/map_files/Mining/Lavaland.dmm b/_maps/map_files/Mining/Lavaland.dmm index 0d8ea59bac9..9d372cd8e8d 100644 --- a/_maps/map_files/Mining/Lavaland.dmm +++ b/_maps/map_files/Mining/Lavaland.dmm @@ -4376,7 +4376,7 @@ /turf/open/floor/iron/smooth_edge, /area/mine/laborcamp/quarters) "zS" = ( -/mob/living/simple_animal/hostile/asteroid/goliath/beast, +/mob/living/basic/mining/goliath, /turf/open/misc/asteroid/basalt/lava_land_surface, /area/lavaland/surface/outdoors) "zU" = ( diff --git a/_maps/shuttles/emergency_meteor.dmm b/_maps/shuttles/emergency_meteor.dmm index a32adfb10ad..658c290b956 100644 --- a/_maps/shuttles/emergency_meteor.dmm +++ b/_maps/shuttles/emergency_meteor.dmm @@ -22,7 +22,7 @@ /turf/open/misc/asteroid, /area/shuttle/escape/meteor) "f" = ( -/mob/living/simple_animal/hostile/asteroid/goliath, +/mob/living/basic/mining/goliath/ancient, /turf/open/misc/asteroid, /area/shuttle/escape/meteor) "g" = ( diff --git a/code/__DEFINES/ai.dm b/code/__DEFINES/ai.dm index e337e12c383..38d337ff982 100644 --- a/code/__DEFINES/ai.dm +++ b/code/__DEFINES/ai.dm @@ -309,3 +309,8 @@ //Festivus AI keys /// Key where we store the charging apc ability #define BB_FESTIVE_APC "BB_festive_apc" + +/// Key where we store the tentacleing ability +#define BB_GOLIATH_TENTACLES "BB_goliath_tentacles" +/// Key where goliath stores a hole it wants to get into +#define BB_GOLIATH_HOLE_TARGET "BB_goliath_hole" diff --git a/code/__DEFINES/basic_mobs.dm b/code/__DEFINES/basic_mobs.dm index 8c544adb459..06a5c4443f9 100644 --- a/code/__DEFINES/basic_mobs.dm +++ b/code/__DEFINES/basic_mobs.dm @@ -1,7 +1,18 @@ #define BASIC_MOB_MAX_STAMINALOSS 200 ///Basic mob flags + +/// Delete mob upon death #define DEL_ON_DEATH (1<<0) +/// Rotate mob 180 degrees while it is dead #define FLIP_ON_DEATH (1<<1) +/// Mob remains dense while dead #define REMAIN_DENSE_WHILE_DEAD (1<<2) +/// Mob can be set on fire #define FLAMMABLE_MOB (1<<3) +/// Mob never takes damage from unarmed attacks +#define IMMUNE_TO_FISTS (1<<4) + +/// Temporary trait applied when an attack forecast animation has completed +#define TRAIT_BASIC_ATTACK_FORECAST "trait_basic_attack_forecast" +#define INTERACTION_BASIC_ATTACK_FORCEAST "interaction_basic_attack_forecast" diff --git a/code/__DEFINES/dcs/signals/signals_mining.dm b/code/__DEFINES/dcs/signals/signals_mining.dm new file mode 100644 index 00000000000..8dc3fe283f6 --- /dev/null +++ b/code/__DEFINES/dcs/signals/signals_mining.dm @@ -0,0 +1,6 @@ +/// Fired by a mob which has been grabbed by a goliath +#define COMSIG_GOLIATH_TENTACLED_GRABBED "comsig_goliath_tentacle_grabbed" +/// Fired by a goliath tentacle which is returning to the earth +#define COMSIG_GOLIATH_TENTACLE_RETRACTING "comsig_goliath_tentacle_retracting" +/// Fired by a mob which has triggered a brimdust explosion from itself (not the mobs that get hit) +#define COMSIG_BRIMDUST_EXPLOSION "comsig_brimdust_explosion" diff --git a/code/__DEFINES/traits.dm b/code/__DEFINES/traits.dm index 1706b093e27..cf2e77da6aa 100644 --- a/code/__DEFINES/traits.dm +++ b/code/__DEFINES/traits.dm @@ -614,6 +614,9 @@ Remember to update _globalvars/traits.dm if you're adding/removing/renaming trai #define TRAIT_VOIDSTORM_IMMUNE "voidstorm_immune" #define TRAIT_WEATHER_IMMUNE "weather_immune" //Immune to ALL weather effects. +/// Cannot be grabbed by goliath tentacles +#define TRAIT_TENTACLE_IMMUNE "tentacle_immune" + //non-mob traits /// Used for limb-based paralysis, where replacing the limb will fix it. #define TRAIT_PARALYSIS "paralysis" diff --git a/code/datums/ai/_ai_controller.dm b/code/datums/ai/_ai_controller.dm index 0345fccba0c..84b415b90f1 100644 --- a/code/datums/ai/_ai_controller.dm +++ b/code/datums/ai/_ai_controller.dm @@ -50,8 +50,6 @@ multiple modular subtrees with behaviors // Movement related things here ///Reference to the movement datum we use. Is a type on initialize but becomes a ref afterwards. var/datum/ai_movement/ai_movement = /datum/ai_movement/dumb - ///Cooldown until next movement - COOLDOWN_DECLARE(movement_cooldown) ///Delay between movements. This is on the controller so we can keep the movement datum singleton var/movement_delay = 0.1 SECONDS diff --git a/code/datums/ai/generic/find_and_set.dm b/code/datums/ai/generic/find_and_set.dm index 02f2980dd72..443febae2a0 100644 --- a/code/datums/ai/generic/find_and_set.dm +++ b/code/datums/ai/generic/find_and_set.dm @@ -8,6 +8,9 @@ /datum/ai_behavior/find_and_set/perform(seconds_per_tick, datum/ai_controller/controller, set_key, locate_path, search_range) . = ..() + var/atom/current_target = controller.blackboard[set_key] + if (!QDELETED(current_target)) + return var/find_this_thing = search_tactic(controller, locate_path, search_range) if(find_this_thing) controller.set_blackboard_key(set_key, find_this_thing) diff --git a/code/datums/components/basic_mob_attack_telegraph.dm b/code/datums/components/basic_mob_attack_telegraph.dm new file mode 100644 index 00000000000..53fec920e28 --- /dev/null +++ b/code/datums/components/basic_mob_attack_telegraph.dm @@ -0,0 +1,82 @@ +/** + * Delays outgoing attacks which are directed at mobs to give players time to get out of the way + */ +/datum/component/basic_mob_attack_telegraph + /// Time to wait before attack can complete + var/telegraph_duration + /// Overlay which we display over targets + var/mutable_appearance/target_overlay + /// Our current target, if we have one + var/mob/living/current_target + /// Callback executed when we start aiming at something + var/datum/callback/on_began_forecast + +/datum/component/basic_mob_attack_telegraph/Initialize( + telegraph_icon = 'icons/mob/telegraphing/telegraph.dmi', + telegraph_state = ATTACK_EFFECT_BITE, + telegraph_duration = 0.3 SECONDS, + datum/callback/on_began_forecast, +) + . = ..() + if (!isbasicmob(parent)) + return ELEMENT_INCOMPATIBLE + + target_overlay = mutable_appearance(telegraph_icon, telegraph_state) + src.telegraph_duration = telegraph_duration + src.on_began_forecast = on_began_forecast + +/datum/component/basic_mob_attack_telegraph/RegisterWithParent() + . = ..() + RegisterSignal(parent, COMSIG_HOSTILE_PRE_ATTACKINGTARGET, PROC_REF(on_attack)) + +/datum/component/basic_mob_attack_telegraph/UnregisterFromParent() + if (current_target) + forget_target(current_target) + QDEL_NULL(target_overlay) + REMOVE_TRAIT(parent, TRAIT_BASIC_ATTACK_FORECAST, REF(src)) + UnregisterSignal(parent, COMSIG_HOSTILE_PRE_ATTACKINGTARGET) + return ..() + +/// When we attempt to attack, check if it is allowed +/datum/component/basic_mob_attack_telegraph/proc/on_attack(mob/living/basic/source, atom/target) + SIGNAL_HANDLER + if (!isliving(target)) + return + if (HAS_TRAIT_FROM(source, TRAIT_BASIC_ATTACK_FORECAST, REF(src))) + REMOVE_TRAIT(source, TRAIT_BASIC_ATTACK_FORECAST, REF(src)) + return + + if (!DOING_INTERACTION(source, INTERACTION_BASIC_ATTACK_FORCEAST)) + INVOKE_ASYNC(src, PROC_REF(delayed_attack), source, target) + return COMPONENT_HOSTILE_NO_ATTACK + +/// Perform an attack after a delay +/datum/component/basic_mob_attack_telegraph/proc/delayed_attack(mob/living/basic/source, mob/living/target) + current_target = target + target.add_overlay(target_overlay) + RegisterSignal(target, COMSIG_QDELETING, PROC_REF(forget_target)) + RegisterSignal(target, COMSIG_MOVABLE_MOVED, PROC_REF(target_moved)) + + on_began_forecast?.Invoke(target) + if (!do_after(source, delay = telegraph_duration, target = source, interaction_key = INTERACTION_BASIC_ATTACK_FORCEAST)) + forget_target(target) + return + if (!current_target) // They got out of the way :( + return + ADD_TRAIT(source, TRAIT_BASIC_ATTACK_FORECAST, REF(src)) + source.melee_attack(current_target) + forget_target(current_target) + +/// The guy we're trying to attack moved, is he still in range? +/datum/component/basic_mob_attack_telegraph/proc/target_moved(mob/living/target) + SIGNAL_HANDLER + if (in_range(parent, target)) + return + forget_target(target) + +/// The guy we're trying to attack isn't a valid target any more +/datum/component/basic_mob_attack_telegraph/proc/forget_target(atom/target) + SIGNAL_HANDLER + current_target = null + target.cut_overlay(target_overlay) + UnregisterSignal(target, list(COMSIG_QDELETING, COMSIG_MOVABLE_MOVED)) diff --git a/code/datums/components/revenge_ability.dm b/code/datums/components/revenge_ability.dm new file mode 100644 index 00000000000..f03bc992276 --- /dev/null +++ b/code/datums/components/revenge_ability.dm @@ -0,0 +1,55 @@ +/** + * Automatically triggers a linked ability at a target who attacks us. + * The ability might not necessarily be on our mob. + * Make sure that /datum/element/relay_attackers is also present or you'll never receive the triggering signal. + */ +/datum/component/revenge_ability + dupe_mode = COMPONENT_DUPE_ALLOWED + /// The ability to use when we are attacked + var/datum/action/cooldown/ability + /// Optional datum for validating targets + var/datum/targetting_datum/targetting + /// Trigger only if target is at least this far away + var/min_range + /// Trigger only if target is at least this close + var/max_range + /// Target the ability at ourself instead of at the offender + var/target_self + +/datum/component/revenge_ability/Initialize(datum/action/cooldown/ability, datum/targetting_datum/targetting, min_range = 0, max_range = INFINITY, target_self = FALSE) + . = ..() + if (!isliving(parent)) + return COMPONENT_INCOMPATIBLE + src.ability = ability + src.targetting = targetting + src.min_range = min_range + src.max_range = max_range + src.target_self = target_self + + RegisterSignal(ability, COMSIG_QDELETING, PROC_REF(ability_destroyed)) + +/datum/component/revenge_ability/RegisterWithParent() + . = ..() + RegisterSignal(parent, COMSIG_ATOM_WAS_ATTACKED, PROC_REF(on_attacked)) + +/datum/component/revenge_ability/UnregisterFromParent() + UnregisterSignal(parent, COMSIG_ATOM_WAS_ATTACKED) + if (ability) + UnregisterSignal(ability, COMSIG_QDELETING) + return ..() + +/// If we were attacked, get revenge +/datum/component/revenge_ability/proc/on_attacked(mob/living/victim, atom/attacker) + SIGNAL_HANDLER + var/atom/ability_user = ability.owner + var/distance = get_dist(ability_user, attacker) + if (distance < min_range || distance > max_range) + return + if (targetting && !targetting.can_attack(victim, attacker)) + return + INVOKE_ASYNC(ability, TYPE_PROC_REF(/datum/action/cooldown, InterceptClickOn), ability_user, null, (target_self) ? ability_user : attacker) + +/// For whatever reason we lost our linked ability so we can drop this behaviour +/datum/component/revenge_ability/proc/ability_destroyed(datum/source) + SIGNAL_HANDLER + qdel(src) diff --git a/code/datums/components/riding/riding.dm b/code/datums/components/riding/riding.dm index cd6ea43eff1..14358a5c5a3 100644 --- a/code/datums/components/riding/riding.dm +++ b/code/datums/components/riding/riding.dm @@ -200,6 +200,11 @@ return FALSE riding_offsets["[index]"] = offsets +/datum/component/riding/proc/set_vehicle_offsets(list/offsets) + if(!islist(offsets)) + return FALSE + directional_vehicle_offsets = offsets + /** * This proc is used to see if we have the appropriate key to drive this atom, if such a key is needed. Returns FALSE if we don't have what we need to drive. * diff --git a/code/datums/components/riding/riding_mob.dm b/code/datums/components/riding/riding_mob.dm index 4f969692189..95e0ba8c772 100644 --- a/code/datums/components/riding/riding_mob.dm +++ b/code/datums/components/riding/riding_mob.dm @@ -378,12 +378,24 @@ /datum/component/riding/creature/goliath keytype = /obj/item/key/lasso + vehicle_move_delay = 4 + +/datum/component/riding/creature/goliath/Initialize(mob/living/riding_mob, force, ride_check_flags, potion_boost) + . = ..() + var/mob/living/basic/mining/goliath/goliath = parent + goliath.RemoveElement(/datum/element/move_cooldown, move_delay = goliath.movement_delay) + +/datum/component/riding/creature/goliath/Destroy(force, silent) + var/mob/living/basic/mining/goliath/goliath = parent + goliath.AddElement(/datum/element/move_cooldown, move_delay = goliath.movement_delay) + return ..() /datum/component/riding/creature/goliath/handle_specials() . = ..() - set_riding_offsets(RIDING_OFFSET_ALL, list(TEXT_NORTH = list(0, 8), TEXT_SOUTH = list(0, 8), TEXT_EAST = list(-2, 8), TEXT_WEST = list(2, 8))) + set_vehicle_offsets(list(TEXT_NORTH = list(-12, 0), TEXT_SOUTH = list(-12, 0), TEXT_EAST = list(-12, 0), TEXT_WEST = list(-12, 0))) + set_riding_offsets(RIDING_OFFSET_ALL, list(TEXT_NORTH = list(0, 12), TEXT_SOUTH = list(0, 12), TEXT_EAST = list(-4, 12), TEXT_WEST = list(3, 12))) set_vehicle_dir_layer(SOUTH, ABOVE_MOB_LAYER) - set_vehicle_dir_layer(NORTH, OBJ_LAYER) + set_vehicle_dir_layer(NORTH, ABOVE_MOB_LAYER) set_vehicle_dir_layer(EAST, OBJ_LAYER) set_vehicle_dir_layer(WEST, OBJ_LAYER) diff --git a/code/datums/components/spawner.dm b/code/datums/components/spawner.dm index 5753b05896f..abb93154099 100644 --- a/code/datums/components/spawner.dm +++ b/code/datums/components/spawner.dm @@ -65,7 +65,7 @@ /// Remove weakrefs to atoms which have been killed or deleted without us picking it up somehow /datum/component/spawner/proc/validate_references() for (var/datum/weakref/weak_thing as anything in spawned_things) - var/atom/previously_spawned = weak_thing.resolve() + var/atom/previously_spawned = weak_thing?.resolve() if (!previously_spawned) spawned_things -= weak_thing continue diff --git a/code/datums/elements/move_cooldown.dm b/code/datums/elements/move_cooldown.dm new file mode 100644 index 00000000000..5114cb36b85 --- /dev/null +++ b/code/datums/elements/move_cooldown.dm @@ -0,0 +1,43 @@ +/** + * Allows something to move only every x interval + * For instances where they'd otherwise need to move so slowly that the glide would look stupid + * Does not differentiate between voluntary and involuntary movement so this is beneficial in some niche circumstances + */ +/datum/element/move_cooldown + element_flags = ELEMENT_BESPOKE + argument_hash_start_idx = 2 + /// Time to wait between moves + var/move_delay + /// List of refs to atoms vs when they can next move + var/list/next_move_cache = list() + +/datum/element/move_cooldown/Attach(datum/target, move_delay = 1 SECONDS) + . = ..() + if (!ismovable(target)) + return ELEMENT_INCOMPATIBLE + src.move_delay = move_delay + next_move_cache[REF(target)] = 0 + RegisterSignal(target, COMSIG_MOVABLE_PRE_MOVE, PROC_REF(tried_move)) + RegisterSignal(target, COMSIG_MOVABLE_MOVED, PROC_REF(on_moved)) + +/datum/element/move_cooldown/Detach(datum/source) + UnregisterSignal(source, list(COMSIG_MOVABLE_PRE_MOVE, COMSIG_MOVABLE_MOVED)) + next_move_cache -= REF(source) + return ..() + +/// Called when something we're tracking tries to move, check if it's allowed +/datum/element/move_cooldown/proc/tried_move(atom/movable/source, new_loc) + SIGNAL_HANDLER + if (source.pulledby || source.throwing || !isturf(new_loc) || !isturf(source.loc)) + return + if (world.time <= next_move_cache[REF(source)]) + return COMPONENT_MOVABLE_BLOCK_PRE_MOVE + +/// Called when we moved successfully, start the cooldown +/datum/element/move_cooldown/proc/on_moved(atom/movable/source, old_loc, direction, forced) + SIGNAL_HANDLER + if (forced || source.pulledby || source.throwing || !isturf(old_loc) || !isturf(source.loc)) + return // Can't really eliminate involuntary movement but we'll try + if (!(source.movement_type & FLYING) && !source.has_gravity()) + return // Skip newtonian movement if it's not under control + next_move_cache[REF(source)] = world.time + move_delay diff --git a/code/datums/mapgen/CaveGenerator.dm b/code/datums/mapgen/CaveGenerator.dm index 0351b2bde99..fac20f277fb 100644 --- a/code/datums/mapgen/CaveGenerator.dm +++ b/code/datums/mapgen/CaveGenerator.dm @@ -51,7 +51,12 @@ /datum/map_generator/cave_generator/New() . = ..() if(!weighted_mob_spawn_list) - weighted_mob_spawn_list = list(/mob/living/simple_animal/hostile/asteroid/goldgrub = 1, /mob/living/simple_animal/hostile/asteroid/goliath = 5, /mob/living/simple_animal/hostile/asteroid/basilisk = 4, /mob/living/simple_animal/hostile/asteroid/hivelord = 3) + weighted_mob_spawn_list = list( + /mob/living/basic/mining/goliath/ancient = 5, + /mob/living/simple_animal/hostile/asteroid/basilisk = 4, + /mob/living/simple_animal/hostile/asteroid/hivelord = 3, + /mob/living/simple_animal/hostile/asteroid/goldgrub = 1, + ) mob_spawn_list = expand_weights(weighted_mob_spawn_list) mob_spawn_no_mega_list = expand_weights(weighted_mob_spawn_list - SPAWN_MEGAFAUNA) if(!weighted_megafauna_spawn_list) diff --git a/code/datums/mapgen/Cavegens/LavalandGenerator.dm b/code/datums/mapgen/Cavegens/LavalandGenerator.dm index 3c21949ce0c..3539dc29afc 100644 --- a/code/datums/mapgen/Cavegens/LavalandGenerator.dm +++ b/code/datums/mapgen/Cavegens/LavalandGenerator.dm @@ -3,7 +3,7 @@ weighted_closed_turf_types = list(/turf/closed/mineral/random/volcanic = 1) weighted_mob_spawn_list = list( - /mob/living/simple_animal/hostile/asteroid/goliath/beast/random = 50, + /mob/living/basic/mining/goliath/random = 50, /obj/structure/spawner/lavaland/goliath = 3, /mob/living/simple_animal/hostile/asteroid/basilisk/watcher/random = 40, /obj/structure/spawner/lavaland = 2, diff --git a/code/datums/memory/_memory.dm b/code/datums/memory/_memory.dm index e12c2847351..19b6a499964 100644 --- a/code/datums/memory/_memory.dm +++ b/code/datums/memory/_memory.dm @@ -255,6 +255,7 @@ /mob/living/basic/cow/wisdom, /mob/living/basic/giant_spider, /mob/living/basic/giant_spider/hunter, + /mob/living/basic/mining/goliath, /mob/living/basic/headslug, /mob/living/basic/killer_tomato, /mob/living/basic/lizard, @@ -269,7 +270,6 @@ /mob/living/basic/stickman/dog, /mob/living/simple_animal/crab, /mob/living/simple_animal/hostile/asteroid/basilisk/watcher, - /mob/living/simple_animal/hostile/asteroid/goliath/beast, /mob/living/simple_animal/hostile/bear, /mob/living/simple_animal/hostile/blob/blobbernaut/independent, /mob/living/simple_animal/hostile/gorilla, diff --git a/code/game/machinery/dna_infuser/infuser_entries/infuser_tier_one_entries.dm b/code/game/machinery/dna_infuser/infuser_entries/infuser_tier_one_entries.dm index be1a3692455..d24a951d76b 100644 --- a/code/game/machinery/dna_infuser/infuser_entries/infuser_tier_one_entries.dm +++ b/code/game/machinery/dna_infuser/infuser_entries/infuser_tier_one_entries.dm @@ -16,7 +16,7 @@ "a tendril hand can easily dig through basalt and obliterate hostile fauna, but your glove-wearing days are behind you...", ) input_obj_or_mob = list( - /mob/living/simple_animal/hostile/asteroid/goliath, + /mob/living/basic/mining/goliath, ) output_organs = list( /obj/item/organ/internal/brain/goliath, diff --git a/code/game/objects/effects/temporary_visuals/effect_trail.dm b/code/game/objects/effects/temporary_visuals/effect_trail.dm new file mode 100644 index 00000000000..028e5141653 --- /dev/null +++ b/code/game/objects/effects/temporary_visuals/effect_trail.dm @@ -0,0 +1,46 @@ +/// An invisible effect which chases a target, spawning spikes every so often. +/obj/effect/temp_visual/effect_trail + name = "effect trail" + desc = "An invisible effect, how did you examine this?" + icon = 'icons/mob/silicon/cameramob.dmi' + icon_state = "marker" + duration = 15 SECONDS + invisibility = INVISIBILITY_ABSTRACT + /// Typepath of our spawned effect + var/spawned_effect + /// How often do we spawn our other effect? + var/spawn_interval = 0.5 SECONDS + /// Speed at which we chase target + var/move_speed = 3 + /// What are we chasing? + var/atom/target + /// Stop spawning if we have this many effects already + var/max_spawned = 20 + /// Do we home in after we started moving? + var/homing = TRUE + /// Handles chasing the target + var/datum/move_loop/movement + +/obj/effect/temp_visual/effect_trail/Initialize(mapload, atom/target) + . = ..() + if (!target) + return INITIALIZE_HINT_QDEL + + AddElement(/datum/element/floor_loving) + AddComponent(/datum/component/spawner, spawn_types = list(spawned_effect), max_spawned = max_spawned, spawn_time = spawn_interval) + src.target = target + movement = SSmove_manager.move_towards(src, chasing = target, delay = move_speed, home = homing, timeout = duration, flags = MOVEMENT_LOOP_START_FAST) + + RegisterSignal(target, COMSIG_QDELETING, PROC_REF(on_target_invalid)) + if (isliving(target)) + RegisterSignal(target, COMSIG_LIVING_DEATH, PROC_REF(on_target_invalid)) + +/// Destroy ourselves if the target is no longer valid +/obj/effect/temp_visual/effect_trail/proc/on_target_invalid() + SIGNAL_HANDLER + target = null + qdel(src) + +/obj/effect/temp_visual/effect_trail/Destroy() + QDEL_NULL(movement) + return ..() diff --git a/code/game/objects/items/stacks/sheets/leather.dm b/code/game/objects/items/stacks/sheets/leather.dm index 3e3ca870e0b..1acac727dbd 100644 --- a/code/game/objects/items/stacks/sheets/leather.dm +++ b/code/game/objects/items/stacks/sheets/leather.dm @@ -178,7 +178,7 @@ GLOBAL_LIST_INIT(leather_recipes, list ( \ new/datum/stack_recipe("muzzle", /obj/item/clothing/mask/muzzle, 2, check_density = FALSE, category = CAT_ENTERTAINMENT), \ new/datum/stack_recipe("basketball", /obj/item/toy/basketball, 20, check_density = FALSE, category = CAT_ENTERTAINMENT), \ new/datum/stack_recipe("baseball", /obj/item/toy/beach_ball/baseball, 3, check_density = FALSE, category = CAT_ENTERTAINMENT), \ - new/datum/stack_recipe("saddle", /obj/item/saddle, 5, check_density = FALSE, category = CAT_EQUIPMENT), \ + new/datum/stack_recipe("saddle", /obj/item/goliath_saddle, 5, check_density = FALSE, category = CAT_EQUIPMENT), \ new/datum/stack_recipe("leather shoes", /obj/item/clothing/shoes/laceup, 2, check_density = FALSE, category = CAT_CLOTHING), \ new/datum/stack_recipe("cowboy boots", /obj/item/clothing/shoes/cowboy, 2, check_density = FALSE, category = CAT_CLOTHING), \ new/datum/stack_recipe("botany gloves", /obj/item/clothing/gloves/botanic_leather, 3, check_density = FALSE, category = CAT_CLOTHING), \ diff --git a/code/game/objects/structures/lavaland/necropolis_tendril.dm b/code/game/objects/structures/lavaland/necropolis_tendril.dm index ee767f4670e..b8463921b0a 100644 --- a/code/game/objects/structures/lavaland/necropolis_tendril.dm +++ b/code/game/objects/structures/lavaland/necropolis_tendril.dm @@ -20,7 +20,7 @@ /obj/structure/spawner/lavaland/goliath - mob_types = list(/mob/living/simple_animal/hostile/asteroid/goliath/beast/tendril) + mob_types = list(/mob/living/basic/mining/goliath) /obj/structure/spawner/lavaland/legion mob_types = list(/mob/living/simple_animal/hostile/asteroid/hivelord/legion/tendril) diff --git a/code/game/objects/structures/spawner.dm b/code/game/objects/structures/spawner.dm index cec59e1f865..90a3366260f 100644 --- a/code/game/objects/structures/spawner.dm +++ b/code/game/objects/structures/spawner.dm @@ -53,7 +53,18 @@ max_integrity = 200 max_mobs = 15 spawn_time = 15 SECONDS - mob_types = list(/mob/living/simple_animal/hostile/retaliate/clown, /mob/living/simple_animal/hostile/retaliate/clown/fleshclown, /mob/living/simple_animal/hostile/retaliate/clown/clownhulk, /mob/living/simple_animal/hostile/retaliate/clown/longface, /mob/living/simple_animal/hostile/retaliate/clown/clownhulk/chlown, /mob/living/simple_animal/hostile/retaliate/clown/clownhulk/honcmunculus, /mob/living/simple_animal/hostile/retaliate/clown/mutant/glutton, /mob/living/simple_animal/hostile/retaliate/clown/banana, /mob/living/simple_animal/hostile/retaliate/clown/honkling, /mob/living/simple_animal/hostile/retaliate/clown/lube) + mob_types = list( + /mob/living/simple_animal/hostile/retaliate/clown, + /mob/living/simple_animal/hostile/retaliate/clown/banana, + /mob/living/simple_animal/hostile/retaliate/clown/clownhulk, + /mob/living/simple_animal/hostile/retaliate/clown/clownhulk/chlown, + /mob/living/simple_animal/hostile/retaliate/clown/clownhulk/honcmunculus, + /mob/living/simple_animal/hostile/retaliate/clown/fleshclown, + /mob/living/simple_animal/hostile/retaliate/clown/mutant/glutton, + /mob/living/simple_animal/hostile/retaliate/clown/honkling, + /mob/living/simple_animal/hostile/retaliate/clown/longface, + /mob/living/simple_animal/hostile/retaliate/clown/lube, + ) spawn_text = "climbs out of" faction = list(FACTION_CLOWN) @@ -65,7 +76,13 @@ max_mobs = 3 icon = 'icons/mob/simple/lavaland/nest.dmi' spawn_text = "crawls out of" - mob_types = list(/mob/living/simple_animal/hostile/asteroid/goldgrub, /mob/living/simple_animal/hostile/asteroid/goliath, /mob/living/simple_animal/hostile/asteroid/hivelord, /mob/living/simple_animal/hostile/asteroid/basilisk, /mob/living/basic/wumborian_fugu) + mob_types = list( + /mob/living/basic/mining/goliath/ancient, + /mob/living/basic/wumborian_fugu, + /mob/living/simple_animal/hostile/asteroid/basilisk, + /mob/living/simple_animal/hostile/asteroid/goldgrub, + /mob/living/simple_animal/hostile/asteroid/hivelord, + ) faction = list(FACTION_MINING) /obj/structure/spawner/mining/goldgrub @@ -76,7 +93,7 @@ /obj/structure/spawner/mining/goliath name = "goliath den" desc = "A den housing a nest of goliaths, oh god why?" - mob_types = list(/mob/living/simple_animal/hostile/asteroid/goliath) + mob_types = list(/mob/living/basic/mining/goliath/ancient) /obj/structure/spawner/mining/hivelord name = "hivelord den" @@ -102,7 +119,11 @@ max_mobs = 15 icon = 'icons/mob/simple/lavaland/nest.dmi' spawn_text = "crawls through" - mob_types = list(/mob/living/basic/migo, /mob/living/basic/creature, /mob/living/basic/blankbody) + mob_types = list( + /mob/living/basic/blankbody, + /mob/living/basic/creature, + /mob/living/basic/migo, + ) faction = list(FACTION_NETHER) /obj/structure/spawner/nether/Initialize(mapload) diff --git a/code/modules/mining/equipment/monster_organs/brimdust_sac.dm b/code/modules/mining/equipment/monster_organs/brimdust_sac.dm index 1051962bbb8..dc6539ff336 100644 --- a/code/modules/mining/equipment/monster_organs/brimdust_sac.dm +++ b/code/modules/mining/equipment/monster_organs/brimdust_sac.dm @@ -171,6 +171,7 @@ var/armor = target.run_armor_check(attack_flag = BOMB) target.apply_damage(damage_dealt, damagetype = BURN, blocked = armor, spread_damage = TRUE) + SEND_SIGNAL(owner, COMSIG_BRIMDUST_EXPLOSION) add_stacks(-1) /// Slowdown applied when you are detonated on the space station diff --git a/code/modules/mining/equipment/monster_organs/rush_gland.dm b/code/modules/mining/equipment/monster_organs/rush_gland.dm index f716f51d7dd..3554d67b2a6 100644 --- a/code/modules/mining/equipment/monster_organs/rush_gland.dm +++ b/code/modules/mining/equipment/monster_organs/rush_gland.dm @@ -21,6 +21,14 @@ if (owner.health <= HEALTH_DANGER_ZONE) trigger_organ_action() +/obj/item/organ/internal/monster_core/rush_gland/on_insert(mob/living/carbon/organ_owner) + . = ..() + RegisterSignal(organ_owner, COMSIG_GOLIATH_TENTACLED_GRABBED, PROC_REF(trigger_organ_action)) + +/obj/item/organ/internal/monster_core/rush_gland/on_remove(mob/living/carbon/organ_owner, special) + . = ..() + UnregisterSignal(organ_owner, COMSIG_GOLIATH_TENTACLED_GRABBED) + /obj/item/organ/internal/monster_core/rush_gland/on_triggered_internal() owner.apply_status_effect(/datum/status_effect/lobster_rush/extended) @@ -44,14 +52,14 @@ . = ..() RegisterSignal(owner, COMSIG_MOVABLE_PRE_MOVE, PROC_REF(on_move)) RegisterSignal(owner, COMSIG_MOVABLE_BUMP, PROC_REF(on_bump)) - ADD_TRAIT(owner, TRAIT_IGNORESLOWDOWN, TRAIT_STATUS_EFFECT(id)) + owner.add_traits(list(TRAIT_IGNORESLOWDOWN, TRAIT_TENTACLE_IMMUNE), TRAIT_STATUS_EFFECT(id)) owner.add_movespeed_modifier(/datum/movespeed_modifier/status_effect/lobster_rush) to_chat(owner, span_notice("You feel your blood pumping!")) /datum/status_effect/lobster_rush/on_remove() . = ..() UnregisterSignal(owner, list(COMSIG_MOVABLE_PRE_MOVE, COMSIG_MOVABLE_BUMP)) - REMOVE_TRAIT(owner, TRAIT_IGNORESLOWDOWN, TRAIT_STATUS_EFFECT(id)) + owner.remove_traits(list(TRAIT_IGNORESLOWDOWN, TRAIT_TENTACLE_IMMUNE), TRAIT_STATUS_EFFECT(id)) owner.remove_movespeed_modifier(/datum/movespeed_modifier/status_effect/lobster_rush) to_chat(owner, span_notice("Your pulse returns to normal.")) diff --git a/code/modules/mob/living/basic/basic_defense.dm b/code/modules/mob/living/basic/basic_defense.dm index cd1ded2ed6f..cc128a9f5e9 100644 --- a/code/modules/mob/living/basic/basic_defense.dm +++ b/code/modules/mob/living/basic/basic_defense.dm @@ -38,7 +38,7 @@ to_chat(user, span_danger("You [response_harm_simple] [src]!")) playsound(loc, attacked_sound, 25, TRUE, -1) var/obj/item/bodypart/arm/active_arm = user.get_active_hand() - var/damage = rand(active_arm.unarmed_damage_low, active_arm.unarmed_damage_high) + var/damage = (basic_mob_flags & IMMUNE_TO_FISTS) ? 0 : rand(active_arm.unarmed_damage_low, active_arm.unarmed_damage_high) attack_threshold_check(damage) log_combat(user, src, "attacked") diff --git a/code/modules/mob/living/basic/lavaland/bileworm/_bileworm.dm b/code/modules/mob/living/basic/lavaland/bileworm/_bileworm.dm index 8b2f8e91d03..3d6bc299ccd 100644 --- a/code/modules/mob/living/basic/lavaland/bileworm/_bileworm.dm +++ b/code/modules/mob/living/basic/lavaland/bileworm/_bileworm.dm @@ -5,7 +5,6 @@ icon_state = "bileworm" icon_living = "bileworm" icon_dead = "bileworm_dead" - mob_size = MOB_SIZE_LARGE mob_biotypes = MOB_BUG maxHealth = 100 health = 100 diff --git a/code/modules/mob/living/basic/lavaland/goliath/goliath.dm b/code/modules/mob/living/basic/lavaland/goliath/goliath.dm new file mode 100644 index 00000000000..80030ff4440 --- /dev/null +++ b/code/modules/mob/living/basic/lavaland/goliath/goliath.dm @@ -0,0 +1,236 @@ +/// Slow moving mob which attempts to immobilise its target +/mob/living/basic/mining/goliath + name = "goliath" + desc = "A hulking, armor-plated beast with long tendrils arching from its back." + icon = 'icons/mob/simple/lavaland/lavaland_monsters_wide.dmi' + icon_state = "goliath" + icon_living = "goliath" + icon_dead = "goliath_dead" + pixel_x = -12 + base_pixel_x = -12 + gender = MALE // Female ones are the bipedal elites + basic_mob_flags = IMMUNE_TO_FISTS + mob_biotypes = MOB_ORGANIC|MOB_BEAST + maxHealth = 300 + health = 300 + friendly_verb_continuous = "wails at" + friendly_verb_simple = "wail at" + speak_emote = list("bellows") + obj_damage = 100 + melee_damage_lower = 25 + melee_damage_upper = 25 + attack_sound = 'sound/weapons/punch1.ogg' + attack_verb_continuous = "pulverizes" + attack_verb_simple = "pulverize" + throw_blocked_message = "does nothing to the tough hide of" + move_force = MOVE_FORCE_VERY_STRONG + move_resist = MOVE_FORCE_VERY_STRONG + pull_force = MOVE_FORCE_VERY_STRONG + + ai_controller = /datum/ai_controller/basic_controller/goliath + + crusher_loot = /obj/item/crusher_trophy/goliath_tentacle + butcher_results = list(/obj/item/food/meat/slab/goliath = 2, /obj/item/stack/sheet/bone = 2) + guaranteed_butcher_results = list(/obj/item/stack/sheet/animalhide/goliath_hide = 1) + /// Goliath can only take a step in intervals of this + var/movement_delay = 4 SECONDS + /// Icon state to use when tentacles are available + var/tentacle_warning_state = "goliath_preattack" + /// Can this kind of goliath be tamed? + var/tameable = TRUE + /// Has this particular goliath been tamed? + var/tamed = FALSE + /// Can someone ride us around like a horse? + var/saddled = FALSE + /// Slight cooldown to prevent double-dipping if we use both abilities at once + COOLDOWN_DECLARE(ability_animation_cooldown) + /// Our base tentacles ability + var/datum/action/cooldown/goliath_tentacles/tentacles + /// Things we want to eat off the floor (or a plate, we're not picky) + var/static/list/goliath_foods = list(/obj/item/food/grown/ash_flora, /obj/item/food/bait/worm) + +/mob/living/basic/mining/goliath/Initialize(mapload) + . = ..() + ADD_TRAIT(src, TRAIT_TENTACLE_IMMUNE, INNATE_TRAIT) + AddElement(/datum/element/ai_retaliate) + AddElement(/datum/element/footstep, FOOTSTEP_MOB_HEAVY) + AddElement(/datum/element/basic_eating, heal_amt = 10, food_types = goliath_foods) + AddElement(/datum/element/move_cooldown, move_delay = movement_delay) + AddComponent(/datum/component/basic_mob_attack_telegraph) + AddComponentFrom(INNATE_TRAIT, /datum/component/shovel_hands) + if (tameable) + AddComponent(\ + /datum/component/tameable,\ + food_types = list(/obj/item/food/grown/ash_flora),\ + tame_chance = 10,\ + bonus_tame_chance = 5,\ + after_tame = CALLBACK(src, PROC_REF(tamed)),\ + ) + + tentacles = new (src) + tentacles.Grant(src) + var/datum/action/cooldown/tentacle_burst/melee_tentacles = new (src) + melee_tentacles.Grant(src) + AddComponent(/datum/component/revenge_ability, melee_tentacles, targetting = ai_controller.blackboard[BB_TARGETTING_DATUM], max_range = 1, target_self = TRUE) + var/datum/action/cooldown/tentacle_grasp/ranged_tentacles = new (src) + ranged_tentacles.Grant(src) + AddComponent(/datum/component/revenge_ability, ranged_tentacles, targetting = ai_controller.blackboard[BB_TARGETTING_DATUM], min_range = 2, max_range = 9) + + tentacles_ready() + RegisterSignal(src, COMSIG_MOB_ABILITY_FINISHED, PROC_REF(used_ability)) + ai_controller.set_blackboard_key(BB_BASIC_FOODS, goliath_foods) + ai_controller.set_blackboard_key(BB_GOLIATH_TENTACLES, tentacles) + +/mob/living/basic/mining/goliath/Destroy() + QDEL_NULL(tentacles) + return ..() + +/mob/living/basic/mining/goliath/examine(mob/user) + . = ..() + if (saddled) + . += span_info("Someone appears to have attached a saddle to this one.") + +/mob/living/basic/mining/goliath/revive(full_heal_flags, excess_healing, force_grab_ghost) + . = ..() + if (!.) + return + move_force = initial(move_force) + move_resist = initial(move_resist) + pull_force = initial(pull_force) + +/mob/living/basic/mining/goliath/death(gibbed) + move_force = MOVE_FORCE_DEFAULT + move_resist = MOVE_RESIST_DEFAULT + pull_force = PULL_FORCE_DEFAULT + return ..() + +// Goliaths can summon tentacles more frequently as they take damage, scary. +/mob/living/basic/mining/goliath/apply_damage(damage, damagetype, def_zone, blocked, forced, spread_damage, wound_bonus, bare_wound_bonus, sharpness, attack_direction, attacking_item) + . = ..() + if (!.) + return + if (damage <= 0) + return + if (tentacles.cooldown_time > 1 SECONDS) + tentacles.cooldown_time -= 1 SECONDS + +/mob/living/basic/mining/goliath/attackby(obj/item/attacking_item, mob/living/user, params) + if (!istype(attacking_item, /obj/item/goliath_saddle)) + return ..() + if (!tameable) + balloon_alert(user, "doesn't fit!") + return + if (saddled) + balloon_alert(user, "already saddled!") + return + if (!tamed) + balloon_alert(user, "too rowdy!") + return + balloon_alert(user, "affixing saddle...") + if (!do_after(user, delay = 5.5 SECONDS, target = src)) + return + balloon_alert(user, "ready to ride") + qdel(attacking_item) + saddled = TRUE + buckle_lying = 0 + add_overlay("goliath_saddled") + AddElement(/datum/element/ridable, /datum/component/riding/creature/goliath) + +/// When we use an ability, activate some kind of visual tell +/mob/living/basic/mining/goliath/proc/used_ability(mob/living/source, datum/action/cooldown/ability) + SIGNAL_HANDLER + if (stat == DEAD || ability.IsAvailable()) + return // We died or the action failed for some reason like being out of range + if (istype(ability, /datum/action/cooldown/goliath_tentacles)) + if (ability.cooldown_time <= 2 SECONDS) + return + icon_state = icon_living + addtimer(CALLBACK(src, PROC_REF(tentacles_ready)), ability.cooldown_time - 2 SECONDS, TIMER_DELETE_ME) + return + if (!COOLDOWN_FINISHED(src, ability_animation_cooldown)) + return + COOLDOWN_START(src, ability_animation_cooldown, 2 SECONDS) + playsound(src, 'sound/magic/demon_attack1.ogg', vol = 50, vary = TRUE) + Shake(1, 0, 1.5 SECONDS) + +/// Called slightly before tentacles ability comes off cooldown, as a warning +/mob/living/basic/mining/goliath/proc/tentacles_ready() + if (stat == DEAD) + return + icon_state = tentacle_warning_state + +/// Get ready for mounting +/mob/living/basic/mining/goliath/proc/tamed() + tamed = TRUE + +// Copy entire faction rather than just placing user into faction, to avoid tentacle peril on station +/mob/living/basic/mining/goliath/befriend(mob/living/new_friend) + . = ..() + faction = new_friend.faction.Copy() + +/// Goliath which sometimes replaces itself with a rare variant +/mob/living/basic/mining/goliath/random + +/mob/living/basic/mining/goliath/random/Initialize(mapload) + . = ..() + if(!prob(1)) + return + new /mob/living/basic/mining/goliath/ancient/immortal(loc) + return INITIALIZE_HINT_QDEL + +/// Legacy Goliath mob with different sprites, largely the same behaviour +/mob/living/basic/mining/goliath/ancient + name = "ancient goliath" + desc = "A massive beast that uses long tentacles to ensnare its prey, threatening them is not advised under any conditions." + icon = 'icons/mob/simple/lavaland/lavaland_monsters_wide.dmi' + icon_state = "ancient_goliath" + icon_living = "ancient_goliath" + icon_dead = "ancient_goliath_dead" + tentacle_warning_state = "ancient_goliath_preattack" + tameable = FALSE + +/// Rare Goliath variant which occasionally replaces the normal mining mob, releases shitloads of tentacles +/mob/living/basic/mining/goliath/ancient/immortal + name = "immortal goliath" + desc = "Goliaths are biologically immortal, and rare specimens have survived for centuries. \ + This one is clearly ancient, and its tentacles constantly churn the earth around it." + maxHealth = 400 + health = 400 + crusher_drop_chance = 30 // Wow a whole 5% more likely, how generous + /// Don't re-check nearby turfs for this long + COOLDOWN_DECLARE(retarget_turfs_cooldown) + /// List of places we might spawn a tentacle, if we're alive + var/list/tentacle_target_turfs + +/mob/living/basic/mining/goliath/ancient/immortal/Life(seconds_per_tick, times_fired) + . = ..() + if (!. || !isturf(loc)) + return + if (!LAZYLEN(tentacle_target_turfs) || COOLDOWN_FINISHED(src, retarget_turfs_cooldown)) + cache_nearby_turfs() + for (var/turf/target_turf in tentacle_target_turfs) + if (target_turf.is_blocked_turf(exclude_mobs = TRUE)) + tentacle_target_turfs -= target_turf + continue + if (prob(10)) + new /obj/effect/goliath_tentacle(target_turf) + +/mob/living/basic/mining/goliath/ancient/immortal/Moved(atom/old_loc, movement_dir, forced, list/old_locs, momentum_change) + . = ..() + if (loc == old_loc || stat == DEAD || !isturf(loc)) + return + cache_nearby_turfs() + +/// Store nearby turfs in our list so we can pop them out later +/mob/living/basic/mining/goliath/ancient/immortal/proc/cache_nearby_turfs() + COOLDOWN_START(src, retarget_turfs_cooldown, 10 SECONDS) + LAZYCLEARLIST(tentacle_target_turfs) + for(var/turf/open/floor in orange(4, loc)) + LAZYADD(tentacle_target_turfs, floor) + +/// Use this to ride a goliath +/obj/item/goliath_saddle + name = "goliath saddle" + desc = "This rough saddle will give you a serviceable seat upon a goliath! Provided you can get one to stand still." + icon = 'icons/obj/mining.dmi' + icon_state = "goliath_saddle" diff --git a/code/modules/mob/living/basic/lavaland/goliath/goliath_actions.dm b/code/modules/mob/living/basic/lavaland/goliath/goliath_actions.dm new file mode 100644 index 00000000000..f98d1630650 --- /dev/null +++ b/code/modules/mob/living/basic/lavaland/goliath/goliath_actions.dm @@ -0,0 +1,85 @@ +/// Place some grappling tentacles underfoot +/datum/action/cooldown/goliath_tentacles + name = "Unleash Tentacles" + desc = "Unleash burrowed tentacles at a targetted location, grappling targets after a delay." + button_icon = 'icons/mob/simple/lavaland/lavaland_monsters.dmi' + button_icon_state = "goliath_tentacle_wiggle" + background_icon_state = "bg_demon" + overlay_icon_state = "bg_demon_border" + click_to_activate = TRUE + cooldown_time = 12 SECONDS + melee_cooldown_time = 0 + check_flags = AB_CHECK_CONSCIOUS | AB_CHECK_INCAPACITATED + /// Furthest range we can activate ability at + var/max_range = 7 + +/datum/action/cooldown/goliath_tentacles/PreActivate(atom/target) + target = get_turf(target) + if (get_dist(owner, target) > max_range) + return FALSE + return ..() + +/datum/action/cooldown/goliath_tentacles/Activate(atom/target) + . = ..() + new /obj/effect/goliath_tentacle(target) + var/list/directions = GLOB.cardinals.Copy() + for(var/i in 1 to 3) + var/spawndir = pick_n_take(directions) + var/turf/adjacent_target = get_step(target, spawndir) + if(adjacent_target) + new /obj/effect/goliath_tentacle(adjacent_target) + + if (isliving(target)) + owner.visible_message(span_warning("[owner] digs its tentacles under [target]!")) + return TRUE + +/// Place grappling tentacles around you to grab attackers +/datum/action/cooldown/tentacle_burst + name = "Tentacle Burst" + desc = "Unleash burrowed tentacles in an area around you, grappling targets after a delay." + button_icon = 'icons/mob/simple/lavaland/lavaland_monsters.dmi' + button_icon_state = "goliath_tentacle_wiggle" + background_icon_state = "bg_demon" + overlay_icon_state = "bg_demon_border" + cooldown_time = 24 SECONDS + melee_cooldown_time = 0 + check_flags = AB_CHECK_CONSCIOUS | AB_CHECK_INCAPACITATED + +/datum/action/cooldown/tentacle_burst/Activate(atom/target) + . = ..() + var/list/directions = GLOB.alldirs.Copy() + for (var/dir in directions) + var/turf/adjacent_target = get_step(target, dir) + if(adjacent_target) + new /obj/effect/goliath_tentacle(adjacent_target) + owner.visible_message(span_warning("[owner] unleashes tentacles from the ground around it!")) + return TRUE + +/// Summon a line of tentacles towards the target +/datum/action/cooldown/tentacle_grasp + name = "Tentacle Grasp" + desc = "Unleash burrowed tentacles in a line towards a targetted location, grappling targets after a delay." + button_icon = 'icons/mob/simple/lavaland/lavaland_monsters.dmi' + button_icon_state = "goliath_tentacle_wiggle" + background_icon_state = "bg_demon" + overlay_icon_state = "bg_demon_border" + click_to_activate = TRUE + cooldown_time = 12 SECONDS + melee_cooldown_time = 0 + check_flags = AB_CHECK_CONSCIOUS | AB_CHECK_INCAPACITATED + +/datum/action/cooldown/tentacle_grasp/Activate(atom/target) + . = ..() + new /obj/effect/temp_visual/effect_trail/burrowed_tentacle(owner.loc, target) + if (isliving(target)) + owner.visible_message(span_warning("[owner] reaches for [target] with its tentacles!")) + return TRUE + +/// An invisible effect which chases a target, spawning tentacles every so often. +/obj/effect/temp_visual/effect_trail/burrowed_tentacle + name = "burrowed_tentacle" + duration = 2 SECONDS + move_speed = 2 + homing = FALSE + spawn_interval = 0.1 SECONDS + spawned_effect = /obj/effect/goliath_tentacle diff --git a/code/modules/mob/living/basic/lavaland/goliath/goliath_ai.dm b/code/modules/mob/living/basic/lavaland/goliath/goliath_ai.dm new file mode 100644 index 00000000000..6c622ce6679 --- /dev/null +++ b/code/modules/mob/living/basic/lavaland/goliath/goliath_ai.dm @@ -0,0 +1,115 @@ +/datum/ai_controller/basic_controller/goliath + blackboard = list( + BB_TARGETTING_DATUM = new /datum/targetting_datum/basic/allow_items/goliath, + ) + + ai_movement = /datum/ai_movement/basic_avoidance + idle_behavior = /datum/idle_behavior/idle_random_walk + planning_subtrees = list( + /datum/ai_planning_subtree/target_retaliate, + /datum/ai_planning_subtree/simple_find_target, + /datum/ai_planning_subtree/find_food, + /datum/ai_planning_subtree/targeted_mob_ability/goliath_tentacles, + /datum/ai_planning_subtree/attack_obstacle_in_path, + /datum/ai_planning_subtree/basic_melee_attack_subtree/goliath, + /datum/ai_planning_subtree/goliath_find_diggable_turf, + /datum/ai_planning_subtree/goliath_dig, + ) + +/datum/targetting_datum/basic/allow_items/goliath + stat_attack = HARD_CRIT + +/datum/ai_planning_subtree/basic_melee_attack_subtree/goliath + melee_attack_behavior = /datum/ai_behavior/basic_melee_attack/goliath + +/// Go for the tentacles if they're available +/datum/ai_behavior/basic_melee_attack/goliath + +/datum/ai_behavior/basic_melee_attack/goliath/perform(seconds_per_tick, datum/ai_controller/controller, target_key, targetting_datum_key, hiding_location_key, health_ratio_key) + var/mob/living/target = controller.blackboard[target_key] + // Interrupt attack chain to use tentacles, unless the target is already tentacled + if (isliving(target) && !target.has_status_effect(/datum/status_effect/incapacitating/stun/goliath_tentacled)) + var/datum/action/cooldown/using_action = controller.blackboard[BB_GOLIATH_TENTACLES] + if (using_action?.IsAvailable()) + finish_action(controller, succeeded = FALSE) + return + return ..() + +/datum/ai_planning_subtree/targeted_mob_ability/goliath_tentacles + ability_key = BB_GOLIATH_TENTACLES + +/datum/ai_planning_subtree/targeted_mob_ability/goliath_tentacles/SelectBehaviors(datum/ai_controller/controller, seconds_per_tick) + var/mob/living/target = controller.blackboard[target_key] + if (!isliving(target) || target.has_status_effect(/datum/status_effect/incapacitating/stun/goliath_tentacled)) + return // Target can be an item or already grabbed, we don't want to tentacle those + return ..() + +/// If we got nothing better to do, find a turf we can search for tasty roots and such +/datum/ai_planning_subtree/goliath_find_diggable_turf + +/datum/ai_planning_subtree/goliath_find_diggable_turf/SelectBehaviors(datum/ai_controller/controller, seconds_per_tick) + controller.queue_behavior(/datum/ai_behavior/goliath_find_diggable_turf) + +/datum/ai_behavior/goliath_find_diggable_turf + action_cooldown = 2 SECONDS + /// Where do we store the target data + var/target_key = BB_GOLIATH_HOLE_TARGET + /// How far do we look for turfs? + var/scan_range = 3 + +/datum/ai_behavior/goliath_find_diggable_turf/perform(seconds_per_tick, datum/ai_controller/controller) + . = ..() + var/turf/target_turf = controller.blackboard[target_key] + if (is_valid_turf(target_turf)) + finish_action(controller, succeeded = FALSE) + return + + var/mob/living/pawn = controller.pawn + var/list/nearby_turfs = RANGE_TURFS(scan_range, pawn) + var/turf/check_turf = pick(nearby_turfs) // This isn't an efficient search algorithm but we don't need it to be + if (!is_valid_turf(check_turf)) + finish_action(controller, succeeded = FALSE) // Otherwise they won't perform idle wanderin + return + controller.set_blackboard_key(target_key, check_turf) + finish_action(controller, succeeded = TRUE) + +/// Return true if this is a turf we can dig +/datum/ai_behavior/goliath_find_diggable_turf/proc/is_valid_turf(turf/check_turf) + if (!isasteroidturf(check_turf)) + return FALSE + var/turf/open/misc/asteroid/floor = check_turf + return !floor.dug + +/datum/ai_planning_subtree/goliath_dig + /// Where did we store the target data + var/target_key = BB_GOLIATH_HOLE_TARGET + +/datum/ai_planning_subtree/goliath_dig/SelectBehaviors(datum/ai_controller/controller, seconds_per_tick) + var/turf/target_turf = controller.blackboard[target_key] + if (QDELETED(target_turf)) + return + controller.queue_behavior(/datum/ai_behavior/goliath_dig, target_key) + return SUBTREE_RETURN_FINISH_PLANNING + +/// If we got nothing better to do, dig a little hole +/datum/ai_behavior/goliath_dig + action_cooldown = 1 MINUTES + behavior_flags = AI_BEHAVIOR_REQUIRE_MOVEMENT | AI_BEHAVIOR_CAN_PLAN_DURING_EXECUTION + +/datum/ai_behavior/goliath_dig/setup(datum/ai_controller/controller, target_key) + . = ..() + var/turf/target_turf = controller.blackboard[target_key] + if (QDELETED(target_turf)) + return + set_movement_target(controller, target_turf) + +/datum/ai_behavior/goliath_dig/perform(seconds_per_tick, datum/ai_controller/controller, target_key) + . = ..() + var/turf/target_turf = controller.blackboard[target_key] + var/mob/living/basic/basic_mob = controller.pawn + basic_mob.melee_attack(target_turf) + finish_action(controller, succeeded = TRUE) + +/datum/ai_behavior/goliath_dig/finish_action(datum/ai_controller/controller, succeeded, target_key) + . = ..() + controller.clear_blackboard_key(target_key) diff --git a/code/modules/mob/living/basic/lavaland/goliath/tentacle.dm b/code/modules/mob/living/basic/lavaland/goliath/tentacle.dm new file mode 100644 index 00000000000..f706162c5fe --- /dev/null +++ b/code/modules/mob/living/basic/lavaland/goliath/tentacle.dm @@ -0,0 +1,120 @@ +/// A tentacle which grabs you if you don't get away from it +/obj/effect/goliath_tentacle + name = "goliath tentacle" + icon = 'icons/mob/simple/lavaland/lavaland_monsters.dmi' + icon_state = "goliath_tentacle_spawn" + layer = BELOW_MOB_LAYER + plane = GAME_PLANE + anchored = TRUE + /// Timer for our current action stage + var/action_timer + /// Time in which to grab people + var/grapple_time = 10 SECONDS + /// Lower bound of damage to inflict + var/min_damage = 10 + /// Upper bound of damage to inflict + var/max_damage = 15 + +/obj/effect/goliath_tentacle/Initialize(mapload) + . = ..() + if (!isopenturf(loc) || isspaceturf(loc) || isopenspaceturf(loc)) + return INITIALIZE_HINT_QDEL + for (var/obj/effect/goliath_tentacle/tentacle in loc) + if (tentacle != src) + return INITIALIZE_HINT_QDEL + if (ismineralturf(loc)) + var/turf/closed/mineral/floor = loc + floor.gets_drilled() + deltimer(action_timer) + action_timer = addtimer(CALLBACK(src, PROC_REF(animate_grab)), 0.7 SECONDS, TIMER_STOPPABLE) + +/obj/effect/goliath_tentacle/Destroy() + deltimer(action_timer) + return ..() + +/// Change to next icon state and set up grapple +/obj/effect/goliath_tentacle/proc/animate_grab() + icon_state = "goliath_tentacle_wiggle" + deltimer(action_timer) + addtimer(CALLBACK(src, PROC_REF(grab)), 0.3 SECONDS, TIMER_STOPPABLE) + +/// Grab everyone we share space with. If it's nobody, go home. +/obj/effect/goliath_tentacle/proc/grab() + for (var/mob/living/victim in loc) + if (victim.stat == DEAD || HAS_TRAIT(victim, TRAIT_TENTACLE_IMMUNE)) + continue + balloon_alert(victim, "grabbed") + visible_message(span_danger("[src] grabs hold of [victim]!")) + victim.adjustBruteLoss(rand(min_damage, max_damage)) + if (victim.apply_status_effect(/datum/status_effect/incapacitating/stun/goliath_tentacled, grapple_time, src)) + buckle_mob(victim, TRUE) + SEND_SIGNAL(victim, COMSIG_GOLIATH_TENTACLED_GRABBED) + if (!has_buckled_mobs()) + retract() + return + deltimer(action_timer) + action_timer = addtimer(CALLBACK(src, PROC_REF(retract)), grapple_time, TIMER_STOPPABLE) + +/// Play exit animation. +/obj/effect/goliath_tentacle/proc/retract() + if (icon_state == "goliath_tentacle_retract") + return // Already retracting + SEND_SIGNAL(src, COMSIG_GOLIATH_TENTACLE_RETRACTING) + unbuckle_all_mobs(force = TRUE) + icon_state = "goliath_tentacle_retract" + deltimer(action_timer) + action_timer = QDEL_IN_STOPPABLE(src, 0.7 SECONDS) + +/obj/effect/goliath_tentacle/attack_hand(mob/living/user, list/modifiers) + . = ..() + if (. || !has_buckled_mobs()) + return + retract() + return TRUE + +/// Goliath tentacle stun with special removal conditions +/datum/status_effect/incapacitating/stun/goliath_tentacled + id = "goliath_tentacled" + duration = 10 SECONDS + /// The tentacle that is tenderly holding us close + var/obj/effect/goliath_tentacle/tentacle + +/datum/status_effect/incapacitating/stun/goliath_tentacled/on_creation(mob/living/new_owner, set_duration, obj/effect/goliath_tentacle/tentacle) + . = ..() + if (!.) + return + src.tentacle = tentacle + +/datum/status_effect/incapacitating/stun/goliath_tentacled/on_apply() + . = ..() + RegisterSignal(owner, COMSIG_CARBON_PRE_MISC_HELP, PROC_REF(on_helped)) + RegisterSignals(owner, list(SIGNAL_ADDTRAIT(TRAIT_TENTACLE_IMMUNE), COMSIG_BRIMDUST_EXPLOSION), PROC_REF(release)) + RegisterSignals(tentacle, list(COMSIG_QDELETING, COMSIG_GOLIATH_TENTACLE_RETRACTING), PROC_REF(on_tentacle_left)) + +/datum/status_effect/incapacitating/stun/goliath_tentacled/on_remove() + . = ..() + UnregisterSignal(owner, list(COMSIG_CARBON_PRE_MISC_HELP, SIGNAL_ADDTRAIT(TRAIT_TENTACLE_IMMUNE), COMSIG_BRIMDUST_EXPLOSION)) + if (isnull(tentacle)) + return + UnregisterSignal(tentacle, list(COMSIG_QDELETING, COMSIG_GOLIATH_TENTACLE_RETRACTING)) + tentacle.retract() + tentacle = null + +/// Some kind soul has rescued us +/datum/status_effect/incapacitating/stun/goliath_tentacled/proc/on_helped(mob/source, mob/helping) + SIGNAL_HANDLER + release() + source.visible_message(span_notice("[helping] rips [source] from the tentacle's grasp!")) + return COMPONENT_BLOCK_MISC_HELP + +/// Something happened to make the tentacle let go +/datum/status_effect/incapacitating/stun/goliath_tentacled/proc/release() + SIGNAL_HANDLER + owner.remove_status_effect(/datum/status_effect/incapacitating/stun/goliath_tentacled) + +/// Something happened to our associated tentacle +/datum/status_effect/incapacitating/stun/goliath_tentacled/proc/on_tentacle_left() + SIGNAL_HANDLER + UnregisterSignal(tentacle, list(COMSIG_QDELETING, COMSIG_GOLIATH_TENTACLE_RETRACTING)) // No endless loops for us please + tentacle = null + release() diff --git a/code/modules/mob/living/basic/lavaland/mining.dm b/code/modules/mob/living/basic/lavaland/mining.dm index 4f2fb0b2dca..924f2b170c2 100644 --- a/code/modules/mob/living/basic/lavaland/mining.dm +++ b/code/modules/mob/living/basic/lavaland/mining.dm @@ -1,6 +1,7 @@ ///prototype for mining mobs /mob/living/basic/mining combat_mode = TRUE + mob_size = MOB_SIZE_LARGE faction = list(FACTION_MINING) unsuitable_atmos_damage = 0 minimum_survivable_temperature = 0 diff --git a/code/modules/mob/living/basic/space_fauna/cat_surgeon.dm b/code/modules/mob/living/basic/space_fauna/cat_surgeon.dm index ba5a627d62c..0ae22a320b6 100644 --- a/code/modules/mob/living/basic/space_fauna/cat_surgeon.dm +++ b/code/modules/mob/living/basic/space_fauna/cat_surgeon.dm @@ -40,6 +40,7 @@ /mob/living/basic/cat_butcherer/Initialize(mapload) . = ..() apply_dynamic_human_appearance(src, mob_spawn_path = /obj/effect/mob_spawn/corpse/human/cat_butcher, l_hand = /obj/item/circular_saw, bloody_slots = ITEM_SLOT_GLOVES|ITEM_SLOT_OCLOTHING) + AddElement(/datum/element/ai_retaliate) AddElement(/datum/element/death_drops, drop_on_death) RegisterSignal(src, COMSIG_HOSTILE_POST_ATTACKINGTARGET, PROC_REF(after_attack)) diff --git a/code/modules/mob/living/basic/space_fauna/meteor_heart/chasing_spikes.dm b/code/modules/mob/living/basic/space_fauna/meteor_heart/chasing_spikes.dm index 061553c8733..729352543ba 100644 --- a/code/modules/mob/living/basic/space_fauna/meteor_heart/chasing_spikes.dm +++ b/code/modules/mob/living/basic/space_fauna/meteor_heart/chasing_spikes.dm @@ -12,7 +12,7 @@ /datum/action/cooldown/chasing_spikes/Activate(atom/target) . = ..() playsound(owner, 'sound/magic/demon_attack1.ogg', vol = 100, vary = TRUE, pressure_affected = FALSE) - var/obj/effect/temp_visual/spike_chaser/chaser = new(get_turf(owner), target) + var/obj/effect/temp_visual/effect_trail/spike_chaser/chaser = new(get_turf(owner), target) LAZYADD(active_chasers, WEAKREF(chaser)) RegisterSignal(chaser, COMSIG_QDELETING, PROC_REF(on_chaser_destroyed)) @@ -27,42 +27,9 @@ return ..() /// An invisible effect which chases a target, spawning spikes every so often. -/obj/effect/temp_visual/spike_chaser +/obj/effect/temp_visual/effect_trail/spike_chaser name = "spike chaser" - desc = "An invisible effect, how did you examine this?" - icon = 'icons/mob/silicon/cameramob.dmi' - icon_state = "marker" - duration = 15 SECONDS - invisibility = INVISIBILITY_ABSTRACT - /// Speed at which we chase target - var/move_speed = 3 - /// What are we chasing? - var/datum/weakref/target - /// Handles chasing the target - var/datum/move_loop/movement - -/obj/effect/temp_visual/spike_chaser/Initialize(mapload, atom/target) - . = ..() - if (!target) - return INITIALIZE_HINT_QDEL - - AddElement(/datum/element/floor_loving) - AddComponent(/datum/component/spawner, spawn_types = list(/obj/effect/temp_visual/emerging_ground_spike), spawn_time = 0.5 SECONDS) - src.target = WEAKREF(target) - movement = SSmove_manager.move_towards(src, chasing = target, delay = move_speed, home = TRUE, timeout = duration, flags = MOVEMENT_LOOP_START_FAST) - - RegisterSignal(target, COMSIG_QDELETING, PROC_REF(on_target_invalid)) - if (isliving(target)) - RegisterSignal(target, COMSIG_LIVING_DEATH, PROC_REF(on_target_invalid)) - -/// Destroy ourselves if the target is no longer valid -/obj/effect/temp_visual/spike_chaser/proc/on_target_invalid() - SIGNAL_HANDLER - qdel(src) - -/obj/effect/temp_visual/spike_chaser/Destroy() - QDEL_NULL(movement) - return ..() + spawned_effect = /obj/effect/temp_visual/emerging_ground_spike /// A spike comes out of the ground, dealing damage after a short delay /obj/effect/temp_visual/emerging_ground_spike diff --git a/code/modules/mob/living/living.dm b/code/modules/mob/living/living.dm index 9d69cf0eec9..cd6ab537828 100644 --- a/code/modules/mob/living/living.dm +++ b/code/modules/mob/living/living.dm @@ -1390,6 +1390,7 @@ /mob/living/basic/cow, /mob/living/basic/giant_spider, /mob/living/basic/giant_spider/hunter, + /mob/living/basic/mining/goliath, /mob/living/basic/headslug, /mob/living/basic/killer_tomato, /mob/living/basic/lizard, @@ -1404,7 +1405,6 @@ /mob/living/basic/stickman/dog, /mob/living/simple_animal/crab, /mob/living/simple_animal/hostile/asteroid/basilisk/watcher, - /mob/living/simple_animal/hostile/asteroid/goliath/beast, /mob/living/simple_animal/hostile/bear, /mob/living/simple_animal/hostile/blob/blobbernaut/independent, /mob/living/simple_animal/hostile/gorilla, diff --git a/code/modules/mob/living/simple_animal/hostile/mining_mobs/elites/goliath_broodmother.dm b/code/modules/mob/living/simple_animal/hostile/mining_mobs/elites/goliath_broodmother.dm index 07ea2e881af..7ad3f2a3d27 100644 --- a/code/modules/mob/living/simple_animal/hostile/mining_mobs/elites/goliath_broodmother.dm +++ b/code/modules/mob/living/simple_animal/hostile/mining_mobs/elites/goliath_broodmother.dm @@ -53,6 +53,10 @@ var/rand_tent = 0 var/list/mob/living/simple_animal/hostile/asteroid/elite/broodmother_child/children_list = list() +/mob/living/simple_animal/hostile/asteroid/elite/broodmother/Initialize(mapload) + . = ..() + ADD_TRAIT(src, TRAIT_TENTACLE_IMMUNE, INNATE_TRAIT) + /datum/action/innate/elite_attack/tentacle_patch name = "Tentacle Patch" button_icon_state = "tentacle_patch" @@ -112,7 +116,7 @@ var/tentacle_loc = spiral_range_turfs(5, get_turf(src)) for(var/i in 1 to tentacle_amount) var/turf/t = pick_n_take(tentacle_loc) - new /obj/effect/temp_visual/goliath_tentacle/broodmother(t, src) + new /obj/effect/goliath_tentacle/broodmother(t, src) /mob/living/simple_animal/hostile/asteroid/elite/broodmother/proc/tentacle_patch(target) ranged_cooldown = world.time + 15 @@ -120,7 +124,7 @@ if(!isturf(tturf)) return visible_message(span_warning("[src] digs its tentacles under [target]!")) - new /obj/effect/temp_visual/goliath_tentacle/broodmother/patch(tturf, src) + new /obj/effect/goliath_tentacle/broodmother/patch(tturf, src) /mob/living/simple_animal/hostile/asteroid/elite/broodmother/proc/spawn_children(target) ranged_cooldown = world.time + 40 @@ -188,6 +192,10 @@ status_flags = CANPUSH var/mob/living/simple_animal/hostile/asteroid/elite/broodmother/mother = null +/mob/living/simple_animal/hostile/asteroid/elite/broodmother_child/Initialize(mapload) + . = ..() + ADD_TRAIT(src, TRAIT_TENTACLE_IMMUNE, INNATE_TRAIT) + /mob/living/simple_animal/hostile/asteroid/elite/broodmother_child/OpenFire(target) ranged_cooldown = world.time + 40 var/tturf = get_turf(target) @@ -195,7 +203,7 @@ return if(get_dist(src, target) <= 7)//Screen range check, so it can't attack people off-screen visible_message(span_warning("[src] digs one of its tentacles under [target]!")) - new /obj/effect/temp_visual/goliath_tentacle/broodmother(tturf, src) + new /obj/effect/goliath_tentacle/broodmother(tturf, src) /mob/living/simple_animal/hostile/asteroid/elite/broodmother_child/death() . = ..() @@ -205,35 +213,24 @@ explosion(src, flame_range = 3, adminlog = FALSE) gib() -//Tentacles have less stun time compared to regular variant, to balance being able to use them much more often. Also, 10 more damage. -/obj/effect/temp_visual/goliath_tentacle/broodmother/trip() - var/latched = FALSE - for(var/mob/living/L in loc) - if((!QDELETED(spawner) && spawner.faction_check_mob(L)) || L.stat == DEAD) - continue - visible_message(span_danger("[src] grabs hold of [L]!")) - L.Stun(10) - L.adjustBruteLoss(rand(30,35)) - latched = TRUE - if(!latched) - retract() - else - deltimer(timerid) - timerid = addtimer(CALLBACK(src, PROC_REF(retract)), 10, TIMER_STOPPABLE) +/obj/effect/goliath_tentacle/broodmother + grapple_time = 1 SECONDS + min_damage = 30 + max_damage = 35 -/obj/effect/temp_visual/goliath_tentacle/broodmother/patch/Initialize(mapload, new_spawner) +/obj/effect/goliath_tentacle/broodmother/patch/Initialize(mapload, new_spawner) . = ..() INVOKE_ASYNC(src, PROC_REF(createpatch)) -/obj/effect/temp_visual/goliath_tentacle/broodmother/patch/proc/createpatch() +/obj/effect/goliath_tentacle/broodmother/patch/proc/createpatch() var/tentacle_locs = spiral_range_turfs(1, get_turf(src)) for(var/T in tentacle_locs) - new /obj/effect/temp_visual/goliath_tentacle/broodmother(T, spawner) + new /obj/effect/goliath_tentacle/broodmother(T) var/list/directions = GLOB.cardinals.Copy() for(var/i in directions) var/turf/T = get_step(get_turf(src), i) T = get_step(T, i) - new /obj/effect/temp_visual/goliath_tentacle/broodmother(T, spawner) + new /obj/effect/goliath_tentacle/broodmother(T) // Broodmother's loot: Broodmother Tongue /obj/item/crusher_trophy/broodmother_tongue @@ -251,7 +248,7 @@ /obj/item/crusher_trophy/broodmother_tongue/on_mark_detonation(mob/living/target, mob/living/user) if(prob(bonus_value) && target.stat != DEAD) - new /obj/effect/temp_visual/goliath_tentacle/broodmother/patch(get_turf(target), user) + new /obj/effect/goliath_tentacle/broodmother/patch(get_turf(target), user) /obj/item/crusher_trophy/broodmother_tongue/attack_self(mob/user) if(!isliving(user)) diff --git a/code/modules/mob/living/simple_animal/hostile/mining_mobs/goliath.dm b/code/modules/mob/living/simple_animal/hostile/mining_mobs/goliath.dm deleted file mode 100644 index 12175696fe4..00000000000 --- a/code/modules/mob/living/simple_animal/hostile/mining_mobs/goliath.dm +++ /dev/null @@ -1,243 +0,0 @@ -//A slow but strong beast that tries to stun using its tentacles -/mob/living/simple_animal/hostile/asteroid/goliath - name = "goliath" - desc = "A massive beast that uses long tentacles to ensnare its prey, threatening them is not advised under any conditions." - icon = 'icons/mob/simple/lavaland/lavaland_monsters_wide.dmi' - icon_state = "ancient_goliath" - icon_living = "ancient_goliath" - icon_aggro = "ancient_goliath_alert" - icon_dead = "ancient_goliath_dead" - icon_gib = "syndicate_gib" - pixel_x = -12 - base_pixel_x = -12 - mob_biotypes = MOB_ORGANIC|MOB_BEAST - mouse_opacity = MOUSE_OPACITY_ICON - move_to_delay = 40 - ranged = 1 - ranged_cooldown_time = 120 - friendly_verb_continuous = "wails at" - friendly_verb_simple = "wail at" - speak_emote = list("bellows") - speed = 3 - maxHealth = 300 - health = 300 - harm_intent_damage = 0 - obj_damage = 100 - melee_damage_lower = 25 - melee_damage_upper = 25 - attack_verb_continuous = "pulverizes" - attack_verb_simple = "pulverize" - attack_sound = 'sound/weapons/punch1.ogg' - throw_message = "does nothing to the rocky hide of the" - vision_range = 5 - aggro_vision_range = 9 - move_force = MOVE_FORCE_VERY_STRONG - move_resist = MOVE_FORCE_VERY_STRONG - pull_force = MOVE_FORCE_VERY_STRONG - gender = MALE//lavaland elite goliath says that i'''' 't s female and i ''t s stronger because of sexual dimorphism, so normal goliaths should be male - var/pre_attack = 0 - var/pre_attack_icon = "ancient_goliath_preattack" - loot = list(/obj/item/stack/sheet/animalhide/goliath_hide) - - footstep_type = FOOTSTEP_MOB_HEAVY - -/mob/living/simple_animal/hostile/asteroid/goliath/Life(seconds_per_tick = SSMOBS_DT, times_fired) - . = ..() - handle_preattack() - -/mob/living/simple_animal/hostile/asteroid/goliath/proc/handle_preattack() - if(ranged_cooldown <= world.time + ranged_cooldown_time*0.25 && !pre_attack) - pre_attack++ - if(!pre_attack || stat || AIStatus == AI_IDLE) - return - icon_state = pre_attack_icon - -/mob/living/simple_animal/hostile/asteroid/goliath/revive(full_heal_flags = NONE, excess_healing = 0, force_grab_ghost = FALSE)//who the fuck anchors mobs - . = ..() - if(!.) - return - - move_force = initial(move_force) - move_resist = initial(move_resist) - pull_force = initial(pull_force) - -/mob/living/simple_animal/hostile/asteroid/goliath/death(gibbed) - move_force = MOVE_FORCE_DEFAULT - move_resist = MOVE_RESIST_DEFAULT - pull_force = PULL_FORCE_DEFAULT - return ..() - -/mob/living/simple_animal/hostile/asteroid/goliath/OpenFire() - var/tturf = get_turf(target) - if(!isturf(tturf)) - return - if(get_dist(src, target) <= 7)//Screen range check, so you can't get tentacle'd offscreen - visible_message(span_warning("[src] digs its tentacles under [target]!")) - new /obj/effect/temp_visual/goliath_tentacle/original(tturf, src) - ranged_cooldown = world.time + ranged_cooldown_time - icon_state = icon_aggro - pre_attack = 0 - -/mob/living/simple_animal/hostile/asteroid/goliath/adjustHealth(amount, updating_health = TRUE, forced = FALSE) - ranged_cooldown -= 10 - handle_preattack() - . = ..() - -/mob/living/simple_animal/hostile/asteroid/goliath/Aggro() - vision_range = aggro_vision_range - handle_preattack() - if(icon_state != icon_aggro) - icon_state = icon_aggro - -//Lavaland Goliath -/mob/living/simple_animal/hostile/asteroid/goliath/beast - name = "goliath" - desc = "A hulking, armor-plated beast with long tendrils arching from its back." - icon = 'icons/mob/simple/lavaland/lavaland_monsters_wide.dmi' - icon_state = "goliath" - icon_living = "goliath" - icon_aggro = "goliath" - icon_dead = "goliath_dead" - throw_message = "does nothing to the tough hide of the" - pre_attack_icon = "goliath_preattack" - crusher_loot = /obj/item/crusher_trophy/goliath_tentacle - butcher_results = list(/obj/item/food/meat/slab/goliath = 2, /obj/item/stack/sheet/bone = 2) - guaranteed_butcher_results = list(/obj/item/stack/sheet/animalhide/goliath_hide = 1) - loot = list() - stat_attack = HARD_CRIT - robust_searching = 1 - - var/can_saddle = FALSE - var/saddled = FALSE - -/mob/living/simple_animal/hostile/asteroid/goliath/beast/Initialize(mapload) - . = ..() - AddComponent(/datum/component/tameable, food_types = list(/obj/item/food/grown/ash_flora), tame_chance = 10, bonus_tame_chance = 5, after_tame = CALLBACK(src, PROC_REF(tamed))) - -/mob/living/simple_animal/hostile/asteroid/goliath/beast/attackby(obj/item/O, mob/user, params) - if(!istype(O, /obj/item/saddle) || saddled) - return ..() - - if(can_saddle && do_after(user,55,target=src)) - user.visible_message(span_notice("You manage to put [O] on [src], you can now ride [p_them()].")) - qdel(O) - saddled = TRUE - buckle_lying = 0 - add_overlay("goliath_saddled") - AddElement(/datum/element/ridable, /datum/component/riding/creature/goliath) - else - user.visible_message(span_warning("[src] is rocking around! You can't put the saddle on!")) - ..() - -/mob/living/simple_animal/hostile/asteroid/goliath/beast/proc/tamed(mob/living/tamer) - can_saddle = TRUE - -/mob/living/simple_animal/hostile/asteroid/goliath/beast/random/Initialize(mapload) - . = ..() - if(prob(1)) - new /mob/living/simple_animal/hostile/asteroid/goliath/beast/ancient(loc) - return INITIALIZE_HINT_QDEL - -/mob/living/simple_animal/hostile/asteroid/goliath/beast/ancient - name = "ancient goliath" - desc = "Goliaths are biologically immortal, and rare specimens have survived for centuries. This one is clearly ancient, and its tentacles constantly churn the earth around it." - icon_state = "ancient_goliath" - icon_living = "ancient_goliath" - icon_aggro = "ancient_goliath_alert" - icon_dead = "ancient_goliath_dead" - maxHealth = 400 - health = 400 - speed = 4 - pre_attack_icon = "ancient_goliath_preattack" - throw_message = "does nothing to the rocky hide of the" - loot = list(/obj/item/stack/sheet/animalhide/goliath_hide) //A throwback to the asteroid days - butcher_results = list(/obj/item/food/meat/slab/goliath = 2, /obj/item/stack/sheet/bone = 2) - guaranteed_butcher_results = list() - crusher_drop_mod = 30 - wander = FALSE - var/list/cached_tentacle_turfs - var/turf/last_location - var/tentacle_recheck_cooldown = 100 - -/mob/living/simple_animal/hostile/asteroid/goliath/beast/ancient/Life(seconds_per_tick = SSMOBS_DT, times_fired) - . = ..() - if(!.) // dead - return - if(isturf(loc)) - if(!LAZYLEN(cached_tentacle_turfs) || loc != last_location || tentacle_recheck_cooldown <= world.time) - LAZYCLEARLIST(cached_tentacle_turfs) - last_location = loc - tentacle_recheck_cooldown = world.time + initial(tentacle_recheck_cooldown) - for(var/turf/open/T in orange(4, loc)) - LAZYADD(cached_tentacle_turfs, T) - for(var/t in cached_tentacle_turfs) - if(isopenturf(t)) - if(prob(10)) - new /obj/effect/temp_visual/goliath_tentacle(t, src) - else - cached_tentacle_turfs -= t - -/mob/living/simple_animal/hostile/asteroid/goliath/beast/tendril - from_spawner = TRUE - -//tentacles -/obj/effect/temp_visual/goliath_tentacle - name = "goliath tentacle" - icon = 'icons/mob/simple/lavaland/lavaland_monsters.dmi' - icon_state = "goliath_tentacle_spawn" - layer = BELOW_MOB_LAYER - plane = GAME_PLANE - var/mob/living/spawner - -/obj/effect/temp_visual/goliath_tentacle/Initialize(mapload, mob/living/new_spawner) - . = ..() - for(var/obj/effect/temp_visual/goliath_tentacle/T in loc) - if(T != src) - return INITIALIZE_HINT_QDEL - if(!QDELETED(new_spawner)) - spawner = new_spawner - if(ismineralturf(loc)) - var/turf/closed/mineral/M = loc - M.gets_drilled() - deltimer(timerid) - timerid = addtimer(CALLBACK(src, PROC_REF(tripanim)), 7, TIMER_STOPPABLE) - -/obj/effect/temp_visual/goliath_tentacle/original/Initialize(mapload, new_spawner) - . = ..() - var/list/directions = GLOB.cardinals.Copy() - for(var/i in 1 to 3) - var/spawndir = pick_n_take(directions) - var/turf/T = get_step(src, spawndir) - if(T) - new /obj/effect/temp_visual/goliath_tentacle(T, spawner) - -/obj/effect/temp_visual/goliath_tentacle/proc/tripanim() - icon_state = "goliath_tentacle_wiggle" - deltimer(timerid) - timerid = addtimer(CALLBACK(src, PROC_REF(trip)), 3, TIMER_STOPPABLE) - -/obj/effect/temp_visual/goliath_tentacle/proc/trip() - var/latched = FALSE - for(var/mob/living/L in loc) - if((!QDELETED(spawner) && spawner.faction_check_mob(L)) || L.stat == DEAD) - continue - visible_message(span_danger("[src] grabs hold of [L]!")) - L.Stun(100) - L.adjustBruteLoss(rand(10,15)) - latched = TRUE - if(!latched) - retract() - else - deltimer(timerid) - timerid = addtimer(CALLBACK(src, PROC_REF(retract)), 10, TIMER_STOPPABLE) - -/obj/effect/temp_visual/goliath_tentacle/proc/retract() - icon_state = "goliath_tentacle_retract" - deltimer(timerid) - timerid = QDEL_IN_STOPPABLE(src, 7) - -/obj/item/saddle - name = "saddle" - desc = "This saddle will solve all your problems with being killed by lava beasts!" - icon = 'icons/obj/mining.dmi' - icon_state = "goliath_saddle" diff --git a/code/modules/mob_spawn/corpses/nonhuman_corpses.dm b/code/modules/mob_spawn/corpses/nonhuman_corpses.dm index 3b8877b3e8a..e80273e1acc 100644 --- a/code/modules/mob_spawn/corpses/nonhuman_corpses.dm +++ b/code/modules/mob_spawn/corpses/nonhuman_corpses.dm @@ -40,7 +40,7 @@ ///dead goliath spawner /obj/effect/mob_spawn/corpse/goliath - mob_type = /mob/living/simple_animal/hostile/asteroid/goliath/beast + mob_type = /mob/living/basic/mining/goliath icon = 'icons/mob/simple/lavaland/lavaland_monsters_wide.dmi' icon_state = "goliath_dead_helper" pixel_x = -12 diff --git a/code/modules/procedural_mapping/mapGenerators/asteroid.dm b/code/modules/procedural_mapping/mapGenerators/asteroid.dm index 18b266fc671..3da5c0d6182 100644 --- a/code/modules/procedural_mapping/mapGenerators/asteroid.dm +++ b/code/modules/procedural_mapping/mapGenerators/asteroid.dm @@ -19,9 +19,11 @@ //Monsters /datum/map_generator_module/splatter_layer/asteroid_monsters spawnableTurfs = list() - spawnableAtoms = list(/mob/living/simple_animal/hostile/asteroid/basilisk = 10, \ - /mob/living/simple_animal/hostile/asteroid/hivelord = 10, \ - /mob/living/simple_animal/hostile/asteroid/goliath = 10) + spawnableAtoms = list( + /mob/living/basic/mining/goliath/ancient = 10, + /mob/living/simple_animal/hostile/asteroid/basilisk = 10, + /mob/living/simple_animal/hostile/asteroid/hivelord = 10, + ) // GENERATORS diff --git a/code/modules/procedural_mapping/mapGenerators/lavaland.dm b/code/modules/procedural_mapping/mapGenerators/lavaland.dm index 9cbc5244ae2..d262a317a13 100644 --- a/code/modules/procedural_mapping/mapGenerators/lavaland.dm +++ b/code/modules/procedural_mapping/mapGenerators/lavaland.dm @@ -10,7 +10,7 @@ /datum/map_generator_module/splatter_layer/lavaland_monsters spawnableTurfs = list() - spawnableAtoms = list(/mob/living/simple_animal/hostile/asteroid/goliath/beast = 10, + spawnableAtoms = list(/mob/living/basic/mining/goliath = 10, /mob/living/simple_animal/hostile/asteroid/hivelord/legion = 10, /mob/living/simple_animal/hostile/asteroid/basilisk/watcher = 10) diff --git a/code/modules/unit_tests/simple_animal_freeze.dm b/code/modules/unit_tests/simple_animal_freeze.dm index c1778bc3b17..d6614b6581d 100644 --- a/code/modules/unit_tests/simple_animal_freeze.dm +++ b/code/modules/unit_tests/simple_animal_freeze.dm @@ -73,11 +73,6 @@ /mob/living/simple_animal/hostile/asteroid/elite/legionnairehead, /mob/living/simple_animal/hostile/asteroid/elite/pandora, /mob/living/simple_animal/hostile/asteroid/goldgrub, - /mob/living/simple_animal/hostile/asteroid/goliath, - /mob/living/simple_animal/hostile/asteroid/goliath/beast, - /mob/living/simple_animal/hostile/asteroid/goliath/beast/ancient, - /mob/living/simple_animal/hostile/asteroid/goliath/beast/random, - /mob/living/simple_animal/hostile/asteroid/goliath/beast/tendril, /mob/living/simple_animal/hostile/asteroid/gutlunch, /mob/living/simple_animal/hostile/asteroid/gutlunch/grublunch, /mob/living/simple_animal/hostile/asteroid/gutlunch/gubbuck, diff --git a/code/modules/vehicles/vehicle_key.dm b/code/modules/vehicles/vehicle_key.dm index 005cdf54ab2..e1b45d55f0a 100644 --- a/code/modules/vehicles/vehicle_key.dm +++ b/code/modules/vehicles/vehicle_key.dm @@ -76,7 +76,7 @@ /obj/item/key/lasso name = "bone lasso" - desc = "Perfect for taming all kinds of supernatural beasts! (Warning: only perfect for taming one kind of supernatural beast.)" + desc = "The perfect tool for directing a Goliath! If only it made them move any faster..." force = 12 icon_state = "lasso" inhand_icon_state = "chain" diff --git a/icons/mob/simple/lavaland/lavaland_monsters.dmi b/icons/mob/simple/lavaland/lavaland_monsters.dmi index 4e074a70eea..c167b45e629 100644 Binary files a/icons/mob/simple/lavaland/lavaland_monsters.dmi and b/icons/mob/simple/lavaland/lavaland_monsters.dmi differ diff --git a/icons/mob/simple/lavaland/lavaland_monsters_wide.dmi b/icons/mob/simple/lavaland/lavaland_monsters_wide.dmi index 6142b5069d7..60d8cc16cae 100644 Binary files a/icons/mob/simple/lavaland/lavaland_monsters_wide.dmi and b/icons/mob/simple/lavaland/lavaland_monsters_wide.dmi differ diff --git a/icons/mob/telegraphing/telegraph.dmi b/icons/mob/telegraphing/telegraph.dmi index 4d9e55d534f..d5e03419cd8 100644 Binary files a/icons/mob/telegraphing/telegraph.dmi and b/icons/mob/telegraphing/telegraph.dmi differ diff --git a/icons/obj/mining.dmi b/icons/obj/mining.dmi index c029b52ba9b..09057c265b5 100644 Binary files a/icons/obj/mining.dmi and b/icons/obj/mining.dmi differ diff --git a/tgstation.dme b/tgstation.dme index eaad164d186..2f99e17665c 100644 --- a/tgstation.dme +++ b/tgstation.dme @@ -291,6 +291,7 @@ #include "code\__DEFINES\dcs\signals\signals_light_eater.dm" #include "code\__DEFINES\dcs\signals\signals_medical.dm" #include "code\__DEFINES\dcs\signals\signals_mind.dm" +#include "code\__DEFINES\dcs\signals\signals_mining.dm" #include "code\__DEFINES\dcs\signals\signals_mod.dm" #include "code\__DEFINES\dcs\signals\signals_moveloop.dm" #include "code\__DEFINES\dcs\signals\signals_movetype.dm" @@ -876,6 +877,7 @@ #include "code\datums\components\attached_sticker.dm" #include "code\datums\components\aura_healing.dm" #include "code\datums\components\bakeable.dm" +#include "code\datums\components\basic_mob_attack_telegraph.dm" #include "code\datums\components\beetlejuice.dm" #include "code\datums\components\blood_walk.dm" #include "code\datums\components\bloodysoles.dm" @@ -988,6 +990,7 @@ #include "code\datums\components\religious_tool.dm" #include "code\datums\components\remote_materials.dm" #include "code\datums\components\rename.dm" +#include "code\datums\components\revenge_ability.dm" #include "code\datums\components\rot.dm" #include "code\datums\components\rotation.dm" #include "code\datums\components\scope.dm" @@ -1237,6 +1240,7 @@ #include "code\datums\elements\light_eater.dm" #include "code\datums\elements\loomable.dm" #include "code\datums\elements\mob_killed_tally.dm" +#include "code\datums\elements\move_cooldown.dm" #include "code\datums\elements\movement_turf_changer.dm" #include "code\datums\elements\movetype_handler.dm" #include "code\datums\elements\nerfed_pulling.dm" @@ -1840,6 +1844,7 @@ #include "code\game\objects\effects\spawners\random\trash.dm" #include "code\game\objects\effects\spawners\random\vending.dm" #include "code\game\objects\effects\temporary_visuals\cult.dm" +#include "code\game\objects\effects\temporary_visuals\effect_trail.dm" #include "code\game\objects\effects\temporary_visuals\miscellaneous.dm" #include "code\game\objects\effects\temporary_visuals\temporary_visual.dm" #include "code\game\objects\effects\temporary_visuals\projectiles\impact.dm" @@ -4030,6 +4035,10 @@ #include "code\modules\mob\living\basic\lavaland\bileworm\bileworm_instrument.dm" #include "code\modules\mob\living\basic\lavaland\bileworm\bileworm_loot.dm" #include "code\modules\mob\living\basic\lavaland\bileworm\bileworm_vileworm.dm" +#include "code\modules\mob\living\basic\lavaland\goliath\goliath.dm" +#include "code\modules\mob\living\basic\lavaland\goliath\goliath_actions.dm" +#include "code\modules\mob\living\basic\lavaland\goliath\goliath_ai.dm" +#include "code\modules\mob\living\basic\lavaland\goliath\tentacle.dm" #include "code\modules\mob\living\basic\pets\dog.dm" #include "code\modules\mob\living\basic\pets\fox.dm" #include "code\modules\mob\living\basic\pets\pet.dm" @@ -4334,7 +4343,6 @@ #include "code\modules\mob\living\simple_animal\hostile\mining_mobs\brimdemon.dm" #include "code\modules\mob\living\simple_animal\hostile\mining_mobs\curse_blob.dm" #include "code\modules\mob\living\simple_animal\hostile\mining_mobs\goldgrub.dm" -#include "code\modules\mob\living\simple_animal\hostile\mining_mobs\goliath.dm" #include "code\modules\mob\living\simple_animal\hostile\mining_mobs\gutlunch.dm" #include "code\modules\mob\living\simple_animal\hostile\mining_mobs\hivelord.dm" #include "code\modules\mob\living\simple_animal\hostile\mining_mobs\ice_demon.dm" diff --git a/tools/UpdatePaths/Scripts/76754_simple_to_basic_goliath.txt b/tools/UpdatePaths/Scripts/76754_simple_to_basic_goliath.txt new file mode 100644 index 00000000000..b0be0bcb32b --- /dev/null +++ b/tools/UpdatePaths/Scripts/76754_simple_to_basic_goliath.txt @@ -0,0 +1,5 @@ +/mob/living/simple_animal/hostile/asteroid/goliath/beast : /mob/living/basic/mining/goliath{@OLD} +/mob/living/simple_animal/hostile/asteroid/goliath/beast/random : /mob/living/basic/mining/goliath/random{@OLD} +/mob/living/simple_animal/hostile/asteroid/goliath : /mob/living/basic/mining/goliath/ancient{@OLD} +/mob/living/simple_animal/hostile/asteroid/goliath/beast/ancient : /mob/living/basic/mining/goliath/ancient/immortal{@OLD} +/obj/item/saddle : /obj/item/goliath_saddle{@OLD}