From 988e8686e579d5d7063d9335376842823794d324 Mon Sep 17 00:00:00 2001 From: Coolthulhu Date: Wed, 8 Jun 2016 20:46:10 +0200 Subject: [PATCH] Make npc class id a string_id --- CataclysmWin.cbp | 2 + astyled_whitelist | 2 + data/json/npcs/classes.json | 71 ++++++++++ data/json/npcs/npc.json | 64 ++++----- src/CMakeLists.txt | 2 + src/game.cpp | 9 +- src/init.cpp | 5 + src/mission.cpp | 1 + src/mission.h | 8 +- src/mission_start.cpp | 25 ++-- src/npc.cpp | 258 ++++++++++-------------------------- src/npc.h | 50 +++---- src/npc_class.cpp | 122 +++++++++++++++++ src/npc_class.h | 59 +++++++++ src/npctalk.cpp | 70 +++++----- src/savegame_json.cpp | 20 ++- 16 files changed, 448 insertions(+), 320 deletions(-) create mode 100644 data/json/npcs/classes.json create mode 100644 src/npc_class.cpp create mode 100644 src/npc_class.h diff --git a/CataclysmWin.cbp b/CataclysmWin.cbp index f84e8a9364d20..30b60b4f2d098 100644 --- a/CataclysmWin.cbp +++ b/CataclysmWin.cbp @@ -553,6 +553,8 @@ + + diff --git a/astyled_whitelist b/astyled_whitelist index 99abe88ac036c..2ff91e1813193 100644 --- a/astyled_whitelist +++ b/astyled_whitelist @@ -48,6 +48,7 @@ src/morale.cpp src/mtype.cpp src/mutation_ui.cpp src/name.cpp +src/npc_class.cpp src/overlay_ordering.cpp src/pathfinding.cpp src/player_activity.cpp @@ -149,6 +150,7 @@ src/morale.h src/morale_types.h src/mutation.h src/name.h +src/npc_class.h src/npc_favor.h src/omdata.h src/options.h diff --git a/data/json/npcs/classes.json b/data/json/npcs/classes.json new file mode 100644 index 0000000000000..e27e40e973e7a --- /dev/null +++ b/data/json/npcs/classes.json @@ -0,0 +1,71 @@ +[ + { + "type" : "npc_class", + "id" : "NC_NONE", + "name" : "No class" + },{ + "type" : "npc_class", + "id" : "NC_EVAC_SHOPKEEP", + "name" : "Merchant" + },{ + "type" : "npc_class", + "id" : "NC_SHOPKEEP", + "name" : "Shopkeep" + },{ + "type" : "npc_class", + "id" : "NC_HACKER", + "name" : "Hacker" + },{ + "type" : "npc_class", + "id" : "NC_DOCTOR", + "name" : "Doctor" + },{ + "type" : "npc_class", + "id" : "NC_TRADER", + "name" : "Trader" + },{ + "type" : "npc_class", + "id" : "NC_NINJA", + "name" : "Ninja" + },{ + "type" : "npc_class", + "id" : "NC_COWBOY", + "name" : "Cowboy" + },{ + "type" : "npc_class", + "id" : "NC_SCIENTIST", + "name" : "Scientist" + },{ + "type" : "npc_class", + "id" : "NC_BOUNTY_HUNTER", + "name" : "Bounty Hunter" + },{ + "type" : "npc_class", + "id" : "NC_THUG", + "name" : "Thug" + },{ + "type" : "npc_class", + "id" : "NC_SCAVENGER", + "name" : "Scavenger" + },{ + "type" : "npc_class", + "id" : "NC_ARSONIST", + "name" : "Arsonist" + },{ + "type" : "npc_class", + "id" : "NC_HUNTER", + "name" : "Hunter" + },{ + "type" : "npc_class", + "id" : "NC_SOLDIER", + "name" : "Soldier" + },{ + "type" : "npc_class", + "id" : "NC_BARTENDER", + "name" : "Bartender" + },{ + "type" : "npc_class", + "id" : "NC_JUNK_SHOPKEEP", + "name" : "Shopkeep" + } +] diff --git a/data/json/npcs/npc.json b/data/json/npcs/npc.json index d7b8ab7da5489..ceced5c17f546 100644 --- a/data/json/npcs/npc.json +++ b/data/json/npcs/npc.json @@ -8,7 +8,7 @@ "comment" : "Class is based on the enum in npc.h. The important ones are 0=NC_NONE, 2=NC_SHOPKEEP,", "comment" : "3=NC_HACKER, 4=NC_DOCTOR, 5=NC_TRADER, 6=NC_NINJA, 7=NC_COWBOY, 8=NC_SCIENTIST,", "comment" : "9=NC_BOUNTY_HUNTER, 10=NC_THUG, 11=NC_SCAVENGER, 13=NC_HUNTER, 14=NC_SOLDIER.", - "class" : 7, + "class" : "NC_COWBOY", "comment" : "Attitude is based on the enum in npc.h. The important ones are 0=NPCATT_NULL, 1=NPCATT_TALK", "comment" : "3=NPCATT_FOLLOW, 7=NPCATT_DEFEND, 10=NPCATT_KILL, and 11=NPCATT_FLEE", "attitude" : 0, @@ -24,7 +24,7 @@ "id" : "old_guard_soldier", "comment" : "Generic guard for the old guard.", "name+" : ", Soldier", - "class" : 14, + "class" : "NC_SOLDIER", "attitude" : 0, "mission" : 7, "chat" : "TALK_OLD_GUARD_SOLDIER", @@ -34,7 +34,7 @@ "id" : "old_guard_necropolis_cpt", "comment" : "Commander in Necropolis", "name+" : ", CPT", - "class" : 14, + "class" : "NC_SOLDIER", "attitude" : 0, "mission" : 7, "chat" : "TALK_OLD_GUARD_NEC_CPT", @@ -45,7 +45,7 @@ "id" : "old_guard_necropolis_commo", "comment" : "In charge of outside communications in Necropolis", "name+" : ", SFC", - "class" : 14, + "class" : "NC_SOLDIER", "attitude" : 0, "mission" : 7, "chat" : "TALK_OLD_GUARD_NEC_COMMO", @@ -56,7 +56,7 @@ "id" : "evac_merchant", "comment" : "Appears in the refugee center as shopkeeper with missions. Faction critical.", "name+" : ", Merchant", - "class" : 1, + "class" : "NC_EVAC_SHOPKEEP", "attitude" : 0, "mission" : 3, "chat" : "TALK_EVAC_MERCHANT", @@ -67,7 +67,7 @@ "id" : "evac_broker", "comment" : "Appears in the refugee center as a bulk trader. Promotes production of nonperishable food.", "name+" : ", Broker", - "class" : 9, + "class" : "NC_BOUNTY_HUNTER", "attitude" : 0, "mission" : 7, "chat" : "TALK_FREE_MERCHANT_STOCKS", @@ -77,7 +77,7 @@ "id" : "evac_guard1", "comment" : "Appears in the refugee center as a guard with custom dialogue.", "name+" : ", Guard", - "class" : 9, + "class" : "NC_BOUNTY_HUNTER", "attitude" : 0, "mission" : 7, "chat" : "TALK_EVAC_GUARD1", @@ -87,7 +87,7 @@ "id" : "evac_guard2", "comment" : "Appears in the refugee center as a guard with custom dialogue.", "name+" : ", Guard", - "class" : 9, + "class" : "NC_BOUNTY_HUNTER", "attitude" : 0, "mission" : 7, "chat" : "TALK_EVAC_GUARD2", @@ -97,7 +97,7 @@ "id" : "evac_guard3", "comment" : "Appears in the refugee center as a guard with custom dialogue.", "name+" : ", Guard", - "class" : 9, + "class" : "NC_BOUNTY_HUNTER", "attitude" : 0, "mission" : 7, "chat" : "TALK_EVAC_GUARD3", @@ -107,7 +107,7 @@ "id" : "guard", "comment" : "A generic guard for the Free Merchants faction.", "name+" : ", Guard", - "class" : 9, + "class" : "NC_BOUNTY_HUNTER", "attitude" : 0, "mission" : 7, "chat" : "TALK_GUARD", @@ -117,7 +117,7 @@ "id" : "hostile_guard", "comment" : "A generic hostile guard for the Free Merchants faction. For where the player shouldn't venture.'", "name+" : ", Guard", - "class" : 9, + "class" : "NC_BOUNTY_HUNTER", "attitude" : 10, "mission" : 7, "chat" : "TALK_DONE", @@ -127,7 +127,7 @@ "id" : "ranch_foreman", "comment" : "Appears at the ranch after you progress in the refugee center quests. Faction critical.", "name+" : ", Foreman", - "class" : 9, + "class" : "NC_BOUNTY_HUNTER", "attitude" : 0, "mission" : 7, "chat" : "TALK_RANCH_FOREMAN", @@ -138,7 +138,7 @@ "id" : "ranch_construction_1", "comment" : "Flavor", "name+" : ", Carpenter", - "class" : 13, + "class" : "NC_HUNTER", "attitude" : 0, "mission" : 7, "chat" : "TALK_RANCH_CONSTRUCTION_1", @@ -148,7 +148,7 @@ "id" : "ranch_construction_2", "comment" : "Construction skill trainer", "name+" : ", Carpenter", - "class" : 10, + "class" : "NC_THUG", "attitude" : 0, "mission" : 7, "chat" : "TALK_RANCH_CONSTRUCTION_2", @@ -158,7 +158,7 @@ "id" : "ranch_woodcutter_1", "comment" : "Can purchase wood", "name+" : ", Lumberjack", - "class" : 7, + "class" : "NC_COWBOY", "attitude" : 0, "mission" : 7, "chat" : "TALK_RANCH_WOODCUTTER", @@ -168,7 +168,7 @@ "id" : "ranch__woodcutter_2", "comment" : "Flavor", "name+" : ", Woodworker", - "class" : 7, + "class" : "NC_COWBOY", "attitude" : 0, "mission" : 7, "chat" : "TALK_RANCH_WOODCUTTER_2", @@ -178,7 +178,7 @@ "id" : "ranch_crop_overseer", "comment" : "Flavor", "name+" : ", Crop Overseer", - "class" : 9, + "class" : "NC_BOUNTY_HUNTER", "attitude" : 0, "mission" : 7, "chat" : "TALK_RANCH_CROP_OVERSEER", @@ -188,7 +188,7 @@ "id" : "ranch_farmer_1", "comment" : "Flavor", "name+" : ", Farmer", - "class" : 10, + "class" : "NC_THUG", "attitude" : 0, "mission" : 7, "chat" : "TALK_RANCH_FARMER_1", @@ -198,7 +198,7 @@ "id" : "ranch_farmer_2", "comment" : "Flavor", "name+" : ", Farmer", - "class" : 13, + "class" : "NC_HUNTER", "attitude" : 0, "mission" : 7, "chat" : "TALK_RANCH_FARMER_2", @@ -208,7 +208,7 @@ "id" : "ranch_ill_1", "comment" : "Flavor", "name+" : ", Laborer", - "class" : 13, + "class" : "NC_HUNTER", "attitude" : 0, "mission" : 7, "chat" : "TALK_RANCH_ILL_1", @@ -219,7 +219,7 @@ "comment" : "Mission source for clinic. Provides medical attention.", "name+" : ", Nurse", "gender" : "female", - "class" : 13, + "class" : "NC_HUNTER", "attitude" : 0, "mission" : 7, "chat" : "TALK_RANCH_NURSE", @@ -230,7 +230,7 @@ "id" : "ranch_doctor", "comment" : "Provides advanced medical attention.", "name+" : ", Doctor", - "class" : 4, + "class" : "NC_DOCTOR", "attitude" : 0, "mission" : 7, "chat" : "TALK_RANCH_DOCTOR", @@ -240,7 +240,7 @@ "id" : "ranch_scrapper_1", "comment" : "Flavor", "name+" : ", Scrapper", - "class" : 13, + "class" : "NC_HUNTER", "attitude" : 0, "mission" : 7, "chat" : "TALK_RANCH_SCRAPPER", @@ -250,7 +250,7 @@ "id" : "ranch_scavenger_1", "comment" : "Mission source, shopkeep", "name+" : ", Scavenger Boss", - "class" : 16, + "class" : "NC_JUNK_SHOPKEEP", "attitude" : 0, "mission" : 3, "chat" : "TALK_RANCH_SCAVENGER_1", @@ -261,7 +261,7 @@ "id" : "ranch_bartender", "comment" : "Mission source, shopkeep", "name+" : ", Bartender", - "class" : 15, + "class" : "NC_BARTENDER", "attitude" : 0, "mission" : 3, "chat" : "TALK_RANCH_BARKEEP", @@ -272,7 +272,7 @@ "id" : "ranch_barber", "comment" : "Provides hair cuts", "name+" : ", Barber", - "class" : 13, + "class" : "NC_HUNTER", "attitude" : 0, "mission" : 7, "chat" : "TALK_RANCH_BARBER", @@ -282,7 +282,7 @@ "id" : "commune_guard", "comment" : "A generic guard for the Tacoma Commune faction.", "name+" : ", Guard", - "class" : 9, + "class" : "NC_BOUNTY_HUNTER", "attitude" : 0, "mission" : 7, "chat" : "TALK_GUARD", @@ -292,7 +292,7 @@ "id" : "scavenger_hunter", "comment" : "Appears in the refugee center as a trader.", "name+" : ", Hunter", - "class" : 13, + "class" : "NC_HUNTER", "attitude" : 0, "mission" : 7, "chat" : "TALK_EVAC_HUNTER", @@ -302,7 +302,7 @@ "id" : "scavenger_merc", "comment" : "Appears in the refugee center as a partner for hire.", "name+" : ", Merc", - "class" : 7, + "class" : "NC_COWBOY", "attitude" : 0, "mission" : 7, "chat" : "TALK_SCAVENGER_MERC", @@ -314,7 +314,7 @@ "name+" : "Makayla Sanchez, Arsonist", "comment" : "Gender is only referenced when the npc has a complete unique name", "gender" : "female", - "class" : 12, + "class" : "NC_ARSONIST", "attitude" : 0, "mission" : 3, "chat" : "TALK_ARSONIST", @@ -324,7 +324,7 @@ "id" : "thug", "comment" : "Generic melee focused guard for the Hell's Raiders faction.", "name+" : ", Thug", - "class" : 10, + "class" : "NC_THUG", "attitude" : 0, "mission" : 7, "chat" : "TALK_DONE", @@ -334,7 +334,7 @@ "id" : "bandit", "comment" : "Generic pistol/rifle focused guard for the Hell's Raiders faction.", "name+" : ", Bandit", - "class" : 11, + "class" : "NC_SCAVENGER", "attitude" : 0, "mission" : 7, "chat" : "TALK_DONE", diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt index 4e4a7553fbb7e..a956d936bbc53 100644 --- a/src/CMakeLists.txt +++ b/src/CMakeLists.txt @@ -101,6 +101,7 @@ SET(CATACLYSM_DDA_SOURCES ${CMAKE_SOURCE_DIR}/src/melee.cpp ${CMAKE_SOURCE_DIR}/src/defense.cpp ${CMAKE_SOURCE_DIR}/src/npc.cpp + ${CMAKE_SOURCE_DIR}/src/npc_class.cpp ${CMAKE_SOURCE_DIR}/src/text_snippets.cpp ${CMAKE_SOURCE_DIR}/src/pickup.cpp ${CMAKE_SOURCE_DIR}/src/mapbuffer.cpp @@ -197,6 +198,7 @@ SET (CATACLYSM_DDA_HEADERS ${CMAKE_SOURCE_DIR}/src/item_location.h ${CMAKE_SOURCE_DIR}/src/map.h ${CMAKE_SOURCE_DIR}/src/npc.h + ${CMAKE_SOURCE_DIR}/src/npc_class.h ${CMAKE_SOURCE_DIR}/src/npc_favor.h ${CMAKE_SOURCE_DIR}/src/rng.h ${CMAKE_SOURCE_DIR}/src/compatibility.h diff --git a/src/game.cpp b/src/game.cpp index 7e8eb1da5a9f5..c3ffcf863ee42 100644 --- a/src/game.cpp +++ b/src/game.cpp @@ -58,6 +58,7 @@ #include "construction.h" #include "lightmap.h" #include "npc.h" +#include "npc_class.h" #include "scenario.h" #include "mission.h" #include "compatibility.h" @@ -955,7 +956,7 @@ void game::create_starting_npcs() npc *tmp = new npc(); tmp->normalize(); - tmp->randomize((one_in(2) ? NC_DOCTOR : NC_NONE)); + tmp->randomize( one_in(2) ? NC_DOCTOR : NC_NONE ); // spawn the npc in the overmap, sets its overmap and submap coordinates tmp->spawn_at( get_levx(), get_levy(), get_levz() ); tmp->setx( SEEX * int(MAPSIZE / 2) + SEEX ); @@ -4050,7 +4051,7 @@ void game::debug() case 5: { npc *temp = new npc(); temp->normalize(); - temp->randomize(); + temp->randomize( NC_NONE ); temp->spawn_at( get_levx(), get_levy(), get_levz() ); temp->setx( u.posx() - 4 ); temp->sety( u.posy() - 4 ); @@ -4178,7 +4179,7 @@ void game::debug() if( np != nullptr ) { std::stringstream data; data << np->name << " " << ( np->male ? _( "Male" ) : _( "Female" ) ) << std::endl; - data << npc_class_name( np->myclass ) << "; " << + data << np->myclass.obj().get_name() << "; " << npc_attitude_name( np->attitude ) << std::endl; if( np->has_destination() ) { data << string_format( _( "Destination: %d:%d:%d (%s)" ), @@ -13999,7 +14000,7 @@ void game::spawn_mon(int /*shiftx*/, int /*shifty*/) if( x_in_y( density, 100 ) ) { npc *tmp = new npc(); tmp->normalize(); - tmp->randomize(); + tmp->randomize( NC_NONE ); //tmp->stock_missions(); // Create the NPC in one of the outermost submaps, // hopefully far away to be invisible to the player, diff --git a/src/init.cpp b/src/init.cpp index 98112c09be4e7..5925a87a8797c 100644 --- a/src/init.cpp +++ b/src/init.cpp @@ -52,6 +52,7 @@ #include "sounds.h" #include "gates.h" #include "overlay_ordering.h" +#include "npc_class.h" #include #include @@ -217,6 +218,8 @@ void DynamicDataLoader::initialize() &faction::load_faction); type_function_map["npc"] = new StaticFunctionAccessor( &npc::load_npc); + type_function_map["npc_class"] = new StaticFunctionAccessor( + &npc_class::load_npc_class ); type_function_map["talk_topic"] = new StaticFunctionAccessor( &load_talk_topic); type_function_map["epilogue"] = new StaticFunctionAccessor( @@ -367,6 +370,7 @@ void DynamicDataLoader::unload_data() gates::reset(); reset_overlay_ordering(); g->options.clear(); + npc_class::reset_npc_classes(); // TODO: // NameGenerator::generator().clear_names(); @@ -411,4 +415,5 @@ void DynamicDataLoader::check_consistency() ammunition_type::check_consistency(); trap::check_consistency(); check_bionics(); + npc_class::check_consistency(); } diff --git a/src/mission.cpp b/src/mission.cpp index b8304179ea13f..9881b9ee1c8e7 100644 --- a/src/mission.cpp +++ b/src/mission.cpp @@ -7,6 +7,7 @@ #include "overmap.h" #include "line.h" #include "npc.h" +#include "npc_class.h" #include #include diff --git a/src/mission.h b/src/mission.h index 291d3c29ba9b3..bd0503db4689e 100644 --- a/src/mission.h +++ b/src/mission.h @@ -14,10 +14,12 @@ class game; class npc; class Creature; class calendar; +class npc_class; -enum npc_class : int; enum npc_mission : int; +using npc_class_id = string_id; + enum mission_type_id { MISSION_NULL, MISSION_GET_ANTIBIOTICS, @@ -236,7 +238,7 @@ struct mission_type { std::vector origins; // Points of origin itype_id item_id; int item_count; - npc_class recruit_class; // The type of NPC you are to recruit + npc_class_id recruit_class; // The type of NPC you are to recruit int target_npc_id; std::string monster_type; int monster_kill_goal; @@ -302,7 +304,7 @@ class mission : public JsonSerializer, public JsonDeserializer itype_id item_id; // Item that needs to be found (or whatever) int item_count; // The number of above items needed oter_id target_id; // Destination type to be reached - npc_class recruit_class;// The type of NPC you are to recruit + npc_class_id recruit_class;// The type of NPC you are to recruit int target_npc_id; // The ID of a specific NPC to interact with std::string monster_type; // Monster ID that are to be killed int monster_kill_goal; // the kill count you wish to reach diff --git a/src/mission_start.cpp b/src/mission_start.cpp index 18a7e2d31325c..d73e2a571141d 100644 --- a/src/mission_start.cpp +++ b/src/mission_start.cpp @@ -18,6 +18,7 @@ #include "mapgen_functions.h" #include "field.h" #include "npc.h" +#include "npc_class.h" const mtype_id mon_charred_nightmare( "mon_charred_nightmare" ); const mtype_id mon_dog( "mon_dog" ); @@ -372,20 +373,16 @@ void mission_start::place_npc_software( mission *miss ) std::string type = "house"; - switch( dev->myclass ) { - case NC_HACKER: - miss->item_id = "software_hacking"; - break; - case NC_DOCTOR: - miss->item_id = "software_medical"; - type = "s_pharm"; - miss->follow_up = MISSION_GET_ZOMBIE_BLOOD_ANAL; - break; - case NC_SCIENTIST: - miss->item_id = "software_math"; - break; - default: - miss->item_id = "software_useless"; + if( dev->myclass == NC_HACKER ) { + miss->item_id = "software_hacking"; + } else if( dev->myclass == NC_DOCTOR ) { + miss->item_id = "software_medical"; + type = "s_pharm"; + miss->follow_up = MISSION_GET_ZOMBIE_BLOOD_ANAL; + } else if( dev->myclass == NC_SCIENTIST ) { + miss->item_id = "software_math"; + } else { + miss->item_id = "software_useless"; } tripoint place; diff --git a/src/npc.cpp b/src/npc.cpp index 77a5f2cb5e17c..73855a0961cc6 100644 --- a/src/npc.cpp +++ b/src/npc.cpp @@ -14,6 +14,7 @@ #include "overmapbuffer.h" #include "messages.h" #include "mission.h" +#include "npc_class.h" #include "json.h" #include "sounds.h" #include "morale_types.h" @@ -64,8 +65,8 @@ const efftype_id effect_pkill3( "pkill3" ); const efftype_id effect_pkill_l( "pkill_l" ); const efftype_id effect_infection( "infection" ); -std::list starting_clothes(npc_class type, bool male); -std::list starting_inv(npc *me, npc_class type); +std::list starting_clothes( const npc_class_id &type, bool male ); +std::list starting_inv( npc *me, const npc_class_id &type ); npc::npc() { @@ -125,7 +126,13 @@ void npc::load_npc(JsonObject &jsobj) } if (jsobj.has_string("faction")) guy.fac_id = jsobj.get_string("faction"); - guy.myclass = npc_class(jsobj.get_int("class")); + + if( jsobj.has_int( "class" ) ) { + guy.myclass = npc_class::from_legacy_int( jsobj.get_int("class") ); + } else if( jsobj.has_string( "class" ) ) { + guy.myclass = npc_class_id( jsobj.get_string("class") ); + } + guy.attitude = npc_attitude(jsobj.get_int("attitude")); guy.mission = npc_mission(jsobj.get_int("mission")); guy.chatbin.first_topic = jsobj.get_string( "chat" ); @@ -154,7 +161,7 @@ void npc::load_npc_template(std::string ident) npc_map::iterator found = _all_npc.find(ident); if (found != _all_npc.end()){ idz = found->second.idz; - myclass = found->second.myclass; + myclass = npc_class_id( found->second.myclass ); randomize(myclass); std::string tmpname = found->second.name.c_str(); if (tmpname[0] == ','){ @@ -203,7 +210,7 @@ void npc::load_info(std::string data) } -void npc::randomize(npc_class type) +void npc::randomize( const npc_class_id &type ) { this->setID(g->assign_npc_id()); str_max = dice(4, 3); @@ -226,18 +233,14 @@ void npc::randomize(npc_class type) male = false; pick_name(); - npc_class typetmp; - if (type == NC_NONE){ - typetmp = npc_class(rng(0, NC_MAX - 1)); - if (typetmp != NC_SHOPKEEP) //Exclude unique classes from random NPCs here - type = typetmp; - if (one_in(5)) - type = NC_NONE; - } + if( type.is_null() && !one_in( 5 ) ){ + npc_class_id typetmp; + myclass = npc_class::random_common(); + } else { + myclass = type; + } - myclass = type; - switch (type) { // Type of character - case NC_NONE: // Untyped; no particular specialization + if( type == npc_class_id( "NC_NONE" ) ) { // Untyped; no particular specialization for( auto &skill : Skill::skills ) { int level = 0; if (one_in(3)) @@ -246,9 +249,8 @@ void npc::randomize(npc_class type) } set_skill_level( skill.ident(), level ); } - break; - case NC_EVAC_SHOPKEEP: + } else if( type == npc_class_id( "NC_EVAC_SHOPKEEP" ) ) { for( auto &skill : Skill::skills ) { int level = 0; if (one_in(3)) @@ -267,9 +269,8 @@ void npc::randomize(npc_class type) personality.collector += rng(1, 5); cash = 100000 * rng(1, 10)+ rng(1, 100000); this->restock = 14400*3; //Every three days - break; - case NC_BARTENDER: + } else if( type == npc_class_id( "NC_BARTENDER" ) ) { for( auto &skill : Skill::skills ) { int level = 0; if (one_in(3)) @@ -284,9 +285,8 @@ void npc::randomize(npc_class type) personality.collector += rng(1, 5); cash = 10000 * rng(1, 10)+ rng(1, 10000); this->restock = 14400*3; //Every three days - break; - case NC_JUNK_SHOPKEEP: + } else if( type == npc_class_id( "NC_JUNK_SHOPKEEP" ) ) { for( auto &skill : Skill::skills ) { int level = 0; if (one_in(3)) @@ -301,9 +301,8 @@ void npc::randomize(npc_class type) personality.collector += rng(1, 5); cash = 25000 * rng(1, 10)+ rng(1, 100000); this->restock = 14400*3; //Every three days - break; - case NC_ARSONIST: + } else if( type == npc_class_id( "NC_ARSONIST" ) ) { for( auto &skill : Skill::skills ) { int level = dice(3, 2) - rng(0, 4); if (level < 0) @@ -323,9 +322,8 @@ void npc::randomize(npc_class type) personality.collector += rng(0, 2); cash = 25000 * rng(1, 10)+ rng(1, 1000); this->restock = 14400*3; //Every three days - break; - case NC_HUNTER: + } else if( type == npc_class_id( "NC_HUNTER" ) ) { for( auto &skill : Skill::skills ) { int level = dice(3, 2) - rng(0, 4); if (level < 0) @@ -346,9 +344,8 @@ void npc::randomize(npc_class type) per_max += rng(2, 4); cash = 15000 * rng(1, 10)+ rng(1, 1000); this->restock = 14400*3; //Every three days - break; - case NC_SOLDIER: + } else if( type == npc_class_id( "NC_SOLDIER" ) ) { for( auto &skill : Skill::skills ) { int level = dice(3, 2) - 3; if (level > 0 && one_in(5)) @@ -367,9 +364,8 @@ void npc::randomize(npc_class type) boost_skill_level( skill_gun, rng(2, 4)); personality.aggression += rng(1, 3); personality.bravery += rng(0, 5); - break; - case NC_HACKER: + } else if( type == npc_class_id( "NC_HACKER" ) ) { for( auto &skill : Skill::skills ) { int level = 0; if (one_in(3)) @@ -386,9 +382,8 @@ void npc::randomize(npc_class type) per_max -= rng(0, 2); personality.bravery -= rng(1, 3); personality.aggression -= rng(0, 2); - break; - case NC_DOCTOR: + } else if( type == npc_class_id( "NC_DOCTOR" ) ) { for( auto &skill : Skill::skills ) { int level = 0; if (one_in(3)) @@ -402,12 +397,9 @@ void npc::randomize(npc_class type) int_max += rng(0, 2); per_max += rng(0, 1) * rng(0, 1); personality.aggression -= rng(0, 4); - if (one_in(4)) - flags |= mfb(NF_DRUGGIE); cash += 10000 * rng(0, 3) * rng(0, 3); - break; - case NC_TRADER: + } else if( type == npc_class_id( "NC_TRADER" ) ) { for( auto &skill : Skill::skills ) { int level = 0; if (one_in(3)) @@ -424,9 +416,8 @@ void npc::randomize(npc_class type) per_max += rng(0, 1) * rng(0, 1); personality.collector += rng(1, 5); cash += 25000 * rng(1, 10); - break; - case NC_NINJA: + } else if( type == npc_class_id( "NC_NINJA" ) ) { for( auto &skill : Skill::skills ) { int level = 0; if (one_in(3)) @@ -445,9 +436,8 @@ void npc::randomize(npc_class type) personality.bravery += rng(0, 3); personality.collector -= rng(1, 6); // TODO: give ninja his styles back - break; - case NC_COWBOY: + } else if( type == npc_class_id( "NC_COWBOY" ) ) { for( auto &skill : Skill::skills ) { int level = dice(3, 2) - rng(0, 4); if (level < 0) @@ -464,9 +454,8 @@ void npc::randomize(npc_class type) per_max += rng(0, 2); personality.aggression += rng(0, 2); personality.bravery += rng(1, 5); - break; - case NC_SCIENTIST: + } else if( type == npc_class_id( "NC_SCIENTIST" ) ) { for( auto &skill : Skill::skills ) { int level = dice(3, 2) - 4; if (level < 0) @@ -483,19 +472,14 @@ void npc::randomize(npc_class type) case 2: boost_skill_level( skill_electronics, rng(2, 6)); break; case 3: boost_skill_level( skill_firstaid, rng(2, 6)); break; } - if (one_in(4)) - flags |= mfb(NF_TECHNOPHILE); - if (one_in(3)) - flags |= mfb(NF_BOOKWORM); str_max -= rng(1, 3); dex_max -= rng(0, 1); int_max += rng(2, 5); personality.aggression -= rng(1, 5); personality.bravery -= rng(2, 8); personality.collector += rng (0, 2); - break; - case NC_BOUNTY_HUNTER: + } else if( type == npc_class_id( "NC_BOUNTY_HUNTER" ) ) { for( auto &skill : Skill::skills ) { int level = dice(3, 2) - 3; if (level > 0 && one_in(3)) @@ -508,9 +492,8 @@ void npc::randomize(npc_class type) boost_skill_level(Skill::random_skill_with_tag("gun_type"), rng(3, 5)); personality.aggression += rng(1, 6); personality.bravery += rng(0, 5); - break; - case NC_THUG: + } else if( type == npc_class_id( "NC_THUG" ) ) { for( auto &skill : Skill::skills ) { int level = dice(3, 2) - 3; if (level > 0 && one_in(3)) @@ -529,9 +512,8 @@ void npc::randomize(npc_class type) boost_skill_level( skill_unarmed, rng(1, 3)); personality.aggression += rng(1, 6); personality.bravery += rng(0, 5); - break; - case NC_SCAVENGER: + } else if( type == npc_class_id( "NC_SCAVENGER" ) ) { for( auto &skill : Skill::skills ) { int level = dice(3, 2) - 3; if (level > 0 && one_in(3)) @@ -546,11 +528,7 @@ void npc::randomize(npc_class type) boost_skill_level( skill_archery, rng(0, 3)); personality.aggression += rng(1, 3); personality.bravery += rng(1, 4); - break; - default: - //Suppress warnings - break; } //A universal barter boost to keep NPCs competitive with players @@ -575,7 +553,7 @@ void npc::randomize_from_faction(faction *fac) // Personality = aggression, bravery, altruism, collector my_fac = fac; fac_id = fac->id; - randomize(); + randomize( NC_NONE ); switch (fac->goal) { case FACGOAL_DOMINANCE: @@ -612,7 +590,7 @@ void npc::randomize_from_faction(faction *fac) break; case FACGOAL_KNOWLEDGE: if (one_in(2)) - randomize(NC_SCIENTIST); + randomize( npc_class_id( "NC_SCIENTIST" ) ); personality.aggression -= rng(2, 5); personality.bravery -= rng(1, 4); personality.collector += rng(2, 4); @@ -648,7 +626,7 @@ void npc::randomize_from_faction(faction *fac) } if (fac->has_job(FACJOB_TRADE) || fac->has_job(FACJOB_CARAVANS)) { if (!one_in(3)) - randomize(NC_TRADER); + randomize( npc_class_id( "NC_TRADER" ) ); personality.aggression -= rng(1, 5); personality.collector += rng(1, 4); personality.altruism -= rng(0, 3); @@ -658,9 +636,9 @@ void npc::randomize_from_faction(faction *fac) if (fac->has_job(FACJOB_MERCENARIES)) { if (!one_in(3)) { switch (rng(1, 3)) { - case 1: randomize(NC_NINJA); break; - case 2: randomize(NC_COWBOY); break; - case 3: randomize(NC_BOUNTY_HUNTER); break; + case 1: randomize( npc_class_id( "NC_NINJA" ) ); break; + case 2: randomize( npc_class_id( "NC_COWBOY" ) ); break; + case 3: randomize( npc_class_id( "NC_BOUNTY_HUNTER" ) ); break; } } personality.aggression += rng(0, 2); @@ -678,7 +656,7 @@ void npc::randomize_from_faction(faction *fac) } if (fac->has_job(FACJOB_RAIDERS)) { if (one_in(3)) - randomize(NC_COWBOY); + randomize( npc_class_id( "NC_COWBOY" ) ); personality.aggression += rng(3, 5); personality.bravery += rng(0, 2); personality.altruism -= rng(3, 6); @@ -687,7 +665,7 @@ void npc::randomize_from_faction(faction *fac) } if (fac->has_job(FACJOB_THIEVES)) { if (one_in(3)) - randomize(NC_NINJA); + randomize( npc_class_id( "NC_NINJA" ) ); personality.aggression -= rng(2, 5); personality.bravery -= rng(1, 3); personality.altruism -= rng(1, 4); @@ -697,7 +675,7 @@ void npc::randomize_from_faction(faction *fac) } if (fac->has_job(FACJOB_DOCTORS)) { if (!one_in(4)) - randomize(NC_DOCTOR); + randomize( npc_class_id( "NC_DOCTOR" ) ); personality.aggression -= rng(3, 6); personality.bravery += rng(0, 4); personality.altruism += rng(0, 4); @@ -811,9 +789,9 @@ void npc::set_fac(std::string fac_name) // item id from group "_" or from fallback group // may still be a null item! -item random_item_from( npc_class type, const std::string &what, const std::string &fallback ) +item random_item_from( const npc_class_id &type, const std::string &what, const std::string &fallback ) { - auto result = item_group::item_from( npc_class_name_str( type ) + "_" + what ); + auto result = item_group::item_from( type.str() + "_" + what ); if( result.is_null() ) { result = item_group::item_from( fallback ); } @@ -821,13 +799,13 @@ item random_item_from( npc_class type, const std::string &what, const std::strin } // item id from "_" or from "npc_" -item random_item_from( npc_class type, const std::string &what ) +item random_item_from( const npc_class_id &type, const std::string &what ) { return random_item_from( type, what, "npc_" + what ); } // item id from "__" or from "npc__" -item get_clothing_item( npc_class type, const std::string &what, bool male ) +item get_clothing_item( const npc_class_id &type, const std::string &what, bool male ) { if( male ) { return random_item_from( type, what + "_male", "npc_" + what + "_male" ); @@ -836,7 +814,7 @@ item get_clothing_item( npc_class type, const std::string &what, bool male ) } } -std::list starting_clothes( npc_class type, bool male ) +std::list starting_clothes( const npc_class_id &type, bool male ) { std::list ret; @@ -882,7 +860,7 @@ std::list starting_clothes( npc_class type, bool male ) return ret; } -std::list starting_inv( npc *me, npc_class type ) +std::list starting_inv( npc *me, const npc_class_id &type ) { std::list res; res.emplace_back( "lighter" ); @@ -897,8 +875,9 @@ std::list starting_inv( npc *me, npc_class type ) ammo = container; } - // NC_COWBOY and NC_BOUNTY_HUNTER get 2-4 whilst all others get 1 or 2 - int qty = 1 + ( type == NC_COWBOY || type == NC_BOUNTY_HUNTER ); + // @todo Move to npc_class + int qty = 1 + ( type == npc_class_id( "NC_COWBOY" ) || + type == npc_class_id( "NC_BOUNTY_HUNTER" ) ); qty = rng( qty, qty * 2 ); while ( qty-- != 0 && me->can_pickVolume( ammo ) ) { @@ -907,12 +886,13 @@ std::list starting_inv( npc *me, npc_class type ) } } - if( type == NC_ARSONIST ) { + if( type == npc_class_id( "NC_ARSONIST" ) ) { res.emplace_back( "molotov" ); } // NC_COWBOY and NC_BOUNTY_HUNTER get 5-15 whilst all others get 3-6 - int qty = ( type == NC_EVAC_SHOPKEEP || type == NC_TRADER ) ? 5 : 2; + int qty = ( type == npc_class_id( "NC_EVAC_SHOPKEEP" ) || + type == npc_class_id( "NC_TRADER" ) ) ? 5 : 2; qty = rng( qty, qty * 3 ); while ( qty-- != 0 ) { @@ -1020,7 +1000,7 @@ skill_id npc::best_skill() const return highest_skill; } -void npc::starting_weapon(npc_class type) +void npc::starting_weapon( const npc_class_id &type ) { const skill_id best = best_skill(); @@ -1710,27 +1690,23 @@ void npc::shop_restock(){ std::list ret; //list all merchant types here along with the item group they pull from and how much extra space they should have //guards and other fixed npcs may need a small supply of food daily... - switch (this->myclass) { - case NC_EVAC_SHOPKEEP: - from = "NC_EVAC_SHOPKEEP_misc"; - total_space += rng(30,40); - this-> cash = 100000 * rng(1, 10)+ rng(1, 100000); - case NC_ARSONIST: - from = "NC_ARSONIST_misc"; - this-> cash = 25000 * rng(1, 10)+ rng(1, 1000); - ret.push_back(item("molotov", 0)); - case NC_HUNTER: - from = "NC_HUNTER_misc"; - this-> cash = 15000 * rng(1, 10)+ rng(1, 1000); - case NC_BARTENDER: - from = "NC_BARTENDER_misc"; - this-> cash = 25000 * rng(1, 10)+ rng(1, 1000);; - case NC_JUNK_SHOPKEEP: - from = "NC_JUNK_SHOPKEEP_misc"; - this-> cash = 25000 * rng(1, 10)+ rng(1, 1000); - default: - //Suppress warnings - break; + if( myclass == npc_class_id( "NC_EVAC_SHOPKEEP" ) ) { + from = "NC_EVAC_SHOPKEEP_misc"; + total_space += rng(30,40); + this-> cash = 100000 * rng(1, 10)+ rng(1, 100000); + } else if( myclass == npc_class_id( "NC_ARSONIST" ) ) { + from = "NC_ARSONIST_misc"; + this-> cash = 25000 * rng(1, 10)+ rng(1, 1000); + ret.push_back(item("molotov", 0)); + } else if( myclass == npc_class_id( "NC_HUNTER" ) ) { + from = "NC_HUNTER_misc"; + this-> cash = 15000 * rng(1, 10)+ rng(1, 1000); + } else if( myclass == npc_class_id( "NC_BARTENDER" ) ) { + from = "NC_BARTENDER_misc"; + this-> cash = 25000 * rng(1, 10)+ rng(1, 1000);; + } else if( myclass == npc_class_id( "NC_JUNK_SHOPKEEP" ) ) { + from = "NC_JUNK_SHOPKEEP_misc"; + this-> cash = 25000 * rng(1, 10)+ rng(1, 1000); } if (from == "NULL") return; @@ -2380,94 +2356,6 @@ std::string npc_attitude_name(npc_attitude att) return _("Unknown"); } -std::string npc_class_name_str(npc_class classtype) -{ - switch(classtype) { - case NC_NONE: - return "NC_NONE"; - case NC_EVAC_SHOPKEEP: // Found in the evacuation center. - return "NC_EVAC_SHOPKEEP"; - case NC_ARSONIST: // Found in the evacuation center. - return "NC_ARSONIST"; - case NC_SHOPKEEP: // Found in towns. Stays in his shop mostly. - return "NC_SHOPKEEP"; - case NC_HACKER: // Weak in combat but has hacking skills and equipment - return "NC_HACKER"; - case NC_DOCTOR: // Found in towns, or roaming. Stays in the clinic. - return "NC_DOCTOR"; - case NC_TRADER: // Roaming trader, journeying between towns. - return "NC_TRADER"; - case NC_NINJA: // Specializes in unarmed combat, carries few items - return "NC_NINJA"; - case NC_COWBOY: // Gunslinger and survivalist - return "NC_COWBOY"; - case NC_SCIENTIST: // Uses intelligence-based skills and high-tech items - return "NC_SCIENTIST"; - case NC_BOUNTY_HUNTER: // Resourceful and well-armored - return "NC_BOUNTY_HUNTER"; - case NC_THUG: // Moderate melee skills and poor equipment - return "NC_THUG"; - case NC_SCAVENGER: // Good with pistols light weapons - return "NC_SCAVENGER"; - case NC_HUNTER: // Good with bows and rifles - return "NC_HUNTER"; - case NC_SOLDIER: // Well equiped and trained combatant, good with rifles and melee - return "NC_SOLDIER"; - case NC_BARTENDER: // Stocks alcohol - return "NC_BARTENDER"; - case NC_JUNK_SHOPKEEP: // Stocks wide range of items... - return "NC_JUNK_SHOPKEEP"; - default: - //Suppress warnings - break; - } - return "Unknown class"; -} - -std::string npc_class_name(npc_class classtype) -{ - switch(classtype) { - case NC_NONE: - return _("No class"); - case NC_EVAC_SHOPKEEP: // Found in the evacuation center. - return _("Merchant"); - case NC_ARSONIST: // Found in the evacuation center. - return _("Arsonist"); - case NC_SHOPKEEP: // Found in towns. Stays in his shop mostly. - return _("Shopkeep"); - case NC_HACKER: // Weak in combat but has hacking skills and equipment - return _("Hacker"); - case NC_DOCTOR: // Found in towns, or roaming. Stays in the clinic. - return _("Doctor"); - case NC_TRADER: // Roaming trader, journeying between towns. - return _("Trader"); - case NC_NINJA: // Specializes in unarmed combat, carries few items - return _("Ninja"); - case NC_COWBOY: // Gunslinger and survivalist - return _("Cowboy"); - case NC_SCIENTIST: // Uses intelligence-based skills and high-tech items - return _("Scientist"); - case NC_BOUNTY_HUNTER: // Resourceful and well-armored - return _("Bounty Hunter"); - case NC_THUG: // Moderate melee skills and poor equipment - return _("Thug"); - case NC_SCAVENGER: // Good with pistols light weapons - return _("Scavenger"); - case NC_HUNTER: // Good with bows and rifles - return _("Hunter"); - case NC_SOLDIER: // Well equiped and trained combatant, good with rifles and melee - return _("Soldier"); - case NC_BARTENDER: // Stocks alcohol - return _("Bartender"); - case NC_JUNK_SHOPKEEP: // Stocks wide range of items... - return _("Shopkeep"); - default: - //Suppress warnings - break; - } - return _("Unknown class"); -} - void npc::setID (int i) { this->player::setID(i); diff --git a/src/npc.h b/src/npc.h index bd41ca669da16..49419d5b99961 100644 --- a/src/npc.h +++ b/src/npc.h @@ -17,8 +17,11 @@ class item; class overmap; class player; class field_entry; +class npc_class; enum game_message_type : int; +using npc_class_id = string_id; + void parse_tags( std::string &phrase, const player &u, const npc &me ); /* @@ -71,29 +74,8 @@ enum npc_mission : int { //std::string npc_mission_name(npc_mission); -enum npc_class : int { - NC_NONE, - NC_EVAC_SHOPKEEP, // Found in the Evacuation Center, unique, has more goods than he should be able to carry - NC_SHOPKEEP, // Found in towns. Stays in his shop mostly. - NC_HACKER, // Weak in combat but has hacking skills and equipment - NC_DOCTOR, // Found in towns, or roaming. Stays in the clinic. - NC_TRADER, // Roaming trader, journeying between towns. - NC_NINJA, // Specializes in unarmed combat, carries few items - NC_COWBOY, // Gunslinger and survivalist - NC_SCIENTIST, // Uses intelligence-based skills and high-tech items - NC_BOUNTY_HUNTER, // Resourceful and well-armored - NC_THUG, // Moderate melee skills and poor equipment - NC_SCAVENGER, // Good with pistols light weapons - NC_ARSONIST, // Evacuation Center, restocks moltovs and anarcist type stuff - NC_HUNTER, // Survivor type good with bow or rifle - NC_SOLDIER, // Well equiped and trained combatant, good with rifles and melee - NC_BARTENDER, // Stocks alcohol - NC_JUNK_SHOPKEEP, // Stocks wide range of items... - NC_MAX -}; - -std::string npc_class_name(npc_class); -std::string npc_class_name_str(npc_class); +std::string npc_class_name( const npc_class_id & ); +std::string npc_class_name_str( const npc_class_id & ); enum npc_action : int; @@ -104,14 +86,13 @@ enum npc_need { num_needs }; -enum npc_flag { - NF_NULL, -// Items desired - NF_FOOD_HOARDER, - NF_DRUGGIE, - NF_TECHNOPHILE, - NF_BOOKWORM, - NF_MAX +// @todo Turn the personality struct into a vector/map? +enum npc_personality_type : int { + NPE_AGGRESSION, + NPE_BRAVERY, + NPE_COLLECTOR, + NPE_ALTRUISM, + NUM_NPE }; struct npc_personality : public JsonSerializer, public JsonDeserializer @@ -547,7 +528,7 @@ class npc : public player void load_npc_template(std::string ident); // Generating our stats, etc. - void randomize(npc_class type = NC_NONE); + void randomize( const npc_class_id &type ); void randomize_from_faction(faction *fac); void set_fac(std::string fac_name); /** @@ -574,7 +555,7 @@ class npc : public player */ void add_new_mission( mission *miss ); skill_id best_skill() const; - void starting_weapon(npc_class type); + void starting_weapon( const npc_class_id &type ); // Save & load virtual void load_info(std::string data) override;// Overloaded from player @@ -811,7 +792,7 @@ class npc : public player // ############# VALUES ################ npc_attitude attitude; // What we want to do to the player - npc_class myclass; // What's our archetype? + npc_class_id myclass; // What's our archetype? std::string idz; // A temp variable used to inform the game which npc json to use as a template int miss_id; // A temp variable used to link to the correct mission @@ -889,7 +870,6 @@ class npc : public player bool marked_for_death; // If true, we die as soon as we respawn! bool hit_by_player; std::vector needs; - unsigned flags : NF_MAX; // Dummy point that indicates that the goal is invalid. static const tripoint no_goal_point; diff --git a/src/npc_class.cpp b/src/npc_class.cpp new file mode 100644 index 0000000000000..a7d2555ca4c11 --- /dev/null +++ b/src/npc_class.cpp @@ -0,0 +1,122 @@ +#include "npc_class.h" +#include "debug.h" +#include "rng.h" +#include "generic_factory.h" + +#include + +static const std::array legacy_ids = {{ + npc_class_id( "NC_NONE" ), + npc_class_id( "NC_EVAC_SHOPKEEP" ), // Found in the Evacuation Center, unique, has more goods than he should be able to carry + npc_class_id( "NC_SHOPKEEP" ), // Found in towns. Stays in his shop mostly. + npc_class_id( "NC_HACKER" ), // Weak in combat but has hacking skills and equipment + npc_class_id( "NC_DOCTOR" ), // Found in towns, or roaming. Stays in the clinic. + npc_class_id( "NC_TRADER" ), // Roaming trader, journeying between towns. + npc_class_id( "NC_NINJA" ), // Specializes in unarmed combat, carries few items + npc_class_id( "NC_COWBOY" ), // Gunslinger and survivalist + npc_class_id( "NC_SCIENTIST" ), // Uses intelligence-based skills and high-tech items + npc_class_id( "NC_BOUNTY_HUNTER" ), // Resourceful and well-armored + npc_class_id( "NC_THUG" ), // Moderate melee skills and poor equipment + npc_class_id( "NC_SCAVENGER" ), // Good with pistols light weapons + npc_class_id( "NC_ARSONIST" ), // Evacuation Center, restocks moltovs and anarcist type stuff + npc_class_id( "NC_HUNTER" ), // Survivor type good with bow or rifle + npc_class_id( "NC_SOLDIER" ), // Well equiped and trained combatant, good with rifles and melee + npc_class_id( "NC_BARTENDER" ), // Stocks alcohol + npc_class_id( "NC_JUNK_SHOPKEEP" ) // Stocks wide range of items... + } +}; + +npc_class_id NC_NONE( "NC_NONE" ); +npc_class_id NC_EVAC_SHOPKEEP( "NC_EVAC_SHOPKEEP" ); +npc_class_id NC_SHOPKEEP( "NC_SHOPKEEP" ); +npc_class_id NC_HACKER( "NC_HACKER" ); +npc_class_id NC_DOCTOR( "NC_DOCTOR" ); +npc_class_id NC_TRADER( "NC_TRADER" ); +npc_class_id NC_NINJA( "NC_NINJA" ); +npc_class_id NC_COWBOY( "NC_COWBOY" ); +npc_class_id NC_SCIENTIST( "NC_SCIENTIST" ); +npc_class_id NC_BOUNTY_HUNTER( "NC_BOUNTY_HUNTER" ); +npc_class_id NC_THUG( "NC_THUG" ); +npc_class_id NC_SCAVENGER( "NC_SCAVENGER" ); +npc_class_id NC_ARSONIST( "NC_ARSONIST" ); +npc_class_id NC_HUNTER( "NC_HUNTER" ); +npc_class_id NC_SOLDIER( "NC_SOLDIER" ); +npc_class_id NC_BARTENDER( "NC_BARTENDER" ); +npc_class_id NC_JUNK_SHOPKEEP( "NC_JUNK_SHOPKEEP" ); + +generic_factory npc_class_factory( "npc_class" ); + +template<> +const npc_class_id string_id::NULL_ID( "NC_NONE" ); + +template<> +const npc_class &string_id::obj() const +{ + return npc_class_factory.obj( *this ); +} + +template<> +bool string_id::is_valid() const +{ + return npc_class_factory.is_valid( *this ); +} + +npc_class::npc_class() : id( NC_NONE ) +{ +} + +void npc_class::load_npc_class( JsonObject &jo ) +{ + npc_class_factory.load( jo ); +} + +void npc_class::reset_npc_classes() +{ + npc_class_factory.reset(); +} + +void npc_class::check_consistency() +{ + for( const auto &legacy : legacy_ids ) { + if( !npc_class_factory.is_valid( legacy ) ) { + debugmsg( "Missing legacy npc class %s", legacy.c_str() ); + } + } +} + +void npc_class::load( JsonObject &jo ) +{ + mandatory( jo, was_loaded, "name", name, translated_string_reader ); +} + +const npc_class_id &npc_class::from_legacy_int( int i ) +{ + if( i < 0 || ( size_t )i >= legacy_ids.size() ) { + debugmsg( "Invalid legacy class id: %d", i ); + return NULL_ID; + } + + return legacy_ids[ i ]; +} + +const npc_class_id &npc_class::random_common() +{ + // @todo Rewrite, make `common` a class member + std::list common_classes; + for( const auto &pr : npc_class_factory.get_all() ) { + if( pr.id != NC_SHOPKEEP ) { + common_classes.push_back( &pr.id ); + } + } + + if( common_classes.empty() ) { + return NC_NONE; + } + + return *random_entry( common_classes ); +} + +const std::string &npc_class::get_name() const +{ + return name; +} diff --git a/src/npc_class.h b/src/npc_class.h new file mode 100644 index 0000000000000..7f8cd49b0eed9 --- /dev/null +++ b/src/npc_class.h @@ -0,0 +1,59 @@ +#ifndef NPC_CLASS_H +#define NPC_CLASS_H + +#include +#include +#include + +#include "string_id.h" + +class npc_class; +using npc_class_id = string_id; +class JsonObject; + +class npc_class +{ + private: + std::string name; + + public: + npc_class_id id; + bool was_loaded; + + npc_class(); + + const std::string &get_name() const; + + void load( JsonObject &jo ); + + static const npc_class_id &from_legacy_int( int i ); + + static const npc_class_id &random_common(); + + static void load_npc_class( JsonObject &jo ); + + static void reset_npc_classes(); + + static void check_consistency(); +}; + +// @todo Get rid of that +extern npc_class_id NC_NONE; +extern npc_class_id NC_EVAC_SHOPKEEP; +extern npc_class_id NC_SHOPKEEP; +extern npc_class_id NC_HACKER; +extern npc_class_id NC_DOCTOR; +extern npc_class_id NC_TRADER; +extern npc_class_id NC_NINJA; +extern npc_class_id NC_COWBOY; +extern npc_class_id NC_SCIENTIST; +extern npc_class_id NC_BOUNTY_HUNTER; +extern npc_class_id NC_THUG; +extern npc_class_id NC_SCAVENGER; +extern npc_class_id NC_ARSONIST; +extern npc_class_id NC_HUNTER; +extern npc_class_id NC_SOLDIER; +extern npc_class_id NC_BARTENDER; +extern npc_class_id NC_JUNK_SHOPKEEP; + +#endif diff --git a/src/npctalk.cpp b/src/npctalk.cpp index 65782e52da3d5..ff8d50db4499f 100644 --- a/src/npctalk.cpp +++ b/src/npctalk.cpp @@ -1,4 +1,5 @@ #include "npc.h" +#include "npc_class.h" #include "output.h" #include "game.h" #include "map.h" @@ -1205,42 +1206,39 @@ std::string dialogue::dynamic_line( const std::string &topic ) const case NPC_MISSION_GUARD: return _("I'm guarding this location."); case NPC_MISSION_NULL: - switch (p->myclass) { - case NC_SHOPKEEP: - return _("I'm a local shopkeeper."); - case NC_EVAC_SHOPKEEP: - return _("I'm a local shopkeeper."); - case NC_HACKER: - return _("I'm looking for some choice systems to hack."); - case NC_DOCTOR: - return _("I'm looking for wounded to help."); - case NC_TRADER: - return _("I'm collecting gear and selling it."); - case NC_NINJA: // TODO: implement this - return _("I'm a wandering master of martial arts but I'm currently not implemented in the code."); - case NC_COWBOY: - return _("Just looking for some wrongs to right."); - case NC_SCIENTIST: - return _("I'm looking for clues concerning these monsters' origins..."); - case NC_BOUNTY_HUNTER: - return _("I'm a killer for hire."); - case NC_THUG: - return _("I'm just here for the paycheck."); - case NC_SCAVENGER: - return _("I'm just trying to survive."); - case NC_ARSONIST: - return _("I'm just watching the world burn."); - case NC_HUNTER: - return _("I'm tracking game."); - case NC_BARTENDER: - return _("I'm looking for new drink recipes."); - case NC_MAX: - return _("I should not be able to exist!"); - case NC_NONE: - return _("I'm just wandering."); - default: - return "ERROR: Someone forgot to code an npc_class text."; - } // switch (p->myclass) + if( p->myclass == NC_SHOPKEEP ) { + return _("I'm a local shopkeeper."); + } else if( p->myclass == NC_EVAC_SHOPKEEP ) { + return _("I'm a local shopkeeper."); + } else if( p->myclass == NC_HACKER ) { + return _("I'm looking for some choice systems to hack."); + } else if( p->myclass == NC_DOCTOR ) { + return _("I'm looking for wounded to help."); + } else if( p->myclass == NC_TRADER ) { + return _("I'm collecting gear and selling it."); + } else if( p->myclass == NC_NINJA ) { // TODO: implement this + return _("I'm a wandering master of martial arts but I'm currently not implemented in the code."); + } else if( p->myclass == NC_COWBOY ) { + return _("Just looking for some wrongs to right."); + } else if( p->myclass == NC_SCIENTIST ) { + return _("I'm looking for clues concerning these monsters' origins..."); + } else if( p->myclass == NC_BOUNTY_HUNTER ) { + return _("I'm a killer for hire."); + } else if( p->myclass == NC_THUG ) { + return _("I'm just here for the paycheck."); + } else if( p->myclass == NC_SCAVENGER ) { + return _("I'm just trying to survive."); + } else if( p->myclass == NC_ARSONIST ) { + return _("I'm just watching the world burn."); + } else if( p->myclass == NC_HUNTER ) { + return _("I'm tracking game."); + } else if( p->myclass == NC_BARTENDER ) { + return _("I'm looking for new drink recipes."); + } else if( p->myclass == NC_NONE ) { + return _("I'm just wandering."); + } else { + return "ERROR: Someone forgot to code an npc_class text."; + } default: return "ERROR: Someone forgot to code an npc_mission text."; } // switch (p->mission) diff --git a/src/savegame_json.cpp b/src/savegame_json.cpp index 54dc1f259c886..834a99d31aa3c 100644 --- a/src/savegame_json.cpp +++ b/src/savegame_json.cpp @@ -1,5 +1,6 @@ #include "player.h" #include "npc.h" +#include "npc_class.h" #include "profession.h" #include "bionics.h" #include "mission.h" @@ -986,14 +987,16 @@ void npc::load(JsonObject &data) // this should call load on the parent class of npc (probably Character). player::load( data ); - int misstmp, classtmp, flagstmp, atttmp, comp_miss_t, stock; - std::string facID, comp_miss; + int misstmp, classtmp, atttmp, comp_miss_t, stock; + std::string facID, comp_miss, classid; data.read("name", name); data.read("marked_for_death", marked_for_death); data.read("dead", dead); - if ( data.read( "myclass", classtmp) ) { - myclass = npc_class( classtmp ); + if( data.read( "myclass", classtmp ) ) { + myclass = npc_class::from_legacy_int( classtmp ); + } else if( data.read( "myclass", classid ) ) { + myclass = npc_class_id( classid ); } data.read("personality", personality); @@ -1040,10 +1043,6 @@ void npc::load(JsonObject &data) mission = npc_mission( misstmp ); } - if ( data.read( "flags", flagstmp) ) { - flags = flagstmp; - } - if ( data.read( "my_fac", facID) ) { fac_id = facID; } @@ -1099,7 +1098,7 @@ void npc::store(JsonOut &json) const json.member( "marked_for_death", marked_for_death ); json.member( "dead", dead ); json.member( "patience", patience ); - json.member( "myclass", (int)myclass ); + json.member( "myclass", myclass.str() ); json.member( "personality", personality ); json.member( "wandf", wander_time ); @@ -1127,7 +1126,6 @@ void npc::store(JsonOut &json) const json.member( "pulp_locationz", pulp_location.z ); json.member( "mission", mission ); // todo: stringid - json.member( "flags", flags ); if ( fac_id != "" ) { // set in constructor json.member( "my_fac", my_fac->id.c_str() ); } @@ -1797,7 +1795,7 @@ void mission::deserialize(JsonIn &jsin) if( !omid.empty() ) { target_id = oter_id( omid ); } - recruit_class = static_cast( jo.get_int( "recruit_class", recruit_class ) ); + recruit_class = npc_class::from_legacy_int( jo.get_int( "recruit_class", 0 ) ); jo.read( "target_npc_id", target_npc_id ); jo.read( "monster_type", monster_type ); jo.read( "monster_kill_goal", monster_kill_goal );