// Copyright (c) Athena Dev Teams - Licensed under GNU GPL // For more information, see LICENCE in the main folder #include "../common/cbasetypes.h" #include "../common/socket.h" #include "../common/timer.h" #include "../common/nullpo.h" #include "../common/showmsg.h" #include "../common/strlib.h" #include "../common/db.h" #include "clif.h" #include "instance.h" #include "map.h" #include "npc.h" #include "party.h" #include "pc.h" #include #define MAX_INSTANCE_DB 15 // Max number of instance types #define INSTANCE_INTERVAL 60000 // Interval used to check when an instance is to be destroyed (ms) #define INSTANCE_LIMIT 300000 // Idle timer before instance is destroyed (ms) : 5 Minute Default int instance_start = 0; // To keep the last index + 1 of normal map inserted in the map[ARRAY] struct instance_data instance_data[MAX_INSTANCE_DATA]; static struct instance_db{ short type; char name[61]; int limit; struct { char mapname[MAP_NAME_LENGTH_EXT]; short x, y; } enter; char mapname[MAX_MAP_PER_INSTANCE][MAP_NAME_LENGTH_EXT]; } instance_db[MAX_INSTANCE_DB]; static struct { int id[MAX_INSTANCE_DATA]; int count; int timer; } instance_wait; /*========================================== * Searches for an instance ID in the database *------------------------------------------*/ static struct instance_db *instance_searchtype_db(short instance_type) { int i; for(i=0; i < MAX_INSTANCE_DB; i++) { if(instance_db[i].type == instance_type) return &instance_db[i]; } return NULL; } /*========================================== * Searches for an instance name in the database *------------------------------------------*/ static struct instance_db *instance_searchname_db(const char *instance_name) { int i; for(i=0; i < MAX_INSTANCE_DB; i++) { if(strcmp(instance_db[i].name, instance_name) == 0) return &instance_db[i]; } return NULL; } /*========================================== * Deletes an instance timer (Destroys instance) *------------------------------------------*/ static int instance_delete_timer(int tid, unsigned int tick, int id, intptr_t data) { instance_destroy(id); return 0; } /*========================================== * Create subscription timer for party *------------------------------------------*/ static int instance_subscription_timer(int tid, unsigned int tick, int id, intptr_t data) { int i, ret; int instance_id = instance_wait.id[0]; struct party_data *p; if(instance_wait.count == 0 || instance_id <= 0) return 0; // Check that maps have been added ret = instance_addmap(instance_id); // If no maps are created, tell party to wait if(ret == 0 && ( p = party_search( instance_data[instance_id].party_id ) ) != NULL) clif_instance_changewait( party_getavailablesd( p ), 0xffff, 1 ); instance_wait.count--; memmove(&instance_wait.id[0],&instance_wait.id[1],sizeof(instance_wait.id[0])*instance_wait.count); memset(&instance_wait.id[instance_wait.count], 0, sizeof(instance_wait.id[0])); for(i = 0; i < instance_wait.count; i++) { if( instance_data[instance_wait.id[i]].state == INSTANCE_IDLE && ( p = party_search( instance_data[instance_wait.id[i]].party_id ) ) != NULL ){ clif_instance_changewait( party_getavailablesd( p ), i + 1, 1 ); } } if(instance_wait.count) instance_wait.timer = add_timer(gettick()+INSTANCE_INTERVAL, instance_subscription_timer, 0, 0); else instance_wait.timer = -1; return 0; } /*========================================== * Adds timer back to party entering instance *------------------------------------------*/ static int instance_startkeeptimer(struct instance_data *im, short instance_id) { struct instance_db *db; struct party_data *p; nullpo_retr(0, im); // No timer if(im->keep_timer != INVALID_TIMER) return 1; if((db = instance_searchtype_db(im->type)) == NULL) return 1; // Add timer im->keep_limit = (unsigned int)time(NULL) + db->limit; im->keep_timer = add_timer(gettick()+db->limit*1000, instance_delete_timer, instance_id, 0); // Notify party of the added instance timer if( ( p = party_search( im->party_id ) ) != NULL ) clif_instance_status( party_getavailablesd( p ), db->name, im->keep_limit, im->idle_limit, 1 ); return 0; } /*========================================== * Creates idle timer * Default before instance destroy is 5 minutes *------------------------------------------*/ static int instance_startidletimer(struct instance_data *im, short instance_id) { struct instance_db *db; struct party_data *p; nullpo_retr(1, im); // No current timer if(im->idle_timer != INVALID_TIMER) return 1; // Add the timer im->idle_limit = (unsigned int)time(NULL) + INSTANCE_LIMIT/1000; im->idle_timer = add_timer(gettick()+INSTANCE_LIMIT, instance_delete_timer, instance_id, 0); // Notify party of added instance timer if( ( p = party_search( im->party_id ) ) != NULL && ( db = instance_searchtype_db( im->type ) ) != NULL ){ clif_instance_status( party_getavailablesd( p ), db->name, im->keep_limit, im->idle_limit, 1 ); } return 0; } /*========================================== * Delete the idle timer *------------------------------------------*/ static int instance_stopidletimer(struct instance_data *im) { struct party_data *p; nullpo_retr(0, im); // No timer if(im->idle_timer == INVALID_TIMER) return 1; // Delete the timer - Party has returned or instance is destroyed im->idle_limit = 0; delete_timer(im->idle_timer, instance_delete_timer); im->idle_timer = INVALID_TIMER; // Notify the party if( ( p = party_search( im->party_id ) ) != NULL ) clif_instance_changestatus( party_getavailablesd( p ), 0, im->idle_limit, 1 ); return 0; } /*========================================== * Run the OnInstanceInit events for duplicated NPCs *------------------------------------------*/ static int instance_npcinit(struct block_list *bl, va_list ap) { struct npc_data* nd; nullpo_retr(0, bl); nullpo_retr(0, ap); nullpo_retr(0, nd = (struct npc_data *)bl); return npc_instanceinit(nd); } /*========================================== * Add an NPC to an instance *------------------------------------------*/ static int instance_addnpc_sub(struct block_list *bl, va_list ap) { struct npc_data* nd; nullpo_retr(0, bl); nullpo_retr(0, ap); nullpo_retr(0, nd = (struct npc_data *)bl); return npc_duplicate4instance(nd, va_arg(ap, int)); } // Separate function used for reloading void instance_addnpc(struct instance_data *im) { int i; // First add the NPCs for(i = 0; i < im->cnt_map; i++) map_foreachinarea(instance_addnpc_sub, im->map[i].src_m, 0, 0, map[im->map[i].src_m].xs, map[im->map[i].src_m].ys, BL_NPC, im->map[i].m); // Now run their OnInstanceInit for(i = 0; i < im->cnt_map; i++) map_foreachinarea(instance_npcinit, im->map[i].m, 0, 0, map[im->map[i].m].xs, map[im->map[i].m].ys, BL_NPC, im->map[i].m); } /*-------------------------------------- * name : instance name * Return value could be * -4 = no free instances | -3 = already exists | -2 = party not found | -1 = invalid type * On success return instance_id *--------------------------------------*/ int instance_create(int party_id, const char *name) { short i; struct instance_db *db = instance_searchname_db(name); struct party_data *p = party_search(party_id); if(db == NULL) return -1; if( p == NULL ) return -2; if( p->instance_id ) return -3; // Party already instancing // Searching a Free Instance // 0 is ignored as this mean "no instance" on maps ARR_FIND(1, MAX_INSTANCE_DATA, i, instance_data[i].state == INSTANCE_FREE); if( i >= MAX_INSTANCE_DATA ) return -4; instance_data[i].type = db->type; instance_data[i].state = INSTANCE_IDLE; instance_data[i].party_id = p->party.party_id; instance_data[i].keep_limit = 0; instance_data[i].keep_timer = INVALID_TIMER; instance_data[i].idle_limit = 0; instance_data[i].idle_timer = INVALID_TIMER; instance_data[i].vars = idb_alloc(DB_OPT_RELEASE_DATA); memset(instance_data[i].map, 0, sizeof(instance_data[i].map)); p->instance_id = i; instance_wait.id[instance_wait.count++] = p->instance_id; clif_instance_create( party_getavailablesd( p ), name, instance_wait.count, 1); instance_subscription_timer(0,0,0,0); ShowInfo("[Instance] Created: %s.\n", name); return i; } /*-------------------------------------- * Adds maps to the instance *--------------------------------------*/ int instance_addmap(short instance_id) { int i, m; int cnt_map = 0; struct instance_data *im; struct instance_db *db; struct party_data *p; if(instance_id <= 0) return 0; im = &instance_data[instance_id]; // If the instance isn't idle, we can't do anything if(im->state != INSTANCE_IDLE) return 0; if((db = instance_searchtype_db(im->type)) == NULL) return 0; // Set to busy, update timers im->state = INSTANCE_BUSY; im->idle_limit = (unsigned int)time(NULL) + INSTANCE_LIMIT/1000; im->idle_timer = add_timer(gettick()+INSTANCE_LIMIT, instance_delete_timer, instance_id, 0); // Add the maps for(i = 0; i < MAX_MAP_PER_INSTANCE; i++) { if(strlen(db->mapname[i]) < 1) continue; else if( (m = map_addinstancemap(db->mapname[i], instance_id)) < 0) { // An error occured adding a map ShowError("instance_addmap: No maps added to instance %d.\n",instance_id); return 0; } else { im->map[cnt_map].m = m; im->map[cnt_map].src_m = map_mapname2mapid(db->mapname[i]); cnt_map++; } } im->cnt_map = cnt_map; // Create NPCs on all maps instance_addnpc(im); // Inform party members of the created instance if( (p = party_search( im->party_id ) ) != NULL ) clif_instance_status( party_getavailablesd( p ), db->name, im->keep_limit, im->idle_limit, 1); return cnt_map; } /*========================================== * Returns an instance map ID using a map name * name : source map * instance_id : where to search * result : mapid of map "name" in this instance *------------------------------------------*/ int instance_mapname2mapid(const char *name, short instance_id) { struct instance_data *im; int m = map_mapname2mapid(name); char iname[MAP_NAME_LENGTH]; int i; if(m < 0) { ShowError("instance_mapname2mapid: map name %s does not exist.\n",name); return m; } strcpy(iname,name); if(instance_id <= 0 || instance_id > MAX_INSTANCE_DATA) return m; im = &instance_data[instance_id]; if(im->state != INSTANCE_BUSY) return m; for(i = 0; i < MAX_MAP_PER_INSTANCE; i++) if(im->map[i].src_m == m) { char alt_name[MAP_NAME_LENGTH]; if((strchr(iname,'@') == NULL) && strlen(iname) > 8) { memmove(iname, iname+(strlen(iname)-9), strlen(iname)); snprintf(alt_name, sizeof(alt_name),"%d#%s", instance_id, iname); } else snprintf(alt_name, sizeof(alt_name),"%.3d%s", instance_id, iname); return map_mapname2mapid(alt_name); } return m; } /*========================================== * Removes a instance, all its maps and npcs. *------------------------------------------*/ int instance_destroy(short instance_id) { struct instance_data *im; struct party_data *p; int i, type = 0; unsigned int now = (unsigned int)time(NULL); if(instance_id <= 0 || instance_id > MAX_INSTANCE_DATA) return 1; im = &instance_data[instance_id]; if(im->state == INSTANCE_FREE) return 1; if(im->state == INSTANCE_IDLE) { for(i = 0; i < instance_wait.count; i++) { if(instance_wait.id[i] == instance_id) { instance_wait.count--; memmove(&instance_wait.id[i],&instance_wait.id[i+1],sizeof(instance_wait.id[0])*(instance_wait.count-i)); memset(&instance_wait.id[instance_wait.count], 0, sizeof(instance_wait.id[0])); for(i = 0; i < instance_wait.count; i++) if(instance_data[instance_wait.id[i]].state == INSTANCE_IDLE) if((p = party_search(instance_data[instance_wait.id[i]].party_id)) != NULL) clif_instance_changewait( party_getavailablesd( p ), i+1, 1); if(instance_wait.count) instance_wait.timer = add_timer(gettick()+INSTANCE_INTERVAL, instance_subscription_timer, 0, 0); else instance_wait.timer = -1; type = 0; break; } } } else { if(im->keep_limit && im->keep_limit <= now) type = 1; else if(im->idle_limit && im->idle_limit <= now) type = 2; else type = 3; for(i = 0; i < MAX_MAP_PER_INSTANCE; i++) map_delinstancemap(im->map[i].m); } if(im->keep_timer != INVALID_TIMER) { delete_timer(im->keep_timer, instance_delete_timer); im->keep_timer = INVALID_TIMER; } if(im->idle_timer != INVALID_TIMER) { delete_timer(im->idle_timer, instance_delete_timer); im->idle_timer = INVALID_TIMER; } if((p = party_search(im->party_id))) { p->instance_id = 0; if(type) clif_instance_changestatus( party_getavailablesd( p ), type, 0, 1 ); else clif_instance_changewait( party_getavailablesd( p ), 0xffff, 1 ); } if( im->vars ) { db_destroy(im->vars); im->vars = NULL; } ShowInfo("[Instance] Destroyed %d.\n", instance_id); memset(&instance_data[instance_id], 0, sizeof(instance_data[instance_id])); return 0; } /*========================================== * Allows a user to enter an instance *------------------------------------------*/ int instance_enter(struct map_session_data *sd, const char *name) { struct instance_db *db = instance_searchname_db(name); if(db == NULL) return 3; return instance_enter_position(sd, name, db->enter.x, db->enter.y); } /*========================================== * Warp a user into instance *------------------------------------------*/ int instance_enter_position(struct map_session_data *sd, const char *name, short x, short y) { struct instance_data *im; struct instance_db *db = instance_searchname_db(name); struct party_data *p; int m; nullpo_retr(-1, sd); // Character must be in instance party if(sd->status.party_id == 0) return 1; if((p = party_search(sd->status.party_id)) == NULL) return 1; // Party must have an instance if(p->instance_id == 0) return 2; if(db == NULL) return 3; im = &instance_data[p->instance_id]; if(im->party_id != p->party.party_id) return 3; if(im->state != INSTANCE_BUSY) return 3; if(im->type != db->type) return 3; // Does the instance match? if((m = instance_mapname2mapid(db->enter.mapname, p->instance_id)) < 0) return 3; if(pc_setpos(sd, map_id2index(m), x, y, 0)) return 3; // If there was an idle timer, let's stop it instance_stopidletimer(im); // Now we start the instance timer instance_startkeeptimer(im, p->instance_id); return 0; } /*========================================== * Request some info about the instance *------------------------------------------*/ int instance_reqinfo(struct map_session_data *sd, short instance_id) { struct instance_data *im; struct instance_db *db; nullpo_retr(1, sd); if(instance_id <= 0 || instance_id > MAX_INSTANCE_DATA) return 1; im = &instance_data[instance_id]; if((db = instance_searchtype_db(im->type)) == NULL) return 1; // Say it's created if instance is not busy if(im->state == INSTANCE_IDLE) { int i; for(i = 0; i < instance_wait.count; i++) { if(instance_wait.id[i] == instance_id) { clif_instance_create(sd, db->name, i+1, 0); break; } } } else if(im->state == INSTANCE_BUSY) // Give info on the instance if busy clif_instance_status(sd, db->name, im->keep_limit, im->idle_limit, 0); return 0; } /*========================================== * Add players to the instance (for timers) *------------------------------------------*/ int instance_addusers(short instance_id) { struct instance_data *im; if(instance_id <= 0 || instance_id > MAX_INSTANCE_DATA) return 1; im = &instance_data[instance_id]; if(im->state != INSTANCE_BUSY) return 1; // Stop the idle timer if we had one instance_stopidletimer(im); // Start the instance keep timer instance_startkeeptimer(im, instance_id); return 0; } /*========================================== * Delete players from the instance (for timers) *------------------------------------------*/ int instance_delusers(short instance_id) { struct instance_data *im; int i, idle = 0; if(instance_id <= 0 || instance_id > MAX_INSTANCE_DATA) return 1; im = &instance_data[instance_id]; if(im->state != INSTANCE_BUSY) return 1; // If no one is in the instance, start the idle timer for(i = 0; im->map[i].m && i < MAX_MAP_PER_INSTANCE; i++) if(map[im->map[i].m].users > 1) // We check this before the actual map.users are updated, hence the 1 idle++; if(!idle) // If idle wasn't added to, we know no one was in the instance instance_startidletimer(im, instance_id); return 0; } /*========================================== * Read the instance_db.txt file *------------------------------------------*/ static bool instance_readdb_sub(char* str[], int columns, int current) { int i, type, k=0; type=atoi(str[0]); instance_db[type].type=type; safestrncpy(instance_db[type].name, str[1], 24); instance_db[type].limit=atoi(str[2]); safestrncpy(instance_db[type].enter.mapname, str[3], MAP_NAME_LENGTH); instance_db[type].enter.x=atoi(str[4]); instance_db[type].enter.y=atoi(str[5]); //Instance maps for(i=6; icnt_map) continue; else { // First we load the NPCs again instance_addnpc(im); // Create new keep timer if((db = instance_searchtype_db(im->type)) != NULL) im->keep_limit = (unsigned int)time(NULL) + db->limit; } } // Reset player to instance beginning iter = mapit_getallusers(); for( sd = (TBL_PC*)mapit_first(iter); mapit_exists(iter); sd = (TBL_PC*)mapit_next(iter) ) if(sd && map[sd->bl.m].instance_id) { struct party_data *p; if(!(p = party_search(sd->status.party_id)) || p->instance_id != map[sd->bl.m].instance_id) // Someone not in party is on instance map continue; im = &instance_data[p->instance_id]; if((db = instance_searchtype_db(im->type)) != NULL && !instance_enter(sd,db->name)) { // All good clif_displaymessage(sd->fd, msg_txt(sd,515)); // Instance has been reloaded instance_reqinfo(sd,p->instance_id); } else // Something went wrong ShowError("do_reload_instance: Error setting character at instance start: character_id=%d instance=%s.\n",sd->status.char_id,db->name); } mapit_free(iter); } void do_init_instance(void) { instance_readdb(); memset(instance_data, 0, sizeof(instance_data)); memset(&instance_wait, 0, sizeof(instance_wait)); instance_wait.timer = -1; add_timer_func_list(instance_delete_timer,"instance_delete_timer"); add_timer_func_list(instance_subscription_timer,"instance_subscription_timer"); } void do_final_instance(void) { int i; for( i = 1; i < MAX_INSTANCE_DATA; i++ ) instance_destroy(i); }