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.
 
 
 

202 lines
4.7 KiB

  1. //Balance
  2. const { hpMults, dmgMults } = require('../config/consts');
  3. //Imports
  4. const animations = require('../config/animations');
  5. const itemGenerator = require('../items/generator');
  6. //Mobs will be given random items to equip for these slots
  7. const generateSlots = [
  8. 'head',
  9. 'chest',
  10. 'neck',
  11. 'hands',
  12. 'waist',
  13. 'legs',
  14. 'feet',
  15. 'finger',
  16. 'trinket',
  17. 'twoHanded'
  18. ];
  19. //Mobs will pick one of these stats to be force rolles onto their items
  20. const statSelector = ['str', 'dex', 'int'];
  21. //These stat values are synced to players
  22. const syncStats = ['hp', 'hpMax', 'mana', 'manaMax', 'level'];
  23. //Component generators
  24. const buildCpnMob = (mob, blueprint, typeDefinition) => {
  25. const { walkDistance, grantRep, deathRep, patrol, needLos } = blueprint;
  26. const cpnMob = mob.addComponent('mob');
  27. extend(cpnMob, {
  28. walkDistance,
  29. grantRep,
  30. deathRep,
  31. needLos
  32. });
  33. if (patrol !== undefined)
  34. cpnMob.patrol = blueprint.patrol;
  35. if (cpnMob.patrol)
  36. cpnMob.walkDistance = 1;
  37. };
  38. const buildCpnStats = (mob, blueprint, typeDefinition) => {
  39. const {
  40. level,
  41. hpMult: baseHpMult = typeDefinition.hpMult
  42. } = blueprint;
  43. const hpMax = ~~(level * 40 * hpMults[level - 1] * baseHpMult);
  44. const cpnStats = mob.addComponent('stats', {
  45. values: {
  46. level,
  47. hpMax,
  48. hp: hpMax
  49. }
  50. });
  51. //Hack to disallow low level mobs from having any lifeOnHit
  52. // since that makes it very difficult (and confusing) for low level players
  53. if (level <= 3)
  54. cpnStats.values.lifeOnHit = 0;
  55. };
  56. const buildCpnInventory = (mob, blueprint, { drops }, preferStat) => {
  57. const { level } = blueprint;
  58. const cpnInventory = mob.addComponent('inventory', drops);
  59. cpnInventory.inventorySize = -1;
  60. cpnInventory.dailyDrops = blueprint.dailyDrops;
  61. if (!drops?.blueprints || drops?.alsoRandom) {
  62. generateSlots.forEach(slot => {
  63. const item = itemGenerator.generate({
  64. noSpell: true,
  65. level,
  66. slot,
  67. quality: 4,
  68. forceStats: [preferStat]
  69. });
  70. delete item.spell;
  71. item.eq = true;
  72. cpnInventory.getItem(item);
  73. });
  74. } else {
  75. //TODO: Don't give mobs these items: they'll drop them anyway
  76. drops.blueprints.forEach(d => {
  77. if (d.type === 'key')
  78. return;
  79. const drop = extend({}, d);
  80. drop.level = level;
  81. cpnInventory.getItem(itemGenerator.generate(drop));
  82. });
  83. }
  84. };
  85. const buildCpnSpells = (mob, blueprint, typeDefinition, preferStat) => {
  86. const dmgMult = 4.5 * typeDefinition.dmgMult * dmgMults[blueprint.level - 1];
  87. const spells = extend([], blueprint.spells);
  88. mob.addComponent('spellbook', { spells });
  89. let spellCount = 0;
  90. if (mob.isRare)
  91. spellCount = 1;
  92. for (let i = 0; i < spellCount; i++) {
  93. const rune = itemGenerator.generate({ spell: true });
  94. rune.eq = true;
  95. mob.inventory.getItem(rune);
  96. }
  97. mob.spellbook.spells.forEach(s => {
  98. s.dmgMult = s.name ? dmgMult / 3 : dmgMult;
  99. s.statType = preferStat;
  100. s.manaCost = 0;
  101. if (!s.animation && mob.sheetName === 'mobs' && animations.mobs[mob.cell])
  102. s.animation = 'basic';
  103. });
  104. };
  105. const fnComponentGenerators = [
  106. buildCpnMob, buildCpnStats, buildCpnInventory, buildCpnSpells
  107. ];
  108. //Main Generator
  109. /*
  110. mob = the mob object
  111. blueprint = mob blueprint (normally from the zoneFile)
  112. type = regular,rare
  113. zoneName = the name of the zone
  114. */
  115. const build = (mob, blueprint, type, zoneName) => {
  116. mob.instance.eventEmitter.emit('onBeforeBuildMob', zoneName, mob.name.toLowerCase(), blueprint);
  117. const typeDefinition = blueprint[type] || blueprint;
  118. if (blueprint.nonSelectable)
  119. mob.nonSelectable = true;
  120. mob.addComponent('effects');
  121. if (type === 'rare') {
  122. mob.effects.addEffect({ type: 'rare' });
  123. mob.isRare = true;
  124. mob.baseName = mob.name;
  125. mob.name = typeDefinition.name ?? mob.name;
  126. }
  127. if (typeDefinition.sheetName)
  128. mob.sheetName = typeDefinition.sheetName;
  129. if (typeDefinition.has('cell'))
  130. mob.cell = typeDefinition.cell;
  131. mob.addComponent('equipment');
  132. const preferStat = statSelector[~~(Math.random() * 3)];
  133. fnComponentGenerators.forEach(fn => fn(mob, blueprint, typeDefinition, preferStat));
  134. if (blueprint.attackable !== false) {
  135. mob.addComponent('aggro', { faction: blueprint.faction });
  136. mob.aggro.calcThreatCeiling(type);
  137. }
  138. const zoneConfig = instancer.instances[0].map.zoneConfig;
  139. const chats = zoneConfig?.chats?.[mob.name.toLowerCase()];
  140. if (chats)
  141. mob.addComponent('chatter', { chats });
  142. const dialogues = zoneConfig?.dialogues?.[mob.name.toLowerCase()];
  143. if (dialogues)
  144. mob.addComponent('dialogue', { config: dialogues });
  145. if (blueprint?.properties?.cpnTrade)
  146. mob.addComponent('trade', blueprint.properties.cpnTrade);
  147. mob.instance.eventEmitter.emit('onAfterBuildMob', {
  148. zoneName,
  149. mob
  150. });
  151. const statValues = mob.stats.values;
  152. syncStats.forEach(s => mob.syncer.setObject(false, 'stats', 'values', s, statValues[s]));
  153. };
  154. module.exports = { build };