From 8e7937b3c6afd3d68a1a4088eefa67edbcabe8d6 Mon Sep 17 00:00:00 2001 From: dalyjame Date: Wed, 3 Jul 2019 21:57:20 -0400 Subject: [PATCH] Created a builder for making maps --- .../src/fmon/builder/AnimationBuilder.scala | 20 +- Builder/src/fmon/builder/MapBuilder.fxml | 107 +++++++++ Builder/src/fmon/builder/MapBuilder.scala | 138 ++++++++++++ FakeMon/src/fmon/battle/BattleUI.scala | 26 +++ FakeMon/src/fmon/battle/msg/AnimateMsg.scala | 25 +++ FakeMon/src/fmon/draw/AnimatedImage.scala | 20 ++ .../src/fmon/draw/tile/AutoFloorTile.scala | 211 ++++++++++++++++++ FakeMon/src/fmon/stat/Move.scala | 1 + FakeMon/src/fmon/util/Direction.scala | 7 + 9 files changed, 545 insertions(+), 10 deletions(-) create mode 100644 Builder/src/fmon/builder/MapBuilder.fxml create mode 100644 Builder/src/fmon/builder/MapBuilder.scala create mode 100644 FakeMon/src/fmon/battle/msg/AnimateMsg.scala create mode 100644 FakeMon/src/fmon/draw/tile/AutoFloorTile.scala create mode 100644 FakeMon/src/fmon/util/Direction.scala diff --git a/Builder/src/fmon/builder/AnimationBuilder.scala b/Builder/src/fmon/builder/AnimationBuilder.scala index 428fbdb..fda546c 100644 --- a/Builder/src/fmon/builder/AnimationBuilder.scala +++ b/Builder/src/fmon/builder/AnimationBuilder.scala @@ -29,7 +29,7 @@ class ObsAnimation(name_ : String) { def toToken = { val filename = frames(0).file.getName - val indices = frames.map(_.index) + val indices = frames.map(_.index).toIndexedSeq AnimationToken(filename, indices) } @@ -42,14 +42,7 @@ class Frame(val img: StillImg, val file: File, val index: Int) { override def toString = s"${file.getName} ${index}" } -case class AnimationToken(val filename: String, val indices: Seq[Int]) { - def toFrames(dir: File) = { - val file = new File(s"${dir.getPath}/$filename") - val palette = Palette.square(new FileInputStream(file), 5) - val images = palette.images - indices.map(i => new Frame(images(i), file, i)) - } -} + case class ImageToken(val filename: String, val index: Int) { def toFrame(dir: File) = { @@ -174,7 +167,7 @@ class AnimationBuilder extends Savable { val aniMap = YamlHelper.extractMap[AnimationToken](new FileInputStream(file)) aniMap.foreach{case (name, token) => { addAnimation(name) - val frames = token.toFrames(new File(imgdir)) + val frames = AnimationBuilder.toFrames(token, new File(imgdir)) animationList.selectionModel().getSelectedItem.frames ++= frames }} } @@ -207,4 +200,11 @@ object AnimationBuilder { def main(args: Array[String]): Unit = { Application.launch(classOf[ABA], args: _*) } + + def toFrames(token: AnimationToken, dir: File) = { + val file = new File(s"${dir.getPath}/${token.filename}") + val palette = Palette.square(new FileInputStream(file), 5) + val images = palette.images + token.indices.map(i => new Frame(images(i), file, i)) + } } \ No newline at end of file diff --git a/Builder/src/fmon/builder/MapBuilder.fxml b/Builder/src/fmon/builder/MapBuilder.fxml new file mode 100644 index 0000000..b2a1cdb --- /dev/null +++ b/Builder/src/fmon/builder/MapBuilder.fxml @@ -0,0 +1,107 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/Builder/src/fmon/builder/MapBuilder.scala b/Builder/src/fmon/builder/MapBuilder.scala new file mode 100644 index 0000000..53bc647 --- /dev/null +++ b/Builder/src/fmon/builder/MapBuilder.scala @@ -0,0 +1,138 @@ +package fmon.builder + +import java.io._ + +import javafx.application.Application +import javafx.fxml.FXML +import javafx.fxml.FXMLLoader +import javafx.fxml.JavaFXBuilderFactory +import javafx.scene.{control => jfxsc, layout => jfxsl, Parent, Scene} +import javafx.stage.Stage + +import scalafx.Includes._ +import scalafx.beans.property._ +import scalafx.collections.ObservableBuffer +import scalafx.scene.control._ +import scalafx.scene.image.ImageView +import scalafx.scene.input.MouseEvent +import scalafx.scene.paint.Color +import scalafx.scene.layout._ + +import fmon.draw.tile._ +import fmon.util.Direction + +import Direction._ + +import scala.math.{min, max} + +class MapBuilder { + @FXML var tileSelector: jfxsl.TilePane = _ + @FXML var gameMap: jfxsl.TilePane = _ + + var tileSeq: IndexedSeq[AutoFloorTile] = _ + var imgSeq: IndexedSeq[ImageView] = _ + + var currTile: AutoFloorTile = _ + + val numRows = 10 + val numCols = 10 + + @FXML + def initialize(): Unit = { + val file = new File(raw"C:\Users\dalyj\Documents\Design\Images\AutoTiles\tilea2.png") + val palette = new AutoTilePalette(file, 32) + val tiles = for (y <- 0 until 4; x <- 0 until 8) yield { + palette(x, y) + } + val icons = tiles.zipWithIndex.map{case(t, i) => { + val view = new ImageView(t.icon.croppedImage()) + view.handleEvent(MouseEvent.Any) { + e: MouseEvent => if (e.eventType == MouseEvent.MouseClicked) { + currTile = t + } + } + view.delegate + }} + tileSelector.children ++= icons + currTile = tiles.head + + tileSeq = for (y <- 0 until numRows; x <- 0 until numCols) yield currTile + imgSeq = for (y <- 0 until numRows; x <- 0 until numCols) yield { + val view = new ImageView(currTile.icon.croppedImage()) // TODO Tile with adjacent + view.handleEvent(MouseEvent.Any) { + e: MouseEvent => if (e.eventType == MouseEvent.MouseClicked) { + val index = point2index(x, y) + tileSeq = tileSeq.updated(index, currTile) + autotile(x, y) + } + } + view + } + gameMap.prefColumns = numCols + gameMap.prefRows = numRows + gameMap.children ++= imgSeq.map(_.delegate) + + for (y <- 0 until numRows; x <- 0 until numCols) { + smoothTile(x, y) + } + } + + def autotile(x: Int, y: Int) = { + for (yy <- max(y - 1, 0) to min(y + 1, numCols - 1); xx <- max(x - 1, 0) to min(x + 1, numRows - 1)) { + smoothTile(xx, yy) + } + } + + def smoothTile(x: Int, y: Int) = { + val i = point2index(x, y) + val dirs = scala.collection.mutable.Set[Direction.Value]() + + val onLeft = x == 0 + val onRight = x + 1 == numCols + val onTop = y == 0 + val onBottom = y + 1 == numRows + + if (onLeft || tileSeq(left(i)) == tileSeq(i)) dirs += West + if (onRight || tileSeq(right(i)) == tileSeq(i)) dirs += East + if (onTop || tileSeq(up(i)) == tileSeq(i)) dirs += North + if (onBottom || tileSeq(down(i)) == tileSeq(i)) dirs += South + if (onLeft || onTop || tileSeq(left(up(i))) == tileSeq(i)) dirs += Northwest + if (onLeft || onBottom || tileSeq(down(left(i))) == tileSeq(i)) dirs += Southwest + if (onTop || onRight || tileSeq(right(up(i))) == tileSeq(i)) dirs += Northeast + if (onBottom || onRight || tileSeq(right(down(i))) == tileSeq(i)) dirs += Southeast + val index = point2index(x, y) + imgSeq(index).image = tileSeq(index).build(dirs.toSet).croppedImage() + } + + def point2index(x: Int, y: Int) = y * numCols + x + + def left(i: Int) = i - 1 + def right(i: Int) = i + 1 + def up(i: Int) = i - numCols + def down(i: Int) = i + numCols +} + +object MapBuilder { + class MBA extends Application { + override def start(primaryStage: Stage): Unit = { + val frameLoader = new FXMLLoader(getClass.getResource("MapBuilder.fxml")) + val root: Parent = frameLoader.load() + val controller = frameLoader.getController[MapBuilder]() + + //val builderLoader = new FXMLLoader(getClass.getResource("ElementBuilder.fxml")) + //val builder: Parent = builderLoader.load() + //val builderController = builderLoader.getController[ElementBuilder]() + + //controller.pane.children = builder + //controller.builder = builderController + + val scene: Scene = new Scene(root) + primaryStage.setScene(scene) + primaryStage.show() + } + } + + def main(args: Array[String]): Unit = { + Application.launch(classOf[MBA], args: _*) + } +} \ No newline at end of file diff --git a/FakeMon/src/fmon/battle/BattleUI.scala b/FakeMon/src/fmon/battle/BattleUI.scala index e35015f..9b935c9 100644 --- a/FakeMon/src/fmon/battle/BattleUI.scala +++ b/FakeMon/src/fmon/battle/BattleUI.scala @@ -1,5 +1,6 @@ package fmon.battle +import java.io._ import java.util.concurrent.locks.{Condition, ReentrantLock} import javafx.application.Platform @@ -23,6 +24,7 @@ import scalafx.scene.transform.Rotate import scalafx.util.Duration import fmon.battle.msg._ +import fmon.draw._ import fmon.stat._ import fmon.stat.Statistic._ @@ -78,6 +80,7 @@ class BattleUI extends SignalConsumer { lock.lock() try { msg match { + case AnimateMsg(ani, target) => playAnimation(ani, target) case Message(text) => messages.text = s"${messages.text()}${text}\n" case DamageMsg(dmg, target, element) => playDamage(dmg, target, element) } @@ -93,6 +96,29 @@ class BattleUI extends SignalConsumer { } + private def playAnimation(aniname: String, target: Monster): Unit = { + numPlaying += 1 + def helper = { + val animation = AnimationToken(aniname).load(new File(raw"C:\Users\dalyj\Documents\Design\Images\TimmahLexusX reduced\Reduced Animations")) + val imgView = new ImageView + animationPane.children += imgView + + val center = findCenter(target) + imgView.translateX = center.x - 96 // TODO : Compute result + imgView.translateY = center.y - 96 + + val duration = Duration(animation.frames.size * 50) + val transition = new SpriteAnimation(imgView, animation, duration) + transition.onFinished = handle{ + animationPane.children -= imgView + finishPlay() + } + transition.play() + } + val task = Task(helper) + Platform.runLater(task) + } + private def playDamage(damage: Int, target: Monster, element: Element): Unit = { numPlaying += 1 def helper = { diff --git a/FakeMon/src/fmon/battle/msg/AnimateMsg.scala b/FakeMon/src/fmon/battle/msg/AnimateMsg.scala new file mode 100644 index 0000000..4b5c2ac --- /dev/null +++ b/FakeMon/src/fmon/battle/msg/AnimateMsg.scala @@ -0,0 +1,25 @@ +package fmon.battle.msg + +import fmon.stat.{Element, Monster} + +case class AnimateMsg(val animation: String, target: Monster) extends Signal { + +} + +object AnimateMsg { + def apply(element: Element, target: Monster): AnimateMsg = { + val ani = element.name match { + case "Poison" => "Poison Bubble" + case "Electric" => "Lightning Lines" + case "Fire" => "Rising Fire 1" + case "Rock" => "Rock Blast" + case "Ground" => "Stalagmites" + case "Water" => "Rain" + case "Flying" => "Gale" + case "Ice" => "Glacier" + case "Fairy" => "Light and Glass" + case _ => "Buster" + } + AnimateMsg(ani, target) + } +} \ No newline at end of file diff --git a/FakeMon/src/fmon/draw/AnimatedImage.scala b/FakeMon/src/fmon/draw/AnimatedImage.scala index 4360e8f..3c2657d 100644 --- a/FakeMon/src/fmon/draw/AnimatedImage.scala +++ b/FakeMon/src/fmon/draw/AnimatedImage.scala @@ -1,5 +1,7 @@ package fmon.draw +import java.io.{File, FileInputStream} + import javafx.animation.{Transition => JTransition} import scalafx.Includes._ @@ -7,6 +9,8 @@ import scalafx.animation._ import scalafx.scene.image.ImageView import scalafx.util.Duration +import fmon.util.YamlHelper + class SpriteAnimation(val view: ImageView, val image: Drawable, val duration: Duration) extends JTransition { setCycleDuration(duration) setInterpolator(Interpolator.Linear) @@ -24,4 +28,20 @@ class AnimatedImage(val frames: IndexedSeq[Drawable]) extends Drawable { val index = Math.min((frames.size * t).toInt, frames.size - 1) frames(index).draw(view, t) } +} + +case class AnimationToken(val filename: String, val indices: IndexedSeq[Int]) { + def load(dir: File) = { + val file = new File(s"${dir.getPath}/${filename}") + val palette = Palette.square(new FileInputStream(file), 5) + val images = palette.images + val frames = indices.map(i => images(i)) + new AnimatedImage(frames) + } +} + +object AnimationToken { + val tokens = YamlHelper.extractMap[AnimationToken](new FileInputStream(raw"C:\Users\dalyj\Documents\Design\animations.yaml")) + + def apply(name: String) = tokens(name) } \ No newline at end of file diff --git a/FakeMon/src/fmon/draw/tile/AutoFloorTile.scala b/FakeMon/src/fmon/draw/tile/AutoFloorTile.scala new file mode 100644 index 0000000..7c8afcb --- /dev/null +++ b/FakeMon/src/fmon/draw/tile/AutoFloorTile.scala @@ -0,0 +1,211 @@ +package fmon.draw.tile + +import java.io.{File, FileInputStream} + +import scalafx.scene.SnapshotParameters +import scalafx.scene.canvas.Canvas +import scalafx.scene.image._ +import scalafx.scene.paint.Color + +import fmon.draw._ +import fmon.util._ + +import Direction._ + +class AutoFloorTile(val palette: Palette, val size: Int = 48) { + + def icon: StillImg = palette.apply(0, 0, 2, 2) + + def build(dirs: Set[Direction.Value]) = { + val canvas = new scalafx.scene.canvas.Canvas(size, size) + val g = canvas.graphicsContext2D + + val ne = getNE(dirs) + val nw = getNW(dirs) + val se = getSE(dirs) + val sw = getSW(dirs) + + canvas.graphicsContext2D.drawImage(nw, 0, 0) + canvas.graphicsContext2D.drawImage(ne, size / 2, 0) + canvas.graphicsContext2D.drawImage(sw, 0, size / 2) + canvas.graphicsContext2D.drawImage(se, size / 2, size / 2) + + val wi = new WritableImage(size, size) + val parameters = new SnapshotParameters(){ + fill = Color.Transparent + } + val img = canvas.snapshot(parameters, wi) + StillImg(img, 0, 0, size, size) + } + + private def getNE(dirs: Set[Direction.Value]): Image = { + if (!dirs(Northeast) && dirs(North) && dirs(East)) { + palette(3, 0).croppedImage() + } else { + val x = if (dirs(East)) 1 else 3 + val y = if (dirs(North)) 4 else 2 + palette(x, y).croppedImage + } + } + + private def getNW(dirs: Set[Direction.Value]): Image = { + if (!dirs(Northwest) && dirs(North) && dirs(West)) { + palette(2, 0).croppedImage() + } else { + val x = if (dirs(West)) 2 else 0 + val y = if (dirs(North)) 4 else 2 + palette(x, y).croppedImage + } + } + + private def getSE(dirs: Set[Direction.Value]): Image = { + if (!dirs(Southeast) && dirs(South) && dirs(East)) { + palette(3, 1).croppedImage() + } else { + val x = if (dirs(East)) 1 else 3 + val y = if (dirs(South)) 3 else 5 + palette(x, y).croppedImage + } + } + + private def getSW(dirs: Set[Direction.Value]): Image = { + if (!dirs(Southwest) && dirs(South) && dirs(West)) { + palette(2, 1).croppedImage() + } else { + val x = if (dirs(West)) 2 else 0 + val y = if (dirs(South)) 3 else 5 + palette(x, y).croppedImage + } + } +} + +class AutoWallTile(val palette: Palette, val size: Int = 48) { + def icon: StillImg = { + val canvas = new scalafx.scene.canvas.Canvas(size, size) + val g = canvas.graphicsContext2D + + val ul = palette.apply(0, 0) + val ur = palette.apply(3, 0) + val ll = palette.apply(0, 3) + val lr = palette.apply(3, 3) + + canvas.graphicsContext2D.drawImage(ul.croppedImage(), 0, 0) + canvas.graphicsContext2D.drawImage(ur.croppedImage(), size / 2, 0) + canvas.graphicsContext2D.drawImage(ll.croppedImage(), 0, size / 2) + canvas.graphicsContext2D.drawImage(lr.croppedImage(), size / 2, size / 2) + + val wi = new WritableImage(size, size) + val parameters = new SnapshotParameters(){ + fill = Color.Transparent + } + val img = canvas.snapshot(parameters, wi) + StillImg(img, 0, 0, size, size) + } + + def build(dirs: Set[Direction.Value]) = { + val canvas = new scalafx.scene.canvas.Canvas(size, size) + val g = canvas.graphicsContext2D + + val ul = buildUL(dirs) + val ur = buildUR(dirs) + val ll = buildLL(dirs) + val lr = buildLR(dirs) + + canvas.graphicsContext2D.drawImage(ul.croppedImage(), 0, 0) + canvas.graphicsContext2D.drawImage(ur.croppedImage(), size / 2, 0) + canvas.graphicsContext2D.drawImage(ll.croppedImage(), 0, size / 2) + canvas.graphicsContext2D.drawImage(lr.croppedImage(), size / 2, size / 2) + + val wi = new WritableImage(size, size) + val parameters = new SnapshotParameters(){ + fill = Color.Transparent + } + val img = canvas.snapshot(parameters, wi) + StillImg(img, 0, 0, size, size) + } + + private def buildUL(dirs: Set[Direction.Value]) = { + val x = if (dirs(West)) 2 else 0 + val y = if (dirs(North)) 2 else 0 + palette(x, y) + } + + private def buildUR(dirs: Set[Direction.Value]) = { + val x = if (dirs(East)) 1 else 3 + val y = if (dirs(North)) 2 else 0 + palette(x, y) + } + + private def buildLL(dirs: Set[Direction.Value]) = { + val x = if (dirs(West)) 2 else 0 + val y = if (dirs(South)) 1 else 3 + palette(x, y) + } + + private def buildLR(dirs: Set[Direction.Value]) = { + val x = if (dirs(East)) 1 else 3 + val y = if (dirs(South)) 1 else 3 + palette(x, y) + } +} + +class AutoTilePalette(val palette: Palette, val size: Int) { + def this(file: File, size: Int = 48) = this(Palette.bySize(new FileInputStream(file), size, size), size) + + def apply(x: Int, y: Int) = { + val img = palette.apply(x * 2, y * 3, 2, 3).croppedImage() + new AutoFloorTile(new Palette(img, 4, 6), size) + } +} + +import scalafx.Includes._ +import scalafx.application.JFXApp +import scalafx.scene._ +import scalafx.scene.image.ImageView +import scalafx.scene.layout.TilePane +import scalafx.scene.paint.Color + +import Color._ + +object AutoTileDemo extends JFXApp { + final val MaxX = 3 + final val MaxY = 3 + + val file = raw"C:\Users\dalyj\Documents\Design\Images\AutoTiles\tilea2.png" + val palette = new AutoTilePalette(new File(file), 32) + val tile = palette(2, 0) + val imgs = for (y <- 0 to MaxY; x <- 0 to MaxX) yield { + val dirs = (x, y) match { + case (0, 0) => Set(East, South) + case (MaxX, MaxY) => Set(West, North) + case (0, MaxY) => Set(East, North) + case (MaxX, 0) => Set(West, South) + case (0, _) => Set(East, North, South) + case (MaxX, _) => Set(West, North, South) + case (_, 0) => Set(East, West, South) + case (_, MaxY) => Set(East, West, North) + case (_, _) => Set(East, West, North, South) + } + tile.build(dirs) + } + val tiles = imgs.map(i => new ImageView(i.croppedImage()).delegate) + + + val pane = new TilePane(){ + prefColumns = MaxX + 1 + prefRows = MaxY + 1 + } + pane.children ++= tiles + pane.children += new ImageView(tile.icon.croppedImage()) + + stage = new JFXApp.PrimaryStage { + title.value = "Hello Stage" + width = 600 + height = 450 + scene = new Scene { + fill = LightGreen + content = pane + } + } + +} \ No newline at end of file diff --git a/FakeMon/src/fmon/stat/Move.scala b/FakeMon/src/fmon/stat/Move.scala index 383ee28..746a922 100644 --- a/FakeMon/src/fmon/stat/Move.scala +++ b/FakeMon/src/fmon/stat/Move.scala @@ -83,6 +83,7 @@ abstract class Move extends MoveTurn { reader ! Message(s"$user used $name.") if (onTryMove(user, this, target, reader, rng)) { if (attackRoll(user, target)) { + reader ! AnimateMsg(element, target) if (pow > 0 || powCallback != null || damageCallback != null) { val dmg = damageRoll(user, target) val actualDmg = target.takeDamage(dmg) diff --git a/FakeMon/src/fmon/util/Direction.scala b/FakeMon/src/fmon/util/Direction.scala new file mode 100644 index 0000000..ed16ffd --- /dev/null +++ b/FakeMon/src/fmon/util/Direction.scala @@ -0,0 +1,7 @@ +package fmon.util + +object Direction extends Enumeration { + val North, Northeast, East, Southeast, South, Southwest, West, Northwest = Value + + val cardinals = Set(North, East, South, West) +} \ No newline at end of file