diff --git a/redhorizon-classic/source/nz/net/ultraq/redhorizon/classic/extensions/CoordinateExtensions.groovy b/redhorizon-classic/source/nz/net/ultraq/redhorizon/classic/extensions/CoordinateExtensions.groovy index 81f64425..5896c13e 100644 --- a/redhorizon-classic/source/nz/net/ultraq/redhorizon/classic/extensions/CoordinateExtensions.groovy +++ b/redhorizon-classic/source/nz/net/ultraq/redhorizon/classic/extensions/CoordinateExtensions.groovy @@ -42,7 +42,7 @@ class CoordinateExtensions { */ static Vector2f asCellCoords(Integer self) { - return new Vector2f(self % TILES_Y, Math.ceil(self / TILES_Y) as float) + return new Vector2f(self % TILES_Y, Math.floor(self / TILES_Y) as float) } /** @@ -66,7 +66,7 @@ class CoordinateExtensions { * * @param self * @param objectHeightInCells - * Optional, height of the object whose coordinates are being translated. + * Vertical space occupied by the objheheight of the object whose coordinates are being translated. * @return */ static Vector2f asWorldCoords(Vector2f self, int objectHeightInCells = 0) { @@ -76,6 +76,15 @@ class CoordinateExtensions { .add(WORLD_OFFSET) } + /** + * Adjust a vector enough so that an arbitrary-sized sprite is centered in a + * map cell. + */ + static Vector2f centerInCell(Vector2f self, float spriteWidth, float spriteHeight) { + + return self.sub((spriteWidth - TILE_WIDTH) / 2 as float, (spriteHeight - TILE_HEIGHT) / 2 as float) + } + /** * Update the rectangle so that min/max values are valid, ie: if minX > maxX * then minX <-> maxX, and same for the Y axis. diff --git a/redhorizon-classic/source/nz/net/ultraq/redhorizon/classic/units/Unit.groovy b/redhorizon-classic/source/nz/net/ultraq/redhorizon/classic/units/Unit.groovy index c627c6ce..95bda901 100644 --- a/redhorizon-classic/source/nz/net/ultraq/redhorizon/classic/units/Unit.groovy +++ b/redhorizon-classic/source/nz/net/ultraq/redhorizon/classic/units/Unit.groovy @@ -66,6 +66,8 @@ class Unit extends Node implements FactionColours, Rotatable, Temporal { this.palette = palette this.unitData = unitData + bounds.setMax(imagesFile.width, imagesFile.height) + body = new UnitBody(imagesFile.width, imagesFile.height, imagesFile.numImages, palette, { _ -> return CompletableFuture.completedFuture(spriteSheet) }, unitData) @@ -105,6 +107,14 @@ class Unit extends Node implements FactionColours, Rotatable, Temporal { return 360f / unitData.shpFile.states[stateIndex].headings } + /** + * Return the height of the unit, often determined by the sprite used for it. + */ + int getHeight() { + + return bounds.lengthY() + } + /** * Return the name of the current state of the unit. */ @@ -113,6 +123,14 @@ class Unit extends Node implements FactionColours, Rotatable, Temporal { return unitData.shpFile.states[stateIndex].name } + /** + * Return the width of the unit, often determined by the sprite used for it. + */ + int getWidth() { + + return bounds.lengthX() + } + @Override CompletableFuture onSceneAdded(Scene scene) { diff --git a/redhorizon-explorer/source/nz/net/ultraq/redhorizon/explorer/MixDatabase.groovy b/redhorizon-explorer/source/nz/net/ultraq/redhorizon/explorer/MixDatabase.groovy index 3154e309..3add199f 100644 --- a/redhorizon-explorer/source/nz/net/ultraq/redhorizon/explorer/MixDatabase.groovy +++ b/redhorizon-explorer/source/nz/net/ultraq/redhorizon/explorer/MixDatabase.groovy @@ -24,7 +24,7 @@ package nz.net.ultraq.redhorizon.explorer */ class MixDatabase { - private static final String[] sources = ['ra-conquer.csv', 'ra-missions.csv', 'ra-videos.csv'] + private static final String[] sources = ['ra-config.csv', 'ra-conquer.csv', 'ra-missions.csv', 'ra-videos.csv'] private final List data = [] diff --git a/redhorizon-explorer/source/nz/net/ultraq/redhorizon/explorer/mixdata/ra-config.csv b/redhorizon-explorer/source/nz/net/ultraq/redhorizon/explorer/mixdata/ra-config.csv new file mode 100644 index 00000000..9812db05 --- /dev/null +++ b/redhorizon-explorer/source/nz/net/ultraq/redhorizon/explorer/mixdata/ra-config.csv @@ -0,0 +1,3 @@ +ID,Name +0xb1c3b238,rules.ini +0x1f223123,tutorial.ini diff --git a/redhorizon-explorer/source/nz/net/ultraq/redhorizon/explorer/objects/GridLines.groovy b/redhorizon-explorer/source/nz/net/ultraq/redhorizon/explorer/objects/GridLines.groovy index fe4ac370..28dba649 100644 --- a/redhorizon-explorer/source/nz/net/ultraq/redhorizon/explorer/objects/GridLines.groovy +++ b/redhorizon-explorer/source/nz/net/ultraq/redhorizon/explorer/objects/GridLines.groovy @@ -31,8 +31,8 @@ import org.joml.Vector2f */ class GridLines extends Node { - private static final int COORD_MIN = -2400 - private static final int COORD_MAX = 2400 + private static final int COORD_MIN = -1536 // The max area a Red Alert map can be + private static final int COORD_MAX = 1536 GridLines() { diff --git a/redhorizon-explorer/source/nz/net/ultraq/redhorizon/explorer/objects/Map.groovy b/redhorizon-explorer/source/nz/net/ultraq/redhorizon/explorer/objects/Map.groovy index 898cf8f6..2f02cb43 100644 --- a/redhorizon-explorer/source/nz/net/ultraq/redhorizon/explorer/objects/Map.groovy +++ b/redhorizon-explorer/source/nz/net/ultraq/redhorizon/explorer/objects/Map.groovy @@ -17,8 +17,10 @@ package nz.net.ultraq.redhorizon.explorer.objects import nz.net.ultraq.redhorizon.classic.Faction +import nz.net.ultraq.redhorizon.classic.filetypes.IniFile import nz.net.ultraq.redhorizon.classic.filetypes.MapFile import nz.net.ultraq.redhorizon.classic.filetypes.PalFile +import nz.net.ultraq.redhorizon.classic.filetypes.RulesFile import nz.net.ultraq.redhorizon.classic.filetypes.ShpFile import nz.net.ultraq.redhorizon.classic.filetypes.TmpFileRA import nz.net.ultraq.redhorizon.classic.maps.InfantryLine @@ -80,7 +82,7 @@ class Map extends Node { private static final int TILE_HEIGHT = 24 final MapFile mapFile - final String name = "Map - ${mapFile.name}" + final String name = "Map - ${mapFile.basicSection.name()}" final Theater theater final Rectanglef boundary final Vector2f initialPosition @@ -88,6 +90,7 @@ class Map extends Node { private final Palette palette private final TileSet tileSet private final ResourceManager resourceManager + private final RulesFile rules private CompletableFuture paletteAsTextureFuture private Texture paletteAsTexture @@ -115,7 +118,7 @@ class Map extends Node { var waypoints = mapFile.waypointsData var waypoint98 = waypoints[98] - initialPosition = waypoint98.asCellCoords().asWorldCoords() + initialPosition = waypoint98.asCellCoords().asWorldCoords(1) var halfMapWidth = (TILES_X * TILE_WIDTH) / 2 as float var halfMapHeight = (TILES_Y * TILE_HEIGHT) / 2 as float @@ -123,14 +126,18 @@ class Map extends Node { tileSet = new TileSet() + // Rules file needed for some object configuration + var rulesIni = resourceManager.loadFile('rules.ini', IniFile) + rules = rulesIni as RulesFile + addChild(new MapBackground()) addChild(new MapPack()) addChild(new OverlayPack()) addChild(new Terrain()) - addChild(new MapLines()) addChild(new Units()) addChild(new Infantry()) addChild(new Structures()) + addChild(new MapLines()) } @Override @@ -217,17 +224,17 @@ class Map extends Node { */ private class MapLines extends Node { - private static final Vector2f X_AXIS_MIN = new Vector2f(-3600, 0) - private static final Vector2f X_AXIS_MAX = new Vector2f(3600, 0) - private static final Vector2f Y_AXIS_MIN = new Vector2f(0, -3600) - private static final Vector2f Y_AXIS_MAX = new Vector2f(0, 3600) + private static final Vector2f X_AXIS_MIN = new Vector2f(-3072, 0) + private static final Vector2f X_AXIS_MAX = new Vector2f(3072, 0) + private static final Vector2f Y_AXIS_MIN = new Vector2f(0, -3072) + private static final Vector2f Y_AXIS_MAX = new Vector2f(0, 3072) MapLines() { - addChild(new Primitive(MeshType.LINES, Colour.RED.withAlpha(0.5), X_AXIS_MIN, X_AXIS_MAX, Y_AXIS_MIN, Y_AXIS_MAX).tap { + addChild(new Primitive(MeshType.LINES, Colour.RED.withAlpha(0.8), X_AXIS_MIN, X_AXIS_MAX, Y_AXIS_MIN, Y_AXIS_MAX).tap { name = "XY axis (red)" }) - addChild(new Primitive(MeshType.LINE_LOOP, Colour.YELLOW.withAlpha(0.5), boundary as Vector2f[]).tap { + addChild(new Primitive(MeshType.LINE_LOOP, Colour.YELLOW.withAlpha(0.8), boundary as Vector2f[]).tap { name = "Map boundary (yellow)" }) } @@ -509,7 +516,7 @@ class Map extends Node { var terrainData = mapFile.terrainData terrainData.each { cell, terrainType -> var terrainFile = resourceManager.loadFile(terrainType + theater.ext, ShpFile) - var cellPosXY = (cell as int).asCellCoords().asWorldCoords(terrainFile.height / TILE_HEIGHT - 1 as int) + var cellPosXY = (cell as int).asCellCoords().asWorldCoords(terrainFile.height / TILE_HEIGHT as int) tileSet.addTiles(terrainFile) // TODO: Get TileSets to work with any sized sprites, then terrain can @@ -553,7 +560,6 @@ class Map extends Node { var unitData = new JsonSlurper().parseText(unitJson) as UnitData return new Unit(unitImages, palette, unitData).tap { - configure(it, unitData) // TODO: Country to faction map @@ -562,9 +568,7 @@ class Map extends Node { case "USSR" -> Faction.RED default -> Faction.GOLD } - heading = objectLine.heading - transform.translate((objectLine.coords as Vector2f).asWorldCoords()) } } } @@ -580,6 +584,9 @@ class Map extends Node { try { var unit = createObject(unitLine) { unit, unitData -> unit.name = "Vehicle - ${unitLine.faction}, ${unitLine.type}" + unit.transform.translate((unitLine.coords as Vector2f) + .asWorldCoords(1) + .centerInCell(unit.width, unit.height)) } addChild(unit) } @@ -601,13 +608,16 @@ class Map extends Node { mapFile.infantryData.eachWithIndex { infantryLine, index -> try { var infantry = createObject(infantryLine) { infantry, infantryData -> + infantry.name = "Infantry - ${infantryLine.faction}, ${infantryLine.type}" + infantry.transform.translate((infantryLine.coords as Vector2f) + .asWorldCoords(1) + .centerInCell(infantry.width, infantry.height)) switch (infantryLine.cellPos) { case 1 -> infantry.transform.translate(-8, 8) case 2 -> infantry.transform.translate(8, 8) case 3 -> infantry.transform.translate(-8, -8) case 4 -> infantry.transform.translate(8, -8) } - infantry.name = "Infantry - ${infantryLine.faction}, ${infantryLine.type}" } addChild(infantry) } @@ -630,10 +640,14 @@ class Map extends Node { try { var structure = createObject(structureLine) { structure, structureData -> structure.name = "Structure - ${structureLine.faction}, ${structureLine.type}" + var translate = (structureLine.coords as Vector2f).asWorldCoords(Math.ceil(structure.height / TILE_HEIGHT) as int) + if (structure.width < TILE_WIDTH || structure.height < TILE_HEIGHT) { + translate.centerInCell(structure.width, structure.height) + } + structure.transform.translate(translate) - // Push down 1 cell to account for bib placement -// structure.transform.translate(0, -TILE_HEIGHT) - + // A special case for structures with secondary parts, namely the + // weapons factory for its garage door var combineWith = structureData.shpFile.parts.body.combineWith if (combineWith) { var combinedImages = resourceManager.loadFile("${combineWith}.shp", ShpFile)