12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970717273747576777879808182838485868788899091929394959697989910010110210310410510610710810911011111211311411511611711811912012112212312412512612712812913013113213313413513613713813914014114214314414514614714814915015115215315415515615715815916016116216316416516616716816917017117217317417517617717817918018118218318418518618718818919019119219319419519619719819920020120220320420520620720820921021121221321421521621721821922022122222322422522622722822923023123223323423523623723823924024124224324424524624724824925025125225325425525625725825926026126226326426526626726826927027127227327427527627727827928028128228328428528628728828929029129229329429529629729829930030130230330430530630730830931031131231331431531631731831932032132232332432532632732832933033133233333433533633733833934034134234334434534634734834935035135235335435535635735835936036136236336436536636736836937037137237337437537637737837938038138238338438538638738838939039139239339439539639739839940040140240340440540640740840941041141241341441541641741841942042142242342442542642742842943043143243343443543643743843944044144244344444544644744844945045145245345445545645745845946046146246346446546646746846947047147247347447547647747847948048148248348448548648748848949049149249349449549649749849950050150250350450550650750850951051151251351451551651751851952052152252352452552652752852953053153253353453553653753853954054154254354454554654754854955055155255355455555655755855956056156256356456556656756856957057157257357457557657757857958058158258358458558658758858959059159259359459559659759859960060160260360460560660760860961061161261361461561661761861962062162262362462562662762862963063163263363463563663763863964064164264364464564664764864965065165265365465565665765865966066166266366466566666766866967067167267367467567667767867968068168268368468568668768868969069169269369469569669769869970070170270370470570670770870971071171271371471571671771871972072172272372472572672772872973073173273373473573673773873974074174274374474574674774874975075175275375475575675775875976076176276376476576676776876977077177277377477577677777877978078178278378478578678778878979079179279379479579679779879980080180280380480580680780880981081181281381481581681781881982082182282382482582682782882983083183283383483583683783883984084184284384484584684784884985085185285385485585685785885986086186286386486586686786886987087187287387487587687787887988088188288388488588688788888989089189289389489589689789889990090190290390490590690790890991091191291391491591691791891992092192292392492592692792892993093193293393493593693793893994094194294394494594694794894995095195295395495595695795895996096196296396496596696796896997097197297397497597697797897998098198298398498598698798898999099199299399499599699799899910001001100210031004100510061007100810091010101110121013101410151016101710181019102010211022102310241025102610271028102910301031103210331034103510361037103810391040104110421043104410451046104710481049105010511052105310541055105610571058105910601061106210631064106510661067106810691070107110721073107410751076107710781079108010811082108310841085108610871088108910901091109210931094109510961097109810991100110111021103110411051106110711081109111011111112111311141115111611171118111911201121112211231124112511261127112811291130113111321133113411351136113711381139114011411142114311441145114611471148114911501151115211531154115511561157115811591160116111621163116411651166116711681169117011711172117311741175117611771178117911801181118211831184118511861187118811891190119111921193119411951196119711981199120012011202120312041205120612071208120912101211121212131214121512161217121812191220122112221223122412251226122712281229123012311232123312341235123612371238123912401241124212431244124512461247124812491250125112521253125412551256125712581259126012611262126312641265126612671268126912701271127212731274127512761277127812791280128112821283128412851286128712881289129012911292 |
- // 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;
- int16 m = map_mapname2mapid(map.c_str());
- if (m == -1) {
- this->invalidWarning(enterNode["Map"], "Map %s is not a valid map, skipping.\n", map.c_str());
- return 0;
- }
- instance->enter.map = m;
- }
- 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);
- }
|