From 60d7a663e272f795be0017b7ff8bf8b541c37d1e Mon Sep 17 00:00:00 2001 From: "Guilherme G. Menaldo" Date: Mon, 9 May 2022 21:05:07 -0300 Subject: [PATCH 1/6] change instance window types and flags into enums also do some style fix to instance functions --- src/map/clif.c | 54 +++++++++++++++++++++++----------------------- src/map/clif.h | 29 ++++++++++++++++++++++++- src/map/instance.c | 36 ++++++++++++++++--------------- src/map/instance.h | 19 ++++++++++++++++ 4 files changed, 93 insertions(+), 45 deletions(-) diff --git a/src/map/clif.c b/src/map/clif.c index ceff9e49b41..c8385b234bd 100644 --- a/src/map/clif.c +++ b/src/map/clif.c @@ -19430,13 +19430,13 @@ static void clif_font(struct map_session_data *sd) /*========================================== * Instancing Window *------------------------------------------*/ -static int clif_instance(int instance_id, int type, int flag) +static int clif_instance(int instance_id, enum instance_window_info_type type, int flag) { struct map_session_data *sd = NULL; unsigned char buf[255]; enum send_target target = PARTY; - switch( instance->list[instance_id].owner_type ) { + switch (instance->list[instance_id].owner_type) { case IOT_NONE: return 0; case IOT_GUILD: @@ -19455,52 +19455,52 @@ static int clif_instance(int instance_id, int type, int flag) break; } - if( !sd ) + if (!sd) return 0; - switch( type ) { - case 1: + switch (type) { + case INSTANCE_WND_INFO_CREATE: // S 0x2cb .61B .W // Required to start the instancing information window on Client // This window re-appear each "refresh" of client automatically until type 4 is send to client. - WBUFW(buf,0) = 0x02CB; - memcpy(WBUFP(buf,2),instance->list[instance_id].name,INSTANCE_NAME_LENGTH); - WBUFW(buf,63) = flag; - clif->send(buf,packet_len(0x02CB),&sd->bl,target); + WBUFW(buf, 0) = 0x02CB; + memcpy(WBUFP(buf, 2), instance->list[instance_id].name, INSTANCE_NAME_LENGTH); + WBUFW(buf, 63) = flag; + clif->send(buf, packet_len(0x02CB), &sd->bl, target); break; - case 2: + case INSTANCE_WND_INFO_QUEUE_POS: // S 0x2cc .W // To announce Instancing queue creation if no maps available // flag is priority, negative value mean cancel reservation - WBUFW(buf,0) = 0x02CC; - WBUFW(buf,2) = flag; - clif->send(buf,packet_len(0x02CC),&sd->bl,target); + WBUFW(buf, 0) = 0x02CC; + WBUFW(buf, 2) = flag; + clif->send(buf, packet_len(0x02CC), &sd->bl, target); break; - case 3: - case 4: + case INSTANCE_WND_INFO_PROGRESS_TIME: + case INSTANCE_WND_INFO_IDLE_TIME: // S 0x2cd .61B .L .L - WBUFW(buf,0) = 0x02CD; - memcpy(WBUFP(buf,2),instance->list[instance_id].name,61); - if( type == 3 ) { - WBUFL(buf,63) = instance->list[instance_id].progress_timeout; - WBUFL(buf,67) = 0; + WBUFW(buf, 0) = 0x02CD; + memcpy(WBUFP(buf, 2), instance->list[instance_id].name, 61); + if (type == INSTANCE_WND_INFO_PROGRESS_TIME) { + WBUFL(buf, 63) = instance->list[instance_id].progress_timeout; + WBUFL(buf, 67) = 0; } else { - WBUFL(buf,63) = 0; - WBUFL(buf,67) = instance->list[instance_id].idle_timeout; + WBUFL(buf, 63) = 0; + WBUFL(buf, 67) = instance->list[instance_id].idle_timeout; } - clif->send(buf,packet_len(0x02CD),&sd->bl,target); + clif->send(buf, packet_len(0x02CD), &sd->bl, target); break; - case 5: + case INSTANCE_WND_INFO_DESTROY: // S 0x2ce .L // 0 = Notification (EnterLimitDate update?) // 1 = The Memorial Dungeon expired; it has been destroyed // 2 = The Memorial Dungeon's entry time limit expired; it has been destroyed // 3 = The Memorial Dungeon has been removed. // 4 = Create failure (removes the instance window) - WBUFW(buf,0) = 0x02CE; - WBUFL(buf,2) = flag; + WBUFW(buf, 0) = 0x02CE; + WBUFL(buf, 2) = flag; //WBUFL(buf,6) = EnterLimitDate; - clif->send(buf,packet_len(0x02CE),&sd->bl,target); + clif->send(buf, packet_len(0x02CE), &sd->bl, target); break; } return 0; diff --git a/src/map/clif.h b/src/map/clif.h index bfccc13e07f..2efc6056afd 100644 --- a/src/map/clif.h +++ b/src/map/clif.h @@ -856,6 +856,33 @@ enum bossmap_info_type { BOSS_INFO_DEAD, // Boss is dead }; +/** + * Instance information window update types. + */ +enum instance_window_info_type { + /** + * Informs about instance creation + */ + INSTANCE_WND_INFO_CREATE = 1, + /** + * Informs an update in instance queue position + */ + INSTANCE_WND_INFO_QUEUE_POS = 2, + /** + * Informs the instance is in progress (has players on it) and when it will timeout + */ + INSTANCE_WND_INFO_PROGRESS_TIME = 3, + /** + * Informs the instance is idle (no players on it) and when it will timeout + */ + INSTANCE_WND_INFO_IDLE_TIME = 4, + /** + * Informs the instance was destroyed. + * @see instance_destroy_reason for reason flag values. + */ + INSTANCE_WND_INFO_DESTROY = 5, +}; + /** * Clif.c Interface **/ @@ -1309,7 +1336,7 @@ struct clif_interface { void (*sendbgemblem_area) (struct map_session_data *sd); void (*sendbgemblem_single) (int fd, struct map_session_data *sd); /* instance-related */ - int (*instance) (int instance_id, int type, int flag); + int (*instance) (int instance_id, enum instance_window_info_type type, int flag); void (*instance_join) (int fd, int instance_id); void (*instance_leave) (int fd); /* pet-related */ diff --git a/src/map/instance.c b/src/map/instance.c index e712d7c46e4..54e66a10818 100644 --- a/src/map/instance.c +++ b/src/map/instance.c @@ -170,7 +170,7 @@ static int instance_create(int owner_id, const char *name, enum instance_owner_t } } - clif->instance(i, 1, 0); // Start instancing window + clif->instance(i, INSTANCE_WND_INFO_CREATE, 0); // Start instancing window return i; } @@ -567,20 +567,18 @@ static void instance_destroy(int instance_id) struct party_data *p = NULL; struct guild *g = NULL; short *iptr = NULL; - int type; unsigned int now = (unsigned int)time(NULL); if( !instance->valid(instance_id) ) return; // nothing to do + enum instance_destroy_reason type = INSTANCE_DESTROY_OTHER; if( instance->list[instance_id].progress_timeout && instance->list[instance_id].progress_timeout <= now ) - type = 1; + type = INSTANCE_DESTROY_PROG_TIMEOUT; else if( instance->list[instance_id].idle_timeout && instance->list[instance_id].idle_timeout <= now ) - type = 2; - else - type = 3; + type = INSTANCE_DESTROY_IDLE_TIMEOUT; - clif->instance(instance_id, 5, type); // Report users this instance has been destroyed + clif->instance(instance_id, INSTANCE_WND_INFO_DESTROY, type); // Report users this instance has been destroyed switch ( instance->list[instance_id].owner_type ) { case IOT_NONE: @@ -655,24 +653,28 @@ static void instance_destroy(int instance_id) *--------------------------------------*/ static void instance_check_idle(int instance_id) { - bool idle = true; - unsigned int now = (unsigned int)time(NULL); - - if( !instance->valid(instance_id) || instance->list[instance_id].idle_timeoutval == 0 ) + if (!instance->valid(instance_id) || instance->list[instance_id].idle_timeoutval == 0) return; - if( instance->list[instance_id].users ) + bool idle = true; + if (instance->list[instance_id].users) idle = false; - if( instance->list[instance_id].idle_timer != INVALID_TIMER && !idle ) { + if (instance->list[instance_id].idle_timer != INVALID_TIMER && !idle) { timer->delete(instance->list[instance_id].idle_timer, instance->destroy_timer); instance->list[instance_id].idle_timer = INVALID_TIMER; instance->list[instance_id].idle_timeout = 0; - clif->instance(instance_id, 3, 0); // Notify instance users normal instance expiration - } else if( instance->list[instance_id].idle_timer == INVALID_TIMER && idle ) { + + // Notify instance users normal instance expiration + clif->instance(instance_id, INSTANCE_WND_INFO_PROGRESS_TIME, 0); + } else if (instance->list[instance_id].idle_timer == INVALID_TIMER && idle) { + unsigned int now = (unsigned int) time(NULL); + int64 destroy_tick = timer->gettick() + instance->list[instance_id].idle_timeoutval * 1000; instance->list[instance_id].idle_timeout = now + instance->list[instance_id].idle_timeoutval; - instance->list[instance_id].idle_timer = timer->add( timer->gettick() + instance->list[instance_id].idle_timeoutval * 1000, instance->destroy_timer, instance_id, 0); - clif->instance(instance_id, 4, 0); // Notify instance users it will be destroyed of no user join it again in "X" time + instance->list[instance_id].idle_timer = timer->add(destroy_tick, instance->destroy_timer, instance_id, 0); + + // Notify instance users it will be destroyed if no user join it again in "X" time + clif->instance(instance_id, INSTANCE_WND_INFO_IDLE_TIME, 0); } } diff --git a/src/map/instance.h b/src/map/instance.h index e2529300d14..2d774b13222 100644 --- a/src/map/instance.h +++ b/src/map/instance.h @@ -46,6 +46,25 @@ enum instance_owner_type { IOT_MAX, }; +/** + * Reason for instance being destroyed. + * Note: These numbers are client-dependent. + */ +enum instance_destroy_reason { + /** + * Time to progress in the instance has expired. + */ + INSTANCE_DESTROY_PROG_TIMEOUT = 1, + /** + * The instance has been empty for too long. + */ + INSTANCE_DESTROY_IDLE_TIMEOUT = 2, + /** + * Other reason + */ + INSTANCE_DESTROY_OTHER = 3, +}; + struct instance_data { unsigned short id; char name[INSTANCE_NAME_LENGTH]; ///< Instance Name - required for clif functions. From 328c757cf9e107f309af0731f6041df77cee19e7 Mon Sep 17 00:00:00 2001 From: "Guilherme G. Menaldo" Date: Tue, 10 May 2022 21:25:38 -0300 Subject: [PATCH 2/6] prevent giving bad instance info to players - Properly clean up freed instance data to prevent link by partial info. Instance state should always be checked, but in case it is forgotten, it would lead to saying a player has an instance that may not even exist - Mark instance being destroyed to stop sending info to player after timeout. This would happen when an instance expire and players are kicked out of it, as leaving the instance would be counted as making it idle - Adds check for active instance on player login. Otherwise player may receive instance window about an instance that doesn't exists anymore --- src/map/clif.c | 3 ++- src/map/instance.c | 12 ++++++++---- src/map/instance.h | 11 ++++++++++- 3 files changed, 20 insertions(+), 6 deletions(-) diff --git a/src/map/clif.c b/src/map/clif.c index c8385b234bd..3b7c7d333df 100644 --- a/src/map/clif.c +++ b/src/map/clif.c @@ -11426,7 +11426,8 @@ static void clif_parse_LoadEndAck(int fd, struct map_session_data *sd) if (first_time) { int i; - ARR_FIND(0, instance->instances, i, instance->list[i].owner_type == IOT_CHAR && instance->list[i].owner_id == sd->status.account_id); + ARR_FIND(0, instance->instances, i, instance_is_active(instance->list[i]) + && instance->list[i].owner_type == IOT_CHAR && instance->list[i].owner_id == sd->status.account_id); if (i < instance->instances) { sd->instances = 1; diff --git a/src/map/instance.c b/src/map/instance.c index 54e66a10818..36657250d2b 100644 --- a/src/map/instance.c +++ b/src/map/instance.c @@ -58,7 +58,7 @@ static bool instance_is_valid(int instance_id) return false; } - if( instance->list[instance_id].state == INSTANCE_FREE ) {// uninitialized/freed instance slot + if (!instance_is_active(instance->list[instance_id])) { // uninitialized/freed/being freed instance slot return false; } @@ -617,6 +617,9 @@ static void instance_destroy(int instance_id) iptr[i] = -1; } + // mark it as being destroyed so server doesn't try to give players more information about it + instance->list[instance_id].state = INSTANCE_DESTROYING; + if (instance->list[instance_id].map) { int last = 0; while (instance->list[instance_id].num_map && last != instance->list[instance_id].map[0]) { @@ -641,11 +644,12 @@ static void instance_destroy(int instance_id) if( instance->list[instance_id].map ) aFree(instance->list[instance_id].map); + HPM->data_store_destroy(&instance->list[instance_id].hdata); + + // Clean up remains of the old instance and mark it as available for a new one + memset(&instance->list[instance_id], 0x0, sizeof(instance->list[0])); instance->list[instance_id].map = NULL; instance->list[instance_id].state = INSTANCE_FREE; - instance->list[instance_id].num_map = 0; - - HPM->data_store_destroy(&instance->list[instance_id].hdata); } /*-------------------------------------- diff --git a/src/map/instance.h b/src/map/instance.h index 2d774b13222..79972691365 100644 --- a/src/map/instance.h +++ b/src/map/instance.h @@ -31,10 +31,19 @@ struct map_session_data; #define INSTANCE_NAME_LENGTH (60+1) +/** + * true if instance is in an active/playable state. + * In other words, if a player can interact with it. + * + * @param inst instance_data to be checked + */ +#define instance_is_active(inst) ((inst).state == INSTANCE_IDLE || (inst).state == INSTANCE_BUSY) + typedef enum instance_state { INSTANCE_FREE, INSTANCE_IDLE, - INSTANCE_BUSY + INSTANCE_BUSY, + INSTANCE_DESTROYING, } instance_state; enum instance_owner_type { From 8af9b9e83c18508a9e88433da8e3468f15f677dc Mon Sep 17 00:00:00 2001 From: "Guilherme G. Menaldo" Date: Mon, 9 May 2022 23:22:24 -0300 Subject: [PATCH 3/6] fix instance reload initializing a bad instance Destroyed instances (invalid) are kept in memory for reuse, but they should not be reinitialized during a reload. Just keep its memory space as is. --- src/map/instance.c | 3 +++ 1 file changed, 3 insertions(+) diff --git a/src/map/instance.c b/src/map/instance.c index 36657250d2b..8be21c1df35 100644 --- a/src/map/instance.c +++ b/src/map/instance.c @@ -828,6 +828,9 @@ static void do_reload_instance(void) int i, k; for(i = 0; i < instance->instances; i++) { + if (!instance_is_valid(i)) + continue; // don't try to restart an invalid instance + for(k = 0; k < instance->list[i].num_map; k++) { if( !map->list[map->list[instance->list[i].map[k]].instance_src_map].flag.src4instance ) break; From 28a31ab56caa6dd3fec126c00b13ec03bc09cdda Mon Sep 17 00:00:00 2001 From: "Guilherme G. Menaldo" Date: Mon, 9 May 2022 23:58:25 -0300 Subject: [PATCH 4/6] mark freed instance maps as invalid Otherwise, hercules will try to free it again during shutdown and fail --- src/map/instance.c | 3 ++- src/map/map.h | 5 +++++ 2 files changed, 7 insertions(+), 1 deletion(-) diff --git a/src/map/instance.c b/src/map/instance.c index 8be21c1df35..552ed21fd2e 100644 --- a/src/map/instance.c +++ b/src/map/instance.c @@ -190,7 +190,7 @@ static int instance_create(int owner_id, const char *name, enum instance_owner_t static int instance_add_map(const char *name, int instance_id, bool usebasename, const char *map_name) { int16 m = map->mapname2mapid(name); - int i, im = -1; + int i, im = MAPID_NONE; size_t num_cell, size, j; nullpo_retr(-1, name); @@ -543,6 +543,7 @@ static void instance_del_map(int16 m) map->removemapdb(&map->list[m]); memset(&map->list[m], 0x00, sizeof(map->list[0])); + map->list[m].m = MAPID_NONE; // Marks this map as unallocated so server doesn't try to clean it up later on. map->list[m].name[0] = 0; map->list[m].instance_id = -1; map->list[m].mob_delete_timer = INVALID_TIMER; diff --git a/src/map/map.h b/src/map/map.h index c7cfa092bc6..e2abe209e2e 100644 --- a/src/map/map.h +++ b/src/map/map.h @@ -948,6 +948,11 @@ struct map_drop_list { int drop_per; }; +/** + * Map ID (m) for "none" or unallocated map. + */ +#define MAPID_NONE -1 + struct map_data { char name[MAP_NAME_LENGTH]; uint16 index; // The map index used by the mapindex* functions. From 4006a50dd7242bc09b7d5136900317b59379fd70 Mon Sep 17 00:00:00 2001 From: "Guilherme G. Menaldo" Date: Tue, 10 May 2022 23:01:29 -0300 Subject: [PATCH 5/6] fix instance destruction reason code in some cases, alive and idle timeout could get mixed and the wrong message would be sent to the user. --- src/map/instance.c | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/src/map/instance.c b/src/map/instance.c index 552ed21fd2e..1e14409404c 100644 --- a/src/map/instance.c +++ b/src/map/instance.c @@ -574,9 +574,10 @@ static void instance_destroy(int instance_id) return; // nothing to do enum instance_destroy_reason type = INSTANCE_DESTROY_OTHER; - if( instance->list[instance_id].progress_timeout && instance->list[instance_id].progress_timeout <= now ) + bool idle = (instance->list[instance_id].users == 0); + if (!idle && instance->list[instance_id].progress_timeout && instance->list[instance_id].progress_timeout <= now) type = INSTANCE_DESTROY_PROG_TIMEOUT; - else if( instance->list[instance_id].idle_timeout && instance->list[instance_id].idle_timeout <= now ) + else if (idle && instance->list[instance_id].idle_timeout && instance->list[instance_id].idle_timeout <= now) type = INSTANCE_DESTROY_IDLE_TIMEOUT; clif->instance(instance_id, INSTANCE_WND_INFO_DESTROY, type); // Report users this instance has been destroyed From 37bca6a51055d70d772155ca55524366d71a8302 Mon Sep 17 00:00:00 2001 From: "Guilherme G. Menaldo" Date: Tue, 10 May 2022 23:03:07 -0300 Subject: [PATCH 6/6] Regenerate HPM hooks --- src/plugins/HPMHooking/HPMHooking.Defs.inc | 4 ++-- src/plugins/HPMHooking/HPMHooking_map.Hooks.inc | 6 +++--- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/src/plugins/HPMHooking/HPMHooking.Defs.inc b/src/plugins/HPMHooking/HPMHooking.Defs.inc index 7cc2003e3cd..5e906cf9f3a 100644 --- a/src/plugins/HPMHooking/HPMHooking.Defs.inc +++ b/src/plugins/HPMHooking/HPMHooking.Defs.inc @@ -1860,8 +1860,8 @@ typedef void (*HPMHOOK_pre_clif_sendbgemblem_area) (struct map_session_data **sd typedef void (*HPMHOOK_post_clif_sendbgemblem_area) (struct map_session_data *sd); typedef void (*HPMHOOK_pre_clif_sendbgemblem_single) (int *fd, struct map_session_data **sd); typedef void (*HPMHOOK_post_clif_sendbgemblem_single) (int fd, struct map_session_data *sd); -typedef int (*HPMHOOK_pre_clif_instance) (int *instance_id, int *type, int *flag); -typedef int (*HPMHOOK_post_clif_instance) (int retVal___, int instance_id, int type, int flag); +typedef int (*HPMHOOK_pre_clif_instance) (int *instance_id, enum instance_window_info_type *type, int *flag); +typedef int (*HPMHOOK_post_clif_instance) (int retVal___, int instance_id, enum instance_window_info_type type, int flag); typedef void (*HPMHOOK_pre_clif_instance_join) (int *fd, int *instance_id); typedef void (*HPMHOOK_post_clif_instance_join) (int fd, int instance_id); typedef void (*HPMHOOK_pre_clif_instance_leave) (int *fd); diff --git a/src/plugins/HPMHooking/HPMHooking_map.Hooks.inc b/src/plugins/HPMHooking/HPMHooking_map.Hooks.inc index fc4b55809e5..105d11899be 100644 --- a/src/plugins/HPMHooking/HPMHooking_map.Hooks.inc +++ b/src/plugins/HPMHooking/HPMHooking_map.Hooks.inc @@ -18880,11 +18880,11 @@ void HP_clif_sendbgemblem_single(int fd, struct map_session_data *sd) { } return; } -int HP_clif_instance(int instance_id, int type, int flag) { +int HP_clif_instance(int instance_id, enum instance_window_info_type type, int flag) { int hIndex = 0; int retVal___ = 0; if (HPMHooks.count.HP_clif_instance_pre > 0) { - int (*preHookFunc) (int *instance_id, int *type, int *flag); + int (*preHookFunc) (int *instance_id, enum instance_window_info_type *type, int *flag); *HPMforce_return = false; for (hIndex = 0; hIndex < HPMHooks.count.HP_clif_instance_pre; hIndex++) { preHookFunc = HPMHooks.list.HP_clif_instance_pre[hIndex].func; @@ -18899,7 +18899,7 @@ int HP_clif_instance(int instance_id, int type, int flag) { retVal___ = HPMHooks.source.clif.instance(instance_id, type, flag); } if (HPMHooks.count.HP_clif_instance_post > 0) { - int (*postHookFunc) (int retVal___, int instance_id, int type, int flag); + int (*postHookFunc) (int retVal___, int instance_id, enum instance_window_info_type type, int flag); for (hIndex = 0; hIndex < HPMHooks.count.HP_clif_instance_post; hIndex++) { postHookFunc = HPMHooks.list.HP_clif_instance_post[hIndex].func; retVal___ = postHookFunc(retVal___, instance_id, type, flag);