1010import readline
1111import math
1212import os
13+ import sys
1314import pyperclip
1415
1516# ---------- EventFlags converter ----------
@@ -161,6 +162,44 @@ def to_signed(val, bits=32):
161162 val -= 1 << bits
162163 return val
163164
165+ def round_fp (val , scale ):
166+ float_val = val / scale ;
167+
168+ # Find shortest decimal that round-trips correctly
169+ found = False
170+
171+ for digits in range (1 , 12 ):
172+ rounded = round (float_val , digits )
173+ increment = 10 ** (- digits )
174+
175+ # Test the rounded value and its nearest neighbor
176+ candidates = [rounded , rounded + (increment if float_val >= 0 else - increment )]
177+
178+ for candidate in candidates :
179+ scaled = candidate * scale
180+ reconverted = int (math .floor (scaled )) if candidate >= 0 else int (math .ceil (scaled ))
181+ if reconverted == val :
182+ float_val = candidate
183+ found = True
184+ break
185+
186+ if found :
187+ break
188+
189+ return f"{ float_val } f"
190+
191+ def process_fp (val , qval = 12 ):
192+ scale = (1 << qval )
193+ float_val = round_fp (val , scale )
194+
195+ return f"Q{ qval } ({ float_val } )"
196+
197+ def process_fp_angle_packed (val ):
198+ return f"FP_ANGLE_PACKED({ round_fp (val , 0.711111 )} )"
199+
200+ def process_fp_angle (val ):
201+ return f"FP_ANGLE({ round_fp (val , 11.377778 )} )"
202+
164203def process_fp_text (text ):
165204 qval = 12 # default Q-format is Q12
166205
@@ -170,7 +209,14 @@ def process_fp_text(text):
170209 qval = int (match .group (1 ))
171210 text = re .sub (r'\bQ\d+\b' , '' , text , count = 1 )
172211
173- scale = (1 << qval )
212+ is_fp_angle_packed = False
213+ is_fp_angle = False
214+ if "FP_ANGLE_PACKED" in text :
215+ is_fp_angle_packed = True
216+ text = text .replace ("FP_ANGLE_PACKED" , "" )
217+ elif "FP_ANGLE" in text :
218+ is_fp_angle = True
219+ text = text .replace ("FP_ANGLE" , "" )
174220
175221 def convert_value (match ):
176222 prefix = match .group (1 ) or "" # may be None if start-of-string
@@ -187,28 +233,31 @@ def convert_value(match):
187233 else :
188234 val = int (value_str , 10 )
189235
190- float_val = val / scale ;
191-
192- # Try to find the shortest rounded representation that still matches
193- for digits in range (1 , 12 ): # up to 12 decimal places
194- rounded = round (float_val , digits )
195- if float_val < 0 :
196- reconverted = int (math .ceil (rounded * scale ))
197- else :
198- reconverted = int (math .floor (rounded * scale ))
199- if reconverted == val :
200- # Good enough — this rounded value still maps to the same fixed integer
201- float_val = rounded
202- break
203-
204- return f"{ prefix } Q{ qval } ({ float_val } f)"
236+ if is_fp_angle :
237+ return f"{ prefix } { process_fp_angle (val )} "
238+ if is_fp_angle_packed :
239+ return f"{ prefix } { process_fp_angle_packed (val )} "
240+ return f"{ prefix } { process_fp (val , qval )} "
205241
206242 # prefix can be: start-of-string, space, or a punctuation
207243 pattern = re .compile (r'(^|[\s,(=:{\[])([-+]?0x[0-9A-Fa-f]+|[-+]?\d+)' )
208244 return pattern .sub (convert_value , text )
209245
210246# ---------- Unified REPL ----------
211247if __name__ == "__main__" :
248+ if len (sys .argv ) > 1 :
249+ # Join all arguments into a single input string
250+ line = " " .join (sys .argv [1 :]).strip ()
251+
252+ result = convert_flag_expression (line )
253+ if result is None :
254+ result = process_fp_text (line )
255+
256+ if result is not None :
257+ print (result )
258+ pyperclip .copy (result )
259+ sys .exit (0 )
260+
212261 print ("Enter expressions like 'g_SavegamePtr->eventFlags_168[2] & 8' or 'g_SavegamePtr->eventFlags_168[2] |= (1 << 3))'." )
213262 print ("Or enter a line containing decimal/hexadecimal Q12 numbers (0x7CCC, -0x1444, 4096, 0xFFFE0DDD) to convert to float" )
214263 print ("(change to other Q** formats by including Q format in the text, 'chara.field_48 = Q8(-0x1999)'" )
0 commit comments