mirror of
https://github.com/283375/arcaea-offline.git
synced 2025-04-18 13:50:16 +00:00
Compare commits
3 Commits
990efee900
...
6fb24d4907
Author | SHA1 | Date | |
---|---|---|---|
6fb24d4907 | |||
eab2a3e520 | |||
caced6eaec |
@ -1,13 +1,11 @@
|
|||||||
import logging
|
import logging
|
||||||
import math
|
import math
|
||||||
from typing import Iterable, List, Optional, Type, Union
|
from typing import Iterable, Optional, Type, Union
|
||||||
|
|
||||||
from sqlalchemy import Engine, func, inspect, select
|
from sqlalchemy import Engine, func, inspect, select
|
||||||
from sqlalchemy.orm import DeclarativeBase, InstrumentedAttribute, sessionmaker
|
from sqlalchemy.orm import DeclarativeBase, InstrumentedAttribute, sessionmaker
|
||||||
|
|
||||||
from arcaea_offline.external.arcsong.arcsong_json import ArcSongJsonBuilder
|
from arcaea_offline.external.arcsong.arcsong_json import ArcSongJsonBuilder
|
||||||
from arcaea_offline.external.exports import exporters
|
|
||||||
from arcaea_offline.external.exports.types import ArcaeaOfflineDEFV2_Score, ScoreExport
|
|
||||||
from arcaea_offline.singleton import Singleton
|
from arcaea_offline.singleton import Singleton
|
||||||
|
|
||||||
from .models.v4.config import ConfigBase, Property
|
from .models.v4.config import ConfigBase, Property
|
||||||
@ -407,20 +405,6 @@ class Database(metaclass=Singleton):
|
|||||||
# endregion
|
# endregion
|
||||||
|
|
||||||
# region export
|
# region export
|
||||||
|
|
||||||
def export_scores(self) -> List[ScoreExport]:
|
|
||||||
scores = self.get_scores()
|
|
||||||
return [exporters.score(score) for score in scores]
|
|
||||||
|
|
||||||
def export_scores_def_v2(self) -> ArcaeaOfflineDEFV2_Score:
|
|
||||||
scores = self.get_scores()
|
|
||||||
return {
|
|
||||||
"$schema": "https://arcaeaoffline.sevive.xyz/schemas/def/v2/score.schema.json",
|
|
||||||
"type": "score",
|
|
||||||
"version": 2,
|
|
||||||
"scores": [exporters.score_def_v2(score) for score in scores],
|
|
||||||
}
|
|
||||||
|
|
||||||
def generate_arcsong(self):
|
def generate_arcsong(self):
|
||||||
with self.sessionmaker() as session:
|
with self.sessionmaker() as session:
|
||||||
arcsong = ArcSongJsonBuilder(session).generate_arcsong_json()
|
arcsong = ArcSongJsonBuilder(session).generate_arcsong_json()
|
||||||
|
@ -1,22 +1,7 @@
|
|||||||
from typing import List, Literal, Optional, TypedDict
|
from typing import List, Literal, Optional, TypedDict
|
||||||
|
|
||||||
|
|
||||||
class ScoreExport(TypedDict):
|
class ArcaeaOfflineDEFv2PlayResultItem(TypedDict, total=False):
|
||||||
id: int
|
|
||||||
song_id: str
|
|
||||||
rating_class: int
|
|
||||||
score: int
|
|
||||||
pure: Optional[int]
|
|
||||||
far: Optional[int]
|
|
||||||
lost: Optional[int]
|
|
||||||
date: Optional[int]
|
|
||||||
max_recall: Optional[int]
|
|
||||||
modifier: Optional[int]
|
|
||||||
clear_type: Optional[int]
|
|
||||||
comment: Optional[str]
|
|
||||||
|
|
||||||
|
|
||||||
class ArcaeaOfflineDEFV2_ScoreItem(TypedDict, total=False):
|
|
||||||
id: Optional[int]
|
id: Optional[int]
|
||||||
songId: str
|
songId: str
|
||||||
ratingClass: int
|
ratingClass: int
|
||||||
@ -32,14 +17,14 @@ class ArcaeaOfflineDEFV2_ScoreItem(TypedDict, total=False):
|
|||||||
comment: Optional[str]
|
comment: Optional[str]
|
||||||
|
|
||||||
|
|
||||||
ArcaeaOfflineDEFV2_Score = TypedDict(
|
ArcaeaOfflineDEFv2PlayResultRoot = TypedDict(
|
||||||
"ArcaeaOfflineDEFV2_Score",
|
"ArcaeaOfflineDEFv2PlayResultRoot",
|
||||||
{
|
{
|
||||||
"$schema": Literal[
|
"$schema": Literal[
|
||||||
"https://arcaeaoffline.sevive.xyz/schemas/def/v2/score.schema.json"
|
"https://arcaeaoffline.sevive.xyz/schemas/def/v2/score.schema.json"
|
||||||
],
|
],
|
||||||
"type": Literal["score"],
|
"type": Literal["score"],
|
||||||
"version": Literal[2],
|
"version": Literal[2],
|
||||||
"scores": List[ArcaeaOfflineDEFV2_ScoreItem],
|
"scores": List[ArcaeaOfflineDEFv2PlayResultItem],
|
||||||
},
|
},
|
||||||
)
|
)
|
40
src/arcaea_offline/external/exporters/defv2/play_result.py
vendored
Normal file
40
src/arcaea_offline/external/exporters/defv2/play_result.py
vendored
Normal file
@ -0,0 +1,40 @@
|
|||||||
|
from typing import List
|
||||||
|
|
||||||
|
from arcaea_offline.database.models.v5 import PlayResult
|
||||||
|
|
||||||
|
from .definitions import (
|
||||||
|
ArcaeaOfflineDEFv2PlayResultItem,
|
||||||
|
ArcaeaOfflineDEFv2PlayResultRoot,
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
class ArcaeaOfflineDEFv2PlayResultExporter:
|
||||||
|
def export(self, items: List[PlayResult]) -> ArcaeaOfflineDEFv2PlayResultRoot:
|
||||||
|
export_items = []
|
||||||
|
for item in items:
|
||||||
|
export_item: ArcaeaOfflineDEFv2PlayResultItem = {}
|
||||||
|
|
||||||
|
export_item["id"] = item.id
|
||||||
|
export_item["songId"] = item.song_id
|
||||||
|
export_item["ratingClass"] = item.rating_class.value
|
||||||
|
export_item["score"] = item.score
|
||||||
|
export_item["pure"] = item.pure
|
||||||
|
export_item["far"] = item.far
|
||||||
|
export_item["lost"] = item.lost
|
||||||
|
export_item["date"] = item.date
|
||||||
|
export_item["maxRecall"] = item.max_recall
|
||||||
|
export_item["modifier"] = (
|
||||||
|
item.modifier.value if item.modifier is not None else None
|
||||||
|
)
|
||||||
|
export_item["clearType"] = (
|
||||||
|
item.clear_type.value if item.clear_type is not None else None
|
||||||
|
)
|
||||||
|
export_item["source"] = "https://arcaeaoffline.sevive.xyz/python"
|
||||||
|
export_item["comment"] = item.comment
|
||||||
|
|
||||||
|
return {
|
||||||
|
"$schema": "https://arcaeaoffline.sevive.xyz/schemas/def/v2/score.schema.json",
|
||||||
|
"type": "score",
|
||||||
|
"version": 2,
|
||||||
|
"scores": export_items,
|
||||||
|
}
|
75
src/arcaea_offline/external/exporters/smartrte.py
vendored
Normal file
75
src/arcaea_offline/external/exporters/smartrte.py
vendored
Normal file
@ -0,0 +1,75 @@
|
|||||||
|
from typing import List, Tuple
|
||||||
|
|
||||||
|
from sqlalchemy import func
|
||||||
|
from sqlalchemy.orm import Session
|
||||||
|
|
||||||
|
from arcaea_offline.constants.enums.arcaea import ArcaeaRatingClass
|
||||||
|
from arcaea_offline.database.models.v5 import (
|
||||||
|
ChartInfo,
|
||||||
|
Difficulty,
|
||||||
|
PlayResultBest,
|
||||||
|
Song,
|
||||||
|
)
|
||||||
|
from arcaea_offline.utils.formatters.rating_class import RatingClassFormatter
|
||||||
|
|
||||||
|
|
||||||
|
class SmartRteBest30CsvExporter:
|
||||||
|
CSV_ROWS = [
|
||||||
|
"SongName",
|
||||||
|
"SongId",
|
||||||
|
"Difficulty",
|
||||||
|
"Score",
|
||||||
|
"Perfect",
|
||||||
|
"Perfect+",
|
||||||
|
"Far",
|
||||||
|
"Lost",
|
||||||
|
"Constant",
|
||||||
|
"PlayRating",
|
||||||
|
]
|
||||||
|
|
||||||
|
@classmethod
|
||||||
|
def rows(cls, session: Session) -> List:
|
||||||
|
results: List[
|
||||||
|
Tuple[str, str, ArcaeaRatingClass, int, int, int, int, int, float]
|
||||||
|
] = (
|
||||||
|
session.query(
|
||||||
|
func.coalesce(Difficulty.title, Song.title),
|
||||||
|
PlayResultBest.song_id,
|
||||||
|
PlayResultBest.rating_class,
|
||||||
|
PlayResultBest.score,
|
||||||
|
PlayResultBest.pure,
|
||||||
|
PlayResultBest.shiny_pure,
|
||||||
|
PlayResultBest.far,
|
||||||
|
PlayResultBest.lost,
|
||||||
|
ChartInfo.constant,
|
||||||
|
PlayResultBest.potential,
|
||||||
|
)
|
||||||
|
.join(
|
||||||
|
ChartInfo,
|
||||||
|
(ChartInfo.song_id == PlayResultBest.song_id)
|
||||||
|
& (ChartInfo.rating_class == PlayResultBest.rating_class),
|
||||||
|
)
|
||||||
|
.join(Song, (Song.id == PlayResultBest.song_id))
|
||||||
|
.join(
|
||||||
|
Difficulty,
|
||||||
|
(Difficulty.song_id == PlayResultBest.song_id)
|
||||||
|
& (Difficulty.rating_class == PlayResultBest.rating_class),
|
||||||
|
)
|
||||||
|
.all()
|
||||||
|
)
|
||||||
|
|
||||||
|
csv_rows = []
|
||||||
|
csv_rows.append(cls.CSV_ROWS.copy())
|
||||||
|
for _result in results:
|
||||||
|
result = list(_result)
|
||||||
|
|
||||||
|
# replace the comma in song title because the target project
|
||||||
|
# cannot handle quoted string
|
||||||
|
result[0] = result[0].replace(",", "") # type: ignore
|
||||||
|
result[2] = RatingClassFormatter.name(result[2]) # type: ignore
|
||||||
|
result[-2] = result[-2] / 10 # type: ignore
|
||||||
|
result[-1] = round(result[-1], 5) # type: ignore
|
||||||
|
|
||||||
|
csv_rows.append(result)
|
||||||
|
|
||||||
|
return csv_rows
|
38
src/arcaea_offline/external/exports/exporters.py
vendored
38
src/arcaea_offline/external/exports/exporters.py
vendored
@ -1,38 +0,0 @@
|
|||||||
from arcaea_offline.database.models.v4 import Score
|
|
||||||
|
|
||||||
from .types import ArcaeaOfflineDEFV2_ScoreItem, ScoreExport
|
|
||||||
|
|
||||||
|
|
||||||
def score(score: Score) -> ScoreExport:
|
|
||||||
return {
|
|
||||||
"id": score.id,
|
|
||||||
"song_id": score.song_id,
|
|
||||||
"rating_class": score.rating_class,
|
|
||||||
"score": score.score,
|
|
||||||
"pure": score.pure,
|
|
||||||
"far": score.far,
|
|
||||||
"lost": score.lost,
|
|
||||||
"date": score.date,
|
|
||||||
"max_recall": score.max_recall,
|
|
||||||
"modifier": score.modifier,
|
|
||||||
"clear_type": score.clear_type,
|
|
||||||
"comment": score.comment,
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
def score_def_v2(score: Score) -> ArcaeaOfflineDEFV2_ScoreItem:
|
|
||||||
return {
|
|
||||||
"id": score.id,
|
|
||||||
"songId": score.song_id,
|
|
||||||
"ratingClass": score.rating_class,
|
|
||||||
"score": score.score,
|
|
||||||
"pure": score.pure,
|
|
||||||
"far": score.far,
|
|
||||||
"lost": score.lost,
|
|
||||||
"date": score.date,
|
|
||||||
"maxRecall": score.max_recall,
|
|
||||||
"modifier": score.modifier,
|
|
||||||
"clearType": score.clear_type,
|
|
||||||
"source": None,
|
|
||||||
"comment": score.comment,
|
|
||||||
}
|
|
@ -1,3 +0,0 @@
|
|||||||
from .b30_csv import SmartRteB30CsvConverter
|
|
||||||
|
|
||||||
__all__ = ["SmartRteB30CsvConverter"]
|
|
64
src/arcaea_offline/external/smartrte/b30_csv.py
vendored
64
src/arcaea_offline/external/smartrte/b30_csv.py
vendored
@ -1,64 +0,0 @@
|
|||||||
from sqlalchemy.orm import Session
|
|
||||||
|
|
||||||
from ...models import Chart, ScoreBest
|
|
||||||
from ...utils.rating import rating_class_to_text
|
|
||||||
|
|
||||||
|
|
||||||
class SmartRteB30CsvConverter:
|
|
||||||
CSV_ROWS = [
|
|
||||||
"songname",
|
|
||||||
"songId",
|
|
||||||
"Difficulty",
|
|
||||||
"score",
|
|
||||||
"Perfect",
|
|
||||||
"criticalPerfect",
|
|
||||||
"Far",
|
|
||||||
"Lost",
|
|
||||||
"Constant",
|
|
||||||
"singlePTT",
|
|
||||||
]
|
|
||||||
|
|
||||||
def __init__(
|
|
||||||
self,
|
|
||||||
session: Session,
|
|
||||||
):
|
|
||||||
self.session = session
|
|
||||||
|
|
||||||
def rows(self) -> list:
|
|
||||||
csv_rows = [self.CSV_ROWS.copy()]
|
|
||||||
|
|
||||||
with self.session as session:
|
|
||||||
results = (
|
|
||||||
session.query(
|
|
||||||
Chart.title,
|
|
||||||
ScoreBest.song_id,
|
|
||||||
ScoreBest.rating_class,
|
|
||||||
ScoreBest.score,
|
|
||||||
ScoreBest.pure,
|
|
||||||
ScoreBest.shiny_pure,
|
|
||||||
ScoreBest.far,
|
|
||||||
ScoreBest.lost,
|
|
||||||
Chart.constant,
|
|
||||||
ScoreBest.potential,
|
|
||||||
)
|
|
||||||
.join(
|
|
||||||
Chart,
|
|
||||||
(Chart.song_id == ScoreBest.song_id)
|
|
||||||
& (Chart.rating_class == ScoreBest.rating_class),
|
|
||||||
)
|
|
||||||
.all()
|
|
||||||
)
|
|
||||||
|
|
||||||
for result in results:
|
|
||||||
# replace the comma in song title because the target project
|
|
||||||
# cannot handle quoted string
|
|
||||||
result = list(result)
|
|
||||||
result[0] = result[0].replace(",", "")
|
|
||||||
result[2] = rating_class_to_text(result[2])
|
|
||||||
# divide constant to float
|
|
||||||
result[-2] = result[-2] / 10
|
|
||||||
# round potential
|
|
||||||
result[-1] = round(result[-1], 5)
|
|
||||||
csv_rows.append(result)
|
|
||||||
|
|
||||||
return csv_rows
|
|
@ -22,7 +22,6 @@ class RatingClassFormatter:
|
|||||||
"""
|
"""
|
||||||
Returns the capitalized rating class name, e.g. Future.
|
Returns the capitalized rating class name, e.g. Future.
|
||||||
"""
|
"""
|
||||||
...
|
|
||||||
|
|
||||||
@overload
|
@overload
|
||||||
@classmethod
|
@classmethod
|
||||||
@ -33,7 +32,6 @@ class RatingClassFormatter:
|
|||||||
The integer will be converted to `ArcaeaRatingClass` enum,
|
The integer will be converted to `ArcaeaRatingClass` enum,
|
||||||
and will return "Unknown" if the convertion fails.
|
and will return "Unknown" if the convertion fails.
|
||||||
"""
|
"""
|
||||||
...
|
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
def name(cls, rating_class: Any) -> NAME_FORMAT_RESULTS:
|
def name(cls, rating_class: Any) -> NAME_FORMAT_RESULTS:
|
||||||
@ -57,7 +55,6 @@ class RatingClassFormatter:
|
|||||||
"""
|
"""
|
||||||
Returns the uppercased rating class name, e.g. FTR.
|
Returns the uppercased rating class name, e.g. FTR.
|
||||||
"""
|
"""
|
||||||
...
|
|
||||||
|
|
||||||
@overload
|
@overload
|
||||||
@classmethod
|
@classmethod
|
||||||
@ -68,7 +65,6 @@ class RatingClassFormatter:
|
|||||||
The integer will be converted to `ArcaeaRatingClass` enum,
|
The integer will be converted to `ArcaeaRatingClass` enum,
|
||||||
and will return "UNK" if the convertion fails.
|
and will return "UNK" if the convertion fails.
|
||||||
"""
|
"""
|
||||||
...
|
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
def abbreviation(cls, rating_class: Any) -> ABBREVIATION_FORMAT_RESULTS:
|
def abbreviation(cls, rating_class: Any) -> ABBREVIATION_FORMAT_RESULTS:
|
||||||
|
Loading…
x
Reference in New Issue
Block a user