feat(web): translate combat log, action buttons, and victory/defeat screen to English
Translates formatEvent() and monsterIntentLabel() combat-log templates, default participant-name fallbacks (Du/Der Gegner -> You/The enemy), and every static string in the combat page template: round header, action buttons, outcome headings, rewards panel, post-combat buttons, log panel, and loading/error/aria-label text. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01ACkMEDYiwtcfchqKkiUJNX
This commit is contained in:
@@ -1,4 +1,4 @@
|
|||||||
<section class="combat" aria-label="Kampf">
|
<section class="combat" aria-label="Combat">
|
||||||
@if (combat(); as combat) {
|
@if (combat(); as combat) {
|
||||||
<div class="combat__stage" [class.combat__stage--shaken]="stageShake()">
|
<div class="combat__stage" [class.combat__stage--shaken]="stageShake()">
|
||||||
<header class="combat__status">
|
<header class="combat__status">
|
||||||
@@ -9,7 +9,7 @@
|
|||||||
<div
|
<div
|
||||||
class="bar bar--player"
|
class="bar bar--player"
|
||||||
role="progressbar"
|
role="progressbar"
|
||||||
[attr.aria-label]="'Lebenspunkte ' + combat.player.name"
|
[attr.aria-label]="'Health points ' + combat.player.name"
|
||||||
[attr.aria-valuenow]="combat.player.currentHp"
|
[attr.aria-valuenow]="combat.player.currentHp"
|
||||||
[attr.aria-valuemin]="0"
|
[attr.aria-valuemin]="0"
|
||||||
[attr.aria-valuemax]="combat.player.maxHp"
|
[attr.aria-valuemax]="combat.player.maxHp"
|
||||||
@@ -20,7 +20,7 @@
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<p class="combat__round" data-combat-round>Runde {{ combat.round }}</p>
|
<p class="combat__round" data-combat-round>Round {{ combat.round }}</p>
|
||||||
|
|
||||||
<div class="fighter fighter--monster">
|
<div class="fighter fighter--monster">
|
||||||
<div class="fighter__meter">
|
<div class="fighter__meter">
|
||||||
@@ -31,7 +31,7 @@
|
|||||||
<div
|
<div
|
||||||
class="bar bar--monster"
|
class="bar bar--monster"
|
||||||
role="progressbar"
|
role="progressbar"
|
||||||
[attr.aria-label]="'Lebenspunkte ' + combat.monster.name"
|
[attr.aria-label]="'Health points ' + combat.monster.name"
|
||||||
[attr.aria-valuenow]="combat.monster.currentHp"
|
[attr.aria-valuenow]="combat.monster.currentHp"
|
||||||
[attr.aria-valuemin]="0"
|
[attr.aria-valuemin]="0"
|
||||||
[attr.aria-valuemax]="combat.monster.maxHp"
|
[attr.aria-valuemax]="combat.monster.maxHp"
|
||||||
@@ -80,7 +80,7 @@
|
|||||||
(click)="performAction('ATTACK')"
|
(click)="performAction('ATTACK')"
|
||||||
>
|
>
|
||||||
<img class="action__icon" src="/images/hud/runtime/AttackIcon-96.png" alt="" />
|
<img class="action__icon" src="/images/hud/runtime/AttackIcon-96.png" alt="" />
|
||||||
<span class="action__label">Angriff</span>
|
<span class="action__label">Attack</span>
|
||||||
<span class="action__key">1</span>
|
<span class="action__key">1</span>
|
||||||
</button>
|
</button>
|
||||||
<button
|
<button
|
||||||
@@ -91,7 +91,7 @@
|
|||||||
(click)="performAction('HEAVY_STRIKE')"
|
(click)="performAction('HEAVY_STRIKE')"
|
||||||
>
|
>
|
||||||
<img class="action__icon" src="/images/hud/runtime/AttackIcon-96.png" alt="" />
|
<img class="action__icon" src="/images/hud/runtime/AttackIcon-96.png" alt="" />
|
||||||
<span class="action__label">Schwerer Hieb</span>
|
<span class="action__label">Heavy Strike</span>
|
||||||
<span class="action__key">2</span>
|
<span class="action__key">2</span>
|
||||||
</button>
|
</button>
|
||||||
<button
|
<button
|
||||||
@@ -102,7 +102,7 @@
|
|||||||
(click)="performAction('SHIELD_BASH')"
|
(click)="performAction('SHIELD_BASH')"
|
||||||
>
|
>
|
||||||
<img class="action__icon" src="/images/hud/runtime/CharacterIcon-128.png" alt="" />
|
<img class="action__icon" src="/images/hud/runtime/CharacterIcon-128.png" alt="" />
|
||||||
<span class="action__label">Schildstoß</span>
|
<span class="action__label">Shield Bash</span>
|
||||||
<span class="action__key">3</span>
|
<span class="action__key">3</span>
|
||||||
</button>
|
</button>
|
||||||
<button
|
<button
|
||||||
@@ -113,7 +113,7 @@
|
|||||||
(click)="performAction('DEFEND')"
|
(click)="performAction('DEFEND')"
|
||||||
>
|
>
|
||||||
<img class="action__icon" src="/images/hud/runtime/CharacterIcon-128.png" alt="" />
|
<img class="action__icon" src="/images/hud/runtime/CharacterIcon-128.png" alt="" />
|
||||||
<span class="action__label">Verteidigen</span>
|
<span class="action__label">Defend</span>
|
||||||
<span class="action__key">4</span>
|
<span class="action__key">4</span>
|
||||||
</button>
|
</button>
|
||||||
<button
|
<button
|
||||||
@@ -124,7 +124,7 @@
|
|||||||
(click)="performAction('POTION')"
|
(click)="performAction('POTION')"
|
||||||
>
|
>
|
||||||
<img class="action__icon" src="/images/items/small-healing-potion.png" alt="" />
|
<img class="action__icon" src="/images/items/small-healing-potion.png" alt="" />
|
||||||
<span class="action__label">Trank {{ combat.player.potionsRemaining }}/{{ combat.player.potionsMax }}</span>
|
<span class="action__label">Potion {{ combat.player.potionsRemaining }}/{{ combat.player.potionsMax }}</span>
|
||||||
<span class="action__key">5</span>
|
<span class="action__key">5</span>
|
||||||
</button>
|
</button>
|
||||||
}
|
}
|
||||||
@@ -132,24 +132,24 @@
|
|||||||
|
|
||||||
@if (combat.status === 'WON') {
|
@if (combat.status === 'WON') {
|
||||||
<div class="outcome outcome--won" data-combat-result="WON">
|
<div class="outcome outcome--won" data-combat-result="WON">
|
||||||
<h2 class="outcome__title">Sieg</h2>
|
<h2 class="outcome__title">Victory</h2>
|
||||||
<p>{{ combat.monster.name }} wurde besiegt.</p>
|
<p>{{ combat.monster.name }} has been defeated.</p>
|
||||||
|
|
||||||
@if (combat.rewards; as rewards) {
|
@if (combat.rewards; as rewards) {
|
||||||
<section class="rewards" data-combat-rewards aria-label="Belohnungen">
|
<section class="rewards" data-combat-rewards aria-label="Rewards">
|
||||||
<h3 class="rewards__title">Belohnungen</h3>
|
<h3 class="rewards__title">Rewards</h3>
|
||||||
|
|
||||||
@if (rewards.silver) {
|
@if (rewards.silver) {
|
||||||
<dl class="rewards__currencies">
|
<dl class="rewards__currencies">
|
||||||
<div class="rewards__currency" data-reward-silver>
|
<div class="rewards__currency" data-reward-silver>
|
||||||
<dt>Silber</dt>
|
<dt>Silver</dt>
|
||||||
<dd>+{{ rewards.silver }}</dd>
|
<dd>+{{ rewards.silver }}</dd>
|
||||||
</div>
|
</div>
|
||||||
</dl>
|
</dl>
|
||||||
}
|
}
|
||||||
|
|
||||||
@if (rewards.items.length) {
|
@if (rewards.items.length) {
|
||||||
<h3 class="rewards__title">Beute</h3>
|
<h3 class="rewards__title">Loot</h3>
|
||||||
<ul class="rewards__loot">
|
<ul class="rewards__loot">
|
||||||
@for (reward of rewards.items; track reward.characterItemId) {
|
@for (reward of rewards.items; track reward.characterItemId) {
|
||||||
<li>
|
<li>
|
||||||
@@ -158,14 +158,14 @@
|
|||||||
}
|
}
|
||||||
</ul>
|
</ul>
|
||||||
} @else {
|
} @else {
|
||||||
<p class="outcome__hint" data-reward-empty>Keine besondere Beute gefunden.</p>
|
<p class="outcome__hint" data-reward-empty>No notable loot found.</p>
|
||||||
}
|
}
|
||||||
</section>
|
</section>
|
||||||
}
|
}
|
||||||
|
|
||||||
<div class="outcome__buttons">
|
<div class="outcome__buttons">
|
||||||
<button type="button" class="outcome__button" data-combat-to-hunt (click)="goToHunt()">
|
<button type="button" class="outcome__button" data-combat-to-hunt (click)="goToHunt()">
|
||||||
Weiter jagen
|
Keep Hunting
|
||||||
</button>
|
</button>
|
||||||
<button
|
<button
|
||||||
type="button"
|
type="button"
|
||||||
@@ -173,7 +173,7 @@
|
|||||||
data-combat-to-location
|
data-combat-to-location
|
||||||
(click)="goToLocation()"
|
(click)="goToLocation()"
|
||||||
>
|
>
|
||||||
Zum Ort
|
To Location
|
||||||
</button>
|
</button>
|
||||||
<button
|
<button
|
||||||
type="button"
|
type="button"
|
||||||
@@ -181,17 +181,17 @@
|
|||||||
data-combat-to-inventory
|
data-combat-to-inventory
|
||||||
(click)="goToInventory()"
|
(click)="goToInventory()"
|
||||||
>
|
>
|
||||||
Inventar öffnen
|
Open Inventory
|
||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
} @else if (combat.status === 'LOST') {
|
} @else if (combat.status === 'LOST') {
|
||||||
<div class="outcome outcome--lost" data-combat-result="LOST">
|
<div class="outcome outcome--lost" data-combat-result="LOST">
|
||||||
<h2 class="outcome__title">Niederlage</h2>
|
<h2 class="outcome__title">Defeat</h2>
|
||||||
<p>{{ combat.player.name }} wurde im Kampf besiegt.</p>
|
<p>{{ combat.player.name }} has been defeated.</p>
|
||||||
<div class="outcome__buttons">
|
<div class="outcome__buttons">
|
||||||
<button type="button" class="outcome__button" data-combat-to-hunt (click)="goToHunt()">
|
<button type="button" class="outcome__button" data-combat-to-hunt (click)="goToHunt()">
|
||||||
Weiter jagen
|
Keep Hunting
|
||||||
</button>
|
</button>
|
||||||
<button
|
<button
|
||||||
type="button"
|
type="button"
|
||||||
@@ -199,18 +199,18 @@
|
|||||||
data-combat-to-location
|
data-combat-to-location
|
||||||
(click)="goToLocation()"
|
(click)="goToLocation()"
|
||||||
>
|
>
|
||||||
Zum Ort
|
To Location
|
||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
}
|
}
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<aside class="combat__log" aria-label="Kampfprotokoll">
|
<aside class="combat__log" aria-label="Combat Log">
|
||||||
<h2 class="combat__log-title">Kampflog</h2>
|
<h2 class="combat__log-title">Combat Log</h2>
|
||||||
<div class="combat__log-body">
|
<div class="combat__log-body">
|
||||||
@for (round of logRounds(); track round.round) {
|
@for (round of logRounds(); track round.round) {
|
||||||
<p class="combat__log-round">Runde {{ round.round }}</p>
|
<p class="combat__log-round">Round {{ round.round }}</p>
|
||||||
@for (event of round.events; track event.sequence) {
|
@for (event of round.events; track event.sequence) {
|
||||||
<p class="combat__log-entry" [class.combat__log-entry--player]="event.source === 'PLAYER'">
|
<p class="combat__log-entry" [class.combat__log-entry--player]="event.source === 'PLAYER'">
|
||||||
{{ formatEvent(event) }}
|
{{ formatEvent(event) }}
|
||||||
@@ -220,13 +220,13 @@
|
|||||||
</div>
|
</div>
|
||||||
</aside>
|
</aside>
|
||||||
} @else if (combatStore.loading()) {
|
} @else if (combatStore.loading()) {
|
||||||
<p class="combat__notice" role="status">Kampf wird geladen…</p>
|
<p class="combat__notice" role="status">Loading combat…</p>
|
||||||
}
|
}
|
||||||
|
|
||||||
@if (combatStore.error(); as error) {
|
@if (combatStore.error(); as error) {
|
||||||
<section class="combat__notice combat__notice--error" role="alert">
|
<section class="combat__notice combat__notice--error" role="alert">
|
||||||
<p>{{ error }}</p>
|
<p>{{ error }}</p>
|
||||||
<button type="button" data-combat-retry (click)="retry()">Erneut versuchen</button>
|
<button type="button" data-combat-retry (click)="retry()">Retry</button>
|
||||||
</section>
|
</section>
|
||||||
}
|
}
|
||||||
</section>
|
</section>
|
||||||
|
|||||||
@@ -28,7 +28,7 @@ const activeCombat: Combat = {
|
|||||||
rewards: null,
|
rewards: null,
|
||||||
};
|
};
|
||||||
|
|
||||||
const monsterHitLine = 'Aschenratte trifft Aric Duskwalker für 5 Schaden.';
|
const monsterHitLine = 'Aschenratte hits Aric Duskwalker for 5 damage.';
|
||||||
|
|
||||||
function countOccurrences(haystack: string | null, needle: string): number {
|
function countOccurrences(haystack: string | null, needle: string): number {
|
||||||
return haystack ? haystack.split(needle).length - 1 : 0;
|
return haystack ? haystack.split(needle).length - 1 : 0;
|
||||||
@@ -91,7 +91,7 @@ describe('CombatPageComponent', () => {
|
|||||||
expect(combatStore.loadCombat).toHaveBeenCalledWith('combat-1');
|
expect(combatStore.loadCombat).toHaveBeenCalledWith('combat-1');
|
||||||
});
|
});
|
||||||
|
|
||||||
it('shows the player, monster, HP bars, round, and the Angriff action', async () => {
|
it('shows the player, monster, HP bars, round, and the Attack action', async () => {
|
||||||
const fixture = await setup(activeCombat);
|
const fixture = await setup(activeCombat);
|
||||||
const element = fixture.nativeElement as HTMLElement;
|
const element = fixture.nativeElement as HTMLElement;
|
||||||
|
|
||||||
@@ -99,22 +99,22 @@ describe('CombatPageComponent', () => {
|
|||||||
expect(element.textContent).toContain('95 / 100');
|
expect(element.textContent).toContain('95 / 100');
|
||||||
expect(element.textContent).toContain('Aschenratte');
|
expect(element.textContent).toContain('Aschenratte');
|
||||||
expect(element.textContent).toContain('31 / 45');
|
expect(element.textContent).toContain('31 / 45');
|
||||||
expect(element.querySelector('[data-combat-round]')?.textContent).toContain('Runde 2');
|
expect(element.querySelector('[data-combat-round]')?.textContent).toContain('Round 2');
|
||||||
expect(element.querySelector('[data-combat-attack]')).toBeTruthy();
|
expect(element.querySelector('[data-combat-attack]')).toBeTruthy();
|
||||||
});
|
});
|
||||||
|
|
||||||
it('shows all five combat actions with their German labels', async () => {
|
it('shows all five combat actions with their English labels', async () => {
|
||||||
const fixture = await setup(activeCombat);
|
const fixture = await setup(activeCombat);
|
||||||
const element = fixture.nativeElement as HTMLElement;
|
const element = fixture.nativeElement as HTMLElement;
|
||||||
|
|
||||||
expect(element.querySelector('[data-combat-attack]')?.textContent).toContain('Angriff');
|
expect(element.querySelector('[data-combat-attack]')?.textContent).toContain('Attack');
|
||||||
expect(element.querySelector('[data-combat-heavy-strike]')?.textContent).toContain('Schwerer Hieb');
|
expect(element.querySelector('[data-combat-heavy-strike]')?.textContent).toContain('Heavy Strike');
|
||||||
expect(element.querySelector('[data-combat-shield-bash]')?.textContent).toContain('Schildstoß');
|
expect(element.querySelector('[data-combat-shield-bash]')?.textContent).toContain('Shield Bash');
|
||||||
expect(element.querySelector('[data-combat-defend]')?.textContent).toContain('Verteidigen');
|
expect(element.querySelector('[data-combat-defend]')?.textContent).toContain('Defend');
|
||||||
expect(element.querySelector('[data-combat-potion]')?.textContent).toContain('Trank 2/2');
|
expect(element.querySelector('[data-combat-potion]')?.textContent).toContain('Potion 2/2');
|
||||||
});
|
});
|
||||||
|
|
||||||
it('sends HEAVY_STRIKE when Schwerer Hieb is clicked', async () => {
|
it('sends HEAVY_STRIKE when Heavy Strike is clicked', async () => {
|
||||||
const fixture = await setup(activeCombat);
|
const fixture = await setup(activeCombat);
|
||||||
const element = fixture.nativeElement as HTMLElement;
|
const element = fixture.nativeElement as HTMLElement;
|
||||||
|
|
||||||
@@ -123,7 +123,7 @@ describe('CombatPageComponent', () => {
|
|||||||
expect(combatStore.performAction).toHaveBeenCalledWith('HEAVY_STRIKE');
|
expect(combatStore.performAction).toHaveBeenCalledWith('HEAVY_STRIKE');
|
||||||
});
|
});
|
||||||
|
|
||||||
it('sends SHIELD_BASH when Schildstoß is clicked', async () => {
|
it('sends SHIELD_BASH when Shield Bash is clicked', async () => {
|
||||||
const fixture = await setup(activeCombat);
|
const fixture = await setup(activeCombat);
|
||||||
const element = fixture.nativeElement as HTMLElement;
|
const element = fixture.nativeElement as HTMLElement;
|
||||||
|
|
||||||
@@ -132,7 +132,7 @@ describe('CombatPageComponent', () => {
|
|||||||
expect(combatStore.performAction).toHaveBeenCalledWith('SHIELD_BASH');
|
expect(combatStore.performAction).toHaveBeenCalledWith('SHIELD_BASH');
|
||||||
});
|
});
|
||||||
|
|
||||||
it('sends DEFEND when Verteidigen is clicked', async () => {
|
it('sends DEFEND when Defend is clicked', async () => {
|
||||||
const fixture = await setup(activeCombat);
|
const fixture = await setup(activeCombat);
|
||||||
const element = fixture.nativeElement as HTMLElement;
|
const element = fixture.nativeElement as HTMLElement;
|
||||||
|
|
||||||
@@ -141,7 +141,7 @@ describe('CombatPageComponent', () => {
|
|||||||
expect(combatStore.performAction).toHaveBeenCalledWith('DEFEND');
|
expect(combatStore.performAction).toHaveBeenCalledWith('DEFEND');
|
||||||
});
|
});
|
||||||
|
|
||||||
it('sends POTION when Trank is clicked', async () => {
|
it('sends POTION when Potion is clicked', async () => {
|
||||||
const fixture = await setup(activeCombat);
|
const fixture = await setup(activeCombat);
|
||||||
const element = fixture.nativeElement as HTMLElement;
|
const element = fixture.nativeElement as HTMLElement;
|
||||||
|
|
||||||
@@ -169,7 +169,7 @@ describe('CombatPageComponent', () => {
|
|||||||
const element = fixture.nativeElement as HTMLElement;
|
const element = fixture.nativeElement as HTMLElement;
|
||||||
|
|
||||||
expect(element.querySelector('[data-combat-telegraph]')?.textContent).toContain(
|
expect(element.querySelector('[data-combat-telegraph]')?.textContent).toContain(
|
||||||
'Aschenratte bereitet Schweren Hieb vor.',
|
'Aschenratte is winding up a Heavy Strike.',
|
||||||
);
|
);
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -192,21 +192,21 @@ describe('CombatPageComponent', () => {
|
|||||||
});
|
});
|
||||||
const element = fixture.nativeElement as HTMLElement;
|
const element = fixture.nativeElement as HTMLElement;
|
||||||
|
|
||||||
expect(element.textContent).toContain('Aric Duskwalker trinkt einen Trank und heilt 35 Lebenspunkte.');
|
expect(element.textContent).toContain('Aric Duskwalker drinks a potion and heals 35 HP.');
|
||||||
expect(element.textContent).toContain('Aric Duskwalker geht in die Verteidigung.');
|
expect(element.textContent).toContain('Aric Duskwalker braces to defend.');
|
||||||
expect(element.textContent).toContain('Aschenratte bereitet Schweren Hieb vor.');
|
expect(element.textContent).toContain('Aschenratte is winding up a Heavy Strike.');
|
||||||
expect(element.textContent).toContain('Aric Duskwalker unterbricht den vorbereiteten Angriff von Aschenratte.');
|
expect(element.textContent).toContain("Aric Duskwalker interrupts Aschenratte's attack.");
|
||||||
});
|
});
|
||||||
|
|
||||||
it('renders the structured events as readable German combat-log entries', async () => {
|
it('renders the structured events as readable English combat-log entries', async () => {
|
||||||
const fixture = await setup(activeCombat);
|
const fixture = await setup(activeCombat);
|
||||||
const element = fixture.nativeElement as HTMLElement;
|
const element = fixture.nativeElement as HTMLElement;
|
||||||
|
|
||||||
expect(element.textContent).toContain('Aric Duskwalker trifft Aschenratte für 14 Schaden.');
|
expect(element.textContent).toContain('Aric Duskwalker hits Aschenratte for 14 damage.');
|
||||||
expect(element.textContent).toContain('Aschenratte trifft Aric Duskwalker für 5 Schaden.');
|
expect(element.textContent).toContain('Aschenratte hits Aric Duskwalker for 5 damage.');
|
||||||
});
|
});
|
||||||
|
|
||||||
it('calls combatStore.performAction("ATTACK") when Angriff is clicked', async () => {
|
it('calls combatStore.performAction("ATTACK") when Attack is clicked', async () => {
|
||||||
const fixture = await setup(activeCombat);
|
const fixture = await setup(activeCombat);
|
||||||
const element = fixture.nativeElement as HTMLElement;
|
const element = fixture.nativeElement as HTMLElement;
|
||||||
|
|
||||||
@@ -303,7 +303,7 @@ describe('CombatPageComponent', () => {
|
|||||||
|
|
||||||
await vi.advanceTimersByTimeAsync(1260);
|
await vi.advanceTimersByTimeAsync(1260);
|
||||||
fixture.detectChanges();
|
fixture.detectChanges();
|
||||||
expect(element.querySelector('[data-combat-telegraph]')?.textContent).toContain('bereitet Schweren Hieb vor');
|
expect(element.querySelector('[data-combat-telegraph]')?.textContent).toContain('is winding up a Heavy Strike');
|
||||||
expect(monster?.classList.contains('sprite--lunge')).toBe(false);
|
expect(monster?.classList.contains('sprite--lunge')).toBe(false);
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -334,7 +334,7 @@ describe('CombatPageComponent', () => {
|
|||||||
await vi.advanceTimersByTimeAsync(540);
|
await vi.advanceTimersByTimeAsync(540);
|
||||||
fixture.detectChanges();
|
fixture.detectChanges();
|
||||||
|
|
||||||
expect(element.textContent).toContain('Aric Duskwalker unterbricht den vorbereiteten Angriff von Aschenratte.');
|
expect(element.textContent).toContain("Aric Duskwalker interrupts Aschenratte's attack.");
|
||||||
expect(element.querySelector('[data-combat-telegraph]')).toBeNull();
|
expect(element.querySelector('[data-combat-telegraph]')).toBeNull();
|
||||||
expect(monster?.classList.contains('sprite--lunge')).toBe(false);
|
expect(monster?.classList.contains('sprite--lunge')).toBe(false);
|
||||||
});
|
});
|
||||||
@@ -365,7 +365,7 @@ describe('CombatPageComponent', () => {
|
|||||||
|
|
||||||
// Before the checkpoint: the pre-heal HP and potion count still show.
|
// Before the checkpoint: the pre-heal HP and potion count still show.
|
||||||
expect(element.textContent).toContain('70 / 100');
|
expect(element.textContent).toContain('70 / 100');
|
||||||
expect(element.querySelector('[data-combat-potion]')?.textContent).toContain('Trank 2/2');
|
expect(element.querySelector('[data-combat-potion]')?.textContent).toContain('Potion 2/2');
|
||||||
|
|
||||||
// First checkpoint: the heal already landed from the player's own action,
|
// First checkpoint: the heal already landed from the player's own action,
|
||||||
// so the HP bar and potion count update here -- well before the monster's
|
// so the HP bar and potion count update here -- well before the monster's
|
||||||
@@ -373,8 +373,8 @@ describe('CombatPageComponent', () => {
|
|||||||
await vi.advanceTimersByTimeAsync(540);
|
await vi.advanceTimersByTimeAsync(540);
|
||||||
fixture.detectChanges();
|
fixture.detectChanges();
|
||||||
expect(element.textContent).toContain('85 / 100');
|
expect(element.textContent).toContain('85 / 100');
|
||||||
expect(element.querySelector('[data-combat-potion]')?.textContent).toContain('Trank 1/2');
|
expect(element.querySelector('[data-combat-potion]')?.textContent).toContain('Potion 1/2');
|
||||||
expect(element.textContent).toContain('Aric Duskwalker trinkt einen Trank und heilt 15 Lebenspunkte.');
|
expect(element.textContent).toContain('Aric Duskwalker drinks a potion and heals 15 HP.');
|
||||||
|
|
||||||
// The monster's reply is still held back at this point.
|
// The monster's reply is still held back at this point.
|
||||||
expect(countOccurrences(element.textContent, monsterHitLine)).toBe(1);
|
expect(countOccurrences(element.textContent, monsterHitLine)).toBe(1);
|
||||||
@@ -444,7 +444,7 @@ describe('CombatPageComponent', () => {
|
|||||||
expect(worldStore.refreshCharacter).toHaveBeenCalledOnce();
|
expect(worldStore.refreshCharacter).toHaveBeenCalledOnce();
|
||||||
});
|
});
|
||||||
|
|
||||||
it('disables Angriff while an action is pending', async () => {
|
it('disables Attack while an action is pending', async () => {
|
||||||
const fixture = await setup(activeCombat);
|
const fixture = await setup(activeCombat);
|
||||||
combatStore.actionPending.set(true);
|
combatStore.actionPending.set(true);
|
||||||
fixture.detectChanges();
|
fixture.detectChanges();
|
||||||
@@ -453,21 +453,21 @@ describe('CombatPageComponent', () => {
|
|||||||
expect(element.querySelector<HTMLButtonElement>('[data-combat-attack]')?.disabled).toBe(true);
|
expect(element.querySelector<HTMLButtonElement>('[data-combat-attack]')?.disabled).toBe(true);
|
||||||
});
|
});
|
||||||
|
|
||||||
it('shows the victory state and hides Angriff when the combat is WON', async () => {
|
it('shows the victory state and hides Attack when the combat is WON', async () => {
|
||||||
const fixture = await setup({ ...activeCombat, status: 'WON' });
|
const fixture = await setup({ ...activeCombat, status: 'WON' });
|
||||||
const element = fixture.nativeElement as HTMLElement;
|
const element = fixture.nativeElement as HTMLElement;
|
||||||
|
|
||||||
expect(element.querySelector('[data-combat-result="WON"]')).toBeTruthy();
|
expect(element.querySelector('[data-combat-result="WON"]')).toBeTruthy();
|
||||||
expect(element.textContent).toContain('Sieg');
|
expect(element.textContent).toContain('Victory');
|
||||||
expect(element.querySelector('[data-combat-attack]')).toBeNull();
|
expect(element.querySelector('[data-combat-attack]')).toBeNull();
|
||||||
});
|
});
|
||||||
|
|
||||||
it('shows the defeat state and hides Angriff when the combat is LOST', async () => {
|
it('shows the defeat state and hides Attack when the combat is LOST', async () => {
|
||||||
const fixture = await setup({ ...activeCombat, status: 'LOST' });
|
const fixture = await setup({ ...activeCombat, status: 'LOST' });
|
||||||
const element = fixture.nativeElement as HTMLElement;
|
const element = fixture.nativeElement as HTMLElement;
|
||||||
|
|
||||||
expect(element.querySelector('[data-combat-result="LOST"]')).toBeTruthy();
|
expect(element.querySelector('[data-combat-result="LOST"]')).toBeTruthy();
|
||||||
expect(element.textContent).toContain('Niederlage');
|
expect(element.textContent).toContain('Defeat');
|
||||||
expect(element.querySelector('[data-combat-attack]')).toBeNull();
|
expect(element.querySelector('[data-combat-attack]')).toBeNull();
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -542,7 +542,7 @@ describe('CombatPageComponent', () => {
|
|||||||
|
|
||||||
it('hides the silver tile entirely when the kill granted none', async () => {
|
it('hides the silver tile entirely when the kill granted none', async () => {
|
||||||
// Every monster seeded in this slice rolls silverMin/silverMax = 0 (design
|
// Every monster seeded in this slice rolls silverMin/silverMax = 0 (design
|
||||||
// R7), so without this guard the victory screen advertises "Silber +0"
|
// R7), so without this guard the victory screen advertises "Silver +0"
|
||||||
// after every single fight in the shipped content.
|
// after every single fight in the shipped content.
|
||||||
const fixture = await setup({
|
const fixture = await setup({
|
||||||
...wonWithRewards,
|
...wonWithRewards,
|
||||||
@@ -589,7 +589,7 @@ describe('CombatPageComponent', () => {
|
|||||||
const element = fixture.nativeElement as HTMLElement;
|
const element = fixture.nativeElement as HTMLElement;
|
||||||
|
|
||||||
expect(element.querySelector('[data-reward-empty]')?.textContent).toContain(
|
expect(element.querySelector('[data-reward-empty]')?.textContent).toContain(
|
||||||
'Keine besondere Beute gefunden.',
|
'No notable loot found.',
|
||||||
);
|
);
|
||||||
expect(element.querySelector('[role="alert"]')).toBeNull();
|
expect(element.querySelector('[role="alert"]')).toBeNull();
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -196,7 +196,7 @@ export class CombatPageComponent implements OnInit {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// The location is the screen a fight resolves back into. It sits beside
|
// The location is the screen a fight resolves back into. It sits beside
|
||||||
// "Weiter jagen" rather than replacing it, so the hunt loop keeps its
|
// "Keep Hunting" rather than replacing it, so the hunt loop keeps its
|
||||||
// one-click rhythm.
|
// one-click rhythm.
|
||||||
protected goToLocation(): void {
|
protected goToLocation(): void {
|
||||||
void this.router.navigate(['/location']);
|
void this.router.navigate(['/location']);
|
||||||
@@ -233,7 +233,7 @@ export class CombatPageComponent implements OnInit {
|
|||||||
if (!combat || combat.monster.pendingIntent !== 'HEAVY_ATTACK') {
|
if (!combat || combat.monster.pendingIntent !== 'HEAVY_ATTACK') {
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
return `${combat.monster.name} bereitet Schweren Hieb vor.`;
|
return `${combat.monster.name} is winding up a Heavy Strike.`;
|
||||||
}
|
}
|
||||||
|
|
||||||
protected logRounds(): CombatLogRound[] {
|
protected logRounds(): CombatLogRound[] {
|
||||||
@@ -254,36 +254,36 @@ export class CombatPageComponent implements OnInit {
|
|||||||
|
|
||||||
protected formatEvent(event: CombatEvent): string {
|
protected formatEvent(event: CombatEvent): string {
|
||||||
const combat = this.displayed();
|
const combat = this.displayed();
|
||||||
const playerName = combat?.player.name ?? 'Du';
|
const playerName = combat?.player.name ?? 'You';
|
||||||
const monsterName = combat?.monster.name ?? 'Der Gegner';
|
const monsterName = combat?.monster.name ?? 'The enemy';
|
||||||
|
|
||||||
if (event.type === 'DAMAGE') {
|
if (event.type === 'DAMAGE') {
|
||||||
const attacker = event.source === 'PLAYER' ? playerName : monsterName;
|
const attacker = event.source === 'PLAYER' ? playerName : monsterName;
|
||||||
const defender = event.target === 'PLAYER' ? playerName : monsterName;
|
const defender = event.target === 'PLAYER' ? playerName : monsterName;
|
||||||
return `${attacker} trifft ${defender} für ${event.amount} Schaden.`;
|
return `${attacker} hits ${defender} for ${event.amount} damage.`;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (event.type === 'HEAL') {
|
if (event.type === 'HEAL') {
|
||||||
return `${playerName} trinkt einen Trank und heilt ${event.amount} Lebenspunkte.`;
|
return `${playerName} drinks a potion and heals ${event.amount} HP.`;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (event.type === 'DEFEND') {
|
if (event.type === 'DEFEND') {
|
||||||
return `${playerName} geht in die Verteidigung.`;
|
return `${playerName} braces to defend.`;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (event.type === 'TELEGRAPH') {
|
if (event.type === 'TELEGRAPH') {
|
||||||
return `${monsterName} bereitet Schweren Hieb vor.`;
|
return `${monsterName} is winding up a Heavy Strike.`;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (event.type === 'INTERRUPT') {
|
if (event.type === 'INTERRUPT') {
|
||||||
return `${playerName} unterbricht den vorbereiteten Angriff von ${monsterName}.`;
|
return `${playerName} interrupts ${monsterName}'s attack.`;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (event.type === 'COMBAT_WON') {
|
if (event.type === 'COMBAT_WON') {
|
||||||
return `${monsterName} wurde besiegt.`;
|
return `${monsterName} has been defeated.`;
|
||||||
}
|
}
|
||||||
|
|
||||||
return `${playerName} wurde im Kampf besiegt.`;
|
return `${playerName} has been defeated.`;
|
||||||
}
|
}
|
||||||
|
|
||||||
private wait(ms: number): Promise<void> {
|
private wait(ms: number): Promise<void> {
|
||||||
|
|||||||
Reference in New Issue
Block a user