Android build v1.0.53

This commit is contained in:
Warren H
2026-06-21 20:07:26 -04:00
parent 1e24aecad8
commit 421540c52b
14 changed files with 1329 additions and 69 deletions
+94 -5
View File
@@ -39,6 +39,13 @@ export type DualScreenCombatState = {
encounterIndex: number
encounterCount: number
party: PartyMember[]
opponentName?: string
opponentClassName?: string
opponentParty?: PartyMember[]
opponentResource?: number
opponentEnemyHealth?: number
opponentBuffSummary?: string
opponentDebuffSummary?: string
floatingTexts: Array<{
id: number
memberId: string
@@ -431,17 +438,62 @@ export function DualScreenBottomDisplay() {
}
return (
<main className="dual-bottom-display">
<main className={`dual-bottom-display ${state.opponentParty ? 'pvp-opponent-bottom-display' : ''}`}>
<header className="dual-controls-header">
<div>
<p className="eyebrow">{state.difficultyName} {state.contentName}</p>
<h1>{state.dungeonName}</h1>
<p className="eyebrow">{state.opponentParty ? 'Opponent View' : `${state.difficultyName} ${state.contentName}`}</p>
<h1>{state.opponentParty ? state.opponentName : state.dungeonName}</h1>
{state.opponentParty && <small>{state.opponentClassName}</small>}
</div>
<div className="dual-controls-progress">
<span>Encounter {state.encounterIndex + 1}/{state.encounterCount}</span>
<span>{state.opponentParty ? 'Live PvP' : `Encounter ${state.encounterIndex + 1}/${state.encounterCount}`}</span>
</div>
</header>
{state.opponentParty ? (
<>
<section className="dual-opponent-progress">
<div>
<p className="eyebrow">Opponent Clear</p>
<strong>{Math.max(0, Math.floor(state.opponentEnemyHealth ?? 0))} / {state.encounterMaxHealth}</strong>
</div>
<div className="bar enemy-health boss-bar">
<span style={{ width: `${Math.max(0, ((state.opponentEnemyHealth ?? 0) / state.encounterMaxHealth) * 100)}%` }} />
</div>
</section>
<section className={`dual-opponent-party-grid ${state.opponentParty.length > 6 ? 'raid' : ''}`}>
{state.opponentParty.map((member) => (
<article className={`dual-opponent-member ${member.health <= 0 ? 'dead' : ''}`} key={member.id}>
<div className="member-header">
<span className={`role role-${member.role.toLowerCase()}`}>{member.role[0]}</span>
<strong>{member.name}</strong>
<small>{Math.ceil(member.health)} / {member.maxHealth}</small>
</div>
<div className="bar member-health">
<span style={{ width: `${(member.health / member.maxHealth) * 100}%` }} />
{member.shield > 0 && (
<i style={{ width: `${(member.shield / member.maxHealth) * 100}%` }} />
)}
</div>
<div className="member-effects">
{memberHotEffects(member).map((effect) => (
<span className="buff" key={effect.id}>{effect.label}</span>
))}
{member.debuff && <span className="debuff">{member.debuff}</span>}
</div>
</article>
))}
</section>
<section className="dual-opponent-effects">
<span>Buffs: {state.opponentBuffSummary || 'none'}</span>
<span>Debuffs: {state.opponentDebuffSummary || 'none'}</span>
</section>
</>
) : (
<>
<section className="dual-controls-resource">
<div>
<p className="eyebrow">Active Target</p>
@@ -542,6 +594,8 @@ export function DualScreenBottomDisplay() {
)
})}
</section>
</>
)}
</main>
)
}
@@ -549,9 +603,11 @@ export function DualScreenBottomDisplay() {
export function DualScreenTopCombat({
state,
onSelectTarget,
onCastSpell,
}: {
state: DualScreenCombatState
onSelectTarget: (id: string) => void
onCastSpell?: (spell: Spell) => void
}) {
const enemyPercent = Math.max(
0,
@@ -602,7 +658,6 @@ export function DualScreenTopCombat({
{member.shield > 0 && (
<i style={{ width: `${(member.shield / member.maxHealth) * 100}%` }} />
)}
<em className="health-text">{Math.ceil(member.health)} / {member.maxHealth}</em>
</div>
<div className="floating-combat-texts" aria-hidden="true">
{state.floatingTexts
@@ -629,6 +684,40 @@ export function DualScreenTopCombat({
</div>
</section>
<section className="dual-top-spell-strip">
{state.spells.map((spell, slotIndex) => {
if (!spell) return <div className="dual-top-spell empty" key={`empty-${slotIndex}`} />
const percent = spell.remaining > 0
? Math.min(100, (spell.remaining / Math.max(1, spell.cooldown)) * 100)
: 0
return (
<button
className="dual-top-spell"
disabled={
!state.playerIsAlive
|| state.resource < spell.cost
|| spell.remaining > 0
|| state.status !== 'playing'
|| state.paused
}
key={spell.id}
onClick={() => onCastSpell?.(spell)}
type="button"
>
<span className={`spell-icon spell-${spell.kind}`}>{spell.glyph}</span>
{spell.remaining > 0 && <i style={{ height: `${percent}%` }} />}
{spell.remaining > 0 && <small>{spell.remaining.toFixed(0)}</small>}
</button>
)
})}
<div className="dual-top-resource">
<strong>{state.resourceName} {Math.floor(state.resource)} / {state.maxResource}</strong>
<div className="bar mana-bar">
<span style={{ width: `${(state.resource / state.maxResource) * 100}%` }} />
</div>
</div>
</section>
</div>
)
}