520 lines
23 KiB
Clojure
520 lines
23 KiB
Clojure
(ns advent.actions
|
|
(:require [play-clj.core :refer :all]
|
|
[play-clj.ui :refer :all]
|
|
[play-clj.utils :refer :all]
|
|
[play-clj.g2d :refer :all]
|
|
[clojure.pprint]
|
|
[clojure.string :as s]
|
|
[clojure.zip :as zip]
|
|
[clojure.set :as set]
|
|
[advent.pathfind]
|
|
[advent.actions :as actions]
|
|
[advent.screens.dialogue :as dialogue]
|
|
[advent.utils :as utils]
|
|
[clojure.core.async :refer [put! <! <!! >! >!! chan go thread take! alts!!]])
|
|
(:import [com.badlogic.gdx.graphics Pixmap Pixmap$Filter Texture Texture$TextureFilter]
|
|
[com.badlogic.gdx.graphics.g2d TextureRegion Animation]
|
|
[com.badlogic.gdx Screen]))
|
|
|
|
(defprotocol IAction
|
|
(begin [this screen entities])
|
|
(done? [this screen entities])
|
|
(continue [this screen entities])
|
|
(terminate [this screen entities])
|
|
(can-skip? [this screen entities])
|
|
(get-channel [this]))
|
|
|
|
(defn has-item? [entities item]
|
|
(if (map? entities)
|
|
((set (get-in entities [:state :inventory])) item)
|
|
((set (get-in @entities [:state :inventory])) item)))
|
|
|
|
(defn has-obtained? [entities item]
|
|
(if (map? entities)
|
|
((get-in entities [:state :obtained-items]) item)
|
|
((get-in @entities [:state :obtained-items]) item)))
|
|
|
|
(defn has-obtained-one-of? [entities items]
|
|
(some (partial has-obtained? entities) items))
|
|
|
|
(defn has-obtained-all-of? [entities items]
|
|
(every? (partial has-obtained? entities) items))
|
|
|
|
(defn has-one-of? [entities items]
|
|
(seq (set/intersection (set (get-in entities [:state :inventory])) (set items))))
|
|
|
|
(defmacro run-action [entities & forms]
|
|
`(let [c# (chan)]
|
|
(do
|
|
(put! (get-in (deref ~entities) [:actions :channel])
|
|
(reify IAction
|
|
(get-channel [_] c#)
|
|
~@forms))
|
|
(reset! ~entities (<!! c#)))))
|
|
|
|
(defn change-script-state [entities state]
|
|
(run-action entities
|
|
(begin [this screen entities]
|
|
(assoc-in entities [:actions :script-running?] state))
|
|
|
|
(continue [this screen entities] entities)
|
|
|
|
(done? [this screen entities]
|
|
true)
|
|
|
|
(terminate [this screen entities]
|
|
entities)
|
|
(can-skip? [this screen entities]
|
|
false)))
|
|
|
|
(defmacro get-script [entities & forms]
|
|
`(fn [starting-entities#]
|
|
(let [~entities (atom starting-entities#)]
|
|
(thread (do (change-script-state ~entities true)
|
|
~@forms
|
|
(change-script-state ~entities false)
|
|
(utils/save @~entities))))))
|
|
|
|
(defmacro get-unsaved-script [entities & forms]
|
|
`(fn [starting-entities#]
|
|
(let [~entities (atom starting-entities#)]
|
|
(thread (change-script-state ~entities true)
|
|
~@forms
|
|
(change-script-state ~entities false)))))
|
|
|
|
|
|
(defn jump-to [screen entities entity [x y] update-baseline?]
|
|
(let [scale-fn (-> entities :room :scale-fn)
|
|
entity (assoc entity :x x :y y)
|
|
entity (if update-baseline?
|
|
(assoc entity :baseline (- 240 y))
|
|
entity)]
|
|
(if (:scaled entity)
|
|
(assoc entity :scale-x (scale-fn [x y]) :scale-y (scale-fn [x y]))
|
|
entity)))
|
|
|
|
(defn find-animation [entity anim]
|
|
(if (instance? Animation anim)
|
|
anim
|
|
(or (get-in entity [(:facing entity) anim])
|
|
(get entity anim))))
|
|
|
|
(defn start-animation [screen entity anim]
|
|
(let [new-anim (find-animation entity anim)]
|
|
(if (and anim (not= new-anim (:anim entity)))
|
|
(assoc entity
|
|
:anim new-anim
|
|
:anim-start (:total-time screen))
|
|
entity)))
|
|
|
|
(defn stop [screen entities target-id & {:keys [face]}]
|
|
(update-in entities [:room :entities target-id] (comp #(start-animation screen % :stand) (if face #(assoc % :facing face) identity))))
|
|
|
|
(defn walk-straight-to [entities target-id [final-x final-y] & {:keys [update-baseline? face speed]}]
|
|
(let [{start-x :x start-y :y} (get-in @entities [:room :entities target-id])
|
|
final-x (int final-x)
|
|
final-y (int final-y)
|
|
update-baseline? (if (nil? update-baseline?) true update-baseline?)]
|
|
(run-action entities
|
|
(begin [this screen entities]
|
|
entities)
|
|
|
|
(continue [this screen entities]
|
|
(let [{from-x :x from-y :y :keys [left right scale-x] :as target-entity} (get-in entities [:room :entities target-id])]
|
|
(let [delta-x (- final-x from-x)
|
|
delta-y (- final-y from-y)
|
|
distance (utils/dist from-x from-y final-x final-y)
|
|
speed (* (or scale-x 1.0) (or speed 1.5))
|
|
moved-x (if (= 0.0 distance)
|
|
0
|
|
(* speed (/ delta-x distance)))
|
|
moved-y (if (= 0.0 distance)
|
|
0
|
|
(* speed (/ delta-y distance)))]
|
|
(if (< distance speed)
|
|
(-> entities
|
|
(assoc-in [:room :entities target-id :x] final-x)
|
|
(assoc-in [:room :entities target-id :y] final-y))
|
|
(update-in entities [:room :entities target-id]
|
|
#(start-animation screen
|
|
(assoc (jump-to screen entities % [(+ moved-x from-x) (+ moved-y from-y)] update-baseline?)
|
|
:facing (cond (< delta-x 0) :left
|
|
(> delta-x 0) :right
|
|
:else (:facing %)))
|
|
:walk
|
|
))))))
|
|
|
|
(done? [this screen entities]
|
|
(let [{from-x :x from-y :y :keys [left right anim] :as target-entity} (get-in entities [:room :entities target-id])]
|
|
(< (utils/dist final-x final-y from-x from-y) 1)))
|
|
|
|
(terminate [this screen entities]
|
|
(stop screen entities target-id :face face))
|
|
(can-skip? [this screen entities]
|
|
false))))
|
|
|
|
(defn play-animation [entities target-id anim & {:keys [stop?]}]
|
|
(run-action entities
|
|
(begin [this screen entities]
|
|
(update-in entities [:room :entities target-id] #(start-animation screen % anim) ))
|
|
|
|
(continue [this screen entities] entities)
|
|
|
|
(done? [this screen entities]
|
|
(animation! (find-animation (get-in entities [:room :entities target-id ]) anim)
|
|
:is-animation-finished
|
|
(- (:total-time screen) (get-in entities [:room :entities target-id :anim-start]))))
|
|
|
|
(terminate [this screen entities]
|
|
(if (or (nil? stop?) stop?)
|
|
(stop screen entities target-id)
|
|
(assoc-in entities [:room :entities target-id :anim] nil)))
|
|
(can-skip? [this screen entities]
|
|
false)))
|
|
|
|
(defn update-entity [entities target-id f]
|
|
(run-action entities
|
|
(begin [this screen entities]
|
|
(update-in entities [:room :entities target-id] f))
|
|
|
|
(continue [this screen entities] entities)
|
|
|
|
(done? [this screen entities] true)
|
|
|
|
(terminate [this screen entities] entities)
|
|
(can-skip? [this screen entities] false)))
|
|
|
|
(defn walk-to [entities target-id [final-x final-y] & {:keys [can-skip? face]}]
|
|
(let [{start-x :x start-y :y} (get-in @entities [:room :entities target-id])
|
|
final-x (int final-x)
|
|
final-y (int final-y)
|
|
path (vec (take-nth 5 (advent.pathfind/visit-all
|
|
(:collision (:room @entities))
|
|
[(int start-x) (int start-y)]
|
|
[final-x final-y])))
|
|
path (if (seq path)
|
|
(conj path [final-x final-y])
|
|
[])
|
|
targets-left (atom path)]
|
|
(if (seq path)
|
|
(run-action entities
|
|
(begin [this screen entities]
|
|
entities)
|
|
|
|
(continue [this screen entities]
|
|
(let [{from-x :x from-y :y :keys [left right scale-x] :as target-entity} (get-in entities [:room :entities target-id])
|
|
[[target-x target-y] remainder] @targets-left]
|
|
(let [delta-x (- target-x from-x)
|
|
delta-y (- target-y from-y)
|
|
distance (utils/dist from-x from-y target-x target-y)
|
|
speed (* (or scale-x 1.0) 1.5)
|
|
moved-x (if (= 0.0 distance)
|
|
0
|
|
(* speed (/ delta-x distance)))
|
|
moved-y (if (= 0.0 distance)
|
|
0
|
|
(* speed (/ delta-y distance)))]
|
|
(if (< distance speed)
|
|
(do (swap! targets-left rest)
|
|
(-> entities
|
|
(assoc-in [:room :entities target-id :x] target-x)
|
|
(assoc-in [:room :entities target-id :y] target-y)))
|
|
(update-in entities [:room :entities target-id]
|
|
#(start-animation screen
|
|
(assoc (jump-to screen entities % [(+ moved-x from-x) (+ moved-y from-y)] true)
|
|
:facing (cond (< delta-x 0) :left
|
|
(> delta-x 0) :right
|
|
:else (:facing %)))
|
|
:walk
|
|
))))))
|
|
|
|
(done? [this screen entities]
|
|
(let [{from-x :x from-y :y :keys [left right anim] :as target-entity} (get-in entities [:room :entities target-id])]
|
|
(< (utils/dist final-x final-y from-x from-y) 1)))
|
|
|
|
(terminate [this screen entities]
|
|
(stop screen entities target-id :face face))
|
|
(can-skip? [this screen entities]
|
|
(or can-skip? false)))
|
|
@entities)))
|
|
|
|
(defn get-text-duration [text]
|
|
(* (count (s/split text #" ")) 0.5))
|
|
|
|
(defn talk [entities target-id text & {:keys [stop? animate?]}]
|
|
(let [initial-time (atom nil)
|
|
stop? (if (nil? stop?) true stop?)
|
|
animate? (if (nil? animate?) true animate?)]
|
|
(run-action entities
|
|
(begin [this screen entities]
|
|
(let [_ (swap! initial-time #(or % (:total-time screen)))
|
|
target-y (get-in entities [:room :entities target-id :y])
|
|
target-x (get-in entities [:room :entities target-id :x])
|
|
scale-fn (get-in entities [:room :scale-fn])
|
|
scale (scale-fn [target-x target-y])
|
|
height (* scale 36)]
|
|
(screen! dialogue/talking-screen :on-talk :text text
|
|
:x (get-in entities [:room :entities target-id :x]) :y (+ (get-in entities [:room :entities target-id :y]) height)
|
|
:target-id target-id
|
|
:scale scale)
|
|
(if animate?
|
|
(update-in entities [:room :entities target-id ] #(start-animation screen % :talk))
|
|
entities)))
|
|
|
|
(continue [this screen entities] entities)
|
|
|
|
(done? [this screen entities]
|
|
(> (- (:total-time screen)
|
|
@initial-time)
|
|
(get-text-duration text)))
|
|
|
|
(terminate [this screen entities]
|
|
(screen! dialogue/talking-screen :stop-talk :target-id target-id)
|
|
(if stop?
|
|
(stop screen entities target-id)
|
|
entities))
|
|
(can-skip? [this screen entities]
|
|
true))))
|
|
|
|
(defn something-else [zipper]
|
|
(-> zipper zip/up zip/up))
|
|
|
|
(defn previous-choices [zipper]
|
|
(-> zipper zip/up))
|
|
|
|
(defn nth-child [zipper i]
|
|
(loop [so-far 0
|
|
zipper (zip/down zipper)]
|
|
(if (= so-far i)
|
|
zipper
|
|
(recur (inc so-far) (zip/right zipper)))))
|
|
|
|
(defn make-zipper [tree]
|
|
(zip/zipper map? (comp #(map second %) #(filter first (partition 2 %)) :choices) (fn [n c] nil) tree))
|
|
|
|
(defn present-choices [entities choices]
|
|
(loop [zipper (make-zipper choices)]
|
|
(let [selected-index (atom nil)
|
|
node (zip/node zipper)
|
|
dialogue-choices (filter first (partition 2 (:choices node)))]
|
|
(run-action entities
|
|
(begin [this screen entities]
|
|
(screen! dialogue/choice-screen :on-present-choices :choices dialogue-choices :callback #(reset! selected-index %))
|
|
(screen! @(resolve 'advent.screens.scene/scene) :on-deactivate)
|
|
entities)
|
|
|
|
(continue [this screen entities] entities)
|
|
|
|
(done? [this screen entities] (not (nil? @selected-index)))
|
|
|
|
(terminate [this screen entities]
|
|
(screen! @(resolve 'advent.screens.scene/scene) :on-reactivate)
|
|
entities)
|
|
(can-skip? [this screen entities]
|
|
false))
|
|
|
|
(let [zipper (nth-child zipper @selected-index)
|
|
node (zip/node zipper)]
|
|
(when-let [run (:run node)]
|
|
(run (-> dialogue-choices
|
|
(nth @selected-index)
|
|
first)))
|
|
(when-let [next-choices (:choices node)]
|
|
(if (fn? next-choices)
|
|
(recur (next-choices zipper))
|
|
(recur zipper)))))))
|
|
|
|
(defn update-state [entities f]
|
|
(run-action entities
|
|
(begin [this screen entities]
|
|
(update-in entities [:state] f ))
|
|
(continue [this screen entities] entities)
|
|
(done? [this screen entities] true)
|
|
(terminate [this screen entities]
|
|
entities)
|
|
(can-skip? [this screen entities]
|
|
false)))
|
|
|
|
(defn remove-item [entities item]
|
|
(run-action entities
|
|
(begin [this screen entities]
|
|
(-> entities
|
|
(update-in [:state :inventory] #(remove (partial = item) %))
|
|
(assoc-in [:cursor :current] :main)))
|
|
|
|
(continue [this screen entities] entities)
|
|
|
|
(done? [this screen entities] true)
|
|
|
|
(terminate [this screen entities]
|
|
entities)
|
|
(can-skip? [this screen entities]
|
|
false)))
|
|
|
|
(defn play-sound [entities sound-file]
|
|
(let [m (music sound-file)]
|
|
(run-action entities
|
|
(begin [this screen entities]
|
|
(music! m :play)
|
|
entities)
|
|
|
|
(continue [this screen entities] entities)
|
|
|
|
(done? [this screen entities]
|
|
(not (music! m :is-playing)))
|
|
|
|
(terminate [this screen entities]
|
|
entities)
|
|
(can-skip? [this screen entities]
|
|
false))))
|
|
|
|
(defn give [entities item]
|
|
(run-action entities
|
|
(begin [this screen entities]
|
|
(sound! (sound "pickup.mp3") :play)
|
|
|
|
(-> entities
|
|
(update-in [:state :inventory] #(conj % item))
|
|
(update-in [:state :obtained-items] #(conj % item))
|
|
(assoc-in [:cursor :current] ((:all-items entities) item))))
|
|
|
|
(continue [this screen entities] entities)
|
|
|
|
(done? [this screen entities] true)
|
|
|
|
(terminate [this screen entities]
|
|
entities)
|
|
(can-skip? [this screen entities]
|
|
false)))
|
|
|
|
(defn remove-entity [entities entity]
|
|
(run-action entities
|
|
(begin [this screen entities]
|
|
(update-in entities [:room :entities] #(dissoc % entity)))
|
|
|
|
(continue [this screen entities] entities)
|
|
|
|
(done? [this screen entities] true)
|
|
|
|
(terminate [this screen entities] entities)
|
|
|
|
(can-skip? [this screen entities]
|
|
false)))
|
|
|
|
(defn add-entity [entities id entity]
|
|
(run-action entities
|
|
(begin [this screen entities]
|
|
(update-in entities [:room :entities] #(assoc % id entity)))
|
|
|
|
(continue [this screen entities] entities)
|
|
|
|
(done? [this screen entities] true)
|
|
|
|
(terminate [this screen entities] entities)
|
|
|
|
(can-skip? [this screen entities]
|
|
false)))
|
|
|
|
(defn transition-music
|
|
([entities new-music]
|
|
(transition-music entities (get-in @entities [:room :music]) new-music))
|
|
([entities old-music new-music]
|
|
(let [current-volume (atom 1.0)]
|
|
(run-action entities
|
|
(begin [this screen entities]
|
|
entities)
|
|
|
|
(continue [this screen entities]
|
|
(let [new-volume (swap! current-volume #(- % 0.01))]
|
|
(music! (get-in entities [:musics old-music]) :set-volume new-volume))
|
|
entities)
|
|
|
|
(done? [this screen entities]
|
|
(>= 0.1 @current-volume))
|
|
|
|
(terminate [this screen entities]
|
|
(music! (get-in entities [:musics old-music]) :stop)
|
|
(music! (get-in entities [:musics new-music]) :set-volume 1.0)
|
|
(music! (get-in entities [:musics new-music]) :play)
|
|
entities)
|
|
(can-skip? [this screen entities]
|
|
false)))))
|
|
|
|
(defn transition-background [entities new-background [x y]]
|
|
(let [old-music (get-in @entities [:room :music])
|
|
new-music (get-in @entities [:rooms new-background :music])
|
|
music-changed? (not= old-music new-music)]
|
|
(run-action entities
|
|
(begin [this screen entities]
|
|
|
|
(-> entities
|
|
(assoc-in [:transition]
|
|
(assoc (texture "black.png")
|
|
:scale-x 20
|
|
:scale-y 20
|
|
:baseline 9500
|
|
:opacity 0.1))))
|
|
|
|
(continue [this screen entities]
|
|
(when music-changed?
|
|
(music! (get-in entities [:musics old-music]) :set-volume (max (- 1.0 (get-in entities [:transition :opacity])) 0.0)))
|
|
(update-in entities [:transition :opacity] + 0.05))
|
|
|
|
(done? [this screen entities]
|
|
(>= (get-in entities [:transition :opacity]) 1.0))
|
|
|
|
(terminate [this screen entities]
|
|
entities)
|
|
(can-skip? [this screen entities]
|
|
false))
|
|
(run-action entities
|
|
(begin [this screen entities]
|
|
(let [ego (get-in entities [:room :entities :ego])
|
|
old-music (get-in entities [:room :music])
|
|
entities (-> entities
|
|
(assoc-in [:room] (get-in entities [:rooms new-background]))
|
|
(assoc-in [:room :entities :ego] ego)
|
|
(assoc-in [:state :last-room] new-background))
|
|
new-music (get-in entities [:room :music])
|
|
apply-state (get-in entities [:room :apply-state])
|
|
entities (if apply-state
|
|
(apply-state entities)
|
|
entities)]
|
|
(when (not= new-music old-music)
|
|
(doseq [[k v] (:musics entities)
|
|
:when (and v (not= new-music k))]
|
|
(music! v :stop))
|
|
(when new-music
|
|
(music! (get-in entities [:musics new-music]) :set-volume 0)
|
|
(music! (get-in entities [:musics new-music]) :play)))
|
|
(-> entities
|
|
(update-in [:room :entities :ego] #(jump-to screen entities % [x y] true)))))
|
|
|
|
(continue [this screen entities]
|
|
(when music-changed?
|
|
(music! (get-in entities [:musics new-music]) :set-volume (max (- 1.0 (get-in entities [:transition :opacity])) 0.0)))
|
|
(update-in entities [:transition :opacity] - 0.075))
|
|
|
|
(done? [this screen entities]
|
|
(<= (get-in entities [:transition :opacity]) 0.0))
|
|
|
|
(terminate [this screen entities]
|
|
(dissoc entities :transition))
|
|
|
|
(can-skip? [this screen entities]
|
|
false))))
|
|
|
|
(defn do-dialogue [entities & pairs]
|
|
(loop [pairs (partition 2 pairs)]
|
|
(let [[[target line]] pairs
|
|
next-speaker-is-different (not= target (ffirst (next pairs)))
|
|
result (talk entities target line :stop? next-speaker-is-different)]
|
|
|
|
(Thread/sleep 200)
|
|
(if (seq (rest pairs))
|
|
(recur (rest pairs))
|
|
result))))
|
|
|
|
(defn respond [entities line & more]
|
|
(apply do-dialogue entities :ego line more))
|