Files
jak-project/goal_src/jak1/engine/level/level.gc
T
2026-07-28 12:00:35 -07:00

1466 lines
75 KiB
Common Lisp

;;-*-Lisp-*-
(in-package goal)
(bundles "ENGINE.CGO" "GAME.CGO")
(require "engine/engine/engines.gc")
(require "engine/level/load-boundary.gc")
(require "engine/gfx/tie/tie-h.gc")
(require "engine/gfx/mood/mood-h.gc")
(require "engine/level/level-info.gc")
(require "engine/level/bsp.gc")
(require "engine/gfx/sprite/sparticle/sparticle.gc")
(require "engine/load/ramdisk.gc")
(require "engine/gfx/tfrag/tfrag.gc")
;; DECOMP BEGINS
;;;;;;;;;;;;;;;;;;;;;;
;; level info/names
;;;;;;;;;;;;;;;;;;;;;;
(defun lookup-level-info ((name symbol))
"Return the first level-load-info whose canonical name, VIS name, or nickname matches name.
Return default-level when no entry matches."
(let* ((rest *level-load-list*)
(current-sym (the symbol (car rest))))
(while (not (null? rest))
(let ((info (the level-load-info (-> current-sym value))))
(if (or (= name (-> info name)) (= name (-> info visname)) (= name (-> info nickname))) (return info)))
(set! rest (cdr rest))
(set! current-sym (the symbol (car rest)))))
default-level)
(defmethod load-command-get-index ((this level-group) (name symbol) (cmd-idx int))
"Return command-index from level-name's alternate load-command list."
(let ((cmd-lst (-> (lookup-level-info name) alt-load-commands)))
(while (nonzero? cmd-idx)
(+! cmd-idx -1)
(set! cmd-lst (cdr cmd-lst))
(nop!)
(nop!)
(nop!))
(the-as pair (car cmd-lst))))
(defun remap-level-name ((info level-load-info))
"Return info's VIS filename symbol when VIS mode is enabled, otherwise its canonical level
name."
(if (-> *level* vis?) (-> info visname) (-> info name)))
(defmethod art-group-get-by-name ((this level) (name string))
"Return this level's art group whose name matches name, or false."
(countdown (i (-> this art-group art-group-array length))
(if (name= (-> this art-group art-group-array i name) name) (return (-> this art-group art-group-array i))))
(the-as art-group #f))
(defmethod bsp-name ((this level))
"Return the loaded BSP name when available, otherwise return this level's requested name."
(if (and (!= (-> this status) 'inactive) (-> this bsp) (nonzero? (-> this bsp name))) (-> this bsp name) (-> this name)))
;;;;;;;;;;;;;;;;;;;;;;;;;
;; BSP
;;;;;;;;;;;;;;;;;;;;;;;;;
;; The "bsp" is the big data structure that contains the level geometry.
;; the background draw engine will use this function to draw an entire level's background.
;; The actual drawing is executed with (execute-connections *background-draw-engine* ...)
;; in drawable.gc
(defun add-bsp-drawable ((bsp-data bsp-header) (level-data level) (unused symbol) (display-frame-data display-frame))
"Draw a level BSP for the current display frame and optionally append its strip-line debug view."
;; do the draw
(draw bsp-data bsp-data display-frame-data)
(if (nonzero? *display-strip-lines*) (debug-draw bsp-data bsp-data display-frame-data))
(none))
(defmethod print ((this level))
"print a level."
(format #t "#<~A ~A ~S @ #x~X>" (-> this type) (-> this status) (-> this name) this)
this)
(defmethod relocate ((this bsp-header) (heap kheap) (name (pointer uint8)))
"Validate a newly linked BSP for the currently loading level, preserve the corrected title and
demo names, and connect the BSP and level to each other. Reject invalid types, versions, and
visibility lists longer than 2048 entries."
;; we expect that we'll have a loading-level set when we link/login a bsp-header
(let ((loading-level (-> *level* loading-level)))
(if loading-level
(cond
(this
(cond
((not (type-type? (-> this type) bsp-header))
(format 0 "ERROR: level ~A is not a bsp-header.~%" (-> loading-level name))
(the-as bsp-header #f))
((not (file-info-correct-version? (-> this info) (file-kind level-bt) 0)) (the-as bsp-header #f))
((< 2048 (-> this visible-list-length))
(format 0
"ERROR: level ~A visible-list-length ~d is greater than 2048 (16384 drawables).~%"
(-> loading-level name)
(-> this visible-list-length))
(the-as bsp-header #f))
(else
(load-dbg "bsp relocate: ~A~%" this)
;; everything is okay, link the bsp and level.
;; og:preserve-this fix bad filenames
(when (= (-> loading-level name) 'title)
(set! (-> this name) 'title))
(when (= (-> loading-level name) 'demo)
(set! (-> this name) 'demo))
(set! (-> loading-level bsp) this)
(set! (-> this level) loading-level)
this)))
(else (format 0 "ERROR: level ~A is not a valid file.~%" (-> loading-level name)) (the-as bsp-header #f)))))
(none))
(defmethod load-required-packages ((this level))
"Load the common package requested by a non-debug BSP when its level package list is nonempty."
(when (not (or (not (-> this bsp)) (= *kernel-boot-mode* 'debug-boot)))
(if (not (null? (-> this info packages))) (load-package "common" global)))
this)
;;;;;;;;;;;;;;
;; vis
;;;;;;;;;;;;;;
(defmethod vis-clear ((this level))
"Clear this level's eight VIS descriptors and 2048-byte visibility bit string, then mark
visibility as loading."
;; clear vis-infos, so we can't try to look up a vis string.
(countdown (i 8)
(nop!) ;; the usual.
(set! (-> this vis-info i) #f))
;; set the vis string to all 0s.
(dotimes (i 128)
(set! (deref int128 (-> this vis-bits) i) (the-as int128 0)))
;; this flag indicates we don't have vis data because loading is in progress
(set! (-> this all-visible?) 'loading)
0)
(defmethod vis-load ((this level))
"Ensure this level's self VIS file owns an IOP ramdisk slot, evicting the other level's slot when
necessary, and return the assigned ramdisk ID."
;; check to see if we have a buffer for loaded vis data.
(when (zero? (-> this vis-info (-> this vis-self-index) ramdisk))
;; nope, we have no vis data buffer, we need to set it up.
;; first, we should see if the other level has loaded vis. if so, kill it.
(let ((other-vis (-> this other vis-info (-> this other vis-self-index))))
(when (and other-vis (nonzero? (-> other-vis ramdisk)))
(set! (-> other-vis flags) (logand #xffffffffbfffffff (-> other-vis flags))) ;; clear waiting-for-load
(set! (-> other-vis ramdisk) 0)
0))
;; set up a ramdisk rpc (fill command, actually load the file from DVD to IOP buffer)
(let ((vis-filename (make-file-name (file-kind vis) (the-as string (-> this nickname)) 0 #f))
(request (the-as ramdisk-rpc-fill (add-element *ramdisk-rpc*)))
(ramdisk-id (+ *current-ramdisk-id* 1)))
(set! *current-ramdisk-id* ramdisk-id)
(set! (-> request filename) (string->sound-name vis-filename))
(set! (-> request ee-id) ramdisk-id)
(load-dbg "doing ramdisk vis load: ~A~%" vis-filename)
(call *ramdisk-rpc* RAMDISK_RPC_FILL_FNO (the-as pointer 0) (the-as uint 0))
;; remember which ramdisk id we are assigned
(set! (-> this vis-info (-> this vis-self-index) ramdisk) ramdisk-id)))
;; return the ramdisk ID.
(-> this vis-info (-> this vis-self-index) ramdisk))
(defun load-vis-info ((vis-name symbol) (old-vis-name symbol))
"Start loading the self VIS file for the active level whose nickname matches vis-name, replacing
the old VIS file when required."
(dotimes (i (-> *level* length))
(let ((active-level (-> *level* level i)))
(when (= (-> active-level status) 'active)
(when (= vis-name (-> active-level nickname))
(format 0 "Swapping in ~A VIS [dumping ~A]~%" vis-name old-vis-name)
(vis-load active-level)))))
0)
(defmethod init-vis ((this level))
"Attach the BSP's self and neighboring level-vis-info records, initialize their owners, output
buffers, packed flags, and ramdisk state, and enable visibility-aware actor-memory warnings."
(when (not (or (= (-> this status) 'inactive) (not (-> this bsp))))
;; no vis loaded at first, mark as loading/invalid.
(set! (-> this all-visible?) 'loading)
;; vis info 0 is always self.
(let ((self-vis (-> this bsp vis-info 0)))
;; check that our vis info is valid.
(cond
((and self-vis (nonzero? self-vis) (valid? self-vis level-vis-info #f #f 0))
;; add to the level
(set! (-> this vis-info 0) self-vis)
;; don't have a string loaded yet
(set! (-> self-vis current-vis-string) (the-as uint -1))
;; link to bsp
(set! (-> self-vis from-bsp) (-> this bsp))
;; the current vis string (uncompressed). The level allocates/manages this.
(set! (-> self-vis vis-bits) (-> this vis-bits))
(logclear! (-> self-vis flags) (vis-info-flag waiting-for-iop-to-ee using-this-as-only-vis))
(logior! (-> self-vis flags) (vis-info-flag from-vis-file))
(set! (-> self-vis ramdisk) (the-as uint 0))
(set! (-> self-vis string-block) (the-as uint #f))
;; remember that we use the vis system. This will enable warnings in the kernel
;; if we run out of actor memory. Without vis, I guess this happens a lot.
(set! *vis-boot* #t))
(else
;; we don't have vis (but it's okay)
(set! (-> this vis-info 0) #f))))
;; check for up to 6 neighbor level vis info. The last one is always left as null.
(dotimes (i 6)
(let* ((vis-index (+ i 1))
(neighbor-vis (-> this bsp vis-info vis-index)))
(cond
((and neighbor-vis (nonzero? neighbor-vis) (valid? neighbor-vis level-vis-info #f #f 0))
(set! (-> this vis-info vis-index) neighbor-vis)
(set! (-> neighbor-vis current-vis-string) (the-as uint -1))
(set! (-> neighbor-vis from-bsp) #f)
(set! (-> neighbor-vis vis-bits) (-> this vis-bits))
(logclear! (-> neighbor-vis flags) (vis-info-flag from-vis-file waiting-for-iop-to-ee using-this-as-only-vis))
(set! *vis-boot* #t))
(else (set! (-> this vis-info vis-index) #f))))))
0)
(defmethod level-get-for-use ((this level-group) (name symbol) (want-status symbol))
"Return a slot for name at want-status. Reuse a matching level when possible; otherwise dispose
of the safest slot, initialize its level-info and mood state, and begin loading."
(local-vars (selected-level level))
;; debug allocate levels if necessary
(alloc-levels! this #f)
(let* ((info (lookup-level-info name))
(load-name (remap-level-name info)))
(awhen (level-get this load-name)
(level-status-set! it want-status)
(return it))
(let ((disposable-level (level-get-most-disposable this)))
(set! selected-level (if disposable-level (level-status-set! disposable-level 'inactive) disposable-level)))
;; THIS WAS BUGGED IN THE ORIGINAL GAME!! Probably due to a fault in the original GOAL compiler and because they had
;; a local variable called "level", this branch here checks for the *TYPE* object called level instead of the
;; variable. Since the type will never be equal to #f when this code runs, this failsafe never runs, and the game will
;; proceed to corrupt the symbol table since it thinks #f is a level, which most definitely crashes the game
;; very quickly.
;; We are fixing it.
(when (not selected-level) ;; level
(format 0 "ERROR: could not find a slot to load ~A into.~%" name)
(return (the-as level #f)))
(set! (-> selected-level info) info)
(set! (-> selected-level name) name)
(set! (-> selected-level load-name) load-name))
(set! (-> selected-level mood) (the mood-context (-> selected-level info mood value)))
(set! (-> selected-level mood-func)
(the (function mood-context float int none) (-> selected-level info mood-func value)))
(set! (-> selected-level display?) #f)
(set! (-> selected-level force-all-visible?) #f)
(set! (-> selected-level force-inside?) #f)
(level-status-set! selected-level 'loading)
(level-status-set! selected-level want-status)
selected-level)
;; level status:
;; - inactive: nothing loaded or loading.
;; - loading: level is reserved and in the process of loading. There can only be 1 loading level at a time.
;; when loading, the loading-level heap is set to the appropriate level heap.
;; also, (-> *level* loading-level) points to the level.
;; - loading-bt: loading the "buffer top". This is the big BSP object file, it loads differently.
;; - loading-done: loading is done, but no login/init has started
;; - login: login is in progress
;; - loaded: login is done
;; - alive: level is birthed, etc
;; - active: level is being drawn
(defmethod level-status ((this level-group) (level-name symbol))
"Return the status of the loaded level matching level-name, or false."
(let ((loaded-level (level-get *level* level-name))) (if loaded-level (-> loaded-level status))))
(defmethod level-status-set! ((this level) (want-status symbol))
"Move this level toward want-status through the legal load, login, birth, activation,
deactivation, and unload transitions. Loading states may only advance in order."
(case want-status
(('inactive)
(case (-> this status))
(unload! this))
(('loading)
(case (-> this status)
(('inactive) (load-begin this))))
(('loading-bt)
(case (-> this status)
(('loading) (set! (-> this status) want-status) (load-continue this))))
(('loading-done)
(case (-> this status)
(('loading-bt) (set! (-> this status) want-status))))
(('loaded)
(case (-> this status)
(('loading-done)
;; will actually put us in login for a bit.
(login-begin this))
(('alive 'active) (deactivate this))))
(('alive 'active)
(when *dproc*
(case (-> this status)
(('loaded)
(birth this)
;; try again. we will be in alive.
;; this will do nothing if we want alive, but will activate if we want activate
(level-status-set! this want-status))
(('alive)
(when (and *dproc* (= want-status 'active))
;; only if we want to do alive -> active
;; will set the level to be drawn.
(remove-by-param1 *background-draw-engine* (-> this bsp))
(add-connection *background-draw-engine*
*dproc*
(the (function object object object object object) add-bsp-drawable)
(-> this bsp)
this
#f)
(dotimes (i 9)
(set! (-> this closest-object i) 0.0)
(set! (-> this texture-mask i) (the-as uint 0)))
(set! (-> this level-distance) 0.0)
(set! (-> this status) 'active)))))))
this)
(define *login-state* (new 'global 'login-state))
(define *print-login* #t)
(defmethod load-continue ((this level))
"Advance this level's asynchronous DGO load, deferred texture relocation, linking, final
BSP-object load, or incremental login according to its current status."
;; see if we are still linking some file
(when (-> this linking)
;; do some more linking
(when (nonzero? (link-resume))
;; done linking and object file!
(set! (-> this linking) #f)
(case (-> this status)
(('loading)
;; load another object if we don't wanna copy anything
(if (not (-> *texture-relocate-later* memcpy)) (dgo-load-continue (the pointer (align64 (-> this heap current))))))
(('loading-bt)
;; finished loading the last object!
(level-status-set! this 'loading-done)
(level-status-set! this 'loaded))))
(return this))
;; otherwise, copy stuff that needs copying
(when (-> *texture-relocate-later* memcpy)
(relocate-later)
(dgo-load-continue (the pointer (align64 (-> this heap current))))
(return this))
;; otherwise, check status
(case (-> this status)
(('loading)
;; we are still loading
(let* ((last-object-name #f)
(loaded-object (dgo-load-get-next (& last-object-name))))
(when loaded-object
;; something has finished loading!
(cond
((not last-object-name)
;; not the last object. start linking
(cond
((dgo-load-link (the-as dgo-header loaded-object) (-> this heap) *print-login* #f)
;; linking finished (that was fast)
(if (not (-> *texture-relocate-later* memcpy)) (dgo-load-continue (the pointer (align64 (-> this heap current))))))
(else
;; linking is not done, resume later.
(set! (-> this linking) #t))))
(else
;; we're loading the last object now, which has different rules
(set! (-> this heap top) (-> this heap top-base))
(level-status-set! this 'loading-bt))))))
(('login)
;; run level login
(level-update-after-load this *login-state*))
(('loading-bt)
;; link the last object
(let ((load-destination (the pointer (align64 (-> this heap current)))))
(cond
((dgo-load-link (the-as dgo-header load-destination) (-> this heap) *print-login* #t)
(level-status-set! this 'loading-done)
;; will start login.
(level-status-set! this 'loaded))
(else (set! (-> this linking) #t))))))
this)
(defmethod load-begin ((this level))
"Reserve this level slot, clear its previous state, select the level texture allocator, build the
DGO filename, allocate two 2 MiB streaming buffers, and begin the asynchronous load."
;; set the level heap. level code logins called from linker may allocate here
(set! loading-level (-> this heap))
;; relocate method of the bsp will look for this
(set! (-> *level* loading-level) this)
;; clear out old stuff
(set! (-> *level* log-in-level-bsp) #f)
(set! (-> this nickname) #f)
(set! (-> this bsp) #f)
(set! (-> this entity) #f)
(set! (-> this ambient) #f)
(set! (-> this linking) #f)
(vis-clear this)
(set! (-> this status) 'loading)
;; incoming textures should use the level allocator
(set! (-> *texture-pool* allocate-func) texture-page-level-allocate)
;; build name
(if (= (-> this load-name) (-> this info visname))
(format (clear *temp-string*) "~S" (-> this info nickname))
(format (clear *temp-string*) "~S" (-> this name)))
(set! (-> *temp-string* data 8) (the-as uint 0))
(format *temp-string* ".DGO")
;; reset temporary allocations on level heap
(set! (-> this heap top) (-> this heap top-base))
;; allocate DGO loading buffers
(let ((buffer-a (kmalloc (-> this heap) (* 2 1024 1024) (kmalloc-flags align-64 top) "dgo-level-buf-2"))
(buffer-b (kmalloc (-> this heap) (* 2 1024 1024) (kmalloc-flags align-64 top) "dgo-level-buf-2")))
(load-dbg " DGO buffers at #x~X #x~X~%" buffer-a buffer-b)
;; we expect to load code first, remember where the heap is now.
(set! (-> this code-memory-start) (-> this heap current))
(format 0 "-----------> begin load ~A [~S]~%" (-> this load-name) *temp-string*)
;; kick off the load!
(dgo-load-begin *temp-string* buffer-b buffer-a (the pointer (align64 (-> this heap current)))))
this)
(defmethod login-begin ((this level))
"Restore the default texture allocator, log in the BSP's texture pages and ADGIF shaders,
initialize the incremental login state, and enter login status. An absent BSP unloads the
level."
;; done with load, reset the texture page allocator
(set! (-> *texture-pool* allocate-func) texture-page-default-allocate)
(cond
((-> this bsp)
(set! (-> *level* log-in-level-bsp) (-> this bsp))
;; login textures
(login-level-textures *texture-pool* this (-> this bsp texture-page-count) (-> this bsp texture-ids))
;; login shaders
(let ((bsp-data (-> this bsp)))
(when (nonzero? (-> bsp-data adgifs))
(let ((adgifs (-> bsp-data adgifs))) (dotimes (i (-> adgifs length)) (adgif-shader-login-no-remap (-> adgifs data i))))))
;; set the login state machine at the beginning.
(set! (-> *login-state* state) -1)
(set! (-> *login-state* pos) (the-as uint 0))
(set! (-> *login-state* elts) (the-as uint 0))
(set! (-> this status) 'login))
(else
;; something went wrong, kill the level.
(level-status-set! this 'inactive)
(set! loading-level global)
(set! (-> *level* loading-level) (-> *level* level-default))))
this)
(defun level-update-after-load ((loaded-level level) (level-login-state login-state))
"Advance a level's incremental login. Log in direct drawable trees and art groups, process queued
tfragment arrays and TIE prototypes in bounded batches, initialize actor navigation, then attach
visibility, load packages, publish subdivision distances, and mark the level loaded."
(local-vars
(current-timer int)
(final-timer int)
(initial-timer int)
(current-prototype prototype-bucket-tie)
(geometry-index int))
;; The EE limits one call to 100,000 Count ticks so BSP login is spread over several frames.
;; The PC has no equivalent Count register and finishes the same state machine in one call.
(let ((level-drawable-trees (-> loaded-level bsp drawable-trees)))
(#unless PC_PORT
(m initial-timer Count))
(label cfg-1)
(#unless PC_PORT
(m current-timer Count)
(let ((elapsed-timer (- current-timer initial-timer)))
(when (< #x186a0 elapsed-timer)
(set! loaded-level loaded-level)
(goto cfg-78))))
(let ((current-login-pos (the-as int (-> level-login-state pos))))
;; Login state -1.
;; in this state, we log in drawables/art-groups that are in referenced in the bsp directly
;; the current-login-pos in the index of the drawable/art to login.
(when (= (-> level-login-state state) -1)
;;(load-dbg "login state -1~%")
;; login some drawables.
(when (< current-login-pos (-> level-drawable-trees length))
(let ((current-drawable (-> level-drawable-trees trees (the-as uint current-login-pos))))
;;(load-dbg "login draw: ~A~%" current-drawable)
(cond
((= (-> current-drawable type) drawable-tree-tfrag)
;; tfrag!
(dotimes (drawable-index (-> current-drawable length))
(cond
((= (-> current-drawable data drawable-index type) drawable-inline-array-tfrag)
;; we got an array of drawables. instead of iterating/recursing, just add it to the back of the login list.
;;(load-dbg " tfrag array case~%")
(set! (-> level-login-state elt (-> level-login-state elts)) (-> current-drawable data drawable-index))
(+! (-> level-login-state elts) 1))
(else
;;(load-dbg " tfrag actual login case~%")
(login (-> current-drawable data drawable-index))))))
((= (-> current-drawable type) drawable-tree-instance-tie)
;; tie! add the tree to the list.
;;(load-dbg " tie tree case~%")
(set! (-> level-login-state elt (-> level-login-state elts)) current-drawable)
(+! (-> level-login-state elts) 1))
(else
;;(load-dbg " other actual login: ~A~%" (method-of-object current-drawable login))
(login current-drawable))))
(+! (-> level-login-state pos) 1)
(goto cfg-1))
;; this makes the art groups go at the end.
(let ((art-index (- (the-as uint current-login-pos) (the-as uint (-> level-drawable-trees length)))))
(when (< (the-as int art-index) (-> loaded-level art-group art-group-array length))
(let ((current-art-group (-> loaded-level art-group art-group-array art-index)))
(login current-art-group)
(if (needs-link? current-art-group) (link-art! current-art-group)))
(+! (-> level-login-state pos) 1)
(goto cfg-1)))
;; if we got here, we're done with state -1!
(set! (-> level-login-state pos) (the-as uint 0))
(set! (-> level-login-state state) 0)
(goto cfg-1))
;; login state 0.
;; we log in children of the drawables from state -1.
(when (< (-> level-login-state state) (the-as int (-> level-login-state elts)))
;; (load-dbg " login state 0~%")
(let ((queued-drawable (-> level-login-state elt (-> level-login-state state))))
(cond
((= (-> queued-drawable type) drawable-inline-array-tfrag)
;; (load-dbg " login drawable-inline-array-tfrag: ~A~%" queued-drawable)
(cond
((< current-login-pos (-> (the-as drawable-inline-array-tfrag queued-drawable) length))
(dotimes (batch-index 200)
(when (< current-login-pos (-> (the-as drawable-inline-array-tfrag queued-drawable) length))
;; (load-dbg " login from drawable-inline-array-tfrag: ~A~%" (-> (the-as drawable-inline-array-tfrag queued-drawable) data (the-as uint current-login-pos)))
(login (-> (the-as drawable-inline-array-tfrag queued-drawable) data (the-as uint current-login-pos)))
(set! current-login-pos (the-as int (+ (the-as uint current-login-pos) 1)))))
(set! (-> level-login-state pos) (the-as uint current-login-pos)))
(else
(set! (-> level-login-state pos) (the-as uint 0))
(set! current-login-pos (+ (-> level-login-state state) 1))
(set! (-> level-login-state state) current-login-pos))))
((= (-> queued-drawable type) drawable-tree-instance-tie)
;;(load-dbg " login drawable-tree-instance-tie: ~A~%" queued-drawable)
(let ((prototypes (-> (the-as drawable-tree-instance-tie queued-drawable) prototypes prototype-array-tie)))
(when (< current-login-pos (-> prototypes length))
(dotimes (batch-index 10)
(when (< current-login-pos (-> prototypes length))
(set! current-prototype (-> prototypes array-data (the-as uint current-login-pos)))
(set! geometry-index 0)
(#when PC_PORT
;; if a TIE uses environment mapping, disable the fade out so it always renderers with
;; the generic renderer. In the port, we just make envmapped things always envmap.
(when (!= (-> current-prototype envmap-fade-far) 0.0)
(*! (-> current-prototype envmap-fade-far) 10000.)))
(while (< geometry-index 4)
(let ((tie-geometry (-> current-prototype geometry geometry-index)))
;;(load-dbg " login geom: ~A~%" tie-geometry)
(if (nonzero? tie-geometry) (login tie-geometry)))
(set! geometry-index (+ geometry-index 1)))
(set! current-login-pos (the-as int (+ (the-as uint current-login-pos) 1)))))
(set! (-> level-login-state pos) (the-as uint current-login-pos)))
(when (= (the-as uint current-login-pos) (-> prototypes length))
(dotimes (prototype-index (-> prototypes length))
(let ((envmap-shader (-> prototypes array-data prototype-index envmap-shader)))
(when (nonzero? envmap-shader)
;;(load-dbg " login adgif shader for envmap~%")
(adgif-shader-login-no-remap envmap-shader)
(set! (-> envmap-shader tex1) (new 'static 'gs-tex1 :mmag #x1 :mmin #x1))
(set! (-> envmap-shader clamp) (new 'static 'gs-clamp :wms (gs-tex-wrap-mode clamp) :wmt (gs-tex-wrap-mode clamp)))
(set! (-> envmap-shader alpha) (new 'static 'gs-alpha :b #x2 :c #x1 :d #x1))
(set! (-> envmap-shader prims 1) (gs-reg64 tex0-1))
(set! (-> envmap-shader prims 3) (gs-reg64 tex1-1))
(set! (-> envmap-shader prims 5) (gs-reg64 miptbp1-1))
(set! (-> envmap-shader clamp-reg) (gs-reg64 clamp-1))
(set! (-> envmap-shader prims 9) (gs-reg64 alpha-1)))))
(set! (-> level-login-state pos) (the-as uint 0))
(+! (-> level-login-state state) 1))))))
(goto cfg-1))
(when (= (-> level-login-state state) (-> level-login-state elts))
(let ((bsp-data (-> loaded-level bsp)))
(cond
((or (zero? (-> bsp-data actors)) (= (the-as uint current-login-pos) (-> bsp-data actors length)))
(set! (-> level-login-state pos) (the-as uint 0))
(+! (-> level-login-state state) 1))
(else
(let ((actor-data (-> bsp-data actors data (the-as uint current-login-pos) actor)))
;; (load-dbg "entity nav login: ~A~%" actor-data)
(entity-nav-login actor-data))
(+! (-> level-login-state pos) 1))))
(goto cfg-1))
(when (zero? (the-as uint current-login-pos))
(set! (-> level-login-state pos) (the-as uint 1))
(set! loaded-level loaded-level)
(goto cfg-78))))
;; done!
(set! (-> loaded-level nickname) (-> loaded-level bsp nickname))
(if (nonzero? (-> loaded-level bsp nodes)) (set! *time-of-day-effects* #t) (set! *time-of-day-effects* #f))
(let ((close-distance (-> loaded-level bsp unk-data-4))
(far-distance (-> loaded-level bsp unk-data-5)))
(when (and (= close-distance 0.0) (= far-distance 0.0))
(set! close-distance 122880.0)
(set! far-distance 286720.0))
(set! (-> *subdivide-settings* close (-> loaded-level index)) close-distance)
(set! (-> *subdivide-settings* far (-> loaded-level index)) far-distance)
(set! (-> *subdivide-settings* close 3) close-distance)
(set! (-> *subdivide-settings* far 3) far-distance))
(load-dbg "init-vis~%")
(init-vis loaded-level)
(load-dbg "package load~%")
(load-required-packages loaded-level)
(set! (-> loaded-level status) 'loaded)
(set! loading-level global)
(set! (-> *level* loading-level) (-> *level* level-default))
(set! (-> *level* log-in-level-bsp) #f)
(#unless PC_PORT
0
(m final-timer Count)
(- final-timer initial-timer))
(label cfg-78)
loaded-level)
(defmethod birth ((this level))
"Birth a loaded BSP, restore its saved permanent entity state, mark the level alive, and notify
the camera and target."
(case (-> this status)
(('loaded)
(protect (loading-level
(-> *level* loading-level)
(-> *level* log-in-level-bsp))
(set! loading-level (-> this heap))
(set! (-> *level* log-in-level-bsp) (-> this bsp))
(set! (-> *level* loading-level) this)
(birth (-> this bsp))
(set! (-> this status) 'alive)
;;(load-dbg "copy perms~%")
(copy-perms-to-level! *game-info* this)
;;(load-dbg "send activate~%")
;; note: this isn't a great name - the level isn't actually activated, just alive.
(send-event *camera* 'level-activate (-> this name))
(send-event *target* 'level-activate (-> this name)))))
this)
(defmethod deactivate ((this level))
"Save permanent entity state, remove this level from drawing, deactivate its entities and
particles, clear inside and visibility state, and leave its data loaded."
(case (-> this status)
(('active 'alive)
(format 0 "----------- kill ~A (status ~A)~%" this (-> this status))
;; copy data from the level to the game-info storage. This will remember permanent level stuff, like
;; what you collected/completed.
(copy-perms-from-level! *game-info* this)
;; og:preserve-this fully clear entity perm status in the level itself (based on reset-actors)
;; it should be copied back out of game-info on birth to prevent "NG+ glitch"
(let ((lev-ents (-> this entity)))
(dotimes (idx (-> lev-ents length))
(let ((ent (-> lev-ents data idx entity))) (update-perm! (-> ent extra perm) 'game (the-as entity-perm-status 1919)))))
(send-event *camera* 'level-deactivate (-> this name))
(send-event *target* 'level-deactivate (-> this name))
;; remove this BSP from the engine. This will stop us from being drawn.
(remove-by-param1 *background-draw-engine* (-> this bsp))
;; track down all the entities and kill them
(deactivate-entities (-> this bsp))
;; kill any remaining particles not associated with a part-tracker
(kill-all-particles-in-level this)
;; clean up our level
(set! (-> this inside-sphere?) #f)
(set! (-> this inside-boxes?) #f)
(set! (-> this meta-inside?) #f)
(set! (-> this force-inside?) #f)
;; we're still loaded.
(set! (-> this status) 'loaded)
(set! (-> this all-visible?) 'loading)
;; clear vis buffers
(dotimes (i 128)
(set! (deref int128 (-> this vis-bits) i) (the-as int128 0)))
(let ((i 8))
(while (nonzero? i)
(+! i -1)
(let ((vis-record (-> this vis-info i))) (if vis-record (set! (-> vis-record current-vis-string) (the-as uint -1))))))))
(if (= (-> *level* log-in-level-bsp) (-> this bsp)) (set! (-> *level* log-in-level-bsp) #f))
this)
(defmethod unload! ((this level))
"Deactivate this level, unlink its art, textures, particle groups, packages, and pending art
loads, reset its heap and visibility state, and make its slot inactive."
(deactivate this)
(when (!= (-> this status) 'inactive)
;; if we linked art group, unlink it.
(when (or (= (-> this status) 'loaded) (= (-> this status) 'alive) (= (-> this status) 'active) (= (-> this status) 'login))
(dotimes (art-index (-> this art-group art-group-array length))
(let ((current-art-group (-> this art-group art-group-array art-index)))
(if (needs-link? current-art-group) (unlink-art! current-art-group)))))
;; turn some things off
(set! (-> this bsp) #f)
(set! (-> this entity) #f)
(set! (-> this ambient) #f)
(set! (-> this status) 'inactive)
(set! (-> this art-group string-array length) 0)
(set! (-> this art-group art-group-array length) 0)
;; unload texture pages
(countdown (texture-index (-> this loaded-texture-page-count))
(dotimes (common-index 32)
(when (= (-> this loaded-texture-page texture-index) (-> *texture-pool* common-page common-index))
(set! (-> *texture-pool* common-page common-index) (the-as texture-page 0))))
(unload! *texture-pool* (-> this loaded-texture-page texture-index)))
(set! (-> this loaded-texture-page-count) 0)
(unlink-textures-in-heap! *texture-page-dir* (-> this heap))
;; unload particle groups that were defined in the level data
(unlink-part-group-by-heap (-> this heap))
;; if there are any in-progress art loads for this level, kill them.
(dotimes (buffer-index 2)
(let ((pending-file (-> *art-control* buffer buffer-index pending-load-file)))
(if (and (>= (the-as int pending-file) (the-as int (-> this heap base)))
(< (the-as int pending-file) (the-as int (-> this heap top-base))))
(set-pending-file (-> *art-control* buffer buffer-index) (the-as string #f) -1 (the-as handle #f) 100000000.0))))
;; unload packages (doesn't really do anything.)
(let* ((packages (-> this info packages))
(package (car packages)))
(while (not (null? packages))
(case (rtype-of package)
((symbol) (unload (symbol->string (the-as symbol package))))
((string) (unload (the-as string package))))
(set! packages (cdr packages))
(set! package (car packages))))
(vis-clear this)
;; reset the level heap!
(let ((level-heap (-> this heap))) (set! (-> level-heap current) (-> level-heap base)))
(set! (-> this code-memory-start) (the-as pointer 0))
(set! (-> this code-memory-end) (the-as pointer 0))
(when (= (-> *level* loading-level) this)
(set! loading-level global)
(set! (-> *level* loading-level) (-> *level* level-default))
(set! (-> *level* log-in-level-bsp) #f)))
this)
;; method 27 level
;; method 10 level
(defmethod is-object-visible? ((this level) (drawable-index int))
"Return whether drawable-index's bit is set in this level's current visibility string. Return
false when the bit is clear; the PC actor visibility setting may bypass this test."
;; og:preserve-this pc port added option to show every actor regardless
(with-pc
(if (not (-> *pc-settings* ps2-actor-vis?)) (return #t)))
;; check the vis bits!
(let* (;; lwu v1, 388(a0)
(vis-data (-> this vis-bits))
;; sra a0, a1, 3
(byte-idx (sar drawable-index 3))
;; daddu v1, a0, v1
;; lb v1, 0(v1)
(vis-byte (-> (the (pointer int8) vis-data) byte-idx))
;; andi a0, a1, 7
(bit-idx (logand drawable-index #b111))
;; addiu a0, a0, 56
(shift-amount (+ bit-idx 56)) ;; 56 + 8 = 64, to set the sign bit
;; dsllv v1, v1, a0
(check-sign-word (the int (shl vis-byte shift-amount))) ;; signed
)
;; slt v1, v1, r0 v1 = (csw < 0)
;; daddiu v0, s7, 8
;; movz v0, s7, v1 if (csw >= 0) result = false
;;(format 0 "vis check ~D ~X ~X ~A~%" drawable-index vis-byte check-sign-word (>= check-sign-word 0))
(< check-sign-word 0)))
(defmethod point-in-boxes? ((this level) (position vector))
"Return true when position lies inside one of this level's half-open BSP boxes. force-inside?
bypasses the geometric test."
(cond
((or (not (-> this bsp)) (zero? (-> this bsp boxes)))
;; no boxes or no bsp
#f)
((-> this force-inside?) #t)
(else
(let* ((boxes (-> this bsp boxes))
(box-cursor (-> boxes data)))
(countdown (boxes-left (-> boxes length))
(if (and (>= (-> position x) (-> box-cursor 0 min x))
(>= (-> position y) (-> box-cursor 0 min y))
(>= (-> position z) (-> box-cursor 0 min z))
(< (-> position x) (-> box-cursor 0 max x))
(< (-> position y) (-> box-cursor 0 max y))
(< (-> position z) (-> box-cursor 0 max z)))
(return #t))
(set! box-cursor (the (inline-array box8s) (-> box-cursor 1)))))
#f)))
(defmethod debug-print-splitbox ((this level) (position vector) (output string))
"Print the split-box identifier for every BSP box containing position."
(cond
((or (not (-> this bsp)) (zero? (-> this bsp boxes)) (zero? (-> this bsp split-box-indices)))
;; do nothing!
)
(else
(let* ((boxes (-> this bsp boxes))
(box-cursor (-> boxes data)))
(dotimes (i (-> boxes length))
(if (and (>= (-> position x) (-> box-cursor 0 min x))
(>= (-> position y) (-> box-cursor 0 min y))
(>= (-> position z) (-> box-cursor 0 min z))
(< (-> position x) (-> box-cursor 0 max x))
(< (-> position y) (-> box-cursor 0 max y))
(< (-> position z) (-> box-cursor 0 max z)))
(format output " splitbox-~D~%" (-> this bsp split-box-indices i)))
(set! box-cursor (the (inline-array box8s) (-> box-cursor 1)))))))
0
(none))
(defmethod mem-usage ((this level) (usage memory-usage-block) (flags mem-usage-flags))
"Account for this active level's entity and ambient links, art, code, texture pages, visibility
records, and BSP data."
(when (= (-> this status) 'active)
(mem-usage-add! usage entity-links (-> this entity length) (asize-of (-> this entity)))
(set! (-> usage length) (max 65 (-> usage length)))
(set! (-> usage data 64 name) "ambient-links")
(+! (-> usage data 64 count) (-> this ambient length))
(let ((ambient-bytes (asize-of (-> this ambient))))
(+! (-> usage data 64 used) ambient-bytes)
(+! (-> usage data 64 total) (logand -16 (+ ambient-bytes 15))))
(mem-usage (-> this art-group) usage flags)
(mem-usage-add! usage level-code 1 (&- (-> this code-memory-end) (the-as uint (-> this code-memory-start))))
(countdown (i (-> this loaded-texture-page-count))
(mem-usage (-> this loaded-texture-page i) usage flags))
(countdown (vis-index 8)
(let ((vis-record (-> this vis-info vis-index)))
(when vis-record
(cond
((zero? vis-index)
(mem-usage-add! usage bsp-leaf-vis-self 1 (asize-of vis-record)))
(else
(mem-usage-add! usage bsp-leaf-vis-adj 1 (+ (asize-of vis-record) (the-as int (-> vis-record allocated-length)))))))))
(mem-usage (-> this bsp) usage flags))
this)
(#cond
(PC_PORT
(defconstant LEVEL_HEAP_SIZE (* 10416 1024)) ;; 10.416K
(defconstant LEVEL_HEAP_SIZE_DEBUG (* 11000 1024)))
(#t
(defconstant LEVEL_HEAP_SIZE (* 10416 1024)) ;; 10.416K
(defconstant LEVEL_HEAP_SIZE_DEBUG (* 25600 1024)) ;; 25.600K
))
(defmethod alloc-levels! ((this level-group) (compact-level-heaps symbol))
"Load the shared art packages and allocate three level heaps. compact-level-heaps selects the
smaller heap size and loads common explicitly."
;; only do stuff if levels are not allocated
(when (zero? (-> *level* level0 heap base))
;; GAME.CGO is made up of ART.CGO and COMMON.CGO
(when (nmember "game" *kernel-packages*)
(set! *kernel-packages* (cons "art" *kernel-packages*))
(set! *kernel-packages* (cons "common" *kernel-packages*)))
(load-package "art" global) ;; load ART
(if compact-level-heaps
(load-package "common" global) ;; load COMMON unless we're debugging levels
)
;; allocate level heaps. turn on compact-level-heaps for use in 32MB systems
(let ((level-heap-size (if compact-level-heaps LEVEL_HEAP_SIZE LEVEL_HEAP_SIZE_DEBUG)))
(dotimes (lev LEVEL_COUNT)
(let ((level-heap (-> this level lev heap)))
(set! (-> level-heap base) (malloc 'global level-heap-size))
(set! (-> level-heap current) (-> level-heap base))
(set! (-> level-heap top-base) (&+ (-> level-heap base) level-heap-size))
(set! (-> level-heap top) (-> level-heap top-base))))))
0)
(defmethod level-get-with-status ((this level-group) (status symbol))
"Return the first level slot with status, or false."
(dotimes (i (-> this length))
(if (= (-> this level i status) status) (return (-> this level i))))
(the-as level #f))
(defmethod level-get-most-disposable ((this level-group))
"Choose a slot for a new load. Prefer inactive, loading, or merely loaded slots; otherwise choose
the lowest-priority active level whose boxes do not contain the camera."
;; check inactive levels first
(dotimes (i (-> this length))
(case (-> this level i status)
(('inactive) (return (-> this level i)))))
;; check for any loading levels
(dotimes (i (-> this length))
(case (-> this level i status)
(('loading 'loading-bt) (return (-> this level i)))))
;; check for loaded, but not active, levels.
(dotimes (i (-> this length))
(if (= (-> this level i status) 'loaded) (return (-> this level i))))
;; check active levels. pick one we're not in bounds of.
(let ((candidate (the-as level #f)))
(dotimes (i (-> this length))
(case (-> this level i status)
(('active)
(if (and (not (-> this level i inside-boxes?))
(or (not candidate) (< (-> this level i info priority) (-> candidate info priority))))
(set! candidate (-> this level i))))))
candidate))
(defmethod level-get ((this level-group) (name symbol))
"Return the non-inactive level whose requested or remapped load name matches name, or false."
(dotimes (i (-> this length))
(if (and (!= (-> this level i status) 'inactive) (or (= (-> this level i name) name) (= (-> this level i load-name) name)))
(return (-> this level i))))
(the level #f))
(defmethod art-group-get-by-name ((this level-group) (name string))
"Search all three level slots for an art group whose name matches name, or return false."
(countdown (level-index 3)
(let ((loaded-level (-> this level level-index)))
(countdown (art-index (-> loaded-level art-group art-group-array length))
(if (name= (-> loaded-level art-group art-group-array art-index name) name)
(return (-> loaded-level art-group art-group-array art-index))))))
(the-as art-group #f))
(defmethod activate-levels! ((this level-group))
"Request active status for every allocated level slot."
(dotimes (i (-> this length))
(level-status-set! (-> this level i) 'active))
0)
(defmethod level-get-target-inside ((this level-group))
"Choose an active level for the target. Prefer the current continue's level, then the first
inside-box level, then the first remembered meta-inside level, and finally the first active
level. The distance accumulators are never updated, so they do not rank later candidates."
(let ((target-position (target-pos 0)))
(let ((continue-level-name (-> *game-info* current-continue level)))
(dotimes (i (-> this length))
(let ((active-level (-> this level i)))
(when (= (-> active-level status) 'active)
(if (= (-> active-level name) continue-level-name) (return active-level))))))
(let ((inside-level (the-as level #f)))
(let ((unused-distance-limit 0.0)) ;; This is never updated, so the first match wins.
(dotimes (i (-> this length))
(let ((active-level (-> this level i)))
(when (= (-> active-level status) 'active)
(let ((distance (vector-vector-distance (-> active-level bsp bsphere) target-position)))
(if (and (-> active-level inside-boxes?) (or (not inside-level) (< distance unused-distance-limit)))
(set! inside-level active-level)))))))
(if inside-level (return inside-level))))
(dotimes (i (-> this length))
(let ((active-level (-> this level i)))
(when (= (-> active-level status) 'active)
(if (-> active-level meta-inside?) (return active-level)))))
(let ((selected-level (the-as level #f)))
(let ((unused-distance-limit 0.0)) ;; This is never updated, so the first active level wins.
(dotimes (i (-> this length))
(let ((active-level (-> this level i)))
(when (= (-> active-level status) 'active)
(if (or (not selected-level) (< (-> active-level level-distance) unused-distance-limit)) (set! selected-level active-level))))))
selected-level))
(defmethod load-commands-set! ((this level-group) (load-commands pair))
"Replace the level group's pending load-command list and return it."
(set! (-> this load-commands) load-commands)
load-commands)
(defmethod mem-usage ((this level-group) (usage memory-usage-block) (flags mem-usage-flags))
"Account for every allocated level slot."
;; get memory usage of each level
(dotimes (i (-> this length))
(mem-usage (-> this level i) usage flags))
this)
(defun bg ((level-name symbol))
"Load and activate one level for the background/debug entry point. Accept its canonical name,
VIS name, or nickname; load its runtime packages, initialize load-state and continue state, and
synchronously advance loading when no display process exists."
(set! *cheat-mode* (if *debug-segment* 'debug #f))
(let ((info (lookup-level-info level-name)))
(cond
((= (-> info visname) level-name) (set! (-> *level* vis?) #t) (set! level-name (-> info name)))
(else (set! (-> *level* vis?) #f) (set! (-> *kernel-context* low-memory-message) #f)))
(let* ((packages (-> info run-packages))
(package (car packages)))
(while (not (null? packages))
(let ((package-type (rtype-of package)))
(cond
((= package-type symbol) (load-package (symbol->string (the-as symbol package)) global))
((= package-type string) (load-package (the-as string package) global))))
(set! packages (cdr packages))
(set! package (car packages)))))
(let ((loaded-level (level-get-for-use *level* level-name 'active)))
(while (and loaded-level
(or (= (-> loaded-level status) 'loading) (= (-> loaded-level status) 'loading-bt) (= (-> loaded-level status) 'login))
(not *dproc*))
(load-continue loaded-level))
(vis-load loaded-level)
(set! (-> *load-state* vis-nick) (if (-> *level* vis?) (-> loaded-level nickname) #f))
(set! (-> *load-state* want 0 name) (-> loaded-level name))
(set! (-> *load-state* want 0 display?) 'display)
(set! (-> *load-state* want 0 force-vis?) #f)
(set! (-> *load-state* want 0 force-inside?) #f)
(set! (-> *load-state* want 1 name) #f)
(set! (-> *load-state* want 1 display?) #f)
(set! (-> *load-state* want 1 force-inside?) #f)
(if (-> loaded-level info continues)
(set-continue! *game-info* (the-as continue-point (car (-> loaded-level info continues))))))
(activate-levels! *level*)
(set! *print-login* #f)
0)
(defun play ((vis-enabled symbol) (initialize-progress symbol))
"Initialize the normal game entry point: choose the startup level from the boot message, allocate
level heaps, reset presentation and load state, load and activate the startup level, start the
display process, and optionally initialize game progress."
;; temp
(format #t "(play :use-vis ~A :init-game ~A) has been called!~%" vis-enabled initialize-progress)
(format 0 "(play :use-vis ~A :init-game ~A) has been called!~%" vis-enabled initialize-progress)
(format 0 "*kernel-boot-message*: ~A~%" *kernel-boot-message*)
;;(kernel-shutdown)
(let ((startup-level (case *kernel-boot-message*
(('play) (if *debug-segment* 'village1 'title))
(else 'demo))))
(stop 'play)
(set! (-> *level* vis?) vis-enabled)
(set! (-> *level* want-level) #f)
(set! (-> *level* border?) #t)
(set! (-> *setting-control* default border-mode) #t)
(set! (-> *level* play?) #t)
(alloc-levels! *level* #f) ;;#t)
(set! *display-profile* #f)
(set! *cheat-mode* (if *debug-segment* 'debug #f))
(set! *time-of-day-fast* #f)
(load-commands-set! *level* '())
(when *time-of-day-proc*
(set! (-> *time-of-day-proc* 0 time-ratio) (fsec 1.0))
(set! (-> *time-of-day-proc* 0 hour) 7) ;; 7AM waking up in the morning
)
(set-blackout-frames 6)
(unless *dproc*
(reset! *load-state*)
(let ((startup-level-data (level-get-for-use *level* startup-level 'active)))
(load-state-want-levels startup-level #f)
(load-state-want-display-level startup-level 'display)
(load-state-want-vis (-> (lookup-level-info startup-level) nickname))
(while (and startup-level-data
(or (= (-> startup-level-data status) 'loading)
(= (-> startup-level-data status) 'loading-bt)
(= (-> startup-level-data status) 'login)))
(set-blackout-frames 6)
(load-continue startup-level-data))))
(set! *print-login* #f)
(level-status-set! (level-get *level* startup-level) 'active))
(load-dbg "Load complete. Level: ~A. Now starting display!~%" (-> *level* level0))
(on #t)
(load-dbg "Display started: ~A~%" *dproc*)
(when initialize-progress
(initialize! *game-info* 'game (the-as game-save #f) (the-as string #f)))
0)
(defun update-sound-banks ()
"Reconcile the two resident sound-bank slots with the distinct banks required by active levels.
Do nothing while the loader RPC is busy or a movie is active, report more than two requirements,
and perform at most one load or unload per call."
(if (nonzero? (rpc-busy? RPC-SOUND-LOADER)) (return 0))
(let ((required-bank-a #f)
(required-bank-b #f))
(dotimes (level-index (-> *level* length))
(let ((active-level (-> *level* level level-index)))
(when (= (-> active-level status) 'active)
(let* ((banks (-> active-level info sound-banks))
(bank (the-as symbol (car banks))))
(while (not (null? banks))
(cond
((or (= required-bank-a bank) (= required-bank-b bank) (-> *setting-control* current movie)))
((not required-bank-a) (set! required-bank-a bank))
((not required-bank-b) (set! required-bank-b bank))
(else (format 0 "ERROR: Soundbanks ~A, ~A and ~A all required~%" required-bank-a required-bank-b bank)))
(set! banks (cdr banks))
(set! bank (the-as symbol (car banks))))))))
(when (and required-bank-a (!= required-bank-a *sound-bank-1*) (!= required-bank-a *sound-bank-2*))
(when (not *sound-bank-1*)
(format 0 "Load soundbank ~A~%" required-bank-a)
(sound-bank-load (string->sound-name (symbol->string required-bank-a)))
(set! *sound-bank-1* required-bank-a)
(return 0))
(when (not *sound-bank-2*)
(format 0 "Load soundbank ~A~%" required-bank-a)
(sound-bank-load (string->sound-name (symbol->string required-bank-a)))
(set! *sound-bank-2* required-bank-a)
(return 0))
(when (!= *sound-bank-1* required-bank-b)
(format 0 "Unload soundbank ~A~%" *sound-bank-1*)
(sound-bank-unload (string->sound-name (symbol->string *sound-bank-1*)))
(set! *sound-bank-1* #f)
(return 0))
(when (!= *sound-bank-2* required-bank-b)
(format 0 "Unload soundbank ~A~%" *sound-bank-2*)
(sound-bank-unload (string->sound-name (symbol->string *sound-bank-2*)))
(set! *sound-bank-2* #f)
(return 0)))
(when (and required-bank-b (!= required-bank-b *sound-bank-1*) (!= required-bank-b *sound-bank-2*))
(when (not *sound-bank-1*)
(format 0 "Load soundbank ~A~%" required-bank-b)
(sound-bank-load (string->sound-name (symbol->string required-bank-b)))
(set! *sound-bank-1* required-bank-b)
(return 0))
(when (not *sound-bank-2*)
(format 0 "Load soundbank ~A~%" required-bank-b)
(sound-bank-load (string->sound-name (symbol->string required-bank-b)))
(set! *sound-bank-2* required-bank-b)
(return 0))
(when (!= *sound-bank-1* required-bank-a)
(format 0 "Unload soundbank ~A~%" *sound-bank-1*)
(sound-bank-unload (string->sound-name (symbol->string *sound-bank-1*)))
(set! *sound-bank-1* #f)
(return 0))
(when (!= *sound-bank-2* required-bank-a)
(format 0 "Unload soundbank ~A~%" *sound-bank-2*)
(sound-bank-unload (string->sound-name (symbol->string *sound-bank-2*)))
(set! *sound-bank-2* #f)
(return 0))))
0)
(defmethod update! ((this load-state))
"Advance requested level loading, display, forced-visibility, and forced-inside state. Discard
unrequested levels before starting another load, wait synchronously only when both ordinary
slots were empty and the requested display mode requires it, then switch the active VIS file
after entering the requested level's boxes."
(update-sound-banks)
(let ((discarded-level #f))
(dotimes (level-index 2)
(let ((loaded-level (-> *level* level level-index)))
(when (!= (-> loaded-level status) 'inactive)
(let ((requested #f))
(dotimes (request-index 2)
(if (= (-> loaded-level name) (-> this want request-index name)) (set! requested #t)))
(when (not requested)
(format 0 "Discarding level ~A~%" (-> loaded-level name))
(level-status-set! loaded-level 'inactive)
(set! discarded-level #t)
(#when PC_PORT
(when *debug-segment*
(define-extern *entity* entity)
(set! *entity* (the entity #f)))))))))
(let ((ordinary-slots-empty #f))
(if (and (= (-> *level* level0 status) 'inactive) (= (-> *level* level1 status) 'inactive)) (set! ordinary-slots-empty #t))
(if discarded-level (return 0))
(let ((need-level0 #f)
(need-level1 #f))
(when (-> this want 0 name)
(set! need-level0 #t)
(dotimes (level-index 3)
(let ((loaded-level (-> *level* level level-index)))
(if (and (!= (-> loaded-level status) 'inactive) (= (-> loaded-level name) (-> this want 0 name))) (set! need-level0 #f)))))
(when (-> this want 1 name)
(set! need-level1 #t)
(dotimes (level-index 3)
(let ((loaded-level (-> *level* level level-index)))
(if (and (!= (-> loaded-level status) 'inactive) (= (-> loaded-level name) (-> this want 1 name))) (set! need-level1 #f)))))
(let ((request-to-load -1))
(cond
((and need-level0 need-level1)
(set! request-to-load 0)
(if (and (-> this want 1 display?) (not (-> this want 0 display?))) (set! request-to-load 1)))
(need-level0 (set! request-to-load 0))
(need-level1 (set! request-to-load 1)))
(when (!= request-to-load -1)
(when (or ordinary-slots-empty (not (check-busy *load-dgo-rpc*)))
(format 0 "Adding level ~A~%" (-> this want request-to-load name))
(let ((requested-level (level-get-for-use *level* (the-as symbol (-> this want request-to-load name)) 'loaded)))
(when (and ordinary-slots-empty (-> this want request-to-load display?))
(format 0 "Waiting for level to load~%")
(while (or (= (-> requested-level status) 'loading)
(= (-> requested-level status) 'loading-bt)
(= (-> requested-level status) 'login))
(load-continue requested-level))))))))))
(dotimes (request-index 2)
(when (-> this want request-index name)
(dotimes (level-index 3)
(let ((loaded-level (-> *level* level level-index)))
(when (!= (-> loaded-level status) 'inactive)
(when (= (-> loaded-level name) (-> this want request-index name))
(when (!= (-> loaded-level display?) (-> this want request-index display?))
(cond
((not (-> loaded-level display?))
(cond
((or (= (-> loaded-level status) 'loaded) (= (-> loaded-level status) 'active))
(format 0 "Displaying level ~A [~A]~%" (-> this want request-index name) (-> this want request-index display?))
(level-get-for-use *level* (-> loaded-level info name) 'active)
(set! (-> loaded-level display?) (-> this want request-index display?)))
(else
(when (and (-> loaded-level info wait-for-load) (!= (-> this want request-index display?) 'display-no-wait))
(send-event *target* 'loading))
(if (= *cheat-mode* 'debug) (format *stdcon* "display on for ~A but level is loading~%" (-> this want request-index name))))))
(else
(cond
((not (-> this want request-index display?))
(set! (-> loaded-level display?) #f)
(format 0 "Turning level ~A off~%" (-> loaded-level name))
(deactivate loaded-level))
(else
(format 0
"Setting level ~A display command to ~A~%"
(-> this want request-index name)
(-> this want request-index display?))
(set! (-> loaded-level display?) (-> this want request-index display?)))))))
(when (!= (-> loaded-level force-all-visible?) (-> this want request-index force-vis?))
(set! (-> loaded-level force-all-visible?) (-> this want request-index force-vis?))
(format 0
"Setting force-all-visible?[~A] to ~A~%"
(-> this want request-index name)
(-> this want request-index force-vis?)))
(when (!= (-> loaded-level force-inside?) (-> this want request-index force-inside?))
(set! (-> loaded-level force-inside?) (-> this want request-index force-inside?))
(format 0
"Setting force-inside?[~A] to ~A~%"
(-> this want request-index name)
(-> this want request-index force-inside?)))))))))
;; load vis info.
;; The load-state's vis-nick is the level we want vis data for.
;; Note that we won't load vis until we are inside the level's boxes.
;; this will be the level that is currently being used.
(let ((loaded-vis-nick #f))
(dotimes (level-index (-> *level* length))
(let ((active-level (-> *level* level level-index)))
(when (= (-> active-level status) 'active) ;; level is active
(if (nonzero? (-> active-level vis-info (-> active-level vis-self-index) ramdisk)) ;; and vis is set up.
(set! loaded-vis-nick (-> active-level nickname))))))
;; if we have the wrong vis
(when (and (!= loaded-vis-nick (-> this vis-nick)) (-> *level* vis?))
;; and we want a vis
(when (-> this vis-nick)
;; find matching level and load vis
(dotimes (level-index (-> *level* length))
(let ((active-level (-> *level* level level-index)))
(when (= (-> active-level status) 'active)
(if (and (= (-> active-level nickname) (-> this vis-nick))
(-> active-level inside-boxes?) ;; note: only start if we are inside boxes.
)
(load-vis-info (-> this vis-nick) loaded-vis-nick))))))))
0)
;; method 16 level-group (debug text stuff)
(defmethod level-update ((this level-group))
"Advance settings, art and DGO loading, inside-box and checkpoint state, load-state requests,
neighboring VIS selection and ownership, and level debug displays for this frame. Publish both
displayed BSP names to the PC renderer."
;; this does nothing...
(camera-pos)
(new 'static 'boxed-array :type symbol :length 0 :allocated-length 2)
;; compute the settings for this frame
(update *setting-control*)
;; run the art loading system
(update *art-control* #t)
(clear-rec *art-control*)
;; run level loading!
(dotimes (load-slot 2)
(load-continue (-> this level load-slot)))
;; compute inside for each level
(dotimes (level-index (-> this length))
(let ((active-level (-> this level level-index)))
(when (= (-> active-level status) 'active)
(set! (-> active-level inside-boxes?) (point-in-boxes? active-level (-> *math-camera* trans)))
(set! (-> active-level inside-sphere?) (>= (-> active-level bsp bsphere w) (-> active-level level-distance)))
;; being inside sets your meta-inside to #t. If you are outside, remember your old inside.
(if (-> active-level inside-boxes?) (set! (-> active-level meta-inside?) #t)))))
;; update load state machine (the level-border one)
(update! *load-state*)
;; checkpoint assignment
(dotimes (level-index (-> this length))
(let ((active-level (-> this level level-index)))
(when (= (-> active-level status) 'active)
;; if you're outside here, and inside somewhere else, kick out of meta inside.
(if (and (-> active-level inside-boxes?) (not (-> active-level other inside-boxes?)))
(set! (-> active-level other meta-inside?) #f))
(when (and (null? (-> this load-commands))
(= (-> active-level nickname) (-> *load-state* vis-nick))
(!= (-> active-level name) (-> *game-info* current-continue level))
(-> *level* border?))
(let ((nearest-continue (the-as continue-point (car (-> active-level info continues)))))
(let* ((target-position (target-pos 0))
(continues (-> active-level info continues))
(candidate-continue (the-as continue-point (car continues))))
(while (not (null? continues))
(if (and (< (vector-vector-distance target-position (-> candidate-continue trans))
(vector-vector-distance target-position (-> nearest-continue trans)))
(zero? (-> candidate-continue flags)))
(set! nearest-continue candidate-continue))
(set! continues (cdr continues))
(set! candidate-continue (the-as continue-point (car continues))))
(set-continue! *game-info* nearest-continue)))))))
;; determine vis info idx for each level
(dotimes (level-index (-> this length))
(let ((active-level (-> this level level-index)))
(when (= (-> active-level status) 'active)
;; self is always 0
(set! (-> active-level vis-self-index) 0)
;; neighbor level defaults to 7 (null placeholder)...
(set! (-> active-level vis-adj-index) 7)
;; but if there's a second level that's active, search for a vis info for that level...
(when (= (-> active-level other status) 'active)
(dotimes (vis-index 8)
(if (and (-> active-level vis-info vis-index)
(= (-> active-level vis-info vis-index from-level) (-> active-level other load-name)))
;; and store it in the adj index.
(set! (-> active-level vis-adj-index) vis-index)))))))
;; display level vis info
(when *display-level-border*
(dotimes (level-index (-> this length))
(let ((active-level (-> this level level-index)))
(when (= (-> active-level status) 'active)
(let ((border-flags (-> active-level bsp current-bsp-back-flags)))
(dotimes (neighbor-index 6)
(when (and (logtest? border-flags 3) (-> active-level vis-info (+ neighbor-index 1)))
(let ((neighbor-info (lookup-level-info (-> active-level vis-info (+ neighbor-index 1) from-level))))
(format *stdcon*
" ~A -> ~A: load: ~A display: ~A~%"
(-> active-level name)
(-> neighbor-info name)
(logtest? border-flags 1)
(logtest? border-flags 2))))
(set! border-flags (shr border-flags 2))))))))
;; if we have vis for level A, but we aren't "in" it, display an error and
;; force us out of the other level. Ideally the boxes and the load boundary system
;; will be consistent and there is no way to set a vis to a level that we aren't in.
;; (you can be "in" multiple levels at the same time, when crossing levels, it is expected
;; that you are in both.)
(dotimes (level-index (-> this length))
(let ((active-level (-> this level level-index)))
(when (= (-> active-level status) 'active)
(when (and (= (-> active-level nickname) (-> *load-state* vis-nick)) ;; vis for A
(not (-> active-level inside-boxes?)) ;; but not in A
)
(if (and (= *cheat-mode* 'debug) (-> active-level other inside-boxes?))
(format *stdcon* "~3LForcing outside of ~A [bad split boxes]~%~0L" (-> active-level other name)))
(set! (-> active-level other inside-boxes?) #f)))))
;; if we are outside of the boxes, we consider ourselves "outside of bsp"
;; if we are outside of both levels boxes, then we don't really know what to do
;; for vis, and we can display the classic "outside of bsp" error.
(cond
((not (or (-> this level0 inside-boxes?) (-> this level1 inside-boxes?)))
(when (or (-> this level0 vis-info 0) (-> this level1 vis-info 0))
(if (= *cheat-mode* 'debug) (format *stdcon* "~3Loutside of bsp~%~0L"))))
(else
;; we are in at least one bsp.
;; now we need to link vis info to bsps.
(dotimes (level-index (-> this length))
(let ((active-level (-> this level level-index)))
(when (= (-> active-level status) 'active)
;; loop over vis infos
(dotimes (vis-index 8)
(let ((vis-record (-> active-level vis-info vis-index)))
(when vis-record
;; clear bit 31
(logclear! (-> vis-record flags) (vis-info-flag using-this-as-only-vis))
;; link info to bsp
(cond
((= vis-index (-> active-level vis-self-index)) (set! (-> vis-record from-bsp) (-> active-level bsp)))
((= vis-index (-> active-level vis-adj-index)) (set! (-> vis-record from-bsp) (-> active-level other bsp)))
(else (set! (-> vis-record from-bsp) #f))))))
;; cam-update only consumes a VIS record with using-this-as-only-vis set. Select the
;; self or adjacent record according to the display override and which split boxes
;; contain the camera.
(cond
;; special display self mode.
((= (-> active-level display?) 'display-self)
(let ((self-vis (-> active-level vis-info (-> active-level vis-self-index))))
(if self-vis (logior! (-> self-vis flags) (vis-info-flag using-this-as-only-vis)))))
;; in this level, but not the other, only use vis for this.
((and (-> active-level inside-boxes?) (not (-> active-level other inside-boxes?)))
(let ((self-vis (-> active-level vis-info (-> active-level vis-self-index))))
(if self-vis (logior! (-> self-vis flags) (vis-info-flag using-this-as-only-vis)))))
;; only in other level, only use vis for other.
((-> active-level other inside-boxes?)
(let ((adjacent-vis (-> active-level vis-info (-> active-level vis-adj-index))))
(if adjacent-vis (logior! (-> adjacent-vis flags) (vis-info-flag using-this-as-only-vis)))))))))))
(when (or *display-level-border* *display-texture-download* *display-split-box-info*)
(when *display-level-border*
(format *stdcon*
" want: ~A ~A/~A ~A ~A/~A~%"
(-> *load-state* want 0 name)
(-> *load-state* want 0 display?)
(-> *load-state* want 0 force-vis?)
(-> *load-state* want 1 name)
(-> *load-state* want 1 display?)
(-> *load-state* want 1 force-vis?))
(format *stdcon*
" nick ~A cur ~S cont ~A~%~%"
(-> *load-state* vis-nick)
(let ((lev-name (and *target* (-> *target* current-level name)))) (if lev-name (symbol->string lev-name)))
(-> *game-info* current-continue name))
; (let ((t9-16 format)
; (a0-53 *stdcon*)
; (a1-49 " nick ~A cur ~S cont ~A~%~%")
; (a2-24 (-> *load-state* vis-nick))
; (v1-142 (and *target* (-> *target* current-level name)))
; )
; (t9-16 a0-53 a1-49 a2-24 (if v1-142
; (->
; (the-as
; (pointer uint32)
; (+ #xff38 (the-as int v1-142))
; )
; )
; )
; (-> *game-info* current-continue name)
; )
; )
)
(dotimes (level-index (-> this length))
(let ((active-level (-> this level level-index)))
(when (= (-> active-level status) 'active)
(format *stdcon*
"~A: ~S ~A~%"
(-> active-level name)
(if (point-in-boxes? active-level (-> *math-camera* trans)) "inside")
(-> active-level display?))
(when *display-texture-download*
(format *stdcon* " tfrag: ~8,,0m " (-> active-level closest-object 0))
(format *stdcon* " shrub: ~8,,0m " (-> active-level closest-object 2))
(format *stdcon* " alpha: ~8,,0m #x~8X~%" (-> active-level closest-object 3) (-> active-level texture-mask 8))
(format *stdcon* " tie: ~8,,0m " (-> active-level closest-object 5))
(format *stdcon* " fg-tf: ~8,,0m " (-> active-level closest-object 6))
(format *stdcon* " fg-pr: ~8,,0m #x~8X~%" (-> active-level closest-object 7) (-> active-level texture-mask 7))
(format *stdcon*
" tf: ~8D pr: ~8D sh: ~8D al: ~8D wa: ~8D~%~1K"
(-> active-level upload-size 0)
(-> active-level upload-size 1)
(-> active-level upload-size 2)
(-> active-level upload-size 3)
(-> active-level upload-size 4)))
(if *display-split-box-info* (debug-print-splitbox active-level (-> *math-camera* trans) *stdcon*))))))
;; tell PC port about our levels
(__pc-set-levels (if (symbol-member? (-> this level0 status) '(active alive loaded)) (symbol->string (bsp-name (-> this level0))) "none")
(if (symbol-member? (-> this level1 status) '(active alive loaded)) (symbol->string (bsp-name (-> this level1))) "none"))
0)
(defun-debug show-level ((level-name symbol))
"Keep the target's current level requested and add level-name as the displayed neighbor."
(set! (-> *setting-control* default border-mode) #t)
(load-state-want-levels (-> (level-get-target-inside *level*) name) level-name)
(load-state-want-display-level level-name 'display)
0)
;; init art buffers and engines
(defconstant FOREGROUND_DRAW_MAX_COUNT_0 (* PROCESS_HEAP_MULT 280))
(defconstant FOREGROUND_DRAW_MAX_COUNT_2 (* PROCESS_HEAP_MULT 16))
(defconstant DEFAULT_DRAW_MAX_COUNT_0 (* PROCESS_HEAP_MULT 280))
(defconstant DEFAULT_DRAW_MAX_COUNT_2 (* PROCESS_HEAP_MULT 10))
(when (zero? (-> *level* level0 art-group))
(let ((lev-group *level*))
(set! (-> lev-group vis?) #f)
(set! (-> lev-group loading-level) (-> lev-group level-default))
(set! (-> lev-group level0 art-group) (new 'global 'load-dir-art-group 50 (-> lev-group level0)))
(set! (-> lev-group level0 foreground-draw-engine 0) (new 'global 'engine 'draw FOREGROUND_DRAW_MAX_COUNT_0))
(set! (-> lev-group level0 foreground-draw-engine 1) (new 'global 'engine 'draw FOREGROUND_DRAW_MAX_COUNT_0))
(set! (-> lev-group level0 foreground-draw-engine 2) (new 'global 'engine 'draw FOREGROUND_DRAW_MAX_COUNT_2))
(set! (-> lev-group level1 art-group) (new 'global 'load-dir-art-group 50 (-> lev-group level1)))
(set! (-> lev-group level1 foreground-draw-engine 0) (new 'global 'engine 'draw FOREGROUND_DRAW_MAX_COUNT_0))
(set! (-> lev-group level1 foreground-draw-engine 1) (new 'global 'engine 'draw FOREGROUND_DRAW_MAX_COUNT_0))
(set! (-> lev-group level1 foreground-draw-engine 2) (new 'global 'engine 'draw FOREGROUND_DRAW_MAX_COUNT_2))
(set! (-> lev-group level-default art-group) (new 'global 'load-dir-art-group 50 (-> lev-group level1)))
(set! (-> lev-group level-default foreground-draw-engine 0) (new 'global 'engine 'draw DEFAULT_DRAW_MAX_COUNT_0))
(set! (-> lev-group level-default foreground-draw-engine 1) (new 'global 'engine 'draw DEFAULT_DRAW_MAX_COUNT_2))
(set! (-> lev-group level0 other) (-> lev-group level1))
(set! (-> lev-group level1 other) (-> lev-group level0))
(set! (-> lev-group level-default other) #f)
(dotimes (i 2)
(let ((lev (-> lev-group level i))) (set! (-> lev vis-bits) (malloc 'global 2048)) (vis-clear lev)))
(dotimes (i 3)
(let ((lev (-> lev-group level i)))
(set! (-> lev linking) #f)
(dotimes (ii 3)
(set! (-> lev foreground-sink-group ii level) lev))))))
(defmacro test-play ()
`(begin
;; before calling play, the C Kernel would set this.
(define *kernel-boot-message* 'play)
(load-package "game" global)
(play #t #t)))