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.
 
 
 

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