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.
 
 
 

204 lines
4.8 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. }
  49. });
  50. //Hack to disallow low level mobs from having any lifeOnHit
  51. // since that makes it very difficult (and confusing) for low level players
  52. if (level <= 3)
  53. cpnStats.values.lifeOnHit = 0;
  54. };
  55. const buildCpnInventory = (mob, blueprint, { drops }, preferStat) => {
  56. const { level } = blueprint;
  57. const cpnInventory = mob.addComponent('inventory', drops);
  58. cpnInventory.inventorySize = -1;
  59. cpnInventory.dailyDrops = blueprint.dailyDrops;
  60. if (!drops?.blueprints || drops?.alsoRandom) {
  61. generateSlots.forEach(slot => {
  62. const item = itemGenerator.generate({
  63. noSpell: true,
  64. level,
  65. slot,
  66. quality: 4,
  67. forceStats: [preferStat]
  68. });
  69. delete item.spell;
  70. item.eq = true;
  71. cpnInventory.getItem(item);
  72. });
  73. } else {
  74. //TODO: Don't give mobs these items: they'll drop them anyway
  75. drops.blueprints.forEach(d => {
  76. if (d.type === 'key')
  77. return;
  78. const drop = extend({}, d);
  79. drop.level = level;
  80. cpnInventory.getItem(itemGenerator.generate(drop));
  81. });
  82. }
  83. };
  84. const buildCpnSpells = (mob, blueprint, typeDefinition, preferStat) => {
  85. const dmgMult = 4.5 * typeDefinition.dmgMult * dmgMults[blueprint.level - 1];
  86. const spells = extend([], blueprint.spells);
  87. spells.forEach(s => {
  88. if (!s.animation && mob.sheetName === 'mobs' && animations.mobs[mob.cell])
  89. s.animation = 'basic';
  90. });
  91. mob.addComponent('spellbook', { spells });
  92. let spellCount = 0;
  93. if (mob.isRare)
  94. spellCount = 1;
  95. for (let i = 0; i < spellCount; i++) {
  96. const rune = itemGenerator.generate({ spell: true });
  97. rune.eq = true;
  98. mob.inventory.getItem(rune);
  99. }
  100. mob.spellbook.spells.forEach(s => {
  101. s.dmgMult = s.name ? dmgMult / 3 : dmgMult;
  102. s.statType = preferStat;
  103. s.manaCost = 0;
  104. });
  105. };
  106. const fnComponentGenerators = [
  107. buildCpnMob, buildCpnStats, buildCpnInventory, buildCpnSpells
  108. ];
  109. //Main Generator
  110. /*
  111. mob = the mob object
  112. blueprint = mob blueprint (normally from the zoneFile)
  113. type = regular,rare
  114. zoneName = the name of the zone
  115. */
  116. const build = (mob, blueprint, type, zoneName) => {
  117. mob.instance.eventEmitter.emit('onBeforeBuildMob', zoneName, mob.name.toLowerCase(), blueprint);
  118. const typeDefinition = blueprint[type] || blueprint;
  119. if (blueprint.nonSelectable)
  120. mob.nonSelectable = true;
  121. mob.addComponent('effects');
  122. if (type === 'rare') {
  123. mob.effects.addEffect({ type: 'rare' });
  124. mob.isRare = true;
  125. mob.baseName = mob.name;
  126. mob.name = typeDefinition.name ?? mob.name;
  127. }
  128. if (typeDefinition.sheetName)
  129. mob.sheetName = typeDefinition.sheetName;
  130. if (typeDefinition.has('cell'))
  131. mob.cell = typeDefinition.cell;
  132. mob.addComponent('equipment');
  133. const preferStat = statSelector[~~(Math.random() * 3)];
  134. fnComponentGenerators.forEach(fn => fn(mob, blueprint, typeDefinition, preferStat));
  135. if (blueprint.attackable !== false) {
  136. mob.addComponent('aggro', { faction: blueprint.faction });
  137. mob.aggro.calcThreatCeiling(type);
  138. }
  139. const zoneConfig = instancer.instances[0].map.zoneConfig;
  140. const chats = zoneConfig?.chats?.[mob.name.toLowerCase()];
  141. if (chats)
  142. mob.addComponent('chatter', { chats });
  143. const dialogues = zoneConfig?.dialogues?.[mob.name.toLowerCase()];
  144. if (dialogues)
  145. mob.addComponent('dialogue', { config: dialogues });
  146. if (blueprint?.properties?.cpnTrade)
  147. mob.addComponent('trade', blueprint.properties.cpnTrade);
  148. mob.instance.eventEmitter.emit('onAfterBuildMob', {
  149. zoneName,
  150. mob
  151. });
  152. const statValues = mob.stats.values;
  153. statValues.hp = statValues.hpMax;
  154. syncStats.forEach(s => mob.syncer.setObject(false, 'stats', 'values', s, statValues[s]));
  155. };
  156. module.exports = { build };