77from ..utility import CData , getGroupIndexFromname , readFile , writeFile
88from ..f3d .flipbook import flipbook_to_c , flipbook_2d_to_c , flipbook_data_to_c
99from ..f3d .f3d_material import createF3DMat , F3DMaterial_UpdateLock , update_preset_manual
10- from .utility import replaceMatchContent , getOOTScale
10+ from .utility import replaceMatchContent , getOOTScale , ootStripComments
1111from .texture_array import TextureFlipbook
1212
1313from ..f3d .f3d_writer import (
@@ -331,6 +331,131 @@ def writeTextureArraysExisting2D(data: str, flipbook: TextureFlipbook, flipbookA
331331 return newData
332332
333333
334+ def ootParseScaleLiteral (scaleExpr : str ) -> Optional [float ]:
335+ scaleExpr = scaleExpr .strip ().removesuffix ("f" )
336+
337+ try :
338+ return float (scaleExpr )
339+ except ValueError :
340+ return None
341+
342+
343+ def ootSplitTopLevel (expr : str ) -> list [str ]:
344+ entries = []
345+ start = 0
346+ depth = 0
347+
348+ for i , char in enumerate (expr ):
349+ if char in "({[" :
350+ depth += 1
351+ elif char in ")}]" :
352+ depth -= 1
353+ elif char == "," and depth == 0 :
354+ entries .append (expr [start :i ].strip ())
355+ start = i + 1
356+
357+ finalEntry = expr [start :].strip ()
358+ if finalEntry != "" :
359+ entries .append (finalEntry )
360+
361+ return entries
362+
363+
364+ def ootResolveStructArrayFieldScale (actorData : str , scaleExpr : str ) -> Optional [float ]:
365+ match = re .fullmatch (r"([A-Za-z_]\w*)\s*\[\s*[^\]]+\s*\]\s*\.\s*([A-Za-z_]\w*)" , scaleExpr .strip ())
366+ if match is None :
367+ return None
368+
369+ tableName , fieldName = match .groups ()
370+ actorData = ootStripComments (actorData )
371+
372+ tableMatch = re .search (
373+ rf"(?:static\s+)?(?P<struct_name>[A-Za-z_]\w*)\s+{ re .escape (tableName )} \s*\[\s*\]\s*=\s*\{{" ,
374+ actorData ,
375+ re .DOTALL ,
376+ )
377+ if tableMatch is None :
378+ return None
379+
380+ structMatch = re .search (
381+ rf"typedef\s+struct(?:\s+{ re .escape (tableMatch .group ('struct_name' ))} )?\s*\{{(?P<body>.*?)\}}\s*{ re .escape (tableMatch .group ('struct_name' ))} \s*;" ,
382+ actorData ,
383+ re .DOTALL ,
384+ )
385+ if structMatch is None :
386+ return None
387+
388+ fieldNames = [
389+ fieldMatch .group (1 )
390+ for fieldMatch in re .finditer (
391+ r"[A-Za-z_]\w*(?:\s*\*+)?\s+([A-Za-z_]\w*)\s*(?:\[[^\]]+\])?\s*;" , structMatch .group ("body" )
392+ )
393+ ]
394+ if fieldName not in fieldNames :
395+ return None
396+
397+ fieldIndex = fieldNames .index (fieldName )
398+ values = set ()
399+ depth = 0
400+ entryStart = None
401+
402+ for i in range (tableMatch .end () - 1 , len (actorData )):
403+ char = actorData [i ]
404+ if char == "{" :
405+ if depth == 1 :
406+ entryStart = i + 1
407+ depth += 1
408+ elif char == "}" :
409+ depth -= 1
410+ if depth == 1 and entryStart is not None :
411+ entryValues = ootSplitTopLevel (actorData [entryStart :i ])
412+ if fieldIndex >= len (entryValues ):
413+ return None
414+
415+ value = ootParseScaleLiteral (entryValues [fieldIndex ])
416+ if value is None :
417+ return None
418+
419+ values .add (value )
420+ if len (values ) > 1 :
421+ return None
422+ entryStart = None
423+ elif depth == 0 :
424+ break
425+
426+ return values .pop () if len (values ) == 1 else None
427+
428+
429+ def ootResolveScaleExpression (actorData : str , scaleExpr : str ) -> Optional [float ]:
430+ scaleExpr = scaleExpr .strip ()
431+ literalValue = ootParseScaleLiteral (scaleExpr )
432+ if literalValue is not None :
433+ return literalValue
434+
435+ while scaleExpr .startswith ("(" ) and scaleExpr .endswith (")" ):
436+ scaleExpr = scaleExpr [1 :- 1 ].strip ()
437+ literalValue = ootParseScaleLiteral (scaleExpr )
438+ if literalValue is not None :
439+ return literalValue
440+
441+ depth = 0
442+ for i , char in enumerate (scaleExpr ):
443+ if char in "({[" :
444+ depth += 1
445+ elif char in ")}]" :
446+ depth -= 1
447+ elif depth == 0 and char in "*/" :
448+ leftValue = ootResolveScaleExpression (actorData , scaleExpr [:i ])
449+ rightValue = ootResolveScaleExpression (actorData , scaleExpr [i + 1 :])
450+ if leftValue is None or rightValue is None :
451+ return None
452+ if char == "*" :
453+ return leftValue * rightValue
454+ return None if rightValue == 0 else leftValue / rightValue
455+
456+ return ootResolveStructArrayFieldScale (actorData , scaleExpr )
457+
458+
334459# Note this does not work well with actors containing multiple "parts". (z_en_honotrap)
335460def ootReadActorScale (basePath : str , overlayName : str , isLink : bool ) -> Optional [float ]:
336461 if not isLink :
@@ -341,16 +466,16 @@ def ootReadActorScale(basePath: str, overlayName: str, isLink: bool) -> Optional
341466 chainInitMatch = re .search (r"CHAIN_VEC3F_DIV1000\s*\(\s*scale\s*,\s*(.*?)\s*," , actorData , re .DOTALL )
342467 if chainInitMatch is not None :
343468 scale = chainInitMatch .group (1 ).strip ()
344- if scale [ - 1 ] == "f" :
345- scale = scale [: - 1 ]
346- return getOOTScale (1 / (float ( scale ) / 1000 ))
469+ resolvedScale = ootResolveScaleExpression ( actorData , scale )
470+ if resolvedScale is not None :
471+ return getOOTScale (1 / (resolvedScale / 1000 ))
347472
348473 actorScaleMatch = re .search (r"Actor\_SetScale\s*\(.*?,\s*(.*?)\s*\)" , actorData , re .DOTALL )
349474 if actorScaleMatch is not None :
350475 scale = actorScaleMatch .group (1 ).strip ()
351- if scale [ - 1 ] == "f" :
352- scale = scale [: - 1 ]
353- return getOOTScale (1 / float ( scale ) )
476+ resolvedScale = ootResolveScaleExpression ( actorData , scale )
477+ if resolvedScale is not None :
478+ return getOOTScale (1 / resolvedScale )
354479
355480 if isLink :
356481 return getOOTScale (100.0 )
0 commit comments