Compare commits

...

10 Commits

36 changed files with 1478 additions and 81 deletions

View File

@@ -69,10 +69,7 @@ class AnimationBuilder extends Savable {
fileChooser.items = imgFiles
fileChooser.selectionModel().selectedIndex.onChange(selectImageFile)
fileChooser.converter = new StringConverter[File] {
override def toString(f: File) = f.getName
override def fromString(s: String) = new File(s)
}
fileChooser.converter = FilenameConverter
val dir = new File(imgdir)
imgFiles ++= dir.listFiles()

View File

@@ -0,0 +1,9 @@
package fmon.builder
import java.io.File
import scalafx.util.StringConverter
object FilenameConverter extends StringConverter[File] {
override def toString(f: File) = if (f != null) f.getName else "<none>"
override def fromString(s: String) = if (s == "<none>") null else new File(s)
}

View File

@@ -18,7 +18,7 @@
<?import javafx.scene.paint.Color?>
<?import javafx.scene.text.Font?>
<VBox prefHeight="600.0" prefWidth="900.0" xmlns="http://javafx.com/javafx/11.0.1" xmlns:fx="http://javafx.com/fxml/1" fx:controller="fmon.builder.MapBuilder">
<VBox prefHeight="600.0" prefWidth="900.0" xmlns="http://javafx.com/javafx/8.0.171" xmlns:fx="http://javafx.com/fxml/1" fx:controller="fmon.builder.MapBuilder">
<children>
<MenuBar VBox.vgrow="NEVER">
<menus>
@@ -50,6 +50,8 @@
<SeparatorMenuItem mnemonicParsing="false" />
<MenuItem mnemonicParsing="false" text="Select All" />
<MenuItem mnemonicParsing="false" text="Unselect All" />
<SeparatorMenuItem mnemonicParsing="false" />
<MenuItem mnemonicParsing="false" onAction="#onPropertiesDialog" text="Properties ..." />
</items>
</Menu>
<Menu mnemonicParsing="false" text="Help">
@@ -65,20 +67,28 @@
<tabs>
<Tab closable="false" text="A">
<content>
<AnchorPane>
<children>
<TilePane fx:id="tileSelector" hgap="2.0" prefColumns="8" vgap="2.0" />
</children>
</AnchorPane>
<ScrollPane>
<content>
<AnchorPane>
<children>
<TilePane fx:id="tileSelector" hgap="2.0" prefColumns="8" vgap="2.0" />
</children>
</AnchorPane>
</content>
</ScrollPane>
</content>
</Tab>
<Tab closable="false" text="B">
<content>
<AnchorPane>
<children>
<TilePane fx:id="doodadSelector" hgap="2.0" prefColumns="8" vgap="2.0" />
</children>
</AnchorPane>
<ScrollPane>
<content>
<AnchorPane>
<children>
<TilePane fx:id="doodadSelector" hgap="2.0" prefColumns="8" vgap="2.0" />
</children>
</AnchorPane>
</content>
</ScrollPane>
</content>
</Tab>
</tabs>
@@ -88,6 +98,7 @@
<AnchorPane id="Content" minHeight="-1.0" minWidth="-1.0" prefHeight="545.0" prefWidth="430.0">
<children>
<TilePane fx:id="gameMap" hgap="1.0" layoutX="14.0" layoutY="28.0" prefColumns="10" prefRows="10" vgap="1.0" AnchorPane.leftAnchor="0.0" AnchorPane.topAnchor="0.0" />
<TilePane fx:id="doodadMap" hgap="1.0" vgap="1.0" AnchorPane.leftAnchor="0.0" AnchorPane.topAnchor="0.0" />
</children>
</AnchorPane>
</content>

View File

@@ -21,27 +21,42 @@ import scalafx.scene.input.MouseEvent
import scalafx.scene.paint.Color
import scalafx.scene.layout._
import scalafx.stage.FileChooser
import scalafx.stage.Modality
import FileChooser.ExtensionFilter
import fmon._
import fmon.builder.dialog.MapProperties
import fmon.draw.Palette
import fmon.draw.tile._
import fmon.util.Direction
import fmon.util.{Direction, YamlHelper}
import fmon.world._
import Direction._
import Alert.AlertType
import scala.math.{min, max}
sealed trait MapBuilderState
case object GroundTile extends MapBuilderState
case object DoodadTile extends MapBuilderState
class MapBuilder {
@FXML var tileSelector: jfxsl.TilePane = _
@FXML var doodadSelector: jfxsl.TilePane = _
@FXML var gameMap: jfxsl.TilePane = _
@FXML var doodadMap: jfxsl.TilePane = _
var imgSeq: IndexedSeq[ImageView] = _
var doodadSeq: IndexedSeq[ImageView] = _
val tilesets = {
val dir = new File(Config.homedir + raw"Progena\Data")
YamlHelper.extractSeq[TilesetToken](new FileInputStream(new File(dir, "Tilesets.yaml"))).map(t => (t.name, t)).toMap
}
var tileset: Tileset = _
var currTile: AutoTile = _
var currState: MapBuilderState = GroundTile
var level: GameMap = _
@@ -62,7 +77,8 @@ class MapBuilder {
@FXML
def initialize(): Unit = {
tileset = new Tileset(new TilesetToken(raw"C:\Users\dalyj\Documents\Design\Images\AutoTiles\tilea2.png"))
val resourceDir = new File(Config.homedir + raw"Progena\Resources\tilesets")
tileset = tilesets("Outside").load(resourceDir)
level = new GameMap(10, 10, tileset)
setup()
}
@@ -74,6 +90,7 @@ class MapBuilder {
view.handleEvent(MouseEvent.Any) {
e: MouseEvent => if (e.eventType == MouseEvent.MouseClicked) {
currTile = t
currState = GroundTile
}
}
view.delegate
@@ -82,31 +99,52 @@ class MapBuilder {
tileSelector.children ++= icons
currTile = tileset.groundTiles.head
val doodadFile = new File(raw"C:\Users\dalyj\Documents\Design\Images\Icons\equipment-icons-tileset.png")
val doodadPalette = Palette.bySize(doodadFile, Config.tileSize, Config.tileSize)
val doodads = doodadPalette.images.map(d => {
val view = new ImageView(d.croppedImage())
val doodads = tileset.doodadTiles.map(d => {
val view = new ImageView(d.icon.croppedImage())
view.handleEvent(MouseEvent.Any) {
e: MouseEvent => if (e.eventType == MouseEvent.MouseClicked) {
currTile = d
currState = DoodadTile
}
}
view.delegate
})
doodadSelector.children.clear()
doodadSelector.children ++= doodads
imgSeq = for (y <- 0 until level.height; x <- 0 until level.width) yield {
val view = new ImageView(currTile.icon.croppedImage()) // TODO Tile with adjacent
val view = new ImageView(level(x, y).icon.croppedImage()) // TODO Tile with adjacent
view.handleEvent(MouseEvent.Any) {
e: MouseEvent => if (e.eventType == MouseEvent.MouseClicked) {
val index = point2index(x, y)
level.tiles = level.tiles.updated(index, currTile)
autotile(x, y)
currState match {
case GroundTile => {
level.tiles = level.tiles.updated(index, currTile)
autotile(x, y)
}
case DoodadTile => {
level.doodads = level.doodads.updated(index, currTile)
doodadSeq(index).image = level.doodads(index).icon.croppedImage()
}
}
}
}
view
}
doodadSeq = for (y <- 0 until level.height; x <- 0 until level.width) yield {
val view = new ImageView(level.doodad(x, y).icon.croppedImage())
view
}
gameMap.children.clear()
gameMap.prefColumns = level.width
gameMap.prefRows = level.height
gameMap.children ++= imgSeq.map(_.delegate)
doodadMap.children.clear()
doodadMap.prefColumns = level.width
doodadMap.prefRows = level.height
doodadMap.children ++= doodadSeq.map(_.delegate)
doodadMap.mouseTransparent = true
for (y <- 0 until level.height; x <- 0 until level.width) {
smoothTile(x, y)
@@ -121,24 +159,39 @@ class MapBuilder {
}
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 == level.width
val onTop = y == 0
val onBottom = y + 1 == level.height
if (onLeft || level.tiles(left(i)) == level.tiles(i)) dirs += West
if (onRight || level.tiles(right(i)) == level.tiles(i)) dirs += East
if (onTop || level.tiles(up(i)) == level.tiles(i)) dirs += North
if (onBottom || level.tiles(down(i)) == level.tiles(i)) dirs += South
if (onLeft || onTop || level.tiles(left(up(i))) == level.tiles(i)) dirs += Northwest
if (onLeft || onBottom || level.tiles(down(left(i))) == level.tiles(i)) dirs += Southwest
if (onTop || onRight || level.tiles(right(up(i))) == level.tiles(i)) dirs += Northeast
if (onBottom || onRight || level.tiles(right(down(i))) == level.tiles(i)) dirs += Southeast
val index = point2index(x, y)
imgSeq(index).image = level.tiles(index).build(dirs.toSet).croppedImage()
imgSeq(level.pt2index(x, y)).image = level.smoothed(x, y)
}
@FXML
def onPropertiesDialog: Unit = {
val fxmlLoader = new FXMLLoader(getClass().getResource("dialog/MapProperties.fxml"))
val parent: Parent = fxmlLoader.load()
val dialogController = fxmlLoader.getController[MapProperties]();
//dialogController.setAppMainObservableList(tvObservableList);
dialogController.init(level)
val dialog = new Alert(AlertType.Confirmation) {
title = "Map Properties"
dialogPane().content = parent
headerText = ""
graphic = null
}
val result = dialog.showAndWait()
println(result)
result match {
case Some(ButtonType.OK) => {
println(dialogController.width, dialogController.height)
level = new GameMap(dialogController.width, dialogController.height, tileset)
level.tiles = level.tiles.map(_ => currTile)
setup()
}
case _ => ()
}
/*val scene = new Scene(parent, 300, 200);
val stage = new Stage();
stage.initModality(Modality.ApplicationModal);
stage.setScene(scene);
stage.showAndWait();*/
}
def save() = {
@@ -211,6 +264,8 @@ object MapBuilder {
val scene: Scene = new Scene(root)
primaryStage.setScene(scene)
primaryStage.width = 1280
primaryStage.height = 800
primaryStage.show()
}
}

View File

@@ -0,0 +1,92 @@
<?xml version="1.0" encoding="UTF-8"?>
<?import javafx.geometry.Insets?>
<?import javafx.scene.control.Button?>
<?import javafx.scene.control.CheckBox?>
<?import javafx.scene.control.ComboBox?>
<?import javafx.scene.control.Label?>
<?import javafx.scene.control.ListView?>
<?import javafx.scene.control.ScrollPane?>
<?import javafx.scene.control.SplitPane?>
<?import javafx.scene.control.TextField?>
<?import javafx.scene.layout.ColumnConstraints?>
<?import javafx.scene.layout.GridPane?>
<?import javafx.scene.layout.RowConstraints?>
<?import javafx.scene.layout.TilePane?>
<?import javafx.scene.layout.VBox?>
<SplitPane dividerPositions="0.3110367892976589, 0.5953177257525084" maxHeight="-Infinity" maxWidth="-Infinity" minHeight="-Infinity" minWidth="-Infinity" prefHeight="400.0" prefWidth="600.0" xmlns="http://javafx.com/javafx/8.0.171" xmlns:fx="http://javafx.com/fxml/1" fx:controller="fmon.builder.TilesetBuilder">
<items>
<VBox>
<children>
<ListView fx:id="tilesetList" />
<Button mnemonicParsing="false" onAction="#addTileset" text="+" />
</children>
</VBox>
<GridPane hgap="2.0" vgap="2.0">
<columnConstraints>
<ColumnConstraints hgrow="SOMETIMES" maxWidth="72.0" minWidth="10.0" prefWidth="54.0" />
<ColumnConstraints hgrow="SOMETIMES" maxWidth="105.0" minWidth="10.0" prefWidth="105.0" />
</columnConstraints>
<rowConstraints>
<RowConstraints minHeight="10.0" prefHeight="30.0" vgrow="SOMETIMES" />
<RowConstraints minHeight="10.0" prefHeight="30.0" vgrow="SOMETIMES" />
<RowConstraints minHeight="10.0" prefHeight="30.0" vgrow="SOMETIMES" />
<RowConstraints minHeight="10.0" prefHeight="30.0" vgrow="SOMETIMES" />
<RowConstraints minHeight="10.0" prefHeight="30.0" vgrow="SOMETIMES" />
<RowConstraints minHeight="10.0" prefHeight="30.0" vgrow="SOMETIMES" />
<RowConstraints minHeight="10.0" prefHeight="30.0" vgrow="SOMETIMES" />
<RowConstraints minHeight="10.0" prefHeight="30.0" vgrow="SOMETIMES" />
</rowConstraints>
<children>
<ComboBox fx:id="a1Chooser" onAction="#setA1" prefWidth="150.0" GridPane.columnIndex="1" GridPane.rowIndex="1" />
<Label text="Tiles A1" GridPane.rowIndex="1" />
<Label text="Tiles A2" GridPane.rowIndex="2" />
<Label text="Tiles A3" GridPane.rowIndex="3" />
<Label text="Tiles A4" GridPane.rowIndex="4" />
<Label text="Tiles A5" GridPane.rowIndex="5" />
<Label text="Tiles B" GridPane.rowIndex="6" />
<Label text="Tiles C" GridPane.rowIndex="7" />
<ComboBox fx:id="a2Chooser" onAction="#setA2" prefWidth="150.0" GridPane.columnIndex="1" GridPane.rowIndex="2" />
<ComboBox fx:id="a3Chooser" onAction="#setA3" prefWidth="150.0" GridPane.columnIndex="1" GridPane.rowIndex="3" />
<ComboBox fx:id="a4Chooser" onAction="#setA4" prefWidth="150.0" GridPane.columnIndex="1" GridPane.rowIndex="4" />
<ComboBox fx:id="a5Chooser" onAction="#setA5" prefWidth="150.0" GridPane.columnIndex="1" GridPane.rowIndex="5" />
<ComboBox fx:id="bChooser" onAction="#setB" prefWidth="150.0" GridPane.columnIndex="1" GridPane.rowIndex="6" />
<ComboBox fx:id="cChooser" onAction="#setC" prefWidth="150.0" GridPane.columnIndex="1" GridPane.rowIndex="7" />
<Label text="Name" />
<TextField fx:id="nameField" onAction="#setName" promptText="Tileset Name" GridPane.columnIndex="1" />
</children>
<padding>
<Insets bottom="2.0" left="2.0" right="2.0" top="2.0" />
</padding>
</GridPane>
<SplitPane dividerPositions="0.5" orientation="VERTICAL" prefWidth="160.0">
<items>
<ScrollPane>
<content>
<TilePane fx:id="tileView" hgap="1.0" prefColumns="8" vgap="1.0">
<padding>
<Insets bottom="1.0" left="1.0" right="1.0" top="1.0" />
</padding></TilePane>
</content>
</ScrollPane>
<ScrollPane>
<content>
<VBox spacing="2.0">
<children>
<CheckBox fx:id="doesPassNorth" mnemonicParsing="false" onAction="#updateTile" text="Pass North" />
<CheckBox fx:id="doesPassEast" mnemonicParsing="false" onAction="#updateTile" text="Pass East" />
<CheckBox fx:id="doesPassSouth" mnemonicParsing="false" onAction="#updateTile" text="Pass South" />
<CheckBox fx:id="doesPassWest" mnemonicParsing="false" onAction="#updateTile" text="Pass West" />
<CheckBox fx:id="isCounter" mnemonicParsing="false" onAction="#updateTile" text="Talk Throuh (Counter)" />
</children>
<padding>
<Insets bottom="2.0" left="2.0" right="2.0" top="2.0" />
</padding>
</VBox>
</content>
</ScrollPane>
</items>
</SplitPane>
</items>
</SplitPane>

View File

@@ -0,0 +1,323 @@
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.Config
import fmon.draw.tile.{AutoTile, AutoTilePalette}
import fmon.util.YamlHelper
import fmon.world.{Tileset, TileInfo, TilesetToken}
class ObsTileset(name_ : String) {
val name = new StringProperty(this, "name", name_)
val a1 = new ObjectProperty[File](this, "a1", null)
val a2 = new ObjectProperty[File](this, "a2", null)
val a3 = new ObjectProperty[File](this, "a3", null)
val a4 = new ObjectProperty[File](this, "a4", null)
val a5 = new ObjectProperty[File](this, "a5", null)
val b = new ObjectProperty[File](this, "b", null)
val c = new ObjectProperty[File](this, "c", null)
val a1Size = new IntegerProperty(this, "a1size", 0)
val a2Size = new IntegerProperty(this, "a2size", 0)
val a3Size = new IntegerProperty(this, "a3size", 0)
val a4Size = new IntegerProperty(this, "a4size", 0)
val a5Size = new IntegerProperty(this, "a5size", 0)
val bSize = new IntegerProperty(this, "bsize", 0)
val cSize = new IntegerProperty(this, "csize", 0)
val tileInfo = ObservableBuffer[TileInfo]()
def toToken = {
def nm(file: File) = if (file != null) file.getName else null
println(tileInfo.size)
TilesetToken(name(), nm(a1()), nm(a2()), nm(a3()), nm(a4()), nm(a5()), nm(b()), nm(c()), IndexedSeq(tileInfo: _*))
}
override def toString = name()
}
class TilesetBuilder extends Savable {
@FXML var tilesetList: jfxsc.ListView[ObsTileset] = _
@FXML var nameField: jfxsc.TextField = _
@FXML var a1Chooser: jfxsc.ComboBox[File] = _
@FXML var a2Chooser: jfxsc.ComboBox[File] = _
@FXML var a3Chooser: jfxsc.ComboBox[File] = _
@FXML var a4Chooser: jfxsc.ComboBox[File] = _
@FXML var a5Chooser: jfxsc.ComboBox[File] = _
@FXML var bChooser: jfxsc.ComboBox[File] = _
@FXML var cChooser: jfxsc.ComboBox[File] = _
@FXML var tileView: jfxsl.TilePane = _
@FXML var doesPassNorth: jfxsc.CheckBox = _
@FXML var doesPassEast: jfxsc.CheckBox = _
@FXML var doesPassSouth: jfxsc.CheckBox = _
@FXML var doesPassWest: jfxsc.CheckBox = _
@FXML var isCounter: jfxsc.CheckBox = _
val tilesets = ObservableBuffer[ObsTileset]()
var a1Palette = IndexedSeq[AutoTile]()
var a2Palette = IndexedSeq[AutoTile]()
var a3Palette = IndexedSeq[AutoTile]()
var a4Palette = IndexedSeq[AutoTile]()
var a5Palette = IndexedSeq[AutoTile]()
var bPalette = IndexedSeq[AutoTile]()
var cPalette = IndexedSeq[AutoTile]()
val resourceDir = new File(Config.homedir + raw"Progena\Resources\tilesets")
val files = resourceDir.listFiles()
val filenames = files.map(f => (f.getName(), f)).toMap
var currTileIndex = 0
def currTileset = getSelected(tilesetList)
@FXML
def initialize(): Unit = {
tilesetList.items = tilesets
tilesetList.selectionModel().selectedIndex.onChange(selectionChange)
a1Chooser.converter = FilenameConverter
a2Chooser.converter = FilenameConverter
a3Chooser.converter = FilenameConverter
a4Chooser.converter = FilenameConverter
a5Chooser.converter = FilenameConverter
bChooser.converter = FilenameConverter
cChooser.converter = FilenameConverter
a1Chooser.items() ++= null +: files.filter(f => f.getName.endsWith("A1.png"))
a2Chooser.items() ++= null +: files.filter(f => f.getName.endsWith("A2.png"))
a3Chooser.items() ++= null +: files.filter(f => f.getName.endsWith("A3.png"))
a4Chooser.items() ++= null +: files.filter(f => f.getName.endsWith("A4.png"))
a5Chooser.items() ++= null +: files.filter(f => f.getName.endsWith("A5.png"))
bChooser.items() ++= null +: files.filter(f => f.getName.endsWith("B.png"))
cChooser.items() ++= null +: files.filter(f => f.getName.endsWith("C.png"))
println(files.size)
}
def addTileset(): Unit = {
tilesets += new ObsTileset("Tileset")
tilesetList.selectionModel().selectLast()
}
def selectionChange(): Unit = {
val ts = getSelected(tilesetList)
nameField.text = ts.name()
def safeSelect(box: ComboBox[File], file: File) = {
box.selectionModel().clearSelection()
box.selectionModel().select(file)
}
safeSelect(a1Chooser, ts.a1())
safeSelect(a2Chooser, ts.a2())
safeSelect(a3Chooser, ts.a3())
safeSelect(a4Chooser, ts.a4())
safeSelect(a5Chooser, ts.a5())
safeSelect(bChooser, ts.b())
safeSelect(cChooser, ts.c())
}
def setName(): Unit = {
getSelected(tilesetList).name.value = nameField.text()
tilesetList.refresh()
}
def setA1(): Unit = {
currTileset.a1.value = getSelected(a1Chooser)
a1Palette = if (getSelected(a1Chooser) == null) IndexedSeq() else AutoTilePalette.a2(getSelected(a1Chooser), Config.tileSize).tiles
if (a1Palette.size != currTileset.a1Size()) {
currTileset.tileInfo.removeRange(0, currTileset.a1Size())
currTileset.a1Size() = a1Palette.size
val info = a1Palette.map(_ => noPassInfo)
currTileset.tileInfo.insert(0, info : _*)
}
updateTileView()
}
def setA2(): Unit = {
currTileset.a2.value = getSelected(a2Chooser)
a2Palette = if (getSelected(a2Chooser) == null) IndexedSeq() else AutoTilePalette.a2(getSelected(a2Chooser), Config.tileSize).tiles
if (a2Palette.size != currTileset.a2Size()) {
val priorSize = currTileset.a1Size()
currTileset.tileInfo.removeRange(priorSize, priorSize + currTileset.a2Size())
currTileset.a2Size() = a1Palette.size
val info = a2Palette.map(_ => allPassInfo)
currTileset.tileInfo.insert(priorSize, info : _*)
}
updateTileView()
}
def setA3(): Unit = {
currTileset.a3.value = getSelected(a3Chooser)
a3Palette = if (getSelected(a3Chooser) == null) IndexedSeq() else AutoTilePalette.a3(getSelected(a3Chooser), Config.tileSize).tiles
if (a3Palette.size != currTileset.a3Size()) {
val priorSize = currTileset.a1Size() + currTileset.a2Size()
currTileset.tileInfo.removeRange(priorSize, priorSize + currTileset.a3Size())
currTileset.a3Size() = a1Palette.size
val info = a3Palette.map(_ => noPassInfo)
currTileset.tileInfo.insert(priorSize, info : _*)
}
updateTileView()
}
def setA4(): Unit = {
currTileset.a4.value = getSelected(a4Chooser)
a4Palette = if (getSelected(a4Chooser) == null) IndexedSeq() else AutoTilePalette.a4(getSelected(a4Chooser), Config.tileSize).tiles
if (a4Palette.size != currTileset.a4Size()) {
val priorSize = currTileset.a1Size() + currTileset.a2Size() + currTileset.a3Size()
currTileset.tileInfo.removeRange(priorSize, priorSize + currTileset.a4Size())
currTileset.a4Size() = a4Palette.size
val info = a4Palette.zipWithIndex.map{case (_, i) => if ((i / 8) % 2 == 0) allPassInfo else noPassInfo}
currTileset.tileInfo.insert(priorSize, info : _*)
}
updateTileView()
}
def setA5(): Unit = {
currTileset.a5.value = getSelected(a5Chooser)
a5Palette = if (getSelected(a5Chooser) == null) IndexedSeq() else AutoTilePalette.basic(getSelected(a5Chooser), Config.tileSize).tiles
if (a5Palette.size != currTileset.a5Size()) {
val priorSize = currTileset.a1Size() + currTileset.a2Size() + currTileset.a3Size() + currTileset.a4Size()
currTileset.tileInfo.removeRange(priorSize, priorSize + currTileset.a5Size())
currTileset.a5Size() = a5Palette.size
val info = a5Palette.map(_ => allPassInfo)
currTileset.tileInfo.insert(priorSize, info : _*)
}
updateTileView()
}
def setB(): Unit = {
currTileset.b.value = getSelected(bChooser)
bPalette = if (getSelected(bChooser) == null) IndexedSeq() else AutoTilePalette.partitioned(getSelected(bChooser), 2, Config.tileSize).tiles
if (bPalette.size != currTileset.bSize()) {
val priorSize = currTileset.a1Size() + currTileset.a2Size() + currTileset.a3Size() + currTileset.a4Size() + currTileset.a5Size()
currTileset.tileInfo.removeRange(priorSize, priorSize + currTileset.bSize())
currTileset.bSize() = bPalette.size
val info = bPalette.zipWithIndex.map{case (_, i) => if (i != 0) doodadInfo else allPassInfo}
currTileset.tileInfo.insert(priorSize, info : _*)
}
updateTileView()
}
def setC(): Unit = {
currTileset.c.value = getSelected(cChooser)
cPalette = if (getSelected(cChooser) == null) IndexedSeq() else AutoTilePalette.partitioned(getSelected(cChooser), 2, Config.tileSize).tiles
if (cPalette.size != currTileset.cSize()) {
val priorSize = currTileset.a1Size() + currTileset.a2Size() + currTileset.a3Size() + currTileset.a4Size() + currTileset.a5Size() + currTileset.bSize()
currTileset.tileInfo.removeRange(priorSize, priorSize + currTileset.cSize())
currTileset.cSize() = cPalette.size
val info = cPalette.map(_ => doodadInfo)
currTileset.tileInfo.insert(priorSize, info : _*)
}
updateTileView()
}
def updateTileView(): Unit = {
tileView.children.clear()
val tiles = a1Palette ++ a2Palette ++ a3Palette ++ a4Palette ++ a5Palette ++ bPalette ++ cPalette
tileView.children ++= tiles.zipWithIndex.map{case (t, i) => new ImageView(t.icon.croppedImage()){
handleEvent(MouseEvent.Any) {
e: MouseEvent => if (e.eventType == MouseEvent.MouseClicked) {
currTileIndex = i
setTile(currTileset.tileInfo(i))
}
}
}.delegate}
}
def setTile(info: TileInfo): Unit = {
doesPassNorth.selected = info.doesPassNorth
doesPassEast.selected = info.doesPassEast
doesPassSouth.selected = info.doesPassSouth
doesPassWest.selected = info.doesPassWest
isCounter.selected = info.isCounter
}
def updateTile(): Unit = {
val northFlag = if (doesPassNorth.selected()) TileInfo.PassNorth else TileInfo.NoPass
val eastFlag = if (doesPassEast.selected()) TileInfo.PassEast else TileInfo.NoPass
val southFlag = if (doesPassSouth.selected()) TileInfo.PassSouth else TileInfo.NoPass
val westFlag = if (doesPassWest.selected()) TileInfo.PassWest else TileInfo.NoPass
val counterFlag = if (isCounter.selected()) TileInfo.IsCounter else TileInfo.NoPass
val overwriteFlag = currTileset.tileInfo(currTileIndex).flags & TileInfo.Overwrite
val flags = northFlag | eastFlag | southFlag | westFlag | counterFlag | overwriteFlag
currTileset.tileInfo(currTileIndex) = TileInfo(flags)
}
def saveTo(file: File): Unit = {
val tileSeq = tilesets.map(a => a.toToken)
YamlHelper.writeSeq(new FileOutputStream(file), tileSeq)
}
def openFrom(file: File): Unit = {
def f(name: String) = if (name != null) new File(resourceDir, name) else null
val tokens = YamlHelper.extractSeq[TilesetToken](new FileInputStream(file))
tilesets.clear()
tilesets ++= tokens.map(t=> {
val ts = new ObsTileset(t.name)
ts.a1.value = f(t.a1)
ts.a2.value = f(t.a2)
ts.a3.value = f(t.a3)
ts.a4.value = f(t.a4)
ts.a5.value = f(t.a5)
ts.b.value = f(t.b)
ts.c.value = f(t.c)
// TODO : Individual sizes
ts.tileInfo ++= t.info
ts
})
tilesetList.refresh()
}
private def allPassInfo = TileInfo(TileInfo.PassAll)
private def noPassInfo = TileInfo(TileInfo.NoPass)
private def doodadInfo = TileInfo(TileInfo.NoPass | TileInfo.Overwrite)
}
object TilesetBuilder {
class TBA extends Application {
override def start(primaryStage: Stage): Unit = {
val frameLoader = new FXMLLoader(getClass.getResource("App.fxml"))
val root: Parent = frameLoader.load()
val controller = frameLoader.getController[App]()
val builderLoader = new FXMLLoader(getClass.getResource("TilesetBuilder.fxml"))
val builder: Parent = builderLoader.load()
val builderController = builderLoader.getController[TilesetBuilder]()
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[TBA], args: _*)
}
}

View File

@@ -0,0 +1,28 @@
<?xml version="1.0" encoding="UTF-8"?>
<?import javafx.scene.control.ComboBox?>
<?import javafx.scene.control.Label?>
<?import javafx.scene.control.Spinner?>
<?import javafx.scene.layout.ColumnConstraints?>
<?import javafx.scene.layout.GridPane?>
<?import javafx.scene.layout.RowConstraints?>
<GridPane maxHeight="-Infinity" maxWidth="-Infinity" minHeight="-Infinity" minWidth="-Infinity" xmlns="http://javafx.com/javafx/8.0.171" xmlns:fx="http://javafx.com/fxml/1" fx:controller="fmon.builder.dialog.MapProperties">
<columnConstraints>
<ColumnConstraints hgrow="SOMETIMES" maxWidth="294.0" minWidth="10.0" prefWidth="74.0" />
<ColumnConstraints hgrow="SOMETIMES" minWidth="10.0" />
</columnConstraints>
<rowConstraints>
<RowConstraints minHeight="10.0" prefHeight="30.0" vgrow="SOMETIMES" />
<RowConstraints minHeight="10.0" prefHeight="30.0" vgrow="SOMETIMES" />
<RowConstraints minHeight="10.0" prefHeight="30.0" vgrow="SOMETIMES" />
</rowConstraints>
<children>
<Label text="Width" />
<Label text="Height" GridPane.rowIndex="1" />
<Label text="Tileset" GridPane.rowIndex="2" />
<Spinner fx:id="widthSpin" editable="true" initialValue="10" max="100" min="10" GridPane.columnIndex="1" />
<Spinner fx:id="heightSpin" editable="true" initialValue="10" max="100" min="10" GridPane.columnIndex="1" GridPane.rowIndex="1" />
<ComboBox fx:id="tilesetChooser" prefWidth="150.0" GridPane.columnIndex="1" GridPane.rowIndex="2" />
</children>
</GridPane>

View File

@@ -0,0 +1,26 @@
package fmon.builder.dialog
import javafx.fxml.FXML
import javafx.scene.{control => jfxsc, layout => jfxsl, Parent, Scene}
import scalafx.Includes._
import scalafx.scene.control.SpinnerValueFactory
import fmon.world.GameMap
class MapProperties {
@FXML var widthSpin: jfxsc.Spinner[Int] = _
@FXML var heightSpin: jfxsc.Spinner[Int] = _
@FXML def initialize(): Unit = {
println(widthSpin)
}
def init(level: GameMap): Unit = {
widthSpin.valueFactory().value = level.width
heightSpin.valueFactory().value = level.height
}
def width = widthSpin.value()
def height = heightSpin.value()
}

View File

@@ -0,0 +1,14 @@
package fmon
import scalafx.Includes._
import scalafx.scene.control._
package object builder {
def getSelected[T](view: ListView[T]): T = {
view.selectionModel().selectedItem()
}
def getSelected[T](view: ComboBox[T]): T = {
view.selectionModel().selectedItem()
}
}

View File

@@ -10,7 +10,9 @@ case class Config(
val weakMult: Double,
val immuneMult: Double,
val maxBoost: Int,
val tileSize: Int
val tileSize: Int,
val moveSpeed: Double,
val yOffset: Int
) {
}
@@ -26,4 +28,8 @@ object Config {
def immuneMult = config.immuneMult
def maxBoost = config.maxBoost
def tileSize = config.tileSize
def moveSpeed = config.moveSpeed
def yOffset = config.yOffset
def homedir = raw"C:\Users\dalyj\Documents\Design\Project\"
}

View File

@@ -20,6 +20,7 @@ object Game {
class Game extends Application {
override def start(primaryStage: Stage): Unit = {
/*
val url = getClass.getResource("battle/battle.fxml")
val loader = new FXMLLoader(url)
val root: Parent = loader.load()
@@ -43,5 +44,6 @@ class Game extends Application {
primaryStage.setTitle(Config.title)
primaryStage.setScene(scene)
primaryStage.show()
*/
}
}

View File

@@ -0,0 +1,31 @@
package fmon
import scalafx.scene.Scene
import scalafx.Includes._
import scalafx.scene.Node
import scalafx.scene.Parent
import scalafx.stage.Stage
import fmon.world.ui.GameView
object GameManager {
var currLevel: Parent = _
var currController: GameView = _
var currView: Parent = _
var root: Stage = _
def setView(view: Parent): Unit = {
currController.timer.stop()
currView = view
root.scene().root = view
}
def restoreMap(): Unit = {
currView = currLevel
root.scene().root = currView
currController.timer.start()
}
}

View File

@@ -2,6 +2,7 @@ package fmon.battle
import scala.util.Random
import fmon._
import fmon.battle.msg._
import fmon.stat._
import fmon.stat.Statistic._
@@ -39,10 +40,11 @@ class BattleEngine(val player: Party, val enemy: Party)(implicit val reader: Sig
if (!player.lead.isAlive) {
if (player.canFight) {
val replace = player.pollReplacement(enemy)
player.switchIn(replace)
val replace = player.pollReplacement(enemy)
player.switchIn(replace)
} else {
this ! Message(s"${player.trainer} has lost!")
GameManager.restoreMap()
}
}
if (!enemy.lead.isAlive) {
@@ -51,6 +53,7 @@ class BattleEngine(val player: Party, val enemy: Party)(implicit val reader: Sig
enemy.switchIn(replace)
} else {
this ! Message(s"${enemy.trainer} has lost!")
GameManager.restoreMap()
}
}
}

View File

@@ -3,12 +3,16 @@ package fmon.battle
import java.io._
import java.util.concurrent.locks.{Condition, ReentrantLock}
import scala.util.Random
import javafx.application.Application
import javafx.application.Platform
import javafx.fxml.FXML
import javafx.fxml.FXMLLoader
import javafx.fxml.JavaFXBuilderFactory
import javafx.scene.{control => jfxsc}
import javafx.scene.image.ImageView
import javafx.stage.Stage
import scalafx.Includes._
import scalafx.animation._
@@ -23,6 +27,7 @@ import scalafx.scene.text.Font
import scalafx.scene.transform.Rotate
import scalafx.util.Duration
import fmon.GameManager
import fmon.battle.msg._
import fmon.draw._
import fmon.stat._
@@ -46,7 +51,24 @@ class BattleUI extends SignalConsumer {
private val lock = new ReentrantLock()
private val finishedPlaying = lock.newCondition()
def beginWildBattle(player: Party, wild: Monster)(implicit rng: Random) {
implicit val consumer = this
setEngine(new BattleEngine(
player,
new Party(BattleUI.WildTrainer, new MonsterPtr(wild), IndexedSeq())
))
this ! Message(s"A wild ${wild.name} appeared!")
}
def beginTrainerBattle(player: Party, trainer: Party)(implicit rng: Random) {
implicit val consumer = this
setEngine(new BattleEngine(player, trainer))
this ! Message(s"${trainer.trainer} wants to fight!")
this ! Message(s"${trainer.trainer} sent out ${trainer.lead}")
}
def setEngine(engine: BattleEngine): Unit = {
messages.clear()
this.engine = engine
updateUI()
}
@@ -204,4 +226,62 @@ class BattleUI extends SignalConsumer {
controller.setup(move, onMove)
button
}
}
import javafx.scene.Scene
import fmon.Config
import fmon.stat.Gender
object BattleUI {
final val WildTrainer = TrainerID("Wild", Gender.Neuter, 0)
def startWildBattle(): Unit = {
val url = getClass.getResource("battle.fxml")
val loader = new FXMLLoader(url)
val root: javafx.scene.Parent = loader.load()
implicit val controller = loader.getController[BattleUI]()
implicit val rng = new scala.util.Random()
val form1 = Form("Diabolo")
val form2 = Form("Chanilla")
val movepool1 = IndexedSeq(Move("eruption"), Move("willowisp"), Move("thunderbolt"), Move("thunderwave"), Move("thundershock"))
val movepool2 = IndexedSeq(Move("tackle"))
val p1 = TrainerID("Jaeda", Gender.Female, 0)
val party1 = new Party(p1, new MonsterPtr(Monster.generate("Allied Mon", p1, 500, form1, movepool1)), IndexedSeq())
val monster = Monster.generate(null, WildTrainer, 500, form2, movepool2)
controller.beginWildBattle(party1, monster)
GameManager.setView(root)
}
class GameApp extends Application {
override def start(primaryStage: Stage): Unit = {
val url = getClass.getResource("battle.fxml")
val loader = new FXMLLoader(url)
val root: javafx.scene.Parent = loader.load()
implicit val controller = loader.getController[BattleUI]()
val scene: Scene = new Scene(root)
implicit val rng = new scala.util.Random()
val form1 = Form("Diabolo")
val form2 = Form("Chanilla")
val movepool1 = IndexedSeq(Move("eruption"), Move("willowisp"), Move("thunderbolt"), Move("thunderwave"), Move("thundershock"))
val movepool2 = IndexedSeq(Move("tackle"))
val p1 = TrainerID("Jaeda", Gender.Female, 0)
val p2 = TrainerID("Wild Monster", Gender.Male, 0)
val party1 = new Party(p1, new MonsterPtr(Monster.generate("Allied Mon", p1, 500, form1, movepool1)), IndexedSeq())
val party2 = new Party(p2, new MonsterPtr(Monster.generate("Wild Mon", p2, 500, form2, movepool2)), IndexedSeq(
Monster.generate("Sideboard Mon", p2, 500, form2, movepool2)
))
controller.beginTrainerBattle(party1, party2)
primaryStage.setTitle(Config.title)
primaryStage.setScene(scene)
primaryStage.show()
}
}
def main(args: Array[String]): Unit = {
Application.launch(classOf[GameApp], args: _*)
}
}

View File

@@ -12,7 +12,7 @@
<?import javafx.scene.layout.Pane?>
<?import javafx.scene.layout.VBox?>
<VBox prefHeight="600.0" prefWidth="800.0" xmlns="http://javafx.com/javafx/8.0.171" xmlns:fx="http://javafx.com/fxml/1" fx:controller="fmon.battle.BattleUI">
<VBox prefHeight="600.0" prefWidth="800.0" xmlns="http://javafx.com/javafx/11.0.1" xmlns:fx="http://javafx.com/fxml/1" fx:controller="fmon.battle.BattleUI">
<children>
<MenuBar VBox.vgrow="NEVER">
<menus>
@@ -53,7 +53,7 @@
</Menu>
</menus>
</MenuBar>
<AnchorPane maxHeight="-1.0" maxWidth="-1.0" prefHeight="-1.0" prefWidth="-1.0" VBox.vgrow="ALWAYS">
<AnchorPane maxHeight="-1.0" maxWidth="-1.0" VBox.vgrow="ALWAYS">
<children>
<ImageView fx:id="backgroundImg" fitHeight="575.0" fitWidth="856.0" pickOnBounds="true">
<image>

View File

@@ -6,4 +6,6 @@ resistMult: 0.5
immuneMult: 0.0
maxBoost: 6
newday: 02:00
tileSize: 32
tileSize: 48
yOffset: -4
moveSpeed: 3.0

View File

@@ -0,0 +1,35 @@
package fmon.draw
import scalafx.scene.image.ImageView
import scalafx.Includes._
import scalafx.animation.Transition
import scalafx.util.Duration
class AnimatedImageView extends ImageView {
def this(img: Drawable, dur: Duration) {
this()
animate(img, dur)
}
var drawable: Drawable = _
var duration: Duration = _
private var animation: SpriteAnimation = _
def animate(img: Drawable, dur: Duration) {
drawable = img
duration = dur
animation = new SpriteAnimation(this, drawable, duration)
animation.cycleCount = Transition.Indefinite
animation.play()
}
def pause(): Unit = animation.pause()
def play(): Unit = animation.play()
def playFromStart(): Unit = animation.playFromStart()
def stop(): Unit = {
animation.stop()
animation.interpolate(0)
}
}

View File

@@ -14,9 +14,9 @@ object CharSet {
val palette = new Palette(new FileInputStream(file), na, nd)
val x = index % NumAcross
val y = index / NumAcross
val xx = x * NumAcross
val xx = x * FramesPerPose + 1
def pose(i: Int): AnimatedImage = {
val yy = y * NumDown + i
val yy = y * Poses.size + i
val images = IndexedSeq(palette(xx, yy), palette(xx - 1, yy), palette(xx, yy), palette(xx + 1, yy))
new AnimatedImage(images)
}

View File

@@ -7,6 +7,7 @@ import scalafx.scene.canvas.Canvas
import scalafx.scene.image._
import scalafx.scene.paint.Color
import fmon._
import fmon.draw._
import fmon.util._
@@ -14,14 +15,18 @@ import Direction._
trait AutoTile {
def icon: StillImg
def build(dirs: Set[Direction.Value]): StillImg
def build(dirs: Set[Direction]): StillImg
}
class BasicTile(val icon: StillImg) extends AutoTile {
def build(dirs: Set[Direction]) = icon
}
class AutoFloorTile(val palette: Palette, val size: Int = 48) extends AutoTile {
def icon: StillImg = palette.apply(0, 0, 2, 2)
def build(dirs: Set[Direction.Value]) = {
def build(dirs: Set[Direction]) = {
val canvas = new scalafx.scene.canvas.Canvas(size, size)
val g = canvas.graphicsContext2D
@@ -43,7 +48,7 @@ class AutoFloorTile(val palette: Palette, val size: Int = 48) extends AutoTile {
StillImg(img, 0, 0, size, size)
}
private def getNE(dirs: Set[Direction.Value]): Image = {
private def getNE(dirs: Set[Direction]): Image = {
if (!dirs(Northeast) && dirs(North) && dirs(East)) {
palette(3, 0).croppedImage()
} else {
@@ -53,7 +58,7 @@ class AutoFloorTile(val palette: Palette, val size: Int = 48) extends AutoTile {
}
}
private def getNW(dirs: Set[Direction.Value]): Image = {
private def getNW(dirs: Set[Direction]): Image = {
if (!dirs(Northwest) && dirs(North) && dirs(West)) {
palette(2, 0).croppedImage()
} else {
@@ -63,7 +68,7 @@ class AutoFloorTile(val palette: Palette, val size: Int = 48) extends AutoTile {
}
}
private def getSE(dirs: Set[Direction.Value]): Image = {
private def getSE(dirs: Set[Direction]): Image = {
if (!dirs(Southeast) && dirs(South) && dirs(East)) {
palette(3, 1).croppedImage()
} else {
@@ -73,7 +78,7 @@ class AutoFloorTile(val palette: Palette, val size: Int = 48) extends AutoTile {
}
}
private def getSW(dirs: Set[Direction.Value]): Image = {
private def getSW(dirs: Set[Direction]): Image = {
if (!dirs(Southwest) && dirs(South) && dirs(West)) {
palette(2, 1).croppedImage()
} else {
@@ -107,7 +112,7 @@ class AutoWallTile(val palette: Palette, val size: Int = 48) extends AutoTile {
StillImg(img, 0, 0, size, size)
}
def build(dirs: Set[Direction.Value]) = {
def build(dirs: Set[Direction]) = {
val canvas = new scalafx.scene.canvas.Canvas(size, size)
val g = canvas.graphicsContext2D
@@ -129,38 +134,124 @@ class AutoWallTile(val palette: Palette, val size: Int = 48) extends AutoTile {
StillImg(img, 0, 0, size, size)
}
private def buildUL(dirs: Set[Direction.Value]) = {
private def buildUL(dirs: Set[Direction]) = {
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]) = {
private def buildUR(dirs: Set[Direction]) = {
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]) = {
private def buildLL(dirs: Set[Direction]) = {
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]) = {
private def buildLR(dirs: Set[Direction]) = {
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) {
trait AutoTilePalette {
def apply(x: Int, y: Int): AutoTile
def tiles: IndexedSeq[AutoTile]
}
class AutoFloorTilePalette(val palette: Palette, val size: Int) extends AutoTilePalette {
final val TileWidth = 2
final val TileHeight = 3
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)
val img = palette.apply(x * TileWidth, y * TileHeight, TileWidth, TileHeight).croppedImage()
new AutoFloorTile(new Palette(img, 2 * TileWidth, 2 * TileHeight), size)
}
def tiles = for (y <- 0 until palette.numDown / TileHeight; x <- 0 until palette.numAcross / TileWidth) yield {
this(x, y)
}
}
class AutoWallTilePalette(val palette: Palette, val size: Int) extends AutoTilePalette {
final val TileWidth = 2
final val TileHeight = 2
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 * TileWidth, y * TileHeight, TileWidth, TileHeight).croppedImage()
new AutoWallTile(new Palette(img, 2 * TileWidth, 2 * TileHeight), size)
}
def tiles = for (y <- 0 until palette.numDown / TileHeight; x <- 0 until palette.numAcross / TileWidth) yield {
this(x, y)
}
}
class AutoComboTilePalette(val palette: Palette, val size: Int) extends AutoTilePalette {
final val TileWidth = 2
final val CeilHeight = 3
final val WallHeight = 2
final val TotalHeight = CeilHeight + WallHeight
def this(file: File, size: Int = 48) = this(Palette.bySize(new FileInputStream(file), size, size), size)
def apply(x: Int, y: Int) = {
val yy = y / 2
if (y % 2 == 0) {
// Ceiling
val img = palette.apply(x * TileWidth, yy * TotalHeight, TileWidth, CeilHeight).croppedImage()
new AutoFloorTile(new Palette(img, 2 * TileWidth, 2 * CeilHeight), size)
} else {
// Wall
val img = palette.apply(x * TileWidth, yy * TotalHeight + CeilHeight, TileWidth, WallHeight).croppedImage()
new AutoWallTile(new Palette(img, 2 * TileWidth, 2 * WallHeight), size)
}
}
def tiles = for (y <- 0 until 2 * palette.numDown / TotalHeight; x <- 0 until palette.numAcross / TileWidth) yield {
this(x, y)
}
}
class BasicTilePalette(val palette: Palette, val size: Int) extends AutoTilePalette {
def this(file: File, size: Int = 48) = this(Palette.bySize(new FileInputStream(file), size, size), size)
def apply(x: Int, y: Int) = new BasicTile(palette(x, y))
def tiles = palette.images.map(new BasicTile(_))
}
class PartitionedTilePalette(val palette: Palette, val parts: Int, val size: Int) extends AutoTilePalette {
val subwidth = palette.numAcross / parts
def this(file: File, parts: Int, size: Int = 48) = this(Palette.bySize(new FileInputStream(file), size, size), parts, size)
def apply(x: Int, y: Int) = {
val yy = y % palette.numDown
val xx = (y / palette.numDown) * subwidth
new BasicTile(palette(xx, yy))
}
def tiles = {
val tiles = palette.images.map(new BasicTile(_))
val windows = tiles.sliding(subwidth, subwidth).toSeq
(0 until parts).flatMap(i => windows.drop(i).sliding(1, parts).flatten).flatten
}
}
object AutoTilePalette {
def a2(file: File, size: Int = 48) = new AutoFloorTilePalette(file, size)
def a3(file: File, size: Int = 48) = new AutoWallTilePalette(file, size)
def a4(file: File, size: Int = 48) = new AutoComboTilePalette(file, size)
def basic(file: File, size: Int = 48) = new BasicTilePalette(file, size)
def partitioned(file: File, parts: Int = 2, size: Int = 48) = new PartitionedTilePalette(file, parts, size)
}
import scalafx.Includes._
@@ -177,7 +268,7 @@ object AutoTileDemo extends JFXApp {
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 palette = new AutoFloorTilePalette(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 {

View File

@@ -6,6 +6,7 @@ import fmon.stat._
import fmon.util.{Dice, IntFraction}
package object fmon {
type Direction = fmon.util.Direction.Val
implicit def rngDice(rng : Random) = new Dice(rng)
implicit def int2Helper(x : Int) = new IntFraction(x)

View File

@@ -0,0 +1,5 @@
package fmon.script
trait Action[A] {
def apply(env: A): Unit
}

View File

@@ -0,0 +1,5 @@
package fmon.script
trait Condition[A] {
def apply(t: A): Boolean
}

View File

@@ -0,0 +1,7 @@
package fmon.script.action
import fmon.script.Action
class MessageAction[A](val msg: String) extends Action[A] {
override def apply(env: A) = println(msg)
}

View File

@@ -0,0 +1,7 @@
package fmon.script.condition
import fmon.script.Condition
class AndCondition[A](seq: Seq[Condition[A]]) extends Condition[A] {
def apply(env: A): Boolean = seq.forall(_(env))
}

View File

@@ -0,0 +1,7 @@
package fmon.script.condition
import fmon.script.Condition
class OrCondition[A](seq: Seq[Condition[A]]) extends Condition[A] {
def apply(env: A): Boolean = seq.exists(_(env))
}

View File

@@ -2,15 +2,18 @@ package fmon.stat
import scala.util.Random
import fmon._
case class Gene(val gender : Gender, val nature : Nature, val ivs : Map[Stat, Int], ot : TrainerID) {
}
object Gene {
final val MaxIV = 31
def randomGene(ot : TrainerID, form : Form)(implicit rng : Random) = {
def randomGene(ot : TrainerID, form : Form)(implicit rng : Random): Gene = {
val gender = Gender.Neuter // TODO
val ivs = Statistic.values.map(s => (s, rng.nextInt(MaxIV + 1))).toMap
val ivs = Statistic.values.toSeq.map(s => (s, rng.nextInt(MaxIV + 1))).toMap
Gene(gender, Nature.randomNature, ivs, ot)
}
}

View File

@@ -1,7 +1,22 @@
package fmon.util
import fmon.Direction
object Direction extends Enumeration {
val North, Northeast, East, Southeast, South, Southwest, West, Northwest = Value
case class Val protected(x: Int = 0, y: Int = 0) extends super.Val {
}
val Stationary = Val()
val North = Val(y = -1)
val Northeast = Val(x = 1, y = -1)
val East = Val(x = 1)
val Southeast = Val(x = 1, y = 1)
val South = Val(y = 1)
val Southwest = Val(x = -1, y = 1)
val West = Val(x = -1)
val Northwest = Val(x = -1, y = -1)
val cardinals = Set(North, East, South, West)
def byName(s: String): Direction = super.withName(s).asInstanceOf[Direction]
}

View File

@@ -90,6 +90,10 @@ object YamlHelper {
mapper.writeValue(source, item)
}
def writeSeq[T](source: OutputStream, items: Seq[T]) = {
mapper.writeValue(source, items)
}
def writeMap[T](source: OutputStream, items: Map[String, T])(implicit m: Manifest[T]) = {
mapper.writeValue(source, items)
}

View File

@@ -0,0 +1,45 @@
package fmon.world
import scalafx.util.Duration
import fmon._
import fmon.draw._
import fmon.util.Direction
abstract class Actor(val sprite: Sprite, var pos: Position, var move: Movement) {
def x: Int = pos.x
def y: Int = pos.y
def xreal: Double = x - move.direction.x * move.completion
def yreal: Double = y - move.direction.y * move.completion
val imgView = new AnimatedImageView(sprite, new Duration(600)) {
x = pos.x * Config.tileSize
y = pos.y * Config.tileSize + Config.yOffset
}
def pose: String = sprite.pose
def pose_=(p: String) = {
sprite.pose = p
}
def slide(dt: Duration): Unit = {
move.completion -= dt.toSeconds() * Config.moveSpeed
if (move.completion <= 0) {
move = selectNextMove
pos += move.direction
if (move.direction != Direction.Stationary) {
imgView.play()
} else {
imgView.stop()
}
}
imgView.x = xreal * Config.tileSize
imgView.y = yreal * Config.tileSize + Config.yOffset
}
def selectNextMove: Movement
}
class Hero(sprite: Sprite, pos: Position) extends Actor(sprite, pos, new Movement(Direction.Stationary, 0)) {
def selectNextMove = new Movement(Direction.Stationary, 0)
}

View File

@@ -0,0 +1,27 @@
package fmon.world
import fmon.draw.Sprite
import fmon.script.Action
class EventPage (
// Conditions
// Switch
// Variable
// Self-Switch
// Item
// Actor
val sprite : Sprite,
// Priority - Above or below
// Trigger - Action button / On contact
// Movement
// Type
// Speed
// Frequency
// Options
// Walking Animation
// Stepping Animation
// Direction Fix
// Pass through (no-collide)
val action: Action[Actor]
) {
}

View File

@@ -0,0 +1,18 @@
package fmon.world
import fmon._
import fmon.util.Direction
class GameEvent(val name: String, val pages: IndexedSeq[EventPage]) {
def currPage = pages.head // TODO : Conditions
// Event Name
// Event Pages
}
class NPC(val event: GameEvent, pos: Position)
extends Actor(event.currPage.sprite, pos, new Movement(Direction.Stationary, 0.0)) {
def selectNextMove: Movement = {
new Movement(Direction.Stationary, 0.0)
}
}

View File

@@ -1,28 +1,78 @@
package fmon.world
import scalafx.scene.image.Image
import java.io._
import fmon._
import fmon.Config
import fmon.draw._
import fmon.draw.tile._
import fmon.util._
import Direction._
class GameMap(val width: Int, val height: Int, val tileset: Tileset) {
var tiles: IndexedSeq[AutoTile] = for (y <- 0 until height; x <- 0 until width) yield tileset.groundTiles.head
var doodads: IndexedSeq[AutoTile] = for (y <- 0 until height; x <- 0 until width) yield tileset.doodadTiles.head
def tileInfo(index: Int) = tileset.infoOf(tiles(index))
def tileInfo(pos: Position) = tileset.infoOf(this(pos))
def compositeInfo(pos: Position) = {
val gi = tileset.infoOf(this(pos))
val di = tileset.infoOf(doodad(pos))
if (di.doesOverwrite) di else gi
}
def saveTo(file: File): Unit = {
val ostream = new ObjectOutputStream(new BufferedOutputStream(new FileOutputStream(file)))
ostream.writeInt(width)
ostream.writeInt(height)
ostream.writeObject(tileset.token.a2)// Tileset
//ostream.writeObject(tileset.token.a2)// Tileset
val tileIndices = tileset.groundTiles.zipWithIndex.toMap
tiles.foreach(t => ostream.writeInt(tileIndices(t)))
//ostream.writeObject(tiles.map(tileIndices(_)).mkString(" "))
val doodadIndices = tileset.doodadTiles.zipWithIndex.toMap
doodads.foreach(t => ostream.writeInt(doodadIndices(t)))
ostream.close()
}
def pt2index(x: Int, y: Int) = y * width + x
def apply(x: Int, y: Int): AutoTile = tiles(pt2index(x, y))
def doodad(x: Int, y: Int): AutoTile = doodads(pt2index(x, y))
def apply(pos: Position): AutoTile = tiles(pt2index(pos.x, pos.y))
def doodad(pos: Position): AutoTile = doodads(pt2index(pos.x, pos.y))
def smoothed(x: Int, y: Int): Image = {
val i = pt2index(x, y)
val dirs = scala.collection.mutable.Set[Direction]()
val onLeft = x == 0
val onRight = x + 1 == width
val onTop = y == 0
val onBottom = y + 1 == height
if (onLeft || tiles(left(i)) == tiles(i)) dirs += West
if (onRight || tiles(right(i)) == tiles(i)) dirs += East
if (onTop || tiles(up(i)) == tiles(i)) dirs += North
if (onBottom || tiles(down(i)) == tiles(i)) dirs += South
if (onLeft || onTop || tiles(left(up(i))) == tiles(i)) dirs += Northwest
if (onLeft || onBottom || tiles(down(left(i))) == tiles(i)) dirs += Southwest
if (onTop || onRight || tiles(right(up(i))) == tiles(i)) dirs += Northeast
if (onBottom || onRight || tiles(right(down(i))) == tiles(i)) dirs += Southeast
val index = pt2index(x, y)
tiles(index).build(dirs.toSet).croppedImage()
}
def pt2index(x: Int, y: Int): Int = y * width + x
def pt2index(pos: Position): Int = pt2index(pos.x, pos.y)
def index2x(i: Int) = i % width
def index2y(i: Int) = i / width
def left(i: Int) = i - 1
def right(i: Int) = i + 1
def up(i: Int) = i - width
def down(i: Int) = i + width
}
object GameMap {
@@ -31,11 +81,17 @@ object GameMap {
val istream = new ObjectInputStream(new BufferedInputStream(new FileInputStream(file)))
val width = istream.readInt()
val height = istream.readInt()
val tileset = new Tileset(new TilesetToken(istream.readObject().toString))
val dir = new File(Config.homedir + raw"Progena\Data")
val resourceDir = new File(Config.homedir + raw"Progena\Resources\tilesets")
val tokens = YamlHelper.extractSeq[TilesetToken](new FileInputStream(new File(dir, "Tilesets.yaml"))).map(t => (t.name, t)).toMap
val tileset = tokens("Outside").load(resourceDir)
val indices = for (y <- 0 until height; x <- 0 until width) yield istream.readInt()
val dindices = for (y <- 0 until height; x <- 0 until width) yield istream.readInt()
val map = new GameMap(width, height, tileset)
val tiles = indices.map(i => tileset.groundTiles(i))
val doodads = dindices.map(i => tileset.doodadTiles(i))
map.tiles = tiles
map.doodads = doodads
map
}
}

View File

@@ -0,0 +1,15 @@
package fmon.world
import fmon._
import fmon.util.Direction._
case class Position(val x: Int, val y: Int) {
def +(d: Direction): Position = {
Position(x + d.x, y + d.y)
}
}
class Movement(val direction: Direction, var completion: Double) {
}

View File

@@ -5,14 +5,63 @@ import java.io._
import fmon.Config
import fmon.draw.tile._
class Tileset(val token: TilesetToken) {
val groundTiles: IndexedSeq[AutoTile] = {
val file = new File(token.a2)
val palette = new AutoTilePalette(file, Config.tileSize)
for (y <- 0 until 4; x <- 0 until 8) yield {
palette(x, y)
}
}
class Tileset(val groundTiles: IndexedSeq[AutoTile], val doodadTiles: IndexedSeq[AutoTile], val info: IndexedSeq[TileInfo]) {
def groundInfo(index: Int) = info(index)
def doodadInfo(index: Int) = info(index + groundTiles.size)
val infoOf = (groundTiles.zipWithIndex.map{case (t, i) => (t, groundInfo(i))} ++ doodadTiles.zipWithIndex.map{case (t, i) => (t, doodadInfo(i))}).toMap
}
case class TilesetToken(val a2: String)
case class TileInfo(val flags: Int = TileInfo.PassAll) {
def doesPassNorth = (flags & TileInfo.PassNorth) != 0
def doesPassEast = (flags & TileInfo.PassEast) != 0
def doesPassSouth = (flags & TileInfo.PassSouth) != 0
def doesPassWest = (flags & TileInfo.PassWest) != 0
def doesOverwrite = (flags & TileInfo.Overwrite) != 0
def isCounter = (flags & TileInfo.IsCounter) != 0
}
object TileInfo {
val PassNorth = 0x0001
val PassEast = 0x0002
val PassSouth = 0x0004
val PassWest = 0x0008
val PassAll = PassNorth | PassEast | PassSouth | PassWest
val NoPass = 0
val Overwrite = 0x0010
val Underfoot = 0x0020
val Overhead = 0x0040
val IsCounter = 0x0100
}
case class TilesetToken(
val name: String,
val a1: String,
val a2: String,
val a3: String,
val a4: String,
val a5: String,
val b: String,
val c: String,
val info: IndexedSeq[TileInfo]) {
def load(dir: File): Tileset = {
val a1Tiles = AutoTilePalette.a2(new File(dir, a1), Config.tileSize).tiles
val a2Tiles = AutoTilePalette.a2(new File(dir, a2), Config.tileSize).tiles
val a3Tiles = AutoTilePalette.a3(new File(dir, a3), Config.tileSize).tiles
val a4Tiles = AutoTilePalette.a4(new File(dir, a4), Config.tileSize).tiles
val a5Tiles = AutoTilePalette.basic(new File(dir, a5), Config.tileSize).tiles
val groundTiles = a1Tiles ++ a2Tiles ++ a3Tiles ++ a4Tiles ++ a5Tiles
val bTiles = AutoTilePalette.partitioned(new File(dir, b), 2, Config.tileSize).tiles
val cTiles = AutoTilePalette.partitioned(new File(dir, c), 2, Config.tileSize).tiles
val doodadTiles = bTiles ++ cTiles
new Tileset(groundTiles, doodadTiles, info)
}
override def toString = name
}

View File

@@ -0,0 +1,18 @@
<?xml version="1.0" encoding="UTF-8"?>
<?import javafx.scene.control.ScrollPane?>
<?import javafx.scene.layout.AnchorPane?>
<?import javafx.scene.layout.StackPane?>
<?import javafx.scene.layout.TilePane?>
<ScrollPane fx:id="scroller" maxHeight="-Infinity" maxWidth="-Infinity" minHeight="-Infinity" minWidth="-Infinity" onKeyPressed="#onKeyDown" onKeyReleased="#onKeyUp" xmlns="http://javafx.com/javafx/11.0.1" xmlns:fx="http://javafx.com/fxml/1" fx:controller="fmon.world.ui.GameView">
<content>
<StackPane maxHeight="1.7976931348623157E308" maxWidth="1.7976931348623157E308">
<children>
<TilePane fx:id="background" />
<TilePane fx:id="doodads" />
<AnchorPane fx:id="characterPane" />
</children>
</StackPane>
</content>
</ScrollPane>

View File

@@ -0,0 +1,310 @@
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: _*)
}
}