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.
 
 
 

646 lines
16 KiB

  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. });