Browse Source

refactor #1944: cleaned up mob builder and related functions

tags/v0.12.0
Shaun 1 year ago
parent
commit
9f94dc9859
9 changed files with 178 additions and 194 deletions
  1. +1
    -2
      src/server/components/aggro.js
  2. +2
    -20
      src/server/components/equipment.js
  3. +0
    -2
      src/server/components/spellbook.js
  4. +0
    -2
      src/server/components/stats.js
  5. +6
    -0
      src/server/config/consts.js
  6. +0
    -3
      src/server/config/effects/effectChampion.js
  7. +1
    -0
      src/server/world/atlas.js
  8. +166
    -158
      src/server/world/mobBuilder.js
  9. +2
    -7
      src/server/world/spawners.js

+ 1
- 2
src/server/components/aggro.js View File

@@ -1,7 +1,6 @@
const configThreatCeiling = {
regular: 1,
rare: 0.5,
champion: 0.2
rare: 0.5
};

module.exports = {


+ 2
- 20
src/server/components/equipment.js View File

@@ -4,7 +4,6 @@ module.exports = {
type: 'equipment',

eq: {},
doAutoEq: true,

quickSlots: {},

@@ -32,24 +31,6 @@ module.exports = {
return !this.eq.has(slot);
},

autoEquip: function (itemId) {
if (!this.doAutoEq)
return;

let item = this.obj.inventory.findItem(itemId);
if (!item)
return;
else if ((!item.slot) || (item.material) || (item.quest) || (item.ability) || (!this.obj.inventory.canEquipItem(item))) {
item.eq = false;
return;
}

if (!this.eq.has(item.slot)) {
this.equip({ itemId });
return true;
}
},

equip: function (itemId) {
let slot = null;
if (typeof (itemId) === 'object') {
@@ -122,7 +103,8 @@ module.exports = {
this.eq[slot] = itemId;
item.equipSlot = slot;

obj.spellbook.calcDps();
if (obj.spellbook)
obj.spellbook.calcDps();

if ((!obj.mob) || (item.ability)) {
if (item.spell)


+ 0
- 2
src/server/components/spellbook.js View File

@@ -27,8 +27,6 @@ module.exports = {
this.objects = this.obj.instance.objects;
this.physics = this.obj.instance.physics;

this.dmgMult = blueprint.dmgMult;

(blueprint.spells || []).forEach(s => this.addSpell(s, -1));

if (blueprint.rotation) {


+ 0
- 2
src/server/components/stats.js View File

@@ -348,8 +348,6 @@ module.exports = {
let mobDiffMult = 1;
if (target.isRare)
mobDiffMult = 2;
else if (target.isChampion)
mobDiffMult = 5;

//Who should get xp?
let aggroList = target.aggro.list;


+ 6
- 0
src/server/config/consts.js View File

@@ -5,6 +5,12 @@ module.exports = {
//The maximum level a player can reach
maxLevel: 20,

//Rune damage is multiplied by nth entry from this array where n = level - 1
dmgMults: [0.25, 0.4, 0.575, 0.8, 1, 1.1, 1.2, 1.3, 1.4, 1.5, 1.6, 1.7, 1.8, 1.9, 2, 2.1, 2.2, 2.3, 2.4, 2.5],

//Mob HP is multiplied by nth entry from this array where n = level - 1
hpMults: [0.1, 0.2, 0.4, 0.7, 0.78, 0.91, 1.16, 1.19, 1.65, 2.36, 3.07, 3.55, 4.1, 4.85, 5.6, 5.9, 6.5, 7.1, 7.9, 12],

//How far a player can see objects horizontally
viewDistanceX: 25,



+ 0
- 3
src/server/config/effects/effectChampion.js View File

@@ -1,3 +0,0 @@
module.exports = {
type: 'champion'
};

+ 1
- 0
src/server/world/atlas.js View File

@@ -196,6 +196,7 @@ module.exports = {
try {
global[message.module][message.method](message);
} catch (e) {
/* eslint-disable-next-line no-console */
console.log('No global method found', message.module, message.method);
process.exit();
}


+ 166
- 158
src/server/world/mobBuilder.js View File

@@ -1,193 +1,201 @@
let animations = require('../config/animations');
let itemGenerator = require('../items/generator');

//const track = {};
//Balance
const { hpMults, dmgMults } = require('../config/consts');

//Imports
const animations = require('../config/animations');
const itemGenerator = require('../items/generator');

//Mobs will be given random items to equip for these slots
const generateSlots = [
'head',
'chest',
'neck',
'hands',
'waist',
'legs',
'feet',
'finger',
'trinket',
'twoHanded'
];

//Mobs will pick one of these stats to be force rolles onto their items
const statSelector = ['str', 'dex', 'int'];

//These stat values are synced to players
const syncStats = ['hp', 'hpMax', 'mana', 'manaMax', 'level'];

//Component generators
const buildCpnMob = (mob, blueprint, typeDefinition) => {
const { walkDistance, grantRep, deathRep, patrol, needLos } = blueprint;

const cpnMob = mob.addComponent('mob');
extend(cpnMob, {
walkDistance,
grantRep,
deathRep,
needLos
});

if (patrol !== undefined)
cpnMob.patrol = blueprint.patrol;

if (cpnMob.patrol)
cpnMob.walkDistance = 1;
};

module.exports = {
build: function (mob, blueprint, type, zoneName) {
mob.instance.eventEmitter.emit('onBeforeBuildMob', zoneName, mob.name.toLowerCase(), blueprint);
const buildCpnStats = (mob, blueprint, typeDefinition) => {
const {
level,
hpMult: baseHpMult = typeDefinition.hpMult
} = blueprint;

let typeDefinition = blueprint[type] || blueprint;
const hpMax = ~~(level * 40 * hpMults[level - 1] * baseHpMult);

if (blueprint.nonSelectable)
mob.nonSelectable = true;
const cpnStats = mob.addComponent('stats', {
values: {
level,
hpMax,
hp: hpMax
}
});

mob.addComponent('effects');
if (type && type !== 'regular') {
mob.effects.addEffect({
type: type
});
//Hack to disallow low level mobs from having any lifeOnHit
// since that makes it very difficult (and confusing) for low level players
if (level <= 3)
cpnStats.values.lifeOnHit = 0;
};

mob['is' + type[0].toUpperCase() + type.substr(1)] = true;
const buildCpnInventory = (mob, blueprint, { drops }, preferStat) => {
const { level } = blueprint;

mob.baseName = mob.name;
mob.name = typeDefinition.name || mob.baseName;
}
const cpnInventory = mob.addComponent('inventory', drops);

if (typeDefinition.sheetName)
mob.sheetName = typeDefinition.sheetName;
cpnInventory.inventorySize = -1;
cpnInventory.dailyDrops = blueprint.dailyDrops;

if (typeDefinition.has('cell'))
mob.cell = typeDefinition.cell;
if (!drops?.blueprints || drops?.alsoRandom) {
generateSlots.forEach(slot => {
const item = itemGenerator.generate({
noSpell: true,
level,
slot,
quality: 4,
forceStats: [preferStat]
});
delete item.spell;
item.eq = true;

mob.addComponent('stats', {
values: {
level: blueprint.level
}
cpnInventory.getItem(item);
});
} else {
//TODO: Don't give mobs these items: they'll drop them anyway
drops.blueprints.forEach(d => {
if (d.type === 'key')
return;

const drop = extend({}, d);
drop.level = level;

let cpnMob = mob.addComponent('mob');
extend(cpnMob, {
walkDistance: blueprint.walkDistance,
hpMult: blueprint.hpMult || typeDefinition.hpMult,
dmgMult: blueprint.dmgMult || typeDefinition.dmgMult,
grantRep: blueprint.grantRep,
deathRep: blueprint.deathRep
cpnInventory.getItem(itemGenerator.generate(drop));
});
if (blueprint.patrol)
cpnMob.patrol = blueprint.patrol;
}
};

if (cpnMob.patrol)
cpnMob.walkDistance = 1;
const buildCpnSpells = (mob, blueprint, typeDefinition, preferStat) => {
const dmgMult = 4.5 * typeDefinition.dmgMult * dmgMults[blueprint.level - 1];

cpnMob.needLos = blueprint.needLos;
const spells = extend([], blueprint.spells);

let spells = extend([], blueprint.spells);
spells.forEach(s => {
if (!s.animation && mob.sheetName === 'mobs' && animations.mobs[mob.cell])
s.animation = 'basic';
});
mob.addComponent('spellbook', { spells });

mob.addComponent('spellbook', {
spells: spells,
dmgMult: typeDefinition.dmgMult
});
let spellCount = 0;
if (mob.isRare)
spellCount = 1;

if (!blueprint.has('attackable') || blueprint.attackable === true) {
mob.addComponent('aggro', {
faction: blueprint.faction
});
for (let i = 0; i < spellCount; i++) {
const rune = itemGenerator.generate({ spell: true });
rune.eq = true;

mob.aggro.calcThreatCeiling(type);
}
mob.inventory.getItem(rune);
}

mob.addComponent('equipment');
mob.addComponent('inventory', typeDefinition.drops);
mob.inventory.inventorySize = -1;
mob.inventory.dailyDrops = blueprint.dailyDrops;

if (this.zoneConfig) {
let chats = this.zoneConfig.chats;
if (chats && chats[mob.name.toLowerCase()]) {
mob.addComponent('chatter', {
chats: chats[mob.name.toLowerCase()]
});
}

let dialogues = this.zoneConfig.dialogues;
if (dialogues && dialogues[mob.name.toLowerCase()]) {
mob.addComponent('dialogue', {
config: dialogues[mob.name.toLowerCase()]
});
}
}
mob.spellbook.spells.forEach(s => {
s.dmgMult = s.name ? dmgMult / 3 : dmgMult;
s.statType = preferStat;
s.manaCost = 0;

if (blueprint.properties && blueprint.properties.cpnTrade)
mob.addComponent('trade', blueprint.properties.cpnTrade);
if (!s.animation && mob.sheetName === 'mobs' && animations.mobs[mob.cell])
s.animation = 'basic';
});
};

this.scale(mob, blueprint.level);
const fnComponentGenerators = [
buildCpnMob, buildCpnStats, buildCpnInventory, buildCpnSpells
];

mob.instance.eventEmitter.emit('onAfterBuildMob', {
zoneName,
mob
});
},

scale: function (mob, level) {
let drops = mob.inventory.blueprint || {};

let statValues = mob.stats.values;

let preferStat = ['str', 'dex', 'int'][~~(Math.random() * 3)];

mob.equipment.unequipAll();
mob.inventory.clear();

let hp = level * 40;
statValues.hpMax = hp;

statValues.level = level;

if ((!drops.blueprints) || (drops.alsoRandom)) {
[
'head',
'chest',
'neck',
'hands',
'waist',
'legs',
'feet',
'finger',
'trinket',
'twoHanded'
].forEach(slot => {
let item = itemGenerator.generate({
noSpell: true,
level: level,
slot: slot,
quality: 4,
forceStats: [preferStat]
});

delete item.spell;
mob.inventory.getItem(item);
mob.equipment.autoEquip(item.id);
});
} else {
//TODO: Don't give the mob these items: he'll drop them anyway
drops.blueprints.forEach(d => {
if (d.type === 'key')
return;
//Main Generator
/*
mob = the mob object
blueprint = mob blueprint (normally from the zoneFile)
type = regular,rare
zoneName = the name of the zone
*/
const build = (mob, blueprint, type, zoneName) => {
mob.instance.eventEmitter.emit('onBeforeBuildMob', zoneName, mob.name.toLowerCase(), blueprint);

let drop = extend({}, d);
drop.level = level;
const typeDefinition = blueprint[type] || blueprint;

mob.inventory.getItem(itemGenerator.generate(drop));
});
}
if (blueprint.nonSelectable)
mob.nonSelectable = true;

let spellCount = (mob.isRare ? 1 : 0) + (mob.isChampion ? 2 : 0);
mob.addComponent('effects');
if (type === 'rare') {
mob.effects.addEffect({ type: 'rare' });
mob.isRare = true;

for (let i = 0; i < spellCount; i++) {
let rune = itemGenerator.generate({
spell: true
});
rune.eq = true;
mob.inventory.getItem(rune);
}
mob.baseName = mob.name;
mob.name = typeDefinition.name ?? mob.name;
}

let dmgMult = 4.5 * mob.mob.dmgMult;
let hpMult = 1 * mob.mob.hpMult;
if (typeDefinition.sheetName)
mob.sheetName = typeDefinition.sheetName;

dmgMult *= [0.25, 0.4, 0.575, 0.8, 1, 1.1, 1.2, 1.3, 1.4, 1.5, 1.6, 1.7, 1.8, 1.9, 2, 2.1, 2.2, 2.3, 2.4, 2.5][level - 1];
if (typeDefinition.has('cell'))
mob.cell = typeDefinition.cell;

statValues.hpMax = ~~(statValues.hpMax * [0.1, 0.2, 0.4, 0.7, 0.78, 0.91, 1.16, 1.19, 1.65, 2.36, 3.07, 3.55, 4.1, 4.85, 5.6, 5.9, 6.5, 7.1, 7.9, 12][level - 1]);
mob.addComponent('equipment');

statValues.hpMax *= hpMult;
statValues.hp = statValues.hpMax;
statValues.mana = statValues.manaMax;
const preferStat = statSelector[~~(Math.random() * 3)];

mob.spellbook.spells.forEach(s => {
s.dmgMult = s.name ? dmgMult / 3 : dmgMult;
s.statType = preferStat;
s.manaCost = 0;
});
fnComponentGenerators.forEach(fn => fn(mob, blueprint, typeDefinition, preferStat));

//Hack to disallow low level mobs from having any lifeOnHit
// since that makes it very difficult (and confusing) for low level players
if (level <= 3)
mob.stats.values.lifeOnHit = 0;
if (blueprint.attackable !== false) {
mob.addComponent('aggro', { faction: blueprint.faction });

['hp', 'hpMax', 'mana', 'manaMax', 'level'].forEach(s => mob.syncer.setObject(false, 'stats', 'values', s, statValues[s]));
mob.aggro.calcThreatCeiling(type);
}

const zoneConfig = instancer.instances[0].map.zoneConfig;

const chats = zoneConfig?.chats?.[mob.name.toLowerCase()];
if (chats)
mob.addComponent('chatter', { chats });

const dialogues = zoneConfig?.dialogues?.[mob.name.toLowerCase()];
if (dialogues)
mob.addComponent('dialogue', { config: dialogues });

if (blueprint?.properties?.cpnTrade)
mob.addComponent('trade', blueprint.properties.cpnTrade);

mob.instance.eventEmitter.emit('onAfterBuildMob', {
zoneName,
mob
});

const statValues = mob.stats.values;
syncStats.forEach(s => mob.syncer.setObject(false, 'stats', 'values', s, statValues[s]));
};

module.exports = { build };

+ 2
- 7
src/server/world/spawners.js View File

@@ -10,9 +10,6 @@ module.exports = {
this.objects = msg.objects;
this.syncer = msg.syncer;
this.zoneConfig = msg.zoneConfig;
this.mobBuilder = extend({
zoneConfig: this.zoneConfig
}, mobBuilder);
},

reset: function () {
@@ -176,9 +173,7 @@ module.exports = {

setupMob: function (mob, blueprint) {
let type = 'regular';
if (blueprint.isChampion)
type = 'champion';
else if (blueprint.rare.count > 0) {
if (blueprint.rare.count > 0) {
const rareCount = this.list.filter(l => (
(l.mob) &&
(!l.mob.destroyed) &&
@@ -194,7 +189,7 @@ module.exports = {

this.setupObj(mob, blueprint);

this.mobBuilder.build(mob, blueprint, type, this.zoneConfig.name);
mobBuilder.build(mob, blueprint, type, this.zoneConfig.name);
},

setupObj: function (obj, blueprint) {


Loading…
Cancel
Save