mirror of
https://github.com/PhaseII-eAmusement-Network/PhaseWeb3-Vue.git
synced 2026-04-26 15:57:07 -05:00
Add Jubeat title support, add Jubeat BTA base support
Some checks failed
Build / build (push) Has been cancelled
Some checks failed
Build / build (push) Has been cancelled
This commit is contained in:
parent
7a258bb8ea
commit
860603a194
|
|
@ -1,4 +1,4 @@
|
||||||
VITE_APP_VERSION="3.0.33"
|
VITE_APP_VERSION="3.0.34"
|
||||||
VITE_API_URL="http://localhost:8000/"
|
VITE_API_URL="http://localhost:8000/"
|
||||||
VITE_API_KEY="your_api_key_should_be_here"
|
VITE_API_KEY="your_api_key_should_be_here"
|
||||||
VITE_ASSET_PATH="/assets"
|
VITE_ASSET_PATH="/assets"
|
||||||
|
|
|
||||||
|
|
@ -1,4 +1,4 @@
|
||||||
VITE_APP_VERSION="3.0.33"
|
VITE_APP_VERSION="3.0.34"
|
||||||
VITE_API_URL="https://restfulsleep.phaseii.network"
|
VITE_API_URL="https://restfulsleep.phaseii.network"
|
||||||
VITE_API_KEY="your_api_key_should_be_here"
|
VITE_API_KEY="your_api_key_should_be_here"
|
||||||
VITE_ASSET_PATH="https://cdn.phaseii.network/file/PhaseII/web-assets"
|
VITE_ASSET_PATH="https://cdn.phaseii.network/file/PhaseII/web-assets"
|
||||||
|
|
|
||||||
BIN
public/assets/games/jubeat/card/15.webp
Normal file
BIN
public/assets/games/jubeat/card/15.webp
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 66 KiB |
|
|
@ -32,5 +32,6 @@
|
||||||
"3.0.30": ["- (Major) Rewrite auth flow at backend and frontend", "- (Minor) Add auth to all api calls", "- (Bugfix) Fix bad user auth bug", "- (Minor) Add more greetings"],
|
"3.0.30": ["- (Major) Rewrite auth flow at backend and frontend", "- (Minor) Add auth to all api calls", "- (Bugfix) Fix bad user auth bug", "- (Minor) Add more greetings"],
|
||||||
"3.0.31": ["- (Major) Change all date formatting to a sortable format", "- (Minor) Add news archive page", "- (Bugfix) Add real news limiting", "- (Shrimp) Add more shrimp"],
|
"3.0.31": ["- (Major) Change all date formatting to a sortable format", "- (Minor) Add news archive page", "- (Bugfix) Add real news limiting", "- (Shrimp) Add more shrimp"],
|
||||||
"3.0.32": ["- (Major) Change phase \"Attempt\" to \"Score\"", "- (Bugfix) Fix pop'n music version sorting", "- (Bugfix) Fix Gitadora chart data formatting issues", "- (Bugfix) Filter personal records to songs with scores"],
|
"3.0.32": ["- (Major) Change phase \"Attempt\" to \"Score\"", "- (Bugfix) Fix pop'n music version sorting", "- (Bugfix) Fix Gitadora chart data formatting issues", "- (Bugfix) Filter personal records to songs with scores"],
|
||||||
"3.0.33": ["- (Major) Condense all score tables to one parser and format", "- (Major) Add more game metadata", "- (Bugfix) Fix difficulties showing as `NaN`", "- (Beta) Add a developmental device plugin for card effect"]
|
"3.0.33": ["- (Major) Condense all score tables to one parser and format", "- (Major) Add more game metadata", "- (Bugfix) Fix difficulties showing as `NaN`", "- (Beta) Add a developmental device plugin for card effect"],
|
||||||
|
"3.0.34": ["- (Major) Add support for Jubeat titles", "- (Major) Add base BTA support"]
|
||||||
}
|
}
|
||||||
9005
public/data-sources/emblems/15.json
Normal file
9005
public/data-sources/emblems/15.json
Normal file
File diff suppressed because it is too large
Load Diff
19049
public/data-sources/jubeat-title/13.json
Normal file
19049
public/data-sources/jubeat-title/13.json
Normal file
File diff suppressed because it is too large
Load Diff
20218
public/data-sources/jubeat-title/14.json
Normal file
20218
public/data-sources/jubeat-title/14.json
Normal file
File diff suppressed because it is too large
Load Diff
21359
public/data-sources/jubeat-title/15.json
Normal file
21359
public/data-sources/jubeat-title/15.json
Normal file
File diff suppressed because it is too large
Load Diff
|
|
@ -33,6 +33,7 @@ function colorText() {
|
||||||
const thisGame = getGameInfo(props.game);
|
const thisGame = getGameInfo(props.game);
|
||||||
const version = ref(props.version);
|
const version = ref(props.version);
|
||||||
const AkanameSettings = ref([]);
|
const AkanameSettings = ref([]);
|
||||||
|
const TitlePartSettings = ref([]);
|
||||||
const fullyLoaded = ref(false);
|
const fullyLoaded = ref(false);
|
||||||
|
|
||||||
async function loadAkanameSettings() {
|
async function loadAkanameSettings() {
|
||||||
|
|
@ -50,9 +51,26 @@ async function loadAkanameSettings() {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
async function loadTitlePartSettings() {
|
||||||
|
try {
|
||||||
|
const response = await axios.get(
|
||||||
|
`/data-sources/jubeat-title/${version.value}.json`,
|
||||||
|
);
|
||||||
|
if (response.data) {
|
||||||
|
TitlePartSettings.value = response.data;
|
||||||
|
}
|
||||||
|
} catch (error) {
|
||||||
|
console.error("Error loading TitleParts settings:", error.message);
|
||||||
|
} finally {
|
||||||
|
fullyLoaded.value = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
onMounted(async () => {
|
onMounted(async () => {
|
||||||
if (thisGame.id === GameConstants.SDVX) {
|
if (thisGame.id === GameConstants.SDVX) {
|
||||||
await loadAkanameSettings();
|
await loadAkanameSettings();
|
||||||
|
} else if (thisGame.id === GameConstants.JUBEAT) {
|
||||||
|
await loadTitlePartSettings();
|
||||||
} else {
|
} else {
|
||||||
fullyLoaded.value = true;
|
fullyLoaded.value = true;
|
||||||
}
|
}
|
||||||
|
|
@ -65,20 +83,22 @@ onMounted(async () => {
|
||||||
class="md:flex md:space-x-12 md:justify-center md:items-center grid grid-cols-1 text-center"
|
class="md:flex md:space-x-12 md:justify-center md:items-center grid grid-cols-1 text-center"
|
||||||
>
|
>
|
||||||
<UserEmblem
|
<UserEmblem
|
||||||
v-if="game == 'jubeat' && version >= 10 && profile.last?.emblem"
|
v-if="
|
||||||
|
game == GameConstants.JUBEAT && version >= 10 && profile.last?.emblem
|
||||||
|
"
|
||||||
:version="version"
|
:version="version"
|
||||||
:profile="profile"
|
:profile="profile"
|
||||||
class="place-self-center pb-6 md:pb-0"
|
class="place-self-center pb-6 md:pb-0"
|
||||||
/>
|
/>
|
||||||
<UserQpro
|
<UserQpro
|
||||||
v-if="game == 'iidx' && version >= 20 && profile.qpro"
|
v-if="game == GameConstants.IIDX && version >= 20 && profile.qpro"
|
||||||
:version="version"
|
:version="version"
|
||||||
:profile="profile"
|
:profile="profile"
|
||||||
class="place-self-center md:mt-10 mb-10 md:mb-0"
|
class="place-self-center md:mt-10 mb-10 md:mb-0"
|
||||||
/>
|
/>
|
||||||
<div class="drop-shadow-2xl">
|
<div class="drop-shadow-2xl">
|
||||||
<p
|
<p
|
||||||
v-if="profile.title"
|
v-if="profile.title && game != GameConstants.JUBEAT"
|
||||||
:class="colorText()"
|
:class="colorText()"
|
||||||
class="text-2xl tracking-widest font-light -mb-1"
|
class="text-2xl tracking-widest font-light -mb-1"
|
||||||
>
|
>
|
||||||
|
|
@ -96,6 +116,17 @@ onMounted(async () => {
|
||||||
AkanameSettings.find((item) => item.id === profile.akaname)?.label
|
AkanameSettings.find((item) => item.id === profile.akaname)?.label
|
||||||
}}
|
}}
|
||||||
</p>
|
</p>
|
||||||
|
|
||||||
|
<p
|
||||||
|
v-if="
|
||||||
|
(profile.title || profile.parts) && game == GameConstants.JUBEAT
|
||||||
|
"
|
||||||
|
class="text-2xl tracking-widest font-light my-1"
|
||||||
|
>
|
||||||
|
{{
|
||||||
|
TitlePartSettings.find((item) => item.id === profile.title)?.label
|
||||||
|
}}
|
||||||
|
</p>
|
||||||
</template>
|
</template>
|
||||||
<p class="text-xl font-mono">{{ dashCode(profile.extid) }}</p>
|
<p class="text-xl font-mono">{{ dashCode(profile.extid) }}</p>
|
||||||
</div>
|
</div>
|
||||||
|
|
|
||||||
140
src/components/Cards/TitlePartCardBox.vue
Normal file
140
src/components/Cards/TitlePartCardBox.vue
Normal file
|
|
@ -0,0 +1,140 @@
|
||||||
|
<script setup>
|
||||||
|
import axios from "axios";
|
||||||
|
import { watch, ref } from "vue";
|
||||||
|
import { useRouter } from "vue-router";
|
||||||
|
import { PhSpinnerBall } from "@phosphor-icons/vue";
|
||||||
|
import BaseButton from "@/components/BaseButton.vue";
|
||||||
|
import BaseIcon from "@/components/BaseIcon.vue";
|
||||||
|
import CardBox from "@/components/CardBox.vue";
|
||||||
|
import FormField from "@/components/FormField.vue";
|
||||||
|
import FormControl from "@/components/FormControl.vue";
|
||||||
|
import PillTag from "@/components/PillTag.vue";
|
||||||
|
|
||||||
|
import { GameConstants } from "@/constants";
|
||||||
|
import { APIUpdateProfile } from "@/stores/api/profile";
|
||||||
|
|
||||||
|
const props = defineProps({
|
||||||
|
profile: {
|
||||||
|
type: Object,
|
||||||
|
default: null,
|
||||||
|
},
|
||||||
|
version: {
|
||||||
|
type: Number,
|
||||||
|
default: 6,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
const router = useRouter();
|
||||||
|
const userProfile = ref(JSON.parse(JSON.stringify(props.profile)));
|
||||||
|
const version = ref(props.version);
|
||||||
|
const TitleKey = ref(0);
|
||||||
|
|
||||||
|
const newTitle = ref(userProfile.value?.title ?? 0);
|
||||||
|
const TitleSettings = ref([]);
|
||||||
|
const isModified = ref(false);
|
||||||
|
const loading = ref(false);
|
||||||
|
|
||||||
|
watch(
|
||||||
|
() => props.version,
|
||||||
|
() => {
|
||||||
|
userProfile.value = JSON.parse(JSON.stringify(props.profile));
|
||||||
|
loadTitleSettings();
|
||||||
|
newTitle.value = userProfile.value?.title;
|
||||||
|
isModified.value = false;
|
||||||
|
},
|
||||||
|
);
|
||||||
|
|
||||||
|
watch(
|
||||||
|
() => props.version,
|
||||||
|
() => {
|
||||||
|
version.value = props.version;
|
||||||
|
},
|
||||||
|
);
|
||||||
|
|
||||||
|
watch(
|
||||||
|
newTitle,
|
||||||
|
() => {
|
||||||
|
TitleKey.value++;
|
||||||
|
isModified.value = !dataEquals(newTitle.value, props.profile.akaname);
|
||||||
|
},
|
||||||
|
{ deep: true },
|
||||||
|
);
|
||||||
|
|
||||||
|
loadTitleSettings();
|
||||||
|
function loadTitleSettings() {
|
||||||
|
axios
|
||||||
|
.get(`/data-sources/jubeat-title/${version.value}.json`)
|
||||||
|
.then((r) => {
|
||||||
|
if (r.data) {
|
||||||
|
TitleSettings.value = r.data;
|
||||||
|
}
|
||||||
|
})
|
||||||
|
.catch((error) => {
|
||||||
|
console.log(error.message);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
function dataEquals(data1, data2) {
|
||||||
|
return JSON.stringify({ data1 }) === JSON.stringify({ data2 });
|
||||||
|
}
|
||||||
|
|
||||||
|
async function updateProfile() {
|
||||||
|
loading.value = true;
|
||||||
|
var newProfile = JSON.parse(JSON.stringify(props.profile));
|
||||||
|
|
||||||
|
newProfile.title = newTitle.value;
|
||||||
|
const partId = TitleSettings.value.find(
|
||||||
|
(item) => item.id === newTitle.value,
|
||||||
|
)?.part;
|
||||||
|
newProfile.parts = partId;
|
||||||
|
const profileStatus = await APIUpdateProfile(
|
||||||
|
GameConstants.JUBEAT,
|
||||||
|
props.version,
|
||||||
|
{ title: newTitle.value, parts: partId },
|
||||||
|
);
|
||||||
|
if (profileStatus.status != "error") {
|
||||||
|
router.go();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function revert() {
|
||||||
|
newTitle.value = userProfile.value?.title ?? 0;
|
||||||
|
isModified.value = false;
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<template>
|
||||||
|
<CardBox class="mt-6">
|
||||||
|
<PillTag color="info" label="Title / Title Part" class="mb-2" />
|
||||||
|
<div class="grid md:grid-cols-2 space-y-6 align-center">
|
||||||
|
<form>
|
||||||
|
<FormField label="Title" help="Sets your profile's title">
|
||||||
|
<FormControl v-model="newTitle" :options="TitleSettings" />
|
||||||
|
</FormField>
|
||||||
|
</form>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<template #footer>
|
||||||
|
<div class="space-x-2 pb-4">
|
||||||
|
<BaseButton
|
||||||
|
v-if="isModified"
|
||||||
|
color="success"
|
||||||
|
label="Save"
|
||||||
|
@click="updateProfile()"
|
||||||
|
/>
|
||||||
|
<BaseButton
|
||||||
|
v-if="isModified"
|
||||||
|
color="danger"
|
||||||
|
label="Revert"
|
||||||
|
@click="revert()"
|
||||||
|
/>
|
||||||
|
<BaseIcon
|
||||||
|
v-if="loading"
|
||||||
|
:icon="PhSpinnerBall"
|
||||||
|
color="text-yellow-500"
|
||||||
|
class="animate animate-spin"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
</CardBox>
|
||||||
|
</template>
|
||||||
|
|
@ -188,6 +188,7 @@ export class VersionConstants {
|
||||||
static JUBEAT_CLAN = 12;
|
static JUBEAT_CLAN = 12;
|
||||||
static JUBEAT_FESTO = 13;
|
static JUBEAT_FESTO = 13;
|
||||||
static JUBEAT_AVE = 14;
|
static JUBEAT_AVE = 14;
|
||||||
|
static JUBEAT_BEYOND = 15;
|
||||||
|
|
||||||
static LOVEPLUS_ARCADE = 1;
|
static LOVEPLUS_ARCADE = 1;
|
||||||
static LOVEPLUS_CC = 2;
|
static LOVEPLUS_CC = 2;
|
||||||
|
|
@ -1650,6 +1651,11 @@ export const gameData = [
|
||||||
label: "Avenue",
|
label: "Avenue",
|
||||||
maxRivals: 3,
|
maxRivals: 3,
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
id: VersionConstants.JUBEAT_BEYOND,
|
||||||
|
label: "Beyond the Avenue",
|
||||||
|
maxRivals: 3,
|
||||||
|
},
|
||||||
],
|
],
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
|
|
|
||||||
|
|
@ -27,6 +27,7 @@ import {
|
||||||
} from "@/constants/values";
|
} from "@/constants/values";
|
||||||
import { getGameInfo } from "@/constants";
|
import { getGameInfo } from "@/constants";
|
||||||
import { getGameOptions } from "@/constants/options";
|
import { getGameOptions } from "@/constants/options";
|
||||||
|
import TitlePartCardBox from "@/components/Cards/TitlePartCardBox.vue";
|
||||||
|
|
||||||
const $route = useRoute();
|
const $route = useRoute();
|
||||||
const $router = useRouter();
|
const $router = useRouter();
|
||||||
|
|
@ -246,6 +247,11 @@ async function updateProfile() {
|
||||||
:profile="myProfile"
|
:profile="myProfile"
|
||||||
:version="versionForm.currentVersion"
|
:version="versionForm.currentVersion"
|
||||||
/>
|
/>
|
||||||
|
<TitlePartCardBox
|
||||||
|
v-if="gameID == 'jubeat' && versionForm.currentVersion >= 13"
|
||||||
|
:profile="myProfile"
|
||||||
|
:version="versionForm.currentVersion"
|
||||||
|
/>
|
||||||
<QproCardBox
|
<QproCardBox
|
||||||
v-if="
|
v-if="
|
||||||
(gameID == 'iidx' || gameID == 'iidxclass') &&
|
(gameID == 'iidx' || gameID == 'iidxclass') &&
|
||||||
|
|
|
||||||
96
tools/title_parser.py
Normal file
96
tools/title_parser.py
Normal file
|
|
@ -0,0 +1,96 @@
|
||||||
|
import sys
|
||||||
|
import os
|
||||||
|
import re
|
||||||
|
import json
|
||||||
|
import xml.etree.ElementTree as ET
|
||||||
|
|
||||||
|
args = sys.argv
|
||||||
|
if len(args) != 3:
|
||||||
|
print('<title_parser> <achievement_info.xml> <title_parts_info.xml>\nWrites a resulting JSON file to the same path.')
|
||||||
|
sys.exit()
|
||||||
|
|
||||||
|
achFilePath = args[1]
|
||||||
|
if not os.path.exists(achFilePath):
|
||||||
|
print(f'The path {achFilePath} does not exist!')
|
||||||
|
sys.exit()
|
||||||
|
|
||||||
|
partsFilePath = args[2]
|
||||||
|
if not os.path.exists(partsFilePath):
|
||||||
|
print(f'The path {partsFilePath} does not exist!')
|
||||||
|
sys.exit()
|
||||||
|
|
||||||
|
if not achFilePath.endswith('.xml'):
|
||||||
|
print('You must supply an XML file.')
|
||||||
|
sys.exit()
|
||||||
|
|
||||||
|
if not partsFilePath.endswith('.xml'):
|
||||||
|
print('You must supply an XML file.')
|
||||||
|
sys.exit()
|
||||||
|
|
||||||
|
def parseParts(xmlFile: str) -> dict:
|
||||||
|
root = ET.fromstring(xmlFile)
|
||||||
|
parts = {}
|
||||||
|
for part in root.findall('body/data'):
|
||||||
|
partId = part.find('parts_id')
|
||||||
|
if not partId.text:
|
||||||
|
continue
|
||||||
|
|
||||||
|
partJIS = part.find('parts')
|
||||||
|
if partJIS == None:
|
||||||
|
continue
|
||||||
|
|
||||||
|
partStr = bytes.fromhex(partJIS.text).decode('shift-jis')
|
||||||
|
parts[int(partId.text)] = partStr
|
||||||
|
return parts
|
||||||
|
|
||||||
|
def parseAch(xmlFile: str, parts: dict) -> list[dict]:
|
||||||
|
root = ET.fromstring(xmlFile)
|
||||||
|
titles = []
|
||||||
|
for title in root.findall('body/data'):
|
||||||
|
title_id = title.find('id', None)
|
||||||
|
if not title_id.text:
|
||||||
|
continue
|
||||||
|
|
||||||
|
index = title.find('index').text
|
||||||
|
if not index:
|
||||||
|
continue
|
||||||
|
|
||||||
|
partStr = ""
|
||||||
|
part_id = 0
|
||||||
|
is_need_parts = title.find('is_need_parts').text
|
||||||
|
if int(is_need_parts):
|
||||||
|
parts_id = title.find('parts_id')
|
||||||
|
partStr = parts[int(parts_id.text)]
|
||||||
|
part_id = int(parts_id.text)
|
||||||
|
|
||||||
|
partJIS = title.find('template')
|
||||||
|
if partJIS == None:
|
||||||
|
continue
|
||||||
|
titleStr = bytes.fromhex(partJIS.text).decode('shift-jis')
|
||||||
|
titleStr = titleStr.replace('%s', partStr)
|
||||||
|
|
||||||
|
conditionJIS = title.find('condition')
|
||||||
|
if conditionJIS == None:
|
||||||
|
continue
|
||||||
|
conditionStr = bytes.fromhex(conditionJIS.text).decode('shift-jis')
|
||||||
|
|
||||||
|
parsedData = {}
|
||||||
|
parsedData['label'] = titleStr
|
||||||
|
parsedData['condition'] = conditionStr
|
||||||
|
parsedData['index'] = int(title_id.text)
|
||||||
|
parsedData['part'] = int(part_id)
|
||||||
|
parsedData['id'] = int(index)
|
||||||
|
titles.append(parsedData)
|
||||||
|
return titles
|
||||||
|
|
||||||
|
achData = []
|
||||||
|
with open(partsFilePath, 'r', encoding='utf-8') as inFile:
|
||||||
|
parts = parseParts(inFile.read())
|
||||||
|
|
||||||
|
with open(achFilePath, 'r', encoding='utf-8') as inFile:
|
||||||
|
achData = parseAch(inFile.read(), parts)
|
||||||
|
|
||||||
|
with open(achFilePath.replace('.xml', '.json'), 'w', encoding='utf-8') as outFile:
|
||||||
|
outFile.write(json.dumps(achData, indent=4))
|
||||||
|
|
||||||
|
print(f'Converted {len(achData)} parts\nyippie!')
|
||||||
Loading…
Reference in New Issue
Block a user