mirror of
https://github.com/Pandipipas/scoreko-dev.git
synced 2026-06-06 03:32:06 +00:00
Load character names from downloaded game assets
This commit is contained in:
@@ -1,7 +1,7 @@
|
|||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
import { computed, onMounted, ref, watch, watchEffect, type Ref } from 'vue';
|
import { computed, onMounted, ref, watch, watchEffect, type Ref } from 'vue';
|
||||||
import { getCountryLabel, getCountryOptions } from '../../../shared/countries';
|
import { getCountryLabel, getCountryOptions } from '../../../shared/countries';
|
||||||
import { getCharactersByGame, getDefaultCharactersByGame } from '../../../shared/fighting-characters';
|
import { buildCharactersByGame, getDefaultCharactersByGame } from '../../../shared/fighting-characters';
|
||||||
import type { Schemas } from '../../../types';
|
import type { Schemas } from '../../../types';
|
||||||
import { usePlayersStore } from '../stores/players';
|
import { usePlayersStore } from '../stores/players';
|
||||||
import { useScoreboardStore } from '../stores/scoreboard';
|
import { useScoreboardStore } from '../stores/scoreboard';
|
||||||
@@ -40,8 +40,11 @@ const allFightingGameOptions = computed(() => gameAssetsStore.installedGames.map
|
|||||||
|
|
||||||
const fightingGameOptions = ref(allFightingGameOptions.value);
|
const fightingGameOptions = ref(allFightingGameOptions.value);
|
||||||
|
|
||||||
const characterOptions = computed(() => getCharactersByGame(scoreboardStore.scoreboard.game));
|
const characterOptions = computed(() => buildCharactersByGame(
|
||||||
type CharacterOption = ReturnType<typeof getCharactersByGame>[number];
|
scoreboardStore.scoreboard.game,
|
||||||
|
gameAssetsStore.characterNamesByGame[scoreboardStore.scoreboard.game] ?? [],
|
||||||
|
));
|
||||||
|
type CharacterOption = ReturnType<typeof buildCharactersByGame>[number];
|
||||||
const leftCharacterOptions = ref<CharacterOption[]>([]);
|
const leftCharacterOptions = ref<CharacterOption[]>([]);
|
||||||
const rightCharacterOptions = ref<CharacterOption[]>([]);
|
const rightCharacterOptions = ref<CharacterOption[]>([]);
|
||||||
|
|
||||||
@@ -720,8 +723,14 @@ watch(
|
|||||||
);
|
);
|
||||||
|
|
||||||
watch(
|
watch(
|
||||||
() => scoreboardStore.scoreboard.game,
|
() => [scoreboardStore.scoreboard.game, characterOptions.value] as const,
|
||||||
(newGame, previousGame) => {
|
([newGame, options], previousState) => {
|
||||||
|
const previousGame = previousState?.[0];
|
||||||
|
|
||||||
|
if (newGame && gameAssetsStore.characterNamesByGame[newGame] === undefined) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
if (previousGame) {
|
if (previousGame) {
|
||||||
charactersByGame.value[previousGame] = {
|
charactersByGame.value[previousGame] = {
|
||||||
leftCharacter: scoreboardStore.scoreboard.leftCharacter,
|
leftCharacter: scoreboardStore.scoreboard.leftCharacter,
|
||||||
@@ -729,7 +738,6 @@ watch(
|
|||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
const options = getCharactersByGame(scoreboardStore.scoreboard.game);
|
|
||||||
leftCharacterOptions.value = options;
|
leftCharacterOptions.value = options;
|
||||||
rightCharacterOptions.value = options;
|
rightCharacterOptions.value = options;
|
||||||
const allowed = new Set(options.map((option) => option.value));
|
const allowed = new Set(options.map((option) => option.value));
|
||||||
|
|||||||
@@ -24,6 +24,7 @@ let progressListenerAttached = false;
|
|||||||
|
|
||||||
export const useGameAssetsStore = defineStore('game-assets', () => {
|
export const useGameAssetsStore = defineStore('game-assets', () => {
|
||||||
const installedGames = ref<string[]>([]);
|
const installedGames = ref<string[]>([]);
|
||||||
|
const characterNamesByGame = ref<Record<string, string[]>>({});
|
||||||
const loadingByTitle = ref<Record<string, boolean>>({});
|
const loadingByTitle = ref<Record<string, boolean>>({});
|
||||||
const removingByTitle = ref<Record<string, boolean>>({});
|
const removingByTitle = ref<Record<string, boolean>>({});
|
||||||
const progressByTitle = ref<Record<string, number>>({});
|
const progressByTitle = ref<Record<string, number>>({});
|
||||||
@@ -53,9 +54,16 @@ export const useGameAssetsStore = defineStore('game-assets', () => {
|
|||||||
progressListenerAttached = true;
|
progressListenerAttached = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const refreshCharacterNamesByGame = async () => {
|
||||||
|
const response = await sendNodecgMessage<Record<string, string[]>>('scoreko-assets:listCharactersByGame');
|
||||||
|
characterNamesByGame.value = response;
|
||||||
|
return characterNamesByGame.value;
|
||||||
|
};
|
||||||
|
|
||||||
const refreshInstalledGames = async () => {
|
const refreshInstalledGames = async () => {
|
||||||
const response = await sendNodecgMessage<string[]>('scoreko-assets:listInstalled');
|
const response = await sendNodecgMessage<string[]>('scoreko-assets:listInstalled');
|
||||||
installedGames.value = Array.isArray(response) ? response : [];
|
installedGames.value = Array.isArray(response) ? response : [];
|
||||||
|
await refreshCharacterNamesByGame();
|
||||||
return installedGames.value;
|
return installedGames.value;
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -72,6 +80,7 @@ export const useGameAssetsStore = defineStore('game-assets', () => {
|
|||||||
try {
|
try {
|
||||||
const response = await sendNodecgMessage<{ installedGames: string[] }>('scoreko-assets:downloadGame', { title });
|
const response = await sendNodecgMessage<{ installedGames: string[] }>('scoreko-assets:downloadGame', { title });
|
||||||
installedGames.value = response.installedGames;
|
installedGames.value = response.installedGames;
|
||||||
|
await refreshCharacterNamesByGame();
|
||||||
loadingByTitle.value = {
|
loadingByTitle.value = {
|
||||||
...loadingByTitle.value,
|
...loadingByTitle.value,
|
||||||
[title]: false,
|
[title]: false,
|
||||||
@@ -99,6 +108,7 @@ export const useGameAssetsStore = defineStore('game-assets', () => {
|
|||||||
try {
|
try {
|
||||||
const response = await sendNodecgMessage<{ installedGames: string[] }>('scoreko-assets:removeGame', { title });
|
const response = await sendNodecgMessage<{ installedGames: string[] }>('scoreko-assets:removeGame', { title });
|
||||||
installedGames.value = response.installedGames;
|
installedGames.value = response.installedGames;
|
||||||
|
await refreshCharacterNamesByGame();
|
||||||
return response;
|
return response;
|
||||||
} finally {
|
} finally {
|
||||||
removingByTitle.value = {
|
removingByTitle.value = {
|
||||||
@@ -110,10 +120,12 @@ export const useGameAssetsStore = defineStore('game-assets', () => {
|
|||||||
|
|
||||||
return {
|
return {
|
||||||
installedGames,
|
installedGames,
|
||||||
|
characterNamesByGame,
|
||||||
loadingByTitle,
|
loadingByTitle,
|
||||||
removingByTitle,
|
removingByTitle,
|
||||||
progressByTitle,
|
progressByTitle,
|
||||||
refreshInstalledGames,
|
refreshInstalledGames,
|
||||||
|
refreshCharacterNamesByGame,
|
||||||
downloadGame,
|
downloadGame,
|
||||||
removeGame,
|
removeGame,
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -6,6 +6,7 @@ import { nodecg } from './util/nodecg.js';
|
|||||||
const GITHUB_OWNER = 'Pandipipas';
|
const GITHUB_OWNER = 'Pandipipas';
|
||||||
const GITHUB_REPO = 'scoreko-assets';
|
const GITHUB_REPO = 'scoreko-assets';
|
||||||
const GITHUB_API_BASE = `https://api.github.com/repos/${GITHUB_OWNER}/${GITHUB_REPO}/contents`;
|
const GITHUB_API_BASE = `https://api.github.com/repos/${GITHUB_OWNER}/${GITHUB_REPO}/contents`;
|
||||||
|
const CHARACTER_NAMES_FILE = 'fighting-characters.json';
|
||||||
|
|
||||||
let cachedDefaultBranch: string | null = null;
|
let cachedDefaultBranch: string | null = null;
|
||||||
|
|
||||||
@@ -122,6 +123,37 @@ const listInstalledGames = async () => {
|
|||||||
return gameCatalog.filter((game) => installedSlugs.includes(game.slug)).map((game) => game.title);
|
return gameCatalog.filter((game) => installedSlugs.includes(game.slug)).map((game) => game.title);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const parseCharacterNames = (content: string, gameTitle: string) => {
|
||||||
|
const parsed = JSON.parse(content) as unknown;
|
||||||
|
const names = Array.isArray(parsed)
|
||||||
|
? parsed
|
||||||
|
: typeof parsed === 'object' && parsed !== null && Array.isArray((parsed as { characters?: unknown }).characters)
|
||||||
|
? (parsed as { characters: unknown[] }).characters
|
||||||
|
: null;
|
||||||
|
|
||||||
|
if (!names || names.some((name) => typeof name !== 'string')) {
|
||||||
|
throw new Error(`El archivo ${CHARACTER_NAMES_FILE} de ${gameTitle} no tiene un formato válido.`);
|
||||||
|
}
|
||||||
|
|
||||||
|
return names;
|
||||||
|
};
|
||||||
|
|
||||||
|
const listInstalledCharacterNamesByGame = async () => {
|
||||||
|
const charactersByGame = await Promise.all(gameCatalog.map(async (game) => {
|
||||||
|
const sourcePath = path.join(assetsRoot, game.slug, CHARACTER_NAMES_FILE);
|
||||||
|
|
||||||
|
try {
|
||||||
|
const fileContent = await readFile(sourcePath, 'utf8');
|
||||||
|
const names = parseCharacterNames(fileContent, game.title);
|
||||||
|
return [game.title, names] as const;
|
||||||
|
} catch {
|
||||||
|
return [game.title, []] as const;
|
||||||
|
}
|
||||||
|
}));
|
||||||
|
|
||||||
|
return Object.fromEntries(charactersByGame) as Record<string, string[]>;
|
||||||
|
};
|
||||||
|
|
||||||
const downloadGameAssets = async (gameTitle: string) => {
|
const downloadGameAssets = async (gameTitle: string) => {
|
||||||
const game = gameCatalog.find((entry) => entry.title === gameTitle);
|
const game = gameCatalog.find((entry) => entry.title === gameTitle);
|
||||||
if (!game) {
|
if (!game) {
|
||||||
@@ -136,6 +168,11 @@ const downloadGameAssets = async (gameTitle: string) => {
|
|||||||
throw new Error(`No se encontraron archivos en ${repoFolderPath} dentro de scoreko-assets.`);
|
throw new Error(`No se encontraron archivos en ${repoFolderPath} dentro de scoreko-assets.`);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const hasCharacterNamesFile = files.some((file) => file.path === `${repoFolderPath}/${CHARACTER_NAMES_FILE}`);
|
||||||
|
if (!hasCharacterNamesFile) {
|
||||||
|
throw new Error(`No se encontró ${CHARACTER_NAMES_FILE} en ${repoFolderPath} dentro de scoreko-assets.`);
|
||||||
|
}
|
||||||
|
|
||||||
const totalBytes = files.reduce((acc, file) => acc + (file.size || 0), 0);
|
const totalBytes = files.reduce((acc, file) => acc + (file.size || 0), 0);
|
||||||
let downloadedBytes = 0;
|
let downloadedBytes = 0;
|
||||||
|
|
||||||
@@ -199,6 +236,18 @@ nodecg.listenFor('scoreko-assets:listInstalled', async (_payload: unknown, ack)
|
|||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
|
nodecg.listenFor('scoreko-assets:listCharactersByGame', async (_payload: unknown, ack) => {
|
||||||
|
if (typeof ack !== 'function') {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
ack(null, await listInstalledCharacterNamesByGame());
|
||||||
|
} catch (error) {
|
||||||
|
ack((error as Error).message);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
nodecg.listenFor('scoreko-assets:downloadGame', async (payload: unknown, ack) => {
|
nodecg.listenFor('scoreko-assets:downloadGame', async (payload: unknown, ack) => {
|
||||||
if (typeof ack !== 'function') {
|
if (typeof ack !== 'function') {
|
||||||
return;
|
return;
|
||||||
|
|||||||
@@ -3,7 +3,7 @@ import { useHead } from '@unhead/vue';
|
|||||||
import { computed, nextTick, onBeforeUnmount, ref, watch } from 'vue';
|
import { computed, nextTick, onBeforeUnmount, ref, watch } from 'vue';
|
||||||
import { graphicsSettingsReplicant, playersReplicant, scoreboardReplicant } from '../../browser_shared/replicants';
|
import { graphicsSettingsReplicant, playersReplicant, scoreboardReplicant } from '../../browser_shared/replicants';
|
||||||
import { resolveCountryCode } from '../../shared/countries';
|
import { resolveCountryCode } from '../../shared/countries';
|
||||||
import { getCharactersByGame } from '../../shared/fighting-characters';
|
import { getCharacterAssetUrl } from '../../shared/fighting-characters';
|
||||||
import type { Schemas } from '../../types';
|
import type { Schemas } from '../../types';
|
||||||
|
|
||||||
useHead({ title: 'Scoreboard 2XKO' });
|
useHead({ title: 'Scoreboard 2XKO' });
|
||||||
@@ -35,9 +35,12 @@ const rightName = computed(() => scoreboard.value.rightNameOverride || players.v
|
|||||||
const leftTeam = computed(() => scoreboard.value.leftTeamOverride);
|
const leftTeam = computed(() => scoreboard.value.leftTeamOverride);
|
||||||
const rightTeam = computed(() => scoreboard.value.rightTeamOverride);
|
const rightTeam = computed(() => scoreboard.value.rightTeamOverride);
|
||||||
|
|
||||||
const charMap = new Map(getCharactersByGame('2XKO').map((char) => [char.value, char.image]));
|
const leftCharacterImage = computed(() => scoreboard.value.leftCharacter
|
||||||
const leftCharacterImage = computed(() => charMap.get(scoreboard.value.leftCharacter) ?? '');
|
? getCharacterAssetUrl('2XKO', scoreboard.value.leftCharacter)
|
||||||
const rightCharacterImage = computed(() => charMap.get(scoreboard.value.rightCharacter) ?? '');
|
: '');
|
||||||
|
const rightCharacterImage = computed(() => scoreboard.value.rightCharacter
|
||||||
|
? getCharacterAssetUrl('2XKO', scoreboard.value.rightCharacter)
|
||||||
|
: '');
|
||||||
|
|
||||||
const flagModules = import.meta.glob('/node_modules/flag-icons/flags/4x3/*.svg', { import: 'default', query: '?url' }) as Record<string, () => Promise<string>>;
|
const flagModules = import.meta.glob('/node_modules/flag-icons/flags/4x3/*.svg', { import: 'default', query: '?url' }) as Record<string, () => Promise<string>>;
|
||||||
const flagUrlCache: Record<string, string> = {};
|
const flagUrlCache: Record<string, string> = {};
|
||||||
|
|||||||
@@ -11,224 +11,6 @@ type GamePalette = readonly [startColor: string, endColor: string];
|
|||||||
const DEFAULT_PLACEHOLDER_PALETTE: GamePalette = ['#334155', '#0f172a'];
|
const DEFAULT_PLACEHOLDER_PALETTE: GamePalette = ['#334155', '#0f172a'];
|
||||||
const MAX_INITIALS = 2;
|
const MAX_INITIALS = 2;
|
||||||
|
|
||||||
const characterNamesByGame: Record<string, string[]> = {
|
|
||||||
'Guilty Gear -Strive-': [
|
|
||||||
'A.B.A',
|
|
||||||
'Anji Mito',
|
|
||||||
'Asuka R. Kreutz',
|
|
||||||
'Axl Low',
|
|
||||||
'Baiken',
|
|
||||||
'Bedman?',
|
|
||||||
'Bridget',
|
|
||||||
'Chipp Zanuff',
|
|
||||||
'Dizzy',
|
|
||||||
'Elphelt Valentine',
|
|
||||||
'Faust',
|
|
||||||
'Giovanna',
|
|
||||||
'Goldlewis Dickinson',
|
|
||||||
'Happy Chaos',
|
|
||||||
'I-No',
|
|
||||||
'Jack-O',
|
|
||||||
'Johnny',
|
|
||||||
'Ky Kiske',
|
|
||||||
'Leo Whitefang',
|
|
||||||
'Lucy',
|
|
||||||
'May',
|
|
||||||
'Millia Rage',
|
|
||||||
'Nagoriyuki',
|
|
||||||
'Potemkin',
|
|
||||||
'Ramlethal Valentine',
|
|
||||||
'Sin Kiske',
|
|
||||||
'Slayer',
|
|
||||||
'Sol Badguy',
|
|
||||||
'Testament',
|
|
||||||
'Unika',
|
|
||||||
'Venom',
|
|
||||||
'Zato-1',
|
|
||||||
],
|
|
||||||
'Street Fighter 6': [
|
|
||||||
'A.K.I.',
|
|
||||||
'Akuma',
|
|
||||||
'Alex',
|
|
||||||
'Bison',
|
|
||||||
'Blanka',
|
|
||||||
'Cammy',
|
|
||||||
'Chun-Li',
|
|
||||||
'Dee Jay',
|
|
||||||
'Dhalsim',
|
|
||||||
'E. Honda',
|
|
||||||
'Ed',
|
|
||||||
'Elena',
|
|
||||||
'Guile',
|
|
||||||
'Jamie',
|
|
||||||
'JP',
|
|
||||||
'Juri',
|
|
||||||
'Ken',
|
|
||||||
'Kimberly',
|
|
||||||
'Lily',
|
|
||||||
'Luke',
|
|
||||||
'Mai',
|
|
||||||
'Manon',
|
|
||||||
'Marisa',
|
|
||||||
'Rashid',
|
|
||||||
'Ryu',
|
|
||||||
'Sagat',
|
|
||||||
'Terry',
|
|
||||||
'Viper',
|
|
||||||
'Zangief',
|
|
||||||
],
|
|
||||||
'TEKKEN 8': [
|
|
||||||
'Alisa',
|
|
||||||
'Anna',
|
|
||||||
'Armor King',
|
|
||||||
'Asuka',
|
|
||||||
'Azucena',
|
|
||||||
'Bob',
|
|
||||||
'Bryan',
|
|
||||||
'Claudio',
|
|
||||||
'Clive',
|
|
||||||
'Devil Jin',
|
|
||||||
'Dragunov',
|
|
||||||
'Eddy',
|
|
||||||
'Fahkumram',
|
|
||||||
'Feng',
|
|
||||||
'Heihachi',
|
|
||||||
'Hwoarang',
|
|
||||||
'Jack-8',
|
|
||||||
'Jin',
|
|
||||||
'Jun',
|
|
||||||
'Kazuya',
|
|
||||||
'King',
|
|
||||||
'Kuma',
|
|
||||||
'Kunimitsu',
|
|
||||||
'Lars',
|
|
||||||
'Law',
|
|
||||||
'Lee',
|
|
||||||
'Leo',
|
|
||||||
'Leroy',
|
|
||||||
'Lidia',
|
|
||||||
'Lili',
|
|
||||||
'Miary Zo',
|
|
||||||
'Nina',
|
|
||||||
'Panda',
|
|
||||||
'Paul',
|
|
||||||
'Raven',
|
|
||||||
'Reina',
|
|
||||||
'Roger Jr',
|
|
||||||
'Shaheen',
|
|
||||||
'Steve',
|
|
||||||
'Victor',
|
|
||||||
'Xiaoyu',
|
|
||||||
'Yoshimitsu',
|
|
||||||
'Zafina',
|
|
||||||
],
|
|
||||||
'2XKO': [
|
|
||||||
'Ahri',
|
|
||||||
'Akali',
|
|
||||||
'Braum',
|
|
||||||
'Caitlyn',
|
|
||||||
'Darius',
|
|
||||||
'Ekko',
|
|
||||||
'Illaoi',
|
|
||||||
'Jinx',
|
|
||||||
'Senna',
|
|
||||||
'Teemo',
|
|
||||||
'Vi',
|
|
||||||
'Warwick',
|
|
||||||
'Yasuo',
|
|
||||||
],
|
|
||||||
'Mortal Kombat 1': [
|
|
||||||
'Ashrah',
|
|
||||||
'Baraka',
|
|
||||||
'Conan the Barbarian',
|
|
||||||
'Cyrax',
|
|
||||||
'Ermac',
|
|
||||||
'Geras',
|
|
||||||
'Ghostface',
|
|
||||||
'Havik',
|
|
||||||
'Homelander',
|
|
||||||
'Johnny Cage',
|
|
||||||
'Kenshi',
|
|
||||||
'Kitana',
|
|
||||||
'Kung Lao',
|
|
||||||
'Li Mei',
|
|
||||||
'Liu Kang',
|
|
||||||
'Mileena',
|
|
||||||
'Nitara',
|
|
||||||
'Noob Saibot',
|
|
||||||
'Omni-Man',
|
|
||||||
'Peacemaker',
|
|
||||||
'Quan Chi',
|
|
||||||
'Raiden',
|
|
||||||
'Rain',
|
|
||||||
'Reiko',
|
|
||||||
'Reptile',
|
|
||||||
'Scorpion',
|
|
||||||
'Sektor',
|
|
||||||
'Shang Tsung',
|
|
||||||
'Sindel',
|
|
||||||
'Smoke',
|
|
||||||
'Sub-Zero',
|
|
||||||
'Takeda',
|
|
||||||
'Tanya',
|
|
||||||
'T-1000',
|
|
||||||
],
|
|
||||||
'THE KING OF FIGHTERS XV': [
|
|
||||||
'Angel',
|
|
||||||
'Antonov',
|
|
||||||
'Ash Crimson',
|
|
||||||
'Athena Asamiya',
|
|
||||||
'Benimaru Nikaido',
|
|
||||||
'Billy Kane',
|
|
||||||
'Blue Mary',
|
|
||||||
'Chizuru Kagura',
|
|
||||||
'Chris',
|
|
||||||
'Clark Still',
|
|
||||||
'Dolores',
|
|
||||||
'Duo Lon',
|
|
||||||
'Elisabeth Blanctorche',
|
|
||||||
'Gato',
|
|
||||||
'Geese Howard',
|
|
||||||
'Goenitz',
|
|
||||||
'Heidern',
|
|
||||||
'Hinako Shijo',
|
|
||||||
'Iori Yagami',
|
|
||||||
'Isla',
|
|
||||||
'Joe Higashi',
|
|
||||||
"K'",
|
|
||||||
'Kim Kaphwan',
|
|
||||||
'King',
|
|
||||||
'King of Dinosaurs',
|
|
||||||
'Krohnen McDougall',
|
|
||||||
'Kula Diamond',
|
|
||||||
'Kukri',
|
|
||||||
'Kyo Kusanagi',
|
|
||||||
'Leona Heidern',
|
|
||||||
'Luong',
|
|
||||||
'Mai Shiranui',
|
|
||||||
'Maxima',
|
|
||||||
'Meitenkun',
|
|
||||||
'Najd',
|
|
||||||
'Orochi Chris',
|
|
||||||
'Orochi Shermie',
|
|
||||||
'Orochi Yashiro',
|
|
||||||
'Ralf Jones',
|
|
||||||
'Ramón',
|
|
||||||
'Robert Garcia',
|
|
||||||
'Rock Howard',
|
|
||||||
'Ryo Sakazaki',
|
|
||||||
'Ryuji Yamazaki',
|
|
||||||
'Shermie',
|
|
||||||
'Shingo Yabuki',
|
|
||||||
'Sylvie Paula Paula',
|
|
||||||
'Terry Bogard',
|
|
||||||
'Vanessa',
|
|
||||||
'Whip',
|
|
||||||
'Yashiro Nanakase',
|
|
||||||
'Yuri Sakazaki',
|
|
||||||
],
|
|
||||||
};
|
|
||||||
|
|
||||||
const defaultCharacterPairByGame: Record<string, { leftCharacter: string; rightCharacter: string }> = {
|
const defaultCharacterPairByGame: Record<string, { leftCharacter: string; rightCharacter: string }> = {
|
||||||
'Guilty Gear -Strive-': {
|
'Guilty Gear -Strive-': {
|
||||||
leftCharacter: 'sol-badguy',
|
leftCharacter: 'sol-badguy',
|
||||||
@@ -296,11 +78,6 @@ const buildCharacterPlaceholder = (game: string, character: string) => {
|
|||||||
return toDataUrl(svg.trim());
|
return toDataUrl(svg.trim());
|
||||||
};
|
};
|
||||||
|
|
||||||
const getCharacterAssetUrl = (game: string, characterValue: string) => {
|
|
||||||
const gameSlug = toSlug(game);
|
|
||||||
return `/bundles/scoreko-dev/game-assets/${gameSlug}/characters/${characterValue}.png`;
|
|
||||||
};
|
|
||||||
|
|
||||||
const characterImageModules = import.meta.glob('/src/shared/character-images/**/*.{png,jpg,jpeg,webp,avif,svg}', {
|
const characterImageModules = import.meta.glob('/src/shared/character-images/**/*.{png,jpg,jpeg,webp,avif,svg}', {
|
||||||
eager: true,
|
eager: true,
|
||||||
import: 'default',
|
import: 'default',
|
||||||
@@ -335,25 +112,21 @@ const getBundledCharacterImage = (game: string, characterValue: string) => {
|
|||||||
return characterImageByKey[`${gameSlug}/${characterValue}`] ?? '';
|
return characterImageByKey[`${gameSlug}/${characterValue}`] ?? '';
|
||||||
};
|
};
|
||||||
|
|
||||||
const getCharacterImage = (game: string, character: string, characterValue: string) => getCharacterAssetUrl(game, characterValue);
|
export const getCharacterAssetUrl = (game: string, characterValue: string) => {
|
||||||
|
const gameSlug = toSlug(game);
|
||||||
|
return `/bundles/scoreko-dev/game-assets/${gameSlug}/characters/${characterValue}.png`;
|
||||||
|
};
|
||||||
|
|
||||||
export const fightingCharactersByGame: Record<string, FightingCharacterOption[]> = Object.fromEntries(
|
export const buildCharactersByGame = (game: string, characterNames: string[]) => characterNames.map((character) => {
|
||||||
Object.entries(characterNamesByGame).map(([game, characterNames]) => [
|
const value = toSlug(character);
|
||||||
game,
|
|
||||||
characterNames.map((character) => {
|
|
||||||
const value = toSlug(character);
|
|
||||||
// Prefer packaged artwork and gracefully fallback to a generated image.
|
|
||||||
return {
|
|
||||||
label: character,
|
|
||||||
value,
|
|
||||||
image: getCharacterImage(game, character, value),
|
|
||||||
bundledImage: getBundledCharacterImage(game, value),
|
|
||||||
fallbackImage: buildCharacterPlaceholder(game, character),
|
|
||||||
};
|
|
||||||
}),
|
|
||||||
]),
|
|
||||||
);
|
|
||||||
|
|
||||||
export const getCharactersByGame = (game: string) => fightingCharactersByGame[game] ?? [];
|
return {
|
||||||
|
label: character,
|
||||||
|
value,
|
||||||
|
image: getCharacterAssetUrl(game, value),
|
||||||
|
bundledImage: getBundledCharacterImage(game, value),
|
||||||
|
fallbackImage: buildCharacterPlaceholder(game, character),
|
||||||
|
};
|
||||||
|
});
|
||||||
|
|
||||||
export const getDefaultCharactersByGame = (game: string) => defaultCharacterPairByGame[game];
|
export const getDefaultCharactersByGame = (game: string) => defaultCharacterPairByGame[game];
|
||||||
|
|||||||
Reference in New Issue
Block a user