No puede seleccionar más de 25 temas Los temas deben comenzar con una letra o número, pueden incluir guiones ('-') y pueden tener hasta 35 caracteres de largo.
 
 
 

763 líneas
16 KiB

  1. let generator = require('../items/generator');
  2. let salvager = require('../items/salvager');
  3. let classes = require('../config/spirits');
  4. let mtx = require('../mtx/mtx');
  5. let factions = require('../config/factions');
  6. let itemEffects = require('../items/itemEffects');
  7. const events = require('../misc/events');
  8. const { isItemStackable } = require('./inventory/helpers');
  9. const transactions = require('../security/transactions');
  10. const getItem = require('./inventory/getItem');
  11. const dropBag = require('./inventory/dropBag');
  12. const useItem = require('./inventory/useItem');
  13. module.exports = {
  14. type: 'inventory',
  15. inventorySize: 50,
  16. items: [],
  17. blueprint: null,
  18. init: function (blueprint, isTransfer) {
  19. let items = blueprint.items || [];
  20. let iLen = items.length;
  21. //Spells should be sorted so they're EQ'd in the right order
  22. items.sort(function (a, b) {
  23. let aId = a.has('spellId') ? ~~a.spellId : 9999;
  24. let bId = b.has('spellId') ? ~~b.spellId : 9999;
  25. return (aId - bId);
  26. });
  27. for (let i = 0; i < iLen; i++) {
  28. let item = items[i];
  29. if ((item.pos >= this.inventorySize) || (item.eq))
  30. delete item.pos;
  31. while (item.name.indexOf('\'\'') > -1)
  32. item.name = item.name.replace('\'\'', '\'');
  33. }
  34. this.hookItemEvents(items);
  35. //Hack to skip attr checks on equip
  36. let oldFn = this.canEquipItem;
  37. this.canEquipItem = () => {
  38. return true;
  39. };
  40. for (let i = 0; i < iLen; i++) {
  41. let item = items[i];
  42. let pos = item.has('pos') ? item.pos : null;
  43. let newItem = this.getItem(item, true, true);
  44. newItem.pos = pos;
  45. }
  46. //Hack to skip attr checks on equip
  47. this.canEquipItem = oldFn.bind(this);
  48. if ((this.obj.player) && (!isTransfer) && (this.obj.stats.values.level === 1))
  49. this.getDefaultAbilities();
  50. delete blueprint.items;
  51. this.blueprint = blueprint;
  52. if (this.obj.equipment)
  53. this.obj.equipment.unequipAttrRqrGear();
  54. },
  55. transfer: function () {
  56. this.hookItemEvents();
  57. },
  58. save: function () {
  59. return {
  60. type: 'inventory',
  61. items: this.items.map(this.simplifyItem.bind(this))
  62. };
  63. },
  64. simplify: function (self) {
  65. if (!self)
  66. return null;
  67. return {
  68. type: 'inventory',
  69. items: this.items.map(this.simplifyItem.bind(this))
  70. };
  71. },
  72. simplifyItem: function (item) {
  73. let result = extend({}, item);
  74. if (result.effects) {
  75. result.effects = result.effects.map(e => ({
  76. factionId: e.factionId || null,
  77. text: e.text || null,
  78. properties: e.properties || null,
  79. mtx: e.mtx || null,
  80. type: e.type || null,
  81. rolls: e.rolls || null
  82. }));
  83. }
  84. let reputation = this.obj.reputation;
  85. if (result.factions) {
  86. result.factions = result.factions.map(function (f) {
  87. let res = {
  88. id: f.id,
  89. tier: f.tier,
  90. tierName: ['Hated', 'Hostile', 'Unfriendly', 'Neutral', 'Friendly', 'Honored', 'Revered', 'Exalted'][f.tier]
  91. };
  92. if (reputation) {
  93. let faction = reputation.getBlueprint(f.id);
  94. let factionTier = reputation.getTier(f.id);
  95. let noEquip = null;
  96. if (factionTier < f.tier)
  97. noEquip = true;
  98. res.name = faction.name;
  99. res.noEquip = noEquip;
  100. }
  101. return res;
  102. }, this);
  103. }
  104. return result;
  105. },
  106. update: function () {
  107. let items = this.items;
  108. let iLen = items.length;
  109. for (let i = 0; i < iLen; i++) {
  110. let item = items[i];
  111. if (!item.cd)
  112. continue;
  113. item.cd--;
  114. this.obj.syncer.setArray(true, 'inventory', 'getItems', item);
  115. }
  116. },
  117. learnAbility: function (itemId, runeSlot) {
  118. if (itemId.has('itemId')) {
  119. let msg = itemId;
  120. itemId = msg.itemId;
  121. runeSlot = msg.slot;
  122. }
  123. let item = this.findItem(itemId);
  124. let statValues = this.obj.stats.values;
  125. if (!item)
  126. return;
  127. else if (!item.spell) {
  128. item.eq = false;
  129. return;
  130. } else if (item.level > statValues.level) {
  131. item.eq = false;
  132. return;
  133. }
  134. let learnMsg = {
  135. success: true,
  136. item: item
  137. };
  138. this.obj.fireEvent('beforeLearnAbility', learnMsg);
  139. if (!learnMsg.success) {
  140. const message = learnMsg.msg || 'you cannot learn that ability';
  141. this.obj.social.notifySelf({ message });
  142. return;
  143. }
  144. let spellbook = this.obj.spellbook;
  145. if ((item.slot === 'twoHanded') || (item.slot === 'oneHanded'))
  146. runeSlot = 0;
  147. else if (!runeSlot) {
  148. runeSlot = 4;
  149. for (let i = 1; i <= 4; i++) {
  150. if (!this.items.some(j => (j.runeSlot === i))) {
  151. runeSlot = i;
  152. break;
  153. }
  154. }
  155. }
  156. let currentEq = this.items.find(i => (i.runeSlot === runeSlot));
  157. if (currentEq) {
  158. spellbook.removeSpellById(runeSlot);
  159. delete currentEq.eq;
  160. delete currentEq.runeSlot;
  161. this.setItemPosition(currentEq.id);
  162. this.obj.syncer.setArray(true, 'inventory', 'getItems', currentEq);
  163. }
  164. item.eq = true;
  165. item.runeSlot = runeSlot;
  166. delete item.pos;
  167. spellbook.addSpellFromRune(item.spell, runeSlot);
  168. this.obj.syncer.setArray(true, 'inventory', 'getItems', item);
  169. },
  170. activateMtx: function (itemId) {
  171. let item = this.findItem(itemId);
  172. if (!item)
  173. return;
  174. else if (item.type !== 'mtx') {
  175. delete item.active;
  176. return;
  177. }
  178. item.active = !item.active;
  179. this.obj.syncer.setArray(true, 'inventory', 'getItems', item);
  180. },
  181. splitStack: function (msg) {
  182. let item = this.findItem(msg.itemId);
  183. if (!item || !item.quantity || item.quantity <= msg.stackSize || msg.stackSize < 1 || item.quest)
  184. return;
  185. const hasSpace = this.hasSpace(item, true);
  186. if (!hasSpace) {
  187. this.notifyNoBagSpace();
  188. return;
  189. }
  190. let newItem = extend({}, item);
  191. item.quantity -= msg.stackSize;
  192. newItem.quantity = msg.stackSize;
  193. this.getItem(newItem, true, true);
  194. this.obj.syncer.setArray(true, 'inventory', 'getItems', item);
  195. },
  196. combineStacks: function (msg) {
  197. let fromItem = this.findItem(msg.fromId);
  198. let toItem = this.findItem(msg.toId);
  199. if ((!fromItem) || (!toItem))
  200. return;
  201. else if ((!isItemStackable(fromItem)) || (!isItemStackable(toItem)))
  202. return;
  203. toItem.quantity += fromItem.quantity;
  204. this.obj.syncer.setArray(true, 'inventory', 'getItems', toItem);
  205. this.destroyItem(fromItem.id, null, true);
  206. },
  207. useItem: function (itemId) {
  208. useItem(this, itemId);
  209. },
  210. unlearnAbility: function (itemId) {
  211. if (itemId.has('itemId'))
  212. itemId = itemId.itemId;
  213. let item = this.findItem(itemId);
  214. if (!item)
  215. return;
  216. else if (!item.spell) {
  217. item.eq = false;
  218. return;
  219. }
  220. let spellbook = this.obj.spellbook;
  221. spellbook.removeSpellById(item.runeSlot);
  222. delete item.eq;
  223. delete item.runeSlot;
  224. if (!item.slot)
  225. this.setItemPosition(itemId);
  226. this.obj.syncer.setArray(true, 'inventory', 'getItems', item);
  227. },
  228. stashItem: function (id) {
  229. let item = this.findItem(id);
  230. if (!item || item.quest || item.noStash)
  231. return;
  232. delete item.pos;
  233. let stash = this.obj.stash;
  234. if (!stash.active)
  235. return;
  236. let clonedItem = extend({}, item);
  237. const success = stash.deposit(clonedItem);
  238. if (!success)
  239. return;
  240. this.destroyItem(id, null, true);
  241. },
  242. salvageItem: function (id) {
  243. let item = this.findItem(id);
  244. if ((!item) || (item.material) || (item.quest) || (item.noSalvage) || (item.eq))
  245. return;
  246. let messages = [];
  247. let items = salvager.salvage(item);
  248. this.destroyItem(id);
  249. for (const material of items) {
  250. this.getItem(material, true, false, false, true);
  251. messages.push({
  252. className: 'q' + material.quality,
  253. message: 'salvage (' + material.name + ' x' + material.quantity + ')'
  254. });
  255. }
  256. this.obj.social.notifySelfArray(messages);
  257. },
  258. destroyItem: function (id, amount, force) {
  259. let item = this.findItem(id);
  260. if (!item || (item.noDestroy && !force))
  261. return;
  262. amount = amount || item.quantity;
  263. if (item.eq)
  264. this.obj.equipment.unequip(id);
  265. if ((item.quantity) && (amount)) {
  266. item.quantity -= amount;
  267. if (item.quantity <= 0) {
  268. this.items.spliceWhere(i => i.id === id);
  269. this.obj.syncer.setArray(true, 'inventory', 'destroyItems', id);
  270. } else
  271. this.obj.syncer.setArray(true, 'inventory', 'getItems', item);
  272. } else {
  273. this.items.spliceWhere(i => i.id === id);
  274. this.obj.syncer.setArray(true, 'inventory', 'destroyItems', id);
  275. this.obj.syncer.deleteFromArray(true, 'inventory', 'getItems', i => i.id === id);
  276. }
  277. this.obj.fireEvent('afterDestroyItem', item, amount);
  278. events.emit('afterPlayerDestroyItem', this.obj, item, amount);
  279. return item;
  280. },
  281. dropItem: function (id) {
  282. let item = this.findItem(id);
  283. if ((!item) || (item.noDrop) || (item.quest))
  284. return;
  285. if (item.has('quickSlot')) {
  286. this.obj.equipment.setQuickSlot({
  287. itemId: null,
  288. slot: item.quickSlot
  289. });
  290. delete item.quickSlot;
  291. }
  292. delete item.pos;
  293. //Find close open position
  294. let x = this.obj.x;
  295. let y = this.obj.y;
  296. let dropCell = this.obj.instance.physics.getOpenCellInArea(x - 1, y - 1, x + 1, y + 1);
  297. if (!dropCell)
  298. return;
  299. if (item.eq)
  300. this.obj.equipment.unequip(id);
  301. this.items.spliceWhere(i => i.id === id);
  302. this.obj.syncer.setArray(true, 'inventory', 'destroyItems', id);
  303. this.createBag(dropCell.x, dropCell.y, [item]);
  304. events.emit('afterPlayerDropItem', this.obj, item);
  305. },
  306. moveItem: function (msgs) {
  307. msgs.forEach(function (m) {
  308. let item = this.findItem(m.id);
  309. if (!item)
  310. return;
  311. item.pos = m.pos;
  312. }, this);
  313. },
  314. mailItem: async function (msg) {
  315. let item = this.findItem(msg.itemId);
  316. if (!item || item.noDrop || item.quest) {
  317. this.resolveCallback(msg);
  318. return;
  319. }
  320. delete item.pos;
  321. let res = await io.getAsync({
  322. key: msg.recipient,
  323. table: 'character'
  324. });
  325. if (!res) {
  326. this.resolveCallback(msg, 'Recipient does not exist');
  327. return;
  328. } else if (!this.findItem(msg.itemId))
  329. return;
  330. const resolveTrans = transactions.register();
  331. let blocked = false;
  332. if (res.components) {
  333. let social = res.components.find(f => f.type === 'social');
  334. if (social.blockedPlayers && social.blockedPlayers.includes(this.obj.name))
  335. blocked = true;
  336. }
  337. const mappedItem = this.simplifyItem(item);
  338. this.destroyItem(item.id);
  339. if (!blocked)
  340. await this.obj.instance.mail.sendMail(msg.recipient, [mappedItem]);
  341. this.resolveCallback(msg);
  342. resolveTrans();
  343. },
  344. hookItemEvents: function (items) {
  345. items = items || this.items;
  346. if (!items.push)
  347. items = [ items ];
  348. let iLen = items.length;
  349. for (let i = 0; i < iLen; i++) {
  350. let item = items[i];
  351. if (item.effects) {
  352. item.effects.forEach(function (e) {
  353. if (e.mtx) {
  354. let mtxUrl = mtx.get(e.mtx);
  355. let mtxModule = require('../' + mtxUrl);
  356. e.events = mtxModule.events;
  357. } else if (e.factionId) {
  358. let faction = factions.getFaction(e.factionId);
  359. let statGenerator = faction.uniqueStat;
  360. statGenerator.generate(item);
  361. } else {
  362. let effectUrl = itemEffects.get(e.type);
  363. try {
  364. let effectModule = require('../' + effectUrl);
  365. e.events = effectModule.events;
  366. if (effectModule.events.onGetText)
  367. e.text = effectModule.events.onGetText(item, e);
  368. } catch (error) {
  369. _.log(`Effect not found: ${e.type}`);
  370. }
  371. }
  372. });
  373. }
  374. if (!item.has('pos') && !item.eq) {
  375. let pos = i;
  376. for (let j = 0; j < iLen; j++) {
  377. if (!items.some(fj => (fj.pos === j))) {
  378. pos = j;
  379. break;
  380. }
  381. }
  382. item.pos = pos;
  383. } else if ((!item.eq) && (items.some(ii => ((ii !== item) && (ii.pos === item.pos))))) {
  384. let pos = item.pos;
  385. for (let j = 0; j < iLen; j++) {
  386. if (!items.some(fi => ((fi !== item) && (fi.pos === j)))) {
  387. pos = j;
  388. break;
  389. }
  390. }
  391. item.pos = pos;
  392. }
  393. }
  394. },
  395. setItemPosition: function (id) {
  396. let item = this.findItem(id);
  397. if (!item)
  398. return;
  399. let iSize = this.inventorySize;
  400. for (let i = 0; i < iSize; i++) {
  401. if (!this.items.some(j => (j.pos === i))) {
  402. item.pos = i;
  403. break;
  404. }
  405. }
  406. },
  407. sortInventory: function () {
  408. this.items
  409. .filter(i => !i.eq)
  410. .map(i => {
  411. //If we don't do this, [waist] goes before [undefined]
  412. const useSlot = i.slot ? i.slot : 'z';
  413. return {
  414. item: i,
  415. sortId: `${useSlot}${i.material}${i.quest}${i.spell}${i.quality}${i.level}${i.sprite}${i.id}`
  416. };
  417. })
  418. .sort((a, b) => {
  419. if (a.sortId < b.sortId)
  420. return 1;
  421. else if (a.sortId > b.sortId)
  422. return -1;
  423. return 0;
  424. })
  425. .forEach((i, index) => {
  426. i.item.pos = index;
  427. this.obj.syncer.setArray(true, 'inventory', 'getItems', this.simplifyItem(i.item));
  428. });
  429. },
  430. resolveCallback: function (msg, result) {
  431. let callbackId = msg.has('callbackId') ? msg.callbackId : msg;
  432. result = result || [];
  433. if (!callbackId)
  434. return;
  435. process.send({
  436. module: 'atlas',
  437. method: 'resolveCallback',
  438. msg: {
  439. id: callbackId,
  440. result: result
  441. }
  442. });
  443. },
  444. findItem: function (id) {
  445. if (id === null)
  446. return null;
  447. return this.items.find(i => i.id === id);
  448. },
  449. getDefaultAbilities: function () {
  450. let hasWeapon = this.items.some(i => {
  451. return (
  452. i.spell &&
  453. i.spell.rolls &&
  454. i.spell.rolls.has('damage') &&
  455. (
  456. i.slot === 'twoHanded' ||
  457. i.slot === 'oneHanded'
  458. )
  459. );
  460. });
  461. if (!hasWeapon) {
  462. let item = generator.generate({
  463. type: classes.weapons[this.obj.class],
  464. quality: 0,
  465. spellQuality: 0
  466. });
  467. item.worth = 0;
  468. item.eq = true;
  469. item.noSalvage = true;
  470. this.getItem(item);
  471. }
  472. classes.spells[this.obj.class].forEach(spellName => {
  473. let hasSpell = this.items.some(i => {
  474. return (
  475. i.spell &&
  476. i.spell.name.toLowerCase() === spellName
  477. );
  478. });
  479. if (!hasSpell) {
  480. let item = generator.generate({
  481. spell: true,
  482. quality: 0,
  483. spellName: spellName
  484. });
  485. item.worth = 0;
  486. item.eq = true;
  487. item.noSalvage = true;
  488. this.getItem(item);
  489. }
  490. });
  491. },
  492. createBag: function (x, y, items, ownerName) {
  493. let bagCell = 50;
  494. let topQuality = 0;
  495. let iLen = items.length;
  496. for (let i = 0; i < iLen; i++) {
  497. let quality = items[i].quality;
  498. items[i].fromMob = !!this.obj.mob;
  499. if (quality > topQuality)
  500. topQuality = ~~quality;
  501. }
  502. if (topQuality === 0)
  503. bagCell = 50;
  504. else if (topQuality === 1)
  505. bagCell = 51;
  506. else if (topQuality === 2)
  507. bagCell = 128;
  508. else if (topQuality === 3)
  509. bagCell = 52;
  510. else
  511. bagCell = 53;
  512. let obj = this.obj.instance.objects.buildObjects([{
  513. sheetName: 'objects',
  514. cell: bagCell,
  515. x: x,
  516. y: y,
  517. properties: {
  518. cpnChest: {
  519. ownerName: ownerName,
  520. ttl: 1710
  521. },
  522. cpnInventory: {
  523. items: extend([], items)
  524. }
  525. }
  526. }]);
  527. return obj;
  528. },
  529. hasSpace: function (item, noStack) {
  530. const itemArray = item ? [item] : [];
  531. return this.hasSpaceList(itemArray, noStack);
  532. },
  533. hasSpaceList: function (items, noStack) {
  534. if (this.inventorySize === -1)
  535. return true;
  536. let slots = this.inventorySize - this.obj.inventory.items.filter(f => !f.eq).length;
  537. for (const item of items) {
  538. if (isItemStackable(item) && (!noStack)) {
  539. let exists = this.items.find(owned => (owned.name === item.name) && (isItemStackable(owned)));
  540. if (exists)
  541. continue;
  542. }
  543. slots--;
  544. }
  545. return (slots >= 0);
  546. },
  547. getItem: function (item, hideMessage, noStack, hideAlert, createBagIfFull) {
  548. return getItem.call(this, this, ...arguments);
  549. },
  550. dropBag: function (ownerName, killSource) {
  551. dropBag(this, ownerName, killSource);
  552. },
  553. giveItems: function (obj, hideMessage) {
  554. let objInventory = obj.inventory;
  555. let items = this.items;
  556. let iLen = items.length;
  557. for (let i = 0; i < iLen; i++) {
  558. let item = items[i];
  559. if (objInventory.getItem(item, hideMessage)) {
  560. items.splice(i, 1);
  561. i--;
  562. iLen--;
  563. }
  564. }
  565. return !iLen;
  566. },
  567. fireEvent: function (event, args) {
  568. let items = this.items;
  569. let iLen = items.length;
  570. for (let i = 0; i < iLen; i++) {
  571. let item = items[i];
  572. if (!item.eq && !item.active) {
  573. if (event !== 'afterUnequipItem' || item !== args[0])
  574. continue;
  575. }
  576. let effects = item.effects;
  577. if (!effects)
  578. continue;
  579. let eLen = effects.length;
  580. for (let j = 0; j < eLen; j++) {
  581. let effect = effects[j];
  582. let effectEvent = effect.events[event];
  583. if (!effectEvent)
  584. continue;
  585. effectEvent.apply(this.obj, [item, ...args]);
  586. }
  587. }
  588. },
  589. clear: function () {
  590. delete this.items;
  591. this.items = [];
  592. },
  593. equipItemErrors: function (item) {
  594. let errors = [];
  595. if (!this.obj.player)
  596. return [];
  597. let stats = this.obj.stats.values;
  598. if (item.level > stats.level)
  599. errors.push('level');
  600. if ((item.requires) && (stats[item.requires[0].stat] < item.requires[0].value))
  601. errors.push(item.requires[0].stat);
  602. if (item.factions) {
  603. if (item.factions.some(function (f) {
  604. return f.noEquip;
  605. }))
  606. errors.push('faction');
  607. }
  608. return errors;
  609. },
  610. canEquipItem: function (item) {
  611. return (this.equipItemErrors(item).length === 0);
  612. },
  613. notifyNoBagSpace: function (message = 'Your bags are too full to loot any more items') {
  614. this.obj.social.notifySelf({ message });
  615. }
  616. };