Files
jak-project/goal_src/jak1/engine/ps2/pad.gc
T
Alex a6c21a9814 Implement analog stick sensitivity setting, default it to 133% (#3919)
This is a simple multiplier to the gamepad axis input value received
from SDL events. Normally the values it provides cannot satisfy the
square range of the stick input. This is usually fine but it might play
differently with different controllers and compared to consoles,
especially considering the DualShock 1/2 have automatic calibration
which works in mysterious ways. The setting is there so any user can
adjust it for their controllers.

Saved to and loaded from the input-settings.json file.

133% matches PCSX2's default setting and is generally a good value to
map the square stick range within most modern(ish) controllers' circular
stick motion.

Progress menu option added to Jak 1 and 2. Setting can be changed from
50% all the way to 200%.

~~Renamed the analog deadzone options to stick deadzone since they don't
apply to the other analog buttons and only the (analog, yes) sticks.~~
2025-05-27 16:01:06 +01:00

432 lines
19 KiB
Common Lisp

;;-*-Lisp-*-
(in-package goal)
(bundles "ENGINE.CGO" "GAME.CGO")
(require "")
(require "engine/sound/gsound.gc")
(require "pc/pc-cheats.gc")
;; Interface for game controllers.
;; the *cpad-list* contains both game controllers.
;; Use the service-cpads functions once per frame to update the data and vibration control
;; The cpad-set-buzz! function can be used for vibration.
;; in display, but we haven't gotten to display-h.gc yet.
(define-extern get-current-time (function time-frame))
(define-extern get-integral-current-time (function time-frame))
(#when PC_PORT
;;;;;;;;;;;;;;;;;;;;;;
;; opengoal territory override
;;;;;;;;;;;;;;;;;;;;;;
(defun scf-get-territory ()
"redefined from C kernel for convenience"
(if (= (-> *pc-settings* territory) -1) *default-territory* (-> *pc-settings* territory))))
;; DECOMP BEGINS
(defenum pad-buttons
:bitfield #t
:type uint32
(select 0)
(l3 1)
(r3 2)
(start 3)
(up 4)
(right 5)
(down 6)
(left 7)
(l2 8)
(r2 9)
(l1 10)
(r1 11)
(triangle 12)
(circle 13)
(x 14)
(square 15))
(defenum abutton-idx
:type uint8
(right 0)
(left 1)
(up 2)
(down 3)
(triangle 4)
(circle 5)
(x 6)
(square 7)
(l1 8)
(r1 9)
(l2 10)
(r2 11))
(defenum pad-type
(normal 4)
(analog 5)
(dualshock 7)
(negcon 2)
(namco-gun 6))
;; this gets set to #f later on.
(define *cheat-mode* #t)
;; data that comes directly from hardware. it's 32 bytes + type tag (ignored in C kernel).
(deftype hw-cpad (basic)
(;; BASIC CONTROLLER data
;; status = 0x40 | (data length / 2)
(valid uint8) ;; 0 if success, 255 if fail
(status uint8) ;; depends on controller
(button0 uint16) ;; binary button states!
;; DUALSHOCK or JOYSTICK data
;; status (dualshock) = 0x70 | (data length / 2)
;; status (joystick) = 0x50 | (data length / 2)
(rightx uint8) ;; right stick xdir
(righty uint8) ;; right stick ydir
(leftx uint8) ;; left stick xdir
(lefty uint8) ;; left stick ydir
;; DUALSHOCK 2 data
;; status = 0x70 | (data length / 2)
(abutton uint8 12) ;; pressure sensitivity information
;; pad buffer needs to be 32 bytes large.
(dummy uint8 12)))
;; data from hardware + additional info calculated here.
(deftype cpad-info (hw-cpad)
((number int32) ;; controller port number
(cpad-file int32)
(button0-abs pad-buttons 3) ;; bitmask of buttons, pressed or not, with history
(button0-shadow-abs pad-buttons 1) ;; modify this to change button history in the future.
(button0-rel pad-buttons 3) ;; bitmask of if button going down.
(stick0-dir float)
(stick0-speed float)
(new-pad int32)
(state int32)
(align uint8 6) ;; hardware control of buzzing.
(direct uint8 6) ;; hardware control of buzzing.
(buzz-val uint8 2) ;; intensity for buzzing
(buzz-time time-frame 2) ;; when to stop buzzing
(buzz basic) ;; is vibration enabled?
(buzz-act int32)
(change-time time-frame))
(:methods
(new (symbol type int) _type_)))
(defmacro cpad-type? (type)
`(= (shr (-> pad status) 4) (cpad-type ,type)))
(defun cpad-invalid! ((pad cpad-info))
"Reset all data in a cpad-info"
(logior! (-> pad valid) 128)
(set! (-> pad button0) (the-as uint 0))
(set! (-> pad button0-abs 0) (pad-buttons))
(set! (-> pad button0-shadow-abs 0) (pad-buttons))
(set! (-> pad button0-rel 0) (pad-buttons))
(dotimes (v1-2 12)
(nop!)
(set! (-> pad abutton v1-2) 0))
(set! (-> pad stick0-dir) 0.0)
(set! (-> pad stick0-speed) 0.0)
(set! (-> pad rightx) (the-as uint 128))
(set! (-> pad righty) (the-as uint 128))
(set! (-> pad leftx) (the-as uint 128))
(set! (-> pad lefty) (the-as uint 128))
(set! (-> pad align 0) (the-as uint 0))
(set! (-> pad align 1) (the-as uint 1))
(set! (-> pad align 2) (the-as uint 255))
(set! (-> pad align 3) (the-as uint 255))
(set! (-> pad align 4) (the-as uint 255))
(set! (-> pad align 5) (the-as uint 255))
(dotimes (v1-14 6)
(set! (-> pad direct v1-14) 0))
;; probably a bug here, this should use v1-17 as the index.
(dotimes (v1-17 2)
(set! (-> pad buzz-val 0) (the-as uint 0))
(set! (-> pad buzz-time 0) 0))
pad)
(defmethod new cpad-info ((alloction symbol) (type-to-make type) (idx int))
"Allocate a new cpad-info and open the pad itself through the kernel"
(let ((this (object-new alloction type-to-make (the-as int (-> type-to-make size)))))
(set! (-> this number) idx)
(set! (-> this buzz) #f)
(cpad-open this idx) ;; kernel function.
(cpad-invalid! this)))
;; List of controllers. It always has 4 controllers.
(deftype cpad-list (basic)
((num-cpads int32)
(cpads cpad-info 4) ;; modified from 2->4 for PC 4-pad support
)
(:methods
(new (symbol type) _type_)))
(defmethod new cpad-list ((allocation symbol) (type-to-make type))
"Create a cpad-list for 4 controllers. It's fine to do this even if controllers
aren't connected yet. "
(let ((gp-0 (object-new allocation type-to-make (the-as int (-> type-to-make size)))))
(set! (-> gp-0 num-cpads) 4)
(set! (-> gp-0 cpads 0) (new 'global 'cpad-info 0))
(set! (-> gp-0 cpads 1) (new 'global 'cpad-info 1))
(set! (-> gp-0 cpads 2) (new 'global 'cpad-info 2))
(set! (-> gp-0 cpads 3) (new 'global 'cpad-info 3))
gp-0))
;; Allocates enough input frames to support counting 50ms of time at 300fps.
(deftype cpad-history (basic)
((button0-abs pad-buttons 15) ;; bitmask of buttons, pressed or not, with history
(button0-rel pad-buttons 15) ;; bitmask of if button going down.
))
;; List of extended history for controllers at high fps. It always has 4 controllers.
(deftype cpad-history-list (basic)
((num-cpads int32)
(cpads cpad-history 4) ;; modified from 2->4 for PC 4-pad support
)
(:methods
(new (symbol type) _type_)))
(defmethod new cpad-history-list ((allocation symbol) (type-to-make type))
"Create a cpad-history-list for 4 controllers. It's fine to do this even if controllers
aren't connected yet. "
(let ((this (object-new allocation type-to-make (the-as int (-> type-to-make size)))))
(set! (-> this num-cpads) 4)
(set! (-> this cpads 0) (new 'global 'cpad-history))
(set! (-> this cpads 1) (new 'global 'cpad-history))
(set! (-> this cpads 2) (new 'global 'cpad-history))
(set! (-> this cpads 3) (new 'global 'cpad-history))
this))
(defun analog-input ((in int) (offset float) (center-val float) (max-val float) (out-range float))
"Convert integer input from pad into a float between -out-range and +out-range.
The offset is applied directly to the input.
The center val is the expected value for 0, after applying offset.
The max val is the expected value with the stick pushed all the way"
(let* ((offset-in (- (the float in) offset)) ;; apply offset to in
(magnitude (- (fabs offset-in) center-val)) ;; determine our magnitude
(max-magnitude (- max-val center-val)) ;; maximum expected magnitude.
)
;; flip the output if negative
(if (< offset-in 0.0) (set! out-range (- out-range)))
;; scale and return value.
(cond
((>= 0.0 magnitude) 0.0)
((>= magnitude max-magnitude) out-range)
(else (/ (* magnitude out-range) max-magnitude)))))
(defmacro analog-input-horizontal-first (in offset center-val max-val out-range)
"Same as analog-input but respects First-Person Horizontal camera control setting."
`(#if PC_PORT ;; first-person horizontal is NOT inverted in original game
(* (if (-> *pc-settings* first-camera-h-inverted?) -1.0 1.0) (analog-input ,in ,offset ,center-val ,max-val ,out-range))
(analog-input ,in ,offset ,center-val ,max-val ,out-range)))
(defmacro analog-input-vertical-first (in offset center-val max-val out-range)
"Same as analog-input but respects First-Person Vertical camera control setting."
`(#if PC_PORT ;; first-person vertical is already inverted in original game
(* (if (-> *pc-settings* first-camera-v-inverted?) 1.0 -1.0) (analog-input ,in ,offset ,center-val ,max-val ,out-range))
(analog-input ,in ,offset ,center-val ,max-val ,out-range)))
(defmacro analog-input-horizontal-third (in offset center-val max-val out-range)
"Same as analog-input but respects Third-Person Horizontal camera control setting."
`(#if PC_PORT ;; third-person horizontal is already inverted in original game
(* (if (-> *pc-settings* third-camera-h-inverted?) 1.0 -1.0) (analog-input ,in ,offset ,center-val ,max-val ,out-range))
(analog-input ,in ,offset ,center-val ,max-val ,out-range)))
(defmacro analog-input-vertical-third (in offset center-val max-val out-range)
"Same as analog-input but respects Third-Person Vertical camera control setting."
`(#if PC_PORT ;; third-person vertical is already inverted in original game
(* (if (-> *pc-settings* third-camera-v-inverted?) 1.0 -1.0) (analog-input ,in ,offset ,center-val ,max-val ,out-range))
(analog-input ,in ,offset ,center-val ,max-val ,out-range)))
(defun cpad-set-buzz! ((pad cpad-info) (buzz-idx int) (buzz-amount int) (duration time-frame))
"Turn on vibration motor 'buzz-idx' for duration, at magnitude buzz-amount."
(cond
((zero? buzz-amount)
;; set buzz-amount to 0, immediately kill it.
(set! (-> pad buzz-val buzz-idx) 0))
((= buzz-amount (-> pad buzz-val buzz-idx))
;; we are already buzzing at this intensity.
;; buzz for max (old_buzz, new_buzz) duration
(set! (-> pad buzz-time buzz-idx) (max (-> pad buzz-time buzz-idx) (+ (get-current-time) duration))))
((< (-> pad buzz-val buzz-idx) (the-as uint buzz-amount))
;; buzz harder than the older value, overwrite the old buzz.
(set! (-> pad buzz-val buzz-idx) buzz-amount)
(set! (-> pad buzz-time buzz-idx) (+ (get-current-time) duration))))
(none))
;; the four controllers
(define *cpad-list* (new 'global 'cpad-list))
;; the cpad-history for each controller
(define *cpad-history-list* (new 'global 'cpad-history-list))
(desfun generate-frame-checks (pad-idx frames)
(if (= frames 0)
0
`(logior (-> *cpad-history-list* cpads ,pad-idx button0-rel ,(- frames 1)) ,(generate-frame-checks pad-idx (- frames 1)))))
;; weird leftover debug thing, enabling overrides the x position of both sticks on all controllers.
(define *cpad-debug* #f)
(#if PC_PORT (defconstant STICK_DEADZONE (-> *pc-settings* stick-deadzone)) (defconstant STICK_DEADZONE 0.3))
(defun service-cpads ()
"Read from cpads and update vibration"
;; iterate over pads
(let ((pad-list *cpad-list*))
(dotimes (pad-idx (-> pad-list num-cpads))
(let ((pad (-> *cpad-list* cpads pad-idx))
(history (-> *cpad-history-list* cpads pad-idx)))
;; read from hardware.
(cpad-get-data pad)
(cond
((zero? (logand (-> pad valid) 128))
(dotimes (buzz-idx 2)
(cond
;; check if okay to buzz:
((and (-> pad buzz) (< (get-current-time) (-> pad buzz-time buzz-idx)) (= *master-mode* 'game))
(let ((v1-10 buzz-idx))
(cond
((zero? v1-10)
;; vibration motor 0 only has on/off. This pulses it to approximate
;; an analog control
(set! (-> pad direct buzz-idx) (logand (ash (-> pad buzz-val buzz-idx) (- (logand (get-integral-current-time) 7))) 1)))
((= v1-10 1)
;; vibration motor 1 has analog control. set the speed.
(set! (-> pad direct buzz-idx) (-> pad buzz-val buzz-idx))))))
(else
;; not okay to buzz this motor, set to zero.
(set! (-> pad buzz-val buzz-idx) (the-as uint 0))
(set! (-> pad direct buzz-idx) (the-as uint 0)))))
;; update button history.
(set! (-> pad button0-abs 2) (-> pad button0-abs 1))
(set! (-> pad button0-abs 1) (-> pad button0-shadow-abs 0))
(set! (-> pad button0-rel 2) (-> pad button0-rel 1))
(set! (-> pad button0-rel 1) (-> pad button0-rel 0))
;; og:preserve-this high fps fix
(when (>= (-> *pc-settings* target-fps) 300)
(set! (-> history button0-abs 14) (-> history button0-abs 13))
(set! (-> history button0-abs 13) (-> history button0-abs 12))
(set! (-> history button0-abs 12) (-> history button0-abs 11))
(set! (-> history button0-rel 14) (-> history button0-rel 13))
(set! (-> history button0-rel 13) (-> history button0-rel 12))
(set! (-> history button0-rel 12) (-> history button0-rel 11)))
(when (>= (-> *pc-settings* target-fps) 240)
(set! (-> history button0-abs 11) (-> history button0-abs 10))
(set! (-> history button0-abs 10) (-> history button0-abs 9))
(set! (-> history button0-abs 9) (-> history button0-abs 8))
(set! (-> history button0-rel 11) (-> history button0-rel 10))
(set! (-> history button0-rel 10) (-> history button0-rel 9))
(set! (-> history button0-rel 9) (-> history button0-rel 8)))
(when (>= (-> *pc-settings* target-fps) 165)
(set! (-> history button0-abs 8) (-> history button0-abs 7))
(set! (-> history button0-rel 8) (-> history button0-rel 7)))
(when (>= (-> *pc-settings* target-fps) 150)
(set! (-> history button0-abs 7) (-> history button0-abs 6))
(set! (-> history button0-abs 6) (-> history button0-abs 5))
(set! (-> history button0-rel 7) (-> history button0-rel 6))
(set! (-> history button0-rel 6) (-> history button0-rel 5)))
(when (>= (-> *pc-settings* target-fps) 120)
(set! (-> history button0-abs 5) (-> history button0-abs 4))
(set! (-> history button0-abs 4) (-> history button0-abs 3))
(set! (-> history button0-rel 5) (-> history button0-rel 4))
(set! (-> history button0-rel 4) (-> history button0-rel 3)))
(when (>= (-> *pc-settings* target-fps) 75)
(set! (-> history button0-abs 3) (-> history button0-abs 2))
(set! (-> history button0-rel 3) (-> history button0-rel 2)))
(set! (-> history button0-abs 2) (-> history button0-abs 1))
(set! (-> history button0-abs 1) (-> pad button0-shadow-abs 0))
(set! (-> history button0-rel 2) (-> history button0-rel 1))
(set! (-> history button0-rel 1) (-> pad button0-rel 0))
;; we might want to clear a button after it is pressed, so we back it up in a "shadow" field
(let ((current-button0 (-> pad button0)))
(set! (-> pad button0-shadow-abs 0) (the-as pad-buttons current-button0))
(set! (-> pad button0-abs 0) (the-as pad-buttons current-button0))
(set! (-> history button0-abs 0) (-> pad button0-abs 0)))
;; buttons going down
(set! (-> pad button0-rel 0) (logclear (-> pad button0-abs 0) (-> pad button0-abs 1)))
(set! (-> history button0-rel 0) (-> pad button0-rel 0))
;; some debugging thing they wrote at some point
(when *cpad-debug*
(set! (-> pad leftx) (the-as uint 255))
(set! (-> pad rightx) (the-as uint 255)))
;; compute a speed and direction for stick0
(set! (-> pad stick0-speed) 1.0)
(cond
;; check if controller is a valid dualshock 2
((= (shr (-> pad status) 4) 7)
;; invert analogs if mirrored mode. trust me, you'll need this.
(#when PC_PORT
(when (pc-cheats? (-> *pc-settings* cheats) mirror)
(set! (-> pad leftx) (- 255 (-> pad leftx)))
(set! (-> pad rightx) (- 255 (-> pad rightx)))))
;; compute speed and direction, with deadband.
(let ((f30-0 (* 0.0078125 (the float (+ (-> pad leftx) -128))))
(f28-0 (* 0.0078125 (the float (- 127 (the-as int (-> pad lefty)))))))
(set! (-> pad stick0-dir) (atan (- f30-0) f28-0))
(set! (-> pad stick0-speed) (fmin 1.0 (sqrtf (+ (* f30-0 f30-0) (* f28-0 f28-0))))))
(if (< (-> pad stick0-speed) STICK_DEADZONE) (set! (-> pad stick0-speed) 0.0))
;; (format *stdcon* "pad ~D - left: ~3D / ~3D (~f at ~f)~%" pad-idx (-> pad leftx) (-> pad lefty) (/ (* 360.0 (-> pad stick0-dir)) DEGREES_PER_ROT) (-> pad stick0-speed))
)
(else
;; analog is invalid? set to zero.
(set! (-> pad leftx) (the-as uint 128))
(set! (-> pad lefty) (the-as uint 128))
(set! (-> pad rightx) (the-as uint 128))
(set! (-> pad righty) (the-as uint 128))
(set! (-> pad stick0-dir) 0.0)
(set! (-> pad stick0-speed) 0.0)))
;; if the pad was changed or stick0 pushed, update the last changed time.
(if (or (!= (-> pad button0-abs 0) (-> pad button0-abs 1))
(or (< STICK_DEADZONE (-> pad stick0-speed)) (zero? (-> pad change-time))))
(set! (-> pad change-time) (get-current-time))))
(else
;; invalid bits set, controller is not connected.
(cpad-invalid! pad))))))
*cpad-list*)
(defun buzz-stop! ((idx int))
"Set the buzz to 0 on both vibration motors of the given cpad."
(cpad-set-buzz! (-> *cpad-list* cpads idx) 0 0 0)
(cpad-set-buzz! (-> *cpad-list* cpads idx) 1 0 0)
(none))
(defmacro cpad-pressed (pad-idx)
`(-> *cpad-list* cpads ,pad-idx button0-rel 0))
(defmacro cpad-hold (pad-idx)
`(-> *cpad-list* cpads ,pad-idx button0-abs 0))
(defmacro cpad-pressed? (pad-idx &rest buttons)
`(logtest? (cpad-pressed ,pad-idx) (pad-buttons ,@buttons)))
(defmacro cpad-hold? (pad-idx &rest buttons)
`(logtest? (cpad-hold ,pad-idx) (pad-buttons ,@buttons)))
(defmacro recently-pressed? (&key (pad-idx (-> self control unknown-cpad-info00 number)) &key (frames 15) &rest buttons)
"Checks if a (pad-button) was pressed within the last 50ms, based on frames"
`(logtest? ,(generate-frame-checks pad-idx frames) (pad-buttons ,@buttons)))
(defmacro cpad-clear! (pad-idx &rest buttons)
`(begin
(logclear! (cpad-pressed ,pad-idx) (pad-buttons ,@buttons))
(logclear! (cpad-hold ,pad-idx) (pad-buttons ,@buttons))))
(defmacro cpad-change-time (pad-idx)
`(-> *cpad-list* cpads ,pad-idx change-time))
(defmacro check-cheat-code (cheat-var pad-idx buttons &rest body)
"execute body when a cheat code made up of sequential inputs has been inputted"
`(when (nonzero? (cpad-pressed ,pad-idx)) ;; only check when some button has been pressed
(case ,cheat-var
,@(apply-i
(lambda (x i)
`((,i)
(if (cpad-pressed? ,pad-idx ,x)
,(if (< i (- (length buttons) 1)) `(1+! ,cheat-var) `(begin ,@body (set! ,cheat-var 0)))
(set! ,cheat-var 0))))
buttons))))
(defmacro cheats-sound-play (cheat?)
"play the appropriate sound for inputting a cheat code"
`(if ,cheat? (sound-play "select-menu") (sound-play "cursor-options")))