battleground.cpp 42 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948949950951952953954955956957958959960961962963964965966967968969970971972973974975976977978979980981982983984985986987988989990991992993994995996997998999100010011002100310041005100610071008100910101011101210131014101510161017101810191020102110221023102410251026102710281029103010311032103310341035103610371038103910401041104210431044104510461047104810491050105110521053105410551056105710581059106010611062106310641065106610671068106910701071107210731074107510761077107810791080108110821083108410851086108710881089109010911092109310941095109610971098109911001101110211031104110511061107110811091110111111121113111411151116111711181119112011211122112311241125112611271128112911301131113211331134113511361137113811391140114111421143114411451146114711481149115011511152115311541155115611571158115911601161116211631164116511661167116811691170117111721173117411751176117711781179118011811182118311841185118611871188118911901191119211931194119511961197119811991200120112021203120412051206120712081209121012111212121312141215121612171218121912201221122212231224122512261227122812291230123112321233123412351236123712381239124012411242124312441245124612471248124912501251125212531254125512561257125812591260126112621263126412651266126712681269127012711272127312741275127612771278127912801281128212831284128512861287128812891290129112921293129412951296129712981299130013011302130313041305130613071308130913101311131213131314131513161317131813191320132113221323132413251326132713281329133013311332133313341335133613371338133913401341134213431344134513461347134813491350135113521353135413551356135713581359136013611362136313641365136613671368136913701371137213731374137513761377137813791380138113821383138413851386138713881389139013911392139313941395139613971398139914001401140214031404140514061407140814091410141114121413141414151416141714181419142014211422142314241425142614271428142914301431143214331434143514361437143814391440144114421443144414451446144714481449145014511452145314541455145614571458145914601461146214631464146514661467146814691470147114721473147414751476147714781479148014811482148314841485148614871488148914901491149214931494149514961497149814991500150115021503150415051506150715081509151015111512151315141515151615171518151915201521152215231524152515261527152815291530153115321533153415351536153715381539154015411542154315441545154615471548154915501551155215531554155515561557155815591560156115621563156415651566156715681569157015711572157315741575
  1. // Copyright (c) rAthena Dev Teams - Licensed under GNU GPL
  2. // For more information, see LICENCE in the main folder
  3. #include "battleground.hpp"
  4. #include <unordered_map>
  5. #include <yaml-cpp/yaml.h>
  6. #include "../common/cbasetypes.hpp"
  7. #include "../common/malloc.hpp"
  8. #include "../common/nullpo.hpp"
  9. #include "../common/random.hpp"
  10. #include "../common/showmsg.hpp"
  11. #include "../common/strlib.hpp"
  12. #include "../common/timer.hpp"
  13. #include "../common/utilities.hpp"
  14. #include "battle.hpp"
  15. #include "clif.hpp"
  16. #include "guild.hpp"
  17. #include "homunculus.hpp"
  18. #include "mapreg.hpp"
  19. #include "mercenary.hpp"
  20. #include "mob.hpp"
  21. #include "npc.hpp"
  22. #include "party.hpp"
  23. #include "pc.hpp"
  24. #include "pet.hpp"
  25. using namespace rathena;
  26. BattlegroundDatabase battleground_db;
  27. std::unordered_map<int, std::shared_ptr<s_battleground_data>> bg_team_db;
  28. std::vector<std::shared_ptr<s_battleground_queue>> bg_queues;
  29. int bg_queue_count = 1;
  30. const std::string BattlegroundDatabase::getDefaultLocation() {
  31. return std::string(db_path) + "/battleground_db.yml";
  32. }
  33. /**
  34. * Reads and parses an entry from the battleground_db
  35. * @param node: The YAML node containing the entry
  36. * @return count of successfully parsed rows
  37. */
  38. uint64 BattlegroundDatabase::parseBodyNode(const YAML::Node &node) {
  39. uint32 id;
  40. if (!this->asUInt32(node, "Id", id))
  41. return 0;
  42. std::shared_ptr<s_battleground_type> bg = this->find(id);
  43. bool exists = bg != nullptr;
  44. if (!exists) {
  45. if (!this->nodesExist(node, { "Name", "Locations" }))
  46. return 0;
  47. bg = std::make_shared<s_battleground_type>();
  48. bg->id = id;
  49. }
  50. if (this->nodeExists(node, "Name")) {
  51. std::string name;
  52. if (!this->asString(node, "Name", name))
  53. return 0;
  54. name.resize(NAME_LENGTH);
  55. bg->name = name;
  56. }
  57. if (this->nodeExists(node, "MinPlayers")) {
  58. int min;
  59. if (!this->asInt32(node, "MinPlayers", min))
  60. return 0;
  61. if (min < 1) {
  62. this->invalidWarning(node["MinPlayers"], "Minimum players %d cannot be less than 1, capping to 1.\n", min);
  63. min = 1;
  64. }
  65. if (min * 2 > MAX_BG_MEMBERS) {
  66. this->invalidWarning(node["MinPlayers"], "Minimum players %d exceeds MAX_BG_MEMBERS, capping to %d.\n", min, MAX_BG_MEMBERS / 2);
  67. min = MAX_BG_MEMBERS / 2;
  68. }
  69. bg->required_players = min;
  70. } else {
  71. if (!exists)
  72. bg->required_players = 1;
  73. }
  74. if (this->nodeExists(node, "MaxPlayers")) {
  75. int max;
  76. if (!this->asInt32(node, "MaxPlayers", max))
  77. return 0;
  78. if (max < 1) {
  79. this->invalidWarning(node["MaxPlayers"], "Maximum players %d cannot be less than 1, capping to 1.\n", max);
  80. max = 1;
  81. }
  82. if (max * 2 > MAX_BG_MEMBERS) {
  83. this->invalidWarning(node["MaxPlayers"], "Maximum players %d exceeds MAX_BG_MEMBERS, capping to %d.\n", max, MAX_BG_MEMBERS / 2);
  84. max = MAX_BG_MEMBERS / 2;
  85. }
  86. bg->max_players = max;
  87. } else {
  88. if (!exists)
  89. bg->max_players = MAX_BG_MEMBERS / 2;
  90. }
  91. if (this->nodeExists(node, "MinLevel")) {
  92. int min;
  93. if (!this->asInt32(node, "MinLevel", min))
  94. return 0;
  95. if (min > MAX_LEVEL) {
  96. this->invalidWarning(node["MinLevel"], "Minimum level %d exceeds MAX_LEVEL, capping to %d.\n", min, MAX_LEVEL);
  97. min = MAX_LEVEL;
  98. }
  99. bg->min_lvl = min;
  100. } else {
  101. if (!exists)
  102. bg->min_lvl = 1;
  103. }
  104. if (this->nodeExists(node, "MaxLevel")) {
  105. int max;
  106. if (!this->asInt32(node, "MaxLevel", max))
  107. return 0;
  108. if (max > MAX_LEVEL) {
  109. this->invalidWarning(node["MaxLevel"], "Maximum level %d exceeds MAX_LEVEL, capping to %d.\n", max, MAX_LEVEL);
  110. max = MAX_LEVEL;
  111. }
  112. bg->max_lvl = max;
  113. } else {
  114. if (!exists)
  115. bg->max_lvl = MAX_LEVEL;
  116. }
  117. if (this->nodeExists(node, "Deserter")) {
  118. uint32 deserter;
  119. if (!this->asUInt32(node, "Deserter", deserter))
  120. return 0;
  121. bg->deserter_time = deserter;
  122. } else {
  123. if (!exists)
  124. bg->deserter_time = 600;
  125. }
  126. if (this->nodeExists(node, "StartDelay")) {
  127. uint32 delay;
  128. if (!this->asUInt32(node, "StartDelay", delay))
  129. return 0;
  130. bg->start_delay = delay;
  131. } else {
  132. if (!exists)
  133. bg->start_delay = 0;
  134. }
  135. if (this->nodeExists(node, "Join")) {
  136. const YAML::Node &joinNode = node["Join"];
  137. if (this->nodeExists(joinNode, "Solo")) {
  138. bool active;
  139. if (!this->asBool(joinNode, "Solo", active))
  140. return 0;
  141. bg->solo = active;
  142. } else {
  143. if (!exists)
  144. bg->solo = true;
  145. }
  146. if (this->nodeExists(joinNode, "Party")) {
  147. bool active;
  148. if (!this->asBool(joinNode, "Party", active))
  149. return 0;
  150. bg->party = active;
  151. } else {
  152. if (!exists)
  153. bg->party = true;
  154. }
  155. if (this->nodeExists(joinNode, "Guild")) {
  156. bool active;
  157. if (!this->asBool(joinNode, "Guild", active))
  158. return 0;
  159. bg->guild = active;
  160. } else {
  161. if (!exists)
  162. bg->guild = true;
  163. }
  164. } else {
  165. if (!exists) {
  166. bg->solo = true;
  167. bg->party = true;
  168. bg->guild = true;
  169. }
  170. }
  171. if (this->nodeExists(node, "JobRestrictions")) {
  172. const YAML::Node &jobsNode = node["JobRestrictions"];
  173. for (const auto &jobit : jobsNode) {
  174. std::string job_name = jobit.first.as<std::string>(), job_name_constant = "JOB_" + job_name;
  175. int64 constant;
  176. if (!script_get_constant(job_name_constant.c_str(), &constant)) {
  177. this->invalidWarning(node["JobRestrictions"], "Job %s does not exist.\n", job_name.c_str());
  178. continue;
  179. }
  180. bool active;
  181. if (!this->asBool(jobsNode, job_name, active))
  182. return 0;
  183. if (active)
  184. bg->job_restrictions.push_back(static_cast<int32>(constant));
  185. else
  186. util::vector_erase_if_exists(bg->job_restrictions, static_cast<int32>(constant));
  187. }
  188. }
  189. if (this->nodeExists(node, "Locations")) {
  190. int count = 0;
  191. for (const auto &locationit : node["Locations"]) {
  192. const YAML::Node &location = locationit;
  193. s_battleground_map map_entry;
  194. if (this->nodeExists(location, "Map")) {
  195. std::string map_name;
  196. if (!this->asString(location, "Map", map_name))
  197. return 0;
  198. map_entry.mapindex = mapindex_name2id(map_name.c_str());
  199. if (map_entry.mapindex == 0) {
  200. this->invalidWarning(location["Map"], "Invalid battleground map name %s, skipping.\n", map_name.c_str());
  201. return 0;
  202. }
  203. }
  204. if (this->nodeExists(location, "StartEvent")) {
  205. if (!this->asString(location, "StartEvent", map_entry.bgcallscript))
  206. return 0;
  207. map_entry.bgcallscript.resize(EVENT_NAME_LENGTH);
  208. if (map_entry.bgcallscript.find("::On") == std::string::npos) {
  209. this->invalidWarning(location["StartEvent"], "Battleground StartEvent label %s should begin with '::On', skipping.\n", map_entry.bgcallscript.c_str());
  210. return 0;
  211. }
  212. }
  213. std::vector<std::string> team_list = { "TeamA", "TeamB" };
  214. for (const auto &it : team_list) {
  215. const YAML::Node &team = location;
  216. if (this->nodeExists(team, it)) {
  217. s_battleground_team *team_ptr;
  218. if (it.find("TeamA") != std::string::npos)
  219. team_ptr = &map_entry.team1;
  220. else if (it.find("TeamB") != std::string::npos)
  221. team_ptr = &map_entry.team2;
  222. else {
  223. this->invalidWarning(team[it], "An invalid Team is defined.\n");
  224. return 0;
  225. }
  226. if (this->nodeExists(team[it], "RespawnX")) {
  227. if (!this->asInt16(team[it], "RespawnX", team_ptr->warp_x))
  228. return 0;
  229. }
  230. if (this->nodeExists(team[it], "RespawnY")) {
  231. if (!this->asInt16(team[it], "RespawnY", team_ptr->warp_y))
  232. return 0;
  233. }
  234. if (this->nodeExists(team[it], "DeathEvent")) {
  235. if (!this->asString(team[it], "DeathEvent", team_ptr->death_event))
  236. return 0;
  237. team_ptr->death_event.resize(EVENT_NAME_LENGTH);
  238. if (team_ptr->death_event.find("::On") == std::string::npos) {
  239. this->invalidWarning(team["DeathEvent"], "Battleground DeathEvent label %s should begin with '::On', skipping.\n", team_ptr->death_event.c_str());
  240. return 0;
  241. }
  242. }
  243. if (this->nodeExists(team[it], "QuitEvent")) {
  244. if (!this->asString(team[it], "QuitEvent", team_ptr->quit_event))
  245. return 0;
  246. team_ptr->quit_event.resize(EVENT_NAME_LENGTH);
  247. if (team_ptr->quit_event.find("::On") == std::string::npos) {
  248. this->invalidWarning(team["QuitEvent"], "Battleground QuitEvent label %s should begin with '::On', skipping.\n", team_ptr->quit_event.c_str());
  249. return 0;
  250. }
  251. }
  252. if (this->nodeExists(team[it], "ActiveEvent")) {
  253. if (!this->asString(team[it], "ActiveEvent", team_ptr->active_event))
  254. return 0;
  255. team_ptr->active_event.resize(EVENT_NAME_LENGTH);
  256. if (team_ptr->active_event.find("::On") == std::string::npos) {
  257. this->invalidWarning(team["ActiveEvent"], "Battleground ActiveEvent label %s should begin with '::On', skipping.\n", team_ptr->active_event.c_str());
  258. return 0;
  259. }
  260. }
  261. if (this->nodeExists(team[it], "Variable")) {
  262. if (!this->asString(team[it], "Variable", team_ptr->bg_id_var))
  263. return 0;
  264. team_ptr->bg_id_var.resize(NAME_LENGTH);
  265. }
  266. map_entry.id = count++;
  267. map_entry.isReserved = false;
  268. }
  269. }
  270. bg->maps.push_back(map_entry);
  271. }
  272. }
  273. if (!exists)
  274. this->put(id, bg);
  275. return 1;
  276. }
  277. /**
  278. * Search for a battleground based on the given name
  279. * @param name: Battleground name
  280. * @return s_battleground_type on success or nullptr on failure
  281. */
  282. std::shared_ptr<s_battleground_type> bg_search_name(const char *name)
  283. {
  284. for (const auto &entry : battleground_db) {
  285. auto bg = entry.second;
  286. if (!stricmp(bg->name.c_str(), name))
  287. return bg;
  288. }
  289. return nullptr;
  290. }
  291. /**
  292. * Search for a Battleground queue based on the given queue ID
  293. * @param queue_id: Queue ID
  294. * @return s_battleground_queue on success or nullptr on failure
  295. */
  296. std::shared_ptr<s_battleground_queue> bg_search_queue(int queue_id)
  297. {
  298. for (const auto &queue : bg_queues) {
  299. if (queue_id == queue->queue_id)
  300. return queue;
  301. }
  302. return nullptr;
  303. }
  304. /**
  305. * Search for an available player in Battleground
  306. * @param bg: Battleground data
  307. * @return map_session_data
  308. */
  309. struct map_session_data* bg_getavailablesd(s_battleground_data *bg)
  310. {
  311. nullpo_retr(nullptr, bg);
  312. for (const auto &member : bg->members) {
  313. if (member.sd != nullptr)
  314. return member.sd;
  315. }
  316. return nullptr;
  317. }
  318. /**
  319. * Delete a Battleground team from the db
  320. * @param bg_id: Battleground ID
  321. * @return True on success or false otherwise
  322. */
  323. bool bg_team_delete(int bg_id)
  324. {
  325. std::shared_ptr<s_battleground_data> bgteam = util::umap_find(bg_team_db, bg_id);
  326. if (bgteam) {
  327. for (const auto &pl_sd : bgteam->members) {
  328. bg_send_dot_remove(pl_sd.sd);
  329. pl_sd.sd->bg_id = 0;
  330. }
  331. bg_team_db.erase(bg_id);
  332. return true;
  333. }
  334. return false;
  335. }
  336. /**
  337. * Warps a Battleground team
  338. * @param bg_id: Battleground ID
  339. * @param mapindex: Map Index
  340. * @param x: X coordinate
  341. * @param y: Y coordinate
  342. * @return True on success or false otherwise
  343. */
  344. bool bg_team_warp(int bg_id, unsigned short mapindex, short x, short y)
  345. {
  346. std::shared_ptr<s_battleground_data> bgteam = util::umap_find(bg_team_db, bg_id);
  347. if (bgteam) {
  348. for (const auto &pl_sd : bgteam->members)
  349. pc_setpos(pl_sd.sd, mapindex, x, y, CLR_TELEPORT);
  350. return true;
  351. }
  352. return false;
  353. }
  354. /**
  355. * Remove a player's Battleground map marker
  356. * @param sd: Player data
  357. */
  358. void bg_send_dot_remove(struct map_session_data *sd)
  359. {
  360. nullpo_retv(sd);
  361. if( sd && sd->bg_id )
  362. clif_bg_xy_remove(sd);
  363. return;
  364. }
  365. /**
  366. * Join a player to a Battleground team
  367. * @param bg_id: Battleground ID
  368. * @param sd: Player data
  369. * @param is_queue: Joined from queue
  370. * @return True on success or false otherwise
  371. */
  372. bool bg_team_join(int bg_id, struct map_session_data *sd, bool is_queue)
  373. {
  374. if (!sd || sd->bg_id)
  375. return false;
  376. std::shared_ptr<s_battleground_data> bgteam = util::umap_find(bg_team_db, bg_id);
  377. if (bgteam) {
  378. if (bgteam->members.size() == MAX_BG_MEMBERS)
  379. return false; // No free slots
  380. s_battleground_member_data member = {};
  381. sd->bg_id = bg_id;
  382. member.sd = sd;
  383. member.x = sd->bl.x;
  384. member.y = sd->bl.y;
  385. if (is_queue) { // Save the location from where the person entered the battleground
  386. member.entry_point.map = sd->mapindex;
  387. member.entry_point.x = sd->bl.x;
  388. member.entry_point.y = sd->bl.y;
  389. }
  390. bgteam->members.push_back(member);
  391. guild_send_dot_remove(sd);
  392. for (const auto &pl_sd : bgteam->members) {
  393. if (pl_sd.sd != sd)
  394. clif_hpmeter_single(sd->fd, pl_sd.sd->bl.id, pl_sd.sd->battle_status.hp, pl_sd.sd->battle_status.max_hp);
  395. }
  396. clif_bg_hp(sd);
  397. clif_bg_xy(sd);
  398. return true;
  399. }
  400. return false;
  401. }
  402. /**
  403. * Remove a player from Battleground team
  404. * @param sd: Player data
  405. * @param quit: True if closed client or false otherwise
  406. * @param deserter: Whether to apply the deserter status or not
  407. * @return Remaining count in Battleground team or -1 on failure
  408. */
  409. int bg_team_leave(struct map_session_data *sd, bool quit, bool deserter)
  410. {
  411. if (!sd || !sd->bg_id)
  412. return -1;
  413. bg_send_dot_remove(sd);
  414. int bg_id = sd->bg_id;
  415. std::shared_ptr<s_battleground_data> bgteam = util::umap_find(bg_team_db, bg_id);
  416. sd->bg_id = 0;
  417. if (bgteam) {
  418. // Warping members out only applies to the Battleground Queue System
  419. if (battle_config.feature_bgqueue) {
  420. auto member = bgteam->members.begin();
  421. while (member != bgteam->members.end()) {
  422. if (member->sd == sd) {
  423. if (member->entry_point.map != 0 && !map_getmapflag(map_mapindex2mapid(member->entry_point.map), MF_NOSAVE))
  424. pc_setpos(sd, member->entry_point.map, member->entry_point.x, member->entry_point.y, CLR_TELEPORT);
  425. else
  426. pc_setpos(sd, sd->status.save_point.map, sd->status.save_point.x, sd->status.save_point.y, CLR_TELEPORT); // Warp to save point if the entry map has no save flag.
  427. bgteam->members.erase(member);
  428. break;
  429. } else
  430. member++;
  431. }
  432. }
  433. char output[CHAT_SIZE_MAX];
  434. if (quit)
  435. sprintf(output, "Server: %s has quit the game...", sd->status.name);
  436. else
  437. sprintf(output, "Server: %s is leaving the battlefield...", sd->status.name);
  438. clif_bg_message(bgteam.get(), 0, "Server", output, strlen(output) + 1);
  439. if (!bgteam->logout_event.empty() && quit)
  440. npc_event(sd, bgteam->logout_event.c_str(), 0);
  441. if (deserter) {
  442. std::shared_ptr<s_battleground_type> bg = battleground_db.find(bg_id);
  443. if (bg)
  444. sc_start(nullptr, &sd->bl, SC_ENTRY_QUEUE_NOTIFY_ADMISSION_TIME_OUT, 100, 1, static_cast<t_tick>(bg->deserter_time) * 1000); // Deserter timer
  445. }
  446. return bgteam->members.size();
  447. }
  448. return -1;
  449. }
  450. /**
  451. * Respawn a Battleground player
  452. * @param sd: Player data
  453. * @return True on success or false otherwise
  454. */
  455. bool bg_member_respawn(struct map_session_data *sd)
  456. {
  457. if (!sd || !sd->bg_id || !pc_isdead(sd))
  458. return false;
  459. std::shared_ptr<s_battleground_data> bgteam = util::umap_find(bg_team_db, sd->bg_id);
  460. if (bgteam) {
  461. if (bgteam->cemetery.map == 0)
  462. return false; // Respawn not handled by Core
  463. pc_setpos(sd, bgteam->cemetery.map, bgteam->cemetery.x, bgteam->cemetery.y, CLR_OUTSIGHT);
  464. status_revive(&sd->bl, 1, 100);
  465. return true; // Warped
  466. }
  467. return false;
  468. }
  469. /**
  470. * Initialize Battleground data
  471. * @param mapindex: Map Index
  472. * @param rx: Return X coordinate (on death)
  473. * @param ry: Return Y coordinate (on death)
  474. * @param ev: Logout NPC Event
  475. * @param dev: Death NPC Event
  476. * @return Battleground ID
  477. */
  478. int bg_create(uint16 mapindex, s_battleground_team* team)
  479. {
  480. int bg_team_counter = 1;
  481. while (bg_team_db.find(bg_team_counter) != bg_team_db.end())
  482. bg_team_counter++;
  483. bg_team_db[bg_team_counter] = std::make_shared<s_battleground_data>();
  484. std::shared_ptr<s_battleground_data> bg = util::umap_find(bg_team_db, bg_team_counter);
  485. bg->id = bg_team_counter;
  486. bg->cemetery.map = mapindex;
  487. bg->cemetery.x = team->warp_x;
  488. bg->cemetery.y = team->warp_y;
  489. bg->logout_event = team->quit_event.c_str();
  490. bg->die_event = team->death_event.c_str();
  491. bg->active_event = team->active_event.c_str();
  492. return bg->id;
  493. }
  494. /**
  495. * Get an object's Battleground ID
  496. * @param bl: Object
  497. * @return Battleground ID
  498. */
  499. int bg_team_get_id(struct block_list *bl)
  500. {
  501. nullpo_ret(bl);
  502. switch( bl->type ) {
  503. case BL_PC:
  504. return ((TBL_PC*)bl)->bg_id;
  505. case BL_PET:
  506. if( ((TBL_PET*)bl)->master )
  507. return ((TBL_PET*)bl)->master->bg_id;
  508. break;
  509. case BL_MOB: {
  510. struct map_session_data *msd;
  511. struct mob_data *md = (TBL_MOB*)bl;
  512. if( md->special_state.ai && (msd = map_id2sd(md->master_id)) != nullptr )
  513. return msd->bg_id;
  514. return md->bg_id;
  515. }
  516. case BL_HOM:
  517. if( ((TBL_HOM*)bl)->master )
  518. return ((TBL_HOM*)bl)->master->bg_id;
  519. break;
  520. case BL_MER:
  521. if( ((TBL_MER*)bl)->master )
  522. return ((TBL_MER*)bl)->master->bg_id;
  523. break;
  524. case BL_SKILL:
  525. return ((TBL_SKILL*)bl)->group->bg_id;
  526. }
  527. return 0;
  528. }
  529. /**
  530. * Send a Battleground chat message
  531. * @param sd: Player data
  532. * @param mes: Message
  533. * @param len: Message length
  534. */
  535. void bg_send_message(struct map_session_data *sd, const char *mes, int len)
  536. {
  537. nullpo_retv(sd);
  538. if (sd->bg_id == 0)
  539. return;
  540. std::shared_ptr<s_battleground_data> bgteam = util::umap_find(bg_team_db, sd->bg_id);
  541. if (bgteam)
  542. clif_bg_message(bgteam.get(), sd->bl.id, sd->status.name, mes, len);
  543. return;
  544. }
  545. /**
  546. * Update a player's Battleground minimap icon
  547. * @see DBApply
  548. */
  549. int bg_send_xy_timer_sub(std::shared_ptr<s_battleground_data> bg)
  550. {
  551. struct map_session_data *sd;
  552. for (auto &pl_sd : bg->members) {
  553. sd = pl_sd.sd;
  554. if (sd->bl.x != pl_sd.x || sd->bl.y != pl_sd.y) { // xy update
  555. pl_sd.x = sd->bl.x;
  556. pl_sd.y = sd->bl.y;
  557. clif_bg_xy(sd);
  558. }
  559. }
  560. return 0;
  561. }
  562. /**
  563. * Update a player's Battleground minimap icon
  564. * @param tid: Timer ID
  565. * @param tick: Timer
  566. * @param id: ID
  567. * @return 0 on success or 1 otherwise
  568. */
  569. TIMER_FUNC(bg_send_xy_timer)
  570. {
  571. for (const auto &entry : bg_team_db)
  572. bg_send_xy_timer_sub(entry.second);
  573. return 0;
  574. }
  575. /**
  576. * Mark a Battleground as ready to begin queuing for a free map
  577. * @param tid: Timer ID
  578. * @param tick: Timer
  579. * @param id: ID
  580. * @return 0 on success or 1 otherwise
  581. */
  582. static TIMER_FUNC(bg_on_ready_loopback)
  583. {
  584. int queue_id = (int)data;
  585. std::shared_ptr<s_battleground_queue> queue = bg_search_queue(queue_id);
  586. if (queue == nullptr) {
  587. ShowError("bg_on_ready_loopback: Invalid battleground queue %d.\n", queue_id);
  588. return 1;
  589. }
  590. std::shared_ptr<s_battleground_type> bg = battleground_db.find(queue->id);
  591. if (bg) {
  592. bg_queue_on_ready(bg->name.c_str(), queue);
  593. return 0;
  594. } else {
  595. ShowError("bg_on_ready_loopback: Can't find battleground %d in the battlegrounds database.\n", queue->id);
  596. return 1;
  597. }
  598. }
  599. /**
  600. * Reset Battleground queue data if players don't accept in time
  601. * @param tid: Timer ID
  602. * @param tick: Timer
  603. * @param id: ID
  604. * @return 0 on success or 1 otherwise
  605. */
  606. static TIMER_FUNC(bg_on_ready_expire)
  607. {
  608. int queue_id = (int)data;
  609. std::shared_ptr<s_battleground_queue> queue = bg_search_queue(queue_id);
  610. if (queue == nullptr) {
  611. ShowError("bg_on_ready_expire: Invalid battleground queue %d.\n", queue_id);
  612. return 1;
  613. }
  614. std::string bg_name = battleground_db.find(queue->id)->name;
  615. for (const auto &sd : queue->teama_members) {
  616. clif_bg_queue_apply_result(BG_APPLY_QUEUE_FINISHED, bg_name.c_str(), sd);
  617. clif_bg_queue_entry_init(sd);
  618. }
  619. for (const auto &sd : queue->teamb_members) {
  620. clif_bg_queue_apply_result(BG_APPLY_QUEUE_FINISHED, bg_name.c_str(), sd);
  621. clif_bg_queue_entry_init(sd);
  622. }
  623. bg_queue_clear(queue, true);
  624. return 0;
  625. }
  626. /**
  627. * Start a Battleground when all players have accepted
  628. * @param tid: Timer ID
  629. * @param tick: Timer
  630. * @param id: ID
  631. * @return 0 on success or 1 otherwise
  632. */
  633. static TIMER_FUNC(bg_on_ready_start)
  634. {
  635. int queue_id = (int)data;
  636. std::shared_ptr<s_battleground_queue> queue = bg_search_queue(queue_id);
  637. if (queue == nullptr) {
  638. ShowError("bg_on_ready_start: Invalid battleground queue %d.\n", queue_id);
  639. return 1;
  640. }
  641. queue->tid_start = INVALID_TIMER;
  642. bg_queue_start_battleground(queue);
  643. return 0;
  644. }
  645. /**
  646. * Check if the given player is in a battleground
  647. * @param sd: Player data
  648. * @return True if in a battleground or false otherwise
  649. */
  650. bool bg_player_is_in_bg_map(struct map_session_data *sd)
  651. {
  652. nullpo_retr(false, sd);
  653. for (const auto &pair : battleground_db) {
  654. for (const auto &it : pair.second->maps) {
  655. if (it.mapindex == sd->mapindex)
  656. return true;
  657. }
  658. }
  659. return false;
  660. }
  661. /**
  662. * Battleground status change check
  663. * @param sd: Player data
  664. * @param name: Battleground name
  665. * @return True if the player is good to join a queue or false otherwise
  666. */
  667. static bool bg_queue_check_status(struct map_session_data* sd, const char *name)
  668. {
  669. nullpo_retr(false, sd);
  670. if (sd->sc.count) {
  671. if (sd->sc.data[SC_ENTRY_QUEUE_APPLY_DELAY]) { // Exclude any player who's recently left a battleground queue
  672. char buf[CHAT_SIZE_MAX];
  673. sprintf(buf, msg_txt(sd, 339), static_cast<int32>((get_timer(sd->sc.data[SC_ENTRY_QUEUE_APPLY_DELAY]->timer)->tick - gettick()) / 1000)); // You can't apply to a battleground queue for %d seconds due to recently leaving one.
  674. clif_bg_queue_apply_result(BG_APPLY_NONE, name, sd);
  675. clif_messagecolor(&sd->bl, color_table[COLOR_LIGHT_GREEN], buf, false, SELF);
  676. return false;
  677. }
  678. if (sd->sc.data[SC_ENTRY_QUEUE_NOTIFY_ADMISSION_TIME_OUT]) { // Exclude any player who's recently deserted a battleground
  679. char buf[CHAT_SIZE_MAX];
  680. int32 status_tick = static_cast<int32>(DIFF_TICK(get_timer(sd->sc.data[SC_ENTRY_QUEUE_NOTIFY_ADMISSION_TIME_OUT]->timer)->tick, gettick()) / 1000);
  681. sprintf(buf, msg_txt(sd, 338), status_tick / 60, status_tick % 60); // You can't apply to a battleground queue due to recently deserting a battleground. Time remaining: %d minutes and %d seconds.
  682. clif_bg_queue_apply_result(BG_APPLY_NONE, name, sd);
  683. clif_messagecolor(&sd->bl, color_table[COLOR_LIGHT_GREEN], buf, false, SELF);
  684. return false;
  685. }
  686. }
  687. return true;
  688. }
  689. /**
  690. * Check to see if a Battleground is joinable
  691. * @param bg: Battleground data
  692. * @param sd: Player data
  693. * @param name: Battleground name
  694. * @return True on success or false otherwise
  695. */
  696. bool bg_queue_check_joinable(std::shared_ptr<s_battleground_type> bg, struct map_session_data *sd, const char *name)
  697. {
  698. nullpo_retr(false, sd);
  699. for (const auto &job : bg->job_restrictions) { // Check class requirement
  700. if (sd->status.class_ == job) {
  701. clif_bg_queue_apply_result(BG_APPLY_PLAYER_CLASS, name, sd);
  702. return false;
  703. }
  704. }
  705. if (bg->min_lvl > 0 && sd->status.base_level < bg->min_lvl) { // Check minimum level requirement
  706. clif_bg_queue_apply_result(BG_APPLY_PLAYER_LEVEL, name, sd);
  707. return false;
  708. }
  709. if (bg->max_lvl > 0 && sd->status.base_level > bg->max_lvl) { // Check maximum level requirement
  710. clif_bg_queue_apply_result(BG_APPLY_PLAYER_LEVEL, name, sd);
  711. return false;
  712. }
  713. if (!bg_queue_check_status(sd, name)) // Check status blocks
  714. return false;
  715. if (bg_player_is_in_bg_map(sd)) { // Is the player currently in a battleground map? Reject them.
  716. clif_bg_queue_apply_result(BG_APPLY_NONE, name, sd);
  717. clif_messagecolor(&sd->bl, color_table[COLOR_LIGHT_GREEN], msg_txt(sd, 337), false, SELF); // You can't apply to a battleground queue from this map.
  718. return false;
  719. }
  720. if (battle_config.bgqueue_nowarp_mapflag > 0 && map_getmapflag(sd->bl.m, MF_NOWARP)) { // Check player's current position for mapflag check
  721. clif_bg_queue_apply_result(BG_APPLY_NONE, name, sd);
  722. clif_messagecolor(&sd->bl, color_table[COLOR_LIGHT_GREEN], msg_txt(sd, 337), false, SELF); // You can't apply to a battleground queue from this map.
  723. return false;
  724. }
  725. return true;
  726. }
  727. /**
  728. * Mark a map as reserved for a Battleground
  729. * @param name: Battleground map name
  730. * @param state: Whether to mark reserved or not
  731. * @param ended: Whether the Battleground event is complete; players getting prize
  732. * @return True on success or false otherwise
  733. */
  734. bool bg_queue_reservation(const char *name, bool state, bool ended)
  735. {
  736. uint16 mapindex = mapindex_name2id(name);
  737. for (auto &pair : battleground_db) {
  738. for (auto &map : pair.second->maps) {
  739. if (map.mapindex == mapindex) {
  740. map.isReserved = state;
  741. for (auto &queue : bg_queues) {
  742. if (queue->map == &map) {
  743. if (ended) // The ended flag is applied from bg_reserve (bg_unbook clears it for the next queue)
  744. queue->state = QUEUE_STATE_ENDED;
  745. if (!state)
  746. bg_queue_clear(queue, true);
  747. }
  748. }
  749. return true;
  750. }
  751. }
  752. }
  753. return false;
  754. }
  755. /**
  756. * Join as an individual into a Battleground
  757. * @param name: Battleground name
  758. * @param sd: Player who requested to join the battlegrounds
  759. */
  760. void bg_queue_join_solo(const char *name, struct map_session_data *sd)
  761. {
  762. if (!sd) {
  763. ShowError("bg_queue_join_solo: Tried to join non-existent player\n.");
  764. return;
  765. }
  766. std::shared_ptr<s_battleground_type> bg = bg_search_name(name);
  767. if (!bg) {
  768. ShowWarning("bq_queue_join_solo: Could not find battleground \"%s\" requested by %s (AID: %d / CID: %d)\n", name, sd->status.name, sd->status.account_id, sd->status.char_id);
  769. return;
  770. }
  771. if (!bg->solo) {
  772. clif_bg_queue_apply_result(BG_APPLY_INVALID_APP, name, sd);
  773. return;
  774. }
  775. bg_queue_join_multi(name, sd, { sd }); // Join as solo
  776. }
  777. /**
  778. * Join a party onto the same side of a Battleground
  779. * @param name: Battleground name
  780. * @param sd: Player who requested to join the battlegrounds
  781. */
  782. void bg_queue_join_party(const char *name, struct map_session_data *sd)
  783. {
  784. if (!sd) {
  785. ShowError("bg_queue_join_party: Tried to join non-existent player\n.");
  786. return;
  787. }
  788. struct party_data *p = party_search(sd->status.party_id);
  789. if (!p) {
  790. clif_bg_queue_apply_result(BG_APPLY_INVALID_APP, name, sd);
  791. return; // Someone has bypassed the client check for being in a party
  792. }
  793. for (const auto &it : p->party.member) {
  794. if (it.leader && sd->status.char_id != it.char_id) {
  795. clif_bg_queue_apply_result(BG_APPLY_PARTYGUILD_LEADER, name, sd);
  796. return; // Not the party leader
  797. }
  798. }
  799. std::shared_ptr<s_battleground_type> bg = bg_search_name(name);
  800. if (bg) {
  801. if (!bg->party) {
  802. clif_bg_queue_apply_result(BG_APPLY_INVALID_APP, name, sd);
  803. return;
  804. }
  805. int p_online = 0;
  806. for (const auto &it : p->party.member) {
  807. if (it.online)
  808. p_online++;
  809. }
  810. if (p_online > bg->max_players) {
  811. clif_bg_queue_apply_result(BG_APPLY_PLAYER_COUNT, name, sd);
  812. return; // Too many party members online
  813. }
  814. std::vector<struct map_session_data *> list;
  815. for (const auto &it : p->party.member) {
  816. if (list.size() == bg->max_players)
  817. break;
  818. if (it.online) {
  819. struct map_session_data *pl_sd = map_charid2sd(it.char_id);
  820. if (pl_sd)
  821. list.push_back(pl_sd);
  822. }
  823. }
  824. bg_queue_join_multi(name, sd, list); // Join as party, all on the same side of the BG
  825. } else {
  826. ShowWarning("clif_parse_bg_queue_apply_request: Could not find Battleground: \"%s\" requested by player: %s (AID:%d CID:%d)\n", name, sd->status.name, sd->status.account_id, sd->status.char_id);
  827. clif_bg_queue_apply_result(BG_APPLY_INVALID_NAME, name, sd);
  828. return; // Invalid BG name
  829. }
  830. }
  831. /**
  832. * Join a guild onto the same side of a Battleground
  833. * @param name: Battleground name
  834. * @param sd: Player who requested to join the battlegrounds
  835. */
  836. void bg_queue_join_guild(const char *name, struct map_session_data *sd)
  837. {
  838. if (!sd) {
  839. ShowError("bg_queue_join_guild: Tried to join non-existent player\n.");
  840. return;
  841. }
  842. if (!sd->guild) {
  843. clif_bg_queue_apply_result(BG_APPLY_INVALID_APP, name, sd);
  844. return; // Someone has bypassed the client check for being in a guild
  845. }
  846. if (strcmp(sd->status.name, sd->guild->master) != 0) {
  847. clif_bg_queue_apply_result(BG_APPLY_PARTYGUILD_LEADER, name, sd);
  848. return; // Not the guild leader
  849. }
  850. std::shared_ptr<s_battleground_type> bg = bg_search_name(name);
  851. if (bg) {
  852. if (!bg->guild) {
  853. clif_bg_queue_apply_result(BG_APPLY_INVALID_APP, name, sd);
  854. return;
  855. }
  856. struct guild* g = sd->guild;
  857. if (g->connect_member > bg->max_players) {
  858. clif_bg_queue_apply_result(BG_APPLY_PLAYER_COUNT, name, sd);
  859. return; // Too many guild members online
  860. }
  861. std::vector<struct map_session_data *> list;
  862. for (const auto &it : g->member) {
  863. if (list.size() == bg->max_players)
  864. break;
  865. if (it.online) {
  866. struct map_session_data *pl_sd = map_charid2sd(it.char_id);
  867. if (pl_sd)
  868. list.push_back(pl_sd);
  869. }
  870. }
  871. bg_queue_join_multi(name, sd, list); // Join as guild, all on the same side of the BG
  872. } else {
  873. ShowWarning("clif_parse_bg_queue_apply_request: Could not find Battleground: \"%s\" requested by player: %s (AID:%d CID:%d)\n", name, sd->status.name, sd->status.account_id, sd->status.char_id);
  874. clif_bg_queue_apply_result(BG_APPLY_INVALID_NAME, name, sd);
  875. return; // Invalid BG name
  876. }
  877. }
  878. /**
  879. * Join multiple players onto the same side of a Battleground
  880. * @param name: Battleground name
  881. * @param sd: Player who requested to join the battlegrounds
  882. * @param list: Contains all players including the player who requested to join
  883. */
  884. void bg_queue_join_multi(const char *name, struct map_session_data *sd, std::vector <map_session_data *> list)
  885. {
  886. if (!sd) {
  887. ShowError("bg_queue_join_multi: Tried to join non-existent player\n.");
  888. return;
  889. }
  890. std::shared_ptr<s_battleground_type> bg = bg_search_name(name);
  891. if (!bg) {
  892. ShowWarning("bq_queue_join_multi: Could not find battleground \"%s\" requested by %s (AID: %d / CID: %d)\n", name, sd->status.name, sd->status.account_id, sd->status.char_id);
  893. return;
  894. }
  895. if (!bg_queue_check_joinable(bg, sd, name)){
  896. return;
  897. }
  898. for (const auto &queue : bg_queues) {
  899. if (queue->id != bg->id || queue->state == QUEUE_STATE_SETUP_DELAY || queue->state == QUEUE_STATE_ENDED)
  900. continue;
  901. // Make sure there's enough space on one side to join as a party/guild in this queue
  902. if (queue->teama_members.size() + list.size() > bg->max_players && queue->teamb_members.size() + list.size() > bg->max_players) {
  903. break;
  904. }
  905. bool r = rnd() % 2 != 0;
  906. std::vector<map_session_data *> *team = r ? &queue->teamb_members : &queue->teama_members;
  907. if (queue->state == QUEUE_STATE_ACTIVE) {
  908. // If one team has lesser members try to balance (on an active BG)
  909. if (r && queue->teama_members.size() < queue->teamb_members.size())
  910. team = &queue->teama_members;
  911. else if (!r && queue->teamb_members.size() < queue->teama_members.size())
  912. team = &queue->teamb_members;
  913. } else {
  914. // If the designated team is full, put the player into the other team
  915. if (team->size() + list.size() > bg->required_players)
  916. team = r ? &queue->teama_members : &queue->teamb_members;
  917. }
  918. while (!list.empty() && team->size() < bg->max_players) {
  919. struct map_session_data *sd2 = list.back();
  920. list.pop_back();
  921. if (!sd2 || sd2->bg_queue_id > 0)
  922. continue;
  923. if (!bg_queue_check_joinable(bg, sd2, name))
  924. continue;
  925. sd2->bg_queue_id = queue->queue_id;
  926. team->push_back(sd2);
  927. clif_bg_queue_apply_result(BG_APPLY_ACCEPT, name, sd2);
  928. clif_bg_queue_apply_notify(name, sd2);
  929. }
  930. if (queue->state == QUEUE_STATE_ACTIVE) { // Battleground is already active
  931. for (auto &pl_sd : *team) {
  932. if (queue->map->mapindex == pl_sd->mapindex)
  933. continue;
  934. pc_set_bg_queue_timer(pl_sd);
  935. clif_bg_queue_lobby_notify(name, pl_sd);
  936. }
  937. } else if (queue->state == QUEUE_STATE_SETUP && queue->teamb_members.size() >= bg->required_players && queue->teama_members.size() >= bg->required_players) // Enough players have joined
  938. bg_queue_on_ready(name, queue);
  939. return;
  940. }
  941. // Something went wrong, sends reconnect and then reapply message to client.
  942. clif_bg_queue_apply_result(BG_APPLY_RECONNECT, name, sd);
  943. }
  944. /**
  945. * Clear Battleground queue for next one
  946. * @param queue: Queue to clean up
  947. * @param ended: If a Battleground has ended through normal means (by script command bg_unbook)
  948. */
  949. void bg_queue_clear(std::shared_ptr<s_battleground_queue> queue, bool ended)
  950. {
  951. if (queue == nullptr)
  952. return;
  953. if (queue->tid_requeue != INVALID_TIMER) {
  954. delete_timer(queue->tid_requeue, bg_on_ready_loopback);
  955. queue->tid_requeue = INVALID_TIMER;
  956. }
  957. if (queue->tid_expire != INVALID_TIMER) {
  958. delete_timer(queue->tid_expire, bg_on_ready_expire);
  959. queue->tid_expire = INVALID_TIMER;
  960. }
  961. if (queue->tid_start != INVALID_TIMER) {
  962. delete_timer(queue->tid_start, bg_on_ready_start);
  963. queue->tid_start = INVALID_TIMER;
  964. }
  965. if (ended) {
  966. if (queue->map != nullptr) {
  967. queue->map->isReserved = false; // Remove reservation to free up for future queue
  968. queue->map = nullptr;
  969. }
  970. for (const auto &sd : queue->teama_members)
  971. sd->bg_queue_id = 0;
  972. for (const auto &sd : queue->teamb_members)
  973. sd->bg_queue_id = 0;
  974. queue->teama_members.clear();
  975. queue->teamb_members.clear();
  976. queue->teama_members.shrink_to_fit();
  977. queue->teamb_members.shrink_to_fit();
  978. queue->accepted_players = 0;
  979. queue->state = QUEUE_STATE_SETUP;
  980. }
  981. }
  982. /**
  983. * Sub function for leaving a Battleground queue
  984. * @param sd: Player leaving
  985. * @param members: List of players in queue data
  986. * @param apply_sc: Apply the SC_ENTRY_QUEUE_APPLY_DELAY status on queue leave (default: true)
  987. * @return True on success or false otherwise
  988. */
  989. static bool bg_queue_leave_sub(struct map_session_data *sd, std::vector<map_session_data *> &members, bool apply_sc)
  990. {
  991. if (!sd)
  992. return false;
  993. auto list_it = members.begin();
  994. while (list_it != members.end()) {
  995. if (*list_it == sd) {
  996. members.erase(list_it);
  997. if (apply_sc)
  998. sc_start(nullptr, &sd->bl, SC_ENTRY_QUEUE_APPLY_DELAY, 100, 1, 60000);
  999. sd->bg_queue_id = 0;
  1000. pc_delete_bg_queue_timer(sd);
  1001. return true;
  1002. } else {
  1003. list_it++;
  1004. }
  1005. }
  1006. return false;
  1007. }
  1008. /**
  1009. * Leave a Battleground queue
  1010. * @param sd: Player data
  1011. * @param apply_sc: Apply the SC_ENTRY_QUEUE_APPLY_DELAY status on queue leave (default: true)
  1012. * @return True on success or false otherwise
  1013. */
  1014. bool bg_queue_leave(struct map_session_data *sd, bool apply_sc)
  1015. {
  1016. if (!sd || sd->bg_queue_id == 0)
  1017. return false;
  1018. pc_delete_bg_queue_timer(sd);
  1019. for (auto &queue : bg_queues) {
  1020. if (sd->bg_queue_id == queue->queue_id) {
  1021. if (!bg_queue_leave_sub(sd, queue->teama_members, apply_sc) && !bg_queue_leave_sub(sd, queue->teamb_members, apply_sc)) {
  1022. ShowError("bg_queue_leave: Couldn't find player %s in battlegrounds queue.\n", sd->status.name);
  1023. return false;
  1024. } else {
  1025. if ((queue->state == QUEUE_STATE_SETUP || queue->state == QUEUE_STATE_SETUP_DELAY) && queue->teama_members.empty() && queue->teamb_members.empty()) // If there are no players left in the queue (that hasn't started), discard it
  1026. bg_queue_clear(queue, true);
  1027. return true;
  1028. }
  1029. }
  1030. }
  1031. return false;
  1032. }
  1033. /**
  1034. * Send packets to all clients in queue to notify them that the battleground is ready to be joined
  1035. * @param name: Battleground name
  1036. * @param queue: Battleground queue
  1037. * @return True on success or false otherwise
  1038. */
  1039. bool bg_queue_on_ready(const char *name, std::shared_ptr<s_battleground_queue> queue)
  1040. {
  1041. std::shared_ptr<s_battleground_type> bg = battleground_db.find(queue->id);
  1042. if (!bg) {
  1043. ShowError("bg_queue_on_ready: Couldn't find battleground ID %d in battlegrounds database.\n", queue->id);
  1044. return false;
  1045. }
  1046. if (queue->teama_members.size() < queue->required_players || queue->teamb_members.size() < queue->required_players)
  1047. return false; // Return players to the queue and stop reapplying the timer
  1048. bool map_reserved = false;
  1049. for (auto &map : bg->maps) {
  1050. if (!map.isReserved) {
  1051. map.isReserved = true;
  1052. map_reserved = true;
  1053. queue->map = &map;
  1054. break;
  1055. }
  1056. }
  1057. if (!map_reserved) { // All the battleground maps are reserved. Set a timer to check for an open battleground every 10 seconds.
  1058. queue->tid_requeue = add_timer(gettick() + 10000, bg_on_ready_loopback, 0, (intptr_t)queue->queue_id);
  1059. return false;
  1060. }
  1061. queue->state = QUEUE_STATE_SETUP_DELAY;
  1062. queue->tid_expire = add_timer(gettick() + 20000, bg_on_ready_expire, 0, (intptr_t)queue->queue_id);
  1063. for (const auto &sd : queue->teama_members)
  1064. clif_bg_queue_lobby_notify(name, sd);
  1065. for (const auto &sd : queue->teamb_members)
  1066. clif_bg_queue_lobby_notify(name, sd);
  1067. return true;
  1068. }
  1069. /**
  1070. * Send a player into an active Battleground
  1071. * @param sd: Player to send in
  1072. * @param queue: Queue data
  1073. */
  1074. void bg_join_active(map_session_data *sd, std::shared_ptr<s_battleground_queue> queue)
  1075. {
  1076. if (sd == nullptr || queue == nullptr)
  1077. return;
  1078. // Check player's current position for mapflag check
  1079. if (battle_config.bgqueue_nowarp_mapflag > 0 && map_getmapflag(sd->bl.m, MF_NOWARP)) {
  1080. clif_messagecolor(&sd->bl, color_table[COLOR_LIGHT_GREEN], msg_txt(sd, 337), false, SELF); // You can't apply to a battleground queue from this map.
  1081. bg_queue_leave(sd);
  1082. clif_bg_queue_entry_init(sd);
  1083. return;
  1084. }
  1085. pc_delete_bg_queue_timer(sd); // Cancel timer so player doesn't leave the queue.
  1086. int bg_id_team_1 = static_cast<int>(mapreg_readreg(add_str(queue->map->team1.bg_id_var.c_str())));
  1087. std::shared_ptr<s_battleground_data> bgteam_1 = util::umap_find(bg_team_db, bg_id_team_1);
  1088. for (auto &pl_sd : queue->teama_members) {
  1089. if (sd != pl_sd)
  1090. continue;
  1091. if (bgteam_1 == nullptr) {
  1092. bg_queue_leave(sd);
  1093. clif_bg_queue_apply_result(BG_APPLY_RECONNECT, battleground_db.find(queue->id)->name.c_str(), sd);
  1094. clif_bg_queue_entry_init(sd);
  1095. return;
  1096. }
  1097. clif_bg_queue_entry_init(pl_sd);
  1098. bg_team_join(bg_id_team_1, pl_sd, true);
  1099. npc_event(pl_sd, bgteam_1->active_event.c_str(), 0);
  1100. return;
  1101. }
  1102. int bg_id_team_2 = static_cast<int>(mapreg_readreg(add_str(queue->map->team2.bg_id_var.c_str())));
  1103. std::shared_ptr<s_battleground_data> bgteam_2 = util::umap_find(bg_team_db, bg_id_team_2);
  1104. for (auto &pl_sd : queue->teamb_members) {
  1105. if (sd != pl_sd)
  1106. continue;
  1107. if (bgteam_2 == nullptr) {
  1108. bg_queue_leave(sd);
  1109. clif_bg_queue_apply_result(BG_APPLY_RECONNECT, battleground_db.find(queue->id)->name.c_str(), sd);
  1110. clif_bg_queue_entry_init(sd);
  1111. return;
  1112. }
  1113. clif_bg_queue_entry_init(pl_sd);
  1114. bg_team_join(bg_id_team_2, pl_sd, true);
  1115. npc_event(pl_sd, bgteam_2->active_event.c_str(), 0);
  1116. return;
  1117. }
  1118. return;
  1119. }
  1120. /**
  1121. * Check to see if any players in the queue are on a map with MF_NOWARP and remove them from the queue
  1122. * @param queue: Queue data
  1123. * @return True if the player is on a map with MF_NOWARP or false otherwise
  1124. */
  1125. bool bg_mapflag_check(std::shared_ptr<s_battleground_queue> queue) {
  1126. if (queue == nullptr || battle_config.bgqueue_nowarp_mapflag == 0)
  1127. return false;
  1128. bool found = false;
  1129. for (const auto &sd : queue->teama_members) {
  1130. if (map_getmapflag(sd->bl.m, MF_NOWARP)) {
  1131. clif_messagecolor(&sd->bl, color_table[COLOR_LIGHT_GREEN], msg_txt(sd, 337), false, SELF); // You can't apply to a battleground queue from this map.
  1132. bg_queue_leave(sd);
  1133. clif_bg_queue_entry_init(sd);
  1134. found = true;
  1135. }
  1136. }
  1137. for (const auto &sd : queue->teamb_members) {
  1138. if (map_getmapflag(sd->bl.m, MF_NOWARP)) {
  1139. clif_messagecolor(&sd->bl, color_table[COLOR_LIGHT_GREEN], msg_txt(sd, 337), false, SELF); // You can't apply to a battleground queue from this map.
  1140. bg_queue_leave(sd);
  1141. clif_bg_queue_entry_init(sd);
  1142. found = true;
  1143. }
  1144. }
  1145. if (found) {
  1146. queue->state = QUEUE_STATE_SETUP; // Set back to queueing state
  1147. queue->accepted_players = 0; // Reset acceptance count
  1148. // Free map to avoid creating a reservation delay
  1149. if (queue->map != nullptr) {
  1150. queue->map->isReserved = false;
  1151. queue->map = nullptr;
  1152. }
  1153. // Announce failure to remaining players
  1154. for (const auto &sd : queue->teama_members)
  1155. clif_messagecolor(&sd->bl, color_table[COLOR_LIGHT_GREEN], msg_txt(sd, 340), false, SELF); // Participants were unable to join. Delaying entry for more participants.
  1156. for (const auto &sd : queue->teamb_members)
  1157. clif_messagecolor(&sd->bl, color_table[COLOR_LIGHT_GREEN], msg_txt(sd, 340), false, SELF); // Participants were unable to join. Delaying entry for more participants.
  1158. }
  1159. return found;
  1160. }
  1161. /**
  1162. * Update the Battleground queue when the player accepts the invite
  1163. * @param queue: Battleground queue
  1164. * @param sd: Player data
  1165. */
  1166. void bg_queue_on_accept_invite(struct map_session_data *sd)
  1167. {
  1168. nullpo_retv(sd);
  1169. std::shared_ptr<s_battleground_queue> queue = bg_search_queue(sd->bg_queue_id);
  1170. if (queue == nullptr) {
  1171. ShowError("bg_queue_on_accept_invite: Couldn't find player %s in battlegrounds queue.\n", sd->status.name);
  1172. return;
  1173. }
  1174. queue->accepted_players++;
  1175. clif_bg_queue_ack_lobby(true, mapindex_id2name(queue->map->mapindex), mapindex_id2name(queue->map->mapindex), sd);
  1176. if (queue->state == QUEUE_STATE_ACTIVE) // Battleground is already active
  1177. bg_join_active(sd, queue);
  1178. else if (queue->state == QUEUE_STATE_SETUP_DELAY) {
  1179. if (queue->accepted_players == queue->required_players * 2) {
  1180. if (queue->tid_expire != INVALID_TIMER) {
  1181. delete_timer(queue->tid_expire, bg_on_ready_expire);
  1182. queue->tid_expire = INVALID_TIMER;
  1183. }
  1184. // Check player's current position for mapflag check
  1185. if (battle_config.bgqueue_nowarp_mapflag > 0 && bg_mapflag_check(queue))
  1186. return;
  1187. queue->tid_start = add_timer(gettick() + battleground_db.find(queue->id)->start_delay * 1000, bg_on_ready_start, 0, (intptr_t)queue->queue_id);
  1188. }
  1189. }
  1190. }
  1191. /**
  1192. * Begin the Battleground from the given queue
  1193. * @param queue: Battleground queue
  1194. */
  1195. void bg_queue_start_battleground(std::shared_ptr<s_battleground_queue> queue)
  1196. {
  1197. if (queue == nullptr)
  1198. return;
  1199. std::shared_ptr<s_battleground_type> bg = battleground_db.find(queue->id);
  1200. if (!bg) {
  1201. bg_queue_clear(queue, true);
  1202. ShowError("bg_queue_start_battleground: Could not find battleground ID %d in battlegrounds database.\n", queue->id);
  1203. return;
  1204. }
  1205. // Check player's current position for mapflag check
  1206. if (battle_config.bgqueue_nowarp_mapflag > 0 && bg_mapflag_check(queue))
  1207. return;
  1208. uint16 map_idx = queue->map->mapindex;
  1209. int bg_team_1 = bg_create(map_idx, &queue->map->team1);
  1210. int bg_team_2 = bg_create(map_idx, &queue->map->team2);
  1211. for (const auto &sd : queue->teama_members) {
  1212. clif_bg_queue_entry_init(sd);
  1213. bg_team_join(bg_team_1, sd, true);
  1214. }
  1215. for (const auto &sd : queue->teamb_members) {
  1216. clif_bg_queue_entry_init(sd);
  1217. bg_team_join(bg_team_2, sd, true);
  1218. }
  1219. mapreg_setreg(add_str(queue->map->team1.bg_id_var.c_str()), bg_team_1);
  1220. mapreg_setreg(add_str(queue->map->team2.bg_id_var.c_str()), bg_team_2);
  1221. npc_event_do(queue->map->bgcallscript.c_str());
  1222. queue->state = QUEUE_STATE_ACTIVE;
  1223. bg_queue_clear(queue, false);
  1224. }
  1225. /**
  1226. * Initialize a Battleground queue
  1227. * @param bg_id: Battleground ID
  1228. * @param req_players: Required amount of players
  1229. * @return s_battleground_queue*
  1230. */
  1231. static void bg_queue_create(int bg_id, int req_players)
  1232. {
  1233. auto queue = std::make_shared<s_battleground_queue>();
  1234. queue->queue_id = bg_queue_count++;
  1235. queue->id = bg_id;
  1236. queue->required_players = req_players;
  1237. queue->accepted_players = 0;
  1238. queue->tid_expire = INVALID_TIMER;
  1239. queue->tid_start = INVALID_TIMER;
  1240. queue->tid_requeue = INVALID_TIMER;
  1241. queue->state = QUEUE_STATE_SETUP;
  1242. bg_queues.push_back(queue);
  1243. }
  1244. /**
  1245. * Initialize the Battleground data
  1246. */
  1247. void do_init_battleground(void)
  1248. {
  1249. if (battle_config.feature_bgqueue) {
  1250. battleground_db.load();
  1251. for (const auto &bg : battleground_db)
  1252. bg_queue_create(bg.first, bg.second->required_players);
  1253. }
  1254. add_timer_func_list(bg_send_xy_timer, "bg_send_xy_timer");
  1255. add_timer_func_list(bg_on_ready_loopback, "bg_on_ready_loopback");
  1256. add_timer_func_list(bg_on_ready_expire, "bg_on_ready_expire");
  1257. add_timer_func_list(bg_on_ready_start, "bg_on_ready_start");
  1258. add_timer_interval(gettick() + battle_config.bg_update_interval, bg_send_xy_timer, 0, 0, battle_config.bg_update_interval);
  1259. }
  1260. /**
  1261. * Clear the Battleground data from memory
  1262. */
  1263. void do_final_battleground(void)
  1264. {
  1265. }