diff --git a/code/__DEFINES/ai/monsters.dm b/code/__DEFINES/ai/monsters.dm index be9a4be34cd..89670d4f346 100644 --- a/code/__DEFINES/ai/monsters.dm +++ b/code/__DEFINES/ai/monsters.dm @@ -147,6 +147,22 @@ /// key holds the tray we will beam #define BB_BEAMABLE_HYDROPLANT_TARGET "beamable_hydroplant_target" +//ice demons +///the list of items we are afraid of +#define BB_LIST_SCARY_ITEMS "list_scary_items" +///our teleportation ability +#define BB_DEMON_TELEPORT_ABILITY "demon_teleport_ability" +///the destination of our teleport ability +#define BB_TELEPORT_DESTINATION "teleport_destination" +///the ability to clone ourself +#define BB_DEMON_CLONE_ABILITY "demon_clone_ability" +///our slippery ice ability +#define BB_DEMON_SLIP_ABILITY "demon_slip_ability" +///the turf we are escaping too +#define BB_ESCAPE_DESTINATION "escape_destination" +///how far away we will be from our target before teleporting +#define BB_MINIMUM_DISTANCE_RANGE "minimum_distance_range" + /// Corpse we have consumed #define BB_LEGION_CORPSE "legion_corpse" /// Things our target recently said diff --git a/code/datums/ai/basic_mobs/basic_subtrees/maintain_distance.dm b/code/datums/ai/basic_mobs/basic_subtrees/maintain_distance.dm index c09e7cdbf75..2a85e9e902b 100644 --- a/code/datums/ai/basic_mobs/basic_subtrees/maintain_distance.dm +++ b/code/datums/ai/basic_mobs/basic_subtrees/maintain_distance.dm @@ -8,6 +8,8 @@ var/maximum_distance = 6 /// How far do we look for our target? var/view_distance = 10 + /// the run away behavior we will use + var/run_away_behavior = /datum/ai_behavior/step_away /datum/ai_planning_subtree/maintain_distance/SelectBehaviors(datum/ai_controller/controller, seconds_per_tick) . = ..() @@ -16,12 +18,15 @@ return // Don't run away from cucumbers, they're not snakes var/range = get_dist(controller.pawn, target) if (range < minimum_distance) - controller.queue_behavior(/datum/ai_behavior/step_away, target_key) + controller.queue_behavior(run_away_behavior, target_key, minimum_distance) return if (range > maximum_distance) controller.queue_behavior(/datum/ai_behavior/pursue_to_range, target_key, maximum_distance) return +/datum/ai_planning_subtree/maintain_distance/cover_minimum_distance + run_away_behavior = /datum/ai_behavior/cover_minimum_distance + /// Take one step away /datum/ai_behavior/step_away behavior_flags = AI_BEHAVIOR_REQUIRE_MOVEMENT | AI_BEHAVIOR_CAN_PLAN_DURING_EXECUTION @@ -80,3 +85,32 @@ if (!QDELETED(current_target) && get_dist(controller.pawn, current_target) > range) return finish_action(controller, succeeded = TRUE) + +///instead of taking a single step, we cover the entire distance +/datum/ai_behavior/cover_minimum_distance + behavior_flags = AI_BEHAVIOR_REQUIRE_MOVEMENT | AI_BEHAVIOR_CAN_PLAN_DURING_EXECUTION + required_distance = 0 + action_cooldown = 0.2 SECONDS + +/datum/ai_behavior/cover_minimum_distance/setup(datum/ai_controller/controller, target_key, minimum_distance) + . = ..() + var/atom/target = controller.blackboard[target_key] + if(QDELETED(target)) + return FALSE + var/required_distance = minimum_distance - get_dist(controller.pawn, target) //the distance we need to move + var/distance = 0 + var/turf/chosen_turf + for(var/turf/open/potential_turf in oview(required_distance, controller.pawn)) + var/new_distance_from_target = get_dist(potential_turf, target) + if(potential_turf.is_blocked_turf()) + continue + if(new_distance_from_target > distance) + chosen_turf = potential_turf + distance = new_distance_from_target + if(isnull(chosen_turf)) + return FALSE + set_movement_target(controller, target = chosen_turf) + +/datum/ai_behavior/cover_minimum_distance/perform(seconds_per_tick, datum/ai_controller/controller, target_key) + . = ..() + finish_action(controller, succeeded = TRUE) diff --git a/code/datums/ai/basic_mobs/basic_subtrees/ranged_skirmish.dm b/code/datums/ai/basic_mobs/basic_subtrees/ranged_skirmish.dm index 1ff752d925f..be395f3dfe4 100644 --- a/code/datums/ai/basic_mobs/basic_subtrees/ranged_skirmish.dm +++ b/code/datums/ai/basic_mobs/basic_subtrees/ranged_skirmish.dm @@ -4,28 +4,28 @@ /// Blackboard key holding target atom var/target_key = BB_BASIC_MOB_CURRENT_TARGET /// What AI behaviour do we actually run? - var/datum/ai_behavior/ranged_skirmish/attack_behavior = /datum/ai_behavior/ranged_skirmish - -/datum/ai_planning_subtree/ranged_skirmish/SelectBehaviors(datum/ai_controller/controller, seconds_per_tick) - . = ..() - if(!controller.blackboard_key_exists(target_key)) - return - controller.queue_behavior(attack_behavior, target_key, BB_TARGETTING_DATUM, BB_BASIC_MOB_CURRENT_TARGET_HIDING_LOCATION) - -/// How often will we try to perform our ranged attack? -/datum/ai_behavior/ranged_skirmish - action_cooldown = 1 SECONDS + var/attack_behavior = /datum/ai_behavior/ranged_skirmish /// If target is further away than this we don't fire var/max_range = 9 /// If target is closer than this we don't fire var/min_range = 2 -/datum/ai_behavior/ranged_skirmish/setup(datum/ai_controller/controller, target_key, targetting_datum_key, hiding_location_key) +/datum/ai_planning_subtree/ranged_skirmish/SelectBehaviors(datum/ai_controller/controller, seconds_per_tick) + . = ..() + if(!controller.blackboard_key_exists(target_key)) + return + controller.queue_behavior(attack_behavior, target_key, BB_TARGETTING_DATUM, BB_BASIC_MOB_CURRENT_TARGET_HIDING_LOCATION, max_range, min_range) + +/// How often will we try to perform our ranged attack? +/datum/ai_behavior/ranged_skirmish + action_cooldown = 1 SECONDS + +/datum/ai_behavior/ranged_skirmish/setup(datum/ai_controller/controller, target_key, targetting_datum_key, hiding_location_key, max_range, min_range) . = ..() var/atom/target = controller.blackboard[hiding_location_key] || controller.blackboard[target_key] return !QDELETED(target) -/datum/ai_behavior/ranged_skirmish/perform(seconds_per_tick, datum/ai_controller/controller, target_key, targetting_datum_key, hiding_location_key) +/datum/ai_behavior/ranged_skirmish/perform(seconds_per_tick, datum/ai_controller/controller, target_key, targetting_datum_key, hiding_location_key, max_range, min_range) . = ..() var/atom/target = controller.blackboard[target_key] if (QDELETED(target)) diff --git a/code/datums/ai/basic_mobs/basic_subtrees/teleport_away_from_target.dm b/code/datums/ai/basic_mobs/basic_subtrees/teleport_away_from_target.dm new file mode 100644 index 00000000000..dadba992e9f --- /dev/null +++ b/code/datums/ai/basic_mobs/basic_subtrees/teleport_away_from_target.dm @@ -0,0 +1,56 @@ +///behavior to activate ability to escape from target +/datum/ai_planning_subtree/teleport_away_from_target + ///minimum distance away from the target before we execute behavior + var/minimum_distance = 2 + ///the ability we will execute + var/ability_key + +/datum/ai_planning_subtree/teleport_away_from_target/SelectBehaviors(datum/ai_controller/controller, seconds_per_tick) + if(!controller.blackboard_key_exists(BB_BASIC_MOB_CURRENT_TARGET)) + return + var/atom/target = controller.blackboard[BB_BASIC_MOB_CURRENT_TARGET] + var/distance_from_target = get_dist(target, controller.pawn) + if(distance_from_target >= minimum_distance) + controller.clear_blackboard_key(BB_ESCAPE_DESTINATION) + return + var/datum/action/cooldown/ability = controller.blackboard[ability_key] + if(!ability?.IsAvailable()) + return + var/turf/location_turf = controller.blackboard[BB_ESCAPE_DESTINATION] + + if(isnull(location_turf)) + controller.queue_behavior(/datum/ai_behavior/find_furthest_turf_from_target, BB_BASIC_MOB_CURRENT_TARGET, BB_ESCAPE_DESTINATION, minimum_distance) + return SUBTREE_RETURN_FINISH_PLANNING + + if(get_dist(location_turf, target) < minimum_distance || !can_see(controller.pawn, location_turf)) //target moved close too close or we moved too far since finding the target turf + controller.clear_blackboard_key(BB_ESCAPE_DESTINATION) + return + + controller.queue_behavior(/datum/ai_behavior/targeted_mob_ability/and_clear_target, ability_key, BB_ESCAPE_DESTINATION) + +///find furtherst turf target so we may teleport to it +/datum/ai_behavior/find_furthest_turf_from_target + +/datum/ai_behavior/find_furthest_turf_from_target/perform(seconds_per_tick, datum/ai_controller/controller, target_key, set_key, range) + var/mob/living/living_target = controller.blackboard[target_key] + if(QDELETED(living_target)) + return + + var/distance = 0 + var/turf/chosen_turf + for(var/turf/open/potential_destination in oview(range, living_target)) + if(potential_destination.is_blocked_turf()) + continue + var/new_distance_to_target = get_dist(potential_destination, living_target) + if(new_distance_to_target > distance) + chosen_turf = potential_destination + distance = new_distance_to_target + if(distance == range) + break //we have already found the max distance + + if(isnull(chosen_turf)) + finish_action(controller, FALSE) + return + + controller.set_blackboard_key(set_key, chosen_turf) + finish_action(controller, TRUE) diff --git a/code/datums/ai/hunting_behavior/hunting_behaviors.dm b/code/datums/ai/hunting_behavior/hunting_behaviors.dm index ba7c7f2ffba..468cfed33fb 100644 --- a/code/datums/ai/hunting_behavior/hunting_behaviors.dm +++ b/code/datums/ai/hunting_behavior/hunting_behaviors.dm @@ -94,8 +94,6 @@ var/atom/hunted = controller.blackboard[hunting_target_key] 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) else target_caught(hunter, hunted) @@ -136,7 +134,7 @@ /datum/ai_behavior/hunt_target/use_ability_on_target/perform(seconds_per_tick, datum/ai_controller/controller, hunting_target_key, hunting_cooldown_key) var/datum/action/cooldown/ability = controller.blackboard[ability_key] - if(QDELETED(ability) || !ability.IsAvailable()) + if(!ability?.IsAvailable()) finish_action(controller, FALSE, hunting_target_key) return ..() diff --git a/code/datums/components/joint_damage.dm b/code/datums/components/joint_damage.dm new file mode 100644 index 00000000000..5397bd307ca --- /dev/null +++ b/code/datums/components/joint_damage.dm @@ -0,0 +1,35 @@ +/* + * A component given to mobs to damage a linked mob + */ +/datum/component/joint_damage + ///the mob we will damage + var/datum/weakref/overlord_mob + ///our last health count + var/previous_health_count + +/datum/component/joint_damage/Initialize(mob/overlord_mob) + if(!isliving(parent)) + return COMPONENT_INCOMPATIBLE + var/mob/living/parent_mob = parent + previous_health_count = parent_mob.health + if(overlord_mob) + src.overlord_mob = WEAKREF(overlord_mob) + +/datum/component/joint_damage/RegisterWithParent() + RegisterSignal(parent, COMSIG_LIVING_HEALTH_UPDATE, PROC_REF(damage_overlord)) + RegisterSignal(parent, COMSIG_LIVING_DEATH, PROC_REF(damage_overlord)) + +/datum/component/joint_damage/UnregisterFromParent() + UnregisterSignal(parent, list(COMSIG_LIVING_HEALTH_UPDATE, COMSIG_LIVING_DEATH)) + +/datum/component/joint_damage/Destroy() + overlord_mob = null + return ..() + +/datum/component/joint_damage/proc/damage_overlord(mob/living/source) + SIGNAL_HANDLER + + var/mob/living/overlord_to_damage = overlord_mob?.resolve() + if(!isnull(overlord_to_damage)) + overlord_to_damage.adjustBruteLoss(previous_health_count - source.health) ///damage or heal overlord + previous_health_count = source.health diff --git a/code/datums/mapgen/Cavegens/IcemoonCaves.dm b/code/datums/mapgen/Cavegens/IcemoonCaves.dm index 7d7437ccda6..b0fcd471db2 100644 --- a/code/datums/mapgen/Cavegens/IcemoonCaves.dm +++ b/code/datums/mapgen/Cavegens/IcemoonCaves.dm @@ -62,12 +62,12 @@ weighted_closed_turf_types = list(/turf/closed/mineral/random/snow/underground = 1) weighted_mob_spawn_list = list( SPAWN_MEGAFAUNA = 1, + /mob/living/basic/mining/ice_demon = 100, /mob/living/basic/mining/ice_whelp = 60, /mob/living/basic/mining/legion/snow = 100, - /mob/living/simple_animal/hostile/asteroid/ice_demon = 100, /obj/structure/spawner/ice_moon/demonic_portal = 6, - /obj/structure/spawner/ice_moon/demonic_portal/snowlegion = 6, /obj/structure/spawner/ice_moon/demonic_portal/ice_whelp = 6, + /obj/structure/spawner/ice_moon/demonic_portal/snowlegion = 6, ) weighted_megafauna_spawn_list = list(/mob/living/simple_animal/hostile/megafauna/colossus = 1) weighted_flora_spawn_list = list( diff --git a/code/game/objects/structures/icemoon/cave_entrance.dm b/code/game/objects/structures/icemoon/cave_entrance.dm index 2a90270ef73..add1f278569 100644 --- a/code/game/objects/structures/icemoon/cave_entrance.dm +++ b/code/game/objects/structures/icemoon/cave_entrance.dm @@ -80,7 +80,7 @@ GLOBAL_LIST_INIT(ore_probability, list( name = "demonic portal" desc = "A portal that goes to another world, normal creatures couldn't survive there." icon_state = "nether" - mob_types = list(/mob/living/simple_animal/hostile/asteroid/ice_demon) + mob_types = list(/mob/living/basic/mining/ice_demon) light_range = 1 light_color = COLOR_SOFT_RED mob_gps_id = "WT|B" // watcher | bluespace diff --git a/code/modules/mining/equipment/kinetic_crusher.dm b/code/modules/mining/equipment/kinetic_crusher.dm index caee1bbac8d..4ceddb09854 100644 --- a/code/modules/mining/equipment/kinetic_crusher.dm +++ b/code/modules/mining/equipment/kinetic_crusher.dm @@ -437,3 +437,36 @@ chaser.monster_damage_boost = FALSE // Weaker cuz no cooldown chaser.damage = 20 log_combat(user, target, "fired a chaser at", src) + +/obj/item/crusher_trophy/ice_demon_cube + name = "demonic cube" + desc = "A stone cold cube dropped from an ice demon." + icon_state = "ice_demon_cube" + denied_type = /obj/item/crusher_trophy/ice_demon_cube + ///how many will we summon? + var/summon_amount = 2 + ///cooldown to summon demons upon the target + COOLDOWN_DECLARE(summon_cooldown) + +/obj/item/crusher_trophy/ice_demon_cube/effect_desc() + return "mark detonation to unleash demonic ice clones upon the target" + +/obj/item/crusher_trophy/ice_demon_cube/on_mark_detonation(mob/living/target, mob/living/user) + if(isnull(target) || !COOLDOWN_FINISHED(src, summon_cooldown)) + return + for(var/i in 1 to summon_amount) + var/turf/drop_off = find_dropoff_turf(target, user) + var/mob/living/basic/mining/demon_afterimage/crusher/friend = new(drop_off) + friend.faction = list(FACTION_NEUTRAL) + friend.befriend(user) + friend.ai_controller?.set_blackboard_key(BB_BASIC_MOB_CURRENT_TARGET, target) + COOLDOWN_START(src, summon_cooldown, 30 SECONDS) + +///try to make them spawn all around the target to surround him +/obj/item/crusher_trophy/ice_demon_cube/proc/find_dropoff_turf(mob/living/target, mob/living/user) + var/list/turfs_list = get_adjacent_open_turfs(target) + for(var/turf/possible_turf in turfs_list) + if(possible_turf.is_blocked_turf()) + continue + return possible_turf + return get_turf(user) diff --git a/code/modules/mob/living/basic/icemoon/ice_demon/ice_demon.dm b/code/modules/mob/living/basic/icemoon/ice_demon/ice_demon.dm new file mode 100644 index 00000000000..960f875365b --- /dev/null +++ b/code/modules/mob/living/basic/icemoon/ice_demon/ice_demon.dm @@ -0,0 +1,89 @@ +/mob/living/basic/mining/ice_demon + name = "demonic watcher" + desc = "A creature formed entirely out of ice, bluespace energy emanates from inside of it." + icon = 'icons/mob/simple/icemoon/icemoon_monsters.dmi' + icon_state = "ice_demon" + icon_living = "ice_demon" + icon_gib = "syndicate_gib" + mob_biotypes = MOB_ORGANIC|MOB_BEAST + mouse_opacity = MOUSE_OPACITY_ICON + basic_mob_flags = DEL_ON_DEATH + speed = 2 + maxHealth = 150 + health = 150 + obj_damage = 40 + melee_damage_lower = 15 + melee_damage_upper = 15 + attack_verb_continuous = "slices" + attack_verb_simple = "slice" + attack_sound = 'sound/weapons/bladeslice.ogg' + attack_vis_effect = ATTACK_EFFECT_SLASH + move_force = MOVE_FORCE_VERY_STRONG + move_resist = MOVE_FORCE_VERY_STRONG + pull_force = MOVE_FORCE_VERY_STRONG + crusher_loot = /obj/item/crusher_trophy/ice_demon_cube + ai_controller = /datum/ai_controller/basic_controller/ice_demon + death_message = "fades as the energies that tied it to this world dissipate." + death_sound = 'sound/magic/demon_dies.ogg' + +/mob/living/basic/mining/ice_demon/Initialize(mapload) + . = ..() + var/datum/action/cooldown/mob_cooldown/slippery_ice_floors/ice_floor = new(src) + ice_floor.Grant(src) + ai_controller.set_blackboard_key(BB_DEMON_SLIP_ABILITY, ice_floor) + var/datum/action/cooldown/mob_cooldown/ice_demon_teleport/demon_teleport = new(src) + demon_teleport.Grant(src) + ai_controller.set_blackboard_key(BB_DEMON_TELEPORT_ABILITY, demon_teleport) + var/datum/action/cooldown/spell/conjure/create_afterimages/afterimage = new(src) + afterimage.Grant(src) + ai_controller.set_blackboard_key(BB_DEMON_CLONE_ABILITY, afterimage) + AddComponent(\ + /datum/component/ranged_attacks,\ + projectile_type = /obj/projectile/temp/ice_demon,\ + projectile_sound = 'sound/weapons/pierce.ogg',\ + ) + var/static/list/death_loot = list(/obj/item/stack/ore/bluespace_crystal = 3) + AddElement(/datum/element/death_drops, death_loot) + AddElement(/datum/element/simple_flying) + +/mob/living/basic/mining/ice_demon/death(gibbed) + if(prob(5)) + new /obj/item/raw_anomaly_core/bluespace(loc) + return ..() + +/mob/living/basic/mining/demon_afterimage + name = "afterimage demonic watcher" + desc = "Is this some sort of illusion?" + icon = 'icons/mob/simple/icemoon/icemoon_monsters.dmi' + icon_state = "ice_demon" + icon_living = "ice_demon" + icon_gib = "syndicate_gib" + mob_biotypes = MOB_ORGANIC|MOB_BEAST + mouse_opacity = MOUSE_OPACITY_ICON + basic_mob_flags = DEL_ON_DEATH + speed = 5 + maxHealth = 20 + health = 20 + melee_damage_lower = 5 + melee_damage_upper = 5 + attack_verb_continuous = "slices" + attack_verb_simple = "slice" + attack_sound = 'sound/weapons/bladeslice.ogg' + alpha = 80 + ai_controller = /datum/ai_controller/basic_controller/ice_demon/afterimage + ///how long do we exist for + var/existence_period = 15 SECONDS + +/mob/living/basic/mining/demon_afterimage/Initialize(mapload) + . = ..() + AddElement(/datum/element/simple_flying) + AddElement(/datum/element/temporary_atom, life_time = existence_period) + +///afterimage subtypes summoned by the crusher +/mob/living/basic/mining/demon_afterimage/crusher + speed = 2 + health = 60 + maxHealth = 60 + melee_damage_lower = 10 + melee_damage_upper = 10 + existence_period = 7 SECONDS diff --git a/code/modules/mob/living/basic/icemoon/ice_demon/ice_demon_abilities.dm b/code/modules/mob/living/basic/icemoon/ice_demon/ice_demon_abilities.dm new file mode 100644 index 00000000000..79c9ee9bd58 --- /dev/null +++ b/code/modules/mob/living/basic/icemoon/ice_demon/ice_demon_abilities.dm @@ -0,0 +1,117 @@ +/obj/projectile/temp/ice_demon + name = "ice blast" + icon_state = "ice_2" + damage = 5 + damage_type = BURN + armor_flag = ENERGY + speed = 1 + pixel_speed_multiplier = 0.25 + temperature = -75 + +/datum/action/cooldown/mob_cooldown/ice_demon_teleport + name = "Bluespace Teleport" + desc = "Teleport towards a destination target!" + button_icon = 'icons/obj/ore.dmi' + button_icon_state = "bluespace_crystal" + cooldown_time = 3 SECONDS + melee_cooldown_time = 0 SECONDS + ///time delay before teleport + var/time_delay = 0.5 SECONDS + +/datum/action/cooldown/mob_cooldown/ice_demon_teleport/Activate(atom/target_atom) + if(isclosedturf(get_turf(target_atom))) + owner.balloon_alert(owner, "blocked!") + return FALSE + animate(owner, transform = matrix().Scale(0.8), time = time_delay, easing = SINE_EASING) + addtimer(CALLBACK(src, PROC_REF(teleport_to_turf), target_atom), time_delay) + StartCooldown() + return TRUE + +/datum/action/cooldown/mob_cooldown/ice_demon_teleport/proc/teleport_to_turf(atom/target) + animate(owner, transform = matrix(), time = 0.5 SECONDS, easing = SINE_EASING) + do_teleport(teleatom = owner, destination = target, channel = TELEPORT_CHANNEL_BLUESPACE, forced = TRUE) + +/datum/action/cooldown/mob_cooldown/slippery_ice_floors + name = "Iced Floors" + desc = "Summon slippery ice floors all around!" + button_icon = 'icons/turf/floors/ice_turf.dmi' + button_icon_state = "ice_turf-6" + cooldown_time = 2 SECONDS + click_to_activate = FALSE + melee_cooldown_time = 0 SECONDS + ///perimeter we will spawn the iced floors on + var/radius = 1 + ///intervals we will spawn the ice floors in + var/spread_duration = 0.2 SECONDS + +/datum/action/cooldown/mob_cooldown/slippery_ice_floors/Activate(atom/target_atom) + for(var/i in 0 to radius) + var/list/list_of_turfs = border_diamond_range_turfs(owner, i) + addtimer(CALLBACK(src, PROC_REF(spawn_icy_floors), list_of_turfs), i * spread_duration) + StartCooldown() + return TRUE + +/datum/action/cooldown/mob_cooldown/slippery_ice_floors/proc/spawn_icy_floors(list/list_of_turfs) + if(!length(list_of_turfs)) + return + for(var/turf/location in list_of_turfs) + if(isnull(location)) + continue + if(isclosedturf(location) || isspaceturf(location)) + continue + new /obj/effect/temp_visual/slippery_ice(location) + +/obj/effect/temp_visual/slippery_ice + name = "slippery acid" + icon = 'icons/turf/floors/ice_turf.dmi' + icon_state = "ice_turf-6" + layer = BELOW_MOB_LAYER + plane = GAME_PLANE + anchored = TRUE + duration = 3 SECONDS + alpha = 100 + /// how long does it take for the effect to phase in + var/phase_in_period = 2 SECONDS + +/obj/effect/temp_visual/slippery_ice/Initialize(mapload) + . = ..() + animate(src, alpha = 160, time = phase_in_period) + animate(alpha = 0, time = duration - phase_in_period) /// slowly fade out of existence + addtimer(CALLBACK(src, PROC_REF(add_slippery_component), phase_in_period)) //only become slippery after we phased in + +/obj/effect/temp_visual/slippery_ice/proc/add_slippery_component() + AddComponent(/datum/component/slippery, 2 SECONDS) + +/datum/action/cooldown/spell/conjure/create_afterimages + name = "Create After Images" + button_icon = 'icons/mob/simple/icemoon/icemoon_monsters.dmi' + button_icon_state = "ice_demon" + spell_requirements = NONE + cooldown_time = 1 MINUTES + summon_type = list(/mob/living/basic/mining/demon_afterimage) + summon_radius = 1 + summon_amount = 2 + ///max number of after images + var/max_afterimages = 2 + ///How many clones do we have summoned + var/number_of_afterimages = 0 + +/datum/action/cooldown/spell/conjure/create_afterimages/can_cast_spell(feedback = TRUE) + . = ..() + if(!.) + return FALSE + if(number_of_afterimages >= max_afterimages) + return FALSE + return TRUE + +/datum/action/cooldown/spell/conjure/create_afterimages/post_summon(atom/summoned_object, atom/cast_on) + var/mob/living/basic/created_copy = summoned_object + created_copy.AddComponent(/datum/component/joint_damage, overlord_mob = owner) + RegisterSignals(created_copy, list(COMSIG_QDELETING, COMSIG_LIVING_DEATH), PROC_REF(delete_copy)) + number_of_afterimages++ + +/datum/action/cooldown/spell/conjure/create_afterimages/proc/delete_copy(mob/source) + SIGNAL_HANDLER + + UnregisterSignal(source, list(COMSIG_QDELETING, COMSIG_LIVING_DEATH)) + number_of_afterimages-- diff --git a/code/modules/mob/living/basic/icemoon/ice_demon/ice_demon_ai.dm b/code/modules/mob/living/basic/icemoon/ice_demon/ice_demon_ai.dm new file mode 100644 index 00000000000..b17d5c1a30d --- /dev/null +++ b/code/modules/mob/living/basic/icemoon/ice_demon/ice_demon_ai.dm @@ -0,0 +1,118 @@ +/datum/ai_controller/basic_controller/ice_demon + blackboard = list( + BB_TARGETTING_DATUM = new /datum/targetting_datum/basic, + BB_LIST_SCARY_ITEMS = list( + /obj/item/weldingtool, + /obj/item/flashlight/flare, + ), + BB_BASIC_MOB_FLEEING = TRUE, + BB_MINIMUM_DISTANCE_RANGE = 3, + ) + + ai_movement = /datum/ai_movement/basic_avoidance + idle_behavior = /datum/idle_behavior/idle_random_walk + planning_subtrees = list( + /datum/ai_planning_subtree/simple_find_target, + /datum/ai_planning_subtree/flee_target/ice_demon, + /datum/ai_planning_subtree/ranged_skirmish/ice_demon, + /datum/ai_planning_subtree/maintain_distance/cover_minimum_distance/ice_demon, + /datum/ai_planning_subtree/teleport_away_from_target, + /datum/ai_planning_subtree/find_and_hunt_target/teleport_destination, + /datum/ai_planning_subtree/targeted_mob_ability/summon_afterimages, + ) + + +/datum/ai_planning_subtree/maintain_distance/cover_minimum_distance/ice_demon + maximum_distance = 7 + +/datum/ai_planning_subtree/teleport_away_from_target + ability_key = BB_DEMON_TELEPORT_ABILITY + +/datum/ai_planning_subtree/find_and_hunt_target/teleport_destination + target_key = BB_TELEPORT_DESTINATION + hunting_behavior = /datum/ai_behavior/hunt_target/use_ability_on_target/demon_teleport + finding_behavior = /datum/ai_behavior/find_valid_teleport_location + hunt_targets = list(/turf/open) + hunt_range = 3 + finish_planning = FALSE + +/datum/ai_planning_subtree/find_and_hunt_target/teleport_destination/SelectBehaviors(datum/ai_controller/controller, seconds_per_tick) + if(!controller.blackboard_key_exists(BB_BASIC_MOB_CURRENT_TARGET)) + return + if(controller.blackboard_key_exists(BB_ESCAPE_DESTINATION)) + controller.clear_blackboard_key(BB_TELEPORT_DESTINATION) + return + var/datum/action/cooldown/ability = controller.blackboard[BB_DEMON_TELEPORT_ABILITY] + if(!ability?.IsAvailable()) + return + return ..() + +/datum/ai_behavior/find_valid_teleport_location + +/datum/ai_behavior/find_valid_teleport_location/perform(seconds_per_tick, datum/ai_controller/controller, hunting_target_key, types_to_hunt, hunt_range) + . = ..() + var/mob/living/target = controller.blackboard[BB_BASIC_MOB_CURRENT_TARGET] + var/list/possible_turfs = list() + + if(QDELETED(target)) + finish_action(controller, FALSE) + return + + for(var/turf/open/potential_turf in oview(hunt_range, target)) //we check for turfs around the target + if(potential_turf.is_blocked_turf()) + continue + if(!can_see(target, potential_turf, hunt_range)) + continue + possible_turfs += potential_turf + + if(!length(possible_turfs)) + finish_action(controller, FALSE) + return + + controller.set_blackboard_key(hunting_target_key, pick(possible_turfs)) + finish_action(controller, TRUE) + +/datum/ai_behavior/hunt_target/use_ability_on_target/demon_teleport + hunt_cooldown = 2 SECONDS + ability_key = BB_DEMON_TELEPORT_ABILITY + behavior_flags = NONE + +/datum/ai_planning_subtree/targeted_mob_ability/summon_afterimages + ability_key = BB_DEMON_CLONE_ABILITY + +/datum/ai_planning_subtree/targeted_mob_ability/summon_afterimages/SelectBehaviors(datum/ai_controller/controller, seconds_per_tick) + var/mob/living/living_pawn = controller.pawn + if(living_pawn.health / living_pawn.maxHealth > 0.5) //only use this ability when under half health + return + return ..() + +/datum/ai_planning_subtree/flee_target/ice_demon + +/datum/ai_planning_subtree/flee_target/ice_demon/SelectBehaviors(datum/ai_controller/controller, seconds_per_tick) + var/atom/target = controller.blackboard[BB_BASIC_MOB_CURRENT_TARGET] + if(QDELETED(target)) + return + if(!iscarbon(target)) + return + var/mob/living/carbon/human_target = target + + for(var/obj/held_item in human_target.held_items) + if(!is_type_in_list(held_item, controller.blackboard[BB_LIST_SCARY_ITEMS])) + continue + if(!held_item.light_on) + continue + var/datum/action/cooldown/slip_ability = controller.blackboard[BB_DEMON_SLIP_ABILITY] + if(slip_ability?.IsAvailable()) + controller.queue_behavior(/datum/ai_behavior/use_mob_ability, BB_DEMON_SLIP_ABILITY) + return ..() + +/datum/ai_planning_subtree/ranged_skirmish/ice_demon + min_range = 0 + +/datum/ai_controller/basic_controller/ice_demon/afterimage + planning_subtrees = list( + /datum/ai_planning_subtree/simple_find_target, + /datum/ai_planning_subtree/flee_target/ice_demon, //even the afterimages are afraid of flames! + /datum/ai_planning_subtree/basic_melee_attack_subtree, + ) + diff --git a/code/modules/mob/living/basic/lavaland/watcher/watcher_ai.dm b/code/modules/mob/living/basic/lavaland/watcher/watcher_ai.dm index a25234817f3..348bbcfcaa7 100644 --- a/code/modules/mob/living/basic/lavaland/watcher/watcher_ai.dm +++ b/code/modules/mob/living/basic/lavaland/watcher/watcher_ai.dm @@ -27,13 +27,10 @@ return ..() /datum/ai_planning_subtree/ranged_skirmish/watcher - attack_behavior = /datum/ai_behavior/ranged_skirmish/watcher + min_range = 0 /datum/ai_planning_subtree/ranged_skirmish/watcher/SelectBehaviors(datum/ai_controller/controller, seconds_per_tick) var/mob/living/target = controller.blackboard[BB_BASIC_MOB_CURRENT_TARGET] if (QDELETED(target) || HAS_TRAIT(target, TRAIT_OVERWATCHED)) return // Don't bully people who are playing red light green light return ..() - -/datum/ai_behavior/ranged_skirmish/watcher - min_range = 0 diff --git a/code/modules/mob/living/simple_animal/hostile/mining_mobs/ice_demon.dm b/code/modules/mob/living/simple_animal/hostile/mining_mobs/ice_demon.dm deleted file mode 100644 index 25d1ea9da74..00000000000 --- a/code/modules/mob/living/simple_animal/hostile/mining_mobs/ice_demon.dm +++ /dev/null @@ -1,85 +0,0 @@ -/mob/living/simple_animal/hostile/asteroid/ice_demon - name = "demonic watcher" - desc = "A creature formed entirely out of ice, bluespace energy emanates from inside of it." - icon = 'icons/mob/simple/icemoon/icemoon_monsters.dmi' - icon_state = "ice_demon" - icon_living = "ice_demon" - icon_dead = "ice_demon_dead" - icon_gib = "syndicate_gib" - mob_biotypes = MOB_ORGANIC|MOB_BEAST - mouse_opacity = MOUSE_OPACITY_ICON - speak_emote = list("telepathically cries") - speed = 2 - move_to_delay = 2 - projectiletype = /obj/projectile/temp/ice_demon - projectilesound = 'sound/weapons/pierce.ogg' - ranged = TRUE - ranged_message = "manifests ice" - ranged_cooldown_time = 1.5 SECONDS - minimum_distance = 3 - retreat_distance = 3 - maxHealth = 150 - health = 150 - obj_damage = 40 - melee_damage_lower = 15 - melee_damage_upper = 15 - attack_verb_continuous = "slices" - attack_verb_simple = "slice" - attack_sound = 'sound/weapons/bladeslice.ogg' - attack_vis_effect = ATTACK_EFFECT_SLASH - vision_range = 9 - aggro_vision_range = 9 - move_force = MOVE_FORCE_VERY_STRONG - move_resist = MOVE_FORCE_VERY_STRONG - pull_force = MOVE_FORCE_VERY_STRONG - del_on_death = TRUE - loot = list() - crusher_loot = /obj/item/crusher_trophy/demon_core /// SKYRAT EDIT CHANGE - ORIGINAL : crusher_loot = /obj/item/crusher_trophy/watcher_wing/ice_wing - death_message = "fades as the energies that tied it to this world dissipate." - death_sound = 'sound/magic/demon_dies.ogg' - stat_attack = HARD_CRIT - robust_searching = TRUE - footstep_type = FOOTSTEP_MOB_CLAW - /// Distance the demon will teleport from the target - var/teleport_distance = 3 - -/mob/living/simple_animal/hostile/asteroid/ice_demon/Initialize(mapload) - . = ..() - AddElement(/datum/element/simple_flying) - -/obj/projectile/temp/ice_demon - name = "ice blast" - icon_state = "ice_2" - damage = 5 - damage_type = BURN - armor_flag = ENERGY - speed = 1 - pixel_speed_multiplier = 0.25 - range = 200 - temperature = -75 - -/mob/living/simple_animal/hostile/asteroid/ice_demon/OpenFire() - ranged_cooldown = world.time + ranged_cooldown_time - // Sentient ice demons teleporting has been linked to server crashes - if(client) - return ..() - if(teleport_distance <= 0) - return ..() - var/list/possible_ends = view(teleport_distance, target.loc) - view(teleport_distance - 1, target.loc) - for(var/turf/closed/turf_to_remove in possible_ends) - possible_ends -= turf_to_remove - if(!possible_ends.len) - return ..() - var/turf/end = pick(possible_ends) - do_teleport(src, end, 0, channel=TELEPORT_CHANNEL_BLUESPACE, forced = TRUE) - SLEEP_CHECK_DEATH(8, src) - return ..() - -/mob/living/simple_animal/hostile/asteroid/ice_demon/death(gibbed) - move_force = MOVE_FORCE_DEFAULT - move_resist = MOVE_RESIST_DEFAULT - pull_force = PULL_FORCE_DEFAULT - new /obj/item/stack/ore/bluespace_crystal(loc, 3) - if(prob(5)) - new /obj/item/raw_anomaly_core/bluespace(loc) - return ..() diff --git a/code/modules/unit_tests/simple_animal_freeze.dm b/code/modules/unit_tests/simple_animal_freeze.dm index 84da6f68478..92416001b79 100644 --- a/code/modules/unit_tests/simple_animal_freeze.dm +++ b/code/modules/unit_tests/simple_animal_freeze.dm @@ -64,7 +64,6 @@ /mob/living/simple_animal/hostile/asteroid/gutlunch/grublunch, /mob/living/simple_animal/hostile/asteroid/gutlunch/gubbuck, /mob/living/simple_animal/hostile/asteroid/gutlunch/guthen, - /mob/living/simple_animal/hostile/asteroid/ice_demon, /mob/living/simple_animal/hostile/asteroid/polarbear, /mob/living/simple_animal/hostile/asteroid/polarbear/lesser, /mob/living/simple_animal/hostile/asteroid/wolf, diff --git a/icons/obj/mining_zones/artefacts.dmi b/icons/obj/mining_zones/artefacts.dmi index f3f7d00e4ee..d4c603834d2 100644 Binary files a/icons/obj/mining_zones/artefacts.dmi and b/icons/obj/mining_zones/artefacts.dmi differ diff --git a/modular_skyrat/master_files/code/modules/mob/living/basic/icemoon/ice_demon.dm b/modular_skyrat/master_files/code/modules/mob/living/basic/icemoon/ice_demon.dm new file mode 100644 index 00000000000..0c8f44b3118 --- /dev/null +++ b/modular_skyrat/master_files/code/modules/mob/living/basic/icemoon/ice_demon.dm @@ -0,0 +1,3 @@ +// crusher loot /obj/item/crusher_trophy/ice_demon_cube -> /obj/item/crusher_trophy/demon_core +/mob/living/basic/mining/ice_demon + crusher_loot = /obj/item/crusher_trophy/demon_core diff --git a/modular_skyrat/modules/ashwalkers/code/effects/ash_rituals.dm b/modular_skyrat/modules/ashwalkers/code/effects/ash_rituals.dm index ba1dd931c3a..7b38a565698 100644 --- a/modular_skyrat/modules/ashwalkers/code/effects/ash_rituals.dm +++ b/modular_skyrat/modules/ashwalkers/code/effects/ash_rituals.dm @@ -198,7 +198,7 @@ /datum/ash_ritual/summon_icemoon_creature/ritual_success(obj/effect/ash_rune/success_rune) . = ..() var/mob_type = pick( - /mob/living/simple_animal/hostile/asteroid/ice_demon, + /mob/living/basic/mining/ice_demon, /mob/living/basic/mining/ice_whelp, /mob/living/basic/mining/lobstrosity, /mob/living/simple_animal/hostile/asteroid/polarbear, diff --git a/tgstation.dme b/tgstation.dme index fee2981a998..4a8d75fedad 100644 --- a/tgstation.dme +++ b/tgstation.dme @@ -922,6 +922,7 @@ #include "code\datums\ai\basic_mobs\basic_subtrees\stare_at_thing.dm" #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\teleport_away_from_target.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" @@ -1100,6 +1101,7 @@ #include "code\datums\components\itembound.dm" #include "code\datums\components\itempicky.dm" #include "code\datums\components\jetpack.dm" +#include "code\datums\components\joint_damage.dm" #include "code\datums\components\jousting.dm" #include "code\datums\components\keep_me_secure.dm" #include "code\datums\components\knockoff.dm" @@ -4465,6 +4467,9 @@ #include "code\modules\mob\living\basic\heretic\raw_prophet.dm" #include "code\modules\mob\living\basic\heretic\rust_walker.dm" #include "code\modules\mob\living\basic\heretic\star_gazer.dm" +#include "code\modules\mob\living\basic\icemoon\ice_demon\ice_demon.dm" +#include "code\modules\mob\living\basic\icemoon\ice_demon\ice_demon_abilities.dm" +#include "code\modules\mob\living\basic\icemoon\ice_demon\ice_demon_ai.dm" #include "code\modules\mob\living\basic\icemoon\ice_whelp\ice_whelp.dm" #include "code\modules\mob\living\basic\icemoon\ice_whelp\ice_whelp_abilities.dm" #include "code\modules\mob\living\basic\icemoon\ice_whelp\ice_whelp_ai.dm" @@ -4828,7 +4833,6 @@ #include "code\modules\mob\living\simple_animal\hostile\megafauna\wendigo.dm" #include "code\modules\mob\living\simple_animal\hostile\mining_mobs\curse_blob.dm" #include "code\modules\mob\living\simple_animal\hostile\mining_mobs\gutlunch.dm" -#include "code\modules\mob\living\simple_animal\hostile\mining_mobs\ice_demon.dm" #include "code\modules\mob\living\simple_animal\hostile\mining_mobs\mining_mobs.dm" #include "code\modules\mob\living\simple_animal\hostile\mining_mobs\polarbear.dm" #include "code\modules\mob\living\simple_animal\hostile\mining_mobs\wolf.dm" @@ -6149,6 +6153,7 @@ #include "modular_skyrat\master_files\code\modules\mob\living\living.dm" #include "modular_skyrat\master_files\code\modules\mob\living\living_defines.dm" #include "modular_skyrat\master_files\code\modules\mob\living\living_movement.dm" +#include "modular_skyrat\master_files\code\modules\mob\living\basic\icemoon\ice_demon.dm" #include "modular_skyrat\master_files\code\modules\mob\living\carbon\death.dm" #include "modular_skyrat\master_files\code\modules\mob\living\carbon\human.dm" #include "modular_skyrat\master_files\code\modules\mob\living\carbon\human_helpers.dm" diff --git a/tools/UpdatePaths/Scripts/78539_ice_demons.txt b/tools/UpdatePaths/Scripts/78539_ice_demons.txt new file mode 100644 index 00000000000..f387d200500 --- /dev/null +++ b/tools/UpdatePaths/Scripts/78539_ice_demons.txt @@ -0,0 +1 @@ +/mob/living/simple_animal/hostile/asteroid/ice_demon : /mob/living/basic/mining/ice_demon{@OLD} \ No newline at end of file