Nelze vybrat více než 25 témat Téma musí začínat písmenem nebo číslem, může obsahovat pomlčky („-“) a může být dlouhé až 35 znaků.
 
 
 

193 řádky
4.5 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, hasNoItems = false }, preferStat) => {
  56. const { level } = blueprint;
  57. const cpnInventory = mob.addComponent('inventory', drops);
  58. cpnInventory.inventorySize = -1;
  59. cpnInventory.dailyDrops = blueprint.dailyDrops;
  60. if (hasNoItems !== true) {
  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. }
  74. };
  75. const buildCpnSpells = (mob, blueprint, typeDefinition, preferStat) => {
  76. const dmgMult = 4.5 * typeDefinition.dmgMult * dmgMults[blueprint.level - 1];
  77. const spells = extend([], blueprint.spells);
  78. spells.forEach(s => {
  79. if (!s.animation && mob.sheetName === 'mobs' && animations.mobs[mob.cell])
  80. s.animation = 'basic';
  81. });
  82. mob.addComponent('spellbook', { spells });
  83. let spellCount = 0;
  84. if (mob.isRare)
  85. spellCount = 1;
  86. for (let i = 0; i < spellCount; i++) {
  87. const rune = itemGenerator.generate({ spell: true });
  88. rune.eq = true;
  89. mob.inventory.getItem(rune);
  90. }
  91. mob.spellbook.spells.forEach(s => {
  92. s.dmgMult = s.name ? dmgMult / 3 : dmgMult;
  93. s.statType = preferStat;
  94. s.manaCost = 0;
  95. });
  96. };
  97. const fnComponentGenerators = [
  98. buildCpnMob, buildCpnStats, buildCpnInventory, buildCpnSpells
  99. ];
  100. //Main Generator
  101. /*
  102. mob = the mob object
  103. blueprint = mob blueprint (normally from the zoneFile)
  104. type = regular,rare
  105. zoneName = the name of the zone
  106. */
  107. const build = (mob, blueprint, type, zoneName) => {
  108. mob.instance.eventEmitter.emit('onBeforeBuildMob', zoneName, mob.name.toLowerCase(), blueprint);
  109. const typeDefinition = blueprint[type] || blueprint;
  110. if (blueprint.nonSelectable)
  111. mob.nonSelectable = true;
  112. mob.addComponent('effects');
  113. if (type === 'rare') {
  114. mob.effects.addEffect({ type: 'rare' });
  115. mob.isRare = true;
  116. mob.baseName = mob.name;
  117. mob.name = typeDefinition.name ?? mob.name;
  118. }
  119. if (typeDefinition.sheetName)
  120. mob.sheetName = typeDefinition.sheetName;
  121. if (typeDefinition.has('cell'))
  122. mob.cell = typeDefinition.cell;
  123. mob.addComponent('equipment');
  124. const preferStat = statSelector[~~(Math.random() * 3)];
  125. fnComponentGenerators.forEach(fn => fn(mob, blueprint, typeDefinition, preferStat));
  126. if (blueprint.attackable !== false) {
  127. mob.addComponent('aggro', { faction: blueprint.faction });
  128. mob.aggro.calcThreatCeiling(type);
  129. }
  130. const zoneConfig = instancer.instances[0].map.zoneConfig;
  131. const chats = zoneConfig?.chats?.[mob.name.toLowerCase()];
  132. if (chats)
  133. mob.addComponent('chatter', { chats });
  134. const dialogues = zoneConfig?.dialogues?.[mob.name.toLowerCase()];
  135. if (dialogues)
  136. mob.addComponent('dialogue', { config: dialogues });
  137. if (blueprint?.properties?.cpnTrade)
  138. mob.addComponent('trade', blueprint.properties.cpnTrade);
  139. mob.instance.eventEmitter.emit('onAfterBuildMob', {
  140. zoneName,
  141. mob
  142. });
  143. const statValues = mob.stats.values;
  144. statValues.hp = statValues.hpMax;
  145. syncStats.forEach(s => mob.syncer.setObject(false, 'stats', 'values', s, statValues[s]));
  146. };
  147. module.exports = { build };