Nie możesz wybrać więcej, niż 25 tematów Tematy muszą się zaczynać od litery lub cyfry, mogą zawierać myślniki ('-') i mogą mieć do 35 znaków.
 
 
 

720 wiersze
16 KiB

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