temp stuff

This commit is contained in:
water111
2026-06-30 23:15:51 -04:00
parent 090a84eba7
commit 39e9be252c
5 changed files with 5756 additions and 71 deletions
@@ -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
+73 -71
View File
@@ -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)
+999
View File
@@ -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()