honeylisp/editor/mapedit.fnl

325 lines
15 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 {: mouse-inside : activate : active? : checkbox : textfield : textbutton : textbox : dropdown} (util.require :editor.imstate))
(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-type [self ?ilayer] (or (?. (platform :layers) (or ?ilayer self.ilayer)) :tiles))
(fn MapEditView.dimensions [self ?ilayer] (or (platform (self:layer-type)) (platform)))
(fn MapEditView.scaled-dimensions [self ?ilayer]
(let [dim (lume.clone (self:dimensions))]
(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] (string.rep "\0" (* (self:mapw) (self:maph))))
(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]
(+ mx -1 (* (self:mapw) (- (self:maph) 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]
(if (platform :layers) (or (?. self.level.layers self.ilayer) (self:empty-map))
self.level.map))
(fn MapEditView.itile-from-xy [self mx my]
(local imap (+ (self:imap-from-xy mx my) 1))
(local enctile (string.byte (string.sub (self:map) imap imap)))
(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))))
(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 x y]
(renderer.draw_text style.font "Map" x (+ y (/ style.padding.y 2)) style.text)
(let [options {}
level-count (length (self:levels))
_ (do (for [i 1 level-count] (tset options i i))
(table.insert options :New))
(ilevel yNext) (dropdown self :map-selector self.ilevel options (+ x (* 50 SCALE)) y (* 100 SCALE))]
(when (not= ilevel self.ilevel)
(set self.ilevel (if (= ilevel :New) (+ level-count 1) ilevel))
(self:load-level))
(- yNext y)))
(fn MapEditView.set-ilayer [self ilayer]
(set self.ilayer ilayer)
(self:set-style (self:layer-type)))
(fn MapEditView.draw-layer-selector [self x y]
(renderer.draw_text style.font "Layer" x (+ y (/ style.padding.y 2)) style.text)
(let [mkopt (fn [ilayer] {: ilayer :label (.. ilayer " (" (self:layer-type ilayer) ")")})
options (icollect [ilayer (ipairs (platform :layers))] (mkopt ilayer))
(selection yNext) (dropdown self :layer-selector (mkopt self.ilayer) options (+ x (* 50 SCALE)) y (* 100 SCALE))]
(when (not= self.ilayer selection.ilayer)
(self:set-ilayer selection.ilayer))
(- yNext y)))
(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-tilestrip [self x y my ?ilayer]
; stripcache leaks but honestly who cares
(local tilestrip [])
(var stripid "")
(for [mx 1 (self:mapw)]
(local itile (self:itile-from-xy mx my))
(local tile (?. self.tilecache.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.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-map-editor [self x y ilayer]
(love.graphics.setColor 1 1 1 1)
(local button-state self.imstate.left)
(local {: mapw : maph : tilew : tileh : xstagger : ystagger} (self:scaled-dimensions ilayer))
(local intileh (or ystagger tileh))
(local [mappw mapph] (self:mapsize ilayer))
(activate self :map x y mappw mapph)
(var iobject-over nil)
(for [my 1 maph]
(local tiley (+ y (* (- my 1) (or ystagger tileh))))
(local intiley (+ tiley (- tileh intileh)))
(local xoff (if (and xstagger (= (% my 2) 0)) xstagger 0))
(self:draw-tilestrip (+ x xoff) tiley my ilayer)
(for [mx 1 mapw]
(local tilex (+ x (* (- mx 1) tilew) xoff))
(local itile (self:itile-from-xy mx my))
(local iobject (self:iobject-from-xy mx my))
(when (= self.itile nil)
(each [_ player (ipairs (or files.game.players [:player]))]
(match (. self.level player)
{:x mx :y my} (renderer.draw_text style.font player tilex intiley style.text)))
(love.graphics.setColor 1 1 1))
(when (and (not= iobject nil) (= self.itile nil))
(love.graphics.setColor 1 0 (if (and (= self.itile nil) (= iobject self.iobject)) 1 0))
(love.graphics.setLineWidth 3)
(love.graphics.rectangle :line tilex intiley tilew intileh)
(love.graphics.setColor 1 1 1))
(when (mouse-inside tilex intiley tilew intileh)
(when (not= iobject nil) (set iobject-over iobject))
(renderer.draw_text style.font (string.format "%x" (encode-yx {:x mx :y my})) tilex (+ intiley 15) style.text)
(love.graphics.setColor 1 1 1))
(when (and self.itile (active? self :map) (mouse-inside tilex intiley tilew intileh) (not= itile self.itile))
(self:set-tile mx my self.itile))
(when (and (= self.itile nil) (active? self :map) (mouse-inside tilex intiley tilew intileh))
(match button-state
:pressed (set self.iobject-linking iobject)
:released
(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 itile)]
(table.insert self.level.objects {:x mx :y my :func (or tile.word "")})
(set self.iobject (length self.level.objects))))))))
(when (= self.itile nil)
(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))))
mapph)
(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 object x y]
(var y y)
(var istep-to-delete nil)
(when (not object.steps) (set object.steps []))
(each [istep step (ipairs object.steps)]
(when (textbutton self "X" (+ x (* 280 SCALE)) y)
(set istep-to-delete istep))
(set step.condition (. (dropdown self [:code-condition istep] (condition-label step.condition) (condition-options)
(+ x (* 100 SCALE) style.padding.x) y (* 100 SCALE))
:flag))
(set (step.action y) (dropdown self [:code-action istep] (or step.action (. actions.actionlist 1)) actions.actionlist x y (* 100 SCALE)))
(set y (actions.edit step self x y (* 300 SCALE) istep))
(set y (+ y style.padding.y)))
(when istep-to-delete (table.remove object.steps istep-to-delete))
(let [(do-new y) (textbutton self "+ New Step" x (+ y style.padding.y))]
(when do-new (table.insert object.steps {}))
y))
(fn advanced? [object]
(or object.advanced
(and (= object.advanced nil)
(not= object.func "")
(not= object.func nil))))
(fn MapEditView.draw-object-advanced-editor [self object x y]
(let [(func y) (textfield self "Word" object.func x y (* 100 SCALE) (* 200 SCALE))
(name y) (textfield self "Name" object.name x (+ y style.padding.y) (* 100 SCALE) (* 200 SCALE))
(linkword y) (textfield self "Link word" object.linkword x (+ y style.padding.y) (* 100 SCALE) (* 200 SCALE))
(do-unlink y) (if object.link (textbutton self "Unlink" x (+ y style.padding.y)) (values false y))
(linkentity y) (if object.link (values object.linkentity y) (textfield self "Link entity" object.linkentity x (+ y style.padding.y) (* 100 SCALE) (* 200 SCALE)))]
(lume.extend object {: func : name : linkword : linkentity})
(when do-unlink (set object.link nil))
y))
(fn MapEditView.draw-object-editor [self x y]
(let [object (self:object)
y (if (advanced? object)
(self:draw-object-advanced-editor object x y)
(self:draw-object-code-editor object x y))
new-flag-name (textbox self :new-flag-name self.new-flag-name x (+ y style.padding.y) (* 200 SCALE))
(mk-new-flag y) (textbutton self "+ New Flag" (+ x (* 200 SCALE) style.padding.x) (+ y style.padding.y))
do-delete (textbutton self "Delete" x (+ y (* style.padding.y 2)))
(do-advanced y) (textbutton self (if (advanced? object) "Simple" "Advanced") (+ x (* 150 SCALE)) (+ y (* style.padding.y 2)))]
(set self.new-flag-name new-flag-name)
(when mk-new-flag
(when (= files.game.flags nil)
(set files.game.flags []))
(table.insert files.game.flags new-flag-name)
(set self.new-flag-name ""))
(when do-delete
(move-object self.level.objects (+ self.iobject 1) self.iobject)
(set self.iobject nil))
(when do-advanced (set object.advanced (not (advanced? object))))
y))
(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]
(var x (+ self.position.x style.padding.x (- self.scroll.x)))
(var y (+ self.position.y style.padding.y (- self.scroll.y)))
(self:draw_background style.background)
(self:draw_scrollbar)
(local {: mapw : maph : tilew : tileh} (self:scaled-dimensions))
(local ytop y)
(local editor-on-side (> self.size.x (+ (* tilew mapw) (* 300 SCALE))))
(when (platform :layers) (self:draw-layer-selector (+ x (* 200 SCALE)) y))
(set y (+ y (self:draw-map-selector x y) style.padding.y))
(set y (+ y (self:draw-map-editor x y) style.padding.y))
(set y (+ y (self:draw-tile-selector x y (if editor-on-side (* tilew mapw)
(- self.size.x (* style.padding.x 2))))))
(set (self.level.tickword y) (textfield self "Tick word" self.level.tickword x (+ y style.padding.y) (* 100 SCALE) (* 200 SCALE)))
(set (self.level.moveword y) (textfield self "Move word" self.level.moveword x (+ y style.padding.y) (* 100 SCALE) (* 200 SCALE)))
(set (self.level.loadword y) (textfield self "Load word" self.level.loadword x (+ y style.padding.y) (* 100 SCALE) (* 200 SCALE)))
(let [(checked y-new) (checkbox self "Edit objects" (= self.itile nil) x (+ y style.padding.y))
_ (when checked
(set self.itile nil)
(set self.playerpos nil))]
(set y y-new)
(each [_ player (ipairs (or files.game.players [:player]))]
(let [(checked y-new) (checkbox self (.. "Position " player) (and (= self.itile nil) (= self.playerpos player)) x (+ y style.padding.y))]
(when checked
(set self.itile nil)
(set self.playerpos player))
(set y y-new))))
(each [_ levelflag (ipairs (or files.game.levelflags []))]
(let [(checked y-new) (checkbox self levelflag (. self.level levelflag) x (+ y style.padding.y))]
(when checked (tset self.level levelflag (not (. self.level levelflag))))
(set y y-new)))
(when self.iobject
(set y (math.max y (if editor-on-side
(self:draw-object-editor (+ x (* tilew mapw) style.padding.x) ytop)
(self:draw-object-editor x (+ y style.padding.y))))))
(set self.scrollheight (+ y (- self.position.y) self.scroll.y style.padding.y)))
(fn MapEditView.get_name [self] (.. "Map " self.ilevel))
MapEditView