
Converted last_point to mapname Converted save_point to mapname Converted memo to mapname Converted start point to mapname Removed default map Converted party member to mapname Converted maplists to mapname Removed mapindex loading Fixed instance loading with multiple map-servers Fixed castle loading with multiple map-servers Fixed battleground loading with multiple map-servers Fixed warping between map-servers Thanks to @aleos89 for his help!
1300 lines
34 KiB
C++
1300 lines
34 KiB
C++
// Copyright (c) rAthena Dev Teams - Licensed under GNU GPL
|
|
// For more information, see LICENCE in the main folder
|
|
|
|
#include "instance.hpp"
|
|
|
|
#include <stdlib.h>
|
|
#include <math.h>
|
|
|
|
#include "../common/cbasetypes.hpp"
|
|
#include "../common/db.hpp"
|
|
#include "../common/ers.hpp" // ers_destroy
|
|
#include "../common/malloc.hpp"
|
|
#include "../common/nullpo.hpp"
|
|
#include "../common/showmsg.hpp"
|
|
#include "../common/socket.hpp"
|
|
#include "../common/strlib.hpp"
|
|
#include "../common/timer.hpp"
|
|
#include "../common/utilities.hpp"
|
|
|
|
#include "clan.hpp"
|
|
#include "clif.hpp"
|
|
#include "guild.hpp"
|
|
#include "map.hpp"
|
|
#include "npc.hpp"
|
|
#include "party.hpp"
|
|
#include "pc.hpp"
|
|
|
|
using namespace rathena;
|
|
|
|
/// Instance Idle Queue data
|
|
struct s_instance_wait {
|
|
std::deque<int> id;
|
|
int timer;
|
|
} instance_wait;
|
|
|
|
#define INSTANCE_INTERVAL 60000 // Interval used to check when an instance is to be destroyed (ms)
|
|
|
|
int16 instance_start = 0; // Instance MapID start
|
|
int instance_count = 1; // Total created instances
|
|
|
|
std::unordered_map<int, std::shared_ptr<s_instance_data>> instances;
|
|
|
|
const std::string InstanceDatabase::getDefaultLocation() {
|
|
return std::string(db_path) + "/instance_db.yml";
|
|
}
|
|
|
|
/**
|
|
* Reads and parses an entry from the instance_db.
|
|
* @param node: YAML node containing the entry.
|
|
* @return count of successfully parsed rows
|
|
*/
|
|
uint64 InstanceDatabase::parseBodyNode(const ryml::NodeRef& node) {
|
|
int32 instance_id = 0;
|
|
|
|
if (!this->asInt32(node, "Id", instance_id))
|
|
return 0;
|
|
|
|
if (instance_id <= 0) {
|
|
this->invalidWarning(node, "Instance Id is invalid. Valid range 1~%d, skipping.\n", INT_MAX);
|
|
return 0;
|
|
}
|
|
|
|
std::shared_ptr<s_instance_db> instance = this->find(instance_id);
|
|
bool exists = instance != nullptr;
|
|
|
|
if (!exists) {
|
|
if (!this->nodesExist(node, { "Name", "Enter" }))
|
|
return 0;
|
|
|
|
instance = std::make_shared<s_instance_db>();
|
|
instance->id = instance_id;
|
|
}
|
|
|
|
if (this->nodeExists(node, "Name")) {
|
|
std::string name;
|
|
|
|
if (!this->asString(node, "Name", name))
|
|
return 0;
|
|
|
|
for (const auto &instance : instance_db) {
|
|
if (instance.second->name.compare(name) == 0) {
|
|
this->invalidWarning(node["Name"], "Instance name %s already exists, skipping.\n", name.c_str());
|
|
return 0;
|
|
}
|
|
}
|
|
|
|
instance->name = name;
|
|
}
|
|
|
|
if (this->nodeExists(node, "TimeLimit")) {
|
|
int64 limit;
|
|
|
|
if (!this->asInt64(node, "TimeLimit", limit))
|
|
return 0;
|
|
|
|
if (limit == 0) // Infinite duration
|
|
limit = INT64_MAX;
|
|
|
|
instance->limit = limit;
|
|
} else {
|
|
if (!exists)
|
|
instance->limit = 3600;
|
|
}
|
|
|
|
if (this->nodeExists(node, "IdleTimeOut")) {
|
|
int64 idle;
|
|
|
|
if (!this->asInt64(node, "IdleTimeOut", idle))
|
|
return 0;
|
|
|
|
if (idle == 0) // Infinite duration
|
|
idle = INT64_MAX;
|
|
|
|
instance->timeout = idle;
|
|
} else {
|
|
if (!exists)
|
|
instance->timeout = 300;
|
|
}
|
|
|
|
if (this->nodeExists(node, "NoNpc")) {
|
|
bool nonpc;
|
|
|
|
if (!this->asBool(node, "NoNpc", nonpc))
|
|
return 0;
|
|
|
|
instance->nonpc = nonpc;
|
|
}
|
|
else {
|
|
if (!exists)
|
|
instance->nonpc = false;
|
|
}
|
|
|
|
if (this->nodeExists(node, "NoMapFlag")) {
|
|
bool nomapflag;
|
|
|
|
if (!this->asBool(node, "NoMapFlag", nomapflag))
|
|
return 0;
|
|
|
|
instance->nomapflag = nomapflag;
|
|
}
|
|
else {
|
|
if (!exists)
|
|
instance->nomapflag = false;
|
|
}
|
|
|
|
if (this->nodeExists(node, "Destroyable")) {
|
|
bool destroy;
|
|
|
|
if (!this->asBool(node, "Destroyable", destroy))
|
|
return 0;
|
|
|
|
instance->destroyable = destroy;
|
|
} else {
|
|
if (!exists)
|
|
instance->destroyable = true;
|
|
}
|
|
|
|
if (this->nodeExists(node, "Enter")) {
|
|
const auto& enterNode = node["Enter"];
|
|
|
|
if (!this->nodesExist(enterNode, { "Map", "X", "Y" }))
|
|
return 0;
|
|
|
|
if (this->nodeExists(enterNode, "Map")) {
|
|
std::string map;
|
|
|
|
if (!this->asString(enterNode, "Map", map))
|
|
return 0;
|
|
|
|
uint16 mapindex = mapindex_name2idx( map.c_str(), nullptr );
|
|
|
|
if( mapindex == 0 ){
|
|
this->invalidWarning(enterNode["Map"], "Map %s is not a valid map, skipping.\n", map.c_str());
|
|
return 0;
|
|
}
|
|
|
|
int16 mapid = map_mapindex2mapid( mapindex );
|
|
|
|
if( mapid < 0 ){
|
|
// Ignore silently, the map is on another mapserver
|
|
return 0;
|
|
}
|
|
|
|
instance->enter.map = mapid;
|
|
}
|
|
|
|
if (this->nodeExists(enterNode, "X")) {
|
|
uint16 x;
|
|
|
|
if (!this->asUInt16(enterNode, "X", x))
|
|
return 0;
|
|
|
|
if (x == 0) {
|
|
this->invalidWarning(node["X"], "X has to be greater than zero.\n");
|
|
return 0;
|
|
}
|
|
|
|
map_data *md = map_getmapdata(instance->enter.map);
|
|
|
|
if (x >= md->xs) {
|
|
this->invalidWarning(node["X"], "X has to be smaller than %hu.\n", md->xs);
|
|
return 0;
|
|
}
|
|
|
|
instance->enter.x = x;
|
|
}
|
|
|
|
if (this->nodeExists(enterNode, "Y")) {
|
|
uint16 y;
|
|
|
|
if (!this->asUInt16(enterNode, "Y", y))
|
|
return 0;
|
|
|
|
if (y == 0) {
|
|
this->invalidWarning(node["Y"], "Y has to be greater than zero.\n");
|
|
return 0;
|
|
}
|
|
|
|
map_data *md = map_getmapdata(instance->enter.map);
|
|
|
|
if (y >= md->ys) {
|
|
this->invalidWarning(node["Y"], "Y has to be smaller than %hu.\n", md->ys);
|
|
return 0;
|
|
}
|
|
|
|
instance->enter.y = y;
|
|
}
|
|
}
|
|
|
|
if (this->nodeExists(node, "AdditionalMaps")) {
|
|
const auto& mapNode = node["AdditionalMaps"];
|
|
|
|
for (const auto& mapIt : mapNode) {
|
|
std::string map;
|
|
c4::from_chars(mapIt.key(), &map);
|
|
|
|
int16 m = map_mapname2mapid(map.c_str());
|
|
|
|
if (m == instance->enter.map) {
|
|
this->invalidWarning(mapNode, "Additional Map %s is already listed as the EnterMap.\n", map.c_str());
|
|
continue;
|
|
}
|
|
|
|
if (m == -1) {
|
|
this->invalidWarning(mapNode, "Additional Map %s is not a valid map, skipping.\n", map.c_str());
|
|
return 0;
|
|
}
|
|
|
|
bool active;
|
|
|
|
if (!this->asBool(mapNode, map, active))
|
|
return 0;
|
|
|
|
if (active)
|
|
instance->maplist.push_back(m);
|
|
else
|
|
util::vector_erase_if_exists(instance->maplist, m);
|
|
}
|
|
}
|
|
|
|
if (!exists)
|
|
this->put(instance_id, instance);
|
|
|
|
return 1;
|
|
}
|
|
|
|
InstanceDatabase instance_db;
|
|
|
|
/**
|
|
* Searches for an instance name in the database
|
|
* @param instance_name: Instance to search for
|
|
* @return shared_ptr of instance or nullptr on failure
|
|
*/
|
|
std::shared_ptr<s_instance_db> instance_search_db_name(const char *instance_name)
|
|
{
|
|
for (const auto &it : instance_db) {
|
|
if (!strcmp(it.second->name.c_str(), instance_name))
|
|
return it.second;
|
|
}
|
|
|
|
return nullptr;
|
|
}
|
|
|
|
/**
|
|
* Search for a sd of an Instance
|
|
* @param instance_id: Instance ID
|
|
* @param sd: Pointer to player data
|
|
* @param target: Target display type
|
|
*/
|
|
void instance_getsd(int instance_id, map_session_data *&sd, enum send_target *target) {
|
|
std::shared_ptr<s_instance_data> idata = util::umap_find(instances, instance_id);
|
|
|
|
if (!idata) {
|
|
sd = nullptr;
|
|
return;
|
|
}
|
|
|
|
switch(idata->mode) {
|
|
case IM_NONE:
|
|
sd = nullptr;
|
|
(*target) = SELF;
|
|
break;
|
|
case IM_GUILD:
|
|
sd = guild_getavailablesd(guild_search(idata->owner_id));
|
|
(*target) = GUILD;
|
|
break;
|
|
case IM_PARTY:
|
|
sd = party_getavailablesd(party_search(idata->owner_id));
|
|
(*target) = PARTY;
|
|
break;
|
|
case IM_CHAR:
|
|
sd = map_charid2sd(idata->owner_id);
|
|
(*target) = SELF;
|
|
break;
|
|
case IM_CLAN:
|
|
sd = clan_getavailablesd(clan_search(idata->owner_id));
|
|
(*target) = CLAN;
|
|
}
|
|
return;
|
|
}
|
|
|
|
/**
|
|
* Deletes an instance timer (Destroys instance)
|
|
*/
|
|
static TIMER_FUNC(instance_delete_timer){
|
|
instance_destroy(id);
|
|
|
|
return 0;
|
|
}
|
|
|
|
/**
|
|
* Create subscription timer
|
|
*/
|
|
static TIMER_FUNC(instance_subscription_timer){
|
|
int instance_id = instance_wait.id[0];
|
|
|
|
if (instance_id <= 0 || instance_wait.id.empty())
|
|
return 0;
|
|
|
|
std::shared_ptr<s_instance_data> idata = util::umap_find(instances, instance_id);
|
|
|
|
if (!idata)
|
|
return 0;
|
|
|
|
map_session_data *sd;
|
|
struct party_data *pd;
|
|
struct guild *gd;
|
|
struct clan *cd;
|
|
e_instance_mode mode = idata->mode;
|
|
int ret = instance_addmap(instance_id); // Check that maps have been added
|
|
|
|
switch(mode) {
|
|
case IM_NONE:
|
|
break;
|
|
case IM_CHAR:
|
|
if (ret == 0 && (sd = map_charid2sd(idata->owner_id))) // If no maps are created, tell player to wait
|
|
clif_instance_changewait(instance_id, 0xffff);
|
|
break;
|
|
case IM_PARTY:
|
|
if (ret == 0 && (pd = party_search(idata->owner_id))) // If no maps are created, tell party to wait
|
|
clif_instance_changewait(instance_id, 0xffff);
|
|
break;
|
|
case IM_GUILD:
|
|
if (ret == 0 && (gd = guild_search(idata->owner_id))) // If no maps are created, tell guild to wait
|
|
clif_instance_changewait(instance_id, 0xffff);
|
|
break;
|
|
case IM_CLAN:
|
|
if (ret == 0 && (cd = clan_search(idata->owner_id))) // If no maps are created, tell clan to wait
|
|
clif_instance_changewait(instance_id, 0xffff);
|
|
break;
|
|
default:
|
|
return 0;
|
|
}
|
|
|
|
instance_wait.id.pop_front();
|
|
|
|
for (int i = 0; i < instance_wait.id.size(); i++) {
|
|
if (idata->state == INSTANCE_IDLE && ((mode == IM_CHAR && sd) || (mode == IM_GUILD && gd) || (mode == IM_PARTY && pd) || (mode == IM_CLAN && cd)))
|
|
clif_instance_changewait(instance_id, i + 1);
|
|
}
|
|
|
|
if (!instance_wait.id.empty())
|
|
instance_wait.timer = add_timer(gettick() + INSTANCE_INTERVAL, instance_subscription_timer, 0, 0);
|
|
else
|
|
instance_wait.timer = INVALID_TIMER;
|
|
|
|
return 0;
|
|
}
|
|
|
|
/**
|
|
* Adds timer back to members entering instance
|
|
* @param idata: Instance data
|
|
* @param instance_id: Instance ID to notify
|
|
* @return True on success or false on failure
|
|
*/
|
|
bool instance_startkeeptimer(std::shared_ptr<s_instance_data> idata, int instance_id)
|
|
{
|
|
// No timer
|
|
if (!idata || idata->keep_timer != INVALID_TIMER)
|
|
return false;
|
|
|
|
std::shared_ptr<s_instance_db> db = instance_db.find(idata->id);
|
|
|
|
if (!db)
|
|
return false;
|
|
|
|
// Add timer
|
|
idata->keep_limit = time(nullptr) + db->limit;
|
|
idata->keep_timer = add_timer(gettick() + db->limit * 1000, instance_delete_timer, instance_id, 0);
|
|
|
|
switch(idata->mode) {
|
|
case IM_NONE:
|
|
break;
|
|
case IM_CHAR:
|
|
if (map_charid2sd(idata->owner_id)) // Notify player of the added instance timer
|
|
clif_instance_status(instance_id, static_cast<uint32>(idata->keep_limit), static_cast<uint32>(idata->idle_limit));
|
|
break;
|
|
case IM_PARTY:
|
|
if (party_search(idata->owner_id)) // Notify party of the added instance timer
|
|
clif_instance_status(instance_id, static_cast<uint32>(idata->keep_limit), static_cast<uint32>(idata->idle_limit));
|
|
break;
|
|
case IM_GUILD:
|
|
if (guild_search(idata->owner_id)) // Notify guild of the added instance timer
|
|
clif_instance_status(instance_id, static_cast<uint32>(idata->keep_limit), static_cast<uint32>(idata->idle_limit));
|
|
break;
|
|
case IM_CLAN:
|
|
if (clan_search(idata->owner_id)) // Notify clan of the added instance timer
|
|
clif_instance_status(instance_id, static_cast<uint32>(idata->keep_limit), static_cast<uint32>(idata->idle_limit));
|
|
break;
|
|
default:
|
|
return false;
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
/**
|
|
* Creates an idle timer for an instance, default is 5 minutes
|
|
* @param idata: Instance data
|
|
* @param instance_id: Instance ID to notify
|
|
* @param True on success or false on failure
|
|
*/
|
|
bool instance_startidletimer(std::shared_ptr<s_instance_data> idata, int instance_id)
|
|
{
|
|
// No current timer
|
|
if (!idata || idata->idle_timer != INVALID_TIMER)
|
|
return false;
|
|
|
|
std::shared_ptr<s_instance_db> db = instance_db.find(idata->id);
|
|
|
|
if (!db)
|
|
return false;
|
|
|
|
// Add the timer
|
|
idata->idle_limit = time(nullptr) + db->timeout;
|
|
idata->idle_timer = add_timer(gettick() + db->timeout * 1000, instance_delete_timer, instance_id, 0);
|
|
|
|
switch(idata->mode) {
|
|
case IM_NONE:
|
|
break;
|
|
case IM_CHAR:
|
|
if (map_charid2sd(idata->owner_id)) // Notify player of added instance timer
|
|
clif_instance_status(instance_id, static_cast<uint32>(idata->keep_limit), static_cast<uint32>(idata->idle_limit));
|
|
break;
|
|
case IM_PARTY:
|
|
if (party_search(idata->owner_id)) // Notify party of added instance timer
|
|
clif_instance_status(instance_id, static_cast<uint32>(idata->keep_limit), static_cast<uint32>(idata->idle_limit));
|
|
break;
|
|
case IM_GUILD:
|
|
if (guild_search(idata->owner_id)) // Notify guild of added instance timer
|
|
clif_instance_status(instance_id, static_cast<uint32>(idata->keep_limit), static_cast<uint32>(idata->idle_limit));
|
|
break;
|
|
case IM_CLAN:
|
|
if (clan_search(idata->owner_id)) // Notify clan of added instance timer
|
|
clif_instance_status(instance_id, static_cast<uint32>(idata->keep_limit), static_cast<uint32>(idata->idle_limit));
|
|
break;
|
|
default:
|
|
return false;
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
/**
|
|
* Remove the idle timer from an instance
|
|
* @param idata: Instace data
|
|
* @param instance_id: Instance ID to notify
|
|
* @return True on success or false on failure
|
|
*/
|
|
bool instance_stopidletimer(std::shared_ptr<s_instance_data> idata, int instance_id)
|
|
{
|
|
// No timer
|
|
if (!idata || idata->idle_timer == INVALID_TIMER)
|
|
return false;
|
|
|
|
// Delete the timer - Party has returned or instance is destroyed
|
|
idata->idle_limit = 0;
|
|
delete_timer(idata->idle_timer, instance_delete_timer);
|
|
idata->idle_timer = INVALID_TIMER;
|
|
|
|
switch(idata->mode) {
|
|
case IM_NONE:
|
|
break;
|
|
case IM_CHAR:
|
|
if (map_charid2sd(idata->owner_id)) // Notify the player
|
|
clif_instance_changestatus(instance_id, IN_NOTIFY, static_cast<uint32>(idata->idle_limit));
|
|
break;
|
|
case IM_PARTY:
|
|
if (party_search(idata->owner_id)) // Notify the party
|
|
clif_instance_changestatus(instance_id, IN_NOTIFY, static_cast<uint32>(idata->idle_limit));
|
|
break;
|
|
case IM_GUILD:
|
|
if (guild_search(idata->owner_id)) // Notify the guild
|
|
clif_instance_changestatus(instance_id, IN_NOTIFY, static_cast<uint32>(idata->idle_limit));
|
|
break;
|
|
case IM_CLAN:
|
|
if (clan_search(idata->owner_id)) // Notify the clan
|
|
clif_instance_changestatus(instance_id, IN_NOTIFY, static_cast<uint32>(idata->idle_limit));
|
|
break;
|
|
default:
|
|
return false;
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
/**
|
|
* 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, nd = (struct npc_data *)bl);
|
|
|
|
return npc_instanceinit(nd);
|
|
}
|
|
|
|
/**
|
|
* Run the OnInstanceDestroy events for duplicated NPCs
|
|
*/
|
|
static int instance_npcdestroy(struct block_list *bl, va_list ap)
|
|
{
|
|
struct npc_data* nd;
|
|
|
|
nullpo_retr(0, bl);
|
|
nullpo_retr(0, nd = (struct npc_data *)bl);
|
|
|
|
return npc_instancedestroy(nd);
|
|
}
|
|
|
|
/**
|
|
* Update instance with new NPC
|
|
*/
|
|
static int instance_addnpc_sub(struct block_list *bl, va_list ap)
|
|
{
|
|
struct npc_data* nd;
|
|
|
|
nullpo_retr(0, bl);
|
|
nullpo_retr(0, nd = (struct npc_data *)bl);
|
|
|
|
return npc_duplicate4instance(nd, va_arg(ap, int));
|
|
}
|
|
|
|
/**
|
|
* Add an NPC to an instance
|
|
* @param idata: Instance data
|
|
*/
|
|
void instance_addnpc(std::shared_ptr<s_instance_data> idata)
|
|
{
|
|
// First add the NPCs
|
|
for (const auto &it : idata->map) {
|
|
struct map_data *mapdata = map_getmapdata(it.m);
|
|
|
|
map_foreachinallarea(instance_addnpc_sub, it.src_m, 0, 0, mapdata->xs, mapdata->ys, BL_NPC, it.m);
|
|
}
|
|
|
|
// Now run their OnInstanceInit
|
|
for (const auto &it : idata->map) {
|
|
struct map_data *mapdata = map_getmapdata(it.m);
|
|
|
|
map_foreachinallarea(instance_npcinit, it.m, 0, 0, mapdata->xs, mapdata->ys, BL_NPC, it.m);
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Create an instance
|
|
* @param owner_id: Owner block ID
|
|
* @param name: Instance name
|
|
* @param mode: Instance mode
|
|
* @return -4 = no free instances | -3 = already exists | -2 = character/party/guild not found | -1 = invalid type | On success return instance_id
|
|
*/
|
|
int instance_create(int owner_id, const char *name, e_instance_mode mode) {
|
|
std::shared_ptr<s_instance_db> db = instance_search_db_name(name);
|
|
|
|
if (!db) {
|
|
ShowError("instance_create: Unknown instance %s creation was attempted.\n", name);
|
|
return -1;
|
|
}
|
|
|
|
map_session_data *sd = nullptr;
|
|
struct party_data *pd;
|
|
struct guild *gd;
|
|
struct clan* cd;
|
|
|
|
switch(mode) {
|
|
case IM_NONE:
|
|
break;
|
|
case IM_CHAR:
|
|
if (!(sd = map_charid2sd(owner_id))) {
|
|
ShowError("instance_create: Character %d not found for instance '%s'.\n", owner_id, name);
|
|
return -2;
|
|
}
|
|
if (sd->instance_id > 0)
|
|
return -3; // Player already instancing
|
|
break;
|
|
case IM_PARTY:
|
|
if (!(pd = party_search(owner_id))) {
|
|
ShowError("instance_create: Party %d not found for instance '%s'.\n", owner_id, name);
|
|
return -2;
|
|
}
|
|
if (pd->instance_id > 0)
|
|
return -3; // Party already instancing
|
|
break;
|
|
case IM_GUILD:
|
|
if (!(gd = guild_search(owner_id))) {
|
|
ShowError("instance_create: Guild %d not found for instance '%s'.\n", owner_id, name);
|
|
return -2;
|
|
}
|
|
if (gd->instance_id > 0)
|
|
return -3; // Guild already instancing
|
|
break;
|
|
case IM_CLAN:
|
|
if (!(cd = clan_search(owner_id))) {
|
|
ShowError("instance_create: Clan %d not found for instance '%s'.\n", owner_id, name);
|
|
return -2;
|
|
}
|
|
if (cd->instance_id > 0)
|
|
return -3; // Clan already instancing
|
|
break;
|
|
default:
|
|
ShowError("instance_create: Unknown mode %u for owner_id %d and name %s.\n", mode, owner_id, name);
|
|
return -2;
|
|
}
|
|
|
|
if (instance_count <= 0)
|
|
return -4;
|
|
|
|
int instance_id = instance_count++;
|
|
std::shared_ptr<s_instance_data> entry = std::make_shared<s_instance_data>();
|
|
|
|
entry->id = db->id;
|
|
entry->owner_id = owner_id;
|
|
entry->mode = mode;
|
|
entry->regs.vars = i64db_alloc(DB_OPT_RELEASE_DATA);
|
|
entry->regs.arrays = nullptr;
|
|
instances.insert({ instance_id, entry });
|
|
|
|
switch(mode) {
|
|
case IM_CHAR:
|
|
sd->instance_id = instance_id;
|
|
break;
|
|
case IM_PARTY:
|
|
pd->instance_id = instance_id;
|
|
int32 i;
|
|
ARR_FIND(0, MAX_PARTY, i, pd->party.member[i].leader);
|
|
|
|
if (i < MAX_PARTY)
|
|
sd = map_charid2sd(pd->party.member[i].char_id);
|
|
break;
|
|
case IM_GUILD:
|
|
gd->instance_id = instance_id;
|
|
sd = map_charid2sd(gd->member[0].char_id);
|
|
break;
|
|
case IM_CLAN:
|
|
cd->instance_id = instance_id;
|
|
break;
|
|
}
|
|
|
|
if (sd != nullptr)
|
|
sd->instance_mode = mode;
|
|
|
|
instance_wait.id.push_back(instance_id);
|
|
clif_instance_create(instance_id, instance_wait.id.size());
|
|
instance_subscription_timer(0,0,0,0);
|
|
|
|
ShowInfo("[Instance] Created: %s (%d)\n", name, instance_id);
|
|
|
|
// Start the instance timer on instance creation
|
|
instance_startkeeptimer(entry, instance_id);
|
|
|
|
return instance_id;
|
|
}
|
|
|
|
/**
|
|
* Adds maps to the instance
|
|
* @param instance_id: Instance ID to add map to
|
|
* @return 0 on failure or map count on success
|
|
*/
|
|
int instance_addmap(int instance_id) {
|
|
if (instance_id <= 0)
|
|
return 0;
|
|
|
|
std::shared_ptr<s_instance_data> idata = util::umap_find(instances, instance_id);
|
|
|
|
// If the instance isn't idle, we can't do anything
|
|
if (idata->state != INSTANCE_IDLE)
|
|
return 0;
|
|
|
|
std::shared_ptr<s_instance_db> db = instance_db.find(idata->id);
|
|
|
|
if (!db)
|
|
return 0;
|
|
|
|
// Set to busy, update timers
|
|
idata->state = INSTANCE_BUSY;
|
|
idata->idle_limit = time(nullptr) + db->timeout;
|
|
idata->idle_timer = add_timer(gettick() + db->timeout * 1000, instance_delete_timer, instance_id, 0);
|
|
idata->nomapflag = db->nomapflag;
|
|
idata->nonpc = db->nonpc;
|
|
|
|
int16 m;
|
|
|
|
// Add initial map
|
|
if ((m = map_addinstancemap(db->enter.map, instance_id, db->nomapflag)) < 0) {
|
|
ShowError("instance_addmap: Failed to create initial map for instance '%s' (%d).\n", db->name.c_str(), instance_id);
|
|
return 0;
|
|
}
|
|
|
|
struct s_instance_map entry;
|
|
|
|
entry.m = m;
|
|
entry.src_m = db->enter.map;
|
|
idata->map.push_back(entry);
|
|
|
|
// Add extra maps (if any)
|
|
for (const auto &it : db->maplist) {
|
|
if ((m = map_addinstancemap(it, instance_id, db->nomapflag)) < 0) { // An error occured adding a map
|
|
ShowError("instance_addmap: No maps added to instance '%s' (%d).\n", db->name.c_str(), instance_id);
|
|
return 0;
|
|
} else {
|
|
entry.m = m;
|
|
entry.src_m = it;
|
|
idata->map.push_back(entry);
|
|
}
|
|
}
|
|
|
|
// Create NPCs on all maps
|
|
if(!db->nonpc)
|
|
instance_addnpc(idata);
|
|
|
|
switch(idata->mode) {
|
|
case IM_NONE:
|
|
break;
|
|
case IM_CHAR:
|
|
if (map_charid2sd(idata->owner_id)) // Inform player of the created instance
|
|
clif_instance_status(instance_id, static_cast<uint32>(idata->keep_limit), static_cast<uint32>(idata->idle_limit));
|
|
break;
|
|
case IM_PARTY:
|
|
if (party_search(idata->owner_id)) // Inform party members of the created instance
|
|
clif_instance_status(instance_id, static_cast<uint32>(idata->keep_limit), static_cast<uint32>(idata->idle_limit));
|
|
break;
|
|
case IM_GUILD:
|
|
if (guild_search(idata->owner_id)) // Inform guild members of the created instance
|
|
clif_instance_status(instance_id, static_cast<uint32>(idata->keep_limit), static_cast<uint32>(idata->idle_limit));
|
|
break;
|
|
case IM_CLAN:
|
|
if (clan_search(idata->owner_id)) // Inform clan members of the created instance
|
|
clif_instance_status(instance_id, static_cast<uint32>(idata->keep_limit), static_cast<uint32>(idata->idle_limit));
|
|
break;
|
|
default:
|
|
return 0;
|
|
}
|
|
|
|
return idata->map.size();
|
|
}
|
|
|
|
/**
|
|
* Fills outname with the name of the instance map name
|
|
* @param map_id: Mapid to use
|
|
* @param instance_id: Instance id
|
|
* @param outname: Pointer to allocated memory that will be filled in
|
|
*/
|
|
void instance_generate_mapname(int map_id, int instance_id, char outname[MAP_NAME_LENGTH]) {
|
|
|
|
#if MAX_MAP_PER_SERVER > 9999
|
|
#error This algorithm is only safe for up to 9999 maps, change at your own risk.
|
|
#endif
|
|
// Safe up to 9999 maps per map-server
|
|
static const int prefix_length = 4;
|
|
// Full map name length - prefix length - seperator character - zero termination
|
|
static const int suffix_length = MAP_NAME_LENGTH - prefix_length - 1 - 1;
|
|
static const int prefix_limit = static_cast<int>(pow(10, prefix_length));
|
|
static const int suffix_limit = static_cast<int>(pow(10, suffix_length));
|
|
safesnprintf(outname, MAP_NAME_LENGTH, "%0*u#%0*u", prefix_length, map_id % prefix_limit, suffix_length, instance_id % suffix_limit);
|
|
}
|
|
|
|
/**
|
|
* Returns an instance map ID
|
|
* @param m: Source map ID
|
|
* @param instance_id: Instance to search
|
|
* @return Map ID in this instance or -1 on failure
|
|
*/
|
|
int16 instance_mapid(int16 m, int instance_id)
|
|
{
|
|
const char *name = map_mapid2mapname(m);
|
|
|
|
if (name == nullptr) {
|
|
ShowError("instance_mapid: Map ID %d does not exist.\n", m);
|
|
return -1;
|
|
}
|
|
|
|
std::shared_ptr<s_instance_data> idata = util::umap_find(instances, instance_id);
|
|
|
|
if(!idata || idata->state != INSTANCE_BUSY)
|
|
return -1;
|
|
|
|
for (const auto &it : idata->map) {
|
|
if (it.src_m == m) {
|
|
char alt_name[MAP_NAME_LENGTH];
|
|
instance_generate_mapname(m, instance_id, alt_name);
|
|
return map_mapname2mapid(alt_name);
|
|
}
|
|
}
|
|
|
|
return m;
|
|
}
|
|
|
|
/**
|
|
* Removes an instance, all its maps, and NPCs invoked by the client button.
|
|
* @param sd: Player data
|
|
*/
|
|
void instance_destroy_command(map_session_data *sd) {
|
|
nullpo_retv(sd);
|
|
|
|
std::shared_ptr<s_instance_data> idata;
|
|
int instance_id = 0;
|
|
|
|
if (sd->instance_mode == IM_CHAR && sd->instance_id > 0) {
|
|
idata = util::umap_find(instances, sd->instance_id);
|
|
|
|
if (idata == nullptr)
|
|
return;
|
|
|
|
instance_id = sd->instance_id;
|
|
} else if (sd->instance_mode == IM_PARTY && sd->status.party_id > 0) {
|
|
party_data *pd = party_search(sd->status.party_id);
|
|
|
|
if (pd == nullptr)
|
|
return;
|
|
|
|
idata = util::umap_find(instances, pd->instance_id);
|
|
|
|
if (idata == nullptr)
|
|
return;
|
|
|
|
int32 i;
|
|
|
|
ARR_FIND(0, MAX_PARTY, i, pd->data[i].sd == sd && pd->party.member[i].leader);
|
|
|
|
if (i == MAX_PARTY) // Player is not party leader
|
|
return;
|
|
|
|
instance_id = pd->instance_id;
|
|
} else if (sd->instance_mode == IM_GUILD && sd->guild != nullptr && sd->guild->instance_id > 0) {
|
|
guild *gd = guild_search(sd->status.guild_id);
|
|
|
|
if (gd == nullptr)
|
|
return;
|
|
|
|
idata = util::umap_find(instances, gd->instance_id);
|
|
|
|
if (idata == nullptr)
|
|
return;
|
|
|
|
if (strcmp(sd->status.name, gd->master) != 0) // Player is not guild master
|
|
return;
|
|
|
|
instance_id = gd->instance_id;
|
|
}
|
|
|
|
if (instance_id == 0) // Checks above failed
|
|
return;
|
|
|
|
if (!instance_db.find(idata->id)->destroyable) // Instance is flagged as non-destroyable
|
|
return;
|
|
|
|
instance_destroy(instance_id);
|
|
|
|
// Check for any other active instances and display their info
|
|
if (sd->instance_id > 0)
|
|
instance_reqinfo(sd, sd->instance_id);
|
|
if (sd->status.party_id > 0) {
|
|
party_data *pd = party_search(sd->status.party_id);
|
|
|
|
if (pd == nullptr)
|
|
return;
|
|
|
|
if (pd->instance_id > 0)
|
|
instance_reqinfo(sd, pd->instance_id);
|
|
}
|
|
if (sd->guild != nullptr && sd->guild->instance_id > 0) {
|
|
guild *gd = guild_search(sd->status.guild_id);
|
|
|
|
if (gd == nullptr)
|
|
return;
|
|
|
|
instance_reqinfo(sd, gd->instance_id);
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Removes an instance, all its maps, and NPCs.
|
|
* @param instance_id: Instance to remove
|
|
* @return True on sucess or false on failure
|
|
*/
|
|
bool instance_destroy(int instance_id)
|
|
{
|
|
std::shared_ptr<s_instance_data> idata = util::umap_find(instances, instance_id);
|
|
|
|
if (!idata)
|
|
return false;
|
|
|
|
map_session_data *sd;
|
|
struct party_data *pd;
|
|
struct guild *gd;
|
|
struct clan *cd;
|
|
e_instance_mode mode = idata->mode;
|
|
e_instance_notify type = IN_NOTIFY;
|
|
|
|
switch(mode) {
|
|
case IM_NONE:
|
|
break;
|
|
case IM_CHAR:
|
|
sd = map_charid2sd(idata->owner_id);
|
|
break;
|
|
case IM_PARTY:
|
|
pd = party_search(idata->owner_id);
|
|
break;
|
|
case IM_GUILD:
|
|
gd = guild_search(idata->owner_id);
|
|
break;
|
|
case IM_CLAN:
|
|
cd = clan_search(idata->owner_id);
|
|
break;
|
|
}
|
|
|
|
if(idata->state == INSTANCE_IDLE) {
|
|
for (auto instance_it = instance_wait.id.begin(); instance_it != instance_wait.id.end(); ++instance_it) {
|
|
if (*instance_it == instance_id) {
|
|
instance_wait.id.erase(instance_it);
|
|
|
|
for (int i = 0; i < instance_wait.id.size(); i++) {
|
|
if (util::umap_find(instances, instance_wait.id[i])->state == INSTANCE_IDLE)
|
|
if ((mode == IM_CHAR && sd) || (mode == IM_PARTY && pd) || (mode == IM_GUILD && gd) || (mode == IM_CLAN && cd))
|
|
clif_instance_changewait(instance_id, i + 1);
|
|
}
|
|
|
|
if (!instance_wait.id.empty())
|
|
instance_wait.timer = add_timer(gettick() + INSTANCE_INTERVAL, instance_subscription_timer, 0, 0);
|
|
else
|
|
instance_wait.timer = INVALID_TIMER;
|
|
break;
|
|
}
|
|
}
|
|
} else {
|
|
int64 now = time(nullptr);
|
|
|
|
if(idata->keep_limit && idata->keep_limit <= now)
|
|
type = IN_DESTROY_LIVE_TIMEOUT;
|
|
else if(idata->idle_limit && idata->idle_limit <= now)
|
|
type = IN_DESTROY_ENTER_TIMEOUT;
|
|
else
|
|
type = IN_DESTROY_USER_REQUEST;
|
|
|
|
// Run OnInstanceDestroy on all NPCs in the instance
|
|
for (const auto &it : idata->map) {
|
|
struct map_data *mapdata = map_getmapdata(it.m);
|
|
|
|
map_foreachinallarea(instance_npcdestroy, it.m, 0, 0, mapdata->xs, mapdata->ys, BL_NPC, it.m);
|
|
map_delinstancemap(it.m);
|
|
}
|
|
}
|
|
|
|
if(idata->keep_timer != INVALID_TIMER) {
|
|
delete_timer(idata->keep_timer, instance_delete_timer);
|
|
idata->keep_timer = INVALID_TIMER;
|
|
}
|
|
if(idata->idle_timer != INVALID_TIMER) {
|
|
delete_timer(idata->idle_timer, instance_delete_timer);
|
|
idata->idle_timer = INVALID_TIMER;
|
|
}
|
|
|
|
if (mode == IM_CHAR && sd)
|
|
sd->instance_id = 0;
|
|
else if (mode == IM_PARTY && pd)
|
|
pd->instance_id = 0;
|
|
else if (mode == IM_GUILD && gd)
|
|
gd->instance_id = 0;
|
|
else if (mode == IM_CLAN && cd)
|
|
cd->instance_id = 0;
|
|
|
|
if (mode != IM_NONE) {
|
|
if(type != IN_NOTIFY)
|
|
clif_instance_changestatus(instance_id, type, 0);
|
|
else
|
|
clif_instance_changewait(instance_id, 0xffff);
|
|
}
|
|
|
|
if( idata->regs.vars ) {
|
|
db_destroy(idata->regs.vars);
|
|
idata->regs.vars = NULL;
|
|
}
|
|
|
|
if( idata->regs.arrays )
|
|
idata->regs.arrays->destroy(idata->regs.arrays, script_free_array_db);
|
|
|
|
ShowInfo("[Instance] Destroyed: %s (%d)\n", instance_db.find(idata->id)->name.c_str(), instance_id);
|
|
|
|
instances.erase(instance_id);
|
|
|
|
return true;
|
|
}
|
|
|
|
/**
|
|
* Warp a user into an instance
|
|
* @param sd: Player to warp
|
|
* @param instance_id: Instance to warp to
|
|
* @param name: Map name
|
|
* @param x: X coordinate
|
|
* @param y: Y coordinate
|
|
* @return e_instance_enter value
|
|
*/
|
|
e_instance_enter instance_enter(map_session_data *sd, int instance_id, const char *name, short x, short y)
|
|
{
|
|
nullpo_retr(IE_OTHER, sd);
|
|
|
|
std::shared_ptr<s_instance_db> db = instance_search_db_name(name);
|
|
|
|
if (!db) {
|
|
ShowError("instance_enter: Unknown instance \"%s\".\n", name);
|
|
return IE_OTHER;
|
|
}
|
|
|
|
// If one of the two coordinates was not given or is below zero, we use the entry point from the database
|
|
if (x < 0 || y < 0) {
|
|
x = db->enter.x;
|
|
y = db->enter.y;
|
|
}
|
|
|
|
std::shared_ptr<s_instance_data> idata = nullptr;
|
|
struct party_data *pd;
|
|
struct guild *gd;
|
|
struct clan *cd;
|
|
e_instance_mode mode;
|
|
|
|
if (instance_id <= 0) // Default party checks will be used
|
|
mode = IM_PARTY;
|
|
else {
|
|
if (!(idata = util::umap_find(instances, instance_id)))
|
|
return IE_NOINSTANCE;
|
|
mode = idata->mode;
|
|
}
|
|
|
|
switch(mode) {
|
|
case IM_NONE:
|
|
break;
|
|
case IM_CHAR:
|
|
if (sd->instance_id == 0) // Player must have an instance
|
|
return IE_NOINSTANCE;
|
|
if (idata->owner_id != sd->status.char_id)
|
|
return IE_OTHER;
|
|
break;
|
|
case IM_PARTY:
|
|
if (sd->status.party_id == 0) // Character must be in instance party
|
|
return IE_NOMEMBER;
|
|
if (!(pd = party_search(sd->status.party_id)))
|
|
return IE_NOMEMBER;
|
|
if (pd->instance_id == 0 || idata == nullptr) // Party must have an instance
|
|
return IE_NOINSTANCE;
|
|
if (idata->owner_id != pd->party.party_id)
|
|
return IE_OTHER;
|
|
break;
|
|
case IM_GUILD:
|
|
if (sd->status.guild_id == 0) // Character must be in instance guild
|
|
return IE_NOMEMBER;
|
|
if (!(gd = guild_search(sd->status.guild_id)))
|
|
return IE_NOMEMBER;
|
|
if (gd->instance_id == 0) // Guild must have an instance
|
|
return IE_NOINSTANCE;
|
|
if (idata->owner_id != gd->guild_id)
|
|
return IE_OTHER;
|
|
break;
|
|
case IM_CLAN:
|
|
if (sd->status.clan_id == 0) // Character must be in instance clan
|
|
return IE_NOMEMBER;
|
|
if (!(cd = clan_search(sd->status.clan_id)))
|
|
return IE_NOMEMBER;
|
|
if (cd->instance_id == 0) // Clan must have an instance
|
|
return IE_NOINSTANCE;
|
|
if (idata->owner_id != cd->id)
|
|
return IE_OTHER;
|
|
break;
|
|
}
|
|
|
|
if (idata->state != INSTANCE_BUSY)
|
|
return IE_OTHER;
|
|
if (idata->id != db->id)
|
|
return IE_OTHER;
|
|
|
|
int16 m;
|
|
|
|
// Does the instance match?
|
|
if ((m = instance_mapid(db->enter.map, instance_id)) < 0)
|
|
return IE_OTHER;
|
|
|
|
if (pc_setpos(sd, map_id2index(m), x, y, CLR_OUTSIGHT))
|
|
return IE_OTHER;
|
|
|
|
return IE_OK;
|
|
}
|
|
|
|
/**
|
|
* Request some info about the instance
|
|
* @param sd: Player to display info to
|
|
* @param instance_id: Instance to request
|
|
* @return True on success or false on failure
|
|
*/
|
|
bool instance_reqinfo(map_session_data *sd, int instance_id)
|
|
{
|
|
nullpo_retr(false, sd);
|
|
|
|
std::shared_ptr<s_instance_data> idata = util::umap_find(instances, instance_id);
|
|
|
|
if (!idata || !instance_db.find(idata->id))
|
|
return false;
|
|
|
|
// Say it's created if instance is not busy
|
|
if(idata->state == INSTANCE_IDLE) {
|
|
for (int i = 0; i < instance_wait.id.size(); i++) {
|
|
if (instance_wait.id[i] == instance_id) {
|
|
clif_instance_create(instance_id, i + 1);
|
|
sd->instance_mode = idata->mode;
|
|
break;
|
|
}
|
|
}
|
|
} else if (idata->state == INSTANCE_BUSY) { // Give info on the instance if busy
|
|
int map_instance_id = map_getmapdata(sd->bl.m)->instance_id;
|
|
if (map_instance_id == 0 || map_instance_id == instance_id) {
|
|
clif_instance_status(instance_id, static_cast<uint32>(idata->keep_limit), static_cast<uint32>(idata->idle_limit));
|
|
sd->instance_mode = idata->mode;
|
|
}
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
/**
|
|
* Add players to the instance (for timers) -- Unused?
|
|
* @param instance_id: Instance to add
|
|
* @return True on success or false on failure
|
|
*/
|
|
bool instance_addusers(int instance_id)
|
|
{
|
|
std::shared_ptr<s_instance_data> idata = util::umap_find(instances, instance_id);
|
|
|
|
if(!idata || idata->state != INSTANCE_BUSY)
|
|
return false;
|
|
|
|
// Stop the idle timer if we had one
|
|
instance_stopidletimer(idata, instance_id);
|
|
|
|
// Start the instance keep timer
|
|
instance_startkeeptimer(idata, instance_id);
|
|
|
|
return true;
|
|
}
|
|
|
|
/**
|
|
* Delete players from the instance (for timers)
|
|
* @param instance_id: Instance to remove
|
|
* @return True on success or false on failure
|
|
*/
|
|
bool instance_delusers(int instance_id)
|
|
{
|
|
std::shared_ptr<s_instance_data> idata = util::umap_find(instances, instance_id);
|
|
|
|
if(!idata || idata->state != INSTANCE_BUSY)
|
|
return false;
|
|
|
|
int users = 0;
|
|
|
|
// If no one is in the instance, start the idle timer
|
|
for (const auto &it : idata->map)
|
|
users += max(map_getmapdata(it.m)->users,0);
|
|
|
|
// We check the actual map.users before being updated, hence the 1
|
|
// The instance should be empty if users are now <= 1
|
|
if(users <= 1)
|
|
instance_startidletimer(idata, instance_id);
|
|
|
|
return true;
|
|
}
|
|
|
|
/**
|
|
* Reloads the instance in runtime (reloadscript)
|
|
*/
|
|
void do_reload_instance(void)
|
|
{
|
|
for (const auto &it : instances) {
|
|
std::shared_ptr<s_instance_data> idata = it.second;
|
|
|
|
if (!idata || idata->map.empty())
|
|
continue;
|
|
else {
|
|
// First we load the NPCs again
|
|
if(!idata->nonpc)
|
|
instance_addnpc(idata);
|
|
|
|
// Create new keep timer
|
|
std::shared_ptr<s_instance_db> db = instance_db.find(idata->id);
|
|
|
|
if (db)
|
|
idata->keep_limit = time(nullptr) + db->limit;
|
|
}
|
|
}
|
|
|
|
// Reset player to instance beginning
|
|
struct s_mapiterator *iter = mapit_getallusers();
|
|
map_session_data *sd;
|
|
|
|
for (sd = (TBL_PC *)mapit_first(iter); mapit_exists(iter); sd = (TBL_PC *)mapit_next(iter)) {
|
|
struct map_data *mapdata = map_getmapdata(sd->bl.m);
|
|
|
|
if (sd && mapdata->instance_id > 0) {
|
|
struct party_data *pd;
|
|
struct guild *gd;
|
|
struct clan *cd;
|
|
int instance_id;
|
|
std::shared_ptr<s_instance_data> idata = util::umap_find(instances, map[sd->bl.m].instance_id);
|
|
std::shared_ptr<s_instance_db> db = instance_db.find(idata->id);
|
|
|
|
switch (idata->mode) {
|
|
case IM_NONE:
|
|
continue;
|
|
case IM_CHAR:
|
|
if (sd->instance_id != mapdata->instance_id) // Someone who is not instance owner is on instance map
|
|
continue;
|
|
instance_id = sd->instance_id;
|
|
break;
|
|
case IM_PARTY:
|
|
if ((!(pd = party_search(sd->status.party_id)) || pd->instance_id != mapdata->instance_id)) // Someone not in party is on instance map
|
|
continue;
|
|
instance_id = pd->instance_id;
|
|
break;
|
|
case IM_GUILD:
|
|
if (!(gd = guild_search(sd->status.guild_id)) || gd->instance_id != mapdata->instance_id) // Someone not in guild is on instance map
|
|
continue;
|
|
instance_id = gd->instance_id;
|
|
break;
|
|
case IM_CLAN:
|
|
if (!(cd = clan_search(sd->status.clan_id)) || cd->instance_id != mapdata->instance_id) // Someone not in clan is on instance map
|
|
continue;
|
|
instance_id = cd->instance_id;
|
|
break;
|
|
default:
|
|
ShowError("do_reload_instance: Unexpected instance mode for instance %s (id=%d, mode=%u).\n", (db) ? db->name.c_str() : "Unknown", mapdata->instance_id, (uint8)idata->mode);
|
|
continue;
|
|
}
|
|
if (db && instance_enter(sd, instance_id, db->name.c_str(), -1, -1) == IE_OK) { // All good
|
|
clif_displaymessage(sd->fd, msg_txt(sd, 515)); // Instance has been reloaded
|
|
instance_reqinfo(sd, 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.c_str());
|
|
}
|
|
}
|
|
mapit_free(iter);
|
|
}
|
|
|
|
/**
|
|
* Initializes the instance database
|
|
*/
|
|
void do_init_instance(void) {
|
|
instance_start = map_num;
|
|
instance_db.load();
|
|
instance_wait.timer = INVALID_TIMER;
|
|
|
|
add_timer_func_list(instance_delete_timer,"instance_delete_timer");
|
|
add_timer_func_list(instance_subscription_timer,"instance_subscription_timer");
|
|
}
|
|
|
|
/**
|
|
* Finalizes the instances and instance database
|
|
*/
|
|
void do_final_instance(void) {
|
|
for (const auto &it : instances)
|
|
instance_destroy(it.first);
|
|
}
|