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.
 
 
 

409 lines
9.2 KiB

  1. let generator = require('../items/generator');
  2. let statGenerator = require('../items/generators/stats');
  3. let skins = require('../config/skins');
  4. const sendMessage = ({ instance, id, serverId }, color, message) => {
  5. instance.syncer.queue('onGetMessages', {
  6. id: id,
  7. messages: [{
  8. class: color,
  9. message,
  10. type: 'info'
  11. }]
  12. }, [serverId]);
  13. };
  14. module.exports = {
  15. type: 'trade',
  16. items: [],
  17. buybackList: {},
  18. maxBuyback: 10,
  19. blueprint: null,
  20. gold: 0,
  21. target: null,
  22. regenCd: 0,
  23. regenCdMax: 1710,
  24. genLeft: 0,
  25. markup: {
  26. buy: 1,
  27. sell: 1
  28. },
  29. init: function (blueprint) {
  30. this.blueprint = blueprint;
  31. this.gold = blueprint.gold;
  32. this.items = [];
  33. this.regenCd = this.regenCdMax;
  34. (blueprint.forceItems || []).forEach(function (f, i) {
  35. let item = extend({}, f);
  36. let id = 0;
  37. this.items.forEach(function (checkItem) {
  38. if (checkItem.id >= id)
  39. id = checkItem.id + 1;
  40. });
  41. if (item.type === 'skin') {
  42. let skinBlueprint = skins.getBlueprint(item.skinId);
  43. item.name = skinBlueprint.name;
  44. item.sprite = skinBlueprint.sprite;
  45. item.spritesheet = skinBlueprint.spritesheet;
  46. item.skinId = item.skinId;
  47. }
  48. item.id = id;
  49. this.items.push(item);
  50. }, this);
  51. if (!blueprint.items)
  52. return;
  53. this.markup = blueprint.markup;
  54. if (blueprint.faction) {
  55. this.obj.extendComponent('trade', 'factionVendor', blueprint);
  56. return;
  57. }
  58. this.genLeft = blueprint.items.max;
  59. },
  60. update: function () {
  61. if (this.regenCd > 0) {
  62. this.regenCd--;
  63. return;
  64. }
  65. this.regenCd = this.regenCdMax;
  66. if (!this.genLeft)
  67. return;
  68. this.genLeft--;
  69. let blueprint = this.blueprint;
  70. let level = 1;
  71. if (blueprint.level)
  72. level = blueprint.level.min + ~~(Math.random() * (blueprint.level.max - blueprint.level.min));
  73. let item = generator.generate({
  74. noSpell: true,
  75. level: level
  76. });
  77. let id = 0;
  78. this.items.forEach(function (checkItem) {
  79. if (checkItem.id >= id)
  80. id = checkItem.id + 1;
  81. });
  82. item.id = id;
  83. this.items.push(item);
  84. },
  85. startBuy: function (msg) {
  86. if (!msg.has('target') && !msg.targetName)
  87. return false;
  88. let target = msg.target;
  89. if (target && !target.id)
  90. target = this.obj.instance.objects.objects.find(o => o.id === target);
  91. else if (msg.targetName)
  92. target = this.obj.instance.objects.objects.find(o => ((o.name) && (o.name.toLowerCase() === msg.targetName.toLowerCase())));
  93. this.target = null;
  94. if ((!target) || (!target.trade))
  95. return false;
  96. this.target = target;
  97. let itemList = target.trade.getItems(this.obj);
  98. let markup = target.trade.markup.sell;
  99. if (msg.action === 'buyback') {
  100. itemList = target.trade.buybackList[this.obj.name] || [];
  101. markup = target.trade.markup.buy;
  102. }
  103. this.obj.syncer.set(true, 'trade', 'buyList', {
  104. markup: markup,
  105. items: itemList,
  106. buyback: (msg.action === 'buyback')
  107. });
  108. },
  109. buySell: function (msg) {
  110. if (msg.action === 'buy')
  111. this.buy(msg);
  112. else if (msg.action === 'sell')
  113. this.sell(msg);
  114. else if (msg.action === 'buyback')
  115. this.buyback(msg);
  116. },
  117. buy: async function (msg) {
  118. let target = this.target;
  119. if (!target)
  120. return;
  121. let item = null;
  122. let targetTrade = target.trade;
  123. let markup = targetTrade.markup.sell;
  124. if (msg.action === 'buyback') {
  125. item = targetTrade.findBuyback(msg.itemId, this.obj.name);
  126. markup = targetTrade.markup.buy;
  127. } else
  128. item = targetTrade.findItem(msg.itemId, this.obj.name);
  129. if (!item) {
  130. this.resolveCallback(msg);
  131. return;
  132. }
  133. let canAfford = false;
  134. if (item.worth.currency) {
  135. let currencyItem = this.obj.inventory.items.find(i => (i.name === item.worth.currency));
  136. canAfford = ((currencyItem) && (currencyItem.quantity >= item.worth.amount));
  137. } else
  138. canAfford = this.gold >= ~~(item.worth * markup);
  139. if (!canAfford) {
  140. sendMessage(this.obj, 'color-redA', 'You can\'t afford that item.');
  141. this.resolveCallback(msg);
  142. return;
  143. }
  144. if (!targetTrade.canBuy(msg.itemId, this.obj, msg.action)) {
  145. this.resolveCallback(msg);
  146. return;
  147. }
  148. if (item.type === 'skin') {
  149. let haveSkin = await this.obj.auth.doesOwnSkin(item.skinId);
  150. if (haveSkin) {
  151. sendMessage(this.obj, 'color-redA', 'You have already unlocked that skin.');
  152. this.resolveCallback(msg);
  153. return;
  154. }
  155. }
  156. if (msg.action === 'buyback')
  157. targetTrade.removeBuyback(msg.itemId, this.obj.name);
  158. else if ((item.type !== 'skin') && (!item.infinite)) {
  159. targetTrade.removeItem(msg.itemId, this.obj.name);
  160. targetTrade.genLeft++;
  161. }
  162. if (item.type !== 'skin') {
  163. //Some shop items have both an in-shop definition (item) as well as a definition
  164. // for the item that should be given to the player (giveItem)
  165. let clonedItem = extend({}, item.giveItem || item);
  166. if (item.worth.currency)
  167. clonedItem.worth = 0;
  168. if ((item.stats) && (item.stats.stats)) {
  169. delete clonedItem.stats;
  170. statGenerator.generate(clonedItem, {});
  171. }
  172. delete clonedItem.infinite;
  173. if (clonedItem.generate) {
  174. clonedItem = generator.generate(clonedItem);
  175. delete clonedItem.generate;
  176. if (item.factions)
  177. clonedItem.factions = item.factions;
  178. }
  179. if (!this.obj.inventory.getItem(clonedItem)) {
  180. this.resolveCallback(msg);
  181. return;
  182. }
  183. if (!item.infinite)
  184. this.obj.syncer.setArray(true, 'trade', 'removeItems', item.id);
  185. }
  186. if (item.worth.currency) {
  187. let currencyItem = this.obj.inventory.items.find(i => (i.name === item.worth.currency));
  188. this.obj.inventory.destroyItem({ itemId: currencyItem.id }, item.worth.amount, true);
  189. } else {
  190. targetTrade.gold += ~~(item.worth * markup);
  191. this.gold -= ~~(item.worth * markup);
  192. this.obj.syncer.set(true, 'trade', 'gold', this.gold);
  193. }
  194. //Hack to always redraw the UI (to give items the red overlay if they can't be afforded)
  195. this.obj.syncer.setArray(true, 'trade', 'redraw', true);
  196. this.resolveCallback(msg);
  197. },
  198. buyback: function (msg) {
  199. msg.action = 'buyback';
  200. this.buy(msg);
  201. },
  202. sell: function (msg) {
  203. let target = this.target;
  204. if (!target)
  205. return;
  206. let targetTrade = target.trade;
  207. const item = this.obj.inventory.findItem(msg.itemId);
  208. if (!item || item.worth <= 0 || item.eq || item.noDestroy)
  209. return;
  210. const oldQuantity = item.quantity;
  211. this.obj.inventory.destroyItem({ itemId: msg.itemId });
  212. if (oldQuantity)
  213. item.quantity = oldQuantity;
  214. let worth = ~~((item.quantity ?? 1) * item.worth * targetTrade.markup.buy);
  215. this.gold += worth;
  216. this.obj.syncer.set(true, 'trade', 'gold', this.gold);
  217. this.obj.syncer.setArray(true, 'trade', 'removeItems', item.id);
  218. let buybackList = targetTrade.buybackList;
  219. let name = this.obj.name;
  220. if (!buybackList[name])
  221. buybackList[name] = [];
  222. buybackList[name].push(item);
  223. if (buybackList[name].length > this.maxBuyback)
  224. buybackList[name].splice(0, 1);
  225. },
  226. startSell: function (msg) {
  227. let target = msg.target;
  228. let targetName = (msg.targetName || '').toLowerCase();
  229. if (!target && !targetName)
  230. return false;
  231. if (target && !target.id)
  232. target = this.obj.instance.objects.objects.find(o => o.id === target);
  233. else if (targetName)
  234. target = this.obj.instance.objects.objects.find(o => ((o.name) && (o.name.toLowerCase() === targetName)));
  235. this.target = null;
  236. if ((!target) || (!target.trade))
  237. return false;
  238. this.target = target;
  239. let itemList = this.obj.inventory.items
  240. .filter(i => i.worth > 0 && !i.eq && !i.noDestroy);
  241. itemList = extend([], itemList);
  242. this.obj.syncer.set(true, 'trade', 'sellList', {
  243. markup: target.trade.markup.buy,
  244. items: itemList.map(i => this.obj.inventory.simplifyItem(i))
  245. });
  246. },
  247. startBuyback: function (msg) {
  248. msg.action = 'buyback';
  249. this.startBuy(msg);
  250. },
  251. removeItem: function (itemId) {
  252. return this.items.spliceFirstWhere(i => i.id === itemId);
  253. },
  254. removeBuyback: function (itemId, name) {
  255. return (this.buybackList[name] || []).spliceFirstWhere(i => i.id === itemId);
  256. },
  257. getItems: function (requestedBy) {
  258. let items = this.items.map(i => requestedBy.inventory.simplifyItem(i));
  259. return items;
  260. },
  261. canBuy: function (itemId, requestedBy, action) {
  262. let item = null;
  263. if (action === 'buy')
  264. item = this.findItem(itemId, requestedBy.name);
  265. else if (action === 'buyback')
  266. item = this.findBuyback(itemId, requestedBy.name);
  267. let result = true;
  268. if (item.factions)
  269. result = requestedBy.reputation.canEquipItem(item);
  270. if (!result) {
  271. const message = 'your reputation is too low to buy that item';
  272. requestedBy.social.notifySelf({ message });
  273. }
  274. return result;
  275. },
  276. findItem: function (itemId, sourceName) {
  277. return this.items.find(i => i.id === itemId);
  278. },
  279. findBuyback: function (itemId, sourceName) {
  280. return (this.buybackList[sourceName] || []).find(i => i.id === itemId);
  281. },
  282. resolveCallback: function (msg, result) {
  283. let callbackId = msg.has('callbackId') ? msg.callbackId : msg;
  284. result = result || [];
  285. if (!callbackId)
  286. return;
  287. process.send({
  288. module: 'atlas',
  289. method: 'resolveCallback',
  290. msg: {
  291. id: callbackId,
  292. result: result
  293. }
  294. });
  295. },
  296. simplify: function (self) {
  297. let result = {
  298. type: 'trade'
  299. };
  300. if (self)
  301. result.gold = this.gold;
  302. return result;
  303. },
  304. events: {
  305. beforeMove: function () {
  306. if (!this.target)
  307. return;
  308. this.obj.syncer.set(true, 'trade', 'closeTrade', true);
  309. this.target = null;
  310. }
  311. }
  312. };