mirror of
https://github.com/Bubberstation/Bubberstation.git
synced 2026-06-05 22:31:04 +01:00
08dee0ff26
## About The Pull Request Allows the specifically coloured mouse subtypes to work. If you want a brown mouse then by god you are going to get one. ## Why It's Good For The Game I will be honest that I don't know if anyone even cares about this but I guess it restore's Tom's consistent colour, which was presumably broken. ## Changelog 🆑 fix: Tom will now always be a brown rat, instead of a random colour, as intended. /🆑
437 lines
16 KiB
Plaintext
437 lines
16 KiB
Plaintext
/mob/living/basic/mouse
|
|
name = "mouse"
|
|
desc = "This cute little guy just loves the taste of uninsulated electrical cables. Isn't he adorable?"
|
|
icon_state = "mouse_gray"
|
|
icon_living = "mouse_gray"
|
|
icon_dead = "mouse_gray_dead"
|
|
held_state = "mouse_gray"
|
|
|
|
maxHealth = 5
|
|
health = 5
|
|
density = FALSE
|
|
pass_flags = PASSTABLE|PASSGRILLE|PASSMOB
|
|
mob_size = MOB_SIZE_TINY
|
|
can_be_held = TRUE
|
|
held_w_class = WEIGHT_CLASS_TINY
|
|
mob_biotypes = MOB_ORGANIC|MOB_BEAST
|
|
gold_core_spawnable = FRIENDLY_SPAWN
|
|
faction = list(FACTION_RAT, FACTION_MAINT_CREATURES)
|
|
butcher_results = list(/obj/item/food/meat/slab/mouse = 1)
|
|
|
|
speak_emote = list("squeaks")
|
|
response_help_continuous = "pets"
|
|
response_help_simple = "pet"
|
|
response_disarm_continuous = "gently pushes aside"
|
|
response_disarm_simple = "gently push aside"
|
|
response_harm_continuous = "splats"
|
|
response_harm_simple = "splat"
|
|
|
|
ai_controller = /datum/ai_controller/basic_controller/mouse
|
|
|
|
/// Whether this rat is friendly to players
|
|
var/tame = FALSE
|
|
/// What color our mouse is. Brown, gray and white - leave blank for random.
|
|
var/body_color
|
|
/// Does this mouse contribute to the ratcap?
|
|
var/contributes_to_ratcap = TRUE
|
|
/// Probability that, if we successfully bite a shocked cable, that we will die to it.
|
|
var/cable_zap_prob = 85
|
|
|
|
/mob/living/basic/mouse/Initialize(mapload, tame = FALSE, new_body_color)
|
|
. = ..()
|
|
if(contributes_to_ratcap)
|
|
SSmobs.cheeserats |= src
|
|
ADD_TRAIT(src, TRAIT_VENTCRAWLER_ALWAYS, INNATE_TRAIT)
|
|
|
|
src.tame = tame
|
|
if(!isnull(new_body_color))
|
|
body_color = new_body_color
|
|
if(isnull(body_color))
|
|
body_color = pick("brown", "gray", "white")
|
|
held_state = "mouse_[body_color]" // not handled by variety element
|
|
AddElement(/datum/element/animal_variety, "mouse", body_color, FALSE)
|
|
AddElement(/datum/element/swabable, CELL_LINE_TABLE_MOUSE, CELL_VIRUS_TABLE_GENERIC_MOB, 1, 10)
|
|
AddComponent(/datum/component/squeak, list('sound/effects/mousesqueek.ogg' = 1), 100, extrarange = SHORT_RANGE_SOUND_EXTRARANGE) //as quiet as a mouse or whatever
|
|
var/static/list/loc_connections = list(
|
|
COMSIG_ATOM_ENTERED = PROC_REF(on_entered),
|
|
)
|
|
AddElement(/datum/element/connect_loc, loc_connections)
|
|
make_tameable()
|
|
|
|
/mob/living/basic/mouse/proc/make_tameable()
|
|
if (tame)
|
|
faction |= FACTION_NEUTRAL
|
|
else
|
|
AddComponent(/datum/component/tameable, food_types = list(/obj/item/food/cheese), tame_chance = 100, after_tame = CALLBACK(src, PROC_REF(tamed)))
|
|
|
|
/mob/living/basic/mouse/Destroy()
|
|
SSmobs.cheeserats -= src
|
|
return ..()
|
|
|
|
/mob/living/basic/mouse/examine(mob/user)
|
|
. = ..()
|
|
|
|
var/sameside = user.faction_check_mob(src, exact_match = TRUE)
|
|
if(isregalrat(user))
|
|
if(sameside)
|
|
. += span_notice("This rat serves under you.")
|
|
else
|
|
. += span_warning("This peasant serves a different king! Strike [p_them()] down!")
|
|
|
|
else if(user != src && ismouse(user))
|
|
if(sameside)
|
|
. += span_notice("You both serve the same king.")
|
|
else
|
|
. += span_warning("This fool serves a different king!")
|
|
|
|
/// Kills the rat and changes its icon state to be splatted (bloody).
|
|
/mob/living/basic/mouse/proc/splat()
|
|
icon_dead = "mouse_[body_color]_splat"
|
|
adjust_health(maxHealth)
|
|
|
|
// On revival, re-add the mouse to the ratcap, or block it if we're at it
|
|
/mob/living/basic/mouse/revive(full_heal_flags = NONE, excess_healing = 0, force_grab_ghost = FALSE)
|
|
if(!contributes_to_ratcap)
|
|
return ..()
|
|
|
|
var/aheal_included = full_heal_flags & HEAL_ADMIN
|
|
var/cap = CONFIG_GET(number/ratcap)
|
|
if(!aheal_included && !ckey && length(SSmobs.cheeserats) >= cap)
|
|
visible_message(span_warning("[src] twitches, but does not continue moving \
|
|
due to the overwhelming rodent population on the station!"))
|
|
return
|
|
|
|
. = ..()
|
|
if(stat != DEAD)
|
|
SSmobs.cheeserats |= src
|
|
|
|
// On death, remove the mouse from the ratcap, and turn it into an item if applicable
|
|
/mob/living/basic/mouse/death(gibbed)
|
|
SSmobs.cheeserats -= src
|
|
// Rats with a mind will not turn into a lizard snack on death
|
|
if(mind)
|
|
return ..()
|
|
|
|
// Call parent with gibbed = TRUE, becuase we're getting rid of the body
|
|
. = ..(TRUE)
|
|
// Now if we were't ACTUALLY gibbed, spawn the dead mouse
|
|
if(!gibbed)
|
|
var/obj/item/food/deadmouse/mouse = new(loc, src)
|
|
if(HAS_TRAIT(src, TRAIT_BEING_SHOCKED))
|
|
mouse.desc = "They're toast."
|
|
mouse.add_atom_colour("#3A3A3A", FIXED_COLOUR_PRIORITY)
|
|
qdel(src)
|
|
|
|
/mob/living/basic/mouse/UnarmedAttack(atom/attack_target, proximity_flag, list/modifiers)
|
|
. = ..()
|
|
if(!.)
|
|
return
|
|
|
|
if(!proximity_flag)
|
|
return
|
|
|
|
if(istype(attack_target, /obj/item/food/cheese))
|
|
try_consume_cheese(attack_target)
|
|
return TRUE
|
|
|
|
if(istype(attack_target, /obj/structure/cable))
|
|
try_bite_cable(attack_target)
|
|
return TRUE
|
|
|
|
/// Signal proc for [COMSIG_ATOM_ENTERED]. Sends a lil' squeak to chat when someone walks over us.
|
|
/mob/living/basic/mouse/proc/on_entered(datum/source, atom/movable/entered)
|
|
SIGNAL_HANDLER
|
|
|
|
if(ishuman(entered) && stat == CONSCIOUS)
|
|
to_chat(entered, span_notice("[icon2html(src, entered)] Squeak!"))
|
|
|
|
/// Called when a mouse is hand-fed some cheese, it will stop being afraid of humans
|
|
/mob/living/basic/mouse/proc/tamed(mob/living/tamer, obj/item/food/cheese/cheese)
|
|
new /obj/effect/temp_visual/heart(loc)
|
|
faction |= FACTION_NEUTRAL
|
|
tame = TRUE
|
|
try_consume_cheese(cheese)
|
|
ai_controller.CancelActions() // Interrupt any current fleeing
|
|
|
|
/// Attempts to consume a piece of cheese, causing a few effects.
|
|
/mob/living/basic/mouse/proc/try_consume_cheese(obj/item/food/cheese/cheese)
|
|
// Royal cheese will evolve us into a regal rat
|
|
if(istype(cheese, /obj/item/food/cheese/royal))
|
|
visible_message(
|
|
span_warning("[src] devours [cheese]! They morph into something... greater!"),
|
|
span_notice("You devour [cheese], and start morphing into something... greater!"),
|
|
)
|
|
evolve_into_regal_rat()
|
|
qdel(cheese)
|
|
return
|
|
|
|
var/cap = CONFIG_GET(number/ratcap)
|
|
// Normal cheese will either heal us
|
|
if(prob(90) || health < maxHealth)
|
|
visible_message(
|
|
span_notice("[src] nibbles [cheese]."),
|
|
span_notice("You nibble [cheese][health < maxHealth ? ", restoring your health" : ""].")
|
|
)
|
|
adjust_health(-maxHealth)
|
|
|
|
// Or, if we're at full health, there's a 10% chance that normal cheese will spawn a new mouse
|
|
// ...if the rat cap allows us, that is
|
|
else if(length(SSmobs.cheeserats) >= cap)
|
|
visible_message(
|
|
span_warning("[src] carefully eats [cheese], hiding it from the [cap] mice on the station!"),
|
|
span_notice("You carefully nibble [cheese], hiding it from the [cap] other mice on board the station.")
|
|
)
|
|
else
|
|
visible_message(
|
|
span_notice("[src] nibbles through [cheese], attracting another mouse!"),
|
|
span_notice("You nibble through [cheese], attracting another mouse!")
|
|
)
|
|
create_a_new_rat()
|
|
|
|
qdel(cheese)
|
|
|
|
/// Evolves this rat into a regal rat
|
|
/mob/living/basic/mouse/proc/evolve_into_regal_rat()
|
|
var/mob/living/simple_animal/hostile/regalrat/controlled/regalrat = new(loc)
|
|
mind?.transfer_to(regalrat)
|
|
INVOKE_ASYNC(regalrat, TYPE_PROC_REF(/atom/movable, say), "RISE, MY SUBJECTS! SCREEEEEEE!")
|
|
qdel(src)
|
|
|
|
/// Creates a new mouse based on this mouse's subtype.
|
|
/mob/living/basic/mouse/proc/create_a_new_rat()
|
|
new /mob/living/basic/mouse(loc, /* tame = */ tame)
|
|
|
|
/// Biting into a cable will cause a mouse to get shocked and die if applicable. Or do nothing if they're lucky.
|
|
/mob/living/basic/mouse/proc/try_bite_cable(obj/structure/cable/cable)
|
|
if(cable.avail() && !HAS_TRAIT(src, TRAIT_SHOCKIMMUNE) && prob(cable_zap_prob))
|
|
visible_message(
|
|
span_warning("[src] chews through \the [cable]. It's toast!"),
|
|
span_userdanger("As you bite deeply into [cable], you suddenly realize this may have been a bad idea."),
|
|
span_hear("You hear electricity crack."),
|
|
)
|
|
// Finely toasted
|
|
ADD_TRAIT(src, TRAIT_BEING_SHOCKED, TRAIT_GENERIC)
|
|
// Unfortunately we can't check the return value of electrocute_act before displaying a message,
|
|
// as it's possible the damage from electrocution results in our hunter being deleted.
|
|
// But what are the odds of the shock failing? Hahaha...
|
|
electrocute_act(maxHealth * 2, cable, flags = SHOCK_SUPPRESS_MESSAGE)
|
|
|
|
else
|
|
visible_message(
|
|
span_warning("[src] chews through \the [cable]."),
|
|
span_notice("You chew through \the [cable]."),
|
|
)
|
|
|
|
playsound(cable, 'sound/effects/sparks2.ogg', 100, TRUE)
|
|
cable.deconstruct()
|
|
|
|
/mob/living/basic/mouse/white
|
|
body_color = "white"
|
|
icon_state = "mouse_white"
|
|
held_state = "mouse_white"
|
|
|
|
/mob/living/basic/mouse/gray
|
|
body_color = "gray"
|
|
icon_state = "mouse_gray"
|
|
|
|
/mob/living/basic/mouse/brown
|
|
body_color = "brown"
|
|
icon_state = "mouse_brown"
|
|
held_state = "mouse_brown"
|
|
|
|
//TOM IS ALIVE! SQUEEEEEEEE~K :)
|
|
/mob/living/basic/mouse/brown/tom
|
|
name = "Tom"
|
|
desc = "Jerry the cat is not amused."
|
|
response_help_continuous = "pets"
|
|
response_help_simple = "pet"
|
|
response_disarm_continuous = "gently pushes aside"
|
|
response_disarm_simple = "gently push aside"
|
|
response_harm_continuous = "splats"
|
|
response_harm_simple = "splat"
|
|
gold_core_spawnable = NO_SPAWN
|
|
contributes_to_ratcap = FALSE
|
|
|
|
/mob/living/basic/mouse/brown/tom/make_tameable()
|
|
tame = TRUE
|
|
return ..()
|
|
|
|
/mob/living/basic/mouse/brown/tom/Initialize(mapload)
|
|
. = ..()
|
|
// Tom fears no cable.
|
|
ADD_TRAIT(src, TRAIT_SHOCKIMMUNE, INNATE_TRAIT)
|
|
AddElement(/datum/element/pet_bonus, "squeaks happily!")
|
|
|
|
/mob/living/basic/mouse/brown/tom/create_a_new_rat()
|
|
new /mob/living/basic/mouse/brown(loc, /* tame = */ tame) // dominant gene
|
|
|
|
/mob/living/basic/mouse/rat
|
|
name = "rat"
|
|
desc = "They're a nasty, ugly, evil, disease-ridden rodent with anger issues."
|
|
|
|
gold_core_spawnable = NO_SPAWN
|
|
melee_damage_lower = 3
|
|
melee_damage_upper = 5
|
|
obj_damage = 5
|
|
maxHealth = 15
|
|
health = 15
|
|
|
|
ai_controller = /datum/ai_controller/basic_controller/mouse/rat
|
|
|
|
/mob/living/basic/mouse/rat/make_tameable()
|
|
return // Unlike in real life, space rats are horrible creatures who don't like you
|
|
|
|
/mob/living/basic/mouse/rat/create_a_new_rat()
|
|
new /mob/living/basic/mouse/rat(loc)
|
|
|
|
/// Mice turn into food when they die
|
|
/obj/item/food/deadmouse
|
|
name = "dead mouse"
|
|
desc = "They look like somebody dropped the bass on it. A lizard's favorite meal."
|
|
icon = 'icons/mob/simple/animal.dmi'
|
|
icon_state = "mouse_gray_dead"
|
|
bite_consumption = 3
|
|
eatverbs = list("devour")
|
|
food_reagents = list(/datum/reagent/consumable/nutriment = 3, /datum/reagent/consumable/nutriment/vitamin = 2)
|
|
foodtypes = GORE | MEAT | RAW
|
|
grind_results = list(/datum/reagent/blood = 20, /datum/reagent/consumable/liquidgibs = 5)
|
|
decomp_req_handle = TRUE
|
|
ant_attracting = FALSE
|
|
decomp_type = /obj/item/food/deadmouse/moldy
|
|
var/body_color = "gray"
|
|
var/critter_type = /mob/living/basic/mouse
|
|
|
|
/obj/item/food/deadmouse/Initialize(mapload, mob/living/basic/mouse/dead_critter)
|
|
. = ..()
|
|
if(dead_critter)
|
|
body_color = dead_critter.body_color
|
|
critter_type = dead_critter.type
|
|
name = dead_critter.name
|
|
icon_state = dead_critter.icon_dead
|
|
AddElement(/datum/element/swabable, CELL_LINE_TABLE_MOUSE, CELL_VIRUS_TABLE_GENERIC_MOB, 1, 10)
|
|
RegisterSignal(src, COMSIG_ATOM_ON_LAZARUS_INJECTOR, PROC_REF(use_lazarus))
|
|
|
|
/obj/item/food/deadmouse/examine(mob/user)
|
|
. = ..()
|
|
if (reagents?.has_reagent(/datum/reagent/yuck) || reagents?.has_reagent(/datum/reagent/fuel))
|
|
. += span_warning("[p_theyre(TRUE)] dripping with fuel and smells terrible.")
|
|
|
|
///Spawn a new mouse from this dead mouse item when hit by a lazarus injector and conditions are met.
|
|
/obj/item/food/deadmouse/proc/use_lazarus(datum/source, obj/item/lazarus_injector/injector, mob/user)
|
|
SIGNAL_HANDLER
|
|
if(injector.revive_type != SENTIENCE_ORGANIC)
|
|
balloon_alert(user, "invalid creature!")
|
|
return
|
|
var/mob/living/basic/mouse/revived_critter = new critter_type (drop_location(), FALSE, body_color)
|
|
revived_critter.name = name
|
|
revived_critter.lazarus_revive(user, injector.malfunctioning)
|
|
injector.expend(revived_critter, user)
|
|
qdel(src)
|
|
return LAZARUS_INJECTOR_USED
|
|
|
|
/obj/item/food/deadmouse/attackby(obj/item/attacking_item, mob/user, params)
|
|
var/mob/living/living_user = user
|
|
if(istype(living_user) && attacking_item.get_sharpness() && living_user.combat_mode)
|
|
if(!isturf(loc))
|
|
balloon_alert(user, "can't butcher here!")
|
|
return
|
|
|
|
balloon_alert(user, "butchering...")
|
|
if(!do_after(user, 0.75 SECONDS, src))
|
|
balloon_alert(user, "interrupted!")
|
|
return
|
|
|
|
loc.balloon_alert(user, "butchered")
|
|
new /obj/item/food/meat/slab/mouse(loc)
|
|
qdel(src)
|
|
return
|
|
|
|
return ..()
|
|
|
|
/obj/item/food/deadmouse/afterattack(obj/target, mob/living/user, proximity_flag)
|
|
. = ..()
|
|
if(proximity_flag && reagents && target.is_open_container())
|
|
. |= AFTERATTACK_PROCESSED_ITEM
|
|
// is_open_container will not return truthy if target.reagents doesn't exist
|
|
var/datum/reagents/target_reagents = target.reagents
|
|
var/trans_amount = reagents.maximum_volume - reagents.total_volume * (4 / 3)
|
|
if(target_reagents.has_reagent(/datum/reagent/fuel) && target_reagents.trans_to(src, trans_amount))
|
|
to_chat(user, span_notice("You dip [src] into [target]."))
|
|
else
|
|
to_chat(user, span_warning("That's a terrible idea."))
|
|
return .
|
|
|
|
/obj/item/food/deadmouse/moldy
|
|
name = "moldy dead mouse"
|
|
desc = "A dead rodent, consumed by mold and rot. There is a slim chance that a lizard might still eat it."
|
|
icon_state = "mouse_gray_dead"
|
|
food_reagents = list(/datum/reagent/consumable/nutriment = 3, /datum/reagent/consumable/nutriment/vitamin = 2, /datum/reagent/consumable/mold = 10)
|
|
foodtypes = GORE | MEAT | RAW | GROSS
|
|
grind_results = list(/datum/reagent/blood = 20, /datum/reagent/consumable/liquidgibs = 5, /datum/reagent/consumable/mold = 10)
|
|
preserved_food = TRUE
|
|
|
|
/// The mouse AI controller
|
|
/datum/ai_controller/basic_controller/mouse
|
|
blackboard = list(
|
|
BB_BASIC_MOB_FLEEING = TRUE, // Always cowardly
|
|
BB_CURRENT_HUNTING_TARGET = null, // cheese
|
|
BB_LOW_PRIORITY_HUNTING_TARGET = null, // cable
|
|
BB_TARGETTING_DATUM = new /datum/targetting_datum/basic(), // Use this to find people to run away from
|
|
)
|
|
|
|
ai_traits = STOP_MOVING_WHEN_PULLED
|
|
ai_movement = /datum/ai_movement/basic_avoidance
|
|
idle_behavior = /datum/idle_behavior/idle_random_walk
|
|
planning_subtrees = list(
|
|
// Top priority is to look for and execute hunts for cheese even if someone is looking at us
|
|
/datum/ai_planning_subtree/find_and_hunt_target/look_for_cheese,
|
|
// Next priority is see if anyone is looking at us
|
|
/datum/ai_planning_subtree/simple_find_nearest_target_to_flee,
|
|
// Skedaddle
|
|
/datum/ai_planning_subtree/flee_target/mouse,
|
|
// Try to speak, because it's cute
|
|
/datum/ai_planning_subtree/random_speech/mouse,
|
|
// Otherwise, look for and execute hunts for cabling
|
|
/datum/ai_planning_subtree/find_and_hunt_target/look_for_cables,
|
|
)
|
|
|
|
/// Don't look for anything to run away from if you are distracted by being adjacent to cheese
|
|
/datum/ai_planning_subtree/flee_target/mouse
|
|
flee_behaviour = /datum/ai_behavior/run_away_from_target/mouse
|
|
|
|
/datum/ai_planning_subtree/flee_target/mouse
|
|
|
|
/datum/ai_planning_subtree/flee_target/mouse/SelectBehaviors(datum/ai_controller/controller, seconds_per_tick)
|
|
var/atom/hunted_cheese = controller.blackboard[BB_CURRENT_HUNTING_TARGET]
|
|
if (!isnull(hunted_cheese))
|
|
return // We see some cheese, which is more important than our life
|
|
return ..()
|
|
|
|
/datum/ai_planning_subtree/flee_target/mouse/select
|
|
|
|
/datum/ai_behavior/run_away_from_target/mouse
|
|
run_distance = 3 // Mostly exists in small tunnels, don't get ahead of yourself
|
|
|
|
/// AI controller for rats, slightly more complex than mice becuase they attack people
|
|
/datum/ai_controller/basic_controller/mouse/rat
|
|
blackboard = list(
|
|
BB_TARGETTING_DATUM = new /datum/targetting_datum/basic(),
|
|
BB_PET_TARGETTING_DATUM = new /datum/targetting_datum/not_friends(),
|
|
BB_BASIC_MOB_CURRENT_TARGET = null, // heathen
|
|
BB_CURRENT_HUNTING_TARGET = null, // cheese
|
|
BB_LOW_PRIORITY_HUNTING_TARGET = null, // cable
|
|
)
|
|
|
|
ai_traits = STOP_MOVING_WHEN_PULLED
|
|
ai_movement = /datum/ai_movement/basic_avoidance
|
|
idle_behavior = /datum/idle_behavior/idle_random_walk
|
|
planning_subtrees = list(
|
|
/datum/ai_planning_subtree/pet_planning,
|
|
/datum/ai_planning_subtree/simple_find_target,
|
|
/datum/ai_planning_subtree/attack_obstacle_in_path,
|
|
/datum/ai_planning_subtree/basic_melee_attack_subtree,
|
|
/datum/ai_planning_subtree/find_and_hunt_target/look_for_cheese,
|
|
/datum/ai_planning_subtree/random_speech/mouse,
|
|
/datum/ai_planning_subtree/find_and_hunt_target/look_for_cables,
|
|
)
|