You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

inventory.js 16 KiB

7 years ago
7 years ago
7 years ago
7 years ago
7 years ago
7 years ago
7 years ago
7 years ago
6 years ago
6 years ago
6 years ago
6 years ago
6 years ago
7 years ago
5 years ago
7 years ago
5 years ago
7 years ago
5 years ago
7 years ago
5 years ago
7 years ago
7 years ago
7 years ago
5 years ago
6 years ago
7 years ago
5 years ago
7 years ago
7 years ago
5 years ago
7 years ago
7 years ago
7 years ago
7 years ago
5 years ago
7 years ago
5 years ago
6 years ago
7 years ago
7 years ago
5 years ago
6 years ago
5 years ago
7 years ago
5 years ago
7 years ago
7 years ago
5 years ago
7 years ago
6 years ago
6 years ago
5 years ago
6 years ago
7 years ago
6 years ago
7 years ago
7 years ago
5 years ago
7 years ago
5 years ago
7 years ago
5 years ago
5 years ago
5 years ago
6 years ago
7 years ago
6 years ago
7 years ago
6 years ago
7 years ago
5 years ago
7 years ago
6 years ago
5 years ago
6 years ago
5 years ago
5 years ago
7 years ago
7 years ago
7 years ago
7 years ago
7 years ago
6 years ago
7 years ago
7 years ago
6 years ago
5 years ago
6 years ago
6 years ago
6 years ago
5 years ago
6 years ago
6 years ago
6 years ago
6 years ago
6 years ago
6 years ago
6 years ago
6 years ago
5 years ago
6 years ago
6 years ago
6 years ago
5 years ago
6 years ago
5 years ago
6 years ago
6 years ago
7 years ago
7 years ago
5 years ago
7 years ago
6 years ago
7 years ago
5 years ago
7 years ago
7 years ago
5 years ago
5 years ago
5 years ago
5 years ago
5 years ago
6 years ago
5 years ago
5 years ago
5 years ago
5 years ago
6 years ago
7 years ago
7 years ago
7 years ago
7 years ago
7 years ago
7 years ago
6 years ago
7 years ago
7 years ago
7 years ago
7 years ago
6 years ago
7 years ago
5 years ago
5 years ago
5 years ago
7 years ago
5 years ago
5 years ago
7 years ago
5 years ago
7 years ago
7 years ago
7 years ago
7 years ago
7 years ago
7 years ago
7 years ago
7 years ago
7 years ago
123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645
  1. define([
  2. 'js/system/events',
  3. 'js/system/client',
  4. 'html!ui/templates/inventory/template',
  5. 'css!ui/templates/inventory/styles',
  6. 'html!ui/templates/inventory/templateItem',
  7. 'html!ui/templates/inventory/templateTooltip',
  8. 'js/input'
  9. ], function (
  10. events,
  11. client,
  12. template,
  13. styles,
  14. tplItem,
  15. tplTooltip,
  16. input
  17. ) {
  18. return {
  19. tpl: template,
  20. centered: true,
  21. items: [],
  22. shiftDown: false,
  23. ctrlDown: false,
  24. dragItem: null,
  25. dragEl: null,
  26. hoverCell: null,
  27. modal: true,
  28. oldSpellsZIndex: 0,
  29. postRender: function () {
  30. this.onEvent('onGetItems', this.onGetItems.bind(this));
  31. this.onEvent('onDestroyItems', this.onDestroyItems.bind(this));
  32. this.onEvent('onShowInventory', this.toggle.bind(this));
  33. this.onEvent('onKeyDown', this.onKeyDown.bind(this));
  34. this.onEvent('onKeyUp', this.onKeyUp.bind(this));
  35. this.find('.grid')
  36. .on('mousemove', this.onMouseMove.bind(this))
  37. .on('mouseleave', this.onMouseDown.bind(this, null, null, false));
  38. this.find('.split-box .amount')
  39. .on('mousewheel', this.onChangeStackAmount.bind(this))
  40. .on('input', this.onEnterStackAmount.bind(this));
  41. this.find('.split-box').on('click', this.splitStackEnd.bind(this, true));
  42. this.find('.split-box .btnSplit').on('click', this.splitStackEnd.bind(this, null));
  43. this.find('.split-box .btnLess').on('click', this.onChangeStackAmount.bind(this, null, -1));
  44. this.find('.split-box .btnMore').on('click', this.onChangeStackAmount.bind(this, null, 1));
  45. },
  46. build: function () {
  47. let container = this.el.find('.grid')
  48. .empty();
  49. let items = this.items
  50. .filter(function (item) {
  51. return !item.eq;
  52. });
  53. let iLen = Math.max(items.length, 50);
  54. let rendered = [];
  55. for (let i = 0; i < iLen; i++) {
  56. let itemEl = null;
  57. let item = items.find(f => (f.pos !== null && f.pos === i));
  58. if (!item) {
  59. itemEl = $(tplItem)
  60. .appendTo(container);
  61. itemEl
  62. .on('mouseup', this.onMouseDown.bind(this, null, null, false))
  63. .on('mousemove', this.onHover.bind(this, itemEl, item))
  64. .on('mouseleave', this.hideTooltip.bind(this, itemEl, item))
  65. .children()
  66. .remove();
  67. continue;
  68. } else
  69. rendered.push(item);
  70. let imgX = -item.sprite[0] * 64;
  71. let imgY = -item.sprite[1] * 64;
  72. itemEl = $(tplItem)
  73. .appendTo(container);
  74. let spritesheet = item.spritesheet || '../../../images/items.png';
  75. if (!item.spritesheet) {
  76. if (item.material)
  77. spritesheet = '../../../images/materials.png';
  78. else if (item.quest)
  79. spritesheet = '../../../images/questItems.png';
  80. else if (item.type === 'consumable')
  81. spritesheet = '../../../images/consumables.png';
  82. }
  83. itemEl
  84. .data('item', item)
  85. .on('click', this.onClick.bind(this, item))
  86. .on('mousedown', this.onMouseDown.bind(this, itemEl, item, true))
  87. .on('mouseup', this.onMouseDown.bind(this, null, null, false))
  88. .on('mousemove', this.onHover.bind(this, itemEl, item))
  89. .on('mouseleave', this.hideTooltip.bind(this, itemEl, item))
  90. .find('.icon')
  91. .css('background', 'url(' + spritesheet + ') ' + imgX + 'px ' + imgY + 'px')
  92. .on('contextmenu', this.showContext.bind(this, item));
  93. if (item.quantity > 1 || item.eq || item.active || item.has('quickSlot')) {
  94. let elQuantity = itemEl.find('.quantity');
  95. let txtQuantity = item.quantity;
  96. if (!txtQuantity)
  97. txtQuantity = item.has('quickSlot') ? 'QS' : 'EQ';
  98. elQuantity.html(txtQuantity);
  99. //If the item doesn't have a quantity and we reach this point
  100. //it must mean that it's active, EQd or QSd
  101. if (!item.quantity)
  102. itemEl.addClass('eq');
  103. } else if (item.isNew) {
  104. itemEl.addClass('new');
  105. itemEl.find('.quantity').html('NEW');
  106. }
  107. }
  108. },
  109. onClick: function (item) {
  110. let msg = {
  111. item: item,
  112. success: true
  113. };
  114. events.emit('beforeInventoryClickItem', msg);
  115. if (!msg.success)
  116. return;
  117. if (!this.ctrlDown)
  118. return;
  119. client.request({
  120. cpn: 'social',
  121. method: 'chat',
  122. data: {
  123. message: '{' + item.name + '}',
  124. item: item
  125. }
  126. });
  127. },
  128. onMouseDown: function (el, item, down, e) {
  129. if (e.button !== 0)
  130. return;
  131. if (down) {
  132. this.dragEl = el.clone()
  133. .appendTo(this.find('.grid'))
  134. .hide()
  135. .on('mouseup', this.onMouseDown.bind(this, null, null, false))
  136. .addClass('dragging');
  137. this.dragItem = el;
  138. events.emit('onHideItemTooltip', this.hoverItem);
  139. this.hoverItem = null;
  140. } else if (this.dragItem) {
  141. let method = 'moveItem';
  142. if ((this.hoverCell) && (this.hoverCell[0] !== this.dragItem[0])) {
  143. let placeholder = $('<div></div>')
  144. .insertAfter(this.dragItem);
  145. this.dragItem.insertBefore(this.hoverCell);
  146. this.hoverCell.insertBefore(placeholder);
  147. placeholder.remove();
  148. let msgs = [{
  149. id: this.dragItem.data('item').id,
  150. pos: this.dragItem.index()
  151. }];
  152. this.items.find(function (i) {
  153. return (i.id === this.dragItem.data('item').id);
  154. }, this).pos = this.dragItem.index();
  155. let hoverCellItem = this.hoverCell.data('item');
  156. if (hoverCellItem) {
  157. if ((hoverCellItem.name !== this.dragItem.data('item').name) || (!hoverCellItem.quantity)) {
  158. msgs.push({
  159. id: hoverCellItem.id,
  160. pos: this.hoverCell.index()
  161. });
  162. this.items.find(function (i) {
  163. return (i.id === hoverCellItem.id);
  164. }, this).pos = this.hoverCell.index();
  165. } else {
  166. method = 'combineStacks';
  167. msgs = {
  168. fromId: this.dragItem.data('item').id,
  169. toId: hoverCellItem.id
  170. };
  171. }
  172. }
  173. client.request({
  174. cpn: 'player',
  175. method: 'performAction',
  176. data: {
  177. cpn: 'inventory',
  178. method: method,
  179. data: msgs
  180. }
  181. });
  182. this.build();
  183. }
  184. this.dragItem = null;
  185. this.dragEl.remove();
  186. this.dragEl = null;
  187. this.hoverCell = null;
  188. this.find('.hover').removeClass('hover');
  189. }
  190. },
  191. onMouseMove: function (e) {
  192. if (!this.dragEl)
  193. return;
  194. let offset = this.find('.grid').offset();
  195. this.dragEl.css({
  196. left: e.clientX - offset.left - 40,
  197. top: e.clientY - offset.top - 40,
  198. display: 'block'
  199. });
  200. },
  201. showContext: function (item, e) {
  202. let menuItems = {
  203. drop: {
  204. text: 'drop',
  205. callback: this.performItemAction.bind(this, item, 'dropItem')
  206. },
  207. destroy: {
  208. text: 'destroy',
  209. callback: this.performItemAction.bind(this, item, 'destroyItem')
  210. },
  211. salvage: {
  212. text: 'salvage',
  213. callback: this.performItemAction.bind(this, item, 'salvageItem')
  214. },
  215. stash: {
  216. text: 'stash',
  217. callback: this.performItemAction.bind(this, item, 'stashItem')
  218. },
  219. learn: {
  220. text: 'learn',
  221. callback: this.performItemAction.bind(this, item, 'learnAbility')
  222. },
  223. quickSlot: {
  224. text: 'quickslot',
  225. callback: this.performItemAction.bind(this, item, 'setQuickSlot')
  226. },
  227. activate: {
  228. text: 'activate',
  229. callback: this.performItemAction.bind(this, item, 'activateMtx')
  230. },
  231. use: {
  232. text: 'use',
  233. callback: this.performItemAction.bind(this, item, 'useItem')
  234. },
  235. equip: {
  236. text: 'equip',
  237. callback: this.performItemAction.bind(this, item, 'equip')
  238. },
  239. augment: {
  240. text: 'craft',
  241. callback: this.openAugmentUi.bind(this, item)
  242. },
  243. mail: {
  244. text: 'mail',
  245. callback: this.openMailUi.bind(this, item)
  246. },
  247. split: {
  248. text: 'split stack',
  249. callback: this.splitStackStart.bind(this, item)
  250. },
  251. divider: '----------'
  252. };
  253. if (item.eq) {
  254. menuItems.learn.text = 'unlearn';
  255. menuItems.equip.text = 'unequip';
  256. }
  257. if (item.active)
  258. menuItems.activate.text = 'deactivate';
  259. let config = [];
  260. if (item.ability)
  261. config.push(menuItems.learn);
  262. else if (item.type === 'mtx')
  263. config.push(menuItems.activate);
  264. else if (item.type === 'toy' || item.type === 'consumable' || item.useText) {
  265. if (item.useText)
  266. menuItems.use.text = item.useText;
  267. config.push(menuItems.use);
  268. if (!item.has('quickSlot'))
  269. config.push(menuItems.quickSlot);
  270. } else if (item.slot) {
  271. config.push(menuItems.equip);
  272. if (!item.eq)
  273. config.push(menuItems.divider);
  274. if (!item.eq) {
  275. config.push(menuItems.augment);
  276. config.push(menuItems.divider);
  277. }
  278. }
  279. if ((!item.eq) && (!item.active)) {
  280. if (!item.quest) {
  281. if ((window.player.stash.active) && (!item.noStash))
  282. config.push(menuItems.stash);
  283. if (!item.noDrop)
  284. config.push(menuItems.drop);
  285. if ((!item.material) && (!item.noSalvage))
  286. config.push(menuItems.salvage);
  287. }
  288. if (!item.noDestroy)
  289. config.push(menuItems.destroy);
  290. }
  291. if (item.quantity > 1)
  292. config.push(menuItems.split);
  293. if ((!item.noDrop) && (!item.quest))
  294. config.push(menuItems.mail);
  295. if (config.length > 0)
  296. events.emit('onContextMenu', config, e);
  297. e.preventDefault();
  298. return false;
  299. },
  300. splitStackStart: function (item) {
  301. let box = this.find('.split-box').show();
  302. box.data('item', item);
  303. box.find('.amount')
  304. .val('1')
  305. .focus();
  306. },
  307. splitStackEnd: function (cancel, e) {
  308. let box = this.find('.split-box');
  309. if ((cancel) || (!e) || (e.target !== box.find('.btnSplit')[0])) {
  310. if ((cancel) && (!$(e.target).hasClass('button')))
  311. box.hide();
  312. return;
  313. }
  314. box.hide();
  315. client.request({
  316. cpn: 'player',
  317. method: 'performAction',
  318. data: {
  319. cpn: 'inventory',
  320. method: 'splitStack',
  321. data: {
  322. itemId: box.data('item').id,
  323. stackSize: ~~this.find('.split-box .amount').val()
  324. }
  325. }
  326. });
  327. },
  328. onChangeStackAmount: function (e, amount) {
  329. let item = this.find('.split-box').data('item');
  330. let delta = amount;
  331. if (e)
  332. delta = (e.originalEvent.deltaY > 0) ? -1 : 1;
  333. if (this.shiftDown)
  334. delta *= 10;
  335. let elAmount = this.find('.split-box .amount');
  336. elAmount.val(Math.max(1, Math.min(item.quantity - 1, ~~elAmount.val() + delta)));
  337. },
  338. onEnterStackAmount: function (e) {
  339. let el = this.find('.split-box .amount');
  340. let val = el.val();
  341. if (val !== ~~val)
  342. el.val('');
  343. else if (val) {
  344. let item = this.find('.split-box').data('item');
  345. if (val < 0)
  346. val = '';
  347. else if (val > item.quantity - 1)
  348. val = item.quantity - 1;
  349. el.val(val);
  350. }
  351. },
  352. hideTooltip: function () {
  353. if (this.dragEl) {
  354. this.hoverCell = null;
  355. return;
  356. }
  357. events.emit('onHideItemTooltip', this.hoverItem);
  358. this.hoverItem = null;
  359. },
  360. onHover: function (el, item, e) {
  361. if (this.dragEl) {
  362. this.hoverCell = el;
  363. this.find('.hover').removeClass('hover');
  364. el.addClass('hover');
  365. return;
  366. }
  367. if (item)
  368. this.hoverItem = item;
  369. else
  370. item = this.hoverItem;
  371. if (!item)
  372. return;
  373. let ttPos = null;
  374. if (el) {
  375. if (el.hasClass('new')) {
  376. el.removeClass('new');
  377. el.find('.quantity').html((item.quantity > 1) ? item.quantity : '');
  378. delete item.isNew;
  379. }
  380. ttPos = {
  381. x: ~~(e.clientX + 32),
  382. y: ~~(e.clientY)
  383. };
  384. }
  385. let compare = null;
  386. if (item.slot) {
  387. compare = this.items.find(function (i) {
  388. return ((i.eq) && (i.slot === item.slot));
  389. });
  390. // check special cases for mismatched weapon/offhand scenarios (only valid when comparing)
  391. if ((!compare) && (this.shiftDown)) {
  392. let equippedTwoHanded = this.items.find(function (i) {
  393. return ((i.eq) && (i.slot === 'twoHanded'));
  394. });
  395. let equippedOneHanded = this.items.find(function (i) {
  396. return ((i.eq) && (i.slot === 'oneHanded'));
  397. });
  398. let equippedOffhand = this.items.find(function (i) {
  399. return ((i.eq) && (i.slot === 'offHand'));
  400. });
  401. if (item.slot === 'twoHanded') {
  402. if (!equippedOneHanded)
  403. compare = equippedOffhand;
  404. else if (!equippedOffhand)
  405. compare = equippedOneHanded;
  406. else {
  407. // compare against oneHanded and offHand combined by creating a virtual item that is the sum of the two
  408. compare = $.extend(true, {}, equippedOneHanded);
  409. compare.refItem = equippedOneHanded;
  410. for (let s in equippedOffhand.stats) {
  411. if (!compare.stats[s])
  412. compare.stats[s] = 0;
  413. compare.stats[s] += equippedOffhand.stats[s];
  414. }
  415. }
  416. }
  417. if (item.slot === 'oneHanded')
  418. compare = equippedTwoHanded;
  419. // this case is kind of ugly, but we don't want to go in when comparing an offHand to (oneHanded + empty offHand) - that should just use the normal compare which is offHand to empty
  420. if ((item.slot === 'offHand') && (equippedTwoHanded)) {
  421. // since we're comparing an offhand to an equipped Twohander, we need to clone the 'spell' values over (setting damage to zero) so that we can properly display how much damage
  422. // the player would lose by switching to the offhand (which would remove the twoHander)
  423. // keep a reference to the original item for use in onHideToolTip
  424. let spellClone = $.extend(true, {}, equippedTwoHanded.spell);
  425. spellClone.name = '';
  426. spellClone.values.damage = 0;
  427. let clone = $.extend(true, {}, item, {
  428. spell: spellClone
  429. });
  430. clone.refItem = item;
  431. item = clone;
  432. compare = equippedTwoHanded;
  433. }
  434. }
  435. }
  436. events.emit('onShowItemTooltip', item, ttPos, compare, false, this.shiftDown);
  437. },
  438. onGetItems: function (items, rerender) {
  439. this.items = items;
  440. if ((this.shown) && (rerender))
  441. this.build();
  442. },
  443. onDestroyItems: function (itemIds) {
  444. itemIds.forEach(function (id) {
  445. let item = this.items.find(i => i.id === id);
  446. if (item === this.hoverItem)
  447. this.hideTooltip();
  448. this.items.spliceWhere(i => i.id === id);
  449. }, this);
  450. if (this.shown)
  451. this.build();
  452. },
  453. toggle: function (show) {
  454. this.shown = !this.el.is(':visible');
  455. if (this.shown) {
  456. this.find('.split-box').hide();
  457. this.show();
  458. this.build();
  459. } else {
  460. this.hide();
  461. events.emit('onHideInventory');
  462. events.emit('onHideContextMenu');
  463. }
  464. this.hideTooltip();
  465. },
  466. beforeDestroy: function () {
  467. this.el.parent().css('background-color', 'transparent');
  468. this.el.parent().removeClass('blocking');
  469. },
  470. beforeHide: function () {
  471. if (this.oldSpellsZIndex) {
  472. $('.uiSpells').css('z-index', this.oldSpellsZIndex);
  473. this.oldSpellsZIndex = null;
  474. }
  475. },
  476. performItemAction: function (item, action) {
  477. if (!item)
  478. return;
  479. else if ((action === 'equip') && ((item.material) || (item.quest) || (item.type === 'mtx') || (!window.player.inventory.canEquipItem(item))))
  480. return;
  481. else if ((action === 'learnAbility') && (!window.player.inventory.canEquipItem(item)))
  482. return;
  483. else if ((action === 'activateMtx') && (item.type !== 'mtx'))
  484. return;
  485. let data = item.id;
  486. let cpn = 'inventory';
  487. if (['equip', 'setQuickSlot'].includes(action)) {
  488. cpn = 'equipment';
  489. if (action === 'setQuickSlot') {
  490. data = {
  491. itemId: item.id,
  492. slot: 0
  493. };
  494. }
  495. }
  496. if (action === 'useItem')
  497. this.hide();
  498. client.request({
  499. cpn: 'player',
  500. method: 'performAction',
  501. data: {
  502. cpn: cpn,
  503. method: action,
  504. data: data
  505. }
  506. });
  507. },
  508. openAugmentUi: function (item) {
  509. events.emit('onSetSmithItem', {
  510. item: item
  511. });
  512. },
  513. openMailUi: function (item) {
  514. events.emit('onSetMailItem', {
  515. item: item
  516. });
  517. },
  518. onKeyDown: function (key) {
  519. if (key === 'i')
  520. this.toggle();
  521. else if (key === 'shift') {
  522. this.shiftDown = true;
  523. if (this.hoverItem)
  524. this.onHover();
  525. } else if (key === 'ctrl')
  526. this.ctrlDown = true;
  527. },
  528. onKeyUp: function (key) {
  529. if (key === 'shift') {
  530. this.shiftDown = false;
  531. if (this.hoverItem)
  532. this.onHover();
  533. } else if (key === 'ctrl')
  534. this.ctrlDown = false;
  535. }
  536. };
  537. });