504 lines
19 KiB
Clojure
504 lines
19 KiB
Clojure
(ns advent.utils
|
|
(:refer-clojure :exclude [load])
|
|
(:require [play-clj.core :refer :all]
|
|
[play-clj.ui :refer :all]
|
|
[play-clj.utils :refer :all]
|
|
[play-clj.math :refer :all]
|
|
[play-clj.g2d :refer :all]
|
|
[play-clj.entities :refer [->TextureEntity]]
|
|
[clojure.java.io :as io]
|
|
[clojure.edn :as edn]
|
|
[clojure.string :as str]
|
|
[advent.saves :as saves])
|
|
(:import [com.badlogic.gdx.graphics Pixmap Pixmap$Format Pixmap$Blending Pixmap$Filter Texture Texture$TextureFilter]
|
|
[com.badlogic.gdx.graphics.g2d TextureRegion Animation]
|
|
[com.badlogic.gdx.utils.viewport FitViewport]
|
|
[com.badlogic.gdx.utils ScreenUtils]
|
|
[com.badlogic.gdx.files FileHandle]
|
|
[com.badlogic.gdx.graphics PixmapIO$PNG]
|
|
[com.badlogic.gdx.scenes.scene2d Actor Stage]
|
|
[com.badlogic.gdx.math CatmullRomSpline]
|
|
[com.badlogic.gdx Application Audio Files Game Gdx Graphics Input
|
|
InputMultiplexer InputProcessor Net Preferences Screen]
|
|
[java.lang Object]))
|
|
|
|
(defn log-coords [screen entities]
|
|
(let [{:keys [x y]} (input->screen screen {:x (:input-x screen) :y (:input-y screen)})]
|
|
(println (:input-x screen) (:input-y screen) "->" x y)))
|
|
|
|
(def +all-cursors+ [:main :wool :mushrooms :carrot :right :down :left :up :flask :flask-with-contents :trophy :ladder :stick :cat-toy :balloon :frog-legs :teddy :portrait :recipe :glass-eye :motivational-tapes :used-earplugs :grass :slobber :flask-with-strength :medal :kiss :sword :hourglass :mandrake :ball-n-chain :key :rope :crowbar :note-1 :ash :sack-lunch :flies :spear :monocle :feather :spell-component :money :watch :broken-clock :slingshot :camera :walkie-talkies :alarm-clock :walkie-talkie :flask-water :flask-water-stuff :flask-water-stuff-2 :note-2 :magic-slingshot :active-main :shovel :broom])
|
|
|
|
(def settings (atom {:music-volume 50.0
|
|
:sound-volume 75.0
|
|
:fullscreen true}))
|
|
|
|
(def selected-save (atom nil))
|
|
|
|
(defn current-music-volume [& [factor]]
|
|
(* (Math/pow (/ (:music-volume @settings) 100.0) 2)
|
|
0.25
|
|
(or factor 1.0)))
|
|
|
|
(defn current-sound-volume [& [factor]]
|
|
(* (Math/pow (/ (:sound-volume @settings) 100.0) 2)
|
|
0.5
|
|
(or factor 1.0)))
|
|
|
|
(defn cursor [filename which]
|
|
(let [scale 2
|
|
base-cursor (pixmap filename)
|
|
target-width (* 16 scale)
|
|
target-height (* 16 scale)
|
|
resized (Pixmap. target-width target-height (.getFormat base-cursor))
|
|
index (.indexOf +all-cursors+ which)]
|
|
(Pixmap/setFilter Pixmap$Filter/NearestNeighbour)
|
|
(pixmap! resized :draw-pixmap base-cursor (* index 16) 0 16 16
|
|
0 0 target-width target-height)
|
|
|
|
resized ))
|
|
|
|
(defn get-texture [path]
|
|
(let [atlas-name (str/replace path #".png" "")
|
|
atlas (texture-atlas "packed/pack.atlas")]
|
|
(texture (texture-atlas! atlas :find-region atlas-name))))
|
|
|
|
(defn save-screenshot-file-name [name]
|
|
(str "screenshot-" (clojure.core/name name) ".png" ))
|
|
|
|
(defn snapshot-list []
|
|
(let [prefs (.getPreferences (Gdx/app) "ticks-tales-saves")]
|
|
(if (.contains prefs "saves")
|
|
(edn/read-string (.getString prefs "saves"))
|
|
[])))
|
|
|
|
(defn snapshot-screenshots []
|
|
(for [snapshot (snapshot-list)]
|
|
(update-in snapshot [:screenshot]
|
|
#(try (Pixmap. (FileHandle. %) )
|
|
(catch Exception e
|
|
(Pixmap. 160 120 Pixmap$Format/RGB888))))))
|
|
|
|
(defn save-chapter [entities chapter]
|
|
(let [prefs (.getPreferences (Gdx/app) "ticks-tales-saves")
|
|
chapters (if (.contains prefs "chapters")
|
|
(edn/read-string (.getString prefs "chapters"))
|
|
saves/chapters)
|
|
chapters (assoc chapters chapter {:state (assoc (entities :state)
|
|
:x (get-in entities [:room :entities :ego :x])
|
|
:y (get-in entities [:room :entities :ego :y]))})]
|
|
(.putString prefs "chapters" (pr-str chapters))
|
|
(.flush prefs)))
|
|
|
|
(defn get-chapters []
|
|
(let [prefs (.getPreferences (Gdx/app) "ticks-tales-saves")]
|
|
(if (.contains prefs "chapters")
|
|
(edn/read-string (.getString prefs "chapters"))
|
|
saves/chapters)))
|
|
|
|
(defn save [entities id name & [blurb]]
|
|
(let [prefs (.getPreferences (Gdx/app) "ticks-tales-saves")
|
|
saves (if (.contains prefs "saves")
|
|
(edn/read-string (.getString prefs "saves"))
|
|
[])
|
|
saves (cons {:name name
|
|
:id id
|
|
:screenshot (save-screenshot-file-name id)
|
|
:state (assoc (entities :state)
|
|
:x (get-in entities [:room :entities :ego :x])
|
|
:y (get-in entities [:room :entities :ego :y]))
|
|
:blurb (or blurb name)}
|
|
(filter (comp (complement #{id}) :id) saves))]
|
|
(.putString prefs "saves" (pr-str saves))
|
|
(.flush prefs)
|
|
(on-gl (let [f (FileHandle. (save-screenshot-file-name id))
|
|
_ (Pixmap/setFilter Pixmap$Filter/BiLinear)
|
|
viewport (-> @ (resolve 'advent.screens.scene/scene) :screen deref :viewport)
|
|
[x y w h g-l g-r] [(.getScreenX viewport) (.getScreenY viewport) (.getScreenWidth viewport) (.getScreenHeight viewport)]
|
|
pm (ScreenUtils/getFrameBufferPixmap x y w h)
|
|
resized (Pixmap. 160 120 Pixmap$Format/RGB888)
|
|
_ (.drawPixmap resized pm 0 0 w h 0 0 160 120)
|
|
png (PixmapIO$PNG. (* w h 1.5))]
|
|
(.write png f resized)
|
|
(.dispose png)))))
|
|
|
|
(defn remove-save [id]
|
|
(let [prefs (.getPreferences (Gdx/app) "ticks-tales-saves")
|
|
saves (if (.contains prefs "saves")
|
|
(edn/read-string (.getString prefs "saves"))
|
|
[])
|
|
saves (filter (comp (complement #{id}) :id) saves)]
|
|
(.putString prefs "saves" (pr-str saves))
|
|
(.flush prefs)
|
|
(on-gl (let [f (FileHandle. (save-screenshot-file-name id))]
|
|
(.delete f)))))
|
|
|
|
(defn save-settings! []
|
|
(doto (.getPreferences (Gdx/app) "ticks-tales-saves")
|
|
(.putString "settings" (pr-str @settings))
|
|
.flush))
|
|
|
|
(defn has-saved-settings? []
|
|
(-> (.getPreferences (Gdx/app) "ticks-tales-saves")
|
|
(.contains "settings")))
|
|
|
|
(defn load-settings! []
|
|
(reset! settings
|
|
(-> (.getPreferences (Gdx/app) "ticks-tales-saves")
|
|
(.getString "settings")
|
|
edn/read-string)))
|
|
|
|
(defn get-font [filename]
|
|
(let [font (bitmap-font filename)
|
|
tr (bitmap-font! font :get-region)
|
|
tx (.getTexture tr)]
|
|
(call! ^Texture tx :set-filter Texture$TextureFilter/Linear Texture$TextureFilter/Linear)
|
|
font))
|
|
|
|
(def +screen-width+ 320)
|
|
(def +screen-height+ 240)
|
|
|
|
(defn scaler-fn-with-baseline [baseline minimum-size & [maximum-size]]
|
|
(let [maximum-size (or maximum-size 1.0)]
|
|
(fn [[_ y]]
|
|
(if (< y baseline) maximum-size
|
|
(let [percent-complete (- 1.0 (/ (- y baseline) (- +screen-height+ baseline)))
|
|
range (+ (* percent-complete (- maximum-size minimum-size)) minimum-size)]
|
|
range)))))
|
|
|
|
(defn get-scale-value [image x y]
|
|
(let [base-y (Math/floor (- 240 y))
|
|
other-y (Math/ceil (- 240 y))
|
|
base-amount (- y (Math/floor y))
|
|
other-amount (- 1.0 base-amount)
|
|
base-v (-> image
|
|
(pixmap! :get-pixel x base-y)
|
|
color
|
|
(.r))
|
|
other-v (-> image
|
|
(pixmap! :get-pixel x other-y)
|
|
color
|
|
(.r))]
|
|
(+ (* base-v base-amount)
|
|
(* other-v other-amount))))
|
|
|
|
(defn scaler-fn-from-image [image minimum-size maximum-size]
|
|
(let [image (pixmap image)
|
|
maximum-size (or maximum-size 1.0)]
|
|
(fn [[x y]]
|
|
(let [percent-complete (get-scale-value image x y)]
|
|
(if (< y 0)
|
|
maximum-size
|
|
(+ (* percent-complete (- maximum-size minimum-size)) minimum-size))))))
|
|
|
|
(defn dist [x1 y1 x2 y2 & {:keys [y-sign x-sign]}]
|
|
(let [y-sign (or y-sign 1.0)
|
|
x-sign (or x-sign 1.0)
|
|
dx (* (- x1 x2) x-sign)
|
|
dy (* y-sign (- y1 y2))]
|
|
(Math/sqrt (+ (* dx dx) (* dy dy)))))
|
|
|
|
|
|
(defn flip [^Animation anim]
|
|
(animation (animation! anim :get-frame-duration)
|
|
(for [src-frame (animation! anim :get-key-frames)
|
|
:let [frame (texture (texture! src-frame :get-texture))]]
|
|
(do
|
|
(texture! frame :set-region ^TextureRegion src-frame)
|
|
(texture! frame :flip true false)
|
|
frame))))
|
|
|
|
(defn split-texture [file [tile-width tile-height] frames]
|
|
(let [sheet (get-texture file)
|
|
sheet-obj ^TextureRegion (:object sheet)
|
|
width ^int (int (.getRegionWidth sheet-obj))
|
|
x ^int (int (.getRegionX sheet-obj))
|
|
y ^int (int (.getRegionY sheet-obj))]
|
|
(for [frame frames
|
|
:let [new-tex ^TextureRegion (TextureRegion. sheet-obj)]]
|
|
(do
|
|
(.setRegion new-tex
|
|
(unchecked-add x (unchecked-multiply tile-width frame))
|
|
y
|
|
tile-width
|
|
tile-height)
|
|
(->TextureEntity new-tex))))
|
|
)
|
|
|
|
(defn make-anim [file [w h] speed frames]
|
|
(animation speed (split-texture file [w h] frames)))
|
|
|
|
(defn make-anim-seq [file [w h] speed frames]
|
|
(animation speed (map #(get-texture (str file "-" (inc %) ".png")) frames)))
|
|
|
|
(defn make-bird [screen p]
|
|
(let [bird-sheet (texture! (get-texture "outside-castle/bird.png") :split 1 2)
|
|
bird-stand (animation 0.15 (for [i [0 1]]
|
|
(aget bird-sheet 0 i)))]
|
|
(assoc (animation->texture screen bird-stand)
|
|
:x 0
|
|
:y 0
|
|
:baseline 21
|
|
:anim bird-stand
|
|
:anim-start 0
|
|
:path (catmull-rom-spline (map #(apply vector-2* %) p) true)
|
|
:update-fn (fn [screen entities entity]
|
|
(let [speed 0.05
|
|
pos-f (- (* (:total-time screen) speed) (int (* (:total-time screen) speed)))
|
|
v (vector-2 0 0)
|
|
a (catmull-rom-spline! ^CatmullRomSpline (:path entity) :value-at v pos-f)]
|
|
(assoc entity :x (vector-2! v :x) :y (vector-2! v :y)))))))
|
|
|
|
(defn path-point [speed screen entities entity]
|
|
(* (- (:total-time screen) (:path-start-time entity 0.0)) speed)
|
|
)
|
|
|
|
(defn update-path-location [speed screen entities entity]
|
|
(if (:path entity)
|
|
(let [pos-f (- (path-point speed screen entities entity) (int (* (- (:total-time screen) (:path-start-time entity 0.0)) speed)))
|
|
v (vector-2 0 0)
|
|
a (catmull-rom-spline! ^CatmullRomSpline (:path entity) :value-at v pos-f)]
|
|
(assoc entity :x (vector-2! v :x) :y (vector-2! v :y)))
|
|
entity))
|
|
|
|
(defn find-override [entities [x y]]
|
|
(first (concat (filter #(and ((:mouse-in? %) entities x y)
|
|
|
|
(:override %))
|
|
(get-in entities [:room :interactions]))
|
|
(filter #(and (:mouse-in? %)
|
|
((:mouse-in? %) entities x y)
|
|
(not= "ego" (:id %))
|
|
(:script %))
|
|
(vals (get-in entities [:room :entities])))
|
|
(filter #(and ((:mouse-in? %) entities x y)
|
|
(:script %))
|
|
(get-in entities [:room :interactions])))))
|
|
|
|
(defn hovering-ego [entities [x y]]
|
|
(when-let [mouse-in (get-in entities [:room :entities :ego :mouse-in?])]
|
|
(mouse-in entities x y)))
|
|
|
|
(defn remove-interaction [entities id]
|
|
(update-in entities [:room :interactions] (fn [i] (remove #(= id (:id %)) i))))
|
|
|
|
|
|
(defn play-music [snd]
|
|
(music! snd :play))
|
|
|
|
(defn stop-music [snd]
|
|
(music! snd :stop))
|
|
|
|
(defn make-music [r]
|
|
(try
|
|
(doto (music r) (music! :set-looping true))
|
|
(catch Exception _
|
|
(doto (music (str r ".mp3")) (music! :set-looping true)))))
|
|
|
|
(defn load-sound [f]
|
|
(try
|
|
(sound f)
|
|
(catch Exception e
|
|
(println e)
|
|
(sound (str f ".mp3")))))
|
|
|
|
|
|
|
|
(defn apply-tweens [screen entities tweens]
|
|
(reduce (fn [e f]
|
|
(f e (:total-time screen)))
|
|
entities
|
|
(vals tweens)))
|
|
|
|
(defn intersects? [e [x y]]
|
|
(and (:object e)
|
|
(< (:x e) x (+ (:x e) (or (:width e) (when (actor? e) (.getWidth ^Actor (:object e))))))
|
|
(< (:y e) y (+ (:y e) (or (:height e) (when (actor? e) (.getHeight ^Actor (:object e))))))))
|
|
|
|
(defn get-current-music [entities]
|
|
(let [time (get-in entities [:state :time])
|
|
musics (:musics entities)
|
|
override-music (musics (get-in entities [:music-override :value]))
|
|
current-music (musics (get-in entities [:room :music]))
|
|
current-time-music (musics (get-in entities [:room :music time]))]
|
|
(or override-music current-music current-time-music)))
|
|
|
|
|
|
(defn setup-viewport [screen width height]
|
|
(let [cam (or (:cam screen) (orthographic))
|
|
viewport (FitViewport. width height cam)
|
|
stage (Stage. viewport)]
|
|
(update! screen :renderer stage :viewport viewport :camera cam)
|
|
(.apply viewport)
|
|
[cam viewport stage]))
|
|
|
|
(defn unproject
|
|
([screen]
|
|
(unproject screen [(:input-x screen) (:input-y screen)]) )
|
|
([screen [x y]]
|
|
(let [pj (.unproject ^FitViewport (:viewport screen) (vector-2 x y))
|
|
x (.x pj)
|
|
y (.y pj)]
|
|
[x y])))
|
|
|
|
(defn project
|
|
([screen [x y]]
|
|
(let [pj (.project ^FitViewport (:viewport screen) (vector-2 x y))
|
|
x (.x pj)
|
|
y (.y pj)]
|
|
[x y])))
|
|
|
|
(defn contains-point? [x1 y1 width height x y]
|
|
(and (< x1 x (+ x1 width))
|
|
(< y1 y (+ y1 height))))
|
|
|
|
(defn is-unstoppable-script-running [screen entities]
|
|
(let [current-action (get-in entities [:actions :current])
|
|
is-script-running (get-in entities [:actions :script-running?])]
|
|
(and is-script-running
|
|
(= :none (get-in entities [:actions :last-skip-type])))))
|
|
|
|
(defn update-override [{:keys [^FitViewport viewport] :as screen} entities]
|
|
(let [raw-pos (get-in entities [:cursor :last-pos])
|
|
last-pos (unproject screen raw-pos)
|
|
mouse-override (find-override entities last-pos)
|
|
out-of-bounds (not (contains-point? (.getScreenX viewport) (.getScreenY viewport)
|
|
(.getScreenWidth viewport) (.getScreenHeight viewport)
|
|
(first raw-pos) (last raw-pos)))
|
|
hovering-ego (hovering-ego entities last-pos)
|
|
current (get-in entities [:cursor :current])]
|
|
|
|
(cond
|
|
out-of-bounds
|
|
(update-in entities [:cursor] assoc :override nil :active false )
|
|
|
|
|
|
(not (get-in entities [:state :active?]))
|
|
(-> entities
|
|
(assoc-in [:cursor :override] nil)
|
|
(assoc-in [:cursor :active] false))
|
|
|
|
(is-unstoppable-script-running screen entities)
|
|
(-> entities
|
|
(assoc-in [:cursor :override] :hourglass)
|
|
(assoc-in [:cursor :active] false))
|
|
|
|
(get-in entities [:state :hud-active?])
|
|
(-> entities
|
|
(assoc-in [:cursor :override] nil)
|
|
(assoc-in [:cursor :active] false))
|
|
|
|
(and hovering-ego (not= :main current))
|
|
(-> entities
|
|
(assoc-in [:cursor :override] (or (:cursor mouse-override) (when (#{:main :active-main} (get-in entities [:cursor :last])) :active-main)))
|
|
(assoc-in [:cursor :active] true))
|
|
|
|
mouse-override
|
|
(-> entities
|
|
(assoc-in [:cursor :override] (or (:cursor mouse-override) (when (#{:main :active-main} (get-in entities [:cursor :last])) :active-main)))
|
|
(assoc-in [:cursor :active] true))
|
|
|
|
|
|
|
|
:else
|
|
(-> entities
|
|
(assoc-in [:cursor :override] nil)
|
|
(assoc-in [:cursor :active] false)))))
|
|
|
|
(def default-night-merge {:r 0.08 :g 0.1 :b 0.36 :multiply-amount 1.0 :hue-amount 1.0})
|
|
(def default-night-merge-sprite {:r 0.08 :g 0.1 :b 0.36 :multiply-amount 0.3 :hue-amount 0.4})
|
|
|
|
(defn clamp-volume [vol]
|
|
(max vol 0.005))
|
|
|
|
(defn proximity-volume [entities [x y] & {:keys [scale]}]
|
|
(-> (max 0.0
|
|
(- 1.0 (/ (dist x y
|
|
(get-in entities [:room :entities :ego :x])
|
|
(get-in entities [:room :entities :ego :y])
|
|
:y-sign 2.0
|
|
:x-sign (/ 1.0 (get-in entities [:room :entities :ego :scale-x])))
|
|
175.0)))
|
|
(* (or scale 1.0))
|
|
(clamp-volume)))
|
|
|
|
(defn fast-forward-particle [p]
|
|
(particle-effect! p :reset)
|
|
(particle-effect! p :start)
|
|
|
|
(particle-effect! p :update 10.0)
|
|
(dotimes [_ 160]
|
|
(particle-effect! p :update 0.2)))
|
|
|
|
|
|
(defn set-fullscreen! [val]
|
|
(try
|
|
(if val
|
|
(on-gl (.setDisplayMode Gdx/graphics
|
|
(doto (.width (.getDesktopDisplayMode Gdx/graphics)) println)
|
|
(doto (.height (.getDesktopDisplayMode Gdx/graphics)) println)
|
|
true)
|
|
(set! (.foregroundFPS (-> (class Gdx/graphics)
|
|
(.getDeclaredField (name "config"))
|
|
(doto (.setAccessible true))
|
|
(.get Gdx/graphics))) 60))
|
|
(on-gl (.setDisplayMode Gdx/graphics 1280 960 false)))
|
|
(swap! settings assoc :fullscreen val)
|
|
(save-settings!)
|
|
nil
|
|
(catch Exception e
|
|
(println e))))
|
|
|
|
(defn toggle-fullscreen! []
|
|
(if (.isFullscreen Gdx/graphics)
|
|
(set-fullscreen! false)
|
|
(set-fullscreen! true)))
|
|
|
|
(defn get-sound-pan [x]
|
|
(/ (- x 160 ) 160))
|
|
|
|
(defn sourced-volume-fn [target vol-scale [x y]]
|
|
(fn [entities]
|
|
(if (= target :ego)
|
|
(-> (* (/ (get-in entities [:room :entities :ego :scale-x]) 1.5) 0.9)
|
|
(* (or vol-scale 1.0))
|
|
(clamp-volume))
|
|
(proximity-volume entities [x y] :scale vol-scale))
|
|
))
|
|
|
|
(defn scale-vol-from-fade [entities vol]
|
|
(* vol
|
|
(- 1.0 (get-in entities [:fade :opacity]))))
|
|
|
|
(defn play-sound!
|
|
([screen entities snd volume-fn]
|
|
(play-sound! screen entities snd volume-fn 0.5))
|
|
|
|
([screen entities snd volume-fn pan]
|
|
(play-sound! screen entities snd volume-fn pan :once))
|
|
|
|
([screen entities snd volume-fn pan type]
|
|
(play-sound! screen entities snd volume-fn pan type 1.0))
|
|
|
|
([screen entities snd volume-fn pan type pitch]
|
|
|
|
(let [vol (volume-fn entities)
|
|
snd (if (keyword? snd)
|
|
(or (-> entities :room :sounds snd)
|
|
(-> entities :sounds snd))
|
|
snd)
|
|
sound-id (if (= :once type)
|
|
(sound! snd :play (scale-vol-from-fade entities (current-sound-volume vol)) (or pitch 1.0) pan )
|
|
(sound! snd :loop 0.0 (or pitch 1.0) pan )) ]
|
|
(update-in entities [:current-sounds :value]
|
|
conj {:id sound-id
|
|
:sound snd
|
|
:volume-fn volume-fn
|
|
:type type
|
|
:ends-at (if (= type :once)
|
|
(+ (:total-time screen) (sound! snd :duration))
|
|
nil)}))))
|
|
|
|
(defn stop-all-sounds! [entities]
|
|
(doseq [snd (get-in entities [:current-sounds :value])]
|
|
(sound! (:sound snd) :stop (:id snd))))
|