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"