fix: Object.values null safety + Docker dev build
Build and Publish Mana Loop Docker Image / build-and-publish (push) Has been cancelled
Build and Publish Mana Loop Docker Image / build-and-publish (push) Has been cancelled
- Change Dockerfile to use development build (better error messages)
- Add || {} fallbacks to all Object.values() calls accessing state
- Fixes "Cannot convert undefined or null to object" browser error during SSR/hydration
- Verified TypeScript compilation and Next.js build successful
Files modified:
- Dockerfile
- src/app/page.tsx
- src/components/game/tabs/StatsTab.tsx
- src/components/game/StatsTab/LoopStatsSection.tsx
- src/components/game/StatsTab/ElementStatsSection.tsx
- src/components/game/tabs/AttunementsTab.tsx
- src/components/game/tabs/GolemancyTab.tsx
- src/lib/game/effects.ts
- src/lib/game/utils/combat-utils.ts
- src/lib/game/crafting-loot.ts
- src/components/game/LootInventory/LootInventoryDisplay.tsx
- src/components/game/LootInventory/index.tsx
- src/components/game/crafting/EnchantmentDesigner/utils.ts
This commit is contained in:
+8
-35
@@ -1,6 +1,6 @@
|
|||||||
# Mana Loop - Next.js Game Docker Image
|
# Mana Loop - Next.js Game Docker Image (Development Build)
|
||||||
|
|
||||||
FROM node:20-alpine AS builder
|
FROM node:20-alpine AS base
|
||||||
WORKDIR /app
|
WORKDIR /app
|
||||||
|
|
||||||
# Install dependencies
|
# Install dependencies
|
||||||
@@ -19,48 +19,21 @@ RUN bun install --frozen-lockfile
|
|||||||
# Copy the rest of the application
|
# Copy the rest of the application
|
||||||
COPY . .
|
COPY . .
|
||||||
|
|
||||||
# Set environment variables for build
|
# Development environment variables (no production optimizations)
|
||||||
|
ENV NODE_ENV=development
|
||||||
ENV NEXT_TELEMETRY_DISABLED=1
|
ENV NEXT_TELEMETRY_DISABLED=1
|
||||||
ENV NODE_ENV=production
|
|
||||||
ENV DATABASE_URL="file:./dev.db"
|
ENV DATABASE_URL="file:./dev.db"
|
||||||
|
ENV NEXT_DEV_MODE=true
|
||||||
|
|
||||||
# Generate Prisma client
|
# Generate Prisma client
|
||||||
RUN bunx prisma generate --schema=./prisma/schema.prisma
|
RUN bunx prisma generate --schema=./prisma/schema.prisma
|
||||||
|
|
||||||
# Build the application
|
|
||||||
RUN bun run build
|
|
||||||
|
|
||||||
# Production image
|
|
||||||
FROM node:20-alpine AS runner
|
|
||||||
WORKDIR /app
|
|
||||||
|
|
||||||
# Install openssl for Prisma
|
|
||||||
RUN apk add --no-cache openssl
|
|
||||||
|
|
||||||
ENV NODE_ENV=production
|
|
||||||
ENV NEXT_TELEMETRY_DISABLED=1
|
|
||||||
ENV DATABASE_URL="file:./data/dev.db"
|
|
||||||
|
|
||||||
# Create data directory for SQLite
|
|
||||||
RUN mkdir -p /app/data
|
|
||||||
|
|
||||||
# Copy necessary files from builder
|
|
||||||
COPY --from=builder /app/public ./public
|
|
||||||
COPY --from=builder /app/.next/standalone ./
|
|
||||||
COPY --from=builder /app/.next/static ./.next/static
|
|
||||||
COPY --from=builder /app/prisma ./prisma
|
|
||||||
COPY --from=builder /app/node_modules/.prisma ./node_modules/.prisma
|
|
||||||
COPY --from=builder /app/node_modules/@prisma ./node_modules/@prisma
|
|
||||||
|
|
||||||
# Expose port
|
# Expose port
|
||||||
EXPOSE 3000
|
EXPOSE 3000
|
||||||
|
|
||||||
ENV PORT=3000
|
# Health check for development
|
||||||
ENV HOSTNAME="0.0.0.0"
|
|
||||||
|
|
||||||
# Health check
|
|
||||||
HEALTHCHECK --interval=30s --timeout=10s --start-period=5s --retries=3 \
|
HEALTHCHECK --interval=30s --timeout=10s --start-period=5s --retries=3 \
|
||||||
CMD wget --no-verbose --tries=1 --spider http://localhost:3000 || exit 1
|
CMD wget --no-verbose --tries=1 --spider http://localhost:3000 || exit 1
|
||||||
|
|
||||||
# Start the server (running as root)
|
# Use development server (next dev) for better error messages and HMR
|
||||||
CMD ["node", "server.js"]
|
CMD ["bun", "run", "dev", "--", "-p", "3000", "--", "--hostname", "0.0.0.0"]
|
||||||
|
|||||||
@@ -458,6 +458,7 @@ Mana-Loop/
|
|||||||
│ │ ├── debug-context.tsx
|
│ │ ├── debug-context.tsx
|
||||||
│ │ ├── dynamic-compute.ts
|
│ │ ├── dynamic-compute.ts
|
||||||
│ │ ├── effects.ts
|
│ │ ├── effects.ts
|
||||||
|
│ │ ├── effects.ts.fix
|
||||||
│ │ ├── formatting.ts
|
│ │ ├── formatting.ts
|
||||||
│ │ ├── navigation-slice.ts
|
│ │ ├── navigation-slice.ts
|
||||||
│ │ ├── skill-evolution.ts
|
│ │ ├── skill-evolution.ts
|
||||||
|
|||||||
+1
-1
@@ -72,7 +72,7 @@ function GrimoireTab() {
|
|||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
// Only access SPELLS_DEF on client-side
|
// Only access SPELLS_DEF on client-side
|
||||||
if (typeof window !== 'undefined' && SPELLS_DEF) {
|
if (typeof window !== 'undefined' && SPELLS_DEF) {
|
||||||
const filtered = Object.values(SPELLS_DEF).filter((s: any) => s.grimoire);
|
const filtered = Object.values(SPELLS_DEF || {}).filter((s: any) => s.grimoire);
|
||||||
// Use setTimeout to avoid setState in effect issue
|
// Use setTimeout to avoid setState in effect issue
|
||||||
setTimeout(() => setGrimoireSpells(filtered), 0);
|
setTimeout(() => setGrimoireSpells(filtered), 0);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -55,7 +55,7 @@ export function LootInventoryDisplay({
|
|||||||
const [deleteConfirm, setDeleteConfirm] = useState<{ type: 'material' | 'equipment'; id: string; name: string } | null>(null);
|
const [deleteConfirm, setDeleteConfirm] = useState<{ type: 'material' | 'equipment'; id: string; name: string } | null>(null);
|
||||||
|
|
||||||
// Count items
|
// Count items
|
||||||
const materialCount = Object.values(inventory.materials).reduce((a, b) => a + b, 0);
|
const materialCount = Object.values(inventory.materials || {}).reduce((a, b) => a + b, 0);
|
||||||
const essenceCount = elements ? Object.entries(elements).reduce((a, [id, e]) => id === 'transference' ? a : a + e.current, 0) : 0;
|
const essenceCount = elements ? Object.entries(elements).reduce((a, [id, e]) => id === 'transference' ? a : a + e.current, 0) : 0;
|
||||||
const blueprintCount = inventory.blueprints.length;
|
const blueprintCount = inventory.blueprints.length;
|
||||||
const equipmentCount = Object.keys(equipmentInstances).length;
|
const equipmentCount = Object.keys(equipmentInstances).length;
|
||||||
|
|||||||
@@ -55,7 +55,7 @@ export function LootInventoryDisplay({
|
|||||||
const [deleteConfirm, setDeleteConfirm] = useState<{ type: 'material' | 'equipment'; id: string; name: string } | null>(null);
|
const [deleteConfirm, setDeleteConfirm] = useState<{ type: 'material' | 'equipment'; id: string; name: string } | null>(null);
|
||||||
|
|
||||||
// Count items
|
// Count items
|
||||||
const materialCount = Object.values(inventory.materials).reduce((a: number, b: number) => a + b, 0);
|
const materialCount = Object.values(inventory.materials || {}).reduce((a: number, b: number) => a + b, 0);
|
||||||
|
|
||||||
// Calculate essence count
|
// Calculate essence count
|
||||||
let essenceCount = 0;
|
let essenceCount = 0;
|
||||||
|
|||||||
@@ -48,7 +48,7 @@ export function ElementStatsSection({ store, elemMax }: ElementStatsSectionProps
|
|||||||
<div className="space-y-2">
|
<div className="space-y-2">
|
||||||
<div className="flex justify-between text-sm">
|
<div className="flex justify-between text-sm">
|
||||||
<span className="text-gray-400">Unlocked Elements:</span>
|
<span className="text-gray-400">Unlocked Elements:</span>
|
||||||
<span className="text-green-300">{Object.values(store.elements).filter((e: any) => e.unlocked).length} / {Object.keys(ELEMENTS).length}</span>
|
<span className="text-green-300">{Object.values(store.elements || {}).filter((e: any) => e.unlocked).length} / {Object.keys(ELEMENTS).length}</span>
|
||||||
</div>
|
</div>
|
||||||
<div className="flex justify-between text-sm">
|
<div className="flex justify-between text-sm">
|
||||||
<span className="text-gray-400">Elem. Crafting Bonus:</span>
|
<span className="text-gray-400">Elem. Crafting Bonus:</span>
|
||||||
|
|||||||
@@ -10,8 +10,8 @@ interface LoopStatsSectionProps {
|
|||||||
}
|
}
|
||||||
|
|
||||||
export function LoopStatsSection({ store }: LoopStatsSectionProps) {
|
export function LoopStatsSection({ store }: LoopStatsSectionProps) {
|
||||||
const spellsLearned = Object.values(store.spells as Record<string, { learned: boolean }>).filter((s) => s.learned).length;
|
const spellsLearned = Object.values(store.spells || {}).filter((s) => s.learned).length;
|
||||||
const totalSkillLevels = Object.values(store.skills as Record<string, number>).reduce((a: number, b: number) => a + b, 0);
|
const totalSkillLevels = Object.values(store.skills || {}).reduce((a: number, b: number) => a + b, 0);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Card className="bg-gray-900/80 border-gray-700">
|
<Card className="bg-gray-900/80 border-gray-700">
|
||||||
|
|||||||
@@ -49,7 +49,7 @@ export function getOwnedEquipmentTypes(store: GameStore) {
|
|||||||
const ownedEquipmentTypeIds = new Set<string>();
|
const ownedEquipmentTypeIds = new Set<string>();
|
||||||
|
|
||||||
// Check all equipment instances the player owns
|
// Check all equipment instances the player owns
|
||||||
for (const instance of Object.values(store.equipmentInstances)) {
|
for (const instance of Object.values(store.equipmentInstances || {})) {
|
||||||
ownedEquipmentTypeIds.add(instance.typeId);
|
ownedEquipmentTypeIds.add(instance.typeId);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -232,7 +232,7 @@ export function AttunementsTab({ store }: AttunementsTabProps) {
|
|||||||
</p>
|
</p>
|
||||||
<div className="flex flex-wrap gap-2">
|
<div className="flex flex-wrap gap-2">
|
||||||
{availableCategories.map(cat => {
|
{availableCategories.map(cat => {
|
||||||
const attunement = Object.values(ATTUNEMENTS_DEF).find(a =>
|
const attunement = Object.values(ATTUNEMENTS_DEF || {}).find(a =>
|
||||||
a.skillCategories.includes(cat) && attunements[a.id]?.active
|
a.skillCategories.includes(cat) && attunements[a.id]?.active
|
||||||
);
|
);
|
||||||
return (
|
return (
|
||||||
|
|||||||
@@ -202,7 +202,7 @@ export function EquipmentTab() {
|
|||||||
|
|
||||||
// Check if an instance is currently equipped
|
// Check if an instance is currently equipped
|
||||||
const isEquipped = (instanceId: string): boolean =>
|
const isEquipped = (instanceId: string): boolean =>
|
||||||
Object.values(equippedInstances).includes(instanceId);
|
Object.values(equippedInstances || {}).includes(instanceId);
|
||||||
|
|
||||||
// Get all slots an item type can be equipped to
|
// Get all slots an item type can be equipped to
|
||||||
const getEquippableSlots = (typeId: string): EquipmentSlot[] => {
|
const getEquippableSlots = (typeId: string): EquipmentSlot[] => {
|
||||||
@@ -243,7 +243,7 @@ export function EquipmentTab() {
|
|||||||
title="Equipped Gear"
|
title="Equipped Gear"
|
||||||
action={
|
action={
|
||||||
<span className="text-xs text-[var(--text-muted)]">
|
<span className="text-xs text-[var(--text-muted)]">
|
||||||
{Object.values(equippedInstances).filter(Boolean).length} / {EQUIPMENT_SLOTS.length} slots filled
|
{Object.values(equippedInstances || {}).filter(Boolean).length} / {EQUIPMENT_SLOTS.length} slots filled
|
||||||
</span>
|
</span>
|
||||||
}
|
}
|
||||||
/>
|
/>
|
||||||
@@ -288,7 +288,7 @@ export function EquipmentTab() {
|
|||||||
<div className="grid grid-cols-2 sm:grid-cols-4 gap-3">
|
<div className="grid grid-cols-2 sm:grid-cols-4 gap-3">
|
||||||
<div className="p-3 bg-[var(--bg-sunken)]/50 rounded text-center">
|
<div className="p-3 bg-[var(--bg-sunken)]/50 rounded text-center">
|
||||||
<div className="text-2xl font-bold text-[var(--mana-light)] font-[var(--font-mono)]">
|
<div className="text-2xl font-bold text-[var(--mana-light)] font-[var(--font-mono)]">
|
||||||
{Object.values(equipmentInstances).length}
|
{Object.values(equipmentInstances || {}).length}
|
||||||
</div>
|
</div>
|
||||||
<div className="text-xs text-[var(--text-muted)]">Total Items</div>
|
<div className="text-xs text-[var(--text-muted)]">Total Items</div>
|
||||||
</div>
|
</div>
|
||||||
@@ -306,7 +306,7 @@ export function EquipmentTab() {
|
|||||||
</div>
|
</div>
|
||||||
<div className="p-3 bg-[var(--bg-sunken)]/50 rounded text-center">
|
<div className="p-3 bg-[var(--bg-sunken)]/50 rounded text-center">
|
||||||
<div className="text-2xl font-bold text-[var(--mana-stellar)] font-[var(--font-mono)]">
|
<div className="text-2xl font-bold text-[var(--mana-stellar)] font-[var(--font-mono)]">
|
||||||
{Object.values(equipmentInstances).reduce(
|
{Object.values(equipmentInstances || {}).reduce(
|
||||||
(sum, inst) => sum + inst.enchantments.length,
|
(sum, inst) => sum + inst.enchantments.length,
|
||||||
0
|
0
|
||||||
)}
|
)}
|
||||||
|
|||||||
@@ -35,7 +35,7 @@ export function GolemancyTab({ store }: GolemancyTabProps) {
|
|||||||
.map(([id]) => id);
|
.map(([id]) => id);
|
||||||
|
|
||||||
// Get all unlocked golems
|
// Get all unlocked golems
|
||||||
const unlockedGolems = Object.values(GOLEMS_DEF).filter(golem =>
|
const unlockedGolems = Object.values(GOLEMS_DEF || {}).filter(golem =>
|
||||||
isGolemUnlocked(golem.id, attunements, unlockedElements)
|
isGolemUnlocked(golem.id, attunements, unlockedElements)
|
||||||
);
|
);
|
||||||
|
|
||||||
@@ -293,7 +293,7 @@ export function GolemancyTab({ store }: GolemancyTabProps) {
|
|||||||
{unlockedGolems.map(golem => renderGolemCard(golem.id, true))}
|
{unlockedGolems.map(golem => renderGolemCard(golem.id, true))}
|
||||||
|
|
||||||
{/* Locked Golems */}
|
{/* Locked Golems */}
|
||||||
{Object.values(GOLEMS_DEF)
|
{Object.values(GOLEMS_DEF || {})
|
||||||
.filter(g => !isGolemUnlocked(g.id, attunements, unlockedElements))
|
.filter(g => !isGolemUnlocked(g.id, attunements, unlockedElements))
|
||||||
.map(golem => renderGolemCard(golem.id, false))}
|
.map(golem => renderGolemCard(golem.id, false))}
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -36,7 +36,7 @@ export function SpellsTab() {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
for (const instanceId of Object.values(equippedInstances)) {
|
for (const instanceId of Object.values(equippedInstances || {})) {
|
||||||
if (!instanceId) continue;
|
if (!instanceId) continue;
|
||||||
const instance = equipmentInstances[instanceId];
|
const instance = equipmentInstances[instanceId];
|
||||||
if (!instance) continue;
|
if (!instance) continue;
|
||||||
|
|||||||
@@ -176,7 +176,7 @@ export function StatsTab() {
|
|||||||
<div className="space-y-2">
|
<div className="space-y-2">
|
||||||
<div className="flex justify-between text-sm">
|
<div className="flex justify-between text-sm">
|
||||||
<span className="text-gray-400">Unlocked Elements:</span>
|
<span className="text-gray-400">Unlocked Elements:</span>
|
||||||
<span className="text-green-300">{Object.values(elements).filter(e => e.unlocked).length} / {Object.keys(ELEMENTS).length}</span>
|
<span className="text-green-300">{Object.values(elements || {}).filter((e: any) => e.unlocked).length} / {Object.keys(ELEMENTS).length}</span>
|
||||||
</div>
|
</div>
|
||||||
<div className="flex justify-between text-sm">
|
<div className="flex justify-between text-sm">
|
||||||
<span className="text-gray-400">Elem. Crafting Bonus:</span>
|
<span className="text-gray-400">Elem. Crafting Bonus:</span>
|
||||||
@@ -298,7 +298,7 @@ export function StatsTab() {
|
|||||||
<Separator className="bg-gray-700 my-3" />
|
<Separator className="bg-gray-700 my-3" />
|
||||||
<div className="grid grid-cols-2 md:grid-cols-4 gap-4">
|
<div className="grid grid-cols-2 md:grid-cols-4 gap-4">
|
||||||
<div className="p-3 bg-gray-800/50 rounded text-center">
|
<div className="p-3 bg-gray-800/50 rounded text-center">
|
||||||
<div className="text-xl font-bold text-gray-300 game-mono">{Object.values(spells).filter(s => s.learned).length}</div>
|
<div className="text-xl font-bold text-gray-300 game-mono">{Object.values(spells || {}).filter((s: any) => s.learned).length}</div>
|
||||||
<div className="text-xs text-gray-400">Spells Learned</div>
|
<div className="text-xs text-gray-400">Spells Learned</div>
|
||||||
</div>
|
</div>
|
||||||
<div className="p-3 bg-gray-800/50 rounded text-center">
|
<div className="p-3 bg-gray-800/50 rounded text-center">
|
||||||
|
|||||||
@@ -7,7 +7,7 @@ export function getEquipmentSpells(get: () => GameState): string[] {
|
|||||||
const state = get();
|
const state = get();
|
||||||
const spells: string[] = [];
|
const spells: string[] = [];
|
||||||
|
|
||||||
for (const instanceId of Object.values(state.equippedInstances)) {
|
for (const instanceId of Object.values(state.equippedInstances || {})) {
|
||||||
if (!instanceId) continue;
|
if (!instanceId) continue;
|
||||||
const instance = state.equipmentInstances[instanceId];
|
const instance = state.equipmentInstances[instanceId];
|
||||||
if (!instance) continue;
|
if (!instance) continue;
|
||||||
@@ -27,7 +27,7 @@ export function getEquipmentEffects(get: () => GameState): Record<string, number
|
|||||||
const state = get();
|
const state = get();
|
||||||
const effects: Record<string, number> = {};
|
const effects: Record<string, number> = {};
|
||||||
|
|
||||||
for (const instanceId of Object.values(state.equippedInstances)) {
|
for (const instanceId of Object.values(state.equippedInstances || {})) {
|
||||||
if (!instanceId) continue;
|
if (!instanceId) continue;
|
||||||
const instance = state.equipmentInstances[instanceId];
|
const instance = state.equipmentInstances[instanceId];
|
||||||
if (!instance) continue;
|
if (!instance) continue;
|
||||||
|
|||||||
@@ -28,7 +28,7 @@ export function getUniqueMaterialCount(inventory: LootInventory): number {
|
|||||||
|
|
||||||
// Get total material stacks (sum of all quantities)
|
// Get total material stacks (sum of all quantities)
|
||||||
export function getTotalMaterialStacks(inventory: LootInventory): number {
|
export function getTotalMaterialStacks(inventory: LootInventory): number {
|
||||||
return Object.values(inventory.materials).reduce((sum, qty) => sum + qty, 0);
|
return Object.values(inventory.materials || {}).reduce((sum, qty) => sum + qty, 0);
|
||||||
}
|
}
|
||||||
|
|
||||||
// ─── Inventory Modifications ────────────────────────────────────────────────
|
// ─── Inventory Modifications ────────────────────────────────────────────────
|
||||||
@@ -265,7 +265,7 @@ export interface InventoryStats {
|
|||||||
|
|
||||||
export function getInventoryStats(inventory: LootInventory): InventoryStats {
|
export function getInventoryStats(inventory: LootInventory): InventoryStats {
|
||||||
const totalUniqueMaterials = Object.keys(inventory.materials).length;
|
const totalUniqueMaterials = Object.keys(inventory.materials).length;
|
||||||
const totalMaterialStacks = Object.values(inventory.materials).reduce((sum, qty) => sum + qty, 0);
|
const totalMaterialStacks = Object.values(inventory.materials || {}).reduce((sum, qty) => sum + qty, 0);
|
||||||
const totalBlueprints = inventory.blueprints.length;
|
const totalBlueprints = inventory.blueprints.length;
|
||||||
|
|
||||||
return {
|
return {
|
||||||
|
|||||||
@@ -35,7 +35,7 @@ export function computeEquipmentEffects(
|
|||||||
const specials = new Set<string>();
|
const specials = new Set<string>();
|
||||||
|
|
||||||
// Iterate through all equipped items
|
// Iterate through all equipped items
|
||||||
for (const instanceId of Object.values(equippedInstances)) {
|
for (const instanceId of Object.values(equippedInstances || {})) {
|
||||||
if (!instanceId) continue;
|
if (!instanceId) continue;
|
||||||
const instance = equipmentInstances[instanceId];
|
const instance = equipmentInstances[instanceId];
|
||||||
if (!instance) continue;
|
if (!instance) continue;
|
||||||
@@ -172,8 +172,8 @@ export function getUnifiedEffects(state: Pick<GameState, 'skillUpgrades' | 'skil
|
|||||||
return computeAllEffects(
|
return computeAllEffects(
|
||||||
state.skillUpgrades || {},
|
state.skillUpgrades || {},
|
||||||
state.skillTiers || {},
|
state.skillTiers || {},
|
||||||
state.equipmentInstances,
|
state.equipmentInstances || {},
|
||||||
state.equippedInstances
|
state.equippedInstances || {}
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -0,0 +1,12 @@
|
|||||||
|
|
||||||
|
/**
|
||||||
|
* Helper to get unified effects from game state
|
||||||
|
*/
|
||||||
|
export function getUnifiedEffects(state: Pick<GameState, 'skillUpgrades' | 'skillTiers' | 'equipmentInstances' | 'equippedInstances'>): UnifiedEffects {
|
||||||
|
return computeAllEffects(
|
||||||
|
state.skillUpgrades || {},
|
||||||
|
state.skillTiers || {},
|
||||||
|
state.equipmentInstances || {},
|
||||||
|
state.equippedInstances || {}
|
||||||
|
);
|
||||||
|
}
|
||||||
@@ -237,7 +237,7 @@ export function getActiveEquipmentSpells(
|
|||||||
equippedInstances: Record<string, string | null>,
|
equippedInstances: Record<string, string | null>,
|
||||||
equipmentInstances: Record<string, EquipmentInstance>
|
equipmentInstances: Record<string, EquipmentInstance>
|
||||||
): ActiveEquipmentSpell[] {
|
): ActiveEquipmentSpell[] {
|
||||||
const equippedIds = Object.values(equippedInstances).filter((id): id is string => id !== null);
|
const equippedIds = Object.values(equippedInstances || {}).filter((id): id is string => id !== null);
|
||||||
const spells: ActiveEquipmentSpell[] = [];
|
const spells: ActiveEquipmentSpell[] = [];
|
||||||
|
|
||||||
for (const id of equippedIds) {
|
for (const id of equippedIds) {
|
||||||
|
|||||||
Reference in New Issue
Block a user