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"