;;-*-Lisp-*-
(in-package goal)


#|

  Processes for the damage numbers for enemy damage and health bar for enemy health.
	Enemies spawn the numbers on hit, and send a message to a health bar manager to spawn a health bar
	with the appropriate settings.

	We do it like this instead of adding code to draw health bars to enemy itself to avoid having to
	add more fields.

 |#


;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;;;; damage number and health bars
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;

(deftype damage-number (process)
  ((trans         vector    :inline)
   (trans-2d      vector4w  :inline)
   (on-screen?    symbol)
   (damage        int32)
   (parent-mask   process-mask)
   (font          font-context)
   (y-ofs         float)
   )
  (:state-methods
    active
    )
  (:methods
    (initialize-font (_type_) font-context))
  )

(defstate active (damage-number)
  :virtual #t

  :trans (behavior ()

    (when (not (logtest? (-> *kernel-context* prevent-from-run) (-> self parent-mask)))
      ;; make font float upwards
      (+! (-> self y-ofs) (* 1.0 (-> self clock time-adjust-ratio)))
      )

    ;; convert 3D -> 2D screen now (result in trans-2d)
    (set! (-> self on-screen?) (transform-point-qword! (-> self trans-2d) (-> self trans)))

    ;; position font
    (set-origin! (-> self font) (+ (/ (-> self trans-2d x) 16) -1792)
                                (+ (/ (-> self trans-2d y) 16) -1855))
    (set-depth! (-> self font) (/ (-> self trans-2d z) 16))
    ;; now center (we could do this earlier but it's fine)
    (-! (-> self font origin x) (/ (-> self font width) 2))
    ;; now move it up
    (-! (-> self font origin y) (-> self y-ofs)))

  :code (behavior ()
    (let ((state-time (current-time)))
        ;; move up for a bit
        (until (>= (- (current-time) state-time) (seconds 1))
          (suspend))
        ;; now fade out
        (set! state-time (current-time))
        (until (>= (- (current-time) state-time) (seconds 0.5))
          (set! (-> self font alpha) (lerp-scale 1.0 0.0 (the float (- (current-time) state-time)) 0.0 (fsec 0.5)))
          (suspend))
        )
    )

  :post (behavior ()
    (when (and (-> self on-screen?) ;; text is onscreen
               (pc-cheats? (-> *pc-settings* cheats) health-bars) ;; cheat enabled
               )
      (print-game-text (string-format "~D" (-> self damage)) (-> self font) #f 44 (bucket-id debug-no-zbuf1)))
    )
  )

(defmethod relocate ((this damage-number) (diff int))
  (if (nonzero? (-> this font))
      (&+! (-> this font) diff)
      )
  (call-parent-method this diff)
  )

(defmethod initialize-font ((self damage-number))
  "create the font that will be used used."

  (if (zero? (-> self font))
      (set! (-> self font) (new 'process 'font-context *font-default-matrix* 0 0 0.0 (font-color default) (font-flags shadow kerning))))

  (set-scale! (-> self font) (lerp-scale 0.45 0.8 (the float (-> self damage)) 1.0 15.0))
  (set-flags! (-> self font) (font-flags shadow kerning large middle))
  (set-width! (-> self font) 100)
  (set-height! (-> self font) 50)
  )


(defbehavior damage-number-init-by-other damage-number ((dmg int) (pos vector) (parent-mask process-mask))
  "initialize a damage-number"

  ;; copy params
  (set! (-> self damage) dmg)
  (set! (-> self parent-mask) parent-mask)
  (vector-copy! (-> self trans) pos)

  ;; init font
  (initialize-font self)

  ;; process settings to still render while paused
  (process-mask-clear! (-> self mask) pause)

  (go-virtual active)
  )



;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;;; METALKOR VARIATION
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;

(deftype damage-number-metalkor (damage-number)
  ((damage-metalkor  float)
   )
  )

(defstate active (damage-number-metalkor)
  :virtual #t

  :post (behavior ()
    (if *debug-segment*
        (add-debug-x #t (bucket-id debug-no-zbuf2) (-> self trans) *color-cyan*))
    (when (and (-> self on-screen?) ;; text is onscreen
               (pc-cheats? (-> *pc-settings* cheats) health-bars) ;; cheat enabled
               )
      (let ((dmg-display *temp-string*))
      (set! dmg-display
            (cond
              ((< (abs (- (* (-> self damage-metalkor) (/ 1000.0 4.0)) (the float (/ (-> self damage) 10)))) 0.001)
                (string-format "~D" (/ (-> self damage) 10)))
              ((< (abs (- (* (-> self damage-metalkor) (/ 10000.0 4.0)) (the float (/ (-> self damage) 1)))) 0.001)
                (string-format "~,,1f" (/ (the float (-> self damage)) 10)))
              (else
                (string-format "~,,2f" (/ (* (-> self damage-metalkor) 2500) 10)))
              ))
      (if #f
          (format dmg-display " (~D/~,,4f)" (-> self damage) (-> self damage-metalkor)))
      (print-game-text dmg-display (-> self font) #f 44 (bucket-id debug-no-zbuf1))
      ;(print-game-text (string-format "~D (~,,4f)" (-> self damage) (-> self damage-metalkor)) (-> self font) #f 44 (bucket-id debug-no-zbuf1))
      ))
    )
  )


(defbehavior damage-number-metalkor-init-by-other damage-number-metalkor ((dmg float) (pos vector) (parent-mask process-mask))
  "initialize a damage-number-metalkor"

  ;; copy params
  (set! (-> self damage-metalkor) dmg)
  (set! (-> self damage) (/ (the int (* (-> self damage-metalkor) 10000)) 4))
  (set! (-> self parent-mask) parent-mask)
  (vector-copy! (-> self trans) pos)

  ;; init font
  (initialize-font self)
  (set-scale! (-> self font) (lerp-scale 0.45 0.8 (/ (the float (-> self damage)) 10) 1.0 15.0))

  ;; process settings to still render while paused
  (process-mask-clear! (-> self mask) pause)

  (go-virtual active)
  )



(defun draw-health-bar ((x int) (y int) (width int) (height int) (hit-points float) (hit-points-max float) (bucket bucket-id))
  "Draw a rectangular health bar at the given location. color fades from green to yellow to red."

  (let* ((color (static-rgba 0 0 0 #x80))
        (bar-w (the int (* (/ hit-points hit-points-max) (max 0 (+ width -4)))))
        (x2 (+ x width))
        (bx2 (+ x 2 bar-w))
        (color-fade (if (<= hit-points-max 1.0)
                        (/ hit-points hit-points-max)
                        (/ (1- hit-points) (1- hit-points-max)))))
    (cond
      ((>= color-fade 0.5)
        ;; fade from green -> yellow
        (set! (-> color r) (the int (lerp-scale (the float (-> *color-green* r)) (the float (-> *color-yellow* r)) color-fade 1.0 0.5)))
        (set! (-> color g) (the int (lerp-scale (the float (-> *color-green* g)) (the float (-> *color-yellow* g)) color-fade 1.0 0.5)))
        (set! (-> color b) (the int (lerp-scale (the float (-> *color-green* b)) (the float (-> *color-yellow* b)) color-fade 1.0 0.5)))
        )
      (else
        ;; fade from yellow -> red
        (set! (-> color r) (the int (lerp-scale (the float (-> *color-yellow* r)) (the float (-> *color-red* r)) color-fade 0.5 0.0)))
        (set! (-> color g) (the int (lerp-scale (the float (-> *color-yellow* g)) (the float (-> *color-red* g)) color-fade 0.5 0.0)))
        (set! (-> color b) (the int (lerp-scale (the float (-> *color-yellow* b)) (the float (-> *color-red* b)) color-fade 0.5 0.0)))
        )
      )
    (with-dma-buffer-add-bucket ((buf (-> *display* frames (-> *display* on-screen) global-buf))
                                  bucket)
      (cond
        ((< x -2)
          (+! width x)
          (+! bar-w (+ x 2)))
        ((< x 0)
          (+! width x))
        ((>= x2 512)
          (-! width (- x2 512)))
        )
      (cond
        ((>= bx2 512)
          (-! bar-w (- bx2 512)))
        )
      (cond
        ((< y -2)
          (+! height y))
        ((< y 0)
          (+! height y))
        )
      (draw-sprite2d-xy buf x y (max 0 width) (max 0 height) (new 'static 'rgba :a #x40))
      (draw-sprite2d-xy buf (+ x 2) (+ y 2) (max 0 bar-w) (max 0 (+ height -4)) color)
      )
    )
  #f
  )


(deftype health-bar (process)
  ((ppointer (pointer health-bar) :override)

   (trans-2d        vector4w  :inline)
   (width           float)
   (height          float)
   (on-screen?      symbol)
   (font            font-context)
   (hit-points-max  float)
   (hit-points      float)

   ;; who this bar is for
   (owner     handle)

   ;; linked list fields
   (prev-bar  (pointer health-bar))
   (next-bar  (pointer health-bar))
   )
  (:state-methods
    idle
    )
  )

(defmethod relocate health-bar ((this health-bar) (diff int))
  (if (nonzero? (-> this font))
      (&+! (-> this font) diff)
      )
  (call-parent-method this diff)
  )


(deftype health-bar-manager (basic)
  ((head    (pointer health-bar))
   (tail    (pointer health-bar))
   )
  (:methods
    (new (symbol type) _type_)
    (append-to-list (_type_ (pointer health-bar)) (pointer health-bar))
    (remove-from-list (_type_ (pointer health-bar)) (pointer health-bar))
    (get-bar-for-process (_type_ process-drawable) (pointer health-bar))
    (send-damage-to-bar (_type_ process-drawable int int) (pointer health-bar))
    (set-proc-bar-hit-points (_type_ process-drawable int) (pointer health-bar))
    )
  )

(defmethod new health-bar-manager ((allocation symbol) (type-to-make type))

  (let ((this (object-new allocation type-to-make (the-as int (-> type-to-make size)))))
    (set! (-> this head) #f)
    (set! (-> this tail) #f)
    this)
  )


(defmethod append-to-list ((this health-bar-manager) (bar-ptr (pointer health-bar)))
  "add a health bar to the end of the linked list"
  (set! (-> bar-ptr 0 prev-bar) (-> this tail))
  (cond
    ((-> this tail)
      ;; list has stuff
      (set! (-> this tail 0 next-bar) bar-ptr)
      )
    (else
      ;; no tail means list is empty. fill head.
      (set! (-> this head) bar-ptr)
      )
    )
  (set! (-> this tail) bar-ptr)

  bar-ptr)

(defmethod remove-from-list ((this health-bar-manager) (bar-ptr (pointer health-bar)))
  "remove a health bar from the linked list. call this when deactivating it"

  (if (= (-> this head) bar-ptr)
      (set! (-> this head) (-> bar-ptr 0 next-bar)))
  (if (= (-> this tail) bar-ptr)
      (set! (-> this tail) (-> bar-ptr 0 prev-bar)))
  (if (-> bar-ptr 0 next-bar)
      (set! (-> bar-ptr 0 next-bar 0 prev-bar) (-> bar-ptr 0 prev-bar)))
  (if (-> bar-ptr 0 prev-bar)
      (set! (-> bar-ptr 0 prev-bar 0 next-bar) (-> bar-ptr 0 next-bar)))

  bar-ptr)


(defmethod get-bar-for-process ((this health-bar-manager) (proc process-drawable))
  "return the health bar associated with that process. #f = not found"

  (let ((cur-bar (-> this head)))
    (while cur-bar
      (if (= (handle->process (-> cur-bar 0 owner)) proc)
          (return cur-bar))
      (set! cur-bar (-> cur-bar 0 next-bar))
      )
    )
  (the (pointer health-bar) #f))

(define *health-bar-manager* (new 'global 'health-bar-manager))


(defstate idle (health-bar)
  :virtual #t

  :event (behavior ((from process) (argc int) (msg symbol) (block event-message-block))
    (case msg
      (('damage)
       (-! (-> self hit-points) (the float (the-as int (-> block param 0))))
       #t)
      (('set-hit-points)
       (set! (-> self hit-points) (the float (the-as int (-> block param 0))))
       #t)
      )
    )

  :trans (behavior ()

    ;; convert 3D -> 2D screen now (result in trans-2d)
    (when (handle->process (-> self owner))
      (set! (-> self on-screen?) (transform-point-qword! (-> self trans-2d) (-> (the process-drawable (handle->process (-> self owner))) root trans)))
      )

    ;; set store width and height.
    (set! (-> self width) (* (-> self font width) (-> *video-params* relative-x-scale)))
    (set! (-> self height) (-> self font height))

    ;; position font
    (set-origin! (-> self font) (+ (/ (-> self trans-2d x) 16) -1792)
                                (+ (/ (-> self trans-2d y) 16) -1855 (the int (-> self height))))
    (set-depth! (-> self font) (/ (-> self trans-2d z) 16))
    ;; now center (we could do this earlier but it's fine)
    (-! (-> self font origin x) (/ (-> self width) 2)))

  :code (behavior ()
    (while (and (handle->process (-> self owner)) (not (case (-> (handle->process (-> self owner)) next-state name) (('inactive 'hidden) #t))))
      (suspend))
    )

  :post (behavior ()
    (when (and (-> self on-screen?) ;; text is onscreen
               (pc-cheats? (-> *pc-settings* cheats) health-bars) ;; cheat enabled
               (> (meters 160) (vector-vector-distance (-> (the process-drawable (handle->process (-> self owner))) root trans) (math-camera-pos))))
      (draw-health-bar (the int (-> self font origin x)) (the int (-> self font origin y))
                       (the int (-> self width)) (the int (-> self height))
                       (-> self hit-points) (-> self hit-points-max)
                       (bucket-id debug-no-zbuf1))
      ;(print-game-text (string-format "~,,0f/~,,0f" (-> self hit-points) (-> self hit-points-max)) (-> self font) #f 44 (bucket-id debug-no-zbuf1))
      )
    )
  )


(defmethod deactivate ((self health-bar))

  (remove-from-list *health-bar-manager* (process->ppointer self))

  ((method-of-type process deactivate) self)
  (none)
  )


(defbehavior health-bar-init-by-other health-bar ((hit-points int) (hit-points-max int) (owner handle))
  "initialize a health-bar"

  ;; copy params
  (set! (-> self hit-points-max) (the float hit-points-max))
  (set! (-> self hit-points) (the float hit-points))

  ;; set owner
  (set! (-> self owner) owner)

  ;; linked list fields
  (set! (-> self prev-bar) #f)
  (set! (-> self next-bar) #f)
  (append-to-list *health-bar-manager* (the (pointer health-bar) (process->ppointer self)))

  ;; init font
  (set! (-> self font) (new 'process 'font-context *font-default-matrix* 0 0 0.0 (font-color default) (font-flags shadow kerning)))
  (set-scale! (-> self font) 0.35)
  (set-flags! (-> self font) (font-flags shadow kerning large middle middle-vert))
  (set-width! (-> self font) 34)
  (set-height! (-> self font) 10)

  ;; process settings to still render while paused
  (process-mask-clear! (-> self mask) pause)

  ;; ready to handle events immediately
  (set! (-> self event-hook) (-> (method-of-object self idle) event))

  (go-virtual idle)
  )



(defmethod send-damage-to-bar ((this health-bar-manager) (from process-drawable) (damage int) (hit-points-old int))
  "send damage and hit point information of an enemy to its health bar. creates one if needed."
  (let ((the-bar (get-bar-for-process this from)))
      (cond
        (the-bar
          (send-event (ppointer->process the-bar) 'damage damage)
          )
        (else
          (set! the-bar (process-spawn health-bar (- hit-points-old damage) hit-points-old (process->handle from)))
          )
        )
      the-bar)
  )

(defmethod set-proc-bar-hit-points ((this health-bar-manager) (proc process-drawable) (hit-points int))
  "simply set the hit points (not max hit points) of a process's health bar"

  (let ((the-bar (get-bar-for-process this proc)))
    (send-event (ppointer->process the-bar) 'set-hit-points hit-points)
    the-bar)
  )



