Implemented dynamic NPCs (#7402)
This is required for features like #6640 Added COLOR_LIGHT_YELLOW Cleaned up color definitions, its useless to store them as string first. Dynamic NPCs will stay when you switch map and come back in time. Dynamic NPCs will be removed instantly when you log out. You can only create one dynamic NPC at a time. Thanks to @Atemo!
This commit is contained in:
153
src/map/npc.cpp
153
src/map/npc.cpp
@@ -70,6 +70,8 @@ static void npc_market_fromsql(void);
|
||||
#define npc_market_clearfromsql(exname) (npc_market_delfromsql_((exname), 0, true))
|
||||
#endif
|
||||
|
||||
TIMER_FUNC(npc_dynamicnpc_removal_timer);
|
||||
|
||||
/// Returns a new npc id that isn't being used in id_db.
|
||||
/// Fatal error if nothing is available.
|
||||
int npc_get_new_npc_id(void) {
|
||||
@@ -864,6 +866,10 @@ int npc_isnear_sub(struct block_list* bl, va_list args) {
|
||||
if (nd->sc.option & (OPTION_HIDE|OPTION_INVISIBLE))
|
||||
return 0;
|
||||
|
||||
if( nd->dynamicnpc.owner_char_id != 0 ){
|
||||
return 0;
|
||||
}
|
||||
|
||||
int skill_id = va_arg(args, int);
|
||||
|
||||
if (skill_id > 0) { //If skill_id > 0 that means is used for INF2_DISABLENEARNPC [Cydh]
|
||||
@@ -954,6 +960,11 @@ bool npc_is_cloaked(struct npc_data* nd, struct map_session_data* sd) {
|
||||
return npc_cloaked;
|
||||
}
|
||||
|
||||
bool npc_is_hidden_dynamicnpc( struct npc_data& nd, struct map_session_data& tsd ){
|
||||
// If the NPC is dynamic and the target character is not the owner of the dynamic NPC
|
||||
return nd.dynamicnpc.owner_char_id != 0 && nd.dynamicnpc.owner_char_id != tsd.status.char_id;
|
||||
}
|
||||
|
||||
static int npc_cloaked_sub(struct block_list *bl, va_list ap)
|
||||
{
|
||||
struct map_session_data* sd;
|
||||
@@ -1839,6 +1850,10 @@ int npc_touch_areanpc(struct map_session_data* sd, int16 m, int16 x, int16 y, st
|
||||
if (npc_is_cloaked(nd, sd))
|
||||
return 1;
|
||||
|
||||
if( npc_is_hidden_dynamicnpc( *nd, *sd ) ){
|
||||
return 1;
|
||||
}
|
||||
|
||||
int xs = -1, ys = -1;
|
||||
switch(nd->subtype) {
|
||||
case NPCTYPE_WARP:
|
||||
@@ -1939,6 +1954,10 @@ int npc_touch_areanpc2(struct mob_data *md)
|
||||
if( mapdata->npc[i]->sc.option&(OPTION_INVISIBLE|OPTION_CLOAK) )
|
||||
continue;
|
||||
|
||||
if( mapdata->npc[i]->dynamicnpc.owner_char_id != 0 ){
|
||||
continue;
|
||||
}
|
||||
|
||||
switch( mapdata->npc[i]->subtype )
|
||||
{
|
||||
case NPCTYPE_WARP:
|
||||
@@ -2032,6 +2051,10 @@ int npc_check_areanpc(int flag, int16 m, int16 x, int16 y, int16 range)
|
||||
if (mapdata->npc[i]->sc.option&OPTION_INVISIBLE)
|
||||
continue;
|
||||
|
||||
if( mapdata->npc[i]->dynamicnpc.owner_char_id != 0 ){
|
||||
continue;
|
||||
}
|
||||
|
||||
switch(mapdata->npc[i]->subtype)
|
||||
{
|
||||
case NPCTYPE_WARP:
|
||||
@@ -2149,11 +2172,19 @@ int npc_click(struct map_session_data* sd, struct npc_data* nd)
|
||||
if (nd->class_ < 0 || nd->sc.option&(OPTION_INVISIBLE|OPTION_HIDE))
|
||||
return 1;
|
||||
|
||||
if( npc_is_hidden_dynamicnpc( *nd, *sd ) ){
|
||||
return 1;
|
||||
}
|
||||
|
||||
if (sd->state.block_action & PCBLOCK_NPCCLICK) {
|
||||
clif_msg(sd, WORK_IN_PROGRESS);
|
||||
return 1;
|
||||
}
|
||||
|
||||
if( nd->dynamicnpc.owner_char_id != 0 ){
|
||||
nd->dynamicnpc.last_interaction = gettick();
|
||||
}
|
||||
|
||||
switch(nd->subtype) {
|
||||
case NPCTYPE_SHOP:
|
||||
clif_npcbuysell(sd,nd->bl.id);
|
||||
@@ -2207,6 +2238,7 @@ int npc_click(struct map_session_data* sd, struct npc_data* nd)
|
||||
*------------------------------------------*/
|
||||
bool npc_scriptcont(struct map_session_data* sd, int id, bool closing){
|
||||
struct block_list *target = map_id2bl(id);
|
||||
struct npc_data* nd = BL_CAST( BL_NPC, target );
|
||||
|
||||
nullpo_retr(true, sd);
|
||||
|
||||
@@ -2217,7 +2249,6 @@ bool npc_scriptcont(struct map_session_data* sd, int id, bool closing){
|
||||
|
||||
if( id != sd->npc_id ){
|
||||
TBL_NPC* nd_sd = (TBL_NPC*)map_id2bl(sd->npc_id);
|
||||
TBL_NPC* nd = BL_CAST(BL_NPC, target);
|
||||
|
||||
ShowDebug("npc_scriptcont: %s (sd->npc_id=%d) is not %s (id=%d).\n",
|
||||
nd_sd?(char*)nd_sd->name:"'Unknown NPC'", (int)sd->npc_id,
|
||||
@@ -2236,6 +2267,10 @@ bool npc_scriptcont(struct map_session_data* sd, int id, bool closing){
|
||||
sd->npc_idle_tick = gettick(); //Update the last NPC iteration
|
||||
#endif
|
||||
|
||||
if( nd != nullptr && nd->dynamicnpc.owner_char_id != 0 ){
|
||||
nd->dynamicnpc.last_interaction = gettick();
|
||||
}
|
||||
|
||||
/**
|
||||
* WPE can get to this point with a progressbar; we deny it.
|
||||
**/
|
||||
@@ -2312,6 +2347,10 @@ int npc_buysellsel(struct map_session_data* sd, int id, int type)
|
||||
if (nd->sc.option & OPTION_INVISIBLE) // can't buy if npc is not visible (hack?)
|
||||
return 1;
|
||||
|
||||
if( npc_is_hidden_dynamicnpc( *nd, *sd ) ){
|
||||
return 1;
|
||||
}
|
||||
|
||||
sd->npc_shopid = id;
|
||||
|
||||
if (type == 0) {
|
||||
@@ -3477,6 +3516,20 @@ int npc_unload(struct npc_data* nd, bool single) {
|
||||
nd->qi_data.clear();
|
||||
|
||||
script_stop_sleeptimers(nd->bl.id);
|
||||
|
||||
if( nd->dynamicnpc.removal_tid != INVALID_TIMER ){
|
||||
delete_timer( nd->dynamicnpc.removal_tid, npc_dynamicnpc_removal_timer );
|
||||
nd->dynamicnpc.removal_tid = INVALID_TIMER;
|
||||
}
|
||||
|
||||
if( nd->dynamicnpc.owner_char_id != 0 ){
|
||||
struct map_session_data* owner = map_charid2sd( nd->dynamicnpc.owner_char_id );
|
||||
|
||||
if( owner != nullptr ){
|
||||
owner->npc_id_dynamic = 0;
|
||||
}
|
||||
}
|
||||
|
||||
aFree(nd);
|
||||
|
||||
return 0;
|
||||
@@ -3719,6 +3772,9 @@ struct npc_data *npc_create_npc(int16 m, int16 x, int16 y){
|
||||
nd->sc_display_count = 0;
|
||||
nd->progressbar.timeout = 0;
|
||||
nd->vd = npc_viewdb[0]; // Default to JT_INVISIBLE
|
||||
nd->dynamicnpc.owner_char_id = 0;
|
||||
nd->dynamicnpc.last_interaction = 0;
|
||||
nd->dynamicnpc.removal_tid = INVALID_TIMER;
|
||||
|
||||
#ifdef MAP_GENERATOR
|
||||
nd->navi.pos = {m, x, y};
|
||||
@@ -4388,8 +4444,7 @@ static const char* npc_parse_script(char* w1, char* w2, char* w3, char* w4, cons
|
||||
/// shop/cashshop/npc: <map name>,<x>,<y>,<facing>%TAB%duplicate(<name of target>)%TAB%<NPC Name>%TAB%<sprite id>
|
||||
/// npc: -%TAB%duplicate(<name of target>)%TAB%<NPC Name>%TAB%<sprite id>,<triggerX>,<triggerY>
|
||||
/// npc: <map name>,<x>,<y>,<facing>%TAB%duplicate(<name of target>)%TAB%<NPC Name>%TAB%<sprite id>,<triggerX>,<triggerY>
|
||||
const char* npc_parse_duplicate(char* w1, char* w2, char* w3, char* w4, const char* start, const char* buffer, const char* filepath)
|
||||
{
|
||||
const char* npc_parse_duplicate( char* w1, char* w2, char* w3, char* w4, const char* start, const char* buffer, const char* filepath, struct map_session_data* owner = nullptr ){
|
||||
short x, y, m, xs = -1, ys = -1;
|
||||
int16 dir;
|
||||
char srcname[128];
|
||||
@@ -4455,6 +4510,12 @@ const char* npc_parse_duplicate(char* w1, char* w2, char* w3, char* w4, const ch
|
||||
nd->src_id = src_id;
|
||||
nd->bl.type = BL_NPC;
|
||||
nd->subtype = (enum npc_subtype)type;
|
||||
|
||||
if( owner != nullptr ){
|
||||
nd->dynamicnpc.owner_char_id = owner->status.char_id;
|
||||
owner->npc_id_dynamic = nd->bl.id;
|
||||
}
|
||||
|
||||
switch( type ) {
|
||||
case NPCTYPE_SCRIPT:
|
||||
++npc_script;
|
||||
@@ -5657,13 +5718,13 @@ int npc_script_event(struct map_session_data* sd, enum npce_event type){
|
||||
* dir: Facing direction of duplicate NPC
|
||||
* Returns duplicate NPC data on success
|
||||
*/
|
||||
npc_data* npc_duplicate_npc( npc_data* nd, char name[NPC_NAME_LENGTH + 1], int16 mapid, int16 x, int16 y, int class_, uint8 dir, int16 xs, int16 ys ){
|
||||
npc_data* npc_duplicate_npc( npc_data& nd, char name[NPC_NAME_LENGTH + 1], int16 mapid, int16 x, int16 y, int class_, uint8 dir, int16 xs, int16 ys, struct map_session_data* owner ){
|
||||
static char w1[128], w2[128], w3[128], w4[128];
|
||||
const char* stat_buf = "- call from duplicate subsystem -\n";
|
||||
char exname[NPC_NAME_LENGTH + 1];
|
||||
|
||||
snprintf(w1, sizeof(w1), "%s,%d,%d,%d", map_getmapdata(mapid)->name, x, y, dir);
|
||||
snprintf(w2, sizeof(w2), "duplicate(%s)", nd->exname);
|
||||
snprintf(w2, sizeof(w2), "duplicate(%s)", nd.exname);
|
||||
|
||||
//Making sure the generated name is not used for another npc.
|
||||
int i = 0;
|
||||
@@ -5681,7 +5742,7 @@ npc_data* npc_duplicate_npc( npc_data* nd, char name[NPC_NAME_LENGTH + 1], int16
|
||||
snprintf( w4, sizeof( w4 ), "%d", class_ );
|
||||
}
|
||||
|
||||
npc_parse_duplicate(w1, w2, w3, w4, stat_buf, stat_buf, "DUPLICATE");//DUPLICATE means nothing for now.
|
||||
npc_parse_duplicate( w1, w2, w3, w4, stat_buf, stat_buf, "DUPLICATE", owner ); //DUPLICATE means nothing for now.
|
||||
|
||||
npc_data* dnd = npc_name2id( exname );
|
||||
|
||||
@@ -5700,6 +5761,81 @@ npc_data* npc_duplicate_npc( npc_data* nd, char name[NPC_NAME_LENGTH + 1], int16
|
||||
return dnd;
|
||||
}
|
||||
|
||||
TIMER_FUNC(npc_dynamicnpc_removal_timer){
|
||||
struct npc_data* nd = map_id2nd( id );
|
||||
|
||||
if( nd == nullptr ){
|
||||
return 0;
|
||||
}
|
||||
|
||||
nd->dynamicnpc.removal_tid = INVALID_TIMER;
|
||||
|
||||
struct map_session_data* sd = map_charid2sd( nd->dynamicnpc.owner_char_id );
|
||||
|
||||
if( sd != nullptr ){
|
||||
// Still talking to the NPC
|
||||
// TODO: are there other fields to check?
|
||||
if( sd->npc_id == nd->bl.id || sd->npc_shopid == nd->bl.id ){
|
||||
// Retry later
|
||||
nd->dynamicnpc.last_interaction = gettick();
|
||||
nd->dynamicnpc.removal_tid = add_timer( nd->dynamicnpc.last_interaction + battle_config.feature_dynamicnpc_timeout, npc_dynamicnpc_removal_timer, nd->bl.id, NULL );
|
||||
return 0;
|
||||
}
|
||||
|
||||
// Last interaction is not long enough in the past
|
||||
if( DIFF_TICK( gettick(), nd->dynamicnpc.last_interaction ) < battle_config.feature_dynamicnpc_timeout ){
|
||||
nd->dynamicnpc.removal_tid = add_timer( nd->dynamicnpc.last_interaction + DIFF_TICK( gettick(), nd->dynamicnpc.last_interaction ), npc_dynamicnpc_removal_timer, nd->bl.id, NULL );
|
||||
return 0;
|
||||
}
|
||||
|
||||
sd->npc_id_dynamic = 0;
|
||||
}
|
||||
|
||||
// Delete the NPC
|
||||
npc_unload( nd, true );
|
||||
// Update NPC event database
|
||||
npc_read_event_script();
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
struct npc_data* npc_duplicate_npc_for_player( struct npc_data& nd, struct map_session_data& sd ){
|
||||
if( sd.npc_id_dynamic != 0 ){
|
||||
clif_msg_color( &sd, C_DYNAMICNPC_TWICE, color_table[COLOR_LIGHT_YELLOW] );
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
// TODO: check maps that might forbid usage? maybe create mapflag?
|
||||
|
||||
int16 new_x, new_y;
|
||||
|
||||
if( !map_search_freecell( &sd.bl, 0, &new_x, &new_y, battle_config.feature_dynamicnpc_rangex, battle_config.feature_dynamicnpc_rangey, 0 ) ){
|
||||
ShowError( "npc_duplicate_npc_for_player: Unable to find a free cell to duplicate NPC \"%s\" for player \"%s\"(AID: %u, CID: %u)\n", nd.exname, sd.status.name, sd.status.account_id, sd.status.char_id );
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
int8 dir;
|
||||
|
||||
if( battle_config.feature_dynamicnpc_direction ){
|
||||
// Let the NPC look to the player
|
||||
dir = map_calc_dir_xy( new_x, new_y, sd.bl.x, sd.bl.y, DIR_CENTER );
|
||||
}else{
|
||||
// Use original NPCs direction
|
||||
dir = nd.ud.dir;
|
||||
}
|
||||
|
||||
struct npc_data* dnd = npc_duplicate_npc( nd, nd.name, sd.bl.m, new_x, new_y, nd.class_, dir, nd.u.scr.xs, nd.u.scr.ys, &sd );
|
||||
|
||||
if( dnd == nullptr ){
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
dnd->dynamicnpc.last_interaction = gettick();
|
||||
dnd->dynamicnpc.removal_tid = add_timer( dnd->dynamicnpc.last_interaction + battle_config.feature_dynamicnpc_timeout, npc_dynamicnpc_removal_timer, dnd->bl.id, NULL );
|
||||
|
||||
return dnd;
|
||||
}
|
||||
|
||||
const char *npc_get_script_event_name(int npce_index)
|
||||
{
|
||||
switch (npce_index) {
|
||||
@@ -6034,11 +6170,10 @@ void do_init_npc(void){
|
||||
|
||||
add_timer_func_list(npc_event_do_clock,"npc_event_do_clock");
|
||||
add_timer_func_list(npc_timerevent,"npc_timerevent");
|
||||
add_timer_func_list( npc_dynamicnpc_removal_timer, "npc_dynamicnpc_removal_timer" );
|
||||
|
||||
// Init dummy NPC
|
||||
fake_nd = (struct npc_data *)aCalloc(1,sizeof(struct npc_data));
|
||||
fake_nd->bl.m = -1;
|
||||
fake_nd->bl.id = npc_get_new_npc_id();
|
||||
fake_nd = npc_create_npc( -1, 0, 0 );
|
||||
fake_nd->class_ = JT_FAKENPC;
|
||||
fake_nd->speed = 200;
|
||||
strcpy(fake_nd->name,"FAKE_NPC");
|
||||
|
||||
Reference in New Issue
Block a user