From 45438b4f04f4d4508975381f913fce58304664cd Mon Sep 17 00:00:00 2001 From: Profakos Date: Tue, 2 Jan 2024 19:37:20 +0100 Subject: [PATCH] Removes the attack_slime proc, and other slime refactors (#80487) ## About The Pull Request - **Removed attack_slime**. Most of the attack_slime content has been moved to a proc that signs up for COMSIG_LIVING_UNARMED_ATTACK. Its ugly, but will make converting slimes to a basic mob easier. They now use attack_animal for now, which might cause some unexpected interactions. Hopefully when they are converted to basic mobs, these can be cleared up properly. - This caused some issues with cyborgs, who used to get only half damage dealt to them. As refactoring this would have been too much of a difficult task without much real gain, after much pondering, I have decided that since slimes can always flash cyborgs with each of their strikes, maybe cyborgs should only fear slimes that have electric charges in them. In addition, slimes electric charges decrease now after they successfully zap an cyborg, making them more consistent with the zaps that affect carbons. AIs are still fully immune to slimes. - The slime.dm and slime.life files were extremely bloated, and unorganized. I have created two new files, defense.dm and ai.dm. I have moved the various attack_by/attack_hand/etc procs to defense.dm. Ai.dm now contains every single proc the slime's "AI" uses; this should help getting a clearer picture of the current functionality, which should aid with basic mob conversion and decision tree creation. The remaining files have been slightly organized, with overrides in front, and new procs at the back. - Created a proc for swapping out Adult and Baby states of a slime. Previously, attack_slime was in many cases ignoring fields like melee_damage_lower and melee_damage_upper, replacing it with magic numbers based on the slime's lifestate. Now these values are hard set by these procs. This has caused slimes to be more consistent, though baby slimes might do a bit less damage on the low end. I am tempted to turn these in datums in the future, or as part of this PR. - Removed baby slime's chance to accidentally attack a window/grille by bumping into it, they had 0 object damage anyways, unlike adult slimes, so there was no reason not to early return. - The proc of `handle_feeding` assumed adjustBruteLoss and adjustToxLoss return positive values when damage has been done, when in reality, it returns the total health change along with its direction. This meant slimes would fell off simple or basic mobs after a single bite. This has been fixed. - Also updated the warning before the slime type defines, as they were out of date. - I have removed the bespoke spacewalk override for slimes, which should allow them to drift, should gravity go out. - The nutrition stats are assigned only once, when the slime grows up, instead of compared to being an adult every life tick ## Why It's Good For The Game Less duplicated code. This refactor should help in the basic mob conversion process. Cyborgs have an easier time wrangling slimes, who could previously kill them in three hits, if charged. They are mostly encased in metal, they should feel fine when not hit with electric attacks. Lets slimes feast on delicious corgis. --- code/__DEFINES/research/slimes.dm | 12 +- code/_onclick/other_mobs.dm | 25 - code/game/atom/atom_defense.dm | 2 +- .../objects/items/devices/chameleonproj.dm | 3 - .../items/devices/scanners/slime_scanner.dm | 8 +- code/game/objects/obj_defense.dm | 6 - code/game/objects/structures/ladders.dm | 11 - code/game/objects/structures/window.dm | 2 +- code/modules/awaymissions/signpost.dm | 3 - .../modules/awaymissions/super_secret_room.dm | 2 - .../ruins/spaceruin_code/hilbertshotel.dm | 3 - .../modules/mob/living/basic/basic_defense.dm | 7 - .../mob/living/carbon/alien/alien_defense.dm | 9 - .../mob/living/carbon/carbon_defense.dm | 22 - .../mob/living/carbon/human/human_defense.dm | 24 - code/modules/mob/living/living_defense.dm | 24 - .../mob/living/silicon/ai/ai_defense.dm | 3 - .../mob/living/silicon/robot/robot_defense.dm | 18 - .../living/simple_animal/animal_defense.dm | 7 - .../hostile/mining_mobs/curse_blob.dm | 2 - .../mob/living/simple_animal/slime/ai.dm | 494 +++++++++++++++++ .../mob/living/simple_animal/slime/death.dm | 25 +- .../mob/living/simple_animal/slime/defense.dm | 131 +++++ .../mob/living/simple_animal/slime/life.dm | 479 +---------------- .../mob/living/simple_animal/slime/powers.dm | 45 +- .../mob/living/simple_animal/slime/slime.dm | 499 ++++++++---------- .../xenobiology/crossbreeding/burning.dm | 2 +- .../research/xenobiology/xenobiology.dm | 14 +- tgstation.dme | 2 + 29 files changed, 898 insertions(+), 986 deletions(-) create mode 100644 code/modules/mob/living/simple_animal/slime/ai.dm create mode 100644 code/modules/mob/living/simple_animal/slime/defense.dm diff --git a/code/__DEFINES/research/slimes.dm b/code/__DEFINES/research/slimes.dm index 64a85afc217..59410564928 100644 --- a/code/__DEFINES/research/slimes.dm +++ b/code/__DEFINES/research/slimes.dm @@ -1,7 +1,13 @@ +#define SLIME_LIFE_STAGE_BABY "baby" +#define SLIME_LIFE_STAGE_ADULT "adult" + +#define SLIME_MIN_POWER 0 +#define SLIME_MEDIUM_POWER 5 +#define SLIME_MAX_POWER 10 + // Just slimin' here. -// Warning: These defines are used for slime cores and their icon states, so if you -// touch these names, remember to update icons/mob/simple/slimes.dmi and the respective -// slime core paths too! +// Warning: These defines are used for slime icon states, so if you +// touch these names, remember to update icons/mob/simple/slimes.dmi! #define SLIME_TYPE_ADAMANTINE "adamantine" #define SLIME_TYPE_BLACK "black" diff --git a/code/_onclick/other_mobs.dm b/code/_onclick/other_mobs.dm index f398a5c0343..03d6606a5f0 100644 --- a/code/_onclick/other_mobs.dm +++ b/code/_onclick/other_mobs.dm @@ -237,31 +237,6 @@ /atom/proc/attack_larva_secondary(mob/user, list/modifiers) return SECONDARY_ATTACK_CALL_NORMAL - -/* - Slimes - Nothing happening here -*/ -/mob/living/simple_animal/slime/resolve_unarmed_attack(atom/attack_target, proximity_flag, list/modifiers) - if(isturf(attack_target)) - return ..() - attack_target.attack_slime(src, modifiers) - -/mob/living/simple_animal/slime/resolve_right_click_attack(atom/target, list/modifiers) - if(isturf(target)) - return ..() - return target.attack_slime_secondary(src, modifiers) - -/atom/proc/attack_slime(mob/user, list/modifiers) - return - -/** - * Called when a slime mob right clicks an atom (that is not a turf). - * Returns a SECONDARY_ATTACK_* value. - */ -/atom/proc/attack_slime_secondary(mob/user, list/modifiers) - return SECONDARY_ATTACK_CALL_NORMAL - /* Drones */ diff --git a/code/game/atom/atom_defense.dm b/code/game/atom/atom_defense.dm index 0541915a38c..e18b3ade2f1 100644 --- a/code/game/atom/atom_defense.dm +++ b/code/game/atom/atom_defense.dm @@ -121,7 +121,7 @@ /atom/proc/hulk_damage() return 150 //the damage hulks do on punches to this atom, is affected by melee armor -/atom/proc/attack_generic(mob/user, damage_amount = 0, damage_type = BRUTE, damage_flag = 0, sound_effect = 1, armor_penetration = 0) //used by attack_alien, attack_animal, and attack_slime +/atom/proc/attack_generic(mob/user, damage_amount = 0, damage_type = BRUTE, damage_flag = 0, sound_effect = 1, armor_penetration = 0) //used by attack_alien, attack_animal if(!uses_integrity) CRASH("unimplemented /atom/proc/attack_generic()!") user.do_attack_animation(src) diff --git a/code/game/objects/items/devices/chameleonproj.dm b/code/game/objects/items/devices/chameleonproj.dm index 75623ed57e8..e5507473fae 100644 --- a/code/game/objects/items/devices/chameleonproj.dm +++ b/code/game/objects/items/devices/chameleonproj.dm @@ -140,9 +140,6 @@ /obj/effect/dummy/chameleon/attack_animal(mob/user, list/modifiers) master.disrupt() -/obj/effect/dummy/chameleon/attack_slime(mob/user, list/modifiers) - master.disrupt() - /obj/effect/dummy/chameleon/attack_alien(mob/user, list/modifiers) master.disrupt() diff --git a/code/game/objects/items/devices/scanners/slime_scanner.dm b/code/game/objects/items/devices/scanners/slime_scanner.dm index 133dfee0b87..05b11b3be3a 100644 --- a/code/game/objects/items/devices/scanners/slime_scanner.dm +++ b/code/game/objects/items/devices/scanners/slime_scanner.dm @@ -27,12 +27,12 @@ /proc/slime_scan(mob/living/simple_animal/slime/scanned_slime, mob/living/user) var/to_render = "Slime scan results:\ - \n[span_notice("[scanned_slime.slime_type.colour] [scanned_slime.is_adult ? "adult" : "baby"] slime")]\ - \nNutrition: [scanned_slime.nutrition]/[scanned_slime.get_max_nutrition()]" + \n[span_notice("[scanned_slime.slime_type.colour] [scanned_slime.life_stage] slime")]\ + \nNutrition: [scanned_slime.nutrition]/[scanned_slime.max_nutrition]" - if (scanned_slime.nutrition < scanned_slime.get_starve_nutrition()) + if (scanned_slime.nutrition < scanned_slime.starve_nutrition) to_render += "\n[span_warning("Warning: slime is starving!")]" - else if (scanned_slime.nutrition < scanned_slime.get_hunger_nutrition()) + else if (scanned_slime.nutrition < scanned_slime.hunger_nutrition) to_render += "\n[span_warning("Warning: slime is hungry")]" to_render += "\nElectric charge strength: [scanned_slime.powerlevel]\nHealth: [round(scanned_slime.health/scanned_slime.maxHealth,0.01)*100]%" diff --git a/code/game/objects/obj_defense.dm b/code/game/objects/obj_defense.dm index 8b608427e21..a2989ad1444 100644 --- a/code/game/objects/obj_defense.dm +++ b/code/game/objects/obj_defense.dm @@ -98,12 +98,6 @@ var/amt = max(0, ((force - (move_resist * MOVE_FORCE_CRUSH_RATIO)) / (move_resist * MOVE_FORCE_CRUSH_RATIO)) * 10) take_damage(amt, BRUTE) -/obj/attack_slime(mob/living/simple_animal/slime/user, list/modifiers) - if(!user.is_adult) - return - if(attack_generic(user, rand(10, 15), BRUTE, MELEE, 1)) - log_combat(user, src, "attacked") - /obj/singularity_act() SSexplosions.high_mov_atom += src if(src && !QDELETED(src)) diff --git a/code/game/objects/structures/ladders.dm b/code/game/objects/structures/ladders.dm index 9b7b5590018..ef6ea9d433e 100644 --- a/code/game/objects/structures/ladders.dm +++ b/code/game/objects/structures/ladders.dm @@ -224,17 +224,6 @@ use(user, going_up = FALSE) return SECONDARY_ATTACK_CANCEL_ATTACK_CHAIN -/obj/structure/ladder/attack_slime(mob/user, list/modifiers) - use(user) - return TRUE - -/obj/structure/ladder/attack_slime_secondary(mob/user, list/modifiers) - . = ..() - if(. == SECONDARY_ATTACK_CANCEL_ATTACK_CHAIN) - return - use(user, going_up = FALSE) - return SECONDARY_ATTACK_CANCEL_ATTACK_CHAIN - /obj/structure/ladder/attackby(obj/item/item, mob/user, params) use(user) return TRUE diff --git a/code/game/objects/structures/window.dm b/code/game/objects/structures/window.dm index 1b2e37cbc95..ad969543416 100644 --- a/code/game/objects/structures/window.dm +++ b/code/game/objects/structures/window.dm @@ -185,7 +185,7 @@ /obj/structure/window/attack_paw(mob/user, list/modifiers) return attack_hand(user, modifiers) -/obj/structure/window/attack_generic(mob/user, damage_amount = 0, damage_type = BRUTE, damage_flag = 0, sound_effect = 1) //used by attack_alien, attack_animal, and attack_slime +/obj/structure/window/attack_generic(mob/user, damage_amount = 0, damage_type = BRUTE, damage_flag = 0, sound_effect = 1) //used by attack_alien, attack_animal if(!can_be_reached(user)) return return ..() diff --git a/code/modules/awaymissions/signpost.dm b/code/modules/awaymissions/signpost.dm index c46b0b9aa3c..864b4fa03f8 100644 --- a/code/modules/awaymissions/signpost.dm +++ b/code/modules/awaymissions/signpost.dm @@ -46,9 +46,6 @@ if (Adjacent(user)) return interact(user) -/obj/structure/signpost/attack_slime(mob/user, list/modifiers) - return interact(user) - /obj/structure/signpost/attack_animal(mob/user, list/modifiers) return interact(user) diff --git a/code/modules/awaymissions/super_secret_room.dm b/code/modules/awaymissions/super_secret_room.dm index 556a9fd63ad..d7ecda85018 100644 --- a/code/modules/awaymissions/super_secret_room.dm +++ b/code/modules/awaymissions/super_secret_room.dm @@ -105,8 +105,6 @@ /obj/structure/speaking_tile/attack_ai(mob/user) return interact(user) -/obj/structure/speaking_tile/attack_slime(mob/user, list/modifiers) - return interact(user) /obj/structure/speaking_tile/attack_animal(mob/user, list/modifiers) return interact(user) diff --git a/code/modules/mapfluff/ruins/spaceruin_code/hilbertshotel.dm b/code/modules/mapfluff/ruins/spaceruin_code/hilbertshotel.dm index 05daaff5864..cf5ad9d324c 100644 --- a/code/modules/mapfluff/ruins/spaceruin_code/hilbertshotel.dm +++ b/code/modules/mapfluff/ruins/spaceruin_code/hilbertshotel.dm @@ -335,9 +335,6 @@ GLOBAL_VAR_INIT(hhMysteryRoomNumber, rand(1, 999999)) /turf/closed/indestructible/hoteldoor/attack_larva(mob/user, list/modifiers) promptExit(user) -/turf/closed/indestructible/hoteldoor/attack_slime(mob/user, list/modifiers) - promptExit(user) - /turf/closed/indestructible/hoteldoor/attack_robot(mob/user) if(get_dist(get_turf(src), get_turf(user)) <= 1) promptExit(user) diff --git a/code/modules/mob/living/basic/basic_defense.dm b/code/modules/mob/living/basic/basic_defense.dm index 03f684b7d87..69370ed49be 100644 --- a/code/modules/mob/living/basic/basic_defense.dm +++ b/code/modules/mob/living/basic/basic_defense.dm @@ -112,13 +112,6 @@ if(damage_done > 0) attacking_larva.amount_grown = min(attacking_larva.amount_grown + damage_done, attacking_larva.max_grown) -/mob/living/basic/attack_slime(mob/living/simple_animal/slime/M, list/modifiers) - if(..()) //successful slime attack - var/damage = rand(15, 25) - if(M.is_adult) - damage = rand(20, 35) - return apply_damage(damage, M.melee_damage_type) - /mob/living/basic/attack_drone(mob/living/basic/drone/attacking_drone) if(attacking_drone.combat_mode) //No kicking dogs even as a rogue drone. Use a weapon. return diff --git a/code/modules/mob/living/carbon/alien/alien_defense.dm b/code/modules/mob/living/carbon/alien/alien_defense.dm index 4ea7e98b05f..9388dcea560 100644 --- a/code/modules/mob/living/carbon/alien/alien_defense.dm +++ b/code/modules/mob/living/carbon/alien/alien_defense.dm @@ -67,15 +67,6 @@ In all, this is a lot like the monkey code. /N var/obj/item/bodypart/affecting = get_bodypart(get_random_valid_zone(user.zone_selected)) apply_damage(rand(1, 3), BRUTE, affecting) -/mob/living/carbon/alien/attack_slime(mob/living/simple_animal/slime/M, list/modifiers) - if(..()) //successful slime attack - var/damage = rand(5, 35) - if(M.is_adult) - damage = rand(10, 40) - adjustBruteLoss(damage) - log_combat(M, src, "attacked") - updatehealth() - /mob/living/carbon/alien/ex_act(severity, target, origin) . = ..() if(!. || QDELETED(src)) diff --git a/code/modules/mob/living/carbon/carbon_defense.dm b/code/modules/mob/living/carbon/carbon_defense.dm index cb565324c67..a2faacb9a94 100644 --- a/code/modules/mob/living/carbon/carbon_defense.dm +++ b/code/modules/mob/living/carbon/carbon_defense.dm @@ -202,28 +202,6 @@ ForceContractDisease(D) return TRUE - -/mob/living/carbon/attack_slime(mob/living/simple_animal/slime/M, list/modifiers) - if(..()) //successful slime attack - if(M.powerlevel > 0) - var/stunprob = M.powerlevel * 7 + 10 // 17 at level 1, 80 at level 10 - if(prob(stunprob)) - M.powerlevel -= 3 - if(M.powerlevel < 0) - M.powerlevel = 0 - - visible_message(span_danger("The [M.name] shocks [src]!"), \ - span_userdanger("The [M.name] shocks you!")) - - do_sparks(5, TRUE, src) - var/power = M.powerlevel + rand(0,3) - Paralyze(power * 2 SECONDS) - set_stutter_if_lower(power * 2 SECONDS) - if (prob(stunprob) && M.powerlevel >= 8) - adjustFireLoss(M.powerlevel * rand(6,10)) - updatehealth() - return 1 - /** * Really weird proc that attempts to dismebmer the passed zone if it is at max damage * Unless the attacker is an NPC, in which case it disregards the zone and picks a random one diff --git a/code/modules/mob/living/carbon/human/human_defense.dm b/code/modules/mob/living/carbon/human/human_defense.dm index bb128182faa..094a812a0ab 100644 --- a/code/modules/mob/living/carbon/human/human_defense.dm +++ b/code/modules/mob/living/carbon/human/human_defense.dm @@ -254,30 +254,6 @@ var/armor_block = run_armor_check(affecting, MELEE) apply_damage(damage, BRUTE, affecting, armor_block) -/mob/living/carbon/human/attack_slime(mob/living/simple_animal/slime/M, list/modifiers) - . = ..() - if(!.) // slime attack failed - return - var/damage = rand(M.melee_damage_lower, M.melee_damage_upper) - if(!damage) - return - var/wound_mod = -45 // 25^1.4=90, 90-45=45 - if(M.is_adult) - damage += rand(5, 10) - wound_mod = -90 // 35^1.4=145, 145-90=55 - - if(check_block(M, damage, "the [M.name]")) - return FALSE - - var/dam_zone = dismembering_strike(M, pick(BODY_ZONE_HEAD, BODY_ZONE_CHEST, BODY_ZONE_L_ARM, BODY_ZONE_R_ARM, BODY_ZONE_L_LEG, BODY_ZONE_R_LEG)) - if(!dam_zone) //Dismemberment successful - return TRUE - - var/obj/item/bodypart/affecting = get_bodypart(get_random_valid_zone(dam_zone)) - var/armor_block = run_armor_check(affecting, MELEE) - apply_damage(damage, BRUTE, affecting, armor_block, wound_bonus=wound_mod) - - /mob/living/carbon/human/ex_act(severity, target, origin) if(HAS_TRAIT(src, TRAIT_BOMBIMMUNE)) return FALSE diff --git a/code/modules/mob/living/living_defense.dm b/code/modules/mob/living/living_defense.dm index 3138f4451fd..2cc5c14b172 100644 --- a/code/modules/mob/living/living_defense.dm +++ b/code/modules/mob/living/living_defense.dm @@ -284,30 +284,6 @@ user.set_pull_offsets(src, grab_state) return TRUE - -/mob/living/attack_slime(mob/living/simple_animal/slime/attacking_slime, list/modifiers) - if(attacking_slime.buckled) - if(attacking_slime in buckled_mobs) - attacking_slime.stop_feeding() - return // can't attack while eating! - - if(HAS_TRAIT(attacking_slime, TRAIT_PACIFISM)) - to_chat(attacking_slime, span_warning("You don't want to hurt anyone!")) - return FALSE - - if(check_block(src, attacking_slime.melee_damage_upper, "[attacking_slime]'s glomp", MELEE_ATTACK, attacking_slime.armour_penetration, attacking_slime.melee_damage_type)) - return FALSE - - if (stat != DEAD) - log_combat(attacking_slime, src, "attacked") - attacking_slime.do_attack_animation(src) - visible_message(span_danger("\The [attacking_slime.name] glomps [src]!"), \ - span_userdanger("\The [attacking_slime.name] glomps you!"), span_hear("You hear a sickening sound of flesh hitting flesh!"), COMBAT_MESSAGE_RANGE, attacking_slime) - to_chat(attacking_slime, span_danger("You glomp [src]!")) - return TRUE - - return FALSE - /mob/living/attack_animal(mob/living/simple_animal/user, list/modifiers) . = ..() if(.) diff --git a/code/modules/mob/living/silicon/ai/ai_defense.dm b/code/modules/mob/living/silicon/ai/ai_defense.dm index da8411f7cb3..1148b639d01 100644 --- a/code/modules/mob/living/silicon/ai/ai_defense.dm +++ b/code/modules/mob/living/silicon/ai/ai_defense.dm @@ -10,9 +10,6 @@ return ..() -/mob/living/silicon/ai/attack_slime(mob/living/simple_animal/slime/user, list/modifiers) - return //immune to slimes - /mob/living/silicon/ai/blob_act(obj/structure/blob/B) if (stat != DEAD) adjustBruteLoss(60) diff --git a/code/modules/mob/living/silicon/robot/robot_defense.dm b/code/modules/mob/living/silicon/robot/robot_defense.dm index 074f9d0049a..44e8583b86f 100644 --- a/code/modules/mob/living/silicon/robot/robot_defense.dm +++ b/code/modules/mob/living/silicon/robot/robot_defense.dm @@ -207,24 +207,6 @@ GLOBAL_LIST_INIT(blacklisted_borg_hats, typecacheof(list( //Hats that don't real ..() return -/mob/living/silicon/robot/attack_slime(mob/living/simple_animal/slime/M, list/modifiers) - if(..()) //successful slime shock - flash_act() - var/stunprob = M.powerlevel * 7 + 10 - if(prob(stunprob) && M.powerlevel >= 8) - adjustBruteLoss(M.powerlevel * rand(6,10)) - - var/damage = rand(1, 3) - - if(M.is_adult) - damage = rand(20, 40) - else - damage = rand(5, 35) - damage = round(damage / 2) // borgs receive half damage - adjustBruteLoss(damage) - - return - /mob/living/silicon/robot/attack_hand(mob/living/carbon/human/user, list/modifiers) add_fingerprint(user) if(!opened) diff --git a/code/modules/mob/living/simple_animal/animal_defense.dm b/code/modules/mob/living/simple_animal/animal_defense.dm index 01d144aea88..afe4c00db4d 100644 --- a/code/modules/mob/living/simple_animal/animal_defense.dm +++ b/code/modules/mob/living/simple_animal/animal_defense.dm @@ -91,13 +91,6 @@ if(damage_done > 0) L.amount_grown = min(L.amount_grown + damage_done, L.max_grown) -/mob/living/simple_animal/attack_slime(mob/living/simple_animal/slime/user, list/modifiers) - if(..()) //successful slime attack - var/damage = rand(15, 25) - if(user.is_adult) - damage = rand(20, 35) - return apply_damage(damage, user.melee_damage_type) - /mob/living/simple_animal/attack_drone(mob/living/basic/drone/user) if(user.combat_mode) //No kicking dogs even as a rogue drone. Use a weapon. return diff --git a/code/modules/mob/living/simple_animal/hostile/mining_mobs/curse_blob.dm b/code/modules/mob/living/simple_animal/hostile/mining_mobs/curse_blob.dm index a3d56af8b89..3a838ac58cd 100644 --- a/code/modules/mob/living/simple_animal/hostile/mining_mobs/curse_blob.dm +++ b/code/modules/mob/living/simple_animal/hostile/mining_mobs/curse_blob.dm @@ -119,8 +119,6 @@ IGNORE_PROC_IF_NOT_TARGET(attack_larva) IGNORE_PROC_IF_NOT_TARGET(attack_animal) -IGNORE_PROC_IF_NOT_TARGET(attack_slime) - /mob/living/simple_animal/hostile/asteroid/curseblob/bullet_act(obj/projectile/Proj) if(Proj.firer != set_target) return diff --git a/code/modules/mob/living/simple_animal/slime/ai.dm b/code/modules/mob/living/simple_animal/slime/ai.dm new file mode 100644 index 00000000000..0272a4e467b --- /dev/null +++ b/code/modules/mob/living/simple_animal/slime/ai.dm @@ -0,0 +1,494 @@ +#define SLIME_CARES_ABOUT(to_check) (to_check && (to_check == Target || to_check == Leader || (to_check in Friends))) +#define SLIME_HUNGER_NONE 0 +#define SLIME_HUNGER_HUNGRY 1 +#define SLIME_HUNGER_STARVING 2 + +/mob/living/simple_animal/slime/handle_automated_movement() + return //slime random movement is currently handled in handle_targets() + +/mob/living/simple_animal/slime/handle_automated_speech() + return //slime random speech is currently handled in handle_speech() + +///Handles slime mood +/mob/living/simple_animal/slime/proc/handle_mood(seconds_per_tick, times_fired) + #define SLIME_MOOD_NONE "" + #define SLIME_MOOD_ANGRY "angry" + #define SLIME_MOOD_MISCHIEVOUS "mischievous" + #define SLIME_MOOD_POUT "pout" + #define SLIME_MOOD_SAD "sad" + #define SLIME_MOOD_SMILE ":3" + + var/newmood = SLIME_MOOD_NONE + if (rabid || attacked_stacks) + newmood = SLIME_MOOD_ANGRY + else if (docile) + newmood = SLIME_MOOD_SMILE + else if (Target) + newmood = SLIME_MOOD_MISCHIEVOUS + + if (!newmood) + if (discipline_stacks && SPT_PROB(13, seconds_per_tick)) + newmood = SLIME_MOOD_POUT + else if (SPT_PROB(0.5, seconds_per_tick)) + newmood = pick(SLIME_MOOD_SAD, ":3", SLIME_MOOD_POUT) + + if ((current_mood == SLIME_MOOD_SAD || current_mood == SLIME_MOOD_SMILE || current_mood == SLIME_MOOD_POUT) && !newmood) + if(SPT_PROB(50, seconds_per_tick)) + newmood = current_mood + + if (newmood != current_mood) // This is so we don't redraw them every time + current_mood = newmood + regenerate_icons() + + #undef SLIME_MOOD_NONE + #undef SLIME_MOOD_ANGRY + #undef SLIME_MOOD_MISCHIEVOUS + #undef SLIME_MOOD_POUT + #undef SLIME_MOOD_SAD + #undef SLIME_MOOD_SMILE + +///Handles the slime understanding commends spoken to it +/mob/living/simple_animal/slime/proc/handle_speech(seconds_per_tick, times_fired) + //Speech understanding starts here + var/to_say + if (speech_buffer.len > 0) + var/who = speech_buffer[1] // Who said it? + var/phrase = speech_buffer[2] // What did they say? + if ((findtext(phrase, num2text(slime_id)) || findtext(phrase, "slimes"))) // Talking to us + if (findtext(phrase, "hello") || findtext(phrase, "hi")) + to_say = pick("Hello...", "Hi...") + else if (findtext(phrase, "follow")) + if (Leader) + if (Leader == who) // Already following him + to_say = pick("Yes...", "Lead...", "Follow...") + else if (Friends[who] > Friends[Leader]) // VIVA + set_leader(who) + to_say = "Yes... I follow [who]..." + else + to_say = "No... I follow [Leader]..." + else + if (Friends[who] >= SLIME_FRIENDSHIP_FOLLOW) + set_leader(who) + to_say = "I follow..." + else // Not friendly enough + to_say = pick("No...", "I no follow...") + else if (findtext(phrase, "stop")) + if (buckled) // We are asked to stop feeding + if (Friends[who] >= SLIME_FRIENDSHIP_STOPEAT) + stop_feeding() + set_target(null) + if (Friends[who] < SLIME_FRIENDSHIP_STOPEAT_NOANGRY) + add_friendship(who, -1) + to_say = "Grrr..." // I'm angry but I do it + else + to_say = "Fine..." + else if (Target) // We are asked to stop chasing + if (Friends[who] >= SLIME_FRIENDSHIP_STOPCHASE) + set_target(null) + if (Friends[who] < SLIME_FRIENDSHIP_STOPCHASE_NOANGRY) + add_friendship(who, -1) + to_say = "Grrr..." // I'm angry but I do it + else + to_say = "Fine..." + else if (Leader) // We are asked to stop following + if (Leader == who) + to_say = "Yes... I stay..." + set_leader(null) + else + if (Friends[who] > Friends[Leader]) + set_leader(null) + to_say = "Yes... I stop..." + else + to_say = "No... keep follow..." + else if (findtext(phrase, "stay")) + if (Leader) + if (Leader == who) + holding_still = Friends[who] * 10 + to_say = "Yes... stay..." + else if (Friends[who] > Friends[Leader]) + holding_still = (Friends[who] - Friends[Leader]) * 10 + to_say = "Yes... stay..." + else + to_say = "No... keep follow..." + else + if (Friends[who] >= SLIME_FRIENDSHIP_STAY) + holding_still = Friends[who] * 10 + to_say = "Yes... stay..." + else + to_say = "No... won't stay..." + else if (findtext(phrase, "attack")) + if (rabid && prob(20)) + set_target(who) + process_slime_ai() //Wake up the slime's Target AI, needed otherwise this doesn't work + to_say = "ATTACK!?!?" + else if (Friends[who] >= SLIME_FRIENDSHIP_ATTACK) + for (var/mob/living/possible_target in view(7,src)-list(src,who)) + if (findtext(phrase, lowertext(possible_target.name))) + if (isslime(possible_target)) + to_say = "NO... [possible_target] slime friend" + add_friendship(who, -1) //Don't ask a slime to attack its friend + else if(!Friends[possible_target] || Friends[possible_target] < 1) + set_target(possible_target) + process_slime_ai()//Wake up the slime's Target AI, needed otherwise this doesn't work + to_say = "Ok... I attack [Target]" + else + to_say = "No... like [possible_target] ..." + add_friendship(who, -1) //Don't ask a slime to attack its friend + break + else + to_say = "No... no listen" + + speech_buffer = list() + + //Speech starts here + if (to_say) + say (to_say) + else if(SPT_PROB(0.5, seconds_per_tick)) + emote(pick("bounce","sway","light","vibrate","jiggle")) + else + var/speech_chance = 10 + var/slimes_near = 0 + var/dead_slimes = 0 + var/friends_near = list() + for (var/mob/living/seen_mob in view(7,src)) + if(isslime(seen_mob) && seen_mob != src) + ++slimes_near + if (seen_mob.stat == DEAD) + ++dead_slimes + if (seen_mob in Friends) + speech_chance += 20 + friends_near += seen_mob + if (nutrition < hunger_nutrition) + speech_chance += 10 + if (nutrition < starve_nutrition) + speech_chance += 10 + if (SPT_PROB(1, seconds_per_tick) && prob(speech_chance)) + var/phrases = list() + if (Target) + phrases += "[Target]... look yummy..." + if (nutrition < starve_nutrition) + phrases += "So... hungry..." + phrases += "Very... hungry..." + phrases += "Need... food..." + phrases += "Must... eat..." + else if (nutrition < hunger_nutrition) + phrases += "Hungry..." + phrases += "Where food?" + phrases += "I want to eat..." + phrases += "Rawr..." + phrases += "Blop..." + phrases += "Blorble..." + if (rabid || attacked_stacks) + phrases += "Hrr..." + phrases += "Nhuu..." + phrases += "Unn..." + if (current_mood == ":3") + phrases += "Purr..." + if (attacked_stacks) + phrases += "Grrr..." + if (bodytemperature < T0C) + phrases += "Cold..." + if (bodytemperature < T0C - 30) + phrases += "So... cold..." + phrases += "Very... cold..." + if (bodytemperature < T0C - 50) + phrases += "..." + phrases += "C... c..." + if (buckled) + phrases += "Nom..." + phrases += "Yummy..." + if (powerlevel > 3) + phrases += "Bzzz..." + if (powerlevel > 5) + phrases += "Zap..." + if (powerlevel > 8) + phrases += "Zap... Bzz..." + if (current_mood == "sad") + phrases += "Bored..." + if (slimes_near) + phrases += "Slime friend..." + if (slimes_near > 1) + phrases += "Slime friends..." + if (dead_slimes) + phrases += "What happened?" + if (!slimes_near) + phrases += "Lonely..." + for (var/friend in friends_near) + phrases += "[friend]... friend..." + if (nutrition < hunger_nutrition) + phrases += "[friend]... feed me..." + if(!stat) + say (pick(phrases)) + +///Sets the slime's current attack target +/mob/living/simple_animal/slime/proc/set_target(new_target) + var/old_target = Target + Target = new_target + if(old_target && !SLIME_CARES_ABOUT(old_target)) + UnregisterSignal(old_target, COMSIG_QDELETING) + if(Target) + RegisterSignal(Target, COMSIG_QDELETING, PROC_REF(clear_memories_of), override = TRUE) + +///Sets the person the slime is following around +/mob/living/simple_animal/slime/proc/set_leader(new_leader) + var/old_leader = Leader + Leader = new_leader + if(old_leader && !SLIME_CARES_ABOUT(old_leader)) + UnregisterSignal(old_leader, COMSIG_QDELETING) + if(Leader) + RegisterSignal(Leader, COMSIG_QDELETING, PROC_REF(clear_memories_of), override = TRUE) + +///Alters the friendship value of the target +/mob/living/simple_animal/slime/proc/add_friendship(new_friend, amount = 1) + if(!Friends[new_friend]) + Friends[new_friend] = 0 + Friends[new_friend] += amount + if(new_friend) + RegisterSignal(new_friend, COMSIG_QDELETING, PROC_REF(clear_memories_of), override = TRUE) + +///Sets the friendship value of the target +/mob/living/simple_animal/slime/proc/set_friendship(new_friend, amount = 1) + Friends[new_friend] = amount + if(new_friend) + RegisterSignal(new_friend, COMSIG_QDELETING, PROC_REF(clear_memories_of), override = TRUE) + +///Removes someone from the friendlist +/mob/living/simple_animal/slime/proc/remove_friend(friend) + Friends -= friend + if(friend && !SLIME_CARES_ABOUT(friend)) + UnregisterSignal(friend, COMSIG_QDELETING) + +///Adds someone to the friend list +/mob/living/simple_animal/slime/proc/set_friends(new_buds) + clear_friends() + for(var/mob/friend as anything in new_buds) + set_friendship(friend, new_buds[friend]) + +///Removes everyone from the friend list +/mob/living/simple_animal/slime/proc/clear_friends() + for(var/mob/friend as anything in Friends) + remove_friend(friend) + +///The passed source will be no longer be the slime's target, leader, or one of its friends +/mob/living/simple_animal/slime/proc/clear_memories_of(datum/source) + SIGNAL_HANDLER + if(source == Target) + set_target(null) + if(source == Leader) + set_leader(null) + remove_friend(source) + +///Handles selecting targets +/mob/living/simple_animal/slime/proc/handle_targets(seconds_per_tick, times_fired) + if(attacked_stacks > 50) + attacked_stacks = 50 + + if(attacked_stacks > 0) + attacked_stacks-- + + if(discipline_stacks > 0) + + if(discipline_stacks >= 5 && rabid) + if(SPT_PROB(37, seconds_per_tick)) + rabid = FALSE + + if(SPT_PROB(5, seconds_per_tick)) + discipline_stacks-- + + if(client) //player controlled slimes can decide for themselves + return + + if(!(mobility_flags & MOBILITY_MOVE)) + return + + if(buckled) + return // if it's eating someone already, continue eating! + + if(Target) + --target_patience + if (target_patience <= 0 || stunned_until > world.time || discipline_stacks || attacked_stacks || docile) // Tired of chasing or something draws out attention + target_patience = 0 + set_target(null) + + if(slime_ai_processing && stunned_until > world.time) + return + + var/hungry = SLIME_HUNGER_NONE // determines if the slime is hungry + + if (nutrition < starve_nutrition) + hungry = SLIME_HUNGER_STARVING + else if (nutrition < grow_nutrition && SPT_PROB(13, seconds_per_tick) || nutrition < hunger_nutrition) + hungry = SLIME_HUNGER_HUNGRY + + if(hungry == SLIME_HUNGER_STARVING && !client) // if a slime is starving, it starts losing its friends + if(Friends.len > 0 && SPT_PROB(0.5, seconds_per_tick)) + var/mob/nofriend = pick(Friends) + add_friendship(nofriend, -1) + + if(!Target) //If we have no target, try to add a target + if(will_hunt() && hungry || attacked_stacks || rabid) // Only add to the list if we need to + var/list/targets = list() + + for(var/mob/living/L in view(7,src)) + + if(isslime(L) || L.stat == DEAD) // Ignore other slimes and dead mobs + continue + + if(L in Friends) // No eating friends! + continue + + var/ally = FALSE + for(var/F in faction) + if(F == FACTION_NEUTRAL) //slimes are neutral so other mobs not target them, but they can target neutral mobs + continue + if(F in L.faction) + ally = TRUE + break + if(ally) + continue + + if(issilicon(L) && (rabid || attacked_stacks)) // They can't eat silicons, but they can glomp them in defence + targets += L // Possible target found! + + if(locate(/mob/living/simple_animal/slime) in L.buckled_mobs) // Only one slime can latch on at a time. + continue + + targets += L // Possible target found! + + if(targets.len > 0) + if(attacked_stacks || rabid || hungry == SLIME_HUNGER_STARVING) + set_target(targets[1]) // I am attacked and am fighting back or so hungry I don't even care + else + for(var/mob/living/carbon/C in targets) + if(!discipline_stacks && SPT_PROB(2.5, seconds_per_tick)) + if(ishuman(C) || isalienadult(C)) + set_target(C) + break + + if(islarva(C) || ismonkey(C)) + set_target(C) + break + + if (Target) + target_patience = rand(5, 7) + if (life_stage == SLIME_LIFE_STAGE_ADULT) + target_patience += 3 + + if(!Target) // If we have no target, we are wandering or following orders + if (Leader) + if(holding_still) + holding_still = max(holding_still - (0.5 * seconds_per_tick), 0) + else if(!HAS_TRAIT(src, TRAIT_IMMOBILIZED) && isturf(loc)) + step_to(src, Leader) + + else if(hungry) + if (holding_still) + holding_still = max(holding_still - (0.5 * hungry * seconds_per_tick), 0) + else if(!HAS_TRAIT(src, TRAIT_IMMOBILIZED) && isturf(loc) && prob(50)) + step(src, pick(GLOB.cardinals)) + + else + if(holding_still) + holding_still = max(holding_still - (0.5 * seconds_per_tick), 0) + else if (docile && pulledby) + holding_still = 10 + else if(!HAS_TRAIT(src, TRAIT_IMMOBILIZED) && isturf(loc) && prob(33)) + step(src, pick(GLOB.cardinals)) + else if(!slime_ai_processing) + INVOKE_ASYNC(src, PROC_REF(process_slime_ai)) + +/// Check for being stopped from feeding and chasing +/mob/living/simple_animal/slime/proc/will_hunt(hunger = -1) + if (docile) + return FALSE + if (hunger == SLIME_HUNGER_STARVING || rabid || attacked_stacks) + return TRUE + if (Leader) + return FALSE + if (holding_still) + return FALSE + return TRUE + +/// the master AI process +/mob/living/simple_animal/slime/proc/process_slime_ai() + + if(slime_ai_processing || stat || client) + return + + var/hungry = SLIME_HUNGER_NONE + if (nutrition < starve_nutrition) + hungry = SLIME_HUNGER_STARVING + else if (nutrition < grow_nutrition && prob(25) || nutrition < hunger_nutrition) + hungry = SLIME_HUNGER_HUNGRY + + slime_ai_processing = TRUE + + while(slime_ai_processing && stat != DEAD && (attacked_stacks || hungry || rabid || buckled)) + if(!(mobility_flags & MOBILITY_MOVE)) //also covers buckling. Not sure why buckled is in the while condition if we're going to immediately break, honestly + break + + if(!Target || client) + break + + if(Target.health <= -70 || Target.stat == DEAD) + set_target(null) + slime_ai_processing = FALSE + break + + if(Target) + if(locate(/mob/living/simple_animal/slime) in Target.buckled_mobs) + set_target(null) + slime_ai_processing = FALSE + break + if(!slime_ai_processing) + break + + if(Target in view(1,src)) + if(!can_feed_on(Target)) //If they're not able to be fed upon, ignore them. + if(!is_attack_on_cooldown) + is_attack_on_cooldown = TRUE + addtimer(VARSET_CALLBACK(src, is_attack_on_cooldown, FALSE), 4.5 SECONDS) + + if(Target.Adjacent(src)) + Target.attack_animal(src) + break + if((Target.body_position == STANDING_UP) && prob(80)) + + if(Target.client && Target.health >= 20) + if(!is_attack_on_cooldown) + is_attack_on_cooldown = TRUE + addtimer(VARSET_CALLBACK(src, is_attack_on_cooldown, FALSE), 4.5 SECONDS) + + if(Target.Adjacent(src)) + Target.attack_animal(src) + + else + if(!is_attack_on_cooldown && Target.Adjacent(src)) + start_feeding(Target) + + else + if(!is_attack_on_cooldown && Target.Adjacent(src)) + start_feeding(Target) + + else if(Target in view(7, src)) + if(!Target.Adjacent(src)) + // Bug of the month candidate: slimes were attempting to move to target only if it was directly next to them, which caused them to target things, but not approach them + step_to(src, Target) + else + set_target(null) + slime_ai_processing = FALSE + break + + var/sleeptime = cached_multiplicative_slowdown + if(sleeptime <= 0) + sleeptime = 1 + + sleep(sleeptime + 2) // this is about as fast as a player slime can go + + slime_ai_processing = FALSE + +#undef SLIME_CARES_ABOUT + +#undef SLIME_HUNGER_NONE +#undef SLIME_HUNGER_HUNGRY +#undef SLIME_HUNGER_STARVING diff --git a/code/modules/mob/living/simple_animal/slime/death.dm b/code/modules/mob/living/simple_animal/slime/death.dm index 033d7d2414e..a072299d663 100644 --- a/code/modules/mob/living/simple_animal/slime/death.dm +++ b/code/modules/mob/living/simple_animal/slime/death.dm @@ -1,22 +1,17 @@ /mob/living/simple_animal/slime/death(gibbed) if(stat == DEAD) return - if(!gibbed) - if(is_adult) - var/mob/living/simple_animal/slime/new_slime = new(drop_location(), slime_type.type) - new_slime.rabid = TRUE - new_slime.regenerate_icons() + if(!gibbed && life_stage == SLIME_LIFE_STAGE_ADULT) + var/mob/living/simple_animal/slime/new_slime = new(drop_location(), slime_type.type) + new_slime.rabid = TRUE + new_slime.regenerate_icons() - is_adult = FALSE - maxHealth = 150 - for(var/datum/action/innate/slime/reproduce/reproduce_action in actions) - reproduce_action.Remove(src) - var/datum/action/innate/slime/evolve/evolve_action = new - evolve_action.Grant(src) - revive(HEAL_ALL) - regenerate_icons() - update_name() - return + //revives us as a baby + set_life_stage(SLIME_LIFE_STAGE_BABY) + revive(HEAL_ALL) + regenerate_icons() + update_name() + return if(buckled) stop_feeding(silent = TRUE) //releases ourselves from the mob we fed on. diff --git a/code/modules/mob/living/simple_animal/slime/defense.dm b/code/modules/mob/living/simple_animal/slime/defense.dm new file mode 100644 index 00000000000..dd94e4e2bab --- /dev/null +++ b/code/modules/mob/living/simple_animal/slime/defense.dm @@ -0,0 +1,131 @@ + +/mob/living/simple_animal/slime/adjustFireLoss(amount, updating_health = TRUE, forced = FALSE, required_bodytype) + if(!forced) + amount = -abs(amount) + return ..() //Heals them + +/mob/living/simple_animal/slime/emp_act(severity) + . = ..() + if(. & EMP_PROTECT_SELF) + return + powerlevel = 0 // oh no, the power! + +/mob/living/simple_animal/slime/attack_animal(mob/living/simple_animal/user, list/modifiers) + . = ..() + if(.) + attacked_stacks += 10 + +/mob/living/simple_animal/slime/attack_paw(mob/living/carbon/human/user, list/modifiers) + if(..()) //successful monkey bite. + attacked_stacks += 10 + +/mob/living/simple_animal/slime/attack_larva(mob/living/carbon/alien/larva/L, list/modifiers) + if(..()) //successful larva bite. + attacked_stacks += 10 + +/mob/living/simple_animal/slime/attack_hulk(mob/living/carbon/human/user) + . = ..() + if(!.) + return + discipline_slime(user) + +/mob/living/simple_animal/slime/attack_hand(mob/living/carbon/human/user, list/modifiers) + if(buckled) + user.do_attack_animation(src, ATTACK_EFFECT_DISARM) + if(buckled == user) + if(prob(60)) + user.visible_message(span_warning("[user] attempts to wrestle \the [name] off!"), \ + span_danger("You attempt to wrestle \the [name] off!")) + playsound(loc, 'sound/weapons/punchmiss.ogg', 25, TRUE, -1) + + else + user.visible_message(span_warning("[user] manages to wrestle \the [name] off!"), \ + span_notice("You manage to wrestle \the [name] off!")) + playsound(loc, 'sound/weapons/thudswoosh.ogg', 50, TRUE, -1) + + discipline_slime(user) + + else + if(prob(30)) + buckled.visible_message(span_warning("[user] attempts to wrestle \the [name] off of [buckled]!"), \ + span_warning("[user] attempts to wrestle \the [name] off of you!")) + playsound(loc, 'sound/weapons/punchmiss.ogg', 25, TRUE, -1) + + else + buckled.visible_message(span_warning("[user] manages to wrestle \the [name] off of [buckled]!"), \ + span_notice("[user] manage to wrestle \the [name] off of you!")) + playsound(loc, 'sound/weapons/thudswoosh.ogg', 50, TRUE, -1) + + discipline_slime(user) + else + if(stat == DEAD && surgeries.len) + if(!user.combat_mode || LAZYACCESS(modifiers, RIGHT_CLICK)) + for(var/datum/surgery/operations as anything in surgeries) + if(operations.next_step(user, modifiers)) + return TRUE + if(..()) //successful attack + attacked_stacks += 10 + +/mob/living/simple_animal/slime/attack_alien(mob/living/carbon/alien/adult/user, list/modifiers) + if(..()) //if harm or disarm intent. + attacked_stacks += 10 + discipline_slime(user) + +/mob/living/simple_animal/slime/attackby(obj/item/attacking_item, mob/living/user, params) + if(stat == DEAD && surgeries.len) + var/list/modifiers = params2list(params) + if(!user.combat_mode || (LAZYACCESS(modifiers, RIGHT_CLICK))) + for(var/datum/surgery/operations as anything in surgeries) + if(operations.next_step(user, modifiers)) + return TRUE + if(istype(attacking_item, /obj/item/stack/sheet/mineral/plasma) && !stat) //Lets you feed slimes plasma. + add_friendship(user, 1) + to_chat(user, span_notice("You feed the slime the plasma. It chirps happily.")) + var/obj/item/stack/sheet/mineral/plasma/sheet = attacking_item + sheet.use(1) + return + if(attacking_item.force > 0) + attacked_stacks += 10 + if(prob(25)) + user.do_attack_animation(src) + user.changeNext_move(CLICK_CD_MELEE) + to_chat(user, span_danger("[attacking_item] passes right through [src]!")) + return + if(discipline_stacks && prob(50)) // wow, buddy, why am I getting attacked?? + discipline_stacks = 0 + if(attacking_item.force >= 3) + var/force_effect = attacking_item.force * (life_stage == SLIME_LIFE_STAGE_BABY ? 2 : 1) + if(prob(10 + force_effect)) + discipline_slime(user) + + if(!istype(attacking_item, /obj/item/storage/bag/xeno)) + return ..() + + var/obj/item/storage/xeno_bag = attacking_item + if(!crossbreed_modification) + to_chat(user, span_warning("The slime is not currently being mutated.")) + return + var/has_output = FALSE //Have we outputted text? + var/has_found = FALSE //Have we found an extract to be added? + for(var/obj/item/slime_extract/extract in xeno_bag.contents) + if(extract.crossbreed_modification == crossbreed_modification) + xeno_bag.atom_storage.attempt_remove(extract, get_turf(src), silent = TRUE) + qdel(extract) + applied_crossbreed_amount++ + has_found = TRUE + if(applied_crossbreed_amount >= SLIME_EXTRACT_CROSSING_REQUIRED) + to_chat(user, span_notice("You feed the slime as many of the extracts from the bag as you can, and it mutates!")) + playsound(src, 'sound/effects/attackblob.ogg', 50, TRUE) + spawn_corecross() + has_output = TRUE + break + + if(has_output) + return + + if(!has_found) + to_chat(user, span_warning("There are no extracts in the bag that this slime will accept!")) + else + to_chat(user, span_notice("You feed the slime some extracts from the bag.")) + playsound(src, 'sound/effects/attackblob.ogg', 50, TRUE) + return diff --git a/code/modules/mob/living/simple_animal/slime/life.dm b/code/modules/mob/living/simple_animal/slime/life.dm index e17fd09e0fa..be158abaed5 100644 --- a/code/modules/mob/living/simple_animal/slime/life.dm +++ b/code/modules/mob/living/simple_animal/slime/life.dm @@ -1,6 +1,4 @@ -#define SLIME_HUNGER_NONE 0 -#define SLIME_HUNGER_HUNGRY 1 -#define SLIME_HUNGER_STARVING 2 + /mob/living/simple_animal/slime/Life(seconds_per_tick = SSMOBS_DT, times_fired) if(HAS_TRAIT(src, TRAIT_NO_TRANSFORM)) @@ -34,84 +32,6 @@ return return ..() -/// the master AI process -/mob/living/simple_animal/slime/proc/process_slime_ai() - - if(slime_ai_processing || stat || client) - return - - var/hungry = SLIME_HUNGER_NONE - if (nutrition < get_starve_nutrition()) - hungry = SLIME_HUNGER_STARVING - else if (nutrition < get_grow_nutrition() && prob(25) || nutrition < get_hunger_nutrition()) - hungry = SLIME_HUNGER_HUNGRY - - slime_ai_processing = TRUE - - while(slime_ai_processing && stat != DEAD && (attacked_stacks || hungry || rabid || buckled)) - if(!(mobility_flags & MOBILITY_MOVE)) //also covers buckling. Not sure why buckled is in the while condition if we're going to immediately break, honestly - break - - if(!Target || client) - break - - if(Target.health <= -70 || Target.stat == DEAD) - set_target(null) - slime_ai_processing = FALSE - break - - if(Target) - if(locate(/mob/living/simple_animal/slime) in Target.buckled_mobs) - set_target(null) - slime_ai_processing = FALSE - break - if(!slime_ai_processing) - break - - if(Target in view(1,src)) - if(!can_feed_on(Target)) //If they're not able to be fed upon, ignore them. - if(!is_attack_on_cooldown) - is_attack_on_cooldown = TRUE - addtimer(VARSET_CALLBACK(src, is_attack_on_cooldown, FALSE), 4.5 SECONDS) - - if(Target.Adjacent(src)) - Target.attack_slime(src) - break - if((Target.body_position == STANDING_UP) && prob(80)) - - if(Target.client && Target.health >= 20) - if(!is_attack_on_cooldown) - is_attack_on_cooldown = TRUE - addtimer(VARSET_CALLBACK(src, is_attack_on_cooldown, FALSE), 4.5 SECONDS) - - if(Target.Adjacent(src)) - Target.attack_slime(src) - - else - if(!is_attack_on_cooldown && Target.Adjacent(src)) - start_feeding(Target) - - else - if(!is_attack_on_cooldown && Target.Adjacent(src)) - start_feeding(Target) - - else if(Target in view(7, src)) - if(!Target.Adjacent(src)) - // Bug of the month candidate: slimes were attempting to move to target only if it was directly next to them, which caused them to target things, but not approach them - step_to(src, Target) - else - set_target(null) - slime_ai_processing = FALSE - break - - var/sleeptime = cached_multiplicative_slowdown - if(sleeptime <= 0) - sleeptime = 1 - - sleep(sleeptime + 2) // this is about as fast as a player slime can go - - slime_ai_processing = FALSE - /mob/living/simple_animal/slime/handle_environment(datum/gas_mixture/environment, seconds_per_tick, times_fired) var/loc_temp = get_temperature(environment) var/divisor = 10 /// The divisor controls how fast body temperature changes, lower causes faster changes @@ -209,7 +129,7 @@ if(need_mob_update) animal_victim.updatehealth() - if(totaldamage <= 0) //if we did no(or negative!) damage to it, stop + if(totaldamage >= 0) // AdjustBruteLoss returns a negative value on succesful damage adjustment stop_feeding(FALSE, FALSE) return @@ -230,411 +150,32 @@ return if(SPT_PROB(7.5, seconds_per_tick)) - adjust_nutrition(-0.5 * (1 + is_adult) * seconds_per_tick) + adjust_nutrition((life_stage == SLIME_LIFE_STAGE_ADULT ? -1 : -0.5) * seconds_per_tick) if(nutrition <= 0) set_nutrition(0) if(SPT_PROB(50, seconds_per_tick)) adjustBruteLoss(rand(0,5)) - else if (nutrition >= get_grow_nutrition() && amount_grown < SLIME_EVOLUTION_THRESHOLD) + else if (nutrition >= grow_nutrition && amount_grown < SLIME_EVOLUTION_THRESHOLD) adjust_nutrition(-10 * seconds_per_tick) amount_grown++ update_mob_action_buttons() if(amount_grown >= SLIME_EVOLUTION_THRESHOLD && !buckled && !Target && !ckey) - if(is_adult && loc.AllowDrop()) + if(life_stage == SLIME_LIFE_STAGE_ADULT && loc.AllowDrop()) Reproduce() else Evolve() ///Adds nutrition to the slime's nutrition level. Has a chance to increase its electric levels. /mob/living/simple_animal/slime/proc/add_nutrition(nutrition_to_add = 0) - set_nutrition(min((nutrition + nutrition_to_add), get_max_nutrition())) - if(nutrition >= get_grow_nutrition()) - if(powerlevel<10) + set_nutrition(min((nutrition + nutrition_to_add), max_nutrition)) + if(nutrition >= grow_nutrition) + if(powerlevel= get_hunger_nutrition() + 100) //can't get power levels unless you're a bit above hunger level. - if(powerlevel<5) + else if(nutrition >= hunger_nutrition + 100) //can't get power levels unless you're a bit above hunger level. + if(powerlevel 50) - attacked_stacks = 50 - - if(attacked_stacks > 0) - attacked_stacks-- - - if(discipline_stacks > 0) - - if(discipline_stacks >= 5 && rabid) - if(SPT_PROB(37, seconds_per_tick)) - rabid = FALSE - - if(SPT_PROB(5, seconds_per_tick)) - discipline_stacks-- - - if(client) //player controlled slimes can decide for themselves - return - - if(!(mobility_flags & MOBILITY_MOVE)) - return - - if(buckled) - return // if it's eating someone already, continue eating! - - if(Target) - --target_patience - if (target_patience <= 0 || stunned_until > world.time || discipline_stacks || attacked_stacks || docile) // Tired of chasing or something draws out attention - target_patience = 0 - set_target(null) - - if(slime_ai_processing && stunned_until > world.time) - return - - var/hungry = SLIME_HUNGER_NONE // determines if the slime is hungry - - if (nutrition < get_starve_nutrition()) - hungry = SLIME_HUNGER_STARVING - else if (nutrition < get_grow_nutrition() && SPT_PROB(13, seconds_per_tick) || nutrition < get_hunger_nutrition()) - hungry = SLIME_HUNGER_HUNGRY - - if(hungry == SLIME_HUNGER_STARVING && !client) // if a slime is starving, it starts losing its friends - if(Friends.len > 0 && SPT_PROB(0.5, seconds_per_tick)) - var/mob/nofriend = pick(Friends) - add_friendship(nofriend, -1) - - if(!Target) //If we have no target, try to add a target - if(will_hunt() && hungry || attacked_stacks || rabid) // Only add to the list if we need to - var/list/targets = list() - - for(var/mob/living/L in view(7,src)) - - if(isslime(L) || L.stat == DEAD) // Ignore other slimes and dead mobs - continue - - if(L in Friends) // No eating friends! - continue - - var/ally = FALSE - for(var/F in faction) - if(F == FACTION_NEUTRAL) //slimes are neutral so other mobs not target them, but they can target neutral mobs - continue - if(F in L.faction) - ally = TRUE - break - if(ally) - continue - - if(issilicon(L) && (rabid || attacked_stacks)) // They can't eat silicons, but they can glomp them in defence - targets += L // Possible target found! - - if(locate(/mob/living/simple_animal/slime) in L.buckled_mobs) // Only one slime can latch on at a time. - continue - - targets += L // Possible target found! - - if(targets.len > 0) - if(attacked_stacks || rabid || hungry == SLIME_HUNGER_STARVING) - set_target(targets[1]) // I am attacked and am fighting back or so hungry I don't even care - else - for(var/mob/living/carbon/C in targets) - if(!discipline_stacks && SPT_PROB(2.5, seconds_per_tick)) - if(ishuman(C) || isalienadult(C)) - set_target(C) - break - - if(islarva(C) || ismonkey(C)) - set_target(C) - break - - if (Target) - target_patience = rand(5, 7) - if (is_adult) - target_patience += 3 - - if(!Target) // If we have no target, we are wandering or following orders - if (Leader) - if(holding_still) - holding_still = max(holding_still - (0.5 * seconds_per_tick), 0) - else if(!HAS_TRAIT(src, TRAIT_IMMOBILIZED) && isturf(loc)) - step_to(src, Leader) - - else if(hungry) - if (holding_still) - holding_still = max(holding_still - (0.5 * hungry * seconds_per_tick), 0) - else if(!HAS_TRAIT(src, TRAIT_IMMOBILIZED) && isturf(loc) && prob(50)) - step(src, pick(GLOB.cardinals)) - - else - if(holding_still) - holding_still = max(holding_still - (0.5 * seconds_per_tick), 0) - else if (docile && pulledby) - holding_still = 10 - else if(!HAS_TRAIT(src, TRAIT_IMMOBILIZED) && isturf(loc) && prob(33)) - step(src, pick(GLOB.cardinals)) - else if(!slime_ai_processing) - INVOKE_ASYNC(src, PROC_REF(process_slime_ai)) - -/mob/living/simple_animal/slime/handle_automated_movement() - return //slime random movement is currently handled in handle_targets() - -/mob/living/simple_animal/slime/handle_automated_speech() - return //slime random speech is currently handled in handle_speech() - -///Handles slime mood -/mob/living/simple_animal/slime/proc/handle_mood(seconds_per_tick, times_fired) - #define SLIME_MOOD_NONE "" - #define SLIME_MOOD_ANGRY "angry" - #define SLIME_MOOD_MISCHIEVOUS "mischievous" - #define SLIME_MOOD_POUT "pout" - #define SLIME_MOOD_SAD "sad" - #define SLIME_MOOD_SMILE ":3" - - var/newmood = SLIME_MOOD_NONE - if (rabid || attacked_stacks) - newmood = SLIME_MOOD_ANGRY - else if (docile) - newmood = SLIME_MOOD_SMILE - else if (Target) - newmood = SLIME_MOOD_MISCHIEVOUS - - if (!newmood) - if (discipline_stacks && SPT_PROB(13, seconds_per_tick)) - newmood = SLIME_MOOD_POUT - else if (SPT_PROB(0.5, seconds_per_tick)) - newmood = pick(SLIME_MOOD_SAD, ":3", SLIME_MOOD_POUT) - - if ((current_mood == SLIME_MOOD_SAD || current_mood == SLIME_MOOD_SMILE || current_mood == SLIME_MOOD_POUT) && !newmood) - if(SPT_PROB(50, seconds_per_tick)) - newmood = current_mood - - if (newmood != current_mood) // This is so we don't redraw them every time - current_mood = newmood - regenerate_icons() - - #undef SLIME_MOOD_NONE - #undef SLIME_MOOD_ANGRY - #undef SLIME_MOOD_MISCHIEVOUS - #undef SLIME_MOOD_POUT - #undef SLIME_MOOD_SAD - #undef SLIME_MOOD_SMILE - -///Handles the slime understanding commends spoken to it -/mob/living/simple_animal/slime/proc/handle_speech(seconds_per_tick, times_fired) - //Speech understanding starts here - var/to_say - if (speech_buffer.len > 0) - var/who = speech_buffer[1] // Who said it? - var/phrase = speech_buffer[2] // What did they say? - if ((findtext(phrase, num2text(slime_id)) || findtext(phrase, "slimes"))) // Talking to us - if (findtext(phrase, "hello") || findtext(phrase, "hi")) - to_say = pick("Hello...", "Hi...") - else if (findtext(phrase, "follow")) - if (Leader) - if (Leader == who) // Already following him - to_say = pick("Yes...", "Lead...", "Follow...") - else if (Friends[who] > Friends[Leader]) // VIVA - set_leader(who) - to_say = "Yes... I follow [who]..." - else - to_say = "No... I follow [Leader]..." - else - if (Friends[who] >= SLIME_FRIENDSHIP_FOLLOW) - set_leader(who) - to_say = "I follow..." - else // Not friendly enough - to_say = pick("No...", "I no follow...") - else if (findtext(phrase, "stop")) - if (buckled) // We are asked to stop feeding - if (Friends[who] >= SLIME_FRIENDSHIP_STOPEAT) - stop_feeding() - set_target(null) - if (Friends[who] < SLIME_FRIENDSHIP_STOPEAT_NOANGRY) - add_friendship(who, -1) - to_say = "Grrr..." // I'm angry but I do it - else - to_say = "Fine..." - else if (Target) // We are asked to stop chasing - if (Friends[who] >= SLIME_FRIENDSHIP_STOPCHASE) - set_target(null) - if (Friends[who] < SLIME_FRIENDSHIP_STOPCHASE_NOANGRY) - add_friendship(who, -1) - to_say = "Grrr..." // I'm angry but I do it - else - to_say = "Fine..." - else if (Leader) // We are asked to stop following - if (Leader == who) - to_say = "Yes... I stay..." - set_leader(null) - else - if (Friends[who] > Friends[Leader]) - set_leader(null) - to_say = "Yes... I stop..." - else - to_say = "No... keep follow..." - else if (findtext(phrase, "stay")) - if (Leader) - if (Leader == who) - holding_still = Friends[who] * 10 - to_say = "Yes... stay..." - else if (Friends[who] > Friends[Leader]) - holding_still = (Friends[who] - Friends[Leader]) * 10 - to_say = "Yes... stay..." - else - to_say = "No... keep follow..." - else - if (Friends[who] >= SLIME_FRIENDSHIP_STAY) - holding_still = Friends[who] * 10 - to_say = "Yes... stay..." - else - to_say = "No... won't stay..." - else if (findtext(phrase, "attack")) - if (rabid && prob(20)) - set_target(who) - process_slime_ai() //Wake up the slime's Target AI, needed otherwise this doesn't work - to_say = "ATTACK!?!?" - else if (Friends[who] >= SLIME_FRIENDSHIP_ATTACK) - for (var/mob/living/possible_target in view(7,src)-list(src,who)) - if (findtext(phrase, lowertext(possible_target.name))) - if (isslime(possible_target)) - to_say = "NO... [possible_target] slime friend" - add_friendship(who, -1) //Don't ask a slime to attack its friend - else if(!Friends[possible_target] || Friends[possible_target] < 1) - set_target(possible_target) - process_slime_ai()//Wake up the slime's Target AI, needed otherwise this doesn't work - to_say = "Ok... I attack [Target]" - else - to_say = "No... like [possible_target] ..." - add_friendship(who, -1) //Don't ask a slime to attack its friend - break - else - to_say = "No... no listen" - - speech_buffer = list() - - //Speech starts here - if (to_say) - say (to_say) - else if(SPT_PROB(0.5, seconds_per_tick)) - emote(pick("bounce","sway","light","vibrate","jiggle")) - else - var/speech_chance = 10 - var/slimes_near = 0 - var/dead_slimes = 0 - var/friends_near = list() - for (var/mob/living/seen_mob in view(7,src)) - if(isslime(seen_mob) && seen_mob != src) - ++slimes_near - if (seen_mob.stat == DEAD) - ++dead_slimes - if (seen_mob in Friends) - speech_chance += 20 - friends_near += seen_mob - if (nutrition < get_hunger_nutrition()) - speech_chance += 10 - if (nutrition < get_starve_nutrition()) - speech_chance += 10 - if (SPT_PROB(1, seconds_per_tick) && prob(speech_chance)) - var/phrases = list() - if (Target) - phrases += "[Target]... look yummy..." - if (nutrition < get_starve_nutrition()) - phrases += "So... hungry..." - phrases += "Very... hungry..." - phrases += "Need... food..." - phrases += "Must... eat..." - else if (nutrition < get_hunger_nutrition()) - phrases += "Hungry..." - phrases += "Where food?" - phrases += "I want to eat..." - phrases += "Rawr..." - phrases += "Blop..." - phrases += "Blorble..." - if (rabid || attacked_stacks) - phrases += "Hrr..." - phrases += "Nhuu..." - phrases += "Unn..." - if (current_mood == ":3") - phrases += "Purr..." - if (attacked_stacks) - phrases += "Grrr..." - if (bodytemperature < T0C) - phrases += "Cold..." - if (bodytemperature < T0C - 30) - phrases += "So... cold..." - phrases += "Very... cold..." - if (bodytemperature < T0C - 50) - phrases += "..." - phrases += "C... c..." - if (buckled) - phrases += "Nom..." - phrases += "Yummy..." - if (powerlevel > 3) - phrases += "Bzzz..." - if (powerlevel > 5) - phrases += "Zap..." - if (powerlevel > 8) - phrases += "Zap... Bzz..." - if (current_mood == "sad") - phrases += "Bored..." - if (slimes_near) - phrases += "Slime friend..." - if (slimes_near > 1) - phrases += "Slime friends..." - if (dead_slimes) - phrases += "What happened?" - if (!slimes_near) - phrases += "Lonely..." - for (var/friend in friends_near) - phrases += "[friend]... friend..." - if (nutrition < get_hunger_nutrition()) - phrases += "[friend]... feed me..." - if(!stat) - say (pick(phrases)) - -/// Can't go above it -/mob/living/simple_animal/slime/proc/get_max_nutrition() - if (is_adult) - return 1200 - else - return 1000 - -/// Above it we grow, below it we can eat -/mob/living/simple_animal/slime/proc/get_grow_nutrition() - if (is_adult) - return 1000 - else - return 800 - -/// Below it we will always eat -/mob/living/simple_animal/slime/proc/get_hunger_nutrition() - if (is_adult) - return 600 - else - return 500 - -/// Below it we will eat before everything else -/mob/living/simple_animal/slime/proc/get_starve_nutrition() - if(is_adult) - return 300 - else - return 200 -/// Check for being stopped from feeding and chasing -/mob/living/simple_animal/slime/proc/will_hunt(hunger = -1) - if (docile) - return FALSE - if (hunger == SLIME_HUNGER_STARVING || rabid || attacked_stacks) - return TRUE - if (Leader) - return FALSE - if (holding_still) - return FALSE - return TRUE - -#undef SLIME_HUNGER_NONE -#undef SLIME_HUNGER_HUNGRY -#undef SLIME_HUNGER_STARVING diff --git a/code/modules/mob/living/simple_animal/slime/powers.dm b/code/modules/mob/living/simple_animal/slime/powers.dm index 32c60f63adc..f129482eb24 100644 --- a/code/modules/mob/living/simple_animal/slime/powers.dm +++ b/code/modules/mob/living/simple_animal/slime/powers.dm @@ -133,25 +133,27 @@ ///The slime will stop feeding /mob/living/simple_animal/slime/proc/stop_feeding(silent = FALSE, living=TRUE) - if(buckled) - if(!living) - to_chat(src, "[pick("This subject is incompatible", \ - "This subject does not have life energy", "This subject is empty", \ - "I am not satisified", "I can not feed from this subject", \ - "I do not feel nourished", "This subject is not food")]!") + if(!buckled) + return - var/mob/living/victim = buckled + if(!living) + to_chat(src, "[pick("This subject is incompatible", \ + "This subject does not have life energy", "This subject is empty", \ + "I am not satisified", "I can not feed from this subject", \ + "I do not feel nourished", "This subject is not food")]!") - if(istype(victim)) - var/bio_protection = 100 - victim.getarmor(null, BIO) - if(prob(bio_protection)) - victim.apply_status_effect(/datum/status_effect/slimed, slime_type.rgb_code, slime_type.colour == SLIME_TYPE_RAINBOW) + var/mob/living/victim = buckled - if(!silent) - visible_message(span_warning("[src] lets go of [buckled]!"), \ - span_notice("I stopped feeding.")) - layer = initial(layer) - buckled.unbuckle_mob(src,force=TRUE) + if(istype(victim)) + var/bio_protection = 100 - victim.getarmor(null, BIO) + if(prob(bio_protection)) + victim.apply_status_effect(/datum/status_effect/slimed, slime_type.rgb_code, slime_type.colour == SLIME_TYPE_RAINBOW) + + if(!silent) + visible_message(span_warning("[src] lets go of [buckled]!"), \ + span_notice("I stopped feeding.")) + layer = initial(layer) + buckled.unbuckle_mob(src,force=TRUE) /mob/living/simple_animal/slime/verb/Evolve() set category = "Slime" @@ -160,19 +162,16 @@ if(stat) to_chat(src, "I must be conscious to do this...") return - if(is_adult) + if(life_stage == SLIME_LIFE_STAGE_ADULT) to_chat(src, "I have already evolved...") return if(amount_grown < SLIME_EVOLUTION_THRESHOLD) to_chat(src, "I am not ready to evolve yet...") return - is_adult = TRUE - maxHealth = 200 + set_life_stage(SLIME_LIFE_STAGE_ADULT) amount_grown = 0 - for(var/datum/action/innate/slime/evolve/evolve_action in actions) - evolve_action.Remove(src) - GRANT_ACTION(/datum/action/innate/slime/reproduce) + regenerate_icons() update_name() @@ -196,7 +195,7 @@ if(!isopenturf(loc)) balloon_alert(src, "can't reproduce here!") - if(!is_adult) + if(life_stage != SLIME_LIFE_STAGE_ADULT) balloon_alert(src, "not old enough to reproduce!") return diff --git a/code/modules/mob/living/simple_animal/slime/slime.dm b/code/modules/mob/living/simple_animal/slime/slime.dm index d9fbfe8dfb3..1d5006bda5e 100644 --- a/code/modules/mob/living/simple_animal/slime/slime.dm +++ b/code/modules/mob/living/simple_animal/slime/slime.dm @@ -1,4 +1,8 @@ -#define SLIME_CARES_ABOUT(to_check) (to_check && (to_check == Target || to_check == Leader || (to_check in Friends))) +#define SLIME_EXTRA_SHOCK_COST 3 +#define SLIME_EXTRA_SHOCK_THRESHOLD 8 +#define SLIME_BASE_SHOCK_PERCENTAGE 10 +#define SLIME_SHOCK_PERCENTAGE_PER_LEVEL 7 + /mob/living/simple_animal/slime name = "grey baby slime (123)" icon = 'icons/mob/simple/slimes.dmi' @@ -10,6 +14,8 @@ harm_intent_damage = 5 icon_living = "grey baby slime" icon_dead = "grey baby slime dead" + attack_verb_simple = "glomp" + attack_verb_continuous = "glomps" response_help_continuous = "pets" response_help_simple = "pet" response_disarm_continuous = "shoos" @@ -28,6 +34,7 @@ mob_biotypes = MOB_SLIME melee_damage_lower = 5 melee_damage_upper = 25 + wound_bonus = -45 verb_say = "blorbles" verb_ask = "inquisitively blorbles" @@ -42,17 +49,25 @@ //Physiology - ///Is the slime an adult slime? - var/is_adult = TRUE + ///What is our current lifestage? + var/life_stage = SLIME_LIFE_STAGE_BABY ///The number of /obj/item/slime_extract's the slime has left inside var/cores = 1 ///Chance of mutating, should be between 25 and 35 var/mutation_chance = 30 ///1-10 controls how much electricity they are generating - var/powerlevel = 0 + var/powerlevel = SLIME_MIN_POWER ///Controls how long the slime has been overfed, if 10, grows or reproduces var/amount_grown = 0 + ///The maximum amount of nutrition a slime can contain + var/max_nutrition = 1000 + /// Above it we grow our amount_grown and our power_level, below it we can eat + var/grow_nutrition = 800 + /// Below this, we feel hungry + var/hunger_nutrition = 500 + /// Below this, we feel starving + var/starve_nutrition = 200 ///Has a mutator been used on the slime? Only one is allowed var/mutator_used = FALSE @@ -106,20 +121,12 @@ ///Last phrase said near it and person who said it var/list/speech_buffer = list() -/mob/living/simple_animal/slime/Initialize(mapload, new_type=/datum/slime_type/grey, new_is_adult=FALSE) +/mob/living/simple_animal/slime/Initialize(mapload, new_type=/datum/slime_type/grey, new_life_stage=SLIME_LIFE_STAGE_BABY) var/datum/action/innate/slime/feed/feeding_action = new feeding_action.Grant(src) - is_adult = new_is_adult + set_life_stage(new_life_stage) - if(is_adult) - var/datum/action/innate/slime/reproduce/reproduce_action = new - reproduce_action.Grant(src) - health = 200 - maxHealth = 200 - else - var/datum/action/innate/slime/evolve/evolve_action = new - evolve_action.Grant(src) set_slime_type(new_type) . = ..() set_nutrition(700) @@ -129,6 +136,8 @@ ADD_TRAIT(src, TRAIT_CANT_RIDE, INNATE_TRAIT) ADD_TRAIT(src, TRAIT_VENTCRAWLER_ALWAYS, INNATE_TRAIT) + RegisterSignal(src, COMSIG_LIVING_UNARMED_ATTACK, PROC_REF(slime_pre_attack)) + /mob/living/simple_animal/slime/Destroy() for (var/A in actions) var/datum/action/AC = A @@ -139,34 +148,25 @@ return ..() ///Random slime subtype -/mob/living/simple_animal/slime/random/Initialize(mapload, new_colour, new_is_adult) - . = ..(mapload, pick(subtypesof(/datum/slime_type)), prob(50)) +/mob/living/simple_animal/slime/random/Initialize(mapload, new_colour, new_life_stage) + . = ..(mapload, pick(subtypesof(/datum/slime_type)), prob(50) ? SLIME_LIFE_STAGE_ADULT : SLIME_LIFE_STAGE_BABY) ///Friendly docile subtype /mob/living/simple_animal/slime/pet docile = TRUE -/mob/living/simple_animal/slime/proc/set_slime_type(new_type) - slime_type = new new_type - update_name() - regenerate_icons() - /mob/living/simple_animal/slime/update_name() ///Checks if the slime has a generic name, in the format of baby/adult slime (123) var/static/regex/slime_name_regex = new("\\w+ (baby|adult) slime \\(\\d+\\)") if(slime_name_regex.Find(name)) slime_id = rand(1, 1000) - name = "[slime_type.colour] [is_adult ? "adult" : "baby"] slime ([slime_id])" + name = "[slime_type.colour] [life_stage] slime ([slime_id])" real_name = name return ..() -///randomizes the colour of a slime -/mob/living/simple_animal/slime/proc/random_colour() - set_slime_type(pick(subtypesof(/datum/slime_type))) - /mob/living/simple_animal/slime/regenerate_icons() cut_overlays() - var/icon_text = "[slime_type.colour] [is_adult ? "adult" : "baby"] slime" + var/icon_text = "[slime_type.colour] [life_stage] slime" icon_dead = "[icon_text] dead" if(stat != DEAD) icon_state = icon_text @@ -197,39 +197,46 @@ if(mod) add_or_update_variable_movespeed_modifier(/datum/movespeed_modifier/slime_tempmod, multiplicative_slowdown = mod) -/mob/living/simple_animal/slime/ObjBump(obj/O) - if(!client && powerlevel > 0) - var/probab = 10 - switch(powerlevel) - if(1 to 2) - probab = 20 - if(3 to 4) - probab = 30 - if(5 to 6) - probab = 40 - if(7 to 8) - probab = 60 - if(9) - probab = 70 - if(10) - probab = 95 - if(prob(probab)) - if(istype(O, /obj/structure/window) || istype(O, /obj/structure/grille)) - if(nutrition <= get_hunger_nutrition() && !is_attack_on_cooldown) - if (is_adult || prob(5)) - O.attack_slime(src) - is_attack_on_cooldown = TRUE - addtimer(VARSET_CALLBACK(src, is_attack_on_cooldown, FALSE), 4.5 SECONDS) +/mob/living/simple_animal/slime/ObjBump(obj/bumped_object) + if(client || powerlevel <= SLIME_MIN_POWER) // slimes with people in control can't accidentally attack + return -/mob/living/simple_animal/slime/Process_Spacemove(movement_dir = 0, continuous_move = FALSE) - return 2 + if (life_stage != SLIME_LIFE_STAGE_ADULT && !prob(5)) //its rare for baby slimes to actually damage windows + return + + var/accidental_attack_probability = 10 + switch(powerlevel) + if(1 to 2) + accidental_attack_probability = 20 + if(3 to 4) + accidental_attack_probability = 30 + if(5 to 6) + accidental_attack_probability = 40 + if(7 to 8) + accidental_attack_probability = 60 + if(9) + accidental_attack_probability = 70 + if(10) + accidental_attack_probability = 95 + if(!prob(accidental_attack_probability)) + return + + if(!istype(bumped_object, /obj/structure/window) && !istype(bumped_object, /obj/structure/grille)) + return + + if(nutrition > hunger_nutrition || is_attack_on_cooldown) //hungry slimes and slimes on cooldown will not attack + return + + bumped_object.attack_animal(src) + is_attack_on_cooldown = TRUE + addtimer(VARSET_CALLBACK(src, is_attack_on_cooldown, FALSE), 4.5 SECONDS) /mob/living/simple_animal/slime/get_status_tab_items() . = ..() if(!docile) - . += "Nutrition: [nutrition]/[get_max_nutrition()]" + . += "Nutrition: [nutrition]/[max_nutrition]" if(amount_grown >= SLIME_EVOLUTION_THRESHOLD) - if(is_adult) + if(life_stage == SLIME_LIFE_STAGE_ADULT) . += "You can reproduce!" else . += "You can evolve!" @@ -241,17 +248,6 @@ . += "Power Level: [powerlevel]" -/mob/living/simple_animal/slime/adjustFireLoss(amount, updating_health = TRUE, forced = FALSE, required_bodytype) - if(!forced) - amount = -abs(amount) - return ..() //Heals them - -/mob/living/simple_animal/slime/emp_act(severity) - . = ..() - if(. & EMP_PROTECT_SELF) - return - powerlevel = 0 // oh no, the power! - /mob/living/simple_animal/slime/MouseDrop(atom/movable/target_atom as mob|obj) if(isliving(target_atom) && target_atom != src && usr == src) var/mob/living/Food = target_atom @@ -268,173 +264,9 @@ /mob/living/simple_animal/slime/attack_ui(slot, params) return -/mob/living/simple_animal/slime/attack_slime(mob/living/simple_animal/slime/attacking_slime, list/modifiers) - if(..()) //successful slime attack - if(attacking_slime == src) - return - if(buckled) - stop_feeding(silent = TRUE) - visible_message(span_danger("[attacking_slime] pulls [src] off!"), \ - span_danger("You pull [src] off!")) - return - attacked_stacks += 5 - if(nutrition >= 100) //steal some nutrition. negval handled in life() - adjust_nutrition(-(50 + (40 * attacking_slime.is_adult))) - attacking_slime.add_nutrition(50 + (40 * attacking_slime.is_adult)) - if(health > 0) - attacking_slime.adjustBruteLoss(-10 + (-10 * attacking_slime.is_adult)) - attacking_slime.updatehealth() - -/mob/living/simple_animal/slime/attack_animal(mob/living/simple_animal/user, list/modifiers) - . = ..() - if(.) - attacked_stacks += 10 - -/mob/living/simple_animal/slime/attack_paw(mob/living/carbon/human/user, list/modifiers) - if(..()) //successful monkey bite. - attacked_stacks += 10 - -/mob/living/simple_animal/slime/attack_larva(mob/living/carbon/alien/larva/L, list/modifiers) - if(..()) //successful larva bite. - attacked_stacks += 10 - -/mob/living/simple_animal/slime/attack_hulk(mob/living/carbon/human/user) - . = ..() - if(!.) - return - discipline_slime(user) - -/mob/living/simple_animal/slime/attack_hand(mob/living/carbon/human/user, list/modifiers) - if(buckled) - user.do_attack_animation(src, ATTACK_EFFECT_DISARM) - if(buckled == user) - if(prob(60)) - user.visible_message(span_warning("[user] attempts to wrestle \the [name] off!"), \ - span_danger("You attempt to wrestle \the [name] off!")) - playsound(loc, 'sound/weapons/punchmiss.ogg', 25, TRUE, -1) - - else - user.visible_message(span_warning("[user] manages to wrestle \the [name] off!"), \ - span_notice("You manage to wrestle \the [name] off!")) - playsound(loc, 'sound/weapons/thudswoosh.ogg', 50, TRUE, -1) - - discipline_slime(user) - - else - if(prob(30)) - buckled.visible_message(span_warning("[user] attempts to wrestle \the [name] off of [buckled]!"), \ - span_warning("[user] attempts to wrestle \the [name] off of you!")) - playsound(loc, 'sound/weapons/punchmiss.ogg', 25, TRUE, -1) - - else - buckled.visible_message(span_warning("[user] manages to wrestle \the [name] off of [buckled]!"), \ - span_notice("[user] manage to wrestle \the [name] off of you!")) - playsound(loc, 'sound/weapons/thudswoosh.ogg', 50, TRUE, -1) - - discipline_slime(user) - else - if(stat == DEAD && surgeries.len) - if(!user.combat_mode || LAZYACCESS(modifiers, RIGHT_CLICK)) - for(var/datum/surgery/operations as anything in surgeries) - if(operations.next_step(user, modifiers)) - return TRUE - if(..()) //successful attack - attacked_stacks += 10 - -/mob/living/simple_animal/slime/attack_alien(mob/living/carbon/alien/adult/user, list/modifiers) - if(..()) //if harm or disarm intent. - attacked_stacks += 10 - discipline_slime(user) - - -/mob/living/simple_animal/slime/attackby(obj/item/attacking_item, mob/living/user, params) - if(stat == DEAD && surgeries.len) - var/list/modifiers = params2list(params) - if(!user.combat_mode || (LAZYACCESS(modifiers, RIGHT_CLICK))) - for(var/datum/surgery/operations as anything in surgeries) - if(operations.next_step(user, modifiers)) - return TRUE - if(istype(attacking_item, /obj/item/stack/sheet/mineral/plasma) && !stat) //Lets you feed slimes plasma. - add_friendship(user, 1) - to_chat(user, span_notice("You feed the slime the plasma. It chirps happily.")) - var/obj/item/stack/sheet/mineral/plasma/sheet = attacking_item - sheet.use(1) - return - if(attacking_item.force > 0) - attacked_stacks += 10 - if(prob(25)) - user.do_attack_animation(src) - user.changeNext_move(CLICK_CD_MELEE) - to_chat(user, span_danger("[attacking_item] passes right through [src]!")) - return - if(discipline_stacks && prob(50)) // wow, buddy, why am I getting attacked?? - discipline_stacks = 0 - if(attacking_item.force >= 3) - var/force_effect = 2 * attacking_item.force - if(is_adult) - force_effect = round(attacking_item.force/2) - if(prob(10 + force_effect)) - discipline_slime(user) - - if(!istype(attacking_item, /obj/item/storage/bag/xeno)) - return ..() - - var/obj/item/storage/xeno_bag = attacking_item - if(!crossbreed_modification) - to_chat(user, span_warning("The slime is not currently being mutated.")) - return - var/has_output = FALSE //Have we outputted text? - var/has_found = FALSE //Have we found an extract to be added? - for(var/obj/item/slime_extract/extract in xeno_bag.contents) - if(extract.crossbreed_modification == crossbreed_modification) - xeno_bag.atom_storage.attempt_remove(extract, get_turf(src), silent = TRUE) - qdel(extract) - applied_crossbreed_amount++ - has_found = TRUE - if(applied_crossbreed_amount >= SLIME_EXTRACT_CROSSING_REQUIRED) - to_chat(user, span_notice("You feed the slime as many of the extracts from the bag as you can, and it mutates!")) - playsound(src, 'sound/effects/attackblob.ogg', 50, TRUE) - spawn_corecross() - has_output = TRUE - break - - if(has_output) - return - - if(!has_found) - to_chat(user, span_warning("There are no extracts in the bag that this slime will accept!")) - else - to_chat(user, span_notice("You feed the slime some extracts from the bag.")) - playsound(src, 'sound/effects/attackblob.ogg', 50, TRUE) - return - - -///Spawns a crossed slimecore item -/mob/living/simple_animal/slime/proc/spawn_corecross() - var/static/list/crossbreeds = subtypesof(/obj/item/slimecross) - visible_message(span_danger("[src] shudders, its mutated core consuming the rest of its body!")) - playsound(src, 'sound/magic/smoke.ogg', 50, TRUE) - var/selected_crossbreed_path - for(var/crossbreed_path in crossbreeds) - var/obj/item/slimecross/cross_item = crossbreed_path - if(initial(cross_item.colour) == slime_type.colour && initial(cross_item.effect) == crossbreed_modification) - selected_crossbreed_path = cross_item - break - if(selected_crossbreed_path) - new selected_crossbreed_path(loc) - else - visible_message(span_warning("The mutated core shudders, and collapses into a puddle, unable to maintain its form.")) - qdel(src) - -/mob/living/simple_animal/slime/proc/apply_water() - adjustBruteLoss(rand(15,20)) - if(client) - return - - if(Target) // Like cats - set_target(null) - ++discipline_stacks - return +/mob/living/simple_animal/slime/get_mob_buckling_height(mob/seat) + if(..()) + return 3 /mob/living/simple_animal/slime/examine(mob/user) . = list("This is [icon2html(src, user)] \a [src]!") @@ -466,6 +298,70 @@ . += "" +/mob/living/simple_animal/slime/proc/apply_water() + adjustBruteLoss(rand(15,20)) + if(client) + return + + if(Target) // Like cats + set_target(null) + ++discipline_stacks + return + +///Changes the slime's current life state +/mob/living/simple_animal/slime/proc/set_life_stage(new_life_stage = SLIME_LIFE_STAGE_BABY) + life_stage = new_life_stage + + switch(life_stage) + if(SLIME_LIFE_STAGE_BABY) + for(var/datum/action/innate/slime/reproduce/reproduce_action in actions) + reproduce_action.Remove(src) + + GRANT_ACTION(/datum/action/innate/slime/evolve) + + health = initial(health) + maxHealth = initial(maxHealth) + + obj_damage = initial(obj_damage) + melee_damage_lower = initial(melee_damage_lower) + melee_damage_upper = initial(melee_damage_upper) + wound_bonus = initial(wound_bonus) + + max_nutrition = initial(max_nutrition) + grow_nutrition = initial(grow_nutrition) + hunger_nutrition = initial(hunger_nutrition) + starve_nutrition = initial(starve_nutrition) + + if(SLIME_LIFE_STAGE_ADULT) + + for(var/datum/action/innate/slime/evolve/evolve_action in actions) + evolve_action.Remove(src) + + GRANT_ACTION(/datum/action/innate/slime/reproduce) + + health = 200 + maxHealth = 200 + + obj_damage = 15 + melee_damage_lower += 10 + melee_damage_upper += 10 + wound_bonus = -90 + + max_nutrition += 200 + grow_nutrition += 200 + hunger_nutrition += 100 + starve_nutrition += 100 + +///Sets the slime's type, name and its icons +/mob/living/simple_animal/slime/proc/set_slime_type(new_type) + slime_type = new new_type + update_name() + regenerate_icons() + +///randomizes the colour of a slime +/mob/living/simple_animal/slime/proc/random_colour() + set_slime_type(pick(subtypesof(/datum/slime_type))) + ///Makes a slime not attack people for a while /mob/living/simple_animal/slime/proc/discipline_slime(mob/user) if(stat) @@ -474,7 +370,7 @@ if(prob(80) && !client) discipline_stacks++ - if(!is_adult && discipline_stacks == 1) //if the slime is a baby and has not been overly disciplined, it will give up its grudge + if(life_stage == SLIME_LIFE_STAGE_BABY && discipline_stacks == 1) //if the slime is a baby and has not been overly disciplined, it will give up its grudge attacked_stacks = 0 set_target(null) @@ -494,66 +390,83 @@ if(user) step_away(src,user,15) -/mob/living/simple_animal/slime/get_mob_buckling_height(mob/seat) - if(..()) - return 3 +///Spawns a crossed slimecore item +/mob/living/simple_animal/slime/proc/spawn_corecross() + var/static/list/crossbreeds = subtypesof(/obj/item/slimecross) + visible_message(span_danger("[src] shudders, its mutated core consuming the rest of its body!")) + playsound(src, 'sound/magic/smoke.ogg', 50, TRUE) + var/selected_crossbreed_path + for(var/crossbreed_path in crossbreeds) + var/obj/item/slimecross/cross_item = crossbreed_path + if(initial(cross_item.colour) == slime_type.colour && initial(cross_item.effect) == crossbreed_modification) + selected_crossbreed_path = cross_item + break + if(selected_crossbreed_path) + new selected_crossbreed_path(loc) + else + visible_message(span_warning("The mutated core shudders, and collapses into a puddle, unable to maintain its form.")) + qdel(src) -///Sets the slime's current attack target -/mob/living/simple_animal/slime/proc/set_target(new_target) - var/old_target = Target - Target = new_target - if(old_target && !SLIME_CARES_ABOUT(old_target)) - UnregisterSignal(old_target, COMSIG_QDELETING) - if(Target) - RegisterSignal(Target, COMSIG_QDELETING, PROC_REF(clear_memories_of), override = TRUE) - -///Sets the person the slime is following around -/mob/living/simple_animal/slime/proc/set_leader(new_leader) - var/old_leader = Leader - Leader = new_leader - if(old_leader && !SLIME_CARES_ABOUT(old_leader)) - UnregisterSignal(old_leader, COMSIG_QDELETING) - if(Leader) - RegisterSignal(Leader, COMSIG_QDELETING, PROC_REF(clear_memories_of), override = TRUE) - -///Alters the friendship value of the target -/mob/living/simple_animal/slime/proc/add_friendship(new_friend, amount = 1) - if(!Friends[new_friend]) - Friends[new_friend] = 0 - Friends[new_friend] += amount - if(new_friend) - RegisterSignal(new_friend, COMSIG_QDELETING, PROC_REF(clear_memories_of), override = TRUE) - -///Sets the friendship value of the target -/mob/living/simple_animal/slime/proc/set_friendship(new_friend, amount = 1) - Friends[new_friend] = amount - if(new_friend) - RegisterSignal(new_friend, COMSIG_QDELETING, PROC_REF(clear_memories_of), override = TRUE) - -///Removes someone from the friendlist -/mob/living/simple_animal/slime/proc/remove_friend(friend) - Friends -= friend - if(friend && !SLIME_CARES_ABOUT(friend)) - UnregisterSignal(friend, COMSIG_QDELETING) - -///Adds someone to the friend list -/mob/living/simple_animal/slime/proc/set_friends(new_buds) - clear_friends() - for(var/mob/friend as anything in new_buds) - set_friendship(friend, new_buds[friend]) - -///Removes everyone from the friend list -/mob/living/simple_animal/slime/proc/clear_friends() - for(var/mob/friend as anything in Friends) - remove_friend(friend) - -///The passed source will be no longer be the slime's target, leader, or one of its friends -/mob/living/simple_animal/slime/proc/clear_memories_of(datum/source) +///Handles slime attacking restrictions, and any extra effects that would trigger +/mob/living/simple_animal/slime/proc/slime_pre_attack(mob/living/simple_animal/slime/our_slime, atom/target, proximity, modifiers) SIGNAL_HANDLER - if(source == Target) - set_target(null) - if(source == Leader) - set_leader(null) - remove_friend(source) + if(isAI(target)) //The aI is not tasty! + target.balloon_alert(our_slime, "not tasty!") + return COMPONENT_CANCEL_ATTACK_CHAIN -#undef SLIME_CARES_ABOUT + if(our_slime.buckled == target) //If you try to attack the creature you are latched on, you instead cancel feeding + our_slime.stop_feeding() + return COMPONENT_CANCEL_ATTACK_CHAIN + + if(iscyborg(target)) + var/mob/living/silicon/robot/borg_target = target + borg_target.flash_act() + do_sparks(5, TRUE, borg_target) + var/stunprob = our_slime.powerlevel * SLIME_SHOCK_PERCENTAGE_PER_LEVEL + SLIME_BASE_SHOCK_PERCENTAGE + if(prob(stunprob) && our_slime.powerlevel >= SLIME_EXTRA_SHOCK_COST) + our_slime.powerlevel = clamp(our_slime.powerlevel - SLIME_EXTRA_SHOCK_COST, SLIME_MIN_POWER, SLIME_MAX_POWER) + borg_target.apply_damage(our_slime.powerlevel * rand(6, 10), BRUTE, spread_damage = TRUE, wound_bonus = CANT_WOUND) + borg_target.visible_message(span_danger("The [our_slime.name] shocks [borg_target]!"), span_userdanger("The [our_slime.name] shocks you!")) + else + borg_target.visible_message(span_danger("The [our_slime.name] fails to hurt [borg_target]!"), span_userdanger("The [our_slime.name] failed to hurt you!")) + + return COMPONENT_CANCEL_ATTACK_CHAIN + + if(iscarbon(target) && our_slime.powerlevel > SLIME_MIN_POWER) + var/mob/living/carbon/carbon_target = target + var/stunprob = our_slime.powerlevel * SLIME_SHOCK_PERCENTAGE_PER_LEVEL + SLIME_BASE_SHOCK_PERCENTAGE // 17 at level 1, 80 at level 10 + if(!prob(stunprob)) + return NONE // normal attack + + carbon_target.visible_message(span_danger("The [our_slime.name] shocks [carbon_target]!"), span_userdanger("The [our_slime.name] shocks you!")) + + do_sparks(5, TRUE, carbon_target) + var/power = our_slime.powerlevel + rand(0,3) + carbon_target.Paralyze(power * 2 SECONDS) + carbon_target.set_stutter_if_lower(power * 2 SECONDS) + if (prob(stunprob) && our_slime.powerlevel >= SLIME_EXTRA_SHOCK_COST) + our_slime.powerlevel = clamp(our_slime.powerlevel - SLIME_EXTRA_SHOCK_COST, SLIME_MIN_POWER, SLIME_MAX_POWER) + carbon_target.apply_damage(our_slime.powerlevel * rand(6, 10), BURN, spread_damage = TRUE, wound_bonus = CANT_WOUND) + + if(isslime(target)) + if(target == our_slime) + return COMPONENT_CANCEL_ATTACK_CHAIN + var/mob/living/simple_animal/slime/target_slime = target + if(target_slime.buckled) + target_slime.stop_feeding(silent = TRUE) + visible_message(span_danger("[our_slime] pulls [target_slime] off!"), \ + span_danger("You pull [target_slime] off!")) + return NONE // normal attack + target_slime.attacked_stacks += 5 + var/is_adult_slime = our_slime.life_stage == SLIME_LIFE_STAGE_ADULT + if(target_slime.nutrition >= 100) //steal some nutrition. negval handled in life() + var/stolen_nutrition = is_adult_slime ? 90 : 50 + target_slime.adjust_nutrition(-stolen_nutrition) + our_slime.add_nutrition(stolen_nutrition) + if(target_slime.health > 0) + our_slime.adjustBruteLoss(is_adult_slime ? -20 : -10) + +#undef SLIME_EXTRA_SHOCK_COST +#undef SLIME_EXTRA_SHOCK_THRESHOLD +#undef SLIME_BASE_SHOCK_PERCENTAGE +#undef SLIME_SHOCK_PERCENTAGE_PER_LEVEL diff --git a/code/modules/research/xenobiology/crossbreeding/burning.dm b/code/modules/research/xenobiology/crossbreeding/burning.dm index 8d849c36569..23daa833838 100644 --- a/code/modules/research/xenobiology/crossbreeding/burning.dm +++ b/code/modules/research/xenobiology/crossbreeding/burning.dm @@ -36,7 +36,7 @@ Burning extracts: new_slime.visible_message(span_danger("A baby slime emerges from [src], and it nuzzles [user] before burbling hungrily!")) new_slime.set_friendship(user, 20) //Gas, gas, gas new_slime.bodytemperature = T0C + 400 //We gonna step on the gas. - new_slime.set_nutrition(new_slime.get_hunger_nutrition()) //Tonight, we fight! + new_slime.set_nutrition(new_slime.hunger_nutrition) //Tonight, we fight! ..() /obj/item/slimecross/burning/orange diff --git a/code/modules/research/xenobiology/xenobiology.dm b/code/modules/research/xenobiology/xenobiology.dm index 70be6275763..8ebfc5719f6 100644 --- a/code/modules/research/xenobiology/xenobiology.dm +++ b/code/modules/research/xenobiology/xenobiology.dm @@ -73,7 +73,7 @@ if(target_slime.stat) to_chat(user, span_warning("The slime is dead!")) return - if(!target_slime.is_adult) + if(target_slime.life_stage != SLIME_LIFE_STAGE_ADULT) to_chat(user, span_warning("The slime must be an adult to cross its core!")) return if(target_slime.crossbreed_modification && target_slime.crossbreed_modification != crossbreed_modification) @@ -804,22 +804,22 @@ icon = 'icons/obj/medical/chemical.dmi' icon_state = "potred" -/obj/item/slimepotion/slime/steroid/attack(mob/living/simple_animal/slime/M, mob/user) - if(!isslime(M))//If target is not a slime. +/obj/item/slimepotion/slime/steroid/attack(mob/living/simple_animal/slime/target, mob/user) + if(!isslime(target))//If target is not a slime. to_chat(user, span_warning("The steroid only works on baby slimes!")) return ..() - if(M.is_adult) //Can't steroidify adults + if(target.life_stage == SLIME_LIFE_STAGE_ADULT) //Can't steroidify adults to_chat(user, span_warning("Only baby slimes can use the steroid!")) return - if(M.stat) + if(target.stat) to_chat(user, span_warning("The slime is dead!")) return - if(M.cores >= 5) + if(target.cores >= 5) to_chat(user, span_warning("The slime already has the maximum amount of extract!")) return to_chat(user, span_notice("You feed the slime the steroid. It will now produce one more extract.")) - M.cores++ + target.cores++ qdel(src) /obj/item/slimepotion/enhancer diff --git a/tgstation.dme b/tgstation.dme index c50c3389ceb..7658cfb8564 100644 --- a/tgstation.dme +++ b/tgstation.dme @@ -4951,7 +4951,9 @@ #include "code\modules\mob\living\simple_animal\hostile\mining_mobs\elites\pandora.dm" #include "code\modules\mob\living\simple_animal\hostile\retaliate\goose.dm" #include "code\modules\mob\living\simple_animal\hostile\retaliate\retaliate.dm" +#include "code\modules\mob\living\simple_animal\slime\ai.dm" #include "code\modules\mob\living\simple_animal\slime\death.dm" +#include "code\modules\mob\living\simple_animal\slime\defense.dm" #include "code\modules\mob\living\simple_animal\slime\emote.dm" #include "code\modules\mob\living\simple_animal\slime\life.dm" #include "code\modules\mob\living\simple_animal\slime\powers.dm"