mirror of
https://github.com/open-goal/jak-project
synced 2026-07-07 22:22:21 -04:00
temp stuff
This commit is contained in:
@@ -8118,6 +8118,7 @@
|
||||
"idx"
|
||||
]
|
||||
},
|
||||
"calculate-basis-functions-vector!": {},
|
||||
"calculate-completion": {
|
||||
"args": [
|
||||
"the-progress"
|
||||
@@ -8290,6 +8291,8 @@
|
||||
]
|
||||
}
|
||||
},
|
||||
"circle-circle-xz-intersect": {},
|
||||
"circle-test": {},
|
||||
"citb-drop-plat-drop-children": {
|
||||
"vars": {
|
||||
"v1-4": [
|
||||
@@ -8303,6 +8306,7 @@
|
||||
"s"
|
||||
]
|
||||
},
|
||||
"closest-pt-in-triangle": {},
|
||||
"cloud-track": {
|
||||
"vars": {
|
||||
"s1-1": [
|
||||
@@ -8407,6 +8411,8 @@
|
||||
]
|
||||
}
|
||||
},
|
||||
"curve-closest-point": {},
|
||||
"curve-copy!": {},
|
||||
"curve-evaluate!": {
|
||||
"vars": {
|
||||
"t2-1": [
|
||||
@@ -8419,6 +8425,8 @@
|
||||
]
|
||||
}
|
||||
},
|
||||
"curve-get-pos!": {},
|
||||
"curve-length": {},
|
||||
"debug-menu-append-item": {
|
||||
"args": [
|
||||
"menu",
|
||||
@@ -9124,6 +9132,8 @@
|
||||
"v1-1": "data"
|
||||
}
|
||||
},
|
||||
"eul->matrix": {},
|
||||
"eul->quat": {},
|
||||
"execute-process-tree": {},
|
||||
"fact": {
|
||||
"args": [
|
||||
@@ -9142,6 +9152,17 @@
|
||||
"s5-0": "expected-version"
|
||||
}
|
||||
},
|
||||
"find-knot-span": {
|
||||
"args": [
|
||||
"idx",
|
||||
"arg1",
|
||||
"query",
|
||||
"pts"
|
||||
],
|
||||
"vars": {
|
||||
"v0-0": "ret"
|
||||
}
|
||||
},
|
||||
"find-parent-method": {
|
||||
"args": [
|
||||
"child-type",
|
||||
@@ -9204,6 +9225,41 @@
|
||||
"clut-addr"
|
||||
]
|
||||
},
|
||||
"forward-down->inv-matrix": {
|
||||
"args": [
|
||||
"dst",
|
||||
"fwd",
|
||||
"dn"
|
||||
]
|
||||
},
|
||||
"forward-down-nopitch->inv-matrix": {
|
||||
"args": [
|
||||
"dst",
|
||||
"fwd",
|
||||
"dn"
|
||||
]
|
||||
},
|
||||
"forward-up->quaternion": {
|
||||
"args": [
|
||||
"dst",
|
||||
"fwd",
|
||||
"up"
|
||||
]
|
||||
},
|
||||
"forward-up-nopitch->inv-matrix": {
|
||||
"args": [
|
||||
"dst",
|
||||
"fwd",
|
||||
"up"
|
||||
]
|
||||
},
|
||||
"forward-up-nopitch->quaternion": {
|
||||
"args": [
|
||||
"dst",
|
||||
"fwd",
|
||||
"up"
|
||||
]
|
||||
},
|
||||
"fractional-part": {
|
||||
"args": [
|
||||
"x"
|
||||
@@ -9837,6 +9893,7 @@
|
||||
"src"
|
||||
]
|
||||
},
|
||||
"matrix->eul": {},
|
||||
"matrix->quaternion": {
|
||||
"args": [
|
||||
"dst",
|
||||
@@ -9864,6 +9921,59 @@
|
||||
]
|
||||
}
|
||||
},
|
||||
"matrix-from-two-vectors!": {
|
||||
"args": [
|
||||
"dst",
|
||||
"a",
|
||||
"b"
|
||||
],
|
||||
"vars": {
|
||||
"a1-3": "rot-axis",
|
||||
"f0-1": "c",
|
||||
"f1-2": "s"
|
||||
}
|
||||
},
|
||||
"matrix-from-two-vectors-max-angle!": {
|
||||
"args": [
|
||||
"dst",
|
||||
"a",
|
||||
"b",
|
||||
"max-rot"
|
||||
],
|
||||
"vars": {
|
||||
"f28-0": "c",
|
||||
"f30-0": "dot",
|
||||
"s4-1": "rot-axis"
|
||||
}
|
||||
},
|
||||
"matrix-from-two-vectors-max-angle-partial!": {
|
||||
"args": [
|
||||
"dst",
|
||||
"a",
|
||||
"b",
|
||||
"max-rot",
|
||||
"frac"
|
||||
],
|
||||
"vars": {
|
||||
"f0-2": "rot-amt",
|
||||
"f28-0": "dot",
|
||||
"f30-0": "c",
|
||||
"s4-1": "rot-axis"
|
||||
}
|
||||
},
|
||||
"matrix-from-two-vectors-partial-linear!": {
|
||||
"args": [
|
||||
"dst",
|
||||
"a",
|
||||
"b",
|
||||
"frac"
|
||||
],
|
||||
"vars": {
|
||||
"f0-1": "dot",
|
||||
"f0-4": "cosine",
|
||||
"gp-1": "rot-axis"
|
||||
}
|
||||
},
|
||||
"matrix-identity": {
|
||||
"args": [
|
||||
"mat"
|
||||
@@ -9897,6 +10007,25 @@
|
||||
"alpha"
|
||||
]
|
||||
},
|
||||
"matrix-remove-z-rot": {
|
||||
"args": [
|
||||
"dst",
|
||||
"src"
|
||||
]
|
||||
},
|
||||
"matrix-rot-diff!": {
|
||||
"args": [
|
||||
"axis",
|
||||
"m0",
|
||||
"m1"
|
||||
],
|
||||
"vars": {
|
||||
"f30-1": "angle",
|
||||
"s2-0": "q1",
|
||||
"s3-0": "q0",
|
||||
"s5-0": "q1-q0"
|
||||
}
|
||||
},
|
||||
"matrix-rotate-x!": {
|
||||
"args": [
|
||||
"dst",
|
||||
@@ -10234,6 +10363,14 @@
|
||||
"lst"
|
||||
]
|
||||
},
|
||||
"normal-of-plane": {
|
||||
"args": [
|
||||
"dst",
|
||||
"p0",
|
||||
"p1",
|
||||
"p2"
|
||||
]
|
||||
},
|
||||
"num-func-loop!": {
|
||||
"args": [
|
||||
"chan",
|
||||
@@ -10300,6 +10437,20 @@
|
||||
"v1-0": "i"
|
||||
}
|
||||
},
|
||||
"point-in-plane-<-point+normal!": {
|
||||
"args": [
|
||||
"pt",
|
||||
"p0",
|
||||
"normal"
|
||||
],
|
||||
"vars": {
|
||||
"f0-3": "offset",
|
||||
"f1-7": "nx",
|
||||
"f2-3": "ny",
|
||||
"f3-1": "nz"
|
||||
}
|
||||
},
|
||||
"point-in-triangle-cross": {},
|
||||
"point-inside-poly?": {
|
||||
"vars": {
|
||||
"a0-3": [
|
||||
@@ -10430,6 +10581,7 @@
|
||||
}
|
||||
},
|
||||
"quad-copy!": {},
|
||||
"quat->eul": {},
|
||||
"quaterion<-rotate-y-vector": {
|
||||
"args": [
|
||||
"quat",
|
||||
@@ -10529,6 +10681,35 @@
|
||||
"f0-1": "inv"
|
||||
}
|
||||
},
|
||||
"quaternion-from-two-vectors!": {
|
||||
"args": [
|
||||
"dst",
|
||||
"a",
|
||||
"b"
|
||||
],
|
||||
"vars": {
|
||||
"f0-0": "rot-axis-length",
|
||||
"f0-1": "scale",
|
||||
"f1-1": "dot",
|
||||
"s5-0": "rot-axis"
|
||||
}
|
||||
},
|
||||
"quaternion-from-two-vectors-max-angle!": {
|
||||
"args": [
|
||||
"dst",
|
||||
"a",
|
||||
"b",
|
||||
"max-rot"
|
||||
],
|
||||
"vars": {
|
||||
"f0-12": "scacle",
|
||||
"f0-5": "limit",
|
||||
"f26-0": "dot",
|
||||
"f28-0": "rot-amt",
|
||||
"f30-0": "rot-axis-len",
|
||||
"s5-0": "rot-axis"
|
||||
}
|
||||
},
|
||||
"quaternion-i!": {
|
||||
"args": [
|
||||
"quat"
|
||||
@@ -10680,6 +10861,20 @@
|
||||
"a1-3": "new-rot"
|
||||
}
|
||||
},
|
||||
"quaternion-seek": {
|
||||
"args": [
|
||||
"dst",
|
||||
"q0",
|
||||
"q1",
|
||||
"unused",
|
||||
"max-rot"
|
||||
],
|
||||
"vars": {
|
||||
"s2-1": "q0-to-q1",
|
||||
"s4-0": "m1",
|
||||
"s5-0": "m0"
|
||||
}
|
||||
},
|
||||
"quaternion-set!": {
|
||||
"args": [
|
||||
"quat",
|
||||
@@ -11030,6 +11225,15 @@
|
||||
"y"
|
||||
]
|
||||
},
|
||||
"set-eul!": {
|
||||
"args": [
|
||||
"angs",
|
||||
"arg1",
|
||||
"arg2",
|
||||
"arg3",
|
||||
"arg4"
|
||||
]
|
||||
},
|
||||
"set-font-color-alpha": {
|
||||
"args": [
|
||||
"idx",
|
||||
@@ -12350,6 +12554,14 @@
|
||||
},
|
||||
"vector+!": {},
|
||||
"vector-!": {},
|
||||
"vector-3pt-cross!": {
|
||||
"args": [
|
||||
"dst",
|
||||
"p0",
|
||||
"p1",
|
||||
"p2"
|
||||
]
|
||||
},
|
||||
"vector-angle<-quaternion!": {
|
||||
"args": [
|
||||
"axis-angle",
|
||||
@@ -12360,6 +12572,8 @@
|
||||
"f30-0": "inv-mag"
|
||||
}
|
||||
},
|
||||
"vector-circle-tangent": {},
|
||||
"vector-circle-tangent-new": {},
|
||||
"vector-copy!": {},
|
||||
"vector-cross!": {},
|
||||
"vector-deg-lerp-clamp!": {
|
||||
@@ -12370,6 +12584,30 @@
|
||||
"in"
|
||||
]
|
||||
},
|
||||
"vector-deg-seek": {
|
||||
"args": [
|
||||
"dst",
|
||||
"vec0",
|
||||
"vec1",
|
||||
"max-rot"
|
||||
],
|
||||
"vars": {
|
||||
"s4-0": "mat"
|
||||
}
|
||||
},
|
||||
"vector-deg-slerp": {
|
||||
"args": [
|
||||
"dst",
|
||||
"a",
|
||||
"b",
|
||||
"frac"
|
||||
],
|
||||
"vars": {
|
||||
"a2-3": "b-dir",
|
||||
"s1-0": "rot",
|
||||
"s2-0": "a-dir"
|
||||
}
|
||||
},
|
||||
"vector-dot": {},
|
||||
"vector-dot-vu": {},
|
||||
"vector-flatten!": {
|
||||
@@ -12395,6 +12633,33 @@
|
||||
"alpha"
|
||||
]
|
||||
},
|
||||
"vector-line-distance": {
|
||||
"args": [
|
||||
"query",
|
||||
"p0",
|
||||
"p1"
|
||||
],
|
||||
"vars": {
|
||||
"a1-3": "line-dir",
|
||||
"f0-1": "frac",
|
||||
"gp-1": "pt-offset",
|
||||
"v1-3": "offset-to-closest-point"
|
||||
}
|
||||
},
|
||||
"vector-line-distance-point!": {
|
||||
"args": [
|
||||
"query",
|
||||
"p0",
|
||||
"p1",
|
||||
"ret"
|
||||
],
|
||||
"vars": {
|
||||
"a1-3": "line-dir",
|
||||
"f0-1": "frac",
|
||||
"s4-1": "query-offset",
|
||||
"v1-4": "closest-pt-offset"
|
||||
}
|
||||
},
|
||||
"vector-matrix*!": {
|
||||
"args": [
|
||||
"dst",
|
||||
@@ -12402,6 +12667,14 @@
|
||||
"mat"
|
||||
]
|
||||
},
|
||||
"vector-orient-by-quat!": {
|
||||
"args": [
|
||||
"dst",
|
||||
"src",
|
||||
"rot"
|
||||
]
|
||||
},
|
||||
"vector-plane-distance": {},
|
||||
"vector-reflect!": {
|
||||
"args": [
|
||||
"dst",
|
||||
@@ -12498,6 +12771,7 @@
|
||||
"f3-7": "step-scale"
|
||||
}
|
||||
},
|
||||
"vector-segment-distance-point!": {},
|
||||
"vector-v*float!": {
|
||||
"args": [
|
||||
"delta-p",
|
||||
@@ -12533,6 +12807,22 @@
|
||||
"velocity"
|
||||
]
|
||||
},
|
||||
"vector-vector-deg-slerp!": {
|
||||
"args": [
|
||||
"dst",
|
||||
"a",
|
||||
"b",
|
||||
"frac",
|
||||
"up"
|
||||
],
|
||||
"vars": {
|
||||
"a1-6": "ab",
|
||||
"a2-5": "b-quat",
|
||||
"s0-0": "a-dir",
|
||||
"s0-1": "a-quat",
|
||||
"s1-0": "b-dir"
|
||||
}
|
||||
},
|
||||
"vector-x-angle": {
|
||||
"args": [
|
||||
"vec"
|
||||
|
||||
@@ -1590,6 +1590,26 @@
|
||||
"entity-actor-lookup",
|
||||
"Get an `entity-actor` from a lump by name and index, return #f if invalid.\nThe lump can contain either an actor name or aid."
|
||||
],
|
||||
"forward-down->inv-matrix": [
|
||||
"forward-down->inv-matrix",
|
||||
"Create transformation matrix where `fwd` is forward and `dn` is down. If these are not perpendicular, `fwd`\ndetermines the pitch of the frame."
|
||||
],
|
||||
"forward-down-nopitch->inv-matrix": [
|
||||
"forward-down-nopitch->inv-matrix",
|
||||
"Create transformation matrix where `fwd` is forward and `dn` is down. If these are not perpendicular, `dn`\ndetermines the pitch of the frame."
|
||||
],
|
||||
"forward-up->quaternion": [
|
||||
"forward-up->quaternion",
|
||||
"Create the inverse transform for a coordinate frame where `fwd` is forward and `up` is up.\nUses the pitch of `fwd` to determine pitch when axes aren't perpendicular."
|
||||
],
|
||||
"forward-up-nopitch->inv-matrix": [
|
||||
"forward-up-nopitch->inv-matrix",
|
||||
"Create the inverse transform for a coordinate frame where `fwd` is forward and `up` is up.\nUses the pitch of `up` to determine pitch when axes aren't perpendicular."
|
||||
],
|
||||
"forward-up-nopitch->quaternion": [
|
||||
"forward-up-nopitch->quaternion",
|
||||
"Create the inverse transform for a coordinate frame where `fwd` is forward and `up` is up.\nUses the pitch of `up` to determine pitch when axes aren't perpendicular."
|
||||
],
|
||||
"handle->process-safe": [
|
||||
"handle->process-safe",
|
||||
"Strange unused implementation of `handle->process`."
|
||||
@@ -1626,6 +1646,30 @@
|
||||
"matrix->quaternion",
|
||||
"Convert rotation matrix to quaternion."
|
||||
],
|
||||
"matrix-from-two-vectors!": [
|
||||
"matrix-from-two-vectors!",
|
||||
"Create a rotation matrix representing the rotation between two vectors."
|
||||
],
|
||||
"matrix-from-two-vectors-max-angle!": [
|
||||
"matrix-from-two-vectors-max-angle!",
|
||||
"Create a rotation matrix representing the rotation between two vectors, allowing at most a rotation of\n`max-rot` degrees."
|
||||
],
|
||||
"matrix-from-two-vectors-max-angle-partial!": [
|
||||
"matrix-from-two-vectors-max-angle-partial!",
|
||||
"Create a rotation matrix representing fraction `frac` of the rotation between `a` and `b`, limited to\n`max-rot` degrees."
|
||||
],
|
||||
"matrix-from-two-vectors-partial-linear!": [
|
||||
"matrix-from-two-vectors-partial-linear!",
|
||||
"Create a rotation matrix representing fraction `frac` of the rotation from `a` to `b`."
|
||||
],
|
||||
"matrix-remove-z-rot": [
|
||||
"matrix-remove-z-rot",
|
||||
"Remove z rotation (roll) from a matrix. Not sure how this works, it's unused and I think it could be wrong."
|
||||
],
|
||||
"matrix-rot-diff!": [
|
||||
"matrix-rot-diff!",
|
||||
"Get the rotation between two matrices, in degrees. Returns the rotation axis in `axis`."
|
||||
],
|
||||
"matrix-with-scale->quaternion": [
|
||||
"matrix-with-scale->quaternion",
|
||||
"Convert a matrix which contains scaling in addition to rotation to a quaternion."
|
||||
@@ -1634,6 +1678,10 @@
|
||||
"name=",
|
||||
"Are `a` and `b` the same name? This works for strings and symbols."
|
||||
],
|
||||
"normal-of-plane": [
|
||||
"normal-of-plane",
|
||||
"Compute normal of plane given three points on it."
|
||||
],
|
||||
"point-in-air-box-area?": [
|
||||
"point-in-air-box-area?",
|
||||
"Check if point `x, z` (relative to air-box origin) is within the projected area of the air-box. Effectively doesn't check height."
|
||||
@@ -1646,6 +1694,10 @@
|
||||
"point-in-air?",
|
||||
"Check if `pt` is in any of the boxes."
|
||||
],
|
||||
"point-in-plane-<-point+normal!": [
|
||||
"point-in-plane-<-point+normal!",
|
||||
"Given a plane's normal and `p0` a point on the plane, generate a second point on the plane.\nThe generated point is somewhat arbitrary - it is spaced out by 1 meter on the two axes that are least\nparallel to the plane's normal (avoiding numerical issues)"
|
||||
],
|
||||
"points-in-air?": [
|
||||
"points-in-air?",
|
||||
"Are both `p1` and `p2` inside any of the boxes?"
|
||||
@@ -1702,6 +1754,14 @@
|
||||
"quaternion-float/!",
|
||||
"Divide each element in the quaternion by `x`."
|
||||
],
|
||||
"quaternion-from-two-vectors!": [
|
||||
"quaternion-from-two-vectors!",
|
||||
"Create a quaternion representing the rotation between two vectors."
|
||||
],
|
||||
"quaternion-from-two-vectors-max-angle!": [
|
||||
"quaternion-from-two-vectors-max-angle!",
|
||||
"Create a quaternion representing the rotation between two vectors, allowing at most a rotation of `max-rot`\ndegrees."
|
||||
],
|
||||
"quaternion-i!": [
|
||||
"quaternion-i!",
|
||||
"Create unit `i` quaternion."
|
||||
@@ -1782,6 +1842,10 @@
|
||||
"quaternion-rotate-z!",
|
||||
"Rotate existing quaternion along world z-axis."
|
||||
],
|
||||
"quaternion-seek": [
|
||||
"quaternion-seek",
|
||||
"Unused function to seek toward an orientation."
|
||||
],
|
||||
"quaternion-set!": [
|
||||
"quaternion-set!",
|
||||
"Set quaternion components."
|
||||
@@ -1818,6 +1882,10 @@
|
||||
"quaternion-zxy!",
|
||||
"Make a quaternion from a sequence of z, x, y axis rotations."
|
||||
],
|
||||
"set-eul!": [
|
||||
"set-eul!",
|
||||
"Set euler angles and a flag (as a float)."
|
||||
],
|
||||
"string->int": [
|
||||
"string->int",
|
||||
"String to int. Supports binary, hex, and decimal, and negatives."
|
||||
@@ -1890,14 +1958,42 @@
|
||||
"string>?",
|
||||
"In dictionary order, is a > b?"
|
||||
],
|
||||
"vector-3pt-cross!": [
|
||||
"vector-3pt-cross!",
|
||||
"Cross product of (2 - 1) and (3 - 1), giving a vector in the direction of the plane normal, but not\nmagnitude of 1."
|
||||
],
|
||||
"vector-angle<-quaternion!": [
|
||||
"vector-angle<-quaternion!",
|
||||
"Convert quaternion into axis-angle with normalized axis and angle in radians in `w`."
|
||||
],
|
||||
"vector-deg-seek": [
|
||||
"vector-deg-seek",
|
||||
"Move `vec1` toward `vec0` by rotating it by at most `max-rot` degrees."
|
||||
],
|
||||
"vector-deg-slerp": [
|
||||
"vector-deg-slerp",
|
||||
"Spherical linear interpolation of orientations represented as the z-axis of two frames."
|
||||
],
|
||||
"vector-line-distance": [
|
||||
"vector-line-distance",
|
||||
"Compute the distance between `query` and the closest point on the infinite line containing `p0` and `p1`."
|
||||
],
|
||||
"vector-line-distance-point!": [
|
||||
"vector-line-distance-point!",
|
||||
"Compute the distance between `query` and the closest point on the infinite line containing `p0` and `p1`.\nReturn the point in `ret`."
|
||||
],
|
||||
"vector-orient-by-quat!": [
|
||||
"vector-orient-by-quat!",
|
||||
"Rotate a vector by a quaternion."
|
||||
],
|
||||
"vector-rotate-y!": [
|
||||
"vector-rotate-y!",
|
||||
"Rotate a vector along the y-axis, not very efficient."
|
||||
],
|
||||
"vector-vector-deg-slerp!": [
|
||||
"vector-vector-deg-slerp!",
|
||||
"Spherical linear interpolation of coordinate frames produced by `(forward-up vec up)`."
|
||||
],
|
||||
"vector-x-angle": [
|
||||
"vector-x-angle",
|
||||
"Get the pitch angle of a vector."
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -8,6 +8,7 @@
|
||||
(deftype aphid (nav-enemy)
|
||||
((try int32)))
|
||||
|
||||
|
||||
(defskelgroup *aphid-sg*
|
||||
aphid-lurker
|
||||
aphid-lurker-lod0-jg
|
||||
@@ -17,33 +18,35 @@
|
||||
:shadow aphid-lurker-shadow-mg)
|
||||
|
||||
(defbehavior aphid-invulnerable aphid ()
|
||||
"Make aphid invulnerable."
|
||||
(logclear! (-> self nav-enemy-flags) (nav-enemy-flags navenmf5))
|
||||
(set-collide-offense (-> self collide-info) 2 (collide-offense indestructible))
|
||||
(set-collide-offense (-> self root) 2 (collide-offense indestructible))
|
||||
(none))
|
||||
|
||||
(defbehavior aphid-vulnerable aphid ()
|
||||
"remove invulnerability"
|
||||
(logior! (-> self nav-enemy-flags) (nav-enemy-flags navenmf5))
|
||||
(set-collide-offense (-> self collide-info) 2 (collide-offense touch))
|
||||
(set-collide-offense (-> self root) 2 (collide-offense touch))
|
||||
(none))
|
||||
|
||||
(defmethod attack-handler ((this aphid) (arg0 process) (arg1 event-message-block))
|
||||
(defmethod attack-handler ((this aphid) (proc process) (msg-block event-message-block))
|
||||
(cond
|
||||
((or (logtest? (-> this nav-enemy-flags) (nav-enemy-flags navenmf5)) (= arg0 (ppointer->process (-> this parent))))
|
||||
(send-event arg0 'get-attack-count 1)
|
||||
((or (logtest? (-> this nav-enemy-flags) (nav-enemy-flags navenmf5)) (= proc (ppointer->process (-> this parent))))
|
||||
(send-event proc 'get-attack-count 1)
|
||||
(logclear! (-> this mask) (process-mask actor-pause attackable))
|
||||
(go (method-of-object this nav-enemy-die)))
|
||||
(else (touch-handler this arg0 arg1))))
|
||||
(else (touch-handler this proc msg-block))))
|
||||
|
||||
(defstate nav-enemy-chase (aphid)
|
||||
:virtual #t
|
||||
:exit aphid-vulnerable
|
||||
:code
|
||||
(behavior ()
|
||||
(let ((gp-0 (cond
|
||||
((>= (-> self try) 15) 450)
|
||||
((>= (-> self try) 10) 300)
|
||||
((>= (-> self try) 5) 150)
|
||||
(else 0))))
|
||||
(let ((time-adjust (cond
|
||||
((>= (-> self try) 15) 450)
|
||||
((>= (-> self try) 10) 300)
|
||||
((>= (-> self try) 5) 150)
|
||||
(else 0))))
|
||||
(loop
|
||||
(ja-channel-push! 1 (seconds 0.05))
|
||||
(sound-play "aphid-spike-out")
|
||||
@@ -55,42 +58,42 @@
|
||||
(ja-channel-push! 1 (seconds 0.1))
|
||||
(ja :group! aphid-lurker-walk-deadly-ja)
|
||||
(ja :num-func num-func-identity :frame-num 0.0)
|
||||
(let ((f30-0 (nav-enemy-rnd-float-range 0.9 1.1))
|
||||
(s5-1 (current-time))
|
||||
(s4-1 (- (the int (nav-enemy-rnd-float-range 900.0 1440.0)) gp-0)))
|
||||
(until (time-elapsed? s5-1 s4-1)
|
||||
(let ((speed (nav-enemy-rnd-float-range 0.9 1.1)))
|
||||
(let ((invuln-start-time (current-time))
|
||||
(invuln-duration (- (the int (nav-enemy-rnd-float-range 900.0 1440.0)) time-adjust)))
|
||||
(until (time-elapsed? invuln-start-time invuln-duration)
|
||||
(suspend)
|
||||
(ja :num! (loop! speed))))
|
||||
(aphid-vulnerable)
|
||||
(ja-channel-push! 1 (seconds 0.05))
|
||||
(sound-play "aphid-spike-in")
|
||||
(ja-no-eval :group! aphid-lurker-spike-out-ja :num! (seek! 0.0) :frame-num max)
|
||||
(until (ja-done? 0)
|
||||
(suspend)
|
||||
(ja :num! (loop! f30-0))))
|
||||
(aphid-vulnerable)
|
||||
(ja-channel-push! 1 (seconds 0.05))
|
||||
(sound-play "aphid-spike-in")
|
||||
(ja-no-eval :group! aphid-lurker-spike-out-ja :num! (seek! 0.0) :frame-num max)
|
||||
(until (ja-done? 0)
|
||||
(suspend)
|
||||
(ja :num! (seek! 0.0)))
|
||||
(ja-channel-push! 1 (seconds 0.1))
|
||||
(ja :group! (-> self draw art-group data (-> self nav-info run-anim)))
|
||||
(ja :num-func num-func-identity :frame-num 0.0)
|
||||
(let ((f30-1 (nav-enemy-rnd-float-range 0.9 1.1))
|
||||
(s5-3 (current-time))
|
||||
(s4-3 (+ (the int (nav-enemy-rnd-float-range 660.0 900.0)) gp-0)))
|
||||
(until (time-elapsed? s5-3 s4-3)
|
||||
(suspend)
|
||||
(ja :num! (loop! f30-1))))))))
|
||||
(ja :num! (seek! 0.0)))
|
||||
(ja-channel-push! 1 (seconds 0.1))
|
||||
(ja :group! (-> self draw art-group data (-> self nav-info run-anim)))
|
||||
(ja :num-func num-func-identity :frame-num 0.0)
|
||||
(set! speed (nav-enemy-rnd-float-range 0.9 1.1))
|
||||
(let ((run-start (current-time))
|
||||
(run-duration (+ (the int (nav-enemy-rnd-float-range 660.0 900.0)) time-adjust)))
|
||||
(until (time-elapsed? run-start run-duration)
|
||||
(suspend)
|
||||
(ja :num! (loop! speed)))))))))
|
||||
|
||||
(defstate nav-enemy-stare (aphid)
|
||||
:virtual #t
|
||||
:code
|
||||
(behavior ()
|
||||
(set! (-> self turn-time) (seconds 0.2))
|
||||
(let ((f30-0 (nav-enemy-rnd-float-range 0.8 1.2)))
|
||||
(when (or (logtest? (-> self nav-enemy-flags) (nav-enemy-flags navenmf8))
|
||||
(let ((speed (nav-enemy-rnd-float-range 0.8 1.2)))
|
||||
(when (or (logtest? (-> self nav-enemy-flags) (nav-enemy-flags just-attacked))
|
||||
(and (nav-enemy-player-vulnerable?) (nav-enemy-rnd-percent? 0.5)))
|
||||
(ja-channel-push! 1 (seconds 0.1))
|
||||
(ja-no-eval :group! aphid-lurker-win-ja :num! (seek! max f30-0) :frame-num 0.0)
|
||||
(ja-no-eval :group! aphid-lurker-win-ja :num! (seek! max speed) :frame-num 0.0)
|
||||
(until (ja-done? 0)
|
||||
(suspend)
|
||||
(ja :num! (seek! max f30-0))))
|
||||
(ja :num! (seek! max speed))))
|
||||
(loop
|
||||
(when (not (nav-enemy-facing-player? 2730.6667))
|
||||
(logior! (-> self nav-enemy-flags) (nav-enemy-flags enable-travel))
|
||||
@@ -105,10 +108,10 @@
|
||||
(logclear! (-> self nav-enemy-flags) (nav-enemy-flags enable-travel)))
|
||||
(when (nav-enemy-rnd-percent? 0.3)
|
||||
(if (not (ja-group? aphid-lurker-win-ja)) (ja-channel-push! 1 (seconds 0.1)))
|
||||
(ja-no-eval :group! aphid-lurker-win-ja :num! (seek! max f30-0) :frame-num 0.0)
|
||||
(ja-no-eval :group! aphid-lurker-win-ja :num! (seek! max speed) :frame-num 0.0)
|
||||
(until (ja-done? 0)
|
||||
(suspend)
|
||||
(ja :num! (seek! max f30-0))))))))
|
||||
(ja :num! (seek! max speed))))))))
|
||||
|
||||
(defstate nav-enemy-give-up (aphid)
|
||||
:virtual #t
|
||||
@@ -117,9 +120,9 @@
|
||||
(set! (-> self rotate-speed) 218453.33)
|
||||
(set! (-> self turn-time) (seconds 0.5))
|
||||
(ja-channel-push! 1 (seconds 0.15))
|
||||
(let ((s4-0 (-> self collide-info))
|
||||
(s5-0 (target-pos 0)))
|
||||
(when (< (fabs (deg-diff (y-angle s4-0) (vector-y-angle (vector-! (new 'stack-no-clear 'vector) s5-0 (-> s4-0 trans)))))
|
||||
(let ((root (-> self root))
|
||||
(tgt-pos (target-pos 0)))
|
||||
(when (< (fabs (deg-diff (y-angle root) (vector-y-angle (vector-! (new 'stack-no-clear 'vector) tgt-pos (-> root trans)))))
|
||||
12743.111)
|
||||
(ja-no-eval :group! aphid-lurker-give-up-ja :num! (seek!) :frame-num 0.0)
|
||||
(until (ja-done? 0)
|
||||
@@ -129,10 +132,7 @@
|
||||
(nav-enemy-get-new-patrol-point)
|
||||
(ja-no-eval :group! aphid-lurker-give-up-hop-ja :num! (seek!) :frame-num 0.0)
|
||||
(until (ja-done? 0)
|
||||
(seek-to-point-toward-point! (-> self collide-info)
|
||||
(-> self nav destination-pos)
|
||||
(-> self rotate-speed)
|
||||
(-> self turn-time))
|
||||
(seek-to-point-toward-point! (-> self root) (-> self nav destination-pos) (-> self rotate-speed) (-> self turn-time))
|
||||
(suspend)
|
||||
(ja :num! (seek!)))
|
||||
(go-virtual nav-enemy-patrol)))
|
||||
@@ -192,25 +192,26 @@
|
||||
:debug-draw-jump #f))
|
||||
|
||||
(defmethod initialize-collision ((this aphid))
|
||||
(let ((s5-0 (new 'process 'collide-shape-moving this (collide-list-enum usually-hit-by-player))))
|
||||
(set! (-> s5-0 dynam) (copy *standard-dynamics* 'process))
|
||||
(set! (-> s5-0 reaction) default-collision-reaction)
|
||||
(set! (-> s5-0 no-reaction) (the-as (function collide-shape-moving collide-shape-intersect vector vector none) nothing))
|
||||
(let ((s4-0 (new 'process 'collide-shape-prim-sphere s5-0 (the-as uint 3))))
|
||||
(set! (-> s4-0 prim-core collide-as) (collide-kind enemy))
|
||||
(set! (-> s4-0 collide-with) (collide-kind target))
|
||||
(set! (-> s4-0 prim-core action) (collide-action solid))
|
||||
(set! (-> s4-0 prim-core offense) (collide-offense touch))
|
||||
(set-vector! (-> s4-0 local-sphere) 0.0 4096.0 0.0 4096.0)
|
||||
(set-root-prim! s5-0 s4-0))
|
||||
(set! (-> s5-0 nav-radius) 6144.0)
|
||||
(backup-collide-with-as s5-0)
|
||||
(set! (-> s5-0 max-iteration-count) (the-as uint 2))
|
||||
(set! (-> this collide-info) s5-0))
|
||||
(let ((cshape (new 'process 'collide-shape-moving this (collide-list-enum usually-hit-by-player))))
|
||||
(set! (-> cshape dynam) (copy *standard-dynamics* 'process))
|
||||
(set! (-> cshape reaction) default-collision-reaction)
|
||||
(set! (-> cshape no-reaction)
|
||||
(the-as (function collide-shape-moving collide-shape-intersect vector vector none) nothing))
|
||||
(let ((sph (new 'process 'collide-shape-prim-sphere cshape (the-as uint 3))))
|
||||
(set! (-> sph prim-core collide-as) (collide-kind enemy))
|
||||
(set! (-> sph collide-with) (collide-kind target))
|
||||
(set! (-> sph prim-core action) (collide-action solid))
|
||||
(set! (-> sph prim-core offense) (collide-offense touch))
|
||||
(set-vector! (-> sph local-sphere) 0.0 4096.0 0.0 4096.0)
|
||||
(set-root-prim! cshape sph))
|
||||
(set! (-> cshape nav-radius) 6144.0)
|
||||
(backup-collide-with-as cshape)
|
||||
(set! (-> cshape max-iteration-count) (the-as uint 2))
|
||||
(set! (-> this root) cshape))
|
||||
0
|
||||
(none))
|
||||
|
||||
(defmethod nav-enemy-method-48 ((this aphid))
|
||||
(defmethod setup-enemy! ((this aphid))
|
||||
(initialize-skeleton this *aphid-sg* '())
|
||||
(init-defaults! this *aphid-nav-enemy-info*)
|
||||
(set! (-> this neck up) (the-as uint 0))
|
||||
@@ -219,18 +220,19 @@
|
||||
0
|
||||
(none))
|
||||
|
||||
(defbehavior aphid-init-by-other aphid ((arg0 nav-enemy) (arg1 vector) (arg2 vector))
|
||||
(defbehavior aphid-init-by-other aphid ((parent-boss nav-enemy) (pos vector) (dest vector))
|
||||
"Initialize an `aphid` and enter chase state."
|
||||
(initialize-collision self)
|
||||
(logior! (-> self mask) (process-mask actor-pause))
|
||||
(set! (-> self collide-info trans quad) (-> arg1 quad))
|
||||
(set! (-> self event-param-point quad) (-> arg2 quad))
|
||||
(let ((s3-1 (vector-! (new 'stack-no-clear 'vector) arg2 arg1)))
|
||||
(set! (-> s3-1 y) 0.0)
|
||||
(vector-normalize! s3-1 1.0)
|
||||
(forward-up->quaternion (-> self collide-info quat) s3-1 *up-vector*))
|
||||
(vector-identity! (-> self collide-info scale))
|
||||
(set! (-> self entity) (-> arg0 entity))
|
||||
(nav-enemy-method-48 self)
|
||||
(set! (-> self root trans quad) (-> pos quad))
|
||||
(set! (-> self event-param-point quad) (-> dest quad))
|
||||
(let ((to-dest (vector-! (new 'stack-no-clear 'vector) dest pos)))
|
||||
(set! (-> to-dest y) 0.0)
|
||||
(vector-normalize! to-dest 1.0)
|
||||
(forward-up->quaternion (-> self root quat) to-dest *up-vector*))
|
||||
(vector-identity! (-> self root scale))
|
||||
(set! (-> self entity) (-> parent-boss entity))
|
||||
(setup-enemy! self)
|
||||
(logclear! (-> self nav-enemy-flags) (nav-enemy-flags navenmf12))
|
||||
(set! (-> self try) (the-as int (send-event (ppointer->process (-> self parent)) 'try)))
|
||||
(go-virtual nav-enemy-chase)
|
||||
|
||||
Executable
+999
@@ -0,0 +1,999 @@
|
||||
#!/usr/bin/env python3
|
||||
"""Qt editor for decompiler var_names and method docstrings."""
|
||||
|
||||
import argparse
|
||||
import bisect
|
||||
import json
|
||||
import re
|
||||
import sys
|
||||
from dataclasses import dataclass, field
|
||||
from pathlib import Path
|
||||
|
||||
from PyQt5 import QtCore, QtGui, QtWidgets
|
||||
|
||||
|
||||
DEFAULT_VAR_NAMES = Path("decompiler/config/jak1/ntsc_v1/var_names.jsonc")
|
||||
DEFAULT_METHOD_DOCS = Path("docs/jak1_cleanup_inputs/method_names.jsonc")
|
||||
|
||||
FUNCTION_COMMENT_RE = re.compile(
|
||||
r"^;; definition (?:\(debug\) )?for (?:function|behavior) (?P<name>.+?)\s*$"
|
||||
)
|
||||
METHOD_COMMENT_RE = re.compile(r"^;; definition for method (?P<id>\d+) of type (?P<type>\S+)\s*$")
|
||||
DEF_HEADER_RE = re.compile(r"\((?:defun|defun-debug|defmethod|defbehavior)\s+(?P<name>\S+)")
|
||||
STATE_HEADER_RE = re.compile(
|
||||
r"^[ \t]*\(\s*defstate\s+(?P<state>\S+)\s+\(\s*(?P<type>\S+)\s*\)", re.MULTILINE
|
||||
)
|
||||
BEHAVIOR_HEADER_RE = re.compile(r"\(\s*behavior\b")
|
||||
LOCAL_VAR_RE = re.compile(r"\b(?:(?:[avts]\d)|(?:f\d+)|(?:sv\d*)|gp|sp|fp|ra|r0|pp)-\d+\b")
|
||||
STATE_HANDLER_NAMES = ("event", "enter", "post", "trans", "exit", "code")
|
||||
|
||||
|
||||
@dataclass
|
||||
class DefinitionBlock:
|
||||
key: str
|
||||
kind: str
|
||||
display_name: str
|
||||
text: str
|
||||
start_line: int
|
||||
end_line: int
|
||||
method_id: int | None = None
|
||||
type_name: str | None = None
|
||||
args: list[str] = field(default_factory=list)
|
||||
|
||||
@property
|
||||
def label(self) -> str:
|
||||
return f"{self.kind:8} {self.key} ({self.start_line}-{self.end_line})"
|
||||
|
||||
|
||||
def strip_jsonc(text: str) -> str:
|
||||
out = []
|
||||
i = 0
|
||||
in_string = False
|
||||
escaped = False
|
||||
|
||||
while i < len(text):
|
||||
char = text[i]
|
||||
if in_string:
|
||||
out.append(char)
|
||||
if escaped:
|
||||
escaped = False
|
||||
elif char == "\\":
|
||||
escaped = True
|
||||
elif char == '"':
|
||||
in_string = False
|
||||
i += 1
|
||||
continue
|
||||
|
||||
if char == '"':
|
||||
in_string = True
|
||||
out.append(char)
|
||||
i += 1
|
||||
continue
|
||||
|
||||
if char == "/" and i + 1 < len(text) and text[i + 1] == "/":
|
||||
i += 2
|
||||
while i < len(text) and text[i] not in "\r\n":
|
||||
i += 1
|
||||
continue
|
||||
|
||||
if char == "/" and i + 1 < len(text) and text[i + 1] == "*":
|
||||
i += 2
|
||||
while i + 1 < len(text) and not (text[i] == "*" and text[i + 1] == "/"):
|
||||
i += 1
|
||||
i += 2
|
||||
continue
|
||||
|
||||
out.append(char)
|
||||
i += 1
|
||||
|
||||
return "".join(out)
|
||||
|
||||
|
||||
def remove_trailing_commas(text: str) -> str:
|
||||
out = []
|
||||
i = 0
|
||||
in_string = False
|
||||
escaped = False
|
||||
|
||||
while i < len(text):
|
||||
char = text[i]
|
||||
if in_string:
|
||||
out.append(char)
|
||||
if escaped:
|
||||
escaped = False
|
||||
elif char == "\\":
|
||||
escaped = True
|
||||
elif char == '"':
|
||||
in_string = False
|
||||
i += 1
|
||||
continue
|
||||
|
||||
if char == '"':
|
||||
in_string = True
|
||||
out.append(char)
|
||||
i += 1
|
||||
continue
|
||||
|
||||
if char == ",":
|
||||
j = i + 1
|
||||
while j < len(text) and text[j].isspace():
|
||||
j += 1
|
||||
if j < len(text) and text[j] in "}]":
|
||||
i += 1
|
||||
continue
|
||||
|
||||
out.append(char)
|
||||
i += 1
|
||||
|
||||
return "".join(out)
|
||||
|
||||
|
||||
def load_jsonc(path: Path) -> dict:
|
||||
if not path or not path.exists():
|
||||
return {}
|
||||
return json.loads(remove_trailing_commas(strip_jsonc(path.read_text())))
|
||||
|
||||
|
||||
def save_json(path: Path, data: dict) -> None:
|
||||
path.write_text(json.dumps(data, indent=2, sort_keys=True) + "\n")
|
||||
|
||||
|
||||
def monospace_font() -> QtGui.QFont:
|
||||
font = QtGui.QFont("monospace")
|
||||
font.setStyleHint(QtGui.QFont.Monospace)
|
||||
return font
|
||||
|
||||
|
||||
def sexpr_text_from_header(text: str) -> str:
|
||||
start = text.find("(def")
|
||||
if start < 0:
|
||||
return ""
|
||||
|
||||
depth = 0
|
||||
in_string = False
|
||||
escaped = False
|
||||
for idx, char in enumerate(text[start:], start):
|
||||
if in_string:
|
||||
if escaped:
|
||||
escaped = False
|
||||
elif char == "\\":
|
||||
escaped = True
|
||||
elif char == '"':
|
||||
in_string = False
|
||||
continue
|
||||
|
||||
if char == '"':
|
||||
in_string = True
|
||||
elif char == "(":
|
||||
depth += 1
|
||||
elif char == ")":
|
||||
depth -= 1
|
||||
if depth == 0:
|
||||
return text[start : idx + 1]
|
||||
return ""
|
||||
|
||||
|
||||
def first_list_after_def_name(header: str) -> str:
|
||||
if not header:
|
||||
return ""
|
||||
header_match = DEF_HEADER_RE.search(header)
|
||||
if not header_match:
|
||||
return ""
|
||||
start = header.find("(", header_match.end())
|
||||
if start < 0:
|
||||
return ""
|
||||
|
||||
depth = 0
|
||||
in_string = False
|
||||
escaped = False
|
||||
for idx, char in enumerate(header[start:], start):
|
||||
if in_string:
|
||||
if escaped:
|
||||
escaped = False
|
||||
elif char == "\\":
|
||||
escaped = True
|
||||
elif char == '"':
|
||||
in_string = False
|
||||
continue
|
||||
|
||||
if char == '"':
|
||||
in_string = True
|
||||
elif char == "(":
|
||||
depth += 1
|
||||
elif char == ")":
|
||||
depth -= 1
|
||||
if depth == 0:
|
||||
return header[start : idx + 1]
|
||||
return ""
|
||||
|
||||
|
||||
def parse_arg_names_from_list(arg_list: str) -> list[str]:
|
||||
if len(arg_list) < 2:
|
||||
return []
|
||||
|
||||
args = []
|
||||
inner = arg_list[1:-1]
|
||||
depth = 0
|
||||
start = None
|
||||
bare_token_start = None
|
||||
for idx, char in enumerate(inner):
|
||||
if char == "(":
|
||||
if depth == 0 and bare_token_start is not None:
|
||||
args.append(inner[bare_token_start:idx].strip())
|
||||
bare_token_start = None
|
||||
if depth == 0:
|
||||
start = idx
|
||||
depth += 1
|
||||
elif char == ")":
|
||||
depth -= 1
|
||||
if depth == 0 and start is not None:
|
||||
arg_expr = inner[start : idx + 1]
|
||||
arg_match = re.match(r"\(\s*(\S+)", arg_expr)
|
||||
if arg_match:
|
||||
args.append(arg_match.group(1))
|
||||
start = None
|
||||
elif depth == 0 and char.isspace():
|
||||
if bare_token_start is not None:
|
||||
args.append(inner[bare_token_start:idx].strip())
|
||||
bare_token_start = None
|
||||
elif depth == 0 and bare_token_start is None:
|
||||
bare_token_start = idx
|
||||
|
||||
if bare_token_start is not None:
|
||||
args.append(inner[bare_token_start:].strip())
|
||||
|
||||
return [arg for arg in args if arg]
|
||||
|
||||
|
||||
def parse_args_from_header(header: str) -> list[str]:
|
||||
return parse_arg_names_from_list(first_list_after_def_name(header))
|
||||
|
||||
|
||||
def first_list_after_behavior_name(header: str) -> str:
|
||||
if not header:
|
||||
return ""
|
||||
header_match = BEHAVIOR_HEADER_RE.search(header)
|
||||
if not header_match:
|
||||
return ""
|
||||
start = header.find("(", header_match.end())
|
||||
if start < 0:
|
||||
return ""
|
||||
|
||||
depth = 0
|
||||
in_string = False
|
||||
escaped = False
|
||||
for idx, char in enumerate(header[start:], start):
|
||||
if in_string:
|
||||
if escaped:
|
||||
escaped = False
|
||||
elif char == "\\":
|
||||
escaped = True
|
||||
elif char == '"':
|
||||
in_string = False
|
||||
continue
|
||||
|
||||
if char == '"':
|
||||
in_string = True
|
||||
elif char == "(":
|
||||
depth += 1
|
||||
elif char == ")":
|
||||
depth -= 1
|
||||
if depth == 0:
|
||||
return header[start : idx + 1]
|
||||
return ""
|
||||
|
||||
|
||||
def parse_args_from_behavior(header: str) -> list[str]:
|
||||
return parse_arg_names_from_list(first_list_after_behavior_name(header))
|
||||
|
||||
|
||||
def line_start_offsets(text: str) -> list[int]:
|
||||
starts = [0]
|
||||
for idx, char in enumerate(text):
|
||||
if char == "\n":
|
||||
starts.append(idx + 1)
|
||||
return starts
|
||||
|
||||
|
||||
def line_number_for_offset(starts: list[int], offset: int) -> int:
|
||||
return bisect.bisect_right(starts, offset)
|
||||
|
||||
|
||||
def skip_ws_and_line_comments(text: str, pos: int, end: int | None = None) -> int:
|
||||
if end is None:
|
||||
end = len(text)
|
||||
while pos < end:
|
||||
if text[pos].isspace():
|
||||
pos += 1
|
||||
continue
|
||||
if text[pos] == ";":
|
||||
newline = text.find("\n", pos, end)
|
||||
if newline < 0:
|
||||
return end
|
||||
pos = newline + 1
|
||||
continue
|
||||
break
|
||||
return pos
|
||||
|
||||
|
||||
def find_matching_paren(text: str, open_idx: int, limit: int | None = None) -> int:
|
||||
if limit is None:
|
||||
limit = len(text)
|
||||
|
||||
depth = 0
|
||||
in_string = False
|
||||
escaped = False
|
||||
in_line_comment = False
|
||||
idx = open_idx
|
||||
while idx < limit:
|
||||
char = text[idx]
|
||||
if in_line_comment:
|
||||
if char == "\n":
|
||||
in_line_comment = False
|
||||
idx += 1
|
||||
continue
|
||||
|
||||
if in_string:
|
||||
if escaped:
|
||||
escaped = False
|
||||
elif char == "\\":
|
||||
escaped = True
|
||||
elif char == '"':
|
||||
in_string = False
|
||||
idx += 1
|
||||
continue
|
||||
|
||||
if char == ";":
|
||||
in_line_comment = True
|
||||
elif char == '"':
|
||||
in_string = True
|
||||
elif char == "(":
|
||||
depth += 1
|
||||
elif char == ")":
|
||||
depth -= 1
|
||||
if depth == 0:
|
||||
return idx
|
||||
idx += 1
|
||||
|
||||
return -1
|
||||
|
||||
|
||||
def read_expr_range(text: str, pos: int, limit: int) -> tuple[int, int] | None:
|
||||
pos = skip_ws_and_line_comments(text, pos, limit)
|
||||
if pos >= limit:
|
||||
return None
|
||||
|
||||
if text[pos] == "(":
|
||||
end = find_matching_paren(text, pos, limit)
|
||||
if end < 0:
|
||||
return None
|
||||
return pos, end + 1
|
||||
|
||||
end = pos
|
||||
while end < limit and not text[end].isspace() and text[end] not in "()":
|
||||
end += 1
|
||||
return pos, end
|
||||
|
||||
|
||||
def iter_state_handler_exprs(text: str, form_start: int, form_end: int):
|
||||
depth = 1
|
||||
in_string = False
|
||||
escaped = False
|
||||
in_line_comment = False
|
||||
idx = form_start + 1
|
||||
|
||||
while idx < form_end:
|
||||
char = text[idx]
|
||||
if in_line_comment:
|
||||
if char == "\n":
|
||||
in_line_comment = False
|
||||
idx += 1
|
||||
continue
|
||||
|
||||
if in_string:
|
||||
if escaped:
|
||||
escaped = False
|
||||
elif char == "\\":
|
||||
escaped = True
|
||||
elif char == '"':
|
||||
in_string = False
|
||||
idx += 1
|
||||
continue
|
||||
|
||||
if char == ";":
|
||||
in_line_comment = True
|
||||
idx += 1
|
||||
continue
|
||||
if char == '"':
|
||||
in_string = True
|
||||
idx += 1
|
||||
continue
|
||||
|
||||
if depth == 1 and char == ":":
|
||||
for handler in STATE_HANDLER_NAMES:
|
||||
keyword = f":{handler}"
|
||||
keyword_end = idx + len(keyword)
|
||||
if text.startswith(keyword, idx) and (
|
||||
keyword_end >= form_end or text[keyword_end].isspace() or text[keyword_end] in "()"
|
||||
):
|
||||
expr_range = read_expr_range(text, keyword_end, form_end)
|
||||
if expr_range:
|
||||
expr_start, expr_end = expr_range
|
||||
yield handler, idx, expr_start, expr_end
|
||||
idx = expr_end
|
||||
break
|
||||
else:
|
||||
idx += 1
|
||||
continue
|
||||
|
||||
if char == "(":
|
||||
depth += 1
|
||||
elif char == ")":
|
||||
depth -= 1
|
||||
idx += 1
|
||||
|
||||
|
||||
def parse_state_blocks(path: Path) -> list[DefinitionBlock]:
|
||||
text = path.read_text()
|
||||
starts = line_start_offsets(text)
|
||||
blocks = []
|
||||
|
||||
for match in STATE_HEADER_RE.finditer(text):
|
||||
form_start = match.start()
|
||||
form_end = find_matching_paren(text, form_start)
|
||||
if form_end < 0:
|
||||
continue
|
||||
|
||||
state_name = match.group("state")
|
||||
type_name = match.group("type")
|
||||
for handler, keyword_start, expr_start, expr_end in iter_state_handler_exprs(text, form_start, form_end):
|
||||
expr_text = text[expr_start:expr_end]
|
||||
if not BEHAVIOR_HEADER_RE.match(expr_text.lstrip()):
|
||||
continue
|
||||
|
||||
block_text = f";; state {handler} for {state_name} ({type_name})\n{text[keyword_start:expr_end]}\n"
|
||||
key = f"({handler} {state_name} {type_name})"
|
||||
blocks.append(
|
||||
DefinitionBlock(
|
||||
key=key,
|
||||
kind=f"state-{handler}",
|
||||
display_name=f"{handler} {state_name} {type_name}",
|
||||
text=block_text,
|
||||
start_line=line_number_for_offset(starts, keyword_start),
|
||||
end_line=line_number_for_offset(starts, expr_end),
|
||||
type_name=type_name,
|
||||
args=parse_args_from_behavior(expr_text),
|
||||
)
|
||||
)
|
||||
|
||||
return blocks
|
||||
|
||||
|
||||
def parse_display_name(header: str, fallback: str) -> str:
|
||||
match = DEF_HEADER_RE.search(header)
|
||||
return match.group("name") if match else fallback
|
||||
|
||||
|
||||
def parse_disasm(path: Path) -> list[DefinitionBlock]:
|
||||
lines = path.read_text().splitlines()
|
||||
starts = []
|
||||
|
||||
for idx, line in enumerate(lines):
|
||||
function_match = FUNCTION_COMMENT_RE.match(line)
|
||||
if function_match:
|
||||
name = function_match.group("name")
|
||||
starts.append((idx, "function", name, None, None))
|
||||
continue
|
||||
|
||||
method_match = METHOD_COMMENT_RE.match(line)
|
||||
if method_match:
|
||||
method_id = int(method_match.group("id"))
|
||||
type_name = method_match.group("type")
|
||||
key = f"(method {method_id} {type_name})"
|
||||
starts.append((idx, "method", key, method_id, type_name))
|
||||
|
||||
blocks = []
|
||||
for start_idx, (line_idx, kind, key, method_id, type_name) in enumerate(starts):
|
||||
end_idx = starts[start_idx + 1][0] if start_idx + 1 < len(starts) else len(lines)
|
||||
text = "\n".join(lines[line_idx:end_idx])
|
||||
if end_idx > line_idx:
|
||||
text += "\n"
|
||||
header = sexpr_text_from_header(text)
|
||||
blocks.append(
|
||||
DefinitionBlock(
|
||||
key=key,
|
||||
kind=kind,
|
||||
display_name=parse_display_name(header, key),
|
||||
text=text,
|
||||
start_line=line_idx + 1,
|
||||
end_line=end_idx,
|
||||
method_id=method_id,
|
||||
type_name=type_name,
|
||||
args=parse_args_from_header(header),
|
||||
)
|
||||
)
|
||||
|
||||
blocks.extend(parse_state_blocks(path))
|
||||
blocks.sort(key=lambda block: (block.start_line, block.end_line, block.kind))
|
||||
return blocks
|
||||
|
||||
|
||||
def get_local_override_name(value):
|
||||
if isinstance(value, str):
|
||||
return value
|
||||
if isinstance(value, list) and value:
|
||||
return value[0]
|
||||
return None
|
||||
|
||||
|
||||
def set_local_override(entry: dict, var_id: str, new_name: str) -> None:
|
||||
vars_entry = entry.setdefault("vars", {})
|
||||
existing = vars_entry.get(var_id)
|
||||
if isinstance(existing, list) and len(existing) >= 2:
|
||||
updated = list(existing)
|
||||
updated[0] = new_name
|
||||
vars_entry[var_id] = updated
|
||||
else:
|
||||
vars_entry[var_id] = new_name
|
||||
|
||||
|
||||
class DefinitionTextEdit(QtWidgets.QTextEdit):
|
||||
clicked_at = QtCore.pyqtSignal(QtCore.QPoint)
|
||||
|
||||
def mousePressEvent(self, event):
|
||||
super().mousePressEvent(event)
|
||||
self.clicked_at.emit(event.pos())
|
||||
|
||||
|
||||
class ColumnGuideTextEdit(QtWidgets.QTextEdit):
|
||||
def __init__(self, column: int, parent=None):
|
||||
super().__init__(parent)
|
||||
self.column = column
|
||||
self.setFont(monospace_font())
|
||||
self.setLineWrapMode(QtWidgets.QTextEdit.NoWrap)
|
||||
|
||||
def paintEvent(self, event):
|
||||
super().paintEvent(event)
|
||||
font_metrics = QtGui.QFontMetrics(self.font())
|
||||
column_x = (
|
||||
self.document().documentMargin()
|
||||
+ font_metrics.horizontalAdvance(" ") * self.column
|
||||
- self.horizontalScrollBar().value()
|
||||
)
|
||||
if column_x < 0 or column_x > self.viewport().width():
|
||||
return
|
||||
|
||||
painter = QtGui.QPainter(self.viewport())
|
||||
pen = QtGui.QPen(QtGui.QColor("#d1d5db"))
|
||||
pen.setWidth(1)
|
||||
painter.setPen(pen)
|
||||
x = int(column_x)
|
||||
painter.drawLine(x, 0, x, self.viewport().height())
|
||||
|
||||
|
||||
class VarNameEditor(QtWidgets.QMainWindow):
|
||||
def __init__(self, args):
|
||||
super().__init__()
|
||||
self.setWindowTitle("OpenGOAL Variable Name Editor")
|
||||
self.resize(1320, 840)
|
||||
|
||||
self.disasm_path = Path(args.disasm).resolve() if args.disasm else None
|
||||
self.var_names_path = Path(args.var_names).resolve() if args.var_names else DEFAULT_VAR_NAMES.resolve()
|
||||
self.method_docs_path = (
|
||||
Path(args.method_docs).resolve() if args.method_docs else DEFAULT_METHOD_DOCS.resolve()
|
||||
)
|
||||
|
||||
self.blocks: list[DefinitionBlock] = []
|
||||
self.filtered_blocks: list[DefinitionBlock] = []
|
||||
self.current_block: DefinitionBlock | None = None
|
||||
self.var_names = {}
|
||||
self.method_docs = {}
|
||||
self.disasm_files: list[Path] = []
|
||||
self.filtered_disasm_files: list[Path] = []
|
||||
self.token_ranges = []
|
||||
self.selected_token = None
|
||||
self.dirty_vars = False
|
||||
self.dirty_docs = False
|
||||
|
||||
self._build_ui()
|
||||
self._load_initial_files()
|
||||
|
||||
def _build_ui(self) -> None:
|
||||
root = QtWidgets.QWidget()
|
||||
self.setCentralWidget(root)
|
||||
layout = QtWidgets.QVBoxLayout(root)
|
||||
|
||||
toolbar = QtWidgets.QHBoxLayout()
|
||||
layout.addLayout(toolbar)
|
||||
|
||||
open_disasm = QtWidgets.QPushButton("Open Disasm")
|
||||
open_disasm.clicked.connect(self.open_disasm)
|
||||
toolbar.addWidget(open_disasm)
|
||||
self.disasm_edit = QtWidgets.QLineEdit()
|
||||
toolbar.addWidget(self.disasm_edit, 2)
|
||||
|
||||
load_vars = QtWidgets.QPushButton("Load Vars")
|
||||
load_vars.clicked.connect(self.load_var_names_dialog)
|
||||
toolbar.addWidget(load_vars)
|
||||
self.var_path_edit = QtWidgets.QLineEdit()
|
||||
toolbar.addWidget(self.var_path_edit, 2)
|
||||
save_vars = QtWidgets.QPushButton("Save Vars")
|
||||
save_vars.clicked.connect(self.save_var_names)
|
||||
toolbar.addWidget(save_vars)
|
||||
|
||||
load_docs = QtWidgets.QPushButton("Load Docs")
|
||||
load_docs.clicked.connect(self.load_method_docs_dialog)
|
||||
toolbar.addWidget(load_docs)
|
||||
self.docs_path_edit = QtWidgets.QLineEdit()
|
||||
toolbar.addWidget(self.docs_path_edit, 2)
|
||||
save_docs = QtWidgets.QPushButton("Save Docs")
|
||||
save_docs.clicked.connect(self.save_method_docs)
|
||||
toolbar.addWidget(save_docs)
|
||||
|
||||
filter_layout = QtWidgets.QHBoxLayout()
|
||||
layout.addLayout(filter_layout)
|
||||
filter_layout.addWidget(QtWidgets.QLabel("Definition Filter"))
|
||||
self.filter_edit = QtWidgets.QLineEdit()
|
||||
self.filter_edit.textChanged.connect(self.refresh_definition_list)
|
||||
filter_layout.addWidget(self.filter_edit)
|
||||
|
||||
splitter = QtWidgets.QSplitter(QtCore.Qt.Horizontal)
|
||||
layout.addWidget(splitter, 1)
|
||||
|
||||
file_panel = QtWidgets.QWidget()
|
||||
file_layout = QtWidgets.QVBoxLayout(file_panel)
|
||||
file_layout.setContentsMargins(0, 0, 0, 0)
|
||||
file_layout.addWidget(QtWidgets.QLabel("File Filter"))
|
||||
self.file_filter_edit = QtWidgets.QLineEdit()
|
||||
self.file_filter_edit.setPlaceholderText("Partial filename")
|
||||
self.file_filter_edit.textChanged.connect(lambda _text: self.apply_file_filter())
|
||||
file_layout.addWidget(self.file_filter_edit)
|
||||
self.file_list = QtWidgets.QListWidget()
|
||||
self.file_list.currentRowChanged.connect(self.on_file_selected)
|
||||
file_layout.addWidget(self.file_list, 1)
|
||||
splitter.addWidget(file_panel)
|
||||
|
||||
self.definition_list = QtWidgets.QListWidget()
|
||||
self.definition_list.currentRowChanged.connect(self.on_definition_selected)
|
||||
splitter.addWidget(self.definition_list)
|
||||
|
||||
self.text = DefinitionTextEdit()
|
||||
self.text.setReadOnly(False)
|
||||
self.text.setLineWrapMode(QtWidgets.QTextEdit.NoWrap)
|
||||
self.text.setFont(monospace_font())
|
||||
self.text.clicked_at.connect(self.on_text_click)
|
||||
splitter.addWidget(self.text)
|
||||
splitter.setStretchFactor(0, 1)
|
||||
splitter.setStretchFactor(1, 2)
|
||||
splitter.setStretchFactor(2, 6)
|
||||
|
||||
bottom = QtWidgets.QGridLayout()
|
||||
layout.addLayout(bottom)
|
||||
bottom.addWidget(QtWidgets.QLabel("Selected"), 0, 0)
|
||||
self.selected_edit = QtWidgets.QLineEdit()
|
||||
self.selected_edit.setReadOnly(True)
|
||||
bottom.addWidget(self.selected_edit, 0, 1)
|
||||
bottom.addWidget(QtWidgets.QLabel("Rename To"), 0, 2)
|
||||
self.rename_edit = QtWidgets.QLineEdit()
|
||||
self.rename_edit.returnPressed.connect(self.apply_rename)
|
||||
bottom.addWidget(self.rename_edit, 0, 3)
|
||||
apply_rename = QtWidgets.QPushButton("Apply Rename")
|
||||
apply_rename.clicked.connect(self.apply_rename)
|
||||
bottom.addWidget(apply_rename, 0, 4)
|
||||
|
||||
doc_group = QtWidgets.QGroupBox("Function/Method Docstring")
|
||||
doc_layout = QtWidgets.QHBoxLayout(doc_group)
|
||||
self.doc_text = ColumnGuideTextEdit(110)
|
||||
self.doc_text.setFixedHeight(90)
|
||||
doc_layout.addWidget(self.doc_text, 1)
|
||||
apply_doc = QtWidgets.QPushButton("Apply Docstring")
|
||||
apply_doc.clicked.connect(self.apply_docstring)
|
||||
doc_layout.addWidget(apply_doc)
|
||||
bottom.addWidget(doc_group, 1, 0, 1, 5)
|
||||
|
||||
self.statusBar()
|
||||
|
||||
def _load_initial_files(self) -> None:
|
||||
self.var_path_edit.setText(str(self.var_names_path))
|
||||
self.docs_path_edit.setText(str(self.method_docs_path))
|
||||
self.var_names = load_jsonc(self.var_names_path)
|
||||
self.method_docs = load_jsonc(self.method_docs_path)
|
||||
if self.disasm_path:
|
||||
self.load_disasm(self.disasm_path)
|
||||
else:
|
||||
self.set_status("Open a *_disasm.gc file to begin.")
|
||||
|
||||
def set_status(self, text: str) -> None:
|
||||
self.statusBar().showMessage(text)
|
||||
|
||||
def open_disasm(self) -> None:
|
||||
path, _ = QtWidgets.QFileDialog.getOpenFileName(
|
||||
self, "Open decompiler output", "", "GOAL disassembly (*_disasm.gc);;GOAL (*.gc);;All (*)"
|
||||
)
|
||||
if path:
|
||||
self.load_disasm(Path(path))
|
||||
|
||||
def load_disasm(self, path: Path) -> None:
|
||||
self.disasm_path = path.resolve()
|
||||
self.disasm_edit.setText(str(self.disasm_path))
|
||||
self.refresh_file_list(self.disasm_path.parent, self.disasm_path)
|
||||
self.blocks = parse_disasm(self.disasm_path)
|
||||
self.refresh_definition_list()
|
||||
if self.blocks:
|
||||
self.definition_list.setCurrentRow(0)
|
||||
self.set_status(f"Loaded {len(self.blocks)} definitions from {self.disasm_path}")
|
||||
|
||||
def refresh_file_list(self, directory: Path, current_path: Path | None = None) -> None:
|
||||
self.disasm_files = sorted(directory.glob("*_disasm.gc"))
|
||||
self.apply_file_filter(current_path)
|
||||
|
||||
def apply_file_filter(self, current_path: Path | None = None) -> None:
|
||||
query = self.file_filter_edit.text().strip().lower()
|
||||
self.filtered_disasm_files = [
|
||||
path for path in self.disasm_files if not query or query in path.name.lower()
|
||||
]
|
||||
self.file_list.blockSignals(True)
|
||||
self.file_list.clear()
|
||||
selected_row = -1
|
||||
active_path = current_path or self.disasm_path
|
||||
for index, path in enumerate(self.filtered_disasm_files):
|
||||
self.file_list.addItem(path.name)
|
||||
if active_path and path.resolve() == active_path.resolve():
|
||||
selected_row = index
|
||||
if selected_row >= 0:
|
||||
self.file_list.setCurrentRow(selected_row)
|
||||
self.file_list.blockSignals(False)
|
||||
|
||||
def on_file_selected(self, row: int) -> None:
|
||||
if row < 0 or row >= len(self.filtered_disasm_files):
|
||||
return
|
||||
path = self.filtered_disasm_files[row]
|
||||
if self.disasm_path and path.resolve() == self.disasm_path.resolve():
|
||||
return
|
||||
self.load_disasm(path)
|
||||
|
||||
def load_var_names_dialog(self) -> None:
|
||||
path, _ = QtWidgets.QFileDialog.getOpenFileName(
|
||||
self, "Load var_names.jsonc", "", "JSONC (*.jsonc);;JSON (*.json);;All (*)"
|
||||
)
|
||||
if path:
|
||||
self.var_names_path = Path(path).resolve()
|
||||
self.var_path_edit.setText(str(self.var_names_path))
|
||||
self.var_names = load_jsonc(self.var_names_path)
|
||||
self.dirty_vars = False
|
||||
self.render_current_block()
|
||||
self.set_status(f"Loaded variable names from {self.var_names_path}")
|
||||
|
||||
def load_method_docs_dialog(self) -> None:
|
||||
path, _ = QtWidgets.QFileDialog.getOpenFileName(
|
||||
self, "Load method docstrings", "", "JSONC (*.jsonc);;JSON (*.json);;All (*)"
|
||||
)
|
||||
if path:
|
||||
self.method_docs_path = Path(path).resolve()
|
||||
self.docs_path_edit.setText(str(self.method_docs_path))
|
||||
self.method_docs = load_jsonc(self.method_docs_path)
|
||||
self.dirty_docs = False
|
||||
self.render_current_block()
|
||||
self.set_status(f"Loaded method docstrings from {self.method_docs_path}")
|
||||
|
||||
def save_var_names(self) -> None:
|
||||
path = Path(self.var_path_edit.text()).expanduser()
|
||||
save_json(path, self.var_names)
|
||||
self.var_names_path = path.resolve()
|
||||
self.dirty_vars = False
|
||||
self.set_status(f"Saved variable names to {self.var_names_path}")
|
||||
|
||||
def save_method_docs(self) -> None:
|
||||
path = Path(self.docs_path_edit.text()).expanduser()
|
||||
save_json(path, self.method_docs)
|
||||
self.method_docs_path = path.resolve()
|
||||
self.dirty_docs = False
|
||||
self.set_status(f"Saved method docstrings to {self.method_docs_path}")
|
||||
|
||||
def refresh_definition_list(self) -> None:
|
||||
query = self.filter_edit.text().strip().lower()
|
||||
self.filtered_blocks = [
|
||||
block
|
||||
for block in self.blocks
|
||||
if not query or query in block.label.lower() or query in block.display_name.lower()
|
||||
]
|
||||
self.definition_list.clear()
|
||||
for block in self.filtered_blocks:
|
||||
self.definition_list.addItem(block.label)
|
||||
|
||||
def on_definition_selected(self, row: int) -> None:
|
||||
if row < 0 or row >= len(self.filtered_blocks):
|
||||
return
|
||||
self.current_block = self.filtered_blocks[row]
|
||||
self.selected_token = None
|
||||
self.selected_edit.clear()
|
||||
self.rename_edit.clear()
|
||||
self.render_current_block()
|
||||
|
||||
def entry_for_current_block(self) -> dict:
|
||||
if not self.current_block:
|
||||
return {}
|
||||
return self.var_names.setdefault(self.current_block.key, {})
|
||||
|
||||
def current_arg_names(self) -> list[str]:
|
||||
block = self.current_block
|
||||
if not block:
|
||||
return []
|
||||
entry = self.entry_for_current_block()
|
||||
configured = entry.get("args", [])
|
||||
result = []
|
||||
for index, original in enumerate(block.args):
|
||||
result.append(configured[index] if index < len(configured) else original)
|
||||
return result
|
||||
|
||||
def current_docstring(self) -> str:
|
||||
block = self.current_block
|
||||
if not block:
|
||||
return ""
|
||||
entry = self.method_docs.get(block.key)
|
||||
if isinstance(entry, list) and len(entry) >= 2:
|
||||
return entry[1]
|
||||
return ""
|
||||
|
||||
def render_current_block(self) -> None:
|
||||
block = self.current_block
|
||||
self.text.clear()
|
||||
self.doc_text.clear()
|
||||
self.token_ranges = []
|
||||
|
||||
if not block:
|
||||
return
|
||||
|
||||
self.doc_text.setEnabled(block.kind in ("function", "method"))
|
||||
if block.kind in ("function", "method"):
|
||||
self.doc_text.setPlainText(self.current_docstring())
|
||||
|
||||
entry = self.entry_for_current_block()
|
||||
local_overrides = {
|
||||
key: get_local_override_name(value) for key, value in entry.get("vars", {}).items()
|
||||
}
|
||||
arg_names = self.current_arg_names()
|
||||
arg_index_by_original = {arg: index for index, arg in enumerate(block.args)}
|
||||
|
||||
arg_pattern = "|".join(re.escape(arg) for arg in sorted(block.args, key=len, reverse=True))
|
||||
token_re = re.compile(f"{LOCAL_VAR_RE.pattern}|\\b(?:{arg_pattern})\\b") if arg_pattern else LOCAL_VAR_RE
|
||||
|
||||
output = []
|
||||
cursor = 0
|
||||
out_pos = 0
|
||||
for match in token_re.finditer(block.text):
|
||||
prefix = block.text[cursor : match.start()]
|
||||
output.append(prefix)
|
||||
out_pos += len(prefix)
|
||||
|
||||
token = match.group(0)
|
||||
kind = "local"
|
||||
token_id = token
|
||||
replacement = local_overrides.get(token, token)
|
||||
if token in arg_index_by_original:
|
||||
kind = "arg"
|
||||
token_id = str(arg_index_by_original[token])
|
||||
replacement = arg_names[arg_index_by_original[token]]
|
||||
|
||||
start_pos = out_pos
|
||||
output.append(replacement)
|
||||
out_pos += len(replacement)
|
||||
self.token_ranges.append((start_pos, out_pos, kind, token_id, replacement))
|
||||
cursor = match.end()
|
||||
|
||||
output.append(block.text[cursor:])
|
||||
self.text.setPlainText("".join(output))
|
||||
self.apply_token_formatting()
|
||||
|
||||
def apply_token_formatting(self) -> None:
|
||||
self.update_extra_selections()
|
||||
|
||||
def update_extra_selections(self) -> None:
|
||||
selections = []
|
||||
|
||||
token_format = QtGui.QTextCharFormat()
|
||||
token_format.setForeground(QtGui.QBrush(QtGui.QColor("#075985")))
|
||||
for start, end, _kind, _token_id, _replacement in self.token_ranges:
|
||||
cursor = self.text.textCursor()
|
||||
cursor.setPosition(start)
|
||||
cursor.setPosition(end, QtGui.QTextCursor.KeepAnchor)
|
||||
selection = QtWidgets.QTextEdit.ExtraSelection()
|
||||
selection.cursor = cursor
|
||||
selection.format = token_format
|
||||
selections.append(selection)
|
||||
|
||||
if self.selected_token:
|
||||
kind, token_id, _replacement = self.selected_token
|
||||
selection_format = QtGui.QTextCharFormat()
|
||||
selection_format.setBackground(QtGui.QBrush(QtGui.QColor("#fde68a")))
|
||||
for start, end, range_kind, range_token_id, _range_replacement in self.token_ranges:
|
||||
if range_kind == kind and range_token_id == token_id:
|
||||
cursor = self.text.textCursor()
|
||||
cursor.setPosition(start)
|
||||
cursor.setPosition(end, QtGui.QTextCursor.KeepAnchor)
|
||||
selection = QtWidgets.QTextEdit.ExtraSelection()
|
||||
selection.cursor = cursor
|
||||
selection.format = selection_format
|
||||
selections.append(selection)
|
||||
|
||||
self.text.setExtraSelections(selections)
|
||||
|
||||
def token_at_position(self, position: int):
|
||||
for start, end, kind, token_id, replacement in self.token_ranges:
|
||||
if start <= position < end:
|
||||
return kind, token_id, replacement
|
||||
return None
|
||||
|
||||
def on_text_click(self, point: QtCore.QPoint) -> None:
|
||||
cursor = self.text.cursorForPosition(point)
|
||||
token = self.token_at_position(cursor.position())
|
||||
if not token:
|
||||
return
|
||||
self.selected_token = token
|
||||
kind, token_id, replacement = token
|
||||
self.selected_edit.setText(f"arg[{token_id}]" if kind == "arg" else token_id)
|
||||
self.rename_edit.setText(replacement)
|
||||
self.rename_edit.setFocus(QtCore.Qt.MouseFocusReason)
|
||||
self.rename_edit.selectAll()
|
||||
self.highlight_selected_token()
|
||||
|
||||
def highlight_selected_token(self) -> None:
|
||||
self.update_extra_selections()
|
||||
|
||||
def apply_rename(self) -> None:
|
||||
if not self.current_block or not self.selected_token:
|
||||
QtWidgets.QMessageBox.information(self, "No Variable Selected", "Click a variable first.")
|
||||
return
|
||||
new_name = self.rename_edit.text().strip()
|
||||
if not new_name:
|
||||
QtWidgets.QMessageBox.information(self, "Missing Name", "Enter a replacement name.")
|
||||
return
|
||||
|
||||
kind, token_id, _replacement = self.selected_token
|
||||
entry = self.entry_for_current_block()
|
||||
if kind == "arg":
|
||||
index = int(token_id)
|
||||
args = list(entry.get("args", []))
|
||||
while len(args) < len(self.current_block.args):
|
||||
args.append(self.current_block.args[len(args)])
|
||||
args[index] = new_name
|
||||
entry["args"] = args
|
||||
else:
|
||||
set_local_override(entry, token_id, new_name)
|
||||
|
||||
self.dirty_vars = True
|
||||
self.render_current_block()
|
||||
self.set_status(f"Renamed {self.selected_edit.text()} to {new_name} in {self.current_block.key}")
|
||||
|
||||
def apply_docstring(self) -> None:
|
||||
block = self.current_block
|
||||
if not block or block.kind not in ("function", "method"):
|
||||
QtWidgets.QMessageBox.information(
|
||||
self, "No Function/Method Selected", "Select a function or method first."
|
||||
)
|
||||
return
|
||||
docstring = self.doc_text.toPlainText().strip()
|
||||
entry = self.method_docs.get(block.key)
|
||||
if isinstance(entry, list) and len(entry) >= 2:
|
||||
entry[1] = docstring
|
||||
else:
|
||||
self.method_docs[block.key] = [block.display_name, docstring]
|
||||
self.dirty_docs = True
|
||||
self.set_status(f"Updated docstring for {block.key}")
|
||||
|
||||
|
||||
def main() -> None:
|
||||
parser = argparse.ArgumentParser(description=__doc__)
|
||||
parser.add_argument("disasm", nargs="?", help="input *_disasm.gc file")
|
||||
parser.add_argument("--var-names", default=str(DEFAULT_VAR_NAMES), help="var_names JSON/JSONC file")
|
||||
parser.add_argument("--method-docs", default=str(DEFAULT_METHOD_DOCS), help="method docstrings JSON/JSONC file")
|
||||
args = parser.parse_args()
|
||||
|
||||
app = QtWidgets.QApplication(sys.argv)
|
||||
window = VarNameEditor(args)
|
||||
window.show()
|
||||
sys.exit(app.exec_())
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
||||
Reference in New Issue
Block a user