Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Che bato/custom decks #25

Merged
merged 10 commits into from
Dec 11, 2024
Merged
Show file tree
Hide file tree
Changes from 8 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion src/app/GameBoard/page.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@
"use client";
import React, { useState, useRef, useEffect, useContext } from "react";
import { Box, Grid2 as Grid } from "@mui/material";
import { s3ImageURL } from "../_utils/s3Assets";
import { s3ImageURL } from "../_utils/s3Utils";
import ChatDrawer from "../_components/Gameboard/_subcomponents/Overlays/ChatDrawer/ChatDrawer";
import OpponentCardTray from "../_components/Gameboard/OpponentCardTray/OpponentCardTray";
import Board from "../_components/Gameboard/Board/Board";
Expand Down
2 changes: 1 addition & 1 deletion src/app/_components/_sharedcomponents/Banner/Banner.tsx
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import React from "react";
import { Box, Typography } from "@mui/material";
import { s3ImageURL } from "@/app/_utils/s3Assets";
import { s3ImageURL } from "@/app/_utils/s3Utils";

const KarabastBanner: React.FC = () => {

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ import {
import Image from "next/image";
import { GameCardProps, CardData } from "@/app/_components/_sharedcomponents/Cards/CardTypes";
import { useGame } from "@/app/_contexts/Game.context";
import { s3CardImageURL } from "@/app/_utils/s3Assets";
import { s3CardImageURL } from "@/app/_utils/s3Utils";

const GameCard: React.FC<GameCardProps> = ({
card,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ import {
import { LeaderBaseCardProps } from "../CardTypes";
import { CardData } from "../CardTypes";
import { useGame } from "@/app/_contexts/Game.context";
import { s3CardImageURL } from "@/app/_utils/s3Assets";
import { s3CardImageURL } from "@/app/_utils/s3Utils";


const LeaderBaseCard: React.FC<LeaderBaseCardProps> = ({
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@ import {
} from "@mui/material";
import StyledTextField from "../_styledcomponents/StyledTextField/StyledTextField";
import { usePathname, useRouter } from "next/navigation";
import {updateIdsWithMapping, mapIdToInternalName, transformDeckWithCardData} from "@/app/_utils/s3Utils";

interface CreateGameFormProps {
format?: string | null;
Expand Down Expand Up @@ -60,7 +61,7 @@ const CreateGameForm: React.FC<CreateGameFormProps> = ({
useState<string>("Vader Green Ramp");
const [deckLink, setDeckLink] = useState<string>("");
const [saveDeck, setSaveDeck] = useState<boolean>(false);
const [deckData, setDeckData] = useState<DeckData | null>(null);
//let [deckData, setDeckData] = useState<DeckData | null>(null); Because of Async this won't set in the correct timeframe

// Additional State for Non-Creategame Path
const [gameName, setGameName] = useState<string>("");
Expand All @@ -71,13 +72,33 @@ const CreateGameForm: React.FC<CreateGameFormProps> = ({
const response = await fetch(
`/api/swudbdeck?deckLink=${encodeURIComponent(deckLink)}`
);

if (!response.ok) {
throw new Error(`Failed to fetch deck: ${response.statusText}`);
}

const data: DeckData = await response.json();
setDeckData(data);

// Fetch setToId mapping from the s3bucket endpoint
const setCodeMapping = await fetch("/api/s3bucket?jsonFile=_setCodeMap.json"); // Adjust to your actual endpoint if different
if (!setCodeMapping.ok) {
throw new Error("Failed to fetch card mapping");
}

const jsonData = await setCodeMapping.json();
const deckWithIds = updateIdsWithMapping(data, jsonData);

// Fetch codeToInternalname mapping
const codeInternalnameMapping = await fetch("/api/s3bucket?jsonFile=_cardMap.json"); // Adjust to your actual endpoint if different
if (!codeInternalnameMapping.ok) {
throw new Error("Failed to fetch card mapping");
}

const codeInternalnameJson = await codeInternalnameMapping.json();
const deckWithInternalNames = mapIdToInternalName(codeInternalnameJson, deckWithIds)

// Fetch internalNameToCardMapping
const finalDeckForm = await transformDeckWithCardData(deckWithInternalNames);
return finalDeckForm
} catch (error) {
if (error instanceof Error) {
console.error("Error fetching deck:", error.message);
Expand All @@ -93,13 +114,13 @@ const CreateGameForm: React.FC<CreateGameFormProps> = ({
console.log("Favourite Deck:", favouriteDeck);
console.log("SWUDB Deck Link:", deckLink);
console.log("beginning fetch for deck link");
fetchDeckData(deckLink);
const deckData = await fetchDeckData(deckLink);
console.log("fetch complete, deck data:", deckData);
console.log("Save Deck To Favourites:", saveDeck);

try {
const payload = {
user: favouriteDeck
user: favouriteDeck,
deck: deckData
};
const response = await fetch("http://localhost:9500/api/create-lobby",
{
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ import Grid from "@mui/material/Grid2";
import LeaderBaseCard from "../../Cards/LeaderBaseCard/LeaderBaseCard";
import { LeaderBaseProps } from "../LeaderBaseBoardTypes";
import { useGame } from "@/app/_contexts/Game.context";
import { s3CardImageURL } from "@/app/_utils/s3Assets";
import { s3CardImageURL } from "@/app/_utils/s3Utils";

const LeaderBase: React.FC<LeaderBaseProps> = ({
player,
Expand Down
12 changes: 0 additions & 12 deletions src/app/_utils/s3Assets.ts

This file was deleted.

194 changes: 194 additions & 0 deletions src/app/_utils/s3Utils.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,194 @@
import { CardData } from "../_components/_sharedcomponents/Cards/CardTypes";

// Deck data interfaces for deck info from swudb
interface DeckMetadata {
name: string;
author: string;
}

interface DeckCard {
id: string;
count: number;
}

interface DeckData {
metadata: DeckMetadata;
leader: DeckCard;
secondleader: DeckCard | null;
base: DeckCard;
deck: DeckCard[];
sideboard: DeckCard[];
}

// Deck data interfaces for deck info on the server
interface ServerCardDetails {
title: string;
subtitle: string | null;
cost: number | null;
hp: number | null;
power: number | null;
text: string;
deployBox: string | null;
epicAction: string | null;
unique: boolean;
rules: string;
id: string;
aspects: string[];
traits: string[];
keywords: string[];
types: string[];
setId: {
set: string;
number: number;
};
internalName: string;
}

interface ServerDeckCard {
count: number;
card: ServerCardDetails;
}

interface ServerDeckData {
leader: ServerDeckCard[];
base: ServerDeckCard[];
deckCards: ServerDeckCard[];
sideboard: ServerDeckCard[];
}

// types of mapping
type Mapping = {
[id:string]: string;
}
type idToInternalNameMapping = {
"id": string,
"internalName": string,
"title": string,
"subtitle": string
}

export const s3ImageURL = (path: string) => {
const s3Bucket = "https://karabast-assets.s3.amazonaws.com/";
return s3Bucket + path;
};

export const s3CardImageURL = (card: CardData) => {
if (!card) return "game/epic-action-token.webp";
const cardNumber = card.setId.number.toString().padStart(3, "0") + (card.type === "leaderUnit" ? "-portrait" : "");
return s3ImageURL(`cards/${card.setId.set}/${cardNumber}.webp`);
};


// Helper function to update a card's id
export const updateIdsWithMapping = (data: DeckData, mapping: Mapping): DeckData => {

const updateCard = (card: DeckCard): DeckCard => {
const updatedId = mapping[card.id] || card.id; // Use mapping if available, else keep the original id
return { ...card, id: updatedId };
};

return {
metadata: data.metadata,
leader: updateCard(data.leader),
secondleader: data.secondleader,
base: updateCard(data.base),
deck: data.deck.map(updateCard), // Update all cards in the deck
sideboard: data.sideboard.map(updateCard) // Update all cards in the sideboard
};
};

// Helper function to update a cards id to its internal name
export function mapIdToInternalName(
mapper: idToInternalNameMapping[],
deckData: DeckData
): DeckData {
// Convert the mapper array to a lookup map for faster access
const idToNameMap = new Map<string, string>();
mapper.forEach(entry => {
idToNameMap.set(entry.id, entry.internalName);
});

// Helper function to update a single CardSlot
const updateCardSlot = (slot: DeckCard): DeckCard => {
const { id, count } = slot;
const mappedName = idToNameMap.get(id);
if (mappedName) {
return { id: mappedName, count };
} else {
// If no mapping found, log a warning and retain the original ID
console.warn(`No internalName mapping found for ID: ${id}`);
return { id, count };
}
};

// Update leader, secondleader, base
const updatedLeader = updateCardSlot(deckData.leader);
let updatedSecondLeader = null;
if(deckData.secondleader) {
updatedSecondLeader = updateCardSlot(deckData.secondleader);
}
const updatedBase = updateCardSlot(deckData.base);

// Update deck and sideboard arrays
const updatedDeck = deckData.deck.map(updateCardSlot).filter((slot): slot is DeckCard => slot !== null);
const updatedSideboard = deckData.sideboard.map(updateCardSlot).filter((slot): slot is DeckCard => slot !== null);

return {
metadata: deckData.metadata,
leader: updatedLeader,
secondleader: updatedSecondLeader,
base: updatedBase,
deck: updatedDeck,
sideboard: updatedSideboard
};
}

// helper function to convert DeckWithInternalName to a playable set of cards.
// Fetch card details from the API
const fetchCardData = async (internalName: string): Promise<ServerCardDetails | null> => {
try {
const response = await fetch(`/api/s3bucket?jsonFile=cards/${encodeURIComponent(internalName)}.json`);
const cardDetails = await response.json();
// Do we want to inform the user that some of the cards weren't imported?
if(cardDetails.status){
console.error(`Failed to fetch card data for ${internalName}:`);
return null
}
return cardDetails[0];
} catch {
console.error(`Failed to fetch card data for ${internalName}:`);
return null;
}
};

// Transform Deck with card details
export const transformDeckWithCardData = async (deckData: DeckData): Promise<ServerDeckData | null> => {
try {
const transformCard = async (deckCard: DeckCard): Promise<ServerDeckCard | null> => {
const cardData = await fetchCardData(deckCard.id);
if (!cardData) return null;
return {
count: deckCard.count,
card: cardData, // Add full card details under "card"
};
};

const leader = await transformCard(deckData.leader);
const base = await transformCard(deckData.base);
const deckCards = (
await Promise.all(deckData.deck.map((card) => transformCard(card)))
).filter((card) => card !== null);
const sideboard = (
await Promise.all(deckData.sideboard.map((card) => transformCard(card)))
).filter((card) => card !== null);
return {
leader: leader ? [leader] : [],
base: base ? [base] : [],
deckCards,
sideboard,
};
} catch {
console.error('Error transforming deck with card data');
return null;
}
};
30 changes: 30 additions & 0 deletions src/app/api/s3bucket/route.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
import {NextResponse} from "next/server";

export async function GET(req: Request) {
try {
const { searchParams } = new URL(req.url);
const jsonFile = searchParams.get("jsonFile");
if(!jsonFile) {
console.error("Error: Missing parameter jsonFile");
return NextResponse.json({ error: "Missing parameter jsonFile", status: 400 });
}
const response = await fetch("https://karabast-assets.s3.amazonaws.com/data/"+encodeURIComponent(jsonFile));

if (!response.ok) {
console.error("Failed to fetch card mapping:", response.statusText);
return NextResponse.json({ error: "Failed to fetch card mapping", status: 500 });
}

const data = await response.json();
return NextResponse.json(data);
} catch (error) {
if (error instanceof Error) {
console.error("Internal Server Error:", error.message);
return NextResponse.json({ error: error.message, status: 500 });
}

return NextResponse.json(
{ error: "An unexpected error occurred", status: 500 }
);
}
}
2 changes: 1 addition & 1 deletion src/app/layout.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ import type { Metadata } from "next";
import React from 'react';
import ClientLayout from './ClientLayout';
import { Barlow } from "next/font/google";
import "./_utils/s3Assets";
import "./_utils/s3Utils";

const barlow = Barlow({
subsets: ["latin"],
Expand Down
Loading