aboutsummaryrefslogtreecommitdiff
path: root/prefabs.scm
diff options
context:
space:
mode:
authorGene Pasquet <dev@etenil.net>2026-04-18 05:59:07 +0100
committerGene Pasquet <dev@etenil.net>2026-04-18 05:59:07 +0100
commit84f251ee6e829d33a4f29aa4043924023a378724 (patch)
treeab03d18fa192303bf2e1758743ac16c11d9da87f /prefabs.scm
parentc2085be2dd2a0cb3da05991847e35080915e547e (diff)
Re-format
Diffstat (limited to 'prefabs.scm')
-rw-r--r--prefabs.scm442
1 files changed, 221 insertions, 221 deletions
diff --git a/prefabs.scm b/prefabs.scm
index ac9d5d0..35c8180 100644
--- a/prefabs.scm
+++ b/prefabs.scm
@@ -1,222 +1,222 @@
(module (downstroke prefabs) *
-(import scheme
- (chicken base)
- (only (chicken keyword) keyword?)
- srfi-1
- (only (list-utils alist) plist->alist)
- defstruct
- (downstroke entity))
-
-;; Registry struct to hold prefab data
-(defstruct prefab-registry
- prefabs group-prefabs file engine-mixin-table user-hooks hook-table)
-
-;; Private: internal prefab composition helper.
-;; Merge alists left-to-right; earlier occurrences of a key win.
-;; Returns a fresh alist.
-(define (alist-merge . alists)
- (fold (lambda (alist acc)
- (fold (lambda (pair acc)
- (if (assq (car pair) acc)
- acc
- (cons pair acc)))
- acc
- alist))
- '()
- alists))
-
-;; Keys whose values are lists-of-plists in user data files and must be
-;; deep-converted to lists-of-alists after the top-level plist->alist pass.
-(define +nested-plist-list-keys+ '(#:animations #:parts))
-
-(define (convert-nested-plist-values alist)
- (map (lambda (pair)
- (if (memq (car pair) +nested-plist-list-keys+)
- (cons (car pair) (map plist->alist (cdr pair)))
- pair))
- alist))
-
-;; Return engine's built-in mixin table
-(define (engine-mixins)
- '((physics-body #:vx 0 #:vy 0 #:ay 0 #:gravity? #t #:solid? #t #:on-ground? #f)
- (has-facing #:facing 1)
- (animated #:anim-name idle #:anim-frame 0 #:anim-tick 0 #:tile-id 0 #:animations #t)))
-
-;; Compose a prefab entry with a mixin-table of alists.
-;; `entry` is the raw user plist-shaped entry: (name mixin-name ... #:k v #:k v ...)
-;; `mixin-table` maps mixin-name → alist (already converted in load-prefabs).
-;; Returns (name . merged-alist).
-(define (compose-prefab entry mixin-table)
- (let* ((name (car entry))
- (rest (cdr entry))
- (split (let loop ((lst rest) (mixins '()))
- (if (or (null? lst) (keyword? (car lst)))
- (cons (reverse mixins) lst)
- (loop (cdr lst) (cons (car lst) mixins)))))
- (mixin-names (car split))
- (inline-fields (cdr split))
- (inline-alist (plist->alist inline-fields))
- (mixin-alists
- (map (lambda (mname)
- (let ((m (assq mname mixin-table)))
- (if m (cdr m) (error "Unknown mixin" mname))))
- mixin-names))
- ;; inline-alist first → highest priority (earlier-wins)
- (merged (apply alist-merge (cons inline-alist mixin-alists))))
- (cons name merged)))
-
-(define *engine-hooks* '())
-
-;; Lookup a hook symbol in the hook table
-(define (lookup-hook hook-table hook-sym)
- (let ((entry (assq hook-sym hook-table)))
- (if entry
- (cdr entry)
- (error "Unknown prefab hook" hook-sym))))
-
-;; Optional profiles (enabled per group via #:pose-only-origin? / #:static-parts? in data).
-;; Pose-only origin: tweened or scripted leader, invisible, does not run physics pipelines.
-(define +pose-only-group-origin-defaults+
- '((#:solid? . #f)
- (#:gravity? . #f)
- (#:skip-render . #t)
- (#:skip-pipelines . (jump acceleration gravity velocity-x velocity-y
- tile-collisions-x tile-collisions-y on-solid entity-collisions))))
-
-;; Physics-driving origin: invisible point mass; members follow via sync-groups.
-(define +physics-group-origin-defaults+
- '((#:solid? . #f)
- (#:gravity? . #t)
- (#:skip-render . #t)
- (#:vx . 0)
- (#:vy . 0)
- (#:on-ground? . #f)))
-
-;; Static rigid parts: no integration; world pose comes from the origin each frame.
-(define +static-group-member-defaults+
- '((#:gravity? . #f)
- (#:vx . 0)
- (#:vy . 0)
- (#:on-ground? . #f)
- (#:solid? . #t)
- (#:immovable? . #t)
- (#:skip-pipelines . (jump acceleration gravity velocity-x velocity-y
- tile-collisions-x tile-collisions-y on-solid))))
-
-(define (part-with-group-locals part)
- (let* ((p part)
- (p (if (entity-ref p #:group-local-x #f) p
- (entity-set p #:group-local-x (entity-ref p #:local-x 0))))
- (p (if (entity-ref p #:group-local-y #f) p
- (entity-set p #:group-local-y (entity-ref p #:local-y 0)))))
- p))
-
-(define (load-prefabs file engine-mixin-table user-hooks)
- (let* ((data (with-input-from-file file read))
- (mixin-section (if (assq 'mixins data) (cdr (assq 'mixins data)) '()))
- (prefab-section (cdr (assq 'prefabs data)))
- (group-section (cond ((assq 'group-prefabs data) => cdr) (else '())))
- ;; Convert engine mixin-table bodies (plists) to alists.
- (engine-mixin-alist-table
- (map (lambda (m) (cons (car m) (plist->alist (cdr m))))
- engine-mixin-table))
- ;; user mixins first → user wins on assq lookup (overrides engine mixin by name)
- (user-mixin-table
- (map (lambda (m) (cons (car m) (plist->alist (cdr m))))
- mixin-section))
- (merged-mixin-table (append user-mixin-table engine-mixin-alist-table))
- ;; user-hooks first → user wins on assq lookup (overrides engine hooks by name)
- (hook-table (append user-hooks *engine-hooks*))
- (prefab-table
- (map (lambda (entry)
- (let* ((composed (compose-prefab entry merged-mixin-table))
- (converted (convert-nested-plist-values (cdr composed))))
- (cons (car composed) converted)))
- prefab-section))
- (group-table
- (map (lambda (entry)
- (let* ((name (car entry))
- (alist-fields (plist->alist (cdr entry)))
- (converted (convert-nested-plist-values alist-fields)))
- (cons name converted)))
- group-section)))
- (make-prefab-registry
- prefabs: prefab-table
- group-prefabs: group-table
- file: file
- engine-mixin-table: engine-mixin-table
- user-hooks: user-hooks
- hook-table: hook-table)))
-
-(define (reload-prefabs! registry)
- (load-prefabs (prefab-registry-file registry)
- (prefab-registry-engine-mixin-table registry)
- (prefab-registry-user-hooks registry)))
-
-(define (do-instantiate-prefab registry entry x y w h)
- (let* ((base (entity-set-many (make-entity x y w h) (cdr entry)))
- (hook-val (entity-ref base #:on-instantiate #f))
- (handler
- (cond
- ((procedure? hook-val) hook-val)
- ((symbol? hook-val)
- (lookup-hook (prefab-registry-hook-table registry) hook-val))
- (else #f))))
- (if handler (handler base) base)))
-
-
-(define (instantiate-prefab registry type x y w h)
- (if (not registry)
- #f
- (let ((entry (assq type (prefab-registry-prefabs registry))))
- (if (not entry)
- #f
- (do-instantiate-prefab registry entry x y w h)))))
-
-(define (instantiate-group-member part ox oy gid type-members static-parts?)
- (let* ((p0 (part-with-group-locals part))
- (merged (alist-merge p0 (if static-parts? +static-group-member-defaults+ '())))
- (lx (entity-ref merged #:group-local-x 0))
- (ly (entity-ref merged #:group-local-y 0))
- (typ (entity-ref merged #:type type-members))
- (with-type (entity-set merged #:type typ))
- (g1 (entity-set with-type #:group-id gid))
- (g2 (entity-set g1 #:group-local-x lx))
- (g3 (entity-set g2 #:group-local-y ly))
- (g4 (entity-set g3 #:x (+ ox lx))))
- (entity-set g4 #:y (+ oy ly))))
-
-;; Instantiate a group prefab: one origin entity (pose) + members with #:group-local-x/y.
-;; Returns (origin member ...) or #f. Each instance gets a fresh gensym #:group-id.
-(define (instantiate-group-prefab registry type ox oy)
- (if (not registry)
- #f
- (let ((entry (assq type (prefab-registry-group-prefabs registry))))
- (if (not entry)
- #f
- (let* ((spec (cdr entry))
- (gid (gensym (symbol->string type)))
- (parts (entity-ref spec #:parts '()))
- (type-members (entity-ref spec #:type-members 'group-part))
- (pose-only? (entity-ref spec #:pose-only-origin? #f))
- (static-parts? (entity-ref spec #:static-parts? #f))
- (ow (entity-ref spec #:origin-width 0))
- (oh (entity-ref spec #:origin-height 0))
- (ot (entity-ref spec #:origin-type 'group-origin))
- (origin-fields
- (alist-merge
- `((#:type . ,ot)
- (#:group-id . ,gid)
- (#:group-origin? . #t)
- (#:x . ,ox) (#:y . ,oy)
- (#:width . ,ow) (#:height . ,oh))
- (if pose-only?
- +pose-only-group-origin-defaults+
- +physics-group-origin-defaults+)))
- (origin origin-fields)
- (members
- (map (lambda (part)
- (instantiate-group-member
- part ox oy gid type-members static-parts?))
- parts)))
- (cons origin members)))))))
+ (import scheme
+ (chicken base)
+ (only (chicken keyword) keyword?)
+ srfi-1
+ (only (list-utils alist) plist->alist)
+ defstruct
+ (downstroke entity))
+
+ ;; Registry struct to hold prefab data
+ (defstruct prefab-registry
+ prefabs group-prefabs file engine-mixin-table user-hooks hook-table)
+
+ ;; Private: internal prefab composition helper.
+ ;; Merge alists left-to-right; earlier occurrences of a key win.
+ ;; Returns a fresh alist.
+ (define (alist-merge . alists)
+ (fold (lambda (alist acc)
+ (fold (lambda (pair acc)
+ (if (assq (car pair) acc)
+ acc
+ (cons pair acc)))
+ acc
+ alist))
+ '()
+ alists))
+
+ ;; Keys whose values are lists-of-plists in user data files and must be
+ ;; deep-converted to lists-of-alists after the top-level plist->alist pass.
+ (define +nested-plist-list-keys+ '(#:animations #:parts))
+
+ (define (convert-nested-plist-values alist)
+ (map (lambda (pair)
+ (if (memq (car pair) +nested-plist-list-keys+)
+ (cons (car pair) (map plist->alist (cdr pair)))
+ pair))
+ alist))
+
+ ;; Return engine's built-in mixin table
+ (define (engine-mixins)
+ '((physics-body #:vx 0 #:vy 0 #:ay 0 #:gravity? #t #:solid? #t #:on-ground? #f)
+ (has-facing #:facing 1)
+ (animated #:anim-name idle #:anim-frame 0 #:anim-tick 0 #:tile-id 0 #:animations #t)))
+
+ ;; Compose a prefab entry with a mixin-table of alists.
+ ;; `entry` is the raw user plist-shaped entry: (name mixin-name ... #:k v #:k v ...)
+ ;; `mixin-table` maps mixin-name → alist (already converted in load-prefabs).
+ ;; Returns (name . merged-alist).
+ (define (compose-prefab entry mixin-table)
+ (let* ((name (car entry))
+ (rest (cdr entry))
+ (split (let loop ((lst rest) (mixins '()))
+ (if (or (null? lst) (keyword? (car lst)))
+ (cons (reverse mixins) lst)
+ (loop (cdr lst) (cons (car lst) mixins)))))
+ (mixin-names (car split))
+ (inline-fields (cdr split))
+ (inline-alist (plist->alist inline-fields))
+ (mixin-alists
+ (map (lambda (mname)
+ (let ((m (assq mname mixin-table)))
+ (if m (cdr m) (error "Unknown mixin" mname))))
+ mixin-names))
+ ;; inline-alist first → highest priority (earlier-wins)
+ (merged (apply alist-merge (cons inline-alist mixin-alists))))
+ (cons name merged)))
+
+ (define *engine-hooks* '())
+
+ ;; Lookup a hook symbol in the hook table
+ (define (lookup-hook hook-table hook-sym)
+ (let ((entry (assq hook-sym hook-table)))
+ (if entry
+ (cdr entry)
+ (error "Unknown prefab hook" hook-sym))))
+
+ ;; Optional profiles (enabled per group via #:pose-only-origin? / #:static-parts? in data).
+ ;; Pose-only origin: tweened or scripted leader, invisible, does not run physics pipelines.
+ (define +pose-only-group-origin-defaults+
+ '((#:solid? . #f)
+ (#:gravity? . #f)
+ (#:skip-render . #t)
+ (#:skip-pipelines . (jump acceleration gravity velocity-x velocity-y
+ tile-collisions-x tile-collisions-y on-solid entity-collisions))))
+
+ ;; Physics-driving origin: invisible point mass; members follow via sync-groups.
+ (define +physics-group-origin-defaults+
+ '((#:solid? . #f)
+ (#:gravity? . #t)
+ (#:skip-render . #t)
+ (#:vx . 0)
+ (#:vy . 0)
+ (#:on-ground? . #f)))
+
+ ;; Static rigid parts: no integration; world pose comes from the origin each frame.
+ (define +static-group-member-defaults+
+ '((#:gravity? . #f)
+ (#:vx . 0)
+ (#:vy . 0)
+ (#:on-ground? . #f)
+ (#:solid? . #t)
+ (#:immovable? . #t)
+ (#:skip-pipelines . (jump acceleration gravity velocity-x velocity-y
+ tile-collisions-x tile-collisions-y on-solid))))
+
+ (define (part-with-group-locals part)
+ (let* ((p part)
+ (p (if (entity-ref p #:group-local-x #f) p
+ (entity-set p #:group-local-x (entity-ref p #:local-x 0))))
+ (p (if (entity-ref p #:group-local-y #f) p
+ (entity-set p #:group-local-y (entity-ref p #:local-y 0)))))
+ p))
+
+ (define (load-prefabs file engine-mixin-table user-hooks)
+ (let* ((data (with-input-from-file file read))
+ (mixin-section (if (assq 'mixins data) (cdr (assq 'mixins data)) '()))
+ (prefab-section (cdr (assq 'prefabs data)))
+ (group-section (cond ((assq 'group-prefabs data) => cdr) (else '())))
+ ;; Convert engine mixin-table bodies (plists) to alists.
+ (engine-mixin-alist-table
+ (map (lambda (m) (cons (car m) (plist->alist (cdr m))))
+ engine-mixin-table))
+ ;; user mixins first → user wins on assq lookup (overrides engine mixin by name)
+ (user-mixin-table
+ (map (lambda (m) (cons (car m) (plist->alist (cdr m))))
+ mixin-section))
+ (merged-mixin-table (append user-mixin-table engine-mixin-alist-table))
+ ;; user-hooks first → user wins on assq lookup (overrides engine hooks by name)
+ (hook-table (append user-hooks *engine-hooks*))
+ (prefab-table
+ (map (lambda (entry)
+ (let* ((composed (compose-prefab entry merged-mixin-table))
+ (converted (convert-nested-plist-values (cdr composed))))
+ (cons (car composed) converted)))
+ prefab-section))
+ (group-table
+ (map (lambda (entry)
+ (let* ((name (car entry))
+ (alist-fields (plist->alist (cdr entry)))
+ (converted (convert-nested-plist-values alist-fields)))
+ (cons name converted)))
+ group-section)))
+ (make-prefab-registry
+ prefabs: prefab-table
+ group-prefabs: group-table
+ file: file
+ engine-mixin-table: engine-mixin-table
+ user-hooks: user-hooks
+ hook-table: hook-table)))
+
+ (define (reload-prefabs! registry)
+ (load-prefabs (prefab-registry-file registry)
+ (prefab-registry-engine-mixin-table registry)
+ (prefab-registry-user-hooks registry)))
+
+ (define (do-instantiate-prefab registry entry x y w h)
+ (let* ((base (entity-set-many (make-entity x y w h) (cdr entry)))
+ (hook-val (entity-ref base #:on-instantiate #f))
+ (handler
+ (cond
+ ((procedure? hook-val) hook-val)
+ ((symbol? hook-val)
+ (lookup-hook (prefab-registry-hook-table registry) hook-val))
+ (else #f))))
+ (if handler (handler base) base)))
+
+
+ (define (instantiate-prefab registry type x y w h)
+ (if (not registry)
+ #f
+ (let ((entry (assq type (prefab-registry-prefabs registry))))
+ (if (not entry)
+ #f
+ (do-instantiate-prefab registry entry x y w h)))))
+
+ (define (instantiate-group-member part ox oy gid type-members static-parts?)
+ (let* ((p0 (part-with-group-locals part))
+ (merged (alist-merge p0 (if static-parts? +static-group-member-defaults+ '())))
+ (lx (entity-ref merged #:group-local-x 0))
+ (ly (entity-ref merged #:group-local-y 0))
+ (typ (entity-ref merged #:type type-members))
+ (with-type (entity-set merged #:type typ))
+ (g1 (entity-set with-type #:group-id gid))
+ (g2 (entity-set g1 #:group-local-x lx))
+ (g3 (entity-set g2 #:group-local-y ly))
+ (g4 (entity-set g3 #:x (+ ox lx))))
+ (entity-set g4 #:y (+ oy ly))))
+
+ ;; Instantiate a group prefab: one origin entity (pose) + members with #:group-local-x/y.
+ ;; Returns (origin member ...) or #f. Each instance gets a fresh gensym #:group-id.
+ (define (instantiate-group-prefab registry type ox oy)
+ (if (not registry)
+ #f
+ (let ((entry (assq type (prefab-registry-group-prefabs registry))))
+ (if (not entry)
+ #f
+ (let* ((spec (cdr entry))
+ (gid (gensym (symbol->string type)))
+ (parts (entity-ref spec #:parts '()))
+ (type-members (entity-ref spec #:type-members 'group-part))
+ (pose-only? (entity-ref spec #:pose-only-origin? #f))
+ (static-parts? (entity-ref spec #:static-parts? #f))
+ (ow (entity-ref spec #:origin-width 0))
+ (oh (entity-ref spec #:origin-height 0))
+ (ot (entity-ref spec #:origin-type 'group-origin))
+ (origin-fields
+ (alist-merge
+ `((#:type . ,ot)
+ (#:group-id . ,gid)
+ (#:group-origin? . #t)
+ (#:x . ,ox) (#:y . ,oy)
+ (#:width . ,ow) (#:height . ,oh))
+ (if pose-only?
+ +pose-only-group-origin-defaults+
+ +physics-group-origin-defaults+)))
+ (origin origin-fields)
+ (members
+ (map (lambda (part)
+ (instantiate-group-member
+ part ox oy gid type-members static-parts?))
+ parts)))
+ (cons origin members)))))))