diff --git a/_maps/RandomRuins/LavaRuins/lavaland_surface_ash_walker1.dmm b/_maps/RandomRuins/LavaRuins/lavaland_surface_ash_walker1.dmm index c68a406c39a..919d141fda7 100644 --- a/_maps/RandomRuins/LavaRuins/lavaland_surface_ash_walker1.dmm +++ b/_maps/RandomRuins/LavaRuins/lavaland_surface_ash_walker1.dmm @@ -213,7 +213,7 @@ dir = 1 }, /obj/structure/stone_tile, -/mob/living/simple_animal/hostile/asteroid/gutlunch/gubbuck, +/mob/living/basic/mining/gutlunch/milk, /turf/open/indestructible/boss, /area/ruin/unpowered/ash_walkers) "aR" = ( @@ -287,7 +287,7 @@ /obj/structure/stone_tile{ dir = 4 }, -/mob/living/simple_animal/hostile/asteroid/gutlunch/guthen, +/mob/living/basic/mining/gutlunch/warrior, /turf/open/indestructible/boss, /area/ruin/unpowered/ash_walkers) "bd" = ( diff --git a/_maps/RandomRuins/LavaRuins/lavaland_surface_mookvillage.dmm b/_maps/RandomRuins/LavaRuins/lavaland_surface_mookvillage.dmm index 9b9157f92fa..70ebd3ce428 100644 --- a/_maps/RandomRuins/LavaRuins/lavaland_surface_mookvillage.dmm +++ b/_maps/RandomRuins/LavaRuins/lavaland_surface_mookvillage.dmm @@ -68,7 +68,7 @@ /turf/open/misc/asteroid/basalt/lava_land_surface, /area/lavaland/surface/outdoors) "p" = ( -/obj/structure/material_stand, +/obj/structure/ore_container/material_stand, /turf/open/misc/asteroid/basalt/lava_land_surface, /area/lavaland/surface/outdoors) "q" = ( diff --git a/_maps/RandomRuins/LavaRuins/skyrat/lavaland_surface_ash_walker1_skyrat.dmm b/_maps/RandomRuins/LavaRuins/skyrat/lavaland_surface_ash_walker1_skyrat.dmm index c07f19269bc..f870375de62 100644 --- a/_maps/RandomRuins/LavaRuins/skyrat/lavaland_surface_ash_walker1_skyrat.dmm +++ b/_maps/RandomRuins/LavaRuins/skyrat/lavaland_surface_ash_walker1_skyrat.dmm @@ -248,7 +248,7 @@ /turf/open/indestructible/boss, /area/ruin/unpowered/ash_walkers) "ky" = ( -/mob/living/simple_animal/hostile/asteroid/gutlunch/gubbuck, +/mob/living/basic/mining/gutlunch/milk, /turf/open/misc/asteroid/basalt/lava_land_surface, /area/ruin/unpowered/ash_walkers) "lB" = ( @@ -261,7 +261,7 @@ /turf/open/lava/smooth/lava_land_surface, /area/ruin/unpowered/ash_walkers) "lH" = ( -/mob/living/simple_animal/hostile/asteroid/gutlunch/grublunch, +/mob/living/basic/mining/gutlunch/grub, /turf/open/misc/asteroid/basalt/lava_land_surface, /area/ruin/unpowered/ash_walkers) "mc" = ( @@ -495,7 +495,7 @@ /obj/structure/stone_tile/block{ dir = 4 }, -/mob/living/simple_animal/hostile/asteroid/gutlunch/guthen, +/mob/living/basic/mining/gutlunch/warrior, /turf/open/misc/asteroid/basalt/lava_land_surface, /area/ruin/unpowered/ash_walkers) "rQ" = ( diff --git a/_maps/RandomRuins/SpaceRuins/russian_derelict.dmm b/_maps/RandomRuins/SpaceRuins/russian_derelict.dmm index e1671448e34..45eee5cbe8b 100644 --- a/_maps/RandomRuins/SpaceRuins/russian_derelict.dmm +++ b/_maps/RandomRuins/SpaceRuins/russian_derelict.dmm @@ -4876,10 +4876,6 @@ /obj/structure/cable, /turf/open/floor/plating/airless, /area/ruin/space/ks13/engineering/singulo) -"IZ" = ( -/obj/effect/mapping_helpers/burnt_floor, -/turf/open/floor/iron/airless, -/area/ruin/space/ks13/engineering/singulo) "Jc" = ( /obj/effect/mapping_helpers/broken_floor, /obj/effect/mapping_helpers/broken_floor, @@ -11718,7 +11714,7 @@ PI Ex pi FI -IZ +FI FI FI zv diff --git a/_maps/map_files/Basketball/ash_gladiators.dmm b/_maps/map_files/Basketball/ash_gladiators.dmm index 3a8c74b4201..75315e028a8 100644 --- a/_maps/map_files/Basketball/ash_gladiators.dmm +++ b/_maps/map_files/Basketball/ash_gladiators.dmm @@ -132,7 +132,7 @@ /turf/open/floor/fakebasalt, /area/centcom/basketball) "ie" = ( -/mob/living/simple_animal/hostile/asteroid/gutlunch/guthen, +/mob/living/basic/mining/gutlunch/warrior, /turf/open/floor/fakebasalt, /area/centcom/basketball) "ix" = ( @@ -609,7 +609,7 @@ /turf/open/lava/smooth/basketball, /area/centcom/basketball) "Fy" = ( -/mob/living/simple_animal/hostile/asteroid/gutlunch/gubbuck, +/mob/living/basic/mining/gutlunch/milk, /turf/open/floor/fakebasalt, /area/centcom/basketball) "FS" = ( diff --git a/_maps/map_files/IceBoxStation/IceBoxStation.dmm b/_maps/map_files/IceBoxStation/IceBoxStation.dmm index db261f9b2b9..f16ecc8c4d2 100644 --- a/_maps/map_files/IceBoxStation/IceBoxStation.dmm +++ b/_maps/map_files/IceBoxStation/IceBoxStation.dmm @@ -33897,7 +33897,7 @@ /turf/open/floor/plating/snowed/smoothed/icemoon, /area/icemoon/underground/explored) "ksu" = ( -/mob/living/simple_animal/hostile/asteroid/gutlunch, +/mob/living/basic/mining/gutlunch/warrior, /turf/open/misc/asteroid/snow/icemoon, /area/icemoon/underground/explored) "ksC" = ( diff --git a/_maps/skyrat/automapper/templates/mining/lavaland_ashwalker_nenest.dmm b/_maps/skyrat/automapper/templates/mining/lavaland_ashwalker_nenest.dmm index 6fe9529fb52..e488cbeb684 100644 --- a/_maps/skyrat/automapper/templates/mining/lavaland_ashwalker_nenest.dmm +++ b/_maps/skyrat/automapper/templates/mining/lavaland_ashwalker_nenest.dmm @@ -687,7 +687,7 @@ /turf/open/misc/asteroid/basalt/lava_land_surface, /area/ruin/unpowered/ash_walkers) "xo" = ( -/mob/living/simple_animal/hostile/asteroid/gutlunch/grublunch, +/mob/living/basic/mining/gutlunch/grub, /turf/open/misc/asteroid/basalt/lava_land_surface, /area/ruin/unpowered/ash_walkers) "xY" = ( @@ -943,7 +943,7 @@ /obj/structure/stone_tile/block{ dir = 4 }, -/mob/living/simple_animal/hostile/asteroid/gutlunch/guthen, +/mob/living/basic/mining/gutlunch/warrior, /turf/open/misc/asteroid/basalt/lava_land_surface, /area/ruin/unpowered/ash_walkers) "Hf" = ( @@ -1185,7 +1185,7 @@ /turf/closed/indestructible/riveted/boss, /area/ruin/unpowered/ash_walkers) "QU" = ( -/mob/living/simple_animal/hostile/asteroid/gutlunch/gubbuck, +/mob/living/basic/mining/gutlunch/milk, /turf/open/misc/asteroid/basalt/lava_land_surface, /area/ruin/unpowered/ash_walkers) "Re" = ( diff --git a/code/__DEFINES/ai/ai_blackboard.dm b/code/__DEFINES/ai/ai_blackboard.dm index 3da9c08ae27..91ad49975fe 100644 --- a/code/__DEFINES/ai/ai_blackboard.dm +++ b/code/__DEFINES/ai/ai_blackboard.dm @@ -10,6 +10,12 @@ #define BB_PATH_TO_USE "BB_path_to_use" ///How close a mob must be for us to select it as a target, if that is less than how far we can maintain it as a target #define BB_AGGRO_RANGE "BB_aggro_range" +///are we hungry? determined by the udder compnent +#define BB_CHECK_HUNGRY "BB_check_hungry" +///are we ready to breed? +#define BB_BREED_READY "BB_breed_ready" +///maximum kids we can have +#define BB_MAX_CHILDREN "BB_max_children" /// Store a single or list of emotes at this key #define BB_EMOTE_KEY "BB_emotes" diff --git a/code/__DEFINES/ai/monsters.dm b/code/__DEFINES/ai/monsters.dm index 750d65e6eb3..26ef41bbe89 100644 --- a/code/__DEFINES/ai/monsters.dm +++ b/code/__DEFINES/ai/monsters.dm @@ -190,6 +190,9 @@ /// the bonfire we will light up #define BB_MOOK_BONFIRE_TARGET "bonfire_target" +//gutlunch keys +///the trough we will eat from +#define BB_TROUGH_TARGET "trough_target" //leaper keys ///key holds our volley ability #define BB_LEAPER_VOLLEY "leaper_volley" diff --git a/code/__DEFINES/traits.dm b/code/__DEFINES/traits.dm index 5d595a2cae5..1afcd7b9c57 100644 --- a/code/__DEFINES/traits.dm +++ b/code/__DEFINES/traits.dm @@ -1349,5 +1349,7 @@ Remember to update _globalvars/traits.dm if you're adding/removing/renaming trai /// Trait given to a dragon who fails to defend their rifts #define TRAIT_RIFT_FAILURE "fail_dragon_loser" +///trait determines if this mob can breed given by /datum/component/breeding +#define TRAIT_MOB_BREEDER "mob_breeder" /// Trait given to mobs that we do not want to mindswap #define TRAIT_NO_MINDSWAP "no_mindswap" diff --git a/code/datums/ai/babies/babies_behaviors.dm b/code/datums/ai/babies/babies_behaviors.dm index 553b192a180..5941bb818f4 100644 --- a/code/datums/ai/babies/babies_behaviors.dm +++ b/code/datums/ai/babies/babies_behaviors.dm @@ -2,74 +2,77 @@ * Find a compatible, living partner, if we're also alone. */ /datum/ai_behavior/find_partner - action_cooldown = 40 SECONDS - /// Range to look. var/range = 7 - /// Maximum number of children var/max_children = 3 /datum/ai_behavior/find_partner/perform(seconds_per_tick, datum/ai_controller/controller, target_key, partner_types_key, child_types_key) . = ..() - + max_children = controller.blackboard[BB_MAX_CHILDREN] || max_children var/mob/pawn_mob = controller.pawn var/list/partner_types = controller.blackboard[partner_types_key] var/list/child_types = controller.blackboard[child_types_key] + var/mob/living/living_pawn = controller.pawn - var/mob/living/partner var/children = 0 - for(var/mob/other in oview(range, pawn_mob)) + for(var/mob/living/other in oview(range, pawn_mob)) + if(!pawn_mob.faction_check_atom(other)) + finish_action(controller, FALSE) + return + + if(children >= max_children) + finish_action(controller, FALSE) + return + if(other.stat != CONSCIOUS) //Check if it's conscious FIRST. continue - var/is_child = is_type_in_list(other, child_types) - if(is_child) //Check for children SECOND. + + if(is_type_in_list(other, child_types)) //Check for children SECOND. children++ - else if(is_type_in_list(other, partner_types)) - if(other.ckey) - continue - else if(!is_child && other.gender == MALE && !(other.flags_1 & HOLOGRAM_1)) //Better safe than sorry ;_; - partner = other + continue - //shyness check. we're not shy in front of things that share a faction with us. - else if(isliving(other) && !pawn_mob.faction_check_atom(other)) - finish_action(controller, FALSE) - return + if(!is_type_in_list(other, partner_types) || !HAS_TRAIT(other, TRAIT_MOB_BREEDER)) + continue + + if(other.ckey) + continue + + if(other.gender != living_pawn.gender && !(other.flags_1 & HOLOGRAM_1)) //Better safe than sorry ;_; + controller.set_blackboard_key(target_key, other) + finish_action(controller, TRUE) - if(partner && children < max_children) - controller.set_blackboard_key(target_key, partner) + finish_action(controller, FALSE) - finish_action(controller, TRUE) /** * Reproduce. */ /datum/ai_behavior/make_babies - action_cooldown = 40 SECONDS behavior_flags = AI_BEHAVIOR_REQUIRE_MOVEMENT | AI_BEHAVIOR_REQUIRE_REACH /datum/ai_behavior/make_babies/setup(datum/ai_controller/controller, target_key, child_types_key) + . = ..() var/atom/target = controller.blackboard[target_key] if(!target) return FALSE set_movement_target(controller, target) - return TRUE /datum/ai_behavior/make_babies/perform(seconds_per_tick, datum/ai_controller/controller, target_key, child_types_key) . = ..() var/mob/target = controller.blackboard[target_key] - if(!target || target.stat != CONSCIOUS) + if(QDELETED(target) || target.stat != CONSCIOUS) finish_action(controller, FALSE, target_key) return - - var/child_type = pick_weight(controller.blackboard[child_types_key]) - var/turf/turf_loc = get_turf(controller.pawn.loc) - if(turf_loc) - new child_type(turf_loc) - + var/mob/living/basic/living_pawn = controller.pawn + living_pawn.set_combat_mode(FALSE) + living_pawn.melee_attack(target) finish_action(controller, TRUE, target_key) /datum/ai_behavior/make_babies/finish_action(datum/ai_controller/controller, succeeded, target_key) . = ..() - controller.clear_blackboard_key(target_key) + if(!succeeded) + return + var/mob/living/living_pawn = controller.pawn + living_pawn.set_combat_mode(initial(living_pawn.combat_mode)) diff --git a/code/datums/ai/babies/babies_subtrees.dm b/code/datums/ai/babies/babies_subtrees.dm index aad92f8422e..8310cd95573 100644 --- a/code/datums/ai/babies/babies_subtrees.dm +++ b/code/datums/ai/babies/babies_subtrees.dm @@ -3,11 +3,19 @@ */ /datum/ai_planning_subtree/make_babies var/chance = 5 + operational_datums = list(/datum/component/breed) /datum/ai_planning_subtree/make_babies/SelectBehaviors(datum/ai_controller/controller, seconds_per_tick) . = ..() - if(controller.pawn.gender != FEMALE || !SPT_PROB(chance, seconds_per_tick)) + if(!SPT_PROB(chance, seconds_per_tick)) + return + + if(controller.blackboard_key_exists(BB_BABIES_TARGET)) + controller.queue_behavior(/datum/ai_behavior/make_babies, BB_BABIES_TARGET, BB_BABIES_CHILD_TYPES) + return SUBTREE_RETURN_FINISH_PLANNING + + if(controller.pawn.gender == FEMALE || !controller.blackboard[BB_BREED_READY]) return var/partner_types = controller.blackboard[BB_BABIES_PARTNER_TYPES] @@ -21,7 +29,4 @@ return // Find target - if(controller.blackboard_key_exists(BB_BABIES_TARGET)) - controller.queue_behavior(/datum/ai_behavior/make_babies, BB_BABIES_TARGET, BB_BABIES_CHILD_TYPES) - return SUBTREE_RETURN_FINISH_PLANNING controller.queue_behavior(/datum/ai_behavior/find_partner, BB_BABIES_TARGET, BB_BABIES_PARTNER_TYPES, BB_BABIES_CHILD_TYPES) diff --git a/code/datums/ai/dog/dog_controller.dm b/code/datums/ai/dog/dog_controller.dm index 6710fa9eb76..a5794ff9b5f 100644 --- a/code/datums/ai/dog/dog_controller.dm +++ b/code/datums/ai/dog/dog_controller.dm @@ -45,3 +45,20 @@ return return corgi_pawn.access_card.GetAccess() + +/datum/ai_controller/basic_controller/dog/puppy + blackboard = list( + BB_DOG_HARASS_HARM = TRUE, + BB_VISION_RANGE = AI_DOG_VISION_RANGE, + BB_PET_TARGETING_STRATEGY = /datum/targeting_strategy/basic/not_friends, + BB_TARGETING_STRATEGY = /datum/targeting_strategy/basic/holding_object, + // With tongs in hand! + BB_TARGET_HELD_ITEM = /obj/item/kitchen/tongs, + ) + planning_subtrees = list( + /datum/ai_planning_subtree/random_speech/dog, + /datum/ai_planning_subtree/pet_planning, + /datum/ai_planning_subtree/dog_harassment, + /datum/ai_planning_subtree/simple_find_target, + /datum/ai_planning_subtree/flee_target, + ) diff --git a/code/datums/ai/hunting_behavior/hunting_behaviors.dm b/code/datums/ai/hunting_behavior/hunting_behaviors.dm index 468cfed33fb..3ea9feb2b34 100644 --- a/code/datums/ai/hunting_behavior/hunting_behaviors.dm +++ b/code/datums/ai/hunting_behavior/hunting_behaviors.dm @@ -81,6 +81,7 @@ /// Do we reset the target after attacking something, so we can check for status changes. var/always_reset_target = FALSE + /datum/ai_behavior/hunt_target/setup(datum/ai_controller/controller, hunting_target_key, hunting_cooldown_key) . = ..() var/atom/hunt_target = controller.blackboard[hunting_target_key] @@ -123,10 +124,27 @@ controller.clear_blackboard_key(hunting_target_key) /datum/ai_behavior/hunt_target/unarmed_attack_target + ///do we toggle combat mode before interacting with the object? + var/switch_combat_mode = FALSE /datum/ai_behavior/hunt_target/unarmed_attack_target/target_caught(mob/living/hunter, obj/structure/cable/hunted) + if(switch_combat_mode) + hunter.combat_mode = !(hunter.combat_mode) hunter.UnarmedAttack(hunted, TRUE) +/datum/ai_behavior/hunt_target/unarmed_attack_target/finish_action(datum/ai_controller/controller, succeeded, hunting_target_key, hunting_cooldown_key) + . = ..() + if(!switch_combat_mode) + return + var/mob/living/living_pawn = controller.pawn + living_pawn.combat_mode = initial(living_pawn.combat_mode) + +/datum/ai_behavior/hunt_target/unarmed_attack_target/switch_combat_mode + switch_combat_mode = TRUE + +/datum/ai_behavior/hunt_target/unarmed_attack_target/reset_target + always_reset_target = TRUE + /datum/ai_behavior/hunt_target/use_ability_on_target always_reset_target = TRUE ///the ability we will use diff --git a/code/datums/components/breeding.dm b/code/datums/components/breeding.dm new file mode 100644 index 00000000000..7c9bcecf7bc --- /dev/null +++ b/code/datums/components/breeding.dm @@ -0,0 +1,76 @@ +/* + * A component to allow us to breed + */ +/datum/component/breed + /// additional mobs we can breed with + var/list/can_breed_with + ///path of the baby + var/baby_path + ///time to wait after breeding + var/breed_timer + ///AI key we set when we're ready to breed + var/breed_key = BB_BREED_READY + ///are we ready to breed? + var/ready_to_breed = TRUE + ///callback after we give birth to the child + var/datum/callback/post_birth + +/datum/component/breed/Initialize(list/can_breed_with = list(), breed_timer = 40 SECONDS, baby_path, post_birth) + if(!isliving(parent)) + return COMPONENT_INCOMPATIBLE + + if(ishuman(parent)) //sin detected + return COMPONENT_INCOMPATIBLE + + if(!ispath(baby_path)) + stack_trace("attempted to add a breeding component with invalid baby path!") + return + + src.can_breed_with = can_breed_with + src.breed_timer = breed_timer + src.baby_path = baby_path + src.post_birth = post_birth + + ADD_TRAIT(parent, TRAIT_SUBTREE_REQUIRED_OPERATIONAL_DATUM, type) + +/datum/component/breed/RegisterWithParent() + RegisterSignal(parent, COMSIG_HOSTILE_PRE_ATTACKINGTARGET, PROC_REF(breed_with_partner)) + ADD_TRAIT(parent, TRAIT_MOB_BREEDER, REF(src)) + var/mob/living/parent_mob = parent + parent_mob.ai_controller?.set_blackboard_key(breed_key, TRUE) + +/datum/component/breed/UnregisterFromParent() + UnregisterSignal(parent, COMSIG_HOSTILE_PRE_ATTACKINGTARGET) + REMOVE_TRAIT(parent, TRAIT_MOB_BREEDER, REF(src)) + post_birth = null + + +/datum/component/breed/proc/breed_with_partner(mob/living/source, mob/living/target) + SIGNAL_HANDLER + + if(source.combat_mode) + return + + if(!is_type_in_typecache(target, can_breed_with)) + return + + if(!HAS_TRAIT(target, TRAIT_MOB_BREEDER) || target.gender == source.gender) + return + + if(!ready_to_breed) + source.balloon_alert(source, "not ready!") + return COMPONENT_HOSTILE_NO_ATTACK + + var/turf/delivery_destination = get_turf(source) + var/mob/living/baby = new baby_path(delivery_destination) + new /obj/effect/temp_visual/heart(delivery_destination) + toggle_status(source) + + addtimer(CALLBACK(src, PROC_REF(toggle_status), source), breed_timer) + post_birth?.Invoke(baby, target) + return COMPONENT_HOSTILE_NO_ATTACK + +/datum/component/breed/proc/toggle_status(mob/living/source) + ready_to_breed = !ready_to_breed + source.ai_controller?.set_blackboard_key(BB_BREED_READY, ready_to_breed) + diff --git a/code/datums/components/pet_commands/pet_commands_basic.dm b/code/datums/components/pet_commands/pet_commands_basic.dm index a1587acef19..c614e623bf8 100644 --- a/code/datums/components/pet_commands/pet_commands_basic.dm +++ b/code/datums/components/pet_commands/pet_commands_basic.dm @@ -158,7 +158,37 @@ return SUBTREE_RETURN_FINISH_PLANNING /** - * # Pet Command: targeted Ability + * # Breed command. breed with a partner! + */ +/datum/pet_command/point_targeting/breed + command_name = "Breed" + command_desc = "Command your pet to attempt to breed with a partner." + radial_icon = 'icons/mob/simple/animal.dmi' + radial_icon_state = "heart" + speech_commands = list("breed", "consummate") + +/datum/pet_command/point_targeting/breed/set_command_target(mob/living/parent, atom/target) + if(isnull(target) || !isliving(target)) + return + if(!HAS_TRAIT(parent, TRAIT_MOB_BREEDER) || !HAS_TRAIT(target, TRAIT_MOB_BREEDER)) + return + if(isnull(parent.ai_controller)) + return + if(!parent.ai_controller.blackboard[BB_BREED_READY] || isnull(parent.ai_controller.blackboard[BB_BABIES_PARTNER_TYPES])) + return + var/mob/living/living_target = target + if(!living_target.ai_controller?.blackboard[BB_BREED_READY]) + return + return ..() + +/datum/pet_command/point_targeting/breed/execute_action(datum/ai_controller/controller) + if(is_type_in_list(controller.blackboard[BB_CURRENT_PET_TARGET], controller.blackboard[BB_BABIES_PARTNER_TYPES])) + controller.queue_behavior(/datum/ai_behavior/make_babies, BB_CURRENT_PET_TARGET) + controller.clear_blackboard_key(BB_ACTIVE_PET_COMMAND) + return SUBTREE_RETURN_FINISH_PLANNING + +/** + * # Pet Command: Targetted Ability * Tells a pet to use some kind of ability on the next thing you point at */ /datum/pet_command/point_targeting/use_ability diff --git a/code/datums/components/torn_wall.dm b/code/datums/components/torn_wall.dm index ebe0fe9a0a6..566275bdfa6 100644 --- a/code/datums/components/torn_wall.dm +++ b/code/datums/components/torn_wall.dm @@ -15,7 +15,7 @@ /datum/component/torn_wall/Initialize() . = ..() - if (!iswallturf(parent) || isindestructiblewall(parent)) + if (!isclosedturf(parent) || isindestructiblewall(parent)) return COMPONENT_INCOMPATIBLE /datum/component/torn_wall/RegisterWithParent() @@ -53,6 +53,12 @@ return var/turf/closed/wall/attached_wall = parent playsound(attached_wall, 'sound/effects/meteorimpact.ogg', 100, vary = TRUE) + + if(ismineralturf(attached_wall)) + var/turf/closed/mineral/mineral_turf = attached_wall + mineral_turf.gets_drilled() + return + attached_wall.dismantle_wall(devastated = TRUE) /// Fix it up on weld diff --git a/code/datums/components/udder.dm b/code/datums/components/udder.dm index c1f0ff03245..7a2f7b56b7b 100644 --- a/code/datums/components/udder.dm +++ b/code/datums/components/udder.dm @@ -13,7 +13,8 @@ /datum/component/udder/Initialize(udder_type = /obj/item/udder, datum/callback/on_milk_callback, datum/callback/on_generate_callback, reagent_produced_typepath = /datum/reagent/consumable/milk) if(!isliving(parent)) //technically is possible to drop this on carbons... but you wouldn't do that to me, would you? return COMPONENT_INCOMPATIBLE - udder = new udder_type(null, parent, on_generate_callback, reagent_produced_typepath) + udder = new udder_type(null) + udder.add_features(parent, on_generate_callback, reagent_produced_typepath) src.on_milk_callback = on_milk_callback /datum/component/udder/RegisterWithParent() @@ -70,14 +71,68 @@ var/mob/living/udder_mob ///optional proc to callback to when the udder generates milk var/datum/callback/on_generate_callback - -/obj/item/udder/Initialize(mapload, udder_mob, on_generate_callback, reagent_produced_typepath = /datum/reagent/consumable/milk) - src.udder_mob = udder_mob - src.on_generate_callback = on_generate_callback + ///do we require some food to generate milk? + var/require_consume_type + ///how long does each food consumption allow us to make milk + var/require_consume_timer = 2 MINUTES + ///hunger key we set to look for food + var/hunger_key = BB_CHECK_HUNGRY + +/obj/item/udder/proc/add_features(parent, callback, reagent = /datum/reagent/consumable/milk) + udder_mob = parent + on_generate_callback = callback create_reagents(size, REAGENT_HOLDER_ALIVE) - src.reagent_produced_typepath = reagent_produced_typepath + reagent_produced_typepath = reagent initial_conditions() + if(isnull(require_consume_type)) + return + RegisterSignal(udder_mob, COMSIG_HOSTILE_PRE_ATTACKINGTARGET, PROC_REF(on_mob_consume)) + RegisterSignal(udder_mob, COMSIG_ATOM_ATTACKBY, PROC_REF(on_mob_feed)) + udder_mob.ai_controller?.set_blackboard_key(BB_CHECK_HUNGRY, TRUE) + +/obj/item/udder/proc/on_mob_consume(datum/source, atom/feed) + SIGNAL_HANDLER + + if(!istype(feed, require_consume_type)) + return + INVOKE_ASYNC(src, PROC_REF(handle_consumption), feed) + return COMPONENT_HOSTILE_NO_ATTACK + +/obj/item/udder/proc/on_mob_feed(datum/source, atom/used_item, mob/living/user) + SIGNAL_HANDLER + + if(!istype(used_item, require_consume_type)) + return + INVOKE_ASYNC(src, PROC_REF(handle_consumption), used_item, user) + return COMPONENT_NO_AFTERATTACK + +/obj/item/udder/proc/handle_consumption(atom/movable/food, mob/user) + if(locate(food.type) in src) + if(user) + user.balloon_alert(user, "already full!") + return + playsound(udder_mob.loc,'sound/items/eatfood.ogg', 50, TRUE) + udder_mob.visible_message(span_notice("[udder_mob] gobbles up [food]!"), span_notice("You gobble up [food]!")) + var/atom/movable/final_food = food + if(isstack(food)) //if stack, only consume 1 + var/obj/item/stack/food_stack = food + final_food = food_stack.split_stack(udder_mob, 1) + final_food.forceMove(src) + +/obj/item/udder/Entered(atom/movable/arrived, atom/old_loc, list/atom/old_locs) + if(!istype(arrived, require_consume_type)) + return ..() + + udder_mob.ai_controller?.set_blackboard_key(hunger_key, FALSE) + QDEL_IN(arrived, require_consume_timer) + return ..() + +/obj/item/udder/Exited(atom/movable/gone, direction) . = ..() + if(!istype(gone, require_consume_type)) + return + udder_mob.ai_controller?.set_blackboard_key(hunger_key, TRUE) + /obj/item/udder/Destroy() . = ..() @@ -101,10 +156,13 @@ * Proc called every 2 seconds from SSMobs to add whatever reagent the udder is generating. */ /obj/item/udder/proc/generate() - if(prob(5)) - reagents.add_reagent(reagent_produced_typepath, rand(5, 10), added_purity = 1) - if(on_generate_callback) - on_generate_callback.Invoke(reagents.total_volume, reagents.maximum_volume) + if(!isnull(require_consume_type) && !(locate(require_consume_type) in src)) + return + if(prob(95)) + return + reagents.add_reagent(reagent_produced_typepath, rand(5, 10), added_purity = 1) + if(on_generate_callback) + on_generate_callback.Invoke(reagents.total_volume, reagents.maximum_volume) /** * Proc called from attacking the component parent with the correct item, moves reagents into the glass basically. @@ -125,48 +183,20 @@ /** * # gutlunch udder subtype - * - * Used by gutlunches, and generates healing reagents instead of milk on eating gibs instead of a process. Starts empty! - * Female gutlunches (ahem, guthens if you will) make babies when their udder is full under processing, instead of milk generation */ + /obj/item/udder/gutlunch name = "nutrient sac" - -/obj/item/udder/gutlunch/initial_conditions() - if(!udder_mob) - return - if(udder_mob.gender == FEMALE) - START_PROCESSING(SSobj, src) - RegisterSignal(udder_mob, COMSIG_HOSTILE_PRE_ATTACKINGTARGET, PROC_REF(on_mob_attacking)) - -/obj/item/udder/gutlunch/process(seconds_per_tick) - var/mob/living/simple_animal/hostile/asteroid/gutlunch/gutlunch = udder_mob - if(reagents.total_volume != reagents.maximum_volume) - return - if(gutlunch.make_babies()) - reagents.clear_reagents() - //usually this would be a callback but this is a specifically gutlunch feature so fuck it, gutlunch specific proccall - gutlunch.regenerate_icons(reagents.total_volume, reagents.maximum_volume) - -/** - * signal called on parent attacking an atom -*/ -/obj/item/udder/gutlunch/proc/on_mob_attacking(mob/living/simple_animal/hostile/gutlunch, atom/target) - SIGNAL_HANDLER - - if(is_type_in_typecache(target, gutlunch.wanted_objects)) //we eats - generate() - gutlunch.visible_message(span_notice("[udder_mob] slurps up [target].")) - qdel(target) - return COMPONENT_HOSTILE_NO_ATTACK //there is no longer a target to attack + require_consume_type = /obj/item/stack/ore + reagent_produced_typepath = /datum/reagent/medicine/mine_salve /obj/item/udder/gutlunch/generate() - var/made_something = FALSE - if(prob(60)) + . = ..() + if(!.) + return + if(locate(/obj/item/stack/ore/gold) in src) reagents.add_reagent(/datum/reagent/consumable/cream, rand(2, 5), added_purity = 1) - made_something = TRUE - if(prob(45)) + if(locate(/obj/item/stack/ore/bluespace_crystal) in src) reagents.add_reagent(/datum/reagent/medicine/salglu_solution, rand(2,5)) - made_something = TRUE - if(made_something && on_generate_callback) + if(on_generate_callback) on_generate_callback.Invoke(reagents.total_volume, reagents.maximum_volume) diff --git a/code/datums/elements/ore_collecting.dm b/code/datums/elements/ore_collecting.dm new file mode 100644 index 00000000000..30c9ecc7598 --- /dev/null +++ b/code/datums/elements/ore_collecting.dm @@ -0,0 +1,26 @@ +/* + * A component to allow us to collect ore + */ +/datum/element/ore_collecting + + +/datum/element/ore_collecting/Attach(datum/target) + . = ..() + + if(!isliving(target)) + return ELEMENT_INCOMPATIBLE + RegisterSignal(target, COMSIG_HOSTILE_PRE_ATTACKINGTARGET, PROC_REF(collect_ore)) + +/datum/element/ore_collecting/Detach(datum/target) + . = ..() + UnregisterSignal(target, COMSIG_HOSTILE_PRE_ATTACKINGTARGET) + +/datum/element/ore_collecting/proc/collect_ore(mob/living/source, atom/target) + SIGNAL_HANDLER + + if(!istype(target, /obj/item/stack/ore)) + return + + var/atom/movable/movable_target = target + movable_target.forceMove(source) + return COMPONENT_HOSTILE_NO_ATTACK diff --git a/code/datums/elements/wall_tearer.dm b/code/datums/elements/wall_tearer.dm index 75e892dc4cb..2c9ff5416d5 100644 --- a/code/datums/elements/wall_tearer.dm +++ b/code/datums/elements/wall_tearer.dm @@ -28,17 +28,15 @@ src.tear_time = tear_time src.reinforced_multiplier = reinforced_multiplier src.do_after_key = do_after_key - RegisterSignal(target, COMSIG_LIVING_UNARMED_ATTACK, PROC_REF(on_attacked_wall)) + RegisterSignals(target, list(COMSIG_HOSTILE_PRE_ATTACKINGTARGET, COMSIG_LIVING_UNARMED_ATTACK), PROC_REF(on_attacked_wall)) /datum/element/wall_tearer/Detach(datum/source) . = ..() - UnregisterSignal(source, COMSIG_LIVING_UNARMED_ATTACK) + UnregisterSignal(source, list(COMSIG_HOSTILE_PRE_ATTACKINGTARGET, COMSIG_LIVING_UNARMED_ATTACK)) /// Try to tear up a wall /datum/element/wall_tearer/proc/on_attacked_wall(mob/living/tearer, atom/target, proximity_flag) SIGNAL_HANDLER - if (!proximity_flag) - return NONE if (DOING_INTERACTION_WITH_TARGET(tearer, target) || (!isnull(do_after_key) && DOING_INTERACTION(tearer, do_after_key))) tearer.balloon_alert(tearer, "busy!") return COMPONENT_HOSTILE_NO_ATTACK diff --git a/code/datums/greyscale/config_types/greyscale_configs/greyscale_mobs.dm b/code/datums/greyscale/config_types/greyscale_configs/greyscale_mobs.dm index a4c7c372525..250eba9a0d5 100644 --- a/code/datums/greyscale/config_types/greyscale_configs/greyscale_mobs.dm +++ b/code/datums/greyscale/config_types/greyscale_configs/greyscale_mobs.dm @@ -39,3 +39,9 @@ name = "Pony" icon_file = 'icons/mob/simple/animal.dmi' json_config = 'code/datums/greyscale/json_configs/pony.json' + +/datum/greyscale_config/gutlunch + name = "Gutlunch" + icon_file = 'icons/mob/simple/lavaland/lavaland_monsters.dmi' + json_config = 'code/datums/greyscale/json_configs/gutlunch.json' + diff --git a/code/datums/greyscale/json_configs/gutlunch.json b/code/datums/greyscale/json_configs/gutlunch.json new file mode 100644 index 00000000000..0b930338163 --- /dev/null +++ b/code/datums/greyscale/json_configs/gutlunch.json @@ -0,0 +1,15 @@ +{ + "gutlunch": [ + { + "type": "icon_state", + "icon_state": "gutlunch", + "blend_mode": "overlay", + "color_ids": [ 1 ] + }, + { + "type": "icon_state", + "icon_state": "gutlunch_face", + "blend_mode": "overlay" + } + ] +} diff --git a/code/game/objects/items/stacks/sheets/sheet_types.dm b/code/game/objects/items/stacks/sheets/sheet_types.dm index a8e20527b8c..9508730c8e7 100644 --- a/code/game/objects/items/stacks/sheets/sheet_types.dm +++ b/code/game/objects/items/stacks/sheets/sheet_types.dm @@ -303,6 +303,7 @@ GLOBAL_LIST_INIT(wood_recipes, list ( \ new/datum/stack_recipe("wooden barricade", /obj/structure/barricade/wooden, 5, time = 5 SECONDS, one_per_turf = TRUE, on_solid_ground = TRUE, category = CAT_STRUCTURE), \ new/datum/stack_recipe("wooden door", /obj/structure/mineral_door/wood, 10, time = 2 SECONDS, one_per_turf = TRUE, on_solid_ground = TRUE, category = CAT_DOORS), \ new/datum/stack_recipe("wooden stairs frame", /obj/structure/stairs_frame/wood, 10, time = 5 SECONDS, one_per_turf = TRUE, on_solid_ground = TRUE, category = CAT_STRUCTURE), \ + new/datum/stack_recipe("wooden fence", /obj/structure/railing/wooden_fence, 2, time = 5 SECONDS, one_per_turf = TRUE, on_solid_ground = TRUE, category = CAT_STRUCTURE), \ new/datum/stack_recipe("coffin", /obj/structure/closet/crate/coffin, 5, time = 1.5 SECONDS, one_per_turf = TRUE, on_solid_ground = TRUE, category = CAT_FURNITURE), \ new/datum/stack_recipe("book case", /obj/structure/bookcase, 4, time = 1.5 SECONDS, one_per_turf = TRUE, on_solid_ground = TRUE, category = CAT_FURNITURE), \ new/datum/stack_recipe("drying rack", /obj/machinery/smartfridge/drying_rack, 10, time = 1.5 SECONDS, one_per_turf = TRUE, on_solid_ground = TRUE, category = CAT_TOOLS), \ diff --git a/code/game/objects/structures/ore_containers.dm b/code/game/objects/structures/ore_containers.dm new file mode 100644 index 00000000000..6bc6f680116 --- /dev/null +++ b/code/game/objects/structures/ore_containers.dm @@ -0,0 +1,62 @@ +///structure to contain ores +/obj/structure/ore_container + +/obj/structure/ore_container/attackby(obj/item/ore, mob/living/carbon/human/user, list/modifiers) + if(istype(ore, /obj/item/stack/ore) && !user.combat_mode) + ore.forceMove(src) + return + return ..() + +/obj/structure/ore_container/Entered(atom/movable/mover) + . = ..() + update_appearance(UPDATE_OVERLAYS) + +/obj/structure/ore_container/Exited(atom/movable/mover) + . = ..() + update_appearance(UPDATE_OVERLAYS) + +/obj/structure/ore_container/ui_interact(mob/user, datum/tgui/ui) + . = ..() + ui = SStgui.try_update_ui(user, src, ui) + if(!ui) + ui = new(user, src, "OreContainer") + ui.open() + +/obj/structure/ore_container/ui_data(mob/user) + var/list/data = list() + data["ores"] = list() + for(var/obj/item/stack/ore/ore_item in contents) + data["ores"] += list(list( + "id" = REF(ore_item), + "name" = ore_item.name, + "amount" = ore_item.amount, + )) + return data + +/obj/structure/ore_container/ui_static_data(mob/user) + var/list/data = list() + data["ore_images"] = list() + for(var/obj/item/stack/ore_item as anything in subtypesof(/obj/item/stack/ore)) + data["ore_images"] += list(list( + "name" = initial(ore_item.name), + "icon" = icon2base64(getFlatIcon(image(icon = initial(ore_item.icon), icon_state = initial(ore_item.icon_state)), no_anim=TRUE)) + )) + return data + +/obj/structure/ore_container/ui_act(action, list/params, datum/tgui/ui, datum/ui_state/state) + . = ..() + + if(. || !isliving(ui.user)) + return TRUE + + var/mob/living/customer = ui.user + var/obj/item/stack_to_move + switch(action) + if("withdraw") + if(isnull(params["reference"])) + return TRUE + stack_to_move = locate(params["reference"]) in contents + if(isnull(stack_to_move)) + return TRUE + stack_to_move.forceMove(get_turf(customer)) + return TRUE diff --git a/code/game/objects/structures/railings.dm b/code/game/objects/structures/railings.dm index 5aaed01e0b8..fa12148d330 100644 --- a/code/game/objects/structures/railings.dm +++ b/code/game/objects/structures/railings.dm @@ -17,6 +17,8 @@ var/climbable = TRUE ///Initial direction of the railing. var/ini_dir + ///item released when deconstructed + var/item_deconstruct = /obj/item/stack/rods /datum/armor/structure_railing melee = 35 @@ -98,13 +100,11 @@ return TRUE /obj/structure/railing/deconstruct(disassembled) - if(!(flags_1 & NODECONSTRUCT_1)) - if (istype(src,/obj/structure/railing/corner)) // Corner railings only cost 1 rod - var/obj/item/stack/rods/rod = new /obj/item/stack/rods(drop_location(), 1) - transfer_fingerprints_to(rod) - else - var/obj/item/stack/rods/rod = new /obj/item/stack/rods(drop_location(), 2) - transfer_fingerprints_to(rod) + if((flags_1 & NODECONSTRUCT_1)) + return ..() + var/rods_to_make = istype(src,/obj/structure/railing/corner) ? 1 : 2 + var/obj/rod = new item_deconstruct(drop_location(), rods_to_make) + transfer_fingerprints_to(rod) return ..() ///Implements behaviour that makes it possible to unanchor the railing. @@ -156,3 +156,35 @@ /obj/structure/railing/proc/check_anchored(checked_anchored) if(anchored == checked_anchored) return TRUE + + +/obj/structure/railing/wooden_fence + name = "wooden fence" + desc = "wooden fence meant to keep animals in." + icon = 'icons/obj/structures.dmi' + icon_state = "wooden_railing" + item_deconstruct = /obj/item/stack/sheet/mineral/wood + plane = GAME_PLANE_FOV_HIDDEN + layer = ABOVE_MOB_LAYER + +/obj/structure/railing/wooden_fence/Initialize(mapload) + . = ..() + RegisterSignal(src, COMSIG_ATOM_DIR_CHANGE, PROC_REF(on_change_layer)) + adjust_dir_layer(dir) + +/obj/structure/railing/wooden_fence/proc/on_change_layer(datum/source, old_dir, new_dir) + SIGNAL_HANDLER + adjust_dir_layer(new_dir) + +/obj/structure/railing/wooden_fence/proc/adjust_dir_layer(direction) + var/new_layer = (direction & NORTH) ? MOB_LAYER : ABOVE_MOB_LAYER + layer = new_layer + + +/obj/structure/railing/corner/end/wooden_fence + icon = 'icons/obj/structures.dmi' + icon_state = "wooden_railing_corner" + +/obj/structure/railing/corner/end/flip/wooden_fence + icon = 'icons/obj/structures.dmi' + icon_state = "wooden_railing_corner_flipped" diff --git a/code/modules/mob/living/basic/lavaland/goldgrub/goldgrub.dm b/code/modules/mob/living/basic/lavaland/goldgrub/goldgrub.dm index 79bfc160804..0cf80b0e68f 100644 --- a/code/modules/mob/living/basic/lavaland/goldgrub/goldgrub.dm +++ b/code/modules/mob/living/basic/lavaland/goldgrub/goldgrub.dm @@ -52,6 +52,9 @@ /datum/action/cooldown/mob_cooldown/burrow = BB_BURROW_ABILITY, ) grant_actions_by_list(innate_actions) + + AddElement(/datum/element/ore_collecting) + AddElement(/datum/element/wall_tearer, allow_reinforced = FALSE) AddComponent(/datum/component/ai_listen_to_weather) AddComponent(\ /datum/component/appearance_on_aggro,\ @@ -65,17 +68,6 @@ RegisterSignal(src, COMSIG_ATOM_PRE_BULLET_ACT, PROC_REF(block_bullets)) -/mob/living/basic/mining/goldgrub/UnarmedAttack(atom/attack_target, proximity_flag, list/modifiers) - . = ..() - if(!.) - return - - if(!proximity_flag) - return - - if(istype(attack_target, /obj/item/stack/ore)) - consume_ore(attack_target) - /mob/living/basic/mining/goldgrub/proc/block_bullets(datum/source, obj/projectile/hitting_projectile) SIGNAL_HANDLER @@ -133,12 +125,14 @@ max_eggs_held = 1,\ ) -/mob/living/basic/mining/goldgrub/proc/consume_ore(obj/item/target_ore) +/mob/living/basic/mining/goldgrub/Entered(atom/movable/arrived, atom/old_loc, list/atom/old_locs) + . = ..() + if(!istype(arrived, /obj/item/stack/ore)) + return playsound(src,'sound/items/eatfood.ogg', rand(10,50), TRUE) - target_ore.forceMove(src) if(!can_lay_eggs) return - if(!istype(target_ore, /obj/item/stack/ore/bluespace_crystal) || prob(60)) + if(!istype(arrived, /obj/item/stack/ore/bluespace_crystal) || prob(60)) return new /obj/item/food/egg/green/grub_egg(get_turf(src)) diff --git a/code/modules/mob/living/basic/lavaland/gutlunchers/gutluncher_foodtrough.dm b/code/modules/mob/living/basic/lavaland/gutlunchers/gutluncher_foodtrough.dm new file mode 100644 index 00000000000..16139da00be --- /dev/null +++ b/code/modules/mob/living/basic/lavaland/gutlunchers/gutluncher_foodtrough.dm @@ -0,0 +1,40 @@ +/obj/structure/ore_container/gutlunch_trough + name = "gutlunch trough" + desc = "The gutlunches will eat out of it!" + icon = 'icons/obj/structures.dmi' + icon_state = "gutlunch_trough" + density = TRUE + anchored = TRUE + ///list of materials in the trough + var/list/list_of_materials = list() + +/obj/structure/ore_container/gutlunch_trough/Entered(atom/movable/mover) + if(!istype(mover, /obj/item/stack/ore)) + return ..() + if(list_of_materials[mover.type]) + return ..() + list_of_materials[mover.type] = list("pixel_x" = rand(-5, 8), "pixel_y" = rand(-2, -7)) + return ..() + +/obj/structure/ore_container/gutlunch_trough/Exited(atom/movable/mover) + if(!istype(mover, /obj/item/stack/ore) || !isnull(locate(mover.type) in contents)) + return ..() + list_of_materials -= mover.type + return ..() + +/obj/structure/ore_container/gutlunch_trough/deconstruct(disassembled = TRUE) + if(flags_1 & NODECONSTRUCT_1) + return + new /obj/item/stack/sheet/mineral/wood(drop_location(), 5) + qdel(src) + +/obj/structure/ore_container/gutlunch_trough/update_overlays() + . = ..() + for(var/ore_entry in list_of_materials) + var/obj/item/ore_item = ore_entry + var/image/ore_icon = image(icon = initial(ore_item.icon), icon_state = initial(ore_item.icon_state), layer = LOW_ITEM_LAYER) + var/list/pixel_positions = list_of_materials[ore_entry] + ore_icon.transform = ore_icon.transform.Scale(0.4, 0.4) + ore_icon.pixel_x = pixel_positions["pixel_x"] + ore_icon.pixel_y = pixel_positions["pixel_y"] + . += ore_icon diff --git a/code/modules/mob/living/basic/lavaland/gutlunchers/gutlunchers.dm b/code/modules/mob/living/basic/lavaland/gutlunchers/gutlunchers.dm new file mode 100644 index 00000000000..df9dc3cb3ec --- /dev/null +++ b/code/modules/mob/living/basic/lavaland/gutlunchers/gutlunchers.dm @@ -0,0 +1,167 @@ +#define MAX_ATTACK_DIFFERENCE 3 +#define MAX_LOWER_ATTACK 15 +#define MINIMUM_POSSIBLE_SPEED 1 +#define MAX_POSSIBLE_HEALTH 100 + +/mob/living/basic/mining/gutlunch + name = "gutlunch" + desc = "A scavenger that eats raw ores, often found alongside ash walkers. Produces a thick, nutritious milk." + icon = 'icons/mob/simple/lavaland/lavaland_monsters.dmi' + icon_state = "gutlunch" + combat_mode = FALSE + icon_living = "gutlunch" + icon_dead = "gutlunch" + mob_biotypes = MOB_ORGANIC|MOB_BEAST + basic_mob_flags = DEL_ON_DEATH + speak_emote = list("warbles", "quavers") + faction = list(FACTION_MINING, FACTION_ASHWALKER) + response_help_continuous = "pets" + response_help_simple = "pet" + response_disarm_continuous = "gently pushes aside" + response_disarm_simple = "gently push aside" + response_harm_continuous = "squishes" + response_harm_simple = "squish" + friendly_verb_continuous = "pinches" + friendly_verb_simple = "pinch" + gold_core_spawnable = FRIENDLY_SPAWN + death_message = "is pulped into bugmash." + greyscale_config = /datum/greyscale_config/gutlunch + ///possible colors we can have + var/list/possible_colors = list(COLOR_WHITE) + ///can we breed? + var/can_breed = TRUE + +/mob/living/basic/mining/gutlunch/Initialize(mapload) + . = ..() + RegisterSignal(src, COMSIG_HOSTILE_PRE_ATTACKINGTARGET, PROC_REF(pre_attack)) + if(greyscale_config) + set_greyscale(colors = list(pick(possible_colors))) + AddElement(/datum/element/ai_retaliate) + if(!can_breed) + return + AddComponent(\ + /datum/component/breed,\ + can_breed_with = typecacheof(list(/mob/living/basic/mining/gutlunch)),\ + baby_path = /mob/living/basic/mining/gutlunch/grub,\ + post_birth = CALLBACK(src, PROC_REF(after_birth)),\ + breed_timer = 3 MINUTES,\ + ) + +/mob/living/basic/mining/gutlunch/proc/pre_attack(mob/living/puncher, atom/target) + SIGNAL_HANDLER + + if(!istype(target, /obj/structure/ore_container/gutlunch_trough)) + return + + var/obj/ore_food = locate(/obj/item/stack/ore) in target + + if(isnull(ore_food)) + balloon_alert(src, "no food!") + else + melee_attack(ore_food) + return COMPONENT_HOSTILE_NO_ATTACK + +/mob/living/basic/mining/gutlunch/proc/after_birth(mob/living/basic/mining/gutlunch/grub/baby, mob/living/partner) + var/our_color = LAZYACCESS(atom_colours, FIXED_COLOUR_PRIORITY) || COLOR_GRAY + var/partner_color = LAZYACCESS(partner.atom_colours, FIXED_COLOUR_PRIORITY) || COLOR_GRAY + baby.add_atom_colour(BlendRGB(our_color, partner_color, 1), FIXED_COLOUR_PRIORITY) + var/atom/male_parent = (gender == MALE) ? src : partner + baby.inherited_stats = new(male_parent) + +/mob/living/basic/mining/gutlunch/proc/roll_stats(input_attack, input_speed, input_health) + melee_damage_lower = rand(input_attack, min(MAX_LOWER_ATTACK, input_attack + MAX_ATTACK_DIFFERENCE)) + melee_damage_upper = melee_damage_lower + MAX_ATTACK_DIFFERENCE + speed = rand(MINIMUM_POSSIBLE_SPEED, input_speed) + maxHealth = rand(input_health, MAX_POSSIBLE_HEALTH) + health = maxHealth + +/mob/living/basic/mining/gutlunch/milk + name = "gubbuck" + gender = FEMALE + possible_colors = list("#E39FBB","#817178","#9d667d") + ai_controller = /datum/ai_controller/basic_controller/gutlunch/gutlunch_milk + ///overlay we display when our udder is full! + var/mutable_appearance/full_udder + +/mob/living/basic/mining/gutlunch/milk/Initialize(mapload) + . = ..() + var/datum/callback/milking_callback = CALLBACK(src, TYPE_PROC_REF(/atom/movable, update_overlays)) + AddComponent(\ + /datum/component/udder,\ + udder_type = /obj/item/udder/gutlunch,\ + on_milk_callback = milking_callback,\ + on_generate_callback = milking_callback,\ + ) + full_udder = mutable_appearance(icon, "gl_full") + full_udder.color = LAZYACCESS(atom_colours, FIXED_COLOUR_PRIORITY) || COLOR_GRAY + +/mob/living/basic/mining/gutlunch/warrior + name = "gunther" + gender = MALE + melee_damage_lower = 8 + melee_damage_upper = 13 + speed = 5 + maxHealth = 70 + health = 70 + ai_controller = /datum/ai_controller/basic_controller/gutlunch/gutlunch_warrior + possible_colors = list("#6d77ff","#8578e4","#97b6f6") + //pet commands when we tame the gutluncher + var/static/list/pet_commands = list( + /datum/pet_command/idle, + /datum/pet_command/free, + /datum/pet_command/point_targeting/attack, + /datum/pet_command/point_targeting/breed, + /datum/pet_command/follow, + /datum/pet_command/point_targeting/fetch, + /datum/pet_command/mine_walls, + ) + +/mob/living/basic/mining/gutlunch/warrior/Initialize(mapload) + . = ..() + roll_stats(melee_damage_lower, speed, maxHealth) + AddComponent(/datum/component/obeys_commands, pet_commands) + AddElement(/datum/element/wall_tearer, allow_reinforced = FALSE) + +/mob/living/basic/mining/gutlunch/milk/update_overlays(new_udder_volume, max_udder_volume) + . = ..() + if(new_udder_volume != max_udder_volume) + return + . += full_udder + +/mob/living/basic/mining/gutlunch/grub + name = "grublunch" + possible_colors = list("#cc9797", "#b74c4c") + can_breed = FALSE + gender = NEUTER + ai_controller = /datum/ai_controller/basic_controller/gutlunch/gutlunch_baby + ///list of stats we inherited + var/datum/gutlunch_inherited_stats/inherited_stats + +/mob/living/basic/mining/gutlunch/grub/Initialize(mapload) + . = ..() + transform = transform.Scale(0.6, 0.6) + AddComponent(\ + /datum/component/growth_and_differentiation,\ + growth_time = 3 MINUTES,\ + growth_probability = 100,\ + lower_growth_value = 0.5,\ + upper_growth_value = 1,\ + signals_to_kill_on = list(COMSIG_MOB_CLIENT_LOGIN),\ + optional_checks = CALLBACK(src, PROC_REF(ready_to_grow)),\ + optional_grow_behavior = CALLBACK(src, PROC_REF(determine_growth_path)),\ + ) + +/mob/living/basic/mining/gutlunch/grub/proc/ready_to_grow() + return (stat == CONSCIOUS) + +/mob/living/basic/mining/gutlunch/grub/proc/determine_growth_path() + var/final_type = prob(50) ? /mob/living/basic/mining/gutlunch/warrior : /mob/living/basic/mining/gutlunch/milk + var/mob/living/basic/mining/gutlunch/grown_mob = new final_type(get_turf(src)) + if(grown_mob.gender == MALE && inherited_stats) + grown_mob.roll_stats(inherited_stats.attack, inherited_stats.speed, inherited_stats.health) + qdel(src) + +#undef MAX_ATTACK_DIFFERENCE +#undef MAX_LOWER_ATTACK +#undef MINIMUM_POSSIBLE_SPEED +#undef MAX_POSSIBLE_HEALTH diff --git a/code/modules/mob/living/basic/lavaland/gutlunchers/gutlunchers_ai.dm b/code/modules/mob/living/basic/lavaland/gutlunchers/gutlunchers_ai.dm new file mode 100644 index 00000000000..8a6379febe0 --- /dev/null +++ b/code/modules/mob/living/basic/lavaland/gutlunchers/gutlunchers_ai.dm @@ -0,0 +1,119 @@ +/datum/ai_controller/basic_controller/gutlunch + ai_movement = /datum/ai_movement/basic_avoidance + idle_behavior = /datum/idle_behavior/idle_random_walk + +/datum/ai_controller/basic_controller/gutlunch/gutlunch_warrior + blackboard = list( + BB_TARGETING_STRATEGY = /datum/targeting_strategy/basic, + BB_PET_TARGETING_STRATEGY = /datum/targeting_strategy/basic/not_friends, + BB_BABIES_PARTNER_TYPES = list(/mob/living/basic/mining/gutlunch/milk), + BB_BABIES_CHILD_TYPES = list(/mob/living/basic/mining/gutlunch/grub), + BB_MAX_CHILDREN = 5, + ) + planning_subtrees = list( + /datum/ai_planning_subtree/target_retaliate/check_faction, + /datum/ai_planning_subtree/pet_planning, + /datum/ai_planning_subtree/make_babies, + /datum/ai_planning_subtree/basic_melee_attack_subtree, + /datum/ai_planning_subtree/befriend_ashwalkers, + ) + +///find ashwalkers and add them to the list of masters +/datum/ai_planning_subtree/befriend_ashwalkers + +/datum/ai_planning_subtree/befriend_ashwalkers/SelectBehaviors(datum/ai_controller/controller, seconds_per_tick) + controller.queue_behavior(/datum/ai_behavior/befriend_ashwalkers) + +/datum/ai_behavior/befriend_ashwalkers + action_cooldown = 5 SECONDS + behavior_flags = AI_BEHAVIOR_CAN_PLAN_DURING_EXECUTION + +/datum/ai_behavior/befriend_ashwalkers/perform(seconds_per_tick, datum/ai_controller/controller, target_key) + var/mob/living/living_pawn = controller.pawn + + for(var/mob/living/potential_friend in oview(9, living_pawn)) + if(!isashwalker(potential_friend)) + continue + if((living_pawn.faction.Find(REF(potential_friend)))) + continue + living_pawn.befriend(potential_friend) + to_chat(potential_friend, span_nicegreen("[living_pawn] looks at you with endearing eyes!")) + finish_action(controller, TRUE) + return + + finish_action(controller, FALSE) + return + + + +/datum/ai_controller/basic_controller/gutlunch/gutlunch_baby + blackboard = list( + BB_TARGETING_STRATEGY = /datum/targeting_strategy/basic, + BB_FIND_MOM_TYPES = list(/mob/living/basic/mining/gutlunch/milk), + ) + planning_subtrees = list( + /datum/ai_planning_subtree/target_retaliate, + /datum/ai_planning_subtree/flee_target, + /datum/ai_planning_subtree/look_for_adult, + ) + +/datum/ai_controller/basic_controller/gutlunch/gutlunch_milk + blackboard = list( + BB_TARGETING_STRATEGY = /datum/targeting_strategy/basic, + ) + planning_subtrees = list( + /datum/ai_planning_subtree/target_retaliate, + /datum/ai_planning_subtree/flee_target, + /datum/ai_planning_subtree/find_and_hunt_target/food_trough + ) + +///consume food! +/datum/ai_planning_subtree/find_and_hunt_target/food_trough + target_key = BB_TROUGH_TARGET + hunting_behavior = /datum/ai_behavior/hunt_target/unarmed_attack_target/food_trough + finding_behavior = /datum/ai_behavior/find_hunt_target/food_trough + hunt_targets = list(/obj/structure/ore_container/gutlunch_trough) + hunt_chance = 75 + hunt_range = 9 + + +/datum/ai_planning_subtree/find_and_hunt_target/food_trough/SelectBehaviors(datum/ai_controller/controller, seconds_per_tick) + if(!controller.blackboard[BB_CHECK_HUNGRY]) + return + return ..() + +/datum/ai_behavior/find_hunt_target/food_trough + +/datum/ai_behavior/find_hunt_target/food_trough/valid_dinner(mob/living/basic/source, obj/target, radius) + if(isnull(target)) + return FALSE + + if(isnull(locate(/obj/item/stack/ore) in target)) + return FALSE + + return can_see(source, target, radius) + +/datum/ai_behavior/hunt_target/unarmed_attack_target/food_trough + always_reset_target = TRUE + switch_combat_mode = TRUE + +/datum/pet_command/mine_walls + command_name = "Mine" + command_desc = "Command your pet to mine down walls." + speech_commands = list("mine", "smash") + +/datum/pet_command/mine_walls/try_activate_command(mob/living/commander) + var/mob/living/parent = weak_parent.resolve() + if(isnull(parent)) + return + //no walls for us to mine + var/target_in_vicinity = locate(/turf/closed/mineral) in oview(9, parent) + if(isnull(target_in_vicinity)) + return + return ..() + +/datum/pet_command/mine_walls/execute_action(datum/ai_controller/controller) + if(controller.blackboard_key_exists(BB_CURRENT_PET_TARGET)) + controller.queue_behavior(/datum/ai_behavior/mine_wall, BB_CURRENT_PET_TARGET) + return SUBTREE_RETURN_FINISH_PLANNING + controller.queue_behavior(/datum/ai_behavior/find_mineral_wall, BB_CURRENT_PET_TARGET) diff --git a/code/modules/mob/living/basic/lavaland/gutlunchers/gutlunchers_inherit_datum.dm b/code/modules/mob/living/basic/lavaland/gutlunchers/gutlunchers_inherit_datum.dm new file mode 100644 index 00000000000..7051dfc2f4b --- /dev/null +++ b/code/modules/mob/living/basic/lavaland/gutlunchers/gutlunchers_inherit_datum.dm @@ -0,0 +1,14 @@ +///stats we inherit from the parent +/datum/gutlunch_inherited_stats + ///attack we inherited + var/attack + ///speed we inherited + var/speed + ///health we inherited + var/health + +/datum/gutlunch_inherited_stats/New(mob/living/basic/parent) + . = ..() + attack = parent.melee_damage_lower + speed = parent.speed + health = parent.maxHealth diff --git a/code/modules/mob/living/basic/lavaland/mook/mook.dm b/code/modules/mob/living/basic/lavaland/mook/mook.dm index 93e28069321..6ecf54bc264 100644 --- a/code/modules/mob/living/basic/lavaland/mook/mook.dm +++ b/code/modules/mob/living/basic/lavaland/mook/mook.dm @@ -107,7 +107,7 @@ ore_target.forceMove(src) return COMPONENT_HOSTILE_NO_ATTACK - if(istype(target, /obj/structure/material_stand)) + if(istype(target, /obj/structure/ore_container/material_stand)) if(held_ore) held_ore.forceMove(target) return COMPONENT_HOSTILE_NO_ATTACK diff --git a/code/modules/mob/living/basic/lavaland/mook/mook_ai.dm b/code/modules/mob/living/basic/lavaland/mook/mook_ai.dm index b3bd9e1c4d5..cf79eb06aa6 100644 --- a/code/modules/mob/living/basic/lavaland/mook/mook_ai.dm +++ b/code/modules/mob/living/basic/lavaland/mook/mook_ai.dm @@ -71,7 +71,7 @@ GLOBAL_LIST_INIT(mook_commands, list( target_key = BB_MATERIAL_STAND_TARGET hunting_behavior = /datum/ai_behavior/hunt_target/unarmed_attack_target/material_stand finding_behavior = /datum/ai_behavior/find_hunt_target - hunt_targets = list(/obj/structure/material_stand) + hunt_targets = list(/obj/structure/ore_container/material_stand) hunt_range = 9 /datum/ai_planning_subtree/find_and_hunt_target/material_stand/SelectBehaviors(datum/ai_controller/controller, seconds_per_tick) @@ -83,6 +83,7 @@ GLOBAL_LIST_INIT(mook_commands, list( /datum/ai_behavior/hunt_target/unarmed_attack_target/material_stand required_distance = 0 always_reset_target = TRUE + switch_combat_mode = TRUE behavior_flags = AI_BEHAVIOR_REQUIRE_MOVEMENT ///try to face the counter when depositing ores diff --git a/code/modules/mob/living/basic/lavaland/mook/mook_village.dm b/code/modules/mob/living/basic/lavaland/mook/mook_village.dm index e3a091f6f0e..ca2719ed7c6 100644 --- a/code/modules/mob/living/basic/lavaland/mook/mook_village.dm +++ b/code/modules/mob/living/basic/lavaland/mook/mook_village.dm @@ -1,5 +1,5 @@ ///unique items that spawn at the mook village -/obj/structure/material_stand +/obj/structure/ore_container/material_stand name = "material stand" desc = "Is everyone free to use this thing?" icon = 'icons/mob/simple/jungle/mook.dmi' @@ -10,22 +10,8 @@ bound_width = 64 bound_height = 64 -/obj/structure/material_stand/attackby(obj/item/ore, mob/living/carbon/human/user, list/modifiers) - if(istype(ore, /obj/item/stack/ore)) - ore.forceMove(src) - return - return ..() - -/obj/structure/material_stand/Entered(atom/movable/mover) - . = ..() - update_appearance(UPDATE_OVERLAYS) - -/obj/structure/material_stand/Exited(atom/movable/mover) - . = ..() - update_appearance(UPDATE_OVERLAYS) - ///put ore icons on the counter! -/obj/structure/material_stand/update_overlays() +/obj/structure/ore_container/material_stand/update_overlays() . = ..() for(var/obj/item/stack/ore/ore_item in contents) var/image/ore_icon = image(icon = initial(ore_item.icon), icon_state = initial(ore_item.icon_state), layer = LOW_ITEM_LAYER) @@ -34,52 +20,6 @@ ore_icon.pixel_y = rand(2, 4) . += ore_icon -/obj/structure/material_stand/ui_interact(mob/user, datum/tgui/ui) - . = ..() - ui = SStgui.try_update_ui(user, src, ui) - if(!ui) - ui = new(user, src, "MaterialStand") - ui.open() - -/obj/structure/material_stand/ui_data(mob/user) - var/list/data = list() - data["ores"] = list() - for(var/obj/item/stack/ore/ore_item in contents) - data["ores"] += list(list( - "id" = REF(ore_item), - "name" = ore_item.name, - "amount" = ore_item.amount, - )) - return data - -/obj/structure/material_stand/ui_static_data(mob/user) - var/list/data = list() - data["ore_images"] = list() - for(var/obj/item/stack/ore_item as anything in subtypesof(/obj/item/stack/ore)) - data["ore_images"] += list(list( - "name" = initial(ore_item.name), - "icon" = icon2base64(getFlatIcon(image(icon = initial(ore_item.icon), icon_state = initial(ore_item.icon_state)), no_anim=TRUE)) - )) - return data - -/obj/structure/material_stand/ui_act(action, list/params, datum/tgui/ui, datum/ui_state/state) - . = ..() - - if(. || !isliving(usr)) - return TRUE - - var/mob/living/customer = usr - var/obj/item/stack_to_move - switch(action) - if("withdraw") - if(isnull(params["reference"])) - return TRUE - stack_to_move = locate(params["reference"]) in contents - if(isnull(stack_to_move)) - return TRUE - stack_to_move.forceMove(get_turf(customer)) - return TRUE - /obj/effect/landmark/mook_village name = "mook village landmark" icon_state = "x" diff --git a/code/modules/mob/living/basic/pets/dog/corgi.dm b/code/modules/mob/living/basic/pets/dog/corgi.dm index 9e120c4e8c0..61c18ab5354 100644 --- a/code/modules/mob/living/basic/pets/dog/corgi.dm +++ b/code/modules/mob/living/basic/pets/dog/corgi.dm @@ -26,6 +26,8 @@ var/is_slow = FALSE ///Item slots that are available for this corgi to equip stuff into var/list/strippable_inventory_slots = list() + ///can this mob breed? + var/can_breed = TRUE /mob/living/basic/pet/dog/corgi/Initialize(mapload) . = ..() @@ -34,6 +36,13 @@ AddElement(/datum/element/swabable, CELL_LINE_TABLE_CORGI, CELL_VIRUS_TABLE_GENERIC_MOB, 1, 5) RegisterSignal(src, COMSIG_MOB_TRIED_ACCESS, PROC_REF(on_tried_access)) RegisterSignals(src, list(COMSIG_BASICMOB_LOOK_ALIVE, COMSIG_BASICMOB_LOOK_DEAD), PROC_REF(on_appearance_change)) + if(!can_breed) + return + AddComponent(\ + /datum/component/breed,\ + can_breed_with = typecacheof(list(/mob/living/basic/pet/dog/corgi)),\ + baby_path = /mob/living/basic/pet/dog/corgi/puppy,\ + ) /mob/living/basic/pet/dog/corgi/Destroy() QDEL_NULL(inventory_head) @@ -532,9 +541,11 @@ icon_dead = "puppy_dead" density = FALSE pass_flags = PASSMOB + ai_controller = /datum/ai_controller/basic_controller/dog/puppy mob_size = MOB_SIZE_SMALL collar_icon_state = "puppy" strippable_inventory_slots = list(/datum/strippable_item/pet_collar, /datum/strippable_item/corgi_id) //puppies are too small to handle hats and back slot items + can_breed = FALSE //PUPPY IAN! SQUEEEEEEEEE~ /mob/living/basic/pet/dog/corgi/puppy/ian diff --git a/code/modules/mob/living/simple_animal/hostile/mining_mobs/gutlunch.dm b/code/modules/mob/living/simple_animal/hostile/mining_mobs/gutlunch.dm deleted file mode 100644 index 7a3451e4ef8..00000000000 --- a/code/modules/mob/living/simple_animal/hostile/mining_mobs/gutlunch.dm +++ /dev/null @@ -1,128 +0,0 @@ -//Gutlunches, passive mods that devour blood and gibs -/mob/living/simple_animal/hostile/asteroid/gutlunch - name = "gutlunch" - desc = "A scavenger that eats raw meat, often found alongside ash walkers. Produces a thick, nutritious milk." - icon = 'icons/mob/simple/lavaland/lavaland_monsters.dmi' - icon_state = "gutlunch" - icon_living = "gutlunch" - icon_dead = "gutlunch" - mob_biotypes = MOB_ORGANIC|MOB_BEAST - speak_emote = list("warbles", "quavers") - emote_hear = list("trills.") - emote_see = list("sniffs.", "burps.") - weather_immunities = list(TRAIT_LAVA_IMMUNE, TRAIT_ASHSTORM_IMMUNE) - faction = list(FACTION_MINING, FACTION_ASHWALKER) - density = FALSE - speak_chance = 1 - turns_per_move = 8 - obj_damage = 0 - environment_smash = ENVIRONMENT_SMASH_NONE - move_to_delay = 15 - response_help_continuous = "pets" - response_help_simple = "pet" - response_disarm_continuous = "gently pushes aside" - response_disarm_simple = "gently push aside" - response_harm_continuous = "squishes" - response_harm_simple = "squish" - friendly_verb_continuous = "pinches" - friendly_verb_simple = "pinch" - combat_mode = FALSE - gold_core_spawnable = FRIENDLY_SPAWN - stat_attack = HARD_CRIT - gender = NEUTER - stop_automated_movement = FALSE - stop_automated_movement_when_pulled = TRUE - stat_exclusive = TRUE - robust_searching = TRUE - search_objects = 3 //Ancient simplemob AI shitcode. This makes them ignore all other mobs. - del_on_death = TRUE - loot = list(/obj/effect/decal/cleanable/blood/gibs) - death_message = "is pulped into bugmash." - - animal_species = /mob/living/simple_animal/hostile/asteroid/gutlunch - childtype = list(/mob/living/simple_animal/hostile/asteroid/gutlunch/grublunch = 100) - - wanted_objects = list(/obj/effect/decal/cleanable/xenoblood/xgibs, /obj/effect/decal/cleanable/blood/gibs/, /obj/item/organ) - -/mob/living/simple_animal/hostile/asteroid/gutlunch/Initialize(mapload) - . = ..() - if(wanted_objects.len) - AddComponent(/datum/component/udder, /obj/item/udder/gutlunch, CALLBACK(src, PROC_REF(regenerate_icons)), CALLBACK(src, PROC_REF(regenerate_icons))) - ADD_TRAIT(src, TRAIT_VENTCRAWLER_ALWAYS, INNATE_TRAIT) - -/mob/living/simple_animal/hostile/asteroid/gutlunch/CanAttack(atom/the_target) // Gutlunch-specific version of CanAttack to handle stupid stat_exclusive = true crap so we don't have to do it for literally every single simple_animal/hostile except the two that spawn in lavaland - if(!the_target || !isturf(the_target.loc)) // bail out on invalids - return FALSE - - if(see_invisible < the_target.invisibility)//Target's invisible to us, forget it - return FALSE - - if(isliving(the_target)) - var/mob/living/L = the_target - - if(faction_check_atom(L) && !attack_same) - return FALSE - if(L.stat > stat_attack || L.stat != stat_attack && stat_exclusive) - return FALSE - - return TRUE - - if(isobj(the_target) && is_type_in_typecache(the_target, wanted_objects)) - return TRUE - - return FALSE - -/mob/living/simple_animal/hostile/asteroid/gutlunch/regenerate_icons(new_udder_volume, max_udder_volume) - cut_overlays() - var/static/gutlunch_full_overlay - if(isnull(gutlunch_full_overlay)) - gutlunch_full_overlay = iconstate2appearance(icon, "gl_full") - if(new_udder_volume == max_udder_volume) - add_overlay(gutlunch_full_overlay) - ..() - -//Male gutlunch. They're smaller and more colorful! -/mob/living/simple_animal/hostile/asteroid/gutlunch/gubbuck - name = "gubbuck" - gender = MALE - -/mob/living/simple_animal/hostile/asteroid/gutlunch/gubbuck/Initialize(mapload) - . = ..() - add_atom_colour(pick("#E39FBB", "#D97D64", "#CF8C4A"), FIXED_COLOUR_PRIORITY) - update_transform(0.85) - -//Lady gutlunch. They make the babby. -/mob/living/simple_animal/hostile/asteroid/gutlunch/guthen - name = "guthen" - gender = FEMALE - -/mob/living/simple_animal/hostile/asteroid/gutlunch/grublunch - name = "grublunch" - wanted_objects = list() //They don't eat. - gold_core_spawnable = NO_SPAWN - var/growth = 0 - -//Baby gutlunch -/mob/living/simple_animal/hostile/asteroid/gutlunch/grublunch/Initialize(mapload) - . = ..() - add_atom_colour("#9E9E9E", FIXED_COLOUR_PRIORITY) //Somewhat hidden - update_transform(0.45) - -/mob/living/simple_animal/hostile/asteroid/gutlunch/grublunch/Life(seconds_per_tick = SSMOBS_DT, times_fired) - ..() - growth++ - if(growth > 50) //originally used a timer for this but it was more of a problem than it was worth. - growUp() - -/mob/living/simple_animal/hostile/asteroid/gutlunch/grublunch/proc/growUp() - var/mob/living/L - if(prob(45)) - L = new /mob/living/simple_animal/hostile/asteroid/gutlunch/gubbuck(loc) - else - L = new /mob/living/simple_animal/hostile/asteroid/gutlunch/guthen(loc) - mind?.transfer_to(L) - L.faction = faction - L.setDir(dir) - L.Stun(20, ignore_canstun = TRUE) - visible_message(span_notice("[src] grows up into [L].")) - qdel(src) diff --git a/code/modules/unit_tests/simple_animal_freeze.dm b/code/modules/unit_tests/simple_animal_freeze.dm index a00d32aaade..dcba0cd69db 100644 --- a/code/modules/unit_tests/simple_animal_freeze.dm +++ b/code/modules/unit_tests/simple_animal_freeze.dm @@ -51,10 +51,6 @@ /mob/living/simple_animal/hostile/asteroid/elite/legionnaire, /mob/living/simple_animal/hostile/asteroid/elite/legionnairehead, /mob/living/simple_animal/hostile/asteroid/elite/pandora, - /mob/living/simple_animal/hostile/asteroid/gutlunch, - /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/polarbear, /mob/living/simple_animal/hostile/asteroid/polarbear/lesser, /mob/living/simple_animal/hostile/asteroid/wolf, diff --git a/icons/mob/simple/lavaland/lavaland_monsters.dmi b/icons/mob/simple/lavaland/lavaland_monsters.dmi index f68e3db4a6c..90fdbaac0e6 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/obj/structures.dmi b/icons/obj/structures.dmi index a41aa161d0f..8707b394c2e 100644 Binary files a/icons/obj/structures.dmi and b/icons/obj/structures.dmi differ diff --git a/modular_skyrat/master_files/code/modules/mob/living/simple_animal/friendly/dogs.dm b/modular_skyrat/master_files/code/modules/mob/living/simple_animal/friendly/dogs.dm index 7856a35690d..c2306b8b2a8 100644 --- a/modular_skyrat/master_files/code/modules/mob/living/simple_animal/friendly/dogs.dm +++ b/modular_skyrat/master_files/code/modules/mob/living/simple_animal/friendly/dogs.dm @@ -14,10 +14,22 @@ gender = MALE can_be_held = FALSE gold_core_spawnable = FRIENDLY_SPAWN + ///can this mob breed? + var/can_breed = TRUE /// List of possible dialogue options. This is both used by the AI and as an override when a sentient Markus speaks. var/static/list/markus_speak = list("Borf!", "Boof!", "Bork!", "Bowwow!", "Burg?") +/mob/living/basic/pet/dog/markus/Initialize(mapload) + . = ..() + if(!can_breed) + return + AddComponent(\ + /datum/component/breed,\ + can_breed_with = typecacheof(list(/mob/living/basic/pet/dog/corgi)),\ + baby_path = /mob/living/basic/pet/dog/corgi/puppy,\ + ) // no mixed breed puppies sadly + /mob/living/basic/pet/dog/markus/treat_message(message) if(client) message = pick(markus_speak) // markus only talks business @@ -71,6 +83,7 @@ ai_controller = /datum/ai_controller/basic_controller/dog/borgi unsuitable_atmos_damage = 0 minimum_survivable_temperature = 0 + can_breed = FALSE // These lights enable when E-N is emagged light_system = MOVABLE_LIGHT_DIRECTIONAL diff --git a/tgstation.dme b/tgstation.dme index a38c4ed34b8..eb3f601fe31 100644 --- a/tgstation.dme +++ b/tgstation.dme @@ -1088,6 +1088,7 @@ #include "code\datums\components\bloodysoles.dm" #include "code\datums\components\boomerang.dm" #include "code\datums\components\boss_music.dm" +#include "code\datums\components\breeding.dm" #include "code\datums\components\bullet_intercepting.dm" #include "code\datums\components\bumpattack.dm" #include "code\datums\components\burning.dm" @@ -1497,6 +1498,7 @@ #include "code\datums\elements\noticable_organ.dm" #include "code\datums\elements\obj_regen.dm" #include "code\datums\elements\openspace_item_click_handler.dm" +#include "code\datums\elements\ore_collecting.dm" #include "code\datums\elements\organ_set_bonus.dm" #include "code\datums\elements\pet_bonus.dm" #include "code\datums\elements\plant_backfire.dm" @@ -2631,6 +2633,7 @@ #include "code\game\objects\structures\morgue.dm" #include "code\game\objects\structures\mystery_box.dm" #include "code\game\objects\structures\noticeboard.dm" +#include "code\game\objects\structures\ore_containers.dm" #include "code\game\objects\structures\petrified_statue.dm" #include "code\game\objects\structures\pinatas.dm" #include "code\game\objects\structures\plasticflaps.dm" @@ -4622,6 +4625,10 @@ #include "code\modules\mob\living\basic\lavaland\goliath\goliath_ai.dm" #include "code\modules\mob\living\basic\lavaland\goliath\goliath_trophy.dm" #include "code\modules\mob\living\basic\lavaland\goliath\tentacle.dm" +#include "code\modules\mob\living\basic\lavaland\gutlunchers\gutluncher_foodtrough.dm" +#include "code\modules\mob\living\basic\lavaland\gutlunchers\gutlunchers.dm" +#include "code\modules\mob\living\basic\lavaland\gutlunchers\gutlunchers_ai.dm" +#include "code\modules\mob\living\basic\lavaland\gutlunchers\gutlunchers_inherit_datum.dm" #include "code\modules\mob\living\basic\lavaland\hivelord\hivelord.dm" #include "code\modules\mob\living\basic\lavaland\hivelord\hivelord_ai.dm" #include "code\modules\mob\living\basic\lavaland\hivelord\spawn_hivelord_brood.dm" @@ -4940,7 +4947,6 @@ #include "code\modules\mob\living\simple_animal\hostile\megafauna\legion.dm" #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\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" diff --git a/tgui/packages/tgui/interfaces/MaterialStand.tsx b/tgui/packages/tgui/interfaces/OreContainer.tsx similarity index 96% rename from tgui/packages/tgui/interfaces/MaterialStand.tsx rename to tgui/packages/tgui/interfaces/OreContainer.tsx index b6e87889cd2..1ec75332b75 100644 --- a/tgui/packages/tgui/interfaces/MaterialStand.tsx +++ b/tgui/packages/tgui/interfaces/OreContainer.tsx @@ -19,7 +19,7 @@ type Data = { ore_images: Ore_images[]; }; -export const MaterialStand = (props, context) => { +export const OreContainer = (props, context) => { const { act, data } = useBackend(context); const { ores = [] } = data; const [searchItem, setSearchItem] = useLocalState(context, 'searchItem', ''); @@ -27,7 +27,7 @@ export const MaterialStand = (props, context) => { const ores_filtered = searchItem.length > 0 ? ores.filter((ore) => search(ore)) : ores; return ( - + diff --git a/tools/UpdatePaths/Scripts/79508_gutlunchers.txt b/tools/UpdatePaths/Scripts/79508_gutlunchers.txt new file mode 100644 index 00000000000..f6a07058fb8 --- /dev/null +++ b/tools/UpdatePaths/Scripts/79508_gutlunchers.txt @@ -0,0 +1,5 @@ +/mob/living/simple_animal/hostile/asteroid/gutlunch : /mob/living/basic/mining/gutlunch{@OLD} +/mob/living/simple_animal/hostile/asteroid/gutlunch/grublunch : /mob/living/basic/mining/gutlunch/grub{@OLD} +/mob/living/simple_animal/hostile/asteroid/gutlunch/gubbuck : /mob/living/basic/mining/gutlunch/milk{@OLD} +/mob/living/simple_animal/hostile/asteroid/gutlunch/guthen : /mob/living/basic/mining/gutlunch/warrior{@OLD} + diff --git a/tools/UpdatePaths/Scripts/79508_ore_container.txt b/tools/UpdatePaths/Scripts/79508_ore_container.txt new file mode 100644 index 00000000000..d3851e0628c --- /dev/null +++ b/tools/UpdatePaths/Scripts/79508_ore_container.txt @@ -0,0 +1 @@ +/obj/structure/material_stand : /obj/structure/ore_container/material_stand{@OLD} \ No newline at end of file