Files
jak-project/goal_src/pc/subtitle.gc
T
ManDude a7eee4fdc9 [game] pc port progress menu (#1281)
* fix typo

* more typo

* shorten discord rpc text

* allow expanding enums after the fact (untested)

* make `game_text` work similar to subtitles

* update progress decomp

* update some types + `do-not-decompile` in bitfield

* fixes and fall back to original progress code

* update `progress` decomp with new enums

* update config files

* fix enums and debug menu

* always allocate (but not use) a lot of particles

* small rework to display mode options

* revert resolution/aspect-ratio symbol mess

* begin the override stuff

* make `progress-draw` more readable

* more fixes

* codacy good boy points

* first step overriding code

* finish progress overrides, game options menu fully functional!

* minor fixes

* Update game.gp

* Update sparticle-launcher.gc

* clang

* change camera controls text

* oops

* some cleanup

* derp

* nice job

* implement menu scrolling lol

* make scrollable menus less cramped, fix arrows

* make some carousell things i guess

* add msaa carousell to test

* oops

* Update progress-pc.gc

* make `pc-get-screen-size` (untested)

* resolution menu

* input fixes

* return when selecting resolution

* scroll fixes

* Update progress-pc.gc

* add "fit to screen" button

* bug

* complete resolutions menu

* aspect ratio menu

* subtitles language

* subtitle speaker

* final adjustments

* ref test

* fix tests

* fix ref!

* reduce redundancy a bit

* fix mem leaks?

* save settings on progress exit

* fix init reorder

* remove unused code

* rename goal project-like files to the project extension

* sha display toggle

* aspect ratio settings fixes

* dont store text db's in compiler

* properly save+load native aspect stuff
2022-04-11 18:38:54 -04:00

374 lines
11 KiB
Common Lisp

;;-*-Lisp-*-
(in-package goal)
#|
Code for subtitles for the PC port. A PC actor pool is provided, and the subtitle process lives there.
It automatically detects the currently playing cutscene and plays the subtitles for it on channel 0.
Channel 1 and 2 are reserved for ambient sounds, which cannot be auto-detected, so the ambient-control needs to
notify the subtitle process manually (TODO: verify this!)
Similarly to the generic text file, only one subtitles text file is loaded at once, stored in a specific
heap.
In Jak 2, the subtitles are stored directly within the .STR files. We don't have that luxury here, unfortunately.
|#
(#when PC_PORT
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;;;; constants
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
(defconstant PC_SUBTITLE_FILE_SIZE (* 64 1024))
(defconstant PC_SUBTITLE_FILE_NAME "subtit")
(defglobalconstant PC_SUBTITLE_DEBUG #f)
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;;;; types and enums
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;;;------------------------
;; data
;;;------------------------
;; enum for the subtitle channels, no more than 2 bytes!
(defenum pc-subtitle-channel
:type int16
(invalid -1) (movie 0) (ambient 1) (hint 2))
;; information about a single line of subtitles
(deftype subtitle-keyframe (structure)
(
(frame int32) ;; the frame to play the subtitle line on
(string string) ;; the text for the subtitle line
(speaker string) ;; name of the speaker. leave blank for no speaker.
(offscreen symbol) ;; speaker is offscreen.
)
:pack-me
)
;; an individual entry to a subtitle text making up a scene, comprised of a series of keyframes (frame+line of text)
(deftype subtitle-text (structure)
(
;; the channel to play the text on, useful for lookup since it can also be used to tell subtitle types apart
(kind pc-subtitle-channel)
;; the amount of keyframes
(length int16)
;; data
(keyframes (inline-array subtitle-keyframe))
;; now we store a way to identify and lookup the subtitles.
;; the game-text-id if this is for a hint
(id game-text-id :offset 8)
;; the name of the spool-anim
(name string :offset 8)
;; the 8-character name for an ambient or hint
(hash uint64 :offset 8)
)
:size-assert #x10 ;; compact!
)
;; the global subtitle text info bank
(deftype subtitle-text-info (basic)
((length int32)
(lang pc-subtitle-lang)
(dummy int32)
(data subtitle-text :inline :dynamic)
)
(:methods
(get-scene-by-name (_type_ pc-subtitle-channel string) subtitle-text)
)
)
;;;----------------------------------
;; process type
;;;----------------------------------
;; the subtitle process! it lives on the PC actor pool and awaits for incoming subtitle messages, or a movie
(deftype subtitle (process)
(
(font font-context)
( spool-name string)
(old-spool-name string)
(cur-channel pc-subtitle-channel)
)
:heap-base #x10
(:methods
(subtitle-format (_type_ subtitle-keyframe) string)
)
)
;;;----------------------------------------------
;; globals
;;;----------------------------------------------
;; the actor pool for PC processes! it has 1K of space for 1 process.
(define *pc-dead-pool* (new 'global 'dead-pool 1 (* 1 1024) '*pc-dead-pool*))
(define *subtitle-temp-string* (new 'global 'string 16 (the string #f)))
(define *subtitle* (the (pointer subtitle) #f))
;; subtitle text data
(define *subtitle-text* (the subtitle-text-info #f))
(kheap-alloc (define *subtitle-text-heap* (new 'global 'kheap)) PC_SUBTITLE_FILE_SIZE)
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;;;; loading files
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
(defmethod get-scene-by-name subtitle-text-info ((obj subtitle-text-info) (kind pc-subtitle-channel) (name string))
"get a subtitle scene info with the corresponding name. #f = none found"
(dotimes (i (-> obj length))
;; name and kind matches, return that!
(when (and (= kind (-> obj data i kind)) (string= (-> obj data i name) name))
(return (-> obj data i))
)
)
(the subtitle-text #f))
(defun load-subtitle-text-info ((txt-name string) (curr-text symbol) (heap kheap))
"load a subtitles text file onto a heap.
txt-name = language-agnostic file name
curr-text = a symbol to a subtitle-text-info to link the file to
heap = the text heap to load the file onto"
(local-vars (v0-2 int) (heap-sym-heap subtitle-text-info) (lang pc-subtitle-lang) (load-status int) (heap-free int))
(set! heap-sym-heap (the-as subtitle-text-info (-> curr-text value)))
(set! lang (-> *pc-settings* subtitle-language))
(set! load-status 0)
(set! heap-free (&- (-> heap top) (the-as uint (-> heap base))))
(when (or (= heap-sym-heap #f)
(!= (-> heap-sym-heap lang) lang)
)
(kheap-reset heap)
(while (not (str-load (string-format "~D~S.TXT" lang txt-name) -1 (logand -64 (&+ (-> heap current) 63)) (&- (-> heap top) (-> heap current))))
(return 0)
)
(label retry)
(let ((v1-20 (str-load-status (the-as (pointer int32) (& load-status)))))
(when (= v1-20 'error)
(format 0 "Error loading subtitle~%")
(return 0)
(goto loaded)
)
(cond
((>= load-status (+ heap-free -300))
(format 0 "Game subtitle heap overrun!~%")
(return 0)
)
((= v1-20 'busy)
(goto retry)
)
)
)
(label loaded)
(let ((s2-1 (logand -64 (&+ (-> heap current) 63))))
(flush-cache 0)
(set! (-> curr-text value) (link s2-1 (-> (string-format "~D~S.TXT" lang txt-name) data) load-status heap 0))
)
(if (<= (the-as int (-> curr-text value)) 0)
(set! (-> curr-text value) (the-as object #f))
)
)
0)
(defun load-level-subtitle-files ((idx int))
"If needed, load subtitles"
;; just load common. These flags are not yet understood.
(if (or *level-text-file-load-flag* (>= idx 0))
(load-subtitle-text-info PC_SUBTITLE_FILE_NAME '*subtitle-text* *subtitle-text-heap*)
)
(none)
)
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;;;; subtitle process and drawing!
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
(defmethod subtitle-format subtitle ((obj subtitle) (keyframe subtitle-keyframe))
"check settings and format subtitle accordingly."
(cond
((= 0 (length (-> keyframe speaker)))
(string-format "~S" (-> keyframe string))
)
((or (= #t (-> *pc-settings* subtitle-speaker?))
(and (= 'auto (-> *pc-settings* subtitle-speaker?)) (-> keyframe offscreen)))
(string-format "~3L~S:~0L ~S" (-> keyframe speaker) (-> keyframe string))
)
(else
(string-format "~S" (-> keyframe string))
)
)
)
(defun subtitle? ()
"can a subtitle be displayed right now?"
(and (-> *pc-settings* subtitles?) ;; subtitles enabled
(or *debug-segment* (= *master-mode* 'game)) ;; current mode is game, OR we are just debugging
)
)
(defstate subtitle-process (subtitle)
:event (behavior ((from process) (argc int) (msg symbol) (block event-message-block))
(case msg
(('subtitle-start)
0
)
)
)
:code (behavior ()
(loop
(suspend))
)
:trans (behavior ()
(load-level-subtitle-files 0)
;; reset params
(set! (-> self spool-name) #f)
(set! (-> self cur-channel) (pc-subtitle-channel invalid))
(none))
:post (behavior ()
;; check what kind of subtitles are running
(when (and (movie?) (-> *art-control* spool-lock) (-> *art-control* active-stream))
;; there's a cutscene happening and an active spool with a valid owner.
(set! (-> self spool-name) (-> *art-control* active-stream))
(set! (-> self cur-channel) (pc-subtitle-channel movie))
(with-proc ((handle->process (-> *art-control* spool-lock)))
(format *stdcon* "current spool frame is ~D~%" (the int (ja-aframe-num 0)))
)
)
;; do subtitles
(case (-> self cur-channel)
(((pc-subtitle-channel movie))
;; cutscenes, check if user toggled subtitles
(if (and (= 'game *master-mode*) (cpad-pressed? 0 square))
(not! (-> *pc-settings* subtitles?)))
(when (subtitle?)
;; subtitles on! get a subtitle info that matches our current cutscene, and find at what point we're in.
(let ((scene (get-scene-by-name *subtitle-text* (-> self cur-channel) (-> self spool-name))))
(when scene
;; got a scene! get closest subtitle now.
(let ((pos 0)
(keyframe (the subtitle-keyframe #f)))
;; get frame num
(with-proc ((handle->process (-> *art-control* spool-lock)))
(set! pos (the int (ja-aframe-num 0)))
)
;; find closest keyframe
(dotimes (i (-> scene length))
(when (>= pos (-> scene keyframes i frame))
(set! keyframe (-> scene keyframes i)))
)
(when keyframe
;; we got the closest keyframe! finally just render the subtitles!
(print-game-text (subtitle-format self keyframe) (-> self font) #f 128 22)
(#when PC_SUBTITLE_DEBUG
(draw-debug-text-box (-> self font))
)
)
)
)
)
;; keep this for later
(set! (-> self old-spool-name) (-> self spool-name))
)
)
)
0)
)
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;;;; helper functions
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
(defmethod length subtitle-text-info ((obj subtitle-text-info))
"Get the length (number of subtitle scenes) in a subtitle-text-info."
(-> obj length)
)
(defmethod length subtitle-text ((obj subtitle-text))
"Get the length (number of subtitle lines) in a subtitle-text."
(-> obj length)
)
(defun subtitle-stop ()
"kill the subtitle process"
(kill-by-type subtitle *active-pool*)
(set! *subtitle* (the (pointer subtitle) #f)))
(defun subtitle-start ()
"start the subtitle process"
(when *subtitle*
(subtitle-stop)
)
(set! *subtitle* (make-init-process subtitle
(lambda :behavior subtitle ()
(set! (-> self font) (new 'process 'font-context *font-default-matrix*
(the int (* 0.12 256)) (+ 112 (* 11 5)) 0.0
(font-color default)
(font-flags shadow kerning middle large)))
(set-scale! (-> self font) 0.5)
(set-width! (-> self font) (the int (* 0.88 256 2)))
(set-height! (-> self font) (* 11 3))
(go subtitle-process)
)
:from *pc-dead-pool* :to *active-pool*
:stack-size (* 1 1024)))
)
;; start the subtitle process
(subtitle-start)
)