Você não pode selecionar mais de 25 tópicos Os tópicos devem começar com uma letra ou um número, podem incluir traços ('-') e podem ter até 35 caracteres.
 
 
 

675 linhas
16 KiB

  1. define([
  2. 'config/animations',
  3. 'config/loginRewards',
  4. 'config/classes'
  5. ], function (
  6. animations,
  7. loginRewards,
  8. classes
  9. ) {
  10. var baseStats = {
  11. mana: 20,
  12. manaMax: 20,
  13. manaReservePercent: 0,
  14. hp: 5,
  15. hpMax: 5,
  16. xpTotal: 0,
  17. xp: 0,
  18. xpMax: 0,
  19. level: 1,
  20. str: 0,
  21. int: 0,
  22. dex: 0,
  23. magicFind: 0,
  24. itemQuantity: 0,
  25. regenHp: 0,
  26. regenMana: 5,
  27. addCritChance: 0,
  28. addCritMultiplier: 0,
  29. critChance: 5,
  30. critMultiplier: 150,
  31. armor: 0,
  32. dmgPercent: 0,
  33. vit: 0,
  34. blockAttackChance: 0,
  35. blockSpellChance: 0,
  36. attackSpeed: 0,
  37. castSpeed: 0,
  38. elementArcanePercent: 0,
  39. elementFrostPercent: 0,
  40. elementFirePercent: 0,
  41. elementHolyPercent: 0,
  42. elementPhysicalPercent: 0,
  43. elementPoisonPercent: 0,
  44. elementArcaneResist: 0,
  45. elementFrostResist: 0,
  46. elementFireResist: 0,
  47. elementHolyResist: 0,
  48. elementPhysicalResist: 0,
  49. elementPoisonResist: 0,
  50. elementAllResist: 0,
  51. sprintChance: 0,
  52. xpIncrease: 0,
  53. //fishing stats
  54. catchChance: 0,
  55. catchSpeed: 0,
  56. fishRarity: 0,
  57. fishWeight: 0,
  58. fishItems: 0
  59. };
  60. return {
  61. type: 'stats',
  62. values: baseStats,
  63. originalValues: null,
  64. vitScale: 10,
  65. syncer: null,
  66. stats: {
  67. logins: 0,
  68. played: 0,
  69. lastLogin: null,
  70. loginStreak: 0
  71. },
  72. dead: false,
  73. init: function (blueprint, isTransfer) {
  74. this.syncer = this.obj.instance.syncer;
  75. var values = (blueprint || {}).values || {};
  76. for (var v in values) {
  77. this.values[v] = values[v];
  78. }
  79. var stats = (blueprint || {}).stats || {};
  80. for (var v in stats) {
  81. this.stats[v] = stats[v];
  82. }
  83. this.calcXpMax();
  84. if (blueprint)
  85. delete blueprint.stats;
  86. },
  87. resetHp: function () {
  88. var values = this.values;
  89. values.hp = values.hpMax;
  90. this.obj.syncer.setObject(false, 'stats', 'values', 'hp', values.hp);
  91. },
  92. update: function () {
  93. if (((this.obj.mob) && (!this.obj.follower)) || (this.dead))
  94. return;
  95. var values = this.values;
  96. var manaMax = values.manaMax;
  97. manaMax -= (manaMax * values.manaReservePercent);
  98. var regen = {
  99. success: true
  100. };
  101. this.obj.fireEvent('beforeRegen', regen);
  102. if (!regen.success)
  103. return;
  104. var isInCombat = (this.obj.aggro.list.length > 0);
  105. if (this.obj.follower) {
  106. isInCombat = (this.obj.follower.master.aggro.list.length > 0);
  107. if (isInCombat)
  108. return;
  109. }
  110. var regenHp = 0;
  111. var regenMana = 0;
  112. regenMana = values.regenMana / 50;
  113. if (!isInCombat)
  114. regenHp = Math.max(values.hpMax / 112, values.regenHp * 0.2);
  115. else
  116. regenHp = values.regenHp * 0.2;
  117. if (values.hp < values.hpMax) {
  118. values.hp += regenHp;
  119. this.obj.syncer.setObject(false, 'stats', 'values', 'hp', this.values.hp);
  120. }
  121. if (values.hp > values.hpMax) {
  122. values.hp = values.hpMax;
  123. this.obj.syncer.setObject(false, 'stats', 'values', 'hp', values.hp);
  124. }
  125. if (values.mana < manaMax) {
  126. values.mana += regenMana;
  127. //Show others what mana is?
  128. var onlySelf = true;
  129. if (this.obj.player)
  130. onlySelf = false;
  131. this.obj.syncer.setObject(onlySelf, 'stats', 'values', 'mana', values.mana);
  132. }
  133. if (values.mana > manaMax) {
  134. values.mana = manaMax;
  135. if (this.obj.player)
  136. onlySelf = false;
  137. this.obj.syncer.setObject(onlySelf, 'stats', 'values', 'mana', values.mana);
  138. }
  139. },
  140. addStat: function (stat, value) {
  141. if (['lvlRequire', 'allAttributes'].indexOf(stat) == -1)
  142. this.values[stat] += value;
  143. var sendOnlyToSelf = (['hp', 'hpMax', 'mana', 'manaMax', 'vit'].indexOf(stat) == -1);
  144. this.obj.syncer.setObject(sendOnlyToSelf, 'stats', 'values', stat, this.values[stat]);
  145. if (stat == 'addCritChance') {
  146. this.values.critChance += (0.05 * value);
  147. this.obj.syncer.setObject(true, 'stats', 'values', 'critChance', this.values.critChance);
  148. } else if (stat == 'addCritMultiplier') {
  149. this.values.critMultiplier += value;
  150. this.obj.syncer.setObject(true, 'stats', 'values', 'critMultiplier', this.values.critMultiplier);
  151. } else if (stat == 'vit') {
  152. this.values.hpMax += (value * this.vitScale);
  153. this.obj.syncer.setObject(true, 'stats', 'values', 'hpMax', this.values.hpMax);
  154. this.obj.syncer.setObject(false, 'stats', 'values', 'hpMax', this.values.hpMax);
  155. } else if (stat == 'allAttributes') {
  156. ['int', 'str', 'dex'].forEach(function (s) {
  157. this.values[s] += value;
  158. this.obj.syncer.setObject(true, 'stats', 'values', s, this.values[s]);
  159. }, this);
  160. } else if (stat == 'elementAllResist') {
  161. ['arcane', 'frost', 'fire', 'holy', 'physical', 'poison'].forEach(function (s) {
  162. var element = 'element' + (s[0].toUpperCase() + s.substr(1)) + 'Resist';
  163. this.values[element] += value;
  164. this.obj.syncer.setObject(true, 'stats', 'values', element, this.values[element]);
  165. }, this);
  166. }
  167. },
  168. calcXpMax: function () {
  169. var level = this.values.level;
  170. this.values.xpMax = (level * 5) + ~~(level * 10 * Math.pow(level, 2.2));
  171. this.obj.syncer.setObject(true, 'stats', 'values', 'xpMax', this.values.xpMax);
  172. },
  173. getXp: function (amount) {
  174. var obj = this.obj;
  175. var values = this.values;
  176. if (values.level == 20)
  177. return;
  178. amount = ~~(amount * (1 + (values.xpIncrease / 100)));
  179. values.xpTotal = ~~(values.xpTotal + amount);
  180. values.xp = ~~(values.xp + amount);
  181. this.syncer.queue('onGetDamage', {
  182. id: obj.id,
  183. event: true,
  184. text: '+' + amount + ' xp'
  185. });
  186. var syncO = {};
  187. var didLevelUp = false;
  188. while (values.xp >= values.xpMax) {
  189. didLevelUp = true;
  190. values.xp -= values.xpMax;
  191. values.level++;
  192. if (values.level == 20)
  193. values.xp = 0;
  194. values.hpMax = values.level * 32.7;
  195. var gainStats = classes.stats[this.obj.class].gainStats;
  196. for (var s in gainStats) {
  197. values[s] += gainStats[s];
  198. }
  199. this.obj.spellbook.calcDps();
  200. this.syncer.queue('onGetDamage', {
  201. id: obj.id,
  202. event: true,
  203. text: 'level up'
  204. });
  205. syncO.level = values.level;
  206. this.calcXpMax();
  207. }
  208. if (didLevelUp) {
  209. var cellContents = obj.instance.physics.getCell(obj.x, obj.y);
  210. cellContents.forEach(function (c) {
  211. c.fireEvent('onCellPlayerLevelUp', obj);
  212. });
  213. obj.auth.doSave();
  214. }
  215. process.send({
  216. method: 'object',
  217. serverId: this.obj.serverId,
  218. obj: syncO
  219. });
  220. if (didLevelUp) {
  221. var maxLevel = this.obj.instance.zone.level[1]
  222. this.rescale(maxLevel, false);
  223. }
  224. },
  225. kill: function (target) {
  226. var level = target.stats.values.level;
  227. //Who should get xp?
  228. var aggroList = target.aggro.list;
  229. var hpMax = target.stats.values.hpMax;
  230. var aLen = aggroList.length;
  231. for (var i = 0; i < aLen; i++) {
  232. var a = aggroList[i];
  233. var dmg = a.damage;
  234. if (dmg <= 0)
  235. continue;
  236. var mult = 1;
  237. //How many party members contributed
  238. // Remember, maybe one of the aggro-ees might be a mob too
  239. var party = a.obj.social ? a.obj.social.party : null;
  240. if (party) {
  241. var partySize = aggroList.filter(function (f) {
  242. return ((a.damage > 0) && (party.indexOf(f.obj.serverId) > -1));
  243. }).length;
  244. partySize--;
  245. mult = (1 + (partySize * 0.1));
  246. }
  247. if ((a.obj.stats) && (!a.obj.follower)) {
  248. //Scale xp by source level so you can't just farm low level mobs (or get boosted on high level mobs).
  249. //Mobs that are farther then 10 levels from you, give no xp
  250. //We don't currently do this for quests/herb gathering
  251. var sourceLevel = a.obj.stats.values.level;
  252. var levelDelta = level - sourceLevel;
  253. var amount = level * 10 * mult;
  254. if (Math.abs(levelDelta) <= 10)
  255. amount = ~~(((sourceLevel + levelDelta) * 10) * Math.pow(1 - (Math.abs(levelDelta) / 10), 2) * mult);
  256. else
  257. amount = 0;
  258. a.obj.stats.getXp(amount, this.obj);
  259. }
  260. a.obj.fireEvent('afterKillMob', target);
  261. }
  262. },
  263. die: function (source) {
  264. this.values.hp = this.values.hpMax;
  265. this.values.mana = this.values.manaMax;
  266. this.obj.syncer.setObject(false, 'stats', 'values', 'hp', this.values.hp);
  267. this.obj.syncer.setObject(false, 'stats', 'values', 'mana', this.values.mana);
  268. this.syncer.queue('onGetDamage', {
  269. id: this.obj.id,
  270. event: true,
  271. text: 'death'
  272. });
  273. this.syncer.queue('onDeath', {
  274. x: this.obj.x,
  275. y: this.obj.y,
  276. source: source.name
  277. }, [this.obj.serverId]);
  278. },
  279. takeDamage: function (damage, threatMult, source) {
  280. source.fireEvent('beforeDealDamage', damage, this.obj);
  281. this.obj.fireEvent('beforeTakeDamage', damage, source);
  282. //Maybe the attacker was stunned?
  283. if (damage.failed)
  284. return;
  285. //Maybe something else killed this mob already?
  286. if (this.obj.destroyed)
  287. return;
  288. var amount = damage.amount;
  289. if (amount > this.values.hp)
  290. amount = this.values.hp;
  291. this.values.hp -= amount;
  292. var recipients = [];
  293. if (this.obj.serverId != null)
  294. recipients.push(this.obj.serverId);
  295. if (source.serverId != null)
  296. recipients.push(source.serverId);
  297. if ((source.follower) && (source.follower.master.serverId))
  298. recipients.push(source.follower.master.serverId);
  299. if ((this.obj.follower) && (this.obj.follower.master.serverId))
  300. recipients.push(this.obj.follower.master.serverId);
  301. if (recipients.length > 0) {
  302. if (!damage.blocked) {
  303. this.syncer.queue('onGetDamage', {
  304. id: this.obj.id,
  305. source: source.id,
  306. crit: damage.crit,
  307. amount: amount
  308. }, recipients);
  309. } else {
  310. this.syncer.queue('onGetDamage', {
  311. id: this.obj.id,
  312. source: source.id,
  313. event: true,
  314. text: 'blocked'
  315. }, recipients);
  316. }
  317. }
  318. this.obj.aggro.tryEngage(source, amount, threatMult);
  319. var died = (this.values.hp <= 0);
  320. if (died) {
  321. var death = {
  322. success: true
  323. };
  324. this.obj.fireEvent('beforeDeath', death);
  325. if (death.success) {
  326. var deathEvent = {};
  327. var killSource = source;
  328. if (source.follower)
  329. killSource = source.follower.master;
  330. if (killSource.player)
  331. killSource.stats.kill(this.obj);
  332. this.obj.fireEvent('afterDeath', deathEvent);
  333. if (this.obj.player) {
  334. this.obj.syncer.setObject(false, 'stats', 'values', 'hp', this.values.hp);
  335. if (deathEvent.permadeath) {
  336. this.obj.auth.permadie();
  337. this.obj.instance.syncer.queue('onGetMessages', {
  338. messages: {
  339. class: 'color-red',
  340. message: `(level ${this.values.level}) ${this.obj.name} has forever left the shores of the living.`
  341. }
  342. });
  343. this.syncer.queue('onPermadeath', {
  344. source: killSource.name
  345. }, [this.obj.serverId]);
  346. } else
  347. this.values.hp = 0;
  348. this.obj.player.die(killSource, deathEvent.permadeath);
  349. } else {
  350. this.obj.effects.die();
  351. if (this.obj.spellbook)
  352. this.obj.spellbook.die();
  353. this.obj.destroyed = true;
  354. var deathAnimation = _.getDeepProperty(animations, ['mobs', this.obj.sheetName, this.obj.cell, 'death']);
  355. if (deathAnimation) {
  356. this.obj.instance.syncer.queue('onGetObject', {
  357. x: this.obj.x,
  358. y: this.obj.y,
  359. components: [deathAnimation]
  360. });
  361. }
  362. if (this.obj.inventory) {
  363. var aggroList = this.obj.aggro.list;
  364. var aLen = aggroList.length;
  365. var done = [];
  366. for (var i = 0; i < aLen; i++) {
  367. var a = aggroList[i].obj;
  368. if (done.some(d => d == a.serverId))
  369. continue;
  370. if ((a.social) && (a.social.party)) {
  371. a.social.party.forEach(function (p) {
  372. if (done.some(d => d == p))
  373. return;
  374. this.obj.inventory.dropBag(p, killSource);
  375. done.push(p);
  376. }, this);
  377. } else {
  378. if (a.serverId == null)
  379. continue;
  380. this.obj.inventory.dropBag(a.serverId, killSource);
  381. done.push(a.serverId);
  382. }
  383. }
  384. }
  385. }
  386. }
  387. } else {
  388. source.aggro.tryEngage(this.obj, 0);
  389. this.obj.syncer.setObject(false, 'stats', 'values', 'hp', this.values.hp);
  390. }
  391. if (!damage.noEvents)
  392. source.fireEvent('afterDealDamage', damage, this.obj);
  393. },
  394. getHp: function (heal, source) {
  395. var amount = heal.amount;
  396. if (amount == 0)
  397. return;
  398. var threatMult = heal.threatMult;
  399. if (!heal.hasOwnProperty('threatMult'))
  400. threatMult = 1;
  401. var values = this.values;
  402. var hpMax = values.hpMax;
  403. if (values.hp >= hpMax)
  404. return;
  405. if (hpMax - values.hp < amount)
  406. amount = hpMax - values.hp;
  407. values.hp += amount;
  408. if (values.hp > hpMax)
  409. values.hp = hpMax;
  410. var recipients = [];
  411. if (this.obj.serverId != null)
  412. recipients.push(this.obj.serverId);
  413. if (source.serverId != null)
  414. recipients.push(source.serverId);
  415. if (recipients.length > 0) {
  416. this.syncer.queue('onGetDamage', {
  417. id: this.obj.id,
  418. source: source.id,
  419. heal: true,
  420. amount: amount,
  421. crit: heal.crit
  422. }, recipients);
  423. }
  424. //Add aggro to all our attackers
  425. var threat = amount * 0.4 * threatMult;
  426. var aggroList = this.obj.aggro.list;
  427. var aLen = aggroList.length;
  428. for (var i = 0; i < aLen; i++) {
  429. var a = aggroList[i].obj;
  430. a.aggro.tryEngage(source, threat);
  431. }
  432. this.obj.syncer.setObject(false, 'stats', 'values', 'hp', values.hp);
  433. },
  434. save: function () {
  435. if (this.sessionDuration) {
  436. this.stats.played = ~~(this.stats.played + this.sessionDuration);
  437. delete this.sessionDuration;
  438. }
  439. return {
  440. type: 'stats',
  441. values: this.originalValues || this.values,
  442. stats: this.stats
  443. };
  444. },
  445. simplify: function (self) {
  446. var values = this.values;
  447. if (!self) {
  448. var result = {
  449. type: 'stats',
  450. values: {
  451. hp: values.hp,
  452. hpMax: values.hpMax,
  453. mana: values.mana,
  454. manaMax: values.manaMax,
  455. level: values.level
  456. }
  457. };
  458. return result
  459. }
  460. return {
  461. type: 'stats',
  462. values: values,
  463. stats: this.stats,
  464. vitScale: this.vitScale
  465. };
  466. },
  467. onLogin: function () {
  468. var stats = this.stats;
  469. var scheduler = require('misc/scheduler');
  470. var time = scheduler.getTime();
  471. var lastLogin = stats.lastLogin;
  472. if ((!lastLogin) || (lastLogin.day != time.day)) {
  473. var daysSkipped = 1;
  474. if (lastLogin) {
  475. if (time.day > lastLogin.day)
  476. daysSkipped = time.day - lastLogin.day;
  477. else {
  478. var daysInMonth = scheduler.daysInMonth(lastLogin.month);
  479. daysSkipped = (daysInMonth - lastLogin.day) + time.day;
  480. for (var i = lastLogin.month + 1; i < time.month - 1; i++) {
  481. daysSkipped += scheduler.daysInMonth(i);
  482. }
  483. }
  484. }
  485. if (daysSkipped == 1) {
  486. stats.loginStreak++;
  487. if (stats.loginStreak > 21)
  488. stats.loginStreak = 21;
  489. } else {
  490. stats.loginStreak -= (daysSkipped - 1);
  491. if (stats.loginStreak < 1)
  492. stats.loginStreak = 1;
  493. }
  494. var mail = this.obj.instance.mail;
  495. var rewards = loginRewards.generate(stats.loginStreak);
  496. mail.sendMail(this.obj.name, rewards);
  497. } else
  498. this.obj.instance.mail.getMail(this.obj.name);
  499. stats.lastLogin = time;
  500. },
  501. rescale: function (level, isMob) {
  502. if (level >= this.values.level)
  503. return;
  504. var sync = this.obj.syncer.setObject.bind(this.obj.syncer);
  505. var oldHp = this.values.hp;
  506. var oldXp = this.values.xp;
  507. var oldXpTotal = this.values.xpTotal;
  508. var oldXpMax = this.values.xpMax;
  509. if (!this.originalValues)
  510. this.originalValues = extend(true, {}, this.values);
  511. var oldValues = this.values;
  512. var newValues = extend(true, {}, baseStats);
  513. this.values = newValues;
  514. var gainStats = classes.stats[this.obj.class].gainStats;
  515. for (var s in gainStats) {
  516. newValues[s] += (gainStats[s] * level);
  517. }
  518. newValues.level = level;
  519. newValues.originalLevel = (this.originalValues || oldValues).level;
  520. newValues.hpMax = level * 32.7;
  521. if (isMob)
  522. newValues.hpMax = ~~(newValues.hpMax * (level / 10));
  523. newValues.hp = oldHp;
  524. var resetHp = false;
  525. if (newValues.hp > newValues.hpMax) {
  526. resetHp = true;
  527. newValues.hp = newValues.hpMax;
  528. }
  529. newValues.xp = oldXp;
  530. newValues.xpMax = oldXpMax;
  531. newValues.xpTotal = oldXpTotal;
  532. var addStats = this.obj.equipment.rescale(level);
  533. for (var p in addStats) {
  534. var statName = p;
  535. this.addStat(statName, addStats[p]);
  536. }
  537. if (resetHp)
  538. newValues.hp = newValues.hpMax;
  539. this.obj.spellbook.calcDps();
  540. for (var p in newValues) {
  541. sync(true, 'stats', 'values', p, newValues[p]);
  542. }
  543. }
  544. };
  545. });