Files
Bubberstation/code/modules/mob/transform_procs.dm
Profakos b20c982404 Converts slimes to basic mobs (#82176)
## About The Pull Request

After months of preparation, and further months of work, I am finally
done. Please bear with me, as this is a massive refactor, but I have
already atomized everything I could. This is now ready for review.

General

- 	The hilbert hotel slimes are now a subtype instead of a varedit.
- The `use_mob_ability` subtree now also accepts non cooldown abilities.
If set_behaviours is set up properly, mobs won't keep continously
triggering it as if it were a 0 second cooldown action. The alternative
would have been turning the slime abilities into cooldown abilities.
- Wrestling off a slime now signs up to the `COMSIG_ATOM_ATTACK_HAND`
signal, instead of being part of attack_hand.
- Adds datum/ai_controller/controller as a fourth, optional argument to
`/datum/ai_behavior/find_hunt_target/valid_dinner()` to make it possible
to access blackboard keys.
- Slimes no longer attack windows if they would accidentally move into
them (when the conditions are met), since random walk behaviour ignores
tiles they can't go in. It was also not worth to keep. Did you know this
was the sole override of `ObjBump()`?
- Examine was made less snowflaky/bespoke. Also added a new element:
`/datum/element/basic_health_examine`, which is a simple bespoke element
that prints out a custom message based on how damaged the basic mob it
is attached to is.
- Slimes only perform knockdown instead of paralysis, as they can attack
more often now, and paralysis is not that fun.
- LAssailant has been removed due being archaic code. To befriend a
slime, you have to spawn a monkey with the slime console, or feed them a
sheet of plasma. Simple grabbing the monkey or stuffing them in
disposals do not work anymore. Slime console spawned monkeys will have a
visible status effect, with pheromones coming off them to make this
clearer.

Actions
- 	Feeding, reproduction and evolution is no longer a verb.
- Slime feeding is no longer an action button. You have to use right
click, or as previously, mousedrop. Slimes can always unbuckle from mobs
they are attached to.

Hunger 

- Instead randomly changing the starvation and max nutrition values
while growing up, evolution costs 200 nutrion. This makes the code more
readable, and behaviour more predictable, while still giving the
intended time between evolving and splitting. As a result, I could also
turn these into defines.
- Added a component that handles doing an effect over time while buckled
to a mob, until the mob dies or you get unbuckled.
- Slimes gained nutrition is no longer randomly multiplied by the damage
config value, but rather gain nutrition equal to twice the damage dealt.
You'll have to eat one monkey to evolve, just as before.
- Slimes do not heal passively. They only heal from eating. It was a
rather miniscule value that did not have much effect.
- Slimes generate electricity from hunger threshold, instead of the
random amount of hunger threshold + 100.

Environment

- Slimes take 15 damage from cold every second, instead of using a
complex formula (that also decreased the damage up to a point?).
- Slimes still heal from burn damage, but this is now set on the damage
coefficient list.
- Slimes instead of getting stunned by the cold, freeze in an ice cube.
BZ instead of setting them unconscious, calls the stasis status effect,
allowing you to safely stash your hungry slimes for later. They also no
longer slow down from the cold, as they are already slowed down by the
damage they get. Conversely they no longer get a speed up from a random
amount of temperature. I could be convinced to readd this either as part
of the basic sensitive component, or a similar one.

AI
- Removed the attacked_stacks system. Slimes will just perform regular
retaliation if you hit them in a harmful manner.
- Slimes now use the pet orders component. They will interrupt their
feeding when given a command by their master.
- Slimes have their own subtrees. I tried to replicate as much as I
could from the old code, dividing ancient code artifacts and intentional
stuff, so there might be some weirdness.
- Slime speech has been almost fully reduced to basic blorbing, as you
can not even understand them anymore, and most of them require the slime
to loop through all of their surroundings.
- Discipline does not have stacks either. Disciplined baby slimes have a
chance to clear their attack and hunt blackboard keys. All slimes will
stop feeding on the target otherwise.
- Since discipline is not a stack, rabidity instead gets removed at a
10% chance per disciplining.
- 	Slimes faces are a bit more randomly picked now.

## Why It's Good For The Game

- We want to convert all simple animals to basic mobs. Old slime code
was also very strange, and had some systems that have been replicated by
components.
- Slimes fully paralyzing you is not fun at all. Knockdown should give
you a fighting chance when a slime would like to eat you.
- Slimes slow down from the heavy damage they get from the cold, so I
don't think they need extra slowdown, nor do they need to speed up from
warmth, as they are already fast.
- Slimes turning into an icecube instead of becoming paralyzed from the
cold is more fun for the slimes, as they can break out for a few
moments. It is also funny.
- Slimes entering proper stasis from BZ is not just a visual indicator
of a slime that is safe to approach, but also keeps the slimes's hunger
value in check, allowing it to not starve while stopped. They can also
look around and blorble, instead of staring at a black screen, if player
controlled.
- The attack_stack and discipline_stack behaviours were rather
overcomplicated, and the xenobio mains I talked with didn't even know it
was a thing, so I argue it needed simplification.
- The bespoke friendship system of slimes was also too complicated.
Slimes slowly gained levels of trust, and at certain levels commands
costed friendship, and other levels, they did not. The binary friend/not
friend system that everything else in the game uses is much more
sensible.
- Using right click for feeding is much more sensible than using an
action, and then picking someone from a dropdown.
- Slime speech was very soulful but not only did it loop through
everything in sight, you couldn't even understand it unless you spoke
slime. Maybe it can be readded later in a different form.
- Slime's passive healing was miniscule, and having them rely on feeding
is more interesting.

also
fixes #81463

## Changelog

🆑
refactor: Slimes are now basic mobs. Please report any strange
behaviours!
balance: Slimes only stun you for two seconds when they shock you, the
rest of the duration is a knockdown.
balance: Slimes are not stunned from the cold, but rather, get frozen in
a freon icecube. BZ also puts them in complete stasis, instead of making
them unconscious. Their speed is likewise unchanged by temperatures.
balance: Slimes do not passively heal, they instead rely on feeding.
fix: Slimes can use the buckling screen alert to unbuckle and stop
feeding, along with clicking on the mob they are riding
/🆑
2024-03-27 16:40:52 -06:00

404 lines
12 KiB
Plaintext

#define TRANSFORMATION_DURATION 22
/// Will be removed once the transformation is complete.
#define TEMPORARY_TRANSFORMATION_TRAIT "temporary_transformation"
/// Considered "permanent" since we'll be deleting the old mob and the client will be inserted into a new one (without this trait)
#define PERMANENT_TRANSFORMATION_TRAIT "permanent_transformation"
/mob/living/carbon/proc/monkeyize(instant = FALSE)
if (transformation_timer || HAS_TRAIT(src, TRAIT_NO_TRANSFORM))
return
if(ismonkey(src))
return
if(instant)
finish_monkeyize()
return
//Make mob invisible and spawn animation
ADD_TRAIT(src, TRAIT_NO_TRANSFORM, TEMPORARY_TRANSFORMATION_TRAIT)
Paralyze(TRANSFORMATION_DURATION, ignore_canstun = TRUE)
icon = null
cut_overlays()
var/obj/effect = new /obj/effect/temp_visual/monkeyify(loc)
effect.SetInvisibility(invisibility)
SetInvisibility(INVISIBILITY_MAXIMUM, id=type)
transformation_timer = addtimer(CALLBACK(src, PROC_REF(finish_monkeyize)), TRANSFORMATION_DURATION, TIMER_UNIQUE)
/mob/living/carbon/proc/finish_monkeyize()
transformation_timer = null
to_chat(src, span_boldnotice("You are now a monkey."))
REMOVE_TRAIT(src, TRAIT_NO_TRANSFORM, TEMPORARY_TRANSFORMATION_TRAIT)
icon = initial(icon)
RemoveInvisibility(type)
set_species(/datum/species/monkey)
name = "monkey"
set_name()
SEND_SIGNAL(src, COMSIG_HUMAN_MONKEYIZE)
uncuff()
return src
////////////////////////// Humanize //////////////////////////////
//Could probably be merged with monkeyize but other transformations got their own procs, too
/mob/living/carbon/proc/humanize(species = /datum/species/human, instant = FALSE)
if (transformation_timer || HAS_TRAIT(src, TRAIT_NO_TRANSFORM))
return
if(!ismonkey(src))
return
if(instant)
finish_humanize(species)
return
//Make mob invisible and spawn animation
ADD_TRAIT(src, TRAIT_NO_TRANSFORM, TEMPORARY_TRANSFORMATION_TRAIT)
Paralyze(TRANSFORMATION_DURATION, ignore_canstun = TRUE)
icon = null
cut_overlays()
var/obj/effect = new /obj/effect/temp_visual/monkeyify/humanify(loc)
effect.SetInvisibility(invisibility)
SetInvisibility(INVISIBILITY_MAXIMUM, id=type)
transformation_timer = addtimer(CALLBACK(src, PROC_REF(finish_humanize), species), TRANSFORMATION_DURATION, TIMER_UNIQUE)
/mob/living/carbon/proc/finish_humanize(species = /datum/species/human)
transformation_timer = null
to_chat(src, span_boldnotice("You are now a human."))
REMOVE_TRAIT(src, TRAIT_NO_TRANSFORM, TEMPORARY_TRANSFORMATION_TRAIT)
icon = initial(icon)
RemoveInvisibility(type)
set_species(species)
SEND_SIGNAL(src, COMSIG_MONKEY_HUMANIZE)
return src
/mob/proc/AIize(client/preference_source, move = TRUE)
var/list/turf/landmark_loc = list()
if(!move)
landmark_loc += loc
else
for(var/obj/effect/landmark/start/ai/sloc in GLOB.landmarks_list)
if(locate(/mob/living/silicon/ai) in sloc.loc)
continue
if(sloc.primary_ai)
LAZYCLEARLIST(landmark_loc)
landmark_loc += sloc.loc
break
landmark_loc += sloc.loc
if(!length(landmark_loc))
to_chat(src, "Oh god sorry we can't find an unoccupied AI spawn location, so we're spawning you on top of someone.")
for(var/obj/effect/landmark/start/ai/sloc in GLOB.landmarks_list)
landmark_loc += sloc.loc
if(!length(landmark_loc))
message_admins("Could not find ai landmark for [src]. Yell at a mapper! We are spawning them at their current location.")
landmark_loc += loc
if(client)
stop_sound_channel(CHANNEL_LOBBYMUSIC)
var/mob/living/silicon/ai/our_AI = new /mob/living/silicon/ai(pick(landmark_loc), null, src)
. = our_AI
if(preference_source)
apply_pref_name(/datum/preference/name/ai, preference_source)
our_AI.apply_pref_hologram_display(preference_source)
our_AI.set_core_display_icon(null, preference_source)
qdel(src)
/mob/living/carbon/AIize(client/preference_source, transfer_after = TRUE)
if(HAS_TRAIT(src, TRAIT_NO_TRANSFORM))
return
ADD_TRAIT(src, TRAIT_NO_TRANSFORM, PERMANENT_TRANSFORMATION_TRAIT)
Paralyze(1, ignore_canstun = TRUE)
for(var/obj/item/W in src)
dropItemToGround(W)
regenerate_icons()
icon = null
SetInvisibility(INVISIBILITY_MAXIMUM)
return ..()
/mob/living/carbon/human/AIize(client/preference_source, transfer_after = TRUE)
if(HAS_TRAIT(src, TRAIT_NO_TRANSFORM))
return
return ..()
/mob/proc/Robotize(delete_items = 0, transfer_after = TRUE)
if(HAS_TRAIT(src, TRAIT_NO_TRANSFORM))
return
ADD_TRAIT(src, TRAIT_NO_TRANSFORM, PERMANENT_TRANSFORMATION_TRAIT)
var/mob/living/silicon/robot/new_borg = new /mob/living/silicon/robot(loc)
new_borg.gender = gender
new_borg.SetInvisibility(INVISIBILITY_NONE)
if(client)
new_borg.updatename(client)
if(mind) //TODO //TODO WHAT
if(!transfer_after)
mind.active = FALSE
mind.transfer_to(new_borg)
else if(transfer_after)
new_borg.key = key
if(new_borg.mmi)
new_borg.mmi.name = "[initial(new_borg.mmi.name)]: [real_name]"
if(new_borg.mmi.brain)
new_borg.mmi.brain.name = "[real_name]'s brain"
if(new_borg.mmi.brainmob)
new_borg.mmi.brainmob.real_name = real_name //the name of the brain inside the cyborg is the robotized human's name.
new_borg.mmi.brainmob.name = real_name
new_borg.job = JOB_CYBORG
new_borg.notify_ai(AI_NOTIFICATION_NEW_BORG)
. = new_borg
if(new_borg.ckey && is_banned_from(new_borg.ckey, JOB_CYBORG))
INVOKE_ASYNC(new_borg, TYPE_PROC_REF(/mob/living/silicon/robot, replace_banned_cyborg))
qdel(src)
/mob/living/Robotize(delete_items = 0, transfer_after = TRUE)
if(HAS_TRAIT(src, TRAIT_NO_TRANSFORM))
return
ADD_TRAIT(src, TRAIT_NO_TRANSFORM, TEMPORARY_TRANSFORMATION_TRAIT)
Paralyze(1, ignore_canstun = TRUE)
drop_everything(delete_items)
regenerate_icons()
icon = null
SetInvisibility(INVISIBILITY_MAXIMUM)
REMOVE_TRAIT(src, TRAIT_NO_TRANSFORM, TEMPORARY_TRANSFORMATION_TRAIT)
return ..()
/mob/living/silicon/robot/proc/replace_banned_cyborg()
to_chat(src, "<b>You are job banned from cyborg! Appeal your job ban if you want to avoid this in the future!</b>")
ghostize(FALSE)
var/mob/chosen_one = SSpolling.poll_ghosts_for_target("Do you want to play as [span_notice(name)]?", check_jobban = JOB_CYBORG, poll_time = 5 SECONDS, checked_target = src, alert_pic = src, role_name_text = "cyborg")
if(chosen_one)
message_admins("[key_name_admin(chosen_one)] has taken control of ([key_name_admin(src)]) to replace a jobbanned player.")
key = chosen_one.key
//human -> alien
/mob/living/carbon/human/proc/Alienize()
if(HAS_TRAIT(src, TRAIT_NO_TRANSFORM))
return
ADD_TRAIT(src, TRAIT_NO_TRANSFORM, PERMANENT_TRANSFORMATION_TRAIT)
add_traits(list(TRAIT_IMMOBILIZED, TRAIT_HANDS_BLOCKED), TRAIT_GENERIC)
for(var/obj/item/W in src)
dropItemToGround(W)
regenerate_icons()
icon = null
SetInvisibility(INVISIBILITY_MAXIMUM)
for(var/t in bodyparts)
qdel(t)
var/alien_caste = pick("Hunter","Sentinel","Drone")
var/mob/living/carbon/alien/adult/new_xeno
switch(alien_caste)
if("Hunter")
new_xeno = new /mob/living/carbon/alien/adult/hunter(loc)
if("Sentinel")
new_xeno = new /mob/living/carbon/alien/adult/sentinel(loc)
if("Drone")
new_xeno = new /mob/living/carbon/alien/adult/drone(loc)
new_xeno.set_combat_mode(TRUE)
new_xeno.key = key
to_chat(new_xeno, span_boldnotice("You are now an alien."))
qdel(src)
return new_xeno
/mob/living/carbon/human/proc/slimeize(reproduce as num)
if(HAS_TRAIT(src, TRAIT_NO_TRANSFORM))
return
ADD_TRAIT(src, TRAIT_NO_TRANSFORM, PERMANENT_TRANSFORMATION_TRAIT)
add_traits(list(TRAIT_IMMOBILIZED, TRAIT_HANDS_BLOCKED), TRAIT_GENERIC)
for(var/obj/item/W in src)
dropItemToGround(W)
regenerate_icons()
icon = null
SetInvisibility(INVISIBILITY_MAXIMUM)
for(var/t in bodyparts)
qdel(t)
var/mob/living/basic/slime/new_slime
if(reproduce)
var/number = pick(14;2,3,4) //reproduce (has a small chance of producing 3 or 4 offspring)
var/list/babies = list()
for(var/i in 1 to number)
var/mob/living/basic/slime/M = new/mob/living/basic/slime(loc)
M.set_nutrition(round(nutrition/number))
step_away(M,src)
babies += M
new_slime = pick(babies)
else
new_slime = new /mob/living/basic/slime(loc)
new_slime.set_combat_mode(TRUE)
new_slime.key = key
to_chat(new_slime, span_boldnotice("You are now a slime. Skreee!"))
qdel(src)
return new_slime
/mob/proc/become_overmind(starting_points = OVERMIND_STARTING_POINTS)
var/mob/camera/blob/B = new /mob/camera/blob(get_turf(src), starting_points)
B.key = key
. = B
qdel(src)
/mob/living/carbon/human/proc/corgize()
if(HAS_TRAIT(src, TRAIT_NO_TRANSFORM))
return
ADD_TRAIT(src, TRAIT_NO_TRANSFORM, PERMANENT_TRANSFORMATION_TRAIT)
Paralyze(1, ignore_canstun = TRUE)
for(var/obj/item/W in src)
dropItemToGround(W)
regenerate_icons()
icon = null
SetInvisibility(INVISIBILITY_MAXIMUM)
for(var/t in bodyparts) //this really should not be necessary
qdel(t)
var/mob/living/basic/pet/dog/corgi/new_corgi = new /mob/living/basic/pet/dog/corgi (loc)
new_corgi.set_combat_mode(TRUE)
new_corgi.key = key
to_chat(new_corgi, span_boldnotice("You are now a Corgi. Yap Yap!"))
qdel(src)
return new_corgi
/mob/living/carbon/proc/gorillize()
if(HAS_TRAIT(src, TRAIT_NO_TRANSFORM))
return
ADD_TRAIT(src, TRAIT_NO_TRANSFORM, PERMANENT_TRANSFORMATION_TRAIT)
Paralyze(1, ignore_canstun = TRUE)
SSblackbox.record_feedback("amount", "gorillas_created", 1)
var/Itemlist = get_equipped_items(include_pockets = TRUE)
Itemlist += held_items
for(var/obj/item/W in Itemlist)
dropItemToGround(W, TRUE)
regenerate_icons()
icon = null
SetInvisibility(INVISIBILITY_MAXIMUM)
var/mob/living/basic/gorilla/new_gorilla = new (get_turf(src))
new_gorilla.set_combat_mode(TRUE)
if(mind)
mind.transfer_to(new_gorilla)
else
new_gorilla.key = key
to_chat(new_gorilla, span_boldnotice("You are now a gorilla. Ooga ooga!"))
qdel(src)
return new_gorilla
/mob/living/carbon/human/Animalize()
var/list/mobtypes = typesof(/mob/living/simple_animal) + typesof(/mob/living/basic)
var/mobpath = tgui_input_list(usr, "Which type of mob should [src] turn into?", "Choose a type", sort_list(mobtypes, GLOBAL_PROC_REF(cmp_typepaths_asc)))
if(isnull(mobpath))
return
if(!safe_animal(mobpath))
to_chat(usr, span_danger("Sorry but this mob type is currently unavailable."))
return
if(HAS_TRAIT(src, TRAIT_NO_TRANSFORM))
return
ADD_TRAIT(src, TRAIT_NO_TRANSFORM, PERMANENT_TRANSFORMATION_TRAIT)
Paralyze(1, ignore_canstun = TRUE)
for(var/obj/item/W in src)
dropItemToGround(W)
regenerate_icons()
icon = null
SetInvisibility(INVISIBILITY_MAXIMUM)
for(var/t in bodyparts)
qdel(t)
var/mob/living/new_mob = new mobpath(src.loc)
new_mob.key = key
new_mob.set_combat_mode(TRUE)
to_chat(new_mob, span_boldnotice("You suddenly feel more... animalistic."))
qdel(src)
return new_mob
/mob/proc/Animalize()
var/list/mobtypes = typesof(/mob/living/simple_animal) + typesof(/mob/living/basic)
var/mobpath = tgui_input_list(usr, "Which type of mob should [src] turn into?", "Choose a type", sort_list(mobtypes, GLOBAL_PROC_REF(cmp_typepaths_asc)))
if(isnull(mobpath))
return
if(!safe_animal(mobpath))
to_chat(usr, span_danger("Sorry but this mob type is currently unavailable."))
return
var/mob/living/new_mob = new mobpath(src.loc)
new_mob.key = key
new_mob.set_combat_mode(TRUE)
to_chat(new_mob, span_boldnotice("You feel more... animalistic."))
. = new_mob
qdel(src)
/* Certain mob types have problems and should not be allowed to be controlled by players.
*
* This proc is here to force coders to manually place their mob in this list, hopefully tested.
* This also gives a place to explain -why- players shouldn't be turn into certain mobs and hopefully someone can fix them.
*/
/mob/proc/safe_animal(MP)
//Bad mobs! - Remember to add a comment explaining what's wrong with the mob
if(!MP)
return FALSE //Sanity, this should never happen.
if(ispath(MP, /mob/living/basic/construct))
return FALSE //Verbs do not appear for players.
//Good mobs!
if(ispath(MP, /mob/living/basic/pet/cat))
return TRUE
if(ispath(MP, /mob/living/basic/pet/dog/corgi))
return TRUE
if(ispath(MP, /mob/living/basic/crab))
return TRUE
if(ispath(MP, /mob/living/basic/carp))
return TRUE
if(ispath(MP, /mob/living/basic/mushroom))
return TRUE
if(ispath(MP, /mob/living/basic/shade))
return TRUE
if(ispath(MP, /mob/living/basic/killer_tomato))
return TRUE
if(ispath(MP, /mob/living/basic/mouse))
return TRUE
if(ispath(MP, /mob/living/basic/bear))
return TRUE
if(ispath(MP, /mob/living/basic/parrot))
return TRUE //Parrots are no longer unfinished! -Nodrak
//Not in here? Must be untested!
return FALSE
#undef PERMANENT_TRANSFORMATION_TRAIT
#undef TEMPORARY_TRANSFORMATION_TRAIT
#undef TRANSFORMATION_DURATION