diff --git a/code/__DEFINES/dcs/signals/signals_mob/signals_mob_basic.dm b/code/__DEFINES/dcs/signals/signals_mob/signals_mob_basic.dm new file mode 100644 index 00000000000..738f033656a --- /dev/null +++ b/code/__DEFINES/dcs/signals/signals_mob/signals_mob_basic.dm @@ -0,0 +1,4 @@ +/// Sent from /mob/living/basic/proc/look_dead() : () +#define COMSIG_BASICMOB_LOOK_DEAD "basicmob_look_dead" +/// Sent from /mob/living/basic/proc/look_alive() : () +#define COMSIG_BASICMOB_LOOK_ALIVE "basicmob_look_alive" diff --git a/code/datums/ai/basic_mobs/pet_commands/play_dead.dm b/code/datums/ai/basic_mobs/pet_commands/play_dead.dm index a84310cb9ab..0520898c4a5 100644 --- a/code/datums/ai/basic_mobs/pet_commands/play_dead.dm +++ b/code/datums/ai/basic_mobs/pet_commands/play_dead.dm @@ -7,6 +7,7 @@ if(!istype(basic_pawn) || basic_pawn.stat) // Can't act dead if you're dead return basic_pawn.emote("deathgasp", intentional=FALSE) + ADD_TRAIT(basic_pawn, TRAIT_FAKEDEATH, BASIC_MOB_DEATH_TRAIT) basic_pawn.look_dead() /datum/ai_behavior/play_dead/perform(seconds_per_tick, datum/ai_controller/controller) @@ -20,5 +21,6 @@ if(!istype(basic_pawn) || basic_pawn.stat) // imagine actually dying while playing dead. hell, imagine being the kid waiting for your pup to get back up :( return basic_pawn.visible_message(span_notice("[basic_pawn] miraculously springs back to life!")) + REMOVE_TRAIT(basic_pawn, TRAIT_FAKEDEATH, BASIC_MOB_DEATH_TRAIT) basic_pawn.look_alive() controller.clear_blackboard_key(BB_ACTIVE_PET_COMMAND) diff --git a/code/datums/dog_fashion.dm b/code/datums/dog_fashion.dm index d35771027b3..84913c234be 100644 --- a/code/datums/dog_fashion.dm +++ b/code/datums/dog_fashion.dm @@ -1,41 +1,65 @@ /datum/dog_fashion - var/name - var/desc - var/emote_see - var/emote_hear - var/speak - var/speak_emote + ///Name modifier for the dog that we're dressing up + var/name = null + ///Description modifier for the dog that we're dressing up + var/desc = null + ///Hearable emotes modifier for the dog that we're dressing up + var/list/emote_hear = list() + ///Visible emotes modifier for the dog that we're dressing up + var/list/emote_see = list() + ///Speech modifier for the dog that we're dressing up + var/list/speak = list() + ///Speech verb modifier for the dog that we're dressing up + var/list/speak_emote = list() // This isn't applied to the dog, but stores the icon_state of the // sprite that the associated item uses - var/icon_file - var/obj_icon_state + ///Icon path for the fashion item + var/icon_file = null + ///Icon state for the fashion item + var/obj_icon_state = null + ///Alpha level (opacity) modifier of the fashion item var/obj_alpha + ///Color modifier of the fasion item var/obj_color -/datum/dog_fashion/New(mob/M) - name = replacetext(name, "REAL_NAME", M.real_name) - desc = replacetext(desc, "NAME", name) +/datum/dog_fashion/New(mob/fashionable_mob) + //replace the placeholder for the real unmodified name in the name of the dog + name = replacetext(name, "%REAL_NAME%", fashionable_mob.real_name) + //replace the placeholder for the capitalized real unmodified name in the name of the dog + name = replacetext(name, "%CAPITAL_REAL_NAME%", capitalize(fashionable_mob.real_name)) + //replace the placeholder for the current full name, which includes our name modifiers + desc = replacetext(desc, "%NAME%", name) + //replace the placeholder for the real unmodified name in the description of the dog + desc = replacetext(desc, "%REAL_NAME%", fashionable_mob.real_name) + //replace the placeholder for the capitalized real unmodified name in the description of the dog + desc = replacetext(desc, "%CAPITAL_REAL_NAME%", capitalize(fashionable_mob.real_name)) -/datum/dog_fashion/proc/apply(mob/living/basic/pet/dog/D) +///Applies the name, description and speak emote modifiers to the dog +/datum/dog_fashion/proc/apply(mob/living/basic/pet/dog/dressup_doggy) if(name) - D.name = name + dressup_doggy.name = name if(desc) - D.desc = desc - if(speak_emote) - D.speak_emote = string_list(speak_emote) + dressup_doggy.desc = desc + if(length(speak_emote)) + dressup_doggy.speak_emote = string_list(speak_emote) +///Applies random speech modifiers to the dog /datum/dog_fashion/proc/apply_to_speech(datum/ai_planning_subtree/random_speech/speech) - if(emote_see) + if(length(emote_see)) speech.emote_see = string_list(emote_see) - if(emote_hear) + if(length(emote_hear)) speech.emote_hear = string_list(emote_hear) - if(speak) + if(length(speak)) speech.speak = string_list(speak) +/** + * Generates the icon overlay for the equipped item + * dir: passed direction for the sprite, e.g. to apply to a dead dog, we use the EAST dir and just flip it 180. + */ /datum/dog_fashion/proc/get_overlay(dir) if(icon_file && obj_icon_state) - var/image/corgI = image(icon_file, obj_icon_state, dir = dir) + var/image/corgI = image(icon_file, icon_state = obj_icon_state, dir = dir) corgI.alpha = obj_alpha corgI.color = obj_color return corgI @@ -51,33 +75,32 @@ obj_icon_state = "armor" /datum/dog_fashion/back/deathsquad - name = "Trooper REAL_NAME" + name = "Trooper %REAL_NAME%" desc = "That's not red paint. That's real corgi blood." /datum/dog_fashion/head/helmet - name = "Sergeant REAL_NAME" + name = "Sergeant %REAL_NAME%" desc = "The ever-loyal, the ever-vigilant." /datum/dog_fashion/head/chef - name = "Sous chef REAL_NAME" + name = "Sous chef %REAL_NAME%" desc = "Your food will be taste-tested. All of it." - /datum/dog_fashion/head/captain - name = "Captain REAL_NAME" + name = "Captain %REAL_NAME%" desc = "Probably better than the last captain." /datum/dog_fashion/head/kitty name = "Runtime" + desc = "They're a cute little kitty-cat! ... wait ... what the hell?" emote_see = list("coughs up a furball", "stretches") emote_hear = list("purrs") speak = list("Purrr", "Meow!", "MAOOOOOW!", "HISSSSS", "MEEEEEEW") - desc = "They're a cute little kitty-cat! ... wait ... what the hell?" /datum/dog_fashion/head/rabbit name = "Hoppy" - emote_see = list("twitches their nose", "hops around a bit") desc = "This is Hoppy. They're a corgi-...urmm... bunny rabbit." + emote_see = list("twitches their nose", "hops around a bit") /datum/dog_fashion/head/beret name = "Yann" @@ -85,16 +108,14 @@ speak = list("le woof!", "le bark!", "JAPPE!!") emote_see = list("cowers in fear.", "surrenders.", "plays dead.","looks as though there is a wall in front of them.") - /datum/dog_fashion/head/detective - name = "Detective REAL_NAME" - desc = "NAME sees through your lies..." + name = "Detective %REAL_NAME%" + desc = "%NAME% sees through your lies..." emote_see = list("investigates the area.","sniffs around for clues.","searches for scooby snacks.","takes a candycorn from the hat.") - /datum/dog_fashion/head/nurse - name = "Nurse REAL_NAME" - desc = "NAME needs 100cc of beef jerky... STAT!" + name = "Nurse %REAL_NAME%" + desc = "%NAME% needs 100cc of beef jerky... STAT!" /datum/dog_fashion/head/pirate name = "Pirate-title Pirate-name" @@ -104,7 +125,7 @@ speak = list("Arrrrgh!!","Grrrrrr!") /datum/dog_fashion/head/pirate/New(mob/M) - ..() + . = ..() name = "[pick("Ol'","Scurvy","Black","Rum","Gammy","Bloody","Gangrene","Death","Long-John")] [pick("kibble","leg","beard","tooth","poop-deck","Threepwood","Le Chuck","corsair","Silver","Crusoe")]" /datum/dog_fashion/head/ushanka @@ -113,85 +134,81 @@ emote_see = list("contemplates the failings of the capitalist economic model.", "ponders the pros and cons of vanguardism.") /datum/dog_fashion/head/ushanka/New(mob/M) - ..() - name = "[pick("Comrade","Commissar","Glorious Leader")] [M.real_name]" + name = "[pick("Comrade","Commissar","Glorious Leader")] %REAL_NAME%" + return ..() /datum/dog_fashion/head/warden - name = "Officer REAL_NAME" - emote_see = list("drools.","looks for donuts.") + name = "Officer %REAL_NAME%" desc = "Stop right there criminal scum!" + emote_see = list("drools.","looks for donuts.") /datum/dog_fashion/head/warden_red - name = "Officer REAL_NAME" - emote_see = list("drools.","looks for donuts.") + name = "Officer %REAL_NAME%" desc = "Stop right there criminal scum!" + emote_see = list("drools.","looks for donuts.") /datum/dog_fashion/head/blue_wizard - name = "Grandwizard REAL_NAME" + name = "Grandwizard %REAL_NAME%" speak = list("YAP", "Woof!", "Bark!", "AUUUUUU", "EI NATH!") /datum/dog_fashion/head/red_wizard - name = "Pyromancer REAL_NAME" + name = "Pyromancer %REAL_NAME%" speak = list("YAP", "Woof!", "Bark!", "AUUUUUU", "ONI SOMA!") /datum/dog_fashion/head/cardborg name = "Borgi" + desc = "Result of robotics budget cuts." speak = list("Ping!","Beep!","Woof!") emote_see = list("goes rogue.", "sniffs out non-humans.") - desc = "Result of robotics budget cuts." /datum/dog_fashion/head/ghost name = "\improper Ghost" + desc = "Spooky!" + obj_icon_state = "sheet" speak = list("WoooOOOooo~","AUUUUUUUUUUUUUUUUUU") emote_see = list("stumbles around.", "shivers.") emote_hear = list("howls!","groans.") - desc = "Spooky!" - obj_icon_state = "sheet" /datum/dog_fashion/head/santa name = "Santa's Corgi Helper" + desc = "They're very fond of milk and cookies." emote_hear = list("barks Christmas songs.", "yaps merrily!") emote_see = list("looks for presents.", "checks their list.") - desc = "They're very fond of milk and cookies." /datum/dog_fashion/head/cargo_tech - name = "Corgi Tech REAL_NAME" + name = "Corgi Tech %REAL_NAME%" desc = "The reason your yellow gloves have chew-marks." /datum/dog_fashion/head/reindeer - name = "REAL_NAME the red-nosed Corgi" - emote_hear = list("lights the way!", "illuminates.", "yaps!") + name = "%REAL_NAME% the red-nosed Corgi" desc = "They have a very shiny nose." + emote_hear = list("lights the way!", "illuminates.", "yaps!") /datum/dog_fashion/head/sombrero - name = "Segnor REAL_NAME" - desc = "You must respect Elder Dogname" - -/datum/dog_fashion/head/sombrero/New(mob/M) - ..() - desc = "You must respect Elder [M.real_name]." + name = "Segnor %REAL_NAME%" + desc = "You must respect Elder %REAL_NAME%." /datum/dog_fashion/head/hop - name = "Lieutenant REAL_NAME" + name = "Lieutenant %REAL_NAME%" desc = "Can actually be trusted to not run off on their own." /datum/dog_fashion/head/deathsquad - name = "Trooper REAL_NAME" + name = "Trooper %REAL_NAME%" desc = "That's not red paint. That's real corgi blood." /datum/dog_fashion/head/clown - name = "REAL_NAME the Clown" + name = "%REAL_NAME% the Clown" desc = "Honkman's best friend." speak = list("HONK!", "Honk!") emote_see = list("plays tricks.", "slips.") /datum/dog_fashion/head/festive - name = "Festive REAL_NAME" + name = "Festive %REAL_NAME%" desc = "Ready to party!" obj_icon_state = "festive" /datum/dog_fashion/head/pumpkin/unlit - name = "Headless HoP-less REAL_NAME" + name = "Headless HoP-less %REAL_NAME%" desc = "A spooky dog spirit of a beloved pet who lost their owner." obj_icon_state = "pumpkin0" speak = list("BOO!", "AUUUUUUU", "RAAARGH!") @@ -202,7 +219,7 @@ obj_icon_state = "pumpkin1" /datum/dog_fashion/head/blumpkin/unlit - name = "Hue-less Headless HoP-less REAL_NAME" + name = "Hue-less Headless HoP-less %REAL_NAME%" desc = "An evil dog spirit of a beloved pet that haunts your treats pantries!" obj_icon_state = "blumpkin0" speak = list("BOO!", "AUUUUUUU", "RAAARGH!") @@ -211,3 +228,11 @@ /datum/dog_fashion/head/blumpkin/lit obj_icon_state = "blumpkin1" + +/datum/dog_fashion/head/butter + name = "Butter %REAL_NAME%" + desc = "%NAME%. %CAPITAL_REAL_NAME% with the butter. %NAME%. %CAPITAL_REAL_NAME% with a butter on 'em." + obj_icon_state = "butter" + speak = list() //they're very patient and focused on holding the butter on 'em + emote_see = list("shakes a little.", "looks around.") + emote_hear = list("licks a trickle of the butter up.", "smiles.") diff --git a/code/game/objects/items/food/misc.dm b/code/game/objects/items/food/misc.dm index d07e4eac079..20450b8c281 100644 --- a/code/game/objects/items/food/misc.dm +++ b/code/game/objects/items/food/misc.dm @@ -238,6 +238,7 @@ tastes = list("butter" = 1) foodtypes = DAIRY w_class = WEIGHT_CLASS_SMALL + dog_fashion = /datum/dog_fashion/head/butter /obj/item/food/butter/examine(mob/user) . = ..() diff --git a/code/modules/mob/living/basic/basic.dm b/code/modules/mob/living/basic/basic.dm index 363983c5cd1..97cb56bb61c 100644 --- a/code/modules/mob/living/basic/basic.dm +++ b/code/modules/mob/living/basic/basic.dm @@ -151,6 +151,7 @@ transform = transform.Turn(180) if(!(basic_mob_flags & REMAIN_DENSE_WHILE_DEAD)) ADD_TRAIT(src, TRAIT_UNDENSE, BASIC_MOB_DEATH_TRAIT) + SEND_SIGNAL(src, COMSIG_BASICMOB_LOOK_DEAD) /mob/living/basic/revive(full_heal_flags = NONE, excess_healing = 0, force_grab_ghost = FALSE) . = ..() @@ -165,6 +166,7 @@ transform = transform.Turn(180) if(!(basic_mob_flags & REMAIN_DENSE_WHILE_DEAD)) REMOVE_TRAIT(src, TRAIT_UNDENSE, BASIC_MOB_DEATH_TRAIT) + SEND_SIGNAL(src, COMSIG_BASICMOB_LOOK_ALIVE) /mob/living/basic/update_sight() lighting_color_cutoffs = list(lighting_cutoff_red, lighting_cutoff_green, lighting_cutoff_blue) diff --git a/code/modules/mob/living/basic/pets/dog.dm b/code/modules/mob/living/basic/pets/dog.dm deleted file mode 100644 index 7bdd65bd767..00000000000 --- a/code/modules/mob/living/basic/pets/dog.dm +++ /dev/null @@ -1,869 +0,0 @@ -//Dogs. - -// Add 'walkies' as valid input -/datum/pet_command/follow/dog - speech_commands = list("heel", "follow", "walkies") - -// Add 'good dog' as valid input -/datum/pet_command/good_boy/dog - speech_commands = list("good dog") - -// Set correct attack behaviour -/datum/pet_command/point_targetting/attack/dog - attack_behaviour = /datum/ai_behavior/basic_melee_attack/dog - -/datum/pet_command/point_targetting/attack/dog/set_command_active(mob/living/parent, mob/living/commander) - . = ..() - parent.ai_controller.set_blackboard_key(BB_DOG_HARASS_HARM, TRUE) - -/mob/living/basic/pet/dog - mob_biotypes = MOB_ORGANIC|MOB_BEAST - response_help_continuous = "pets" - response_help_simple = "pet" - response_disarm_continuous = "bops" - response_disarm_simple = "bop" - response_harm_continuous = "kicks" - response_harm_simple = "kick" - speak_emote = list("barks", "woofs") - faction = list(FACTION_NEUTRAL) - can_be_held = TRUE - ai_controller = /datum/ai_controller/basic_controller/dog - // The dog attack pet command can raise melee attack above 0 - attack_verb_continuous = "bites" - attack_verb_simple = "bite" - attack_sound = 'sound/weapons/bite.ogg' - attack_vis_effect = ATTACK_EFFECT_BITE - /// Instructions you can give to dogs - var/static/list/pet_commands = list( - /datum/pet_command/idle, - /datum/pet_command/free, - /datum/pet_command/good_boy/dog, - /datum/pet_command/follow/dog, - /datum/pet_command/point_targetting/attack/dog, - /datum/pet_command/point_targetting/fetch, - /datum/pet_command/play_dead, - ) - -/mob/living/basic/pet/dog/Initialize(mapload) - . = ..() - AddElement(/datum/element/pet_bonus, "woofs happily!") - AddElement(/datum/element/footstep, FOOTSTEP_MOB_CLAW) - AddElement(/datum/element/unfriend_attacker, untamed_reaction = "%SOURCE% fixes %TARGET% with a look of betrayal.") - AddComponent(/datum/component/tameable, food_types = list(/obj/item/food/meat/slab/human/mutant/skeleton, /obj/item/stack/sheet/bone), tame_chance = 30, bonus_tame_chance = 15, after_tame = CALLBACK(src, PROC_REF(tamed)), unique = FALSE) - AddComponent(/datum/component/obeys_commands, pet_commands) - -/mob/living/basic/pet/dog/proc/update_dog_speech(datum/ai_planning_subtree/random_speech/speech) - speech.speak = string_list(list("YAP", "Woof!", "Bark!", "AUUUUUU")) - speech.emote_hear = string_list(list("barks!", "woofs!", "yaps.","pants.")) - speech.emote_see = string_list(list("shakes [p_their()] head.", "chases [p_their()] tail.","shivers.")) - -/mob/living/basic/pet/dog/proc/tamed(mob/living/tamer) - visible_message(span_notice("[src] licks at [tamer] in a friendly manner!")) - -//Corgis and pugs are now under one dog subtype - -/mob/living/basic/pet/dog/corgi - name = "\improper corgi" - real_name = "corgi" - desc = "They're a corgi." - icon_state = "corgi" - icon_living = "corgi" - icon_dead = "corgi_dead" - held_state = "corgi" - butcher_results = list(/obj/item/food/meat/slab/corgi = 3, /obj/item/stack/sheet/animalhide/corgi = 1) - gold_core_spawnable = FRIENDLY_SPAWN - collar_icon_state = "corgi" - ai_controller = /datum/ai_controller/basic_controller/dog/corgi - var/obj/item/inventory_head - var/obj/item/inventory_back - /// Access card for Ian. - var/obj/item/card/id/access_card = null - var/shaved = FALSE - var/nofur = FALSE //Corgis that have risen past the material plane of existence. - /// Is this corgi physically slow due to age, etc? - var/is_slow = FALSE - -/mob/living/basic/pet/dog/corgi/examine(mob/user) - . = ..() - if(access_card) - . += "There appears to be [icon2html(access_card, user)] \a [access_card] pinned to [p_them()]." - -/mob/living/basic/pet/dog/corgi/Destroy() - QDEL_NULL(inventory_head) - QDEL_NULL(inventory_back) - QDEL_NULL(access_card) - return ..() - -/mob/living/basic/pet/dog/corgi/gib() - if(inventory_head) - inventory_head.forceMove(drop_location()) - inventory_head = null - if(inventory_back) - inventory_back.forceMove(drop_location()) - inventory_back = null - if(access_card) - access_card.forceMove(drop_location()) - access_card = null - return ..() - -/mob/living/basic/pet/dog/corgi/deadchat_plays(mode = ANARCHY_MODE, cooldown = 12 SECONDS) - . = AddComponent(/datum/component/deadchat_control/cardinal_movement, mode, list( - "speak" = CALLBACK(src, PROC_REF(bork)), - "wear_hat" = CALLBACK(src, PROC_REF(find_new_hat)), - "drop_hat" = CALLBACK(src, PROC_REF(drop_hat)), - "spin" = CALLBACK(src, TYPE_PROC_REF(/mob, emote), "spin")), cooldown, CALLBACK(src, PROC_REF(stop_deadchat_plays))) - - if(. == COMPONENT_INCOMPATIBLE) - return - - // Stop all automated behavior. - QDEL_NULL(ai_controller) - -///Deadchat bark. -/mob/living/basic/pet/dog/corgi/proc/bork() - var/emote = pick("barks!", "woofs!", "yaps.","pants.") - - manual_emote(emote) - -///Deadchat plays command that picks a new hat for Ian. -/mob/living/basic/pet/dog/corgi/proc/find_new_hat() - if(!isturf(loc)) - return - var/list/possible_headwear = list() - for(var/obj/item/item in loc) - if(ispath(item.dog_fashion, /datum/dog_fashion/head)) - possible_headwear += item - if(!length(possible_headwear)) - for(var/obj/item/item in orange(1)) - if(ispath(item.dog_fashion, /datum/dog_fashion/head) && CanReach(item)) - possible_headwear += item - if(!length(possible_headwear)) - return - if(inventory_head) - inventory_head.forceMove(drop_location()) - inventory_head = null - place_on_head(pick(possible_headwear)) - visible_message(span_notice("[src] puts [inventory_head] on [p_their()] own head, somehow.")) - -///Deadchat plays command that drops the current hat off Ian. -/mob/living/basic/pet/dog/corgi/proc/drop_hat() - if(!inventory_head) - return - visible_message(span_notice("[src] vigorously shakes [p_their()] head, dropping [inventory_head] to the ground.")) - inventory_head.forceMove(drop_location()) - inventory_head = null - update_corgi_fluff() - regenerate_icons() - -///Turn AI back on. -/mob/living/basic/pet/dog/corgi/proc/stop_deadchat_plays() - var/controller_type = initial(ai_controller) - ai_controller = new controller_type(src) - ai_controller?.set_blackboard_key(BB_DOG_IS_SLOW, is_slow) - -/mob/living/basic/pet/dog/corgi/handle_atom_del(atom/A) - if(A == inventory_head) - inventory_head = null - update_corgi_fluff() - regenerate_icons() - if(A == inventory_back) - inventory_back = null - update_corgi_fluff() - regenerate_icons() - return ..() - -/mob/living/basic/pet/dog/pug - name = "\improper pug" - real_name = "pug" - desc = "They're a pug." - icon = 'icons/mob/simple/pets.dmi' - icon_state = "pug" - icon_living = "pug" - icon_dead = "pug_dead" - butcher_results = list(/obj/item/food/meat/slab/pug = 3) - gold_core_spawnable = FRIENDLY_SPAWN - collar_icon_state = "pug" - held_state = "pug" - -/mob/living/basic/pet/dog/pug/Initialize(mapload) - . = ..() - - AddElement(/datum/element/swabable, CELL_LINE_TABLE_PUG, CELL_VIRUS_TABLE_GENERIC_MOB, 1, 5) - -/mob/living/basic/pet/dog/pug/mcgriff - name = "McGriff" - desc = "This dog can tell something smells around here, and that something is CRIME!" - gold_core_spawnable = NO_SPAWN - unique_pet = TRUE - -/mob/living/basic/pet/dog/bullterrier - name = "\improper bull terrier" - real_name = "bull terrier" - desc = "They're a bull terrier." - icon = 'icons/mob/simple/pets.dmi' - icon_state = "bullterrier" - icon_living = "bullterrier" - icon_dead = "bullterrier_dead" - butcher_results = list(/obj/item/food/meat/slab/corgi = 3) // Would feel redundant to add more new dog meats. - gold_core_spawnable = FRIENDLY_SPAWN - collar_icon_state = "bullterrier" - held_state = "bullterrier" - -/mob/living/basic/pet/dog/corgi/exoticcorgi - name = "Exotic Corgi" - desc = "As cute as they are colorful!" - icon = 'icons/mob/simple/pets.dmi' - icon_state = "corgigrey" - icon_living = "corgigrey" - icon_dead = "corgigrey_dead" - nofur = TRUE - -/mob/living/basic/pet/dog/Initialize(mapload) - . = ..() - var/dog_area = get_area(src) - for(var/obj/structure/bed/dogbed/D in dog_area) - if(D.update_owner(src)) //No muscling in on my turf you fucking parrot - break - -/mob/living/basic/pet/dog/corgi/Initialize(mapload) - . = ..() - regenerate_icons() - AddElement(/datum/element/strippable, GLOB.strippable_corgi_items) - AddElement(/datum/element/swabable, CELL_LINE_TABLE_CORGI, CELL_VIRUS_TABLE_GENERIC_MOB, 1, 5) - RegisterSignal(src, COMSIG_MOB_TRIED_ACCESS, PROC_REF(on_tried_access)) - -/** - * Handler for COMSIG_MOB_TRIED_ACCESS - */ -/mob/living/basic/pet/dog/corgi/proc/on_tried_access(mob/accessor, obj/locked_thing) - SIGNAL_HANDLER - - return locked_thing?.check_access(access_card) ? ACCESS_ALLOWED : ACCESS_DISALLOWED - -/mob/living/basic/pet/dog/corgi/exoticcorgi/Initialize(mapload) - . = ..() - var/newcolor = rgb(rand(0, 255), rand(0, 255), rand(0, 255)) - add_atom_colour(newcolor, FIXED_COLOUR_PRIORITY) - -/mob/living/basic/pet/dog/corgi/death(gibbed) - ..(gibbed) - regenerate_icons() - -GLOBAL_LIST_INIT(strippable_corgi_items, create_strippable_list(list( - /datum/strippable_item/corgi_head, - /datum/strippable_item/corgi_back, - /datum/strippable_item/pet_collar, - /datum/strippable_item/corgi_id, -))) - -/datum/strippable_item/corgi_head - key = STRIPPABLE_ITEM_HEAD - -/datum/strippable_item/corgi_head/get_item(atom/source) - var/mob/living/basic/pet/dog/corgi/corgi_source = source - if (!istype(corgi_source)) - return - - return corgi_source.inventory_head - -/datum/strippable_item/corgi_head/finish_equip(atom/source, obj/item/equipping, mob/user) - var/mob/living/basic/pet/dog/corgi/corgi_source = source - if (!istype(corgi_source)) - return - - corgi_source.place_on_head(equipping, user) - -/datum/strippable_item/corgi_head/finish_unequip(atom/source, mob/user) - var/mob/living/basic/pet/dog/corgi/corgi_source = source - if (!istype(corgi_source)) - return - - user.put_in_hands(corgi_source.inventory_head) - corgi_source.inventory_head = null - corgi_source.update_corgi_fluff() - corgi_source.regenerate_icons() - -/datum/strippable_item/pet_collar - key = STRIPPABLE_ITEM_PET_COLLAR - -/datum/strippable_item/pet_collar/get_item(atom/source) - var/mob/living/basic/pet/pet_source = source - if (!istype(pet_source)) - return - - return pet_source.collar - -/datum/strippable_item/pet_collar/try_equip(atom/source, obj/item/equipping, mob/user) - . = ..() - if (!.) - return FALSE - - if (!istype(equipping, /obj/item/clothing/neck/petcollar)) - to_chat(user, span_warning("That's not a collar.")) - return FALSE - - return TRUE - -/datum/strippable_item/pet_collar/finish_equip(atom/source, obj/item/equipping, mob/user) - var/mob/living/basic/pet/pet_source = source - if (!istype(pet_source)) - return - - pet_source.add_collar(equipping, user) - -/datum/strippable_item/pet_collar/finish_unequip(atom/source, mob/user) - var/mob/living/basic/pet/pet_source = source - if (!istype(pet_source)) - return - - var/obj/collar = pet_source.remove_collar(user.drop_location()) - user.put_in_hands(collar) - -/datum/strippable_item/corgi_back - key = STRIPPABLE_ITEM_BACK - -/datum/strippable_item/corgi_back/get_item(atom/source) - var/mob/living/basic/pet/dog/corgi/corgi_source = source - if (!istype(corgi_source)) - return - - return corgi_source.inventory_back - -/datum/strippable_item/corgi_back/try_equip(atom/source, obj/item/equipping, mob/user) - . = ..() - if (!.) - return FALSE - - if (!ispath(equipping.dog_fashion, /datum/dog_fashion/back)) - to_chat(user, span_warning("You set [equipping] on [source]'s back, but it falls off!")) - equipping.forceMove(source.drop_location()) - if (prob(25)) - step_rand(equipping) - dance_rotate(source, set_original_dir = TRUE) - - return FALSE - - return TRUE - -/datum/strippable_item/corgi_back/finish_equip(atom/source, obj/item/equipping, mob/user) - var/mob/living/basic/pet/dog/corgi/corgi_source = source - if (!istype(corgi_source)) - return - - equipping.forceMove(corgi_source) - corgi_source.inventory_back = equipping - corgi_source.update_corgi_fluff() - corgi_source.regenerate_icons() - -/datum/strippable_item/corgi_back/finish_unequip(atom/source, mob/user) - var/mob/living/basic/pet/dog/corgi/corgi_source = source - if (!istype(corgi_source)) - return - - user.put_in_hands(corgi_source.inventory_back) - corgi_source.inventory_back = null - corgi_source.update_corgi_fluff() - corgi_source.regenerate_icons() - -/datum/strippable_item/corgi_id - key = STRIPPABLE_ITEM_ID - -/datum/strippable_item/corgi_id/get_item(atom/source) - var/mob/living/basic/pet/dog/corgi/corgi_source = source - if (!istype(corgi_source)) - return - - return corgi_source.access_card - -/datum/strippable_item/corgi_id/try_equip(atom/source, obj/item/equipping, mob/user) - . = ..() - if (!.) - return FALSE - - if (!isidcard(equipping)) - to_chat(user, span_warning("You can't pin [equipping] to [source]!")) - return FALSE - - return TRUE - -/datum/strippable_item/corgi_id/finish_equip(atom/source, obj/item/equipping, mob/user) - var/mob/living/basic/pet/dog/corgi/corgi_source = source - if (!istype(corgi_source)) - return - - equipping.forceMove(source) - corgi_source.access_card = equipping - -/datum/strippable_item/corgi_id/finish_unequip(atom/source, mob/user) - var/mob/living/basic/pet/dog/corgi/corgi_source = source - if (!istype(corgi_source)) - return - - user.put_in_hands(corgi_source.access_card) - corgi_source.access_card = null - corgi_source.update_corgi_fluff() - corgi_source.regenerate_icons() - -/mob/living/basic/pet/dog/corgi/getarmor(def_zone, type) - var/armorval = 0 - - if(def_zone) - if(def_zone == BODY_ZONE_HEAD) - if(inventory_head) - armorval = inventory_head.get_armor_rating(type) - else - if(inventory_back) - armorval = inventory_back.get_armor_rating(type) - return armorval - else - if(inventory_head) - armorval += inventory_head.get_armor_rating(type) - if(inventory_back) - armorval += inventory_back.get_armor_rating(type) - return armorval*0.5 - -/mob/living/basic/pet/dog/corgi/attackby(obj/item/O, mob/user, params) - if (istype(O, /obj/item/razor)) - if (shaved) - to_chat(user, span_warning("You can't shave this corgi, [p_they()] has already been shaved!")) - return - if (nofur) - to_chat(user, span_warning("You can't shave this corgi, [p_they()] [p_do()]n't have a fur coat!")) - return - user.visible_message(span_notice("[user] starts to shave [src] using \the [O]."), span_notice("You start to shave [src] using \the [O]...")) - if(do_after(user, 50, target = src)) - user.visible_message(span_notice("[user] shaves [src]'s hair using \the [O].")) - playsound(loc, 'sound/items/welder2.ogg', 20, TRUE) - shaved = TRUE - icon_living = "[initial(icon_living)]_shaved" - icon_dead = "[initial(icon_living)]_shaved_dead" - if(stat == CONSCIOUS) - icon_state = icon_living - else - icon_state = icon_dead - return - ..() - update_corgi_fluff() - -//Corgis are supposed to be simpler, so only a select few objects can actually be put -//to be compatible with them. The objects are below. -//Many hats added, Some will probably be removed, just want to see which ones are popular. -// > some will probably be removed - -/mob/living/basic/pet/dog/corgi/proc/place_on_head(obj/item/item_to_add, mob/living/user) - if(inventory_head) - if(user) - to_chat(user, span_warning("You can't put more than one hat on [src]!")) - return - if(!item_to_add) - user.visible_message(span_notice("[user] pets [src]."), span_notice("You rest your hand on [src]'s head for a moment.")) - if(flags_1 & HOLOGRAM_1) - return - user.add_mood_event(REF(src), /datum/mood_event/pet_animal, src) - return - - if(user && !user.temporarilyRemoveItemFromInventory(item_to_add)) - to_chat(user, span_warning("\The [item_to_add] is stuck to your hand, you cannot put it on [src]'s head!")) - return - - var/valid = FALSE - if(ispath(item_to_add.dog_fashion, /datum/dog_fashion/head)) - valid = TRUE - - //Various hats and items (worn on his head) change Ian's behaviour. His attributes are reset when a hat is removed. - - if(valid) - if(health <= 0) - to_chat(user, span_notice("There is merely a dull, lifeless look in [real_name]'s eyes as you put the [item_to_add] on [p_them()].")) - else if(user) - user.visible_message(span_notice("[user] puts [item_to_add] on [real_name]'s head. [src] looks at [user] and barks once."), - span_notice("You put [item_to_add] on [real_name]'s head. [src] gives you a peculiar look, then wags [p_their()] tail once and barks."), - span_hear("You hear a friendly-sounding bark.")) - item_to_add.forceMove(src) - src.inventory_head = item_to_add - update_corgi_fluff() - regenerate_icons() - else - to_chat(user, span_warning("You set [item_to_add] on [src]'s head, but it falls off!")) - item_to_add.forceMove(drop_location()) - if(prob(25)) - step_rand(item_to_add) - dance_rotate(src, set_original_dir=TRUE) - - return valid - -/mob/living/basic/pet/dog/corgi/proc/update_corgi_fluff() - // First, change back to defaults - name = real_name - desc = initial(desc) - // BYOND/DM doesn't support the use of initial on lists. - speak_emote = list("barks", "woofs") - desc = initial(desc) - set_light(0) - - if(inventory_head?.dog_fashion) - var/datum/dog_fashion/DF = new inventory_head.dog_fashion(src) - DF.apply(src) - - if(inventory_back?.dog_fashion) - var/datum/dog_fashion/DF = new inventory_back.dog_fashion(src) - DF.apply(src) - -/mob/living/basic/pet/dog/corgi/update_dog_speech(datum/ai_planning_subtree/random_speech/speech) - ..() - - if(inventory_head?.dog_fashion) - var/datum/dog_fashion/DF = new inventory_head.dog_fashion(src) - DF.apply_to_speech(speech) - - if(inventory_back?.dog_fashion) - var/datum/dog_fashion/DF = new inventory_back.dog_fashion(src) - DF.apply_to_speech(speech) - -//IAN! SQUEEEEEEEEE~ -/mob/living/basic/pet/dog/corgi/ian - name = "Ian" - real_name = "Ian" //Intended to hold the name without altering it. - gender = MALE - desc = "He's the HoP's beloved corgi." - response_help_continuous = "pets" - response_help_simple = "pet" - response_disarm_continuous = "bops" - response_disarm_simple = "bop" - response_harm_continuous = "kicks" - response_harm_simple = "kick" - gold_core_spawnable = NO_SPAWN - unique_pet = TRUE - var/age = 0 - var/record_age = 1 - var/memory_saved = FALSE - var/saved_head //path - -/mob/living/basic/pet/dog/corgi/ian/Initialize(mapload) - . = ..() - // Ensure Ian exists - REGISTER_REQUIRED_MAP_ITEM(1, 1) - - //parent call must happen first to ensure IAN - //is not in nullspace when child puppies spawn - Read_Memory() - if(age == 0) - var/turf/target = get_turf(loc) - if(target) - new /mob/living/basic/pet/dog/corgi/puppy/ian(target) - Write_Memory(FALSE) - return INITIALIZE_HINT_QDEL - else if(age == record_age) - icon_state = "old_corgi" - icon_living = "old_corgi" - held_state = "old_corgi" - icon_dead = "old_corgi_dead" - desc = "At a ripe old age of [record_age], Ian's not as spry as he used to be, but he'll always be the HoP's beloved corgi." //RIP - ai_controller?.set_blackboard_key(BB_DOG_IS_SLOW, TRUE) - is_slow = TRUE - speed = 2 - -/mob/living/basic/pet/dog/corgi/ian/Life(seconds_per_tick = SSMOBS_DT, times_fired) - if(!stat && SSticker.current_state == GAME_STATE_FINISHED && !memory_saved) - Write_Memory(FALSE) - memory_saved = TRUE - ..() - -/mob/living/basic/pet/dog/corgi/ian/death() - if(!memory_saved) - Write_Memory(TRUE) - ..() - -/mob/living/basic/pet/dog/corgi/ian/proc/Read_Memory() - if(fexists("data/npc_saves/Ian.sav")) //legacy compatability to convert old format to new - var/savefile/S = new /savefile("data/npc_saves/Ian.sav") - S["age"] >> age - S["record_age"] >> record_age - S["saved_head"] >> saved_head - fdel("data/npc_saves/Ian.sav") - else - var/json_file = file("data/npc_saves/Ian.json") - if(!fexists(json_file)) - return - var/list/json = json_decode(file2text(json_file)) - age = json["age"] - record_age = json["record_age"] - saved_head = json["saved_head"] - if(isnull(age)) - age = 0 - if(isnull(record_age)) - record_age = 1 - if(saved_head) - place_on_head(new saved_head) - -/mob/living/basic/pet/dog/corgi/ian/Write_Memory(dead, gibbed) - . = ..() - if(!.) - return - var/json_file = file("data/npc_saves/Ian.json") - var/list/file_data = list() - if(!dead) - file_data["age"] = age + 1 - if((age + 1) > record_age) - file_data["record_age"] = record_age + 1 - else - file_data["record_age"] = record_age - if(inventory_head) - file_data["saved_head"] = inventory_head.type - else - file_data["saved_head"] = null - else - file_data["age"] = 0 - file_data["record_age"] = record_age - file_data["saved_head"] = null - fdel(json_file) - WRITE_FILE(json_file, json_encode(file_data)) - -/mob/living/basic/pet/dog/corgi/ian/narsie_act() - playsound(src, 'sound/magic/demon_dies.ogg', 75, TRUE) - var/mob/living/basic/pet/dog/corgi/narsie/N = new(loc) - N.setDir(dir) - investigate_log("has been gibbed by Nar'Sie.", INVESTIGATE_DEATHS) - gib() - -/mob/living/basic/pet/dog/corgi/narsie - name = "Nars-Ian" - desc = "Ia! Ia!" - icon_state = "narsian" - icon_living = "narsian" - icon_dead = "narsian_dead" - faction = list(FACTION_NEUTRAL, FACTION_CULT) - gold_core_spawnable = NO_SPAWN - nofur = TRUE - unique_pet = TRUE - held_state = "narsian" - -/mob/living/basic/pet/dog/corgi/narsie/Life(seconds_per_tick = SSMOBS_DT, times_fired) - ..() - for(var/mob/living/simple_animal/pet/P in range(1, src)) - if(P != src && !istype(P,/mob/living/basic/pet/dog/corgi/narsie)) - visible_message(span_warning("[src] devours [P]!"), \ - "DELICIOUS SOULS") - playsound(src, 'sound/magic/demon_attack1.ogg', 75, TRUE) - narsie_act() - P.gib() - for(var/mob/living/basic/pet/P in range(1, src)) - if(P != src && !istype(P,/mob/living/basic/pet/dog/corgi/narsie)) - visible_message(span_warning("[src] devours [P]!"), \ - "DELICIOUS SOULS") - playsound(src, 'sound/magic/demon_attack1.ogg', 75, TRUE) - narsie_act() - P.investigate_log("has been gibbed by [src].", INVESTIGATE_DEATHS) - P.gib() - -/mob/living/basic/pet/dog/corgi/narsie/update_corgi_fluff() - ..() - speak_emote = list("growls", "barks ominously") - -/mob/living/basic/pet/dog/corgi/narsie/update_dog_speech(datum/ai_planning_subtree/random_speech/speech) - speech.speak = string_list(list("Tari'karat-pasnar!", "IA! IA!", "BRRUUURGHGHRHR")) - speech.emote_hear = string_list(list("barks echoingly!", "woofs hauntingly!", "yaps in an eldritch manner.", "mutters something unspeakable.")) - speech.emote_see = string_list(list("communes with the unnameable.", "ponders devouring some souls.", "shakes.")) - -/mob/living/basic/pet/dog/corgi/narsie/narsie_act() - adjustBruteLoss(-maxHealth) - - -/mob/living/basic/pet/dog/corgi/regenerate_icons() - ..() - cut_overlays() //we are redrawing the mob after all - if(inventory_head) - var/image/head_icon - var/datum/dog_fashion/DF = new inventory_head.dog_fashion(src) - - if(!DF.obj_icon_state) - DF.obj_icon_state = inventory_head.icon_state - if(!DF.obj_alpha) - DF.obj_alpha = inventory_head.alpha - if(!DF.obj_color) - DF.obj_color = inventory_head.color - - if(health <= 0) - head_icon = DF.get_overlay(dir = EAST) - head_icon.pixel_y = -8 - head_icon.transform = turn(head_icon.transform, 180) - else - head_icon = DF.get_overlay() - - add_overlay(head_icon) - - if(inventory_back) - var/image/back_icon - var/datum/dog_fashion/DF = new inventory_back.dog_fashion(src) - - if(!DF.obj_icon_state) - DF.obj_icon_state = inventory_back.icon_state - if(!DF.obj_alpha) - DF.obj_alpha = inventory_back.alpha - if(!DF.obj_color) - DF.obj_color = inventory_back.color - - if(health <= 0) - back_icon = DF.get_overlay(dir = EAST) - back_icon.pixel_y = -11 - back_icon.transform = turn(back_icon.transform, 180) - else - back_icon = DF.get_overlay() - add_overlay(back_icon) - - return - - - -/mob/living/basic/pet/dog/corgi/puppy - name = "\improper corgi puppy" - real_name = "corgi" - desc = "They're a corgi puppy!" - icon_state = "puppy" - icon_living = "puppy" - icon_dead = "puppy_dead" - density = FALSE - pass_flags = PASSMOB - mob_size = MOB_SIZE_SMALL - collar_icon_state = "puppy" - -//puppies cannot wear anything. -/mob/living/basic/pet/dog/corgi/puppy/Topic(href, href_list) - if(href_list["remove_inv"] || href_list["add_inv"]) - to_chat(usr, span_warning("You can't fit this on [src], [p_they()] [p_are()] too small!")) - return - ..() - -//PUPPY IAN! SQUEEEEEEEEE~ -/mob/living/basic/pet/dog/corgi/puppy/ian - name = "Ian" - real_name = "Ian" - gender = MALE - desc = "He's the HoP's beloved corgi puppy." - - -/mob/living/basic/pet/dog/corgi/puppy/void //Tribute to the corgis born in nullspace - name = "\improper void puppy" - real_name = "voidy" - desc = "A corgi puppy that has been infused with deep space energy. It's staring back..." - gender = NEUTER - icon_state = "void_puppy" - icon_living = "void_puppy" - icon_dead = "void_puppy_dead" - nofur = TRUE - held_state = "void_puppy" - unsuitable_atmos_damage = 0 - minimum_survivable_temperature = TCMB - maximum_survivable_temperature = T0C + 40 - -/mob/living/basic/pet/dog/corgi/puppy/void/Initialize(mapload) - . = ..() - ADD_TRAIT(src, TRAIT_AI_BAGATTACK, INNATE_TRAIT) - -/mob/living/basic/pet/dog/corgi/puppy/void/Process_Spacemove(movement_dir = 0, continuous_move = FALSE) - return 1 //Void puppies can navigate space. - -//LISA! SQUEEEEEEEEE~ -/mob/living/basic/pet/dog/corgi/lisa - name = "Lisa" - real_name = "Lisa" - gender = FEMALE - desc = "She's tearing you apart." - gold_core_spawnable = NO_SPAWN - unique_pet = TRUE - icon_state = "lisa" - icon_living = "lisa" - icon_dead = "lisa_dead" - response_help_continuous = "pets" - response_help_simple = "pet" - response_disarm_continuous = "bops" - response_disarm_simple = "bop" - response_harm_continuous = "kicks" - response_harm_simple = "kick" - held_state = "lisa" - var/puppies = 0 - -//Lisa already has a cute bow! -/mob/living/basic/pet/dog/corgi/lisa/Topic(href, href_list) - if(href_list["remove_inv"] || href_list["add_inv"]) - to_chat(usr, span_warning("[src] already has a cute bow!")) - return - ..() - -/mob/living/basic/pet/dog/breaddog //Most of the code originates from Cak - name = "Kobun" - desc = "It is a dog made out of bread. 'The universe is definitely half full'." - icon_state = "breaddog" - icon_living = "breaddog" - icon_dead = "breaddog_dead" - head_icon = 'icons/mob/clothing/head/pets_head.dmi' - health = 50 - maxHealth = 50 - gender = NEUTER - damage_coeff = list(BRUTE = 3, BURN = 3, TOX = 1, CLONE = 1, STAMINA = 0, OXY = 1) - butcher_results = list(/obj/item/organ/internal/brain = 1, /obj/item/organ/internal/heart = 1, /obj/item/food/breadslice/plain = 3, \ - /obj/item/food/meat/slab = 2) - response_harm_continuous = "takes a bite out of" - response_harm_simple = "take a bite out of" - attacked_sound = 'sound/items/eatfood.ogg' - held_state = "breaddog" - worn_slot_flags = ITEM_SLOT_HEAD - -/mob/living/basic/pet/dog/breaddog/CheckParts(list/parts) - ..() - var/obj/item/organ/internal/brain/candidate = locate(/obj/item/organ/internal/brain) in contents - if(!candidate || !candidate.brainmob || !candidate.brainmob.mind) - return - candidate.brainmob.mind.transfer_to(src) - to_chat(src, "[span_boldbig("You are a bread dog!")] You're a harmless dog/bread hybrid that everyone loves. People can take bites out of you if they're hungry, but you regenerate health \ - so quickly that it generally doesn't matter. You're remarkably resilient to any damage besides this and it's hard for you to really die at all. You should go around and bring happiness and \ - free bread to the station! 'I’m not alone, and you aren’t either'") - var/default_name = "Kobun" - var/new_name = sanitize_name(reject_bad_text(tgui_input_text(src, "You are the [name]. Would you like to change your name to something else?", "Name change", default_name, MAX_NAME_LEN)), cap_after_symbols = FALSE) - if(new_name) - to_chat(src, span_notice("Your name is now [new_name]!")) - name = new_name - -/mob/living/basic/pet/dog/breaddog/Life(seconds_per_tick = SSMOBS_DT, times_fired) - ..() - if(stat) - return - - if(health < maxHealth) - adjustBruteLoss(-4 * seconds_per_tick) //Fast life regen - - for(var/mob/living/carbon/humanoid_entities in view(3, src)) //Mood aura which stay as long you do not wear Sanallite as hat or carry(I will try to make it work with hat someday(obviously weaker than normal one)) - humanoid_entities.add_mood_event("kobun", /datum/mood_event/kobun) - -/mob/living/basic/pet/dog/breaddog/attack_hand(mob/living/user, list/modifiers) - ..() - if(user.combat_mode && user.reagents && !stat) - user.reagents.add_reagent(/datum/reagent/consumable/nutriment, 0.4) - user.reagents.add_reagent(/datum/reagent/consumable/nutriment/vitamin, 0.4) - -/// A dog bone fully heals a dog, and befriends it if it's not your friend. -/obj/item/dog_bone - name = "jumbo dog bone" - desc = "A tasty femur full of juicy marrow, the perfect gift for your best friend." - w_class = WEIGHT_CLASS_SMALL - icon = 'icons/obj/food/meat.dmi' - icon_state = "skeletonmeat" - custom_materials = list(/datum/material/bone = SHEET_MATERIAL_AMOUNT * 4) - force = 3 - throwforce = 5 - attack_verb_continuous = list("attacks", "bashes", "batters", "bludgeons", "whacks") - attack_verb_simple = list("attack", "bash", "batter", "bludgeon", "whack") - -/obj/item/dog_bone/pre_attack(atom/target, mob/living/user, params) - if (!isdog(target) || user.combat_mode) - return ..() - var/mob/living/basic/pet/dog/dog_target = target - if (dog_target.stat != CONSCIOUS) - return ..() - dog_target.emote("spin") - dog_target.fully_heal() - if (dog_target.befriend(user)) - dog_target.tamed(user) - new /obj/effect/temp_visual/heart(target.loc) - qdel(src) - return TRUE diff --git a/code/modules/mob/living/basic/pets/dog/_dog.dm b/code/modules/mob/living/basic/pets/dog/_dog.dm new file mode 100644 index 00000000000..93947173cd9 --- /dev/null +++ b/code/modules/mob/living/basic/pets/dog/_dog.dm @@ -0,0 +1,94 @@ +// Add 'walkies' as valid input +/datum/pet_command/follow/dog + speech_commands = list("heel", "follow", "walkies") + +// Add 'good dog' as valid input +/datum/pet_command/good_boy/dog + speech_commands = list("good dog") + +// Set correct attack behaviour +/datum/pet_command/point_targetting/attack/dog + attack_behaviour = /datum/ai_behavior/basic_melee_attack/dog + +/datum/pet_command/point_targetting/attack/dog/set_command_active(mob/living/parent, mob/living/commander) + . = ..() + parent.ai_controller.set_blackboard_key(BB_DOG_HARASS_HARM, TRUE) + +//Dogs. + +/mob/living/basic/pet/dog + mob_biotypes = MOB_ORGANIC|MOB_BEAST + response_help_continuous = "pets" + response_help_simple = "pet" + response_disarm_continuous = "bops" + response_disarm_simple = "bop" + response_harm_continuous = "kicks" + response_harm_simple = "kick" + speak_emote = list("barks", "woofs") + faction = list(FACTION_NEUTRAL) + can_be_held = TRUE + ai_controller = /datum/ai_controller/basic_controller/dog + // The dog attack pet command can raise melee attack above 0 + attack_verb_continuous = "bites" + attack_verb_simple = "bite" + attack_sound = 'sound/weapons/bite.ogg' + attack_vis_effect = ATTACK_EFFECT_BITE + /// Instructions you can give to dogs + var/static/list/pet_commands = list( + /datum/pet_command/idle, + /datum/pet_command/free, + /datum/pet_command/good_boy/dog, + /datum/pet_command/follow/dog, + /datum/pet_command/point_targetting/attack/dog, + /datum/pet_command/point_targetting/fetch, + /datum/pet_command/play_dead, + ) + +/mob/living/basic/pet/dog/Initialize(mapload) + . = ..() + AddElement(/datum/element/pet_bonus, "woofs happily!") + AddElement(/datum/element/footstep, FOOTSTEP_MOB_CLAW) + AddElement(/datum/element/unfriend_attacker, untamed_reaction = "%SOURCE% fixes %TARGET% with a look of betrayal.") + AddComponent(/datum/component/tameable, food_types = list(/obj/item/food/meat/slab/human/mutant/skeleton, /obj/item/stack/sheet/bone), tame_chance = 30, bonus_tame_chance = 15, after_tame = CALLBACK(src, PROC_REF(tamed)), unique = FALSE) + AddComponent(/datum/component/obeys_commands, pet_commands) + var/dog_area = get_area(src) + for(var/obj/structure/bed/dogbed/dog_bed in dog_area) + if(dog_bed.update_owner(src)) //No muscling in on my turf you fucking parrot + break + +///Updates dog speech and emotes +/mob/living/basic/pet/dog/proc/update_dog_speech(datum/ai_planning_subtree/random_speech/speech) + speech.speak = string_list(list("YAP", "Woof!", "Bark!", "AUUUUUU")) + speech.emote_hear = string_list(list("barks!", "woofs!", "yaps.","pants.")) + speech.emote_see = string_list(list("shakes [p_their()] head.", "chases [p_their()] tail.","shivers.")) + +///Proc to run on a successful taming attempt +/mob/living/basic/pet/dog/proc/tamed(mob/living/tamer) + visible_message(span_notice("[src] licks at [tamer] in a friendly manner!")) + +/// A dog bone fully heals a dog, and befriends it if it's not your friend. +/obj/item/dog_bone + name = "jumbo dog bone" + desc = "A tasty femur full of juicy marrow, the perfect gift for your best friend." + w_class = WEIGHT_CLASS_SMALL + icon = 'icons/obj/food/meat.dmi' + icon_state = "skeletonmeat" + custom_materials = list(/datum/material/bone = SHEET_MATERIAL_AMOUNT * 4) + force = 3 + throwforce = 5 + attack_verb_continuous = list("attacks", "bashes", "batters", "bludgeons", "whacks") + attack_verb_simple = list("attack", "bash", "batter", "bludgeon", "whack") + +/obj/item/dog_bone/pre_attack(atom/target, mob/living/user, params) + if (!isdog(target) || user.combat_mode) + return ..() + var/mob/living/basic/pet/dog/dog_target = target + if (dog_target.stat != CONSCIOUS) + return ..() + dog_target.emote("spin") + dog_target.fully_heal() + if (dog_target.befriend(user)) + dog_target.tamed(user) + new /obj/effect/temp_visual/heart(target.loc) + qdel(src) + return TRUE diff --git a/code/modules/mob/living/basic/pets/dog/corgi.dm b/code/modules/mob/living/basic/pets/dog/corgi.dm new file mode 100644 index 00000000000..fe08d9648a6 --- /dev/null +++ b/code/modules/mob/living/basic/pets/dog/corgi.dm @@ -0,0 +1,555 @@ +//CORGIS! + +/mob/living/basic/pet/dog/corgi + name = "\improper corgi" + real_name = "corgi" + desc = "They're a corgi." + icon_state = "corgi" + icon_living = "corgi" + icon_dead = "corgi_dead" + held_state = "corgi" + butcher_results = list(/obj/item/food/meat/slab/corgi = 3, /obj/item/stack/sheet/animalhide/corgi = 1) + gold_core_spawnable = FRIENDLY_SPAWN + collar_icon_state = "corgi" + ai_controller = /datum/ai_controller/basic_controller/dog/corgi + ///Access card for the corgi. + var/obj/item/card/id/access_card = null + ///Can this corgi be shaved by an electric razor? + var/can_be_shaved = TRUE + ///Did this corgi get mutilated and has had their fur shaved by an electric razor, oh the humanity? + var/shaved = FALSE + ///Currently worn item on the head slot + var/obj/item/inventory_head = null + ///Currently worn item on the back slot + var/obj/item/inventory_back = null + ///Is this corgi physically slow due to age, etc? + var/is_slow = FALSE + ///Item slots that are available for this corgi to equip stuff into + var/list/strippable_inventory_slots = list() + +/mob/living/basic/pet/dog/corgi/Initialize(mapload) + . = ..() + update_appearance() + AddElement(/datum/element/strippable, length(strippable_inventory_slots) ? create_strippable_list(strippable_inventory_slots) : GLOB.strippable_corgi_items) + AddElement(/datum/element/swabable, CELL_LINE_TABLE_CORGI, CELL_VIRUS_TABLE_GENERIC_MOB, 1, 5) + RegisterSignal(src, COMSIG_MOB_TRIED_ACCESS, PROC_REF(on_tried_access)) + RegisterSignals(src, list(COMSIG_BASICMOB_LOOK_ALIVE, COMSIG_BASICMOB_LOOK_DEAD), PROC_REF(on_appearance_change)) + +/mob/living/basic/pet/dog/corgi/Destroy() + QDEL_NULL(inventory_head) + QDEL_NULL(inventory_back) + QDEL_NULL(access_card) + UnregisterSignal(src, list(COMSIG_BASICMOB_LOOK_ALIVE, COMSIG_BASICMOB_LOOK_DEAD)) + return ..() + +/mob/living/basic/pet/dog/corgi/handle_atom_del(atom/deleting_atom) + if(deleting_atom == inventory_head) + inventory_head = null + update_corgi_fluff() + update_appearance(UPDATE_OVERLAYS) + if(deleting_atom == inventory_back) + inventory_back = null + update_corgi_fluff() + update_appearance(UPDATE_OVERLAYS) + return ..() + +/mob/living/basic/pet/dog/corgi/gib() + if(inventory_head) + inventory_head.forceMove(drop_location()) + inventory_head = null + if(inventory_back) + inventory_back.forceMove(drop_location()) + inventory_back = null + if(access_card) + access_card.forceMove(drop_location()) + access_card = null + return ..() + +/mob/living/basic/pet/dog/corgi/examine(mob/user) + . = ..() + if(access_card) + . += "There appears to be [icon2html(access_card, user)] \a [access_card] pinned to [p_them()]." + +/** + * Corgis get full protection from their equipped fashion items if attacked in a way that passes def_zone, + * which usually means any direct attack like melee or gunshot. Anything abstract like a bomb or acid or something + * will instead give half the armor value. + */ +/mob/living/basic/pet/dog/corgi/getarmor(def_zone, type) + var/armorval = 0 + + if(def_zone) + if(def_zone == BODY_ZONE_HEAD) + if(inventory_head) + armorval = inventory_head.get_armor_rating(type) + else + if(inventory_back) + armorval = inventory_back.get_armor_rating(type) + return armorval + else + if(inventory_head) + armorval += inventory_head.get_armor_rating(type) + if(inventory_back) + armorval += inventory_back.get_armor_rating(type) + return armorval * 0.5 + +/mob/living/basic/pet/dog/corgi/attackby(obj/item/attacking_item, mob/user, params) + if(istype(attacking_item, /obj/item/razor)) + if(shaved) + to_chat(user, span_warning("You can't shave this corgi, [p_they()] has already been shaved!")) + return + if(!can_be_shaved) + to_chat(user, span_warning("You can't shave this corgi, [p_they()] [p_do()]n't have a fur coat!")) + return + user.visible_message(span_notice("[user] starts to shave [src] using \the [attacking_item]."), span_notice("You start to shave [src] using \the [attacking_item]...")) + if(do_after(user, 5 SECONDS, target = src)) + user.visible_message(span_notice("[user] shaves [src]'s hair using \the [attacking_item].")) + playsound(get_turf(src), 'sound/items/welder2.ogg', 20, TRUE) + shaved = TRUE + icon_living = "[icon_living]_shaved" + icon_dead = "[icon_living]_shaved_dead" + if(stat == CONSCIOUS) + icon_state = icon_living + else + icon_state = icon_dead + return TRUE + + return ..() + +/mob/living/basic/pet/dog/corgi/update_dog_speech(datum/ai_planning_subtree/random_speech/speech) + . = ..() + + if(inventory_head?.dog_fashion) + var/datum/dog_fashion/equipped_head_fashion_item = new inventory_head.dog_fashion(src) + equipped_head_fashion_item.apply_to_speech(speech) + + if(inventory_back?.dog_fashion) + var/datum/dog_fashion/equipped_back_fashion_item = new inventory_back.dog_fashion(src) + equipped_back_fashion_item.apply_to_speech(speech) + +/mob/living/basic/pet/dog/corgi/deadchat_plays(mode = ANARCHY_MODE, cooldown = 12 SECONDS) + . = AddComponent(/datum/component/deadchat_control/cardinal_movement, mode, list( + "speak" = CALLBACK(src, PROC_REF(bork)), + "wear_hat" = CALLBACK(src, PROC_REF(find_new_hat)), + "drop_hat" = CALLBACK(src, PROC_REF(drop_hat)), + "spin" = CALLBACK(src, TYPE_PROC_REF(/mob, emote), "spin")), cooldown, CALLBACK(src, PROC_REF(stop_deadchat_plays))) + + if(. == COMPONENT_INCOMPATIBLE) + return + + // Stop all automated behavior. + QDEL_NULL(ai_controller) + +/mob/living/basic/pet/dog/corgi/update_overlays() + . = ..() + if(inventory_head) + var/image/head_icon + var/datum/dog_fashion/equipped_head_fashion_item = new inventory_head.dog_fashion(src) + + if(!equipped_head_fashion_item.obj_icon_state) + equipped_head_fashion_item.obj_icon_state = inventory_head.icon_state + if(!equipped_head_fashion_item.obj_alpha) + equipped_head_fashion_item.obj_alpha = inventory_head.alpha + if(!equipped_head_fashion_item.obj_color) + equipped_head_fashion_item.obj_color = inventory_head.color + + if(stat == DEAD || HAS_TRAIT(src, TRAIT_FAKEDEATH)) + head_icon = equipped_head_fashion_item.get_overlay(dir = EAST) + head_icon.pixel_y = -8 + head_icon.transform = head_icon.transform.Turn(180) + else + head_icon = equipped_head_fashion_item.get_overlay() + + . += head_icon + + if(inventory_back) + var/image/back_icon + var/datum/dog_fashion/equipped_back_fashion_item = new inventory_back.dog_fashion(src) + + if(!equipped_back_fashion_item.obj_icon_state) + equipped_back_fashion_item.obj_icon_state = inventory_back.icon_state + if(!equipped_back_fashion_item.obj_alpha) + equipped_back_fashion_item.obj_alpha = inventory_back.alpha + if(!equipped_back_fashion_item.obj_color) + equipped_back_fashion_item.obj_color = inventory_back.color + + if(stat == DEAD || HAS_TRAIT(src, TRAIT_FAKEDEATH)) + back_icon = equipped_back_fashion_item.get_overlay(dir = EAST) + back_icon.pixel_y = -11 + back_icon.transform = back_icon.transform.Turn(180) + else + back_icon = equipped_back_fashion_item.get_overlay() + + . += back_icon + +//Corgis are supposed to be simpler, so only a select few objects can actually be put +//to be compatible with them. The objects are below. +//Many hats added, Some will probably be removed, just want to see which ones are popular. +// > some will probably be removed + +/** + * Places an item on the corgi's head, handling updating the corgi's appearance and the item's dog fashion modifiers + * to the name, description, speech etc. Doesn't need the user to complete, and is also used in station traits/events/persistence reading. +*/ +/mob/living/basic/pet/dog/corgi/proc/place_on_head(obj/item/item_to_add, mob/living/user) + if(inventory_head) + if(user) + to_chat(user, span_warning("You can't put more than one hat on [src]!")) + return + if(!item_to_add) + user.visible_message(span_notice("[user] pets [src]."), span_notice("You rest your hand on [src]'s head for a moment.")) + if(flags_1 & HOLOGRAM_1) + return + user.add_mood_event(REF(src), /datum/mood_event/pet_animal, src) + return + + if(user && !user.temporarilyRemoveItemFromInventory(item_to_add)) + to_chat(user, span_warning("\The [item_to_add] is stuck to your hand, you cannot put it on [src]'s head!")) + return + + var/valid = FALSE + if(ispath(item_to_add.dog_fashion, /datum/dog_fashion/head)) + valid = TRUE + + //Various hats and items (worn on his head) change Ian's behaviour. His attributes are reset when a hat is removed. + + if(valid) + if(user && (stat == DEAD || HAS_TRAIT(src, TRAIT_FAKEDEATH))) + to_chat(user, span_notice("There is merely a dull, lifeless look in [real_name]'s eyes as you put \the [item_to_add] on [p_them()].")) + else if(user) + user.visible_message(span_notice("[user] puts [item_to_add] on [real_name]'s head. [src] looks at [user] and barks once."), + span_notice("You put [item_to_add] on [real_name]'s head. [src] gives you a peculiar look, then wags [p_their()] tail once and barks."), + span_hear("You hear a friendly-sounding bark.")) + item_to_add.forceMove(src) + inventory_head = item_to_add + update_corgi_fluff() + update_appearance(UPDATE_OVERLAYS) + else + to_chat(user, span_warning("You set [item_to_add] on [src]'s head, but it falls off!")) + item_to_add.forceMove(drop_location()) + if(prob(25)) + step_rand(item_to_add) + dance_rotate(src, set_original_dir = TRUE) + + return valid + +/mob/living/basic/pet/dog/corgi/proc/update_corgi_fluff() + // First, change back to defaults + name = real_name + desc = initial(desc) + // BYOND/DM doesn't support the use of initial on lists. + speak_emote = list("barks", "woofs") + desc = initial(desc) + set_light(0) + + if(inventory_head?.dog_fashion) + var/datum/dog_fashion/equipped_head_fashion_item = new inventory_head.dog_fashion(src) + equipped_head_fashion_item.apply(src) + + if(inventory_back?.dog_fashion) + var/datum/dog_fashion/equipped_back_fashion_item = new inventory_back.dog_fashion(src) + equipped_back_fashion_item.apply(src) + +///Handler for COMSIG_MOB_TRIED_ACCESS +/mob/living/basic/pet/dog/corgi/proc/on_tried_access(mob/accessor, obj/locked_thing) + SIGNAL_HANDLER + return locked_thing?.check_access(access_card) ? ACCESS_ALLOWED : ACCESS_DISALLOWED + +///Handles updating any existing overlays for the corgi (such as fashion items) when it changes how it appears, as in, dead or alive. +/mob/living/basic/pet/dog/corgi/proc/on_appearance_change() + SIGNAL_HANDLER + update_appearance(UPDATE_OVERLAYS) + +///Deadchat plays bark. +/mob/living/basic/pet/dog/corgi/proc/bork() + var/emote = pick("barks!", "woofs!", "yaps.", "pants.") + manual_emote(emote) + +///Deadchat plays command that picks a new hat for Ian. +/mob/living/basic/pet/dog/corgi/proc/find_new_hat() + if(!isturf(loc)) + return + var/list/possible_headwear = list() + for(var/obj/item/item in loc) + if(ispath(item.dog_fashion, /datum/dog_fashion/head)) + possible_headwear += item + if(!length(possible_headwear)) + for(var/obj/item/item in orange(1)) + if(ispath(item.dog_fashion, /datum/dog_fashion/head) && CanReach(item)) + possible_headwear += item + if(!length(possible_headwear)) + return + if(inventory_head) + inventory_head.forceMove(drop_location()) + inventory_head = null + place_on_head(pick(possible_headwear)) + visible_message(span_notice("[src] puts [inventory_head] on [p_their()] own head, somehow.")) + +///Deadchat plays command that drops the current hat off Ian. +/mob/living/basic/pet/dog/corgi/proc/drop_hat() + if(!inventory_head) + return + visible_message(span_notice("[src] vigorously shakes [p_their()] head, dropping [inventory_head] to the ground.")) + inventory_head.forceMove(drop_location()) + inventory_head = null + update_corgi_fluff() + update_appearance(UPDATE_OVERLAYS) + +///Turn AI back on. +/mob/living/basic/pet/dog/corgi/proc/stop_deadchat_plays() + var/controller_type = initial(ai_controller) + ai_controller = new controller_type(src) + ai_controller?.set_blackboard_key(BB_DOG_IS_SLOW, is_slow) + +//SUBTYPES! + +/mob/living/basic/pet/dog/corgi/exoticcorgi + name = "Exotic Corgi" + desc = "As cute as they are colorful!" + icon = 'icons/mob/simple/pets.dmi' + icon_state = "corgigrey" + icon_living = "corgigrey" + icon_dead = "corgigrey_dead" + can_be_shaved = FALSE + +/mob/living/basic/pet/dog/corgi/exoticcorgi/Initialize(mapload) + . = ..() + var/newcolor = rgb(rand(0, 255), rand(0, 255), rand(0, 255)) + add_atom_colour(newcolor, FIXED_COLOUR_PRIORITY) + +//IAN! SQUEEEEEEEEE~ +/mob/living/basic/pet/dog/corgi/ian + name = "Ian" + real_name = "Ian" //Intended to hold the name without altering it. + gender = MALE + desc = "He's the HoP's beloved corgi." + response_help_continuous = "pets" + response_help_simple = "pet" + response_disarm_continuous = "bops" + response_disarm_simple = "bop" + response_harm_continuous = "kicks" + response_harm_simple = "kick" + gold_core_spawnable = NO_SPAWN + unique_pet = TRUE + ///Tracks how many rounds did Ian survive from start to finish + var/age = 0 + ///Callback to execute upon roundend to check whether Ian has survived the round or not + var/datum/callback/i_will_survive + ///Highest achieved consecutive rounds that Ian survived from start to finish + var/record_age = 1 + ///Whether we have already recorded this Ian's achievements(survival and equipped hat) to the database + var/memory_saved = FALSE + ///Path of the item Ian was wearing in a previous shift, if he survived through it + var/saved_head = null + +/mob/living/basic/pet/dog/corgi/ian/Initialize(mapload) + . = ..() + // Ensure Ian exists + REGISTER_REQUIRED_MAP_ITEM(1, 1) + + //parent call must happen first to ensure IAN + //is not in nullspace when child puppies spawn + Read_Memory() + if(age == 0) + var/turf/target = get_turf(loc) + if(target) + new /mob/living/basic/pet/dog/corgi/puppy/ian(target) + Write_Memory(FALSE) + return INITIALIZE_HINT_QDEL + else if(age == record_age) + icon_state = "old_corgi" + icon_living = "old_corgi" + held_state = "old_corgi" + icon_dead = "old_corgi_dead" + desc = "At a ripe old age of [record_age], Ian's not as spry as he used to be, but he'll always be the HoP's beloved corgi." //RIP + ai_controller?.set_blackboard_key(BB_DOG_IS_SLOW, TRUE) + is_slow = TRUE + speed = 2 + + //setup roundend check for Ian's whereabouts + i_will_survive = CALLBACK(src, PROC_REF(check_ian_survival)) + SSticker.OnRoundend(i_will_survive) + +/mob/living/basic/pet/dog/corgi/ian/Destroy() + LAZYREMOVE(SSticker.round_end_events, i_will_survive) //cleanup the survival callback + QDEL_NULL(i_will_survive) + return ..() + +/mob/living/basic/pet/dog/corgi/ian/death() + if(!memory_saved) + Write_Memory(TRUE) + return ..() + +/mob/living/basic/pet/dog/corgi/ian/narsie_act() + playsound(src, 'sound/magic/demon_dies.ogg', 75, TRUE) + var/mob/living/basic/pet/dog/corgi/narsie/narsIan = new(loc) + narsIan.setDir(dir) + investigate_log("has been gibbed and replaced with Nars-Ian by Nar'Sie.", INVESTIGATE_DEATHS) + gib() + +/mob/living/basic/pet/dog/corgi/ian/Write_Memory(dead, gibbed) + . = ..() + if(!.) + return + memory_saved = TRUE + var/json_file = file("data/npc_saves/Ian.json") + var/list/file_data = list() + if(!dead) + file_data["age"] = age + 1 + if((age + 1) > record_age) + file_data["record_age"] = record_age + 1 + else + file_data["record_age"] = record_age + if(inventory_head) + file_data["saved_head"] = inventory_head.type + else + file_data["saved_head"] = null + else + file_data["age"] = 0 + file_data["record_age"] = record_age + file_data["saved_head"] = null + fdel(json_file) + WRITE_FILE(json_file, json_encode(file_data)) + +///Reads the database's persistence json file and applies age, record_age and saved_head to Ian. +/mob/living/basic/pet/dog/corgi/ian/proc/Read_Memory() + if(fexists("data/npc_saves/Ian.sav")) //legacy compatability to convert old format to new + var/savefile/S = new /savefile("data/npc_saves/Ian.sav") + S["age"] >> age + S["record_age"] >> record_age + S["saved_head"] >> saved_head + fdel("data/npc_saves/Ian.sav") + else + var/json_file = file("data/npc_saves/Ian.json") + if(!fexists(json_file)) + return + var/list/json = json_decode(file2text(json_file)) + age = json["age"] + record_age = json["record_age"] + saved_head = json["saved_head"] + if(isnull(age)) + age = 0 + if(isnull(record_age)) + record_age = 1 + if(saved_head) + place_on_head(new saved_head) + +///Checks whether Ian has survived the round or not +/mob/living/basic/pet/dog/corgi/ian/proc/check_ian_survival() + if(!stat && !memory_saved) + Write_Memory(FALSE) + +//NARS-IAN! SQ-Q-QooEglor-r'EEn-nl-luEEEf-f-fth-h +/mob/living/basic/pet/dog/corgi/narsie + name = "Nars-Ian" + real_name = "Nars-Ian" + desc = "Ia! Ia!" + icon_state = "narsian" + icon_living = "narsian" + icon_dead = "narsian_dead" + faction = list(FACTION_NEUTRAL, FACTION_CULT) + speak_emote = list("growls", "barks ominously") + gold_core_spawnable = NO_SPAWN + can_be_shaved = FALSE + unique_pet = TRUE + held_state = "narsian" + +//this could maybe be turned into an element +/mob/living/basic/pet/dog/corgi/narsie/Life(seconds_per_tick = SSMOBS_DT, times_fired) + . = ..() + //consume simple_animal pets + for(var/mob/living/simple_animal/pet/simple_pet in range(1, src)) + if(simple_pet != src && !istype(simple_pet, /mob/living/basic/pet/dog/corgi/narsie)) + visible_message(span_warning("Dark magic resonating from [src] devours [simple_pet]!"), \ + "DELICIOUS SOULS") + playsound(src, 'sound/magic/demon_attack1.ogg', 75, TRUE) + new /obj/effect/temp_visual/cult/sac(get_turf(simple_pet)) + narsie_act() + simple_pet.investigate_log("has been gibbed by [src].", INVESTIGATE_DEATHS) + simple_pet.gib() + //consume basic pets + for(var/mob/living/basic/pet/basic_pet in range(1, src)) + if(basic_pet != src && !istype(basic_pet, /mob/living/basic/pet/dog/corgi/narsie)) + visible_message(span_warning("Dark magic resonating from [src] devours [basic_pet]!"), \ + "DELICIOUS SOULS") + playsound(src, 'sound/magic/demon_attack1.ogg', 75, TRUE) + new /obj/effect/temp_visual/cult/sac(get_turf(basic_pet)) + narsie_act() + basic_pet.investigate_log("has been gibbed by [src].", INVESTIGATE_DEATHS) + basic_pet.gib() + +/mob/living/basic/pet/dog/corgi/narsie/update_corgi_fluff() + . = ..() + speak_emote = list("growls", "barks ominously") + +/mob/living/basic/pet/dog/corgi/narsie/update_dog_speech(datum/ai_planning_subtree/random_speech/speech) + speech.speak = string_list(list("Tari'karat-pasnar!", "IA! IA!", "BRRUUURGHGHRHR")) + speech.emote_hear = string_list(list("barks echoingly!", "woofs hauntingly!", "yaps in an eldritch manner.", "mutters something unspeakable.")) + speech.emote_see = string_list(list("communes with the unnameable.", "ponders devouring some souls.", "shakes.")) + +/mob/living/basic/pet/dog/corgi/narsie/narsie_act() + if(stat == DEAD) //Nar'Sie loves her doggy + visible_message(span_warning("[src] arises again, revived by the dark magicks!"), \ + span_cultlarge("RISE")) + revive(ADMIN_HEAL_ALL) //also means that a dead Nars-Ian can consume a pet and revive + adjustBruteLoss(-maxHealth) + +//LISA! SQUEEEEEEEEE~ +/mob/living/basic/pet/dog/corgi/lisa + name = "Lisa" + real_name = "Lisa" + gender = FEMALE + desc = "She's tearing you apart." + gold_core_spawnable = NO_SPAWN + unique_pet = TRUE + icon_state = "lisa" + icon_living = "lisa" + icon_dead = "lisa_dead" + response_help_continuous = "pets" + response_help_simple = "pet" + response_disarm_continuous = "bops" + response_disarm_simple = "bop" + response_harm_continuous = "kicks" + response_harm_simple = "kick" + held_state = "lisa" + strippable_inventory_slots = list(/datum/strippable_item/corgi_back, /datum/strippable_item/pet_collar, /datum/strippable_item/corgi_id) //Lisa already has a cute bow! + +//PUPPIES! SQUEEEEEEEEE~ +/mob/living/basic/pet/dog/corgi/puppy + name = "\improper corgi puppy" + real_name = "corgi" + desc = "They're a corgi puppy!" + icon_state = "puppy" + icon_living = "puppy" + icon_dead = "puppy_dead" + density = FALSE + pass_flags = PASSMOB + mob_size = MOB_SIZE_SMALL + collar_icon_state = "puppy" + strippable_inventory_slots = list(/datum/strippable_item/pet_collar, /datum/strippable_item/corgi_id) //puppies are too small to handle hats and back slot items + +//PUPPY IAN! SQUEEEEEEEEE~ +/mob/living/basic/pet/dog/corgi/puppy/ian + name = "Ian" + real_name = "Ian" + gender = MALE + desc = "He's the HoP's beloved corgi puppy." + +/mob/living/basic/pet/dog/corgi/puppy/void //Tribute to the corgis born in nullspace + name = "\improper void puppy" + real_name = "voidy" + desc = "A corgi puppy that has been infused with deep space energy. It's staring back..." + gender = NEUTER + icon_state = "void_puppy" + icon_living = "void_puppy" + icon_dead = "void_puppy_dead" + can_be_shaved = FALSE + held_state = "void_puppy" + unsuitable_atmos_damage = 0 + minimum_survivable_temperature = TCMB + maximum_survivable_temperature = T0C + 40 + +/mob/living/basic/pet/dog/corgi/puppy/void/Initialize(mapload) + . = ..() + //void puppies can spacewalk and can harass people from within a container because they're void puppies + add_traits(list(TRAIT_AI_BAGATTACK, TRAIT_SPACEWALK), INNATE_TRAIT) diff --git a/code/modules/mob/living/basic/pets/dog/dog_subtypes.dm b/code/modules/mob/living/basic/pets/dog/dog_subtypes.dm new file mode 100644 index 00000000000..a78a553729f --- /dev/null +++ b/code/modules/mob/living/basic/pets/dog/dog_subtypes.dm @@ -0,0 +1,91 @@ +//Less exciting dog breeds + +/mob/living/basic/pet/dog/pug + name = "\improper pug" + real_name = "pug" + desc = "They're a pug." + icon = 'icons/mob/simple/pets.dmi' + icon_state = "pug" + icon_living = "pug" + icon_dead = "pug_dead" + butcher_results = list(/obj/item/food/meat/slab/pug = 3) + gold_core_spawnable = FRIENDLY_SPAWN + collar_icon_state = "pug" + held_state = "pug" + +/mob/living/basic/pet/dog/pug/Initialize(mapload) + . = ..() + AddElement(/datum/element/swabable, CELL_LINE_TABLE_PUG, CELL_VIRUS_TABLE_GENERIC_MOB, 1, 5) + +/mob/living/basic/pet/dog/pug/mcgriff + name = "McGriff" + real_name = "McGriff" + desc = "This dog can tell something smells around here, and that something is CRIME!" + gold_core_spawnable = NO_SPAWN + unique_pet = TRUE + +/mob/living/basic/pet/dog/bullterrier + name = "\improper bull terrier" + real_name = "bull terrier" + desc = "They're a bull terrier." + icon = 'icons/mob/simple/pets.dmi' + icon_state = "bullterrier" + icon_living = "bullterrier" + icon_dead = "bullterrier_dead" + butcher_results = list(/obj/item/food/meat/slab/corgi = 3) // Would feel redundant to add more new dog meats. + gold_core_spawnable = FRIENDLY_SPAWN + collar_icon_state = "bullterrier" + held_state = "bullterrier" + +/mob/living/basic/pet/dog/breaddog //Most of the code originates from Cak + name = "Kobun" + real_name = "Kobun" + desc = "It is a dog made out of bread. 'The universe is definitely half full'." + icon_state = "breaddog" + icon_living = "breaddog" + icon_dead = "breaddog_dead" + head_icon = 'icons/mob/clothing/head/pets_head.dmi' + health = 50 + maxHealth = 50 + gender = NEUTER + damage_coeff = list(BRUTE = 3, BURN = 3, TOX = 1, CLONE = 1, STAMINA = 0, OXY = 1) + butcher_results = list(/obj/item/organ/internal/brain = 1, /obj/item/organ/internal/heart = 1, /obj/item/food/breadslice/plain = 3, \ + /obj/item/food/meat/slab = 2) + response_harm_continuous = "takes a bite out of" + response_harm_simple = "take a bite out of" + attacked_sound = 'sound/items/eatfood.ogg' + held_state = "breaddog" + worn_slot_flags = ITEM_SLOT_HEAD + +/mob/living/basic/pet/dog/breaddog/CheckParts(list/parts) + . = ..() + var/obj/item/organ/internal/brain/candidate = locate(/obj/item/organ/internal/brain) in contents + if(!candidate || !candidate.brainmob || !candidate.brainmob.mind) + return + candidate.brainmob.mind.transfer_to(src) + to_chat(src, "[span_boldbig("You are a bread dog!")] You're a harmless dog/bread hybrid that everyone loves. People can take bites out of you if they're hungry, but you regenerate health \ + so quickly that it generally doesn't matter. You're remarkably resilient to any damage besides this and it's hard for you to really die at all. You should go around and bring happiness and \ + free bread to the station! 'I’m not alone, and you aren’t either'") + var/default_name = "Kobun" + var/new_name = sanitize_name(reject_bad_text(tgui_input_text(src, "You are the [name]. Would you like to change your name to something else?", "Name change", default_name, MAX_NAME_LEN)), cap_after_symbols = FALSE) + if(new_name) + to_chat(src, span_notice("Your name is now [new_name]!")) + name = new_name + real_name = new_name + +/mob/living/basic/pet/dog/breaddog/Life(seconds_per_tick = SSMOBS_DT, times_fired) + . = ..() + if(stat) + return + + if(health < maxHealth) + adjustBruteLoss(-4 * seconds_per_tick) //Fast life regen + + for(var/mob/living/carbon/humanoid_entities in view(3, src)) //Mood aura which stay as long you do not wear Sanallite as hat or carry(I will try to make it work with hat someday(obviously weaker than normal one)) + humanoid_entities.add_mood_event("kobun", /datum/mood_event/kobun) + +/mob/living/basic/pet/dog/breaddog/attack_hand(mob/living/user, list/modifiers) + . = ..() + if(user.combat_mode && user.reagents && !stat) + user.reagents.add_reagent(/datum/reagent/consumable/nutriment, 0.4) + user.reagents.add_reagent(/datum/reagent/consumable/nutriment/vitamin, 0.4) diff --git a/code/modules/mob/living/basic/pets/dog/strippable_items.dm b/code/modules/mob/living/basic/pets/dog/strippable_items.dm new file mode 100644 index 00000000000..2799ca38b97 --- /dev/null +++ b/code/modules/mob/living/basic/pets/dog/strippable_items.dm @@ -0,0 +1,156 @@ +//corgi's stippable items + +GLOBAL_LIST_INIT(strippable_corgi_items, create_strippable_list(list( + /datum/strippable_item/corgi_head, + /datum/strippable_item/corgi_back, + /datum/strippable_item/pet_collar, + /datum/strippable_item/corgi_id, +))) + +/datum/strippable_item/corgi_head + key = STRIPPABLE_ITEM_HEAD + +/datum/strippable_item/corgi_head/get_item(atom/source) + var/mob/living/basic/pet/dog/corgi/corgi_source = source + if(!istype(corgi_source)) + return + + return corgi_source.inventory_head + +/datum/strippable_item/corgi_head/finish_equip(atom/source, obj/item/equipping, mob/user) + var/mob/living/basic/pet/dog/corgi/corgi_source = source + if(!istype(corgi_source)) + return + + corgi_source.place_on_head(equipping, user) + +/datum/strippable_item/corgi_head/finish_unequip(atom/source, mob/user) + var/mob/living/basic/pet/dog/corgi/corgi_source = source + if(!istype(corgi_source)) + return + + user.put_in_hands(corgi_source.inventory_head) + corgi_source.inventory_head = null + corgi_source.update_corgi_fluff() + corgi_source.update_appearance(UPDATE_OVERLAYS) + +/datum/strippable_item/pet_collar + key = STRIPPABLE_ITEM_PET_COLLAR + +/datum/strippable_item/pet_collar/get_item(atom/source) + var/mob/living/basic/pet/pet_source = source + if(!istype(pet_source)) + return + + return pet_source.collar + +/datum/strippable_item/pet_collar/try_equip(atom/source, obj/item/equipping, mob/user) + . = ..() + if(!.) + return FALSE + + if(!istype(equipping, /obj/item/clothing/neck/petcollar)) + to_chat(user, span_warning("That's not a collar.")) + return FALSE + + return TRUE + +/datum/strippable_item/pet_collar/finish_equip(atom/source, obj/item/equipping, mob/user) + var/mob/living/basic/pet/pet_source = source + if(!istype(pet_source)) + return + + pet_source.add_collar(equipping, user) + +/datum/strippable_item/pet_collar/finish_unequip(atom/source, mob/user) + var/mob/living/basic/pet/pet_source = source + if(!istype(pet_source)) + return + + var/obj/collar = pet_source.remove_collar(user.drop_location()) + user.put_in_hands(collar) + +/datum/strippable_item/corgi_back + key = STRIPPABLE_ITEM_BACK + +/datum/strippable_item/corgi_back/get_item(atom/source) + var/mob/living/basic/pet/dog/corgi/corgi_source = source + if(!istype(corgi_source)) + return + + return corgi_source.inventory_back + +/datum/strippable_item/corgi_back/try_equip(atom/source, obj/item/equipping, mob/user) + . = ..() + if(!.) + return FALSE + + if(!ispath(equipping.dog_fashion, /datum/dog_fashion/back)) + to_chat(user, span_warning("You set [equipping] on [source]'s back, but it falls off!")) + equipping.forceMove(source.drop_location()) + if(prob(25)) + step_rand(equipping) + dance_rotate(source, set_original_dir = TRUE) + + return FALSE + + return TRUE + +/datum/strippable_item/corgi_back/finish_equip(atom/source, obj/item/equipping, mob/user) + var/mob/living/basic/pet/dog/corgi/corgi_source = source + if(!istype(corgi_source)) + return + + equipping.forceMove(corgi_source) + corgi_source.inventory_back = equipping + corgi_source.update_corgi_fluff() + corgi_source.update_appearance(UPDATE_OVERLAYS) + +/datum/strippable_item/corgi_back/finish_unequip(atom/source, mob/user) + var/mob/living/basic/pet/dog/corgi/corgi_source = source + if(!istype(corgi_source)) + return + + user.put_in_hands(corgi_source.inventory_back) + corgi_source.inventory_back = null + corgi_source.update_corgi_fluff() + corgi_source.update_appearance(UPDATE_OVERLAYS) + +/datum/strippable_item/corgi_id + key = STRIPPABLE_ITEM_ID + +/datum/strippable_item/corgi_id/get_item(atom/source) + var/mob/living/basic/pet/dog/corgi/corgi_source = source + if(!istype(corgi_source)) + return + + return corgi_source.access_card + +/datum/strippable_item/corgi_id/try_equip(atom/source, obj/item/equipping, mob/user) + . = ..() + if(!.) + return FALSE + + if(!isidcard(equipping)) + to_chat(user, span_warning("You can't pin [equipping] to [source]!")) + return FALSE + + return TRUE + +/datum/strippable_item/corgi_id/finish_equip(atom/source, obj/item/equipping, mob/user) + var/mob/living/basic/pet/dog/corgi/corgi_source = source + if(!istype(corgi_source)) + return + + equipping.forceMove(source) + corgi_source.access_card = equipping + +/datum/strippable_item/corgi_id/finish_unequip(atom/source, mob/user) + var/mob/living/basic/pet/dog/corgi/corgi_source = source + if(!istype(corgi_source)) + return + + user.put_in_hands(corgi_source.access_card) + corgi_source.access_card = null + corgi_source.update_corgi_fluff() + corgi_source.update_appearance(UPDATE_OVERLAYS) diff --git a/code/modules/mob/living/basic/pets/pet.dm b/code/modules/mob/living/basic/pets/pet.dm index 60dbb277b4b..6e8c87059e5 100644 --- a/code/modules/mob/living/basic/pets/pet.dm +++ b/code/modules/mob/living/basic/pets/pet.dm @@ -47,7 +47,7 @@ return // Determine which status tag to add to the middle of the icon state. - var/dead_tag = stat == DEAD ? "_dead" : null + var/dead_tag = (stat == DEAD || HAS_TRAIT(src, TRAIT_FAKEDEATH)) ? "_dead" : null var/rest_tag = has_collar_resting_icon_state && resting ? "_rest" : null var/stat_tag = dead_tag || rest_tag || "" @@ -55,9 +55,8 @@ . += mutable_appearance(icon, "[collar_icon_state][stat_tag]tag") /mob/living/basic/pet/gib() - . = ..() - remove_collar(drop_location(), update_visuals = FALSE) + return ..() /mob/living/basic/pet/revive(full_heal_flags = NONE, excess_healing = 0, force_grab_ghost = FALSE) . = ..() diff --git a/code/modules/research/xenobiology/crossbreeding/_mobs.dm b/code/modules/research/xenobiology/crossbreeding/_mobs.dm index ecf18cd027d..24e44f0a389 100644 --- a/code/modules/research/xenobiology/crossbreeding/_mobs.dm +++ b/code/modules/research/xenobiology/crossbreeding/_mobs.dm @@ -45,7 +45,7 @@ Slimecrossing Mobs icon_state = "slime_puppy" icon_living = "slime_puppy" icon_dead = "slime_puppy_dead" - nofur = TRUE + can_be_shaved = FALSE gold_core_spawnable = NO_SPAWN speak_emote = list("blorbles", "bubbles", "borks") diff --git a/icons/mob/simple/corgi_head.dmi b/icons/mob/simple/corgi_head.dmi index 1ee6a999644..b582d0406f9 100644 Binary files a/icons/mob/simple/corgi_head.dmi and b/icons/mob/simple/corgi_head.dmi differ diff --git a/tgstation.dme b/tgstation.dme index 2ff19a996bf..d5258688c01 100644 --- a/tgstation.dme +++ b/tgstation.dme @@ -335,6 +335,7 @@ #include "code\__DEFINES\dcs\signals\signals_atom\signals_atom_x_act.dm" #include "code\__DEFINES\dcs\signals\signals_mob\signals_mob_ai.dm" #include "code\__DEFINES\dcs\signals\signals_mob\signals_mob_arcade.dm" +#include "code\__DEFINES\dcs\signals\signals_mob\signals_mob_basic.dm" #include "code\__DEFINES\dcs\signals\signals_mob\signals_mob_carbon.dm" #include "code\__DEFINES\dcs\signals\signals_mob\signals_mob_living.dm" #include "code\__DEFINES\dcs\signals\signals_mob\signals_mob_main.dm" @@ -4067,10 +4068,13 @@ #include "code\modules\mob\living\basic\lavaland\goliath\goliath_actions.dm" #include "code\modules\mob\living\basic\lavaland\goliath\goliath_ai.dm" #include "code\modules\mob\living\basic\lavaland\goliath\tentacle.dm" -#include "code\modules\mob\living\basic\pets\dog.dm" #include "code\modules\mob\living\basic\pets\fox.dm" #include "code\modules\mob\living\basic\pets\penguin.dm" #include "code\modules\mob\living\basic\pets\pet.dm" +#include "code\modules\mob\living\basic\pets\dog\_dog.dm" +#include "code\modules\mob\living\basic\pets\dog\corgi.dm" +#include "code\modules\mob\living\basic\pets\dog\dog_subtypes.dm" +#include "code\modules\mob\living\basic\pets\dog\strippable_items.dm" #include "code\modules\mob\living\basic\ruin_defender\stickman.dm" #include "code\modules\mob\living\basic\space_fauna\ant.dm" #include "code\modules\mob\living\basic\space_fauna\cat_surgeon.dm"