diff --git a/data/json/monsters/mammal.json b/data/json/monsters/mammal.json index 63ce97efdec9b..43d37c68ab529 100644 --- a/data/json/monsters/mammal.json +++ b/data/json/monsters/mammal.json @@ -2065,6 +2065,16 @@ "petfood": { "food": [ "CATTLEFOOD" ], "feed": "The %s seems to like you! It lets you pat its head and seems friendly." }, "flags": [ "SEES", "HEARS", "SMELLS", "ANIMAL", "PET_WONT_FOLLOW", "PET_MOUNTABLE", "PATH_AVOID_DANGER_1", "WARM" ] }, + { + "id": "mon_horse_police", + "type": "MONSTER", + "copy-from": "mon_horse", + "looks_like": "mon_horse", + "description": "A hooved grazing mammal with a mane of hair, a sweeping tail, and powerful-looking muscles. This one has been trained to have less of a fear reaction in order to assist with riot control and standard police work. With some effort, you should be able to get this one to trust you enough to ride it.", + "morale": 35, + "mount_items": { "tied": "rope_30", "tack": "horse_tack", "armor": "kevlar_armor_horse" }, + "extend": { "flags": [ "COMBAT_MOUNT" ] } + }, { "id": "mon_lemming", "type": "MONSTER", diff --git a/data/json/professions.json b/data/json/professions.json index de12903fbeb3e..9298db3a55e7b 100644 --- a/data/json/professions.json +++ b/data/json/professions.json @@ -5,6 +5,12 @@ "id": "holster_supp_MEU", "entries": [ { "item": "m1911", "variant": "m1911_MEU", "ammo-item": "45_acp", "charges": 7, "contents-item": [ "suppressor" ] } ] }, + { + "type": "item_group", + "subtype": "collection", + "id": "holster_supp_usp9", + "entries": [ { "item": "usp_9mm", "ammo-item": "9mmfmj", "charges": 15, "contents-item": [ "suppressor_compact" ] } ] + }, { "type": "item_group", "subtype": "collection", @@ -1883,6 +1889,46 @@ "female": [ "bra", "boy_shorts" ] } }, + { + "type": "profession", + "id": "horse_cop", + "name": "Mounted Police Officer", + "description": "You have spent many long years forging a bond with your equine companion, from running down common thugs, to entertaining kids when you ride on by. The cataclysm is upon you, but at least you have eachother.", + "points": 5, + "skills": [ + { "level": 4, "name": "survival" }, + { "level": 2, "name": "gun" }, + { "level": 2, "name": "pistol" }, + { "level": 1, "name": "swimming" } + ], + "traits": [ "PROF_POLICE" ], + "proficiencies": [ "prof_spotting" ], + "pets": [ { "name": "mon_horse_police", "amount": 1 } ], + "items": { + "both": { + "items": [ + "pants_army", + "socks", + "badge_deputy", + "police_belt", + "boots", + "wristwatch", + "helmet_bike", + "knee_pads", + "elbow_pads" + ], + "entries": [ + { "group": "charged_smart_phone" }, + { "group": "charged_two_way_radio" }, + { "group": "holster_supp_usp9", "container-item": "XL_holster" }, + { "item": "legpouch_large", "contents-group": "army_mags_usp9" }, + { "item": "sheriffshirt", "variant": "sheriff" } + ] + }, + "male": [ "boxer_shorts" ], + "female": [ "bra", "boy_shorts" ] + } + }, { "type": "profession", "id": "dog_lover", diff --git a/src/monster.cpp b/src/monster.cpp index 64a764eb99c9c..2e7f37dfcaf08 100644 --- a/src/monster.cpp +++ b/src/monster.cpp @@ -83,11 +83,14 @@ static const efftype_id effect_dripping_mechanical_fluid( "dripping_mechanical_f static const efftype_id effect_emp( "emp" ); static const efftype_id effect_grabbed( "grabbed" ); static const efftype_id effect_grabbing( "grabbing" ); +static const efftype_id effect_has_bag( "has_bag" ); static const efftype_id effect_heavysnare( "heavysnare" ); static const efftype_id effect_hit_by_player( "hit_by_player" ); static const efftype_id effect_in_pit( "in_pit" ); +static const efftype_id effect_leashed( "leashed" ); static const efftype_id effect_lightsnare( "lightsnare" ); static const efftype_id effect_monster_armor( "monster_armor" ); +static const efftype_id effect_monster_saddled( "monster_saddled" ); static const efftype_id effect_natures_commune( "natures_commune" ); static const efftype_id effect_no_sight( "no_sight" ); static const efftype_id effect_onfire( "onfire" ); @@ -240,6 +243,32 @@ monster::monster( const mtype_id &id ) : monster() mech_bat_item.ammo_consume( rng( 0, max_charge ), tripoint_zero, nullptr ); battery_item = cata::make_value( mech_bat_item ); } + if( monster::has_flag( MF_PET_MOUNTABLE ) ) { + if( !type->mount_items.tied.is_empty() ) { + itype_id tied_item_id = itype_id( type->mount_items.tied ); + item tied_item_item = item( tied_item_id, calendar::turn_zero ); + add_effect( effect_leashed, 1_turns, true ); + tied_item = cata::make_value( tied_item_item ); + } + if( !type->mount_items.tack.is_empty() ) { + itype_id tack_item_id = itype_id( type->mount_items.tack ); + item tack_item_item = item( tack_item_id, calendar::turn_zero ); + add_effect( effect_monster_saddled, 1_turns, true ); + tack_item = cata::make_value( tack_item_item ); + } + if( !type->mount_items.armor.is_empty() ) { + itype_id armor_item_id = itype_id( type->mount_items.armor ); + item armor_item_item = item( armor_item_id, calendar::turn_zero ); + add_effect( effect_monster_armor, 1_turns, true ); + armor_item = cata::make_value( armor_item_item ); + } + if( !type->mount_items.storage.is_empty() ) { + itype_id storage_item_id = itype_id( type->mount_items.storage ); + item storage_item_item = item( storage_item_id, calendar::turn_zero ); + add_effect( effect_has_bag, 1_turns, true ); + tack_item = cata::make_value( storage_item_item ); + } + } aggro_character = type->aggro_character; } diff --git a/src/monstergenerator.cpp b/src/monstergenerator.cpp index 956fe79a6899a..6ff6b0665af7e 100644 --- a/src/monstergenerator.cpp +++ b/src/monstergenerator.cpp @@ -919,6 +919,14 @@ void mtype::load( const JsonObject &jo, const std::string &src ) optional( jo, was_loaded, "mech_str_bonus", mech_str_bonus, 0 ); optional( jo, was_loaded, "mech_battery", mech_battery, itype_id() ); + if( jo.has_object( "mount_items" ) ) { + JsonObject jo_mount_items = jo.get_object( "mount_items" ); + optional( jo_mount_items, was_loaded, "tied", mount_items.tied, itype_id() ); + optional( jo_mount_items, was_loaded, "tack", mount_items.tack, itype_id() ); + optional( jo_mount_items, was_loaded, "armor", mount_items.armor, itype_id() ); + optional( jo_mount_items, was_loaded, "storage", mount_items.storage, itype_id() ); + } + optional( jo, was_loaded, "zombify_into", zombify_into, string_id_reader<::mtype> {}, mtype_id() ); optional( jo, was_loaded, "fungalize_into", fungalize_into, string_id_reader<::mtype> {}, @@ -1484,6 +1492,22 @@ void MonsterGenerator::check_monster_definitions() const debugmsg( "monster %s has unknown mech_battery: %s", mon.id.c_str(), mon.mech_battery.c_str() ); } + if( !mon.mount_items.tied.is_empty() && !item::type_is_defined( mon.mount_items.tied ) ) { + debugmsg( "monster %s has unknown mount_items.tied: %s", mon.id.c_str(), + mon.mount_items.tied.c_str() ); + } + if( !mon.mount_items.tack.is_empty() && !item::type_is_defined( mon.mount_items.tack ) ) { + debugmsg( "monster %s has unknown mount_items.tack: %s", mon.id.c_str(), + mon.mount_items.tack.c_str() ); + } + if( !mon.mount_items.armor.is_empty() && !item::type_is_defined( mon.mount_items.armor ) ) { + debugmsg( "monster %s has unknown mount_items.armor: %s", mon.id.c_str(), + mon.mount_items.armor.c_str() ); + } + if( !mon.mount_items.storage.is_empty() && !item::type_is_defined( mon.mount_items.storage ) ) { + debugmsg( "monster %s has unknown mount_items.storage: %s", mon.id.c_str(), + mon.mount_items.storage.c_str() ); + } if( !mon.harvest.is_valid() ) { debugmsg( "monster %s has invalid harvest_entry: %s", mon.id.c_str(), mon.harvest.c_str() ); } diff --git a/src/mtype.h b/src/mtype.h index 4ac5810143d4d..b8923e4200da0 100644 --- a/src/mtype.h +++ b/src/mtype.h @@ -241,6 +241,25 @@ struct monster_death_effect { void deserialize( const JsonObject &data ); }; +struct mount_item_data { + /** + * If this monster is a rideable mount that spawns with a tied item (leash), this is the tied item id + */ + itype_id tied; + /** + * If this monster is a rideable mount that spawns with a tack item, this is the tack item id + */ + itype_id tack; + /** + * If this monster is a rideable mount that spawns with armor, this is the armor item id + */ + itype_id armor; + /** + * If this monster is a rideable mount that spawns with storage bags, this is the storage item id + */ + itype_id storage; +}; + struct mtype { private: friend class MonsterGenerator; @@ -466,6 +485,9 @@ struct mtype { /** Emission sources that cycle each turn the monster remains alive */ std::map emit_fields; + /** Mount-specific items this monster spawns with */ + mount_item_data mount_items; + /** * If this monster is a rideable mech with enhanced strength, this is the strength it gives to the player */