mirror of
https://github.com/PhaseII-eAmusement-Network/PhaseWeb3-Vue.git
synced 2026-03-21 17:54:26 -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_KEY="your_api_key_should_be_here"
|
||||
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_KEY="your_api_key_should_be_here"
|
||||
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.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.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 version = ref(props.version);
|
||||
const AkanameSettings = ref([]);
|
||||
const TitlePartSettings = ref([]);
|
||||
const fullyLoaded = ref(false);
|
||||
|
||||
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 () => {
|
||||
if (thisGame.id === GameConstants.SDVX) {
|
||||
await loadAkanameSettings();
|
||||
} else if (thisGame.id === GameConstants.JUBEAT) {
|
||||
await loadTitlePartSettings();
|
||||
} else {
|
||||
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"
|
||||
>
|
||||
<UserEmblem
|
||||
v-if="game == 'jubeat' && version >= 10 && profile.last?.emblem"
|
||||
v-if="
|
||||
game == GameConstants.JUBEAT && version >= 10 && profile.last?.emblem
|
||||
"
|
||||
:version="version"
|
||||
:profile="profile"
|
||||
class="place-self-center pb-6 md:pb-0"
|
||||
/>
|
||||
<UserQpro
|
||||
v-if="game == 'iidx' && version >= 20 && profile.qpro"
|
||||
v-if="game == GameConstants.IIDX && version >= 20 && profile.qpro"
|
||||
:version="version"
|
||||
:profile="profile"
|
||||
class="place-self-center md:mt-10 mb-10 md:mb-0"
|
||||
/>
|
||||
<div class="drop-shadow-2xl">
|
||||
<p
|
||||
v-if="profile.title"
|
||||
v-if="profile.title && game != GameConstants.JUBEAT"
|
||||
:class="colorText()"
|
||||
class="text-2xl tracking-widest font-light -mb-1"
|
||||
>
|
||||
|
|
@ -96,6 +116,17 @@ onMounted(async () => {
|
|||
AkanameSettings.find((item) => item.id === profile.akaname)?.label
|
||||
}}
|
||||
</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>
|
||||
<p class="text-xl font-mono">{{ dashCode(profile.extid) }}</p>
|
||||
</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_FESTO = 13;
|
||||
static JUBEAT_AVE = 14;
|
||||
static JUBEAT_BEYOND = 15;
|
||||
|
||||
static LOVEPLUS_ARCADE = 1;
|
||||
static LOVEPLUS_CC = 2;
|
||||
|
|
@ -1650,6 +1651,11 @@ export const gameData = [
|
|||
label: "Avenue",
|
||||
maxRivals: 3,
|
||||
},
|
||||
{
|
||||
id: VersionConstants.JUBEAT_BEYOND,
|
||||
label: "Beyond the Avenue",
|
||||
maxRivals: 3,
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
|
|
|
|||
|
|
@ -27,6 +27,7 @@ import {
|
|||
} from "@/constants/values";
|
||||
import { getGameInfo } from "@/constants";
|
||||
import { getGameOptions } from "@/constants/options";
|
||||
import TitlePartCardBox from "@/components/Cards/TitlePartCardBox.vue";
|
||||
|
||||
const $route = useRoute();
|
||||
const $router = useRouter();
|
||||
|
|
@ -246,6 +247,11 @@ async function updateProfile() {
|
|||
:profile="myProfile"
|
||||
:version="versionForm.currentVersion"
|
||||
/>
|
||||
<TitlePartCardBox
|
||||
v-if="gameID == 'jubeat' && versionForm.currentVersion >= 13"
|
||||
:profile="myProfile"
|
||||
:version="versionForm.currentVersion"
|
||||
/>
|
||||
<QproCardBox
|
||||
v-if="
|
||||
(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