Files
gitea-docker/desktop/src-common/advent/actions.clj

584 lines
26 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]
(if (map? entities)
(seq (set/intersection (set (get-in entities [:state :inventory])) (set 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
([entity anim]
(start-animation {:total-time 0} entity anim))
([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 anim override-dir stop?]}]
(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 (or override-dir (cond (< delta-x 0) :left
(> delta-x 0) :right
:else (:facing %))))
(or anim :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]
(if (or (nil? stop?) stop?)
(stop screen entities target-id :face face)
entities))
(can-skip? [this screen entities]
false))))
(defn play-animation [entities target-id anim & {:keys [stop? continue?]}]
(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 continue?
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 stop-walking [entities target-id & {:keys [face]}]
(run-action entities
(begin [this screen entities]
(stop screen entities target-id :face face))
(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 force-dir]}]
(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 force-dir force-dir
(< 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? anim]}]
(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])
width (or (get-in entities [:room :entities target-id :width])
(.getRegionWidth (get-in entities [:room :entities target-id :object])))
origin-x (get-in entities [:room :entities target-id :origin-x])
target-x (if (nil? origin-x) (+ target-x (/ width 2)) target-x )
height (or (get-in entities [:room :entities target-id :height])
(.getRegionHeight (get-in entities [:room :entities target-id :object])))
scaled (get-in entities [:room :entities target-id :scaled])
scale-fn (get-in entities [:room :scale-fn])
scale (get-in entities [:room :entities target-id :scale-y] 1)
height (* scale height)]
(screen! dialogue/talking-screen :on-talk :text text
:x target-x :y (+ (get-in entities [:room :entities target-id :y]) height)
:color (get-in entities [:room :entities target-id :talk-color])
:target-id target-id
:scale scale)
(if animate?
(update-in entities [:room :entities target-id ] #(start-animation screen % (or anim :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
(assoc-in [:cursor :override] nil)
(assoc-in [:cursor :current] :main)))
(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-entities [entities f]
(run-action entities
(begin [this screen entities]
(f entities))
(continue [this screen entities] entities)
(done? [this screen entities] true)
(terminate [this screen entities]
entities)
(can-skip? [this screen entities]
false)))
(defn begin-animation [entities target-id anim]
(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] true)
(terminate [this screen entities]
entities)
(can-skip? [this screen entities]
false)))
(defn update-state [entities f]
(update-entities entities #(update-in % [:state] f)))
(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 get-music [music time]
(if (keyword? music)
music
(time music)))
(defn transition-music [entities new-music]
(let [current-volume (atom 1.0)]
(run-action entities
(begin [this screen entities]
(assoc-in entities [:tweens :fade-out-music] (utils/tween :fade-out-music screen [:volume :value] 1.0 0.0 2.0)))
(continue [this screen entities]
entities)
(done? [this screen entities]
(nil? (get-in entities [:tweens :fade-out-music])))
(terminate [this screen entities]
(music! (utils/get-current-music entities) :stop)
(let [entities (-> entities
(assoc-in [:music-override :value] new-music)
(assoc-in [:volume :value] 1.0))]
(music! (utils/get-current-music entities) :set-volume 1.0)
(music! (utils/get-current-music entities) :play)
entities))
(can-skip? [this screen entities]
false))))
(defn transition-background [entities new-background [x y]]
(let [old-music (get-music (get-in @entities [:room :music]) (get-in @entities [:state :time]))
new-music (get-music (get-in @entities [:rooms new-background :music]) (get-in @entities [:state :time]))
music-changed? (not= old-music new-music)]
(run-action entities
(begin [this screen entities]
(doseq [[k] (get-in entities [:room :timers])]
(remove-timer! screen k))
(as-> entities e
(assoc-in e [:tweens :fade-out] (utils/tween :fade-out screen [:fade :opacity] 0.0 1.0 0.5))
(if music-changed?
(assoc-in e [:tweens :fade-out-music] (utils/tween :fade-out-music screen [:volume :value] 1.0 0.0 0.5))
e)
(assoc-in e [:cursor :current] :main)))
(continue [this screen entities]
(when music-changed?
(music! (get-in entities [:musics old-music]) :set-volume (max (- 1.0 (get-in entities [:fade :opacity])) 0.0)))
entities)
(done? [this screen entities]
(>= (get-in entities [:fade :opacity]) 1.0))
(terminate [this screen entities]
(if-let [next-time (get-in entities [:state :next-time])]
(-> entities
(assoc-in [:state :time] next-time)
(assoc-in [:state :next-time] nil))
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-music (get-in entities [:room :music]) (get-in entities [:state :time]))
entities (as-> entities e
(assoc-in e [:room] (get-in entities [:rooms new-background]))
(assoc-in e [:room :entities :ego] ego)
(assoc-in e [:state :last-room] new-background)
(assoc-in e [:tweens :fade-in] (utils/tween :fade-in screen [:fade :opacity] 1.0 0.0 0.5))
(if music-changed?
(assoc-in e [:tweens :fade-in-music] (utils/tween :fade-in-music screen [:volume :value] 0.0 1.0 0.5))
e))
new-music (get-music (get-in entities [:room :music]) (get-in entities [:state :time]))
apply-state (get-in entities [:room :apply-state])
entities (if apply-state
(apply-state entities)
entities)
entities (utils/update-override 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]
entities)
(done? [this screen entities]
(<= (get-in entities [:fade :opacity]) 0.0))
(terminate [this screen entities]
(doseq [[k [start time fn]] (get-in entities [:room :timers])]
(add-timer! screen k start time))
entities)
(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))