package fmon.world.ui 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.scene.input.KeyEvent import javafx.stage.Stage import scalafx.Includes._ import scalafx.animation.AnimationTimer import scalafx.beans.property._ import scalafx.collections.ObservableBuffer import scalafx.geometry.Point2D import scalafx.scene.control._ import scalafx.scene.image.ImageView import scalafx.scene.input.{KeyCode, MouseEvent} import scalafx.scene.paint.Color import scalafx.scene.layout._ import scalafx.stage.FileChooser import scalafx.stage.Modality import scalafx.util.Duration import FileChooser.ExtensionFilter import fmon._ import fmon.draw._ import fmon.util.Direction import fmon.world._ import fmon.util.YamlHelper import Direction._ class GameView { @FXML var scroller: jfxsc.ScrollPane = _ @FXML var background: jfxsl.TilePane = _ @FXML var doodads: jfxsl.TilePane = _ @FXML var characterPane: jfxsl.AnchorPane = _ var hero: Actor = _ var heroImg: AnimatedImageView = _ var currDir: Option[Direction] = None var level: GameMap = _ var lastTime = 0L val timer = AnimationTimer(t => { if (lastTime != 0L) { act(new Duration((t - lastTime) * 0.000001)) } lastTime = t }) def loadLevel(level: GameMap): Unit = { this.level = level background.prefColumns = level.width val tiles = for (y <- 0 until level.height; x <- 0 until level.width) yield { new ImageView(level.smoothed(x, y)).delegate } background.children ++= tiles doodads.prefColumns = level.width val dtiles = for (y <- 0 until level.height; x <- 0 until level.width) yield { new ImageView(level.doodad(x, y).icon.croppedImage()).delegate } doodads.children ++= dtiles val heroFile = Config.homedir + raw"Progena\Resources\characters\Actor1.png" hero = new Actor(CharSet(new File(heroFile), 1), Position(5, 4), new Movement(Direction.Stationary, 0.0)) heroImg = new AnimatedImageView(hero.sprite, new Duration(600)) { x = hero.x * Config.tileSize y = hero.y * Config.tileSize + Config.yOffset } characterPane.children += heroImg timer.start() } def onKeyDown(k: KeyEvent): Unit = { k.code match { case KeyCode.W | KeyCode.Up => setDir(North) case KeyCode.D | KeyCode.Right => setDir(East) case KeyCode.A | KeyCode.Left => setDir(West) case KeyCode.S | KeyCode.Down => setDir(South) case _ => () } } def onKeyUp(k: KeyEvent): Unit = { k.code match { case KeyCode.W | KeyCode.Up => clearDir(North) case KeyCode.D | KeyCode.Right => clearDir(East) case KeyCode.A | KeyCode.Left => clearDir(West) case KeyCode.S | KeyCode.Down => clearDir(South) case _ => () } } def setDir(dir: Direction): Unit = { currDir = Some(dir) heroImg.play() } def clearDir(dir: Direction): Unit = { if (currDir == Some(dir)) { currDir = None heroImg.stop() } } def act(dur: Duration): Unit = { /*currDir match { case Some(North) => slide(hero, 0, -dur.toSeconds() * Config.moveSpeed) case Some(East) => slide(hero, dur.toSeconds() * Config.moveSpeed, 0) case Some(West) => slide(hero, -dur.toSeconds() * Config.moveSpeed, 0) case Some(South) => slide(hero, 0, +dur.toSeconds() * Config.moveSpeed) case _ => () }*/ if (hero.move.completion <= 0 && currDir.isDefined) { val dir = currDir.get if (hero.pose != dir.toString()) { hero.pose = dir.toString() } else { val info = level.compositeInfo(hero.pos + dir) val canPass = (dir.x < 0 && info.doesPassEast) || (dir.x > 0 && info.doesPassWest) || (dir.y < 0 && info.doesPassSouth) || (dir.y > 0 && info.doesPassNorth) if (canPass) { hero.pos += dir hero.move = new Movement(dir, 1.0) } } } hero.slide(dur) heroImg.x = hero.xreal * Config.tileSize heroImg.y = hero.yreal * Config.tileSize + Config.yOffset } /* def slide(actor: Actor, dx: Double, dy: Double): Unit = { // Need to do all corners val px = if (dx < 0) actor.x else actor.x + 1 val py = if (dy < 0) actor.y else actor.y + 1 val tiles = tilesOnLine(new Point2D(px, py), new Point2D(px + dx, py + dy)) val condition = if (dx < 0 && dy < 0) { info:TileInfo => info.doesPassSouth && info.doesPassEast } else if (dx < 0) { info: TileInfo => info.doesPassNorth && info.doesPassEast } else if (dy < 0) { info: TileInfo => info.doesPassSouth && info.doesPassWest } else { info: TileInfo => info.doesPassNorth && info.doesPassWest } val collisions = tiles.filterNot{case (i, _) => condition(level.tileInfo(i))} if (!collisions.isEmpty) { val (i, pt) = collisions.head println(actor.y, pt.y) actor.x += (pt.x - px) actor.y += (pt.y - py) //println(actor.x, actor.y) } else { actor.x += dx actor.y += dy } }*/ def lineIntersectionIndices(p: Point2D, q: Point2D): Seq[Int] = { def p2i(pt: Point2D) = level.pt2index(pt.x.toInt, pt.y.toInt) val dx = q.x - p.x val dy = q.y - p.y val nx = Math.abs(dx).toInt val ny = Math.abs(dy).toInt val sx = Math.signum(dx) val sy = Math.signum(dy) var pts = Seq(p2i(p)) var pt = p var ix = 0 var iy = 0 while (ix < nx || iy < ny) { if ((0.5+ix) * ny < (0.5+iy) * nx) { // next step is horizontal pt = new Point2D(pt.x + sx, pt.y) ix += 1 } else { // next step is vertical pt = new Point2D(pt.x, pt.y + sy) ix += 1 } pts :+= p2i(pt) } pts } def lineIntersectsGrid(p: Point2D, q: Point2D, gi: Int): Point2D = { val gx = level.index2x(gi) val gy = level.index2y(gi) val dx = q.x - p.x val dy = q.y - p.y if (dx > 0) { new Point2D(gx, p.y) } else if (dx < 0) { new Point2D(gx + 1.0, p.y) } else if (dy > 0) { new Point2D(p.x, gy) } else if (dy < 0) { new Point2D(p.x, gy + 1) } else { new Point2D(q.x, q.y) } } def tilesOnLine(p: Point2D, q: Point2D): Seq[(Int, Point2D)] = { val tileIndices = lineIntersectionIndices(p, q) tileIndices.map(i => (i, lineIntersectsGrid(p, q, i))) } def tilesOnLine_(p: Point2D, q: Point2D): Seq[(Int, Point2D)] = { val dx = q.x - p.x val dy = q.y - p.y val nx = Math.abs(dx).toInt val ny = Math.abs(dy).toInt val sx = Math.signum(dx) val sy = Math.signum(dy) var pts = Seq(p) var pt = p var ix = 0 var iy = 0 while (ix < nx || iy < ny) { if ((0.5+ix) * ny < (0.5+iy) * nx) { // next step is horizontal pt = new Point2D(pt.x + sx, pt.y) ix += 1 } else { // next step is vertical pt = new Point2D(pt.x, pt.y + sy) ix += 1 } pts :+= pt } pts.map(pt => (level.pt2index(pt.x.toInt, pt.y.toInt), pt)) } } object GameView { class GameApp extends Application { override def start(primaryStage: Stage): Unit = { val frameLoader = new FXMLLoader(getClass.getResource("GameView.fxml")) val root: Parent = frameLoader.load() val controller = frameLoader.getController[GameView]() val stageFile = Config.homedir + raw"Progena\Data\outside.map" val level = GameMap.loadFrom(new File(stageFile)) controller.loadLevel(level) //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.width = 1280 primaryStage.height = 800 //primaryStage.maximized = true primaryStage.show() } } def main(args: Array[String]): Unit = { Application.launch(classOf[GameApp], args: _*) } }