Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Unique npc ids, allow interacting with npcs that are not in reality bubble #57533

Merged
merged 6 commits into from
May 9, 2022
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions doc/MAPGEN.md
Original file line number Diff line number Diff line change
Expand Up @@ -759,6 +759,7 @@ Example:
| class | (required, string) the npc class id, see `data/json/npcs/npc.json` or define your own npc class.
| target | (optional, bool) this NPC is a mission target. Only valid for `update_mapgen`.
| add_trait | (optional, string or string array) this NPC gets these traits, in addition to any from the class definition.
| unique_id | (optional, string) This is a unique id to descibe the npc, if an npc with this id already exists the command will silently fail.


### Place signs with "signs"
Expand Down
2 changes: 1 addition & 1 deletion doc/NPCs.md
Original file line number Diff line number Diff line change
Expand Up @@ -754,7 +754,7 @@ Effect | Description
`offer_mission" mission_type_id string or array`. Adds mission_type_id(s) to the npc's missions that they offer.
`run_eocs : effect_on_condition_array or single effect_condition_object` | Will run up all members of the `effect_on_condition_array`. Members should either be the id of an effect_on_condition or an inline effect_on_condition.
`queue_eocs : effect_on_condition_array or single effect_condition_object`, `time_in_future: time_in_future_int or string or variable_object` | Will queue up all members of the `effect_on_condition_array`. Members should either be the id of an effect_on_condition or an inline effect_on_condition. Members will be run `time_in_future` seconds or if it is a string the future values of them or if they are variable objects the variable they name. If the eoc is global the avatar will be u and npc will be invalid. Otherwise it will be queued for the current alpha if they are a character and not be queued otherwise.
`u_run_npc_eocs or npc_run_npc_eocs : effect_on_condition_array`, (*optional* `npcs_to_affect: npcs_to_affect_string_array`, (*optional* `npcs_must_see: npcs_must_see_bool`), (*optional* `npc_range: npc_range_int`) | Will run all members of the `effect_on_condition_array` on nearby npcs. Members should either be the id of an effect_on_condition or an inline effect_on_condition. If any names are listed in `npcs_to_affect` then only they will be affected. If a value is given for `npc_range` the npc must be that close to the source and if `npcs_must_see`(defaults to false) is true the npc must be able to see the source. For `u_run_npc_eocs` u is the source for `npc_run_npc_eocs` it is the npc.
`u_run_npc_eocs or npc_run_npc_eocs : effect_on_condition_array`, (*optional* `unique_ids: unique_ids_string_array`), (*optional* `npcs_must_see: npcs_must_see_bool`), (*optional* `npc_range: npc_range_int`), (*optional* `local: local_bool`) | Will run all members of the `effect_on_condition_array` on npcs. Members should either be the id of an effect_on_condition or an inline effect_on_condition. If `local`(default: false) is false, then regardless of location all npcs with unique ids in the array `unique_ids` will be affected. If `local` is true, only unique_ids listed in `unique_ids` will be affected, if it is empty all npcs in range will be effected. If a value is given for `npc_range` the npc must be that close to the source and if `npcs_must_see`(defaults to false) is true the npc must be able to see the source. For `u_run_npc_eocs` u is the source for `npc_run_npc_eocs` it is the npc.
`weighted_list_eocs: array_array` | Will choose one of a list of eocs to activate based on weight. Members should be an array of first the id of an effect_on_condition or an inline effect_on_condition and second an object that resolves to an integer weight.
Example: This will cause "EOC_SLEEP" 1/10 as often as it makes a test message appear.
``` json
Expand Down
3 changes: 3 additions & 0 deletions src/debug_menu.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -1621,6 +1621,9 @@ static void character_edit_menu()
std::stringstream data;
data << np->get_name() << " - " << ( np->male ? _( "Male" ) : _( "Female" ) ) << " " <<
np->myclass->get_name() << std::endl;
if( !np->get_unique_id().empty() ) {
data << string_format( _( "Unique Id: %s" ), np->get_unique_id() ) << std::endl;
}
data << string_format( _( "Faction: %s (api v%d)" ), np->get_faction()->id.str(),
np->get_faction_ver() ) << "; "
<< string_format( _( "Attitude: %s" ), npc_attitude_name( np->get_attitude() ) ) << std::endl;
Expand Down
26 changes: 26 additions & 0 deletions src/game.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -734,6 +734,7 @@ void game::setup()
remoteveh_cache = nullptr;
global_variables &globvars = get_globals();
globvars.clear_global_values();
unique_npcs.clear();
get_weather().weather_override = WEATHER_NULL;
// back to menu for save loading, new game etc
}
Expand Down Expand Up @@ -1533,6 +1534,11 @@ npc *game::find_npc( character_id id )
return overmap_buffer.find_npc( id ).get();
}

npc *game::find_npc_by_unique_id( std::string unique_id )
{
return overmap_buffer.find_npc_by_unique_id( unique_id ).get();
}

void game::add_npc_follower( const character_id &id )
{
follower_ids.insert( id );
Expand Down Expand Up @@ -2906,6 +2912,26 @@ memorial_logger &game::memorial()
return *memorial_logger_ptr;
}

void game::update_unique_npc_location( std::string id, point_abs_om loc )
{
unique_npcs[id] = loc;
}

point_abs_om game::get_unique_npc_location( std::string id )
{
if( unique_npc_exists( id ) ) {
return unique_npcs[id];
} else {
debugmsg( "Tried to find npc %s which doesn't exist.", id );
return point_abs_om();
}
}

bool game::unique_npc_exists( std::string id )
{
return unique_npcs.count( id ) > 0;
}

spell_events &game::spell_events_subscriber()
{
return *spell_events_ptr;
Expand Down
6 changes: 6 additions & 0 deletions src/game.h
Original file line number Diff line number Diff line change
Expand Up @@ -497,6 +497,8 @@ class game
int assign_mission_id();
/** Find the npc with the given ID. Returns NULL if the npc could not be found. Searches all loaded overmaps. */
npc *find_npc( character_id id );
/** Find the npc with the given unique ID. Returns NULL if the npc could not be found. Searches all loaded overmaps. */
npc *find_npc_by_unique_id( std::string unique_id );
/** Makes any nearby NPCs on the overmap active. */
void load_npcs();
private:
Expand Down Expand Up @@ -1018,7 +1020,11 @@ class game
memorial_logger &memorial();

global_variables global_variables_instance;
std::unordered_map<std::string, point_abs_om> unique_npcs;
public:
void update_unique_npc_location( std::string id, point_abs_om loc );
point_abs_om get_unique_npc_location( std::string id );
bool unique_npc_exists( std::string id );
std::vector<effect_on_condition_id> inactive_global_effect_on_condition_vector;
std::priority_queue<queued_eoc, std::vector<queued_eoc>, eoc_compare>
queued_global_effect_on_conditions;
Expand Down
12 changes: 12 additions & 0 deletions src/mapgen.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -1740,6 +1740,7 @@ class jmapgen_npc : public jmapgen_piece
mapgen_value<string_id<npc_template>> npc_class;
bool target;
std::vector<trait_id> traits;
std::string unique_id;
jmapgen_npc( const JsonObject &jsi, const std::string &/*context*/ ) :
npc_class( jsi.get_member( "class" ) )
, target( jsi.get_bool( "target", false ) ) {
Expand All @@ -1750,13 +1751,21 @@ class jmapgen_npc : public jmapgen_piece
} else if( jsi.has_array( "add_trait" ) ) {
jsi.read( "add_trait", traits );
}
if( jsi.has_string( "unique_id" ) ) {
jsi.read( "unique_id", unique_id );
}
}
void apply( const mapgendata &dat, const jmapgen_int &x, const jmapgen_int &y,
const std::string &/*context*/ ) const override {
string_id<npc_template> chosen_id = npc_class.get( dat );
if( chosen_id.is_null() ) {
return;
}
if( !unique_id.empty() && g->unique_npc_exists( unique_id ) ) {
get_avatar().add_msg_debug_if_player( debugmode::DF_NPC, "NPC with unique id %s already exists.",
unique_id );
return;
}
character_id npc_id = dat.m.place_npc( point( x.get(), y.get() ), chosen_id );
if( dat.mission() && target ) {
dat.mission()->set_target_npc_id( npc_id );
Expand All @@ -1766,6 +1775,9 @@ class jmapgen_npc : public jmapgen_piece
for( const trait_id &new_trait : traits ) {
p->set_mutation( new_trait );
}
if( !unique_id.empty() ) {
p->set_unique_id( unique_id );
}
}
}

Expand Down
18 changes: 18 additions & 0 deletions src/npc.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -1081,6 +1081,9 @@ void npc::on_move( const tripoint_abs_ms &old_pos )
if( !is_fake() && pos_om_old != pos_om_new ) {
overmap &om_old = overmap_buffer.get( pos_om_old );
overmap &om_new = overmap_buffer.get( pos_om_new );
if( !unique_id.empty() ) {
g->update_unique_npc_location( unique_id, pos_om_new );
}
if( const auto ptr = om_old.erase_npc( getID() ) ) {
om_new.insert_npc( ptr );
} else {
Expand Down Expand Up @@ -3539,6 +3542,21 @@ attitude_group npc::get_attitude_group( npc_attitude att ) const
return attitude_group::neutral;
}

void npc::set_unique_id( std::string id )
{
if( !unique_id.empty() ) {
debugmsg( "Tried to set unique_id of npc with one already of value: ", unique_id );
} else {
unique_id = id;
g->update_unique_npc_location( id, project_to<coords::om>( get_location().xy() ) );
}
}

std::string npc::get_unique_id() const
{
return unique_id;
}

void npc::set_mission( npc_mission new_mission )
{
if( new_mission != mission ) {
Expand Down
5 changes: 4 additions & 1 deletion src/npc.h
Original file line number Diff line number Diff line change
Expand Up @@ -1405,7 +1405,8 @@ class npc : public Character
bool has_companion_mission() const;
npc_companion_mission get_companion_mission() const;
attitude_group get_attitude_group( npc_attitude att ) const;

void set_unique_id( std::string id );
std::string get_unique_id() const;
protected:
void store( JsonOut &json ) const;
void load( const JsonObject &data );
Expand All @@ -1425,6 +1426,8 @@ class npc : public Character
std::vector<sphere> find_dangerous_explosives() const;

npc_companion_mission comp_mission;

std::string unique_id;
};

/** An NPC with standard stats */
Expand Down
60 changes: 40 additions & 20 deletions src/npctalk.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -3729,34 +3729,54 @@ void talk_effect_fun_t::set_run_npc_eocs( const JsonObject &jo,
{
std::vector<effect_on_condition_id> eocs = load_eoc_vector( jo, member );

std::vector<std::string> names = jo.get_string_array( "npcs_to_affect" );
std::vector<std::string> unique_ids = jo.get_string_array( "unique_ids" );

bool local = jo.get_bool( "local", false );
cata::optional<int> npc_range;
if( jo.has_int( "npc_range" ) ) {
npc_range = jo.get_int( "npc_range" );
}
bool npc_must_see = jo.get_bool( "npc_must_see", false );
function = [eocs, names, npc_must_see, npc_range, is_npc]( const dialogue & d ) {
tripoint actor_pos = d.actor( is_npc )->pos();
const std::vector<npc *> available = g->get_npcs_if( [npc_must_see, npc_range, actor_pos,
names]( const npc & guy ) {
bool name_valid = names.empty();
for( const std::string &name : names ) {
if( name == guy.name ) {
name_valid = true;
break;
if( local ) {
function = [eocs, unique_ids, npc_must_see, npc_range, is_npc]( const dialogue & d ) {
tripoint actor_pos = d.actor( is_npc )->pos();
const std::vector<npc *> available = g->get_npcs_if( [npc_must_see, npc_range, actor_pos,
unique_ids]( const npc & guy ) {
bool id_valid = unique_ids.empty();
for( const std::string &id : unique_ids ) {
if( id == guy.get_unique_id() ) {
id_valid = true;
break;
}
}
return id_valid && ( !npc_range.has_value() || actor_pos.z == guy.posz() ) && ( !npc_must_see ||
guy.sees( actor_pos ) ) &&
( !npc_range.has_value() || rl_dist( actor_pos, guy.pos() ) <= npc_range.value() );
} );
for( npc *target : available ) {
for( const effect_on_condition_id &eoc : eocs ) {
dialogue newDialog( get_talker_for( target ), nullptr );
eoc->activate( newDialog );
}
}
return name_valid && ( !npc_range.has_value() || actor_pos.z == guy.posz() ) && ( !npc_must_see ||
guy.sees( actor_pos ) ) &&
( !npc_range.has_value() || rl_dist( actor_pos, guy.pos() ) <= npc_range.value() );
} );
for( npc *target : available ) {
for( const effect_on_condition_id &eoc : eocs ) {
dialogue newDialog( get_talker_for( target ), nullptr );
eoc->activate( newDialog );
};
} else {
function = [eocs, unique_ids]( const dialogue & ) {
for( const std::string &target : unique_ids ) {
if( g->unique_npc_exists( target ) ) {
for( const effect_on_condition_id &eoc : eocs ) {
npc *npc = g->find_npc_by_unique_id( target );
if( npc ) {
dialogue newDialog( get_talker_for( npc ), nullptr );
eoc->activate( newDialog );
} else {
debugmsg( "Tried to use invalid npc: %s", target );
}
}
}
}
}
};
};
}
}

void talk_effect_fun_t::set_queue_eocs( const JsonObject &jo, const std::string &member )
Expand Down
10 changes: 10 additions & 0 deletions src/overmap.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -6432,6 +6432,16 @@ shared_ptr_fast<npc> overmap::find_npc( const character_id &id ) const
return nullptr;
}

shared_ptr_fast<npc> overmap::find_npc_by_unique_id( const std::string &id ) const
{
for( const auto &guy : npcs ) {
if( guy->get_unique_id() == id ) {
return guy;
}
}
return nullptr;
}

cata::optional<basecamp *> overmap::find_camp( const point_abs_omt &p )
{
for( auto &v : camps ) {
Expand Down
2 changes: 1 addition & 1 deletion src/overmap.h
Original file line number Diff line number Diff line change
Expand Up @@ -369,7 +369,7 @@ class overmap
void for_each_npc( const std::function<void( const npc & )> &callback ) const;

shared_ptr_fast<npc> find_npc( const character_id &id ) const;

shared_ptr_fast<npc> find_npc_by_unique_id( const std::string &id ) const;
const std::vector<shared_ptr_fast<npc>> &get_npcs() const {
return npcs;
}
Expand Down
6 changes: 6 additions & 0 deletions src/overmapbuffer.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -1267,6 +1267,12 @@ shared_ptr_fast<npc> overmapbuffer::find_npc( character_id id )
return nullptr;
}

shared_ptr_fast<npc> overmapbuffer::find_npc_by_unique_id( std::string unique_id )
{
point_abs_om loc = g->get_unique_npc_location( unique_id );
return get( loc ).find_npc_by_unique_id( unique_id );
}

cata::optional<basecamp *> overmapbuffer::find_camp( const point_abs_omt &p )
{
for( auto &it : overmaps ) {
Expand Down
1 change: 1 addition & 0 deletions src/overmapbuffer.h
Original file line number Diff line number Diff line change
Expand Up @@ -283,6 +283,7 @@ class overmapbuffer
* Searches all loaded overmaps.
*/
shared_ptr_fast<npc> find_npc( character_id id );
shared_ptr_fast<npc> find_npc_by_unique_id( std::string unique_id );
/**
* Get all NPCs active on the overmap
*/
Expand Down
3 changes: 2 additions & 1 deletion src/savegame.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -143,7 +143,7 @@ void game::serialize( std::ostream &fout )
json.end_array();
global_variables_instance.serialize( json );
Messages::serialize( json );

json.member( "unique_npcs", unique_npcs );
json.end_object();
}

Expand Down Expand Up @@ -269,6 +269,7 @@ void game::unserialize( std::istream &fin, const std::string &path )
queued_global_effect_on_conditions.push( temp );
}
global_variables_instance.unserialize( data );
data.read( "unique_npcs", unique_npcs );
inp_mngr.pump_events();
data.read( "stats_tracker", *stats_tracker_ptr );
data.read( "achievements_tracker", *achievements_tracker_ptr );
Expand Down
2 changes: 2 additions & 0 deletions src/savegame_json.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -2235,6 +2235,7 @@ void npc::load( const JsonObject &data )
member.read( p );
complaints.emplace( member.name(), p );
}
data.read( "unique_id", unique_id );
}

/*
Expand Down Expand Up @@ -2302,6 +2303,7 @@ void npc::store( JsonOut &json ) const
json.member( "restock", restock );

json.member( "complaints", complaints );
json.member( "unique_id", unique_id );
}

////////////////////////////////////////////////////////////////////////////////////////////////////
Expand Down