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:
parent
774d4ca2e8
commit
a763ad0629
@ -56,7 +56,7 @@ feature.autotrade_head_direction: 0
|
||||
// 1 = Sitting
|
||||
feature.autotrade_sit: 1
|
||||
|
||||
// Delay in miliseconds to open vending/buyingsotre after player logged in.
|
||||
// Delay in milliseconds to open vending/buyingsotre after player logged in.
|
||||
feature.autotrade_open_delay: 5000
|
||||
|
||||
// Battlegrounds queue interface. Makes it possible to queue for a battleground anywhere using the battle menu.
|
||||
@ -119,3 +119,19 @@ feature.barter: on
|
||||
// Extended Barter Shop System (Note 1)
|
||||
// Requires: 2019-11-06RagexeRE or later
|
||||
feature.barter_extended: on
|
||||
|
||||
// The timeout in milliseconds when a dynamic NPC will be despawned if not used.
|
||||
// Default: 60000 (60s)
|
||||
feature.dynamicnpc_timeout: 60000
|
||||
|
||||
// The x range in which the dynamic NPC will be spawned relative to the player.
|
||||
// Default: 2
|
||||
feature.dynamicnpc_rangex: 2
|
||||
|
||||
// The y range in which the dynamic NPC will be spawned relative to the player.
|
||||
// Default: 2
|
||||
feature.dynamicnpc_rangey: 2
|
||||
|
||||
// Should the dynamic NPCs look into the direction of the player? (Note 1)
|
||||
// Default: no
|
||||
feature.dynamicnpc_direction: no
|
||||
|
@ -6895,6 +6895,17 @@ This command will duplicate the NPC with the given <NPC name> on <map> at <x>/<y
|
||||
If <Duplicate NPC name>, <sprite>, <dir>, <xs> or <ys> is not provided the value of the original NPC will be used.
|
||||
The Unique name of the new duplicated NPC is returned on success. An empty string is returned on failure.
|
||||
|
||||
NOTE:
|
||||
Duplicates will always have the same NPC variables as the original NPC.
|
||||
Editing a NPC variable in a duplicate or the original NPC will change it for the others.
|
||||
|
||||
---------------------------------------
|
||||
|
||||
*duplicate_dynamic("<NPC name>"{,<character ID>});
|
||||
|
||||
This command will duplicate the NPC with the given <NPC name> near the attached player or the player with the given <character ID>.
|
||||
The Unique name of the new duplicated NPC is returned on success. An empty string is returned on failure.
|
||||
|
||||
NOTE:
|
||||
Duplicates will always have the same NPC variables as the original NPC.
|
||||
Editing a NPC variable in a duplicate or the original NPC will change it for the others.
|
||||
|
@ -10271,6 +10271,11 @@ static const struct _battle_data {
|
||||
{ "macro_detection_retry", &battle_config.macro_detection_retry, 3, 1, INT_MAX, },
|
||||
{ "macro_detection_timeout", &battle_config.macro_detection_timeout, 60000, 0, INT_MAX, },
|
||||
|
||||
{ "feature.dynamicnpc_timeout", &battle_config.feature_dynamicnpc_timeout, 1000, 60000, INT_MAX, },
|
||||
{ "feature.dynamicnpc_rangex", &battle_config.feature_dynamicnpc_rangex, 2, 0, INT_MAX, },
|
||||
{ "feature.dynamicnpc_rangey", &battle_config.feature_dynamicnpc_rangey, 2, 0, INT_MAX, },
|
||||
{ "feature.dynamicnpc_direction", &battle_config.feature_dynamicnpc_direction, 0, 0, 1, },
|
||||
|
||||
#include "../custom/battle_config_init.inc"
|
||||
};
|
||||
|
||||
|
@ -714,6 +714,11 @@ struct Battle_Config
|
||||
int macro_detection_retry;
|
||||
int macro_detection_timeout;
|
||||
|
||||
int feature_dynamicnpc_timeout;
|
||||
int feature_dynamicnpc_rangex;
|
||||
int feature_dynamicnpc_rangey;
|
||||
int feature_dynamicnpc_direction;
|
||||
|
||||
#include "../custom/battle_config_struct.inc"
|
||||
};
|
||||
|
||||
|
@ -448,6 +448,11 @@ static int clif_send_sub(struct block_list *bl, va_list ap)
|
||||
break;
|
||||
}
|
||||
|
||||
if( src_bl->type == BL_NPC && npc_is_hidden_dynamicnpc( *( (struct npc_data*)src_bl ), *sd ) ){
|
||||
// Do not send anything
|
||||
return 0;
|
||||
}
|
||||
|
||||
/* unless visible, hold it here */
|
||||
if (!battle_config.update_enemy_position && clif_ally_only && !sd->special_state.intravision &&
|
||||
!sd->sc.data[SC_INTRAVISION] && battle_check_target(src_bl,&sd->bl,BCT_ENEMY) > 0)
|
||||
@ -4954,6 +4959,11 @@ void clif_getareachar_unit( struct map_session_data* sd,struct block_list *bl ){
|
||||
if (clif_npc_mayapurple(bl))
|
||||
return;
|
||||
|
||||
if( bl->type == BL_NPC && npc_is_hidden_dynamicnpc( *( (struct npc_data*)bl ), *sd ) ){
|
||||
// Do not send anything
|
||||
return;
|
||||
}
|
||||
|
||||
ud = unit_bl2ud(bl);
|
||||
|
||||
if( ud && ud->walktimer != INVALID_TIMER ){
|
||||
@ -25087,21 +25097,21 @@ void packetdb_readdb(){
|
||||
*
|
||||
*------------------------------------------*/
|
||||
void do_init_clif(void) {
|
||||
const char* colors[COLOR_MAX] = {
|
||||
"0x00FF00",
|
||||
"0xFF0000",
|
||||
"0xFFFFFF",
|
||||
"0xFFFF00",
|
||||
"0x00FFFF",
|
||||
"0xB5FFB5",
|
||||
const int colors[COLOR_MAX] = {
|
||||
0x00FF00, // COLOR_DEFAULT
|
||||
0xFF0000, // COLOR_RED
|
||||
0xFFFFFF, // COLOR_WHITE
|
||||
0xFFFF00, // COLOR_YELLOW
|
||||
0x00FFFF, // COLOR_CYAN
|
||||
0xB5FFB5, // COLOR_LIGHT_GREEN
|
||||
0xFFFF63, // COLOR_LIGHT_YELLOW
|
||||
};
|
||||
int i;
|
||||
|
||||
/**
|
||||
* Setup Color Table (saves unnecessary load of strtoul on every call)
|
||||
**/
|
||||
for(i = 0; i < COLOR_MAX; i++) {
|
||||
color_table[i] = strtoul(colors[i],NULL,0);
|
||||
color_table[i] = (color_table[i] & 0x0000FF) << 16 | (color_table[i] & 0x00FF00) | (color_table[i] & 0xFF0000) >> 16;//RGB to BGR
|
||||
for( int i = 0; i < COLOR_MAX; i++ ){
|
||||
color_table[i] = ( colors[i] & 0x0000FF ) << 16 | ( colors[i] & 0x00FF00 ) | ( colors[i] & 0xFF0000 ) >> 16; //RGB to BGR
|
||||
}
|
||||
|
||||
packetdb_readdb();
|
||||
|
@ -556,6 +556,7 @@ enum clif_messages : uint16_t {
|
||||
MSG_ATTENDANCE_DISABLED = 0xd92,
|
||||
|
||||
// Unofficial names
|
||||
C_DYNAMICNPC_TWICE = 0xa47, /// <"Is already in service. Please try again in a few minutes."
|
||||
C_ITEM_EQUIP_SWITCH = 0xbc7,
|
||||
C_ITEM_NOEQUIP = 0x174, /// <"You can't put this item on."
|
||||
C_ENCHANT_OVERWEIGHT = 0xEFD,
|
||||
@ -961,6 +962,7 @@ void clif_equipcheckbox(struct map_session_data* sd);
|
||||
void clif_msg(struct map_session_data* sd, unsigned short id);
|
||||
void clif_msg_value(struct map_session_data* sd, unsigned short id, int value);
|
||||
void clif_msg_skill(struct map_session_data* sd, uint16 skill_id, int msg_id);
|
||||
void clif_msg_color( struct map_session_data* sd, uint16 msg_id, uint32 color );
|
||||
|
||||
//quest system [Kevin] [Inkfish]
|
||||
void clif_quest_send_list(struct map_session_data * sd);
|
||||
@ -1126,6 +1128,7 @@ enum clif_colors {
|
||||
COLOR_YELLOW,
|
||||
COLOR_CYAN,
|
||||
COLOR_LIGHT_GREEN,
|
||||
COLOR_LIGHT_YELLOW,
|
||||
COLOR_MAX
|
||||
};
|
||||
extern unsigned long color_table[COLOR_MAX];
|
||||
|
@ -571,7 +571,7 @@ int map_count_oncell(int16 m, int16 x, int16 y, int type, int flag)
|
||||
if(bl->x == x && bl->y == y && bl->type&type) {
|
||||
if (bl->type == BL_NPC) { // Don't count hidden or invisible npc. Cloaked npc are counted
|
||||
npc_data *nd = BL_CAST(BL_NPC, bl);
|
||||
if (nd && (nd->bl.m < 0 || nd->sc.option && nd->sc.option&(OPTION_HIDE|OPTION_INVISIBLE)))
|
||||
if (nd->bl.m < 0 || nd->sc.option&(OPTION_HIDE|OPTION_INVISIBLE) || nd->dynamicnpc.owner_char_id != 0)
|
||||
continue;
|
||||
}
|
||||
if(flag&1) {
|
||||
|
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");
|
||||
|
@ -208,6 +208,12 @@ struct npc_data {
|
||||
unsigned long color;
|
||||
} progressbar;
|
||||
|
||||
struct{
|
||||
uint32 owner_char_id;
|
||||
t_tick last_interaction;
|
||||
int removal_tid;
|
||||
} dynamicnpc;
|
||||
|
||||
#ifdef MAP_GENERATOR
|
||||
struct navi_link navi; // for warps and the src of npcs
|
||||
std::vector<navi_link> links; // for extra links, like warper npc
|
||||
@ -1494,12 +1500,14 @@ void npc_parse_mob2(struct spawn_data* mob);
|
||||
struct npc_data* npc_add_warp(char* name, short from_mapid, short from_x, short from_y, short xs, short ys, unsigned short to_mapindex, short to_x, short to_y);
|
||||
int npc_globalmessage(const char* name,const char* mes);
|
||||
const char *npc_get_script_event_name(int npce_index);
|
||||
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 = nullptr );
|
||||
struct npc_data* npc_duplicate_npc_for_player( struct npc_data& nd, struct map_session_data& sd );
|
||||
|
||||
void npc_setcells(struct npc_data* nd);
|
||||
void npc_unsetcells(struct npc_data* nd);
|
||||
bool npc_movenpc(struct npc_data* nd, int16 x, int16 y);
|
||||
bool npc_is_cloaked(struct npc_data* nd, struct map_session_data* sd);
|
||||
bool npc_is_hidden_dynamicnpc( struct npc_data& nd, struct map_session_data& tsd );
|
||||
bool npc_enable_target(npc_data& nd, uint32 char_id, e_npcv_status flag);
|
||||
#define npc_enable(nd, flag) npc_enable_target(nd, 0, flag)
|
||||
void npc_setdisplayname(struct npc_data* nd, const char* newname);
|
||||
|
@ -491,6 +491,7 @@ struct map_session_data {
|
||||
unsigned char head_dir; //0: Look forward. 1: Look right, 2: Look left.
|
||||
t_tick client_tick;
|
||||
int npc_id,npc_shopid; //for script follow scriptoid; ,npcid
|
||||
int npc_id_dynamic;
|
||||
std::vector<int> areanpc, npc_ontouch_; ///< Array of OnTouch and OnTouch_ NPC ID
|
||||
int npc_item_flag; //Marks the npc_id with which you can use items during interactions with said npc (see script command enable_itemuse)
|
||||
int npc_menu; // internal variable, used in npc menu handling
|
||||
|
@ -25193,7 +25193,40 @@ BUILDIN_FUNC(duplicate)
|
||||
ys = nd->u.scr.ys;
|
||||
}
|
||||
|
||||
npc_data* dnd = npc_duplicate_npc( nd, name, mapid, x, y, class_, dir, xs, ys );
|
||||
npc_data* dnd = npc_duplicate_npc( *nd, name, mapid, x, y, class_, dir, xs, ys );
|
||||
|
||||
if( dnd == nullptr ){
|
||||
script_pushstrcopy( st, "" );
|
||||
return SCRIPT_CMD_FAILURE;
|
||||
}else{
|
||||
script_pushstrcopy( st, dnd->exname );
|
||||
return SCRIPT_CMD_SUCCESS;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Duplicate a NPC for a player.
|
||||
* Return the duplicate Unique name on success or empty string on failure.
|
||||
* duplicate_dynamic("<NPC name>"{,<character ID>});
|
||||
*/
|
||||
BUILDIN_FUNC(duplicate_dynamic){
|
||||
const char* old_npcname = script_getstr( st, 2 );
|
||||
struct npc_data* nd = npc_name2id( old_npcname );
|
||||
|
||||
if( nd == nullptr ){
|
||||
ShowError( "buildin_duplicate_dynamic: No such NPC '%s'.\n", old_npcname );
|
||||
script_pushstrcopy( st, "" );
|
||||
return SCRIPT_CMD_FAILURE;
|
||||
}
|
||||
|
||||
struct map_session_data* sd;
|
||||
|
||||
if( !script_charid2sd( 3, sd ) ){
|
||||
script_pushstrcopy( st, "" );
|
||||
return SCRIPT_CMD_FAILURE;
|
||||
}
|
||||
|
||||
struct npc_data* dnd = npc_duplicate_npc_for_player( *nd, *sd );
|
||||
|
||||
if( dnd == nullptr ){
|
||||
script_pushstrcopy( st, "" );
|
||||
@ -27427,6 +27460,7 @@ struct script_function buildin_func[] = {
|
||||
BUILDIN_DEF(openstorage2,"ii?"),
|
||||
BUILDIN_DEF(unloadnpc, "s"),
|
||||
BUILDIN_DEF(duplicate, "ssii?????"),
|
||||
BUILDIN_DEF(duplicate_dynamic, "s?"),
|
||||
|
||||
// WoE TE
|
||||
BUILDIN_DEF(agitstart3,""),
|
||||
|
@ -3426,6 +3426,19 @@ int unit_free(struct block_list *bl, clr_type clrtype)
|
||||
sd->npc_id = 0;
|
||||
}
|
||||
|
||||
if( sd->npc_id_dynamic != 0 ){
|
||||
struct npc_data* nd = map_id2nd( sd->npc_id_dynamic );
|
||||
|
||||
if( nd != nullptr ){
|
||||
// Delete the NPC
|
||||
npc_unload( nd, true );
|
||||
// Update NPC event database
|
||||
npc_read_event_script();
|
||||
}
|
||||
|
||||
sd->npc_id_dynamic = 0;
|
||||
}
|
||||
|
||||
sd->combos.clear();
|
||||
|
||||
if( sd->sc_display_count ) { /* [Ind] */
|
||||
|
Loading…
x
Reference in New Issue
Block a user