選択できるのは25トピックまでです。 トピックは、先頭が英数字で、英数字とダッシュ('-')を使用した35文字以内のものにしてください。
 
 
 

744 行
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. if (item.eq)
  158. this.obj.equipment.unequip(id);
  159. if ((item.quantity) && (amount)) {
  160. item.quantity -= amount;
  161. if (item.quantity <= 0) {
  162. this.items.spliceWhere(i => i.id == id);
  163. this.obj.syncer.setArray(true, 'inventory', 'destroyItems', id);
  164. } else
  165. this.obj.syncer.setArray(true, 'inventory', 'getItems', item);
  166. } else {
  167. this.items.spliceWhere(i => i.id == id);
  168. this.obj.syncer.setArray(true, 'inventory', 'destroyItems', id);
  169. }
  170. if (this.obj.player)
  171. this.getDefaultAbilities();
  172. return item;
  173. },
  174. dropItem: function(id) {
  175. var item = this.findItem(id);
  176. if (!item)
  177. return;
  178. delete item.pos;
  179. //Find close open position
  180. var x = this.obj.x;
  181. var y = this.obj.y;
  182. var dropCell = this.obj.instance.physics.getOpenCellInArea(x - 1, y - 1, x + 1, y + 1);
  183. if (!dropCell)
  184. return;
  185. if (item.eq)
  186. this.obj.equipment.unequip(id);
  187. this.items.spliceWhere(i => i.id == id);
  188. this.obj.syncer.setArray(true, 'inventory', 'destroyItems', id);
  189. this.createBag(dropCell.x, dropCell.y, [item]);
  190. },
  191. moveItem: function(msgs) {
  192. msgs.forEach(function(m) {
  193. var item = this.findItem(m.id);
  194. if (!item)
  195. return;
  196. item.pos = m.pos;
  197. }, this);
  198. },
  199. //Helpers
  200. resolveCallback: function(msg, result) {
  201. var callbackId = (msg.callbackId != null) ? msg.callbackId : msg;
  202. result = result || [];
  203. if (callbackId == null)
  204. return;
  205. this.obj.instance.syncer.queue('serverModule', {
  206. module: 'atlas',
  207. method: 'resolveCallback',
  208. msg: {
  209. id: callbackId,
  210. result: result
  211. }
  212. });
  213. },
  214. findItem: function(id) {
  215. if (id == null)
  216. return null;
  217. return this.items.find(i => i.id == id);
  218. },
  219. getDefaultAbilities: function() {
  220. var hasWeapon = this.items.some(function(i) {
  221. return (
  222. (i.spell) &&
  223. (i.spell.rolls) &&
  224. (i.spell.rolls.damage != null) &&
  225. (i.slot == 'twoHanded')
  226. );
  227. });
  228. if (!hasWeapon) {
  229. var item = generator.generate({
  230. slot: 'twoHanded',
  231. type: classes.weapons[this.obj.class],
  232. quality: 0,
  233. spellQuality: 'mid'
  234. });
  235. item.eq = true;
  236. item.noSalvage = true;
  237. this.getItem(item);
  238. }
  239. var hasSpell = this.items.some(function(i) {
  240. return (
  241. (i.spell) &&
  242. (i.spell.rolls) &&
  243. ((i.spell.rolls.damage != null) || (i.spell.rolls.healing != null)) &&
  244. (i.slot != 'twoHanded')
  245. );
  246. });
  247. if (!hasSpell) {
  248. var item = generator.generate({
  249. spell: true,
  250. spellQuality: 'basic',
  251. spellName: classes.spells[this.obj.class][0]
  252. });
  253. item.eq = true;
  254. item.noSalvage = true;
  255. this.getItem(item);
  256. }
  257. },
  258. createBag: function(x, y, items, ownerId) {
  259. if (ownerId == null)
  260. ownerId = -1;
  261. var bagCell = 50;
  262. var topQuality = 0;
  263. var iLen = items.length;
  264. for (var i = 0; i < iLen; i++) {
  265. var quality = items[i].quality;
  266. items[i].fromMob = !!this.obj.mob;
  267. if (quality > topQuality)
  268. topQuality = quality;
  269. }
  270. if (topQuality == 0)
  271. bagCell = 50;
  272. else if (topQuality < 3)
  273. bagCell = 51;
  274. else if (topQuality == 3)
  275. bagCell = 52;
  276. else
  277. bagCell = 53;
  278. var obj = this.obj.instance.objects.buildObjects([{
  279. sheetName: 'objects',
  280. cell: bagCell,
  281. x: x,
  282. y: y,
  283. properties: {
  284. cpnChest: {
  285. ownerId: ownerId
  286. },
  287. cpnInventory: {
  288. items: extend(true, [], items)
  289. }
  290. }
  291. }]);
  292. return obj;
  293. },
  294. getItem: function(item, hideMessage) {
  295. //We need to know if a mob dropped it for quest purposes
  296. var fromMob = item.fromMob;
  297. if (item.quality == null)
  298. item.quality = 0;
  299. //Players can't have fromMob items in their inventory but bags can (dropped by a mob)
  300. if (this.obj.player)
  301. delete item.fromMob;
  302. //Material?
  303. var exists = false;
  304. if ((item.material) || (item.quest)) {
  305. var existItem = this.items.find(i => i.name == item.name);
  306. if (existItem) {
  307. exists = true;
  308. if (!existItem.quantity)
  309. existItem.quantity = 1;
  310. existItem.quantity += item.quantity;
  311. item = existItem;
  312. }
  313. }
  314. //Get next id
  315. if (!exists) {
  316. var id = 0;
  317. var items = this.items;
  318. var iLen = items.length;
  319. var nonEqItems = items.filter(f => !f.eq).length;
  320. if ((nonEqItems >= this.inventorySize) && (!hideMessage)) {
  321. this.obj.instance.syncer.queue('onGetMessages', {
  322. id: this.obj.id,
  323. messages: [{
  324. class: 'q0',
  325. message: 'you bags are too full to loot any more items',
  326. type: 'info'
  327. }]
  328. }, [this.obj.serverId]);
  329. return false;
  330. }
  331. for (var i = 0; i < iLen; i++) {
  332. var fItem = items[i];
  333. if (fItem.id >= id) {
  334. id = fItem.id + 1;
  335. }
  336. }
  337. item.id = id;
  338. if (item.eq)
  339. delete item.pos;
  340. if ((item.pos == null) && (!item.eq)) {
  341. var pos = iLen;
  342. for (var i = 0; i < iLen; i++) {
  343. if (!items.some(fi => (fi.pos == i))) {
  344. pos = i;
  345. break;
  346. }
  347. }
  348. item.pos = pos;
  349. }
  350. }
  351. if ((this.obj.player) && (!hideMessage)) {
  352. var messages = [];
  353. var msg = item.name;
  354. if (item.quantity)
  355. msg += ' x' + item.quantity;
  356. messages.push({
  357. class: 'q' + item.quality,
  358. message: 'loot (' + msg + ')',
  359. type: 'loot'
  360. });
  361. this.obj.instance.syncer.queue('onGetDamage', {
  362. id: this.obj.id,
  363. event: true,
  364. text: 'loot'
  365. });
  366. this.obj.instance.syncer.queue('onGetMessages', {
  367. id: this.obj.id,
  368. messages: messages
  369. }, [this.obj.serverId]);
  370. }
  371. //TODO: Remove later, just for test
  372. if (item.stats) {
  373. var stats = Object.keys(item.stats);
  374. var sLen = stats.length
  375. for (var i = 0; i < sLen; i++) {
  376. var s = stats[i];
  377. var val = item.stats[s];
  378. if (s == 'maxHp') {
  379. delete item.stats[s];
  380. item.stats.hpMax = val;
  381. } else if (s == 'maxMana') {
  382. delete item.stats[s];
  383. item.stats.manaMax = val;
  384. }
  385. }
  386. }
  387. if (!exists)
  388. this.items.push(item);
  389. if (item.eq) {
  390. if (item.ability)
  391. this.learnAbility(item.id, true);
  392. else
  393. this.obj.equipment.equip(item.id);
  394. } else {
  395. var didEq = false;
  396. //if ((item.slot) && (this.obj.equipment)) {
  397. // didEq = this.obj.equipment.autoEquip(item.id);
  398. //}
  399. if (!didEq) {
  400. if (!item.effects)
  401. this.obj.syncer.setArray(true, 'inventory', 'getItems', item);
  402. else {
  403. var result = extend(true, {}, item);
  404. result.effects = result.effects.map(e => ({
  405. factionId: e.factionId,
  406. text: e.text,
  407. properties: e.properties
  408. }));
  409. var reputation = this.obj.reputation;
  410. if (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. }
  429. if (!hideMessage) {
  430. if (fromMob)
  431. this.obj.fireEvent('afterLootMobItem', item);
  432. }
  433. return item;
  434. },
  435. dropBag: function(ownerId, killSource) {
  436. if (!this.blueprint)
  437. return;
  438. //Only drop loot if this player is in the zone
  439. var playerObject = this.obj.instance.objects.find(o => o.serverId == ownerId);
  440. if (!playerObject)
  441. return;
  442. //Get player's spells' statTypes
  443. var stats = [];
  444. playerObject.spellbook.spells.forEach(function(s) {
  445. var spellStatType = s.statType;
  446. if (!(spellStatType instanceof Array))
  447. spellStatType = [spellStatType];
  448. spellStatType.forEach(function(ss) {
  449. if (stats.indexOf(ss) == -1)
  450. stats.push(ss);
  451. });
  452. });
  453. var items = this.items;
  454. var iLen = items.length;
  455. for (var i = 0; i < iLen; i++) {
  456. delete items[i].eq;
  457. delete items[i].pos;
  458. }
  459. var blueprint = this.blueprint;
  460. if (blueprint.noRandom) {
  461. this.items = [];
  462. var blueprints = blueprint.blueprints;
  463. for (var i = 0; i < blueprints.length; i++) {
  464. var drop = blueprints[i];
  465. drop.level = drop.level || level;
  466. drop.magicFind = magicFind;
  467. this.getItem(generator.generate(drop), true);
  468. }
  469. killSource.fireEvent('beforeTargetDeath', this.obj, this.items);
  470. if (this.items.length > 0)
  471. this.createBag(this.obj.x, this.obj.y, this.items, ownerId);
  472. } else {
  473. var instancedItems = extend(true, [], this.items);
  474. var useItems = [];
  475. var magicFind = (blueprint.magicFind || 0) + killSource.stats.values.magicFind;
  476. for (var i = 0; i < blueprint.rolls; i++) {
  477. if (Math.random() * 100 >= (blueprint.chance || 35))
  478. continue;
  479. var useItem = null;
  480. if (Math.random() < generator.spellChance) {
  481. useItem = instancedItems
  482. .filter(item => item.ability);
  483. if (useItem.length > 0)
  484. useItem = useItem[~~(Math.random() * useItem.length)];
  485. }
  486. if (!useItem) {
  487. var slot = generator.pickRandomSlot();
  488. var useItem = instancedItems.find(item => item.slot == slot);
  489. }
  490. if (!useItem)
  491. useItem = instancedItems[~~(Math.random() * iLen)];
  492. iLen--;
  493. instancedItems.spliceWhere(item => item == useItem);
  494. //Spells don't have stats
  495. if (useItem.stats)
  496. delete useItem.stats.armor;
  497. var itemBlueprint = {
  498. level: useItem.level,
  499. slot: useItem.slot,
  500. type: useItem.type,
  501. spell: !!useItem.ability,
  502. stats: useItem.stats ? Object.keys(useItem.stats) : null,
  503. magicFind: magicFind
  504. };
  505. useItem = generator.generate(itemBlueprint);
  506. useItems.push(useItem);
  507. }
  508. killSource.fireEvent('beforeTargetDeath', this.obj, useItems);
  509. if (useItems.length > 0)
  510. this.createBag(this.obj.x, this.obj.y, useItems, ownerId);
  511. }
  512. },
  513. giveItems: function(obj, hideMessage) {
  514. var objInventory = obj.inventory;
  515. var messages = [];
  516. var items = this.items;
  517. var iLen = items.length;
  518. for (var i = 0; i < iLen; i++) {
  519. var item = items[i];
  520. if (objInventory.getItem(item, hideMessage)) {
  521. items.splice(i, 1);
  522. i--;
  523. iLen--;
  524. } else
  525. return false;
  526. }
  527. return true;
  528. },
  529. rollItems: function(party) {
  530. var items = this.items;
  531. var iLen = items.length;
  532. for (var i = 0; i < iLen; i++) {
  533. var item = items[i];
  534. this.obj.instance.syncer.queue('serverModule', {
  535. module: 'lootRoller',
  536. method: 'enqueue',
  537. msg: {
  538. party: party,
  539. item: item
  540. }
  541. });
  542. }
  543. this.items = [];
  544. },
  545. fireEvent: function(event, args) {
  546. var items = this.items;
  547. var iLen = items.length;
  548. for (var i = 0; i < iLen; i++) {
  549. var item = items[i];
  550. var effects = item.effects;
  551. if (!effects)
  552. continue;
  553. var eLen = effects.length;
  554. for (var j = 0; j < eLen; j++) {
  555. var effect = effects[j];
  556. var effectEvent = effect.events[event];
  557. if (!effectEvent)
  558. continue;
  559. effectEvent.call(this.obj, item, args[0]);
  560. }
  561. }
  562. },
  563. clear: function() {
  564. delete this.items;
  565. this.items = [];
  566. },
  567. save: function() {
  568. return {
  569. type: 'inventory',
  570. items: this.items
  571. };
  572. },
  573. simplify: function(self) {
  574. if (!self)
  575. return null;
  576. var reputation = this.obj.reputation;
  577. return {
  578. type: 'inventory',
  579. items: this.items
  580. .map(function(i) {
  581. if (!i.effects)
  582. return i;
  583. else {
  584. var item = extend(true, {}, i);
  585. item.effects = item.effects.map(e => ({
  586. factionId: e.factionId,
  587. text: e.text,
  588. properties: e.properties
  589. }));
  590. if (item.factions) {
  591. item.factions = item.factions.map(function(f) {
  592. var faction = reputation.getBlueprint(f.id);
  593. var factionTier = reputation.getTier(f.id);
  594. var noEquip = null;
  595. if (factionTier < f.tier)
  596. noEquip = true;
  597. return {
  598. id: f.id,
  599. name: faction.name,
  600. tier: f.tier,
  601. tierName: ['Hated', 'Hostile', 'Unfriendly', 'Neutral', 'Friendly', 'Honored', 'Revered', 'Exalted'][f.tier],
  602. noEquip: noEquip
  603. };
  604. }, this);
  605. }
  606. return item;
  607. }
  608. })
  609. };
  610. }
  611. };
  612. });