From c51a58401d78ad4b461962dfb7966fcab8e95e2a Mon Sep 17 00:00:00 2001 From: SkyratBot <59378654+SkyratBot@users.noreply.github.com> Date: Wed, 18 Jan 2023 14:37:37 +0100 Subject: [PATCH] [MIRROR] New Mob [Garden Gnomes] [MDB IGNORE] (#18784) * New Mob [Garden Gnomes] (#72672) ## About The Pull Request Garden gnomes can go into the ground when they stand still for a while. If they are damaged and underground they will heal. If they are underground they will also gain damage resistance. They can make people trip when underground and deal some damage. They are available through golden slime cores or you can collect them from cargo with an emag. If one of them gets damaged they will all retaliate against the attacker. They have a very realistic colour pallet based on online gathered data. Depending on their colour pallets some of them have a very low chance of spawning. ## Why It's Good For The Game These new garden gnomes come with expanding the world building of space station 13. These gnomes will add more variety to the game play and use an interesting AI and behavior that interacts with the player. ![gnome_promo1](https://user-images.githubusercontent.com/25363960/212195653-0b434f17-e73c-4a87-b8b8-e96dcf057269.PNG) ![gnome_promo2](https://user-images.githubusercontent.com/25363960/212195658-4cbcd863-dcab-4643-9e3b-686e79dcb435.PNG) ## Changelog :cl: add: Adds garden gnomes. imageadd: Adds garden gnome sprites. /:cl: * New Mob [Garden Gnomes] Co-authored-by: Comxy --- code/__DEFINES/colors.dm | 17 ++ .../basic_subtrees/speech_subtree.dm | 6 + .../ai/idle_behaviors/idle_random_walk.dm | 2 + .../components/ai_retaliate_advanced.dm | 33 ++++ code/datums/components/caltrop.dm | 8 +- code/datums/components/ground_sinking.dm | 142 +++++++++++++++++ .../config_types/greyscale_configs.dm | 5 + .../greyscale/json_configs/garden_gnome.json | 126 +++++++++++++++ code/modules/cargo/packs/livestock.dm | 13 ++ .../living/basic/retaliate/garden_gnome.dm | 149 ++++++++++++++++++ icons/mob/simple/garden_gnome.dmi | Bin 0 -> 1446 bytes tgstation.dme | 3 + 12 files changed, 502 insertions(+), 2 deletions(-) create mode 100644 code/datums/components/ai_retaliate_advanced.dm create mode 100644 code/datums/components/ground_sinking.dm create mode 100644 code/datums/greyscale/json_configs/garden_gnome.json create mode 100644 code/modules/mob/living/basic/retaliate/garden_gnome.dm create mode 100644 icons/mob/simple/garden_gnome.dmi diff --git a/code/__DEFINES/colors.dm b/code/__DEFINES/colors.dm index 9e1fe18270a..78e6e78d5cb 100644 --- a/code/__DEFINES/colors.dm +++ b/code/__DEFINES/colors.dm @@ -291,6 +291,23 @@ #define COLOR_CARP_SILVER "#fdfbf3" #define COLOR_CARP_DARK_BLUE "#3a384d" +#define COLOR_GNOME_RED_ONE "#f10b0b" +#define COLOR_GNOME_RED_TWO "#bc5347" +#define COLOR_GNOME_RED_THREE "#b40f1a" +#define COLOR_GNOME_BLUE_ONE "#2e8ff7" +#define COLOR_GNOME_BLUE_TWO "#312bd6" +#define COLOR_GNOME_BLUE_THREE "#4e409a" +#define COLOR_GNOME_GREEN_ONE "#28da1c" +#define COLOR_GNOME_GREEN_TWO "#50a954" +#define COLOR_GNOME_YELLOW "#f6da3c" +#define COLOR_GNOME_ORANGE "#d56f2f" +#define COLOR_GNOME_BROWN_ONE "#874e2a" +#define COLOR_GNOME_BROWN_TWO "#543d2e" +#define COLOR_GNOME_PURPLE "#ac1dd7" +#define COLOR_GNOME_WHITE "#e8e8e8" +#define COLOR_GNOME_GREY "#a9a9a9" +#define COLOR_GNOME_BLACK "#303030" + #define SOFA_BROWN "#a75400" #define SOFA_MAROON "#830000" diff --git a/code/datums/ai/basic_mobs/basic_subtrees/speech_subtree.dm b/code/datums/ai/basic_mobs/basic_subtrees/speech_subtree.dm index 221b2259600..bfabe1cb8b7 100644 --- a/code/datums/ai/basic_mobs/basic_subtrees/speech_subtree.dm +++ b/code/datums/ai/basic_mobs/basic_subtrees/speech_subtree.dm @@ -110,3 +110,9 @@ /datum/ai_planning_subtree/random_speech/faithless speech_chance = 1 emote_see = list("wails.") + +/datum/ai_planning_subtree/random_speech/garden_gnome + speech_chance = 5 + speak = list("Gnot a gnelf!", "Gnot a gnoblin!", "Howdy chum!") + emote_hear = list("snores.", "burps.") + emote_see = list("blinks.") diff --git a/code/datums/ai/idle_behaviors/idle_random_walk.dm b/code/datums/ai/idle_behaviors/idle_random_walk.dm index 1187d8786c1..69a236ba831 100644 --- a/code/datums/ai/idle_behaviors/idle_random_walk.dm +++ b/code/datums/ai/idle_behaviors/idle_random_walk.dm @@ -10,3 +10,5 @@ var/move_dir = pick(GLOB.alldirs) living_pawn.Move(get_step(living_pawn, move_dir), move_dir) +/datum/idle_behavior/idle_random_walk/less_walking + walk_chance = 10 diff --git a/code/datums/components/ai_retaliate_advanced.dm b/code/datums/components/ai_retaliate_advanced.dm new file mode 100644 index 00000000000..8be84dc5cef --- /dev/null +++ b/code/datums/components/ai_retaliate_advanced.dm @@ -0,0 +1,33 @@ +/** + * Attached to a mob with an AI controller, passes things which have damaged it to a blackboard. + * The AI controller is responsible for doing anything with that information. + */ +/datum/component/ai_retaliate_advanced + /// Callback to a mob for custom behaviour + var/datum/callback/post_retaliate_callback + +/datum/component/ai_retaliate_advanced/Initialize(datum/callback/post_retaliate_callback) + if(!ismob(parent)) + return ELEMENT_INCOMPATIBLE + + src.post_retaliate_callback = post_retaliate_callback + parent.AddElement(/datum/element/relay_attackers) + +/datum/component/ai_retaliate_advanced/RegisterWithParent() + RegisterSignal(parent, COMSIG_ATOM_WAS_ATTACKED, PROC_REF(on_attacked)) + +/datum/component/ai_retaliate_advanced/UnregisterFromParent() + UnregisterSignal(parent, COMSIG_ATOM_WAS_ATTACKED) + +/// Add an attacking atom to a blackboard list of things which attacked us +/datum/component/ai_retaliate_advanced/proc/on_attacked(mob/victim, atom/attacker) + SIGNAL_HANDLER + + if (!victim.ai_controller) + return + var/list/enemy_refs = victim.ai_controller.blackboard[BB_BASIC_MOB_RETALIATE_LIST] + if (!enemy_refs) + enemy_refs = list() + enemy_refs |= WEAKREF(attacker) + victim.ai_controller.blackboard[BB_BASIC_MOB_RETALIATE_LIST] = enemy_refs + post_retaliate_callback?.InvokeAsync(attacker) diff --git a/code/datums/components/caltrop.dm b/code/datums/components/caltrop.dm index a9b3ebf7812..726c705ee8d 100644 --- a/code/datums/components/caltrop.dm +++ b/code/datums/components/caltrop.dm @@ -13,6 +13,9 @@ ///Probability of actually "firing", stunning and doing damage var/probability + ///Amount of time the spike will paralyze + var/paralyze_duration + ///Miscelanous caltrop flags; shoe bypassing, walking interaction, silence var/flags @@ -27,7 +30,7 @@ ///So we can update ant damage dupe_mode = COMPONENT_DUPE_UNIQUE_PASSARGS -/datum/component/caltrop/Initialize(min_damage = 0, max_damage = 0, probability = 100, flags = NONE, soundfile = null) +/datum/component/caltrop/Initialize(min_damage = 0, max_damage = 0, probability = 100, paralyze_duration = 6 SECONDS, flags = NONE, soundfile = null) . = ..() if(!isatom(parent)) return COMPONENT_INCOMPATIBLE @@ -35,6 +38,7 @@ src.min_damage = min_damage src.max_damage = max(min_damage, max_damage) src.probability = probability + src.paralyze_duration = paralyze_duration src.flags = flags src.soundfile = soundfile @@ -115,7 +119,7 @@ H.apply_damage(damage, BRUTE, picked_def_zone, wound_bonus = CANT_WOUND) if(!(flags & CALTROP_NOSTUN)) // Won't set off the paralysis. - H.Paralyze(60) + H.Paralyze(paralyze_duration) if(!soundfile) return diff --git a/code/datums/components/ground_sinking.dm b/code/datums/components/ground_sinking.dm new file mode 100644 index 00000000000..3123172b222 --- /dev/null +++ b/code/datums/components/ground_sinking.dm @@ -0,0 +1,142 @@ +#define REGENERATION_FILTER "healing_glow" + +/** + * # Ground sinking component (basicly only for garden gnomes ever at this point) + * + * A basic mob with this component will sink into the ground, once sinked into the ground it will regenerate and + * might gain damage resistence. Can be combined with caltrop. + */ +/datum/component/ground_sinking + /// The icon state of the parent + var/target_icon_state + /// You will only sink if you haven't been walking for this many seconds + var/ground_sinking_delay + /// The speed at which the mob will sink + var/sink_speed + /// If we will heal once we are sinked + var/heal_when_sinked + /// Health to regenerate per second + var/health_per_second + /// Outline colour of the regeneration + var/outline_colour + /// Our damage_coeffs when we are sinked + var/damage_res_sinked + /// If we sinked into the ground right now + var/sinked = FALSE + /// If we sinking into the ground right now + var/is_sinking = FALSE + /// How far we have sinked + var/sink_count = 0 + /// When this timer completes we start sinking + var/ground_sinking_start_timer + +/datum/component/ground_sinking/Initialize(target_icon_state, + ground_sinking_delay = 8 SECONDS, + sink_speed = 1 SECONDS, + heal_when_sinked = TRUE, + health_per_second = 1, + outline_colour = COLOR_PALE_GREEN, + damage_res_sinked = list(BRUTE = 1, BURN = 1, TOX = 1, CLONE = 1, STAMINA = 0, OXY = 1)) + + if (!isbasicmob(parent)) + return COMPONENT_INCOMPATIBLE + + src.target_icon_state = target_icon_state + src.ground_sinking_delay = ground_sinking_delay + src.sink_speed = sink_speed + src.heal_when_sinked = heal_when_sinked + src.health_per_second = health_per_second + src.outline_colour = outline_colour + src.damage_res_sinked = damage_res_sinked + +/datum/component/ground_sinking/RegisterWithParent() + RegisterSignal(parent, COMSIG_MOVABLE_MOVED, PROC_REF(on_moved)) + +/datum/component/ground_sinking/UnregisterFromParent() + if(sinked || is_sinking) + unsink() + if(ground_sinking_start_timer) + deltimer(ground_sinking_start_timer) + UnregisterSignal(parent, COMSIG_MOVABLE_MOVED) + +/datum/component/ground_sinking/Destroy(force, silent) + if(sinked || is_sinking) + unsink() + . = ..() + if(ground_sinking_start_timer) + deltimer(ground_sinking_start_timer) + +/// When you take damage, reset the cooldown and start processing +/datum/component/ground_sinking/proc/on_moved(mob/living/basic/living_target, atom/OldLoc, Dir, Forced) + SIGNAL_HANDLER + if(sinked || is_sinking) + unsink() + if(ground_sinking_start_timer) + deltimer(ground_sinking_start_timer) + ground_sinking_start_timer = addtimer(CALLBACK(src, PROC_REF(start_sinking), living_target), ground_sinking_delay, TIMER_STOPPABLE) + +/// Start processing health regeneration, and show animation if provided +/datum/component/ground_sinking/proc/start_sinking(mob/living/basic/living_target) + if(!sinked || is_sinking) + is_sinking = TRUE + INVOKE_ASYNC(src, PROC_REF(sink_once), living_target) + +/datum/component/ground_sinking/proc/sink_once(mob/living/basic/living_target) + living_target.visible_message(span_notice("[living_target] starts sinking into the ground!")) + for(var/i in 1 to 3) + if(do_after(living_target, sink_speed, living_target)) + sink_count += 1 + living_target.icon_state = "[target_icon_state]_burried_[sink_count]" + sink_count = 0 + is_sinked(living_target) + +/datum/component/ground_sinking/proc/is_sinked(mob/living/basic/living_target) + sinked = TRUE + is_sinking = FALSE + living_target.density = FALSE + living_target.damage_coeff = damage_res_sinked + if(heal_when_sinked) + start_regenerating() + +/datum/component/ground_sinking/proc/unsink() + var/mob/living/basic/living_target = parent + if(sinked && heal_when_sinked) + stop_regenerating() + living_target.icon_state = target_icon_state + living_target.damage_coeff = list(BRUTE = 1, BURN = 1, TOX = 1, CLONE = 1, STAMINA = 0, OXY = 1) + living_target.density = TRUE + sinked = FALSE + +/datum/component/ground_sinking/proc/start_regenerating() + var/mob/living/basic/living_parent = parent + if (living_parent.stat == DEAD) + return + if (living_parent.health == living_parent.maxHealth) + return + living_parent.visible_message(span_notice("[living_parent]'s wounds begin to knit closed!")) + START_PROCESSING(SSobj, src) + if (!outline_colour) + return + living_parent.add_filter(REGENERATION_FILTER, 2, list("type" = "outline", "color" = outline_colour, "alpha" = 0, "size" = 1)) + var/filter = living_parent.get_filter(REGENERATION_FILTER) + animate(filter, alpha = 200, time = 0.5 SECONDS, loop = -1) + animate(alpha = 0, time = 0.5 SECONDS) + +/datum/component/ground_sinking/proc/stop_regenerating() + STOP_PROCESSING(SSobj, src) + var/mob/living/basic/living_parent = parent + var/filter = living_parent.get_filter(REGENERATION_FILTER) + animate(filter) + living_parent.remove_filter(REGENERATION_FILTER) + +/datum/component/ground_sinking/process(delta_time = SSMOBS_DT) + var/mob/living/basic/living_parent = parent + if (living_parent.stat == DEAD) + stop_regenerating() + return + if (living_parent.health == living_parent.maxHealth) + stop_regenerating() + return + living_parent.heal_overall_damage(health_per_second * delta_time) + +#undef REGENERATION_FILTER diff --git a/code/datums/greyscale/config_types/greyscale_configs.dm b/code/datums/greyscale/config_types/greyscale_configs.dm index 9ad57fdd575..0d1ab52cbe8 100644 --- a/code/datums/greyscale/config_types/greyscale_configs.dm +++ b/code/datums/greyscale/config_types/greyscale_configs.dm @@ -52,6 +52,11 @@ name = "Space Carp, Disk in Mouth" json_config = 'code/datums/greyscale/json_configs/carp_disk_mouth.json' +/datum/greyscale_config/garden_gnome + name = "Garden Gnome" + icon_file = 'icons/mob/simple/garden_gnome.dmi' + json_config = 'code/datums/greyscale/json_configs/garden_gnome.json' + /datum/greyscale_config/wirecutters name = "Wirecutters" icon_file = 'icons/obj/tools.dmi' diff --git a/code/datums/greyscale/json_configs/garden_gnome.json b/code/datums/greyscale/json_configs/garden_gnome.json new file mode 100644 index 00000000000..c443e6f04a6 --- /dev/null +++ b/code/datums/greyscale/json_configs/garden_gnome.json @@ -0,0 +1,126 @@ +{ + "gnome": [ + { + "type": "icon_state", + "icon_state": "gnome", + "blend_mode": "overlay" + }, + { + "type": "icon_state", + "icon_state": "gnome_hat", + "blend_mode": "overlay", + "color_ids": [ 1 ] + }, + { + "type": "icon_state", + "icon_state": "gnome_body", + "blend_mode": "overlay", + "color_ids": [ 2 ] + }, + { + "type": "icon_state", + "icon_state": "gnome_pants", + "blend_mode": "overlay", + "color_ids": [ 3 ] + }, + { + "type": "icon_state", + "icon_state": "gnome_beard", + "blend_mode": "overlay", + "color_ids": [ 4 ] + } + ], + "gnome_burried_1": [ + { + "type": "icon_state", + "icon_state": "gnome_burried_1", + "blend_mode": "overlay" + }, + { + "type": "icon_state", + "icon_state": "gnome_hat_burried_1", + "blend_mode": "overlay", + "color_ids": [ 1 ] + }, + { + "type": "icon_state", + "icon_state": "gnome_body_burried_1", + "blend_mode": "overlay", + "color_ids": [ 2 ] + }, + { + "type": "icon_state", + "icon_state": "gnome_pants_burried_1", + "blend_mode": "overlay", + "color_ids": [ 3 ] + }, + { + "type": "icon_state", + "icon_state": "gnome_beard_burried_1", + "blend_mode": "overlay", + "color_ids": [ 4 ] + } + ], + "gnome_burried_2": [ + { + "type": "icon_state", + "icon_state": "gnome_burried_2", + "blend_mode": "overlay" + }, + { + "type": "icon_state", + "icon_state": "gnome_hat_burried_2", + "blend_mode": "overlay", + "color_ids": [ 1 ] + }, + { + "type": "icon_state", + "icon_state": "gnome_body_burried_2", + "blend_mode": "overlay", + "color_ids": [ 2 ] + }, + { + "type": "icon_state", + "icon_state": "gnome_pants_burried_2", + "blend_mode": "overlay", + "color_ids": [ 3 ] + }, + { + "type": "icon_state", + "icon_state": "gnome_beard_burried_2", + "blend_mode": "overlay", + "color_ids": [ 4 ] + } + ], + "gnome_burried_3": [ + { + "type": "icon_state", + "icon_state": "gnome_burried_3", + "blend_mode": "overlay" + }, + { + "type": "icon_state", + "icon_state": "gnome_hat_burried_3", + "blend_mode": "overlay", + "color_ids": [ 1 ] + }, + { + "type": "icon_state", + "icon_state": "gnome_body_burried_3", + "blend_mode": "overlay", + "color_ids": [ 2 ] + }, + { + "type": "icon_state", + "icon_state": "gnome_pants_burried_3", + "blend_mode": "overlay", + "color_ids": [ 3 ] + }, + { + "type": "icon_state", + "icon_state": "gnome_beard_burried_3", + "blend_mode": "overlay", + "color_ids": [ 4 ] + } + ] +} diff --git a/code/modules/cargo/packs/livestock.dm b/code/modules/cargo/packs/livestock.dm index a1f6b33930b..a2a1f641945 100644 --- a/code/modules/cargo/packs/livestock.dm +++ b/code/modules/cargo/packs/livestock.dm @@ -197,3 +197,16 @@ access_view = ACCESS_JANITOR contains = list(/mob/living/simple_animal/hostile/lizard) crate_name = "lizard crate" + +/datum/supply_pack/critter/garden_gnome + name = "Garden Gnome Crate" + desc = "Collect them all for your garden. Comes with three!" + hidden = TRUE + cost = CARGO_CRATE_VALUE * 20 + contains = list(/mob/living/basic/garden_gnome) + crate_name = "garden gnome crate" + +/datum/supply_pack/critter/garden_gnome/generate() + . = ..() + for(var/i in 1 to 2) + new /mob/living/basic/garden_gnome(.) diff --git a/code/modules/mob/living/basic/retaliate/garden_gnome.dm b/code/modules/mob/living/basic/retaliate/garden_gnome.dm new file mode 100644 index 00000000000..36c2b64a961 --- /dev/null +++ b/code/modules/mob/living/basic/retaliate/garden_gnome.dm @@ -0,0 +1,149 @@ +/mob/living/basic/garden_gnome + name = "Garden Gnome" + desc = "You have been gnomed." + icon = 'icons/mob/simple/garden_gnome.dmi' + icon_state = "gnome" + icon_living = "gnome" + pass_flags = PASSMOB + mob_biotypes = MOB_ORGANIC|MOB_HUMANOID + speed = 1 + maxHealth = 40 + health = 40 + basic_mob_flags = DEL_ON_DEATH + + obj_damage = 20 + melee_damage_lower = 5 + melee_damage_upper = 10 + attack_verb_continuous = "punches" + attack_verb_simple = "punch" + attack_sound = 'sound/weapons/punch1.ogg' + damage_coeff = list(BRUTE = 1, BURN = 1, TOX = 1, CLONE = 1, STAMINA = 0, OXY = 1) + speak_emote = list("announces") + + unsuitable_atmos_damage = 0 + minimum_survivable_temperature = 0 + maximum_survivable_temperature = 500 + + faction = list("gnomes") + mob_size = MOB_SIZE_SMALL + gold_core_spawnable = HOSTILE_SPAWN + greyscale_config = /datum/greyscale_config/garden_gnome + ai_controller = /datum/ai_controller/basic_controller/garden_gnome + /// The damage resistence when sinked into the ground + var/resistance_when_sinked = list(BRUTE = 0.5, BURN = 0.5, TOX = 1, CLONE = 1, STAMINA = 0, OXY = 1) + /// Realistically weighted list of usual gnome hat colours + var/static/list/gnome_hat_colours = list( + COLOR_GNOME_RED_ONE = 9, + COLOR_GNOME_RED_TWO = 9, + COLOR_GNOME_RED_THREE = 9, + COLOR_GNOME_BLUE_ONE = 3, + COLOR_GNOME_BLUE_TWO = 3, + COLOR_GNOME_BLUE_THREE = 3, + COLOR_GNOME_GREEN_ONE = 3, + COLOR_GNOME_GREEN_TWO = 3, + COLOR_GNOME_ORANGE = 3, + COLOR_GNOME_BROWN_ONE = 3, + COLOR_GNOME_YELLOW = 2, + COLOR_GNOME_GREY = 2, + COLOR_GNOME_PURPLE = 1, + COLOR_GNOME_WHITE = 1, + COLOR_GNOME_BLACK = 1, + ) + /// The chosen hat colour + var/chosen_hat_colour + /// Realistically weighted list of usual gnome body colours + var/static/list/gnome_body_colours = list( + COLOR_GNOME_YELLOW = 6, + COLOR_GNOME_RED_ONE = 3, + COLOR_GNOME_RED_TWO = 3, + COLOR_GNOME_RED_THREE = 3, + COLOR_GNOME_BLUE_ONE = 3, + COLOR_GNOME_BLUE_TWO = 3, + COLOR_GNOME_BLUE_THREE = 3, + COLOR_GNOME_GREEN_ONE = 3, + COLOR_GNOME_GREEN_TWO = 3, + COLOR_GNOME_BROWN_ONE = 3, + COLOR_GNOME_ORANGE = 2, + COLOR_GNOME_WHITE = 1, + COLOR_GNOME_GREY = 1, + COLOR_GNOME_PURPLE = 1, + COLOR_GNOME_BLACK = 1, + ) + /// Realistically weighted list of usual gnome pants colours + var/static/list/gnome_pants_colours = list( + COLOR_GNOME_BLUE_ONE = 6, + COLOR_GNOME_BLUE_TWO = 6, + COLOR_GNOME_BLUE_THREE = 6, + COLOR_GNOME_GREEN_ONE = 6, + COLOR_GNOME_GREEN_TWO = 6, + COLOR_GNOME_BROWN_ONE = 3, + COLOR_GNOME_BROWN_TWO = 3, + COLOR_GNOME_RED_ONE = 1, + COLOR_GNOME_ORANGE = 1, + COLOR_GNOME_WHITE = 1, + COLOR_GNOME_GREY = 1, + COLOR_GNOME_PURPLE = 1, + COLOR_GNOME_BLACK = 1, + ) + /// Realistically weighted list of usual gnome beard colours + var/static/list/gnome_beard_colours = list( + COLOR_GNOME_WHITE = 9, + COLOR_GNOME_GREY = 9, + COLOR_GNOME_BROWN_ONE = 6, + COLOR_GNOME_BROWN_TWO = 6, + COLOR_GNOME_BLACK = 6, + COLOR_GNOME_ORANGE = 3, + COLOR_GNOME_GREEN_TWO = 1, + COLOR_GNOME_RED_ONE = 1, + COLOR_GNOME_PURPLE = 1, + ) + +/mob/living/basic/garden_gnome/Initialize(mapload) + . = ..() + var/datum/callback/retaliate_callback = CALLBACK(src, PROC_REF(ai_retaliate_behaviour)) + chosen_hat_colour = pick_weight(gnome_hat_colours) + apply_colour() + AddElement(/datum/element/death_drops, list(/obj/effect/gibspawner/generic)) + AddElement(/datum/element/footstep, FOOTSTEP_MOB_SHOE) + AddComponent(/datum/component/ai_retaliate_advanced, retaliate_callback) + AddComponent(/datum/component/swarming) + AddComponent(/datum/component/ground_sinking, target_icon_state = icon_state, outline_colour = chosen_hat_colour, damage_res_sinked = resistance_when_sinked) + AddComponent(/datum/component/caltrop, min_damage = 5, max_damage = 10, paralyze_duration = 1 SECONDS, flags = CALTROP_BYPASS_SHOES) + ADD_TRAIT(src, TRAIT_SPACEWALK, INNATE_TRAIT) + ADD_TRAIT(src, TRAIT_VENTCRAWLER_ALWAYS, INNATE_TRAIT) + +/mob/living/basic/garden_gnome/proc/apply_colour() + if(!greyscale_config) + return + set_greyscale(colors = list(chosen_hat_colour, pick_weight(gnome_body_colours), pick_weight(gnome_pants_colours), pick_weight(gnome_beard_colours))) + +/mob/living/basic/garden_gnome/proc/ai_retaliate_behaviour(mob/living/attacker) + if (!istype(attacker)) + return + var/list/enemy_refs + for (var/mob/living/basic/garden_gnome/potential_gnome in oview(src, 7)) + enemy_refs = potential_gnome.ai_controller.blackboard[BB_BASIC_MOB_RETALIATE_LIST] + if (!enemy_refs) + enemy_refs = list() + enemy_refs |= WEAKREF(attacker) + potential_gnome.ai_controller.blackboard[BB_BASIC_MOB_RETALIATE_LIST] = enemy_refs + +/datum/ai_controller/basic_controller/garden_gnome + blackboard = list( + BB_TARGETTING_DATUM = new /datum/targetting_datum/basic(), + ) + + ai_movement = /datum/ai_movement/basic_avoidance + idle_behavior = /datum/idle_behavior/idle_random_walk/less_walking + planning_subtrees = list( + /datum/ai_planning_subtree/target_retaliate, + /datum/ai_planning_subtree/attack_obstacle_in_path, + /datum/ai_planning_subtree/basic_melee_attack_subtree/garden_gnome, + /datum/ai_planning_subtree/random_speech/garden_gnome, + ) + +/datum/ai_planning_subtree/basic_melee_attack_subtree/garden_gnome + melee_attack_behavior = /datum/ai_behavior/basic_melee_attack/garden_gnome + +/datum/ai_behavior/basic_melee_attack/garden_gnome + action_cooldown = 1.2 SECONDS diff --git a/icons/mob/simple/garden_gnome.dmi b/icons/mob/simple/garden_gnome.dmi new file mode 100644 index 0000000000000000000000000000000000000000..f4158c7069eb86ff8d074c5d19f4bb737d02df37 GIT binary patch literal 1446 zcmYjQc{tR082=3!ImRHZhe_+%lB0}vlgUxW6_O))(3%#_mMdlqG6zP6a_mN;ka@gZRh~IrJ>5fuRe*;W$AX-!>*yB!QB(bKmV&{xz5+kw~mhpr0gC#z9WU zlLwE>s;|+Ly}Z1*Ty9ra*W~16NJt2sPA@AfgRo{}P7M$hEYjWOJb<0(zXBzVR1fb+ z+shGnQV=mRh!6$<IO;^6G4OXnw>4q?xixC!(yjJP?sHsE=<ra(v_`iAU1Gf}8ZATmRdm7BZylnpq+ zsB%G-CY(YIvf1~>`|>g>1UL@;_FNGM9QxEEj*}=WEpSwqkT!)J(06aYC8t78pBW>G z{<__Y8q6kL$R6@4#@+Dm@93+LQXjXleRl^_e>lpU&#)QNa?`piH!o~AykV9s?Y<~L zpv9Lp8tll(cINU~lURn6X>-vE5@~fZ+R_iES`Qa&BCthDbv7$rULljGp-eR-u z`ABUF6=(SQ^x6_RmWmwF4_;T0{Zo)TJ7mai%8o%8{s;id#`rkMH!xmp0v4qEve&|! z*0!gU1Xnv%Jk0vNEk4U)3mB9&)`vta$mj@Ju`WejyQ;Ugq^Y+?Z~k^STF#^%x3rR< z1#6|;^JBC}W5*80$%jyGAkZDG*jreDY>Kt%9-$>?uNowG_(Uynys?w#r;Ic+_ zlHvR)W(Uq>Ctdq&^D{q*Kr|KlSS62gY3ri>%}nI+2H9B6pqjQ_%T}fJ&(f( z6uttN*L3wHGQG*D{fMn@X3e7ZB)WgGjIuQ{PkC*_6s3rj8gy zR{epg7nOF(r16K{Q8+GLg3F#qw?O#dQ-3?gz$BXy9Ue)WeU2=`8oW<|d7{{>S}b>b z32#5Ijkd?!cgGCZy~vDiCk`VAJEvv)t)1965K*)Fx?876NU`*-2w(9izkW#AT4UzgZLH8PvvlIt z<~k{j$s2|K3)AG}J(xw(xRhz#FLoV#zktwOuf}*mOlUGy$Mu2L3cM(v4eIuBjEFVn z+B&k|h5&Oe9Po1VA_8r`B)RkKNIF?K*JWw-zXo8qWuRWQ+^Q_&n1)t1s6)t{bUoRuJty;0egN|%m0I7S=d?Jzjnx!9Il`z8DXOoO;7 literal 0 HcmV?d00001 diff --git a/tgstation.dme b/tgstation.dme index 48f829cb819..d78d895b0e7 100644 --- a/tgstation.dme +++ b/tgstation.dme @@ -894,6 +894,7 @@ #include "code\datums\components\acid.dm" #include "code\datums\components\action_item_overlay.dm" #include "code\datums\components\admin_popup.dm" +#include "code\datums\components\ai_retaliate_advanced.dm" #include "code\datums\components\anti_magic.dm" #include "code\datums\components\aquarium.dm" #include "code\datums\components\area_sound_manager.dm" @@ -953,6 +954,7 @@ #include "code\datums\components\genetic_damage.dm" #include "code\datums\components\gps.dm" #include "code\datums\components\grillable.dm" +#include "code\datums\components\ground_sinking.dm" #include "code\datums\components\gunpoint.dm" #include "code\datums\components\hazard_area.dm" #include "code\datums\components\heirloom.dm" @@ -3760,6 +3762,7 @@ #include "code\modules\mob\living\basic\lavaland\bileworm\bileworm_vileworm.dm" #include "code\modules\mob\living\basic\pets\dog.dm" #include "code\modules\mob\living\basic\pets\pet.dm" +#include "code\modules\mob\living\basic\retaliate\garden_gnome.dm" #include "code\modules\mob\living\basic\retaliate\ghost.dm" #include "code\modules\mob\living\basic\ruin_defender\stickman.dm" #include "code\modules\mob\living\basic\space_fauna\carp\carp.dm"