Android build v1.0.43

This commit is contained in:
Warren H
2026-06-20 22:34:38 -04:00
parent 5aac39c6c9
commit 6e10b37f8e
21 changed files with 2095 additions and 337 deletions
+77
View File
@@ -6732,8 +6732,16 @@ h2 {
}
/* ─── Admin Panel ─── */
.admin-screen {
height: 100dvh;
margin-top: 0;
overflow-x: hidden;
overflow-y: auto;
}
.admin-tabs {
display: flex;
flex-wrap: wrap;
gap: 8px;
margin: 16px 0;
}
@@ -6831,6 +6839,16 @@ h2 {
width: 84px;
}
.admin-thumb {
aspect-ratio: 1;
background: #1c1e25;
border: 2px solid #090a0d;
display: block;
object-fit: cover;
outline: 2px solid #41404a;
width: 48px;
}
.boss-upload-button {
background: #15161c;
border: 2px solid #090a0d;
@@ -7062,6 +7080,7 @@ h2 {
.admin-add-form {
align-items: end;
display: flex;
flex-wrap: wrap;
gap: 12px;
}
@@ -7091,6 +7110,59 @@ h2 {
outline-color: var(--gold);
}
.admin-upgrade-toolbar {
align-items: end;
display: grid;
gap: 12px;
grid-template-columns: minmax(180px, 0.8fr) minmax(120px, 0.45fr) minmax(120px, 0.45fr) minmax(320px, 1.4fr);
}
.admin-upgrade-toolbar label,
.admin-upgrade-step label {
align-items: stretch;
color: var(--muted);
display: flex;
flex-direction: column;
font-family: 'Press Start 2P', monospace;
font-size: 7px;
gap: 6px;
text-transform: uppercase;
}
.admin-upgrade-toolbar select,
.admin-upgrade-step select {
background: #15161c;
border: 2px solid #090a0d;
color: var(--ink);
cursor: pointer;
font-family: 'Press Start 2P', monospace;
font-size: 8px;
min-height: 36px;
outline: 2px solid #41404a;
padding: 0 10px;
}
.admin-upgrade-chain {
display: grid;
gap: 10px;
}
.admin-upgrade-step {
background: var(--panel-light);
border: 2px solid #090a0d;
display: grid;
gap: 10px;
grid-template-columns: minmax(260px, 1fr) minmax(280px, 1.2fr);
outline: 2px solid #494754;
padding: 12px;
}
.admin-upgrade-current {
align-items: center;
display: flex;
gap: 10px;
}
.admin-crafting-filters {
align-items: end;
display: grid;
@@ -7173,6 +7245,11 @@ h2 {
}
@media (max-width: 800px) {
.admin-upgrade-toolbar,
.admin-upgrade-step {
grid-template-columns: 1fr;
}
.admin-crafting-filters {
grid-template-columns: 1fr;
}
File diff suppressed because it is too large Load Diff
+21 -7
View File
@@ -29,6 +29,26 @@ const CRAFTING_LIST_PAGE_SIZE = 3
const CRAFTING_FILTER_SLOTS = (Object.keys(SLOT_LABELS) as EquipmentSlot[])
.filter((slot) => slot !== 'component')
const DIRECT_CRAFT_ITEM_LEVELS = new Set([1, 10, 20, 25])
type CraftingRecipe = CharacterProfile['craftingRecipes'][number]
function selectUpgradeRecipe(
paths: CharacterProfile['gearUpgradePaths'],
recipes: CraftingRecipe[],
item: Pick<Item, 'id' | 'slot' | 'itemLevel'>,
) {
const path = paths.find((candidate) => candidate.fromItemId === item.id)
if (path) {
const pathRecipe = recipes.find((recipe) => recipe.item.id === path.toItemId)
if (pathRecipe) return pathRecipe
}
const candidates = recipes.filter((recipe) =>
recipe.item.slot === item.slot
&& recipe.item.itemLevel > item.itemLevel
)
const nextItemLevel = Math.min(...candidates.map((recipe) => recipe.item.itemLevel))
if (!Number.isFinite(nextItemLevel)) return undefined
return candidates.find((recipe) => recipe.item.itemLevel === nextItemLevel)
}
type Props = {
profile: CharacterProfile
@@ -85,13 +105,7 @@ export function EquipmentScreen({
? profile.craftingRecipes.find((recipe) => recipe.item.id === selectedItem.id)
: undefined
const upgradeRecipe = selectedItem && selectedItemRecipe
? profile.craftingRecipes
.filter((recipe) =>
recipe.sourceEncounterId === selectedItemRecipe.sourceEncounterId
&& recipe.item.slot === selectedItem.slot
&& recipe.item.itemLevel > selectedItem.itemLevel,
)
.sort((left, right) => left.item.itemLevel - right.item.itemLevel)[0]
? selectUpgradeRecipe(profile.gearUpgradePaths ?? [], profile.craftingRecipes, selectedItem)
: undefined
const equippedBySlot = useMemo(
() => new Map(
+22 -7
View File
@@ -386,6 +386,27 @@ function addInventoryItem(inventory: Item[], item: Omit<Item, 'quantity' | 'equi
return { duplicate: false, quantityAfter: quantity }
}
type CraftingRecipe = CharacterProfile['craftingRecipes'][number]
function selectUpgradeRecipe(
paths: CharacterProfile['gearUpgradePaths'],
recipes: CraftingRecipe[],
item: Pick<Item, 'id' | 'slot' | 'itemLevel'>,
) {
const path = paths.find((candidate) => candidate.fromItemId === item.id)
if (path) {
const pathRecipe = recipes.find((recipe) => recipe.item.id === path.toItemId)
if (pathRecipe) return pathRecipe
}
const candidates = recipes.filter((recipe) =>
recipe.item.slot === item.slot
&& recipe.item.itemLevel > item.itemLevel
)
const nextItemLevel = Math.min(...candidates.map((recipe) => recipe.item.itemLevel))
if (!Number.isFinite(nextItemLevel)) return undefined
return candidates.find((recipe) => recipe.item.itemLevel === nextItemLevel)
}
function experienceForLevel(level: number) {
return (level - 1) * (level - 1) * 100
}
@@ -1307,13 +1328,7 @@ function createLocalRepository(store: LocalSaveStore): GameRepository {
if (item.slot === 'component') throw new Error('Components cannot be upgraded.')
const currentRecipe = profile.craftingRecipes.find((recipe) => recipe.item.id === item.id)
const targetRecipe = currentRecipe
? profile.craftingRecipes
.filter((recipe) =>
recipe.sourceEncounterId === currentRecipe.sourceEncounterId
&& recipe.item.slot === item.slot
&& recipe.item.itemLevel > item.itemLevel,
)
.sort((left, right) => left.item.itemLevel - right.item.itemLevel)[0]
? selectUpgradeRecipe(profile.gearUpgradePaths ?? [], profile.craftingRecipes, item)
: null
if (!targetRecipe) throw new Error('No upgrade is available for this item.')
const missing = targetRecipe.components.find((component) => component.owned < component.quantity)
+249
View File
@@ -833,6 +833,228 @@
"active": false
}
],
"gearUpgradePaths": [
{
"fromItemId": 1,
"toItemId": 201
},
{
"fromItemId": 2,
"toItemId": 202
},
{
"fromItemId": 3,
"toItemId": 203
},
{
"fromItemId": 4,
"toItemId": 204
},
{
"fromItemId": 5,
"toItemId": 205
},
{
"fromItemId": 6,
"toItemId": 206
},
{
"fromItemId": 7,
"toItemId": 207
},
{
"fromItemId": 8,
"toItemId": 208
},
{
"fromItemId": 9,
"toItemId": 209
},
{
"fromItemId": 100,
"toItemId": 3
},
{
"fromItemId": 101,
"toItemId": 5
},
{
"fromItemId": 102,
"toItemId": 2
},
{
"fromItemId": 103,
"toItemId": 6
},
{
"fromItemId": 104,
"toItemId": 4
},
{
"fromItemId": 105,
"toItemId": 1
},
{
"fromItemId": 106,
"toItemId": 7
},
{
"fromItemId": 107,
"toItemId": 3
},
{
"fromItemId": 108,
"toItemId": 8
},
{
"fromItemId": 109,
"toItemId": 9
},
{
"fromItemId": 201,
"toItemId": 301
},
{
"fromItemId": 202,
"toItemId": 302
},
{
"fromItemId": 203,
"toItemId": 303
},
{
"fromItemId": 204,
"toItemId": 304
},
{
"fromItemId": 205,
"toItemId": 305
},
{
"fromItemId": 206,
"toItemId": 306
},
{
"fromItemId": 207,
"toItemId": 307
},
{
"fromItemId": 208,
"toItemId": 308
},
{
"fromItemId": 209,
"toItemId": 309
},
{
"fromItemId": 301,
"toItemId": 401
},
{
"fromItemId": 302,
"toItemId": 402
},
{
"fromItemId": 303,
"toItemId": 403
},
{
"fromItemId": 304,
"toItemId": 404
},
{
"fromItemId": 305,
"toItemId": 405
},
{
"fromItemId": 306,
"toItemId": 406
},
{
"fromItemId": 307,
"toItemId": 407
},
{
"fromItemId": 308,
"toItemId": 408
},
{
"fromItemId": 309,
"toItemId": 409
},
{
"fromItemId": 401,
"toItemId": 501
},
{
"fromItemId": 402,
"toItemId": 502
},
{
"fromItemId": 403,
"toItemId": 503
},
{
"fromItemId": 404,
"toItemId": 504
},
{
"fromItemId": 405,
"toItemId": 505
},
{
"fromItemId": 406,
"toItemId": 506
},
{
"fromItemId": 407,
"toItemId": 507
},
{
"fromItemId": 408,
"toItemId": 508
},
{
"fromItemId": 409,
"toItemId": 509
},
{
"fromItemId": 710,
"toItemId": 301
},
{
"fromItemId": 711,
"toItemId": 302
},
{
"fromItemId": 712,
"toItemId": 303
},
{
"fromItemId": 713,
"toItemId": 304
},
{
"fromItemId": 714,
"toItemId": 305
},
{
"fromItemId": 715,
"toItemId": 306
},
{
"fromItemId": 716,
"toItemId": 307
},
{
"fromItemId": 717,
"toItemId": 308
},
{
"fromItemId": 718,
"toItemId": 309
}
],
"craftingRecipes": [
{
"id": 1002,
@@ -2852,6 +3074,7 @@
"partySize": 6,
"completionItemLevel": null,
"experienceReward": 125,
"imageUrl": "/boss-placeholder.svg",
"description": "A focused hunt through Bulldrome territory.",
"locationName": "The Ember Wastes",
"completionCount": 0,
@@ -3081,6 +3304,7 @@
"partySize": 6,
"completionItemLevel": null,
"experienceReward": 125,
"imageUrl": "/boss-placeholder.svg",
"description": "A focused hunt through Yian Kut-Ku territory.",
"locationName": "The Ember Wastes",
"completionCount": 0,
@@ -3310,6 +3534,7 @@
"partySize": 6,
"completionItemLevel": null,
"experienceReward": 125,
"imageUrl": "/boss-placeholder.svg",
"description": "A focused hunt through Rathian territory.",
"locationName": "The Ember Wastes",
"completionCount": 0,
@@ -3539,6 +3764,7 @@
"partySize": 6,
"completionItemLevel": null,
"experienceReward": 205,
"imageUrl": "/boss-placeholder.svg",
"description": "A focused hunt through Tigrex territory.",
"locationName": "The Monster Frontier",
"completionCount": 0,
@@ -3737,6 +3963,7 @@
"partySize": 6,
"completionItemLevel": null,
"experienceReward": 205,
"imageUrl": "/boss-placeholder.svg",
"description": "A focused hunt through Rathalos territory.",
"locationName": "The Monster Frontier",
"completionCount": 0,
@@ -3935,6 +4162,7 @@
"partySize": 6,
"completionItemLevel": null,
"experienceReward": 205,
"imageUrl": "/boss-placeholder.svg",
"description": "A focused hunt through Gypceros territory.",
"locationName": "The Monster Frontier",
"completionCount": 0,
@@ -4133,6 +4361,7 @@
"partySize": 6,
"completionItemLevel": null,
"experienceReward": 245,
"imageUrl": "/boss-placeholder.svg",
"description": "A focused hunt through Nargacuga territory.",
"locationName": "The Monster Frontier",
"completionCount": 0,
@@ -4300,6 +4529,7 @@
"partySize": 6,
"completionItemLevel": null,
"experienceReward": 245,
"imageUrl": "/boss-placeholder.svg",
"description": "A focused hunt through Azuros territory.",
"locationName": "The Monster Frontier",
"completionCount": 0,
@@ -4467,6 +4697,7 @@
"partySize": 6,
"completionItemLevel": null,
"experienceReward": 245,
"imageUrl": "/boss-placeholder.svg",
"description": "A focused hunt through Diablos territory.",
"locationName": "The Monster Frontier",
"completionCount": 0,
@@ -4634,6 +4865,7 @@
"partySize": 6,
"completionItemLevel": null,
"experienceReward": 285,
"imageUrl": "/boss-placeholder.svg",
"description": "A focused hunt through Barroth territory.",
"locationName": "The Monster Frontier",
"completionCount": 0,
@@ -4770,6 +5002,7 @@
"partySize": 6,
"completionItemLevel": null,
"experienceReward": 285,
"imageUrl": "/boss-placeholder.svg",
"description": "A focused hunt through Tobi Kadachi territory.",
"locationName": "The Monster Frontier",
"completionCount": 0,
@@ -4906,6 +5139,7 @@
"partySize": 6,
"completionItemLevel": null,
"experienceReward": 285,
"imageUrl": "/boss-placeholder.svg",
"description": "A focused hunt through Monoblos territory.",
"locationName": "The Monster Frontier",
"completionCount": 0,
@@ -5042,6 +5276,7 @@
"partySize": 6,
"completionItemLevel": null,
"experienceReward": 325,
"imageUrl": "/boss-placeholder.svg",
"description": "A focused hunt through Anjanath territory.",
"locationName": "The Monster Frontier",
"completionCount": 0,
@@ -5147,6 +5382,7 @@
"partySize": 6,
"completionItemLevel": null,
"experienceReward": 325,
"imageUrl": "/boss-placeholder.svg",
"description": "A focused hunt through Bazelgeuse territory.",
"locationName": "The Monster Frontier",
"completionCount": 0,
@@ -5252,6 +5488,7 @@
"partySize": 6,
"completionItemLevel": null,
"experienceReward": 325,
"imageUrl": "/boss-placeholder.svg",
"description": "A focused hunt through Odogaron territory.",
"locationName": "The Monster Frontier",
"completionCount": 0,
@@ -5357,6 +5594,7 @@
"partySize": 18,
"completionItemLevel": null,
"experienceReward": 275,
"imageUrl": "/boss-placeholder.svg",
"description": "A raid-scale hunt against Apex Tigrex.",
"locationName": "The Monster Frontier",
"completionCount": 0,
@@ -5462,6 +5700,7 @@
"partySize": 18,
"completionItemLevel": null,
"experienceReward": 275,
"imageUrl": "/boss-placeholder.svg",
"description": "A raid-scale hunt against Apex Rathalos.",
"locationName": "The Monster Frontier",
"completionCount": 0,
@@ -5567,6 +5806,7 @@
"partySize": 18,
"completionItemLevel": null,
"experienceReward": 275,
"imageUrl": "/boss-placeholder.svg",
"description": "A raid-scale hunt against Apex Gypceros.",
"locationName": "The Monster Frontier",
"completionCount": 0,
@@ -5672,6 +5912,7 @@
"partySize": 18,
"completionItemLevel": null,
"experienceReward": 325,
"imageUrl": "/boss-placeholder.svg",
"description": "A raid-scale hunt against Apex Nargacuga.",
"locationName": "The Monster Frontier",
"completionCount": 0,
@@ -5777,6 +6018,7 @@
"partySize": 18,
"completionItemLevel": null,
"experienceReward": 325,
"imageUrl": "/boss-placeholder.svg",
"description": "A raid-scale hunt against Apex Azuros.",
"locationName": "The Monster Frontier",
"completionCount": 0,
@@ -5882,6 +6124,7 @@
"partySize": 18,
"completionItemLevel": null,
"experienceReward": 325,
"imageUrl": "/boss-placeholder.svg",
"description": "A raid-scale hunt against Apex Diablos.",
"locationName": "The Monster Frontier",
"completionCount": 0,
@@ -5987,6 +6230,7 @@
"partySize": 18,
"completionItemLevel": null,
"experienceReward": 375,
"imageUrl": "/boss-placeholder.svg",
"description": "A raid-scale hunt against Apex Barroth.",
"locationName": "The Monster Frontier",
"completionCount": 0,
@@ -6092,6 +6336,7 @@
"partySize": 18,
"completionItemLevel": null,
"experienceReward": 375,
"imageUrl": "/boss-placeholder.svg",
"description": "A raid-scale hunt against Apex Tobi Kadachi.",
"locationName": "The Monster Frontier",
"completionCount": 0,
@@ -6197,6 +6442,7 @@
"partySize": 18,
"completionItemLevel": null,
"experienceReward": 375,
"imageUrl": "/boss-placeholder.svg",
"description": "A raid-scale hunt against Apex Monoblos.",
"locationName": "The Monster Frontier",
"completionCount": 0,
@@ -6302,6 +6548,7 @@
"partySize": 18,
"completionItemLevel": null,
"experienceReward": 425,
"imageUrl": "/boss-placeholder.svg",
"description": "A raid-scale hunt against Apex Anjanath.",
"locationName": "The Monster Frontier",
"completionCount": 0,
@@ -6407,6 +6654,7 @@
"partySize": 18,
"completionItemLevel": null,
"experienceReward": 425,
"imageUrl": "/boss-placeholder.svg",
"description": "A raid-scale hunt against Apex Bazelgeuse.",
"locationName": "The Monster Frontier",
"completionCount": 0,
@@ -6512,6 +6760,7 @@
"partySize": 18,
"completionItemLevel": null,
"experienceReward": 425,
"imageUrl": "/boss-placeholder.svg",
"description": "A raid-scale hunt against Apex Odogaron.",
"locationName": "The Monster Frontier",
"completionCount": 0,
+7
View File
@@ -134,6 +134,11 @@ export type CraftingRecipe = {
canCraft: boolean
}
export type GearUpgradePath = {
fromItemId: number
toItemId: number
}
export type LootRollItem = Omit<Item, 'quantity' | 'equipped'> & {
quantity: number
duplicate: boolean
@@ -163,6 +168,7 @@ export type Dungeon = {
partySize: number
completionItemLevel: number | null
experienceReward: number
imageUrl: string
description: string
locationName: string
difficulties: Difficulty[]
@@ -224,6 +230,7 @@ export type CharacterProfile = {
}
setBonuses: SetBonus[]
craftingRecipes: CraftingRecipe[]
gearUpgradePaths: GearUpgradePath[]
dungeons: Dungeon[]
}