diff --git a/editor/src/main/kotlin/com/bartlomiejpluta/base/editor/map/canvas/MapCanvas.kt b/editor/src/main/kotlin/com/bartlomiejpluta/base/editor/map/canvas/MapCanvas.kt index 7444ed3a..a9714a7e 100644 --- a/editor/src/main/kotlin/com/bartlomiejpluta/base/editor/map/canvas/MapCanvas.kt +++ b/editor/src/main/kotlin/com/bartlomiejpluta/base/editor/map/canvas/MapCanvas.kt @@ -1,6 +1,7 @@ package com.bartlomiejpluta.base.editor.map.canvas import com.bartlomiejpluta.base.editor.map.model.layer.Layer +import com.bartlomiejpluta.base.editor.map.model.layer.ObjectLayer import com.bartlomiejpluta.base.editor.map.model.layer.TileLayer import com.bartlomiejpluta.base.editor.map.viewmodel.EditorStateVM import com.bartlomiejpluta.base.editor.map.viewmodel.GameMapVM @@ -48,6 +49,7 @@ class MapCanvas(val map: GameMapVM, private val editorStateVM: EditorStateVM, pr private fun dispatchLayerRender(gc: GraphicsContext, layer: Layer) { when (layer) { is TileLayer -> renderTileLayer(gc, layer) + is ObjectLayer -> renderObjectPassageMap(gc, layer) } } @@ -70,12 +72,26 @@ class MapCanvas(val map: GameMapVM, private val editorStateVM: EditorStateVM, pr } } - private fun renderGrid(gc: GraphicsContext) { - if(!editorStateVM.showGrid) { + private fun renderObjectPassageMap(gc: GraphicsContext, objectLayer: ObjectLayer) { + if (editorStateVM.selectedLayer !is ObjectLayer) { return } + for ((row, columns) in objectLayer.passageMap.withIndex()) { + for ((column, passage) in columns.withIndex()) { + PassageAbilitySymbol.render(gc, column * tileWidth, row * tileHeight, tileWidth, tileHeight, passage) + } + } + } + + private fun renderGrid(gc: GraphicsContext) { + if (!editorStateVM.showGrid) { + return + } + + val lineWidth = gc.lineWidth gc.lineWidth = 1.5 + gc.setLineDashes(0.7) gc.strokeLine(0.0, 0.0, map.width, 0.0) gc.strokeLine(0.0, 0.0, 0.0, map.height) @@ -89,6 +105,8 @@ class MapCanvas(val map: GameMapVM, private val editorStateVM: EditorStateVM, pr for (column in 0 until map.columns) { gc.strokeLine(column * tileWidth, 0.0, column * tileWidth, map.height) } + + gc.lineWidth = lineWidth } companion object { diff --git a/editor/src/main/kotlin/com/bartlomiejpluta/base/editor/map/canvas/MapPainter.kt b/editor/src/main/kotlin/com/bartlomiejpluta/base/editor/map/canvas/MapPainter.kt index 74c2abbf..e94f6b47 100644 --- a/editor/src/main/kotlin/com/bartlomiejpluta/base/editor/map/canvas/MapPainter.kt +++ b/editor/src/main/kotlin/com/bartlomiejpluta/base/editor/map/canvas/MapPainter.kt @@ -1,5 +1,6 @@ package com.bartlomiejpluta.base.editor.map.canvas +import com.bartlomiejpluta.base.editor.map.model.layer.ObjectLayer import com.bartlomiejpluta.base.editor.map.model.layer.TileLayer import com.bartlomiejpluta.base.editor.map.viewmodel.BrushVM import com.bartlomiejpluta.base.editor.map.viewmodel.EditorStateVM @@ -27,6 +28,7 @@ class MapPainter( editorStateVM.selectedLayerProperty.addListener { _, _, layer -> cursor = when (layer) { is TileLayer -> TilePaintingCursor(tileWidth, tileHeight, editorStateVM, brushVM) + is ObjectLayer -> ObjectPaintingCursor(tileWidth, tileHeight, editorStateVM) else -> null } } @@ -56,6 +58,7 @@ class MapPainter( if (event.button == MouseButton.PRIMARY && editorStateVM.selectedLayerIndex >= 0) { currentTrace = when (editorStateVM.selectedLayer) { is TileLayer -> TilePaintingTrace(mapVM, "Paint trace") + is ObjectLayer -> ObjectPaintingTrace(mapVM, "Toggle passage") else -> null }?.apply { beginTrace(editorStateVM, brushVM) } } diff --git a/editor/src/main/kotlin/com/bartlomiejpluta/base/editor/map/canvas/ObjectPaintingCursor.kt b/editor/src/main/kotlin/com/bartlomiejpluta/base/editor/map/canvas/ObjectPaintingCursor.kt new file mode 100644 index 00000000..7a57f113 --- /dev/null +++ b/editor/src/main/kotlin/com/bartlomiejpluta/base/editor/map/canvas/ObjectPaintingCursor.kt @@ -0,0 +1,34 @@ +package com.bartlomiejpluta.base.editor.map.canvas + +import com.bartlomiejpluta.base.editor.map.viewmodel.EditorStateVM +import javafx.scene.canvas.GraphicsContext +import javafx.scene.paint.Color + +class ObjectPaintingCursor( + private val tileWidth: Double, + private val tileHeight: Double, + private val editorStateVM: EditorStateVM +) : PaintingCursor { + + override fun render(gc: GraphicsContext) { + val alpha = gc.globalAlpha + val stroke = gc.stroke + val width = gc.lineWidth + gc.globalAlpha = 1.0 + gc.stroke = Color.WHITE + gc.lineWidth = 3.0 + + val x = editorStateVM.cursorColumn * tileWidth + val y = editorStateVM.cursorRow * tileHeight + gc.strokeLine(x + tileWidth / 2, y + (1 - SIZE) * tileHeight, x + tileWidth / 2, y + SIZE * tileHeight) + gc.strokeLine(x + (1 - SIZE) * tileWidth, y + tileHeight / 2, x + SIZE * tileWidth, y + tileHeight / 2) + + gc.globalAlpha = alpha + gc.stroke = stroke + gc.lineWidth = width + } + + companion object { + private const val SIZE = 0.3 + } +} \ No newline at end of file diff --git a/editor/src/main/kotlin/com/bartlomiejpluta/base/editor/map/canvas/ObjectPaintingTrace.kt b/editor/src/main/kotlin/com/bartlomiejpluta/base/editor/map/canvas/ObjectPaintingTrace.kt new file mode 100644 index 00000000..db836f5c --- /dev/null +++ b/editor/src/main/kotlin/com/bartlomiejpluta/base/editor/map/canvas/ObjectPaintingTrace.kt @@ -0,0 +1,56 @@ +package com.bartlomiejpluta.base.editor.map.canvas + +import com.bartlomiejpluta.base.editor.map.model.brush.BrushMode +import com.bartlomiejpluta.base.editor.map.model.enumeration.PassageAbility +import com.bartlomiejpluta.base.editor.map.model.layer.ObjectLayer +import com.bartlomiejpluta.base.editor.map.viewmodel.BrushVM +import com.bartlomiejpluta.base.editor.map.viewmodel.EditorStateVM +import com.bartlomiejpluta.base.editor.map.viewmodel.GameMapVM + +class ObjectPaintingTrace(val map: GameMapVM, override val commandName: String) : PaintingTrace { + private var layerIndex = 0 + private var row = 0 + private var column = 0 + private lateinit var layer: ObjectLayer + private lateinit var formerPassageAbility: PassageAbility + private lateinit var passageAbility: PassageAbility + + + override fun beginTrace(editorStateVM: EditorStateVM, brushVM: BrushVM) { + // Do nothing + } + + override fun proceedTrace(editorStateVM: EditorStateVM, brushVM: BrushVM) { + // Do nothing + } + + override fun commitTrace(editorStateVM: EditorStateVM, brushVM: BrushVM) { + this.layerIndex = editorStateVM.selectedLayerIndex + this.row = editorStateVM.cursorRow + this.column = editorStateVM.cursorColumn + + if (row >= map.rows || column >= map.columns || row < 0 || column < 0 || layerIndex < 0) { + return + } + + this.layer = (map.layers[layerIndex] as ObjectLayer) + + formerPassageAbility = layer.passageMap[row][column] + + passageAbility = when (brushVM.mode) { + BrushMode.PAINTING_MODE -> PassageAbility.values()[(formerPassageAbility.ordinal + 1) % PassageAbility.values().size] + BrushMode.ERASING_MODE -> PassageAbility.ALLOW + else -> throw IllegalStateException("Unknown brush mode") + } + + layer.passageMap[row][column] = passageAbility + } + + override fun undo() { + layer.passageMap[row][column] = formerPassageAbility + } + + override fun redo() { + layer.passageMap[row][column] = passageAbility + } +} \ No newline at end of file diff --git a/editor/src/main/kotlin/com/bartlomiejpluta/base/editor/map/canvas/PassageAbilitySymbol.kt b/editor/src/main/kotlin/com/bartlomiejpluta/base/editor/map/canvas/PassageAbilitySymbol.kt new file mode 100644 index 00000000..6c25a155 --- /dev/null +++ b/editor/src/main/kotlin/com/bartlomiejpluta/base/editor/map/canvas/PassageAbilitySymbol.kt @@ -0,0 +1,102 @@ +package com.bartlomiejpluta.base.editor.map.canvas + +import com.bartlomiejpluta.base.editor.map.model.enumeration.PassageAbility +import javafx.scene.canvas.GraphicsContext +import javafx.scene.paint.Color + +object PassageAbilitySymbol { + private const val SIZE = 0.6 + + fun render(gc: GraphicsContext, x: Double, y: Double, w: Double, h: Double, passageAbility: PassageAbility) { + val fill = gc.fill + val alpha = gc.globalAlpha + + when (passageAbility) { + PassageAbility.ALLOW -> allow(gc, x, y, w, h) + PassageAbility.BLOCK -> block(gc, x, y, w, h) + PassageAbility.UP_ONLY -> up(gc, x, y, w, h) + PassageAbility.DOWN_ONLY -> down(gc, x, y, w, h) + PassageAbility.LEFT_ONLY -> left(gc, x, y, w, h) + PassageAbility.RIGHT_ONLY -> right(gc, x, y, w, h) + } + + gc.fill = fill + gc.globalAlpha = alpha + } + + fun allow(gc: GraphicsContext, x: Double, y: Double, w: Double, h: Double) { + gc.fill = Color.GREEN + gc.globalAlpha = 0.1 + gc.fillRect(x, y, w, h) + } + + fun block(gc: GraphicsContext, x: Double, y: Double, w: Double, h: Double) { + gc.fill = Color.RED + gc.globalAlpha = 0.4 + gc.fillRect(x, y, w, h) + } + + fun down(gc: GraphicsContext, x: Double, y: Double, w: Double, h: Double) { + gc.fill = Color.GREEN + gc.globalAlpha = 0.1 + gc.fillRect(x, y, w, h) + gc.globalAlpha = 1.0 + + gc.fill = Color.WHITE + gc.beginPath() + gc.moveTo(x + (1 - SIZE) * w, y + (1 - SIZE) * h) + gc.lineTo(x + w * SIZE, y + (1 - SIZE) * h) + gc.lineTo(x + w / 2, y + h * SIZE) + gc.closePath() + + gc.fill() + } + + fun up(gc: GraphicsContext, x: Double, y: Double, w: Double, h: Double) { + gc.fill = Color.GREEN + gc.globalAlpha = 0.1 + gc.fillRect(x, y, w, h) + gc.globalAlpha = 1.0 + + gc.fill = Color.WHITE + gc.beginPath() + gc.moveTo(x + (1 - SIZE) * w, y + h * SIZE) + gc.lineTo(x + w * SIZE, y + h * SIZE) + gc.lineTo(x + w / 2, y + (1 - SIZE) * h) + gc.closePath() + + gc.fill() + } + + fun left(gc: GraphicsContext, x: Double, y: Double, w: Double, h: Double) { + gc.fill = Color.GREEN + gc.globalAlpha = 0.1 + gc.fillRect(x, y, w, h) + gc.globalAlpha = 1.0 + + gc.fill = Color.WHITE + gc.beginPath() + gc.moveTo(x + (1 - SIZE) * w, y + h / 2) + gc.lineTo(x + SIZE * w, y + (1 - SIZE) * h) + gc.lineTo(x + w * SIZE, y + h * SIZE) + gc.closePath() + + gc.fill() + } + + fun right(gc: GraphicsContext, x: Double, y: Double, w: Double, h: Double) { + gc.fill = Color.GREEN + gc.globalAlpha = 0.1 + gc.fillRect(x, y, w, h) + gc.globalAlpha = 1.0 + + gc.fill = Color.WHITE + gc.beginPath() + gc.moveTo(x + SIZE * w, y + h / 2) + gc.lineTo(x + (1 - SIZE) * w, y + (1 - SIZE) * h) + gc.lineTo(x + w * (1 - SIZE), y + h * SIZE) + gc.closePath() + + gc.fill() + } +} \ No newline at end of file diff --git a/editor/src/main/kotlin/com/bartlomiejpluta/base/editor/map/model/enumeration/PassageAbility.kt b/editor/src/main/kotlin/com/bartlomiejpluta/base/editor/map/model/enumeration/PassageAbility.kt new file mode 100644 index 00000000..62da4a14 --- /dev/null +++ b/editor/src/main/kotlin/com/bartlomiejpluta/base/editor/map/model/enumeration/PassageAbility.kt @@ -0,0 +1,10 @@ +package com.bartlomiejpluta.base.editor.map.model.enumeration + +enum class PassageAbility { + ALLOW, + BLOCK, + UP_ONLY, + DOWN_ONLY, + LEFT_ONLY, + RIGHT_ONLY +} \ No newline at end of file diff --git a/editor/src/main/kotlin/com/bartlomiejpluta/base/editor/map/model/layer/ObjectLayer.kt b/editor/src/main/kotlin/com/bartlomiejpluta/base/editor/map/model/layer/ObjectLayer.kt index c32ef90a..934d6654 100644 --- a/editor/src/main/kotlin/com/bartlomiejpluta/base/editor/map/model/layer/ObjectLayer.kt +++ b/editor/src/main/kotlin/com/bartlomiejpluta/base/editor/map/model/layer/ObjectLayer.kt @@ -1,14 +1,27 @@ package com.bartlomiejpluta.base.editor.map.model.layer +import com.bartlomiejpluta.base.editor.map.model.enumeration.PassageAbility import javafx.beans.property.SimpleStringProperty import tornadofx.getValue import tornadofx.setValue -class ObjectLayer(name: String) : Layer { +class ObjectLayer( + name: String, + rows: Int, + columns: Int, + passageMap: Array> = Array(rows) { Array(columns) { PassageAbility.ALLOW } } +) : Layer { + var passageMap = passageMap + private set + override val nameProperty = SimpleStringProperty(name) override fun resize(rows: Int, columns: Int) { - // We essentially need to do nothing + passageMap = Array(rows) { row -> + Array(columns) { column -> + passageMap.getOrNull(row)?.getOrNull(column) ?: PassageAbility.ALLOW + } + } } override var name by nameProperty diff --git a/editor/src/main/kotlin/com/bartlomiejpluta/base/editor/map/serial/ProtobufMapDeserializer.kt b/editor/src/main/kotlin/com/bartlomiejpluta/base/editor/map/serial/ProtobufMapDeserializer.kt index 0d28a4a3..62b04c75 100644 --- a/editor/src/main/kotlin/com/bartlomiejpluta/base/editor/map/serial/ProtobufMapDeserializer.kt +++ b/editor/src/main/kotlin/com/bartlomiejpluta/base/editor/map/serial/ProtobufMapDeserializer.kt @@ -37,7 +37,7 @@ class ProtobufMapDeserializer : MapDeserializer { private fun deserializeLayer(rows: Int, columns: Int, tileSet: TileSet, proto: GameMapProto.Layer): Layer { return when { proto.hasTileLayer() -> deserializeTileLayer(rows, columns, tileSet, proto) - proto.hasObjectLayer() -> deserializeObjectLayer(proto) + proto.hasObjectLayer() -> deserializeObjectLayer(rows, columns, proto) proto.hasImageLayer() -> deserializeImageLayer(proto) else -> throw IllegalStateException("Not supported layer type") @@ -57,8 +57,8 @@ class ProtobufMapDeserializer : MapDeserializer { return TileLayer(proto.name, rows, columns, layer) } - private fun deserializeObjectLayer(proto: GameMapProto.Layer): Layer { - return ObjectLayer(proto.name) + private fun deserializeObjectLayer(rows: Int, columns: Int, proto: GameMapProto.Layer): Layer { + return ObjectLayer(proto.name, rows, columns) } private fun deserializeImageLayer(proto: GameMapProto.Layer): Layer { diff --git a/editor/src/main/kotlin/com/bartlomiejpluta/base/editor/map/view/editor/MapLayersView.kt b/editor/src/main/kotlin/com/bartlomiejpluta/base/editor/map/view/editor/MapLayersView.kt index 95e6f372..91638882 100644 --- a/editor/src/main/kotlin/com/bartlomiejpluta/base/editor/map/view/editor/MapLayersView.kt +++ b/editor/src/main/kotlin/com/bartlomiejpluta/base/editor/map/view/editor/MapLayersView.kt @@ -62,7 +62,7 @@ class MapLayersView : View() { item("Object Layer", graphic = FontIcon("fa-cube")) { action { - val layer = ObjectLayer("Layer ${mapVM.layers.size + 1}") + val layer = ObjectLayer("Layer ${mapVM.layers.size + 1}", mapVM.rows, mapVM.columns) val command = CreateLayerCommand(mapVM.item, layer) command.execute() layersPane.selectionModel.select(mapVM.layers.size - 1) diff --git a/editor/src/main/kotlin/com/bartlomiejpluta/base/editor/map/view/editor/MapToolbarView.kt b/editor/src/main/kotlin/com/bartlomiejpluta/base/editor/map/view/editor/MapToolbarView.kt index 67d77b46..434c560d 100644 --- a/editor/src/main/kotlin/com/bartlomiejpluta/base/editor/map/view/editor/MapToolbarView.kt +++ b/editor/src/main/kotlin/com/bartlomiejpluta/base/editor/map/view/editor/MapToolbarView.kt @@ -5,9 +5,12 @@ import com.bartlomiejpluta.base.editor.command.service.UndoRedoService import com.bartlomiejpluta.base.editor.event.RedrawMapRequestEvent import com.bartlomiejpluta.base.editor.map.controller.MapController import com.bartlomiejpluta.base.editor.map.model.brush.BrushMode +import com.bartlomiejpluta.base.editor.map.model.layer.ObjectLayer +import com.bartlomiejpluta.base.editor.map.model.layer.TileLayer import com.bartlomiejpluta.base.editor.map.viewmodel.BrushVM import com.bartlomiejpluta.base.editor.map.viewmodel.EditorStateVM import com.bartlomiejpluta.base.editor.map.viewmodel.GameMapVM +import javafx.beans.binding.Bindings import javafx.scene.control.ToggleGroup import org.kordamp.ikonli.javafx.FontIcon import tornadofx.* @@ -28,6 +31,16 @@ class MapToolbarView : View() { } } + private val isTileLayerSelected = Bindings.createBooleanBinding( + { editorStateVM.selectedLayer is TileLayer }, + editorStateVM.selectedLayerProperty + ) + + private val isObjectLayerSelected = Bindings.createBooleanBinding( + { editorStateVM.selectedLayer is ObjectLayer }, + editorStateVM.selectedLayerProperty + ) + override val root = toolbar { button(graphic = FontIcon("fa-floppy-o")) { shortcut("Ctrl+S") @@ -71,6 +84,8 @@ class MapToolbarView : View() { togglebutton(value = BrushMode.PAINTING_MODE, group = brushMode) { graphic = FontIcon("fa-paint-brush") + enableWhen(isTileLayerSelected.or(isObjectLayerSelected)) + action { brushVM.item = brushVM.withMode(BrushMode.PAINTING_MODE) brushVM.commit() @@ -80,6 +95,8 @@ class MapToolbarView : View() { togglebutton(value = BrushMode.ERASING_MODE, group = brushMode) { graphic = FontIcon("fa-eraser") + enableWhen(isTileLayerSelected.or(isObjectLayerSelected)) + action { brushVM.item = brushVM.withMode(BrushMode.ERASING_MODE) brushVM.commit() @@ -93,6 +110,8 @@ class MapToolbarView : View() { isSnapToTicks = true minorTickCount = 0 + enableWhen(isTileLayerSelected) + valueProperty().addListener { _, _, newValue -> brushVM.item = brushVM.withRange(newValue.toInt()) brushVM.commit()