Files
gtav-src/tools_ng/script/coding/CollisionFlagHelper.pyw
T
2025-09-29 00:52:08 +02:00

231 lines
7.9 KiB
Python
Executable File

# Helper tool to quickly convert a string representation of either a hexadecimal or decimal
# u32 into the corresponding set of collision flags as defined in gtaArchetype.h
#
# Author: Richard Archibald
# Started: 20/1/12
from __future__ import with_statement
# Python libraries needed:
import re
import traceback
import functools
from Tkinter import *
#-----------------------------------------------------
# Local function definitions
#-----------------------------------------------------
def parseLine(line):
"""
Decides what is on a line in the enum definition. Skips any whitespace or comment
lines.
"""
# Skip any line which doesn't meet expectations (e.g. a comment or whitespace).
if re.match('^\ *GTA_.*=\ BIT([0-9]*)', line) and not re.match('^.*//', line):
# Split enum def lines into the name and value.
tokens = line.split("=")
value = tokens[0].strip(",").strip() # Get rid of any remaining white-space too!
key = int(tokens[1].split("(")[1].strip().split(")")[0].strip())
return (key, value)
def readEnumNames():
"""
Populate a dictionary with the data in gtaArchetype.h so that
we can match include flag values to the enum value in C++ code.
"""
enum_dict = {}
with open("x:/gta5/src/dev_ng/game/physics/gtaArchetype.h", "r") as enumDefFile:
# Read until we hit the enum definition.
for line in enumDefFile:
line = line.strip()
if line.find("namespace ArchetypeFlags")!=-1: break
# Skip lines until we hit the actual enum definition.
for line in enumDefFile:
line = line.strip()
if line.find("enum")!=-1: break
for line in enumDefFile:
line = line.strip()
if line.find("{")!=-1: break
# Add definitions to the dictionary until we reach the end tag.
for line in enumDefFile:
line = line.strip()
if line.find("};")!=-1: break
returnValue = parseLine(line)
if returnValue:
key = returnValue[0]
value = returnValue[1]
enum_dict[key] = value
return enum_dict
#--------------------------------
class exceptionDecorator(object):
#--------------------------------
"""Used to pop-up a little exception window when bad things happen."""
def __init__(self, fn):
self.fn = fn
def __get__(self, obj, objtype):
"""Support instance methods."""
return functools.partial(self.__call__, obj)
def __call__(self, *args):
try:
self.fn(*args)
except:
root = Tk()
root.title("UNHANDLED EXCEPTION:")
def close_and_quit():
Tk().quit()
root.protocol("WM_DELETE_WINDOW", close_and_quit)
# Get screen width and height
screen_width = root.winfo_screenwidth()
screen_height = root.winfo_screenheight()
# Size and position window near task bar.
wx = screen_width/2 - 300
wy = screen_height/2 - 100
root.geometry('+%d+%d' % (wx, wy))
e_type = sys.exc_info()[1]
e_text = "\"%s\"\n\n%s" % (e_type, traceback.format_exc())
msg = Text(root)
msg.insert(INSERT, e_text)
msg.pack()
closebutton = Button(root, text="Close.", command=close_and_quit)
closebutton.pack()
root.mainloop()
Tk().quit()
finally: # Close any files or whatever.
pass
#---------
class App:
#---------
@exceptionDecorator
def __init__(self):
self.root = Tk()
self.root.title("GTA collision flag helper tool")
# Get screen width and height
screen_width = self.root.winfo_screenwidth()
screen_height = self.root.winfo_screenheight()
# Size and position window near task bar.
window_w = 300
window_h = 490
wx = 100
wy = screen_height - window_h - 100
self.root.geometry('%dx%d+%d+%d' % (window_w, window_h, wx, wy))
# Layout of frames:
# ---------------------- frmMain
# | |----------------- |
# | |frmH1 | |
# | |----------------- |
# | |----------------- |
# | |frmH2 | |
# | |----------------- |
# | ... |
# ----------------------
self.frmMain = Frame(self.root)
self.frmMain.pack()
#
self.frmH1 = Frame(self.frmMain)
self.frmH1.pack(side=TOP)
#
self.frmH2 = Frame(self.frmMain)
self.frmH2.pack(side=TOP)
#
self.frmH3 = Frame(self.frmMain)
self.frmH3.pack(side=TOP)
#
self.frmH4 = Frame(self.frmMain, bd=3, relief=SUNKEN)
self.frmH4.pack(side=BOTTOM)
# Create the widgets.
self.status = Label(self.frmH1, text='Enter collision flags (use 0x for hex):')
self.status.pack(side=LEFT)
#
self.col_flags = Entry(self.frmH1)
self.col_flags.bind('<Return>', lambda e: self.lookup_flags())
self.col_flags.bind('<Escape>', lambda e: self.exit_app())
self.col_flags.pack(side=RIGHT)
#
self.msg = Label(self.frmH2, text='', width=window_w)
self.msg.pack(side=LEFT)
#
self.okButton = Button(self.frmH3, text="Ok.", command=self.lookup_flags, \
default=ACTIVE, state=ACTIVE)
self.okButton.pack(side=LEFT)
#
self.cancelButton = Button(self.frmH3, text="Cancel.", command=self.exit_app)
self.cancelButton.pack(side=RIGHT)
# The results area.
self.results = Label(self.frmH4, text='', width=75)
self.results.pack(side=BOTTOM)
# Run the lookup function as part of initialisation.
self.lookup_flags()
@exceptionDecorator
def exit_app(self):
self.root.destroy()
self.root.quit()
@exceptionDecorator
def lookup_flags(self):
"""Check the user defined flag value against the enums and display the appropriate flags."""
self.enum_dict = readEnumNames()
self.msg.config(text='')
user_value = 0
user_flags = self.col_flags.get()
if user_flags == '':
user_flags = '0'
flag_text = ''
class OutOfRangeError(Exception):
def __init__(self, value):
self.value = value
def __str__(self):
return repr(self.value)
try:
if re.match('^0x', user_flags):
user_value = long(user_flags, 16)
self.msg.config(text='Converting from hex...', fg="darkgreen")
else:
user_value = long(user_flags, 10)
self.msg.config(text='Converting from decimal...', fg="darkgreen")
if user_value >= long(1<<32): raise OutOfRangeError(user_value)
for k in self.enum_dict:
if (long(1<<k) & user_value) > 0:
flag_text += "%s\n" % self.enum_dict[k]
else:
flag_text += "---\n"
self.results.config(text=flag_text)
except ValueError:
# Catch bad user input data and inform them what they are doing wrong.
self.msg.config(text=sys.exc_info()[1], fg="red")
except OutOfRangeError:
# Value user entered was bigger than a u32.
self.msg.config(text="ERROR: value entered is greater than max for u32!", fg="red")
@exceptionDecorator
def run(self):
self.root.mainloop()
#-----------------------------------------------------
# Main
#-----------------------------------------------------
if __name__ == "__main__":
app = App()
app.run()