<commit_before>(ns voke.system.movement
  (:require [clojure.set :refer [intersection difference]]
            [voke.events :refer [publish-event]]
            [voke.schemas :refer [Direction Entity GameState System]])
  (:require-macros [schema.core :as sm]))

; A dumb-as-rocks velocity/acceleration system.
; Based on http://www.randygaul.net/2012/02/22/basic-2d-vector-physics-acceleration-orientation-and-friction/ .
;
; If I were a better man, I would have implemented http://buildnewgames.com/gamephysics/
; or http://blog.wolfire.com/2009/07/linear-algebra-for-game-developers-part-2/
;
; This system was easy to implement and I don't need all the bells/whistles in the linear-algebra-based
; articles, though, so I don't feel *too* bad.

(def direction-value-mappings {:down  (/ Math/PI 2)
                               :up    (- (/ Math/PI 2))
                               :left  Math/PI
                               :right 0})

(def friction-value 0.80)
(def min-velocity 0.05)

(sm/defn remove-conflicting-directions :- [Direction]
  "Takes a seq of directions like #{:up :down :left} and returns a seq of directions
  where the pairs of conflicting directions - up+down, left+right - are stripped out if they're present."
  [directions :- [Direction]]
  (reduce (fn [directions conflicting-pair]
            (if (= (intersection directions conflicting-pair)
                   conflicting-pair)
              (difference directions conflicting-pair)
              directions))
          directions
          [#{:up :down} #{:left :right}]))

(sm/defn human-controlled-entity-movement-directions
  [entity :- Entity]
  (remove-conflicting-directions (get-in entity [:brain :intended-move-direction])))

(sm/defn update-orientation :- Entity
  [entity :- Entity]
  ; TODO - currently only implemented for player-controlled entities, doesn't handle monsters/projectiles
  (assoc-in entity
            [:shape :orientation]
            (let [directions (human-controlled-entity-movement-directions entity)
                  intended-direction-values (map direction-value-mappings directions)]
              (Math/atan2 (apply + (map Math/sin intended-direction-values))
                          (apply + (map Math/cos intended-direction-values))))))

(sm/defn update-velocity :- Entity
  [entity :- Entity]
  (let [acceleration (if (not-empty (human-controlled-entity-movement-directions entity))
                       (get-in entity [:motion :max-acceleration])
                       0)]
    (if (or (> acceleration 0)
            (> (or (get-in entity [:motion :velocity :x] 0)
                   (get-in entity [:motion :velocity :y] 0))))
      (let [orientation (get-in entity [:shape :orientation])
            update-axis-velocity (fn [trig-fn axis-velocity]
                                   (let [new-velocity (min (get-in entity [:motion :max-speed])
                                                        (* (+ axis-velocity
                                                              (* acceleration
                                                                 (trig-fn orientation)))
                                                           friction-value))]
                                     (if (> (Math/abs new-velocity) min-velocity)
                                       new-velocity
                                       0)))]
        (-> entity
            (update-in [:motion :velocity :x]
                       (partial update-axis-velocity Math/cos))
            (update-in [:motion :velocity :y]
                       (partial update-axis-velocity Math/sin))))
      entity)))

(sm/defn update-position :- Entity
  [entity :- Entity]
  (-> entity
      (update-in [:shape :x]
                 (fn [x]
                   (+ x (get-in entity [:motion :velocity :x]))))
      (update-in [:shape :y]
                 (fn [y]
                   (+ y (get-in entity [:motion :velocity :y]))))))

;; System definition

(sm/def move-system :- System
  {:every-tick {:fn (fn move-system-tick [entities publish-chan]
                      (doseq [entity (filter #(get-in % [:brain :intended-move-direction]) entities)]
                        (let [moved-entity (-> entity
                                               update-orientation
                                               update-velocity
                                               update-position)]

                          (doseq [axis [:x :y]]
                            (publish-event publish-chan {:event-type   :intended-movement
                                                         :entity       entity<commit_msg>break friction out into its own function<commit_after>(ns voke.system.movement
  (:require [clojure.set :refer [intersection difference]]
            [voke.events :refer [publish-event]]
            [voke.schemas :refer [Direction Entity GameState System]])
  (:require-macros [schema.core :as sm]))

; A dumb-as-rocks velocity/acceleration system.
; Based on http://www.randygaul.net/2012/02/22/basic-2d-vector-physics-acceleration-orientation-and-friction/ .
;
; If I were a better man, I would have implemented http://buildnewgames.com/gamephysics/
; or http://blog.wolfire.com/2009/07/linear-algebra-for-game-developers-part-2/
;
; This system was easy to implement and I don't need all the bells/whistles in the linear-algebra-based
; articles, though, so I don't feel *too* bad.

(def direction-value-mappings {:down  (/ Math/PI 2)
                               :up    (- (/ Math/PI 2))
                               :left  Math/PI
                               :right 0})

(def friction-value 0.80)
(def min-velocity 0.05)

(sm/defn remove-conflicting-directions :- [Direction]
  "Takes a seq of directions like #{:up :down :left} and returns a seq of directions
  where the pairs of conflicting directions - up+down, left+right - are stripped out if they're present."
  [directions :- [Direction]]
  (reduce (fn [directions conflicting-pair]
            (if (= (intersection directions conflicting-pair)
                   conflicting-pair)
              (difference directions conflicting-pair)
              directions))
          directions
          [#{:up :down} #{:left :right}]))

(sm/defn human-controlled-entity-movement-directions
  [entity :- Entity]
  (remove-conflicting-directions (get-in entity [:brain :intended-move-direction])))

(sm/defn update-orientation :- Entity
  [entity :- Entity]
  ; TODO - currently only implemented for player-controlled entities, doesn't handle monsters/projectiles
  (assoc-in entity
            [:shape :orientation]
            (let [directions (human-controlled-entity-movement-directions entity)
                  intended-direction-values (map direction-value-mappings directions)]
              (Math/atan2 (apply + (map Math/sin intended-direction-values))
                          (apply + (map Math/cos intended-direction-values))))))

(sm/defn update-velocity :- Entity
  [entity :- Entity]
  ; TODO - acceleration will be computed differently for AI-controlled monsters
  (let [acceleration (if (not-empty (human-controlled-entity-movement-directions entity))
                       (get-in entity [:motion :max-acceleration])
                       0)]
    (if (or (> acceleration 0)
            (> (or (get-in entity [:motion :velocity :x] 0)
                   (get-in entity [:motion :velocity :y] 0))))
      (let [orientation (get-in entity [:shape :orientation])
            update-axis-velocity (fn [trig-fn axis-velocity]
                                   (let [new-velocity (min (get-in entity [:motion :max-speed])
                                                           (+ axis-velocity
                                                              (* acceleration
                                                                 (trig-fn orientation))))]
                                     (if (> (Math/abs new-velocity) min-velocity)
                                       new-velocity
                                       0)))]
        (-> entity
            (update-in [:motion :velocity :x]
                       (partial update-axis-velocity Math/cos))
            (update-in [:motion :velocity :y]
                       (partial update-axis-velocity Math/sin))))
      entity)))

(sm/defn apply-friction :- Entity
  [entity :- Entity]
  (reduce
    (fn [entity axis]
      (update-in entity [:motion :velocity axis] #(* % friction-value)))
    entity
    [:x :y]))

(sm/defn update-position :- Entity
  [entity :- Entity]
  (-> entity
      (update-in [:shape :x]
                 (fn [x]
                   (+ x (get-in entity [:motion :velocity :x]))))
      (update-in [:shape :y]
                 (fn [y]
                   (+ y (get-in entity [:motion :velocity :y]))))))

;; System definition

(sm/def move-system :- System
  {:every-tick {:fn (fn move-system-tick [entities publish-chan]
                      (doseq [entity (filter #(get-in % [:brain :intended-move-direction]) entities)]
                        (let [moved-entity (-> entity
                                               update-orientation
                                               update-velocity
                                               apply-friction
                                               update-position)]

                          (doseq [axis [:x :y]]
                            (publish-event publish-chan {:event-type   :intended-movement
                                                         :entity       entity
{'fraction_non_alphanumeric': 0.11655864520175623, 'fraction_numerical': 0.006481287894626803, 'mean_word_length': 1.5766226770805278, 'pattern_counts': {'":': 0, '<': 3, '<?xml version=': 0, '>': 15, 'https://': 0, 'lorem ipsum': 0, 'www.': 2, 'xml': 0}, 'pii_count': 0, 'substrings_counts': 0, 'word_list_counts': {'cursed_substrings.json': 46, 'profanity_word_list.json': 0, 'sexual_word_list.json': 0, 'zh_pornsignals.json': 0}}
{'dir': 'git-commits-cleaned', 'id': '5912157', 'n_tokens_mistral': 2647, 'n_tokens_neox': 2407, 'n_words': 668}