Dev: Extract map worlds and planes, add extract framework

This commit is contained in:
LmeSzinc 2024-02-07 02:32:40 +08:00
parent 94b7bc4566
commit 1e79353294
9 changed files with 501 additions and 133 deletions

View File

@ -3,26 +3,15 @@ import os
import re
import typing as t
from collections import defaultdict
from functools import cache, cached_property
from functools import cache
from hashlib import md5
from dev_tools.keywords.base import TextMap, UI_LANGUAGES, replace_templates, text_to_variable
from module.base.code_generator import CodeGenerator
from module.config.utils import deep_get, read_file
from module.exception import ScriptError
from module.logger import logger
UI_LANGUAGES = ['cn', 'cht', 'en', 'jp', 'es']
def text_to_variable(text):
text = re.sub("'s |s' ", '_', text)
text = re.sub('[ \-—:\'/•.]+', '_', text)
text = re.sub(r'[(),#"?!&%*]|</?\w+>', '', text)
# text = re.sub(r'[#_]?\d+(_times?)?', '', text)
text = re.sub(r'<color=#?\w+>', '', text)
text = text.replace('é', 'e')
return text.strip('_')
def dungeon_name(name: str) -> str:
name = text_to_variable(name)
@ -61,72 +50,6 @@ def convert_inner_character_to_keyword(name):
return convert_dict.get(name, name)
class TextMap:
DATA_FOLDER = ''
def __init__(self, lang: str):
self.lang = lang
def __contains__(self, name: t.Union[int, str]) -> bool:
if isinstance(name, int) or (isinstance(name, str) and name.isdigit()):
return int(name) in self.data
return False
@cached_property
def data(self) -> dict[int, str]:
if not os.path.exists(TextMap.DATA_FOLDER):
logger.critical('`TextMap.DATA_FOLDER` does not exist, please set it to your path to StarRailData')
exit(1)
file = os.path.join(TextMap.DATA_FOLDER, 'TextMap', f'TextMap{self.lang.upper()}.json')
data = {}
for id_, text in read_file(file).items():
text = text.replace('\u00A0', '')
text = text.replace(r'{NICKNAME}', 'Trailblazer')
data[int(id_)] = text
return data
def find(self, name: t.Union[int, str]) -> tuple[int, str]:
"""
Args:
name:
Returns:
text id (hash in TextMap)
text
"""
if isinstance(name, int) or (isinstance(name, str) and name.isdigit()):
name = int(name)
try:
return name, self.data[name]
except KeyError:
pass
name = str(name)
for row_id, row_name in self.data.items():
if row_id >= 0 and row_name == name:
return row_id, row_name
for row_id, row_name in self.data.items():
if row_name == name:
return row_id, row_name
logger.error(f'Cannot find name: "{name}" in language {self.lang}')
return 0, ''
def replace_templates(text: str) -> str:
"""
Replace templates in data to make sure it equals to what is shown in game
Examples:
replace_templates("Complete Echo of War #4 time(s)")
== "Complete Echo of War 1 time(s)"
"""
text = re.sub(r'#4', '1', text)
text = re.sub(r'</?\w+>', '', text)
text = re.sub(r'<color=#?\w+>', '', text)
text = re.sub(r'{.*?}', '', text)
return text
class KeywordExtract:
def __init__(self):
self.text_map: dict[str, TextMap] = {lang: TextMap(lang) for lang in UI_LANGUAGES}
@ -438,32 +361,10 @@ class KeywordExtract:
self.write_keywords(keyword_class=class_name, output_file=output_file)
def generate_map_planes(self):
planes = {
'Special': ['黑塔的办公室', '锋芒崭露'],
'Rogue': [ '区域-战斗', '区域-事件', '区域-遭遇', '区域-休整', '区域-精英', '区域-首领', '区域-交易'],
'Herta': ['观景车厢', '主控舱段', '基座舱段', '收容舱段', '支援舱段', '禁闭舱段'],
'Jarilo': ['行政区', '城郊雪原', '边缘通路', '铁卫禁区', '残响回廊', '永冬岭',
'造物之柱', '旧武器试验场', '磐岩镇', '大矿区', '铆钉镇', '机械聚落'],
'Luofu': ['星槎海中枢', '流云渡', '迴星港', '长乐天', '金人巷', '太卜司',
'工造司', '绥园', '丹鼎司', '鳞渊境'],
}
def text_convert(world_):
def text_convert_wrapper(name):
name = text_to_variable(name).replace('_', '')
name = f'{world_}_{name}'
return name
return text_convert_wrapper
gen = None
for world, plane in planes.items():
self.load_keywords(plane)
gen = self.write_keywords(keyword_class='MapPlane', output_file='',
text_convert=text_convert(world), generator=gen)
gen.write('./tasks/map/keywords/plane.py')
self.load_keywords(['Herta Space Station', 'Jarilo-VI', 'The Xianzhou Luofu'], lang='en')
self.write_keywords(keyword_class='MapWorld', output_file='./tasks/map/keywords/world.py')
from dev_tools.keywords.map_world import GenerateMapWorld
GenerateMapWorld()()
from dev_tools.keywords.map_plane import GenerateMapPlane
GenerateMapPlane()()
def generate_character_keywords(self):
self.load_character_name_keywords()

184
dev_tools/keywords/base.py Normal file
View File

@ -0,0 +1,184 @@
import os
import re
import typing as t
from functools import cached_property
from module.base.code_generator import CodeGenerator
from module.config.utils import read_file
from module.logger import logger
UI_LANGUAGES = ['cn', 'cht', 'en', 'jp', 'es']
class TextMap:
DATA_FOLDER = ''
def __init__(self, lang: str):
self.lang = lang
def __contains__(self, name: t.Union[int, str]) -> bool:
if isinstance(name, int) or (isinstance(name, str) and name.isdigit()):
return int(name) in self.data
return False
@cached_property
def data(self) -> dict[int, str]:
if not os.path.exists(TextMap.DATA_FOLDER):
logger.critical('`TextMap.DATA_FOLDER` does not exist, please set it to your path to StarRailData')
exit(1)
file = os.path.join(TextMap.DATA_FOLDER, 'TextMap', f'TextMap{self.lang.upper()}.json')
data = {}
for id_, text in read_file(file).items():
text = text.replace('\u00A0', '')
text = text.replace(r'{NICKNAME}', 'Trailblazer')
data[int(id_)] = text
return data
def find(self, name: t.Union[int, str]) -> tuple[int, str]:
"""
Args:
name:
Returns:
text id (hash in TextMap)
text
"""
if isinstance(name, int) or (isinstance(name, str) and name.isdigit()):
name = int(name)
try:
return name, self.data[name]
except KeyError:
pass
name = str(name)
for row_id, row_name in self.data.items():
if row_id >= 0 and row_name == name:
return row_id, row_name
for row_id, row_name in self.data.items():
if row_name == name:
return row_id, row_name
logger.error(f'Cannot find name: "{name}" in language {self.lang}')
return 0, ''
def text_to_variable(text):
text = re.sub("'s |s' ", '_', text)
text = re.sub(r'[ \-—:\'/•.]+', '_', text)
text = re.sub(r'[(),#"?!&%*]|</?\w+>', '', text)
# text = re.sub(r'[#_]?\d+(_times?)?', '', text)
text = re.sub(r'<color=#?\w+>', '', text)
text = text.replace('é', 'e')
return text.strip('_')
def replace_templates(text: str) -> str:
"""
Replace templates in data to make sure it equals to what is shown in game
Examples:
replace_templates("Complete Echo of War #4 time(s)")
== "Complete Echo of War 1 time(s)"
"""
text = re.sub(r'#4', '1', text)
text = re.sub(r'</?\w+>', '', text)
text = re.sub(r'<color=#?\w+>', '', text)
text = re.sub(r'{.*?}', '', text)
return text
class GenerateKeyword:
text_map: dict[str, TextMap] = {lang: TextMap(lang) for lang in UI_LANGUAGES}
text_map['cn'] = TextMap('chs')
@staticmethod
def read_file(file: str) -> dict:
"""
Args:
file: ./ExcelOutput/GameplayGuideData.json
Returns:
dict:
"""
file = os.path.join(TextMap.DATA_FOLDER, file)
return read_file(file)
@classmethod
def find_keyword(cls, keyword, lang) -> tuple[int, str]:
"""
Args:
keyword: text string or text id
lang: Language to find
Returns:
text id (hash in TextMap)
text
"""
text_map = cls.text_map[lang]
return text_map.find(keyword)
output_file = ''
def __init__(self):
self.gen = CodeGenerator()
self.keyword_class = self.__class__.__name__.removeprefix('Generate')
self.keyword_index = 0
self.keyword_format = {
'id': 0,
'name': 'Unnamed_Keyword'
}
for lang in UI_LANGUAGES:
self.keyword_format[lang] = ''
def gen_import(self):
self.gen.Import(
f"""
from .classes import {self.keyword_class}
"""
)
def iter_keywords(self) -> t.Iterable[dict]:
"""
Yields
dict: {'text_id': 123456, 'any_attr': 1}
"""
pass
def convert_name(self, text: str, keyword: dict) -> str:
return text_to_variable(text)
def format_keywords(self, keyword: dict) -> dict | None:
base = self.keyword_format.copy()
text_id = keyword.pop('text_id')
if text_id is None:
return
# id
self.keyword_index += 1
base['id'] = self.keyword_index
# Attrs
base.update(keyword)
# Name
_, name = self.find_keyword(text_id, lang='en')
name = self.convert_name(replace_templates(name), keyword=base)
base['name'] = name
# Translations
for lang in UI_LANGUAGES:
value = replace_templates(self.find_keyword(text_id, lang=lang)[1])
base[lang] = value
return base
def generate(self):
self.gen_import()
self.gen.CommentAutoGenerage('dev_tools.keyword_extract')
for keyword in self.iter_keywords():
keyword = self.format_keywords(keyword)
with self.gen.Object(key=keyword['name'], object_class=self.keyword_class):
for key, value in keyword.items():
self.gen.ObjectAttr(key, value)
if self.output_file:
print(f'Write {self.output_file}')
self.gen.write(self.output_file)
def __call__(self, *args, **kwargs):
self.generate()

View File

@ -0,0 +1,70 @@
import typing as t
from dev_tools.keywords.base import GenerateKeyword
from module.base.decorator import cached_property
from module.config.utils import deep_get
class GenerateMapPlane(GenerateKeyword):
output_file = './tasks/map/keywords/plane.py'
@cached_property
def data(self):
return self.read_file('./ExcelOutput/AreaMapConfig.json')
def iter_planes(self) -> t.Iterable[dict]:
for plane_id, data in self.data.items():
plane_id = int(plane_id)
world_id = int(str(plane_id)[-5])
sort_id = int(deep_get(data, 'MenuSortID', 0))
text_id = deep_get(data, 'Name.Hash')
yield dict(
text_id=text_id,
world_id=world_id,
plane_id=plane_id,
sort_id=sort_id,
)
def iter_keywords(self) -> t.Iterable[dict]:
"""
1010201
^^ floor
^^ plane
^ world
"""
def to_id(name):
return self.find_keyword(name, lang='cn')[0]
domains = ['黑塔的办公室', '锋芒崭露']
for index, domain in enumerate(domains):
yield dict(
text_id=to_id(domain),
world_id=-1,
plane_id=index + 1,
)
domains = ['区域-战斗', '区域-事件', '区域-遭遇', '区域-休整', '区域-精英', '区域-首领', '区域-交易']
for index, domain in enumerate(domains):
yield dict(
text_id=to_id(domain),
world_id=-2,
plane_id=index + 1,
)
keywords = sorted(self.iter_planes(), key=lambda x: (x['world_id'], x['sort_id']))
for keyword in keywords:
keyword.pop('sort_id')
yield keyword
def convert_name(self, text: str, keyword: dict) -> str:
text = super().convert_name(text, keyword=keyword)
text = text.replace('_', '')
from tasks.map.keywords import MapWorld
world = MapWorld.find_world_id(keyword['world_id'])
if world is None:
if text.startswith('Domain'):
return f'Rogue_{text}'
else:
return f'Special_{text}'
else:
return f'{world.short_name}_{text}'

View File

@ -0,0 +1,33 @@
import typing as t
from dev_tools.keywords.base import GenerateKeyword
class GenerateMapWorld(GenerateKeyword):
output_file = './tasks/map/keywords/world.py'
def iter_keywords(self) -> t.Iterable[dict]:
def to_id(name):
return self.find_keyword(name, lang='en')[0]
yield dict(
text_id=to_id('Herta Space Station'),
world_id=0,
short_name='Herta'
)
yield dict(
text_id=to_id('Jarilo-VI'),
world_id=1,
short_name='Jarilo'
)
yield dict(
text_id=to_id('The Xianzhou Luofu'),
world_id=2,
short_name='Luofu'
)
yield dict(
text_id=to_id('Penacony'),
world_id=3,
short_name='Penacony'
)

View File

@ -17,11 +17,11 @@ FLOOR_BUTTONS = [FLOOR_1, FLOOR_2, FLOOR_3]
def world_entrance(plane: MapPlane) -> ButtonWrapper:
if plane.is_HertaSpaceStation:
if plane.world.is_Herta:
return WORLD_HERTA
if plane.is_JariloVI:
if plane.world.is_Jarilo:
return WORLD_JARILO
if plane.is_Luofu:
if plane.world.is_Luofu:
return WORLD_LUOFU
raise ScriptError(f'world_entrance() got unknown plane: {plane}')

View File

@ -10,6 +10,11 @@ from module.ocr.keyword import Keyword
class MapPlane(Keyword):
instances: ClassVar = {}
# 0, 1, 2, 3
world_id: int
# 1010201
plane_id: int
# Map floors, 'F1' by default
# Example: ['B1', 'F1', 'F2']
floors = ['F1']
@ -18,33 +23,27 @@ class MapPlane(Keyword):
# 'top' or 'bottom'
page = 'top'
@classmethod
def find_plane_id(cls, plane_id):
"""
Args:
plane_id:
Returns:
MapPlane: MapPlane object or None
"""
for instance in cls.instances.values():
if instance.plane_id == plane_id:
return instance
return None
@cached_property
def world(self) -> str:
def world(self) -> "MapWorld":
"""
Returns:
str: World name. Note that "Parlor Car" is considered as a plane of Herta.
"Herta" for Herta Space Station
"Jarilo" for Jarilo-VI
"Luofu" for The Xianzhou Luofu
"" for unknown
MapWorld: MapWorld object or None
"""
for world in ['Herta', 'Jarilo', 'Luofu']:
if self.name.startswith(world):
return world
return ''
@cached_property
def is_HertaSpaceStation(self):
return self.world == 'Herta'
@cached_property
def is_JariloVI(self):
return self.world == 'Jarilo'
@cached_property
def is_Luofu(self):
return self.world == 'Luofu'
return MapWorld.find_world_id(self.world_id)
@cached_property
def has_multiple_floors(self):
@ -135,3 +134,38 @@ class MapPlane(Keyword):
@dataclass(repr=False)
class MapWorld(Keyword):
instances: ClassVar = {}
# 0, 1, 2, 3
world_id: int
# Herta
short_name: str
@classmethod
def find_world_id(cls, world_id):
"""
Args:
world_id:
Returns:
MapWorld: MapWorld object or None
"""
for instance in cls.instances.values():
if instance.world_id == world_id:
return instance
return None
@cached_property
def is_Herta(self):
return self.short_name == 'Herta'
@cached_property
def is_Jarilo(self):
return self.short_name == 'Jarilo'
@cached_property
def is_Luofu(self):
return self.short_name == 'Luofu'
@cached_property
def is_Penacony(self):
return self.short_name == 'Penacony'

View File

@ -11,6 +11,8 @@ Special_HertaOffice = MapPlane(
en="Herta's Office",
jp='ヘルタのオフィス',
es='Oficina de Herta',
world_id=-1,
plane_id=1,
)
Special_AptitudeShowcase = MapPlane(
id=2,
@ -20,6 +22,8 @@ Special_AptitudeShowcase = MapPlane(
en='Aptitude Showcase',
jp='躍進する新星',
es='Demostración de aptitudes',
world_id=-1,
plane_id=2,
)
Rogue_DomainCombat = MapPlane(
id=3,
@ -29,6 +33,8 @@ Rogue_DomainCombat = MapPlane(
en='Domain — Combat',
jp='エリア-戦闘',
es='Batalla',
world_id=-2,
plane_id=1,
)
Rogue_DomainOccurrence = MapPlane(
id=4,
@ -38,6 +44,8 @@ Rogue_DomainOccurrence = MapPlane(
en='Domain — Occurrence',
jp='エリア-イベント',
es='Evento',
world_id=-2,
plane_id=2,
)
Rogue_DomainEncounter = MapPlane(
id=5,
@ -47,6 +55,8 @@ Rogue_DomainEncounter = MapPlane(
en='Domain — Encounter',
jp='エリア-遭遇',
es='Encuentro',
world_id=-2,
plane_id=3,
)
Rogue_DomainRespite = MapPlane(
id=6,
@ -56,6 +66,8 @@ Rogue_DomainRespite = MapPlane(
en='Domain — Respite',
jp='エリア-休憩',
es='Reposo',
world_id=-2,
plane_id=4,
)
Rogue_DomainElite = MapPlane(
id=7,
@ -65,6 +77,8 @@ Rogue_DomainElite = MapPlane(
en='Domain — Elite',
jp='エリア-精鋭',
es='Élite',
world_id=-2,
plane_id=5,
)
Rogue_DomainBoss = MapPlane(
id=8,
@ -74,6 +88,8 @@ Rogue_DomainBoss = MapPlane(
en='Domain — Boss',
jp='エリア-ボス',
es='Jefe',
world_id=-2,
plane_id=6,
)
Rogue_DomainTransaction = MapPlane(
id=9,
@ -83,6 +99,8 @@ Rogue_DomainTransaction = MapPlane(
en='Domain — Transaction',
jp='エリア-取引',
es='Transacción',
world_id=-2,
plane_id=7,
)
Herta_ParlorCar = MapPlane(
id=10,
@ -92,6 +110,8 @@ Herta_ParlorCar = MapPlane(
en='Parlor Car',
jp='列車のラウンジ',
es='Vagón panorámico',
world_id=0,
plane_id=1000001,
)
Herta_MasterControlZone = MapPlane(
id=11,
@ -101,6 +121,8 @@ Herta_MasterControlZone = MapPlane(
en='Master Control Zone',
jp='主制御部分',
es='Zona de mando principal',
world_id=0,
plane_id=1000101,
)
Herta_BaseZone = MapPlane(
id=12,
@ -110,6 +132,8 @@ Herta_BaseZone = MapPlane(
en='Base Zone',
jp='ベース部分',
es='Zona de la base',
world_id=0,
plane_id=2000101,
)
Herta_StorageZone = MapPlane(
id=13,
@ -119,6 +143,8 @@ Herta_StorageZone = MapPlane(
en='Storage Zone',
jp='収容部分',
es='Zona de almacenamiento',
world_id=0,
plane_id=2000201,
)
Herta_SupplyZone = MapPlane(
id=14,
@ -128,6 +154,8 @@ Herta_SupplyZone = MapPlane(
en='Supply Zone',
jp='サポート部分',
es='Zona de suministros',
world_id=0,
plane_id=2000301,
)
Herta_SeclusionZone = MapPlane(
id=15,
@ -137,6 +165,8 @@ Herta_SeclusionZone = MapPlane(
en='Seclusion Zone',
jp='封鎖部分',
es='Zona de confinamiento',
world_id=0,
plane_id=2000401,
)
Jarilo_AdministrativeDistrict = MapPlane(
id=16,
@ -146,6 +176,8 @@ Jarilo_AdministrativeDistrict = MapPlane(
en='Administrative District',
jp='行政区',
es='Distrito administrativo',
world_id=1,
plane_id=1010101,
)
Jarilo_OutlyingSnowPlains = MapPlane(
id=17,
@ -155,6 +187,8 @@ Jarilo_OutlyingSnowPlains = MapPlane(
en='Outlying Snow Plains',
jp='郊外雪原',
es='Llanuras nevadas de las afueras',
world_id=1,
plane_id=2010101,
)
Jarilo_BackwaterPass = MapPlane(
id=18,
@ -164,6 +198,8 @@ Jarilo_BackwaterPass = MapPlane(
en='Backwater Pass',
jp='外縁通路',
es='Paso del Remanso',
world_id=1,
plane_id=2011101,
)
Jarilo_SilvermaneGuardRestrictedZone = MapPlane(
id=19,
@ -173,6 +209,8 @@ Jarilo_SilvermaneGuardRestrictedZone = MapPlane(
en='Silvermane Guard Restricted Zone',
jp='シルバーメイン禁区',
es='Zona restringida de la Guardia Crinargenta',
world_id=1,
plane_id=2013101,
)
Jarilo_CorridorofFadingEchoes = MapPlane(
id=20,
@ -182,6 +220,8 @@ Jarilo_CorridorofFadingEchoes = MapPlane(
en='Corridor of Fading Echoes',
jp='残響回廊',
es='Pasadizo de los ecos apagados',
world_id=1,
plane_id=2013201,
)
Jarilo_EverwinterHill = MapPlane(
id=21,
@ -191,6 +231,8 @@ Jarilo_EverwinterHill = MapPlane(
en='Everwinter Hill',
jp='常冬峰',
es='Colina del Siempreinvierno',
world_id=1,
plane_id=2013401,
)
Jarilo_PillarsofCreation = MapPlane(
id=22,
@ -200,6 +242,8 @@ Jarilo_PillarsofCreation = MapPlane(
en='Pillars of Creation',
jp='造物の柱',
es='Pilares de la Creación',
world_id=1,
plane_id=2013501,
)
Jarilo_OldWeaponTestingGround = MapPlane(
id=23,
@ -209,6 +253,8 @@ Jarilo_OldWeaponTestingGround = MapPlane(
en='Old Weapon Testing Ground',
jp='旧武器実験場',
es='Antiguo campo de prueba de armas',
world_id=1,
plane_id=2013601,
)
Jarilo_BoulderTown = MapPlane(
id=24,
@ -218,6 +264,8 @@ Jarilo_BoulderTown = MapPlane(
en='Boulder Town',
jp='ボルダータウン',
es='Villarroca',
world_id=1,
plane_id=1010201,
)
Jarilo_GreatMine = MapPlane(
id=25,
@ -227,6 +275,8 @@ Jarilo_GreatMine = MapPlane(
en='Great Mine',
jp='大鉱区',
es='Mina principal',
world_id=1,
plane_id=2012101,
)
Jarilo_RivetTown = MapPlane(
id=26,
@ -236,6 +286,8 @@ Jarilo_RivetTown = MapPlane(
en='Rivet Town',
jp='リベットタウン',
es='Villarremache',
world_id=1,
plane_id=2012201,
)
Jarilo_RobotSettlement = MapPlane(
id=27,
@ -245,6 +297,8 @@ Jarilo_RobotSettlement = MapPlane(
en='Robot Settlement',
jp='機械集落',
es='Asentamiento robot',
world_id=1,
plane_id=2012301,
)
Luofu_CentralStarskiffHaven = MapPlane(
id=28,
@ -254,6 +308,8 @@ Luofu_CentralStarskiffHaven = MapPlane(
en='Central Starskiff Haven',
jp='星槎海中枢',
es='Zona central de la Dársena de astroesquifes',
world_id=2,
plane_id=1020101,
)
Luofu_Cloudford = MapPlane(
id=29,
@ -263,6 +319,8 @@ Luofu_Cloudford = MapPlane(
en='Cloudford',
jp='流雲渡し',
es='Vado de las Nubes',
world_id=2,
plane_id=2021101,
)
Luofu_StargazerNavalia = MapPlane(
id=30,
@ -272,6 +330,8 @@ Luofu_StargazerNavalia = MapPlane(
en='Stargazer Navalia',
jp='廻星港',
es='Puerto Miraestrellas',
world_id=2,
plane_id=2021201,
)
Luofu_ExaltingSanctum = MapPlane(
id=31,
@ -281,6 +341,8 @@ Luofu_ExaltingSanctum = MapPlane(
en='Exalting Sanctum',
jp='長楽天',
es='Sánctum de la Exaltación',
world_id=2,
plane_id=1020201,
)
Luofu_AurumAlley = MapPlane(
id=32,
@ -290,6 +352,8 @@ Luofu_AurumAlley = MapPlane(
en='Aurum Alley',
jp='金人巷',
es='Callejón Aurum',
world_id=2,
plane_id=1020204,
)
Luofu_DivinationCommission = MapPlane(
id=33,
@ -299,6 +363,8 @@ Luofu_DivinationCommission = MapPlane(
en='Divination Commission',
jp='太卜司',
es='Comisión de Adivinación',
world_id=2,
plane_id=2022101,
)
Luofu_ArtisanshipCommission = MapPlane(
id=34,
@ -308,6 +374,8 @@ Luofu_ArtisanshipCommission = MapPlane(
en='Artisanship Commission',
jp='工造司',
es='Comisión de Artesanía',
world_id=2,
plane_id=2022201,
)
Luofu_FyxestrollGarden = MapPlane(
id=35,
@ -317,6 +385,8 @@ Luofu_FyxestrollGarden = MapPlane(
en='Fyxestroll Garden',
jp='綏園',
es='Jardín del Sosiego',
world_id=2,
plane_id=2022301,
)
Luofu_AlchemyCommission = MapPlane(
id=36,
@ -326,6 +396,8 @@ Luofu_AlchemyCommission = MapPlane(
en='Alchemy Commission',
jp='丹鼎司',
es='Comisión de Alquimia',
world_id=2,
plane_id=2023101,
)
Luofu_ScalegorgeWaterscape = MapPlane(
id=37,
@ -335,4 +407,61 @@ Luofu_ScalegorgeWaterscape = MapPlane(
en='Scalegorge Waterscape',
jp='鱗淵境',
es='Desfiladero de Escamas',
world_id=2,
plane_id=2023201,
)
Penacony_TheReverieReality = MapPlane(
id=38,
name='Penacony_TheReverieReality',
cn='「白日梦」酒店-现实',
cht='「白日夢」飯店-現實',
en='The Reverie (Reality)',
jp='ホテル・レバリー-現実',
es='Hotel Fantasía (realidad)',
world_id=3,
plane_id=1030501,
)
Penacony_GoldenHour = MapPlane(
id=39,
name='Penacony_GoldenHour',
cn='黄金的时刻',
cht='黃金的時刻',
en='Golden Hour',
jp='黄金の刻',
es='Momento Dorado',
world_id=3,
plane_id=1030101,
)
Penacony_DreamEdge = MapPlane(
id=40,
name='Penacony_DreamEdge',
cn='筑梦边境',
cht='築夢邊境',
en="Dream's Edge",
jp='ドリームボーダー',
es='Frontera de los Sueños',
world_id=3,
plane_id=2031301,
)
Penacony_AChildDream = MapPlane(
id=41,
name='Penacony_AChildDream',
cn='稚子的梦',
cht='稚子的夢',
en="A Child's Dream",
jp='稚児の夢',
es='Sueño infantil',
world_id=3,
plane_id=2031201,
)
Penacony_TheReverieDreamscape = MapPlane(
id=42,
name='Penacony_TheReverieDreamscape',
cn='「白日梦」酒店-梦境',
cht='「白日夢」飯店-夢境',
en='The Reverie (Dreamscape)',
jp='ホテル・レバリー-夢境',
es='Hotel Fantasía (paisaje onírico)',
world_id=3,
plane_id=2031101,
)

View File

@ -11,6 +11,8 @@ Herta_Space_Station = MapWorld(
en='Herta Space Station',
jp='宇宙ステーション「ヘルタ」',
es='Estación Espacial Herta',
world_id=0,
short_name='Herta',
)
Jarilo_VI = MapWorld(
id=2,
@ -20,6 +22,8 @@ Jarilo_VI = MapWorld(
en='Jarilo-VI',
jp='ヤリーロ-VI',
es='Jarilo-VI',
world_id=1,
short_name='Jarilo',
)
The_Xianzhou_Luofu = MapWorld(
id=3,
@ -29,4 +33,17 @@ The_Xianzhou_Luofu = MapWorld(
en='The Xianzhou Luofu',
jp='仙舟「羅浮」',
es='El Luofu de Xianzhou',
world_id=2,
short_name='Luofu',
)
Penacony = MapWorld(
id=4,
name='Penacony',
cn='匹诺康尼',
cht='匹諾康尼',
en='Penacony',
jp='ピノコニー',
es='Colonipenal',
world_id=3,
short_name='Penacony',
)

View File

@ -79,9 +79,9 @@ class MapResource(ResourceConst):
@cached_property
def assets_file_basename(self):
if self.plane.has_multiple_floors or self.is_special_plane:
return f'./position/{self.plane.world}/{self.plane.name}_{self.floor}'
return f'./position/{self.plane.world.short_name}/{self.plane.name}_{self.floor}'
else:
return f'./position/{self.plane.world}/{self.plane.name}'
return f'./position/{self.plane.world.short_name}/{self.plane.name}'
@cached_property
def assets_floor(self):