Release Healer Man 0.1.22
This commit is contained in:
1 parent
f6f2375145
commit
7f6001723a
144 files changed
+54107
-690
No files matched your search
+58
-9
@@ -1,4 +1,4 @@
|
||||
import type { DungeonDefinition, DungeonExpansion } from "./dungeonTypes";
|
||||
import type { DungeonBossObjective, DungeonDefinition, DungeonExpansion } from "./dungeonTypes";
|
||||
import {
|
||||
normalizeEnemyAttack,
|
||||
type EnemyAbilityCondition,
|
||||
@@ -114,6 +114,10 @@ function describeEffect(effect: EnemyAbilityEffect | undefined, attack: EnemyAtt
|
||||
}
|
||||
if (effect.kind === "heal") return `Heals ${target} for ${effect.multiplier}× boss power`;
|
||||
if (effect.kind === "aura") {
|
||||
if (effect.meleeAutoCriticalProc) {
|
||||
const proc = effect.meleeAutoCriticalProc;
|
||||
return `Applies ${titleCase(effect.aura)} to ${target}; successful main-hand autos have a ${Math.round(proc.chance * 100)}% chance to grant ${proc.charges} charge${proc.charges === 1 ? "" : "s"} of +${proc.criticalChancePercent}% critical chance to the next main-hand auto for ${seconds(proc.durationMs)}`;
|
||||
}
|
||||
const magnitude = effect.magnitude === undefined ? "" : ` at ${effect.magnitude} strength`;
|
||||
return `Applies ${titleCase(effect.aura)} to ${target} for ${seconds(effect.durationMs)}${magnitude}`;
|
||||
}
|
||||
@@ -121,7 +125,10 @@ function describeEffect(effect: EnemyAbilityEffect | undefined, attack: EnemyAtt
|
||||
return `Applies ${titleCase(effect.aura)} to ${target} for ${seconds(effect.durationMs)}`;
|
||||
}
|
||||
if (effect.kind === "control") {
|
||||
return `${titleCase(effect.mechanic)} controls ${target} for ${seconds(effect.durationMs)}`;
|
||||
const damage = effect.damageMultiplier === undefined
|
||||
? ""
|
||||
: `${titleCase(attack.school)} damage (${Number(effect.damageMultiplier.toFixed(2))}× boss power) and `;
|
||||
return `${damage}${titleCase(effect.mechanic)} controls ${target} for ${seconds(effect.durationMs)}`;
|
||||
}
|
||||
if (effect.kind === "transform") return `Transforms ${target} into ${titleCase(effect.form)}`;
|
||||
if (effect.kind === "summon") return `Summons ${effect.count} add${effect.count === 1 ? "" : "s"} (creature ${effect.creatureEntry})`;
|
||||
@@ -271,18 +278,25 @@ function normalizedName(value: string): string {
|
||||
|
||||
function bossEntityForObjective(
|
||||
dungeon: DungeonDefinition,
|
||||
objectiveId: string,
|
||||
objectiveName: string,
|
||||
objective: DungeonBossObjective,
|
||||
): readonly [string, PopulationEntityDefinition] | undefined {
|
||||
const staticSpawn = dungeon.staticSpawns.find((spawn) => spawn.id === objectiveId);
|
||||
const staticSpawn = dungeon.staticSpawns.find((spawn) => spawn.id === objective.id);
|
||||
if (staticSpawn) {
|
||||
const entity = dungeon.entities[staticSpawn.entityId];
|
||||
if (entity) return [staticSpawn.entityId, entity];
|
||||
}
|
||||
|
||||
for (const group of dungeon.encounterSpawnGroups ?? []) {
|
||||
const spawn = group.spawns.find((candidate) => candidate.id === objective.id);
|
||||
if (spawn) {
|
||||
const entity = dungeon.entities[spawn.entityId];
|
||||
if (entity) return [spawn.entityId, entity];
|
||||
}
|
||||
}
|
||||
|
||||
for (const pack of dungeon.roamingPacks) {
|
||||
const member = pack.members.find((candidate) => (
|
||||
`${pack.id}:${candidate.id}` === objectiveId || candidate.id === objectiveId
|
||||
`${pack.id}:${candidate.id}` === objective.id || candidate.id === objective.id
|
||||
));
|
||||
if (member) {
|
||||
const entity = dungeon.entities[member.entityId];
|
||||
@@ -290,11 +304,46 @@ function bossEntityForObjective(
|
||||
}
|
||||
}
|
||||
|
||||
const wanted = normalizedName(objectiveName);
|
||||
return Object.entries(dungeon.entities).find(([, entity]) => (
|
||||
const wanted = normalizedName(objective.name);
|
||||
const named = Object.entries(dungeon.entities).find(([, entity]) => (
|
||||
entity.kind === "boss"
|
||||
&& (normalizedName(entity.name) === wanted || normalizedName(entity.title) === wanted)
|
||||
));
|
||||
if (named) return named;
|
||||
|
||||
if (objective.eventOnly) {
|
||||
const related = Object.entries(dungeon.entities)
|
||||
.filter(([, entity]) => {
|
||||
const entityName = normalizedName(entity.name);
|
||||
return entityName.length >= 5 && (wanted.includes(entityName) || entityName.includes(wanted));
|
||||
})
|
||||
.sort((left, right) => {
|
||||
const leftBoss = left[1].kind === "boss" ? 1 : 0;
|
||||
const rightBoss = right[1].kind === "boss" ? 1 : 0;
|
||||
if (leftBoss !== rightBoss) return rightBoss - leftBoss;
|
||||
const leftMechanics = left[1].combat?.attacks.filter((attack) => attack.id !== "basic-attack").length ?? 0;
|
||||
const rightMechanics = right[1].combat?.attacks.filter((attack) => attack.id !== "basic-attack").length ?? 0;
|
||||
return rightMechanics - leftMechanics || left[0].localeCompare(right[0]);
|
||||
});
|
||||
if (related[0]) return related[0];
|
||||
}
|
||||
|
||||
// Aggregate objectives such as Blackrock Depths' "The Seven" use a
|
||||
// synthetic objective id but retain the final source actor's exact position.
|
||||
// That representative may be typed as a mob because the objective covers a
|
||||
// multi-actor event rather than one loot-bearing boss.
|
||||
for (const group of dungeon.encounterSpawnGroups ?? []) {
|
||||
const exact = group.spawns.filter((candidate) => {
|
||||
const dx = candidate.position[0] - objective.position[0];
|
||||
const dy = candidate.position[1] - objective.position[1];
|
||||
const dz = candidate.position[2] - objective.position[2];
|
||||
return dx * dx + dy * dy + dz * dz <= 0.25 * 0.25;
|
||||
});
|
||||
const spawn = exact.find((candidate) => dungeon.entities[candidate.entityId]?.kind === "boss")
|
||||
?? exact.find((candidate) => dungeon.entities[candidate.entityId] !== undefined);
|
||||
if (spawn) return [spawn.entityId, dungeon.entities[spawn.entityId]];
|
||||
}
|
||||
return undefined;
|
||||
}
|
||||
|
||||
function bossGuide(
|
||||
@@ -325,7 +374,7 @@ export function buildDungeonBossGuide(
|
||||
): DungeonBossGuide {
|
||||
const representedEntityIds = new Set<string>();
|
||||
const objectiveBosses = dungeon.bosses.map((objective) => {
|
||||
const resolved = bossEntityForObjective(dungeon, objective.id, objective.name);
|
||||
const resolved = bossEntityForObjective(dungeon, objective);
|
||||
if (resolved) representedEntityIds.add(resolved[0]);
|
||||
return bossGuide(dungeon, objective.id, objective.name, resolved, overrides);
|
||||
});
|
||||
|
||||
Reference in new issue
Block a user