From 33969471158f66523f057399b28f69685cc5a860 Mon Sep 17 00:00:00 2001 From: yyc12345 Date: Mon, 1 Apr 2024 14:39:11 +0800 Subject: [PATCH] feat: add ballance map sector field in scene - add ballance map sector info in scene to indicate the maximum sector count of this map. - this adding will prevent the bug that the exported ballance map do not have successive sector groups. because original implement will not create sector group if no component in corresponding sector and previous remedy still have bug. and if this happended, ballance will show spaceship in wrong sector. this adding is the final solution of this bug. - exlarge ballance map sector info when user adding component. the enlarged value will be calculated by user input sector. - auto enlarge ballance map sector info when importing. this will give user a fluent experience when modifying existing map. - exporting map will also use ballance map sector info to pre-create successive sector group as term 2 stated. - move sector name extractor from virtools file exporting module to naming convention module. --- bbp_ng/OP_ADDS_component.py | 24 +++++++- bbp_ng/OP_EXPORT_virtools.py | 100 ++++++++----------------------- bbp_ng/OP_IMPORT_virtools.py | 19 +++++- bbp_ng/PROP_ballance_map_info.py | 81 +++++++++++++++++++++++++ bbp_ng/PROP_virtools_group.py | 2 +- bbp_ng/UTIL_naming_convension.py | 47 ++++++++++++--- bbp_ng/__init__.py | 5 +- 7 files changed, 190 insertions(+), 88 deletions(-) create mode 100644 bbp_ng/PROP_ballance_map_info.py diff --git a/bbp_ng/OP_ADDS_component.py b/bbp_ng/OP_ADDS_component.py index 6b46ea2..bc710e4 100644 --- a/bbp_ng/OP_ADDS_component.py +++ b/bbp_ng/OP_ADDS_component.py @@ -1,7 +1,7 @@ import bpy, mathutils import math, typing from . import UTIL_functions, UTIL_icons_manager, UTIL_naming_convension -from . import PROP_ballance_element, PROP_virtools_group +from . import PROP_ballance_element, PROP_virtools_group, PROP_ballance_map_info #region Param Help Classes @@ -128,6 +128,7 @@ class _GeneralComponentCreator(): """ # get element info first ele_info: UTIL_naming_convension.BallanceObjectInfo = _get_component_info(comp_type, comp_sector) + # create blc element context with PROP_ballance_element.BallanceElementsHelper(bpy.context.scene) as creator: # object creation counter @@ -143,6 +144,27 @@ class _GeneralComponentCreator(): # put into created object list self.__mObjList.append(obj) + # enlarge scene sector field for non-PS (start point) PE (end point) component + # read from scene and create var for enlarged sector count + map_info: PROP_ballance_map_info.RawBallanceMapInfo = PROP_ballance_map_info.get_raw_ballance_map_info(bpy.context.scene) + enlarged_sector: int + # check component type to get enlarged value + match(ele_info.mBasicType): + case UTIL_naming_convension.BallanceObjectType.COMPONENT: + enlarged_sector = comp_sector + case UTIL_naming_convension.BallanceObjectType.CHECKPOINT: + # checkpoint 1 means that there is sector 2, so we plus 1 for it. + enlarged_sector = comp_sector + 1 + case UTIL_naming_convension.BallanceObjectType.RESETPOINT: + enlarged_sector = comp_sector + case _: + # this component is not a sector based component + # so we do not change it (use original value) + enlarged_sector = map_info.mSectorCount + # enlarge it + map_info.mSectorCount = max(map_info.mSectorCount, enlarged_sector) + PROP_ballance_map_info.set_raw_ballance_map_info(bpy.context.scene, map_info) + def finish_component(self) -> None: """ Finish up component creation. diff --git a/bbp_ng/OP_EXPORT_virtools.py b/bbp_ng/OP_EXPORT_virtools.py index f181e06..e7d45df 100644 --- a/bbp_ng/OP_EXPORT_virtools.py +++ b/bbp_ng/OP_EXPORT_virtools.py @@ -1,9 +1,9 @@ import bpy from bpy_extras.wm_utils.progress_report import ProgressReport -import tempfile, os, typing, re +import tempfile, os, typing from . import PROP_preferences, UTIL_ioport_shared -from . import UTIL_virtools_types, UTIL_functions, UTIL_file_browser, UTIL_blender_mesh, UTIL_ballance_texture, UTIL_icons_manager -from . import PROP_virtools_group, PROP_virtools_material, PROP_virtools_mesh, PROP_virtools_texture +from . import UTIL_virtools_types, UTIL_functions, UTIL_file_browser, UTIL_blender_mesh, UTIL_ballance_texture, UTIL_icons_manager, UTIL_naming_convension +from . import PROP_virtools_group, PROP_virtools_material, PROP_virtools_mesh, PROP_virtools_texture, PROP_ballance_map_info from .PyBMap import bmap_wrapper as bmap # define global tex save opt blender enum prop helper @@ -84,6 +84,11 @@ class BBP_OT_export_virtools(bpy.types.Operator, UTIL_file_browser.ExportVirtool if self.use_compress: box.prop(self, 'compress_level') + # show sector info to notice user + layout.separator() + map_info: PROP_ballance_map_info.RawBallanceMapInfo = PROP_ballance_map_info.get_raw_ballance_map_info(bpy.context.scene) + layout.label(text = f'Map Sectors: {map_info.mSectorCount}') + _TObj3dPair = tuple[bpy.types.Object, bmap.BM3dObject] _TMeshPair = tuple[bpy.types.Object, bpy.types.Mesh, bmap.BMMesh] _TMaterialPair = tuple[bpy.types.Material, bmap.BMMaterial] @@ -170,8 +175,22 @@ def _export_virtools_groups( # start saving progress.enter_substeps(len(obj3d_crets), "Saving Groups") - # create group exporting helper - group_cret_guard: VirtoolsGroupCreationGuard = VirtoolsGroupCreationGuard(writer) + # create sector group first + # This step is designed for ensure that the created sector group is successive. + # + # Due to the design of Ballance, Ballance rely on checking the existance of Sector_XX to get how many sectors this map have. + # Thus if there are no component in a sector, it still need to create a empty Sector_XX group, otherwise the game will crash + # or be ended at a accident sector. + # + # So we create all needed sector group in here to make sure exported virtools file can be read by Ballancde correctly. + map_info: PROP_ballance_map_info.RawBallanceMapInfo = PROP_ballance_map_info.get_raw_ballance_map_info(bpy.context.scene) + for i in range(map_info.mSectorCount): + gp_name: str = UTIL_naming_convension.build_name_from_sector_index(i + 1) + vtgroup: bmap.BMGroup | None = group_cret_map.get(gp_name, None) + if vtgroup is None: + vtgroup = writer.create_group() + vtgroup.set_name(gp_name) + group_cret_map[gp_name] = vtgroup for obj3d, vtobj3d in obj3d_crets: # open group visitor @@ -180,8 +199,8 @@ def _export_virtools_groups( # get group or create new group from guard vtgroup: bmap.BMGroup | None = group_cret_map.get(gp_name, None) if vtgroup is None: - # note: no need to set name, guard has set it. - vtgroup = group_cret_guard.create_group(gp_name) + vtgroup = writer.create_group() + vtgroup.set_name(gp_name) group_cret_map[gp_name] = vtgroup # group this object @@ -471,73 +490,6 @@ def _save_virtools_document( progress.step() progress.leave_substeps() -class VirtoolsGroupCreationGuard(): - """ - This class is designed for ensure that the created sector group is successive. - - Due to the design of Ballance, Ballance rely on checking the existance of Sector_XX to get how many sectors this map have. - Thus if there are no component in a sector, it still need to create a empty Sector_XX group, otherwise the game will crash - or be ended at a accident sector. - - This class hook the operation of Virtools group creation and check all Sector group creation. - Create essential group to make Sector_XX group successive. - Thus all group creation in this module should be passed by this class. - """ - cRegexGroupSector: typing.ClassVar[re.Pattern] = re.compile('^Sector_(0[1-8]|[1-9][0-9]{1,2}|9)$') - - @staticmethod - def __get_group_index(group_name: str) -> int | None: - """ - Return the sector index of group name if it is. Otherwise None. - """ - regex_result = VirtoolsGroupCreationGuard.cRegexGroupSector.match(group_name) - if regex_result is not None: - return int(regex_result.group(1)) - else: - return None - - @staticmethod - def __get_group_name(group_index: int) -> str: - """ - Output Sector group name by given sector index - """ - if group_index == 9: - return 'Sector_9' - else: - return f'Sector_{group_index:0>2d}' - - __mWriter: bmap.BMFileWriter - __mSectors: list[bmap.BMGroup] - - def __init__(self, assoc_writer: bmap.BMFileWriter): - self.__mWriter = assoc_writer - self.__mSectors = [] - - def create_group(self, group_name: str) -> bmap.BMGroup: - """ - The hooked group creation function. - - Please note the passed group name argument is just for name checking. - This function will set group name for you, not like BMFileWriter.create_group() operated. - """ - # check whether it is sector group - # note: the return sector index is 1 based, not 0 - sector_idx: int | None = VirtoolsGroupCreationGuard.__get_group_index(group_name) - # if it is regular group, return normal creation - if sector_idx is None: - gp: bmap.BMGroup = self.__mWriter.create_group() - gp.set_name(group_name) - return gp - - # get from sector cahce list - # enlarge sector cache list if it is not fulfilled given sector index - while sector_idx > len(self.__mSectors): - gp: bmap.BMGroup = self.__mWriter.create_group() - self.__mSectors.append(gp) - gp.set_name(self.__get_group_name(len(self.__mSectors))) - # return ordered sector from sector caches - return self.__mSectors[sector_idx - 1] - def register() -> None: bpy.utils.register_class(BBP_OT_export_virtools) diff --git a/bbp_ng/OP_IMPORT_virtools.py b/bbp_ng/OP_IMPORT_virtools.py index d1fbb6f..670bd9f 100644 --- a/bbp_ng/OP_IMPORT_virtools.py +++ b/bbp_ng/OP_IMPORT_virtools.py @@ -2,8 +2,8 @@ import bpy from bpy_extras.wm_utils.progress_report import ProgressReport import tempfile, os, typing from . import PROP_preferences, UTIL_ioport_shared -from . import UTIL_virtools_types, UTIL_functions, UTIL_file_browser, UTIL_blender_mesh, UTIL_ballance_texture -from . import PROP_virtools_group, PROP_virtools_material, PROP_virtools_mesh, PROP_virtools_texture +from . import UTIL_virtools_types, UTIL_functions, UTIL_file_browser, UTIL_blender_mesh, UTIL_ballance_texture, UTIL_naming_convension +from . import PROP_virtools_group, PROP_virtools_material, PROP_virtools_mesh, PROP_virtools_texture, PROP_ballance_map_info from .PyBMap import bmap_wrapper as bmap class BBP_OT_import_virtools(bpy.types.Operator, UTIL_file_browser.ImportVirtoolsFile, UTIL_ioport_shared.ImportParams, UTIL_ioport_shared.VirtoolsParams): @@ -344,10 +344,12 @@ def _import_virtools_groups( reader: bmap.BMFileReader, progress: ProgressReport, obj3d_cret_map: dict[bmap.BM3dObject, bpy.types.Object] - ) -> dict[bmap.BM3dObject, bpy.types.Object]: + ) -> None: # we need iterate all groups to construct a reversed map # to indicate which groups should this 3dobject be grouped into. reverse_map: dict[bmap.BM3dObject, set[str]] = {} + # sector counter to record the maximum sector we have processed. + sector_count: int = 1 # prepare progress progress.enter_substeps(reader.get_material_count(), "Loading Groups") @@ -357,6 +359,12 @@ def _import_virtools_groups( group_name: str | None = vtgroup.get_name() if group_name is None: continue + # try extracting sector info + potential_sector_count: int | None = UTIL_naming_convension.extract_sector_from_name(group_name) + if potential_sector_count is not None: + sector_count = max(sector_count, potential_sector_count) + + # creating map for item in vtgroup.get_objects(): # get or create set objgroups: set[str] = reverse_map.get(item, None) @@ -370,6 +378,11 @@ def _import_virtools_groups( # step progress.step() + # assign to ballance map info according to gotten sector count + map_info: PROP_ballance_map_info.RawBallanceMapInfo = PROP_ballance_map_info.get_raw_ballance_map_info(bpy.context.scene) + map_info.mSectorCount = max(map_info.mSectorCount, sector_count) + PROP_ballance_map_info.set_raw_ballance_map_info(bpy.context.scene, map_info) + # leave progress progress.leave_substeps() diff --git a/bbp_ng/PROP_ballance_map_info.py b/bbp_ng/PROP_ballance_map_info.py new file mode 100644 index 0000000..9deec6a --- /dev/null +++ b/bbp_ng/PROP_ballance_map_info.py @@ -0,0 +1,81 @@ +import bpy +import typing +from . import UTIL_functions + +class RawBallanceMapInfo(): + cSectorCount: typing.ClassVar[int] = 1 + + mSectorCount: int + + def __init__(self, **kwargs): + self.mSectorCount = kwargs.get("mSectorCount", RawBallanceMapInfo.cSectorCount) + + def regulate(self): + self.mSectorCount = UTIL_functions.clamp_int(self.mSectorCount, 1, 999) + +#region Prop Decl & Getter Setter + +class BBP_PG_ballance_map_info(bpy.types.PropertyGroup): + sector_count: bpy.props.IntProperty( + name = "Sector", + description = "The sector count of this Ballance map which is used in exporting map and may be changed when importing map.", + default = 1, + max = 999, min = 1, + soft_max = 8, soft_min = 1, + step = 1 + ) # type: ignore + +def get_ballance_map_info(scene: bpy.types.Scene) -> BBP_PG_ballance_map_info: + return scene.ballance_map_info + +def get_raw_ballance_map_info(scene: bpy.types.Scene) -> RawBallanceMapInfo: + props: BBP_PG_ballance_map_info = get_ballance_map_info(scene) + rawdata: RawBallanceMapInfo = RawBallanceMapInfo() + + rawdata.mSectorCount = props.sector_count + + rawdata.regulate() + return rawdata + +def set_raw_ballance_map_info(scene: bpy.types.Scene, rawdata: RawBallanceMapInfo) -> None: + props: BBP_PG_ballance_map_info = get_ballance_map_info(scene) + + props.sector_count = rawdata.mSectorCount + +#endregion + +class BBP_PT_ballance_map_info(bpy.types.Panel): + """Show Ballance Map Infos.""" + bl_label = "Ballance Map" + bl_idname = "BBP_PT_ballance_map_info" + bl_space_type = 'PROPERTIES' + bl_region_type = 'WINDOW' + bl_context = "scene" + + @classmethod + def poll(cls, context): + return context.scene is not None + + def draw(self, context): + layout: bpy.types.UILayout = self.layout + target: bpy.types.Scene = context.scene + props: BBP_PG_ballance_map_info = get_ballance_map_info(target) + + # show map sector count numberbox + layout.prop(props, 'sector_count') + +def register() -> None: + # register + bpy.utils.register_class(BBP_PG_ballance_map_info) + bpy.utils.register_class(BBP_PT_ballance_map_info) + + # add into scene metadata + bpy.types.Scene.ballance_map_info = bpy.props.PointerProperty(type = BBP_PG_ballance_map_info) + +def unregister() -> None: + # del from scene metadata + del bpy.types.Scene.ballance_map_info + + # unregister + bpy.utils.unregister_class(BBP_PG_ballance_map_info) + bpy.utils.unregister_class(BBP_PT_ballance_map_info) diff --git a/bbp_ng/PROP_virtools_group.py b/bbp_ng/PROP_virtools_group.py index bdc7823..c183d62 100644 --- a/bbp_ng/PROP_virtools_group.py +++ b/bbp_ng/PROP_virtools_group.py @@ -8,7 +8,7 @@ class BBP_PG_virtools_group(bpy.types.PropertyGroup): group_name: bpy.props.StringProperty( name = "Group Name", default = "" - ) + ) # type: ignore def get_virtools_groups(obj: bpy.types.Object) -> bpy.types.CollectionProperty: return obj.virtools_groups diff --git a/bbp_ng/UTIL_naming_convension.py b/bbp_ng/UTIL_naming_convension.py index dea9b5d..ed7e674 100644 --- a/bbp_ng/UTIL_naming_convension.py +++ b/bbp_ng/UTIL_naming_convension.py @@ -184,6 +184,42 @@ class BallanceObjectInfo(): #endregion +#region Sector Extractor + +_g_RegexBlcSectorGroup: re.Pattern = re.compile('^Sector_(0[1-8]|[1-9][0-9]{1,2}|9)$') + +def extract_sector_from_name(group_name: str) -> int | None: + """ + A convenient function to extract sector index from given group name. + This function also supports 999 sector plugin. + + Not only in this module, but also in outside modules, this function is vary used to extract sector index info. + + Function return the index extracted, or None if given group name is not a valid sector group. + The valid sector index is range from 1 to 999 (inclusive) + """ + regex_result = _g_RegexBlcSectorGroup.match(group_name) + if regex_result is not None: + return int(regex_result.group(1)) + else: + return None + +def build_name_from_sector_index(sector_index: int) -> str: + """ + A convenient function to build Ballance recognizable sector group name. + This function also supports 999 sector plugin. + + This function also is used in this module or other modules outside. + + Function return a sector name string. It basically the reverse operation of `extract_sector_from_name`. + """ + if sector_index == 9: + return 'Sector_9' + else: + return f'Sector_{sector_index:0>2d}' + +#endregion + #region Naming Convention Declaration _g_BlcNormalComponents: set[str] = set(( @@ -227,7 +263,6 @@ _g_BlcWood: set[str] = set(( )) class VirtoolsGroupConvention(): - cRegexGroupSector: typing.ClassVar[re.Pattern] = re.compile('^Sector_(0[1-8]|[1-9][0-9]{1,2}|9)$') cRegexComponent: typing.ClassVar[re.Pattern] = re.compile('^(' + '|'.join(_g_BlcNormalComponents) + ')_(0[1-9]|[1-9][0-9])_.*$') cRegexPC: typing.ClassVar[re.Pattern] = re.compile('^PC_TwoFlames_(0[1-7])$') cRegexPR: typing.ClassVar[re.Pattern] = re.compile('^PR_Resetpoint_(0[1-8])$') @@ -255,9 +290,9 @@ class VirtoolsGroupConvention(): counter: int = 0 last_matched_sector: int = 0 for i in gps: - regex_result = VirtoolsGroupConvention.cRegexGroupSector.match(i) + regex_result: int | None = extract_sector_from_name(i) if regex_result is not None: - last_matched_sector = int(regex_result.group(1)) + last_matched_sector = regex_result counter += 1 if counter != 1: return None @@ -377,12 +412,8 @@ class VirtoolsGroupConvention(): # group into component type # use typing.cast() to force linter accept it because None is impossible gp.add_group(typing.cast(str, info.mComponentType)) - # group to sector - if info.mSector == 9: - gp.add_group('Sector_9') - else: - gp.add_group(f'Sector_{info.mSector:0>2d}') + gp.add_group(build_name_from_sector_index(typing.cast(int, info.mSector))) case _: if reporter is not None: diff --git a/bbp_ng/__init__.py b/bbp_ng/__init__.py index e6f5c0e..8da9d63 100644 --- a/bbp_ng/__init__.py +++ b/bbp_ng/__init__.py @@ -29,7 +29,8 @@ from . import UTIL_icons_manager UTIL_icons_manager.register() # then load other modules -from . import PROP_preferences, PROP_ptrprop_resolver, PROP_virtools_material, PROP_virtools_texture, PROP_virtools_mesh, PROP_virtools_group, PROP_ballance_element, PROP_bme_material +from . import PROP_preferences, PROP_ptrprop_resolver, PROP_virtools_material, PROP_virtools_texture, PROP_virtools_mesh, PROP_virtools_group +from . import PROP_ballance_element, PROP_bme_material, PROP_ballance_map_info from . import OP_IMPORT_bmfile, OP_EXPORT_bmfile, OP_IMPORT_virtools, OP_EXPORT_virtools from . import OP_UV_flatten_uv, OP_UV_rail_uv from . import OP_MTL_fix_material @@ -217,6 +218,7 @@ def register() -> None: PROP_virtools_group.register() PROP_ballance_element.register() PROP_bme_material.register() + PROP_ballance_map_info.register() OP_IMPORT_bmfile.register() OP_EXPORT_bmfile.register() @@ -275,6 +277,7 @@ def unregister() -> None: OP_EXPORT_bmfile.unregister() OP_IMPORT_bmfile.unregister() + PROP_ballance_map_info.unregister() PROP_bme_material.unregister() PROP_ballance_element.unregister() PROP_virtools_group.unregister()