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.
 
 
 

747 lines
16 KiB

  1. define([
  2. '../items/generator',
  3. '../items/salvager',
  4. '../items/enchanter',
  5. '../objects/objects',
  6. '../config/classes'
  7. ], function(
  8. generator,
  9. salvager,
  10. enchanter,
  11. objects,
  12. classes
  13. ) {
  14. return {
  15. type: 'inventory',
  16. inventorySize: 50,
  17. items: [],
  18. blueprint: null,
  19. init: function(blueprint, isTransfer) {
  20. var items = blueprint.items || [];
  21. var iLen = items.length;
  22. //Spells should be sorted so they're EQ'd in the right order
  23. items.sort(function(a, b) {
  24. var aId = (a.spellId != null) ? ~~a.spellId : 9999;
  25. var bId = (b.spellId != null) ? ~~b.spellId : 9999;
  26. return (aId - bId);
  27. });
  28. for (var i = 0; i < iLen; i++) {
  29. var item = items[i];
  30. if ((item.spell) && (!item.spell.rolls))
  31. continue;
  32. if (item.effects) {
  33. item.effects.forEach(function(e) {
  34. var faction = require('config/factions/' + e.factionId);
  35. var statGenerator = faction.uniqueStat;
  36. statGenerator.generate(item);
  37. });
  38. }
  39. this.getItem(items[i], true);
  40. }
  41. if ((this.obj.player) && (!isTransfer)) {
  42. this.getDefaultAbilities();
  43. /*this.getItem(generator.generate({
  44. spell: true,
  45. spellName: 'arcane barrier'
  46. }));*/
  47. /*for (var i = 0; i < 10; i++) {
  48. var item = generator.generate({
  49. slot: 'twoHanded',
  50. quality: 4,
  51. level: 1
  52. });
  53. this.getItem(item);
  54. }*/
  55. }
  56. delete blueprint.items;
  57. this.blueprint = blueprint;
  58. },
  59. //Client Actions
  60. enchantItem: function(msg) {
  61. var item = this.findItem(msg.itemId);
  62. if ((!item) || (!item.slot) || (item.eq) || ((msg.action == 'scour') && (item.power == 0))) {
  63. this.resolveCallback(msg);
  64. return;
  65. }
  66. enchanter.enchant(this.obj, item, msg);
  67. },
  68. getEnchantMaterials: function(msg) {
  69. var result = [];
  70. var item = this.findItem(msg.itemId);
  71. if ((item) && (item.slot))
  72. result = enchanter.getEnchantMaterials(item, msg.action);
  73. this.resolveCallback(msg, result);
  74. },
  75. learnAbility: function(id, forceLearn) {
  76. var replaceId = null;
  77. var newSpellId = id.spellId;
  78. if (id.id != null) {
  79. replaceId = id.replaceId;
  80. id = id.id;
  81. }
  82. var item = this.findItem(id);
  83. if ((!item) || (!item.spell) || ((item.spellId == null) && (item.eq) && (!forceLearn))) {
  84. if (item)
  85. item.eq = false;
  86. return;
  87. }
  88. var spellbook = this.obj.spellbook;
  89. if ((item.eq) && (!forceLearn)) {
  90. delete item.eq;
  91. spellbook.removeSpellById(item.spellId);
  92. delete item.spellId;
  93. this.obj.syncer.setArray(true, 'inventory', 'getItems', item);
  94. return;
  95. }
  96. if (replaceId != null) {
  97. var replaceItem = this.findItem(replaceId);
  98. if (replaceItem) {
  99. delete replaceItem.eq;
  100. spellbook.removeSpellById(replaceItem.spellId);
  101. newSpellId = replaceItem.spellId;
  102. delete replaceItem.spellId;
  103. this.obj.syncer.setArray(true, 'inventory', 'getItems', replaceItem);
  104. }
  105. }
  106. if (spellbook.spells.length >= 3) {
  107. if (item.slot)
  108. item.spellId = -1;
  109. this.obj.syncer.setArray(true, 'inventory', 'getItems', item);
  110. return;
  111. }
  112. item.spellId = spellbook.addSpellFromRune(item.spell, newSpellId);
  113. if (item.spellId != -1)
  114. item.eq = true;
  115. else
  116. delete item.spell;
  117. this.obj.syncer.setArray(true, 'inventory', 'getItems', item);
  118. },
  119. stashItem: function(id) {
  120. var item = this.findItem(id);
  121. if ((!item) || (item.quest) || (item.noSalvage))
  122. return;
  123. delete item.pos;
  124. var stash = this.obj.stash;
  125. if (!stash.active)
  126. return;
  127. this.destroyItem(id);
  128. stash.deposit(item);
  129. },
  130. salvageItem: function(id) {
  131. var item = this.findItem(id);
  132. if ((!item) || (item.material) || (item.quest) || (item.noSalvage) || (item.eq))
  133. return;
  134. var messages = [];
  135. var items = salvager.salvage(item);
  136. var iLen = items.length;
  137. if (!iLen)
  138. return;
  139. for (var i = 0; i < iLen; i++) {
  140. var material = items[i];
  141. this.getItem(material, true);
  142. messages.push({
  143. class: 'q' + material.quality,
  144. message: 'salvage (' + material.name + ' x' + material.quantity + ')'
  145. });
  146. }
  147. this.obj.instance.syncer.queue('onGetMessages', {
  148. id: this.obj.id,
  149. messages: messages
  150. }, [this.obj.serverId]);
  151. this.destroyItem(id);
  152. },
  153. destroyItem: function(id, amount) {
  154. var item = this.findItem(id);
  155. if (!item)
  156. return;
  157. amount = amount || item.quantity;
  158. if (item.eq)
  159. this.obj.equipment.unequip(id);
  160. if ((item.quantity) && (amount)) {
  161. item.quantity -= amount;
  162. if (item.quantity <= 0) {
  163. this.items.spliceWhere(i => i.id == id);
  164. this.obj.syncer.setArray(true, 'inventory', 'destroyItems', id);
  165. } else
  166. this.obj.syncer.setArray(true, 'inventory', 'getItems', item);
  167. } else {
  168. this.items.spliceWhere(i => i.id == id);
  169. this.obj.syncer.setArray(true, 'inventory', 'destroyItems', id);
  170. }
  171. if (this.obj.player)
  172. this.getDefaultAbilities();
  173. this.obj.fireEvent('afterDestroyItem', item, amount);
  174. return item;
  175. },
  176. dropItem: function(id) {
  177. var item = this.findItem(id);
  178. if (!item)
  179. return;
  180. delete item.pos;
  181. //Find close open position
  182. var x = this.obj.x;
  183. var y = this.obj.y;
  184. var dropCell = this.obj.instance.physics.getOpenCellInArea(x - 1, y - 1, x + 1, y + 1);
  185. if (!dropCell)
  186. return;
  187. if (item.eq)
  188. this.obj.equipment.unequip(id);
  189. this.items.spliceWhere(i => i.id == id);
  190. this.obj.syncer.setArray(true, 'inventory', 'destroyItems', id);
  191. this.createBag(dropCell.x, dropCell.y, [item]);
  192. },
  193. moveItem: function(msgs) {
  194. msgs.forEach(function(m) {
  195. var item = this.findItem(m.id);
  196. if (!item)
  197. return;
  198. item.pos = m.pos;
  199. }, this);
  200. },
  201. //Helpers
  202. resolveCallback: function(msg, result) {
  203. var callbackId = (msg.callbackId != null) ? msg.callbackId : msg;
  204. result = result || [];
  205. if (callbackId == null)
  206. return;
  207. this.obj.instance.syncer.queue('serverModule', {
  208. module: 'atlas',
  209. method: 'resolveCallback',
  210. msg: {
  211. id: callbackId,
  212. result: result
  213. }
  214. });
  215. },
  216. findItem: function(id) {
  217. if (id == null)
  218. return null;
  219. return this.items.find(i => i.id == id);
  220. },
  221. getDefaultAbilities: function() {
  222. var hasWeapon = this.items.some(function(i) {
  223. return (
  224. (i.spell) &&
  225. (i.spell.rolls) &&
  226. (i.spell.rolls.damage != null) &&
  227. (i.slot == 'twoHanded')
  228. );
  229. });
  230. if (!hasWeapon) {
  231. var item = generator.generate({
  232. slot: 'twoHanded',
  233. type: classes.weapons[this.obj.class],
  234. quality: 0,
  235. spellQuality: 'mid'
  236. });
  237. item.eq = true;
  238. item.noSalvage = true;
  239. this.getItem(item);
  240. }
  241. var hasSpell = this.items.some(function(i) {
  242. return (
  243. (i.spell) &&
  244. (i.spell.rolls) &&
  245. ((i.spell.rolls.damage != null) || (i.spell.rolls.healing != null)) &&
  246. (i.slot != 'twoHanded')
  247. );
  248. });
  249. if (!hasSpell) {
  250. var item = generator.generate({
  251. spell: true,
  252. spellQuality: 'basic',
  253. spellName: classes.spells[this.obj.class][0]
  254. });
  255. item.eq = true;
  256. item.noSalvage = true;
  257. this.getItem(item);
  258. }
  259. },
  260. createBag: function(x, y, items, ownerId) {
  261. if (ownerId == null)
  262. ownerId = -1;
  263. var bagCell = 50;
  264. var topQuality = 0;
  265. var iLen = items.length;
  266. for (var i = 0; i < iLen; i++) {
  267. var quality = items[i].quality;
  268. items[i].fromMob = !!this.obj.mob;
  269. if (quality > topQuality)
  270. topQuality = quality;
  271. }
  272. if (topQuality == 0)
  273. bagCell = 50;
  274. else if (topQuality < 3)
  275. bagCell = 51;
  276. else if (topQuality == 3)
  277. bagCell = 52;
  278. else
  279. bagCell = 53;
  280. var obj = this.obj.instance.objects.buildObjects([{
  281. sheetName: 'objects',
  282. cell: bagCell,
  283. x: x,
  284. y: y,
  285. properties: {
  286. cpnChest: {
  287. ownerId: ownerId
  288. },
  289. cpnInventory: {
  290. items: extend(true, [], items)
  291. }
  292. }
  293. }]);
  294. return obj;
  295. },
  296. getItem: function(item, hideMessage) {
  297. //We need to know if a mob dropped it for quest purposes
  298. var fromMob = item.fromMob;
  299. if (item.quality == null)
  300. item.quality = 0;
  301. //Players can't have fromMob items in their inventory but bags can (dropped by a mob)
  302. if (this.obj.player)
  303. delete item.fromMob;
  304. //Store the quantity to send to the player
  305. var quantity = item.quantity;
  306. //Material?
  307. var exists = false;
  308. if ((item.material) || (item.quest)) {
  309. var existItem = this.items.find(i => i.name == item.name);
  310. if (existItem) {
  311. exists = true;
  312. if (!existItem.quantity)
  313. existItem.quantity = 1;
  314. existItem.quantity += item.quantity;
  315. item = existItem;
  316. }
  317. }
  318. //Get next id
  319. if (!exists) {
  320. var id = 0;
  321. var items = this.items;
  322. var iLen = items.length;
  323. var nonEqItems = items.filter(f => !f.eq).length;
  324. if ((nonEqItems >= this.inventorySize) && (!hideMessage)) {
  325. this.obj.instance.syncer.queue('onGetMessages', {
  326. id: this.obj.id,
  327. messages: [{
  328. class: 'q0',
  329. message: 'you bags are too full to loot any more items',
  330. type: 'info'
  331. }]
  332. }, [this.obj.serverId]);
  333. return false;
  334. }
  335. for (var i = 0; i < iLen; i++) {
  336. var fItem = items[i];
  337. if (fItem.id >= id) {
  338. id = fItem.id + 1;
  339. }
  340. }
  341. item.id = id;
  342. if (item.eq)
  343. delete item.pos;
  344. if ((item.pos == null) && (!item.eq)) {
  345. var pos = iLen;
  346. for (var i = 0; i < iLen; i++) {
  347. if (!items.some(fi => (fi.pos == i))) {
  348. pos = i;
  349. break;
  350. }
  351. }
  352. item.pos = pos;
  353. }
  354. }
  355. if ((this.obj.player) && (!hideMessage)) {
  356. var messages = [];
  357. var msg = item.name;
  358. if (quantity)
  359. msg += ' x' + quantity;
  360. messages.push({
  361. class: 'q' + item.quality,
  362. message: 'loot (' + msg + ')',
  363. type: 'loot'
  364. });
  365. this.obj.instance.syncer.queue('onGetDamage', {
  366. id: this.obj.id,
  367. event: true,
  368. text: 'loot'
  369. });
  370. this.obj.instance.syncer.queue('onGetMessages', {
  371. id: this.obj.id,
  372. messages: messages
  373. }, [this.obj.serverId]);
  374. }
  375. //TODO: Remove later, just for test
  376. if (item.stats) {
  377. var stats = Object.keys(item.stats);
  378. var sLen = stats.length
  379. for (var i = 0; i < sLen; i++) {
  380. var s = stats[i];
  381. var val = item.stats[s];
  382. if (s == 'maxHp') {
  383. delete item.stats[s];
  384. item.stats.hpMax = val;
  385. } else if (s == 'maxMana') {
  386. delete item.stats[s];
  387. item.stats.manaMax = val;
  388. }
  389. }
  390. }
  391. if (!exists)
  392. this.items.push(item);
  393. if (item.eq) {
  394. if (item.ability)
  395. this.learnAbility(item.id, true);
  396. else
  397. this.obj.equipment.equip(item.id);
  398. } else {
  399. if (!item.effects)
  400. this.obj.syncer.setArray(true, 'inventory', 'getItems', item);
  401. else {
  402. var result = extend(true, {}, item);
  403. result.effects = result.effects.map(e => ({
  404. factionId: e.factionId,
  405. text: e.text,
  406. properties: e.properties
  407. }));
  408. var reputation = this.obj.reputation;
  409. //Don't do this check if we don't have a reputation cpn. That means this is most likely a bag
  410. if ((reputation) && (result.factions)) {
  411. result.factions = result.factions.map(function(f) {
  412. var faction = reputation.getBlueprint(f.id);
  413. var factionTier = reputation.getTier(f.id);
  414. var noEquip = null;
  415. if (factionTier < f.tier)
  416. noEquip = true;
  417. return {
  418. name: faction.name,
  419. tier: f.tier,
  420. tierName: ['Hated', 'Hostile', 'Unfriendly', 'Neutral', 'Friendly', 'Honored', 'Revered', 'Exalted'][f.tier],
  421. noEquip: noEquip
  422. };
  423. }, this);
  424. }
  425. this.obj.syncer.setArray(true, 'inventory', 'getItems', result);
  426. }
  427. }
  428. if (!hideMessage) {
  429. if (fromMob)
  430. this.obj.fireEvent('afterLootMobItem', item);
  431. }
  432. return item;
  433. },
  434. dropBag: function(ownerId, killSource) {
  435. if (!this.blueprint)
  436. return;
  437. //Only drop loot if this player is in the zone
  438. var playerObject = this.obj.instance.objects.find(o => o.serverId == ownerId);
  439. if (!playerObject)
  440. return;
  441. //Get player's spells' statTypes
  442. var stats = [];
  443. playerObject.spellbook.spells.forEach(function(s) {
  444. var spellStatType = s.statType;
  445. if (!(spellStatType instanceof Array))
  446. spellStatType = [spellStatType];
  447. spellStatType.forEach(function(ss) {
  448. if (stats.indexOf(ss) == -1)
  449. stats.push(ss);
  450. });
  451. });
  452. var items = this.items;
  453. var iLen = items.length;
  454. for (var i = 0; i < iLen; i++) {
  455. delete items[i].eq;
  456. delete items[i].pos;
  457. }
  458. var blueprint = this.blueprint;
  459. if (blueprint.noRandom) {
  460. this.items = [];
  461. var blueprints = blueprint.blueprints;
  462. for (var i = 0; i < blueprints.length; i++) {
  463. var drop = blueprints[i];
  464. drop.level = drop.level || level;
  465. drop.magicFind = magicFind;
  466. this.getItem(generator.generate(drop), true);
  467. }
  468. killSource.fireEvent('beforeTargetDeath', this.obj, this.items);
  469. if (this.items.length > 0)
  470. this.createBag(this.obj.x, this.obj.y, this.items, ownerId);
  471. } else {
  472. var instancedItems = extend(true, [], this.items);
  473. var useItems = [];
  474. var magicFind = (blueprint.magicFind || 0) + killSource.stats.values.magicFind;
  475. for (var i = 0; i < blueprint.rolls; i++) {
  476. if (Math.random() * 100 >= (blueprint.chance || 35))
  477. continue;
  478. var useItem = null;
  479. if (Math.random() < generator.spellChance) {
  480. useItem = instancedItems
  481. .filter(item => item.ability);
  482. if (useItem.length > 0)
  483. useItem = useItem[~~(Math.random() * useItem.length)];
  484. }
  485. if (!useItem) {
  486. var slot = generator.pickRandomSlot();
  487. var useItem = instancedItems.find(item => item.slot == slot);
  488. }
  489. if (!useItem)
  490. useItem = instancedItems[~~(Math.random() * iLen)];
  491. iLen--;
  492. instancedItems.spliceWhere(item => item == useItem);
  493. //Spells don't have stats
  494. if (useItem.stats)
  495. delete useItem.stats.armor;
  496. var itemBlueprint = {
  497. level: useItem.level,
  498. slot: useItem.slot,
  499. type: useItem.type,
  500. spell: !!useItem.ability,
  501. stats: useItem.stats ? Object.keys(useItem.stats) : null,
  502. magicFind: magicFind
  503. };
  504. useItem = generator.generate(itemBlueprint);
  505. useItems.push(useItem);
  506. }
  507. killSource.fireEvent('beforeTargetDeath', this.obj, useItems);
  508. if (useItems.length > 0)
  509. this.createBag(this.obj.x, this.obj.y, useItems, ownerId);
  510. }
  511. },
  512. giveItems: function(obj, hideMessage) {
  513. var objInventory = obj.inventory;
  514. var messages = [];
  515. var items = this.items;
  516. var iLen = items.length;
  517. for (var i = 0; i < iLen; i++) {
  518. var item = items[i];
  519. if (objInventory.getItem(item, hideMessage)) {
  520. items.splice(i, 1);
  521. i--;
  522. iLen--;
  523. } else
  524. return false;
  525. }
  526. return true;
  527. },
  528. rollItems: function(party) {
  529. var items = this.items;
  530. var iLen = items.length;
  531. for (var i = 0; i < iLen; i++) {
  532. var item = items[i];
  533. this.obj.instance.syncer.queue('serverModule', {
  534. module: 'lootRoller',
  535. method: 'enqueue',
  536. msg: {
  537. party: party,
  538. item: item
  539. }
  540. });
  541. }
  542. this.items = [];
  543. },
  544. fireEvent: function(event, args) {
  545. var items = this.items;
  546. var iLen = items.length;
  547. for (var i = 0; i < iLen; i++) {
  548. var item = items[i];
  549. var effects = item.effects;
  550. if (!effects)
  551. continue;
  552. var eLen = effects.length;
  553. for (var j = 0; j < eLen; j++) {
  554. var effect = effects[j];
  555. var effectEvent = effect.events[event];
  556. if (!effectEvent)
  557. continue;
  558. effectEvent.call(this.obj, item, args[0]);
  559. }
  560. }
  561. },
  562. clear: function() {
  563. delete this.items;
  564. this.items = [];
  565. },
  566. save: function() {
  567. return {
  568. type: 'inventory',
  569. items: this.items
  570. };
  571. },
  572. simplify: function(self) {
  573. if (!self)
  574. return null;
  575. var reputation = this.obj.reputation;
  576. return {
  577. type: 'inventory',
  578. items: this.items
  579. .map(function(i) {
  580. if (!i.effects)
  581. return i;
  582. else {
  583. var item = extend(true, {}, i);
  584. item.effects = item.effects.map(e => ({
  585. factionId: e.factionId,
  586. text: e.text,
  587. properties: e.properties
  588. }));
  589. if (item.factions) {
  590. item.factions = item.factions.map(function(f) {
  591. var faction = reputation.getBlueprint(f.id);
  592. var factionTier = reputation.getTier(f.id);
  593. var noEquip = null;
  594. if (factionTier < f.tier)
  595. noEquip = true;
  596. return {
  597. id: f.id,
  598. name: faction.name,
  599. tier: f.tier,
  600. tierName: ['Hated', 'Hostile', 'Unfriendly', 'Neutral', 'Friendly', 'Honored', 'Revered', 'Exalted'][f.tier],
  601. noEquip: noEquip
  602. };
  603. }, this);
  604. }
  605. return item;
  606. }
  607. })
  608. };
  609. }
  610. };
  611. });