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