honeylisp/editor/mapedit.fnl

351 lines
16 KiB
Fennel

(local GraphicsEditView (require :editor.gfxedit))
(local style (require :core.style))
(local util (require :lib.util))
(local lume (require :lib.lume))
(local files (require :game.files))
(local {: show} (util.require :inspector.debug))
(local {: mouse-inside : activate : active? : checkbox : textfield : textbutton : textbox : dropdown : labelled-dropdown : under : right-of : reform : group-wrapper} (util.require :editor.imgui))
(local {: tilestrip-to-sprite} (util.require :editor.tiledraw))
(local {: encode-yx : encode-itile : decode-itile : dimensions} (util.require :game.tiles))
(local actions (require :editor.actions))
(local MapEditView (GraphicsEditView:extend))
(local sprite-scale 3)
(fn platform [?key] (let [p (dimensions)] (if ?key (. p ?key) p)))
(fn MapEditView.layer [self ?ilayer] (or (?. (platform :layers) (or ?ilayer self.ilayer)) {:style :tiles}))
(fn MapEditView.layer-type [self ?ilayer] (. (self:layer ?ilayer) :style))
(fn MapEditView.layer-offset [self ?ilayer] (let [{: x : y} (self:layer ?ilayer)] [(* sprite-scale (or x 0)) (* sprite-scale (or y 0))]))
(fn MapEditView.dimensions [self ?ilayer] (or (platform (self:layer-type ?ilayer)) (platform)))
(fn MapEditView.scaled-dimensions [self ?ilayer]
(let [dim (lume.clone (self:dimensions ?ilayer))]
(each [_ key (ipairs [:tilew :tileh :xstagger :ystagger])]
(when (. dim key) (tset dim key (* sprite-scale (. dim key)))))
dim))
(fn MapEditView.mapw [self ?ilayer] (. (self:dimensions ?ilayer) :mapw))
(fn MapEditView.maph [self ?ilayer] (. (self:dimensions ?ilayer) :maph))
(fn MapEditView.tilew [self ?ilayer] (. (self:scaled-dimensions ?ilayer) :tilew))
(fn MapEditView.tileh [self ?ilayer] (. (self:scaled-dimensions ?ilayer) :tileh))
(fn MapEditView.empty-map [self ?ilayer] (string.rep "\0" (* (self:mapw ?ilayer) (self:maph ?ilayer))))
(fn MapEditView.new [self]
(MapEditView.super.new self)
(set self.sprite-scale sprite-scale)
(set self.stripcache {})
(set self.ilevel 1)
(self:set-ilayer 1)
(self:reload))
; map is stored bottom-to-top
(fn MapEditView.imap-from-xy [self mx my ?ilayer]
(+ mx -1 (* (self:mapw ?ilayer) (- (self:maph ?ilayer) my))))
(fn MapEditView.update-map [self map mx my itile]
(local imap (self:imap-from-xy mx my))
(local enctile (encode-itile itile))
(..
(map:sub 1 imap)
(string.char enctile)
(map:sub (+ imap 2))))
(fn MapEditView.map [self ?ilayer]
(if (platform :layers) (or (?. self.level.layers (or ?ilayer self.ilayer)) (self:empty-map ?ilayer))
self.level.map))
(fn MapEditView.itile-from-xy [self mx my ?ilayer]
(local imap (+ (self:imap-from-xy mx my ?ilayer) 1))
(local enctile (or (string.byte (string.sub (self:map ?ilayer) imap imap)) 0))
(decode-itile enctile))
(fn MapEditView.set-tile [self mx my itile]
(let [updated-map (self:update-map (self:map) mx my itile)]
(if (platform :layers) (util.nested-tset self.level [:layers self.ilayer] updated-map)
(set self.level.map updated-map))))
; todo: objects exist on layers
(fn MapEditView.iobject-from-xy [self mx my ?iobj]
(local iobj (or ?iobj 1))
(local obj (. self.level.objects iobj))
(when obj
(if (and (= obj.x mx) (= obj.y my))
iobj
(self:iobject-from-xy mx my (+ iobj 1)))))
(fn MapEditView.object [self] (. self.level.objects self.iobject))
(fn move-object [objects iobjectsrc iobjectdst]
(each [_ object (pairs objects)]
(when (= object.link iobjectsrc)
(set object.link iobjectdst)))
(tset objects iobjectdst (. objects iobjectsrc))
(tset objects iobjectsrc nil)
(when (. objects (+ iobjectsrc 1))
(move-object objects (+ iobjectsrc 1) iobjectsrc)))
(fn MapEditView.levels [self]
(when (= files.game.levels nil)
(set files.game.levels []))
files.game.levels)
(fn MapEditView.draw-map-selector [self form]
(let [level-count (length (self:levels))
options (icollect [i (util.countiter (+ level-count 1))] (if (<= i level-count) i :New))
ilevel (labelled-dropdown (reform form {:tag :map-selector :wdropdown (* 100 SCALE)}) "Map" self.ilevel options)]
(when (not= ilevel self.ilevel)
(set self.ilevel (if (= ilevel :New) (+ level-count 1) ilevel))
(self:load-level))))
(fn MapEditView.set-ilayer [self ilayer]
(set self.ilayer ilayer)
(self:set-style (self:layer-type)))
(fn MapEditView.draw-layer-selector [self {: x : y &as form}]
(let [mkopt (fn [ilayer] {: ilayer :label (.. ilayer " (" (self:layer-type ilayer) ")")})
options (icollect [ilayer (ipairs (platform :layers))] (mkopt ilayer))
selection (labelled-dropdown (reform form {:wdropdown (* 100 SCALE) :tag :layer-selector}) "Layer" (mkopt self.ilayer) options)]
(when (not= self.ilayer selection.ilayer)
(self:set-ilayer selection.ilayer))))
(fn MapEditView.linking-obj [self] (. self.level.objects self.iobject-linking))
(fn MapEditView.draw-link-line [self x y iobjectSrc color toMouse?]
(local objectSrc (. self.level.objects iobjectSrc))
(local objectDest (. self.level.objects objectSrc.link))
(local coord (fn [c m d] (+ c (* (- m 1) d) (/ d 2))))
(local [tilew tileh] [(self:tilew) (self:tileh)])
(local xStart (coord x objectSrc.x tilew))
(local yStart (coord y objectSrc.y tileh))
(when (or toMouse? objectDest)
(local xEnd (if toMouse? (love.mouse.getX) (coord x objectDest.x tilew)))
(local yEnd (if toMouse? (love.mouse.getY) (coord y objectDest.y tileh)))
(love.graphics.setColor (table.unpack color))
(love.graphics.line xStart yStart xEnd yEnd)
(love.graphics.circle :line xEnd yEnd (/ tilew 5))
(love.graphics.setColor 1 1 1)))
(fn MapEditView.draw-link-lines [self {: x : y} iobject-over]
(for [iobject 1 (length self.level.objects)]
(self:draw-link-line x y iobject [0 0 1 0.3]))
(when (not= iobject-over nil) (self:draw-link-line x y iobject-over [0 0.5 1] false))
(when (not= self.iobject-linking nil)
(if (= self.imstate.left :released) (set self.iobject-linking nil)
(self:draw-link-line x y self.iobject-linking [0 1 0] true))))
(fn MapEditView.draw-tilestrip [self x y my ?ilayer translucent?]
; stripcache leaks but honestly who cares
(local tilestrip [])
(var stripid (tostring ?ilayer))
(for [mx 1 (self:mapw ?ilayer)]
(local itile (self:itile-from-xy mx my ?ilayer))
(local tile (?. (files.cache (self:layer-type ?ilayer)) :tiles itile :gfx))
(table.insert tilestrip tile)
(set stripid (.. stripid (string.char itile))))
(var sprite (. self.stripcache stripid))
(when (= sprite nil)
(set sprite (tilestrip-to-sprite tilestrip (self:layer-type ?ilayer)))
(tset self.stripcache stripid sprite))
(love.graphics.setColor 1 1 1 (if translucent? 0.4 1))
(love.graphics.draw sprite x y 0 self.sprite-scale self.sprite-scale))
(fn MapEditView.mapsize [self ilayer]
(let [{: mapw : maph : tilew : tileh : xstagger : ystagger} (self:scaled-dimensions ilayer)
intileh (or ystagger tileh)]
[(+ (or xstagger 0) (* mapw tilew)) (+ tileh (* (- maph 1) intileh))]))
(fn MapEditView.draw-player [self mx my x y]
(each [_ player (ipairs (or files.game.players [:player]))]
(match (. self.level player)
{:x mx :y my} (renderer.draw_text style.font player x y style.text)))
(love.graphics.setColor 1 1 1))
(fn MapEditView.draw-box [self x y w h color thickness]
(love.graphics.setColor (table.unpack color))
(love.graphics.setLineWidth thickness)
(love.graphics.rectangle :line x y w h)
(love.graphics.setColor 1 1 1))
(fn MapEditView.draw-object-box [self x y w h iobject]
(when iobject
(let [color [1 0 (if (and (= self.itile nil) (= iobject self.iobject)) 1 0) 1]]
(self:draw-object-box x y w h color 3))))
(fn MapEditView.handle-mouseedits-object [self mx my x y w h ilayer]
(when (and (active? self [:map ilayer]) (mouse-inside x y w h))
(let [iobject (self:iobject-from-xy mx my)]
(match self.imstate.left
:down (when (= self.iobject-linking nil) (set self.iobject-linking iobject))
:released
(do (if (and (not= iobject nil) (= self.iobject-linking iobject))
(set self.iobject iobject)
(not= self.iobject-linking nil)
(tset (self:linking-obj) :link iobject)
(not= self.playerpos nil)
(do (tset self.level self.playerpos {:x mx :y my})
(set self.playerpos nil))
(= iobject nil)
(let [tile (self.tilecache:tile (self:itile-from-xy mx my ilayer))]
(table.insert self.level.objects {:x mx :y my :func (or tile.word "")})
(set self.iobject (length self.level.objects))))
(set self.iobject-linking nil))))))
(fn MapEditView.handle-mouseedits-tile [self mx my x y w h ilayer]
(when (and (active? self [:map ilayer]) (mouse-inside x y w h) (not= (self:itile-from-xy mx my ilayer) self.itile))
(self:set-tile mx my self.itile)))
(fn MapEditView.draw-tile-xy-label [self mx my x y h ystagger]
(local labely (math.floor (+ y (- (or ystagger 0)) (/ (- (if ystagger (* ystagger 2) h) (style.font:get_height)) 2))))
(renderer.draw_text style.font (string.format "%x" (encode-yx {:x mx :y my})) (+ x 20) labely style.text)
(love.graphics.setColor 1 1 1))
(fn MapEditView.draw-map-layer [self {: x : y &as form} live ilayer]
(love.graphics.setColor 1 1 1 1)
(local {: mapw : maph : tilew : tileh : xstagger : ystagger} (self:scaled-dimensions ilayer))
(local [xoffset-layer yoffset-layer] (self:layer-offset ilayer))
(local intileh (or ystagger tileh))
(let [[w h] (self:mapsize ilayer)] (lume.extend form {: w : h :tag [:map ilayer]}))
(when live (activate form))
(var iobject-over nil)
(for [my 1 maph]
(local tiley (+ y yoffset-layer (* (- my 1) (or ystagger tileh))))
(local intiley (+ tiley (- tileh intileh)))
(local xoff (+ xoffset-layer (if (and xstagger (= (% my 2) 0)) xstagger 0)))
(self:draw-tilestrip (+ x xoff) tiley my ilayer (and (mouse-inside x y form.w form.h) (not live)))
(when live
(for [mx 1 mapw]
(local tilex (+ x (* (- mx 1) tilew) xoff))
(local iobject (self:iobject-from-xy mx my))
(when (= self.itile nil)
(self:draw-player mx my tilex intiley)
(self:draw-object-box tilex intiley tilew intileh iobject))
(if self.itile
(self:handle-mouseedits-tile mx my tilex intiley tilew intileh ilayer)
(self:handle-mouseedits-object mx my tilex intiley tilew intileh ilayer))
(when (mouse-inside tilex intiley tilew intileh)
(when (not= iobject nil) (set iobject-over iobject))
(self:draw-tile-xy-label mx my tilex intiley tileh ystagger)
(self:draw-box tilex intiley tilew intileh [1 1 1 0.5] 1)))))
(when (and live (= self.itile nil))
(self:draw-link-lines form iobject-over)))
(fn MapEditView.draw-map-editor [self form]
(let [g (group-wrapper form)
layers (platform :layers)]
(if layers
(do (each [ilayer (ipairs (platform :layers))]
(self:draw-map-layer (g) (= ilayer self.ilayer) ilayer))
(let [{: x : y : w : h} (g)]
(when (mouse-inside x y w h)
(self:draw-map-layer form true self.ilayer))))
(self:draw-map-layer form true))
(g)))
(fn condition-label [flag]
(if flag {:label flag : flag} {:label "<always>"}))
(fn condition-options []
(let [options [(condition-label nil)]]
(each [_ flag (ipairs (or files.game.flags []))]
(table.insert options (condition-label flag)))
options))
(fn MapEditView.draw-object-code-editor [self form object]
(var istep-to-delete nil)
(when (not object.steps) (set object.steps []))
(each [istep step (ipairs object.steps)]
(when (textbutton (reform form {:x (+ form.x (* 280 SCALE)) :into {}}) "X")
(set istep-to-delete istep))
(set step.condition (. (dropdown (reform form {:x (+ form.x (* 150 SCALE)) :w (* 100 SCALE) :tag [:code-condition istep] :into {}}) (condition-label step.condition) (condition-options)) :flag))
(set step.action (dropdown (reform form {:w (* 100 SCALE) :tag [:code-action istep]}) (or step.action (. actions.actionlist 1)) actions.actionlist))
(actions.edit step (under form {:w (* 300 SCALE)}) istep)
(under form))
(when istep-to-delete (table.remove object.steps istep-to-delete))
(when (textbutton (under form) "+ New Step")
(table.insert object.steps {})))
(fn advanced? [object]
(or object.advanced
(and (= object.advanced nil)
(not= object.func "")
(not= object.func nil))))
(fn MapEditView.draw-object-advanced-editor [self form object]
(let [fieldform {:wlabel (* 100 SCALE) :wtext (* 200 SCALE)}]
(set object.func (textfield (reform form fieldform) "Word" object.func))
(set object.name (textfield (under form fieldform) "Name" object.name))
(set object.linkword (textfield (under form fieldform) "Link word" object.linkword))
(if object.link
(when (textbutton (under form) "Unlink")
(set object.link nil))
(set object.linkentity (textfield (under form fieldform) "Link entity" object.linkentity)))))
(fn MapEditView.draw-object-editor [self form]
(let [object (self:object)
footer (group-wrapper form)]
(if (advanced? object)
(self:draw-object-advanced-editor form object)
(self:draw-object-code-editor form object))
(set self.new-flag-name (footer textbox (under form {:tag :new-flag-name :w (* 200 SCALE)}) self.new-flag-name))
(when (footer textbutton (right-of form) "+ New Flag")
(when (= files.game.flags nil)
(set files.game.flags []))
(table.insert files.game.flags self.new-flag-name)
(set self.new-flag-name ""))
(when (footer textbutton (under (footer)) "Delete")
(move-object self.level.objects (+ self.iobject 1) self.iobject)
(set self.iobject nil))
(when (footer textbutton (right-of form) (if (advanced? object) "Simple" "Advanced"))
(set object.advanced (not (advanced? object))))
(footer)))
(fn MapEditView.load-level [self]
(set self.stripcache {})
(when (= (. (self:levels) self.ilevel) nil)
(tset (self:levels) self.ilevel {:map (self:empty-map) :objects []}))
(set self.level (. (self:levels) self.ilevel))
(set self.iobject nil))
(fn MapEditView.reload [self]
(MapEditView.super.reload self)
(self:load-level))
(fn MapEditView.draw [self]
(self:draw_background style.background)
(self:draw_scrollbar)
(let [form (self:form)
form-editor (self:form)
header (group-wrapper form)
_ (header #(self:draw-map-selector $...) form)
_ (when (platform :layers) (header #(self:draw-layer-selector $...) (right-of form)))
_ (self:draw-map-editor (under (header)))
editor-on-side (> self.size.x (+ form.w (* 300 SCALE)))
fieldform {:wlabel (* 100 SCALE) :wtext (* 200 SCALE)}]
(when editor-on-side
(set form-editor.x (+ form.x form.w style.padding.x))
(set form-editor.w (- form-editor.w form.w style.padding.x)))
(self:draw-tile-selector (under form {:w (if editor-on-side form.w (- self.size.x (* style.padding.x 2)))}))
(set self.level.tickword (textfield (under form fieldform) "Tick word" self.level.tickword))
(set self.level.moveword (textfield (under form fieldform) "Move word" self.level.moveword))
(set self.level.loadword (textfield (under form fieldform) "Load word" self.level.loadword))
(when (checkbox (under form) "Edit objects" (= self.itile nil))
(set self.itile nil)
(set self.playerpos nil))
(each [_ player (ipairs (or files.game.players [:player]))]
(when (checkbox (under form) (.. "Position " player) (and (= self.itile nil) (= self.playerpos player)))
(set self.itile nil)
(set self.playerpos player)))
(each [_ levelflag (ipairs (or files.game.levelflags []))]
(when (checkbox (under form) levelflag (. self.level levelflag))
(tset self.level levelflag (not (. self.level levelflag)))))
(when (not editor-on-side) (set form-editor.y (+ form.y form.h style.padding.y)))
(when self.iobject (self:draw-object-editor form-editor))
(self:end-scroll (if (> (+ form.y form.h) (+ form-editor.y (or form-editor.h 0))) form form-editor))))
(fn MapEditView.get_name [self] (.. "Map " self.ilevel))
MapEditView