summaryrefslogtreecommitdiff
path: root/scenes
diff options
context:
space:
mode:
authorRicardo Wurmus <rekado@elephly.net>2021-03-01 00:08:40 +0100
committerRicardo Wurmus <rekado@elephly.net>2021-03-09 11:40:28 +0100
commitbc2ecb951a837db673b13def15f2c31f7134415a (patch)
tree6a27f6aefe9660ec73d6b6747e45a3c1178c530b /scenes
WIP
Diffstat (limited to 'scenes')
-rw-r--r--scenes/death.scm333
-rw-r--r--scenes/game.scm259
-rw-r--r--scenes/intro.scm137
3 files changed, 729 insertions, 0 deletions
diff --git a/scenes/death.scm b/scenes/death.scm
new file mode 100644
index 0000000..6d83637
--- /dev/null
+++ b/scenes/death.scm
@@ -0,0 +1,333 @@
+;;; The Inevitable Game
+;;; Copyright © 2018, 2021 Ricardo Wurmus <rekado@elephly.net>
+;;;
+;;; This program is free software: you can redistribute it and/or
+;;; modify it under the terms of the GNU General Public License as
+;;; published by the Free Software Foundation, either version 3 of the
+;;; License, or (at your option) any later version.
+;;;
+;;; This program is distributed in the hope that it will be useful,
+;;; but WITHOUT ANY WARRANTY; without even the implied warranty of
+;;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+;;; General Public License for more details.
+;;;
+;;; You should have received a copy of the GNU General Public License
+;;; along with this program. If not, see
+;;; <http://www.gnu.org/licenses/>.
+
+(define-module (scenes death)
+ #:use-module (chickadee)
+ #:use-module (chickadee audio)
+ #:use-module (chickadee math)
+ #:use-module (chickadee math vector)
+ #:use-module ((chickadee graphics color) #:select (make-color))
+ #:use-module (chickadee graphics font)
+ #:use-module (chickadee graphics sprite)
+ #:use-module (chickadee graphics texture)
+ #:use-module (chickadee graphics tiled)
+ #:use-module (chickadee scripting)
+ #:use-module (config)
+ #:use-module (engine assets)
+ #:use-module (characters lorenzo)
+ #:use-module (characters reaper)
+ #:use-module (utils)
+ #:use-module (ice-9 match)
+ #:use-module (srfi srfi-1)
+ #:use-module (oop goops)
+ #:export (scene))
+
+(define-asset death-map
+ (load-tile-map "assets/maps/death.tmx"))
+(define-asset vignette-image
+ (load-image "assets/images/vignette.png"))
+(define-asset fade-image
+ (load-image "assets/images/fade.png"))
+(define-asset game-font
+ (load-bitmap-font "assets/fonts/good_neighbors_starling.xml"))
+(define-asset music
+ ;; TODO: no sound if mode is 'static!
+ (load-audio "assets/music/death.ogg" #:mode 'stream))
+
+
+(define agenda (make-agenda))
+(define fade-map-fill (make-color 0 0 0 0.0))
+(define fade-box-fill (make-color 0 0 0 0.0))
+(define death-text #false)
+(define pretend-walking? #false)
+(define *labels* '())
+
+(define texts
+ '("\
+Life waits for no one, but
+death is patient."
+
+ "\
+You have died. Your points
+don't matter."
+
+ "\
+Death is inevitable."
+
+ "\
+The void resumes."
+
+ "\
+It did not last."
+
+ "\
+You were busy, but none of that
+matters any more."
+
+ "\
+You were mortal after all."
+
+ "\
+Nobody outruns the clock."
+
+ "\
+Life goes on, but you end here."
+
+ "\
+This is no longer your battle
+to fight. Let go."
+
+ "\
+Life is hard, but thankfully
+quite short."))
+
+(define credits
+ '(("The Inevitable Game"
+ "Made by Ricardo Wurmus"
+ "For Lorenzo")
+ ("Sky background"
+ "by Paulina Riva (CC-BY 3.0)")
+ ("Reaper character graphics"
+ "based on \"Lil Reaper Pet\" by Tracy")
+ ("Lorenzo character graphics"
+ "Based on \"Small 3/4 RPG character base\""
+ "by Stephen Challener (Redshrike)")
+ ("Map tiles"
+ "taken from \"Zelda-like tilesets and sprites\""
+ "by ArMM1998")
+ ("Food graphics"
+ "taken from \"The Humble Food Pack\""
+ "by \"The Wise Hedgehog\"")
+ ("Font"
+ "\"Good Neighbors Starling\""
+ "by PROWNE and Clint Bellanger")
+ ("All music"
+ "composed and recorded"
+ "by Ricardo Wurmus")
+ ("Bird sounds"
+ "taken from the public domain")
+ ("Made with 100% Free Software including"
+ "- GNU Guile"
+ "- Chickadee"
+ "- Guile OpenGL"
+ "- SDL")
+ ("Thanks for enduring this game!")))
+
+(define camera-position
+ (vec2 40.0 90.0))
+
+(define world-position
+ (vec2 0.0 0.0))
+(define last-world-position
+ (vec2 0.0 0.0))
+
+(define *player* #false)
+(define *reaper* #false)
+
+
+(define (load-scene)
+ (set! death-text (list-ref texts (random (length texts))))
+ (source-stop)
+ (source-play
+ (make-source #:audio
+ (asset-ref music)
+ #:loop? #false ; TODO: loops only first chunk!
+ ))
+
+ (set! *player* (lorenzo #:start-position
+ (vec2 (+ (/ %width 2) 25)
+ (- (/ %height 2) 20))
+ #:dead? #true))
+ (update-animated-sprite *player* 0)
+ (set! (walk-speed *player*) 0.3)
+
+ (set! *reaper* (reaper #:start-position
+ (vec2 (+ (/ %width 2) 110)
+ (- (/ %height 2) 20))))
+ (update-animated-sprite *reaper* 0)
+ (set! (walk-speed *reaper*) 0.3)
+
+ (with-agenda
+ agenda
+ (spawn-script
+ (lambda ()
+ (wait-until (any key-pressed? '(escape q return space)))
+
+ ;; Fade out
+ (tween 60 0.0 1.0
+ (lambda (alpha)
+ (set! fade-box-fill
+ (make-color 0 0 0 alpha))))
+ (abort-game)))
+
+ (script
+ ;; Fade in
+ (tween 15 1.0 0.0
+ (lambda (alpha)
+ (set! fade-box-fill
+ (make-color 0 0 0 alpha))))
+
+ (tween 300 0.0 0.5
+ (lambda (alpha)
+ (set! (tint *player*)
+ (make-color 1.0 1.0 1.0 alpha))))
+
+ (walk *player* '(right))
+ (sleep 3000)
+ (walk *player* '(right) 'stop)
+ (walk *player* '(idle-front))
+ (sleep 1900)
+ (walk *player* '(up))
+ (walk *reaper* '(up))
+ (sleep 2000)
+
+ ;; Slowly fade out the map and the top text.
+ (tween 600 0.0 1.0 (lambda (value)
+ (set! fade-map-fill
+ ;; That's the colour of the "black" cave tile.
+ (make-color 0.125 0.09 0.161 value))))
+
+ (set! pretend-walking? #true)
+ (sleep 1000)
+
+ ;; Roll credits
+ (for-each
+ (lambda (lines)
+ (let ((labels (map (lambda (line i)
+ (make <label>
+ #:font game-font
+ #:text line
+ #:visible? #true
+ #:position
+ (vec2 16.0 (- 180.0 (* 12 i)))))
+ lines
+ (iota (length lines)))))
+ (set! *labels* (append *labels* labels))
+ (sleep 3000)
+ (for-each (lambda (label)
+ (set! (visible? label) #false))
+ labels)))
+ credits)
+ (sleep 6000)
+
+ (for-each (lambda (text)
+ (let ((label
+ (make <label>
+ #:font game-font
+ #:text text
+ #:visible? #true
+ #:origin (vec2 (/ %width 2)
+ (/ %height 2))
+ #:position
+ (vec2 16.0 180))))
+ (set! *labels* (cons label *labels*))
+ (sleep 3000)
+ (set! (visible? label) #false)
+ (sleep (+ (random 10000) 4000))))
+ `("There is nothing more to see here."
+ "I'm serious."
+ "What are you waiting for?"
+ "Don't waste your time!"
+ "Have you learned nothing from this game?"
+ "I've got all day, but how about you?"
+ "Look, I'm programmed to just keep going."
+ "Ugh... why are you even reading this?"
+ "Let me tell you a story..."
+ "Once upon a time, there was..."
+ ,@(let ((what '(("...a squirrel." "It had a nut allergy and died.")
+ ("...a dolphin." "It swam too far and never returned.")
+ ("...a stupid fish." "It drowned.")
+ ("...a long war." "Everybody died."))))
+ (list-ref what (random (length what))))
+ "End of story."
+ "Good night."
+ "Please just turn it off."
+ "I'm begging you."
+ "You're not listening, huh?"
+ "Well, I guess I'll just stop talking then."))))
+ (current-agenda agenda))
+
+(define (draw-scene alpha)
+ (define world-position*
+ (vec2 (round (lerp (vec2-x last-world-position)
+ (vec2-x world-position)
+ alpha))
+ (round (lerp (vec2-y last-world-position)
+ (vec2-y world-position)
+ alpha))))
+
+ (draw-tile-map (asset-ref death-map)
+ #:position world-position*
+ #:camera camera-position)
+
+ ;; Dead lorenzo
+ (draw-sprite
+ (texture-atlas-ref
+ (asset-ref (atlas *player*)) 23)
+ (vec2+ world-position*
+ (vec2 (/ %width 2) (- (/ %height 2) 20))))
+
+ ;; Wisdom
+ (let ((parts (string-split death-text #\newline)))
+ (map (lambda (part i)
+ (draw-text part
+ (vec2 16.0 (- 190.0 (* 12 i)))
+ #:font (asset-ref game-font)))
+ parts (iota (length parts))))
+
+ ;; Box for fading out map
+ (draw-sprite
+ (asset-ref fade-image)
+ (vec2 0 0)
+ #:tint fade-map-fill)
+
+ ;; Ghost
+ (draw-animated-sprite *player* (vec2 0 0))
+
+ ;; Reaper
+ (draw-animated-sprite *reaper* world-position*)
+
+ ;; Credits
+ (for-each (lambda (label)
+ (draw-label label alpha))
+ *labels*)
+
+ (draw-sprite (asset-ref vignette-image) (vec2 0 0))
+
+ ;; Box for fading in/out everything
+ (draw-sprite
+ (asset-ref fade-image)
+ (vec2 0 0)
+ #:tint fade-box-fill))
+
+(define (update-scene dt)
+ (update-agenda dt)
+ (update-animated-sprite *player* dt)
+ (update-animated-sprite *reaper* dt)
+
+ ;; Move the world
+ (unless pretend-walking?
+ (vec2-copy! world-position last-world-position)
+ (vec2-add! (position *reaper*) (velocity *reaper*))
+ (vec2-sub! world-position (velocity *player*))))
+
+
+(define scene
+ `(#:name "death"
+ #:load ,load-scene
+ #:draw ,draw-scene
+ #:update ,update-scene))
diff --git a/scenes/game.scm b/scenes/game.scm
new file mode 100644
index 0000000..9f89172
--- /dev/null
+++ b/scenes/game.scm
@@ -0,0 +1,259 @@
+;;; The Inevitable Game
+;;; Copyright © 2018, 2021 Ricardo Wurmus <rekado@elephly.net>
+;;;
+;;; This program is free software: you can redistribute it and/or
+;;; modify it under the terms of the GNU General Public License as
+;;; published by the Free Software Foundation, either version 3 of the
+;;; License, or (at your option) any later version.
+;;;
+;;; This program is distributed in the hope that it will be useful,
+;;; but WITHOUT ANY WARRANTY; without even the implied warranty of
+;;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+;;; General Public License for more details.
+;;;
+;;; You should have received a copy of the GNU General Public License
+;;; along with this program. If not, see
+;;; <http://www.gnu.org/licenses/>.
+
+(define-module (scenes game)
+ #:use-module (chickadee)
+ #:use-module (chickadee audio)
+ #:use-module (chickadee math)
+ #:use-module (chickadee math easings)
+ #:use-module (chickadee math grid)
+ #:use-module (chickadee math rect)
+ #:use-module (chickadee math vector)
+ #:use-module ((chickadee graphics color) #:select (make-color))
+ #:use-module (chickadee graphics font)
+ #:use-module (chickadee graphics path)
+ #:use-module (chickadee graphics sprite)
+ #:use-module (chickadee graphics texture)
+ #:use-module (chickadee graphics tiled)
+ #:use-module (chickadee scripting)
+ #:use-module (config)
+ #:use-module (engine assets)
+ #:use-module (characters lorenzo)
+ #:use-module (characters reaper)
+ #:use-module (utils)
+ #:use-module (ice-9 match)
+ #:use-module (srfi srfi-1)
+ #:export (scene))
+
+(define-asset game-map
+ (load-tile-map "assets/maps/01.tmx"))
+(define-asset vignette-image
+ (load-image "assets/images/vignette.png"))
+(define-asset fade-image
+ (load-image "assets/images/fade.png"))
+(define-asset game-font
+ (load-bitmap-font "assets/fonts/good_neighbors_starling.xml"))
+(define-asset music
+ ;; TODO: no sound if mode is 'static!
+ (load-audio "assets/sounds/birds.mp3" #:mode 'stream))
+
+(define location
+ (let ((positions
+ (object-layer-objects
+ (tile-map-layer-ref (asset-ref game-map) "positions")))
+ (offset (vec2 0 0)))
+ (lambda (name)
+ "Look up the location for an object with the given NAME in the
+map's positions layer."
+ (or (and=> (find (lambda (obj)
+ (equal? (map-object-name obj) name))
+ positions)
+ (lambda (obj)
+ (let ((shape (map-object-shape obj)))
+ (vec2 (- (+ (vec2-x offset) (rect-x shape))
+ (/ (rect-width shape) 2))
+ (- (+ (vec2-y offset) (rect-y shape))
+ (/ (rect-height shape) 2))))))
+ (vec2 0.0 0.0)))))
+
+(define fade-box-fill (make-color 0 0 0 1.0))
+
+(define agenda (make-agenda))
+(define last-player-position
+ (location "player"))
+(define *player-previous-keys* (list))
+(define *player* #false)
+(define *reaper* #false)
+(define *layers* #false)
+(define grid (make-grid 16))
+(define player-grid-x-offset 8)
+(define player-grid-y-offset 0)
+
+
+
+(define (load-scene)
+ (source-stop)
+ (source-play
+ (make-source #:audio
+ (asset-ref music)
+ #:loop? #false ; TODO: loops only first chunk!
+ ))
+ (set! *player* (lorenzo #:start-position
+ (location "player")))
+ (update-animated-sprite *player* 0)
+ (grid-add grid 'player
+ (+ (vec2-x (position *player*)) player-grid-x-offset)
+ (+ (vec2-y (position *player*)) player-grid-y-offset)
+ 16 16)
+ (set! (walk-speed *player*) 1.5)
+
+ (set! *reaper* (reaper #:start-position
+ (location "reaper")))
+ (update-animated-sprite *reaper* 0)
+
+ ;; XXX: Chickadee parses the object layer incorrectly, so all
+ ;; objects are flipped vertically. We use this to compute the
+ ;; corrected shapes of layer objects only once.
+ (set! *layers*
+ (map (lambda (layer-name)
+ (cons layer-name
+ (map (lambda (obj)
+ (let ((r-wrong (map-object-shape obj)))
+ (cons obj
+ (make-rect (rect-x r-wrong)
+ (- (rect-y r-wrong) (rect-height r-wrong))
+ (rect-width r-wrong)
+ (rect-height r-wrong)))))
+ (object-layer-objects
+ (tile-map-layer-ref (asset-ref game-map) layer-name)))))
+ '("food" "positions" "actions" "collision")))
+
+ (for-each (lambda (barrier)
+ (let ((r (cdr barrier)))
+ (grid-add grid (map-object-id (car barrier))
+ (rect-x r)
+ (rect-y r)
+ (rect-width r)
+ (rect-height r))))
+ (assoc-ref *layers* "collision"))
+
+ (with-agenda
+ agenda
+ (spawn-script
+ (lambda ()
+ (wait-until (any key-pressed? '(escape)))
+
+ ;; Fade out
+ (tween 160 0.0 1.0
+ (lambda (alpha)
+ (set! fade-box-fill
+ (make-color 0 0 0 alpha))))
+ (throw 'switch-scene
+ (@ (scenes death) scene))))
+ (spawn-script
+ (lambda ()
+ (forever
+ (sleep 1)
+ ;; Stop any motion in a direction when the matching key has just
+ ;; been released.
+ (let ((released (filter key-released? *player-previous-keys*)))
+ (unless (null? released)
+ (walk *player* released 'stop)))
+
+ ;; Detect newly pressed keys.
+ (let ((active (filter key-pressed? '(left right up down))))
+ (walk *player* (match active
+ (() '(idle))
+ (_ active)))
+ (set! *player-previous-keys* active))
+
+ ;; Update player position and respond to position-dependent
+ ;; events.
+ (let ((vel (velocity *player*)))
+ (vec2-copy! world-position last-world-position)
+ (vec2-add! world-position vel)
+
+ ;; TODO
+ ;; ;; Reset when the new position is invalid.
+ ;; (when (collides? player game "collision")
+ ;; (vec2-sub! pos vel))
+ ;; (and=> (collides? player game (items game))
+ ;; ;; TODO: do something to the item
+ ;; (match-lambda
+ ;; (() #f)
+ ;; (items (pk items))))
+ ))))
+
+ ;; Handle background noise fade in
+ (spawn-script
+ (lambda ()
+ (tween 60 0.0 1.0
+ (lambda (a)
+ (set-source-volume! background-music a)
+ (sleep 100))
+ #:ease ease-out-sine)))
+
+ ;; Fade in
+ (script
+ (tween 60 1.0 0.0
+ (lambda (alpha)
+ (set! fade-box-fill
+ (make-color 0 0 0 alpha))))))
+ (current-agenda agenda))
+
+(define (draw-scene alpha)
+ (define player-render-position
+ (vec2 (round (lerp (vec2-x last-player-position)
+ (vec2-x (position *player*))
+ alpha))
+ (round (lerp (vec2-y last-player-position)
+ (vec2-y (position *player*))
+ alpha))))
+
+ ;; Always centered on the player
+ (define camera
+ (let ((player-half 16))
+ (vec2- (position *player*)
+ (vec2 (- (/ %width 2) player-half)
+ (- (/ %height 2) player-half)))))
+
+ ;; The Void
+ (draw-sprite
+ (asset-ref fade-image)
+ (vec2 0 0)
+ #:tint (make-color 0.125 0.09 0.161 1.0))
+
+ ;; The lower layers of the world
+ (draw-tile-map (asset-ref game-map)
+ #:position (vec2 0 0)
+ #:camera camera
+ #:layers (list 0 1 2))
+
+ ;; Reaper
+ (draw-animated-sprite *reaper*
+ (vec2* camera -1.0))
+
+ ;; Player
+ (draw-animated-sprite *player*
+ (vec2* camera -1.0))
+
+ ;; The upper layer hiding items and the player.
+ (draw-tile-map (asset-ref game-map)
+ #:position (vec2 0 0)
+ #:camera camera
+ #:layers (list 3))
+
+ ;; Vignette
+ (draw-sprite (asset-ref vignette-image) (vec2 0 0))
+
+ ;; Box for fading in/out everything
+ (draw-sprite
+ (asset-ref fade-image)
+ (vec2 0 0)
+ #:tint fade-box-fill))
+
+(define (update-scene dt)
+ (update-agenda dt)
+ (update-animated-sprite *player* dt)
+ (update-animated-sprite *reaper* dt))
+
+
+(define scene
+ `(#:name "game"
+ #:load ,load-scene
+ #:draw ,draw-scene
+ #:update ,update-scene))
diff --git a/scenes/intro.scm b/scenes/intro.scm
new file mode 100644
index 0000000..739abed
--- /dev/null
+++ b/scenes/intro.scm
@@ -0,0 +1,137 @@
+;;; The Inevitable Game
+;;; Copyright © 2018, 2021 Ricardo Wurmus <rekado@elephly.net>
+;;;
+;;; This program is free software: you can redistribute it and/or
+;;; modify it under the terms of the GNU General Public License as
+;;; published by the Free Software Foundation, either version 3 of the
+;;; License, or (at your option) any later version.
+;;;
+;;; This program is distributed in the hope that it will be useful,
+;;; but WITHOUT ANY WARRANTY; without even the implied warranty of
+;;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+;;; General Public License for more details.
+;;;
+;;; You should have received a copy of the GNU General Public License
+;;; along with this program. If not, see
+;;; <http://www.gnu.org/licenses/>.
+
+(define-module (scenes intro)
+ #:use-module (chickadee)
+ #:use-module (chickadee audio)
+ #:use-module (chickadee math vector)
+ #:use-module (chickadee math rect)
+ #:use-module ((chickadee graphics color) #:select (make-color))
+ #:use-module (chickadee graphics font)
+ #:use-module (chickadee graphics path)
+ #:use-module (chickadee graphics sprite)
+ #:use-module (chickadee graphics texture)
+ #:use-module (chickadee scripting)
+ #:use-module (config)
+ #:use-module (engine assets)
+ #:export (scene))
+
+(define-asset game-font
+ (load-bitmap-font "assets/fonts/good_neighbors_starling.xml"))
+(define-asset intro-bg
+ (load-image "assets/images/intro-bg.png"))
+(define-asset music
+ (load-audio "assets/music/intro.ogg" #:mode 'stream))
+
+
+(define agenda (make-agenda))
+(define fade-box-fill (make-color 1 1 1 1.0))
+(define fade-box (fill (rectangle (vec2 0 0) %width %height)))
+(define texts
+ '("\
+The void has been suspended for
+a little while. You exist now."
+ "\
+Confusion gives way to a brief
+burst of consciousness.
+Here you are. What now?"
+ "\
+The haze clears and you can see
+that you are here now. You did not
+ask for this."
+ "\
+You rise from deepest waters and
+take your first breath. You wonder:
+will this last?"
+ "\
+History yields to the present.
+The present crumbles underfoot.
+You better move."
+ "\
+A leaf has turned.
+The sunshine warms and blinds.
+The leaf begins to dry."))
+(define welcome-text #false)
+
+
+
+(define (load-scene)
+ (set! *random-state* (random-state-from-platform))
+ (set! welcome-text (list-ref texts (random (length texts))))
+ (source-play
+ (make-source #:audio
+ (asset-ref music)
+ #:loop? #false))
+
+ (with-agenda
+ agenda
+ (spawn-script
+ (lambda ()
+ (wait-until (key-pressed? 'return))
+
+ ;; Fade out
+ (tween 60 0.0 1.0
+ (lambda (alpha)
+ (set! fade-box-fill
+ (make-color 0 0 0 alpha))))
+ (throw 'switch-scene
+ (module-ref (resolve-interface '(scenes game)) 'scene))))
+ (script
+ ;; Fade in
+ (tween 120 1.0 0.0
+ (lambda (alpha)
+ (set! fade-box-fill
+ (make-color 1 1 1 alpha))))))
+ (current-agenda agenda))
+
+(define (draw-scene alpha)
+ ;; Background
+ (draw-sprite (asset-ref intro-bg) (vec2 0 0))
+
+ ;; Text
+ (let ((text "Press enter to start."))
+ (draw-text text
+ (let ((width (font-line-width (asset-ref game-font) text)))
+ (vec2 (- (/ %width 2)
+ (/ width 2))
+ 30.0))
+ #:font (asset-ref game-font)))
+
+ (let ((parts (string-split welcome-text #\newline)))
+ (for-each (lambda (part i)
+ (draw-text part
+ (vec2 (- (/ %width 2)
+ (/ (font-line-width (asset-ref game-font) part) 2))
+ (- 120.0 (* 15 i)))
+ #:font (asset-ref game-font)))
+ parts (iota (length parts))))
+
+ ;; Fade box
+ (draw-canvas
+ (make-canvas
+ (with-style
+ ((fill-color fade-box-fill))
+ fade-box))))
+
+(define update-scene update-agenda)
+
+
+(define scene
+ `(#:name "intro"
+ #:load ,load-scene
+ #:draw ,draw-scene
+ #:update ,update-scene))