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.
 
 
 

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