310 lines
8.8 KiB
Scala
310 lines
8.8 KiB
Scala
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.battle.BattleUI
|
|
import fmon.draw._
|
|
import fmon.script.action.MessageAction
|
|
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 npcs = Seq[NPC]()
|
|
def actors = hero +: npcs
|
|
|
|
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 Hero(CharSet(new File(heroFile), 1), Position(5, 4))
|
|
|
|
heroImg = hero.imgView
|
|
|
|
npcs = (0 until 8).map(i => {
|
|
val event = new GameEvent(s"NPC $i", IndexedSeq(new EventPage(
|
|
CharSet(new File(heroFile), i),
|
|
new MessageAction(s"I am NPC $i")
|
|
)))
|
|
new NPC(event, Position(7 + i, 3))
|
|
})
|
|
|
|
characterPane.children += heroImg
|
|
val npcImgs = npcs.map(npc => npc.imgView)
|
|
npcImgs.foreach(characterPane.children += _)
|
|
|
|
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 KeyCode.Space => activate()
|
|
case KeyCode.BackQuote => beginWildBattle()
|
|
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 beginWildBattle(): Unit = {
|
|
BattleUI.startWildBattle()
|
|
}
|
|
|
|
def activate(): Unit = {
|
|
if (hero.move.direction == Direction.Stationary) {
|
|
val dir = Direction.byName(hero.sprite.pose)
|
|
val np = hero.pos + dir
|
|
npcs.filter(npc => npc.pos == np).foreach(npc => npc.event.currPage.action(npc))
|
|
}
|
|
}
|
|
|
|
def setDir(dir: Direction): Unit = {
|
|
currDir = Some(dir)
|
|
}
|
|
|
|
def clearDir(dir: Direction): Unit = {
|
|
if (currDir == Some(dir)) {
|
|
currDir = None
|
|
}
|
|
}
|
|
|
|
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 dest = hero.pos + dir
|
|
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)
|
|
val unoccupied = !actors.exists(a => a.pos == dest && a != hero)
|
|
|
|
if (canPass && unoccupied) {
|
|
hero.pos += dir
|
|
hero.move = new Movement(dir, 1.0)
|
|
hero.imgView.play()
|
|
}
|
|
}
|
|
}
|
|
hero.slide(dur)
|
|
npcs.foreach(_.slide(dur))
|
|
}
|
|
|
|
/*
|
|
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 scene: Scene = new Scene(root)
|
|
|
|
GameManager.root = primaryStage
|
|
GameManager.currLevel = root
|
|
GameManager.currController = controller
|
|
GameManager.currView = 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: _*)
|
|
}
|
|
} |