diff --git a/apps/web/src/components/game/gameLayout.tsx b/apps/web/src/components/game/gameLayout.tsx index 86b4f86..d18dfc8 100644 --- a/apps/web/src/components/game/gameLayout.tsx +++ b/apps/web/src/components/game/gameLayout.tsx @@ -54,7 +54,9 @@ import { StoryToast } from "./storyToast.js"; import { TranscendencePanel } from "./transcendencePanel.js"; import { UpgradePanel } from "./upgradePanel.js"; import { VampireAchievementsPanel } from "./vampireAchievementsPanel.js"; +import { VampireBossPanel } from "./vampireBossPanel.js"; import { VampireQuestsPanel } from "./vampireQuestsPanel.js"; +import { VampireThrallsPanel } from "./vampireThrallsPanel.js"; import { VampireZonesPanel } from "./vampireZonesPanel.js"; type Mode = "mortal" | "goddess" | "vampire"; @@ -486,9 +488,7 @@ const GameLayout = (): JSX.Element => { } {activeMode === "vampire" && activeVampireTab === "vampire-bosses" - &&
-

{"๐Ÿฉธ Vampire Bosses coming soon..."}

-
+ && } {activeMode === "vampire" && activeVampireTab === "vampire-quests" @@ -496,9 +496,7 @@ const GameLayout = (): JSX.Element => { } {activeMode === "vampire" && activeVampireTab === "thralls" - &&
-

{"๐ŸงŸ Thralls coming soon..."}

-
+ && } {activeMode === "vampire" && activeVampireTab === "vampire-equipment" diff --git a/apps/web/src/components/game/vampireBossPanel.tsx b/apps/web/src/components/game/vampireBossPanel.tsx new file mode 100644 index 0000000..66dd52a --- /dev/null +++ b/apps/web/src/components/game/vampireBossPanel.tsx @@ -0,0 +1,504 @@ +/** + * @file Vampire Boss panel โ€” challenge vampire realm bosses. + * @copyright nhcarrigan + * @license Naomi's Public License + * @author Naomi Carrigan + */ +/* eslint-disable max-lines -- Panel with sub-component, modal, and zone filter */ +/* eslint-disable max-lines-per-function -- Complex component with many render paths */ +/* eslint-disable complexity -- Boss card requires many conditional render paths */ +/* eslint-disable max-statements -- Panel requires many variable declarations */ +/* eslint-disable react/no-multi-comp -- Sub-components are tightly coupled to this panel */ +import { type JSX, useState } from "react"; +import { useGame } from "../../context/gameContext.js"; +import type { + VampireBoss, + VampireBossChallengeResponse, + VampireZone, +} from "@elysium/types"; + +interface VampireBossCardProperties { + readonly boss: VampireBoss; + readonly onChallenge: (bossId: string)=> void; + readonly isChallenging: boolean; + readonly unlockHint: string | undefined; + readonly formatNumber: (n: number)=> string; + readonly formatInteger: (n: number)=> string; +} + +/** + * Renders a single vampire boss card. + * @param props - The boss card properties. + * @param props.boss - The boss data. + * @param props.onChallenge - Callback to challenge this boss. + * @param props.isChallenging - Whether this boss is currently being challenged. + * @param props.unlockHint - Optional hint for how to unlock this boss. + * @param props.formatNumber - The number formatting utility function. + * @param props.formatInteger - The integer formatting utility function. + * @returns The JSX element. + */ +const VampireBossCard = ({ + boss, + onChallenge, + isChallenging, + unlockHint, + formatNumber, + formatInteger, +}: VampireBossCardProperties): JSX.Element => { + const canChallenge + = (boss.status === "available" || boss.status === "in_progress") + && !isChallenging; + + const hpRatio = boss.currentHp / boss.maxHp; + const hpPercent = hpRatio * 100; + + function handleChallenge(): void { + onChallenge(boss.id); + } + + return ( +
+
+

{boss.name}

+

{boss.description}

+ {boss.status === "locked" && unlockHint !== undefined + ?

{unlockHint}

+ : null} + {boss.siringRequirement > 0 + ?

+ {"๐Ÿฉธ Requires Siring "} + {boss.siringRequirement} +

+ : null} +
+ + {boss.status !== "locked" && boss.status !== "defeated" + ?
+
+
+
+ + {formatNumber(boss.currentHp)} + {" / "} + {formatNumber(boss.maxHp)} + {" HP"} + +
+ : null} + +
+ + {"๐Ÿ’ข Boss DPS: "} + {formatNumber(boss.damagePerSecond)} + +
+ +
+ {boss.bloodReward > 0 + && + {"๐Ÿฉธ "} + {formatNumber(boss.bloodReward)} + {" Blood"} + + } + {boss.ichorReward > 0 + && + {"๐Ÿ’ง "} + {formatInteger(boss.ichorReward)} + {" Ichor"} + + } + {boss.soulShardsReward > 0 + && + {"๐Ÿ’  "} + {formatInteger(boss.soulShardsReward)} + {" Soul Shards"} + + } + {boss.equipmentRewards.length > 0 + && + {"๐Ÿฆ‡ "} + {boss.equipmentRewards.length} + {" Equipment"} + + } + {boss.status !== "defeated" + && boss.bountyIchor > 0 + && boss.bountyIchorClaimed !== true + ? + {"๐Ÿ’ง "} + {boss.bountyIchor} + {" Ichor (first kill)"} + + : null} +
+ + {boss.status === "available" || boss.status === "in_progress" + ? + : null} + + {boss.status === "defeated" + ? {"โ˜ ๏ธ Defeated"} + : null} +
+ ); +}; + +interface VampireBattleModalProperties { + readonly result: VampireBossChallengeResponse; + readonly onDismiss: ()=> void; + readonly formatNumber: (n: number)=> string; + readonly formatInteger: (n: number)=> string; +} + +/** + * Renders the vampire battle result modal overlay. + * @param props - The modal properties. + * @param props.result - The battle result data. + * @param props.onDismiss - Callback to dismiss the modal. + * @param props.formatNumber - The number formatting utility function. + * @param props.formatInteger - The integer formatting utility function. + * @returns The JSX element. + */ +const VampireBattleModal = ({ + result, + onDismiss, + formatNumber, + formatInteger, +}: VampireBattleModalProperties): JSX.Element => { + return ( +
+
+

+ {result.won + ? "๐Ÿฉธ Victory!" + : "๐Ÿ’€ Defeated!"} +

+ +
+
+ {"โš”๏ธ Your Thrall DPS"} + {formatNumber(result.partyDPS)} +
+
+ {"๐Ÿ’ข Boss DPS"} + {formatNumber(result.bossDPS)} +
+
+ {"โค๏ธ Boss HP Before"} + + {formatNumber(result.bossHpBefore)} + {" / "} + {formatNumber(result.bossMaxHp)} + +
+
+ {"โค๏ธ Boss HP After"} + {formatNumber(result.bossNewHp)} +
+
+ {"๐Ÿ›ก๏ธ Thrall HP Remaining"} + + {formatNumber(result.partyHpRemaining)} + {" / "} + {formatNumber(result.partyMaxHp)} + +
+
+ + {result.won && result.rewards !== undefined + ?
+

{"Rewards"}

+ {result.rewards.blood > 0 + ?

+ {"๐Ÿฉธ "} + {formatNumber(result.rewards.blood)} + {" Blood"} +

+ : null} + {result.rewards.ichor > 0 + ?

+ {"๐Ÿ’ง "} + {formatInteger(result.rewards.ichor)} + {" Ichor"} +

+ : null} + {result.rewards.soulShards > 0 + ?

+ {"๐Ÿ’  "} + {formatInteger(result.rewards.soulShards)} + {" Soul Shards"} +

+ : null} + {result.rewards.bountyIchor > 0 + ?

+ {"๐Ÿ’ง "} + {formatInteger(result.rewards.bountyIchor)} + {" Ichor (first kill bonus!)"} +

+ : null} + {result.rewards.upgradeIds.length > 0 + ?

+ {"๐Ÿ”“ "} + {result.rewards.upgradeIds.length} + {" Upgrade(s) unlocked"} +

+ : null} + {result.rewards.equipmentIds.length > 0 + ?

+ {"๐Ÿฆ‡ "} + {result.rewards.equipmentIds.length} + {" Equipment item(s) gained"} +

+ : null} +
+ : null} + + {result.casualties !== undefined && result.casualties.length > 0 + ?
+

{"Casualties"}

+ {result.casualties.map((casualty) => { + return ( +

+ {casualty.killed} + {" "} + {casualty.thrallId} + {" lost"} +

+ ); + })} +
+ : null} + + +
+
+ ); +}; + +/** + * Renders the Vampire Boss panel with zone filtering and battle result modal. + * @returns The JSX element. + */ +const VampireBossPanel = (): JSX.Element => { + const { + state, + challengeVampireBoss, + vampireBattleResult, + dismissVampireBattle, + formatNumber, + formatInteger, + } = useGame(); + + const [ challengingBossId, setChallengingBossId ] = useState( + null, + ); + const [ activeZoneId, setActiveZoneId ] = useState(() => { + return sessionStorage.getItem("elysium_vampire_boss_zone"); + }); + + if (state === null) { + return ( +
+

{"Loading..."}

+
+ ); + } + + const { vampire } = state; + + if (vampire === undefined) { + return ( +
+

{"The Vampire expansion is not yet unlocked."}

+
+ ); + } + + const { bosses, quests, zones } = vampire; + + async function handleChallenge(bossId: string): Promise { + setChallengingBossId(bossId); + try { + await challengeVampireBoss(bossId); + } finally { + setChallengingBossId(null); + } + } + + function handleChallengeClick(bossId: string): void { + void handleChallenge(bossId); + } + + function handleZoneSelect(zoneId: string): void { + setActiveZoneId(zoneId); + sessionStorage.setItem("elysium_vampire_boss_zone", zoneId); + } + + function handleShowAll(): void { + setActiveZoneId(null); + sessionStorage.removeItem("elysium_vampire_boss_zone"); + } + + const filteredBosses = activeZoneId === null + ? bosses + : bosses.filter((boss) => { + return boss.zoneId === activeZoneId; + }); + + const bossUnlockHints = new Map(); + for (const zone of zones) { + const { id: zoneId, unlockBossId, unlockQuestId } = zone; + const zoneBosses = bosses.filter((boss) => { + return boss.zoneId === zoneId; + }); + for (let index = 0; index < zoneBosses.length; index = index + 1) { + const boss = zoneBosses[index]; + if (boss === undefined || boss.status !== "locked") { + continue; + } + if (index === 0) { + const parts: Array = []; + if (unlockBossId !== null) { + const gateBoss = bosses.find((candidate) => { + return candidate.id === unlockBossId; + }); + if (gateBoss !== undefined) { + parts.push(`๐Ÿฉธ Defeat: ${gateBoss.name}`); + } + } + if (unlockQuestId !== null) { + const gateQuest = quests.find((candidate) => { + return candidate.id === unlockQuestId; + }); + if (gateQuest !== undefined) { + parts.push(`๐Ÿ“œ Complete: ${gateQuest.name}`); + } + } + if (parts.length > 0) { + bossUnlockHints.set(boss.id, parts.join(" & ")); + } + } else { + const previousBoss = zoneBosses[index - 1]; + if (previousBoss !== undefined) { + bossUnlockHints.set(boss.id, `๐Ÿฉธ Defeat: ${previousBoss.name} first`); + } + } + } + } + + const activeZoneData: VampireZone | undefined = activeZoneId === null + ? undefined + : zones.find((zone) => { + return zone.id === activeZoneId; + }); + + return ( +
+
+

{"๐Ÿฉธ Vampire Bosses"}

+
+ +
+ + {zones.map((zone) => { + function handleSelect(): void { + handleZoneSelect(zone.id); + } + return ( + + ); + })} +
+ + {activeZoneData?.status === "locked" + ?
+

{"๐Ÿ”’ This zone is locked."}

+ {activeZoneData.unlockBossId === null + ? null + :

+ {"๐Ÿฉธ Defeat: "} + {bosses.find((boss) => { + return boss.id === activeZoneData.unlockBossId; + })?.name ?? activeZoneData.unlockBossId} +

} + {activeZoneData.unlockQuestId === null + ? null + :

+ {"๐Ÿ“œ Complete: "} + {quests.find((quest) => { + return quest.id === activeZoneData.unlockQuestId; + })?.name ?? activeZoneData.unlockQuestId} +

} +
+ : null} + +
+ {filteredBosses.map((boss) => { + return ( + + ); + })} + {filteredBosses.length === 0 + ?

{"No bosses to show in this zone."}

+ : null} +
+ + {vampireBattleResult === null + ? null + : } +
+ ); +}; + +export { VampireBossPanel }; diff --git a/apps/web/src/components/game/vampireThrallsPanel.tsx b/apps/web/src/components/game/vampireThrallsPanel.tsx new file mode 100644 index 0000000..830050e --- /dev/null +++ b/apps/web/src/components/game/vampireThrallsPanel.tsx @@ -0,0 +1,273 @@ +/** + * @file Thralls panel component for purchasing vampire thralls. + * @copyright nhcarrigan + * @license Naomi's Public License + * @author Naomi Carrigan + */ +/* eslint-disable max-lines-per-function -- Complex component with many render paths */ +/* eslint-disable react/no-multi-comp -- ThrallCard sub-component is tightly coupled */ +/* eslint-disable complexity -- ThrallCard has inherent branching for batch/afford logic */ +import { type JSX, useState } from "react"; +import { useGame } from "../../context/gameContext.js"; +import type { VampireThrall } from "@elysium/types"; + +type BatchSize = 1 | 10 | "max"; +const batchOptions: Array = [ 1, 10, "max" ]; + +const growthRate = 1.15; + +/** + * Computes the total blood cost to buy a batch of thralls. + * @param thrall - The thrall tier to purchase. + * @param quantity - The number to buy. + * @returns The total blood cost. + */ +const computeBatchCost = ( + thrall: VampireThrall, + quantity: number, +): number => { + let total = 0; + for (let index = 0; index < quantity; index = index + 1) { + const exponent = thrall.count + index; + const cost = thrall.baseCost * Math.pow(growthRate, exponent); + total = total + cost; + } + return total; +}; + +/** + * Computes the maximum number of thralls affordable with the available blood. + * @param thrall - The thrall tier. + * @param blood - The available blood balance. + * @returns The maximum affordable quantity. + */ +const computeMaxAffordable = ( + thrall: VampireThrall, + blood: number, +): number => { + let total = 0; + let quantity = 0; + for (let index = 0; index < 100_000; index = index + 1) { + const exponent = thrall.count + index; + const cost = thrall.baseCost * Math.pow(growthRate, exponent); + if (total + cost > blood) { + break; + } + total = total + cost; + quantity = quantity + 1; + } + return quantity; +}; + +/** + * Parses a localStorage string back into a valid BatchSize, defaulting to 1. + * @param stored - The raw string from localStorage (or null if absent). + * @returns A valid BatchSize value. + */ +const parseBatchSize = (stored: string | null): BatchSize => { + if (stored === "max") { + return "max"; + } + if (stored === "10") { + return 10; + } + return 1; +}; + +interface ThrallCardProperties { + readonly thrall: VampireThrall; + readonly blood: number; + readonly selectedBatch: BatchSize; +} + +/** + * Renders a single thrall purchase card. + * @param props - The component properties. + * @param props.thrall - The thrall tier to display. + * @param props.blood - The player's current blood balance. + * @param props.selectedBatch - The active batch size selection. + * @returns The JSX element. + */ +const ThrallCard = ({ + thrall, + blood, + selectedBatch, +}: ThrallCardProperties): JSX.Element => { + const { buyVampireThrall, formatNumber } = useGame(); + + const maxAffordable = computeMaxAffordable(thrall, blood); + const effectiveBatch = selectedBatch === "max" + ? maxAffordable + : selectedBatch; + const batchCost = computeBatchCost(thrall, effectiveBatch); + const canAffordBatch = blood >= batchCost && effectiveBatch > 0; + + const singleCost = computeBatchCost(thrall, 1); + + function handleBuy(): void { + if (effectiveBatch > 0) { + buyVampireThrall(thrall.id, effectiveBatch); + } + } + + function getBuyButtonLabel(): string { + if (selectedBatch === "max") { + if (maxAffordable === 0) { + return "Can't Afford"; + } + return `Buy Max (ร—${String(maxAffordable)})`; + } + return `Buy ร—${String(effectiveBatch)}`; + } + + return ( +
+
+
+

{thrall.name}

+ {thrall.class} +
+ + {"ร—"} + {formatNumber(thrall.count)} + +
+
+ {thrall.bloodPerSecond > 0 + && + {"๐Ÿฉธ "} + {formatNumber(thrall.bloodPerSecond)} + {"/s blood"} + + } + {thrall.ichorPerSecond > 0 + && + {"๐Ÿ’ง "} + {formatNumber(thrall.ichorPerSecond)} + {"/s ichor"} + + } + + {"โš”๏ธ "} + {formatNumber(thrall.combatPower)} + {" combat power each"} + +
+
+ + {"Next: ๐Ÿฉธ "} + {formatNumber(singleCost)} + + {selectedBatch !== 1 + && effectiveBatch > 0 + && + {selectedBatch === "max" + ? "Max" + : String(selectedBatch)} + {" (ร—"} + {String(effectiveBatch)} + {"): ๐Ÿฉธ "} + {formatNumber(batchCost)} + + } +
+ {thrall.unlocked + ? + : {"๐Ÿ”’ Locked"} + } +
+ ); +}; + +/** + * Renders the thralls panel for purchasing vampire thralls. + * @returns The JSX element. + */ +const VampireThrallsPanel = (): JSX.Element => { + const { state, formatNumber } = useGame(); + const [ selectedBatch, setSelectedBatch ] = useState(() => { + return parseBatchSize(localStorage.getItem("elysium_thrall_batch")); + }); + + if (state === null) { + return ( +
+

{"Loading..."}

+
+ ); + } + + const vampireState = state.vampire; + if (vampireState === undefined) { + return ( +
+

{"Vampire expansion not yet unlocked."}

+
+ ); + } + + const blood = state.resources.blood ?? 0; + const { thralls } = vampireState; + + function handleBatchSelect(batch: BatchSize): void { + setSelectedBatch(batch); + localStorage.setItem("elysium_thrall_batch", String(batch)); + } + + return ( +
+

{"Thralls"}

+
+ + {"๐Ÿฉธ Blood: "} + {formatNumber(blood)} + +
+
+ {batchOptions.map((batch) => { + function handleClick(): void { + handleBatchSelect(batch); + } + return ; + })} +
+
+ {thralls.map((thrall: VampireThrall) => { + return ; + })} +
+
+ ); +}; + +export { VampireThrallsPanel }; diff --git a/apps/web/src/context/gameContext.tsx b/apps/web/src/context/gameContext.tsx index 656a1ea..bdb9cef 100644 --- a/apps/web/src/context/gameContext.tsx +++ b/apps/web/src/context/gameContext.tsx @@ -22,6 +22,7 @@ import { type GameState, type GoddessBossChallengeResponse, type GoddessExploreCollectResponse, + type VampireBossChallengeResponse, type LoginBonusResult, type NumberFormat, type Quest, @@ -49,6 +50,7 @@ import { buyPrestigeUpgrade as buyPrestigeUpgradeApi, challengeBoss as challengeBossApi, challengeGoddessBoss as challengeGoddessBossApi, + challengeVampireBoss as challengeVampireBossApi, collectExploration as collectExplorationApi, collectGoddessExploration as collectGoddessExplorationApi, consecrate as consecrateApi, @@ -768,6 +770,26 @@ interface GameContextValue { collectGoddessExploration: ( areaId: string, )=> Promise; + + /** + * Challenge a vampire boss โ€” runs full server-side vampire combat simulation. + */ + challengeVampireBoss: (bossId: string)=> Promise; + + /** + * Vampire battle result to display (null when no battle pending). + */ + vampireBattleResult: VampireBossChallengeResponse | null; + + /** + * Dismiss the vampire battle result modal. + */ + dismissVampireBattle: ()=> void; + + /** + * Buy one or more thralls (client-side blood deduction). + */ + buyVampireThrall: (thrallId: string, quantity: number)=> void; } export interface BattleResult { @@ -820,6 +842,8 @@ export const GameProvider = ({ const [ bossError, setBossError ] = useState(null); const [ goddessBattleResult, setGoddessBattleResult ] = useState(null); + const [ vampireBattleResult, setVampireBattleResult ] + = useState(null); const [ showConsecrationToast, setShowConsecrationToast ] = useState(false); const [ showEnlightenmentToast, setShowEnlightenmentToast ] = useState(false); const syncErrorTimerReference = useRef | null>( @@ -2085,6 +2109,115 @@ export const GameProvider = ({ setGoddessBattleResult(null); }, []); + const challengeVampireBoss = useCallback(async(bossId: string) => { + setVampireBattleResult(null); + try { + const result = await challengeVampireBossApi({ bossId }); + if (result.signature !== undefined) { + signatureReference.current = result.signature; + localStorage.setItem("elysium_save_signature", result.signature); + } + setState((previous) => { + if (previous?.vampire === undefined) { + return previous; + } + const updatedBosses = previous.vampire.bosses.map((boss) => { + return boss.id === bossId + ? { + ...boss, + currentHp: result.bossNewHp, + status: result.won + ? ("defeated" as const) + : ("available" as const), + } + : boss; + }); + const updatedThralls = result.casualties === undefined + ? previous.vampire.thralls + : previous.vampire.thralls.map((thrall) => { + const casualty = result.casualties?.find((c) => { + return c.thrallId === thrall.id; + }); + return casualty === undefined + ? thrall + : { + ...thrall, + count: Math.max(0, thrall.count - casualty.killed), + }; + }); + return { + ...previous, + resources: { + ...previous.resources, + blood: (previous.resources.blood ?? 0) + + (result.rewards?.blood ?? 0), + }, + vampire: { + ...previous.vampire, + awakening: { + ...previous.vampire.awakening, + soulShards: previous.vampire.awakening.soulShards + + (result.rewards?.soulShards ?? 0), + }, + bosses: updatedBosses, + siring: { + ...previous.vampire.siring, + ichor: previous.vampire.siring.ichor + + (result.rewards?.ichor ?? 0), + }, + thralls: updatedThralls, + }, + }; + }); + setVampireBattleResult(result); + } catch (error_: unknown) { + logError("challenge_vampire_boss", error_); + } + }, []); + + const dismissVampireBattle = useCallback(() => { + setVampireBattleResult(null); + }, []); + + const buyVampireThrall = useCallback( + (thrallId: string, quantity: number) => { + setState((previous) => { + if (previous?.vampire === undefined) { + return previous; + } + const thrall = previous.vampire.thralls.find((t) => { + return t.id === thrallId; + }); + if (thrall === undefined) { + return previous; + } + const geometric = thrall.baseCost * (1 - Math.pow(1.15, quantity)); + const normalised = geometric / (1 - 1.15); + const totalCost = normalised * Math.pow(1.15, thrall.count); + const currentBlood = previous.resources.blood ?? 0; + if (currentBlood < totalCost) { + return previous; + } + return { + ...previous, + resources: { + ...previous.resources, + blood: currentBlood - totalCost, + }, + vampire: { + ...previous.vampire, + thralls: previous.vampire.thralls.map((t) => { + return t.id === thrallId + ? { ...t, count: t.count + quantity } + : t; + }), + }, + }; + }); + }, + [], + ); + const consecrate = useCallback(async() => { try { const result = await consecrateApi({}); @@ -3046,8 +3179,10 @@ export const GameProvider = ({ buyGoddessUpgrade, buyPrestigeUpgrade, buyUpgrade, + buyVampireThrall, challengeBoss, challengeGoddessBoss, + challengeVampireBoss, collectExploration, collectGoddessExploration, completeChapter, @@ -3071,6 +3206,7 @@ export const GameProvider = ({ dismissPrestigeToast, dismissStoryChapter, dismissTranscendenceToast, + dismissVampireBattle, enableNotifications, enableSounds, enlighten, @@ -3124,6 +3260,7 @@ export const GameProvider = ({ unlockedAchievements, unlockedCodexEntryIds, unlockedStoryChapterIds, + vampireBattleResult, }; }, [ apotheosis, @@ -3141,8 +3278,10 @@ export const GameProvider = ({ buyGoddessUpgrade, buyPrestigeUpgrade, buyUpgrade, + buyVampireThrall, challengeBoss, challengeGoddessBoss, + challengeVampireBoss, collectExploration, collectGoddessExploration, completeChapter, @@ -3166,6 +3305,7 @@ export const GameProvider = ({ dismissPrestigeToast, dismissStoryChapter, dismissTranscendenceToast, + dismissVampireBattle, enableNotifications, enableSounds, enlighten, @@ -3218,6 +3358,7 @@ export const GameProvider = ({ unlockedAchievements, unlockedCodexEntryIds, unlockedStoryChapterIds, + vampireBattleResult, ]); return (