Files
Leland Kemble e5f29ab03c Fixes random slimes not being random & slimes not being revived with the correct icon (#95078)
## About The Pull Request

Random slimes didn't have their type randomized because the check in
`set_slime_type()` that causes randomization is checking for `null`, and
the way this was supposed to be activating was passing `null` through
the `/random` subtype's `Initialize()` arguments, but apparently even if
you pass `null` in the arguments explicitly, default values get
applied(in this case, grey slime). This has been resolved by switching
out `null` for a non-null define.

Also, slimes revived via aheals and probably other stuff would have
their icon reset to grey slime baby, regardless of their actual state,
because `icon_living` was not being set. `icon_living` is now being set
along with `icon_dead`.

## Why It's Good For The Game

fixes #95072
fixes fake grey slimes

## Changelog
🆑

fix: Sources of random slimes will now produce random slimes
fix: Slimes revived via magic will now continue to look as they should

/🆑
2026-02-03 22:15:37 -05:00

408 lines
14 KiB
Plaintext

#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/basic/slime
name = "grey baby slime (123)"
icon = 'icons/mob/simple/slimes.dmi'
icon_state = "grey-baby"
pass_flags = PASSTABLE | PASSGRILLE
gender = NEUTER
faction = list(FACTION_SLIME, FACTION_NEUTRAL)
icon_living = "grey-baby"
icon_dead = "grey-baby-dead"
attack_sound = 'sound/items/weapons/bite.ogg'
//Base physiology
maxHealth = 150
health = 150
mob_biotypes = MOB_SLIME
melee_damage_lower = 7
melee_damage_upper = 17
wound_bonus = -45
can_buckle_to = FALSE
damage_coeff = list(BRUTE = 1, BURN = -1, TOX = 1, STAMINA = 1, OXY = 1) //Healed by fire
unsuitable_cold_damage = 15
unsuitable_heat_damage = 0
maximum_survivable_temperature = INFINITY
habitable_atmos = null
//Messages
attack_verb_simple = "glomp"
attack_verb_continuous = "glomps"
response_help_continuous = "pets"
response_help_simple = "pet"
response_disarm_continuous = "shoos"
response_disarm_simple = "shoo"
response_harm_continuous = "stomps on"
response_harm_simple = "stomp on"
//Speech
speak_emote = list("blorbles")
bubble_icon = "slime"
initial_language_holder = /datum/language_holder/slime
verb_say = "blorbles"
verb_ask = "inquisitively blorbles"
verb_exclaim = "loudly blorbles"
verb_yell = "loudly blorbles"
//AI controller
ai_controller = /datum/ai_controller/basic_controller/slime
//Slime physiology
///What is our current lifestage?
var/life_stage = SLIME_LIFE_STAGE_BABY
///Our slime's current mood
var/current_mood = SLIME_MOOD_NONE
///If the slime is currently overcrowded and unable to reproduce.
var/overcrowded = FALSE
///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 = SLIME_MIN_POWER
///Controls how long the slime has been overfed, if 10, grows or reproduces
var/amount_grown = 0
/// No hunger
var/hunger_disabled = FALSE
///Has a mutator been used on the slime? Only one is allowed
var/mutator_used = FALSE
// The datum that handles the slime colour's core and possible mutations
var/datum/slime_type/slime_type = null
// After a slime is initialized, a list of all possible initialized datums of slime_types
var/static/list/possible_slime_types = null
//CORE-CROSSING CODE
///What cross core modification is being used.
var/crossbreed_modification
///How many extracts of the modtype have been applied.
var/applied_crossbreed_amount = 0
//AI related traits
/// Instructions you can give to slimes
var/static/list/pet_commands = list(
/datum/pet_command/idle,
/datum/pet_command/free,
/datum/pet_command/follow,
/datum/pet_command/attack/slime,
)
/// Our evolve action
var/datum/action/innate/slime/evolve/evolve_action
/// Our reproduction action
var/datum/action/innate/slime/reproduce/reproduce_action
/mob/living/basic/slime/Initialize(mapload, new_type = /datum/slime_type/grey, new_life_stage = SLIME_LIFE_STAGE_BABY)
. = ..()
evolve_action = new (src)
evolve_action.Grant(src)
reproduce_action = new (src)
reproduce_action.Grant(src)
if(isnull(possible_slime_types))
possible_slime_types = list()
for(var/datum/slime_type/slime_type as anything in subtypesof(/datum/slime_type))
possible_slime_types[slime_type] = new slime_type
set_life_stage(new_life_stage, TRUE)
set_slime_type(new_type)
set_nutrition(SLIME_STARTING_NUTRITION)
AddComponent(/datum/component/health_scaling_effects, min_health_slowdown = 2)
AddComponent(/datum/component/obeys_commands, pet_commands)
AddElement(/datum/element/ai_retaliate)
AddElement(/datum/element/basic_health_examine, light_damage_message = "It has some punctures in its flesh!", heavy_damage_message = span_bold("It has severe punctures and tears in its flesh!"), heavy_threshold = 0.4)
AddElement(/datum/element/footstep, footstep_type = FOOTSTEP_MOB_SLIME)
AddElement(/datum/element/soft_landing)
AddElement(/datum/element/swabable, CELL_LINE_TABLE_SLIME, CELL_VIRUS_TABLE_GENERIC_MOB, 1, 5)
add_traits(list(TRAIT_CANT_RIDE, TRAIT_CAN_MOUNT_HUMANS, TRAIT_VENTCRAWLER_ALWAYS), INNATE_TRAIT)
RegisterSignal(src, COMSIG_HOSTILE_PRE_ATTACKINGTARGET, PROC_REF(on_slime_pre_attack))
RegisterSignal(src, COMSIG_ATOM_ATTACK_HAND, PROC_REF(on_attack_hand) )
ai_controller.set_blackboard_key(BB_SLIME_EVOLVE, evolve_action)
ai_controller.set_blackboard_key(BB_SLIME_REPRODUCE, reproduce_action)
/mob/living/basic/slime/Destroy()
QDEL_NULL(evolve_action)
QDEL_NULL(reproduce_action)
slime_type = null
return ..()
///Random slime subtype
/mob/living/basic/slime/random
/mob/living/basic/slime/random/Initialize(mapload, new_colour, new_life_stage)
return ..(mapload, SLIME_TYPE_RANDOM, prob(50) ? SLIME_LIFE_STAGE_ADULT : SLIME_LIFE_STAGE_BABY)
///Friendly docile subtype
/mob/living/basic/slime/pet
hunger_disabled = TRUE
/mob/living/basic/slime/pet/Initialize(mapload, new_colour, new_life_stage)
. = ..()
set_pacified_behaviour()
//Hilbert subtype
/mob/living/basic/slime/hilbert
/mob/living/basic/slime/hilbert/Initialize(mapload, new_colour, new_life_stage)
. = ..(mapload, /datum/slime_type/bluespace)
ai_controller?.set_blackboard_key(BB_SLIME_RABID, TRUE)
/mob/living/basic/slime/adjust_nutrition(change, forced)
. = ..()
nutrition = min(nutrition, SLIME_MAX_NUTRITION)
/mob/living/basic/slime/set_nutrition(set_to, forced = FALSE)
. = ..()
nutrition = min(nutrition, SLIME_MAX_NUTRITION)
/mob/living/basic/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))
var/slime_id = rand(1, 1000)
name = "[slime_type.colour] [life_stage] slime ([slime_id])"
real_name = name
return ..()
/mob/living/basic/slime/regenerate_icons()
cut_overlays()
if(slime_type.transparent)
alpha = SLIME_TRANSPARENCY_ALPHA
icon_living = "[slime_type.colour]-[life_stage]"
icon_dead = !cores ? "[slime_type.colour]-cut" : "[slime_type.colour]-[life_stage]-dead"
if(stat != DEAD)
icon_state = icon_living
if(current_mood && current_mood != SLIME_MOOD_NONE && !stat)
add_overlay("aslime-[current_mood]")
else
icon_state = icon_dead
return ..()
/mob/living/basic/slime/get_status_tab_items()
. = ..()
if(!hunger_disabled)
. += "Nutrition: [nutrition]/[SLIME_MAX_NUTRITION]"
. += "Growth: [amount_grown]/[SLIME_EVOLUTION_THRESHOLD]"
. += "Power Level: [powerlevel]/[SLIME_MAX_POWER]"
/mob/living/basic/slime/mouse_drop_dragged(atom/target_atom, mob/user)
if(isliving(target_atom) && target_atom != src && user == src)
var/mob/living/food = target_atom
if(can_feed_on(food))
start_feeding(food)
///Slimes can hop off mobs they have latched onto
/mob/living/basic/slime/resist_buckle()
if(isliving(buckled))
buckled.unbuckle_mob(src,force=TRUE)
//slimes can not pull
/mob/living/basic/slime/start_pulling(atom/movable/moveable_atom, state, force = move_force, supress_message = FALSE)
return
/mob/living/basic/slime/examine(mob/user)
. = ..()
switch(powerlevel)
if(SLIME_MIN_POWER to SLIME_EXTRA_SHOCK_COST)
. += "It is flickering gently with harmless levels of electrical activity."
if(SLIME_EXTRA_SHOCK_COST to SLIME_MEDIUM_POWER)
. += "It is glowing brightly with medium levels electrical activity."
if(SLIME_MEDIUM_POWER to SLIME_MAX_POWER)
. += "It is glowing alarmingly with high levels of electrical activity."
if(SLIME_MAX_POWER)
. += span_boldwarning("It is radiating with massive levels of electrical activity!")
if(overcrowded)
. += span_warning("It seems too overcroweded to properly reproduce!")
///Changes the slime's current life state
/mob/living/basic/slime/proc/set_life_stage(new_life_stage = SLIME_LIFE_STAGE_BABY, initial = FALSE)
life_stage = new_life_stage
if(life_stage == SLIME_LIFE_STAGE_ADULT)
health /= 0.75
maxHealth /= 0.75
melee_damage_lower *= 2
melee_damage_upper *= 2
obj_damage = 15
wound_bonus = -90
else if(!initial)
health *= 0.75
maxHealth *= 0.75
melee_damage_lower *= 0.5
melee_damage_upper *= 0.5
obj_damage = initial(obj_damage)
wound_bonus = initial(wound_bonus)
ai_controller.set_blackboard_key(BB_SLIME_LIFE_STAGE, life_stage)
update_mob_action_buttons()
/// Sets the slime's type, name and its icons.
/// If not provided with a type it will instead be random
/mob/living/basic/slime/proc/set_slime_type(new_type = SLIME_TYPE_RANDOM)
if(new_type == SLIME_TYPE_RANDOM)
new_type = pick(subtypesof(/datum/slime_type))
slime_type = possible_slime_types[new_type]
update_name()
regenerate_icons()
///Handles slime attacking restrictions, and any extra effects that would trigger
/mob/living/basic/slime/proc/on_slime_pre_attack(mob/living/basic/slime/our_slime, atom/target, proximity, modifiers)
SIGNAL_HANDLER
if(LAZYACCESS(modifiers, RIGHT_CLICK) && isliving(target) && target != src)
if(our_slime.can_feed_on(target))
our_slime.start_feeding(target)
return COMPONENT_HOSTILE_NO_ATTACK
if(isAI(target)) //The aI is not tasty!
target.balloon_alert(our_slime, "not tasty!")
return COMPONENT_HOSTILE_NO_ATTACK
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_HOSTILE_NO_ATTACK
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] shocks [borg_target]!"), span_userdanger("\The [our_slime] shocks you!"))
else
borg_target.visible_message(span_danger("\The [our_slime] fails to hurt [borg_target]!"), span_userdanger("\The [our_slime] failed to hurt you!"))
return COMPONENT_HOSTILE_NO_ATTACK
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] shocks [carbon_target]!"), span_userdanger("\The [our_slime] shocks you!"))
do_sparks(5, TRUE, carbon_target)
var/power = our_slime.powerlevel + rand(0,3)
carbon_target.Paralyze(2 SECONDS)
carbon_target.Knockdown(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_HOSTILE_NO_ATTACK
var/mob/living/basic/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
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 = min(is_adult_slime ? 90 : 50, target_slime.nutrition)
target_slime.adjust_nutrition(-stolen_nutrition)
our_slime.adjust_nutrition(stolen_nutrition)
if(target_slime.health > 0)
our_slime.adjust_brute_loss(is_adult_slime ? -20 : -10)
///Spawns a crossed slimecore item
/mob/living/basic/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/effects/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)
///Proc for slime core removal surgery, tries to remove cores from a dead slime.
/mob/living/basic/slime/proc/try_extract_cores(count = 1)
if(stat != DEAD)
return FALSE
if(count <= 0 || cores < count)
return FALSE
var/core_count = min(count, cores)
for(var/i in 1 to core_count)
new slime_type.core_type(loc)
cores--
regenerate_icons()
return TRUE
///Makes the slime peaceful and content
/mob/living/basic/slime/proc/set_pacified_behaviour()
hunger_disabled = TRUE
ai_controller?.set_blackboard_key(BB_SLIME_RABID, FALSE)
ai_controller?.set_blackboard_key(BB_SLIME_HUNGER_DISABLED, TRUE)
set_nutrition(SLIME_STARTING_NUTRITION)
///Makes the slime angry and hungry
/mob/living/basic/slime/proc/set_enraged_behaviour()
hunger_disabled = FALSE
ai_controller?.set_blackboard_key(BB_SLIME_HUNGER_DISABLED, FALSE)
ai_controller?.set_blackboard_key(BB_SLIME_RABID, TRUE)
///Makes the slime hungry but mostly friendly
/mob/living/basic/slime/proc/set_default_behaviour()
hunger_disabled = FALSE
ai_controller?.set_blackboard_key(BB_SLIME_HUNGER_DISABLED, FALSE)
ai_controller?.set_blackboard_key(BB_SLIME_RABID, FALSE)
#undef SLIME_EXTRA_SHOCK_COST
#undef SLIME_EXTRA_SHOCK_THRESHOLD
#undef SLIME_BASE_SHOCK_PERCENTAGE
#undef SLIME_SHOCK_PERCENTAGE_PER_LEVEL