mercenary.cpp 20 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922
  1. // Copyright (c) rAthena Dev Teams - Licensed under GNU GPL
  2. // For more information, see LICENCE in the main folder
  3. #include "mercenary.hpp"
  4. #include <map>
  5. #include <math.h>
  6. #include <stdlib.h>
  7. #include <common/cbasetypes.hpp>
  8. #include <common/malloc.hpp>
  9. #include <common/mmo.hpp>
  10. #include <common/nullpo.hpp>
  11. #include <common/random.hpp>
  12. #include <common/showmsg.hpp>
  13. #include <common/strlib.hpp>
  14. #include <common/timer.hpp>
  15. #include <common/utilities.hpp>
  16. #include <common/utils.hpp>
  17. #include "clif.hpp"
  18. #include "intif.hpp"
  19. #include "itemdb.hpp"
  20. #include "log.hpp"
  21. #include "npc.hpp"
  22. #include "party.hpp"
  23. #include "pc.hpp"
  24. #include "trade.hpp"
  25. using namespace rathena;
  26. MercenaryDatabase mercenary_db;
  27. /**
  28. * Get View Data of Mercenary by Class ID
  29. * @param class_ The Class ID
  30. * @return View Data of Mercenary
  31. **/
  32. struct view_data *mercenary_get_viewdata( uint16 class_ ){
  33. std::shared_ptr<s_mercenary_db> db = mercenary_db.find(class_);
  34. if( db ){
  35. return &db->vd;
  36. }else{
  37. return nullptr;
  38. }
  39. }
  40. /**
  41. * Create a new Mercenary for Player
  42. * @param sd The Player
  43. * @param class_ Mercenary Class
  44. * @param lifetime Contract duration
  45. * @return false if failed, true otherwise
  46. **/
  47. bool mercenary_create(map_session_data *sd, uint16 class_, unsigned int lifetime) {
  48. nullpo_retr(false,sd);
  49. std::shared_ptr<s_mercenary_db> db = mercenary_db.find(class_);
  50. if (db == nullptr) {
  51. ShowError("mercenary_create: Unknown mercenary class %d.\n", class_);
  52. return false;
  53. }
  54. s_mercenary merc = {};
  55. merc.char_id = sd->status.char_id;
  56. merc.class_ = class_;
  57. merc.hp = db->status.max_hp;
  58. merc.sp = db->status.max_sp;
  59. merc.life_time = lifetime;
  60. // Request Char Server to create this mercenary
  61. intif_mercenary_create(&merc);
  62. return true;
  63. }
  64. /**
  65. * Get current Mercenary lifetime
  66. * @param md The Mercenary
  67. * @return The Lifetime
  68. **/
  69. t_tick mercenary_get_lifetime(s_mercenary_data *md) {
  70. if( md == NULL || md->contract_timer == INVALID_TIMER )
  71. return 0;
  72. const struct TimerData *td = get_timer(md->contract_timer);
  73. return (td != NULL) ? DIFF_TICK(td->tick, gettick()) : 0;
  74. }
  75. /**
  76. * Get Guild type of Mercenary
  77. * @param md Mercenary
  78. * @return enum e_MercGuildType
  79. **/
  80. e_MercGuildType mercenary_get_guild(s_mercenary_data *md){
  81. if( md == NULL || md->db == NULL )
  82. return NONE_MERC_GUILD;
  83. uint16 class_ = md->db->class_;
  84. if( class_ >= MERID_MER_ARCHER01 && class_ <= MERID_MER_ARCHER10 )
  85. return ARCH_MERC_GUILD;
  86. if( class_ >= MERID_MER_LANCER01 && class_ <= MERID_MER_LANCER10 )
  87. return SPEAR_MERC_GUILD;
  88. if( class_ >= MERID_MER_SWORDMAN01 && class_ <= MERID_MER_SWORDMAN10 )
  89. return SWORD_MERC_GUILD;
  90. return NONE_MERC_GUILD;
  91. }
  92. /**
  93. * Get Faith value of Mercenary
  94. * @param md Mercenary
  95. * @return the Faith value
  96. **/
  97. int mercenary_get_faith(s_mercenary_data *md) {
  98. map_session_data *sd;
  99. if( md == NULL || md->db == NULL || (sd = md->master) == NULL )
  100. return 0;
  101. e_MercGuildType guild = mercenary_get_guild(md);
  102. switch( guild ){
  103. case ARCH_MERC_GUILD:
  104. return sd->status.arch_faith;
  105. case SPEAR_MERC_GUILD:
  106. return sd->status.spear_faith;
  107. case SWORD_MERC_GUILD:
  108. return sd->status.sword_faith;
  109. case NONE_MERC_GUILD:
  110. default:
  111. return 0;
  112. }
  113. }
  114. /**
  115. * Set faith value of Mercenary
  116. * @param md The Mercenary
  117. * @param value Faith Value
  118. **/
  119. void mercenary_set_faith(s_mercenary_data *md, int value) {
  120. map_session_data *sd;
  121. if( md == NULL || md->db == NULL || (sd = md->master) == NULL )
  122. return;
  123. e_MercGuildType guild = mercenary_get_guild(md);
  124. int *faith = nullptr;
  125. switch( guild ){
  126. case ARCH_MERC_GUILD:
  127. faith = &sd->status.arch_faith;
  128. break;
  129. case SPEAR_MERC_GUILD:
  130. faith = &sd->status.spear_faith;
  131. break;
  132. case SWORD_MERC_GUILD:
  133. faith = &sd->status.sword_faith;
  134. break;
  135. case NONE_MERC_GUILD:
  136. return;
  137. }
  138. *faith += value;
  139. *faith = cap_value(*faith, 0, SHRT_MAX);
  140. clif_mercenary_updatestatus(sd, SP_MERCFAITH);
  141. }
  142. /**
  143. * Get Mercenary's calls
  144. * @param md Mercenary
  145. * @return Number of calls
  146. **/
  147. int mercenary_get_calls(s_mercenary_data *md) {
  148. map_session_data *sd;
  149. if( md == NULL || md->db == NULL || (sd = md->master) == NULL )
  150. return 0;
  151. e_MercGuildType guild = mercenary_get_guild(md);
  152. switch( guild ){
  153. case ARCH_MERC_GUILD:
  154. return sd->status.arch_calls;
  155. case SPEAR_MERC_GUILD:
  156. return sd->status.spear_calls;
  157. case SWORD_MERC_GUILD:
  158. return sd->status.sword_calls;
  159. case NONE_MERC_GUILD:
  160. default:
  161. return 0;
  162. }
  163. }
  164. /**
  165. * Set Mercenary's calls
  166. * @param md Mercenary
  167. * @param value
  168. **/
  169. void mercenary_set_calls(s_mercenary_data *md, int value) {
  170. map_session_data *sd;
  171. if( md == NULL || md->db == NULL || (sd = md->master) == NULL )
  172. return;
  173. e_MercGuildType guild = mercenary_get_guild(md);
  174. int *calls = nullptr;
  175. switch( guild ){
  176. case ARCH_MERC_GUILD:
  177. calls = &sd->status.arch_calls;
  178. break;
  179. case SPEAR_MERC_GUILD:
  180. calls = &sd->status.spear_calls;
  181. break;
  182. case SWORD_MERC_GUILD:
  183. calls = &sd->status.sword_calls;
  184. break;
  185. case NONE_MERC_GUILD:
  186. return;
  187. }
  188. *calls += value;
  189. *calls = cap_value(*calls, 0, INT_MAX);
  190. }
  191. /**
  192. * Save Mercenary data
  193. * @param md Mercenary
  194. **/
  195. void mercenary_save(s_mercenary_data *md) {
  196. md->mercenary.hp = md->battle_status.hp;
  197. md->mercenary.sp = md->battle_status.sp;
  198. md->mercenary.life_time = mercenary_get_lifetime(md);
  199. intif_mercenary_save(&md->mercenary);
  200. }
  201. /**
  202. * Ends contract of Mercenary
  203. **/
  204. static TIMER_FUNC(merc_contract_end){
  205. map_session_data *sd;
  206. s_mercenary_data *md;
  207. if( (sd = map_id2sd(id)) == NULL )
  208. return 1;
  209. if( (md = sd->md) == NULL )
  210. return 1;
  211. if( md->contract_timer != tid )
  212. {
  213. ShowError("merc_contract_end %d != %d.\n", md->contract_timer, tid);
  214. return 0;
  215. }
  216. md->contract_timer = INVALID_TIMER;
  217. mercenary_delete(md, 0); // Mercenary soldier's duty hour is over.
  218. return 0;
  219. }
  220. /**
  221. * Delete Mercenary
  222. * @param md Mercenary
  223. * @param reply
  224. **/
  225. int mercenary_delete(s_mercenary_data *md, int reply) {
  226. map_session_data *sd = md->master;
  227. md->mercenary.life_time = 0;
  228. mercenary_contract_stop(md);
  229. if( !sd )
  230. return unit_free(&md->bl, CLR_OUTSIGHT);
  231. if( md->devotion_flag )
  232. {
  233. md->devotion_flag = 0;
  234. status_change_end(&sd->bl, SC_DEVOTION);
  235. }
  236. switch( reply )
  237. {
  238. case 0: mercenary_set_faith(md, 1); break; // +1 Loyalty on Contract ends.
  239. case 1: mercenary_set_faith(md, -1); break; // -1 Loyalty on Mercenary killed
  240. }
  241. clif_mercenary_message(sd, reply);
  242. return unit_remove_map(&md->bl, CLR_OUTSIGHT);
  243. }
  244. /**
  245. * Stop contract of Mercenary
  246. * @param md Mercenary
  247. **/
  248. void mercenary_contract_stop(s_mercenary_data *md) {
  249. nullpo_retv(md);
  250. if( md->contract_timer != INVALID_TIMER )
  251. delete_timer(md->contract_timer, merc_contract_end);
  252. md->contract_timer = INVALID_TIMER;
  253. }
  254. /**
  255. * Init contract of Mercenary
  256. * @param md Mercenary
  257. **/
  258. void merc_contract_init(s_mercenary_data *md) {
  259. if( md->contract_timer == INVALID_TIMER )
  260. md->contract_timer = add_timer(gettick() + md->mercenary.life_time, merc_contract_end, md->master->bl.id, 0);
  261. md->regen.state.block = 0;
  262. }
  263. /**
  264. * Received mercenary data from char-serv
  265. * @param merc : mercenary datas
  266. * @param flag : if inter-serv request was sucessfull
  267. * @return false:failure, true:sucess
  268. */
  269. bool mercenary_recv_data(s_mercenary *merc, bool flag)
  270. {
  271. map_session_data *sd;
  272. t_tick tick = gettick();
  273. if( (sd = map_charid2sd(merc->char_id)) == NULL )
  274. return false;
  275. std::shared_ptr<s_mercenary_db> db = mercenary_db.find(merc->class_);
  276. if( !flag || !db ){ // Not created - loaded - DB info
  277. sd->status.mer_id = 0;
  278. return false;
  279. }
  280. s_mercenary_data *md;
  281. if( !sd->md ) {
  282. sd->md = md = (s_mercenary_data*)aCalloc(1,sizeof(s_mercenary_data));
  283. md->bl.type = BL_MER;
  284. md->bl.id = npc_get_new_npc_id();
  285. md->devotion_flag = 0;
  286. md->master = sd;
  287. md->db = db;
  288. memcpy(&md->mercenary, merc, sizeof(s_mercenary));
  289. status_set_viewdata(&md->bl, md->mercenary.class_);
  290. status_change_init(&md->bl);
  291. unit_dataset(&md->bl);
  292. md->ud.dir = sd->ud.dir;
  293. md->bl.m = sd->bl.m;
  294. md->bl.x = sd->bl.x;
  295. md->bl.y = sd->bl.y;
  296. unit_calc_pos(&md->bl, sd->bl.x, sd->bl.y, sd->ud.dir);
  297. md->bl.x = md->ud.to_x;
  298. md->bl.y = md->ud.to_y;
  299. // Ticks need to be initialized before adding bl to map_addiddb
  300. md->regen.tick.hp = tick;
  301. md->regen.tick.sp = tick;
  302. map_addiddb(&md->bl);
  303. status_calc_mercenary(md, SCO_FIRST);
  304. md->contract_timer = INVALID_TIMER;
  305. md->masterteleport_timer = INVALID_TIMER;
  306. merc_contract_init(md);
  307. } else {
  308. memcpy(&sd->md->mercenary, merc, sizeof(s_mercenary));
  309. md = sd->md;
  310. }
  311. if( sd->status.mer_id == 0 )
  312. mercenary_set_calls(md, 1);
  313. sd->status.mer_id = merc->mercenary_id;
  314. if( md && md->bl.prev == NULL && sd->bl.prev != NULL ) {
  315. if(map_addblock(&md->bl))
  316. return false;
  317. clif_spawn(&md->bl);
  318. clif_mercenary_info(sd);
  319. clif_mercenary_skillblock(sd);
  320. }
  321. return true;
  322. }
  323. /**
  324. * Heals Mercenary
  325. * @param md Mercenary
  326. * @param hp HP amount
  327. * @param sp SP amount
  328. **/
  329. void mercenary_heal(s_mercenary_data *md, int hp, int sp) {
  330. if (md->master == NULL)
  331. return;
  332. if( hp )
  333. clif_mercenary_updatestatus(md->master, SP_HP);
  334. if( sp )
  335. clif_mercenary_updatestatus(md->master, SP_SP);
  336. }
  337. /**
  338. * Delete Mercenary
  339. * @param md: Mercenary
  340. * @return false for status_damage
  341. */
  342. bool mercenary_dead(s_mercenary_data *md) {
  343. mercenary_delete(md, 1);
  344. return false;
  345. }
  346. /**
  347. * Gives bonus to Mercenary
  348. * @param md Mercenary
  349. **/
  350. void mercenary_killbonus(s_mercenary_data *md) {
  351. std::vector<sc_type> scs = { SC_MERC_FLEEUP, SC_MERC_ATKUP, SC_MERC_HPUP, SC_MERC_SPUP, SC_MERC_HITUP };
  352. sc_start(&md->bl,&md->bl, util::vector_random(scs), 100, rnd_value(1, 5), 600000);
  353. }
  354. /**
  355. * Mercenary does kill
  356. * @param md Mercenary
  357. **/
  358. void mercenary_kills(s_mercenary_data *md){
  359. if(md->mercenary.kill_count <= (INT_MAX-1)) //safe cap to INT_MAX
  360. md->mercenary.kill_count++;
  361. if( (md->mercenary.kill_count % 50) == 0 )
  362. {
  363. mercenary_set_faith(md, 1);
  364. mercenary_killbonus(md);
  365. }
  366. if( md->master )
  367. clif_mercenary_updatestatus(md->master, SP_MERCKILLS);
  368. }
  369. /**
  370. * Check if Mercenary has the skill
  371. * @param md Mercenary
  372. * @param skill_id The skill
  373. * @return Skill Level or 0 if Mercenary doesn't have the skill
  374. **/
  375. uint16 mercenary_checkskill(s_mercenary_data *md, uint16 skill_id) {
  376. if (!md || !md->db)
  377. return 0;
  378. auto skill_level = util::umap_find(md->db->skill, skill_id);
  379. return skill_level ? *skill_level : 0;
  380. }
  381. const std::string MercenaryDatabase::getDefaultLocation() {
  382. return std::string(db_path) + "/mercenary_db.yml";
  383. }
  384. /*
  385. * Reads and parses an entry from the mercenary_db.
  386. * @param node: YAML node containing the entry.
  387. * @return count of successfully parsed rows
  388. */
  389. uint64 MercenaryDatabase::parseBodyNode(const ryml::NodeRef& node) {
  390. uint32 id;
  391. if (!this->asUInt32(node, "Id", id))
  392. return 0;
  393. std::shared_ptr<s_mercenary_db> mercenary = this->find(id);
  394. bool exists = mercenary != nullptr;
  395. if (!exists) {
  396. if (!this->nodesExist(node, { "AegisName", "Name" }))
  397. return 0;
  398. mercenary = std::make_shared<s_mercenary_db>();
  399. mercenary->class_ = id;
  400. }
  401. if (this->nodeExists(node, "AegisName")) {
  402. std::string name;
  403. if (!this->asString(node, "AegisName", name))
  404. return 0;
  405. if (name.size() > NAME_LENGTH) {
  406. this->invalidWarning(node["AegisName"], "AegisName \"%s\" exceeds maximum of %d characters, capping...\n", name.c_str(), NAME_LENGTH - 1);
  407. }
  408. name.resize(NAME_LENGTH);
  409. mercenary->sprite = name;
  410. }
  411. if (this->nodeExists(node, "Name")) {
  412. std::string name;
  413. if (!this->asString(node, "Name", name))
  414. return 0;
  415. if (name.size() > NAME_LENGTH) {
  416. this->invalidWarning(node["Name"], "Name \"%s\" exceeds maximum of %d characters, capping...\n", name.c_str(), NAME_LENGTH - 1);
  417. }
  418. name.resize(NAME_LENGTH);
  419. mercenary->name = name;
  420. }
  421. if (this->nodeExists(node, "Level")) {
  422. uint16 level;
  423. if (!this->asUInt16(node, "Level", level))
  424. return 0;
  425. if (level > MAX_LEVEL) {
  426. this->invalidWarning(node["Level"], "Level %d exceeds MAX_LEVEL, capping to %d.\n", level, MAX_LEVEL);
  427. level = MAX_LEVEL;
  428. }
  429. mercenary->lv = level;
  430. } else {
  431. if (!exists)
  432. mercenary->lv = 1;
  433. }
  434. if (this->nodeExists(node, "Hp")) {
  435. uint32 hp;
  436. if (!this->asUInt32(node, "Hp", hp))
  437. return 0;
  438. mercenary->status.max_hp = hp;
  439. } else {
  440. if (!exists)
  441. mercenary->status.max_hp = 1;
  442. }
  443. if (this->nodeExists(node, "Sp")) {
  444. uint32 sp;
  445. if (!this->asUInt32(node, "Sp", sp))
  446. return 0;
  447. mercenary->status.max_sp = sp;
  448. } else {
  449. if (!exists)
  450. mercenary->status.max_sp = 1;
  451. }
  452. if (this->nodeExists(node, "Attack")) {
  453. uint16 atk;
  454. if (!this->asUInt16(node, "Attack", atk))
  455. return 0;
  456. mercenary->status.rhw.atk = atk;
  457. } else {
  458. if (!exists)
  459. mercenary->status.rhw.atk = 0;
  460. }
  461. if (this->nodeExists(node, "Attack2")) {
  462. uint16 atk;
  463. if (!this->asUInt16(node, "Attack2", atk))
  464. return 0;
  465. mercenary->status.rhw.atk2 = mercenary->status.rhw.atk + atk;
  466. } else {
  467. if (!exists)
  468. mercenary->status.rhw.atk2 = mercenary->status.rhw.atk;
  469. }
  470. if (this->nodeExists(node, "Defense")) {
  471. int32 def;
  472. if (!this->asInt32(node, "Defense", def))
  473. return 0;
  474. if (def < DEFTYPE_MIN || def > DEFTYPE_MAX) {
  475. this->invalidWarning(node["Defense"], "Invalid defense %d, capping...\n", def);
  476. def = cap_value(def, DEFTYPE_MIN, DEFTYPE_MAX);
  477. }
  478. mercenary->status.def = static_cast<defType>(def);
  479. } else {
  480. if (!exists)
  481. mercenary->status.def = 0;
  482. }
  483. if (this->nodeExists(node, "MagicDefense")) {
  484. int32 def;
  485. if (!this->asInt32(node, "MagicDefense", def))
  486. return 0;
  487. if (def < DEFTYPE_MIN || def > DEFTYPE_MAX) {
  488. this->invalidWarning(node["MagicDefense"], "Invalid magic defense %d, capping...\n", def);
  489. def = cap_value(def, DEFTYPE_MIN, DEFTYPE_MAX);
  490. }
  491. mercenary->status.mdef = static_cast<defType>(def);
  492. } else {
  493. if (!exists)
  494. mercenary->status.mdef = 0;
  495. }
  496. if (this->nodeExists(node, "Str")) {
  497. uint16 stat;
  498. if (!this->asUInt16(node, "Str", stat))
  499. return 0;
  500. mercenary->status.str = stat;
  501. } else {
  502. if (!exists)
  503. mercenary->status.str = 1;
  504. }
  505. if (this->nodeExists(node, "Agi")) {
  506. uint16 stat;
  507. if (!this->asUInt16(node, "Agi", stat))
  508. return 0;
  509. mercenary->status.agi = stat;
  510. } else {
  511. if (!exists)
  512. mercenary->status.agi = 1;
  513. }
  514. if (this->nodeExists(node, "Vit")) {
  515. uint16 stat;
  516. if (!this->asUInt16(node, "Vit", stat))
  517. return 0;
  518. mercenary->status.vit = stat;
  519. } else {
  520. if (!exists)
  521. mercenary->status.vit = 1;
  522. }
  523. if (this->nodeExists(node, "Int")) {
  524. uint16 stat;
  525. if (!this->asUInt16(node, "Int", stat))
  526. return 0;
  527. mercenary->status.int_ = stat;
  528. } else {
  529. if (!exists)
  530. mercenary->status.int_ = 1;
  531. }
  532. if (this->nodeExists(node, "Dex")) {
  533. uint16 stat;
  534. if (!this->asUInt16(node, "Dex", stat))
  535. return 0;
  536. mercenary->status.dex = stat;
  537. } else {
  538. if (!exists)
  539. mercenary->status.dex = 1;
  540. }
  541. if (this->nodeExists(node, "Luk")) {
  542. uint16 stat;
  543. if (!this->asUInt16(node, "Luk", stat))
  544. return 0;
  545. mercenary->status.luk = stat;
  546. } else {
  547. if (!exists)
  548. mercenary->status.luk = 1;
  549. }
  550. if (this->nodeExists(node, "AttackRange")) {
  551. uint16 range;
  552. if (!this->asUInt16(node, "AttackRange", range))
  553. return 0;
  554. mercenary->status.rhw.range = range;
  555. } else {
  556. if (!exists)
  557. mercenary->status.rhw.range = 0;
  558. }
  559. if (this->nodeExists(node, "SkillRange")) {
  560. uint16 range;
  561. if (!this->asUInt16(node, "SkillRange", range))
  562. return 0;
  563. mercenary->range2 = range;
  564. } else {
  565. if (!exists)
  566. mercenary->range2 = 0;
  567. }
  568. if (this->nodeExists(node, "ChaseRange")) {
  569. uint16 range;
  570. if (!this->asUInt16(node, "ChaseRange", range))
  571. return 0;
  572. mercenary->range3 = range;
  573. } else {
  574. if (!exists)
  575. mercenary->range3 = 0;
  576. }
  577. if (this->nodeExists(node, "Size")) {
  578. std::string size;
  579. if (!this->asString(node, "Size", size))
  580. return 0;
  581. std::string size_constant = "Size_" + size;
  582. int64 constant;
  583. if (!script_get_constant(size_constant.c_str(), &constant)) {
  584. this->invalidWarning(node["Size"], "Unknown mercenary size %s, defaulting to Small.\n", size.c_str());
  585. constant = SZ_SMALL;
  586. }
  587. if (constant < SZ_SMALL || constant > SZ_BIG) {
  588. this->invalidWarning(node["Size"], "Invalid mercenary size %s, defaulting to Small.\n", size.c_str());
  589. constant = SZ_SMALL;
  590. }
  591. mercenary->status.size = static_cast<e_size>(constant);
  592. } else {
  593. if (!exists)
  594. mercenary->status.size = SZ_SMALL;
  595. }
  596. if (this->nodeExists(node, "Race")) {
  597. std::string race;
  598. if (!this->asString(node, "Race", race))
  599. return 0;
  600. std::string race_constant = "RC_" + race;
  601. int64 constant;
  602. if (!script_get_constant(race_constant.c_str(), &constant)) {
  603. this->invalidWarning(node["Race"], "Unknown mercenary race %s, defaulting to Formless.\n", race.c_str());
  604. constant = RC_FORMLESS;
  605. }
  606. if (!CHK_RACE(constant)) {
  607. this->invalidWarning(node["Race"], "Invalid mercenary race %s, defaulting to Formless.\n", race.c_str());
  608. constant = RC_FORMLESS;
  609. }
  610. mercenary->status.race = static_cast<e_race>(constant);
  611. } else {
  612. if (!exists)
  613. mercenary->status.race = RC_FORMLESS;
  614. }
  615. if (this->nodeExists(node, "Element")) {
  616. std::string ele;
  617. if (!this->asString(node, "Element", ele))
  618. return 0;
  619. std::string ele_constant = "ELE_" + ele;
  620. int64 constant;
  621. if (!script_get_constant(ele_constant.c_str(), &constant)) {
  622. this->invalidWarning(node["Element"], "Unknown mercenary element %s, defaulting to Neutral.\n", ele.c_str());
  623. constant = ELE_NEUTRAL;
  624. }
  625. if (!CHK_ELEMENT(constant)) {
  626. this->invalidWarning(node["Element"], "Invalid mercenary element %s, defaulting to Neutral.\n", ele.c_str());
  627. constant = ELE_NEUTRAL;
  628. }
  629. mercenary->status.def_ele = static_cast<e_element>(constant);
  630. } else {
  631. if (!exists)
  632. mercenary->status.def_ele = ELE_NEUTRAL;
  633. }
  634. if (this->nodeExists(node, "ElementLevel")) {
  635. uint16 level;
  636. if (!this->asUInt16(node, "ElementLevel", level))
  637. return 0;
  638. if (!CHK_ELEMENT_LEVEL(level)) {
  639. this->invalidWarning(node["ElementLevel"], "Invalid mercenary element level %hu, defaulting to 1.\n", level);
  640. level = 1;
  641. }
  642. mercenary->status.ele_lv = static_cast<uint8>(level);
  643. } else {
  644. if (!exists)
  645. mercenary->status.ele_lv = 1;
  646. }
  647. if (this->nodeExists(node, "WalkSpeed")) {
  648. uint16 speed;
  649. if (!this->asUInt16(node, "WalkSpeed", speed))
  650. return 0;
  651. if (speed < MIN_WALK_SPEED || speed > MAX_WALK_SPEED) {
  652. this->invalidWarning(node["WalkSpeed"], "Invalid mercenary walk speed %hu, capping...\n", speed);
  653. speed = cap_value(speed, MIN_WALK_SPEED, MAX_WALK_SPEED);
  654. }
  655. mercenary->status.speed = speed;
  656. } else {
  657. if (!exists)
  658. mercenary->status.speed = DEFAULT_WALK_SPEED;
  659. }
  660. if (this->nodeExists(node, "AttackDelay")) {
  661. uint16 speed;
  662. if (!this->asUInt16(node, "AttackDelay", speed))
  663. return 0;
  664. mercenary->status.adelay = cap_value(speed, 0, 4000);
  665. } else {
  666. if (!exists)
  667. mercenary->status.adelay = 4000;
  668. }
  669. if (this->nodeExists(node, "AttackMotion")) {
  670. uint16 speed;
  671. if (!this->asUInt16(node, "AttackMotion", speed))
  672. return 0;
  673. mercenary->status.amotion = cap_value(speed, 0, 2000);
  674. } else {
  675. if (!exists)
  676. mercenary->status.amotion = 2000;
  677. }
  678. if (this->nodeExists(node, "DamageMotion")) {
  679. uint16 speed;
  680. if (!this->asUInt16(node, "DamageMotion", speed))
  681. return 0;
  682. mercenary->status.dmotion = speed;
  683. } else {
  684. if (!exists)
  685. mercenary->status.dmotion = 0;
  686. }
  687. mercenary->status.aspd_rate = 1000;
  688. if (this->nodeExists(node, "Skills")) {
  689. const ryml::NodeRef& skillsNode = node["Skills"];
  690. for (const ryml::NodeRef& skill : skillsNode) {
  691. std::string skill_name;
  692. if (!this->asString(skill, "Name", skill_name))
  693. return 0;
  694. uint16 skill_id = skill_name2id(skill_name.c_str());
  695. if (skill_id == 0) {
  696. this->invalidWarning(skill["Name"], "Invalid skill %s, skipping.\n", skill_name.c_str());
  697. return 0;
  698. }
  699. if (!SKILL_CHK_MERC(skill_id)) {
  700. this->invalidWarning(skill["Name"], "Skill %s (%u) is out of the mercenary skill range [%u-%u], skipping.\n", skill_name.c_str(), skill_id, MC_SKILLBASE, MC_SKILLBASE + MAX_MERCSKILL - 1);
  701. return 0;
  702. }
  703. uint16 level;
  704. if (!this->asUInt16(skill, "MaxLevel", level))
  705. return 0;
  706. if (level == 0) {
  707. if (mercenary->skill.erase(skill_id) == 0)
  708. this->invalidWarning(skill["Name"], "Failed to remove %s, the skill doesn't exist for mercenary %hu.\n", skill_name.c_str(), id);
  709. continue;
  710. }
  711. mercenary->skill[skill_id] = level;
  712. }
  713. }
  714. if (!exists)
  715. this->put(id, mercenary);
  716. return true;
  717. }
  718. /**
  719. * Init Mercenary datas
  720. **/
  721. void do_init_mercenary(void){
  722. mercenary_db.load();
  723. add_timer_func_list(merc_contract_end, "merc_contract_end");
  724. }
  725. /**
  726. * Do Final Mercenary datas
  727. **/
  728. void do_final_mercenary(void){
  729. mercenary_db.clear();
  730. }