diff --git a/code/_globalvars/lists/maintenance_loot.dm b/code/_globalvars/lists/maintenance_loot.dm
index 22abd0db..6f63206e 100644
--- a/code/_globalvars/lists/maintenance_loot.dm
+++ b/code/_globalvars/lists/maintenance_loot.dm
@@ -120,3 +120,21 @@ GLOBAL_LIST_INIT(maintenance_loot, list(
/obj/item/autosurgeon/womb = 1,
/obj/item/toy/plush/random = 1
))
+
+GLOBAL_LIST_INIT(ratking_trash, list(//Garbage: used by the regal rat mob when spawning garbage.
+ /obj/item/cigbutt,
+ /obj/item/trash/cheesie,
+ /obj/item/trash/candy,
+ /obj/item/trash/chips,
+ /obj/item/trash/pistachios,
+ /obj/item/trash/plate,
+ /obj/item/trash/popcorn,
+ /obj/item/trash/raisins,
+ /obj/item/trash/sosjerky,
+ /obj/item/trash/syndi_cakes))
+
+GLOBAL_LIST_INIT(ratking_coins, list(//Coins: Used by the regal rat mob when spawning coins.
+ /obj/item/coin/iron,
+ /obj/item/coin/silver,
+ /obj/item/coin/gold,
+ /obj/item/coin/plasma))
diff --git a/code/controllers/configuration/entries/game_options.dm b/code/controllers/configuration/entries/game_options.dm
index b487dc3d..1965d4d8 100644
--- a/code/controllers/configuration/entries/game_options.dm
+++ b/code/controllers/configuration/entries/game_options.dm
@@ -367,6 +367,10 @@
config_entry_value = 64
min_val = 0
+/datum/config_entry/number/ratcap
+ config_entry_value = 64
+ min_val = 0
+
/datum/config_entry/flag/disable_stambuffer
/datum/config_entry/keyed_list/box_random_engine
diff --git a/code/controllers/subsystem/mobs.dm b/code/controllers/subsystem/mobs.dm
index 56cdf2fa..aac9f9d3 100644
--- a/code/controllers/subsystem/mobs.dm
+++ b/code/controllers/subsystem/mobs.dm
@@ -8,6 +8,7 @@ SUBSYSTEM_DEF(mobs)
var/static/list/clients_by_zlevel[][]
var/static/list/dead_players_by_zlevel[][] = list(list()) // Needs to support zlevel 1 here, MaxZChanged only happens when z2 is created and new_players can login before that.
var/static/list/cubemonkeys = list()
+ var/static/list/cheeserats = list()
/datum/controller/subsystem/mobs/stat_entry()
..("P:[GLOB.mob_living_list.len]")
diff --git a/code/datums/components/storage/concrete/pockets.dm b/code/datums/components/storage/concrete/pockets.dm
index e0a23b02..1068a5a0 100644
--- a/code/datums/components/storage/concrete/pockets.dm
+++ b/code/datums/components/storage/concrete/pockets.dm
@@ -43,6 +43,26 @@
/datum/component/storage/concrete/pockets/small/detective
attack_hand_interact = TRUE // so the detectives would discover pockets in their hats
+/datum/component/storage/concrete/pockets/chefhat
+ attack_hand_interact = TRUE
+ max_items = 1
+ max_w_class = WEIGHT_CLASS_NORMAL
+
+/datum/component/storage/concrete/pockets/chefhat/Initialize()
+ . = ..()
+ can_hold = typecacheof(list(
+ /obj/item/clothing/head/mob_holder,
+ /obj/item/reagent_containers/food/snacks/deadmouse
+ ))
+
+/datum/component/storage/concrete/pockets/chefhat/can_be_inserted(obj/item/I, stop_messages, mob/M)
+ . = ..()
+ if(istype(I,/obj/item/clothing/head/mob_holder))
+ var/obj/item/clothing/head/mob_holder/mausholder = I
+ if(locate(/mob/living/simple_animal/mouse) in mausholder.contents)
+ return
+ return FALSE
+
/datum/component/storage/concrete/pockets/shoes
attack_hand_interact = FALSE
quickdraw = TRUE
diff --git a/code/modules/clothing/head/jobs.dm b/code/modules/clothing/head/jobs.dm
index b181da1e..3d6f1929 100644
--- a/code/modules/clothing/head/jobs.dm
+++ b/code/modules/clothing/head/jobs.dm
@@ -13,6 +13,7 @@
strip_delay = 10
equip_delay_other = 10
dynamic_hair_suffix = ""
+ pocket_storage_component_path = /datum/component/storage/concrete/pockets/chefhat
dog_fashion = /datum/dog_fashion/head/chef
/obj/item/clothing/head/chefhat/suicide_act(mob/user)
@@ -24,6 +25,12 @@
playsound(user, 'sound/machines/ding.ogg', 50, 1)
return(FIRELOSS)
+/obj/item/clothing/head/chefhat/relaymove(mob/user, direction)
+ if(!istype(user, /mob/living/simple_animal/mouse) || !isliving(loc) || !prob(20))
+ return
+ var/mob/living/L = loc
+ step_towards(L, get_step(L, direction))
+
//Captain
/obj/item/clothing/head/caphat
name = "captain's hat"
diff --git a/code/modules/food_and_drinks/food/snacks_other.dm b/code/modules/food_and_drinks/food/snacks_other.dm
index eeb69ba8..26f2c956 100644
--- a/code/modules/food_and_drinks/food/snacks_other.dm
+++ b/code/modules/food_and_drinks/food/snacks_other.dm
@@ -12,6 +12,17 @@
tastes = list("cheese" = 1)
foodtype = DAIRY
+
+
+/obj/item/reagent_containers/food/snacks/royalcheese
+ name = "royal cheese"
+ desc = "Ascend the throne. Consume the wheel. Feel the POWER."
+ icon_state = "royalcheese"
+ list_reagents = list(/datum/reagent/consumable/nutriment = 15, /datum/reagent/consumable/nutriment/vitamin = 5, /datum/reagent/gold = 20, /datum/reagent/toxin/mutagen = 5)
+ w_class = WEIGHT_CLASS_BULKY
+ tastes = list("cheese" = 4, "royalty" = 1)
+ foodtype = DAIRY
+
/obj/item/reagent_containers/food/snacks/cheesewedge
name = "cheese wedge"
desc = "A wedge of delicious Cheddar. The cheese wheel it was cut from can't have gone far."
diff --git a/code/modules/food_and_drinks/recipes/tablecraft/recipes_misc.dm b/code/modules/food_and_drinks/recipes/tablecraft/recipes_misc.dm
index 9cf5ea31..5b5ff4c5 100644
--- a/code/modules/food_and_drinks/recipes/tablecraft/recipes_misc.dm
+++ b/code/modules/food_and_drinks/recipes/tablecraft/recipes_misc.dm
@@ -340,4 +340,15 @@
/obj/item/reagent_containers/food/snacks/grown/strawberry = 1
)
result = /obj/item/reagent_containers/food/snacks/chocolatestrawberry
- subcategory = CAT_MISCFOOD
\ No newline at end of file
+ subcategory = CAT_MISCFOOD
+
+/datum/crafting_recipe/food/royalcheese
+ name = "Royal Cheese"
+ reqs = list(
+ /obj/item/reagent_containers/food/snacks/store/cheesewheel = 1,
+ /obj/item/clothing/head/crown = 1,
+ /datum/reagent/medicine/strange_reagent = 5,
+ /datum/reagent/toxin/mutagen = 5
+ )
+ result = /obj/item/reagent_containers/food/snacks/royalcheese
+ subcategory = CAT_MISCFOOD
diff --git a/code/modules/mob/living/simple_animal/friendly/mouse.dm b/code/modules/mob/living/simple_animal/friendly/mouse.dm
index 876feef8..f3ee9d7a 100644
--- a/code/modules/mob/living/simple_animal/friendly/mouse.dm
+++ b/code/modules/mob/living/simple_animal/friendly/mouse.dm
@@ -27,6 +27,7 @@
gold_core_spawnable = FRIENDLY_SPAWN
var/chew_probability = 1
size_multiplier = 0.5
+ faction = list("rat")
/mob/living/simple_animal/mouse/Initialize()
. = ..()
@@ -61,11 +62,15 @@
else
..(gibbed)
+
/mob/living/simple_animal/mouse/Crossed(AM as mob|obj)
if( ishuman(AM) )
if(!stat)
var/mob/M = AM
to_chat(M, "[icon2html(src, M)] Squeak!")
+ if(istype(AM, /obj/item/reagent_containers/food/snacks/royalcheese))
+ evolve()
+ qdel(AM)
..()
/mob/living/simple_animal/mouse/handle_automated_action()
@@ -86,6 +91,40 @@
C.deconstruct()
visible_message("[src] chews through the [C].")
+ for(var/obj/item/reagent_containers/food/snacks/cheesewedge/cheese in range(1, src))
+ if(prob(10))
+ be_fruitful()
+ qdel(cheese)
+ return
+ for(var/obj/item/reagent_containers/food/snacks/royalcheese/bigcheese in range(1, src))
+ qdel(bigcheese)
+ evolve()
+ return
+
+/**
+ *Checks the mouse cap, if it's above the cap, doesn't spawn a mouse. If below, spawns a mouse and adds it to cheeserats.
+ */
+
+/mob/living/simple_animal/mouse/proc/be_fruitful()
+ var/cap = CONFIG_GET(number/ratcap)
+ if(LAZYLEN(SSmobs.cheeserats) >= cap)
+ visible_message("[src] carefully eats the cheese, hiding it from the [cap] mice on the station!")
+ return
+ var/mob/living/newmouse = new /mob/living/simple_animal/mouse(loc)
+ SSmobs.cheeserats += newmouse
+ visible_message("[src] nibbles through the cheese, attracting another mouse!")
+
+/**
+ *Spawns a new regal rat, says some good jazz, and if sentient, transfers the relivant mind.
+ */
+/mob/living/simple_animal/mouse/proc/evolve()
+ var/mob/living/simple_animal/hostile/regalrat = new /mob/living/simple_animal/hostile/regalrat(loc)
+ visible_message("[src] devours the cheese! He morphs into something... greater!")
+ regalrat.say("RISE, MY SUBJECTS! SCREEEEEEE!")
+ if(mind)
+ mind.transfer_to(regalrat)
+ qdel(src)
+
/*
* Mouse types
*/
@@ -102,6 +141,10 @@
body_color = "brown"
icon_state = "mouse_brown"
+/mob/living/simple_animal/mouse/Destroy()
+ SSmobs.cheeserats -= src
+ return ..()
+
//TOM IS ALIVE! SQUEEEEEEEE~K :)
/mob/living/simple_animal/mouse/brown/Tom
name = "Tom"
diff --git a/code/modules/mob/living/simple_animal/hostile/regalrat.dm b/code/modules/mob/living/simple_animal/hostile/regalrat.dm
new file mode 100644
index 00000000..8c609c60
--- /dev/null
+++ b/code/modules/mob/living/simple_animal/hostile/regalrat.dm
@@ -0,0 +1,236 @@
+/mob/living/simple_animal/hostile/regalrat
+ name = "regal rat"
+ desc = "An evolved rat, created through some strange science. It leads nearby rats with deadly efficiency to protect its kingdom. Not technically a king."
+ icon_state = "regalrat"
+ icon_living = "regalrat"
+ icon_dead = "regalrat_dead"
+ gender = NEUTER
+ speak_chance = 0
+ turns_per_move = 5
+ maxHealth = 70
+ health = 70
+ see_in_dark = 5
+ obj_damage = 10
+ butcher_results = list(/obj/item/clothing/head/crown = 1,)
+ response_help = "glare at"
+ response_disarm = "skoff at"
+ response_harm = "slash"
+ melee_damage_lower = 10
+ melee_damage_upper = 12
+ attacktext = "slashes"
+ attack_sound = 'sound/weapons/punch1.ogg'
+ ventcrawler = VENTCRAWLER_ALWAYS
+ unique_name = TRUE
+ faction = list("rat")
+ var/datum/action/cooldown/coffer
+ var/datum/action/cooldown/riot
+ ///Number assigned to rats and mice, checked when determining infighting.
+
+/mob/living/simple_animal/hostile/regalrat/Initialize()
+ . = ..()
+ coffer = new /datum/action/cooldown/coffer
+ coffer.Grant(src)
+ riot = new /datum/action/cooldown/riot
+ riot.Grant(src)
+ var/list/mob/dead/observer/candidates = pollGhostCandidates("Do you want to play as the Royal Rat, cheesey be his crown?", ROLE_SENTIENCE, null, FALSE, 100, POLL_IGNORE_SENTIENCE_POTION)
+ if(LAZYLEN(candidates) && !mind)
+ var/mob/dead/observer/C = pick(candidates)
+ key = C.key
+ notify_ghosts("All rise for the rat king, ascendant to the throne in \the [get_area(src)].", source = src, action = NOTIFY_ORBIT, flashwindow = FALSE)
+
+/mob/living/simple_animal/hostile/regalrat/handle_automated_action()
+ if(prob(20))
+ riot.Trigger()
+ else if(prob(50))
+ coffer.Trigger()
+ return ..()
+
+/mob/living/simple_animal/hostile/regalrat/CanAttack(atom/the_target)
+ if(istype(the_target,/mob/living/simple_animal))
+ var/mob/living/A = the_target
+ if(istype(the_target, /mob/living/simple_animal/hostile/regalrat) && A.stat == CONSCIOUS)
+ return TRUE
+ if(istype(the_target, /mob/living/simple_animal/hostile/rat) && A.stat == CONSCIOUS)
+ var/mob/living/simple_animal/hostile/rat/R = the_target
+ if(R.faction_check_mob(src, TRUE))
+ return FALSE
+ else
+ return TRUE
+ return ..()
+
+/mob/living/simple_animal/hostile/regalrat/examine(mob/user)
+ . = ..()
+ if(istype(user,/mob/living/simple_animal/hostile/rat))
+ var/mob/living/simple_animal/hostile/rat/ratself = user
+ if(ratself.faction_check_mob(src, TRUE))
+ . += "This is your king. Long live his majesty!"
+ else
+ . += "This is a false king! Strike him down!"
+ else if(istype(user,/mob/living/simple_animal/hostile/regalrat))
+ . += "Who is this foolish false king? This will not stand!"
+
+/**
+ *This action creates trash, money, dirt, and cheese.
+ */
+
+/datum/action/cooldown/coffer
+ name = "Fill Coffers"
+ desc = "Your newly granted regality and poise let you scavenge for lost junk, but more importantly, cheese."
+ icon_icon = 'icons/mob/actions/actions_animal.dmi'
+ background_icon_state = "bg_clock"
+ button_icon_state = "coffer"
+ cooldown_time = 50
+
+/datum/action/cooldown/coffer/Trigger()
+ . = ..()
+ if(!.)
+ return
+ var/turf/T = get_turf(owner)
+ var/loot = rand(1,100)
+ switch(loot)
+ if(1 to 5)
+ to_chat(owner, "Score! You find some cheese!")
+ new /obj/item/reagent_containers/food/snacks/cheesewedge(T)
+ if(6 to 10)
+ var/pickedcoin = pick(GLOB.ratking_coins)
+ to_chat(owner, "You find some leftover coins. More for the royal treasury!")
+ for(var/i = 1 to rand(1,3))
+ new pickedcoin(T)
+ if(11)
+ to_chat(owner, "You find a... Hunh. This coin doesn't look right.")
+ var/rarecoin = rand(1,2)
+ if (rarecoin == 1)
+ new /obj/item/coin/twoheaded(T)
+ else
+ new /obj/item/coin/antagtoken(T)
+ if(12 to 40)
+ var/pickedtrash = pick(GLOB.ratking_trash)
+ to_chat(owner, "You just find more garbage and dirt. Lovely, but beneath you now.")
+ new /obj/effect/decal/cleanable/dirt(T)
+ new pickedtrash(T)
+ if(41 to 100)
+ to_chat(owner, "Drat. Nothing.")
+ new /obj/effect/decal/cleanable/dirt(T)
+ StartCooldown()
+
+/**
+ *This action checks all nearby mice, and converts them into hostile rats. If no mice are nearby, creates a new one.
+ */
+
+/datum/action/cooldown/riot
+ name = "Raise Army"
+ desc = "Raise an army out of the hordes of mice and pests crawling around the maintenance shafts."
+ icon_icon = 'icons/mob/actions/actions_animal.dmi'
+ button_icon_state = "riot"
+ background_icon_state = "bg_clock"
+ cooldown_time = 80
+ ///Checks to see if there are any nearby mice. Does not count Rats.
+
+/datum/action/cooldown/riot/Trigger()
+ . = ..()
+ if(!.)
+ return
+ var/cap = CONFIG_GET(number/ratcap)
+ var/something_from_nothing = FALSE
+ for(var/mob/living/simple_animal/mouse/M in oview(owner, 5))
+ var/mob/living/simple_animal/hostile/rat/new_rat = new(get_turf(M))
+ something_from_nothing = TRUE
+ if(M.mind && M.stat == CONSCIOUS)
+ M.mind.transfer_to(new_rat)
+ if(istype(owner,/mob/living/simple_animal/hostile/regalrat))
+ var/mob/living/simple_animal/hostile/regalrat/giantrat = owner
+ new_rat.faction = giantrat.faction
+ qdel(M)
+ if(!something_from_nothing)
+ if(LAZYLEN(SSmobs.cheeserats) >= cap)
+ to_chat(owner,"There's too many mice on this station to beckon a new one! Find them first!")
+ return
+ new /mob/living/simple_animal/mouse(owner.loc)
+ owner.visible_message("[owner] commands a mouse to its side!")
+ else
+ owner.visible_message("[owner] commands its army to action, mutating them into rats!")
+ StartCooldown()
+
+/mob/living/simple_animal/hostile/rat
+ name = "rat"
+ desc = "It's a nasty, ugly, evil, disease-ridden rodent with anger issues."
+ icon_state = "mouse_gray"
+ icon_living = "mouse_gray"
+ icon_dead = "mouse_gray_dead"
+ speak = list("Skree!","SKREEE!","Squeak?")
+ speak_emote = list("squeaks")
+ emote_hear = list("Hisses.")
+ emote_see = list("runs in a circle.", "stands on its hind legs.")
+ melee_damage_lower = 3
+ melee_damage_upper = 5
+ obj_damage = 5
+ speak_chance = 1
+ turns_per_move = 5
+ see_in_dark = 6
+ maxHealth = 15
+ health = 15
+ butcher_results = list(/obj/item/reagent_containers/food/snacks/meat/slab = 1)
+ density = FALSE
+ ventcrawler = VENTCRAWLER_ALWAYS
+ pass_flags = PASSTABLE | PASSGRILLE | PASSMOB
+ mob_size = MOB_SIZE_TINY
+ mob_biotypes = list(MOB_ORGANIC, MOB_BEAST)
+ faction = list("rat")
+
+/mob/living/simple_animal/hostile/rat/Initialize()
+ . = ..()
+ SSmobs.cheeserats += src
+ AddComponent(/datum/component/swarming)
+
+/mob/living/simple_animal/hostile/rat/Destroy()
+ SSmobs.cheeserats -= src
+ return ..()
+
+/mob/living/simple_animal/hostile/rat/examine(mob/user)
+ . = ..()
+ if(istype(user,/mob/living/simple_animal/hostile/rat))
+ var/mob/living/simple_animal/hostile/rat/ratself = user
+ if(ratself.faction_check_mob(src, TRUE))
+ . += "You both serve the same king."
+ else
+ . += "This fool serves a different king!"
+ else if(istype(user,/mob/living/simple_animal/hostile/regalrat))
+ var/mob/living/simple_animal/hostile/regalrat/ratking = user
+ if(ratking.faction_check_mob(src, TRUE))
+ . += "This rat serves under you."
+ else
+ . += "This peasant serves a different king! Strike him down!"
+
+/mob/living/simple_animal/hostile/rat/CanAttack(atom/the_target)
+ if(istype(the_target,/mob/living/simple_animal))
+ var/mob/living/A = the_target
+ if(istype(the_target, /mob/living/simple_animal/hostile/regalrat) && A.stat == CONSCIOUS)
+ var/mob/living/simple_animal/hostile/regalrat/ratking = the_target
+ if(ratking.faction_check_mob(src, TRUE))
+ return FALSE
+ else
+ return TRUE
+ if(istype(the_target, /mob/living/simple_animal/hostile/rat) && A.stat == CONSCIOUS)
+ var/mob/living/simple_animal/hostile/rat/R = the_target
+ if(R.faction_check_mob(src, TRUE))
+ return FALSE
+ else
+ return TRUE
+ return ..()
+
+/mob/living/simple_animal/hostile/rat/handle_automated_action()
+ . = ..()
+ if(prob(40))
+ var/turf/open/floor/F = get_turf(src)
+ if(istype(F) && !F.intact)
+ var/obj/structure/cable/C = locate() in F
+ if(C && prob(15))
+ if(C.avail())
+ visible_message("[src] chews through the [C]. It's toast!")
+ playsound(src, 'sound/effects/sparks2.ogg', 100, TRUE)
+ C.deconstruct()
+ death()
+ else if(C && C.avail())
+ visible_message("[src] chews through the [C]. It looks unharmed!")
+ playsound(src, 'sound/effects/sparks2.ogg', 100, TRUE)
+ C.deconstruct()
diff --git a/config/game_options.txt b/config/game_options.txt
index 00a1fa3a..0242f084 100644
--- a/config/game_options.txt
+++ b/config/game_options.txt
@@ -566,6 +566,9 @@ ROUNDSTART_TRAITS
## A cap on how many monkeys may be created via monkey cubes
MONKEYCAP 64
+## A cap on how many mice can be bred via cheese wedges
+RATCAP 64
+
## Uncomment to use TG-style combat
#DISABLE_STAMBUFFER
diff --git a/icons/mob/actions/actions_animal.dmi b/icons/mob/actions/actions_animal.dmi
index aefa471d..78eb9f5f 100644
Binary files a/icons/mob/actions/actions_animal.dmi and b/icons/mob/actions/actions_animal.dmi differ
diff --git a/icons/mob/animal.dmi b/icons/mob/animal.dmi
index e9343c7e..53d87e1e 100644
Binary files a/icons/mob/animal.dmi and b/icons/mob/animal.dmi differ
diff --git a/icons/obj/food/food.dmi b/icons/obj/food/food.dmi
index 03db1876..0f8662df 100644
Binary files a/icons/obj/food/food.dmi and b/icons/obj/food/food.dmi differ
diff --git a/tgstation.dme b/tgstation.dme
index e29c8f05..c83d8436 100644
--- a/tgstation.dme
+++ b/tgstation.dme
@@ -2302,6 +2302,7 @@
#include "code\modules\mob\living\simple_animal\hostile\nanotrasen.dm"
#include "code\modules\mob\living\simple_animal\hostile\netherworld.dm"
#include "code\modules\mob\living\simple_animal\hostile\pirate.dm"
+#include "code\modules\mob\living\simple_animal\hostile\regalrat.dm"
#include "code\modules\mob\living\simple_animal\hostile\russian.dm"
#include "code\modules\mob\living\simple_animal\hostile\sharks.dm"
#include "code\modules\mob\living\simple_animal\hostile\skeleton.dm"