Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
The table of contents is too big for display.
Diff view
Diff view
  •  
  •  
  •  
350,663 changes: 331,101 additions & 19,562 deletions datasets/in_game/event_catalog.json

Large diffs are not rendered by default.

380,629 changes: 349,372 additions & 31,257 deletions datasets/in_game/events.json

Large diffs are not rendered by default.

105,587 changes: 105,118 additions & 469 deletions datasets/in_game/skills.json

Large diffs are not rendered by default.

50 changes: 25 additions & 25 deletions prefs/config.sample.json
Original file line number Diff line number Diff line change
Expand Up @@ -70,37 +70,37 @@
"supports": [
{
"slot": 0,
"name": "Silence Suzuka",
"name": "Silence Suzuka SPD SSR",
"rarity": "SSR",
"attribute": "SPD"
},
{
"slot": 1,
"name": "Nishino Flower",
"name": "Nishino Flower SPD SSR",
"rarity": "SSR",
"attribute": "SPD"
},
{
"slot": 2,
"name": "Gold Ship",
"name": "Gold Ship STA SSR",
"rarity": "SSR",
"attribute": "STA"
},
{
"slot": 3,
"name": "Tokai Teio",
"name": "Tokai Teio SPD SSR",
"rarity": "SSR",
"attribute": "SPD"
},
{
"slot": 4,
"name": "Mayano Top Gun",
"name": "Mayano Top Gun STA SR",
"rarity": "SR",
"attribute": "STA"
},
{
"slot": 5,
"name": "Kitasan Black",
"name": "Kitasan Black SPD SSR",
"rarity": "SSR",
"attribute": "SPD"
}
Expand All @@ -113,8 +113,8 @@
},
"prefs": {
"overrides": {
"support/Silence Suzuka/SPD/SSR/What Should I Do?#s1": 2,
"support/Kitasan Black/SPD/SSR/Ah, Home Sweet Home#s1": 1
"support/Silence Suzuka SPD SSR/SPD/SSR/What Should I Do?#s1": 2,
"support/Kitasan Black SPD SSR/SPD/SSR/Ah, Home Sweet Home#s1": 1
},
"patterns": [],
"defaults": {
Expand Down Expand Up @@ -168,31 +168,31 @@
"supports": [
{
"slot": 0,
"name": "Silence Suzuka",
"name": "Silence Suzuka SPD SSR",
"rarity": "SSR",
"attribute": "SPD"
},
{
"slot": 1,
"name": "Eishin Flash",
"name": "Eishin Flash SPD SR",
"rarity": "SR",
"attribute": "SPD"
},
{
"slot": 2,
"name": "Shinko Windy",
"name": "Shinko Windy SPD SR",
"rarity": "SR",
"attribute": "SPD"
},
{
"slot": 3,
"name": "Kitasan Black",
"name": "Kitasan Black SPD R",
"rarity": "R",
"attribute": "SPD"
},
{
"slot": 4,
"name": "Twin Turbo",
"name": "Twin Turbo SPD SSR",
"rarity": "SSR",
"attribute": "SPD"
},
Expand Down Expand Up @@ -264,37 +264,37 @@
"supports": [
{
"slot": 0,
"name": "Tokai Teio",
"name": "Tokai Teio SPD SSR",
"rarity": "SSR",
"attribute": "SPD"
},
{
"slot": 1,
"name": "Nishino Flower",
"name": "Nishino Flower SPD SSR",
"rarity": "SSR",
"attribute": "SPD"
},
{
"slot": 2,
"name": "Sweep Tosho",
"name": "Sweep Tosho SPD SR",
"rarity": "SR",
"attribute": "SPD"
},
{
"slot": 3,
"name": "Mayano Top Gun",
"name": "Mayano Top Gun STA SR",
"rarity": "SR",
"attribute": "STA"
},
{
"slot": 4,
"name": "Zenno Rob Roy",
"name": "Zenno Rob Roy STA SR",
"rarity": "SR",
"attribute": "STA"
},
{
"slot": 5,
"name": "Kitasan Black",
"name": "Kitasan Black SPD SSR",
"rarity": "SSR",
"attribute": "SPD"
}
Expand All @@ -307,9 +307,9 @@
},
"prefs": {
"overrides": {
"support/Tokai Teio/SPD/SSR/My Weapon#s1": 2,
"support/Sweep Tosho/SPD/SR/Premeditated Mischief#s2": 1,
"support/Kitasan Black/SPD/SSR/Ah, Home Sweet Home#s1": 1
"support/Tokai Teio SPD SSR/SPD/SSR/My Weapon#s1": 2,
"support/Sweep Tosho SPD SR/SPD/SR/Premeditated Mischief#s2": 1,
"support/Kitasan Black SPD SSR/SPD/SSR/Ah, Home Sweet Home#s1": 1
},
"patterns": [],
"defaults": {
Expand Down Expand Up @@ -411,13 +411,13 @@
"supports": [
{
"slot": 0,
"name": "Aoi Kiryuuin",
"name": "Aoi Kiryuin PAL SR",
"rarity": "SR",
"attribute": "PAL"
},
{
"slot": 1,
"name": "Mejiro Dober",
"name": "Mejiro Dober WIT SSR",
"rarity": "SSR",
"attribute": "WIT"
},
Expand Down Expand Up @@ -445,4 +445,4 @@
}
],
"activePresetId": "930e0374-d039-4e99-8b89-d34442fd3050"
}
}
159 changes: 156 additions & 3 deletions server/utils.py
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,11 @@
SAMPLE_NAV_PATH = PREFS_DIR / "nav.sample.json"

_DATASET_CACHE: Dict[str, Tuple[float, object]] = {}
_VALID_SUPPORT_RARITIES = {"SSR", "SR", "R"}
_VALID_SUPPORT_ATTRIBUTES = {"SPD", "STA", "PWR", "GUTS", "WIT", "PAL"}
_LEGACY_SUPPORT_NAME_ALIASES = {
"Aoi Kiryuuin": "Aoi Kiryuin",
}


def _repo_root() -> Path:
Expand Down Expand Up @@ -86,17 +91,160 @@ def _ensure_bool(value: Any, default: bool = True) -> bool:
return bool(value)
return default

def _collapse_spaces(value: str) -> str:
return " ".join(value.strip().split())

def _apply_legacy_support_alias(name: str) -> str:
normalized = _collapse_spaces(name)
for legacy, canonical in _LEGACY_SUPPORT_NAME_ALIASES.items():
if normalized == legacy or normalized.startswith(f"{legacy} "):
return f"{canonical}{normalized[len(legacy):]}"
return normalized

def _canonicalize_support_name(name: Any, rarity: Any, attribute: Any) -> Optional[str]:
if not isinstance(name, str):
return None

normalized = _apply_legacy_support_alias(name)
if not normalized:
return None

if not isinstance(rarity, str) or not isinstance(attribute, str):
return normalized

attr = attribute.strip().upper()
rar = rarity.strip().upper()
if attr not in _VALID_SUPPORT_ATTRIBUTES or rar not in _VALID_SUPPORT_RARITIES:
return normalized

suffix = f"{attr} {rar}"
if normalized.endswith(suffix) or normalized.endswith(f"{suffix}(Duplicate)"):
return normalized

return f"{normalized} {suffix}"

def _canonicalize_support_event_key(key: Any) -> Any:
if not isinstance(key, str):
return key

parts = key.split("/", 4)
if len(parts) != 5 or parts[0] != "support":
return key

canonical_name = _canonicalize_support_name(parts[1], parts[3], parts[2])
if not canonical_name:
return key

return f"support/{canonical_name}/{parts[2]}/{parts[3]}/{parts[4]}"

def _canonicalize_event_setup(raw: Any) -> Any:
if not isinstance(raw, dict):
return raw

result = dict(raw)
supports = raw.get("supports")
if isinstance(supports, list):
normalized_supports = []
for entry in supports:
if not isinstance(entry, dict):
normalized_supports.append(entry)
continue

support = dict(entry)
canonical_name = _canonicalize_support_name(
support.get("name"),
support.get("rarity"),
support.get("attribute"),
)
if canonical_name:
support["name"] = canonical_name
normalized_supports.append(support)

result["supports"] = normalized_supports

prefs = raw.get("prefs")
if not isinstance(prefs, dict):
return result

prefs_out = dict(prefs)
overrides = prefs.get("overrides")
if isinstance(overrides, dict):
prefs_out["overrides"] = {
_canonicalize_support_event_key(key): value
for key, value in overrides.items()
}

patterns = prefs.get("patterns")
if isinstance(patterns, list):
normalized_patterns = []
for entry in patterns:
if not isinstance(entry, dict):
normalized_patterns.append(entry)
continue
pattern = entry.get("pattern")
normalized_patterns.append(
{
**entry,
"pattern": _canonicalize_support_event_key(pattern),
}
if isinstance(pattern, str)
else entry
)
prefs_out["patterns"] = normalized_patterns

result["prefs"] = prefs_out
return result

def _canonicalize_config_event_setups(data: Any) -> Any:
if not isinstance(data, dict):
return data

result = dict(data)
presets = result.get("presets")
if isinstance(presets, list):
result["presets"] = [
{**preset, "event_setup": _canonicalize_event_setup(preset.get("event_setup"))}
if isinstance(preset, dict)
else preset
for preset in presets
]

scenarios = result.get("scenarios")
if not isinstance(scenarios, dict):
return result

normalized_scenarios = {}
for key, branch in scenarios.items():
if not isinstance(branch, dict):
normalized_scenarios[key] = branch
continue

branch_out = dict(branch)
branch_presets = branch.get("presets")
if isinstance(branch_presets, list):
branch_out["presets"] = [
{**preset, "event_setup": _canonicalize_event_setup(preset.get("event_setup"))}
if isinstance(preset, dict)
else preset
for preset in branch_presets
]
normalized_scenarios[key] = branch_out

result["scenarios"] = normalized_scenarios
return result

def _normalize_support(entry: Any, slot: int, fallback_priority: Optional[List[str]] = None) -> Optional[Dict[str, Any]]:
if not isinstance(entry, dict):
return None
name = entry.get("name")
rarity = entry.get("rarity")
attribute = entry.get("attribute")
if not (isinstance(name, str) and isinstance(rarity, str) and isinstance(attribute, str)):
canonical_name = _canonicalize_support_name(name, rarity, attribute)
if not (canonical_name and isinstance(rarity, str) and isinstance(attribute, str)):
return None
result = {
"slot": slot,
"name": name,
"name": canonical_name,
"rarity": rarity,
"attribute": attribute,
}
Expand Down Expand Up @@ -132,6 +280,8 @@ def _normalize_entity(entry: Any, fallback_priority: Optional[List[str]] = None)
def load_event_setup_defaults(raw: Any) -> Dict[str, Any]:
if not isinstance(raw, dict):
raw = {}
else:
raw = _canonicalize_event_setup(raw)

prefs_raw = raw.get("prefs")
prefs_in = prefs_raw if isinstance(prefs_raw, dict) else {}
Expand Down Expand Up @@ -186,6 +336,8 @@ def load_config() -> dict:
if not isinstance(data, dict):
data = {}

data = _canonicalize_config_event_setups(data)

general = data.get("general")
if not isinstance(general, dict):
general = {}
Expand Down Expand Up @@ -251,8 +403,9 @@ def load_config() -> dict:


def save_config(data: dict):
normalized = _canonicalize_config_event_setups(data)
with open(CONFIG_PATH, "w") as f:
json.dump(data, f, indent=2)
json.dump(normalized, f, indent=2)


def load_nav_prefs() -> Dict[str, Dict[str, Any]]:
Expand Down
Loading