Não pode escolher mais do que 25 tópicos Os tópicos devem começar com uma letra ou um número, podem incluir traços ('-') e podem ter até 35 caracteres.
 
 
 

717 linhas
16 KiB

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