Browse Source

Add multi-target skill support with Whirlwind test skill

- Modified executePlayerSkill() to loop through all living monsters when
  skill.target === 'all_enemies'
- Each target gets individual hit chance roll
- Added Whirlwind skill (75% ATK to all enemies, 12 MP) to database seeds
- Added whirlwind to Trail Runner class skills for testing
- Fixed skill seeding to check for existence before inserting
- Added try-catch for class skill names to prevent duplicate errors

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
master
HikeMap User 1 month ago
parent
commit
ca45e96f80
  1. 32
      database.js
  2. 97
      index.html

32
database.js

@ -1115,11 +1115,7 @@ class HikeMapDB {
// ===================== // =====================
seedDefaultSkills() { seedDefaultSkills() {
// Check if skills already exist
const existing = this.getSkill('basic_attack');
if (existing) return;
console.log('Seeding default skills...');
console.log('Checking/seeding default skills...');
const defaultSkills = [ const defaultSkills = [
{ {
@ -1205,10 +1201,28 @@ class HikeMapDB {
statusEffect: { type: 'poison', damage: 5, duration: 3 }, statusEffect: { type: 'poison', damage: 5, duration: 3 },
playerUsable: false, playerUsable: false,
monsterUsable: true monsterUsable: true
},
{
id: 'whirlwind',
name: 'Whirlwind',
description: 'A spinning attack that hits all enemies',
type: 'damage',
mpCost: 12,
basePower: 75,
accuracy: 85,
hitCount: 1,
target: 'all_enemies',
statusEffect: null,
playerUsable: true,
monsterUsable: false
} }
]; ];
for (const skill of defaultSkills) { for (const skill of defaultSkills) {
// Only seed if skill doesn't exist
const existing = this.getSkill(skill.id);
if (existing) continue;
this.createSkill(skill); this.createSkill(skill);
console.log(` Seeded skill: ${skill.name}`); console.log(` Seeded skill: ${skill.name}`);
} }
@ -1222,13 +1236,18 @@ class HikeMapDB {
]; ];
for (const name of trailRunnerSkillNames) { for (const name of trailRunnerSkillNames) {
try {
this.createClassSkillName(name); this.createClassSkillName(name);
console.log(` Seeded class skill name: ${name.customName} for ${name.classId}`); console.log(` Seeded class skill name: ${name.customName} for ${name.classId}`);
} catch (err) {
// Skip if already exists
}
} }
// Assign poison skill to Moop monster // Assign poison skill to Moop monster
const moop = this.getMonsterType('moop'); const moop = this.getMonsterType('moop');
if (moop) { if (moop) {
try {
this.createMonsterSkill({ this.createMonsterSkill({
monsterTypeId: 'moop', monsterTypeId: 'moop',
skillId: 'poison', skillId: 'poison',
@ -1236,6 +1255,9 @@ class HikeMapDB {
minLevel: 1 minLevel: 1
}); });
console.log(' Assigned poison skill to Moop'); console.log(' Assigned poison skill to Moop');
} catch (err) {
// Skip if already exists
}
} }
console.log('Default skills seeded successfully'); console.log('Default skills seeded successfully');

97
index.html

@ -3561,7 +3561,7 @@
mpPerLevel: 10, mpPerLevel: 10,
atkPerLevel: 3, atkPerLevel: 3,
defPerLevel: 2, defPerLevel: 2,
skills: ['basic_attack', 'brand_new_hokas', 'runners_high', 'shin_kick']
skills: ['basic_attack', 'brand_new_hokas', 'runners_high', 'shin_kick', 'whirlwind']
}, },
'gym_bro': { 'gym_bro': {
name: 'Gym Bro', name: 'Gym Bro',
@ -3677,6 +3677,16 @@
type: 'admin_clear', type: 'admin_clear',
adminOnly: true, adminOnly: true,
description: 'Instantly banish all enemies (Admin only)' description: 'Instantly banish all enemies (Admin only)'
},
'whirlwind': {
name: 'Whirlwind',
icon: '🌀',
mpCost: 12,
levelReq: 1,
type: 'damage',
target: 'all_enemies',
calculate: (atk) => Math.floor(atk * 0.75),
description: 'Spinning attack that hits all enemies for 75% ATK'
} }
}; };
@ -11295,22 +11305,12 @@
}, 1000); }, 1000);
return; return;
} else if (skill.type === 'damage') { } else if (skill.type === 'damage') {
// Calculate hit chance
const skillAccuracy = dbSkill ? dbSkill.accuracy : 95;
const hitChance = calculateHitChance(
combatState.player.accuracy,
target.dodge,
skillAccuracy
);
// Roll for hit
if (!rollHit(hitChance)) {
addCombatLog(`❌ ${displayName} missed ${target.data.name}! (${hitChance}% chance)`, 'miss');
endPlayerTurn();
return;
}
// Determine targets based on skill.target
const skillTarget = dbSkill ? dbSkill.target : (skill.target || 'enemy');
const livingMonsters = combatState.monsters.filter(m => m.hp > 0);
const targets = skillTarget === 'all_enemies' ? livingMonsters : [target];
// Calculate damage - support both old calculate() and new basePower
// Calculate base damage - support both old calculate() and new basePower
let rawDamage; let rawDamage;
if (hardcodedSkill && hardcodedSkill.calculate) { if (hardcodedSkill && hardcodedSkill.calculate) {
rawDamage = hardcodedSkill.calculate(combatState.player.atk); rawDamage = hardcodedSkill.calculate(combatState.player.atk);
@ -11320,32 +11320,79 @@
rawDamage = combatState.player.atk; rawDamage = combatState.player.atk;
} }
// Handle multi-hit skills
const hitCount = skill.hitCount || skill.hits || 1; const hitCount = skill.hitCount || skill.hits || 1;
const skillAccuracy = dbSkill ? dbSkill.accuracy : 95;
let grandTotalDamage = 0;
let monstersHit = 0;
let monstersKilled = 0;
// Apply damage to each target
for (const currentTarget of targets) {
// Calculate hit chance for this target
const hitChance = calculateHitChance(
combatState.player.accuracy,
currentTarget.dodge,
skillAccuracy
);
// Roll for hit
if (!rollHit(hitChance)) {
if (targets.length === 1) {
addCombatLog(`❌ ${displayName} missed ${currentTarget.data.name}! (${hitChance}% chance)`, 'miss');
}
continue; // Miss this target, continue to next
}
monstersHit++;
let totalDamage = 0; let totalDamage = 0;
// Calculate effective defense (with buff if active) // Calculate effective defense (with buff if active)
let effectiveMonsterDef = target.def;
if (target.buffs && target.buffs.defense && target.buffs.defense.turnsLeft > 0) {
const buffPercent = target.buffs.defense.percent || 50;
effectiveMonsterDef = Math.floor(target.def * (1 + buffPercent / 100));
let effectiveMonsterDef = currentTarget.def;
if (currentTarget.buffs && currentTarget.buffs.defense && currentTarget.buffs.defense.turnsLeft > 0) {
const buffPercent = currentTarget.buffs.defense.percent || 50;
effectiveMonsterDef = Math.floor(currentTarget.def * (1 + buffPercent / 100));
} }
for (let hit = 0; hit < hitCount; hit++) { for (let hit = 0; hit < hitCount; hit++) {
const damage = Math.max(1, rawDamage - effectiveMonsterDef); const damage = Math.max(1, rawDamage - effectiveMonsterDef);
totalDamage += damage; totalDamage += damage;
target.hp -= damage;
currentTarget.hp -= damage;
} }
grandTotalDamage += totalDamage;
// Log for single-target skills
if (targets.length === 1) {
if (hitCount > 1) { if (hitCount > 1) {
addCombatLog(`✨ ${displayName} hits ${target.data.name} ${hitCount} times for ${totalDamage} total damage!`, 'damage');
addCombatLog(`✨ ${displayName} hits ${currentTarget.data.name} ${hitCount} times for ${totalDamage} total damage!`, 'damage');
} else { } else {
addCombatLog(`⚔️ ${displayName} hits ${target.data.name} for ${totalDamage} damage!`, 'damage');
addCombatLog(`⚔️ ${displayName} hits ${currentTarget.data.name} for ${totalDamage} damage!`, 'damage');
}
} }
// Check if this monster died // Check if this monster died
if (currentTarget.hp <= 0) {
monstersKilled++;
if (targets.length === 1) {
addCombatLog(`💀 ${currentTarget.data.name} was defeated!`, 'victory');
}
}
}
// Log for multi-target skills
if (targets.length > 1) {
if (monstersHit === 0) {
addCombatLog(`❌ ${displayName} missed all enemies!`, 'miss');
} else {
addCombatLog(`🌟 ${displayName} hits ${monstersHit} enemies for ${grandTotalDamage} total damage!`, 'damage');
if (monstersKilled > 0) {
addCombatLog(`💀 ${monstersKilled} enemy${monstersKilled > 1 ? 'ies' : ''} defeated!`, 'victory');
}
}
}
// Auto-retarget if current target died
if (target.hp <= 0) { if (target.hp <= 0) {
addCombatLog(`💀 ${target.data.name} was defeated!`, 'victory');
autoRetarget(); autoRetarget();
} }
} else if (skill.type === 'heal') { } else if (skill.type === 'heal') {

Loading…
Cancel
Save